pax_global_header00006660000000000000000000000064152462470750014526gustar00rootroot0000000000000052 comment=ea360281c48e265951cdcccbd3e9e6abf526fca9 statistics-release-1.9.2/000077500000000000000000000000001524624707500153475ustar00rootroot00000000000000statistics-release-1.9.2/CONTRIBUTING.md000066400000000000000000000405511524624707500176050ustar00rootroot00000000000000# Contribution Guidelines Thank you for considering a contribution to the **statistics** package! These guidelines describe how to write code that blends into the package so your submission can be reviewed and merged quickly. When in doubt, copy the conventions of an existing neighbouring file rather than introducing new ones. ## 1. License Every source file in the **statistics** package is licensed under the [GNU General Public License, version 3 or later](https://www.gnu.org/licenses/gpl-3.0.en.html) (GPLv3+) — there are no other licenses in the package. If you are submitting a new function, it must carry the following header (use the appropriate year and your own name and email): ``` ## Copyright (C) 2026 Your Name ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ``` Use the current year for a brand-new file (e.g. `2026`); when editing an existing file, extend its year into a range (e.g. `2022-2026`). ## 2. Documentation New functions should be properly documented with the embedded help text in [Texinfo](https://www.gnu.org/software/texinfo/) format. The documentation block goes **after** the License header and **before** the function body. The Texinfo manual is [here](https://www.gnu.org/software/texinfo/manual/texinfo/), but reading through existing function files should do the trick. ``` ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} anova1 (@var{x}) ## @deftypefnx {statistics} {@var{p} =} anova1 (@var{x}, @var{group}) ## ## Help text goes here. ## ## @seealso{anova2, anovan} ## @end deftypefn ``` A few conventions for the Texinfo block: - Open every block with `## -*- texinfo -*-`. - Standalone functions use `@deftypefn {statistics} ... @end deftypefn`; additional signatures use `@deftypefnx`. - Mark arguments and variables with `@var{X}`, inline code with `@code{...}`, and quoted literals or option names with `@qcode{'name'}`. - Add cross-references with `@seealso{a, b, c}` as the last line before `@end deftypefn`. - Put **two spaces after a sentence-ending period** inside the block, matching GNU Texinfo style. For **classdef classes**, the documentation is split across several blocks that use `@deftp` (define-type) rather than `@deftypefn`: - The **class** itself is documented with a `@deftp {statistics} ... @end deftp` block, placed right after the `classdef` line. - Each public **property** carries its own `@deftp {} {property} ... @end deftp` block, immediately above the property in its `properties` section. - Each public **method**, by contrast, is documented exactly like a standalone function — with a `@deftypefn {} ... @end deftypefn` block above the method definition. The scope name in the braces is the class name, and the method's error messages are prefixed `.:` (see §4). In short: **classes and properties use `@deftp`; methods use `@deftypefn`**, just as free functions do. - Keep every body line **within 80 columns**. The only lines allowed to run over are the `@deftypefn` / `@deftypefnx` header lines, whose signatures may be longer. **Note:** the Texinfo source is not printed verbatim on the command window. Type e.g. `help anova1` to render and review the documentation as the user sees it. ## 3. Demos Although examples can live in the help documentation, it is always useful to embed runnable examples as demos, which the user invokes with the `demo` command: ``` >> demo anova1 ``` Demos go at the **end of the file**, after the function and any local helper functions. A small demo looks like this: ``` %!demo %! x = meshgrid (1:6); %! x = x + normrnd (0, 1, 6, 6); %! anova1 (x, [], 'off'); ``` ### Demo comments and online documentation The online reference pages for the package are generated with [pkg-octave-doc](https://github.com/gnu-octave/pkg-octave-doc), which renders each `%!demo` block as an interleaved **notebook**: the block is split into cells so that every statement's console output and any figures appear immediately beneath the code that produced them. To make a demo read well both in the terminal (`demo funcname`) and online, its comment lines support a **small subset of Markdown** instead of Texinfo. The supported constructs are: - inline `` `code` ``, `**bold**`, `*italic*`, and `[text](url)` links; - unordered lists (lines starting with `-` or `*`) and ordered lists (lines starting with `1.`); - paragraphs, separated by a blank comment line. Underscore emphasis (`_text_`) and `#` headings are intentionally **not** supported — they would clash with identifier names and the `#` comment marker. Do **not** use Texinfo tags inside demo comments. ``` %!demo %! ## Subtract 30.92 from x to simulate a 3-parameter Weibull with %! ## `gamma = 30.92`, then plot the result. %! x = [46 64 83 105 123 150 150]; %! c = [0 0 0 0 0 0 1]; %! f = [1 1 1 1 1 1 4]; %! wblplot (x - 30.92, c, f, 0.05); ``` Consecutive statements that print nothing are merged into a single input box, so muted setup code reads as one block; a statement prints (and gets an output box) as soon as it is left unterminated by a semicolon or calls `disp`, `printf`, and the like. ## 4. BISTs (testing suite) It is **very important** that function files ship a built-in self-test (BIST) suite that checks for correct output and properly catches error conditions. BISTs go at the bottom of the file, using `%!` line prefixes. ``` %!test %! x = [1 2 1 3 2 4 3 2 4 3 2 2]; %! [h, p, stats] = chi2gof (x); %! assert_equal (h, 0); %! assert_equal (p, NaN); %! assert_equal (stats.chi2stat, 0.1205375022748029, 1e-14); %!error chi2gof () %!error chi2gof ([2, 3; 3, 4]) ``` A few points on layout and content: - **Put positive tests first, then a blank line, then the `%!error` tests.** - The package requires **Octave >= 11**, so use **`assert_equal`** for value comparisons — it gives clearer failure messages than the older `assert`. - In a `%!error` test, match the **full** error message; this line is exempt from the 80-column limit. Escape regex metacharacters such as parentheses (`\(`, `\)`). - **Only test errors that this function itself emits.** Do not re-test errors raised by core Octave functions — those are covered in core Octave. - Use `%!shared var1, var2` to declare fixtures shared across following tests. Append tests as you develop; it saves debugging time and catches marginal errors that would otherwise come back as bug reports. It is equally important to add tests when fixing a bug or extending an existing function. There is no such thing as too many tests. :metal: ## 5. Coding style The package has its own coding style. It is close in spirit to the conventions used in GNU Octave itself, but it is **not** identical — there are deliberate differences (Allman-style block terminators, a specific quoting policy, naming rules, and so on) that are spelled out below. Follow these rules for any new code, and when in doubt copy the layout of an existing neighbouring file. The overriding goal is consistency: a reader should not be able to tell which contributor wrote a given file. ### Layout and whitespace - Keep lines **within 80 columns**. Wrap long calls and strings with `...` continuation, aligning the continuation under the first argument. - Use `LF` (unix) line endings, **never** `CRLF` (windows). - **Indent with 2 spaces. Never use tabs** — anywhere, including compiled sources. - Use `##` for comments. Do **not** use `%` or `%%` as in MATLAB. (`%!` is reserved for BIST/demo blocks; see §3 and §4.) - When **calling or defining** a function, put a space after each comma and **before the opening parenthesis**. This is the single most visible Octave-vs-MATLAB tell — do not omit it: ``` x = max (sin (y + 3), 2); function out = foo (a, b) ``` - The exception is **matrix and cell constructors**, where a space before the paren would be parsed as a separator and split the element in two: ``` a = [sin(x), cos(x)]; b = {sin(x), cos(x)}; ``` - For an **indexing** expression, do **not** put a space after the identifier — this distinguishes indexing from a function call. The space after a comma is optional for simple indices but recommended for complex ones: ``` A(:,i,j) A([1:i-1; i+1:n]) ``` ### Operators and control flow - Use `!` for logical NOT (not `~`) and `!=` for not-equal (not `~=`): ``` a != 0; b(! isnan (a)) = []; ``` - Enclose `if`, `while`, `until`, and `switch` conditions in parentheses: ``` if (isvector (a)) s = sum (a); endif ``` Do **not** parenthesise the iteration counter of a `for` statement: ``` for ii = 1:numel (a) b(ii) = sum (a(:,ii)); endfor ``` - Always close a block with its **specific typed keyword** — `endif`, `endfor`, `endwhile`, `endswitch`, `endfunction`, `endclassdef`, `endproperties`, `endmethods` — never the generic `end`. (`end` still means the last index inside an indexing expression, e.g. `x(end)`.) ### Quoting - **Single quotes are the default** for every character vector and option string: `'Format'`, `'off'`, `anova2 (x, reps, 'off')`, `set (gca, 'XLim', xl)`. - **Double quotes** are used **only** for the string literals passed to the message functions `error`, `warning`, `printf`, `fprintf`, and `sprintf` — always, whether or not the string contains an escape (`\n`) or a format specifier (`%d`). Keeping these double-quoted lets apostrophes read naturally: ``` error ("anova1: X must be numeric."); % yes error ("Prediction must be 'curve' or 'observation'."); % yes error ('Prediction must be ''curve'' or ''observation''.'); % no ``` - **Cell arrays of character vectors** always use single quotes: `{'red', 'green', 'blue'}`, never `{"red", "green", "blue"}`. - In a `switch`/`case`, a single-char-vector label uses single quotes — `case 'first'`, and `case {'first', 'last'}` for several. ### Naming | Kind | Convention | Examples | |--------------------------|--------------|-----------------------------------| | Class / type | lowercase | `cvpartition`, `LinearModel`\* | | Public property | PascalCase | `Formula`, `NumObservations` | | Local variable | camelCase | `optNames`, `inputFormat` | | Loop index | `ii` (or `i`)| `for ii = 1:n` | | Object handle in methods | `this` | `function disp (this)` | | Private helper (`private/`) | `__name__.m` | `__disp__`, `__paramci__` | | Compiled oct-file (`src/`) | `__name__.cc`| `__editdist__.cc` | Double-underscore wrapping (`__foo__`) marks a function as **internal / not user-facing**. (\*Established statistics classes such as `LinearModel` keep their historical MATLAB-compatible casing; new lower-level types follow the lowercase rule.) ### Error messages Error messages follow the format **`": ."`**: - Prefix with the function name (`"anova1: ..."`) — or, inside a class method, `".: ..."` (e.g. `"LinearModel.predict: ..."`). - Start lower-case and **end with a period**. - Refer to arguments by their UPPERCASE documentation name (`"... X must be numeric."`). - Build long messages with `strcat (...)` across `...`-continued lines, aligned under the first fragment — **never** with `[...]` bracket concatenation: ``` error (strcat ("anova1: GROUP must be a vector of the same", ... " length as X.")); ``` - When forwarding a computed message, use `error ("scope: %s", errmsg)`. ### Function and file structure A function file is laid out in this order: 1. GPL header (§1). 2. Texinfo documentation block (§2). 3. The `function` line, then **input validation first**, under an `## Input validation` comment: guard `nargin` / `nargout`, then type- and shape-check each argument, erroring early. 4. The body, with `##` full-line comments marking the logical steps. 5. `endfunction`. 6. Any local helper functions. 7. The `%!demo` blocks (§3), then the `%!test` / `%!error` blocks (§4). ``` function p = anova1 (x, group, displayopt) ## Input validation if (nargin < 1) error ("anova1: too few input arguments."); endif ... ## Compute the group means ... endfunction ``` Default argument values may be set in the signature, e.g. `function [err, days] = hms2days (H, MI, S, MS = 0)`. ### classdef classes Inside a class file, order the sections as: the `classdef` line and class `@deftp` block; `properties` blocks (public first, then attributed blocks such as `properties (SetAccess = private, Hidden)`), each documented property carrying its own `@deftp` block; `methods (Hidden)` for `disp`/`display` and internals; one or more `methods (Access = public)` blocks grouped by theme; `methods (Access = private)`; `endclassdef`; and finally any local helper functions after `endclassdef`. See §2 for the `@deftp`-vs-`@deftypefn` documentation split. ### Compiled sources (`src/`) Oct-files follow the same spirit with a few C++ specifics: - GPL header in a `/* ... */` block (same text as §1). - `#include `; define functions with `DEFUN_DLD`. - **2-space indentation, no tabs**; opening brace of a free function on its own line. - `//` line comments; built via `src/Makefile`. ### Keeping metadata in sync When you add a user-facing function or class, update **`INDEX`** — add the new name under the right category heading (this drives the function index and the online docs). Do **not** touch `DESCRIPTION`: its `Version` and `Date` are bumped only at release time, by the maintainer. The package also ships a set of `doc-cache` files (one per function directory). These feed `lookfor` and let `pkg install` skip a slow regeneration step, so keep them current. Do this whenever you: - add a new function or class, or - change the docstring of a function, of a class, or of one of a class's methods or properties. Regeneration is done by the **`pkg-octave-doc`** package, version 0.8.0 or newer, which caches a class's documented members under their qualified names alongside the class itself. Build the oct-files first with `make -C src`: a compiled function's help text lives inside its `.oct` file, and a missing or stale one stops the run and asks for a build. ``` pkg load pkg-octave-doc # At the package root, for every cache below it: package_texi2cache ('-auto') # only what git reports as changed (seconds) package_texi2cache () # the whole package (slow, ~minutes) package_texi2cache ('-check') # writes nothing, reports what would change # Standing in one function directory, e.g. inst/Distribution_Functions: folder_texi2cache ('-auto') # that directory's cache alone # Standing in the directory that holds the file, for a single name: function_texi2cache ('anova1') classdef_texi2cache ('prob.NormalDistribution') ``` `-auto` is the fast path and takes the work from `git`: everything differing from `HEAD`, staged and unstaged, plus untracked files, so a function written but not yet committed still counts. `-check` writes nothing and reports what would change, which is how a tree is tested for a stale cache; since every docstring is linted as it is parsed, it is also how the package is checked for broken texinfo without touching anything. Naming a class caches the class and all of its documented members together, so a renamed or newly documented method needs the class named once and no more. **`INDEX` decides what is cached** at the package and directory scopes: only the names it lists are written, so add yours there first. The two per-name functions cache whatever you name and only warn when it is unlisted. --- **That's about it!** Keep more or less consistent with the other function files in the package and you should be fine. :smile: If anything here is unclear, open an issue or ask in your pull request — we are happy to help. statistics-release-1.9.2/COPYING000066400000000000000000001045121524624707500164050ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read .statistics-release-1.9.2/DESCRIPTION000066400000000000000000000006041524624707500170550ustar00rootroot00000000000000Name: statistics Version: 1.9.2 Date: 2026-09-03 Author: various authors Maintainer: Andreas Bertsatos Title: Statistics Description: The Statistics package for GNU Octave. Categories: Statistics Depends: octave (>= 11.1.0), datatypes(>=1.3.3) License: GPLv3+ Url: https://github.com/gnu-octave/statistics Tracker: https://github.com/gnu-octave/statistics/issues statistics-release-1.9.2/INDEX000066400000000000000000000155741524624707500161550ustar00rootroot00000000000000statistics >> Statistics Descriptive Statistics cdfcalc cl_multinom dcov ecdf geomean grpstats harmmean jackknife ksdensity mvksdensity nancov nanmax nanmean nanmedian nanmin nanstd nansum nanvar partialcorr partialcorri tabulate trimmean Data Manipulation combnk crosstab datasample dummyvar fillmissing grp2idx ismissing isoutlier multiway normalise_distribution randsample rmmissing standardizeMissing tiedrank Hypothesis Testing adtest anova anova1 anova2 anovan ansaribradley bartlett_test barttest binotest chi2gof chi2test correlation_test dwtest fishertest friedman hotelling_t2test hotelling_t2test2 jbtest kruskalwallis kstest kstest2 levene_test lillietest manova1 mcnemar_test multcompare ranksum regression_ftest regression_ttest runstest sampsizepwr signrank signtest ttest ttest2 vartest vartest2 vartestn ztest ztest2 Experimental Design ff2n fullfact parseWilkinsonFormula sigma_pts x2fx Regression CompactLinearModel CoxModel coxphfit fitcox fitglm fitglme fitlm fitlme fitlmematrix fitnlm GeneralizedLinearMixedModel GeneralizedLinearModel glmfit glmval invpred lasso lassoglm LinearFormula LinearMixedModel LinearModel logistic_regression mnrfit mnrval monotone_smooth mvregress mvregresslike nlinfit nlparci nlpredci NonLinearModel plsregress regress regress_gp ridge robustfit stepwisefit stepwiseglm stepwiselm Supervised Learning ClassificationDiscriminant ClassificationGAM ClassificationKernel ClassificationKNN ClassificationLinear ClassificationNaiveBayes ClassificationNeuralNetwork ClassificationPartitionedKernel ClassificationPartitionedLinear ClassificationPartitionedModel ClassificationSVM CompactClassificationDiscriminant CompactClassificationGAM CompactClassificationNaiveBayes CompactClassificationNeuralNetwork CompactClassificationSVM CompactRegressionGAM CompactRegressionGP CompactRegressionNeuralNetwork CompactRegressionSVM fcnnpredict fcnntrain fitcdiscr fitcgam fitckernel fitcknn fitclinear fitcnb fitcnet fitcsvm fitrgam fitrgp fitrkernel fitrlinear fitrnet fitrsvm gamboostinter gamboostpairs gamboostpredict gamboosttrain gampredict gamtrain RegressionGAM RegressionGP RegressionKernel RegressionLinear RegressionNeuralNetwork RegressionPartitionedKernel RegressionPartitionedLinear RegressionPartitionedModel RegressionSVM svmpredict svmtrain Clustering CalinskiHarabaszEvaluation cluster ClusterCriterion clusterdata cophenet DaviesBouldinEvaluation dbscan evalclusters fitgmdist GapEvaluation gmdistribution inconsistent kmeans kmedoids linkage optimalleaforder SilhouetteEvaluation spectralcluster Nearest Neighbors createns editDistance ExhaustiveSearcher hnswSearcher KDTreeSearcher knnsearch mahal pdist pdist2 rangesearch squareform Anomaly Detection iforest IsolationForest LocalOutlierFactor lof ocsvm OneClassSVM robustcov Dimensionality Reduction canoncorr cmdscale factoran mdscale nnmf pca pcacov pcares ppca princomp procrustes ReconstructionICA rica rotatefactors sparsefilt SparseFiltering tsne Model Evaluation confusionchart confusionmat ConfusionMatrixChart crossval cvpartition perfcurve rocmetrics Markov Models hmmdecode hmmestimate hmmgenerate hmmtrain hmmviterbi Random Sampling johnsrnd mhsample pearsrnd qrandn slicesample Distribution Classes paretotails prob.ProbabilityDistribution prob.BetaDistribution prob.BinomialDistribution prob.BirnbaumSaundersDistribution prob.BurrDistribution prob.ExponentialDistribution prob.ExtremeValueDistribution prob.GammaDistribution prob.GeneralizedExtremeValueDistribution prob.GeneralizedParetoDistribution prob.HalfNormalDistribution prob.InverseGaussianDistribution prob.KernelDistribution prob.LogisticDistribution prob.LoglogisticDistribution prob.LognormalDistribution prob.LoguniformDistribution prob.MultinomialDistribution prob.NakagamiDistribution prob.NegativeBinomialDistribution prob.NormalDistribution prob.PiecewiseLinearDistribution prob.PoissonDistribution prob.RayleighDistribution prob.RicianDistribution prob.StableDistribution prob.tLocationScaleDistribution prob.TriangularDistribution prob.UniformDistribution prob.WeibullDistribution Distribution Fitting betafit betalike binofit binolike bisafit bisalike burrfit burrlike copulafit evfit evlike expfit explike gamfit gamlike geofit gevfit gevlike gevfit_lmom gpfit gplike gumbelfit gumbellike hnfit hnlike invgfit invglike logifit logilike loglfit logllike lognfit lognlike nakafit nakalike nbinfit nbinlike normfit normlike poissfit poisslike raylfit rayllike ricefit ricelike stblfit stbllike tlsfit tlslike unidfit unifit wblfit wbllike Distribution Functions betacdf betainv betapdf betarnd binocdf binoinv binopdf binornd bisacdf bisainv bisapdf bisarnd burrcdf burrinv burrpdf burrrnd bvncdf bvtcdf cauchycdf cauchyinv cauchypdf cauchyrnd chi2cdf chi2inv chi2pdf chi2rnd copulacdf copulapdf copularnd evcdf evinv evpdf evrnd expcdf expinv exppdf exprnd fcdf finv fpdf frnd gamcdf gaminv gampdf gamrnd geocdf geoinv geopdf geornd gevcdf gevinv gevpdf gevrnd gpcdf gpinv gppdf gprnd gumbelcdf gumbelinv gumbelpdf gumbelrnd hncdf hninv hnpdf hnrnd hygecdf hygeinv hygepdf hygernd invgcdf invginv invgpdf invgrnd iwishpdf iwishrnd jsucdf jsupdf laplacecdf laplaceinv laplacepdf laplacernd logicdf logiinv logipdf logirnd loglcdf loglinv loglpdf loglrnd logncdf logninv lognpdf lognrnd mnpdf mnrnd mvncdf mvnpdf mvnrnd mvtcdf mvtpdf mvtrnd nakacdf nakainv nakapdf nakarnd nbincdf nbininv nbinpdf nbinrnd ncfcdf ncfinv ncfpdf ncfrnd nctcdf nctinv nctpdf nctrnd ncx2cdf ncx2inv ncx2pdf ncx2rnd normcdf norminv normpdf normrnd plcdf plinv plpdf plrnd poisscdf poissinv poisspdf poissrnd raylcdf raylinv raylpdf raylrnd ricecdf riceinv ricepdf ricernd stblcdf stblinv stblpdf stblrnd tcdf tinv tpdf trnd tlscdf tlsinv tlspdf tlsrnd tricdf triinv tripdf trirnd unidcdf unidinv unidpdf unidrnd unifcdf unifinv unifpdf unifrnd vmcdf vminv vmpdf vmrnd wblcdf wblinv wblpdf wblrnd wienrnd wishpdf wishrnd Distribution Statistics betastat binostat bisastat burrstat chi2stat copulaparam copulastat evstat expstat fstat gamstat geostat gevstat gpstat hnstat hygestat invgstat logistat loglstat lognstat nakastat nbinstat ncfstat nctstat ncx2stat normstat plstat poisstat raylstat ricestat tlsstat tristat tstat unidstat unifstat wblstat Distribution Wrappers cdf fitdist icdf makedist mle mlecov pdf random Plotting andrewsplot bar3 bar3h biplot boxplot cdfplot dendrogram ecdfhist einstein glyphplot gplotmatrix gscatter hist3 histfit manovacluster normplot parallelcoords ppplot probplot qqplot scatterhist silhouette violin wblplot I/O libsvmread libsvmwrite loadmodel Utilities cholcov logit makima probit statget statset statistics-release-1.9.2/NEWS000066400000000000000000001561531524624707500160610ustar00rootroot00000000000000 Summary of important user-visible changes for statistics 1.9.0: ------------------------------------------------------------------- Important Notice: 1) Update dependency to datatypes 1.3.3. 2) The 29 probability distribution classes have moved into the `prob` namespace. The flat class names are gone; this is a rename, not an alias. 3) The generalized additive model classes now fit boosted trees by default, as MATLAB does. Every fitted value, score and loss moves. The spline engine remains available as 'FitMethod', 'splines'. 4) `fitcnet` and `fitrnet` now default to the LBFGS solver, and `fitcnet` to rectified hidden layers and a softmax output. Every default fit returns different weights. 5) Models of the compact classes saved by an earlier version no longer load and must be re-saved from the model they were compacted from. 6) Incompatibility with the `nan` package. 7) Using `tablicious` as a drop in replacement for `datatypes` might cause issues. Breaking changes: ================= This release changes behaviour that working code may depend on. Each item below says what breaks and what to write instead. ** anova2: NaN values are refused again, as they always were and as MATLAB does. ** anovan: the returned table has nine columns where it had seven; 'Singular?' is inserted at column 4, moving 'Mean Sq.' to column 5, and 'Eta Sq.' and 'Part. Eta Sq.' are appended at 8 and 9. A model with any random factor keeps the interactions that factor appears in, so `p` and the table are longer and the table gains eight further columns; p-values change for every random effects model. An interaction is named 'X1:X2', and the random factor marker is no longer carried in the names returned in `atab`, `stats.varnames` or the expected mean square expressions. Reading the table by name from `atab(1,:)` is unaffected. ** boxplot: the quartiles are taken from `quantile` at its own default method, the one `prctile` and MATLAB use, rather than from core's `statistics`. Boxes, whisker fences, the returned statistics matrix and what counts as an outlier all move. ** ClassificationGAM, RegressionGAM: the classes fit boosted trees by default; pass 'FitMethod', 'splines' for the previous engine. Eleven spline options now raise unless that engine is selected, and a second output from `RegressionGAM.predict` raises under the default engine. The boosted classifier fits by local scoring, so every multi-predictor classification GAM fits to different numbers. A tree leaf now holds at least five observations. `Interactions` reports the K-by-2 matrix of fitted predictor pairs, not the request that was passed; use `IntMatrix` to recover the argument. `ScoreTransform` defaults to 'logit' and the raw score is the log-odds pair MATLAB reports. `RegressionGAM.fitBoosted` is no longer a public method. ** ClassificationKNN: `DistanceWeight` holds the name of the weight rather than a function handle, and now applies: nothing read it before, so 'inverse' and 'squaredinverse' changed no prediction. The documented spelling 'squaredinverse' is accepted. ** ClassificationSVM: `loss` returns different numbers, having scaled every loss by the class labels so that only the classification error was right. `margin` takes Y in the form the class labels are in, as documented, where it required the +1/-1 coding. `Alpha` is populated for every kernel and holds unsigned magnitudes, `Beta` is the primal coefficient vector for a linear kernel only, `SupportVectorLabels` is s-by-1 of +/-1, and `IsSupportVector` is logical. No classification result changes. ** cluster, clusterdata: 'MaxClust' yields exactly N clusters however the merge heights tie, and a 'Cutoff' on the inconsistency coefficient requires every node below to be under the cutoff. Both returned too few clusters. ** ClusterCriterion, CalinskiHarabaszEvaluation, DaviesBouldinEvaluation, GapEvaluation, SilhouetteEvaluation: the cluster evaluation classes are value classes, not handles, so `e2 = e1` copies; assign results back, as `e = addK (e, 4)`. `ClusterCriterion` is abstract and can no longer be constructed directly. ** CompactClassificationSVM: the class drops `IsSupportVector`, which was sized to the training set and which MATLAB's compact class does not carry. ** cvpartition: `test` and `training` return one column per requested set; given a vector of set indices they returned one column per set per pass. ** evalclusters: a matrix of clustering solutions with no `KList` is numbered by the clusters each column holds, not by column position, so criterion values and `OptimalK` change. `SilhouetteEvaluation.ClusterSilhouettes` holds the mean silhouette of each cluster rather than every observation's value. ** factoran: the function is rewritten and its output signature is now MATLAB's, `[lambda, psi, T, stats, F]`. The third output was the factor scores and is now the rotation matrix; scores moved to the fifth, so code taking the third output as scores silently receives an m-by-m matrix. The fit is maximum likelihood by default where it was principal axis factoring, which remains available as 'Extraction', 'paf'. ** fcnntrain, fcnnpredict: both take activation names in two arguments, `Activations` and `OutputLayerActivation`, rather than numeric codes in a single vector. `fcnnpredict` takes the layer weights and biases, not the model structure `fcnntrain` returns. The `Alpha` argument and field are removed. The 'elu' activation saturates at -1, as it is defined to, where its negative arm carried the leaky rectifier's 0.01 scale. ** fitcnet: 'Solver' defaults to 'lbfgs', MATLAB's solver, where it defaulted to 'sgd', so every default fit returns different weights. 'LearningRate' belongs to the epoch loop and now raises unless a solver is named, while 'GradientTolerance', 'LossTolerance' and 'StepTolerance' are now accepted by default; `TrainingHistory` and `ConvergenceInfo` carry the fields of the solver that ran. `fitrnet` is new in this release. `fitcnet` additionally defaults 'Activations' to 'relu', 'OutputLayerActivation' to 'softmax' and 'LearningRate' to 0.003. Initial weights are drawn from Octave's generator, so repeated fits no longer return an identical model and `rand ('seed', s)` governs a fit. `ConvergenceInfo` reports the value the fit ended at as a scalar and carries the series beside it as `History`. `ModelParameters` reports the fit rather than the network; read `Mdl.LayerWeights` and `Mdl.LayerBiases`. ** fitdist: the 'theta' option defaults to 0 for the generalized Pareto, as MATLAB assumes it, where it defaulted to 1; `mle` changes with it. ** fpdf: the density at x = 0 is 1 at df1 = 2 and Inf below it, where it was zero whatever the numerator degrees of freedom. ** friedman: the second output is a cell array with a header row, as MATLAB's is and as `anova1` and `anova2` already were; read `tbl{2,2}` rather than `tbl.SS(1)`. The table is displayed by default. ** gpfit: the second argument is `alpha`, the confidence level, not `theta`, a known location; `paramhat` is 1-by-2 and `paramci` 2-by-2. Fit a known location by shifting the data. Note `gpfit (x, 0.05)` was a fit at location 0.05 and is now a 95% confidence level. ** gplike: `params` is the two element vector [k, sigma] and `acov` is 2-by-2, where it was 3-by-3 with a zero row and column. ** hmmviterbi, hmmestimate: `hmmviterbi` returns the true maximum probability path, having scored a spurious transition out of the last state, and `hmmestimate` no longer counts a phantom transition out of an assumed initial state. Both return different values. ** LinearModel: a weighted fit's `LogLikelihood` carries the 0.5 * sum (log (w)) term and counts only nonzero weight observations, following R rather than MATLAB; `AIC`, `AICc`, `BIC` and `CAIC` move with it. Unweighted and robust fits are unchanged. ** LinearModel: `Formula` is a `LinearFormula` object rather than a structure or a character vector. Term order follows the variable order of the data rather than the alphabetical order of their names, and no longer depends on the order the formula names them, so coefficient names move with it. The omitted reference level of a character or string grouping column is the level the data presents first, not the alphabetically first one, so coefficients change name and meaning; a categorical column is unaffected. A model with no intercept and a categorical predictor gives the first categorical an indicator for every level by both routes, so its coefficients are the group means; this is a deliberate deviation from MATLAB, which drops the reference level. ** mle: the 'normal' and 'lognormal' distributions return the maximum likelihood estimate, `std (x, 1)`, where they returned the unbiased `std (x, 0)`. ** nbinfit: the confidence bounds are no longer clamped to the parameter space, a bound outside it being what tells the caller the normal approximation has broken down. ** negloglik: the distribution object method returns the negative log likelihood, as documented and as MATLAB returns it; it returned the log likelihood, the wrong sign. ** pca: `tsquared` is 0 where the fit has one degree of freedom or fewer, and NaN for dropped rows, returned at the full observation count. ** prob distribution objects: the 29 classes are now `prob.NormalDistribution` and so on; the flat names are gone, so `class (pd)` and `isa (pd, ...)` take the qualified name. `makedist`, `fitdist`, `cdf`, `pdf`, `icdf`, `random` and `mle` take the distribution's name and are unaffected. `DistributionName` holds the distribution's name ('Normal'), not the class name. `prob.WeibullDistribution` renames `lambda` and `k` to `A` and `B` and `prob.RayleighDistribution` renames `sigma` to `B`. `ParameterValues` is a cell for the piecewise linear and multinomial classes, `x` and `Fx` of the piecewise linear class are rows, and the 29 classes now share an abstract base, `prob.ProbabilityDistribution`, as MATLAB's do. ** proflik: the distribution object method computes a true profile likelihood, re- maximizing over the remaining parameters at each point, where it held them fixed at their fitted values and so gave wrong results for every multi-parameter distribution. The default grid is 101 values over the 98% confidence interval when the selected parameter is the only one estimated, and 21 otherwise. ** regress_gp: the prediction intervals were wrong in both branches and are now correct, and both branches return the lower bound in column 1; code reading Yint(:,1) as an upper bound must be changed. ** signrank: two differences agreeing to within eps (x) + eps (y) rank as tied and one smaller than that is dropped, as MATLAB does, so `signedrank`, `zval` and the p-value move on such data. `stats` carries `signedrank` where it carried `sign`, which belongs to `signtest`, and `zval` is empty for the exact test. ** signtest: the test decision `h` is returned as a logical, as documented and as MATLAB returns it, where it was a double. ** tabulate: the percentage column is count / total * 100 taken literally, so data with no observations gives NaN where it gave zero. ** The compact classes: `CompactClassificationSVM` drops `ModelParameters` and `IsSupportVector`, `CompactClassificationNeuralNetwork` drops six properties describing the fit, and `CompactClassificationGAM` drops seven, MATLAB's own compact classes carrying none of them. Read any of them off the model that was compacted from. A compact SVM, neural network or GAM model saved by an earlier version therefore no longer loads, and `loadmodel` raises 'invalid model'. The compact regression and Gaussian process classes are new in this release and never carried them. ** The distribution functions: the 130 *cdf, *pdf and *inv functions validate the class of their numeric arguments. Continuous distributions and every *inv accept double and single only; the discrete *cdf and *pdf additionally accept the integer types and promote to double, so the result is always a probability. Logical and character arrays are rejected. This fixes silently wrong answers: `geocdf (int32 (0:4), 0.3)` returned [0 1 1 1 1], `nbincdf` returned zeros and `binopdf` returned Inf. ** The random generators: the 39 generators and the distribution objects' `random` method treat a negative dimension as zero and return an empty array, as core Octave and MATLAB do, instead of raising. ** The supervised learner classes: `ScoreTransform` and `ResponseTransform` hold the name of the transform rather than a function handle, so `strcmp (Mdl.ScoreTransform, 'none')` works and `predict` applies the transform for you. `Prior` is a 1-by-K row in `ClassNames` order. `W` weighs each class by its prior rather than being uniform. An observation is dropped only for a missing response, so `size (X, 1)`, `numel (W)` and `NumObservations` now agree and `RowsUsed` is empty when nothing was dropped. The `Standardize` property is gone from eleven classes; test `! isempty (Mdl.Mu)` instead. `Cost` refuses anything that is not a square matrix. `BinEdges` is a cell on the cross-validated classifier, and a column cell on the GAM classes. `RowsUsed` is a logical mask on `ClassificationGAM`, `ClassificationSVM` and `RegressionGAM`, where it was a double. `edge` normalizes its 'Weights' within each class to that class's prior. The cross-validated models are read only apart from `Cost`, `Prior` and the transforms, either assignment now reaching every fold, and their transforms are applied where they used to be inert. `CrossValidatedModel` holds the learner's short name, not the class name. An observation that no fold held out gets a missing label with NaN scores, a deliberate deviation from MATLAB, which names the first class. ** tiedrank: an array is ranked along its first dimension, as MATLAB does, instead of being refused; `tieadj` carries one entry per column. ** trimmean: a non-scalar percentage is refused, as in MATLAB; it was accepted and returned an untrimmed mean behind a warning. ** ttest, ttest2: `Alpha` is validated; a negative, zero, unit or vector value was accepted, a negative one returning a NaN confidence interval together with 'do not reject'. ** unifit: the function returns MATLAB's four outputs [ahat, bhat, aci, bci], where it packed both estimates into one vector and both intervals into one matrix. X may now be a matrix, negative data is accepted, and the upper endpoint's interval was inverted. New functions: ============== ** andrewsplot: Andrews plot of multivariate data, with grouping, four standardizations and a quantile mode. ** ansaribradley: Ansari-Bradley test for equal dispersions, with an exact permutation p-value or a tie corrected normal approximation. ** biplot: biplot of principal component coefficients or factor loadings. ** ClassificationDiscriminant.cvshrink: cross-validate the discriminant over a grid of `Gamma` and optionally `Delta`. ** ClassificationDiscriminant.mahal, ClassificationDiscriminant.logp, ClassificationDiscriminant.nLinearCoeffs: squared Mahalanobis distance to each class mean, the log of the density summed over the classes, and the number of predictors kept at a regularization threshold; also on the compact class. ** ClassificationGAM.addInteractions, RegressionGAM.addInteractions: fit interaction terms onto a model that already carries its univariate ones. ** ClassificationGAM.resume, RegressionGAM.resume: add trees to a fitted model, continuing in the phase that ran last. ** ClassificationPartitionedLinear, ClassificationPartitionedKernel, RegressionPartitionedLinear, RegressionPartitionedKernel: the cross-validated counterparts of the linear and kernel learners. ** ClassificationPartitionedModel.kfoldLoss, ClassificationPartitionedModel.kfoldMargin, ClassificationPartitionedModel.kfoldEdge: the class had none of the three; `kfoldLoss` takes 'classiferror', 'classifcost', 'mincost' or a function handle, the margin based losses not being offered. ** CompactLinearModel: a fitted linear model without its training data, returned by `LinearModel`'s new `compact` method. ** CompactRegressionGAM: the compact counterpart of `RegressionGAM`. ** copulastat, copulaparam, copulafit: complete the copula family, which already shipped `copulacdf`, `copulapdf` and `copularnd`. ** coxphfit: Cox proportional hazards regression, opening survival analysis, with stratified models, the counting process form and seven residual types. ** dbscan: density-based spatial clustering of applications with noise. ** discardSupportVectors: empty the support vectors of a linear kernel model and leave `Beta` and `Bias` to predict, on the four SVM classes; the engine's own copy is collapsed, so the memory is freed. ** dwtest: Durbin-Watson test for autocorrelation in regression residuals, with an exact p-value by Imhof's method. ** ecdfhist: histogram built from the output of `ecdf`. ** fitckernel, ClassificationKernel, fitrkernel, RegressionKernel: Gaussian kernel models for large data, fitted linearly in a randomized feature space, with `resume`. ** fitclinear, ClassificationLinear, fitrlinear, RegressionLinear: linear models for data with many predictors, with ridge or lasso penalties and six solvers; a vector 'Lambda' fits one model per strength inside a single object, narrowed afterwards by `selectModels`. ** fitcnb, ClassificationNaiveBayes, CompactClassificationNaiveBayes: naive Bayes classification with the 'normal', 'kernel', 'mvmn' and 'mn' predictor distributions, mixable one per predictor. ** fitcox, CoxModel: the Cox proportional hazards model as an object, with `survival`, `hazardratio`, `coefci`, `linhyptest`, `plotSurvival` and `discardResiduals`. ** fitglm, GeneralizedLinearModel: generalized linear regression as a fitted model object over five families, the GLM counterpart of `fitlm` and `LinearModel`. A binomial response given with 'BinomialSize' is the number of successes, as MATLAB reads it, and `predict` returns the probability of success. ** fitglme, GeneralizedLinearMixedModel: generalized linear mixed effects models by penalized quasi-likelihood, with four fit methods. ** fitlme, fitlmematrix, LinearMixedModel: linear mixed effects models from a Wilkinson formula extended with random effects terms or from design matrices, by maximum likelihood or REML. ** fitnlm, NonLinearModel: nonlinear regression by iterative least squares as a fitted model object, with an error model and robust fitting. ** fitrgp, RegressionGP, CompactRegressionGP: Gaussian process regression with ten covariance functions, four explicit bases, and `postFitStatistics`. ** fitrnet, RegressionNeuralNetwork, CompactRegressionNeuralNetwork: neural network regression against the mean squared error with an identity output layer. ** fitrsvm, RegressionSVM, CompactRegressionSVM: support vector machine regression by epsilon-insensitive loss, with 'SVMtype', 'nu_svr' as an Octave extension. ** gamtrain, gampredict, gamboosttrain, gamboostpredict, gamboostpairs, gamboostinter, __lbfgs__, __bhtsne__, __knnselect__, __knnbrute__: new compiled functions: the spline and boosted-tree GAM engines, the limited-memory BFGS solver, the Barnes-Hut t-SNE summation and the nearest-neighbour kernels. They are not meant to be called directly. ** glyphplot: star glyph plot of multivariate data; Chernoff faces are not supported. ** gplotmatrix: matrix of grouped scatter plots, with grouped histograms on the diagonal. ** hmmdecode: posterior state probabilities of a hidden Markov model by a scaled forward-backward recursion. ** hmmtrain: maximum likelihood estimation of hidden Markov model parameters by Baum- Welch or Viterbi training. Baum-Welch is verified bit-for-bit against MATLAB; Viterbi training carries two documented deviations. ** iforest, IsolationForest: isolation forest anomaly detection, with an `isanomaly` method. ** invpred: inverse prediction from a simple linear regression, with Fieller confidence bounds that need not be finite. ** jbtest: Jarque-Bera hypothesis test of composite normality. ** johnsrnd: random arrays from the Johnson system of distributions. ** KernelDistribution: nonparametric kernel smoothing distribution object, created by `fitdist (x, 'Kernel')`. ** kfoldfun: apply a function across the folds of a cross-validated model, on `ClassificationPartitionedModel` and `RegressionPartitionedModel`. ** kmedoids: k-medoids clustering, with the 'pam' and 'small' algorithms. ** ksdensity: kernel smoothing density estimate, with four kernels, five output functions and bounded support; 'Censoring' is not yet implemented. ** lasso, lassoglm: lasso and elastic net regularized regression by cyclic coordinate descent, for least squares and for the five GLM families, with cross- validation and the 1-SE rule. ** lillietest: Lilliefors composite goodness of fit test for the normal, exponential and extreme value families. ** LinearFormula: model formula object, the class of the `Formula` property of a `LinearModel` and of a `GeneralizedLinearModel`. ** LinearModel.plot, LinearModel.plotInteraction, LinearModel.anova, LinearModel.step, LinearModel.compact: five new methods. ** lof, LocalOutlierFactor: local outlier factor anomaly detection, with an `isanomaly` method. ** mdscale: nonclassical metric and nonmetric multidimensional scaling over five stress criteria. ** mlecov: asymptotic covariance matrix of maximum likelihood estimators, from the observed Fisher information. ** mnrval: predict category probabilities and confidence bounds from a multinomial logistic regression model, the companion to `mnrfit`. ** mvksdensity: multivariate product-kernel smoothing density estimate. ** mvregress, mvregresslike: multivariate linear regression by maximum likelihood, with three algorithms for missing responses. ** nanvar, nanstd, nanmedian, nancov: complete the nan* family alongside the existing `nanmean`, `nansum`, `nanmax` and `nanmin`. ** nlinfit, nlparci, nlpredci: Levenberg-Marquardt nonlinear least squares with a numeric Jacobian, and confidence and prediction intervals for its coefficients and predictions. ** nnmf: nonnegative matrix factorization, by alternating least squares or multiplicative updates. ** ocsvm, OneClassSVM: one-class support vector machine anomaly detection, with an `isanomaly` method. ** parallelcoords: parallel coordinates plot of multivariate data. ** paretotails: piecewise distribution object with generalized Pareto tails and an empirical middle; the 'kernel' middle segment is not supported. ** partialcorr, partialcorri: linear and rank partial correlation coefficients, with p-values. ** pearsrnd: random arrays from the Pearson system of distributions. ** perfcurve: ROC and other classifier performance curves, returning the curve, thresholds, AUC and optimal operating point, with bootstrap bounds. ** ppca: probabilistic principal component analysis, handling missing data by expectation-maximization. ** probplot: probability plot of a sample against eight reference distributions, with censoring and frequency weights. ** RegressionPartitionedModel: the regression counterpart of `ClassificationPartitionedModel`, returned by `crossval` on a `RegressionGAM`, `RegressionGP`, `RegressionNeuralNetwork` or `RegressionSVM`. ** rica, ReconstructionICA: reconstruction independent component analysis for feature extraction, with a `transform` method. `Mu` and `Sigma` are columns and `FitInfo` carries the whole minimisation history, as in MATLAB. ** robustcov: robust multivariate covariance and mean estimate, with the 'fmcd' and 'ogk' methods; 'olivehawkins' is not implemented. ** robustfit: robust M-estimator linear regression by iteratively reweighted least squares, with nine weight functions. ** rocmetrics: receiver operating characteristic metrics object, with per-class AUC and the `addMetrics`, `average` and `plot` methods. ** rotatefactors: rotate a factor loading matrix by the orthomax family, promax, or procrustes rotation, the last defaulting to 'Type', 'oblique' as in MATLAB. ** scatterhist: scatter plot with marginal histograms or kernel densities. As in MATLAB, the marginal bars point toward the scatter by default and 'Location' names the corner the histograms occupy, not the scatter. ** sparsefilt, SparseFiltering: sparse filtering for feature extraction, with a `transform` method. ** spectralcluster: spectral clustering using the eigenvectors of a similarity graph Laplacian. ** StableDistribution, stblpdf, stblcdf, stblinv, stblrnd, stblfit, stbllike: the stable distribution in the Nolan S0 parameterization, by numerical inversion of the characteristic function; `makedist ('Stable')` was previously an unsupported stub. ** statset, statget: the options structure pair used across the package's iterative algorithms; seven functions documented an 'Options' argument and nothing could build one. ** stepwiseglm: fit a `GeneralizedLinearModel` by stepwise term selection, with the selection trace in its `Steps` property. ** stepwiselm: stepwise linear regression over five criteria, with term hierarchy enforced on both addition and removal. ** The classification margin and edge surface: fifteen new methods complete it: every full classifier now has `margin`, `edge`, `resubPredict`, `resubMargin`, `resubEdge` and `resubLoss`, and every compact classifier has `margin` and `edge`. A compact classifier gets no resubstitution method, as in MATLAB. ** tsne: t-distributed stochastic neighbor embedding, with the 'exact' and 'barneshut' algorithms. Improvements: ============= ** anova: the `anova1`, `anova2` and `anovan` backends all run silently, so fitting never prints a table or opens a figure. `varianceComponent` no longer returns an interval for a negative variance estimate, and a saturated model reports its error term as zero on zero degrees of freedom rather than producing no F-ratios at all. ** anova1: `p` and `F` are NaN for a design with no variation at all and `p` is 0 where the fit is exact, both having been the wrong way round. A categorical level that no observation uses leaves every field of `stats`, so they can be indexed together; this is a deliberate deviation from MATLAB, whose own fields disagree in length there. The `vartype` argument is documented as an Octave extension. ** anovan: the table reports a 'Singular?' flag and the degrees of freedom that can actually be estimated for each term. A design that is not of full column rank now takes the minimum-norm least squares solution, where the coefficients came from a triangular solve of a singular system and the sums of squares were nonsense. A continuous factor raised to a power is squared from the raw predictor rather than the centred one, which could put Type II and Type III out by orders of magnitude. `stats.termcols` counts one column per level, and 'sstype' accepts 'H' beside 'h'. ** boxplot: a single observation, an entirely missing variable and an empty input are plotted rather than failing inside the function, integer observations are accepted, and a grouped plot with a one-observation group no longer fails. ** canoncorr: rank deficient input was solved against a singular triangular factor, so the coefficients ran away and the canonical correlations came back wrong with nothing said. The factorisation now pivots, deficient columns are dropped, and a warning is raised as MATLAB raises one. ** ClassificationDiscriminant, CompactClassificationDiscriminant: all six discriminant types are available where only 'linear' was. `DiscrimType`, `Gamma` and `Delta` may be assigned after fitting, re- deriving `Sigma`, `LogDetSigma` and `Coeffs` without refitting, and `Delta` now eliminates predictors, the new `DeltaPredictor` reporting the value at which each drops out. `predict` applies the `ScoreTransform` the model holds, where it ignored it entirely. Assigning `Prior` or `Cost` no longer destroys the model, `LogDetSigma` is accurate on nearly collinear predictors, and the class can be loaded at all, `load_model` having raised 'invalid model' whatever the model held. ** ClassificationGAM, CompactClassificationGAM: 'Interactions' given as 'all' or as a count built no interaction term at all. Assigning `Cost` was refused on every model. `ClassNames` keeps the type of Y, where it was a cell of character vectors whatever the response was. A numeric response coded 1 and 2 gave a NaN intercept and every score with it. `predict` applies the `ScoreTransform`, which was documented, settable and read by nothing. ** ClassificationKNN: `NumNeighbors`, `Distance`, `DistanceWeight`, `BreakTies`, `IncludeTies` and `DistParameter` may be assigned after fitting and are live. `predict` applies the score transform once rather than once per query row, and its third output honours the cost matrix where it returned 1 - posterior. A model fitted with any metric a kd-tree cannot search could not be reloaded; eight of the twelve metrics were affected. 'CacheSize' is carried for compatibility. ** ClassificationNeuralNetwork: the trained parameters are reachable through `LayerWeights` and `LayerBiases`; before this a network could be trained with no way to see what it had learned. `loss` accepts every loss MATLAB documents for the class and defaults to 'mincost'. A saved model came back carrying another model's numbers, `savemodel` having written 21 of 29 properties into a placeholder object. ** ClassificationPartitionedModel: an explicit `Prior` reached no fold on the discriminant, SVM and neural network backings, and a non-default `Cost` reached no fold on the SVM backing, which enters it into the box constraint. A standardized `ClassificationKNN` refitted every fold on the raw scale. `kfoldPredict` discarded the predictions of a 'Holdout' partition, raised for a GAM backing and refused a cost for a network backing. The class gains `W` and a `disp` of its own, and takes a `Prior` on a naive Bayes backing. ** ClassificationSVM, CompactClassificationSVM: `predict` and `resubPredict` return the expected cost as a third output, and `kfoldPredict` does for an SVM-backed cross-validated model, where it raised. MATLAB returns the cost matrix read transposed; we return the row, which is documented. `resubLoss` returned the wrong number or none, and `resubPredict` selected the training rows with a bare mask, so a three-predictor model was asked about a single column. The classes report `Prior`, `Cost`, `W`, `CategoricalPredictors` and `ExpandedPredictorNames`. ** crossval: the partition covers only the observations actually trained on; with any missing value present each fold trained on rows its own fit then discarded. A function handle over a predictor and a response is no longer rejected, the last data variable having been dropped, which is why `plsregress`'s 'CV' never worked. ** cvpartition: 'Stratify' and 'GroupingVariables' can be combined, and 'GroupingVariables' applies to 'Holdout' and 'Leaveout' as well as 'KFold', where all three accepted it and silently ignored it, giving the caller the leakage the option exists to prevent. A scalar SVAL to `repartition` never worked, and seeding is now confined to the call. ** evalclusters: the returned object has MATLAB's shape and reports MATLAB's names. The silhouette criterion honours the 'Distance' it is given, where it always used 'sqeuclidean' and every metric returned the same numbers. ** ExhaustiveSearcher, KDTreeSearcher, hnswSearcher, knnsearch, rangesearch: the exhaustive search no longer forms the whole distance matrix for the four Minkowski-family metrics, and the searches take their K smallest by partial selection: a 4000-point self-search at K = 5 falls from 4.5 s to 1.9 s and searches that could not allocate now run. The kd-tree search is about five times faster, resolving the metric once per query. `knnsearch` and `rangesearch` accept a per-call metric. Asking for more neighbours than the data holds is answered with all of them. Single data stays single and every other class converts to double, which fixes integer data silently rounding each coordinate difference. Raising `SearchSetSize` on `hnswSearcher` returned the same neighbour for every query past a threshold. ** fcnntrain, fcnnpredict: every network the package trained descended the wrong gradient: the mean squared error layer differentiated sum (y - t)^2 as 2y - t rather than 2 (y - t), so training settled at 2y = t and the network converged on half its target. The backward pass of a hidden activation layer recovered the incoming gradient by dividing by its own input, read past the end of the array for any widening network, and applied the wrong local derivative for six of the eight activations. The `Accuracy` and `Loss` vectors described no network that ever existed and were then stored behind as many zeros as the epoch count. Training visited the samples in a fixed order, so with sorted labels the weights swung between the classes instead of settling. Weights are drawn on a range set by the fan-in rather than always on [-1, 1]. ** fitcgam, fitrgam: nine new boosted-tree options matching MATLAB in name and default, and an interaction asked for by count or 'all' is chosen by testing each candidate pair against the predictor phase's residuals and ranking by p-value, with 'MaxPValue' setting the cut. The GAM classes report `FitMethod`, `TreeModel`, `ReasonForTermination`, `BinEdges` and `PairDetectionBinEdges`, and `ModelParameters` reports MATLAB's thirteen fields under the boosted-tree engine. ** fitcnet: 'Prior' and 'Cost' are accepted, having been rejected as unknown parameters, so there had been no way to weigh the classes at all. 'Activations' accepts 'none', which the documentation listed and the code rejected. A model fitted with 'Standardize' set to true was trained on the raw predictors while `predict` standardized its own, so resubstitution accuracy on a badly scaled problem was 0.275 and is now 0.925. ** fitcsvm: a model fitted with 'Standardize' set to true was trained on the raw predictors while `predict` standardized its own, so resubstitution accuracy on a badly scaled problem was 0.500, chance, and is now 0.912. ** glmfit: the returned coefficient vector was clobbered by the binomial Anscombe residual computation, returning garbage coefficients whenever the `stats` output was requested and taking `glmval`'s confidence intervals with it. ** glmval: accept a numeric 0 or 1 for the 'simultaneous' option and 'BinomialSize' as an alias for 'size'. ** gpfit: the boundary warnings say which boundary was reached and that the confidence intervals are withheld. NaN and Inf observations propagate into the estimates rather than being dropped with a warning. ** hygecdf, poisstat, wblpdf, ClassificationNeuralNetwork: error messages and help text corrected: `hygecdf` named its third argument `k` where `hygepdf` names it `M`, `poisstat` named SIGMA, `wblpdf`'s three signature lines named `wblinv`, and the `Solver` property read 'Gradient Descend'. ** LinearModel, fitlm: a robust fit reported the wrong `LogLikelihood` and information criteria, and a robust fit given weights scored the wrong residuals, so a precisely measured observation was less likely to be downweighted. A variable the model does not use is no longer counted as one of its predictors. `Steps` is populated by `stepwiselm` and `step`, where it was always empty. `removeTerms` could not name a term on a table model whose formula uses only some of the table's variables, `step` refused any model holding a power term, and `stepwiselm` silently dropped one from a starting model or bound. Thresholds letting a term enter and leave at once are refused, where the search never returned. `VariableNames` and `PredictorNames` are column cell arrays. ** mle: the custom distribution interface is implemented, having been a stub that printed a message and silently fitted a normal. The 'bernoulli' and 'unif' families could not be fitted at all, and an empty 'Frequency' raised instead of meaning no frequencies. ** mnrfit: rewritten for MATLAB compatibility: nominal models with any number of categories, having errored for more than two, plus ordinal and hierarchical models, four links, the full `stats` structure and the `EstDisp` option. ** ModelParameters: the property reaches its final shape on every learner that has one. `ClassificationKNN` and `ClassificationDiscriminant` gain it, having been the only learners without one; the neural network pair reports the fit as it was asked for rather than the network that came out of it; `RegressionGP` reports its starting values; and the six partitioned classes report the learner's parameters under their own tags. ** optimalleaforder: a custom 'Transformation' is reachable, having been documented and implemented but refused before the code that uses it ran, and a two-leaf tree no longer crashes. ** paramci, proflik: `paramci` reports one column per parameter on `BinomialDistribution`, holding the fixed N at its own value, which also unblocks `proflik` there, and a fixed parameter is addressable by name again on every distribution. `proflik` returns a third output, defaults `pnum` to the first free parameter, keeps the profiled-out parameters inside their own range, and refuses a parameter with no confidence interval. On `BurrDistribution` it profiled a censored likelihood, the frequencies having landed in the censoring slot. ** pca: three defects in the handling of missing data and weights, contributed by Swayam Shah (GitHub PR #451). With 'Rows' set to 'all' the guard that refuses NaN input fired only when every column held a NaN. ** pdist, pdist2: no longer return a negative distance: 'cosine', 'correlation' and 'spearman' put a row at -2.2e-16 from itself. ** prob.PiecewiseLinearDistribution: `mean`, `var` and `std` of a truncated distribution are computed exactly segment by segment, where quadrature integrated across the density's jumps and lost four to five digits. ** randsample: added to the package INDEX, so the function is listed in `pkg describe` and the online documentation; it shipped but was undiscoverable there. ** regress: the coefficient standard errors and confidence intervals are computed from the QR factor directly rather than by inverting R' * R, which squared the condition number of the design. On the Longley benchmark they now match the NIST certified values to 6e-13 relative, previously 2e-8, with no spurious singularity warning. ** regression_ftest, regression_ttest: `regression_ttest` had no working call form, `cov` of two vectors returning a matrix, and its fitted values were evaluated at mean (x) instead of at x, so the residual sum of squares was really the total. `regression_ftest` refused the commonest test of all, dropping a single predictor. Positive tests have been added to both. ** RegressionGAM: `predict` computes ySD and yInt from the model's own terms rather than the stored predictors, so a model built with 'Interactions' or a 'Formula' no longer reports a standard deviation several times too large, or raises. A single Inf passed validation, and a scalar 'Knots', 'Order' or 'DoF' was never expanded. `savemodel` wrote a text file where every other class writes a binary one. ** RegressionGAM, ClassificationGAM: the spline fitting is compiled and each predictor's design is factorised once for the whole fit: fitting a classifier is four times faster and cross-validating one between five and nine times. A rank deficient design no longer divides by a singular value that is rounding noise. ** sampsizepwr: the 'r' test type for the correlation coefficient was unreachable by any route, and the sample size came from Fisher's transformation of the difference between the two correlations, which is right only when the null correlation is zero, the single value the function refused. ** slicesample: a log density supplied through 'logpdf' was logged a second time, so the chain sampled an entirely different distribution without complaint; drawing from a standard normal returned values with a mean around -8000. Three missing input checks are added. ** svmpredict: scoring a single observation corrupted the heap and aborted the interpreter; two or more rows were fine. The result pointers referred to a temporary that was freed before the prediction loop wrote to it. ** The copula family: `copulapdf` gains the Gaussian and Student's t families and `copularnd` the Frank and Gumbel-Hougaard families, so every family MATLAB supports now works in every copula function; only Clayton previously worked in all five. The Ali-Mikhail-Haq and Farlie-Gumbel-Morgenstern Octave extensions work throughout as well. ** The distribution name wrappers: `cdf`, `pdf`, `icdf`, `random`, `makedist`, `fitdist` and `mle` accept the same set of distribution names, matched ignoring case, spaces and hyphens; they previously accepted disjoint spellings, so `makedist ('Extreme Value')` and `cdf ('ExtremeValue', ...)` were both errors while their opposites worked. ** The distribution objects: `prob.KernelDistribution` hides `NumParameters`, `ParameterNames` and `ParameterDescription`, MATLAB carrying none of the three, and `prob.StableDistribution` no longer lists `ParameterCI`; all remain readable by name. The classes list `Truncation` and `IsTruncated` where MATLAB lists them. `MultinomialDistribution.random` and `LoguniformDistribution.random` were broken in every call form, and `KernelDistribution`'s `pdf`, `cdf` and `icdf` errored on an empty query. ** The learner classes: each reaches the property and method surface MATLAB gives it. `Intercept`, `CategoricalPredictors`, `ExpandedPredictorNames`, `W`, `BetweenSigma`, `KernelParameters`, `BoxConstraints`, `OutlierFraction`, `Nu` and `BinEdges` are added where each was missing, and the ten classes with a MATLAB counterpart declare `HyperparameterOptimizationResults`, read-only and always empty. Every classification learner accepts a response given as a character matrix, which the documentation always listed and which had never worked. Every classifier with a settable `Cost` validates the matrix as MATLAB does and accepts a struct of `ClassNames` and `ClassificationCosts`; `Prior` accepts the matching struct form. The seventeen supervised classes enforce access with property attributes rather than hand-written `subsref` and `subsasgn` whitelists. Setting `ScoreTransform` to 'none' or 'identity' made `predict` raise on four classes, and assigning it raised on five others, so the property could not be set at all. 'ismax' and 'symmetricismax' mark the largest score of each observation, where the maximum was taken down the column. ** The paired argument parser: `crossval`, `grpstats`, `rmmissing`, `stepwisefit` and `scatterhist` parse Name-Value arguments through `parsePairedArguments` from the `datatypes` package, and the statistics package's own private `pairedArgs` is gone. ** tiedrank: accepts MATLAB's fourth argument, a tolerance, which defaults to exact comparison so every existing call is unaffected. ** violin: a row vector is read as one variable, a cell of row vectors works, integer and logical observations are accepted, and a single observation, an empty input and a call with no arguments report what is wrong instead of failing inside the function. ** wblplot: a non-finite observation is rejected; `wblplot ([1, Inf, 2, 3])` never returned, creating nine graphics objects per iteration for as long as it was left alone. Removed functions: ================== ** mvtcdfqmc: the quasi-Monte-Carlo integrator behind `mvtcdf` and `mvncdf` is now the private helper `__mvtcdfqmc__` and can no longer be called directly. MATLAB ships no such function, so nothing portable depended on it. Call `mvtcdf` or `mvncdf` instead. Summary of important user-visible changes for statistics 1.9.1: ------------------------------------------------------------------- Improvements: ============= ** ClassificationKNN: a custom distance function handle works again. The constructor probed the handle against the response instead of the predictors and sized the result against the number of response rows, so a correctly written `distfun` was rejected and no custom metric could be used at all. A handle assigned to `Distance` after fitting was accepted without being probed and failed later inside `predict`, and `disp` could not print one. The `D2 = distfun (ZI, ZJ)` signature is now documented in the property's help text. ** documentation: `doc/statistics.qch` is rebuilt with pkg-octave-doc 0.7.7. Double- clicking the package in the GUI Documentation tab opens an overview listing every documented name under its category, rather than whichever category came first, and the one formula that reached the page as raw TeX now renders as the plain text form of its docstring. The online pages for `NonLinearModel` and `gmdistribution` were published as plain function pages, listing none of their properties or methods, because a member documented outside texinfo made the class renderer fail and fall back silently; both are rendered as classes again, restoring 105 and 72 documented members. Summary of important user-visible changes for statistics 1.9.2: ------------------------------------------------------------------- New functions and methods: ========================== ** rica, ReconstructionICA: a `'NonGaussianityIndicator'` name-value pair, and the read-only property of the same name, giving one sign per learned feature: `+1` seeks a super-Gaussian feature and `-1` a sub-Gaussian one. It sets the sign that feature's contrast term carries in the objective, so it moves the fit. The default is all `+1`, which is the fit as it stood. Improvements: ============= ** binoinv: an exactly attained probability is reached rather than stepped past, so the median of a symmetric binomial is right: `binoinv (0.5, 5, 0.5)` returned 3 and `binoinv (0.5, 2001, 0.5)` returned 1001. A `p` of 1 returns `n` rather than wherever the CDF first saturates, which was 206 for 500 trials at 0.25. An answer above 500 no longer errors on a row vector, on a vector `n`, or with a NaN among the probabilities. ** prob.BinomialDistribution: `median` and `icdf` at 0.5 follow the `binoinv` fix, so `median (prob.BinomialDistribution (5, 0.5))` is 2 rather than 3 on platforms whose `betainc` is less accurate. ** nbinpdf: a density is returned wherever one exists. The three factors of the formula left the range of a double before their product did, so `nbinpdf (1000, 1001, 0.5)` was `Inf` where the density peaks at 0.0089, and `NaN` past the mode. ** nbincdf: follows the `nbinpdf` fix. The lower tail sums the density, so it read 1 from 308 failures on for 1001 successes, where the value is 4.2e-39, `NaN` past the mode, and was not monotone. ** nbininv: an exactly attained probability is reached rather than stepped past. For `r` successes at a probability of 0.5 the median is exactly `r-1`, and `nbininv (0.5, r, 0.5)` returned `r` for 188 of the 399 values of `r` from 2 to 400, `nbininv (0.5, 101, 0.5)` among them. ** prob.NegativeBinomialDistribution: `median` and `icdf` follow the `nbininv` fix, so `median (prob.NegativeBinomialDistribution (101, 0.5))` is 100 rather than 101. ** documentation: `disp` and `display` are hidden on every class that defines one, which was already true of 141 of the 147 and is now true of all of them. They no longer appear in `methods (CLASS)` or in the online pages for `anova`, `paretotails`, `NonLinearModel`, `ConfusionMatrixChart`, `gmdistribution` and `rocmetrics`. Calling them is unaffected, and `NonLinearModel`'s `subsref` is hidden with them. `gmdistribution`'s methods are documented: `cdf`, `cluster`, `mahal`, `pdf`, `posterior`, `random` and the static `fit` carried no help text at all and now answer `help gmdistribution.METHOD`. `help` headings name the package rather than a category that does not exist: `copularnd`, `createns` and `normplot` were headed `Function File`, and `loadmodel` was headed `ClassificationSVM`, a class it is not part of. `NonLinearModel` and `LinearMixedModel` document their properties: all 23 and all 17 carried no help text at all and now answer `help CLASS.PROPERTY`. `NonLinearModel`'s `ErrorModelInfo` is hidden, MATLAB not carrying it in its public interface, so `properties ('NonLinearModel')` lists 23 names rather than 24; reading `MDL.ErrorModelInfo` is unaffected. `CoxModel`, `GeneralizedLinearMixedModel` and `gmdistribution` document their properties: all 16, all 14 and all 14 carried no help text and now answer `help CLASS.PROPERTY`, and `gmdistribution`'s constructor is documented. `gmdistribution`'s `NlogL` is hidden, MATLAB hiding the same alias, so `properties ('gmdistribution')` lists 14 names rather than 15; reading `OBJ.NlogL` is unaffected and `NegativeLogLikelihood` carries the same value. `ConfusionMatrixChart`'s fifteen property set methods move into a hidden block, so `set.XLabel` and its fourteen siblings no longer appear as methods in the online pages. Assigning the properties is unaffected. `ReconstructionICA` and `SparseFiltering` document their properties: the same eight on each carried no help text and now answer `help CLASS.PROPERTY`. `FitInfo` states that the fitting trajectory is this implementation's own, so its length and its iteration counts differ from MATLAB's under either solver. Six more property set methods move into hidden blocks, in `ClassificationKernel`, `ClassificationLinear`, `RegressionKernel`, `RegressionLinear`, `RegressionGP` and `CompactRegressionGP`, so `set.ScoreTransform` and `set.ResponseTransform` no longer appear as methods in the online pages. Assigning the properties is unaffected. `RegressionGP` and `RegressionGAM` document their constructors, and every compact class answers `help CLASS.CLASS` with real text where eight of the ten had returned a one-word source comment. All ten hide the constructor, as MATLAB does, so `methods` no longer lists it; `compact` is named in every one of their class descriptions as the way to create one. Every demo that draws random numbers seeds each generator stream it uses, so `demo` gives the same result twice and a rebuild of the online pages diffs only where something really changed. Octave seeds `rand`/`randn`, `randg`, `rande` and `randp` separately, and the legacy `('seed', N)` form selects a different generator, so many demos that looked seeded were not. The 29 `+prob` distribution class constructors answer `help`: each carried no help text at all and now gives its call signature, names its parameters and states their defaults. They stay hidden, `makedist` being the usual way to create a distribution object. ** johnsrnd: `quantiles` may be given as a 2-by-4 matrix, the first row four strictly increasing, evenly spaced standard normal quantiles and the second row the data quantiles at them; a four-element vector still means the fixed `[-1.5, -0.5, 0.5, 1.5]` points. The `SL` family returned a curve of the wrong sign whenever `delta` was negative, `lambda` having been fixed at 1 rather than taken as `sign (delta)`. A `NaN` among the quantiles is refused instead of being carried into the fit. ** fitclinear, fitrlinear: a fit that stops because the line search cannot improve the objective says so, reporting `TerminationCode` -11 and `'Unable to find a step decreasing the objective.'` as MATLAB does. Every exit but three was reported as `'Iteration limit exceeded.'`, so a fit that gave up after 59 iterations of a limit of 1000 claimed to have run out of iterations. ** partialcorr: a coefficient is NaN where the controlling variables explain either of the two variables completely, as MATLAB returns. The residual variance was tested against exact zero, which floating-point arithmetic does not produce, so the coefficient was computed from rounding error and was not reproducible between builds: `partialcorr ([1 2; 3 4; 5 5], [2; 4; 6])` gave an off-diagonal of -0.13 here and -0.089 elsewhere, where MATLAB gives NaN. ** lasso: `'Standardize'` is forced to false, with a warning, when `'Intercept'` is false. The two were accepted together and the predictors were standardised anyway. ** mixed effects models: `fitlme`, `fitlmematrix`, `fitglme`, `LinearMixedModel` and `GeneralizedLinearMixedModel` fit markedly more accurately. Both profiled deviances are now optimised with their closed form gradients rather than by finite differences, every matrix is symmetrised before it is factored, and the fitting path forms no explicit inverse. On a balanced one-way fixture, where REML must equal the ANOVA estimate exactly, the variance component was 6.9e-07 out and is now 1.4e-09. statistics-release-1.9.2/ONEWS-1.1.x000066400000000000000000000051151524624707500167320ustar00rootroot00000000000000Summary of important user-visible changes for statistics 1.1.3: ------------------------------------------------------------------- ** The following functions are new in 1.1.3: copularnd mvtrnd ** The functions mnpdf and mnrnd are now also usable for greater numbers of categories for which the rows do not exactly sum to 1. Summary of important user-visible changes for statistics 1.1.2: ------------------------------------------------------------------- ** The following functions are new in 1.1.2: mnpdf mnrnd ** The package is now dependent on the io package (version 1.0.18 or later) since the functions that it depended of from miscellaneous package have been moved to io. ** The function `kmeans' now accepts the 'emptyaction' property with the 'singleton' value. This allows for the kmeans algorithm to handle empty cluster better. It also throws an error if the user does not request an empty cluster handling, and there is an empty cluster. Plus, the returned items are now a closer match to Matlab. Summary of important user-visible changes for statistics 1.1.1: ------------------------------------------------------------------- ** The following functions are new in 1.1.1: monotone_smooth kmeans jackknife ** Bug fixes on the functions: normalise_distribution combnk repanova ** The following functions were removed since equivalents are now part of GNU octave core: zscore ** boxplot.m now returns a structure with handles to the plot elemenets. Summary of important user-visible changes for statistics 1.1.0: ------------------------------------------------------------------- ** IMPORTANT note about `fstat' shadowing core library function: GNU octave's 3.2 release added a new function `fstat' to return information of a file. Statistics' `fstat' computes F mean and variance. Since MatLab's `fstat' is the equivalent to statistics' `fstat' (not to core's `fstat'), and to avoid problems with the statistics package, `fstat' has been deprecated in octave 3.4 and will be removed in Octave 3.8. In the mean time, please ignore this warning when installing the package. ** The following functions are new in 1.1.0: normalise_distribution repanova combnk ** The following functions were removed since equivalents are now part of GNU octave core: prctile ** The __tbl_delim__ function is now private. ** The function `boxplot' now accepts named arguments. ** Bug fixes on the functions: harmmean nanmax nanmin regress ** Small improvements on help text. statistics-release-1.9.2/ONEWS-1.2.x000066400000000000000000000047501524624707500167370ustar00rootroot00000000000000Summary of important user-visible changes for statistics 1.2.4: ------------------------------------------------------------------- ** Made princomp work with nargout < 2. ** Renamed dendogram to dendrogram. ** Added isempty check to kmeans. ** Transposed output of hist3. ** Converted calculation in hmmviterbi to log space. ** Bug fixes for stepwisefit wishrnd. ** Rewrite of cmdscale for improved compatibility. ** Fix in squareform for improved compatibility. ** New cvpartition class, with methods: display repartition test training ** New sample data file fisheriris.txt for tests ** The following functions are new: cdf crossval dcov pdist2 qrandn randsample signtest ttest ttest2 vartest vartest2 ztest Summary of important user-visible changes for statistics 1.2.3: ------------------------------------------------------------------- ** Made sure that output of nanstd is real. ** Fixed second output of nanmax and nanmin. ** Corrected handle for outliers in boxplot. ** Bug fix and enhanced functionality for mvnrnd. ** The following functions are new: wishrnd iwishrnd wishpdf iwishpdf cmdscale Summary of important user-visible changes for statistics 1.2.2: ------------------------------------------------------------------- ** Fixed documentation of dendogram and hist3 to work with TexInfo 5. Summary of important user-visible changes for statistics 1.2.1: ------------------------------------------------------------------- ** The following functions are new: pcares pcacov runstest stepwisefit hist3 ** dendogram now returns the leaf node numbers and order that the nodes were displayed in. ** New faster implementation of princomp. Summary of important user-visible changes for statistics 1.2.0: ------------------------------------------------------------------- ** The following functions are new: regress_gp dendogram plsregress ** New functions for the generalized extreme value (GEV) distribution: gevcdf gevfit gevfit_lmom gevinv gevlike gevpdf gevrnd gevstat ** The interface of the following functions has been modified: mvnrnd ** `kmeans' has been fixed to deal with clusters that contain only one element. ** `normplot' has been fixed to avoid use of functions that have been removed from Octave core. Also, the plot produced should now display some aesthetic elements and appropriate legends. ** The help text of `mvtrnd' has been improved. ** Package is no longer autoloaded. statistics-release-1.9.2/ONEWS-1.3.0000066400000000000000000000012641524624707500166250ustar00rootroot00000000000000Summary of important user-visible changes for statistics 1.3.0: ------------------------------------------------------------------- ** The following functions are new: bbscdf bbsinv bbspdf bbsrnd binotest burrcdf burrinv burrpdf burrrnd gpcdf gpinv gppdf gprnd grp2idx mahal mvtpdf nakacdf nakainv nakapdf nakarnd pdf tricdf triinv tripdf trirnd violin ** Other functions that have been changed for smaller bugfixes, increased Matlab compatibility, or performance: betastat binostat cdf combnk gevfit hist3 kmeans linkage randsample squareform ttest statistics-release-1.9.2/ONEWS-1.4.x000066400000000000000000000141701524624707500167360ustar00rootroot00000000000000Summary of important user-visible changes for statistics 1.4.3: ------------------------------------------------------------------- New functions: ============== ** anova1 (patch #10127) kruskalwallis ** cluster (patch #10009) ** clusterdata (patch #10012) ** confusionchart (patch #9985) ** confusionmat (patch #9971) ** cophenet (patch #10040) ** datasample (patch #10050) ** evalclusters (patch #10052) ** expfit (patch #10092) explike ** gscatter (patch #10043) ** ismissing (patch #10102) ** inconsistent (patch #10008) ** mhsample.m (patch #10016) ** ncx2pdf (patch #9711) ** optimalleaforder.m (patch #10034) ** pca (patch #10104) ** rmmissing (patch #10102) ** silhouette (patch #9743) ** slicesample (patch #10019) ** wblplot (patch #8579) Improvements: ============= ** anovan.m: use double instead of toascii (bug #60514) ** binocdf: new option "upper" (bug #43721) ** boxplot: better Matlab compatibility; several Matlab-compatible plot options added (OutlierTags, Sample_IDs, BoxWidth, Widths, BoxStyle, Positions, Labels, Colors) and an Octave-specific one (CapWidhts); demos added; texinfo improved (patch #9930) ** auto MPG (carbig) sample dataset added from https://archive.ics.uci.edu/ml/datasets/Auto+MPG (patch #10045) ** crosstab.m: make n-dimensional (patch #10014) ** dendrogram.m: many improvements (patch #10036) ** fitgmdist.m: fix typo in ComponentProportion (bug #59386) ** gevfit: change orientation of results for Matlab compatibility (bug #47369) ** hygepdf: avoid overflow for certain inputs (bug #35827) ** kmeans: efficiency and compatibility tweaks (patch #10042) ** pdist: option for squared Euclidean distance (patch #10051) ** stepwisefit.m: give another option to select predictors (patch #8584) ** tricdf, triinv: fixes (bug #60113) Summary of important user-visible changes for statistics 1.4.2: ------------------------------------------------------------------- ** canoncorr: allow more variables than observations ** fitgmdist: return fitgmdist parameters (Bug #57917) ** gamfit: invert parameter per docs (Bug #57849) ** geoXXX: update docs 'number of failures (X-1)' => 'number of failures (X)' (Bug #57606) ** kolmogorov_smirnov_test.m: update function handle usage from octave6+ (Bug #57351) ** linkage.m: fix octave6+ parse error (Bug #57348) ** unifrnd: changed unifrnd(a,a) to return a 0 rather than NaN (Bug #56342) ** updates for usage of depreciated octave functions Summary of important user-visible changes for statistics 1.4.1: ------------------------------------------------------------------- ** update install scripts for octave 5.0 depreciated functions ** bug fixes to the following functions: pdist2.m: use max in distEucSq (Bug #50377) normpdf: use eps tolerance in tests (Bug #51963) fitgmdist: fix an output bug in fitgmdist t_test: Set tolerance on t_test BISTS (Bug #54557) gpXXXXX: change order of inputs to match matlab (Bug #54009) bartlett_test: df = k-1 (Bug #45894) gppdf: apply scale factor (Bug #54009) gmdistribution: updates for bug #54278, ##54279 wishrnd: Bug #55860 Summary of important user-visible changes for statistics 1.4.0: ------------------------------------------------------------------- ** The following functions are new: canoncorr fitgmdist gmdistribution sigma_pts ** The following functions have been moved from the statistics package but are conditionally installed: mad ** The following functions have been moved from octave to be conditionally installed: BASE cloglog logit prctile probit qqplot table (renamed to crosstab) DISTRIBUTIONS betacdf betainv betapdf betarnd binocdf binoinv binopdf binornd cauchy_cdf cauchy_inv cauchy_pdf cauchy_rnd chi2cdf chi2inv chi2pdf chi2rnd expcdf expinv exppdf exprnd fcdf finv fpdf frnd gamcdf gaminv gampdf gamrnd geocdf geoinv geopdf geornd hygecdf hygeinv hygepdf hygernd kolmogorov_smirnov_cdf laplace_cdf laplace_inv laplace_pdf laplace_rnd logistic_cdf logistic_inv logistic_pdf logistic_rnd logncdf logninv lognpdf lognrnd nbincdf nbininv nbinpdf nbinrnd normcdf norminv normpdf normrnd poisscdf poissinv poisspdf poissrnd stdnormal_cdf stdnormal_inv stdnormal_pdf stdnormal_rnd tcdf tinv tpdf trnd unidcdf unidinv unidpdf unidrnd unifcdf unifinv unifpdf unifrnd wblcdf wblinv wblpdf wblrnd wienrnd MODELS logistic_regression TESTS anova bartlett_test chisquare_test_homogeneity chisquare_test_independence cor_test f_test_regression hotelling_test hotelling_test_2 kolmogorov_smirnov_test kolmogorov_smirnov_test_2 kruskal_wallis_test manova mcnemar_test prop_test_2 run_test sign_test t_test t_test_2 t_test_regression u_test var_test welch_test wilcoxon_test z_test z_test_2 ** Functions marked with known test failures: grp2idx: bug #51928 gevfir_lmom: bug #31070 ** Other functions that have been changed for smaller bugfixes, increased Matlab compatibility, or performance: dcov: returned dcov instead of dcor. added demo. violin: can be used with subplots. violin quality improved. princomp: Fix expected values of tsquare in unit tests fitgmdist: test number inputs to function hist3: fix removal of rows with NaN values ** added the packages test data to install statistics-release-1.9.2/ONEWS-1.5.x000066400000000000000000000300501524624707500167320ustar00rootroot00000000000000Summary of important user-visible changes for statistics 1.5.0: ------------------------------------------------------------------- Important Notice: 1) dependency change to Octave>=6.1.0 2) `mean` shadows core Octave's respective function 3) removed dependency on `io` package 4) incompatibility with the `nan` package New functions: ============== ** anova2 (fully Matlab compatible) ** bvncdf ** cdfcalc, cdfplot ** chi2gof (fully Matlab compatible. bug #46764) ** chi2test (bug #58838) ** cholcov (fully Matlab compatible) ** ecdf (fully Matlab compatible) ** evfit (fully Matlab compatible) ** evlike (fully Matlab compatible) ** fitlm (mostly Matlab compatible) ** fillmissing (patch #10102) ** friedman (fully Matlab compatible) ** grpstats (complementary to manova1) ** kruskalwallis (fully Matlab compatible) ** kstest (fully Matlab compatible) ** kstest2 (fully Matlab compatible. bug #56572) ** libsvmread, libsvmwrite (I/O functions for LIBSVM data files) ** manova1 (fully Matlab compatible) ** manovacluster (fully Matlab compatible) ** mean (fully Matlab compatible, it shadows mean from core Octave) ** multcompare (fully Matlab compatible) ** mvtcdfqmc ** ranksum (fully Matlab compatible. bug #42079) ** standardizeMissing (patch #10102) ** svmpredict, svmtrain (wrappers for LIBSVM 3.25) ** tiedrank (complementary to ranksum) ** x2fx (missing function: bug #48146) Improvements: ============= ** anova1: added extra feature for performing Welch's ANOVA (PR #15) ** anovan: mostly Matlab compatible, extra features. (patch #10123, PR #1-42) ** binopdf: implement high accuracy Loader algorithm for m>=10 (bug #34362) ** cdf: extended to include all available distributions ** crosstab: can handle char arrays, fixed ordering of groups ** gaminv: fixed accuracy for small 1st argument (bug #56453) ** geomean: fully Matlab compatible. (patch #59410) ** gevlike: fully Matlab compatible. ACOV output is the inverse Fisher Inf Mat ** grp2idx (fully Matlab compatible, indexes in order of appeearance) ** harmmean: fully Matlab compatible. ** hygepdf: added optional parameter "vectorexpand" to facilitate vectorization of other hyge functions. Allows different inputs lengths for x and t,m,n parameters, with broadcast expanded output (bug #34363) ** hygecdf: improved vectorization for non-scalar inputs. hygeinv hygernd ** ismissing: corrects handling of n-D arrays, NaN indicators, and improves matlab compatibility for different data types. (patch #10102) ** kmeans: improved help file, evaluate efficiency (bug #8959) ** laplace_cdf: allow for parameters mu and scale (bug #58688) laplace_inv laplace_pdf logistic_cdf logistic_inv logistic_pdf ** logistic_regression: fixed incorrect results (bug #60348) ** mvncdf: improved performance and accuracy (bug #44130) ** normplot: fixed ploting error (bug #62394), updated features ** pdf: extended to include all available distributions ** pdist: updated the 'cosine' metric to be more efficient (bug #62495) ** rmmissing: corrects cellstr array handling and improves matlab compatibility for different data types. (patch #10102) ** signtest: fix erroneous results, fully Matlab compatible (bug #49961) ** ttest2: can handle NaN values as missing data (bug #58697) ** violin: fix parsing color vector affecting Octave>=6.1.0 (bug #62805) ** wblplot: fixed coding style and help texinfo. (patch #8579) Removed Functions: ================== ** anova (replaced by anova1) ** caseread, casewrite (do not belong here) ** chisquare_test_homogeneity (replaced by chi2test) ** chisquare_test_independence (replaced by chi2test) ** kolmogorov_smirnov_test (replaced by kstest) ** kolmogorov_smirnov_test_2 (replaced by kstest2) ** kruskal_wallis_test (replaced by kruskalwallis) ** manova (replaced by manova1) ** repanova (replaced by anova2) ** sign_test (replaced by updated signtest) ** tblread, tblwrite (belong to `io` package when tables are implemented) ** t_test, t_test_2 (deprecated: use ttest & ttest2) ** wilcoxon_test (replaced by ranksum) Available Data Sets: ==================== ** acetylene Chemical reaction data with correlated predictors ** arrhythmia Cardiac arrhythmia data from the UCI machine learning repository ** carbig Measurements of cars, 1970–1982 ** carsmall Subset of carbig. Measurements of cars, 1970, 1976, 1982 ** cereal Breakfast cereal ingredients ** examgrades Exam grades on a scale of 0–100 ** fisheriris Fisher's 1936 iris data ** hald Heat of cement vs. mix of ingredients ** heart_scale.dat Used for SVM testing ** kmeansdata Four-dimensional clustered data ** mileage Mileage data for three car models from two factories ** morse Recognition of Morse code distinctions by non-coders ** popcorn Popcorn yield by popper type and brand ** stockreturns Simulated stock returns ** weather Daily high temperatures in the same month in two consecutive years Summary of important user-visible changes for statistics 1.5.1: ------------------------------------------------------------------- Important Notice: 1) `mean` shadows core Octave's respective function 2) incompatibility with the `nan` package New functions: ============== ** barttest (fully Matlab compatible) ** evcdf (fully Matlab compatible) ** evinv (fully Matlab compatible) ** evpdf (fully Matlab compatible) ** evrnd (fully Matlab compatible) ** evstat (fully Matlab compatible) ** gpfit (fully Matlab compatible) ** gplike (fully Matlab compatible) ** gpstat (fully Matlab compatible) ** levene_test (options for testtypes, handling NaNs and GROUPS like anova1) ** ncfcdf (fully Matlab compatible) ** ncfinv (fully Matlab compatible) ** ncfpdf (fully Matlab compatible) ** ncfrnd (fully Matlab compatible) ** ncfstat (fully Matlab compatible) ** nctcdf (fully Matlab compatible) ** nctinv (fully Matlab compatible) ** nctpdf (fully Matlab compatible) ** nctrnd (fully Matlab compatible) ** nctstat (fully Matlab compatible) ** ncx2cdf (fully Matlab compatible) ** ncx2inv (fully Matlab compatible) ** ncx2rnd (fully Matlab compatible) ** ncx2stat (fully Matlab compatible) ** normlike (fully Matlab compatible) ** sampsizepwr (fully Matlab compatible with extra functionality) ** vartestn (fully Matlab compatible) Improvements: ============= ** bartlett_test: improved functionality, hanlding NaNs and GROUPS like anova1 ** chi2cdf: added "upper" option and confidence bounds ** chi2test: improved functionality, handles multi-way tables ** crosstab: returns chi-square and p-value for multiway tables ** evfit: fixed bug that caused an error when x is a row vector ** fcdf: added "upper" option and confidence bounds ** gamcdf: added "upper" option and confidence bounds ** gpcdf: added "upper" option and confidence bounds ** mvnpdf: fixed MATLAB compatibility ** mvnrnd: fixed MATLAB compatibility ** ncx2pdf: reimplemented to be fully MATLAB compatible ** normcdf: added "upper" option and confidence bounds ** tcdf: added "upper" option ** vartest: fixed MATLAB compatibility ** vartest2: fixed MATLAB compatibility ** ztest: fixed MATLAB compatibility Removed Functions: ================== ** cloglog ** nanmean (replaced by mean) ** kolmogorov_smirnov_cdf (unused by new kstest, kstest2 functions) ** u_test (replaced by ranksum) ** var_test (replaced by vartest) ** z_test (replaced by ztest) Summary of important user-visible changes for statistics 1.5.2: ------------------------------------------------------------------- Important Notice: 1) `mean`, `median`, `std`, and `var` functions shadow core Octave's respective functions 2) incompatibility with the `nan` package New functions: ============== ** median (fully Matlab compatible) ** std (fully Matlab compatible) ** var (fully Matlab compatible) Improvements: ============= ** mean: fixed MATLAB compatibility ** multcompare: fixed erroneous results for Welch ANOVA, updated features ** tcdf: fixed erroneous results ** ttest: added support for NaN values and matrix inputs ** ttest2: added support for matrices and multiple t-tests Removed Functions: ================== ** nanmedian (replaced by median) ** nanstd (replaced by std) ** nanvar (replaced by var) Summary of important user-visible changes for statistics 1.5.3: ------------------------------------------------------------------- Important Notice: 1) `mean`, `median`, `std`, and `var` functions shadow core Octave's respective functions 2) incompatibility with the `nan` package New functions: ============== ** adtest (fully Matlab compatible) ** hotelling_t2test (new functionality, replacing old hotelling_test) ** hotelling_t2test2 (new functionality, replacing old hotelling_test_2) ** regression_ftest (new functionality, replacing old f_test_regression) ** regression_ttest (replacing old t_test_regression) ** vmcdf (von Mises cummulative distribution function) Improvements: ============= ** betacdf: added "upper" option ** binocdf: added "upper" option ** expcdf: added "upper" option and confidence bounds ** geocdf: added "upper" option ** gevcdf: added "upper" option ** hygecdf: added "upper" option ** laplace_cdf: updated functionality ** laplace_inv: updated functionality ** laplace_pdf: updated functionality ** laplace_rnd: updated functionality ** logistic_cdf: updated functionality ** logistic_inv: updated functionality ** logistic_pdf: updated functionality ** logistic_rnd: updated functionality ** logncdf: added "upper" option and confidence bounds ** mean: fixed MATLAB compatibility ** median: fixed MATLAB compatibility ** multcompare: print PostHoc Test table ** nbincdf: added "upper" option ** poisscdf: added "upper" option ** raylcdf: added "upper" option ** std: fixed MATLAB compatibility ** unidcdf: added "upper" option ** unifcdf: added "upper" option ** var: fixed MATLAB compatibility ** vmpdf: updated functionality ** vmrnd: updated functionality ** wblcdf: added "upper" option and confidence bounds Removed Functions: ================== ** anderson_darling_cdf (replaced by adtest) ** anderson_darling_test (replaced by adtest) ** hotelling_test (replaced by hotelling_t2test) ** hotelling_test_2 (replaced by hotelling_t2test2) ** f_test_regression (replaced by regression_ftest) ** t_test_regression (replaced by regression_ttest) Summary of important user-visible changes for statistics 1.5.4: ------------------------------------------------------------------- Important Notice: 1) `mean`, `median`, `std`, and `var` functions shadow core Octave's respective functions 2) incompatibility with the `nan` package New functions: ============== ** bvtcdf ** correlation_test (new functionality, replacing old cor_test) ** icdf (wrapper for all available *inv distribution functions) ** fishertest (fully Matlab compatible) ** procrustes (fully Matlab compatible) ** ztest2 (new functionality, replacing old prop_test_2) Improvements: ============= ** cdf: updated wrapper for all available *cdf distribution functions ** dcov: handles missing values and multivariate samples ** geomean: fixed MATLAB compatibility ** harmmean: fixed MATLAB compatibility ** mean: fixed MATLAB compatibility ** median: fixed MATLAB compatibility ** mvtcdf: improved speed, fixed Matlab compatibility ** pdf: updated wrapper for all available *pdf distribution functions ** random: updated wrapper for all available *rnd distribution functions ** regression_ttest: new functionality ** std: fixed MATLAB compatibility ** var: fixed MATLAB compatibility Removed Functions: ================== ** cor_test (replaced by correlation_test) ** prop_test_2 (replaced by ztest2) statistics-release-1.9.2/ONEWS-1.6.x000066400000000000000000000302701524624707500167370ustar00rootroot00000000000000 Summary of important user-visible changes for statistics 1.6.0: ------------------------------------------------------------------- Important Notice: 1) dependency changed to Octave>=7.2.0 2) various distribution functions have been renamed, deprecated, or modified extensively so that backwards compatibility is broken 3) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions 4) incompatibility with the `nan` package New functions: ============== ** betafit (fully Matlab compatible) ** betalike (fully Matlab compatible) ** binofit (fully Matlab compatible) ** binolike (similar functionality to MATLAB's negloglik) ** bisafit (similar functionality to MATLAB's mle) ** bisalike (similar functionality to MATLAB's negloglik and mlecov) ** burrfit (similar functionality to MATLAB's mle) ** burrlike (similar functionality to MATLAB's negloglik and mlecov) ** einstein (plotting function for the einstein tile) ** geofit (similar functionality to MATLAB's mle) ** gumbelcdf ** gumbelfit (similar functionality to MATLAB's mle) ** gumbelinv ** gumbellike (similar functionality to MATLAB's negloglik and mlecov) ** gumbelpdf ** gumbelrnd ** hncdf ** hnfit (similar functionality to MATLAB's mle) ** hninv ** hnlike (similar functionality to MATLAB's negloglik and mlecov) ** hnpdf ** hnrnd ** invgcdf ** invgfit (similar functionality to MATLAB's mle) ** invginv ** invglike (similar functionality to MATLAB's negloglik and mlecov) ** invgpdf ** invgrnd ** isoutlier (fully Matlab compatible) ** logifit (similar functionality to MATLAB's mle) ** logilike (similar functionality to MATLAB's negloglik and mlecov) ** loglcdf ** loglfit (similar functionality to MATLAB's mle) ** loglinv ** logllike (similar functionality to MATLAB's negloglik and mlecov) ** loglpdf ** loglrnd ** lognfit (fully Matlab compatible) ** lognlike (fully Matlab compatible) ** nakafit (similar functionality to MATLAB's mle) ** nakalike (similar functionality to MATLAB's negloglik and mlecov) ** nbinfit (similar functionality to MATLAB's mle) ** nbinlike (similar functionality to MATLAB's negloglik and mlecov) ** mad (fully Matlab compatible) ** normfit (fully Matlab compatible) ** poissfit (fully Matlab compatible, extra functionality) ** poisslike (similar functionality to MATLAB's negloglik) ** raylfit (fully Matlab compatible, extra functionality) ** rayllike (similar functionality to MATLAB's negloglik) ** ridge (fully Matlab compatible) ** unidfit (similar functionality to MATLAB's mle) ** unifit (similar functionality to MATLAB's mle) ** vminv (quantile function for the von Mises distribution) ** wblfit (fully Matlab compatible) ** wbllike (fully Matlab compatible) Improvements: ============= ** bisacdf: supports "upper" option ** burrcdf: supports "upper" option ** cauchycdf: supports "upper" option ** cdf: update support for all univariate cumulative distribution functions ** gamfit: fixed MATLAB compatibility ** gamlike: fixed MATLAB compatibility ** icdf: update support for all univariate quantile functions ** laplacecdf: supports "upper" option ** logicdf: supports "upper" option ** mcnemar_test: updated functionality ** nakacdf: supports "upper" option ** pcacov: fixed MATLAB compatibility ** pdf: update support for all univariate probability density functions ** plsregress: fixed MATLAB compatibility ** random: update support for all univariate functions ** runstest: fixed MATLAB compatibility ** tabulate: fixed MATLAB compatibility ** tricdf: supports "upper" option ** trimmean: fixed MATLAB compatibility Removed Functions: ================== ** run_test (replaced by runstest) ** stdnormal_cdf (replaced by normcdf) ** stdnormal_inv (replaced by norminv) ** stdnormal_pdf (replaced by normpdf) ** stdnormal_rnd (replaced by normrnd) Renamed Functions: ================== ** bisacdf (replacing bbscdf) ** bisainv (replacing bbsinv) ** bisapdf (replacing bbspdf) ** bisarnd (replacing bbsrnd) ** cauchycdf (replacing cauchy_cdf) ** cauchyinv (replacing cauchy_inv) ** cauchypdf (replacing cauchy_pdf) ** cauchyrnd (replacing cauchy_rnd) ** laplacecdf (replacing laplace_cdf) ** laplaceinv (replacing laplace_inv) ** laplacepdf (replacing laplace_pdf) ** laplacernd (replacing laplace_rnd) ** logicdf (replacing logistic_cdf) ** logiinv (replacing logistic_inv) ** logipdf (replacing logistic_pdf) ** logirnd (replacing logistic_rnd) Summary of important user-visible changes for statistics 1.6.1: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package New functions: ============== ** ClassificationKNN (new classdef) ** predict (for ClassificationKNN classdef) ** fitcknn ** fitrgam ** knnsearch (fully Matlab compatible) ** mnrfit ** rangesearch (fully Matlab compatible) ** RegressionGAM (new classdef) ** predict (for RegressionGAM classdef) Improvements: ============= ** anovan: new features ** friedman: bug fixes ** pdist: updated functionality, fully MATLAB compatible ** pdist2: updated functionality, fully MATLAB compatible ** regress_gp: updated functionality with RBF kernel ** ridge: bug fixes Summary of important user-visible changes for statistics 1.6.2: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package New functions: ============== ** ricecdf ** riceinv ** ricepdf ** ricernd Improvements: ============= ** changed ClassificationKNN legacy class to classdef ** changed RegressionGAM legacy class to classdef ** update figure handling in BISTs, bug fix when calling 'pkg test statistics' Summary of important user-visible changes for statistics 1.6.3: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package New functions: ============== ** ricefit ** ricelike ** ricestat ** tlscdf ** tlsinv ** tlspdf ** tlsrnd ** tlsstat Improvements: ============= ** cdf: add support for Rician and location-scale T distributions ** gevlike: deprecate gradient as a second output argument (undocumented) ** icdf: add support for Rician and location-scale T distributions ** pdf: add support for Rician and location-scale T distributions ** random: add support for Rician and location-scale T distributions ** ricernd: fixed bug that produced erroneous results ** updated input validation and documentation to distribution *stat functions Summary of important user-visible changes for statistics 1.6.4: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package New functions: ============== ** bisastat ** burrstat ** fitdist ** glmfit (needs a lot of work yet) ** hnstat ** invgstat ** logistat ** loglstat ** makedist ** mle ** nakastat ** plcdf ** plinv ** plpdf ** plrnd ** plstat ** tlsfit ** tlslike ** tristat New classdefs: ============== ** BetaDistribution ** BinomialDistribution (methods on truncated distributions do not work yet) ** BirnbaumSaundersDistribution ** BurrDistribution ** ExponentialDistribution ** ExtremeValueDistribution ** GammaDistribution ** GeneralizedExtremeValueDistribution ** GeneralizedParetoDistribution ** HalfNormalDistribution ** InverseGaussianDistribution ** LogisticDistribution ** LoglogisticDistribution ** LognormalDistribution ** LoguniformDistribution ** MultinomialDistribution ** NakagamiDistribution ** NegativeBinomialDistribution (methods on truncated distributions do not work yet) ** NormalDistribution ** PiecewiseLinearDistribution ('mean', 'std', and 'var' methods fail for truncated distributions) ** PoissonDistribution (methods on truncated distributions do not work yet) ** RayleighDistribution ** RicianDistribution ** tLocationScaleDistribution ** TriangularDistribution ** UniformDistribution ** WeibullDistribution Improvements: ============= ** binolike: accepts frequency vector as third input argument ** gevfit: accepts frequency vector as third input argument ** gevlike: accepts frequency vector as third input argument ** gpfit: accepts frequency vector and requires fixed parameter 'theta' ** gplike: accepts frequency vector as third input argument ** logl* distribution functions: changed parameters for MATLAB compatibility ** hnfit: accepts frequency vector as third input argument ** hnlike: accepts frequency vector as third input argument ** nbinfit: accepts frequency vector as third input argument ** nbinlike: accepts frequency vector as third input argument ** tri* distribution functions: swapped the order of b and c input arguments ** unidfit: accepts frequency vector as third input argument ** unifit: accepts frequency vector as third input argument Summary of important user-visible changes for statistics 1.6.5: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package Improvements: ============= ** mad/median: correct mad handling of vectors with multiple Infs ** PiecewiseLinearDistribution: fix truncated `mean`, `std`, and `var` methods ** various fixes to avoid errors when testing the package functions Summary of important user-visible changes for statistics 1.6.6: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package New functions: ============== ** editDistance Improvements: ============= ** fillmissing: combining `movmedian` method and `missinglocation` option no longer ignores non-NaN values. ** gumbelinv: fix incorrect return value due to misplaced minus sign ** glmfit: fix input validation, parameter parsing, and update documentation ** histfit: updated support for other distributions, fixed MATLAB compatibility ** plot method for probability distribution objects partially implemented to allow plotting PDF and superimposing it over histograms of fitted data. Summary of important user-visible changes for statistics 1.6.7: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package New functions: ============== ** bar3 ** bar3h Improvements: ============= ** ClassificationKNN: bug fixes in computing Prior and when parsing explicit ClassNanes (github issue #148) ** nanmax: update functionality to support VECDIM and 'all' options, fix error when requesting a mutual comparison (savannah bug #65802) ** nanmin: update functionality to support VECDIM and 'all' options, fix error when requesting a mutual comparison (savannah bug #65802) statistics-release-1.9.2/ONEWS-1.7.x000066400000000000000000000177661524624707500167570ustar00rootroot00000000000000 Summary of important user-visible changes for statistics 1.7.0: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package New functions: ============== ** fcnnpredict ** fcnntrain ** fitcdiscr ** fitcgam ** fitcnet ** fitcsvm ** loadmodel ** signrank New classdefs: ============== ** ClassificationDiscriminant ** ClassificationGAM ** ClassificationNeuralNetwork ** ClassificationPartitionedModel ** ClassificationSVM ** CompactClassificationDiscriminant ** CompactClassificationGAM ** CompactClassificationNeuralNetwork ** CompactClassificationSVM Improvements: ============= ** BinomialDistribution: fix computations on truncated distribution methods (github issue #128) ** ClassificationKNN: new methods and various bug fixes ** finv: fix erratic output for very large DF2 (savannah bug #66034) ** fitcknn: support for cross-validation ** NegativeBinomialDistribution: fixed computations on truncated distribution methods (github issue #128) ** plot method for probability distribution objects updated to support 'cdf' and 'probability' PlotType options (github issue #129) ** PoissonDistribution: fixed computations on truncated distribution methods (github issue #128) Summary of important user-visible changes for statistics 1.7.1: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package Improvements: ============= ** ClassificationPartitionedModel: add input validation for cvpartition object (github issue #160) ** ClassificationNeuralNetwork: use named variable for timing training (github issue #169) ** kstest: add support for probability distribution objects, fix previous regression related to failing test (github issues #164, #165, #167), add more BISTs and a DEMO ** RegressionGAM: add iteration limit to the private fitGAM function to avoid infinite loop with Netlib BLAS/LAPACK (github issue #160) Summary of important user-visible changes for statistics 1.7.2: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package Improvements: ============= ** fcnnpredict, fcnntrain: various bug fixes, allow MacOS users to enable support for OpenMP (github issues #168, #171, #172) ** editDistance: use correct index types to avoid compiler warnings (github issue #171) Summary of important user-visible changes for statistics 1.7.3: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package New functions: ============== ** glmval Improvements: ============= ** glmfit: fixed output and updated functionality and compatibility Summary of important user-visible changes for statistics 1.7.4: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package Improvements: ============= ** ClassificationDiscriminant, ClassificationKNN, ClassificationNeuralNetwork: fix type of returning labels and ClassNames to be the same as input Y, add custom display and subsref/subsassgn methods ** confusionchart: fix empty figures in demos (github issue #178) ** ConfusionMatrixChart: properly update cdata in figure (github issue #178) ** fitdist: fix occasionally failing BISTs (github issue #174) ** kmeans: fix empty figures in demos (github issue #179) Summary of important user-visible changes for statistics 1.7.5: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package New functions: ============== ** createns ** multiway New classdefs: ============== ** cvpartition ** ExhaustiveSearcher ** hnswSearcher ** KDTreeSearcher Improvements: ============= ** chi2pdf, gampdf: fix INF handling (#203) ** crosstab: allow row vectors as input, fix numeric input ordering (#184) ** cvpartition: old style class has been replaced by classdef implementation. 'K-fold' partitioning is now randomized, and 'repartition' method works for both 'k-fold' and 'holdout' partition types. ** fitdist: fix NaN handling as missing values (#203) ** fpdf, finv: fix output when DoF tend to infinity (#203) ** fullfact: fix MATLAB compatibility (#212) ** grpstats: fix MATLAB compatibility (#215) ** knnsearch, rangesearch: fix slow kd-tree implementation (#151) ** vartest, vartest2: fix computation of right tail p-value (#183) ** x2fx: fix variable shadowing in nested loops (#204) ** ztest: fix second output for one-tailed tests (#199, #200) Summary of important user-visible changes for statistics 1.7.6: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package New functions: ============== ** factoran (#257) ** nanmean (#239) Improvements: ============= ** Update documentation add demos to all distrbution objects. Several minor bug fixes. ** ClassificationDiscriminant: improve documentation, allow changing 'Cost' and 'Prior' properties in existing objects ** ClassificationKNN: 'predict' can use either exhaustive or kdtree method ** crosstab: fix NaN input handling, improve efficiency (#214, #219) ** crossval: fix MATLAB compatibility (#217, #223, #229) ** cvpartition: fix MATLAB compatibility (#227) ** multiway: simplified function signature, improved efficiency (#258, #259) ** nansum: fix MATLAB compatibility to handle 'vecdim' and 'all' options ** pdist2: fix returning the smallest/largest K values for single output ** tcdf: improve speed and fix edge case in processing small DF (#232) ** ztest2: improve input validation (#226) Summary of important user-visible changes for statistics 1.7.7: ------------------------------------------------------------------- Important Notice: 1) `mad`, `mean`, `median`, `std`, `var` functions shadow core Octave's respective functions prior to Octave v9.1 2) incompatibility with the `nan` package Improvements: ============= ** Update documentation to all classification objects. Minor bug fixes (#263) ** Update documentation to ConfusionMatrixChart class (#285) ** cvpartition: fix error in stratified input vector (#268) fix handling of missing observations (#269) ** pca: fix erroneous Hotelling's T^2 statistics when applying weights (#279) statistics-release-1.9.2/ONEWS-1.8.x000066400000000000000000000140761524624707500167470ustar00rootroot00000000000000 Summary of important user-visible changes for statistics 1.8.0: ------------------------------------------------------------------- Important Notice: 1) Minimum Octave version required is 9.1.0 2) Add dependency to the datatypes package 3) Incompatibility with the `nan` package 4) Update 'libsvm' library to version 3.36 5) Using `tablicious` as a drop in replacement for `datatypes` might cause issues. Future development of statistical functions to enable support for tables and categorical arrays will be based on the implementations in `datatypes` New functions: ============== ** makima Improvements: ============= ** fillmissing: add support for the "makima" interpolation method ** svmpredict: add support for one class posterior probablity Removed functions: (available in core Octave) ================== ** mad ** mean ** median ** std ** var Summary of important user-visible changes for statistics 1.8.1: ------------------------------------------------------------------- Important Notice: 1) Soon after Octave 11.1 is officially released the 'statistics' package will update its dependency to Octave 11, so that further development can rely on advancements to core statistics and data functions that will be available with Octave 11. 2) Incompatibility with the `nan` package 3) Using `tablicious` as a drop in replacement for `datatypes` might cause issues. New functions: ============== ** dummyvar ** parseWilkinsonFormula Improvements: ============= All random generator distribution functions now accept empty size vectors as third input argument for MATLAB compatibility. ** confusionmat: add support for new data types ** crosstab: add support for new data types ** cvpartition: implement 'summary' method ** datasample: improve input validation ** fillmissing: char arrays do not have standard missing values ** friedman: return ANOVA results in a table ** grp2idx: fix MATLAB compatibility, add support for new data types ** grpstats: add support for tables and categorical arrays, add ploting functionality, fix remaining MATLAB compatiblity issue ** hnswSearcher: improve performance in building index and query operations ** ismissing: fix MATLAB compatibility, char arrays do not have standard missing values ** KDTreeSearcher: improve query speed ** knnsearch: improve input validation and avoid infinite recursion ** pcacov: improve input validation ** pdist, pdist2: improve performance, add fast euclidean algorithms ** rangesearch: improve input validation and avoid infinite recursion ** rmmissing: fix MATLAB compatibility, char arrays do not have standard missing values ** standardizeMissing: fix MATLAB compatibility, char arrays do not have standard missing values ** stepwisefit: fix MATLAB compatibility, replace legacy Draper-Smith algorithm, results may differ from previous implementation ** tabulate: add support for tables and categorical arrays ** wishrnd: allow for non-integral DF under certain constrains Summary of important user-visible changes for statistics 1.8.2: ------------------------------------------------------------------- Important Notice: 1) Update dependency to datatypes 1.2.0 2) Incompatibility with the `nan` package 3) Using `tablicious` as a drop in replacement for `datatypes` might cause issues. Improvements: ============= ** anova1: properly handle NaN values as group labels ** boxplot: add support for multi grouping variables ** grp2idx: improve integration with datatypes class objects ** makima: improve MATLAB compatibility ** parseWilkinsonFormula: add 'equations' mode ** sampsizepwr: fix computations for single-tailed distributions ** signtest: properly handle NaN values Summary of important user-visible changes for statistics 1.8.3: ------------------------------------------------------------------- Important Notice: 1) Minimum Octave version required is 11.1.0 2) Update dependency to datatypes 1.2.3 3) Incompatibility with the `nan` package 4) Using `tablicious` as a drop in replacement for `datatypes` might cause issues. Improvements: ============= ** ClusterCriterion: improve speed ** cmdscale: add support for second argument ** DaviesBouldinEvaluation: fix index ** harmmean, trimmean: improve MATLAB compatibility ** linkage: improve speed and fix unused variable ** pca, princomp: improve speed ** randsample: fix edge cases ** stepwisefit: improve speed ** squareform, tabulate: improve MATLAB compatibility ** RegressionGAM.predict: correct ySD computation using residual variance Summary of important user-visible changes for statistics 1.8.4: ------------------------------------------------------------------- Important Notice: 1) Update dependency to datatypes 1.2.6 2) Incompatibility with the `nan` package 3) Using `tablicious` as a drop in replacement for `datatypes` might cause issues. New functions: ============== ** LinearModel: object-oriented linear regression model class, returned by `fitlm` ** anova: object-oriented interface for analysis of variance, built on `anova1`, `anova2`, and `anovan` with a MATLAB-compatible object API Improvements: ============= ** anova1: add support for categorical arrays as grouping variables ** fitlm: now returns a LinearModel object; the previous `[TAB, STATS] = fitlm (...)` output form is no longer supported ** glmfit: fix a variable error (GitHub PR #440) ** parseWilkinsonFormula: fix power-term resolution from a base table column statistics-release-1.9.2/doc/000077500000000000000000000000001524624707500161145ustar00rootroot00000000000000statistics-release-1.9.2/doc/statistics.png000066400000000000000000000006421524624707500210160ustar00rootroot00000000000000PNG  IHDRddtIME @tEXtCommentCreated with GIMPW1IDATxA Ph颫&m(vaO%RJ< ,X` ,`Qcw^:9`K`kvg؄6a63z&+faw< <,Xp +jMJ>u<Hw^~pe Zw7 ,]a߈3 ,X\c^XomY`%`O3t)GJ!,X`X`K`uyCĒ9`Y` ,X`%`>Od1'#IENDB`statistics-release-1.9.2/doc/statistics.qch000066400000000000000000042500001524624707500210040ustar00rootroot00000000000000SQLite format 3@ .zq  oA $ 3 ? oX''qtableMetaDataTableMetaDataTableCREATE TABLE MetaDataTable(Name Text, Value BLOB )t ##/tableFolderTableFolderTableCREATE TABLE FolderTable(Id INTEGER PRIMARY KEY, Name Text, NamespaceID INTEGER )| ''7tableFileNameTableFileNameTable CREATE TABLE FileNameTable (FolderId INTEGER, Name TEXT, FileId INTEGER, Title TEXT )t ++tableFileFilterTableFileFilterTable CREATE TABLE FileFilterTable (FilterAttributeId INTEGER, FileId INTEGER )f '' tableFileDataTableFileDataTable CREATE TABLE FileDataTable (Id INTEGER PRIMARY KEY, Data BLOB ) 77#tableFileAttributeSetTableFileAttributeSetTable CREATE TABLE FileAttributeSetTable (Id INTEGER, FilterAttributeId INTEGER )33/tableContentsFilterTableContentsFilterTable CREATE TABLE ContentsFilterTable (FilterAttributeId INTEGER, ContentsId INTEGER ){''5tableContentsTableContentsTableCREATE TABLE ContentsTable (Id INTEGER PRIMARY KEY, NamespaceId INTEGER, Data BLOB )x--#tableIndexFilterTableIndexFilterTableCREATE TABLE IndexFilterTable (FilterAttributeId INTEGER, IndexId INTEGER ) !! tableIndexTableIndexTableCREATE TABLE IndexTable (Id INTEGER PRIMARY KEY, Name TEXT, Identifier TEXT, NamespaceId INTEGER, FileId INTEGER, Anchor TEXT )h##tableFilterTableFilterTableCREATE TABLE FilterTable (NameId INTEGER, FilterAttributeId INTEGER )l++tableFilterNameTableFilterNameTableCREATE TABLE FilterNameTable (Id INTEGER PRIMARY KEY, Name TEXT ){55tableFilterAttributeTableFilterAttributeTableCREATE TABLE FilterAttributeTable (Id INTEGER PRIMARY KEY, Name TEXT )h)) tableNamespaceTableNamespaceTableCREATE TABLE NamespaceTable (Id INTEGER PRIMARY KEY,Name TEXT ) Coctave.community.statistics   +|vpjd^XRLF@:4.(" _1}Kp@  a 2  a0S] b+xKQj?a2[)o7CP5  $ Hindex.htmlstatistics6Descriptive_Statistics.html,Descriptive Statistics,Data_Manipulation.html"Data Manipulation.Hypothesis_Testing.html$Hypothesis Testinganova.html anova*anova_properties.htmlProperties$anova_   ~ytoje`[VQLGB=83.)$ }wqke_YSMGA;5/)#  { u o i c ] W Q K E ? 9 3 - ' !  ܂ۂڂق؂ ׂ ւ ՂԂӂ҂тЁ}ρ{΁źx́wˁuʁtɁqȁnǁlƁkŁiāhÁfeb`_]\ZYWVTSQOMKJHGEDB>;63.(#  ~|{zywusrqponlieca`^[XWU}R|O{NzLyJvGuCtBs@q?o<n:l9j6g4f1b/`-_,^*]'[$Z!YXVUSNLJB>=< ; 7 6 4 1+#! m<}G  doc !qchVersion1.0/)TŰ{'lU:W#U LNW$D{ ;Osp"э?$gvHY,@' 6揵m7 (P/3@$}G" ςAX鈬R@bކyQ^%e)o_,h'`+ 5Xqjr, G?QRAE?xh H'k0Xz? pT" 6݄ T}ch]5d̰M` Wn'3r4:9\GNe8IWUX~c`t0t:zwUJTqCW_|j*҃j9"}zUasZTL-4nTC a(';65uVB8q7!b?U=Os, :zhC,[`F ,J~!~ѣE^),ۺ&T/PԢX2j/7*‡ӂMuiC)WQWWgWW2GN2'ES૛פHy MTCl.:)8 ht[~vEFc_R2d[`\AI'ςoCVF\n[՝]6^eP3bk % wx{>k| @FPs+|HjB (`{> ^hpA&Y| ؀ #ش22&ih"w9$Rzeۍ[{csU?e}7,p w  H-7R{%{ Z4NqQE Ff%/~ߦ! qcU b)LR$`4=5񂌔L,@)Zcj _d&k !lVfƉF:1R?%g5LT7pu O2p-qF/M%ZPry=5 d@}&RN)>{N/_(8zH]UնLɆ3Qr9\But UXe-)mU4Jv}ev 5YDfqqlN]M [QWG0 #pk k4^{ eU}[,Vk=P4aG TcgWt*1v*oG" ߿:o4`=f4͹ R[ZRLiXSv^tj5OL+V.MSRrBB~sF?UVO׹jHCEX"hX|<]-Tt lﶿBua SSjb, K@:M)W,[CT?K2OQ|7=kyYzs#=NةVw@'l` Tq"3#_BY'O1.4{M-Ӏ $\PĎ);gr5„<8gr|Ak "f>=Uԇw)%q' #@ј|,s΍?:CGq,=%Y!h48_BjX|> XLS֕uuaeiG࿋g`ꙻtG A"Yf;wyu!}E-e^R\_`[ze:LHp!M%+JH¶ȿ}:Np ̇ ;}^63i/"Is.F\;T C%γd.|>4Cˢi"8? : :vxÚ`o.-iko?BYdzBMʔ㐉ZQm)9rUw_*eURr,J-0{w1i.-F=1̀J$#LT@њk|"ȹ.6"Ljkn[#̯?~v<]P;8>̯IgV?Tz(*F#gw@̣uΪl_Jj * 8+8Wy:%h^z1Rg8F6n,ROi k@g 5 ɬjܜԏ9t;.%xd^9{;3eɬ兄.t 䡊'ҁ& $ `=&e<].>.#PnJiޣlژ5Gjzf1;ٺ2ñE&M:?Vz:Vl8`AZZ$ؑYJD\\4:e'Ep5dE']ާ݀EW wDǒc>'\N#$?! #SRV(́:Yڭ酶V<<Vb{D~\\꣬c x*rJcʞW-@z~0I"H6Enބ,95aY]!ɜOH\!YW< Nt) t~> b'E 7e/=Q-6 rS(ӠT&]VAθ_z`N/FU|ci|`9 %\)BAB8R||2Oq6Mf{EW"ݝ-.%FFu}FV_! Mz7 `KLM/ aW؇{ÆN 5%P`v`:ISF. ?M[PUTڪe\nJAA\GUd;iO\a߉|1^Rký$:ۗRɀRI '*EKY%Fgh^rbCyH~cm v۳rǃ\ q}+e[ v &͐ b3@e}Jz<= g5^KP'Ps'uvȄ<\Rc MNĸiN ؛s!azR7i%/Z_\xF1!H4u=ynoAczݩ9_os%P%_y]Lo@[x 6TPx](?@~3.x27qa,x%ID{cyA@E<4;[ىʌrЊy>}u(v=HZg>;RiMn_km?m;د:[[E81_Y_Gg'@3^:9 eѯѥqw8XqhOt]9AP8GӘs|:V՜n\2dJ:P^7SKa>6upYx! QLb+B$Pu!]t"9Ao+* [/hPrpX1M#UbwFTc|.\[Oc9/!_yG'M";X^eY$}L&7}f>?m`!dּ-UN5L9+@Ȝ.KbcRWǒ8`>mn:.<<K̦KCqtRz_y79pc9V= 8acm^=iR Ks9C ibS0sd{@qis.s<X fV(wꖅ $ah؁$VZ FBvF ޘ#=cK`70?NSohgSz7uzz<|L:@-sQ 9 7"EU{ĺ ڰ(U'k͉C' ?dQE3kAYU5cB;ʢ;.T*C7Nf" i))aJa\2|2>zuO&j`2k|:@n6X>~mTo6+ˆ<F6MC0ekHVPB;tbjv7Ո`~P_)#ezH}@~U*6$xB@wO:`Rl$lnojYRA}Eo71ݞxYk`!\vx2ڿaQnb˼*t9Z=+8|t2n>7t d%;4'o'ǎǘ6Hq{JYtk9ivr*Hq{f-gָj% qi*Z^(h_U"J_hT -߆6O/zTzI*._o'dqh=:U9U9|5-e;32p2k=C@YFrnmb`'?e<Y yX|Exr@A"~6Di.[F:0D,=w#J5K10; >;(ݕ(=:D4#Wܻ ~'dNe5h+2NNQ r0y5xvLf\n-ϒ`؀9< K~[ϴRO8uchل7=6gF>%g{z_mK(3ߥ[b#I6PY%ZQ]̼/9zގ *scw0hav^߷wݵ#e X0(grFG*8 [ԼaAMݲ4{]*G2گuo$`Jkʊ] q6X‰J;=%~j~-InJdK|oiF:e i Ͻ֋`q,C˿ME|U"Wcy+-oSpQhv3-fCrv<)Nw1US*ivLF?L>DHf3`ۈU0\esyE*W!cWn0CW~5Ǔ36EOC/3{HE"[4JtAF%X(Rx}= 1ٷ?"Cj22>QX8NgV1h,>QD8F}!i6*#@wkEN6]Iپ/O&@2ǂl`A˷QFt^_-OTٰAk{Rҟʞ=Ro.p.=Ix}>fNC{ES#z5@EIHtM=둒ctV֤CAB Et-1YX]{y@lLf8@/D23>g!dba tp }XB**ba#CB*XH|Rq0k^=ZyΤcs J& C7Aўl{O4gka%1K.[.ò])ʃ]2j<JKbY `>J&j9J:{dT0+qDb`Z`U&Vb`a^0UQP eFs.Z5P[^5\2} 9/TFk(ά=l}XEшcrrnSe$]rzB猪ˀ:BD裖лW.5#E1zhː!cVu B,9iؽ,zȘUtE+O+R+:w!׊F ي(r̅d[VBtk"4C($_Pm(6PhA.fh 2u<,i!{gzu ?JR2Y^L0ƺ,JcKa+q c30Pqd !qp|Efz KF0aKf}zLGڻ`0~2/'.3X," 2 DЋ)IlD 2p\ F8d#zuu*$$UUe1DP c*f; ,ˆZ"`m~হ)hds܋p D}P eFgBُ*>J|q N(B"XfS(NB" Sw/cO Hݜ606z['iDc/BAai| aeF_"َ>ID>r {u7xk1}PG\cYT?xj䰕-["6j^9eMA@'4j3Q=2b󝯣|ι}7-_m Єq q2goZd9M:@J7ҙ7c Q"iUpb! b`ftXGg'!0j=%5s(28=r[ҹ-zVv#'-H\>>; U.C\3>qi7r§֘GPƽs$Ür^F2d GD2q0xN{Ǹ;P/KjrJ/\0ͨvE2Qsi >BU?|:R,$=.k>(5̼ N_Kb\d;ة~9d H0y bi$ڐHN]:PsCD(4y H :sU<<|TsPD_sA6l^6!':̃tw.*7}kD*ynf>UXb4xP ,5q.nLEyry\5e:fG(,Xi֘,ő+5zm &ʝM.h$CC_;/ ë1ʔ6K)4>unli񚥃\':B; gKlޛ&z4(SO{CW~*5WnPgso Nff+`HSmT"?V*d0nM!5d6Ŧ'M@ Ӣ Y|SqA]V`TTjŇűM r 0ehYhDpezDZ8 ;0` h$`_.tM[ܓu j6U}l7?h[`-(F 6 Ts鶄qDhp \)d]M1[ Vz+pPg=fN%X"i2:OSGKAz|<<d-uG5BUI U6]y#/2E08ٸD}k@?Nh 7"#efI x{EOfc[~Qj쏶qQ7vv q lE _E7Q 3R+ңv(m;kD[oUMґMԬdVq{_aT*_XS"6VC !MRwI@Mz?щRg4BtvRC\= WMpL#ؼv /uz{;xԷk8/v1d/`Xb\Ge5@rBBߊY#GӱQ@_`ʁ?. }b\ig.5hSih eK`V7\TmЈDKyeqsX#"HpHȓ%n+1BʂŒ@Sc:9(pt)rRUn"*5ηB2ϝ_Џ 0KO)C-#T6iDCg"XkRx)ArMVl@g @x7'em^idXu4X݅qTM1]2u_/3?wYkfƊJ`7=6_O~VOQ&J,) ltYոcT.nzD%-r4PNY9PXIM` $iZR}O_]Tz& aۖHߒ" GL.=?\! +TQ) - '\F dw_UZB(3r ˅l٦)zMW!y9?ڠ( hJ23"MI`?_G"e!DL"W`wD94P[E>3'sP+%2 ?CaI- r gʫBPI{#|ؓhR)) k &+q5AC^)uҥeX׭ CJxfW_ӚB>dww4L;7? ?|4} 4G8/ Q/?e ٻ9Eg;`}*u:KZb\"@4yBd jc~u~>^~ȸ)_+I;FXčh>_$ ɌJ:O?v#exshxsd*x`A6.cwm* dsO=|3F5 4jp ' k8iw9OrK ^=nj%*ePS3?0jB6vȏ :01t8aqOe3/ X0|v4}'&_`7=lUbQ$<cHl, H+-}Gm8q*z\QqX:w h IDmQ50h`Sr_5yIZ$M@v)z5@K~%X JvJ,DY"arJ.2A4g lGή["AQĵVJeڟXY੟չazٲ=ŭ{U4%[#ڷ{zAndO-I'igבܪ_4w^-OHs0 Q#"=CQ5劇|AH{`ʴ>R-ZQ6|8 [~^ݙjfH~uF\eRbJv_Zt2s?4˳aH&-9#]$0Il3rߠjٵ7 ˜͚e~lq hhMh+ e3"`>!g-nu:})d'U&JeyMYбӡֈJ$ډo?R gBcDx~~?^,0$ի LeK;(ixz^qP, Gl8솶 q;)QǠͿ(\n JhӛZPK9z *+:0'5]BFec֧eq"#\~ m`Qn0;g}q;_t0~~0g5SBάlSXG00~o-STn`Wݣ!%/E\Vxlg^P,x2d{:Ӝ7@e/w=cx<}FnuwoelBȣXbt>I[|DTWڀ^~oZPvTXC?r5ϊ\I;ynFusڲ eܲ#XE=G {LZ8 &@#)Syuw{XKjHGr_<0sf(gl4ryi 2Z=B/lB؊748""|%c 1 QhՂ$zSIhl](,e^c[ * aCIkyI ޲ *jj A`e63O'az"zSܮ,}cAtYTVp@Yq<7F69ʱx'Q!4s=ËȢ@lGsCp`g49h9IALcJ݈^̀>3`anòeW y:0A Q2҄ ꖬ5=Ɇx2 2&CeQQP?-EFA[0+4k;!btFHbgh#A=:v`2Ý ̊t 6LSObT m^20ZCմLh.?L+.^]Q^ ʫam9G]ŇvN/j$򩫇q؛Ț`$;6@)'BycP6fo L7X겉䩖a݉twoL{V-c̊r /-ˀ(pxȫC[Z[PRx%0܎%A9#UZJOt&l)>ĝCIkARgnizy Ͻcm|ddk|řrԐi_q:n`uv-rgZl0(%}q&C,N4ةWig׎s؏P'/Z :&joxD VO;;g3;{+rk'`J |8)Bd4P*[G\:eYOqt})< 欢1X S_qQҿS0BdHHאW XFα a"fb ސK@tN5i4GmuLp>.)ug~1PvdŹNэB'5V2MLH8`BlŒep~x{B+Bū bߚMkSsyj i^ݍ1e sxR*` p k[d0Ad#oЭTC%!~`#AJ<ƈN:VYc|6/6f_`81?&}AݹS\OinweU`b#]wQ/N`S*"wHULY O21B#J.]K8KDx,)ue3Y "-)hqK%u!$GXʵWRWVu;] ,>`az; me'}ץoF8;ɑL;䍉B1gR '·qd2vA+P{4ðTĤN޸'UJK.v=t+ ,b%(/,GԶ',Te ySXdfYAd4.Eެm[T=>dsY7Dn(0WhQ9!TM2HSn'ύhZ%4S1kac:6@R y>H?)`j(Jht)J # e UCٙ9Bkh0AS:DO`Z:]RC{'j}&( 5({$64h:e^* _e3=[?uZC'}^~ N?x^C ~A5ʭ 5Ry^C`NМ3/ߣ=iԆ)w5RStޱ2ݒK4y6=_jQc/Aw P=K)xwH.DVa*z[?KᲢyTZeTQjPP@F(Ϗ@H67I LA{UkxL ƞd(v3ɬ*6z&TOja8)??rW_t1SUd<|i9$w/ !O*j b UKtVk#g&A+<:4C0/e0..]' ͇:f2nc`}R8.h~Z7g* ݺ3*^n?eޡeUY&0>].sU5+qJ^#'K%Duio$]Hˈp o^@S4]C;.1.@B;ĸ]lH.f)Wi0~^m5@A )*-s{rz>0tȱ)@6a>q[R d =YpBrXe-njUbPpiħXmv tv߮XKвIqɵ^$hr[pvܧh8z sdt}9՘ht of6Ͼg^FRGn?+$e>tʠA?Ԅ܌>Ey5}G~ojc_^wj6n.HAW+t+$%zÍ*<{ ceh* uݓ+ʓgwIé> ?Dۆ\ x̕6η[1~߂s|Џu|CFޥr%'kd\sT>F#ڕpz11Y@37V~|}'`T|C`Xe>*HH]|7Oz/"W;J66 `.z[n: &n Ԯk ->Kp[il!t(lˏ@ߞ[>hG8)I_p<'Ephke{6_r{5Ih[ځVrM/@Mp(x`9@S5_t:(YDdlLTk ^M*{(޳jhRkfkxtb rQY^֨\nR 5 qj*Sg 65gMo\K}D7Yϴbho2_HZDEX0UC4Yh[-  pVҝZ `G3 wF=r,4҆20✻B:{'FE|k_;ٺlNU aȄ-F` jdT m3 #͐B ":.ĢT AGicvrmA*'dm pѬ .uLߥ,`imt> A[4G'@ɅK|a%)ŞDie߁Gn tU>F(֏(JӠ"0F#rI%\|R m=aIW+$cH!fI˄{тuË"ܾ`C,`9ݲMy3q9q2͋éSG!By,P(i\ ~ьPQGM UDpd2'`5b'$S^3%&F";U' ץW}ozn4[-ޅw~#HN  ,(qw i^jWǟݭl2OM:tQ2LLђzv jv YpֶT 0F8+@k_)+|jiBetEvu]LWu 6u[[jϾ2`擇rV/* s):IԹ7po1T{X ]яBqRIzea2N&coƊտ3l˓5>›x q1ObZPTmZPxBF4x{wp~4Ű:\&G0 X{6/|4?N\жw?N}LN͂滅uu U3yA%Ol'4GAceEQ{_ZMYoZkwN|6ǞBwDW:3aLXKO">~M:_+#Pzuɧ)ISlރWL_(h6}y*C2U>ԴeĥM w?^E-yx`Q?Wj{dѾs%Etfu6dl'TƱO9_8vNOܒvZەJwhøGu j[c(1?(n~h0GԧTN4H-r7ҾmlYw,O< 6{/J҆k,A4{w؈~4:^@r*9r>@kR#ͭ6|NP ڽvNG[.Ou>i{>]קiO.Ӗ=G gI`/g'L*89 kIf*8kx663Nvx*v|&] hXv/\sa9<ґ n7$G U!S ]ǾެCoJȒކ6Uܓ5H),W7ӢMk!՗WTLd uЂ!ˤ@A"VƸ@䪎Cc$YǤęhR}v&?1RӶY& Q uד %l.;{ ieڅu\ߏtx_R$mk1ِE$ XLEA79XJ%#vm~W3˦0%R"2 8(K^G#|ilH[u_H"x:o}JKm;Z L[{hz١lLƦ9׍;mzB}Ġ7=9lGoѵIp^6D'S`ä[P\!^W6-D,Q%NKj<@ TwW? ԞnsgR~Fw$u\.H]~l哼ڮ nzzwoM)s{9wOuu,ojR3gmQ Ff!O !,qzS@@QՐ /جcKi!w3ƞM:)RlcٞP\'{_tg޴~sxNp50Sy;2~ h/]Wua[Ӗ:*'Z5&8'`RgL(u!C.i4ŀ aUHO<"QCetrAl9ډPDŽ>Lju~j;WHLցL* Sima80f0P'?~1,AT 4$1-$t…3W G [u"qΦZڿt,E&~!JE XTIK -F ڴX]80jdN,*B?3rt18!Wo`Â;:K h3HNǸdہ0T-G([$}Ay'>KU]˞<{E\\SĜJ%'kY%Z4b4}{- hƨ`2JXry`DŘĹҨb FۖJeQzuF A<"!-s{'biDBg_Aym}yoi9B!J4S!pJ5X5ʋB2ׅˌA &F1P:.LuXY S$mQ ^6h-'6}8WV`Sl 튔0k6zQV!+$AJ\`'jQ--[ΓVVtC =>t깻|U^}rۿ/3š4\,{ _(p\'gK9<8 D2 $qdsJpe1"g[C {c4 <9:G`dxa,?|ԓ w\P~V)ő8D7@/e+xF'pׂjCg=|Z1h΍!e_%KxMI6 g~&vDΊ wS?w}o095$Mzy7.*q; p Փ$ ّHX VWpfpH?ECg8e];]/KKR3E񟴿qEۚp6y3=BBjCzPha!2{-cI_ِSd90A4"u):p~'ٰkhgc_h*ԝs]kGפuKYX[]V-e^91 8`78$ R#Z$o![2sYf%YAem}y(Jȷs 1,/)RAF2l O$,KB.]$|W1+pQ7E8߯qߑ}oDeDM˯md!k~}Alii(Vjg}+MtRu$-Z JDҲv5;LҊv^>u2r:ӎnο;0|8uiƾhdoSZ5;j"+wVo;[ćՒLdԔ{׳YVGK=>8X0ǘ z fЧF57UN+M_Yڬ<='/e:q{;7ҿKD}i[Oد($A v&Gswcа%u>lthՒbgG(2}tn5(I{J{YS,1IdUzE0#kU6ggU; a"h{zcBa 8Px,k4g\ ڠMUS[Q³j\cjIt:3JEn(b0ViQJ7k!^Fιq48پ uCϔ&E -!X#)(ɮ pO<КE7w$v}nѮ_\zƾff 6"xvpn[ -]6A:ڵY`7G[0DSaڟMA]GwJ@hP)2ܛc ._DD7,{wo1En6p=kmCou[sv2zqϫl]{ZC+̖+MSI5pkfP$mnZp$3=^$J̹)n mܶo at9$.Mjt8Ҥ?0cW!,46{C$!\fg>h~eHRG jT3qq_؁c*ޕ+1bT>RRaЯSÊa? A۹gyX1HSF#z5;yrY-c5Twit7\fRJu48kpzW$79`*Rp+?QƑTqjb 8f=P ,[,h B7¼I"V';}5VAS%kNHatA+NjD, G2竛$wCHzm:2Av`E]J MEDܽ>rHo$Y@ʨ/֒|bɶgKN%{[BlOmˣ}"#! ;ہzqӄA\*pt'er]OAkEl]/UwWKqv@T.#f^Kv Ϯe)Qrh%=>=LagcMdk۳?fSʹX-՜L㨶t`Z: `)7Qr I$H pc A9wЍi, nWZ-k%AR ܆HDkU4ɏqL1=6r1`nܻaX%u|du:Q`xLM92CF0riQ̎?xQ4DGd?~GaAsE<& ڤj%ݔ cBwDګ@L Bo۶ 8U U;ɠzl]6fTTwN n/GL[45[8{Cڔ%%0/X=&NF5"0`⡒:(.%/hg`o\4v[SnYhz.YXfo;[/+)O qD;BvOUs.&4SH{7q(.@9uǤ:`oO!|p#3\YLS(aohz֟w׻8;mpM;fF "r̻oVkGZ+Xbe9N ؄Ǥڃ6yR=:a;> 5;lB>{q ml(>p|g=4Y] ]kJ`XrbT0q$ >nkʭ^!Hw?מFGYci.wC]7ꚻ[(>\L*pk!ŌrILc9w)N]8>OW`̓ "0z~I(Jb^ok5'7v;w4C-XY(-rjL8$8w}dJIWZ) p`RPO5z9k+45BQQjr mH@>kXlT+~N pxZm޹lˊ&s-1)9 °%ju|I"6 \50;g!aQEuZUkcvp \;>uUvQ~V Pz|4=ZfɕB\cuHg 0Piw]E%~{:n~%S`.eʴUY/2j R]sf׳I[uŧ*Ã~JvlWG{$ >}´hMὯ +ljT H ̜}fEn7Y>8xiܴNؕqcY-<$>ރs9wZ Qaӝ@.`= D0x*L&|oM"Ƈ0SkϘ֪)qbB: CggGI8~J<۞VDg)Q)uH7C7_J[{o]P*crK?΅bx]ϼwho )dq[cؽeXRQbgc C5y?ZA49+^ki4VEHnQZ ҚfIziq%o0Da}u8z/Ľxa@,l`ӵm"h܍o\ȯs`1~ **q/k]WZmK 2 {lr&ol>1NWkIw|jW74o}hh{hFmmP%rn%!;#seHoHÎʌsz}=xcZnDY޵e:@THр@??묗Nf9bȘz)YYRx`=n7qP\CW҉F|iMuUxv9/&801LFor`u^ڛnԞۿ|f[^o{ m7$JYoO((s2-Kwo6Q9xs9odF_q"vF5ᒞAYloEi# xbxiB*z5p>klcLn({Q8])P!xozT"SObh[P>1-0ˆ'X3a=V%GO;ׯns 4V+I N'v,|7lO*Xz9*SO.‡02rbWcOJ6,oDf+joyHd ߠDJ_^_4DŖe9[;-jΧ\4Ha}mm[ddKηӈHe)Sdk0v?vJ<1>"%B7Ib%TzRF|FJޣ`׹=c{*CR4P=dD=|Ѯ}26-^yN + dQ>u&lEowpAeqiQ\60yu`וyVNH-sOؗÞGEx53|3I5@oG'i|$je<]mm*J@Ӿ"W ȒD[c>*5MeKP'5vL I>l烥a/n-nĉD;YxM*hmn~kg5v (ΰHiVJR;K6-݆/ֽB3 }Xne, 4RAjx}(#Tjj2 Li\ S%ž!S1(<"o*0j/J}PݟzIC  .^x}}Ƒ}*nB.W_$y+e|8v|.ɱ@@Wݟ~W`o+9%_t<|ΫEzsq15CLPXGjϳzyYZߒC鵹$AriER,߈8i W\4,Da0xG&fy>_p^$ξIev٭R]m glf6nw:x}m۶\MխeVMUnvA$1ͷ;~H(RC"<{m)hv\Z%֥ d[}aYQ2,pQ|O^>_#~Fgd}ӿ{<'6:ZNuJOR3y1{&NJn^nx^`?&FWR~TIm#&x4~c]7 $ɬ~3ׯ>d:cHnt~uQZvB/o?h'~߿E g }_p9d< G߾JXER/8GQP=r&ՠ|[zt۷;m$8twK!|3/ULۯ*"R1?wz2[s:RE4tޅ%x t z>+{Ź_s_1]#B42)j5?&J ب~,՜2, [ gbZh1Sce:Oĥ!&3oʱTdQ:r \4 Ng[ 4Cc^>˩` H68_qF`ЊYF: &o`~%JOf؇ UCUP "v\5A D`i Q˱U:+ؠY%8 I*'w $X!Qe;3jzkJ Ç~F:4EK/Q7>Ԍɚ&=X-ɏu0)$GA;|ТcCpKs"n:{0uC9CzKIw$DjE< TlaB.*uH^,!zv9h*AN_/Zs +*O ?׎VdVV@H(F$B`?R%iQ'A'vFQ )4n *G޳#* "\>P[?ZÃL<,WӔHe}]\Dg%#c{]4ډ'dϡ'EM~hcmN>!r('PQ)v4O{R#@խf.1 pvwp* **Gx}nG{}E&M6ey|(XZ}tlI$@lvuŋmj?bd㚗4/<1]>89O:)6'Nj25'ǫG"OɃe q'$ȟvy#k7'ӴU'yYAkrܬMqVĝ٦0+z&;qQ5,jcC>v$Yq|9X<6ufO/c.VYk&'sՇ!ƭ42Icb8=,,ʢɊ3S,b>7!UTshʪB,0ӻ۴@g6pO񺭛xnt$`kqnfY50E6Uyy-|B "N*age?M 'qYEO(NhL]$贎4[Kŧ<[K$ L7 ߽VQؑB4`lGt'E61 X^Dd Zڎ'eqvqu 'd_N2'Cs)2zVm,a/zQe&;7緅̓Y a;uc<;+ c݉ʧ4[n0:|A_J*!K!k8R$ne e9Mu. L'vehO!CS ^'.l&[T{^bv5"arx2x }n+:EAeTVaPהXyjI7%@>0⨫?a( /p]&nM5@F݊v데{ S/SVԔ4qpC=a n*۳UI2Y$(AEܛ7$Qĺh#%O6 H?qA}c6y0?|R* ehsyjxGxI^(ߏd M!zO+Rĵn!@N7zn:C QJ 6U9OY5WV . Urn(>2I;tpόѵ K'7Zw$ŇU v})_Ix> ćʰ38/@US Piyj0DWP "ghdl15<?GC?Yԛv?O dy}/ʼ] ' *̞HTmO/۹;N;1ZB7X!4.N8X1l6C02eO˲-6܏VSTy7W=Gpoo{Q8[ws|fN ]x_I tȹzM@2OѲ]dO9aU Px\cpTF B;ՉkO6$%n7j:}꙽ᄈ܍|5++ϋSήe ;)ٱVHg0JL?N^z y"d)kvp01j T+֚lG FWyn \ NnJfK/ pKLfV9!!4t `#5OϦ`D--ẅ́D$~0~a%'E>|*Sp09*"f_=F~o_,i(S7&eu9ӽGmĹ7 紣Q:y~K34Xy?^ I}op^N%,T:W<70M3jK<+z€쩻'1:u<< ̫ @w'!W Gkf-9.*)ݞS8p>=y;M)!m x^"ϙ{07Y 8좝#>}bm)e3:4@^j_**@4 M 2zSߓ/c߄paI1ڤӬ HJ%q[s|I@PڠE3F~\ؙOѼT\pU&@1lBB jb&qymmrlb<|k殂 -  ^SϪ`Y԰Ǚ$?!!P/H|iEb 5RR"p" p#g%P!q8[:}&7T{lakx_D<>:46S߉pHvy Jtx_>Coof@Bh| 0"hjs2g@\1*NMMi.%m-UCbpB#*hJ  NdzC1R !F`]]Ai(*Ϩg.7, Q F,jtj٥&C ,6񧹍tz(Q>Yjvk͖b BmU32s_{N0sCsy7EcV3w׆jڳE5}˛ZĂ 6نr K+24q'e˃C3,M97kˢ0[yrPFy^D#΀5E%>/. hOY$WWҷ1Áz&_%?I+\ϲ+)68E7W;MBD,"zC荰ey'OXI\T PL?]CU1ߎqkUz=V Lyhc'#P+J!'o{}'JР>IB]+pS~xOPQdA+nd4.zXS%i /$:QM1D rV-_SlFU^_4?Q.֍J mr.bYeI|VT$Z+[G{@qFIکɇVgG&AMO0 wMaH!86/Hz?1=[t}=waGպ4" e, QAJ*Q! *-mN 8,#9hcІ|ejJ1. \r,C~$[8'RBƶ4oP$s8sпʊX2c:/z 5Hř:AB9kE T:Z+qIa:s nN4||4dy}iX .IBXG`䐮51(5u C=[oQ2gm #9lg.=qI2EcE*UH"*K V}#) I`rj!跇F &wQ-Lv殹+`lm6eBwVuRAC+oQE<^|.Ya"Xf~䯱k s#MeVTh1lVdwY<`)QY:'2reA )卡ѬA9@1ɍI9d&CxI5>JړStjPa@7Pڙ^^ڝe1H|eu gͼ{7k.g;1Yj#P%DӋ}U@s5F*T%ꥆ KlOo2 *|V:[8GznM82J2d\)ۧ)MIn7~n6Cц5,y#ٻbYzwƆ 9# f 'BQ E%?d"mxuFQb?zIǔ(UPt@bߐV/PlWQqYnX{hԨ 8N~1hFdóW6_^EHjAʽY֬u'b尃FW"_?Ç[KdB!X툩GZMT@"l 2uX9QLP߼>1ks!,E5j<;_|Max|3q?Xv;g+x݆ӳ\aEaNzz7\0%D{ȎXU憯QU $9X5H[IBFRЂ: \BdItv@tmm^"8@o>^خf?cR yQn_:o!WE$gyXDzԢUPav.bKcQgAthvsN_?ʛ0! Y ZA`"'"H O-"%A@߼d$bZ^D jmA7;zvq{Z^ @o[\drd=ɔ7W]u|͋$ 2c6KϝvFY3L^ b薸lO^?1^CEzZeUKce.<? WFK̛$=(, Jq" qh8G@7&҉h9JG  jS|Vh#W-R,TBlƴBbcIXF EF6rM6g,uK!Map{j;f6esSk3p\HzNQ_9MR5Klf( jkSxSy:Af>0K 7 : ǛPVS1:LD2<`U{-Ss3Wvcv fd}j3o*g{ecT X_44[n4vPQ!15%d{kH(7=É8 DOO MeׯZEAy*z.E #XbYWgy5e)_ .;"= (?ّSG,;!̹TJ=B?&3FMݲ%8Jj hp*L;B; adK~6D^ ՝_} *xG yB75K7Ps6e+4M3@cſm<>G:9?r@䀲c,[_d})P" tɉ/J:C}Tt#(rpnD&(d@зcI%BffHOmmd99{8ٺ $fS({8p%3E5@(`-99ۢ|ƎSPHE\ Âm>LKQ!~WMxJC75HoSeF6O yp\͢5M|hV҉G4#9YsǞiJ]$s:t+TS;=ʿo fgq'|q̑Mea8hcj A5`#ns,q7M0"` }RDiD9© O .p J1 5L۱P| ec\oaq5Wa L*8?%XLۏ8]bᴲP^ ;+9 S:U}kF~aS>D<1t͸D~7q =a~nPgEpN&(&6{kC`8mnA#͋l(/3mja}W'8_ǩMInEh({.l[xGY97rnїЗ7/?}\9glȯ.|:&un<+@ERZIq]; w_чÀxE2> BDyΗ*\UiJcI4 #xm-ه%q9Cn_2AOU9Y>sa:7eH,~!E.^դK.ŷ(PS䉂4T|RzK4rwW )dQ2fDT*ʮGH:ڲtI "zVdFgǼ h*B[(B}% (ߚYޜ504W ځ 5 0Zv<&^9)Ɂ#i["k Az Sh,2XhDα,;+`ñ0))5%IR6*hdž{Ȼas)/vϺt^m˯t Ų~D51VAWM:^5{6)M띨v~9_ή~< BL҅}f>vَjYr-Un[H-4}WR+:lb۟\gopxap`l~r[~g.!u]0bH|K4V,Ga8SkWLĨDRhMd~q.A&˚ IAC}js ũl"p,fηOVG F>4%{^4۰WMK0#쉩HnA찐0!i #[q;S=,_nDj2+"p1mV93T-?Yfxڥ=zAב02`;1EϏ߆BW܂dZVPjbzFqL  i8p.$Wߓ\T̫aD,p@ǯ״<@v+/ LBB8khbvbatoZF/N'ӫ`1FCrdF HVC*/"uRI;ВdulՐ%sW:,#6t5I!; &PEyE3 ;(Q+AXٚʞMnCr6SU(#-#J.Ah6(}?t09(99-*Q^EJ1H+Wsy; u~Yأ-GDK_,bûٷz^?8-m.l+ _aᜂ`RK,/fL/[kL0|pcrzmSlH$`S D0ki+2gY#[HBv+u8|# T ^BY^dpZWu]RD-zpS %=Q -4[) ?D>}YַЬW~wi3Љ}秲 󩒘 aV+G qtgk)嗩?,͐x_P*U@~o]sd 9#8Ҙǫ*= 5*C)S|8i ^'!|=X3P ve@*R#&^ģ_b j|zq@$lfkv䪆FHނET IX.c.)7%mLbW؀J̰4ʸ$b`׺Vg}}er(B<:2j>EETY DGCc>iқWsu;AlCRGkQ'"t H_q:YϫjV& WCBD-a6USs JT^DL. "}*#cW_L1$ J5 G~@̋߈AJ\CLL`#NO؛;HO7aa纆W% [ΧOArB!Lej(HwVʢ{>-"`": `aVaMqhq|[2tW">y`euL;$2 $o&N UB>;G+ݜxAR_Ÿ*S+;1+tcCӿQRrd FfrGbnlKcmI4պ#&-7Xc6ҲEw"N n{~a\)hdQJua=c`!<*7u%+dۿٱr$/f3@~/ sڒ~ёc\ ?O0 B"-Td1)_enGüfmclȰbft@$Y,%Dq;۞{Z}:*ڔ{g.53?oz:ƓBUlz󞦟2BGFow- 4q?n]tn\D$)@ڻ|aFN71'#M ft=Aک h`|Kp %j&y c\TA~3lEeP AHEnIѲ2gK?Y%ڶ (D2D ȱN  Xop=lQm̊D$ RFiq"'>y$҄%Xڂ (N G,Bfuև-sZ.Ͳ(6K؇PM≮qAuIIC5ӋRbPo6qْn#(T,fǚs5guL.92Grz=U; < lԁoOy"%>6_Ú/uU(i6NXJ\ lXzBX'fX" HuN6)'4$Đ/ߢ㞧Ǻ&բ5,"PXxtF%]EJٽ8ȵ{,ށWݬNX~C~!%+b P"l:3bI>N o2Bs/nJ m%I#ZT`ZՂx S̪n{Y6,k_p౳Eń l#i|XDA(I XX=R  Ԅ#K.*;I,]fTنzɢUgSZ#QSWj&5HOQ809&O6!eӊ9^6vwsCoМk` lT[L.)#/ZC$Նųu7鬺*b)nd q2FbI]U??s,_dgFQD'B,ռ$<."M4(L.f v6n)lKCUW*wꅈ`vy:`0Uf"/F{pr͂RR@2@8nb}SKFˆEؿBO0x-P{Kmz$w&7ggOƝ=~3pGvo+!vQR:ϯ!AcxYxG6ٱEI᠚)Ji{hg-3Gm}]/ remǴZk>݄/7Fh]LBLSזNQh,@}~GN 04úsܲW:=b1qFyǟZJ;<~vFmS'u{q:z2N{z`%c εblReCBO4Ω'%==@xhA Gf*S1N7(o:9K֛K6%Th`('Vxf%5+v&\x^&8/_ Hbp֛MS@$-0r{ByJizait&7H"¡_VXw*hcXCv \Sйs9dJ7ɺl}ORĩѲ@*:4mzC Ԇ@@h:˛}"^_2)~nZ/Ek?d1eWݑGM{r go mjвZ+1 '&sxXeg`r3f[7V%1mkoV]*4bм%)_@|84vC$XXhyQRXϲ&lBT^ʟhgOs/U< NO:T r!,K=%ZA8!=طw8ZR#ЄcMRU`Iu$pbRg7ˈT s5d}ؚMV¥d\7"@'C"5@e*DaaY$k,:z !$m ,D(]c/A} ?ZP˒ r:Flզ0Y=s/ HclTɱi*tU;/y[kvv54dŤoE+~ntGNy '@#v.t+d @v MA/@<^MͭX\ҺGn]u&bw5Gư`?z^lSwltM[ ]f}/u^Zۺ9?,> }ٴj(An+MҎUnToߝWSfA8(Of}V?-*bnWs VtCJ \э!M<*w^ xDi *((X6Uc*υVVFhȗ^p X `FPNQ)sMՂܛB6jݷLWikj_@HSxZCtxe]4F<[+6 FT92k0&_P1)OX(*[BPA9\zi_7'`}aEet%   ٤WY qVQ1v uYf0/Dc{mrpz4t @ Uݮ}@mz2KyV0 )3h_9t3"umDX|agx[{5M(߰qʞ - M\fRư,12z9ۖiҫl6;)i~lVgzƙcpD6U^G {ulH^J6c3z` Xxn/tJW~LbcNeVj]TY>gNlj˛7FLL!;^3fGcVHVWc+ӧtM3?eq|&/tz u!yDejA)V3@PVLkO},j5ơ["ZB]`A=s^[z|Ul!Vw0EO2o1xI)wd#Bb,Џ?+M|jm5>8]TfrL5 84Co+P 0ߎq%k}#3ɸ}q{ oŕ_w͞`nj@2*nBXOwR-+=1mn9&)mSkY?&F]|S_T] B"ljM/!>Le2esj"hS-+#.iu)m^KpMQһ#2tc (E ʕq]]|k5H2>ho_h,H&G >857)|Uw/t`[It)1- ɒ] *fIlTl(Qcn^JϪOQÓUi]_K?'&׫&Qf>[gX->\nB6mE( IXCY2GI?XP>ګShpB?`{lB?"3Z^ 7P7 [YXHs0 cVQw] ކ6KG\\% HЭGÅI;@bCIy[;0[ &nԙ,yDBD,8xٮ\ڹHƭ OfMjw_]M՝{rF$t]4a|P(|yk2d@U47v'EÑ&+A)y}1%/CjjWg(A}X Rp=x@?$PX|8]Dk"QM(|%$%L:I եW4DlQi.?KG~B]`ё-J/~uYwW% _.ocݬ {.z@[,d+T*yd Œmrc笛1'Ăf[gyd]F2H4Z~ {k !ň!ȟlI?.߈ cr5ZhL6ԩe,wXK+Z 9/L£@ g^`R1qG&8⽩RUEi@ `CRjhG'c=)[.^YY^>{;к\1/<#<5 ol N?tH;kyTlXq=^g7pGq:x K;M׺X>Nk 2p"Xr$|gtUBu3WkL *p|WW7(;D CՅUNa9lw4y{ #<"n62î\\c }jhTgcdP0>ģf'_rc2rN$[*$r[>,(w9}~6zN>6 htƍ~?@FV?LȊhw69Q etm6@̩>_VfkN0`P8N(YBYO1o:uQk8Oz2kphkm؏ba!oƑc;H#RgX Ub-)G0ΉGaVijU4ڑKf7h`WGԤk739lv"o,Qn>fw*HZӄiPM Q̶] ,:V7Kp}"bO]C&5ȕDIWW^m/9k~p'xw{ăgջբjhU`%;ڑrv-zn`*dZhwk5R:'{؂UhK:Zi-vy#kی 4g:;pWi*nT/2+^Py88'`z\Yn;oVƻ"Iua<x.=#֠!g̪0}38D=0D =]dI~ ׼Aә2a"|+קcܐpC{֏+ܝ;Gsjޥn[/oD x9U-y.,uY]:|sW t5[T'}UgXH -wl5Y"x.1Hūз3$vH(%O \6P}%v*9BRTNO{͎"l\#lC(gO@a-+r<8P6%]C{•K58.A=;T+DA~4;!Eb`1MZ9c h~ۘ|E)~ ,M'edEđyIrLip:^0YnF+cg^CqNeq2y={ ~D-sz`w9 Jg{TKHv0RS+Z՚5ege5"%;JVhKeE@Rʽ`z4wYķw;̭l$lkzw+qq( ;5`F&i(A:/bb*gl䠸RPh ;<%A4[0F{;)4KNMefV=f:.bqqqY՞"rp_tLVSAP$ g։tjTh05VڊUOR&wȬv9ʼ9BstzZڌ]n jSfq7Aٯ ]_,Jk&bXi c85 ޏ_FfM$#̄0h8KߛYzDZmB_B#A-ic eN"rix9gTrfz .6 Ct[wPDJ"6U0(gsp{?Hˁtl.ۦ>x7s.?X'd ֦z ӠzWɰce qJ-m;Uz{o֛dxMnyЖ`9N +@4kfGE ht#Uj)F_{u^U"!-Kq~p?;*7DK@WX/u}m~ }Ybv,/⨖o Vb![^"9kv0'bCc*vzYnyӰ:<{ތߖ۲GVYNVֵU[wjA]!aë:p&WhUEi*g oPˡhOY=u[j_k}Q}ۗ ڍ\[ϖ 4;-Ć h %i:R-5JX0J6}ʧԁWa:|~$<ۢ9YJYםq(6m JA#P1y>wgjAvK Œ;8VX^ߙY+ 7*GV{S>\'qI+/F.ey(E5Ϩ9s,s"V9hF VJȶiAV= # qDgUW^ 3|sXf#*:/{p!z@G9uj:P~(Dֺ؃=n[-IrM te*َy| jBdҁ49BJ(ƒ ǏL@2)QHkA uw)oKH)m)6m3PنoyNQ}bYQ|mkMCˆyeu!S8#hD)9Ot>t^[IO\=lpȺm`i $dc'P>lYQzehO=6G[ FN4 3L'f L(̮Z79ALx0ea~|'yi-r_;!f֏!} m}9}}kYƈ% DtQ9Zs+LyA< Y1j Z Ŵ9^1a J3WuXkA%Ap},ke:YARbbq~f6qu|3l\`I'vkv{_ٞ;YY¥b.,O=fqei6M_{nEC\ 鍼oO=Gn.hۃm/!|I`/}1.$zo[a/թvI6nRo==;H櫪6w4ZYI~ @~2ՂK ;* ̤ыܰds^˟};ܯNQ#K- PX]Ƭ [^[B݇)9%-~FOXf% q.#oehX( pbt̆= !If h#??|g)T]SXś@כJ$xf.FN6x?L"B?q-mhu@K]EP/49 QLf+!>Y,t]{{:8Ȼwij^ۅU=N3}퐌ۘwB_')^tvbG31CM*.p5y! #hW;9>5YdI| NShf/̩}MLs ?ή{3a1 pD%C6i8}0YX( 8Lu%g_+Y¹tWh߳3*fRz9kD$>OTo[v9^)jm見5zQVJ2܂iJג%ϧ<p/K>d.,|zz>?\^6j$?N/ 9?,v[3Ť{zL]@H,A/܂4Pw ̅eU^A~_xyCs.Vj} +~ґeiQ hi2D]$$D:`Kk5_皉EYXC5\9&s8V[4/wCFd?7:2[iQdMAòWK5 VuMFٰ%\ XjX گvkؗS?\a z1E wkoBc2_E Zm}H x@U6w$3H2AlR#9tf rX7phr_vQ͖y6-\pF8W\-K.y| V VA)b[DYϡ B`%5Tqh1Gq}J*)щ~N*%{@:+Ի`Y|\]<%y +9h>l  GA_ I|esr)j-\<o*MDuYH{:hO2F6r{[Q&t 6>Mj-KŗwɈ2':<;~I4X~j--چQ͋fH&OwW0/5t}tgO^t>TM?LZs#b İlbZ3 Ťp8xJ\ 9ReI2+.̯ws7Fs7Q! Q66,*񪕲ߺ}$ox_mhhOIPGzf XVM`}6dzc#o Av r;@fcvZN&42blaE %7tdw]6] 5\-5iAUMıfGg|lHE>J%q ̳bAr_1Zv Ie nEI<~|a۴"q[* ~,΁ {l'~+nz󋆶&+о=Tw٢W9 T+H@i]at^F'ӹDEP, 5,78<,?Eؤs㵶++!-}I']Mi`b> V.C! ZySZ+m*c>3`AV8 wu')Nm'} 1=~vǫv0a^ÄT8(6 uL_˺eF'm _;g?Kߙ',>1K|^繕EYXJ%-\B-gyc)(Hc& GGo:-F8<FKUix2lAL"+rq`  2Μ@q\+nc %ߚW٫:@kjnjK۰VڢD:`>SošR&c];8 GZlj:%Z^UXkk0 GoEA0 "&%dĭӈGpH)] &IKlo bS,o NLx"55zH2e~"1Iw61JA+Tbb< [>̭+@8UU)]G>&(nKVTW:f ta|4$+&/mQ$+;Ϋ#&O mu%P5A] ƛfl0;w Ur \E W0.]NĤ覷4DM0BNi;|[t7` sIHcya3E^8KXș#yoo%5GO /~>_ُ=1mOMv"*8m~zvkP䔜ru"Đ ܰQxosZE (RsDդ="݌|~)(`wDf\F&Ao~7 Ppn$ .CMK[KD99589m9PDzbqC8WVI{aNG'((Hq=[L6!$Sgʰ\ aD}-b~*k;ڧ W^1f3YEq4d]iKG(̽|\F䞎βP 3n/TOm k_h ʗ.5貾BhURRa`+A)ҋXzfX΅<Ǟb`DMlCޟ2o,Feٔu=(+z݇0#m܍E$nkTwwEݔΝAyXPVMtJku>!e0%2s2;%jD/k"Ë)CE"QD?=1N/Za}@+wie Y\;k~|ED߆NC܂ y´}H(j療)'X!;:t_QѤy_`(kfb39|%U^yo xɑfю@Zz: ;JEe}@A%v ' zv% gD7@Pn '8o@pF(2F&DE*ƻN"nvTYGAa^";_`W^gEgPp z,&+\|t,9]#=٥[5S.ٻ>f]d.mOjشثaZcExD_z%?ubr)d!ƬlIpikT;G~=P/6Ƭ'{!ҤF*I},5*'&1Kv?;E4[e$ џ᳆AT4Xn-=Č) E-F cpC_fyk ]Sx!phvKﳉqEq:5d~~+7tksW^f (yثsI(dM zGq^Ym0fNEHۓu"CvvvNho/%06 ZA/rJqc0--yfu~!]Xzlp7 IqK~B3HιRS-#s>s {WAWaY4_],y\X e\oKP,H,,DklHMiٱ;hk&o3U߆Trkuٌ05Ȅ8G>l)H{Uד`!u#ࡎ2zwՏ]Fb Pxb X N4Y(]U+ÙT̝뜑E0tKfWr%dZF}߹aȶ^x mYo)ZJ쪅Z'ZN{ʹ$cfͿdxb"d)DR OѢ.7 < rV0]Je< lK߭6sbCNzEVJ#G`E>Qy!.An܃DJ$jpg25zۋCh='ݳὖJ&ɰm9<FC0yJ*3q T{]!;"f`\ŸF9޽H9Rm5%AumW56]Ik5"/ y|^0ٔ986hQXjc%gP"0j`ӽIY4Z7Md D:<yqE'Ntv"bq*#*x{$CoknaDihsi"A$oFC* Ej-)E'Wd(%E@ YԋN`pX6dN2 TeƝ^i4y^1 <8mId*U$HS=%e/4`a`ڑ7 f(6et*gvv >[@#*T?ݝJ]⸟D*6![52I:Ȱ亣! v7 ?nϐdykKzjC4t۾pbo2a4Oy]ʼnOTyB+Q#/B U3'z1Fd< 4tp /OUkxSM~> kju`u1׭ǧ׭e1L#n_`wUrUg5pW8TP;2HWtaFf[sG 9y0wGCڊV*L.s]SWΟЦ8Jc\MlᶲS;avvh83pF ]%ir`?VEPwWwid-KB64=Uf{Wc+kóS͇8YYY89 mS }_jG=@q~`1ûk^?0;6!{3 ĎqzTKncR!|_|2\'C'!#}22222t ?N&pIHLᾪ>hF>oh ;b+:ݨ~jz>ED}'v8}?} RO*>51qG]* &S;UHEkc!UEj#~-!BfX Nx+>ґ;0iHo>j?iw2iʹ Y)Cf,*I?;oE:T7-"[s.n= :XR*|fN3[.l û:wn6W K˜1_ʻ57`ZY 2y_- z)_LW[&mDzuZfHF*)w_A>ޠx{J^ahIޒ>aDg\ nw6g&usOk/-jRLHI }uMdn; n'jVW#F,/CuUjеԍE$֙Q(1jnS+@&-AU$A[&v=D|}/Nn/@$IN ue6i3*RII\,GLK1!x;&5^I'j:ٿt}30L~)`OV(ԧ ;a)ۋ?EO@EЩ &j-Q{ ge{AdzЕ89hW]KD42`Gk NKNSoue*Ϋ`ն?R 8Gro}O,6FJ6:u|1 ؉nZZO োD)N:|F |*v+wkq]2f٩[&Vd4xZ;DR>#Չ]¶4~:N Jm)T3u7QbKΌk@'n&DGa%?tt"ѽ(2`Cz3j\Dq'sZЇ 늘4'ɶђ[DA Uw# 5vk}ITq;@Bp)bqV zպ?IG{sިp?оۗ>\ls?nueUnϡz kbwy3$~Dᮯrp-`'m]{gX{gۓo )hr.셃@ ˳dȶUw)T)E9Wy VILIu@5w7V`@(m?ȭS~C} k8|/_% Ds LC-'CC})n:pwņG=ee< &h &ZGAȍ9Ͳ;8S39Ida-V]-;%^ ݼM?濭EuzEAڶ߫(u)dJ Sd7:_ |\A{޲Qo<[n5ҽv}* EORdQ5ڥM1iqCV$ԑ7V|r 0~Ǎ_y~%'J:a8&v:R:ځ[9x=bPe5 }o_ܝM=>oǬB@Y=饡ǀ &ݸtMh'x]9Ql̅@*+8 7 ?6~xUeq1fHʲ7wp*{τmGpR9vws$8m4̀K8b\(|cl[>.\QQtrHNVp 8[>^":W3'ijWtHa:@il'St6-"};3B#v2aqAGr>oĩ)1v*Z:W-fQؗsY{Jf:#o(Nuk1'2݆ .6'-?r@P >W3?V<=JJ-Xɐc!`8\Al!YFwi&MlGdk-Y3cvr}/M: 4)jWFMI9]]_۳oY.'_?y63,&KٺΛ/m7ϛYr.4<>%>pVͮu5ykZ?Lc_W/̖Yc{ <歉}$Κ_LYt=/./?gz}j}0w_yvDfWقԦ볋|(VC~F$_=N|]Wl0&=Ya5?Mz64}d]Kʲ>|pZa+H47GΛͺ [OgL:5ݦfH<".2I*26ezjޛiuU6 3ͦɫ0OjL33lfo٢ץi24uBt˪ֻ6͢IoZ;I9҅1íh*x^&ӬI/85st»뫢ewY[I{a̳401fP|?/YzvUp:OC ]e:e^6Y]\-qz]mR3#5-4?%[/̬_T3Pb^Lqy/qZof@0/ӧfY%  bQ]A+lmFfz34fXۧ]+3l8{!`&VZ5K!պXfks4k߅YYsɃ\?;Y^OMo0 9lj5?7UZ/zb>闱yMoire9._79W9Ef1K/2jӡu?ǰfKfksꚫU1_fv?#Q! fì&02q|('7p5]E ͖R遍L?In<_T*7.0yQBy&O?[Gtx<5[6EO:7sc9?0-?lY,N"9]\JRû"]gEiP뽪ؼs׉a3|msS÷Թ6$Ymᖔ[\p1 gl OҩMڭSb.ϫ'CڌN"܄ 1p%@Yu x"L{u>b$C-] QG7jzxs53^$4j-eyepFYgx?,VY|OgyIyރ Rg]LN\yi '{|tljM== /Ɛgi^aN8rcA3\ y-`89jab?_7O&co,Pd`,V#sfB)8N7k3+vףF0AYaS-+,N(Ď#+hㄤgR\Gzl̮Dsi5ǺUD# zÝ[[X߉vHNHZE1ٵuK܇Ew̉7hLGȺh' eٶ+l[xYEedq_lfNl1MUFnؔSى͂hk swֲY͊#p~/&k.=E9zwpO|q?_>NF9NYV#;I?[ {ttJl 갠W'HE1[+zJg/B*2n_ZUҨa[bZ>~_GI~|=g-G`d MuڲNP̨^ z#v6'?1jȮ sHz ߟ9gسA5uF=2/b 6h*P=yOH5GArg%iWUbmrx~kiJ95C>m8u}ReɬpM >gEv^VxUSQ+(zmڛvVtOr"a`*cefm%ff*0*%b@E,K(7ȬZ i6<]QH;Rx ʡy[xM.RKԤk73e1[{#g)(1_m` _WW*_˼UОBUfm2j24m47ksD~khDFfz/QO"/ϛ gbsa;&6q/r\=I[""|!%:5( mrz5#Xm 0%f&@+Lc~ҳ0ZXIs>pPHlUZ1>O8!CXV6n=3b?*b]m|.ȾF |]ݓhęU?%SJ ͆םxܠ@Q^䷖.5?-~[y$  #iymmVvC2B2שN$8ky6A5y '}mZ]_'ls'f >AI䵙U9Y|RN4_~,!Ǭ5SrhO/&p?{4>5Wk85G厕2]oJ<4;̨(} \ua%Z qwelCǫLC id`qsmÉ53KVљh؀Al\2(ɓIM3D(Y6`e6$ ]3mXŤuġřOڍ+Z7fj&"׍;A| " h52=ܚ4<+yOyɷlA+R۷:\'lחz FNߠG= rqȵfev!k#AWxgks?䋙մឤ'DZ[ ro>gY2j`_v#r-%dYso<{q̴0m:/ v xFLtS"Ic]"F,ҷyBW<1S۸ڠ _p$㋯W^TuNP3wxvNr{T@wYôMl ~ djйe|UZmZj%i-3ڲ2!t..::f2Z\xX3lՀlVl 1frjGc`wgP@TcyN3ӒM?+u(=b^R8}g4{3mbNQߥ Y8=4?OFb-?dhoo$2s W޳Vߩ}?ݽIh*1lJf7}fM韎[@%X4-nLټHZ3~2]6yĬDb!ZU9r0\ʆyDh2 +kI"ԔeQ{u2"9vSMR9dy;2rCKH k%$;d"U@wG-!e6= DȬPqb gx/ $KYtQIAIlUorak*U1s1blg>"ް.Nܕ (xZTSqG>#]q|*ɒzTf?NXL>tg{Y $cҦuY0qH6Eg݀w˚%k7KOa.40%f}z3hÉ586,okT!c"m'7܇9wB; z9:ۃjsu>uyW"!0*ucOY)PX2ž%ǗsA`M@DlT`&6#[?ZLPN 6vw!*[v{V!] Pg/C$Ɛ0P~H)D\*+e,NhHA!j<젳_>F[+C?2Ru$OB  ER##icqVd/ R-4XDd Ԗ5t4 x<rR#2*b Ȉ ga.M5Eo2O[Rv♈&1FIxәg@5:0 >?x~/UQi #DĀx =\E.<3?{EL>᪪?G}⡇mܝ]PI*gm<[IՒ^ w;BdLasm/cq. ,DN9K}g`,mKTqx$ z0 {nCS8[TEڮ!tV٦I~T^7b6bwqIHhtvy5l.]u-VU}<\a -BZ; Xdn W7 s#v˪hʍ 'I fGhRcu6PyM4^V.\s7v'ts/MlA5:B$m6.C:`kӞx#أ5};F5ڒ6G02.1t5/Rak}7RV~ 6I_%BF]Ba &j34 )Z<[<#+C0)3{ecc]%XojI*sejܲr}ڃ:]0<#&b+^S5[S,ZqJ;jnd;n)U\"H,}M3/L@ Qt;@QS/ -a*!FF&.'9T]F%R>}'JUv{#xV.HU`Uϡm<l:9:0_3 %[V N1iuĿ̯g2@0}8]PO̿-bԦg0ؙ&q4C䤩6{`Rt 8@9#^si9/4H#4p-0/%n[A <oRZVUH:3Ly_bj_M6o+젽rYxد3<=-q w;LΩ@[zE7p`nN]Cz0h{E*4F.la,(m=1tͳ(J3ͳ̛ۥ*R!wgX&, ̥kuTHWe H(nC8X|;t맦r- _|rb>&VhTn}(0xfwC148=;`q2!qT~z LnEtߦJ‘nzs]hgz%.gC$8̇[0] JT7zaL} zI$)C8Ҙ}ǒ`]G>B:g"B6m-e &n&!LSEk g)MEh(^=x5ZKe"Ke#<8226ZYhSf˔X~.\X; %%ydP8W# wz ~΀۪b$`L"rT߈7 k9͠>L r48}RB^ZZJ6.:e,mt`wƽrNξ՝66lpq !u^Ӊ-4DLm ; @ٛ2id=[䖌 T8͘d `␄IP p5hw.IVTT7B@;Z4>$ P"%)֯$b dgBrH `N`8pÉx3w BAPE{;k=[ƈ mqTJ;)P9:d+dkKB|@5hy$r ͈FK4E{NVlT hWf rQcb3dfdnpF:61Ќ=xZ0Q ЈD[kG\8>q y).XwI殏\w T9`QWp|xMqWӘLcj5EcԳbN\j&wLhBc2Q\u E] OM' le4A>: Of6+bzXQ"sM{Y]+ZO7C/Ik^qc5ݨp[39v}FIj7 #,6s46s4މ Zn?%rD R~qOO;û5H>Kʭ»TٻˆP"3H$eGt:cȑ+mLE2{נ%oSIahf0df 5Y}03^!5%hSwԠaF(*mXFֈqF_7X@OS \4MU=_nhUQ_e:siPF1fU99JBf9<5Lw9ڥѪʡUxErjv6y,_XMyx^Iok {y; XT 9 Z1P/Yi ު'<ÐJ*i ](UYx%2{~|tJ}yv,:y. -030%!;wȈk }Upp)6˂pʓmiI~7׿=C 8?u@$ODFTq$&㟬l}l\:C%UA(`Sû0E?4/fsE=ъCCSu%b*R$Pa$XRa^ݥ8:ӆ"6\"ctjXuS48O:r]Q(YsHc8ejUMz?.5:j= Bq;R%=Tal&QV"ӷUS JD3_dCB9:R¹X (hvi[8y)TGhԩ>p+n9km>i7;Vzc5Ma1uMsF[;V 7aim8ޱ#bgm Tn$#gYR YK }+|ka.z #V, ƛtY@wRFn ߁60ytjt.RRx5 VQ;=C+ե :UMr qmY{B٦%y?=׷&d<&U(l0(Cm{UåLvA29lBY\lҳ["'DE]<h򘻣أ.'┡r9nTD&ߙs'$.uG>rwa |`7vy 0G_nĶ>*#QS]jbW6DA{˱)\ ~SQgBʭC:'4 4/zmn/~ J NSʥQc.B/.{å{IK_6k FmWsU7c_9?~GBᘿ63y)=@RAŝ nvyDG!"rϐNq[*2)f9 jWGY_5Aǘ&QCDYK( SvgyY=̘K& ~k |ςԪH5*/{h]p? "DHR0^R{~eX^1nPX&X SpC'OB& mФEH0a- I0$_X.Y UDYxtN"9nhFťr2up5!C$v7tɛ6xvlf1͘y錝uLZ;ȑw"ƀ@q֎ȸ$IgnA:9C5 +Ͽ50T2 :'(0s2$"QEģSx4xLɳҍY7p+dKmؖdCp]uP.up VT*;%aKq: NC'(wiiwl7dB {/M~"tY"ADFei.$6jaEs3Kta8fې%=]p?ԇL?}t$|bG} p;ct2!a_Q9Pؙ&zFN(x\lngY%s41fnJvE3IF]la݌8qtD7Y[3F3q#SoD8h&IYu֤6@#ѕU+BL14sZ1Rm[Ij5~x\g:9z$hcҢW='vFD{~%KJYĆY 9Fۨۿ{ݚO x-D\tz(z1%s seRFyJ'5ez>J-٭ Oa>G;u>mzlESEBri.*6Yp:4z%Pݎӂ4$ QFa"LpA?Ղ4ӱO](5†}4{Go}UB-yϟ:q{mt!_>2_q.׋(!2G7׏3v~]tN疛b*By7ɍovMS͎ɘI؀,4ᐡ?5tO>=!4*voIr e0?qMDy'׏&KmeqV8Q 0LA2=ѻrh~+a 3} A4$/8< >; ykKx@'=ZlVĵzF;f.ajr8.WXL>-ݏm+B?:`|ݶy\58& i<XmF,3&NEd`uD/b t{KjkN1l_U, )՚/cxpCqpqeSc;~Y5T(cꯪ&"_pDqHBFbU%t"}vSW% 0FRڐI~U`a)÷ehyVcwLXэ>>U|AEboZf: /aKCi?XbҫO">+ٕ2H6H -vA3t }7pݓc6,jͨ')vl g)E.W.Zp⁰ȟ%s#@o|7i#G&`r=G' 5!&+(YV4N`_/c-2ZIЀXxrql9afxAG!͢?> FF'3z+pmw =UFeFƁܘ%R֨+N0 -~5 fOBp%\=1S? 0㣑@TxJ RD80. 5,?K xAv;}:T:52=Dx4T:Q1EPAxuh Gwvy$$lg < fRJ_([oڝw?*j93삙w7`N>jO_C*#%uY>T2B<1`S_ V4y4cexȘڇΑ_־G{p&oh*GU֚''t$3S;Gv8tQèm4{U'qߓyK9u{j #BLj% cg҅r暲j]~xoB:[d_z7{Ï]lhкFg#SG@enUQCt*mjK3~zѲa0w[Ŭn;.^V:p2x ֮[O&#ÎjY8Ma妢Zi47Y4"O{8˘uSQ&,5瑫7 (Ff;pD\KM\«ByUG-g1:ؗ~@?f u;~G_qRdG86␈djZnT,͡:4w8g" xl[J , ^+cK_a{۳RY1a|5XmMc˄G3Z#|L6LUD |&HʙpMB\W//&%w'e q T*|;'6=6;urZF 6lIKfq ]h1mD؅a#[B>|0NV[v\}qX~c|(6c英cJ`Y+Tk%>:%۲Ux3ɱ]Egh.{[zNe3\&9Ag=ܣu"0c^񸸃GglWDܸd-vklz[U;@g9_R+6Z-mp2AW^3U=c6v I3枊vZ;zKfzNRЅ̃N:k%ق·Ǧ|Lk.1ܱ A[ϣ{Qpڊbu&*pbIZ8?:PaNt\5ž;x;tw'/y 6P;iH"v-ԲQRu!=*h75ڝѝzؠj{)PethؖU'QS ~OKoڲP'& M7g|xz7%tWӞ-ah{X{cC=Qȗ 8)_S[0>? bW)RkvYLN +c%6#VY'o$1'XabLR%J#9"w})bʧ~phAaɑ&G&I.}s R9 C H{PdR߮ΙJ$ޜmpU KBJ2T%v H ~*%#]_21?:5׊ןe~W p_G(ll fsxڭ7|xcr8lѓ9u9jKp+&,>"OV-rhM\ ۘU_>pϩJTcZ`(CV ( pɀ[MMKSJs][A/7,NK()}k^CD T4E/T uAm mbM?A5bGvӞDÇ8MȝF++ ݘ<4JK?Np˯1ϑt ԁq65 B.uUk*C` AUP67To7{ywJmKfx+ 'uNRЛɑ2l,_G(qd,@G7>pOifwd/&_T=';D K:ݕ6ɘ[͏l)+4bXaIǀ,gD ʚ)abD*hh\7Bcz ֙qtTLlU- ^gʫLk.~Am} k=?wd/y\F o~iqpϙ?c\nݔmﵗ(Kj= hD>&Mq5}eC- Mk֓whVEh*5HaLae%2>vs܄'!Uu k1i ȝUeXh,*1K ~$FO7 v"yԇFZ՘XvN ul}9G^QH\f(CzGa ];v098b4]wp.SқEy;$~,2[I)P$f;a0A9h$Cd)؀l8p"0.yP!"vּ#R)ȇ*]Vdk͖Ad n.GO nzK nsZ;c vӃUqhbAJOš7:]/7Zw|uU~zY/k{y*VB5B1{K[E8ZAp*A4]r:3dٕb3wÐfWT j67tZʛLF6,=ۯx(O'װ>oۼE F=81|tFXGW&Xm Z}ow2ƼGeѓrM z^N2kpX|^$a^@Kй@bW e2NjP:E~vj 8Fˋ3RRmPd5@1X$6±̔T3wjxP` g?"RY^ٲJ}MԊ}2؜w:g"!?X na&>;oPNi R$Fp*ID׽d.;pd]+k1)1-o5]cSEcoǨ񻮃a\gOܨn7bӵ D%rM>bo(N*jtRK\ζBݩdM滴LriLBÑTHu.H ''OIng#^[MpqF[hazKñQΚ%VȘ'`q 9u_s非]͸l (Z[O4+P>Q.PB!RuP8y}h"nP xK<ґːhV+;P AP,z$5۷XAZN#Qg0?*LuY4_qnp ڝG_ml@.jXW;̹5Ai #XVcWC= +ε$ lip;ªF@T&>Vy BA:GC@ap4}yjsP(r)]"4P+[2ym !h-bzFǟF]@{w:Iɽ#:*g͌3ԣCqGy<^#;vj%Tvjt].,IGL@Wrga\Pv#NZbUoB;,kEw%t$NJ|d?|q'A ]S뚉=K&YShY:uX֭=Kedތib=LpP2(Qо\>.q|h~oDkioiiU0*S鞉 J.}F 4IrP%4.0Ag@?#(/TO j=~I6׶Ir b$c>#ww[n1ؘ1Tp 1ɦY,:5aߩ~nONN<<)΍H^`9:KIǢ|Wrpe8W$SvJF~ga|g'l'N㙴Y5&璁E4bf 42Z I.ċFZ Ruƪ7p9D;9 WlMFtg!')5aM?$)5= pqϷ,&p2t_.PKci-iJdh;zY]Z}C;C!GfTn2LXiG4Ԩ`-j4"=^.; o"1C]EI 4UGi;[xq㦱aa!.wwSkAqɈ< 9dfEdp:KOcQTAYeQ.I :Ch:"ٷŋ+YyC{:B> "c &NSSF7hfjTlOD]0p8mr.ESܷVF#̐"y|TӎEd;atzgaj*FZhɫcq63v4;c~$>p&X<ɥ:)([$G 'c"r'6JvMfG;q-u.Een~^||u={> e>cQaC[Z";~D1%REO\VE w.n%L3`gn5XXP Beɩ~Ζ/>,~/T]wJ8UR|\Y<[ߩz SG\H^&at4O "z(KuES# ){uDλ&S!1sו893PwꅉoE\CApoquiDzmUWV%sX,$Y-`6B.oUW: |uVcp.7\\8f8keUPwA2 | S0* FuwEnzA ],V~keJR#+dI;096xcW+TȬrlc*uO} 龩_xJ\2Q *kF6β>;[_=gϰ<*ω̶-F_PjfE.6^β,'eV:IY N 2:b-;QowĶc }@ٚ 5`cW*O@ײ`'#+%]Äj~d_!qʼ.eJTBjxiaxyx,Ȋ^pe ^ bn'éJxت~&"mG>m9 TUX#͊nZfQ.Fg0ωSӹv@[T2+Z0L΢\5QnͳC | g_4 sW\ʐp^~ V=o 玫㏤]3[o5❑qT&2 ?~^kvJӽ1Xpj8;kU7~ gw774u I .LZy`PH9݃j  %luaˑx#* ,`4U 4' RitcPq,v&lXMm&z)MπKrM!Nz>ċyԣ[B*rg :2lz0t"+-]MALz[vwk#aic|{fOnq 'OǣoII7Y! o<*c('.. CEÀSvF B4ۣ G@ )L lˣuͼmP5j4Dї qmЏ^-CÂଂ>h:3pj3fe4iNo?۝t*q/s?5K{X]_ܖ Pb8TG vϴ,80[\"a`.lu$*]I/D3G/89z7̈́16S?f悈%:[q<<U@'1ܨܴك57='ٽy-?0ZP.',W-oNNI_8U+ w70 lk1|Otw'qHWާͥoypLOeuKd͏H][c_9It\ԟ]<3Q6Y"cN:Lm/npHI294MrAD?-׍"0 Q\=SI›b.Alv&9nɂGV!=aǣpIы"pN[?+!LT0_l8(_毫L\~FpS >6/`PqsHl9E}-L_->y26% {^2ү'?YO|0;p5lE͋$,c*誼;uL2Idz0@Ccetd@ +2X~W'cQ&ͬhzO&C`ƺ1˩aJ->]$(!1Z + Ojy&kru􌆫근Irvvh8ϡ!e;4#rS> u7Kd4AAf$Kߨ5+ uR18aݙZ({H&7p+Mi/~ a{?X@ -b479*t4duz3\o" Vl މSa&|ް>Қz=0hߵN,eTYzw{"=!b[48&b ݕ@`YD{XI]!rMQ_nd)`rSJmHה;jCULu{ފHe=t}RSh tlw.#ϣBCV[a᠝RhA&\7R7ÅӚ&‘/L_ySx  "hVWEeGtfySrO(&#u'l4J:#cP| [5νBS N) 41A5:Ƹ!^D< N̩Qj; #\W>w6rHpT$ż>n/Օs> jqS鍀:᭴i.s,:m%f#u.Ȑ J(7 UW"~Դڣu@Q[+e%˵ q@N䑢%PA-pDrqNJS9!ZB#\Vz=+A?0:QTΥ>21{"%<($LatNU@ [#E2^s?qSS³:Ut7۪zf^13BQ.5֡X;Ո)yAZ*03#F 5h@bM4'l 1搞 ,&P$Uy^Љ8&w=PnZ-(e;|X=YA^ϒ'>P5+A}FXV%<%tn &d>_g ҫlq >ϏdG!|$wMQ ?+&QC&e [&"< SUbyg]Dfig(cqu?сҧ> 6'TK%,kKdD-˳]eA UJ\fB4qcqL Fq%j氙A6y71 X=y_ѝ~S"ݕ{ d^ M&xڼElP%jQͥkxҭ̏NUr)1aTEV4NS[,rm+]jBOV>gto|ߕ4Q/941|'鑋!]RYI'#iynGGѡ_Ċ$]c*کF7i,bvD,94"&"5` 9u3R'ussh*=)`Ļ~F̒,4),D?;`V8Vo/@sLB8T湙42(W\Bz[|#}_7DphFOH1ǿ}K1E޺nW_qpn'݌No̮Eѹ! ڙ&Ss3Q;&Mp{pO}sN% Q,ώդ⚼;0%cյ;r Vţ>z)cyH%ݻlh22Zy 3B"DAzi<] zcR/em;ZBTPWopW{űeFVC1YN;lQY;*uAּ.)RUz!5Ix +B0ё|WF1YP0ae;Two3W1 AO} pYIN @,Nv|Cq6[bo^\7p>?0T~\!Z:B; |tqu|2# - ظ :wC ʮ:sL<&g#ߚbd:u EX<]Ms'k@.\SosGmg8vW˒!C8/\cDA_(+솭XVg҅kx3abyF˒ )?$úMG3;W1T] ?^v1Nf)|%ik5}o>n޴V}ժ,ϕسwf(sD-1Nb'޽|ջ>g_cE$VEsx ?Xıh lG`ajNH2A  GĂ≨r`O1R^R29̍%MDXԱӏlӝ>n䜛/At{l?˩q8IIyhYIoKt&5;ֈsn>Kݯ= /졖n%5ӕZv-_6~-=# ȼQtd\FqNJ ($'u`x_İۭq^/$;JF3Umz+YtەɑGXO*`0P*8C&rBtOu苴.S&fhxcRe$⨛]ʜWc\ 8 Fx5OsC+q$ ap3j{ 7w+Iۢuh[Ჿo\|G,}؀Ӝ!w$FB+`!J栨 Z[dG:@6D/N>5>`*0C!ro2Ɉ9 ӝTA+=umb ՂXGD b7wA si0V 98-NDf7[Ih)ODc.ka60̙L]g;MMw!oopCRRs=J,1i˺c!gdOJ˓Sem0:ՇqmdrTbYK xJWel:,ԅIU`aC椫L<)3TMKCۣdXƜGKH>> ǭ .adA^}۽b_؝yaKb߃Q3 nP3{/Uõ^Xz nsW-~]_::~4I𻕷Wmԯ8;|4GqX@v;^ھY ĚE1/$/Md.͟E֓b E]K&^)UK,iv%Ɲq4:b {ZЬQԘE(ѲZDԵרWQܔьHWcg% {,_/)+&'@ bONV@ȳWoHhŽfW}Z:fv3M:YԌgIЧyG};}/[UQ|GVuTE/k9&m\KZCG-t3Eyr y6ͼ4*бy.qEe[nu񵩅Xg܌̈-!46gY; uV qHϟk==nD}p͍ꣶzm+&#hB7BSP8XyQ*ߛn ۢc/>l-|wMQeaw[R͝7hG!Aa6/~m+/( lQ#>5Nᗲ yЮHX7D5t&*`2ﵙsOT49 +Kay!e gPN*y)+EGb^OIWO4)K%Gl6_3X=$PEH0F9?wo&@yS/S){D&+rzכs Lly?("N'iؤXM6A9]KUg"<4 8Tͽlٻv`cE/6@' eH nMa-K9_W\)^#%W6܃C?Ϩ *h$awO 1(v,% v>/(AEd זp{?h._tVR*.82 y13˥CJB>.aF\≩6]aOkDtA Aӵq7ұTza$8h\PŮᷚ+ɀ8r`$onQ=b8gW>Z SJ&o50n2/˦ɔϗ^@AtM. A'AK c .5sx.͂5$PcAG9 qS$s +T ƒ4etm9XrѵE80g&P589,t\b{l:&J ޙ">Azj|͢Q[mƒkmV (1 .V 寧I Pi rg]gċ:nczDE)c}ȔV |}٨QU8}@uZ/m@(3۵+xZ [pAtsjq7+ag֙6iN>μq'vqDJ;PK8`ɢYI0f "h9hƋ$F# vW1 !&ENhpQE^$3l>)DM8noWEfH~> O#vۺ4ߴ Sjfj{*w6+6 ѕa=X͆?-`T˴tFL!Έ>-o46spS17ٌ`XBR#0!d*(%ИcLfCc%ZӍjR✨JDvέ̑L,h:@F3[GRCD;7PBOחkDg!ĥ["ZXF|y}3IǨt^pRX- |YF<#% #ZdXi2q L>̽8l֛ٲ.pn¹h֝2ڀDsڳ)%@~vi5㑊ohYJ^5%[rPb+uM;Bri̮`X&.oV+#Uح`ҤM2dZ3OzjRNU5Hgh@0Y뾣rΗg7|yC5AN* "ּYȄpc4/o|𼥔$r`dzBӻ5V`3K ~D#p~aD9z~:jB6+Gp>(9u)ߴLupde'a]&hcB= ˳`xӖXRw{b\0 LWZ{c3(H"DkH\8;=&1%-bH_p&AgcVlй.-WKg * Y2\'^5gQ2T$~An Z2*yx C!ߎ`;j4GaAet\= J1:.#q< G|7| éi6"^C߬!1a H1$o f)2 B *.3hK?'+NuʗWXmd-p R;˶u0^BY3粷teXC"' 6ڠyq،$̾&+4Z%F4`A.m\U~pz:q'#eZC@lYcy|% Z!8J'A]Cd$ϊqě vճe&֞9|H#L=cu3|/.i8s7/o|<6J4ރt]"}gk3"9[ &1N:8A ou$ƄEQLB]>LAD_~ߜuJ\*K ~A70PJ锨5 ˙`F|-AJ[GibEH4W?l?0¹{<J'E p|A ͝" 0hCMP؛PdA={NNw9/XYky&=H 'F:R`0٬9̜qCLX9] Y#<*mcSICKwV Qt> XY@]fCលt9Ct]:bKxTjTp"Eu9{ pSPS/ԝy}|odg/IY..mҮq;ܴ /"ṹ˝ANs",,)6ޝ{Y"YgYyQf֨ѝ`NIBBc +a)VI0 $ÉT^!ڏqVgo6\+ Fd-K9-|yh^G= =O*Io::?N7[d['G^yb&@0 V5E BWK5tr0[g{ :Sc RKҢp msQPPoE~7#NLu'2ǀrO]? QHE羦q2 Fp> 'b_M%vH YhL6ik g7ۛ)c%'%8tMc2b? d`͚9M܄qwHE'(3l h{Q6Tܙb/ҥv7 p\:ʹrShkaũ7W40a#HPS݋߄[WYH 7\b<4!NydoS uP)|ˊÄB['jH 0 ۴伧fвIAl1K*%9U=%Wx\bLwz2?[Y\St&ld L|*h_jWgXFٱ8n pbͱ̷O9 ß{aÿQ#?6@ $NnM.=~ņ!P&8?'ro_@RbƤZ`_kH?x[or@p"F˅&&M ڍ5-|u6#X<aՓD1e^|sF.xMJ?/Ñjhe-6{<͗ww8 Q8Yf '_f(S7)`ժbp22|%n *;35En̹b1V*$Bќcoʶ AfK[)(dOƠTi+UjzΊ'oE{%૦ uTt"ΐZ$lP݁UJO> v޵ Dѽ»`SeS I袋6 v8.dl891sg4z4BV[i,:=D;i-!v40Hf Ge*IcGJyiE\T(}vS7{9iLR*;mD2.އ%$ vYFVPh[2D`M(t(lPM 4G:Y̾ ⹮TQ5 n]$XXLT".շDuo7Q.wgW_nRu}&gVNPO$4  @3U{(td%q:cAf[,dXt_%2b`ga]Y3j P{nv6 ]eЌLчxϸ01 gߔjK4( 'Od$VƮ!EO!ڵ V=ˆ5x^@Ȕ2-ʳa?whm=772GM P(^5kBpd8 `Л"J@BiKƽْX4wϳhz 9*3r)mo䑔V@/X%oUH&cKd~qD!֒-=opcL{,VAlf; e. c,10iBd^!+m˙7CւcZZrL7~ G7D&;lnfL &`{\E : \6͚y`g V)[3[Ft9+"wcuj1p, O\n6'h6SMuxWM"ݲt3(NZcyO'JZ;R>Q ڑF?zztŘѡTM/[o8H'}Z)4tuW [,nl-$:UݗoMݽ-IMO\ 􍁳 WW Wx{wו/}ݕAJTD,[:Զs&^HVT!Ug< $eo'+gAgGk}qY,7__e~r;>`ٜ/Fq/ |׻Qi"\Y7m^,Ǣ]tyk?_AxuS.u&{ܘҧ.BM[wyQ柕+G_{Azz<o,oΠ쬚Wew>iÏΎF/Ǒm_]PM~#Q^s~>PuN_.I6CnEsEƳ]2`;%?UyK{{m^]\/H.ϫKx/~y74/[ 0 ׳6m9E7s9X~ִ)]U X:|I2{S YU!ˁMtW=9`T4+rYհLS~^g}u9ΪrXPZіy Rm3XjUR[Ð*]]4dFM3lUg׷M}#զyS\urqN>{ {=L;-X"$W9/f]`BuJHϜdK`["#E!^m K2%FqX4""E$ϼ4mMYϷQE\f__ur\ K4<=|{l`ȑX,*f@.ZjΏ-!fn|,]Cm)9t_FSTn-zj)tNDUy xj;CEa<vP{fk5_Jx<HޢVJ(SipNL't yx ܀!T3l9 <<< xzYi4DVMOLqԼ6,8/~\Tгhۭ-j`b.=[6z]eaLEc%a\-QYgw54gdUMU22\&/i=0x%J$D,fVvDx,_}Jpa\<堾-c8 k(w9?(AЧ-ح]TL-xo0E'_?-y=EF^#{^oQM%/mdެ3J*aOHOwP,M(ϊUY_K#E~/`)t]\mq^z" b $P;=Km)B⦭ΫN0){C`iڟ,','t rTsH -'LHJ{I9108ci^pe6me}4= ?)SYZdVVʼvn<=ޞBE|cou߼ni! &"DuyN9rzԖ38hပ!L,9.Ƞg*qeU N7%͢{]xHؼdP,@zq!EX rڅ^3n0k+ޓL5Qr$;k8c$PFf wBf1u2X9nDx^0_/GoS7c.>eխDmqY"d@οƒߢGZFsP}H]YZ. s`}r)dt.\(x\AXaӟ@I3%8eogD֭if%=\&h8_ !0l'YR}]U 4X8fsaR#q ֌*q݂ <5eۢUNس;Cq\ϾUmҚhǍ uwaL"_R %s&s(]GL0#}\KV*q@OrtW,gՊŅbyκށ_gbM_X&51P8B\ԤYB@/nAwM u Ae_${#xrұobnk~$ZճB ITԾDpEf+ߟ}XZչ3%:B]^Bwp6']5"ZmYDP*0F]fMݽ2ٷH>}sCU᪙CI{oիn^)I |3Kd kP!E~W|^F,A̋g^腘oK0UUo:M#{)G/@ zy.AssdWfK2lߎ9V,qa@TY^[cxWg_=m?=Qcqޠu,i`m";tVe=: pĢYf ^Nm>o7l]13 q S^4V8IgABx>xA5 '!KY\`aKNWL^.DLP3ϑ9dM]ڰؼfy5mV[mAO! :]2V| o EhSN'W<8zذ`ei;^IVҕZ6cpQƕ xhƆIbRdp 4iU=șC?,Gg%hbC_ px/'4ȏt ^-0L=$[<Ӆr'9H4PJ\'٘x^v8l&6C9Ix(ƈ^k"4Ap ,3N~b7GFvpRWRZ/φ:ÆzI}^zmR 2%Qn*$sأMPt\]kYAa*"_5!Ά-uBj]S4 N#\ !a)CqfaYlы4ӽs*MGUkʈ (ˬ)T 6?I|Q6XeMT㫜H|]36vEAWE{L$$!/W--] OV.>H͠h~9!X`ZVP!mk<7AB_-SdqM2A2#&ɰA,98؍xvKrG]`FSnp%]6CD6EhH97VY_SLbAߢY7> qqvA N?`ۇ ?ŋOqLjQ1ZU c\,~k|h+t,ϙ\J VqVAa,B ¦2 JRX'(65sE`TT:wZq) Be+Cd 2L x oWM'ya̼.@i˲5z<3(%ظ$jP<5Z`a%4z/X,{{+X{ROmTW>hdA7b FvSWg\WLL[-_R>?%ﲬhy/ wSf @` ۨRa .zIa;.<Ӫ)^U|y&rJGp!s⼵Y:ĭUaAi cJ |}0-SY$|G4Y 9S<>Ƙ>xBå륄e*W~I#\ql9W"7c4\8sOX%ȏl !9}tg]tdbzGݙ$@+&႔a2gr@S8&zU qdž5kkO]"czl`aIJA6;cA=”W&,^@*.]Pp*N%ҷOͪXI%f]72 5H@Ah ߌ7kŚɀ;)?l1ٚ(9H-Nexsgh[{ \)ʦ5ZňkWpX^54,2Ł8Yh+jEm*3sU3:$2TQJ):5vaiX̆3 AAF';(S1jG]Bc?8@%ecP\&=^qU &:>~]P7q(hlqF$6"XΫfc-o16U\sib/QU#2$ϛHںf[=̔ A??zDu b>7w7oHݼXC,BDk5|j V. {^ۍSRE986I5+Vs<:ƮR "~]NX\(96..9Kks2;,] 6(`wyOerp ʓ\!FbY~qJp딕W>:{]$@4%oar6Usy O-xdz;o+&yn>qÞg"owV,oیG\9*ݼ~ SV~;Z/0m5ޥ[pz peNKƵxоQ}ᖴקN?d[TxhoRĪ`r+SIAb[<]R\G hʉSgxq.|FRަ`c:I[$Wg_rݮaN| Q\^H& |"%sGT'ӲX-A ΎqYRSB%1Mi?M D2BmߤNs72HuCNVAAaIz1:8u붴6zE6aM6: Vjz[ۢȫѹo3'"v-V )L #5 .˹5fj98OTRKSa12.gNm mI#E_6|NQmcȜ1o3`2Xmtfk0 E'fJJ3Fn~']nBSVD;[Y~^\V登YgHGn_h8{a&Dz[:``M.y32m`Hn5]mXyT.4U:^F:`<`uR]i/< "lyk  ډ̤aNJuEqjnƸW&ߑ/Ok U4zyDM{%Le69nH#4i>CIUEGq5X9LY:t 03pmM[wji]jMb*i Hg@}b;*Y#lxT3"7qkڑ<17وs93k r dEmi<7vF:J6PՃq8=Z.Vҧ,{s{(y1:xϰ\+Yfz$8L|- ]X.m?c,& 3[,#mMGZz.Ŝ=̛#=߾vo-.R,|Eګ3"YCo^1uņ Zo"ۨMUJʥ }4G n1w[V\ 8%.+,.Pp G1Ҷ2-kUcH̞+LC#*!OAs |f*8>7jZߦt7~߶0{0b^X k%ܶLʵP 8/7s[dѫV.Mن<35;`g7jHz̨"u$$搶kΓsF xȡ Gi}h趥VIFEp8A6R/B YCfOy`̴ÖlK*v7lĐtJD\BAwCx4 < {V[FB4tıGZEj퐛 j Ӌtƽ<42fKUfdjEfP%;u.s*uX8[73D/pG㛺89jbOrV]Mop}6)ƌh%2cAܻ??@?P8sQ>5}Z҂tM{"vΓ1/ J/\fyv\:lS^k>h8cĦT1n;=.68cCc#  L~F s4!w[G[9g;H٨,9)v4%xatK|U QFM?髗V'kj^JlFD/J>ff̶{NzLUd#J赖D/Qu }7 / BpԜ)) M_ym.LrZG9@75AŖژI4˺_XVu4zoUꖍfZWU F1*l`e7 k/_ZUL.nVG{vۅP]z\^uMC>d>Z$/.>>.zeܩK kG3;xm+ DM"6hPT9iq(gͬ ƀ$b[PSхlYo;iZAvرhCVːaXSiPѶ`ERmu$99>9yM{EqLЫ<#D%@5@ N6Iu48Z)y՘x9X r p-dx7tnp`ݍa&N]CY|8V+v7Ǧ- 0%u׎cA[dG EO_|'RYp= 2[(<rf0Z۶$ U+HgjMVZIlDVc5(䉀Ghg!Xi˂*TdQ<'X4!sҤ+rK,nAR fvBZUHZYC&+(q_,9J3}A+M'.s#Pd\5&9Q^=}ʁJAZWS7S}Gw3&Bpk8OBġkQn?OWp(ü 7l*^@ert%?KXTz>KMURq` P ' 0_qwKO'(0EܕYGhֵhS;׀?XpR2[ }s(taʦ"zj=%ֺ(è*&yȾ=>ف^tpMed=-d#W4U:XlO6D F|tVK+FK(:V%ݥTok'_N񑤊&-b~f^ SgAˌ]KtYn,3&SZtX5vK ցˢ /#Ana"<9p3qI]ݬM7_$rGO^9oG_- h >G38[ r}4~}4K>$H^*R,een lJ)⊘t>9QCKEzv]p5"F8f~b(Q9lT>ʊi+1ePp&"UR_:9crcJΡTͧ=T\&K&Ac/J0 W.yQ/W؀,Vf0_bI%y@Vχ-ڍA*2] byWTa\I9$Bt!mhW/sywr}D!08h"߹:UaU3/Y _U],Ud_U4W)_%aONޥgK1*\ڸk[фwV{զtVLc5t]ɺ[k<"K%". #:.o ź/$RE?POIzPOIS( M\^w>ZlS15ue6:MT_׶}ML>H:9k*.WFE)pCc:p^>FaE@%&(_6:%j&<@~1|<{'].i.a\uږ,[y.*fjT;iP\]P+yeXaL:ւX(H(®,]C&ag',蟯ziTدY1;2xEM|5ruHifyIr& ad+48QQ]|@'b,.P,<'ܖ5qC:Y͉ʱ֢24!5=tiJGz@돴,=04/b"ݯȔzǀ =rm.#ށ$Im%\2C@Ift4Kj$Pj# , WRWZQ\\ u (UE ެ:Og1n*9^KMhBƤRjnE,_;=-uc/kxKKǕJ`KR nMZf)*S2 ).9#;ԿteȅiX'Ln0=\+xU#ǸP4V߼Nڮ0RyY@w3Ո%~G_H( &. zqhA[SśkHFQaޖp]!d?Cg读/I݆Ay!|Lu lIRNtHjZ\xK:`Z>g+JY>Sh$xܶ z$]֘P,BO t)4=$[a4d"E/;Pkhm{QAB»A]{{}:oY6B ÄTqP 9֨y4q侶`viq H8qޑ[FW!o:J̮A%~1ȯup> %'g qQp|@ "ct.[mn<(w)NwR-bӁTP{k Hmyl>5l6zڥ;.n Pock9m3.=Zss90\i \hV0TqIy\\a9ȅdC̽&ư4Q:=WFvX%_iy\[Jc;th|!~h:c],|W_z+΍={ Hr2}hddj L7yB];XII略\AX‡s lHb~:M>:[)wW}!NɃNUiv5Ns7Uƪ:h?g$^h2RZMrZGr&0,0n+ձ\GKm0u]imfNl"9XNiE⁇聗T欄ŵҍ0UjzFՉZq:Bg.Ԯ,6]JU`C@'PB٨$:BNigK+@>Sn RΒ:Ztv5t(\F\C(pUFT #nAQYk3CQz&'Lˊ[Lkug+?ۃe D,l^6 |ƞEqQ/O*4R;q38Ï|5iN%. *'q]bŬɌ%æ|i6ra/RP OV#pna'bd\XHb,RjG B,Bo霃W|ÁN?`ۇ ?q1GmCC<ot~sݱ[uƷY-ўT$<[P+f8c`5aT(/x-0 /}Xch{.uz}W)*[q"]yOc݅OÒDi4|N,yqz))4jLͪp HFgpN;,m l}*T%8/q0/@ZS!*I%H@4MHUlb*ţJ5B[n`|dGwDc{89S<1eLjw*D<\ʹ^֬fF y=-lM}`"7\cizLN꜍=A:U=lX>cv]q  M@ߕ;BY8qڦ413ZWRs@L-我?NR1+TuUvYCW4b(T*$/'X˫G ^.;':$kY!a15X-Iz,b9™ K OSpljiArW^H8n.aUOGgl'kXӥ%i bZd&t3-si.6fe2a;V,U!mvc>F (_ISFaK= L=@!eM)}7iFu3ҌqqFO6"qe7ޡU"X4 =V.~>Ť°l?#o_n15GHdĪ Y1p?v%N-$"{@d O-IÐw-7*grSPpkMO |Q=.Y'K (J*M*X NHpa0W2A\K !%w|VvޢӁ``n$T؟,Q1_c$n/d_ ur&CdpGZ5{ayy-ӺcW,6IyA:I`PؼָՋZ &&) wB߸Af lK?*NsSqe7}fQsöy'R#"ұceE DG8t 0Lkdžް)p}pCP}9;\+1覾/fIm Y^8" cױ"aAci,{4:A2 o.^5v K? >FO<2;|恭 E T*g(J|%F %6TcGzD8(r28f%⃓݁.iO0 ݘ(P YK]H^)*6sB[ ϖtMݮ6J؍Uy綛::k:5Oѧ޳Tde=-h ׶Tס3x~A2!qZ1wC˗%J}s,vDtVS\LQ$HO=Bd;rS'憈͓69X $.,*? !*0N!NJM2ϳj .~LRrUki%Y[g]+Rg7# rN)ȆO4Ee*|qQK,Di{G3tLYry(eВcgN"bكۅM!FײNrno6͌R*_[ %e9&. VmR?4/Jߺ^I{ZЉC(QcGr KJ@.sը| ,e};J18$5uysm%SUq͆q<:ϽB|< QU6AK,mE8 {!A3}5ߔxtߐ`t?^ݜ#rZFOGy.?w.լɾ4:u  e Wt s_Al.9AiSauUڝ`, q2gF`Df YeɍlPpz58Q{m7Qse6q,OZ{CrF=w; s w; n[wݏw69%2u?":WV`<O_]<9!yh|&%h X\CiQ0E&z}`R~G?8,1QpM坞`Հ M2s0d'=d#UN+vI]ǰҏx5\5²e=I&"Im vsvS]枴گO衭'qxwhw9 7'[RXM.ޟ0'/KkV/){®۽bD ER`P1>V]ao_ )jUZȣ]-Fe@J쉴.Mqĉ_yĿe_AtuvpvʦƛvڟnNKo=h 'U?.|A@g}G4/p V_) Y쬷1p.)gi WC'\'x#'jg tn>S5Nr)G3ʹa aVH+_bk(EcC Y%(@-ɍ40\>vu)@r;L 08C|ݴި XXز^P鶧fh+~d\xMubQX}T[%4E(*\'oPOAil-ۿr$*F8utM'vVbӚ߻]'{!byj~ח SCZ~%!I`7",)F:/5 j a+`}HC6"o;h0J :l=\.͋f;_[.y6if"/ܾu&H)+H>&Pp#^5PpUb޷D;{C[lWC=EVVqžrItg&YzH,.5*eS$E:`a::F݆T!=:OHmJנxgK_VKPɩfk2MΚ-*=0 @VG3pe`6' a0²e1Fk:ӂVjc t Q5zH>.NE_ jl?2k%tQb̈́;1~ZYP>ƨ<:D*זLŶvNEqtp P}Dl |˒omC HBRr Ɠ(Ƌ69zAύiX!ǿU{(ZS{Owc $[3+sM8(2Ѡ͏&X^pA*EM-Xך1%Uw \$p W׏mW0V&瓲b!̀VhsDgR{JNbrmQv^5 dc 1Q&F$ba!9᪙mZi 5|]thyh~j9HS?Gep )B*dryqFH`%}ڧn%C.Wo "δA*xFXuktdӈ vy2U=+1 !ۑQ+PʓNCs8y1=*(#h69DN%kpT㳻}$֪i`Ӻe;Z4H9a"Q2bwgY9RĂ U 3aLAvl p¢4J.:^4lrOL,'dp״s|SڻUw;b% 4{9~#޽uƮ9׼HphۗIĦH[}R3y+\gdr0BG I&g (w#kr5yܒSNΪegMn͇fzS=^LMk$R9m—Hlq!+a18;ؿ i(g|*|isԮ)ַ!'YV/?QI4Or}Iݾ*iu=[5N!wWhfο`*$+mH V:==2jYlB{Gʉ16[|Z\ !umO_MGtD\J-!'6*A-6렵)}VSi@ DA2L7'N2d 3hӖcE-h;{*p([aA 99Q{5;Oo٫;&8'cNO;%_b.]?1 Y/eodiXE!F׶5oGy]pU]`1^G,>","޲ Jp>۝&莬ͫ?cDAY0Z[`'0tH ģ%eLҭBBcn0 r:38MRd:ƞIbUD,S-7J{nZ8H* *R_u\="I"1ei=<\֊~i9s$U̮fi ooNΡwI?6% Z>.KD #.>cf$0KkMhJdy"?g{;QSw1mi,>pիɎ\qLH{v^ F"ӼL ]hbakՁ$DgzoSҶƉ΢|니.xU*"Plut5*bV]%5x@rʟxmwXGl}jKeh*:\et/ԿCnCM5~FFkFK L$RtQm $ uka@}5칎6-b-+雔%9KSߠ셺պKT͏JZQ[-fj8ׇMz- Jn j֏(J^;{؀=6ɼjZM-;ϥbn}\PҲW/b5 iw4t{3+?پXΧ9;="gbf|ƣMb2>쮧=馩X yLcu隬]ĬB|"+V/È@jl`5ZT .{#eieJP2 >b ϥʣ9S\ ^+ |A= ˕+͛z8ˈ9V#6H~XvO_,ZgYkɁ8Ad`˧v+);E/"P~77՚К~@ºRmw39H-EW+kVkܥa-oZ/lYsTDJ0ƛW|Ō}Zɳt۲479E?/ ?#K˪f6r_JKKS<>zf0f:c};Q)!'tjuWP9nѰoDlW۳camrB5-܍tmw!J.AbSl]iJlİ2#y)4j W?[~zD hk0 ?9BgGz,`љdž?I2_}D'b_g؇a6l3=V)v#*-9xK[.D(3ΟEJO"M~]A\}ey?IrpU |wxYdjyp?WG zpx5/Bֵ?mm3vjcwghF#` McÑK70An$2P7%ϐ_őKj ȗ9Ul|S0;Ჸ#'Cf99:2#tw0ঈme c(?UzԃI kAMX9~''<|:*|;Y8+MPh5[[bF o36Tشi?J FJD&CX?2No2^ nUh.,SKdE?zYDKąG>k%/?ҁaWzJ%Ht}G $QNr҃俾j9+k-~ȍѴ-:lty{5Y`@9cΖI1s@[V6=0:er٦a\xY#ܠv'f%F?wmm47T{|ϩQuZW8@OAt@Hqm= v>"ہf7Bq3Z߾n–;Mrm+m3 !#DA=ޮ)5N@Dԇ cܖQ}MTxE~&_7xryX@3@o( 4bXLg8Fu<*PNk i/.ŦMDOG gZt(oćxH?M]ZWёF{lv:; u lxЯY1iLZbžzz.{׬]Fk#v;[_e:~>/C <}\iUu6Β6Mtto2e٨)ǸqQ1֏}iUս`o/1~o㿓?%zUl:+~{$YZi=2 oWrUW=ܟ} {ƒ|-ɼoVNi-@&FEih5&Og|:=׉|M&eHdɨs=U?eQ'0]/ј%n@[…Կ2bRt2)J0PBuu,[]!cY$ēL.lE̊,r,Һ̮$:͒ˬ<)&w4KbZ.5uuCx]]&E-'Z+OsAY6ǯD '|ʎrXR,H' P10 H?#VSˢDR's`a>qL\P X}}lNbö[TSϓoa撦7W J5adg@Z[_9Ⲋ C]+/ 5sxJ9~a< :6D 8+Y!Aanyj\X 皦MpzVTNG:\@tp"؍ a җ\2#c_/*\QS"|7XmlͣQP(e X9q t_+]ppɉPct]ռOktp&9!<깙#xGxxrY X"k>wbe>gv3pT-kyx|΀|S (J$!*n/6ZL|A748H[.uR-Ŝcby:%3ğx;y/2+J3I`ty$:UQ?z Ų?+zXKc ghg0Ѥ .ev[ E>}^k:Z#d:Nt 0|rOS~g>?}t-d#BDClx ?NpԵ!˞^6w xޔ{e0+YD$!uQFHNǴ-,|yV-' rz'7hҸTζp#=JrGThIdl˩Y "60N'L$Wy,'- '#bC$%YZV\e HS8!`CGr __V=!KrIgK=-99L0O5Jdb@)c 1DX=, D,[9T|\F%uxXGGPmiC=yd;LW`@JXJ\F*YN -ytUlDryX`YV ~ibC`Gђ0$6| g'_U^ySP_+C n~m=)^P䗫l(yB+o4?hYN t9,xYI1UJR~%uz/o+n%A˘"Ji{p}Da T3"RJ2۷Ix{i8 u]b`ocI`@MlTNCVNI- qmFv6/-$pZ ұF(`:%l`WDl l-`.āX\KajI4(`g;1%0L3*PsD,|fkPm3+,%:/gaH8NS ;Erݳ䄠 =[\iѬ$c]! V.@XY~xex|U*+ʟ!Ͳ;dMd3H0LMF+סb4p˘@p}dL_+HФ(H@/!Ф:"46J]m`X).2xK%pmPd̋&@@g$f)bSgqsbvxQ̔+Γ8hmx|L–dhpbמ}*nbsp4@^GlĐH?g=O 5==.5!jfu JTðYg6?*A / kvm=6Ы?ԈGR/$6_CBՂ/r KK ٟ~ gz͉y'w\48V ,Wtc 6$Vrk+ۑS9{IRCWTiXB| FRMτPv7mGp6ٴRlMy"!Ac;!`TauZS\kh n+U#-21Ѡ}:i&@~@ޡػ$  3[&](xVd3givZݷq{c?9!A.V*d Xf6&\Ul?*wQg<_1u]EhHZT#?FNJUl &V)GH? "5#\U)} K}fNCbT{^ ,'e%ٍVĄS \j4,J9 7B.~o[V^F Ή Ro" ?t _ BOۻ+Mu>7&,(}amκD  Qفp`hqqN_?ևveWUn}F /bhű<R6t%4;5(n7HrrB xۆ ѹK`hj&XH; ]ds[[o#-|ULXb&&ޠ)'¤pUQx{wU2|AJ #>Q68֮IZ@۴/Y'PGK} e`1Eb2O=6]&*?Flv~H$5@hqIá/Blr,1\՜HA,35v&]G1,I-k@V$=Jo4[nv_y@۫TK4xe$^jWy9ޠ1a"B}+gCWF|KCV\6a׈H8ϮTQ؄4$<fx߲BV9YR=s9(iw>V&{w :Fڎ~-޶7Q9-:JT\ F;Q;l=8V 1ܴX8^<a9<b :a܅ͅGis፞Ǭ%(ovBAS 寖% gH()cIbSB 5SuTw9=;N U |Xeha'5~nؙ0WԈ rD2-Hgg0)Y3UGR@^Jq(Y?S<)|T|hj=BCw ݝ߱fyDjBs2M}T%W?ՀqW|iwLΐMɊhďv*tۤn[$/ ɵ@ysyR6|:fDl=.X26jKLRX+-vJt}ѷ ^DZ|>J֪< Ks>N5mN||J Aee9kJw nx"^<4]҂oP Zl M}$ JU{ׄĄ $_a!<_$ϺϟisLd<нB4T/VQu5 6A!<Z/]= ЖG)0vi3= ==/ǝ~?Y8AvB?X8@e7 x8_^ug0ʨS7y#OQWYS%]+z̀pBf-M8 _85ڣ.!JmjSU6E=k~F̛2zcoLi8V`x]?8U-g/"gxǎ\ɝ*dd97q,cM)0<Ϝ-/P%YxvEI/AݪF=22SsvL' QP'E[Xr"Oede. '(cH 19̘7\,'xAM52oGe \\g9Xa2ÄZ$>^XG@TXc- ֔7`c.LmMHLM1dzSev1UU|-뇞zi_m%k?`5²wJѺ<~7tX)GM1?RkbuyiX<6WubM}859tԃQy%Z.s7 XG$NFyXM̹i=T:𘡣+e,&B e8t)`W- Qݚ᥷pHd8&ML0]lZX|D/0Uיq5 8 4^^d:|zE @;=`A|`8s2 %6U񫊄(r/~ _9]ő0Z.{i=c5>-}lNH9Cՙ_%w$W y͚\-$k('}Gs]h^g%~EMq,dAc"gH7F[5;sgڋuu llIM$@ZO:jG)8 C74vku?ˠ#a wdT[%7EgS Hsat_2鏒n'/wc벬`f]6,b/I'SNn2ۊ)oM-zآR bRqs)\襤=jФ<f[Z8aeGÌLJfWxlWڂL A~T,0CPMSxeŜJ}$Uw&{o ?`*юQDXd1Fo퉢尓Pa'4kb$4͟ZM z5;Ҏn`'y%rP[a.~\5#(b0W2I-P Vp%dFkSHҁQ"Xݝ"DiN[OW̤ߢ #}ǀ=. 8NT3.r]4k4DTa4B{ "kӉBea ܈/rۊWz6Rv ށdSXJ#,SLB ؑn&'X9i[&سIhdZ؞jӆ VמI1}.qĮ aV(.N##&O#1k=qn37^?v0ZuϠ(NeS4tdDI/gMr(D|k' !a4,-٧K"(eͩŝ[RdaB'9=:NCg@|I,ND" "EV_v3gwǦi}xi.[9Ar|Gl oshM7E9lj3]9x%4VA`օ1;`F]Ӻ'w58:wN걉WVK'*@5I DR]R {@3/i*M fĹ?"[L&eܥ3͍ gI^IYIwؓщ0:މpDKg83|%5VvS7 2B9Ub43(ݣ ┮Zp1UjtW^yχ8>]OMH%~$^(WÉJeir)$8 ^{{[*]> qFK{n\ gMDJNlq_ԙt3 M ]ӾƼӺL7Z[xu^N52Ռ2ޫww˩aou A:Yw\8+Zβ^ŷFP#y.l7ɺv}JN63ι*2wJjqf5xeZiZ-MS9(`fs5%iش\eˇȄnǣ]sɢg-%̓mY 1.g4Ѻ $ f^IG%3&P읺ڴ ]馿k7ޠNV EHsX2EFD0µ/m.T&$IGC hD%p({hE, NNJQ91uDFb3h*ӧhgwL 6c3%P+aNhom\Noy @Yl {cS;-t|]"MoiMgŦu1 G BvcV%nсoQ:TIP8e|OKT14aG{"+Cnt{;_BԴOsXXp'-a5q[=F3t㳵aWV(>j:*크 ~},Xi8K- ĄLZ,nl$<d3$r\bK@i@ꓴ{eC3>G4KH)m'%M/U0~Lʨ+3w) 33vaD_<]Qdf(̫)*'l3}ѿD~oS0>LBm0ON4O=LGms U̪ﯟzk([EO+"Gݚ-gEI#(8a=?KfUY5CL!u}i:b꯬SKCRi` HN Erf9 Tʟ!'ω3i)p2!ݞ:ZlNJޠz LP}:r434a4%_7Xr|U#ݔ8B-B7G}`qI'97m #|w|:jkѦM°h ؋%ӓXErg F=QN &g߱`aQS\BT %v5 C"WDlB5~ @iaW_Iê[1d.p ev-h:BVгV/mO|<%³:1f(x? N4B}ƣm#m߻Wk=3qSڼ3\i]o`$-31I OqSqnJs{gc6ydIԮMkz[%VH2 ujq$?SmOA>$\f-xmZx^_`Rk+ țՆoUs@@dYИ=P3*P0b`Ye_&sIA 5 zNs,_-h}m-OW%ۤ|??beӬf51HT9oQO<>X{<~75.J:9G6]9䞸AEnt+s'b][zvN)G\eh؅fxpWs/}mV\jfɭUqxq&~{nܑ 5rMbl^K05Wr~+2h'qCoojnut:iQ-F~*1uƷ%[3;'"32M~YS|dg,mrbJY-!8s#I5< COdž;p9NlҟN7S gtgN:6-.F/(BjבiR2z8ϳ90"]K2O&˃kl9%oh ~4C42'`ѣKÏ kؤvU~Fj֐&~S_vMma*z;o7/ww :4qg#J@!AޑFq1F12 Ռ&?ջL=jF~*~l&'$T˭.[!\B4=G7y QE=J$ЯK@wN^,&fuv.#a gUfDw'2Zrs#ǭ&4ҫ,KDoMI˱m{ЖXvKڙB-9R*WHSIBۅc^OWI j'|s>{Rӛ UԸFF$s;ӌ U*ɷKC+[-KEfu\ p+N@Vxwyd|{sl4Hάta7vz:u!׆9'oC*ҏ]OXȏߎKNAwq(V8hi) ڔ-Q2l;؀s,u9w0)0W[b (h29{V5,>F _x; Dm0;xd[@jkzݶ/B;&'g(&*obNꟃ{wNȋ ':SSLʊ0=k#JHJ dG;oF+upV7TDM_-9)Lf!5xP+RPI.GU'y0cJnkE? p·*[d_.qk"QQyX\u` D8.yBG$f1lPabt ҅,"]I5%M{B+;]X {VXpkv΅Ikh2ax/rie-`FgL]i|Y(Eg \R 0u! hNHZ y2F A$<]>zl(#a#ED.#>*TڴNX"NSO#ѻhTGf̸ߜ 4{ mkj%,ހ5d# _vɏf9:`!3F3WYncoPG e ɐh+wl¥gjc|`cqs4- /spVHsO>j2@̏xX|^ѻUR݁TY~svNf>#gQЩ?twRY"Z͉zay1ȇCE9x2Y?"*c&ob}Aw>XGCVekEiC¹gHCk!4pfcx4wQKC#tl=Hǂ^3g?6˛P(d6GjM@z\#U57,'Ay%q'ˆ[?W(ey)#q%(pI8 x)t46F(RT4jd"r9 Dи/(&cl*BAJ,FWZJJ~{b^Exuү qA+mHqp_t^xLA7 y_M}V=*#ڙ)I2;BKS NT~'1Z N"&>YXEGq7K\}3 Vr%-.qN;(.W$;s4lӳ|Z_24P"o`~w)+CʆTm LR5.XglzXmߎfPc54LhE&fnظe ݇LҴU !'rqB 905RUxD-&wfhG%lt=R0"Df/1ŅbBAu^\e/ u`CWtbEKy5q1RPKŦ.)$(,Y+SE*A`0ץ⺬U[n`YW[=Z#,Jg@MV}*1ۈ"X25e.lHRC00V`i]S3pfցHEMJyzh Kv*&|a!}%N{FUn28e$Stʎ_{ˍXD0a$;St)3llӢAGtj3 eТ KKu[׺ R,ul T#yB|eUl%?2 n+ Y|An2 , d!ee'?C TNk1bUD2B/EsrdLGO+*$_ Zd42I M11\ddWJbuA(v؅ԭis)s'Os;Ϭ:-Q?Qh,Grk^NB5KV)_p ;a֌WMN}~pRSB50٭mԐ,u= lq;K<ؓ|C _ heq-/헜)`_k躔c d9D!MRL{LA@)<W“Xj(]7i#ᨷ("2,ݾIqA{3`mߨg}߰VhМ7ó5= kqƚ+khYCj-BD@?dpT_KAla3bSVtaAY@ՒnU{!C, pStEu~J3!ڍSۚDU"NKyE{WiKTK~l}:թ#B:VcXi lxH!PpWk;YVeGJب<:DaZHIbЂl'RCKRѻqsfovk:[^ouHAL UY XwUuy̖SdIo%E&CNE/(22.,-%gb.*زX:īa `I??n4mbV?g ԊQ+lPEl8הQOHhV <VV6o1+TUu^3/(> HTjC@X+vX^,[If?dϚdqO̶Uݚ#-헅?Y轶-^}@դ mrp~ʁwmJWnNK.f'Pߛ'6:%4HҞPqԓ / }`CF`к1;&܏UOVքÙ=P> /( ѝhK^ Zoނ=UY5iv渍޸Vq[z67_\Wޞ+hqaT:-CjК?2MeC՗ _?FS\)kmf0q)8"q^0B.߼9iWXkδ5*t5OO1X5wG\ E'iYDO#&]럼~ɻɻɻɻɿMdU6ߗA殇柢r/ Ȯr.W9Dn¾ k [[@cU5tez1K~y6ەf2UnׂA!]0˴SiJon<4$"f*sѓ0M,176ጌi$TlTZ8VNh1n1LJ>JQn7]bpKJ. 'P2p:hbsI(P0JG$% ϥ rnY1Jz$aw^<ŰER4D*A54p>O(;-O߉*sv&nt(!HI.+"IN~>}vNtlC󺌩^W{<sw)UQ~كٲR+\\gz1py#"t:iVݞòX.M̉hQT%(g&Ky=",MP{ x ,g!)ݗ!/Yp<i؅&M@2e&=ZgA}T[OZ[1HŃG_TZ0Oڻ\|zjyAdѣZ( BB3 j0x=ksF+攺ڢHR:El+w/-@C H=eb`:?~zE2 NNǂ!a^^UN;e\&|tÜ%l>H| d2,㢌#ӐႿڋr<-FG!~2R` < rte>:Ǣ$,W{Sx^&G^ӱ5x4'鑘h <*C1< J?zΓ$^tD_9o(+A49xTKKL|(7vǑZ=˳+眕yamVl§aC,Ң̫5@BnVA.KVz]EIOx? zŁ;2a/s6)=q W WQchP/7!~h;5ʳx9&4{{,-p&QyT3YQĈ: q[!&g\Zc$T$ s^Oʜ\|{O7Opqc*[ѠؒNEZ!ߌdgI,& C_H̑<%x(`$dh-6rd:h?[w9SdQiI"6[rf_)]Hf_o<5v/:ncP`%/{~rvUt/| 6HPZ=ϯwQ{~o_0i5,.eU|F{+^/kk9v>:z(UG  ڈ2^#5< - `Η> 7veIH!S9~1FJ(**ak6m;[kFvƐi""ЄcxJheXx}. oREN҂S8u\̰7b_}-Δ5.L/i9T:!!{d:O/^K4-;L`\eK*I=}8}bcg({l ~˓(,@1BKypWVO|ߔE=PO%im %aRdic G1dƫOӊ)TȐyθz ba,a=g" {oAOl֝M{pEY t޽r䤓,o? ˉ/܏?w RB|$"^FwּG'( cg]tb?P<-2/y3-)C/=hz(c 17_=+40?.2hŶmdj;0mT*nɒcNop9 \sJLQJX9s>Wޒq#:]Nώ/Ĵoh  9 :%Vb9ox 3]xdu+A9@1(|}=+_} 1>"` X ,7[# kEf/ w. ;ˢ?2,J1{h"}Xˊe2 Rzni!vKEasIi|j""#%p4!!v&]b-8t?e+7ZwH^j4F}r7qVX'_!A. ;4 M&C > a9K\aj;}9s+m.OJU=VVr2v/ OV+S3F$J^9WZ;NgLw2d1I21,OYscfYhzz1ɬ!5/p2vS_o ݪv?Y5)t)\V˅;-li7^n3j + ([n)ܨer%cLyV<狅,DلMG` Ie2m05{dI4/}:D{–rM OM-+pFW74 @ѺA l p閥MtփOs0z'6fiZ 77pWB`g(qpA3qx_&Dt)ui4uDep6%W Me~ SJbKة =@ p:pk T0S /Lf 8+ԃri-ڢ* ˓)!?qlZɨz ,m^\LKOLG*! R؅iʹ~CF?p c~h4/WKDYL?6kUPe]@` k n*e@+ b21unG*~\cF*XmP*!>4 bٹw\]d?Y a'CrRTV MxJh+L!odMB|W'Ƙf Ty\֍~[JF6ǧ^ts#à( (ذl/Ez %#Bņ)Td4H9؎[n ފ>Ess=~ ZjcSL{DxF#4u~;,#2 qpnXba]/Vͱy>xy{حl`'(_ljFmϵcva^vl"܅\9ml$XBĎ.ڒ% Cn4#hgޚ+F⠆ LV X yP5kpєƧ!=H To|&!^ABB>9\@$#` 'p/Ko$' ꎄT8'DiX =!ifPasL,_$}߳Q$x,c@`7@@ꋓ[)G:"h۪yZ6 KJ^<1bDZ呄G{wˆϺon߁wڂypPjKF>ᆂKlH+$w 3#4xk'xp  mh U˙,/ ?!x *F4@(?\qP9KRͤShfAo= 7> hf3pr0u6Ԭpw={vxl|ymD\1SW6no]j*w#$#/J}=]Aƙ]Ўlw^ks#GƷatD 1f 3_;91{Ba>$mJŃ\w$E,P%x ɂyyWB -aa 'd1tv)'e=6^vbIUQծjЪ^,8r9#ُaGڴy4!kǻ]. m:;!<;"kG΢KU:Pz$Z=w\[7|&n zi_uXAkOp~WG2#ε*\)(L.jUG8V`1V zْպC )GМ*s,I^i yQ뿐ʴ(ޙ\~YV`J1Y ltMЮsXN G*—eڞx7\PZ(ݐlj5GQ XUL&I "rF<-ې(|#&{~S)m^*LDBf_G Q Y/h`.? {+Yv26e2%uPyS/]_< E>ɲd[r9}]*D GߴsF"}5KWѕ(~}Q/?ãGӗhx}g*Ag@cIQV;'Ukc kCk#KVYp=̕+}z ֨Tʲt}O~sY ;ߑ-wT?]_3*91{gt<ٌ;?YR*}@++6IKVG.G)x)qvx *wMo%WzO"Aڏd줕ޡ!8}\RRN?'y`Z'i?`.D)"ZqB%>⮳rv6U(nr z@Ԣ@jf)h. k`ƇƵ$. :4_H8ub Xm]S@U|b{ džQ`DVC=j"y`5-ݎ7"p,Tm## SxZ,pG5:Jc9z sÎ_vZX Mcc%!N#G뫂XcG;F` 78oI+Yu<mz0/jIY f'EG _n Do->=#~ 8qj K:Ik0eo¬PA-| ; gcO՜Ǟ(x}:h,qp^n\tėG)V(cm#oz"?O'8&|VTf[,@됅#e"XO_v ,ŭp_xtϮeJ6J؀wsў=#=qӻWBerO mBuuJr $"p {>!n<<΢1& _efȀ3bu<(IܪJݰ#ҫ3. h[dȢUf`G`B㥂G]0Emz_,9VT0N]ZpB"46˞IS] ]$6& S O%xfÓ{ճ(;:jH+8_"*O`< ;pP:$ 1yݞ."K]5 ' l9CtxWO2҅odfI$v\:AbHjX5'IX$mj79>Y3A$C~DDf((^vh?Ϝ|:#eI0i0=Vz1lrn@}NpZ`0K.> '&(~E/T,mG Mvx"+T  `(AC_ÜO΋׉OS0`!"6j3SB?$ ~gϿ5U?wbq4-9#R^. *VL: ÚFE~Bo% ̘mƽA>_U'I̹Ntn7`4U(FDENalDWRqU\\JAqlv8 $LE0夃sz8/UZ"SQlDNĢ'r77D^ eTaA{.eG=*e\լ? Bt@.SR [䉼*3\UucvZ_( I }9ݹ7fV_z1J'|#+’&<챠`:0X*Y*Hjm:9Rv7s&lIfriF܃u3` C; "e״LlP㬵9"ΩY ^_zy$, <@ @b$%F!q8KKsABPBI?8I kDaQ jpA0]Tg/lTçV_d VJ uѵ\,Huh:|+~wd=4!Z:?G!ӡ&BI kY #L)3pT ltҨ١ F:BuQ`ne%]JS6ZӵBqaj~#Չ'xFzL8=t*> @=d3m- JמhϦDъ̛5I+ vQd/|I{팩q(2HlL1nF~x p9BuhODta_ˇBU{EN[ R& aVJ{3IՒTǔ%2AŜܪ+ժ$P Lx(M@3uFvPt]&"NѪ,H]6U[ $( clup %jH'"諴:hԆ ( *Y0Kd5APJn+$C\: kV7kh@ T\Ko.HHɉΕI8gƃoOnRb?SufB13%PS4Xxx\)"-X~ I`0.V4ފNbn[ ޵ y>A=yuပeWdSӼu ԫ 3@+aqIlC&#&b#iW}yΩR;\6bB ZhP-+LtLDX J/&$TdZ'ޛ؆|5wn~ WG5=ٷ| >u~F$k%Zgic_ @/ (cho\m5v'A|#g#)$e 'GzK[Cp)5g9FIRfc|w>:ƫaAO5Zr~ zw1wWNLCF}^wMpor5(>$L}'W 쒵;O"z%wә?RwFϠSx~cGi$*Wc]Q? GDg5>/(WH#4Un =Zv3Z G?Ȏ`lPШSLH@w](f.t ڦb7KnD% Ȱma <0>1gE)q_o#o\dZ |GrU"9Ұ!>w:Mei2yBjus5UU,r5\\Q-] aR 76X8kVE2TA *!UwZd{qwõ%+m0uPԬkΒ2VRuaȴ"S[q:0]3].(bxVWiV"`tՔveSWꈍHӨ+ɭ@vL7l 糏JS\+SӢp^X X.JA֬0' [LXE68UAm[EfBQ=#`TmHre΅C.apG%s ">@.K s4/׋6\!lWs)!HVi8*R7\YH/]]нkdt~4og"r 1ҶHk0al Җj_xn3{60*j4ɪK)Ba59\Z6X$ ÚEPu> ?yL;6M-q(w- ,% IkͿIjϬIJʰãXka!F,8OJ'(z1Miѿ?G8F-oQȌQbuYoA\ o (~_5ntS*k=\ZsJmV[LMoY,ۊ'enn?4pUߘ`[8F{"7V|@{\RژܣE>(p #jnt4)u>$gwm\$)CJڃo3:ւla^2}tx6Γ-MًcjB ,1>ʛ۝֫-(%5¸\t|~04fwÓD]-nFG = d!E׸|2ΉPZ*]˯{Uٽc3T' WI&f\>K凌{knUQ+U w5\|M&Po};vP@)ܪ$SpC%ԤkO܌"˸Qן~Hq9*UuRV_Kg/F90~yt8Zd쎸/.ˇz[Q(jQ3bk_Xu&#?pCJsK0s8oc"ox Z fuG!}"G $%UyN~_ A$ ^7X[} =Yb0/o&irә$?ǯ|PH7) WOL_h[Bu_%ob`l")z.u( Ão?v.9{/2ٿo fwPǃMB8xpkd>~؍]\2^QΒ3,\%NB.fN>}nBsHBu RJ3䚀V1 {t VJH3E0FTvv*Nh@U}>[p( +a,.Vս`u6{I-!Z :y1 DK]`Cz@-bI;s^wbʪ3QYAEZ1˰uA߼#ަ-73؏#JDÒK;ԗ~z_z<˘g+07,o76(b"{qQjvGG~> hNc7Po{=F]CMEn(ʡhm܌.عCyN %{y)U=u*BMp)w*2:A)cwoJKҬliz݄\am18'3j \ ϿKc cFkߴqf3[Ͽoso -Xx]}o@go=C㶕%RG<[lCm|^|uv3Ϩ!N|ߙ?< ,)DOMd#=*9Z3x*;bzݖpr]nWkhT"k I ndMCcECH*%^@ed<뻊tVe, z'O?tsf;ygěf>N~̜%e:='k?u03i'Ywu I+;gޅLꆲh@I7;]f]pFQß+ӪueZj%߉vJ`j[ v^1d 00jcR\(bt~ք1IT08.ZЯ;r{Oc@ v˗c^s ѐ$B׷m:Qbce©xo⯊+GKFdS^:e;́ AINgWݵKą&>t#lOUݫ)߽Z/uG4|Eٷ~{h6$jm c}?woQIJI&+)TٞEfL[(TP&ˆoTKFWv;S ;{%-eY۱]-~;@-KU\xL'?.V[=+~ݡۜ=i;bհ<;MCS̋Jp.FrrӔ|2 ~-ܳV}I<eareU)crWb o?AFUշ A@wѵ]sB4>fHeRoYQxBLgRǸN`;“>[?l3Z\M$-2NۢnqyB 8d߅@l6h2nbzS n *>quehzznVvI>'q>^FIUaOk%,1 ?C1Oxc_?vBS[fREH..=-VhH g'))>ބ|Drdj!JTx3ts#^`wxvn/ފ{CiLaqB"kq |nѿ+@DZdZ%sQpO @ؕVΎ$ Ӥe_tOy@zɄ`ɊP2Hԋ$0:Ee<86_o\:1Vm2_zcĶ̝NO1^.X# JqʷNt^O&X}{5kNi ;x:#mq:_l":i*M`kiЁb,Dg3:hKRFrF60]6trTQ`Jd5xW:546QٮvEJRؔd :XZw~uo%5/YLpU Xk]S-<>!Q5ɻ ;Qr&:#k&`?t@4d4l黆r*<`?&%`}Oq\?MD9 - E9#R8*( - NTb?Ke6@\O۪44"3'JV2+j5.ن*tSzɹ Zl}B9:Ed6>#:A^ 77 2,^0qÒHֺLm"DAQ7*A!߽ l 9/z=/`Z 0iNz=A|wMOh-ݻHnwkJdI뽐zA>iM_غ}tkyZ|W6ιĉg^Bʝr{xЫcSl'a1LOE+;po5=wȭ-Ct\%FN{:A[m݆?O2{.lUs|!T+~HB,v5{Hs "}恸gIĭOD\ ĐuzF3ʳ |%rG$^Rj<`75!.vA[VL0`87kǽB2N&, G'[G&79T`nsҹSy:sqyلy^ă7^Fƒ3b- Ve`ۖ;d|tԐ۝DqXbzꓸ YP!' (DHhD $qW8igAd!urϡ "]fTEVQlIg W?جUۼϤK4n%Y"JɅ7 *+f.L.V­sX%Q/&s5 K`6HHp4<-H;),j岧\FrO"pHIpk:~xf;:z̨4RDCzwn^k \affy33[Km](*cQ5y6[tyn}]Kە(11 R6ڗx@ E!a!BȾ*~)pѷRwTڝЪ!6ihr-(p1-zhKd ղP;DSœ^r%K`jU`^6UpfSL]!F$?QxMaT͌ gЈYB}M0 T!A SP :ZoFnXn@S" M'9ut 'Рn(-ܰ0v>O⇇&=|p cOMz=>ԣ}i Y$xȑWcj#GFXQ"4> |Pj G䕒}oNKU "2PJ$8q18nYp!O㩲7L˓eZ$`Z# ԔńKFDZKd3iMĻ)c-lHC˦Ah㗜.2,*= Y:p~lTĭ :W('A{.ɼИh0:AKА\bYMS^lIf J]r+yJ/Tՙ`Ń#J6OMثwS,Ն*{3p<+>@[@tk=V{vMָⱓq|~v8dעC_w{B"!~\Tt o'?:LG=:N:VqJ=ٛ&yO;}|0ǧ;x!г񴘩ˇ4K޼ǗIy33S}7br?`pjiGIIP*KqHTiOqm,iU_*݉AէaAgpp\?|qz*ŏWQ/K5ˁ9l;X $X*@r%궂cfw30#勘{6 U 8; :Yqp9pIr" 䞐' V> aff< v׹ Gkğ̽.Y 0$ eu^-2N ѯS^**TUΊRDVw)0=.Pt$kD]D&GH-OoaM#>Fdo`sW ErI3*-S ]~fԚe2`UQ׈8EDCN`ԓimL 9FhZ@MMaO"\DE_j jy-C>cMCC\tYMb5C.}0~-MSG=z D*S+RYA1MnjtnB79a3LXFY!I(gEBsHdW(ob[d"wk /8qY+ be pjȢS$=Ȁ)6űf"W؀ۍl, p]l"&0$nf!ǎZK;~|LVY:>2(״L*L`\q{iEtbƣĮ_dgRx,_:M)iNȗqd"Y:Mlt}LJy:XAt!--g;`Nd KgӉa5Y`H)hrsҔ'Oɑ[%]O&5$9}H>6ŌH醘RD,F2Jm :29:kFԝ ,VHCI˄w5M#d QʊJ`nHJ*`$aKÂ;zR0PxΞ̱Gw\kЏ.!DIL\[;p #9 l #o$Zg+y%̤ 4]=HZZ|U*Y >FH6 D#1~F nX$KSJh! PIx + ʅ?_b|4r+ǚGy}*yK="g@dFH`gy;i|0cIU<̆.H! Qe(dYR<P4>\Pe-7;۠+rK'/齪ISAJ v =S" ։k\ 3&湍+;6! hЅ(C#كmu]p%WX`+VZ͹Fecw HHkZ{,ā C#Qα۷˸L+8dHhe.ja.6}[/4r }WŤA7]v{۶GĦנ; MAN1YR#*ܷfH)AZEm _—KVY瘜W%DUv)? SRsM͝Cix_׭F$ |X䇇Zw]{}G42)9x>b1pTGAYB@UәxY0 j(|$yA;zF52Ĉ~ I1WmOH{>b#׈@Nքnս 8鑹ز,_5v G$F!V`pvۮ$cypp(XDaqc^?np% B7z3a-ZRYx~M_i<*d uJn^^ չlCeBHݐ2 Aw, K^v_ajv(cq,.T5&3 mFnj2k%&xeKj֧ffN: 4Qy)JNMAt^poia>Ȁn J`0Qm||~H@B>wv\l vaWbF%㳷{CrI]}]j˺S//?v9g8jA=BۡƼG.`{Le2g+}lE4~S{# ôtNy_A.㵣2Z*ȫ6;ChGBn32e?Tt²ɸl^ b jfΠ7{'{s8!sn~}2ׯ`nրNEQCrq^ Rv{4 sUt\U5CSIKf!QR`mN[Y(sw}ݩ{yћ$QBCF+!ܟ0;(vRLwo9 ~`[D)q>_ai*v)؃9V {{c٢"쐒ʑx@шXR'wR1_n̼:K5}P%>GHBLK (;;øddw;0ө=8>86sC 'bfE_KR4l2Gԕ#IW& (94n}СkcoM~)PlP3/<[6Ac/wgܑ _ݴ~yմ޿{gQ&'{WIؠRV s쵠j^Qp1$Q)71>ϛ7BP~{hGZr[8//]~st^1ps)}mf0w03hf_M4JF=eU:ӯ5[_(±K`~Co:.db^a <Z/xkoF;Ş?$6@I'Y|mZVJbCqU.iE9}qI.%JcuX;;g9?~ϗd/Q04F`9%9`8OG}GD b0cԋi<$ )I邽;/p@q^'p)B|4kL*Ļ8,4hW?;2#r( g SB57h*W"/  ZpDNOl بk{1~6gI/xq}mZ2w<"!=X/<^ nn k9"?s,\"(q:8 |J(QR T>@X"m!i!z M&E9@ܶ2jI,fQ`Hd0X|Ns9kRQ\f@ - ad\$39L$EļH#g3@2ͅ5=/ E1F|8a6/GA0,PcPȈfr&9t^c7GѴP O(DrZr6c `f(U+TCm# _皝Cg gg8@jHJKa?(Zm$: /aH?ĭT t*!u= FAd6~`yklFD mʪldm|9i F:怦#Jʻ,aUpް#1 '0tVZ_EnFxkM13DL et.?2f#(c$G#\;9>~Q?Е UV-lx}_U&+ LژQI!2eY*=OCx& 0|trwuHYA5yov Z _˫]3հ2d{7gnrcuF3H{~An{hApSD6# Q;CR-qM[i@FԤl_/PYd / |Ջ*"`F; F\c4`ߎF,ʄ)LDYCXȘO&EaPb"M E n7lřj̹ Ecx. hED*E$1\+t#ݨ'.2>r Q!H')鈂@Q3 B<2=ŖlcR/oBm! 1PLjVګ4LӽJfϵ}0~c"Y5ɩm*1A^P驏JR#RC,Z7 M Jc*}0gjj0܀)ղvhC[_5-8KpS _)$o _-*u<k\3ܳ]jkYY:WW m*wC[8Vo:7n>PUgB ku!];M<;Lh++|vnh0RQ Zm R'9^){ieVs8yi2<[pԽ%:eA! sS%)D4xCZ'`$@>hA?þQg8>f#ӈtW8^l̅W뮧 uogh2xO [ӌcLRP}1)gB '꡵r 94}pMǟ[upU6:RbTFN4Q@*Ds#ۢ`Ooic ­MiGH毸1ܮ'j'Z͆ vB%{C}N0P|P4a%E՞ģV")Sa@gqTjI6n "o9jyfaQ8]+FmA׶BRg-h4`dngf?{"9%f*itl Eab!LI l5tA񖶻 еxm+6$@TNp]={+c=V'Kp&RŗؠB8;ref^isX먶_]!#wSk-#Jd t YOykUSLJC"')^#y7I5z(%tvLfXﵞwd![' EJ!Lo2oŌO̕ Șn0r1K̴ٞy n8f ^ء dc\FJWiAӵb+!uܲcr [Kɦ'\ k3 thb*/A$Х `92V XYBhӫN hKb<Ҳ nkEm3eȦxmaS3%^Y&ʲ,B]LmR0C }AD Cʘ*h]IOL+*R>,iPM$ Us7[ܗ|a34/ :D93 u"34gp+zʓ8T|#0̒xuoʋ̌ZR+SwV"J[1T@F (e! e-\Wf59""e :j/2s5/!8)g FIMZ2=<03;ΔF- rD^@ 0GR;Z,42YOϣVGZQ)5\sN06wu;M+j J>j|%g'/>\}9.Vܛz!,ҭG]Iڙ`}E=5@%;WGc8u6 JipfF ^S{ѹi?O΢eDDyF@ߋ=UZ`/.Ͷq6g0;{y漵!XI` zl暺n[$[.qeZu1ZZ>2ChίH1z$e.W}إ3mSB>N"e[T;3fkԭ$].W}*cj9Cc=L5O] hނ)}CuK'4}gb 2睉wlLX-˗S?@B64x[sܶοUgܻ9|$I$v5!!+va􂯋j`J8$l_fF.`6]AuȫPR;3@t;$JS,G ϊmxUk<^E}y OO̱3] tUTr2V!g!:i#oⷎ7KݺD-ҶQC8+:.t=8fDA.>m\ Pţm?\i0v#ɰ9iAY o/F}BaQ㹶\h e'JP@O|\ԠLW,RH} /vLh& -%XjN3xj~qvd7&'0{ESX5B^Z)!-~8qrr O@xO f-i9쥌 EAk@??Hn-QUSCvW۷vWogeɊozWX5|#3znߠ{tݼXy{Kn 7!%֪cJwV12[1Y7W ȺP\n*QM[qֻP'|=#-޵):\UgZV8вBbyD140cO ܍wdC%g(=%=OA̩(BˆBtcþL14*m$lݲH#@_ll{Ό>'hķ>qk"SӁoO^S`v4)Gh~|+T+i:z|hma~وE}Y20 ˢ KUt#33e oĀt4og^'|B9#1f ) "c5d/v) }I^j%ïiI$׺>ϩ򫙐9]yU"$*h0*GW 0G==a#dnPd%yF;>h^]4 җlw77sc[ {Įřkd]n7– y;. ݯD&ݑw>~-ޏܔ`/0fK8k_q0VjҨOLOsLt47PpwE pHT;ZyE *ۯ F#!STk.^6P{!ށ`>1rDŽ~#&1g vCPge U~}橨D?ϴ-^bn:?`7GNYwZUItz9σ*%jTh\ < 5=^ 6#<=#=Wթo)?L:ji;A*\*VA~ v3#f F(K#csu9B$5}u/Jۦ垹SYxXYXc^8xdl$Ha JWkWlb̝4SReK!i}o}w귞2ހS̯IMooDTkKM^na}s S_|*rQ"eBg}A:N2r[)ӭ该L?e7]{,LnE-ݠvm ֭`9ͳ[Ukn,"L֘ :h~3O+{2!֓\E\s& K6pż`n.rޮbe.GsI6 6UyNaU+YFyp"?Q#1F--nu@JoK~x ,ױhC1]l1)JPhC5-J&@[( XtZ}D#j?\\P/:]׼8S[ދ -I:}OZ/Km#mZJ|VkB?+%\`\È_4`.+Q,NWag0 _Q_^M:q.B $3WR$(.WW%(M5.(0E7'IuGf^GIo NȉrBFb owt3;D%ݠ* Yϙ@u撇׏&>!|KM,*Qӿ# " xt>BnʟSqZP_gq@{wz-`%a&~'0ڋ(z'qw C>Ot`c[SFǗ?<6dDt y_?+%cCl8Ŵf)͌NC~Bc@;vCdCdäM %O%f" ftVF]AB,uHYc Y*|-ܭ3\=ҍƎ8벏+Nnf~[AŖ.x&1fax!A}5jZ3a.U@ϙK.r\].N1JD+?G"7J/Ɠ] [0=4"R/R/0˝r8[œ팍&C: 4pp oh&q_ ^7=;(8L4Rq ϒ4PDN8ńqZ3߆8j qx{vBxmJٽLoQ Œ/Y:A߈@hҙ")|w^h[0\uqʪaNfRBb44+AadQg4qTeU;}SliF0<)OMyn}2MUJ,m]ܪAW8UaٌՕבh_f <[hv=##Gy$:gC/5i=FŸPS0yTy"L钎cf x,>ȒYgY#K|#Kndimd3 .d,\d#}FtK z¼S:A=q241dꖧJWY̞~ŭju#ZfE3 Υyws{wv餍þ_+ ` {!Тz1,m 'g+:{'>9̖ u▋]A7Ȱ_ձ"#F9GfT69a=![FfטIܐyk*3w*er9g Liyct>9FWuy " 8,z?s_vAk}? \L%?b zen~tؙ및)<sx:)e9yc^}br(57*௙ (XkO]e%m6Z_.khۢo#AI&ʧn01/ɋK{qts鞼0zTš^Ľ`h8>5lJTY ģ qX>Vo<4&J}`] 'd[ ~CG$^,AmX.K}o )΢{ &Kp#cƐSf{L9y^xw!뀐*i\+fxEgiF+ QjxeT9T6x+pz0'W#|_*+vuziU>Ϩ*6|J W߽ 0Qs }sKφ݋XӀM Z]6ej/ܪ}t;޼@cbc.( KY]p*U$eb*PZ~>K:(Jfv6FLn"k rTNr,khI;y kw߿‰Hv{[/33fgJo*\_%;zOlKnXz*!뫄쯥*a~o~q 5<n}7m=7{Q.u[&=MOq[RϢ>IZO"#7 %(,vL 8b䀮I1bA7ڊ`!nۇ0VGG5gaōC? c㥍a/mt.0bq r|]ØF^Â.qxc9ڱpf"jBŭCc|U; Ѷ5LB T"/,3cY:∥z_&Ԩ.Q]&GZSp $;s0Y49n~1LaBq'3K1KFQKFISFQj=dl|$%:NL&ɩɤLjEf}/*~kBBQ0vP0CXP0/m[`,D(?ؠ`,xd@,qF ΁4:4uѡr%xu&zvjHc?#MdVFPH} 8iT?sE8iR kt31t9L!H7әrJ kQ\:0E 1J .;3Ü|mFfhdbX1\aAe\.@џ\ 2C}M'~t^j3i@ru ,1,,T[o,:9e)o'$FN!^@IlhʉyeX"70i-1l°a% Æ}h,$ ѡ0lF"1|4mMua3aƪсm gk,YQeu,Pm0)wL+ʜf4#`j^L IjLm$7mL9IlPkâu)V& }@{aPƶ1uP!uP+kGRRi)+(pχũlZmD'_C H|/JtQRqA}25]3(R+f֛`vQQΫ%k"N8';aH]}ǁ=b֧'Hd\?%0G3B&oX]O6eniKBԹMQCk̓VR=%yX$OQ91w2ڑzh(6y"i,_اB~a mJ ," >鿰`InQ6V1a8%dO| NAZʤpVddkثMF6whR$ 7 9k.x˓G<\!X4aBERV+ |f}R>4b<))r){$Fm f(v?vs2`n8c*M|,9+;9zGZ49 :LYb } WhyLJ#d:jz yt ޳{yayώ>B#@\|,g I*xݶt$2\DgOd\ߊڬЭsla[`/a[`_z#ڶƶX-Xqib1nS;ՈU]Y9ܤ<4yCe3U1#)]xK <ԮH=\I.q_/qSk9ꬅ ie-~b$-ܾZIqMK1rb $?$.eB ЍK Xk%֏E05Ul87NŰQ27!,ޗ>8װA!LauhgD9Ճ̂>&ԗaߖa =`;N@tѧW+`=Q7& @xtuJqhYЬ .\gi|hZlg23[5ִ&2XtE@ yQ>`\-1VGC{,4@h‹լq\a;'*vA\4c"렶%]XEx&~@"#2E^0O'~]D&ar:Kr?If pqDR_67Pgm"3]voÂf =XڑaѶ 1{aqCB1nOV,:femp#-1ھeH^ Q:)ojqScFK%h ;jPUhHPH%jnWjϋ3톲=(qCG84q0hdY2'z{=z(É_>iΓ8Q(nj`ݛҕ )V,* }P҈FFq\#dJR5AˈA^5Vl3CushZ(lpcOe}˩X)XZceת'_mQ;e2ͣ&:qqYF4K鲨CKDVs}٤=̬,G#'}o凖~Н;NwBe@yX_b1 b$B$KC{4 ߓ ~s&#ar6q )K=|%s?! j㼥]=MfVACxCU&bUVPNK-q9.Vn>B ^Z; RMw:7aWAImnܞkYo߼/UARg͑CxqdkjWYT r{eLʸRF#!yyX6^7Ŵ2#e0$TCW7т ),X.ĵ'vyX?+b^:o*ur-ֺjt[Q-h<׿cr9aN{"o`7 B\?AiLk\H(;=%6_i}x]GY7QuOOnnaN=`T=.!s|_rO))TqALWe9*V"y[خL*m2+TƹFK6dB6d}\'Ozi"TBUEdmn8jC-w δu$W'?|O^٣8r><M]\Z:yVwMhlsѸAEAj Ҹ_EmVjPU/5q-J|_)D:$I)iԸ;oe`[߼)LH?)Xp/m[ ,D?@ ,Lb`11S;e.̝emRk3(N=yQL6m5tWgR d]jTYYm6t##)[o:>L3j;qJpN4 5s*YRY߉mEshrk)I1X4b'˟2.#Aں~IdCʘ[BҫhszJؤa;haU5 YqH:3(NE45޼IՌĞ#+qeԅOLmG31Ľ碗T{1}\Y2NX'Ll2&H]S#1WRN 6~!vi(7 !9,.;8ppb`MFGPj`_b`g-2bvdU',j4Q1`sp9P~ Ă _҈TŒDOimm_6X}t$ $0¤#¤QA>HA>L=L>DAfđ0rT8#d}'"nζ}CH䎾ZĽYHyl)gINNi'B=|?> $M |!@)A]}B*sO]Ӱ(Ec%_jU..Jxh<0q^G?΋@ )Z>Kצ`oø{1ggNr@.IGoa^Lq99ocD-M1\#6IؕQ9NIEHl(0K;vWXtqtkO5ؼ4Ta(bD` `\ *eg@6gh?oPJz%sx0qia[v)6 zv+W*G-+{u>EUu?VNV zЪPKOiMz}z2RI|:fUZ8r3JTw"LS;Hn:8]1Mb ,ϓyGl`j'[ǭN4*kŧ|>2+yB6y65lSyRej{$QmG_# Q:k]+y~Fn޳[5MX/kKqU׹*p_; e9e7]kes6~POJ17UЊ5EEmqaӖ)9"w+[j!H ]. b橢Vp. rjw`S 㹦|'%iz<8IKsVk& %9QGG0x=lh^% ';)uwGwvR;Ofʅ>H;O.^ T<훗Q1UE4ݠ:O{o&&X}yM:)/݌!u-=^Zعz#``!Ґ}hZIB%KeK1mG<;5mԕtZ++Quĭ\xN(٦ۼZi^wWHl!آ[$-ڑNsZmwfgpب{$`TJuK*?r*nh&Q;uUiwD:wDLmwDs?S191scʝ&HʬLk\^` 0e+PpCn\2B$&oW}kxC;a#~Îoްa'v=Nx]h^'MPyЊK(;v;(!y^Bq.K(2n@W7;tdo B:5\6pB}{4OG4b1U#<<`>ƫ#sud#z.4_s$( P"ąx}mFw 6`wKvzmiF<1֍bIDhlv{>/t7a/]f  $%!bF TOV?W:~=`aUaX\ۃ8|}Y4Q1MXyP$#i':|` /7V9~>Y:?]} ϟ=X*m= ֫ Ӳs~uǾ|z*Y} @#Z,b|2AY*dWy~RUz7 :(B$ 0 4YD0~A4U{\ Ft+a_}*Κ!? m:۵G8ZC.} IMV݆ }7(oYfp7}GRYeaI9ROõX=x= qp=÷žWFNJA<9Fy k&]΅Az~L,`Ϳ׾dd9*h͇RU&WCw#&faR{בfO{.m\Lg bvQ?~,QX80FlF+ug,{>~Kaa0~A^{se<ix^$MpࢦN.aY\4 4T G ?7SR*4.E.cĽMǷ?Zeiqb"/6ALXmkV,]oÿ}; ۨ:J0f+bhk y6 ޸nq9;L(ķon )QiMÀ~6-{BEPݫm^g>+7gcr?УUK`iM7P00VQa]N,OGtl1݅a&WT'Y$l&t̡AreKcf 9lw 4_(u b_J" q7# ߅`Xo9j~Pf~AU1,`΀ Y\UJ΁p7iє6v=(S |΂diNgq[0 #&DFܾ{!  QWQʗY˓r,'Wg<1)֐* T' #ɲ$"kFPt!)IAd%G]r%G$C0iQjT Ւ<815[J?.90-VFxd_hޫ.#k{&ÿx4g4FqTGPW_ՐqLTH/gAd8IwFLh)Z_/qDZڨT fhN1ܠFt^+Ox-4zeMR ZI_4m:9(TIC &;úk~ߑ߫4{_[\(kZY!ӷ;0ȷY lb MUތt'. 34,U,SwMa<&hήŀmj/?*L*$`&*5dܨS6=:v}=f+ /K 1}=ؗވE(E`/Bn{O##juۣlՉQv ,ilKZ.v+gtih~$3D[$R bHnqƪqAѿmjѼ ==[k ^Zl~~ұ_mB"TR zn4qh]j2IDNsC P-m.,rDSݳA%#휯E䕳yz0 7k>~Ʃux~X.= ߘv?Tf~JTo2ιK {9wa7`:.5L=?Y\8v8]HrٲH!R$CCp޴oxi|qB޽d%׼2.-ya,]i]O58mnAey1:^0WA_ZK>W,A.^"5+QvRQw~572T%a1.Vᒯo 8,E{R>@O;&{YbZĻ{OQ,WYMIw|2Q4 UprI*;NQylύ~X<͈xͧr7O\dq춛M,g:Xl<z$'}^VX7oRs.XbMɶ+,80a6f ? >.2 ADX`m+FqV8ꀬ^H։kX?6rvX]NKqwHmjݒn-VZwK>nzB(?8?7[1σ 5"r6/!$e7߉nf}y:;'~sR%MʋRb nwU; ?ډ׎nVR RyvL>#bHBvJ@. #% イEm9MiYC]Ɲr RrC,OJ65{_.EGM|˜)sئf赘\Ƞ|o*ZSS"Vv)51iOpuG- xpfBAm̋צ⡮MF4ۃla0 #bґ=f,{p 2R,Xz ^Ifrs<.q2Sᔢb^taUr(* ]N$Qȑ&I?g0,f+߀b7QI|m&WY+MFo"NܷwRۭw:ϳ!D5c!usWeT܆*_T\].SksLY~sw)'>գL3Y "![I\:Dc,^r, a*z %,5MbX}s]J}`̮4,/e"*k]\-`6tjV:sM&[Д?P\|VU-^~^~D೗=秗"Pn*ė.^Cz vsþ̂+wLHk BsYk i(`op؈,%lty50gr)%mz r0_b̰ * yGݷ]0i"1͂K%Җ%N%gģ>D1xܤƏj*M%;Si\(FMc&uf? n it \t_q*NÅx/G1u&qE)h@_|YֱZEx]PD0N`g;kM ba_׀96'ejUjhVж* .olN;AX1UB[DCfI]SLhV#[?VSܵ*\v),Hv_5qTo=)A 5X<%GU(tѽ [fRc&Qz^6>mq'SmT\"P l n w jاSA|w &┼ *κ$v@4Y~Poj%_d W! lCPV6R~7)㔿y(FDF]ltLF]UTRA#)ߖ+jn|u쭕"& !0VQJIC) {-ZiRJZ fXr`@/(HSU7oi޸3Bf?o錛$uAQ[㩡쾵:)14aٺ*ab/ב]C@9JvTX2(PI4mѴ [Uh-%*`s9 ,8V^{ͬ1F?MG\t4T'P6*Q^_U3DyvD [z@vF4jE92=ut(=kjyxNAb)ӺmTW~ji!LL.qɔA1"lҊmKiW zIJ/[A:/ Qtx5 z- 캽m^\P b'z{IW}Z#(g-'<֥aEf b *\IY-7گH} &hlnxE8;\_jLmbu9N@+'臛+ыõ$u}G[zo)p+,eԢ[vJ}uTgn P`/ix5fQ*a|*{C+ohEh~i[Qcz`;8jzhT]#Aǂؤm'5E6s?iz޽'rqAqC7ФM'EhؿIQFPnxxi"4~P'`>!ݙnSb&31kkn/|XlF- ls8G.yَWd줴^P- {M4t)OiiR|]M)nyER hFEsv0TPdnVn2>gJtԲsfŎN?t)iӱ$W؃vsE=tjjF\3сͼ-F6ISۏqlذנ፪JK]۪l5B`RP>ep@4#'ś2"+MAP}9G^ շl1Lj?YF:uȽ=0'قQ\ t> 9 Ic|hiDNzlkbMxj;9^Q zH,*%e) <3 8^i 9 m =n`xde.. Xaklo&>9H6›_f!MOD~79I2QuFDlo\tJU/%q[Ub$d3cY[^[,"sE0oOևC>,4^:%Kg7ÒS.]nzKβ+>IE _{ َ蒒8f} '(0Zpr4Ġ'=AS`h*K/^etn۝>ݪ"ō;X'ntmY;6.AI_ Eo_jWLJ0#iQ߱د=jN!c!^oq+A?M_Aل&M7Rą5`9i$h, %,BEnW;/Ţ#5 {Ƞt[O{!Ves9]t/Sd+6ӱ!7z{˿}}-O~ͨkDAShzYD;qM;49leLIP84&1+Q"]Њ!m.x3#'bYG`Qp.[t3IGa* Vl"ʳEBͥ;8c#V喰Qb2i%~:&W,6t.K%6JШFXE5G J1?tjzDݱ1Zg[DF_5`TSn|&0a 3 3̹wKO3eKgڰlHs(Pq`GL`>cOakcD=]hoAk IZ_<5Y4Fo3(8m(kuƤnFf9ƣE4EH ?Q鱡(3''y=-V X(M_ÏgZ"K r=Ƞ}J Ը3%GԷilUlX~"z:^ǍuӽyDBK s HK%W15T@42A,V/޲IT&zf6C,`YTK ">L>oxQI`VnC[MrGkV紶&af8,sYFxݲX0Ps:V&+Ny$}&@lN뛉4,9.b>F1@?QB1@?2;t QtE QQ{D F '.rr v2FR7Păn ꑢ,DoQ6y-કPL&iRK9D&ze`+:\XDǤMZФmWnѠ^nsNh9ւۚئ_wqmC<̖0." ׫C0V]w$Ԁc郞N^r(~оJWr6MZ}U>3_%+2$3>!~w|"$е9r]aI#֚Y:u^>s-P:JIRΥ*XZ&߼ycl.I}m[!SDͧ"O5vEOW`*>Q 轻J,b5 8&kL OlXBUF +Lڂ 0*2UM+Ҹ(`Ux-@Ξ( (p^նbj1H:ֳϳ8svkФйɳ2f[?i$MgZi4R*g6҉ mXJRY/_h8O- \Hp!rSܭ)[א薇<gV( 3S0mW0=a0'1r|='9AO/Гۛo؛'=ȧ[OO]O^&#K|`3'9 fN0s`蓆f⭧@[=n'A~Hj/v9\/j:|o:+=RI)]d]kMOlHs=tR&nR&i(u[/q0?6$7"s?h <}42X{'UvMw1j{lݤ҃q^~7bqq5 YfUwWR:Rp7 } HZ)EilwcSyKr3VrvBu?`i!p']`-JW /"݄!sлM5sF;i$ܻvП֮C;+JADO;Qc2j<1ƣ}*Hp*K1ػ&z(wǬi''}5γqõoaߌx=q_tG8ګG~HѤ~*+:y$M޶[WJTQtZrןįen~Ht/԰ 44,OSqsgskHP5z0aY5Rku:A GD$<5-t"+ãh<,#IM¦)(/.OڳC5B!|FA~^]Є%˕6a8?ǣ=[d jNLYT+7!ɦ@m*2,P=J/IAT{Qi )kiV C`0W}Z#ԣ0H1W ; 6[dܝ`/\*J"5)8F gH6#L`R*_ VPNm4%{ m2+.7EYdxT! #GG=cxRG'͙bZxt/ϵok`? >!~]eō#>BRWe#mħ.>Mo{ 7T{J6LJGꭊNjhUCvU"A:8ж]EmeUCrSCMj~z閭~ZijI k]8iCMm82;W6[<~nUyLmħF64^ڐ:أgOxwQ{ Z#'3'`rf7suGr~;1GÛQtЃ9X ʖe y5axq6BCOZ}聴zȮSv:qI47CՁ4Svvw^M? rN_pP 9L')nG'| aQp[Qz5XBr^Ncg랶sHz[2,$@z"ZƩaa?NkPlT'Vn&~}Z Y"oR[9ا㡸c]@#:6W/j&h q{/7g tj{F̉%k1L$"&QDwV0'aPD%jV`= pJwMԾ)NQ"|:Jk1#'6CLG;DFhst26wгHaiCzX?ޝ!t@ 8Z\'6#ަ_, B®1HbLQH;Bz( `,]BB~Tpu`oO,c 7E5HtIcLFHu`1|F{3w_[~uP w/V'Yxrc 6Mc!*{ij#.QX'ײ,FY<9,xzQ0~i69žMK>A~IM)KO $zIZ{ 8Q׃ أ( be(DqjG!tX-7fGN<ӎ$zHwJ>iރkPdDΉMdX҉=rM_߷ L`p{` q♻1ts\$Oy_URv %S?O@dXTj9/u2Tj>2&T6}o\QH)gAYNc",+H%&gT.ƀsJYUF2YAppŘhyvCP@r*;Eo,{yQTˇF41B0aI> ySsTVFҴqnhcoܯpFj /.$C y;rh* 6CA!:  f")&Ĥ}` QH (ɒXyR8Ts(# Out9g'b*dxlE,P5٩l :m[ǦX' ǵfTkڏQitH.fԘNXpWKQ?k`Bm6@:NY2꽊; 3O\y P'䷮WQzE˛l@Dj8-tƹ\\'Pd0Y=C!b͕뫛(\|~ҧ/ <vL~]3o8SZ(iwwwwh>BהO?=PYn|~*"4XBDvzL`޶8 &cO=ܭ5IK?%bSX^zuj:\IT `<5@ɀ:e3yD-"x|I†uxUG&kx‡*,P1sMj]mg_.j>TEmu(*UC؝aǚ:;4XDФFC$.{pPZ^1kYvX<9>f[+w>! ?NQQ?L=:8'E$&aCͅ8r?'aG uُUTE]n*m$$&5IkQH.+z;+*j)2nu~1dDq2{A._'}t8}IW~_'u]BBkN|5a.ws)&[ F*,`\V0X!̖Fl"P2uԫJjݮ>VFf5ɢxڒ?M9Dw+{*u!ݫw_3nY [7YDaςj/L zH*~肙YN\RMUUfO%2-$k$F"F([t֬to}aH: (/,PE"d6ۻ,3::!DwY"0kYYa55ٌY d' :.q &ab>&^<$NhF4£ (Nh34j$baHp0;GA^xP֍eC#p&3 Lg4"_T^"zHd*ʁB?TT]X*#PGV3Gi^%@Wj)*DXa)Ī-XM~;O+2Mvtם6av▣kdjWݹ'}ءγqcg teSɣ夺B_"XDШ6G(2uP@7 =α$\49,+x8J/& cO]2U &AJTYityU=}WV$Αp{+GSg\Z#XCP`&ޠ\+)Һ 19Ad9z$,_2^%B_w@0Ӌْ5olt^#+4ˆIu$ o}ɐ6@y)#tcGCd ]<*˰xԫM 7,B [9?=l&B6l/σ?G^c #GX5_n] #^3 C,I^Hz Zm6¯I[uZ~d6h>g}Rl:~`~T}6l:$>%~O m8u׆S ôTjfm M~MԆ8:B`ʈĺd2ElƼ_/Ȥ< ed=z @fM}֠Q_>AϠ#9K<8GpzD+OgB8™cD8BiߦoӽnuU~omA.o:v?w 遳-O*Ic:%i}ǂj0s1\=J`2 O+?fU}&-q'_}.;(Pnⳗ5S|w 3\ !p' )8-P1^pSf ;U0$HS(h5l.U(+;qTyZuJ:} #pgX:+/@VJiԤOAחTȝ0KKbrL2zȲX {#~1 8 .~p~z9 O?>şWJYpt?_},/-$=7?R'0<{8c]eT`%cɝS.߆7 L$0*zCjaJ?C,$cn|PK @OLoCocPD8W|'4K*`1.>ŧ4 ~ڈg\1(øsryf62gݮ+({(LCNqU H섔K[;Z $j'*jq& $zk}GilF9099z `p#kY*5$bBl873XɢgɯUtLv+alCaoǬ2Fiof/ $.Z>OVOB_f̡"B:@B=h ^nIJ-,!BW 0z 0]` >7iГH01o愔muRT!C|5Rڔm"bE'5Э!vBuZtR_׫V@' Vw6 Od,֙Y&2s\3m={ǫZټ]ٺy,QH_%EPP`N³HkwgKJA I EWG΂U]<<}o RO/ I9 N6ԕ0[?a)QM~1csahi̦&vd]D?s\m+WV"Bsk}JsR~i P'̓@jM(% JIU>n2R2Xρ,L;Y"[./9OTr,K_ɥD(~yΪ" F7c+ʣ+u,I{f#%_XV[Mv*kxfmYZ [ ש3pf3n3ɭP(ܒfhۭضm>)c!*RAy/zom\ٵmѱE[#oON^;Yg1¥j#+2XFV(PS}fau_m!u8/ôϮۿ]B0uZfuE.ۤe+QzPrxm<[W-_.Kl5:zt&%x(k]4'J!thSyn '!uZ%f"VaAsvO;݌e6Xm]WF'A¼pʉ\Dr'ܺW*pa/4n=EᬫC3-$Hp*n j5u:AX5mDGM}! DQ]-HO3da@&\1Vقa΅0<5m^MM̤MC*I;D 8PVkfKXtX0K `JoethZPʏovƙ{9r(X1mlm}{m ]ѻE]W7YeX]nMГX*v_%NuO:O_oݐMUP7ozt7/lxZeOKU [ } @zВLy-<'ävAzGuCpMT[S4eZ#+T 6\p`+Th:6PmD=05/9[ݺ͈-jLm*k9n3#0}Iu YX 7$ZAuvtɵ k;}yT6;8/PQYs^#̫7Z+Ua*x?j86 )Vۑ 8/pRVq:cy&=D(6)( br7S =0vHxjYX8M(aZy@N;4|y֘we".S3]]nDtY]W&=HAY0j 3{taf; S#Sƚ\X' ta򴳬H@2}2,igc;;<7\qfywVq_Wqg+ŊWw;M r:gKP:0ɬpţVu%VHOa{)Ǎ")~gP m"GlZLhik57nh 0 G(K #(׊0R-+ tgYvbjljX/X{9^ǃEi鹠~^ń?-Og,7SߠE󛔂hYQQ7 \W&IGV8E9=aNc1 4--3QEyI>-(=+:FSap2 9l%⎍Aȡ7#*O?&7G4k,J/ѐuU3+ewUu.Ym73_룷k}6ߠ#c/Wȑb`5?.:Wk+wJ7']t[c3 4>OlIKtbY~p=-U7vz^x#jsZh;g]?M(c!*{ij.`zSp-n O7a1vSx#t5/$$&CͅrVH'K ]KTȯ쓌cl!7mwskdCA7itTďB cdEm9<|*|㐞?̳DurꟇ\fp{)` ?.ܦ"x3PBjy/RtVN%Mt >[%A,2 λ!CW#J^We q;Zg$  Ոlk]&#Z]Ē rQ re$OX'̹˘/|뢢cڷs=m0U1cGJlZ cG" DȼAirb  pyЬu HXEh: i(=dà1czT_wueQ~ CyO Rx26 mH* lU*ɠi u^ M,yhp #"*w4-Yp#eD8罁,x  ^O/pۘ>4PiJW$NE5(wd@}k{ !]~A٤⑮U>V%ԮƴA]Mt:Gƴv'JebiųOLOLO+O@%sq =Nl4<xt = WW 15pO ;o=OlCSyFr|-y6V78N3}{XchvpV.J٠We<~ 5D;[hۢ vfvy~ qM9q{J^/ߜ8 ݶSAE̗ mOG1_2A?21t % iΜ|XT'=bƂ ߊW$8!mڤ3X{Q$8&{IpVjFwUʻQ`Tn2zӛrS\A|u'6 4?m΅HUB~ ԙAz3(,UTH/I5tk49xA3Fڛ"d}['O4~Fzٙ9{8d.nzөEL Ŵ 1)|) gK>ݦ*)ŭKz({QC;a "bo t&*G4',(fьIp_ʓO)[wO u@; UUB=)iu4~N: $& [ Pu@SLrɧ\uQQ%{s 3ILMLPPmWGL;Kݖ]9ȥ[t;'ږрN٥1IfkU!ʦV.Xhs-*2zD ڹ_܆K\}Lq:gBEc +&=铥p:8,~bar-CV9=j& u 9 5G/b F`[\Waħ)_c݋ָ"P._7<]._!(e)9kKIy r n+l tg>`O˚~!z G[ex#x, N{LÕH&}մDx1hoǁؗ6^Ni0HI﵆?_{IO'01ܝ[qG+{q?ypɚZDv2;5;)j?ЦAޝMD?ߘ `-], i\>ru?ϭfˬ2 [̦PA64D(ŽT7 qUT$vBLS*Y]qA8F 9U%K%P^`]MOh袳jeI*4Xl,K?]?yjqy0yȟE"bR? (ϔ\=C\GW`ODc>hm]Sohu'OV/3{.|w:ڧ%s&6lRͨF(k5=&8 W{xW<)e`d~$*0!LK3|H*j_I']agpqG@A`6|+?s%?1w0^%#M;䧠}5`d A;53v~NP۠՝ޤKG[(uPҏU?"IoxSeG;TeMRo'-Buly7MDypڄ賙iqB89xC;3hD AZYRr ?`OU~Ʋ!?qY/ɷ;#O 8-@@v/];V;4:7ו6E$Ԇ4zpړЯuczu;}ga{[*DmNZh2{3Ӓ^~6_8,.4I7]@Jژ%Rȯb&9YmWq-M_",Qc1&S}rǺpeE-܁=D'nWbZ'2ɗ/s!!UI/yCE+ Sz}  $L&a%a(zl?V>N(ĺIľl)N\HaNi =Q`5wP,Tlzp6~w}yB6q?N#~gvuJtn 5 3 LƢC:>ۄ`(4') EgeOq Ο%f#=,*js3{q:[>ڰ#MG)v tC Mҡt'ȇ~gQ0޿x<A1Nf+BxW-u3YVRm7L'j]( h jcD q)"Xղ_ugLc^U<PboEpfq1{ KjreO)NN0vtREEGN%#pXJe^jaC韊 2g/Sd}lޱ>6YQmpĕ}~$MFB)#F7M~~ 2RG%X. WE"Q2o;(7Y//w&.o(+ڛ.xIs~:X3EStI=!AHT;*[IA_e㢓KgO>䣵`>ZcZٕ"K;{`':=Aց8Y6 5b_kd+P B'ݢ!l]1ɲucLƔ;[bu SP"{56ESA'Pц9E3$=",3s#$?,?Q99*?tb5ԯ)]N^=SYX~ 7 j"s!1ס~UwgU^|YbdOmq''Zp1v vlͰ xvDf.D6 %GR~ޏ+Uisl/0(IJdsҷ[?yIgd: ,CQU4HIǍiQVkkziDg_Wo*ԃ`k"s7fWK4$bk f !C^Opis0ndS=$^1+`|Sg?`20xd$A͹rW'*rSlC)}OmoO[|3\μμ)vcD6]ڍw{NǛ?|u~~~c-sW r?:4fɫf_/V ^y+~)G6 ~@؀\ ^O['ەLe@t"5r#jZm"x[@=4< ?(9V0,[pT9ZRx0oo%@}sL]qWhi%  aH}_ϊ앚œ.*B|qi+i;<Y.NSlU]Qqm8JPϏI0^Jm ԚVJYfN24i!s7nj[Civ?-#r QcHWWD&!~* ^A!O=-8+b M6w-AN? 0Sk_2) Fe]| c?Q;~$ )+ԲtN5Ž1-d4jaDd0Iqau0rE P&Bto0*$sirA|葥c2~$dC'ƮDIDPUoA"kCɳ2Qe,@!,ݭ= B(KPJ#zc+zc%:c,..kw &8ĭ, jjtN0mlӐ&iGL˜$M&˷= p/}9ӗ="x+ipAޏ8N f~Y;WRS^wE-f<hv(t|<pֽpx1E|{>i7Gc[O|+ɴn_CL@Ua}0aMZNx,ld҂TJ!E s ]}N+ϏGkM4X!XKG9E$!1x AEQXo1:+`V Dټi=4_q ms*o#H+@CوMoi#H 0|4 ^[C_EES٬#,Ԋ(d$;}96P8y5+=)T. :s:voژxkkk8x寏uj$݂t@{[t$`'׾yp9+:"3.S(@`wWyc_C[ۤ*z+G[?ܳs>h嵮UU{?ݟS=lk/$\]-=6-׮J]}|o#I_a,dhx-Cv.lDM.Ut 44-\b\eo/E`I"f ڤ{:l6#4E\P1sXF} (A'{Rxԟ 1.c#3D=fh(=W :cwsBa'AX°4$;KdgӔi +"j Ղoi>Qi.F\t;DL{ݓD6[; %NX2?D(>`Ĩ xHɺ%F4?9!&@1{/A'z#6'FDwxnjh: OK0iDR -=9ᦃM撧(5rI\ibn}K{#~r?;q.O$"C?isZ8?w7K)2 &=ZDegm3&,S1a6Cx޼ws-^bzڱ{SaILyQW?Z| t0t/0c7fJ:C|Vmyc/ "jJkjkˮ5~mH%,^@^XT^=lň_S=$2 _ƿ)kJYb%a]SM֮Ù -"b2@T8צ7 mr".6>x> RhȨk$8>=VIl&Ixݘq\eT":yn(lv v2$`OBM{t y0Țh9B<ڰ؏ r:T0fbn(5йVog&E{ ~G把%c_0 ?y5ZAp,,]TSW;E>f<C"UpycTXyFV&KȦsPIU_'tԼ4KOM70D ϑiJ9sG*6 ެԐZ&V_PM%zE8 0wRܪuY)#5VƑY^j!%|Erf mJF* SY&[' ${LCsCfnE0Ԩ9'[ BvF9b xC;~*cHg/}3dEI Wji$V8a;<8?B݁zn["cNߠc}w2"HE4QUT&,ʨٺZ˳3$z:."ZWq߬9>x-WxYVayb9v(X'=c}Ao(VQZ2g|]EAumn1["O{.n&̣  XUTڠa4A]e[D+ֆ;.Ejw!TeP$,8>ُ2Py #YŐ?R 7-_?"+eu|:^sC }#8 " $-S1Ġ#hEa&Tm߬ > ˬö«l]1S`aB`7EMU(=?yӬ>ogX/jM^:`G'052X cWI$F k s]G,H֌u6(CE"G-`!~廿~UQ^ѥ`Ї8q%f~)(gM y.**L,2(fT#K3`2x'v+d/e,S;j^1"Iw") [#No8;tB?.+ JU_]+ eC ӆ?JSW2UOʢ>o]:ܥ#=@ޛV$_>LJ&xxm508#`cd3,߈)7„g6sC87_>+]Q"JK՘=1['FMu` ӊ$o: VdwPgᓛ(I{O?wd+–<[9zU\5ɜ>kP.RʩTPHWWȨ7Y4̤M2K[ḽ&Q4%O7$vML^\ \48%N ]+C<˘ F@$󕥑uX2.@Vw"QT.oNLfzL̆)"3>jm C7LDG$:P_vL ܂(u |.q1ߘEwKo!1hhW8<aM %(IM#g^M Þoo.#(֠MB:X4  ǖ25dӔ{ى,xmn"Vh?)3T - }/u /V({UVQ C\D:E֬Rp8 -&4D 9 9P wtӀ93X(5Z+`Dž(tXߢdaugъK?]?y!`=8B( {p/l 4@8؃kY8+bpf~ȑcr]T/eE1KbQvi4^ -h}''"'K6,Bk:$JJ ڏ;TT4Zn*(WU$YݭlmX%V9?5~R(WAVdwt^N0"fM\_^\IŁtr6XeB93LVe,Z1_qr5q~; 𐋑!Iٿ#Jn佩%!rFt5pkh!\/@d+>NB{+ rr80@.6y!WG~y *N ~^ɏ!`v&ɖ5J9t Qmtvţnz#d+z^+_O4csHhjHz;J۽K羥)&ܷ[nߺζoi}:׾US3K~w;nNbl0"^*ܥ0 XlSбOHu-zGӲb*B'>vM],(/n*R`],(/nrFvm4[#ѱ鉏mʳgA:➏abNݳ/Xc3pW$`F;pGq?LU;{2WmLޔ>n̠< EZu*4VF,G{ff>P/uƮczvA;_iF:F<#N7-:%!pNuVR.b[.[;U]n{HXmyיvWFJk{%SՉa“S 뫤 $T]:i;S $T/Gѫ,y:0 bo؃Ҙ~5}QV}>YnXH~bscPČJ(PHBQܥS3QHBQ\"**KPԡ^(M(A1|PL\|i89O‘l4)ly㗅Y4(] W5-bp)- =ۓW[m7Zx~wY噇k̮>[~ƅ*Kglit]Ɩ1q6qc(M= i>BĹ㣈6qc(RD4Yςxl+ Yed17# b# b# b# }!Fޜ5 I栤 ;N,~x Eo@~ɺya.5"w= 9SW\ݜ)Nʀtʛ3J!x +co^I}ԓ/ύPQ!ܥS;@(EFI ʪIcj/MgZ֛ڋzoET\''cZ_ۯy sbP~7=E5Sg끾O~&kV𴨑1_~XR~c}E?NɩF4Wɔt&eJNK۴RfUsI 6~&a(:|67oG9qҭ)2'Mf掉PW!RUh|Z"#eC/nev!T\B)v+:A eo7s4B:6' 7k/ z/ȏʢOX>ΌKG~BV;I>p Itv I橀 PDO\ew:ԃ;\[3஌gߋ`3DC8z֢Y^Tn)Lc[`r+%p!#ܥS;AH' * "X,#0O;+%BƋba'˩r86wV+yo8DwCw n/cԬʭ5'UNNyh:tu,HY6U:d$Bhp$]zȞ tg? bKDvN?9rឃkd:4'W8L1DX0]rvYn,zo<)8Mȇ5(eYYJɏ.8Қz^xXtc/=m ` EmW}WUV}7wcGQO_yP@=K2,b# [hg77F"U!ȯ·/#>,(?sDNdA1.n퓹Mu^ zm=P%2! [RU2wMox]2̭%S;fi|תmnI˒ޱس}dڃ\)=>X[_ߚUSQsml^,>g_MMU($x [{IZ7 xe9>Y-b7"G c9\}f@9dUn?FU*SOg7"jU|-QzF[G"~n~O-eMBez@|n4EݷOjuBB6Qe^ۤLO2F"NWFi>X끓m,zu*m]kaẺkm2V><"XN5kQ|R,R, bDd%D|wJ|AI":"rYG<.L A1T\ݔ!Qb kx$8d*K-`1!a.㛇wvEK߿"k'v-1W\8 PSzIx!a<ϗWJ'#,Xo\&]E|EpNu阴xM8$-BJv# l(.9 cRqb3Nݚqe%b4? r$X89ރtndUW͹✙ |La+G+ &)yS\?fɊD([xEhh&4=Q!"*BDT K:mwNT"*\eQU)m.<7e Aq8խ9h' Cbˢx,^}tA BMB^3ګLW5T2b}m@G.Eg|XG!\pc\ K:PΕK7ͥr~g Vuk1` YZ\ .,ytr̊g8Mvf2ӶTQX /LH&`24܈k L-l@FβJ #JW0d*]%* `',[*O{Ըv(:pxKAیs I4-*]әy\ED.AGB=*.괝B?*Ѓ$݇L: @A*U PE"5bM SpP->|OЁJ] S.k?_Kl(ZrT43pLe;\y >XH3#(q5_;(䳼QK=-4@ 9ɢ55U(]81$>U_}CtˈgUZ~ -_ ?mzeVɯp8lO~;:oZ3"x/]^Ԛg_< 3qceR~7RYG, ?Q?sys-#\C̸mP+~:'_5g4 i `ǩ]@l9OXt ЊFQ=Twb~ɧ ޽ tϺP!%':ғ>s<5FpL t 37Dj]ZQt6QP}&cfBlAP6Ai*iVSMYU[GnO\jP[1clJyb* $$[&=KM˱vm߂%"V6o>,ɒhio $HBE-҈UrFUUeDF) $廒exAf# @u'lBgcy">2@;ۿ/y%KۥS-R2;FZGHJKAΔ!bpI1ࡎ=JR+ݢ)FFZ&>K{.PfY(ͰB+{@u1= kɼɼz2a:o6".2kP6'ӟՇygR'CN-0v3ٵc fzc㿆_aȎӡW JyGIgcXÛ1Qǯu*kUe"X"hp_?eG&U׸-?-ѵf5?c/y#G!n'{ 'UO/< _1 ov @gOԭ8'7p>U;X'ރͲUW:^)'~+tHa =V&Նg2޻43 U "$Pa4<90}tΰ辚GtT^Xx{>x- p>GY%j #)֞s>+ i/cV]JkzٸC 1Ԃ9TcNֱG簌lCyy@ ,G|A1,YU,Al4YA& R@SR5aZOwOm=_|8HC9`F;J;kF ht3Yx] գPI^?@/(,d[ZKiNdz%܈(k@,VEj2A\Kt V~B=ԾTnhxq'p_j9ҝXB%EJ}Vr}Ml5C-͆DӾDq=wj8r俆**g'}z:"a'3=u`j&WE'&[BOŠ}.e\XëuYO#u"nHbodݿE-bĂzۜKq{m4O Bˮ/0UԴZ`d2yS95k;YnܣW\@˹و#Qx+=G%taۈ;sLV]5b1V8m 4Fͅ.zbgib ͽBo{y/GyH~{8z{QP %mC[w`(v}`ȁ?}ϧ5K7mU7JPVq[G]z Z_JQИm|%IgXbC82iy)ʍ/7em$vrub gK{/)b;mucTSmcvpZQEk?"7^2xבFn.H<#gьiޠ=o6氈QE S،+˝ǖts&wj"m@= vJϾ c1!Ga!{;z,ZCkG'2K=^ 2.7hBE_R&Vcn yWٰBE07$ bb,jQrki'5ݦHאW8Y$!a#J3LhU ¤@/>'Nk8h/JvC@0^ojiK$~:qNb||D?:cluu'IL| uuxNbNtNb|$Σh:0)Ls ?P &z;({m[Bu ͅ$,* ”$._'\N`|3: \ÁI NaU8l,6a2m©.v$I0,¯ h[tUДï ~:zUt8?Bж~Cs!: &|؇!\u\p_dKI-_'z3 v,C&g-ړl I8p395p؝;Mk] G]"n ]c랠9."/v[%#mq6km~_~>IUEc%2gȲG GK|GLFG,`:ÕVeVgb]@6GD`٣&Ok#O׵g BRw6P#tuzҼ4WXTEtVpFs`I⒦, cD\KsJ0v޿:qT?yÃ\f J0$^8O^.Ƌ23v`$ezsjl:H|cWRM&Z-ZCL,_אXHN+ҕ?6sCо9oCо9oCо9oCо9oC|sh7uFB8>"84NHWGР>BxP6pWE\Gu1<GcPz:KgpPêpX2m&idڄS}[ 튡A}M>BxPA¤>)q\sTGΟ{}8F<Yf>*' >2x}kƕw Zs֎$ODƲciT.H$,pfT=~ 939&kI }y?߾x~ϛE~=?d,&˃U3=7Yed ;z~"ߍG9&Oc>=O"Y_$ŤJ%zp$/9< `Oqzt_% q2!30᪼WI2O>y-foS0s-sMSA-/U4i%q9y/Vy3IѲ"vZ"ٕB 3)8Miu,xPׇJ$h}O:"i/N8EY6x`4+I4>_V9I<=?eNjΊooy8>b?3z3'}GVx {°Gt)b7$.`Ӆ9\¤ <[ܒh{Ng?һzy~j9ӟ#8B'Mqjf^uA Vѧ2*8o+%gX&chV*g_Wj5wo"g3i0(V @0Y6N!Qb>q X̆qYc8'JְH~ YqZ4:^x,_ e^V19X͛n^RMiߚ^0m&ÀWy*)iY-p'6yRN.a0^)aL\%D`8E܄Ɋ l"w*d᎛2VeHK n76oɟ4k 4Ζ TÍaOV0x=]㶑J{ٱ7qU;NR]& f_ EJGaFF ߿y?|#.nCOoeު$+@D|R!|b!!ğUY(J[U+^&|luTc7޵~g?b&j,6YY{HjW%}Jc,HȽԉ\&Jhn|C)ܨBgEL+nj~]W+#Qf"QM:_沀/a G/ w ?i2FCd ,Uu:;U [DiA65 )?4j.*'ina*EL-r19"[5Q1z,-]JK;#"WePzTd7]V(!*@*Jua.AjE"/\DZS} 1 ZgIʃA:,66XU*#!-TѦʔb M&EA,|OX%V@朐҈~W %Hoa(dIKfL-i^Br#9]G(P'az#,FʤCѨ0V gaC+ ۡiMkFk^ GSICi+U͎`QqlWz'he[qW˫~oķ^{}7b?=nMiuY¾ja TOj\[xE'4i ^g.eQV)\UQw=q*`O7[} 1 V+t< :-F*UL/ ЃyjsFN[ U9سӬ57xys͐qLiVM != a3!$cuG-G_7w2Od<ӯa6޽@af9E|/AǙ»6lcq)qA1, %EbƧÑkÏeB8I:׏u4~8snX>q ?dژq㫗,қ{:0uwd]&`ZCґ3]TelRR1rJ%G]zpF: Ef6jtI"Hˀ85wyZWdoѯJbiB4)C!ߪw9jVβ߯ I"JT>o.D'QW^P$rtYږ}_*O|}pu}us(D}[j ~^[%d?&ɪW@39aDPX:~я#Xޠ[>FEFSUW>.%U[ JxeGUYZ=eN&Uuﬤm=} :Qq.y3OrpcKbACTqG2@weRNMi;jndC_TUojAcC9Alk!i0ϊR,eCtsH/*V-^`¿j !#ryLߺMVBo.AB 4( ͅPhcqs!* B42 zB4Jahm-Bk7X [+$ :?#p3@a%VGYZM)і+?4lMB~Yklb٪y?&牭3}"r(āB #1HC;'[c>wBrdx0inZ 8PF!*_#j *3D@ /BۑSܪ]r{ug[h%N1ګ!Zd$V# ŧwLW}vMDQ{1ii\@4&$OyJ:(b*JwvD^Ev%IU=,N}S?V(i UM.W \ ogȝq$BG_=\lsRY.pI^pO&a*"K wy㲬& FQq=9Ko^fifd5&vXJ9yđ^GI[wW3#} }X,C_gfp:Onxb7&`ggE`*mBz2$.R=}FVMKrcF=>lxh! htrxeO|o(4c:c H%PI+0`r-?OX4F,GMX%7YB uiU.:jC?ၥtؼn3;NDCE/sų5"f-2g N[/ +Vr% Irh81S$5)fÙ/k@\cpi.W5P (D3ow>捁ΆESƬ'"A]Κ6 DŚ(2LH I=QbHM!hX .=">qjx`IL?Ewña\eA |E4Qk<ݣId䑌J#sdFcR;Xh -CE̓5^ƞ)Y 1PO3 ]4$Q:JAOcVZ&GLW*"Q$yKl'@%~#>29 ?CsStX.߹Ք ChM|"me*/oɉ˩hhHViHw9Q[sIȮ D`kZЙ^UeW9IUGCJ۳]4:f6/о~sӶD#ƿxÁzb~ -KLרX-: '@|sfZ_'wfϼnE| d7zȨ$0$Q1qGaxF_ԉAR`}%O(ehK%ѭirDY*`Zs.1f+ܗy'pgRFvq#LIJ^b;OU7TbxlȺl>f;oE&Ft(=LtY-31XGUEZulăC#OyV惏+]V8L/WGNlA'gWYR}Uڬ*)@3w]#rO% ()07S`5ŋ.YQyp6 <f <0hm4fa ==Z>-|^VP[,>[m..g8UVoc;߲{Pvqgn)|tKøg0n=7 jwa<<=}LX?22F}x{4{{L$V4VGq^6n0G$C8y[w0GuY῭h.Mb`ԡa_w>JJϪNWX:sٶV.ϡ0y)s Jz#"<2Jug6Js2h*xbp,b̟&CC Ĩ_&x/S0+\ l Cn?'QpV R3,c2 p3Z{&3=nқ pl!Џ M .KxX^6=3 s&DAZ%s+D~K)ߞ0)TjI-޳z6 Ttp[*cu$~Ӄ.0 qw J:7Z6b=fVƒxHisbv7؉ )!+)p:m@F&p27^1Ц_ŀ^2tf 7×jdW4d$2*Pˎ^y!kh:ʊ8 $%H,XAqK{]:gϋSSDdTHc ##|D+٢dТ* 5KA'p-(MZ잸{Zg4>CQk8~MP{@()V IT$BX=Qc(@8uj8Y~("}cYUЖjE&$^7s≆m20K!z)p F6h 6%6ւ080*L&sQӻ갽R 㘼v@ǩ<ӥ*H)>OΡqcgOzZs|*Dѽ)hD:t_{:9'&Q8}$c;s(FG$7w7!d ]&>}m|sl 44`rh Tz<;th@c@%m҉彚0a+lydօ?~rIpw#L2ɦswdQ'[HY R Zԏ,bjaE q{*abٲbWW XЬX;tnΔ-9ZGjwHOCZr{c۲ Xȧ7l]愲@v_V2Spjl5F2)whfw֐%gV<06`t 1E2F*p̦wo6#]a!]6J\6U ㇍ʐLjHOB@==>b܋D;#h;g!XB q4w@݁Q %Ѫk( M8˖[8%Mos X5Fm%fOf3Zճ΀=%nA{ſINaV[8 ZLϼ=E8X8j-ߨoE5mW9QȢ cWoM@tCqG\vW '>u..Ѐ2Q 6 Ezer?]zh=TNFml 1T|2HH_Vxh D"r~$ᕬrb:A6o)oߕ7? ܥCZw=JeByA/E$ڀ%!&~CCfla~oa^p?)`(u lʿHwWh K? sl:@~|bS>eYp#@>x^~}]Da)[v C'HL=0m:D\o _9Kl ɑ^5ʫA@2a3&Qgv$@ "S#N!ӛoP]d>}PqkאII(0t-iZ&^;z5<~kCl,afHpsZ-0r]%KDrUL6F̊Fz`7咃<Mט "fqd`"D x1~ @Z% >i# /7pkEx5}{y sԮ)zn>YHɮ<u~ek *[d i˨wvE#ꞈ=C'n((E OR-T ),N9rYd:ad0o''?!kD 9~ |SKI0I%wfG .ↄ{]B&j;*c"F =Zi 9/}:M1ټg ޝ?Lѝ?zD?2* ,wGZQopbp={?#OAV9EwWHݜ3Ѭ1\N+~e6Q>ɛg_I.YZy }Ʉshy:ݮf'6k \2iZB#`Ajae(y{"+B0o2d:;rr=c4Zzm`+ M_ONA0 ]ɦ6ҙx4_~a6vD[3 K-˓ۻp}x5cJ^e䡗yT= .q+ BN!p7j h!fe5%PRT[nо;'R{6ۇUOQw dfq]` V8{fI况O5b6hqav`-i< O7ibE͉#,) BH>YDG6˾ik-!@`#R5GYB/_ p/q+rN M*6'C{iÌ'q?^ΤĹGOHIߧbyE҃g;xwSc:F3'-¶౏4yO?zc$/ VKi|l/]*_^%YΩm)M'Z9L`cӔZWNQ(Vy:~*kxlx1Ltð=1~.٨ k3+% SgM]i-@5Ņx5`%[Y:mbn\J)8d e>-b[SFך! %t>'|~Y.Υ߶TߊPt`)5]Gُh":?U9h}t[Oj8Q^Fz̹_0xq5,=4XnUMuGp&j Bp&+cPwHTh;vvڈKZ +;mh[oYGJ͎bm!9;. 2}Rfn|vv8*KcqaRV*RP\-8NeñL "D0HL((Tw02pjkN@`E|V272|C1~E aYp :LXc#H>cmX1Onozow a^dڭK.j-(#Ta_Ƶ2 (b;T%?,0졹Nm3Тq^XFyU2<>bD}A"Dfh 8"6nmJfܰE#Q=BV<~^6Gp،CNqa+8x_\Q?dgU˃>ֵE2u6V7q.m\P2,pic?diLQ͂<Oʳ)Ry,''Iu]$'O~/.h%leYMwB qymd7ԗc;ްcL?`31r5߲l~GT%b 6>ڲo4hӾѦ|ZFb+Q8% ıX@m $xsio;dp!?!ߌHY|r,}Em㔅af@t; ]0v˸s 8k #vz‘Zb*ШD<N=$WGqczռ8aHfbKe|GÜ#TɄvLd2[R9Ӳ Ӷ/Jf #"J$9 b0iY]UHv=x ׯqw*g/"l{N!F{Z~; }1O#d-dX뾺o$P)[fP} LX:=\<7MlQ{4OzU9T:tar ݋KȌej*GNQeke$퉜+NBas8SWNɿ)Rn|]n,n*H kWY5CB!v<*X \70d]  a7 B[ts5`(Noy6d)#xA=Lo!z&Ur?^0*\sj+3Ӯة"׈+b ;G}NATJfs{D53,RY[ TN:t^.:PIeMNu>i>юWfUKoPX~EnT˨ln9-d(vҝ,)X 5牦)|[}`c : ފN_*^읍U#Orf#Of.Gߨ&w)5*phkH.)5I)RinN ^5ܲlZ~+ЇTtW}M&\$ԱC/KY8MPD;, FC޶} jZ0cgi11z Wf#ZWp"، +E9`pB;@&ݽŲY? WYAt)Wt ߘ0NBq=pZGq٬umo+HYKk*kR1?p|6HiwV*ݎUmX@ i=|:X}mtvL CTSK\Qv+ww1bv<,o|2ŕ]JlTl0.fD*)vk  ܐ$N!Nm]VAa4 KSEц8>GLR L Gȴ{+Jol%fbk(1Jb>xpN~LKVcC"x'Lz`@7mQ6s]!W GVi':fl)6)Zu(C̪ȅQ˞ft,^#lJF>4u~bxF$Hk"-j,q|zI.\*lhPGSt+y,t,GCu=Φsm58IZufT@!WPEgM cYj!mFd=$U a\뻎F)4}ttS(M ʢ;5NJnOuNvZ{ G=#j<B{Ji/sc].(``ko_x(=d;v ^ZLJ;=e C'RH`'F% S^6 |}q("e Ś||gy?nV\`RT3"=BS>6 d]nlXc2 R2,YA5w+95/Ԧ]lw7hZJ-tvjk0#]L u'zI $'k¸Iv^YЄɘ__X8Ji=5^ҬZCL\K/2Q-`<jXLC~?x y7>}sϸ}7v\26F-2v !`G[399|h  p! =!-Apc#C8۔wZ0_sNwy!`|j^@h*wi6Ci DKCPd-2`b[@*&¼"cyPʼh0UnNj\ 0r!Kn 'LB41aMcKF9Jz b3 ZR;~acRl.흺g vT|1{Ph~`5O#З{s2fuDYv/h8v-(tY2.-MfcsfgVj?m\YGk,a 6! S"P1\ u .v(Bѹ xD]e_¡Mudv4{r\zԋw_z䯽gᚭ ^/2ױ-{ɚ &.L#ScU,t/,($XۂX#wzb6_^e%&_Ab'rmIkEQ&'v1 Qk;~>@wj;K}]6*a0EQp3= "&G"Z!X84)-wuW>Jkۯ:v6lW'IQ^%WwZ v߃0)>2َaR.ۓa Z9Ov&.B^>HWn=&KLp CV1+gGSyFv [1-3˕b{3_`$`ynYh +3L `!ڬA{Z4zˮm8PˤZDv -("e:վg ^Տ֝+CT_*s,bgq#3:e|+UZ0yK1мI4I6ty9Cg.$ ; )SIabX/l?Mkt\X5-{6 [J5AbTr㖄s*tVRIӢz,Pc5A˽ked";S2[SR2:,O"巍dܱ)R]yD(Υ qrMxFuC"ڐ4'AYP1}tb'Bjmo Y).s"G=ֆWPXKHk˞.MlD4jRڏ$}%/ݒ?L*[VqK!٬!&E$!#~՞{_r~_O_C[ӯp=RmC1xHbפ{Wù`EkKKƍ![-H hM$'*/F1[s#φŁa&TN ??撷iQ`$(w4TCfsӲJ +|%f* #7at&['Ց25ҪeҪK58LLV t5Pi4Ĩ䟄cfMeEɗĈ;bmPZ85Jp(\ks$K㶄f^f*'''Lf% (<lRpt.26!gG_elMaoV/l]%3ŕ-GZћSq _N)BDgcA%8`O-NJVΟSm6sС~>IG\e\,!q.Brc?JW:uL0mm~ڋ7TVYN+*Z}wĊe0n@J4`UkW dVO0GT`u]?N.k@FdZHVmGGX80fh|'MF8ē[3x > "ZR[̩%W8HqLpX7\ ? X敘Nyιha|qem5QH4 goR8Pi]{ : *{|ُQ (1S|#v2鵷9m ^3б(iY& ^[JӗhCû!e\c竩E8~m &t%eقdvytb>5u}6E?,6L=NGCCX9N~2YdLެjnYۢkbj7>%R Ԕ*@ yP'A$`by0e`xjR\3KLSL5>* ,M U9I9Oxc\"flYÀ Vr7 :16/gC7g[jk|+u 7 ek6f%*+axM8ү*D7yìDLf,!wz6Qr* ~ֳe2H*o#:"^1y bGN%-|*wgɯ$ˊ3G1WB@v6F T߀} B֠CdMIUsˢޡ[jq,Cndq#3Ezmvm|}ƚȳh!xTz1|UumIͅ( ;ۨb̤ͭ3kV!/bȥkv&4see|ӎE;^gQir*APaӊ6>sɶXxNr}nMY9E/j.=o+2wvk c%.bڳSr}X6}m)&?qVƯHV%/uXuqǺ,Xc|mic) 06Fmko:J9W6&d8a}&ok &`EW& dL"F#va ^c,E ̡Q=tP9|iO9[;Y rӞP W S%5uq:wh}0v|_-~m)S9bh(C%t2!6uqT4>RUo(8JّX&;ͺQכľɤj|/D?h5!FqV6Bf j^JP>;\d/1YG- ˕ĐUwaŨsQ[:nTDˈpJ Kz3:IUϰ:ĪU}+1/66Ʀ;͓O5)j$$Bf^#x xlM;&6nj7_KVAҰNټa'A|'Ez5sQuzKcTdΥD 9quA#ۈ{:2.u56qRo\v o6zem @Hq>r 5ScӜ_a^w pHl>8:3Qcu[W*bV7ȁL9P%W<4qR.<6eԑ`AcziS̔J3wsICtrLcO48?a _^fAs1L;9d)joG-}> 䁟79E)o*SQl$Dc,nwaX矟ߚcCvR1/&[#>[ByFF=Yo&1$qQ ;vx(0 e)9Kf<{Mո aÙڶ= T~ӬԾjrSdj`ĭGܟA>)o?e2ZmNXPZUӗ*7 Y^SBQi>iM~`cݶ ~sUV ˸̀M^~\떊37\;Xgs0Xd`Ґڤ锕F_DY~f}- fH/k4bt>ՙ7sTI&[$eJ#PwifJLS9= 8!<)(upvǺ5)5i(GY>n;x_^9 5 s캖FG ݂OZ Ұ,mrtޙ78~$u3I !s7PV) lGX]`QhV/;~ICt ^q; 48\TH5`x.3C_a5PF*a4B.a%{/ϏagggurϏN?;|燿==;r=T[SԽ0$vYy߾r ҟߗiO1"~Tj%_\nx0#';jwXc c7.ޱZ/?"(#aDůQ_Y (l26]dI5p<9J-ZARͤ!HXVp!o@2]C8zIx^K8^-՝;<=U6{Aҕ4Lo6@f5A˾FƞH[.oh>SȜ 'oɔ`\`$i@dr|i0UK0>zB8u~<9=dX`GЊ`L9g"tJ9rYQM ĨӦPB;%Ҝ_댦w{kz(A*A]gպ%H| dՔBag:8&qlgQD:ZbH>H'Zdegt\)N/83 7gZ\YP]$S7ٓHЛQ]ÉpNt 7c^!R`IE=߳U}q}2 P&i8PHLơ;>}0'}4*&̲J3=SW') .VS^'}v:czŧg]uZ(81DŽn }mTpE!$I2ɺce W'w]kV73oWީQVGQ^|Jpُr1>GZhO+u0kӸgԭzsZ:MJqӸ;XI 9#EN^3J[b3?$?ڿE)H Ӱ x`Y.u̽.UU#@(46Z\`.9󉤢/Ư c}Dqe 'fH:5WMO$|G%`RK(e/Q$:?3ë8Cٴ=TQ'*GMŚ1(F)H=MNYIid02U^s&-oa˩~zX" )Ru&ƍh&pN7>̉pvnPZRT2bsQQW1 x2)IgT2*'k߾?  8 H aFxXKoHWrH#EZ$6=mnC[n1sH r#z[{d NՇmTBڑ K 7I݆cJ#nAH&^4m\ Sڊ`4Ҷgag򫮟%7ߩ{#IRwQ;]ќGVrWo)mōH7{__s* F]H݀Icrw'fГPyP[G5VƔ<,,w5 Al ^Ԏe0Ӆ}1XSQ0>V)N ֡d?D;0ҧuR`T6ZD M:1* 'A1[Pu\ռv4*ƞbey!-x.o{ǜNJq^:E:!{\ocG5X8!>3H+kZZ*5tE'Pʏ+`3tjR:~Rni:& QCc$ňnIC7%xrR,\b^"MyISkw}J6 \-ouF %T+2LKԆ6H^yWȷ\7ē ެNذGlM[+V%lHћϵ>c#6^Vֱ`01ev[N/ya(i&N4cv%S`\<0۽&a׊&lS pvv༠ތm[L\kx)[ԎFR܊TEqxVk0} Y0cX1ڴa0uRt{~ҽ{wd;zs~}yr[$jq+2Hs yPcn-0Da(qbcƊ|I@A!.5q2r@}LwbTq҂32tLK0Q讧!,0(Hbxwe@> |a~B?RV2JV( ƀ$JI.IZeJfV%08qN"8>XE87J I 9c T90C MnaԶKm3f-bݺQ7Ikaq`G_־&za| [+<&W5"@3YV:5)AXiV,+ox( WΘl=C93>3ACn@Aܹ~:I|Rd$2RTX ΢}a E [ ̲J#T#lLy9P4MNPÈ!Lҁ}(8#gij)rE=+_D1U R* 1+e'f'|e섔 nޣ&#\mJƉւ{%0hFH;,s҃ܝ;8i5+ #@r9tyϼ79;/w^*#ewDQ,YIkxv&$U7v98*COA{U\Kp7Q:8FI#Xgt`~e5dYJ:̜AjA sz'@!a/+8w:>A{SeK4Mf8X6weʅe缂(k^TJLXA6c'ONiqWR#:$Z3g+#'-J|`ϕ 4`#!;AU]KX¤rr~9leEB\Gagu~S> q |Pdou&JHi)05Si!hƖ\ejq/hd?œ6ſ_rm R+)+1H2#1 F \f@k[Z">JF^i@$.3%$^epS]>sR`Vi`&*I-=Dserv'0qGTo=D,Q,aڦR)m^ t%&3Db5 PBmł9=A@ϙIR~Σ6{^ m@8lhA)k PQ;@a@mW)@Y4{_Ѣ9na#nA)Bgi& QZ` Uo;i?zZo#$0[9CA,GG nW|#~'+ D:}DWFNLo#,T<s"~$PwSIP7cPm&̉EaNP7IK*6@= {|@NJJZi^u}^=h:tTWuJq`d@/5[l+D0oiMڎ!Yc>CsQSe>ߦA)\|F_`2-QLBne+iELJ&=\kwF SR?>]ئ:M#2t}iQ-aˆD I#rj"C)cS#۞g=nyq㙍IcAq,tc DC5~~S(y/o,tSI+Q&xe(|s 6 kFh{' +%#;JF({%S1fKWW϶R7!4]"9!G}F!k0~%(BJ`ũ'߲{`VJS #ko7*jϑpVc^ZW |_S9.PKX"d'-`=P BrW<Ôw9ۖg`pQ CI8D"$ѡӱ:8*C0xF' !R"H[򴃵 uDFfX`:5u2q] s]`>;k»U&_Og^h7IL% i,uL0iwp:Wz|ֆp>zd<۹DhlN+!$BKkAz$2 lZ?l&0? {/BLO[Z|7淓p}93z>_&>2gnoKaSد<\Di.4"/pܘFۼW}FPmSϢ8ea)K9.r@.io&ˢ.zEWiZtY+wHFفHv.ngCE%DVpL\{$`{2@Uҁr[8q-҄VY0zCLZXF`}g.O;II-}suygS'G!NX~!70W0rɉv >l"*f!SQ_<\Dich^Dzu5T&סeq~' .8WXZ ӓ24qg R [VA8A5xUfFlQ?c! =SR^ KaUx=^agՆʦPeJPe >`XHu<Ƃw]m|m'Kqmv5umRQT:ymHNx/֩ 5ss-MxY161`P%|wQ91!,K$'@|$e7O5tVdj%| G݇V( :1{Uzypw1&|m< W2-ܰ}lTJom[:0;xx)gS]Պ/ķT_Wp[S^4zL¿ipX: > *SaPG{I4[4^PhGr4Sx5燴a42*ѻJs==U1{Cjk:7uQm,7)Z‡Lv],у")["t5 2ZZ7 w{yv7eeҿy^~`/*n8%"|?G~(ukRb1ݼXN@v故/H;/pc`]+ƑRxspM~b!>zB+ٚt> Np8E壀N=neD.$$VA>\BAm)f3=0 hTVxX6C׹{R'[,4AhqDYl%QGRvܿ )YQ>d+Ù73o5ϋ} _dd*H i7){K5_k%'r6 o4ZXlG6w^zN[qn'$3)jY|6ds8ez^taY#+N{)DVy(yjmsԢXzӝZ(}.^;_]yB\{Ӓpj_ J]z-2fV)Ģ]Ӿ5ZXؚzTBa2arxѱufj]TsxƪLÑܚ7 x+T c-tD]*mFY9e7ȭadQv2x`\׆S_AυvSN[au,Ke)R'[ɟJ'" UKyx6UwrxLMnZ5mޞVRMPh EyJ'26Cκ?]{7VW_:|0z'r $R wG^OiGaSفQySj/r_I{J6݁دw|/d`'{#cuZcOld"OBɴ^x_Bq VKT{!g {=4{ӔfU* OɈSPes ASq)V0L 7`Qw3**KRI@sYTN .RBWP`-@ Љ{tGhJž!23NW΄ `&C4*6H-KJ.EU&S24iiȌ3(@9!g,2+5KtTHZw7ӥWYfKE{΅RHZsz]k8J)ŋLM#fꯒWa$]dzw559M2@jҵW>i]aɅ+B+Gz7Dc cfZCYOB2ԵD\Pcx KhB1a y-œzR~^/k L ױanKMS!"kaZ^֠qݹ*$7x%+巊8ݻ$^64FVh%FYC,_(y+m OL^Q_ tC" io1*. { ;AC¢6v} [#ū#dIHXӠ= Aa#k\ /!U=1;AS%y(񽀊A_G 5<ҹM+hZ-}B!a@h(6K7aMg<Ӣd MUU1`=xQ1|ӊp].43n/bV̦Q"L=_>i$1c<'yvz_:[&Yy)ޫo>HBSd?N߯؎ 0xYn8}WpV(hA&hbhPJRN=CR^7ަ] G$zxfFvyukVR͓YKJ8 npg'[LIĜWz[ 4/&GwdAó(4avPݷjс3XOvWh@e݁ ѧmZIfE T >+NPL[L.̝t $-+ ڧwTz,@5G2pvDl]g *7x LF +r*SM.Zo ZːŸ)7_$~Ԣq˸ @.ǫ1O`칪 ? +֊o5ܿk\e[y9 rtz[2x٭:ooK!GSNmwzlC^:.häGv@/R=hB 2UQ(ֲp@Rl+MYra>#֌,wV}":8څcбL؃BO]8XʋNe}'M^'`r J[@+Jdb޸_@9 \uaV=tb8&Io) 8Zׄ- d +Pۗ"*!L)ɔ%j$ǚ' sJT؍E{'x1.QĮ IHܺxOE--_t m@ >p[_$=K\\gI H$m$"~ihzp@ i+A4QU9EO`Ꝅm Y?a|ƵROoےS]Vx)VEXL=BtB^]Fǭ#g_6XwrsǿoLak, ``?x^1M* ,\!h݀3tq[*@W|p>[T;AD*ۜRfƜ#K+:kCGb}D 4RCfaTX:֖kXv^UzQ0kD}+uD!+w@how 2Ay ۹}b gf;5u-oٛs1o/y\?t4ΛaRX9;V0Il9bDH'A I[* E،!p90.eWzu]܊}PJpx=/+u߸vEk7: 'F:OoF0>s Yt0 !bnu(&fQ7$Y 2f|:a0? A`o益o·ot*+ w*|=EoZeE#Hf6P65AOi+]o^X$lf/~ߘϹrةGֹ-`g9[+w@omC.jo*&π1¼',!jNiLuXk,Jhck@u%{>E)S4}{+ܽ,fR@n 8H!{Ԟk<fyJ/ %S.nw9b;ŕ~ TO5E[p`BHh8r?7_nL_.ߺ[SV{qh"Ũފ{-Z >58 5I$Ը5d.PA09$bmY'k7DSdD3%VuZ">&޸2."r՚ =D{mX;)=l@fP'x=}faA!oE 8Ӡ˕4_w+xbP3tgn.eSQt‘s\iZa+>R_´,Qm'ڦ8,.>`SqA?ZhEBAo~V쁆h#,!E(_!ۤׯ+]uw|^c $nNtwws]e V&~%2-6O/͘G;ZFE.s8'NJg\}ޑ$hҪ6׏M91H }O_`؋W iThJ#~va'dҐ+ 35.݁/&&/(ScypfԐ M:2J@[-o;(-0HBVMeo~)qUW(zH&C6/$,Hvms2" YuN'zn@(Vz<o |ZЩuAbIOhdxW{ x~fE353%L"IM〠z-%tB6G+ "$9lXYsYԔ-aPPTL+b2G"uo+9r,c(k0~%Kj] Or 2ؕl@A̎69sZ4.G|&}55aK&h(Kl/@_8Dd2㆜mӸXJ8Qj4+pzg v;O\l7Y=֓:cMBXbX19fQ GuQ7=9~<| LQ.M@dIet!!)uh vUS a!`9#E]vbBy8E{r}]]WM;0iUG :x;>aˮHS ۬U6!`K & A,0-#9%Y&lPN&K=y>@rO~qѣ6 nz~^k8N]W)B'"󾅶*|"#Fln.JpʪF.A=89G*,xw׸umV)̙E͓"AЏp >1ϔ3U9u`"gn,QO4'\H$Ѣ٦ 6! p}Jw6\(i^6jXsX{Q`La <͏MmV[ j@f*nHMDT S~TsEc@6:SYoةk"ŋlsn[ Bʶ+e2ݧJ5I>3@GOa/_bsb̓Aǧ} ZoF2M_~8uDW3͇1#&}7}Ю)EXH$n>[CDuއ:} 9ZtQ^ }Av Lף1_+l>ug׭i:n@UcOOC:ӵ)Zf\k>=cM$pV R2^y3fh ТE3X\xֵ)d#FfH C`ycLV]~&E>~lRC* DMﲼ7Rۈ S$BEYPpM(> .d{--i6:1 Rgcʳ =\uT%9 ? #a:|,}lCi)Ґ$jϩq@]LS,Ѫ89@]}Z;3sSѨCtr![d2zܝLatU֦5*0-xf?U(V+ "tMe<9QHQN.qqpjä%2$MDV#%ARnFh4(+=9@C>ͣ]dB j ~W:{g~Y 6ùu[>ΤӟIX8P+Kg٦88^4(d~+;a;{tx)MݏŜ{deb-q_w?/ZFA }`eXؙQ+3uxkST_GqG3[xeF0K.'XOCOŖiݚH?tL'lcYٷ Pوwj<5%0?BT>J la2_R(~X@iqzVw+%__ c8-0&2Ҝ t8;~ڵo4Z]Z"[jm_567l<9qP8p:^zήclJӆys&z.0>k*F3ZF/|Dޝ΍7RN-CEb UWxCK|+e2Z Ds .}kL氻lK> skC>+"d X5߾|D x)ɍ(|rXK].L[ȉI 0KؔZTd&I^M3L&œA ]Jɕ/u?K)pslP}-@tA+۽Hʔ+ G&a>ՔDd{ֻbVs|] X+e{0q8X$rn2Z@(b楉_ClA"rD Kfe)tEA9̀$puUv7Dq"!m`}u:;v̘cLd*vh趩k)ISٯlx^\7N{^ߒ 0E̽Ө8/>2/o]/ G xW[o6~༇ b u@m0tO%Y\%R#)~CbGwn߹(o/nxJԾmV,,Ӓu/罯~/qohloLIMϳl91ӕ+U.>^2)lrl)YdJ (r|"/ ,X#:k:c2Z6ҖNXZ[re[6IyΞ#ؕ핇Ga?t!;7ғ몣>S 9KZ0hXܱi*Uu Q)NrZuZ F}FƔBr``azmV)̶d1ml+f7%ɪZDTLDnq5ʹ#DzGv#zDFasCc~tOm;q=lIj/zWB{ ]vfuTJqإC\h*!VFY^e?bOiob<Qo fVY9HU EڻS)j/f]lA2y?YAwd1(}Ձ Q$HErMpm#E_~ ^6ɟE0kK4F̥[wp~R"׻oTXeܱHGgq ehz!Ы1C!^mxYTצt]ƪB6RDUkVI>,Lӷ 6R6(\z Ԙ*b8(T,?G<.|N'|AiMr#+jjE}b^!RE[UY8 [E^~!p,+2cGhJL5e4Brjs1cS~h~RK( ʚV\^_aLl9\Z8$n*㤔ijFc+)UUa ]# r&ƃ#kcK"$1lO?&f5( 6LN-Obd$Cn 6uLH%>|zzrN>rjv: BbբtH 8L|frgZLF0TTaMqѕ1avHM"NQ> Qۇb ; 6$ol\0i~7{LO}<|IJMIt͕7P2DiLTQk\#DPz,\QC ƚx>sXQ ø gq ߺdȰ2hZx\[o~jXBf$Kƞdc# NN7gv41߷xˌF_f],U_]ݏO3E~?RO![L4Zv9|ߪ6OI*h^T fUϏv~d)-tt/t+Z[S e炗J.~wq~$;v :!).,m/<{ē\hxo&>WHŅ#s#C({<_p.:? rRY&͕5_V o3qJ,[.a_ Jͫ%|V<'>o䪑ZqʭG{fR̚6 h?}C\3b[^fc\OwZ+XC*[ժT0\ (Ff|xe-+ؼ# %,çfND.Z,dE]pƏ;?!o(, )W &c*Ҵor^˖ ymdxTi‚B(رR_θUh;$UøI)P ,Iԕ4%Bյuΐ j]!pQ7: l(1BZ6|QueE2/ptuX],Tj TUc[3;z|dS^S9ۺ8:ߛw>| _?~̘QIfl,@MeӀ,AZY9p4 <0l Ȫɱ1.%n0==M7jѵ,$ \g Rt.,?iU2Nވ9 +ɕР\5 "I~hbE1u#6ch ,R1 γYnpk̊%}i>6יsU}y8dzu]h#<0aS0lw|4f'hTafS50v*hX7Cre~,~A:1A'p>3 yF8,Zk5'Qg05WRWuO qf݃(P/Xnzyn/(SFcޑ-;p& @-oYF#K6ATܸLY{9I!#Ym J0nSe3mr" ?+ zmr4H2_yr"/K4ޢ{+ 26\=Ŋ.ֆm[ [H$F&駪 gմg0_yUa|Ow~d T ž澕Uv{1ˤb$pT2ڬm`MYf}#غW o U m"{Dgw?JS50JD>mlE Pب/ZjwPF7x z3 .Ř}Bqݷ&ekylLLn@R>xZƬ<`VϨDv@HɱdD2zF KՅ{i4sI*(~1^7ƢV0&d=z6"\+%,?= zVA$/Ӯ2*)MrYf#Dw=Mr zq"VJEET8/Rc8;!cх ؗ}h;:>W'd"cE:|@:caޝgh0]؂HuK%e9a19 9U}eXBe۵u=»kh"=5jwnXsV{Y1i(63bG A2[7[A+I-)(Uv)A) ߚpVH|isR M$O:|Dd3^pP5mVBEW}꺳[:}O/xm; 9־*R:/*Jdy@rumuko3 ,gHH>܈L/HF>GƲ2,9+#?4c7WvAb?L7`$9{F_6稯 _ !>;xUM0W K* BH,[ rIcp`;qlUo޼ly%tUV/dzt n]> ;$VPaR@F(dJ|G~9ʲ,"U?cNX'{ JgyUs?DY}zUeCƪ.U7ie=VSQtAD?Tlj|Kl[l޳@+#cD0xy6"!hU5!@_( ZTPۃw6<'rTub2Q.M}8qq2@EP8ͤ2!Y-EykML)Qc΃ѷ|yC"«ROVvAS`#tAS$KU,=!2'Ԑ=>X3E5 :zZNA;QJ {RJih tU$Rhݏ(?Kʈ[X]Tw8.5@4DcEb;fSoΎgکmW-U:j*:dxyeR`w2ūO. ^\ṙ.8?U>Bǧs`5YvWȬX#@ejZP I^əwJ9dE?Œ%957x]htx?} '5t m[]:tKJzEPF 1LQJQ),֗]΀V>L7mAk4Y;-JaZqĖ ؃ " (Պ2qA·|CӠJ(6D uȈ\A D"Y5^w2W8:MGv ISFS݈i*4#N;,V2SPvǘ2A[H$S˹OLؑ@ިXdk< " Za/1`E}Nz˯jq4Sg`Eشiv00hX&0 K[ ؋:DLݻ];ٖRP}<#p9U2m4Mɇ&?Gk`;Ia}evAތQF1kp*s<3S\b?zJu>sZK)DuWk-ׯA,I I,K`^bQmeFCz+\}Zʩh/R+ Bu,LWR@!Ohx3j1zT\&wxo5]~)?oJP~yBVK lBSG-۶Q=yxķczN c$ט읚wX`vH0F蘷 +TOKݭQ.0}uŞ]O:Bmr  j7 % Z.} P\kB-"а2\ SbcQ)LjRM՘1wGݶoYq"Ɔ!ӝñvd`ާ5h>Mp0T:*+>@ϯ nߩhxøN.7l(:"1>Ss/ n_cxp =.QE x`cܵI0tUC4=6,9!@v0KUaJ4%4 5Z֍.;L`:>6>6}Þ !?-?hf<@or x7q `[] ݱ m4#Pqʆ]/>Eaq]üa}}슍A#s0w_q(>{z^V$EG&)`gWL^`4݅;:3)<ãժ1=.1BhhyW(H[ 7$:w4m|`(s"e,#n5Hb0pƦt2hŽT?%S%XiB#*<xRBzyr2w_7EXƵ_`S*k wEa~fGkO_(Թdvkg8z-ۚU^tug B;W=4Y0$QړdwX=\hMB3 !C!^F VyU {}Z -jwйPOcu(hqc2< *P{y|8ƣbhXrYs ߂CGf1h <,%ۍo.tqa"݋Ѵ,t]cPsr&&1Ll@.*nOA+A }'B3Y-`hpؖCu픇cx؇D$&S.b_yT69 v{{+Z֌j4qzŲ x)I>WZbxT҃Ùy^oܲA   >$xX[4~ϯ0D[iv*UBtv^!Z*/ '9IL;V}:>;/^]?H1uoVo]mpi#iWRFinbIZJSX7;WΫ}'Ƶb+ݬ._Z>>ۚCn[p힉ݬ˶|PQ [Q|=?PȂq BALXj-96!47.;1g慈.>D(LU^^zrB̅"~㛷Oip F;hcIjUycZ4{(ByhC=O=I{w-O^('|GDe& QZFcDaB崱,IVW ڢ(i!LHjN$W0DC﬙qרUĒfȐJ0GWuQԅQ}vaS/J%Z9&r}#pŖ0w]аCmh zn%5?`߈V<'A$ۃA'GCz f 5rKe@.kikAWG Ww=ɹ~MwHcE99ӯēܒS${wrC`k%[m ftRdg?dtcr,UN;UuwBZ3mO5P_U9ZzYDưN}|*?EBr/K򔇫OO#UpTUKSdIϣ[dvcnҩ=MEIǐLB}͋r`p*OĹm09= _s 4`c w{}r|iluB)=N^+M"OGq!yڹ}?&F,^Ж .gSkpWUR<(bGhE#.S Z#Y;.SXhY֒i(B :bbVBosHBby.2Y^2rס3 9a#Jٞ?::>S18 ĭM5 z}ъRUM/wb9f/TF}U$ɧ֔T򇬃̉: -ԅƣvP0w% !X:RxKjŽG~1 C">$_SkB I0P(3ɇۙ=\Eaf8nBWFӆ9Iي FYewQ4K߆"!. $py LEX;؃X%='vHH4@fqBײRDJ>tV D]'DҠwI}(!9P  Ahx| t:vM\{p{ #yrYȓ1 w%,}o%~sCYy_B)%c}!xK_`-a~}lq2K ګikӄ֭fPfOB"7X Rq&t&=Zs&3Ya[E{&)jqv4K qNm洘3jZ)跐n6Ui i`P~rzd %A3[v>c vgX=EY^Lv|_iFj8j_S=x^F+z!C>\v@KH,]\&T$p-&ݚ\e|{TzX"GԌ*ǂC[EF#$F܎L:R9>q_J~?^pX8)̭U\X4.pfM[[+6( w-*T6ZpOM~9G~9VzQᆸ5˿U>Z7Y0p̡/Zel㤅]Wä_y$vF*l]Szii r~bt6 h )49p&Y7S 9ȁ6zF1xst~4uCyz?KuT]PEg_s=yNJG _}y&YՔl܋e!(q)W- K6iyfćaݐyz_v$Iq햎fQp8pk|~~om/7-="0xYn6}W}cڅc M"n FTINՎ-o8^\/w_o$"W@cPTi0ד$&3f8,r+&G񣌁\a3g5]Bj6,ҟP44 "+I1YgԮЮ;&'uf1Pc1OHĩדDOa}6J n,S/7йH2sj,nJhd@ DF*{=LI0D&L5.€}EE.RTrBE\}%KB~Lh<^$yD@꨼9|Y"z'Oq*>rLE# 0^{T=3`RabY2Z}VRCf NlDO']ќd(79Şzjc qIy%k6$GҐPGͥfmr8p^&H9^W YWb7L GUI=h`lfPڔnӳDzMRń5GC D 퉳``^}E f=ҋhM6mXm.M?A89}UGo)ҴG14@̤e RrKpKo3rA ^ (:R,;U7E=e֙x'p%,h;y-GU~C辘Wx="S39=V|a'n4"Gc"z}X<{#L{>D9JW ou}lVrbY*O9q.1TnX*g n_LI%]m ‡Mks6^3OAL~!xl_ozK-ӣ[B]QCfA$vn)o.rFtl"2}sqUon{FU{_U IBʚY,]{}$]8jzͺ߲c~}_N3# `cX+py=_bq!5mOkL+l̰3 tC"'>7y}GK4ಚ7Kw$3rՠ@Hc#b*&:HTjk 0~I) 4 RM6"Cp4e "۽A@bbrk4P ؂D`T uG8xˆxHEDbq\} pܣڼ#tXFK` q>g%Ĕ{Q@NPM<_ȂyESR$@׏-, l@I@/C(br9hw$=f-OQ/ y+ 뜬94:Cے$`ў+2;/ L;W?Z0?KNXwnCG&W !#Imkn va:.jKdc|V0 FCDq @OliC5tH.>m%2/K O}̻I>295vf !$ j[kt%REqTfYgq]Ġ}&v0\49; fܶ`7f} ؅(n -ڌ0_bLb&sGʓ,`'C2.@;K2V ?Tahhnэ+0"9]2 8UѹV!-_r0 G,B9BWM53q򋳹-f͊T"cswivrn, Rͅ \owG7Kk7E ^H>igGfXB빈LĺmTT;"ht aN7֎ěnM@0d7츱mMkY(#װ'CNWxX<(iI.7TnJr*ҭ+iooNώ5U2oqFo6簵FU$č-G`?6Rﵮc$rľ|Q1ĩ#PDZQmAjta/-O{p\,Q,P|˺ADTd5{)[ aC50;Z2"&`HzȷmH9)|MkRB`S %xHR/^J{eve,+:J~gfG) HǏ.ynՁ9|sOf £x2YE&]t/k T$w:_ґ\h[h~:Up>Y%Z N}EOz˧Zw`J#Gsl {'؂Jf_誦*U>LFS}zGT*Tsc-w"E)vA]>jRmR9`@1X2`{gyk=A}=zw"Ayu\<[lV,س@2|U/fwpfnΜtmzmߋYIuh'_* NR1R9ka (zjp(҇f7xmgxn}E n5(4m 'ɊY?.$i(IlOR΍rOs13;!oj\~xԺ= )|D/SuWޕ֡aY9.t5ZB92F: g<۱HI'.,YWi%, ʲ#&#o<9uOƶ-r䭇Hf$}~ P[.d6wr[0ho "\ySvB~lKx-?T?ox^:ߨ**]xɣ6B=/3@'z@ ۦ=Ѵ?sW#{8H׼?a-a͡C팟tzۻ{49jA}hMv޲5"u"[-u &qfҤ dë |I'2Z[<;'y03'[T ݉E߯WBjݍjDdԻvɍ\&~C}q0AةOnS>:dR=,ĪO˓)Pqo@*T2vc>Bۏ E>a]a  4.Runm/ %UP~Gvzj_-_K]  J('Tx}Mo0FT i^8!'v6ؓM~;3qqnx東S=ؙ2+ҏ~:Vfczi5U~V 7bVsCꀺWpDY!N,e 8MD+Y!SbYNh/6"EG Կ+o/AQz )+h1 _L^ A;{FQ6L E{ f\WT5TRUw!4RvvC6ZTf@z1]B^Arb%+EsFeW5U#dJO 'gt4\:.'wq Me3Ɓŧ3XJ& xם^'Fa!u'.HLS`1\aҿv@`l)ł* ;Ҍ &~l]ľ`OLQGIiqfLEuw6&e6*ij[n9/ljὦҴn-]$KZmlXV:g>77Q$:Bg` lj& v8A%1O]4@|O%}mwEdkK%<1_sx".N>WR1##W-'hI6UNr!MqĞsn 4Qc+1n\Q-IݜnxӒ4K;n֫1J4gXENKdr<1>}Hm6MGp&dxYmo6_Xq(0 VtZ4A!l4҂sq=th~al:3v:ة/~™7Խ%05h:AMy>ʡ(d"D;_h`x"-l 犹5>f읆 490LEQ,kJUU.ec xSX磣0sݰ;m_G|i1FV <;V5XNWdhʊ-ЮmtEjB; Lثύ5aʾ >݃6S܅*Ȯ~;?h84t`FwpgPay}-ueJCTRjBVBDu@EcV,X_RBEvb)UyCc{BkІ>QqJPA;5z`y4m@y]Q^ASz?B0G}Ю`^9a&Pw>ЃNV9)Q:=l]&PE g{"ub]D\7]#'{w/IM~H01@ nMSE 'XhCi~ڏ&Xʆ;h2y)Fo 2WJq㤢͍z8v#KA}uj @ߗ/W[;0W~֯W }(}'hP ?xyo}ni]h%Ńok%@($GH7{9BS-4/Uţ6ȔPZDx~.b}M j|. i;ź7X- >Cocy)2ډe8-mIU9ol+ܮ:a98^S^J F΄'nCRC u|0|p?";iNF^W$i֘:Tx*UAj- {SѾEm:?swп: ‘W jgzݒ47\@mIZi'*u=yI6>,dX6g}Zw=2hpܦܴJmKtDb|~ Nnws *Rp)b_sm% E zpfZNKpD\`im4mMF{E' 7t;dZ}!Vhę⼅ұ?OxŚ8%tVrx\{o?k&@CH.@^p|9E{>Tk;3|,+KdÖ!p?>fKSٹO+lJ 1Ns#M!.~PW\lPF }~jf秎 mș~9g/ŋO.k1˙GWz6@) ssq>glVp_́f2."9=XJgPEV,x _pί[gGvKfl-͒Օ`^3X":ټnâ)+ܑlpB^0dKn;f}'h[@fF?n j93KOvva\æw86U]J ,XKʜ8OSXg\,.|Dyq,c Bᇼ..i#YcMDž=+^!%Y#[r|\  jL*4xTo0_qm LeH`H ė(#9ۮ=,o.u}Wf"%zruƒ[ǝrj 4W䕖ԉ"3QD|g{WZNYj8!RYΞQ`&,])j=CkycGʢzb,s~0W0qe4 ̩ivzF9GO4z.v~O=  FC=  yRi'?)22\d TCS|Q_308HC>76i^ʪ$Lj4?d6v?ݪug q p3ʠ6^cP܂Y>1T 0dS׷-qޣkI xê#>6zn}Zujl8gAO `D×y.i͉\% N>_X=)-F)=X&Fna&èY*JeKDqX=eݽڻ9MLúNL4n&YځLg7KeaJ2ZԸMl.&fy F"K2*9YaJ̳ %[n6=~ VܬRKV*4s{fm<_N?-ʁj*%SӐ&[#DښN5Qnq58 8UݐE[ X7~;<IYU+UI^!]eNx)bP,)XW]8-[u, B-Xw;[hB554 kԞO`kveT:j#Nz7d!|8KiJ-504#HM6~/nRKewU%Z2Jw6-&_ɷ;Pi<=ǵT}udQ%OW`{ҫ,}]^:S6 6`Qt9z~_k ~"Oqe(x_Ot"| |Az<-荙1?fjRd V^C(I1sV 4IPV'&í@n j;!ja&$ҵh3%H~J޵1 :F nZߧHPya)C"C\e!B辉?]G=JXZ_l$}' 7?KnB9 eIy )}p͟}^!)x0QS9,壻{cs3N% w|Y^ۋ(*xXn6}W~XYb۠ZwZTIʎ;CXؖ59sRU',Nf^<[N8 ߅n Ynt E-N:u"h-9S)T,|~10]-wY)#i'rj҈4H@9Z=ba=5К;7Ltn$L Mх<=aj+io"iTriYds!ƌ>E X?0/3 Ѳ%dZ?^z+ri +G[??5W1V`W YJ p)M<* p09P7P$ wFļ)_D9#69Ƃ٣:}Y"4b ۭ;kkPoٳzF b|TU2R=9#Y$I gki̓c6r/-0Up B;eu]}}XvAAX?0!k5qn:Ww>r-+e<:Suz=Bk5:}˥yu;[h"y]W QKmץd7 >[Δh᪞iNݳ op幠:\z0@*T.Kƾm LPO|Sr. ՚}2#|߈[ndI;jv>'n{1y؞/ZWiH3OӅtNdö "R@R4(`ы@!#R|C/҇D2!c~|Q!a(3,f4N d,`QmP wϿwb^^}/I* 6nYuJryU*${ejhXVBf+vV[رr8-6-)Q[3?<ȺzY n~5|axNWzxj_ؾ5l5rɈRˎ;1VSG78)Hwb|p;1|kǟ@=]w3xM5f?춰.OP J|/  Lm,^Y]o6}ׯ3 `Kmڬ,h3J,]C(;S%/\{s7\}2U l~Qூ*qF|Tth늩>qž'4+2pjSdQ_Τ"Iby w?AH$=͎ Y8%x# [>FDo2Q0Y@)GmB b4paaN%+SnM©0NzC1Ϋ\R*Q @612h2h2)ai'`? &c )*J+V f \A62H2V ʨЫ% |u;iEsNb6MG"k7eЇmkyF.GeDeޣN6i0شJ=̕uAI@%!Ms mmp-j[+ $)+'e~w7(B34%wܯ: ^airLT6Ni1牴#VΗJK2w» ixe.wIk7@XodwRѭӲGlӶK?08oRyy s<φ39ur$1+fxYn6+Xl @;p yIfIŎD$KRˎd&VK=߮ub&`(&RQc:}2qtBOS,썒 4AQ59`>+Tmſ-ƚ)Bu`)%/^R 2Ygب|J}bGbˡHH7)JwX %.ҒzrdԀz~56SEuTo;"%:$(Y=LfB1v1"[Wr~7 QBF.: M0_dAp, ]=%g0pt(TTN)P$Ej QKW XJHR)a/lj*䁭Q,Te{{&Q{8xP N>S` irG;u _Hqa̪ʧῳ9.Fp0WGP:O q,$,VX`M0mvi%YDQfc ^RB%CoZ HnдX$G*<Ъ?*Xo9³F$ؓ^q:+!Mvhd0auh{w [H!N0&6F 6i-Z5]\dXC3NlJFxaMጵuSȡ?(M'[e4!ԛV D0v򻣻"WԀ7B髽mh9պ5E6ĪpYLw5X#P!/t+L:} 7JS_apkb8,!a?Rܐs)iWA) P9~ly)5E9nTGWCҦ{maecM'B(qe HC*ngTyjl/r9b۝z>:CJS*eJ!fVW9c 3FnW]h x j W})el©-@hx)^a|_ Alɒ\+@n cDe4X3_ُz]S7T?ml)j |8x=8DWuM{楳3Cz1Op'2e'rg~f{p$~l&g.na"rC"B¸1hP*Bw;kŚbC['N ڹ>!Ւ٬ck}53 J-'xZݏܶ_^v}i|>9ہ;1|F4J]ƒҭ7R~ݺ}}w9Pzէ|x}S_~4ת\kLl}m.hU8GyܺpՖ}{z2|v]qU,{svyqi#wqQ{UyQ*k‹e89b{| eredLy&Z6<{iٙ6J9r@ϴ}PZF~5h} [&U퐺 tVJ&-ևZL!`JJ'_O1,v7`%|rzGqW}-M z}qEljvn_?BofcrTb(3~z'<$#,([r%Rzmw-(_ʂG9y%i7l;#פQ") )}cj̈́wjC?D37k& 5bj_Vy48F=I6ZR-Y׽qxxFA4Llf`&ɱӍV@G-N7z %mۡrr}]ހݡ\daaCG>Ռ[(jXQl Z[c `E,23Y^,ƀ^mLd,:(ނIKG{sY0xXy :aK`m+5xXzhZ7G+JZs,Fʍ"y0#yoUkj$!4r.xB!<[H%'Xs,#%%`rstrRd-QosjpqMkD9X7Չi48sq{U,GT~uqNm>I_UծߟqXx,,N$`zaE%O45[y86z@ |:>98eP8Wh8BJ^/|h !S[E?ZZDw+#PejdhM5VaWl] I8M3Tht^ƹ~=ZLdbK4/ 26Vݶ ɛ}}xq-^dyW $ >bˤ/$܍&&⤯(? m־W`Nh 'xjkF2X2^s!(WkW~:^`'Ʊ'1x8#K}Wg@šB49rD*TґSbBZoF k-%]=5 #VV{ni8=Թ2Diqݼ%N5%|@4ӕ.'!\C#Kwϼf Ex)^L3{Y[{T:0u=uv?X_g 6NOl)ZwRKxÖ40HїrW0ʝ˗w=k!aM"gh9pFbmЊ?QEM}U,"/**ƼC R)xlb7TbrjAF%$- _6|76 YOGD9>JP #nwdzDbH-! C(üj!eB*6>+ =-ZcVc~O_ۊ7o"1D=Ɣk2;nޘ)PzEQwSbMz7PDzݞZޚVҲ|IQ֥Hc6($-tҦ ׼vj'88 > c^9IX|"ja#@k YŻ(A1i\J,%kPT@jkU1S Crˡ T"1/jd}k֘Ϛ8j T=VIì[D]]!#U+:glC<4V~-߸IAx, HOVa83!،HGlQdJSɗUup-TOkki{vœԯvn5.32h:e71pd RUrG>3X )3v0)BȳCFnX IBjّ%jԎW+ơ%BFȇB21 \CaDz6|HAjg8Bk8Pog@ɉ.@7"bs;2ɋrPC7B-5|v9>e?0>ZK1o?@ؑu 9xPX;p͉$wV)2 jNIXaXh>0r(N I$XsC8Ue<}ęUHn_fKYKj2Ǟ$w!TÛxs\&)#e-AYo08e,%rF%SH"r+iU*Dp:"M1R9$O,İJE@ U!KfsЈAH*NgτPdpS* K c#4oO`#H>cA8K3}Ku|zcqvæny9'feC7+ DbJݕ!YA~񴶅,֜vafؖ̚\qv$)LpD*z+jі iBFmգ'(iЀ j{XrcT %Zh"Mj8?_7V t%(a3Lj| (<Ⱥ <"󥉺)'¨EA2ӓq>&'{' :ayƗ5:_H;Y3]D@>\lϲxkil? 88P!z>.tr`9ZILvPktOY@_Aڈ`UcצAzR)kanݚv[&ʸ8[i_V|zbjQ'z RPBu=L?!a >i >n_l÷m @ )bEۈA)]_nX15!CWj{ޕCE-3mexC!,uC|`H:R6EP3/"ʞݪ=^~8Pr{_+@ 2.`3ꑦUv@p5-s(]%2}-n&sW ,ƩIģ44d &abщ&vk*Xuch1kk;q`z p̸Rq8chz#;4`zwx,mj:o6 ǀN̢G7z<8=@C9)0PeSlT~{9Q7uor{nfowǪNFst [ECl@j@źۭWw]AA#&Tv͵'AؑI oAppj1F5@zw5\^fdMl5Nuhygt-ؠLFoqa3yq|3wXDNXqKJ&Qm$hՄpEVF-°N0oA |!* S ,Ʊ*7HH1Wsih/nƠ+OFh"UnF1JRb%RܬM%,`cgkGO\K)2~)[$Wwpx0pm0 y ̜:bݗӻa@T}bQÇt 2Kb]^Y:},^!38cc㲸 B!H3uIQM!^`JK#_s (?UF:u 9o %oړջ>*~%$/gL(R{I:=2F?FLѭt|!ENcj#F[0.6́ $%'of0#ev["DY߉aS2߮Ӵ*A ܿm ]{sS" =6fHYvĠg;{9y&vBoMc-Pd8;v/EOpy>6$%=/6&zdygȳ8}X'.}Oĸ8.<5h2~q5*Q76}*OD١GmY0!u%Wxsww M`GА&'5^0 rZ?Gx } rm{2anm1BX\0`>ϑ0 #jهߓ#w7jCC*kfȝ2D`34_Umq/&rtVm[j_?.9cJL;LnʤaJO\M-STD59c{*S u@KJlKX%5Ƀa2Yk4:Y6pS|;zY{e^,9b{nU4`AQ_a cv!HE~ Ú!]!x0a)=d۞/n~*%CڗƁVsd4]8]F 5~keb;Jc%e#h{"X1k;r/ f;[Rpe/Sb#xH-V_iY]Ʋp*lbFmx eĩee}67ƒoمBMLGkvnOH';ר.{08|`aoCi^?_?uPPatq̍y0~SG vt!?1=x͏V؟}^&I1pȄ,j`{ ;DLt[ pqswy ]4onV|Yl0T],űKQh;%?G|?X`辂S|]x.^RTI^/BƢDgY@`iGUQډeQ H#'8yk@ BF x%'{q10M*Ee@r3z~'CmAe')?^Gqf>۳xʌX5 X7+e6d_|&x+Q/٫t:?W?2]BK#L;c{YH6B@OaP1e%C*N<{ƓllM:k'-x glgr/P  f/Nx}kFw -3tKc"056va U)Jj%Mٻ0cZ%effzʄ?_nV>o3˽+UofŦm]<627j Hī$or[DJ%!f' p\ީyWjVEK(#xuV1fʳ3u=).=# &mڪ$^^dMܔqR1KY3NbX^͛ QZLc]4>hT5WLL-A,$C &>1cMP@x Mj ˅35`"fcfu6Zէܻ]*0W^.y^]l>&Q|R`<1"YqPjkQ e p 8~jyh30 TcXzg'Y# `h;Cǃ:S!3<5_u3Ea!8L'qRw mcP{vz ro,EҦsȴ:qVЃt@Lf"=gkkDaw3Npf~miBl.ZljX[hG]P t ,v.W&VM]kv@liy^7#=>,+TmIU%zDbs%syGNڦӁs̆IP9)i$q_b ˤ5LU@:)H' ֈLc"w_`A7kVV)MgoQOT:5ogir~Wƹr_m#UazDꭆ)͐30?R&}&ɗmeo<~8Y]f>w*+_AؼMx9d;+<4 -uߒ V kyЇ⥯t񡧕E2DTy1'M!q qdB~ib RQ‹2ߛ9c~2 |UZq z+@3uV=Cf&8Q?$Fyi,ŒX÷^g{8 @m P[O526/} A4vմ2 H 6[ځ8temN*_ (i`VA@e|Q gVm3YF0.+WLed1K8H @L*kaúZO\@5ȹM,DŽy*$CjB ;v#֞0LMe6 ͠eQ"QTwGJd֮t;?dQSc,HWI1dXrϛ=F0BTHng% z4^+W&h7mA!DM,1mfe~~9)8qȼ'UT$PoaS^6O8%T/e[(_jtڂѪ.`7z p(?^཈⃧cX.4WCxўvPh^R X+xS-L6359`/Ϛ%(ŝkvw*=YyoVɐiCȖE hbYGe=;MЃP*n Ēo>8I7C8>^-k h»t$\3UY/2BM Pg[6:SϮwH~)Sw~šƋ;+P>h&/Y-גiZ/=  ЦY^2p%~nfÚG{FUp-[i3mNcti=AzX/P\ f`F%ۣU/# E-K U."Ňȓ%{HK!-H(kkgd=&_~ԋ śǿV` Pv aՌ=qs"Ș\d(K!)tHT6 K`maȔ3n;O0z+F&4aG&L4貪hb-\n@UJI5IO2zF'ia- Dj/@0i~.IΖ 5? [G(> ^3RkΚÞbhȋGlXb FAQ;H'? 23Dg1Htq4=C&%5dfZ{/SwKV(y AX ,"nb݌ o3af_G5W0"UG^j$Qd#uDLFQo:ouK>YS2CD@י5,J>l߬'J"̍+F6Iy\3V>רMSu8y 郂YP Md(H'mW+0ڱt 쭺H}֮ R5B4^ g6 qb%*8QtNImDlݬ*t2s`G/7^%iOD5PErv~Jc_g9&}yYrhe`)Jk\7 q~S,q,(GK: WPr]#f[]lêBjv5C-[yPYaptj7" 6l60T+`"H %"trw`iB9gNwĠhEԆ"hYYm`xç־Szk=]ja{dC 1BRec:IrM lȗՈĸ뾧&;İdڻ!jש^UqH\Bq*C&:'c"QR7zF4}ch0h:<ȄwݪL _!Pgo۽w݀=G ) 4/6XL"[ ^X6M!hZ (*~xBFtBhODaM@ biS^뜗}9 z}X٧:S f7o?#qdf=y;Lvh\ⲓs*4 0V/iRi7b82yqtRe0Y0 dz_4LXEI”d}8 #-X#S110KP<~poRY%}!qXืrK:|5-dUxVCFO#R?pRdl1xy6>H|ꄺ;7jLQlNN%B J kS"Y?*!I'SMm`@*LuZ"n*4])qɗɿ3Z f[j1/R4{{2E4ލH";HpXr0^}X]5Fصe>MR5)K9dj+QāRP5+: sX6l^$68ԞXiMStשC5y>E L4muSSEn:B脔PqTp:\puuz]|lB՛LF6ǂ(W!Bެ(q6{]MDHSj." "sC0G>1U~c%2Occ ⡮/^ GpdzýN.J~GjnM`X>lď>;J( J;^5X@{ 8~xP1[ ~_?_Ƿj7{ye}dE6F؎DA皑#FJǵZ~!  T?Kְ O¤F;s,~MTzHMJQywuNz3 Ul&zsˡa";+gw]2GeQ(n+ёD/(!Y:b%e@Hrj`2[b[+JGK"Iv0nRc}ۨ UlϦ夬9Z b]WA&vbPoOʱHSc,^)stKjQn-sh`b]RH// |Y7{g `ں\k7׾jzid ^U\쟤h5\&'R 5-H\M2u0D7%bN1r\0wa&3=ևjKwh rT4RUF4!~cj+=aMTkX#\lnV w?C\Guu=JNq0J3MI^R+]ArPe ܂JLi]JDyP2Uvv&G Ϩ St4;.2FK櫭"NB\ `FUo:ZE޾8P]Vm ܥΨ7`UNI{hC P qhyie!an2/vJ7Zcγ}yMt ' FiwBʌSk`PQӂm!M}.N:]oUt5 LqE{.:/[.s$ia?PN =,YRij" ld/Y'<"vsfKxõ1(d  Kӝ,Lu+]N \UÏ0y> JZq͗AȈ7jwTOK*4K-@K ? ŏ&aU%b^c˪l7FEK (BrNSN~jɷrR37Kde9&,Zx0~sBFFJ$}jgduF}_s (yǁwM)/"aIdz({TıbOE!F{UA Q(Ѱ(dЬ6X zH.Yt.ʬj`%Hba -TO5rx>e<sZlhԕ$Z1jؾ&u؜?54bOޜ~%7^NɵҜ\3Rp9{ F52 ɑf u@fUߠdxސQI1/:4}:~g̋1/^ygbO*г}rv6r 2/~u 49s^SLѤ6aW][.d阆(rҬJ.~bl=+؝ocLn+qs|Ȍt;]'YVQB}3zh]ǽɝy, (-lKuRq4kɵ\mT6y/N,t .[{1BKD!r6t:"[$U0M1Vݞ6ڏ, &0eHŴiɗStn?n9պ^]>)AQ<}iQ\xP W=-0ȕ.0ByYқ8]?8<˞~|5:=it8>6e1\a;M.PK֑eS޹wHJKn/Lv\еc5nz^GLzupՃjPt~P;0@MW@{0cY[.&Ґ$U־o0?sWMe:(r? y\Io<|Hic 7j+bjzPp_.BjBi`_"U>#R!0Clӝި@w\il#\;\ED5wi`l5af~B;9#. NmuN<%ֹ /CU"(B (8e<?Opa`4$==hHvͶhj\  lBuCziRb?~V#ʑ|c0p: 8s!Kҵ)Ol3ՍdXO)jС`uԎhT(˯QJW=0$xk/tL(XClV|sYMDδi9;M~B|pN"uEW=@Q|lbm#D4ME)! z̮7X8:O}Ɋ9rd칯;fwW;6vn w3GrosM[QlnSno?N:99Զ%*.$ٿ릥ACxM~C61/ϹmдN#ƃ9nI0 rC}ۿߎo}f[g4@J+ [Xсc,KmOn7ݟ e4e0a ÀЕpfF"Fa;uI`ѷ@Gr@^wix?1XӠf>q =~l(q};8^VIQ6 $e!U@(KE"|[WyC\o%w9@/ gsũ:ct#T,}_eFlt[PwS0 }[ t x܀?<gz^׊c+l_!施&X214nv  3sv(iv]ZRxD.$ږmG##n6. ]j (3HzOG{GqJҮU161"/jDRiΑ2 _0QrhH#h=CI㔸Qh|,߇;nQnU4,~$.zh p;P;Ϥ8?b)BPx#_I*7⍚VIa@nCT$v;Pv#:DqZ᥶)ɞ wЬc3&Y{\fs8h$ǪG|UfHqܡ m.GRN3R) pXH m QFx{qk4io$-C!q h<:94L]S}PdzBaOI|6?}A_ƍZ$xN*=de9;eIyKKB2#(rb\ NKV#طD.~g ?z @N"m-L\M04RfnaH:Dj`q< 2(=Q8p*w]7DJobOl3e#fo}39ijƅvEzWbPliDD@D\# o\5`> Bh+˯YkwMݦKLcAVvvK?S&U F1x rˋ(Y- +"sN$m`yoɯ8yzuIr]/m\ ,1˪y޹tb׊;[FGIs6SJA/~mn^oz 1 ·iw5L<{Uf@n^$lI`ud:ELdR@c[E^MwG$6m97 \֥y<d57hJp`deyυx^U3u*s~$`}LܗSzsU ,˱H% X]a,$tㆎakH>}}j}#J vMč>|ltFy%=Z<(k!dk(N剈ESR_%;?S4^֤ Cfxx qqOfq|@&4-Bbs/LOD[š `-t;swK1Q$ 1ekU]\Л?ہ7M liGzG\;TʛS'l ^'{`xIm6/Qxqam0_}$B{y-Fj*O)6CN e}1 a3l+}_9z-q+s(?HvR5? ҇{7Ǒ[u# ya˼j h. ǵǍEPXZq8hNc~L_h`p`43}3 fQCJ(MIuޏ lcRWeM<a0 ]Vwt?5ގt hX )  ;rr"ss6EV떱&kbo:%:R:"KEfﭬͷ79v?TGFbm__`>]_ ? /r9 kvgy:hg P0;}7B[E20gRO  #;\W|O&lq9en@MƫNAm(]bյW אNVVʉl~_d#su0 .RD[n.ɔJ{Q"a~(wy`d@Ƣm%I(,pQ`{~ؓТZ{>LKKXS$Mx#{G '9PB) Ks0We B›!zNZ4^"l`Ͳ\uHKQp4ףCȢP*Zguatz'1M6|āc \}a_3G">{4BŸDJ2Nxj/ڡg8o]UؕYw}pr='\__0H=Лr}]fu_YSѕ*ӍtJ:9z!s>C2$u!jB}@n+ uv28<*~~8U p_.!I}ƅxi8<ǷGn:Vj'T؛vPS"Hb #M\ַL3Y\y/XFD~q~,srkS:? m/(S2'gT1p,:jM>-`m #Qo'pH3Ojǰٓ`ɡ$#ᔨnłX <%Ħ@9QtTl]XeC<9%8݃'a!rʹO"}a;L%nwp0 2Ѽ&ֆsusjn4\5yr-H|Ƿ )&d4҆unbyC4wڑQȒ q{n=Gy]ǭIj.W9%?n\bݏiz6V8qXRk~lZ C9hX+.rk1OIO)INY!i+F+[Ii=J<$1Uꅜ788jdii@BX[)Zet{ d0n B`ʌ!2+1a"g& DvHFz]0RB(Iw/ ¯ʝk͚H,2N)f:;:RM9Jg UJj$''nSaL_)?`x SWB$Cr;:q!= ]ϳ"N8$M^"7Xq3n7X\wGݸ lm]oS5 myEP!<qXm5s0xM"H:rK TTHQvy=%+[0`x䠦Q.%޵k]DlQE Qž3|&G{\(Hr)j6*{)1B"k&aÓTI[6 p r >voxJX8:b_&ܚݏ/   1(xZKo8Wp}ʼn]@&M[A=mq+Z;|mɩԽ! |8_zRgh~HsAI%7׿Of\RgV|%$C%g%1h>,9(BbIx:d4Ǩ9y7i1ИBb>JE{ BZ"$ 3,Ļ4u9zԵ9Y~FةV^.P l4ǒɔPjR-:rqfb"A{*S 8#XkSD/A ,cJ.|փvS5튝SC}gZy\~YFߠST#o I*D8gnFٵ$2,`J/_"AK8 ?< l9!"RC6WrS z9x5+Íqi59l4X +1295Xe Kp%de\DzFxuVTE,)EKФQ7 *H2WWW-Z U0XUs E;=hKwQ8B5>q0,brW#F?\H?7'ESaV94FOu eU^Jʲ[/c9uqFt&dAPeV~MfrRɕ2Y &9-GzH9 . *!l5U!#r,1E)8E&Cqk}&-H#bN3z/2_i_>CޢLw ɎCȩJRH 30>IBuDP٥X~ JNMsdEQ%| Oe-aEGR4X0j#%39۲8ҠJ^XEAMS!t)F. rR(I2B(#?à*z>@p2UA8hZ'++sà* HpayePq2;}7p\Saf[vm9Xn 75 `f~W]&[ ت`&vE=-%0n3Jj%`A(Оm*M.H!xW.> SE(FkhK -]D ׬PGx۾'1J .-,EaDIHu,ܒpؘf^tDˬ}xb p@9E6(7EL@PS :0~A22!6nP@:^ڻ*"B\((kjŲ*vgWy8ʔU_lAqN3Q?8;hMCn0iiFcj AlደCB*$K\"xn“ݲ壟]=`2@G8G8 *1(`⏀eyw&jM'/fK@:ؗv 2eqb~sx&WBE6/WCꪉ:}r㈕6Ngirn[A/əѾtl`WUOG_\Ա}tw'nl&Tj<]5F4ps!# mr6~(fNK7`F&|q{=a[i yY֋C!$;@VKߙTIi39+Wy 3SRl]]YC!t vKIta&}xSeOp(}5|M=Ǻhu>o/fH=<Ѯ=%GM𵵽ۉW#B0_DDGD8x>ED ox`ZD<7kbCo)o kr[2r@YM3Ѽ7z3 YG0 xWmo6_qӀ6]`htH 4mH~_$۲-xo=w<翼4;mI>|qVQY3mH{<# 6䵐U&ς4ɳPգ0Ya(kju3˴3, $\-"Ϩl1Ҥ;>5E[A==ռiDw>˳`2{ @I%uFS\`b=Ӛ4h_Rv I-R@9j)-h ٴ1YIXC˥(;}ڀsLLVBת2 -&!\sYc4Di6[IOJ{$~C-ʚ*UKDȮ PBTŚ> cB{X∧ev>^8z+[báηt2XhyvC5 v )"V(pxp\ {8G^ iRBjdn=F=gP箿 \WL+J"^Gd|H-)Wʲfr=FS:zbUaDq6NhD'f[>/YoC:O8"y[H5KԷ!&T6x]xIlL?nN~\d> 6Ko(֘Ů:r1+^j&ƛ-0nfۭF0 `Բoۓf<q(Q4fBfsybāi!)'7{@)mlI2!PWF#6CdL}U_-%%MgS`7kpy@߈̵̠[՟Va%?~b1{N/vn<j }[gG<קЅNMՃu1F9Nm][fޓI8}xWr  [O޺dXC0hF9sZ9}wUdǾpԪz'?G6D;8VK+-%WGfQj~ގOnkجqb(SPٴ?*?TٺΞ OU߇{Ng8,k@<kTnoD1]-[KGG3g}@ϢM)   46[x >Q)n:72U7'=;[xvf~ *Z  a}s+UUznpi}Wg<(\Uz!eV煸S [VbZN@Fb Z]&՝񠅬*k% P!4gn)S& ~Mg^E{vr[ǢXaJ}8ƢwJ5c)\eꇍZiY3Th‚]B/Be?@6cxa] JKQ[2pU2,8D⣓ ̅=,m'vQV HQc0N&u63N<:â60%ҙcySmZBˬ.t (A#;rglcrlaW)gDя ÜqAM3U෌ú+Xԁ[Wl\CW2d,057ٓ q)иx 0]^Ð?@+2D QbtT% ,\K-)p|mDe?S N //DFPycX:0tgX@tY9z"PVdC$hHhR3( ,xVn6+a/N-X(( ɥŘYl%R%){}$%Up͛7áxԅjn1H`R;/~7] {СgumͣA6*GQ36>h?kp-*&Ã#qQ5&ԕUwG*ZU^; g(;$*McM[_bk3xp#}Z( n^:L5!G^1ԼJ@_žΝV)64:i ďxZW356G +a9ls;WF Uvg$- {qbM4z:p ,Xjpc*M%5ꋵ@Nql VDSWD)H8*f,zelBB`'t֡Ka~! ֵ2Q 8CNEnpL6[TŷJIQ(nR gze@ĺC/{4R˃DDhl:fǧ|:zEhmʞi6 Ĕ~(?Oԡ7lHV z4,ߌ'oK̋+ꉐVP-@l-bMI,1_NʑΗ$N2ZNk$vᆂ,-LfM)9gE,VE2&ո?]}Qzww3p/>Z` Vhs4 x\8%ˤ(#<"+.Q9^aYCBU517]oz ^X4+0/U"szSiY0P%Q`W'֓e&_؝O=ARYE :Zq6u3sR/mVfD5"9#m(-IQ |A.X0i5j"3rQeHdwhȘ@t aS.UNRp1gtpyqy=:kR(QB6neY`6z_"!Q%Pvs19Ꝁ9w_>Z%m~z^0F*o^EV%214 T3r`KLt&RYX1eZ֥f^jE^ !(0XQШYsZ()}tV ]>Vp E0i}p׾c592l/֯,1>hQ"dToX2aai MQ ʳgC %/sT ͵I\d"$ /q-%^'c,WDӄ,x?3|4} @w<3 I&O(bYi jM{Vc'hf r?4N۴[fhzb\xutAzR>Bu9y~rjmp C~~"7LtŃ>iH 9t҆Gb1A؆/]/d,e[`OܾkWoŋ(I6osyx:܋+\}wPL\} EEB:\: b12>t6.D,B>ڔe( Uiڡ0"ṗr{}9{'ego,\PU8xP.a ȁ..]PNZłEb9A~ -LǡqFߞݨ:&{g." ˆ [GvbΉͤeHOR,{{ @9)N?Pu`ka(A 25ƞ8!q ]Q{S}?$൱E禓8xNfq(F,Hlenl5~1,-]@ Pv1S!-:=ِ_5~~ReVոá=gC aly^f l,Q_R#'0 8 IAG6 Ji_U׺|jZ07UQစ*xF\j̖+D3\ rqLJQzeTR1ȪȞ ^ 8*"8ZX+-dLKeF)`H؍+0I2c@U \Ɲ,qXeUr%.PFv3~l]a2DaAq$ BD%&I2Y&(,eR{G7h~ O"*51R d~w2l%tAMFreUƉn+ 3` d׹RQWt)qM^j~bUM:,2Ԗ 4-0됍3O~UJ=dÓ!j5nj {}8JISRgH:ZP7ʢ>:h :$fI#.D;夺R kĞGoi^1:)zJs>s)-,]r R7Յ84DhM'=nūt5GOڀ`ih+ч=Ҧjߜkh9?* KpXIδmFP,; 4õxwϿ VjSV^m5$6F& S5R:&uKן\t!9 V[ű5q^p3cWi6gMz,?ڰμ7i?'o\HZX?xoT^VWTY `tJ:b/AB&S)g#ۢ6uYl:f\{ Ɩ@?z:R> i)>圵>DK^^[vԙ5GLG#;tü[AMYHȼ= ]޾ fV ,݅4DS4Fږ9L0Jw(cJ.9c18qvXVu=/H45@z0)BM]tzOmW:[ᠥ\&!lo@<3rQnr_͜ !=k[`$M_}D+L ֺ`lȡ%Ṝa$Zb;l2X<\be#dmgT>p8YbŸ>a`MŤTj) ŠiiGTMW:\r.*0>fe];JMj,6yA6sA4j neBi $rCNM''/J.NFf .{*5$rp1?P{5~A=A Ps7eKƤ5l7h]E.ޝ6ed2͑~hjD$G.BKLΚ۲r~%e{I4JQWTp nV2kX,B$ŝ;/&RNU.ʫ%rk+Ye PLB/9+mB\4W76a!5XQ?F?ApoA{JK):h; +mXSQ(nT]|+J􆯀Pjhl NlʽpH_n%#%`XX.z"uN CIMLqsd&/ tWËQ\;@Fh06%d❚!$Z\<ã˂a'c;aĔNX!ֽ(-ǎdRt-м JSlZap1Ga\rn@nk9' K5}{D>@ys54IȅsQ0Ha%7G$<(Zsbϭwҽ2wI"Z1%|R֙u*И}x&>F'0ffA('8F`E8Ð踰#PZ٬i jFhA8JKlO1'gI7Z༘+1 w5lU(i1k2lٵ"Ŧ0ª; X*,I#{ћCL辝q s[_g@q knAw}܊yÀˬ.%7wג}S|"u⨄=OD0ʷƷ2aCLEڻжf +4A&嘿u;+OQExWAB:}#?_{_'ƩCYpZ\M\yr.֛a,&MTlp*,LZ%Zϼ np^)\FA->*mA,KIk3(!71JڔOCt,.rkџyyy+P`m͡ZRΔD (\$C(8)^ Bz#f֫i]&-^>{{{pWpr5 }"-96 \?_@? EJllKA[mzې i& ދq~-w1PX|fg%n# u͉urH>xFOGʑXTe>%!=|\2XO8e6X$NNdA&hw+ q_ema9YX[ITuv($ .Ɨ[K`k.Q[-hYjbjPnAhD`8`>OZLJ#506i> #},۬8Z0RS70|gOԝ8q w6߂In(FqäU歃IBQ>C]\RZYVPD[P$6,I;D/AX]V6PppBD"xdVrU{޶{ct@K>?XvzڞʟT{NTAά3N% ύBsa&NnV.b0`"^:j3p r> cFm{?xxE}U>_t0l2-:f`fBo%<ƎkcM'N@yж;~I"#.^~BǗ%{_;EnZ—8MطlF/b0]`*`SD M~+QH$^q4Mr%@~7r$&?ShMpCG!pP]zZ{=$l)i؟{-t${|1nvt )jW#_S-f{ <휅߳ih {cJ^R+1]PqD&.>g_#힮/?6=8 VGSїކv$p^΃G,(ubݸ9WnG14#JmlkXb Naf]Y5(Msĩp@8L-7;BVݗucJ"~<~lrlQT)FAظIFCB5"K/U46XżlDpU٬bbt'ٱ[?=Nj>n?yu_3Dag`˼ MPdF;dWSS"4t/e_ 'BB߫IAƯCr rxk#1}ȢtuVFSჁ,1Zik#C7o.Um/W[p8'D{+ULO ð=\b[aݽGJeᏹ" IvtdZ̝N %i\P9|/+{lkmhfK'3R3Y&qc݂֮XLE)S<`"4m y& mII ڌꙁvq<3F:kv?.W /9k4Y2vp54veUTbܽuڈ\Vڷ.ir H(hӶbWۦ_1iAFxOw7 3( X(xwyc׿pxi_.^>0*[JhruOiz%4ߐܲh@cv ~Y6ԂL04gF726 צ;K&vtvEt+i zIy>s*N?>tfwK4w<>_wv Zs^wg&J==krwM1?7k-mtrQa?$75oS6VF]`~6צ*k؉G%K\}p^ck Q _ mCwt<mV[h@!KU@+s;sz8vR5qPJh؎;SiXaU1?VU%CG7amen'QLpo%Gq2OZ^h">N;hhLq+j N L2)PwӷnsʂW`T(0/r}q4}ŏyhY~1vҟh۶{X!e+^ \! !{psΘKq&N ``gIݪ0~ -݌م3nZsa sYǖPS_d ^DQ`6[@CЏ^Y4VMhȽ%LOE@PD%ɰW* ϼF4S!۞՞p ,M Ǫk9<π ׈F6[k.lo^Eɵ<?>z)u=\fWEXa/]R&F-QE}Gx/Z;<_~R޷տv+rb/. "`?eCg&o N1@TA=mz4 6'MRɲJ;W0 hDg<cY)e|3 H^s^KuSأZGB_aʦ><=Hny]4@vfd]l}v> OSxÅ9 /OÔ%DܻUo}x}B&|| ߇,u8y]% ?Ix F/J7l4&, ZCA8?آ(ƨ)J%W=Ye;u9f"gp>qWcS`XLLE *GVdP x$WJK lEZǛTp-H*zbZT*Uo2;LIŷRI[Q~G[K虭th4љlpU)cVϗT5*#@!;={,'yHbL\xVwN(32FRDj.IDLGGoQ(7`Ǥ-趌 X$ KzR)',]#CXPQp8)})C5!m7Fs~קLp )Y(ѐy' :fLBGaR<ēR jObn}~<;t宏̮o{N?ѧSM7BKƑ[uTq(ަN' hp : {-։udZ۷ՎԹϤ#Ylʼ”P^qAuG0d9$j<YKv4xx%$ Ĵ<30σVbJcja(5V{c =w΋v%\D,+2}b)*,I' c'6'@[ Gt/z (sy0kYµV17̲)MSY02ְÁ EHrx6+A!ĐRQ+mRA@ XHB PiRpزE}H HiS6l'\wFFXI?KkmW"2`O@0NA!і>+&ZD,O}L2'|*t+Ѱ ~#M^ BG  )Ǝl3[& ?>x`-Dy]eu11xAjr.i*%AU q{TTWiw@;?|;8ϐAH63)bLZ딜K=iY <*:׀@:~m0J&>Iom% 0-:J [Mә]ǘT U/Y^I3}n9C8)-t"z|RXiқf.+AygM,es鹙.-/"Mo*`_:LMi.pQ$ (rẆmd\V}ƢB, <2C~JY X9/bqSu;v 13˗i. G0bh(a: BiUn3c-7 K "*[ 8&#ukds9Q3z[`87>.8 I>o%b,%t%kbCJJqspr^/h*py]8$fY: gF8AuMQvq#g޻Y'Y@e@p?|${>[ W6vY-.cQ!J-Tx&/"zi1 "1Y: x)i₈gnZ/2%7I~.=omp-'^9o*h8U̒91⹬BG^O]*&pс-7a1wi]QrM@`xrX@X:l< A@垀~}{g㦶_G/r" g%ό3@@XJBKV2î$t~{+eS=`Ԍ/oNϯZ:]t{ LXyDTE9퓫 U\b'^enNm'D DrN."MՕ/ēGB['[$**V5'bϤ cӪ&b\߽2UXH16y"!2>j6VjBo^pV`VĵbʖJ54l/AM!+\Y`\?317NV+AiM" .ץ:6R2X;/a7C̓ٽE8A@qgo}4;;B/' g -a& @.` ɪl|{O`ɰ^E 87a_&gNٍn4ۦ+Uj_a=rDVtM#j GHQuH.o2k GW`TN}=BG8icĀ\?z~un.Sv_;,vol2 .O TG B #cل9ȑT3T[4x)ILyt"NmɒUNixz#!4G~:`P^kv06xC]}Aݫw3:H9@JeCRV{Xp(Q osj q7gs%`@u^I2 Zжf?7o3HA*}ITʴT#:|3L_V/Qri^gCdU '{c !'}UtGx$wt {>To]mmIw؎ eE2#]`P_)94LL'.U'j4abj}n}z 8z8u͵·bqdqh5J } VWc-?[y]Z>Y0`Aٓ_|.E^,ܝW>JCƦVnP>p,ؿMU7?[*ubNm"C4]N"Y5ey*lJ TFQkiAǦ.˲ u L:9.`( (K)Zc"bO4*a*sB# }t إ=9Ln3pK;uqSTtZšl><}+$~d_Ȍ&`fe8Q:J⥽Vl'5$>q73^m-9۱tC FFxa3ݏ<69H4n 4|d&//'zt'KW!ifCp:=I "σÊ{{F0aG=7OdXz7j)睴DX8_W*"W=*28nb5nwyVvZ4"f_"G/915:c"tW2}T#qv1֥Āey̠e7PXT;gE՜19eS;+sԻϷ _<,-PX/:鮗"hWeݿcqᐪÂ+ | ~ N1n@T__42u8nPxێ}ݢ hv^`$81b׍Q5CIEr$ \qg7?|ͷbe*S2Jdm}qg6WW/si^TZ]~-^+2sy h^e;XXmN3qL.(e^ =|svuy!q/h W/aqf22uuJœu/|Jow,%2/nKzE]>~~Rlm_'W)"(쬱]V\]KcZŮvZ I.U嵱Xb'WRP;0Oa/뫴["쳳9.{pWk=x[0KݵxWLE&;L= A)TSQH[[VFحM )3Yg71bB6EySZYUZ#REc-Wz]("jT+"lqMK  \\Ve~8w/yo9ɝϓ<w>\LN]!E^m`\<oRuOCVǺT9jax{ms-V /£d\D蒙1_ Ax}mꄞU Ueq244V̤ `gix(I<hoϪ rU D൹E4'i>h 0>A,CXz3l;#4sZT\Akm-SvԴg*7YcKU.OW:zlޘbi^N'(+S8,ߪRUT`nʪ\ '3EIĶI ǀfHJdf~ 9N繵EoFo`C]XGxGԡ1^s}C-i#pV`-2 [9 $;y>'GnhzGsX#V2\WcAf5kk#50-SإBhAx\K ؄+]1;QErP!rTJ*OS %_?ƛhӋ)lH :XԌV2aMO9RҨՐ=BZ&Im y %_mP:MO(A{ ~V>aۢ՞?+ܒ;bV5_gÓ|!LU(Ӊ Ndj!2Ď*`ԁï`QphFrCǫ=`uUb^9Fm|4b]Ws99䈀"jpH@*r+$UڝPqĉ~d{,TJL[L쪆D(p/>PZS| O0q%tP@@uÀ:~i pD!fw,Mi9.]5eJ6l0T; ,nyspy\HX^( Tfj\A2M&SY@ȫ~nr}'ǡ@tne9XnvU*P$5'wwp`lGb2-#t#E6%0d0ZL>?nfa{WH j?onKP,XpىueF d=.2b2o zGP0[1z'ײ)cCx٣Iop vrYD ~D_I(D~*&M'6SZq?d,^5'ȄF /N}n#Qcj?!d=Ԅ1b"?4+mzHho1 {DQMYx*TRNo\bOC`eȍxQ"]%샙xCo h#L-(+RF/+DForoKG֬UJ`$>KY{ Z_ ܸz0)73qyJbUŋ3H4T>W+|mh9ŖYǑ=)^rhc՝evl_)%{#&GP"ʋJ=@>KIJfPFʚO6)fɖ|Vr[a -$W fP:N8& !|Xn"@Uh0fj#GIp52AoRa7.,u(౭3`#\GdKfX@M .Rƞ6 E||aqRm,*zD;OD *4)4V,·+iw_ $hܴU,jRgܱϼQwMA8Pѱ,*UoȂ#̞ʈڿLt5P_p[Ś nr#}wwQfƜ(1QenC%:˪xKUSG( $P=n$7 w 6PbOK,*?Uׁ19_ 0 +2Qܬ&D3\\B2qN#ݴ4:>7=he7r ?TVTn%)OpAҤ^\32W2#VW8lHUƊE;OݰI`N /߇%@-aP<1 Nx*wpdǺhoow"I|g'*ɛ>#DwJssv=N ή*@XoD@dJ1#Bc!_N B߁`$ [w.=G}0HRC!0gPBd|5[_/izlL_`ߪ;0tPr1eT$NR{D:dLڈc'\a~dC0٨![tI=QU H|V;~{pEwh `{X&RY[೬tB VD[wn>j#f_P/B,xѳNo`/κ9G(J!_ǰ *6[s9-Nid'߽Nκ:s #9rsLB3QP(tӴTn%n3#0d/I.{?ƙ>pDk篅;_\̇Fd2Ԧ-ޥuUQs1 {2'Q| v_So8DЁy7:^*moXuo%p4M),BBaսCO3phX 8FnT!'̯"{U+Ń2{q`(lfe RXK ZUpYShC]%@ o$÷vh&K%e5u +8ix8Z*(+fBW2s2tL&>|L~;п:>y,Jg1v']}Iktm8)=X6; 8"Xm}/_F(Dq[ 9|4a#(fo-_(4µd*aZ]6O~WnC] l[ x+/fP=Ǘ2J+B+C ^ `V6-,ڄnU|3VְPw46r˄_"x5Q_R@nf9͹H\$Yv3a#Ӎ|T/VȫY|KQԻrXPXSr#uNht!1܀:tue.d)* p{ɿgUzRg<[IYHQˊ\Y?ӵuF],sܜPo 3\:墸)/}s'bPą!;w˶v ! T81( ³lhA6}Hg[ MJĊJ]X'Qڌ慦Su+/VrJ!?^#!ȨTExRI.ɺ 'F>Ym[UK@BgmlQ RAqtrvD5Ǐe̕iPSzXFPPf%Q{ \w9:t[h|YP떮X^ٟW9U>$L;džk*DsяE2-5lmʔu-K>J}B|ϸ2)CDwo "~COD", ܃@ֺPQC\]*$ҠDlI兣39;5pe/aUuhzkiMC #jBL@̍iRs'xN}|odmqj,c3 ,Uc(S) f'JX^(/\PTCݣȺP:F[Dml-]UNȔv$ (:lK=&3$ /t5hib͓HѡT}P3Q%Hߟ5{*!J,"*3ݼ8\D=ɚJGFˑeqZ{jDxZCphtt ȩx&j`zV^/<Ķ=c` ePx*E3#9F & 2իӫqc~[xn[^}_M v%-T@Ғ3Q@ሏce tӆk}sͮ^0M?U~kq_M0=jL=i?ٻ.BL]#pVʡr8H nUKq q#.ibWq;na(rhQkݚah2n~}`0S3APp+ <-N[&=;EYho@Bi/9M ˘QU eg+`zw33OC9::Y? c1$q NyR_=JsWMlt]S.sٓGUQHحQhfOWJ#ļB7݌9HpdƏ ZHl:0hC 7 \}ldْ=a0ϻq;~fcuĩ0hPz*ysd[nz. llq 6 +t5||BE D@yPL{2Q$Y[2Ä GHtPVeq88Wڿ6ޅh bul,ÿ};?|.|^u9e7U_ڴ&>-˴/mbdVh FKi20ONcAb&Э4i|Ry5JDޠCコQ0ȼ{ybQ r<h;xTn0+-(d]nS@Q+D $ߡHAӿpc6$;aU>v-/pﹺsURxez]CV 6>}GJt%Y)HWyU"|FӬlW4 WewK2De} C,[\*fбfayҬ6]mGAz=ny)<$R;WH#)[2 K]dGRh-7JzBhcGHQ#)!qĤ43 rJ!oUԛ08L+F-[ kE%= u7$q`GdrrZS>msT,HQVFL=B>n`/bE=B!2 Ũ^[¿ J,da($}g!X,ɠzjyJi[S &-Pg%IzcKzgIE:ʈv?N#W'WoOcO"F&maROazU+un糰&ϢTlanNd%;,'s|2tb>t =xn[3b^`->v2(%Y>s6OWgٯ92Cg A|S. Zq~h3z 8{<+vL &`▚$VM*{b[޼w!+'6uc+Ȝ6Y)0*ו [m3`N3WBPu+'ן36(q'Cl3zu)Uu\ sk$myW7{+.RZS?-amͅvv9t*{Xf O{OnvVP& ֝gFRod(+`F\5X?J'Ue߾ qvD1~xkUYNGV?/ K7SLZ-@Djlg񫄱9g|r٠WB `GtJ?"*!i 1e}v^Г-69<óhc㑸S?vs[ok[!ۄ9Hlֻ E\#1aͪ-hP$0dne(,:*CX-,(*U)#j5]-񁸃h`rF*m9Y9|d=al2b(=ǻխo)f2;D^jXUr=vy[N]_;Ǻqv}爔-FȍZw; o dUoP<l%wqrsh}y7MZu#H+cCy$-mw\0it:Nv9 ^57]9ESMaKle.yˣŨFPXꚷ< `qba[ M7ݒjx?;k7qYenw qt7wQw/ixq0E6ŶPdL 4}7)&}+2?SŲ&7 T.yUX%#"~ ux@b!ܒؖLJɞw7 ,##\xZH4 OqOQN&J**4YKb>Z%ӳ|8ZR7TdIyvu@XdVq-J Kxci[L.zRBHfy *R;bߜB!/CyTJ!yTx'aV[X,=, X\4X۲`nlXIwM C}np>F5o g~ oQm'`Or(oRHF\Ωsah< o! L?[ix~ "AIm s<.<4: Qx!B:p7hXË})?á5e-Xdq)By9qfYb&USBdDM jf`{Iy߁<,"t-c2Ȝ-ṕO`f6}{:\P<+C=Ho}ð 86·ImɞZ i f=7MVw=.,R,hQ-3c$bңLIuIIk  מ`x-@W{7h ~ƹd-8 跔=OϿiomNlg jR*4ԋF'Ǟdּ6Km[sQ^(Io_~DG.lI`("F ;F`_o_8 VCmp=j+iӴiat ߁.y4I[9,V-/o)=*+\~P>H.}@x $?X"< nnIY#_GntoRWIfڃwWaă  pqѼᛥo8tIv;Of B& 1o7)Vb=?E7%:ߊ=[zx- xu.xd/]ϴT[m. @ww9Co(e _V!$?MܜGw IUzX b} EV`=yYL0LHSaoX iڦ\rRۡSESwO2Cߢݲcy G-z|Tr©W{#wKI DtTQȃ{?Tx>>M`+s6]+=chASY?#S}ǻ\{( ZV]դڥoOlBjh3 dNVa?̒Qw] Uvz~Y5(Im?F4S]7j%ٲptP[{#(2$Љ5Y992+ ҠKwƖnKM$KƬ:\MF/J<].ֲ N#nÀ~ H[lK䭛DiEeֹd<?"9+[Sa6}MN#&~FP(J'St9U:/E+r'SƘ!N/ YWqRCU0 Z*ju|>%JNM\MKGl ljD+Q#V'+cztZRU2B6Vw+JYMHGTH>0RJŽ$1s5/,{؁a j"3H6;H0+Dm"UR@nȖ\Y"rY00yVm_#e\gf2 Pn5|i~r6W?R O1NޗS>"&FQ+s'+;yu=іw'NmY¯ęл\); ==‘FgdҪo`6g'(XKyሐUkH^l^YXB^$p I{,Xd\m)yV71&rIēܒhM,h2mj+n 9EwR ųb>V| "IV#0;@Xw4S/voo^?J{#xp)롪׼3|'tݸZ/fptL{\]-W3? 4Q57;l?}]2㝸5&jኸl b#@- >@pݠv֩ZyW1Q8]MvH w>5߯:KNVr~!k޷a$NjFo붴Uw~Pf.W}0lX4 EM8Nk:ȁzw; L=:Rܮoi ]`A:~U3t;={*6n q\xxkK3wX]p8 d؝]!< #c0Owد0t'œ@PDZϳ Bý apj "Dq3빂@ng)W+T5kN%ϔv,EQ3(AY56WGW/i ƯNe<߾uހ ]NC/ J+X仯<k 2W̙'gWJNO)/pp#cPSғ'.K4{iICWRpxK6>t3/xF3=[K'B>=y0cUє8q~S {00,u쟄v̭~ \ a+.H {D&n&R5ѝZkSoElubtn\B:ʺ|6s KާM|8QTEe +9]I'cQo_"Ok*JcCًʰ36:վN b<YSǁ=FRr2㽥2tDvv*xbҙ#)~wy?;~PGIV@ۙeԠ('.~x/WD=BK,Oh]ZW-|t)b>#th(P0U(XbS2`TaYVmxj'>ƢWdg8082P%M2Vvh&Y ZÉ8bc+]NvDɍKbrBwG Ѭ(N 4L:eڳA:Ap:ӣ8#0`9Zvnz%yJuR,\ЛiF)V3.'M2C_Yс%4@+NJ*O]t1.q绺YU5?; 3sk4Dz(I. S:\٣͍'eؙ@e g: $dos3ɭ7t-j1w)&|P^Owpgy9s)N[_x07$PF^%F/oze?8AO1'CLRW@u2} Jl)#߭`+>Zc>x=ksƑ+&LMV-IS.W\*V.)Ib*Tk厉V_?f3x컫J"݃~xB,U~H2~ԋïZ׹:;ϥ1zSYxyq!^zYf$м6԰:5O29+|O`qfiZftHqً=Y9鼿Lp~OǼ+7l\ٍ{&72o86Đ8؍gZeQK] >PRNąWyYjFD~ѿ2_17]fE*a&nu!0!J+22 J!,% ̚\UZFU7pLh`L{1Ӂ瓬{gz̳7ju&f* ҟpc]]J5ndz.C2oVwlbgQV̵c:(z/Ĕk?v 4$#Vq*OJGDK .hHDt"وzV ˜u[% h#\U$?9tu%rpG'RZVFZvAF'bQyyL ݶ;p VDeu[i&(h|zF(]@2ҰFѣO`y2UfEϢ_+I ɷ:N=Sn ]H$.:/:A+>w1{\g7,DLl@'pgqgA2Y2Pܷ9WB؄+]2[" ]aTmdG]D:,UeECy4,Hbcb M{flB׵;3Mjt@|Q[9|9b 0kZ ңMۧ!1,. )RՓ{@p6(oU=B}o 'Q*ҵq6uy 8v'3 _nj}x^9bV`4_gœ|.LR=*@ۍB69D+U(ÏX_Q}tb-W?(6QX=>.m% 0rZxub 4WM.!1̩jAX`LttvرuYRY7i0HSlxidq6+yytq_J~R}ȧ7}F_f DeJp$IoMDrzw Le~9zb7wb'4}Dl^9xٻ>`@iFM77RkLN[syi0/ nA`]dꓐiLUU6kӞ5'1t SLÿr=^x{.MY$9)iQkg~tύ2ZʄQut+%I/,yo60A8 \|u4zB Ыf?+xrssչ^~f8B tؤ15x[ &yeHӵTK WTnR}{_U[FeiVmY23uYu%SYf:j PƨNRF;\b)sv;ߤf)s<̢n*x+ ?OKvn #wOl?paɡ5.rqFQuuSkd-saā ,Ҩ'`UV EWd;4q~ nx I4xļ3 %IJDl/u>R:4?-\Jf]h5U D/2 R&6 H3YTó50Et^%F W4Rΐc(&6 rdi~זף<;G VEGWVF=JHUQ@Jb1e5|- l7ehK8L6mR(ܠ @S 8*x9+duE,-=ԍW _GNf@"7)S6vk}1M2H;Yff-nwisb#Vb$@XV,4 5JBfR*ah@'~o~9{O穥>?%Gw0`BݡppPsejͭ?jIXdÍm T)M*@Dӈُxthm h8i$ (eV#Y+ 䴶W>ѣH@_/^%=\` }T@ XZQurl)C1=L %|o#F!%3E>qG+8c4#6꘣,/ 엡0l ;PVtN3XxΕ;3k.$\YI/d1\VcD$.z"-ҞE| Bq]a͟mPswa_\0{u_Qshʥ\>B١,mmRe)^jnM:`| 'Ug9Qp&lRHz.tc5KXwZPYT|יKTWxdꥬw}ohclL2 deVH K]dMdkpڱ Md"w>Xg"07|Vq'qe|m#In0Ld>>]ZY #?"q 1 {-=D#l$>9ds8Ai5.Pg0"7CȩWřC@, a~Xaʮ胻Zى1eг$0*Juu9;(}<:1mӶ m59aMUwHj.jI!-)#apd:պCZɥeQ.8(m%8 {t!R x 1%̔ELHUƢ?P~ Nvj DA?_uq YnQWv;ǯaI{6Adr`+].f"la*o$dċ&p3(e&ٴI=dP bP ) 'Y%F:? )# _'.  RLȜٗso7IY͙lޡ#`+&&S*`2}gJl,ᒘKQ4RNPNȬޔ#hqwzeh*JPڦt.{pmk 䯋znxwՕdRbR𤍫 RyM b0aBr&V%bفNU` AK(e>~"_%y^ENIF$>^ W~EĹcSQ߸ޅ+H{xenJWR,+x@T+@8>NA ❉Qd'6P&> iוr&d`'ؗfK[f9[>=l\9oafh?{{`zX\؉jz ,4An;ZF=S}֧AZ+}zܝsN$էMl]l%"ybkJvG] o8V1u֙=Ӡ>}!S\i 4J^omʏf@hp#7.ˇsLL9EOu` R _rpbVF_'ؾDzcĕY;PAI3ki؄;>a'[ {(ȕAlI&q? 2$Vm&#/ z`IOe] ?sj쥱4gvfXpSЫ5. 81 *]귏Ue0M | yi<$xF lܙ?tH< zȤEG| LZ =ge|P~>bRi:i0F06Fy y'̑,-cdɹ?\<͖6峹 UW3D om>|w_i.Ȭ[G}kyJQبaxIM , ǦԳbG_L\%~@ѲgQ]e}X/ (J/ŽMeĺ= (u,IC"ۏ9IpqW[\m;ag-Fv@=ogɬ7tǾp?0t'81?mC  t|#=d;J(Α쿟vT`%~fɰSadqvMfpZwҽ +±#q?bM'pRLnKCS3pWؚd50p"ڭx{&C֌r)#01!S,EoxNNDiYbR0ގt[D sU*U *G)aX|[=SȒLru{K!u6fbb>U\~Tt';iwBL\GҽG ףm$R1E߿(8ֺw;lozM9Np1 ;Aiwz}4Kfޭle+iYkwFIV2Թۭ5GO8%[-Gv$۵*i|! G5~Kor@C@4+,"C.& ~_>] ~ ڎ5my)6}ӎm:FA& =}$^'k%8ri5Zy];Q(]tzR3|Cs[)%Bֶ)8v_ߖ'gʶ!4 so%P܍὜2MZz=[YrHy׼9WbR1GO~Not7zծr6o9[Ѽt&;y:Ѽơo%jM B`&; @Vl<6eS/f+25=aNDV뵍u$!QF_gݶrpPf-b80`uM[~ jbp QU+3{ֽJUFZ[s6{@6a0_l)l2@/?Q=/{;b].3;5j\]SX^)谦iZtEE*Fҋ.;Nj]q_hcaA~qBRb *0djnm?["*W~"Ɨ"bJlc_Nox.cЖQjNNA->+hzF ?Et]iϲ*?]S)ï\~e.$#4m&cI m?ꓤ@fBE*~釓΋jHV\آ~}w~:W&m՛K{X{ct'l>fMHRU|BN;K/(Ro>A"^^̞}?*Ї?bfpuڬe<1犗Gi/Į[;t.1d7v   |@|xYKo8Wpsi 1p .P HK#-E$e;Ç$'58o⏿p#\-"jp7ܻ֕N֊R VB71L`pS.3n6t\8gۺjCNp' (y+ua2G:Нka[s ӵ#,-GOdP$r;J[@&./۫e 1i}oϏqES"߼c^U`Pgϡq^BV\}:?\nu'݂QẤh0̖XhUOڸB5ÌYM99A (u$fxu[g.Y;nw;4kq)KԆUd\ (Da v1Zl|DfxϜNl7 /0I87|zJJH;Õ-Pz}y>;|F*pxJ?d(b̨T .MLʽr#cۡϦx'8 0c]d*xm 8Q$PIyY/ 4^baҟyGO"W> 5Du;hC dgئ*q|~R(3OgwШ9g BqYj&릾āu4"R{sODGY?7APރc%iLoo2&y5(pf0H0 )GVAPdM3hƉ|! Xz]D^ea/UɎd&b#=5L`S1ps-tddF-vS(cw^nۅGĬ V+8%ar7PR-!KHFc`D91 g#T֏P;&uJlSo/0xPe`8Kz; ^6U?ᝈ t6$zS$,b66 oOSM/1`\c'5d'8>Y~&[jD7ħ!xfmf܇V?%k֢45ŠS!)szÃB?Y!nDӭ,X}>eV6{~uM?^s:' sBjxUn6+*_@R'&S@.EU8E {f4yQBg )/+*hn^U'PGCUu5V#oHAu=E-ȈʦDD}%%ԝ-u]VQ_[< PʤW $__ߖ)z8YRèy`o0%~PTifF}?sÍ`7j󾇣tBq^{^$Gs3f2gidH|5Q/dj&ߦ\iK93GK4M' 䛂 W^ XK*_5U=#?rC(R^Qv®LeePg/FnWn[^`:2FitSRd4+1)+[)F7 +gV,~Q!!ބV>bfRɪYAF҄=̈uBJ:NLjǙ=Ob'.s)\jw}8ښr{T31_2ra.PrKR?#Bjfs_rZeɄ R#[4*},n!#ޚHGgSA%S떝!Жr8Ce!=bCI 3^+QAVMFL5SKk`(Xɪb xaUh Ǥ3,fI6T*}k-|P6;Ճ=Q ;|w=L@Ju0u-8 iT5& * 2Ys(G?kVhCwjB NgIPۊI6P2t7;܄IєwkGrn}aJsI+Tj;ug#߅#Z}0`h I,ĎCk8ʦjD8 Z:=i|<9j!sfBH ]en#9QPW'w v3! |LT@q"{;,Uȕ>GY PlU)h0O;)>EȄe=^$[8_olѿ+#aN!I6OgҟJC̡h0kH4dѲ |cPrt ZI%(hdOZLwD3}@.8RsX[=&eؾQ+@ {{aeV^z"4ʪ)t@ob~jwifLlo3Nkx 8'u_+,ɰc!&ب3hrRZtw6_ٿl]sN 27pBNbPY[V%x.z"V1L`64fc1.BE'gQc'b-lG!0u?ۀE8hMb]mE&.Rs6Ufq1Py<чXC=}3[{Yx})6{]|xFr#.w^_i_B$[L|NzGl5B찶v8>HԪ,{Z*m~beY+edݝ(ƝtυE| ݷAdT2ȍ R^^,1QN=@*jov?EFnwZ>J}Vq O _dwX6 ᬛ4M{bgjbCJo|ٿC},x,P۰VzpVFnЃ:w7[ᕑ[7FgR~tI.'xDe;̹`L[`}}ّd4ZN2ݵGxv} ޔ~!i8gP(F'R}:Ήy⾗/T F xCt33x[[oܺ~ׯ`Pmts)ZĨ:A]1Dͦ̐mbG>>r6<<)Fg]] [i3xqcJJU|r#0ԪJaW4_yKp_:cel^Q`M4x}vh嗳/Jx 7< 59Z~+,00^AFp@oDa SE{O^e&~1^$Lx+"+R#1Pk8X+i-p`Y]&5m6hd2J˵,O3fu%H@r˶; r^95$f>v7c&o3MEF][8d3 \M b6<Ar $ o > $*@BɤZu?FӣS }{BS{<\|>h`J.ź* tjGA}K+ċG},}@弁 f %A84ڞ@ۀǶ҂wxNw"D$byMW z[ҶG{/ZD~ ;}6UH'tvOp:p1Z@Oz Ź6b ^xꓐbdkRqj.ab+O"Byn>!ܦXBEtγzIJthH@|@6,\ ],A!p!22 Qi!C9wXYݢcnFYkotֺ~޶o;a +ְP d|3J! P(w/vCpRNPhF^q $<Q%˦\~Gm~ԟ)ڰG`U, RX L6`w"D^@b8y#6ƒ[-els!2b9[:4ٖ8ND2Ď2tIB( Iq"q#D,|&"aA.6 yU0X8)eOш iոȓQTwle 2:#|{#j (-Eߦ2N][EAN6 U>~弟:{Q?#2N'E^CeV}@\8؟5ZH'  ?|3%_>x6#΀aA7Po8ȡ?5@U'txڮ2ўQmRKL.w. olR$w+C؄U-44n|}a.+h4ܪȄnc{kp<}њybF4|py1q٥l:!DKqZ`T3Fͅ۴ bѦP2;>V;sFd2WګNTQMU^\? Yy"`Mw]]K࿔&,fب>Ƶ60o@6D]RUєNe I.* f*6e-'2iFpq9;N1n:x /l$=WJOں_9`xuIV0 LtChUAZ2g VP*0[ӧ?pJ/,pښx`5#nc4 4627> H{È1j00#JXġ&SNî ~Ú&$J+"M8ЬF0 ǖxh\!7SgHݢ1t>̄ZKTl`L((Yu"X!,3߶Eh^?4mnC= [(_hn,f9bʅ7ՉG~ԆH] ҅G W8qMuNZO[jLq;ɽڶvvځ\q !} "no ]dk錪~~ 5-"Nm}Ѿ:~-]HFѬ5MlDؑ[C-qvTdh&̭`FPFAZsל69ah*h꿐ɕ>~"G!#)&qF%;Ҏn*rS#8)FfݨR7z}=%Wmnzv-0k@躥7^\tt8eߘU oaGFSaxOvby ԖqD~{ɾE!skVpaʜ~}>gD-sVq[[keTR(8@U𺐿R>c%7L*J1b׼ V oi%R<\0PaS҈{=  >ժܝ<ȻyqV}<;J˹w[Êng^% \x |B#Qxn|kEY/7۰i7$NXC \3HeCTN4>zѐ4!o_t\ [ ǔe⪩޶g&JHo֬2_z"_/GVi嵀w.E`xTM0W 6Bn.,+^8\{R;-I4[mǀ$A"ҮNݼ^'K=^h;$Yn1"})D3S~oHdU'+Ⱥ-JOS+ .Ϭ:ہU GcÜv[(`p]4.L {ZćxP̭O08\gET*J:jѻ:x֧h`5[<χz3_l G\/$"1V`aP9vkqHCrT (+ FIM`Sg^aǘHdOGjna;[2 Sq%Se'ʔW%M,mӿ,).\,7QsRBRÕk{d3}<ͼT2]}F 'Q?~;ɗY!ɝϛV͇+m{a_Iei',d-0-T_O3ykk)oQܒD8(xZm_h.MtA %APD[JBRϐL+֖Ù<3O߿Xnb?gS Ysm4vdVBo n\ʔ[\VF\ef6tl-T%_ύ"cej^`A2⥸ _gSN~3(Z fdg|`)-,}h+6[s=WϦݰbz6]ٗn/ʯj6Oo$"E!o4t=ּhİSgznV0μÛ 2^AMu_ixA}iuRZx_2ץ*fsj-2ZY"1_ƫѸU +B5cA`Vڊ,(֙}'md5i2eXj&iΤqRd*mJQ.%qxms RUҤ55T8(4{sc7^ NAqѱ<^26#bR1*c]B]zٶisĬ\V^8vTl]8*E }%V416\Xs<7Q4%8sPڠSUFNsOuɀ!(P;k4BKhPk YH%uͮ\ړ/kAe4i#zDxA4H=[R? ZXh}j C'w}bRI2jLVii/Vn?zie0X/Xre\Zf"..NF烾CB]0Onx҄ɊJ w(wjA5vD $z9\q1׹91B? E&.Pob=.G v3S[ 9TxԳhC؃kHNg67"i{у]?Kk$MxZQ*:jCᦴ"*v7@`U01t\d; y3c^*3wJllm蒰 05j H!´:#6ib#nh^p.h&,KcO- 4 kE@4snh\iUS5}wtKTs Y@D~@yy888#x~٠vėR#Ə3V=^li:K5' :w1>e/zc(MG? ZB ܽ__w$e; *xL2bRKJ5A+h*wdӏ4 KO1>Px$i nkэ^ڈn񿘑F$nH8?^|YNiy*N;?D_h+. o F_LW.b}#&A%lg!9]2cVq/P8m;u# $zGEpc!+쥰q+nu2J /_RAeBZAe3f+[!rvwX߯sBoIHi[(Уsg;V8ZIƝp"눴rC.:U9X*JN8;l85(Ċ7 ųZXKbxoޙyg^KOkPkw`8B2\E/ SD]GlB1BdʼCxxf.Ὦy!уw*0 淐A&v'a,,uk@+lYWZkYI;eI2Oz?[Y5U4bPBLcwXW 7W.\u(rʥ7H{ך szFp*XdlS:bQC|r㓻oC^;ͼR ;U>w#>1J }Nۈa*~ ,L~gJJt @[[ZWxo"8Cmk0T)6"q.$+HV0ɍ;g RU{m47v~DS۷y0y-Xq\+4/9I|7I A/jpYp޾oe-FKeY'H R[Z4@{1ţe] FXvj P.B-j]RV\Ҡ05Zxƒݫcrf1'z<7r`r @8p%EY@(v<[2n^7GT""8,Y_sLbU_6i Q Q2C:ĖkA]mZU>n.̝yp 4%;H4mb3-8Yiẃb ;%OJ?fZY&uqEo]A/GX.DpTM YxNpa(/&f pE(I22vJGEnX&ZLWeܥyQuJÈ,3 LBd`,JP\/jA X^RO?4vbxGISFDqbЈ`\ܓ/BO?颾}t47 YsM{/t\D"+is@q g:9/3Jr-B@&c+8Kpj^me헃By$ Inw -KD-I|Ͼ\7f | &k_jjNU{1~fUqlB XԌWTl3/҅i6Аat&!)H:ɕR.Ӿvo uDm4(i2m QmKqܸOғ[їFΛ_vBI4.y er*=ql;MJ-Ȍu65ً6bz rv,v +^'㻖x /ywFݱ*cG1# K/J͝Fz2YZ0#$I0w[p@d iZX}07B/e8ŎUz+i͸S(3=_^kKkFYnKEF=vkeD!;HgV雰o9Pn:N Z/fŹ6O!`f¢Ù9>s2X=1}-cݨ"fV4iz <.G(*E7>Br:wK mЙ`kAIXtN>I܌B} E!֌} ??5~K~?TBT;džni@򾿊Zc]|5W('ta mQ| D^ g͐X]=̉e>msw2}8 A"Ǥ)&︛$D'cMn$XswS_;dMT "!s` o,))! $»=.u`c\2.QGGh08U]o]1+~_AOࡢt9M䩣*Uxgl M'[=ѰR$rr) 72O1`:ؙ4oͱG`W8Jߵm ~F(R/JnL,_YXtY3j*Fh0%ʩI.u01_BF Cc$KѢc?^Y{ ntƖ,Ln^@ `4 ,4Ը5SK(-vpS;Aܧ%٪T4K|[ˊS-Ȃszn7NǛbOpvaayo[?{pB],-_щUrVZ}0xdN/\ۧw~N Q3GY?kȭCa GԹs;EhnL-#WZ ( ê;$-We[t L/KaXʱ,KĶ5BL(I OOniv|jU{!S:pI*ۑt@мUw5kZ+>H~+%VZ P@ղ^ kQ1m$E_e5d4kVhL#rĿ#jW,yݣ`x T Z+NF t+[eAoK WSѽSw,`b+ y~roOI#Ic75_]5Ts>PL47AMqytApw-df+D]09فm泌@ ]$Sh0| DechHU$ws"H!"YQQ!㚉a3F;4P-\+imǤGj/+בLr,BJ"QOG-KN=+Y#]mSRԌ#)XDṕڀK8xӔ̢҅&%2շm(+!-gjA]J6оd}^S_F맡@tNڻ1h#hB}e$I@q]X#8(M$_؈cn5zSOQ, .wǼujAB+V ^Ŷy:noCQFZK\&L~aE hZ[t+Cbty1`U>;+Ěldkxܭ3hxq# ̓ D cJblo҃b"Q aq...~X B^NrPwk)P}DנGX`!8oj+`ċƙ]o[U:MTzX\ p C#ل>-6-W3>3usT|`WE jmPvtmeUDDA$FOS`HMՋw7/LT+C%ʵ2S.r BzuY o@? 8Ƙm'LM0!?02g+9K=hk&5@ [ FejJG7G#[j[;K 8!n ۚ97!aeRPRA@{y9_=JvY^OPl(@g|zrk E[!#?^XV L'gOiG3pVLbɪzPQyqqu!b;U[ \@VQ`1!}xm9x.x.K @I9|RHކChf~ !S_sSd\Z@^-4+Q#g%'yeV;ї3[~60aGgi]tޟF^Dk߈J ?r]a@l]QmњCl)VZU ۇx<3I?pm,1i' +PƳ9o{&N;`w~ ׇϏ)[PvBk;Ayvz=z~8m9AݾW{ۛԭwӲQ9>`<jS ۄySFz͢4̷(u8xp"]!ЌQ :{ Ϡ* 0 g{ |?]@m׬0)rI>1b)4g#LhW+ e遢ґ$vB.V ו*W|ؒPGZxsWKAU,[= "体9{?b7G-O-QWAib"Uw01AO. 3`腰QMEAО1CfI@n+ײhqU߽˗C-vp݂6(l! s;lJTEZmhL1 kyK;하IK!1x䓭~f:vz/!-%jn YH Rx2xkC03m >9GG7Hp?Urwkܗ;\@GA}ZLj&%gq6MAfvcP.ޛ;]`;F\ gΦ8&"W4aҏ#Z ~sNr8>[k:<ڣѹs@_"vjG4miI2XOJ7z'&ž޷m,fD F7z05YF}H?p~x+/4?ޓ=fs-?oS 88Q=а(\Д1Rw˓u2$x <:~f7 \ Z 4ȜаP 5#4ac!$sX4gD wf8];B XSqF-ypd2 Z(VȔz;'*;@TFd30w.!xgwA遙,\ HPd N4>N1{';v>;ˣ):'\- >9> &鑙c?q|2[0ԡ~>y:0zp{?ۏe(^aBtf?rU5|_T[~lnس#gr"ní O9z15[:,+u53Ar7<lgu%K{-|'Qp@}n!e"ֈ^MM&R`E8NL/Pw-Om;^q'!UŇ{nb-/\cr#?]G`;񽳱I ~D{X{ + |+NJ xXmo6_qf+`Hmb@@n0 tHKRNd9v/,;>Bg *]]MbQf'(/F䪒?5KQ5eibBF[{ݲIP% CS #-ryXQTzߛ]|@Ztb\r+@74UaI( *U.Sf)bW//_|fF+Rƌfa&N=TVrófrhnG~P&gy4TQöcgyVy<{D_ Vbʕu;}^ ڲhsv`}C{۸C=; kw_f>CGѠbL +#ᘆ%H5䆎=@ 趔y FU zVLE~CZ_B&!{>j I|߈7m@+aR=Qjԯ7XYx*zT5߱N遅8*K0]h<6 'jaؤ} (/9?ܽN7N= Nc9/4Ɵ W>|8&snjd+b S8jkJnRpch*TTmz%R!hVi[p1ƨ8c" 1gSscq `f_dl++R+{0u{_ PϘ{ &|1oln5tz^5jE&^Jk杳oMvfc?/`G99H^9HrJ-ZԈ_ntk9ޫ^"hi…}I~xYmo6_ʼn5P4M5hD[l%Q%)ޯs|eEjмs/>*,<ǏJXβk#웓.~;bJ[ے#2Vk,?]5Z5̦^"MT:X+3dY+d@}d>r:=!29&Yr1XF[ޜ,v@hy_u i ;3`v`KCU42gNTVu3PiTmĄ+rnX a U )Zܜ8z 1{)}ߴVIyY//S#ʝ3|SYVBˌUj-LcZ/wN0k:g콅V`  i@{8ˡY}ܒZKG i&Ky*K II"xV"Peڪ" 0Yף4 f .76gP0u6#wt`'=8x\uK^gJ,ErP+˾Rrx@qހH )-n4BjD <\?7F)z1GY,:yP9D 3A.~ؐhAQRqڦVMغoXJ%º.Zi+K3Jw-wRR!B<&.RRzíɊԛ twqZ+qS{X\ٝ:I]JuF0˓V|9HSKx=t0wENi;[EuOtKY-h_J[iւ]էm!C$=UHXt5j0qe{wQ[NJ ںU8a<+G< v>Ow7#t7߁GHʮ}Dgk x.=Ko\{l-䲰\hM|d~l*yGg5R _S[#xe!o#zv󯭱U3F;.tWzWnH%ZsG Q[TB!Oy56nQȶ:H!D|* UD|7пc0biWW@ł -Юmoy{;вEJkBZצ<'2Vܗ݃wXܮotZۇpC>>G(k? ȫXr}_M[+ٷTs;`=,~漕)Db1 7ɤ[϶.eiPJk{rV8? c['Gv^>K͘،x{z fxE;g"QFZi ŭx 0#a˾}֑hbL>YLǞQ_lcGvq4e3x.)lNWqz-.̫?,q 5aQc|k  {LzxYmo6_ʼn5P,ɂ5hD[l%Q%)ޯs|eEjмsOz{;+lUΓY!xeFw']r2ź%7F.dƭT X_ZpEafS/̦As jn,čyhh2xw2 xVs2M9zN{ \,Sϊ7,-NP;m4~f鼯:E^xɝ̙ԂB0VHa!k*Q7'^el:(avƍ[46b 9K7Lް*l-g T]nS=ec>oCnɼ×zp©n+e*n 1З'53 +Xi4 =Zп >nw#s]6>WvNg襤Rݻl|1y)sȃcC)#mg.|amۂEufW(躭1tnO=?N[EW#A(CQWxK4@_coZr3J! y m0z#?8BChq|4ڇY4>zึ܀.һnȥ'B. ;5/wO_ͦw zVSm-55"W0gg<X8ӑhcJBw@wՏF(:HDY25w| %ѼGU[* /?|M1XclzB7+0Q5oMw#^ +<l1S@/vpvq~4X,x[ڐ?N耨΍py8;-[-{mKQn*k;[y!}Y=xQvKס(pȂ֗ݗ ugyK. ǩS +߃& C5`_+9hnVr<ҶC`r1.[53 R8FM(w2v ؓ<8?~Ci̶xzG9lItB4$LMZ˂-ڝXϼT8$!B!&a3g ITt%l`$oсU˗.!S&BQǥ7q'wdCʳbS$]ֵ?2PC3hl^اW$5ߪN9ȕSj;%ճP0:<]n4E'6)>XGhbɩNe6-E)5+ c*&S?͔lÁg$Ngb(9 [|"-7W:IF Vd|uDffwI աseǣE\fԯc y\Rs`B=& &Lbc7d.n_ \*$Xm.]]9#yd Vu4_zh< Gg5 fjoy3TQ{J:m'x˜qFr}<҄,?qfXUXM_*A  yTN,Mxn0 ~ N;' EX/;Elː}QР˦ISV_~>qЍZ4@כDn]Ha0)y;C>LG97hAɚ(`2N[.[NoɌ' w7qJl.ՒVwm{xQ7-kWw|a,W^f Rڜ!UB138}~y1NTSPm2R* P49È $m0lEЙ w3s! }S 8%7EO/kS. Xqk<}ۥ5%T3Bl}YY5ܙ5՝.$UYwHA.G(>3TlQŕRq q2<6u>11LEU%d֧+>]dq(-_^‹M xXko6_y@N0$4K1`NAbZHKRN_CRGc'/,>yνN+۲DIEԅ*fM9<5uliش3'gBZ,Ifn8*2øēxjhO&"i$Dd05-q;M⭪lмQg'^:zЉ={A.${oU46 #:usISZʼ&q1N>N<؈tU%=ZZ(M_2qMLQ Lḭɡ[<݅|p1&\^Y 5BhK6VC^2>? 9m!a&bZ=  ppW2.!=$ބ Tz(ɥ_m*\_ /޾K\s>}V7w#Pr2@Qgq*yC5wPЪ]HG/~E6ϮYW: VZ{ݻ"rzYGTnXx_D]L;=TCOP @؟oJWݬ)+_8{ Eo*ȖX=7TrZ/ ڽ3r} D:>/Ϋ%eUDޢFBv"ߋp7rȞP٦ {aS9@Tk t&9Y?W]!IUQMІb]1|x@k'BAj,WXiVzk4ݠI<ۅC /e0̠`sŊg@c[XDs[ jDȍ"K>`(7ws/ޜGB{&8tyw\Dk+8iE^Vw BMĪRȮ:'#mV֘[;  ffO21x[[۶~ׯ`p6ٶ@==A۠ ry-f#*IufHJKv əod߽o+_74P2ǏRy)+i5~qGWe! =^ꭴ/*5*DmM0*dӫxf--Ív^ݵ8!(,ճG=wL$h֏ I,fIiʳG puVLg7}3'Z2Y𫮄_)aMXKPiwz6U˟.+![bT&pe'~Lf+fgaW"6ا}*v.MUlf"Ȳ/sYaڦ].LQ/}ݐ }#^KsX7a:f7,">5z& x4ΟD>;9$[;Gp5*QJoj[iqC4(Emt;``c=I…v;L"fi@o‡H ڃ:Mi~%Mej6XH.CYH=MYͮ5'lJ}fu+ć,Bk-s'66{+WrFVF+k2/B6 *Z΋35=tߏudX5Xl"18 r{1 I)O# ^V}aY5kt%ijX)潕ٞ֙t/4mMbH;}}c> TVyg.܂!@Ñ>ܶ;۬ tPx ff(CY]) q}&,1bG>lZ!sD|4vQHze?;e]Ma}nʺPeƱH( 5tHJKSɂ-<_iT&Nޥ3#բ3qa?깨`f䦙3No6p#6O"B-2%W^@ܮvȃ؇ȒD$ Ug"R<Z`؎l_i5d; N m72 o( `XSvdeDzc~ɯ6e}}+y^tSK |(8W SeTu{o˕g7-B?q5.jqF""B]LmVFJwv<~ACzhyRԅ1vc0 -7 ./3i6]M2ߦd E>k%HT*$:8am!0IݭChn4$Ez/`i5ɤA]E_߶i췻;XY)8b?F_ m% h;O5I)֗cD#ƅ,+48:Sɶ'vw+Y9&LJ+[@\*1yEXD)tu b"x0i%|J_ph|/; AtD!C >jzt(Z >չմ~dC%FL..s)9e[+{8ζezT3QS(:;=/8ǒ5:tMQud%ZJ=y6H8P z3:(O(q]Eq2'!ؚM6$AJ|0]3UTJbN nqB_x|p&8m0dxlΩ /R<(:ñɺؑNeͧξs!p|vjJi;X$C-uZ&7Ab-!ˬTveBay1 s@xnE_mfr{ ~ۑ^Ϋ yX9s< 1>Pd l1/z<Io\W㋰a3X̃C&B]ޭ1EVs>ku6(RBǗ\;K2/ts< QyB}H^^̩Oa< rř@~WjdabF()Bz #BxEF W$aWKH D QN BE|UG6QXI7OK NÉgVb2iA3 x*{Z#}6p_ICzPvB^Vs2%=-7HH%ߚL^$wqF69aL,[ P<1wH<^uII,6 I&u{~83'(^9|zM2KGWڕAb.JwR jw-H2q vC<7@;2ܣfFic >Z]3A!s!8cb!~S.kp5}o.`o6Vs)xGH=Jz>u;Et?J0oh ,a =ь'g.2}|$=z0j6J# **&D9":T8bs IF+gBeMOCv,Ę0"Zy^ͻ]AГU5uY-2*o3wGDZ [=mA["M4esC]Ikn毓jQ׬hнBP+xZ[o7~_b [۠,袱ޤAjX UcE[﹐se9q}h4<<3ή޽~)Vg4dKuʟ*8z4u}fgtN/t*6kia>d*ʯLޝL'd[㋙pyPjp,JQB:x4N$2Q<=2*.i HhFw\\Lo8Lg93z+>Nxc8Jz%`ﺓ6dQ cZэuF'Y𮚖(;ר)}Tg#&(o;dUFUY_"~RÁF2R$˺\Cfr`5 Io ZIĢ*S\EE:oM]*m $,%B@ZZԎ1ZuP!01x0yڪLkz\RSy"CYCV:+q~>ڔN9X. Di!F?ԥȾHD $H[[EL襸ݑ-Aݸ$){P۬$2JRE"aUz_ =Ka3r8Ny퓐iy"t>z dֶcNBt#z#~ ( f:# d CUPs= żKd}TNt8i=f0 BSx{4:85~YWǨomfkHeqtUq&b5kvgTղ/F!ƕ^ʥm׺jA &Lp\HHw͉ m]d~J[hӾ[̢{>3줊 LzV?Bo5B ? THQ;8p9ڹ{B>` xa 1*QqaB3mo?1ƌމ1,j4x؉kTF<_ju3Maٔ<0&P. ]@pF B[$N>b.9x85BgSSE/dPTK`bАo0/b aBH*E;$&ᨉ]ZҠ=gDo$?e9ښJV#F(:Ƃ^JV8.nvY@a/߃38H_,f"05ޛqAYo(-}#H^vRFO΅׿` J5#~f!I?YI;<cU#Z]fؽTWEehRL 4`'~xSP"rζV^ k*Or`%.hD4'Z5y ֕*WK<0q4~i;ˣ4sJT\A+i-c)AckjгD5|Umi%r-+_MOf"7-S-嵣p^62"DsD埞8lbZ]I3f207*SG‹b*2NhQ"T5'Z8d_xA/!*z_yj/O|A7ϡ:,j"(B(3ī!dZ8 ?Eqk9 "f#dhv2q}'ϧQȑ4),x ݀i8UtjP47êRM@**BE4mS]+%?"K&ĭkkVx}ӈ\;*A c.r%IvT~;Gz%~ q}GnOej s<{]u{e &/glZrRk9*>V5:tD`.V3<̥ w 6NdTfB@XUͶgIyyrQ>cK BUV_! ceʭ=͍덅(<(tg!fQ=PLZP1]8`$"X6y^)"+bт!р " R8&|B ߂Nŋ% x"R)%I.|gm$wuqI_]8X#K3Qٜp\ vZdTpӅH2gv@(mYq!(vOjz~aMDYMm'MlFߒC*/5HXR^[ ܠ8Q*xM,nIRн3x aEz5,Xbjo#P.&&ߢ,);_+ N\4_+ieb@Ij-Xq&ərws 먭VM M_S/"\7<{9kkQhG τm@~~ "u%2X'}ܖmMGCC[@`qCkHhE ؀ۄ+wP<G;-FA)J" zBeºX߸ͧ Q+G-ig;&m`߳ӞR}&$` 40 <~:(ٔli_R;Z`+FT}tM$zV OGJxO9.EF޻LG^qKt(aU{ E1>P{'w' ,`xtH^b; s&4ss?G'g@._'lLT=6Rgw..,wiꭖ &ia/K@m287FaW48m7zGChbT8 8cyb @H! #B-+W5R +-TA9>\w`\ !F%e?WwcU*R w5ʠނLk[C[+H³YgjjN{+{@;[ߧ):f``PRi5l#ÀӢi}:T w#`kYܶxY'4\3T\v5P.fL:( {Yq\cX ܂'%amki,q/bٔ@<cohכXjkDM ĩrÖeE-#/g=%jVҧ[D85&~9~`J~Q.&{.|ۚ@i*B̙$8E#Iv6Y9p]Eϵֻ+Ajk2PuNQj De~BʾUZ ca|M3{dܼ,7{۞f.ZEϛncY9FTp+yjzcF#}ŨI>_2ޫ^menGkz;0[7p8+]CVʢ`NB/#(hE0ն1H[g/$\0KC[t]U3daDګ߹ID4J( Cj)܊ǥO `%.𶝚_c \1PD`Di ģKuU5lZ. RހXk!0ܼ! n g4((<6<1>0>P${9'@ȗ*j͉FTĿ%H!JTn 46QbJ#"[(A)n|zJ1#'JV []̧WOTFCD Xu B xuzA%£Ư;y*Z%((+-r:HPYelqz&TCUp'fx.ujH f l+8wVܽYja) HꯊKvg} Vw) ,!rޝDp |CH_i!˭J? %^1=|8cUy𱬂WCy<ƜN@b;x[>ԋRsq l VASէ}eƄ{`+孄sHi!wg?t#\!v|'斠L4X@59@_ ^I @^>t8Q`61]v5 ţȣ AN1λwF+s[|ձl?<ſ!3_ 4u<  S6MG=sy–L;IɒH7X@ܳ,'uK.!7uܖȿډB#Ч5&G>iM@լ<{{71?v9MO ^Qn]Ѡ["tB\?qepZg*;9;8a?`2߷f:q !,p36]pv܏"[W8PYirA ='0G'[]b3~ŵ}!{GR *jr-6?qo?:UAPsEhTHsw/=U0a hwHX4,%$<9EƏǝ xxbH( L]8rR1vxQH1e iȁ;FkF\4iU\X^WnϾ Su[ӝz=ha6|*[r/ۤqEGl_wdAȠׂ=x8:g/u!;p:X?lx:W&MHiL 峎>vxV2d<\ YZ* ?{{*g5Uֲ JC 8L0ZI-F_zAsvXg a"'2OlgSTBs =ռϘJ`u;;qs6UbPs K8XG8ƲRO!?lEf5L*s ڢ6{K+75ΦJϖЬ3΅A^WD13Rhҟ)nn
KP5$rmM0< JސEa!B-=ƵWKYsn#+alQ::ǚ`3^H,O|)ns ^.(m8ZݿM=lHxB1 |T[%3Uf"cG<!S#IJbh5||OJX {Ībmo7$g,kkOءjDxP$.v_Qy*'XrOJ;]L-!,l{VBLÀUq])sx;ok=*8)^#.U_ $bE+ĢD*RE[k3BL)6?sZ MlH:u9]0yhOLq ֙_ rBڅ+ZchjM9iz!_^ijVA]=Zt2hu~X\N▎+= pϤ 1jMkڱ*˂H96D8HS2'o LH%2`Y-Θ h5 CVY )fl) Y̥S.}qX7سs(+X j(QkD1 up=?a tAWE9^z- q*sq|3lcFXXjP,mxQ1~ }=J\4FλT.Q <\ᯏ>o;SU&-iN1jLx9WA<#m8Pgwkjq'=[0CiVV%Ad[vN!:pC;2Zބ7 =cxOǾ *#X l}`SB&OnuyjM]d# Kq@+o%.a;ƸѡmՏQ4oߣF1giZU?4׺u~3[ؙf?['~rӧLN&}LcaB>`ӫF^x,x3CYV} yׯ c}B0d[b;bN8tx^hY'[A*~@+_< 0,}l{Q6E)GBkF5| Ӑ,8}x4r֮tnXY#q @d wQÀ.R-_n]ןJiƲ'Q"W:pC&S.CEfSwO77w(Bҁk6krꕇ{Gwiڈx wa^Vt9[-,3hr/^'ɿ)@XK&xpz>1%&!mv˶[i߳R]]ޣ ָst]? gk휰ٵXk`CZbnL i^ʀBc Zy-fu*ƒ"݃o[ep{Uv7?^mE5_jW 6ݬ`>{G&zܲ7<cJ܂v ůBHJXďnCV27ʅh<(TSbUrߥ܍RxURSc,F/[hwc =Bf7~8;]nޮ=rv>hm,@tVW滦V;XpA$ lTbn]pOT分p͞ vo5{ PAW+ ^Uc@;/IβI@q? Ǽ[U26a3>K3hsjkƵiuq$VbE[5Z;Wp=vg֫gU5׸;M`^o!};`) j&̘s9{,w}nsSJ\w.,gfbm# ﺙ:. D?iբR7ЭkB:wܟ@ʼn_^  U4Gx\{ >n&n\x %"%Mdl+9#z߽$ïͺh6Ht?ӻ$O\fK^jan*3lύ4P̋k-rƍTKge VZH#;'J7Dg809sV\ܜm+E*gFl2qdzF3akR3T*&S6C7gs3 O'-SR-* 8Zp5a0x*J@ρ+dGz.r=|zf'|VJK#o |˳J039bR0w 3%L`"esUoލ',eO]e Um`?Gi8-L8Nͩ'q{=JA UJɧا2˒op>_u.FZ+ᏰZR/Gx)I0U@~rGW+˭Fs/Z\(O& T xVn6+XB (Y@..¾p%j55E$ )ĉ7x$͛7,c/ rPGu'W.C{s^`=PЪ~uj=T@ܓd efe1lsb78Y)_=We!9Oĩl&TemUݭ9.o}~F]Ttd \hV1BO|J"l`jt6O@O" ^)6<„m(^᨜T6Q݉ZL N55^yZ':FQ'a>umn@Ƃ֩$u3-}g d_G^Ks#o+dj=6/j:W2{5]I5 b j|IL웨(SAg/I0+RS I95yjdT m.,?%v/>I~ލlۘ3g/ ]iY }E>%Wµ I˝Fp=iR2Z\=mfWijJ9;!8ۂ7ywjlgmHR۹'eH^ AӋ#Dt ZuE1g3eE~9s_GRr]0ߐsHjnd!J >ٝ3x#6JJyM~q̯bVDB9cR#:jx`U P]p ji= elmRKKX,kD"hfƂ';eF!Zb SԠ[RV|.D̍.{A n#WiUXmm ؑ&W rjo(L;?v6wsv2>gpd?h~N?AwwSruڋ89!مƙen7zn9fH KpY>gJWbj\1+zQk4phwFB?~sv"sVօP2eR/ر,gL/B FvzVC* ;n67e€sa15yZWF^H)i2  腕zVJ/b)8fD+ 6vc)~1,禔w{T>yxln<d8 rtRehUg\B'OUeoEŀ\, 8ȭr7բ%i&8J`V o,|L6QY~:A}a@Xai"yW?* 88VLVc26%7<*zB\J?p (c'w+ޏ SnK}SY` M~(^FiϢnw$[KE}B~=abi=`)fg'`C wc04Z.*g23 {peGw4h `vahGu:XSkTrs撔%l)5˛] h@^?C6 })Lg@\fLMfH3Y^>*pWZTgZMF;ںb/hoassӵ+zwHI=tx{*ȡ$YBZkMgm+2MlTRfΆGBK'9>W GV +::?}vEzo :ٿNn7"*6Yz4%sWոZWq=2#: X@o*-Qp-_:l*Wk,u]=m9W+?i^@0UHJSNO=LXŷh +w0QCcֳS|O:ci y?,{naBI%ZBⱚP d)ZW8{ck?y=9 OyTEV-ڐLUU\KtFLjJRdiouÞ2ᑌ)>~Tq;&F8.k&bΧC1@臾)mSf]Qy+r,C"YJ&L|*8UL`B`ٯDZf\#µ )ʂL /K"BdZ>n~56x)2C(Мzh^`wTxN;rF7t@ѻ.ԥ@MeR! †`g[̋8N$CM=Q?WnDQIa--z#QEyfB`–5:iۨCqPC7\3Lv$,juHt.dc~P? t `Hf֜>M/* A6n#*g+k`ZڴFXDٗi@vLvFL KapQ%L-2kp8Z`'0#V=ή >%/=P\ư㧧NHxEsoc.GsX&ݠ۪a(Yh']6KF&7|!p`Њ 8`~" 5'E }= ;ϼ(0 UO=Ws; k3LeqHc7A*Cс$P]@ A$.{,986bɤQo I^[aD7DEYY8q)}b껏_j%Jؖ[/o\p9&5O`KY${qUsɤ@y*#6rHjlESklaP-t'UH\NKA+c!6(GA W 9f!Pa|xN9TϬ\؉XtB`B uX+@wќjĎrTQDþZ3weM坫Fg{3ڂgN؈ }uO"'~uQ: y2,e羣Qk^٧EE#=# xJN B58T3$a [0*9Ŷr6)z<L3$8,Y!+FU.*rI骤 UA }l NL銘/8,Lܨ uXb WB3P[\'M()_2Փ׹"TtoQxҽؿ{:nEBRkwע=vٮ uHv_N>}TW-ܙ#Cf} 7zPR78 w cF)aҞhSVKϓښzwX4˻rhgB]p~VXlёE$片8WW_rGD=+'=KwJ+,ryA[솅wɾaH1w3E%SA6Nd1XL(BgLЂ8wQ5 vʘ \9$e k3ڋcs(hpAn-[(JFr^]]Aa*w50khImi[ٛBbga1HB갱O{Wbh8nAwM(`i+ G`@+"wzc?!gy oeW<.D؇u35䧅-UaW|=)n }4MTffR׬CZ!f"PI޷yc A!l4l<ӾvikkM<]k۰#8< Ui&vW!sJ'Ds%-M;q{=ݽwUKiqӵ]M=%?ײ*ćP!nkٍZ` m$WvHxmYnrO3?= dĘn>;Ƌ^7}L?23CYGDX[0~<[ 'ˎ$JB>%Pk~ ߆ / wvR2L-v%+ ~·] dw{e v~Xc=r4PٱcFHy 9qM%p9,<Pq/0[ζz8P)Q*F+vCܱGy.pd/vF1 .W0-xn8SޤvŮ"M(PŢWHYJ9uCQr4zcY$oO^}t:*+@t2}"/t]T]a\l[۠XޮeujoQϊEO؆z0+t:WeQGHt3+M HU6PUvēy~DVI.G5I{[-w2X;Aۈew /v$,DGe*)nߢnϱH1QwH_JvvOV"6x[moUQκ*@>)Dv حE%KNwfHrߤ;Yq[4Iwq3$ë.{۲X&C ~r2¾8w'sJ[.<7Feʭ4i%K쵰b&$՜/i[k^Ԃ1ݤ=cEk0_k0VJu%X!8Nf0ͣp'@1+.~{}.+z˿4{9tGE¥n"Z8A,  0%B/N[lD%SV?Mi,_Tg,~ &s';uw ;:]0*B>M*?~CDCc?^&U}{:F|jc|*3%v,vZR(,6j_%(غ% D>u;ra4A qT@T( fmT$/"m2m!`=j< q Xl4ݽ](I, =\n@,D,#+Ka]ER-*27l'mN01X Y&ǰdks ae݊SL"&!vje!_m~5 u{X1V_I Y1 !0a"\afT{>$޼f+>AU'L=^j?ƨũ.>RY&Jx.CE4'K+d CN=P?:oE X`%capjc5ӱ 10$㪖l+Ӕ.G:ݗaㄇ71q§IB篟^{¨G( } w,㒜d)EZWH=oT@Jf8e޵UΞnǽ<9YT69 H 3bVN*DQ;)/D.5LBW# aF,9Ƥ`jRՉHE Q6 $,n~$\ys! ub?S#E ,^.s!:ޛPPǴ':)[?ڜB :"jPo܎ Bws-Ly'- 6=F2N&L GWpLP0E㌝t>s0tV.3=x.nsn1cQWۯlۻr|oYmgdL9mv 3:m}|ܔ}ʸ⿼韗ev\ѹ.kEC^W{2սCQcX)=Fv vv_eyY`G\xv'.o n]%֠wwYyTBkU+A=W 8Pi(FV1ܓ2j-39hjsrԭکlK?Ǡo X3 }p1kqH 3f^wI&I g~ dʏI%Uudy}F5\d.T+w[+i0Gɇ=~sꤹxεNMItbn5qw1!HNJ^O!M )NH+|̝I^39܎N%Mv:/ܜEoS^nu\(fAv~J\}`*>C^>ʇ [qP6&P2 h=6O󙷋48ڊ K3^fLP+ďhŽJ:l%Uw l l۶{SJp7cRlmȫ{q'|cdAj¹ ͳ_QvK&5bb^)*6/*=9M]r>:ûɝ N+ C$a]=%kfHqP^D6GIW^]x`#ԦE+4E( {(HJu{ |4O4s[:-$& "Pm{',O\ͬB29 YkYva6e3􄞶5'$a<98@VԢ[+#>M2@k݌^o= i `߼yTHK[[qfB/? xLW8Ӱ x/?cƤغ/~Oi@w4o#* VX04x[moܸ_3FwE6Ai=EQp%.ITDZ/4!n["M3tk-Yc?M๑&TQԼ˹r.Sn*UDm &Ӊc9S\j ~z5ɔlߊs-2U鄣뽻.)@2s;MU&n˗,u7gs`+5n7`'l| +e@<5Ss` w!B_т7RZ ,X7Jr備`ư%\HqĬ"csUoǶU 8H"aKC5y|~cO@lm!QG`ZY.0yx]5(͡YiӒ^ҒV tLpLhxU ^uj(]JZ6ά]insÀWo __gOzUK=c=ueUM)4Qɒk `f]ah6رaX!Ky y֥9 awDlg2S`|+H_E. QL6Lg݄ ҄;sUg<>=n7(YE>vY;0hrlbᜡGܯx๥(}T97˳ۿ>^O'e𳖏"Qxd1UՊq[m$s`ޑ#WQ+/G"IY !?}KX%7lYݢ%/w\i:5OH?#<ϽGX;2Z Lsx,L @e&.YP21CP/8&t;ZEMPq-HV5F TBkaR;8ߝ=zиG7[묜 M&cWf}wIJO euc@q bSgy=QG#lu\`dVm ZB<= 9=öТVTHs̤ES#0>tσGت b>!>e-|`9cPҩ -G'{sa I ¡;ceMB,l9E'$jd;nB٧5w.ﴛ1a*J[;퀥3amf{p\PQTp$[c=Ԁ+"{Wg}{mű4]F\C{i!b̗.otEX۔5┅׳p*HSeMQ7]Hoe![O;"pj oʌr$3_>SRoyAq3{FN1΅6swZdKcHF֘&pdn4wY~'#۲-7QsWv_qvh fW\l V|q5վa\g쐜T߮s#=#tn&7Mv8ϽO7[ *^wc] % +Ѝ38ebGVa.ZMO p#jlJ !E dㄡζ&.qȯfܴF }'336zWPML\խ\ bD?hA]``1*# 1ԪfXMV4_9ۛ s.e%OvgQ<^aLCFp4q!R.bm-X]aFSR,8Q8[XVK Ue?qtOS[tàyoCt\Dѻk2ũi ćy'uW 65<˻JP4ixFQ&d_8ԉAI(ܟϻҟ͟R>.Ȉ&%ڮ/ `3AQ0x W|#0N[?sʖl]*PGo:eqi,Dk.xr8Yޜ]_lta1^zB ON ER[ YW]wBpb3YLN6Yb/&;L||͞ =^\دnd鰄?-O xPF6 | OTWH0PqFW|G`EO@*3ҬR\%n3r=kv9b /ur. (ﯞ"Җ'ĭ>!a&C^,8&poV544k]P 1mEZ*-pvn &p{ `+E,MS]Dex+cK}`ޗ ~Ų{}1 m Zbs;qWЄ0v  A?[8xKo6SpbtV1f6mX쑒ԃr{Iby|~χǧ<[a YBr I~Q͏]\g~yb1LsQaTPJQUrA-G"9??&=y bDy<b9xKߐjbuaz1qؒo[9@
gwC.5|m<x phjDCST9RmQ|aꊙU :mH[/.^3}E^ƆʶدXX{XwI Ǻ k$֓BN`dgS[=LiDr"q3ʤ[}F yDlp7??’Jfښ0.#>nXcU3FGS`_5zƱ +/?}_6O麍~` Bs04b޺gKp9*̲=,Ɋ\:+BFNO/22̟FjA7p ɟ욹XYfܭIv"hr4]QI :;K)j LB}+so,kyܺM*ێ3rV_seMڎo8!23Є-6#z,ސ fBUZ湜WKil?S^QL?T0^ ŗܜhy rBfG)4n8>ZaxTK0W mR%Hlci5CbGU=c;)P8qfnj_w߾?|)y!Բ CwoJpO #*lHxPW9 u_5%8Q-,H|Q#Xh[.DM]ax,ObZGZ9MMm@ńmku {Ʃ<W'ɀ.X" 44X}vW&ęw*30H4h^CgTO" w 8;Rĝө\?+ pO ZlƦFӟ )?Cƒʻ }}nVV y$0wv86+fx}> EL]kjՏGHvo')=d=IG%w\w`ZfN:˷aYʼ\NmׄPMJ<L*;ߡ F:hk8phc^ZX`.gAڝVJݪ2ȼ`*k"֖͒\PJVjcF"zf_u qؔ rcKɋUWH• Ż궢8椼3 b0D%HK<~0sڪCI躰?t"SxSӽQA9NE 箣(#  Va¡h<}k$P3- ni<|~;=wHIoP|ͤK|vNl*a>dz'溿;>r!H=̃f07noU+!}Z{rɆ!S.5'[ZM#[^Z;.] ŠAE6ǰtX` 1G=qHL%A6rPNrK9y8~I&T'$akd* e:+ieC^M0Q]=r&eràpHe'K9@Qz._ YXt1db'PгJ|C ؔ~IG_Bl (ߍwrTVByl}#'6%LI BhnWnvNya+NP:*,D希E`Y6 ʡP[*QX9Q(:Jf-Pɵ7^2 BʓY״b:ujZKvw폐̞l|vJD B Ȃ_:k.P>ΏtHወc];tz x7[ km;UV8 1`[5ڭ\L .c/­,r@&7`5 $Pܵ[3$uq  +1?#=9lEq94dZ)td(9N^dixUsN5I܈W10`{f@ n :4ɽ2yKn|]ܑ,t}C^=~^=fe G0D׹T7rs~ tjpMU]մou# `]D:xMo0 wMkl@ap @{٩Pl֪OSߏ6&DA>|Rfw_mn~_}iz9PurXd!_gJfQ1HU)(s}]Ȣfw!xbg8$)E)񄲩++}5t \KxWQo6~ׯi@i0ml)k1 %$]݉gM.b}wwN7?\|ghC$.($]: V8aC4v•ޫZU"(k~j?z Ckϳh趴rA/| `wKrFtJgPCy&89<#rP䕕XT \O\]BТDg| +b,ĖDyu>ɳ7k/x|g3uxRYߧ` E ڪXN]zwFfʵO/e }h} PAl[ q9 ['Q*l0EPg  &L`7nJ$Lo'\̂5owظ뇷Tnj{:`a-Q6l~:aϯEUi<im1z@Z6?~XkK{i1F5.ו[:࿬_s@FF3*G`ࠟxk􋚫O\{665Rq@w*@|w^ ǜeq:Jrޒ;`W@c#緈0 EE@jL Rp;h$ m(i!4/#hH4G=RdѓZDanG=>ЗCX<OljB8$:!G9>43ꇓv,+nR_eR@ۡuX_ܜԷ{J!;A:4]jQF2M*TLx8'3F{'n3?>ۂ  H`}xTOk0Sy$fad;VH)4d*KFzN>,'ںFTc{ԙ*+? 6@S^s!$ώr2N۱Y4#Bj4ȾCqmd1UAjTPŜؿx{XX]w p@+#һx*a'ՑYsDң1+S4ԧ'6%('WW{D&8b[ c~+yo)هؐFWf/=zFyusDe(oJYqvitR,+欍w_rjxYێ6}W.h'B (^l.nEPbC IwdI]۱[!/x/o޿IހV,+6`f-4w+ZU ƈBd UVsZ5Rf"SoHX5ZpV f7>m4"dswPۏ''hr,2C(^ 3;@sXqH\]EJ҄}疨o`q~|/hi95(36&a ar(Q[E1J 05^2[$y{X$+?$~lS!Īì2OͼP86΍jU"bNA |@ku QuN΋S΀+o&< B%#cL-I4yΪXB1ۯơ Ttn5 Pm9K^_XQ|JXVEZҹo"a 8 zO`͎ǥA!;#CDr.&u`s\78gRd%V[.BhnсzRG"V)Oғ"8 ꂴdY[O-ӅBpku72n^{4b/Tj@y]wLxuFs)Ų?JO\^yҲ;kUD"m7/RLǙyS71xQi r9QQ)! 0V- }Sޜ⠛jo;ʡwF*~T1n[<0kΫ(0gI+d7quFRǕ~YjXatG~װ@,qz3u wD\Hɶ..\oD50'I~)Vt/[nK|,`goyDذi1.R :+ Gfpt3+7s4ѫԤ+0[EZ CkQ_b %T[) HqD@;J0/t(AJ\\|j i;7MmqO!)PQxVlg@2SV6#߄."g;}!%Uxű_yhDLt3y/DX5 zL8LZqC M>[S' U?4/Snpcg7Mȩ*jM}ֳ mc տla&BDrNkƒ*Qg0ODPV1զ/+AxXlw~lFoĞtRM<\>"DR-h_|jy1?:|^bk\8)GQdmce%/B# (*5hfԝSKH.:h1؅6m]݀qLx#*1DfU y,x7ArQ`?Du4hJ?IDHZ9~ԉ  Fݧa_ݼwAf<SfPkYMCIcgi+0R=o)De2ZJ3Z )+i2`R ;"ŝωģc-e?z$::W Vo3K-yG(S =vCR4ZuJVIHTs4z`>6o6DXZ@ tZxNكC+S_=2m!Aɋ 2!ͼ֧dolhIsƩxc? ϗ war*`xZ[o8~ׯIq:@16m;&<-h٥Hx^dKe vZG|FJ?}/'ky2?f3KI0{=*⯣ ܷ 6UyAS{+1|SjwޓBi˙M'UȁSVk|gZyv>zOXSt#3 !ۂ.Yǘnʤyq}lW˃ZPҠC2W+ ҠsnҚ/%nEj9*D1ݸԫLHPJ5dZ+ԮGs>|vώ _p8c ca:#{N."J`rIE#h8+/ܗvϺoHZDF;&Z &zmMxsjfcXJۛ|;&z3d?sԆH %80%Wnĉ/ ^I=-m-Vޮz1[N]ϔ1'sɉ)rjZ0JudIјaOMѮ&iÁv Ɇ 0HS4K^V0rПaT2J42wWI96,~JNP'Z+HX4 kiS #d.BcٺtrMS C L;QJĹ1B4OkDŽ[wh?jɅ[@ѡ,Kqo&GjzZ)`l܏Yxҡc{5 T߄;.Mx㡺B $o nfףyl>a&vKW;fRP_oI'Yr?TgcZq{Vevɽf5 9o9"/\rА.| BΟ 49}wǬ.}w%n6)yŌ T01L}w8}gp|-~'ΥGW^ vj5|%;5wm!'o1yZ\7 f Gzv ~5 _'UMDOVGM\jn;'CH^ Vc0xUmD _aD\/$HkAH]q~w3̄gyIvo~{c{@Ek$Fzty;kF;Gkk~Uu1Z3;\CE`pґ+ h}&8Z7ug5u݀ߖ[_>cQi%O 0(NBvd!f'%lIb/oʄ$yDW=Nd؃4%Sؓ n{* z09RXqOx򰵮COP&Is3|~a'C[̠:!PmvEjHwk ^'Q!)uA@a0$*Udѡ &rROX*YNYERWg})Ԙ[h6Xe] 2 E{{G$ot } qQy˓V>= q5Ȏ.^=AZY ՞5rVTȝ;ޖφ:f5 v2;?hYNjf3 bkT8e)f0B#:^W1=(*+yqn L}^M#"|-˕ # <kUD0z*-24ХY)DbXlkbʌBGL [à\ɸe҆0`)R`;Ei!=Ly,p ^n. V&B[o6kXdހ,KgTK0'ZUH@'Tnd rsL`Bkif˕hQ}nVjq%Q3(b 6"= F$^]% A{*mldt݄uwC>6$sx8g )3 zXcFuѨ?SbnjUL iCO?wj^-@ц%nd 0)Z+VJڸ<p/%!/!y}R 1~,yhGT҈9n lR(Ys(c/0m1 yƢQ Bs 'gFո;sSUj9 #7ފ:n򞕅0OgX.16z/v#l/ٳ9M(C` _;(nvX&vv=GgSC<, O`P[Kf]HA8@ʥd(*2'D9@6,lMxj+V=XK5O߾|!lh;,Te;rN+Dih5{st} m J܋҅soļ:6/U]&!GwsϷ&b3 MQ6-9#ݠ3&9XS@7BrmԃѰ l` WYu^bnp prDu%S2h^RB?koE񈽎r-ErB?q؍b|Ro]QS)s e׉—@=>y_)%Y-;;EVC YUC>#Fo>$yDv\Aaqelt=! S^\x# rƃ e:iX^s@mYB"8@ǶV- 4N+q 5+-h&]?Uj<z:8?Ab5B)+圙5ȳ?~ Makob!J]4c%^ TC`u]qw?13i|0%t{_vd~?ޭ.2Y=z8~H%qY"YF--%nJ2z鴽 !s;һnܩw!iㄿ-~ߐb$wLU٬;Oa̻4]9I_4KpuyA-t/[@h3b64bDk^e SUkPPkla/jO]S}1 +Ϲ s{U8bVOlhym䕃;<bg\d uyߧ(x@ .`0 bEQ{ b;S_tvf64Q3 `ˑ^w rJCX wfѐ pZPa܃W=m\瘤=% ٯ>~; N[`]m\evߺ}UEMQain)@C ={ 1t|t3{F0u,{ HI +p>%,^Sdx}(h /SA ;[J Cqgd[U|2=Fv7fP`HV^A"z]:D >BIϯp5:,}pZtpڃ*cѽꂖ {[AT-1/(3/N5,Srq0Isx =I7~t!\#> *)ZxP[]py]9E,p] E'<8t*[@ ^}oK,3Yt?tթ]>1YamtXWהU)7FM $I4<xΰ@A?:m!R}Q2-?82Q.@Tl21R r@tЋ\$|aOcs⥬n;|[ȖCTM0E`6{\wJdQ"AAಸ@z+ S)x6B}m+5"Ӯ k*R0.:hL5{l[fjQ)tI;jpǹT|ؓ7W]>]{On_`V=9N,5 !fʭN ֣P[.JB_aaݤǥ#LV[L5 HeP]0TXz+܃&ΞuG,<>dOO^Mf%2_, d, rYe._Geۢm}5=kJO "]?/hp`z@j=}Klbڹ)iA'I1յD2riP7~+W @lFfKhH^-;Z\HQ໤, *wC?"Ejd"r?*x,7x;)iD{tz!Gj]¯:OCXaˑA`$B]h$SX#fÅoIׅo: ?FNYd6xYKoFWl}qQc@B)i(r*VHܚ䲻K꡿3CEar%jvv׷7o~ᆥ&ϖ" gqʕsyRُ's|n`y-#lh-d-+,Az1wbbխd'/p#~ՠ|9\Ix_$"6Rbi^ ¨er:1E,X. g\˓5*Xg@ -pAݣ\2^br*$ 6l볋R #Da`jL QD7(Da 7ER"ۿ{q!k'ߣ ]i>95 Hjؖ+W C1ߓ4߂"; dI/ఉt) PhڲxXlP0o)\ŜyχcI%b7 W`Sqc|C$"Ybˬ wA  :ixxUn0 +8؀CݺX{٩-&jKH7˾~dn6.qL]gh窱 XKSg#zZ8߯a(nFaQAhL]Mn:nGRnՎ}-b,'a Z OajlnCI'#͂QTKɤ;mR8v<~n*91h50!zH<\cBBUb5eOg:p ȸ;n2(&S&PЊu˜}upjKqi%*'h/W;?iF,yZF41,To Ê R̙WCHWOh_ {Qoo[ą=h~ RxVN0}WLBT$f#UPP%REUfcuv;d/esf<Ǜ磯̓pdE(*ԆɨQJVX#UX/JscjNb&KCJjɍE+9{!(7d4jDaGyg>%1WyV(:B1QiFC!'0l/@9j+$ÎGu:?DHD&&_V\JѾcDw˶[۬&pVH@áT~ i\6SH&6jUb [dI,ιDzMg;5n,YV8^1s$*XY1l/;M mG_'6 5g)XTKK=\ Q:qZg( is@TQJ)}2f]ʿlnY.9X1 ^iwqĝ3=;8"we m\L͑Fhbp2\_T~tWƉ_¢ \z_Og$k *lo\\U,B- }w##[&~+ / 3VQC}4->Jmf|$RCD#RG~S,~:4~٤/cagFx͙[o6+XKaP,nvF#--)tbjKvL9C 8*Oy6ϢI GN8ڐniq+lFѽ 'Zk2F(ۜZ jR%sd,X$L'7sM? 1( xy/7 IȉX%-3nt._ XK̠6 [|1T("֩ ׂ/3@R/ss};/qU$J/ss*~^]``-L(鋼}<*X}dOd % @|(oxgCt1P[Z+-buA Гxy$dز)xXWPKBa 1!C\1|@yaw? lNym vUPXr0BT1rga}jUbl(I/p#F;^4lY^m}va(rjR錼E ~[ V HvQnrBh(^ۛ$+z]5"^'Żŷx8%,@B_)Ћe9v roEwҒ/ohyyWTUeL.H[~or)l\? j֛n4WaS%펯۸׫fso;9-izkUF=ϣٻڈKm?_gYnq:o[uX{D83- \V4{&0*7}Z6d1#eIIVs!\[^-耝IR}m:oa@y- ^ueUдo{Nbmը<2P]wCָ1 lU=YjAM#͎Ğ4!Gz䲗AmcK0Ė;أy2W.n,Q{t9 ˏͽ}X 3NŹ´*ȃ1n+7f6(3<ߺۘ <@$? 41 KfxUn0 +8؀(aNmѱV2$:i$;I%Ed7_ A#4:O'n>䅜fCՕGlmyp}Sɔ Q[Ugp2T|a * XOTl.1:o}BYF%K- ,&ŽBq`t`p{]R"O񗐈L-`_$:k|5a`fPz؀B<kס= D8RZ2PΎя;*BɎI/ &&͎›awDfPiLo OE6*:GQM>E'64DIsD'f_zn$A^ČRۊu t m5bϢT6o9[)ړۦr2pvERKyAgt0XoCiKRO{*J3nh鴐Q9Y7' Z؄dz>H8 эuxUL^fp`mh Fƈlܪ'HI!~l1|D0VK'm8T{TXaSݒ02B* @`'z8ˍrlZq4&҉+ 6c^gZY!kkY"W'(IȚ|LW7,(i9,Q|?K~]^  +Cl DxQo0 {mR␦noVU=U#+$(6ǺO?S_v_˷_-|yTM>+Hd{ɳhKlfN?⇂X!6]i_ٔ pڒy+Ő:+ț ZEMkJT+RD笹IF/p7 XpXħR~QE)!;`2+ j4ǀ-pd"=j0L^kbu>k ixT} $np׼Qt 7NWb8\{1dd,|؁4UeyIɋIɬㅍum*i 2 {O- Sym~K8p;KjjvLPY3hʛ)j×KQJ[3} :5ZF ߍ,G!cc/( &Ys͢ hhѲ*1=J/r&6~nZVgfgDj^|h v XǀB2$uǭ -4L$<0XNw"_gR"PH_@|bhUl cAQ=^A_Hxwi1Y6dF,sMRteC9)VkKg4EJu!3!/¸_lR#=$b-%ykv~xX]o6}ׯӰ dCڴXy(:JI~.?$Kn8E׽!%]{8˫77~ƶHႬKA0m.ޮ= ,T۱J^cFQ@ۨYN,:?2ˍyѲ$g YNc+yƜNj;b ]*j[䕪țK3fLzLۧ'XB^#~YCo?"xnȳ5k.EA u 0`)iVJ Ԍ]Kh5Ex5gipD(9 9Ƈ%V&Rԍ5NzN YՀVP993#>hmlrBVZޥV"9h{cD:x>ѫsA! [ԼY1eqQho>ՌK";sB1J S(j4,kDL.`󨳃^f֧PƜVB19FD7(~]^gᬙ!s1n?$` M`q"`M%$T%E(<&3 oo5o#S_[vo{F E"ncp>؞g`U-RBĂkB8{Vqm>Ѳb/5fHyG) w5ִ~;e;[$jV_CɫmL/|\Lm] 6ow9F ©CshsJ^>S!Eh\^P겲 44E.ELJ++)Y?bX6CfՔ!OLcy}0G 4641C]IVo &Ǡ![ԓb%'i#yo(H@[&߮uDZtb=K@k_?]r0l^O?bK9,&xO+.pGwxZ}+Lp hE3c41a'6\Z|*z%~^QYJL;}.Yvձ(~3Gt?_g3ůYw+Rj(xXMo8W\ *i6iXQԒ]leeؽ؆|O2כ?n!w&iWA3c]*7?0s'.JG|1h+=6ߠ4Dttg>u Df?BBB&) u. ƎЗ ^i9N 2ɬ͉c;t|Fj('}P+D+@gh@ωߥXS(ը|R[A(/\<®B Yr/"HWw{Cm8C)jۄfGR/0O#R+~+Zm*"3$zG$^ varFnSV_]QHX VCeÜT/-J2G8D"Zom0? k~m2e~PkܼL+Etf;z[7IwHHp2 ގVlbیPBezNj2B`*L5Y_+WV.o^:5),NXYJ:/vAv96!d%Jђa}ThOn/#$qmaTUp&yO] Ə*PdW)܉S:-u ͨ 7[{C;Jn(#5 e!$FiY1%/n| $SNa!?~h^[`+(#Tφ)Kǂ- z~6BSIxiP( 7VFNJgu"e<@13JjcRY;CͽtD ƀ]!-os*KmZ֚w^I64 9 w wn<xXn6}߯`h\ @nS~ (id1HvΐVZk}{VFf^OƂ;]v:zLp '!{O4`5{ ֥M HrCȿfq'}̮@Eʙ .DKa nʳmqP8m2KNNfm߄NpOI.K ]BY! atMOiB씥yGES_$hJjƐy#ڌ=(lbt8B7 <-9 Vݍ=z pѧ]sgmg'u"=mo\qyqx$PQ)hkT~sPlXk33) SդCfסPXc VPiEH`!4 ƞ Z 3hQGUњh_nOs ,XqILcU>W%F-lukQ  mΚK}F.3ƥ՘I\Z0eXNx`VBXJ8cW2TluZ~diM^VǙ2_ M&Jb$ ,2^ðgw&ցm0DZ`֬kj" zG8'b7KvZWkvfF&X'/݂qM0U'ed, $bknJC} E]x#0@qF|ٚo1cǠx>A>4BLIr.J |z7Mܡ76$sR~5〷'o+s R51be92) <`xdWX ^5˚J$?^e,spk5%1N=y[`CAl46{揭ꋽ 4]'σfƋZR3}GaWo0v&Q% +kĐB6k-6~/KSֈ 3>]u""[4`B4e2T Y  C($R#W& 2dr.'j?Sn[VQ&E;A!coupD|(O qX[ 8fuS*U+O S*(gpN%78b78طOSH[[yB<δk{=daC I S4.X}|0ޗ/+ks ;7W:^?~} L#θE u EZOJo0T~(^8vr@D3бꋆ|q{Nkt~DTT D+%P|eUz~ f ^peԧ^άق4ްzM#m 'w_np *@*l/~кu{!j79i>8|  M6= P<.m{*E򨻭u wwo#YxZ]s6}jg+[ufE3Nt24m: IؐK )u!D_{/^uWbr-JRN|'[܋Y6'Og qȷ TL8=N^*a%_KҎ7π lcЖH%`:pFz> ~ '?x^T#u+dkgE z4WGH8NB$Dhl@ehm.mmH_m{oZ]vZ^}w QUw隿?k~-/]f8~_N\f!zR'n@Rh I`KLH™xa̛-hާ XPٕh^볹~4Pȣ2d {g3osbLUy9{{D1*Y3ofAQ5UW[}`RJ!bދ̴b)(6$WPG0W=_#a͘&>.nB\M`.g=yC` 7kY5z]c1~=O=~[MVe뽓Fk^soǤ?O<.9K߳we-H!Xșu'p0-NtW‚G=\.8jQy'Xv\=H|*Y{ɒo:ѡ$zk"rQӋ5XI%eOh:j=2:>e~HqZ7TQA;UCg!`wQ|5>6:3Rp`,)03^@+%?֬c4s`#dУa osKpviQѴR1@E7)o_&/d2ϭh6:u9ݯ7蠕obX^a@ /2( E2E)pA뒮vCpmWϝ$ ȑ[a곜Q@ѴP, ב@c; յS5e.>^Zz^r|+C f1r4^BWpprNA׮jB|vʶ8.dt=$znM*荛J!nR9+<gy SxmL8y LH~x,if+[o| B䃪O+ΑB\T3}55,fY@:L~^PdQb4KZZľoY7 ;>}gW98 >=]C4 Iە`|"(Yb+RF{F Z՘R/Bmb 5?G hA쭊$ӕ;?ۮ0 uFFkQ>W#X.£ I#ib}LBzoQG^̆>C2ᥝR` mog7 loA^VM) _?9u Hڊt~y+vwߗ.]/h    qpf8qx[sܶ~_n'+LfZ笩۩'uqN#qG$q@Ivߧ&/.ow]@߽z^u6M,V^)?_\{}?Ui|{N,Y*rӫ<^E93SHkܽl*E#kbm^\O$ {~=ǃ6Pin u=-y%{y1w민Htvg޺*H&XZ`Uso(x]_//Ѵ:V?{Ka%RS0MrSuC3,Mf—Jx+uOTR~BחVOҮzWF1=~Pniƨ;*鴩oW[ έ}?۫q#չ%r1|4#Z^SB7^-0⚝v1j҉^'x Vk/ɪUNj UDd#,sT$iH[b b *йv!tmQfP0}fZ;i)/׶צd^rLl,Zlh'[zWGL2q_F;ge"0i.YDl\Xa' Z~Eu9wv?jce֝]:u/hwƪx_F*uSҠa#Z*uLù*!+ARɝ*ÒVNI)B>ѭIp~ ;zIO<<dzϟBP'³1> 9`c sA'같n=YW# sCs^aDKxq/z**hQQ1&XiD(8 ^aEW/+ɝs,G^SI.|t/ӑNe<-+ݨ;54xGlYsπ.+nϠ3#5eaH DKSSk8VLʼ٫b<`pXP@GaF717g3%zF[S11K8rMTyP|j/ny],tӀs_\h#s`E0_?,!@B*V35'|~0%~ Yo_ T,ks),3 %C}Hv u@hMfFQ\ g Z)4<æ m"g0938l+x.Q|=8{S#E ݯ0X2;.(uit6D9*)1Pp1 Of'P s:l Ds #rHF X8 :7hLЬE`bJٺ32cO5{9ǰb0i8I> SQ.&DVDQC= 'Yc\؈O(?+NUItj_o J}TwgaÞXTx@7z/O:'NLr)^XU= 5\|fpю4E5;9Y7blsUR.m ]q轢)s;Ƙ `/aBJ|ISKEw*&1lʕkȉgm 4CK]6+^Ƭv+"m6XUTG_mlH N]#Ͽ؎G__8~Pg?C0<9WpzSc\+-P'Y_2$iO] ,y0N]mvI"Ê($[| tq7L40s"6R9J7J#0S@U!6q J7E P&[JV#Iq#Jlf/^'Y'״,O$Iduz ]l],?+l q.4x[mU;tpԩQ;6bIPKi]nIwCRΗi]r83y慼~{waY6 /O-,gyɵrٗ ˭4V9;0FeS^Qoyh6ptvd{ i 1X^qc.G 3OΦ+gjt_%g iҏ鄖}d¯1U'33qhg쥴h(/ iJ&Ul4qsڢ0rdٳK¤aaSQǁܖٷyn:5]-Wբe nFdL͍+հVGj=EO븤-UceөUH]d s A,5Vw9315h$+( RldGR(sZC"C* [K V ԂUW6^l`;yGZzk8P9J|A-a@lp1o3  e0WN7=?PzWazU[0VT"Au'!0r`2Y,l9DHՒ+ D)xfRlX ]-l2 Ⓡug*ki`Ï_5y"U0bV0u^Ƭ&T 6Q `O$>|H%[ u!G^=Lvi=3iTӈ%w k.r5TY2UN v~ԍRׁV5Yz *J[6NwZpRhV@ lCH^FT"0>dVQСnHs$A%kqq5 䥂psdBl&pZK RX5;`.3-!.`DKΑϮY ExSSҠ{>>'Q`y ZVkur}Lm `JMuax`rT) "|ڃTa*x*x"0o G~:fF] Z~oXg4_O={o!q./BDDsP3n8U?E@!Tφk9GS8$ʸ x㔁Y`]ZugXaND %` A,a&.@+[4J7lu"F}9*W@h:A(m~<'^*ElSp]B$ KcLk9t yVڲ5IUWwMF`)ǢA $K6']ed.) CW?SV5ǴZAH:Vr!]:r'\/F`$0tŊ \Tj-+A= `4 lFxe"ֲ闟fM'L"IJ&Q+ő.Cjז1 E:7rـ^sP.BUkf'hq,EɋBJ^a]_< c4mJ᪤??,af9 8Iŀܓggs*~ /$.9IW 8fGC(H종Cri2D@dޝ::yׯ|Ci(*w5dvcd +(QբvF ] P @bI ǘeUype3KJ9؟ %n,!BU_3 i@/^/~lSt@ѐ?$oofmDo֘1`#-ӥ17#NgQe4'I[ʹiLIZCJ#4/h 5Mf!f* j 8rm+ImAapxW4*?1H=TKOs1O*cPGcᬄztFI=6eh^{$e^qăiP+.F5螶K4m|_u17;!Rg萌I7X-D4R) 8La A .Uj[1,PNsT64-ܻ6pInuNC{o;A:̔]'g0UPyl[O{ލIn$6iAX;cU EV9leo/a_;}ov&mu r;#\3fY[=5Ábd | L{rbCɒ.BWć]0aÝycBzbl1*IH|~1~s?VT7WaPh Ar߲az&j!y'lN;Ê "x>*{W}Ud"Oi0nhz=r [@" MO;rxsQ
j.`t{T,'c}{35{tyܥ)2Er+r'V(< ŋ<cODgߛRp,1,~UzG= 2rs'NN3e#CB +׾tsn'xw;|[JoToE~-vbP;b ZS~hmaA!R~;1˚lC#LKXCHt}vX3&݃@<t/G껗: dz}2sH<dNGϝxr6WAL[ 86n T"%DZfK-nvjՁތn ;WBD ZC5QNl5N6b1Jݐ9HW,ekA4AqZtyocD*#{wnA#wl.JJ9$;(%jڞD2PbR6_qY4+t?RZ'cشH/y~q^FWUv?=8xO7z->NM还TNa erN%(xZm۶_ڮیR;8t:D$_g EJ:ؙ|u$KN/!r_d(T^4):jQKӦz~1d:{&[IgKש{,{0JQRFD[Tp#fbgdj6Νn46]I;3M'8yV׌y{70I䗤MBO"M€,u3#z&έ^ )!pKzK]jeU5)6$v;Emŷ RzJZG#&-j!U8JZv8ov,E+ LXI/!lBXD:UcjS9%"<2s fMV7 гdD8N@RLUiO|4{u r&TV&C!]\:`&FhJg C#X [Qn얦ђ;{J€HϕMx^H|lBx΢.$~VĬJbWut)Di׌Z!R/ێi5j$TS$WVt]4gfXXS=9L U^TF'9ʤDV:R/K]F:&u]2ΥQQK xev * )ǫT[k]ꏭf]JTscq@ 0EA֏:0/-$TP~iiмyM2cĦfGrJBW pjPrA *ٓ$z!\t7X xCq_͍ +^*G0[!_>_ujk]Fsh/ lxtPȝ)[Y;]EYG t@Lm[7X#K* v]NҔP(MM&!H"2M)"TE+E⚺6LQóUu-J[^:$UQl$`fߧڳԨl;$䎲2WU*Tv/M֩q#MJ-!&R\* iA8iacMp vwzq$9|/̕OMSeRiO7ڎA'mN?]s'ΎsBp2 o6h!tY4p76G\^#;oHzk?'(BU+ MHC^ߪE7ucЉCmZIbviRB3( %SPciqwN1_,;H"* R2'*]Ϟ_VΓ$F'm C h %(`~PgI9kminЃ>TZUj݋~tP|jMJ,: .bGP DԁJp $)RD!~ < j)NfR L$5ij_"2@#(EHr'Z$f^\1]eo;R5uF-'5QuT^w;8.Zu;sx~'HG@ucҭ'{3lQ׻^ 9'sZak{>R:}j[^-t6L;=7È;رnҿEdz෡ӱ^l<+\(xiajqI5ю>#8W!}}aǹm?oRjRU~ePg0tb4)Nv%~ T5|SA3~!gpz}.z7!BXE!EݢKAlt}tLd^3F[-}GQ!׿-Ub{}/=:HP{ 2V>_zyеi:b%3icj냹W _PbqS7`כHS5wjPH"/h;hKepG8aQGeM]L DuXC>12oR"d TXHD"˫xĥz'L }ޘ,n̳ewn-6|0, H( ޽ Z,1y,XW,θoV3uXqN_d7<}?P+JA"b#Ku&WgnE/I/XQD35/]e& aF2ehMnvVsYd~3縛!ؾ ml`]9੨좶BsYjP4Y;ao4Dw/A1b5U#(,.9+R #6Da`bu3pDz*(֌┼nPjsz!6 < V_ʸBx? Fʈ]񐄅Tb- <4Z ٽ0) 2ȡ0 7H%J-r !!/8`$xҎ" EOW!|chy N>]!58V -?:j=l ie 7\ buC1ߒ!ALaM 2c73F?H 9!=$0a#w!"(VJ摇M%mrL)5K1,/ 8piq0ALLIGƔSd|59h{OCl@a>@fZ٘|"F!1)ݐcYNR%[X"u8d=iiVc@JVC+4Ԧ0jk}-ffav*AjY?OO(ͺ-4TǦ\]:V`w3#J0D\{ F crt럾H !^z[+Ai> c CEck?TEL|qЋۃ&rW(DLBS+{`yq) ~ųJ?+(q3 C*mf参lH2pq]OgoA້ FPw,aB`l-n񲿎tFogEk%=:Bn:ھq,.Đ&\%,eݱ]ʥ\_qkΠi(Ddm@;V uq+԰l$/I1GXwUg!{æ#lnrZKQEu mT"exK9\c 'bEpZNRf +wҪ 6TU),QebH_CBp9]q(x$f$ܻ,tGLӺ@ѼbˁY3q+w%+R*Gx\[i笭su\4}+<2/ZwG[ Н5ݍCx?:I? @fuMT apxj۞R"s{KX"K1mq/ Ȗ66x8-ZfBY%4C|6 (z̐-BiJ O})}qkK,`fc۩-î޵\G}Yr?Ps[)3݀yDwkGUu0'8] @-׻q n}/5YcB@G k qrpiI/p>^M2+GvM#mGcd}* vNGa $$W=3>&38e^"K mb1DObQWPqoY@m}n[ ۪z][ܖv2=q:CѶ/Ce<0&{w?QtGMd|Ƴ ܭվ_gˁu !xrfWպS.[N֥wgjč^t;(\pxޡz/T/pzn[w>K &Au"xY#\c*jM?-Ϋ;iqM!N#ď~f> y*&*] K2{i+bpU߲v75r1w%B3'E eJw49cBrVaģ 6`=4 6z!ewB|GqF"j7m/|kUcgA $/_ /ubxV]o4}ϯn#*!H#"Dڊ>flؑt:93t:^}?=8n/^?|+0MQ,$#As,пtc53}Н!1ey]6u%bOtbQYV ˲u{D9RB ՜M̈S8C>H6LRq)O{wQbW!ɶsh&Rw:OK0Ҏ+˂zavL~VY3gBb¯g=G>t6,2vP]附'cM- ?Ou8D6muPl: o؋=`K[ؠbɃy<,*&'P#˽,$jVzog 4J:tǝ g$8٤R5L<EI>c2Y;bx2Ѝ~7'&uk쳜feciGH: \ "}; |gۭ.ehbqGNk:J&O%IZG3VlhܔIpE=R HZP$lPTX[^bI[P]&L$J pxYaȄ,M*{W{q&%D藘y2v}(Cn:1Ц'uhæa;n ?v4'w iŸm-Kc ٷeϒd#h6Jao`Ѵxk嗇zzN8mH.X6-%D*(i;h.?˸(0 REUX =KʦbEK8C!az#Ecai|KtdD]Lԧ#4΍E*;1B_3/#>C{2S(׉4[6ъ$ҾCA|uO4>ncyZFԳאlE*!Y۱NNrh9;.'2."#a\ܯ8RuһaR(t(\h/4IaF㲣{ Mx =eσZp^&v8*t% 5ח)}m @ux?Ǫhm$ţDmG|݉6Ȼy(5-u_k~Lh5O Jő> Xv\sFm۔覚S{!1|{58m$f6FASˢA 17,Z,ELA%YlE]av\iK.1@:J%a!5[kYoD]*)SOΙGA;jAwckߟ7e?8x~IH?B6]2oT鷘8X7I_@E4w{(3)_G o_3^}B:^Rh2\2Wr_܁^ V/!^I&.8b5X⸙O5QA@[&ǺYy^ÞWЊ&.{Lr|63<7 ݄ba>xH[ksѲ@1QY /sT՜9$/$_W])t9ÙI:st;$J= s s$wL(xYYs6~@iV$=Y3sN'c{K5(E] \L1\@K\13xL[:Gc<%dL n/;`0im->ƥ)d*qs2`MS2S=Qӿ#tmx4?úa1|q< 'u||Ow6\3F4儼PVx&H7gCQ:MV0OAFT650ɸ5TeMR4)hs K|d7%MXEld E|0aç G)!ԂY<㖔kNDE_$,3hMMf{! /5/^b[r'_xH'y"woFnFB؊shM z˺Kظ̋z[AF+aD$jhza 4\(*2HT1}A^*)aFCRA5aʪVEӜpYNF)f Ap%j=:|!!oRpX[ɈP;ix ^`iU5(: *i:QH+$qT'tߞD I2:I%@ԀF=9Ŕѽ *  Hy*V) ~"~XPK Iʢd$~0Am bn Skmw+ьjk'?rxn jɡz3* tVh.Az9Q1JSYS McN8x*=Q֔9iN݄l}3ԐqT?-s^ݱwN d7a]i>|77۠7H&J/HZ$0|sfuldopυJo YBi$*Mr9D&H3:]Zh`=G{㺌5Ǝ0. [bd%>[¡mc@|Y%S/ Ǵ}.${H\u?$㺳*RCN;3>Hj#?}@v? }Q+M89/H*;`Tr6WIs% kNW:p |ġ*8<>$]Ls}iw OdmuK% $g?z 1Vׅjw^AZ,Y] fyU]5T-dq81.<"l&`_#SÞ 1Q !dmW ڂK]'O.w= , }lL}6 X3럣Wlg#  13\fl.8b\T#G0GS]҉! l&W]|@ayc\^*`W__*•o_5@3FVsq3$ﻟΨז' -9ҚΪgߘa].cYte<›j *"j '&mW)F(-7"tF9/H7n\Ix{*]qǢ顝EI9;0z`:7c/y1!Ux|l*l07Gz暦7  u;1JÅ>K{bfў8t$lÌ h i'\>@,bmQl~h 9ҹ%aZPLE:Qr~%*cvJOxYMs6W؞V3iFLmNIrɥ31!%;AѲLO2X,[hۿ|r{r[d3'@%$2wN'h:ɯXZpc^ѣ/%l2f-,Wu8\Mٟ3[L(,>Ґ4Š%V"(zͤ܋\Ҍ3SWҖ-)xQ4F *,r +̿5\["Y C>;K{gYFs^0 Ғv(i.yZf?3p-ršlqAR0J/6Pm0i>Z@FP)Yܟ7^Í}{8U_<<θXC]7WݜE^*y >oPJ m]p8xOtcDZ  hx]~օIoBs+_A$ ϮN[#K-L YBhz61Z+3|>+n2Mm[H:@*m8,%OfM>J>*3 PqǷ}8n_q8O݁cKj&2x7s}]](I vR%;,V{Ո]\⦋@˙Zwq,|9,z4u$huwa",\Z(CŽojTR{'4H]Xezg-uxtuYSNϓMmf(z܅=qi6=4ita!)B:j$9f ?{=m bOSq&P@o*l#q$AU,n#w ҹ*2? mh#☻M:ڪ6libLT׎U,h5;i??JPPŻE*HDX{}E vrsyxr6KL#qa1,lpO4agx|Br_-˔r؇}]#YEįgE͍%nܹϝpiiZյP kn8Av6&4A-ty/q'}!`;qxCۖWR|6?H(J7~ ɭ}7ڷٓ{6[{v˧9ˊZf:CqPN-*Uvgo=SZ)Z`n .JƙVVT8m^jEd~nB<( apZvJ3\INxWwe~jlr> s\|DN/С>~\p]=ܡf4=E|u4ÿFm*x*t١OgΐZ.k,JI,- iw?>(d (1f ('N:/pY帧$5>QO)D K#mtuA)7yG9p >0TA$u=ZA6Vdzkrf3ҠCmJKCT؞n/z6f*D,;{j9Onc! qC'˚pA7*iu* y}׊r8r@Ղ Ӧon!p$WBS*x l$IPGATm0aL'~ 1mC\&9t}LW־Zy4dP삕uFRT6P[-gdj+#_ҝwNX bG@CZV$R09sn^*4C%AOWg&v42v'2B3]pX.̞s36f-.j6w-'W^߅/0slKәїֲ|K9Tn7?wQ)^Z5/~AO1c'C PW@b9(Uʢ*-2 @z#xZms6_SuFl_Y3=d8wi?݀RD<.^&Q23q,Ń}Ǯow?|_@SU$ɩ`&Na fR0T`r̿'r=_hC ӆ%%ٿ 9%p5&;~0YgԲ؁C/4Lb$j}5dK2_Q@<ޡMo3,pW9pWaw+4s)gf%kF5[WS$,ت]ˉ_δ yG#E2ft#"*K YAb"Mr|C$&2CM>Y~ϠXRAGݚPF{JVK)49U1h65˰-?_iKʔЕ1p7G@xw[`P2m^ˣQUZQCgKw:jY;1% UnjH}@'k :Fo'~hFJ_X>M$dKMn*DPٍ'sW>z,zF(.$P T>4kE"enPP2ѯ3[x/2 Ǵ IiF jB(\U>6GBP`A4"_tJ2ʱ\:N$z3.˵~:{sۙ`RÊHcU1m7]Gy&p=pYMM2a9#61 ![F_)tJAdX Ut~l?78,Jr-iYglz E :3A7T-cOYX7iٲp%Cwpu&jhbLrjoAp"vys]M2jzy[R6~v#A cSs==6 ]}I96ӆ@Ux?J$k@1w;(wf Ad6a)Kbפx󵅂`A+(7?buɇk)PKx*ng:i9IJP#-XBNLv2,xE/.^P㷔xDZ Wk*5CcU']IRْ„> `Q- &`@٨G[>`<+T_fX:g WQݩ'ui~$v24v G5{W.콪{󥯻uc3Ox="۩i p{9m1-bxiN}qĤekf,[-"T|lkٶ0З㟣c~DލL/q`==O1l|3tk.n^܇gv\QR,L/|E)8]M*.j<@nYBNˠmJvO8^Af3Fu' f4Co#=v/`*p#"pq-nPu=(atފm*Da鈥Q &GXw2@ݪޭXlfi |0޵kE3=)Vook3 [35ۂ3i,56g m+B6$3uht"VG+şGމC3.6)Dc8en@ܾ]9B Wmإ U>׽ҏ#,yT0$-WnhE=q% "^|n :]7 f+/&RZ뵅\jejttA˨ih-&1du7ЎEVkC (ښsI 9TM#ɇ-Eߢ_ {&xYmo7 [vX&wE5b|*]J{IZG]$%g<372[h|8{3mzdq+m.J c*Dy~'F38ςJ$ja,Xlt9EsJ^Ȭb>$q3,'.Jbbq΍f20gz a傽p Okeڿ _e"eub{<ŸM3N`oV0μ:S ElYJe)n">̈V+(`f952auivl3mmX_[ ڦHdlX‘LI,kUh3*`*u1d9PӀT[rF*\-f|&+Bh3 kDZ[]&`ZmLC70aV m* |dzRc.z}<ʼyk,T4 7 ;`L‰6H Rp7{fGlFh +|B: .vk9cBk8i Sjr%r)[%)3 vF) wV7[HEjv& fqfZCaBW|x EN"8n1w|8 zCe*c b5#r#v̌0S7V~ \;Ўv(RZsi)G4I qFٲxH2QS`_mL_P/R3WH"Wi2!-ZYQT-Nn­r<^iYpE53/䛯2޵0g9~OY͗':vx=Xr';-,W+ <Ϲpo)@g2vHd `pƒbd8SJRSEDシiTD--Rh8ρr쁠7%FL#4Qd_҅, L„mi=^=OYcc¦\DG{m2(M-w©1 є& "Zm\6fxh>:6m}|F֭4O$ Zj>v`ofQ&"L&gaƗb"iJ(}FoE 0+UtMoLW#vr((Ts|oVɉE}FU4N56ƛLH5LWml.r&a,4x+H`Ebʌ 7Pr,DdPaJXܙNSwAg+67Awg.ME?ȼ&g1O*6];bVqCaAR4+f(Fх$2?p:] HŸp n)= ȶ)u6w V\clÍgv3]]x1x]R/&j{k6ƱZ]R:n!G,uIw8qMYA~J8ˌ 0Țci2R }|>;9/{+=z!HY.KE. ,#BI<׎BߕiE2i;̣/a9{B(,wRh=1#%2j[R5ݰx H` 嵧80JS9~y@QIXf]uR;v~WKFOԔ&}Nz ٣.;w]?{%OzMg9So4X^s3E<3aq+t;'.WM^@{D/U]{H7۩VȍoVPoQ =uq J1?_=|3K:M f|P7x[[۸~ׯ`iIŬ3@n IdIPh$zIjwIY=f}3yHy4w/n%V*YQOJZӳ/ϽV9MV5VoUY+r02]DsSlWK׹c(aJ6zzvDŽ'Ϯf= Z?bHA-,7~y){zpgwML[ڊ X/̿X@e+ުBXQH/bMK۴Rt>LU WgWo]JM[\H'f.*7¯MmhLX%dlȥ[]/Eq6ʲQNlF8ZOYi;v@ﱩx b_Fe2`p0#xB3A ,XhO;,58vMUGmWsRK-?΄zkYJK&~!)')]vkSXkOj"0,ܬT-ԭ΅31b#*%(h$rXfa7~JХ:$*>^\:o8縏l'}acxޟ0ӇdOpge!PapA Ir>ys:>}=r"X7-:4#&\ve i Q[,}e\E5îdwE?yEnijKĉf?wTy i;ց[F@xpd`Aް&TZȦ)y%FD ̽{(s1ni0coHDZ'x T+xENYU! Mxsޅ7wXt~Iʻ5@%VCZM 7gAz2۟KTSbXg1K?zkKp#` D#nj@7æZ)Cli˥K:?QM, -E:J[uYDgPϵttnXB$d0m&Yn,+Ӽ05@^*@;% 9JGޘ@ӳT):Ѫu$d}AQ݅S>Z?.99R"6CL(0װ+U/IZ7G[\XS2B }K-pxGUjvy@9K pKl7Ϟ&*(3p*[ `A(b Ik3! Wk6n. ZJS͋Vc t8fyB!`a ڶ6!]nV29wՔUMto"Gs1 &[ B LTqujno X%+Fn7$(j2,gsly0hTbZhOLdS^5'A흄$$e* K<[/,k2 )rXkdKǀHAF ˓L")?[mirv,7pYj9YyA~zq#t,Lo=u Qq#`m)܎^myp+cLU&NnߣKCJ3QǓ抗! | jIzjo)\|<>єަ-Ҷeږ zvU;PU1bX(R5ȟ*{1:4ǐŬ,yEZ{b,}\`,)sD8a찉GPA݅2vaGDȘ|5UZN{_OWmtgŅXհq];B”%7@ Tzq[(qKFvϜdYc~V:?;C-j\S9>PO)S"  ǥ;uPhV:;MXgÑ_d(UNa֤W/WTtGyoS?b8ݵ_SWg!$u:, Nok{wu8wPWjVH"i.h}_XH+ [4q4M A!i1#IZ7e\ׯ(<:Bi;yA>o]\YB( Sh[冊ؓ4958/e(&z =eW ,gJ0+ 0ϯ5׆]ػUlZud7b_RM}(=zt̙˫g妕'4L0ƖXjME5.y!H#II:%˅lm.j 2M]]FhݸpeԷ*ܢئʠk@=,>esҏ bQtNv/DƓ@k;iVT-AbT~[on ;unC `p*TsR -qLuH’?+ܨb.r?X  L ?~wxUMo0+K6"UAҨ~Em.=U 6Kنd7IW,x͛]]CƣTltrm.[#*=ܰ /R3Hg%ɳ.cS}G'!LrLϼG^ OIWh45 `ȊJs׆g2@JʞcugTeC't;8AB#s_>H81,ۏ33o'13 b=a;=~RT?l~Sb7XyG[f~d[|} n1}f&xZmo6_yX_'Z+ZX4 .m?DG\%#){ޯ% ЭZxoJ/N~8egATO!,gIƵxRIϭZ#Uy&*3aWJb/TjĂ(bQJcebj@>8+y!'҇'(hhݡ`΢DbeYsc's3REKg*3 (xI|J?($aWŸ{sx}w[.m;т[8#m <e*-,/a`Ѝ V-0'rY0'dfްT%U!J,>>WڽI"V3#\jSZ3Ts 0i1?B/S5s~.p&3%:eC`v4L -R7l7hחwNj<*p_K9YSf`LX. *f*O H$cZwOYe`l dk u/PϽQTƲx4 wk *[jk[ZK +ot@Xh'AiHl⅛/Y mTem;ܔIx_H^A Ax݄F}[KAI0XBMw_zWvQaH0N0iq ͮHw${onSڭ*>N)]W*Rr5QҎa+}ķ`TY ( ESA TSx]iqN1"RZ_È?] ˕1# b7igrx];* H!+Lb7ow ݺ2C '˾BݎVxa\huAӞB`rMnuR[_ sjFUǹ8E =0&*H]t*tsk 7Z\aY 2H^jѓ&&(Y[槣Zb -]SvUBW6m-ŦT6bΫNL9gL7dLs~38 A}mI`K\us\ش5+tU {k uw"{alΜUDfU{ z+ڦ*Myj!]'?eF.H.0.\00s0g9 ]6<8y:A4{-miJ[9&hٺS ^V6-B!F¶,RJVʨ}~W9^;kB 7+zd̫<%g )_QR5-;7.?VΣP5a5>n#Pf[nb/x#"u^GfyDY1Ԟ*G8^\=d4`/Inh} Pfn9$I7e݇bW4{kX(=|E;#_K{*|_3 8Ge=!gf3ʡ*ĩ7Ta`0>F%H4HDM[pզItkKFxWao6_qf+`hi`@6Xb4BI9#)Jx^-Sww_>zsAkTd_ ;AE-ew:\u,źNq/ [+u{# 6-+fWfiܚdi;)N8i,; 6I& vXx7˳Txow߅oP0. ]rT(a鬲]([ şY蔲.,]C0=#,zSRrusy@r-TMCW9m!vR8&AJ, 50VGYp"J٥="j0j&P$[qKV%іTI:,,Xp~>ɳ''`HW׃bQwn\upb͓7/#уiPHzCnH?-0p5:HEQq\RWj60)cFǫ'GK`A9|npRY=uRia@c{?uPWA諈+҆:Lչ_xMvcE~U_xПbuԜL 9YѬCg"@$PJۆ5jfѠe-Dq7Gq( h#b'k]>z\8}I>pz-MPh-Ig/Fˮ^X9пn,Mъc8"qɰ_1VPqC  #w #qܝL|Wo}ʾJt }@e4ʲ}Y0@Gu^7b)} \xYmo6_ʼnT504I.hDY\%R#)}&: Xg=x?7R'i`G %yEf3/ 75b ŴRτ3ȑ;*Cqhg9t- /;ՐVԿ $4!5K^P Kqv) $ԯ ʸ|2m\euy1Yl7gD(]ľʎp26zFui  ڨ.7K|oڈfBѴoܕJnd$V0MɖzӼ`I;+d&|螙J1_Z,9e w^'-5Q PY:FY?V yeD/5S%# {+$ǭgtm&t!S/b-8*IDvbTHҏv챘x͙s-,rt ~ kk`FeHlFdRvc9K&["z?_KE] @z @DBp&vf~jR\JHjܜd?2 M3|&Mm`ȦEWF^N _vDi M h$ cnb8E]R1@,K tw@w!Yxe5U\ 7Hnw3eoar)oj3j# 7 7FVxV]0|ϯX HE P 'Cˉ;x: ؕ3\k+x釳;-(XH|0,޺C`ݕ; [[ju' \K$DDi"-xҗ&]XJX;W6Jwdz;MQM3Uș>4MuR\TPjэazk9wd>jQnU)*I pA^Q,9#JU5!L\KT:ej;V4y&XyJ$*gڀJW>2!yA e>uuCH%:r<ވE3fx RrnqJqv/g0ZvH3\zO_gᖈl\ xWo68V (,9miŅ]r{+iyA/L=޽o~z%Ri_,r|Ue0٬qN:鯼6l0XKY0tͮԹM5JrwCjp:7tlDP-*>Xy7KXx߇0.M2s')a٬}o( ՟I茒>$^]k{0I( >@dssV&aܤta-s|3 j?,t~x}: mz3KdEAvTh[uNtx:>`2EV'y&=yr-ty= ~g, ,</>s)R7fN wZ FPV-RD_.f '-@nG׷ЄQ]}ϟ΂#zӣe8n}npR'eFJioa2Cۯ7Xǭ7q 1 pϾb5!o@p$(!/=;+bB/hQ[@$@>ZسNk`op55KIU'\n;kWگ/L  5.3\`|y$ |tw=ZC#_G㷎G=#aLw\5x`Εuey VF #)N-bGXAAyD\pW1brϧxuF\FaF7c>#^1}i߮pt\ \xYmo6_ʼnV50,I.HDY\%R#){&: Xg=x?|t LSϓ4~0ZaJ3aҙ 놛?bZs) FilNg~wj&=\j6ؽDB9)r80\,+9םZb{Y#A}bi8v;Jbu GzXzcr(nmZN3 Hʚjtƚ9]C]ۧ/}2O_t+ȊEeF i\LK{ ^W!-CʮШއ>_u6+ 9kk6D{ -n飲ޭO86nΈ@Q C;!|{nHyٙ圌g ]2⿆Y 3KY'~eWƽ7i2/3ye9'B"Vvt'sE'5OSx%WgFu6_{F4~S7ஜUJv 7%'R1R/c, pgvbx^mgdʊu⪡ zSΠqBRCYNAͿໟ%ﵝSlP 0gQ[FOZ3%Zz0 <[!>nU?8m˅4Ѥ ` ~)l1gW,L4$j#XͥB~ch΄kgN#؄g8^v^3*wD~f+5"xhX(E qo7n%󅩻tZmܵ` 'a D$q gRjjif짆!F<ͩHS+ 4'PolZtua8e7@$@aZF >&S-ʲ^@ר{_tŁ]VsmmWpk{@\s?_6&ёb9Vnfa?i3 II3j$ExZ[o~ׯ`ig.0@^;&䑖h$$$+X%Nd\*˿|w߳*[ |r\aߟuvsϳ Ս;  7>XJFY23jyUЮ)Er#sFuFo7%&$^6WQf%9m,tmjN%*{:N73we#J#U2nn?4Xػ=eLA[bST ^ QsWb䏽F%;aciթ7N^MѦ9B vZ|T]'\* J jهi|{PEom}"e5 fW;8۞vv(\kйc,- ӕ'b}"6OIZj|l0Ư@<cu{N@m|DpmH#hR[qq-l8inߊm`bHXP&Wm۩h\kh+̡aO f-cntT8- @Bd%;,} @"|A4Wh %KQ aZ^VRF q8^"(pn+)f,nw<.I0&tv#-RO{]`%Y8bB<2JӺ2T4ҧ!̤h!77ٸB$%]:H&-(/T݂=py͏18yIDTZAw^@0< ߦ.[WҐ-. J?؟2wa\/k轉_ ~NxӴ IkHLDb%K3|곇I-Lj:N8W+]*P %47t7-@!S O8ur҃T}9x? = }!ٝZ z-vJ_od}zW^~:&h\Ibl}ӫseH ir-n-6*HL‰l~ppuD> { ITl/H-ߢfA*D O,ݍ*EP`-NPEKOƵyyT:cם7ұz-Tq̴ȗj̯HiesNx*Kkę8ȕNJĨ]z$w)_IF6IxҞMNjtS "iӗiLl9樇Nd Ψc9l𾈲cvQ eқI zOqa"-`g$) J"x! W/,2M[}0A{0l6vj <:h]ҕT.{^Ao3Yŋwa^/m4ndt{W!0n&AB@F~~YCb:Oymq}+='ؤƲT2_`z:{m{_fk /r*ƣF#:nzô0b}W0i$yn(4r]d4HTQcw;`FH9@hM:;1osKբZ/;F2sC ][T_xop-NS NcFTc{x\:XSIT/n0AX}B)\-@H n~xYm۸_mx׻zMr{)PY.CbC:wdI]QC"gyJ?<|{JWy gYɍw=i\qdN8 aiZ,,&,^|Mq'}Fmrxד&,+sz8ZyZpbH{m,Px="7]i鳜2nQr;qVdw(Ai4t٥'7ґwCbbrfşM1^婢J|r@ao=4-3sҕP$jO~` !VR~CkC؃}<K!oQ 4H@rVHM'@HF ?66\kM?Kr(6 )ѾܕדWihž[|}ӆiSh^t> 9Ym\cR/E|[H2 *2ߖD0ːpK39a<"ТQHZUEt:v\6"9RZz{ c#5 O¶$\$<|v-֟V '% JU +;`y)-t$oIBdj4Nx{x'WGbo1~G}IQıns1b!-y-w]PZg /XVZ']'#c]ކ% % H̶^X*mBO,ŃZ҉ȷk{5ι{cQ*A3ӦX #6#!X ǂć9ߍ f@xF8}Y^AqNM6wnށ}UQaX$Nql>`!O |4pOHe^p8o!%zX怣b7Sv'KV4> J?<2qOdxT#./ACvf j\pjl3#'>c'\wǦgiy,!.)q^ýxc3_ĪwS oX:vhX.޽6m,:_U]n'> JơRң!3ӈ3r­Z>M!`/^zǩ{i*N< j9`xoX:K c)gzr`V藟p_GƮ0}&pb 'ma3\ނ{kdE|S40BqON6~o@F#1N`&]@WXt~No,O,C\eH }xYko6_yX~tŲG(5]ahHJRva}璔%َmTs=T/',E>)~rd\a~LnWK^TBL&Q< g*]or+y#QY q>Wi< q<v'X)Krn`n?`멚O聝xv`@1DT\6ft:B_ l&.DIT%+VLZ<5)V =I&FjU.$F!R1gZ?,y^;McꗉV`m0m(C=y N! 91AC!"=v!Wuoi`^|2l?N&\Vgm6 C1<̫~NkGf6U49KרB#2TVFCRSTR aI|׻" lKT0KT^%[5(CVBw-2qVy"e;ڼՙakMRRYf%ZrØe/" ƚ!ڍ:~I._ Ca22%&;Sxr7A)[D#E )4V, wt9&묛R60˔딥b)}\{ZRS')vFZ߶BҘaҬ޻JM?vBelJ*aHz_U^й2ev9lpC_3 2xuIpt m@q?nM'"LE)2pD!)fobB=bixrg /wԒ{{]hJ"GUrЁ&yP4MfU(dHz0msA\URkMg}-$o5ߥMs3H:?op>Orzt;B'o@XhGa+wxv*Pzݙ8pL'e=0Y7GOFphjN=YhM]Yؕ[Eڎ|Zv}yӟD*-lEOQY5 ͏FS3! 1zOz_-b/z߆"+$otflxYt% ,^έ f6~' Nd?˓Mشk[ #Hq j؀DTF "``9\(S1"n. 3`>fko8b`s,M5s x"c9- cJig~$;6RmȋL)жF@Jx`BƎ})iEEuL5_\@5B'E+bF j[ϴ$#^RJ7%F5[==]5DkH֑^?/~ؤ:#ɜDxQZ"6qZ{VM\nF,B@݀[rԂΘwwE(M'88krJw{WGOǚzHɦ\#io;;V|vZՒ*S 8+@G-+й!Hؾf"-wA"n5G/\2rQ.0|ac1>C)g:kz49j<yQx:}0] _{i}-]z[j :CO3niWӺiaզM Ų(ފҐ$ @zW0 uw\Gp=-6 Qo #֛pL{VUO@_O C:>p]s~3e dL*xZ[o~ׯ`Z'.0@11̤A7`( Z:H츿c[(hyxHO+۲%<Ï,giεᤶӿs+l`PkViUܿOA\eXn"580r&y Nz~;M9 q/2@fgTe0oYZpc>,IʻM3?G~y܄XحLViqEE U_ӷ'~dS('/Os(gL%h2|ӄqÄek0uU2f9TIcuMk%>׈,(sE^侍Dr_T^~Bܗ+. ָ>vb>Ԃ,VB:hJQV iabM V%#8mW8' B\/HRq~Kk8`Z1& a  )Zԥ fkasmsm0RHSbF4~ N7iN#ljj'QoV?2;Ơ>[LLA2XtumV0TS'Ӂe Q)a*2ңSA6B-M4?ܕu37v5j/c6mah {4`)Z M"qa*$=Ro z!阜1X>/`-#uUQ(~$>ċZdy@l=‡"%w/lJ^<h9X R.28I)6TQR64]܉eƽi Y,`%|fyS&Z%PV̤&,*VD0\rO.I:wf6> 8 5'ܹ/ƍ5ö^Ԣ2%,A%IQօ=|-Q>Œ ,eraa'v*VT7}%n`!-ϗE17/zo\`+S $]A (v #0)tu r-#*ּBhj-ڤժ0=![#\c#EWQ]8>]^$c<',YZJWoHU ^ ttjF53c(Y-QB,iV e\_|q6bޯhȶ\q ػAX~IػZ,fmUw[!@6+RkH'!?cǯSltP,jƏ4aӪ嶳P6P y;US{:5p=p;Y *##T۩8[aPd&` bG%Ox|{>QBRZa/#|{-"ҮJpBw!W`1uI8sZJk_ʖYhm}2\$kBҢJLTu41,G ^!?BsQނN*vPy7FpVr[4{_;'# NlNNo*+pL]#gD"<:roY]X7v>LN+P\~A+GP2z~ ѭ܅t]ˬIpX;Ο"1T4F~Wؠ<^^siֽJWykN^ ΋@} r p bKm-UMn„mU[2XpG04"C肇#i fASyP)%pe{pm߷f0QcP V : F4Rɦ*>4bstLgYi,i!WJW&acUgG5%- X% s7YIdʹruc@4#M7жH.0[)Ms ֮rnn&u-%9 [?˦ () Ai(`E4Z:5SG 1J(/xbujW,J 9Z-؏Tsxe6f>`8s?e/W1[h8i'jQ[dS' x*[HdstJZ%g,hsd8=fd4jїaTvg ltčW;zg%zMCfz|PMذJĘ9{LVS="Cz QoPߍ20I#y%xbh[t~DkiEVL*yuoݗ`x*1ˍ);ĝt:'{z=gPl M}}n>Wi,L F< OY_#cu{wݹt7~  { zxUQo0 ~0aLڭiB]$v1q'䶾5,MJiۭwV?$NT2aɿGX䮮, uMׅr.<| ݩh9#EWo~ TD]{=kG|36;c[w/VQ# ,[uS@Q96 ^U&wllhx)rK  &F7J63"D ` f-*J(xL (RT.E p)3j9V2r@-b@Q9K&iɾU.=(d@.)jPVOACuWOP6 }2Xԥ;9%ZIS Rtu+e8A1L`'QpJ&nmo&mO%! FGxjXR0ZqSD(H?1޲ҥ_JXT"Y)=*?Iz+ Kc^vP'92"o`F(=5$Wsgʅ$f+Q &gofp-z#Q`:*qi%pyZi.=%sK^(S"^50!Ȫv>-_k5[vMĴ3y|x?*jXo ;\<lu>R*c0Jnu8Ҿs`P `q@ڟ6smY/<5_wC֍(p^B -|{P58PmOp_2W2SH0#L~Ћi_o@ײdBgWORc?u,~tggDBhGd8!|CS"`\6՞N9\WKhz*[ 3W[t\^UHƄ yn&ϿۄCklv`,6Zv @M7  @}0fE3Q? ;t(BMwAx~4Y|?R=Y;s}7ѣ}+ & RҴ_;Qmb_NW[ćޕG <#u$,q(w$hB%j :},]kg> cf7+:xyW!1uoAC9?qɄ` ߽-6|x . JVĈ_Bv~Azx]eP=`Ȏq \ygnMvb|AbLQ f_wh>2D E$bU"|o~¿H8D2?M(ӢCGȁ׊t=Gc8 3O J J8txM0\w7B%Xi MƐQ6=w*OLOӁ8$-Gq`k%!2/`_LlǐV7Ue !639X&\-ՊFy$A^`B ak݋"40~T +Dһl0$lRMkc)2CUiTᬷL#nb/aQ[(66 b>p%l2-gReN)ޚ>A(+aQYv@@YyfIXzq7ȝeǽǓ*"eS&?Znm]ŷq#N"Vy\._>ٿ[90L_]}fly xu;$9Gk4dsatq VOO ľ^ԏ!Q9Csf|KFkʇm;|| dxVێ0}W H+!FB HhE"xticֱ#e i˖"xe<>sfxO>6t. c8[ a${PAc}Н;ExWeZ/2㬬eǵ"(TɱbdװR0=J{\%')C]5Vb]hb{ ϠU? ՞WUjx=$eװ-2Q(S JTȮܡ D pB{J]t"AFPGw0mP P3A+ R9 oO>{Z&E:> 25#Rv[pM):\O+9ᾼ?_%P)/u .IhSgLR:o@Sۀ]CЭ(yV1,tA┴06% -4 4Ф+9išK΃?)z :Ҍ`;?Ƙb|bR gy>N9w3 wEF9 50 .YnUsƉq- =b>Ǫخ.\NHw4#y|elO-RhLf,WCop\{F"xίpC",q=hfV3(dmp~pN_f;=q΅`hLm; ّ ʀ<"\S|h+V2˕Gww2ruQLrI莌* VW-=䋙g!5]t^[Ze*|sDfIgH1VKeJpBy)H9c cVиo;Ͻ2=&)xtxO0|)RbVZ!Xa6߾6 N"${f_6A,p|,5(eBV:Y|q=:0x*|*9s)(e|8Y2ƟJH=Ղ@c;{%"MX]dhgG⸾X~$c )&=y0ۃ/iQ2o~~>C[o s\kYU)T˛JK{kfNN.xa/c}hlfdU#\ JM Sw%lMkYB1dulD)A<$̝O.ѷb*^ȗ폞|T!jt2 vGW&yb47qUDTCy>Yu66Te;-a`% W$VpNZ1S /(R@I9Ծ&bԤ47pE/Uv_1au=ܥϱ S+Ie$._p7={<:Ėiʙ(;NLyZfs<=10i N74ϻe2Y}8:E M+EҗΈ ( \xWo6 ~_e;s taȖ!Is(r_t{C@ȏ?J/_zd%0珊,BV6dg-nżn>YPOk-ZjLTIbo%qqcj,+23\≏D Bxw~k&1'Og xmd*4) h05T-Nb!YLNE ݙWb=d5vowVnÈ;ǰaƒmf %>`ߣ:Ed l-468L gd2Ʊ%ց DRaAWy7v)q&lF._lijUw;ˬ.jDtZV9@EW`X;A]m#cy ?b=rLUMk) mJCE^sM)X *|ڧqX="NN&WQFXV4dzdx0&r w|ǂةutIf *+z @u" ߚ*Y.1& +-Υ~Һq)&*4zu$(-^/Xpbp@> *~͒=#S,1;ifbǻ28·gT% 3LTF06vU9, /h_Nŷcy-nz5VEm%&+eo^s͠u^r"ߥ!Co(8Q[2M{kPqD͓x^69AM0*|;FM4QR&Bƽ? IJez0[i*Cp G[KL.Ht*]26m.&KSR`J:cÇ(zlQߌ8Z)ڻ. ~Gڌ׮Eztݖ:c45ݦ8|#h'zН H7Ne ZuUi7*`(-5 קFw(G=[n@&_j[g3bVM=WtuIݏ> ݤ/A;xH9D韏n>/n8jPZkh\Tgv\6,H_2Tռ)Em W@17󻥷Ho:Aqxk_qbm0vSr o'}+37t.SNAme!n^d|6It6x6tZ6VTL3av#>X*JƂcPx^Y_vǩ;̺5XdLkJ>^eNxTn0)smT %H+TT*Nq&#S>=8neTo^C+jAg@*ƝbwA&Z_x줢*s1QE#l@-*N /rBVJ[!UlQVڷ(UD;ёx!X5[|U,z&e2f(b0)ʼncr`_G6s, ]$|uWߠI@b\|zM7PG0QeSd\ ڻ# ږG[* z"g`~7 Z!`F?$.,QT6yML~fP44l@kT*X],=SQ&&tӹ!avQ |53K'wȾ,yr &zNk/|܋8S `׎fmTϋ Qz <\O{9DO#ne<[ibɯ?o: 8e9MiX#Ws ~,.HU[73d9.N u}4 NxVN@}WLP@ XEBDJ U[^w/ zI0$ڝ˙3ggxyD%YNYɔFs688p#0d "Ղ 5ܠ)e؛EI܅|H!Smx?a>JԬ³Ɉ$Mbr;:r&1MlRɳ2SJb LY Ó7zLXm\ =<m5)\Ze3 <-R$k#Bh)3װSbק."d;c"`4$Tdum3XFi u 6g\B9 TT-ULڐvX`uSYVObRmE3Kş@$j\ꃨ%QHbz?#FZ#@6PJy[viun3v%Q%jhZPb A Yp,x7 :\ͫDmu8u[(E `f[yo^b=rJZLV5H%*zqJ~iYrm4telƬ> ''X뿃H )ac<1kݐ[;&{L*j,ʅH4|5Ug I`y~zpfo6L&yC˞jҬ`OzpoZO+{G"!D:#rۼԿ5o'e9\XpX 挺ko1P(Y9hݻc9*\G|GcUݮ D S'wb]xQo07RH[Iնt<8%6wL42w3a:Ƿd1r [d:m9/b.,R@QKM|t|U e3)ZiD)@oݞ]V)%%7=f*^DUXd^C1nDAuWr+ō08P%/?R&c"E `Fl om\-&sME[,~ L*0IqRA%x JfUKG "`^ V"4tgt&Ґ3]?$rw+Jꔅn>G?%1g ZnԶ#q)#@ .ӱK.d̈ y;Ƕu):(")4¢h$*pwD9@(=EeN(t ()|0YRY4&٠t~Kg>p^wBFa=ELڤN9 [\njv̈́oI8 [U;sʟ6Qto,][:: Ǭ7v9Ą |*qgO966t{a۸g^e ,²v9m_fz'f v)uFVYRd!Eh#䜁5'^{r{3A8Y 9赟^ `a d 0 LddLmxXn8}W/v(IbGZ%ERKRv_Ënndyqx(Ù9췏><dhV](I©!gDijnGYN~Ma&'LaR@dAaTϦ> MB&[ {=׆bz02Q7lJ,}CFbX&t>n ΉַLPbnMA܋, y!8%BHabj '7#{b;I\2'L`2f\%qf ]1l2($ %_N\" 9z?LGP*<8XqPȹ4PĴgd[qf wk)lac9Mٚ Pe- aOg5 #$j} ݅֘Z4蚪m31 $Yc_B"wzB?؋Mp* [+H NaZjI r1:KT Vzm*N~V0^CWTF=xIl0`Qߥ8{1ꢾ (pTdw†vںTT4D:R3g)U㝖y߀mZG^GaZG]ֹ]UT&S}p)+/"/ME\A i gya t]YpU}t;Q"Z,eߘ_+[S ɾQ,\Wʢ `I`j$&O,YtɁk;'N@g-b$5Q cDюtlr;TKU]O0HO0_`:,ezOPpya҃bт"$>^z2w3,J[*v+]1AP׮'+o ke'L{FE%?Y؛jcUY)S%2н/fxǸE{W_dz*^cݺenQo昶l5ay~ߪ#xf$ UGّo=7 }pTx1zxJUYAh_\(7zS!23X#2ƅG_, q.&i# 틁\ t'-nGk&'ޘj_/܎7/ǎ͇L 2xVKo8W̺6-`nC6ۢGJ,4&3H=^,k8oF>?x 2+҃[3»Er'{_ N7aV䃓J7wUiZ'jVV0PlWr(#Z5*5Rʢ /jɬ]-Z*v̔ZVPTêv^DZzfDywHW8ώK)!(5vq$y]_Z+댯A NB!}nڀғo=qذ\.庵gw~s=AtlW K)Zb ofbF4֫:=E+P{ Pj0[挸` A)YAZX=A`zS;Hu}2<B RtO2/z"CЌf7Vc̀5PoB}fq_KpRC>U O9O(ooQ%$J`ǶSa93Swy'H"[.-6_,yH]1yno.G'XƺaJ1#ܜ%CVcH@ pet86zKZe s5}خ K}ܗM;G9X\[tu5:ոDCO`ۿW9\@OܣlIv,X&`ư9MOngX @OgkfFڰ8VȤFrD@a*tL|Jz4*^V>4t ~%8ш\%PGG =XpR2)С4‰-E^%7_7)WCFU Zƽ!^d8+q C>|  5nxXQo:~WxyY+5ETihnWvo;i>:,&]&@^?9džͧO>b%4KSm.*Y~Xxr+`\U`-|RQQ˕$V%h$qӭUsV"XG2J(DnןJUS78|-5VI~C.6fqckGc[ GrkrE@Mnb fyhc5[Pv [JZT9,aU8lYwøeq%uWqMȏ#QZnFJݵ?s'*cA\i3x(}`$@t#-:0`\=`)` 6:BLCd6JJQT4".|$o(9zF[2HaC+a/ F~UdvߺgS던=a;uk#9@YN@@Ң"5X`uʾ59QXq߂ \_05R M$\L*"F&[W<řHC"{#0E|YuTgl {Ą荘~ԝndTu'Bڼ(sVkfbrtVGD2-zM-@qQ9R;8(SXu(1tn^vRP!~67=K׃ 5}aՆ S ]٣o9[wpگ#iϺ'[g4%T_դN|pNaJZe0˾QϜ rߏ|\g(ٞ"!ۚHgہcB{*D{ش*sG )ðqs1WڀҦsYR|k{ "ެY=^ΖUЋjL6 Tk9l=-ddReI(-/-wӰrRiufUstts[𧉂?&TlfJ| Wז1|N/,fqLUbzn}sLj~:( =..C=9 bxVMo6WLcCKNEZl)Q!53ڗՊ7*kК*+ E1C~t0X}צw;a"ee1bVcd:MfWe! vJ!P* [yQ'`jI~"}pp>[gɼg/n"o<<4ɹu΁n{-v Čق5Cx<"olhK/\4q֛D-3bm!_r36pXt <v5nCdvO:4я :9Kėc>HCm j,d:KbdR%E u /R rXP-wG4k֣[Z$Tg&.$'OM0b[bĹGo*rij4|ᖄp~Aͱ68tQ^%/a\ULN >RS!>4zoB'IJé%yV sZ*T|3D%K|&qŅE Xas&}$]tl85C9U*q?\eϝQD?#6d׳Hr=/O48{v;Kh |wW4,͗JSAPg+n}L68- n7WTJi{=C+ #W޴ $gm|8r=Ҝ@a>ІJ>hOF{rߢι~MW$yXd-#Uʡi֏y4/uļD1ЫښElB p0;&ZTRehHV6 nfj]uD~hhN` N9dך.JMq-%ͩJCu;+3n>vHTTq>StO & O{ꨭڜ}>raP:<Pƪ%G'.8 džλxλ8jdﺃ! ,JZ؃z!wt릖mWsh~7 L*Tu.q(%]j,u3&FW# ȔTZƋԲ6rzD~tBBCCr<+}hu d e݃hʺjGIy< v}یݠɇz_D5@܃ۼ8@e"7wWNfBj7ZwC+b6,mZp2T$~uδD#xHv$/r_ ZhEVѥIm)rq+IQuŢ&g޼y3r맻7ԅ*?X(<d'pByСzX8ٱّ]2U5V=E"h8JIuQ 2bU]"ž೾9nULJVq]u${ժ~UM Qk]PHlFkR"17;#?ԭfE“0T[_=*yL;- N?$XȎgxvT^~lA-09^]߻\~Rc5LU 63e x^EnȊlrTfCKbXSiCɮ &OdCCN<DZkRvjv2M8khP% *U՞-Mi%bZ ^?ϊxV,̻Ya7wj2rV];# (HejM܊\}NKn C[: i@vo(1{U].<<FD=cDG6-C*O:pď5yk,n1c.JɈzԊx⋸ReO"}g7s;>JCCotZp~AtF@mt`pӔ/= 7= ptR2!7M6qA,g26{7v/ӟf{z"xUn0 )8؀0 E#mIt(Im$E{`II>\C]+갠yhBDڈ8YjQ^3jdx*h*NQωﳌrSJSniUӗ.:YIV(+ݪ7b_mϒlY#CKJF^LcFɃx dkǍ-v(r/Y|d:hXŸN.PPYZP C"{" 4>pX3≕ < v`Z/ke ~ Ey*NM%=C# v07~-j,R(m[˳g*s9z 1YPvsC.6Dh"A R\<7dK{ioo4s[O\!Si6qx31һ`J) WF2nr}a\|Z2#BοNѯ?]O;# !YcwK~u=ZCz_Z+z;FN'=!o$QgI9=e+q$&  xVoF ~_iXkZ!Ð С:ںw''GYnaGG} +u I%:y!E[]:wi>)]-:{[T]Z4 (M6|bϿe:+8Q*%)j0DpI6L$'/],-.!uw&7 5`i`яnܽWrD>M8Q́LE!ϙR&v(+;gZz~&StIhz(M:RCmݱV9 u ۶- ~M=rNyS[AUw\`O'ģJ(b%\ v&"Kn'F(c҉ƥAېĀF0AyD6vU D+AIW_X 1ov ąoazr:TTUޗlCt n nJVnqZE|B W7vW]دZI.*f_ZE MF1;f23=BF_/?K)ռv'9mM%VPapu5϶t4{0cwt_ߴ6=NO$l@ Bר{k1/YSӫ)xd[/=KI#‡?<\8Z6놜' N(Ořuj˘& #gJW #RS~?_h8>'G50im Z ju± 3 B!)}OpivzƠkMEmpH[/L_>7+o )M) D TxWn6}WLM E*f}h &GH^f}cqxrwr'BSY>~Ph8pŬ g <:\TonX8Yٖ]Efi6~,|A?a傌hbj~HBDb90Nqb**:#Y /f=x>R,P]Tg,d`ۂF;B8i?4l-u֑C'[x.x 5 Rdv5"*>ᛮYrݘ+CuZƾQ!Sc,h!ui~˳nx/? ?倌FxW:cƆ8eMAk4UkvWx_|[;ܯWʐIÛQu5iX%K/DF|VN՜~.K8l b!xW );l-0ˇi?cfNhsFc1[lmDҢ ±ld1zc~vJXTb;'"]>.$|5I 2|#7ݦݴO?|xzwH{wFߵ"8у)ln"TXWuঈq'ә6wv v٭5 /D1;X^ui%i$5ࡷziY/-]l|%n~᧭0f[sz vpl=bx_?/05xTk0_q^6Xk6(-teJY>GdG;YNHjAIө~sdtSe $:OaUП}.+45?B'$ʱflkA N1YZ[eSWП^pd M-lGM-/@h~UT:=+R+Dt;S.(h:qc:e/¡xFr :gڨt J3cpE#Iezd8>-2)>=I- Iƭ3I\{J+ Xre9!Ĕ;˘۰ Fh8IN(zTAaMΚK?3F8bш`!q{^OaBl 1R{Ctԯu.z.?Ovo<28^?IOfBzDjӅ /3ub6XuR%ȕexuBߌ3"R r e+3'FKBwNG{wwWAlNmUg! 5\1cOĄ,CTy^yp2^0gjQ/Hݛ:;vW?RCQd @X+2`}j9kگΆ^e]BI͙"׌=V1V$&ޟ=q|6^~5QHR#RC ˮ '$r6 ^,i>gΗ 85|n.q|0u7=mIəɝMyjŧ}ii]mTR,gs qoEa@B1*!`UoGC&ak.W/̆F[u5ݖH _i`r-c &Z3F~a@湺u \VBcM3nowO<]ҕ̮(qt^/^&K|w2&xp!71Y]&T^ kiT@VFb[5 Ƕ>Emߒ+q]{k"nsᮛ NqF)8O[bsqGic0Tj4jC3wc)_׭n9B--FaWlZ66G]ɄzhyOu62b v^9 S:w\05z-s#^DzSa^1`7ojX˒x%۵x^(r\}m9˶mߟPHי~X9*޻4mϯoj{)$SpATo #9h/պyiۉ.iZnRD]'K}]>j[ox{7:)G!t}$8yq5Q5߮o[ *qlR路cɻt@pWn-m~ugwXEv1e O  4#lxVo0~_q1im`R%4H@TZѱ/Ա#iWzqҎN&}wIj*_<Ɇ2A=^1_OZ_NORzW.VZhiz.KA=Pa.˝g^:/L2x=ncg) py^҉Eg̳j\1'\ qw9jNB:&ڃ`&Dv\\0LCރj{e2KA5ZɁ-%W`K) h+975o-V=IșCPѧYz{(^*nAe)c{\⼱BjRCK(j# 2فAC@ʚ\ @CR)pZ0ZF'il^*HZe֡"4MЎ HUJ2*UE>GM>v1T,U6L?;"I3ԂV|,p9v!Is#[h 4vw xh׆e,p6$QȌ?w-WR g} ~W(d8^[˃Snpe3BHN>K*B;,7ǓwMؤ~# ؠL?sK:1^;pc/RfK+ϯ (ߴrC9S7F#ވ@&aux7uC} ⍫y㛗(g{XTSAj%Ҡ,7m(TTM^I Nxcg+ͪĝa/!#Y`<@ޫ:>̚?`}S1M DNCk];.'f Ӏ`60:4$d DRjpԉ@]$Z2:ZD~ZL^CwN91эD}bO`A4Ģ W (Ct80)0"ωk1i;l"%B&5%cۗܥ2K`+4п^lIhj/cɬ~}N=  `#Hapd6wHR c&}4I62N{dM[D.+6{K煮Baݱ\Ǜ~< !&XB(f2vg|)L_[+[ϖ?kw" % <. i>L.i~Xqpu4@J¾I@^fFϿrtPNs/D*@!xXmoD_1A{RjAHP'Bhco⥶쮓~=ڎI;/̳|v͛wRj, &g ~rn)SQeF!a2^=p+&]0 k3Hf6"֯iZF+2ތIȬʫY躂HbIL4e£Ii}=Qok,Ih/&qѩze^LB6L_:kldz,.eD73É ⱡTR'5ĸ6t&U@/clPZŲ5/G~:ۮ23BUF+aR2B J@KMJUu*DYqҨLPVĜ}%q_S^!3y53(~iDUΡR3ZpNJ4)^"0@sD]ehM{V:2sRp~~bYf{g§dH]qeT2! eJ,{ /L' LͭZJKp5ZQKĤףW_|\IiWE#{/[SU{(9ik$]YmJgKw \geA1gپE =MS%'0Y&Ջ1֠Ycz~[j`R*˽/z=kTf3bTJ- 1!_"!Ydk[MM/d R#x=EdSΝ̙}F3Ӻ1NzE <pYH柾mJ( ]#pC"pm_@Rॐ3}xӴ=WS<ۏp!Z?) ,xR`/PL[7].:7v*>p X!M+@ ѦR6q͐p8hEuqGP2 L:Y,"ۈX%hRX? @ S۴t>7]=di(<Eыcǭ 襾Վږki>.pD‡_]novԷ4e!wK?!˟fcmaToIV\QA[w9$h#Z#_\SG9{;6`ah ̸!3Xmxo ' UVo)PD}Ղ˷>>opw ߁ M~ &9(ݥ:aJ8) gcQ'Z;[x3-rZyO8{R CǙC*D4V+t^VBЯ%bCyW$}+|ς:$Žk,s)':K~ˎЛqL6Q~ Iߐ;7VIY;>5{镋l;; Yv!)k>!Æ]w|MǖCN5EiANJ%Q=E]F7KM/XC3\SBͫkqba8_Oi   o(byxTMo0 Wpޥ P`EZzNlѱY $:A(I5N&{%?><|JmDR *DE=Q'C{)85TG>HQTRۄwDRd".~cRIN'ԍ*!}:ڗÕԉFv^c#+謊qQ>/*4J`=6psS) ­oR<9D6ban n'4vdUd;)c< 5ǘ_ CArxwZXT"@y 1UϘřIУwKGtSr;άMr^_TL81몹~WO.AqB,&2:OaZ nwƲ̈HJ i.W*gXitv0\%,ևU p iӱd9 Zj,X)A޸4U:\&&R2yX<;6zv0e׿ڏqXQ)4{k ~XVU7O@j'᡼)OBQNݫN' xUM0W 9 XZ\XVP=,BxXrʞdvfقpib{o;Y?$E-CZ)Hc-HXtHp6F̑p wO{ hEtⵘӌ3/r;ʐFb%Z8JKN!fRs^`<:qg":n/9Y?_3ΆP^b1(т@9 Pkni3ly?2 op,rbvjT9fOPvm#rP֑ 2vnC%U`tePQNzwfX>>&TЭC[ܷF `IkZmzocpqnj\-/FQ_#a8(t8f>0CG}n%tNUt-O'{Y\T!4&sR 11pEQ{$THkۀ3x$k?v,B74y&=IU h[xPi󍋋8> l5ү 6|;&1&f^xMk@@S JX%mP@})4.Y9wd٭#&hhi1zn^c l$xĔWEE)l#lcq.ŎRĪ+<,=mͷ0G҅BiUL +a Ъ}3Jx7R kVulH+!h3i=U'J9J{xamB+J[G*D`E (0ѕ4vPv{v}Ŕ$?P: 8o%؊' brM@X!a%8c7{zޚ{/nCC= ma fOM_$_{-qmDH|Zf{Sym.Lw>f`S=  n(ؤ mk̕>%p@O-,)Gơԁ?Аyl :H)jƸ]B+LHӺ2 t>{j/#:A%rw8{iNDUP h\~\k34JMԨFh}TvawmhOEtS%Z0%;`/0qN"lScKC̲9{ \btkZDbSzNT_E5F5 ji\WP} L:f z>ݪ̺w7IJWk[~|M()pաϳ{D`5|nXVw@e]!~`,7' $xYao6_yزPـ"K `Z,.dJI;6E=>Nw}s Iuya4K4%QJbdThqu³k2J$L"VӉ{L'WPiOgPnhi&\6u{߈$D" suJŜ&2:eYBLVXe/ ;֙@4$,?b|T]kXk) ūF6TSy:z7i;bt5*紮tϵ[v鐨E<ሎ64K#\-P¤lKa6%qe"m4=riE)ofWߠJQ0U|[UVa?ΠY 5 YW.2xS0+pi[ Ju-8 V˞Vd1rȞ{qR;ϼ_}}|=Tz[bǘջZI-;2{jO ?*V3! $FmÕBɨ4ǁymŒD+f-݄78Li[w\aT~# Ih ַ4, |BlW{| !#'G,0YhjS;(\8!QÐ8DJ=ԅ8DjƊ\`Q hq %Gwa*+YN>ZK}hS-ld4jUb>R@>Ic,Ԗ+Yg̢Y4f[wֱ]|6IT2],NՎΚ3ŬFl)Mdy82VR/ra [trMM7-r&xUM0W 9 mR%­lVhƓƒcW$K=v>Klyr؋ۻ{(`IvXuPR,Pׂd9<'Ug g+%Sg փR(SU k<&jO}6OT{ABϴgAbCE `/ GjvC/uVVˋvCѻHKgQAB(`7]+bl;RL$dB~Bj\nU!8e/n#AL8diy:iLH,E)d҉㜍j m9%%xTTE Kֽ ܶЯg,^IB<,|xQk0+AZ(_nkABa=\[7%IΚ]qiGMDtk+yss|z WU:IP#%ZG~5~{!yK_QziYsd$$.y:^:/sw C,$-gd5U+$`9K3caOJry/^[C/|2WWsa7)lJ`6I5Q*$ ۷-o,=֨4S7qwx҄x+t>xTMk0WLK ID j aSz $xF$oY.zY}xdv+t7U! KOrRp=h5)rbNҠUu agDӦНvS^eqҷ{ 3X|t'{#oDTVwB⑂,Л|wj"&dǀ\*<xUQo0~ϯ8@Z1in^{blb}B<\Kcɉ+պ=|wxqu~2n:y>p3{>Ί5Mv`^c qkńD2UFC7cdXeL <RHH;.aɥRPktn7.?ĈZas(^EÄ|-h/7p;@?VċYpp$*H! l[AB'QPWΪlo.nc =x4ҦI 'V6c;"{իpjҹEݨxC)PG*>ɹ-޶ЩZ==> wзeKZջ'A[IbO7jّEǎT숡s08/#Q$43`}fg eH^Nn,tKƚ.A/Բu`=K:[T}!XARfd ,d3IkH*)Gm ͎4,r_y7=ZDS̃G)sxAo0 0/zev%:`ID{ȿ8-񡹘ttXmPBhxbm1euss(T}ۧ[G5]%vUy-T9F*3tQ/B@OcmmS.*Uph'JjR:]n1uшkd;PuuWK+|U5% DNg2t],}>Lt ƙpUryT󘎪K zc&-%4.RK <2Ї]azқ- <>Ikq0DJqB\؂Ax?CE|!?uL6ӵ*uR ըG?X[lDq[RCW?h`6~>=gCO,ۀ59wf}n/KHBQ׉CF̗}hh``Q{0akLM'GkBMۈY/ "GuvAlQ3MP\vt?NT=x ${VB2+2ZBxX]o6}ׯò0dtkɒ=ҕN$U^RȵI`Qr9}֤vJ7 FB9l')2-1AtB{OQ=rP +or3qT[SdhN}3fؼTD@JlJov ފ葜߁9z iKb#$)? ֺʢ)FVhL'xrL`È>)` I ()99koI$9LH<)7B fc3\)*p(5 oxڵXX@w#\r׵DL5(z.t~zsν\05E$)eӶ.`Tk Ũbhފ& .կ tJ{ ubLS~lF}v0%nvo@7.ѪYY(h^#]C+YvY,Vf+x\Xp0;Hzqwyޓhh_ uǚ ؿĎ>#ԁ𢠥~Z رTdwՓDݴ~nNk֢4+$eat_y,[#]cp>B$6l)zr;>*}f, gͼMwϰR꺢$d=?t$qٜ!Hg; EcJk$]gZ ؓvyg(sv[5n@#!TZ2_%_^!?;1zxM0 &\@ A&jn˅*mmPT#^W_|&.hz^&$hbBڋ# Br,9Էsla^n\mOV'2d.} 7ʀ7ų]42g=iՅw9^ I j2iۑ,ʕ?'rs&LQLyr%HT2>>w +b(A.RN4M5CjxNº@J /4xXmo6_òaq0d4k:I.}V"U_H4o:>wӟX{*g4MIZP@Mwt !\i٤ >8'5H@M'&ND ebl.Nv3Ǩb>CVˌ~ӔgTf'Bp-җ)o-s-_BV[ZK1z!J u3QA @?6J1AlC;=eM j?\7ŶG}mt<ܬ-dSǒ*pڝ,)Dp X`4o|M$np^={LȆ"tVZ*\;$57u='3 Pg&/\ rmfD/' g3/LGR52NK2-Ia6.:CQOEõ"Romg̙(PC+eC+85yAag8`~j0 'Ov[/KЧR}i iS/ UF E*<SHѬ rj rkxviX23kGr\=SKnLv3B"CZ< i76xHBq%&BduW!Z<: '?znZ^t/0wT?1;uז B<'F<[w 2f9 qcE4Ia# [TߝW>'Sוֹ]uzb>F;f;~~8>2C7u17Bò@5w<]e" tB؊ۢ㈭'zJi˚Wb  DOøfwr0$6y  u6nJxYmo6_q kZ4Yb J-ncHJdˉ\7_"[>>|x?z75$:S/H&%D*/>Ls:l^(&DKT'"Vl/B d4Lis@FRz12wL;)vqHlzD" 9.s5hDN4 |. ;' p OuBVzXnw)A2QB9grr%Mȷʧ0h  O ia.$,*g_Y6gjTIJqgJuF7uMٝM+Byȉ 6&ChHI;IHjPst;mZ( Y\8; r 7 tBN6t971S9'k|mBjK!+R*YT)Ai WMIjXҐF[sIquʑE5MHث mF8)j'k&bnrpX!Xp%vb_KxE7ޟ W8ffnB5(_4½HcMw%9aVH$t+L%5[پk3S h|r|bO&Tn>1b>uJWyv|zjG?si*.,,GS5 <,W)&IusF$k+ΟQЄwXzYWEjCn~C̙}DDaXH. 1WT128H%X NDE-̙3:+M~2 +*j3(ԡG.at5im2ׯ>M rAyVhr.bZq8?kȓ91&9LN&^saFMnorw`kU1Ks櫐?!QE$9Y6M."BX,~ Y^cWr*:{W4DWT& PR-? ,li% Y LVyjA" ~ ӒKܕD{y[R!YqR$RM/0n8TOLP *[5oWMp}2j=RnIqa|l;3P4$xxT͎ ԽnbR"tQW^zZ30X Nvd[0 >|yЙRѯ yJŗjl3vv+6DcL#ΩSdTaAc5~ĄVv.(ed@LX J+^.,\dpٮCdTQy%PjQYH7&{$l-Ūl-8@6 ܵ:߂{3!SнzzMËErވ"$7ujՌLn,# f A PV; xWn8+X_z"lE.mY,HQcE$eΐMl%lGΛ|/r|oсL:W׽ *k:X)z ,1+RfGUs/ǀOL0ze%gwp8\|EU>Tύm %l/Pܹ Q}w=D;/Tf_{+c; Чmfܭs"F=3+?_ .R;&܃Oø!| \Ƥf1ZE\:.3˶ҷh@~kp-Fא:<;R tw"C(⟏_D;[v:pQxU_o0ϧ RIhFi^~\_@U&A&Ӡckf,Uڻ*{w),tHn%`#lIxbFfL) {Cpz&Ͽ U5] Z+ غ gi:MY_&C9QBY̘dГ !1E`pgNA8aBj- ʞmqz|vC8iZĂVZ)+;:J;V02ic3suZ~)rlj*luJomK0irr#2RԖc|,r쏦hv,mpz||\@F{Op<ڿx9~v(_ 9Oxn}b"Pb4@*\7I ' A"ݡr%Ps\vF,@wgΜ9<߼,))RXJ#xJK2d&d ~ʂ,͢Ldl_؁>j#LMs޻ְXgGъd,ӁkX5R3$e*ŷ<Ʌώ(z'k/&cd6m@M1-) |C_N}?pc>`nO GUVULr[ >G?ee>7[;> j4L8k1YF3/RW3Ҧ…F>qy#p k.:߶+~l{zlO`EEM|ߵ01RKivX1E])E]* 't- aVxlq_o,w&qx@TGKsܺrLLZ#Ydɂr!8Fx͗o0+nyi+ATi(P_I40NU дdt[_>syw$֡71֒Dt=+iyvs?)2U7UB^dizT,X2hvzs.wEG+{x;vR1b%\ϖ jS@O"QK"OC`H-!ECnWU;ڜ]ΚwgG$Í@|˥PˍJW@YmVb$dFpRFa2ƶYHW.;-ۂmLh١nMi([`k͓mE}Ը?vߛV|ϼ|.b`>Svs,ZVgDSuKwN9B\$=h-;nNmd +Qe-22^#9r jNMf\d8>F3,\XbRl 7ɷ,$r&/ g_ \T!t3͟~EFt=6}]%rnJֺ`6ĸ~E&Q֧?H՞>.‘\ƤrRR ~⹘|ąf5n4BK]SZ8qt3Wy6)-#Aز.`;Cq]ravA2p/s,8}3R;S@'MfNs)TAzr(L"Bdtݖ߈O:#`i`wʕ HW@[Gޔ/ЕKxIX NElҼ WDX,bJ|,)@x17MWrfĠTBW0h`LEȊ2x; `sB?gSuW,kBFA R~Cg(~E$B{&@{ [XsPH,)T;߃ y$D0-8Iԫ 5ڍa+DY7F$W?=UEʶ+źSpJ'J%H_vV@H IX UAA}O-48vYcPj6+U*UlM+1M'FX,}GӷLf//a ;+9}5ƕqr i0|1݀x§߆墚syR/b BC]>؁-}SHM-4zcBq)l)CTЋ(jPvTo,xXj@-EvI (鸂6Fp˜Ni&mR{*yπL" 7r -Yd[HB#$%}s)ZoH=Géiw sR#Mrg 5YH TpYE"I qA[12ND.C5IS\R!{XYvQLM S<7X)SA*6EN`Y.쮂Ko.- JO} Om0{Qn )V=)cֺ_ޣ CVj Iz#;,dd%pw~XsJwoQ"T.f.Dzf=o7Yy.c[]{ +-~PֱUXPfN6o-Fz\Ћ4; ~C[Y_C)nض9cF43fSC`xxXgf6Ў8IJuLJJ i]=x]` W=MCQrY\uxҿ:!MW.T=K^'*,j=Xeb1`x;ZPs ȷHr؅_Ըl٪ir k#r2w=fS&&N(.t9k89%2voeIwO Wt Uny[}W `\_8upd5Z8yAfPVG;0eq=9#*9om,ۍpLA`Zb ~BalN+A Vq*xn0{xCrK}oH1bt#\y=gWjx \N.i`c9uA) v z=j> e%e!!J^4p{ Cm$Ri89)#BFn7Fw?yW悖ɳQ׈pZKv-D@6݂ =?!qH eQVbI<*0f q}1B(y6, ZY?$:WVܘq#xLR|JI^A z S(O;>WJbiK0x҈|G<}}9iBCw>^2BV.!8m0Ip|eCbDԆ-ގ@iiV/p8HI"m5I4RǝcL.n(eE˼Šp/kҔc> &tIJHQ@{ 7 D_nWC/ a F{1X*TKyP?:l[@AZԃ𞬧ʹ :4+ =M[49]\G޶ڋͮF4[4 {ӷ_҆AW:v'İSyN)G>f-gfaԻvH}>>ZrSgXwb`QIJ(l>Mמw֠Q0FdɋvsME:W/ [9 ~4BϸˋQicVUa\ka1:@7j={[nYsD. A.yۘ:@Aw_<C_Ap,䂇7H# {{b𪟙+|Y01 ppY>6xRj0 +:hV(ˎN481}6eݡCbc{~{ܼ@îՙ:h@՘1/䝉[ |=&8U3U*eoIAG6L+X t:_[\$Ʌl2cYU51Z4.PSJh5vZb82:V`rr`;hPR)pdm Lg [ڡh \-,yf olJ0ڴ#&!Q; /o#!b\߈G= xVMo6WLUH0P$ZҴ[@AI+uiy*WWUV/-z$M'E~ $( &!-"Bkݞя%AҒlXA[V DI2DR$ha ;O S#^ĕ] `\FעJi-b/ Hg`E?*(H\?k{nwe   TQ'O4iI#D8&6Թ!̑A/ۖ3esh`?J#IaG&YOxHd:;pnךyA Z2o`B?(f"JB 1bB=S\,ɢ1U@`|vΠ >OwjH,ʣ0HeްMw ?oCպ;9!Ef>)$0Ls`vV ? T#C>';۶T 'mU{PnJo Ys1#b[MS^(U}^|M^RӤmsn'$>.ybڰaoA*8^LC( Dȶg#kgWV3Q[gp7"9Ua+r,!}) 9rɯgyMⷵmUq7C^F;|K);_'N6ܕdO*54/ vK. ~O}z oTIxd<LYxXmog,\# P} 1rnj9wGre3^p^yf~| YeH*RtE_.>``lN^|PZ ^ɖac՞AJ߈S;Q$[JQ n/j:xˊPa̭ղ|'r#(P }#;VvrA?ĭXnVcm2Fnx6[W2?Z.؂EC8/ݼOG[!M X5+q@RUc v@Dl%8 B֊Mׅ1kTܐzV >(B.C [Z}IO: @u MvBQɽ؀ 0vv`&^׳ݹD;u:E&CcwQtC^10h!FOfkA%>8m݋g7r FEsk!}o$TvDFq WmH2𳡁(>؄W5ҙb;C/Qi?yd]{h=Vf'W0V&qi(g3cbV}1b/uW០e Fq|k-ܾhO-Ф.ܡnzJZن[,o{k3s*&D]H$[S^G :J9QV ]&`n9Ʊ/uIpWdD^N5x@hl>lP-΍Ah] jElqlջUƃ+" 8a1FԖ^{S\؁/q'`G!m,l>.32_1'V"JB(dGp ED9Jу&{n2l!? ik)^ڇSZ<='T$C.4x/fqA: $NF c.6w]y|u ,aM&99Rܭ M[/ʌWU FϪNt|ΧB'Y=ѹ4nL4`nd;Ǩ=&o,+Pu v;pU=&}Ml+ umQu|~aRf2(u$ԓ]) œ^r:bq\ņו^"3 #8Ch+m$ߨT{ɄݿK;S*@Ԏf'bR)ХGZJs=zBٹEY',y%ך†2?XNϫ@J2-Ⱦ U֜G qImՓ~xdiC !%Z쮈5:p{Dܱ3!'VSW a3_ {7(*Ggq3ɳ ! 煇{ $4^ޤge"}{vf7j_䕮eTu¹qH^^)a)jg5~)Tʤ˿d巐7Cg{wF)`>M@Taבnxi݆2ա&'zIh6B|L>2ʮ=!FC\QeOW5VZСݿJ~6gJO&!]kF؇f@gCK˸BYyjbXZ.:I8ƼT Ks   .nXIY3V9Pk[9 PQM" gIī&ru"w<8 { `^ͅbMۏ24jq Cnb@DsiX"/wCLjySRO: ts*n+i8 m +]?%?f;Hk|R?(x՘_o6)n~R Zv0`%/ðZ-.c(RDItbxwt~G(yuF(>$Ԇ˷OR WfV[…O̖,CI MNXb0OQ&("|ғ8O,%Ny> zQePg, (1f>Y{֤+ syni+"oR?cZ^Z].5lYbr[ PqV{nPܩ/˵{nKX1j"F5 K߮3ZEuVi^L<@T dP4/kUdv\{Vh _-ЍF7cN&w}{weغ#8- ɆFLRkؠ M%pek] $SRɊ׼ "fGH VXؔn#([FX-FC%-p%-jgD/Է8-l{khg3p5 ~еZv4d(}*܈#x )98ؚ # ޵;0Z BBF'k^-:2y$ИgnG馏? 3ٲ{ze9]כ<9CD_Qz?8~݇)zues-6_)eR]dBj=ƞCw60khԵCd%Za޾AS6e8LZ`fr (7 vѺ]~wߣ9ߎA=%@Wtnɻ36 K1PhHkյ_uv ߍun+S$hh2v9Ĩ'1l QE*u]hejaC'ySU=TT9s²2뵻oD;Uu,hywIÿQo<[ ttD xVMo8WR [ Pl@X)Q#DjIi>-vS ef̛!g>_{-|bձ'!U:i^ޚrVi)j)*γ!ɳ 4Cp<*ވؙ$4u|ݗyF`UU""o.lɹv*ģvs{uF?D D&NZd2]I`eQ[߷WH'7zJ!~7HαJ?ka)ߑ->Yj=C{`jE"d#o`t~ }YS,wlBBO =a7,`bvLCtQ+̀\ hsP+y~puQ; {'䥣;>Oc\A`C*g ^,tWJ:e%9_UK`o)Ҙ 01ј<訴7jzXƚCKBMz?n GW@BƀrYݨ>tdM ,Kue-̇G%=-̤RNЅ¼KV_X!"|a WE[`;Qi/t0IҢd̺X޽2֛{uz0,f}VSA5sȱ`ʠ:c'^%RY{ M8OUK#H'ʦ4+CeUtkwOgx8gGclЀOmGtJrBQ.tmcѩw+E4ΖŊ=;}(e* x58}C~9 x[[۸~ׯP& 8L1v!hQ,BRq~}ËDْ-_&x~Y3]h?8KQp$cRqonxG,I(-ŪѢ*qUZh1wVU%wK+"Q?G`u`q fpmo9#گ5 KRR/ER|^IΔz{V7//6L.Ŝ>b4VUj7+sc?k.91ęk}Uԍx4EAOMovMWP/^XzNɾ}J8`Wk12V"zۗZ'ڑ y3mwe9(3aƒFVvR$/5TvԹHΠ6sh)#?aq\'`ĊrQ&D8)t;hևbsJ-r?7t'YΌ@h:->Y^  Do/Npf̒ƼlԠI G{{D3*I^+8+]"%x+y*}'&CˮK'!;K^S>]yJk豀NZR<YUWrb_VIE}ɢ6[3ɊDu[wcBՖ$>%?g¿R$pb%U)/|x]q78(洚iye46=WUSL ^7р$뤽p?Q\Y= t4$)jAŪX jp, Q78|*/$W58 ɟYoAljVLl¼Kc>Z-YK͠_7woYXn1YK&0]4';0>i=kSDBC]3i8aq6hÝf2rي޼} $ܙ 9yqB k1y%#)r0V! |iq 42b  gēd[ .RV蒐/@zBÁz[wjFG )f `svO X[(~Uk)נg4CW Ay E*d,WcFOO&' numpS-]>kr+Ny8qc%>S ';7p V?<w0%Bߩ]62>05>wMeSPE5`B1"+*jwV-0^lE])PmEq=pgf XLq @SԢ&4Dq[S.\B9Ҩᾷ <( ;n4x4HPl,%=ޟ&D1@H@ǜVF:<;T'L5l bإh)$)AbDn"{2NQ cW<7`E..Ό3\$#s梴Ak`/3*ApJٖaj֕YLE;`ۨ l%`eUƋ# qQ6U/w{%jM}M5@!=N$#5ұƎ#,&7[UpF΂"nog16/ T.s%aU-@p-[FTOLvvQE{:36o}uBzԭ';Q:&W<²)Dꜵ-Lo^P\d~SupǑa\i ޠ-frgeSFVDN=FNX\nR{?Fr#|/7O*%>W滩2Ó/F4MwGvRM Oi}7}{y %Ek qHU98Y=%/`^UV=B|#Gڝ7rg%zOCw>>q^1iҾXgWVtk6f!BpY֚mI:HʽrMW͵6/Fbw.OV P''nAf Ngu`x ,Ι& qiaoVM~F+|~ް1xRC2iU첫^$4nR 7=H}p>dΪ/%ҳY?O>sƭGhdJ[RByohޑ05(aySwg! dN'~+XY(} n{i(h A!g\JҶAWa~>˃5RF |KxKn+k)ɠ bhHLyk vbk.1a0k`bӤV`!~S$PIZOzug[[V.X/v}2Wl_z[P]"N7UJf7~+~NE+YNH:nR,ޤ5 F 2>]Ҧ6Vq(  @3<.^$75g֑*<(Z.5DF'Գ`bѬXTUn-[ \"."Xo<.N&;΀%o}V\Mi46;&?KT~ 㚶PX8V umcڰEШF9;Ѥ6Bfv"b: =r\PscC<4E XV䢘Ϩl/rur.~@&#s]YH太pGS ~0%/eɞ._陜fyK8.^dqqޱփsie;U-OT*tnG+Aa#/&Vm"/#6˼aYKsT6(T07>}ׁ5b}^e}mFWpR0vgHu" !\P&BrɳB$ +@[<+yV_!.%dDQ!H|rJ@jr1C"KsT)֝_$-Y`7hUM; Nk Ղ;jFrAEe,Sa . dOx.+tdM|5XrZ|^jz+L \?p"X۲Q%o +!Ұ(ܕlC6/p,4bQjf6,mG%qdDwr#-5nJ$ Χ%{>HQ9V`"ʰsۡT1V|Q HՔ^W+j/ц^}6FiN C^AƎ޼xN>=GSrxjJɁUq,4c4j3Te 3l4U{<$vݷRˠP9AQgVr+KNăۻg;lSrcHv*wWh"0Ce }/ $;yRA2wݯ'DRTI>NVz18ݻbw?j18䭼wN4FFov=@8= fJ+< B{oӽqBJIa7"Tx'<4>F9x[[۸~ׯP& 8L1ME^(@K$*$5~%L7@$ks;wӿ?)-LUU,G K)ۋl_XF叢`Y㦐^-d72'Wkm+"?3dҚU,^WKF<^M H@ܬWzUNib/ nZ7%}ofminsnV޹o߯ϲx myQDZZ4<ڪ-a汍mx]Jx}Ň쳔fNmTn:ۈRlrOoX2K簙e)?VoiUd7ȳNBb3r&GuR3784}G̸3zN2cw* [ZS;<ܶƺdžx=j+ 3.J(D 4Kj&~Xn̡Bsr8-P麟%|Iӿlwa'4 eÑ}{tY-nDH $-X^#G|`؈V\7T%hXPT~,  Bpv)oFuyeRyNyoo҃_jk}މ}[K;MŦ^I:Y?E##H͉CF868:*r[1x%qWB8@ }*1 -⌀[WC'Bf;•]Te~IBvGJnNPbǟw"SwV7y4YY!ߣWWS.#{M>47oNoO'bgkFg]ڵ#sF3륟H(zw0qb{z T~?#<0'AcDj,^X趆3j?uI{?e3>eUʿ"|wP!n?:TC+,JDQMHF6hj qb*Fh(j=Lƻ"+n*aC.g;pF>N DLaR/7)Rpɒ8&sXF[ Y~`6n]?d>FщjIq~E+qG$-dIiqAG/ OE<3 T>Ƞmbw_9/QMYIOϜ7hZy[w#sAy[]@ըz~թkI!S "m6.8&2ouGKs,I]K_=::dOt;z6dgsii )ׅq! h"B trh|~F:txtRf5]nozaG>7#ɲ{p>t. >5Ws'` ?ϸs5LhyޯC @W&/`ܔ乿cv0}Y4]o_{i.*Bna1J*)t׍.ݴj)gPcfS 9_tV~rj+;Ʉftm$7}z-tAxsw)Gr֝#8,ӯiQ<{#̳\년@BT2ˈa9,ibKE 'aO9, $~}:`Šñ~-Q0QClp67Dhok{?Wc2<&\ޟ`jeH:Xf2BIZӞpS<:&&~RrîE ,:+ۜ{Tu8ֱ! >7q`ZX,Nyk/>f%Xӥ/K?; #  TH,xYKo6W>HEn m qDQ TI*^m9rV@/%q/}~'Lou  4yXT&mmJ%\+T"aJ?rm+å dp+/WAw,t Jq4((2,6Xwh'6,٬LfT/kޓujc+[ew1:N!_)䌔po)|RZ[mw )憿1Q yb9(p%&Hu3I'dE(23jVik&!BMf_BQLÌY{ 9@sj? Zu/$3`yhbT19wcrNƨQkl]=Kqn+,RR/5a$z34?.1&hBqaؖ7AF͂YiEg@mh lv$֬h_ G%߬WRjɎ03[ uJB?ixio^lǦY?JeHG?m0`|4p~k,XoiE[ ]zuΖnM#eHsu@]퀑LJd"4s1^U%(L *L5bʔۣL4tsh֧̍W ˈsCcjyr|qU㝤a๝xkL&3YX2j>NZ=N\n9[j CHeH:%<~S?7Pv";"?gZ]I(9H5i Ϡ;ǻqM[_)a+' ]k쩳:5I!u2u)"ؑvS1 ԻET0[N<=@( ,aSY<"n\J&M 8&` ZNL5ALI}\.˽ J,G'ʳxRz!(#QY2oӓ2ig~~]{t+j1jC/PZFme^]fTw;ǁV3jfٴ#e>eh07vTzLߨ,f|I(;xmo۸~gI1KwÀXa8Z-y"QHڒwq?"γU&3MIRUzs~댭J)W?rYiVE¤ȕ|]i. 7Sn x-=Y)M5lL;%%}Wq 8>M8oayHj;gTF͞Y\~[\ ?%]h_.,Ed]Te#R"w"/+͈N@;FP͗7w vyrunf9GbK22"652Fh1RB7=F%:b-N/r|ٲ}`,C3 F[:(P $׶ZN'k6gZpNn-ӕϤ͆.t:= G/O nyAZ"Bx=4)xM]lm?ORg-_CXNW, قbOHΤ65 )OI%c>In9uFp;$z/{&Ze, OX "!nbT΀1( 1=HDdG%ߝqw},ӆg׫SB#("d1p}-!JAr[aH\ѡ]JJqi޵ W0l~TًS6R=87n7[u$\4\گ i-f8tm[YҋEt.4?ӌJK`+f%ks:\o@,_EN!{^lŷ'Ό5ϻ"@6ʴC~}u DH|C@+J[HǽIԑ3Q'Qw([W\TEXюj5PM[ %D!#IϿ8sn,,9=Nuu[bo ͈~ʄܪkŚpv@4\Bi᪫X#cqxPP4~$+p:Hm}L~4I U!.Tٯ'fLb)n)5?6C)DgeWppTdxcxYK[TYrsՎCiwȝd>XX V<vo \'VDc N"LZuxm5tmi{p+Y"k{S{[^첣*/av͑^"__JWf>³'PI>. U {#ne12 ǧ^4|[sEeҢ,v>mHxb `a\5s^bM7vZZ?6Qwq a8fRڀ36e$jP.VMq oȿ>TdRL *2JS>@ZYn6Dvw ]')o:uP7IG'o?&~vya^\ݝ{R?oI]ęb w(TE|Vdw;IR2oܑ 1ގ+U[7!<)Ic$8F|9Kks@EU sj.䶰 =>=&bn.'7ǨHpuva[p  aKFxYMo6W>Ht\#M,6T_ߡ(˲dg`/-|קOdil?P4Eak|nI!̕ܮ(z(- 7kglVF~jC Lߑi\l(4ŠyBfM79+ĻA42MrKXJ_z1;u mXamqONn"K<$3ɩB ($WYn#ɥ憿Ѹ*JJ(;;b9o$_eA2Zf8ȑ4e@U}$Mf > g!:;?J>Υ/TDd"z/. xy/2o$73Qu ;B-O9.oEc* HEtϛ,?O@_\" QŽr%Ez/E%'YXDAsFkK~Qg!Oz pp~M xVQk8~Pݗ5=1iP.垂ljl'7ɿ'ش-d]I73F_}D}W$<1RT*ygawT ֔OѤYrlU92:$&O}X2}19ɥвִ38 !\䕩KQuҹq]^+7d% ,UI aTfgv3޽L#_"R=N&lDeê uBۺD*oY] '#QKԼ1;g`*LRhFw8D$3o-1(pNUO$~ ҂vpwksF'[EέZ  pFT$c/ q0<:|(ff՜] OzzFrNK/锛^^@lNJ!әoV%z;yHV =(A++V ~ R=}I"TH;cWSW&!ۇ{APB8aݎ}tJ"?TK0o'U۶{*Fdt/=-Oʉֆ %i?"x s\vqxoFLc=BO) mDK)ZKU2C<{5 h1v_BIw=xWX0z[|!9k'LqoVׯuP b5%n"g* ϶&oO*ƥ["e٩{ /gܟ`,4>׸_}9Ep/a`EcZ'Y΀v-B *> +v L9Yx[Y~` #F൳9H,ErwS3_ )Q8^ m8ͪ:n/~?q|-g)> YdL*^5zϵ9_ֲZ]HA(-ŪѢ*qUZh1wVUHoJ3WD~h$]_-sF_ Ek@T/I"{'9SZ] C0sƋ[-W̮|߾_-yWbodz|gnQ/UQ7:qMmx6Y7eb~xO_v$6~#zs'),N4ʈfq#,l%rUD;ruay4Oc 1zfJ繨_云6[snvw3mF>x z(7)j犣\Ynw?7"s^NgD_vO|< 幞 {@vk.fi,YygF:qDGd9!sehѹv+BY޿: )ܷB/mR1:$ϱQxӬ%xj;.r頩ڕZ (n4ВȪ*"N,ڕwmk&Yuε+QdҧTwh*"eו7cmܮ&9Z^s2 >UՔ)w m쀳$=~#L9 Z^,fi*HR쯪'b-WHVBb1j&GyP78<`CV9!qN4z bWdeU]RL>" KuwW--: ׼yʴdt'm۽wygm4\h迋Ur&-'(Nf3]-XF 3o(qW> #s|íkDu$H5v_^D HبPF,h&F, 6 98A 4ۋԶ n}&؊saLJF~|zrFG Jf %Pasm9 ~PxbX/VX>S #/jd!I_~u[˪F+|4:QȤUq9.zIϓ|K Ӹ AR}()%I[}qx/H\cKϞ$I<. 7(zh parPDL̜Q|g.tWE79ժv(Xȵ$ FM;vzJYW'PcU9^wnJ$K3fpn$6\3z) A~8s{)F ǑJs֜TAft(f"7:!!kNâ<]c-#u4_xthXn6tѾ5;gHۥ= 8]5. ɤrlp#Q0Pv6tscyHc4VM\efc2ܨ8\Sdj~`haRx{qhem/֫%'NJJCAFf̮Wzfiy{5iV7\Ւ>[ڬ͚=?ey矾_-=eǓckiƤZ:k^uc`iS6lx#XkT&o O/u(>d7zi̘Blj@.nxp+2ƭ#FϠ+d]Aia]yi!(aZ,!Y΃V 3%l37%[wo˃]OרF pgy?"I9C`ϝOr YNt䀿:AE==wVh^;k^];I-^J8!&d9!ǝbRj-i왌YtO&?z9kih1&яc4O!f# `8GvZ x.u!?ϵ_7L&@#[DIBJegPNYnK%;~O=5׼L\sg26-ўe'`whq⟄STU[* )a[Mjf{bYNX[2抄mTSe\K[[iK@hp䂎0Q7j[isƳLXaJ` r)'sFxלK> db˛//_"(-!6 Ͽ!M6,~E ) bg)jx"dv!e~_nsR;>4VmϠˌ;9'$3yXsn~4bXXFmX:3n9K[ZJ5d <4ADv:Qq?NTyA|<KZR-^(;{ҎcI'x>NyУ?bpf)E]69>2ׅwPc$;d_%0IHt>F 5Ltٮ)d=ɲV [G|وVb-2J:P?К?X@x4m Nn>=jllq}Olj~2PQI^5 A!'a=٭ H I}Afp=7D)ӯ[zo$ 3J^kC=)H͉CF8qLkÀV E4ֆ *D&9<#Qh ڳ[x5ԭx;(df.V*Q/9=iP]D7'HGt[Cɟw;-t˿%஖QgՔ"^w^uf0FdE]<4iL70 :ǍtZ8稙f*8H?7$QNva'X%T4ot{j@Wn{/†ﴵf[OcBz5;}WxWWL|iJ<2( BzRhF8MP`F(v,mUKkB1{WE2]{U݈NSBg'\m!"Ft+HH e x~ߤ6#M).6N}12J 9wGv+1BX{htWw{DX J :qZ.j[kQB jJ~'Kwcp^$ͨ dyD,DUi類DvE\ ]ZMtPd{H-vz/pBp iӦ@ܶl%wof\]m nJ3yh"tL}Rj*VQ~ck)? yLC@ߒE!|jDy?YTB#@4ϸs=hb+SvAx{UʋBd.eA'm˽Qa}Y ﰍt LԒ ]w\Ki{o;Cm[p?2g 9^QsXfdOmSFAB~&dC)ݡdHP 8wۘB[=ln3<#{y0wvN^ȉؠ9?,[Q[lR)^Hu8fɁj<% \}>[mz; =r εY@ދ츷lPM&`=p`qg ћ8Dӳ.KwU< rm,32ΥrNhDxYKo6W>HE(6 ns 쑦(D$;CQÎ,{myqot_+mY$QܾpK-%,pmvb[a TZ_?*%X-ֵJxXqm77V$R Z{2E1%a1,E( $-x&1S)Ob}wce 9p)j8TXJs}hD 2Š7N 5wP⇐bsNJ2|EJiwU1nlq7QwڠTYrщ,GB 4;/\֧{t- ̺ 2F8JSq&m Kr}~"tGjZb{ :򥥤 @鼸LOk*.9/'!-x1;*hs2LP0m )e|j&^2!Z-!S 0%-2`sb>rôˣ^ρiT{#l)1Җ 翱_څmvo^BoI!犲i~߼ {Fnjz?:Syз;̓NK 9۽G2sIhcǽYq @ZTo"b8`m /U4\֑͒l~2a+6Vz^|P؃R5ޫJ օun)4w2%Rk7;h0VsɶG(&i2G=9ڟSrBzF߯'  MQQ&xYKo6W>HEXl6@bA=b!*IPӶ$6Kb[|Z.ݵwjew%ebLfj󼜭 Nޮ#FQ|1 v1 nٸ:[Wdp+3ỸPZta|XwR;ͬ¬7{d@%S s)6z1" q.q!Z_/<%v> Crc *|22^J'_M7)BE;Olxb͏ZfջIx>ilB&K J^sJ_숗Ze̟jrN 8.(bT6Pfeur[W;1RCo@EBBj y9wluhewdvXfmKp2ƄVKWu`+,Q>=vDGrfSmMG::J;7=i~_lKE=[|iV.؟-F };jzv @Ck-*ۣ!ȍxvXb~>>n,llk]E,pYpցZ}4̪T:5󐯩?ĮGx2::'W825oFؿ`uM1@480(O7}~Fr%ߝkt=CJ2x#_+O3Ub~x G7X0.EQ}>g h {-#Q.PZ]ƣڐ"PrD*ԧEV#'ec[nY"C. 7O@VoR|^P1N~ .J(ױL$Q1urq*nQrNz *f93n3yJ;TyGG9}Ƽnd ƀ-h"/HVńgv\H՜vTExOZR2֊%'޴/TP40lpwXYlo*[ fvwK8;({%t$]mȽT2ϖo>0bSbƫtKztTTCtDk_&̜ yx{~Jf J3i09IQ@#9Z=^?8-zq/Pb xVMo6WLK 8+"NP4@Г1F+";$%䍽ރM,5͛Y/|W}B##}{^𾗾j|x}~y+K"ee1զ귕T NuV"hTthWeS"+fRѣsWy>WRtY9``.cX^=4 0$C&w\HjO6!u`Zr=Yw r!@yW7P =A\XzU xV'U6Ёg_ >HI TK=2T13dtZ;gF3u4Fg RSz#0|rRcY5%P [h'KhG{=À?_~ n8iN;]aHYrJfvb,3EY؊^@Bsu>Ve'wtWGQ[aoGṼHEvT_gD?ˎ8DYgp'k$"VkՆ`vAt ab%|t?#HdIw.,)c秳m0n2g1OiyxN [o0bN BSBd}.h2%۫8&kÙzn xh ='^{W7N*nWnw6$ݖ?>^1mepl/Mx4<*? _ ˙/ "f.wN4m^\Ի\| H^N!܌ ;; @~~|EHIkA$VԻ^RrW788K's&+Z0į-ҋ>F ggS fxV]o6}ׯS_6B0MaaSpEQIHʩIɖ۩$M{d?5k$N:&Ѱݦfwʵ.7ΨrtJyOPPPpZT-IH6P< tM!~Hp8!нũ%]1rAMV5xUYVRd $9dK#'SDG=޻]eTM5v4;i7+Y^VT6<ȣg~IwgeX{s+wpKO0HŒ;hUA71rhU GZ9/7c/)͌VO5TG#dG? |E93 ڸq׫H4JV=Q:X&ϒ-" >9ur`vh> [mV[6th0>T/Y-xTOK@J#vd.gL+ sI|=j}ԨmH;U9L)n:Y=Ҳa^yI u<ԙ|ɪOcIz.z~46|rJ75v"Scd}6 ]~wy6P=꾹V#O3%mƅM[ i-+/Àپ7)Y1kZN9"G}VTuZj].֫u?욾 X_U nJU2//~><@ O޹ýG™ۨdL rF=Z@)WS|e9F R:x[ێ۸Slq&@tHݢ@Z%zFCσDْWx@[2dVǴ0UNVჳ7, 47o/Z}bF%7?+OVFMkӿqS\nqZz6`L`ؕXZ82˷ՒW Ksb?7U&s^ӬdZ@~HW7LՒ>ҷjp7)w-;jp-sfM [uD!+Ͽx F#Tr{կ?C= 3ԉ7+<%םWS.B{B7I7%ǿ3i5HADHL {$;;A1qJ#h?ya@Fj0f,tcCf'oA.OwGnROߌOi}?zy/-eHE(6M6@{B"U;޲,ۉ87O~y;ImEAؼp`s &׿6 *^yX- % >.pS 6+wo"c果a5?װ 8Gb[ZE lRxwf`*1#M<{[IÃzEDJ[¹o\߭*HC!M +|E8596v\.7VdW1?geQ{4 wڣaV}ʟBiLHϦL /Ey.=el4VJ6K1 wq`PR-z-@3(JOy:IujNn1 P9 U״NIJ[%uvUe+쥒lXR@bnwc9r+4EspGN,pDX((pT7<'v ${zvqP-b"]9xz91hN~p gtAiUk8CiCu ܋{;ãö xjGSQ57H>5-̫)gY 䌑؈.6;'l! Vu|dʗԖh2\;,⌙^DTC|{ f~_g#ÖQ{($-+Ʉ[ ]* .jSqo:'VNyrEjxUh}~@Y%/)I4w6%nIk(G@K8G0I9Lh:ѳ-M;ʦ?d UU%VN xVQo0~ϯ8 H[#&)C< &mx.%1K`;ʯl'mui|v2M ѐA*TY؛M{#LMId@-)/E }źwB%ƑhMeQ^%ڠaSSņ}$F}}@lĹ$7Iɜ:FB Qu83bu L <, SQ |2AY0Hg1F-"OәؐVY@7Q ՐΔ`Ʀ rd%RaPe5q=0ChS`ni*4= "Tkv-?x*#8a=YZ Zfbaj^"@hZL^ cJ \m|ڍEfcɡIowL6XW7+M./+Q̰c%NܬE/t&xL{׿+ Op_-S\@*בY"Xw՜B 鶿OR7].Y'.4Dlt:ܶY{:j'xbTgfy&yhWΗvC qU;_~r$zg-nȹ/;|6w8~h({MΕ7%B =L.7/l`71WKm7W7~U9Dx[[۸~ׯP& 8L1f!hQ,BRq~}ËDْ-_&x~Y3]h?8KQp$cRqonxgVGFuVj1Q[Uh-fDт%+ۛŷIY.挨+IIT/I"{'9SZ݌C0sƋ\-W̮|߾_-yoɳ|gnQ/UQ7:qM/mx~Y7eb~xǟ5$6^Bx}c9-CI+2Y\D [\m_jhG7,oi̴!FAWQWxKYZIP!Q"m:$&F=,r=DA+ GD[ +yDӅrf8D'׉YN`` {p547f6NJ8"^#QNs\Yysb,}G_Y\oy; ={R1A.4/qQx7%xrW>s鸷ڥ;N H7%sUUz%?EnN/X/{idE";-صDkKuW3aރ81Ғ\ET+\K sZM z2feū)S&~RhYu^ .ju|y:Y U5bUD JHL_8FC(jiTCkz>lO ~5oJ\_UaޥiBx-E%f/ۛ7,w,,\_.k^iuƞ)p.V ˙؜9EE ^tv|rvob9lEyoP⾄Fɼ8H!5kr혿}RIQa[Y>8b%g܀5Iw]+tIH֗L = E|[wjFG )f `sO X[(~Uk)נg4CW A,E*d,WcF۩OO*+L 4ZSY_q5y'x.r3jH< ~&9+^@ )aJ`N؝OAr/jp쌡+Yq:WV['?zAS (JjC.3,QU=#>{pk*`+T=TE'ۚvϑFY~m`0A_ywǣDZbdc--Y4ޏ& BB6j ֙١:a2Qd{ctW.G Q!M! &rٓqDZ 7q<\,(vhruf2X&3 Z5Z>m m]T$@o ƍ:؀^2@vPVe;2PeS5}WWޏT[DB>^#k8;$`rUEq g,(vvh=A2WQ%zHrزkD5]0Ğa\حlUS9?ijW'DY'DK/EyO~u(3=H,z0IkY[>u@%o_I% : my5#k@M뤟Z9:@w$0ruT~7#(ͼ?~AyR)aS0Uy7ڭQn;:njfxjNi˻n>,0!V,z b0Eh^4m՝'َqH(; w׍Y4^Qܻ?UtP~ڂP? / +h:^IH _JD|aߤ6]%.t80RG] ɺ3G0u-)B X{lW${Dʩ,(aC}-y*Q⋝><%(;;{ȼ l L[=z?wOpooPQngC_aXkǶ' )- 6Z5wNVDY1B-qt;G'8<1tSٓ8g4.}m͡Z)"BtkvExiUq{|عrdV !xr:z^K.g$~p挋NUti ^:a*Q*CLɜQYY( ni(h 9ڑnLJAqa܎?iSU0gj݂@ nˍXI`iF? qXEN 1Kjm0 \w3uH_9TT:=^аj2Ͽ-4kA)F0W) ']Y/kVʋCvT*'1鏎5 %@ڮ0pnvPVoowO(X$k$D<.Smx0v_ZfײDˬhSN cׂD ,>F>3|j;Oc^W[ w?ĚŐ*Ikt]:)5xVb H1Pl6Eb@{F"UzVyey6Y _205Uݤ0/~nIa+M_^ i\ŗR<2|E,Xxk ^-8ӷdj)4ɱϓ|F Gh֮Q&bd j}7Ihm-d\%z7 `|AZHNӀ'u3}y3g#є*b:()C!Ŋ5ĽYzɈ BsRXLDE3 !ȡA `P(Rs_}8]86҆4`!͊!xfx`ZH!dZ*<`8[Z6|E!vuﮥ~)Gf}&/TDx-w@؉\S溾 ^JnOEk}+R0v3%F[>.Q02aQI1c=ɶyK{CP!>f=Y|pT|ق0HSB[lY `$aREo]*ς=hxn?< 톞3`޲֏|cTmB4ܣӒޫ8JC7.JXYٙkt]r{eon㚃)^MgH.^Ed u@t=ŞaPCSi ! #WF?ꓮ+ GttM# gQϜ6ʰDXBTm/"y8ߗ*ڷtWJwe)SR{4!>[Fҕ@)qxgf*WfG{ru U 6hgܣ i$kU 4Vf]~fݸdGޕh/St%*]jl:Uq#${.Ed f R)PU^9)Ȳ^t\$XG)yma#V6//lІn &vr,1O8֢ 4m$PcTTy%}JF#~<)!3dcerphyWݶYsE[wn?gZ7Ъy1I@#qwXXބ_@Z<0hv)Z!qF:o7KU5Hf|F>!Cadڥqj?+/ v\g.Ҵ:y+7$>:VEnu}v 6d2魠v=jBa_:>:CXF vDy}s+m'#|'FM@uiyi<р$t7[ٓ|+$|pU} v4I>v.5h+LQ 1[5N[!˄<9 xhl|ՅU6C̱ն,j\4ЁR\lv*jz.Cg:υO_Y;TuaSp.Ө{b8)ܦYį;g쏅wB5^M=xӫoO{@ NkZ=O-C4k^ ŧ%,X\=x[m۸_.pMpҫc M>m[Hʻί3$%Qe{sWHV3Cfx߇e?8q(aRqonxNGsR?j3OVPZUE:)bub诊xGJ3%"R߅`œeQY.$x5z7 _aULr1_.a2ެhz߅-r1mX- 2|n웏۷e;O^d7YU\>sI87:+"++C0*<}<2?<ooMmLx}c}wŇtFVeXӏEлl?<Ҏ\KoSy2mwy\oOxOJ+[i#)Lkl!Y bb(h"rd./NVd11\\ύr:!Y&:Qp$&:rD$,BBN%#}$7.l"uY&s͝qO73t c^ObXL5+a-sLK`\Cvyk sIM*>Mq fA=fӭ "[R<IQ~WFQNP Jvz?)J&Y).^!h Q"8sZ<0tOب"yj= ׅv #ǫjzsie84P*s?o fEI+홰 `fρ"B'!cAbEI?P<kB>P2=ME SVp(džRBrUr<H%oB\6_UfK"cr %&_7woYin1׉Λ;2Ksm&C:}Lس&Dth UR&mB yſ 5tr|rvkl8D1+OȜ)rwbfU/U i-#6 Ͽ!M}g؀ˆ)I:쿤!U _V;>4]z.dƽCIWܸgSڼ)B4y7w[k)ְϘiB@ PƳYhO?;3Zng=<>NW:en쳷 vrʘ4ZdH =7o0D t 򛈰.I'> hG lc4gÁ /d쮙qZq^iW;'?&񈬪*I‚bK 3\lklI|_VNz5^S 4gP??J~*'  Bpղ+wҝA P"{eUƊȫRE;Mņ tj*ƷAH͉|DF886@Tac4K~;h=J"WQ! ICزkx5]Pf\R*en9=PMDWgPY'NП54>aqq^eׄW~^eʚl^Cqa&{bf2òkȽÕ&ݛ%zvҏI@>8p28rc ܂zO*Zߎ0 3AGIyR(a;FfRnʾ`OS7 ~;{yBE<߆)TAȵُAMhH#\i4S1zuWlFtQV)- ,:yZ,^oWNzt;.$$8 Cӏ%ܠ i$x%|3w.z);'&A-j\ۼB,L_躿7x zw_߇ˬ=6q L6h~-= Li.;'~pByV1wSl{g4Ne,Bo06,go,V4|پgs '  v vZxYKo6W>H1Pl"@ РijdHu~}=m+7*zc|߼|~+IMe4Ɨ %,J&i 7 %@*0k4\ G P^.P\k﬩6ԠgTђAs/; j^-\%6%1 an zn+wd^5zU-10J"RP2š篕\__f4Ҭe(߸NLܛ}cUQA(c~ .7w)KoD̃ tz@!@r"St >jN>jQT!9d>9iF"D@.(Fwɨ/(iY-\?H/(4` 586 q*yf"Ɠ]˖oqNDo*w)E;/eTU#:R=05Y>-0*PKhi`b<-=4 %Ԥէ G%?_-,#T)jIɖe'[P.j4Ĥݔ={L~x&- "ܱ3smT]N>PE܄“]xꃱ{ƊU%7Qnm7TTDֻ06Yl+pu.L ÏiϫgU ?ET#7ڇԜtmZat* }z# 6Ee)bvрLJE:ב˒/99YKFB$X&H-%gFvV*[qf1aL5k'7 +%%gD[oik3FK>1s5ˊ]IetJ O9RDZJױVDhovV|" h _\EdsGk!VD~J|bo Pn3Z!#;V?$kk)>/CLeO`~ V:RX`5yHh>T+iZU ~ EqvKPt͎76mC΢1551Nу`hSLE97E!Viͺol\7gziMdJlh> *v2v.G UwH('*O)g>~5@oZD",jJ XNtF5A1M\C;=6N)zA1G4?o.r8A9)pHwKvi4k \^}b+y:Z:6É= 7t,sE[BmDEdɑ(k83gw?d>`4J36op#غRrs3+f-U6ojeIL.3Zj)odv@>7km%g^!ٖVO՚EUFLHZ b;n2?<O_ aW~;OI4ڊ̈⏉6ႛCW6/K'Rg{*jjXF;e6sRL^z XV7"XJ[Hzynvw̋8>;c>v~ݫtl^ gyx+2uꔋii` wLIND@G'5c>:8 ?bk(k E˝diG:ň,g[0Zγ.yؼ2h+%:Ţ{/G6a<)<)-i <9Kr'ym]Ԩ%[l؈"8;RL\B;AX|E9_tQ# I2^v qLLe+S_KijV.mjs yeFl[ˌ* m4o} И&j8-rMNhq'+A]o9` di'!2}9GmDqЍ`/p܀:/bF.ZtWT ލ9/3۫7]{[-Mex'R{iiOncIg]Ү}'>kBd;S*rmXѸ;5 X˝])؞9kB> dlKkahχ0h2c/g^2 Xٔ({<6!Kq ;-7kό ݫ;0@-VT&~Xr7yaJGuǡSΜ8֮LN=n =ZIzO8;43=V\CQNgG@މF0|=&Gc lTT(B%Gֆr0>1S6 RϜ#&;,kr1!9,wVD>Gpn@+s[_:gI,jCfӀzC梒>28' )YݏJȠCeOK{Sǽb|--~hGł |:i֪٩GARB 3S.gOYQ0rMȝUo 7!>m1sO f&"J+ؙɸ)X!G=D5{f$9d ĒH*\ߎj3;O -_l>z KSf'yTO7oo~;MyyCاŠ,oC3@$"K 68'АF6M ٌwNQk[t wE2isY\'Q/\_w 7K:}5 xzfIO&KXF[@6LNm! v=GшuQq~Wɥ {"Ԇ^Tɠmb\d#σ=!$wN?>2VAw[i'L\EEW 0UcgpLH[ j{czD@/ҊNcw#wU˹P `G߭þ05v%4{)?Α"A Zܸjh$h6]y#̐gHo f&y}[x*1^[oO yp y?*>ݟ_c;zP θĶ m @7Fo،R!Xo u'4<tE]`kas G3 Ci!8IOM[n 2Q9R:aὨ ?%Nĕdl6-6>aZ-dާՇmL!z.M6/1‚fEǙ%~EGm(z9u>{K câ<]sy .jd0-8<}oi_AH&]#i>pT9A,ۜ9\I{hkCGgaYC4-\х?зi2; huyB)6, C,:5G~T'fv YBe*+ ΋\XpѠAkeno!^&Ǥ| ,wwܥY  R](xYn6+X/c$@"U P 0K,T<%eZNd?חoYrf0")VUi/ |nXT(_Eg\p)|W0e8ᦒMm5to}D`Z{4!FaQUnt-Q{tjHʢ0C$Z?Yoܣ0OnPjc+[ ۯ*P.)O8W֣@8efZ)~0%/Vpj.Sj]r 'q3.pv' OHډNz"R) ;skfa3W=V3KϢ(#1/;D;'xAV 1x|KbJƭ6STyF}md<.rhR{h5-+"qA+JesX j.V0n^sbu WȞ:*k[ݸ֭ۦv m̸k ,,/Pw_>Iؿޜi%ߙAьc%dvaIL$i:IOMLV >>1_f XxV]o6}ׯ^6BPЇbaSp%QHʮE)4W es//|zOk,J/%Zᘊ&]u{`I׈3:|P;podNZgd;U(MF\[f;¾uIq+n58K^^!).K ],hڛ"4?2@bT`e4Bis.TΈH_E6v>Xda;Q^TN:p- 'Cz+Ֆ \UyIUI`n{j7^أhXsDjt/tu^9E_.:yeN3<³0= պ<3 Z)~3 ޸j^BYmK.i]*D6>A:R<0^w=̶o0a;WT-|OnZnklO# ݂ {{y xNiѣ>[}!'I?j40Ɵ   mڀ-8([r9#KpFT7q\g$P|26JF\?'[ZmhKko]yf|l+Cgdz޿^EYp9rt7oʀZ:#Վ2l#DKӑ@fJp [~x{69a8)8Bʰ'׌ldm.- ^:ex[[~`Hڕ׀'vh?- #r$NMr虡v_̅T(vlkŝ9s;g?%L/E,G )͛ڬ__eG`{Y&^h1$W2ۥ6`H$R-X\:&IWŜĤ SR;5E"S\d$gZZc$[bNxZZe*u۷4^p~Rf9=g^!22~'6<6sou]& vWu }Ň'NmXȆ[\]_6O'R[4f+ʨ9(sT?f-22_bJ.^bWJLFoR}rpɺk^k:t&t'˷ygML9OH\PM3]-™XF!;Qn-6%0rgηE戟o#Eh׬΍gT#)RCd Y> +%=[$'OlH[A*e U|GyMa+-h1;VúlNNbV(eBZr z04Qw)/*!kɠ/z3f<=<Lu:>phř(zƗG>V/1CO;φg?SF㿭aX8W3fQ}\N|* N@y% o| XpAY}i⧢}2 Q| xPNhnh,VXldKeD{whFł 5sZu5kMXTk=^6a>Ge!K! [&rٓD4Z)Vrc=xx#YQМ*H=eL22g.J&0h_'}%sIEqFXǎX#anGē {=pᎲ}T\V B6JOW_'2SAW8zYŅߣH\uW2{hBP#O)bgMknj; M#KJ륟ZRN-a^wPEh;#<0GlADj]lMX OւݑS0ޛĻA|;=gImPVH el:<"gNW(afSV2=DӋO> Mt!F3c .B*:eM9~vT:}͝#0>od9SPE`ug{ٲ̖LrvqDWAk܂T o>pn^Xw)חڤV^nFYȧ Rܭ`.:/_+$$԰(O2QQP&2,?Mן-nqDo'8} B m[vlUZo弄i?^d8kki ńfCWEc6~^fDˬn[k1ܴҁ=(iBڹT&~ʼrY ,2{mf<ů&(V^&=T/X,{u)hTd<M_@  X`4txYKo6W>HE(6MCX`IY,$R%){3m9rl[8̓>};I]eW.%, 4+]rltXFWZZ+zNjE`A!.H\Ԫcwhneu %}$TlD\g|i/]WP830.imS3<pJ髌M7S6u@kdv%`ta*@tn:rRk:(2HZy`ƞ:ԁKԈVݍ%J[>u)"p[!Ա!a@A_|l9)٭:#RKQh::'>07 M qe\5-*BNLjjA~ԋ{7wVղW̺;PL60w&.D f$5M˘6F;3tme&4G⇂WUiR BWhGXCJ y /+Dg0/({#83~-DrSoa L({TGQ<6h^*0WjKmD<|}Edɑ<(ĖLoJrSu fQ0CISy{U7WKxol])3'Zkik6\o2ӫ[FfrֆS6&+JJZW_vf۫jI㫯R+X(2^2cUjj SVK oj7kn{~tk?= d=g!ʖVF՚EUFLHZbm2_װB>T,nI]25- <i, u|Vl峺 #Ka)ЃzPYQOJ q9rF;W *`Y!UtYSt7yTe|p)A #(т@*d;!wfY_bFH*qI؁bRfrI%ǔ*)z傊G`tDR'93-2gL}ZIT*t1X'[fĘjQ yeFlÑˌ*{X0-,'f24CE ωoP#wfGJ?YMtRPn .;  a(9] & 3j(t8P\(d%7cLegysC'9CZ1ggVgnpT74l_@cOxάhỳ7),M0ufݫy  P@@N0>Rn~xF V ?t%$0! Vꦨm5 wE2e{Y΂:Nv@a_p:Hm .i>f,C\ E]ϻ (k[~11aa_?Br)0,.*yDZWw"vP:2' ?ۑ#!ȼsd +Hz?1 qњ+H獵ܾndtǰ^ B&~iĈm sxD޺Ά9twS%\V(:`@п7JTũt[Qd+!;M θ6?u6ඁ+oElK,7K^!vrSevL t!·fbo1B*Ex\?l,hs'S 'F0' ?h/E]I!-qRdfѭH2}r) ے"d{ 07W1Іy)6ٜh/ ubW xG*xϩ3׋\/l '& tUدlsAX-@o#O%(~mP;c BfaƑpUzu$MEG qKYd[:{IwODzh 1lSW+6)6 ,:F|dGi Y@e*" m3YX~AwjAa1w.[cR xx]t+[e  3'eR xV]8}P/-LcvaLw`K mYik[[r%9i},;;)$#{R/ծm( JZ'{Nj&A J(Yݠ`߲3Ro&>)gi=_`7J VT育fĥWV!Dx9wtx.X0#6<huPޱ@ acB+]t:n2G^1Z5+ #;UU0ۮ!Vp%eR,NZ@׽= vvun[=-LPeZID)n"`R&F!$,r}gA" "VV-Rk 1-y;i& q'- z_=؝tYECz+U"ly^)K@n'V+t5+i̖7=H4Zi]2W3p q`/WzWet#?_]Ɓ`uqə R1:cozrV)j4}>QyxCu,%v'lw=Lշ0,]fN%:5:tAZV5Z֐^R*9XEEKF0=1$j 'xCJ11; u\evw.07x0wr XN=6Tĵs}$6; ;x7TLEtDP C 3jeɸܲv3pXlǘxNGU>QylY)AYe/זj|nmK|f]D;y|.h>\sf-**$\nq1ޤ`h I a[fSBN@ aDnIYӋd"xZn6}W NBy"i7?-\b# Iΐ.kI^$j4sg.$W{ww$3E4Jss"q#LΗ럸*y^hĪ6Bn2t;(u+vRj7?Հ|PR҂,&S^,SP !n2LR2^4Z,z1lZVIrCj\-3wN==Obg%^`'k墮*. cb;#ՒEUNLIZuTm9a}nuڛg'kASFRF[8`5F6lJF]oi^S*g+h`;3繨O/QԪtё4BIDCw& ֆxzsZfXrՆo$܈旄,+B; #lμb# EXC?l6UӉ<7r(#J̠j.>ִ4"M"BrdDF3 6UH@~Tǣ#j| d+KƒL'i %@7 ,GT+8-C+9ӇߟUBW`@P,9)6+* m.>ʞY ~}Qהl%He̵}֟߳kx qzp>`wr)SchbueP#W 4 "?2^9g]SpOCx D]p J#T]} Z4gd{s/{ \f'8F^d+;\] J%7uG>{kƑs@p%n6ZeߗgNC>lڰR4O#Cm`ީb B,H@t0e $>/z:avPÊ}/F98@{' }$#k70Z/a`B9MN!e4(G#ȳ> 9:=ڶh ,hכ\DGq7 C%Ec|xX[4~ϯ0}`_zю42aVbxX}pƬcۙ-I]Ҧ9mӯ?|x#il+,Oo@+|kR Yo7[nV'7V\Ih hT[K-jqf5P%) /(9+uNE{Prel3UA7 j6SR[š V=y=%իi\(׹k5YcDM^Yz jܝUKB^n\Y#z %vYa$W@!ݬ)bZlJUR|Y  zQ2R4.h,uVҫH[*DTgY:rP0JtTk$^ PYHEeT)Xkقɍ𤋮6doƐ$Sz,B+1*(RS^r̎qA[uJ+7iw(vqĝ*"=”˄RP?(8HEXl bA=ReHPӲ"geǖy y뷗?HjlNcsK K6>J\s+lƷV c{W- % |Spm7ᦒ nRt=g Is:6Pr{:J c 6L˨1ĬNסHmX^+^$Y/b:p5|YgV;ݪD f%w(PJ⇐;bSNڠJs(I=&J¨$1nSN17A{ҠXYeQ̹k(tNrA,i8X+g/JWPiAMOfi$2ݜay*MLH5t.r 5yMi5k%٠|-󗚇f"=؝#H&d"3 iU4a҂IŦIs8}~: sjW+7[x5=a96%2x8'”Thx/#7L`G0OAJI$G7` x.rfiV: b $\ +E=d!6 E!Hjl%TS\&ʫ.e)H+ =FJ1!]vqkzz> m)/#UʘU@ q(:~udUr'ujGSͨgQ,JuT42P5Nod ,q{PRR Nmj}şՂJ63w ѵ> [+I\O)|1hШ¸fmDw$57Sx{.A,J^Ǔڐq\PZJ)9),&SCa1P`1tMG%x@0\3J!'lrD =JBNN<5Ơ\ʂ<Ϋ1tkLlCL~r-xStg9B`*zXZ~bJkn #b3/V=Q)˭ϲ(#˼iYK/9+[, CJa-wzKfK͇5QK :ͽWb8z c{{LoqA!`qiN{Ұb4\6rVn5'VlRu7H^շITvjB̺,[WPJovJ9K񩼒9ǭ?f4 NjM6hʔuZ\hgj+[-qߺO" HHjZ\H_u?6C'4s ! iMd7*T*B?%iSL: p\Jy o$W\l]4?98T Ɇ#I[{&˄5dW<>i(P λlZJHr)/=ΑM(:u0=A/t.}nVAjURo7.Ķx8-=jx":3w#@ <-`8[uqj,%4⥈1a!GpqX_ qh"oY g9x[m۸_.pMpҫc MzEkAph^D>CR%K>^IR>gwӿ?9LUY'SWپxss#LוF$Y&^-h726 Bx#Zd{5:IWՒW:|e)*)_q3^m4ՎuZG6^m֖f?wJnj,|9Y>eLJˢ M.N8 }i%v2~Ň쳌'NmXnۈ\}_6O'Q;4f+ʨǹQx+njU:I4P! m:,Y$榓P>݇!-t5P+G0Dq\/5+y܆r#̸yO⋚_%Bh a`ɕ5b554@褄=?!d\H /"#z y㶆 %S*f{>هx(<,yk$x 66i+VK(}v H?,NЊ#zH%!U{W_2*>ӾGqP3:Ѡ1;UB-mxSB[ 5PAPAϔnQ NVE*d-(/ǂ^w mΜ)T){;י# j\wL:Ibp 6۹4܍7l<dp&97^P"5ȣ!ˏw!sO%صϏ1;@?q&=|As;<F6+~'e]\=li5xU9k+_߄ |sx:Q;n^7580hˮ\7i]/BՃC [AnF 4s4&Onū k2㓳ƍn:GnMߌOi}7yy/5%<ySlСZaQ"6Y# naiH#4B=RUFefM\d -6#ˆM@}"AdkVpˢą.٠sXF[ Y~&c6n}7d>EՒ WHɜ₎^Awpft0yjZE!1r#_=E杳O`ϜWh`ZyWwk^.LaۢAwuk˶;j4-_o?r1w2Q|;8gwaz鷐3n3[Pp;z@dmnuҺqM"v D&hF$kNs5[/Je1rK/~ï!7q53"`cK*KQ^rt=w ;g\&6uS$c#[6%,yoE47i#4Sez(+7v䟊#5k!m,tߍ1vȔ6\s'FϠ*g T c.4>Vv\ّiH)}v)P-~A.xs{ )4]3%Ig8Da6Y_ӆ]v'̋\넄ABˎR2ˉKx҄OmzOl;8̚z츹3*HEXl @ РijdH)i]؋-Ko͋r߿F2S(n&UeTi0Oʤ_f oaY*]sp*å e8xQ/ⅇ]dk-G2墘A xnj+&vYL&r,ճ ccī[e=rI*L bQMVpF>h^E54 =bf4^ȡ̀߹X)<O"h$}_UV! e vH?EVs"c|- od;t*ineyaB,JpDݨ i xDǫOt,wYvwD!2]x߹GnI%TDxmvN,DDAiLn/xΥȷ*5ly]bI9Jѭ&(Ïl"L ChkҎz$>fTdD@C\Jt&!кx.D߮Cq-v%z#uմX^vԹN5X89ۉCG!DV[?X l 7*sB]US*B'q@c[کBw>2Fƨ-]}$eh/\i3ꕬDB&"yqUGCgc: K`#k&sU{ ]=ˆ8_/9]7pYpzѵзm:;(!/?EslqiH_An%tW[{i R|v~OUgfYgQc-3k)0d|% ς1$jeƵ !< u6Edf 9\_/Ƚ]UnZt>XK%P}l`V•oSl=sݬUID Crc5 BW|ta :;(y{/vg''Rk嘝ّʹۑ77GYjnoܵmk~bR)Хm;14Ŕ:,z(m.:Ὴ2Ϙ$K[][e+nKհ&{Y_< j`<릴ʍ; EOR}b{5uo0oL#XmOvR4RI=Nsږ{jQ^n cck. xV]k8}Pݗ-4cZ,1BeS[r%y=GS{{Ͻ~Iw\=k\I>= <:rU 7U:4ú7|[Mw:#I,J$y6"Z\xjyysɪ[{6]'J،Ys@i@ W x)[X 0]~m{nriyˢ #صL׬qsdKY6aledU[b#˵6l>9 6PX`pI5}-%kFv!Ib7R`눫i7E7Fh$QmG EJY%lہ운!#2Ye;l=1o˧ (=$iRmF-AUSЮ'a?V˟u6V:~Gk)Йr(sq`22yex!JSƯ #BFt6HQEj3ťЏՂ}iCHO Fބ~) nC Z(/PmЗ*N eG1P ͖sf\bIΓSLV5g C:f pk`sg[Sz!ǜ5ꫴqo^Fme(w+VSo"cCmGX6.%lvpY8!6>.sQ<7]$͔#]:-ƗkKxyv?ĶQ"j圶t8.m4VQTog73oJT8oP!q|b $EiyM `u'O&L*53.aj:x[ێ۸Slmrw,ǐFπ+ʨ90sg2vZc)WSOq%3]_Z\CXG^!;-:s2'|ǟBSeunSfK ;V/*!+Ͽx <=)ΟETgE<2؉cD51꤉3NԎR{.lcM[@OYM8zg=o ̓}j{o0sOUQ\Eq !&P@|l8BO? OO!՞(*A -p(^u6"cJPON5Ֆ yY[e Aq @0<AEĨ¿Tik,VCS-6%uG{}8b Pr*j֊š8a=${=g!mz[O+΅@3@ȭeϮfV *_37 $_73DόسXF$#qtFke4-=/(uGBM֨&,b lME1ld QZ>n8.Zֺ[羪Ԗ)$6:p GEDnSUPF,/:Ao//CH﹊pB8:ZUC'@f¥aUќ D!h 'Jn/%.CL{󋯻ɓβ.]= yI8pU9k_ +ivu7v1M߼;sЄc;9G4Sq%d_r8-UR<0Wli}#p[>3㓶Fzjԃɼ̷CCyZ_]K Wb OlhCTY6JU;VH, "z}@6#7{KQkmw絛E4]{Y]B&]PXR0Bފ3p:HT6,u$‘*! E]O g2f}ڭo11]a7g2{ݢ;Z DދڱV{QnՑ5A潓OGv9P@t{;<ː{)nZS9+FM aiq[0à yDhq of@ Hl *Y4©.:c8g.mf\L!7KF68^qǍԖZ*][䧾1)x[¿Hd޼8T ?.>>K )Q)|qkDjjS_ȵ{F/M^*ayS"gN'abTMOg| MxAG3s .B*6tX?l*eas'; πm-' 2y]Y+i֬l{%e@`hR ۢy%mBh~M:7a"z{=?m]\JQsz;~-p֥qRҴQUj3?JMW-Xn'*n?6xPw䊑ެYR0nES  Wvq6vw9d'<6(XߟۈK<5]r# 3|Srwv _`H`l6 L:bw-_ng]C-Ӄ. {<% BmإߡKP l  jlXxYKo6W>HE(6 ijdH)iYRŖE9O_JRgQ_@cPR4Ui۟Vko *&Bf/\7R|T2tpA72>%#mA- ZAH9;JO咔 @ :V4:&PgݐAf<]!6Fg׹$CC3YE 7IWÏ#02B ;L+MX PJErV'qc{/ͅ$v4BeЎVJt*ٳnE(uVJW!>験1xb5]%ͥbj6i0h&gk}mc#.kd< :GEUdtQtmSǖĽј_sIPͪ-+mAqx񼑥7рq(:gA|gm:' Ygu@Me<޴' yqQd``#5ۊZ;qx8SڧUduZ;(!/=h7e 64xvх$k.MIpT Q̸eExdӭ,>u ó vݸŻqAHqO6df N9ɅZW/]'efY9$XKP}k1)0gR¿uolЎnCrC|cWfa 7;(yl{ӯv_ Sk]%qu<ݙ,7no6Y5ib~bR)ЅmGUfzZT\pEi^0ifvZ=3 )5 c,{ۼ"(_2S*Df8B_Y<^ixy oȒ}z=lUqs;K3Օ.EbpKq ò5VWj,㭲U_/Ɔs0cZfƝKp@B'&}b-u{o0cL#X ;){iI2#NwGi]/"p ==8nt#xUQk0~y/Fq `б=Y>ZmH8u6v{HlKw}Truhʬ(k~HT+GZ恚wua98[-n*X_kONW5MB*[o#ғ$6_I FOXe!d~{gEӫ,, P~7>?VT*:bMC usKIJwP0VPт݆HJ'.?G02=%Y@Yy׃m`#tABϖ]5zX0ot <1eZ-9ZAqEș!6&tݠ+D|MŻa/2XA:MmU^;?ktz ʡ$W'k&$7J7ǒZv=͊lGhM0jäPw( O҇Wy%)\>\6eM(e'mUm}*pz=uhT nXdLin^fύ09_WJnR6Jlj#dLzt+S߬a[DP–h$^WKF\^+KIԬWLzijP!^ZW%}ofmnsnV޹o߯2{)e*yYdZZT,dde8qh+rK?F6Fexr-ugy Ocf,1z2G%}\T\"+A맮;!ӑȤL/Ӿȍ\%;<SHiL˫=k2Ĥ݅X',gA{B$Whj:bW? #w|]dN:?Q|x/_H<"5$Nv%YcȊ_MXBٳGj@raqؽm mRw6N? |XOaAh DZ T lió9:Y?)EPp[# _:hV52'a!K! {&rٳqLZ!Zrg!=xx#YQ*H=eL22g.J 0h_'}%,mJvF5ueSyN~o1pԂ` vQY!n:.ZֺG硫Ԗ=cA*;wH '# UQ\Msqw~{ttyAU$c$Z?0~;b%O~)eCTqV B6πJngSȟrO~+0,ⲰQkYS=6M̷''?3i5BA;%zϭ&Q{zp0q| TThgf@}"p-&,t]}e'iuB&nߌi};̾yy/5҈%<ySlСZa" YkqN^Hy6ݢhl,U&2ol ~ہ0"0b't( !)mY&e>r貨[.٠wXF[ Y~&c6n];t>Eݒ  89]{=%oj/`^N݉B|u3宏߽큆(2}:y۱ޏ5q|[tہ6Naߢuc̦;j4̯(`A4uCw)2Q|;z8o)aϐ54gPwڠ9)#csfHB2Er*׹HFG$N13z0Je1sk/`o9c \vd7GdWڣ/ I;zxLg4q5߯C %@Ma+TeF9]}K7*CY45 jnRQ 4S0 r-^#'-w7Asm31ޕ3j E_u^~J&+;lɄfMH*}v)P-Ak%ۥUjΫtXx2 e6^BJo{zp R):0W(ZrӞ$ڸ.7Cò4rkѶJo<#zVJr)m/ >a)0 E5Ixd vxF°iGr=WV4 ҃ !+t\t57ebaȼ $. [-m˳~*yrpNf؀IjԦiwdPnL,9Sq3X|t""s1nvm.8":me^=CX[ʽ;)3::3z_V=UvΧLu/k$k3! x\qG aP,+0荑3CioF Ap" xZ[o6~ׯ lI'Z (ZnÀ]l=P"mqH~bIhcC"9<;C:/>HarS[Jjͤ7֭%Ze?e#B0VBI3bfzdbkrn,E- $3J$d`u,(Lgb|>+^Lf2-׳̲ɥzbzIi~goNES\_0@2WUXNlITM [qºZ42w.5Q/T Qf#GcF?&.h&Ja}l*6k-j9#:f|Lz2ȿ*xYݰycGEJ^qiAdK'i9MgT$>SMVZl\nk˗\OI"4!T2W7hn&Tk69,O!6FfdyM,1YޛCB8$L-dq0lPiEɥK3D$b92:dڎ3Ώzlp~ԧM݋MF08^;k*Gc ׏XGTt$ƓUד']1(}<$cS|5v"\=ק#g;X~"oyL&ȺTvlӣUN,"8Jo7t9ΠE/6h0n|X?(Pÿm1\|3ul/C`bj䣌 K˄?r/lBWKP ̜eKCJYRﱤWazē!j`2כWSxg^kQQ~ Pf͗Yf-4o4KyOɱf},Av&-:t < %˻a %{`Gk`tM1E?v8N"|/p;z%CCR=_bĪ;/8kیIٛ)3ф@DV}JufyX)`r M ,%n"Pc5Nܖ\X61M ~-@Жd|}2#XJ94ҡ v$jRӺ@UEB$8#6_ƒbЄ~B|j)))1^Xg P&M]ش/n7ͭmU.X&sF#F82U"<2C@\ItkpK!߅#ݎ  Nmf:Tthl%t3?P7KC6݆bDW$?J)kBwVPmϼ GV 0cUg37{tt@prQeSvB%6mg&6v $Y`qi6E8-1쎨xƲ(OSL7oVP`*X $,-H@8e?քGPQF2Ɵϖ"{+[ OtDmuW&n8dkPYyJnWPf\pa&{4;mdi"oՍ\s Bl 6eafJ f3+ ǸpEsD0l#;ـ܈XFcY!MPN'&GQe<ʲ RdWN7-7)0B%cU*!\t#ex6LSŋjTp3#t*d y(:[}uj[bWI s7džs.8X7b͜J+M`[/]ucgẈ*%'aBʺkOQvje\[<88f&&H}@3=o\񁃡+M//&tz. @c5+*nIxZ[#]wqedе/߇EOBz㧨AimS\X@S}'ݾ/?Zg n n"tH xVn6}WL՗HVhE CIQɠъH$}EiWZ9sf}zI>?Xx(VX6|4ú㢷!V(A:oe9xitE$&S{G 䂴P|K<K`7@E^ݦKWN%#Br uFf{d/\ tn(&():yqh]G^X̳uoHXPͮX59Hoc,$z> >|_PpK=oO{L!釚 O#RYAE v` U) o>xQs#5=V| ;a 5YxhUzIQQlM]ѓ؃ ([ =dAc-S58pM4t5 H# I ִh$(x+CQTW@?2e-.{NJfF/3~*2΄՚ R1z^X;cXw`t/Y7'k!x!!iisQ pv?nAEhf NJmCk+;ScOOrl+LBb<f7_L㢏 i \+1AV M%cb`Nrs]6@h0->q|-V@Gdnūzs]2sh%xZ[D~"iJU[@<PabO؝g~=ߙ/zR~;g&]|盷~r[d/gr\ag]?9㹕ZWJUoZ++~62bdlK\-^yŦKɂ3Kq=;UgŜZJK!+.i"҂s=[Qb^®btbWK.ܫ/V?֌Y5w* fsҦl p#X׺Qw_Oiyyס}RX1(c՚>&O|% iC۪՟"A\']lxp+2ƭF+U29jg( Y>^m#(p!q磼Il%Yլ >z+͸x+fv[ +;dExj%ךo . EvUP& \VW㆘ʹAU+w.!C0VTPĒ컆++ vܓ-'@r@ TˈK~wz2>3SIb<"Ƚv\ݹYJx!rB9%jj.i홒Y垞MzLrB/j!ɇV?NҼIIx9R-x{}>q[0ۇn~3kעg6Mbs D =x~&A9R-0ER p&h`R8Rp]J;Qb&а$ًy&:xx !\v#c8HI:Š:a,vd? ^9.Y%%Y[:?M7 !ԧ!4M@,=SmĞΧX,Jdg.*3J2rhlx͗Mo0 \;1ZйuvXC1`GYfb dHr׏ϴR`$R4bwߞ?}yD xƴA{"VRdʭ,jTV( Dm(˃(ܚMTZM.cc%6G b Y9D q2լcvE%qUq]ϙ1vz^C y 5}f2rU`NDKxi=(+VxAn+aE7 7*La'6WkHm !ֲ@i[BrPHËZ~(vVC:\=\1nQL\jdbL<}PRL]跦P2OPOHt^0Thq:iay;@^1Si]0K씺\=ՍTgu{@EGq3-ˏV/QE ֬vf%f`#l!zp%-זtACõ(ͣQt<.)m!{wLr|z;m!j|G'uIޞv=켉q}_7Pj5ZN0Q~Js[|j-hkǹ1nQl {?)iA  MukxYn6+X/Jb$@"U& Р,)JJRxeY@7m9A;iǗ ̄Ly +ƈdX*WNoYstNR `dqTjFUvAm"9X# ͳJA}^T+Up:J #': HhfXUV|hƑ1Wgb=0 ᒢK^S OJB>oiHcv8f"skVM|(Bs JYEwZ}ǩ1m3 8z QBBtga%ģwR)iPேK΂h ZǦ{8f9nRϠvh!-JXz:l[  GT{xmÂRht(&'A!"ƌ7"m"vd2;g~͎S {%.7X߿L#85P:!T*t-b ?t)6K 0o%DGF0O"\̔Yqg+ ?FJQ)!]&(!#WC)-vb̾0/6{ s,*`y <ʲ#|f43o} Ե0{|f"SƉ&HՑ_U=Ӵz5N/tŖ*׿uH (q@P\,V7a0';#zט]Ӹ[o5TAC&?=8}s48\_vGb87 ouɞ=cĬA۹yM&1(zO)?$˳?/P-ͩ*7\G B)&Rb MTwc l4lCJٷ1Ս) QG0T&7#G8dfZIS)Tj:Qd陝q]'>cRwv$tæ$f~5ޤLXKqUOUrK+/+74#yfET)ȥCm޷ _]I:lam2D=*N΢Fn_>A0ӻ~= 2B:KkG!fGs#4B­-l`n69&EM T\֭R.í5UEJZ $ɜ b)}2DUI YNRf$I LֆMX׿oJ8 Zw8 xV]o6}ഗh,l@Pa[PԕE"U~8Ϳߡ(RM{׹,{ǿoﻒDGO^p _e7?e95w vNi+ѽW[U.$Ȋ|L(J hBp-zʎgehS+vBʢ]eV)GeI6ɮs=ю, #ÑRcц=!clToO:TVFvt>jr3L h׳w+[ި/۞|'J/阙OFbxO,2G\pl+*<ӈi%9PZSSڠ4l:i'mq$neBtjdqd!6G FRMHǑZm͖ut%8&9򰽥e ¡uYl6|'JprUE; i \b4&*|T хYZjsқu.6n/ s7իch#z BF:gpwˊa蓼f D٢TI#9ed8_[]|a=VIc{Ayn))K 6Krܔhtϛ W g~#ZϹgҿ8pL<]{̖v 9x[m۸_.pMpҫc MzEAphHBR>Y^Hⵖ3Cexg*|pIƔUm/\-u'X!>mF2;7LjF'=Yk D ъ%+۫IZxց+,K鸩Y*{'9V_M!^ZW%}ofminsnV޹o߯2{ *yQDZZT,d/^_9z}!,ʼnF[q,[16"/'Ɠk=kfx3cwesTg]MԨrϑfx8ɍ[ŤWKJ#r?@7u_|Q⋺WW8 WP*WYUIvVJ8!^N`Y9˄6,6tD7"Z,9f !=R1Rl>ZDeγX)lV].wi9˫=k"Ĥ݅X',gAyBdWhj:bW> #s|ϝgN:?|x/_"H,"4$*„_&ic_ XBѳGh@raqNؽP m Rw6N{4ȟ>B$Ah D\ T*liݳNSp[#e?uЪ0k8g C(u+z*B cA4W͙#2^:sB7Z&u;Ix :k aK3XP{ʣAIgs:9u#RDuX~{x - Gg?>_,k %qp@6^~|'>MV Ak:QTRc֋ 6@i  iu(UͳcyFPI{-B#tDt. 3HȭgG3+rudk˝Ud!GCSU83Y.i:sQ:ߠ5 JpI!ELI;žQ ~P& pGe֊#(kY&bS[4֏Hqs" * ܖ(.a)^t^c!]\W>rIDqFǎ«nGēA!3{pl*L9=P&["vQ_@p 2uW/n~e]\=le5xU9k2_9Cs! ZDObZy v}k羠Iv$shvsIɽܡ!L#oGz'/hL`H-\քnY0+1C !!;o7/obFchC XDP (E5B#Eڠ1TƉѫ@[TwE4WsY<& \wg;pFFt'$$1 /W9e ESuwʖLdv^ qh^ $p_ڗFv).ڤr]"n4Y翍 WRڽ`3/&^+$$Ը(OQhّJf1,%Mג-᩼mCmcg} ˶B mXvob YoXoi+١Y6M8TRRܚ]&Eqx#fR|y.v`ﯱm~%t!Iܡ0d8çvWi箘HNd=L1d$S׽N QXwwͭ6U7ДD;I "0p0 Q9a9D|P"8I [ ^H|+2&M5na^Yxmks #!O9oCAQ@{2@ /顉! Ψ t(Mrژ#CԻv<(~Mnk)ha٠XҳI.@u3odB. ۲:d7 25qZ_t90p7hl>˚$gQɯeP@"vPÎ2[Ja uR~7.T޼ َ%홙F5He {7v3q ] G#W֏K.;iKDKt62Il٤&հd=lW{`Ș DDf0<\L.*fKuaˑۊ#wtsshb\̕6Rթ!CYh|q'HCF{YQGjRXD&2k/‹E֑[m=t{NɶĻ8>sO0[c !.P#`N Єߔ/(Nw$_tLٮ,oNRMaF3+SqM[JW1_'NR :k=}V^F6j]"&>y2cL _W/RB"vDC̍aD A/cM<:${w&F҆7)dc΢+kQI;Q1΂).˿JxkLYI`}}u}S|g1 깽٤ BVCJ1N4=\AcicY j^󭥖h\1Bz9 q,SV4º7r"naPv YdQ{eB{I-."nS{8RY\ɬ[[9ʣư+BvT^z@կؕlUZ 0cRƝ}p`eYcaLbh9L!Ҩ`gGi:H  -Aʰ [ S] ]]z( xV]o6}ׯ^6BP ЇmڢS@QWHʉEɶȩb$Ϲ~O-| u:iy|EoMNVWFY\Nl)Mo V%[Zc_ ҢteWZZW4ysE]KrrϚ+ލ}A1#J*No%tU9Q*a\H @y 갭Mb'"'eZY.}nx޽K9@Wz0[oܾT$  L hwݐg\cF(Z$,'2ՖXrԓt>5Q4WTjC4b @ém'eȠ *-" !iYx,]xתn2&;7 ʇ}3a=h9L#,iA|CzD.D;;I&+|q< (/˲H`szΝ'~j9G!QS1yO6H;8Jz1aƃ.:PAc#Шmɲ48BpJClCOk~P "B+#@C/г "?9vn :`4v4ϕD(E߄G5woAݫ,bŕt1$a FoO [gp'eTBGauuՊG߄)_1q59IvoG|괘E4vl[]E4/Ǖ=bRl9{HB$҈/8;nzJӺ<+a"{ʏ1œ:p P+_) y;xm۶~w@pι1P`Ca(h %*$;y"QH>ߵ$%;~ M!*|0G %iNfUm/\-FugpY3LnCZzZخ Owd@ؙ()i^}qum֫%EL8`yzʌWk ۫2jOպZ-%ެsS~[[O߳zI"miT{YTa䌤uQ Kbn2_oaw}`/^_9x.@$Q-9-~M Yj<:SQS2BIs`$̙iLJG;2"XJ,$=vMeg%f/o^;ܠUr Nn>˼[}WSMK#ϭ: -gKr;to/K~%uZ7&p{ =32gL}BITjt1X'[gĘja yeFlˌ*{oX0--f24CE ɸB 2{/芥|ˡ@tv"CPQbT[4M0;w@n{,Ӿ, n`i#UT *]Uuoe&{u曫uOp5o^5,ݢqǂκ]GNT9}x;S*rvWXX;5 (+˝ݡ)؞95!B30Mr5$dÿv$NI6x55rCIʄ{R`"ݗB냘 \x9x'?4| 0hc/d^,@X d{8.0KqX j QCY uǡ!+/z ,,;?uTt: `v΀:k~wV gRvEtfP;ߐ'M %MnDTX+gZi|܃۴u^{t` :Ğra-{r}5XBe%srg[ i2^%NMٳX$Eq ^:e54Z |^`GB(g;[ $<؎89ig &[ Ҫq'v q^ֲM:UEǶ$M]h@$!nMog V'8=kp 2MU )AMS^b_@!*! Z(P;ڡ[jW݃@&"¥~UҖB6|'H__]*;{.Tcҟ6W_e]@g>F H.eDz8n6cנ.ɩQ#VL -zKnk)Ӆpk>j@ Σ1rԍ^ֻ0l0bvP[,:+$òr ,}p}+t.!˛ؤgȯ7",V a it&E 1ی`.m"/@{d(^؇@Q[y;#01ҩT~tq >_o\TpvAr45U,.+zP\?ZB_SĞBo$6uFXБm'RMZ^]h%8TKpp;ʄX5PbY03Jfhı,t s:.\EGx7o1fղI*jgT =F. SGz56l) Zcz{Y&ִݦCc ,gMr]_+Yr{ZtmJ"fſ7f>0 )4lDTa\6*`,-L3 ]{ D*m]/ڐ3sdLLPxHΰ=rR (,:bnl0(6xYUp (߻Wۓ% gzgxjAC?U}س~g^h|rm|Srg Tށ>l*aΩfFMH. Qp D?4P$Kfc-dl-z *5$1ٵ /)6DfwP^ixѣ.riYzaBO{~^T=¹jaZmBvlm0oƒn .3.0<<%b̦#}v`d.QOlZ/#9OGrl*U }V Q}& PxVak0_qlP5v Tdk%!$ǩnM3h*b{wIJ7}W$UE:4s/}Dž\Hx-[Y^jgc\g[Rכp^xʝa\=_"D"(r틼5y{FU'HUϠ,$319LA;(uP=#"]/58KQp$cJs6gnxnRr=I6Jk#dLzpˢ“[tGoW0-"?_$ђ%+뛑$ܬ F_F@tԬLjifoƶ/L>r׫U~7s+ܷ e?X*xAZXT,dN0Xr :7H\dRW2e_FI E^Op+1~%i *z~!\q%܈/&:H5nN+R=:p*fe[kY)SN $kxLԜ97Ƚ0YT8'b#-ȫN@q *&FuWS0a?S\Wp'sg[F@}[Kr|ՅKC>J(pdon_}wSraWÚ.ݢq?%IM^eI!&.:a9SR'8Ed7g0&/V7Nwvw%02gηy戝rДoX9RT )"CܝqLCP|A 4ab  Zlb;0/JhCr.@p(AuI{ e2q2XW_ MMO-65bw8/VmH1SfZA }{ЈRUd8ܯy3<=VNTטz̉`])V 8j\)Ʋ, Z}Fɟssʍ@axqctb1lDl8j4>Q1SN^CR5LhvН92Op߉MB N 8by9\J9_;*j Viz"ga /ìI*F)X"1r$K%{{`oG!sZ5mzyr,E5uo6ydcң`T"–:d$4"K'@_\YxH\x954\#3:2 i3W Z>k<QMJŔ@F-ʅg@_{,U[ࢬekt>Fw#wȠǍX|@F$V(5:$p*QQ\M;$oWB$@K }* PWKC'>& {•}0CIB6YOJnEʋ#ƀ}rO~*pW,Q$kY=NAoLo_KĎE`]vMk@u륟>NBp`7,Eh;#Fom3u/ki=K_@Rd Pv)uޒ93Beu(r"л*$k"EQs,Je1r J^ &LZN7!Ǖͥ^ԣPo ;zK o֤ۜg4Ru!5AF_(aySnCLʜn̑ô=$!ʹLe&xSQ@4s!B**$!T??lq(Xs/;WQNH$yy]+i2,%:9V~Jm 2]W7.0>QtBVêSFx;t0zQ(z <*ϵBBI/1  dmh6lɎi zOl:YkcK$Ov},{n&Y9])ߵEXTױ$at_ ^ޚrwosFT[U^gq=ɳ41T8/<*6\ߤ["D`%XL*T""oI¹vJQ^@U^$Țk!<&(UL50ܻ4|(q[ĸrZ񊣽?MCcFxtgׂdTxOGafDP$$_xZo6~_I(Pt-݆ݖ`0쁖舛$$Ierl%^q=KmͣY!x\Xk#դˋ)ƭZ-.oŽ4,辰Z.*+U~6UMh6 r*YWscwpyX8+x.&-d>rQ4^cvBH|DggܘLV\ٔ>-Nb^?%o~{9zu ~AcSwVgQ9g*/++Mו`I7~˪Ë?ꒌڦ\xy>|`?w,heL-1r IcSElVXV$['!WQOsm^_"d~2-l D.  s>˚oΠ5SfLɑ&Ok]˽D/w' eP[v Y$w+^X BX$K=\GŕT=-q.w|fGwa#j߱a/ĕ0y7^' e =$䍞4x>Zt?'$ 3'݇GYf"-j-oMLQQwJۧ <\d笴Ye )QD*L7F>)t(7c?-CHrL-G[y8e)G +-%bMILKe@e^#K8B.w[U .y;xNG8dpxEJG >IaثKV"Y;ui6[ն[fW\B5S{XjHUүxZSw+V$ *kOn>v?BhdViƠ|=oAюͻ01bDJhEI4=dBXdK@9T?j"s1zujQ1- H ;b`'WΖ2qg`$>vVLv0$j 0~9&gFn uI_B2Լ; ~Rh)DYf,Bub%I.Z@xЭ0gFT(3c"[9axm7]J{#;xpd"-sҨp} u]*EZS#7&+W,Чy*shmxdu[䐾J&3\D:(X pV6R|&Q=fqTYDwm~p) mcOn":YLO5a iu#Id>~@x͘Kk@SzJlTBIsWE+;޶rҋ_ݑO?Bl$ YDo)Z5%4B+Wt$T3-Fȸ]5lK+F9rLqQ=(RlۅHق%0b%SvZnu (3P];u0r5LdĴ Bzo}xB5eo{zHY(AQwSsۄɐ51)N"q7x_MXba{ϚvK%Ji"Ŗ5>` g Ӵ,by%lKT&;Y5mql@sFl ʣcӚC l t2!rd @h(3ҏcf`b:K!ڭnsП(ޥQ (ڶH#UuzZU{fb_ͨTԽ Vӑ@Tgj,F#uΓ%gcwJkY7V!WB_Un̊睞c{!GڐCˈ-=yn8ϲH}KyBˍV $*:VhL 1J븝^j;Hت]{=bk0Q+Ab=4 L{HCj@?xtF] 3 3]>9(x[m۸_.pMpֻW4\Q-+6w_gHJlɑ(k-93|f,}83Eg)> nXdLinn.j~bF/+%WRh-B%VM&Subd%Kmhble/9#GeRR55E"S\do$gZ\ņeG|/VKKr__w3}~1wl,uUqK2$ڤZeQՆ&qRumxYebxÏ_bwEJMK_p}!$ʼnF[q,k1V"fۗMu53<;2qn9(3aƊZNvR$/ Tvy6Q_Am͔LSI'xO冣zMaqT w" 皕F<~i㸑rbPDb'AYM !z!A$"0\[ڙZvrtRBnZv )ugp`:<݃L.Dᾓѓ8)SD+(G1" O,Kv a%].>MY n+A{D!H\dRgf_VINm%;<SHgp0ҝ'fOXGF\"˵HyXEk]jX'G^jR򞫘il}d]L ޟ|7Հ$}1BG9wȽ0YTPOVu+W' |80qi}\h*8 ɝYoA양*sVR\_uaOR`j6 3藚w-& ׼jtt'wym4\h鿋urt'Nqpq6ڙ./fp&/wVwvw%0rg7E戟/#Eh׬΍g~uy""HԐ8 FҩC8d&+b=gԀm7Imw]+t9HޕL ;h0al{4m~[wJFG کe հ`sdO-*X[(~Uka(נg Cs(au/j#Bl7cF肧GYީu(np-A'iu'_]K3XHgûSaQ'9s+Q3m<X~x %.Ԙ;'Z3TW0/dg ʊJzլT5]͉0^gX,PehG|׀UZ ^Z4eQ?(?n+~,  Bd. 3HlmdOEQ+juDp5d!GC{FgFY.e9sQpߠ%3*ApJږpOj,"|k1oԁ^ AY>n:.ZֺizwȡXdPCB7k:'%lc+d g@{N,T gՀX5VxD<Tw0;v@{:3lFG^$}4Sr=q>;3l-uBo"5F/ ˺8'J{KdrQ0uO5Cs6}l'v0.۴~;&4NNL4~j q5+}[RnF'z7C5"lMXp5ntGK@ûYY8fW1\#X]54a |>9B+,a!pmЃ:d;"E vw 0n7h{wIt޽afrX0`gt !`)mY.e>p(,qw6vZ(H֥ɘ=k͂bhCdð"x#g28{ݦ,n6 LޫکVQ/nkFJD4DyVޗno`}pt࿋RzkO/g*e~i,EHi-EAjK/5buV;7E sfHBe5tr zCFGw8Z;zW&R*)][ywO4>瑑"ȼ}p>pn/ %WHswvM ?Ox7=#+~J i4 *%,y_5i@6*s3nk^0^s^T4$OC{l `^ $|423>+9kPEucᬲL3& ^pDڗ=k U oApno!rڅ6egm:%K۴aN Y^l넄,6 56kF;V-L=NFGH. y 4ܷ -A4OOwk4ݻY+ iFH̺EGsJ94GqO{}fipK u'ތf1d$SAOXv܄YiI^\XطSR;1<U_shTxXKo6W>HE(6] mPHQ#D$wHQö"oe?yPOO|z;)LU&QܽC +`7ɯlwM Idz"RR OKʆ0*e vZaݗ?RLr@^]Tg=-ViKÌS_@3R]%1?ߨ}C^ g5u-E?/MQP/c@ptߗ vЕ#f'%&0!kX SSl۟ G#[ , Ui!iMA2|)v%¤0WW\t\!‹)^Bow GDR[mq^Q*@3G (Mll=ZZlϳS%6$!b`M'x=.^z= mtCPaV~O9Grs{sʹ=T6"jնw (Whz0  mUD-NG,d!si=X-@|=b3sIw- vqqw '9Er}9O*N[t9vQ04>Y/Ia4զs0|SQ+ 578 j$iޟQCo*'ϐ7sR9L6q/) I%3sȮC6pX;(yb>6Xu.@`ya&qWI͵ {c4xhT tƭt>5I,TЬ m 쑢F TI^۶,v/ql|3o^T')( ~`(aU2 7J}|6ǕR|R2tA1c쭅O6ԠgB D'U_Qi'_{D! DaHXN~^zqZqlO8BmzUˈh ]7IimcDžRs÷@4QE4Aj\ls 2_I9 &+¨ 1)þ.a*mi\ 7akW,_,RJWzw19~*f"}Ûͥ1;ߨ~d\wgR|1+_B=N1FC֐>A];\@SCc,$9 ]D<'T)jdMF2m@Ia(-'May܈5hxi-xO9DgR _@_p Fpaqy[ !h|O;G8leD#['I^xsgs%,6#5)0 ςE huP Be2 @ ˣFs^'JSca~*NΣy}Bg7QdDBkB\$^fu@_ $s|ܐꚨ=3WT ;l[Bcs)r BQ"FzT?1HWDX.c)Fi:H#A:Mжw+_Vqv* xVao6_i_N 0`Ŋ5ðO%,.])rMÖI޻wN̿y?~"5~zRTu:iu|G`MS:Rrުr<Bli#]r󗛋LBwz~BO2tf_u۷pN,?FSmWh/cXp G<)&3 _BPJ#zc㺅\?%KJ66l0VǑt1q?hVF>Beu8wg˂w$#q0#x7Z( di/rBWi%GD'=Ԕ\{eW[D,WƮ3h+{?axOslZJāܲ(r[eI DE~lݬ| &-[+xx0Ʒu%kR_lЀϳ#Ec~`~7;pGD~ZTTUc/3;((:Lwf˗bŞ 0p,޾s  || TxVMo6WZ@'.r4)4Z1HGR]7kIyo>9dû?nh]I>?jyk.JTY{66]L%E|^] !*e'^'p˯\ Efz*oEtpՅ D ݈ WD.5Cja:56<.|򬽈KZ'ըGגKk,z*ƚ}$*TA8 Fka*g(=dC;23)x =EeĆgyǠZTAtIfK' q+)ұ6j 4yU5WLfviA ]+$CQ7=I&+=93Y~sf$NKy,\2'iaWL_cL3lmJ=~+7N>3 F 0& ]'Zhy a0\3~DvQ.bsN78XXwXG{8\Z|:,x;a|Sܥ3uG~Pqf9x[k۸_Mpƙҭc  AbhD<ˇ^3x!^{~?ř)u (aq1y{U7WK<7|])\f?mF2+7LjF'߬a[DgŽh$^WKF<^M H@nj֫D|^Iδ~{WW;jIxY[u*u[۷^}BfKDE<p+kr[kj6iفJ '{~B3*,8+g^‡EׇNZ+%:m %S*f{>هx(<,y +P%=;$'Om_=\Vhs+/w@vBh"=BAh DZ T lió9:Y?)ŧPp[#|x/j+ Lah% ZO@%^T(B֒A<M3xz9sP]f\gNFϧjN>2Hh%Κ}xy|os7>phPhܠzA݈GC>BD-1BK;=dqj㳟scv~&=|As2S~#"ȼsVޕnoz?vmj7E}Wu~ o]DOߟ9mo$X\ˎ^Jv確ҸkPF(r9\cB[%Je1SrK/B^ / s3"̛`cK*KQg^rt=wkxs384סFmAѷ&_)aySCL3ǜQ=T 7L,o`4#T4d 'L\ a`ݍ84w3g}5_r@U<XI^eNj-0xZ}. {KހoB{DmY5.Qt7,ߞӆѫA)@N0yszmi\x((%_“ɖTݶĶ3\Ӿg[-UGe7bF7\ﷸA4Ѐ,[&*Mhz;[sMM_7X#hWbL(-t)!#x>姝]W;-0ZĐ*NſoujϬĺK$殛%}2  kWӥ_K1 z  X4(xYn6+X/Jl$@"U & ڠ,)XHJRx=-rIbxϹ*O|y;Lo0_3 +ê2/ <7l[*cȵQ< )Ù7^27b"ڿj hqK \ ^m nOnAڃRlgwXU VN(!PlR&`L["a(s( S QZvY뻕 )AzXwWA X߹!1+q!S0B>rDF諬ǘS*mPi"qQ(:D w[om l6*|"ڙH HO}Pe1 O Fqϻ9 3bBd,d ?? e\gtk)ÅxTkͿ9'gzZU$GHVS9LCpa؎Օ3D`젺IxɆ\ 34{+t01+dφ_o +dp>A t/+̹Ōh-?k%ߚG3#~xL zCs4&|פ~0 N Hu]ٳF*5w庿}A'1E(afϘ85Pf9Ai[QX(L, >O t Xl5\J)e?D%# *$)gz睤r{b;Sس~ p|r֭x3;sgx(|o6`5êTa_"R)K;NG\s=:^mϢ#ʜ Y.7[gvpA,zVcA;=JaGMeۑ՜S}U6o|E`y= uYzaB-a'5^T=cK/MjN q=unhƲ%.)r.7@58'bN e4 4H$m,A߄l?N  {{} ~ xW]o6|ׯت/9.Pm("XIFUIɖ(!L.gt߮GmdQ:}. TԬ`VFdVrވX-JեIxr@/2cT-.J:ne`gi2 ҥS,-T)~OE\ƕ2Rh?𐝇3 >e#ʙ;Q5S HP[C5 e6+bjaDT Sh;Wv%n{p,Z) K<.}d}4+A fEH[QЕBS#;m.;vvH`#:ېEN閛]PԻ`fMuՐ\<ʯn۱ܘCR,x*%Ss/o(cqל&,:ޮi"퍹NCgFll{]` nf(d툱тݲQU("GԨ§} M U1sD[ L^! xp=Ȍ>I #u0BJZg7VCWLG(n!;naA1J˷0y~z(,:"٩rV鮯NvphWLC]RƏL )ՂVYc7ǪW1JpqU'9EEn.C$A)7C+:Y637_E$;&LZnfG-V`K}yӽp71 /U7J#*0;HlQ+Rôl^Ci~+Y+ R8F);5;ZPNȩt*%&cҘ#\N}U m-\݌SJ7`#Ө4 1@19=TfId]+.BQ7l-4TkQ]Ƶ$[oɼVzoI萛jp9اxMv-ˍQ$RDbCsߣkw#vi|P+yr<&p}Yzb.7zoݕ/y|Q0o^:28 @ w77ƫt,cw!8_pq:0]&_·Iur>9x[m۸_.pMpҫc MzE.(>mD>ؒ#lz $ZK ݻ~?Ǚ)e(aq1y}SW7s<7|Y)H+ mXF2;7LbE'鎈-a[DHKV7#ot}\1&X1S\$2E2Nr뛵C2s_NjR\-W̭|p߾_yWco˓zhn5R-㷲jc8:N0Xr :7H\dRW2e_FI E^Op+1~%i *zIDoKd)/_MtjܜV.zuO5ryU4(>W.S~/:=gIJ{ae?@'9s4n{a AqJ(pdo^}wSbnWÚ.ݢq?%IM^eI!&.:a9SR'8Ed7˧gw0&/7NCM $̙-w9b8@5s?}"HА#C8!*1gЀ=6I}-1HIޥL :qѠC#}I{)%2yrXWasMUDO-5w8/VX9SfA {d8/z3<=ZNTטz%̙`e)#V8j\)Ʋ, Z~Fɟtsʍ@axqc|b1tDl84>Q1SNuĐU >ĝ14(+3jwd:fE%56-a(ΰXn]#>k@+@tDE X@MPv?=!8'`A_` n7}W/6%uG=MbP9yr,O5u/8ydc֣ԅd-xh"ZENnXxH]x9=4\33:2&3sZ  Z>qn ?QM^Ŕ$@,֍Z ߀6Y@;(V<-EYZ7y<45(~>G"oMo?H|pPqt0M@TEq c4M] HH* 3{]?vl^ uK<" ; WQEs%#g' dKNо>+*6!+/BO$>=m􊱬+BGn*gM& S8}hn:d1qIK|-A;mZÃtkڹhҬ\[/urw#@#0s)HEۑAv 9` 'R WW5afx2JJPyoַw1T#XchCXP E4BDڠ1Tы@[ wY4SsYa% ^0ov`4|/! +t@6,}M2)',ꦸw:hvZ(Hֆɘ=kObb4bXa7"x8 g28{O; Tˠ`ݰk#W:7w)r}pB$7N?}BhG\YN eFM+wHRVZl Wv7߅Q,.m_.}h*?;J8{EG3|ڻk7O;į@:w"[a5!%T&yrݼƿuZp˄ĪCr IK¾vdڊIɴ+)tOj4Y  b- ^/xZ{6_5<z7wr@4E^n 8-JԒwO×DْC\4W3yRrUE2gQQc!juj2*F*dJe(/߉y&3Kgݒg[z!V'U6@1*qAn&ԗi,3MS@cfj1OyF%Jf?E5t˅\g6elku]UDدN>fF7EU+TNPZ5mB+25_}%/';p p(J#8+51Kʨve?H;`VcE2aL:;_"~fW4V B"0e!fd8hӳ`!=jܽ@ @Vd:j\*zfH{DZrj"NNPDQ(5 x]rD<i2xt,ͧן^\i;nXqÁJ' \$tU9v>a vStQQ]fdEK@F#A  h6 ^c0Z%Y3fsmVC70ײ"gg2eWF(z}'^pTQ(%&j.ڙKЊ*GVx$N*,p81yiFqwt%CToL15h(4l!;'EvIE ڬt]}RȆXd8- ˒r_]^A Z$:!_hMhAOpg1[pdHjAmx)x'š^~i$; C1,TJ )MpU X@&=Y9(DSIv4ZW ͬ `z$/9N:r`כ:@0vq;f Y5&f\N32Xxgeq(;(nzC냘%E VCMF^eowU6 Dxn650i O(YmbO>}?}rzWq< msmA68-⌠}|} {U o6C٧)$*`]Z[ ➘RjX +:4ATꑺudž`A,C)_oBIg} ^v+X};#y¥iWAi9/m%I@YY\pJc$Τ5)y uh%SыkϲݾmstFbi2Ss^GMV; ƴ0kxӣop 콂v@p J&KFbZS9V#DhMFa|a 9aq[u3nV}xgw"G+-6RnLč !ȼ tJœߗvm>ف^ܦI.4y Cycuً>f3WKF rJ1.YkE9q Nsk'!\fWe5B_2^D/xxsH=5U+ iUK_II Yh|h Qсw-!yzowgf+Πk_|CJ{V4Lq(U@u5X̆ڸt;VH0ꊮ_cQ/4YĿ?bӯ҅9+7TPAxaBl—i:OLc9` %>AZ?2vT~_sҼ?}sƑAD ɀ l$n`IdtlBcJZ#*p9?c̽q?4 8x͙Qo6)n~2V104+`P {M})Qd[SKw;?>.>٤ kOh'Y~&EdtŰ(Qà稌@K W_BfKp(K2''Y'l̾TiޒYlEe02'+=9%އa"zo1Oi|%),gZ 9S$'2m(b#ݤt4g)Sei$&AP,[#=I&pw8c"[;/Ca¶B =n7UYsV>|ھ&ơJ fZߜ8eD{oIk6RqL\i2_)ܜ}(r`\/m#93( XgXR #؆vmQ0 Bz 1ٍFRglqzPK Lt]Fs(ODY$RAlZ ȍTsb⡹V @F7U2zh,}be?j?2ua%#2k%pDS^A/1ٰrh`gX~4D>T=)0؋0 #Eێ첚/,.:7+ۇkO%ϱa?hƔ@܎Ӷ =hn8cRcD۳rWŸisl@_Qڕʚ7b;AMBi\@ 顫Ϭ@,/uHm;^+\q/hzdXpSP;EFZakQdUT;]pε23Z #xO[!AWA  , 2MxYKo6W>),Y@n6H#DjI*^z˲rыy^뷗Idx/jq[acXZmoQ+h a= >m2VBVs6ō<)Q4XQ"i$[WK,ލQ.ܮWLqX{bj"41-ڮ-z+ZF1F&"R[e싷lb߈AT7g1 O!wF@R }P=v@ڌO !TF2xy fEP13.WB] )}L :3~v y-UFX(u F;]#A/>9%&Gm; `\C* 8~yA 6oD{l`TN"(ތ U%>E'DmFi5 ^kJƇ3q%/e1@j͎PG0f y=LrKc0cVנ> #0RNIuՂJ6#}nӴ1d`Gul, ۳v-)5܍kV{T{|A7D*[7,||PYS@`T<~| vfc'yVW{=_gwƂXh(õ,^JT.wFn=(fE#,$y6*59UAIΠcX3ig>DIΝ{\ysb.ڦGr^[IV?Iz})#+>Q.*\T0Mڳ4蹄tA;Kt =3<ƾ-ݎempFɮ!N<>v6b*q`$Z+[O@udgF޹. ~ >R6\=i[/Xyod̎ J2=xI{f0IL߾ׇP]d^eBx0OΐOY<VPy-}U1lsW+Si<ar5FPbxa Wz:o}'/rS$F?+60[2P۵7>,`uVRi^TTN S5`]QE%#=!ZKfoVuP!ᵯPoSW#r?i|yDga6\X}>#H \7jFTmEnTT+)n!i0th&o;X2EbnO!5Q/Lt|pQ6Q!-pFOr:ݺ_e5ݠٸഈ55]gNjtFi}TӘQ*^EX-+ٲh ^^ؽX$$L+$ZEv| 0tFA)A gwӵQ#7GU_5۔Vᥘ&P;x[i_.+ n" pb(|$_睃DjIvlKf?%LU,G )͛l_-u,f,IX m"7LjG!\kڈDO}ъ%+{v]$lZ2>i5Đ*)_Wq3ߜm}`WLՒ>7j7)u-[j-W؟9YNTN0 +٨'FK%H!o #iY9CtZ-ZuW+?[,) w6zLͥbhãi#hЂ@iWŧlN%g*x(LD*;IӁhyHWL"3wNUy~Ÿ!ދR]3YnEħ]F:J5nKR=*;y&d.oYƶ⍬˔)߽Q7C,X8KV3h\!7d1KSA=Y'b+Pnq*& FuUSLаM0 :ނ-#U6R\5{iH^)Q0u/7 3o;[-M]EXӑ[4XiUUƞ5bB mLV:RDWhl: ݡU $ԙk,sDq,4[V#qD*!)F ±C8$+XBs ?I;.һRh} /7woѠC" &0@a]-y6Y;8-2CZ| |04Q7-*^TBV_8Oxzl65ĨN9ø륲#gk4~sF-Q3O8\9姱zi͑L>|CX4X~3Ŧ XuE]4Y}j;Rv'"(&P~l8";O> M!E(*A^Si+Pm@(s*PfN 5E-iǻ->v A @(f,ꆸw2h"vV(Pֺɘ=*w⶝a!b8c4dU$n|+q$dNaqA';PɛEw:x Ac`_L#w:7G9r!['>q^рhMvyOԱS#o\tn5_HK}vqzDŽ]@Z\ն8 ^Hv.H `s䆁9'F.$9<hR3bWqm+hB34.hx%( t2آg̛_y\&v*A.+qy#Im6p@eћ&6~A9OՏ&9](@ G !j\2'EZY9HEAf̾]E[nKYm1,0}^~Jy+;cmkڋc$j>n6.Xsz{-paRQuI -ӭ?m{c F>[ gEGbw il(q]߄ޯH;pk8GmEx4M(;H;@w$[a4AT&9_{ٻv ӹuU-0 {e4% oܥKq :\  /M xVQk0~y/1Fq ]aB[ƞlɱV[S,+qiXo??A][$y| dP5Xa}!h%.gwJtҢCU$FRG}WXdHp؟ b"ϘG}:LrSXț3Zf]Ȼ2Y>B`{pqX CVlm=tEd{\AH/JT!"`TkEHE腃Z&y6 4 #яb[i*-sۗQS[}%dC?oF.>#*uBq@l=PznFiKR&HBOz1b+Xcj'dS<z i 7FAy 5[S^l"qά/0c9h(nBk5lJ`Kxi e3n NJxsi:W'X:-˽ZoZܻm޴8-Ƒ1n6wݯ&>| v884RxiqL(:k7F }L/,|o]ӌU. xZKo6WN] bhmA鈭$jIʉw=YU;acK|3qflͯw~O2S( 2($ͨ\-j9&Jŝsn6Jk#dI~&L'ث\KCVPKD?I`MPR҂_-FV\lX%1E;`$} ffU]4Z_-6z1#IT$rEj]+~tm;Kqv ?򫚇'k鬮*pCZPd0\A 6`'ӡE mF NˉDdfҩJ\ܵ) gtl1|I2$gYj- xUiu^欚92^jlsg 5 ޾(tL7RuQ8%IOTX1"8@_3-6D Wh?BdzǶ\4}:i١$lZҠgp"d8M8mN/R7/F>ѹG^r8}iQ}ӌo,/WB4xN;8$٪ &M3h(ƠeٔpJk0]Pxl"& 와)kTwPqAϝ%h56Biy3OQ\Fm5k hȾ]]BKVMZe>,\KZBr["A݌ X@8Nw:vCU ƃGhAIP^j221A84|q8p{c3!w^1ۂ_-[#]- ñM* #O[)ʌ-50h,\/ijea޶˘2i߫F;S e-|\VkfO@ݸF)]:%G&XW@ o ~Z~+ /ɗY)Hx dD4 S<+g4Wne1M :gA(\ \0u $sDi] TsvIS.8`h|-R&l1 d1)! Jdw@Yr$P̲ɫ$oi@8z$٤^()&!O858xfH:cbFks+<ΥHo`s6H0I.'=0ll]i՞3jjDy:QhRH}AGAYZ'&vO G/(K Jm!/. nb`GŠ- pQG\Gx2LSų[q0̴5?"K@Rٙb[9P+Ӱ]j|/ZUqlYg`kk,G٠]OS Ɔ`䞷^zDDNyjҽ!yU.zd4L[v8[yp͑]\͑޹l^+ށ+mnd2Q34Um8"\,.mFhsZ]ސYFVb']B]kzgxԟ 븃[vZIΙ]"ϸGOvE^隊d^MW P%Ea 6x)pV/.m{oDg\EO;G,VieknX Y1leDUjfyKb5G]m yaxw, MX'Ԡ{:.Y|sßf r08HP6*QqgQg7 55BQcJXMqg"䕢>ͥM ɻyF]}2?/S Ide T .`"@Ȍ})X 64Jk^'Q'F[s ..e8Re~?J@1UP55ARV8@ #W+6f?@4yǙ@ h7O+p-kWX.i6V:sf1*־ÒUk.8Zf٦{.d a V/Ld :.vE\HYmVK/Om-~(F`Pliqyf;tK.؂h,yR300<Vl[y`*F`O/9$=-$, ]6NFN>^U }{;HBP/wjW\0 g6NPsmNof+9zxSZmO8q)aGזlx4cc8SYE_TAn">_+Cպhv.tѿ5tnWiJ_~/n_p݊7Ro3[85F4XK7&7Bp>įHGoKs9mN #2xZo6~_n(Pt-݆=lCa@KtMJRNܿ~ߑ/[reG Rw}w<2]~?ogIU> YsflFD /&W[WVz^_Ews'^."~0|d\E!ԅG҇hacRuĂeZ&VMˋ?ꂜʗTz;s]>*f,thc,re"ͮ-_#B5Ŗ'%7"bXe 2 :+g"*I"o)aJ9H"R(o;,1$G|G ] S/ue^#(,OpY8F%όL{ay\D ,g"8IQG5%T'px+oų;g>,hPnNM_*x6&$7Av3H'JY*^1OHMIN(1" O,v\*ϐbFYLOک*Μ9{6zAZ5<7:InFrL6k*)`Ht^N!w\#콁1dxiX~t_2% 7},ØZ4D3uHtL\cA OIv;=Q9L"Q$,/fִE_y->\+餤YHN+%t)]G91HeiHQv-]Wt#W;ԿQÅWワ²\-J)'0jw$ qQڲķ0T˕ONfwC ΉPyh]l?bQԚ`L; eOqbk]zP2&B*.:`Mh. [A!u_E| 3TlL,,١OcR띾t!zuݒΆGWd'z8ԛ|䞵&93#-CR<"wY527\O' X`U" ݪ!Us&o]]ЛUUSN3/q_;rV#9^Mt]\p#h3\lxr|FSE_:0t阑IN}mRi})rA?(^_a/͝O /?翏N?L(T s0&)CW>-oh`FneeFO`[#DDnL䌶PTD=VW%BK6N9L?4g$SbUCn[M\!OvlI2/q7``#}+S{ewFM6ów$hv$qI9cFn@Hth4"xwj& 6AkR*Ͽ*޿r $ pmdL+ hil5l꼱c[{6+ď@4#)n+s4L݉UL_!h*AXԘSiA#Ԧ^[G Y9u $\x+fR'a: mp~'429~4j =1>I%FSwjA4VQ&e:boguSfƔ&&m*+%©@ǨFiekתQ&x͘o0+n6!y Juʓ*%S^.AZ-Rr͈:#V3b՚>ƶ{v=|+!jyrظsZFy GKEi 4܄kL,QR':STPG\ɨmhz|+UDuB$tkr]?ZRMa]ĆMZBRlm5 yS xnѷks t*+"܌a0a;?T'3W Z3DzE4t\,0ξC`oxjhTS +èrs7ZF;͵Ln8m OQ xN b{}%sӃ?w8d֭fD sk|VAY+4si(tHGŠCT~F㒴WّX'7RM׵ɞ=knAK[{n).餱6lHը d< `{,@C[QїNkE =;ẵrIyY'+;FЂL1)y:{T'Ys k,CD~qrnSsg%xzn?ljV3ʼnH.`.x RYlAEiXfZxIٚ< *5$Y6 /)6Df*wPw҆B;fz# Hu Xx L4r3ګՕʺ\~l;U͉յW!;nlgmJj% .3.D@ݥw?'b'sbg`}2DLI YNRf$I H֖MiJ L zx9x[[6~ׯM 8Lٮc M{CŢ-#n$Q!wxŖ6mmks?9TWxϷ/,eNVC 9F>{us+m!ֵV_4ENJU ̬~]ZzbLt!YqVRZ~fۋjɉËIh(TzLWKܘ[s1z]^fHnS+WKf/ISB? It6* fsҦl FmSӷ~kRJD]J內gٌ^35q7~(G6먋^4܊qoЕU29(sQa2-l+/;)RRT*$;g'}Ѓ {qD Xaoyu'-;; :)!xCOrB-;}Ѻ38;<݃L.rD,rIQɹnwWGp#;]QJ\꺐]|UzOCғ̕͡ȭK%;<5׼Lgp0ҝGfXm_3TU[* g#a[]XjfG^Oլ+ԭЌWs>aTߍ4<;ix.juNr+mxI꩚fjʭܠz3uC}aqB<'wg/# m΄nÐxe=jC/^}w1jiW㚞-ÑΆ}s:oIy5.DF;)ur/k I97G感/@-o ?|"HP8 &ҫC8d1+`=g)nē]dv z_^ns*!E~m~[wJFG ٩eNհ.<ۓ;y⚭֓ԧd. ٳqLԊZ}) u %iN}~GnoCPH yztq7Q hE;X, *d&9<#ъh |س[D5̭x;df.Q*JީßtPZ"wрQ́iX01Bcj֊&o<'{h V0O5#mIĴl+@[n=L֐Ws@ݻ/2 mN`O*ߍt3@ ?d=QF[S2㓲F4&w&~ _i}7z~ħ)SaDSrlqHУZ! YDfM^\EȌݡdkᾥW~p{ȼ5VV~oz?.JaQu<7%۞nԋY~ z[h}nL᎞,]l8$.yxVã?ӻ rGwvSg4.un}[Am 8|~& stJ*Z~_݋Ty C@^ߋA!k>zmUTB#4x,+ai}kE WR^" o>0Wtn ]ezÙN3Y|r83-^e#s?3ުޭm# ^`tєaR+k7ęeH)`Bsf)QdA  ۧϧ>)nXvfH^%bo$Qy:!!לE $[Ud3ѝ$ u?pk'tvEꀋlU\ &8qWZ\ b@nRi6vcw͓xfuyF L,|vӭK=Jy, ',ǧw!LJu/[ *-Ll6#l&!+oYH^\XWSR;1xx!]or$ GuDm> M  m P =   ; } : U Y"uR~E\-t)p1l/LQG ]SClassificationNeuralNetwork_methods.htmlGClassificationNeuralNetwork MethodsWF cYClassificationNeuralNetwork_properties.htmlFClassificationNeuralNetwork propertiesAE MCClassificationNeuralNetwork.htmlEClassificationNeuralNetworkKD WMClassificationNaiveBayes_methods.htmlDClassificationNaiveBayes MethodsQC ]SClassificationNaiveBayes_properties.htmlCClassificationNaiveBayes properties;B G=ClassificationNaiveBayes.htmlBClassificationNaiveBayesCA OEClassificationLinear_methods.htmlAClassificationLinear MethodsI@ UKClassificationLinear_properties.html@ClassificationLinear properties3? ?5ClassificationLinear.html?ClassificationLinear=> I?ClassificationKNN_methods.html>ClassificationKNN MethodsC= OEClassificationKNN_properties.html=ClassificationKNN properties-< 9/ClassificationKNN.html + N  G.QY%LlG g*}BI} UKRegressionNeuralNetwork_methods.html}RegressionNeuralNetwork MethodsO| [QRegressionNeuralNetwork_properties.html|RegressionNeuralNetwork properties9{ E;RegressionNeuralNetwork.html{RegressionNeuralNetwork;z G=RegressionLinear_methods.htmlzRegressionLinear MethodsAy MCRegressionLinear_properties.htmlyRegressionLinear properties+x 7-RegressionLinear.htmlxRegressionLinear;w G=RegressionKernel_methods.htmlwRegressionKernel MethodsAv MCRegressionKernel_properties.htmlvRegressionKernel properties+u 7-RegressionKernel.htmluRegressionKernel3t ?5RegressionGP_methods.htmltRegressionGP Methods9s E;RegressionGP_properties.htmlsRegressionGP properties#r /%RegressionGP.htmlrRegressionGP5q A7RegressionGAM_methods.htmlqRegressionGAM Methods;p G=RegressionGAM_properties.htmlpRegressionGAM properties%o 1'RegressionGAM.htmloRegressionGAMCn OECompactRegressionSVM_methods.htmlnCompactRegressionSVM MethodsIm UKCompactRegressionSVM_properties.htmlmCompactRegressionSVM properties3l ?5CompactRegressionSVM.htmllCompactRegressionSVMWk cYCompactRegressionNeuralNetwork_methods.htmlkCompactRegressionNeuralNetwork Methods]j i_CompactRegressionNeuralNetwork_properties.htmljCompactRegressionNeuralNetwork propertiesGi SICompactRegressionNeuralNetwork.htmliCompactRegressionNeuralNetworkAh MCCompactRegressionGP_methods.htmlhCompactRegressionGP MethodsGg SICompactRegressionGP_properties.htmlgCompactRegressionGP properties1f =3CompactRegressionGP.htmlfCompactRegressionGPCe OECompactRegressionGAM_methods.htmleCompactRegressionGAM MethodsId UKCompactRegressionGAM_properties.htmldCompactRegressionGAM properties3c ?5CompactRegressionGAM.htmlcCompactRegressionGAMKb WMCompactClassificationSVM_methods.htmlbCompactClassificationSVM MethodsQa ]SCompactClassificationSVM_properties.htmlaCompactClassificationSVM properties;` G=CompactClassificationSVM.html`CompactClassificationSVM__ kaCompactClassificationNeuralNetwork_methods.html_CompactClassificationNeuralNetwork Methodse^ qgCompactClassificationNeuralNetwork_properties.html^CompactClassificationNeuralNetwork propertiesO] [QCompactClassificationNeuralNetwork.html]CompactClassificationNeuralNetworkY\ e[CompactClassificationNaiveBayes_methods.html\CompactClassificationNaiveBayes Methods_[ kaCompactClassificationNaiveBayes_properties.html[CompactClassificationNaiveBayes propertiesIZ UKCompactClassificationNaiveBayes.htmlZCompactClassificationNaiveBayesKY WMCompactClassificationGAM_methods.htmlYCompactClassificationGAM MethodsQX ]SCompactClassificationGAM_properties.htmlXCompactClassificationGAM properties;W G=CompactClassificationGAM.htmlWCompactClassificationGAM]V i_CompactClassificationDiscriminant_methods.htmlVCompactClassificationDiscriminant MethodscU oeCompactClassificationDiscriminant_properties.htmlUCompactClassificationDiscriminant propertiesMT YOCompactClassificationDiscriminant.htmlTCompactClassificationDiscriminant=S I?ClassificationSVM_methods.htmlSClassificationSVM MethodsCR OEClassificationSVM_properties.htmlRClassificationSVM properties-Q 9/ClassificationSVM.htmlQClassificationSVMWP cYClassificationPartitionedModel_methods.htmlPClassificationPartitionedModel Methods]O i_ClassificationPartitionedModel_properties.htmlOClassificationPartitionedModel propertiesGN SIClassificationPartitionedModel.htmlNClassificationPartitionedModelYM e[ClassificationPartitionedLinear_methods.htmlMClassificationPartitionedLinear Methods_L kaClassificationPartitionedLinear_properties.htmlLClassificationPartitionedLinear propertiesIK UKClassificationPartitionedLinear.htmlKClassificationPartitionedLinearYJ e[ClassificationPartitionedKernel_methods.htmlJClassificationPartitionedKernel Methods_I kaClassificationPartitionedKernel_properties.htmlIClassificationPartitionedKernel propertiesIH UKClassificationPartitionedKernel.htmlHClassificationPartitionedKernel ?do ~ +  G l ' X j ??|9s2Jf+~=Y e0 0< ;1rocmetrics_methods.htmlrocmetrics Methods6; A7rocmetrics_properties.htmlrocmetrics properties : +!rocmetrics.htmlrocmetrics29 =3cvpartition_methods.htmlcvpartition Methods88 C9cvpartition_properties.htmlcvpartition properties"7 -#cvpartition.htmlcvpartitionD6 OEConfusionMatrixChart_methods.htmlConfusionMatrixChart MethodsJ5 UKConfusionMatrixChart_properties.htmlConfusionMatrixChart properties44 ?5ConfusionMatrixChart.htmlConfusionMatrixChart:3 E;SparseFiltering_methods.htmlSparseFiltering Methods@2 KASparseFiltering_properties.htmlSparseFiltering properties*1 5+SparseFiltering.htmlSparseFiltering>0 I?ReconstructionICA_methods.htmlReconstructionICA MethodsD/ OEReconstructionICA_properties.htmlReconstructionICA properties.. 9/ReconstructionICA.htmlReconstructionICA2- =3OneClassSVM_methods.htmlOneClassSVM Methods8, C9OneClassSVM_properties.htmlOneClassSVM properties"+ -#OneClassSVM.htmlOneClassSVM@* KALocalOutlierFactor_methods.htmlLocalOutlierFactor MethodsF) QGLocalOutlierFactor_properties.htmlLocalOutlierFactor properties0( ;1LocalOutlierFactor.htmlLocalOutlierFactor:' E;IsolationForest_methods.htmlIsolationForest Methods@& KAIsolationForest_properties.htmlIsolationForest properties*% 5+IsolationForest.htmlIsolationForest8$ C9KDTreeSearcher_methods.htmlKDTreeSearcher Methods># I?KDTreeSearcher_properties.htmlKDTreeSearcher properties(" 3)KDTreeSearcher.htmlKDTreeSearcher4! ?5hnswSearcher_methods.htmlhnswSearcher Methods:  E;hnswSearcher_properties.htmlhnswSearcher properties$ /%hnswSearcher.htmlhnswSearcher@ KAExhaustiveSearcher_methods.htmlExhaustiveSearcher MethodsF QGExhaustiveSearcher_properties.htmlExhaustiveSearcher properties0 ;1ExhaustiveSearcher.htmlExhaustiveSearcherD OESilhouetteEvaluation_methods.htmlSilhouetteEvaluation MethodsJ UKSilhouetteEvaluation_properties.htmlSilhouetteEvaluation properties4 ?5SilhouetteEvaluation.htmlSilhouetteEvaluation8 C9gmdistribution_methods.htmlgmdistribution Methods> I?gmdistribution_properties.htmlgmdistribution properties( 3)gmdistribution.htmlgmdistribution6 A7GapEvaluation_methods.htmlGapEvaluation Methods< G=GapEvaluation_properties.htmlGapEvaluation properties& 1'GapEvaluation.htmlGapEvaluationJ UKDaviesBouldinEvaluation_methods.htmlDaviesBouldinEvaluation MethodsP [QDaviesBouldinEvaluation_properties.htmlDaviesBouldinEvaluation properties: E;DaviesBouldinEvaluation.htmlDaviesBouldinEvaluation< G=ClusterCriterion_methods.htmlClusterCriterion MethodsB MCClusterCriterion_properties.htmlClusterCriterion properties,  7-ClusterCriterion.htmlClusterCriterionP  [QCalinskiHarabaszEvaluation_methods.htmlCalinskiHarabaszEvaluation MethodsV  aWCalinskiHarabaszEvaluation_properties.htmlCalinskiHarabaszEvaluation properties@  KACalinskiHarabaszEvaluation.htmlCalinskiHarabaszEvaluation6  A7RegressionSVM_methods.htmlRegressionSVM Methods< G=RegressionSVM_properties.htmlRegressionSVM properties& 1'RegressionSVM.htmlRegressionSVMP [QRegressionPartitionedModel_methods.htmlRegressionPartitionedModel MethodsV aWRegressionPartitionedModel_properties.htmlRegressionPartitionedModel properties@ KARegressionPartitionedModel.htmlRegressionPartitionedModelR ]SRegressionPartitionedLinear_methods.htmlRegressionPartitionedLinear MethodsX cYRegressionPartitionedLinear_properties.htmlRegressionPartitionedLinear propertiesB MCRegressionPartitionedLinear.htmlRegressionPartitionedLinearR ]SRegressionPartitionedKernel_methods.htmlRegressionPartitionedKernel MethodsW cYRegressionPartitionedKernel_properties.htmlRegressionPartitionedKernel propertiesA~ MCRegressionPartitionedKernel.html~RegressionPartitionedKernel 1k$S j  Q  9 < INjKVCf%}6?Pm [Qprob.LognormalDistribution_methods.htmlprob.LognormalDistribution MethodsVl aWprob.LognormalDistribution_properties.htmlprob.LognormalDistribution properties@k KAprob.LognormalDistribution.htmlprob.LognormalDistributionTj _Uprob.LoglogisticDistribution_methods.htmlprob.LoglogisticDistribution MethodsZi e[prob.LoglogisticDistribution_properties.htmlprob.LoglogisticDistribution propertiesDh OEprob.LoglogisticDistribution.htmlprob.LoglogisticDistributionNg YOprob.LogisticDistribution_methods.htmlprob.LogisticDistribution MethodsTf _Uprob.LogisticDistribution_properties.htmlprob.LogisticDistribution properties>e I?prob.LogisticDistribution.htmlprob.LogisticDistributionJd UKprob.KernelDistribution_methods.htmlprob.KernelDistribution MethodsPc [Qprob.KernelDistribution_properties.htmlprob.KernelDistribution properties:b E;prob.KernelDistribution.htmlprob.KernelDistribution\a g]prob.InverseGaussianDistribution_methods.htmlprob.InverseGaussianDistribution Methodsb` mcprob.InverseGaussianDistribution_properties.htmlprob.InverseGaussianDistribution propertiesL_ WMprob.InverseGaussianDistribution.htmlprob.InverseGaussianDistributionR^ ]Sprob.HalfNormalDistribution_methods.htmlprob.HalfNormalDistribution MethodsX] cYprob.HalfNormalDistribution_properties.htmlprob.HalfNormalDistribution propertiesB\ MCprob.HalfNormalDistribution.htmlprob.HalfNormalDistribution`[ kaprob.GeneralizedParetoDistribution_methods.htmlprob.GeneralizedParetoDistribution MethodsfZ qgprob.GeneralizedParetoDistribution_properties.htmlprob.GeneralizedParetoDistribution propertiesPY [Qprob.GeneralizedParetoDistribution.htmlprob.GeneralizedParetoDistributionlX wmprob.GeneralizedExtremeValueDistribution_methods.htmlprob.GeneralizedExtremeValueDistribution MethodsrW }sprob.GeneralizedExtremeValueDistribution_properties.htmlprob.GeneralizedExtremeValueDistribution properties\V g]prob.GeneralizedExtremeValueDistribution.htmlprob.GeneralizedExtremeValueDistributionHU SIprob.GammaDistribution_methods.htmlprob.GammaDistribution MethodsNT YOprob.GammaDistribution_properties.htmlprob.GammaDistribution properties8S C9prob.GammaDistribution.htmlprob.GammaDistributionVR aWprob.ExtremeValueDistribution_methods.htmlprob.ExtremeValueDistribution Methods\Q g]prob.ExtremeValueDistribution_properties.htmlprob.ExtremeValueDistribution propertiesFP QGprob.ExtremeValueDistribution.htmlprob.ExtremeValueDistributionTO _Uprob.ExponentialDistribution_methods.htmlprob.ExponentialDistribution MethodsZN e[prob.ExponentialDistribution_properties.htmlprob.ExponentialDistribution propertiesDM OEprob.ExponentialDistribution.htmlprob.ExponentialDistributionFL QGprob.BurrDistribution_methods.htmlprob.BurrDistribution MethodsLK WMprob.BurrDistribution_properties.htmlprob.BurrDistribution properties6J A7prob.BurrDistribution.htmlprob.BurrDistribution^I i_prob.BirnbaumSaundersDistribution_methods.htmlprob.BirnbaumSaundersDistribution MethodsdH oeprob.BirnbaumSaundersDistribution_properties.htmlprob.BirnbaumSaundersDistribution propertiesNG YOprob.BirnbaumSaundersDistribution.htmlprob.BirnbaumSaundersDistributionNF YOprob.BinomialDistribution_methods.htmlprob.BinomialDistribution MethodsTE _Uprob.BinomialDistribution_properties.htmlprob.BinomialDistribution properties>D I?prob.BinomialDistribution.htmlprob.BinomialDistributionFC QGprob.BetaDistribution_methods.htmlprob.BetaDistribution MethodsLB WMprob.BetaDistribution_properties.htmlprob.BetaDistribution properties6A A7prob.BetaDistribution.htmlprob.BetaDistributionD@ OEprob.ProbabilityDistribution.htmlprob.ProbabilityDistribution2? =3paretotails_methods.htmlparetotails Methods8> C9paretotails_properties.htmlparetotails properties"= -#paretotails.htmlparetotails *` g x ' o  ~ 1 }  ;Ru8KFWtL WMprob.WeibullDistribution_methods.htmlprob.WeibullDistribution MethodsR ]Sprob.WeibullDistribution_properties.htmlprob.WeibullDistribution properties< G=prob.WeibullDistribution.htmlprob.WeibullDistributionL WMprob.UniformDistribution_methods.htmlprob.UniformDistribution MethodsR ]Sprob.UniformDistribution_properties.htmlprob.UniformDistribution properties< G=prob.UniformDistribution.htmlprob.UniformDistributionR ]Sprob.TriangularDistribution_methods.htmlprob.TriangularDistribution MethodsX cYprob.TriangularDistribution_properties.htmlprob.TriangularDistribution propertiesB MCprob.TriangularDistribution.htmlprob.TriangularDistributionZ e[prob.tLocationScaleDistribution_methods.htmlprob.tLocationScaleDistribution Methods`  kaprob.tLocationScaleDistribution_properties.html prob.tLocationScaleDistribution propertiesJ  UKprob.tLocationScaleDistribution.html prob.tLocationScaleDistributionJ  UKprob.StableDistribution_methods.html prob.StableDistribution MethodsP  [Qprob.StableDistribution_properties.html prob.StableDistribution properties:  E;prob.StableDistribution.html prob.StableDistributionJ UKprob.RicianDistribution_methods.htmlprob.RicianDistribution MethodsP [Qprob.RicianDistribution_properties.htmlprob.RicianDistribution properties: E;prob.RicianDistribution.htmlprob.RicianDistributionN YOprob.RayleighDistribution_methods.htmlprob.RayleighDistribution MethodsT _Uprob.RayleighDistribution_properties.htmlprob.RayleighDistribution properties> I?prob.RayleighDistribution.htmlprob.RayleighDistributionL WMprob.PoissonDistribution_methods.htmlprob.PoissonDistribution MethodsR ]Sprob.PoissonDistribution_properties.htmlprob.PoissonDistribution properties< G=prob.PoissonDistribution.htmlprob.PoissonDistribution\ g]prob.PiecewiseLinearDistribution_methods.htmlprob.PiecewiseLinearDistribution Methodsb~ mcprob.PiecewiseLinearDistribution_properties.htmlprob.PiecewiseLinearDistribution propertiesL} WMprob.PiecewiseLinearDistribution.htmlprob.PiecewiseLinearDistributionJ| UKprob.NormalDistribution_methods.htmlprob.NormalDistribution MethodsP{ [Qprob.NormalDistribution_properties.htmlprob.NormalDistribution properties:z E;prob.NormalDistribution.htmlprob.NormalDistribution^y i_prob.NegativeBinomialDistribution_methods.htmlprob.NegativeBinomialDistribution Methodsdx oeprob.NegativeBinomialDistribution_properties.htmlprob.NegativeBinomialDistribution propertiesNw YOprob.NegativeBinomialDistribution.htmlprob.NegativeBinomialDistributionNv YOprob.NakagamiDistribution_methods.htmlprob.NakagamiDistribution MethodsTu _Uprob.NakagamiDistribution_properties.htmlprob.NakagamiDistribution properties>t I?prob.NakagamiDistribution.htmlprob.NakagamiDistributionTs _Uprob.MultinomialDistribution_methods.htmlprob.MultinomialDistribution MethodsZr e[prob.MultinomialDistribution_properties.htmlprob.MultinomialDistribution propertiesDq OEprob.MultinomialDistribution.htmlprob.MultinomialDistributionRp ]Sprob.LoguniformDistribution_methods.htmlprob.LoguniformDistribution MethodsXo cYprob.LoguniformDistribution_properties.htmlprob.LoguniformDistribution propertiesBn MCprob.LoguniformDistribution.htmlprob.LoguniformDistributionmethods.htmlMethods0Experimental_Design.html&Experimental DesignRegression.htmlRegression.CompactLinearModel.html$CompactLinearModelDCompactLinearModel_properties.htmlProperties>CompactLinearModel_methods.htmlMethodsCoxModel.htmlCoxModel0CoxModel_properties.htmlProperties*CoxModel_methods.htmlMethods@GeneralizedLinearMixedModel.html6GeneralizedLinearMixedModelVGeneralizedLinearMixedModel_properties.htmlPropertiesPGeneralizedLinearMixedModel_methods.htmlMethods6GeneralizedLinearModel.html,GeneralizedLinearModelLGeneralizedLinearModel_properties.htmlPropertiesFGeneralizedLinearModel_methods.htmlMethods$LinearFormula.htmlLinearFormula:LinearFormula_properties.htmlProperties4LinearFormula_methods.htmlMethods*LinearMixedModel.html LinearMixedModel@LinearMixedModel_properties.htmlProperties:LinearMixedModel_methods.htmlMethods LinearModel.htmlLinearModel6LinearModel_properties.htmlProperties0LinearModel_methods.htmlMethods&NonLinearModel.htmlNonLinearModel<NonLinearModel_properties.htmlProperties6NonLinearModel_methods.htmlMethods0Supervised_Learning.html&Supervised Learning>ClassificationDiscriminant.html4ClassificationDiscriminantTClassificationDiscriminant_properties.htmlPropertiesNClassificationDiscriminant_methods.htmlMethods,ClassificationGAM.html"ClassificationGAMBClassificationGAM_properties.htmlProperties<ClassificationGAM_methods.htmlMethods2ClassificationKernel.html(ClassificationKernelHClassificationKernel_properties.htmlPropertiesBClassificationKernel_methods.htmlMethods,ClassificationKNN.html"ClassificationKNNBClassificationKNN_properties.htmlProperties<ClassificationKNN_methods.htmlMethods2ClassificationLinear.html(ClassificationLinearHClassificationLinear_properties.htmlPropertiesBClassificationLinear_methods.htmlMethods:ClassificationNaiveBayes.html0ClassificationNaiveBayesPClassificationNaiveBayes_properties.htmlPropertiesJClassificationNaiveBayes_methods.htmlMethods@ClassificationNeuralNetwork.html6ClassificationNeuralNetworkVClassificationNeuralNetwork_properties.htmlPropertiesPClassificationNeuralNetwork_methods.htmlMethodsHClassificationPartitionedKernel.html>ClassificationPartitionedKernel^ClassificationPartitionedKernel_properties.htmlPropertiesXClassificationPartitionedKernel_methods.htmlMethodsHClassificationPartitionedLinear.html>ClassificationPartitionedLinear^ClassificationPartitionedLinear_properties.htmlPropertiesXClassificationPartitionedLinear_methods.htmlMethodsFClassificationPartitionedModel.html<ClassificationPartitionedModel\ClassificationPartitionedModel_properties.htmlPropertiesVClassificationPartitionedModel_methods.htmlMethods,ClassificationSVM.html"ClassificationSVMBClassificationSVM_properties.htmlProperties<ClassificationSVM_methods.htmlMethodsLCompactClassificationDiscriminant.htmlBCompactClassificationDiscriminantbCompactClassificationDiscriminant_properties.htmlProperties\CompactClassificationDiscriminant_methods.htmlMethods:CompactClassificationGAM.html0CompactClassificationGAMPCompactClassificationGAM_properties.htmlPropertiesJCompactClassificationGAM_methods.htmlMethodsHCompactClassificationNaiveBayes.html>CompactClassificationNaiveBayes^CompactClassificationNaiveBayes_properties.htmlPropertiesXCompactClassificationNaiveBayes_methods.htmlMethodsNCompactClassificationNeuralNetwork.htmlDCompactClassificationNeuralNetworkdCompactClassificationNeuralNetwork_properties.htmlProperties^CompactClassificationNeuralNetwork_methods.htmlMethods:CompactClassificationSVM.html0CompactClassificationSVMPCompactClassificationSVM_properties.htmlPropertiesJCompactClassificationSVM_methods.htmlMethods2CompactRegressionGAM.html(CompactRegressionGAMHCompactRegressionGAM_properties.htmlPropertiesBCompactRegressionGAM_methods.htmlMethods0CompactRegressionGP.html&CompactRegressionGPFCompactRegressionGP_properties.htmlProperties@CompactRegressionGP_methods.htmlMethodsFCompactRegressionNeuralNetwork.html<CompactRegressionNeuralNetwork\CompactRegressionNeuralNetwork_properties.htmlPropertiesVCompactRegressionNeuralNetwork_methods.htmlMethods2CompactRegressionSVM.html(CompactRegressionSVMHCompactRegressionSVM_properties.htmlPropertiesBCompactRegressionSVM_methods.htmlMethods$RegressionGAM.htmlRegressionGAM:RegressionGAM_properties.htmlProperties4RegressionGAM_methods.htmlMethods"RegressionGP.htmlRegressionGP8RegressionGP_properties.htmlProperties2RegressionGP_methods.htmlMethods*RegressionKernel.html RegressionKernel@RegressionKernel_properties.htmlProperties:RegressionKernel_methods.htmlMethods*RegressionLinear.html RegressionLinear@RegressionLinear_properties.htmlProperties:RegressionLinear_methods.htmlMethods8RegressionNeuralNetwork.html.RegressionNeuralNetworkNRegressionNeuralNetwork_properties.htmlPropertiesHRegressionNeuralNetwork_methods.htmlMethods@RegressionPartitionedKernel.html6RegressionPartitionedKernelVRegressionPartitionedKernel_properties.htmlPropertiesPRegressionPartitionedKernel_methods.htmlMethods@RegressionPartitionedLinear.html6RegressionPartitionedLinearVRegressionPartitionedLinear_properties.htmlPropertiesPRegressionPartitionedLinear_methods.htmlMethods>RegressionPartitionedModel.html4RegressionPartitionedModelTRegressionPartitionedModel_properties.htmlPropertiesNRegressionPartitionedModel_methods.htmlMethods$RegressionSVM.htmlRegressionSVM:RegressionSVM_properties.htmlProperties4RegressionSVM_methods.htmlMethodsClustering.htmlClustering>CalinskiHarabaszEvaluation.html4CalinskiHarabaszEvaluationTCalinskiHarabaszEvaluation_properties.htmlPropertiesNCalinskiHarabaszEvaluation_methods.htmlMethods*ClusterCriterion.html ClusterCriterion@ClusterCriterion_properties.htmlProperties:ClusterCriterion_methods.htmlMethods8DaviesBouldinEvaluation.html.DaviesBouldinEvaluationNDaviesBouldinEvaluation_properties.htmlPropertiesHDaviesBouldinEvaluation_methods.htmlMethods$GapEvaluation.htmlGapEvaluation:GapEvaluation_properties.htmlProperties4GapEvaluation_methods.htmlMethods&gmdistribution.htmlgmdistribution<gmdistribution_properties.htmlProperties6gmdistribution_methods.htmlMethods2SilhouetteEvaluation.html(SilhouetteEvaluationHSilhouetteEvaluation_properties.htmlPropertiesBSilhouetteEvaluation_methods.htmlMethods,Nearest_Neighbors.html"Nearest Neighbors.ExhaustiveSearcher.html$ExhaustiveSearcherDExhaustiveSearcher_properties.htmlProperties>ExhaustiveSearcher_methods.htmlMethods"hnswSearcher.htmlhnswSearcher8hnswSearcher_properties.htmlProperties2hnswSearcher_methods.htmlMethods&KDTreeSearcher.htmlKDTreeSearcher<KDTreeSearcher_properties.htmlProperties6KDTreeSearcher_methods.htmlMethods,Anomaly_Detection.html"Anomaly Detection(IsolationForest.htmlIsolationForest>IsolationForest_properties.htmlProperties8IsolationForest_methods.htmlMethods.LocalOutlierFactor.html$LocalOutlierFactorDLocalOutlierFactor_properties.htmlProperties>LocalOutlierFactor_methods.htmlMethods OneClassSVM.htmlOneClassSVM6OneClassSVM_properties.htmlProperties0OneClassSVM_methods.htmlMethods:Dimensionality_Reduction.html0Dimensionality Reduction,ReconstructionICA.html"ReconstructionICABReconstructionICA_properties.htmlProperties<ReconstructionICA_methods.htmlMethods(SparseFiltering.htmlSparseFiltering>SparseFiltering_properties.htmlProperties8SparseFiltering_methods.htmlMethods*Model_Evaluation.html Model Evaluation2ConfusionMatrixChart.html(ConfusionMatrixChartHConfusionMatrixChart_properties.htmlPropertiesBConfusionMatrixChart_methods.htmlMethods cvpartition.htmlcvpartition6cvpartition_properties.htmlProperties0cvpartition_methods.htmlMethodsrocmetrics.htmlrocmetrics4rocmetrics_properties.htmlProperties.rocmetrics_methods.htmlMethods$Markov_Models.htmlMarkov Models(Random_Sampling.htmlRandom Sampling2Distribution_Classes.html(Distribution Classes paretotails.htmlparetotails6paretotails_properties.htmlProperties0paretotails_methods.htmlMethodsBprob.ProbabilityDistribution.html8prob.ProbabilityDistribution4prob.BetaDistribution.html*prob.BetaDistributionJprob.BetaDistribution_properties.htmlPropertiesDprob.BetaDistribution_methods.htmlMethods<prob.BinomialDistribution.html2prob.BinomialDistributionRprob.BinomialDistribution_properties.htmlPropertiesLprob.BinomialDistribution_methods.htmlMethodsLprob.BirnbaumSaundersDistribution.htmlBprob.BirnbaumSaundersDistributionbprob.BirnbaumSaundersDistribution_properties.htmlProperties\prob.BirnbaumSaundersDistribution_methods.htmlMethods4prob.BurrDistribution.html*prob.BurrDistributionJprob.BurrDistribution_properties.htmlPropertiesDprob.BurrDistribution_methods.htmlMethodsBprob.ExponentialDistribution.html8prob.ExponentialDistributionXprob.ExponentialDistribution_properties.htmlPropertiesRprob.ExponentialDistribution_methods.htmlMethodsDprob.ExtremeValueDistribution.html:prob.ExtremeValueDistributionZprob.ExtremeValueDistribution_properties.htmlPropertiesTprob.ExtremeValueDistribution_methods.htmlMethods6prob.GammaDistribution.html,prob.GammaDistributionLprob.GammaDistribution_properties.htmlPropertiesFprob.GammaDistribution_methods.htmlMethodsZprob.GeneralizedExtremeValueDistribution.htmlPprob.GeneralizedExtremeValueDistributionpprob.GeneralizedExtremeValueDistribution_properties.htmlPropertiesjprob.GeneralizedExtremeValueDistribution_methods.htmlMethodsNprob.GeneralizedParetoDistribution.htmlDprob.GeneralizedParetoDistributiondprob.GeneralizedParetoDistribution_properties.htmlProperties^prob.GeneralizedParetoDistribution_methods.htmlMethods@prob.HalfNormalDistribution.html6prob.HalfNormalDistributionVprob.HalfNormalDistribution_properties.htmlPropertiesPprob.HalfNormalDistribution_methods.htmlMethodsJprob.InverseGaussianDistribution.html@prob.InverseGaussianDistribution`prob.InverseGaussianDistribution_properties.htmlPropertiesZprob.InverseGaussianDistribution_methods.htmlMethods8prob.KernelDistribution.html.prob.KernelDistributionNprob.KernelDistribution_properties.htmlPropertiesHprob.KernelDistribution_methods.htmlMethods<prob.LogisticDistribution.html2prob.LogisticDistributionRprob.LogisticDistribution_properties.htmlPropertiesLprob.LogisticDistribution_methods.htmlMethodsBprob.LoglogisticDistribution.html8prob.LoglogisticDistributionXprob.LoglogisticDistribution_properties.htmlPropertiesRprob.LoglogisticDistribution_methods.htmlMethods>prob.LognormalDistribution.html4prob.LognormalDistributionTprob.LognormalDistribution_properties.htmlPropertiesNprob.LognormalDistribution_methods.htmlMethods@prob.LoguniformDistribution.html6prob.LoguniformDistributionVprob.LoguniformDistribution_properties.htmlPropertiesPprob.LoguniformDistribution_methods.htmlMethodsBprob.MultinomialDistribution.html8prob.MultinomialDistributionXprob.MultinomialDistribution_properties.htmlPropertiesRprob.MultinomialDistribution_methods.htmlMethods<prob.NakagamiDistribution.html2prob.NakagamiDistributionRprob.NakagamiDistribution_properties.htmlPropertiesLprob.NakagamiDistribution_methods.htmlMethodsLprob.NegativeBinomialDistribution.htmlBprob.NegativeBinomialDistributionbprob.NegativeBinomialDistribution_properties.htmlProperties\prob.NegativeBinomialDistribution_methods.htmlMethods8prob.NormalDistribution.html.prob.NormalDistributionNprob.NormalDistribution_properties.htmlPropertiesHprob.NormalDistribution_methods.htmlMethodsJprob.PiecewiseLinearDistribution.html@prob.PiecewiseLinearDistribution`prob.PiecewiseLinearDistribution_properties.htmlPropertiesZprob.PiecewiseLinearDistribution_methods.htmlMethods:prob.PoissonDistribution.html0prob.PoissonDistributionPprob.PoissonDistribution_properties.htmlPropertiesJprob.PoissonDistribution_methods.htmlMethods<prob.RayleighDistribution.html2prob.RayleighDistributionRprob.RayleighDistribution_properties.htmlPropertiesLprob.RayleighDistribution_methods.htmlMethods8prob.RicianDistribution.html.prob.RicianDistributionNprob.RicianDistribution_properties.htmlPropertiesHprob.RicianDistribution_methods.htmlMethods8prob.StableDistribution.html.prob.StableDistributionNprob.StableDistribution_properties.htmlPropertiesHprob.StableDistribution_methods.htmlMethodsHprob.tLocationScaleDistribution.html>prob.tLocationScaleDistribution^prob.tLocationScaleDistribution_properties.htmlPropertiesXprob.tLocationScaleDistribution_methods.htmlMethods@prob.TriangularDistribution.html6prob.TriangularDistributionVprob.TriangularDistribution_properties.htmlPropertiesPprob.TriangularDistribution_methods.htmlMethods:prob.UniformDistribution.html0prob.UniformDistributionPprob.UniformDistribution_properties.htmlPropertiesJprob.UniformDistribution_methods.htmlMethods:prob.WeibullDistribution.html0prob.WeibullDistributionPprob.WeibullDistribution_properties.htmlPropertiesJprob.WeibullDistribution_methods.htmlMethods2Distribution_Fitting.html(Distribution Fitting6Distribution_Functions.html,Distribution Functions8Distribution_Statistics.html.Distribution Statistics4Distribution_Wrappers.html*Distribution WrappersPlotting.htmlPlottingI_O.htmlI/OUtilities.htmlUtilities &rX< t^H( j J 2  p B (  n T < " T >   t T < zfP8z^L2v`H4zV>& hH* fP8 b={bA&  cophenet cophenet# #clusterdata clusterdata cluster cluster svmtrainsvmtrain! !svmpredictsvmpredict gamtraingamtrain! !gampredictgampredict"' 'gamboosttraingamboosttrain&+ +gamboostpredictgamboostpredict"' 'gamboostpairsgamboostpairs"' 'gamboostintergamboostinter~ fitrsvmfitrsvm} fitrnetfitrnet|! !fitrlinearfitrlinear{! !fitrkernelfitrkernelz fitrgpfitrgpy fitrgamfitrgamx fitcsvmfitcsvmw fitcnetfitcnetv fitcnbfitcnbu! !fitclinearfitclineart fitcknnfitcknns! !fitckernelfitckernelr fitcgamfitcgamq fitcdiscrfitcdiscrp fcnntrainfcnntraino# #fcnnpredictfcnnpredictn! !stepwiselmstepwiselmm# #stepwiseglmstepwiseglml# #stepwisefitstepwisefitk robustfitrobustfitj ridgeridgei! !regress_gpregress_gph regressregressg! !plsregressplsregressf nlpredcinlpredcie nlparcinlparcid nlinfitnlinfit"c' 'mvregresslikemvregresslikeb mvregressmvregress&a+ +monotone_smoothmonotone_smooth` mnrvalmnrval_ mnrfitmnrfit.^3 3logistic_regressionlogistic_regression] lassoglmlassoglm\ lassolasso[ invpredinvpredZ glmvalglmvalY glmfitglmfitX fitnlmfitnlm W% %fitlmematrixfitlmematrixV fitlmefitlmeU fitlmfitlmT fitglmefitglmeS fitglmfitglmR fitcoxfitcoxQ coxphfitcoxphfitP x2fxx2fxO sigma_ptssigma_pts2N7 7parseWilkinsonFormulaparseWilkinsonFormulaM fullfactfullfactL ff2nff2nK ztest2ztest2J ztestztestI vartestnvartestnH vartest2vartest2G vartestvartestF ttest2ttest2E ttestttestD signtestsigntestC signranksignrankB# #sampsizepwrsampsizepwrA runstestrunstest(@- -regression_ttestregression_ttest(?- -regression_ftestregression_ftest> ranksumranksum=# #multcomparemultcompare <% %mcnemar_testmcnemar_test; manova1manova1:! !lillietestlillietest9# #levene_testlevene_test8 kstest2kstest27 kstestkstest"6' 'kruskalwalliskruskalwallis5 jbtestjbtest*4/ /hotelling_t2test2hotelling_t2test2(3- -hotelling_t2testhotelling_t2test2 friedmanfriedman1! !fishertestfishertest0 dwtestdwtest(/- -correlation_testcorrelation_test. chi2testchi2test- chi2gofchi2gof, binotestbinotest+ barttestbarttest"*' 'bartlett_testbartlett_test")' 'ansaribradleyansaribradley( anovananovan' anova2anova2& anova1anova1% adtestadtest$ tiedranktiedrank,#1 1standardizeMissingstandardizeMissing" rmmissingrmmissing!! !randsamplerandsample4 9 9normalise_distributionnormalise_distribution multiwaymultiway isoutlierisoutlier ismissingismissing grp2idxgrp2idx# #fillmissingfillmissing dummyvardummyvar! !datasampledatasample crosstabcrosstab combnkcombnk trimmeantrimmean tabulatetabulate % %partialcorripartialcorri# #partialcorrpartialcorr nanvarnanvar nansumnansum nanstdnanstd nanminnanmin nanmediannanmedian  nanmeannanmean  nanmaxnanmax  nancovnancov # #mvksdensitymvksdensity  ksdensityksdensity jackknifejackknife harmmeanharmmean grpstatsgrpstats geomeangeomean ecdfecdf dcovdcov# #cl_multinomcl_multinom cdfcalccdfcalc :oT;wbK* w \ C 0   l M :  z Y >   } b I .  } f M 6  lM8!lQ8}dI0jQ8pW>% vY<dO:%{hQ: gaminvgaminv gamcdfgamcdf frndfrnd fpdffpdf finvfinv fcdffcdf exprndexprnd exppdfexppdf expinvexpinv expcdfexpcdf evrndevrnd evpdfevpdf evinvevinv evcdfevcdf  copularndcopularnd  copulapdfcopulapdf  copulacdfcopulacdf  chi2rndchi2rnd  chi2pdfchi2pdf chi2invchi2inv chi2cdfchi2cdf cauchyrndcauchyrnd cauchypdfcauchypdf cauchyinvcauchyinv cauchycdfcauchycdf bvtcdfbvtcdf bvncdfbvncdf burrrndburrrnd burrpdfburrpdf~ burrinvburrinv} burrcdfburrcdf| bisarndbisarnd{ bisapdfbisapdfz bisainvbisainvy bisacdfbisacdfx binorndbinorndw binopdfbinopdfv binoinvbinoinvu binocdfbinocdft betarndbetarnds betapdfbetapdfr betainvbetainvq betacdfbetacdfp wbllikewbllikeo wblfitwblfitn unifitunifitm unidfitunidfitl tlsliketlslikek tlsfittlsfitj stbllikestbllikei stblfitstblfith ricelikericelikeg ricefitricefitf rayllikerayllikee raylfitraylfitd poisslikepoisslikec poissfitpoissfitb normlikenormlikea normfitnormfit` nbinlikenbinlike_ nbinfitnbinfit^ nakalikenakalike] nakafitnakafit\ lognlikelognlike[ lognfitlognfitZ logllikelogllikeY loglfitloglfitX logilikelogilikeW logifitlogifitV invglikeinvglikeU invgfitinvgfitT hnlikehnlikeS hnfithnfitR! !gumbellikegumbellikeQ gumbelfitgumbelfitP gplikegplikeO gpfitgpfitN# #gevfit_lmomgevfit_lmomM gevlikegevlikeL gevfitgevfitK geofitgeofitJ gamlikegamlikeI gamfitgamfitH explikeexplikeG expfitexpfitF evlikeevlikeE evfitevfitD copulafitcopulafitC burrlikeburrlikeB burrfitburrfitA bisalikebisalike@ bisafitbisafit? binolikebinolike> binofitbinofit= betalikebetalike< betafitbetafit;# #slicesampleslicesample: qrandnqrandn9 pearsrndpearsrnd8 mhsamplemhsample7 johnsrndjohnsrnd6! !hmmviterbihmmviterbi5 hmmtrainhmmtrain4# #hmmgeneratehmmgenerate3# #hmmestimatehmmestimate2 hmmdecodehmmdecode1 perfcurve perfcurve0 crossval crossval /% %confusionmat confusionmat$.) )confusionchart confusionchart- tsne tsne,! !sparsefilt sparsefilt"+' 'rotatefactors rotatefactors* rica rica)! !procrustes procrustes( princomp princomp' ppca ppca& pcares pcares% pcacov pcacov$ pca pca# nnmf nnmf" mdscale mdscale! factoran factoran  cmdscale cmdscale canoncorr canoncorr robustcov robustcov ocsvm ocsvm lof lof iforest iforest! !squareform squareform# #rangesearch rangesearch pdist2 pdist2 pdist pdist mahal mahal knnsearch knnsearch % %editDistance editDistance createns createns&+ +spectralcluster spectralcluster(- -optimalleaforder optimalleaforder linkage linkage kmedoids kmedoids kmeans kmeans % %inconsistent inconsistent  fitgmdist fitgmdist % %evalclusters evalclusters  dbscan dbscan Tv_H1oR=( h O 6  u V = $ u \ C *  v ] D +  i R ; $ y`G.nU<# t[B/ lU>% vaL7" {`E*oV=$ oT5 nakastatnakastat4 lognstatlognstat3 loglstatloglstat2 logistatlogistat1 invgstatinvgstat0 hygestathygestat/ hnstathnstat. gpstatgpstat- gevstatgevstat, geostatgeostat+ gamstatgamstat* fstatfstat) expstatexpstat( evstatevstat'! !copulastatcopulastat&# #copulaparamcopulaparam% chi2statchi2stat$ burrstatburrstat# bisastatbisastat" binostatbinostat! betastatbetastat  wishrndwishrnd wishpdfwishpdf wienrndwienrnd wblrndwblrnd wblpdfwblpdf wblinvwblinv wblcdfwblcdf vmrndvmrnd vmpdfvmpdf vminvvminv vmcdfvmcdf unifrndunifrnd unifpdfunifpdf unifinvunifinv unifcdfunifcdf unidrndunidrnd unidpdfunidpdf unidinvunidinv unidcdfunidcdf  trirndtrirnd  tripdftripdf  triinvtriinv  tricdftricdf  tlsrndtlsrnd tlspdftlspdf tlsinvtlsinv tlscdftlscdf trndtrnd tpdftpdf tinvtinv tcdftcdf stblrndstblrnd stblpdfstblpdf stblinvstblinv~ stblcdfstblcdf} ricerndricernd| ricepdfricepdf{ riceinvriceinvz ricecdfricecdfy raylrndraylrndx raylpdfraylpdfw raylinvraylinvv raylcdfraylcdfu poissrndpoissrndt poisspdfpoisspdfs poissinvpoissinvr poisscdfpoisscdfq plrndplrndp plpdfplpdfo plinvplinvn plcdfplcdfm normrndnormrndl normpdfnormpdfk norminvnorminvj normcdfnormcdfi ncx2rndncx2rndh ncx2pdfncx2pdfg ncx2invncx2invf ncx2cdfncx2cdfe nctrndnctrndd nctpdfnctpdfc nctinvnctinvb nctcdfnctcdfa ncfrndncfrnd` ncfpdfncfpdf_ ncfinvncfinv^ ncfcdfncfcdf] nbinrndnbinrnd\ nbinpdfnbinpdf[ nbininvnbininvZ nbincdfnbincdfY nakarndnakarndX nakapdfnakapdfW nakainvnakainvV nakacdfnakacdfU mvtrndmvtrndT mvtpdfmvtpdfS mvtcdfmvtcdfR mvnrndmvnrndQ mvnpdfmvnpdfP mvncdfmvncdfO mnrndmnrndN mnpdfmnpdfM lognrndlognrndL lognpdflognpdfK logninvlogninvJ logncdflogncdfI loglrndloglrndH loglpdfloglpdfG loglinvloglinvF loglcdfloglcdfE logirndlogirndD logipdflogipdfC logiinvlogiinvB logicdflogicdfA! !laplacerndlaplacernd@! !laplacepdflaplacepdf?! !laplaceinvlaplaceinv>! !laplacecdflaplacecdf= jsupdfjsupdf< jsucdfjsucdf; iwishrndiwishrnd: iwishpdfiwishpdf9 invgrndinvgrnd8 invgpdfinvgpdf7 invginvinvginv6 invgcdfinvgcdf5 hygerndhygernd4 hygepdfhygepdf3 hygeinvhygeinv2 hygecdfhygecdf1 hnrndhnrnd0 hnpdfhnpdf/ hninvhninv. hncdfhncdf- gumbelrndgumbelrnd, gumbelpdfgumbelpdf+ gumbelinvgumbelinv* gumbelcdfgumbelcdf) gprndgprnd( gppdfgppdf' gpinvgpinv& gpcdfgpcdf% gevrndgevrnd$ gevpdfgevpdf# gevinvgevinv" gevcdfgevcdf! georndgeornd  geopdfgeopdf geoinvgeoinv geocdfgeocdf gamrndgamrnd gampdfgampdf c}fK0nUB' w ^ E & i D )  y b I * w ^ I 0 | E uFd/?Vs&u>Tq$FK KCompactLinearModel.ResponseNameCompactLinearModel.ResponseNameJO OCompactLinearModel.PredictorNamesCompactLinearModel.PredictorNamesFK KCompactLinearModel.NumVariablesCompactLinearModel.NumVariablesHM MCompactLinearModel.NumPredictorsCompactLinearModel.NumPredictorsLQ QCompactLinearModel.NumObservationsCompactLinearModel.NumObservations<A ACompactLinearModel.FormulaCompactLinearModel.Formula:? ?CompactLinearModel.RobustCompactLinearModel.Robust49 9CompactLinearModel.SSTCompactLinearModel.SST49 9CompactLinearModel.SSRCompactLinearModel.SSR49 9CompactLinearModel.SSECompactLinearModel.SSE>C CCompactLinearModel.RsquaredCompactLinearModel.Rsquared6 ; ;CompactLinearModel.RMSECompactLinearModel.RMSE4 9 9CompactLinearModel.MSECompactLinearModel.MSEJ O OCompactLinearModel.ModelCriterionCompactLinearModel.ModelCriterionH M MCompactLinearModel.LogLikelihoodCompactLinearModel.LogLikelihood4 9 9CompactLinearModel.DFECompactLinearModel.DFE^c cCompactLinearModel.NumEstimatedCoefficientsCompactLinearModel.NumEstimatedCoefficientsLQ QCompactLinearModel.NumCoefficientsCompactLinearModel.NumCoefficientsFK KCompactLinearModel.CoefficientsCompactLinearModel.CoefficientsNS SCompactLinearModel.CoefficientNamesCompactLinearModel.CoefficientNamesX] ]CompactLinearModel.CoefficientCovarianceCompactLinearModel.CoefficientCovariance,1 1CompactLinearModelCompactLinearModel*/ /anova.multcompareanova.multcompare6; ;anova.varianceComponentanova.varianceComponent27 7anova.plotComparisonsanova.plotComparisons$) )anova.boxchartanova.boxchart(~- -anova.groupmeansanova.groupmeans}# #anova.statsanova.stats|# #anova.anovaanova.anova"{' 'anova.Metricsanova.Metrics&z+ +anova.Residualsanova.Residuals,y1 1anova.Coefficientsanova.Coefficients2x7 7anova.NumObservationsanova.NumObservations,w1 1anova.ResponseNameanova.ResponseName8v= =anova.CategoricalFactorsanova.CategoricalFactors.u3 3anova.RandomFactorsanova.RandomFactors4t9 9anova.SumOfSquaresTypeanova.SumOfSquaresType:s? ?anova.ExpandedFactorNamesanova.ExpandedFactorNames*r/ /anova.FactorNamesanova.FactorNames"q' 'anova.Formulaanova.Formula"p' 'anova.Factorsanova.Factorso anova.Yanova.Yn anovaanovam statsetstatsetl statgetstatgetk probitprobitj makimamakimai logitlogith cholcovcholcovg loadmodelloadmodelf# #libsvmwritelibsvmwritee! !libsvmreadlibsvmreadd wblplotwblplotc violinviolinb! !silhouettesilhouettea# #scatterhistscatterhist` qqplotqqplot_ probplotprobplot^ ppplotppplot$]) )parallelcoordsparallelcoords\ normplotnormplot"[' 'manovaclustermanovaclusterZ histfithistfitY hist3hist3X gscattergscatterW# #gplotmatrixgplotmatrixV glyphplotglyphplotU einsteineinsteinT ecdfhistecdfhistS! !dendrogramdendrogramR cdfplotcdfplotQ boxplotboxplotP biplotbiplotO bar3hbar3hN bar3bar3M# #andrewsplotandrewsplotL randomrandomK pdfpdfJ mlecovmlecovI mlemleH makedistmakedistG icdficdfF fitdistfitdistE cdfcdfD wblstatwblstatC unifstatunifstatB unidstatunidstatA tstattstat@ tristattristat? tlsstattlsstat> ricestatricestat= raylstatraylstat< poisstatpoisstat; plstatplstat: normstatnormstat9 ncx2statncx2stat8 nctstatnctstat7 ncfstatncfstat6 nbinstatnbinstat 8l/r7 f K   F  9  ) x O yL eX/ovG0NPS SGeneralizedLinearMixedModel.predict#GeneralizedLinearMixedModel.predictROW WGeneralizedLinearMixedModel.residuals#GeneralizedLinearMixedModel.residualsLNQ QGeneralizedLinearMixedModel.fitted#GeneralizedLinearMixedModel.fittedhMm mGeneralizedLinearMixedModel.covarianceParameters#GeneralizedLinearMixedModel.covarianceParametersZL_ _GeneralizedLinearMixedModel.randomEffects#GeneralizedLinearMixedModel.randomEffectsXK] ]GeneralizedLinearMixedModel.fixedEffects#GeneralizedLinearMixedModel.fixedEffectsvJ{ {GeneralizedLinearMixedModel.GeneralizedLinearMixedModel#GeneralizedLinearMixedModel.GeneralizedLinearMixedModelXI] ]GeneralizedLinearMixedModel.ResponseName"GeneralizedLinearMixedModel.ResponseNameNHS SGeneralizedLinearMixedModel.Formula"GeneralizedLinearMixedModel.FormulaFGK KGeneralizedLinearMixedModel.DFE"GeneralizedLinearMixedModel.DFE\Fa aGeneralizedLinearMixedModel.ModelCriterion"GeneralizedLinearMixedModel.ModelCriterionZE_ _GeneralizedLinearMixedModel.LogLikelihood"GeneralizedLinearMixedModel.LogLikelihood`De eGeneralizedLinearMixedModel.CoefficientNames"GeneralizedLinearMixedModel.CoefficientNamesjCo oGeneralizedLinearMixedModel.CoefficientCovariance"GeneralizedLinearMixedModel.CoefficientCovarianceXB] ]GeneralizedLinearMixedModel.Coefficients"GeneralizedLinearMixedModel.Coefficients^Ac cGeneralizedLinearMixedModel.NumCoefficients"GeneralizedLinearMixedModel.NumCoefficients^@c cGeneralizedLinearMixedModel.NumObservations"GeneralizedLinearMixedModel.NumObservationsT?Y YGeneralizedLinearMixedModel.Dispersion"GeneralizedLinearMixedModel.DispersionR>W WGeneralizedLinearMixedModel.FitMethod"GeneralizedLinearMixedModel.FitMethodH=M MGeneralizedLinearMixedModel.Link"GeneralizedLinearMixedModel.LinkX<] ]GeneralizedLinearMixedModel.Distribution"GeneralizedLinearMixedModel.Distribution>;C CGeneralizedLinearMixedModel!GeneralizedLinearMixedModel*:/ /CoxModel.survival CoxModel.survival297 7CoxModel.plotSurvival CoxModel.plotSurvival.83 3CoxModel.linhyptest CoxModel.linhyptest075 5CoxModel.hazardratio CoxModel.hazardratio:6? ?CoxModel.discardResiduals CoxModel.discardResiduals&5+ +CoxModel.coefci CoxModel.coefci*4/ /CoxModel.CoxModel CoxModel.CoxModel237 7CoxModel.VariableInfoCoxModel.VariableInfoL2Q QCoxModel.LikelihoodRatioTestPValueCoxModel.LikelihoodRatioTestPValueX1] ]CoxModel.ProportionalHazardsPValueGlobalCoxModel.ProportionalHazardsPValueGlobalL0Q QCoxModel.ProportionalHazardsPValueCoxModel.ProportionalHazardsPValue,/1 1CoxModel.ResidualsCoxModel.Residuals4.9 9CoxModel.StandardErrorCoxModel.StandardErrorD-I ICoxModel.CoefficientCovarianceCoxModel.CoefficientCovariance6,; ;CoxModel.StratificationCoxModel.Stratification*+/ /CoxModel.BaselineCoxModel.Baseline(*- -CoxModel.FormulaCoxModel.Formula2)7 7CoxModel.ResponseNameCoxModel.ResponseName6(; ;CoxModel.PredictorNamesCoxModel.PredictorNames&'+ +CoxModel.HazardCoxModel.Hazard4&9 9CoxModel.LogLikelihoodCoxModel.LogLikelihood4%9 9CoxModel.NumPredictorsCoxModel.NumPredictors2$7 7CoxModel.CoefficientsCoxModel.Coefficients# CoxModelCoxModel8"= =CompactLinearModel.anovaCompactLinearModel.anovaL!Q QCompactLinearModel.plotInteractionCompactLinearModel.plotInteractionD I ICompactLinearModel.plotEffectsCompactLinearModel.plotEffects8= =CompactLinearModel.fevalCompactLinearModel.feval:? ?CompactLinearModel.randomCompactLinearModel.random<A ACompactLinearModel.predictCompactLinearModel.predict>C CCompactLinearModel.coefTestCompactLinearModel.coefTest:? ?CompactLinearModel.coefCICompactLinearModel.coefCIHM MCompactLinearModel.VariableNamesCompactLinearModel.VariableNamesFK KCompactLinearModel.VariableInfoCompactLinearModel.VariableInfo 2`. r  _  3 B c r3poz/8Ix'8^c cGeneralizedLinearModel.plotAdjustedResponse&GeneralizedLinearModel.plotAdjustedResponseLQ QGeneralizedLinearModel.plotEffects&GeneralizedLinearModel.plotEffectsTY YGeneralizedLinearModel.plotDiagnostics&GeneralizedLinearModel.plotDiagnosticsPU UGeneralizedLinearModel.plotResiduals&GeneralizedLinearModel.plotResidualsB~G GGeneralizedLinearModel.random&GeneralizedLinearModel.randomN}S SGeneralizedLinearModel.devianceTest&GeneralizedLinearModel.devianceTestF|K KGeneralizedLinearModel.coefTest&GeneralizedLinearModel.coefTestB{G GGeneralizedLinearModel.coefCI&GeneralizedLinearModel.coefCI@zE EGeneralizedLinearModel.feval&GeneralizedLinearModel.fevalDyI IGeneralizedLinearModel.predict&GeneralizedLinearModel.predictbxg gGeneralizedLinearModel.GeneralizedLinearModel&GeneralizedLinearModel.GeneralizedLinearModel@wE EGeneralizedLinearModel.Steps%GeneralizedLinearModel.StepsDvI IGeneralizedLinearModel.Formula%GeneralizedLinearModel.FormulaVu[ [GeneralizedLinearModel.ObservationNames%GeneralizedLinearModel.ObservationNamesTtY YGeneralizedLinearModel.ObservationInfo%GeneralizedLinearModel.ObservationInfoHsM MGeneralizedLinearModel.Variables%GeneralizedLinearModel.VariablesNrS SGeneralizedLinearModel.VariableInfo%GeneralizedLinearModel.VariableInfoNqS SGeneralizedLinearModel.NumVariables%GeneralizedLinearModel.NumVariablesPpU UGeneralizedLinearModel.VariableNames%GeneralizedLinearModel.VariableNamesRoW WGeneralizedLinearModel.PredictorNames%GeneralizedLinearModel.PredictorNamesNnS SGeneralizedLinearModel.ResponseName%GeneralizedLinearModel.ResponseNameXm] ]GeneralizedLinearModel.LikelihoodPenalty%GeneralizedLinearModel.LikelihoodPenaltyBlG GGeneralizedLinearModel.Offset%GeneralizedLinearModel.OffsetbC CGeneralizedLinearModel.Link%GeneralizedLinearModel.LinkNaS SGeneralizedLinearModel.Distribution%GeneralizedLinearModel.Distribution\`a aGeneralizedLinearModel.DispersionEstimated%GeneralizedLinearModel.DispersionEstimatedJ_O OGeneralizedLinearModel.Dispersion%GeneralizedLinearModel.Dispersion<^A AGeneralizedLinearModel.DFE%GeneralizedLinearModel.DFEF]K KGeneralizedLinearModel.Deviance%GeneralizedLinearModel.DevianceT\Y YGeneralizedLinearModel.NumObservations%GeneralizedLinearModel.NumObservationsP[U UGeneralizedLinearModel.NumPredictors%GeneralizedLinearModel.NumPredictorsfZk kGeneralizedLinearModel.NumEstimatedCoefficients%GeneralizedLinearModel.NumEstimatedCoefficientsTYY YGeneralizedLinearModel.NumCoefficients%GeneralizedLinearModel.NumCoefficients`Xe eGeneralizedLinearModel.CoefficientCovariance%GeneralizedLinearModel.CoefficientCovarianceVW[ [GeneralizedLinearModel.CoefficientNames%GeneralizedLinearModel.CoefficientNamesNVS SGeneralizedLinearModel.Coefficients%GeneralizedLinearModel.Coefficients4U9 9GeneralizedLinearModel$GeneralizedLinearModelXT] ]GeneralizedLinearMixedModel.designMatrix#GeneralizedLinearMixedModel.designMatrixLSQ QGeneralizedLinearMixedModel.coefCI#GeneralizedLinearMixedModel.coefCIPRU UGeneralizedLinearMixedModel.coefTest#GeneralizedLinearMixedModel.coefTestJQO OGeneralizedLinearMixedModel.anova#GeneralizedLinearMixedModel.anova AQc. { D  b ! U b  y 2yFr-TLZ9n-xI [&&C+ +LinearModel.SSE.LinearModel.SSE0B5 5LinearModel.Rsquared.LinearModel.Rsquared(A- -LinearModel.RMSE.LinearModel.RMSE2@7 7LinearModel.Residuals.LinearModel.Residuals&?+ +LinearModel.MSE.LinearModel.MSEF>K KLinearModel.ModelFitVsNullModel.LinearModel.ModelFitVsNullModel<=A ALinearModel.ModelCriterion.LinearModel.ModelCriterion:<? ?LinearModel.LogLikelihood.LinearModel.LogLikelihood,;1 1LinearModel.Fitted.LinearModel.Fitted6:; ;LinearModel.Diagnostics.LinearModel.Diagnostics&9+ +LinearModel.DFE.LinearModel.DFEP8U ULinearModel.NumEstimatedCoefficients.LinearModel.NumEstimatedCoefficients>7C CLinearModel.NumCoefficients.LinearModel.NumCoefficients86= =LinearModel.Coefficients.LinearModel.Coefficients@5E ELinearModel.CoefficientNames.LinearModel.CoefficientNamesJ4O OLinearModel.CoefficientCovariance.LinearModel.CoefficientCovariance3# #LinearModel-LinearModelB2G GLinearMixedModel.designMatrix,LinearMixedModel.designMatrix61; ;LinearMixedModel.coefCI,LinearMixedModel.coefCI:0? ?LinearMixedModel.coefTest,LinearMixedModel.coefTest4/9 9LinearMixedModel.anova,LinearMixedModel.anova8.= =LinearMixedModel.predict,LinearMixedModel.predict<-A ALinearMixedModel.residuals,LinearMixedModel.residuals6,; ;LinearMixedModel.fitted,LinearMixedModel.fittedR+W WLinearMixedModel.covarianceParameters,LinearMixedModel.covarianceParametersD*I ILinearMixedModel.randomEffects,LinearMixedModel.randomEffectsB)G GLinearMixedModel.fixedEffects,LinearMixedModel.fixedEffectsJ(O OLinearMixedModel.LinearMixedModel,LinearMixedModel.LinearMixedModelB'G GLinearMixedModel.ResponseName+LinearMixedModel.ResponseName8&= =LinearMixedModel.Formula+LinearMixedModel.Formula0%5 5LinearMixedModel.DFE+LinearMixedModel.DFE0$5 5LinearMixedModel.MSE+LinearMixedModel.MSE0#5 5LinearMixedModel.SST+LinearMixedModel.SST0"5 5LinearMixedModel.SSR+LinearMixedModel.SSR0!5 5LinearMixedModel.SSE+LinearMixedModel.SSE: ? ?LinearMixedModel.Rsquared+LinearMixedModel.RsquaredFK KLinearMixedModel.ModelCriterion+LinearMixedModel.ModelCriterionDI ILinearMixedModel.LogLikelihood+LinearMixedModel.LogLikelihoodJO OLinearMixedModel.CoefficientNames+LinearMixedModel.CoefficientNamesTY YLinearMixedModel.CoefficientCovariance+LinearMixedModel.CoefficientCovarianceBG GLinearMixedModel.Coefficients+LinearMixedModel.CoefficientsZ_ _LinearMixedModel.NumEstimatedCoefficients+LinearMixedModel.NumEstimatedCoefficientsHM MLinearMixedModel.NumCoefficients+LinearMixedModel.NumCoefficientsHM MLinearMixedModel.NumObservations+LinearMixedModel.NumObservations<A ALinearMixedModel.FitMethod+LinearMixedModel.FitMethod(- -LinearMixedModel*LinearMixedModel05 5LinearFormula.string)LinearFormula.string,1 1LinearFormula.char)LinearFormula.char>C CLinearFormula.LinearFormula)LinearFormula.LinearFormula:? ?LinearFormula.NPredictors(LinearFormula.NPredictors.3 3LinearFormula.NVars(LinearFormula.NVars05 5LinearFormula.NTerms(LinearFormula.NTerms>C CLinearFormula.FunctionCalls(LinearFormula.FunctionCalls49 9LinearFormula.ModelFun(LinearFormula.ModelFun, 1 1LinearFormula.Link(LinearFormula.LinkB G GLinearFormula.LinearPredictor(LinearFormula.LinearPredictor< A ALinearFormula.HasIntercept(LinearFormula.HasIntercept2 7 7LinearFormula.InModel(LinearFormula.InModel. 3 3LinearFormula.Terms(LinearFormula.Terms6; ;LinearFormula.TermNames(LinearFormula.TermNames@E ELinearFormula.PredictorNames(LinearFormula.PredictorNames>C CLinearFormula.VariableNames(LinearFormula.VariableNames<A ALinearFormula.ResponseName(LinearFormula.ResponseName"' 'LinearFormula'LinearFormulaHM MGeneralizedLinearModel.plotAdded&GeneralizedLinearModel.plotAdded DR!h' j / S $ f 3 | C W &f*qBASao:B<A AClassificationDiscriminant3ClassificationDiscriminant8= =NonLinearModel.plotSlice2NonLinearModel.plotSliceDI INonLinearModel.plotDiagnostics2NonLinearModel.plotDiagnostics@E ENonLinearModel.plotResiduals2NonLinearModel.plotResiduals6; ;NonLinearModel.coefTest2NonLinearModel.coefTest27 7NonLinearModel.coefCI2NonLinearModel.coefCI27 7NonLinearModel.random2NonLinearModel.random05 5NonLinearModel.feval2NonLinearModel.feval49 9NonLinearModel.predict2NonLinearModel.predictB~G GNonLinearModel.NonLinearModel2NonLinearModel.NonLinearModel@}E ENonLinearModel.VariableNames1NonLinearModel.VariableNamesB|G GNonLinearModel.PredictorNames1NonLinearModel.PredictorNames>{C CNonLinearModel.ResponseName1NonLinearModel.ResponseName4z9 9NonLinearModel.Formula1NonLinearModel.Formula2y7 7NonLinearModel.Robust1NonLinearModel.Robust2x7 7NonLinearModel.Fitted1NonLinearModel.Fitted8w= =NonLinearModel.Residuals1NonLinearModel.Residuals6v; ;NonLinearModel.Rsquared1NonLinearModel.RsquaredBuG GNonLinearModel.ModelCriterion1NonLinearModel.ModelCriterion@tE ENonLinearModel.LogLikelihood1NonLinearModel.LogLikelihood,s1 1NonLinearModel.SSR1NonLinearModel.SSR,r1 1NonLinearModel.SST1NonLinearModel.SST,q1 1NonLinearModel.SSE1NonLinearModel.SSE.p3 3NonLinearModel.RMSE1NonLinearModel.RMSE,o1 1NonLinearModel.MSE1NonLinearModel.MSE,n1 1NonLinearModel.DFE1NonLinearModel.DFEDmI INonLinearModel.NumObservations1NonLinearModel.NumObservations@lE ENonLinearModel.NumPredictors1NonLinearModel.NumPredictorsVk[ [NonLinearModel.NumEstimatedCoefficients1NonLinearModel.NumEstimatedCoefficientsDjI INonLinearModel.NumCoefficients1NonLinearModel.NumCoefficientsPiU UNonLinearModel.CoefficientCovariance1NonLinearModel.CoefficientCovarianceFhK KNonLinearModel.CoefficientNames1NonLinearModel.CoefficientNames>gC CNonLinearModel.Coefficients1NonLinearModel.Coefficients$f) )NonLinearModel0NonLinearModel(e- -LinearModel.step/LinearModel.step*d/ /LinearModel.anova/LinearModel.anova.c3 3LinearModel.compact/LinearModel.compact>bC CLinearModel.plotInteraction/LinearModel.plotInteraction(a- -LinearModel.plot/LinearModel.plot2`7 7LinearModel.plotAdded/LinearModel.plotAddedH_M MLinearModel.plotAdjustedResponse/LinearModel.plotAdjustedResponse6^; ;LinearModel.plotEffects/LinearModel.plotEffects>]C CLinearModel.plotDiagnostics/LinearModel.plotDiagnostics:\? ?LinearModel.plotResiduals/LinearModel.plotResiduals6[; ;LinearModel.removeTerms/LinearModel.removeTerms0Z5 5LinearModel.addTerms/LinearModel.addTerms,Y1 1LinearModel.dwtest/LinearModel.dwtest0X5 5LinearModel.coefTest/LinearModel.coefTest,W1 1LinearModel.coefCI/LinearModel.coefCI*V/ /LinearModel.feval/LinearModel.feval,U1 1LinearModel.random/LinearModel.random.T3 3LinearModel.predict/LinearModel.predict6S; ;LinearModel.LinearModel/LinearModel.LinearModel2R7 7LinearModel.Variables.LinearModel.Variables:Q? ?LinearModel.VariableNames.LinearModel.VariableNames8P= =LinearModel.VariableInfo.LinearModel.VariableInfo8O= =LinearModel.ResponseName.LinearModel.ResponseNameLC CLinearModel.ObservationInfo.LinearModel.ObservationInfo8K= =LinearModel.NumVariables.LinearModel.NumVariables:J? ?LinearModel.NumPredictors.LinearModel.NumPredictors>IC CLinearModel.NumObservations.LinearModel.NumObservations.H3 3LinearModel.Formula.LinearModel.Formula*G/ /LinearModel.Steps.LinearModel.Steps,F1 1LinearModel.Robust.LinearModel.Robust&E+ +LinearModel.SST.LinearModel.SST&D+ +LinearModel.SSR.LinearModel.SSR 0z7, v E `  e  j .O2?R ur{NJ7O OClassificationGAM.NumObservations7ClassificationGAM.NumObservations.63 3ClassificationGAM.Y7ClassificationGAM.Y.53 3ClassificationGAM.X7ClassificationGAM.X*4/ /ClassificationGAM6ClassificationGAMN3S SClassificationDiscriminant.cvshrink5ClassificationDiscriminant.cvshrinkP2U UClassificationDiscriminant.savemodel5ClassificationDiscriminant.savemodelP1U UClassificationDiscriminant.resubLoss5ClassificationDiscriminant.resubLossP0U UClassificationDiscriminant.resubEdge5ClassificationDiscriminant.resubEdgeT/Y YClassificationDiscriminant.resubMargin5ClassificationDiscriminant.resubMarginV.[ [ClassificationDiscriminant.resubPredict5ClassificationDiscriminant.resubPredictF-K KClassificationDiscriminant.logp5ClassificationDiscriminant.logpH,M MClassificationDiscriminant.mahal5ClassificationDiscriminant.mahalF+K KClassificationDiscriminant.edge5ClassificationDiscriminant.edgeL*Q QClassificationDiscriminant.compact5ClassificationDiscriminant.compactN)S SClassificationDiscriminant.crossval5ClassificationDiscriminant.crossvalJ(O OClassificationDiscriminant.margin5ClassificationDiscriminant.marginF'K KClassificationDiscriminant.loss5ClassificationDiscriminant.lossL&Q QClassificationDiscriminant.predict5ClassificationDiscriminant.predictX%] ]ClassificationDiscriminant.nLinearCoeffs5ClassificationDiscriminant.nLinearCoeffsr$w wClassificationDiscriminant.ClassificationDiscriminant5ClassificationDiscriminant.ClassificationDiscriminantZ#_ _ClassificationDiscriminant.ScoreTransform4ClassificationDiscriminant.ScoreTransformH"M MClassificationDiscriminant.Prior4ClassificationDiscriminant.PriorF!K KClassificationDiscriminant.Cost4ClassificationDiscriminant.CostH M MClassificationDiscriminant.Delta4ClassificationDiscriminant.DeltaHM MClassificationDiscriminant.Gamma4ClassificationDiscriminant.GammaTY YClassificationDiscriminant.DiscrimType4ClassificationDiscriminant.DiscrimType  ClassificationDiscriminant.HyperparameterOptimizationResults4ClassificationDiscriminant.HyperparameterOptimizationResults\a aClassificationDiscriminant.ModelParameters4ClassificationDiscriminant.ModelParametersNS SClassificationDiscriminant.BinEdges4ClassificationDiscriminant.BinEdgesPU UClassificationDiscriminant.XCentered4ClassificationDiscriminant.XCenteredTY YClassificationDiscriminant.LogDetSigma4ClassificationDiscriminant.LogDetSigmaNS SClassificationDiscriminant.MinGamma4ClassificationDiscriminant.MinGammaZ_ _ClassificationDiscriminant.DeltaPredictor4ClassificationDiscriminant.DeltaPredictorJO OClassificationDiscriminant.Coeffs4ClassificationDiscriminant.CoeffsBG GClassificationDiscriminant.Mu4ClassificationDiscriminant.MuHM MClassificationDiscriminant.Sigma4ClassificationDiscriminant.SigmaRW WClassificationDiscriminant.ClassNames4ClassificationDiscriminant.ClassNamesV[ [ClassificationDiscriminant.ResponseName4ClassificationDiscriminant.ResponseNamejo oClassificationDiscriminant.ExpandedPredictorNames4ClassificationDiscriminant.ExpandedPredictorNameshm mClassificationDiscriminant.CategoricalPredictors4ClassificationDiscriminant.CategoricalPredictorsV[ [ClassificationDiscriminant.BetweenSigma4ClassificationDiscriminant.BetweenSigmaZ_ _ClassificationDiscriminant.PredictorNames4ClassificationDiscriminant.PredictorNamesX ] ]ClassificationDiscriminant.NumPredictors4ClassificationDiscriminant.NumPredictorsN S SClassificationDiscriminant.RowsUsed4ClassificationDiscriminant.RowsUsed\ a aClassificationDiscriminant.NumObservations4ClassificationDiscriminant.NumObservations@ E EClassificationDiscriminant.Y4ClassificationDiscriminant.Y@ E EClassificationDiscriminant.X4ClassificationDiscriminant.X@E EClassificationDiscriminant.W4ClassificationDiscriminant.W 8x-j- t ? n = H  . I `w:LHX DC4>oC CClassificationKernel.Lambda:ClassificationKernel.LambdaFnK KClassificationKernel.FittedLoss:ClassificationKernel.FittedLoss^mc cClassificationKernel.NumExpansionDimensions:ClassificationKernel.NumExpansionDimensions^lc cClassificationKernel.ExpandedPredictorNames:ClassificationKernel.ExpandedPredictorNamesJkO OClassificationKernel.ResponseName:ClassificationKernel.ResponseName\ja aClassificationKernel.CategoricalPredictors:ClassificationKernel.CategoricalPredictorsNiS SClassificationKernel.PredictorNames:ClassificationKernel.PredictorNamesNhS SClassificationKernel.ScoreTransform:ClassificationKernel.ScoreTransform:g? ?ClassificationKernel.Cost:ClassificationKernel.CostaC CClassificationGAM.savemodel8ClassificationGAM.savemodel>`C CClassificationGAM.resubLoss8ClassificationGAM.resubLoss>_C CClassificationGAM.resubEdge8ClassificationGAM.resubEdgeB^G GClassificationGAM.resubMargin8ClassificationGAM.resubMarginD]I IClassificationGAM.resubPredict8ClassificationGAM.resubPredict4\9 9ClassificationGAM.loss8ClassificationGAM.loss4[9 9ClassificationGAM.edge8ClassificationGAM.edge8Z= =ClassificationGAM.margin8ClassificationGAM.margin:Y? ?ClassificationGAM.compact8ClassificationGAM.compactQC CClassificationGAM.TreeModel7ClassificationGAM.TreeModel>PC CClassificationGAM.FitMethod7ClassificationGAM.FitMethodTOY YClassificationGAM.ReasonForTermination7ClassificationGAM.ReasonForTerminationJNO OClassificationGAM.ModelParameters7ClassificationGAM.ModelParametersVM[ [ClassificationGAM.PairDetectionBinEdges7ClassificationGAM.PairDetectionBinEdgesKC CClassificationGAM.IntMatrix7ClassificationGAM.IntMatrix>JC CClassificationGAM.ModelwInt7ClassificationGAM.ModelwInt>IC CClassificationGAM.BaseModel7ClassificationGAM.BaseModelXH] ]ClassificationGAM.ExpandedPredictorNames7ClassificationGAM.ExpandedPredictorNamesVG[ [ClassificationGAM.CategoricalPredictors7ClassificationGAM.CategoricalPredictors.F3 3ClassificationGAM.W7ClassificationGAM.W>EC CClassificationGAM.Intercept7ClassificationGAM.InterceptFDK KClassificationGAM.NumIterations7ClassificationGAM.NumIterationsDCI IClassificationGAM.LearningRate7ClassificationGAM.LearningRate2B7 7ClassificationGAM.DoF7ClassificationGAM.DoF6A; ;ClassificationGAM.Order7ClassificationGAM.Order6@; ;ClassificationGAM.Knots7ClassificationGAM.KnotsD?I IClassificationGAM.Interactions7ClassificationGAM.Interactions:>? ?ClassificationGAM.Formula7ClassificationGAM.Formula6=; ;ClassificationGAM.Prior7ClassificationGAM.Prior@<E EClassificationGAM.ClassNames7ClassificationGAM.ClassNamesD;I IClassificationGAM.ResponseName7ClassificationGAM.ResponseNameH:M MClassificationGAM.PredictorNames7ClassificationGAM.PredictorNamesF9K KClassificationGAM.NumPredictors7ClassificationGAM.NumPredictors<8A AClassificationGAM.RowsUsed7ClassificationGAM.RowsUsed :\V u 8 s F  f ' : U g(j#XZ}@ }>{:<:)? ?ClassificationLinear.Cost@ClassificationLinear.Cost<(A AClassificationLinear.Prior@ClassificationLinear.PriorF'K KClassificationLinear.ClassNames@ClassificationLinear.ClassNames0&5 5ClassificationLinear?ClassificationLinear>%C CClassificationKNN.savemodel>ClassificationKNN.savemodel>$C CClassificationKNN.resubLoss>ClassificationKNN.resubLoss>#C CClassificationKNN.resubEdge>ClassificationKNN.resubEdgeB"G GClassificationKNN.resubMargin>ClassificationKNN.resubMarginD!I IClassificationKNN.resubPredict>ClassificationKNN.resubPredict4 9 9ClassificationKNN.edge>ClassificationKNN.edge<A AClassificationKNN.crossval>ClassificationKNN.crossvalNS SClassificationKNN.partialDependence>ClassificationKNN.partialDependence8= =ClassificationKNN.margin>ClassificationKNN.margin49 9ClassificationKNN.loss>ClassificationKNN.loss:? ?ClassificationKNN.predict>ClassificationKNN.predictNS SClassificationKNN.ClassificationKNN>ClassificationKNN.ClassificationKNN>C CClassificationKNN.CacheSize=ClassificationKNN.CacheSizeHM MClassificationKNN.ScoreTransform=ClassificationKNN.ScoreTransform6; ;ClassificationKNN.Prior=ClassificationKNN.Prior49 9ClassificationKNN.Cost=ClassificationKNN.CostFK KClassificationKNN.DistParameter=ClassificationKNN.DistParameterBG GClassificationKNN.IncludeTies=ClassificationKNN.IncludeTies>C CClassificationKNN.BreakTies=ClassificationKNN.BreakTiesHM MClassificationKNN.DistanceWeight=ClassificationKNN.DistanceWeight<A AClassificationKNN.Distance=ClassificationKNN.DistanceDI IClassificationKNN.NumNeighbors=ClassificationKNN.NumNeighborsns sClassificationKNN.HyperparameterOptimizationResults=ClassificationKNN.HyperparameterOptimizationResultsJO OClassificationKNN.ModelParameters=ClassificationKNN.ModelParameters< A AClassificationKNN.BinEdges=ClassificationKNN.BinEdges@ E EClassificationKNN.BucketSize=ClassificationKNN.BucketSize< A AClassificationKNN.NSMethod=ClassificationKNN.NSMethod0 5 5ClassificationKNN.Mu=ClassificationKNN.Mu6 ; ;ClassificationKNN.Sigma=ClassificationKNN.Sigma@E EClassificationKNN.ClassNames=ClassificationKNN.ClassNamesDI IClassificationKNN.ResponseName=ClassificationKNN.ResponseNameX] ]ClassificationKNN.ExpandedPredictorNames=ClassificationKNN.ExpandedPredictorNamesV[ [ClassificationKNN.CategoricalPredictors=ClassificationKNN.CategoricalPredictorsHM MClassificationKNN.PredictorNames=ClassificationKNN.PredictorNamesFK KClassificationKNN.NumPredictors=ClassificationKNN.NumPredictors<A AClassificationKNN.RowsUsed=ClassificationKNN.RowsUsedJO OClassificationKNN.NumObservations=ClassificationKNN.NumObservations.3 3ClassificationKNN.Y=ClassificationKNN.Y.3 3ClassificationKNN.X=ClassificationKNN.X.~3 3ClassificationKNN.W=ClassificationKNN.W*}/ /ClassificationKNN{C CClassificationKernel.resume;ClassificationKernel.resume:z? ?ClassificationKernel.loss;ClassificationKernel.loss:y? ?ClassificationKernel.edge;ClassificationKernel.edge>xC CClassificationKernel.margin;ClassificationKernel.margin@wE EClassificationKernel.predict;ClassificationKernel.predictZv_ _ClassificationKernel.ClassificationKernel;ClassificationKernel.ClassificationKernelOC CClassificationNaiveBayes.MuCClassificationNaiveBayes.Mu\Na aClassificationNaiveBayes.DistributionNamesCClassificationNaiveBayes.DistributionNamesVM[ [ClassificationNaiveBayes.ScoreTransformCClassificationNaiveBayes.ScoreTransformBLG GClassificationNaiveBayes.CostCClassificationNaiveBayes.CostDKI IClassificationNaiveBayes.PriorCClassificationNaiveBayes.Prior~J  ClassificationNaiveBayes.HyperparameterOptimizationResultsCClassificationNaiveBayes.HyperparameterOptimizationResultsNIS SClassificationNaiveBayes.ClassNamesCClassificationNaiveBayes.ClassNamesfHk kClassificationNaiveBayes.ExpandedPredictorNamesCClassificationNaiveBayes.ExpandedPredictorNamesRGW WClassificationNaiveBayes.ResponseNameCClassificationNaiveBayes.ResponseNamedFi iClassificationNaiveBayes.CategoricalPredictorsCClassificationNaiveBayes.CategoricalPredictorsVE[ [ClassificationNaiveBayes.PredictorNamesCClassificationNaiveBayes.PredictorNamesJDO OClassificationNaiveBayes.BinEdgesCClassificationNaiveBayes.BinEdgesXC] ]ClassificationNaiveBayes.NumObservationsCClassificationNaiveBayes.NumObservationsXB] ]ClassificationNaiveBayes.ModelParametersCClassificationNaiveBayes.ModelParametersA AClassificationNaiveBayes.YCClassificationNaiveBayes.Y8== =ClassificationNaiveBayesBClassificationNaiveBayesD<I IClassificationLinear.savemodelAClassificationLinear.savemodelJ;O OClassificationLinear.selectModelsAClassificationLinear.selectModels::? ?ClassificationLinear.lossAClassificationLinear.loss:9? ?ClassificationLinear.edgeAClassificationLinear.edge>8C CClassificationLinear.marginAClassificationLinear.margin@7E EClassificationLinear.predictAClassificationLinear.predictZ6_ _ClassificationLinear.ClassificationLinearAClassificationLinear.ClassificationLinearN5S SClassificationLinear.Regularization@ClassificationLinear.RegularizationP4U UClassificationLinear.ModelParameters@ClassificationLinear.ModelParameters>3C CClassificationLinear.Lambda@ClassificationLinear.LambdaF2K KClassificationLinear.FittedLoss@ClassificationLinear.FittedLoss:1? ?ClassificationLinear.Bias@ClassificationLinear.Bias:0? ?ClassificationLinear.Beta@ClassificationLinear.Beta@/E EClassificationLinear.Learner@ClassificationLinear.Learner^.c cClassificationLinear.ExpandedPredictorNames@ClassificationLinear.ExpandedPredictorNamesJ-O OClassificationLinear.ResponseName@ClassificationLinear.ResponseName\,a aClassificationLinear.CategoricalPredictors@ClassificationLinear.CategoricalPredictorsN+S SClassificationLinear.PredictorNames@ClassificationLinear.PredictorNamesN*S SClassificationLinear.ScoreTransform@ClassificationLinear.ScoreTransform -v!0 [  b  K ` C(nk x IvMX V[ [ClassificationNeuralNetwork.resubMarginGClassificationNeuralNetwork.resubMarginHM MClassificationNeuralNetwork.lossGClassificationNeuralNetwork.lossHM MClassificationNeuralNetwork.edgeGClassificationNeuralNetwork.edgeLQ QClassificationNeuralNetwork.marginGClassificationNeuralNetwork.marginX] ]ClassificationNeuralNetwork.resubPredictGClassificationNeuralNetwork.resubPredictNS SClassificationNeuralNetwork.predictGClassificationNeuralNetwork.predictv{ {ClassificationNeuralNetwork.ClassificationNeuralNetworkGClassificationNeuralNetwork.ClassificationNeuralNetwork\a aClassificationNeuralNetwork.ScoreTransformFClassificationNeuralNetwork.ScoreTransformHM MClassificationNeuralNetwork.CostFClassificationNeuralNetwork.Cost  ClassificationNeuralNetwork.HyperparameterOptimizationResultsFClassificationNeuralNetwork.HyperparameterOptimizationResultsP~U UClassificationNeuralNetwork.BinEdgesFClassificationNeuralNetwork.BinEdgesl}q qClassificationNeuralNetwork.ExpandedPredictorNamesFClassificationNeuralNetwork.ExpandedPredictorNamesj|o oClassificationNeuralNetwork.CategoricalPredictorsFClassificationNeuralNetwork.CategoricalPredictorsB{G GClassificationNeuralNetwork.WFClassificationNeuralNetwork.WJzO OClassificationNeuralNetwork.PriorFClassificationNeuralNetwork.Prior^yc cClassificationNeuralNetwork.TrainingHistoryFClassificationNeuralNetwork.TrainingHistoryVx[ [ClassificationNeuralNetwork.LayerBiasesFClassificationNeuralNetwork.LayerBiasesXw] ]ClassificationNeuralNetwork.LayerWeightsFClassificationNeuralNetwork.LayerWeightsLvQ QClassificationNeuralNetwork.SolverFClassificationNeuralNetwork.SolverVu[ [ClassificationNeuralNetwork.DisplayInfoFClassificationNeuralNetwork.DisplayInfo^tc cClassificationNeuralNetwork.ConvergenceInfoFClassificationNeuralNetwork.ConvergenceInfo^sc cClassificationNeuralNetwork.ModelParametersFClassificationNeuralNetwork.ModelParameters\ra aClassificationNeuralNetwork.IterationLimitFClassificationNeuralNetwork.IterationLimitXq] ]ClassificationNeuralNetwork.LearningRateFClassificationNeuralNetwork.LearningRatejpo oClassificationNeuralNetwork.OutputLayerActivationFClassificationNeuralNetwork.OutputLayerActivationVo[ [ClassificationNeuralNetwork.ActivationsFClassificationNeuralNetwork.ActivationsTnY YClassificationNeuralNetwork.LayerSizesFClassificationNeuralNetwork.LayerSizesDmI IClassificationNeuralNetwork.MuFClassificationNeuralNetwork.MuJlO OClassificationNeuralNetwork.SigmaFClassificationNeuralNetwork.SigmaTkY YClassificationNeuralNetwork.ClassNamesFClassificationNeuralNetwork.ClassNamesXj] ]ClassificationNeuralNetwork.ResponseNameFClassificationNeuralNetwork.ResponseName\ia aClassificationNeuralNetwork.PredictorNamesFClassificationNeuralNetwork.PredictorNamesZh_ _ClassificationNeuralNetwork.NumPredictorsFClassificationNeuralNetwork.NumPredictorsPgU UClassificationNeuralNetwork.RowsUsedFClassificationNeuralNetwork.RowsUsed^fc cClassificationNeuralNetwork.NumObservationsFClassificationNeuralNetwork.NumObservationsBeG GClassificationNeuralNetwork.YFClassificationNeuralNetwork.YBdG GClassificationNeuralNetwork.XFClassificationNeuralNetwork.X>cC CClassificationNeuralNetworkEClassificationNeuralNetworkLbQ QClassificationNaiveBayes.savemodelDClassificationNaiveBayes.savemodelLaQ QClassificationNaiveBayes.resubLossDClassificationNaiveBayes.resubLossL`Q QClassificationNaiveBayes.resubEdgeDClassificationNaiveBayes.resubEdgeP_U UClassificationNaiveBayes.resubMarginDClassificationNaiveBayes.resubMarginR^W WClassificationNaiveBayes.resubPredictDClassificationNaiveBayes.resubPredictB]G GClassificationNaiveBayes.logpDClassificationNaiveBayes.logpB\G GClassificationNaiveBayes.lossDClassificationNaiveBayes.loss *V] b 5  2 V E/u,z%MJn ]f2k kClassificationPartitionedLinear.ModelParametersLClassificationPartitionedLinear.ModelParametersZ1_ _ClassificationPartitionedLinear.PartitionLClassificationPartitionedLinear.PartitionR0W WClassificationPartitionedLinear.KFoldLClassificationPartitionedLinear.KFoldV/[ [ClassificationPartitionedLinear.TrainedLClassificationPartitionedLinear.Trained`.e eClassificationPartitionedLinear.ResponseNameLClassificationPartitionedLinear.ResponseNamer-w wClassificationPartitionedLinear.CategoricalPredictorsLClassificationPartitionedLinear.CategoricalPredictorsd,i iClassificationPartitionedLinear.PredictorNamesLClassificationPartitionedLinear.PredictorNamesJ+O OClassificationPartitionedLinear.WLClassificationPartitionedLinear.WJ*O OClassificationPartitionedLinear.YLClassificationPartitionedLinear.Yf)k kClassificationPartitionedLinear.NumObservationsLClassificationPartitionedLinear.NumObservationsn(s sClassificationPartitionedLinear.CrossValidatedModelLClassificationPartitionedLinear.CrossValidatedModeld'i iClassificationPartitionedLinear.ScoreTransformLClassificationPartitionedLinear.ScoreTransformR&W WClassificationPartitionedLinear.PriorLClassificationPartitionedLinear.PriorP%U UClassificationPartitionedLinear.CostLClassificationPartitionedLinear.Cost\$a aClassificationPartitionedLinear.ClassNamesLClassificationPartitionedLinear.ClassNamesF#K KClassificationPartitionedLinearKClassificationPartitionedLinearZ"_ _ClassificationPartitionedKernel.kfoldLossJClassificationPartitionedKernel.kfoldLossZ!_ _ClassificationPartitionedKernel.kfoldEdgeJClassificationPartitionedKernel.kfoldEdge^ c cClassificationPartitionedKernel.kfoldMarginJClassificationPartitionedKernel.kfoldMargin`e eClassificationPartitionedKernel.kfoldPredictJClassificationPartitionedKernel.kfoldPredict  ClassificationPartitionedKernel.ClassificationPartitionedKernelJClassificationPartitionedKernel.ClassificationPartitionedKernelfk kClassificationPartitionedKernel.ModelParametersIClassificationPartitionedKernel.ModelParametersZ_ _ClassificationPartitionedKernel.PartitionIClassificationPartitionedKernel.PartitionRW WClassificationPartitionedKernel.KFoldIClassificationPartitionedKernel.KFoldV[ [ClassificationPartitionedKernel.TrainedIClassificationPartitionedKernel.Trained`e eClassificationPartitionedKernel.ResponseNameIClassificationPartitionedKernel.ResponseNamerw wClassificationPartitionedKernel.CategoricalPredictorsIClassificationPartitionedKernel.CategoricalPredictorsdi iClassificationPartitionedKernel.PredictorNamesIClassificationPartitionedKernel.PredictorNamesJO OClassificationPartitionedKernel.WIClassificationPartitionedKernel.WJO OClassificationPartitionedKernel.YIClassificationPartitionedKernel.Yfk kClassificationPartitionedKernel.NumObservationsIClassificationPartitionedKernel.NumObservationsns sClassificationPartitionedKernel.CrossValidatedModelIClassificationPartitionedKernel.CrossValidatedModeldi iClassificationPartitionedKernel.ScoreTransformIClassificationPartitionedKernel.ScoreTransformRW WClassificationPartitionedKernel.PriorIClassificationPartitionedKernel.PriorPU UClassificationPartitionedKernel.CostIClassificationPartitionedKernel.Cost\a aClassificationPartitionedKernel.ClassNamesIClassificationPartitionedKernel.ClassNamesFK KClassificationPartitionedKernelHClassificationPartitionedKernelR W WClassificationNeuralNetwork.savemodelGClassificationNeuralNetwork.savemodelN S SClassificationNeuralNetwork.compactGClassificationNeuralNetwork.compactP U UClassificationNeuralNetwork.crossvalGClassificationNeuralNetwork.crossvalR W WClassificationNeuralNetwork.resubLossGClassificationNeuralNetwork.resubLossR W WClassificationNeuralNetwork.resubEdgeGClassificationNeuralNetwork.resubEdge /tS R  I u  : YH)@+tCn#`-p9RaW WClassificationSVM.SupportVectorLabelsRClassificationSVM.SupportVectorLabelsJ`O OClassificationSVM.IsSupportVectorRClassificationSVM.IsSupportVector4_9 9ClassificationSVM.BiasRClassificationSVM.Bias4^9 9ClassificationSVM.BetaRClassificationSVM.Beta6]; ;ClassificationSVM.AlphaRClassificationSVM.AlphaJ\O OClassificationSVM.ModelParametersRClassificationSVM.ModelParameters0[5 5ClassificationSVM.MuRClassificationSVM.Mu6Z; ;ClassificationSVM.SigmaRClassificationSVM.Sigma@YE EClassificationSVM.ClassNamesRClassificationSVM.ClassNamesDXI IClassificationSVM.ResponseNameRClassificationSVM.ResponseNameHWM MClassificationSVM.PredictorNamesRClassificationSVM.PredictorNamesFVK KClassificationSVM.NumPredictorsRClassificationSVM.NumPredictorsg gClassificationPartitionedModel.PredictorNamesOClassificationPartitionedModel.PredictorNamesl=q qClassificationPartitionedModel.CrossValidatedModelOClassificationPartitionedModel.CrossValidatedModelb<g gClassificationPartitionedModel.ScoreTransformOClassificationPartitionedModel.ScoreTransformP;U UClassificationPartitionedModel.PriorOClassificationPartitionedModel.PriorN:S SClassificationPartitionedModel.CostOClassificationPartitionedModel.CostZ9_ _ClassificationPartitionedModel.ClassNamesOClassificationPartitionedModel.ClassNamesD8I IClassificationPartitionedModelNClassificationPartitionedModelZ7_ _ClassificationPartitionedLinear.kfoldLossMClassificationPartitionedLinear.kfoldLossZ6_ _ClassificationPartitionedLinear.kfoldEdgeMClassificationPartitionedLinear.kfoldEdge^5c cClassificationPartitionedLinear.kfoldMarginMClassificationPartitionedLinear.kfoldMargin`4e eClassificationPartitionedLinear.kfoldPredictMClassificationPartitionedLinear.kfoldPredict3  ClassificationPartitionedLinear.ClassificationPartitionedLinearMClassificationPartitionedLinear.ClassificationPartitionedLinear 0||Ez- F  K d  j + r 1:ht Q8t]B|Z_ _CompactClassificationDiscriminant.predictVCompactClassificationDiscriminant.predictfk kCompactClassificationDiscriminant.nLinearCoeffsVCompactClassificationDiscriminant.nLinearCoeffshm mCompactClassificationDiscriminant.ScoreTransformUCompactClassificationDiscriminant.ScoreTransformV[ [CompactClassificationDiscriminant.PriorUCompactClassificationDiscriminant.PriorT Y YCompactClassificationDiscriminant.CostUCompactClassificationDiscriminant.CostV [ [CompactClassificationDiscriminant.DeltaUCompactClassificationDiscriminant.DeltaV [ [CompactClassificationDiscriminant.GammaUCompactClassificationDiscriminant.Gammab g gCompactClassificationDiscriminant.DiscrimTypeUCompactClassificationDiscriminant.DiscrimTypeb g gCompactClassificationDiscriminant.LogDetSigmaUCompactClassificationDiscriminant.LogDetSigma\a aCompactClassificationDiscriminant.MinGammaUCompactClassificationDiscriminant.MinGammahm mCompactClassificationDiscriminant.DeltaPredictorUCompactClassificationDiscriminant.DeltaPredictorX] ]CompactClassificationDiscriminant.CoeffsUCompactClassificationDiscriminant.CoeffsPU UCompactClassificationDiscriminant.MuUCompactClassificationDiscriminant.MuV[ [CompactClassificationDiscriminant.SigmaUCompactClassificationDiscriminant.Sigma`e eCompactClassificationDiscriminant.ClassNamesUCompactClassificationDiscriminant.ClassNamesdi iCompactClassificationDiscriminant.ResponseNameUCompactClassificationDiscriminant.ResponseNamex} }CompactClassificationDiscriminant.ExpandedPredictorNamesUCompactClassificationDiscriminant.ExpandedPredictorNamesv{ {CompactClassificationDiscriminant.CategoricalPredictorsUCompactClassificationDiscriminant.CategoricalPredictorsdi iCompactClassificationDiscriminant.BetweenSigmaUCompactClassificationDiscriminant.BetweenSigmah~m mCompactClassificationDiscriminant.PredictorNamesUCompactClassificationDiscriminant.PredictorNamesf}k kCompactClassificationDiscriminant.NumPredictorsUCompactClassificationDiscriminant.NumPredictorsJ|O OCompactClassificationDiscriminantTCompactClassificationDiscriminant>{C CClassificationSVM.savemodelSClassificationSVM.savemodel>zC CClassificationSVM.resubEdgeSClassificationSVM.resubEdgeByG GClassificationSVM.resubMarginSClassificationSVM.resubMargin4x9 9ClassificationSVM.edgeSClassificationSVM.edge:w? ?ClassificationSVM.compactSClassificationSVM.compactuC CClassificationSVM.resubLossSClassificationSVM.resubLoss4t9 9ClassificationSVM.lossSClassificationSVM.loss8s= =ClassificationSVM.marginSClassificationSVM.marginDrI IClassificationSVM.resubPredictSClassificationSVM.resubPredict:q? ?ClassificationSVM.predictSClassificationSVM.predictVp[ [ClassificationSVM.discardSupportVectorsSClassificationSVM.discardSupportVectorsNoS SClassificationSVM.ClassificationSVMSClassificationSVM.ClassificationSVMHnM MClassificationSVM.ScoreTransformRClassificationSVM.ScoreTransformnms sClassificationSVM.HyperparameterOptimizationResultsRClassificationSVM.HyperparameterOptimizationResultsi iCompactClassificationNaiveBayes.PredictorNames[CompactClassificationNaiveBayes.PredictorNamesd=i iCompactClassificationNaiveBayes.ScoreTransform[CompactClassificationNaiveBayes.ScoreTransformP<U UCompactClassificationNaiveBayes.Cost[CompactClassificationNaiveBayes.CostR;W WCompactClassificationNaiveBayes.Prior[CompactClassificationNaiveBayes.Prior\:a aCompactClassificationNaiveBayes.ClassNames[CompactClassificationNaiveBayes.ClassNamesR9W WCompactClassificationNaiveBayes.Width[CompactClassificationNaiveBayes.WidthV8[ [CompactClassificationNaiveBayes.Support[CompactClassificationNaiveBayes.SupportT7Y YCompactClassificationNaiveBayes.Kernel[CompactClassificationNaiveBayes.Kernelj6o oCompactClassificationNaiveBayes.CategoricalLevels[CompactClassificationNaiveBayes.CategoricalLevelst5y yCompactClassificationNaiveBayes.DistributionParameters[CompactClassificationNaiveBayes.DistributionParametersR4W WCompactClassificationNaiveBayes.Sigma[CompactClassificationNaiveBayes.SigmaL3Q QCompactClassificationNaiveBayes.Mu[CompactClassificationNaiveBayes.Muj2o oCompactClassificationNaiveBayes.DistributionNames[CompactClassificationNaiveBayes.DistributionNamesF1K KCompactClassificationNaiveBayesZCompactClassificationNaiveBayesL0Q QCompactClassificationGAM.savemodelYCompactClassificationGAM.savemodelB/G GCompactClassificationGAM.lossYCompactClassificationGAM.lossB.G GCompactClassificationGAM.edgeYCompactClassificationGAM.edgeF-K KCompactClassificationGAM.marginYCompactClassificationGAM.marginH,M MCompactClassificationGAM.predictYCompactClassificationGAM.predictV+[ [CompactClassificationGAM.ScoreTransformXCompactClassificationGAM.ScoreTransformB*G GCompactClassificationGAM.CostXCompactClassificationGAM.CostL)Q QCompactClassificationGAM.TreeModelXCompactClassificationGAM.TreeModelL(Q QCompactClassificationGAM.FitMethodXCompactClassificationGAM.FitMethodd'i iCompactClassificationGAM.PairDetectionBinEdgesXCompactClassificationGAM.PairDetectionBinEdgesJ&O OCompactClassificationGAM.BinEdgesXCompactClassificationGAM.BinEdgesf%k kCompactClassificationGAM.ExpandedPredictorNamesXCompactClassificationGAM.ExpandedPredictorNamesd$i iCompactClassificationGAM.CategoricalPredictorsXCompactClassificationGAM.CategoricalPredictorsL#Q QCompactClassificationGAM.InterceptXCompactClassificationGAM.InterceptL"Q QCompactClassificationGAM.IntMatrixXCompactClassificationGAM.IntMatrixL!Q QCompactClassificationGAM.ModelwIntXCompactClassificationGAM.ModelwIntL Q QCompactClassificationGAM.BaseModelXCompactClassificationGAM.BaseModelRW WCompactClassificationGAM.InteractionsXCompactClassificationGAM.InteractionsHM MCompactClassificationGAM.FormulaXCompactClassificationGAM.FormulaDI ICompactClassificationGAM.PriorXCompactClassificationGAM.PriorNS SCompactClassificationGAM.ClassNamesXCompactClassificationGAM.ClassNamesRW WCompactClassificationGAM.ResponseNameXCompactClassificationGAM.ResponseNameV[ [CompactClassificationGAM.PredictorNamesXCompactClassificationGAM.PredictorNamesTY YCompactClassificationGAM.NumPredictorsXCompactClassificationGAM.NumPredictors8= =CompactClassificationGAMWCompactClassificationGAM^c cCompactClassificationDiscriminant.savemodelVCompactClassificationDiscriminant.savemodelTY YCompactClassificationDiscriminant.logpVCompactClassificationDiscriminant.logpV[ [CompactClassificationDiscriminant.mahalVCompactClassificationDiscriminant.mahalTY YCompactClassificationDiscriminant.edgeVCompactClassificationDiscriminant.edgeX] ]CompactClassificationDiscriminant.marginVCompactClassificationDiscriminant.marginTY YCompactClassificationDiscriminant.lossVCompactClassificationDiscriminant.loss +a&v# } f + {  4d K6z?2b u0aDjI ICompactClassificationSVM.AlphaaCompactClassificationSVM.Alpha>iC CCompactClassificationSVM.MuaCompactClassificationSVM.MuDhI ICompactClassificationSVM.SigmaaCompactClassificationSVM.SigmaBgG GCompactClassificationSVM.CostaCompactClassificationSVM.CostDfI ICompactClassificationSVM.PrioraCompactClassificationSVM.PriorNeS SCompactClassificationSVM.ClassNamesaCompactClassificationSVM.ClassNamesRdW WCompactClassificationSVM.ResponseNameaCompactClassificationSVM.ResponseNamefck kCompactClassificationSVM.ExpandedPredictorNamesaCompactClassificationSVM.ExpandedPredictorNamesdbi iCompactClassificationSVM.CategoricalPredictorsaCompactClassificationSVM.CategoricalPredictorsZa_ _CompactClassificationSVM.KernelParametersaCompactClassificationSVM.KernelParametersV`[ [CompactClassificationSVM.PredictorNamesaCompactClassificationSVM.PredictorNamesT_Y YCompactClassificationSVM.NumPredictorsaCompactClassificationSVM.NumPredictors8^= =CompactClassificationSVM`CompactClassificationSVM`]e eCompactClassificationNeuralNetwork.savemodel_CompactClassificationNeuralNetwork.savemodelV\[ [CompactClassificationNeuralNetwork.loss_CompactClassificationNeuralNetwork.lossV[[ [CompactClassificationNeuralNetwork.edge_CompactClassificationNeuralNetwork.edgeZZ_ _CompactClassificationNeuralNetwork.margin_CompactClassificationNeuralNetwork.margin\Ya aCompactClassificationNeuralNetwork.predict_CompactClassificationNeuralNetwork.predictjXo oCompactClassificationNeuralNetwork.ScoreTransform^CompactClassificationNeuralNetwork.ScoreTransformVW[ [CompactClassificationNeuralNetwork.Cost^CompactClassificationNeuralNetwork.CostzV CompactClassificationNeuralNetwork.ExpandedPredictorNames^CompactClassificationNeuralNetwork.ExpandedPredictorNamesxU} }CompactClassificationNeuralNetwork.CategoricalPredictors^CompactClassificationNeuralNetwork.CategoricalPredictorsXT] ]CompactClassificationNeuralNetwork.Prior^CompactClassificationNeuralNetwork.PriordSi iCompactClassificationNeuralNetwork.LayerBiases^CompactClassificationNeuralNetwork.LayerBiasesfRk kCompactClassificationNeuralNetwork.LayerWeights^CompactClassificationNeuralNetwork.LayerWeightsxQ} }CompactClassificationNeuralNetwork.OutputLayerActivation^CompactClassificationNeuralNetwork.OutputLayerActivationdPi iCompactClassificationNeuralNetwork.Activations^CompactClassificationNeuralNetwork.ActivationsbOg gCompactClassificationNeuralNetwork.LayerSizes^CompactClassificationNeuralNetwork.LayerSizesRNW WCompactClassificationNeuralNetwork.Mu^CompactClassificationNeuralNetwork.MuXM] ]CompactClassificationNeuralNetwork.Sigma^CompactClassificationNeuralNetwork.SigmabLg gCompactClassificationNeuralNetwork.ClassNames^CompactClassificationNeuralNetwork.ClassNamesfKk kCompactClassificationNeuralNetwork.ResponseName^CompactClassificationNeuralNetwork.ResponseNamejJo oCompactClassificationNeuralNetwork.PredictorNames^CompactClassificationNeuralNetwork.PredictorNameshIm mCompactClassificationNeuralNetwork.NumPredictors^CompactClassificationNeuralNetwork.NumPredictorsLHQ QCompactClassificationNeuralNetwork]CompactClassificationNeuralNetworkZG_ _CompactClassificationNaiveBayes.savemodel\CompactClassificationNaiveBayes.savemodelPFU UCompactClassificationNaiveBayes.logp\CompactClassificationNaiveBayes.logpPEU UCompactClassificationNaiveBayes.loss\CompactClassificationNaiveBayes.lossPDU UCompactClassificationNaiveBayes.edge\CompactClassificationNaiveBayes.edgeTCY YCompactClassificationNaiveBayes.margin\CompactClassificationNaiveBayes.marginVB[ [CompactClassificationNaiveBayes.predict\CompactClassificationNaiveBayes.predicttAy yCompactClassificationNaiveBayes.ExpandedPredictorNames[CompactClassificationNaiveBayes.ExpandedPredictorNames`@e eCompactClassificationNaiveBayes.ResponseName[CompactClassificationNaiveBayes.ResponseName 3va f ! Z m  f # u .[nPq&7p>KRW WCompactRegressionGP.ResponseTransformgCompactRegressionGP.ResponseTransformLQ QCompactRegressionGP.PredictorScalegCompactRegressionGP.PredictorScaleRW WCompactRegressionGP.PredictorLocationgCompactRegressionGP.PredictorLocationJO OCompactRegressionGP.ActiveSetSizegCompactRegressionGP.ActiveSetSizeNS SCompactRegressionGP.ActiveSetMethodgCompactRegressionGP.ActiveSetMethodPU UCompactRegressionGP.ActiveSetVectorsgCompactRegressionGP.ActiveSetVectors:? ?CompactRegressionGP.AlphagCompactRegressionGP.AlphaJO OCompactRegressionGP.PredictMethodgCompactRegressionGP.PredictMethodRW WCompactRegressionGP.KernelInformationgCompactRegressionGP.KernelInformationLQ QCompactRegressionGP.KernelFunctiongCompactRegressionGP.KernelFunction:? ?CompactRegressionGP.SigmagCompactRegressionGP.Sigma8= =CompactRegressionGP.BetagCompactRegressionGP.BetaJO OCompactRegressionGP.BasisFunctiongCompactRegressionGP.BasisFunctionBG GCompactRegressionGP.FitMethodgCompactRegressionGP.FitMethodZ_ _CompactRegressionGP.CategoricalPredictorsgCompactRegressionGP.CategoricalPredictorsHM MCompactRegressionGP.ResponseNamegCompactRegressionGP.ResponseName\ a aCompactRegressionGP.ExpandedPredictorNamesgCompactRegressionGP.ExpandedPredictorNamesL Q QCompactRegressionGP.PredictorNamesgCompactRegressionGP.PredictorNames. 3 3CompactRegressionGPfCompactRegressionGPD I ICompactRegressionGAM.savemodeleCompactRegressionGAM.savemodel: ? ?CompactRegressionGAM.losseCompactRegressionGAM.loss@E ECompactRegressionGAM.predicteCompactRegressionGAM.predictTY YCompactRegressionGAM.ResponseTransformdCompactRegressionGAM.ResponseTransformDI ICompactRegressionGAM.TreeModeldCompactRegressionGAM.TreeModelDI ICompactRegressionGAM.FitMethoddCompactRegressionGAM.FitMethod\a aCompactRegressionGAM.PairDetectionBinEdgesdCompactRegressionGAM.PairDetectionBinEdgesBG GCompactRegressionGAM.BinEdgesdCompactRegressionGAM.BinEdgesDI ICompactRegressionGAM.IntMatrixdCompactRegressionGAM.IntMatrixDI ICompactRegressionGAM.ModelwIntdCompactRegressionGAM.ModelwIntDI ICompactRegressionGAM.BaseModeldCompactRegressionGAM.BaseModel^c cCompactRegressionGAM.IsStandardDeviationFitdCompactRegressionGAM.IsStandardDeviationFitJ~O OCompactRegressionGAM.InteractionsdCompactRegressionGAM.Interactions@}E ECompactRegressionGAM.FormuladCompactRegressionGAM.FormulaD|I ICompactRegressionGAM.InterceptdCompactRegressionGAM.Intercept^{c cCompactRegressionGAM.ExpandedPredictorNamesdCompactRegressionGAM.ExpandedPredictorNames\za aCompactRegressionGAM.CategoricalPredictorsdCompactRegressionGAM.CategoricalPredictorsJyO OCompactRegressionGAM.ResponseNamedCompactRegressionGAM.ResponseNameNxS SCompactRegressionGAM.PredictorNamesdCompactRegressionGAM.PredictorNamesLwQ QCompactRegressionGAM.NumPredictorsdCompactRegressionGAM.NumPredictors0v5 5CompactRegressionGAMcCompactRegressionGAMLuQ QCompactClassificationSVM.savemodelbCompactClassificationSVM.savemodelBtG GCompactClassificationSVM.edgebCompactClassificationSVM.edgeBsG GCompactClassificationSVM.lossbCompactClassificationSVM.lossFrK KCompactClassificationSVM.marginbCompactClassificationSVM.marginHqM MCompactClassificationSVM.predictbCompactClassificationSVM.predictdpi iCompactClassificationSVM.discardSupportVectorsbCompactClassificationSVM.discardSupportVectorsVo[ [CompactClassificationSVM.ScoreTransformaCompactClassificationSVM.ScoreTransformVn[ [CompactClassificationSVM.SupportVectorsaCompactClassificationSVM.SupportVectors`me eCompactClassificationSVM.SupportVectorLabelsaCompactClassificationSVM.SupportVectorLabelsBlG GCompactClassificationSVM.BiasaCompactClassificationSVM.BiasBkG GCompactClassificationSVM.BetaaCompactClassificationSVM.Beta 4?0 | / s @ X EhHVQ:sN%?l2Q7 7RegressionGAM.FormulapRegressionGAM.Formula6P; ;RegressionGAM.InterceptpRegressionGAM.Intercept&O+ +RegressionGAM.WpRegressionGAM.WPNU URegressionGAM.ExpandedPredictorNamespRegressionGAM.ExpandedPredictorNamesNMS SRegressionGAM.CategoricalPredictorspRegressionGAM.CategoricalPredictorsJC CRegressionGAM.NumPredictorspRegressionGAM.NumPredictors4I9 9RegressionGAM.RowsUsedpRegressionGAM.RowsUsedBHG GRegressionGAM.NumObservationspRegressionGAM.NumObservations&G+ +RegressionGAM.YpRegressionGAM.Y&F+ +RegressionGAM.XpRegressionGAM.X"E' 'RegressionGAMoRegressionGAMDDI ICompactRegressionSVM.savemodelnCompactRegressionSVM.savemodel:C? ?CompactRegressionSVM.lossnCompactRegressionSVM.loss@BE ECompactRegressionSVM.predictnCompactRegressionSVM.predict\Aa aCompactRegressionSVM.discardSupportVectorsnCompactRegressionSVM.discardSupportVectorsT@Y YCompactRegressionSVM.ResponseTransformmCompactRegressionSVM.ResponseTransform^?c cCompactRegressionSVM.ExpandedPredictorNamesmCompactRegressionSVM.ExpandedPredictorNames\>a aCompactRegressionSVM.CategoricalPredictorsmCompactRegressionSVM.CategoricalPredictorsR=W WCompactRegressionSVM.KernelParametersmCompactRegressionSVM.KernelParametersN<S SCompactRegressionSVM.SupportVectorsmCompactRegressionSVM.SupportVectors:;? ?CompactRegressionSVM.BiasmCompactRegressionSVM.Bias::? ?CompactRegressionSVM.BetamCompactRegressionSVM.Beta<9A ACompactRegressionSVM.AlphamCompactRegressionSVM.Alpha68; ;CompactRegressionSVM.MumCompactRegressionSVM.Mu<7A ACompactRegressionSVM.SigmamCompactRegressionSVM.Sigma@6E ECompactRegressionSVM.EpsilonmCompactRegressionSVM.EpsilonJ5O OCompactRegressionSVM.ResponseNamemCompactRegressionSVM.ResponseNameN4S SCompactRegressionSVM.PredictorNamesmCompactRegressionSVM.PredictorNamesL3Q QCompactRegressionSVM.NumPredictorsmCompactRegressionSVM.NumPredictors025 5CompactRegressionSVMlCompactRegressionSVMX1] ]CompactRegressionNeuralNetwork.savemodelkCompactRegressionNeuralNetwork.savemodelN0S SCompactRegressionNeuralNetwork.losskCompactRegressionNeuralNetwork.lossT/Y YCompactRegressionNeuralNetwork.predictkCompactRegressionNeuralNetwork.predicth.m mCompactRegressionNeuralNetwork.ResponseTransformjCompactRegressionNeuralNetwork.ResponseTransformr-w wCompactRegressionNeuralNetwork.ExpandedPredictorNamesjCompactRegressionNeuralNetwork.ExpandedPredictorNamesp,u uCompactRegressionNeuralNetwork.CategoricalPredictorsjCompactRegressionNeuralNetwork.CategoricalPredictors\+a aCompactRegressionNeuralNetwork.LayerBiasesjCompactRegressionNeuralNetwork.LayerBiases^*c cCompactRegressionNeuralNetwork.LayerWeightsjCompactRegressionNeuralNetwork.LayerWeightsp)u uCompactRegressionNeuralNetwork.OutputLayerActivationjCompactRegressionNeuralNetwork.OutputLayerActivation\(a aCompactRegressionNeuralNetwork.ActivationsjCompactRegressionNeuralNetwork.ActivationsZ'_ _CompactRegressionNeuralNetwork.LayerSizesjCompactRegressionNeuralNetwork.LayerSizesJ&O OCompactRegressionNeuralNetwork.MujCompactRegressionNeuralNetwork.MuP%U UCompactRegressionNeuralNetwork.SigmajCompactRegressionNeuralNetwork.Sigma^$c cCompactRegressionNeuralNetwork.ResponseNamejCompactRegressionNeuralNetwork.ResponseNameb#g gCompactRegressionNeuralNetwork.PredictorNamesjCompactRegressionNeuralNetwork.PredictorNames`"e eCompactRegressionNeuralNetwork.NumPredictorsjCompactRegressionNeuralNetwork.NumPredictorsD!I ICompactRegressionNeuralNetworkiCompactRegressionNeuralNetworkB G GCompactRegressionGP.savemodelhCompactRegressionGP.savemodel8= =CompactRegressionGP.losshCompactRegressionGP.loss>C CCompactRegressionGP.predicthCompactRegressionGP.predict @_2y@  : y  A e . j C }<_*XNXL]i<DI IRegressionGP.postFitStatisticstRegressionGP.postFitStatistics49 9RegressionGP.resubLosstRegressionGP.resubLoss*/ /RegressionGP.losstRegressionGP.loss:? ?RegressionGP.resubPredicttRegressionGP.resubPredict0 5 5RegressionGP.predicttRegressionGP.predict: ? ?RegressionGP.RegressionGPtRegressionGP.RegressionGPD I IRegressionGP.ResponseTransformsRegressionGP.ResponseTransformd i iRegressionGP.HyperparameterOptimizationResultssRegressionGP.HyperparameterOptimizationResults> C CRegressionGP.PredictorScalesRegressionGP.PredictorScaleDI IRegressionGP.PredictorLocationsRegressionGP.PredictorLocation>C CRegressionGP.BCDInformationsRegressionGP.BCDInformationBG GRegressionGP.ActiveSetHistorysRegressionGP.ActiveSetHistoryDI IRegressionGP.IsActiveSetVectorsRegressionGP.IsActiveSetVector<A ARegressionGP.ActiveSetSizesRegressionGP.ActiveSetSize@E ERegressionGP.ActiveSetMethodsRegressionGP.ActiveSetMethodBG GRegressionGP.ActiveSetVectorssRegressionGP.ActiveSetVectors,1 1RegressionGP.AlphasRegressionGP.Alpha<A ARegressionGP.PredictMethodsRegressionGP.PredictMethodDI IRegressionGP.KernelInformationsRegressionGP.KernelInformation>~C CRegressionGP.KernelFunctionsRegressionGP.KernelFunction@}E ERegressionGP.ModelParameterssRegressionGP.ModelParameters<|A ARegressionGP.LogLikelihoodsRegressionGP.LogLikelihood,{1 1RegressionGP.SigmasRegressionGP.Sigma*z/ /RegressionGP.BetasRegressionGP.BetasC CRegressionGP.PredictorNamessRegressionGP.PredictorNames$r) )RegressionGP.WsRegressionGP.W2q7 7RegressionGP.RowsUsedsRegressionGP.RowsUsed@pE ERegressionGP.NumObservationssRegressionGP.NumObservations$o) )RegressionGP.YsRegressionGP.Y$n) )RegressionGP.XsRegressionGP.X m% %RegressionGPrRegressionGP0l5 5RegressionGAM.resumeqRegressionGAM.resume6k; ;RegressionGAM.savemodelqRegressionGAM.savemodel2j7 7RegressionGAM.compactqRegressionGAM.compact4i9 9RegressionGAM.crossvalqRegressionGAM.crossval6h; ;RegressionGAM.resubLossqRegressionGAM.resubLosscC CRegressionGAM.RegressionGAMqRegressionGAM.RegressionGAMFbK KRegressionGAM.ResponseTransformpRegressionGAM.ResponseTransformfak kRegressionGAM.HyperparameterOptimizationResultspRegressionGAM.HyperparameterOptimizationResults6`; ;RegressionGAM.TreeModelpRegressionGAM.TreeModel6_; ;RegressionGAM.FitMethodpRegressionGAM.FitMethodL^Q QRegressionGAM.ReasonForTerminationpRegressionGAM.ReasonForTerminationB]G GRegressionGAM.ModelParameterspRegressionGAM.ModelParametersN\S SRegressionGAM.PairDetectionBinEdgespRegressionGAM.PairDetectionBinEdges4[9 9RegressionGAM.BinEdgespRegressionGAM.BinEdges6Z; ;RegressionGAM.IntMatrixpRegressionGAM.IntMatrix6Y; ;RegressionGAM.ModelwIntpRegressionGAM.ModelwInt6X; ;RegressionGAM.BaseModelpRegressionGAM.BaseModelPWU URegressionGAM.IsStandardDeviationFitpRegressionGAM.IsStandardDeviationFit*V/ /RegressionGAM.TolpRegressionGAM.Tol*U/ /RegressionGAM.DoFpRegressionGAM.DoF.T3 3RegressionGAM.OrderpRegressionGAM.Order.S3 3RegressionGAM.KnotspRegressionGAM.Knots; ;RegressionNeuralNetwork{RegressionNeuralNetwork<=A ARegressionLinear.savemodelzRegressionLinear.savemodelB<G GRegressionLinear.selectModelszRegressionLinear.selectModels2;7 7RegressionLinear.losszRegressionLinear.loss8:= =RegressionLinear.predictzRegressionLinear.predictJ9O ORegressionLinear.RegressionLinearzRegressionLinear.RegressionLinearF8K KRegressionLinear.RegularizationyRegressionLinear.RegularizationH7M MRegressionLinear.ModelParametersyRegressionLinear.ModelParameters66; ;RegressionLinear.LambdayRegressionLinear.Lambda>5C CRegressionLinear.FittedLossyRegressionLinear.FittedLoss247 7RegressionLinear.BiasyRegressionLinear.Bias237 7RegressionLinear.BetayRegressionLinear.Beta82= =RegressionLinear.LearneryRegressionLinear.LearnerV1[ [RegressionLinear.ExpandedPredictorNamesyRegressionLinear.ExpandedPredictorNamesB0G GRegressionLinear.ResponseNameyRegressionLinear.ResponseNameT/Y YRegressionLinear.CategoricalPredictorsyRegressionLinear.CategoricalPredictorsF.K KRegressionLinear.PredictorNamesyRegressionLinear.PredictorNamesL-Q QRegressionLinear.ResponseTransformyRegressionLinear.ResponseTransform8,= =RegressionLinear.EpsilonyRegressionLinear.Epsilon(+- -RegressionLinearxRegressionLinear<*A ARegressionKernel.savemodelwRegressionKernel.savemodel6); ;RegressionKernel.resumewRegressionKernel.resume2(7 7RegressionKernel.losswRegressionKernel.loss8'= =RegressionKernel.predictwRegressionKernel.predictJ&O ORegressionKernel.RegressionKernelwRegressionKernel.RegressionKernel4%9 9RegressionKernel.SigmavRegressionKernel.Sigma.$3 3RegressionKernel.MuvRegressionKernel.Mu8#= =RegressionKernel.LearnervRegressionKernel.Learner@"E ERegressionKernel.KernelScalevRegressionKernel.KernelScaleF!K KRegressionKernel.RegularizationvRegressionKernel.RegularizationH M MRegressionKernel.ModelParametersvRegressionKernel.ModelParameters6; ;RegressionKernel.LambdavRegressionKernel.Lambda>C CRegressionKernel.FittedLossvRegressionKernel.FittedLossV[ [RegressionKernel.NumExpansionDimensionsvRegressionKernel.NumExpansionDimensionsV[ [RegressionKernel.ExpandedPredictorNamesvRegressionKernel.ExpandedPredictorNamesBG GRegressionKernel.ResponseNamevRegressionKernel.ResponseNameTY YRegressionKernel.CategoricalPredictorsvRegressionKernel.CategoricalPredictorsFK KRegressionKernel.PredictorNamesvRegressionKernel.PredictorNamesLQ QRegressionKernel.ResponseTransformvRegressionKernel.ResponseTransformDI IRegressionKernel.BoxConstraintvRegressionKernel.BoxConstraint8= =RegressionKernel.EpsilonvRegressionKernel.Epsilon(- -RegressionKerneluRegressionKernel49 9RegressionGP.savemodeltRegressionGP.savemodel05 5RegressionGP.compacttRegressionGP.compact27 7RegressionGP.crossvaltRegressionGP.crossval -P_ b % Y  4 / T }N eLIsu c]xa aRegressionPartitionedLinear.PredictorNamesRegressionPartitionedLinear.PredictorNamesCwG GRegressionPartitionedLinear.WRegressionPartitionedLinear.WCvG GRegressionPartitionedLinear.YRegressionPartitionedLinear.Y_uc cRegressionPartitionedLinear.NumObservationsRegressionPartitionedLinear.NumObservationsgtk kRegressionPartitionedLinear.CrossValidatedModelRegressionPartitionedLinear.CrossValidatedModelcsg gRegressionPartitionedLinear.ResponseTransformRegressionPartitionedLinear.ResponseTransform?rC CRegressionPartitionedLinearRegressionPartitionedLinearSqW WRegressionPartitionedKernel.kfoldLossRegressionPartitionedKernel.kfoldLossYp] ]RegressionPartitionedKernel.kfoldPredictRegressionPartitionedKernel.kfoldPredictwo{ {RegressionPartitionedKernel.RegressionPartitionedKernelRegressionPartitionedKernel.RegressionPartitionedKernel^nc cRegressionPartitionedKernel.ModelParametersRegressionPartitionedKernel.ModelParametersRmW WRegressionPartitionedKernel.PartitionRegressionPartitionedKernel.PartitionJlO ORegressionPartitionedKernel.KFoldRegressionPartitionedKernel.KFoldNkS SRegressionPartitionedKernel.TrainedRegressionPartitionedKernel.TrainedXj] ]RegressionPartitionedKernel.ResponseNameRegressionPartitionedKernel.ResponseNamejio oRegressionPartitionedKernel.CategoricalPredictorsRegressionPartitionedKernel.CategoricalPredictors\ha aRegressionPartitionedKernel.PredictorNamesRegressionPartitionedKernel.PredictorNamesBgG GRegressionPartitionedKernel.WRegressionPartitionedKernel.WBfG GRegressionPartitionedKernel.YRegressionPartitionedKernel.Y^ec cRegressionPartitionedKernel.NumObservationsRegressionPartitionedKernel.NumObservationsfdk kRegressionPartitionedKernel.CrossValidatedModelRegressionPartitionedKernel.CrossValidatedModelbcg gRegressionPartitionedKernel.ResponseTransformRegressionPartitionedKernel.ResponseTransform>bC CRegressionPartitionedKernel~RegressionPartitionedKernelJaO ORegressionNeuralNetwork.savemodel}RegressionNeuralNetwork.savemodelF`K KRegressionNeuralNetwork.compact}RegressionNeuralNetwork.compactH_M MRegressionNeuralNetwork.crossval}RegressionNeuralNetwork.crossvalJ^O ORegressionNeuralNetwork.resubLoss}RegressionNeuralNetwork.resubLoss@]E ERegressionNeuralNetwork.loss}RegressionNeuralNetwork.lossP\U URegressionNeuralNetwork.resubPredict}RegressionNeuralNetwork.resubPredictF[K KRegressionNeuralNetwork.predict}RegressionNeuralNetwork.predictfZk kRegressionNeuralNetwork.RegressionNeuralNetwork}RegressionNeuralNetwork.RegressionNeuralNetworkZY_ _RegressionNeuralNetwork.ResponseTransform|RegressionNeuralNetwork.ResponseTransformzX RegressionNeuralNetwork.HyperparameterOptimizationResults|RegressionNeuralNetwork.HyperparameterOptimizationResultsHWM MRegressionNeuralNetwork.BinEdges|RegressionNeuralNetwork.BinEdgesdVi iRegressionNeuralNetwork.ExpandedPredictorNames|RegressionNeuralNetwork.ExpandedPredictorNamesbUg gRegressionNeuralNetwork.CategoricalPredictors|RegressionNeuralNetwork.CategoricalPredictors:T? ?RegressionNeuralNetwork.W|RegressionNeuralNetwork.WVS[ [RegressionNeuralNetwork.TrainingHistory|RegressionNeuralNetwork.TrainingHistoryNRS SRegressionNeuralNetwork.LayerBiases|RegressionNeuralNetwork.LayerBiasesPQU URegressionNeuralNetwork.LayerWeights|RegressionNeuralNetwork.LayerWeightsDPI IRegressionNeuralNetwork.Solver|RegressionNeuralNetwork.SolverNOS SRegressionNeuralNetwork.DisplayInfo|RegressionNeuralNetwork.DisplayInfoVN[ [RegressionNeuralNetwork.ConvergenceInfo|RegressionNeuralNetwork.ConvergenceInfoVM[ [RegressionNeuralNetwork.ModelParameters|RegressionNeuralNetwork.ModelParametersTLY YRegressionNeuralNetwork.IterationLimit|RegressionNeuralNetwork.IterationLimit 36@ d  r  H " VZFllFt2xFrBp,Q+U URegressionSVM.ExpandedPredictorNamesRegressionSVM.ExpandedPredictorNamesO*S SRegressionSVM.CategoricalPredictorsRegressionSVM.CategoricalPredictorsA)E ERegressionSVM.BoxConstraintsRegressionSVM.BoxConstraintsE(I IRegressionSVM.KernelParametersRegressionSVM.KernelParametersA'E ERegressionSVM.SupportVectorsRegressionSVM.SupportVectorsC&G GRegressionSVM.IsSupportVectorRegressionSVM.IsSupportVector-%1 1RegressionSVM.BiasRegressionSVM.Bias-$1 1RegressionSVM.BetaRegressionSVM.Beta/#3 3RegressionSVM.AlphaRegressionSVM.AlphaC"G GRegressionSVM.ModelParametersRegressionSVM.ModelParameters)!- -RegressionSVM.MuRegressionSVM.Mu/ 3 3RegressionSVM.SigmaRegressionSVM.Sigma37 7RegressionSVM.EpsilonRegressionSVM.Epsilon=A ARegressionSVM.ResponseNameRegressionSVM.ResponseNameAE ERegressionSVM.PredictorNamesRegressionSVM.PredictorNames?C CRegressionSVM.NumPredictorsRegressionSVM.NumPredictors59 9RegressionSVM.RowsUsedRegressionSVM.RowsUsedCG GRegressionSVM.NumObservationsRegressionSVM.NumObservations'+ +RegressionSVM.YRegressionSVM.Y'+ +RegressionSVM.XRegressionSVM.X#' 'RegressionSVMRegressionSVMOS SRegressionPartitionedModel.kfoldfunRegressionPartitionedModel.kfoldfunQU URegressionPartitionedModel.kfoldLossRegressionPartitionedModel.kfoldLossW[ [RegressionPartitionedModel.kfoldPredictRegressionPartitionedModel.kfoldPredictsw wRegressionPartitionedModel.RegressionPartitionedModelRegressionPartitionedModel.RegressionPartitionedModelae eRegressionPartitionedModel.NumTrainedPerFoldRegressionPartitionedModel.NumTrainedPerFoldko oRegressionPartitionedModel.IsStandardDeviationFitRegressionPartitionedModel.IsStandardDeviationFitOS SRegressionPartitionedModel.BinEdgesRegressionPartitionedModel.BinEdgesQU URegressionPartitionedModel.PartitionRegressionPartitionedModel.PartitionIM MRegressionPartitionedModel.KFoldRegressionPartitionedModel.KFoldM Q QRegressionPartitionedModel.TrainedRegressionPartitionedModel.Trained] a aRegressionPartitionedModel.ModelParametersRegressionPartitionedModel.ModelParametersA E ERegressionPartitionedModel.WRegressionPartitionedModel.WA E ERegressionPartitionedModel.YRegressionPartitionedModel.YA E ERegressionPartitionedModel.XRegressionPartitionedModel.X]a aRegressionPartitionedModel.NumObservationsRegressionPartitionedModel.NumObservationsW[ [RegressionPartitionedModel.ResponseNameRegressionPartitionedModel.ResponseNameim mRegressionPartitionedModel.CategoricalPredictorsRegressionPartitionedModel.CategoricalPredictors[_ _RegressionPartitionedModel.PredictorNamesRegressionPartitionedModel.PredictorNamesei iRegressionPartitionedModel.CrossValidatedModelRegressionPartitionedModel.CrossValidatedModelae eRegressionPartitionedModel.ResponseTransformRegressionPartitionedModel.ResponseTransform=A ARegressionPartitionedModelRegressionPartitionedModelSW WRegressionPartitionedLinear.kfoldLossRegressionPartitionedLinear.kfoldLossY] ]RegressionPartitionedLinear.kfoldPredictRegressionPartitionedLinear.kfoldPredictw{ {RegressionPartitionedLinear.RegressionPartitionedLinearRegressionPartitionedLinear.RegressionPartitionedLinear_~c cRegressionPartitionedLinear.ModelParametersRegressionPartitionedLinear.ModelParametersS}W WRegressionPartitionedLinear.PartitionRegressionPartitionedLinear.PartitionK|O ORegressionPartitionedLinear.KFoldRegressionPartitionedLinear.KFoldO{S SRegressionPartitionedLinear.TrainedRegressionPartitionedLinear.TrainedYz] ]RegressionPartitionedLinear.ResponseNameRegressionPartitionedLinear.ResponseNamekyo oRegressionPartitionedLinear.CategoricalPredictorsRegressionPartitionedLinear.CategoricalPredictors 74V v >  ( l  f  ~ tHb Zh2\\l x4'b+ +GapEvaluation.BGapEvaluation.B#a' 'GapEvaluationGapEvaluationA`E EDaviesBouldinEvaluation.plotDaviesBouldinEvaluation.plotA_E EDaviesBouldinEvaluation.addKDaviesBouldinEvaluation.addKg^k kDaviesBouldinEvaluation.DaviesBouldinEvaluationDaviesBouldinEvaluation.DaviesBouldinEvaluation;]? ?DaviesBouldinEvaluation.XDaviesBouldinEvaluation.XI\M MDaviesBouldinEvaluation.OptimalYDaviesBouldinEvaluation.OptimalYI[M MDaviesBouldinEvaluation.OptimalKDaviesBouldinEvaluation.OptimalKWZ[ [DaviesBouldinEvaluation.NumObservationsDaviesBouldinEvaluation.NumObservationsGYK KDaviesBouldinEvaluation.MissingDaviesBouldinEvaluation.MissingMXQ QDaviesBouldinEvaluation.InspectedKDaviesBouldinEvaluation.InspectedKWW[ [DaviesBouldinEvaluation.CriterionValuesDaviesBouldinEvaluation.CriterionValuesSVW WDaviesBouldinEvaluation.CriterionNameDaviesBouldinEvaluation.CriterionName]Ua aDaviesBouldinEvaluation.ClusteringFunctionDaviesBouldinEvaluation.ClusteringFunction7T; ;DaviesBouldinEvaluationDaviesBouldinEvaluation9S= =ClusterCriterion.compactClusterCriterion.compact3R7 7ClusterCriterion.plotClusterCriterion.plot3Q7 7ClusterCriterion.addKClusterCriterion.addKKPO OClusterCriterion.ClusterCriterionClusterCriterion.ClusterCriterion-O1 1ClusterCriterion.XClusterCriterion.X;N? ?ClusterCriterion.OptimalYClusterCriterion.OptimalY;M? ?ClusterCriterion.OptimalKClusterCriterion.OptimalKILM MClusterCriterion.NumObservationsClusterCriterion.NumObservations9K= =ClusterCriterion.MissingClusterCriterion.Missing?JC CClusterCriterion.InspectedKClusterCriterion.InspectedKIIM MClusterCriterion.CriterionValuesClusterCriterion.CriterionValuesEHI IClusterCriterion.CriterionNameClusterCriterion.CriterionNameOGS SClusterCriterion.ClusteringFunctionClusterCriterion.ClusteringFunction)F- -ClusterCriterionClusterCriterionGEK KCalinskiHarabaszEvaluation.plotCalinskiHarabaszEvaluation.plotGDK KCalinskiHarabaszEvaluation.addKCalinskiHarabaszEvaluation.addKsCw wCalinskiHarabaszEvaluation.CalinskiHarabaszEvaluationCalinskiHarabaszEvaluation.CalinskiHarabaszEvaluationABE ECalinskiHarabaszEvaluation.XCalinskiHarabaszEvaluation.XOAS SCalinskiHarabaszEvaluation.OptimalYCalinskiHarabaszEvaluation.OptimalYO@S SCalinskiHarabaszEvaluation.OptimalKCalinskiHarabaszEvaluation.OptimalK]?a aCalinskiHarabaszEvaluation.NumObservationsCalinskiHarabaszEvaluation.NumObservationsM>Q QCalinskiHarabaszEvaluation.MissingCalinskiHarabaszEvaluation.MissingS=W WCalinskiHarabaszEvaluation.InspectedKCalinskiHarabaszEvaluation.InspectedK]<a aCalinskiHarabaszEvaluation.CriterionValuesCalinskiHarabaszEvaluation.CriterionValuesY;] ]CalinskiHarabaszEvaluation.CriterionNameCalinskiHarabaszEvaluation.CriterionNamec:g gCalinskiHarabaszEvaluation.ClusteringFunctionCalinskiHarabaszEvaluation.ClusteringFunction=9A ACalinskiHarabaszEvaluationCalinskiHarabaszEvaluation78; ;RegressionSVM.savemodelRegressionSVM.savemodel377 7RegressionSVM.compactRegressionSVM.compact569 9RegressionSVM.crossvalRegressionSVM.crossval75; ;RegressionSVM.resubLossRegressionSVM.resubLoss-41 1RegressionSVM.lossRegressionSVM.loss=3A ARegressionSVM.resubPredictRegressionSVM.resubPredict327 7RegressionSVM.predictRegressionSVM.predictO1S SRegressionSVM.discardSupportVectorsRegressionSVM.discardSupportVectors?0C CRegressionSVM.RegressionSVMRegressionSVM.RegressionSVMG/K KRegressionSVM.ResponseTransformRegressionSVM.ResponseTransformg.k kRegressionSVM.HyperparameterOptimizationResultsRegressionSVM.HyperparameterOptimizationResults5-9 9RegressionSVM.BinEdgesRegressionSVM.BinEdges',+ +RegressionSVM.WRegressionSVM.W >v6d T  h > t F  x 4 b 2 r.h0Z*`\z&bX$I M MExhaustiveSearcher.DistParameterExhaustiveSearcher.DistParameter?C CExhaustiveSearcher.DistanceExhaustiveSearcher.Distance15 5ExhaustiveSearcher.XExhaustiveSearcher.X-1 1ExhaustiveSearcherExhaustiveSearcher;? ?SilhouetteEvaluation.plotSilhouetteEvaluation.plot;? ?SilhouetteEvaluation.addKSilhouetteEvaluation.addK[_ _SilhouetteEvaluation.SilhouetteEvaluationSilhouetteEvaluation.SilhouetteEvaluation59 9SilhouetteEvaluation.XSilhouetteEvaluation.XCG GSilhouetteEvaluation.OptimalYSilhouetteEvaluation.OptimalYCG GSilhouetteEvaluation.OptimalKSilhouetteEvaluation.OptimalKQU USilhouetteEvaluation.NumObservationsSilhouetteEvaluation.NumObservationsAE ESilhouetteEvaluation.MissingSilhouetteEvaluation.MissingGK KSilhouetteEvaluation.InspectedKSilhouetteEvaluation.InspectedKQU USilhouetteEvaluation.CriterionValuesSilhouetteEvaluation.CriterionValuesMQ QSilhouetteEvaluation.CriterionNameSilhouetteEvaluation.CriterionNameW[ [SilhouetteEvaluation.ClusteringFunctionSilhouetteEvaluation.ClusteringFunctionW[ [SilhouetteEvaluation.ClusterSilhouettesSilhouetteEvaluation.ClusterSilhouettesMQ QSilhouetteEvaluation.ClusterPriorsSilhouetteEvaluation.ClusterPriorsCG GSilhouetteEvaluation.DistanceSilhouetteEvaluation.Distance1 5 5SilhouetteEvaluationSilhouetteEvaluation- 1 1gmdistribution.fitgmdistribution.fit3 7 7gmdistribution.randomgmdistribution.random9 = =gmdistribution.posteriorgmdistribution.posterior- 1 1gmdistribution.pdfgmdistribution.pdf15 5gmdistribution.mahalgmdistribution.mahal59 9gmdistribution.clustergmdistribution.cluster-1 1gmdistribution.cdfgmdistribution.cdfCG Ggmdistribution.gmdistributiongmdistribution.gmdistributionMQ Qgmdistribution.RegularizationValuegmdistribution.RegularizationValueAE Egmdistribution.NumIterationsgmdistribution.NumIterationsQU Ugmdistribution.NegativeLogLikelihoodgmdistribution.NegativeLogLikelihood9= =gmdistribution.Convergedgmdistribution.Converged-1 1gmdistribution.BICgmdistribution.BIC-1 1gmdistribution.AICgmdistribution.AICG~K Kgmdistribution.SharedCovariancegmdistribution.SharedCovarianceC}G Ggmdistribution.CovarianceTypegmdistribution.CovarianceType?|C Cgmdistribution.NumVariablesgmdistribution.NumVariablesA{E Egmdistribution.NumComponentsgmdistribution.NumComponentsGzK Kgmdistribution.DistributionNamegmdistribution.DistributionNameMyQ Qgmdistribution.ComponentProportiongmdistribution.ComponentProportion1x5 5gmdistribution.Sigmagmdistribution.Sigma+w/ /gmdistribution.mugmdistribution.mu%v) )gmdistributiongmdistribution-u1 1GapEvaluation.plotGapEvaluation.plot-t1 1GapEvaluation.addKGapEvaluation.addK?sC CGapEvaluation.GapEvaluationGapEvaluation.GapEvaluation'r+ +GapEvaluation.XGapEvaluation.X5q9 9GapEvaluation.OptimalYGapEvaluation.OptimalY5p9 9GapEvaluation.OptimalKGapEvaluation.OptimalKCoG GGapEvaluation.NumObservationsGapEvaluation.NumObservations3n7 7GapEvaluation.MissingGapEvaluation.Missing9m= =GapEvaluation.InspectedKGapEvaluation.InspectedKClG GGapEvaluation.CriterionValuesGapEvaluation.CriterionValues?kC CGapEvaluation.CriterionNameGapEvaluation.CriterionNameIjM MGapEvaluation.ClusteringFunctionGapEvaluation.ClusteringFunction3i7 7GapEvaluation.StdLogWGapEvaluation.StdLogW)h- -GapEvaluation.SEGapEvaluation.SE-g1 1GapEvaluation.LogWGapEvaluation.LogW=fA AGapEvaluation.ExpectedLogWGapEvaluation.ExpectedLogW=eA AGapEvaluation.SearchMethodGapEvaluation.SearchMethodOdS SGapEvaluation.ReferenceDistributionGapEvaluation.ReferenceDistribution5c9 9GapEvaluation.DistanceGapEvaluation.Distance =f: ^ 6 N  b l $ l "6z@jl>RVHL3]7 7SparseFiltering.SigmaSparseFiltering.Sigma-\1 1SparseFiltering.MuSparseFiltering.MuM[Q QSparseFiltering.NumLearnedFeaturesSparseFiltering.NumLearnedFeaturesCZG GSparseFiltering.NumPredictorsSparseFiltering.NumPredictorsGYK KSparseFiltering.ModelParametersSparseFiltering.ModelParameters'X+ +SparseFilteringSparseFiltering?WC CReconstructionICA.transformReconstructionICA.transformOVS SReconstructionICA.ReconstructionICAReconstructionICA.ReconstructionICA[U_ _ReconstructionICA.NonGaussianityIndicatorReconstructionICA.NonGaussianityIndicator[T_ _ReconstructionICA.InitialTransformWeightsReconstructionICA.InitialTransformWeightsMSQ QReconstructionICA.TransformWeightsReconstructionICA.TransformWeights;R? ?ReconstructionICA.FitInfoReconstructionICA.FitInfo7Q; ;ReconstructionICA.SigmaReconstructionICA.Sigma1P5 5ReconstructionICA.MuReconstructionICA.MuQOU UReconstructionICA.NumLearnedFeaturesReconstructionICA.NumLearnedFeaturesGNK KReconstructionICA.NumPredictorsReconstructionICA.NumPredictorsKMO OReconstructionICA.ModelParametersReconstructionICA.ModelParameters+L/ /ReconstructionICAReconstructionICA3K7 7OneClassSVM.isanomalyOneClassSVM.isanomaly7J; ;OneClassSVM.OneClassSVMOneClassSVM.OneClassSVM=IA AOneClassSVM.ScoreThresholdOneClassSVM.ScoreThresholdKHO OOneClassSVM.ContaminationFractionOneClassSVM.ContaminationFraction+G/ /OneClassSVM.SigmaOneClassSVM.Sigma%F) )OneClassSVM.MuOneClassSVM.MuMEQ QOneClassSVM.NumExpansionDimensionsOneClassSVM.NumExpansionDimensions-D1 1OneClassSVM.LambdaOneClassSVM.Lambda7C; ;OneClassSVM.KernelScaleOneClassSVM.KernelScaleB# #OneClassSVMOneClassSVMAAE ELocalOutlierFactor.isanomalyLocalOutlierFactor.isanomalyS@W WLocalOutlierFactor.LocalOutlierFactorLocalOutlierFactor.LocalOutlierFactorK?O OLocalOutlierFactor.ScoreThresholdLocalOutlierFactor.ScoreThresholdY>] ]LocalOutlierFactor.ContaminationFractionLocalOutlierFactor.ContaminationFraction?=C CLocalOutlierFactor.DistanceLocalOutlierFactor.DistanceG<K KLocalOutlierFactor.NumNeighborsLocalOutlierFactor.NumNeighbors-;1 1LocalOutlierFactorLocalOutlierFactor;:? ?IsolationForest.isanomalyIsolationForest.isanomalyG9K KIsolationForest.IsolationForestIsolationForest.IsolationForestE8I IIsolationForest.ScoreThresholdIsolationForest.ScoreThresholdS7W WIsolationForest.ContaminationFractionIsolationForest.ContaminationFraction[6_ _IsolationForest.NumObservationsPerLearnerIsolationForest.NumObservationsPerLearner?5C CIsolationForest.NumLearnersIsolationForest.NumLearners'4+ +IsolationForestIsolationForest=3A AKDTreeSearcher.rangesearchKDTreeSearcher.rangesearch92= =KDTreeSearcher.knnsearchKDTreeSearcher.knnsearchC1G GKDTreeSearcher.KDTreeSearcherKDTreeSearcher.KDTreeSearcherA0E EKDTreeSearcher.DistParameterKDTreeSearcher.DistParameter7/; ;KDTreeSearcher.DistanceKDTreeSearcher.Distance;.? ?KDTreeSearcher.BucketSizeKDTreeSearcher.BucketSize)-- -KDTreeSearcher.XKDTreeSearcher.X%,) )KDTreeSearcherKDTreeSearcher5+9 9hnswSearcher.knnsearchhnswSearcher.knnsearch;*? ?hnswSearcher.hnswSearcherhnswSearcher.hnswSearcher%)) )hnswSearcher.XhnswSearcher.X;(? ?hnswSearcher.TrainSetSizehnswSearcher.TrainSetSizeG'K KhnswSearcher.MaxNumLinksPerNodehnswSearcher.MaxNumLinksPerNode=&A AhnswSearcher.DistParameterhnswSearcher.DistParameter3%7 7hnswSearcher.DistancehnswSearcher.Distance!$% %hnswSearcherhnswSearcherE#I IExhaustiveSearcher.rangesearchExhaustiveSearcher.rangesearchA"E EExhaustiveSearcher.knnsearchExhaustiveSearcher.knnsearchS!W WExhaustiveSearcher.ExhaustiveSearcherExhaustiveSearcher.ExhaustiveSearcher Bz d" Z  n  8 L j (~\vJj0~N&l>rH&tJ Z /3 3paretotails.segmentparetotails.segment7; ;paretotails.upperparamsparetotails.upperparams7; ;paretotails.lowerparamsparetotails.lowerparams37 7paretotails.nsegmentsparetotails.nsegments15 5paretotails.boundaryparetotails.boundary-1 1paretotails.randomparetotails.random)- -paretotails.icdfparetotails.icdf'+ +paretotails.pdfparetotails.pdf'+ +paretotails.cdfparetotails.cdf7; ;paretotails.paretotailsparetotails.paretotails;? ?paretotails.NumParametersparetotails.NumParameters7; ;paretotails.NumSegmentsparetotails.NumSegments# #paretotailsparetotails'+ +rocmetrics.plotrocmetrics.plot-1 1rocmetrics.averagerocmetrics.average37 7rocmetrics.addMetricsrocmetrics.addMetrics37 7rocmetrics.rocmetricsrocmetrics.rocmetrics-1 1rocmetrics.Weightsrocmetrics.Weights+ / /rocmetrics.Scoresrocmetrics.Scores+ / /rocmetrics.Labelsrocmetrics.Labels) - -rocmetrics.Priorrocmetrics.Prior' + +rocmetrics.Costrocmetrics.Cost3 7 7rocmetrics.ClassNamesrocmetrics.ClassNames%) )rocmetrics.AUCrocmetrics.AUC-1 1rocmetrics.Metricsrocmetrics.Metrics! !rocmetricsrocmetrics15 5cvpartition.trainingcvpartition.training)- -cvpartition.testcvpartition.test/3 3cvpartition.summarycvpartition.summary7; ;cvpartition.repartitioncvpartition.repartition7; ;cvpartition.cvpartitioncvpartition.cvpartition9= =cvpartition.IsStratifiedcvpartition.IsStratified37 7cvpartition.IsGroupedcvpartition.IsGrouped1~5 5cvpartition.IsCustomcvpartition.IsCustom)}- -cvpartition.Typecvpartition.Type1|5 5cvpartition.TestSizecvpartition.TestSize3{7 7cvpartition.TrainSizecvpartition.TrainSize7z; ;cvpartition.NumTestSetscvpartition.NumTestSets?yC Ccvpartition.NumObservationscvpartition.NumObservationsx# #cvpartitioncvpartitionIwM MConfusionMatrixChart.sortClassesConfusionMatrixChart.sortClasses[v_ _ConfusionMatrixChart.ConfusionMatrixChartConfusionMatrixChart.ConfusionMatrixChart?uC CConfusionMatrixChart.ParentConfusionMatrixChart.ParentStW WConfusionMatrixChart.NormalizedValuesConfusionMatrixChart.NormalizedValuesIsM MConfusionMatrixChart.ClassLabelsConfusionMatrixChart.ClassLabels=rA AConfusionMatrixChart.UnitsConfusionMatrixChart.UnitsCqG GConfusionMatrixChart.PositionConfusionMatrixChart.PositionMpQ QConfusionMatrixChart.OuterPositionConfusionMatrixChart.OuterPositionSoW WConfusionMatrixChart.HandleVisibilityConfusionMatrixChart.HandleVisibilityInM MConfusionMatrixChart.GridVisibleConfusionMatrixChart.GridVisibleGmK KConfusionMatrixChart.RowSummaryConfusionMatrixChart.RowSummaryMlQ QConfusionMatrixChart.ColumnSummaryConfusionMatrixChart.ColumnSummaryMkQ QConfusionMatrixChart.NormalizationConfusionMatrixChart.NormalizationSjW WConfusionMatrixChart.OffDiagonalColorConfusionMatrixChart.OffDiagonalColorMiQ QConfusionMatrixChart.DiagonalColorConfusionMatrixChart.DiagonalColorChG GConfusionMatrixChart.FontSizeConfusionMatrixChart.FontSizeCgG GConfusionMatrixChart.FontNameConfusionMatrixChart.FontName=fA AConfusionMatrixChart.TitleConfusionMatrixChart.Title?eC CConfusionMatrixChart.YLabelConfusionMatrixChart.YLabel?dC CConfusionMatrixChart.XLabelConfusionMatrixChart.XLabel1c5 5ConfusionMatrixChartConfusionMatrixChart;b? ?SparseFiltering.transformSparseFiltering.transformGaK KSparseFiltering.SparseFilteringSparseFiltering.SparseFilteringW`[ [SparseFiltering.InitialTransformWeightsSparseFiltering.InitialTransformWeightsI_M MSparseFiltering.TransformWeightsSparseFiltering.TransformWeights7^; ;SparseFiltering.FitInfoSparseFiltering.FitInfo 4Lh ^  f  F  : v 0f(fP6z:`z2ISM Mprob.BinomialDistribution.randomprob.BinomialDistribution.randomKRO Oprob.BinomialDistribution.proflikprob.BinomialDistribution.proflikEQI Iprob.BinomialDistribution.plotprob.BinomialDistribution.plotCPG Gprob.BinomialDistribution.pdfprob.BinomialDistribution.pdfKOO Oprob.BinomialDistribution.paramciprob.BinomialDistribution.paramciONS Sprob.BinomialDistribution.negloglikprob.BinomialDistribution.negloglikIMM Mprob.BinomialDistribution.medianprob.BinomialDistribution.medianELI Iprob.BinomialDistribution.meanprob.BinomialDistribution.meanCKG Gprob.BinomialDistribution.iqrprob.BinomialDistribution.iqrEJI Iprob.BinomialDistribution.icdfprob.BinomialDistribution.icdfCIG Gprob.BinomialDistribution.cdfprob.BinomialDistribution.cdfOHS Sprob.BinomialDistribution.InputDataprob.BinomialDistribution.InputData]Ga aprob.BinomialDistribution.ParameterIsFixedprob.BinomialDistribution.ParameterIsFixedcFg gprob.BinomialDistribution.ParameterCovarianceprob.BinomialDistribution.ParameterCovarianceSEW Wprob.BinomialDistribution.IsTruncatedprob.BinomialDistribution.IsTruncatedQDU Uprob.BinomialDistribution.Truncationprob.BinomialDistribution.Truncation[C_ _prob.BinomialDistribution.ParameterValuesprob.BinomialDistribution.ParameterValueseBi iprob.BinomialDistribution.ParameterDescriptionprob.BinomialDistribution.ParameterDescriptionYA] ]prob.BinomialDistribution.ParameterNamesprob.BinomialDistribution.ParameterNamesW@[ [prob.BinomialDistribution.NumParametersprob.BinomialDistribution.NumParameters]?a aprob.BinomialDistribution.DistributionNameprob.BinomialDistribution.DistributionName?>C Cprob.BinomialDistribution.pprob.BinomialDistribution.p?=C Cprob.BinomialDistribution.Nprob.BinomialDistribution.N;<? ?prob.BinomialDistributionprob.BinomialDistribution;;? ?prob.BetaDistribution.varprob.BetaDistribution.varE:I Iprob.BetaDistribution.truncateprob.BetaDistribution.truncate;9? ?prob.BetaDistribution.stdprob.BetaDistribution.stdA8E Eprob.BetaDistribution.randomprob.BetaDistribution.randomC7G Gprob.BetaDistribution.proflikprob.BetaDistribution.proflik=6A Aprob.BetaDistribution.plotprob.BetaDistribution.plot;5? ?prob.BetaDistribution.pdfprob.BetaDistribution.pdfC4G Gprob.BetaDistribution.paramciprob.BetaDistribution.paramciG3K Kprob.BetaDistribution.negloglikprob.BetaDistribution.negloglikA2E Eprob.BetaDistribution.medianprob.BetaDistribution.median=1A Aprob.BetaDistribution.meanprob.BetaDistribution.mean;0? ?prob.BetaDistribution.iqrprob.BetaDistribution.iqr=/A Aprob.BetaDistribution.icdfprob.BetaDistribution.icdf;.? ?prob.BetaDistribution.cdfprob.BetaDistribution.cdfG-K Kprob.BetaDistribution.InputDataprob.BetaDistribution.InputDataU,Y Yprob.BetaDistribution.ParameterIsFixedprob.BetaDistribution.ParameterIsFixed[+_ _prob.BetaDistribution.ParameterCovarianceprob.BetaDistribution.ParameterCovarianceK*O Oprob.BetaDistribution.IsTruncatedprob.BetaDistribution.IsTruncatedI)M Mprob.BetaDistribution.Truncationprob.BetaDistribution.TruncationS(W Wprob.BetaDistribution.ParameterValuesprob.BetaDistribution.ParameterValues]'a aprob.BetaDistribution.ParameterDescriptionprob.BetaDistribution.ParameterDescriptionQ&U Uprob.BetaDistribution.ParameterNamesprob.BetaDistribution.ParameterNamesO%S Sprob.BetaDistribution.NumParametersprob.BetaDistribution.NumParametersU$Y Yprob.BetaDistribution.DistributionNameprob.BetaDistribution.DistributionName7#; ;prob.BetaDistribution.bprob.BetaDistribution.b7"; ;prob.BetaDistribution.aprob.BetaDistribution.a3!7 7prob.BetaDistributionprob.BetaDistributionA E Eprob.ProbabilityDistributionprob.ProbabilityDistribution -j$~$ J f . H 8.nbPHdZb ;? ?prob.BurrDistribution.cdfprob.BurrDistribution.cdfGK Kprob.BurrDistribution.InputDataprob.BurrDistribution.InputDataU~Y Yprob.BurrDistribution.ParameterIsFixedprob.BurrDistribution.ParameterIsFixed[}_ _prob.BurrDistribution.ParameterCovarianceprob.BurrDistribution.ParameterCovarianceK|O Oprob.BurrDistribution.IsTruncatedprob.BurrDistribution.IsTruncatedI{M Mprob.BurrDistribution.Truncationprob.BurrDistribution.TruncationSzW Wprob.BurrDistribution.ParameterValuesprob.BurrDistribution.ParameterValues]ya aprob.BurrDistribution.ParameterDescriptionprob.BurrDistribution.ParameterDescriptionQxU Uprob.BurrDistribution.ParameterNamesprob.BurrDistribution.ParameterNamesOwS Sprob.BurrDistribution.NumParametersprob.BurrDistribution.NumParametersUvY Yprob.BurrDistribution.DistributionNameprob.BurrDistribution.DistributionName7u; ;prob.BurrDistribution.kprob.BurrDistribution.k7t; ;prob.BurrDistribution.cprob.BurrDistribution.c?sC Cprob.BurrDistribution.alphaprob.BurrDistribution.alpha3r7 7prob.BurrDistributionprob.BurrDistributionSqW Wprob.BirnbaumSaundersDistribution.varprob.BirnbaumSaundersDistribution.var]pa aprob.BirnbaumSaundersDistribution.truncateprob.BirnbaumSaundersDistribution.truncateSoW Wprob.BirnbaumSaundersDistribution.stdprob.BirnbaumSaundersDistribution.stdYn] ]prob.BirnbaumSaundersDistribution.randomprob.BirnbaumSaundersDistribution.random[m_ _prob.BirnbaumSaundersDistribution.proflikprob.BirnbaumSaundersDistribution.proflikUlY Yprob.BirnbaumSaundersDistribution.plotprob.BirnbaumSaundersDistribution.plotSkW Wprob.BirnbaumSaundersDistribution.pdfprob.BirnbaumSaundersDistribution.pdf[j_ _prob.BirnbaumSaundersDistribution.paramciprob.BirnbaumSaundersDistribution.paramci_ic cprob.BirnbaumSaundersDistribution.negloglikprob.BirnbaumSaundersDistribution.negloglikYh] ]prob.BirnbaumSaundersDistribution.medianprob.BirnbaumSaundersDistribution.medianUgY Yprob.BirnbaumSaundersDistribution.meanprob.BirnbaumSaundersDistribution.meanSfW Wprob.BirnbaumSaundersDistribution.iqrprob.BirnbaumSaundersDistribution.iqrUeY Yprob.BirnbaumSaundersDistribution.icdfprob.BirnbaumSaundersDistribution.icdfSdW Wprob.BirnbaumSaundersDistribution.cdfprob.BirnbaumSaundersDistribution.cdf_cc cprob.BirnbaumSaundersDistribution.InputDataprob.BirnbaumSaundersDistribution.InputDatambq qprob.BirnbaumSaundersDistribution.ParameterIsFixedprob.BirnbaumSaundersDistribution.ParameterIsFixedsaw wprob.BirnbaumSaundersDistribution.ParameterCovarianceprob.BirnbaumSaundersDistribution.ParameterCovariancec`g gprob.BirnbaumSaundersDistribution.IsTruncatedprob.BirnbaumSaundersDistribution.IsTruncateda_e eprob.BirnbaumSaundersDistribution.Truncationprob.BirnbaumSaundersDistribution.Truncationk^o oprob.BirnbaumSaundersDistribution.ParameterValuesprob.BirnbaumSaundersDistribution.ParameterValuesu]y yprob.BirnbaumSaundersDistribution.ParameterDescriptionprob.BirnbaumSaundersDistribution.ParameterDescriptioni\m mprob.BirnbaumSaundersDistribution.ParameterNamesprob.BirnbaumSaundersDistribution.ParameterNamesg[k kprob.BirnbaumSaundersDistribution.NumParametersprob.BirnbaumSaundersDistribution.NumParametersmZq qprob.BirnbaumSaundersDistribution.DistributionNameprob.BirnbaumSaundersDistribution.DistributionNameWY[ [prob.BirnbaumSaundersDistribution.gammaprob.BirnbaumSaundersDistribution.gammaUXY Yprob.BirnbaumSaundersDistribution.betaprob.BirnbaumSaundersDistribution.betaKWO Oprob.BirnbaumSaundersDistributionprob.BirnbaumSaundersDistributionCVG Gprob.BinomialDistribution.varprob.BinomialDistribution.varMUQ Qprob.BinomialDistribution.truncateprob.BinomialDistribution.truncateCTG Gprob.BinomialDistribution.stdprob.BinomialDistribution.std 0Bn0 f ( ^  N ~  d:T\n|&H,XY0] ]prob.ExtremeValueDistribution.Truncationprob.ExtremeValueDistribution.Truncationc/g gprob.ExtremeValueDistribution.ParameterValuesprob.ExtremeValueDistribution.ParameterValuesm.q qprob.ExtremeValueDistribution.ParameterDescriptionprob.ExtremeValueDistribution.ParameterDescriptiona-e eprob.ExtremeValueDistribution.ParameterNamesprob.ExtremeValueDistribution.ParameterNames_,c cprob.ExtremeValueDistribution.NumParametersprob.ExtremeValueDistribution.NumParameterse+i iprob.ExtremeValueDistribution.DistributionNameprob.ExtremeValueDistribution.DistributionNameO*S Sprob.ExtremeValueDistribution.sigmaprob.ExtremeValueDistribution.sigmaI)M Mprob.ExtremeValueDistribution.muprob.ExtremeValueDistribution.muC(G Gprob.ExtremeValueDistributionprob.ExtremeValueDistributionI'M Mprob.ExponentialDistribution.varprob.ExponentialDistribution.varS&W Wprob.ExponentialDistribution.truncateprob.ExponentialDistribution.truncateI%M Mprob.ExponentialDistribution.stdprob.ExponentialDistribution.stdO$S Sprob.ExponentialDistribution.randomprob.ExponentialDistribution.randomQ#U Uprob.ExponentialDistribution.proflikprob.ExponentialDistribution.proflikK"O Oprob.ExponentialDistribution.plotprob.ExponentialDistribution.plotI!M Mprob.ExponentialDistribution.pdfprob.ExponentialDistribution.pdfQ U Uprob.ExponentialDistribution.paramciprob.ExponentialDistribution.paramciUY Yprob.ExponentialDistribution.negloglikprob.ExponentialDistribution.negloglikOS Sprob.ExponentialDistribution.medianprob.ExponentialDistribution.medianKO Oprob.ExponentialDistribution.meanprob.ExponentialDistribution.meanIM Mprob.ExponentialDistribution.iqrprob.ExponentialDistribution.iqrKO Oprob.ExponentialDistribution.icdfprob.ExponentialDistribution.icdfIM Mprob.ExponentialDistribution.cdfprob.ExponentialDistribution.cdfUY Yprob.ExponentialDistribution.InputDataprob.ExponentialDistribution.InputDatacg gprob.ExponentialDistribution.ParameterIsFixedprob.ExponentialDistribution.ParameterIsFixedim mprob.ExponentialDistribution.ParameterCovarianceprob.ExponentialDistribution.ParameterCovarianceY] ]prob.ExponentialDistribution.IsTruncatedprob.ExponentialDistribution.IsTruncatedW[ [prob.ExponentialDistribution.Truncationprob.ExponentialDistribution.Truncationae eprob.ExponentialDistribution.ParameterValuesprob.ExponentialDistribution.ParameterValuesko oprob.ExponentialDistribution.ParameterDescriptionprob.ExponentialDistribution.ParameterDescription_c cprob.ExponentialDistribution.ParameterNamesprob.ExponentialDistribution.ParameterNames]a aprob.ExponentialDistribution.NumParametersprob.ExponentialDistribution.NumParameterscg gprob.ExponentialDistribution.DistributionNameprob.ExponentialDistribution.DistributionNameGK Kprob.ExponentialDistribution.muprob.ExponentialDistribution.muAE Eprob.ExponentialDistributionprob.ExponentialDistribution; ? ?prob.BurrDistribution.varprob.BurrDistribution.varE I Iprob.BurrDistribution.truncateprob.BurrDistribution.truncate; ? ?prob.BurrDistribution.stdprob.BurrDistribution.stdA E Eprob.BurrDistribution.randomprob.BurrDistribution.randomC G Gprob.BurrDistribution.proflikprob.BurrDistribution.proflik=A Aprob.BurrDistribution.plotprob.BurrDistribution.plot;? ?prob.BurrDistribution.pdfprob.BurrDistribution.pdfCG Gprob.BurrDistribution.paramciprob.BurrDistribution.paramciGK Kprob.BurrDistribution.negloglikprob.BurrDistribution.negloglikAE Eprob.BurrDistribution.medianprob.BurrDistribution.median=A Aprob.BurrDistribution.meanprob.BurrDistribution.mean;? ?prob.BurrDistribution.iqrprob.BurrDistribution.iqr=A Aprob.BurrDistribution.icdfprob.BurrDistribution.icdf 14r$ 6 2 > D FB:0b"N<l,p_ac cprob.GeneralizedExtremeValueDistribution.muprob.GeneralizedExtremeValueDistribution.mue`i iprob.GeneralizedExtremeValueDistribution.sigmaprob.GeneralizedExtremeValueDistribution.sigma]_a aprob.GeneralizedExtremeValueDistribution.kprob.GeneralizedExtremeValueDistribution.kY^] ]prob.GeneralizedExtremeValueDistributionprob.GeneralizedExtremeValueDistribution=]A Aprob.GammaDistribution.varprob.GammaDistribution.varG\K Kprob.GammaDistribution.truncateprob.GammaDistribution.truncate=[A Aprob.GammaDistribution.stdprob.GammaDistribution.stdCZG Gprob.GammaDistribution.randomprob.GammaDistribution.randomEYI Iprob.GammaDistribution.proflikprob.GammaDistribution.proflik?XC Cprob.GammaDistribution.plotprob.GammaDistribution.plot=WA Aprob.GammaDistribution.pdfprob.GammaDistribution.pdfEVI Iprob.GammaDistribution.paramciprob.GammaDistribution.paramciIUM Mprob.GammaDistribution.negloglikprob.GammaDistribution.negloglikCTG Gprob.GammaDistribution.medianprob.GammaDistribution.median?SC Cprob.GammaDistribution.meanprob.GammaDistribution.mean=RA Aprob.GammaDistribution.iqrprob.GammaDistribution.iqr?QC Cprob.GammaDistribution.icdfprob.GammaDistribution.icdf=PA Aprob.GammaDistribution.cdfprob.GammaDistribution.cdfIOM Mprob.GammaDistribution.InputDataprob.GammaDistribution.InputDataWN[ [prob.GammaDistribution.ParameterIsFixedprob.GammaDistribution.ParameterIsFixed]Ma aprob.GammaDistribution.ParameterCovarianceprob.GammaDistribution.ParameterCovarianceMLQ Qprob.GammaDistribution.IsTruncatedprob.GammaDistribution.IsTruncatedKKO Oprob.GammaDistribution.Truncationprob.GammaDistribution.TruncationUJY Yprob.GammaDistribution.ParameterValuesprob.GammaDistribution.ParameterValues_Ic cprob.GammaDistribution.ParameterDescriptionprob.GammaDistribution.ParameterDescriptionSHW Wprob.GammaDistribution.ParameterNamesprob.GammaDistribution.ParameterNamesQGU Uprob.GammaDistribution.NumParametersprob.GammaDistribution.NumParametersWF[ [prob.GammaDistribution.DistributionNameprob.GammaDistribution.DistributionName9E= =prob.GammaDistribution.bprob.GammaDistribution.b9D= =prob.GammaDistribution.aprob.GammaDistribution.a5C9 9prob.GammaDistributionprob.GammaDistributionKBO Oprob.ExtremeValueDistribution.varprob.ExtremeValueDistribution.varUAY Yprob.ExtremeValueDistribution.truncateprob.ExtremeValueDistribution.truncateK@O Oprob.ExtremeValueDistribution.stdprob.ExtremeValueDistribution.stdQ?U Uprob.ExtremeValueDistribution.randomprob.ExtremeValueDistribution.randomS>W Wprob.ExtremeValueDistribution.proflikprob.ExtremeValueDistribution.proflikM=Q Qprob.ExtremeValueDistribution.plotprob.ExtremeValueDistribution.plotK<O Oprob.ExtremeValueDistribution.pdfprob.ExtremeValueDistribution.pdfS;W Wprob.ExtremeValueDistribution.paramciprob.ExtremeValueDistribution.paramciW:[ [prob.ExtremeValueDistribution.negloglikprob.ExtremeValueDistribution.negloglikQ9U Uprob.ExtremeValueDistribution.medianprob.ExtremeValueDistribution.medianM8Q Qprob.ExtremeValueDistribution.meanprob.ExtremeValueDistribution.meanK7O Oprob.ExtremeValueDistribution.iqrprob.ExtremeValueDistribution.iqrM6Q Qprob.ExtremeValueDistribution.icdfprob.ExtremeValueDistribution.icdfK5O Oprob.ExtremeValueDistribution.cdfprob.ExtremeValueDistribution.cdfW4[ [prob.ExtremeValueDistribution.InputDataprob.ExtremeValueDistribution.InputDatae3i iprob.ExtremeValueDistribution.ParameterIsFixedprob.ExtremeValueDistribution.ParameterIsFixedk2o oprob.ExtremeValueDistribution.ParameterCovarianceprob.ExtremeValueDistribution.ParameterCovariance[1_ _prob.ExtremeValueDistribution.IsTruncatedprob.ExtremeValueDistribution.IsTruncated $    0 f  2V R0$F^ uy yprob.GeneralizedParetoDistribution.ParameterCovarianceprob.GeneralizedParetoDistribution.ParameterCovarianceei iprob.GeneralizedParetoDistribution.IsTruncatedprob.GeneralizedParetoDistribution.IsTruncatedcg gprob.GeneralizedParetoDistribution.Truncationprob.GeneralizedParetoDistribution.Truncationmq qprob.GeneralizedParetoDistribution.ParameterValuesprob.GeneralizedParetoDistribution.ParameterValuesw{ {prob.GeneralizedParetoDistribution.ParameterDescriptionprob.GeneralizedParetoDistribution.ParameterDescriptionko oprob.GeneralizedParetoDistribution.ParameterNamesprob.GeneralizedParetoDistribution.ParameterNamesim mprob.GeneralizedParetoDistribution.NumParametersprob.GeneralizedParetoDistribution.NumParameterso~s sprob.GeneralizedParetoDistribution.DistributionNameprob.GeneralizedParetoDistribution.DistributionNameY}] ]prob.GeneralizedParetoDistribution.thetaprob.GeneralizedParetoDistribution.thetaY|] ]prob.GeneralizedParetoDistribution.sigmaprob.GeneralizedParetoDistribution.sigmaQ{U Uprob.GeneralizedParetoDistribution.kprob.GeneralizedParetoDistribution.kMzQ Qprob.GeneralizedParetoDistributionprob.GeneralizedParetoDistributionaye eprob.GeneralizedExtremeValueDistribution.varprob.GeneralizedExtremeValueDistribution.varkxo oprob.GeneralizedExtremeValueDistribution.truncateprob.GeneralizedExtremeValueDistribution.truncateawe eprob.GeneralizedExtremeValueDistribution.stdprob.GeneralizedExtremeValueDistribution.stdgvk kprob.GeneralizedExtremeValueDistribution.randomprob.GeneralizedExtremeValueDistribution.randomium mprob.GeneralizedExtremeValueDistribution.proflikprob.GeneralizedExtremeValueDistribution.proflikctg gprob.GeneralizedExtremeValueDistribution.plotprob.GeneralizedExtremeValueDistribution.plotase eprob.GeneralizedExtremeValueDistribution.pdfprob.GeneralizedExtremeValueDistribution.pdfirm mprob.GeneralizedExtremeValueDistribution.paramciprob.GeneralizedExtremeValueDistribution.paramcimqq qprob.GeneralizedExtremeValueDistribution.negloglikprob.GeneralizedExtremeValueDistribution.negloglikgpk kprob.GeneralizedExtremeValueDistribution.medianprob.GeneralizedExtremeValueDistribution.mediancog gprob.GeneralizedExtremeValueDistribution.meanprob.GeneralizedExtremeValueDistribution.meanane eprob.GeneralizedExtremeValueDistribution.iqrprob.GeneralizedExtremeValueDistribution.iqrcmg gprob.GeneralizedExtremeValueDistribution.icdfprob.GeneralizedExtremeValueDistribution.icdfale eprob.GeneralizedExtremeValueDistribution.cdfprob.GeneralizedExtremeValueDistribution.cdfmkq qprob.GeneralizedExtremeValueDistribution.InputDataprob.GeneralizedExtremeValueDistribution.InputData{j prob.GeneralizedExtremeValueDistribution.ParameterIsFixedprob.GeneralizedExtremeValueDistribution.ParameterIsFixedi  prob.GeneralizedExtremeValueDistribution.ParameterCovarianceprob.GeneralizedExtremeValueDistribution.ParameterCovarianceqhu uprob.GeneralizedExtremeValueDistribution.IsTruncatedprob.GeneralizedExtremeValueDistribution.IsTruncatedogs sprob.GeneralizedExtremeValueDistribution.Truncationprob.GeneralizedExtremeValueDistribution.Truncationyf} }prob.GeneralizedExtremeValueDistribution.ParameterValuesprob.GeneralizedExtremeValueDistribution.ParameterValuese  prob.GeneralizedExtremeValueDistribution.ParameterDescriptionprob.GeneralizedExtremeValueDistribution.ParameterDescriptionwd{ {prob.GeneralizedExtremeValueDistribution.ParameterNamesprob.GeneralizedExtremeValueDistribution.ParameterNamesucy yprob.GeneralizedExtremeValueDistribution.NumParametersprob.GeneralizedExtremeValueDistribution.NumParameters{b prob.GeneralizedExtremeValueDistribution.DistributionNameprob.GeneralizedExtremeValueDistribution.DistributionName -*x h  L 4 z " J(Z>:X`x(@O2S Sprob.InverseGaussianDistribution.muprob.InverseGaussianDistribution.muI1M Mprob.InverseGaussianDistributionprob.InverseGaussianDistributionG0K Kprob.HalfNormalDistribution.varprob.HalfNormalDistribution.varQ/U Uprob.HalfNormalDistribution.truncateprob.HalfNormalDistribution.truncateG.K Kprob.HalfNormalDistribution.stdprob.HalfNormalDistribution.stdM-Q Qprob.HalfNormalDistribution.randomprob.HalfNormalDistribution.randomO,S Sprob.HalfNormalDistribution.proflikprob.HalfNormalDistribution.proflikI+M Mprob.HalfNormalDistribution.plotprob.HalfNormalDistribution.plotG*K Kprob.HalfNormalDistribution.pdfprob.HalfNormalDistribution.pdfO)S Sprob.HalfNormalDistribution.paramciprob.HalfNormalDistribution.paramciS(W Wprob.HalfNormalDistribution.negloglikprob.HalfNormalDistribution.negloglikM'Q Qprob.HalfNormalDistribution.medianprob.HalfNormalDistribution.medianI&M Mprob.HalfNormalDistribution.meanprob.HalfNormalDistribution.meanG%K Kprob.HalfNormalDistribution.iqrprob.HalfNormalDistribution.iqrI$M Mprob.HalfNormalDistribution.icdfprob.HalfNormalDistribution.icdfG#K Kprob.HalfNormalDistribution.cdfprob.HalfNormalDistribution.cdfS"W Wprob.HalfNormalDistribution.InputDataprob.HalfNormalDistribution.InputDataa!e eprob.HalfNormalDistribution.ParameterIsFixedprob.HalfNormalDistribution.ParameterIsFixedg k kprob.HalfNormalDistribution.ParameterCovarianceprob.HalfNormalDistribution.ParameterCovarianceW[ [prob.HalfNormalDistribution.IsTruncatedprob.HalfNormalDistribution.IsTruncatedUY Yprob.HalfNormalDistribution.Truncationprob.HalfNormalDistribution.Truncation_c cprob.HalfNormalDistribution.ParameterValuesprob.HalfNormalDistribution.ParameterValuesim mprob.HalfNormalDistribution.ParameterDescriptionprob.HalfNormalDistribution.ParameterDescription]a aprob.HalfNormalDistribution.ParameterNamesprob.HalfNormalDistribution.ParameterNames[_ _prob.HalfNormalDistribution.NumParametersprob.HalfNormalDistribution.NumParametersae eprob.HalfNormalDistribution.DistributionNameprob.HalfNormalDistribution.DistributionNameKO Oprob.HalfNormalDistribution.sigmaprob.HalfNormalDistribution.sigmaEI Iprob.HalfNormalDistribution.muprob.HalfNormalDistribution.mu?C Cprob.HalfNormalDistributionprob.HalfNormalDistributionUY Yprob.GeneralizedParetoDistribution.varprob.GeneralizedParetoDistribution.var_c cprob.GeneralizedParetoDistribution.truncateprob.GeneralizedParetoDistribution.truncateUY Yprob.GeneralizedParetoDistribution.stdprob.GeneralizedParetoDistribution.std[_ _prob.GeneralizedParetoDistribution.randomprob.GeneralizedParetoDistribution.random]a aprob.GeneralizedParetoDistribution.proflikprob.GeneralizedParetoDistribution.proflikW[ [prob.GeneralizedParetoDistribution.plotprob.GeneralizedParetoDistribution.plotUY Yprob.GeneralizedParetoDistribution.pdfprob.GeneralizedParetoDistribution.pdf]a aprob.GeneralizedParetoDistribution.paramciprob.GeneralizedParetoDistribution.paramcia e eprob.GeneralizedParetoDistribution.negloglikprob.GeneralizedParetoDistribution.negloglik[ _ _prob.GeneralizedParetoDistribution.medianprob.GeneralizedParetoDistribution.medianW [ [prob.GeneralizedParetoDistribution.meanprob.GeneralizedParetoDistribution.meanU Y Yprob.GeneralizedParetoDistribution.iqrprob.GeneralizedParetoDistribution.iqrW [ [prob.GeneralizedParetoDistribution.icdfprob.GeneralizedParetoDistribution.icdfUY Yprob.GeneralizedParetoDistribution.cdfprob.GeneralizedParetoDistribution.cdfae eprob.GeneralizedParetoDistribution.InputDataprob.GeneralizedParetoDistribution.InputDataos sprob.GeneralizedParetoDistribution.ParameterIsFixedprob.GeneralizedParetoDistribution.ParameterIsFixed /8f " J | ( ~ ( n h ^r6F~:bHAaE Eprob.LogisticDistribution.muprob.LogisticDistribution.mu;`? ?prob.LogisticDistributionprob.LogisticDistribution?_C Cprob.KernelDistribution.varprob.KernelDistribution.varI^M Mprob.KernelDistribution.truncateprob.KernelDistribution.truncate?]C Cprob.KernelDistribution.stdprob.KernelDistribution.stdE\I Iprob.KernelDistribution.randomprob.KernelDistribution.randomA[E Eprob.KernelDistribution.plotprob.KernelDistribution.plot?ZC Cprob.KernelDistribution.pdfprob.KernelDistribution.pdfKYO Oprob.KernelDistribution.negloglikprob.KernelDistribution.negloglikEXI Iprob.KernelDistribution.medianprob.KernelDistribution.medianAWE Eprob.KernelDistribution.meanprob.KernelDistribution.mean?VC Cprob.KernelDistribution.iqrprob.KernelDistribution.iqrAUE Eprob.KernelDistribution.icdfprob.KernelDistribution.icdf?TC Cprob.KernelDistribution.cdfprob.KernelDistribution.cdfKSO Oprob.KernelDistribution.InputDataprob.KernelDistribution.InputDataORS Sprob.KernelDistribution.IsTruncatedprob.KernelDistribution.IsTruncatedMQQ Qprob.KernelDistribution.Truncationprob.KernelDistribution.TruncationGPK Kprob.KernelDistribution.Supportprob.KernelDistribution.SupportKOO Oprob.KernelDistribution.Bandwidthprob.KernelDistribution.BandwidthENI Iprob.KernelDistribution.Kernelprob.KernelDistribution.KernelYM] ]prob.KernelDistribution.DistributionNameprob.KernelDistribution.DistributionName7L; ;prob.KernelDistributionprob.KernelDistributionQKU Uprob.InverseGaussianDistribution.varprob.InverseGaussianDistribution.var[J_ _prob.InverseGaussianDistribution.truncateprob.InverseGaussianDistribution.truncateQIU Uprob.InverseGaussianDistribution.stdprob.InverseGaussianDistribution.stdWH[ [prob.InverseGaussianDistribution.randomprob.InverseGaussianDistribution.randomYG] ]prob.InverseGaussianDistribution.proflikprob.InverseGaussianDistribution.proflikSFW Wprob.InverseGaussianDistribution.plotprob.InverseGaussianDistribution.plotQEU Uprob.InverseGaussianDistribution.pdfprob.InverseGaussianDistribution.pdfYD] ]prob.InverseGaussianDistribution.paramciprob.InverseGaussianDistribution.paramci]Ca aprob.InverseGaussianDistribution.negloglikprob.InverseGaussianDistribution.negloglikWB[ [prob.InverseGaussianDistribution.medianprob.InverseGaussianDistribution.medianSAW Wprob.InverseGaussianDistribution.meanprob.InverseGaussianDistribution.meanQ@U Uprob.InverseGaussianDistribution.iqrprob.InverseGaussianDistribution.iqrS?W Wprob.InverseGaussianDistribution.icdfprob.InverseGaussianDistribution.icdfQ>U Uprob.InverseGaussianDistribution.cdfprob.InverseGaussianDistribution.cdf]=a aprob.InverseGaussianDistribution.InputDataprob.InverseGaussianDistribution.InputDatak<o oprob.InverseGaussianDistribution.ParameterIsFixedprob.InverseGaussianDistribution.ParameterIsFixedq;u uprob.InverseGaussianDistribution.ParameterCovarianceprob.InverseGaussianDistribution.ParameterCovariancea:e eprob.InverseGaussianDistribution.IsTruncatedprob.InverseGaussianDistribution.IsTruncated_9c cprob.InverseGaussianDistribution.Truncationprob.InverseGaussianDistribution.Truncationi8m mprob.InverseGaussianDistribution.ParameterValuesprob.InverseGaussianDistribution.ParameterValuess7w wprob.InverseGaussianDistribution.ParameterDescriptionprob.InverseGaussianDistribution.ParameterDescriptiong6k kprob.InverseGaussianDistribution.ParameterNamesprob.InverseGaussianDistribution.ParameterNamese5i iprob.InverseGaussianDistribution.NumParametersprob.InverseGaussianDistribution.NumParametersk4o oprob.InverseGaussianDistribution.DistributionNameprob.InverseGaussianDistribution.DistributionNameW3[ [prob.InverseGaussianDistribution.lambdaprob.InverseGaussianDistribution.lambda /V8 0 j  D ^  4R ~.h4~Tn v"KO Oprob.LoglogisticDistribution.plotprob.LoglogisticDistribution.plotIM Mprob.LoglogisticDistribution.pdfprob.LoglogisticDistribution.pdfQU Uprob.LoglogisticDistribution.paramciprob.LoglogisticDistribution.paramciU Y Yprob.LoglogisticDistribution.negloglikprob.LoglogisticDistribution.negloglikO S Sprob.LoglogisticDistribution.medianprob.LoglogisticDistribution.medianK O Oprob.LoglogisticDistribution.meanprob.LoglogisticDistribution.meanI M Mprob.LoglogisticDistribution.iqrprob.LoglogisticDistribution.iqrK O Oprob.LoglogisticDistribution.icdfprob.LoglogisticDistribution.icdfIM Mprob.LoglogisticDistribution.cdfprob.LoglogisticDistribution.cdfUY Yprob.LoglogisticDistribution.InputDataprob.LoglogisticDistribution.InputDatacg gprob.LoglogisticDistribution.ParameterIsFixedprob.LoglogisticDistribution.ParameterIsFixedim mprob.LoglogisticDistribution.ParameterCovarianceprob.LoglogisticDistribution.ParameterCovarianceY] ]prob.LoglogisticDistribution.IsTruncatedprob.LoglogisticDistribution.IsTruncatedW[ [prob.LoglogisticDistribution.Truncationprob.LoglogisticDistribution.Truncationae eprob.LoglogisticDistribution.ParameterValuesprob.LoglogisticDistribution.ParameterValuesko oprob.LoglogisticDistribution.ParameterDescriptionprob.LoglogisticDistribution.ParameterDescription_c cprob.LoglogisticDistribution.ParameterNamesprob.LoglogisticDistribution.ParameterNames]a aprob.LoglogisticDistribution.NumParametersprob.LoglogisticDistribution.NumParametersc~g gprob.LoglogisticDistribution.DistributionNameprob.LoglogisticDistribution.DistributionNameM}Q Qprob.LoglogisticDistribution.sigmaprob.LoglogisticDistribution.sigmaG|K Kprob.LoglogisticDistribution.muprob.LoglogisticDistribution.muA{E Eprob.LoglogisticDistributionprob.LoglogisticDistributionCzG Gprob.LogisticDistribution.varprob.LogisticDistribution.varMyQ Qprob.LogisticDistribution.truncateprob.LogisticDistribution.truncateCxG Gprob.LogisticDistribution.stdprob.LogisticDistribution.stdIwM Mprob.LogisticDistribution.randomprob.LogisticDistribution.randomKvO Oprob.LogisticDistribution.proflikprob.LogisticDistribution.proflikEuI Iprob.LogisticDistribution.plotprob.LogisticDistribution.plotCtG Gprob.LogisticDistribution.pdfprob.LogisticDistribution.pdfKsO Oprob.LogisticDistribution.paramciprob.LogisticDistribution.paramciOrS Sprob.LogisticDistribution.negloglikprob.LogisticDistribution.negloglikIqM Mprob.LogisticDistribution.medianprob.LogisticDistribution.medianEpI Iprob.LogisticDistribution.meanprob.LogisticDistribution.meanCoG Gprob.LogisticDistribution.iqrprob.LogisticDistribution.iqrEnI Iprob.LogisticDistribution.icdfprob.LogisticDistribution.icdfCmG Gprob.LogisticDistribution.cdfprob.LogisticDistribution.cdfOlS Sprob.LogisticDistribution.InputDataprob.LogisticDistribution.InputData]ka aprob.LogisticDistribution.ParameterIsFixedprob.LogisticDistribution.ParameterIsFixedcjg gprob.LogisticDistribution.ParameterCovarianceprob.LogisticDistribution.ParameterCovarianceSiW Wprob.LogisticDistribution.IsTruncatedprob.LogisticDistribution.IsTruncatedQhU Uprob.LogisticDistribution.Truncationprob.LogisticDistribution.Truncation[g_ _prob.LogisticDistribution.ParameterValuesprob.LogisticDistribution.ParameterValuesefi iprob.LogisticDistribution.ParameterDescriptionprob.LogisticDistribution.ParameterDescriptionYe] ]prob.LogisticDistribution.ParameterNamesprob.LogisticDistribution.ParameterNamesWd[ [prob.LogisticDistribution.NumParametersprob.LogisticDistribution.NumParameters]ca aprob.LogisticDistribution.DistributionNameprob.LogisticDistribution.DistributionNameGbK Kprob.LogisticDistribution.sigmaprob.LogisticDistribution.sigma 0zZl, 8 ~  ^  < V v"@Z~0~ T@`zG@K Kprob.LoguniformDistribution.pdfprob.LoguniformDistribution.pdfM?Q Qprob.LoguniformDistribution.medianprob.LoguniformDistribution.medianI>M Mprob.LoguniformDistribution.meanprob.LoguniformDistribution.meanG=K Kprob.LoguniformDistribution.iqrprob.LoguniformDistribution.iqrI<M Mprob.LoguniformDistribution.icdfprob.LoguniformDistribution.icdfG;K Kprob.LoguniformDistribution.cdfprob.LoguniformDistribution.cdfW:[ [prob.LoguniformDistribution.IsTruncatedprob.LoguniformDistribution.IsTruncatedU9Y Yprob.LoguniformDistribution.Truncationprob.LoguniformDistribution.Truncation_8c cprob.LoguniformDistribution.ParameterValuesprob.LoguniformDistribution.ParameterValuesi7m mprob.LoguniformDistribution.ParameterDescriptionprob.LoguniformDistribution.ParameterDescription]6a aprob.LoguniformDistribution.ParameterNamesprob.LoguniformDistribution.ParameterNames[5_ _prob.LoguniformDistribution.NumParametersprob.LoguniformDistribution.NumParametersa4e eprob.LoguniformDistribution.DistributionNameprob.LoguniformDistribution.DistributionNameK3O Oprob.LoguniformDistribution.Upperprob.LoguniformDistribution.UpperK2O Oprob.LoguniformDistribution.Lowerprob.LoguniformDistribution.Lower?1C Cprob.LoguniformDistributionprob.LoguniformDistributionE0I Iprob.LognormalDistribution.varprob.LognormalDistribution.varO/S Sprob.LognormalDistribution.truncateprob.LognormalDistribution.truncateE.I Iprob.LognormalDistribution.stdprob.LognormalDistribution.stdK-O Oprob.LognormalDistribution.randomprob.LognormalDistribution.randomM,Q Qprob.LognormalDistribution.proflikprob.LognormalDistribution.proflikG+K Kprob.LognormalDistribution.plotprob.LognormalDistribution.plotE*I Iprob.LognormalDistribution.pdfprob.LognormalDistribution.pdfM)Q Qprob.LognormalDistribution.paramciprob.LognormalDistribution.paramciQ(U Uprob.LognormalDistribution.negloglikprob.LognormalDistribution.negloglikK'O Oprob.LognormalDistribution.medianprob.LognormalDistribution.medianG&K Kprob.LognormalDistribution.meanprob.LognormalDistribution.meanE%I Iprob.LognormalDistribution.iqrprob.LognormalDistribution.iqrG$K Kprob.LognormalDistribution.icdfprob.LognormalDistribution.icdfE#I Iprob.LognormalDistribution.cdfprob.LognormalDistribution.cdfQ"U Uprob.LognormalDistribution.InputDataprob.LognormalDistribution.InputData_!c cprob.LognormalDistribution.ParameterIsFixedprob.LognormalDistribution.ParameterIsFixede i iprob.LognormalDistribution.ParameterCovarianceprob.LognormalDistribution.ParameterCovarianceUY Yprob.LognormalDistribution.IsTruncatedprob.LognormalDistribution.IsTruncatedSW Wprob.LognormalDistribution.Truncationprob.LognormalDistribution.Truncation]a aprob.LognormalDistribution.ParameterValuesprob.LognormalDistribution.ParameterValuesgk kprob.LognormalDistribution.ParameterDescriptionprob.LognormalDistribution.ParameterDescription[_ _prob.LognormalDistribution.ParameterNamesprob.LognormalDistribution.ParameterNamesY] ]prob.LognormalDistribution.NumParametersprob.LognormalDistribution.NumParameters_c cprob.LognormalDistribution.DistributionNameprob.LognormalDistribution.DistributionNameIM Mprob.LognormalDistribution.sigmaprob.LognormalDistribution.sigmaCG Gprob.LognormalDistribution.muprob.LognormalDistribution.mu=A Aprob.LognormalDistributionprob.LognormalDistributionIM Mprob.LoglogisticDistribution.varprob.LoglogisticDistribution.varSW Wprob.LoglogisticDistribution.truncateprob.LoglogisticDistribution.truncateIM Mprob.LoglogisticDistribution.stdprob.LoglogisticDistribution.stdOS Sprob.LoglogisticDistribution.randomprob.LoglogisticDistribution.randomQU Uprob.LoglogisticDistribution.proflikprob.LoglogisticDistribution.proflik 0zd|8 r  B ( B V jFB~ v^BVzKpO Oprob.NakagamiDistribution.proflikprob.NakagamiDistribution.proflikEoI Iprob.NakagamiDistribution.plotprob.NakagamiDistribution.plotCnG Gprob.NakagamiDistribution.pdfprob.NakagamiDistribution.pdfKmO Oprob.NakagamiDistribution.paramciprob.NakagamiDistribution.paramciOlS Sprob.NakagamiDistribution.negloglikprob.NakagamiDistribution.negloglikIkM Mprob.NakagamiDistribution.medianprob.NakagamiDistribution.medianEjI Iprob.NakagamiDistribution.meanprob.NakagamiDistribution.meanCiG Gprob.NakagamiDistribution.iqrprob.NakagamiDistribution.iqrEhI Iprob.NakagamiDistribution.icdfprob.NakagamiDistribution.icdfCgG Gprob.NakagamiDistribution.cdfprob.NakagamiDistribution.cdfOfS Sprob.NakagamiDistribution.InputDataprob.NakagamiDistribution.InputData]ea aprob.NakagamiDistribution.ParameterIsFixedprob.NakagamiDistribution.ParameterIsFixedcdg gprob.NakagamiDistribution.ParameterCovarianceprob.NakagamiDistribution.ParameterCovarianceScW Wprob.NakagamiDistribution.IsTruncatedprob.NakagamiDistribution.IsTruncatedQbU Uprob.NakagamiDistribution.Truncationprob.NakagamiDistribution.Truncation[a_ _prob.NakagamiDistribution.ParameterValuesprob.NakagamiDistribution.ParameterValuese`i iprob.NakagamiDistribution.ParameterDescriptionprob.NakagamiDistribution.ParameterDescriptionY_] ]prob.NakagamiDistribution.ParameterNamesprob.NakagamiDistribution.ParameterNamesW^[ [prob.NakagamiDistribution.NumParametersprob.NakagamiDistribution.NumParameters]]a aprob.NakagamiDistribution.DistributionNameprob.NakagamiDistribution.DistributionNameG\K Kprob.NakagamiDistribution.omegaprob.NakagamiDistribution.omegaA[E Eprob.NakagamiDistribution.muprob.NakagamiDistribution.mu;Z? ?prob.NakagamiDistributionprob.NakagamiDistributionIYM Mprob.MultinomialDistribution.varprob.MultinomialDistribution.varSXW Wprob.MultinomialDistribution.truncateprob.MultinomialDistribution.truncateIWM Mprob.MultinomialDistribution.stdprob.MultinomialDistribution.stdOVS Sprob.MultinomialDistribution.randomprob.MultinomialDistribution.randomKUO Oprob.MultinomialDistribution.plotprob.MultinomialDistribution.plotITM Mprob.MultinomialDistribution.pdfprob.MultinomialDistribution.pdfOSS Sprob.MultinomialDistribution.medianprob.MultinomialDistribution.medianKRO Oprob.MultinomialDistribution.meanprob.MultinomialDistribution.meanIQM Mprob.MultinomialDistribution.iqrprob.MultinomialDistribution.iqrKPO Oprob.MultinomialDistribution.icdfprob.MultinomialDistribution.icdfIOM Mprob.MultinomialDistribution.cdfprob.MultinomialDistribution.cdfYN] ]prob.MultinomialDistribution.IsTruncatedprob.MultinomialDistribution.IsTruncatedWM[ [prob.MultinomialDistribution.Truncationprob.MultinomialDistribution.TruncationaLe eprob.MultinomialDistribution.ParameterValuesprob.MultinomialDistribution.ParameterValueskKo oprob.MultinomialDistribution.ParameterDescriptionprob.MultinomialDistribution.ParameterDescription_Jc cprob.MultinomialDistribution.ParameterNamesprob.MultinomialDistribution.ParameterNames]Ia aprob.MultinomialDistribution.NumParametersprob.MultinomialDistribution.NumParameterscHg gprob.MultinomialDistribution.DistributionNameprob.MultinomialDistribution.DistributionName]Ga aprob.MultinomialDistribution.Probabilitiesprob.MultinomialDistribution.ProbabilitiesAFE Eprob.MultinomialDistributionprob.MultinomialDistributionGEK Kprob.LoguniformDistribution.varprob.LoguniformDistribution.varQDU Uprob.LoguniformDistribution.truncateprob.LoguniformDistribution.truncateGCK Kprob.LoguniformDistribution.stdprob.LoguniformDistribution.stdMBQ Qprob.LoguniformDistribution.randomprob.LoguniformDistribution.randomIAM Mprob.LoguniformDistribution.plotprob.LoguniformDistribution.plot ,n8 v ( V z RL0$rBJ40KO Oprob.NormalDistribution.InputDataprob.NormalDistribution.InputDataY] ]prob.NormalDistribution.ParameterIsFixedprob.NormalDistribution.ParameterIsFixed_c cprob.NormalDistribution.ParameterCovarianceprob.NormalDistribution.ParameterCovarianceOS Sprob.NormalDistribution.IsTruncatedprob.NormalDistribution.IsTruncatedMQ Qprob.NormalDistribution.Truncationprob.NormalDistribution.TruncationW[ [prob.NormalDistribution.ParameterValuesprob.NormalDistribution.ParameterValuesae eprob.NormalDistribution.ParameterDescriptionprob.NormalDistribution.ParameterDescriptionUY Yprob.NormalDistribution.ParameterNamesprob.NormalDistribution.ParameterNamesSW Wprob.NormalDistribution.NumParametersprob.NormalDistribution.NumParametersY] ]prob.NormalDistribution.DistributionNameprob.NormalDistribution.DistributionNameCG Gprob.NormalDistribution.sigmaprob.NormalDistribution.sigma=A Aprob.NormalDistribution.muprob.NormalDistribution.mu7; ;prob.NormalDistributionprob.NormalDistributionSW Wprob.NegativeBinomialDistribution.varprob.NegativeBinomialDistribution.var]a aprob.NegativeBinomialDistribution.truncateprob.NegativeBinomialDistribution.truncateS W Wprob.NegativeBinomialDistribution.stdprob.NegativeBinomialDistribution.stdY ] ]prob.NegativeBinomialDistribution.randomprob.NegativeBinomialDistribution.random[ _ _prob.NegativeBinomialDistribution.proflikprob.NegativeBinomialDistribution.proflikU Y Yprob.NegativeBinomialDistribution.plotprob.NegativeBinomialDistribution.plotS W Wprob.NegativeBinomialDistribution.pdfprob.NegativeBinomialDistribution.pdf[_ _prob.NegativeBinomialDistribution.paramciprob.NegativeBinomialDistribution.paramci_c cprob.NegativeBinomialDistribution.negloglikprob.NegativeBinomialDistribution.negloglikY] ]prob.NegativeBinomialDistribution.medianprob.NegativeBinomialDistribution.medianUY Yprob.NegativeBinomialDistribution.meanprob.NegativeBinomialDistribution.meanSW Wprob.NegativeBinomialDistribution.iqrprob.NegativeBinomialDistribution.iqrUY Yprob.NegativeBinomialDistribution.icdfprob.NegativeBinomialDistribution.icdfSW Wprob.NegativeBinomialDistribution.cdfprob.NegativeBinomialDistribution.cdf_c cprob.NegativeBinomialDistribution.InputDataprob.NegativeBinomialDistribution.InputDatamq qprob.NegativeBinomialDistribution.ParameterIsFixedprob.NegativeBinomialDistribution.ParameterIsFixedsw wprob.NegativeBinomialDistribution.ParameterCovarianceprob.NegativeBinomialDistribution.ParameterCovariancec~g gprob.NegativeBinomialDistribution.IsTruncatedprob.NegativeBinomialDistribution.IsTruncateda}e eprob.NegativeBinomialDistribution.Truncationprob.NegativeBinomialDistribution.Truncationk|o oprob.NegativeBinomialDistribution.ParameterValuesprob.NegativeBinomialDistribution.ParameterValuesu{y yprob.NegativeBinomialDistribution.ParameterDescriptionprob.NegativeBinomialDistribution.ParameterDescriptionizm mprob.NegativeBinomialDistribution.ParameterNamesprob.NegativeBinomialDistribution.ParameterNamesgyk kprob.NegativeBinomialDistribution.NumParametersprob.NegativeBinomialDistribution.NumParametersmxq qprob.NegativeBinomialDistribution.DistributionNameprob.NegativeBinomialDistribution.DistributionNameOwS Sprob.NegativeBinomialDistribution.Pprob.NegativeBinomialDistribution.POvS Sprob.NegativeBinomialDistribution.Rprob.NegativeBinomialDistribution.RKuO Oprob.NegativeBinomialDistributionprob.NegativeBinomialDistributionCtG Gprob.NakagamiDistribution.varprob.NakagamiDistribution.varMsQ Qprob.NakagamiDistribution.truncateprob.NakagamiDistribution.truncateCrG Gprob.NakagamiDistribution.stdprob.NakagamiDistribution.stdIqM Mprob.NakagamiDistribution.randomprob.NakagamiDistribution.random /z8^ D n , > h VXTPbbF<MKQ Qprob.PoissonDistribution.InputDataprob.PoissonDistribution.InputData[J_ _prob.PoissonDistribution.ParameterIsFixedprob.PoissonDistribution.ParameterIsFixedaIe eprob.PoissonDistribution.ParameterCovarianceprob.PoissonDistribution.ParameterCovarianceQHU Uprob.PoissonDistribution.IsTruncatedprob.PoissonDistribution.IsTruncatedOGS Sprob.PoissonDistribution.Truncationprob.PoissonDistribution.TruncationYF] ]prob.PoissonDistribution.ParameterValuesprob.PoissonDistribution.ParameterValuescEg gprob.PoissonDistribution.ParameterDescriptionprob.PoissonDistribution.ParameterDescriptionWD[ [prob.PoissonDistribution.ParameterNamesprob.PoissonDistribution.ParameterNamesUCY Yprob.PoissonDistribution.NumParametersprob.PoissonDistribution.NumParameters[B_ _prob.PoissonDistribution.DistributionNameprob.PoissonDistribution.DistributionNameGAK Kprob.PoissonDistribution.lambdaprob.PoissonDistribution.lambda9@= =prob.PoissonDistributionprob.PoissonDistributionQ?U Uprob.PiecewiseLinearDistribution.varprob.PiecewiseLinearDistribution.var[>_ _prob.PiecewiseLinearDistribution.truncateprob.PiecewiseLinearDistribution.truncateQ=U Uprob.PiecewiseLinearDistribution.stdprob.PiecewiseLinearDistribution.stdW<[ [prob.PiecewiseLinearDistribution.randomprob.PiecewiseLinearDistribution.randomS;W Wprob.PiecewiseLinearDistribution.plotprob.PiecewiseLinearDistribution.plotQ:U Uprob.PiecewiseLinearDistribution.pdfprob.PiecewiseLinearDistribution.pdfW9[ [prob.PiecewiseLinearDistribution.medianprob.PiecewiseLinearDistribution.medianS8W Wprob.PiecewiseLinearDistribution.meanprob.PiecewiseLinearDistribution.meanQ7U Uprob.PiecewiseLinearDistribution.iqrprob.PiecewiseLinearDistribution.iqrS6W Wprob.PiecewiseLinearDistribution.icdfprob.PiecewiseLinearDistribution.icdfQ5U Uprob.PiecewiseLinearDistribution.cdfprob.PiecewiseLinearDistribution.cdfa4e eprob.PiecewiseLinearDistribution.IsTruncatedprob.PiecewiseLinearDistribution.IsTruncated_3c cprob.PiecewiseLinearDistribution.Truncationprob.PiecewiseLinearDistribution.Truncationi2m mprob.PiecewiseLinearDistribution.ParameterValuesprob.PiecewiseLinearDistribution.ParameterValuess1w wprob.PiecewiseLinearDistribution.ParameterDescriptionprob.PiecewiseLinearDistribution.ParameterDescriptiong0k kprob.PiecewiseLinearDistribution.ParameterNamesprob.PiecewiseLinearDistribution.ParameterNamese/i iprob.PiecewiseLinearDistribution.NumParametersprob.PiecewiseLinearDistribution.NumParametersk.o oprob.PiecewiseLinearDistribution.DistributionNameprob.PiecewiseLinearDistribution.DistributionNameO-S Sprob.PiecewiseLinearDistribution.Fxprob.PiecewiseLinearDistribution.FxM,Q Qprob.PiecewiseLinearDistribution.xprob.PiecewiseLinearDistribution.xI+M Mprob.PiecewiseLinearDistributionprob.PiecewiseLinearDistribution?*C Cprob.NormalDistribution.varprob.NormalDistribution.varI)M Mprob.NormalDistribution.truncateprob.NormalDistribution.truncate?(C Cprob.NormalDistribution.stdprob.NormalDistribution.stdE'I Iprob.NormalDistribution.randomprob.NormalDistribution.randomG&K Kprob.NormalDistribution.proflikprob.NormalDistribution.proflikA%E Eprob.NormalDistribution.plotprob.NormalDistribution.plot?$C Cprob.NormalDistribution.pdfprob.NormalDistribution.pdfG#K Kprob.NormalDistribution.paramciprob.NormalDistribution.paramciK"O Oprob.NormalDistribution.negloglikprob.NormalDistribution.negloglikE!I Iprob.NormalDistribution.medianprob.NormalDistribution.medianA E Eprob.NormalDistribution.meanprob.NormalDistribution.mean?C Cprob.NormalDistribution.iqrprob.NormalDistribution.iqrAE Eprob.NormalDistribution.icdfprob.NormalDistribution.icdf?C Cprob.NormalDistribution.cdfprob.NormalDistribution.cdf 2v2R | 0 T  0 z  ` Dd8\|,n(v`O}S Sprob.RicianDistribution.IsTruncatedprob.RicianDistribution.IsTruncatedM|Q Qprob.RicianDistribution.Truncationprob.RicianDistribution.TruncationW{[ [prob.RicianDistribution.ParameterValuesprob.RicianDistribution.ParameterValuesaze eprob.RicianDistribution.ParameterDescriptionprob.RicianDistribution.ParameterDescriptionUyY Yprob.RicianDistribution.ParameterNamesprob.RicianDistribution.ParameterNamesSxW Wprob.RicianDistribution.NumParametersprob.RicianDistribution.NumParametersYw] ]prob.RicianDistribution.DistributionNameprob.RicianDistribution.DistributionNameCvG Gprob.RicianDistribution.sigmaprob.RicianDistribution.sigma;u? ?prob.RicianDistribution.sprob.RicianDistribution.s7t; ;prob.RicianDistributionprob.RicianDistributionCsG Gprob.RayleighDistribution.varprob.RayleighDistribution.varMrQ Qprob.RayleighDistribution.truncateprob.RayleighDistribution.truncateCqG Gprob.RayleighDistribution.stdprob.RayleighDistribution.stdIpM Mprob.RayleighDistribution.randomprob.RayleighDistribution.randomKoO Oprob.RayleighDistribution.proflikprob.RayleighDistribution.proflikEnI Iprob.RayleighDistribution.plotprob.RayleighDistribution.plotCmG Gprob.RayleighDistribution.pdfprob.RayleighDistribution.pdfKlO Oprob.RayleighDistribution.paramciprob.RayleighDistribution.paramciOkS Sprob.RayleighDistribution.negloglikprob.RayleighDistribution.negloglikIjM Mprob.RayleighDistribution.medianprob.RayleighDistribution.medianEiI Iprob.RayleighDistribution.meanprob.RayleighDistribution.meanChG Gprob.RayleighDistribution.iqrprob.RayleighDistribution.iqrEgI Iprob.RayleighDistribution.icdfprob.RayleighDistribution.icdfCfG Gprob.RayleighDistribution.cdfprob.RayleighDistribution.cdfOeS Sprob.RayleighDistribution.InputDataprob.RayleighDistribution.InputData]da aprob.RayleighDistribution.ParameterIsFixedprob.RayleighDistribution.ParameterIsFixedccg gprob.RayleighDistribution.ParameterCovarianceprob.RayleighDistribution.ParameterCovarianceSbW Wprob.RayleighDistribution.IsTruncatedprob.RayleighDistribution.IsTruncatedQaU Uprob.RayleighDistribution.Truncationprob.RayleighDistribution.Truncation[`_ _prob.RayleighDistribution.ParameterValuesprob.RayleighDistribution.ParameterValuese_i iprob.RayleighDistribution.ParameterDescriptionprob.RayleighDistribution.ParameterDescriptionY^] ]prob.RayleighDistribution.ParameterNamesprob.RayleighDistribution.ParameterNamesW][ [prob.RayleighDistribution.NumParametersprob.RayleighDistribution.NumParameters]\a aprob.RayleighDistribution.DistributionNameprob.RayleighDistribution.DistributionName?[C Cprob.RayleighDistribution.Bprob.RayleighDistribution.B;Z? ?prob.RayleighDistributionprob.RayleighDistributionAYE Eprob.PoissonDistribution.varprob.PoissonDistribution.varKXO Oprob.PoissonDistribution.truncateprob.PoissonDistribution.truncateAWE Eprob.PoissonDistribution.stdprob.PoissonDistribution.stdGVK Kprob.PoissonDistribution.randomprob.PoissonDistribution.randomIUM Mprob.PoissonDistribution.proflikprob.PoissonDistribution.proflikCTG Gprob.PoissonDistribution.plotprob.PoissonDistribution.plotASE Eprob.PoissonDistribution.pdfprob.PoissonDistribution.pdfIRM Mprob.PoissonDistribution.paramciprob.PoissonDistribution.paramciMQQ Qprob.PoissonDistribution.negloglikprob.PoissonDistribution.negloglikGPK Kprob.PoissonDistribution.medianprob.PoissonDistribution.medianCOG Gprob.PoissonDistribution.meanprob.PoissonDistribution.meanANE Eprob.PoissonDistribution.iqrprob.PoissonDistribution.iqrCMG Gprob.PoissonDistribution.icdfprob.PoissonDistribution.icdfALE Eprob.PoissonDistribution.cdfprob.PoissonDistribution.cdf 4xBn, R  8 b \  x "f j^R r0Z@Jxc1g gprob.tLocationScaleDistribution.NumParameters prob.tLocationScaleDistribution.NumParametersi0m mprob.tLocationScaleDistribution.DistributionName prob.tLocationScaleDistribution.DistributionNameM/Q Qprob.tLocationScaleDistribution.nu prob.tLocationScaleDistribution.nuS.W Wprob.tLocationScaleDistribution.sigma prob.tLocationScaleDistribution.sigmaM-Q Qprob.tLocationScaleDistribution.mu prob.tLocationScaleDistribution.muG,K Kprob.tLocationScaleDistribution prob.tLocationScaleDistribution?+C Cprob.StableDistribution.var prob.StableDistribution.varI*M Mprob.StableDistribution.truncate prob.StableDistribution.truncate?)C Cprob.StableDistribution.std prob.StableDistribution.stdE(I Iprob.StableDistribution.random prob.StableDistribution.randomG'K Kprob.StableDistribution.proflik prob.StableDistribution.proflikA&E Eprob.StableDistribution.plot prob.StableDistribution.plot?%C Cprob.StableDistribution.pdf prob.StableDistribution.pdfG$K Kprob.StableDistribution.paramci prob.StableDistribution.paramciK#O Oprob.StableDistribution.negloglik prob.StableDistribution.negloglikE"I Iprob.StableDistribution.median prob.StableDistribution.medianA!E Eprob.StableDistribution.mean prob.StableDistribution.mean? C Cprob.StableDistribution.iqr prob.StableDistribution.iqrAE Eprob.StableDistribution.icdf prob.StableDistribution.icdf?C Cprob.StableDistribution.cdf prob.StableDistribution.cdfKO Oprob.StableDistribution.InputData prob.StableDistribution.InputDataY] ]prob.StableDistribution.ParameterIsFixed prob.StableDistribution.ParameterIsFixed_c cprob.StableDistribution.ParameterCovariance prob.StableDistribution.ParameterCovarianceOS Sprob.StableDistribution.IsTruncated prob.StableDistribution.IsTruncatedMQ Qprob.StableDistribution.Truncation prob.StableDistribution.TruncationW[ [prob.StableDistribution.ParameterValues prob.StableDistribution.ParameterValuesae eprob.StableDistribution.ParameterDescription prob.StableDistribution.ParameterDescriptionUY Yprob.StableDistribution.ParameterNames prob.StableDistribution.ParameterNamesSW Wprob.StableDistribution.NumParameters prob.StableDistribution.NumParametersY] ]prob.StableDistribution.DistributionName prob.StableDistribution.DistributionNameCG Gprob.StableDistribution.delta prob.StableDistribution.delta?C Cprob.StableDistribution.gam prob.StableDistribution.gamAE Eprob.StableDistribution.beta prob.StableDistribution.betaCG Gprob.StableDistribution.alpha prob.StableDistribution.alpha7; ;prob.StableDistribution prob.StableDistribution?C Cprob.RicianDistribution.varprob.RicianDistribution.varI M Mprob.RicianDistribution.truncateprob.RicianDistribution.truncate? C Cprob.RicianDistribution.stdprob.RicianDistribution.stdE I Iprob.RicianDistribution.randomprob.RicianDistribution.randomG K Kprob.RicianDistribution.proflikprob.RicianDistribution.proflikA E Eprob.RicianDistribution.plotprob.RicianDistribution.plot?C Cprob.RicianDistribution.pdfprob.RicianDistribution.pdfGK Kprob.RicianDistribution.paramciprob.RicianDistribution.paramciKO Oprob.RicianDistribution.negloglikprob.RicianDistribution.negloglikEI Iprob.RicianDistribution.medianprob.RicianDistribution.medianAE Eprob.RicianDistribution.meanprob.RicianDistribution.mean?C Cprob.RicianDistribution.iqrprob.RicianDistribution.iqrAE Eprob.RicianDistribution.icdfprob.RicianDistribution.icdf?C Cprob.RicianDistribution.cdfprob.RicianDistribution.cdfKO Oprob.RicianDistribution.InputDataprob.RicianDistribution.InputDataY] ]prob.RicianDistribution.ParameterIsFixedprob.RicianDistribution.ParameterIsFixed_~c cprob.RicianDistribution.ParameterCovarianceprob.RicianDistribution.ParameterCovariance .$Z  j  p  `  ` Z:2fRr&@RE_I Iprob.UniformDistribution.Lowerprob.UniformDistribution.Lower9^= =prob.UniformDistributionprob.UniformDistributionG]K Kprob.TriangularDistribution.varprob.TriangularDistribution.varQ\U Uprob.TriangularDistribution.truncateprob.TriangularDistribution.truncateG[K Kprob.TriangularDistribution.stdprob.TriangularDistribution.stdMZQ Qprob.TriangularDistribution.randomprob.TriangularDistribution.randomIYM Mprob.TriangularDistribution.plotprob.TriangularDistribution.plotGXK Kprob.TriangularDistribution.pdfprob.TriangularDistribution.pdfMWQ Qprob.TriangularDistribution.medianprob.TriangularDistribution.medianIVM Mprob.TriangularDistribution.meanprob.TriangularDistribution.meanGUK Kprob.TriangularDistribution.iqrprob.TriangularDistribution.iqrITM Mprob.TriangularDistribution.icdfprob.TriangularDistribution.icdfGSK Kprob.TriangularDistribution.cdfprob.TriangularDistribution.cdfWR[ [prob.TriangularDistribution.IsTruncatedprob.TriangularDistribution.IsTruncatedUQY Yprob.TriangularDistribution.Truncationprob.TriangularDistribution.Truncation_Pc cprob.TriangularDistribution.ParameterValuesprob.TriangularDistribution.ParameterValuesiOm mprob.TriangularDistribution.ParameterDescriptionprob.TriangularDistribution.ParameterDescription]Na aprob.TriangularDistribution.ParameterNamesprob.TriangularDistribution.ParameterNames[M_ _prob.TriangularDistribution.NumParametersprob.TriangularDistribution.NumParametersaLe eprob.TriangularDistribution.DistributionNameprob.TriangularDistribution.DistributionNameCKG Gprob.TriangularDistribution.Cprob.TriangularDistribution.CCJG Gprob.TriangularDistribution.Bprob.TriangularDistribution.BCIG Gprob.TriangularDistribution.Aprob.TriangularDistribution.A?HC Cprob.TriangularDistributionprob.TriangularDistributionOGS Sprob.tLocationScaleDistribution.varprob.tLocationScaleDistribution.varYF] ]prob.tLocationScaleDistribution.truncateprob.tLocationScaleDistribution.truncateOES Sprob.tLocationScaleDistribution.stdprob.tLocationScaleDistribution.stdUDY Yprob.tLocationScaleDistribution.randomprob.tLocationScaleDistribution.randomWC[ [prob.tLocationScaleDistribution.proflikprob.tLocationScaleDistribution.proflikQBU Uprob.tLocationScaleDistribution.plotprob.tLocationScaleDistribution.plotOAS Sprob.tLocationScaleDistribution.pdfprob.tLocationScaleDistribution.pdfW@[ [prob.tLocationScaleDistribution.paramciprob.tLocationScaleDistribution.paramci[?_ _prob.tLocationScaleDistribution.negloglikprob.tLocationScaleDistribution.negloglikU>Y Yprob.tLocationScaleDistribution.medianprob.tLocationScaleDistribution.medianQ=U Uprob.tLocationScaleDistribution.meanprob.tLocationScaleDistribution.meanO<S Sprob.tLocationScaleDistribution.iqrprob.tLocationScaleDistribution.iqrQ;U Uprob.tLocationScaleDistribution.icdfprob.tLocationScaleDistribution.icdfO:S Sprob.tLocationScaleDistribution.cdfprob.tLocationScaleDistribution.cdf[9_ _prob.tLocationScaleDistribution.InputData prob.tLocationScaleDistribution.InputDatai8m mprob.tLocationScaleDistribution.ParameterIsFixed prob.tLocationScaleDistribution.ParameterIsFixedo7s sprob.tLocationScaleDistribution.ParameterCovariance prob.tLocationScaleDistribution.ParameterCovariance_6c cprob.tLocationScaleDistribution.IsTruncated prob.tLocationScaleDistribution.IsTruncated]5a aprob.tLocationScaleDistribution.Truncation prob.tLocationScaleDistribution.Truncationg4k kprob.tLocationScaleDistribution.ParameterValues prob.tLocationScaleDistribution.ParameterValuesq3u uprob.tLocationScaleDistribution.ParameterDescription prob.tLocationScaleDistribution.ParameterDescriptione2i iprob.tLocationScaleDistribution.ParameterNames prob.tLocationScaleDistribution.ParameterNames .ZB @ r , X  | 8 | lXBh$Dn"FA E Eprob.WeibullDistribution.varprob.WeibullDistribution.varK O Oprob.WeibullDistribution.truncateprob.WeibullDistribution.truncateA E Eprob.WeibullDistribution.stdprob.WeibullDistribution.stdG K Kprob.WeibullDistribution.randomprob.WeibullDistribution.randomI M Mprob.WeibullDistribution.proflikprob.WeibullDistribution.proflikCG Gprob.WeibullDistribution.plotprob.WeibullDistribution.plotAE Eprob.WeibullDistribution.pdfprob.WeibullDistribution.pdfIM Mprob.WeibullDistribution.paramciprob.WeibullDistribution.paramciMQ Qprob.WeibullDistribution.negloglikprob.WeibullDistribution.negloglikGK Kprob.WeibullDistribution.medianprob.WeibullDistribution.medianCG Gprob.WeibullDistribution.meanprob.WeibullDistribution.meanAE Eprob.WeibullDistribution.iqrprob.WeibullDistribution.iqrCG Gprob.WeibullDistribution.icdfprob.WeibullDistribution.icdfAE Eprob.WeibullDistribution.cdfprob.WeibullDistribution.cdfMQ Qprob.WeibullDistribution.InputDataprob.WeibullDistribution.InputData[~_ _prob.WeibullDistribution.ParameterIsFixedprob.WeibullDistribution.ParameterIsFixeda}e eprob.WeibullDistribution.ParameterCovarianceprob.WeibullDistribution.ParameterCovarianceQ|U Uprob.WeibullDistribution.IsTruncatedprob.WeibullDistribution.IsTruncatedO{S Sprob.WeibullDistribution.Truncationprob.WeibullDistribution.TruncationYz] ]prob.WeibullDistribution.ParameterValuesprob.WeibullDistribution.ParameterValuescyg gprob.WeibullDistribution.ParameterDescriptionprob.WeibullDistribution.ParameterDescriptionWx[ [prob.WeibullDistribution.ParameterNamesprob.WeibullDistribution.ParameterNamesUwY Yprob.WeibullDistribution.NumParametersprob.WeibullDistribution.NumParameters[v_ _prob.WeibullDistribution.DistributionNameprob.WeibullDistribution.DistributionName=uA Aprob.WeibullDistribution.Bprob.WeibullDistribution.B=tA Aprob.WeibullDistribution.Aprob.WeibullDistribution.A9s= =prob.WeibullDistributionprob.WeibullDistributionArE Eprob.UniformDistribution.varprob.UniformDistribution.varKqO Oprob.UniformDistribution.truncateprob.UniformDistribution.truncateApE Eprob.UniformDistribution.stdprob.UniformDistribution.stdGoK Kprob.UniformDistribution.randomprob.UniformDistribution.randomCnG Gprob.UniformDistribution.plotprob.UniformDistribution.plotAmE Eprob.UniformDistribution.pdfprob.UniformDistribution.pdfGlK Kprob.UniformDistribution.medianprob.UniformDistribution.medianCkG Gprob.UniformDistribution.meanprob.UniformDistribution.meanAjE Eprob.UniformDistribution.iqrprob.UniformDistribution.iqrCiG Gprob.UniformDistribution.icdfprob.UniformDistribution.icdfAhE Eprob.UniformDistribution.cdfprob.UniformDistribution.cdfQgU Uprob.UniformDistribution.IsTruncatedprob.UniformDistribution.IsTruncatedOfS Sprob.UniformDistribution.Truncationprob.UniformDistribution.TruncationYe] ]prob.UniformDistribution.ParameterValuesprob.UniformDistribution.ParameterValuescdg gprob.UniformDistribution.ParameterDescriptionprob.UniformDistribution.ParameterDescriptionWc[ [prob.UniformDistribution.ParameterNamesprob.UniformDistribution.ParameterNamesUbY Yprob.UniformDistribution.NumParametersprob.UniformDistribution.NumParameters[a_ _prob.UniformDistribution.DistributionNameprob.UniformDistribution.DistributionNameE`I Iprob.UniformDistribution.Upperprob.UniformDistribution.Upperstatistics-release-1.9.2/inst/000077500000000000000000000000001524624707500163245ustar00rootroot00000000000000statistics-release-1.9.2/inst/Anomaly_Detection/000077500000000000000000000000001524624707500217225ustar00rootroot00000000000000statistics-release-1.9.2/inst/Anomaly_Detection/IsolationForest.m000066400000000000000000000250201524624707500252230ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} IsolationForest (@var{X}) ## @deftypefnx {statistics} {@var{Mdl} =} IsolationForest (@var{X}, @var{name}, @var{value}) ## ## Isolation Forest model for anomaly detection. ## ## An @code{IsolationForest} object stores an ensemble of isolation trees fitted ## to a set of observations and detects anomalies through the @code{isanomaly} ## method. Create a model with the @code{iforest} function rather than by ## calling this constructor directly. ## ## Anomalies are easier to isolate, so they sit closer to the root of a random ## isolation tree; the shorter its average path length across the ensemble, the ## higher an observation's anomaly score. ## ## @seealso{iforest, isanomaly} ## @end deftypefn classdef IsolationForest properties (GetAccess = public, SetAccess = private) ## -*- texinfo -*- ## @deftp {IsolationForest} {property} NumLearners ## The number of isolation trees in the ensemble. ## @end deftp NumLearners = []; ## -*- texinfo -*- ## @deftp {IsolationForest} {property} NumObservationsPerLearner ## The number of observations subsampled to grow each isolation tree. ## @end deftp NumObservationsPerLearner = []; ## -*- texinfo -*- ## @deftp {IsolationForest} {property} ContaminationFraction ## The assumed fraction of anomalies in the training data, in @math{[0, 1]}. ## @end deftp ContaminationFraction = []; ## -*- texinfo -*- ## @deftp {IsolationForest} {property} ScoreThreshold ## The score above which an observation is flagged as an anomaly. ## @end deftp ScoreThreshold = []; endproperties properties (GetAccess = public, SetAccess = private, Hidden) Trees_ = {}; # ensemble of isolation trees (nested structs) scores_ = []; # anomaly scores of the training observations tf_ = []; # anomaly flags of the training observations endproperties methods (Hidden) ## Custom display function disp (obj) printf (" IsolationForest model\n"); printf (" NumLearners: %d\n", obj.NumLearners); printf (" NumObservationsPerLearner: %d\n", ... obj.NumObservationsPerLearner); printf (" ContaminationFraction: %g\n", obj.ContaminationFraction); printf (" ScoreThreshold: %g\n", obj.ScoreThreshold); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {IsolationForest} {@var{Mdl} =} IsolationForest (@var{X}) ## @deftypefnx {IsolationForest} {@var{Mdl} =} IsolationForest (@var{X}, @var{name}, @var{value}) ## ## Fit an isolation forest to the @math{N}-by-@math{P} matrix @var{X}. ## Prefer the @code{iforest} function to this constructor. ## ## @end deftypefn function obj = IsolationForest (X, varargin) if (nargin < 1) error ("iforest: too few input arguments."); endif if (! isnumeric (X) || ! isreal (X) || ndims (X) != 2 || isempty (X)) error ("iforest: X must be a nonempty real numeric matrix."); endif [n, p] = size (X); ## Defaults numlearners = 100; numobs = min (n, 256); contam = 0; ## Parse Name-Value pairs if (mod (numel (varargin), 2) != 0) error ("iforest: each NAME must be followed by a VALUE."); endif while (numel (varargin) > 0) name = varargin{1}; val = varargin{2}; if (! ischar (name)) error ("iforest: optional argument names must be strings."); endif switch (tolower (name)) case "numlearners" numlearners = val; case "numobservationsperlearner" numobs = val; case "contaminationfraction" contam = val; otherwise error ("iforest: unknown parameter name '%s'.", name); endswitch varargin(1:2) = []; endwhile ## Validate options if (! isscalar (numlearners) || ! isnumeric (numlearners) || numlearners < 1 || fix (numlearners) != numlearners) error ("iforest: NUMLEARNERS must be a positive integer scalar."); endif if (! isscalar (numobs) || ! isnumeric (numobs) || numobs < 3 || fix (numobs) != numobs || numobs > n) error (strcat ("iforest: NUMOBSERVATIONSPERLEARNER must be an", ... " integer in [3, N].")); endif if (! isscalar (contam) || ! isnumeric (contam) || contam < 0 || contam > 1) error ("iforest: CONTAMINATIONFRACTION must be a scalar in [0, 1]."); endif ## Grow the ensemble, each tree from a random subsample of NUMOBS rows. maxdepth = ceil (log2 (numobs)); trees = cell (numlearners, 1); for t = 1:numlearners idx = randperm (n, numobs); trees{t} = IsolationForest.buildTree_ (X(idx, :), 0, maxdepth); endfor scores = IsolationForest.scoreAll_ (X, trees, numobs); if (contam == 0) thr = max (scores); else thr = quantile (scores, 1 - contam); endif obj.NumLearners = numlearners; obj.NumObservationsPerLearner = numobs; obj.ContaminationFraction = contam; obj.ScoreThreshold = thr; obj.Trees_ = trees; obj.scores_ = scores; obj.tf_ = scores > thr; endfunction ## -*- texinfo -*- ## @deftypefn {IsolationForest} {@var{tf} =} isanomaly (@var{Mdl}, @var{Xnew}) ## @deftypefnx {IsolationForest} {[@var{tf}, @var{scores}] =} isanomaly (@var{Mdl}, @var{Xnew}) ## @deftypefnx {IsolationForest} {[@dots{}] =} isanomaly (@dots{}, @qcode{'ScoreThreshold'}, @var{t}) ## ## Detect anomalies in the new observations @var{Xnew} using the fitted ## model @var{Mdl}. Returns the logical vector @var{tf} flagging anomalies ## and the anomaly @var{scores}, each obtained by dropping @var{Xnew} ## through the isolation trees. The threshold defaults to ## @code{@var{Mdl}.ScoreThreshold} and may be overridden with the ## @qcode{'ScoreThreshold'} name-value argument. ## ## @end deftypefn function [tf, scores] = isanomaly (obj, Xnew, varargin) if (nargin < 2) error ("isanomaly: too few input arguments."); endif if (! isnumeric (Xnew) || ! isreal (Xnew) || ndims (Xnew) != 2 || isempty (Xnew)) error ("isanomaly: XNEW must be a nonempty real numeric matrix."); endif thr = obj.ScoreThreshold; if (mod (numel (varargin), 2) != 0) error ("isanomaly: each NAME must be followed by a VALUE."); endif while (numel (varargin) > 0) if (! ischar (varargin{1}) || ! strcmpi (varargin{1}, "ScoreThreshold")) error ("isanomaly: unknown parameter name."); endif thr = varargin{2}; varargin(1:2) = []; endwhile scores = IsolationForest.scoreAll_ (Xnew, obj.Trees_, ... obj.NumObservationsPerLearner); tf = scores > thr; endfunction endmethods methods (Static, Access = private) ## Grow one isolation tree by recursive random splits. function node = buildTree_ (X, depth, maxdepth) n = rows (X); if (depth >= maxdepth || n <= 1) node = struct ("leaf", true, "size", n); return; endif mn = min (X, [], 1); mx = max (X, [], 1); valid = find (mx > mn); if (isempty (valid)) # all rows identical node = struct ("leaf", true, "size", n); return; endif q = valid(randi (numel (valid))); sp = mn(q) + (mx(q) - mn(q)) * rand; isleft = X(:, q) < sp; if (! any (isleft) || all (isleft)) # degenerate split node = struct ("leaf", true, "size", n); return; endif L = IsolationForest.buildTree_ (X(isleft, :), depth + 1, maxdepth); R = IsolationForest.buildTree_ (X(! isleft, :), depth + 1, maxdepth); node = struct ("leaf", false, "feature", q, "split", sp, ... "left", L, "right", R); endfunction ## Path length of a single observation down one tree. function h = pathLength_ (x, node, depth) if (node.leaf) h = depth + IsolationForest.cFactor_ (node.size); return; endif if (x(node.feature) < node.split) h = IsolationForest.pathLength_ (x, node.left, depth + 1); else h = IsolationForest.pathLength_ (x, node.right, depth + 1); endif endfunction ## Average path length of an unsuccessful search in a binary search tree. function c = cFactor_ (n) if (n <= 1) c = 0; else H = log (n - 1) + 0.5772156649015329; c = 2 * H - 2 * (n - 1) / n; endif endfunction ## Anomaly scores of the rows of P over the whole ensemble. function scores = scoreAll_ (P, trees, psi) m = rows (P); T = numel (trees); H = zeros (m, 1); for t = 1:T node = trees{t}; for i = 1:m H(i) += IsolationForest.pathLength_ (P(i, :), node, 0); endfor endfor Eh = H / T; scores = 2 .^ (- Eh / IsolationForest.cFactor_ (psi)); endfunction endmethods endclassdef ## Direct construction and isanomaly dispatch on the class %!test %! rand ("state", 5); %! X = [randn(40,2)*0.3; 10 10; -9 8]; %! Mdl = IsolationForest (X, "NumLearners", 50); %! assert_equal (isa (Mdl, "IsolationForest"), true); %! assert_equal (Mdl.NumLearners, 50); %! assert_equal (Mdl.NumObservationsPerLearner, 42); %! [tf, scores] = isanomaly (Mdl, [0 0; 12 12]); %! assert_equal (all (scores >= 0 & scores <= 1, 'all'), true); %! assert_equal (scores(2) > scores(1), true); # far point is more anomalous %! assert_equal (islogical (tf), true); statistics-release-1.9.2/inst/Anomaly_Detection/LocalOutlierFactor.m000066400000000000000000000250001524624707500256320ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} LocalOutlierFactor (@var{X}) ## @deftypefnx {statistics} {@var{Mdl} =} LocalOutlierFactor (@var{X}, @var{name}, @var{value}) ## ## Local Outlier Factor model for anomaly detection. ## ## A @code{LocalOutlierFactor} object stores a Local Outlier Factor (LOF) model ## fitted to a set of observations, and detects anomalies among those or new ## observations through the @code{isanomaly} method. Create a model with the ## @code{lof} function rather than by calling this constructor directly. ## ## The LOF of an observation compares its local density with the local density ## of its neighbors; a value near 1 indicates an inlier, whereas a value well ## above 1 indicates an outlier that lies in a sparser region than its ## neighbors. ## ## @seealso{lof, isanomaly} ## @end deftypefn classdef LocalOutlierFactor properties (GetAccess = public, SetAccess = private) ## -*- texinfo -*- ## @deftp {LocalOutlierFactor} {property} NumNeighbors ## The number of nearest neighbors used to compute the Local Outlier Factor. ## @end deftp NumNeighbors = []; ## -*- texinfo -*- ## @deftp {LocalOutlierFactor} {property} Distance ## The distance metric used to find neighbors, as a character vector. ## @end deftp Distance = []; ## -*- texinfo -*- ## @deftp {LocalOutlierFactor} {property} ContaminationFraction ## The assumed fraction of anomalies in the training data, in @math{[0, 1]}. ## @end deftp ContaminationFraction = []; ## -*- texinfo -*- ## @deftp {LocalOutlierFactor} {property} ScoreThreshold ## The score above which an observation is flagged as an anomaly. ## @end deftp ScoreThreshold = []; endproperties properties (GetAccess = public, SetAccess = private, Hidden) X_ = []; # training observations DistParameter_ = []; # metric parameter (exponent or covariance) kdist_ = []; # k-distance of each training observation lrd_ = []; # local reachability density of each observation scores_ = []; # LOF scores of the training observations tf_ = []; # anomaly flags of the training observations endproperties methods (Hidden) ## Custom display function disp (obj) printf (" LocalOutlierFactor model\n"); printf (" NumNeighbors: %d\n", obj.NumNeighbors); printf (" Distance: %s\n", obj.Distance); printf (" ContaminationFraction: %g\n", obj.ContaminationFraction); printf (" ScoreThreshold: %g\n", obj.ScoreThreshold); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {LocalOutlierFactor} {@var{Mdl} =} LocalOutlierFactor (@var{X}) ## @deftypefnx {LocalOutlierFactor} {@var{Mdl} =} LocalOutlierFactor (@var{X}, @var{name}, @var{value}) ## ## Fit a Local Outlier Factor model to the @math{N}-by-@math{P} matrix ## @var{X}. Prefer the @code{lof} function to this constructor. ## ## @end deftypefn function obj = LocalOutlierFactor (X, varargin) if (nargin < 1) error ("lof: too few input arguments."); endif if (! isnumeric (X) || ! isreal (X) || ndims (X) != 2 || isempty (X)) error ("lof: X must be a nonempty real numeric matrix."); endif [n, p] = size (X); ## Defaults k = min (20, rows (unique (X, "rows")) - 1); distance = "euclidean"; contam = 0; exponent = 2; covmat = []; ## Parse Name-Value pairs if (mod (numel (varargin), 2) != 0) error ("lof: each NAME must be followed by a VALUE."); endif while (numel (varargin) > 0) name = varargin{1}; val = varargin{2}; if (! ischar (name)) error ("lof: optional argument names must be strings."); endif switch (tolower (name)) case "numneighbors" k = val; case "distance" distance = tolower (val); case "contaminationfraction" contam = val; case "exponent" exponent = val; case "cov" covmat = val; otherwise error ("lof: unknown parameter name '%s'.", name); endswitch varargin(1:2) = []; endwhile ## Validate options metrics = {"euclidean", "seuclidean", "mahalanobis", "cityblock", ... "minkowski", "chebychev", "cosine", "correlation", ... "hamming", "jaccard", "spearman"}; if (! any (strcmp (distance, metrics))) error ("lof: unsupported distance metric '%s'.", distance); endif if (! isscalar (k) || ! isnumeric (k) || k < 1 || fix (k) != k || k >= n) error ("lof: NUMNEIGHBORS must be a positive integer less than N."); endif if (! isscalar (contam) || ! isnumeric (contam) || contam < 0 || contam > 1) error ("lof: CONTAMINATIONFRACTION must be a scalar in [0, 1]."); endif ## Select the metric parameter for pdist2. if (strcmp (distance, "minkowski")) distparam = exponent; elseif (strcmp (distance, "mahalanobis")) distparam = covmat; else distparam = []; endif ## Pairwise distances, excluding each point from its own neighborhood. D = LocalOutlierFactor.pdists_ (X, X, distance, distparam); D(1:(n + 1):end) = Inf; [kdist, lrd, scores] = LocalOutlierFactor.lofTrain_ (D, k); if (contam == 0) thr = max (scores); else thr = quantile (scores, 1 - contam); endif obj.NumNeighbors = k; obj.Distance = distance; obj.ContaminationFraction = contam; obj.ScoreThreshold = thr; obj.X_ = X; obj.DistParameter_ = distparam; obj.kdist_ = kdist; obj.lrd_ = lrd; obj.scores_ = scores; obj.tf_ = scores > thr; endfunction ## -*- texinfo -*- ## @deftypefn {LocalOutlierFactor} {@var{tf} =} isanomaly (@var{Mdl}, @var{Xnew}) ## @deftypefnx {LocalOutlierFactor} {[@var{tf}, @var{scores}] =} isanomaly (@var{Mdl}, @var{Xnew}) ## @deftypefnx {LocalOutlierFactor} {[@dots{}] =} isanomaly (@dots{}, @qcode{'ScoreThreshold'}, @var{t}) ## ## Detect anomalies in the new observations @var{Xnew} using the fitted ## model @var{Mdl}. Returns the logical vector @var{tf} flagging anomalies ## and the Local Outlier Factor @var{scores}, each computed from the nearest ## neighbors of @var{Xnew} in the training data. The threshold defaults to ## @code{@var{Mdl}.ScoreThreshold} and may be overridden with the ## @qcode{'ScoreThreshold'} name-value argument. ## ## @end deftypefn function [tf, scores] = isanomaly (obj, Xnew, varargin) if (nargin < 2) error ("isanomaly: too few input arguments."); endif if (! isnumeric (Xnew) || ! isreal (Xnew) || ndims (Xnew) != 2 || isempty (Xnew)) error ("isanomaly: XNEW must be a nonempty real numeric matrix."); endif if (columns (Xnew) != columns (obj.X_)) error ("isanomaly: XNEW must have the same number of columns as X."); endif thr = obj.ScoreThreshold; if (mod (numel (varargin), 2) != 0) error ("isanomaly: each NAME must be followed by a VALUE."); endif while (numel (varargin) > 0) if (! ischar (varargin{1}) || ! strcmpi (varargin{1}, "ScoreThreshold")) error ("isanomaly: unknown parameter name."); endif thr = varargin{2}; varargin(1:2) = []; endwhile Dq = LocalOutlierFactor.pdists_ (Xnew, obj.X_, obj.Distance, ... obj.DistParameter_); scores = LocalOutlierFactor.lofQuery_ (Dq, obj.NumNeighbors, ... obj.kdist_, obj.lrd_); tf = scores > thr; endfunction endmethods methods (Static, Access = private) ## Pairwise distances between the rows of A and B in the given metric. function D = pdists_ (A, B, distance, distparam) if (isempty (distparam)) D = pdist2 (A, B, distance); else D = pdist2 (A, B, distance, distparam); endif endfunction ## LOF of the training observations from the self-excluded distance matrix. function [kdist, lrd, scores] = lofTrain_ (D, k) n = rows (D); [NB, Ds] = __knnselect__ (D, k); kdist = Ds(:, k); lrd = zeros (n, 1); for i = 1:n rd = max (kdist(NB(i,:)), D(i, NB(i,:))'); lrd(i) = 1 / mean (rd); endfor scores = zeros (n, 1); for i = 1:n scores(i) = mean (lrd(NB(i,:))) / lrd(i); endfor endfunction ## LOF of query rows against the training neighbors, k-distances, and lrd. function scores = lofQuery_ (Dq, k, kdist, lrd) m = rows (Dq); [~, ord] = sort (Dq, 2); NB = ord(:, 1:k); scores = zeros (m, 1); for i = 1:m rd = max (kdist(NB(i,:)), Dq(i, NB(i,:))'); lrdq = 1 / mean (rd); scores(i) = mean (lrd(NB(i,:))) / lrdq; endfor endfunction endmethods endclassdef ## Direct construction and isanomaly dispatch on the class %!test %! X = [0 0; 0.1 0.1; 0.2 -0.1; -0.1 0.2; 0.1 -0.2; -0.2 0.1; 0.15 0.05; 5 5]; %! Mdl = LocalOutlierFactor (X, "NumNeighbors", 3); %! assert_equal (isa (Mdl, "LocalOutlierFactor"), true); %! assert_equal (Mdl.NumNeighbors, 3); %! assert_equal (Mdl.Distance, "euclidean"); %! [tf, scores] = isanomaly (Mdl, [0 0; 6 6]); %! assert_equal (size (scores), [2, 1]); %! assert_equal (scores(2) > scores(1), true); # far point is more anomalous %! assert_equal (islogical (tf), true); statistics-release-1.9.2/inst/Anomaly_Detection/OneClassSVM.m000066400000000000000000000257731524624707500242130ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} OneClassSVM (@var{X}) ## @deftypefnx {statistics} {@var{Mdl} =} OneClassSVM (@var{X}, @var{name}, @var{value}) ## ## One-class support vector machine model for anomaly detection. ## ## A @code{OneClassSVM} object stores a one-class support vector machine fitted ## to a set of observations in an expanded feature space, and detects anomalies ## through the @code{isanomaly} method. Create a model with the @code{ocsvm} ## function rather than by calling this constructor directly. ## ## The model maps the data to a randomized feature space that approximates a ## Gaussian kernel and fits a linear boundary that encloses the bulk of the ## observations; points outside the boundary receive higher anomaly scores. ## ## @seealso{ocsvm, isanomaly} ## @end deftypefn classdef OneClassSVM properties (GetAccess = public, SetAccess = private) ## -*- texinfo -*- ## @deftp {OneClassSVM} {property} KernelScale ## The scale of the Gaussian kernel approximated by the feature expansion. ## @end deftp KernelScale = []; ## -*- texinfo -*- ## @deftp {OneClassSVM} {property} Lambda ## The strength of the ridge (L2) regularization term. ## @end deftp Lambda = []; ## -*- texinfo -*- ## @deftp {OneClassSVM} {property} NumExpansionDimensions ## The number of dimensions of the expanded feature space. ## @end deftp NumExpansionDimensions = []; ## -*- texinfo -*- ## @deftp {OneClassSVM} {property} Mu ## The predictor means used for standardization, or empty. ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {OneClassSVM} {property} Sigma ## The predictor standard deviations used for standardization, or empty. ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {OneClassSVM} {property} ContaminationFraction ## The assumed fraction of anomalies in the training data, in @math{[0, 1]}. ## @end deftp ContaminationFraction = []; ## -*- texinfo -*- ## @deftp {OneClassSVM} {property} ScoreThreshold ## The score above which an observation is flagged as an anomaly. ## @end deftp ScoreThreshold = []; endproperties properties (GetAccess = public, SetAccess = private, Hidden) W_ = []; # random projection weights (D-by-P) b_ = []; # random phases (D-by-1) beta_ = []; # fitted linear coefficients (D-by-1) scores_ = []; # anomaly scores of the training observations tf_ = []; # anomaly flags of the training observations endproperties methods (Hidden) ## Custom display function disp (obj) printf (" OneClassSVM model\n"); printf (" KernelScale: %g\n", obj.KernelScale); printf (" Lambda: %g\n", obj.Lambda); printf (" NumExpansionDimensions: %d\n", obj.NumExpansionDimensions); printf (" ContaminationFraction: %g\n", obj.ContaminationFraction); printf (" ScoreThreshold: %g\n", obj.ScoreThreshold); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {OneClassSVM} {@var{Mdl} =} OneClassSVM (@var{X}) ## @deftypefnx {OneClassSVM} {@var{Mdl} =} OneClassSVM (@var{X}, @var{name}, @var{value}) ## ## Fit a one-class support vector machine to the @math{N}-by-@math{P} matrix ## @var{X}. Prefer the @code{ocsvm} function to this constructor. ## ## @end deftypefn function obj = OneClassSVM (X, varargin) if (nargin < 1) error ("ocsvm: too few input arguments."); endif if (! isnumeric (X) || ! isreal (X) || ndims (X) != 2 || isempty (X)) error ("ocsvm: X must be a nonempty real numeric matrix."); endif [n, p] = size (X); ## Defaults ("auto" values resolved below) kernelscale = "auto"; lambda = "auto"; numdims = "auto"; standardize = false; contam = 0; ## Parse Name-Value pairs if (mod (numel (varargin), 2) != 0) error ("ocsvm: each NAME must be followed by a VALUE."); endif while (numel (varargin) > 0) name = varargin{1}; val = varargin{2}; if (! ischar (name)) error ("ocsvm: optional argument names must be strings."); endif switch (tolower (name)) case "kernelscale" kernelscale = val; case "lambda" lambda = val; case "numexpansiondimensions" numdims = val; case "standardizedata" standardize = logical (val); case "contaminationfraction" contam = val; otherwise error ("ocsvm: unknown parameter name '%s'.", name); endswitch varargin(1:2) = []; endwhile ## Resolve and validate the "auto" defaults. if (ischar (kernelscale) && strcmpi (kernelscale, "auto")) kernelscale = 1; elseif (! isscalar (kernelscale) || ! isnumeric (kernelscale) || kernelscale <= 0) error ("ocsvm: KERNELSCALE must be a positive scalar or 'auto'."); endif if (ischar (lambda) && strcmpi (lambda, "auto")) lambda = 1 / n; elseif (! isscalar (lambda) || ! isnumeric (lambda) || lambda < 0) error ("ocsvm: LAMBDA must be a nonnegative scalar or 'auto'."); endif if (ischar (numdims) && strcmpi (numdims, "auto")) numdims = 2 ^ ceil (log2 (max (n, 2))); elseif (! isscalar (numdims) || ! isnumeric (numdims) || numdims < 1 || fix (numdims) != numdims) error (strcat ("ocsvm: NUMEXPANSIONDIMENSIONS must be a positive", ... " integer or 'auto'.")); endif if (! isscalar (contam) || ! isnumeric (contam) || contam < 0 || contam > 1) error ("ocsvm: CONTAMINATIONFRACTION must be a scalar in [0, 1]."); endif ## Optionally standardize the predictors. if (standardize) mu = mean (X, 1); sigma = std (X, 0, 1); sigma(sigma == 0) = 1; Xs = (X - mu) ./ sigma; else mu = []; sigma = []; Xs = X; endif ## Random Fourier feature map approximating a Gaussian kernel, then a ## squared-hinge one-class SVM with ridge regularization. W = randn (numdims, p) / kernelscale; b = 2 * pi * rand (numdims, 1); Phi = OneClassSVM.features_ (Xs, W, b); beta = OneClassSVM.fitOneClass_ (Phi, lambda); scores = - (Phi * beta); if (contam == 0) thr = max (scores); else thr = quantile (scores, 1 - contam); endif obj.KernelScale = kernelscale; obj.Lambda = lambda; obj.NumExpansionDimensions = numdims; obj.Mu = mu; obj.Sigma = sigma; obj.ContaminationFraction = contam; obj.ScoreThreshold = thr; obj.W_ = W; obj.b_ = b; obj.beta_ = beta; obj.scores_ = scores; obj.tf_ = scores > thr; endfunction ## -*- texinfo -*- ## @deftypefn {OneClassSVM} {@var{tf} =} isanomaly (@var{Mdl}, @var{Xnew}) ## @deftypefnx {OneClassSVM} {[@var{tf}, @var{scores}] =} isanomaly (@var{Mdl}, @var{Xnew}) ## @deftypefnx {OneClassSVM} {[@dots{}] =} isanomaly (@dots{}, @qcode{'ScoreThreshold'}, @var{t}) ## ## Detect anomalies in the new observations @var{Xnew} using the fitted ## model @var{Mdl}. Returns the logical vector @var{tf} flagging anomalies ## and the anomaly @var{scores}. The threshold defaults to ## @code{@var{Mdl}.ScoreThreshold} and may be overridden with the ## @qcode{'ScoreThreshold'} name-value argument. ## ## @end deftypefn function [tf, scores] = isanomaly (obj, Xnew, varargin) if (nargin < 2) error ("isanomaly: too few input arguments."); endif if (! isnumeric (Xnew) || ! isreal (Xnew) || ndims (Xnew) != 2 || isempty (Xnew)) error ("isanomaly: XNEW must be a nonempty real numeric matrix."); endif if (columns (Xnew) != columns (obj.W_)) error ("isanomaly: XNEW must have the same number of columns as X."); endif thr = obj.ScoreThreshold; if (mod (numel (varargin), 2) != 0) error ("isanomaly: each NAME must be followed by a VALUE."); endif while (numel (varargin) > 0) if (! ischar (varargin{1}) || ! strcmpi (varargin{1}, "ScoreThreshold")) error ("isanomaly: unknown parameter name."); endif thr = varargin{2}; varargin(1:2) = []; endwhile if (! isempty (obj.Mu)) Xnew = (Xnew - obj.Mu) ./ obj.Sigma; endif Phi = OneClassSVM.features_ (Xnew, obj.W_, obj.b_); scores = - (Phi * obj.beta_); tf = scores > thr; endfunction endmethods methods (Static, Access = private) ## Random Fourier features approximating a Gaussian kernel. function Phi = features_ (X, W, b) D = rows (W); Phi = sqrt (2 / D) * cos (X * W' + b'); endfunction ## Squared-hinge one-class SVM (unit margin from the origin) with ridge ## regularization, minimized by Newton iterations over the active set. function beta = fitOneClass_ (Phi, lambda) [n, D] = size (Phi); beta = zeros (D, 1); I = eye (D); for it = 1:100 d = Phi * beta; active = d < 1; # observations inside the margin Pa = Phi(active, :); r = 1 - d(active); g = lambda * beta - (Pa' * r) / n; H = lambda * I + (Pa' * Pa) / n; step = H \ g; beta = beta - step; if (norm (step) < 1e-10) break; endif endfor endfunction endmethods endclassdef ## Direct construction and isanomaly dispatch on the class %!test %! rand ("state", 8); %! randn ("state", 8); %! X = [randn(60,2)*0.3; 9 9; -8 7]; %! Mdl = OneClassSVM (X, "KernelScale", 2, "NumExpansionDimensions", 64); %! assert_equal (isa (Mdl, "OneClassSVM"), true); %! assert_equal (Mdl.NumExpansionDimensions, 64); %! [tf, scores] = isanomaly (Mdl, [0 0; 12 12]); %! assert_equal (scores(2) > scores(1), true); # far point is more anomalous %! assert_equal (islogical (tf), true); statistics-release-1.9.2/inst/Anomaly_Detection/doc-cache000066400000000000000000000547411524624707500234660ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 28 # name: # type: sq_string # elements: 1 # length: 15 IsolationForest # name: # type: sq_string # elements: 1 # length: 610 statistics: Mdl = IsolationForest ( X ) statistics: Mdl = IsolationForest ( X , name , value ) Isolation Forest model for anomaly detection. An IsolationForest object stores an ensemble of isolation trees fitted to a set of observations and detects anomalies through the isanomaly method. Create a model with the iforest function rather than by calling this constructor directly. Anomalies are easier to isolate, so they sit closer to the root of a random isolation tree; the shorter its average path length across the ensemble, the higher an observation’s anomaly score. See also: iforest, isanomaly # name: # type: sq_string # elements: 1 # length: 45 Isolation Forest model for anomaly detection. # name: # type: sq_string # elements: 1 # length: 37 IsolationForest.ContaminationFraction # name: # type: sq_string # elements: 1 # length: 116 IsolationForest: property ContaminationFraction The assumed fraction of anomalies in the training data, in [0, 1] . # name: # type: sq_string # elements: 1 # length: 66 The assumed fraction of anomalies in the training data, in [0, 1]. # name: # type: sq_string # elements: 1 # length: 31 IsolationForest.IsolationForest # name: # type: sq_string # elements: 1 # length: 205 IsolationForest: Mdl = IsolationForest ( X ) IsolationForest: Mdl = IsolationForest ( X , name , value ) Fit an isolation forest to the N -by- P matrix X . Prefer the iforest function to this constructor. # name: # type: sq_string # elements: 1 # length: 47 Fit an isolation forest to the N-by-P matrix X. # name: # type: sq_string # elements: 1 # length: 27 IsolationForest.NumLearners # name: # type: sq_string # elements: 1 # length: 85 IsolationForest: property NumLearners The number of isolation trees in the ensemble. # name: # type: sq_string # elements: 1 # length: 46 The number of isolation trees in the ensemble. # name: # type: sq_string # elements: 1 # length: 41 IsolationForest.NumObservationsPerLearner # name: # type: sq_string # elements: 1 # length: 119 IsolationForest: property NumObservationsPerLearner The number of observations subsampled to grow each isolation tree. # name: # type: sq_string # elements: 1 # length: 66 The number of observations subsampled to grow each isolation tree. # name: # type: sq_string # elements: 1 # length: 30 IsolationForest.ScoreThreshold # name: # type: sq_string # elements: 1 # length: 104 IsolationForest: property ScoreThreshold The score above which an observation is flagged as an anomaly. # name: # type: sq_string # elements: 1 # length: 62 The score above which an observation is flagged as an anomaly. # name: # type: sq_string # elements: 1 # length: 25 IsolationForest.isanomaly # name: # type: sq_string # elements: 1 # length: 505 IsolationForest: tf = isanomaly ( Mdl , Xnew ) IsolationForest: [ tf , scores ] = isanomaly ( Mdl , Xnew ) IsolationForest: […] = isanomaly (…, 'ScoreThreshold' , t ) Detect anomalies in the new observations Xnew using the fitted model Mdl . Returns the logical vector tf flagging anomalies and the anomaly scores , each obtained by dropping Xnew through the isolation trees. The threshold defaults to Mdl .ScoreThreshold and may be overridden with the 'ScoreThreshold' name-value argument. # name: # type: sq_string # elements: 1 # length: 73 Detect anomalies in the new observations Xnew using the fitted model Mdl. # name: # type: sq_string # elements: 1 # length: 18 LocalOutlierFactor # name: # type: sq_string # elements: 1 # length: 683 statistics: Mdl = LocalOutlierFactor ( X ) statistics: Mdl = LocalOutlierFactor ( X , name , value ) Local Outlier Factor model for anomaly detection. A LocalOutlierFactor object stores a Local Outlier Factor (LOF) model fitted to a set of observations, and detects anomalies among those or new observations through the isanomaly method. Create a model with the lof function rather than by calling this constructor directly. The LOF of an observation compares its local density with the local density of its neighbors; a value near 1 indicates an inlier, whereas a value well above 1 indicates an outlier that lies in a sparser region than its neighbors. See also: lof, isanomaly # name: # type: sq_string # elements: 1 # length: 49 Local Outlier Factor model for anomaly detection. # name: # type: sq_string # elements: 1 # length: 40 LocalOutlierFactor.ContaminationFraction # name: # type: sq_string # elements: 1 # length: 119 LocalOutlierFactor: property ContaminationFraction The assumed fraction of anomalies in the training data, in [0, 1] . # name: # type: sq_string # elements: 1 # length: 66 The assumed fraction of anomalies in the training data, in [0, 1]. # name: # type: sq_string # elements: 1 # length: 27 LocalOutlierFactor.Distance # name: # type: sq_string # elements: 1 # length: 105 LocalOutlierFactor: property Distance The distance metric used to find neighbors, as a character vector. # name: # type: sq_string # elements: 1 # length: 66 The distance metric used to find neighbors, as a character vector. # name: # type: sq_string # elements: 1 # length: 37 LocalOutlierFactor.LocalOutlierFactor # name: # type: sq_string # elements: 1 # length: 222 LocalOutlierFactor: Mdl = LocalOutlierFactor ( X ) LocalOutlierFactor: Mdl = LocalOutlierFactor ( X , name , value ) Fit a Local Outlier Factor model to the N -by- P matrix X . Prefer the lof function to this constructor. # name: # type: sq_string # elements: 1 # length: 56 Fit a Local Outlier Factor model to the N-by-P matrix X. # name: # type: sq_string # elements: 1 # length: 31 LocalOutlierFactor.NumNeighbors # name: # type: sq_string # elements: 1 # length: 116 LocalOutlierFactor: property NumNeighbors The number of nearest neighbors used to compute the Local Outlier Factor. # name: # type: sq_string # elements: 1 # length: 73 The number of nearest neighbors used to compute the Local Outlier Factor. # name: # type: sq_string # elements: 1 # length: 33 LocalOutlierFactor.ScoreThreshold # name: # type: sq_string # elements: 1 # length: 107 LocalOutlierFactor: property ScoreThreshold The score above which an observation is flagged as an anomaly. # name: # type: sq_string # elements: 1 # length: 62 The score above which an observation is flagged as an anomaly. # name: # type: sq_string # elements: 1 # length: 28 LocalOutlierFactor.isanomaly # name: # type: sq_string # elements: 1 # length: 538 LocalOutlierFactor: tf = isanomaly ( Mdl , Xnew ) LocalOutlierFactor: [ tf , scores ] = isanomaly ( Mdl , Xnew ) LocalOutlierFactor: […] = isanomaly (…, 'ScoreThreshold' , t ) Detect anomalies in the new observations Xnew using the fitted model Mdl . Returns the logical vector tf flagging anomalies and the Local Outlier Factor scores , each computed from the nearest neighbors of Xnew in the training data. The threshold defaults to Mdl .ScoreThreshold and may be overridden with the 'ScoreThreshold' name-value argument. # name: # type: sq_string # elements: 1 # length: 73 Detect anomalies in the new observations Xnew using the fitted model Mdl. # name: # type: sq_string # elements: 1 # length: 11 OneClassSVM # name: # type: sq_string # elements: 1 # length: 665 statistics: Mdl = OneClassSVM ( X ) statistics: Mdl = OneClassSVM ( X , name , value ) One-class support vector machine model for anomaly detection. A OneClassSVM object stores a one-class support vector machine fitted to a set of observations in an expanded feature space, and detects anomalies through the isanomaly method. Create a model with the ocsvm function rather than by calling this constructor directly. The model maps the data to a randomized feature space that approximates a Gaussian kernel and fits a linear boundary that encloses the bulk of the observations; points outside the boundary receive higher anomaly scores. See also: ocsvm, isanomaly # name: # type: sq_string # elements: 1 # length: 61 One-class support vector machine model for anomaly detection. # name: # type: sq_string # elements: 1 # length: 33 OneClassSVM.ContaminationFraction # name: # type: sq_string # elements: 1 # length: 112 OneClassSVM: property ContaminationFraction The assumed fraction of anomalies in the training data, in [0, 1] . # name: # type: sq_string # elements: 1 # length: 66 The assumed fraction of anomalies in the training data, in [0, 1]. # name: # type: sq_string # elements: 1 # length: 23 OneClassSVM.KernelScale # name: # type: sq_string # elements: 1 # length: 106 OneClassSVM: property KernelScale The scale of the Gaussian kernel approximated by the feature expansion. # name: # type: sq_string # elements: 1 # length: 71 The scale of the Gaussian kernel approximated by the feature expansion. # name: # type: sq_string # elements: 1 # length: 18 OneClassSVM.Lambda # name: # type: sq_string # elements: 1 # length: 81 OneClassSVM: property Lambda The strength of the ridge (L2) regularization term. # name: # type: sq_string # elements: 1 # length: 51 The strength of the ridge (L2) regularization term. # name: # type: sq_string # elements: 1 # length: 14 OneClassSVM.Mu # name: # type: sq_string # elements: 1 # length: 81 OneClassSVM: property Mu The predictor means used for standardization, or empty. # name: # type: sq_string # elements: 1 # length: 55 The predictor means used for standardization, or empty. # name: # type: sq_string # elements: 1 # length: 34 OneClassSVM.NumExpansionDimensions # name: # type: sq_string # elements: 1 # length: 101 OneClassSVM: property NumExpansionDimensions The number of dimensions of the expanded feature space. # name: # type: sq_string # elements: 1 # length: 55 The number of dimensions of the expanded feature space. # name: # type: sq_string # elements: 1 # length: 23 OneClassSVM.OneClassSVM # name: # type: sq_string # elements: 1 # length: 202 OneClassSVM: Mdl = OneClassSVM ( X ) OneClassSVM: Mdl = OneClassSVM ( X , name , value ) Fit a one-class support vector machine to the N -by- P matrix X . Prefer the ocsvm function to this constructor. # name: # type: sq_string # elements: 1 # length: 62 Fit a one-class support vector machine to the N-by-P matrix X. # name: # type: sq_string # elements: 1 # length: 26 OneClassSVM.ScoreThreshold # name: # type: sq_string # elements: 1 # length: 100 OneClassSVM: property ScoreThreshold The score above which an observation is flagged as an anomaly. # name: # type: sq_string # elements: 1 # length: 62 The score above which an observation is flagged as an anomaly. # name: # type: sq_string # elements: 1 # length: 17 OneClassSVM.Sigma # name: # type: sq_string # elements: 1 # length: 98 OneClassSVM: property Sigma The predictor standard deviations used for standardization, or empty. # name: # type: sq_string # elements: 1 # length: 69 The predictor standard deviations used for standardization, or empty. # name: # type: sq_string # elements: 1 # length: 21 OneClassSVM.isanomaly # name: # type: sq_string # elements: 1 # length: 433 OneClassSVM: tf = isanomaly ( Mdl , Xnew ) OneClassSVM: [ tf , scores ] = isanomaly ( Mdl , Xnew ) OneClassSVM: […] = isanomaly (…, 'ScoreThreshold' , t ) Detect anomalies in the new observations Xnew using the fitted model Mdl . Returns the logical vector tf flagging anomalies and the anomaly scores . The threshold defaults to Mdl .ScoreThreshold and may be overridden with the 'ScoreThreshold' name-value argument. # name: # type: sq_string # elements: 1 # length: 73 Detect anomalies in the new observations Xnew using the fitted model Mdl. # name: # type: sq_string # elements: 1 # length: 7 iforest # name: # type: sq_string # elements: 1 # length: 1972 statistics: Mdl = iforest ( X ) statistics: [ Mdl , tf ] = iforest ( X ) statistics: [ Mdl , tf , scores ] = iforest ( X ) statistics: […] = iforest (…, name , value ) Detect anomalies with an isolation forest. Mdl = iforest ( X ) fits an isolation forest to the N -by- P matrix X , whose rows are observations and columns are variables, and returns an IsolationForest object Mdl . [ Mdl , tf , scores ] = iforest ( X ) also returns the N -by-1 logical vector tf flagging the anomalous observations and the N -by-1 vector scores of anomaly scores in the range [0, 1] . A higher score indicates an observation that is more easily isolated, and therefore more likely to be an anomaly. The score of an observation is 2^(-E[h] / c) , where E[h] is its average path length over the isolation trees and c is the expected path length of an unsuccessful search in a binary tree of NumObservationsPerLearner nodes. Each tree is grown from a random subsample of the data by recursively splitting on a random variable at a random value, so anomalies, being easier to isolate, obtain shorter paths. Additional parameters can be specified by Name-Value pair arguments. Name Value 'NumLearners' the number of isolation trees, a positive integer (default 100). 'NumObservationsPerLearner' the subsample size used to grow each tree, an integer in [3, N] (default min ( N , 256) ). 'ContaminationFraction' the assumed fraction of anomalies in X , a scalar in [0, 1] (default 0). It sets Mdl .ScoreThreshold to quantile ( scores , 1 - ContaminationFraction ) ; when it is 0 the threshold is the maximum score and no training observation is flagged. Because the trees are grown from random subsamples and random splits, the scores depend on the state of the random number generator and are not reproducible across runs unless the generator is seeded. Use the isanomaly method of Mdl to detect anomalies in new data. See also: IsolationForest, isanomaly, lof, robustcov # name: # type: sq_string # elements: 1 # length: 42 Detect anomalies with an isolation forest. # name: # type: sq_string # elements: 1 # length: 3 lof # name: # type: sq_string # elements: 1 # length: 1971 statistics: Mdl = lof ( X ) statistics: [ Mdl , tf ] = lof ( X ) statistics: [ Mdl , tf , scores ] = lof ( X ) statistics: […] = lof (…, name , value ) Detect anomalies with the Local Outlier Factor (LOF) method. Mdl = lof ( X ) fits a Local Outlier Factor model to the N -by- P matrix X , whose rows are observations and columns are variables, and returns a LocalOutlierFactor object Mdl . [ Mdl , tf , scores ] = lof ( X ) also returns the N -by-1 logical vector tf flagging the anomalous observations and the N -by-1 vector scores of LOF values. A score near 1 indicates an inlier, whereas a score well above 1 indicates an outlier lying in a region sparser than its neighbors. The Local Outlier Factor of an observation is the average ratio of the local reachability density of its NumNeighbors nearest neighbors to its own local reachability density, where the local reachability density is the inverse mean reachability distance to those neighbors and the reachability distance from p to o is max (k-distance (o), d (p, o)) . Additional parameters can be specified by Name-Value pair arguments. Name Value 'NumNeighbors' the number of nearest neighbors, a positive integer less than N . The default is min (20, u - 1) , where u is the number of unique observations. 'Distance' the distance metric used to find neighbors, one of the metrics accepted by pdist2 ( 'euclidean' by default). 'ContaminationFraction' the assumed fraction of anomalies in X , a scalar in [0, 1] (default 0). It sets Mdl .ScoreThreshold to quantile ( scores , 1 - ContaminationFraction ) ; when it is 0 the threshold is the maximum score and no training observation is flagged. 'Exponent' the Minkowski distance exponent (default 2), used only with the 'minkowski' distance. 'Cov' the covariance matrix used only with the 'mahalanobis' distance. Use the isanomaly method of Mdl to detect anomalies in new data. See also: LocalOutlierFactor, isanomaly, dbscan, robustcov # name: # type: sq_string # elements: 1 # length: 60 Detect anomalies with the Local Outlier Factor (LOF) method. # name: # type: sq_string # elements: 1 # length: 5 ocsvm # name: # type: sq_string # elements: 1 # length: 2589 statistics: Mdl = ocsvm ( X ) statistics: [ Mdl , tf ] = ocsvm ( X ) statistics: [ Mdl , tf , scores ] = ocsvm ( X ) statistics: […] = ocsvm (…, name , value ) Detect anomalies with a one-class support vector machine. Mdl = ocsvm ( X ) fits a one-class support vector machine to the N -by- P matrix X , whose rows are observations and columns are variables, and returns a OneClassSVM object Mdl . [ Mdl , tf , scores ] = ocsvm ( X ) also returns the N -by-1 logical vector tf flagging the anomalous observations and the N -by-1 vector scores of anomaly scores. A higher score indicates an observation that lies further outside the boundary enclosing the data, and is therefore more likely to be an anomaly. The observations are mapped to a randomized feature space that approximates a Gaussian kernel of scale KernelScale using NumExpansionDimensions features, and a linear one-class boundary is fitted there with ridge regularization of strength Lambda . Additional parameters can be specified by Name-Value pair arguments. Name Value 'KernelScale' the scale of the approximated Gaussian kernel, a positive scalar or 'auto' (default). 'Lambda' the ridge regularization strength, a nonnegative scalar or 'auto' (default). 'NumExpansionDimensions' the number of expanded feature dimensions, a positive integer or 'auto' (default). 'StandardizeData' a logical scalar (default false ); when true each predictor is centered and scaled and the means and standard deviations are stored in Mdl .Mu and Mdl .Sigma . 'ContaminationFraction' the assumed fraction of anomalies in X , a scalar in [0, 1] (default 0). It sets Mdl .ScoreThreshold to quantile ( scores , 1 - ContaminationFraction ) ; when it is 0 the threshold is the maximum score and no training observation is flagged. The feature expansion uses random projections, so the scores depend on the state of the random number generator and are not reproducible across runs unless the generator is seeded. The 'auto' selections and the fitted model differ from MATLAB’s implementation, which uses a different feature expansion and solver. For a deterministic, classic one-class support vector machine (a nu-SVM with an exact kernel, computed through libsvm ), use fitcsvm with a single class in the response or with the 'Nu' name-value argument; that path returns a ClassificationSVM object whose predict method labels observations, rather than the anomaly-scoring interface provided here. Use the isanomaly method of Mdl to detect anomalies in new data. See also: OneClassSVM, isanomaly, iforest, lof, fitcsvm # name: # type: sq_string # elements: 1 # length: 57 Detect anomalies with a one-class support vector machine. # name: # type: sq_string # elements: 1 # length: 9 robustcov # name: # type: sq_string # elements: 1 # length: 2411 statistics: sig = robustcov ( X ) statistics: [ sig , mu ] = robustcov ( X ) statistics: [ sig , mu , mah ] = robustcov ( X ) statistics: [ sig , mu , mah , outliers ] = robustcov ( X ) statistics: [ sig , mu , mah , outliers , s ] = robustcov ( X ) statistics: […] = robustcov (…, name , value ) Robust multivariate covariance and mean estimate. sig = robustcov ( X ) returns a robust estimate sig of the covariance matrix of the N×P data matrix X , computed so that it is not distorted by outlying observations. Rows of X are observations and columns are variables. Rows containing NaN values are removed. [ sig , mu , mah , outliers , s ] = robustcov (…) also returns the robust mean mu ( 1×P ), the robust Mahalanobis distances mah ( N×1 ) of each observation from the estimated distribution, a logical vector outliers ( N×1 ) flagging observations whose distance exceeds sqrt (chi2inv (0.975, P )) , and a structure s holding the estimate metadata. Additional parameters can be specified by Name-Value pair arguments. Name Value 'Method' the estimator, either 'fmcd' (default, the Fast Minimum Covariance Determinant algorithm) or 'ogk' (the Orthogonalized Gnanadesikan-Kettenring estimator). 'olivehawkins' is not implemented. 'OutlierFraction' the maximum fraction of outliers, a scalar in [0, 0.5] (default 0.5), used to set the size of the elemental subsets in 'fmcd' . 'NumTrials' the number of random elemental subsets drawn by 'fmcd' , a positive integer (default 500). 'BiasCorrection' a logical scalar (default true ) that applies the small-sample bias correction to the 'fmcd' estimate. 'NumOGKIterations' the number of orthogonalization iterations for 'ogk' , a positive integer (default 2). 'UnivariateEstimator' the robust univariate location/scale estimator used by 'ogk' , either 'tauscale' (default) or 'qn' . Note on reproducibility. 'fmcd' draws random subsets, so its exact estimate depends on the random number generator and is not identical to MATLAB’s on data where the optimal subset is ambiguous; on well-separated data both converge to the same estimate. For 'fmcd' with 'BiasCorrection' enabled, the small-sample factor uses the published Pison-Van Aelst-Willems asymptotic formula, which differs from MATLAB’s tabulated simulation values by up to about 1.6% for very small samples. See also: mahal, cov, mad, dbscan # name: # type: sq_string # elements: 1 # length: 49 Robust multivariate covariance and mean estimate. statistics-release-1.9.2/inst/Anomaly_Detection/iforest.m000066400000000000000000000145111524624707500235550ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} iforest (@var{X}) ## @deftypefnx {statistics} {[@var{Mdl}, @var{tf}] =} iforest (@var{X}) ## @deftypefnx {statistics} {[@var{Mdl}, @var{tf}, @var{scores}] =} iforest (@var{X}) ## @deftypefnx {statistics} {[@dots{}] =} iforest (@dots{}, @var{name}, @var{value}) ## ## Detect anomalies with an isolation forest. ## ## @code{@var{Mdl} = iforest (@var{X})} fits an isolation forest to the ## @math{N}-by-@math{P} matrix @var{X}, whose rows are observations and columns ## are variables, and returns an @code{IsolationForest} object @var{Mdl}. ## ## @code{[@var{Mdl}, @var{tf}, @var{scores}] = iforest (@var{X})} also returns ## the @math{N}-by-1 logical vector @var{tf} flagging the anomalous observations ## and the @math{N}-by-1 vector @var{scores} of anomaly scores in the range ## @math{[0, 1]}. A higher score indicates an observation that is more easily ## isolated, and therefore more likely to be an anomaly. ## ## The score of an observation is @code{2^(-E[h] / c)}, where @math{E[h]} is its ## average path length over the isolation trees and @math{c} is the expected ## path length of an unsuccessful search in a binary tree of ## @var{NumObservationsPerLearner} nodes. Each tree is grown from a random ## subsample of the data by recursively splitting on a random variable at a ## random value, so anomalies, being easier to isolate, obtain shorter paths. ## ## Additional parameters can be specified by @qcode{Name-Value} pair arguments. ## ## @multitable @columnfractions 0.34 0.66 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'NumLearners'} @tab the number of isolation trees, a positive ## integer (default 100). ## ## @item @qcode{'NumObservationsPerLearner'} @tab the subsample size used to ## grow each tree, an integer in @math{[3, N]} (default @code{min (@var{N}, ## 256)}). ## ## @item @qcode{'ContaminationFraction'} @tab the assumed fraction of anomalies ## in @var{X}, a scalar in @math{[0, 1]} (default 0). It sets ## @code{@var{Mdl}.ScoreThreshold} to @code{quantile (@var{scores}, 1 - ## @var{ContaminationFraction})}; when it is 0 the threshold is the maximum ## score and no training observation is flagged. ## @end multitable ## ## Because the trees are grown from random subsamples and random splits, the ## scores depend on the state of the random number generator and are not ## reproducible across runs unless the generator is seeded. ## ## Use the @code{isanomaly} method of @var{Mdl} to detect anomalies in new data. ## ## @seealso{IsolationForest, isanomaly, lof, robustcov} ## @end deftypefn function [Mdl, tf, scores] = iforest (X, varargin) if (nargin < 1) error ("iforest: too few input arguments."); endif Mdl = IsolationForest (X, varargin{:}); tf = Mdl.tf_; scores = Mdl.scores_; endfunction %!demo %! ## Flag a handful of outliers around a Gaussian cluster. %! rng (42); %! X = [randn(200,2); 6 + randn(10,2)]; %! [Mdl, tf, scores] = iforest (X, "ContaminationFraction", 0.05); %! gscatter (X(:,1), X(:,2), tf); %! title ("iforest: inliers vs. flagged anomalies"); ## Well-separated outliers score higher and are flagged (RNG seeded) %!test %! rand ("state", 42); %! X = [randn(60,2)*0.3; 12 12; -11 10; 10 -12]; %! [Mdl, tf, scores] = iforest (X, "ContaminationFraction", 3/63); %! assert_equal (Mdl.NumLearners, 100); %! assert_equal (Mdl.NumObservationsPerLearner, 63); %! assert_equal (all (scores >= 0 & scores <= 1, 'all'), true); %! # outliers rank highest %! assert_equal (all (scores(61:63) > max (scores(1:60)), 'all'), true); %! assert_equal (tf, logical ([false(60,1); true(3,1)])); ## ScoreThreshold follows the quantile rule; contamination 0 flags none %!test %! rand ("state", 7); %! X = [randn(50,2)*0.3; 9 9; -8 7]; %! [Mdl, tf, scores] = iforest (X); %! assert_equal (Mdl.ContaminationFraction, 0); %! assert_equal (Mdl.ScoreThreshold, max (scores), 1e-12); %! assert_equal (tf, false (52, 1)); %! [Mdl2, tf2, s2] = iforest (X, "ContaminationFraction", 0.1); %! assert_equal (Mdl2.ScoreThreshold, quantile (s2, 0.9), 1e-12); %! assert_equal (tf2, s2 > Mdl2.ScoreThreshold); ## NumObservationsPerLearner caps at 256 for large samples %!test %! rand ("state", 1); %! X = randn (400, 2); %! Mdl = iforest (X); %! assert_equal (Mdl.NumObservationsPerLearner, 256); ## isanomaly scores new observations against the trained forest %!test %! rand ("state", 3); %! X = [randn(60,2)*0.3; 12 12; -11 10; 10 -12]; %! Mdl = iforest (X, "ContaminationFraction", 3/63); %! [tf, scores] = isanomaly (Mdl, [0 0; 15 15]); %! assert_equal (all (scores >= 0 & scores <= 1, 'all'), true); %! assert_equal (scores(2) > scores(1), true); # far point scores higher %! assert_equal (tf, logical ([false; true])); ## Test input validation %!error iforest () %!error iforest ([]) %!error iforest ("a") %!error ... %! iforest (randn (10,2), "NumLearners") %!error ... %! iforest (randn (10,2), "foo", "bar") %!error ... %! iforest (randn (10,2), "NumLearners", 0) %!error ... %! iforest (randn (10,2), "NumObservationsPerLearner", 2) %!error ... %! iforest (randn (10,2), "NumObservationsPerLearner", 20) %!error ... %! iforest (randn (10,2), "ContaminationFraction", 2) statistics-release-1.9.2/inst/Anomaly_Detection/lof.m000066400000000000000000000162131524624707500226630ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} lof (@var{X}) ## @deftypefnx {statistics} {[@var{Mdl}, @var{tf}] =} lof (@var{X}) ## @deftypefnx {statistics} {[@var{Mdl}, @var{tf}, @var{scores}] =} lof (@var{X}) ## @deftypefnx {statistics} {[@dots{}] =} lof (@dots{}, @var{name}, @var{value}) ## ## Detect anomalies with the Local Outlier Factor (LOF) method. ## ## @code{@var{Mdl} = lof (@var{X})} fits a Local Outlier Factor model to the ## @math{N}-by-@math{P} matrix @var{X}, whose rows are observations and columns ## are variables, and returns a @code{LocalOutlierFactor} object @var{Mdl}. ## ## @code{[@var{Mdl}, @var{tf}, @var{scores}] = lof (@var{X})} also returns the ## @math{N}-by-1 logical vector @var{tf} flagging the anomalous observations and ## the @math{N}-by-1 vector @var{scores} of LOF values. A score near 1 ## indicates an inlier, whereas a score well above 1 indicates an outlier lying ## in a region sparser than its neighbors. ## ## The Local Outlier Factor of an observation is the average ratio of the local ## reachability density of its @var{NumNeighbors} nearest neighbors to its own ## local reachability density, where the local reachability density is the ## inverse mean reachability distance to those neighbors and the reachability ## distance from @math{p} to @math{o} is @code{max (k-distance (o), d (p, o))}. ## ## Additional parameters can be specified by @qcode{Name-Value} pair arguments. ## ## @multitable @columnfractions 0.28 0.72 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'NumNeighbors'} @tab the number of nearest neighbors, a positive ## integer less than @math{N}. The default is @code{min (20, @var{u} - 1)}, ## where @var{u} is the number of unique observations. ## ## @item @qcode{'Distance'} @tab the distance metric used to find neighbors, one ## of the metrics accepted by @code{pdist2} (@qcode{'euclidean'} by default). ## ## @item @qcode{'ContaminationFraction'} @tab the assumed fraction of anomalies ## in @var{X}, a scalar in @math{[0, 1]} (default 0). It sets ## @code{@var{Mdl}.ScoreThreshold} to @code{quantile (@var{scores}, 1 - ## @var{ContaminationFraction})}; when it is 0 the threshold is the maximum ## score and no training observation is flagged. ## ## @item @qcode{'Exponent'} @tab the Minkowski distance exponent (default 2), ## used only with the @qcode{'minkowski'} distance. ## ## @item @qcode{'Cov'} @tab the covariance matrix used only with the ## @qcode{'mahalanobis'} distance. ## @end multitable ## ## Use the @code{isanomaly} method of @var{Mdl} to detect anomalies in new data. ## ## @seealso{LocalOutlierFactor, isanomaly, dbscan, robustcov} ## @end deftypefn function [Mdl, tf, scores] = lof (X, varargin) if (nargin < 1) error ("lof: too few input arguments."); endif Mdl = LocalOutlierFactor (X, varargin{:}); tf = Mdl.tf_; scores = Mdl.scores_; endfunction %!demo %! ## Flag a handful of outliers around a Gaussian cluster. %! rng (42); %! X = [randn(100,2); 4 + randn(6,2)]; %! [Mdl, tf, scores] = lof (X, "ContaminationFraction", 0.05); %! gscatter (X(:,1), X(:,2), tf); %! title ("lof: inliers vs. flagged anomalies"); ## MATLAB parity: LOF scores are exact to machine precision (k = 5) %!test %! X = [0 0; 0.1 0.1; 0.2 -0.1; -0.1 0.2; 0.1 -0.2; -0.2 0.1; 0.15 0.05; ... %! -0.05 -0.15; 0.05 0.12; -0.12 -0.05; 5 5; -4 3]; %! [Mdl, tf, scores] = lof (X, "NumNeighbors", 5); %! exp_scores = [1.066572826392391; 0.965333187918473; 1.035972400143920; ... %! 1.019798243339781; 1.055271577857807; 1.047371774849485; ... %! 0.941746286231341; 0.987111513710991; 0.948358372496933; ... %! 1.004238203765983; 29.441894930160952; 20.058918161335612]; %! assert_equal (scores, exp_scores, 1e-12); %! assert_equal (tf, false (12, 1)); # contamination 0 flags none %! assert_equal (Mdl.ScoreThreshold, 29.441894930160952, 1e-12); ## MATLAB parity: ContaminationFraction sets the quantile threshold and flags %!test %! X = [0 0; 0.1 0.1; 0.2 -0.1; -0.1 0.2; 0.1 -0.2; -0.2 0.1; 0.15 0.05; ... %! -0.05 -0.15; 0.05 0.12; -0.12 -0.05; 5 5; -4 3]; %! [Mdl, tf, scores] = lof (X, "NumNeighbors", 5, "ContaminationFraction", 0.2); %! assert_equal (Mdl.ScoreThreshold, 2.965807359886740, 1e-12); %! assert_equal (tf, logical ([0;0;0;0;0;0;0;0;0;0;1;1])); ## MATLAB parity: default NumNeighbors is min (20, unique-1) %!test %! X = [0 0; 0.1 0.1; 0.2 -0.1; -0.1 0.2; 0.1 -0.2; -0.2 0.1; 0.15 0.05; ... %! -0.05 -0.15; 0.05 0.12; -0.12 -0.05; 5 5; -4 3]; %! Mdl = lof (X); %! assert_equal (Mdl.NumNeighbors, 11); ## MATLAB parity: isanomaly reproduces LOF for new observations %!test %! X = [0 0; 0.1 0.1; 0.2 -0.1; -0.1 0.2; 0.1 -0.2; -0.2 0.1; 0.15 0.05; ... %! -0.05 -0.15; 0.05 0.12; -0.12 -0.05; 5 5; -4 3]; %! Mdl = lof (X, "NumNeighbors", 5); %! [tf, scores] = isanomaly (Mdl, [0 0.05; 6 6; -0.1 -0.1]); %! assert_equal (scores, [0.954484172585537; 27.876003442184082; ... %! 1.001784195086020], 1e-12); %! assert_equal (tf, logical ([0; 0; 0])); # cutoff 29.44 from training %! [tf2, ~] = isanomaly (Mdl, [6 6], "ScoreThreshold", 5); %! assert_equal (tf2, true); ## A non-euclidean metric is passed through to pdist2 %!test %! X = [0 0; 0.1 0.1; 0.2 -0.1; -0.1 0.2; 0.1 -0.2; -0.2 0.1; 0.15 0.05; ... %! -0.05 -0.15; 0.05 0.12; -0.12 -0.05; 5 5; -4 3]; %! [~, ~, scores] = lof (X, "NumNeighbors", 5, "Distance", "cityblock"); %! assert_equal (scores(11), 34.198447151536712, 1e-10); %! assert_equal (scores(1) > 1 && scores(7) < 1, true); ## Test input validation %!error lof () %!error lof ([]) %!error lof ("a") %!error lof (ones (5,2), "Distance") %!error lof (ones (5,2), "foo", "bar") %!error ... %! lof (ones (5,2), "Distance", "taxicab") %!error ... %! lof (ones (5,2), "NumNeighbors", 0) %!error ... %! lof (ones (5,2), "NumNeighbors", 5) %!error ... %! lof (magic (5), "NumNeighbors", 2, "ContaminationFraction", 1.5) %!error ... %! isanomaly (lof (magic (6)), ones (3,3)) statistics-release-1.9.2/inst/Anomaly_Detection/ocsvm.m000066400000000000000000000170711524624707500232350ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} ocsvm (@var{X}) ## @deftypefnx {statistics} {[@var{Mdl}, @var{tf}] =} ocsvm (@var{X}) ## @deftypefnx {statistics} {[@var{Mdl}, @var{tf}, @var{scores}] =} ocsvm (@var{X}) ## @deftypefnx {statistics} {[@dots{}] =} ocsvm (@dots{}, @var{name}, @var{value}) ## ## Detect anomalies with a one-class support vector machine. ## ## @code{@var{Mdl} = ocsvm (@var{X})} fits a one-class support vector machine to ## the @math{N}-by-@math{P} matrix @var{X}, whose rows are observations and ## columns are variables, and returns a @code{OneClassSVM} object @var{Mdl}. ## ## @code{[@var{Mdl}, @var{tf}, @var{scores}] = ocsvm (@var{X})} also returns the ## @math{N}-by-1 logical vector @var{tf} flagging the anomalous observations and ## the @math{N}-by-1 vector @var{scores} of anomaly scores. A higher score ## indicates an observation that lies further outside the boundary enclosing the ## data, and is therefore more likely to be an anomaly. ## ## The observations are mapped to a randomized feature space that approximates a ## Gaussian kernel of scale @var{KernelScale} using @var{NumExpansionDimensions} ## features, and a linear one-class boundary is fitted there with ridge ## regularization of strength @var{Lambda}. ## ## Additional parameters can be specified by @qcode{Name-Value} pair arguments. ## ## @multitable @columnfractions 0.34 0.66 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'KernelScale'} @tab the scale of the approximated Gaussian ## kernel, a positive scalar or @qcode{'auto'} (default). ## ## @item @qcode{'Lambda'} @tab the ridge regularization strength, a nonnegative ## scalar or @qcode{'auto'} (default). ## ## @item @qcode{'NumExpansionDimensions'} @tab the number of expanded feature ## dimensions, a positive integer or @qcode{'auto'} (default). ## ## @item @qcode{'StandardizeData'} @tab a logical scalar (default ## @code{false}); when @code{true} each predictor is centered and scaled and the ## means and standard deviations are stored in @code{@var{Mdl}.Mu} and ## @code{@var{Mdl}.Sigma}. ## ## @item @qcode{'ContaminationFraction'} @tab the assumed fraction of anomalies ## in @var{X}, a scalar in @math{[0, 1]} (default 0). It sets ## @code{@var{Mdl}.ScoreThreshold} to @code{quantile (@var{scores}, 1 - ## @var{ContaminationFraction})}; when it is 0 the threshold is the maximum ## score and no training observation is flagged. ## @end multitable ## ## The feature expansion uses random projections, so the scores depend on the ## state of the random number generator and are not reproducible across runs ## unless the generator is seeded. The @qcode{'auto'} selections and the fitted ## model differ from MATLAB's implementation, which uses a different feature ## expansion and solver. ## ## For a deterministic, classic one-class support vector machine (a ## nu-SVM with an exact kernel, computed through @code{libsvm}), use ## @code{fitcsvm} with a single class in the response or with the @qcode{'Nu'} ## name-value argument; that path returns a @code{ClassificationSVM} object ## whose @code{predict} method labels observations, rather than the ## anomaly-scoring interface provided here. ## ## Use the @code{isanomaly} method of @var{Mdl} to detect anomalies in new data. ## ## @seealso{OneClassSVM, isanomaly, iforest, lof, fitcsvm} ## @end deftypefn function [Mdl, tf, scores] = ocsvm (X, varargin) if (nargin < 1) error ("ocsvm: too few input arguments."); endif Mdl = OneClassSVM (X, varargin{:}); tf = Mdl.tf_; scores = Mdl.scores_; endfunction %!demo %! ## Flag a handful of outliers around a Gaussian cluster. %! rng (42); %! X = [randn(200,2); 5 + randn(10,2)]; %! [Mdl, tf, scores] = ocsvm (X, "KernelScale", 2, ... %! "ContaminationFraction", 0.05); %! gscatter (X(:,1), X(:,2), tf); %! title ("ocsvm: inliers vs. flagged anomalies"); ## Well-separated outliers score higher and are flagged (RNG seeded) %!test %! rand ("state", 42); %! randn ("state", 42); %! X = [randn(60,2)*0.3; 10 10; -9 8; 8 -10]; %! [Mdl, tf, scores] = ocsvm (X, "KernelScale", 2, ... %! "NumExpansionDimensions", 128, ... %! "ContaminationFraction", 3/63); %! # outliers rank highest %! assert_equal (all (scores(61:63) > max (scores(1:60)), 'all'), true); %! assert_equal (tf, logical ([false(60,1); true(3,1)])); ## ScoreThreshold follows the quantile rule; contamination 0 flags none %!test %! rand ("state", 7); %! randn ("state", 7); %! X = [randn(50,2)*0.3; 9 9; -8 7]; %! [Mdl, tf, scores] = ocsvm (X, "KernelScale", 2); %! assert_equal (Mdl.ContaminationFraction, 0); %! assert_equal (Mdl.ScoreThreshold, max (scores), 1e-12); %! assert_equal (tf, false (52, 1)); %! [Mdl2, tf2, s2] = ocsvm (X, "KernelScale", 2, "ContaminationFraction", 0.1); %! assert_equal (Mdl2.ScoreThreshold, quantile (s2, 0.9), 1e-12); %! assert_equal (tf2, s2 > Mdl2.ScoreThreshold); ## 'auto' defaults are resolved to concrete numeric values %!test %! rand ("state", 1); %! randn ("state", 1); %! X = randn (64, 3); %! Mdl = ocsvm (X); %! assert_equal (Mdl.KernelScale, 1); %! assert_equal (Mdl.Lambda, 1/64, 1e-12); %! assert_equal (Mdl.NumExpansionDimensions, 64); # 2^ceil(log2(64)) ## StandardizeData stores Mu and Sigma and isanomaly reuses them %!test %! rand ("state", 3); %! randn ("state", 3); %! X = [randn(60,2)*[3 0; 0 0.2] + [5 -2]; 20 5]; %! Mdl = ocsvm (X, "KernelScale", 2, "StandardizeData", true); %! assert_equal (numel (Mdl.Mu), 2); %! assert_equal (numel (Mdl.Sigma), 2); %! [tf, scores] = isanomaly (Mdl, [5 -2; 20 5]); %! assert_equal (scores(2) > scores(1), true); # the outlier scores higher ## isanomaly scores new observations against the trained model %!test %! rand ("state", 5); %! randn ("state", 5); %! X = [randn(60,2)*0.3; 10 10; -9 8]; %! Mdl = ocsvm (X, "KernelScale", 2, "ContaminationFraction", 2/62); %! [tf, scores] = isanomaly (Mdl, [0 0; 15 15]); %! assert_equal (scores(2) > scores(1), true); %! assert_equal (tf, logical ([false; true])); ## Test input validation %!error ocsvm () %!error ocsvm ([]) %!error ocsvm ("a") %!error ... %! ocsvm (randn (10,2), "KernelScale") %!error ... %! ocsvm (randn (10,2), "foo", "bar") %!error ... %! ocsvm (randn (10,2), "KernelScale", -1) %!error ... %! ocsvm (randn (10,2), "Lambda", -1) %!error ... %! ocsvm (randn (10,2), "NumExpansionDimensions", 0) %!error ... %! ocsvm (randn (10,2), "ContaminationFraction", 2) statistics-release-1.9.2/inst/Anomaly_Detection/robustcov.m000066400000000000000000000445171524624707500241410ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{sig} =} robustcov (@var{X}) ## @deftypefnx {statistics} {[@var{sig}, @var{mu}] =} robustcov (@var{X}) ## @deftypefnx {statistics} {[@var{sig}, @var{mu}, @var{mah}] =} robustcov (@var{X}) ## @deftypefnx {statistics} {[@var{sig}, @var{mu}, @var{mah}, @var{outliers}] =} robustcov (@var{X}) ## @deftypefnx {statistics} {[@var{sig}, @var{mu}, @var{mah}, @var{outliers}, @var{s}] =} robustcov (@var{X}) ## @deftypefnx {statistics} {[@dots{}] =} robustcov (@dots{}, @var{name}, @var{value}) ## ## Robust multivariate covariance and mean estimate. ## ## @code{@var{sig} = robustcov (@var{X})} returns a robust estimate @var{sig} of ## the covariance matrix of the @math{N*P} data matrix @var{X}, computed so that ## it is not distorted by outlying observations. Rows of @var{X} are ## observations and columns are variables. Rows containing @code{NaN} values ## are removed. ## ## @code{[@var{sig}, @var{mu}, @var{mah}, @var{outliers}, @var{s}] = robustcov ## (@dots{})} also returns the robust mean @var{mu} (@math{1*P}), the robust ## Mahalanobis distances @var{mah} (@math{N*1}) of each observation from the ## estimated distribution, a logical vector @var{outliers} (@math{N*1}) flagging ## observations whose distance exceeds @code{sqrt (chi2inv (0.975, @var{P}))}, ## and a structure @var{s} holding the estimate metadata. ## ## Additional parameters can be specified by @qcode{Name-Value} pair arguments. ## ## @multitable @columnfractions 0.22 0.76 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Method'} @tab the estimator, either @qcode{'fmcd'} (default, ## the Fast Minimum Covariance Determinant algorithm) or @qcode{'ogk'} (the ## Orthogonalized Gnanadesikan-Kettenring estimator). @qcode{'olivehawkins'} is ## not implemented. ## ## @item @qcode{'OutlierFraction'} @tab the maximum fraction of outliers, a ## scalar in @math{[0, 0.5]} (default 0.5), used to set the size of the ## elemental subsets in @qcode{'fmcd'}. ## ## @item @qcode{'NumTrials'} @tab the number of random elemental subsets drawn ## by @qcode{'fmcd'}, a positive integer (default 500). ## ## @item @qcode{'BiasCorrection'} @tab a logical scalar (default @code{true}) ## that applies the small-sample bias correction to the @qcode{'fmcd'} estimate. ## ## @item @qcode{'NumOGKIterations'} @tab the number of orthogonalization ## iterations for @qcode{'ogk'}, a positive integer (default 2). ## ## @item @qcode{'UnivariateEstimator'} @tab the robust univariate ## location/scale estimator used by @qcode{'ogk'}, either @qcode{'tauscale'} ## (default) or @qcode{'qn'}. ## @end multitable ## ## @strong{Note on reproducibility.} @qcode{'fmcd'} draws random subsets, so its ## exact estimate depends on the random number generator and is not identical to ## MATLAB's on data where the optimal subset is ambiguous; on well-separated ## data both converge to the same estimate. For @qcode{'fmcd'} with ## @qcode{'BiasCorrection'} enabled, the small-sample factor uses the published ## Pison-Van Aelst-Willems asymptotic formula, which differs from MATLAB's ## tabulated simulation values by up to about 1.6% for very small samples. ## ## @seealso{mahal, cov, mad, dbscan} ## @end deftypefn function [sig, mu, mah, outliers, s] = robustcov (X, varargin) ## Check number of input arguments if (nargin < 1) error ("robustcov: too few input arguments."); endif ## Validate X and drop rows with NaN values if (! isnumeric (X) || ! isreal (X) || ndims (X) != 2 || isempty (X)) error ("robustcov: X must be a nonempty real numeric matrix."); endif X = X(! any (isnan (X), 2), :); [n, p] = size (X); ## Defaults method = "fmcd"; outlierfrac = 0.5; numtrials = 500; biascorrection = true; numogkiter = 2; univestimator = "tauscale"; ## Parse Name-Value pairs if (mod (numel (varargin), 2) != 0) error ("robustcov: each NAME must be followed by a VALUE."); endif while (numel (varargin) > 0) name = varargin{1}; val = varargin{2}; if (! ischar (name)) error ("robustcov: optional argument names must be strings."); endif switch (tolower (name)) case "method" method = tolower (val); case "outlierfraction" outlierfrac = val; case "numtrials" numtrials = val; case "biascorrection" biascorrection = logical (val); case "numogkiterations" numogkiter = val; case "univariateestimator" univestimator = tolower (val); otherwise error ("robustcov: unknown parameter name '%s'.", name); endswitch varargin(1:2) = []; endwhile ## Validate options if (strcmp (method, "olivehawkins")) error ("robustcov: the 'olivehawkins' method is not implemented."); endif if (! any (strcmp (method, {"fmcd", "ogk"}))) error ("robustcov: METHOD must be 'fmcd' or 'ogk'."); endif if (! isscalar (outlierfrac) || ! isnumeric (outlierfrac) || outlierfrac < 0 || outlierfrac > 0.5) error ("robustcov: OUTLIERFRACTION must be a scalar in [0, 0.5]."); endif if (! any (strcmp (univestimator, {"tauscale", "qn"}))) error ("robustcov: UNIVARIATEESTIMATOR must be 'tauscale' or 'qn'."); endif if (n <= p) error ("robustcov: X must have more rows than columns."); endif ## Robust cutoff for reweighting and for flagging outliers. cutoff = chi2inv (0.975, p); if (strcmp (method, "fmcd")) ## Raw Fast-MCD subset and its robust distances. h = floor (2 * floor ((n + p + 1) / 2) - n ... + 2 * (n - floor ((n + p + 1) / 2)) * (1 - outlierfrac)); h = max (min (h, n), p + 1); [Traw, Sraw] = fastmcd (X, h, numtrials); Sraw = mcdcons (p, h / n) * mcdcnp2raw (p, n) * Sraw; ## Reweighting: keep observations within the 0.975 cutoff. d2 = mahal2 (X, Traw, Sraw); keep = d2 <= cutoff; mu = mean (X(keep, :)); C = cov (X(keep, :)); hk = sum (keep); fac = mcdcons (p, hk / n); if (biascorrection) fac *= mcdcnp2rew (p, n); endif sig = fac * C; else ## Orthogonalized Gnanadesikan-Kettenring, then MCD-style reweighting. [Togk, Sogk] = ogk (X, numogkiter, univestimator); d2 = mahal2 (X, Togk, Sogk); ## Scale so that the median squared distance matches the chi-square median, ## making the reweighting cutoff meaningful. scale = median (d2) / chi2inv (0.5, p); d2 = d2 / scale; keep = d2 <= cutoff; mu = mean (X(keep, :)); sig = cov (X(keep, :), 1); # 1/N normalization, as MATLAB's OGK endif ## Final robust Mahalanobis distances and outlier flags. md2 = mahal2 (X, mu, sig); mah = sqrt (md2); outliers = md2 > cutoff; if (nargout > 4) if (strcmp (method, "fmcd")) s = struct ("BiasCorrection", biascorrection, "NumTrials", numtrials, ... "OutlierFraction", outlierfrac, "Mu", mu, "Sigma", sig, ... "Method", "fmcd", "Distances", mah, "Outliers", outliers); else s = struct ("NumOGKIterations", numogkiter, ... "UnivariateScale", univestimator, "Mu", mu, "Sigma", sig, ... "Method", "ogk", "Distances", mah, "Outliers", outliers); endif endif endfunction ## Squared Mahalanobis distances of the rows of X to (T, S). function d2 = mahal2 (X, T, S) R = chol (S); Z = (X - T) / R; d2 = sum (Z .^ 2, 2); endfunction ## One MCD concentration step (C-step): keep the h closest points, recompute. function [T, S, H, dt] = cstep (X, T, S, h) d2 = mahal2 (X, T, S); [~, ord] = sort (d2); H = ord(1:h); T = mean (X(H, :)); S = cov (X(H, :)); dt = det (S); endfunction ## Fast-MCD raw location and scatter of the best h-subset. function [Tbest, Sbest] = fastmcd (X, h, nsamp) [n, p] = size (X); bestdet = Inf; Tbest = mean (X); Sbest = cov (X); ## Candidate starts: random elemental subsets plus the classical estimate. cand = cell (nsamp + 1, 1); for t = 1:nsamp idx = randperm (n, p + 1); T = mean (X(idx, :)); S = cov (X(idx, :)); k = p + 1; while (rcond (S) < 1e-12 && k < h) k += 1; idx = randperm (n, k); T = mean (X(idx, :)); S = cov (X(idx, :)); endwhile cand{t} = {T, S}; endfor cand{nsamp + 1} = {mean(X), cov(X)}; ## Two C-steps from each start, then keep the best few and iterate them. dets = inf (nsamp + 1, 1); sols = cell (nsamp + 1, 1); for t = 1:(nsamp + 1) T = cand{t}{1}; S = cand{t}{2}; if (rcond (S) < 1e-12) continue; endif [T, S] = cstep (X, T, S, h); [T, S, H, dt] = cstep (X, T, S, h); dets(t) = dt; sols{t} = {T, S}; endfor [~, ord] = sort (dets); nbest = min (10, sum (isfinite (dets))); for j = 1:nbest t = ord(j); T = sols{t}{1}; S = sols{t}{2}; dt_prev = Inf; for it = 1:50 [T, S, H, dt] = cstep (X, T, S, h); if (dt >= dt_prev * (1 - 1e-12)) break; endif dt_prev = dt; endfor if (dt < bestdet) bestdet = dt; Tbest = T; Sbest = S; endif endfor endfunction ## MCD consistency factor c = alpha / F_{chi2_{p+2}}(chi2inv(alpha, p)). function c = mcdcons (p, alpha) if (alpha >= 1) c = 1; else c = alpha / chi2cdf (chi2inv (alpha, p), p + 2); endif endfunction ## Raw MCD small-sample correction factor (Pison et al.), alpha = 0.5. function f = mcdcnp2raw (p, n) if (p == 1) fp = 1 - exp (0.262024211897096) / n ^ 0.604756680630497; elseif (p == 2) fp = 1 - exp (0.673292623522027) / n ^ 0.691365864961895; else coeff = [-1.42764571687802, 1.26263336932151, 2; ... -1.06141115981725, 1.28907991440387, 3]; fp = mcdcnp2_solve (coeff, p, n); endif f = 1 / fp; endfunction ## Reweighted MCD small-sample correction factor (Pison et al.), alpha = 0.5. function f = mcdcnp2rew (p, n) if (p == 1) fp = 1 - exp (1.11098143415027) / n ^ 1.5182890270453; elseif (p == 2) fp = 1 - exp (3.11101712909049) / n ^ 1.91401056721863; else coeff = [-1.02842572724793, 1.67659883081926, 2; ... -0.26800273450853, 1.35968562893582, 3]; fp = mcdcnp2_solve (coeff, p, n); endif f = 1 / fp; endfunction ## Shared p > 2 interpolation for the Pison correction coefficients. Each row ## of COEFF is [coefficient, exponent, reference-dimension] for the two anchor ## dimensions (p = 2 and p = 3), following the robustbase covMcd formulas. function fp = mcdcnp2_solve (coeff, p, n) a = coeff(:, 1); b = coeff(:, 2); dim = coeff(:, 3); y = log (- a ./ p .^ b); A = [1, - log(dim(1) * p ^ 2); 1, - log(dim(2) * p ^ 2)]; c = A \ y; fp = 1 - exp (c(1)) / n ^ c(2); endfunction ## Orthogonalized Gnanadesikan-Kettenring robust location and scatter. ## Maintains the affine invariant x = A * z + centre where z is the current ## (rotated) coordinate of an observation, so the final diagonal scatter and ## location in z-space map straight back to the original coordinates. function [T, C] = ogk (X, niter, estimator) [n, p] = size (X); Z = X; A = eye (p); centre = zeros (p, 1); for iter = 1:niter ## Robust marginal location/scale of the current coordinates. m = zeros (p, 1); d = zeros (1, p); for j = 1:p [d(j), m(j)] = uniscale (Z(:, j), estimator); endfor d(d == 0) = 1; Y = (Z - m') ./ d; # standardized coordinates ## Pairwise robust covariance (Gnanadesikan-Kettenring). U = eye (p); for j = 1:p for k = (j + 1):p sp = uniscale (Y(:, j) + Y(:, k), estimator); sm = uniscale (Y(:, j) - Y(:, k), estimator); U(j, k) = 0.25 * (sp ^ 2 - sm ^ 2); U(k, j) = U(j, k); endfor endfor ## Orthogonalizing rotation E; update the affine map and rotate the data. [E, ~] = eig ((U + U') / 2); centre = A * m + centre; # uses the pre-update A A = A * diag (d) * E; Z = Y * E; endfor ## Robust location/scale in the final rotated coordinates map straight back. gm = zeros (p, 1); gs = zeros (1, p); for j = 1:p [gs(j), gm(j)] = uniscale (Z(:, j), estimator); endfor T = (A * gm + centre)'; C = A * diag (gs .^ 2) * A'; C = (C + C') / 2; endfunction ## Robust univariate scale (and location) via tau-scale or Qn. function [s, m] = uniscale (x, estimator) x = x(:); med = median (x); s0 = 1.4826 * median (abs (x - med)); if (s0 == 0) s = 0; m = med; return; endif if (strcmp (estimator, "qn")) m = med; s = qn (x); else ## Tau-scale (Maronna & Zamar): bisquare-weighted mean, bounded rho scale. c1 = 4.5; r = (x - med) / s0; w = (1 - (r / c1) .^ 2) .^ 2; w(abs (r) > c1) = 0; m = sum (w .* x) / sum (w); c2 = 3.0; r2 = (x - m) / s0; rho = min (r2 .^ 2, c2 ^ 2); s = s0 * sqrt (mean (rho)); endif endfunction ## Qn robust scale estimator (Croux & Rousseeuw). function s = qn (x) x = sort (x(:)); n = numel (x); diffs = []; for i = 1:(n - 1) diffs = [diffs; abs (x((i + 1):n) - x(i))]; endfor diffs = sort (diffs); h = floor (n / 2) + 1; k = h * (h - 1) / 2; if (k < 1) k = 1; endif s = 2.2219 * diffs(k); endfunction %!demo %! ## Robust covariance is unaffected by a cluster of outliers. %! rng (42); %! X = [randn(80,2); 8 + randn(10,2)]; %! [sig, mu, mah, outliers] = robustcov (X); %! gscatter (X(:,1), X(:,2), outliers); %! title ("robustcov: inliers vs. flagged outliers"); ## FMCD without bias correction is bit-exact: consistency factor c(p, |J|/n) %!test %! X = [0.1 0.2; -0.3 0.5; 0.4 -0.1; 0.2 0.3; -0.2 -0.4; 0.5 0.1; -0.1 0.2; ... %! 0.3 -0.3; -0.4 0.1; 0.2 -0.2; 0.1 0.4; -0.3 -0.2; 5 5; -6 4]; %! [sig, mu, mah, ol] = robustcov (X, "BiasCorrection", 0); %! assert_equal (mu, [0.041666666666667 0.05], 1e-12); %! assert_equal (sig, [0.130395818314974 -0.012781705320642; ... %! -0.012781705320642 0.122435282545100], 1e-9); %! assert_equal (ol, logical ([0;0;0;0;0;0;0;0;0;0;0;0;1;1])); ## OGK is bit-exact on separated data: covariance of the retained set (1/N) %!test %! X = [0.1 0.2; -0.3 0.5; 0.4 -0.1; 0.2 0.3; -0.2 -0.4; 0.5 0.1; -0.1 0.2; ... %! 0.3 -0.3; -0.4 0.1; 0.2 -0.2; 0.1 0.4; -0.3 -0.2; 5 5; -6 4]; %! [sig, mu, mah, ol] = robustcov (X, "Method", "ogk"); %! assert_equal (mu, [0.041666666666667 0.05], 1e-12); %! assert_equal (sig, [0.080763888888889 -0.007916666666667; ... %! -0.007916666666667 0.075833333333333], 1e-9); %! assert_equal (ol, logical ([0;0;0;0;0;0;0;0;0;0;0;0;1;1])); ## FMCD with default bias correction inflates the covariance (formula factor) %!test %! X = [0.1 0.2; -0.3 0.5; 0.4 -0.1; 0.2 0.3; -0.2 -0.4; 0.5 0.1; -0.1 0.2; ... %! 0.3 -0.3; -0.4 0.1; 0.2 -0.2; 0.1 0.4; -0.3 -0.2; 5 5; -6 4]; %! sig0 = robustcov (X, "BiasCorrection", 0); %! sig1 = robustcov (X); %! r = sig1 ./ sig0; %! # scalar inflation factor %! assert_equal (all (abs (r - r(1)) < 1e-12, 'all'), true); %! assert_equal (r(1) > 1, true); ## mah is the robust Mahalanobis distance, outliers use the 0.975 cutoff %!test %! X = [0.1 0.2; -0.3 0.5; 0.4 -0.1; 0.2 0.3; -0.2 -0.4; 0.5 0.1; -0.1 0.2; ... %! 0.3 -0.3; -0.4 0.1; 0.2 -0.2; 0.1 0.4; -0.3 -0.2; 5 5; -6 4]; %! [sig, mu, mah, ol] = robustcov (X); %! d2 = sum (((X - mu) / chol (sig)) .^ 2, 2); %! assert_equal (mah, sqrt (d2), 1e-12); %! assert_equal (ol, mah > sqrt (chi2inv (0.975, 2))); ## The metadata structure carries the expected fields %!test %! X = [randn(40,2); 10 10; -10 8]; %! [sig, mu, mah, ol, s] = robustcov (X); %! assert_equal (isfield (s, "Method") && strcmp (s.Method, "fmcd"), true); %! assert_equal (isfield (s, "Sigma") && isfield (s, "Mu"), true); %! assert_equal (isfield (s, "Distances") && isfield (s, "Outliers"), true); %! [~, ~, ~, ~, s2] = robustcov (X, "Method", "ogk"); %! assert_equal (strcmp (s2.Method, "ogk"), true); %! assert_equal (isfield (s2, "NumOGKIterations"), true); ## OGK is bit-exact in 3-D: cube vertices have identity covariance (1/N) %!test %! X = [1 1 1; 1 1 -1; 1 -1 1; 1 -1 -1; -1 1 1; -1 1 -1; -1 -1 1; -1 -1 -1; ... %! 10 10 10; -10 8 -9]; %! [sig, mu, mah, ol] = robustcov (X, "Method", "ogk"); %! assert_equal (sig, eye (3), 1e-12); %! assert_equal (mu, [0 0 0], 1e-12); %! assert_equal (ol, logical ([0;0;0;0;0;0;0;0;1;1])); ## FMCD in 3-D returns a symmetric positive-definite estimate (p > 2 path) %!test %! X = [1.2 0.4 2.1; 0.8 1.1 1.9; 1.5 0.9 2.3; 0.9 0.7 1.8; 1.1 1.3 2.0; ... %! 1.4 0.6 2.2; 0.7 1.0 1.7; 1.3 0.8 2.4; 1.0 1.2 1.6; 0.7 0.9 2.0; ... %! 15 16 17; -9 12 -10]; %! [sig, mu, mah, ol] = robustcov (X); %! assert_equal (size (sig), [3 3]); %! assert_equal (sig, sig', 1e-12); %! assert_equal (all (eig (sig) > 0, 'all'), true); %! assert_equal (ol(11:12), logical ([1;1])); ## Rows with NaN values are dropped before estimation %!test %! X = [randn(30,2); NaN 1; 2 NaN]; %! [sig, mu, mah] = robustcov (X); %! assert_equal (numel (mah), 30); ## Test input validation %!error robustcov () %!error robustcov ([]) %!error robustcov ("a") %!error ... %! robustcov (ones (5,2), "Method") %!error ... %! robustcov (ones (5,2), "foo", "bar") %!error ... %! robustcov (ones (5,2), "Method", "olivehawkins") %!error ... %! robustcov (ones (5,2), "Method", "bogus") %!error ... %! robustcov (ones (5,2), "OutlierFraction", 0.8) %!error ... %! robustcov (ones (5,2), "Method", "ogk", "UnivariateEstimator", "mad") %!error robustcov (ones (2,5)) statistics-release-1.9.2/inst/Clustering/000077500000000000000000000000001524624707500204435ustar00rootroot00000000000000statistics-release-1.9.2/inst/Clustering/CalinskiHarabaszEvaluation.m000066400000000000000000000273431524624707500260730ustar00rootroot00000000000000## Copyright (C) 2021 Stefano Guidoni ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef CalinskiHarabaszEvaluation < ClusterCriterion ## -*- texinfo -*- ## @deftp {statistics} CalinskiHarabaszEvaluation ## ## Calinski-Harabasz clustering evaluation. ## ## A @code{CalinskiHarabaszEvaluation} object contains the results of ## evaluating clustering solutions using the Calinski-Harabasz criterion. ## ## The Calinski-Harabasz index (also known as the Variance Ratio Criterion) is ## determined by the ratio of the between-cluster sum of squares (SSB) to the ## within-cluster sum of squares (SSW). A higher Calinski-Harabasz index ## value indicates a better clustering solution, implying that clusters are ## dense and well-separated. ## ## Create a @code{CalinskiHarabaszEvaluation} object by using the ## @code{evalclusters} function with the @qcode{'CalinskiHarabasz'} criterion. ## ## @seealso{evalclusters, ClusterCriterion, DaviesBouldinEvaluation, ## GapEvaluation, SilhouetteEvaluation} ## @end deftp properties(Access = protected) Centroids = {}; # a list of the centroids for every solution endproperties methods(Access = public) ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} CalinskiHarabaszEvaluation (@var{x}, @var{clust}, @var{KList}) ## ## Construct a @code{CalinskiHarabaszEvaluation} object to evaluate ## clustering solutions with the Calinski-Harabasz criterion. ## ## @code{@var{obj} = CalinskiHarabaszEvaluation (@var{x}, @var{clust}, ## @var{KList})} clusters the data in @var{x} for every cluster count in ## @var{KList} and evaluates each solution. The evaluation runs at ## construction, so @var{obj} arrives with its @code{CriterionValues} and ## @code{OptimalK} already set. ## ## @itemize ## @item @var{x} is an @math{N*P} numeric matrix of observations (rows) ## and predictors (columns). A row holding a @qcode{NaN} is left out of ## @code{NumObservations}. ## @item @var{clust} names the clustering method, one of ## @qcode{'kmeans'}, @qcode{'linkage'} and @qcode{'gmdistribution'}; or a ## function handle that clusters the data; or an @math{N*M} numeric matrix ## of clustering solutions computed elsewhere, one column per cluster ## count, in which case @code{ClusteringFunction} is left empty. ## @item @var{KList} is a vector of positive integers, the cluster counts ## to inspect. ## @end itemize ## ## @code{evalclusters} is the usual way to create one of these objects. ## ## @end deftypefn function this = CalinskiHarabaszEvaluation (x, clust, KList) this@ClusterCriterion (x, clust, KList); this.CriterionName = 'CalinskiHarabasz'; this = this.evaluate (this.InspectedK); endfunction ## -*- texinfo -*- ## @deftypefn {CalinskiHarabaszEvaluation} {@var{obj} =} addK (@var{obj}, @var{K}) ## ## Add new cluster sizes for evaluation. ## ## @code{addK (@var{obj}, @var{K})} evaluates clustering solutions for the ## number of clusters specified in the vector @var{K} and adds them to the ## @code{CalinskiHarabaszEvaluation} object @var{obj}. ## ## @seealso{CalinskiHarabaszEvaluation, evalclusters} ## @end deftypefn function this = addK (this, K) this = addK@ClusterCriterion (this, K); ## if we have new data, we need a new evaluation if (this.OptimalK == 0) Centroids_tmp = {}; pS = 0; # position shift of the elements of Centroids for iter = 1 : length (this.InspectedK) ## reorganize Centroids according to the new list of cluster numbers if (any (this.InspectedK(iter) == K)) pS += 1; else Centroids_tmp{iter} = this.Centroids{iter - pS}; endif endfor this.Centroids = Centroids_tmp; this = this.evaluate (K); endif endfunction ## -*- texinfo -*- ## @deftypefn {CalinskiHarabaszEvaluation} {} plot (@var{obj}) ## @deftypefnx {CalinskiHarabaszEvaluation} {@var{h} =} plot (@var{obj}) ## ## Plot the clustering evaluation results. ## ## @code{plot (@var{obj})} plots the Calinski-Harabasz criterion values ## against the number of clusters. The optimal number of clusters is marked ## with an asterisk. ## ## @code{@var{h} = plot (@var{obj})} additionally returns the handle to the ## plot axes. ## ## @seealso{CalinskiHarabaszEvaluation, evalclusters} ## @end deftypefn function h = plot (this) yLabel = sprintf ("%s value", this.CriterionName); h = gca (); hold on; plot (this.InspectedK, this.CriterionValues, 'bo-'); plot (this.OptimalK, this.CriterionValues(this.OptimalIndex), 'b*'); xlabel ('number of clusters'); ylabel (yLabel); hold off; endfunction endmethods methods(Access = protected) ## evaluate ## do the evaluation function this = evaluate (this, K) ## use complete observations only UsableX = this.X(find (this.Missing == false), :); if (! isempty (this.ClusteringFunction)) ## build the clusters for iter = 1 : length (this.InspectedK) ## do it only for the specified K values if (any (this.InspectedK(iter) == K)) if (isa (this.ClusteringFunction, 'function_handle')) ## custom function ClusteringSolution = ... this.ClusteringFunction(UsableX, this.InspectedK(iter)); if (ismatrix (ClusteringSolution) && ... rows (ClusteringSolution) == this.NumObservations && ... columns (ClusteringSolution) == this.P) ## the custom function returned a matrix: ## we take the index of the maximum value for every row [~, this.ClusteringSolutions(:, iter)] = ... max (ClusteringSolution, [], 2); elseif (iscolumn (ClusteringSolution) && length (ClusteringSolution) == this.NumObservations) this.ClusteringSolutions(:, iter) = ClusteringSolution; elseif (isrow (ClusteringSolution) && length (ClusteringSolution) == this.NumObservations) this.ClusteringSolutions(:, iter) = ClusteringSolution'; else error (strcat ("CalinskiHarabaszEvaluation: invalid return", ... " value from custom clustering function.")); endif else switch (this.ClusteringFunction) case 'kmeans' [this.ClusteringSolutions(:, iter), this.Centroids{iter}] =... kmeans (UsableX, this.InspectedK(iter), ... 'Distance', 'sqeuclidean', 'EmptyAction', 'singleton', ... 'Replicates', 5); case 'linkage' ## use clusterdata this.ClusteringSolutions(:, iter) = clusterdata (UsableX, ... 'MaxClust', this.InspectedK(iter), ... 'Distance', 'euclidean', 'Linkage', 'ward'); this.Centroids{iter} = this.computeCentroids (UsableX, iter); case 'gmdistribution' gmm = fitgmdist (UsableX, this.InspectedK(iter), ... 'SharedCov', true, 'Replicates', 5); this.ClusteringSolutions(:, iter) = cluster (gmm, UsableX); this.Centroids{iter} = gmm.mu; otherwise error (strcat ("CalinskiHarabaszEvaluation:", ... " unexpected error, report this bug.")); endswitch endif endif endfor endif ## get the criterion values for every clustering solution for iter = 1 : length (this.InspectedK) ## do it only for the specified K values if (any (this.InspectedK(iter) == K)) ## not defined for one cluster if (this.InspectedK(iter) == 1) this.CriterionValues(iter) = NaN; continue; endif ## Caliński-Harabasz index ## reference: calinhara function from the fpc package of R, ## by Christian Hennig ## https://CRAN.R-project.org/package=fpc W = zeros (columns (UsableX)); # between clusters covariance for i = 1 : this.InspectedK(iter) vIndicesI = find (this.ClusteringSolutions(:, iter) == i); ni = length (vIndicesI); # size of cluster i if (ni == 1) ## if the cluster has just one member the covariance is zero continue; endif ## weighted update of the covariance matrix W += cov (UsableX(vIndicesI, :)) * (ni - 1); endfor S = (this.NumObservations - 1) * cov (UsableX); # within clusters cov. B = S - W; # between clusters means ## tr(B) / tr(W) * (N-k) / (k-1) this.CriterionValues(iter) = (this.NumObservations - ... this.InspectedK(iter)) * trace (B) / ... ((this.InspectedK(iter) - 1) * trace (W)); endif endfor ## A criterion that came out undefined everywhere leaves no optimum to ## report, and the solutions are echoed back only when this object built ## them: given a matrix of clusterings the caller already has them. if (all (isnan (this.CriterionValues))) this.OptimalIndex = []; this.OptimalK = NaN; this.OptimalY = []; else [~, this.OptimalIndex] = max (this.CriterionValues); this.OptimalK = this.InspectedK(this.OptimalIndex(1)); if (isempty (this.ClusteringFunction)) this.OptimalY = []; else this.OptimalY = this.ClusteringSolutions(:, this.OptimalIndex(1)); endif endif endfunction endmethods methods(Access = private) ## computeCentroids ## compute the centroids if they are not available by other means function C = computeCentroids (this, X, index) C = zeros (this.InspectedK(index), columns (X)); for iter = 1 : this.InspectedK(index) vIndicesI = find (this.ClusteringSolutions(:, index) == iter); C(iter, :) = mean (X(vIndicesI, :)); endfor endfunction endmethods endclassdef %!test %! load fisheriris %! eva = evalclusters (meas, 'kmeans', 'calinskiharabasz', 'KList', [1:6]); %! assert_equal (class (eva), "CalinskiHarabaszEvaluation"); %!function C = count_calls_calinskiharabasz (X, k) %! global count_calls_calinskiharabasz_n; %! count_calls_calinskiharabasz_n += 1; %! C = mod ((0 : rows (X) - 1)', k) + 1; %!endfunction %!test %! ## custom function must be called exactly once per inspected K %! global count_calls_calinskiharabasz_n; %! count_calls_calinskiharabasz_n = 0; %! evalclusters (rand (20, 2), @count_calls_calinskiharabasz, ... %! 'CalinskiHarabasz', 'KList', [2, 3]); %! assert_equal (count_calls_calinskiharabasz_n, 2); %! clear -global count_calls_calinskiharabasz_n; statistics-release-1.9.2/inst/Clustering/ClusterCriterion.m000066400000000000000000000342401524624707500241240ustar00rootroot00000000000000## Copyright (C) 2021 Stefano Guidoni ## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef (Abstract) ClusterCriterion ## -*- texinfo -*- ## @deftp {statistics} ClusterCriterion ## ## A clustering evaluation object. ## ## The @code{ClusterCriterion} is a superclass for clustering evaluation ## objects, which are created by the @code{evalclusters} function. It is not ## meant to be instantiated directly. ## ## @seealso{evalclusters, CalinskiHarabaszEvaluation, DaviesBouldinEvaluation, ## GapEvaluation, SilhouetteEvaluation} ## @end deftp properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClusterCriterion} {property} ClusteringFunction ## ## Clustering algorithm ## ## A character vector or a function handle specifying the clustering ## algorithm used to generate the clustering solutions. It can be empty if ## the clustering solutions are passed as an input matrix. This property is ## read-only. ## ## @end deftp ClusteringFunction = ''; ## -*- texinfo -*- ## @deftp {ClusterCriterion} {property} CriterionName ## ## Name of the evaluation criterion ## ## A character vector specifying the name of the criterion used to evaluate ## the clustering solutions. This property is read-only. ## ## @end deftp CriterionName = ''; ## -*- texinfo -*- ## @deftp {ClusterCriterion} {property} CriterionValues ## ## Criterion values ## ## A numeric vector containing the values generated by the evaluation ## criterion for each clustering solution. This property is read-only. ## ## @end deftp CriterionValues = []; ## -*- texinfo -*- ## @deftp {ClusterCriterion} {property} InspectedK ## ## List of the number of clusters ## ## A numeric vector containing the list of the number of clusters evaluated. ## This property is read-only. ## ## @end deftp InspectedK = []; ## -*- texinfo -*- ## @deftp {ClusterCriterion} {property} Missing ## ## Missing values ## ## A logical vector indicating which observations in the data matrix contain ## missing values (@code{NaN}). This property is read-only. ## ## @end deftp Missing = []; ## -*- texinfo -*- ## @deftp {ClusterCriterion} {property} NumObservations ## ## Number of observations ## ## An integer specifying the number of non-missing observations in the data ## matrix. This property is read-only. ## ## @end deftp NumObservations = 0; ## -*- texinfo -*- ## @deftp {ClusterCriterion} {property} OptimalK ## ## Optimal number of clusters ## ## An integer specifying the optimal number of clusters based on the ## evaluation criterion. This property is read-only. ## ## @end deftp OptimalK = 0; ## -*- texinfo -*- ## @deftp {ClusterCriterion} {property} OptimalY ## ## Optimal clustering solution ## ## A numeric vector representing the clustering solution that corresponds to ## the optimal number of clusters. This property is read-only. ## ## @end deftp OptimalY = []; ## -*- texinfo -*- ## @deftp {ClusterCriterion} {property} X ## ## Data used for clustering ## ## A numeric matrix containing the data used for clustering. This property ## is read-only. ## ## @end deftp X = []; endproperties properties(Access = protected) N = 0; # number of observations P = 0; # number of variables ClusteringSolutions = []; # OptimalIndex = 0; # index of the optimal K endproperties ## Every subclass must define an 'evaluate' method: this class provides ## the data, the K list and the shared bookkeeping, but never computes a ## criterion itself. MATLAB declares that method abstract; Octave cannot, ## because a bodyless signature in a 'methods (Abstract)' block is read as ## an external method and rejected outside an @-folder, so a subclass that ## omits 'evaluate' is caught when it is called rather than when it is ## defined. All four subclasses shipped here define it. methods(Access = public) ## -*- texinfo -*- ## @deftypefn {ClusterCriterion} {@var{obj} =} ClusterCriterion (@var{x}, @var{clust}, @var{KList}) ## ## Create a @qcode{ClusterCriterion} object. ## ## @code{ClusterCriterion} is a superclass and is not meant to be ## instantiated directly. Use @code{evalclusters} instead. ## ## @seealso{evalclusters} ## @end deftypefn function this = ClusterCriterion (x, clust, KList) ## parsing input data if (! ismatrix (x) || ! isnumeric (x)) error ("ClusterCriterion: X must be a numeric matrix."); endif this.X = x; this.N = rows (this.X); this.P = columns (this.X); ## look for missing values, one flag per observation and shaped like ## the observations themselves this.Missing = any (isnan (x), 2); ## number of usable observations this.NumObservations = sum (this.Missing == false); ## parsing the clustering algorithm if (ischar (clust)) if (any (strcmpi (clust, {'kmeans', 'linkage', 'gmdistribution'}))) this.ClusteringFunction = lower (clust); else error ("ClusterCriterion: unknown clustering algorithm '%s'.", clust); endif elseif (isa (clust, 'function_handle')) this.ClusteringFunction = clust; elseif (ismatrix (clust)) if (isnumeric (clust) && (length (size (clust)) == 2) && ... (rows (clust) == this.N)) ## Nothing clustered the data, the caller did, so there is no ## clustering function to report this.ClusteringFunction = []; this.ClusteringSolutions = clust(find (this.Missing == false), :); else error ("ClusterCriterion: invalid matrix of clustering solutions."); endif else error ("ClusterCriterion: invalid argument."); endif ## parsing the list of cluster sizes to inspect this.InspectedK = parseKList (this, KList); endfunction ## -*- texinfo -*- ## @deftypefn {ClusterCriterion} {@var{obj} =} addK (@var{obj}, @var{k}) ## ## Add a new list of cluster numbers to evaluate. ## ## @code{addK} adds a new list of cluster numbers, @var{k}, to the ## @qcode{ClusterCriterion} object. ## ## @end deftypefn function this = addK (this, k) ## A compacted object has no observations left to cluster if (isempty (this.X)) error (strcat ("ClusterCriterion.addK: this method can only be", ... " called when the X property is not empty.")); endif ## if there is not a clustering function, then we are using a predefined ## set of clustering solutions, hence we cannot redefine the number of ## solutions if (isempty (this.ClusteringFunction)) warning (strcat ("ClusterCriterion.addK: cannot redefine the list", ... " of cluster numbers to evaluate when there is", ... " not a clustering function")); return; endif ## otherwise go on newList = this.parseKList ([this.InspectedK k]); ## check if the list has changed if (length (newList) == length (this.InspectedK)) warning ("ClusterCriterion.addK: the list has not changed"); else ## update ClusteringSolutions and CriterionValues ClusteringSolutions_tmp = zeros (this.NumObservations, ... length (newList)); CriterionValues_tmp = zeros (length (newList), 1); for iter = 1 : length (this.InspectedK) idx = find (newList == this.InspectedK(iter)); if (! isempty (idx)) ClusteringSolutions_tmp(:, idx) = this.ClusteringSolutions(:, iter); CriterionValues_tmp(idx) = this.CriterionValues(iter); endif endfor this.ClusteringSolutions = ClusteringSolutions_tmp; this.CriterionValues = CriterionValues_tmp; ## reset the old results this.OptimalK = 0; this.OptimalY = []; this.OptimalIndex = 0; ## update the list of cluster numbers to evaluate this.InspectedK = newList; endif endfunction ## -*- texinfo -*- ## @deftypefn {ClusterCriterion} {@var{h} =} plot (@var{obj}) ## ## Plot the clustering evaluation values. ## ## @code{plot} generates a plot of the criterion values against the number ## of clusters. ## ## The optimal number of clusters is marked with an asterisk. ## ## The optional return value, @var{h}, is a graphics handle to the plot. ## ## @end deftypefn function h = plot (this) yLabel = sprintf ("%s value", this.CriterionName); h = gca (); hold on; plot (this.InspectedK, this.CriterionValues, 'bo-'); plot (this.OptimalK, this.CriterionValues(this.OptimalIndex), 'b*'); xlabel ('number of clusters'); ylabel (yLabel); hold off; endfunction ## -*- texinfo -*- ## @deftypefn {ClusterCriterion} {@var{obj} =} compact (@var{obj}) ## ## Create a compact clustering evaluation object. ## ## @code{@var{obj} = compact (@var{obj})} returns an object of the same ## class holding the results of the evaluation but none of the data it was ## computed from, which is useful when the evaluation is kept and the ## sample is large. ## ## @var{X}, @var{Missing} and @var{OptimalY} are emptied; @var{InspectedK}, ## @var{CriterionValues}, @var{OptimalK}, @var{NumObservations}, ## @var{CriterionName} and @var{ClusteringFunction} are kept. The object ## compacted from is not changed. ## ## A compacted object can still be displayed and plotted, and compacting ## one again does nothing, but @code{addK} raises: there are no ## observations left to cluster. ## ## @end deftypefn function this = compact (this) ## Drop everything held per observation and keep every result, as ## MATLAB does. The object is a value, so the caller's own copy is ## untouched: only the returned one is compacted. this.X = []; this.Missing = []; this.OptimalY = []; this.ClusteringSolutions = []; endfunction endmethods methods(Access = private) ## check if a list of cluster sizes is correct function retList = parseKList (this, KList) if (isnumeric (KList) && isvector (KList) && all (find (KList > 0)) && ... all (floor (KList) == KList)) retList = unique (KList); else error (strcat ("ClusterCriterion: the list of cluster sizes", ... " must be an array of positive integer numbers.")); endif endfunction endmethods endclassdef ## Test input validation %!test %! ## compact drops everything held per observation and keeps every %! ## result, returning an object of the same class. Verified against %! ## R2024a. %! X = [1, 1; 1.2, 1.1; 3, 3; 3.2, 3.1; 6, 6; 6.1, 6.2; 1.1, 1.3; 3.1, 3.2]; %! e = evalclusters (X, 'kmeans', 'CalinskiHarabasz', 'KList', 2:3); %! c = compact (e); %! assert_equal (class (c), class (e)); %! assert_equal (isempty (c.X), true); %! assert_equal (isempty (c.Missing), true); %! assert_equal (isempty (c.OptimalY), true); %! ## the results survive %! assert_equal (c.NumObservations, e.NumObservations); %! assert_equal (c.InspectedK, e.InspectedK); %! assert_equal (c.CriterionValues, e.CriterionValues); %! assert_equal (c.OptimalK, e.OptimalK); %!test %! ## the object compacted from is left alone: these are value classes, %! ## as MATLAB's are, so compact hands back a compacted copy %! X = [1, 1; 1.2, 1.1; 3, 3; 3.2, 3.1; 6, 6; 6.1, 6.2; 1.1, 1.3; 3.1, 3.2]; %! e = evalclusters (X, 'kmeans', 'CalinskiHarabasz', 'KList', 2:3); %! c = compact (e); %! assert_equal (size (e.X), [8, 2]); %! ## and compacting twice is harmless %! assert_equal (isempty (getfield (compact (c), 'X')), true); %!test %! ## a compacted object still plots, since plotting needs no observations %! X = [1, 1; 1.2, 1.1; 3, 3; 3.2, 3.1; 6, 6; 6.1, 6.2; 1.1, 1.3; 3.1, 3.2]; %! c = compact (evalclusters (X, 'kmeans', 'CalinskiHarabasz', 'KList', 2:3)); %! hf = figure ('visible', 'off'); %! unwind_protect %! h = plot (c); %! assert_equal (all (isaxes (h), 'all'), true); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!error ... %! addK (compact (evalclusters ([1, 1; 1.2, 1.1; 3, 3; 3.2, 3.1; 6, 6; ... %! 6.1, 6.2; 1.1, 1.3; 3.1, 3.2], 'kmeans', 'CalinskiHarabasz', ... %! 'KList', 2:3)), 4) ## This class is abstract, so its input validation is reached through a ## subclass, which is the only way a user can reach it either. %!error ... %! CalinskiHarabaszEvaluation ('1', 'kmeans', [1:6]) %!error ... %! CalinskiHarabaszEvaluation ([1, 2, 1, 3, 2, 4, 3], 'k', [1:6]) %!error ... %! CalinskiHarabaszEvaluation ([1, 2, 1; 3, 2, 4], 1, [1:6]) %!error ... %! CalinskiHarabaszEvaluation ([1, 2, 1; 3, 2, 4], ones (2, 2, 2), [1:6]) %!error ... %! ClusterCriterion ([1, 2, 1; 3, 2, 4], 'kmeans', [1:6]) statistics-release-1.9.2/inst/Clustering/DaviesBouldinEvaluation.m000066400000000000000000000315331524624707500254060ustar00rootroot00000000000000## Copyright (C) 2021 Stefano Guidoni ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef DaviesBouldinEvaluation < ClusterCriterion ## -*- texinfo -*- ## @deftp {statistics} DaviesBouldinEvaluation ## ## Davies-Bouldin object to evaluate clustering solutions ## ## A @code{DaviesBouldinEvaluation} object is a @code{ClusterCriterion} ## object used to evaluate clustering solutions using the Davies-Bouldin ## criterion. ## ## The Davies-Bouldin criterion is based on the ratio between the distances ## between clusters and within clusters, that is between centroids and ## between each datapoint and its centroid. ## ## The best solution according to the Davies-Bouldin criterion is the one ## that produces the lowest Davies-Bouldin value. ## ## @seealso{evalclusters, ClusterCriterion, CalinskiHarabaszEvaluation, ## GapEvaluation, SilhouetteEvaluation} ## @end deftp properties(Access = protected, Hidden) Centroids = {}; # a list of the centroids for every solution endproperties methods(Access = public) ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} DaviesBouldinEvaluation (@var{x}, @var{clust}, @var{KList}) ## ## Construct a @code{DaviesBouldinEvaluation} object to evaluate clustering ## solutions with the Davies-Bouldin criterion. ## ## @code{@var{obj} = DaviesBouldinEvaluation (@var{x}, @var{clust}, ## @var{KList})} clusters the data in @var{x} for every cluster count in ## @var{KList} and evaluates each solution. The evaluation runs at ## construction, so @var{obj} arrives with its @code{CriterionValues} and ## @code{OptimalK} already set. ## ## @itemize ## @item @var{x} is an @math{N*P} numeric matrix of observations (rows) ## and predictors (columns). A row holding a @qcode{NaN} is left out of ## @code{NumObservations}. ## @item @var{clust} names the clustering method, one of ## @qcode{'kmeans'}, @qcode{'linkage'} and @qcode{'gmdistribution'}; or a ## function handle that clusters the data; or an @math{N*M} numeric matrix ## of clustering solutions computed elsewhere, one column per cluster ## count, in which case @code{ClusteringFunction} is left empty. ## @item @var{KList} is a vector of positive integers, the cluster counts ## to inspect. ## @end itemize ## ## @code{evalclusters} is the usual way to create one of these objects. ## ## @end deftypefn function this = DaviesBouldinEvaluation (x, clust, KList) this@ClusterCriterion (x, clust, KList); this.CriterionName = 'DaviesBouldin'; this = this.evaluate (this.InspectedK); endfunction ## -*- texinfo -*- ## @deftypefn {DaviesBouldinEvaluation} {@var{obj} =} addK (@var{obj}, @var{K}) ## ## Add new cluster numbers to inspect in the DaviesBouldinEvaluation object. ## ## @end deftypefn function this = addK (this, K) this = addK@ClusterCriterion (this, K); ## if we have new data, we need a new evaluation if (this.OptimalK == 0) Centroids_tmp = {}; pS = 0; # position shift of the elements of Centroids for iter = 1 : length (this.InspectedK) ## reorganize Centroids according to the new list of cluster numbers if (any (this.InspectedK(iter) == K)) pS += 1; else Centroids_tmp{iter} = this.Centroids{iter - pS}; endif endfor this.Centroids = Centroids_tmp; this = this.evaluate (K); endif endfunction ## -*- texinfo -*- ## @deftypefn {DaviesBouldinEvaluation} {} plot (@var{obj}) ## @deftypefnx {DaviesBouldinEvaluation} {@var{h} =} plot (@var{obj}) ## ## Plot Davies-Bouldin evaluation results. ## ## Plot the CriterionValues against InspectedK from the ## DaviesBouldinEvaluation ClusterCriterion to the current plot. ## Returns an axes handle if requested. ## ## @end deftypefn function h = plot (this) yLabel = sprintf ("%s value", this.CriterionName); h = gca (); hold on; plot (this.InspectedK, this.CriterionValues, 'bo-'); plot (this.OptimalK, this.CriterionValues(this.OptimalIndex), 'b*'); xlabel ('number of clusters'); ylabel (yLabel); hold off; endfunction endmethods methods(Access = protected) ## evaluate ## do the evaluation function this = evaluate (this, K) ## use complete observations only UsableX = this.X(find (this.Missing == false), :); if (! isempty (this.ClusteringFunction)) ## build the clusters for iter = 1 : length (this.InspectedK) ## do it only for the specified K values if (any (this.InspectedK(iter) == K)) if (isa (this.ClusteringFunction, 'function_handle')) ## custom function ClusteringSolution = ... this.ClusteringFunction(UsableX, this.InspectedK(iter)); if (ismatrix (ClusteringSolution) && ... rows (ClusteringSolution) == this.NumObservations && ... columns (ClusteringSolution) == this.P) ## the custom function returned a matrix: ## we take the index of the maximum value for every row [~, this.ClusteringSolutions(:, iter)] = ... max (ClusteringSolution, [], 2); elseif (iscolumn (ClusteringSolution) && length (ClusteringSolution) == this.NumObservations) this.ClusteringSolutions(:, iter) = ClusteringSolution; elseif (isrow (ClusteringSolution) && length (ClusteringSolution) == this.NumObservations) this.ClusteringSolutions(:, iter) = ClusteringSolution'; else error (strcat ("DaviesBouldinEvaluation: invalid return", ... " value from custom clustering function.")); endif this.ClusteringSolutions(:, iter) = ... this.ClusteringFunction(UsableX, this.InspectedK(iter)); else switch (this.ClusteringFunction) case 'kmeans' [this.ClusteringSolutions(:, iter), this.Centroids{iter}] =... kmeans (UsableX, this.InspectedK(iter), ... 'Distance', 'sqeuclidean', 'EmptyAction', 'singleton', ... 'Replicates', 5); case 'linkage' ## use clusterdata this.ClusteringSolutions(:, iter) = clusterdata (UsableX, ... 'MaxClust', this.InspectedK(iter), ... 'Distance', 'euclidean', 'Linkage', 'ward'); this.Centroids{iter} = this.computeCentroids (UsableX, iter); case 'gmdistribution' gmm = fitgmdist (UsableX, this.InspectedK(iter), ... 'SharedCov', true, 'Replicates', 5); this.ClusteringSolutions(:, iter) = cluster (gmm, UsableX); this.Centroids{iter} = gmm.mu; otherwise error (strcat ("DaviesBouldinEvaluation: unexpected", ... " error, report this bug.")); endswitch endif endif endfor endif ## get the criterion values for every clustering solution for iter = 1 : length (this.InspectedK) ## do it only for the specified K values if (any (this.InspectedK(iter) == K)) ## not defined for one cluster if (this.InspectedK(iter) == 1) this.CriterionValues(iter) = NaN; continue; endif ## Davies-Bouldin value ## an evaluation of the ratio between within-cluster and ## between-cluster distances ## Compute centroids when clustering labels are provided as input. if (numel (this.Centroids) < iter || isempty (this.Centroids{iter})) this.Centroids{iter} = this.computeCentroids (UsableX, iter); endif ## mean distances between cluster members and their centroid vD = zeros (this.InspectedK(iter), 1); for i = 1 : this.InspectedK(iter) vIndicesI = find (this.ClusteringSolutions(:, iter) == i); vD(i) = mean (vecnorm (UsableX(vIndicesI, :) - ... this.Centroids{iter}(i, :), 2, 2)); endfor ## within-to-between cluster distance ratio Dij = zeros (this.InspectedK(iter)); for i = 1 : (this.InspectedK(iter) - 1) for j = (i + 1) : this.InspectedK(iter) ## centroid to centroid distance dij = vecnorm (this.Centroids{iter}(i, :) - ... this.Centroids{iter}(j, :)); ## within-to-between cluster distance ratio for clusters i and j Dij(i, j) = (vD(i) + vD(j)) / dij; endfor endfor ## ( max_j D1j + max_j D2j + ... + max_j Dkj) / k Dij = Dij + Dij'; this.CriterionValues(iter) = sum (max (Dij, [], 2)) / ... this.InspectedK(iter); endif endfor ## A criterion that came out undefined everywhere leaves no optimum to ## report, and the solutions are echoed back only when this object built ## them: given a matrix of clusterings the caller already has them. if (all (isnan (this.CriterionValues))) this.OptimalIndex = []; this.OptimalK = NaN; this.OptimalY = []; else [~, this.OptimalIndex] = min (this.CriterionValues); this.OptimalK = this.InspectedK(this.OptimalIndex(1)); if (isempty (this.ClusteringFunction)) this.OptimalY = []; else this.OptimalY = this.ClusteringSolutions(:, this.OptimalIndex(1)); endif endif endfunction endmethods methods(Access = private) ## computeCentroids ## compute the centroids if they are not available by other means function C = computeCentroids (this, X, index) C = zeros (this.InspectedK(index), columns (X)); for iter = 1 : this.InspectedK(index) vIndicesI = find (this.ClusteringSolutions(:, index) == iter); C(iter, :) = mean (X(vIndicesI, :)); endfor endfunction endmethods endclassdef %!test %! load fisheriris %! eva = evalclusters (meas, 'kmeans', 'DaviesBouldin', 'KList', [1:6]); %! assert_equal (class (eva), "DaviesBouldinEvaluation"); %!test %! ## Verify DB index for a known 2-cluster case %! X = [ones(5,1); 5 * ones(5,1)]; %! clust = [ones(5,1); 2 * ones(5,1)]; %! eva = evalclusters (X, clust, 'DaviesBouldin', 'KList', 2); %! assert_equal (eva.CriterionValues, 0, 1); %!test %! ## Deterministic 1-D example; expected value is 7/30 (matches MATLAB) %! rand ('seed', 1); %! randn ('seed', 1); %! X = [0; 1; 4; 5; 9; 10]; %! eva = evalclusters (X, 'kmeans', 'DaviesBouldin', 'KList', 3); %! assert_equal (eva.CriterionValues, 7 / 30, 1e-12); %!test %! ## Verify aggregation uses all cluster rows in Dij %! rand ('seed', 1); %! randn ('seed', 1); %! X = [0; 1; 4; 5; 9; 10]; %! eva = evalclusters (X, 'kmeans', 'DaviesBouldin', 'KList', 3); %! idx = eva.OptimalY; %! k = 3; %! vD = zeros (k, 1); %! C = zeros (k, 1); %! for i = 1:k %! Xi = X(idx == i); %! C(i) = mean (Xi); %! vD(i) = mean (abs (Xi - C(i))); %! endfor %! Dij = zeros (k); %! for i = 1:(k - 1) %! for j = (i + 1):k %! Dij(i, j) = (vD(i) + vD(j)) / abs (C(i) - C(j)); %! endfor %! endfor %! Dij = Dij + Dij'; %! expected = sum (max (Dij, [], 2)) / k; %! assert_equal (eva.CriterionValues, expected, 1e-12); %!test %! ## MATLAB reference case: well-separated tight clusters %! rand ('seed', 1); %! randn ('seed', 1); %! X = [0; 0.1; 0.2; 5; 5.1; 5.2]; %! X = horzcat (X, zeros (rows (X), 1)); %! eva = evalclusters (X, 'kmeans', 'DaviesBouldin', 'KList', 2); %! assert_equal (eva.CriterionValues, 0.0267, 1e-4); %!test %! ## MATLAB reference case: uneven spread clusters %! rand ('seed', 1); %! randn ('seed', 1); %! X = [0; 0.1; 0.2; 10; 20; 30]; %! X = horzcat (X, zeros (rows (X), 1)); %! eva = evalclusters (X, 'kmeans', 'DaviesBouldin', 'KList', 2); %! assert_equal (eva.CriterionValues, 0.3885, 1e-4);statistics-release-1.9.2/inst/Clustering/GapEvaluation.m000066400000000000000000000472271524624707500233740ustar00rootroot00000000000000## Copyright (C) 2021 Stefano Guidoni ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef GapEvaluation < ClusterCriterion ## -*- texinfo -*- ## @deftp {statistics} GapEvaluation ## ## Gap evaluation for clustering solutions ## ## The @code{GapEvaluation} class implements the gap statistic criterion for ## evaluating clustering solutions. A @code{GapEvaluation} object is a ## specialization of @code{ClusterCriterion} and contains fields and methods ## to compute the gap statistic, its Monte-Carlo reference expectations, and ## to select the optimal number of clusters according to a chosen search ## method. ## ## Create a @code{GapEvaluation} object by using the @code{evalclusters} ## function or by calling the class constructor directly. ## ## @seealso{evalclusters, ClusterCriterion, CalinskiHarabaszEvaluation, ## DaviesBouldinEvaluation, SilhouetteEvaluation} ## @end deftp properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {GapEvaluation} {property} B ## ## Number of reference datasets ## ## A positive integer specifying how many reference datasets are generated ## to compute the expected log within-cluster dispersion via Monte-Carlo ## simulation. This property is read-only. ## ## @end deftp B = 0; ## -*- texinfo -*- ## @deftp {GapEvaluation} {property} Distance ## ## Distance metric ## ## A character vector or function handle specifying the distance measure ## passed to clustering routines (as accepted by @code{pdist}). When a ## numeric vector is supplied it is interpreted as a precomputed distance ## vector. This property is read-only. ## ## @end deftp Distance = ''; ## -*- texinfo -*- ## @deftp {GapEvaluation} {property} ReferenceDistribution ## ## Reference distribution for Monte-Carlo ## ## A character vector naming the reference distribution used to generate ## reference datasets. Supported values include @qcode{'pca'} and ## @qcode{'uniform'}. This property is read-only. ## ## @end deftp ReferenceDistribution = ''; ## -*- texinfo -*- ## @deftp {GapEvaluation} {property} SearchMethod ## ## Search method to select optimal K ## ## A character vector specifying the method used to select the optimal ## number of clusters from the gap statistic. Supported values include ## @qcode{'globalMaxSE'} and @qcode{'firstMaxSE'}. This property is ## read-only. ## ## @end deftp SearchMethod = ''; ## -*- texinfo -*- ## @deftp {GapEvaluation} {property} ExpectedLogW ## ## Expected log within-cluster dispersion ## ## A numeric vector containing the Monte-Carlo estimate of the expected ## values for the natural logarithm of the within-cluster dispersion, ## computed across the generated reference datasets. This property is ## read-only. ## ## @end deftp ExpectedLogW = []; ## -*- texinfo -*- ## @deftp {GapEvaluation} {property} LogW ## ## Observed log within-cluster dispersion ## ## A numeric vector containing the observed values of the natural ## logarithm of the within-cluster dispersion computed on the actual data. ## This property is read-only. ## ## @end deftp LogW = []; ## -*- texinfo -*- ## @deftp {GapEvaluation} {property} SE ## ## Standard error of expected logW ## ## A numeric vector containing the standard error of the expected values ## for the natural logarithm of the within-cluster dispersion. This ## property is read-only. ## ## @end deftp SE = []; ## -*- texinfo -*- ## @deftp {GapEvaluation} {property} StdLogW ## ## Standard deviation of expected logW ## ## A numeric vector containing the standard deviation of the Monte-Carlo ## estimates of the log within-cluster dispersion. This property is ## read-only. ## ## @end deftp StdLogW = []; endproperties properties(Access = protected) ## -*- texinfo -*- ## @deftp {GapEvaluation} {property} DistanceVector ## ## Precomputed distance vector ## ## If a numeric vector is supplied as the distance metric it is stored ## here and used instead of computing distances via @code{pdist}. This ## property is read-only. ## ## @end deftp DistanceVector = []; ## -*- texinfo -*- ## @deftp {GapEvaluation} {property} mExpectedLogW ## ## Monte-Carlo results matrix ## ## Internal matrix storing the log within-cluster dispersion values ## computed for each Monte-Carlo run (rows) and each inspected K (columns). ## This property is read-only. ## ## @end deftp mExpectedLogW = []; endproperties methods(Access = public) ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} GapEvaluation (@var{x}, @var{clust}, @var{KList}) ## @deftypefnx {statistics} {@var{obj} =} GapEvaluation (@var{x}, @var{clust}, @var{KList}, @var{B}) ## @deftypefnx {statistics} {@var{obj} =} GapEvaluation (@dots{}, @var{name}, @var{value}) ## ## Construct a GapEvaluation object to evaluate clustering solutions using ## the gap statistic. ## ## @code{@var{obj} = GapEvaluation (@var{x}, @var{clust}, @var{KList})} ## returns a @code{GapEvaluation} object configured to evaluate the ## clustering function specified by @var{clust} on the data matrix ## @var{x} for the list of cluster counts in @var{KList}. ## ## Optional inputs: ## ## @itemize ## @item @qcode{B} - Number of reference datasets to generate (default 100). ## @item @qcode{'Distance'} - Distance metric name or function handle as ## accepted by @code{pdist} (default @qcode{'sqeuclidean'}). ## @item @qcode{'ReferenceDistribution'} - Reference distribution to use ## (default @qcode{'pca'}; @qcode{'uniform'} is supported). ## @item @qcode{'SearchMethod'} - Method to select the optimal K; one of ## @qcode{'globalMaxSE'} or @qcode{'firstMaxSE'} (default ## @qcode{'globalMaxSE'}). ## @end itemize ## ## @seealso{evalclusters, ClusterCriterion} ## @end deftypefn function this = GapEvaluation (x, clust, KList, b = 100, ... distanceMetric = 'sqeuclidean', ... referenceDistribution = 'pca', searchMethod = 'globalmaxse') this@ClusterCriterion (x, clust, KList); ## parsing the distance criterion if (ischar (distanceMetric)) if (any (strcmpi (distanceMetric, {'sqeuclidean', 'euclidean', ... 'cityblock', 'cosine', 'correlation', 'hamming', 'jaccard'}))) ## Report the metric under its canonical spelling, as MATLAB does. ## The name has already been matched exactly above, so validatestring ## only canonicalises it and never widens what is accepted. this.Distance = validatestring (distanceMetric, ... {'sqEuclidean', 'Euclidean', 'cityblock', ... 'cosine', 'correlation', 'Hamming', 'Jaccard'}); ## kmeans can use only a subset if (strcmpi (clust, 'kmeans') && any (strcmpi (this.Distance, ... {'euclidean', 'jaccard'}))) error (strcat ("GapEvaluation: invalid distance criterion", ... " '%s' for 'kmeans'"), distanceMetric); endif else error ("GapEvaluation: unknown distance criterion '%s'", ... distanceMetric); endif elseif (isa (distanceMetric, 'function_handle')) this.Distance = distanceMetric; ## kmeans cannot use a function handle if (strcmpi (clust, 'kmeans')) error ("GapEvaluation: invalid distance criterion for 'kmeans'."); endif elseif (isvector (distanceMetric) && isnumeric (distanceMetric)) this.Distance = ''; this.DistanceVector = distanceMetric; # the validity check is delegated ## kmeans cannot use a distance vector if (strcmpi (clust, 'kmeans')) error ("GapEvaluation: invalid distance criterion for 'kmeans'."); endif else error ("GapEvaluation: invalid distance metric."); endif ## B: number of Monte-Carlo iterations if (! isnumeric (b) || ! isscalar (b) || b != floor (b) || b < 1) error ("GapEvaluation: b must a be positive integer number"); endif this.B = b; ## reference distribution if (! ischar (referenceDistribution) || ! any (strcmpi ... (referenceDistribution, {'pca', 'uniform'}))) error (strcat ("GapEvaluation: the reference distribution", ... " must be either 'PCA' or 'uniform'.")); elseif (strcmpi (referenceDistribution, 'pca')) warning (strcat ("GapEvaluation: 'PCA' distribution not", ... " implemented, defaulting to 'uniform'.")); endif this.ReferenceDistribution = validatestring (referenceDistribution, ... {'PCA', 'uniform'}); if (! ischar (searchMethod) || ! any (strcmpi (searchMethod, ... {'globalmaxse', 'firstmaxse'}))) error (strcat ("evalclusters: the search method must be", ... " either 'globalMaxSE' or 'firstMaxSE'.")); endif this.SearchMethod = validatestring (searchMethod, ... {'GlobalMaxSE', 'FirstMaxSE'}); ## a matrix to store the results from the Monte-Carlo runs this.mExpectedLogW = zeros (this.B, length (this.InspectedK)); this.CriterionName = 'Gap'; this = this.evaluate (this.InspectedK); endfunction ## -*- texinfo -*- ## @deftypefn {GapEvaluation} {@var{obj} =} addK (@var{obj}, @var{K}) ## ## Add new K values to inspect ## ## Add a new cluster array to inspect in the @code{GapEvaluation} object. ## This updates internal storage for Monte-Carlo results and evaluates the ## newly requested cluster counts. ## ## @end deftypefn function this = addK (this, K) this = addK@ClusterCriterion (this, K); ## if we have new data, we need a new evaluation if (this.OptimalK == 0) mExpectedLogW_tmp = zeros (this.B, length (this.InspectedK)); pS = 0; # position shift for iter = 1 : length (this.InspectedK) ## reorganize all the arrays according to the new list ## of cluster numbers if (any (this.InspectedK(iter) == K)) pS += 1; else mExpectedLogW_tmp(:, iter) = this.mExpectedLogW(:, iter - pS); endif endfor this.mExpectedLogW = mExpectedLogW_tmp; this = this.evaluate (K); endif endfunction ## -*- texinfo -*- ## @deftypefn {GapEvaluation} {} plot (@var{obj}) ## @deftypefnx {GapEvaluation} {@var{h} =} plot (@var{obj}) ## ## Plot Gap evaluation results ## ## Plot the gap statistic (criterion values) versus the inspected numbers ## of clusters and display error bars representing the Monte-Carlo ## standard deviations. Optionally returns the axes handle. ## ## @end deftypefn function h = plot (this) yLabel = sprintf ("%s value", this.CriterionName); h = gca (); hold on; errorbar (this.InspectedK, this.CriterionValues, this.StdLogW); plot (this.InspectedK, this.CriterionValues, 'bo'); plot (this.OptimalK, this.CriterionValues(this.OptimalIndex), 'b*'); xlabel ('number of clusters'); ylabel (yLabel); hold off; endfunction endmethods methods(Access = protected) ## evaluate ## do the evaluation function this = evaluate (this, K) ## use complete observations only ActualX = this.X(find (this.Missing == false), :); colMins = min (ActualX); colRange = max (ActualX) - colMins; ## Monte-Carlo runs for mcrun = 1 : (this.B + 1) ## the last run use tha actual data, ## the others are Monte-Carlo runs with reconstructed data if (mcrun <= this.B) ## uniform distribution UsableX = colMins + rand (this.NumObservations, columns (ActualX)) ... .* colRange; else UsableX = ActualX; endif if (! isempty (this.ClusteringFunction)) ## build the clusters for iter = 1 : length (this.InspectedK) ## do it only for the specified K values if (any (this.InspectedK(iter) == K)) if (isa (this.ClusteringFunction, 'function_handle')) ## custom function ClusteringSolution = ... this.ClusteringFunction(UsableX, this.InspectedK(iter)); if (ismatrix (ClusteringSolution) && ... rows (ClusteringSolution) == this.NumObservations && ... columns (ClusteringSolution) == this.P) ## the custom function returned a matrix: ## we take the index of the maximum value for every row [~, this.ClusteringSolutions(:, iter)] = ... max (ClusteringSolution, [], 2); elseif (iscolumn (ClusteringSolution) && length (ClusteringSolution) == this.NumObservations) this.ClusteringSolutions(:, iter) = ClusteringSolution; elseif (isrow (ClusteringSolution) && length (ClusteringSolution) == this.NumObservations) this.ClusteringSolutions(:, iter) = ClusteringSolution'; else error (strcat ("GapEvaluation: invalid return value", ... " from custom clustering function")); endif else switch (this.ClusteringFunction) case 'kmeans' this.ClusteringSolutions(:, iter) = kmeans (UsableX, ... this.InspectedK(iter), 'Distance', this.Distance, ... 'EmptyAction', 'singleton', 'Replicates', 5); case 'linkage' if (! isempty (this.Distance)) ## use clusterdata Distance_tmp = this.Distance; LinkageMethod = 'average'; # for non euclidean methods if (strcmpi (this.Distance, 'sqeuclidean')) ## pdist uses different names for its algorithms Distance_tmp = 'squaredeuclidean'; LinkageMethod = 'ward'; elseif (strcmpi (this.Distance, 'euclidean')) LinkageMethod = 'ward'; endif this.ClusteringSolutions(:, iter) = clusterdata ... (UsableX, 'MaxClust', this.InspectedK(iter), ... 'Distance', Distance_tmp, 'Linkage', LinkageMethod); else ## use linkage Z = linkage (this.DistanceVector, 'average'); this.ClusteringSolutions(:, iter) = ... cluster (Z, 'MaxClust', this.InspectedK(iter)); endif case 'gmdistribution' gmm = fitgmdist (UsableX, this.InspectedK(iter), ... 'SharedCov', true, 'Replicates', 5); this.ClusteringSolutions(:, iter) = cluster (gmm, UsableX); otherwise ## this should not happen error ("GapEvaluation: unexpected error, report this bug."); endswitch endif endif endfor endif ## get the gap values for every clustering distance_pdist = this.Distance; if (strcmpi (distance_pdist, 'sqeuclidean')) distance_pdist = 'squaredeuclidean'; endif ## compute LogW for iter = 1 : length (this.InspectedK) ## do it only for the specified K values if (any (this.InspectedK(iter) == K)) wk = 0; for r = 1 : this.InspectedK(iter) vIndicesR = find (this.ClusteringSolutions(:, iter) == r); nr = length (vIndicesR); Dr = pdist (UsableX(vIndicesR, :), distance_pdist); wk += sum (Dr) / (2 * nr); endfor if (mcrun <= this.B) this.mExpectedLogW(mcrun, iter) = log (wk); else this.LogW(iter) = log (wk); endif endif endfor endfor this.ExpectedLogW = mean (this.mExpectedLogW); this.SE = sqrt ((1 + 1 / this.B) * sumsq (this.mExpectedLogW - ... this.ExpectedLogW) / this.B); this.StdLogW = std (this.mExpectedLogW); this.CriterionValues = this.ExpectedLogW - this.LogW; ## As in the other criteria, the solutions are echoed back only when ## this object built them. this.OptimalIndex = this.gapSearch (); if (isempty (this.OptimalIndex) || all (isnan (this.CriterionValues))) this.OptimalIndex = []; this.OptimalK = NaN; this.OptimalY = []; else this.OptimalK = this.InspectedK(this.OptimalIndex(1)); if (isempty (this.ClusteringFunction)) this.OptimalY = []; else this.OptimalY = this.ClusteringSolutions(:, this.OptimalIndex(1)); endif endif endfunction ## gapSearch ## find the best solution according to the gap method function ind = gapSearch (this) if (strcmpi (this.SearchMethod, 'globalmaxse')) [gapmax, indgp] = max (this.CriterionValues); for iter = 1 : length (this.InspectedK) ind = iter; if (this.CriterionValues(iter) > (gapmax - this.SE(indgp))) return endif endfor elseif (strcmpi (this.SearchMethod, 'firstmaxse')) for iter = 1 : (length (this.InspectedK) - 1) ind = iter; if (this.CriterionValues(iter) > (this.CriterionValues(iter + 1) - ... this.SE(iter + 1))) return endif endfor else ## this should not happen error ("GapEvaluation: unexpected error, please report this bug."); endif endfunction endmethods endclassdef %!test %! load fisheriris %! eva = evalclusters (meas([1:50],:), 'kmeans', 'gap', 'KList', [1:3], ... %! 'referencedistribution', 'uniform'); %! assert_equal (class (eva), "GapEvaluation"); %!function C = count_calls_gap (X, k) %! global count_calls_gap_n; %! count_calls_gap_n += 1; %! C = mod ((0 : rows (X) - 1)', k) + 1; %!endfunction %!test %! ## custom function must be called exactly once per inspected K per run %! global count_calls_gap_n; %! count_calls_gap_n = 0; %! evalclusters (rand (20, 2), @count_calls_gap, 'gap', ... %! 'KList', [2, 3], 'B', 2); %! assert_equal (count_calls_gap_n, 6); %! clear -global count_calls_gap_n; statistics-release-1.9.2/inst/Clustering/SilhouetteEvaluation.m000066400000000000000000000401741524624707500250040ustar00rootroot00000000000000## Copyright (C) 2021 Stefano Guidoni ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef SilhouetteEvaluation < ClusterCriterion ## -*- texinfo -*- ## @deftp {statistics} SilhouetteEvaluation ## ## Silhouette evaluation for clustering ## ## The @code{SilhouetteEvaluation} class implements an object to evaluate ## clustering solutions using the silhouette criterion. A ## @code{SilhouetteEvaluation} object is a @code{ClusterCriterion} object ## that computes silhouette values for clustering solutions and selects the ## best number of clusters as the one with the highest average silhouette ## value. ## ## Create a @code{SilhouetteEvaluation} object by using the ## @code{evalclusters} function or the class constructor. ## ## List of public properties specific to @code{SilhouetteEvaluation}: ## @table @code ## @item @qcode{Distance} ## A valid distance metric name (string), a function handle, or a numeric ## vector as returned by @code{pdist}. This specifies how pairwise ## distances are computed. ## ## @item @qcode{ClusterPriors} ## A character vector specifying how to evaluate silhouette values across ## clusters: @qcode{'empirical'} (default) uses empirical cluster priors, ## or @qcode{'equal'} treats clusters equally. ## ## @item @qcode{ClusterSilhouettes} ## A cell array containing silhouette values for each observation for each ## inspected cluster number. ## @end table ## ## The best clustering solution according to the silhouette criterion is the ## one that yields the highest average silhouette value. ## ## @seealso{evalclusters, ClusterCriterion, CalinskiHarabaszEvaluation, ## DaviesBouldinEvaluation, GapEvaluation} ## @end deftp properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {SilhouetteEvaluation} {property} Distance ## ## Distance measure ## ## A string naming a distance metric, a function handle that computes ## distances, or a numeric vector as produced by @code{pdist}. This ## property is read-only. ## ## @end deftp Distance = ''; ## -*- texinfo -*- ## @deftp {SilhouetteEvaluation} {property} ClusterPriors ## ## Cluster prior handling ## ## Specifies how cluster-level silhouette aggregation is computed. Valid ## values are @qcode{'empirical'} (default) and @qcode{'equal'}. This ## property is read-only. ## ## @end deftp ClusterPriors = ''; ## -*- texinfo -*- ## @deftp {SilhouetteEvaluation} {property} ClusterSilhouettes ## ## Silhouette values ## ## A cell array where each element holds the mean silhouette value of ## each cluster of a given clustering (corresponding to an inspected K), ## so element @var{i} is a vector of @code{InspectedK(@var{i})} values. ## This property is read-only. ## ## @end deftp ClusterSilhouettes = {}; endproperties properties(Access = protected) ## -*- texinfo -*- ## @deftp {SilhouetteEvaluation} {property} DistanceVector ## ## Precomputed distance vector ## ## If a numeric vector is supplied as the distance metric it is stored ## here and used instead of computing distances via @code{pdist}. This ## property is read-only. ## ## @end deftp DistanceVector = []; endproperties methods(Access = public) ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} SilhouetteEvaluation (@var{x}, @var{clust}, @var{KList}) ## @deftypefnx {statistics} {@var{obj} =} SilhouetteEvaluation (@dots{}, @var{Name}, @var{Value}) ## ## Create a @code{SilhouetteEvaluation} object to evaluate clustering ## solutions for data @var{x} using clustering method @var{clust} over the ## list of cluster numbers @var{KList}. ## ## @itemize ## @item ## @var{x} is an @math{N*P} numeric matrix of observations (rows) and ## predictors (columns). ## @item ## @var{clust} is a string naming the clustering method (for example ## @qcode{'kmeans'}, @qcode{'linkage'}, or a custom function handle). ## @item ## @var{KList} is a vector of positive integers specifying the cluster ## numbers to inspect. ## @end itemize ## ## Optional name-value pairs: ## ## @multitable @columnfractions 0.20 0.78 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Distance'} @tab Distance metric name, function handle, ## or numeric pdist vector. Default: @qcode{'sqeuclidean'}. ## ## @item @qcode{'ClusterPriors'} @tab Either @qcode{'empirical'} ## (default) or @qcode{'equal'}. ## @end multitable ## ## @seealso{silhouette, evalclusters, ClusterCriterion} ## @end deftypefn function this = SilhouetteEvaluation (x, clust, KList, ... distanceMetric = 'sqeuclidean', clusterPriors = 'empirical') this@ClusterCriterion (x, clust, KList); ## parsing the distance criterion if (ischar (distanceMetric)) if (any (strcmpi (distanceMetric, {'sqeuclidean', ... 'euclidean', 'cityblock', 'cosine', 'correlation', ... 'hamming', 'jaccard'}))) ## Report the metric under its canonical spelling, as MATLAB does. ## The name has already been matched exactly above, so validatestring ## only canonicalises it and never widens what is accepted. this.Distance = validatestring (distanceMetric, ... {'sqEuclidean', 'Euclidean', 'cityblock', ... 'cosine', 'correlation', 'Hamming', 'Jaccard'}); ## kmeans can use only a subset if (strcmpi (clust, 'kmeans') && any (strcmpi (this.Distance, ... {'euclidean', 'jaccard'}))) error (strcat ("SilhouetteEvaluation: invalid distance", ... " criterion '%s' for 'kmeans'."), distanceMetric); endif else error ("SilhouetteEvaluation: unknown distance criterion '%s'.", ... distanceMetric); endif elseif (isa (distanceMetric, 'function_handle')) this.Distance = distanceMetric; ## kmeans cannot use a function handle if (strcmpi (clust, 'kmeans')) error (strcat ("SilhouetteEvaluation: invalid distance", ... " criterion for 'kmeans'.")); endif elseif (isvector (distanceMetric) && isnumeric (distanceMetric)) this.Distance = ''; this.DistanceVector = distanceMetric; # the validity check is delegated ## kmeans cannot use a distance vector if (strcmpi (clust, 'kmeans')) error (strcat ("SilhouetteEvaluation: invalid", ... " distance criterion for 'kmeans'.")); endif else error ("SilhouetteEvaluation: invalid distance metric."); endif ## parsing the prior probabilities of each cluster if (ischar (distanceMetric)) if (any (strcmpi (clusterPriors, {'empirical', 'equal'}))) this.ClusterPriors = lower (clusterPriors); else error (strcat ("SilhouetteEvaluation: unknown prior", ... " probability criterion '%s'"), clusterPriors); endif else error ("SilhouetteEvaluation: invalid prior probabilities."); endif this.CriterionName = 'Silhouette'; this = this.evaluate (this.InspectedK); endfunction ## -*- texinfo -*- ## @deftypefn {SilhouetteEvaluation} {@var{obj} =} addK (@var{obj}, @var{K}) ## ## Add a new cluster number to inspect and re-evaluate silhouette values ## for newly added cluster numbers. ## ## @end deftypefn function this = addK (this, K) this = addK@ClusterCriterion (this, K); ## if we have new data, we need a new evaluation if (this.OptimalK == 0) ClusterSilhouettes_tmp = {}; pS = 0; # position shift of the elements of ClusterSilhouettes for iter = 1 : length (this.InspectedK) ## reorganize ClusterSilhouettes according to the new list ## of cluster numbers if (any (this.InspectedK(iter) == K)) pS += 1; else ClusterSilhouettes_tmp{iter} = this.ClusterSilhouettes{iter - pS}; endif endfor this.ClusterSilhouettes = ClusterSilhouettes_tmp; this = this.evaluate (K); endif endfunction ## -*- texinfo -*- ## @deftypefn {SilhouetteEvaluation} {} plot (@var{obj}) ## @deftypefnx {SilhouetteEvaluation} {@var{h} =} plot (@var{obj}) ## ## Plot the silhouette evaluation results. ## ## Plot the criterion values (average silhouette) against inspected cluster ## numbers (@code{InspectedK}) for the given @var{obj}. Optionally returns ## the axis handle for the plot. ## ## @end deftypefn function h = plot (this) yLabel = sprintf ("%s value", this.CriterionName); h = gca (); hold on; plot (this.InspectedK, this.CriterionValues, 'bo-'); plot (this.OptimalK, this.CriterionValues(this.OptimalIndex), 'b*'); xlabel ('number of clusters'); ylabel (yLabel); hold off; endfunction endmethods methods(Access = protected) ## evaluate ## do the evaluation function this = evaluate (this, K) ## use complete observations only UsableX = this.X(find (this.Missing == false), :); if (! isempty (this.ClusteringFunction)) ## build the clusters for iter = 1 : length (this.InspectedK) ## do it only for the specified K values if (any (this.InspectedK(iter) == K)) if (isa (this.ClusteringFunction, 'function_handle')) ## custom function ClusteringSolution = ... this.ClusteringFunction(UsableX, this.InspectedK(iter)); if (ismatrix (ClusteringSolution) && ... rows (ClusteringSolution) == this.NumObservations && ... columns (ClusteringSolution) == this.P) ## the custom function returned a matrix: ## we take the index of the maximum value for every row [~, this.ClusteringSolutions(:, iter)] = ... max (ClusteringSolution, [], 2); elseif (iscolumn (ClusteringSolution) && length (ClusteringSolution) == this.NumObservations) this.ClusteringSolutions(:, iter) = ClusteringSolution; elseif (isrow (ClusteringSolution) && length (ClusteringSolution) == this.NumObservations) this.ClusteringSolutions(:, iter) = ClusteringSolution'; else error (strcat ("SilhouetteEvaluation: invalid return", ... " value from custom clustering function.")); endif else switch (this.ClusteringFunction) case 'kmeans' this.ClusteringSolutions(:, iter) = kmeans (UsableX, ... this.InspectedK(iter), 'Distance', this.Distance, ... 'EmptyAction', 'singleton', 'Replicates', 5); case 'linkage' if (! isempty (this.Distance)) ## use clusterdata Distance_tmp = this.Distance; LinkageMethod = 'average'; # for non euclidean methods if (strcmpi (this.Distance, 'sqeuclidean')) ## pdist uses different names for its algorithms Distance_tmp = 'squaredeuclidean'; LinkageMethod = 'ward'; elseif (strcmpi (this.Distance, 'euclidean')) LinkageMethod = 'ward'; endif this.ClusteringSolutions(:, iter) = clusterdata (UsableX,... 'MaxClust', this.InspectedK(iter), ... 'Distance', Distance_tmp, 'Linkage', LinkageMethod); else ## use linkage Z = linkage (this.DistanceVector, 'average'); this.ClusteringSolutions(:, iter) = ... cluster (Z, 'MaxClust', this.InspectedK(iter)); endif case 'gmdistribution' gmm = fitgmdist (UsableX, this.InspectedK(iter), ... 'SharedCov', true, 'Replicates', 5); this.ClusteringSolutions(:, iter) = cluster (gmm, UsableX); otherwise error (strcat ("SilhouetteEvaluation: unexpected", ... " error, report this bug.")); endswitch endif endif endfor endif ## get the silhouette values for every clustering for iter = 1 : length (this.InspectedK) ## do it only for the specified K values if (any (this.InspectedK(iter) == K)) ## Custom call to silhouette to avoid plotting any figures. The ## metric is the one this object was built with: hard-coding ## 'sqeuclidean' here made every Distance give the same answer. if (isempty (this.Distance)) metric = this.DistanceVector; else metric = this.Distance; endif si = silhouette (UsableX, this.ClusteringSolutions(:, iter), ... metric, 'DoNotPlot'); ## ClusterSilhouettes holds the mean silhouette of each cluster, one ## value per cluster, not the value of every observation. means = zeros (this.InspectedK(iter), 1); for k = 1 : this.InspectedK(iter) means(k) = mean (si(this.ClusteringSolutions(:, iter) == k)); endfor this.ClusterSilhouettes{iter} = means; if (strcmpi (this.ClusterPriors, 'empirical')) ## Weighted by cluster size, which is what averaging over the ## observations does this.CriterionValues(iter) = mean (si); else ## equal this.CriterionValues(iter) = mean (means); endif endif endfor ## A criterion that came out undefined everywhere leaves no optimum to ## report, and the solutions are echoed back only when this object built ## them: given a matrix of clusterings the caller already has them. if (all (isnan (this.CriterionValues))) this.OptimalIndex = []; this.OptimalK = NaN; this.OptimalY = []; else [~, this.OptimalIndex] = max (this.CriterionValues); this.OptimalK = this.InspectedK(this.OptimalIndex(1)); if (isempty (this.ClusteringFunction)) this.OptimalY = []; else this.OptimalY = this.ClusteringSolutions(:, this.OptimalIndex(1)); endif endif endfunction endmethods endclassdef %!test %! load fisheriris %! eva = evalclusters (meas, 'kmeans', 'silhouette', 'KList', [1:6]); %! assert_equal (class (eva), "SilhouetteEvaluation"); %!function C = count_calls_silhouette (X, k) %! global count_calls_silhouette_n; %! count_calls_silhouette_n += 1; %! C = mod ((0 : rows (X) - 1)', k) + 1; %!endfunction %!test %! ## custom function must be called exactly once per inspected K %! global count_calls_silhouette_n; %! count_calls_silhouette_n = 0; %! evalclusters (rand (20, 2), @count_calls_silhouette, ... %! 'silhouette', 'KList', [2, 3]); %! assert_equal (count_calls_silhouette_n, 2); %! clear -global count_calls_silhouette_n; statistics-release-1.9.2/inst/Clustering/cluster.m000066400000000000000000000245421524624707500223110ustar00rootroot00000000000000## Copyright (C) 2021 Stefano Guidoni ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{T} =} cluster (@var{Z}, "Cutoff", @var{C}) ## @deftypefnx {statistics} {@var{T} =} cluster (@var{Z}, "Cutoff", @var{C}, "Depth", @var{D}) ## @deftypefnx {statistics} {@var{T} =} cluster (@var{Z}, "Cutoff", @var{C}, "Criterion", @var{criterion}) ## @deftypefnx {statistics} {@var{T} =} cluster (@var{Z}, "MaxClust", @var{N}) ## ## Define clusters from an agglomerative hierarchical cluster tree. ## ## Given a hierarchical cluster tree @var{Z} generated by the @code{linkage} ## function, @code{cluster} defines clusters, using a threshold value @var{C} to ## identify new clusters ('Cutoff') or according to a maximum number of desired ## clusters @var{N} ('MaxClust'). ## ## @var{criterion} is used to choose the criterion for defining clusters, which ## can be either "inconsistent" (default) or "distance". When using ## "inconsistent", @code{cluster} compares the threshold value @var{C} to the ## inconsistency coefficient of each link; when using "distance", @code{cluster} ## compares the threshold value @var{C} to the height of each link. ## @var{D} is the depth used to evaluate the inconsistency coefficient, its ## default value is 2. ## ## @code{cluster} uses "distance" as a criterion for defining new clusters when ## it is used with the 'MaxClust' method. ## ## @seealso{clusterdata, dendrogram, inconsistent, kmeans, linkage, pdist} ## @end deftypefn function T = cluster (Z, opt, varargin) switch (lower (opt)) ## check the input case 'cutoff' if (nargin < 3) print_usage (); else C = varargin{1}; D = 2; criterion = 'inconsistent'; if (nargin > 3) pair_index = 2; while (pair_index < (nargin - 2)) switch (lower (varargin{pair_index})) case 'depth' D = varargin{pair_index + 1}; case 'criterion' criterion = varargin{pair_index + 1}; otherwise error ("cluster: unknown property '%s'.", varargin{pair_index}); endswitch pair_index += 2; endwhile endif endif if ((! (isscalar (C) || isvector (C))) || (C < 0)) error (strcat ("cluster: C must be a positive scalar", ... " or a vector of positive numbers.")); endif case 'maxclust' if (nargin != 3) print_usage (); else N = varargin{1}; C = []; endif if ((! (isscalar (N) || isvector (N))) || (N < 0)) error (strcat ("cluster: N must be a positive number", ... " or a vector of positive numbers.")); endif otherwise error ("cluster: unknown option '%s'.", opt); endswitch if (columns (Z) != 3 || ! isnumeric (Z) || ! (max (Z(end, 1:2)) == rows (Z) * 2)) error ("cluster: Z must be a matrix generated by the linkage function."); endif ## number of observations n = rows (Z) + 1; ## vector of values used by the threshold check vThresholds = []; ## starting number of clusters nClusters = 1; ## the return value is the matrix T, constituted by one or more vector vT T = []; vT = zeros (1, n); ## main logic switch (lower (opt)) case 'cutoff' switch (lower (criterion)) case 'inconsistent' vThresholds = inconsistent (Z, D)(:, 4); case 'distance' vThresholds = Z(:, 3); otherwise error ("cluster: unknown criterion '%s'.", criterion); endswitch ## A node is a cluster only when it and everything below it are under ## the cutoff. Testing the node alone leaves a low node holding a high ## descendant undivided, and so returns too few clusters. smax = subtree_maximum (Z, vThresholds, n); for k = 1:numel (C) T = [T; cut_by_cutoff(Z, n, smax, C(k))]; endfor case 'maxclust' ## Undoing the highest N-1 merges leaves exactly N groups. Cutting at ## the height of the Nth node from the top cannot: when merges tie there ## is no height between them, and on a tree whose merges all sit at one ## height -- every observation identical, say -- it separates nothing and ## every observation comes back in a cluster of its own. for k = 1:numel (N) T = [T; cut_by_count(Z, n, N(k))]; endfor endswitch T = T'; # return value endfunction ## Largest criterion value anywhere in the subtree each internal node roots. ## The rows of Z are in merge order, so a node's children are always earlier ## rows and one forward pass suffices. function s = subtree_maximum (Z, v, n) s = v(:); for i = 1:rows (Z) for j = 1:2 child = Z(i, j); if (child > n) s(i) = max (s(i), s(child - n)); endif endfor endfor endfunction ## Put every leaf below an internal node into one cluster. function t = collect_leaves (Z, n, index, label, t) for j = 1:2 child = Z(index, j); if (child > n) t = collect_leaves (Z, n, child - n, label, t); else t(child) = label; endif endfor endfunction ## Descend from the root, taking a node as a cluster as soon as its whole ## subtree is under the cutoff and splitting it otherwise. function t = cut_by_cutoff (Z, n, smax, c) t = zeros (1, n); k = 0; pending = rows (Z); while (! isempty (pending)) index = pending(end); pending(end) = []; if (smax(index) < c) k++; t = collect_leaves (Z, n, index, k, t); else for j = 1:2 child = Z(index, j); if (child > n) pending(end+1) = child - n; else k++; t(child) = k; endif endfor endif endwhile endfunction ## Perform the lowest n-N merges and let what remains be the clusters, so that ## exactly N of them come back however the merge heights fall. function t = cut_by_count (Z, n, N) if (N >= n) t = 1:n; return; endif keep = n - max (N, 1); comp = 1:n; # component of each leaf nodecomp = zeros (1, rows (Z)); # component of each internal node for i = 1:keep ca = node_component (Z(i, 1), n, comp, nodecomp); cb = node_component (Z(i, 2), n, comp, nodecomp); comp(comp == cb) = ca; nodecomp(i) = ca; nodecomp(nodecomp == cb) = ca; endfor [~, ~, t] = unique (comp); t = t(:)'; endfunction ## Component holding a node, whether it is a leaf or an internal node. function c = node_component (node, n, comp, nodecomp) if (node > n) c = nodecomp(node - n); else c = comp(node); endif endfunction ## Test input validation %!error cluster () %!error cluster ([1 1], 'Cutoff', 1) %!error cluster ([1 2 1], 'Bogus', 1) %!error cluster ([1 2 1], 'Cutoff', -1) %!error cluster ([1 2 1], 'Cutoff', 1, 'Bogus', 1) ## Test output ## The partitions below are MATLAB R2024a's on the same data. Cluster numbers ## are arbitrary, so the tests compare groupings rather than labels. %!shared X, Z %! X = [0, 0; 0.1, 0.1; 0.2, 0; 5, 5; 5.1, 5.2; 5.2, 5.0; 10, 0; ... %! 10.1, 0.2; 20, 20; 0.05, 0.3]; %! Z = linkage (pdist (X), "single"); %!test # MaxClust returns exactly the number of clusters asked for %! for n = 1:5 %! assert_equal (numel (unique (cluster (Z, "MaxClust", n))), n); %! endfor %!test # and the groupings are MATLAB's %! t = cluster (Z, "MaxClust", 3); %! assert_equal (numel (unique (t([1, 2, 3, 4, 5, 6, 10]))), 1); %! assert_equal (numel (unique (t([7, 8]))), 1); %! assert_equal (t(9) != t(1) && t(7) != t(1), true); %!test # merges at equal heights still divide: a height threshold cannot %! ## split a tree whose every merge sits at the same height, which is what %! ## five identical observations produce. %! Zflat = linkage (pdist (ones (5, 2)), "single"); %! assert_equal (all (Zflat(:,3) == 0), true); %! for n = 1:5 %! assert_equal (numel (unique (cluster (Zflat, "MaxClust", n))), n); %! endfor %!test # a tie at the cut does not cost a cluster either %! Ztie = [1, 2, 1; 3, 4, 2; 6, 7, 2; 5, 8, 3]; %! for n = 1:5 %! assert_equal (numel (unique (cluster (Ztie, "MaxClust", n))), n); %! endfor %!test # a cluster is a node whose whole subtree is under the cutoff %! assert_equal (numel (unique (cluster (Z, "Cutoff", 0.5))), 6); %! assert_equal (numel (unique (cluster (Z, "Cutoff", 0.8))), 4); %! assert_equal (numel (unique (cluster (Z, "Cutoff", 1.2))), 1); %!test # the depth the inconsistency is measured over reaches the result %! assert_equal (numel (unique (cluster (Z, "Cutoff", 0.9, "Depth", 2))), 4); %! assert_equal (numel (unique (cluster (Z, "Cutoff", 0.9, "Depth", 3))), 5); %! t = cluster (Z, "Cutoff", 0.9, "Depth", 3); %! assert_equal (numel (unique (t([1, 2, 3]))), 1); %! assert_equal (numel (unique (t([4, 5, 6]))), 1); %! assert_equal (t(10) != t(1), true); %!test # the distance criterion is unchanged %! assert_equal (numel (unique (cluster (Z, "Cutoff", 1, ... %! "Criterion", "distance"))), 4); %! assert_equal (numel (unique (cluster (Z, "Cutoff", 30, ... %! "Criterion", "distance"))), 1); %!test # a vector of cutoffs gives one column per cutoff %! T = cluster (Z, "MaxClust", [2, 3, 4]); %! assert_equal (size (T), [10, 3]); %! assert_equal (max (T), [2, 3, 4]); %!test # well separated groups come out whole %! Xs = [randn(10, 2) * 0.05 + 1; randn(10, 2) * 0.05 - 1]; %! t = cluster (linkage (pdist (Xs), "ward"), "MaxClust", 2); %! assert_equal (numel (unique (t)), 2); %! assert_equal (numel (unique (t(1:10))), 1); %! assert_equal (numel (unique (t(11:20))), 1); %! assert_equal (t(1) != t(11), true); statistics-release-1.9.2/inst/Clustering/clusterdata.m000066400000000000000000000120011524624707500231260ustar00rootroot00000000000000## Copyright (C) 2021 Stefano Guidoni ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{T} =} clusterdata (@var{X}, @var{cutoff}) ## @deftypefnx {statistics} {@var{T} =} clusterdata (@var{X}, @var{Name}, @var{Value}) ## ## Wrapper function for @code{linkage} and @code{cluster}. ## ## If @var{cutoff} is used, then @code{clusterdata} calls @code{linkage} and ## @code{cluster} with default value, using @var{cutoff} as a threshold value ## for @code{cluster}. If @var{cutoff} is an integer and greater or equal to 2, ## then @var{cutoff} is interpreted as the maximum number of cluster desired ## and the "MaxClust" option is used for @code{cluster}. ## ## If @var{cutoff} is not used, then @code{clusterdata} expects a list of pair ## arguments. Then you must specify either the "Cutoff" or "MaxClust" option ## for @code{cluster}. The method and metric used by @code{linkage}, are ## defined through the "linkage" and "distance" arguments. ## ## @seealso{cluster, dendrogram, inconsistent, kmeans, linkage, pdist} ## @end deftypefn function T = clusterdata (X, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("clusterdata: function called with too few input arguments."); endif linkage_criterion = 'single'; distance_method = 'euclidean'; savememory = 'off'; clustering_method = []; criterion = 'inconsistent'; D = 2; if (isnumeric (varargin{1})) # clusterdata (X, cutoff) C = varargin{1}; if (fix (C) == C && (C >= 2)) clustering_method = 'MaxClust'; else clustering_method = 'Cutoff'; endif else # clusterdata (Name, Value) pair_index = 1; while (pair_index < (nargin - 1)) switch (lower (varargin{pair_index})) case 'criterion' criterion = varargin{pair_index + 1}; case 'cutoff' clustering_method = 'Cutoff'; C = varargin{pair_index + 1}; case 'depth' D = varargin{pair_index + 1}; case 'distance' distance_method = varargin{pair_index + 1}; case 'linkage' linkage_criterion = varargin{pair_index + 1}; case 'maxclust' clustering_method = 'MaxClust'; C = varargin{pair_index + 1}; case 'savememory' savememory = varargin{pair_index + 1}; otherwise error ("clusterdata: unknown property %s", varargin{pair_index}); endswitch pair_index += 2; endwhile endif if (isempty (clustering_method)) error (strcat ("clusterdata: you must specify either 'MaxClust'", ... " or 'Cutoff' when using name-value arguments.")); endif ## main body Z = linkage (X, linkage_criterion, distance_method, 'savememory'); if (strcmp (lower (clustering_method), 'cutoff')) T = cluster (Z, clustering_method, C, 'Criterion', criterion, 'Depth', D); else T = cluster (Z, clustering_method, C); endif endfunction %!demo %! rng (42); %! r1 = randn (10, 2) * 0.25 + 1; %! r2 = randn (20, 2) * 0.5 - 1; %! X = [r1; r2]; %! %! wnl = warning ("off", 'Octave:linkage_savemem', 'local'); %! T = clusterdata (X, 'linkage', 'ward', 'MaxClust', 2); %! scatter (X(:,1), X(:,2), 36, T, 'filled'); ## Test input validation %!error ... %! clusterdata () %!error ... %! clusterdata (1) %!error clusterdata ([1 1], 'Bogus', 1) %!error clusterdata ([1 1], 'Depth', 1) ## MaxClust must deliver the number of clusters it was asked for, whatever the ## merge heights do. Verified against MATLAB R2024a. %!test %! assert_equal (numel (unique (clusterdata (ones (5, 2), "MaxClust", 2))), 2); %! assert_equal (numel (unique (clusterdata (ones (5, 2), "MaxClust", 4))), 4); %!test # the depth reaches the result, as it does in cluster %! X = [0, 0; 0.1, 0.1; 0.2, 0; 5, 5; 5.1, 5.2; 5.2, 5.0; 10, 0; ... %! 10.1, 0.2; 20, 20; 0.05, 0.3]; %! t2 = clusterdata (X, "Cutoff", 0.9, "Depth", 2); %! t3 = clusterdata (X, "Cutoff", 0.9, "Depth", 3); %! assert_equal (numel (unique (t2)), 4); %! assert_equal (numel (unique (t3)), 5); %! t = clusterdata (X, "MaxClust", 3); %! assert_equal (numel (unique (t)), 3); %! assert_equal (numel (unique (t([7, 8]))), 1); statistics-release-1.9.2/inst/Clustering/cophenet.m000066400000000000000000000110751524624707500224320ustar00rootroot00000000000000## Copyright (C) 2021 Stefano Guidoni ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{c}, @var{d}] =} cophenet (@var{Z}, @var{y}) ## ## Compute the cophenetic correlation coefficient. ## ## The cophenetic correlation coefficient @var{C} of a hierarchical cluster tree ## @var{Z} is the linear correlation coefficient between the cophenetic ## distances @var{d} and the euclidean distances @var{y}. ## @tex ## \def\frac#1#2{{\begingroup#1\endgroup\over#2}} ## $$ c = \frac {\sum_{i < j}(Y_{ij}-{\bar {y}})(Z_{ij}-{\bar{z}})} ## {\sqrt{\sum_{i < j}(Y_{ij}-{\bar {y}})^2(Z_{ij}-{\bar{z}})^2}} $$ ## @end tex ## ## It is a measure of the similarity between the distance of the leaves, as seen ## in the tree, and the distance of the original data points, which were used to ## build the tree. When this similarity is greater, that is the coefficient is ## closer to 1, the tree renders an accurate representation of the distances ## between the original data points. ## ## @var{Z} is a hierarchical cluster tree, as the output of @code{linkage}. ## @var{y} is a vector of euclidean distances, as the output of @code{pdist}. ## ## The optional output @var{d} is a vector of cophenetic distances, in the same ## lower triangular format as @var{y}. The cophenetic distance between two data ## points is the height of the lowest common node of the tree. ## ## @seealso{cluster, dendrogram, inconsistent, linkage, pdist, squareform} ## @end deftypefn function [c, d] = cophenet (Z, y) ## Check input arguments if (nargin < 2) error ("cophenet: function called with too few input arguments."); endif ## Z must be a tree [m w] = size (Z); if ((w != 3) || (! isnumeric (Z)) || (! (max (Z(end,1:2)) == m * 2))) error ("cophenet: Z must be a matrix as generated by the linkage function."); endif ## Data set size n = m + 1; ## Y must be a vector of distances if ((! isnumeric (y)) || (length (y) != (n - 1) * n / 2)) error ("cophenet: Y must be a vector of euclidean distances."); endif ## Compute the cophenetic distances d d = zeros (1, length (y)); N = sparse ((m - 1), m); # to keep track of the leaves from each branch for i = 1 : m l_n = Z(i, 1); r_n = Z(i, 2); if (l_n > n) l_v = nonzeros (N(l_n - n, :)); # the list of leaves from the left branch else l_v = l_n; endif if (r_n > n) r_v = nonzeros (N(r_n - n, :)); # the list of leaves from the right branch else r_v = r_n; endif j_max = length (l_v); k_max = length (r_v); ## Keep track of the leaves in each sub-branch, i.e. node; ## this does not matter for the last node, which includes all leaves if (i < m) N(i, 1 : (j_max + k_max)) = [l_v' r_v']; endif for j = 1 : j_max for k = 1: k_max ## d is in the same format as y if (l_v(j) < r_v(k)) index = (l_v(j) - 1) * m - sum (1 : (l_v(j) - 2)) + (r_v(k) - l_v(j)); else index = (r_v(k) - 1) * m - sum (1 : (r_v(k) - 2)) + (l_v(j) - r_v(k)); endif d(index) = Z(i, 3); endfor endfor endfor ## Compute the cophenetic correlation c y_mean = mean (y); z_mean = mean (d); Y_sigma = y - y_mean; Z_sigma = d - z_mean; c = sum (Z_sigma .* Y_sigma) / sqrt (sum (Y_sigma .^ 2) * sum (Z_sigma .^ 2)); endfunction %!demo %! rng (42); %! X = randn (10,2); %! y = pdist (X); %! Z = linkage (y, 'average'); %! cophenet (Z, y) ## Test input validation %!error cophenet () %!error cophenet (1) %!error ... %! cophenet (ones (2,2), 1) %!error ... %! cophenet ([1 2 1], 'a') %!error ... %! cophenet ([1 2 1], [1 2]) statistics-release-1.9.2/inst/Clustering/dbscan.m000066400000000000000000000255251524624707500220640ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{idx} =} dbscan (@var{X}, @var{epsilon}, @var{minpts}) ## @deftypefnx {statistics} {@var{idx} =} dbscan (@var{D}, @var{epsilon}, @var{minpts}, @qcode{'Distance'}, @qcode{'precomputed'}) ## @deftypefnx {statistics} {@var{idx} =} dbscan (@dots{}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{idx}, @var{corepts}] =} dbscan (@dots{}) ## ## Density-Based Spatial Clustering of Applications with Noise (DBSCAN). ## ## @code{@var{idx} = dbscan (@var{X}, @var{epsilon}, @var{minpts})} partitions ## the observations in the @math{N*P} numeric matrix @var{X} into clusters ## using the DBSCAN algorithm with neighborhood radius @var{epsilon} and ## minimum number of neighbors @var{minpts}. Rows of @var{X} correspond to ## observations and columns correspond to features or variables. @var{epsilon} ## must be a nonnegative scalar and @var{minpts} a positive integer scalar. ## @var{idx} is an @math{N*1} vector of cluster indices, numbered @math{1} to ## the number of clusters found; observations flagged as noise are assigned the ## value @math{-1}. ## ## A point is a @emph{core point} when at least @var{minpts} observations ## (@strong{including the point itself}) lie within distance @var{epsilon} of ## it. Clusters grow from core points to every observation that is ## density-reachable from them; a non-core observation that lies within ## @var{epsilon} of a core point becomes a @emph{border point} and joins that ## point's cluster, while an observation that is neither core nor within reach ## of a core point is labelled noise. A border point that is reachable from ## more than one cluster is assigned to the first cluster that reaches it, ## following the order of the observations in @var{X}. ## ## @code{@var{idx} = dbscan (@var{D}, @var{epsilon}, @var{minpts}, ## @qcode{'Distance'}, @qcode{'precomputed'})} treats the @math{N*N} matrix ## @var{D} as a precomputed matrix of pairwise distances between observations, ## such as the output of @code{pdist2}; @qcode{@var{D}(i,j)} is the distance ## between observations @math{i} and @math{j}. ## ## @code{[@var{idx}, @var{corepts}] = dbscan (@dots{})} also returns an ## @math{N*1} logical vector @var{corepts} that is @qcode{true} for each ## observation that is a core point. ## ## Additional parameters can be specified by @qcode{Name-Value} pair arguments. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Distance'} @tab is the distance metric used to find neighbors, ## specified as one of the metrics accepted by @code{rangesearch} ## (@qcode{'euclidean'} by default, and also @qcode{'seuclidean'}, ## @qcode{'cityblock'}, @qcode{'chebychev'}, @qcode{'minkowski'}, ## @qcode{'mahalanobis'}, @qcode{'cosine'}, @qcode{'correlation'}, ## @qcode{'spearman'}, @qcode{'hamming'}, @qcode{'jaccard'}, or a custom ## distance function handle), or the string @qcode{'precomputed'} to interpret ## the first input as a matrix of pairwise distances. ## ## @item @qcode{'P'} @tab is the Minkowski distance exponent, a positive scalar. ## This argument is only valid when the selected distance metric is ## @qcode{'minkowski'}. By default it is 2. ## ## @item @qcode{'Scale'} @tab is the scale parameter for the standardized ## Euclidean distance, a nonnegative numeric vector of length equal to the ## number of columns in @var{X}. This argument is only valid when the selected ## distance metric is @qcode{'seuclidean'}. ## ## @item @qcode{'Cov'} @tab is the covariance matrix for the mahalanobis ## distance, a positive definite matrix matching the number of columns in ## @var{X}. This argument is only valid when the selected distance metric is ## @qcode{'mahalanobis'}. ## @end multitable ## ## @seealso{kmeans, rangesearch, pdist2, knnsearch} ## @end deftypefn function [idx, corepts] = dbscan (X, epsilon, minpts, varargin) ## Check number of input arguments if (nargin < 3) error ("dbscan: too few input arguments."); endif ## Validate X if (! isnumeric (X) || ! isreal (X) || ndims (X) != 2 || isempty (X)) error ("dbscan: X must be a nonempty real numeric matrix."); endif ## Validate epsilon if (! isscalar (epsilon) || ! isnumeric (epsilon) || ! isreal (epsilon) || epsilon < 0) error ("dbscan: EPSILON must be a nonnegative scalar."); endif ## Validate minpts if (! isscalar (minpts) || ! isnumeric (minpts) || ! isreal (minpts) || minpts < 1 || fix (minpts) != minpts) error ("dbscan: MINPTS must be a positive integer scalar."); endif ## Detect a precomputed distance matrix among the optional arguments precomputed = false; for i = 1:2:numel (varargin) if (ischar (varargin{i}) && strcmpi (varargin{i}, 'Distance') && i < numel (varargin) && ischar (varargin{i+1}) && strcmpi (varargin{i+1}, 'precomputed')) precomputed = true; endif endfor ## Build the epsilon-neighborhood (including the point itself) of each ## observation. For a precomputed distance matrix simply threshold each row; ## otherwise delegate the metric handling to rangesearch, which already ## includes the query point and applies a "distance <= epsilon" cutoff. if (precomputed) if (numel (varargin) != 2) error (strcat ("dbscan: no other Name-Value arguments are allowed", ... " with the 'precomputed' distance.")); endif if (rows (X) != columns (X)) error ("dbscan: X must be a square distance matrix for 'precomputed'."); endif N = rows (X); neigh = cell (N, 1); for i = 1:N neigh{i} = find (X(i,:) <= epsilon)(:); endfor else N = rows (X); neigh = rangesearch (X, X, epsilon, varargin{:}); neigh = cellfun (@(c) c(:), neigh, "UniformOutput", false); endif ## A point is a core point when its neighborhood (self included) holds at ## least minpts observations. corepts = cellfun (@numel, neigh) >= minpts; ## Canonical DBSCAN scan. Labels: 0 = unvisited, -1 = noise, k = cluster k. labels = zeros (N, 1); C = 0; for i = 1:N if (labels(i) != 0) continue; # already assigned to a cluster or as noise endif if (! corepts(i)) labels(i) = -1; # tentatively noise (may become a border) continue; endif ## Start a new cluster and expand it over the growing seed queue. C += 1; labels(i) = C; seeds = neigh{i}; k = 1; while (k <= numel (seeds)) q = seeds(k); k += 1; if (labels(q) == -1) labels(q) = C; # border point reclaimed from noise elseif (labels(q) == 0) labels(q) = C; if (corepts(q)) seeds = [seeds; neigh{q}]; # density-reachable: keep expanding endif endif endwhile endfor idx = labels; endfunction %!demo %! ## Cluster a set of points with two dense blobs and scattered noise. %! rng (42); %! X = [randn(30,2)*0.3 + 2; randn(30,2)*0.3 - 2; 5*(rand(6,2)-0.5)]; %! idx = dbscan (X, 0.6, 4); %! gscatter (X(:,1), X(:,2), idx); %! title ("dbscan: clusters (>=0) and noise (-1)"); ## Two well-separated clusters and one noise point (euclidean) %!test %! X = [0 0; 0 1; 1 0; 1 1; 10 10; 10 11; 11 10; 5 5]; %! [idx, cp] = dbscan (X, 1.5, 3); %! assert_equal (idx, [1; 1; 1; 1; 2; 2; 2; -1]); %! assert_equal (cp, logical ([1; 1; 1; 1; 1; 1; 1; 0])); ## Border point reachable from two clusters joins the first one reached %!test %! X = [0; 0.3; 0.6; 0.9; 2.5; 2.8; 3.1; 3.4; 1.7]; %! [idx, cp] = dbscan (X, 1, 4); %! assert_equal (idx, [1; 1; 1; 1; 2; 2; 2; 2; 1]); %! assert_equal (cp, logical ([1; 1; 1; 1; 1; 1; 1; 1; 0])); ## Cluster order follows the order of the observations, not geometry %!test %! X = [2.5; 2.8; 3.1; 3.4; 0; 0.3; 0.6; 0.9; 1.7]; %! idx = dbscan (X, 1, 4); %! assert_equal (idx, [1; 1; 1; 1; 2; 2; 2; 2; 1]); ## minpts counts the point itself: two neighbors + self meets minpts = 3 %!test %! X = [0; 1; 2]; %! [idx, cp] = dbscan (X, 1, 2); %! assert_equal (idx, [1; 1; 1]); %! assert_equal (cp, logical ([1; 1; 1])); ## minpts = 1 makes every point a core point and yields no noise %!test %! X = [0; 0; 5; 5; 10]; %! [idx, cp] = dbscan (X, 0.5, 1); %! assert_equal (idx, [1; 1; 2; 2; 3]); %! assert_equal (cp, logical ([1; 1; 1; 1; 1])); ## Precomputed distance matrix matches the euclidean result %!test %! X = [0 0; 0 1; 1 0; 1 1; 10 10; 10 11; 11 10; 5 5]; %! D = pdist2 (X, X); %! [idx, cp] = dbscan (D, 1.5, 3, "Distance", "precomputed"); %! assert_equal (idx, [1; 1; 1; 1; 2; 2; 2; -1]); %! assert_equal (cp, logical ([1; 1; 1; 1; 1; 1; 1; 0])); ## A non-euclidean metric is passed through to rangesearch %!test %! X = [0 0; 0 1; 1 0; 1 1; 10 10; 10 11; 11 10; 5 5]; %! idx = dbscan (X, 2, 3, "Distance", "cityblock"); %! assert_equal (idx, [1; 1; 1; 1; 2; 2; 2; -1]); ## Every point is noise when no neighborhood reaches minpts %!test %! X = [0; 10; 20; 30]; %! [idx, cp] = dbscan (X, 1, 2); %! assert_equal (idx, [-1; -1; -1; -1]); %! assert_equal (cp, logical ([0; 0; 0; 0])); ## Test input validation %!error dbscan (1) %!error dbscan (1, 1) %!error dbscan ([], 1, 1) %!error dbscan ("a", 1, 1) %!error dbscan (i, 1, 1) %!error dbscan (ones (3,2), [1 2], 1) %!error dbscan (ones (3,2), -1, 1) %!error dbscan (ones (3,2), "a", 1) %!error dbscan (ones (3,2), 1, 0) %!error dbscan (ones (3,2), 1, 1.5) %!error dbscan (ones (3,2), 1, [1 2]) %!error ... %! dbscan (ones (3,2), 1, 1, "Distance", "precomputed") %!error ... %! dbscan (ones (3,3), 1, 1, "Distance", "precomputed", "P", 3) statistics-release-1.9.2/inst/Clustering/doc-cache000066400000000000000000002571341524624707500222100ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 112 # name: # type: sq_string # elements: 1 # length: 26 CalinskiHarabaszEvaluation # name: # type: sq_string # elements: 1 # length: 755 statistics: CalinskiHarabaszEvaluation Calinski-Harabasz clustering evaluation. A CalinskiHarabaszEvaluation object contains the results of evaluating clustering solutions using the Calinski-Harabasz criterion. The Calinski-Harabasz index (also known as the Variance Ratio Criterion) is determined by the ratio of the between-cluster sum of squares (SSB) to the within-cluster sum of squares (SSW). A higher Calinski-Harabasz index value indicates a better clustering solution, implying that clusters are dense and well-separated. Create a CalinskiHarabaszEvaluation object by using the evalclusters function with the 'CalinskiHarabasz' criterion. See also: evalclusters, ClusterCriterion, DaviesBouldinEvaluation, GapEvaluation, SilhouetteEvaluation # name: # type: sq_string # elements: 1 # length: 40 Calinski-Harabasz clustering evaluation. # name: # type: sq_string # elements: 1 # length: 53 CalinskiHarabaszEvaluation.CalinskiHarabaszEvaluation # name: # type: sq_string # elements: 1 # length: 982 statistics: obj = CalinskiHarabaszEvaluation ( x , clust , KList ) Construct a CalinskiHarabaszEvaluation object to evaluate clustering solutions with the Calinski-Harabasz criterion. obj = CalinskiHarabaszEvaluation ( x , clust , KList ) clusters the data in x for every cluster count in KList and evaluates each solution. The evaluation runs at construction, so obj arrives with its CriterionValues and OptimalK already set. x is an N×P numeric matrix of observations (rows) and predictors (columns). A row holding a NaN is left out of NumObservations . clust names the clustering method, one of 'kmeans' , 'linkage' and 'gmdistribution' ; or a function handle that clusters the data; or an N×M numeric matrix of clustering solutions computed elsewhere, one column per cluster count, in which case ClusteringFunction is left empty. KList is a vector of positive integers, the cluster counts to inspect. evalclusters is the usual way to create one of these objects. # name: # type: sq_string # elements: 1 # length: 116 Construct a CalinskiHarabaszEvaluation object to evaluate clustering solutions with the Calinski-Harabasz criterion. # name: # type: sq_string # elements: 1 # length: 45 CalinskiHarabaszEvaluation.ClusteringFunction # name: # type: sq_string # elements: 1 # length: 290 ClusterCriterion: property ClusteringFunction Clustering algorithm A character vector or a function handle specifying the clustering algorithm used to generate the clustering solutions. It can be empty if the clustering solutions are passed as an input matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Clustering algorithm # name: # type: sq_string # elements: 1 # length: 40 CalinskiHarabaszEvaluation.CriterionName # name: # type: sq_string # elements: 1 # length: 202 ClusterCriterion: property CriterionName Name of the evaluation criterion A character vector specifying the name of the criterion used to evaluate the clustering solutions. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Name of the evaluation criterion # name: # type: sq_string # elements: 1 # length: 42 CalinskiHarabaszEvaluation.CriterionValues # name: # type: sq_string # elements: 1 # length: 196 ClusterCriterion: property CriterionValues Criterion values A numeric vector containing the values generated by the evaluation criterion for each clustering solution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 16 Criterion values # name: # type: sq_string # elements: 1 # length: 37 CalinskiHarabaszEvaluation.InspectedK # name: # type: sq_string # elements: 1 # length: 172 ClusterCriterion: property InspectedK List of the number of clusters A numeric vector containing the list of the number of clusters evaluated. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 List of the number of clusters # name: # type: sq_string # elements: 1 # length: 34 CalinskiHarabaszEvaluation.Missing # name: # type: sq_string # elements: 1 # length: 177 ClusterCriterion: property Missing Missing values A logical vector indicating which observations in the data matrix contain missing values ( NaN ). This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Missing values # name: # type: sq_string # elements: 1 # length: 42 CalinskiHarabaszEvaluation.NumObservations # name: # type: sq_string # elements: 1 # length: 176 ClusterCriterion: property NumObservations Number of observations An integer specifying the number of non-missing observations in the data matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 35 CalinskiHarabaszEvaluation.OptimalK # name: # type: sq_string # elements: 1 # length: 180 ClusterCriterion: property OptimalK Optimal number of clusters An integer specifying the optimal number of clusters based on the evaluation criterion. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Optimal number of clusters # name: # type: sq_string # elements: 1 # length: 35 CalinskiHarabaszEvaluation.OptimalY # name: # type: sq_string # elements: 1 # length: 199 ClusterCriterion: property OptimalY Optimal clustering solution A numeric vector representing the clustering solution that corresponds to the optimal number of clusters. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Optimal clustering solution # name: # type: sq_string # elements: 1 # length: 28 CalinskiHarabaszEvaluation.X # name: # type: sq_string # elements: 1 # length: 141 ClusterCriterion: property X Data used for clustering A numeric matrix containing the data used for clustering. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Data used for clustering # name: # type: sq_string # elements: 1 # length: 31 CalinskiHarabaszEvaluation.addK # name: # type: sq_string # elements: 1 # length: 304 CalinskiHarabaszEvaluation: obj = addK ( obj , K ) Add new cluster sizes for evaluation. addK ( obj , K ) evaluates clustering solutions for the number of clusters specified in the vector K and adds them to the CalinskiHarabaszEvaluation object obj . See also: CalinskiHarabaszEvaluation, evalclusters # name: # type: sq_string # elements: 1 # length: 37 Add new cluster sizes for evaluation. # name: # type: sq_string # elements: 1 # length: 31 CalinskiHarabaszEvaluation.plot # name: # type: sq_string # elements: 1 # length: 396 CalinskiHarabaszEvaluation: plot ( obj ) CalinskiHarabaszEvaluation: h = plot ( obj ) Plot the clustering evaluation results. plot ( obj ) plots the Calinski-Harabasz criterion values against the number of clusters. The optimal number of clusters is marked with an asterisk. h = plot ( obj ) additionally returns the handle to the plot axes. See also: CalinskiHarabaszEvaluation, evalclusters # name: # type: sq_string # elements: 1 # length: 39 Plot the clustering evaluation results. # name: # type: sq_string # elements: 1 # length: 16 ClusterCriterion # name: # type: sq_string # elements: 1 # length: 341 statistics: ClusterCriterion A clustering evaluation object. The ClusterCriterion is a superclass for clustering evaluation objects, which are created by the evalclusters function. It is not meant to be instantiated directly. See also: evalclusters, CalinskiHarabaszEvaluation, DaviesBouldinEvaluation, GapEvaluation, SilhouetteEvaluation # name: # type: sq_string # elements: 1 # length: 31 A clustering evaluation object. # name: # type: sq_string # elements: 1 # length: 33 ClusterCriterion.ClusterCriterion # name: # type: sq_string # elements: 1 # length: 227 ClusterCriterion: obj = ClusterCriterion ( x , clust , KList ) Create a ClusterCriterion object. ClusterCriterion is a superclass and is not meant to be instantiated directly. Use evalclusters instead. See also: evalclusters # name: # type: sq_string # elements: 1 # length: 33 Create a ClusterCriterion object. # name: # type: sq_string # elements: 1 # length: 35 ClusterCriterion.ClusteringFunction # name: # type: sq_string # elements: 1 # length: 290 ClusterCriterion: property ClusteringFunction Clustering algorithm A character vector or a function handle specifying the clustering algorithm used to generate the clustering solutions. It can be empty if the clustering solutions are passed as an input matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Clustering algorithm # name: # type: sq_string # elements: 1 # length: 30 ClusterCriterion.CriterionName # name: # type: sq_string # elements: 1 # length: 202 ClusterCriterion: property CriterionName Name of the evaluation criterion A character vector specifying the name of the criterion used to evaluate the clustering solutions. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Name of the evaluation criterion # name: # type: sq_string # elements: 1 # length: 32 ClusterCriterion.CriterionValues # name: # type: sq_string # elements: 1 # length: 196 ClusterCriterion: property CriterionValues Criterion values A numeric vector containing the values generated by the evaluation criterion for each clustering solution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 16 Criterion values # name: # type: sq_string # elements: 1 # length: 27 ClusterCriterion.InspectedK # name: # type: sq_string # elements: 1 # length: 172 ClusterCriterion: property InspectedK List of the number of clusters A numeric vector containing the list of the number of clusters evaluated. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 List of the number of clusters # name: # type: sq_string # elements: 1 # length: 24 ClusterCriterion.Missing # name: # type: sq_string # elements: 1 # length: 177 ClusterCriterion: property Missing Missing values A logical vector indicating which observations in the data matrix contain missing values ( NaN ). This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Missing values # name: # type: sq_string # elements: 1 # length: 32 ClusterCriterion.NumObservations # name: # type: sq_string # elements: 1 # length: 176 ClusterCriterion: property NumObservations Number of observations An integer specifying the number of non-missing observations in the data matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 25 ClusterCriterion.OptimalK # name: # type: sq_string # elements: 1 # length: 180 ClusterCriterion: property OptimalK Optimal number of clusters An integer specifying the optimal number of clusters based on the evaluation criterion. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Optimal number of clusters # name: # type: sq_string # elements: 1 # length: 25 ClusterCriterion.OptimalY # name: # type: sq_string # elements: 1 # length: 199 ClusterCriterion: property OptimalY Optimal clustering solution A numeric vector representing the clustering solution that corresponds to the optimal number of clusters. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Optimal clustering solution # name: # type: sq_string # elements: 1 # length: 18 ClusterCriterion.X # name: # type: sq_string # elements: 1 # length: 141 ClusterCriterion: property X Data used for clustering A numeric matrix containing the data used for clustering. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Data used for clustering # name: # type: sq_string # elements: 1 # length: 21 ClusterCriterion.addK # name: # type: sq_string # elements: 1 # length: 166 ClusterCriterion: obj = addK ( obj , k ) Add a new list of cluster numbers to evaluate. addK adds a new list of cluster numbers, k , to the ClusterCriterion object. # name: # type: sq_string # elements: 1 # length: 46 Add a new list of cluster numbers to evaluate. # name: # type: sq_string # elements: 1 # length: 24 ClusterCriterion.compact # name: # type: sq_string # elements: 1 # length: 639 ClusterCriterion: obj = compact ( obj ) Create a compact clustering evaluation object. obj = compact ( obj ) returns an object of the same class holding the results of the evaluation but none of the data it was computed from, which is useful when the evaluation is kept and the sample is large. X , Missing and OptimalY are emptied; InspectedK , CriterionValues , OptimalK , NumObservations , CriterionName and ClusteringFunction are kept. The object compacted from is not changed. A compacted object can still be displayed and plotted, and compacting one again does nothing, but addK raises: there are no observations left to cluster. # name: # type: sq_string # elements: 1 # length: 46 Create a compact clustering evaluation object. # name: # type: sq_string # elements: 1 # length: 21 ClusterCriterion.plot # name: # type: sq_string # elements: 1 # length: 279 ClusterCriterion: h = plot ( obj ) Plot the clustering evaluation values. plot generates a plot of the criterion values against the number of clusters. The optimal number of clusters is marked with an asterisk. The optional return value, h , is a graphics handle to the plot. # name: # type: sq_string # elements: 1 # length: 38 Plot the clustering evaluation values. # name: # type: sq_string # elements: 1 # length: 23 DaviesBouldinEvaluation # name: # type: sq_string # elements: 1 # length: 637 statistics: DaviesBouldinEvaluation Davies-Bouldin object to evaluate clustering solutions A DaviesBouldinEvaluation object is a ClusterCriterion object used to evaluate clustering solutions using the Davies-Bouldin criterion. The Davies-Bouldin criterion is based on the ratio between the distances between clusters and within clusters, that is between centroids and between each datapoint and its centroid. The best solution according to the Davies-Bouldin criterion is the one that produces the lowest Davies-Bouldin value. See also: evalclusters, ClusterCriterion, CalinskiHarabaszEvaluation, GapEvaluation, SilhouetteEvaluation # name: # type: sq_string # elements: 1 # length: 54 Davies-Bouldin object to evaluate clustering solutions # name: # type: sq_string # elements: 1 # length: 42 DaviesBouldinEvaluation.ClusteringFunction # name: # type: sq_string # elements: 1 # length: 290 ClusterCriterion: property ClusteringFunction Clustering algorithm A character vector or a function handle specifying the clustering algorithm used to generate the clustering solutions. It can be empty if the clustering solutions are passed as an input matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Clustering algorithm # name: # type: sq_string # elements: 1 # length: 37 DaviesBouldinEvaluation.CriterionName # name: # type: sq_string # elements: 1 # length: 202 ClusterCriterion: property CriterionName Name of the evaluation criterion A character vector specifying the name of the criterion used to evaluate the clustering solutions. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Name of the evaluation criterion # name: # type: sq_string # elements: 1 # length: 39 DaviesBouldinEvaluation.CriterionValues # name: # type: sq_string # elements: 1 # length: 196 ClusterCriterion: property CriterionValues Criterion values A numeric vector containing the values generated by the evaluation criterion for each clustering solution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 16 Criterion values # name: # type: sq_string # elements: 1 # length: 47 DaviesBouldinEvaluation.DaviesBouldinEvaluation # name: # type: sq_string # elements: 1 # length: 970 statistics: obj = DaviesBouldinEvaluation ( x , clust , KList ) Construct a DaviesBouldinEvaluation object to evaluate clustering solutions with the Davies-Bouldin criterion. obj = DaviesBouldinEvaluation ( x , clust , KList ) clusters the data in x for every cluster count in KList and evaluates each solution. The evaluation runs at construction, so obj arrives with its CriterionValues and OptimalK already set. x is an N×P numeric matrix of observations (rows) and predictors (columns). A row holding a NaN is left out of NumObservations . clust names the clustering method, one of 'kmeans' , 'linkage' and 'gmdistribution' ; or a function handle that clusters the data; or an N×M numeric matrix of clustering solutions computed elsewhere, one column per cluster count, in which case ClusteringFunction is left empty. KList is a vector of positive integers, the cluster counts to inspect. evalclusters is the usual way to create one of these objects. # name: # type: sq_string # elements: 1 # length: 110 Construct a DaviesBouldinEvaluation object to evaluate clustering solutions with the Davies-Bouldin criterion. # name: # type: sq_string # elements: 1 # length: 34 DaviesBouldinEvaluation.InspectedK # name: # type: sq_string # elements: 1 # length: 172 ClusterCriterion: property InspectedK List of the number of clusters A numeric vector containing the list of the number of clusters evaluated. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 List of the number of clusters # name: # type: sq_string # elements: 1 # length: 31 DaviesBouldinEvaluation.Missing # name: # type: sq_string # elements: 1 # length: 177 ClusterCriterion: property Missing Missing values A logical vector indicating which observations in the data matrix contain missing values ( NaN ). This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Missing values # name: # type: sq_string # elements: 1 # length: 39 DaviesBouldinEvaluation.NumObservations # name: # type: sq_string # elements: 1 # length: 176 ClusterCriterion: property NumObservations Number of observations An integer specifying the number of non-missing observations in the data matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 32 DaviesBouldinEvaluation.OptimalK # name: # type: sq_string # elements: 1 # length: 180 ClusterCriterion: property OptimalK Optimal number of clusters An integer specifying the optimal number of clusters based on the evaluation criterion. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Optimal number of clusters # name: # type: sq_string # elements: 1 # length: 32 DaviesBouldinEvaluation.OptimalY # name: # type: sq_string # elements: 1 # length: 199 ClusterCriterion: property OptimalY Optimal clustering solution A numeric vector representing the clustering solution that corresponds to the optimal number of clusters. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Optimal clustering solution # name: # type: sq_string # elements: 1 # length: 25 DaviesBouldinEvaluation.X # name: # type: sq_string # elements: 1 # length: 141 ClusterCriterion: property X Data used for clustering A numeric matrix containing the data used for clustering. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Data used for clustering # name: # type: sq_string # elements: 1 # length: 28 DaviesBouldinEvaluation.addK # name: # type: sq_string # elements: 1 # length: 122 DaviesBouldinEvaluation: obj = addK ( obj , K ) Add new cluster numbers to inspect in the DaviesBouldinEvaluation object. # name: # type: sq_string # elements: 1 # length: 73 Add new cluster numbers to inspect in the DaviesBouldinEvaluation object. # name: # type: sq_string # elements: 1 # length: 28 DaviesBouldinEvaluation.plot # name: # type: sq_string # elements: 1 # length: 273 DaviesBouldinEvaluation: plot ( obj ) DaviesBouldinEvaluation: h = plot ( obj ) Plot Davies-Bouldin evaluation results. Plot the CriterionValues against InspectedK from the DaviesBouldinEvaluation ClusterCriterion to the current plot. Returns an axes handle if requested. # name: # type: sq_string # elements: 1 # length: 39 Plot Davies-Bouldin evaluation results. # name: # type: sq_string # elements: 1 # length: 13 GapEvaluation # name: # type: sq_string # elements: 1 # length: 643 statistics: GapEvaluation Gap evaluation for clustering solutions The GapEvaluation class implements the gap statistic criterion for evaluating clustering solutions. A GapEvaluation object is a specialization of ClusterCriterion and contains fields and methods to compute the gap statistic, its Monte-Carlo reference expectations, and to select the optimal number of clusters according to a chosen search method. Create a GapEvaluation object by using the evalclusters function or by calling the class constructor directly. See also: evalclusters, ClusterCriterion, CalinskiHarabaszEvaluation, DaviesBouldinEvaluation, SilhouetteEvaluation # name: # type: sq_string # elements: 1 # length: 39 Gap evaluation for clustering solutions # name: # type: sq_string # elements: 1 # length: 15 GapEvaluation.B # name: # type: sq_string # elements: 1 # length: 238 GapEvaluation: property B Number of reference datasets A positive integer specifying how many reference datasets are generated to compute the expected log within-cluster dispersion via Monte-Carlo simulation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Number of reference datasets # name: # type: sq_string # elements: 1 # length: 32 GapEvaluation.ClusteringFunction # name: # type: sq_string # elements: 1 # length: 290 ClusterCriterion: property ClusteringFunction Clustering algorithm A character vector or a function handle specifying the clustering algorithm used to generate the clustering solutions. It can be empty if the clustering solutions are passed as an input matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Clustering algorithm # name: # type: sq_string # elements: 1 # length: 27 GapEvaluation.CriterionName # name: # type: sq_string # elements: 1 # length: 202 ClusterCriterion: property CriterionName Name of the evaluation criterion A character vector specifying the name of the criterion used to evaluate the clustering solutions. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Name of the evaluation criterion # name: # type: sq_string # elements: 1 # length: 29 GapEvaluation.CriterionValues # name: # type: sq_string # elements: 1 # length: 196 ClusterCriterion: property CriterionValues Criterion values A numeric vector containing the values generated by the evaluation criterion for each clustering solution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 16 Criterion values # name: # type: sq_string # elements: 1 # length: 22 GapEvaluation.Distance # name: # type: sq_string # elements: 1 # length: 289 GapEvaluation: property Distance Distance metric A character vector or function handle specifying the distance measure passed to clustering routines (as accepted by pdist ). When a numeric vector is supplied it is interpreted as a precomputed distance vector. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Distance metric # name: # type: sq_string # elements: 1 # length: 26 GapEvaluation.ExpectedLogW # name: # type: sq_string # elements: 1 # length: 291 GapEvaluation: property ExpectedLogW Expected log within-cluster dispersion A numeric vector containing the Monte-Carlo estimate of the expected values for the natural logarithm of the within-cluster dispersion, computed across the generated reference datasets. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Expected log within-cluster dispersion # name: # type: sq_string # elements: 1 # length: 27 GapEvaluation.GapEvaluation # name: # type: sq_string # elements: 1 # length: 895 statistics: obj = GapEvaluation ( x , clust , KList ) statistics: obj = GapEvaluation ( x , clust , KList , B ) statistics: obj = GapEvaluation (…, name , value ) Construct a GapEvaluation object to evaluate clustering solutions using the gap statistic. obj = GapEvaluation ( x , clust , KList ) returns a GapEvaluation object configured to evaluate the clustering function specified by clust on the data matrix x for the list of cluster counts in KList . Optional inputs: B - Number of reference datasets to generate (default 100). 'Distance' - Distance metric name or function handle as accepted by pdist (default 'sqeuclidean' ). 'ReferenceDistribution' - Reference distribution to use (default 'pca' ; 'uniform' is supported). 'SearchMethod' - Method to select the optimal K; one of 'globalMaxSE' or 'firstMaxSE' (default 'globalMaxSE' ). See also: evalclusters, ClusterCriterion # name: # type: sq_string # elements: 1 # length: 90 Construct a GapEvaluation object to evaluate clustering solutions using the gap statistic. # name: # type: sq_string # elements: 1 # length: 24 GapEvaluation.InspectedK # name: # type: sq_string # elements: 1 # length: 172 ClusterCriterion: property InspectedK List of the number of clusters A numeric vector containing the list of the number of clusters evaluated. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 List of the number of clusters # name: # type: sq_string # elements: 1 # length: 18 GapEvaluation.LogW # name: # type: sq_string # elements: 1 # length: 232 GapEvaluation: property LogW Observed log within-cluster dispersion A numeric vector containing the observed values of the natural logarithm of the within-cluster dispersion computed on the actual data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Observed log within-cluster dispersion # name: # type: sq_string # elements: 1 # length: 21 GapEvaluation.Missing # name: # type: sq_string # elements: 1 # length: 177 ClusterCriterion: property Missing Missing values A logical vector indicating which observations in the data matrix contain missing values ( NaN ). This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Missing values # name: # type: sq_string # elements: 1 # length: 29 GapEvaluation.NumObservations # name: # type: sq_string # elements: 1 # length: 176 ClusterCriterion: property NumObservations Number of observations An integer specifying the number of non-missing observations in the data matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 22 GapEvaluation.OptimalK # name: # type: sq_string # elements: 1 # length: 180 ClusterCriterion: property OptimalK Optimal number of clusters An integer specifying the optimal number of clusters based on the evaluation criterion. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Optimal number of clusters # name: # type: sq_string # elements: 1 # length: 22 GapEvaluation.OptimalY # name: # type: sq_string # elements: 1 # length: 199 ClusterCriterion: property OptimalY Optimal clustering solution A numeric vector representing the clustering solution that corresponds to the optimal number of clusters. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Optimal clustering solution # name: # type: sq_string # elements: 1 # length: 35 GapEvaluation.ReferenceDistribution # name: # type: sq_string # elements: 1 # length: 251 GapEvaluation: property ReferenceDistribution Reference distribution for Monte-Carlo A character vector naming the reference distribution used to generate reference datasets. Supported values include 'pca' and 'uniform' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Reference distribution for Monte-Carlo # name: # type: sq_string # elements: 1 # length: 16 GapEvaluation.SE # name: # type: sq_string # elements: 1 # length: 218 GapEvaluation: property SE Standard error of expected logW A numeric vector containing the standard error of the expected values for the natural logarithm of the within-cluster dispersion. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Standard error of expected logW # name: # type: sq_string # elements: 1 # length: 26 GapEvaluation.SearchMethod # name: # type: sq_string # elements: 1 # length: 269 GapEvaluation: property SearchMethod Search method to select optimal K A character vector specifying the method used to select the optimal number of clusters from the gap statistic. Supported values include 'globalMaxSE' and 'firstMaxSE' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Search method to select optimal K # name: # type: sq_string # elements: 1 # length: 21 GapEvaluation.StdLogW # name: # type: sq_string # elements: 1 # length: 215 GapEvaluation: property StdLogW Standard deviation of expected logW A numeric vector containing the standard deviation of the Monte-Carlo estimates of the log within-cluster dispersion. This property is read-only. # name: # type: sq_string # elements: 1 # length: 35 Standard deviation of expected logW # name: # type: sq_string # elements: 1 # length: 15 GapEvaluation.X # name: # type: sq_string # elements: 1 # length: 141 ClusterCriterion: property X Data used for clustering A numeric matrix containing the data used for clustering. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Data used for clustering # name: # type: sq_string # elements: 1 # length: 18 GapEvaluation.addK # name: # type: sq_string # elements: 1 # length: 235 GapEvaluation: obj = addK ( obj , K ) Add new K values to inspect Add a new cluster array to inspect in the GapEvaluation object. This updates internal storage for Monte-Carlo results and evaluates the newly requested cluster counts. # name: # type: sq_string # elements: 1 # length: 27 Add new K values to inspect # name: # type: sq_string # elements: 1 # length: 18 GapEvaluation.plot # name: # type: sq_string # elements: 1 # length: 281 GapEvaluation: plot ( obj ) GapEvaluation: h = plot ( obj ) Plot Gap evaluation results Plot the gap statistic (criterion values) versus the inspected numbers of clusters and display error bars representing the Monte-Carlo standard deviations. Optionally returns the axes handle. # name: # type: sq_string # elements: 1 # length: 27 Plot Gap evaluation results # name: # type: sq_string # elements: 1 # length: 20 SilhouetteEvaluation # name: # type: sq_string # elements: 1 # length: 1262 statistics: SilhouetteEvaluation Silhouette evaluation for clustering The SilhouetteEvaluation class implements an object to evaluate clustering solutions using the silhouette criterion. A SilhouetteEvaluation object is a ClusterCriterion object that computes silhouette values for clustering solutions and selects the best number of clusters as the one with the highest average silhouette value. Create a SilhouetteEvaluation object by using the evalclusters function or the class constructor. List of public properties specific to SilhouetteEvaluation : Distance A valid distance metric name (string), a function handle, or a numeric vector as returned by pdist . This specifies how pairwise distances are computed. ClusterPriors A character vector specifying how to evaluate silhouette values across clusters: 'empirical' (default) uses empirical cluster priors, or 'equal' treats clusters equally. ClusterSilhouettes A cell array containing silhouette values for each observation for each inspected cluster number. The best clustering solution according to the silhouette criterion is the one that yields the highest average silhouette value. See also: evalclusters, ClusterCriterion, CalinskiHarabaszEvaluation, DaviesBouldinEvaluation, GapEvaluation # name: # type: sq_string # elements: 1 # length: 36 Silhouette evaluation for clustering # name: # type: sq_string # elements: 1 # length: 34 SilhouetteEvaluation.ClusterPriors # name: # type: sq_string # elements: 1 # length: 214 SilhouetteEvaluation: property ClusterPriors Cluster prior handling Specifies how cluster-level silhouette aggregation is computed. Valid values are 'empirical' (default) and 'equal' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Cluster prior handling # name: # type: sq_string # elements: 1 # length: 39 SilhouetteEvaluation.ClusterSilhouettes # name: # type: sq_string # elements: 1 # length: 286 SilhouetteEvaluation: property ClusterSilhouettes Silhouette values A cell array where each element holds the mean silhouette value of each cluster of a given clustering (corresponding to an inspected K), so element i is a vector of InspectedK( i ) values. This property is read-only. # name: # type: sq_string # elements: 1 # length: 17 Silhouette values # name: # type: sq_string # elements: 1 # length: 39 SilhouetteEvaluation.ClusteringFunction # name: # type: sq_string # elements: 1 # length: 290 ClusterCriterion: property ClusteringFunction Clustering algorithm A character vector or a function handle specifying the clustering algorithm used to generate the clustering solutions. It can be empty if the clustering solutions are passed as an input matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Clustering algorithm # name: # type: sq_string # elements: 1 # length: 34 SilhouetteEvaluation.CriterionName # name: # type: sq_string # elements: 1 # length: 202 ClusterCriterion: property CriterionName Name of the evaluation criterion A character vector specifying the name of the criterion used to evaluate the clustering solutions. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Name of the evaluation criterion # name: # type: sq_string # elements: 1 # length: 36 SilhouetteEvaluation.CriterionValues # name: # type: sq_string # elements: 1 # length: 196 ClusterCriterion: property CriterionValues Criterion values A numeric vector containing the values generated by the evaluation criterion for each clustering solution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 16 Criterion values # name: # type: sq_string # elements: 1 # length: 29 SilhouetteEvaluation.Distance # name: # type: sq_string # elements: 1 # length: 207 SilhouetteEvaluation: property Distance Distance measure A string naming a distance metric, a function handle that computes distances, or a numeric vector as produced by pdist . This property is read-only. # name: # type: sq_string # elements: 1 # length: 16 Distance measure # name: # type: sq_string # elements: 1 # length: 31 SilhouetteEvaluation.InspectedK # name: # type: sq_string # elements: 1 # length: 172 ClusterCriterion: property InspectedK List of the number of clusters A numeric vector containing the list of the number of clusters evaluated. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 List of the number of clusters # name: # type: sq_string # elements: 1 # length: 28 SilhouetteEvaluation.Missing # name: # type: sq_string # elements: 1 # length: 177 ClusterCriterion: property Missing Missing values A logical vector indicating which observations in the data matrix contain missing values ( NaN ). This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Missing values # name: # type: sq_string # elements: 1 # length: 36 SilhouetteEvaluation.NumObservations # name: # type: sq_string # elements: 1 # length: 176 ClusterCriterion: property NumObservations Number of observations An integer specifying the number of non-missing observations in the data matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 29 SilhouetteEvaluation.OptimalK # name: # type: sq_string # elements: 1 # length: 180 ClusterCriterion: property OptimalK Optimal number of clusters An integer specifying the optimal number of clusters based on the evaluation criterion. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Optimal number of clusters # name: # type: sq_string # elements: 1 # length: 29 SilhouetteEvaluation.OptimalY # name: # type: sq_string # elements: 1 # length: 199 ClusterCriterion: property OptimalY Optimal clustering solution A numeric vector representing the clustering solution that corresponds to the optimal number of clusters. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Optimal clustering solution # name: # type: sq_string # elements: 1 # length: 41 SilhouetteEvaluation.SilhouetteEvaluation # name: # type: sq_string # elements: 1 # length: 810 statistics: obj = SilhouetteEvaluation ( x , clust , KList ) statistics: obj = SilhouetteEvaluation (…, Name , Value ) Create a SilhouetteEvaluation object to evaluate clustering solutions for data x using clustering method clust over the list of cluster numbers KList . x is an N×P numeric matrix of observations (rows) and predictors (columns). clust is a string naming the clustering method (for example 'kmeans' , 'linkage' , or a custom function handle). KList is a vector of positive integers specifying the cluster numbers to inspect. Optional name-value pairs: Name Value 'Distance' Distance metric name, function handle, or numeric pdist vector. Default: 'sqeuclidean' . 'ClusterPriors' Either 'empirical' (default) or 'equal' . See also: silhouette, evalclusters, ClusterCriterion # name: # type: sq_string # elements: 1 # length: 150 Create a SilhouetteEvaluation object to evaluate clustering solutions for data x using clustering method clust over the list of cluster numbers KList. # name: # type: sq_string # elements: 1 # length: 22 SilhouetteEvaluation.X # name: # type: sq_string # elements: 1 # length: 141 ClusterCriterion: property X Data used for clustering A numeric matrix containing the data used for clustering. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Data used for clustering # name: # type: sq_string # elements: 1 # length: 25 SilhouetteEvaluation.addK # name: # type: sq_string # elements: 1 # length: 148 SilhouetteEvaluation: obj = addK ( obj , K ) Add a new cluster number to inspect and re-evaluate silhouette values for newly added cluster numbers. # name: # type: sq_string # elements: 1 # length: 102 Add a new cluster number to inspect and re-evaluate silhouette values for newly added cluster numbers. # name: # type: sq_string # elements: 1 # length: 25 SilhouetteEvaluation.plot # name: # type: sq_string # elements: 1 # length: 280 SilhouetteEvaluation: plot ( obj ) SilhouetteEvaluation: h = plot ( obj ) Plot the silhouette evaluation results. Plot the criterion values (average silhouette) against inspected cluster numbers ( InspectedK ) for the given obj . Optionally returns the axis handle for the plot. # name: # type: sq_string # elements: 1 # length: 39 Plot the silhouette evaluation results. # name: # type: sq_string # elements: 1 # length: 7 cluster # name: # type: sq_string # elements: 1 # length: 1145 statistics: T = cluster ( Z , "Cutoff", C ) statistics: T = cluster ( Z , "Cutoff", C , "Depth", D ) statistics: T = cluster ( Z , "Cutoff", C , "Criterion", criterion ) statistics: T = cluster ( Z , "MaxClust", N ) Define clusters from an agglomerative hierarchical cluster tree. Given a hierarchical cluster tree Z generated by the linkage function, cluster defines clusters, using a threshold value C to identify new clusters (’Cutoff’) or according to a maximum number of desired clusters N (’MaxClust’). criterion is used to choose the criterion for defining clusters, which can be either "inconsistent" (default) or "distance". When using "inconsistent", cluster compares the threshold value C to the inconsistency coefficient of each link; when using "distance", cluster compares the threshold value C to the height of each link. D is the depth used to evaluate the inconsistency coefficient, its default value is 2. cluster uses "distance" as a criterion for defining new clusters when it is used with the ’MaxClust’ method. See also: clusterdata, dendrogram, inconsistent, kmeans, linkage, pdist # name: # type: sq_string # elements: 1 # length: 64 Define clusters from an agglomerative hierarchical cluster tree. # name: # type: sq_string # elements: 1 # length: 11 clusterdata # name: # type: sq_string # elements: 1 # length: 754 statistics: T = clusterdata ( X , cutoff ) statistics: T = clusterdata ( X , Name , Value ) Wrapper function for linkage and cluster . If cutoff is used, then clusterdata calls linkage and cluster with default value, using cutoff as a threshold value for cluster . If cutoff is an integer and greater or equal to 2, then cutoff is interpreted as the maximum number of cluster desired and the "MaxClust" option is used for cluster . If cutoff is not used, then clusterdata expects a list of pair arguments. Then you must specify either the "Cutoff" or "MaxClust" option for cluster . The method and metric used by linkage , are defined through the "linkage" and "distance" arguments. See also: cluster, dendrogram, inconsistent, kmeans, linkage, pdist # name: # type: sq_string # elements: 1 # length: 41 Wrapper function for linkage and cluster. # name: # type: sq_string # elements: 1 # length: 8 cophenet # name: # type: sq_string # elements: 1 # length: 1035 statistics: [ c , d ] = cophenet ( Z , y ) Compute the cophenetic correlation coefficient. The cophenetic correlation coefficient C of a hierarchical cluster tree Z is the linear correlation coefficient between the cophenetic distances d and the euclidean distances y . $$ c = \frac {\sum_{i It is a measure of the similarity between the distance of the leaves, as seen in the tree, and the distance of the original data points, which were used to build the tree. When this similarity is greater, that is the coefficient is closer to 1, the tree renders an accurate representation of the distances between the original data points. Z is a hierarchical cluster tree, as the output of linkage . y is a vector of euclidean distances, as the output of pdist . The optional output d is a vector of cophenetic distances, in the same lower triangular format as y . The cophenetic distance between two data points is the height of the lowest common node of the tree. See also: cluster, dendrogram, inconsistent, linkage, pdist, squareform # name: # type: sq_string # elements: 1 # length: 47 Compute the cophenetic correlation coefficient. # name: # type: sq_string # elements: 1 # length: 6 dbscan # name: # type: sq_string # elements: 1 # length: 2955 statistics: idx = dbscan ( X , epsilon , minpts ) statistics: idx = dbscan ( D , epsilon , minpts , 'Distance' , 'precomputed' ) statistics: idx = dbscan (…, name , value ) statistics: [ idx , corepts ] = dbscan (…) Density-Based Spatial Clustering of Applications with Noise (DBSCAN). idx = dbscan ( X , epsilon , minpts ) partitions the observations in the N×P numeric matrix X into clusters using the DBSCAN algorithm with neighborhood radius epsilon and minimum number of neighbors minpts . Rows of X correspond to observations and columns correspond to features or variables. epsilon must be a nonnegative scalar and minpts a positive integer scalar. idx is an N×1 vector of cluster indices, numbered 1 to the number of clusters found; observations flagged as noise are assigned the value -1 . A point is a core point when at least minpts observations ( including the point itself ) lie within distance epsilon of it. Clusters grow from core points to every observation that is density-reachable from them; a non-core observation that lies within epsilon of a core point becomes a border point and joins that point’s cluster, while an observation that is neither core nor within reach of a core point is labelled noise. A border point that is reachable from more than one cluster is assigned to the first cluster that reaches it, following the order of the observations in X . idx = dbscan ( D , epsilon , minpts , 'Distance' , 'precomputed' ) treats the N×N matrix D as a precomputed matrix of pairwise distances between observations, such as the output of pdist2 ; D (i,j) is the distance between observations i and j . [ idx , corepts ] = dbscan (…) also returns an N×1 logical vector corepts that is true for each observation that is a core point. Additional parameters can be specified by Name-Value pair arguments. Name Value 'Distance' is the distance metric used to find neighbors, specified as one of the metrics accepted by rangesearch ( 'euclidean' by default, and also 'seuclidean' , 'cityblock' , 'chebychev' , 'minkowski' , 'mahalanobis' , 'cosine' , 'correlation' , 'spearman' , 'hamming' , 'jaccard' , or a custom distance function handle), or the string 'precomputed' to interpret the first input as a matrix of pairwise distances. 'P' is the Minkowski distance exponent, a positive scalar. This argument is only valid when the selected distance metric is 'minkowski' . By default it is 2. 'Scale' is the scale parameter for the standardized Euclidean distance, a nonnegative numeric vector of length equal to the number of columns in X . This argument is only valid when the selected distance metric is 'seuclidean' . 'Cov' is the covariance matrix for the mahalanobis distance, a positive definite matrix matching the number of columns in X . This argument is only valid when the selected distance metric is 'mahalanobis' . See also: kmeans, rangesearch, pdist2, knnsearch # name: # type: sq_string # elements: 1 # length: 69 Density-Based Spatial Clustering of Applications with Noise (DBSCAN). # name: # type: sq_string # elements: 1 # length: 12 evalclusters # name: # type: sq_string # elements: 1 # length: 3780 statistics: eva = evalclusters ( x , clust , criterion ) statistics: eva = evalclusters (…, Name , Value ) Create a clustering evaluation object to find the optimal number of clusters. evalclusters creates a clustering evaluation object to evaluate the optimal number of clusters for data x , using criterion criterion . The input data x is a matrix with n observations of p variables. The evaluation criterion criterion is one of the following: CalinskiHarabasz to create a CalinskiHarabaszEvaluation object. DaviesBouldin to create a DaviesBouldinEvaluation object. gap to create a GapEvaluation object. silhouette to create a SilhouetteEvaluation object. The clustering algorithm clust is one of the following: kmeans to cluster the data using kmeans with EmptyAction set to singleton and Replicates set to 5. linkage to cluster the data using clusterdata with linkage set to Ward . gmdistribution to cluster the data using fitgmdist with SharedCov set to true and Replicates set to 5. If the criterion is CalinskiHarabasz , DaviesBouldin , or silhouette , clust can also be a function handle to a function of the form c = clust(x, k) , where x is the input data, k the number of clusters to evaluate and c the clustering result. The clustering result can be either an array of size n with k different integer values, or a matrix of size n by k with a likelihood value assigned to each one of the n observations for each one of the k clusters. In the latter case, each observation is assigned to the cluster with the higher value. If the criterion is CalinskiHarabasz , DaviesBouldin , or silhouette , clust can also be a matrix of size n by k , where k is the number of proposed clustering solutions, so that each column of clust is a clustering solution. In addition to the obligatory x , clust and criterion inputs there is a number of optional arguments, specified as pairs of Name and Value options. The known Name arguments are: KList a vector of positive integer numbers, that is the cluster sizes to evaluate. This option is necessary, unless clust is a matrix of proposed clustering solutions. Distance a distance metric as accepted by the chosen clust . It can be the name of the distance metric as a string or a function handle. When criterion is silhouette , it can be a vector as created by function pdist . Valid distance metric strings are: sqEuclidean (default), Euclidean , cityblock , cosine , correlation , Hamming , Jaccard . Only used by silhouette and gap evaluation. ClusterPriors the prior probabilities of each cluster, which can be either empirical (default), or equal . When empirical the silhouette value is the average of the silhouette values of all points; when equal the silhouette value is the average of the average silhouette value of each cluster. Only used by silhouette evaluation. B the number of reference datasets generated from the reference distribution. Only used by gap evaluation. ReferenceDistribution the reference distribution used to create the reference data. It can be PCA (default) for a distribution based on the principal components of X , or uniform for a uniform distribution based on the range of the observed data. PCA is currently not implemented. Only used by gap evaluation. SearchMethod the method for selecting the optimal value with a gap evaluation. It can be either globalMaxSE (default) for selecting the smallest number of clusters which is inside the standard error of the maximum gap value, or firstMaxSE for selecting the first number of clusters which is inside the standard error of the following cluster number. Only used by gap evaluation. Output eva is a clustering evaluation object. See also: CalinskiHarabaszEvaluation, DaviesBouldinEvaluation, GapEvaluation, SilhouetteEvaluation # name: # type: sq_string # elements: 1 # length: 77 Create a clustering evaluation object to find the optimal number of clusters. # name: # type: sq_string # elements: 1 # length: 9 fitgmdist # name: # type: sq_string # elements: 1 # length: 2426 statistics: GMdist = fitgmdist ( data , k , param1 , value1 , …) Fit a Gaussian mixture model with k components to data . Each row of data is a data sample. Each column is a variable. Optional parameters are: 'start' : Initialization conditions. Possible values are: 'randSample' (default) Takes means uniformly from rows of data. 'plus' Use k-means++ to initialize means. 'cluster' Performs an initial clustering with 10% of the data. vector A vector whose length is the number of rows in data, and whose values are 1 to k specify the components each row is initially allocated to. The mean, variance, and weight of each component is calculated from that. structure A structure with fields mu , Sigma and ComponentProportion . For 'randSample' , 'plus' , and 'cluster' , the initial variance of each component is the variance of the entire data sample. 'Replicates' : Number of random restarts to perform. 'RegularizationValue' or 'Regularize' : A small number added to the diagonal entries of the covariance to prevent singular covariances. 'SharedCovariance' or 'SharedCov' (logical). True if all components must share the same variance, to reduce the number of free parameters 'CovarianceType' or 'CovType' (string). Possible values are: 'full' (default) Allow arbitrary covariance matrices. 'diagonal' Force covariances to be diagonal, to reduce the number of free parameters. 'Options' : A structure with all of the following fields: MaxIter Maximum number of EM iterations (default 100). TolFun Threshold increase in likelihood to terminate EM (default 1e-6). Display Possible values are: 'off' (default): Display nothing. 'final' : Display the total number of iterations and likelihood once the execution completes. 'iter' : Display the number of iteration and likelihood after each iteration. 'Weight' : A column vector or N×2 matrix. The first column consists of non-negative weights given to the samples. If these are all integers, this is equivalent to specifying weight (i) copies of row i of data , but potentially faster. If a row of data is used to represent samples that are similar but not identical, then the second column of weight indicates the variance of those original samples. Specifically, in the EM algorithm, the contribution of row i towards the variance is set to at least weight (i,2) , to prevent spurious components with zero variance. See also: gmdistribution, kmeans # name: # type: sq_string # elements: 1 # length: 55 Fit a Gaussian mixture model with k components to data. # name: # type: sq_string # elements: 1 # length: 14 gmdistribution # name: # type: sq_string # elements: 1 # length: 1083 statistics: GMdist = gmdistribution ( mu , Sigma ) statistics: GMdist = gmdistribution ( mu , Sigma , p ) statistics: GMdist = gmdistribution ( mu , Sigma , p , extra ) Create an object of the gmdistribution class which represents a Gaussian mixture model with k components of n-dimensional Gaussians. Input mu is a k-by-n matrix specifying the n-dimensional mean of each of the k components of the distribution. Input Sigma is an array that specifies the variances of the distributions, in one of four forms depending on its dimension. n-by-n-by-k: Slice Sigma (:,:,i) is the variance of the i’th component 1-by-n-by-k: Slice diag( Sigma (1,:,i)) is the variance of the i’th component n-by-n: Sigma is the variance of every component 1-by-n-by-k: Slice diag( Sigma ) is the variance of every component If p is specified, it is a vector of length k specifying the proportion of each component. If it is omitted or empty, each component has an equal proportion. Input extra is used by fitgmdist to indicate the parameters of the fitting process. See also: fitgmdist # name: # type: sq_string # elements: 1 # length: 132 Create an object of the gmdistribution class which represents a Gaussian mixture model with k components of n-dimensional Gaussians. # name: # type: sq_string # elements: 1 # length: 18 gmdistribution.AIC # name: # type: sq_string # elements: 1 # length: 230 gmdistribution: property AIC Akaike information criterion A scalar, twice the negative log-likelihood plus twice the number of estimated parameters. It is empty unless the object came from fitgmdist . This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Akaike information criterion # name: # type: sq_string # elements: 1 # length: 18 gmdistribution.BIC # name: # type: sq_string # elements: 1 # length: 270 gmdistribution: property BIC Bayesian information criterion A scalar, twice the negative log-likelihood plus the number of estimated parameters times the log of the number of observations. It is empty unless the object came from fitgmdist . This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 Bayesian information criterion # name: # type: sq_string # elements: 1 # length: 34 gmdistribution.ComponentProportion # name: # type: sq_string # elements: 1 # length: 234 gmdistribution: property ComponentProportion Mixing proportions A 1-by-k row vector holding the proportion of each component. The proportions are scaled to sum to 1, and are all equal when none was given. This property is read-only. # name: # type: sq_string # elements: 1 # length: 18 Mixing proportions # name: # type: sq_string # elements: 1 # length: 24 gmdistribution.Converged # name: # type: sq_string # elements: 1 # length: 228 gmdistribution: property Converged Whether the fit converged A logical scalar, true when the fit reached its tolerance within the iteration limit. It is empty unless the object came from fitgmdist . This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Whether the fit converged # name: # type: sq_string # elements: 1 # length: 29 gmdistribution.CovarianceType # name: # type: sq_string # elements: 1 # length: 229 gmdistribution: property CovarianceType Form of the component covariances A character vector, 'diagonal' when each covariance was given as a row of variances and 'full' when it was given as a matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Form of the component covariances # name: # type: sq_string # elements: 1 # length: 31 gmdistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 151 gmdistribution: property DistributionName Name of the distribution The character vector 'gaussian mixture distribution' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Name of the distribution # name: # type: sq_string # elements: 1 # length: 36 gmdistribution.NegativeLogLikelihood # name: # type: sq_string # elements: 1 # length: 246 gmdistribution: property NegativeLogLikelihood Negative log-likelihood at the fit A scalar, the negative of the log-likelihood of the data under the fitted mixture. It is empty unless the object came from fitgmdist . This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Negative log-likelihood at the fit # name: # type: sq_string # elements: 1 # length: 28 gmdistribution.NumComponents # name: # type: sq_string # elements: 1 # length: 144 gmdistribution: property NumComponents Number of mixture components A positive integer, the number of rows of mu . This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Number of mixture components # name: # type: sq_string # elements: 1 # length: 28 gmdistribution.NumIterations # name: # type: sq_string # elements: 1 # length: 164 gmdistribution: property NumIterations Iterations the fit took A positive integer. It is empty unless the object came from fitgmdist . This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Iterations the fit took # name: # type: sq_string # elements: 1 # length: 27 gmdistribution.NumVariables # name: # type: sq_string # elements: 1 # length: 179 gmdistribution: property NumVariables Number of variables A positive integer, the dimension of each component, which is the number of columns of mu . This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Number of variables # name: # type: sq_string # elements: 1 # length: 34 gmdistribution.RegularizationValue # name: # type: sq_string # elements: 1 # length: 266 gmdistribution: property RegularizationValue Regularization added to the covariance diagonal A nonnegative scalar added to the diagonal of each covariance to keep it positive definite. It is empty unless the object came from fitgmdist . This property is read-only. # name: # type: sq_string # elements: 1 # length: 47 Regularization added to the covariance diagonal # name: # type: sq_string # elements: 1 # length: 31 gmdistribution.SharedCovariance # name: # type: sq_string # elements: 1 # length: 237 gmdistribution: property SharedCovariance Whether the components share one covariance A logical scalar, true when a single covariance was given for every component and false when one was given per component. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Whether the components share one covariance # name: # type: sq_string # elements: 1 # length: 20 gmdistribution.Sigma # name: # type: sq_string # elements: 1 # length: 325 gmdistribution: property Sigma Component covariances The covariances of the components, in the form they were given in. A full covariance per component is d-by-d-by-k, a diagonal one per component 1-by-d-by-k, a full covariance shared by every component d-by-d, and a shared diagonal one 1-by-d. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Component covariances # name: # type: sq_string # elements: 1 # length: 18 gmdistribution.cdf # name: # type: sq_string # elements: 1 # length: 455 gmdistribution: c = cdf ( obj , X ) Cumulative distribution function of a Gaussian mixture distribution. X is an n-by-d matrix of points at which the distribution is evaluated, one point per row, where d is the number of variables of obj . c is an n-by-1 vector holding the value of the cumulative distribution function at each of them, the components’ cumulative distributions summed with the mixing proportions in ComponentProportion as weights. # name: # type: sq_string # elements: 1 # length: 68 Cumulative distribution function of a Gaussian mixture distribution. # name: # type: sq_string # elements: 1 # length: 22 gmdistribution.cluster # name: # type: sq_string # elements: 1 # length: 905 gmdistribution: idx = cluster ( obj , X ) gmdistribution: [ idx , nlogl ] = cluster ( obj , X ) gmdistribution: [ idx , nlogl , P ] = cluster ( obj , X ) gmdistribution: [ idx , nlogl , P , logpdf ] = cluster ( obj , X ) gmdistribution: [ idx , nlogl , P , logpdf , M ] = cluster ( obj , X ) Assign each observation to a mixture component. X is an n-by-d matrix of observations, one per row, where d is the number of variables of obj . Each is assigned to the component under which it is most probable. idx is an n-by-1 vector of component indices. nlogl is the negative log-likelihood of X under the mixture. P is an n-by-k matrix of posterior probabilities, one column per component, whose rows sum to one. logpdf is an n-by-1 vector holding the logarithm of the mixture density at each observation. M is an n-by-k matrix of squared Mahalanobis distances from each observation to each component mean. # name: # type: sq_string # elements: 1 # length: 47 Assign each observation to a mixture component. # name: # type: sq_string # elements: 1 # length: 18 gmdistribution.fit # name: # type: sq_string # elements: 1 # length: 402 gmdistribution: obj = fit ( X , k ) gmdistribution: obj = fit ( X , k , Name , Value ) Fit a Gaussian mixture distribution to data. X is an n-by-d matrix of observations, one per row, and k is the number of components to fit. Any Name - Value pair accepted by fitgmdist may follow, which is the function this method calls and where the options are documented. obj is the fitted gmdistribution object. # name: # type: sq_string # elements: 1 # length: 44 Fit a Gaussian mixture distribution to data. # name: # type: sq_string # elements: 1 # length: 29 gmdistribution.gmdistribution # name: # type: sq_string # elements: 1 # length: 1025 gmdistribution: obj = gmdistribution ( mu , Sigma ) gmdistribution: obj = gmdistribution ( mu , Sigma , p ) gmdistribution: obj = gmdistribution ( mu , Sigma , p , extra ) Create a Gaussian mixture distribution. mu is a k-by-d matrix holding the mean of each of the k components, one per row, where d is the number of variables. Sigma holds the covariances in one of four forms. A full covariance per component is d-by-d-by-k and a diagonal one per component is 1-by-d-by-k, while a single d-by-d matrix or a single 1-by-d row of variances is shared by every component. The form given sets CovarianceType and SharedCovariance . p is a vector of k mixing proportions, scaled to sum to 1. A proportion may not be negative and they may not all be zero. When p is omitted or empty the components are equally weighted. extra carries the results of a fit and is passed by fitgmdist . It fills AIC , BIC , Converged , NegativeLogLikelihood , NumIterations and RegularizationValue , which stay empty for an object built by hand. # name: # type: sq_string # elements: 1 # length: 39 Create a Gaussian mixture distribution. # name: # type: sq_string # elements: 1 # length: 20 gmdistribution.mahal # name: # type: sq_string # elements: 1 # length: 377 gmdistribution: D = mahal ( obj , X ) Squared Mahalanobis distance to each mixture component. X is an n-by-d matrix of observations, one per row, where d is the number of variables of obj . D is an n-by-k matrix holding the squared Mahalanobis distance from each observation to the mean of each of the k components, measured in the covariance of the component it is taken to. # name: # type: sq_string # elements: 1 # length: 55 Squared Mahalanobis distance to each mixture component. # name: # type: sq_string # elements: 1 # length: 17 gmdistribution.mu # name: # type: sq_string # elements: 1 # length: 184 gmdistribution: property mu Component means A k-by-d matrix holding the mean of each of the k components, one per row, where d is the number of variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Component means # name: # type: sq_string # elements: 1 # length: 18 gmdistribution.pdf # name: # type: sq_string # elements: 1 # length: 393 gmdistribution: c = pdf ( obj , X ) Probability density function of a Gaussian mixture distribution. X is an n-by-d matrix of points at which the density is evaluated, one point per row, where d is the number of variables of obj . c is an n-by-1 vector holding the density at each of them, the components’ densities summed with the mixing proportions in ComponentProportion as weights. # name: # type: sq_string # elements: 1 # length: 64 Probability density function of a Gaussian mixture distribution. # name: # type: sq_string # elements: 1 # length: 24 gmdistribution.posterior # name: # type: sq_string # elements: 1 # length: 320 gmdistribution: c = posterior ( obj , X ) Posterior probability of each mixture component. X is an n-by-d matrix of observations, one per row, where d is the number of variables of obj . c is an n-by-k matrix whose (i,j) element is the probability that observation i was drawn from component j, so its rows sum to one. # name: # type: sq_string # elements: 1 # length: 48 Posterior probability of each mixture component. # name: # type: sq_string # elements: 1 # length: 21 gmdistribution.random # name: # type: sq_string # elements: 1 # length: 368 gmdistribution: c = random ( obj ) gmdistribution: c = random ( obj , n ) Random numbers from a Gaussian mixture distribution. n is the number of observations to draw and defaults to 1. c is an n -by-d matrix holding one observation per row, where d is the number of variables of obj . Each row is drawn from a component chosen with probability ComponentProportion . # name: # type: sq_string # elements: 1 # length: 52 Random numbers from a Gaussian mixture distribution. # name: # type: sq_string # elements: 1 # length: 12 inconsistent # name: # type: sq_string # elements: 1 # length: 955 statistics: Y = inconsistent ( Z ) statistics: Y = inconsistent ( Z , d ) Compute the inconsistency coefficient for each link of a hierarchical cluster tree. Given a hierarchical cluster tree Z generated by the linkage function, inconsistent computes the inconsistency coefficient for each link of the tree, using all the links down to the d -th level below that link. The default depth d is 2, which means that only two levels are considered: the level of the computed link and the level below that. Each row of Y corresponds to the row of same index of Z . The columns of Y are respectively: the mean of the heights of the links used for the calculation, the standard deviation of the heights of those links, the number of links used, the inconsistency coefficient. Reference Jain, A., and R. Dubes. Algorithms for Clustering Data. Upper Saddle River, NJ: Prentice-Hall, 1988. See also: cluster, clusterdata, dendrogram, linkage, pdist, squareform # name: # type: sq_string # elements: 1 # length: 83 Compute the inconsistency coefficient for each link of a hierarchical cluster tree. # name: # type: sq_string # elements: 1 # length: 6 kmeans # name: # type: sq_string # elements: 1 # length: 4586 statistics: idx = kmeans ( data , k ) statistics: [ idx , centers ] = kmeans ( data , k ) statistics: [ idx , centers , sumd ] = kmeans ( data , k ) statistics: [ idx , centers , sumd , dist ] = kmeans ( data , k ) statistics: […] = kmeans ( data , k , param1 , value1 , …) statistics: […] = kmeans ( data , [], 'start' , start , …) Perform a k -means clustering of the N×D matrix data . If parameter 'start' is specified, then k may be empty in which case k is set to the number of rows of start . The outputs are: idx An N×1 vector whose i -th element is the class to which row i of data is assigned. centers A K×D array whose i -th row is the centroid of cluster i . sumd A k×1 vector whose i -th entry is the sum of the distances from samples in cluster i to centroid i . dist An N×k matrix whose i j -th element is the distance from sample i to centroid j . The following parameters may be placed in any order. Each parameter must be followed by its value, as in Name-Value pairs. Name Description 'Start' The initialization method for the centroids. Value Description 'plus' The k-means++ algorithm. (Default) 'sample' A subset of k rows from data , sampled uniformly without replacement. 'cluster' Perform a pilot clustering on 10% of the rows of data . 'uniform' Each component of each centroid is drawn uniformly from the interval between the maximum and minimum values of that component within data . This performs poorly and is implemented only for Matlab compatibility. numeric matrix A k×D matrix of centroid starting locations. The rows correspond to seeds. numeric array A k×D×r array of centroid starting locations. The third dimension invokes replication of the clustering routine. Page r contains the set of seeds for replicate r . kmeans infers the number of replicates (specified by the 'Replicates' Name-Value pair argument) from the size of the third dimension. Name Description 'Distance' The distance measure used for partitioning and calculating centroids. Value Description 'sqeuclidean' The squared Euclidean distance. i.e. the sum of the squares of the differences between corresponding components. In this case, the centroid is the arithmetic mean of all samples in its cluster. This is the only distance for which this algorithm is truly "k-means". 'cityblock' The sum metric, or L1 distance, i.e. the sum of the absolute differences between corresponding components. In this case, the centroid is the median of all samples in its cluster. This gives the k-medians algorithm. 'cosine' One minus the cosine of the included angle between points (treated as vectors). Each centroid is the mean of the points in that cluster, after normalizing those points to unit Euclidean length. 'correlation' One minus the sample correlation between points (treated as sequences of values). Each centroid is the component-wise mean of the points in that cluster, after centering and normalizing those points to zero mean and unit standard deviation. 'hamming' The number of components in which the sample and the centroid differ. In this case, the centroid is the median of all samples in its cluster. Unlike Matlab, Octave allows non-logical data . Name Description 'EmptyAction' What to do when a centroid is not the closest to any data sample. Value Description 'error' Throw an error. 'singleton' (Default) Select the row of data that has the highest error and use that as the new centroid. 'drop' Remove the centroid, and continue computation with one fewer centroid. The dimensions of the outputs centroids and d are unchanged, with values for omitted centroids replaced by NaN. Name Description 'Display' Display a text summary. Value Description 'off' (Default) Display no summary. 'final' Display a summary for each clustering operation. 'iter' Display a summary for each iteration of a clustering operation. Name Value 'Replicates' A positive integer specifying the number of independent clusterings to perform. The output values are the values for the best clustering, i.e., the one with the smallest value of sumd . If Start is numeric, then Replicates defaults to (and must equal) the size of the third dimension of Start . Otherwise it defaults to 1. 'MaxIter' The maximum number of iterations to perform for each replicate. If the maximum change of any centroid is less than 0.001, then the replicate terminates even if MaxIter iterations have no occurred. The default is 100. Example: [~,c] = kmeans (rand(10, 3), 2, "emptyaction", "singleton"); See also: linkage # name: # type: sq_string # elements: 1 # length: 52 Perform a k-means clustering of the N*D matrix data. # name: # type: sq_string # elements: 1 # length: 8 kmedoids # name: # type: sq_string # elements: 1 # length: 2926 statistics: idx = kmedoids ( X , k ) statistics: [ idx , C ] = kmedoids ( X , k ) statistics: [ idx , C , sumd ] = kmedoids ( X , k ) statistics: [ idx , C , sumd , D ] = kmedoids ( X , k ) statistics: [ idx , C , sumd , D , midx ] = kmedoids ( X , k ) statistics: [ idx , C , sumd , D , midx , info ] = kmedoids ( X , k ) statistics: […] = kmedoids (…, name , value ) Partition observations into k clusters using the k-medoids algorithm. idx = kmedoids ( X , k ) partitions the N×P numeric matrix X into k clusters, each represented by one of the observations (its medoid ), and returns the N×1 vector idx of cluster indices. Rows of X correspond to observations and columns correspond to features or variables. Unlike kmeans , whose centroids are the mean of each cluster, a medoid is an actual data point, which makes k-medoids more robust to outliers and applicable to any distance metric. [ idx , C , sumd , D , midx , info ] = kmedoids (…) returns additional results: C a k×P matrix with the coordinates of the k medoids, one per row ( C = X ( midx ,:) ). sumd a k×1 vector with the within-cluster sum of the distances from each point to its cluster medoid, measured with the selected metric. D an N×k matrix with the distance from every observation to every medoid. midx a k×1 vector with the row indices into X of the k medoids. info a scalar structure with the fields 'algorithm' , 'start' , 'distance' , 'iterations' , and 'bestReplicate' describing the chosen run. Additional parameters can be specified by Name-Value pair arguments. Name Value 'Distance' the distance metric, one of 'sqeuclidean' (default), 'euclidean' , 'seuclidean' , 'cityblock' , 'minkowski' , 'chebychev' , 'cosine' , 'correlation' , 'hamming' , 'jaccard' , 'spearman' , 'mahalanobis' , or a custom distance function handle accepted by pdist2 . 'Algorithm' the optimization algorithm, either 'pam' (default) for Partitioning Around Medoids, which searches over all medoid/non-medoid swaps, or 'small' for the faster Voronoi iteration that reassigns points and re-selects each cluster medoid until convergence. 'Start' the method used to choose the initial medoids: 'plus' (default, k-means++), 'sample' (a random subset of the observations), 'cluster' (a preliminary pass on a subsample), or a k×P numeric matrix of starting medoid locations, each snapped to the nearest observation. A k×P×R array supplies a separate start for each of R replicates. 'Replicates' a positive integer number of times to repeat the clustering, each with a new set of initial medoids; the solution with the lowest total sum of distances is returned. The default is 1, or the size of the third dimension of a numeric 'Start' . 'Options' a structure, as created by statset , whose 'MaxIter' field caps the number of iterations (default 100). See also: kmeans, linkage, pdist2, dbscan # name: # type: sq_string # elements: 1 # length: 69 Partition observations into k clusters using the k-medoids algorithm. # name: # type: sq_string # elements: 1 # length: 7 linkage # name: # type: sq_string # elements: 1 # length: 3290 statistics: y = linkage ( d ) statistics: y = linkage ( d , method ) statistics: y = linkage ( x ) statistics: y = linkage ( x , method ) statistics: y = linkage ( x , method , metric ) statistics: y = linkage ( x , method , arglist ) Produce a hierarchical clustering dendrogram. d is the dissimilarity matrix relative to n observations, formatted as a (n-1)×n/2 x1 vector as produced by pdist . Alternatively, x contains data formatted for input to pdist , metric is a metric for pdist and arglist is a cell array containing arguments that are passed to pdist . linkage starts by putting each observation into a singleton cluster and numbering those from 1 to n. Then it merges two clusters, chosen according to method , to create a new cluster numbered n+1, and so on until all observations are grouped into a single cluster numbered 2(n-1). Row k of the (m-1)x3 output matrix relates to cluster n+k: the first two columns are the numbers of the two component clusters and column 3 contains their distance. When several pairs of clusters are equally close, which of them is merged first is not determined by the data, and the cluster numbers in the first two columns are therefore implementation-defined. Only column 3, the sequence of merge distances, is reproducible across implementations, and even that is so only for the methods whose recomputation rule does not depend on the merge order ( "weighted" , "centroid" and "median" do depend on it). Code that must be portable should read column 3, or the cluster assignment obtained from cluster , rather than the raw numbering. method defines the way the distance between two clusters is computed and how they are recomputed when two clusters are merged: ‘ "single" (default) ’ Distance between two clusters is the minimum distance between two elements belonging each to one cluster. Produces a cluster tree known as minimum spanning tree. ‘ "complete" ’ Furthest distance between two elements belonging each to one cluster. ‘ "average" ’ Unweighted pair group method with averaging (UPGMA). The mean distance between all pair of elements each belonging to one cluster. ‘ "weighted" ’ Weighted pair group method with averaging (WPGMA). When two clusters A and B are joined together, the new distance to a cluster C is the mean between distances A-C and B-C. ‘ "centroid" ’ Unweighted Pair-Group Method using Centroids (UPGMC). Assumes Euclidean metric. The distance between cluster centroids, each centroid being the center of mass of a cluster. ‘ "median" ’ Weighted pair-group method using centroids (WPGMC). Assumes Euclidean metric. Distance between cluster centroids. When two clusters are joined together, the new centroid is the midpoint between the joined centroids. ‘ "ward" ’ Ward’s sum of squared deviations about the group mean (ESS). Also known as minimum variance or inner squared distance. Assumes Euclidean metric. How much the moment of inertia of the merged cluster exceeds the sum of those of the individual clusters. Reference Ward, J. H. Hierarchical Grouping to Optimize an Objective Function J. Am. Statist. Assoc. 1963, 58, 236-244, http://iv.slis.indiana.edu/sw/data/ward.pdf . See also: pdist, squareform # name: # type: sq_string # elements: 1 # length: 45 Produce a hierarchical clustering dendrogram. # name: # type: sq_string # elements: 1 # length: 16 optimalleaforder # name: # type: sq_string # elements: 1 # length: 1222 statistics: leafOrder = optimalleaforder ( tree , D ) statistics: leafOrder = optimalleaforder (…, Name , Value ) Compute the optimal leaf ordering of a hierarchical binary cluster tree. The optimal leaf ordering of a tree is the ordering which minimizes the sum of the distances between each leaf and its adjacent leaves, without altering the structure of the tree, that is without redefining the clusters of the tree. Required inputs: tree : a hierarchical cluster tree tree generated by the linkage function. D : a matrix of distances as computed by pdist . Optional inputs can be the following property/value pairs: property ’Criteria’ at the moment can only have the value ’adjacent’, for minimizing the distances between leaves. property ’Transformation’ can have one of the values ’linear’, ’inverse’ or a handle to a custom function which computes S the similarity matrix. optimalleaforder’s output leafOrder is the optimal leaf ordering. Reference Bar-Joseph, Z., Gifford, D.K., and Jaakkola, T.S. Fast optimal leaf ordering for hierarchical clustering. Bioinformatics vol. 17 suppl. 1, 2001. See also: dendrogram, linkage, pdist # name: # type: sq_string # elements: 1 # length: 72 Compute the optimal leaf ordering of a hierarchical binary cluster tree. # name: # type: sq_string # elements: 1 # length: 15 spectralcluster # name: # type: sq_string # elements: 1 # length: 2819 statistics: idx = spectralcluster ( X , k ) statistics: idx = spectralcluster ( S , k , 'Distance' , 'precomputed' ) statistics: [ idx , V ] = spectralcluster (…) statistics: [ idx , V , D ] = spectralcluster (…) statistics: […] = spectralcluster (…, name , value ) Partition observations into k clusters using spectral clustering. idx = spectralcluster ( X , k ) partitions the N×P numeric matrix X into k clusters and returns the N×1 vector idx of cluster indices. Rows of X correspond to observations and columns to features. Spectral clustering builds a similarity graph over the observations, embeds them with the eigenvectors of the graph Laplacian, and clusters that embedding, which lets it recover clusters that are not linearly separable in the original space. [ idx , V , D ] = spectralcluster (…) also returns the N×k matrix V whose columns are the eigenvectors associated with the k smallest eigenvalues of the Laplacian, and the k×1 vector D of those eigenvalues. The signs of the eigenvectors, and the basis within a repeated eigenvalue, are arbitrary. Additional parameters can be specified by Name-Value pair arguments. Name Value 'Distance' the distance metric used to build the similarity graph, one of 'euclidean' (default), 'seuclidean' , 'mahalanobis' , 'cityblock' , 'minkowski' , 'chebychev' , 'cosine' , 'correlation' , 'hamming' , 'jaccard' , 'spearman' , or a function handle accepted by pdist2 , or the string 'precomputed' to interpret the first input as an N×N similarity matrix. 'SimilarityGraph' 'knn' (default) to connect each observation to its nearest neighbors, or 'epsilon' to connect observations that are within a fixed radius. 'NumNeighbors' the number of nearest neighbors for the 'knn' graph, a positive integer. The default is ceil (log ( N )) . 'KNNGraphType' 'complete' (default) to connect i and j when either is a nearest neighbor of the other, or 'mutual' to connect them only when each is a nearest neighbor of the other. 'Radius' the radius for the 'epsilon' graph, a nonnegative scalar. Required when 'SimilarityGraph' is 'epsilon' . 'KernelScale' the positive scale factor sigma in the Gaussian similarity kernel exp (-(dist / sigma)^2) applied to the graph edges. The default is 1. 'LaplacianNormalization' 'randomwalk' (default), 'symmetric' , or 'none' , selecting how the graph Laplacian is normalized before the eigendecomposition. 'ClusterMethod' 'kmeans' (default) or 'kmedoids' to cluster the eigenvector embedding. 'P' the Minkowski exponent (default 2), used only with the 'minkowski' distance. 'Cov' the covariance matrix used only with the 'mahalanobis' distance. 'Scale' the scaling vector used only with the 'seuclidean' distance. See also: kmeans, kmedoids, dbscan, linkage, pdist2 # name: # type: sq_string # elements: 1 # length: 65 Partition observations into k clusters using spectral clustering. statistics-release-1.9.2/inst/Clustering/evalclusters.m000066400000000000000000000525251524624707500233460ustar00rootroot00000000000000## Copyright (C) 2021 Stefano Guidoni ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{eva} =} evalclusters (@var{x}, @var{clust}, @var{criterion}) ## @deftypefnx {statistics} {@var{eva} =} evalclusters (@dots{}, @qcode{Name}, @qcode{Value}) ## ## Create a clustering evaluation object to find the optimal number of clusters. ## ## @code{evalclusters} creates a clustering evaluation object to evaluate the ## optimal number of clusters for data @var{x}, using criterion @var{criterion}. ## The input data @var{x} is a matrix with @code{n} observations of @code{p} ## variables. ## The evaluation criterion @var{criterion} is one of the following: ## @table @code ## @item @qcode{CalinskiHarabasz} ## to create a @code{CalinskiHarabaszEvaluation} object. ## ## @item @qcode{DaviesBouldin} ## to create a @code{DaviesBouldinEvaluation} object. ## ## @item @qcode{gap} ## to create a @code{GapEvaluation} object. ## ## @item @qcode{silhouette} ## to create a @code{SilhouetteEvaluation} object. ## ## @end table ## The clustering algorithm @var{clust} is one of the following: ## @table @code ## @item @qcode{kmeans} ## to cluster the data using @code{kmeans} with @code{EmptyAction} set to ## @code{singleton} and @code{Replicates} set to 5. ## ## @item @qcode{linkage} ## to cluster the data using @code{clusterdata} with @code{linkage} set to ## @code{Ward}. ## ## @item @qcode{gmdistribution} ## to cluster the data using @code{fitgmdist} with @code{SharedCov} set to ## @code{true} and @code{Replicates} set to 5. ## ## @end table ## If the @var{criterion} is @code{CalinskiHarabasz}, @code{DaviesBouldin}, or ## @code{silhouette}, @var{clust} can also be a function handle to a function ## of the form @code{c = clust(x, k)}, where @var{x} is the input data, ## @var{k} the number of clusters to evaluate and @var{c} the clustering result. ## The clustering result can be either an array of size @code{n} with @code{k} ## different integer values, or a matrix of size @code{n} by @code{k} with a ## likelihood value assigned to each one of the @code{n} observations for each ## one of the @var{k} clusters. In the latter case, each observation is assigned ## to the cluster with the higher value. If the @var{criterion} is ## @code{CalinskiHarabasz}, @code{DaviesBouldin}, or ## @code{silhouette}, @var{clust} can also be a matrix of size @code{n} by ## @code{k}, where @code{k} is the number of proposed clustering solutions, so ## that each column of @var{clust} is a clustering solution. ## ## In addition to the obligatory @var{x}, @var{clust} and @var{criterion} inputs ## there is a number of optional arguments, specified as pairs of @code{Name} ## and @code{Value} options. The known @code{Name} arguments are: ## @table @code ## @item @qcode{KList} ## a vector of positive integer numbers, that is the cluster sizes to evaluate. ## This option is necessary, unless @var{clust} is a matrix of proposed ## clustering solutions. ## ## @item @qcode{Distance} ## a distance metric as accepted by the chosen @var{clust}. It can be the ## name of the distance metric as a string or a function handle. When ## @var{criterion} is @code{silhouette}, it can be a vector as created by ## function @code{pdist}. Valid distance metric strings are: @code{sqEuclidean} ## (default), @code{Euclidean}, @code{cityblock}, @code{cosine}, ## @code{correlation}, @code{Hamming}, @code{Jaccard}. ## Only used by @code{silhouette} and @code{gap} evaluation. ## ## @item @qcode{ClusterPriors} ## the prior probabilities of each cluster, which can be either @code{empirical} ## (default), or @code{equal}. When @code{empirical} the silhouette value is ## the average of the silhouette values of all points; when @code{equal} the ## silhouette value is the average of the average silhouette value of each ## cluster. Only used by @code{silhouette} evaluation. ## ## @item @qcode{B} ## the number of reference datasets generated from the reference distribution. ## Only used by @code{gap} evaluation. ## ## @item @qcode{ReferenceDistribution} ## the reference distribution used to create the reference data. It can be ## @code{PCA} (default) for a distribution based on the principal components of ## @var{X}, or @code{uniform} for a uniform distribution based on the range of ## the observed data. @code{PCA} is currently not implemented. ## Only used by @code{gap} evaluation. ## ## @item @qcode{SearchMethod} ## the method for selecting the optimal value with a @code{gap} evaluation. It ## can be either @code{globalMaxSE} (default) for selecting the smallest number ## of clusters which is inside the standard error of the maximum gap value, or ## @code{firstMaxSE} for selecting the first number of clusters which is inside ## the standard error of the following cluster number. ## Only used by @code{gap} evaluation. ## ## @end table ## ## Output @var{eva} is a clustering evaluation object. ## ## @seealso{CalinskiHarabaszEvaluation, DaviesBouldinEvaluation, GapEvaluation, ## SilhouetteEvaluation} ## ## @end deftypefn function cc = evalclusters (x, clust, criterion, varargin) ## input check if (nargin < 3) print_usage (); endif ## parsing input data if ((! ismatrix (x)) || (! isnumeric (x))) error ("evalclusters: X must be a numeric matrix."); endif ## useful values for input check n = rows (x); p = columns (x); ## parsing the clustering algorithm if (ischar (clust)) clust = lower (clust); if (! any (strcmpi (clust, {'kmeans', 'linkage', 'gmdistribution'}))) error ("evalclusters: unknown clustering algorithm '%s'", clust); endif elseif (! isscalar (clust)) if ((! isnumeric (clust)) || (length (size (clust)) != 2) || ... (rows (clust) != n)) error ("evalclusters: invalid matrix of clustering solutions."); endif elseif (! isa (clust, 'function_handle')) error ("evalclusters: invalid argument for 'clust'."); endif ## parsing the criterion parameter ## we check the rest later, as the check depends on the chosen criterion if (! ischar (criterion)) error ("evalclusters: invalid criterion, it must be a string."); else criterion = lower (criterion); if (! any (strcmpi (criterion, {'calinskiharabasz', 'daviesbouldin', ... 'silhouette', 'gap'}))) error ("evalclusters: unknown criterion '%s'", criterion); endif endif ## some default value klist = []; distance = 'sqeuclidean'; clusterpriors = 'empirical'; b = 100; referencedistribution = 'pca'; searchmethod = 'globalmaxse'; ## parse the name/value pairs pair_index = 1; while (pair_index < (nargin - 3)) ## type check if (! ischar (varargin{pair_index})) error ("evalclusters: invalid property, string expected."); endif ## now parse the parameter switch (lower (varargin{pair_index})) case 'klist' ## klist must be an array of positive integer numbers; ## there is a special case when it can be empty, but that is not the ## suggested way to use it (it is better to omit it instead) if (isempty (varargin{pair_index + 1})) if (ischar (clust) || isa (clust, 'function_handle')) error (strcat ("evalclusters: 'KList' can be empty", ... " only when 'clust' is a matrix")); endif elseif ((! isnumeric (varargin{pair_index + 1})) || ... (! isvector (varargin{pair_index + 1})) || ... any (find (varargin{pair_index + 1} < 1)) || ... any (floor (varargin{pair_index + 1}) != varargin{pair_index + 1})) error ("evalclusters: 'KList' must be an array of positive integers.") endif klist = varargin{pair_index + 1}; case 'distance' ## used by silhouette and gap if (! (strcmpi (criterion, 'silhouette') || strcmpi (criterion, 'gap'))) error (strcat ("evalclusters: distance metric cannot", ... " be used with '%s' criterion"), criterion); endif if (ischar (varargin{pair_index + 1})) if (! any (strcmpi (varargin{pair_index + 1}, ... {'sqeuclidean', 'euclidean', 'cityblock', 'cosine', ... 'correlation', 'hamming', 'jaccard'}))) error ("evalclusters: unknown distance criterion '%s'", ... varargin{pair_index + 1}); endif elseif (! isa (varargin{pair_index + 1}, 'function_handle') || ! ((isvector (varargin{pair_index + 1}) && ... isnumeric (varargin{pair_index + 1})))) error ("evalclusters: invalid distance metric."); endif distance = varargin{pair_index + 1}; case 'clusterpriors' ## used by silhouette evaluation if (! strcmpi (criterion, 'silhouette')) error (strcat ("evalclusters: cluster prior probabilities cannot", ... " be used with '%s' criterion"), criterion); endif if (any (strcmpi (varargin{pair_index + 1}, {'empirical', 'equal'}))) clusterpriors = lower (varargin{pair_index + 1}); else error ("evalclusters: invalid cluster prior probabilities value."); endif case 'b' ## used by gap evaluation if (! isnumeric (varargin{pair_index + 1}) || ... ! isscalar (varargin{pair_index + 1}) || ... varargin{pair_index + 1} != floor (varargin{pair_index + 1}) || ... varargin{pair_index + 1} < 1) error ("evalclusters: b must a be positive integer number."); endif b = varargin{pair_index + 1}; case 'referencedistribution' ## used by gap evaluation if (! ischar (varargin{pair_index + 1}) || ! any (strcmpi ... (varargin{pair_index + 1}, {'pca', 'uniform'}))) error (strcat ("evalclusters: the reference distribution", ... " must be either 'PCA' or 'uniform'.")); endif referencedistribution = lower (varargin{pair_index + 1}); case 'searchmethod' ## used by gap evaluation if (! ischar (varargin{pair_index + 1}) || any (strcmpi ... (varargin{pair_index + 1}, {'globalmaxse', 'uniform'}))) error (strcat ("evalclusters: the search method must be", ... " either'globalMaxSE' or 'firstmaxse'")); endif searchmethod = lower (varargin{pair_index + 1}); otherwise error ("evalclusters: unknown property %s", varargin{pair_index}); endswitch pair_index += 2; endwhile ## check if there are parameters without a value or a name left if (nargin - 2 - pair_index) if (ischar (varargin{pair_index})) error ("evalclusters: invalid parameter '%s'", varargin{pair_index}); else error ("evalclusters: invalid parameter '%d'", varargin{pair_index}); endif endif ## another check on klist if (isempty (klist) && (ischar (clust) || isa (clust, 'function_handle'))) error (strcat ("evalclusters: 'KList' can be empty", ... " only when 'clust' is a matrix")); endif ## When 'clust' is a matrix of solutions and no 'KList' was given, each ## column is a solution in its own right, so the number of clusters it holds ## is the number of distinct labels in it -- not the column's position. ## Numbering by position evaluated column j as k = j, which mislabelled every ## result and, because the criteria loop over the labels 1 : k, silently left ## out every cluster past the j-th. if (isempty (klist) && isnumeric (clust)) klist = arrayfun (@(j) numel (unique (clust(! isnan (clust(:, j)), j))), ... 1 : columns (clust)); if (numel (unique (klist)) != numel (klist)) error (strcat ("evalclusters: two or more columns of 'clust' propose", ... " the same number of clusters, so 'KList' cannot be", ... " inferred from them.")); endif ## The evaluation classes sort and de-duplicate the cluster sizes they are ## given, so the columns have to be put in the same order; otherwise a ## solution would be scored against another column's number of clusters. [klist, korder] = sort (klist); clust = clust(:, korder); endif ## main switch (lower (criterion)) case 'calinskiharabasz' ## further compatibility checks between the chosen parameters are ## delegated to the class constructor cc = CalinskiHarabaszEvaluation (x, clust, klist); case 'daviesbouldin' ## further compatibility checks between the chosen parameters are ## delegated to the class constructor cc = DaviesBouldinEvaluation (x, clust, klist); case 'silhouette' ## further compatibility checks between the chosen parameters are ## delegated to the class constructor cc = SilhouetteEvaluation (x, clust, klist, distance, clusterpriors); case 'gap' ## gap cannot be used with a pre-computed solution, i.e. a matrix for ## 'clust', and klist must be specified if (isnumeric (clust)) error (strcat ("evalclusters: 'clust' must be a clustering",... " algorithm when using the gap criterion.")); endif if (isempty (klist)) error (strcat ("evalclusters: 'klist' cannot be empty", ... " when using the gap criterion.")); endif cc = GapEvaluation (x, clust, klist, b, distance, ... referencedistribution, searchmethod); otherwise error ("evalclusters: invalid criterion '%s'", criterion); endswitch endfunction ## Demo code %!demo %! load fisheriris; %! eva = evalclusters (meas, 'kmeans', 'calinskiharabasz', 'KList', [1:6]) %! plot (eva) ## input tests %!error evalclusters () %!error evalclusters ([1 1;0 1]) %!error evalclusters ([1 1;0 1], 'kmeans') %!error ... %! evalclusters ('abc', 'kmeans', 'gap') %!error evalclusters ([1 1;0 1], 'xxx', 'gap') %!error evalclusters ([1 1;0 1], [1 2], 'gap') %!error evalclusters ([1 1;0 1], 1.2, 'gap') %!error evalclusters ([1 1;0 1], [1; 2], 123) %!error evalclusters ([1 1;0 1], [1; 2], 'xxx') %!error <'KList' can be empty*> evalclusters ([1 1;0 1], 'kmeans', 'gap') %!error evalclusters ([1 1;0 1], [1; 2], 'gap', 1) %!error evalclusters ([1 1;0 1], [1; 2], 'gap', 1, 1) %!error evalclusters ([1 1;0 1], [1; 2], 'gap', 'xxx', 1) %!error <'KList'*> evalclusters ([1 1;0 1], [1; 2], 'gap', 'KList', [-1 0]) %!error <'KList'*> evalclusters ([1 1;0 1], [1; 2], 'gap', 'KList', [1 .5]) %!error <'KList'*> evalclusters ([1 1;0 1], [1; 2], 'gap', 'KList', [1 1; 1 1]) %!error evalclusters ([1 1;0 1], [1; 2], 'gap', ... %! 'distance', 'a') %!error evalclusters ([1 1;0 1], [1; 2], 'daviesbouldin', ... %! 'distance', 'a') %!error evalclusters ([1 1;0 1], [1; 2], 'gap', ... %! 'clusterpriors', 'equal') %!error evalclusters ([1 1;0 1], [1; 2], ... %! 'silhouette', 'clusterpriors', 'xxx') %!error <'clust' must be a clustering*> evalclusters ([1 1;0 1], [1; 2], 'gap') %!test %! load fisheriris; %! eva = evalclusters (meas, 'kmeans', 'calinskiharabasz', 'KList', [1:6]); %! assert_equal (isa (eva, 'CalinskiHarabaszEvaluation'), true); %! assert_equal (eva.NumObservations, 150); %! assert_equal (eva.OptimalK, 3); %! assert_equal (eva.InspectedK, [1 2 3 4 5 6]); ## A matrix of solutions with no 'KList' takes the number of clusters from the ## labels in each column, not from the column's position. Numbering by ## position evaluated column j as k = j, so the first criterion was always NaN ## and the rest were computed for the wrong k, over only the first j clusters. %!test %! x = [randn(20,2); 6 + randn(20,2); [12, 0] + randn(20,2)]; %! k2 = [ones(30,1); 2*ones(30,1)]; %! k3 = [ones(20,1); 2*ones(20,1); 3*ones(20,1)]; %! k4 = [ones(15,1); 2*ones(15,1); 3*ones(15,1); 4*ones(15,1)]; %! sols = [k2, k3, k4]; %! for crit = {'CalinskiHarabasz', 'DaviesBouldin', 'silhouette'} %! eva = evalclusters (x, sols, crit{1}); %! assert_equal (eva.InspectedK, [2, 3, 4]); %! assert_equal (any (isnan (eva.CriterionValues)), false); %! ## an explicit KList naming the same sizes must give the same answer %! ref = evalclusters (x, sols, crit{1}, 'KList', [2, 3, 4]); %! assert_equal (eva.CriterionValues, ref.CriterionValues); %! assert_equal (eva.OptimalK, ref.OptimalK); %! endfor ## Columns need not be given in ascending order of size. The evaluation ## classes sort the cluster sizes, so the columns are reordered to match and ## each solution is still scored against its own k. %!test %! x = [randn(20,2); 6 + randn(20,2); [12, 0] + randn(20,2)]; %! k4 = [ones(15,1); 2*ones(15,1); 3*ones(15,1); 4*ones(15,1)]; %! k2 = [ones(30,1); 2*ones(30,1)]; %! fwd = evalclusters (x, [k2, k4], 'CalinskiHarabasz'); %! rev = evalclusters (x, [k4, k2], 'CalinskiHarabasz'); %! assert_equal (rev.InspectedK, [2, 4]); %! assert_equal (rev.CriterionValues, fwd.CriterionValues); %! assert_equal (rev.OptimalK, fwd.OptimalK); ## Two columns proposing the same number of clusters cannot be told apart by ## the cluster sizes alone. %!error ... %! evalclusters ([randn(20,2); 6 + randn(20,2)], ... %! [[ones(20,1); 2*ones(20,1)], [2*ones(20,1); ones(20,1)]], ... %! 'CalinskiHarabasz') ## Shape and naming of the returned object, verified against MATLAB R2024a. %!shared X, sols %! randn ("seed", 11); %! X = [randn(20, 2); 6 + randn(20, 2); [12, 0] + randn(20, 2)]; %! sols = [[ones(30,1); 2*ones(30,1)], ... %! [ones(20,1); 2*ones(20,1); 3*ones(20,1)], ... %! [ones(15,1); 2*ones(15,1); 3*ones(15,1); 4*ones(15,1)]]; %!test # given the clusterings, the object does not echo them back %! e = evalclusters (X, sols, "CalinskiHarabasz"); %! assert_equal (isempty (e.OptimalY), true); %! assert_equal (isempty (e.ClusteringFunction), true); %! assert_equal (class (e.ClusteringFunction), "double"); %!test # asked to cluster, it reports both the solution and the function %! e = evalclusters (X, "kmeans", "CalinskiHarabasz", "KList", 2:4); %! assert_equal (size (e.OptimalY), [60, 1]); %! assert_equal (e.ClusteringFunction, "kmeans"); %!test # Missing carries one flag per observation, shaped like the observations %! e = evalclusters (X, sols, "CalinskiHarabasz"); %! assert_equal (size (e.Missing), [60, 1]); %! assert_equal (class (e.Missing), "logical"); %! Xn = X; Xn(5,1) = NaN; %! e = evalclusters (Xn, sols, "CalinskiHarabasz"); %! assert_equal (find (e.Missing), 5); %! assert_equal (e.NumObservations, 59); %!test # a criterion that is undefined everywhere leaves no optimal K %! e = evalclusters (X, ones (60, 1), "CalinskiHarabasz"); %! assert_equal (isnan (e.CriterionValues), true); %! assert_equal (isnan (e.OptimalK), true); %! assert_equal (isempty (e.OptimalY), true); %!test # criterion and option names are reported as MATLAB spells them %! assert_equal (evalclusters (X, sols, "CalinskiHarabasz").CriterionName, ... %! "CalinskiHarabasz"); %! assert_equal (evalclusters (X, sols, "DaviesBouldin").CriterionName, ... %! "DaviesBouldin"); %! assert_equal (evalclusters (X, sols, "silhouette").CriterionName, ... %! "Silhouette"); %! e = evalclusters (X, sols, "silhouette", "Distance", "sqeuclidean"); %! assert_equal (e.Distance, "sqEuclidean"); %! e = evalclusters (X, sols, "silhouette", "Distance", "cityblock"); %! assert_equal (e.Distance, "cityblock"); %!test # ClusterSilhouettes holds one mean per cluster, not one per observation %! e = evalclusters (X, sols, "silhouette"); %! assert_equal (numel (e.ClusterSilhouettes), 3); %! assert_equal (size (e.ClusterSilhouettes{1}), [2, 1]); %! assert_equal (size (e.ClusterSilhouettes{2}), [3, 1]); %! assert_equal (size (e.ClusterSilhouettes{3}), [4, 1]); %!test # the silhouette criterion honours the metric it was given %! a = evalclusters (X, sols, "silhouette", "Distance", "sqeuclidean"); %! b = evalclusters (X, sols, "silhouette", "Distance", "cityblock"); %! c = evalclusters (X, sols, "silhouette", "Distance", "cosine"); %! assert_equal (isequal (a.CriterionValues, b.CriterionValues), false); %! assert_equal (isequal (b.CriterionValues, c.CriterionValues), false); %! ## each cluster mean is the mean of that cluster's silhouette values %! si = silhouette (X, sols(:,2), "cityblock", "DoNotPlot"); %! m = [mean(si(sols(:,2) == 1)); mean(si(sols(:,2) == 2)); ... %! mean(si(sols(:,2) == 3))]; %! assert_equal (b.ClusterSilhouettes{2}, m, 1e-12); statistics-release-1.9.2/inst/Clustering/fitgmdist.m000066400000000000000000000455731524624707500226310ustar00rootroot00000000000000## Copyright (C) 2015 Lachlan Andrew ## Copyright (C) 2018 John Donoghue ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{GMdist} =} fitgmdist (@var{data}, @var{k}, @var{param1}, @var{value1}, @dots{}) ## ## Fit a Gaussian mixture model with @var{k} components to @var{data}. ## Each row of @var{data} is a data sample. Each column is a variable. ## ## Optional parameters are: ## @itemize ## @item @qcode{'start'}: Initialization conditions. Possible values are: ## @itemize ## @item @qcode{'randSample'} (default) Takes means uniformly from rows of data. ## @item @qcode{'plus'} Use k-means++ to initialize means. ## @item @qcode{'cluster'} Performs an initial clustering with 10% of the data. ## @item @var{vector} A vector whose length is the number of rows in data, and ## whose values are 1 to k specify the components each row is initially ## allocated to. The mean, variance, and weight of each component is calculated ## from that. ## @item @var{structure} A structure with fields @qcode{mu}, @qcode{Sigma} and ## @qcode{ComponentProportion}. ## @end itemize ## For @qcode{'randSample'}, @qcode{'plus'}, and @qcode{'cluster'}, the initial ## variance of each component is the variance of the entire data sample. ## ## @item @qcode{'Replicates'}: Number of random restarts to perform. ## ## @item @qcode{'RegularizationValue'} or @qcode{'Regularize'}: A small number ## added to the diagonal entries of the covariance to prevent singular ## covariances. ## ## @item @qcode{'SharedCovariance'} or @qcode{'SharedCov'} (logical). True if ## all components must share the same variance, to reduce the number of free ## parameters ## ## @item @qcode{'CovarianceType'} or @qcode{'CovType'} (string). Possible values ## are: ## @itemize ## @item @qcode{'full'} (default) Allow arbitrary covariance matrices. ## @item @qcode{'diagonal'} Force covariances to be diagonal, to reduce the ## number of free parameters. ## @end itemize ## ## @item @qcode{'Options'}: A structure with all of the following fields: ## @itemize ## @item @qcode{MaxIter} Maximum number of EM iterations (default 100). ## @item @qcode{TolFun} Threshold increase in likelihood to terminate EM ## (default 1e-6). ## @item @qcode{Display} Possible values are: ## @itemize ## @item @qcode{'off'} (default): Display nothing. ## @item @qcode{'final'}: Display the total number of iterations and likelihood ## once the execution completes. ## @item @qcode{'iter'}: Display the number of iteration and likelihood after ## each iteration. ## @end itemize ## @end itemize ## @item @qcode{'Weight'}: A column vector or @math{N*2} matrix. The first ## column consists of non-negative weights given to the samples. If these are ## all integers, this is equivalent to specifying @qcode{@var{weight}(i)} copies ## of row @qcode{i} of @var{data}, but potentially faster. If a row of ## @var{data} is used to represent samples that are similar but not identical, ## then the second column of @var{weight} indicates the variance of those ## original samples. Specifically, in the EM algorithm, the contribution of row ## @qcode{i} towards the variance is set to at least @qcode{@var{weight}(i,2)}, ## to prevent spurious components with zero variance. ## @end itemize ## ## @seealso{gmdistribution, kmeans} ## @end deftypefn function obj = fitgmdist (data, k, varargin) if (nargin < 2 || mod (nargin, 2) == 1) print_usage; endif [~, prop] = parseparams (varargin); ## defaults for options diagonalCovar = false; # "full". (true is "diagonal") sharedCovar = false; start = 'randSample'; replicates = 1; option.MaxIter = 100; option.TolFun = 1e-6; option.Display = 'off'; # "off" (1 is "final", 2 is "iter") Regularizer = 0; weights = []; # Each row i counts as "weights(i,1)" rows ## Remove rows containing NaN / NA data = data(! any (isnan (data), 2), :); ## Used for getting the number of samples nRows = rows (data); nCols = columns (data); ## Parse options while (! isempty (prop)) try switch (lower (prop{1})) case {'sharedcovariance', 'sharedcov'} sharedCovar = prop{2}; case {'covariancetype', 'covartype'} diagonalCovar = prop{2}; case {'regularizationvalue', 'regularize'} Regularizer = prop{2}; case 'replicates' replicates = prop{2}; case 'start' start = prop{2}; case 'weights' weights = prop{2}; case 'options' option.MaxIter = prop{2}.MaxIter; option.TolFun = prop{2}.TolFun; option.Display = prop{2}.Display; otherwise error ("fitgmdist: Unknown option %s.", prop{1}); endswitch catch ME if (length (prop) < 2) error ("fitgmdist: Option '%s' has no argument.", prop{1}); else rethrow (ME) endif end_try_catch prop = prop(3:end); endwhile ## Process options ## Check for the "replicates" property try if isempty (1:replicates) error ("fitgmdist: replicates must be positive."); endif catch error ("fitgmdist: invalid number of replicates."); end_try_catch ## check for the "option" property MaxIter = option.MaxIter; TolFun = option.TolFun; switch (lower (option.Display)) case 'off' Display = 0; case 'final' Display = 1; case 'iter' Display = 2; case 'notify' Display = 0; otherwise error ("fitgmdist: Unknown Display option %s.", option.Display); endswitch try p = ones (1, k) / k; # Default is uniform component proportions catch ME if (! isscalar (k) || ! isnumeric (k)) error ("fitgmdist: The second argument must be a numeric scalar."); else rethrow (ME) endif end_try_catch ## Check for the "start" property if (ischar (start)) start = lower (start); switch (start) case {'randsample', 'plus', 'cluster', 'randsamplep', 'plusp', 'clusterp'} otherwise error ("fitgmdist: Unknown Start value %s\n.", start); endswitch component_order_free = true; else component_order_free = false; if (! ismatrix (start) || ! isnumeric (start)) try mu = start.mu; Sigma = start.Sigma; if (isfield (start, 'ComponentProportion')) p = start.ComponentProportion(:)'; endif if (any (size (data, 2) != [size(mu, 2), size(Sigma, 1)]) || ... any (k != [size(mu,1), size(p,2)])) error ("fitgmdist: Start parameter has mismatched dimensions."); endif catch error ("fitgmdist: invalid start parameter."); end_try_catch else validIndices = 0; mu = zeros (k, nRows); Sigma = zeros (nRows, nRows, k); for i = 1:k idx = (start == i); validIndices = validIndices + sum (idx); mu(i,:) = mean (data(idx,:)); Sigma(:,:,i) = cov (data(idx,:)) + Regularizer * eye (nCols); endfor if (validIndices < nRows) error (strcat ("fitgmdist: Start is numeric, but is not", ... " integers between 1 and k.")); endif endif start = []; # so that variance isn't recalculated later replicates = 1; # Will be the same each time anyway endif ## Check for the "SharedCovariance" property if (! islogical (sharedCovar)) error ("fitgmdist: SharedCoveriance must be logical true or false."); endif ## Check for the "CovarianceType" property if (! islogical (diagonalCovar)) try if (strcmpi (diagonalCovar, 'diagonal')) diagonalCovar = true; elseif (strcmpi (diagonalCovar, 'full')) diagonalCovar = false; else error ("fitgmdist: CovarianceType must be Full or Diagonal."); endif catch error ("fitgmdist: CovarianceType must be 'Full' or 'Diagonal'."); end_try_catch endif ## Check for the "Regularizer" property try if (Regularizer < 0) error ("fitgmdist: Regularizer must be non-negative"); endif catch ME if (! isscalar (Regularizer) || ! isnumeric (Regularizer)) error ("fitgmdist: Regularizer must be a numeric scalar"); else rethrow (ME) endif end_try_catch ## Check for the "Weights" property and the matrix try if (! isempty (weights)) if (columns (weights) > 2 || any (weights(:) < 0)) error (strcat ("fitgmdist: weights must be a nonnegative", ... " numeric dx1 or dx2 matrix.")); endif if (rows (weights) != nRows) error (strcat ("fitgmdist: number of weights %d must match", ... " number of samples %d."), rows (weights), nRows); endif non_zero = (weights(:,1) > 0); weights = weights(non_zero,:); data = data(non_zero,:); nRows = rows (data); raw_samples = sum (weights(:,1)); else raw_samples = nRows; endif ## Validate the matrix if (! isreal (data(k,1))) error ("fitgmdist: first input argument must be a DxN real data matrix."); endif catch ME if (! isnumeric (data) || ! ismatrix (data) || ! isreal (data)) error ("fitgmdist: first input argument must be a DxN real data matrix."); elseif (k > nRows || k < 0) if (exists ('non_zero', 'var') && k <= length (non_zero)) error (strcat ("fitgmdist: The number of non-zero weights (%d)", ... " must be at least the number of components", ... " (%d)."), nRows, k); else error (strcat ("fitgmdist: The number of components (%d) must be", ... " a positive number less than the number of data", ... " rows (%d)."), k, nRows); endif elseif (! ismatrix (weights) || ! isnumeric (weights)) error (strcat ("fitgmdist: weights must be a nonnegative numeric", ... " dx1 or dx2 matrix.")); else rethrow (ME) endif end_try_catch ## Done processing options ####################################### ## #sed to hold the probability of each class, given each data vector try p_x_l = zeros (nRows, k); # probability of observation x given class l best = -realmax; best_params = []; diag_slice = 1:(nCols+1):(nCols)^2; ## Create index slices to calculate symmetric ## completion of upper triangular Mx lower_half = zeros (nCols * (nCols - 1) / 2, 1); upper_half = zeros (nCols * (nCols - 1) / 2, 1); i = 1; for rw = 1:nCols for cl = rw+1:nCols upper_half(i) = sub2ind ([nCols, nCols], rw, cl); lower_half(i) = sub2ind ([nCols, nCols], cl, rw); i = i + 1; endfor endfor for rep = 1:replicates if (! isempty (start)) ## Initialize the means switch (start) case {'randsample'} if (isempty (weights)) idx = randperm (nRows, k); else idx = randsample (nRows, k, false, weights); endif mu = data(idx, :); case {'plus'} # k-means++, by Arthur and Vassilios mu(1,:) = data(randi (nRows),:); d = inf (nRows, 1); # Distance to nearest centroid so far for i = 2:k d = min (d, sum (bsxfun (@minus, data, mu(i-1, :)).^2, 2)); # pick next sample with prob. prop to dist.*weights if (isempty (weights)) cs = cumsum (d); else cs = cumsum (d .* weights(:,1)); endif mu(i,:) = data(find (cs > rand * cs(end), 1), :); endfor case {'cluster'} subsamp = max (k, ceil (nRows/10)); if (isempty (weights)) idx = randperm (nRows, subsamp); else idx = randsample (nRows, subsamp, false, weights); endif [~, mu] = kmeans (data(idx), k, 'start', 'sample'); endswitch ## Initialize the variance, unless set explicitly Sigma = var (data) + Regularizer; if (! diagonalCovar) Sigma = diag (Sigma); endif if (! sharedCovar) Sigma = repmat (Sigma, [1, 1, k]); endif endif ## Run the algorithm iter = 1; log_likeli = -inf; incr = 1; while (incr > TolFun && iter <= MaxIter) iter = iter + 1; ####################################### ## "E step" ## Calculate probability of class l given observations for i = 1:k if (sharedCovar) sig = Sigma; else sig = Sigma(:,:,i); endif if (diagonalCovar) sig = diag (sig); endif try p_x_l(:, i) = mvnpdf (data, mu(i, :), sig); catch ME if (strfind (ME.message, 'positive definite')) error (strcat ("fitgmdist: Covariance is not positive", ... " definite. Increase RegularizationValue.")); else rethrow (ME) endif end_try_catch endfor ## Bayes' rule p_x_l = bsxfun (@times, p_x_l, p); # weight by priors p_l_x = bsxfun (@rdivide, p_x_l, sum (p_x_l, 2)); # Normalize ####################################### ## "M step" ## Calculate new parameters if (! isempty (weights)) p_l_x = bsxfun (@times, p_l_x, weights(:,1)); endif sum_p_l_x = sum (p_l_x); # row vec of \sum_{data} p(class|data,params) p = sum_p_l_x / raw_samples; # new proportions mu = bsxfun (@rdivide, p_l_x' * data, sum_p_l_x'); # new means if (sharedCovar) sumSigma = zeros (size (Sigma(:,:,1))); # diagonalCovar gives size endif for i = 1:k ## Sigma deviation = bsxfun (@minus, data, mu(i,:)); lhs = bsxfun (@times, p_l_x(:,i), deviation); ## Calculate covariance ## Iterate either over elements of the covariance matrix, ## since there should be fewer of those than rows of data. for rw = 1:nCols for cl = rw:nCols sig(rw,cl) = lhs(:,rw)' * deviation(:,cl); endfor endfor sig(lower_half) = sig(upper_half); sig = sig/sum_p_l_x(i) + Regularizer*eye (nCols); if (columns (weights) > 1) # don't give "singleton" clusters low var sig(diag_slice) = max (sig(diag_slice), weights(i,2)); endif if (diagonalCovar) sig = diag (sig)'; endif if (sharedCovar) sumSigma = sumSigma + sig * p(i); # Heuristic. Should it use else # old p? Something else? Sigma(:,:,i) = sig; endif endfor if (sharedCovar) Sigma = sumSigma; endif ####################################### ## Calculate the new (and relative change in) log-likelihood if (isempty (weights)) new_log_likeli = sum (log (sum (p_x_l, 2))); else new_log_likeli = sum (weights(:,1) .* log (sum (p_x_l, 2))); endif incr = (new_log_likeli - log_likeli)/max (1,abs (new_log_likeli)); if (Display == 2) fprintf ("iter %d log-likelihood %g\n", iter-1, new_log_likeli); endif log_likeli = new_log_likeli; endwhile if (log_likeli > best) best = log_likeli; best_params.mu = mu; best_params.Sigma = Sigma; best_params.p = p; endif endfor catch ME try if (1 < MaxIter), end catch error ("fitgmdist: invalid MaxIter."); end_try_catch rethrow (ME) end_try_catch ## List components in descending order of proportion, ## unless the order was implicitly specified by "start" if (component_order_free) [~, idx] = sort (-best_params.p); best_params.p = best_params.p (idx); best_params.mu = best_params.mu(idx,:); if (! sharedCovar) best_params.Sigma = best_params.Sigma(:,:,idx); endif endif ## Calculate number of parameters if (diagonalCovar) params = nCols; else params = nCols * (nCols+1) / 2; endif params = params*size (Sigma, 3) + 2*rows (mu) - 1; ## This works in Octave, but not in Matlab #obj = gmdistribution (best_params.mu, best_params.Sigma, best_params.p', extra); obj = gmdistribution (best_params.mu, best_params.Sigma, best_params.p'); obj.NegativeLogLikelihood = -best; obj.AIC = -2*(best - params); obj.BIC = -2*best + params * log (raw_samples); obj.Converged = (incr <= TolFun); obj.NumIterations = iter-1; obj.RegularizationValue = Regularizer; if (Display == 1) fprintf (" %d iterations log-likelihood = %g\n", ... obj.NumIterations, -obj.NegativeLogLikelihood); endif endfunction %!demo %! ## Generate a two-cluster problem %! rng (42); %! C1 = randn (100, 2) + 2; %! C2 = randn (100, 2) - 2; %! data = [C1; C2]; %! %! ## Perform clustering %! GMModel = fitgmdist (data, 2); %! %! ## Plot the result %! figure %! [heights, bins] = hist3 ([C1; C2]); %! [xx, yy] = meshgrid (bins{1}, bins{2}); %! bbins = [xx(:), yy(:)]; %! contour (reshape (GMModel.pdf (bbins), size (heights))); %!demo %! rng (42); %! Angle_Theta = [ 30 + 10 * randn(1, 10), 60 + 10 * randn(1, 10) ]'; %! nbOrientations = 2; %! initial_orientations = [38.0; 18.0]; %! initial_weights = ones (1, nbOrientations) / nbOrientations; %! initial_Sigma = 10 * ones (1, 1, nbOrientations); %! start = struct ('mu', initial_orientations, 'Sigma', initial_Sigma, ... %! 'ComponentProportion', initial_weights); %! GMModel_Theta = fitgmdist (Angle_Theta, nbOrientations, 'Start', start , ... %! 'RegularizationValue', 0.0001) ## Test results against MATLAB example %!test %! load fisheriris %! classes = unique (species); %! [~, score] = pca (meas, 'NumComponents', 2); %! options.MaxIter = 1000; %! options.TolFun = 1e-6; %! options.Display = 'off'; %! GMModel = fitgmdist (score, 2, 'Options', options); %! assert_equal (isa (GMModel, 'gmdistribution'), true); %! assert_equal (GMModel.mu, [1.3212, -0.0954; -2.6424, 0.1909], 1e-4); statistics-release-1.9.2/inst/Clustering/gmdistribution.m000066400000000000000000000551101524624707500236660ustar00rootroot00000000000000## Copyright (C) 2015 Lachlan Andrew ## Copyright (C) 2018-2020 John Donoghue ## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef gmdistribution ## -*- texinfo -*- ## @deftypefn {statistics} {@var{GMdist} =} gmdistribution (@var{mu}, @var{Sigma}) ## @deftypefnx {statistics} {@var{GMdist} =} gmdistribution (@var{mu}, @var{Sigma}, @var{p}) ## @deftypefnx {statistics} {@var{GMdist} =} gmdistribution (@var{mu}, @var{Sigma}, @var{p}, @var{extra}) ## ## Create an object of the gmdistribution class which represents a Gaussian ## mixture model with k components of n-dimensional Gaussians. ## ## Input @var{mu} is a k-by-n matrix specifying the n-dimensional mean of ## each of the k components of the distribution. ## ## Input @var{Sigma} is an array that specifies the variances of the ## distributions, in one of four forms depending on its dimension. ## @itemize ## @item n-by-n-by-k: Slice @var{Sigma}(:,:,i) is the variance of the ## i'th component ## @item 1-by-n-by-k: Slice diag(@var{Sigma}(1,:,i)) is the variance of the ## i'th component ## @item n-by-n: @var{Sigma} is the variance of every component ## @item 1-by-n-by-k: Slice diag(@var{Sigma}) is the variance of every ## component ## @end itemize ## ## If @var{p} is specified, it is a vector of length k specifying the ## proportion of each component. If it is omitted or empty, each component ## has an equal proportion. ## ## Input @var{extra} is used by fitgmdist to indicate the parameters of the ## fitting process. ## @seealso{fitgmdist} ## @end deftypefn properties ## -*- texinfo -*- ## @deftp {gmdistribution} {property} mu ## ## Component means ## ## A k-by-d matrix holding the mean of each of the k components, one ## per row, where d is the number of variables. This property is ## read-only. ## ## @end deftp mu ## -*- texinfo -*- ## @deftp {gmdistribution} {property} Sigma ## ## Component covariances ## ## The covariances of the components, in the form they were given in. ## A full covariance per component is d-by-d-by-k, a diagonal one per ## component 1-by-d-by-k, a full covariance shared by every component ## d-by-d, and a shared diagonal one 1-by-d. This property is ## read-only. ## ## @end deftp Sigma ## -*- texinfo -*- ## @deftp {gmdistribution} {property} ComponentProportion ## ## Mixing proportions ## ## A 1-by-k row vector holding the proportion of each component. The ## proportions are scaled to sum to 1, and are all equal when none was ## given. This property is read-only. ## ## @end deftp ComponentProportion ## -*- texinfo -*- ## @deftp {gmdistribution} {property} DistributionName ## ## Name of the distribution ## ## The character vector @qcode{'gaussian mixture distribution'}. This ## property is read-only. ## ## @end deftp DistributionName ## -*- texinfo -*- ## @deftp {gmdistribution} {property} NumComponents ## ## Number of mixture components ## ## A positive integer, the number of rows of @code{mu}. This property ## is read-only. ## ## @end deftp NumComponents ## -*- texinfo -*- ## @deftp {gmdistribution} {property} NumVariables ## ## Number of variables ## ## A positive integer, the dimension of each component, which is the ## number of columns of @code{mu}. This property is read-only. ## ## @end deftp NumVariables ## -*- texinfo -*- ## @deftp {gmdistribution} {property} CovarianceType ## ## Form of the component covariances ## ## A character vector, @qcode{'diagonal'} when each covariance was ## given as a row of variances and @qcode{'full'} when it was given as ## a matrix. This property is read-only. ## ## @end deftp CovarianceType ## -*- texinfo -*- ## @deftp {gmdistribution} {property} SharedCovariance ## ## Whether the components share one covariance ## ## A logical scalar, true when a single covariance was given for every ## component and false when one was given per component. This property ## is read-only. ## ## @end deftp SharedCovariance ## -*- texinfo -*- ## @deftp {gmdistribution} {property} AIC ## ## Akaike information criterion ## ## A scalar, twice the negative log-likelihood plus twice the number of ## estimated parameters. It is empty unless the object came from ## @code{fitgmdist}. This property is read-only. ## ## @end deftp AIC ## -*- texinfo -*- ## @deftp {gmdistribution} {property} BIC ## ## Bayesian information criterion ## ## A scalar, twice the negative log-likelihood plus the number of ## estimated parameters times the log of the number of observations. ## It is empty unless the object came from @code{fitgmdist}. This ## property is read-only. ## ## @end deftp BIC ## -*- texinfo -*- ## @deftp {gmdistribution} {property} Converged ## ## Whether the fit converged ## ## A logical scalar, true when the fit reached its tolerance within the ## iteration limit. It is empty unless the object came from ## @code{fitgmdist}. This property is read-only. ## ## @end deftp Converged ## -*- texinfo -*- ## @deftp {gmdistribution} {property} NegativeLogLikelihood ## ## Negative log-likelihood at the fit ## ## A scalar, the negative of the log-likelihood of the data under the ## fitted mixture. It is empty unless the object came from ## @code{fitgmdist}. This property is read-only. ## ## @end deftp NegativeLogLikelihood ## -*- texinfo -*- ## @deftp {gmdistribution} {property} NumIterations ## ## Iterations the fit took ## ## A positive integer. It is empty unless the object came from ## @code{fitgmdist}. This property is read-only. ## ## @end deftp NumIterations ## -*- texinfo -*- ## @deftp {gmdistribution} {property} RegularizationValue ## ## Regularization added to the covariance diagonal ## ## A nonnegative scalar added to the diagonal of each covariance to ## keep it positive definite. It is empty unless the object came from ## @code{fitgmdist}. This property is read-only. ## ## @end deftp RegularizationValue endproperties ## Kept out of the documented surface. MATLAB carries the same alias and ## hides it, along with eight other legacy names this class does not have. properties (Hidden) ## A second name for NegativeLogLikelihood, holding the same value. NlogL endproperties properties(Access = private) DiagonalCovariance ## bool summary of "CovarianceType" endproperties methods ## -*- texinfo -*- ## @deftypefn {gmdistribution} {@var{obj} =} gmdistribution (@var{mu}, @var{Sigma}) ## @deftypefnx {gmdistribution} {@var{obj} =} gmdistribution (@var{mu}, @var{Sigma}, @var{p}) ## @deftypefnx {gmdistribution} {@var{obj} =} gmdistribution (@var{mu}, @var{Sigma}, @var{p}, @var{extra}) ## ## Create a Gaussian mixture distribution. ## ## @var{mu} is a k-by-d matrix holding the mean of each of the k ## components, one per row, where d is the number of variables. ## ## @var{Sigma} holds the covariances in one of four forms. A full ## covariance per component is d-by-d-by-k and a diagonal one per ## component is 1-by-d-by-k, while a single d-by-d matrix or a single ## 1-by-d row of variances is shared by every component. The form given ## sets @code{CovarianceType} and @code{SharedCovariance}. ## ## @var{p} is a vector of k mixing proportions, scaled to sum to 1. A ## proportion may not be negative and they may not all be zero. When ## @var{p} is omitted or empty the components are equally weighted. ## ## @var{extra} carries the results of a fit and is passed by ## @code{fitgmdist}. It fills @code{AIC}, @code{BIC}, ## @code{Converged}, @code{NegativeLogLikelihood}, ## @code{NumIterations} and @code{RegularizationValue}, which stay ## empty for an object built by hand. ## ## @end deftypefn function obj = gmdistribution (mu,sigma,p = [],extra = []) obj.DistributionName = 'gaussian mixture distribution'; obj.mu = mu; obj.Sigma = sigma; obj.NumComponents = rows (mu); obj.NumVariables = columns (mu); if (isempty (p)) obj.ComponentProportion = ones (1,obj.NumComponents) / ... obj.NumComponents; else if any (p < 0) error ("gmdistribution: component weights must be non-negative"); endif s = sum (p); if (s == 0) error ("gmdistribution: component weights must not be all zero"); elseif (s != 1) p = p / s; endif obj.ComponentProportion = p(:)'; endif if (length (size (sigma)) == 3) obj.SharedCovariance = false; else obj.SharedCovariance = true; endif if (rows (sigma) == 1 && columns (mu) > 1) obj.DiagonalCovariance = true; obj.CovarianceType = 'diagonal'; else obj.DiagonalCovariance = false; ## full obj.CovarianceType = 'full'; endif if (! isempty (extra)) obj.AIC = extra.AIC; obj.BIC = extra.BIC; obj.Converged = extra.Converged; obj.NegativeLogLikelihood = extra.NegativeLogLikelihood; obj.NlogL = extra.NegativeLogLikelihood; obj.NumIterations = extra.NumIterations; obj.RegularizationValue = extra.RegularizationValue; endif endfunction ## -*- texinfo -*- ## @deftypefn {gmdistribution} {@var{c} =} cdf (@var{obj}, @var{X}) ## ## Cumulative distribution function of a Gaussian mixture distribution. ## ## @var{X} is an n-by-d matrix of points at which the distribution is ## evaluated, one point per row, where d is the number of variables of ## @var{obj}. @var{c} is an n-by-1 vector holding the value of the ## cumulative distribution function at each of them, the components' ## cumulative distributions summed with the mixing proportions in ## @code{ComponentProportion} as weights. ## ## @end deftypefn function c = cdf (obj, X) X = checkX (obj, X, 'cdf'); p_x_l = zeros (rows (X), obj.NumComponents); if (obj.SharedCovariance) if (obj.DiagonalCovariance) sig = diag (obj.Sigma); else sig = obj.Sigma; endif endif for i = 1:obj.NumComponents if (! obj.SharedCovariance) if (obj.DiagonalCovariance) sig = diag (obj.Sigma(:,:,i)); else sig = obj.Sigma(:,:,i); endif endif p_x_l(:,i) = mvncdf (X,obj.mu(i,:),sig)*obj.ComponentProportion(i); endfor c = sum (p_x_l, 2); endfunction ## -*- texinfo -*- ## @deftypefn {gmdistribution} {@var{idx} =} cluster (@var{obj}, @var{X}) ## @deftypefnx {gmdistribution} {[@var{idx}, @var{nlogl}] =} cluster (@var{obj}, @var{X}) ## @deftypefnx {gmdistribution} {[@var{idx}, @var{nlogl}, @var{P}] =} cluster (@var{obj}, @var{X}) ## @deftypefnx {gmdistribution} {[@var{idx}, @var{nlogl}, @var{P}, @var{logpdf}] =} cluster (@var{obj}, @var{X}) ## @deftypefnx {gmdistribution} {[@var{idx}, @var{nlogl}, @var{P}, @var{logpdf}, @var{M}] =} cluster (@var{obj}, @var{X}) ## ## Assign each observation to a mixture component. ## ## @var{X} is an n-by-d matrix of observations, one per row, where d is ## the number of variables of @var{obj}. Each is assigned to the ## component under which it is most probable. ## ## @var{idx} is an n-by-1 vector of component indices. @var{nlogl} is ## the negative log-likelihood of @var{X} under the mixture. @var{P} is ## an n-by-k matrix of posterior probabilities, one column per component, ## whose rows sum to one. @var{logpdf} is an n-by-1 vector holding the ## logarithm of the mixture density at each observation. @var{M} is an ## n-by-k matrix of squared Mahalanobis distances from each observation ## to each component mean. ## ## @end deftypefn function [idx, nlogl, P, logpdf, M] = cluster (obj,X) X = checkX (obj, X, 'cluster'); [p_x_l, M] = componentProb (obj, X); [~, idx] = max (p_x_l, [], 2); if (nargout >= 2) PDF = sum (p_x_l, 2); logpdf = log (PDF); nlogl = -sum (logpdf); if (nargout >= 3) P = bsxfun (@rdivide, p_x_l, PDF); endif endif endfunction ## -*- texinfo -*- ## @deftypefn {gmdistribution} {@var{D} =} mahal (@var{obj}, @var{X}) ## ## Squared Mahalanobis distance to each mixture component. ## ## @var{X} is an n-by-d matrix of observations, one per row, where d is ## the number of variables of @var{obj}. @var{D} is an n-by-k matrix ## holding the squared Mahalanobis distance from each observation to the ## mean of each of the k components, measured in the covariance of the ## component it is taken to. ## ## @end deftypefn function D = mahal (obj,X) X = checkX (obj, X, 'mahal'); [~, D] = componentProb (obj,X); endfunction ## -*- texinfo -*- ## @deftypefn {gmdistribution} {@var{c} =} pdf (@var{obj}, @var{X}) ## ## Probability density function of a Gaussian mixture distribution. ## ## @var{X} is an n-by-d matrix of points at which the density is ## evaluated, one point per row, where d is the number of variables of ## @var{obj}. @var{c} is an n-by-1 vector holding the density at each of ## them, the components' densities summed with the mixing proportions in ## @code{ComponentProportion} as weights. ## ## @end deftypefn function c = pdf (obj,X) X = checkX (obj, X, 'pdf'); p_x_l = componentProb (obj, X); c = sum (p_x_l, 2); endfunction ## -*- texinfo -*- ## @deftypefn {gmdistribution} {@var{c} =} posterior (@var{obj}, @var{X}) ## ## Posterior probability of each mixture component. ## ## @var{X} is an n-by-d matrix of observations, one per row, where d is ## the number of variables of @var{obj}. @var{c} is an n-by-k matrix ## whose (i,j) element is the probability that observation i was drawn ## from component j, so its rows sum to one. ## ## @end deftypefn function c = posterior (obj,X) X = checkX (obj, X, 'posterior'); p_x_l = componentProb (obj, X); c = bsxfun (@rdivide, p_x_l, sum (p_x_l, 2)); endfunction ## -*- texinfo -*- ## @deftypefn {gmdistribution} {@var{c} =} random (@var{obj}) ## @deftypefnx {gmdistribution} {@var{c} =} random (@var{obj}, @var{n}) ## ## Random numbers from a Gaussian mixture distribution. ## ## @var{n} is the number of observations to draw and defaults to 1. ## @var{c} is an @var{n}-by-d matrix holding one observation per row, ## where d is the number of variables of @var{obj}. Each row is drawn ## from a component chosen with probability @code{ComponentProportion}. ## ## @end deftypefn function c = random (obj,n) if nargin == 1 n = 1; endif c = zeros (n, obj.NumVariables); classes = randsample (obj.NumComponents, n, true, ... obj.ComponentProportion); if (obj.SharedCovariance) if (obj.DiagonalCovariance) sig = diag (obj.Sigma); else sig = obj.Sigma; endif endif for i = 1:obj.NumComponents idx = (classes == i); k = sum (idx); if (k > 0) if (! obj.SharedCovariance) if (obj.DiagonalCovariance) sig = diag (obj.Sigma(:,:,i)); else sig = obj.Sigma(:,:,i); endif endif # [sig] forces [sig] not to have class "diagonal", # since mvnrnd uses automatic broadcast, # which fails on structured matrices c(idx,:) = mvnrnd ([obj.mu(i,:)], [sig], k); endif endfor endfunction endmethods ######################################## methods (Hidden) function c = disp (obj) msg = ['Gaussian mixture distribution with %d ', ... "components in %d dimension(s)\n"]; fprintf (msg, obj.NumComponents, columns (obj.mu)); for i = 1:obj.NumComponents fprintf ("Clust %d: weight %d\n\tMean: ", ... i, obj.ComponentProportion(i)); fprintf ("%g ", obj.mu(i,:)); fprintf ("\n"); if (! obj.SharedCovariance) fprintf ("\tVariance:"); if (! obj.DiagonalCovariance) if (columns (obj.mu) > 1) fprintf ("\n"); endif disp (squeeze (obj.Sigma(:,:,i))) else fprintf (" diag("); fprintf ("%g ", obj.Sigma(:,:,i)); fprintf (")\n"); endif endif endfor if (obj.SharedCovariance) fprintf ("Shared variance\n"); if (! obj.DiagonalCovariance) obj.Sigma else fprintf (" diag("); fprintf ("%g ", obj.Sigma); fprintf (")\n"); endif endif if (! isempty (obj.AIC)) fprintf ("AIC=%g BIC=%g NLogL=%g Iter=%d Cged=%d Reg=%g\n", ... obj.AIC, obj.BIC, obj.NegativeLogLikelihood, ... obj.NumIterations, obj.Converged, obj.RegularizationValue); endif endfunction function c = display (obj) disp (obj); endfunction endmethods ######################################## methods(Static) ## -*- texinfo -*- ## @deftypefn {gmdistribution} {@var{obj} =} fit (@var{X}, @var{k}) ## @deftypefnx {gmdistribution} {@var{obj} =} fit (@var{X}, @var{k}, @var{Name}, @var{Value}) ## ## Fit a Gaussian mixture distribution to data. ## ## @var{X} is an n-by-d matrix of observations, one per row, and @var{k} ## is the number of components to fit. Any @var{Name}-@var{Value} pair ## accepted by @code{fitgmdist} may follow, which is the function this ## method calls and where the options are documented. @var{obj} is the ## fitted @code{gmdistribution} object. ## ## @end deftypefn function c = fit (X, k, varargin) c = fitgmdist (X, k, varargin{:}); endfunction endmethods ######################################## methods(Access = private) ## Probability density of (row of) X *and* component l ## Second argument is an array of the Mahalanobis distances function [p_x_l, M] = componentProb (obj, X) M = zeros (rows (X), obj.NumComponents); dets = zeros (1, obj.NumComponents); % sqrt(determinant) if (obj.SharedCovariance) if (obj.DiagonalCovariance) r = diag (sqrt (obj.Sigma)); else r = chol (obj.Sigma); endif endif for i = 1:obj.NumComponents dev = bsxfun (@minus, X, obj.mu(i,:)); if (! obj.SharedCovariance) if (obj.DiagonalCovariance) r = diag (sqrt (obj.Sigma(:,:,i))); else r = chol (obj.Sigma(:,:,i)); endif endif M(:,i) = sumsq (dev / r, 2); dets(i) = prod (diag (r)); endfor p_x_l = exp (-M/2); coeff = obj.ComponentProportion ./ ... ((2 * pi) ^ (obj.NumVariables / 2) .* dets); p_x_l = bsxfun (@times, p_x_l, coeff); endfunction ######################################## ## Check format of argument X function X = checkX (obj, X, name) if (columns (X) != obj.NumVariables) if (columns (X) == 1 && rows (X) == obj.NumVariables) X = X'; else error ("gmdistribution.%s: X has %d columns instead of %d\n", ... name, columns (X), obj.NumVariables); endif endif endfunction endmethods endclassdef %!test %! mu = eye (2); %! Sigma = eye (2); %! GM = gmdistribution (mu, Sigma); %! density = GM.pdf ([0 0; 1 1]); %! assert_equal (density(1) - density(2), 0, 1e-6); %! %! [idx, nlogl, P, logpdf,M] = cluster (GM, eye (2)); %! assert_equal (idx, [1; 2]); %! [idx2,nlogl2,P2,logpdf2] = GM.cluster (eye (2)); %! assert_equal (nlogl - nlogl2, 0, 1e-6); %! [idx3,nlogl3,P3] = cluster (GM, eye (2)); %! assert_equal (P - P3, zeros (2), 1e-6); %! [idx4,nlogl4] = cluster (GM, eye (2)); %! assert_equal (size (nlogl4), [1 1]); %! idx5 = cluster (GM, eye (2)); %! assert_equal (idx - idx5, zeros (2,1)); %! %! D = GM.mahal ([1;0]); %! assert_equal (D - M(1,:), zeros (1,2), 1e-6); %! %! P = GM.posterior ([0 1]); %! assert_equal (P - P2(2,:), zeros (1,2), 1e-6); %! %! R = GM.random(20); %! assert_equal (size (R), [20, 2]); %! %! R = GM.random(); %! assert_equal (size (R), [1, 2]); statistics-release-1.9.2/inst/Clustering/inconsistent.m000066400000000000000000000103021524624707500233350ustar00rootroot00000000000000## Copyright (C) 2020-2021 Stefano Guidoni ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Y} =} inconsistent (@var{Z}) ## @deftypefnx {statistics} {@var{Y} =} inconsistent (@var{Z}, @var{d}) ## ## Compute the inconsistency coefficient for each link of a hierarchical cluster ## tree. ## ## Given a hierarchical cluster tree @var{Z} generated by the @code{linkage} ## function, @code{inconsistent} computes the inconsistency coefficient for each ## link of the tree, using all the links down to the @var{d}-th level below that ## link. ## ## The default depth @var{d} is 2, which means that only two levels are ## considered: the level of the computed link and the level below that. ## ## Each row of @var{Y} corresponds to the row of same index of @var{Z}. ## The columns of @var{Y} are respectively: the mean of the heights of the links ## used for the calculation, the standard deviation of the heights of those ## links, the number of links used, the inconsistency coefficient. ## ## @strong{Reference} ## Jain, A., and R. Dubes. Algorithms for Clustering Data. ## Upper Saddle River, NJ: Prentice-Hall, 1988. ## ## @seealso{cluster, clusterdata, dendrogram, linkage, pdist, squareform} ## ## @end deftypefn function Y = inconsistent (Z, d = 2) ## check the input if (nargin < 1) || (nargin > 2) print_usage (); endif ## MATLAB compatibility: ## when d = 0, which does not make sense, the result of inconsistent is the ## same as d = 1, which is... inconsistent if ((d < 0) || (! isscalar (d)) || (mod (d, 1))) error ("inconsistent: d must be a positive integer scalar"); endif if ((columns (Z) != 3) || (! isnumeric (Z)) || ... (! (max (Z(end, 1:2)) == rows (Z) * 2))) error (strcat ("inconsistent: Z must be a matrix generated by the linkage", " function")); endif ## number of observations n = rows (Z) + 1; ## compute the inconsistency coefficient for every link for i = 1:rows (Z) v = inconsistent_recursion (i, d); # nested recursive function - see below Y(i, 1) = mean (v); Y(i, 2) = std (v); Y(i, 3) = length (v); ## the inconsistency coefficient is (current_link_height - mean) / std; ## if the standard deviation is zero, it is zero by definition if (Y(i, 2) != 0) Y(i, 4) = (v(end) - Y(i, 1)) / Y(i, 2); else Y(i, 4) = 0; endif endfor ## recursive function ## while depth > 1 search the links (columns 1 and 2 of Z) below the current ## link and then append the height of the current link to the vector v. ## The height of the starting link should be the last one of the vector. function v = inconsistent_recursion (index, depth) v = []; if (depth > 1) for j = 1:2 if (Z(index, j) > n) new_index = Z(index, j) - n; v = [v (inconsistent_recursion (new_index, depth - 1))]; endif endfor endif v(end+1) = Z(index, 3); endfunction endfunction ## Test input validation %!error inconsistent () %!error inconsistent ([1 2 1], 2, 3) %!error inconsistent (ones (2, 2)) %!error inconsistent ([1 2 1], -1) %!error inconsistent ([1 2 1], 1.3) %!error inconsistent ([1 2 1], [1 1]) %!error inconsistent (ones (2, 3)) ## Test output %!test %! load fisheriris; %! Z = linkage (meas, 'average', 'chebychev'); %! assert_equal (cond (inconsistent (Z)), 39.9, 1e-3); statistics-release-1.9.2/inst/Clustering/kmeans.m000066400000000000000000000603431524624707500221050ustar00rootroot00000000000000## Copyright (C) 2011 Soren Hauberg ## Copyright (C) 2012 Daniel Ward ## Copyright (C) 2015-2016 Lachlan Andrew ## Copyright (C) 2016 Michael Bentley ## Copyright (C) 2021 Stefano Guidoni ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{idx} =} kmeans (@var{data}, @var{k}) ## @deftypefnx {statistics} {[@var{idx}, @var{centers}] =} kmeans (@var{data}, @var{k}) ## @deftypefnx {statistics} {[@var{idx}, @var{centers}, @var{sumd}] =} kmeans (@var{data}, @var{k}) ## @deftypefnx {statistics} {[@var{idx}, @var{centers}, @var{sumd}, @var{dist}] =} kmeans (@var{data}, @var{k}) ## @deftypefnx {statistics} {[@dots{}] =} kmeans (@var{data}, @var{k}, @var{param1}, @var{value1}, @dots{}) ## @deftypefnx {statistics} {[@dots{}] =} kmeans (@var{data}, [], @qcode{'start'}, @var{start}, @dots{}) ## ## Perform a @var{k}-means clustering of the @math{N*D} matrix @var{data}. ## ## If parameter @qcode{'start'} is specified, then @var{k} may be empty ## in which case @var{k} is set to the number of rows of @var{start}. ## ## The outputs are: ## ## @multitable @columnfractions 0.15 0.8 ## @item @var{idx} @tab An @math{N*1} vector whose @math{i}-th element is ## the class to which row @math{i} of @var{data} is assigned. ## ## @item @var{centers} @tab A @math{K*D} array whose @math{i}-th row is the ## centroid of cluster @math{i}. ## ## @item @var{sumd} @tab A @math{k*1} vector whose @math{i}-th entry is the ## sum of the distances from samples in cluster @math{i} to centroid @math{i}. ## ## @item @var{dist} @tab An @math{N*k} matrix whose @math{i}@math{j}-th ## element is the distance from sample @math{i} to centroid @math{j}. ## @end multitable ## ## The following parameters may be placed in any order. Each parameter ## must be followed by its value, as in Name-Value pairs. ## ## @multitable @columnfractions 0.15 0.83 ## @headitem Name @tab Description ## @item @qcode{'Start'} @tab The initialization method for the centroids. ## @end multitable ## ## @multitable @columnfractions 0.19 0.75 ## @headitem Value @tab Description ## @item @qcode{'plus'} @tab The k-means++ algorithm. (Default) ## @item @qcode{'sample'} @tab A subset of @math{k} rows from ## @var{data}, sampled uniformly without replacement. ## @item @qcode{'cluster'} @tab Perform a pilot clustering on 10% of ## the rows of @var{data}. ## @item @qcode{'uniform'} @tab Each component of each centroid is ## drawn uniformly from the interval between the maximum and minimum values of ## that component within @var{data}. This performs poorly and is implemented ## only for Matlab compatibility. ## @item @var{numeric matrix} @tab A @math{k*D} matrix of centroid ## starting locations. The rows correspond to seeds. ## @item @var{numeric array} @tab A @math{k*D*r} array of centroid ## starting locations. The third dimension invokes replication of the ## clustering routine. Page @math{r} contains the set of seeds for replicate ## @math{r}. @qcode{kmeans} infers the number of replicates (specified by the ## @qcode{'Replicates'} Name-Value pair argument) from the size of the third ## dimension. ## @end multitable ## ## @multitable @columnfractions 0.15 0.838 ## @headitem Name @tab Description ## @item @qcode{'Distance'} @tab The distance measure used for partitioning ## and calculating centroids. ## @end multitable ## ## @multitable @columnfractions 0.19 0.75 ## @headitem Value @tab Description ## @item @qcode{'sqeuclidean'} @tab The squared Euclidean distance. ## i.e. the sum of the squares of the differences between corresponding ## components. In this case, the centroid is the arithmetic mean of all samples ## in its cluster. This is the only distance for which this algorithm is truly ## "k-means". ## @item @qcode{'cityblock'} @tab The sum metric, or L1 distance, ## i.e. the sum of the absolute differences between corresponding components. ## In this case, the centroid is the median of all samples in its cluster. ## This gives the k-medians algorithm. ## @item @qcode{'cosine'} @tab One minus the cosine of the included ## angle between points (treated as vectors). Each centroid is the mean of the ## points in that cluster, after normalizing those points to unit Euclidean ## length. ## @item @qcode{'correlation'} @tab One minus the sample correlation ## between points (treated as sequences of values). Each centroid is the ## component-wise mean of the points in that cluster, after centering and ## normalizing those points to zero mean and unit standard deviation. ## @item @qcode{'hamming'} @tab The number of components in which the ## sample and the centroid differ. In this case, the centroid is the median of ## all samples in its cluster. Unlike Matlab, Octave allows non-logical ## @var{data}. ## @end multitable ## ## @multitable @columnfractions 0.15 0.838 ## @headitem Name @tab Description ## @item @qcode{'EmptyAction'} @tab What to do when a centroid is not the ## closest to any data sample. ## @end multitable ## ## @multitable @columnfractions 0.19 0.75 ## @headitem Value @tab Description ## @item @qcode{'error'} @tab Throw an error. ## @item @qcode{'singleton'} @tab (Default) Select the row of ## @var{data} that has the highest error and use that as the new centroid. ## @item @qcode{'drop'} @tab Remove the centroid, and continue ## computation with one fewer centroid. The dimensions of the outputs ## @var{centroids} and @var{d} are unchanged, with values for omitted centroids ## replaced by NaN. ## @end multitable ## ## @multitable @columnfractions 0.15 0.838 ## @headitem Name @tab Description ## @item @qcode{'Display'} @tab Display a text summary. ## @end multitable ## ## @multitable @columnfractions 0.19 0.75 ## @headitem Value @tab Description ## @item @qcode{'off'} @tab (Default) Display no summary. ## @item @qcode{'final'} @tab Display a summary for each clustering ## operation. ## @item @qcode{'iter'} @tab Display a summary for each iteration of a ## clustering operation. ## @end multitable ## ## @multitable @columnfractions 0.15 0.838 ## @headitem Name @tab Value ## @item @qcode{'Replicates'} @tab A positive integer specifying the number ## of independent clusterings to perform. The output values are the values for ## the best clustering, i.e., the one with the smallest value of @var{sumd}. ## If @var{Start} is numeric, then @var{Replicates} defaults to ## (and must equal) the size of the third dimension of @var{Start}. ## Otherwise it defaults to 1. ## @item @qcode{'MaxIter'} @tab The maximum number of iterations to perform ## for each replicate. If the maximum change of any centroid is less than ## 0.001, then the replicate terminates even if @var{MaxIter} iterations have no ## occurred. The default is 100. ## @end multitable ## ## Example: ## ## [~,c] = kmeans (rand(10, 3), 2, "emptyaction", "singleton"); ## ## @seealso{linkage} ## @end deftypefn function [classes, centers, sumd, D] = kmeans (data, k, varargin) [reg, prop] = parseparams (varargin); ## defaults for options emptyaction = 'singleton'; start = 'plus'; replicates = 1; max_iter = 100; distance = 'sqeuclidean'; display = 'off'; replicates_set_explicitly = false; ## Remove rows containing NaN / NA, but record which rows are used data_idx = ! any (isnan (data), 2); original_rows = rows (data); data = data(data_idx,:); #used for getting the number of samples n_rows = rows (data); #used for convergence of the centroids err = 1; ## Input checking, validate the matrix if (! isnumeric (data) || ! ismatrix (data) || ! isreal (data)) error ("kmeans: first input argument must be a DxN real data matrix"); elseif (! isnumeric (k)) error ("kmeans: second argument must be numeric"); endif ## Parse options while (length (prop) > 0) if (length (prop) < 2) error ("kmeans: Option '%s' has no argument", prop{1}); endif switch (lower (prop{1})) case 'emptyaction' emptyaction = prop{2}; case 'start' start = prop{2}; case 'maxiter' max_iter = prop{2}; case 'distance' distance = prop{2}; case 'replicates' replicates = prop{2}; replicates_set_explicitly = true; case 'display' display = prop{2}; case {'onlinephase', 'options'} warning ("kmeans: Ignoring unimplemented option '%s'", prop{1}); otherwise error ("kmeans: Unknown option %s", prop{1}); endswitch prop = {prop{3:end}}; endwhile ## Process options ## check for the 'emptyaction' property switch (emptyaction) case {'singleton', 'error', 'drop'} ; otherwise d = [', ' disp(emptyaction)] (1:end-1); # strip trailing \n if (length (d) > 20) d = ''; endif error ("kmeans: unsupported empty cluster action parameter%s", d); endswitch ## check for the 'replicates' property if (! isnumeric (replicates) || ! isscalar (replicates) || ! isreal (replicates) || replicates < 1) d = [', ' disp(replicates)] (1:end-1); # strip trailing \n if (length (d) > 20) d = ''; endif error ("kmeans: invalid number of replicates%s", d); endif ## check for the 'MaxIter' property if (! isnumeric (max_iter) || ! isscalar (max_iter) || ! isreal (max_iter) || max_iter < 1) d = [', ' disp(max_iter)] (1:end-1); # strip trailing \n if (length (d) > 20) d = ''; endif error ("kmeans: invalid MaxIter%s", d); endif ## check for the 'start' property switch (lower (start)) case {'sample', 'plus', 'cluster'} start = lower (start); case {'uniform'} start = 'uniform'; min_data = min (data); range = max (data) - min_data; otherwise if (! isnumeric (start)) d = [', ' disp(start)] (1:end-1); # strip trailing \n if (length (d) > 20) d = ''; endif error ("kmeans: invalid start parameter%s", d); endif if (isempty (k)) k = rows (start); elseif (rows (start) != k) error (strcat ("kmeans: Number of initializers (%d)", " should match number of centroids (%d)"), rows (start), k); endif if (replicates_set_explicitly) if (replicates != size (start, 3)) error (strcat ("kmeans: The third dimension of the initializer (%d)", " should match the number of replicates (%d)"), ... size (start, 3), replicates); endif else replicates = size (start, 3); endif endswitch ## check for the 'distance' property ## dist returns the distance btwn each row of matrix x and a row vector c switch (lower (distance)) case 'sqeuclidean' dist = @(x, c) sumsq (bsxfun (@minus, x, c), 2); centroid = @(x) mean (x, 1); case 'cityblock' dist = @(x, c) sum (abs (bsxfun (@minus, x, c)), 2); centroid = @(x) median (x, 1); case 'cosine' ## Pre-normalize all data. ## (when Octave implements normr, will use data = normr (data) ) for i = 1:rows (data) data(i,:) = data(i,:) / sqrt (sumsq (data(i,:))); endfor dist = @(x, c) 1 - (x * c') ./ sqrt (sumsq (c)); centroid = @(x) mean (x, 1); ## already normalized case 'correlation' ## Pre-normalize all data. data = data - mean (data, 2); ## (when Octave implements normr, will use data = normr (data) ) for i = 1:rows (data) data(i,:) = data(i,:) / sqrt (sumsq (data(i,:))); endfor dist = @(x, c) 1 - (x * (c - mean (c))') ... ./ sqrt (sumsq (c - mean (c))); centroid = @(x) mean (x, 1); ## already normalized case 'hamming' dist = @(x, c) sum (bsxfun (@ne, x, c), 2); centroid = @(x) median (x, 1); otherwise error ("kmeans: unsupported distance parameter %s", distance); endswitch ## check for the 'display' property if (! strcmp (display, 'off')) display = lower (display); switch (display) case {'off', 'final'} ; case 'iter' printf ("%6s\t%6s\t%8s\t%12s\n", 'iter', 'phase', 'num', 'sum'); otherwise error ("kmeans: invalid display parameter %s", display); endswitch endif ## Done processing options ######################################## ## Now that k has been set (possibly by 'replicates' option), check/use it. if (! isscalar (k)) error ("kmeans: second input argument must be a scalar"); endif ## used to hold the distances from each sample to each class D = zeros (n_rows, k); best = Inf; best_centers = []; for rep = 1:replicates ## keep track of the number of data points that change class old_classes = zeros (rows (data), 1); n_changes = -1; ## check for the 'start' property switch (lower (start)) case 'sample' idx = randperm (n_rows, k); centers = data(idx, :); case 'plus' # k-means++, by Arthur and Vassilios(?) centers(1,:) = data(randi (n_rows),:); d = inf (n_rows, 1); # Distance to nearest centroid so far for i = 2:k d = min (d, dist(data, centers(i - 1, :))); centers(i,:) = data(find (cumsum (d) > rand * sum (d), 1), :); endfor case 'cluster' idx = randperm (n_rows, max (k, ceil (n_rows / 10))); [~, centers] = kmeans (data(idx,:), k, 'start', 'sample', ... 'distance', distance); case 'uniform' # vectorised 'min_data + range .* rand' centers = bsxfun (@plus, min_data, bsxfun (@times, range, rand (k, columns (data)))); otherwise centers = start(:,:,rep); endswitch ## Run the algorithm iter = 1; ## Classify once before the loop; to set sumd, and if max_iter == 0 ## Compute distances and classify [D, classes, sumd] = update_dist (data, centers, D, k, dist); while (err > 0.001 && iter <= max_iter && n_changes != 0) ## Calculate new centroids replaced_centroids = []; ## Used by "emptyaction = singleton" for i = 1:k ## Get binary vector indicating membership in cluster i membership = (classes == i); ## Check for empty clusters if (! any (membership)) switch emptyaction ## if 'singleton', then find the point that is the ## farthest from any centroid (and not replacing an empty cluster ## from earlier in this pass) and add it to the empty cluster case 'singleton' available = setdiff (1:n_rows, replaced_centroids); [~, idx] = max (min (D(available,:)')); idx = available(idx); replaced_centroids = [replaced_centroids, idx]; classes(idx) = i; membership(idx) = 1; ## if 'drop' then set C and D to NA case 'drop' centers(i,:) = NA; D(i,:) = NA; ## if 'error' then throw the error otherwise error ("kmeans: empty cluster created"); endswitch endif ## end check for empty clusters ## update the centroids if (any (membership)) ## if we didn't "drop" the cluster centers(i, :) = centroid(data(membership, :)); endif endfor ## Compute distances, classes and sums [D, classes, new_sumd] = update_dist (data, centers, D, k, dist); ## calculate the difference in the sum of distances err = sum (sumd - new_sumd); ## update the current sum of distances sumd = new_sumd; ## compute the number of class changes n_changes = sum (old_classes != classes); old_classes = classes; ## display iteration status if (strcmp (display, 'iter')) printf ("%6d\t%6d\t%8d\t%12.3f\n", (iter), 1, ... n_changes, sum (sumd)); endif iter++; endwhile ## throw a warning if the algorithm did not converge if (iter > max_iter && err > 0.001 && n_changes != 0) warning ("kmeans: failed to converge in %d iterations", max_iter); endif if (sum (sumd) < sum (best) || isinf (best)) best = sumd; best_centers = centers; endif ## display final results if (strcmp (display, 'final')) printf ("Replicate %d, %d iterations, total sum of distances = %.3f.\n", ... rep, iter, sum (sumd)); endif endfor centers = best_centers; ## Compute final distances, classes and sums [D, classes, sumd] = update_dist (data, centers, D, k, dist); ## display final results if (strcmp (display, 'final') || strcmp (display, 'iter')) printf ("Best total sum of distances = %.3f\n", sum (sumd)); endif ## Return with equal size as inputs if (original_rows != rows (data)) final = NA (original_rows,1); final(data_idx) = classes; ## other positions already NaN / NA classes = final; endif endfunction ## Update distances, classes and sums function [D, classes, sumd] = update_dist (data, centers, D, k, dist) for i = 1:k D(:, i) = dist(data, centers(i, :)); endfor [~, classes] = min (D, [], 2); ## calculate the sum of within-class distances sumd = zeros (k, 1); for i = 1:k sumd(i) = sum (D(classes == i,i)); endfor endfunction %!demo %! ## Generate a two-cluster problem %! rng (42); %! C1 = randn (100, 2) + 1; %! C2 = randn (100, 2) - 1; %! data = [C1; C2]; %! %! ## Perform clustering %! [idx, centers] = kmeans (data, 2); %! %! ## Plot the result %! figure; %! plot (data(idx==1, 1), data(idx==1, 2), 'ro'); %! hold on; %! plot (data(idx==2, 1), data(idx==2, 2), 'bs'); %! plot (centers(:, 1), centers(:, 2), 'kv', 'markersize', 10); %! title ('A simple two-clusters example'); %! hold off; %!demo %! ## Cluster data using k-means clustering, then plot the cluster regions %! ## Load Fisher's iris data set and use the petal lengths and widths as %! ## predictors %! %! rng (42); %! load fisheriris %! X = meas(:,3:4); %! %! plot (X(:,1), X(:,2), 'k*', 'MarkerSize', 5); %! title ('Fisher''s Iris Data'); %! xlabel ('Petal Lengths (cm)'); %! ylabel ('Petal Widths (cm)'); %! %! ## Cluster the data. Specify k = 3 clusters %! [idx, C] = kmeans (X, 3); %! x1 = min (X(:,1)):0.01:max (X(:,1)); %! x2 = min (X(:,2)):0.01:max (X(:,2)); %! [x1G, x2G] = meshgrid (x1, x2); %! XGrid = [x1G(:), x2G(:)]; %! %! idx2Region = kmeans (XGrid, 3, 'MaxIter', 10, 'Start', C); %! figure; %! gscatter (XGrid(:,1), XGrid(:,2), idx2Region, ... %! [0, 0.75, 0.75; 0.75, 0, 0.75; 0.75, 0.75, 0], '..'); %! hold on; %! plot (X(:,1), X(:,2), 'k*', 'MarkerSize', 5); %! title ('Fisher''s Iris Data'); %! xlabel ('Petal Lengths (cm)'); %! ylabel ('Petal Widths (cm)'); %! legend ('Region 1', 'Region 2', 'Region 3', 'Data', 'Location', 'SouthEast'); %! hold off %!demo %! ## Partition Data into Two Clusters %! %! rng (42); %! r1 = randn (100, 2) * 0.75 + ones (100, 2); %! r2 = randn (100, 2) * 0.5 - ones (100, 2); %! X = [r1; r2]; %! %! plot (X(:,1), X(:,2), '.'); %! title ('Randomly Generated Data'); %! [idx, C] = kmeans (X, 2, 'Distance', 'cityblock', ... %! 'Replicates', 5, 'Display', 'final'); %! figure; %! plot (X(idx==1,1), X(idx==1,2), 'r.', 'MarkerSize', 12); %! hold on %! plot (X(idx==2,1), X(idx==2,2), 'b.', 'MarkerSize', 12); %! plot (C(:,1), C(:,2), 'kx', 'MarkerSize', 15, 'LineWidth', 3); %! legend ('Cluster 1', 'Cluster 2', 'Centroids', 'Location', 'NorthWest'); %! title ('Cluster Assignments and Centroids'); %! hold off %!demo %! ## Assign New Data to Existing Clusters %! %! ## Generate a training data set using three distributions %! rng (42); %! r1 = randn (100, 2) * 0.75 + ones (100, 2); %! r2 = randn (100, 2) * 0.5 - ones (100, 2); %! r3 = randn (100, 2) * 0.75; %! X = [r1; r2; r3]; %! %! ## Partition the training data into three clusters by using kmeans %! %! [idx, C] = kmeans (X, 3); %! %! ## Plot the clusters and the cluster centroids %! %! gscatter (X(:,1), X(:,2), idx, 'bgm', '***'); %! hold on %! plot (C(:,1), C(:,2), 'kx'); %! legend ('Cluster 1', 'Cluster 2', 'Cluster 3', 'Cluster Centroid') %! %! ## Generate a test data set %! r1 = randn (100, 2) * 0.75 + ones (100, 2); %! r2 = randn (100, 2) * 0.5 - ones (100, 2); %! r3 = randn (100, 2) * 0.75; %! Xtest = [r1; r2; r3]; %! %! ## Classify the test data set using the existing clusters %! ## Find the nearest centroid from each test data point by using pdist2 %! %! D = pdist2 (C, Xtest, 'euclidean'); %! [group, ~] = find (D == min (D)); %! %! ## Plot the test data and label the test data using idx_test with gscatter %! %! gscatter (Xtest(:,1), Xtest(:,2), group, 'bgm', 'ooo'); %! box on; %! legend ('Cluster 1', 'Cluster 2', 'Cluster 3', 'Cluster Centroid', ... %! 'Data classified to Cluster 1', 'Data classified to Cluster 2', ... %! 'Data classified to Cluster 3', 'Location', 'NorthWest'); %! title ('Assign New Data to Existing Clusters'); ## Test output %!test %! samples = 4; %! dims = 3; %! k = 2; %! [cls, c, d, z] = kmeans (rand (samples,dims), k, 'start', rand (k,dims, 5), %! 'emptyAction', 'singleton'); %! assert_equal (size (cls), [samples, 1]); %! assert_equal (size (c), [k, dims]); %! assert_equal (size (d), [k, 1]); %! assert_equal (size (z), [samples, k]); %!test %! samples = 4; %! dims = 3; %! k = 2; %! [cls, c, d, z] = kmeans (rand (samples,dims), [], 'start', rand (k,dims, 5), %! 'emptyAction', 'singleton'); %! assert_equal (size (cls), [samples, 1]); %! assert_equal (size (c), [k, dims]); %! assert_equal (size (d), [k, 1]); %! assert_equal (size (z), [samples, k]); %!test %! [cls, c] = kmeans ([1 0; 2 0], 2, 'start', [8,0;0,8], 'emptyaction', 'drop'); %! assert_equal (cls, [1; 1]); %! assert_equal (c, [1.5, 0; NA, NA]); %!test %! kmeans (rand (4,3), 2, 'start', rand (2,3, 5), 'replicates', 5, %! 'emptyAction', 'singleton'); %!test %! kmeans (rand (3,4), 2, 'start', 'sample', 'emptyAction', 'singleton'); %!test %! kmeans (rand (3,4), 2, 'start', 'plus', 'emptyAction', 'singleton'); %!test %! kmeans (rand (3,4), 2, 'start', 'cluster', 'emptyAction', 'singleton'); %!test %! kmeans (rand (3,4), 2, 'start', 'uniform', 'emptyAction', 'singleton'); %!test %! kmeans (rand (4,3), 2, 'distance', 'sqeuclidean', 'emptyAction', 'singleton'); %!test %! kmeans (rand (4,3), 2, 'distance', 'cityblock', 'emptyAction', 'singleton'); %!test %! kmeans (rand (4,3), 2, 'distance', 'cosine', 'emptyAction', 'singleton'); %!test %! kmeans (rand (4,3), 2, 'distance', 'correlation', 'emptyAction', 'singleton'); %!test %! kmeans (rand (4,3), 2, 'distance', 'hamming', 'emptyAction', 'singleton'); %!test %! kmeans ([1 0; 1.1 0], 2, 'start', eye (2), 'emptyaction', 'singleton'); ## Test input validation %!error kmeans (rand (3,2), 4); %!error kmeans ([1 0; 1.1 0], 2, 'start', eye (2), 'emptyaction', 'panic'); %!error kmeans (rand (4,3), 2, 'start', rand (2,3, 5), 'replicates', 1); %!error kmeans (rand (4,3), 2, 'start', rand (2,2)); %!error kmeans (rand (4,3), 2, 'distance', 'manhattan'); %!error kmeans (rand (3,4), 2, 'start', 'normal'); %!error kmeans (rand (4,3), 2, 'replicates', i); %!error kmeans (rand (4,3), 2, 'replicates', -1); %!error kmeans (rand (4,3), 2, 'replicates', []); %!error kmeans (rand (4,3), 2, 'replicates', [1 2]); %!error kmeans (rand (4,3), 2, 'replicates', 'one'); %!error kmeans (rand (4,3), 2, 'MAXITER', i); %!error kmeans (rand (4,3), 2, 'MaxIter', -1); %!error kmeans (rand (4,3), 2, 'maxiter', []); %!error kmeans (rand (4,3), 2, 'maxiter', [1 2]); %!error kmeans (rand (4,3), 2, 'maxiter', 'one'); %!error kmeans ([1 0; 1.1 0], 2, 'start', eye (2), 'emptyaction', 'error'); statistics-release-1.9.2/inst/Clustering/kmedoids.m000066400000000000000000000417171524624707500224320ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{idx} =} kmedoids (@var{X}, @var{k}) ## @deftypefnx {statistics} {[@var{idx}, @var{C}] =} kmedoids (@var{X}, @var{k}) ## @deftypefnx {statistics} {[@var{idx}, @var{C}, @var{sumd}] =} kmedoids (@var{X}, @var{k}) ## @deftypefnx {statistics} {[@var{idx}, @var{C}, @var{sumd}, @var{D}] =} kmedoids (@var{X}, @var{k}) ## @deftypefnx {statistics} {[@var{idx}, @var{C}, @var{sumd}, @var{D}, @var{midx}] =} kmedoids (@var{X}, @var{k}) ## @deftypefnx {statistics} {[@var{idx}, @var{C}, @var{sumd}, @var{D}, @var{midx}, @var{info}] =} kmedoids (@var{X}, @var{k}) ## @deftypefnx {statistics} {[@dots{}] =} kmedoids (@dots{}, @var{name}, @var{value}) ## ## Partition observations into @var{k} clusters using the k-medoids algorithm. ## ## @code{@var{idx} = kmedoids (@var{X}, @var{k})} partitions the @math{N*P} ## numeric matrix @var{X} into @var{k} clusters, each represented by one of the ## observations (its @emph{medoid}), and returns the @math{N*1} vector ## @var{idx} of cluster indices. Rows of @var{X} correspond to observations and ## columns correspond to features or variables. Unlike @code{kmeans}, whose ## centroids are the mean of each cluster, a medoid is an actual data point, ## which makes k-medoids more robust to outliers and applicable to any distance ## metric. ## ## @code{[@var{idx}, @var{C}, @var{sumd}, @var{D}, @var{midx}, @var{info}] = ## kmedoids (@dots{})} returns additional results: ## ## @multitable @columnfractions 0.18 0.8 ## @item @var{C} @tab a @math{k*P} matrix with the coordinates of the @var{k} ## medoids, one per row (@code{@var{C} = @var{X}(@var{midx},:)}). ## ## @item @var{sumd} @tab a @math{k*1} vector with the within-cluster sum of the ## distances from each point to its cluster medoid, measured with the selected ## metric. ## ## @item @var{D} @tab an @math{N*k} matrix with the distance from every ## observation to every medoid. ## ## @item @var{midx} @tab a @math{k*1} vector with the row indices into @var{X} ## of the @var{k} medoids. ## ## @item @var{info} @tab a scalar structure with the fields @qcode{'algorithm'}, ## @qcode{'start'}, @qcode{'distance'}, @qcode{'iterations'}, and ## @qcode{'bestReplicate'} describing the chosen run. ## @end multitable ## ## Additional parameters can be specified by @qcode{Name-Value} pair arguments. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Distance'} @tab the distance metric, one of ## @qcode{'sqeuclidean'} (default), @qcode{'euclidean'}, @qcode{'seuclidean'}, ## @qcode{'cityblock'}, @qcode{'minkowski'}, @qcode{'chebychev'}, ## @qcode{'cosine'}, @qcode{'correlation'}, @qcode{'hamming'}, ## @qcode{'jaccard'}, @qcode{'spearman'}, @qcode{'mahalanobis'}, or a custom ## distance function handle accepted by @code{pdist2}. ## ## @item @qcode{'Algorithm'} @tab the optimization algorithm, either ## @qcode{'pam'} (default) for Partitioning Around Medoids, which searches over ## all medoid/non-medoid swaps, or @qcode{'small'} for the faster Voronoi ## iteration that reassigns points and re-selects each cluster medoid until ## convergence. ## ## @item @qcode{'Start'} @tab the method used to choose the initial medoids: ## @qcode{'plus'} (default, k-means++), @qcode{'sample'} (a random subset of the ## observations), @qcode{'cluster'} (a preliminary pass on a subsample), or a ## @math{k*P} numeric matrix of starting medoid locations, each snapped to the ## nearest observation. A @math{k*P*R} array supplies a separate start for each ## of @var{R} replicates. ## ## @item @qcode{'Replicates'} @tab a positive integer number of times to repeat ## the clustering, each with a new set of initial medoids; the solution with the ## lowest total sum of distances is returned. The default is 1, or the size of ## the third dimension of a numeric @qcode{'Start'}. ## ## @item @qcode{'Options'} @tab a structure, as created by @code{statset}, whose ## @qcode{'MaxIter'} field caps the number of iterations (default 100). ## @end multitable ## ## @seealso{kmeans, linkage, pdist2, dbscan} ## @end deftypefn function [idx, C, sumd, D, midx, info] = kmedoids (X, k, varargin) ## Check number of input arguments if (nargin < 2) error ("kmedoids: too few input arguments."); endif ## Validate X if (! isnumeric (X) || ! isreal (X) || ndims (X) != 2 || isempty (X)) error ("kmedoids: X must be a nonempty real numeric matrix."); endif [N, P] = size (X); ## Validate k if (! isscalar (k) || ! isnumeric (k) || ! isreal (k) || k < 1 || fix (k) != k) error ("kmedoids: K must be a positive integer scalar."); endif if (k > N) error ("kmedoids: K cannot exceed the number of observations in X."); endif ## Defaults distance = "sqeuclidean"; algorithm = "pam"; start = "plus"; replicates = []; opts = statset ("kmedoids"); ## Parse Name-Value pairs if (mod (numel (varargin), 2) != 0) error ("kmedoids: each NAME must be followed by a VALUE."); endif while (numel (varargin) > 0) name = varargin{1}; val = varargin{2}; if (! ischar (name)) error ("kmedoids: optional argument names must be strings."); endif switch (tolower (name)) case "distance" distance = val; case "algorithm" algorithm = tolower (val); case "start" start = val; case "replicates" replicates = val; case "options" if (! isstruct (val)) error ("kmedoids: 'Options' must be a structure."); endif opts = statset (opts, val); case "onlinephase" ## Accepted for compatibility; the medoid update is already exact. otherwise error ("kmedoids: unknown parameter name '%s'.", name); endswitch varargin(1:2) = []; endwhile maxiter = statget (opts, "MaxIter", 100); ## Resolve the distance metric and map it onto a pdist2 metric metrics = {"sqeuclidean", "euclidean", "seuclidean", "cityblock", ... "minkowski", "chebychev", "cosine", "correlation", "hamming", ... "jaccard", "spearman", "mahalanobis"}; if (ischar (distance)) dname = tolower (distance); if (! any (strcmp (dname, metrics))) error ("kmedoids: unsupported distance metric '%s'.", distance); endif if (strcmp (dname, "sqeuclidean")) pmetric = "squaredeuclidean"; else pmetric = dname; endif elseif (is_function_handle (distance)) dname = distance; pmetric = distance; else error ("kmedoids: DISTANCE must be a metric name or a function handle."); endif ## Validate the algorithm switch (algorithm) case {"pam", "small"} ## supported case {"clara", "large"} error ("kmedoids: the '%s' algorithm is not implemented.", algorithm); otherwise error ("kmedoids: unknown algorithm '%s'.", algorithm); endswitch ## Resolve the start method and the number of replicates if (isnumeric (start)) if (columns (start) != P) error ("kmedoids: numeric START must have the same columns as X."); endif if (rows (start) != k) error ("kmedoids: numeric START must have K rows."); endif startmode = "numeric"; rep_start = size (start, 3); if (isempty (replicates)) replicates = rep_start; elseif (replicates != rep_start) error ("kmedoids: REPLICATES must equal the pages of a numeric START."); endif else startmode = tolower (start); if (! any (strcmp (startmode, {"plus", "sample", "cluster"}))) error ("kmedoids: unknown START '%s'.", start); endif if (isempty (replicates)) replicates = 1; endif endif if (! isscalar (replicates) || ! isnumeric (replicates) || replicates < 1 || fix (replicates) != replicates) error ("kmedoids: REPLICATES must be a positive integer scalar."); endif ## Precompute the full pairwise distance matrix in the chosen metric. For ## 'sqeuclidean' this holds squared distances, so sums match MATLAB. Dall = pdist2 (X, X, pmetric); ## Run the requested number of replicates and keep the cheapest solution. best_cost = Inf; best_M = []; best_iter = 0; best_rep = 1; for rep = 1:replicates switch (startmode) case "numeric" M = snap_to_data (X, start(:,:,rep)); case "sample" M = randperm (N, k)(:); case "plus" M = kpp_init (Dall, k); case "cluster" sub = randperm (N, max (k, ceil (N / 10))); Msub = voronoi_step (Dall(sub, sub), randperm (numel (sub), k)(:), ... maxiter); M = sub(Msub)(:); endswitch if (strcmp (algorithm, "pam")) [M, iter] = pam_swap (Dall, M, maxiter); else [M, iter] = voronoi_step (Dall, M, maxiter); endif cost = sum (min (Dall(:, M), [], 2)); if (cost < best_cost) best_cost = cost; best_M = M; best_iter = iter; best_rep = rep; endif endfor M = best_M; ## Assemble the outputs from the winning medoid set. D = Dall(:, M); [mind, idx] = min (D, [], 2); midx = M(:); C = X(midx, :); sumd = accumarray (idx, mind, [k, 1]); info = struct ("algorithm", algorithm, "start", startmode, ... "distance", dname, "iterations", best_iter, ... "bestReplicate", best_rep); endfunction ## Snap each starting location to the nearest observation (Euclidean) function M = snap_to_data (X, coords) k = rows (coords); M = zeros (k, 1); for j = 1:k [~, M(j)] = min (sum ((X - coords(j,:)) .^ 2, 2)); endfor endfunction ## k-means++ style seeding on the precomputed distance matrix function M = kpp_init (Dall, k) N = rows (Dall); M = zeros (k, 1); M(1) = randi (N); d = Dall(:, M(1)); for i = 2:k total = sum (d); if (total <= 0) M(i) = randi (N); # all remaining points coincide else M(i) = find (cumsum (d) >= rand * total, 1); endif d = min (d, Dall(:, M(i))); endfor endfunction ## Partitioning Around Medoids: keep the best cost-reducing swap until none help function [M, iter] = pam_swap (Dall, M, maxiter) N = rows (Dall); k = numel (M); curcost = sum (min (Dall(:, M), [], 2)); iter = 0; do iter += 1; bestcost = curcost; bj = 0; bh = 0; ismed = false (N, 1); ismed(M) = true; for j = 1:k for h = 1:N if (ismed(h)) continue; endif Mtry = M; Mtry(j) = h; c = sum (min (Dall(:, Mtry), [], 2)); if (c < bestcost) bestcost = c; bj = j; bh = h; endif endfor endfor improved = (bj > 0); if (improved) M(bj) = bh; curcost = bestcost; endif until (! improved || iter >= maxiter) endfunction ## Voronoi iteration ('small'): reassign, then re-select each cluster's medoid function [M, iter] = voronoi_step (Dall, M, maxiter) k = numel (M); iter = 0; do iter += 1; oldM = M; [~, lab] = min (Dall(:, M), [], 2); for j = 1:k members = find (lab == j); if (isempty (members)) continue; # keep the medoid of an empty cluster endif [~, loc] = min (sum (Dall(members, members), 1)); M(j) = members(loc); endfor until (isequal (M, oldM) || iter >= maxiter) endfunction %!demo %! ## Cluster three noisy blobs and mark the medoids. %! rng (42); %! X = [randn(20,2)*0.4 + 3; randn(20,2)*0.4; randn(20,2)*0.4 + [3 -3]]; %! [idx, C] = kmedoids (X, 3); %! gscatter (X(:,1), X(:,2), idx); %! hold on; %! plot (C(:,1), C(:,2), "kp", "MarkerSize", 14, "MarkerFaceColor", "y"); %! hold off; %! title ("kmedoids: three clusters with their medoids"); ## Exact anchor: numeric Start is RNG-free, verified against MATLAB R2024a %!test %! X = [1 1; 1.2 0.8; 0.8 1.1; 10 10; 10.2 9.8; 9.8 10.1; 1 10; 1.1 10.2; ... %! 0.9 9.8]; %! S = [1 1; 10 10; 1 10]; %! [idx, C, sumd, D, midx] = kmedoids (X, 3, "Start", S); %! assert_equal (idx, [1; 1; 1; 2; 2; 2; 3; 3; 3]); %! assert_equal (midx, [1; 4; 7]); %! assert_equal (C, [1 1; 10 10; 1 10]); %! assert_equal (sumd, [0.13; 0.13; 0.10], 1e-12); %! assert_equal (C, X(midx,:)); %! assert_equal (D(6,:), [160.25 0.05 77.45], 1e-12); %! assert_equal (size (D), [9 3]); ## Default path (sqeuclidean, k-means++): partition and medoid set are unique %!test %! X = [1 1; 1.2 0.8; 0.8 1.1; 10 10; 10.2 9.8; 9.8 10.1; 1 10; 1.1 10.2; ... %! 0.9 9.8]; %! [idx, C, sumd, D, midx, info] = kmedoids (X, 3); %! assert_equal (sort (midx), [1; 4; 7]); %! assert_equal (sort (sumd), [0.10; 0.13; 0.13], 1e-12); %! assert_equal (arrayfun (@(c) numel (unique (idx(idx == c))), 1:3), [1 1 1]); %! assert_equal (info.algorithm, "pam"); %! assert_equal (info.start, "plus"); %! assert_equal (info.distance, "sqeuclidean"); ## Euclidean metric: sumd is the un-squared distance (numeric Start, exact) %!test %! X = [1 1; 1.2 0.8; 0.8 1.1; 10 10; 10.2 9.8; 9.8 10.1; 1 10; 1.1 10.2; ... %! 0.9 9.8]; %! S = [1 1; 10 10; 1 10]; %! [idx, C, sumd, D, midx] = kmedoids (X, 3, "Start", S, "Distance", ... %! "euclidean"); %! assert_equal (midx, [1; 4; 7]); %! assert_equal (sumd, [sqrt(0.08)+sqrt(0.05); sqrt(0.08)+sqrt(0.05); ... %! 2*sqrt(0.05)], 1e-12); ## Cityblock metric: sumd is the L1 distance (numeric Start, exact) %!test %! X = [1 1; 1.2 0.8; 0.8 1.1; 10 10; 10.2 9.8; 9.8 10.1; 1 10; 1.1 10.2; ... %! 0.9 9.8]; %! S = [1 1; 10 10; 1 10]; %! [idx, C, sumd, D, midx] = kmedoids (X, 3, "Start", S, "Distance", ... %! "cityblock"); %! assert_equal (midx, [1; 4; 7]); %! assert_equal (sumd, [0.7; 0.7; 0.6], 1e-12); %! assert_equal (D(1,:), [0 18 9], 1e-12); ## The 'small' Voronoi algorithm finds the same partition on separated data %!test %! X = [1 1; 1.2 0.8; 0.8 1.1; 10 10; 10.2 9.8; 9.8 10.1; 1 10; 1.1 10.2; ... %! 0.9 9.8]; %! S = [1 1; 10 10; 1 10]; %! [idx, C, sumd, D, midx, info] = kmedoids (X, 3, "Start", S, "Algorithm", ... %! "small"); %! assert_equal (midx, [1; 4; 7]); %! assert_equal (sumd, [0.13; 0.13; 0.10], 1e-12); %! assert_equal (info.algorithm, "small"); ## Two clusters, a custom number of replicates runs and returns a valid result %!test %! X = [randn(10,2) - 5; randn(10,2) + 5]; %! [idx, C, sumd, D, midx] = kmedoids (X, 2, "Replicates", 3); %! assert_equal (numel (idx), 20); %! assert_equal (all (idx >= 1 & idx <= 2, 'all'), true); %! assert_equal (C, X(midx,:)); %! assert_equal (size (D), [20 2]); ## k == number of observations places one medoid on every point %!test %! X = [0; 5; 9]; %! [idx, C, sumd, D, midx] = kmedoids (X, 3, "Start", X); %! assert_equal (sort (midx), [1; 2; 3]); %! assert_equal (sumd, [0; 0; 0], 1e-12); ## Test input validation %!error kmedoids (1) %!error kmedoids ([], 2) %!error kmedoids ("a", 2) %!error kmedoids (ones (4,2), 0) %!error kmedoids (ones (4,2), 1.5) %!error ... %! kmedoids (ones (3,2), 4) %!error ... %! kmedoids (ones (4,2), 2, "Distance") %!error ... %! kmedoids (ones (4,2), 2, "foo", "bar") %!error ... %! kmedoids (ones (4,2), 2, "Distance", "taxicab") %!error ... %! kmedoids (ones (4,2), 2, "Algorithm", "clara") %!error ... %! kmedoids (ones (4,2), 2, "Algorithm", "foo") %!error ... %! kmedoids (ones (4,2), 2, "Start", "middle") %!error ... %! kmedoids (ones (4,2), 2, "Start", [1 1; 2 2; 3 3]) %!error ... %! kmedoids (ones (4,2), 2, "Start", [1 1 1; 2 2 2]) %!error ... %! kmedoids (ones (4,2), 2, "Replicates", 0) %!error ... %! kmedoids (ones (4,2), 2, "Options", 5) statistics-release-1.9.2/inst/Clustering/linkage.m000066400000000000000000000404431524624707500222400ustar00rootroot00000000000000## Copyright (C) 2008 Francesco Potortì ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} linkage (@var{d}) ## @deftypefnx {statistics} {@var{y} =} linkage (@var{d}, @var{method}) ## @deftypefnx {statistics} {@var{y} =} linkage (@var{x}) ## @deftypefnx {statistics} {@var{y} =} linkage (@var{x}, @var{method}) ## @deftypefnx {statistics} {@var{y} =} linkage (@var{x}, @var{method}, @var{metric}) ## @deftypefnx {statistics} {@var{y} =} linkage (@var{x}, @var{method}, @var{arglist}) ## ## Produce a hierarchical clustering dendrogram. ## ## @var{d} is the dissimilarity matrix relative to n observations, ## formatted as a @math{(n-1)*n/2}x1 vector as produced by @code{pdist}. ## Alternatively, @var{x} contains data formatted for input to ## @code{pdist}, @var{metric} is a metric for @code{pdist} and ## @var{arglist} is a cell array containing arguments that are passed to ## @code{pdist}. ## ## @code{linkage} starts by putting each observation into a singleton ## cluster and numbering those from 1 to n. Then it merges two ## clusters, chosen according to @var{method}, to create a new cluster ## numbered n+1, and so on until all observations are grouped into ## a single cluster numbered 2(n-1). Row k of the ## (m-1)x3 output matrix relates to cluster n+k: the first ## two columns are the numbers of the two component clusters and column ## 3 contains their distance. ## ## When several pairs of clusters are equally close, which of them is merged ## first is not determined by the data, and the cluster numbers in the first two ## columns are therefore implementation-defined. Only column 3, the sequence of ## merge distances, is reproducible across implementations, and even that is so ## only for the methods whose recomputation rule does not depend on the merge ## order (@qcode{"weighted"}, @qcode{"centroid"} and @qcode{"median"} do depend ## on it). Code that must be portable should read column 3, or the cluster ## assignment obtained from @code{cluster}, rather than the raw numbering. ## ## @var{method} defines the way the distance between two clusters is ## computed and how they are recomputed when two clusters are merged: ## ## @table @samp ## @item "single" (default) ## Distance between two clusters is the minimum distance between two ## elements belonging each to one cluster. Produces a cluster tree ## known as minimum spanning tree. ## ## @item "complete" ## Furthest distance between two elements belonging each to one cluster. ## ## @item "average" ## Unweighted pair group method with averaging (UPGMA). ## The mean distance between all pair of elements each belonging to one ## cluster. ## ## @item "weighted" ## Weighted pair group method with averaging (WPGMA). ## When two clusters A and B are joined together, the new distance to a ## cluster C is the mean between distances A-C and B-C. ## ## @item "centroid" ## Unweighted Pair-Group Method using Centroids (UPGMC). ## Assumes Euclidean metric. The distance between cluster centroids, ## each centroid being the center of mass of a cluster. ## ## @item "median" ## Weighted pair-group method using centroids (WPGMC). ## Assumes Euclidean metric. Distance between cluster centroids. When ## two clusters are joined together, the new centroid is the midpoint ## between the joined centroids. ## ## @item "ward" ## Ward's sum of squared deviations about the group mean (ESS). ## Also known as minimum variance or inner squared distance. ## Assumes Euclidean metric. How much the moment of inertia of the ## merged cluster exceeds the sum of those of the individual clusters. ## @end table ## ## @strong{Reference} ## Ward, J. H. Hierarchical Grouping to Optimize an Objective Function ## J. Am. Statist. Assoc. 1963, 58, 236-244, ## @url{http://iv.slis.indiana.edu/sw/data/ward.pdf}. ## ## @seealso{pdist,squareform} ## @end deftypefn function dgram = linkage (d, method = 'single', distarg, savememory) ## check the input if (nargin == 4) && (strcmpi (savememory, 'savememory')) warning ("Octave:linkage_savemem", ... 'linkage: option ''savememory'' not implemented'); elseif (nargin < 1) || (nargin > 3) print_usage (); endif if (isempty (d)) error ("linkage: d cannot be empty"); endif methods = struct ... ('name', { 'single'; 'complete'; 'average'; 'weighted'; 'centroid'; 'median'; 'ward' }, 'distfunc', {(@(x) min(x)) # single (@(x) max(x)) # complete (@(x,i,j,w) sum(diag(w([i,j]))*x)/sum(w([i,j]))) # average (@(x) mean(x)) # weighted (@massdist) # centroid (@(x,i) massdist(x,i)) # median (@inertialdist) # ward }); mask = strcmp (lower (method), {methods.name}); if (! any (mask)) error ("linkage: %s: unknown method", method); endif dist = {methods.distfunc}{mask}; if (nargin >= 3 && ! isvector (d)) if (ischar (distarg)) d = pdist (d, distarg); elseif (iscell (distarg)) d = pdist (d, distarg{:}); else print_usage (); endif elseif (nargin < 3) if (! isvector (d)) d = pdist (d); endif else print_usage (); endif d = squareform (d, 'tomatrix'); # dissimilarity NxN matrix n = rows (d); # the number of observations diagidx = sub2ind ([n,n], 1:n, 1:n); # indices of diagonal elements d(diagidx) = Inf; # consider a cluster as far from itself ## For equal-distance nodes, the order in which clusters are ## merged is arbitrary. Rotating the initial matrix produces an ## ordering similar to Matlab's. cname = n:-1:1; # cluster names in d d = rot90 (d, 2); # exchange low and high cluster numbers weight = ones (1, n); # cluster weights dgram = zeros (n-1, 3); # clusters from n+1 to 2*n-1 sz = n; # current matrix size (avoid size calls) mcase = find (mask); # pre-compute method case for cluster = n+1 : 2*n-1 ## Find the two nearest clusters [~, midx] = min (d(:)); ## Compute row/column indices directly (faster than ind2sub) c = ceil (midx / sz); r = midx - (c - 1) * sz; ## Here is the new cluster dgram(cluster-n, :) = [cname(r) cname(c) d(r, c)]; ## Put it in place of the first one and remove the second cname(r) = cluster; cname(c) = []; ## Compute the new distances. ## (Octave-7+ needs switch stmt to avoid 'called with too many inputs' err.) d_rc = d([r, c], :); # cache row slice switch mcase case {1, 2, 4} # 1 arg newd = dist(d_rc); case {3, 5, 7} # 4 args newd = dist(d_rc, r, c, weight); case 6 # 2 args newd = dist(d_rc, r); otherwise endswitch newd(r) = Inf; # Take care of the diagonal element ## Put distances in place of the first ones, remove the second ones d(r,:) = newd; d(:,r) = newd'; d(c,:) = []; d(:,c) = []; sz -= 1; # update cached size ## The new weight is the sum of the components' weights weight(r) += weight(c); weight(c) = []; endfor ## Sort the cluster numbers, as Matlab does dgram(:,1:2) = sort (dgram(:,1:2), 2); ## Check that distances are monotonically increasing if (any (diff (dgram(:,3)) < 0)) warning ("Octave:clustering", "linkage: cluster distances do not monotonically increase\n\ you should probably use a method different from \"%s\"", method); endif endfunction ## Take two row vectors, which are the Euclidean distances of clusters I ## and J from the others. Column I of second row contains the distance ## between clusters I and J. The centre of gravity of the new cluster ## is on the segment joining the old ones. W are the weights of all ## clusters. Use the law of cosines to find the distances of the new ## cluster from all the others. function y = massdist (x, i, j, w) x .^= 2; # Squared Euclidean distances if (nargin == 2) # Median distance qi = 0.5; # Equal weights ("weighted") else # Centroid distance qi = 1 / (1 + w(j) / w(i)); # Proportional weights ("unweighted") endif y = sqrt (qi * x(1, :) + (1 - qi) * (x(2, :) - qi * x(2, i))); endfunction ## Take two row vectors, which are the inertial distances of clusters I ## and J from the others. Column I of second row contains the inertial ## distance between clusters I and J. The centre of gravity of the new ## cluster K is on the segment joining I and J. W are the weights of ## all clusters. Convert inertial to Euclidean distances, then use the ## law of cosines to find the Euclidean distances of K from all the ## other clusters, convert them back to inertial distances and return ## them. function y = inertialdist (x, i, j, w) wi = w(i); # The cluster wj = w(j); # weights. s = [wi + w; # Sum of weights for wj + w]; # all cluster pairs. p = [wi * w; # Product of weights for wj * w]; # all cluster pairs. x = x.^2 .* s ./ p; # Convert inertial dist. to squared Eucl. sij = wi + wj; # Sum of weights of I and J qi = wi / sij; # Normalise the weight of I ## Squared Euclidean distances between all clusters and new cluster K x = qi * x(1, :) + (1 - qi) * (x(2, :) - qi * x(2, i)); y = sqrt (x * sij .* w ./ (sij + w)); # convert Eucl. dist. to inertial endfunction %!shared x, t %! x = reshape (mod (magic (6),5), [], 3); %! t = 1e-6; ## Z(:,3) holds the merge heights and is the meaningful output; Z(:,1:2) are ## cluster labels whose values depend on how ties in the distance matrix are ## broken. This fixture has only 20 distinct distances among its 66 pairs, so ## the labels are implementation-defined and are deliberately not asserted. ## Do not reintroduce cond (Z) here: it mixes the labels into the metric, is ## blind to the merge order (a row permutation cannot move a matrix's singular ## values) and ranges over 32.6 to 95.5 on this fixture under nothing but a ## different tie-break. The heights below are exact against MATLAB R2024a. %!test %! Z = linkage (pdist (x)); %! assert_equal (Z(:,3), [1; 1; 1; 1.414214; 1.414214; 1.414214; ... %! 2.236068; 2.236068; 2.236068; 2.236068; 3], t); %!test %! Z = linkage (pdist (x), 'complete'); %! assert_equal (Z(:,3), [1; 1; 1.414214; 1.414214; 1.414214; 2.236068; ... %! 2.236068; 3.162278; 3.741657; 4.690416; 6], t); %!test %! Z = linkage (pdist (x), 'average'); %! assert_equal (Z(:,3), [1; 1; 1.207107; 1.414214; 1.414214; 1.962117; ... %! 2.236068; 2.948887; 3.081139; 3.515667; ... %! 4.177650], t); ## 'weighted' (McQuitty) feeds the Lance-Williams recursion with whichever pair ## merged first, so its later heights follow the tie-break. Three pairs of this ## fixture lie at distance exactly 1 and observation 10 belongs to two of them, ## so at most two can merge as singletons and the choice is forced to be ## arbitrary: we take {9,10} then {1,7}, MATLAB takes {1,7} then {4,10}, and the ## heights part company from row 8. Both replay correctly through the WPGMA ## recursion. Only the first seven merges are tie-break invariant, so only they ## are asserted by value; a tie-free fixture would restore the rest. %!test %! Z = linkage (pdist (x), 'weighted'); %! assert_equal (Z(1:7,3), [1; 1; 1.207107; 1.414214; 1.414214; ... %! 2.030604; 2.236068], t); %! assert_equal (all (diff (Z(:,3)) >= -eps), true); %! lastwarn (); # Clear last warning before the test %!warning linkage (pdist (x), 'centroid'); ## Regression values only -- 'centroid' and 'median' were not measured against ## MATLAB, so these pin our own behaviour rather than parity. Both build a ## non-monotonic tree, which is what the warning above reports. %!test %! warning off Octave:clustering %! Z = linkage (pdist (x), 'centroid'); %! assert_equal (Z(:,3), [1; 1; 1.118034; 1.414214; 1.224745; 1.885618; ... %! 2.236068; 2.708013; 2.980378; 3.041381; ... %! 3.529418], t); %! warning on Octave:clustering %!warning linkage (pdist (x), 'median'); %!test %! warning off Octave:clustering %! Z = linkage (pdist (x), 'median'); %! assert_equal (Z(:,3), [1; 1; 1.118034; 1.414214; 1.224745; 1.952562; ... %! 2.236068; 2.452677; 3.041381; 3.057394; ... %! 3.163667], t); %! warning on Octave:clustering %!test %! Z = linkage (pdist (x), 'ward'); %! assert_equal (Z(:,3), [1; 1; 1.290994; 1.414214; 1.414214; 2.236068; ... %! 2.309401; 3.511885; 4.690416; 5.228129; ... %! 7.713624], t); ## All four ward call forms must return the same tree, labels included. MATLAB ## does not manage this -- its raw-data and distance-vector paths break the ties ## differently and return different trees for this fixture, at identical merge ## heights. Ours agree exactly, so assert the whole of Z. %!test %! Z = linkage (pdist (x), 'ward'); %! assert_equal (linkage (x, 'ward', 'euclidean'), Z); %! assert_equal (linkage (x, 'ward', {'euclidean'}), Z); %! assert_equal (linkage (x, 'ward', {'minkowski', 2}), Z); ## Structural validity of Z, which no cond value could ever check. The merge ## bookkeeping is shared by every method, so one method exercises it: each of ## the 2*n-2 labels is consumed exactly once and every row is sorted. %!test %! Z = linkage (pdist (x)); %! assert_equal (sort ([Z(:,1); Z(:,2)])', 1:22); %! assert_equal (all (Z(:,1) < Z(:,2)), true); ## Additional tests for method/metric combinations %!test %! y = [1 2; 3 5; 4 6; 7 8; 9 11]; %! L = linkage (y, 'single', 'cityblock'); %! assert_equal (size (L), [4, 3]); %! assert_equal (all (L(:,3) >= 0), true); # distances non-negative %!test %! y = [1 2; 3 5; 4 6; 7 8; 9 11]; %! L = linkage (y, 'complete', 'cityblock'); %! assert_equal (size (L), [4, 3]); %! assert_equal (all (diff (L(:,3)) >= -eps), true); # monotonically increasing %!test %! y = [1 2; 3 5; 4 6; 7 8; 9 11]; %! L = linkage (y, 'average', 'chebychev'); %! assert_equal (size (L), [4, 3]); %! assert_equal (all (L(:,3) >= 0), true); %!test %! y = [1 2 3; 4 5 6; 7 8 9; 10 11 12]; %! L = linkage (y, 'weighted', {'minkowski', 3}); %! assert_equal (size (L), [3, 3]); %! assert_equal (all (L(:,3) >= 0), true); %!test %! y = [1 0 1; 0 1 1; 1 1 0; 0 0 1]; %! L = linkage (y, 'single', 'cosine'); %! assert_equal (size (L), [3, 3]); %! assert_equal (all (L(:,3) >= 0), true); %!test %! y = [1 2 3; 2 3 4; 5 6 7]; %! L = linkage (y, 'complete', 'correlation'); %! assert_equal (size (L), [2, 3]); %! assert_equal (all (L(:,3) >= 0), true); ## Test with 2 observations (minimal case) %!test %! y = [1 2; 3 4]; %! L = linkage (y, 'single', 'euclidean'); %! assert_equal (size (L), [1, 3]); %! assert_equal (L(1,1:2), [1, 2]); ## Test output structure: cluster indices are valid %!test %! y = rand (6, 3); %! L = linkage (y, 'average', 'euclidean'); %! assert_equal (all (L(:,1) >= 1 & L(:,1) <= 11), true); # valid cluster refs %! assert_equal (all (L(:,2) >= 1 & L(:,2) <= 11), true); %! assert_equal (all (L(:,1) < L(:,2)), true); # sorted within rows statistics-release-1.9.2/inst/Clustering/optimalleaforder.m000066400000000000000000000307021524624707500241540ustar00rootroot00000000000000## Copyright (C) 2021 Stefano Guidoni ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{leafOrder} =} optimalleaforder (@var{tree}, @var{D}) ## @deftypefnx {statistics} {@var{leafOrder} =} optimalleaforder (@dots{}, @var{Name}, @var{Value}) ## ## Compute the optimal leaf ordering of a hierarchical binary cluster tree. ## ## The optimal leaf ordering of a tree is the ordering which minimizes the sum ## of the distances between each leaf and its adjacent leaves, without altering ## the structure of the tree, that is without redefining the clusters of the ## tree. ## ## Required inputs: ## @itemize ## @item ## @var{tree}: a hierarchical cluster tree @var{tree} generated by the ## @code{linkage} function. ## ## @item ## @var{D}: a matrix of distances as computed by @code{pdist}. ## @end itemize ## ## Optional inputs can be the following property/value pairs: ## @itemize ## @item ## property 'Criteria' at the moment can only have the value 'adjacent', ## for minimizing the distances between leaves. ## ## @item ## property 'Transformation' can have one of the values 'linear', 'inverse' ## or a handle to a custom function which computes @var{S} the similarity ## matrix. ## @end itemize ## ## optimalleaforder's output @var{leafOrder} is the optimal leaf ordering. ## ## @strong{Reference} ## Bar-Joseph, Z., Gifford, D.K., and Jaakkola, T.S. Fast optimal leaf ordering ## for hierarchical clustering. Bioinformatics vol. 17 suppl. 1, 2001. ## ## @seealso{dendrogram,linkage,pdist} ## ## @end deftypefn function leafOrder = optimalleaforder ( varargin ) ## check the input if ( nargin < 2 ) print_usage (); endif tree = varargin{1}; D = varargin{2}; criterion = 'adjacent'; # default and only value at the moment transformation = 'linear'; if ((columns (tree) != 3) || (! isnumeric (tree)) || ... (! (max (tree(end, 1:2)) == rows (tree) * 2))) error (strcat ("optimalleaforder: tree must be a matrix as generated by the", " linkage function")); endif ## Read the paired arguments. Only the names have to be character vectors: ## 'Transformation' takes a function handle as well, and lower-casing every ## argument rejected it before it could be used. names = 3:2:(nargin - 1); if (! all (cellfun ('ischar', varargin(names)))) error ("optimalleaforder: character inputs expected for arguments 3 and up"); endif varargin(names) = lower (varargin(names)); pair_index = 3; while (pair_index <= (nargin - 1)) switch (varargin{pair_index}) case 'criteria' criterion = varargin{pair_index + 1}; if (! ischar (criterion)) error ("optimalleaforder: character inputs expected for CRITERIA"); endif criterion = lower (criterion); if (strcmp (criterion, 'group')) ## MATLAB compatibility: ## the 'group' criterion is not implemented error ("optimalleaforder: unavailable criterion 'group'"); elseif (! strcmp (criterion, 'adjacent')) error ("optimalleaforder: invalid criterion %s", criterion); endif case 'transformation' transformation = varargin{pair_index + 1}; if (ischar (transformation)) transformation = lower (transformation); elseif (! is_function_handle (transformation)) error (strcat ("optimalleaforder: TRANSFORMATION must be a", ... " character vector or a function handle")); endif otherwise error ("optimalleaforder: unknown property %s", varargin{pair_index}); endswitch pair_index += 2; endwhile ## D can be either a vector or a matrix, ## but it is easier to work with a matrix if (isvector (D)) D = squareform (D); endif n = rows (D); m = rows (tree); if (n != (m + 1)) error (strcat ("optimalleaforder: D must be a matrix or vector generated by", " the pdist function")); endif ## the similarity matrix, basically an inverted distance matrix S = zeros (n); if (strcmpi (transformation, 'linear')) ## linear similarity maxD = max (max (D)); S = maxD - D; elseif (strcmpi (transformation, 'inverse')) ## similarity as inverted distance S = 1 ./ D; elseif (is_function_handle (transformation)) ## custom similarity S = feval (transformation, D); else error ("optimalleaforder: invalid transformation %s", transformation); endif ## main body ## With two leaves the tree has a single node and the only orderings are the ## one below and its reverse, both optimal. The tables below index the root ## where a leaf is expected and run off the end of M. if (n <= 2) leafOrder = 1:n; return; endif ## for each node v we compute the maximum similarity of the subtree M(w,u,v), ## where the leftmost leaf is w and the rightmost is u; remember that ## M(w,u,v) = M(u,w,v) M = zeros (n, n, n + m); ## O is a utility matrix: for each node of the tree we store the left and ## right leaves of the optimal subtree O = [1:( n + m ); 1:( n + m ); (zeros (1, (n + m)))]'; ## compute M for every node v for iter = 1 : m v = iter + n; # current node l = optimalleaforder_getLeafList (tree(iter, 1)); # the left subtree r = optimalleaforder_getLeafList (tree(iter, 2)); # the right subtree if (tree(iter,1) > n) l_l = optimalleaforder_getLeafList (tree(tree(iter, 1) - n, 1)); l_r = optimalleaforder_getLeafList (tree(tree(iter, 1) - n, 2)); else l_l = l_r = l; endif if (tree(iter,2) > n) r_l = optimalleaforder_getLeafList (tree(tree(iter, 2) - n, 1)); r_r = optimalleaforder_getLeafList (tree(tree(iter, 2) - n, 2)); else r_l = r_r = r; endif ## let's find the maximum value of M(w,u,v) when: w is a leaf of the left ## subtree of v and u is a leaf of the right subtree of v for i = 1 : length (l) if (isempty (find (l(i) == l_l))) x = l_l; else x = l_r; endif for j = 1 : length (r) if (isempty (find (r(j) == r_l))) y = r_l; else y = r_r; endif ## max(M(w,u,v)) = max(M(w,k,v_l)) + max(M(h,u,v_r)) + S(k,h) ## where: v_l is the left child of v and v_r the right child of v M_tmp = repmat (M(l(i), x(:), tree(iter, 1)), length (y), 1) + ... repmat (M(y(:), r(j), tree(iter, 2)), 1, length (x)) + ... S(y(:), x(:)); M_max = max (max (M_tmp)); # this is M(l(i), r(j), v) [h, k] = find (M_tmp == M_max); M(l(i), r(j), v) = M_max; M(r(j), l(i), v) = M(l(i), r(j), v); if (M_max > O(v,3)) O(v, 1) = l(i); # this is w O(v, 2) = r(j); # this is u O(v, 3) = M_max; # this is M(w, u, v) endif endfor endfor endfor ## reordering: ## we found the M(w,u,v) corresponding to the optimal leaf order, now we can ## compute the optimal leaf order given our M(w,u,v) ## the return value leafOrder = zeros ( 1, n ); leafOrder(1) = O(end, 1); leafOrder(n) = O(end, 2); ## the inverse operation, only easier, to get the leaf order: now we know the ## leftmost and rightmost leaves of the best subtree, we may have to flip it ## though for iter = m : -1 : 1 v = iter + n; extremes = O(v, [1, 2]); l_node = tree(iter, 1); r_node = tree(iter, 2); l = optimalleaforder_getLeafList (l_node); r = optimalleaforder_getLeafList (r_node); if (l_node > n) l_l = optimalleaforder_getLeafList (tree(l_node - n, 1)); l_r = optimalleaforder_getLeafList (tree(l_node - n, 2)); else l_l = l_r = l; endif if (r_node > n) r_l = optimalleaforder_getLeafList (tree(r_node - n, 1)); r_r = optimalleaforder_getLeafList (tree(r_node - n, 2)); else r_l = r_r = r; endif ## this means that we need to flip the subtree if (isempty (find (extremes(1) == l))) l_tmp = l; l_l_tmp = l_l; l_r_tmp = l_r; l = r; l_l = r_l; l_r = r_r; r = l_tmp; r_l = l_l_tmp; r_r = l_r_tmp; node_tmp = l_node; l_node = r_node; r_node = node_tmp; endif if (isempty (find (extremes(1) == l_l))) x = l_l; else x = l_r; endif if (isempty (find (extremes(2) == r_l))) y = r_l; else y = r_r; endif M_tmp = repmat (M(extremes(1), x(:), l_node), length (y), 1) + ... repmat (M(y(:), extremes(2), r_node), 1, length (x)) + ... S(y(:), x(:)); M_max = max (max (M_tmp)); [h, k] = find (M_tmp == M_max); O(l_node, 1) = extremes(1); O(l_node, 2) = x(k); O(r_node, 1) = y(h); O(r_node, 2) = extremes(2); p_1 = find (leafOrder == extremes(1)); p_2 = find (leafOrder == extremes(2)); leafOrder(p_1 + (length (l)) - 1) = x(k); leafOrder(p_1 + (length (l))) = y(h); endfor ## function: optimalleaforder_getLeafList ## get the list of leaves under a given node function vector = optimalleaforder_getLeafList (nodes_to_visit) vector = []; while (! isempty (nodes_to_visit)) currentnode = nodes_to_visit(1); nodes_to_visit(1) = []; if (currentnode > n) node = currentnode - n; nodes_to_visit = [tree(node, [2 1]) nodes_to_visit]; endif if (currentnode <= n) vector = [vector currentnode]; endif endwhile endfunction endfunction %!demo %! rng (42); %! X = randn (10, 2); %! D = pdist (X); %! tree = linkage (D, 'average'); %! optimalleaforder (tree, D, 'Transformation', 'linear') ## Test input validation %!error optimalleaforder () %!error optimalleaforder (1) %!error optimalleaforder (ones (2, 2), 1) %!error optimalleaforder ([1 2 3], [1 2; 3 4], 'criteria', 5) %!error optimalleaforder ([1 2 1], [1 2 3]) %!error optimalleaforder ([1 2 1], 1, 'xxx', 'xxx') %!error optimalleaforder ([1 2 1], 1, 'Transformation', 'xxx') ## A leaf ordering and its reverse are equally optimal, so the tests below ## compare against MATLAB R2024a up to reversal. %!shared X, D, Z %! X = [0, 0; 0.2, 0.1; 3, 3; 3.1, 3.4; 6, 0; 6.2, 0.3; 9, 9; 9.1, 9.2; ... %! 1, 5; 5, 1]; %! D = pdist (X); %! Z = linkage (D, "average"); %!test # a custom transformation is a documented input and now reaches the fit %! o = optimalleaforder (Z, D, "Transformation", @(x) 1 ./ (1 + x)); %! assert_equal (sort (o), 1:10); %! assert_equal (o(end:-1:1), [1, 2, 9, 4, 3, 10, 6, 5, 7, 8]); %!test # the named transformations are unchanged %! o = optimalleaforder (Z, D, "Transformation", "linear"); %! assert_equal (o(end:-1:1), [1, 2, 9, 4, 3, 10, 5, 6, 7, 8]); %! o = optimalleaforder (Z, D, "Transformation", "inverse"); %! assert_equal (o(end:-1:1), [1, 2, 9, 4, 3, 10, 6, 5, 7, 8]); %!test # two leaves have a single ordering, where the tables used to run off %! ## the end of the similarity array %! for n = 2:5 %! o = optimalleaforder (linkage (pdist (X(1:n,:))), pdist (X(1:n,:))); %! assert_equal (sort (o), 1:n); %! endfor %! assert_equal (optimalleaforder (linkage (pdist (X(1:2,:))), ... %! pdist (X(1:2,:))), [1, 2]); %!test # the ordering is optimal: no reordering that keeps the tree beats it %! o = optimalleaforder (Z, D); %! Dsq = squareform (D); %! obj = @(p) sum (Dsq(sub2ind (size (Dsq), p(1:end-1), p(2:end)))); %! assert_equal (obj (o), obj (o(end:-1:1)), 1e-12); %! assert_equal (obj (o) <= obj (1:10), true); %!error ... %! optimalleaforder (Z, D, @(x) x, "linear") %!error ... %! optimalleaforder ([1 2 1], 1, 'Criteria', 5) %!error ... %! optimalleaforder ([1 2 1], 1, 'Transformation', 5) statistics-release-1.9.2/inst/Clustering/spectralcluster.m000066400000000000000000000417731524624707500240540ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{idx} =} spectralcluster (@var{X}, @var{k}) ## @deftypefnx {statistics} {@var{idx} =} spectralcluster (@var{S}, @var{k}, @qcode{'Distance'}, @qcode{'precomputed'}) ## @deftypefnx {statistics} {[@var{idx}, @var{V}] =} spectralcluster (@dots{}) ## @deftypefnx {statistics} {[@var{idx}, @var{V}, @var{D}] =} spectralcluster (@dots{}) ## @deftypefnx {statistics} {[@dots{}] =} spectralcluster (@dots{}, @var{name}, @var{value}) ## ## Partition observations into @var{k} clusters using spectral clustering. ## ## @code{@var{idx} = spectralcluster (@var{X}, @var{k})} partitions the ## @math{N*P} numeric matrix @var{X} into @var{k} clusters and returns the ## @math{N*1} vector @var{idx} of cluster indices. Rows of @var{X} correspond ## to observations and columns to features. Spectral clustering builds a ## similarity graph over the observations, embeds them with the eigenvectors of ## the graph Laplacian, and clusters that embedding, which lets it recover ## clusters that are not linearly separable in the original space. ## ## @code{[@var{idx}, @var{V}, @var{D}] = spectralcluster (@dots{})} also returns ## the @math{N*k} matrix @var{V} whose columns are the eigenvectors associated ## with the @var{k} smallest eigenvalues of the Laplacian, and the @math{k*1} ## vector @var{D} of those eigenvalues. The signs of the eigenvectors, and the ## basis within a repeated eigenvalue, are arbitrary. ## ## Additional parameters can be specified by @qcode{Name-Value} pair arguments. ## ## @multitable @columnfractions 0.22 0.76 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Distance'} @tab the distance metric used to build the ## similarity graph, one of @qcode{'euclidean'} (default), @qcode{'seuclidean'}, ## @qcode{'mahalanobis'}, @qcode{'cityblock'}, @qcode{'minkowski'}, ## @qcode{'chebychev'}, @qcode{'cosine'}, @qcode{'correlation'}, ## @qcode{'hamming'}, @qcode{'jaccard'}, @qcode{'spearman'}, or a function ## handle accepted by @code{pdist2}, or the string @qcode{'precomputed'} to ## interpret the first input as an @math{N*N} similarity matrix. ## ## @item @qcode{'SimilarityGraph'} @tab @qcode{'knn'} (default) to connect each ## observation to its nearest neighbors, or @qcode{'epsilon'} to connect ## observations that are within a fixed radius. ## ## @item @qcode{'NumNeighbors'} @tab the number of nearest neighbors for the ## @qcode{'knn'} graph, a positive integer. The default is ## @code{ceil (log (@var{N}))}. ## ## @item @qcode{'KNNGraphType'} @tab @qcode{'complete'} (default) to connect ## @math{i} and @math{j} when either is a nearest neighbor of the other, or ## @qcode{'mutual'} to connect them only when each is a nearest neighbor of the ## other. ## ## @item @qcode{'Radius'} @tab the radius for the @qcode{'epsilon'} graph, a ## nonnegative scalar. Required when @qcode{'SimilarityGraph'} is ## @qcode{'epsilon'}. ## ## @item @qcode{'KernelScale'} @tab the positive scale factor @var{sigma} in ## the Gaussian similarity kernel @code{exp (-(dist / sigma)^2)} applied to the ## graph edges. The default is 1. ## ## @item @qcode{'LaplacianNormalization'} @tab @qcode{'randomwalk'} (default), ## @qcode{'symmetric'}, or @qcode{'none'}, selecting how the graph Laplacian is ## normalized before the eigendecomposition. ## ## @item @qcode{'ClusterMethod'} @tab @qcode{'kmeans'} (default) or ## @qcode{'kmedoids'} to cluster the eigenvector embedding. ## ## @item @qcode{'P'} @tab the Minkowski exponent (default 2), used only with the ## @qcode{'minkowski'} distance. ## ## @item @qcode{'Cov'} @tab the covariance matrix used only with the ## @qcode{'mahalanobis'} distance. ## ## @item @qcode{'Scale'} @tab the scaling vector used only with the ## @qcode{'seuclidean'} distance. ## @end multitable ## ## @seealso{kmeans, kmedoids, dbscan, linkage, pdist2} ## @end deftypefn function [idx, V, D] = spectralcluster (X, k, varargin) ## Check number of input arguments if (nargin < 2) error ("spectralcluster: too few input arguments."); endif ## Validate X if (! isnumeric (X) || ! isreal (X) || ndims (X) != 2 || isempty (X)) error ("spectralcluster: X must be a nonempty real numeric matrix."); endif N = rows (X); ## Validate k if (! isscalar (k) || ! isnumeric (k) || ! isreal (k) || k < 1 || fix (k) != k) error ("spectralcluster: K must be a positive integer scalar."); endif if (k > N) error ("spectralcluster: K cannot exceed the number of observations in X."); endif ## Defaults distance = "euclidean"; simgraph = "knn"; numneighbors = []; knntype = "complete"; radius = []; kernelscale = 1; laplnorm = "randomwalk"; clustermethod = "kmeans"; Pexp = 2; Cov = []; Scl = []; ## Parse Name-Value pairs if (mod (numel (varargin), 2) != 0) error ("spectralcluster: each NAME must be followed by a VALUE."); endif while (numel (varargin) > 0) name = varargin{1}; val = varargin{2}; if (! ischar (name)) error ("spectralcluster: optional argument names must be strings."); endif switch (tolower (name)) case "distance" distance = val; case "similaritygraph" simgraph = tolower (val); case "numneighbors" numneighbors = val; case "knngraphtype" knntype = tolower (val); case "radius" radius = val; case "kernelscale" kernelscale = val; case "laplaciannormalization" laplnorm = tolower (val); case "clustermethod" clustermethod = tolower (val); case "p" Pexp = val; case "cov" Cov = val; case "scale" Scl = val; otherwise error ("spectralcluster: unknown parameter name '%s'.", name); endswitch varargin(1:2) = []; endwhile ## Validate option choices if (! any (strcmp (simgraph, {"knn", "epsilon"}))) error ("spectralcluster: SIMILARITYGRAPH must be 'knn' or 'epsilon'."); endif if (! any (strcmp (knntype, {"complete", "mutual"}))) error ("spectralcluster: KNNGRAPHTYPE must be 'complete' or 'mutual'."); endif if (! any (strcmp (laplnorm, {"randomwalk", "symmetric", "none"}))) error (strcat ("spectralcluster: LAPLACIANNORMALIZATION must be", ... " 'randomwalk', 'symmetric', or 'none'.")); endif if (! any (strcmp (clustermethod, {"kmeans", "kmedoids"}))) error ("spectralcluster: CLUSTERMETHOD must be 'kmeans' or 'kmedoids'."); endif if (! isscalar (kernelscale) || ! isnumeric (kernelscale) || kernelscale <= 0) error ("spectralcluster: KERNELSCALE must be a positive scalar."); endif ## Build the N-by-N similarity matrix S. if (ischar (distance) && strcmpi (distance, "precomputed")) if (rows (X) != columns (X)) error (strcat ("spectralcluster: X must be a square similarity", ... " matrix for the 'precomputed' distance.")); endif S = X; else ## Pairwise distances in the requested metric. metrics = {"euclidean", "seuclidean", "mahalanobis", "cityblock", ... "minkowski", "chebychev", "cosine", "correlation", ... "hamming", "jaccard", "spearman"}; if (ischar (distance)) dname = tolower (distance); if (! any (strcmp (dname, metrics))) error ("spectralcluster: unsupported distance metric '%s'.", distance); endif switch (dname) case "minkowski" Dmat = pdist2 (X, X, dname, Pexp); case "mahalanobis" if (isempty (Cov)) Dmat = pdist2 (X, X, dname); else Dmat = pdist2 (X, X, dname, Cov); endif case "seuclidean" if (isempty (Scl)) Dmat = pdist2 (X, X, dname); else Dmat = pdist2 (X, X, dname, Scl); endif otherwise Dmat = pdist2 (X, X, dname); endswitch elseif (is_function_handle (distance)) Dmat = pdist2 (X, X, distance); else error (strcat ("spectralcluster: DISTANCE must be a metric name or", ... " a function handle.")); endif ## Adjacency of the similarity graph. A = false (N); if (strcmp (simgraph, "knn")) if (isempty (numneighbors)) numneighbors = max (1, ceil (log (N))); endif if (! isscalar (numneighbors) || ! isnumeric (numneighbors) || numneighbors < 1 || fix (numneighbors) != numneighbors) error (strcat ("spectralcluster: NUMNEIGHBORS must be a positive", ... " integer scalar.")); endif nn = min (numneighbors, N - 1); for i = 1:N d = Dmat(i,:); d(i) = Inf; [~, ord] = sort (d); A(i, ord(1:nn)) = true; endfor if (strcmp (knntype, "complete")) A = A | A'; else A = A & A'; endif else if (isempty (radius)) error (strcat ("spectralcluster: RADIUS is required for the", ... " 'epsilon' similarity graph.")); endif if (! isscalar (radius) || ! isnumeric (radius) || radius < 0) error ("spectralcluster: RADIUS must be a nonnegative scalar."); endif A = Dmat <= radius; A(1:(N + 1):end) = false; # drop self-edges on the diagonal endif ## Gaussian kernel on the graph edges; zero elsewhere. S = zeros (N); S(A) = exp (-(Dmat(A) / kernelscale) .^ 2); endif ## Graph Laplacian and its k smallest eigenpairs. deg = sum (S, 2); Dg = diag (deg); if (! strcmp (laplnorm, "none") && any (deg <= 0)) error (strcat ("spectralcluster: the similarity graph has isolated", ... " points; increase NumNeighbors or Radius.")); endif L = Dg - S; switch (laplnorm) case "none" [Vec, Val] = eig (L); case "randomwalk" [Vec, Val] = eig (L, Dg); case "symmetric" Dh = diag (1 ./ sqrt (deg)); Ls = Dh * L * Dh; Ls = (Ls + Ls') / 2; # enforce exact symmetry for real eig [Vec, Val] = eig (Ls); endswitch ev = real (diag (Val)); [ev, order] = sort (ev); D = ev(1:k); V = real (Vec(:, order(1:k))); if (strcmp (laplnorm, "symmetric")) rn = sqrt (sum (V .^ 2, 2)); # row-normalize the embedding rn(rn == 0) = 1; V = V ./ rn; endif ## Cluster the eigenvector embedding. if (strcmp (clustermethod, "kmeans")) idx = kmeans (V, k, "Replicates", 5, "EmptyAction", "singleton"); else idx = kmedoids (V, k, "Replicates", 5); endif endfunction %!demo %! ## Two concentric rings are not separable by kmeans but are by spectral %! ## clustering. %! rng (42); %! t = linspace (0, 2*pi, 100)'; %! Xin = [cos(t), sin(t)] + randn (100, 2) * 0.05; %! Xout = 4 * [cos(t), sin(t)] + randn (100, 2) * 0.05; %! X = [Xin; Xout]; %! idx = spectralcluster (X, 2, "NumNeighbors", 10); %! gscatter (X(:,1), X(:,2), idx); %! axis equal; %! title ("spectralcluster: two concentric rings"); ## Exact anchor: precomputed similarity, random-walk Laplacian (MATLAB R2024a) %!test %! S = [0 1 1 0 0 0; 1 0 1 0 0 0; 1 1 0 0.1 0 0; 0 0 0.1 0 1 1; ... %! 0 0 0 1 0 1; 0 0 0 1 1 0]; %! [idx, V, D] = spectralcluster (S, 2, "Distance", "precomputed"); %! assert_equal (abs (D(1)) < 1e-9, true); %! assert_equal (D(2), 0.0314065796348158, 1e-12); %! assert_equal (size (V), [6 2]); %! assert_equal (idx(1) == idx(2) && idx(2) == idx(3), true); %! assert_equal (idx(4) == idx(5) && idx(5) == idx(6), true); %! assert_equal (idx(1) != idx(4), true); ## Symmetric normalization has the same spectrum as random-walk %!test %! S = [0 1 1 0 0 0; 1 0 1 0 0 0; 1 1 0 0.1 0 0; 0 0 0.1 0 1 1; ... %! 0 0 0 1 0 1; 0 0 0 1 1 0]; %! [~, ~, D] = spectralcluster (S, 2, "Distance", "precomputed", ... %! "LaplacianNormalization", "symmetric"); %! assert_equal (D(2), 0.0314065796348156, 1e-12); ## Unnormalized Laplacian ('none') spectrum %!test %! S = [0 1 1 0 0 0; 1 0 1 0 0 0; 1 1 0 0.1 0 0; 0 0 0.1 0 1 1; ... %! 0 0 0 1 0 1; 0 0 0 1 1 0]; %! [~, ~, D] = spectralcluster (S, 2, "Distance", "precomputed", ... %! "LaplacianNormalization", "none"); %! assert_equal (D(2), 0.0637708504262773, 1e-12); ## Precomputed path graph splits at the weak middle edge; eigenvalue is 2/7 %!test %! Sc = [0 0.5 0 0; 0.5 0 0.2 0; 0 0.2 0 0.5; 0 0 0.5 0]; %! [idx, ~, D] = spectralcluster (Sc, 2, "Distance", "precomputed"); %! assert_equal (D(2), 2/7, 1e-12); %! assert_equal (idx(1) == idx(2), true); %! assert_equal (idx(3) == idx(4), true); %! assert_equal (idx(1) != idx(3), true); ## Full knn pipeline (graph + Gaussian kernel + Laplacian) matches MATLAB %!test %! X = [0 0; 0.1 0; 0 0.1; 5 0; 5.1 0; 5 0.1]; %! [idx, ~, D] = spectralcluster (X, 2, "NumNeighbors", 3, "KernelScale", 2); %! assert_equal (D(2), 0.00358001836237202, 1e-11); %! assert_equal (idx(1) == idx(2) && idx(2) == idx(3), true); %! assert_equal (idx(4) == idx(5) && idx(5) == idx(6), true); %! assert_equal (idx(1) != idx(4), true); ## Default NumNeighbors is ceil (log (N)): for N = 8 that is 3 %!test %! X = (1:8)'; %! [~, ~, Ddef] = spectralcluster (X, 2); %! [~, ~, D3] = spectralcluster (X, 2, "NumNeighbors", 3); %! assert_equal (Ddef(2), D3(2), 1e-12); %! assert_equal (Ddef(2), 0.113482181378817, 1e-11); ## Default path clusters two well-separated blobs %!test %! X = [0 0; 0.2 0; 0 0.2; 0.2 0.2; 10 10; 10.2 10; 10 10.2; 10.2 10.2]; %! idx = spectralcluster (X, 2); %! assert_equal (numel (unique (idx(1:4))) == 1, true); %! assert_equal (numel (unique (idx(5:8))) == 1, true); %! assert_equal (idx(1) != idx(5), true); ## kmedoids embedding-clustering and the epsilon graph both work %!test %! X = [0 0; 0.2 0; 0 0.2; 0.2 0.2; 10 10; 10.2 10; 10 10.2; 10.2 10.2]; %! idx = spectralcluster (X, 2, "ClusterMethod", "kmedoids"); %! assert_equal (numel (unique (idx(1:4))) == 1, true); %! assert_equal (numel (unique (idx(5:8))) == 1, true); %!test %! X = [0 0; 0.2 0; 0 0.2; 0.2 0.2; 10 10; 10.2 10; 10 10.2; 10.2 10.2]; %! [idx, ~, D] = spectralcluster (X, 2, "SimilarityGraph", "epsilon", ... %! "Radius", 1); %! assert_equal (numel (idx), 8); %! assert_equal (idx(1) != idx(5), true); ## Three clusters: mutual knn graph and output shapes %!test %! X = [0 0; 0.2 0; 0 0.2; 10 10; 10.2 10; 10 10.2; 0 10; 0.2 10; 0 10.2]; %! [idx, V, D] = spectralcluster (X, 3, "NumNeighbors", 2, ... %! "KNNGraphType", "mutual"); %! assert_equal (size (V), [9 3]); %! assert_equal (size (D), [3 1]); %! assert_equal (numel (unique (idx(1:3))) == 1, true); %! assert_equal (numel (unique (idx(4:6))) == 1, true); %! assert_equal (numel (unique (idx(7:9))) == 1, true); ## Test input validation %!error spectralcluster (1) %!error ... %! spectralcluster ([], 2) %!error ... %! spectralcluster (ones (4,2), 0) %!error ... %! spectralcluster (ones (3,2), 4) %!error ... %! spectralcluster (ones (4,2), 2, "Distance") %!error ... %! spectralcluster (ones (4,2), 2, "foo", "bar") %!error ... %! spectralcluster (ones (4,2), 2, "Distance", "taxicab") %!error ... %! spectralcluster (ones (4,2), 2, "SimilarityGraph", "tree") %!error ... %! spectralcluster (ones (4,2), 2, "KNNGraphType", "partial") %!error ... %! spectralcluster (ones (4,2), 2, "ClusterMethod", "dbscan") %!error ... %! spectralcluster (ones (4,2), 2, "KernelScale", 0) %!error ... %! spectralcluster (ones (4,2), 2, "SimilarityGraph", "epsilon") %!error ... %! spectralcluster (ones (4,2), 2, "Distance", "precomputed") %!error ... %! spectralcluster ([0 0; 10 10], 2, "SimilarityGraph", "epsilon", "Radius", 1) statistics-release-1.9.2/inst/Data_Manipulation/000077500000000000000000000000001524624707500217155ustar00rootroot00000000000000statistics-release-1.9.2/inst/Data_Manipulation/combnk.m000066400000000000000000000050371524624707500233510ustar00rootroot00000000000000## Copyright (C) 2010 Soren Hauberg ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{c} =} combnk (@var{data}, @var{k}) ## ## Return all combinations of @var{k} elements in @var{data}. ## ## @end deftypefn function retval = combnk (data, k) ## Check input if (nargin != 2) print_usage; elseif (! isvector (data)) error ("combnk: first input argument must be a vector"); elseif (!isreal (k) || k != round (k) || k < 0) error ("combnk: second input argument must be a non-negative integer"); endif ## Simple checks n = numel (data); if (k == 0 || k > n) retval = resize (data, 0, k); elseif (k == n) retval = data(:).'; else retval = __combnk__ (data, k); endif ## For some odd reason Matlab seems to treat strings differently compared to ## other data-types... if (ischar (data)) retval = flipud (retval); endif endfunction function retval = __combnk__ (data, k) ## Recursion stopping criteria if (k == 1) retval = data(:); else ## Process data n = numel (data); if (iscell (data)) retval = {}; else retval = []; endif for j = 1:n C = __combnk__ (data((j+1):end), k-1); C = cat (2, repmat (data(j), rows (C), 1), C); if (! isempty (C)) if (isempty (retval)) retval = C; else retval = [retval; C]; endif endif endfor endif endfunction %!demo %! c = combnk (1:5, 2); %! disp ('All pairs of integers between 1 and 5:'); %! disp (c); %!test %! c = combnk (1:3, 2); %! assert_equal (c, [1, 2; 1, 3; 2, 3]); %!test %! c = combnk (1:3, 6); %! assert_equal (isempty (c), true); %!test %! c = combnk ({1, 2, 3}, 2); %! assert_equal (c, {1, 2; 1, 3; 2, 3}); %!test %! c = combnk ('hello', 2); %! assert_equal (c, ['lo'; 'lo'; 'll'; 'eo'; 'el'; 'el'; 'ho'; 'hl'; 'hl'; 'he']); statistics-release-1.9.2/inst/Data_Manipulation/crosstab.m000066400000000000000000000254751524624707500237300ustar00rootroot00000000000000## Copyright (C) 1995-2017 Kurt Hornik ## Copyright (C) 2018 John Donoghue ## Copyright (C) 2021 Stefano Guidoni ## Copyright (C) 2022-2025 Andreas Bertsatos ## Copyright (C) 2025 Yasin Achengli ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{t} =} crosstab (@var{x1}, @var{x2}) ## @deftypefnx {statistics} {@var{t} =} crosstab (@var{x1}, @dots{}, @var{xn}) ## @deftypefnx {statistics} {[@var{t}, @var{chisq}, @var{p}, @var{labels}] =} crosstab (@dots{}) ## ## Create a cross-tabulation (contingency table) @var{t} from data vectors. ## ## The inputs @var{x1}, @var{x2}, ... @var{xn} must be vectors of equal length ## with a data type of numeric, logical, char array, categorical, strings, or ## cell array of character vectors. ## ## As additional return values @code{crosstab} returns the chi-square statistics ## @var{chisq}, its p-value @var{p} and a cell array @var{labels}, containing ## the labels of each input argument. ## ## @seealso{grp2idx, tabulate} ## @end deftypefn function [t, chisq, p, labels] = crosstab (varargin) ## check input if (nargin < 2) print_usage (); endif ## main - begin v_length = []; # vector of lengths of input vectors reshape_format = []; # vector of the dimensions of t X = []; # matrix of the indexed input values labels_data = {}; # temporary cell array for unique labels coordinates = {}; # cell array of unique elements for i = 1:nargin vector = varargin{i}; ## Convert char, cellstr, or categorical to indexed numeric vector if (ischar (vector) || iscellstr (vector) || iscategorical (vector) || isstring (vector)) try [vector, gnames] = grp2idx (vector); labels_data{i} = gnames; catch error ("crosstab: x1, x2 ... xn must be vectors."); end_try_catch elseif (isnumeric (vector) || islogical (vector)) if (! isvector (vector)) error ("crosstab: x1, x2 ... xn must be vectors."); endif vector = vector(:); unique_vals = unique (vector(! isnan (vector))); labels_data{i} = cellstr (num2str (unique_vals)); else error ("crosstab: unsupported type for data vector."); endif v_length(i) = length (vector); if (length (unique (v_length)) != 1) error ("crosstab: x1, x2 ... xn must be vectors of the same length."); endif X = [X, vector]; reshape_format(i) = length (unique (vector(! isnan (vector)))); coordinates(i) = unique (vector(! isnan (vector))); endfor if (nargout > 3) max_rows = 0; for i = 1:nargin max_rows = max (max_rows, numel (labels_data{i})); endfor labels = cell (max_rows, nargin); for i = 1:nargin col_labels = labels_data{i}; labels(1:numel (col_labels), i) = col_labels; endfor else labels = {}; endif t = zeros (reshape_format); ## Main logic: ## For each combination of x1, x2, ... xn, search in unique elements stored ## in coordinates for each dimension and increment the position value in t ## multidimensional matrix (always if there is no NaN element in the combination). for idx = 1:size (X, 1) if (! any (isnan (X(idx,:)))) location = zeros (1,size (X, 2)); for jdx = 1:size (X,2) location(jdx) = find (cell2mat (coordinates(jdx)) == X(idx, jdx)); endfor t(num2cell (location){:}) += 1; endif endfor if (nargout > 1) if (isscalar (t) || isempty (t)) chisq = NaN; p = NaN; else [p, chisq] = chi2test (t); endif endif endfunction ## Test input validation %!error crosstab () %!error crosstab (1) %!error crosstab (ones (2), [1 1]) %!error crosstab ([1 1], ones (2)) %!error crosstab ([1], [1 2]) %!error crosstab ([1 2], [1]) %!error crosstab ([1 2], {1, 2}) %!test %! load carbig %! [t, chisq, p, labels] = crosstab (cyl4, when, org); %! assert_equal (t(2,3,1), 38); %! assert_equal (labels{3,3}, "Japan"); %!test %! load carbig %! [t, chisq, p, labels] = crosstab (cyl4, when, org); %! assert_equal (t(2,3,2), 17); %! assert_equal (labels{1,3}, "USA"); %!test %! x = [1, 1, 2, 3, 1]; %! y = [1, 2, 5, 3, 1]; %! t = crosstab (x, y); %! assert_equal (t, [2, 1, 0, 0; 0, 0, 0, 1; 0, 0, 1, 0]); %!test %! x = [1, 1, 2, 3, 1]; %! y = [1, 2, 3, 5, 1]; %! t = crosstab (x, y); %! assert_equal (t, [2, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1]); %!test %! x1 = [1, 3, 7, 7, 8]; %! x2 = [4, 2, 1, 1, 1]; %! x3 = [6, 2, 6, 2, NaN]; %! T1 = [0, 0, 0; 0, 1, 0; 1, 0, 0; 0, 0, 0]; %! T2 = [0, 0, 1; 0, 0, 0; 1, 0, 0; 0, 0, 0]; %! T = zeros (4, 3, 2); %! T(:,:,1) = T1; %! T(:,:,2) = T2; %! t = crosstab (x1, x2, x3); %! assert_equal (t, T); %!test %! x = [1, 2, NaN, 1]; %! y = [1, 2, 3, NaN]; %! t = crosstab (x, y); %! assert_equal (t, [1, 0, 0; 0, 1, 0]); ## Test categorical input %!test %! x = categorical ({'A', 'B', 'A', 'C', 'B'}); %! y = [1, 2, 1, 3, 2]; %! t = crosstab (x, y); %! assert_equal (size (t), [3, 3]); %! assert_equal (t(1, 1), 2); # A with 1 %! assert_equal (t(2, 2), 2); # B with 2 %!test %! x = categorical ({'low', 'med', 'high', 'low', 'med'}); %! y = categorical ({'X', 'Y', 'X', 'Y', 'X'}); %! t = crosstab (x, y); %! assert_equal (size (t), [3, 2]); %!test %! ## Test categorical with numeric %! x = categorical ([10, 20, 10, 30, 20]); %! y = [1, 2, 1, 3, 2]; %! t = crosstab (x, y); %! assert_equal (t, [2, 0, 0; 0, 2, 0; 0, 0, 1]); %!test %! smoker = [1 1 0 0 1 0 1 1 0 0 1 0]'; %! gender = [1 0 1 0 1 1 0 0 1 0 0 1]'; %! w = warning (); %! warning ('off'); %! [t, chisq, p, labels] = crosstab (smoker, gender); %! warning (w); %! assert_equal (t, [2 4; 4 2]); %! assert_equal (chisq, 1.33333333, 1e-8); %! assert_equal (p, 0.24821308, 1e-8); %! assert_equal (labels{1,1}, '0'); %! assert_equal (labels{1,2}, '0'); %! assert_equal (labels{2,1}, '1'); %! assert_equal (labels{2,2}, '1'); %!test %! ## Test for categorical %! smk_cat = categorical ([0 0 1 1 0 1 0 0 1 1 0 1]'); %! gen_cat = categorical ([0 1 0 1 0 0 1 1 0 1 1 0]'); %! w = warning (); %! warning ('off'); %! [t, chisq, p] = crosstab (smk_cat, gen_cat); %! warning (w); %! assert_equal (t, [2 4; 4 2]); %! assert_equal (chisq, 1.33333333, 1e-6); %! assert_equal (p, 0.24821308, 1e-6); %!test %! x = [1 1 1 2 2 2 3 3 3]'; %! y = [1 1 1 2 2 2 3 3 3]'; %! w = warning (); %! warning ('off'); %! [t, chisq, p, labels] = crosstab (x, y); %! warning (w); %! assert_equal (t, diag ([3 3 3])); %! assert_equal (chisq, 18.00000000); %! assert_equal (p, 0.00123410, 1e-8); %!test %! ## Test for Partial NaN giving NaN for chisq/p %! x7 = [1 2 3 4 NaN NaN]'; %! y7 = [10 20 30 40 50 60]'; %! w = warning (); %! warning ('off'); %! [t, chisq, p, labels] = crosstab (x7, y7); %! warning (w); %! assert_equal (t, [eye(4), zeros(4, 2)]); %! assert_equal (isnan (chisq), true); %! assert_equal (isnan (p), true); %! assert_equal (labels{1,1}, '1'); %! assert_equal (labels{1,2}, '10'); %! assert_equal (labels{2,1}, '2'); %! assert_equal (labels{2,2}, '20'); %! assert_equal (labels{3,1}, '3'); %! assert_equal (labels{3,2}, '30'); %! assert_equal (labels{4,1}, '4'); %! assert_equal (labels{4,2}, '40'); %! assert_equal (isempty (labels{5,1}), true); %! assert_equal (labels{5,2}, '50'); %! assert_equal (isempty (labels{6,1}), true); %! assert_equal (labels{6,2}, '60'); %!test %! a = ones (15,1); %! b = ones (15,1); %! w = warning (); %! warning ('off'); %! [t, chisq, p] = crosstab (a, b); %! warning (w); %! assert_equal (t, 15); %! assert_equal (isnan (chisq), true); %! assert_equal (isnan (p), true); %!test %! ## all NaN → empty table + NaN stats %! na = NaN (6,1); %! nb = (1:6)'; %! w = warning (); %! warning ('off'); %! [t, chisq, p] = crosstab (na, nb); %! warning (w); %! assert_equal (all (t(:) == 0), true); %! assert_equal (isnan (chisq), true); %! assert_equal (isnan (p), true); %!test %! ## single observation → 1×1 table, NaN statistic %! w = warning (); %! warning ('off'); %! [t, chisq, p, labels] = crosstab (5, 'Z'); %! warning (w); %! assert_equal (t, 1); %! assert_equal (isnan (chisq), true); %! assert_equal (isnan (p), true); %! assert_equal (labels{1,1}, '5'); %! assert_equal (labels{1,2}, 'Z'); %!test %! xx = [1 1 1 1 2 2 2 2]'; %! yy = [10 10 10 10 20 20 20 20]'; %! w = warning (); %! warning ('off'); %! [t, chisq, p] = crosstab (xx, yy); %! warning (w); %! assert_equal (t, [4 0; 0 4]); %! assert_equal (chisq, 8.00000000); %! assert_equal (p, 0.00467773, 1e-8); %!test %! set1 = repmat ((1:5)', 20, 1); %! set2 = repmat ([1; 2], 50, 1); %! w = warning (); %! warning ('off'); %! [t, chisq, p] = crosstab (set1, set2); %! warning (w); %! assert_equal (t, 10 * ones (5,2)); %! assert_equal (chisq, 0); %! assert_equal (p, 1); %!test %! ## 3-way table with NaN %! a = [1 1 2 2 3 3 1]'; %! b = [1 2 1 2 1 2 1]'; %! c = [1 1 NaN 2 2 1 2]'; %! w = warning (); %! warning ('off'); %! [t, chisq, p] = crosstab (a, b, c); %! warning (w); %! expected(:,:,1) = [1 1; 0 0; 0 1]; %! expected(:,:,2) = [1 0; 0 1; 1 0]; %! assert_equal (t, expected); %! assert_equal (chisq, 6.00000000); %! assert_equal (p, 0.53974935, 1e-9); %!test %! ## sparse cellstr %! g = [1 5 7 12 1 5 19]'; %! h = {'A', 'B', 'A', 'C', 'D', 'E', 'A'}'; %! w = warning (); %! warning ('off'); %! [t, chisq, p] = crosstab (g, h); %! warning (w); %! assert_equal (sum (t(:)), 7); %! assert_equal (chisq, 16.33333333, 1e-8); %! assert_equal (p, 0.42994852, 1e-8); %!test %! ## string array %! str1 = ['low'; 'high'; 'med'; 'low'; 'high']; %! str2 = ['X'; 'Y'; 'X'; 'Y'; 'X']; %! w = warning (); %! warning ('off'); %! [t, chisq, p] = crosstab (str1, str2); %! warning (w); %! assert_equal (t, [1 1; 1 1; 1 0]); %! assert_equal (chisq, 0.83333333, 1e-8); %! assert_equal (p, 0.659240631, 1e-9); %!test %! ## cellstr %! c1 = {'A','B','A','C','B','A'}'; %! c2 = {'1','2','1','3','2','1'}'; %! w = warning (); %! warning ('off'); %! [t, chisq, p] = crosstab (c1, c2); %! warning (w); %! assert_equal (t, [3 0 0; 0 2 0; 0 0 1]); %! assert_equal (chisq, 12.00000000, 1e-14); %! assert_equal (p, 0.01735127, 1e-8); statistics-release-1.9.2/inst/Data_Manipulation/datasample.m000066400000000000000000000202151524624707500242060ustar00rootroot00000000000000## Copyright (C) 2021 Stefano Guidoni ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} datasample (@var{data}, @var{k}) ## @deftypefnx {statistics} {@var{y} =} datasample (@var{data}, @var{k}, @var{dim}) ## @deftypefnx {statistics} {@var{y} =} datasample (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{y} @var{idcs}] =} datasample (@dots{}) ## ## Randomly sample data. ## ## Return @var{k} observations randomly sampled from @var{data}. @var{data} can ## be a vector or a matrix of any data. When @var{data} is a matrix or a ## n-dimensional array, the samples are the subarrays of size n - 1, taken along ## the dimension @var{dim}. The default value for @var{dim} is 1, that is the ## row vectors when sampling a matrix. ## ## Output @var{y} is the returned sampled data. Optional output @var{idcs} is ## the vector of the indices to build @var{y} from @var{data}. ## ## Additional options are set through pairs of parameter name and value. ## Available parameters are: ## ## @table @code ## @item @qcode{Replace} ## a logical value that can be @code{true} (default) or @code{false}: when set ## to @code{true}, @code{datasample} returns data sampled with replacement. ## ## @item @qcode{Weights} ## a vector of positive numbers that sets the probability of each element. It ## must have the same size as @var{data} along dimension @var{dim}. ## ## @end table ## ## ## @seealso{rand, randi, randperm, randsample} ## ## @end deftypefn function [y, idcs] = datasample (data, k, varargin) ## check input if ( nargin < 2 ) print_usage (); endif ## data: some data, any type, any format but cell ## MATLAB compatibility: there are no "table" or "dataset array" types in ## Octave if (iscell (data)) error ("datasample: data must be a vector or matrix"); endif ## k, a positive integer if ((! isnumeric (k) || ! isscalar (k)) || (! (floor (k) == k)) || (k <= 0)) error ("datasample: k must be a positive integer scalar"); endif dim = 1; replace = true; weights = []; if ( nargin > 2 ) pair_index = 1; if (! ischar (varargin{1})) ## it must be dim dim = varargin{1}; ## the (Name, Value) pairs start further pair_index += 1; ## dim, another positive integer if ((! isscalar (dim)) || (! (floor (dim) == dim)) || (dim <= 0)) error ("datasample: DIM must be a positive integer scalar"); endif endif ## (Name, Value) pairs while (pair_index < (nargin - 2)) switch (lower (varargin{pair_index})) case 'replace' if (! islogical (varargin{pair_index + 1})) error ("datasample: expected a logical value for 'Replace'"); endif replace = varargin{pair_index + 1}; case 'weights' if ((! isnumeric (varargin{pair_index + 1})) || (! isvector (varargin{pair_index + 1})) || (any (varargin{pair_index + 1} < 0))) error (strcat ("datasample: the sampling weights must be defined as a", " vector of positive values")); endif weights = varargin{pair_index + 1}; otherwise error ("datasample: unknown property %s", varargin{pair_index}); endswitch pair_index += 2; endwhile endif ## get the size of the population to sample if (isvector (data)) imax = length (data); else imax = size (data, dim); endif if (isempty (weights)) ## all elements have the same probability of being chosen ## this is easy ## with or without replacement if (replace) idcs = randi (imax, k, 1); else idcs = randperm (imax, k); endif else ## first check if the weights vector is right if (imax != length (weights)) error (strcat ("datasample: the size of the vector of sampling weights must", " be equal to the size of the sampled data")); endif if (replace) ## easy case: ## normalize the weights, weights_n = cumsum (weights ./ sum (weights)); weights_n(end) = 1; # just to be sure ## then choose k numbers uniformly between 0 and 1 samples = rand (k, 1); ## we have subdivided the space between 0 and 1 accordingly to the ## weights vector: we have just to map back the random numbers to the ## indices of the original dataset for iter = 1 : k idcs(iter) = find (weights_n >= samples(iter), 1); endfor else ## complex case if (k > imax) error (strcat ("datasample: K must not exceed the number of", ... " available elements when sampling without replacement.")); endif if (k > sum (weights > 0)) error (strcat ("datasample: sampling without replacement", ... " requires at least K elements with positive weights.")); endif ## choose k numbers uniformly between 0 and 1 samples = rand (k, 1); for iter = 1 : k ## normalize the weights weights_n = cumsum (weights ./ sum (weights)); weights_n(end) = 1; # just to be sure idcs(iter) = find (weights_n >= samples(iter), 1); ## remove the element from the set, i. e. set its probability to zero weights(idcs(iter)) = 0; endfor endif endif ## let's get the actual data from the original set if (isvector (data)) ## data is a vector y = data(idcs); else vS = size (data); if (length (vS) == 2) ## data is a 2-dimensional matrix if (dim == 1) y = data(idcs, :); else y = data(:, idcs); endif else ## data is an n-dimensional matrix s = 'y = data('; for iter = 1 : length (vS) if (iter == dim) s = [s 'idcs,']; else s = [s ':,']; endif endfor s = [s ':);']; eval (s); endif endif endfunction ## some tests %!error datasample (); %!error datasample (1); %!error datasample ({1, 2, 3}, 1); %!error datasample ([1 2], -1); %!error datasample ([1 2], 1.5); %!error datasample ([1 2], [1 1]); %!error datasample ([1 2], 'g', [1 1]); %!error datasample ([1 2], 1, -1); %!error datasample ([1 2], 1, 1.5); %!error datasample ([1 2], 1, [1 1]); %!error datasample ([1 2], 1, 1, 'Replace', -2); %!error datasample ([1 2], 1, 1, 'Weights', 'abc'); %!error datasample ([1 2], 1, 1, 'Weights', [1 -2 3]); %!error datasample ([1 2], 1, 1, 'Weights', ones (2)); %!error datasample ([1 2], 1, 1, 'Weights', [1 2 3]); %!error ... %! data = 1:5; weights = [0.077846, 0.103765, 0.703748, 0.840937, 0.422901]; %! sampled = datasample (data, 8, 'Weights', weights, 'Replace', false); %!error ... %! data = 1:5; weights = [1, 0, 1, 0, 0]; %! sampled = datasample (data, 3, 'Weights', weights, 'Replace', false); %!test %! dat = randn (10, 4); %! assert_equal (size (datasample (dat, 3, 1)), [3 4]); %!test %! dat = randn (10, 4); %! assert_equal (size (datasample (dat, 3, 2)), [10 3]); statistics-release-1.9.2/inst/Data_Manipulation/doc-cache000066400000000000000000001037751524624707500234630ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 14 # name: # type: sq_string # elements: 1 # length: 6 combnk # name: # type: sq_string # elements: 1 # length: 84 statistics: c = combnk ( data , k ) Return all combinations of k elements in data . # name: # type: sq_string # elements: 1 # length: 46 Return all combinations of k elements in data. # name: # type: sq_string # elements: 1 # length: 8 crosstab # name: # type: sq_string # elements: 1 # length: 576 statistics: t = crosstab ( x1 , x2 ) statistics: t = crosstab ( x1 , …, xn ) statistics: [ t , chisq , p , labels ] = crosstab (…) Create a cross-tabulation (contingency table) t from data vectors. The inputs x1 , x2 , ... xn must be vectors of equal length with a data type of numeric, logical, char array, categorical, strings, or cell array of character vectors. As additional return values crosstab returns the chi-square statistics chisq , its p-value p and a cell array labels , containing the labels of each input argument. See also: grp2idx, tabulate # name: # type: sq_string # elements: 1 # length: 66 Create a cross-tabulation (contingency table) t from data vectors. # name: # type: sq_string # elements: 1 # length: 10 datasample # name: # type: sq_string # elements: 1 # length: 1035 statistics: y = datasample ( data , k ) statistics: y = datasample ( data , k , dim ) statistics: y = datasample (…, Name , Value ) statistics: [ y idcs ] = datasample (…) Randomly sample data. Return k observations randomly sampled from data . data can be a vector or a matrix of any data. When data is a matrix or a n-dimensional array, the samples are the subarrays of size n - 1, taken along the dimension dim . The default value for dim is 1, that is the row vectors when sampling a matrix. Output y is the returned sampled data. Optional output idcs is the vector of the indices to build y from data . Additional options are set through pairs of parameter name and value. Available parameters are: Replace a logical value that can be true (default) or false : when set to true , datasample returns data sampled with replacement. Weights a vector of positive numbers that sets the probability of each element. It must have the same size as data along dimension dim . See also: rand, randi, randperm, randsample # name: # type: sq_string # elements: 1 # length: 21 Randomly sample data. # name: # type: sq_string # elements: 1 # length: 8 dummyvar # name: # type: sq_string # elements: 1 # length: 1325 statistics: D = dummyvar ( group ) Create dummy variables. D = dummyvar ( group ) returns a matrix D containing the dummy variables associated with the grouping variables in group . Each row in D corresponds to the same observation across all variables in group and each column in D corresponds to a separate dummy variable. D is a numeric matrix of double data type containing ones and zeros. The grouping variable in group can be specified in one of the following options: a positive integer vector representing the different group levels in the ordered range 1:max ( group ) . a positive integer matrix with each column corresponding to a separate grouping variable and the integer values representing the group levels within that grouping variable in the ordered range 1:max ( group ) . a categorical column vector, in which case the number and order of columns in D correspond to the categories returned by categories ( group ) . Categories that are defined but not present in group produce columns of zeros. Elements of group that are result in rows of NaN values in D . a cell array with its elements containing grouping variables specified as any of the above options. Note that all grouping variables in the cell array must have the same number of observations. See also: tabulate, grp2idx, grpstats # name: # type: sq_string # elements: 1 # length: 23 Create dummy variables. # name: # type: sq_string # elements: 1 # length: 11 fillmissing # name: # type: sq_string # elements: 1 # length: 7516 statistics: B = fillmissing ( A , "constant", v ) statistics: B = fillmissing ( A , method ) statistics: B = fillmissing ( A , move_method , window_size ) statistics: B = fillmissing ( A , fill_function , window_size ) statistics: B = fillmissing (…, dim ) statistics: B = fillmissing (…, PropertyName , PropertyValue ) statistics: [ B , idx ] = fillmissing (…) Fill missing data in arrays. Replace missing entries of array A either with values in v or as determined by other specified methods. ’missing’ values are determined by the data type of A as identified by the function ismissing, currently defined as: Standard missing values and their corresponding data types are: NaN - for double , single , duration , and calendarDuration arrays. NaT - for datetime arrays. - for string arrays. - for categorical arrays. {0x0 char} - for cell arrays of character vectors. For any data types that do not support missing values, ismissing returns TF = false (size ( A )) . A can be a numeric scalar or array, a character vector or array, or a cell array of character vectors (a.k.a. string cells). v can be a scalar or an array containing values for replacing the missing values in A with a compatible data type for insertion into A . The shape of v must be a scalar or an array with number of elements in v equal to the number of elements orthogonal to the operating dimension. E.g., if size( A ) = [3 5 4], operating along dim = 2 requires v to contain either 1 or 3x4=12 elements. If requested, the optional output idx will contain a logical array the same shape as A indicating with 1’s which locations in A were filled. Alternate Input Arguments and Values: method - replace missing values with: next previous nearest next, previous, or nearest non-missing value (nearest defaults to next when equidistant as determined by SamplePoints .) linear linear interpolation of neighboring, non-missing values spline piecewise cubic spline interpolation of neighboring, non-missing values pchip ’shape preserving’ piecewise cubic spline interpolation of neighboring, non-missing values move_method - moving window calculated replacement values: movmean movmedian moving average or median using a window determined by window_size . window_size must be either a positive scalar value or a two element positive vector of sizes [ nb , na ] measured in the same units as SamplePoints . For scalar values, the window is centered on the missing element and includes all data points within a distance of half of window_size on either side of the window center point. Note that for compatibility, when using a scalar value, the backward window limit is inclusive and the forward limit is exclusive. If a two-element window_size vector is specified, the window includes all points within a distance of nb backward and na forward from the current element at the window center (both limits inclusive). fill_function - custom method specified as a function handle. The supplied fill function must accept three inputs in the following order for each missing gap in the data: A_values - elements of A within the window on either side of the gap as determined by window_size . (Note these elements can include missing values from other nearby gaps.) A_locs - locations of the reference data, A_values , in terms of the default or specified SamplePoints . gap_locs - location of the gap data points that need to be filled in terms of the default or specified SamplePoints . The supplied function must return a scalar or vector with the same number of elements in gap_locs . The required window_size parameter follows similar rules as for the moving average and median methods described above, with the two exceptions that (1) each gap is processed as a single element, rather than gap elements being processed individually, and (2) the window extended on either side of the gap has inclusive endpoints regardless of how window_size is specified. dim - specify a dimension for vector operation (default = first non-singeton dimension) A one-sided window_size , [ nb , 0] or [0, na ] , leaves the gap at the corresponding end of the data with no values in its window. The fill function is still called there, with empty value and location arguments, as MATLAB calls it: a fill function may depend only on the gap’s own sample points. One that cannot take empty arguments is reported as such. Along a dimension in which A is singleton, every element is a vector of length one, so a missing value has no neighbour to be filled from and is returned unchanged. MATLAB fills it regardless, which is a defect there and not a convention this follows: it returns [1, 1, 3, 1, 5] for fillmissing ([1, NaN, 3, NaN, 5], @testfcn, 99, 3) , inventing values from a window that contains nothing. Measured against R2024a, two releases after the behaviour was first recorded here. PropertyName - PropertyValue pairs SamplePoints PropertyValue is a vector of sample point values representing the sorted and unique x-axis values of the data in A . If unspecified, the default is assumed to be the vector [1 : size (A, dim)] . The values in SamplePoints will affect methods and properties that rely on the effective distance between data points in A , such as interpolants and moving window functions where the window_size specified for moving window functions is measured relative to the SamplePoints . EndValues Apply a separate handling method for missing values at the front or back of the array. PropertyValue can be: A constant scalar or array with the same shape requirements as v . none - Do not fill end gap values. extrap - Use the same procedure as method to fill the end gap values. Any valid method listed above except for movmean , movmedian , and fill_function . Those methods can only be applied to end gap values with extrap . MissingLocations PropertyValue must be a logical array the same size as A indicating locations of known missing data with a value of true . (cannot be combined with MaxGap) MaxGap PropertyValue is a numeric scalar indicating the maximum gap length to fill, and assumes the same distance scale as the sample points. Gap length is calculated by the difference in locations of the sample points on either side of the gap, and gaps larger than MaxGap are ignored by fillmissing . (cannot be combined with MissingLocations) Compatibility Notes: Numerical and logical inputs for A and v may be specified in any combination. The output will be the same class as A , with the v converted to that data type for filling. Only single and double have defined ’missing’ values, so except for when the missinglocations option specifies the missing value identification of logical and other numeric data types, the output will always be B = A with idx = false(size( A )) . All interpolation methods can be individually applied to EndValues . MATLAB ’s fill_function method currently has several inconsistencies with the other methods (tested against version 2022a), and Octave’s implementation has chosen the following consistent behavior over compatibility: (1) a column full of missing data is considered part of EndValues , (2) such columns are then excluded from fill_function processing because the moving window is always empty. (3) operation in dimensions higher than 2 perform identically to operations in dims 1 and 2, most notable on vectors. See also: ismissing, rmmissing, standardizeMissing # name: # type: sq_string # elements: 1 # length: 28 Fill missing data in arrays. # name: # type: sq_string # elements: 1 # length: 7 grp2idx # name: # type: sq_string # elements: 1 # length: 1598 statistics: g = grp2idx ( s ) statistics: [ g , gn ] = grp2idx ( s ) statistics: [ g , gn , gl ] = grp2idx ( s ) Get index for grouping variable. g = grp2idx ( s ) returns a numeric column vector of integer values g indexing the distinct groups in the grouping variable s . s can specified as any of the following data types: categorical vector cell array of character vectors character array duration vector logical vector numeric vector s must be a vector, unless it is a 2-D character array. In the case of numerical and logical data types, the group indices are ordered in sorted order of s . In the case of categorical arrays, the group indices are allocated by the order of the categories in s . For the rest of the data types, the group indices are allocated by order of first appearance in s . Note that in case of a categorical grouping variable, the indexing integer values might not be continuous, since s may contain unassigned categories. For every other data type, g will contain integer values in the range [1:K] , where K is the number of distinct groups in s . [ g , gn ] = grp2idx ( s ) also returns a cell array of character vectors gn representing the list of group names. The order of the group names in gn follow the same pattern as the group indices in g according to the data type of s , as described above. [ g , gn , gl ] = grp2idx ( s ) further returns a column vector gl representing the list of the group levels with the same data type as s . Note that standard missing values in s appear as NaN in g and are not present on either gn and gl . See also: grpstats # name: # type: sq_string # elements: 1 # length: 32 Get index for grouping variable. # name: # type: sq_string # elements: 1 # length: 9 ismissing # name: # type: sq_string # elements: 1 # length: 2139 statistics: TF = ismissing ( A ) statistics: TF = ismissing ( A , indicator ) Find missing data in arrays. TF = ismissing ( A ) returns a logical array, TF , with the same dimensions as A , where true values match the standard missing values in the input data according to their data type. Standard missing values and their corresponding data types are: NaN - for double , single , duration , and calendarDuration arrays. NaT - for datetime arrays. - for string arrays. - for categorical arrays. {0x0 char} - for cell arrays of character vectors. For any data types that do not support missing values, ismissing returns TF = false (size ( A )) . Note: the generic ismissing function from the statistics package only operates on core Octave datatypes and it explicitly identifies missing values in double and single arrays, as well as in cell arrays of character vectors. All other data types are handled by the overloaded methods from their respective data class from the datatypes package. Use help class_name.ismissing to find more information about the functional specialization of their respective class implementation. The optional input indicator can be a scalar or a vector, of the same type as the input data A , specifying alternative missing values in the input data. When specifying indicator values, the standard missing values are ignored, unless explicitly stated in the indicator . Additional data type matches between indicator and A are: double indicators also match single , all integer types, and logical data in A . string and char indicators also match categorical data in A . char and cellstr indicators also match string data in A . Note: the generic ismissing function from the statistics package only accepts indicator argument for numeric, logical , and char arrays, as well as for cell arrays of character vectors. For all other core Octave data types, ismissing produces an error. However, indicator is supported for data classes from the datatypes package through their respective class implementation of overloaded methods. See also: fillmissing, rmmissing, standardizeMissing # name: # type: sq_string # elements: 1 # length: 28 Find missing data in arrays. # name: # type: sq_string # elements: 1 # length: 9 isoutlier # name: # type: sq_string # elements: 1 # length: 6605 statistics: TF = isoutlier ( x ) statistics: TF = isoutlier ( x , method ) statistics: TF = isoutlier ( x , 'percentiles' , threshold ) statistics: TF = isoutlier ( x , movmethod , window ) statistics: TF = isoutlier (…, dim ) statistics: TF = isoutlier (…, Name , Value ) statistics: [ TF , L , U , C ] = isoutlier (…) Find outliers in data isoutlier ( x ) returns a logical array whose elements are true when an outlier is detected in the corresponding element of x . isoutlier treats NaNs as missing values and removes them. If x is a matrix, then isoutlier operates on each column of x separately. If x is a multidimensional array, then isoutlier operates along the first dimension of x whose size does not equal 1. By default, an outlier is a value that is more than three scaled median absolute deviations (MAD) from the median. The scaled median is defined as c*median(abs(A-median(A))) , where c=-1/(sqrt(2)*erfcinv(3/2)) . isoutlier ( x , method ) specifies a method for detecting outliers. The following methods are available: Method Description 'median' Outliers are defined as elements more than three scaled MAD from the median. 'mean' Outliers are defined as elements more than three standard deviations from the mean. 'quartiles' Outliers are defined as elements more than 1.5 interquartile ranges above the upper quartile (75 percent) or below the lower quartile (25 percent). This method is useful when the data in x is not normally distributed. 'grubbs' Outliers are detected using Grubbs’ test for outliers, which removes one outlier per iteration based on hypothesis testing. This method assumes that the data in x is normally distributed. 'gesd' Outliers are detected using the generalized extreme Studentized deviate test for outliers. This iterative method is similar to 'grubbs' , but can perform better when there are multiple outliers masking each other. isoutlier ( x , 'percentiles' , threshold ) detects outliers based on a percentile thresholds, specified as a two-element row vector whose elements are in the interval [0, 100] . The first element indicates the lower percentile threshold, and the second element indicates the upper percentile threshold. The first element of threshold must be less than the second element. isoutlier ( x , movmethod , window ) specifies a moving method for detecting outliers. The following methods are available: Method Description 'movmedian' Outliers are defined as elements more than three local scaled MAD from the local median over a window length specified by window . 'movmean' Outliers are defined as elements more than three local standard deviations from the from the local mean over a window length specified by window . window must be a positive integer scalar or a two-element vector of positive integers. When window is a scalar, if it is an odd number, the window is centered about the current element and contains window - 1 neighboring elements. If even, then the window is centered about the current and previous elements. When window is a two-element vector of positive integers [nb, na] , the window contains the current element, nb elements before the current element, and na elements after the current element. When 'SamplePoints' are also specified, window can take any real positive values (either as a scalar or a two-element vector) and in this case, the windows are computed relative to the sample points. dim specifies the operating dimension and it must be a positive integer scalar. If not specified, then, by default, isoutlier operates along the first non-singleton dimension of x . The following optional parameters can be specified as Name / Value paired arguments. 'SamplePoints' can be specified as a vector of sample points with equal length as the operating dimension. The sample points represent the x-axis location of the data and must be sorted and contain unique elements. Sample points do not need to be uniformly sampled. By default, the vector is [1, 2, 3, …, n ] , where n = size ( x , dim ) . You can use unequally spaced 'SamplePoints' to define a variable-length window for one of the moving methods available. 'ThresholdFactor' can be specified as a nonnegative scalar. For methods 'median' and 'movmedian' , the detection threshold factor replaces the number of scaled MAD, which is 3 by default. For methods 'mean' and 'movmean' , the detection threshold factor replaces the number of standard deviations, which is 3 by default. For methods 'grubbs' and 'gesd' , the detection threshold factor ranges from 0 to 1, specifying the critical alpha -value of the respective test, and it is 0.05 by default. For the 'quartiles' method, the detection threshold factor replaces the number of interquartile ranges, which is 1.5 by default. 'ThresholdFactor' is not supported for the 'quartiles' method. 'MaxNumOutliers' is only relevant to the 'gesd' method and it must be a positive integer scalar specifying the maximum number of outliers returned by the 'gesd' method. By default, it is the integer nearest to the 10% of the number of elements along the operating dimension in x . The 'gesd' method assumes the nonoutlier input data is sampled from an approximate normal distribution. When the data is not sampled in this way, the number of returned outliers might exceed the MaxNumOutliers value. [ TF , L , U , C ] = isoutlier (…) returns up to 4 output arguments as described below. TF is the outlier indicator with the same size a x . L is the lower threshold used by the outlier detection method. If method is used for outlier detection, then L has the same size as x in all dimensions except for the operating dimension where the length is 1. If movmethod is used, then L has the same size as x . U is the upper threshold used by the outlier detection method. If method is used for outlier detection, then U has the same size as x in all dimensions except for the operating dimension where the length is 1. If movmethod is used, then U has the same size as x . C is the center value used by the outlier detection method. If method is used for outlier detection, then C has the same size as x in all dimensions except for the operating dimension where the length is 1. If movmethod is used, then C has the same size as x . For 'median' , 'movmedian' , 'mean' , and 'movmean' methods, C is computed by taking into account the outlier values. For 'grubbs' and 'gesd' methods, C is computed by excluding the outliers. For the 'percentiles' method, C is the average between U and L thresholds. See also: filloutliers, rmoutliers, ismissing # name: # type: sq_string # elements: 1 # length: 21 Find outliers in data # name: # type: sq_string # elements: 1 # length: 8 multiway # name: # type: sq_string # elements: 1 # length: 1865 statistics: groupindex = multiway ( numbers , num_parts ) statistics: groupindex = multiway ( numbers , num_parts , method ) statistics: [ groupindex , partition ] = multiway (…) statistics: [ groupindex , partition , groupsizes ] = multiway (…) Solve the multiway number partitioning problem. groupindex = multiway ( numbers , num_parts ) splits a set of numbers in numbers into a number of subsets specified in num_parts such that the sums of the subsets are nearly as equal as possible and returns a vector of group indices in groupindex with each index corresponding to the set of numbers provided as input. numbers is a vector of positive real numbers to be partitioned. num_parts is a positive integer scalar specifying the number of partitions (subsets) to split the numbers into. groupindex = multiway ( numbers , num_parts , method ) also specifies the algorithm used for partitioning the set of numbers. By default, multiway uses the complete Karmarkar-Karp algorithm, when the set of numbers contains up to 10 elements and the requested number of subsets does not exceed 5, otherwise it defaults to the greedy algorithm, which is optimized for speed, but may not return the optimal partitioning. The following methods are supported: 'greedy' (Greedy algorithm) 'completeKK' (Complete Karmarkar-Karp algorithm) The multiway function may return up to three output arguments described below: groupindex : A vector of the same length as numbers containing the group index (from 1 to num_parts ) for each number. partition : A cell array of length num_parts with each cell containing the numbers assigned to that partition. groupsizes : A vector of the sums of the numbers in each partition. Example: numbers = [4, 5, 6, 7, 8]; num_parts = 2; [groupindex, partition, groupsizes] = multiway (numbers, num_parts); See also: cvpartition # name: # type: sq_string # elements: 1 # length: 47 Solve the multiway number partitioning problem. # name: # type: sq_string # elements: 1 # length: 22 normalise_distribution # name: # type: sq_string # elements: 1 # length: 1895 statistics: NORMALISED = normalise_distribution ( DATA ) statistics: NORMALISED = normalise_distribution ( DATA , DISTRIBUTION ) statistics: NORMALISED = normalise_distribution ( DATA , DISTRIBUTION , DIMENSION ) Transform a set of data so as to be N(0,1) distributed according to an idea by van Albada and Robinson. This is achieved by first passing it through its own cumulative distribution function (CDF) in order to get a uniform distribution, and then mapping the uniform to a normal distribution. The data must be passed as a vector or matrix in DATA . If the CDF is unknown, then [] can be passed in DISTRIBUTION , and in this case the empirical CDF will be used. Otherwise, if the CDFs for all data are known, they can be passed in DISTRIBUTION , either in the form of a single function name as a string, or a single function handle, or a cell array consisting of either all function names as strings, or all function handles. In the latter case, the number of CDFs passed must match the number of rows, or columns respectively, to normalise. If the data are passed as a matrix, then the transformation will operate either along the first non-singleton dimension, or along DIMENSION if present. Notes: The empirical CDF will map any two sets of data having the same size and their ties in the same places after sorting to some permutation of the same normalised data: normalise_distribution([1 2 2 3 4]) ⇒ -1.28 0.00 0.00 0.52 1.28 normalise_distribution([1 10 100 10 1000]) ⇒ -1.28 0.00 0.52 0.00 1.28 Original source: S.J. van Albada, P.A. Robinson "Transformation of arbitrary distributions to the normal distribution with application to EEG test-retest reliability" Journal of Neuroscience Methods, Volume 161, Issue 2, 15 April 2007, Pages 205-211 ISSN 0165-0270, 10.1016/j.jneumeth.2006.11.004. (http://www.sciencedirect.com/science/article/pii/S0165027006005668) # name: # type: sq_string # elements: 1 # length: 103 Transform a set of data so as to be N(0,1) distributed according to an idea by van Albada and Robinson. # name: # type: sq_string # elements: 1 # length: 10 randsample # name: # type: sq_string # elements: 1 # length: 670 statistics: y = randsample ( v , k ) statistics: y = randsample ( v , k , replacement =false) statistics: y = randsample ( v , k , replacement =false, [ w =[]]) Sample elements from a vector. Returns k random elements from a vector v with n elements, sampled without or with replacement , with an optional weight vector. If v is a scalar, samples from 1: v . If a weight vector w of the same size as v is specified, the probability of each element being sampled is proportional to w . Unlike Matlab’s function of the same name, this can be done for sampling with or without replacement. Randomization is performed using rand(). See also: datasample, randperm # name: # type: sq_string # elements: 1 # length: 30 Sample elements from a vector. # name: # type: sq_string # elements: 1 # length: 9 rmmissing # name: # type: sq_string # elements: 1 # length: 1925 statistics: R = rmmissing ( A ) statistics: R = rmmissing ( A , dim ) statistics: R = rmmissing (…, Name , Value ) statistics: [ R , TF ] = rmmissing (…) Remove missing data from arrays. Given an input vector or matrix (2-D array) A , R = rmmissing ( A ) returns an output vector or matrix R of the same type as input A and any missing elements removed. If A is a vector, missing elements are removed individually, if A is a matrix, then rows containing missing elements are removed. Standard missing values and their corresponding data types are: NaN - for double , single , duration , and calendarDuration arrays. NaT - for datetime arrays. - for string arrays. - for categorical arrays. {0x0 char} - for cell arrays of character vectors. For any data types that do not support missing values, rmmissing returns R == A and if a second output argument is requested it also returns TF = false (size ( A )) . Given an input matrix (2-D array) A , R = rmmissing ( A , dim ) further specifies whether rows or columns containing missing data are removed from the output R based on the value of dim , which must be either 1 or 0. 1 : remove rows. 2 : remove columns. R = rmmissing (…, Name , Value ) also accepts the following paired arguments. Name Value 'MinNumMissing' A positive integer scalar value specifying the required minimum number of missing values for removing any particular row or column from a matrix input. Note that this argument is ignored if input A is a vector. 'MissingLocations' A logical array of the same size as input A indexing the locations of missing values in input array A . Note that specifying 'MissingLocations' overrides any standard missing values in A . Optional return value TF is a logical array where true values represent removed entries, rows or columns from the original data A . See also: fillmissing, ismissing, standardizeMissing # name: # type: sq_string # elements: 1 # length: 32 Remove missing data from arrays. # name: # type: sq_string # elements: 1 # length: 18 standardizeMissing # name: # type: sq_string # elements: 1 # length: 1932 statistics: B = standardizeMissing ( A , indicator ) Replace selected values by standard missing values. Β = standardizeMissing ( A , indicator ) returns a standardized array B of the same size and data type as the input array A and with all elements specified by indicator replaced by the standard missing value corresponding the data type of A . indicator can be either a scalar or a vector. Standard missing values and their corresponding data types are: NaN - for double , single , duration , and calendarDuration arrays. NaT - for datetime arrays. - for string arrays. - for categorical arrays. {0x0 char} - for cell arrays of character vectors. For any other data type input that does not support missing values, standardizeMissing returns B = A and any indicator value is ignored. The nonstandard missing value indicator must be of the same type as the data input A or have a compatible data types according to the following rules: all numeric indicators match both double and single data types in A . indicators specified as string arrays, char vectors, and cell arrays of character vectors match categorical data type in A . a char vector matches a cell array of character vectors in A . Note: the generic standardizeMissing function from the statistics does not operate on table inputs, which is handled by the overloaded method of the table class. Use help table.standardizeMissing to find more information about the functional specialization on tables. Standardizing a category of a categorical array removes that category from the array’s type, as MATLAB removes it: no element carries it once the values are missing, so leaving it in the category list would be stale metadata, and the codes of the remaining categories shift down accordingly. Only the standardized categories are removed; one that is declared but unused is left alone. See also: fillmissing, ismissing, rmmissing # name: # type: sq_string # elements: 1 # length: 51 Replace selected values by standard missing values. # name: # type: sq_string # elements: 1 # length: 8 tiedrank # name: # type: sq_string # elements: 1 # length: 1845 statistics: [ r , tieadj ] = tiedrank ( x ) statistics: [ r , tieadj ] = tiedrank ( x , tieflag ) statistics: [ r , tieadj ] = tiedrank ( x , tieflag , bidir ) statistics: [ r , tieadj ] = tiedrank ( x , tieflag , bidir , tol ) x may be a vector or an array. An array is ranked along its first dimension, so a matrix is ranked column by column, and tieadj then carries one entry per column: its first dimension has length 1 by default, or 3 when tieflag is set, and its higher dimensions are those of x . Compute rank adjusted for ties. [ r , tieadj ] = tiedrank ( x ) computes the ranks of the values in vector x . If any values in x are tied, tiedrank computes their average rank. The return value tieadj is an adjustment for ties required by the nonparametric tests signrank and ranksum , and for the computation of Spearman’s rank correlation. [ r , tieadj ] = tiedrank ( x , 1) computes the ranks of the values in the vector x . tieadj is a vector of three adjustments for ties required in the computation of Kendall’s tau. tiedrank ( x , 0) is the same as tiedrank ( x ) . [ r , tieadj ] = tiedrank ( x , 0, 1) computes the ranks from each end, so that the smallest and largest values get rank 1, the next smallest and largest get rank 2, etc. These ranks are used in the Ansari-Bradley test. [ r , tieadj ] = tiedrank ( x , tieflag , bidir , tol ) treats two values as tied when they lie within a tolerance of each other rather than only when they are exactly equal. tol is either a scalar or an array the size of x giving each element its own tolerance, and two neighbouring values are tied when the gap between them does not exceed the sum of their two tolerances. The default is 0 , which is exact comparison. signrank uses this to rank differences that are equal to within the precision of the values they came from. # name: # type: sq_string # elements: 1 # length: 30 x may be a vector or an array. statistics-release-1.9.2/inst/Data_Manipulation/dummyvar.m000066400000000000000000000171151524624707500237440ustar00rootroot00000000000000## Copyright (C) 2025 Jayant Chauhan <0001jayant@gmail.com> ## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{D} =} dummyvar (@var{group}) ## ## Create dummy variables. ## ## @code{@var{D} = dummyvar (@var{group})} returns a matrix @var{D} containing ## the dummy variables associated with the grouping variables in @var{group}. ## Each row in @var{D} corresponds to the same observation across all variables ## in @var{group} and each column in @var{D} corresponds to a separate dummy ## variable. @var{D} is a numeric matrix of @qcode{double} data type containing ## ones and zeros. ## ## The grouping variable in @var{group} can be specified in one of the following ## options: ## ## @itemize ## @item a positive integer vector representing the different group levels in ## the ordered range @code{1:max (@var{group})}. ## ## @item a positive integer matrix with each column corresponding to a separate ## grouping variable and the integer values representing the group levels within ## that grouping variable in the ordered range @code{1:max (@var{group})}. ## ## @item a @qcode{categorical} column vector, in which case the number and order ## of columns in @var{D} correspond to the categories returned by ## @code{categories (@var{group})}. Categories that are defined but not present ## in @var{group} produce columns of zeros. Elements of @var{group} that are ## @code{} result in rows of @code{NaN} values in @var{D}. ## ## @item a @qcode{cell} array with its elements containing grouping variables ## specified as any of the above options. Note that all grouping variables in ## the cell array must have the same number of observations. ## @end itemize ## ## @seealso{tabulate, grp2idx, grpstats} ## @end deftypefn function D = dummyvar (g) if (nargin < 1) print_usage; endif [nr, nc] = size (g); ## --- CATEGORICAL branch --- if (isa (g, 'categorical')) if (! isvector (g) || nc != 1) error (strcat ("dummyvar: categorical grouping", ... " variable must be a column vector.")); endif g_cats = cellstr (categories (g)); g_cstr = cellstr (g(:)); K = numel (g_cats); D = zeros (nr, K); for i = 1:nr if (isundefined (g(i))) D(i,:) = NaN; else for k = 1:K if (strcmp (g_cstr{i}, g_cats{k})) D(i,k) = 1; break; endif endfor endif endfor ## --- NUMERIC branch --- elseif (isnumeric (g)) if (ndims (g) != 2) error (strcat ("dummyvar: numeric grouping variable", ... " must be either a vector or a matrix.")); endif if (any (g(:) <= 0) || any (g(:) != fix (g(:)))) error (strcat ("dummyvar: numeric grouping variable", ... " must explicitly contain positive integers.")); endif ## Force vector to column vector if (isvector (g) && nc > 1) g = g(:); nr = nc; nc = 1; endif K = max (g, [], 1); D = zeros (nr, sum (K)); ij = 0; for i = 1:nc tmp = g(:,i); for j = 1:K(i) ij++; D(tmp == j, ij) = 1; endfor endfor ## --- CELLSTRING branch --- elseif (iscellstr (g) && isvector (g)) if (! isvector (g) || nc != 1) error (strcat ("dummyvar: cellstring grouping", ... " variable must be a column vector.")); endif g = grp2idx (g); K = max (g); D = zeros (length (g), K); for i = 1:K D(g == i, i) = 1; endfor ## --- CELL ARRAY branch --- elseif (iscell (g) && isvector (g)) if (any (diff (cellfun (@(x) size (x, 1), g)))) error (strcat ("dummyvar: all grouping variables in cell array", ... " must have the same number of observations.")); endif D = []; for i = 1:numel (g) g_var = g{i}; D_var = dummyvar (g_var); D = [D, D_var]; endfor else error ("dummyvar: unsupported type of grouping variable."); endif endfunction ## Test output %!assert_equal (dummyvar ([]), []) %!assert_equal (dummyvar (ones (2, 0)), ones (2, 0)) %!test %! ## numeric grouping vector %! g = [1; 2; 1; 3; 2]; %! D = dummyvar (g); %! assert_equal (D, [1, 0, 0; 0, 1, 0; 1, 0, 0; 0, 0, 1; 0, 1, 0]); %!test %! g = categorical ({'a'; 'b'; 'a'}, {'a', 'b', 'c'}); %! D = dummyvar (g); %! cats = categories (g); %! g_str = cellstr (g); %! for k = 1:numel (cats) %! mask = strcmp (g_str, cats{k}); %! assert_equal (all (D(mask, k) == 1), true); %! assert_equal (all (D(! mask, k) == 0), true); %! endfor %!test %! g = categorical ({'a'; ''; 'b'}, {'a', 'b', 'c'}); %! D = dummyvar (g); %! assert_equal (D, [1, 0, 0; NaN, NaN, NaN; 0, 1, 0]); %!test %! colors = categorical ({'Red'; 'Blue'; 'Green'; 'Red'; 'Green'; 'Blue'}); %! D = dummyvar (colors); %! assert_equal (D, [0, 0, 1; 1, 0, 0; 0, 1, 0; 0, 0, 1; 0, 1, 0; 1, 0, 0]); %!test %! g1 = [1; 1; 1; 1; 2; 2; 2; 2]; %! g2 = [1; 2; 3; 1; 2; 3; 1; 2]; %! D = dummyvar ([g1, g2]); %! D1 = [1, 0, 1, 0, 0; 1, 0, 0, 1, 0; 1, 0, 0, 0, 1; 1, 0, 1, 0, 0; ... %! 0, 1, 0, 1, 0; 0, 1, 0, 0, 1; 0, 1, 1, 0, 0; 0, 1, 0, 1, 0]; %! assert_equal (D, D1); %!test %! phone = {'mob'; 'land'; 'mob';'mob';'mob';'land';'land'}; %! codes = categorical ([202; 202; 103; 103; 202; 103; 202]); %! D = dummyvar ({phone, codes}); %! D1 = [1, 0, 0, 1; 0, 1, 0, 1; 1, 0, 1, 0; 1, 0, 1, 0; ... %! 1, 0, 0, 1; 0, 1, 1, 0; 0, 1, 0, 1]; %! assert_equal (D, D1); %!test %! colors = {'red'; 'blue'; 'red'; 'green'; 'yellow'; 'blue'}; %! D = dummyvar (categorical (colors)); %! D1 = [0, 0, 1, 0; 1, 0, 0, 0; 0, 0, 1, 0; 0, 1, 0, 0; 0, 0, 0, 1; 1, 0, 0, 0]; %! assert_equal (D, D1); %!test %! colors = {'red'; 'blue'; 'red'; 'green'; 'yellow'; 'blue'}; %! D = dummyvar (colors); %! D1 = [1, 0, 0, 0; 0, 1, 0, 0; 1, 0, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1; 0, 1, 0, 0]; %! assert_equal (D, D1); %! D = dummyvar ({colors}); %! D1 = [1, 0, 0, 0; 0, 1, 0, 0; 1, 0, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1; 0, 1, 0, 0]; %! assert_equal (D, D1); %!test %! g = [1, 2, 1, 2, 1, 3, 2, 1]; %! D = dummyvar (g); %! D1 = [1, 0, 0; 0, 1, 0; 1, 0, 0; 0, 1, 0; 1, 0, 0; 0, 0, 1; 0, 1, 0; 1, 0, 0]; %! assert_equal (D, D1); ## Test input validation %!error dummyvar () %!error dummyvar (1, 2) %!error ... %! dummyvar (categorical ({'a', 'b'})) %!error ... %! dummyvar (ones (3, 3, 3)) %!error ... %! dummyvar ([2, 4, 0, 8, 1]) %!error ... %! dummyvar ({'a', 'b'}) %!error ... %! dummyvar ({[2;3;4;5], [1;2;3]}) %!error dummyvar ([true; false]) statistics-release-1.9.2/inst/Data_Manipulation/fillmissing.m000066400000000000000000003554371524624707500244340ustar00rootroot00000000000000## Copyright (C) 1995-2026 The Octave Project Developers ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{B} =} fillmissing (@var{A}, "constant", @var{v}) ## @deftypefnx {statistics} {@var{B} =} fillmissing (@var{A}, @var{method}) ## @deftypefnx {statistics} {@var{B} =} fillmissing (@var{A}, @var{move_method}, @var{window_size}) ## @deftypefnx {statistics} {@var{B} =} fillmissing (@var{A}, @var{fill_function}, @var{window_size}) ## @deftypefnx {statistics} {@var{B} =} fillmissing (@dots{}, @var{dim}) ## @deftypefnx {statistics} {@var{B} =} fillmissing (@dots{}, @var{PropertyName}, @var{PropertyValue}) ## @deftypefnx {statistics} {[@var{B}, @var{idx}] =} fillmissing (@dots{}) ## ## Fill missing data in arrays. ## ## Replace missing entries of array @var{A} either with values in @var{v} or ## as determined by other specified methods. 'missing' values are determined ## by the data type of @var{A} as identified by the function @ref{ismissing}, ## currently defined as: ## ## Standard missing values and their corresponding data types are: ## ## @itemize ## @item @qcode{NaN} - for @qcode{double}, @qcode{single}, @qcode{duration}, and ## @qcode{calendarDuration} arrays. ## @item @qcode{NaT} - for @qcode{datetime} arrays. ## @item @qcode{} - for @qcode{string} arrays. ## @item @qcode{} - for @qcode{categorical} arrays. ## @item @qcode{@{0x0 char@}} - for @qcode{cell} arrays of character vectors. ## @end itemize ## ## For any data types that do not support missing values, @code{ismissing} ## returns @code{@var{TF} = false (size (@var{A}))}. ## ## @var{A} can be a numeric scalar or array, a character vector or array, or ## a cell array of character vectors (a.k.a. string cells). ## ## @var{v} can be a scalar or an array containing values for replacing the ## missing values in @var{A} with a compatible data type for insertion into ## @var{A}. The shape of @var{v} must be a scalar or an array with number ## of elements in @var{v} equal to the number of elements orthogonal to the ## operating dimension. E.g., if @code{size(@var{A})} = [3 5 4], operating ## along @code{dim} = 2 requires @var{v} to contain either 1 or 3x4=12 ## elements. ## ## If requested, the optional output @var{idx} will contain a logical array ## the same shape as @var{A} indicating with 1's which locations in @var{A} ## were filled. ## ## Alternate Input Arguments and Values: ## @itemize ## @item @var{method} - replace missing values with: ## @table @code ## ## @item next ## @itemx previous ## @itemx nearest ## next, previous, or nearest non-missing value (nearest defaults to next ## when equidistant as determined by @code{SamplePoints}.) ## ## @item linear ## linear interpolation of neighboring, non-missing values ## ## @item spline ## piecewise cubic spline interpolation of neighboring, non-missing values ## ## @item pchip ## 'shape preserving' piecewise cubic spline interpolation of neighboring, ## non-missing values ## @end table ## ## @item @var{move_method} - moving window calculated replacement values: ## @table @code ## ## @item movmean ## @itemx movmedian ## moving average or median using a window determined by @var{window_size}. ## @var{window_size} must be either a positive scalar value or a two element ## positive vector of sizes @w{@code{[@var{nb}, @var{na}]}} measured in the ## same units as @code{SamplePoints}. For scalar values, the window is ## centered on the missing element and includes all data points within a ## distance of half of @var{window_size} on either side of the window center ## point. Note that for compatibility, when using a scalar value, the backward ## window limit is inclusive and the forward limit is exclusive. If a ## two-element @var{window_size} vector is specified, the window includes all ## points within a distance of @var{nb} backward and @var{na} forward from the ## current element at the window center (both limits inclusive). ## @end table ## ## @item @var{fill_function} - custom method specified as a function handle. ## The supplied fill function must accept three inputs in the following order ## for each missing gap in the data: ## @table @var ## @item A_values - ## elements of @var{A} within the window on either side of the gap as ## determined by @var{window_size}. (Note these elements can include missing ## values from other nearby gaps.) ## @item A_locs - ## locations of the reference data, @var{A_values}, in terms of the default ## or specified @code{SamplePoints}. ## @item gap_locs - ## location of the gap data points that need to be filled in terms of the ## default or specified @code{SamplePoints}. ## @end table ## ## The supplied function must return a scalar or vector with the same number of ## elements in @var{gap_locs}. The required @var{window_size} parameter ## follows similar rules as for the moving average and median methods ## described above, with the two exceptions that (1) each gap is processed as a ## single element, rather than gap elements being processed individually, and ## (2) the window extended on either side of the gap has inclusive endpoints ## regardless of how @var{window_size} is specified. ## ## @item @var{dim} - specify a dimension for vector operation (default = ## first non-singeton dimension) ## ## A one-sided @var{window_size}, @qcode{[@var{nb}, 0]} or ## @qcode{[0, @var{na}]}, leaves the gap at the corresponding end of the data ## with no values in its window. The fill function is still called there, with ## empty value and location arguments, as MATLAB calls it: a fill function may ## depend only on the gap's own sample points. One that cannot take empty ## arguments is reported as such. ## ## Along a dimension in which @var{A} is singleton, every element is a vector ## of length one, so a missing value has no neighbour to be filled from and is ## returned unchanged. MATLAB fills it regardless, which is a defect there and ## not a convention this follows: it returns @code{[1, 1, 3, 1, 5]} for ## @code{fillmissing ([1, NaN, 3, NaN, 5], @@testfcn, 99, 3)}, inventing values ## from a window that contains nothing. Measured against R2024a, two releases ## after the behaviour was first recorded here. ## ## @item @var{PropertyName}-@var{PropertyValue} pairs ## @table @code ## @item SamplePoints ## @var{PropertyValue} is a vector of sample point values representing the ## sorted and unique x-axis values of the data in @var{A}. If unspecified, ## the default is assumed to be the vector @var{[1 : size (A, dim)]}. The ## values in @code{SamplePoints} will affect methods and properties that rely ## on the effective distance between data points in @var{A}, such as ## interpolants and moving window functions where the @var{window_size} ## specified for moving window functions is measured relative to the ## @code{SamplePoints}. ## ## @item EndValues ## Apply a separate handling method for missing values at the front or back of ## the array. @var{PropertyValue} can be: ## @itemize ## @item A constant scalar or array with the same shape requirements as @var{v}. ## @item @code{none} - Do not fill end gap values. ## @item @code{extrap} - Use the same procedure as @var{method} to fill the ## end gap values. ## @item Any valid @var{method} listed above except for @code{movmean}, ## @code{movmedian}, and @code{fill_function}. Those methods can only be ## applied to end gap values with @code{extrap}. ## @end itemize ## ## @item MissingLocations ## @var{PropertyValue} must be a logical array the same size as @var{A} ## indicating locations of known missing data with a value of @code{true}. ## (cannot be combined with MaxGap) ## ## @item MaxGap ## @var{PropertyValue} is a numeric scalar indicating the maximum gap length ## to fill, and assumes the same distance scale as the sample points. Gap ## length is calculated by the difference in locations of the sample points ## on either side of the gap, and gaps larger than MaxGap are ignored by ## @var{fillmissing}. (cannot be combined with MissingLocations) ## @end table ## @end itemize ## ## Compatibility Notes: ## @itemize ## @item ## Numerical and logical inputs for @var{A} and @var{v} may be specified ## in any combination. The output will be the same class as @var{A}, with the ## @var{v} converted to that data type for filling. Only @code{single} and ## @code{double} have defined 'missing' values, so except for when the ## @code{missinglocations} option specifies the missing value identification of ## logical and other numeric data types, the output will always be ## @code{@var{B} = @var{A}} with @code{@var{idx} = false(size(@var{A}))}. ## @item ## All interpolation methods can be individually applied to @code{EndValues}. ## @item ## @sc{Matlab}'s @var{fill_function} method currently has several ## inconsistencies with the other methods (tested against version 2022a), and ## Octave's implementation has chosen the following consistent behavior over ## compatibility: (1) a column full of missing data is considered part of ## @code{EndValues}, (2) such columns are then excluded from ## @var{fill_function} processing because the moving window is always empty. ## (3) operation in dimensions higher than 2 perform identically to operations ## in dims 1 and 2, most notable on vectors. ## @end itemize ## ## @seealso{ismissing, rmmissing, standardizeMissing} ## @end deftypefn function [A, idx_out] = fillmissing (A, varargin) if (nargin < 2 || nargin > 12) print_usage (); endif method = varargin{1}; if (ischar (method)) method = lower (method); elseif (! is_function_handle (method)) error ("fillmissing: second input must be a string or function handle."); endif sz_A = size (A); ndims_A = numel (sz_A); dim = []; missing_locs = []; endgap_method = []; endgap_locs = []; endgap_val = []; fill_vals = []; idx_flag = (nargout > 1); maxgap = []; missinglocations = false; reshape_flag = false; samplepoints = []; standard_samplepoints = true; v = []; if (idx_flag) idx_out = false (sz_A); endif ## Process input arguments. if (is_function_handle (method)) ## Verify function handle and window. if ((nargin < 3) || ! isnumeric (varargin{2}) || ... ! any (numel (varargin{2}) == [1, 2])) error (strcat ("fillmissing: fill function handle must be followed by", ... " a numeric scalar or two-element vector window size.")); elseif (nargin (method) < 3) error ("fillmissing: fill function must accept at least three inputs."); endif move_fcn = method; method = 'movfcn'; window_size = varargin{2}; next_varg = 3; else switch (method) case {'previous', 'next', 'nearest'} next_varg = 2; case {'linear', 'spline', 'pchip', 'makima'} next_varg = 2; if (! (isnumeric (A) || islogical (A))) error (strcat ("fillmissing: interpolation methods only", ... " valid for numeric input types.")); endif case 'constant' if ((nargin < 3)) error (strcat ("fillmissing: 'constant' method must be", ... " followed by a numeric scalar or array.")); endif v = varargin{2}; if (! (isscalar (v) || isempty (v))) v = v(:); endif if ((! ischar (v)) && isempty (v)) error ("fillmissing: a numeric fill value cannot be empty."); endif ## Type check v against A. if (iscellstr (A) && ischar (v) && ! iscellstr (v)) v = {v}; endif if ((! isempty (v)) && ... ((isnumeric (A) && ! (isnumeric (v) || islogical (v))) || ... (ischar (A) && ! ischar (v)) || ... (iscellstr (A) && ! (iscellstr (v))))) error ("fillmissing: fill value must be the same data type as 'A'."); endif ## v can't be size checked until after processing rest of inputs. next_varg = 3; case {'movmean', 'movmedian'} if (! (isnumeric (A) || islogical (A))) error (strcat ("fillmissing: 'movmean' and 'movmedian' methods", ... " only valid for numeric input types.")); endif if ((nargin < 3) || ! isnumeric (varargin{2}) || ... ! any (numel (varargin{2}) == [1, 2])) error (strcat ("fillmissing: moving window method must be", ... " followed by a numeric scalar or two-element vector.")); endif window_size = varargin{2}; next_varg = 3; otherwise error ("fillmissing: unknown fill method '%s'.", method); endswitch endif ## Process any more parameters. if (next_varg < nargin) ## Set dim. If specified, it is the only numeric option allowed next. if (isnumeric (varargin{next_varg})) dim = varargin{next_varg}; if (! (isscalar (dim) && (dim > 0))) error ("fillmissing: DIM must be a positive scalar."); endif next_varg++; else ## Default dim is first nonsingleton dimension of A. if (isscalar (A)) dim = 1; else dim = find (sz_A > 1, 1, 'first'); endif endif sz_A_dim = size (A, dim); ## Process any remaining inputs, must be name-value pairs. while (next_varg < nargin) propname = varargin{next_varg}; if (next_varg + 1 == nargin) ## Must be at least one more input with 1st containing value. error ("fillmissing: properties must be given as name-value pairs."); else propval = varargin{next_varg + 1}; next_varg = next_varg + 2; if (! ischar (propname)) error ("fillmissing: invalid parameter name specified."); else propname = lower (propname); endif ## Input validation for names and values. switch (propname) case 'samplepoints' ## val must be sorted, unique, numeric vector the same size ## as size(A,dim). if (! (isnumeric (propval) && isvector (propval) && (numel (propval) == sz_A_dim) && issorted (propval) && (numel (propval) == numel (unique (propval))))) error (strcat ("fillmissing: SamplePoints must be a sorted ", ... "non-repeating, numeric vector with %d", ... " elements."), sz_A_dim); endif samplepoints = propval(:); standard_samplepoints = all (diff (samplepoints, 1, 1) == 1); case 'endvalues' ## For numeric A, val must be numeric scalar, a numeric ## array with numel equal to the elements orthogonal to ## the dim or certain string methods. For non-numeric A, ## "constant" method is not valid. if (ischar (propval)) switch (lower (propval)) case {'extrap', 'previous', 'next', 'nearest', 'none', ... 'linear', 'spline', 'pchip', 'makima'} endgap_method = propval; otherwise error ("fillmissing: invalid EndValues method '%s'.", propval); endswitch elseif (isnumeric (propval)) if (! (isnumeric (A) || islogical (A))) error (strcat ("fillmissing: EndValues method 'constant'", ... " only valid for numeric arrays.")); endif endgap_method = 'constant'; endgap_val = propval; else error (strcat ("fillmissing: EndValues must be numeric", ... " or a valid method name.")); endif case 'missinglocations' if (! (isnumeric (A) || islogical (A) || isinteger (A) || ... ischar (A) || iscellstr (A))) error (strcat ("fillmissing: MissingLocations option is not", ... " compatible with data type '%s'."), class (A)); endif if (! isempty (maxgap)) error (strcat ("fillmissing: MissingLocations and MaxGap", ... " options cannot be used simultaneously.")); endif ## val must be logical array same size as A. if (! (islogical (propval) && isequal (sz_A, size (propval)))) error (strcat ("fillmissing: MissingLocations must be", ... " a logical array the same size as A.")); endif missinglocations = true; missing_locs = propval; case 'maxgap' ## val must be positive numeric scalar. if (! (isnumeric (propval) && isscalar (propval) && (propval > 0))) error ("fillmissing: MaxGap must be a positive numeric scalar."); endif if (! isempty (missing_locs)) error (strcat ("fillmissing: MissingLocations and MaxGap", ... " options cannot be used simultaneously.")); endif maxgap = propval; case {'replacevalues', 'datavariables'} error ("fillmissing: the '%s' option has not been implemented.", ... propname); otherwise error ("invalid parameter name '%s'.", propname); endswitch endif endwhile else ## No inputs after method. ## Set default dim. if (isscalar (A)) dim = 1; else dim = find (sz_A > 1, 1, 'first'); endif sz_A_dim = size (A, dim); endif ## Reduce calls to size and avoid overruns checking sz_A for high dims. if (dim > ndims_A) sz_A = [sz_A, ones(1, dim - ndims_A)]; ndims_A = numel (sz_A); endif ## Set defaults for any unspecified parameters. if (isempty (samplepoints)) samplepoints = [1 : sz_A_dim]'; endif if (isempty (missing_locs)) missing_locs = ismissing (A); endif ## endvalues treated separately from interior missing_locs. if (isempty (endgap_method) || strcmp (endgap_method, 'extrap')) endgap_method = method; if (strcmp (endgap_method, 'constant')) endgap_val = v; endif endif ## missingvalues option not compatible with some methods and inputs: if (isinteger (A) || islogical (A)) if (any (ismember (method, ... {'linear', 'spline', 'pchip', 'makima', 'movmean', 'movmedian'}))) error (strcat ("fillmissing: MissingLocations cannot be used", ... " with method '%s' and inputs of type '%s'."), ... method, class (A)); elseif (any (ismember (endgap_method, ... {'linear', 'spline', 'pchip', 'makima'}))) error (strcat ("fillmissing: MissingLocations cannot be used with", ... " EndValues method '%s' and inputs of type '%s'."), ... method, class (A)); endif endif ## Verify size of v and endgap_val for 'constant' methods, resize for A. orthogonal_size = [sz_A(1:dim-1), 1, sz_A(dim+1:end)]; # orthog. to dim size. numel_orthogonal = prod (orthogonal_size); # numel perpen. to dim. if (strcmp (method, 'constant') && (! isscalar (v))) if (numel (v) != numel_orthogonal) error (strcat ("fillmissing: fill value 'V' must be a scalar or", ... " a %d element array."), numel_orthogonal); else v = reshape (v, orthogonal_size); endif endif if (strcmp (endgap_method, 'constant') && (! isscalar (endgap_val))) if (numel (endgap_val) != numel_orthogonal) error (strcat ("fillmissing: EndValues must be a scalar or a %d", ... " element array."), numel_orthogonal); else endgap_val = reshape (endgap_val, orthogonal_size); endif endif ## Simplify processing by temporarily permuting A so operation always on dim1. ## Revert permutation at the end. dim_idx_perm = [1 : ndims_A]; dim_idx_flip(1 : max (dim, ndims_A)) = {':'}; dim_idx_flip(1) = [sz_A_dim:-1:1]; if (dim != 1) dim_idx_perm([1, dim]) = [dim, 1]; A = permute (A, dim_idx_perm); sz_A([1, dim]) = sz_A([dim, 1]); missing_locs = permute (missing_locs, dim_idx_perm); reshape_flag = true; orthogonal_size = [1, sz_A(2:end)]; if (idx_flag) idx_out = false (sz_A); endif if (! isempty (v) && ! isscalar (v)) v = permute (v, dim_idx_perm); endif if (! isempty (endgap_val) && ! isscalar (endgap_val)) endgap_val = permute (endgap_val, dim_idx_perm); endif endif ## Precalculate fill data for several methods. zero_padding = zeros (orthogonal_size); samplepoints_expand = samplepoints(:, ones (1, prod (sz_A(2:end)))); ## Find endgap locations. if (sz_A_dim < 3) ## All missing are endgaps. endgap_locs = missing_locs; else ## Use cumsums starting from first and last part in dim to find missing ## values in and adjacent to end locations. endgap_locs = cumprod (missing_locs,1) | ... (cumprod (missing_locs(dim_idx_flip{:}),1))(dim_idx_flip{:}); endif ## Remove endgap_locs from missing_locs to avoid double processing. missing_locs(endgap_locs) = false; ## Remove elements from missing and end location arrays if maxgap is specified. if (! isempty (maxgap)) ## missing_locs: If samplepoints value diff on either side of missing ## elements is > maxgap, remove those values. ## For endgaps, use diff of inside and missing end samplepoint values ## and remove from endgaps. ## First check gapsize of any interior missings in missing_locs. if (any (missing_locs(:))) ## Locations in front of gaps loc_before = [diff(missing_locs,1,1); zero_padding] == 1; ## Locations in back of gaps loc_after = diff ([zero_padding; missing_locs],1,1) == -1; ## Value of samplepoints at front and back locs sampvals_before = samplepoints_expand(loc_before); sampvals_after = samplepoints_expand(loc_after); ## Evaluate which gaps are too big to fill. gaps_to_remove = (sampvals_after - sampvals_before) > maxgap; ## Convert those gaps into an array element list. idxs_to_remove = arrayfun ('colon', ... ((find (loc_before))(gaps_to_remove ) + 1), ... ((find (loc_after))(gaps_to_remove ) - 1), ... 'UniformOutput', false); ## Remove those elements from missing_locs. missing_locs([idxs_to_remove{:}]) = false; endif ## Then do any endgaps. if (any (endgap_locs(:))) ## If any are all missing, remove for any value of maxgap. endgap_locs &= ! prod (endgap_locs, 1); if ((sz_A_dim < 3) && (abs (samplepoints(2) - samplepoints(1)) > maxgap)) ## Shortcut - all missings are ends and exceed maxgap. endgap_locs(:) = false; else ## Check gap size of front endgaps. ## Find loc element after gap. nextvals = sum (cumprod (endgap_locs,1)) + 1; ## Compare diff between values at those points and at base with maxgap. ends_to_remove = abs (samplepoints(nextvals) - samplepoints(1)) ... > maxgap; ## Remove any with gap>maxgap. endgap_locs((cumprod (endgap_locs,1)) & ends_to_remove) = false; ## Flip, repeat for back endgaps, then unflip and remove. nextvals = sum (cumprod (endgap_locs(dim_idx_flip{:}),1)) + 1; ends_to_remove = abs (samplepoints(end:-1:1)(nextvals) ... - samplepoints(end)) > maxgap; endgap_locs((cumprod (... endgap_locs(dim_idx_flip{:}), 1)(dim_idx_flip{:})) & ... ends_to_remove) = false; endif endif endif if (any (strcmp (endgap_method, {'movmean', 'movmedian', 'movfcn'}))) ## These methods only called for endgaps with "extrap", so endgaps ## are processed together in the missing_locs section. missing_locs = missing_locs | endgap_locs; endgap_locs(:) = false; endif ## Actually fill the missing data. ## Process central missing values (all gaps bound by two valid datapoints). ## For each method, calculate fill_vals, which will be used in assignment ## A(missing_locs) = fill_vals, and if idx_flag, populate idx_out. if (any (missing_locs(:))) switch (method) case 'constant' if (isscalar (v)) fill_vals = v; else fill_vals = (missing_locs .* v)(missing_locs); endif if (idx_flag) ## If any v are the missing type, those get removed from idx_out ## unless using 'missinglocations'. if ((! missinglocations) && any (miss_v = ismissing (v))) idx_out(missing_locs) = true; idx_out(missing_locs & miss_v) = false; else idx_out(missing_locs) = true; endif endif case {'previous', 'next', 'nearest', 'linear'} ## Find element locations bounding each gap. loc_before = [diff(missing_locs, 1, 1); zero_padding] == 1; loc_after = diff ([zero_padding; missing_locs], 1, 1) == -1; gapsizes = find (loc_after) - find (loc_before) - 1; gap_count_idx = [1 : numel(gapsizes); gapsizes']; switch (method) case 'previous' fill_vals = repelems (A(loc_before), gap_count_idx)'; case 'next' fill_vals = repelems (A(loc_after), gap_count_idx)'; case {'nearest', 'linear'} ## Determine which missings go with values before or after ## gap based on samplevalue distance. (Equal dist goes to after.) ## Find sample values before and after gaps. sampvals_before = samplepoints_expand(loc_before); sampvals_after = samplepoints_expand(loc_after); ## Build cell with linear indices of elements in each gap. gap_locations = arrayfun ('colon', (find (loc_before)) + 1, ... (find (loc_after)) - 1, 'UniformOutput', false); ## Get sample values at those elements. [sampvals_in_gaps, ~] = ind2sub (sz_A, [gap_locations{:}]); sampvals_in_gaps = samplepoints(sampvals_in_gaps); ## Expand first and last vectors for each gap point. Avals_before = repelems (A(loc_before), gap_count_idx)'; Avals_after = repelems (A(loc_after), gap_count_idx)'; switch (method) case 'nearest' ## Calculate gap mid point for each gap element. sampvals_midgap = repelems ( ... (sampvals_before + sampvals_after)/2, gap_count_idx)'; ## Generate fill vectors sorting elements into nearest before ## or after. prev_fill = (sampvals_in_gaps < sampvals_midgap); next_fill = (sampvals_in_gaps >= sampvals_midgap); fill_vals = A(missing_locs); fill_vals(prev_fill) = Avals_before(prev_fill); fill_vals(next_fill) = Avals_after(next_fill); case 'linear' ## Expand samplepoint values for interpolation x-values. sampvals_before = repelems (sampvals_before, gap_count_idx)'; sampvals_after = repelems (sampvals_after, gap_count_idx)'; ## Linearly interpolate: fill_vals = ((Avals_after - Avals_before) ... ./ (sampvals_after - sampvals_before)) ... .* (sampvals_in_gaps - sampvals_before) ... + Avals_before; endswitch endswitch if (idx_flag) ## Mid gaps will always be filled by above methods. idx_out(missing_locs) = true; endif case {'spline', 'pchip', 'makima'} ## Pass more complex interpolations to interp1. ## FIXME: vectorized 'linear' is ~10-100x faster than using interp1. ## Look to speed these up as well. ## Identify columns needing interpolation to reduce empty operations. cols_to_use = any (missing_locs, 1); ## missinglocations may send columns with NaN and less than 2 ## real values resulting in interp1 error. Trim those columns, ## pre-populate fill_vals with NaN, mark as filled. if (missinglocations) fill_vals = NaN (sum (missing_locs(:, cols_to_use)(:)), 1); cols_enough_points = (sum ( ... ! isnan (A) & (! missing_locs), 1) > 1) & cols_to_use; interp_cols = (cols_enough_points & cols_to_use); interp_vals = (missing_locs & cols_enough_points)(missing_locs & ... cols_to_use); fill_vals(interp_vals) = other_interpolants (A(:, interp_cols), missing_locs(:, interp_cols), endgap_locs(:, interp_cols), ... method, samplepoints); else fill_vals = other_interpolants (A(:, cols_to_use), missing_locs(:, cols_to_use), endgap_locs(:, cols_to_use), ... method, samplepoints); endif if (idx_flag) idx_out(missing_locs) = true; endif case {'movmean', 'movmedian'} ## Check window size versus smallest sample gaps. If window smaller, ## nothing to do, break out early. if ((isscalar (window_size) && ... (window_size/2 >= min (diff (samplepoints)))) || ... (isvector (window_size) && (sum (window_size) >= min (diff (samplepoints))))) switch (method) case 'movmean' if (sz_A_dim > 1) allmissing = (missing_locs | endgap_locs)(:,:); ## Create temporary flattened array for processing, A_sum = A(:,:); A_sum(allmissing) = 0; if (standard_samplepoints && ... all (round (window_size) == window_size)) ## Window size based on vector elements. ## Faster codepath for uniform, unit-spacing samplepoints ## and integer valued window sizes. if (isscalar (window_size)) window_width = window_size; if (mod (window_size, 2)) ## Odd window size: ## Equal number of values on either side of gap. window_size = (window_width - 1) .* [0.5, 0.5]; else ## Even window size: ## One extra element on previous side of gap. window_size(1) = window_width/2; window_size(2) = window_size(1) - 1; endif else window_width = window_size(1) + window_size(2) + 1; endif ## Use columnwise convolution of windowing vector and A for ## vectorized summation. conv_vector = ones (window_width, 1); A_sum = convn (A_sum, conv_vector, ... 'full')(1 + window_size(2):end - window_size(1), :); ## Get count of values contributing to convolution to account ## for missing elements and to calculate mean. A_sum_count = convn (! allmissing, conv_vector, ... 'full')(1 + window_size(2):end - window_size(1), :); else ## Window size based on sample point distance. Works for non ## integer, non uniform values. ## Use A_sum (flattened to 2D), project slice windows in dim3 ## automatic broadcasting to get window summations & counts samplepoints_shift = ... samplepoints(:, ones (1, sz_A_dim)) - samplepoints'; if (isscalar (window_size)) ## [nb, na) window_size = window_size * [-0.5, 0.5]; samplepoints_slice_windows = permute (... samplepoints_shift >= window_size(1) & ... samplepoints_shift < window_size(2), [1,3,2]); else ## [nb, na] window_size(1) = -window_size(1); samplepoints_slice_windows = permute (... samplepoints_shift >= window_size(1) & ... samplepoints_shift <= window_size(2), [1,3,2]); endif if (missinglocations) ## NaNs left in A_sum will cause all sums to produce NaN ## FIXME: when sum can handle nanflag, the 'else' path ## should be able to be made to handle the vectorized ## summation even with 'missinglocations'. A_nan = isnan (A_sum); A_temp = A_sum .* samplepoints_slice_windows; A_temp(! samplepoints_slice_windows & A_nan) = 0; A_sum = permute (sum (A_temp, 1), [3,2,1]); else A_sum = permute (... sum (A_sum .* samplepoints_slice_windows, 1), [3,2,1]); endif A_sum_count = permute (... sum (! allmissing & samplepoints_slice_windows, 1), ... [3,2,1]); endif ## Build fill values. fill_vals = A(missing_locs); # Prefill to include missing vals. fillable_gaps = missing_locs(:,:) & A_sum_count; fill_vals(fillable_gaps(missing_locs(:,:))) = ... A_sum(fillable_gaps) ./ A_sum_count(fillable_gaps); endif case 'movmedian' if (sz_A_dim > 1) if (missinglocations) ## Median assumes empty locs have NaN. Missinglocations ## may point to a non-NaN number that will be assumed valid. ## Replace with NaNs. A(missing_locs) = NaN; endif cols_to_use = any (missing_locs(:,:), 1); samplepoints_shift = ... samplepoints(:, ones (1, sz_A_dim)) - samplepoints'; if (isscalar (window_size)) window_size = window_size * [-0.5, 0.5]; ## [nb, na) samplepoints_slice_windows = permute (... samplepoints_shift >= window_size(1) & ... samplepoints_shift < window_size(2), [1,3,2]); else window_size(1) = -window_size(1); ## [nb, na] samplepoints_slice_windows = permute (... samplepoints_shift >= window_size(1) & ... samplepoints_shift <= window_size(2), [1,3,2]); endif ## Use moving window slices to project A and use ## custom function for vectorized full array median computation. A_med = A(:, cols_to_use); nan_slice_windows = double (samplepoints_slice_windows); nan_slice_windows(! samplepoints_slice_windows) = NaN; A_med_slices = A_med .* nan_slice_windows; A_med = permute (columnwise_median (A_med_slices), [3 2 1]); fillable_gaps = missing_locs(:, cols_to_use); fill_vals = A_med(fillable_gaps); endif endswitch if (idx_flag) ## Matlab compatibility - NaNs filled back in by movmean and ## movmedian should _not_ show as filled. idx_out(fillable_gaps) = true; still_nan = missing_locs; still_nan(missing_locs) = isnan (fill_vals); idx_out(still_nan) = false; endif endif case 'movfcn' ## For each gap construct: ## xval - data values in window on either side of gap, including ## other missing values ## xloc - sample point values for those xval ## gap_loc - sample point values for gap elements ## If window has xval fully empty skip processing gap. ## missing_locs might include endgap_locs. ## Need to build gap locations accounting for both types. ## Missing values can include more than just numeric inputs. ## Windows containing no data points (e.g. endgaps when the window is ## one sided, [3 0] or [0 2]) are still passed to the mov_fcn, with ## empty value and location vectors, as MATLAB passes them: the fill ## function may depend only on the gap's own location. if (isscalar (window_size)) window_size = window_size * [-0.5, 0.5]; else window_size(1) = -window_size(1); endif ## Midgap bounds loc_before = [diff(missing_locs, 1, 1); zero_padding] == 1; loc_after = diff ([zero_padding; missing_locs], 1, 1) == -1; ## Front/back endgap locations and bounds front_gap_locs = logical (cumprod (missing_locs, 1)); front_next_locs = diff ([zero_padding; front_gap_locs], 1, 1) == -1; back_gap_locs = logical ( ... cumprod (missing_locs(dim_idx_flip{:}), 1)(dim_idx_flip{:})); back_prev_locs = [diff(back_gap_locs, 1, 1); zero_padding] == 1; ## Remove gap double counting. back_gap_locs &= ! front_gap_locs; loc_before &= ! back_prev_locs; loc_after &= ! front_next_locs; ## Build gap location array using gap starts and lengths. ## Simplest to use front / mid / back ordering, track later with sort. gap_start_locs = ... [find(front_gap_locs & [true; false(sz_A_dim-1,1)])(:); ... find(circshift (loc_before, 1, 1))(:); find(circshift (back_prev_locs, 1, 1))(:)]; gapsizes = [(sum (front_gap_locs, 1))(any (front_gap_locs, 1))(:);... find(loc_after) - find(loc_before) - 1;... (sum (back_gap_locs, 1))(any (back_gap_locs, 1))(:)]; ## Separate arrayfun/cellfun faster than single fun with ## composite anonymous function. gap_locations = arrayfun ('colon', gap_start_locs, ... gap_start_locs + gapsizes - 1, 'UniformOutput', false); gap_locations = cellfun ('transpose', ... gap_locations, 'UniformOutput', false); ## Sorting index to bridge front-mid-back and linear index ordering. [~, gap_full_sort_idx] = sort (vertcat (gap_locations{:})); ## Remove front or back gaps from gapsizes & gap_locations. ## If front/back window size = 0, or if full column is missing. ## Index to track empty/removed elements. removed_element_idx = true (numel (gap_full_sort_idx), 1); removed_front_elements = 0; removed_back_elements = 0; ## Simple front/back gap trimming for either window size = 0. if (any (missing_col_gaps = (gapsizes == sz_A_dim))) missing_col_elements = ... repelems (missing_col_gaps, [1:numel(gapsizes); gapsizes'])'; removed_element_idx(missing_col_elements) = false; gap_locations(missing_col_gaps) = []; gapsizes(missing_col_gaps) = []; endif if (! isempty (gapsizes)) gap_sample_values = cellfun_subsref (gap_locations, false, ... {samplepoints_expand}); ## Build [row,column] locations array for windows around each gap. window_points_r_c = cell (numel (gapsizes), 2); window_points_r_c(:,1) = cellfun (@(x) ... ([1:sz_A_dim]')((samplepoints= ... max (x(1) + window_size(1), samplepoints(1))) | ... (samplepoints>x(end) & samplepoints <= ... min (x(end) + window_size(2), samplepoints(end)))), ... gap_sample_values, 'UniformOutput', false); window_points_r_c(:,2) = cellfun ( ... @(x,y) (fix ((x(1)-1)/sz_A_dim)+1)(ones (size (y))), ... gap_locations, window_points_r_c(:,1), 'UniformOutput',false); if (! isempty (gapsizes)) ## Aval = A values at window locations ## Aloc = sample values at window locations A_window_indexes = cellfun ('sub2ind', {sz_A}, ... window_points_r_c(:,1), window_points_r_c(:,2), ... 'UniformOutput', false); Aval = cellfun_subsref (A_window_indexes, false, {A}); Aloc = cellfun_subsref (window_points_r_c(:,1), false, ... {samplepoints}); ## Build fill values. A window can be empty, when a one-sided ## window meets a gap at that end of the data, and the fill ## function is still called with empty values and locations, as ## MATLAB calls it. A function that cannot take them is reported ## as such rather than leaking whatever it raised. try fill_vals_C = cellfun (move_fcn, Aval, Aloc, ... gap_sample_values(:,1), 'UniformOutput', false); catch err if (any (cellfun ('isempty', Aval))) error (strcat ("fillmissing: invalid call to the fill", ... " function when its value and location", ... " arguments are empty.")); else rethrow (err); endif end_try_catch ## Check for output of move_fcn having different size than gaps. if (! all (cellfun ('numel', fill_vals_C) == gapsizes)) error (strcat ("fillmissing: fill function return values must be", " the same size as the gaps.")); endif [~, gap_trim_sort_idx] = sort (vertcat (gap_locations{:})); fill_vals_trim = cell2mat (fill_vals_C); if (! isempty (fill_vals_trim)) fill_vals = A(missing_locs); # prefill to include missing vals fill_vals(removed_element_idx(gap_full_sort_idx)) = ... fill_vals_trim(gap_trim_sort_idx); if (idx_flag) ## For movfcn with A of type having missing: ## Any outputs still containing class's 'missing' values ## are counted as not filled in idx_out, even if the value ## was put there by the movfcn. This is true even if ## missinglocations is used. If missinglocations changed ## a value with no apparent change, it still shows up ## as filled. ## If A has no missing value (int or logical), then without ## missinglocations, idx_out is always empty. With ## missinglocations, compatible behavior is undefined as ## Matlab 2022a has an apparent bug producing a error message ## saying missinglocations with int/logical needs a method that ## includes function handle. Expect behavior should match other ## methods, where any processed missing value should be marked ## as filled no matter the fill value. if ((isnumeric (A) && ! isinteger (A)) || ischar (A) || iscellstr (A)) idx_out(missing_locs) = ! ismissing (fill_vals); elseif (missinglocations) ## Any missing_locs processed and not skipped must become true. idx_out(missing_locs) = removed_element_idx(gap_full_sort_idx); endif endif endif endif endif endswitch if (! isempty (fill_vals)) A(missing_locs) = fill_vals; fill_vals = []; endif endif ## Process endgaps: if (any (endgap_locs(:))) switch (endgap_method) case 'none' endgap_locs(:) = false; case 'constant' if (isscalar (endgap_val)) fill_vals = endgap_val; else fill_vals = (endgap_locs .* endgap_val)(endgap_locs); endif if (idx_flag) ## If any v are the missing type, those get removed from idx_out ## unless using 'missinglocations'. idx_out(endgap_locs) = true; if (! missinglocations) && any (miss_ev = ismissing (endgap_val)) idx_out(endgap_locs & miss_ev) = false; endif endif case {'previous', 'next', 'nearest', 'linear', ... 'spline', 'pchip', 'makima'} ## All of these methods require sz_A_dim >= 2. shortcut path otherwise. if (sz_A_dim < 2) endgap_locs(:) = false; else switch (endgap_method) case 'previous' ## Remove any gaps at front of array, includes all-missing cols. endgap_locs(logical (cumprod (endgap_locs,1))) = false; if (any (endgap_locs(:))) ## Find locations of the 'prev' value to use for filling. subsval_loc = [diff(endgap_locs, 1, 1); zero_padding] == 1; ## Calculate the number of spots each 'prev' needs to fill. gapsizes = (sum (endgap_locs, 1))(any (endgap_locs, 1))(:); ## Construct substitution value vector. fill_vals = repelems (A(subsval_loc), ... [1:numel(gapsizes); gapsizes'])'; endif case 'next' ## Remove any gaps at back of array from endgap_locs ## including any all-missing columns. endgap_locs(logical (cumprod ( ... endgap_locs(dim_idx_flip{:}), 1)(dim_idx_flip{:}))) ... = false; if (any (endgap_locs(:))) ## Find locations of the 'next' value to use for filling. subsval_loc = diff ([zero_padding; endgap_locs],1,1) == -1; ## Calculate the number of spots each 'next' needs to fill. gapsizes = (sum (endgap_locs, 1))(any (endgap_locs, 1))(:); ## Construct substitution value vector. fill_vals = repelems (A(subsval_loc), ... [1:numel(gapsizes); gapsizes'])'; endif case 'nearest' ## Remove any all-missing columns. endgap_locs &= (! prod (endgap_locs, 1)); if (any (endgap_locs(:))) ## Find front end info. front_gap_locs = logical (cumprod (endgap_locs, 1)); front_next_loc = diff ( ... [zero_padding; front_gap_locs], 1, 1) == -1; front_gapsizes = (sum (front_gap_locs, 1))(any ... (front_gap_locs,1)); ## Find back end info. back_gap_locs = logical ( ... cumprod (endgap_locs(dim_idx_flip{:}), 1)(dim_idx_flip{:})); back_prev_loc = [diff(back_gap_locs, 1, 1); zero_padding] == 1; back_gapsizes = (sum (back_gap_locs, 1))(any (back_gap_locs,1)); ## Combine into fill variables. [~, fb_sort_idx] = sort ... ([find(front_gap_locs); find(back_gap_locs)]); fillval_loc = [find(front_next_loc); find(back_prev_loc)]; gapsizes = [front_gapsizes; back_gapsizes]; ## Construct substitution value vector with sort order to mix ## fronts and backs in column order. fill_vals = (repelems (A(fillval_loc), ... [1:numel(gapsizes); gapsizes'])')(fb_sort_idx); endif case 'linear' ## Endgaps not guaranteed to have enough points to interpolate. cols_to_use = (sum (! (missing_locs | endgap_locs), 1) > 1) ... & any (endgap_locs, 1); interp_locs = ! (missing_locs | endgap_locs) & cols_to_use; endgap_locs &= cols_to_use; if (any (endgap_locs(:))) ## Process front endgaps: front_gap_locs = logical (cumprod (endgap_locs, 1)); fill_vals_front = []; if (any (front_gap_locs(:))) front_gapsizes = (sum (front_gap_locs, 1))(any ... (front_gap_locs,1)); ## Collect first data point after gap & expand to gapsize. front_interppoint_1 = repelems ( find (... diff ([zero_padding; front_gap_locs], 1, 1) == -1), ... [1:numel(front_gapsizes); front_gapsizes'])'; ## Collect second data point after gap & expand to gapsize. front_interppoint_2 = repelems ( find ( ... diff ([zero_padding; ((cumsum (interp_locs, 1) .* ... any (front_gap_locs, 1)) == 2)], 1, 1) == 1), ... [1:numel(front_gapsizes); front_gapsizes'])'; front_interp_Avals = A([front_interppoint_1, ... front_interppoint_2]); front_interp_sampvals = samplepoints_expand( ... [front_interppoint_1, front_interppoint_2]); front_gap_loc_sampvals = samplepoints_expand(front_gap_locs); ## Hack for vector automatic orientation forcing col vector. if (isvector (front_interp_Avals)) front_interp_Avals = (front_interp_Avals(:)).'; endif if (isvector (front_interp_sampvals)) front_interp_sampvals = (front_interp_sampvals(:)).'; endif ## Perform interpolation for every gap point. interp_slopes_front = diff (front_interp_Avals, 1, 2) ... ./ diff (front_interp_sampvals, 1, 2); fill_vals_front = interp_slopes_front .* ... (front_gap_loc_sampvals - ... front_interp_sampvals(:,1)) + ... front_interp_Avals(:,1); endif ## Process back endgaps: back_gap_locs = logical ( ... cumprod (endgap_locs(dim_idx_flip{:}), 1)(dim_idx_flip{:})); fill_vals_back = []; if (any (back_gap_locs(:))) back_gapsizes = (sum ( ... back_gap_locs, 1))(any (back_gap_locs,1)); ## Collect last data point before gap & expand to gapsize. back_interppoint_2 = repelems ( ... find ([diff(back_gap_locs, 1, 1); zero_padding] == 1), ... [1:numel(back_gapsizes); back_gapsizes'])'; ## Collect 2nd to last data point before gap & expand to gap. back_interppoint_1 = repelems ( ... find ((diff ([zero_padding; ... ((cumsum (interp_locs(dim_idx_flip{:}), 1) .* ... any (back_gap_locs, 1)) == 2)], ... 1, 1) == 1)(dim_idx_flip{:})), ... [1:numel(back_gapsizes); back_gapsizes'])'; ## Build linear interpolant vectors. back_interp_Avals = A([back_interppoint_1, ... back_interppoint_2]); back_interp_sampvals = samplepoints_expand( ... [back_interppoint_1, back_interppoint_2]); back_gap_loc_sampvals = samplepoints_expand(back_gap_locs); ## Hack for vector automatic orientation forcing col vector. if (isvector (back_interp_Avals)) back_interp_Avals = (back_interp_Avals(:)).'; endif if (isvector (back_interp_sampvals)) back_interp_sampvals = (back_interp_sampvals(:)).'; endif ## Perform interpolation for every gap point. interp_slopes_back = diff (back_interp_Avals, 1, 2) ... ./ diff (back_interp_sampvals, 1, 2); fill_vals_back = interp_slopes_back .* ... (back_gap_loc_sampvals - ... back_interp_sampvals(:,1)) + ... back_interp_Avals(:,1); endif [~, fb_sort_idx] = sort ... ([find(front_gap_locs); find(back_gap_locs)]); fill_vals = [fill_vals_front; fill_vals_back](fb_sort_idx); endif case {'spline', 'pchip', 'makima'} ## endgap_locs not guaranteed to have 2 points. ## Need to ignore columns with < 2 values, or with nothing to do. cols_to_use = (sum (! (endgap_locs | missing_locs), 1) > 1) ... & any (endgap_locs, 1); ## Trim out unused cols from endgap_locs. endgap_locs &= cols_to_use; if (missinglocations) ## missinglocations may send columns with NaN and less than 2 ## real values resulting in interp1 error. Trim those columns, ## prepopulate fill_vals with NaN, mark as filled. fill_vals = NaN (sum (endgap_locs(:, cols_to_use)(:)), 1); cols_enough_points = (sum ( ... ! isnan (A) & (! endgap_locs), 1) > 1) & cols_to_use; interp_cols = (cols_enough_points & cols_to_use); interp_vals = (endgap_locs & ... cols_enough_points)(endgap_locs & cols_to_use); fill_vals(interp_vals) = other_interpolants ( ... A(:, interp_cols),endgap_locs(:, interp_cols), ... missing_locs(:, interp_cols), endgap_method, ... samplepoints); else fill_vals = other_interpolants ( A(:, cols_to_use), endgap_locs(:, cols_to_use), ... missing_locs(:, cols_to_use), endgap_method, samplepoints); endif endswitch endif if (idx_flag) idx_out(endgap_locs) = true; endif endswitch ## Some methods remove fill locations, only process if not empty. if (any (endgap_locs(:))) ## Replace missings with appropriate fill values. A(endgap_locs) = fill_vals; endif endif if (reshape_flag) ## Revert permutation: A = permute (A, dim_idx_perm); if (idx_flag) idx_out = permute (idx_out, dim_idx_perm); endif endif endfunction function varargout = cellfun_subsref (x, TF, varargin) ## Utility fcn for cellfun (@(x) A(x), x, "UniformOutput", true/false). ## ~50% faster than anonymous function call. ## pass A, x, and truefalse. ## If nargout > 1, repeat for C2 with B(x), C3 with C(x), etc. x_C = num2cell (struct ('type', '()', 'subs', num2cell (x))); for (idx = 1 : numel (varargin)) varargout{idx} = cellfun ('subsref', varargin{idx}, x_C, ... 'UniformOutput', TF); endfor endfunction function fill_vals = other_interpolants (data_array, primary_locs, secondary_locs, method, samplepoints) ## Use interp1 to perform more complex interpolations. Will only be performed ## on numerical data. ## primary_locs is missing_locs or endgap_locs, whichever the fill_vals are ## being returned for. secondary_locs is the other. ## ## FIXME: splitting out from columnwise cellfun to interp1 would increase ## speed a lot, but cannot count on same number of elements being processed ## in each column. ## ## Will error on any columns without at least two non-NaN interpolation ## values. ## ## Matlab incompatibility - using interp1, if 'missinglocations' sends through ## data_array values with NaN, interp1 will ignore those and interpolate with ## other data points. Matlab will instead return NaN. ## Find logical data and empty location indices for each column to be ## used in columnwise call to interp1 (cast as columnwise cell arrays). interp_data_locs = num2cell ((! (primary_locs | secondary_locs)), 1); interp_locs_tofill = num2cell (primary_locs, 1); ## Build cell arrays with sample and interp values to pass to interp1. [A_interpvalues, interp_samplevals] = cellfun_subsref (interp_data_locs, ... false, num2cell (data_array, 1), {samplepoints}); interp_empty_samplevals = cellfun_subsref (interp_locs_tofill, false, ... {samplepoints}); ## Generate fill_vals using interp1 for missing locations. if (strcmpi (method, 'makima')) ## Column-wise compute makima interpolation manually ncols = numel (A_interpvalues); col_results = cell (1, ncols); for k = 1:ncols xvals = interp_samplevals{k}; yvals = A_interpvalues{k}; xq = interp_empty_samplevals{k}; ## If not enough points → follow original behavior → NaN if (numel (xvals) < 2) col_results{k} = NaN (numel (xq), 1); continue; endif ## Call our new makima function (supports “extrap”) col_results{k} = makima (xvals(:), yvals(:), xq(:), 'extrap'); endfor fill_vals = vertcat (col_results{:}); else ## Default path (same as original) fill_vals = vertcat (cellfun ('interp1', interp_samplevals, ... A_interpvalues, interp_empty_samplevals, {method}, ... {'extrap'}, 'UniformOutput', false){:}); endif endfunction function med = columnwise_median (x) ## Take a column of values, ignore any NaN values, return the median ## of what's left. Return NaN if no values. ## Uses only built-in fns to avoid 3x 'median' slowdown. szx = size (x); if (isempty (x)) med = NaN ([1, szx(2:end)]); elseif (isvector (x)) x = x(! isnan (x)); x = sort (x); n = numel (x); if (mod (n, 2)) ## odd med = x((n+1)/2); elseif (n > 0) ## even med = (x(n/2) + x((n/2)+1))/2; else ## Only called for types with missing = NaN. med = NaN; endif else x = sort (x, 1); # NaNs sent to bottom. n = sum (! isnan (x), 1); m_idx_odd = logical (mod (n, 2)); # 0 even or zero, 1 odd. m_idx_even = !m_idx_odd & (n != 0); k = floor ((n + 1) ./ 2); med = NaN ([1, szx(2:end)]); if (! ismatrix (x)) szx = [szx(1), prod(szx(2:end))]; endif if (any (m_idx_odd(:))) x_idx_odd = sub2ind (szx, k(m_idx_odd)(:), find (m_idx_odd)(:)); med(m_idx_odd) = x(x_idx_odd); endif if (any (m_idx_even(:))) k_even = k(m_idx_even)(:); x_idx_even = sub2ind (szx, [k_even, k_even+1], ... (find (m_idx_even))(:, [1, 1])); med(m_idx_even) = sum (x(x_idx_even), 2) / 2; endif endif endfunction %!assert_equal (fillmissing ([1, 2, 3], 'constant', 99), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, NaN], 'constant', 99), [1, 2, 99]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'constant', 99), [99, 2, 99]) %!assert_equal (fillmissing ([1, 2, 3]', 'constant', 99), [1, 2, 3]') %!assert_equal (fillmissing ([1, 2, NaN]', 'constant', 99), [1, 2, 99]') %!assert_equal (fillmissing ([1, 2, 3; 4, 5, 6], 'constant', 99), [1, 2, 3; 4, 5, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'constant', 99), [1, 2, 99; 4, 99, 6]) %!assert_equal (fillmissing ([NaN, 2, NaN; 4, NaN, 6], 'constant', [97, 98, 99]), [97, 2, 99; 4, 98, 6]) %!test %! x = cat (3, [1, 2, NaN; 4, NaN, 6], [NaN, 2, 3; 4, 5, NaN]); %! y = cat (3, [1, 2, 99; 4, 99, 6], [99, 2, 3; 4, 5, 99]); %! assert_equal (fillmissing (x, 'constant', 99), y); %! y = cat (3, [1, 2, 96; 4, 95, 6], [97, 2, 3; 4, 5, 99]); %! assert_equal (fillmissing (x, 'constant', [94:99]), y); %! assert_equal (fillmissing (x, 'constant', [94:99]'), y); %! assert_equal (fillmissing (x, 'constant', permute ([94:99], [1 3 2])), y); %! assert_equal (fillmissing (x, 'constant', [94, 96, 98; 95, 97, 99]), y); %! assert_equal (fillmissing (x, 'constant', [94:99], 1), y); %! y = cat (3, [1, 2, 96; 4, 97, 6], [98, 2, 3; 4, 5, 99]); %! assert_equal (fillmissing (x, 'constant', [96:99], 2), y); %! y = cat (3, [1, 2, 98; 4, 97, 6], [94, 2, 3; 4, 5, 99]); %! assert_equal (fillmissing (x, 'constant', [94:99], 3), y); %! y = cat (3, [1, 2, 92; 4, 91, 6], [94, 2, 3; 4, 5, 99]); %! assert_equal (fillmissing (x, 'constant', [88:99], 99), y); %!test %! x = reshape ([1:24], 4, 3, 2); %! x([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = NaN; %! y = x; %! y([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = [94, 95, 95, 96, 96, 97, 97, 98, 99, 99]; %! assert_equal (fillmissing (x, 'constant', [94:99], 1), y); %! y([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = [92, 93, 94, 92, 95, 97, 99, 98, 97, 98]; %! assert_equal (fillmissing (x, 'constant', [92:99], 2), y); %! y([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = [88, 93, 94, 96, 99, 89, 91, 94, 97, 98]; %! assert_equal (fillmissing (x, 'constant', [88:99], 3), y); %! y([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = [76, 81, 82, 84, 87, 89, 91, 94, 97, 98]; %! assert_equal (fillmissing (x, 'constant', [76:99], 99), y); ## Tests with different endvalues behavior %!assert_equal (fillmissing ([1, 2, 3], 'constant', 99, 'endvalues', 88), [1, 2, 3]) %!assert_equal (fillmissing ([1, NaN, 3], 'constant', 99, 'endvalues', 88), [1, 99, 3]) %!assert_equal (fillmissing ([1, 2, NaN], 'constant', 99, 'endvalues', 88), [1, 2, 88]) %!assert_equal (fillmissing ([NaN, 2, 3], 'constant', 99, 'endvalues', 88), [88, 2, 3]) %!assert_equal (fillmissing ([NaN, NaN, 3], 'constant', 99, 'endvalues', 88), [88, 88, 3]) %!assert_equal (fillmissing ([1, NaN, NaN], 'constant', 99, 'endvalues', 88), [1, 88, 88]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'constant', 99, 'endvalues', 88), [88, 2, 88]) %!assert_equal (fillmissing ([NaN, 2, NaN]', 'constant', 99, 'endvalues', 88), [88, 2, 88]') %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'constant', 99, 'endvalues', 88), [1, 99, 3, 99, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'constant', 99, 'endvalues', 88), [1, 99, 99, 99, 5]) %!assert_equal (fillmissing ([NaN, NaN, NaN, NaN, 5], 'constant', 99, 'endvalues', 88), [88, 88, 88, 88, 5]) %!assert_equal (fillmissing ([1, NaN, 3, 4, NaN], 'constant', 99, 'endvalues', 88), [1, 99, 3, 4, 88]) %!assert_equal (fillmissing ([1, NaN, 3, 4, NaN], 'constant', 99, 1, 'endvalues', 88), [1, 88, 3, 4, 88]) %!assert_equal (fillmissing ([1, NaN, 3, 4, NaN], 'constant', 99, 1, 'endvalues', 'extrap'), [1, 99, 3, 4, 99]) %!test %! x = reshape ([1:24], 3, 4, 2); %! y = x; %! x([1, 2, 5, 6, 8, 10, 13, 16, 18, 19, 20, 21, 22]) = NaN; %! y([1, 2, 5, 6, 10, 13, 16, 18, 19, 20, 21, 22]) = 88; %! y([8]) = 99; %! assert_equal (fillmissing (x, 'constant', 99, 'endvalues', 88), y); %! assert_equal (fillmissing (x, 'constant', 99, 1, 'endvalues', 88), y); %! y = x; %! y([1, 2, 5, 8, 10, 13, 16, 19, 22]) = 88; %! y([6, 18, 20, 21]) = 99; %! assert_equal (fillmissing (x, 'constant', 99, 2, 'endvalues', 88), y); %! y(y == 99) = 88; %! assert_equal (fillmissing (x, 'constant', 99, 3, 'endvalues', 88), y); %! assert_equal (fillmissing (x, 'constant', 99, 4, 'endvalues', 88), y); %! assert_equal (fillmissing (x, 'constant', 99, 99, 'endvalues', 88), y); %! y([8]) = 94; %! assert_equal (fillmissing (x, 'constant', [92:99], 1, 'endvalues', 88), y); %! y([6, 8, 18, 20, 21]) = [96, 88, 99, 98, 99]; %! assert_equal (fillmissing (x, 'constant', [94:99], 2, 'endvalues', 88), y); %! y = x; %! y(isnan (y)) = 88; %! assert_equal (fillmissing (x, 'constant', [88:99], 3, 'endvalues', 88), y); %! y = x; %! y(isnan (y)) = [82, 82, 83, 83, 94, 85, 86, 87, 87, 88, 88, 88, 89]; %! assert_equal (fillmissing (x, 'constant', [92:99], 1, 'endvalues', [82:89]), y); %! y = x; %! y(isnan (y)) = [84, 85, 85, 96, 85, 84, 87, 87, 99, 87, 98, 99, 87]; %! assert_equal (fillmissing (x, 'constant', [94:99], 2, 'endvalues', [84:89]), y); %! y = x; %! y(isnan (y)) = [68, 69, 72, 73, 75, 77, 68, 71, 73, 74, 75, 76, 77]; %! assert_equal (fillmissing (x, 'constant', [88:99], 3, 'endvalues', [68:79]), y); %! assert_equal (fillmissing (x, 'constant', [88:93; 94:99]', 3, 'endvalues', [68:73; 74:79]'), y) %!test %! x = reshape ([1:24],4,3,2); %! x([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = NaN; %! y = x; %! y([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = [94, 95, 95, 96, 96, 97, 97, 98, 99, 99]; %! assert_equal (fillmissing (x, 'constant', [94:99], 1), y); %! y([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = [92, 93, 94, 92, 95, 97, 99, 98, 97, 98]; %! assert_equal (fillmissing (x, 'constant', [92:99], 2), y); %! y([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = [88, 93, 94, 96, 99, 89, 91, 94, 97, 98]; %! assert_equal (fillmissing (x, 'constant', [88:99], 3), y); %! y([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = [76, 81, 82, 84, 87, 89, 91, 94, 97, 98]; %! assert_equal (fillmissing (x, 'constant', [76:99], 99), y); ## next/previous tests %!assert_equal (fillmissing ([1, 2, 3], 'previous'), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, 3], 'next'), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, 3]', 'previous'), [1, 2, 3]') %!assert_equal (fillmissing ([1, 2, 3]', 'next'), [1, 2, 3]') %!assert_equal (fillmissing ([1, 2, NaN], 'previous'), [1, 2, 2]) %!assert_equal (fillmissing ([1, 2, NaN], 'next'), [1, 2, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'previous'), [NaN, 2, 2]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'next'), [2, 2, NaN]) %!assert_equal (fillmissing ([1, NaN, 3], 'previous'), [1, 1, 3]) %!assert_equal (fillmissing ([1, NaN, 3], 'next'), [1, 3, 3]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'previous', 1), [1, 2, NaN; 4, 2, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'previous', 2), [1, 2, 2; 4, 4, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'previous', 3), [1, 2, NaN; 4, NaN, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'next', 1), [1, 2, 6; 4, NaN, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'next', 2), [1, 2, NaN; 4, 6, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'next', 3), [1, 2, NaN; 4, NaN, 6]) %!test %! x = reshape ([1:24], 4, 3, 2); %! x([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = NaN; %! y = x; %! y([1, 6, 7, 9, 14, 19, 22, 23]) = [2, 8, 8, 10, 15, 20, 24, 24]; %! assert_equal (fillmissing (x, 'next', 1), y); %! y = x; %! y([1, 6, 7, 14, 16]) = [5, 10, 11, 18, 20]; %! assert_equal (fillmissing (x, 'next', 2), y); %! y = x; %! y([1, 6, 9, 12]) = [13, 18, 21, 24]; %! assert_equal (fillmissing (x, 'next', 3), y); %! assert_equal (fillmissing (x, 'next', 99), x); %! y = x; %! y([6, 7, 12, 14, 16, 19, 22, 23]) = [5, 5, 11, 13, 15, 18, 21, 21]; %! assert_equal (fillmissing (x, 'previous', 1), y); %! y = x; %! y([6, 7, 9, 12, 19, 22, 23]) = [2, 3, 5, 8, 15, 18, 15]; %! assert_equal (fillmissing (x, 'previous', 2), y); %! y = x; %! y([14, 16, 22, 23]) = [2, 4, 10, 11]; %! assert_equal (fillmissing (x, 'previous', 3), y); %! assert_equal (fillmissing (x, 'previous', 99), x); ## next/previous tests with different endvalue behavior %!assert_equal (fillmissing ([1, 2, 3], 'constant', 0, 'endvalues', 'previous'), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, 3], 'constant', 0, 'endvalues', 'next'), [1, 2, 3]) %!assert_equal (fillmissing ([1, NaN, 3], 'constant', 0, 'endvalues', 'previous'), [1, 0, 3]) %!assert_equal (fillmissing ([1, NaN, 3], 'constant', 0, 'endvalues', 'next'), [1, 0, 3]) %!assert_equal (fillmissing ([1, 2, NaN], 'constant', 0, 'endvalues', 'previous'), [1, 2, 2]) %!assert_equal (fillmissing ([1, 2, NaN], 'constant', 0, 'endvalues', 'next'), [1, 2, NaN]) %!assert_equal (fillmissing ([1, NaN, NaN], 'constant', 0, 'endvalues', 'previous'), [1, 1, 1]) %!assert_equal (fillmissing ([1, NaN, NaN], 'constant', 0, 'endvalues', 'next'), [1, NaN, NaN]) %!assert_equal (fillmissing ([NaN, 2, 3], 'constant', 0, 'endvalues', 'previous'), [NaN, 2, 3]) %!assert_equal (fillmissing ([NaN, 2, 3], 'constant', 0, 'endvalues', 'next'), [2, 2, 3]) %!assert_equal (fillmissing ([NaN, NaN, 3], 'constant', 0, 'endvalues', 'previous'), [NaN, NaN, 3]) %!assert_equal (fillmissing ([NaN, NaN, 3], 'constant', 0, 'endvalues', 'next'), [3, 3, 3]) %!assert_equal (fillmissing ([NaN, NaN, NaN], 'constant', 0, 'endvalues', 'previous'), [NaN, NaN, NaN]) %!assert_equal (fillmissing ([NaN, NaN, NaN], 'constant', 0, 'endvalues', 'next'), [NaN, NaN, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 0, 'endvalues', 'previous'), [NaN, 2, 0, 4, 4]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 0, 'endvalues', 'next'), [2, 2, 0, 4, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 0, 1, 'endvalues', 'previous'), [NaN, 2, NaN, 4, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 0, 1, 'endvalues', 'next'), [NaN, 2, NaN, 4, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 0, 2, 'endvalues', 'previous'), [NaN, 2, 0, 4, 4]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 0, 2, 'endvalues', 'next'), [2, 2, 0, 4, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 0, 3, 'endvalues', 'previous'), [NaN, 2, NaN, 4, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 0, 3, 'endvalues', 'next'), [NaN, 2, NaN, 4, NaN]) %!test %! x = reshape ([1:24], 3, 4, 2); %! x([1, 2, 5, 6, 8, 10, 13, 16, 18, 19, 20, 21, 22]) = NaN; %! y = x; %! y([5, 6, 8, 18]) = [4, 4, 0, 17]; %! assert_equal (fillmissing (x, 'constant', 0, 'endvalues', 'previous'), y); %! assert_equal (fillmissing (x, 'constant', 0, 1, 'endvalues', 'previous'), y); %! y = x; %! y([6, 10, 18, 20, 21]) = [0, 7, 0, 0, 0]; %! assert_equal (fillmissing (x, 'constant', 0, 2, 'endvalues', 'previous'), y); %! y = x; %! y([16, 19, 21]) = [4, 7, 9]; %! assert_equal (fillmissing (x, 'constant', 0, 3, 'endvalues', 'previous'), y); %! assert_equal (fillmissing (x, 'constant', 0, 4, 'endvalues', 'previous'), x); %! assert_equal (fillmissing (x, 'constant', 0, 99, 'endvalues', 'previous'), x); %! y = x; %! y([1, 2, 8, 10, 13, 16, 22]) = [3, 3, 0, 11, 14, 17, 23]; %! assert_equal (fillmissing (x, 'constant', 0, 'endvalues', 'next'), y); %! assert_equal (fillmissing (x, 'constant', 0, 1, 'endvalues', 'next'), y); %! y = x; %! y([1, 2, 5, 6, 8, 18, 20, 21]) = [4, 11, 11, 0, 11, 0, 0, 0]; %! assert_equal (fillmissing (x, 'constant', 0, 2, 'endvalues', 'next'), y); %! y = x; %! y([2, 5]) = [14, 17]; %! assert_equal (fillmissing (x, 'constant', 0, 3, 'endvalues', 'next'), y); %! assert_equal (fillmissing (x, 'constant', 0, 4, 'endvalues', 'next'), x); %! assert_equal (fillmissing (x, 'constant', 0, 99, 'endvalues', 'next'), x); ## Tests for nearest %!assert_equal (fillmissing ([1, 2, 3], 'nearest'), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, 3]', 'nearest'), [1, 2, 3]') %!assert_equal (fillmissing ([1, 2, NaN], 'nearest'), [1, 2, 2]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'nearest'), [2, 2, 2]) %!assert_equal (fillmissing ([1, NaN, 3], 'nearest'), [1, 3, 3]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'nearest', 1), [1, 2, 6; 4, 2, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'nearest', 2), [1, 2, 2; 4, 6, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'nearest', 3), [1, 2, NaN; 4, NaN, 6]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'nearest'), [1, 3, 3, 5, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'nearest', 'samplepoints', [0, 1, 2, 3, 4]), [1, 3, 3, 5, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'nearest', 'samplepoints', [0.5, 1, 2, 3, 5]), [1, 1, 3, 3, 5]) %!test %! x = reshape ([1:24], 4, 3, 2); %! x([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = NaN; %! y = x; %! y([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = [2, 5, 8, 10, 11, 15, 15, 20, 21, 24]; %! assert_equal (fillmissing (x, 'nearest', 1), y); %! y = x; %! y([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = [5, 10, 11, 5, 8, 18, 20, 15, 18, 15]; %! assert_equal (fillmissing (x, 'nearest', 2), y); %! y = x; %! y([1, 6, 9, 12, 14, 16, 22, 23]) = [13, 18, 21, 24, 2, 4, 10, 11]; %! assert_equal (fillmissing (x, 'nearest', 3), y); %! assert_equal (fillmissing (x, 'nearest', 99), x); ## Tests for nearest with diff endvalue behavior %!assert_equal (fillmissing ([1, 2, 3], 'constant', 0, 'endvalues', 'nearest'), [1, 2, 3]) %!assert_equal (fillmissing ([1, NaN, 3], 'constant', 0, 'endvalues', 'nearest'), [1 0 3]) %!assert_equal (fillmissing ([1, 2, NaN], 'constant', 0, 'endvalues', 'nearest'), [1, 2, 2]) %!assert_equal (fillmissing ([1, NaN, NaN], 'constant', 0, 'endvalues', 'nearest'), [1, 1, 1]) %!assert_equal (fillmissing ([NaN, 2, 3], 'constant', 0, 'endvalues', 'nearest'), [2, 2, 3]) %!assert_equal (fillmissing ([NaN, NaN, 3], 'constant', 0, 'endvalues', 'nearest'), [3, 3, 3]) %!assert_equal (fillmissing ([NaN, NaN, NaN], 'constant', 0, 'endvalues', 'nearest'), [NaN, NaN, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 0, 'endvalues', 'nearest'), [2, 2, 0, 4, 4]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 0, 1, 'endvalues', 'nearest'), [NaN, 2, NaN, 4, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 0, 2, 'endvalues', 'nearest'), [2, 2, 0, 4, 4]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 0, 3, 'endvalues', 'nearest'), [NaN, 2, NaN, 4, NaN]) %!test %! x = reshape ([1:24], 3, 4, 2); %! x([1, 2, 5, 6, 8, 10, 13, 16, 18, 19, 20, 21, 22]) = NaN; %! y = x; %! y([1, 2, 5, 6, 8, 10, 13, 16, 18, 22]) = [3, 3, 4, 4, 0, 11, 14, 17, 17, 23]; %! assert_equal (fillmissing (x, 'constant', 0, 'endvalues', 'nearest'), y); %! assert_equal (fillmissing (x, 'constant', 0, 1, 'endvalues', 'nearest'), y); %! y = x; %! y([1, 2, 5, 6, 8, 10, 18, 20, 21]) = [4, 11, 11, 0, 11, 7, 0, 0, 0]; %! assert_equal (fillmissing (x, 'constant', 0, 2, 'endvalues', 'nearest'), y); %! y = x; %! y([2, 5, 16, 19, 21]) = [14, 17, 4, 7, 9]; %! assert_equal (fillmissing (x, 'constant', 0, 3, 'endvalues', 'nearest'), y); %! assert_equal (fillmissing (x, 'constant', 0, 99, 'endvalues', 'nearest'), x); ## Tests for linear %!assert_equal (fillmissing ([1, 2, 3], 'linear'), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, 3]', 'linear'), [1, 2, 3]') %!assert_equal (fillmissing ([1, 2, NaN], 'linear'), [1, 2, 3]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'linear'), [NaN, 2, NaN]) %!assert_equal (fillmissing ([1, NaN, 3], 'linear'), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'linear', 1), [1, 2, NaN; 4, NaN, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'linear', 2), [1, 2, 3; 4, 5, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'linear', 3), [1, 2, NaN; 4, NaN, 6]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'linear'), [1, 2, 3, 4, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'linear', 'samplepoints', [0, 1, 2, 3, 4]), [1, 2, 3, 4, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'linear', 'samplepoints', [0, 1.5, 2, 5, 14]), [1, 2.5, 3, 3.5, 5], eps) %!test %! x = reshape ([1:24], 4, 3, 2); %! x([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = NaN; %! assert_equal (fillmissing (x, 'linear', 1), reshape ([1:24], 4, 3, 2)); %! y = reshape ([1:24], 4, 3, 2); %! y([1, 9, 14, 19, 22, 23]) = NaN; %! assert_equal (fillmissing (x, 'linear', 2), y); %! y = reshape ([1:24], 4, 3, 2); %! y([1, 6, 7, 9, 12, 14, 16, 19, 22, 23]) = NaN; %! assert_equal (fillmissing (x, 'linear', 3), y); %! assert_equal (fillmissing (x, 'linear', 99), x); ## Tests for linear with diff endvalue behavior %!assert_equal (fillmissing ([1, 2, 3], 'linear', 'endvalues', 0), [1, 2, 3]) %!assert_equal (fillmissing ([1, NaN, 3], 'linear', 'endvalues', 0), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, NaN], 'linear', 'endvalues', 0), [1, 2, 0]) %!assert_equal (fillmissing ([1, NaN, NaN], 'linear', 'endvalues', 0), [1, 0, 0]) %!assert_equal (fillmissing ([NaN, 2, 3], 'linear', 'endvalues', 0), [0, 2, 3]) %!assert_equal (fillmissing ([NaN, NaN, 3], 'linear', 'endvalues', 0), [0, 0, 3]) %!assert_equal (fillmissing ([NaN, NaN, NaN], 'linear', 'endvalues', 0), [0, 0, 0]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'linear', 'endvalues', 0), [0, 2, 3, 4, 0]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'linear', 1, 'endvalues', 0), [0, 2, 0, 4, 0]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'linear', 2, 'endvalues', 0), [0, 2, 3, 4, 0]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'linear', 3, 'endvalues', 0), [0, 2, 0, 4, 0]) %!test %! x = reshape ([1:24], 3, 4, 2); %! x([1, 2, 5, 6, 8, 10, 13, 16, 18, 19, 20, 21, 22]) = NaN; %! y = x; %! y([1, 2, 5, 6, 10, 13, 16, 18, 19, 20, 21, 22]) = 0; %! y(8) = 8; %! assert_equal (fillmissing (x, 'linear', 'endvalues', 0), y); %! assert_equal (fillmissing (x, 'linear', 1, 'endvalues', 0), y); %! y = x; %! y([1, 2, 5, 8, 10, 13, 16, 19, 22]) = 0; %! y([6, 18, 20, 21]) = [6, 18, 20, 21]; %! assert_equal (fillmissing (x, 'linear', 2, 'endvalues', 0), y); %! y = x; %! y(isnan (y)) = 0; %! assert_equal (fillmissing (x, 'linear', 3, 'endvalues', 0), y); %! assert_equal (fillmissing (x, 'linear', 99, 'endvalues', 0), y); ## Tests with linear only on endvalues %!assert_equal (fillmissing ([1, 2, 3], 'constant', 99, 'endvalues', 'linear'), [1, 2, 3]) %!assert_equal (fillmissing ([1, NaN, 3], 'constant', 99, 'endvalues', 'linear'), [1, 99, 3]) %!assert_equal (fillmissing ([1, NaN, 3, NaN], 'constant', 99, 'endvalues', 'linear'), [1, 99, 3, 4]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 99, 'endvalues', 'linear'), [1, 2, 99, 4, 5]) %!assert_equal (fillmissing ([NaN, 2, NaN, NaN], 'constant', 99, 'endvalues', 'linear'), [NaN, 2, NaN, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 99, 'endvalues', 'linear', 'samplepoints', [1, 2, 3, 4, 5]), [1, 2, 99, 4, 5]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 99, 'endvalues', 'linear', 'samplepoints', [0, 2, 3, 4, 10]), [0, 2, 99, 4, 10]) ## Test other interpolants %!test %! x = reshape ([1:24], 3, 4, 2); %! x([1, 2, 5, 6, 8, 10, 13, 16, 18, 19, 20, 21, 22]) = NaN; %! y = x; %! y([1, 6, 10, 18, 20, 21]) = [2.5, 5, 8.5, 17.25, 21, 21.75]; %! assert_equal (fillmissing (x, 'linear', 2, 'samplepoints', [2 4 8 10]), y, eps); %! y([1, 6, 10, 18, 20, 21]) = [2.5, 4.5, 8.5, 17.25, 21.5, 21.75]; %! assert_equal (fillmissing (x, 'spline', 2, 'samplepoints', [2, 4, 8, 10]), y, eps); %! y([1, 6, 10, 18, 20, 21]) = [2.5, 4.559386973180077, 8.5, 17.25, 21.440613026819925, 21.75]; %! assert_equal (fillmissing (x, 'pchip', 2, 'samplepoints', [2, 4, 8, 10]), y, 10*eps); ## known fail: makima method not yet implemented in interp1 %!test <60965> %! x = reshape ([1:24], 3, 4, 2); %! x([1, 2, 5, 6, 8, 10, 13, 16, 18, 19, 20, 21, 22]) = NaN; %! y = x; %! y([1, 6, 10, 18, 20, 21]) = [2.5, 4.609523809523809, 8.5, 17.25, 21.390476190476186, 21.75]; %! assert_equal (fillmissing (x, 'makima', 2, 'samplepoints', [2, 4, 8, 10]), y, 1e-14); ## Test other interpolants code path on endvalues %!assert_equal (fillmissing ([1, 2, 3], 'constant', 99, 'endvalues', 'spline'), [1, 2, 3]) %!assert_equal (fillmissing ([1, NaN, 3], 'constant', 99, 'endvalues', 'spline'), [1, 99, 3]) %!assert_equal (fillmissing ([1, NaN, 3, NaN], 'constant', 99, 'endvalues', 'spline'), [1, 99, 3, 4]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 99, 'endvalues', 'spline'), [1, 2, 99, 4, 5]) %!assert_equal (fillmissing ([NaN, 2, NaN, NaN], 'constant', 99, 'endvalues', 'spline'), [NaN, 2, NaN, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 99, 'endvalues', 'spline', 'samplepoints', [1, 2, 3, 4, 5]), [1, 2, 99, 4, 5]) %!assert_equal (fillmissing ([NaN, 2, NaN, 4, NaN], 'constant', 99, 'endvalues', 'spline', 'samplepoints', [0, 2, 3, 4, 10]), [0, 2, 99, 4, 10]) ## Test movmean %!assert_equal (fillmissing ([1, 2, 3], 'movmean', 1), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, NaN], 'movmean', 1), [1, 2, NaN]) %!assert_equal (fillmissing ([1, 2, 3], 'movmean', 2), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, 3], 'movmean', [1, 0]), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, 3]', 'movmean', 2), [1, 2, 3]') %!assert_equal (fillmissing ([1, 2, NaN], 'movmean', 2), [1, 2, 2]) %!assert_equal (fillmissing ([1, 2, NaN], 'movmean', [1, 0]), [1, 2, 2]) %!assert_equal (fillmissing ([1, 2, NaN], 'movmean', [1, 0]'), [1, 2, 2]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'movmean', 2), [NaN, 2, 2]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'movmean', [1, 0]), [NaN, 2, 2]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'movmean', [0, 1]), [2, 2, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'movmean', [0, 1.1]), [2, 2, NaN]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'movmean', [3, 0]), [1, 1, 3, 2, 5]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'movmean', 3, 1), [1, 2, 6; 4, 2, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'movmean', 3, 2), [1, 2, 2; 4, 5, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'movmean', 3, 3), [1, 2, NaN; 4, NaN, 6]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'movmean', 99), [1, 3, 3, 3, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'movmean', 99, 1), [1, NaN, 3, NaN, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5]', 'movmean', 99, 1), [1, 3, 3, 3, 5]') %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'movmean', 99, 2), [1, 3, 3, 3, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5]', 'movmean', 99, 2), [1, NaN, 3, NaN, 5]') %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmean', 3, 'samplepoints', [1, 2, 3, 4, 5]), [1, 1, NaN, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmean', [1, 1], 'samplepoints', [1, 2, 3, 4, 5]), [1, 1, NaN, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmean', [1.5, 1.5], 'samplepoints', [1, 2, 3, 4, 5]), [1, 1, NaN, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmean', 4, 'samplepoints', [1, 2, 3, 4, 5]), [1, 1, 1, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmean', [2, 2], 'samplepoints', [1, 2, 3, 4, 5]), [1, 1, 3, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmean', 4.0001, 'samplepoints', [1, 2, 3, 4, 5]), [1, 1, 3, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmean', 3, 'samplepoints', [1.5, 2, 3, 4, 5]), [1, 1, 1, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmean', 3, 'samplepoints', [1 2, 3, 4, 4.5]), [1, 1, NaN, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmean', 3, 'samplepoints', [1.5, 2, 3, 4, 4.5]), [1, 1, 1, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmean', [1.5, 1.5], 'samplepoints', [1.5, 2, 3, 4, 5]), [1, 1, 1, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmean', [1.5, 1.5], 'samplepoints', [1, 2, 3, 4, 4.5]), [1, 1, 5, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmean', [1.5, 1.5], 'samplepoints', [1.5, 2 3, 4, 4.5]), [1, 1, 3, 5, 5]) %!test %! x = reshape ([1:24], 3, 4, 2); %! x([1, 2, 5, 6, 8, 10, 13, 16, 18, 19, 20, 21, 22]) = NaN; %! y = x; %! y([2, 5, 8, 10, 13, 16, 18, 22]) = [3, 4, 8, 11, 14, 17, 17, 23]; %! assert_equal (fillmissing (x, 'movmean', 3), y); %! assert_equal (fillmissing (x, 'movmean', [1, 1]), y); %! assert_equal (fillmissing (x, 'movmean', 3, 'endvalues', 'extrap'), y); %! assert_equal (fillmissing (x, 'movmean', 3, 'samplepoints', [1, 2, 3]), y); %! y = x; %! y([1, 6, 8, 10, 18, 20, 21]) = [4, 6, 11, 7, 15, 20, 24]; %! assert_equal (fillmissing (x, 'movmean', 3, 2), y); %! assert_equal (fillmissing (x, 'movmean', [1, 1], 2), y); %! assert_equal (fillmissing (x, 'movmean', 3, 2, 'endvalues', 'extrap'), y); %! assert_equal (fillmissing (x, 'movmean', 3, 2, 'samplepoints', [1, 2, 3, 4]), y); %! y([1, 18]) = NaN; %! y(6) = 9; %! assert_equal (fillmissing (x, 'movmean', 3, 2, 'samplepoints', [0, 2, 3, 4]), y); %! y = x; %! y([1, 2, 5, 6, 10, 13, 16, 18, 19, 20, 21, 22]) = 99; %! y(8) = 8; %! assert_equal (fillmissing (x, 'movmean', 3, 'endvalues', 99), y); %! y = x; %! y([1, 2, 5, 8, 10, 13, 16, 19, 22]) = 99; %! y([6, 18, 20, 21]) = [6, 15, 20, 24]; %! assert_equal (fillmissing (x, 'movmean', 3, 2, 'endvalues', 99), y); ## Test movmedian %!assert_equal (fillmissing ([1, 2, 3], 'movmedian', 1), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, NaN], 'movmedian', 1), [1, 2, NaN]) %!assert_equal (fillmissing ([1, 2, 3], 'movmedian', 2), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, 3], 'movmedian', [1, 0]), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, 3]', 'movmedian', 2), [1, 2, 3]') %!assert_equal (fillmissing ([1, 2, NaN], 'movmedian', 2), [1, 2, 2]) %!assert_equal (fillmissing ([1, 2, NaN], 'movmedian', [1, 0]), [1, 2, 2]) %!assert_equal (fillmissing ([1, 2, NaN], 'movmedian', [1, 0]'), [1, 2, 2]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'movmedian', 2), [NaN, 2, 2]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'movmedian', [1, 0]), [NaN, 2, 2]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'movmedian', [0, 1]), [2, 2, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'movmedian', [0, 1.1]), [2, 2, NaN]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'movmedian', [3, 0]), [1, 1, 3, 2, 5]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'movmedian', 3, 1), [1, 2, 6; 4, 2, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'movmedian', 3, 2), [1, 2, 2; 4, 5, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], 'movmedian', 3, 3), [1, 2, NaN; 4, NaN, 6]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'movmedian', 99), [1, 3, 3, 3, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'movmedian', 99, 1), [1, NaN, 3, NaN, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5]', 'movmedian', 99, 1), [1, 3, 3, 3, 5]') %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'movmedian', 99, 2), [1, 3, 3, 3, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5]', 'movmedian', 99, 2), [1, NaN, 3, NaN, 5]') %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmedian', 3, 'samplepoints', [1, 2, 3, 4, 5]), [1, 1, NaN, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmedian', [1, 1], 'samplepoints', [1, 2, 3, 4, 5]), [1, 1, NaN, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmedian', [1.5, 1.5], 'samplepoints', [1, 2, 3, 4, 5]), [1, 1, NaN, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmedian', 4, 'samplepoints', [1, 2, 3, 4, 5]), [1, 1, 1, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmedian', [2, 2], 'samplepoints', [1, 2, 3, 4, 5]), [1, 1, 3, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmedian', 4.0001, 'samplepoints', [1, 2, 3, 4, 5]), [1, 1, 3, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmedian', 3, 'samplepoints', [1.5 2 3 4 5]), [1, 1, 1, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmedian', 3, 'samplepoints', [1 2 3 4 4.5]), [1, 1, NaN, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmedian', 3, 'samplepoints', [1.5 2 3 4 4.5]), [1, 1, 1, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmedian', [1.5, 1.5], 'samplepoints', [1.5 2 3 4 5]), [1, 1, 1, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmedian', [1.5, 1.5], 'samplepoints', [1 2 3 4 4.5]), [1, 1, 5, 5, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], 'movmedian', [1.5, 1.5], 'samplepoints', [1.5 2 3 4 4.5]), [1, 1, 3, 5, 5]) %!test %! x = reshape ([1:24], 3, 4, 2); %! x([1, 2, 5, 6, 8, 10, 13, 16, 18, 19, 20, 21, 22]) = NaN; %! y = x; %! y([2, 5, 8, 10, 13, 16, 18, 22]) = [3, 4, 8, 11, 14, 17, 17, 23]; %! assert_equal (fillmissing (x, 'movmedian', 3), y); %! assert_equal (fillmissing (x, 'movmedian', [1, 1]), y); %! assert_equal (fillmissing (x, 'movmedian', 3, 'endvalues', 'extrap'), y); %! assert_equal (fillmissing (x, 'movmedian', 3, 'samplepoints', [1, 2, 3]), y); %! y = x; %! y([1, 6, 8, 10, 18, 20, 21]) = [4, 6, 11, 7, 15, 20, 24]; %! assert_equal (fillmissing (x, 'movmedian', 3, 2), y); %! assert_equal (fillmissing (x, 'movmedian', [1, 1], 2), y); %! assert_equal (fillmissing (x, 'movmedian', 3, 2, 'endvalues', 'extrap'), y); %! assert_equal (fillmissing (x, 'movmedian', 3, 2, 'samplepoints', [1, 2, 3, 4]), y); %! y([1,18]) = NaN; %! y(6) = 9; %! assert_equal (fillmissing (x, 'movmedian', 3, 2, 'samplepoints', [0, 2, 3, 4]), y); %! y = x; %! y([1, 2, 5, 6, 10, 13, 16, 18, 19, 20, 21, 22]) = 99; %! y(8) = 8; %! assert_equal (fillmissing (x, 'movmedian', 3, 'endvalues', 99), y); %! y = x; %! y([1, 2, 5, 8, 10, 13, 16, 19, 22]) = 99; %! y([6, 18, 20, 21]) = [6, 15, 20, 24]; %! assert_equal (fillmissing (x, 'movmedian', 3, 2, 'endvalues', 99), y); ## Test movfcn %!assert_equal (fillmissing ([1, 2, 3], @(x,y,z) x+y+z, 2), [1, 2, 3]) %!error ... %! fillmissing ([1, 2, NaN], @(x,y,z) x+y+z, 1) %!assert_equal (fillmissing ([1, 2, 3], @(x,y,z) x+y+z, 2), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, 3], @(x,y,z) x+y+z, [1, 0]), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, 3]', @(x,y,z) x+y+z, 2), [1, 2, 3]') %!assert_equal (fillmissing ([1, 2, NaN], @(x,y,z) x+y+z, 2), [1, 2, 7]) %!assert_equal (fillmissing ([1, 2, NaN], @(x,y,z) x+y+z, [1, 0]), [1, 2, 7]) %!assert_equal (fillmissing ([1, 2, NaN], @(x,y,z) x+y+z, [1, 0]'), [1, 2, 7]) %!assert_equal (fillmissing ([NaN, 2, NaN], @(x,y,z) x+y+z, 2), [5, 2, 7]) %!error ... %! fillmissing ([NaN, 2, NaN], @(x,y,z) x+y+z, [1, 0]) %!error ... %! fillmissing ([NaN, 2, NaN], @(x,y,z) x+y+z, [0, 1]) %!error ... %! fillmissing ([NaN, 2, NaN], @(x,y,z) x+y+z, [0, 1.1]) %!assert_equal (fillmissing ([1, 2, NaN, NaN, 3, 4], @(x,y,z) x+y+z, 2), [1, 2, 7, 12, 3, 4]) %!error ... %! fillmissing ([1, 2, NaN, NaN, 3, 4], @(x,y,z) x+y+z, 0.5) %!function A = testfcn (x, y, z) %! if (isempty (y)) %! A = z; %! elseif (numel (y) == 1) %! A = repelem (x(1), numel (z)); %! else %! A = interp1 (y, x, z, 'linear', 'extrap'); %! endif %!endfunction %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], @testfcn, [3, 0]), [1, 1, 3, NaN, 5]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], @testfcn, 3, 1), [1, 2, 6; 4, 2, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], @testfcn, 3, 2), [1, 2, 2; 4, 5, 6]) %!assert_equal (fillmissing ([1, 2, NaN; 4, NaN, 6], @testfcn, 3, 3), [1, 2, NaN; 4, NaN, 6]) ##known not-compatible. matlab bug ML2022a: [1, 2, 1; 4, 1, 6] %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], @testfcn, 99), [1, 2, 3, 4, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], @testfcn, 99, 1), [1, NaN, 3, NaN, 5]) ##known not-compatible. matlab bug ML2022a: [1, 1, 3, 1, 5] %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5]', @testfcn, 99, 1), [1, 2, 3, 4, 5]') %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], @testfcn, 99, 2), [1, 2, 3, 4, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5]', @testfcn, 99, 2), [1, NaN, 3, NaN, 5]') ##known not-compatible. matlab bug ML2022a: [1, 1, 3, 1, 5]' %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], @testfcn, 99, 3), [1, NaN, 3, NaN, 5]) ##known not-compatible. matlab bug ML2022a: [1, 1, 3, 1, 5] %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5]', @testfcn, 99, 3), [1, NaN, 3, NaN, 5]') ##known not-compatible. matlab bug ML2022a: [1, 1, 3, 1, 5]' %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], @testfcn, 3, 'samplepoints', [1, 2, 3, 4, 5]), [1, 2, 3, 4, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], @testfcn, [1, 1], 'samplepoints', [1, 2, 3, 4, 5]), [1, 2, 3, 4, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], @testfcn, [1.5, 1.5], 'samplepoints', [1, 2, 3, 4, 5]), [1, 2, 3, 4, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], @testfcn, 4, 'samplepoints', [1, 2, 3, 4, 5]), [1, 2, 3, 4, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], @testfcn, [2, 2], 'samplepoints', [1, 2, 3, 4, 5]), [1, 2, 3, 4, 5]) %!assert_equal (fillmissing ([1, NaN, NaN, NaN, 5], @testfcn, 3, 'samplepoints', [1, 2, 2.5, 3, 3.5]), [1, 2.6, 3.4, 4.2, 5], 10*eps) %!assert_equal (fillmissing ([NaN, NaN, 3, NaN, 5], @testfcn, 99, 1), [NaN, NaN, 3, NaN, 5]) ##known not-compatible. matlab bug ML2022a: [1, 1, 3, 1, 5] ## Known noncompatible. For move_fcn method, ML2021b (1) ignores windowsize ## for full missing column and processes it anyway, (2) doesn't consider it ## part of endvalues unlike all other methods, (3) ignores samplepoint values ## when calculating move_fcn results. should review against future versions. %!test %!function A = testfcn (x, y, z) %! if (isempty (y)) %! A = z; %! elseif (numel (y) == 1) %! A = repelem (x(1), numel (z)); %! else %! A = interp1 (y, x, z, 'linear', 'extrap'); %! endif %!endfunction %! x = reshape ([1:24], 3, 4, 2); %! x([1, 2, 5, 6, 8, 10, 13, 16, 18, 19, 20, 21, 22]) = NaN; %! y = x; %! y([1, 2, 5, 6, 8, 10, 13, 16, 18, 22]) = [3, 3, 4, 4, 8, 11, 14, 17, 17, 23]; %! assert_equal (fillmissing (x, @testfcn, 3), y); %! assert_equal (fillmissing (x, @testfcn, [1, 1]), y); %! assert_equal (fillmissing (x, @testfcn, 3, 'endvalues', 'extrap'), y); %! assert_equal (fillmissing (x, @testfcn, 3, 'samplepoints', [1, 2, 3]), y); %! y= x; %! y(isnan (x)) = 99; %! y(8) = 8; %! assert_equal (fillmissing (x, @testfcn, 3, 'endvalues', 99), y) %! y = x; %! y([1, 2, 5, 6, 8, 10, 18, 20, 21]) = [4, 11, 11, 6, 11, 7, 18, 20, 21]; %! assert_equal (fillmissing (x, @testfcn, 3, 2), y); %! assert_equal (fillmissing (x, @testfcn, [1, 1], 2), y); %! assert_equal (fillmissing (x, @testfcn, 3, 2, 'endvalues', 'extrap'), y); %! assert_equal (fillmissing (x, @testfcn, 3, 2, 'samplepoints', [1, 2, 3, 4]), y); %! y(1) = NaN; %! y([6, 18, 21]) = [9, 24, 24]; %! assert_equal (fillmissing (x, @testfcn, 3, 2, 'samplepoints', [0, 2, 3, 4]), y); %! y = x; %! y([1, 2, 5, 6, 10, 13, 16, 18, 19, 20, 21, 22]) = 99; %! y(8) = 8; %! assert_equal (fillmissing (x, @testfcn, 3, 'endvalues', 99), y); %! y([6, 18, 20, 21]) = [6, 18, 20, 21]; %! y(8) = 99; %! assert_equal (fillmissing (x, @testfcn, 3, 2, 'endvalues', 99), y); %! y([6, 18, 20, 21]) = 99; %! assert_equal (fillmissing (x, @testfcn, 3, 3, 'endvalues', 99), y); ## Test maxgap for mid and end points %!assert_equal (fillmissing ([1, 2, 3], 'constant', 0, 'maxgap', 1), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, 3], 'constant', 0, 'maxgap', 99), [1, 2, 3]) %!assert_equal (fillmissing ([1, NaN, 3], 'constant', 0, 'maxgap', 1), [1, NaN, 3]) %!assert_equal (fillmissing ([1, NaN, 3], 'constant', 0, 'maxgap', 1.999), [1, NaN, 3]) %!assert_equal (fillmissing ([1, NaN, 3], 'constant', 0, 'maxgap', 2), [1, 0, 3]) %!assert_equal (fillmissing ([1, NaN, NaN, 4], 'constant', 0, 'maxgap', 2), [1, NaN, NaN, 4]) %!assert_equal (fillmissing ([1, NaN, NaN, 4], 'constant', 0, 'maxgap', 3), [1, 0, 0, 4]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'constant', 0, 'maxgap', 2), [1, 0, 3, 0, 5]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'constant', 0, 'maxgap', 0.999), [NaN, 2, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN], 'constant', 0, 'maxgap', 1), [0, 2, 0]) %!assert_equal (fillmissing ([NaN, 2, NaN, NaN], 'constant', 0, 'maxgap', 1), [0, 2, NaN, NaN]) %!assert_equal (fillmissing ([NaN, 2, NaN, NaN], 'constant', 0, 'maxgap', 2), [0, 2, 0, 0]) %!assert_equal (fillmissing ([NaN, NaN, NaN], 'constant', 0, 'maxgap', 1), [NaN, NaN, NaN]) %!assert_equal (fillmissing ([NaN, NaN, NaN], 'constant', 0, 'maxgap', 3), [NaN, NaN, NaN]) %!assert_equal (fillmissing ([NaN, NaN, NaN], 'constant', 0, 'maxgap', 999), [NaN, NaN, NaN]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'constant', 0, 'maxgap', 2, 'samplepoints', [0, 1, 2, 3, 5]), [1, 0, 3, NaN, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5]', 'constant', 0, 'maxgap', 2, 'samplepoints', [0, 1, 2, 3, 5]), [1, 0, 3, NaN, 5]') %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'constant', 0, 'maxgap', 2, 'samplepoints', [0, 2, 3, 4, 5]), [1, NaN, 3, 0, 5]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5; 1, NaN, 3, NaN, 5], 'constant', 0, 2, 'maxgap', 2, 'samplepoints', [0, 2, 3, 4, 5]), [1, NaN, 3, 0, 5; 1, NaN, 3, 0, 5]) %!test %! x = cat (3, [1, 2, NaN; 4, NaN, NaN], [NaN, 2, 3; 4, 5, NaN]); %! assert_equal (fillmissing (x, 'constant', 0, 'maxgap', 0.1), x); %! y = x; %! y([4, 7, 12]) = 0; %! assert_equal (fillmissing (x, 'constant', 0, 'maxgap', 1), y); %! assert_equal (fillmissing (x, 'constant', 0, 1, 'maxgap', 1), y); %! y = x; %! y([5, 7, 12]) = 0; %! assert_equal (fillmissing (x, 'constant', 0, 2, 'maxgap', 1), y); %! y = x; %! y([4, 5, 7]) = 0; %! assert_equal (fillmissing (x, 'constant', 0, 3, 'maxgap', 1), y); ## 2nd output ## verify consistent with dim %!test %! x = cat (3, [1, 2, NaN; 4, NaN, NaN], [NaN, 2, 3; 4, 5, NaN]); %! [~, idx] = fillmissing (x, 'constant', 0, 'maxgap', 1); %! assert_equal (idx, logical (cat (3, [0, 0, 0; 0, 1, 0], [1, 0, 0; 0, 0, 1]))); %! [~, idx] = fillmissing (x, 'constant', 0, 1, 'maxgap', 1); %! assert_equal (idx, logical (cat (3, [0, 0, 0; 0, 1, 0], [1, 0, 0; 0, 0, 1]))); %! [~, idx] = fillmissing (x, 'constant', 0, 2, 'maxgap', 1); %! assert_equal (idx, logical (cat (3, [0, 0, 1; 0, 0, 0], [1, 0, 0; 0, 0, 1]))); %! [~, idx] = fillmissing (x, 'constant', 0, 3, 'maxgap', 1); %! assert_equal (idx, logical (cat (3, [0, 0, 1; 0, 1, 0], [1, 0, 0; 0, 0, 0]))); ## Verify idx matches when methods leave gaps unfilled, or when fill looks ## the same. %!test %! x = [NaN, 2, 3]; %! [~, idx] = fillmissing (x, 'previous'); %! assert_equal (idx, logical ([0, 0, 0])); %! [~, idx] = fillmissing (x, 'movmean', 1); %! assert_equal (idx, logical ([0, 0, 0])); %! x = [1:3; 4:6; 7:9]; %! x([2, 4, 7, 9]) = NaN; %! [~, idx] = fillmissing (x, 'linear'); %! assert_equal (idx, logical ([0, 1, 0; 1, 0, 0; 0, 0, 0])); %! [~, idx] = fillmissing (x, 'movmean', 2); %! assert_equal (idx, logical ([0, 0, 0; 1, 0, 0; 0, 0, 1])); %! [A, idx] = fillmissing ([1, 2, 3, NaN, NaN], 'movmean',2); %! assert_equal (A, [1, 2, 3, 3, NaN]); %! assert_equal (idx, logical ([0, 0, 0, 1, 0])); %! [A, idx] = fillmissing ([1, 2, 3, NaN, NaN], 'movmean',3); %! assert_equal (A, [1, 2, 3, 3, NaN]); %! assert_equal (idx, logical ([0, 0, 0, 1, 0])); %! [A, idx] = fillmissing ([1, 2, NaN, NaN, NaN], 'movmedian', 2); %! assert_equal (A, [1, 2, 2, NaN, NaN]); %! assert_equal (idx, logical ([0, 0, 1, 0, 0])); %! [A, idx] = fillmissing ([1, 2, 3, NaN, NaN], 'movmedian', 3); %! assert_equal (A, [1, 2, 3, 3, NaN]); %! assert_equal (idx, logical ([0, 0, 0, 1, 0])); %! [A, idx] = fillmissing ([1, NaN, 1, NaN, 1], @(x,y,z) z, 3); %! assert_equal (A, [1, 2, 1, 4, 1]); %! assert_equal (idx, logical ([0, 1, 0, 1, 0])); %! [A, idx] = fillmissing ([1, NaN, 1, NaN, 1], @(x,y,z) NaN (size (z)), 3); %! assert_equal (A, [1, NaN, 1, NaN, 1]); %! assert_equal (idx, logical ([0, 0, 0, 0, 0])); ## Test missinglocations %!assert_equal (fillmissing ([1, 2, 3], 'constant', 99, 'missinglocations', logical ([0, 0, 0])), [1, 2, 3]) %!assert_equal (fillmissing ([1, 2, 3], 'constant', 99, 'missinglocations', logical ([1, 1, 1])), [99, 99, 99]) %!assert_equal (fillmissing ([1, NaN, 2, 3, NaN], 'constant', 99, 'missinglocations', logical ([1, 0, 1, 0, 1])), [99, NaN, 99, 3, 99]) %!assert_equal (fillmissing ([1, NaN, 3, NaN, 5], 'constant', NaN, 'missinglocations', logical ([0, 1, 1, 1, 0])), [1, NaN, NaN, NaN, 5]) %!assert_equal (fillmissing (['foo '; ' bar'], 'constant', 'X', 'missinglocations', logical ([0, 0, 0, 0; 0, 0, 0, 0])), ['foo '; ' bar']) %!assert_equal (fillmissing (['foo '; ' bar'], 'constant', 'X', 'missinglocations', logical ([1, 0, 1, 0; 0, 1, 1, 0])), ['XoX '; ' XXr']) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'constant', 'X', 'missinglocations', logical ([0, 0, 0])), {'foo', '', 'bar'}) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'constant', 'X', 'missinglocations', logical ([1, 1, 0])), {'X', 'X', 'bar'}) %!test %! [~, idx] = fillmissing ([1, NaN, 3, NaN, 5], 'constant', NaN); %! assert_equal (idx, logical ([0, 0, 0, 0, 0])); %! [~, idx] = fillmissing ([1 NaN 3 NaN 5], 'constant', NaN, 'missinglocations', logical ([0, 1, 1, 1, 0])); %! assert_equal (idx, logical ([0, 1, 1, 1, 0])); %! [A, idx] = fillmissing ([1, 2, NaN, 1, NaN], 'movmean', 3.1, 'missinglocations', logical ([0, 0, 1, 1, 0])); %! assert_equal (A, [1, 2, 2, NaN, NaN]); %! assert_equal (idx, logical ([0, 0, 1, 0, 0])); %! [A, idx] = fillmissing ([1, 2, NaN, NaN, NaN], 'movmean', 2, 'missinglocations', logical ([0, 0, 1, 1, 0])); %! assert_equal (A, [1, 2, 2, NaN, NaN]); %! assert_equal (idx, logical ([0, 0, 1, 0, 0])); %! [A, idx] = fillmissing ([1, 2, NaN, 1, NaN], 'movmean', 3, 'missinglocations', logical ([0, 0, 1, 1, 0])); %! assert_equal (A, [1, 2, 2, NaN, NaN]); %! assert_equal (idx, logical ([0, 0, 1, 0, 0])); %! [A, idx] = fillmissing ([1, 2, NaN, NaN, NaN], 'movmean', 3, 'missinglocations', logical ([0, 0, 1, 1, 0])); %! assert_equal (A, [1, 2, 2, NaN, NaN]); %! assert_equal (idx, logical ([0, 0, 1, 0, 0])); %! [A, idx] = fillmissing ([1, 2, NaN, NaN, NaN], 'movmedian', 2, 'missinglocations', logical ([0, 0, 1, 1, 0])); %! assert_equal (A, [1, 2, 2, NaN, NaN]); %! assert_equal (idx, logical ([0, 0, 1, 0, 0])); %! [A, idx] = fillmissing ([1, 2, NaN, NaN, NaN], 'movmedian', 3, 'missinglocations', logical ([0, 0, 1, 1, 0])); %! assert_equal (A, [1, 2, 2, NaN, NaN]); %! assert_equal (idx, logical ([0, 0, 1, 0, 0])); %! [A, idx] = fillmissing ([1, 2, NaN, NaN, NaN], 'movmedian', 3.1, 'missinglocations', logical ([0, 0, 1, 1, 0])); %! assert_equal (A, [1, 2, 2, NaN, NaN]); %! assert_equal (idx, logical ([0, 0, 1, 0, 0])); %! [A, idx] = fillmissing ([1, NaN, 1, NaN, 1], @(x,y,z) ones (size (z)), 3, 'missinglocations', logical ([0, 1, 0, 1, 1])); %! assert_equal (A, [1, 1, 1, 1, 1]); %! assert_equal (idx, logical ([0, 1, 0, 1, 1])); %! [A, idx] = fillmissing ([1, NaN, 1, NaN, 1], @(x,y,z) NaN (size (z)), 3, 'missinglocations', logical ([0, 1, 0, 1, 1])); %! assert_equal (A, [1, NaN, 1, NaN, NaN]); %! assert_equal (idx, logical ([0, 0, 0, 0, 0])); %!test %! [A, idx] = fillmissing ([1, 2, 5], 'movmedian', 3, 'missinglocations', logical ([0, 1, 0])); %! assert_equal (A, [1, 3, 5]); %! assert_equal (idx, logical ([0, 1, 0])); ## Test char and cellstr %!assert_equal (fillmissing (' foo bar ', 'constant', 'X', 'missinglocations', logical ([1, 0, 0, 0, 1, 0, 0, 0, 1])), 'XfooXbarX') %!assert_equal (fillmissing ([' foo'; 'bar '], 'constant', 'X', 'missinglocations', logical ([1, 0, 0, 0; 0, 0, 0, 1])), ['Xfoo'; 'barX']) %!assert_equal (fillmissing ([' foo'; 'bar '], 'next', 'missinglocations', logical ([1, 0, 0, 0; 0, 0, 0, 1])), ['bfoo'; 'bar ']) %!assert_equal (fillmissing ([' foo'; 'bar '], 'next', 1, 'missinglocations', logical ([1, 0, 0, 0; 0, 0, 0, 1])), ['bfoo'; 'bar ']) %!assert_equal (fillmissing ([' foo'; 'bar '], 'previous', 'missinglocations', logical ([1, 0, 0, 0; 0, 0, 0, 1])), [' foo'; 'baro']) %!assert_equal (fillmissing ([' foo'; 'bar '], 'previous', 1, 'missinglocations', logical ([1, 0, 0, 0; 0, 0, 0, 1])), [' foo'; 'baro']) %!assert_equal (fillmissing ([' foo'; 'bar '], 'nearest', 'missinglocations', logical ([1, 0, 0, 0; 0, 0, 0, 1])), ['bfoo'; 'baro']) %!assert_equal (fillmissing ([' foo'; 'bar '], 'nearest', 1, 'missinglocations', logical ([1, 0, 0, 0; 0, 0, 0, 1])), ['bfoo'; 'baro']) %!assert_equal (fillmissing ([' foo'; 'bar '], 'next', 2, 'missinglocations', logical ([1, 0, 0, 0; 0, 0, 0, 1])), ['ffoo'; 'bar ']) %!assert_equal (fillmissing ([' foo'; 'bar '], 'previous', 2, 'missinglocations', logical ([1, 0, 0, 0; 0, 0, 0, 1])), [' foo'; 'barr']) %!assert_equal (fillmissing ([' foo'; 'bar '], 'nearest', 2, 'missinglocations', logical ([1, 0, 0, 0; 0, 0, 0, 1])), ['ffoo'; 'barr']) %!assert_equal (fillmissing ([' foo'; 'bar '], 'next', 3, 'missinglocations', logical ([1, 0, 0, 0; 0, 0, 0, 1])), [' foo'; 'bar ']) %!assert_equal (fillmissing ([' foo'; 'bar '], 'previous', 3, 'missinglocations', logical ([1, 0, 0, 0; 0, 0, 0, 1])), [' foo'; 'bar ']) %!assert_equal (fillmissing ([' foo'; 'bar '], 'nearest', 3, 'missinglocations', logical ([1, 0, 0, 0; 0, 0, 0, 1])), [' foo'; 'bar ']) %!assert_equal (fillmissing ({'foo', 'bar'}, 'constant', 'a'), {'foo', 'bar'}) %!assert_equal (fillmissing ({'foo', 'bar'}, 'constant', {'a'}), {'foo', 'bar'}) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'constant', 'a'), {'foo', 'a', 'bar'}) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'constant', {'a'}), {'foo', 'a', 'bar'}) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'previous'), {'foo', 'foo', 'bar'}) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'next'), {'foo', 'bar', 'bar'}) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'nearest'), {'foo', 'bar', 'bar'}) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'previous', 2), {'foo', 'foo', 'bar'}) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'next', 2), {'foo', 'bar', 'bar'}) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'nearest', 2), {'foo', 'bar', 'bar'}) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'previous', 1), {'foo', '', 'bar'}) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'previous', 1), {'foo', '', 'bar'}) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'next', 1), {'foo', '', 'bar'}) %!assert_equal (fillmissing ({'foo', '', 'bar'}, 'nearest', 1), {'foo', '', 'bar'}) %!assert_equal (fillmissing ('abc ', @(x,y,z) x+y+z, 2, 'missinglocations', logical ([0, 0, 0, 1])), 'abcj') %!assert_equal (fillmissing ({'foo', '', 'bar'}, @(x,y,z) x(1), 3), {'foo', 'foo', 'bar'}) %!test %! [A, idx] = fillmissing (' a b c', 'constant', ' ', 'missinglocations', logical ([1, 0, 1, 0, 1, 0])); %! assert_equal (A, ' a b c'); %! assert_equal (idx, logical ([1, 0, 1, 0, 1, 0])); %!test %! [A, idx] = fillmissing (' a b c', 'constant', ' '); %! assert_equal (A, ' a b c'); %! assert_equal (idx, logical ([0, 0, 0, 0, 0, 0])); %!test %! [A, idx] = fillmissing ({'foo', '', 'bar', ''}, 'constant', ''); %! assert_equal (A, {'foo', '', 'bar', ''}); %! assert_equal (idx, logical ([0, 0, 0, 0])); %!test %! [A, idx] = fillmissing ({'foo', '', 'bar', ''}, 'constant', {''}); %! assert_equal (A, {'foo', '', 'bar', ''}); %! assert_equal (idx, logical ([0, 0, 0, 0])); %!test %! [A, idx] = fillmissing (' f o o ', @(x,y,z) repelem ('a', numel (z)), 3, 'missinglocations', logical ([1, 0, 1, 0, 1, 0, 1])); %! assert_equal (A, 'afaoaoa'); %! assert_equal (idx, logical ([1, 0, 1, 0, 1, 0, 1])); %!test %! [A, idx] = fillmissing (' f o o ', @(x,y,z) repelem (' ', numel (z)), 3); %! assert_equal (A, ' f o o '); %! assert_equal (idx, logical ([0, 0, 0, 0, 0, 0, 0])); %!test %! [A, idx] = fillmissing ({'', 'foo', ''}, @(x,y,z) repelem ({'a'}, numel (z)), 3); %! assert_equal (A, {'a', 'foo', 'a'}); %! assert_equal (idx, logical ([1, 0, 1])); %!test %! [A, idx] = fillmissing ({'', 'foo', ''}, @(x,y,z) repelem ({''}, numel (z)), 3); %! assert_equal (A, {'', 'foo', ''}); %! assert_equal (idx, logical ([0, 0, 0])); ## Types without a defined 'missing' (currently logical, int) that can be filled %!assert_equal (fillmissing (logical ([1, 0, 1, 0, 1]), 'constant', true), logical ([1, 0, 1, 0, 1])) %!assert_equal (fillmissing (logical ([1, 0, 1, 0, 1]), 'constant', false, 'missinglocations', logical ([1, 0, 1, 0, 1])), logical ([0, 0, 0, 0, 0])) %!assert_equal (fillmissing (logical ([1, 0, 1, 0, 1]), 'previous', 'missinglocations', logical ([1, 0, 1, 0, 1])), logical ([1, 0, 0, 0, 0])) %!assert_equal (fillmissing (logical ([1, 0, 1, 0, 1]), 'next', 'missinglocations', logical ([1, 0, 1, 0, 1])), logical ([0, 0, 0, 0, 1])) %!assert_equal (fillmissing (logical ([1, 0, 1, 0, 1]), 'nearest', 'missinglocations', logical ([1, 0, 1, 0, 1])), logical ([0, 0, 0, 0, 0])) %!assert_equal (fillmissing (logical ([1, 0, 1, 0, 1]), @(x,y,z) false (size (z)), 3), logical ([1, 0, 1, 0, 1])) %!assert_equal (fillmissing (logical ([1, 0, 1, 0, 1]), @(x,y,z) false (size (z)), 3, 'missinglocations', logical ([1, 0, 1, 0, 1])), logical ([0, 0, 0, 0, 0])) %!assert_equal (fillmissing (logical ([1, 0, 1, 0, 1]), @(x,y,z) false (size (z)), [2, 0], 'missinglocations', logical ([1, 0, 1, 0, 1])), logical ([0, 0, 0, 0, 0])) %!test %! x = logical ([1, 0, 1, 0, 1]); %! [~, idx] = fillmissing (x, 'constant', true); %! assert_equal (idx, logical ([0, 0, 0, 0, 0])); %! [~, idx] = fillmissing (x, 'constant', false, 'missinglocations', logical ([1, 0, 1, 0, 1])); %! assert_equal (idx, logical ([1, 0, 1, 0, 1])); %! [~, idx] = fillmissing (x, 'constant', true, 'missinglocations', logical ([1, 0, 1, 0, 1])); %! assert_equal (idx, logical ([1, 0, 1, 0, 1])); %! [~, idx] = fillmissing (x, 'previous', 'missinglocations', logical ([1, 0, 1, 0, 1])); %! assert_equal (idx, logical ([0, 0, 1, 0, 1])); %! [~, idx] = fillmissing (x, 'next', 'missinglocations', logical ([1, 0, 1, 0, 1])); %! assert_equal (idx, logical ([1, 0, 1, 0, 0])); %! [~, idx] = fillmissing (x, 'nearest', 'missinglocations', logical ([1, 0, 1, 0, 1])); %! assert_equal (idx, logical ([1, 0, 1, 0, 1])); %! [~, idx] = fillmissing (x, @(x,y,z) false (size (z)), 3); %! assert_equal (idx, logical ([0, 0, 0, 0, 0])) %! [~, idx] = fillmissing (x, @(x,y,z) false (size (z)), 3, 'missinglocations', logical ([1, 0, 1, 0, 1])); %! assert_equal (idx, logical ([1, 0, 1, 0, 1])) %! [~, idx] = fillmissing (x, @(x,y,z) false (size (z)), [2 0], 'missinglocations', logical ([1, 0, 1, 0, 1])); %! assert_equal (idx, logical ([1, 0, 1, 0, 1])) %!assert_equal (fillmissing (int32 ([1, 2, 3, 4, 5]), 'constant', 0), int32 ([1, 2, 3, 4, 5])) %!assert_equal (fillmissing (int32 ([1, 2, 3, 4, 5]), 'constant', 0, 'missinglocations', logical ([1, 0, 1, 0, 1])), int32 ([0, 2, 0, 4, 0])) %!assert_equal (fillmissing (int32 ([1, 2, 3, 4, 5]), 'previous', 'missinglocations', logical ([1, 0, 1, 0, 1])), int32 ([1, 2, 2, 4, 4])) %!assert_equal (fillmissing (int32 ([1, 2, 3, 4, 5]), 'next', 'missinglocations', logical ([1, 0, 1, 0, 1])), int32 ([2, 2, 4, 4, 5])) %!assert_equal (fillmissing (int32 ([1, 2, 3, 4, 5]), 'nearest', 'missinglocations', logical ([1, 0, 1, 0, 1])), int32 ([2, 2, 4, 4, 4])) %!assert_equal (fillmissing (int32 ([1, 2, 3, 4, 5]), @(x,y,z) z+10, 3), int32 ([1, 2, 3, 4, 5])) %!assert_equal (fillmissing (int32 ([1, 2, 3, 4, 5]), @(x,y,z) z+10, 3, 'missinglocations', logical ([1, 0, 1, 0, 1])), int32 ([11, 2, 13, 4, 15])) %!assert_equal (fillmissing (int32 ([1, 2, 3, 4, 5]), @(x,y,z) z+10, [2, 0], 'missinglocations', logical ([1, 0, 1, 0, 1])), int32 ([11, 2, 13, 4, 15])) ## A one-sided window leaves the gap at that end of the data with nothing to ## fill from, and the fill function is called there with empty value and ## location arguments rather than being skipped. Values are R2024a's, ## measured 2026-08-17. %!assert_equal (fillmissing (int32 ([1, 2, 3, 4, 5]), @(x,y,z) z+10, [0, 2], 'missinglocations', logical ([1, 0, 1, 0, 1])), int32 ([11, 2, 13, 4, 15])) %!assert_equal (fillmissing ([1, 2, 3, 4, 5], @(x,y,z) numel (x), [2, 0], 'missinglocations', logical ([1, 0, 1, 0, 1])), [0, 2, 2, 4, 2]) %!assert_equal (fillmissing ([1, 2, 3, 4, 5], @(x,y,z) numel (x), [0, 2], 'missinglocations', logical ([1, 0, 1, 0, 1])), [2, 2, 2, 4, 0]) %!test %! x = int32 ([1, 2, 3, 4, 5]); %! [~, idx] = fillmissing (x, 'constant', 0); %! assert_equal (idx, logical ([0, 0, 0, 0, 0])); %! [~, idx] = fillmissing (x, 'constant', 0, 'missinglocations', logical ([1, 0, 1, 0, 1])); %! assert_equal (idx, logical ([1, 0, 1, 0, 1])); %! [~, idx] = fillmissing (x, 'constant', 3, 'missinglocations', logical ([0, 0, 1, 0, 0])); %! assert_equal (idx, logical ([0, 0, 1, 0, 0])); %! [~, idx] = fillmissing (x, 'previous', 'missinglocations', logical ([1, 0, 1, 0, 1])); %! assert_equal (idx, logical ([0, 0, 1, 0, 1])); %! [~, idx] = fillmissing (x, 'next', 'missinglocations', logical ([1, 0, 1, 0, 1])); %! assert_equal (idx, logical ([1, 0, 1, 0, 0])); %! [~, idx] = fillmissing (x, 'nearest', 'missinglocations', logical ([1, 0, 1, 0, 1])); %! assert_equal (idx, logical ([1, 0, 1, 0, 1])); %! [~, idx] = fillmissing (x, @(x,y,z) z+10, 3); %! assert_equal (idx, logical ([0, 0, 0, 0, 0])); %! [~, idx] = fillmissing (x, @(x,y,z) z+10, 3, 'missinglocations', logical ([1, 0, 1, 0, 1])); %! assert_equal (idx, logical ([1, 0, 1, 0, 1])); %! [~, idx] = fillmissing (x, @(x,y,z) z+10, [2 0], 'missinglocations', logical ([1, 0, 1, 0, 1])); %! assert_equal (idx, logical ([1, 0, 1, 0, 1])); ## Other data type passthrough %!test %! [A, idx] = fillmissing ([struct, struct], 'constant', 1); %! assert_equal (A, [struct, struct]) %! assert_equal (idx, [false, false]) ## Test input validation and error messages %!error fillmissing () %!error fillmissing (1) %!error fillmissing (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) %!error fillmissing (1, 2) %!error fillmissing (1, 'foo') %!error fillmissing (1, @(x) x, 1) %!error fillmissing (1, @(x,y) x+y, 1) %!error fillmissing ('a b c', 'linear') %!error fillmissing ({'a', 'b'}, 'linear') %!error <'movmean' and 'movmedian' methods only valid for numeric> fillmissing ('a b c', 'movmean', 2) %!error <'movmean' and 'movmedian' methods only valid for numeric> fillmissing ({'a', 'b'}, 'movmean', 2) %!error <'constant' method must be followed by> fillmissing (1, 'constant') %!error fillmissing (1, 'constant', []) %!error fillmissing (1, 'constant', 'a') %!error fillmissing ('a', 'constant', 1) %!error fillmissing ('a', 'constant', {'foo'}) %!error fillmissing ({'foo'}, 'constant', 1) %!error fillmissing (1, 'movmean') %!error fillmissing (1, 'movmedian') %!error fillmissing (1, 'constant', 1, 0) %!error fillmissing (1, 'constant', 1, -1) %!error fillmissing (1, 'constant', 1, [1, 2]) %!error fillmissing (1, 'constant', 1, 'samplepoints') %!error fillmissing (1, 'constant', 1, 'foo') %!error fillmissing (1, 'constant', 1, 1, 'foo') %!error fillmissing (1, 'constant', 1, 2, {1}, 4) %!error fillmissing ([1, 2, 3], 'constant', 1, 2, 'samplepoints', [1, 2]) %!error fillmissing ([1, 2, 3], 'constant', 1, 2, 'samplepoints', [3, 1, 2]) %!error fillmissing ([1, 2, 3], 'constant', 1, 2, 'samplepoints', [1, 1, 2]) %!error fillmissing ([1, 2, 3], 'constant', 1, 2, 'samplepoints', 'abc') %!error fillmissing ([1, 2, 3], 'constant', 1, 2, 'samplepoints', logical ([1, 1, 1])) %!error fillmissing ([1, 2, 3], 'constant', 1, 1, 'samplepoints', [1, 2, 3]) %!error fillmissing ('foo', 'next', 'endvalues', 1) %!error fillmissing (1, 'constant', 1, 1, 'endvalues', 'foo') %!error fillmissing ([1, 2, 3], 'constant', 1, 2, 'endvalues', [1, 2, 3]) %!error fillmissing ([1, 2, 3], 'constant', 1, 1, 'endvalues', [1, 2]) %!error fillmissing (randi (5,4,3,2), 'constant', 1, 3, 'endvalues', [1, 2]) %!error fillmissing (1, 'constant', 1, 1, 'endvalues', {1}) %!error fillmissing (1, 'constant', 1, 2, 'foo', 4) %!error fillmissing (struct, 'constant', 1, 'missinglocations', false) %!error fillmissing (1, 'constant', 1, 2, 'maxgap', 1, 'missinglocations', false) %!error fillmissing (1, 'constant', 1, 2, 'missinglocations', false, 'maxgap', 1) %!error fillmissing (1, 'constant', 1, 'replacevalues', true) %!error fillmissing (1, 'constant', 1, 'datavariables', 'Varname') %!error fillmissing (1, 'constant', 1, 2, 'missinglocations', 1) %!error fillmissing (1, 'constant', 1, 2, 'missinglocations', 'a') %!error fillmissing (1, 'constant', 1, 2, 'missinglocations', [true, false]) %!error fillmissing (true, 'linear', 'missinglocations', true) %!error fillmissing (int8 (1), 'linear', 'missinglocations', true) %!error fillmissing (true, 'next', 'missinglocations', true, 'EndValues', 'linear') %!error fillmissing (true, 'next', 'EndValues', 'linear', 'missinglocations', true) %!error fillmissing (int8 (1), 'next', 'missinglocations', true, 'EndValues', 'linear') %!error fillmissing (int8 (1), 'next', 'EndValues', 'linear', 'missinglocations', true) %!error fillmissing (1, 'constant', 1, 2, 'maxgap', true) %!error fillmissing (1, 'constant', 1, 2, 'maxgap', 'a') %!error fillmissing (1, 'constant', 1, 2, 'maxgap', [1, 2]) %!error fillmissing (1, 'constant', 1, 2, 'maxgap', 0) %!error fillmissing (1, 'constant', 1, 2, 'maxgap', -1) %!error fillmissing ([1, 2, 3], 'constant', [1, 2, 3]) %!error fillmissing ([1, 2, 3]', 'constant', [1, 2, 3]) %!error fillmissing ([1, 2, 3]', 'constant', [1, 2, 3], 1) %!error fillmissing ([1, 2, 3], 'constant', [1, 2, 3], 2) %!error fillmissing (randi (5, 4, 3, 2), 'constant', [1, 2], 1) %!error fillmissing (randi (5, 4, 3, 2), 'constant', [1, 2], 2) %!error fillmissing (randi (5, 4, 3, 2), 'constant', [1, 2], 3) %!error fillmissing (1, @(x,y,z) x+y+z) %!error fillmissing ([1, NaN, 2], @(x,y,z) [1, 2], 2) statistics-release-1.9.2/inst/Data_Manipulation/grp2idx.m000066400000000000000000000371131524624707500234570ustar00rootroot00000000000000## Copyright (C) 2015 Carnë Draug ## Copyright (C) 2022-2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{g} =} grp2idx (@var{s}) ## @deftypefnx {statistics} {[@var{g}, @var{gn}] =} grp2idx (@var{s}) ## @deftypefnx {statistics} {[@var{g}, @var{gn}, @var{gl}] =} grp2idx (@var{s}) ## ## Get index for grouping variable. ## ## @code{@var{g} = grp2idx (@var{s})} returns a numeric column vector of integer ## values @var{g} indexing the distinct groups in the grouping variable @var{s}. ## @var{s} can specified as any of the following data types: ## ## @itemize ## @item categorical vector ## @item cell array of character vectors ## @item character array ## @item duration vector ## @item logical vector ## @item numeric vector ## @end itemize ## ## @var{s} must be a vector, unless it is a 2-D character array. In the case of ## numerical and logical data types, the group indices are ordered in sorted ## order of @var{s}. In the case of categorical arrays, the group indices are ## allocated by the order of the categories in @var{s}. For the rest of the ## data types, the group indices are allocated by order of first appearance in ## @var{s}. Note that in case of a categorical grouping variable, the indexing ## integer values might not be continuous, since @var{s} may contain unassigned ## categories. For every other data type, @var{g} will contain integer values ## in the range @math{[1:K]}, where @math{K} is the number of distinct groups ## in @var{s}. ## ## @code{[@var{g}, @var{gn}] = grp2idx (@var{s})} also returns a cell array of ## character vectors @var{gn} representing the list of group names. The order ## of the group names in @var{gn} follow the same pattern as the group indices ## in @var{g} according to the data type of @var{s}, as described above. ## ## @code{[@var{g}, @var{gn}, @var{gl}] = grp2idx (@var{s})} further returns a ## column vector @var{gl} representing the list of the group levels with the ## same data type as @var{s}. ## ## Note that standard missing values in @var{s} appear as NaN in @var{g} and are ## not present on either @var{gn} and @var{gl}. ## ## @seealso{grpstats} ## @end deftypefn function [g, gn, gl] = grp2idx (s) if (nargin != 1) print_usage (); endif if (ndims (s) != 2) error ("grp2idx: S must be either a vector or a matrix."); endif is_categorical = false; is_char_array = false; #is_datetime = false; is_duration = false; is_string = false; if (iscategorical (s)) if (! isvector (s)) error ("grp2idx: 'categorical' grouping variable must be a vector."); endif is_categorical = true; undef = isundefined (s); cats = categories (s); s = cellstr (s); s(undef) = {''}; elseif (ischar (s)) is_char_array = true; s = cellstr (s); elseif (isdatetime (s)) error ("grp2idx: 'datetime' grouping variable is not supported yet."); elseif (isduration (s)) if (! isvector (s)) error ("grp2idx: 'duration' grouping variable must be a vector."); endif is_duration = true; elseif (isstring (s)) if (! isvector (s)) error ("grp2idx: 'string' grouping variable must be a vector."); endif is_string = true; s = cellstr (s); elseif (isnumeric (s)) if (! isvector (s)) error ("grp2idx: 'numeric' grouping variable must be a vector."); endif elseif (islogical (s)) if (! isvector (s)) error ("grp2idx: 'logical' grouping variable must be a vector."); endif elseif (iscellstr (s)) if (! isvector (s)) error ("grp2idx: 'cell array' grouping variable must be a vector."); endif else error ("grp2idx: unsupported type for input S."); endif [gl, I, g] = unique (s(:)); ## Fix order in here, since unique does not support this yet if (iscellstr (s) && ! is_categorical) I = sort (I); for i = 1:length (gl) gl_s(i) = gl(g(I(i))); idx(i,:) = (g == g(I(i))); endfor for i = 1:length (gl) g(idx(i,:)) = i; endfor gl = gl_s; gl = gl'; endif ## handle NaNs and empty strings if (iscellstr (s)) empties = cellfun (@isempty, s); if (any (empties)) g(empties) = NaN; rm = find (cellfun (@isempty, gl)); to_decrement = ! isnan (g) & g > rm; g(to_decrement) -= 1; endif empties = cellfun (@isempty, gl); if (any (empties)) gl(empties) = []; endif else ## This works fine because NaN come at the end after sorting, we don't ## have to worry about change on the indices. g(isnan (s)) = NaN; gl(isnan (gl)) = []; endif if (nargout > 1) if (is_categorical) if (isempty (cats)) gn = cell (0,1); else gn = cellstr (cats); endif elseif (is_duration) if (isempty (gl)) gn = cell (0,1); else gn = cellstr (gl); endif elseif (iscellstr (gl)) gn = gl; if (isempty (gn)) gn = cell (0,1); endif elseif (iscell (gl)) gn = cellfun (@num2str, gl, 'UniformOutput', false); if (isempty (gn)) gn = cell (0,1); endif else gn = arrayfun (@num2str, gl, 'UniformOutput', false); if (isempty (gn)) gn = cell (0,1); endif endif endif if (nargout > 2) if (is_categorical) gl = categorical (cats); elseif (is_char_array) if (isempty (gl)) gl = char (cell (0,1)); else gl = char (gn); endif elseif (is_duration) if (isempty (gl)) gl = duration (NaN (0,3)); endif elseif (is_string) gl = string (gn); elseif (iscell (gl)) if (isempty (gl)) gl = cell (0,1); endif endif endif endfunction # test for one output argument %!test %! g = grp2idx ([3 2 1 2 3 1]); %! assert_equal (g, [3; 2; 1; 2; 3; 1]); # test for two output arguments %!test %! [g, gn] = grp2idx (['b'; 'a'; 'c'; 'a']); %! assert_equal (g, [1; 2; 3; 2]); %! assert_equal (gn, {'b'; 'a'; 'c'}); ## test boolean input and note that row or column vector makes no difference %!test %! in = [true, false, false, true]; %! out = {[2; 1; 1; 2] {'0'; '1'} [false; true]}; %! assert_equal (nthargout (1:3, @grp2idx, in), out) %! assert_equal (nthargout (1:3, @grp2idx, in), nthargout (1:3, @grp2idx, in')) ## test that boolean groups are ordered in order of appearance %!test %! assert_equal (nthargout (1:3, @grp2idx, [false, true]), %! {[1; 2] {'0'; '1'} [false; true]}); %! assert_equal (nthargout (1:3, @grp2idx, [true, false]), %! {[2; 1] {'0'; '1'} [false; true]}); ## test char matrix and cell array of strings %!assert_equal (nthargout (1:3, @grp2idx, ['oct'; 'sci'; 'oct'; 'oct'; 'sci']), %! {[1; 2; 1; 1; 2] {'oct'; 'sci'} ['oct'; 'sci']}) ## and cell array of strings %!assert_equal (nthargout (1:3, @grp2idx, {'oct'; 'sci'; 'oct'; 'oct'; 'sci'}), %! {[1; 2; 1; 1; 2] {'oct'; 'sci'} {'oct'; 'sci'}}) ## test numeric arrays %!assert_equal (nthargout (1:3, @grp2idx, [1, -3, -2, -3, -3, 2, 1, -1, 3, -3]), %! {[4; 1; 2; 1; 1; 5; 4; 3; 6; 1], {'-3'; '-2'; '-1'; '1'; '2'; '3'}, ... %! [-3; -2; -1; 1; 2; 3]}) %!test %! s = [1e6, 2e6, 1e6, 3e6]; %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, [1; 2; 1; 3]); %! assert_equal (gn, {'1000000'; '2000000'; '3000000'}); %! assert_equal (gl, [1000000; 2000000; 3000000]); %!test %! s = [0.1, 0.2, 0.3, 0.1, 0.2]; %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, [1; 2; 3; 1; 2]); %! assert_equal (gn, {'0.1'; '0.2'; '0.3'}); %! assert_equal (gl, [0.1; 0.2; 0.3]); %!test %! s = [-5 -10 0 5 10 -5]; %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, [2; 1; 3; 4; 5; 2]); %! assert_equal (gn, {'-10'; '-5'; '0'; '5'; '10'}); %! assert_equal (gl, [-10; -5; 0; 5; 10]); ## test for NaN and empty strings %!assert_equal (nthargout (1:3, @grp2idx, [2, 2, 3, NaN, 2, 3]), ... %! {[1; 1; 2; NaN; 1; 2] {'2'; '3'} [2; 3]}) %!assert_equal (nthargout (1:3, @grp2idx, {'et', 'sa', 'sa', '', 'et'}), ... %! {[1; 2; 2; NaN; 1] {'et'; 'sa'} {'et'; 'sa'}}) %!assert_equal (nthargout (1:3, @grp2idx, [2, 2, 3, NaN, 2, 4]), ... %! {[1; 1; 2; NaN; 1; 3] {'2'; '3'; '4'} [2; 3; 4]}) %!test %! s = [NaN, NaN, NaN]; %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, [NaN; NaN; NaN]); %! assert_equal (gn, cell (0,1)); %! assert_equal (gl, zeros (0,1)); %!test %! s = single ([NaN, NaN, NaN]); %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, [NaN; NaN; NaN]); %! assert_equal (gn, cell (0,1)); %! assert_equal (gl, single (zeros (0,1))); %!test %! s = {''; ''; ''; ''}; %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, [NaN; NaN; NaN; NaN]); %! assert_equal (gn, cell (0,1)); %! assert_equal (gl, cell (0,1)); %!test %! s = {'', '', '', ''}; %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, [NaN; NaN; NaN; NaN]); %! assert_equal (gn, cell (0,1)); %! assert_equal (gl, cell (0,1)); %!test %! s = {'a'; ''; 'b'; ''; 'c'}; %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, [1; NaN; 2; NaN; 3]); %! assert_equal (gn, {'a'; 'b'; 'c'}); %! assert_equal (gl, {'a'; 'b'; 'c'}); %!test %! s = categorical ({''; ''; ''; ''}); %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, [NaN; NaN; NaN; NaN]); %! assert_equal (gn, cell (0,1)); %! assert_equal (isequaln (gl, categorical (cell (0,1))), true); %!test %! s = string ({missing, missing, missing}); %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, [NaN; NaN; NaN]); %! assert_equal (gn, cell (0,1)); %! assert_equal (isequal (gl, string (cell (0,1))), true); %!test %! s = [duration(NaN, 0, 0), duration(NaN, 0, 0), duration(NaN, 0, 0)]; %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, [NaN; NaN; NaN]); %! assert_equal (gn, cell (0,1)); %! assert_equal (isequal (gl, duration (NaN (0,3))), true); %!test %! [g, gn, gl] = grp2idx (duration (NaN (3, 1), 0, 0)); %! assert_equal (g, [NaN; NaN; NaN]); %! assert_equal (gn, cell (0,1)); %! assert_equal (isequal (gl, duration (NaN (0,3))), true); ## Test that order when handling strings is by order of appearance %!test assert_equal (nthargout (1:3, @grp2idx, ['sci'; 'oct'; 'sci'; 'oct'; 'oct']), %! {[1; 2; 1; 2; 2] {'sci'; 'oct'} ['sci'; 'oct']}); %!test assert_equal (nthargout (1:3, @grp2idx, {'sci'; 'oct'; 'sci'; 'oct'; 'oct'}), %! {[1; 2; 1; 2; 2] {'sci'; 'oct'} {'sci'; 'oct'}}); %!test assert_equal (nthargout (1:3, @grp2idx, {'sa' 'et' 'et' '' 'sa'}), %! {[1; 2; 2; NaN; 1] {'sa'; 'et'} {'sa'; 'et'}}) ## test for categorical arrays %!test %! [g, gn, gl] = grp2idx (categorical ({'low', 'med', 'high', 'low'})); %! assert_equal (g, [2; 3; 1; 2]); %! assert_equal (gn, {'high'; 'low'; 'med'}); %! assert_equal (isequal (gl, categorical ({'high'; 'low'; 'med'})), true); %!test %! [g, gn, gl] = grp2idx (categorical ([10, 20, 10, 30, 20])); %! assert_equal (g, [1; 2; 1; 3; 2]); %! assert_equal (gn, {'10'; '20'; '30'}); %! assert_equal (isequal (gl, categorical ([10; 20; 30])), true); %!test %! cats = categorical ({'high', '', 'low', ''}); %! [g, gn, gl] = grp2idx (cats); %! assert_equal (g, [2; 1; 3; 1]); %! assert_equal (gn, {''; 'high'; 'low'}); %! assert_equal (isequal (gl, categorical ({''; 'high'; 'low'})), true); %!test %! s = categorical ({''; ''; ''; ''}, {'1', '2', '3'}, {'1', '2', '3'}); %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, nan (4, 1)); %! assert_equal (gn, {'1'; '2'; '3'}); %! assert_equal (iscategorical (gl), true); %! assert_equal (cellstr (gl), gn); %!test %! s = categorical ({''; '1'; ''; '2'}, {'1', '2', '3'}, {'1', '2', '3'}); %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, [NaN; 1; NaN; 2]); %! assert_equal (gn, {'1'; '2'; '3'}); %! assert_equal (iscategorical (gl), true); %! assert_equal (cellstr (gl), gn); %!test %! s = categorical ({''; '2'; ''; '1'}, {'1', '2', '3'}, {'1', '2', '3'}); %! [g, gn, gl] = grp2idx (s); %! assert_equal (g, [NaN; 2; NaN; 1]); %! assert_equal (gn, {'1'; '2'; '3'}); %! assert_equal (iscategorical (gl), true); %! assert_equal (cellstr (gl), gn); ## test for duration arrays %!test %! g = gn = gl = []; %! [g, gn, gl] = grp2idx (seconds ([1.234, 1.234, 2.5, 3.000])); %! assert_equal (g, [1; 1; 2; 3]); %! assert_equal (gn, {'1.234 sec'; '2.5 sec'; '3 sec'}); %! assert_equal (isequal (gl, seconds ([1.234; 2.5; 3.000])), true); %!test %! [g, gn, gl] = grp2idx ([hours(1); hours(2); hours(1); hours(3)]); %! assert_equal (g, [1; 2; 1; 3]); %! assert_equal (gn, {'1 hr'; '2 hr'; '3 hr'}); %! assert_equal (isequal (gl, [hours(1); hours(2); hours(3)]), true); %!test %! in = [duration(1, 30, 0); duration(0, 45, 30); duration(1, 30, 0); duration(2, 15, 15)]; %! [g, gn, gl] = grp2idx (in); %! assert_equal (g, [2; 1; 2; 3]); %! assert_equal (gn, {'00:45:30'; '01:30:00'; '02:15:15'}); %! assert_equal (isequal (gl, [duration(0, 45, 30); duration(1, 30, 0); duration(2, 15, 15)]), true); ## Inconsistency Note: following test is inconsistent with MATLAB due to a ## probable bug in their implementation, where they include multiple NaNs ## in the output group labels for duration array inputs. %!test %! in = [hours(1); NaN; minutes(30); hours(1); NaN; seconds(90)]; %! [g, gn, gl] = grp2idx (in); %! assert_equal (g, [3; NaN; 2; 3; NaN; 1]); %! assert_equal (gn, {'0.025 hr'; '0.5 hr'; '1 hr'}); %! assert_equal (isequal (gl, [seconds(90); minutes(30); hours(1)]), true); ## test for string arrays %!test %! [g, gn, gl] = grp2idx (string ({'123', 'erw', missing, '', '234'})); %! assert_equal (g, [1; 2; NaN; NaN; 3]); %! assert_equal (gn, {'123'; 'erw'; '234'}); %! assert_equal (isequal (gl, string ({'123'; 'erw'; '234'})), true); %!test %! [g, gn, gl] = grp2idx (string ({'medium', 'low', 'high', 'medium', 'medium'})); %! assert_equal (g, [1; 2; 3; 1; 1]); %! assert_equal (gn, {'medium'; 'low'; 'high'}); %! assert_equal (isequal (gl, string ({'medium'; 'low'; 'high'})), true); %!test %! [g, gn, gl] = grp2idx (string ({'', 'high', 'low', ''})); %! assert_equal (g, [NaN; 1; 2; NaN]); %! assert_equal (gn, {'high'; 'low'}); %! assert_equal (isequal (gl, string ({'high'; 'low'})), true); %!test %! [g, gn, gl] = grp2idx (string ({'a', 'a', 'b', 'c'})); %! assert_equal (g, [1; 1; 2; 3]); %! assert_equal (gn, {'a'; 'b'; 'c'}); %! assert_equal (isstring (gl), true); %! assert_equal (cellstr (gl), gn); ## Test input validation %!error grp2idx (ones (3, 3, 3)) %!error ... %! grp2idx (categorical ([1, 2; 1, 3])) %!error ... %! grp2idx (datetime ('now')) %!error ... %! grp2idx (days ([1, 2; 1, 3])) %!error ... %! grp2idx (string ({'a', 'a'; 'b', 'c'})) %!error grp2idx ({1}) %!error grp2idx ([10, 20; 10, 30]) %!error grp2idx ([true, false; false, true]) %!error grp2idx ({'a', 'b'; 'c', 'd'}) statistics-release-1.9.2/inst/Data_Manipulation/ismissing.m000066400000000000000000000240051524624707500241010ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{TF} =} ismissing (@var{A}) ## @deftypefnx {statistics} {@var{TF} =} ismissing (@var{A}, @var{indicator}) ## ## Find missing data in arrays. ## ## @code{@var{TF} = ismissing (@var{A})} returns a logical array, @var{TF}, with ## the same dimensions as @var{A}, where @code{true} values match the standard ## missing values in the input data according to their data type. ## ## Standard missing values and their corresponding data types are: ## ## @itemize ## @item @qcode{NaN} - for @qcode{double}, @qcode{single}, @qcode{duration}, and ## @qcode{calendarDuration} arrays. ## @item @qcode{NaT} - for @qcode{datetime} arrays. ## @item @qcode{} - for @qcode{string} arrays. ## @item @qcode{} - for @qcode{categorical} arrays. ## @item @qcode{@{0x0 char@}} - for @qcode{cell} arrays of character vectors. ## @end itemize ## ## For any data types that do not support missing values, @code{ismissing} ## returns @code{@var{TF} = false (size (@var{A}))}. ## ## Note: the generic @code{ismissing} function from the statistics package only ## operates on core Octave datatypes and it explicitly identifies missing values ## in @qcode{double} and @qcode{single} arrays, as well as in @qcode{cell} ## arrays of character vectors. All other data types are handled by the ## overloaded methods from their respective data class from the datatypes ## package. Use @code{help class_name.ismissing} to find more information about ## the functional specialization of their respective class implementation. ## ## The optional input @var{indicator} can be a scalar or a vector, of the same ## type as the input data @var{A}, specifying alternative missing values in the ## input data. When specifying @var{indicator} values, the standard missing ## values are ignored, unless explicitly stated in the @var{indicator}. ## ## Additional data type matches between @var{indicator} and @var{A} are: ## ## @itemize ## @item @qcode{double} indicators also match @qcode{single}, all integer types, ## and @qcode{logical} data in @var{A}. ## ## @item @qcode{string} and @qcode{char} indicators also match ## @qcode{categorical} data in @var{A}. ## ## @item @qcode{char} and @qcode{cellstr} indicators also match @qcode{string} ## data in @var{A}. ## @end itemize ## ## Note: the generic @code{ismissing} function from the statistics package only ## accepts @var{indicator} argument for numeric, @qcode{logical}, and ## @qcode{char} arrays, as well as for @qcode{cell} arrays of character vectors. ## For all other core Octave data types, @code{ismissing} produces an error. ## However, @var{indicator} is supported for data classes from the datatypes ## package through their respective class implementation of overloaded methods. ## ## @seealso{fillmissing, rmmissing, standardizeMissing} ## @end deftypefn function TF = ismissing (A, indicator) if (nargin < 1) || (nargin > 2) print_usage (); endif ## Check INDICATOR if (nargin != 2) indicator = []; endif ## If A is a cell array of character vectors and INDICATOR is a character ## vector, convert it to a single-element cell array of character vectors if (iscellstr (A) && ischar (indicator) && ! iscellstr (indicator)) indicator = {indicator}; endif if ((! isempty (indicator)) && ((isnumeric (A) && ! isnumeric (indicator)) || (iscellstr (A) && ! iscellstr (indicator)) || (ischar (A) && ! ischar (indicator)) || (islogical (A) && ! (islogical (indicator) || isnumeric (indicator))))) error ("ismissing: 'indicator' and 'A' must have the same data type."); endif ## main logic if (isempty (indicator)) if (isnumeric (A)) ## Numeric matrix: just find the NaNs ## integer types have no missing value, but isnan will return false TF = isnan (A); elseif (iscellstr (A)) ## Cell array of character vectors - find cells with empty char {''} TF = cellfun ('isempty', A); else ## No standard missing type defined, return false. ## Other datatypes with standard missing values are ## handled by their respective overloading methods. TF = false (size (A)); endif else ## INDICATOR specified for missing data TF = false (size (A)); if (isnumeric (A)) for iter = 1:numel (indicator) if (isnan (indicator(iter))) TF(isnan (A)) = true; else TF(A == indicator(iter)) = true; endif endfor elseif (islogical (A) || ischar (A)) for iter = 1:numel (indicator) TF(A == indicator(iter)) = true; endfor elseif (iscellstr (A)) for iter = 1:numel (indicator) if (isempty (indicator{iter})) TF(cellfun ('isempty', A)) = true; else TF(strcmp (A, indicator(iter))) = true; endif endfor else error ("ismissing: indicators not supported for data type '%s'", ... class (A)); endif endif endfunction %!assert_equal (ismissing ([1, NaN, 3]), [false, true, false]) %!assert_equal (ismissing ('abcd f'), [false, false, false, false, false, false]) %!assert_equal (ismissing ({'xxx', '', 'xyz'}), [false, true, false]) %!assert_equal (ismissing ({'x', '', 'y'}), [false, true, false]) %!assert_equal (ismissing ({'x', '', 'y'; 'z', 'a', ''}), logical ([0, 1, 0; 0, 0, 1])) %!assert_equal (ismissing ([1, 2; NaN, 2]), [false, false; true, false]) %!assert_equal (ismissing ([1, 2; NaN, 2], 2), [false, true; false, true]) %!assert_equal (ismissing ([1, 2; NaN, 2], [1, 2]), [true, true; false, true]) %!assert_equal (ismissing ([1, 2; NaN, 2], NaN), [false, false; true, false]) ## test nD array data %!assert_equal (ismissing (cat (3, magic (2), magic (2))), logical (zeros (2, 2, 2))) %!assert_equal (ismissing (cat (3, magic (2), [1, 2; 3, NaN])), ... %! logical (cat (3, [0, 0; 0, 0], [0, 0; 0, 1]))) %!assert_equal (ismissing ([1, 2; 3, 4], [5, 1; 2, 0]), logical ([1, 1; 0, 0])) %!assert_equal (ismissing (cat (3, 'f oo', 'ba r')), ... %! logical (cat (3, [0, 0, 0, 0], [0, 0, 0, 0]))) %!assert_equal (ismissing (cat (3, {'foo'}, {''}, {'bar'})), logical (cat (3, 0, 1, 0))) ## test data type handling %!assert_equal (ismissing (double (NaN)), true) %!assert_equal (ismissing (single (NaN)), true) %!assert_equal (ismissing (' '), false) %!assert_equal (ismissing ({''}), true) %!assert_equal (ismissing ({' '}), false) %!assert_equal (ismissing (double (eye (3)), single (1)), logical (eye (3))) %!assert_equal (ismissing (double (eye (3)), int32 (1)), logical (eye (3))) %!assert_equal (ismissing (single (eye (3)), double (1)), logical (eye (3))) %!assert_equal (ismissing (single (eye (3)), int32 (1)), logical (eye (3))) ## test data types without missing values %!assert_equal (ismissing ({'123', '', 123}), [false, false, false]) %!assert_equal (ismissing (logical ([1, 0, 1])), [false, false, false]) %!assert_equal (ismissing (int32 ([1, 2, 3])), [false, false, false]) %!assert_equal (ismissing (uint32 ([1, 2, 3])), [false, false, false]) %!assert_equal (ismissing ({1, 2, 3}), [false, false, false]) %!assert_equal (ismissing ([struct struct struct]), [false, false, false]) %!assert_equal (ismissing (logical (eye (3)), true), logical (eye (3))) %!assert_equal (ismissing (logical (eye (3)), double (1)), logical (eye (3))) %!assert_equal (ismissing (logical (eye (3)), single (1)), logical (eye (3))) %!assert_equal (ismissing (logical (eye (3)), int32 (1)), logical (eye (3))) %!assert_equal (ismissing (int32 (eye (3)), int32 (1)), logical (eye (3))) %!assert_equal (ismissing (int32 (eye (3)), double (1)), logical (eye (3))) %!assert_equal (ismissing (int32 (eye (3)), single (1)), logical (eye (3))) ## test empty input handling %!assert_equal (ismissing ([]), logical ([])) %!assert_equal (ismissing (''), logical ([])) %!assert_equal (ismissing (ones (0,1)), logical (ones (0,1))) %!assert_equal (ismissing (ones (1,0)), logical (ones (1,0))) %!assert_equal (ismissing (ones (1,2,0)), logical (ones (1,2,0))) ## test indicators and standard missing values %!assert_equal (ismissing ([1, NaN, 0, 2]), [false, true, false, false]) %!assert_equal (ismissing ([1, NaN, 0, 2], [0, 1]), [true, false, true, false]) %!assert_equal (ismissing ([1, NaN, 0, 2], [0, NaN]), [false, true, true, false]) %!assert_equal (ismissing ([true, false, true]), [false, false, false]) %!assert_equal (ismissing ([true, false, true], 1), [true, false, true]) %!assert_equal (ismissing ([true, false, true], 0), [false, true, false]) %!assert_equal (ismissing ({'', 'a', 'f'}), [true, false, false]) %!assert_equal (ismissing ({'', 'a', 'f'}, 'a'), [false, true, false]) %!assert_equal (ismissing ({'', 'a', 'f'}, {'a', 'g'}), [false, true, false]) %!assert_equal (ismissing ({'', 'a', 'f'}, {'a', 'f'}), [false, true, true]) ## Test input validation %!error ismissing () %!error ismissing (1, 2, 3) %!error ... %! ismissing ([1, 2; 3, 4], 'abc') %!error ... %! ismissing ({'', '', ''}, 1) %!error ... %! ismissing (1, struct) %!error ... %! ismissing (struct, 1) %!error ... %! ismissing ({1, 2, 3}, 2) statistics-release-1.9.2/inst/Data_Manipulation/isoutlier.m000066400000000000000000001057621524624707500241250ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{TF} =} isoutlier (@var{x}) ## @deftypefnx {statistics} {@var{TF} =} isoutlier (@var{x}, @var{method}) ## @deftypefnx {statistics} {@var{TF} =} isoutlier (@var{x}, @qcode{'percentiles'}, @var{threshold}) ## @deftypefnx {statistics} {@var{TF} =} isoutlier (@var{x}, @var{movmethod}, @var{window}) ## @deftypefnx {statistics} {@var{TF} =} isoutlier (@dots{}, @var{dim}) ## @deftypefnx {statistics} {@var{TF} =} isoutlier (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{TF}, @var{L}, @var{U}, @var{C}] =} isoutlier (@dots{}) ## ## Find outliers in data ## ## @code{isoutlier (@var{x})} returns a logical array whose elements are true ## when an outlier is detected in the corresponding element of @var{x}. ## @code{isoutlier} treats NaNs as missing values and removes them. ## ## @itemize ## @item ## If @var{x} is a matrix, then @code{isoutlier} operates on each column of ## @var{x} separately. ## @item ## If @var{x} is a multidimensional array, then @code{isoutlier} operates along ## the first dimension of @var{x} whose size does not equal 1. ## @end itemize ## ## By default, an outlier is a value that is more than three scaled median ## absolute deviations (MAD) from the median. The scaled median is defined as ## @code{c*median(abs(A-median(A)))}, where @code{c=-1/(sqrt(2)*erfcinv(3/2))}. ## ## @code{isoutlier (@var{x}, @var{method})} specifies a method for detecting ## outliers. The following methods are available: ## ## @multitable @columnfractions 0.13 0.8 ## @headitem Method @tab Description ## @item @qcode{'median'} @tab Outliers are defined as elements more than ## three scaled MAD from the median. ## @item @qcode{'mean'} @tab Outliers are defined as elements more than ## three standard deviations from the mean. ## @item @qcode{'quartiles'} @tab Outliers are defined as elements more ## than 1.5 interquartile ranges above the upper quartile (75 percent) or below ## the lower quartile (25 percent). This method is useful when the data in ## @var{x} is not normally distributed. ## @item @qcode{'grubbs'} @tab Outliers are detected using Grubbs’ test for ## outliers, which removes one outlier per iteration based on hypothesis ## testing. This method assumes that the data in @var{x} is normally ## distributed. ## @item @qcode{'gesd'} @tab Outliers are detected using the generalized ## extreme Studentized deviate test for outliers. This iterative method is ## similar to @qcode{'grubbs'}, but can perform better when there are multiple ## outliers masking each other. ## @end multitable ## ## @code{isoutlier (@var{x}, @qcode{'percentiles'}, @var{threshold})} detects ## outliers based on a percentile thresholds, specified as a two-element row ## vector whose elements are in the interval @math{[0, 100]}. The first element ## indicates the lower percentile threshold, and the second element indicates ## the upper percentile threshold. The first element of threshold must be less ## than the second element. ## ## @code{isoutlier (@var{x}, @var{movmethod}, @var{window})} specifies a moving ## method for detecting outliers. The following methods are available: ## ## @multitable @columnfractions 0.13 0.8 ## @headitem Method @tab Description ## @item @qcode{'movmedian'} @tab Outliers are defined as elements more ## than three local scaled MAD from the local median over a window length ## specified by @var{window}. ## @item @qcode{'movmean'} @tab Outliers are defined as elements more than ## three local standard deviations from the from the local mean over a window ## length specified by @var{window}. ## @end multitable ## ## @var{window} must be a positive integer scalar or a two-element vector of ## positive integers. When @var{window} is a scalar, if it is an odd number, ## the window is centered about the current element and contains ## @qcode{@var{window} - 1} neighboring elements. If even, then the window is ## centered about the current and previous elements. When @var{window} is a ## two-element vector of positive integers @math{[nb, na]}, the window contains ## the current element, @math{nb} elements before the current element, and ## @math{na} elements after the current element. When @qcode{'SamplePoints'} ## are also specified, @var{window} can take any real positive values (either as ## a scalar or a two-element vector) and in this case, the windows are computed ## relative to the sample points. ## ## @var{dim} specifies the operating dimension and it must be a positive integer ## scalar. If not specified, then, by default, @code{isoutlier} operates along ## the first non-singleton dimension of @var{x}. ## ## The following optional parameters can be specified as @var{Name}/@var{Value} ## paired arguments. ## ## @itemize ## @item @qcode{'SamplePoints'} can be specified as a vector of sample points ## with equal length as the operating dimension. The sample points represent ## the x-axis location of the data and must be sorted and contain unique ## elements. Sample points do not need to be uniformly sampled. By default, ## the vector is @qcode{[1, 2, 3, @dots{}, @var{n}]}, where ## @qcode{@var{n} = size (@var{x}, @var{dim})}. You can use unequally spaced ## @qcode{'SamplePoints'} to define a variable-length window for one of the ## moving methods available. ## ## @item @qcode{'ThresholdFactor'} can be specified as a nonnegative scalar. ## For methods @qcode{'median'} and @qcode{'movmedian'}, the detection threshold ## factor replaces the number of scaled MAD, which is 3 by default. For methods ## @qcode{'mean'} and @qcode{'movmean'}, the detection threshold factor replaces ## the number of standard deviations, which is 3 by default. For methods ## @qcode{'grubbs'} and @qcode{'gesd'}, the detection threshold factor ranges ## from 0 to 1, specifying the critical @math{alpha}-value of the respective ## test, and it is 0.05 by default. For the @qcode{'quartiles'} method, the ## detection threshold factor replaces the number of interquartile ranges, which ## is 1.5 by default. @qcode{'ThresholdFactor'} is not supported for the ## @qcode{'quartiles'} method. ## ## @item @qcode{'MaxNumOutliers'} is only relevant to the @qcode{'gesd'} method ## and it must be a positive integer scalar specifying the maximum number of ## outliers returned by the @qcode{'gesd'} method. By default, it is the ## integer nearest to the 10% of the number of elements along the operating ## dimension in @var{x}. The @qcode{'gesd'} method assumes the nonoutlier input ## data is sampled from an approximate normal distribution. When the data is ## not sampled in this way, the number of returned outliers might exceed the ## @qcode{MaxNumOutliers} value. ## @end itemize ## ## @code{[@var{TF}, @var{L}, @var{U}, @var{C}] = isoutlier (@dots{})} returns ## up to 4 output arguments as described below. ## ## @itemize ## @item @var{TF} is the outlier indicator with the same size a @var{x}. ## ## @item @var{L} is the lower threshold used by the outlier detection method. ## If @var{method} is used for outlier detection, then @var{L} has the same size ## as @var{x} in all dimensions except for the operating dimension where the ## length is 1. If @var{movmethod} is used, then @var{L} has the same size as ## @var{x}. ## ## @item @var{U} is the upper threshold used by the outlier detection method. ## If @var{method} is used for outlier detection, then @var{U} has the same size ## as @var{x} in all dimensions except for the operating dimension where the ## length is 1. If @var{movmethod} is used, then @var{U} has the same size as ## @var{x}. ## ## @item @var{C} is the center value used by the outlier detection method. ## If @var{method} is used for outlier detection, then @var{C} has the same size ## as @var{x} in all dimensions except for the operating dimension where the ## length is 1. If @var{movmethod} is used, then @var{C} has the same size as ## @var{x}. For @qcode{'median'}, @qcode{'movmedian'}, @qcode{'mean'}, and ## @qcode{'movmean'} methods, @var{C} is computed by taking into account the ## outlier values. For @qcode{'grubbs'} and @qcode{'gesd'} methods, @var{C} is ## computed by excluding the outliers. For the @qcode{'percentiles'} method, ## @var{C} is the average between @var{U} and @var{L} thresholds. ## @end itemize ## ## @seealso{filloutliers, rmoutliers, ismissing} ## @end deftypefn function [TF, L, U, C] = isoutlier (x, varargin) ## Check for valid input data if (nargin < 1) print_usage; endif ## Handle case if X is a scalar if (isscalar (x)) TF = false; L = x; U = x; C = x; return endif ## Add defaults dim = []; method = 'median'; window = []; SamplePoints = []; ThresholdFactor = 3; MaxNumOutliers = []; ## MATLAB's constant for scaled Median Absolute Deviation ## c = -1 / (sqrt (2) * erfcinv (3/2)) c = 1.482602218505602; ## Parse extra arguments while (numel (varargin) > 0) if (ischar (varargin{1})) switch (lower (varargin{1})) case 'median' method = 'median'; ThresholdFactor = 3; varargin(1) = []; case 'mean' method = 'mean'; ThresholdFactor = 3; varargin(1) = []; case 'quartiles' method = 'quartiles'; ThresholdFactor = 1.5; varargin(1) = []; case 'grubbs' method = 'grubbs'; ThresholdFactor = 0.05; varargin(1) = []; case 'gesd' method = 'gesd'; ThresholdFactor = 0.05; MaxNumOutliers = []; varargin(1) = []; case 'movmedian' method = 'movmedian'; window = varargin{2}; if (! isnumeric (window) || numel (window) < 1 || numel (window) > 2 || any (window <= 0)) error (strcat ("isoutlier: WINDOW must be a positive scalar", ... " or a two-element vector of positive values")); endif varargin([1:2]) = []; case 'movmean' method = 'movmean'; window = varargin{2}; if (! isnumeric (window) || numel (window) < 1 || numel (window) > 2 || any (window <= 0)) error (strcat ("isoutlier: WINDOW must be a positive scalar", ... " or a two-element vector of positive values")); endif varargin([1:2]) = []; case 'percentiles' method = 'percentiles'; threshold = varargin{2}; if (! isnumeric (threshold) || ! (numel (threshold) == 2)) error (strcat ("isoutlier: THRESHOLD must be a two-element", ... " vector whose elements are in the interval", ... " [0, 100].")); endif if (! (threshold(1) < threshold(2)) || threshold(1) < 0 || threshold(2) > 100) error (strcat ("isoutlier: THRESHOLD must be a two-element", ... " vector whose elements are in the interval", ... " [0, 100].")); endif varargin([1:2]) = []; case 'samplepoints' SamplePoints = varargin{2}; if (! isvector (SamplePoints) || isscalar (SamplePoints)) error ("isoutlier: sample points must be a vector."); endif if (numel (unique (SamplePoints)) != numel (SamplePoints)) error ("isoutlier: sample points must be unique."); endif if (any (sort (SamplePoints) != SamplePoints)) error ("isoutlier: sample points must be sorted."); endif varargin([1:2]) = []; case 'thresholdfactor' ThresholdFactor = varargin{2}; if (! isscalar (ThresholdFactor) || ThresholdFactor <= 0) error ("isoutlier: threshold factor must be a nonnegative scalar."); endif varargin([1:2]) = []; case 'maxnumoutliers' MaxNumOutliers = varargin{2}; if (! isscalar (MaxNumOutliers) || MaxNumOutliers <= 0 || ! (fix (MaxNumOutliers) == MaxNumOutliers)) error (strcat ("isoutlier: maximum outlier count must be a", ... " positive integer scalar.")); endif varargin([1:2]) = []; otherwise error ("isoutlier: invalid input argument."); endswitch elseif (isnumeric (varargin{1})) dim = varargin{1}; if (! fix (dim) == dim || dim < 1 || ! isscalar (dim) || ! isscalar (varargin{1})) error ("isoutlier: DIM must be a positive integer scalar."); endif varargin(1) = []; else error ("isoutlier: invalid input argument."); endif endwhile ## Find 1st operating dimension (if empty) if (isempty (dim)) szx = size (x); (dim = find (szx != 1, 1)) || (dim = 1); endif ## Check for valid WINDOW unless Sample Points are given if (isempty (SamplePoints) && ! isempty (window)) if (! all (fix (window) == window)) error (strcat ("isoutlier: WINDOW must be a positive integer", ... " scalar or a two-element vector of positive", ... " integers, unless SamplePoints are defined.")); endif endif ## Check for valid value of ThresholdFactor for 'grubbs' and 'geds' methods if (any (strcmpi (method, {'grubbs', 'gesd'})) && ThresholdFactor > 1) error (strcat ("isoutlier: threshold factor must be in [0 1]", ... " range for 'grubbs' and 'gesd' methods.")); endif ## Switch methods switch method case 'median' [L, U, C] = median_method (x, dim, ThresholdFactor, c); TF = x < L | x > U; case 'mean' [L, U, C] = mean_method (x, dim, ThresholdFactor); TF = x < L | x > U; case 'quartiles' [L, U, C] = quartiles_method (x, dim, ThresholdFactor); TF = x < L | x > U; case 'grubbs' [TF, L, U, C] = grubbs_method (x, dim, ThresholdFactor); case 'gesd' [L, U, C] = gesd_method (x, dim, ThresholdFactor, MaxNumOutliers); TF = x < L | x > U; case 'movmedian' sp = SamplePoints; [L, U, C] = movmedian_method (x, dim, ThresholdFactor, c, window, sp); TF = x < L | x > U; case 'movmean' sp = SamplePoints; [L, U, C] = movmean_method (x, dim, ThresholdFactor, window, sp); TF = x < L | x > U; case 'percentiles' [L, U, C] = percentiles_method (x, dim, threshold); TF = x < L | x > U; endswitch endfunction ## Find lower and upper outlier thresholds with median method function [L, U, C] = median_method (x, dim, ThresholdFactor, c) C = median (x, dim, 'omitnan'); sMAD = c * mad (x, 1, dim); L = C - ThresholdFactor * sMAD; U = C + ThresholdFactor * sMAD; endfunction ## Find lower and upper outlier thresholds with mean method function [L, U, M] = mean_method (x, dim, ThresholdFactor) M = mean (x, dim, 'omitnan'); S = std (x, [], dim, 'omitnan'); L = M - ThresholdFactor * S; U = M + ThresholdFactor * S; endfunction ## Find lower and upper outlier thresholds with quartiles method function [L, U, C] = quartiles_method (x, dim, ThresholdFactor) Q = quantile (x, dim); C = Q(3); L = Q(2) - (Q(4) - Q(2)) * ThresholdFactor; U = Q(4) + (Q(4) - Q(2)) * ThresholdFactor; endfunction ## Find lower and upper outlier thresholds with grubbs method function [TF, L, U, C] = grubbs_method (x, dim, ThresholdFactor) ## Move the desired dim to be the 1st dimension (rows) szx = size (x); # size of dimensions N = szx(dim); # elements in operating dimension nd = length (szx); # number of dimensions dperm = [dim, 1:(dim-1), (dim+1):nd]; # permutation of dimensions x = permute (x, dperm); # permute dims to first dimension ncols = prod (szx(dperm(2:end))); # rest of dimensions as single column x = reshape (x, N, ncols); # reshape input ## Create return matrices L = zeros ([1, szx(dperm(2:end))]); U = L; C = L; TF = false (size (x)); ## Apply processing to each column for i = 1:ncols tmp_x = x(:,i); TFvec = [(i-1)*size(x,1)+1:i*size(x,1)]; TFvec(isnan (tmp_x)) = []; tmp_x(isnan (tmp_x)) = []; ## Search for outliers (one at a time) while (true) ## Get descriptive statistics n = length (tmp_x); C(i) = mean (tmp_x); S = std (tmp_x); ## Locate maximum deviation from mean dif_x = abs (tmp_x - C(i)); max_x = max (dif_x); loc_x = find (dif_x == max_x, 1); ## Calculate Grubbs's critical value t_crit = tinv (ThresholdFactor / (2 * n), n - 2); G_crit = ((n - 1) / sqrt (n)) * abs (t_crit) / sqrt (n - 2 + t_crit ^ 2); ## Check hypothesis if (max_x / S > G_crit) tmp_x(loc_x) = []; TF(TFvec(loc_x)) = true; TFvec(loc_x) = []; else break; endif endwhile L(i) = C(i) - S * G_crit; U(i) = C(i) + S * G_crit; endfor ## Restore shape TF = ipermute (TF, dperm); L = ipermute (L, dperm); U = ipermute (U, dperm); C = ipermute (C, dperm); endfunction ## Find lower and upper outlier thresholds with gesd method function [L, U, C] = gesd_method (x, dim, ThresholdFactor, MaxNumOutliers) ## Add default value in MaxNumOutliers (if empty) szx = size (x); N = szx(dim); if (isempty (MaxNumOutliers)) MaxNumOutliers = ceil (N * 0.1); endif ## Move the desired dim to be the 1st dimension (rows) nd = length (szx); # number of dimensions dperm = [dim, 1:(dim-1), (dim+1):nd]; # permutation of dimensions x = permute (x, dperm); # permute dims to first dimension ncols = prod (szx(dperm(2:end))); # rest of dimensions as single column x = reshape (x, N, ncols); # reshape input ## Create return matrices L = zeros ([1, szx(dperm(2:end))]); U = L; C = L; TF = false (size (x)); ## Apply processing to each column for i = 1:ncols tmp_x = x(:,i); vec_x = [(i-1)*size(x,1)+1:i*size(x,1)]; vec_x(isnan (tmp_x)) = []; tmp_x(isnan (tmp_x)) = []; n = length (tmp_x); if (n > 1) mean_x = zeros (MaxNumOutliers,1); S = zeros (MaxNumOutliers,1); lambda = zeros (MaxNumOutliers,1); R = zeros (MaxNumOutliers,1); Ridx = zeros (MaxNumOutliers,1); ## Search for given outliers for j = 1:MaxNumOutliers ## Get descriptive statistics mean_x(j) = mean (tmp_x); S(j) = std (tmp_x); ## Locate maximum deviation from mean dif_x = abs (tmp_x - mean_x(j)); max_x = max (dif_x); loc_x = find (dif_x == max_x, 1); ## Calculate R R(j) = max_x / S(j); tmp_x(loc_x) = []; Ridx(j) = vec_x(loc_x); vec_x(loc_x) = []; ## Calculate lambda pp = 1 - ThresholdFactor / (2 * (n - j + 1)); t = tinv (pp, n - j - 1); lambda(j) = (n - j) * t / sqrt ((n - j - 1 + t .^ 2) * (n - j + 1)); endfor ## Find largest index idx = find (R > lambda, 1, 'last'); if (isempty (idx)) TFidx = 1; else TFidx = min (idx + 1, MaxNumOutliers); endif L(i) = mean_x(TFidx) - S(TFidx) * lambda(TFidx); U(i) = mean_x(TFidx) + S(TFidx) * lambda(TFidx); C(i) = mean_x(TFidx); endif endfor ## Restore shape L = ipermute (L, dperm); U = ipermute (U, dperm); C = ipermute (C, dperm); endfunction ## Find lower and upper outlier thresholds with movmedian method function [L, U, C] = movmedian_method (x, dim, ThresholdFactor, c, window, sp); szx = size (x); N = szx(dim); ## Constrain window to the element in the operating dimension if (numel (window) == 1 && window > N) window = N; elseif (numel (window) == 2 && sum (window) > N) window = N; endif if (isempty (sp)) FCN = @(x) median (x, 'omitnan'); C = movfun (FCN, x, window, 'dim', dim); FCN = @(x) mad (x, 1); MAD = movfun (FCN, x, window, 'dim', dim); else ## Check that sample points(sp) have the N elements if (numel (sp) != N) error (strcat ("isoutlier: sample points must have the same size", ... " as the operating dimension.")); endif ## Move the desired dim to be the 1st dimension (rows) nd = length (szx); # number of dimensions dperm = [dim, 1:(dim-1), (dim+1):nd]; # permutation of dimensions x = permute (x, dperm); # permute dims to first dimension ncols = prod (szx(dperm(2:end))); # rest of dimensions as single column x = reshape (x, N, ncols); # reshape input ## Find beg+end from window if (numel (window) == 2) w_lo = window(1); w_hi = window(2); else if (mod (window, 2) == 1) w_lo = w_hi = (window - 1) / 2; else w_lo = window / 2; w_hi = w_lo - 1; endif endif ## Create return matrices C = zeros (size (x)); MAD = C; for i = 1:ncols tmp_x = x(:,i); for j = 1:N cp = sp - sp(j); nb = length (cp(cp < 0 & cp >= -w_lo)); na = length (cp(cp > 0 & cp <= w_hi)); sp_ind = [j-nb:j+na]; C(j,i) = median (tmp_x(sp_ind), 'omitnan'); MAD(j,i) = mad (tmp_x(sp_ind), 1); endfor endfor ## Restore shape C = ipermute (C, dperm); MAD = ipermute (MAD, dperm); endif ## Compute scaled MAD sMAD = c * MAD; L = C - ThresholdFactor * sMAD; U = C + ThresholdFactor * sMAD; endfunction ## Find lower and upper outlier thresholds with movmean method function [L, U, M] = movmean_method (x, dim, ThresholdFactor, window, sp); ## Constrain window to the element in the operating dimension szx = size (x); N = szx(dim); if (numel (window) == 1 && window > N) window = N; elseif (numel (window) == 2 && sum (window) > N) window = N; endif if (isempty (sp)) FCN = @(x) mean (x, 'omitnan'); M = movfun (FCN, x, window, 'dim', dim); FCN = @(x) std (x, [], 'omitnan'); S = movfun (FCN, x, window, 'dim', dim); else ## Check that sample points(sp) have the N elements if (numel (sp) != N) error (strcat ("isoutlier: sample points must have the same size", ... " as the operating dimension.")); endif ## Move the desired dim to be the 1st dimension (rows) nd = length (szx); # number of dimensions dperm = [dim, 1:(dim-1), (dim+1):nd]; # permutation of dimensions x = permute (x, dperm); # permute dims to first dimension ncols = prod (szx(dperm(2:end))); # rest of dimensions as single column x = reshape (x, N, ncols); # reshape input ## Find beg+end from window if (numel (window) == 2) w_lo = window(1); w_hi = window(2); else if (mod (window, 2) == 1) w_lo = w_hi = (window - 1) / 2; else w_lo = window / 2; w_hi = w_lo - 1; endif endif ## Create return matrices M = zeros (size (x)); S = M; for i = 1:ncols tmp_x = x(:,i); for j = 1:N cp = sp - sp(j); nb = length (cp(cp < 0 & cp >= -w_lo)); na = length (cp(cp > 0 & cp <= w_hi)); sp_ind = [j-nb:j+na]; M(j,i) = mean (tmp_x(sp_ind), 'omitnan'); S(j,i) = std (tmp_x(sp_ind), [], 'omitnan'); endfor endfor ## Restore shape M = ipermute (M, dperm); S = ipermute (S, dperm); endif L = M - ThresholdFactor * S; U = M + ThresholdFactor * S; endfunction ## Find lower and upper outlier thresholds with percentiles method function [L, U, C] = percentiles_method (x, dim, threshold) P = [threshold(1)/100, threshold(2)/100]; Q = quantile (x, P, dim); L = Q(1); U = Q(2); C = (L + U) / 2; endfunction %!demo %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! TF = isoutlier (A, 'mean') %!demo %! ## Use a moving detection method to detect local outliers in a sine wave %! %! x = -2*pi:0.1:2*pi; %! A = sin (x); %! A(47) = 0; %! time = datenum (2023,1,1,0,0,0) + (1/24)*[0:length(x)-1] - 730485; %! TF = isoutlier (A, 'movmedian', 5*(1/24), 'SamplePoints', time); %! plot (time, A) %! hold on %! plot (time(TF), A(TF), 'x') %! datetick ('x', 20, 'keepticks') %! legend ('Original Data', 'Outlier Data') %!demo %! ## Locate an outlier in a vector of data and visualize the outlier %! %! x = 1:10; %! A = [60 59 49 49 58 100 61 57 48 58]; %! [TF, L, U, C] = isoutlier (A); %! plot (x, A); %! hold on %! plot (x(TF), A(TF), 'x'); %! xlim ([1,10]); %! line ([1,10], [L, L], 'Linestyle', ':'); %! text (1.1, L-2, 'Lower Threshold'); %! line ([1,10], [U, U], 'Linestyle', ':'); %! text (1.1, U-2, 'Upper Threshold'); %! line ([1,10], [C, C], 'Linestyle', ':'); %! text (1.1, C-3, 'Center Value'); %! legend ('Original Data', 'Outlier Data'); ## Output validation tests (checked against MATLAB) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! assert_equal (isoutlier (A, 'mean'), logical ([zeros(1,8) 1 zeros(1,6)])) %! assert_equal (isoutlier (A, 'median'), ... %! logical ([zeros(1,3) 1 zeros(1,4) 1 zeros(1,6)])) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! [TF, L, U, C] = isoutlier (A, 'mean'); %! assert_equal (L, -109.2459044922864, 1e-12) %! assert_equal (U, 264.9792378256198, 1e-12) %! assert_equal (C, 77.8666666666666, 1e-12) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! [TF, L, U, C] = isoutlier (A, 'median'); %! assert_equal (L, 50.104386688966386, 1e-12) %! assert_equal (U, 67.895613311033610, 1e-12) %! assert_equal (C, 59) %!test %! A = magic (5) + diag (200*ones (1,5)); %! T = logical (eye (5)); %! assert_equal (isoutlier (A, 2), T) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! [TF, L, U, C] = isoutlier (A, 'movmedian', 5); %! l = [54.5522, 52.8283, 54.5522, 54.5522, 54.5522, 53.5522, 53.5522, ... %! 53.5522, 47.6566, 56.5522, 57.5522, 56.5522, 51.1044, 52.3283, 53.5522]; %! u = [63.4478, 66.1717, 63.4478, 63.4478, 63.4478, 62.4478, 62.4478, ... %! 62.4478, 74.3434, 65.4478, 66.4478, 65.4478, 68.8956, 65.6717, 62.4478]; %! c = [59, 59.5, 59, 59, 59, 58, 58, 58, 61, 61, 62, 61, 60, 59, 58]; %! assert_equal (L, l, 1e-4) %! assert_equal (U, u, 1e-4) %! assert_equal (C, c) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! [TF, L, U, C] = isoutlier (A, 'movmedian', 5, 'SamplePoints', [1:15]); %! l = [54.5522, 52.8283, 54.5522, 54.5522, 54.5522, 53.5522, 53.5522, ... %! 53.5522, 47.6566, 56.5522, 57.5522, 56.5522, 51.1044, 52.3283, 53.5522]; %! u = [63.4478, 66.1717, 63.4478, 63.4478, 63.4478, 62.4478, 62.4478, ... %! 62.4478, 74.3434, 65.4478, 66.4478, 65.4478, 68.8956, 65.6717, 62.4478]; %! c = [59, 59.5, 59, 59, 59, 58, 58, 58, 61, 61, 62, 61, 60, 59, 58]; %! assert_equal (L, l, 1e-4) %! assert_equal (U, u, 1e-4) %! assert_equal (C, c) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! [TF, L, U, C] = isoutlier (A, 'movmean', 5); %! l = [54.0841, 6.8872, 11.5608, 12.1518, 11.0210, 10.0112, -218.2840, ... %! -217.2375, -215.1239, -213.4890, -211.3264, 55.5800, 52.9589, ... %! 52.5979, 51.0627]; %! u = [63.2492, 131.1128, 122.4392, 122.2482, 122.5790, 122.7888, 431.0840, ... %! 430.8375, 430.3239, 429.8890, 429.3264, 65.6200, 66.6411, 65.9021, ... %! 66.9373]; %! c = [58.6667, 69, 67, 67.2, 66.8, 66.4, 106.4, 106.8, 107.6, 108.2, 109, ... %! 60.6, 59.8, 59.25, 59]; %! assert_equal (L, l, 1e-4) %! assert_equal (U, u, 1e-4) %! assert_equal (C, c, 1e-4) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! [TF, L, U, C] = isoutlier (A, 'movmean', 5, 'SamplePoints', [1:15]); %! l = [54.0841, 6.8872, 11.5608, 12.1518, 11.0210, 10.0112, -218.2840, ... %! -217.2375, -215.1239, -213.4890, -211.3264, 55.5800, 52.9589, ... %! 52.5979, 51.0627]; %! u = [63.2492, 131.1128, 122.4392, 122.2482, 122.5790, 122.7888, 431.0840, ... %! 430.8375, 430.3239, 429.8890, 429.3264, 65.6200, 66.6411, 65.9021, ... %! 66.9373]; %! c = [58.6667, 69, 67, 67.2, 66.8, 66.4, 106.4, 106.8, 107.6, 108.2, 109, ... %! 60.6, 59.8, 59.25, 59]; %! assert_equal (L, l, 1e-4) %! assert_equal (U, u, 1e-4) %! assert_equal (C, c, 1e-4) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! [TF, L, U, C] = isoutlier (A, 'gesd'); %! assert_equal (TF, logical ([0 0 0 1 0 0 0 0 1 0 0 0 0 0 0])) %! assert_equal (L, 34.235977035439944, 1e-12) %! assert_equal (U, 89.764022964560060, 1e-12) %! assert_equal (C, 62) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! [TF, L, U, C] = isoutlier (A, 'gesd', 'ThresholdFactor', 0.01); %! assert_equal (TF, logical ([0 0 0 1 0 0 0 0 1 0 0 0 0 0 0])) %! assert_equal (L, 31.489256770616173, 1e-12) %! assert_equal (U, 92.510743229383820, 1e-12) %! assert_equal (C, 62) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! [TF, L, U, C] = isoutlier (A, 'gesd', 'ThresholdFactor', 5e-10); %! assert_equal (TF, logical ([0 0 0 0 0 0 0 0 1 0 0 0 0 0 0])) %! assert_equal (L, 23.976664158788935, 1e-12) %! assert_equal (U, 100.02333584121110, 1e-12) %! assert_equal (C, 62) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! [TF, L, U, C] = isoutlier (A, 'grubbs'); %! assert_equal (TF, logical ([0 0 0 1 0 0 0 0 1 0 0 0 0 0 0])) %! assert_equal (L, 54.642809574646606, 1e-12) %! assert_equal (U, 63.511036579199555, 1e-12) %! assert_equal (C, 59.076923076923080, 1e-12) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! [TF, L, U, C] = isoutlier (A, 'grubbs', 'ThresholdFactor', 0.01); %! assert_equal (TF, logical ([0 0 0 1 0 0 0 0 1 0 0 0 0 0 0])) %! assert_equal (L, 54.216083184201850, 1e-12) %! assert_equal (U, 63.937762969644310, 1e-12) %! assert_equal (C, 59.076923076923080, 1e-12) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! [TF, L, U, C] = isoutlier (A, 'percentiles', [10 90]); %! assert_equal (TF, logical ([0 0 0 0 0 0 0 0 1 0 0 0 0 0 0])) %! assert_equal (L, 57) %! assert_equal (U, 100) %! assert_equal (C, 78.5) %!test %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %! [TF, L, U, C] = isoutlier (A, 'percentiles', [20 80]); %! assert_equal (TF, logical ([1 0 0 1 0 0 1 0 1 0 0 0 0 0 1])) %! assert_equal (L, 57.5) %! assert_equal (U, 62) %! assert_equal (C, 59.75) ## Test input validation %!shared A %! A = [57 59 60 100 59 58 57 58 300 61 62 60 62 58 57]; %!error ... %! isoutlier (A, 'movmedian', 0); %!error ... %! isoutlier (A, 'movmedian', []); %!error ... %! isoutlier (A, 'movmedian', [2 3 4]); %!error ... %! isoutlier (A, 'movmedian', 1.4); %!error ... %! isoutlier (A, 'movmedian', [0 1]); %!error ... %! isoutlier (A, 'movmedian', [2 -1]); %!error ... %! isoutlier (A, 'movmedian', {2 3}); %!error ... %! isoutlier (A, 'movmedian', 'char'); %! %!error ... %! isoutlier (A, 'movmean', 0); %!error ... %! isoutlier (A, 'movmean', []); %!error ... %! isoutlier (A, 'movmean', [2 3 4]); %!error ... %! isoutlier (A, 'movmean', 1.4); %!error ... %! isoutlier (A, 'movmean', [0 1]); %!error ... %! isoutlier (A, 'movmean', [2 -1]); %!error ... %! isoutlier (A, 'movmean', {2 3}); %!error ... %! isoutlier (A, 'movmean', 'char'); %! %!error ... %! isoutlier (A, 'percentiles', [-1 90]); %!error ... %! isoutlier (A, 'percentiles', [10 -90]); %!error ... %! isoutlier (A, 'percentiles', [90]); %!error ... %! isoutlier (A, 'percentiles', [90 20]); %!error ... %! isoutlier (A, 'percentiles', [90 20]); %!error ... %! isoutlier (A, 'percentiles', [10 20 90]); %!error ... %! isoutlier (A, 'percentiles', {10 90}); %!error ... %! isoutlier (A, 'percentiles', 'char'); %! %!error ... %! isoutlier (A, 'movmean', 5, 'SamplePoints', ones (3,15)); %!error ... %! isoutlier (A, 'movmean', 5, 'SamplePoints', 15); %!error ... %! isoutlier (A, 'movmean', 5, 'SamplePoints', [1,1:14]); %!error ... %! isoutlier (A, 'movmean', 5, 'SamplePoints', [2,1,3:15]); %!error ... %! isoutlier (A, 'movmean', 5, 'SamplePoints', [1:14]); %! %!error ... %! isoutlier (A, 'movmean', 5, 'ThresholdFactor', [1:14]); %!error ... %! isoutlier (A, 'movmean', 5, 'ThresholdFactor', -1); %!error ... %! isoutlier (A, 'gesd', 'ThresholdFactor', 3); %!error ... %! isoutlier (A, 'grubbs', 'ThresholdFactor', 3); %! %!error ... %! isoutlier (A, 'movmean', 5, 'MaxNumOutliers', [1:14]); %!error ... %! isoutlier (A, 'movmean', 5, 'MaxNumOutliers', -1); %!error ... %! isoutlier (A, 'movmean', 5, 'MaxNumOutliers', 0); %!error ... %! isoutlier (A, 'movmean', 5, 'MaxNumOutliers', 1.5); %! %!error ... %! isoutlier (A, {'movmean'}, 5, 'SamplePoints', [1:15]); %!error isoutlier (A, {1}); %!error isoutlier (A, true); %!error isoutlier (A, false); %!error isoutlier (A, 0); %!error isoutlier (A, [1 2]); %!error isoutlier (A, -2); statistics-release-1.9.2/inst/Data_Manipulation/multiway.m000066400000000000000000000317301524624707500237520ustar00rootroot00000000000000## Copyright (C) 2025 Swayam Shah ## Copyright (C) 2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{groupindex} =} multiway (@var{numbers}, @var{num_parts}) ## @deftypefnx {statistics} {@var{groupindex} =} multiway (@var{numbers}, @var{num_parts}, @var{method}) ## @deftypefnx {statistics} {[@var{groupindex}, @var{partition}] =} multiway (@dots{}) ## @deftypefnx {statistics} {[@var{groupindex}, @var{partition}, @var{groupsizes}] =} multiway (@dots{}) ## ## Solve the multiway number partitioning problem. ## ## @code{@var{groupindex} = multiway (@var{numbers}, @var{num_parts})} splits ## a set of numbers in @var{numbers} into a number of subsets specified in ## @var{num_parts} such that the sums of the subsets are nearly as equal as ## possible and returns a vector of group indices in @var{groupindex} with each ## index corresponding to the set of numbers provided as input. ## ## @itemize ## @item @var{numbers} is a vector of positive real numbers to be partitioned. ## @item @var{num_parts} is a positive integer scalar specifying the number of ## partitions (subsets) to split the numbers into. ## @end itemize ## ## @code{@var{groupindex} = multiway (@var{numbers}, @var{num_parts}, ## @var{method})} also specifies the algorithm used for partitioning the set of ## numbers. By default, @code{multiway} uses the complete Karmarkar-Karp ## algorithm, when the set of numbers contains up to 10 elements and the ## requested number of subsets does not exceed 5, otherwise it defaults to the ## greedy algorithm, which is optimized for speed, but may not return the ## optimal partitioning. The following methods are supported: ## ## @itemize ## @item @qcode{'greedy'} (Greedy algorithm) ## @item @qcode{'completeKK'} (Complete Karmarkar-Karp algorithm) ## @end itemize ## ## The @code{multiway} function may return up to three output arguments ## described below: ## ## @itemize ## @item ## @var{groupindex}: A vector of the same length as @var{numbers} containing ## the group index (from 1 to @var{num_parts}) for each number. ## @item ## @var{partition}: A cell array of length @var{num_parts} with each cell ## containing the numbers assigned to that partition. ## @item ## @var{groupsizes}: A vector of the sums of the numbers in each partition. ## @end itemize ## ## Example: ## @example ## @group ## numbers = [4, 5, 6, 7, 8]; ## num_parts = 2; ## [groupindex, partition, groupsizes] = multiway (numbers, num_parts); ## @end group ## @end example ## ## @seealso{cvpartition} ## @end deftypefn function [gindex, partition, gsize] = multiway (numbers, num_parts, method) if (nargin < 2) error ("multiway: too few input arguments."); elseif (nargin == 2) if (numel (numbers) <= 10 && num_parts <= 5) method = 'completeKK'; else method = 'greedy'; endif elseif (! (ischar (method) && isvector (method))) error ("multiway: METHOD value must be a character vector."); endif ## Validate numbers vector if (! isvector (numbers) || isempty (numbers)) error ("multiway: NUMBERS must be a non-empty vector."); endif if (! isnumeric (numbers) || any (isnan (numbers))) error ("multiway: NUMBERS must be numeric and cannot contain NaN values."); endif if (any (numbers < 0)) error ("multiway: NUMBERS must be non-negative."); endif ## Validate number of partitions if (! isscalar (num_parts)) error ("multiway: NUM_PARTS must be a scalar."); endif if (! isnumeric (num_parts) || ! isreal (num_parts)) error ("multiway: NUM_PARTS must be a real numeric value."); endif if (num_parts < 1 || fix (num_parts) != num_parts) error ("multiway: NUM_PARTS must be a positive integer."); endif if (num_parts > numel (numbers)) error (strcat ("multiway: NUM_PARTS cannot be greater than", ... " number of elements in NUMBERS.")); endif ## Select method switch (lower (method)) case 'completekk' [gindex, partition, gsize] = completeKK (numbers, num_parts); case 'greedy' [gindex, partition, gsize] = greedy (numbers, num_parts); otherwise error ("multiway: unsupported method '%s'.", method); endswitch endfunction function [groupindex, partition, groupsizes] = greedy (numbers, num_parts) [sorted_numbers, sorted_indices] = sort (numbers, 'descend'); n = numel (sorted_numbers); partition = cell (1, num_parts); sums = zeros (1, num_parts); group_assignment = zeros (1, n); for i = 1:n [min_sum, min_idx] = min (sums); partition{min_idx}(end+1) = sorted_numbers(i); sums(min_idx) = min_sum + sorted_numbers(i); group_assignment(i) = min_idx; endfor groupindex = zeros (size (numbers)); groupindex(sorted_indices) = group_assignment; groupsizes = sums; if (iscolumn (numbers)) groupsizes = groupsizes'; endif endfunction function [groupindex, partition, groupsizes] = completeKK (numbers, num_parts) [gidx_g, part_g, gsize_g] = greedy (numbers, num_parts); best_diff = max (gsize_g) - min (gsize_g); best_partition = part_g; groupindex = gidx_g; if (best_diff == 0) partition = best_partition; groupsizes = gsize_g; if (iscolumn (numbers)) groupsizes = groupsizes'; endif return; endif sorted_numbers = numbers(:).'; [sorted_numbers, sorted_indices] = sort (sorted_numbers, 'descend'); n = numel (sorted_numbers); total = sum (sorted_numbers); average = total / num_parts; function rec (k, current_sums, current_assign) if (k > n) this_diff = max (current_sums) - min (current_sums); if (this_diff < best_diff) best_diff = this_diff; for jj = 1:num_parts best_partition{jj} = sorted_numbers(current_assign == jj); endfor endif return; endif rem_max = sorted_numbers(k); this_min = min (current_sums); lb_max = max (max (current_sums), this_min + rem_max); lb_diff = max (0, lb_max - average); if (lb_diff >= best_diff) return; endif [~, perm] = sort (current_sums); for jj = 1:num_parts j = perm (jj); new_sums = current_sums; new_sums(j) += sorted_numbers(k); new_max = max (new_sums); if (k < n) next_rem_max = sorted_numbers(k + 1); sub_min = min (new_sums); sub_lb_max = max (new_max, sub_min + next_rem_max); else sub_lb_max = new_max; endif sub_lb_diff = max (0, sub_lb_max - average); if (sub_lb_diff >= best_diff) continue; endif new_assign = current_assign; new_assign(k) = j; rec (k + 1, new_sums, new_assign); endfor endfunction rec (1, zeros (1, num_parts), zeros (1, n)); partition = best_partition; groupsizes = cellfun (@sum, partition); idx_cell = convert_to_indices (partition, numbers); groupindex = zeros (size (numbers)); for j = 1:num_parts groupindex(idx_cell{j}) = j; endfor if (iscolumn (numbers)) groupsizes = groupsizes'; endif endfunction function indices = convert_to_indices (parts, original_numbers) indices = cell (size (parts)); numbers_copy = original_numbers(:)'; for i = 1:numel (parts) idxs = []; for val = parts{i} pos = find (numbers_copy == val, 1); if (isempty (pos)) error ("Value %g not found during index conversion", val); endif idxs = [idxs, pos]; numbers_copy(pos) = NaN; endfor indices{i} = idxs; endfor endfunction ## Test completeKK method %!test %! numbers = [4, 5, 6, 7, 8]; %! num_parts = 2; %! [groupindex, partition, groupsizes] = multiway (numbers, num_parts, 'completeKK'); %! assert_equal (sort (cellfun (@sum, partition)), sort ([15, 15])); %!test %! numbers = [1, 2, 3, 4, 5, 6]; %! num_parts = 3; %! [groupindex, partition, groupsizes] = multiway (numbers, num_parts, 'completeKK'); %! assert_equal (sort (cellfun (@sum, partition)), sort ([7, 7, 7])); %!test %! numbers = [24, 21, 18, 17, 12, 11, 8, 2]; %! num_parts = 3; %! [groupindex, partition, groupsizes] = multiway (numbers, num_parts, 'completeKK'); %! assert_equal (sort (cellfun (@sum, partition)), sort ([38, 38, 37])); %!test %! numbers = [10, 10, 10]; %! num_parts = 3; %! [~, partition] = multiway (numbers, num_parts, 'completeKK'); %! assert_equal (sort (cellfun (@sum, partition)), [10, 10, 10]); %!test %! numbers = 1:10; %! num_parts = 2; %! [~, partition] = multiway (numbers, num_parts, 'completeKK'); %! assert_equal (sort (cellfun (@sum, partition)), [27, 28]); ## Test greedy method %!test %! numbers = [4, 5, 6, 7, 8]; %! num_parts = 2; %! [groupindex, partition, groupsizes] = multiway (numbers, num_parts, 'greedy'); %! assert_equal (sort (cellfun (@sum, partition)), sort ([13, 17])); %!test %! numbers = [1, 2, 3, 4, 5, 6]; %! num_parts = 3; %! [groupindex, partition, groupsizes] = multiway (numbers, num_parts, 'greedy'); %! assert_equal (sort (cellfun (@sum, partition)), sort ([7, 7, 7])); %!test %! numbers = [10, 7, 5, 5, 6, 4, 10, 11, 12, 9, 10, 4, 3, 4, 5]; %! num_parts = 4; %! [groupindex, partition, groupsizes] = multiway (numbers, num_parts, 'greedy'); %! assert_equal (sort (cellfun (@sum, partition)), sort ([27, 27, 27, 24])); %!test %! numbers = [24, 21, 18, 17, 12, 11, 8, 2]; %! num_parts = 3; %! [groupindex, partition, groupsizes] = multiway (numbers, num_parts, 'greedy'); %! assert_equal (sort (cellfun (@sum, partition)), sort ([35, 37, 41])); %!test %! numbers = [10, 10, 10]; %! num_parts = 3; %! [~, partition] = multiway (numbers, num_parts, 'greedy'); %! assert_equal (sort (cellfun (@sum, partition)), [10, 10, 10]); %!test %! numbers = 1:10; %! num_parts = 2; %! [~, partition] = multiway (numbers, num_parts, 'greedy'); %! assert_equal (sort (cellfun (@sum, partition)), [27, 28]); ## Test algorithm switch %!test %! grpidx_ckk = multiway ([3 2 4 3 9 3 64], 3); %! grpidx_greedy = multiway ([3 2 4 3 9 3 64], 3, 'greedy'); %! assert_equal (isequal (grpidx_ckk, grpidx_greedy), false); ## Test column vector input %!test %! numbers = [4; 5; 6; 7; 8]; %! num_parts = 2; %! [groupindex, partition, groupsizes] = multiway (numbers, num_parts, 'completeKK'); %! assert_equal (iscolumn (groupindex), true) %! assert_equal (iscolumn (groupsizes), true); %! assert_equal (sort (cellfun (@sum, partition)), sort ([15, 15])); %! numbers = [4; 5; 6; 7; 8]; %! num_parts = 2; %! [groupindex, partition, groupsizes] = multiway (numbers, num_parts, 'greedy'); %! assert_equal (iscolumn (groupindex), true) %! assert_equal (iscolumn (groupsizes), true); %! assert_equal (sort (cellfun (@sum, partition)), sort ([13, 17])); ## Test row vector input %!test %! numbers = [4, 5, 6, 7, 8]; %! num_parts = 2; %! [groupindex, partition, groupsizes] = multiway (numbers, num_parts, 'completeKK'); %! assert_equal (isrow (groupindex), true) %! assert_equal (isrow (groupsizes), true); %! assert_equal (sort (cellfun (@sum, partition)), sort ([15, 15])); %!test %! numbers = [4, 5, 6, 7, 8]; %! num_parts = 2; %! [groupindex, partition, groupsizes] = multiway (numbers, num_parts, 'greedy'); %! assert_equal (isrow (groupindex), true) %! assert_equal (isrow (groupsizes), true); %! assert_equal (sort (cellfun (@sum, partition)), sort ([13, 17])); ## Test input validation %!error multiway () %!error multiway ([1, 2]) %!error ... %! multiway ([1, 2, 3], 2, 1) %!error multiway ([], 2) %!error multiway (ones (2, 2), 2) %!error ... %! multiway ({1, 2, 3}, 2) %!error multiway ([1, -2, 3], 2) %!error ... %! multiway ([1, 2, NaN], 2) %!error multiway ([1,2,3], [1,2]) %!error ... %! multiway ([1, 2, 3], '2') %!error multiway ([1, 2, 3], 0) %!error multiway ([1, 2, 3], 1.5) %!error multiway ([1, 2, 3], -1) %!error ... %! multiway ([1, 2], 3) %!error ... %! multiway ([1,2,3], 2, 'greedyalgo') statistics-release-1.9.2/inst/Data_Manipulation/normalise_distribution.m000066400000000000000000000220511524624707500266630ustar00rootroot00000000000000## Copyright (C) 2011 Alexander Klein ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{NORMALISED} =} normalise_distribution (@var{DATA}) ## @deftypefnx {statistics} {@var{NORMALISED} =} normalise_distribution (@var{DATA}, @var{DISTRIBUTION}) ## @deftypefnx {statistics} {@var{NORMALISED} =} normalise_distribution (@var{DATA}, @var{DISTRIBUTION}, @var{DIMENSION}) ## ## Transform a set of data so as to be N(0,1) distributed according to an idea ## by van Albada and Robinson. ## ## This is achieved by first passing it through its own cumulative distribution ## function (CDF) in order to get a uniform distribution, and then mapping ## the uniform to a normal distribution. ## ## The data must be passed as a vector or matrix in @var{DATA}. ## If the CDF is unknown, then [] can be passed in @var{DISTRIBUTION}, and in ## this case the empirical CDF will be used. ## Otherwise, if the CDFs for all data are known, they can be passed in ## @var{DISTRIBUTION}, ## either in the form of a single function name as a string, ## or a single function handle, ## or a cell array consisting of either all function names as strings, ## or all function handles. ## In the latter case, the number of CDFs passed must match the number ## of rows, or columns respectively, to normalise. ## If the data are passed as a matrix, then the transformation will ## operate either along the first non-singleton dimension, ## or along @var{DIMENSION} if present. ## ## Notes: ## The empirical CDF will map any two sets of data ## having the same size and their ties in the same places after sorting ## to some permutation of the same normalised data: ## @example ## @code{normalise_distribution([1 2 2 3 4])} ## @result{} -1.28 0.00 0.00 0.52 1.28 ## ## @code{normalise_distribution([1 10 100 10 1000])} ## @result{} -1.28 0.00 0.52 0.00 1.28 ## @end example ## ## Original source: ## S.J. van Albada, P.A. Robinson ## "Transformation of arbitrary distributions to the ## normal distribution with application to EEG ## test-retest reliability" ## Journal of Neuroscience Methods, Volume 161, Issue 2, ## 15 April 2007, Pages 205-211 ## ISSN 0165-0270, 10.1016/j.jneumeth.2006.11.004. ## (http://www.sciencedirect.com/science/article/pii/S0165027006005668) ## @end deftypefn function normalised = normalise_distribution (data, distribution, dimension) if (nargin < 1 || nargin > 3) print_usage; elseif (! ismatrix (data) || length (size (data)) > 2) error (strcat ("normalise_distribution: first argument", ... " must be a vector or matrix.")); endif if (nargin >= 2) if (! isempty (distribution)) ## Wrap a single handle in a cell array. if (strcmp (typeinfo (distribution), typeinfo (@(x)(x)))) distribution = {distribution}; ## Do we have a string argument instead? elseif (ischar (distribution)) ## Is it a single string? if (rows (distribution) == 1) temp = str2func ([distribution]); distribution = {temp}; else error (strcat ("normalise_distribution: second argument cannot", ... " contain more than one string unless in a cell", ... " array.")); endif ## Do we have a cell array of distributions instead? elseif (iscell (distribution)) ## Does it consist of strings only? if (all (cellfun (@ischar, distribution))) distribution = cellfun (@str2func, distribution, ... 'UniformOutput', false ); endif ## Does it eventually consist of function handles only if (! all (cellfun (@(h) (strcmp (typeinfo (h), typeinfo ... (@(x)(x)))), distribution))) error (strcat ("normalise_distribution: second argument must", ... " contain either a single function name or", ... " handle or a cell array of either all function", ... " names or handles!")); endif else error ( "Illegal second argument: ", typeinfo ( distribution ) ); endif endif else distribution = []; endif if (nargin == 3) if (! isscalar (dimension) || (dimension != 1 && dimension != 2)) error ("normalise_distribution: third argument must be either 1 or 2."); endif else if (isvector (data) && rows (data) == 1) dimension = 2; else dimension = 1; endif endif trp = (dimension == 2); if (trp) data = data'; endif r = rows (data); c = columns (data); normalised = NA (r, c); ## Do we know the distribution of the sample? if (isempty (distribution)) precomputed_normalisation = []; for k = 1 : columns ( data ) ## Note that this line is in accordance with equation (16) in the ## original text. The author's original program, however, produces ## different values in the presence of ties, namely those you'd ## get replacing "last" by "first". [uniq, indices] = unique (sort (data(:, k)), 'last'); ## Does the sample have ties? if (rows (uniq) != r) ## Transform to uniform, then normal distribution. uniform = ( indices - 1/2 ) / r; normal = norminv ( uniform ); else ## Without ties everything is pretty much straightforward as ## stated in the text. if (isempty (precomputed_normalisation)) precomputed_normalisation = norminv (1 / (2*r) : 1/r : 1 - 1 / (2*r)); endif normal = precomputed_normalisation; endif ## Find the original indices in the unsorted sample. ## This somewhat quirky way of doing it is still faster than ## using a for-loop. [ ignore, ignore, target_indices ] = unique ( data(:, k ) ); ## Put normalised values in the places where they belong. normalised( :, k ) = normal(target_indices); endfor else ## With known distributions, everything boils down to a few lines of code ##The same distribution for all data? if (all (size (distribution) == 1)) normalised = norminv (distribution{1,1}(data)); elseif (length (vec (distribution)) == c) for k = 1 : c normalised(:, k) = norminv (distribution{k}(data)(:, k)); endfor else error (strcat ("normalise_distribution: number of distributions", ... " does not match data size!")); endif endif if (trp) normalised = normalised'; endif endfunction %!test %! v = normalise_distribution ([1 2 3], [], 1); %! assert_equal (v, [0 0 0]) %!test %! v = normalise_distribution ([1 2 3], [], 2); %! assert_equal (v, norminv ([1 3 5] / 6), 3 * eps) %!test %! v = normalise_distribution ([1 2 3]', [], 2); %! assert_equal (v, [0 0 0]') %!test %! v = normalise_distribution ([1 2 3]', [], 1); %! assert_equal (v, norminv ([1 3 5]' / 6), 3 * eps) %!test %! v = normalise_distribution ([1 1 2 2 3 3], [], 2); %! assert_equal (v, norminv ([3 3 7 7 11 11] / 12), 3 * eps) %!test %! v = normalise_distribution ([1 1 2 2 3 3]', [], 1); %! assert_equal (v, norminv ([3 3 7 7 11 11]' / 12), 3 * eps) %!test %! A = randn ( 10 ); %! N = normalise_distribution (A, @normcdf); %! assert_equal (A, N, 10000 * eps) %!test %! A = exprnd (1, 100); %! N = normalise_distribution (A, @(x)(expcdf (x, 1))); %! assert_equal (mean (vec (N)), 0, 0.1) %! assert_equal (std (vec (N)), 1, 0.1) %!test %! A = rand (1000,1); %! N = normalise_distribution (A, {@(x)(unifcdf (x, 0, 1))}); %! assert_equal (mean (vec (N)), 0, 0.2) %! assert_equal (std (vec (N)), 1, 0.1) %!test %! A = [rand(1000,1), randn(1000, 1)]; %! N = normalise_distribution (A, {@(x)(unifcdf (x, 0, 1)), @normcdf}); %! assert_equal (mean (N), [0, 0], 0.2) %! assert_equal (std (N), [1, 1], 0.1) %!test %! A = [rand(1000,1), randn(1000, 1), exprnd(1, 1000, 1)]'; %! N = normalise_distribution (A, {@(x)(unifcdf (x, 0, 1)); @normcdf; @(x)(expcdf (x, 1))}, 2); %! assert_equal (mean (N, 2), [0, 0, 0]', 0.2); %! assert_equal (std (N, [], 2), [1, 1, 1]', 0.1); %!xtest %! A = exprnd (1, 1000, 9); A(300:500, 4:6) = 17; %! N = normalise_distribution (A); %! assert_equal (mean (N), [0 0 0 0.38 0.38 0.38 0 0 0], 0.1); %! assert_equal (var (N), [1 1 1 2.59 2.59 2.59 1 1 1], 0.1); %!test %!error normalise_distribution (zeros (3, 4), ... %! {@(x)(unifcdf (x, 0, 1)); @normcdf; @(x)(expcdf (x,1))}); statistics-release-1.9.2/inst/Data_Manipulation/randsample.m000066400000000000000000000201011524624707500242130ustar00rootroot00000000000000## Copyright (C) 2014 - Nir Krakauer ## Copyright (C) 2025 Andreas Bertsatos ## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program. If not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} randsample (@var{v}, @var{k}) ## @deftypefnx {statistics} {@var{y} =} randsample (@var{v}, @var{k}, @var{replacement}=false) ## @deftypefnx {statistics} {@var{y} =} randsample (@var{v}, @var{k}, @var{replacement}=false, [@var{w}=[]]) ## ## Sample elements from a vector. ## ## Returns @var{k} random elements from a vector @var{v} with @var{n} elements, ## sampled without or with @var{replacement}, with an optional weight vector. ## ## If @var{v} is a scalar, samples from 1:@var{v}. ## ## If a weight vector @var{w} of the same size as @var{v} is specified, the ## probability of each element being sampled is proportional to @var{w}. ## Unlike Matlab's function of the same name, this can be done for sampling with ## or without replacement. ## ## Randomization is performed using rand(). ## ## @seealso{datasample, randperm} ## @end deftypefn function y = randsample (v, k, replacement=false, w=[]) if (isscalar (v) && isnumeric (v) && (round (v) == v) && (v >= 0)) n = v; vector_v = false; elseif (isvector (v) || isscalar (v)) n = length (v); vector_v = true; else error ("randsample: The input v must be a vector or non-negative integer."); endif if (! isscalar (k) || ! isnumeric (k) || round (k) != k) error ("randsample: The input k must be an integer."); endif if (max (0, k) > n && ! replacement) error ("randsample: Sampling without replacement needs k <= n."); endif k = max (0, k); if (! isempty (w)) if (length (w) != n) error ("randsample: the size w (%d) must match the first argument (%d)", ... length (w), n); elseif (! all (w >= 0) || sum (w) <= 0) error ("randsample: the weight vector w must consist of non-negative elements and sum to a positive number"); endif endif if (replacement) # sample with replacement if (isempty (w)) # all elements are equally likely to be sampled y = round (n * rand (1, k) + 0.5); else y = weighted_replacement (k, w); endif else # sample without replacement if (isempty (w)) # all elements are equally likely to be sampled y = randperm (n, k); else # use "accept-reject"-like sampling if (k > nnz (w)) error ("randsample: not enough non-zero weights for sampling without replacement"); endif y = weighted_replacement (k, w); while (1) [yy, idx] = sort (y); # Note: sort keeps order of equal elements. Idup = [false, (diff (yy)==0)]; if (! any (Idup)) break else Idup(idx) = Idup; # find duplicates in original vector w(y) = 0; # don't permit resampling ## remove duplicates, then sample again y = [y(! Idup), (weighted_replacement (sum (Idup), w))]; endif endwhile endif endif if vector_v y = v(y); if (iscolumn (v)) y = y(:); elseif (isrow (v)) y = y(:).'; endif else y = y(:); endif endfunction function y = weighted_replacement (k, w) w = w / sum (w); w = [0, (cumsum (w(:))')]; ## distribute k uniform random deviates based on the given weighting y = arrayfun (@(x) find (w <= x, 1, 'last'), rand (1, k)); endfunction %!test %! n = 20; %! k = 5; %! x = randsample (n, k); %! assert_equal (size (x), [k 1]); %! x = randsample (n, k, true); %! assert_equal (size (x), [k 1]); %! x = randsample (n, k, false); %! assert_equal (size (x), [k 1]); %! x = randsample (n, k, true, ones (n, 1)); %! assert_equal (size (x), [k 1]); %! x = randsample (1:n, k); %! assert_equal (size (x), [1 k]); %! x = randsample (1:n, k, true); %! assert_equal (size (x), [1 k]); %! x = randsample (1:n, k, false); %! assert_equal (size (x), [1 k]); %! x = randsample (1:n, k, true, ones (n, 1)); %! assert_equal (size (x), [1 k]); %! x = randsample ((1:n)', k); %! assert_equal (size (x), [k 1]); %! x = randsample ((1:n)', k, true); %! assert_equal (size (x), [k 1]); %! x = randsample ((1:n)', k, false); %! assert_equal (size (x), [k 1]); %! x = randsample ((1:n)', k, true, ones (n, 1)); %! assert_equal (size (x), [k 1]); %! n = 10; %! k = 100; %! x = randsample (n, k, true, 1:n); %! assert_equal (size (x), [k 1]); %! x = randsample ((1:n)', k, true); %! assert_equal (size (x), [k 1]); %! x = randsample (k, k, false, 1:k); %! assert_equal (size (x), [k 1]); %!test %! n = 20; %! k = 5; %! p = 1:n; %! x = randsample (p, k); %! assert_equal (isnumeric (x), true); %! assert_equal (size (x), [1 k]); %! x = randsample (p, k, true); %! assert_equal (isnumeric (x), true); %! assert_equal (size (x), [1 k]); %! x = randsample (p, k, false); %! assert_equal (isnumeric (x), true); %! assert_equal (size (x), [1 k]); %! k = 30; %! x = randsample (p, k, true); %! assert_equal (isnumeric (x), true); %! assert_equal (size (x), [1 k]); %!test %! p = categorical ({'a', 'b', 'c', 'd', 'a'}); %! k = 3; %! x = randsample (p, k, true); %! assert_equal (iscategorical (x), true); %! assert_equal (size (x), [1 k]); %! x = randsample (p, k, false); %! assert_equal (iscategorical (x), true); %! assert_equal (size (x), [1 k]); %! k = 30; %! x = randsample (p, k, true, ones (length (p),1)); %! assert_equal (iscategorical (x), true); %! assert_equal (size (x), [1 k]); %!test %! p = {'a', 'b', 'c', 'd', 'a'}; %! k = 2; %! x = randsample (p, k, true); %! assert_equal (iscell (x), true); %! assert_equal (size (x), [1 k]); %! x = randsample (p, k, false); %! assert_equal (iscell (x), true); %! assert_equal (size (x), [1 k]); %! k = 30; %! x = randsample (p, k, true, ones (length (p),1)); %! assert_equal (iscell (x), true); %! assert_equal (size (x), [1 k]); %!test %! p = string ({'a', 'b', 'c', 'd', 'a'}); %! k = 2; %! x = randsample (p, k, true); %! assert_equal (isstring (x), true); %! assert_equal (size (x), [1 k]); %! x = randsample (p, k, false); %! assert_equal (isstring (x), true); %! assert_equal (size (x), [1 k]); %! k = 30; %! x = randsample (p, k, true, ones (length (p),1)); %! assert_equal (isstring (x), true); %! assert_equal (size (x), [1 k]); %!test %! assert_equal (randsample ('A', 1), 'A'); %! assert_equal (randsample (true, 1), true); %! assert_equal (randsample (5.5, 1), 5.5); %! assert_equal (randsample (-5, 1), -5); %!test %! x = randsample (10, -2); %! assert_equal (isempty (x), true); %! assert_equal (size (x), [0, 1]); %!error ... %! randsample ([1 2 3; 1 2 3], 5) %!error ... %! randsample (10, 100) %!error ... %! randsample (10, 5, false, ones (5,1)) %!error ... %! randsample (5, 2, true, [0, 0, 0, 0, 0]) %!error ... %! randsample (5, 2, true, [1, 2, -1, 4, 5]) %!error ... %! randsample (5, 2, true, [1, 2, NaN, 4, 5]) %!error ... %! randsample (5, 4, false, [1, 1, 0, 0, 0]) %!error ... %! randsample (10, 2.5) statistics-release-1.9.2/inst/Data_Manipulation/rmmissing.m000066400000000000000000000217201524624707500241050ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{R} =} rmmissing (@var{A}) ## @deftypefnx {statistics} {@var{R} =} rmmissing (@var{A}, @var{dim}) ## @deftypefnx {statistics} {@var{R} =} rmmissing (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{R}, @var{TF}] =} rmmissing (@dots{}) ## ## Remove missing data from arrays. ## ## Given an input vector or matrix (2-D array) @var{A}, @code{@var{R} = ## rmmissing (@var{A})} returns an output vector or matrix @var{R} of the same ## type as input @var{A} and any missing elements removed. If @var{A} is a ## vector, missing elements are removed individually, if @var{A} is a matrix, ## then rows containing missing elements are removed. ## ## Standard missing values and their corresponding data types are: ## ## @itemize ## @item @qcode{NaN} - for @qcode{double}, @qcode{single}, @qcode{duration}, and ## @qcode{calendarDuration} arrays. ## @item @qcode{NaT} - for @qcode{datetime} arrays. ## @item @qcode{} - for @qcode{string} arrays. ## @item @qcode{} - for @qcode{categorical} arrays. ## @item @qcode{@{0x0 char@}} - for @qcode{cell} arrays of character vectors. ## @end itemize ## ## For any data types that do not support missing values, @code{rmmissing} ## returns @code{@var{R} == @var{A}} and if a second output argument is ## requested it also returns @code{@var{TF} = false (size (@var{A}))}. ## ## Given an input matrix (2-D array) @var{A}, @code{@var{R} = rmmissing ## (@var{A}, @var{dim})} further specifies whether rows or columns containing ## missing data are removed from the output @var{R} based on the value of ## @var{dim}, which must be either 1 or 0. ## ## @itemize ## @item ## @qcode{1}: remove rows. ## ## @item ## @qcode{2}: remove columns. ## @end itemize ## ## @code{@var{R} = rmmissing (@dots{}, @var{Name}, @var{Value})} also accepts ## the following paired arguments. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'MinNumMissing'} @tab A positive integer scalar value ## specifying the required minimum number of missing values for removing any ## particular row or column from a matrix input. Note that this argument is ## ignored if input @var{A} is a vector. ## ## @item @qcode{'MissingLocations'} @tab A logical array of the same size ## as input @var{A} indexing the locations of missing values in input array ## @var{A}. Note that specifying @qcode{'MissingLocations'} overrides any ## standard missing values in @var{A}. ## @end multitable ## ## Optional return value @var{TF} is a logical array where @code{true} values ## represent removed entries, rows or columns from the original data @var{A}. ## ## @seealso{fillmissing, ismissing, standardizeMissing} ## @end deftypefn function [R, TF] = rmmissing (A, varargin) ## Validate data in A if (nargin < 1) print_usage (); endif if (ndims (A) > 2) error ("rmmissing: A must be a matrix; no more than 2 dimensions allowed."); endif if (isempty (A)) R = A; TF = false (size (A)); return; endif ## Parse optional Name-Value paired arguments optNames = {'MinNumMissing', 'MissingLocations'}; dfValues = {1, []}; [MinNumMissing, MissingLocations, args] = parsePairedArguments (optNames, dfValues, ... varargin(:)); ## Validate optional Name-Value paired arguments if (! (isscalar (MinNumMissing) && isnumeric (MinNumMissing) && MinNumMissing > 0 && fix (MinNumMissing) == MinNumMissing)) error ("rmmissing: 'MinNumMissing' must be a positive integer value."); endif if (! isempty (MissingLocations)) if (! (islogical (MissingLocations) && isequal (size (MissingLocations), size (A)))) error (strcat ("rmmissing: 'MissingLocations' must be a", ... " logical matrix of the same size as input A.")); endif endif ## Check for DIM if (isempty (args)) dim = 2; elseif (isscalar (args)) dim = args{1}; if (! (isscalar (dim) && (dim == 1 || dim == 2))) error ("rmmissing: specified DIM must be either 1 or 2."); endif ## Swap DIM to operate orthogonal to specified DIM if (dim == 1) dim = 2; else dim = 1; endif else error ("rmmissing: too many input arguments."); endif ## Get missing values if (isempty (MissingLocations)) TF = ismissing (A); else TF = MissingLocations; endif ## Remove missing values if (isvector (A)) R = A(TF == 0); # MinNumMissing does not matter here else ## matrix: ismissing returns an array, so it must be converted ## to a row or column vector according to the "dim" of choice if (MinNumMissing > 1) TF = sum (TF, dim); TF(TF < MinNumMissing) = 0; # true only if at least MinNumMissing TF = logical (TF); else TF = any (TF, dim); endif if (dim == 2) ## remove the rows R = A((TF == 0), :); else ## remove the columns R = A(:, (TF == 0)); endif endif endfunction %!assert_equal (rmmissing ([1, NaN, 3]), [1, 3]) %!assert_equal (rmmissing ('abcd f'), 'abcd f') %!assert_equal (rmmissing ({'xxx', '', 'xyz'}), {'xxx', 'xyz'}) %!assert_equal (rmmissing ({'xxx', ''; 'xyz', 'yyy'}), {'xyz', 'yyy'}) %!assert_equal (rmmissing ({'xxx', ''; 'xyz', 'yyy'}, 2), {'xxx'; 'xyz'}) %!assert_equal (rmmissing ([1, 2; NaN, 2]), [1, 2]) %!assert_equal (rmmissing ([1, 2; NaN, 2], 2), [2, 2]') %!assert_equal (rmmissing ([1, 2; NaN, 4; NaN, NaN],'MinNumMissing', 2), [1, 2; NaN, 4]) ## Test second output %!test %! x = [1:6]; %! x([2,4]) = NaN; %! [~, idx] = rmmissing (x); %! assert_equal (idx, logical ([0, 1, 0, 1, 0, 0])); %! assert_equal (class (idx), 'logical'); %! x = reshape (x, [2, 3]); %! [~, idx] = rmmissing (x); %! assert_equal (idx, logical ([0; 1])); %! assert_equal (class (idx), 'logical'); %! [~, idx] = rmmissing (x, 2); %! assert_equal (idx, logical ([1, 1, 0])); %! assert_equal (class (idx), 'logical'); %! [~, idx] = rmmissing (x, 1, 'MinNumMissing', 2); %! assert_equal (idx, logical ([0; 1])); %! assert_equal (class (idx), 'logical'); %! [~, idx] = rmmissing (x, 2, 'MinNumMissing', 2); %! assert_equal (idx, logical ([0, 0, 0])); %! assert_equal (class (idx), 'logical'); ## Test data type handling %!assert_equal (rmmissing (single ([1, 2, NaN; 3, 4, 5])), single ([3, 4, 5])) %!assert_equal (rmmissing (logical (ones (3))), logical (ones (3))) %!assert_equal (rmmissing (int32 (ones (3))), int32 (ones (3))) %!assert_equal (rmmissing (uint32 (ones (3))), uint32 (ones (3))) %!assert_equal (rmmissing ({1, 2, 3}), {1, 2, 3}) %!assert_equal (rmmissing ([struct, struct, struct]), [struct, struct, struct]) ## Test empty input handling %!assert_equal (rmmissing ([]), []) %!assert_equal (rmmissing (ones (1, 0)), ones (1, 0)) %!assert_equal (rmmissing (ones (1, 0), 1), ones (1, 0)) %!assert_equal (rmmissing (ones (1, 0), 2), ones (1, 0)) %!assert_equal (rmmissing (ones (0, 1)), ones (0, 1)) %!assert_equal (rmmissing (ones (0, 1), 1), ones (0, 1)) %!assert_equal (rmmissing (ones (0, 1), 2), ones (0, 1)) %!error ... %! rmmissing (ones (0, 1, 2)) ## Test input validation %!error rmmissing () %!error ... %! rmmissing (ones (2, 2, 2)) %!error ... %! rmmissing (ones (2, 2), 'MinNumMissing', 0) %!error ... %! rmmissing ([1, 2; 3, 4], 2, 'MinNumMissing', -2) %!error ... %! rmmissing ([1, 2; 3, 4], 'MinNumMissing', 3.8) %!error ... %! rmmissing ([1, 2; 3, 4], 'MinNumMissing', [1, 2, 3]) %!error ... %! rmmissing ([1, 2; 3, 4], 'MinNumMissing', 'xxx') %!error ... %! rmmissing ([1, 2; 3, 4], 'MissingLocations', false ([1, 1, 1])) %!error rmmissing ([1, 2; 3, 4], 5) %!error rmmissing ([1, 2; 3, 4], 'XXX', 1) statistics-release-1.9.2/inst/Data_Manipulation/standardizeMissing.m000066400000000000000000000302311524624707500257340ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{B} =} standardizeMissing (@var{A}, @var{indicator}) ## ## Replace selected values by standard missing values. ## ## @code{@var{Β} = standardizeMissing (@var{A}, @var{indicator})} returns a ## standardized array @var{B} of the same size and data type as the input array ## @var{A} and with all elements specified by @var{indicator} replaced by the ## standard missing value corresponding the data type of @var{A}. ## @var{indicator} can be either a scalar or a vector. ## ## Standard missing values and their corresponding data types are: ## ## @itemize ## @item @qcode{NaN} - for @qcode{double}, @qcode{single}, @qcode{duration}, and ## @qcode{calendarDuration} arrays. ## @item @qcode{NaT} - for @qcode{datetime} arrays. ## @item @qcode{} - for @qcode{string} arrays. ## @item @qcode{} - for @qcode{categorical} arrays. ## @item @qcode{@{0x0 char@}} - for @qcode{cell} arrays of character vectors. ## @end itemize ## ## For any other data type input that does not support missing values, ## @code{standardizeMissing} returns @code{@var{B} = @var{A}} and any ## @var{indicator} value is ignored. ## ## The nonstandard missing value @var{indicator} must be of the same type as the ## data input @var{A} or have a compatible data types according to the following ## rules: ## ## @itemize ## @item all numeric indicators match both @qcode{double} and @qcode{single} ## data types in @var{A}. ## @item indicators specified as @qcode{string} arrays, @qcode{char} vectors, ## and @code{cell} arrays of character vectors match categorical data type in ## @var{A}. ## @item a @qcode{char} vector matches a @qcode{cell} array of character vectors ## in @var{A}. ## @end itemize ## ## Note: the generic @code{standardizeMissing} function from the statistics does ## not operate on table inputs, which is handled by the overloaded method of the ## table class. Use @code{help table.standardizeMissing} to find more ## information about the functional specialization on tables. ## ## Standardizing a category of a @code{categorical} array removes that category ## from the array's type, as MATLAB removes it: no element carries it once the ## values are missing, so leaving it in the category list would be stale ## metadata, and the codes of the remaining categories shift down accordingly. ## Only the standardized categories are removed; one that is declared but ## unused is left alone. ## ## @seealso{fillmissing, ismissing, rmmissing} ## @end deftypefn function A = standardizeMissing (A, indicator) if (nargin != 2) print_usage (); endif if (isnumeric (A)) if (! isnumeric (indicator)) error ("standardizeMissing: incompatible INDICATOR and input data A."); elseif (! isvector (indicator)) error ("standardizeMissing: INDICATOR must be a scalar or a vector."); endif switch (class (A)) case 'double' A(ismember (A, indicator)) = NaN ('double'); case 'single' A(ismember (A, indicator)) = NaN ('single'); endswitch elseif (iscellstr (A)) if (ischar (indicator)) if (! isrow (indicator)) error ("standardizeMissing: character INDICATOR must be a row vector."); endif indicator = {indicator}; elseif (! iscellstr (indicator)) error ("standardizeMissing: incompatible INDICATOR and input data A."); endif A(ismember (A, indicator)) = {''}; elseif (iscategorical (A)) if (ischar (indicator)) if (! isrow (indicator)) error ("standardizeMissing: character INDICATOR must be a row vector."); endif indicator = {indicator}; elseif (! (iscellstr (indicator) || isstring (indicator) || iscategorical (indicator))) error ("standardizeMissing: incompatible INDICATOR and input data A."); elseif (! isvector (indicator)) error ("standardizeMissing: INDICATOR must be a scalar or a vector."); endif ## Standardizing a category to missing removes that category from the ## type, as MATLAB removes it: no element carries it afterwards, so ## keeping it would leave stale metadata. Only the standardized ## categories go; a declared but unused category is left alone. drop = intersect (categories (A), cellstr (indicator)); A(ismember (A, indicator)) = categorical (NaN); if (! isempty (drop)) A = removecats (A, drop); endif elseif (isdatetime (A)) if (! isdatetime (indicator)) error ("standardizeMissing: incompatible INDICATOR and input data A."); elseif (! isvector (indicator)) error ("standardizeMissing: INDICATOR must be a scalar or a vector."); endif A(ismember (A, indicator)) = NaT; elseif (isduration (A)) if (! isduration (indicator)) error ("standardizeMissing: incompatible INDICATOR and input data A."); elseif (! isvector (indicator)) error ("standardizeMissing: INDICATOR must be a scalar or a vector."); endif A(ismember (A, indicator)) = days (NaN); elseif (iscalendarduration (A)) if (! iscalendarduration (indicator)) error ("standardizeMissing: incompatible INDICATOR and input data A."); elseif (! isvector (indicator)) error ("standardizeMissing: INDICATOR must be a scalar or a vector."); endif A(ismember (A, indicator)) = days (NaN); elseif (isstring (A)) if (! isstring (indicator)) error ("standardizeMissing: incompatible INDICATOR and input data A."); elseif (! isvector (indicator)) error ("standardizeMissing: INDICATOR must be a scalar or a vector."); endif A(ismember (A, indicator)) = missing; endif endfunction ## numeric tests %!assert_equal (standardizeMissing (1, 1), NaN) %!assert_equal (standardizeMissing (1, 0), 1) %!assert_equal (standardizeMissing (eye (2), 1), [NaN 0;0 NaN]) %!assert_equal (standardizeMissing ([1:3;4:6], [2 3 4 5]), [1, NaN, NaN; NaN, NaN, 6]) %!assert_equal (standardizeMissing (cat (3,1,2,3,4), 3), cat (3,1,2,NaN,4)) ## char and cellstr tests %!assert_equal (standardizeMissing ('foo', 'a'), 'foo') %!assert_equal (standardizeMissing ('foo', 'f'), 'foo') %!assert_equal (standardizeMissing ('foo', 'o'), 'foo') %!assert_equal (standardizeMissing ('foo', 'oo'), 'foo') %!assert_equal (standardizeMissing ({'foo'}, 'f'), {'foo'}) %!assert_equal (standardizeMissing ({'foo'}, {'f'}), {'foo'}) %!assert_equal (standardizeMissing ({'foo'}, 'test'), {'foo'}) %!assert_equal (standardizeMissing ({'foo'}, {'test'}), {'foo'}) %!assert_equal (standardizeMissing ({'foo'}, 'foo'), {''}) %!assert_equal (standardizeMissing ({'foo'}, {'foo'}), {''}) ## char and cellstr array tests %!assert_equal (standardizeMissing (['foo';'bar'], 'oar'), ['foo';'bar']) %!assert_equal (standardizeMissing (['foo';'bar'], ['o';'a';'r']), ['foo';'bar']) %!assert_equal (standardizeMissing (['foo';'bar'], ['o ';'ar']), ['foo';'bar']) %!assert_equal (standardizeMissing ({'foo','bar'}, 'foo'), {'','bar'}) %!assert_equal (standardizeMissing ({'foo','bar'}, 'f'), {'foo','bar'}) %!assert_equal (standardizeMissing ({'foo','bar'}, {'foo', 'a'}), {'','bar'}) %!assert_equal (standardizeMissing ({'foo'}, {'f', 'oo'}), {'foo'}) %!assert_equal (standardizeMissing ({'foo','bar'}, {'foo'}), {'','bar'}) %!assert_equal (standardizeMissing ({'foo','bar'}, {'foo', 'a'}), {'','bar'}) ## numeric type preservation tests %!assert_equal (standardizeMissing (double (1), single (1)), double (NaN)) %!assert_equal (standardizeMissing (single (1), single (1)), single (NaN)) %!assert_equal (standardizeMissing (single (1), double (1)), single (NaN)) %!assert_equal (standardizeMissing (single (1), uint8 (1)), single (NaN)) %!assert_equal (standardizeMissing (double (1), int32 (1)), double (NaN)) ## pass-trough tests %!assert_equal (standardizeMissing (true, true), true) %!assert_equal (standardizeMissing (true, 1), true) %!assert_equal (standardizeMissing (int32 (1), int32 (1)), int32 (1)) %!assert_equal (standardizeMissing (int32 (1), 1), int32 (1)) %!assert_equal (standardizeMissing (uint32 (1), uint32 (1)), uint32 (1)) %!assert_equal (standardizeMissing (uint32 (1), 1), uint32 (1)) %!assert_equal (standardizeMissing ({'abc', 1}, 1), {'abc', 1}) %!assert_equal (standardizeMissing (struct ('a','b'), 1), struct ('a','b')) ## categorical array tests ## Values below are R2024a's, measured 2026-08-17: standardizing a category ## removes it from the type, and only it. %!test %! ## every instance goes, and the remaining codes shift down %! a = standardizeMissing (categorical ({'a','b','c','b','a'}), 'b'); %! assert_equal (double (a), [1, NaN, 2, NaN, 1]); %! assert_equal (categories (a), {'a'; 'c'}); %!test %! ## a category that is declared but unused is left alone %! A = categorical ({'a','b','c'}, {'a','b','c','d'}); %! a = standardizeMissing (A, 'b'); %! assert_equal (double (a), [1, NaN, 2]); %! assert_equal (categories (a), {'a'; 'c'; 'd'}); %!test %! ## standardizing a level that is not present changes nothing %! a = standardizeMissing (categorical ({'a','b','c'}), 'z'); %! assert_equal (double (a), [1, 2, 3]); %! assert_equal (categories (a), {'a'; 'b'; 'c'}); %!test %! ## several at once %! a = standardizeMissing (categorical ({'a','b','c'}), {'a','b'}); %! assert_equal (double (a), [NaN, NaN, 1]); %! assert_equal (categories (a), {'c'}); %!assert_equal (double (standardizeMissing (categorical (1), categorical (1))), NaN) %!assert_equal (double (standardizeMissing (categorical (1), '1')), NaN) %!assert_equal (class (standardizeMissing (categorical (1), categorical (1))), 'categorical') %!assert_equal (double (standardizeMissing (categorical (1), categorical (2))), 1) %!assert_equal (double (standardizeMissing (categorical (1), '2')), 1) %!assert_equal (class (standardizeMissing (categorical (1), categorical (2))), 'categorical') ## fix GitHub PR #419, requires datatypes 1.2.0+ %!test %! A = categorical ({'a', 'b', 'c'}); %! indicator = 'b'; %! a = standardizeMissing (A , indicator); %! assert_equal (class (a), 'categorical'); %! assert_equal (double (a), [1, NaN, 2]); %! assert_equal (categories (a), {'a'; 'c'}); ## datetime array tests %!assert_equal (isnat (standardizeMissing (datetime ('today'), datetime ('today'))), true) %!assert_equal (isnat (standardizeMissing (datetime ('today'), datetime ('yesterday'))), false) ## duration array tests %!assert_equal (days (standardizeMissing (days (1), days (1))), NaN) %!assert_equal (days (standardizeMissing (days (1), days (2))), 1) ## string array tests %!assert_equal (cellstr (standardizeMissing (string (1), string (1))), {''}) %!assert_equal (cellstr (standardizeMissing (string (1), string (2))), {'1'}) ## Test input validation %!error standardizeMissing (); %!error standardizeMissing (1); %!error standardizeMissing (1, 2, 3); %!error ... %! standardizeMissing ([1, 2, 3], {1}); %!error ... %! standardizeMissing ([1, 2, 3], 'a'); %!error ... %! standardizeMissing ([1, 2, 3], struct ('a', 1)); %!error ... %! standardizeMissing (categorical (1), 1); %!error ... %! standardizeMissing ({'foo'}, string ('foo')); %!error ... %! standardizeMissing ({'foo'}, ['a';'b']); statistics-release-1.9.2/inst/Data_Manipulation/tiedrank.m000066400000000000000000000247341524624707500237060ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{r}, @var{tieadj}] =} tiedrank (@var{x}) ## @deftypefnx {statistics} {[@var{r}, @var{tieadj}] =} tiedrank (@var{x}, @var{tieflag}) ## @deftypefnx {statistics} {[@var{r}, @var{tieadj}] =} tiedrank (@var{x}, @var{tieflag}, @var{bidir}) ## @deftypefnx {statistics} {[@var{r}, @var{tieadj}] =} tiedrank (@var{x}, @var{tieflag}, @var{bidir}, @var{tol}) ## ## @var{x} may be a vector or an array. An array is ranked along its first ## dimension, so a matrix is ranked column by column, and @var{tieadj} then ## carries one entry per column: its first dimension has length 1 by default, ## or 3 when @var{tieflag} is set, and its higher dimensions are those of ## @var{x}. ## ## Compute rank adjusted for ties. ## ## @code{[@var{r}, @var{tieadj}] = tiedrank (@var{x})} computes the ranks of the ## values in vector @var{x}. If any values in @var{x} are tied, @code{tiedrank} ## computes their average rank. The return value @var{tieadj} is an adjustment ## for ties required by the nonparametric tests @code{signrank} and ## @code{ranksum}, and for the computation of Spearman's rank correlation. ## ## @code{[@var{r}, @var{tieadj}] = tiedrank (@var{x}, 1)} computes the ranks of ## the values in the vector @var{x}. @var{tieadj} is a vector of three ## adjustments for ties required in the computation of Kendall's tau. ## @code{tiedrank (@var{x}, 0)} is the same as @code{tiedrank (@var{x})}. ## ## @code{[@var{r}, @var{tieadj}] = tiedrank (@var{x}, 0, 1)} computes the ranks ## from each end, so that the smallest and largest values get rank 1, the next ## smallest and largest get rank 2, etc. These ranks are used in the ## Ansari-Bradley test. ## ## @code{[@var{r}, @var{tieadj}] = tiedrank (@var{x}, @var{tieflag}, ## @var{bidir}, @var{tol})} treats two values as tied when they lie within a ## tolerance of each other rather than only when they are exactly equal. ## @var{tol} is either a scalar or an array the size of @var{x} giving each ## element its own tolerance, and two neighbouring values are tied when the gap ## between them does not exceed the @strong{sum} of their two tolerances. The ## default is @qcode{0}, which is exact comparison. @code{signrank} uses this ## to rank differences that are equal to within the precision of the values ## they came from. ## ## @end deftypefn function [r, tieadj] = tiedrank (x, tieflag, bidir, tol) ## Check input arguments and add defaults if (nargin < 1 || nargin > 4) print_usage (); endif if (nargin < 2) tieflag = false; elseif (! isscalar (tieflag) || ! (isnumeric (tieflag) || isbool (tieflag))) error ("tiedrank: TIEFLAG must be a numeric or boolean scalar."); endif if (nargin < 3) bidir = false; elseif (! isscalar (bidir) || ! (isnumeric (bidir) || isbool (bidir))) error ("tiedrank: BIDIR must be a numeric or boolean scalar."); endif if (nargin < 4) tol = 0; elseif (! isnumeric (tol) || ! isreal (tol) || any (tol(:) < 0)) error ("tiedrank: TOL must be a non-negative numeric array."); elseif (! isscalar (tol) && ! isequal (size (tol), size (x))) error ("tiedrank: TOL must be a scalar or the same size as X."); endif if (isscalar (tol)) tol = repmat (tol, size (x)); endif ## A matrix is ranked column by column, as MATLAB does; a vector keeps its ## own orientation if (! isvector (x)) ## Rank along the first dimension, as MATLAB does: every higher dimension ## is folded into columns and unfolded again afterwards sz = size (x); xm = reshape (x, sz(1), prod (sz(2:end))); tm = reshape (tol, sz(1), prod (sz(2:end))); rm = zeros (size (xm), class (x)); tieadj = []; for j = 1:columns (xm) [rj, tj] = rank_vector (xm(:,j), tieflag, bidir, tm(:,j)); rm(:,j) = rj; tieadj = [tieadj, tj]; endfor r = reshape (rm, sz); if (numel (sz) > 2 && ! isempty (tieadj)) tieadj = reshape (tieadj, [rows(tieadj), sz(2:end)]); endif return; endif [r, tieadj] = rank_vector (x, tieflag, bidir, tol); endfunction ## Rank one vector, leaving NaNs at the end function [r, tieadj] = rank_vector (x, tieflag, bidir, tol) ## Sort X and leave NaNs at the end of vector [sx, idx] = sort (x(:)); stol = tol(:)(idx); NaNs = sum (isnan (x)); xLen = length (x) - NaNs; ## Count ranks from low end if (! bidir) ranks = [1:xLen, NaN(1,NaNs)]'; ## Count ranks from both ends else ## For even number of samples if (mod (xLen, 2) == 0) ranks = [(1:xLen/2), (xLen/2:-1:1), NaN(1,NaNs)]'; ## For odd number of samples else ranks = [(1:(xLen+1)/2), ((xLen-1)/2:-1:1), NaN(1,NaNs)]'; endif endif ## Define number of adjustments if (! tieflag) tieadj = 0; else tieadj = [0; 0; 0]; endif ## Check precision of X if (isa (x, 'single')) ranks = single (ranks); tieadj = single (tieadj); endif ## Adjust for ties. Exact equality always ties -- which also keeps equal ## infinities tied, their difference being NaN -- and a nonzero TOL ties a ## neighbouring pair whose gap is within the sum of their two tolerances. ties = sx(1:xLen-1) >= sx(2:xLen); if (any (stol(1:xLen) > 0)) ties = ties | (sx(2:xLen) - sx(1:xLen-1) <= ... stol(1:xLen-1) + stol(2:xLen)); endif tieloc = [find(ties); xLen+2]; maxTies = length (tieloc); tiecount = 1; while (tiecount < maxTies) tiestart = tieloc(tiecount); ntied = 2; while (tieloc(tiecount + 1) == tieloc(tiecount) + 1) tiecount = tiecount + 1; ntied = ntied + 1; endwhile if (! tieflag) tieadj = tieadj + ntied * (ntied - 1) * (ntied + 1) / 2; else n2minusn = ntied * (ntied - 1); tieadj = tieadj + [n2minusn/2; n2minusn*(ntied-2); n2minusn*(2*ntied+5)]; endif ## Compute mean of tied ranks ranks(tiestart:tiestart+ntied-1) = ... sum (ranks(tiestart:tiestart+ntied-1)) / ntied; tiecount = tiecount + 1; endwhile ## Reshape ranks including NaN where required. r(idx) = ranks; r = reshape (r, size (x)); endfunction ## testing against mileage data and results from Matlab %!test %! [r,tieadj] = tiedrank ([10, 20, 30, 40, 20]); %! assert_equal (r, [1, 2.5, 4, 5, 2.5]); %! assert_equal (tieadj, 3); %!test %! [r,tieadj] = tiedrank ([10; 20; 30; 40; 20]); %! assert_equal (r, [1; 2.5; 4; 5; 2.5]); %! assert_equal (tieadj, 3); %!test %! [r,tieadj] = tiedrank ([10, 20, 30, 40, 20], 1); %! assert_equal (r, [1, 2.5, 4, 5, 2.5]); %! assert_equal (tieadj, [1; 0; 18]); %!test %! [r,tieadj] = tiedrank ([10, 20, 30, 40, 20], 0, 1); %! assert_equal (r, [1, 2.5, 2, 1, 2.5]); %! assert_equal (tieadj, 3); %!test %! [r,tieadj] = tiedrank ([10, 20, 30, 40, 20], 1, 1); %! assert_equal (r, [1, 2.5, 2, 1, 2.5]); %! assert_equal (tieadj, [1; 0; 18]); %!test %! ## TOL ties a pair whose gap is within the SUM of their two tolerances, and %! ## separates it beyond. Boundaries measured against R2024a. %! tol = [eps(1), 1e-10, 3e-10, eps(3)]; %! assert_equal (tiedrank ([1.0, 1.9, 1.9+3.9e-10, 3.0], 0, 0, tol), ... %! [1, 2.5, 2.5, 4]); %! assert_equal (tiedrank ([1.0, 1.9, 1.9+4.1e-10, 3.0], 0, 0, tol), ... %! [1, 2, 3, 4]); %!test %! ## A scalar TOL applies to every element, and the default 0 is exact %! assert_equal (tiedrank ([1, 1+1e-9, 2], 0, 0, 1e-9), [1.5, 1.5, 3]); %! assert_equal (tiedrank ([1, 1+1e-9, 2], 0, 0, 0), [1, 2, 3]); %! assert_equal (tiedrank ([1, 1+1e-9, 2]), [1, 2, 3]); %!test %! ## Exact equality still ties whatever TOL says, infinities included %! assert_equal (tiedrank ([Inf, Inf, 1]), [2.5, 2.5, 1]); %! assert_equal (tiedrank ([Inf, Inf, 1], 0, 0, 0), [2.5, 2.5, 1]); ## Test input validation %!test %! ## A matrix is ranked column by column, as MATLAB does, and TIEADJ carries %! ## one entry per column. %! x = [3, 1; 1, 4; 4, 1; 1, 5; 5, 9]; %! [r, tieadj] = tiedrank (x); %! assert_equal (r, [3, 1.5; 1.5, 3; 4, 1.5; 1.5, 4; 5, 5]); %! assert_equal (tieadj, [3, 3]); %! [r1, t1] = tiedrank (x(:,1)); %! [r2, t2] = tiedrank (x(:,2)); %! assert_equal (r, [r1, r2]); %! assert_equal (tieadj, [t1, t2]); %!test %! ## With the tie flag TIEADJ gains its three rows per column %! x = [3, 1; 1, 4; 4, 1; 1, 5; 5, 9]; %! [r, tieadj] = tiedrank (x, 1); %! assert_equal (size (tieadj), [3, 2]); %! [~, t1] = tiedrank (x(:,1), 1); %! assert_equal (tieadj(:,1), t1); %!test %! ## An empty matrix is ranked, not refused %! [r, tieadj] = tiedrank (zeros (0, 2)); %! assert_equal (size (r), [0, 2]); %! assert_equal (size (tieadj), [1, 2]); %!test %! ## An N-D array is ranked along its first dimension, as MATLAB does, and %! ## TIEADJ keeps the higher dimensions of X. Verified against R2024a. %! y = cat (3, [1, 2; 3, 4], [5, 6; 7, 8]); %! [r, tieadj] = tiedrank (y); %! assert_equal (r(:,:,1), [1, 1; 2, 2]); %! assert_equal (r(:,:,2), [1, 1; 2, 2]); %! assert_equal (size (tieadj), [1, 2, 2]); %!error ... %! tiedrank ([1, 2, 3, 4, 5], [1, 1]) %!error ... %! tiedrank ([1, 2, 3, 4, 5], 'A') %!error ... %! tiedrank ([1, 2, 3, 4, 5], [true, true]) %!error ... %! tiedrank ([1, 2, 3, 4, 5], 0, [1, 1]) %!error ... %! tiedrank ([1, 2, 3, 4, 5], 0, 'A') %!error ... %! tiedrank ([1, 2, 3, 4, 5], 0, [true, true]) %!error ... %! tiedrank ([1, 2, 3, 4, 5], 0, 0, -1) %!error ... %! tiedrank ([1, 2, 3, 4, 5], 0, 0, 'A') %!error ... %! tiedrank ([1, 2, 3, 4, 5], 0, 0, [1, 2]) statistics-release-1.9.2/inst/Descriptive_Statistics/000077500000000000000000000000001524624707500230175ustar00rootroot00000000000000statistics-release-1.9.2/inst/Descriptive_Statistics/cdfcalc.m000066400000000000000000000060021524624707500245520ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{yCDF}, @var{xCDF}, @var{n}, @var{emsg}, @var{eid}] =} cdfcalc (@var{x}) ## ## Calculate an empirical cumulative distribution function. ## ## @code{[@var{yCDF}, @var{xCDF}] = cdfcalc (@var{x})} calculates an empirical ## cumulative distribution function (CDF) of the observations in the data sample ## vector @var{x}. @var{x} may be a row or column vector, and represents a ## random sample of observations from some underlying distribution. On return ## @var{xCDF} is the set of @var{x} values at which the CDF increases. ## At XCDF(i), the function increases from YCDF(i) to YCDF(i+1). ## ## @code{[@var{yCDF}, @var{xCDF}, @var{n}] = cdfcalc (@var{x})} also returns ## @var{n}, the sample size. ## ## @code{[@var{yCDF}, @var{xCDF}, @var{n}, @var{emsg}, @var{eid}] = cdfcalc ## (@var{x})} also returns an error message and error id if @var{x} is not a ## vector or if it contains no values other than NaN. ## ## @seealso{cdfplot} ## @end deftypefn function [yCDF, xCDF, n, emsg, eid] = cdfcalc (x) ## Check number of input and output argument narginchk (1,1); nargoutchk (2,5); ## Add defaults yCDF = []; xCDF = []; n = 0; ## Check that x is a vector if (! isvector (x)) warning ("cdfcalc: vector required as input."); emsg = 'VectorRequired'; eid = 'VectorRequired'; return endif ## Remove NaNs and check if there are remaining data to calculate ecdf x = x(! isnan (x)); n = length (x); if (n == 0) warning ("cdfcalc: not enough data."); emsg = 'NotEnoughData'; eid = 'NotEnoughData'; return endif ## Sort data in ascending order x = sort (x(:)); ## Get cumulative sums yCDF = (1:n)' / n; ## Remove duplicates, keep the last one keep_idx = ([diff(x(:)); 1] > 0); xCDF = x(keep_idx); yCDF = [0; yCDF(keep_idx)]; emsg = ''; eid = ''; endfunction %!test %! x = [2, 4, 3, 2, 4, 3, 2, 5, 6, 4]; %! [yCDF, xCDF, n, emsg, eid] = cdfcalc (x); %! assert_equal (yCDF, [0, 0.3, 0.5, 0.8, 0.9, 1]'); %! assert_equal (xCDF, [2, 3, 4, 5, 6]'); %! assert_equal (n, 10); %!shared x %! x = [2, 4, 3, 2, 4, 3, 2, 5, 6, 4]; %!error yCDF = cdfcalc (x); %!error [yCDF, xCDF] = cdfcalc (); %!error [yCDF, xCDF] = cdfcalc (x, x); %!warning [yCDF, xCDF] = cdfcalc (ones (10,2)); statistics-release-1.9.2/inst/Descriptive_Statistics/cl_multinom.m000066400000000000000000000131051524624707500255170ustar00rootroot00000000000000## Copyright (C) 2009 Levente Torok ## Copyright (C) 2023 Andreas Bertsatos ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{CL} =} cl_multinom (@var{X}, @var{N}, @var{b}) ## @deftypefnx {statistics} {@var{CL} =} cl_multinom (@var{X}, @var{N}, @var{b}, @var{method}) ## ## Confidence level of multinomial portions. ## ## @code{cl_multinom} returns confidence level of multinomial parameters ## estimated as @math{p = X / sum(X)} with predefined confidence interval ## @var{b}. Finite population is also considered. ## ## This function calculates the level of confidence at which the samples ## represent the true distribution given that there is a predefined tolerance ## (confidence interval). This is the upside down case of the typical exercises ## at which we want to get the confidence interval given the confidence level ## (and the estimated parameters of the underlying distribution). ## But once we accept (lets say at elections) that we have a standard predefined ## maximal acceptable error rate (e.g. @var{b}=0.02 ) in the estimation and we ## just want to know that how sure we can be that the measured proportions are ## the same as in the entire population (ie. the expected value and mean of the ## samples are roughly the same) we need to use this function. ## ## @subheading Arguments ## @multitable @columnfractions 0.1 0.10 0.78 ## @headitem Variable @tab Type @tab Description ## @item @var{X} @tab int vector @tab sample frequencies bins. ## @item @var{N} @tab int scalar @tab Population size that was sampled ## by @var{X}. If @qcode{N < sum (@var{X})}, infinite number assumed. ## @item @var{b} @tab real vector @tab confidence interval. If vector, ## it should be the size of @var{X} containing confidence interval for each ## cells. If scalar, each cell will have the same value of b unless it is zero ## or -1. If value is 0, @var{b} = 0.02 is assumed which is standard choice at ## elections otherwise it is calculated in a way that one sample in a cell ## alteration defines the confidence interval. ## @item @var{method} @tab string @tab An optional argument ## for defining the calculation method. Available choices are ## @qcode{'bromaghin'} (default), @qcode{'cochran'}, and @qcode{agresti_cull}. ## @end multitable ## ## Note! The @qcode{agresti_cull} method is not exactly the solution at ## reference given below but an adjustment of the solutions above. ## ## @subheading Returns ## Confidence level. ## ## @subheading Example ## CL = cl_multinom ([27; 43; 19; 11], 10000, 0.05) ## returns 0.69 confidence level. ## ## @subheading References ## @enumerate ## @item ## "bromaghin" calculation type (default) is based on the article: ## ## Jeffrey F. Bromaghin, "Sample Size Determination for Interval Estimation ## of Multinomial Probabilities", The American Statistician vol 47, 1993, ## pp 203-206. ## ## @item ## "cochran" calculation type is based on article: ## ## Robert T. Tortora, "A Note on Sample Size Estimation for Multinomial ## Populations", The American Statistician, , Vol 32. 1978, pp 100-102. ## ## @item ## "agresti_cull" calculation type is based on article: ## ## A. Agresti and B.A. Coull, "Approximate is better than 'exact' for ## interval estimation of binomial portions", The American Statistician, ## Vol. 52, 1998, pp 119-126 ## @end enumerate ## ## @end deftypefn function CL = cl_multinom (X, N, b = 0.05, method = 'bromaghin') if (nargin < 2 || nargin > 4) print_usage; elseif (! ischar (method)) error ("cl_multinom: argument method must be a string."); endif k = rows (X); nn = sum (X); p = X / nn; if (isscalar (b)) if (b==0) b=0.02; endif b = ones (rows (X), 1 ) * b; if (b<0) b = 1 ./ max (X, 1); endif endif bb = b .* b; if (N == nn) CL = 1; return; endif if (N < nn) fpc = 1; else fpc = (N - 1) / (N - nn); # finite population correction tag endif beta = p .* (1 - p); switch lower (method) case 'cochran' t = sqrt (fpc * nn * bb ./ beta); alpha = (1 - normcdf (t)) * 2; case 'bromaghin' t = sqrt (fpc * (nn * 2 * bb ) ./ ... (beta - 2 * bb + sqrt (beta .* beta - bb .* (4 * beta - 1)))); alpha = (1 - normcdf (t)) * 2; case 'agresti_cull' ts = fpc * nn * bb ./ beta ; if (k <= 2) alpha = 1 - chi2cdf (ts, k - 1); # adjusted Wilson interval else alpha = 1 - chi2cdf (ts / k, 1); # Goodman interval with Bonferroni arg. endif otherwise error ("cl_multinom: unknown calculation type '%s'.", method); endswitch CL = 1 - max ( alpha ); endfunction %!demo %! CL = cl_multinom ([27; 43; 19; 11], 10000, 0.05) ## Test input validation %!error cl_multinom (); %!error cl_multinom (1, 2, 3, 4, 5); %!error ... %! cl_multinom (1, 2, 3, 4); %!error ... %! cl_multinom (1, 2, 3, 'some string'); statistics-release-1.9.2/inst/Descriptive_Statistics/dcov.m000066400000000000000000000114061524624707500241320ustar00rootroot00000000000000## Copyright (C) 2014 - Maria L. Rizzo and Gabor J. Szekely ## Copyright (C) 2014 Juan Pablo Carbajal ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program. If not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{dCor}, @var{dCov}, @var{dVarX}, @var{dVarY}] =} dcov (@var{x}, @var{y}) ## ## Distance correlation, covariance and correlation statistics. ## ## It returns the distance correlation (@var{dCor}) and the distance covariance ## (@var{dCov}) between @var{x} and @var{y}, the distance variance of @var{x} ## in (@var{dVarX}) and the distance variance of @var{y} in (@var{dVarY}). ## ## @var{x} and @var{y} must have the same number of observations (rows) but they ## can have different number of dimensions (columns). Rows with missing values ## (@qcode{NaN}) in either @var{x} or @var{y} are omitted. ## ## The Brownian covariance is the same as the distance covariance: ## ## @tex ## $$ cov_W (X, Y) = dCov(X, Y) $$ ## ## @end tex ## @ifnottex ## @math{cov_W (@var{x}, @var{y}) = dCov (@var{x}, @var{y})} ## @end ifnottex ## ## and thus Brownian correlation is the same as distance correlation. ## ## @seealso{corr, cov} ## @end deftypefn function [dCor, dCov, dVarX, dVarY] = dcov (x, y) ## Validate input size if (size (x, 1) != size (y, 1)) error ("dcov: Sample sizes (rows) in X and Y must agree."); endif ## Exclude missing values is_nan = any ([isnan(x) isnan(y)], 2); x(is_nan,:) = []; y(is_nan,:) = []; ## Calculate double centered distance A = pdist2 (x, x); A_col = mean (A, 1); A_row = mean (A, 2); Acbar = ones (size (A_row)) * A_col; Arbar = A_row * ones (size (A_col)); A_bar = mean (A(:)) * ones (size (A)); A = A - Acbar - Arbar + A_bar; B = pdist2 (y, y); B_col = mean (B, 1); B_row = mean (B, 2); Bcbar = ones (size (B_row)) * B_col; Brbar = B_row * ones (size (B_col)); B_bar = mean (B(:)) * ones (size (B)); B = B - Bcbar - Brbar + B_bar; ## Calculate distance covariance and variances dCov = sqrt (mean (A(:) .* B(:))); dVarX = sqrt (mean (A(:) .^ 2)); dVarY = sqrt (mean (B(:) .^ 2)); ## Calculate distance correlation V = sqrt (dVarX .* dVarY); if V > 0 dCor = dCov / V; else dCor = 0; endif endfunction %!demo %! rng (42); %! base=@(x) (x- min (x))./(max (x)-min (x)); %! N = 5e2; %! x = randn (N,1); x = base(x); %! z = randn (N,1); z = base(z); %! # Linear relations %! cy = [1 0.55 0.3 0 -0.3 -0.55 -1]; %! ly = x .* cy; %! ly(:,[1:3 5:end]) = base(ly(:,[1:3 5:end])); %! # Correlated Gaussian %! cz = 1 - abs (cy); %! gy = base( ly + cz.*z); %! # Shapes %! sx = repmat (x,1,7); %! sy = zeros (size (ly)); %! v = 2 * rand (size (x,1),2) - 1; %! sx(:,1) = v(:,1); sy(:,1) = cos (2*pi*sx(:,1)) + 0.5*v(:,2).*exp (-sx(:,1).^2/0.5); %! R =@(d) [cosd(d) sind(d); -sind(d) cosd(d)]; %! tmp = R(35) * v.'; %! sx(:,2) = tmp(1,:); sy(:,2) = tmp(2,:); %! tmp = R(45) * v.'; %! sx(:,3) = tmp(1,:); sy(:,3) = tmp(2,:); %! sx(:,4) = v(:,1); sy(:,4) = sx(:,4).^2 + 0.5*v(:,2); %! sx(:,5) = v(:,1); sy(:,5) = 3*sign (v(:,2)).*(sx(:,5)).^2 + v(:,2); %! sx(:,6) = cos (2*pi*v(:,1)) + 0.5*(x-0.5); %! sy(:,6) = sin (2*pi*v(:,1)) + 0.5*(z-0.5); %! sx(:,7) = x + sign (v(:,1)); sy(:,7) = z + sign (v(:,2)); %! sy = base(sy); %! sx = base(sx); %! # scaled shape %! sc = 1/3; %! ssy = (sy-0.5) * sc + 0.5; %! n = size (ly,2); %! ym = 1.2; %! xm = 0.5; %! fmt={'horizontalalignment','center'}; %! ff = '% .2f'; %! figure (1) %! for i=1:n %! subplot (4,n,i); %! plot (x, gy(:,i), '.b'); %! axis tight %! axis off %! text (xm,ym,sprintf (ff, dcov (x,gy(:,i))),fmt{:}) %! %! subplot (4,n,i+n); %! plot (x, ly(:,i), '.b'); %! axis tight %! axis off %! text (xm,ym,sprintf (ff, dcov (x,ly(:,i))),fmt{:}) %! %! subplot (4,n,i+2*n); %! plot (sx(:,i), sy(:,i), '.b'); %! axis tight %! axis off %! text (xm,ym,sprintf (ff, dcov (sx(:,i),sy(:,i))),fmt{:}) %! v = axis (); %! %! subplot (4,n,i+3*n); %! plot (sx(:,i), ssy(:,i), '.b'); %! axis (v) %! axis off %! text (xm,ym,sprintf (ff, dcov (sx(:,i),ssy(:,i))),fmt{:}) %! endfor %!error dcov (randn (30, 5), randn (25,5)) statistics-release-1.9.2/inst/Descriptive_Statistics/doc-cache000066400000000000000000001337521524624707500245630ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 22 # name: # type: sq_string # elements: 1 # length: 7 cdfcalc # name: # type: sq_string # elements: 1 # length: 755 statistics: [ yCDF , xCDF , n , emsg , eid ] = cdfcalc ( x ) Calculate an empirical cumulative distribution function. [ yCDF , xCDF ] = cdfcalc ( x ) calculates an empirical cumulative distribution function (CDF) of the observations in the data sample vector x . x may be a row or column vector, and represents a random sample of observations from some underlying distribution. On return xCDF is the set of x values at which the CDF increases. At XCDF(i), the function increases from YCDF(i) to YCDF(i+1). [ yCDF , xCDF , n ] = cdfcalc ( x ) also returns n , the sample size. [ yCDF , xCDF , n , emsg , eid ] = cdfcalc ( x ) also returns an error message and error id if x is not a vector or if it contains no values other than NaN. See also: cdfplot # name: # type: sq_string # elements: 1 # length: 56 Calculate an empirical cumulative distribution function. # name: # type: sq_string # elements: 1 # length: 11 cl_multinom # name: # type: sq_string # elements: 1 # length: 2619 statistics: CL = cl_multinom ( X , N , b ) statistics: CL = cl_multinom ( X , N , b , method ) Confidence level of multinomial portions. cl_multinom returns confidence level of multinomial parameters estimated as p = X / sum(X) with predefined confidence interval b . Finite population is also considered. This function calculates the level of confidence at which the samples represent the true distribution given that there is a predefined tolerance (confidence interval). This is the upside down case of the typical exercises at which we want to get the confidence interval given the confidence level (and the estimated parameters of the underlying distribution). But once we accept (lets say at elections) that we have a standard predefined maximal acceptable error rate (e.g. b =0.02 ) in the estimation and we just want to know that how sure we can be that the measured proportions are the same as in the entire population (ie. the expected value and mean of the samples are roughly the same) we need to use this function. Arguments Variable Type Description X int vector sample frequencies bins. N int scalar Population size that was sampled by X . If N < sum ( X ) , infinite number assumed. b real vector confidence interval. If vector, it should be the size of X containing confidence interval for each cells. If scalar, each cell will have the same value of b unless it is zero or -1. If value is 0, b = 0.02 is assumed which is standard choice at elections otherwise it is calculated in a way that one sample in a cell alteration defines the confidence interval. method string An optional argument for defining the calculation method. Available choices are 'bromaghin' (default), 'cochran' , and agresti_cull . Note! The agresti_cull method is not exactly the solution at reference given below but an adjustment of the solutions above. Returns Confidence level. Example CL = cl_multinom ([27; 43; 19; 11], 10000, 0.05) returns 0.69 confidence level. References "bromaghin" calculation type (default) is based on the article: Jeffrey F. Bromaghin, "Sample Size Determination for Interval Estimation of Multinomial Probabilities", The American Statistician vol 47, 1993, pp 203-206. "cochran" calculation type is based on article: Robert T. Tortora, "A Note on Sample Size Estimation for Multinomial Populations", The American Statistician, , Vol 32. 1978, pp 100-102. "agresti_cull" calculation type is based on article: A. Agresti and B.A. Coull, "Approximate is better than ’exact’ for interval estimation of binomial portions", The American Statistician, Vol. 52, 1998, pp 119-126 # name: # type: sq_string # elements: 1 # length: 41 Confidence level of multinomial portions. # name: # type: sq_string # elements: 1 # length: 4 dcov # name: # type: sq_string # elements: 1 # length: 676 statistics: [ dCor , dCov , dVarX , dVarY ] = dcov ( x , y ) Distance correlation, covariance and correlation statistics. It returns the distance correlation ( dCor ) and the distance covariance ( dCov ) between x and y , the distance variance of x in ( dVarX ) and the distance variance of y in ( dVarY ). x and y must have the same number of observations (rows) but they can have different number of dimensions (columns). Rows with missing values ( NaN ) in either x or y are omitted. The Brownian covariance is the same as the distance covariance: $$ cov_W (X, Y) = dCov(X, Y) $$ and thus Brownian correlation is the same as distance correlation. See also: corr, cov # name: # type: sq_string # elements: 1 # length: 60 Distance correlation, covariance and correlation statistics. # name: # type: sq_string # elements: 1 # length: 4 ecdf # name: # type: sq_string # elements: 1 # length: 1864 statistics: [ f , x ] = ecdf ( y ) statistics: [ f , x , flo , fup ] = ecdf ( y ) statistics: ecdf (…) statistics: ecdf ( ax , …) statistics: […] = ecdf ( y , name , value , …) statistics: […] = ecdf ( ax , y , name , value , …) Empirical (Kaplan-Meier) cumulative distribution function. [ f , x ] = ecdf ( y ) calculates the Kaplan-Meier estimate of the cumulative distribution function (cdf), also known as the empirical cdf. y is a vector of data values. f is a vector of values of the empirical cdf evaluated at x . [ f , x , flo , fup ] = ecdf ( y ) also returns lower and upper confidence bounds for the cdf. These bounds are calculated using Greenwood’s formula, and are not simultaneous confidence bounds. ecdf (…) without output arguments produces a plot of the empirical cdf. ecdf ( ax , …) plots into existing axes ax . […] = ecdf ( y , name , value , …) specifies additional parameter name/value pairs chosen from the following: name value "censoring" A boolean vector of the same size as Y that is 1 for observations that are right-censored and 0 for observations that are observed exactly. Default is all observations observed exactly. "frequency" A vector of the same size as Y containing non-negative integer counts. The jth element of this vector gives the number of times the jth element of Y was observed. Default is 1 observation per Y element. "alpha" A value alpha between 0 and 1 specifying the significance level. Default is 0.05 for 5% significance. "function" The type of function returned as the F output argument, chosen from "cdf" (the default), "survivor", or "cumulative hazard". "bounds" Either "on" to include bounds or "off" (the default) to omit them. Used only for plotting. Type demo ecdf to see examples of usage. See also: cdfplot, ecdfhist # name: # type: sq_string # elements: 1 # length: 58 Empirical (Kaplan-Meier) cumulative distribution function. # name: # type: sq_string # elements: 1 # length: 7 geomean # name: # type: sq_string # elements: 1 # length: 1828 statistics: m = geomean ( x ) statistics: m = geomean ( x , "all") statistics: m = geomean ( x , dim ) statistics: m = geomean ( x , vecdim ) statistics: m = geomean (…, nanflag ) Compute the geometric mean of x . If x is a vector, then geomean( x ) returns the geometric mean of the elements in x defined as $$ {\rm geomean}(x) = \left( \prod_{i=1}^N x_i \right)^\frac{1}{N} = exp \left({1\over N} \sum_{i=1}^N log x_i \right) $$ where N is the length of the x vector. If x is a matrix, then geomean( x ) returns a row vector with the geometric mean of each columns in x . If x is a multidimensional array, then geomean( x ) operates along the first nonsingleton dimension of x . x must not contain any negative or complex values. geomean( x , "all") returns the geometric mean of all the elements in x . If x contains any 0, then the returned value is 0. geomean( x , dim ) returns the geometric mean along the operating dimension dim of x . Calculating the harmonic mean of any subarray containing any 0 will return 0. geomean( x , vecdim ) returns the geometric mean over the dimensions specified in the vector vecdim . For example, if x is a 2-by-3-by-4 array, then geomean( x , [1 2]) returns a 1-by-1-by-4 array. Each element of the output array is the geometric mean of the elements on the corresponding page of x . If vecdim indexes all dimensions of x , then it is equivalent to geomean ( x , "all") . Any dimension in vecdim greater than ndims ( x ) is ignored. geomean(…, nanflag ) specifies whether to exclude NaN values from the calculation, using any of the input argument combinations in previous syntaxes. By default, geomean includes NaN values in the calculation ( nanflag has the value "includenan"). To exclude NaN values, set the value of nanflag to "omitnan". See also: harmmean, mean # name: # type: sq_string # elements: 1 # length: 32 Compute the geometric mean of x. # name: # type: sq_string # elements: 1 # length: 8 grpstats # name: # type: sq_string # elements: 1 # length: 5899 statistics: stats = grpstats ( x ) statistics: stats = grpstats ( x , group ) statistics: [ stats1 , …, statsN ] = grpstats ( x , group , whichstats ) statistics: [ stats1 , …, statsN ] = grpstats ( x , group , whichstats , 'Alpha' , alpha ) statistics: tblstats = grpstats ( tbl , groupvars ) statistics: tblstats = grpstats ( tbl , groupvars , whichstats ) statistics: tblstats = grpstats ( tbl , groupvars , whichstats , Name , Value ) statistics: grpstats ( x , group , alpha ) statistics: h = grpstats ( x , group , alpha ) Summary statistics by group. grpstats computes groupwise summary statistics for the data in x , which can be a numeric matrix or a table. Numeric vectors are treated as a single column matrix. NaN s are treated as missing values and removed from calculations. Syntax for Numeric Input stats = grpstats ( x ) calculates the mean statistic for each column in x and returns it as row vector in stats . stats = grpstats ( x , group ) calculates the mean statistic for each column in x grouped by group . The returned argument, stats , is also a matrix with equal columns as x and the number of rows is equal to the groups specified by group . The grouping variable, group can be a vector of any data type supported by the grp2idx function. Alternatively, it can be a cell vector specifying multiple grouping variables with each cell element containing any of the aforementioned supported grouping vectors. If group is empty ( [] ), then input x is treated as a single group. [ stats1 , …, statsN ] = grpstats ( x , group , whichstats ) calculates the summary statistics specified by the whichstats argument, which can include any of the available statistics shown below. The number of output arguments must match the number of requested statistics specified in whichstats . computes summary statistics for the numeric matrix x grouped by group . x must be a numeric vector or a 2-D matrix. Vectors are treated as a single-column matrix. group is a grouping variable that defines the groups for the rows of x . It can be a categorical variable, numeric vector, string array, or cell array of strings. group can also be a cell array containing multiple grouping variables. If group is empty ( [] ) or omitted, all of x is treated as a single group. whichstats specifies the statistics to compute. It can be either a string array or a cell array of strings specifying any of the following builtin statistics. If omitted, the default is 'mean' . whichstats can also contain function handles for custom statistics. The available statistics are: 'mean' Mean of each group. 'median' Median of each group. 'sem' Standard error of the mean for each group. 'std' Standard deviation of each group. 'var' Variance of each group. 'min' Minimum value in each group. 'max' Maximum value in each group. 'range' Difference between max and min in each group. 'numel' Number of elements (count) in each group. 'meanci' Confidence interval for the mean. 'predci' Prediction interval for a new observation. 'gname' Group names. […] = grpstats (…, 'Alpha' , alpha ) specifies the significance level for the confidence intervals ( 'meanci' and 'predci' ) as 100 * (1- alpha )@% . alpha must be a scalar between 0 and 1. When not specified, it defaults to 0.05. Note that this paired input argument is also valid for table input. Syntax for Table Input tblstats = grpstats ( tbl , groupvars ) computes the summary statistics for the data in table tbl , grouped by the variables specified in groupvars . If groupvars is empty or omitted, then all of tbl is treated as a single group. groupvars can be a cell array of character vectors or a string array specifying one or more variable names in tbl to be used as grouping variables. Alternatively, all valid methods for indexing table variables are supported (e.g. vartype object, logical vector, function handle). The output tblstats is a table with one row for each group. It contains the grouping variables, an additional 'GroupCount' variable, and the specified summary statistics for the variables in tbl , expect for those specified as grouping variables. When input is a table, only a single output variable, tblstats can be specified. The output tblstats also contains RowNames , which are the unique combinations of the specified groups, for which data are available in tbl . When no groups are specified, the row name of the single row output table defaults to 'All' . tblstats = grpstats ( tbl , groupvars , whichstats ) specifies which statistics to calculate for the variables in tbl . Unless specified, the mean is calculated for each variable. When specifying more than one statistic, tblstats contains multiple variables for each variable in tbl and each is named by combining the applied statistic with the name of the original variable. When a function handle is applied, its string representation is used instead. For table input specifically, grpstats also accepts the following paired arguments. Name Value 'DataVars' A vector specifying the variables in tbl , for which to calculate the specified statistics. The vector can be any of the valid options for indexing table variables. 'VarNames' A cell array of character vectors or a string array specifying the names of the variables in the output table. The number of specified names must match the number of expected variables in the output table. Plotting Syntax The syntax grpstats ( x , group , alpha ) generates an errorbar plot with the group means and their respective confidence intervals. x must be a numeric vector or matrix. alpha is a scalar between 0 and 1 that determines the confidence level. This syntax is an alternative to calling errorbar after computing 'mean' and 'meanci' statistics. The optional output h is a handle to the hggroup object representing the data plot and errorbars. See also: grp2idx # name: # type: sq_string # elements: 1 # length: 464 (x, group, whichstats) @deftypefnx {statistics} {[stats1, , statsN] =} grpstats @ (x, group, whichstats, 'Alpha', alpha) @deftypefnx {statistics} {tblstats =} grpstats (tbl, groupvars) @deftypefnx {statistics} {tblstats =} grpstats (tbl, groupvars, whichstats) @deftypefnx {statistics} {tblstats =} grpstats (tbl, groupvars, @ whichstats, Name, Value) @deftypefnx {statistics} {} grpstats (x, group, alpha) @deftypefnx {statistics} {h =} grpstats (x, group, alpha) # name: # type: sq_string # elements: 1 # length: 8 harmmean # name: # type: sq_string # elements: 1 # length: 1776 statistics: m = harmmean ( x ) statistics: m = harmmean ( x , "all") statistics: m = harmmean ( x , dim ) statistics: m = harmmean ( x , vecdim ) statistics: m = harmmean (…, nanflag ) Compute the harmonic mean of x . If x is a vector, then harmmean( x ) returns the harmonic mean of the elements in x defined as $$ {\rm harmmean}(x) = \frac{N}{\sum_{i=1}^N \frac{1}{x_i}} $$ where N is the length of the x vector. If x is a matrix, then harmmean( x ) returns a row vector with the harmonic mean of each columns in x . If x is a multidimensional array, then harmmean( x ) operates along the first nonsingleton dimension of x . x must not contain any negative or complex values. harmmean( x , "all") returns the harmonic mean of all the elements in x . If x contains any 0, then the returned value is 0. harmmean( x , dim ) returns the harmonic mean along the operating dimension dim of x . Calculating the harmonic mean of any subarray containing any 0 will return 0. harmmean( x , vecdim ) returns the harmonic mean over the dimensions specified in the vector vecdim . For example, if x is a 2-by-3-by-4 array, then harmmean( x , [1 2]) returns a 1-by-1-by-4 array. Each element of the output array is the harmonic mean of the elements on the corresponding page of x . If vecdim indexes all dimensions of x , then it is equivalent to harmmean ( x , "all") . Any dimension in vecdim greater than ndims ( x ) is ignored. harmmean(…, nanflag ) specifies whether to exclude NaN values from the calculation, using any of the input argument combinations in previous syntaxes. By default, harmmean includes NaN values in the calculation ( nanflag has the value "includenan"). To exclude NaN values, set the value of nanflag to "omitnan". See also: geomean, mean # name: # type: sq_string # elements: 1 # length: 31 Compute the harmonic mean of x. # name: # type: sq_string # elements: 1 # length: 9 jackknife # name: # type: sq_string # elements: 1 # length: 1753 statistics: jackstat = jackknife ( E , x ) statistics: jackstat = jackknife ( E , x , …) Compute jackknife estimates of a parameter taking one or more given samples as parameters. In particular, E is the estimator to be jackknifed as a function name, handle, or inline function, and x is the sample for which the estimate is to be taken. The i -th entry of jackstat will contain the value of the estimator on the sample x with its i -th row omitted. jackstat ( i ) = E ( x (1 : i - 1, i + 1 : length( x ))) Depending on the number of samples to be used, the estimator must have the appropriate form: If only one sample is used, then the estimator need not be concerned with cell arrays, for example jackknifing the standard deviation of a sample can be performed with jackstat = jackknife (@std, rand (100, 1)) . If, however, more than one sample is to be used, the samples must all be of equal size, and the estimator must address them as elements of a cell-array, in which they are aggregated in their order of appearance: jackstat = jackknife (@(x) std(x{1})/var(x{2}), rand (100, 1), randn (100, 1)) If all goes well, a theoretical value P for the parameter is already known, n is the sample size, t = n * E ( x ) - ( n - 1) * mean( jackstat ) and v = sumsq( n * E ( x ) - ( n - 1) * jackstat - t ) / ( n * ( n - 1)) then ( t - P )/sqrt( v ) should follow a t-distribution with n -1 degrees of freedom. Jackknifing is a well known method to reduce bias. Further details can be found in: References Rupert G. Miller. The jackknife - a review. Biometrika (1974), 61(1):1-15. doi:10.1093/biomet/61.1.1 Rupert G. Miller. Jackknifing Variances. Ann. Math. Statist. (1968), Volume 39, Number 2, 567-582. doi:10.1214/aoms/1177698418 # name: # type: sq_string # elements: 1 # length: 90 Compute jackknife estimates of a parameter taking one or more given samples as parameters. # name: # type: sq_string # elements: 1 # length: 9 ksdensity # name: # type: sq_string # elements: 1 # length: 1747 statistics: f = ksdensity ( x ) statistics: f = ksdensity ( x , pts ) statistics: [ f , xi ] = ksdensity (…) statistics: [ f , xi , bw ] = ksdensity (…) statistics: […] = ksdensity (…, Name , Value ) Kernel smoothing density estimate. f = ksdensity ( x ) computes a probability density estimate of the sample in the vector x , evaluated at 100 equally spaced points xi that span the range of the data. [ f , xi ] = ksdensity ( x ) also returns those points. Both are row vectors, whichever way x itself lies. When called without output arguments, the estimate is plotted instead. f = ksdensity ( x , pts ) evaluates the estimate at the values in pts instead; f is then the same size as pts . For 'Function' equal to 'icdf' the entries of pts are probabilities in [0, 1] . [ f , xi , bw ] = ksdensity (…) additionally returns the bandwidth bw of the smoothing kernel. The following Name-Value pairs are supported: Name Value 'Kernel' The smoothing kernel: 'normal' (default), 'box' , 'triangle' , 'epanechnikov' , or a function handle @(z) evaluating a kernel density at the standardized distance z . 'Bandwidth' The kernel bandwidth, a positive scalar. The default is the value that is optimal for estimating a normal density, bw = sigma × (4 / (3 × n )) ^ (1 / 5) , with sigma a robust estimate of the standard deviation of x . 'Function' The function to estimate: 'pdf' (default), 'cdf' , 'icdf' , 'survivor' , or 'cumhazard' . 'Weights' A vector of non-negative weights, one for each element of x . The default weights are all equal. 'NumPoints' The number of equally spaced points xi at which to evaluate the estimate when pts is not given. The default is 100 . See also: hist, histc, ecdf # name: # type: sq_string # elements: 1 # length: 34 Kernel smoothing density estimate. # name: # type: sq_string # elements: 1 # length: 11 mvksdensity # name: # type: sq_string # elements: 1 # length: 1221 statistics: f = mvksdensity ( x , pts , Name , Value ) Multivariate kernel smoothing density estimate. f = mvksdensity ( x , pts ) computes a probability density estimate of the sample in the N×D matrix x , evaluated at the points in the M×D matrix pts . Each row of x is a single D -dimensional observation, and each row of pts is a point at which to evaluate the estimate. The result f is an M×1 vector, with one density value per row of pts . The density estimate uses a product kernel: the multivariate kernel is the product of the univariate kernels applied to each dimension, each with its own bandwidth. The following Name-Value pairs are supported: Name Value 'Bandwidth' The kernel bandwidth, either a positive scalar applied to every dimension or a 1×D vector of positive values, one per dimension. The default is a diagonal normal-reference (Silverman) rule computed from x . 'Kernel' The smoothing kernel applied in each dimension: 'normal' (default), 'box' , 'triangle' , or 'epanechnikov' . 'Function' The function to estimate: 'pdf' (default) or 'cdf' . 'Weights' A vector of non-negative weights, one for each row of x . The default weights are all equal. See also: ksdensity # name: # type: sq_string # elements: 1 # length: 47 Multivariate kernel smoothing density estimate. # name: # type: sq_string # elements: 1 # length: 6 nancov # name: # type: sq_string # elements: 1 # length: 1195 statistics: c = nancov ( x ) statistics: c = nancov ( x , y ) statistics: c = nancov (…, normalization ) statistics: c = nancov (…, method ) Compute the covariance matrix while ignoring NaN values. c = nancov ( x ) returns the covariance matrix of the columns of x , treating each row as an observation, after removing NaN values. If x is a vector, the scalar variance of its non- NaN elements is returned. c = nancov ( x , y ) , where x and y are of equal length, is equivalent to nancov ([ x (:), y (:)]) and returns the 2-by-2 covariance matrix. c = nancov (…, normalization ) specifies the normalization. When normalization is 0 (default), the covariance is normalized by N-1 , where N is the number of observations used. When it is 1, it is normalized by N . c = nancov (…, method ) selects how NaN values are handled. With "complete" (the default), any row of the data that contains a NaN value is removed before the covariance is computed. With "pairwise" , each element c (i,j) is computed using all rows in which both column i and column j are non- NaN ; the resulting matrix may fail to be positive semidefinite. See also: cov, nanvar, nanstd, nanmean # name: # type: sq_string # elements: 1 # length: 56 Compute the covariance matrix while ignoring NaN values. # name: # type: sq_string # elements: 1 # length: 6 nanmax # name: # type: sq_string # elements: 1 # length: 1781 statistics: v = nanmax ( x ) statistics: v = nanmax ( x , [], dim ) statistics: [ v , idx ] = nanmax (…) statistics: v = nanmax ( x , [], 'all' ) statistics: v = nanmax ( x , [], vecdim ) statistics: v = nanmax ( x , y ) Find the maximum while ignoring NaN values. v = nanmax ( x ) returns the maximum of x , after removing NaN values. If x is a vector, a scalar maximum value is returned. If x is a matrix, a row vector of column maxima is returned. If x is a multidimensional array, the nanmax operates along the first nonsingleton dimension. If all values in a column are NaN , the maximum is returned as NaN rather than [] . v = nanmax ( x , [], dim ) operates along the dimension dim of x . [ v , idx ] = nanmax (…) also returns the row indices of the maximum values for each column in the vector idx . When x is a vector, then idx is a scalar value as v . v = nanmax ( x , [], 'all' ) returns the maximum of all elements of x , after removing NaN values. It is the equivalent of nanmax ( x (:)) . The optional flag 'all' cannot be used together with dim or vecdim input arguments. v = nanmax ( x , [], vecdim ) returns the maximum over the dimensions specified in the vector vecdim . Each element of vecdim represents a dimension of the input array x and the output v has length 1 in the specified operating dimensions. The lengths of the other dimensions are the same for x and y . For example, if x is a 2-by-3-by-4 array, then nanmax ( x , [1 2]) returns a 1-by-1-by-4 array. Each element of the output array is the maximum of the elements on the corresponding page of x . If vecdim indexes all dimensions of x , then it is equivalent to nanmax ( x , 'all' ) . Any dimension in vecdim greater than ndims ( x ) is ignored. See also: max, nanmin, nansum # name: # type: sq_string # elements: 1 # length: 43 Find the maximum while ignoring NaN values. # name: # type: sq_string # elements: 1 # length: 7 nanmean # name: # type: sq_string # elements: 1 # length: 1752 statistics: s = nanmean ( x ) statistics: s = nanmean ( x , 'all' ) statistics: s = nanmean ( x , dim ) statistics: s = nanmean ( x , vecdim ) Compute the mean while ignoring NaN values. s = nanmean ( x ) returns the mean of x after removing NaN values. If x is a vector, a scalar value is returned. If x is a matrix, a row vector of column means is returned. If x is a multidimensional array, nanmean operates along the first nonsingleton dimension. If all values along a dimension are NaN , the mean is returned returned as NaN . s = nanmean ( x , 'all' ) returns the mean of all elements of x , after removing NaN values. It is the equivalent of nanmean ( x (:)) . s = nanmean ( x , dim ) operates along the dimension dim of x . s = nanmean ( x , vecdim ) returns the mean over the dimensions specified in the vector vecdim . Each element of vecdim represents a dimension of the input array x and the output s has length 1 in the specified operating dimensions. The lengths of the other dimensions are the same for x and y . For example, if x is a 2-by-3-by-4 array, then nanmean ( x , [1 2]) returns a 1-by-1-by-4 array. Each element of the output array is the mean of the elements on the corresponding page of x . If vecdim indexes all dimensions of x , then it is equivalent to nanmean ( x , 'all' ) . Any dimension in vecdim greater than ndims ( x ) is ignored. nanmean primarily operates on single and double numeric types, since they support NaN values, while preserving the data type. Nevertheless, it can also operate on integer types by treating them as double types. To avoid overflow on very large int64 and uint64 values, use the mean function, which applies special handling for such cases. See also: mean, nansum, nanmin, nanmax # name: # type: sq_string # elements: 1 # length: 43 Compute the mean while ignoring NaN values. # name: # type: sq_string # elements: 1 # length: 9 nanmedian # name: # type: sq_string # elements: 1 # length: 1181 statistics: m = nanmedian ( x ) statistics: m = nanmedian ( x , 'all' ) statistics: m = nanmedian ( x , dim ) statistics: m = nanmedian ( x , vecdim ) Compute the median while ignoring NaN values. m = nanmedian ( x ) returns the median of x , after removing NaN values. If x is a vector, a scalar value is returned. If x is a matrix, a row vector of column medians is returned. If x is a multidimensional array, nanmedian operates along the first nonsingleton dimension. If all values along a dimension are NaN , the median is returned as NaN . m = nanmedian ( x , 'all' ) returns the median of all elements of x , after removing NaN values. It is the equivalent of nanmedian ( x (:)) . m = nanmedian ( x , dim ) operates along the dimension dim of x . m = nanmedian ( x , vecdim ) returns the median over the dimensions specified in the vector vecdim . Each element of vecdim represents a dimension of the input array x and the output m has length 1 in the specified operating dimensions. If vecdim indexes all dimensions of x , then it is equivalent to nanmedian ( x , 'all' ) . Any dimension in vecdim greater than ndims ( x ) is ignored. See also: median, nanmean, nansum # name: # type: sq_string # elements: 1 # length: 45 Compute the median while ignoring NaN values. # name: # type: sq_string # elements: 1 # length: 6 nanmin # name: # type: sq_string # elements: 1 # length: 1781 statistics: v = nanmin ( x ) statistics: v = nanmin ( x , [], dim ) statistics: [ v , idx ] = nanmin (…) statistics: v = nanmin ( x , [], 'all' ) statistics: v = nanmin ( x , [], vecdim ) statistics: v = nanmin ( x , y ) Find the minimum while ignoring NaN values. v = nanmin ( x ) returns the minimum of x , after removing NaN values. If x is a vector, a scalar minimum value is returned. If x is a matrix, a row vector of column minima is returned. If x is a multidimensional array, the nanmin operates along the first nonsingleton dimension. If all values in a column are NaN , the minimum is returned as NaN rather than [] . v = nanmin ( x , [], dim ) operates along the dimension dim of x . [ v , idx ] = nanmin (…) also returns the row indices of the minimum values for each column in the vector idx . When x is a vector, then idx is a scalar value as v . v = nanmin ( x , [], 'all' ) returns the minimum of all elements of x , after removing NaN values. It is the equivalent of nanmin ( x (:)) . The optional flag 'all' cannot be used together with dim or vecdim input arguments. v = nanmin ( x , [], vecdim ) returns the minimum over the dimensions specified in the vector vecdim . Each element of vecdim represents a dimension of the input array x and the output v has length 1 in the specified operating dimensions. The lengths of the other dimensions are the same for x and y . For example, if x is a 2-by-3-by-4 array, then nanmin ( x , [1 2]) returns a 1-by-1-by-4 array. Each element of the output array is the minimum of the elements on the corresponding page of x . If vecdim indexes all dimensions of x , then it is equivalent to nanmin ( x , 'all' ) . Any dimension in vecdim greater than ndims ( x ) is ignored. See also: min, nanmax, nansum # name: # type: sq_string # elements: 1 # length: 43 Find the minimum while ignoring NaN values. # name: # type: sq_string # elements: 1 # length: 6 nanstd # name: # type: sq_string # elements: 1 # length: 1604 statistics: s = nanstd ( x ) statistics: s = nanstd ( x , w ) statistics: s = nanstd ( x , w , 'all' ) statistics: s = nanstd ( x , w , dim ) statistics: s = nanstd ( x , w , vecdim ) Compute the standard deviation while ignoring NaN values. s = nanstd ( x ) returns the standard deviation of x , after removing NaN values. If x is a vector, a scalar value is returned. If x is a matrix, a row vector of column standard deviations is returned. If x is a multidimensional array, nanstd operates along the first nonsingleton dimension. If a dimension contains fewer than two non- NaN values, the standard deviation is returned as 0 for a single value and as NaN when all values are NaN . s = nanstd ( x , w ) specifies the normalization. When w is 0 (default), the standard deviation is normalized by N-1 , where N is the number of non- NaN observations. When w is 1, it is normalized by N . w may also be a vector of nonnegative weights whose length matches the operating dimension, in which case the weighted standard deviation normalized by the sum of the weights is returned. s = nanstd ( x , w , 'all' ) returns the standard deviation of all elements of x , after removing NaN values. Use an empty value, w = [] , to pass the default normalization. s = nanstd ( x , w , dim ) operates along the dimension dim of x . s = nanstd ( x , w , vecdim ) returns the standard deviation over the dimensions specified in the vector vecdim . A weight vector is not supported together with 'all' or vecdim . Any dimension in vecdim greater than ndims ( x ) is ignored. See also: std, nanvar, nanmean, nansum # name: # type: sq_string # elements: 1 # length: 57 Compute the standard deviation while ignoring NaN values. # name: # type: sq_string # elements: 1 # length: 6 nansum # name: # type: sq_string # elements: 1 # length: 1392 statistics: s = nansum ( x ) statistics: s = nanmax ( x , 'all' ) statistics: s = nanmax ( x , dim ) statistics: s = nanmax ( x , vecdim ) Compute the sum while ignoring NaN values. s = nansum ( x ) returns the sum of x , after removing NaN values. If x is a vector, a scalar value is returned. If x is a matrix, a row vector of column sums is returned. If x is a multidimensional array, the nansum operates along the first nonsingleton dimension. If all values along a dimension are NaN , the sum is returned returned as 0. s = nansum ( x , 'all' ) returns the sum of all elements of x , after removing NaN values. It is the equivalent of nansum ( x (:)) . s = nansum ( x , dim ) operates along the dimension dim of x . s = nansum ( x , vecdim ) returns the sum over the dimensions specified in the vector vecdim . Each element of vecdim represents a dimension of the input array x and the output s has length 1 in the specified operating dimensions. The lengths of the other dimensions are the same for x and y . For example, if x is a 2-by-3-by-4 array, then nanmax ( x , [1 2]) returns a 1-by-1-by-4 array. Each element of the output array is the maximum of the elements on the corresponding page of x . If vecdim indexes all dimensions of x , then it is equivalent to nanmax ( x , 'all' ) . Any dimension in vecdim greater than ndims ( x ) is ignored. See also: sum, nanmin, nanmax # name: # type: sq_string # elements: 1 # length: 42 Compute the sum while ignoring NaN values. # name: # type: sq_string # elements: 1 # length: 6 nanvar # name: # type: sq_string # elements: 1 # length: 1524 statistics: v = nanvar ( x ) statistics: v = nanvar ( x , w ) statistics: v = nanvar ( x , w , 'all' ) statistics: v = nanvar ( x , w , dim ) statistics: v = nanvar ( x , w , vecdim ) Compute the variance while ignoring NaN values. v = nanvar ( x ) returns the variance of x , after removing NaN values. If x is a vector, a scalar value is returned. If x is a matrix, a row vector of column variances is returned. If x is a multidimensional array, nanvar operates along the first nonsingleton dimension. If a dimension contains fewer than two non- NaN values, the variance is returned as 0 for a single value and as NaN when all values are NaN . v = nanvar ( x , w ) specifies the normalization. When w is 0 (default), the variance is normalized by N-1 , where N is the number of non- NaN observations. When w is 1, it is normalized by N . w may also be a vector of nonnegative weights whose length matches the operating dimension, in which case the weighted variance normalized by the sum of the weights is returned. v = nanvar ( x , w , 'all' ) returns the variance of all elements of x , after removing NaN values. Use an empty value, w = [] , to pass the default normalization. v = nanvar ( x , w , dim ) operates along the dimension dim of x . v = nanvar ( x , w , vecdim ) returns the variance over the dimensions specified in the vector vecdim . A weight vector is not supported together with 'all' or vecdim . Any dimension in vecdim greater than ndims ( x ) is ignored. See also: var, nanstd, nanmean, nansum # name: # type: sq_string # elements: 1 # length: 47 Compute the variance while ignoring NaN values. # name: # type: sq_string # elements: 1 # length: 11 partialcorr # name: # type: sq_string # elements: 1 # length: 2609 statistics: rho = partialcorr ( x ) statistics: rho = partialcorr ( x , z ) statistics: rho = partialcorr ( x , y , z ) statistics: [ rho , pval ] = partialcorr (…) statistics: […] = partialcorr (…, Name , Value ) Linear or rank partial correlation coefficients. rho = partialcorr ( x ) returns the sample linear partial correlation coefficients between pairs of variables in the n -by- p matrix x , controlling for the remaining columns of x . Each element rho (i,j) is the partial correlation between x (:,i) and x (:,j) , adjusted for the other p-2 columns. rho is a symmetric p -by- p matrix with ones on the diagonal. rho = partialcorr ( x , z ) controls instead for the variables in the n -by- q matrix z , returning the p -by- p partial correlations among the columns of x . rho = partialcorr ( x , y , z ) returns the p1 -by- p2 matrix of partial correlations between the columns of the n -by- p1 matrix x and the n -by- p2 matrix y , controlling for z . Element rho (i,j) is the partial correlation between x (:,i) and y (:,j) . [ rho , pval ] = partialcorr (…) also returns pval , a matrix of p-values for testing the hypothesis of no partial correlation against the alternative selected by 'Tail' . A coefficient is NaN where the controlling variables explain either of the two variables completely, since the partial correlation is then undefined: no variation is left to correlate. This covers a controlling variable that duplicates one of them and any set of them that spans it. The following Name / Value pairs are accepted: 'Type' 'Pearson' (default) for linear partial correlation, or 'Spearman' for rank partial correlation (computed on the ranks of the data). 'Kendall' is not supported and raises an error, as in MATLAB . 'Rows' 'all' (default) uses all rows regardless of missing values (any NaN yields a NaN result); 'complete' uses only the rows with no missing values across all supplied variables; 'pairwise' uses, for each computed coefficient, the rows with no missing values among just the variables involved in that coefficient. 'Tail' The alternative hypothesis for pval : 'both' (default, nonzero correlation), 'right' (greater than zero), or 'left' (less than zero). The partial correlation is computed by regressing each of the two variables on the controlling variables (with an intercept) and correlating the residuals. The p-value uses a Student’s t statistic with n - 2 - k degrees of freedom, where k is the number of controlling variables and n the number of observations used. See also: partialcorri, corr, corrcoef, tiedrank # name: # type: sq_string # elements: 1 # length: 48 Linear or rank partial correlation coefficients. # name: # type: sq_string # elements: 1 # length: 12 partialcorri # name: # type: sq_string # elements: 1 # length: 1262 statistics: rho = partialcorri ( y , x ) statistics: rho = partialcorri ( y , x , z ) statistics: [ rho , pval ] = partialcorri (…) statistics: […] = partialcorri (…, Name , Value ) Partial correlation of each response with each predictor, adjusting for the remaining predictors. rho = partialcorri ( y , x ) returns the sample partial correlation coefficients between the columns of the n -by- p response matrix y and the columns of the n -by- q predictor matrix x . Element rho (i,j) is the partial correlation between y (:,i) and x (:,j) , adjusted for the other columns of x (that is, all columns of x except the j -th). rho is a p -by- q matrix. rho = partialcorri ( y , x , z ) additionally controls for the variables in the n -by- r matrix z , so that rho (i,j) is adjusted for both the other columns of x and all columns of z . [ rho , pval ] = partialcorri (…) also returns pval , a matrix of p-values for testing the hypothesis of no partial correlation against the alternative selected by 'Tail' . The 'Type' , 'Rows' , and 'Tail' Name / Value options are accepted with the same meaning as in partialcorr . 'Kendall' is not supported and raises an error, as in MATLAB . See also: partialcorr, corr, corrcoef, tiedrank # name: # type: sq_string # elements: 1 # length: 97 Partial correlation of each response with each predictor, adjusting for the remaining predictors. # name: # type: sq_string # elements: 1 # length: 8 tabulate # name: # type: sq_string # elements: 1 # length: 1869 statistics: tabulate ( x ) statistics: tbl = tabulate ( x ) Create a frequency table of unique values in vector x . tabulate (x) displays a frequency table of the data in the vector x . The input x can be a numeric vector, a logical vector, a character matrix, a cell vector of character vectors, a categorical vector, or a string vector. The table displays the value, the number of instances (count), and the percentage of that value in x . If no output argument is requested, the table is displayed in the command window. tbl = tabulate ( x ) returns the frequency table, tbl , as a numeric matrix when x is numeric and as a cell array otherwise. If x is numeric, any missing values ( NaNs ) are ignored. Similarly, undefined elements in categorical arrays and missing elements in string arrays are ignored. If all the elements of x are positive integers, then the frequency table includes 0 counts for the integers between 1 and max ( x ) that do not appear in x . For categorical arrays, the frequency table includes 0 counts for any categories that are defined but do not appear in x . Missing values are not tabulated. The percentage column is count / total * 100 taken literally, so a category with no observations out of none at all is NaN rather than zero: there is no total to take a proportion of. A categorical carries its categories independently of its data, so an all-undefined one still tabulates every category with a zero count; a string array has no levels beyond those its data carries, so an all-missing one tabulates to an empty table. MATLAB agrees on the categorical case but returns a malformed 1 -by- 2 cell for the all-missing string, lacking the label column its own documentation describes, while returning a well-formed 0 -by- 3 for an empty string array. This implementation returns the empty table in both. See also: bar, pareto # name: # type: sq_string # elements: 1 # length: 54 Create a frequency table of unique values in vector x. # name: # type: sq_string # elements: 1 # length: 8 trimmean # name: # type: sq_string # elements: 1 # length: 2489 statistics: m = trimmean ( x , p ) statistics: m = trimmean ( x , p , flag ) statistics: m = trimmean (…, 'all' ) statistics: m = trimmean (…, dim ) statistics: m = trimmean (…, vecdim ) Compute the trimmed mean. The trimmed mean of x is defined as the mean of x excluding the highest and lowest k data values of x , calculated as k = n * ( p / 100) / 2) , where n is the sample size. m = trimmean ( x , p ) returns the mean of x after removing the outliers in x defined by p percent. If x is a vector, then trimmean ( x , p ) is the mean of all the values of x , computed after removing the outliers. If x is a matrix, then trimmean ( x , p ) is a row vector of column means, computed after removing the outliers. If x is a multidimensional array, then trimmean operates along the first nonsingleton dimension of x . To specify the operating dimension(s) when x is a matrix or a multidimensional array, use the dim or vecdim input argument. trimmean treats NaN values in x as missing values and removes them. m = trimmean ( x , p , flag ) specifies how to trim when k , i.e. half the number of outliers, is not an integer. flag can be specified as one of the following values: Value Description 'round' Round k to the nearest integer. This is the default. 'floor' Round k down to the next smaller integer. 'weighted' If k = i + f , where i is an integer and f is a fraction, compute a weighted mean with weight (1 - f) for the (i + 1) -th and (n - i) -th values, and full weight for the values between them. m = trimmean (…, 'all' ) returns the trimmed mean of all the values in x using any of the input argument combinations in the previous syntaxes. m = trimmean (…, dim ) returns the trimmed mean along the operating dimension dim specified as a positive integer scalar. If not specified, then the default value is the first nonsingleton dimension of x , i.e. whose size does not equal 1. If dim is greater than ndims ( X ) or if size ( x , dim ) is 1, then trimmean returns x . m = trimmean (…, vecdim ) returns the trimmed mean over the dimensions specified in the vector vecdim . For example, if x is a 2-by-3-by-4 array, then mean ( x , [1 2]) returns a 1-by-1-by-4 array. Each element of the output array is the mean of the elements on the corresponding page of x . If vecdim indexes all dimensions of x , then it is equivalent to mean ( x , "all") . Any dimension in vecdim greater than ndims ( x ) is ignored. See also: mean # name: # type: sq_string # elements: 1 # length: 25 Compute the trimmed mean. statistics-release-1.9.2/inst/Descriptive_Statistics/ecdf.m000066400000000000000000000260701524624707500241030ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{f}, @var{x}] =} ecdf (@var{y}) ## @deftypefnx {statistics} {[@var{f}, @var{x}, @var{flo}, @var{fup}] =} ecdf (@var{y}) ## @deftypefnx {statistics} {} ecdf (@dots{}) ## @deftypefnx {statistics} {} ecdf (@var{ax}, @dots{}) ## @deftypefnx {statistics} {[@dots{}] =} ecdf (@var{y}, @var{name}, @var{value}, @dots{}) ## @deftypefnx {statistics} {[@dots{}] =} ecdf (@var{ax}, @var{y}, @var{name}, @var{value}, @dots{}) ## ## Empirical (Kaplan-Meier) cumulative distribution function. ## ## @code{[@var{f}, @var{x}] = ecdf (@var{y})} calculates the Kaplan-Meier ## estimate of the cumulative distribution function (cdf), also known as the ## empirical cdf. @var{y} is a vector of data values. @var{f} is a vector of ## values of the empirical cdf evaluated at @var{x}. ## ## @code{[@var{f}, @var{x}, @var{flo}, @var{fup}] = ecdf (@var{y})} also returns ## lower and upper confidence bounds for the cdf. These bounds are calculated ## using Greenwood's formula, and are not simultaneous confidence bounds. ## ## @code{ecdf (@dots{})} without output arguments produces a plot of the ## empirical cdf. ## ## @code{ecdf (@var{ax}, @dots{})} plots into existing axes @var{ax}. ## ## @code{[@dots{}] = ecdf (@var{y}, @var{name}, @var{value}, @dots{})} specifies ## additional parameter name/value pairs chosen from the following: ## ## @multitable @columnfractions 0.20 0.8 ## @headitem @var{name} @tab @var{value} ## @item "censoring" @tab A boolean vector of the same size as Y that is 1 for ## observations that are right-censored and 0 for observations that are observed ## exactly. Default is all observations observed exactly. ## ## @item "frequency" @tab A vector of the same size as Y containing non-negative ## integer counts. The jth element of this vector gives the number of times the ## jth element of Y was observed. Default is 1 observation per Y element. ## ## @item "alpha" @tab A value @var{alpha} between 0 and 1 specifying the ## significance level. Default is 0.05 for 5% significance. ## ## @item "function" @tab The type of function returned as the F output argument, ## chosen from "cdf" (the default), "survivor", or "cumulative hazard". ## ## @item "bounds" @tab Either "on" to include bounds or "off" (the default) to ## omit them. Used only for plotting. ## @end multitable ## ## Type @code{demo ecdf} to see examples of usage. ## ## @seealso{cdfplot, ecdfhist} ## @end deftypefn function [Fout, x, Flo, Fup] = ecdf (y, varargin) ## Check for valid input arguments narginchk (1, Inf); ## Parse input arguments if (nargin == 1) ax = []; if (! isvector (y) || ! isreal (y)) error ("ecdf: Y must be a vector of real numbers."); endif ##x = varargin{1}; else ## ax = y; ## Check that ax is a valid axis handle try isstruct (get (y)); ax = y; y = varargin{1}; varargin{1} = []; catch ##error ("ecdf: invalid handle %f.", ax); ax = []; end_try_catch #y = varargin{1}; #varargin{1} = []; endif ## Make y a column vector x = y(:); ## Add defaults cens = zeros (size (x)); freq = ones (size (x)); alpha = 0.05; fname = 'cdf'; bound = 'off'; ## Check for remaining varargins and parse extra parameters if (length (varargin) > 0 && mod (numel (varargin), 2) == 0) [~, prop] = parseparams (varargin); while (! isempty (prop)) switch (lower (prop{1})) case 'censoring' cens = prop{2}; ## Check for valid input if (! isequal (size (x), size (cens))) error ("ecdf: censoring data mismatch Y vector."); endif ## Make double in case censoring data is logical if (islogical (cens)) cens = double (cens); endif case 'frequency' freq = prop{2}; ## Check for valid input if (! isequal (size (x), size (freq))) error ("ecdf: frequency data mismatch Y vector."); endif ## Make double in case frequency data is logical if (islogical (freq)) freq = double (freq); endif case 'alpha' alpha = prop{2}; ## Check for valid alpha value if (numel (alpha) != 1 || ! isnumeric (alpha) || alpha <= 0 || alpha >= 1) error ("ecdf: alpha must be a numeric scalar in the range (0,1)."); endif case 'function' fname = prop{2}; ## Check for valid function name option if (sum (strcmpi (fname, {'cdf', 'survivor', 'cumulative hazard'})) < 1) error ("ecdf: wrong function name."); endif case 'bounds' bound = prop{2}; ## Check for valid bounds option if (! (strcmpi (bound, 'off') || strcmpi (bound, 'on'))) error ("ecdf: wrong bounds."); endif otherwise error ("ecdf: unknown option %s", prop{1}); endswitch prop = prop(3:end); endwhile elseif nargin > 2 error ("ecdf: optional parameters must be in name/value pairs."); endif ## Remove NaNs from data rn = ! isnan (x) & ! isnan (cens) & freq > 0; x = x(rn); if (length (x) == 0) error ("ecdf: not enough data."); endif cens = cens(rn); freq = freq(rn); ## Sort data in ascending order [x, sr] = sort (x); cens = cens(sr); freq = freq(sr); ## Keep class for data (single | double) if (isa (x, 'single')) freq = single (freq); endif ## Calculate cumulative frequencies tcfreq = cumsum (freq); ocfreq = cumsum (freq .* ! cens); x_diff = (diff (x) == 0); if (any (x_diff)) x(x_diff) = []; tcfreq(x_diff) = []; ocfreq(x_diff) = []; endif max_count = tcfreq(end); ## Get Deaths and Number at Risk for each unique X Death = [ocfreq(1); diff(ocfreq)]; NRisk = max_count - [0; tcfreq(1:end-1)]; ## Remove no Death observations x = x(Death > 0); NRisk = NRisk(Death > 0); Death = Death(Death > 0); ## Estimate function switch (fname) case 'cdf' S = cumprod (1 - Death ./ NRisk); Fun_x = 1 - S; Fzero = 0; fdisp = 'F(x)'; case 'survivor' S = cumprod (1 - Death ./ NRisk); Fun_x = S; Fzero = 1; fdisp = 'S(x)'; case 'cumulative hazard' Fun_x = cumsum (Death ./ NRisk); Fzero = 0; fdisp = 'H(x)'; endswitch ## Check for remaining non-censored data and add a starting value if (! isempty (Death)) x = [min(y); x]; F = [Fzero; Fun_x]; else warning ("ecdf: No Death in data"); F = Fun_x; endif ## Calculate lower and upper confidence bounds if requested if (nargout > 2 || (nargout == 0 && strcmpi (bound, 'on'))) switch (fname) case {'cdf', 'survivor'} se = NaN (size (Death)); if (! isempty (Death)) if (NRisk(end) == Death(end)) t = 1:length (NRisk) - 1; else t = 1:length (NRisk); endif se(t) = S(t) .* sqrt (cumsum (Death(t) ./ ... (NRisk(t) .* (NRisk(t) - Death(t))))); endif case 'cumulative hazard' se = sqrt (cumsum (Death ./ (NRisk .* NRisk))); endswitch ## Calculate confidence limits if (! isempty (se)) z_a = - norminv (alpha / 2); h_w = z_a * se; Flo = max (0, Fun_x - h_w); Flo(isnan (h_w)) = NaN; switch (fname) case {'cdf', 'survivor'} Fup = min (1, Fun_x + h_w); Fup(isnan (h_w)) = NaN; case 'cumulative hazard' Fup = Fun_x + h_w; endswitch Flo = [NaN; Flo]; Fup = [NaN; Fup]; else Flo = []; Fup = []; endif else Flo = []; Fup = []; endif ## Plot stairs if no output is requested if (nargout == 0) if (isempty (ax)) ax = newplot (); endif h = stairs (ax, x , [F, Flo, Fup]); xlabel (ax, 'x'); ylabel (ax, fdisp); title ('ecdf'); else Fout = F; endif endfunction %!demo %! rande ('state', 42); %! y = exprnd (10, 50, 1); ## random failure times are exponential(10) %! d = exprnd (20, 50, 1); ## drop-out times are exponential(20) %! t = min (y, d); ## we observe the minimum of these times %! censored = (y > d); ## we also observe whether the subject failed %! %! ## Calculate and plot the empirical cdf and confidence bounds %! [f, x, flo, fup] = ecdf (t, 'censoring', censored); %! stairs (x, f); %! hold on; %! stairs (x, flo, 'r:'); stairs (x, fup, 'r:'); %! %! ## Superimpose a plot of the known true cdf %! xx = 0:.1:max (t); yy = 1 - exp (-xx / 10); plot (xx, yy, 'g-'); %! hold off; %!demo %! rande ('state', 42); %! R = wblrnd (100, 2, 100, 1); %! ecdf (R, 'Function', 'survivor', 'Alpha', 0.01, 'Bounds', 'on'); %! hold on %! x = 1:1:250; %! wblsurv = 1 - cdf ('weibull', x, 100, 2); %! plot (x, wblsurv, 'g-', 'LineWidth', 2) %! legend ('Empirical survivor function', 'Lower confidence bound', ... %! 'Upper confidence bound', 'Weibull survivor function', ... %! 'Location', 'northeast'); %! hold off ## Test input %!error ecdf (); %!error ecdf (randi (15,2)); %!error ecdf ([3,2,4,3+2i,5]); %!error kstest ([2,3,4,5,6],'tail'); %!error kstest ([2,3,4,5,6],'tail', 'whatever'); %!error kstest ([2,3,4,5,6],'function', ''); %!error kstest ([2,3,4,5,6],'badoption', 0.51); %!error kstest ([2,3,4,5,6],'tail', 0); %!error kstest ([2,3,4,5,6],'alpha', 0); %!error kstest ([2,3,4,5,6],'alpha', NaN); %!error kstest ([NaN,NaN,NaN,NaN,NaN],'tail', 'unequal'); %!error kstest ([2,3,4,5,6],'alpha', 0.05, 'CDF', [2,3,4;1,3,4;1,2,1]); ## Test output against MATLAB results %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! x = [2, 3, 4, 3, 5, 4, 6, 5, 8, 3, 7, 8, 9, 0]; %! [F, x, Flo, Fup] = ecdf (x); %! F_out = [0; 0.0714; 0.1429; 0.3571; 0.5; 0.6429; 0.7143; 0.7857; 0.9286; 1]; %! assert_equal (F, F_out, ones (10,1) * 1e-4); %! x_out = [0 0 2 3 4 5 6 7 8 9]'; %! assert_equal (x, x_out); %! Flo_out = [NaN, 0, 0, 0.1061, 0.2381, 0.3919, 0.4776, 0.5708, 0.7937, NaN]'; %! assert_equal (Flo, Flo_out, ones (10,1) * 1e-4); %! Fup_out = [NaN, 0.2063, 0.3262, 0.6081, 0.7619, 0.8939, 0.9509, 1, 1, NaN]'; %! assert_equal (Fup, Fup_out, ones (10,1) * 1e-4); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! x = [2, 3, 4, 3, 5, 4, 6, 5, 8, 3, 7, 8, 9, 0]; %! ecdf (x); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect statistics-release-1.9.2/inst/Descriptive_Statistics/geomean.m000066400000000000000000000245131524624707500246150ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{m} =} geomean (@var{x}) ## @deftypefnx {statistics} {@var{m} =} geomean (@var{x}, "all") ## @deftypefnx {statistics} {@var{m} =} geomean (@var{x}, @var{dim}) ## @deftypefnx {statistics} {@var{m} =} geomean (@var{x}, @var{vecdim}) ## @deftypefnx {statistics} {@var{m} =} geomean (@dots{}, @var{nanflag}) ## ## Compute the geometric mean of @var{x}. ## ## @itemize ## @item If @var{x} is a vector, then @code{geomean(@var{x})} returns the ## geometric mean of the elements in @var{x} defined as ## @tex ## $$ {\rm geomean}(x) = \left( \prod_{i=1}^N x_i \right)^\frac{1}{N} ## = exp \left({1\over N} \sum_{i=1}^N log x_i \right) $$ ## ## @end tex ## @ifnottex ## ## @example ## geomean (@var{x}) = PROD_i @var{x}(i) ^ (1/N) ## @end example ## ## @end ifnottex ## @noindent ## where @math{N} is the length of the @var{x} vector. ## ## @item If @var{x} is a matrix, then @code{geomean(@var{x})} returns a row ## vector with the geometric mean of each columns in @var{x}. ## ## @item If @var{x} is a multidimensional array, then @code{geomean(@var{x})} ## operates along the first nonsingleton dimension of @var{x}. ## ## @item @var{x} must not contain any negative or complex values. ## @end itemize ## ## @code{geomean(@var{x}, "all")} returns the geometric mean of all the elements ## in @var{x}. If @var{x} contains any 0, then the returned value is 0. ## ## @code{geomean(@var{x}, @var{dim})} returns the geometric mean along the ## operating dimension @var{dim} of @var{x}. Calculating the harmonic mean of ## any subarray containing any 0 will return 0. ## ## @code{geomean(@var{x}, @var{vecdim})} returns the geometric mean over the ## dimensions specified in the vector @var{vecdim}. For example, if @var{x} is ## a 2-by-3-by-4 array, then @code{geomean(@var{x}, [1 2])} returns a ## 1-by-1-by-4 array. Each element of the output array is the geometric mean of ## the elements on the corresponding page of @var{x}. If @var{vecdim} indexes ## all dimensions of @var{x}, then it is equivalent to @code{geomean (@var{x}, ## "all")}. Any dimension in @var{vecdim} greater than @code{ndims (@var{x})} ## is ignored. ## ## @code{geomean(@dots{}, @var{nanflag})} specifies whether to exclude NaN ## values from the calculation, using any of the input argument combinations in ## previous syntaxes. By default, geomean includes NaN values in the calculation ## (@var{nanflag} has the value "includenan"). To exclude NaN values, set the ## value of @var{nanflag} to "omitnan". ## ## @seealso{harmmean, mean} ## @end deftypefn function m = geomean (x, varargin) if (nargin < 1 || nargin > 3) print_usage (); endif if (! isnumeric (x) || ! isreal (x) || ! all (x(! isnan (x))(:) >= 0)) error ("geomean: X must contain real nonnegative values."); endif ## Set initial conditions all_flag = false; omitnan = false; nvarg = numel (varargin); varg_chars = cellfun ('ischar', varargin); szx = size (x); ndx = ndims (x); if (nvarg > 1 && ! varg_chars(2:end)) ## Only first varargin can be numeric print_usage (); endif ## Process any other char arguments. if (any (varg_chars)) for i = varargin(varg_chars) switch (lower (i{:})) case 'all' all_flag = true; case 'omitnan' omitnan = true; case 'includenan' omitnan = false; otherwise print_usage (); endswitch endfor varargin(varg_chars) = []; nvarg = numel (varargin); endif ## Single numeric input argument, no dimensions given. if (nvarg == 0) if (all_flag) x = x(:); if (omitnan) x = x(! isnan (x)); endif if (any (x == 0)) m = 0; return; endif m = exp (sum (log (x), 1) ./ length (x)); elseif (ndx == 2 && isempty (x) && szx == [0,0]) m = NaN; else ## Find the first non-singleton dimension. (dim = find (szx != 1, 1)) || (dim = 1); n = szx(dim); if (omitnan) idx = isnan (x); n = sum (! idx, dim); x(idx) = 1; # log (1) = 0 endif m = exp (sum (log (x), dim) ./ n); m(m == -Inf) = 0; # handle zeros in X endif else ## Two numeric input arguments, dimensions given. Note scalar is vector! vecdim = varargin{1}; if (isempty (vecdim) || ! (isvector (vecdim) && all (vecdim > 0)) ... || any (rem (vecdim, 1))) error ("geomean: DIM must be a positive integer scalar or vector."); endif if (isscalar (vecdim)) if (vecdim > ndx) m = x; else n = szx(vecdim); if (omitnan) nanx = isnan (x); n = sum (! nanx, vecdim); x(nanx) = 1; # log (1) = 0 endif m = exp (sum (log (x), vecdim) ./ n); m(m == -Inf) = 0; # handle zeros in X endif else vecdim = sort (vecdim); if (! all (diff (vecdim))) error (strcat ("geomean: VECDIM must contain non-repeating", ... " positive integers.")); endif ## Ignore exceeding dimensions in VECDIM vecdim(find (vecdim > ndims (x))) = []; if (isempty (vecdim)) m = x; else ## Move vecdims to dim 1. ## Calculate permutation vector remdims = 1 : ndx; # All dimensions remdims(vecdim) = []; # Delete dimensions specified by vecdim nremd = numel (remdims); ## If all dimensions are given, it is similar to all flag if (nremd == 0) x = x(:); if (omitnan) x = x(! isnan (x)); endif if (any (x == 0)) m = 0; return; endif m = exp (sum (log (x), 1) ./ length (x)); m(m == -Inf) = 0; # handle zeros in X else ## Permute to bring vecdims to front perm = [vecdim, remdims]; x = permute (x, perm); ## Reshape to squash all vecdims in dim1 num_dim = prod (szx(vecdim)); szx(vecdim) = []; szx = [ones(1, length(vecdim)), szx]; szx(1) = num_dim; x = reshape (x, szx); ## Calculate mean on dim1 if (omitnan) nanx = isnan (x); n = sum (! nanx, 1); x(nanx) = 1; # log (1) = 0 else n = szx(1); endif m = exp (sum (log (x), 1) ./ n); m(m == -Inf) = 0; # handle zeros in X ## Inverse permute back to correct dimensions m = ipermute (m, perm); endif endif endif endif endfunction ## Test single input and optional arguments "all", DIM, "omitnan") %!test %! x = [0:10]; %! y = [x;x+5;x+10]; %! assert_equal (geomean (x), 0); %! m = [0 9.462942809849169 14.65658770861967]; %! assert_equal (geomean (y, 2), m', 4e-14); %! assert_equal (geomean (y, 'all'), 0); %! y(2,4) = NaN; %! m(2) = 9.623207231679554; %! assert_equal (geomean (y, 2), [0 NaN m(3)]', 4e-14); %! assert_equal (geomean (y', 'omitnan'), m, 4e-14); %! z = y + 20; %! assert_equal (geomean (z, 'all'), NaN); %! assert_equal (geomean (z, 'all', 'includenan'), NaN); %! assert_equal (geomean (z, 'all', 'omitnan'), 29.59298474535024, 4e-14); %! m = [24.79790781765634 NaN 34.85638839503932]; %! assert_equal (geomean (z'), m, 4e-14); %! assert_equal (geomean (z', 'includenan'), m, 4e-14); %! m(2) = 30.02181156156319; %! assert_equal (geomean (z', 'omitnan'), m, 4e-14); %! assert_equal (geomean (z, 2, 'omitnan'), m', 4e-14); ## Test dimension indexing with vecdim in n-dimensional arrays %!test %! x = repmat ([1:20;6:25], [5 2 6 3]); %! assert_equal (size (geomean (x, [3 2])), [10 1 1 3]); %! assert_equal (size (geomean (x, [1 2])), [1 1 6 3]); %! assert_equal (size (geomean (x, [1 2 4])), [1 1 6]); %! assert_equal (size (geomean (x, [1 4 3])), [1 40]); %! assert_equal (size (geomean (x, [1 2 3 4])), [1 1]); ## Test results with vecdim in n-dimensional arrays and "omitnan" %!test %! x = repmat ([1:20;6:25], [5 2 6 3]); %! m = repmat ([8.304361203739333;14.3078118884256], [5 1 1 3]); %! assert_equal (geomean (x, [3 2]), m, 4e-13); %! x(2,5,6,3) = NaN; %! m(2,3) = NaN; %! assert_equal (geomean (x, [3 2]), m, 4e-13); %! m(2,3) = 14.3292729579901; %! assert_equal (geomean (x, [3 2], 'omitnan'), m, 4e-13); ## Test errors %!error geomean ('char') %!error geomean ([1 -1 3]) %!error ... %! geomean (repmat ([1:20;6:25], [5 2 6 3 5]), -1) %!error ... %! geomean (repmat ([1:20;6:25], [5 2 6 3 5]), 0) %!error ... %! geomean (repmat ([1:20;6:25], [5 2 6 3 5]), [1 1]) ## Test default handling of empty arrays. %!test %! a = geomean ([]); %! assert_equal (isnan (a), true); %! assert_equal (size (a), [1, 1]); %!assert_equal (geomean (ones (2, 0, 3, 2)), ones (1, 0, 3, 2)) %!assert_equal (geomean (ones (2, 0, 3, 2), [1, 2]), NaN (1, 1, 3, 2)) %!assert_equal (geomean (ones (2, 0, 3, 2), 'all'), NaN) %!assert_equal (geomean (ones (2, 0, 3, 2), 1), ones (1, 0, 3, 2)) %!assert_equal (geomean (ones (2, 0, 3, 2), 2), NaN (2, 1, 3, 2)) %!assert_equal (geomean (ones (2, 0, 3, 2), 3), ones (2, 0, 1, 2)) %!assert_equal (geomean (ones (2, 0, 3, 2), 4), ones (2, 0, 3)) %!assert_equal (geomean ([], 1), ones (1, 0)) %!assert_equal (geomean ([], 2), ones (0, 1)) %!assert_equal (geomean ([], 3), []) %!assert_equal (geomean (zeros (0, 3)), NaN (1, 3)) %!assert_equal (geomean (zeros (3, 0)), ones (1, 0)) %!assert_equal (geomean ([], 'all'), NaN) statistics-release-1.9.2/inst/Descriptive_Statistics/grpstats.m000066400000000000000000001667761524624707500250730ustar00rootroot00000000000000## Copyright (C) 2022-2025 Andreas Bertsatos ## Copyright (C) 2025 jayantchauhan <0001jayant@gmail.com> ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{stats} =} grpstats (@var{x}) ## @deftypefnx {statistics} {@var{stats} =} grpstats (@var{x}, @var{group}) ## @deftypefnx {statistics} {[@var{stats1}, @dots{}, @var{statsN}] =} grpstats @ ##(@var{x}, @var{group}, @var{whichstats}) ## @deftypefnx {statistics} {[@var{stats1}, @dots{}, @var{statsN}] =} grpstats @ ## (@var{x}, @var{group}, @var{whichstats}, @qcode{'Alpha'}, @var{alpha}) ## @deftypefnx {statistics} {@var{tblstats} =} grpstats (@var{tbl}, @var{groupvars}) ## @deftypefnx {statistics} {@var{tblstats} =} grpstats (@var{tbl}, @var{groupvars}, @var{whichstats}) ## @deftypefnx {statistics} {@var{tblstats} =} grpstats (@var{tbl}, @var{groupvars}, @ ## @var{whichstats}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {} grpstats (@var{x}, @var{group}, @var{alpha}) ## @deftypefnx {statistics} {@var{h} =} grpstats (@var{x}, @var{group}, @var{alpha}) ## ## Summary statistics by group. ## ## @code{grpstats} computes groupwise summary statistics for the data in ## @var{x}, which can be a numeric matrix or a table. Numeric vectors are ## treated as a single column matrix. @qcode{NaN}s are treated as missing ## values and removed from calculations. ## ## @subheading Syntax for Numeric Input ## ## @code{@var{stats} = grpstats (@var{x})} calculates the mean statistic for ## each column in @var{x} and returns it as row vector in @var{stats}. ## ## @code{@var{stats} = grpstats (@var{x}, @var{group})} calculates the mean ## statistic for each column in @var{x} grouped by @var{group}. The returned ## argument, @var{stats}, is also a matrix with equal columns as @var{x} and the ## number of rows is equal to the groups specified by @var{group}. ## ## The grouping variable, @var{group} can be a vector of any data type ## supported by the @code{grp2idx} function. Alternatively, it can be a cell ## vector specifying multiple grouping variables with each cell element ## containing any of the aforementioned supported grouping vectors. If ## @var{group} is empty (@code{[]}), then input @var{x} is treated as a single ## group. ## ## @code{[@var{stats1}, @dots{}, @var{statsN}] = grpstats (@var{x}, @var{group}, ## @var{whichstats})} calculates the summary statistics specified by the ## @var{whichstats} argument, which can include any of the available statistics ## shown below. The number of output arguments must match the number of ## requested statistics specified in @var{whichstats}. ## computes summary statistics for the numeric matrix @var{x} grouped by ## @var{group}. ## ## @var{x} must be a numeric vector or a 2-D matrix. Vectors are treated as ## a single-column matrix. ## ## @var{group} is a grouping variable that defines the groups for the rows of ## @var{x}. It can be a categorical variable, numeric vector, string array, or ## cell array of strings. @var{group} can also be a cell array containing ## multiple grouping variables. If @var{group} is empty (@code{[]}) or omitted, ## all of @var{x} is treated as a single group. ## ## @var{whichstats} specifies the statistics to compute. It can be either a ## string array or a cell array of strings specifying any of the following ## builtin statistics. If omitted, the default is @qcode{'mean'}. ## @var{whichstats} can also contain function handles for custom statistics. ## ## The available statistics are: ## @multitable @columnfractions 0.2 0.75 ## @item @qcode{'mean'} @tab Mean of each group. ## @item @qcode{'median'} @tab Median of each group. ## @item @qcode{'sem'} @tab Standard error of the mean for each group. ## @item @qcode{'std'} @tab Standard deviation of each group. ## @item @qcode{'var'} @tab Variance of each group. ## @item @qcode{'min'} @tab Minimum value in each group. ## @item @qcode{'max'} @tab Maximum value in each group. ## @item @qcode{'range'} @tab Difference between max and min in each ## group. ## @item @qcode{'numel'} @tab Number of elements (count) in each group. ## @item @qcode{'meanci'} @tab Confidence interval for the mean. ## @item @qcode{'predci'} @tab Prediction interval for a new observation. ## @item @qcode{'gname'} @tab Group names. ## @end multitable ## ## @code{[@dots{}] = grpstats (@dots{}, @qcode{'Alpha'}, @var{alpha})} specifies ## the significance level for the confidence intervals (@qcode{'meanci'} and ## @qcode{'predci'}) as @code{100 * (1-@var{alpha})@@%}. @var{alpha} must be a ## scalar between 0 and 1. When not specified, it defaults to 0.05. Note that ## this paired input argument is also valid for table input. ## ## @subheading Syntax for Table Input ## ## @code{@var{tblstats} = grpstats (@var{tbl}, @var{groupvars})} computes the ## summary statistics for the data in table @var{tbl}, grouped by the variables ## specified in @var{groupvars}. If @var{groupvars} is empty or omitted, then ## all of @var{tbl} is treated as a single group. @var{groupvars} can be a cell ## array of character vectors or a string array specifying one or more variable ## names in @var{tbl} to be used as grouping variables. Alternatively, all ## valid methods for indexing table variables are supported (e.g. @code{vartype} ## object, logical vector, function handle). ## ## The output @var{tblstats} is a table with one row for each group. It contains ## the grouping variables, an additional @qcode{'GroupCount'} variable, and the ## specified summary statistics for the variables in @var{tbl}, expect for those ## specified as grouping variables. When input is a table, only a single output ## variable, @var{tblstats} can be specified. The output @var{tblstats} also ## contains @qcode{RowNames}, which are the unique combinations of the specified ## groups, for which data are available in @var{tbl}. When no groups are ## specified, the row name of the single row output table defaults to ## @qcode{'All'}. ## ## @code{@var{tblstats} = grpstats (@var{tbl}, @var{groupvars}, ## @var{whichstats})} specifies which statistics to calculate for the variables ## in @var{tbl}. Unless specified, the mean is calculated for each variable. ## When specifying more than one statistic, @var{tblstats} contains multiple ## variables for each variable in @var{tbl} and each is named by combining the ## applied statistic with the name of the original variable. When a function ## handle is applied, its string representation is used instead. ## ## For table input specifically, @code{grpstats} also accepts the following ## paired arguments. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'DataVars'} @tab A vector specifying the variables in ## @var{tbl}, for which to calculate the specified statistics. The vector can ## be any of the valid options for indexing table variables. ## ## @item @qcode{'VarNames'} @tab A cell array of character vectors or a ## string array specifying the names of the variables in the output table. The ## number of specified names must match the number of expected variables in the ## output table. ## @end multitable ## ## @subheading Plotting Syntax ## ## The syntax @code{grpstats (@var{x}, @var{group}, @var{alpha})} generates an ## @code{errorbar} plot with the group means and their respective confidence ## intervals. @var{x} must be a numeric vector or matrix. @var{alpha} is a ## scalar between 0 and 1 that determines the confidence level. This syntax is ## an alternative to calling @code{errorbar} after computing @qcode{'mean'} and ## @qcode{'meanci'} statistics. The optional output @var{h} is a handle to the ## hggroup object representing the data plot and errorbars. ## ## @seealso{grp2idx} ## @end deftypefn function [varargout] = grpstats (x, group = [], whichstats = [], varargin) ## Check data input if (nargin < 1) print_usage (); endif if (ndims (x) != 2) error ("grpstats: X must be a matrix or a table."); endif if (isempty (x)) [varargout] = repmat ({[]}, nargout, 1); return; endif if (! istable (x) && isvector (x)) x = x(:); endif [r, c] = size (x); ## For table input no more than 1 output argument is allowed if (istable (x) && nargout > 1) error ("grpstats: only one output argument in allowed when X is a table."); endif ## Add default grouping variable no_group = true; if (isempty (group)) grp_idx = ones (r, 1); r_names = {'All'}; ngroups = 1; no_group = false; endif ## Check for plotting functional form with three input arguments if (nargin == 3 && isscalar (whichstats) && whichstats > 0 && whichstats < 1) do_plot = true; alpha = whichstats; whichstats = []; fcn_names = {'mean', 'meanci'}; if (! (isnumeric (x))) error ("grpstats: X must be numeric to plot mean and CI for each group."); endif else do_plot = false; endif ## Parse statistical functions if (isempty (whichstats)) fcn_names = {'mean'}; else if (ischar (whichstats) || isstring (whichstats)) fcn_names = cellstr (whichstats); elseif (iscellstr (whichstats)) fcn_names = whichstats; elseif (is_function_handle (whichstats)) fcn_names = {whichstats}; elseif (iscell (whichstats)) TF = cellfun (@(x) ischar (x) || is_function_handle (x), whichstats); if (all (TF)) fcn_names = whichstats; else error ("grpstats: invalid WHICHSTATS specification in cell array."); endif else #if (! (isscalar (whichstats) && whichstats > 0 && whichstats < 1)) error ("grpstats: invalid WHICHSTATS data type."); endif ## At this point we have cell array with either function names or ## function handles which need to be tested before going any further valid_fcn = {'mean', 'median', 'sem', 'std', 'var', 'min', 'max', ... 'range', 'numel', 'meanci', 'predci', 'gname'}; is_char = cellfun ('ischar', fcn_names); if (any (is_char)) isvalid = cellfun (@(x) ismember (x, valid_fcn), fcn_names(is_char)); if (! all (isvalid)) error ("grpstats: unrecognized function names in WHICHSTATS."); endif endif ## Function handles need to be tested on the actual data with try..catch ## blocks, because there are numerous combination in I/O size which does ## not worth to test preemptively. endif ## Parse optional Name-Value paired arguments optNames = {'Alpha', 'DataVars', 'VarNames'}; dfValues = {0.05, [], {}}; [alpha, DataVars, VarNames, args] = parsePairedArguments (optNames, dfValues, ... varargin(:)); if (! isempty (args)) tmp = args{1}; if (isscalar (tmp) && tmp > 0 && tmp < 1) alpha = tmp; else error ("grpstats: unrecognized input arguments."); endif endif ## Check alpha value if (! (isscalar (alpha) && alpha > 0 && alpha < 1)) error ("grpstats: 'alpha' must be a real scalar in the range (0,1)."); endif ## Force VarNames to cell array of character vectors if (! isempty (VarNames)) if (ischar (VarNames) || isstring (VarNames)) VarNames = cellstr (VarNames); endif if (! iscellstr (VarNames)) error ("grpstats: invalid data types for 'VarNames'."); endif endif ## Handle group and whichstats for matrices and tables if (istable (x)) ## Get grouping variables for table input data if (no_group) try grp_vars = x(:, group); # () returns groupvars as a table catch error ("grpstats: cannot resolve GROUPVARS in input table."); end_try_catch ## Get unique groups and their indices to table data [g_names, ~, grp_idx] = unique (grp_vars); ## Get number of groups ngroups = rows (g_names); ## Convert logical to double c_names = convertvars (g_names, @islogical, 'double'); ## Create unique names for each group cstrtbl = cellstr (string (table2cell (c_names))); r_names = cstrtbl(:,1); [cr, cc] = size (cstrtbl); tmp_tmp = repmat ({'_'}, cr, 1); for idx = 2:cc r_names = strcat (r_names, tmp_tmp, cstrtbl(:,idx)); endfor endif ## Compute group count by default in tables for idx = 1:ngroups GroupCount(idx,:) = sum (grp_idx == idx); endfor ## Add group count and row names if (no_group) g_names = addvars (g_names, GroupCount); g_names.Properties.RowNames = r_names; else g_names = table (GroupCount, 'RowNames', r_names); endif ## Check that DataVars exist (if given) if (! isempty (DataVars)) ## Octave extension allows DataVars to be specified as a vartype object ## or a function handle to select variables based on their data types. ## To avoid multiple checking for all possible referencing schemes, ## let's add another try...catch blocks try work_tbl = x(:, DataVars); catch error ("grpstats: invalid 'DataVars' reference to table X."); end_try_catch else ## No specified DataVars, all table except grouping variables gets used work_tbl = removevars (x, group); endif ## From this point we can start applying functions on table variables ## We have to apply each function to every working variable ## We name the variable accordingly and append the output of the ## function at the g_names table. ## The function is applied on each group of each variable ## Handle functions need try..catch block to ensure they are compatible for var_idx = 1:columns (work_tbl) vname = work_tbl.Properties.VariableNames(var_idx); vdata = work_tbl{:,var_idx}; ncols = columns (vdata); for fcn_idx = 1:numel (fcn_names) new_vdata = []; fcn_op = fcn_names{fcn_idx}; if (is_function_handle (fcn_op)) fname = cellstr (fcn_op); new_vname = strcat (fname, '_', vname); try for idx = 1:ngroups new_vdata(idx,:) = fcn_op(vdata(grp_idx == idx, :)); endfor catch # no-op end_try_catch if (! isempty (new_vdata)) g_names = addvars (g_names, new_vdata, 'NewVariableNames', new_vname); endif else # it must be a function name switch (fcn_op) case 'mean' new_vname = strcat ('mean_', vname); for idx = 1:ngroups gdata = vdata(grp_idx == idx, :); new_vdata(idx,:) = mean (gdata, 1, 'omitnan'); endfor case 'median' new_vname = strcat ('median_', vname); for idx = 1:ngroups gdata = vdata(grp_idx == idx, :); new_vdata(idx,:) = median (gdata, 1, 'omitnan'); endfor case 'sem' new_vname = strcat ('sem_', vname); for idx = 1:ngroups gdata = vdata(grp_idx == idx, :); new_vdata(idx,:) = std (gdata, 0, 1, 'omitnan') ./ ... sqrt (size (gdata, 1) - sum (isnan (gdata), 1)); endfor case 'std' new_vname = strcat ('std_', vname); for idx = 1:ngroups gdata = vdata(grp_idx == idx, :); new_vdata(idx,:) = std (gdata, 0, 1, 'omitnan'); endfor case 'var' new_vname = strcat ('var_', vname); for idx = 1:ngroups gdata = vdata(grp_idx == idx, :); new_vdata(idx,:) = var (gdata, 0, 1, 'omitnan'); endfor case 'min' new_vname = strcat ('min_', vname); for idx = 1:ngroups gdata = vdata(grp_idx == idx, :); new_vdata(idx,:) = min (gdata, [], 1); endfor case 'max' new_vname = strcat ('max_', vname); for idx = 1:ngroups gdata = vdata(grp_idx == idx, :); new_vdata(idx,:) = max (gdata, [], 1); endfor case 'range' new_vname = strcat ('range_', vname); for idx = 1:ngroups gdata = vdata(grp_idx == idx, :); new_vdata(idx,:) = range (gdata, 1); endfor case 'numel' new_vname = strcat ('numel_', vname); for idx = 1:ngroups gdata = vdata(grp_idx == idx, :); new_vdata(idx,:) = size (gdata, 1) - sum (isnan (gdata), 1); endfor case 'meanci' new_vname = strcat ('meanci_', vname); ## Preallocate twice the columns in variable (lower, upper) new_vdata = NaN (ngroups, ncols * 2); for idx = 1:ngroups gdata = vdata(grp_idx == idx, :); m = mean (gdata, 1, 'omitnan'); n = size (gdata, 1) - sum (isnan (gdata), 1); s = std (gdata, 0, 1, 'omitnan') ./ sqrt (max (n,1)); ## Avoid invalid tinv calls for degenerate df df = max (n - 1, 0); tval = zeros (1, size (gdata, 2)); pos = (df > 0); if (any (pos)) tval(pos) = - tinv (alpha / 2, df(pos)); endif d = s .* tval; d(n < 2) = NaN; new_vdata(idx,[1:2:end]) = m - d; new_vdata(idx,[2:2:end]) = m + d; endfor case 'predci' new_vname = strcat ('predci_', vname); ## Preallocate twice the columns in variable (lower, upper) new_vdata = NaN (ngroups, ncols * 2); for idx = 1:ngroups gdata = vdata(grp_idx == idx, :); m = mean (gdata, 1, 'omitnan'); n = size (gdata, 1) - sum (isnan (gdata), 1); s = std (gdata, 0, 1, 'omitnan') .* sqrt (1 + (1 ./ max (n,1))); df = max (n - 1, 0); tval = zeros (1, size (gdata, 2)); pos = (df > 0); if (any (pos)) tval(pos) = - tinv (alpha / 2, df(pos)); endif d = s .* tval; d(n < 2) = NaN; new_vdata(idx,[1:2:end]) = m - d; new_vdata(idx,[2:2:end]) = m + d; endfor endswitch if (! isempty (new_vdata)) g_names = addvars (g_names, new_vdata, 'NewVariableNames', new_vname); endif endif endfor endfor ## Last check. VarNames must equal the number of expected variables if (! isempty (VarNames)) if (numel (VarNames) != columns (g_names)) error ("grpstats: 'VarNames' do not match expected variables."); endif g_names - renamevars (g_names, VarNames); endif varargout{1} = g_names; return; else ## Get groups for array input data if (no_group) if (iscell (group) && ! iscellstr (group)) ## Multiple grouping variables in cell array grp_vars = numel (group); [grp_idx, g_names1] = grp2idx (group{1}); if (numel (grp_idx) != r) error ("grpstats: samples in X and GROUPS mismatch."); endif all_g_names = {g_names1}; if (grp_vars > 1) for g_idx = 2:grp_vars [tmp_grp_idx, tmp_g_names] = grp2idx (group{g_idx}); if (numel (tmp_grp_idx) != r) error ("grpstats: samples in X and GROUPS mismatch."); endif grp_idx = [grp_idx, tmp_grp_idx]; all_g_names{g_idx} = tmp_g_names; endfor endif ## Get combination of unique groups and their common index to X [g_names_idx, ~, grp_idx] = unique (grp_idx, 'rows'); ngroups = rows (g_names_idx); c_names = cell (ngroups, grp_vars); for gvar_idx = 1:grp_vars gn = all_g_names{gvar_idx}; c_names(:, gvar_idx) = gn(g_names_idx(:,gvar_idx)); endfor g_names = c_names; else [grp_idx, g_names] = grp2idx (group); ngroups = numel (g_names); if (numel (grp_idx) != r) error ("grpstats: samples in X and GROUPS mismatch."); endif endif endif ## Check for plot option if (do_plot) ## Calculate mean and ci for idx = 1:ngroups group_x = x(find (grp_idx == idx), :); mu(idx,:) = mean (group_x, 1, 'omitnan'); n = size (group_x, 1) - sum (isnan (group_x), 1); s = std (group_x, 0, 1, 'omitnan') ./ sqrt (max (n,1)); ## Avoid invalid tinv calls for degenerate df df = max (n - 1, 0); tval = zeros (1, size (group_x, 2)); pos = (df > 0); if (any (pos)) tval(pos) = - tinv (alpha / 2, df(pos)); endif d = s .* tval; d(n < 2) = NaN; ci(idx,:) = d; endfor ## Plot the error bars h = errorbar ([1:ngroups]', mu, ci); xticks ([1:ngroups]); xlim ([0, ngroups+1]); ## For multiple grouping variables, the ticklabels need some work labels = g_names(:,1); if (columns (g_names) > 1) newlines = repmat ("\n", ngroups, 1); for ic = 2:columns (g_names) labels = strcat (labels, newlines, g_names(:,ic)); endfor endif xticklabels (labels); ## MATLAB functional form does not return an output. In Octave, ## if output is requested, we return an axes handle to the plot. if (nargout > 0) varargout{1} = h; endif return; endif ## Check consistent number of output arguments fcn_num = numel (fcn_names); if (! (nargout == 0 && fcn_num == 1) && nargout != fcn_num) error ("grpstats: inconsistent number of output arguments."); endif ## From this point we can start applying functions on the entire array for fcn_idx = 1:fcn_num switch (fcn_names{fcn_idx}) case 'mean' for idx = 1:ngroups group_x = x(find (grp_idx == idx), :); group_mean(idx,:) = mean (group_x, 1, 'omitnan'); endfor varargout{fcn_idx} = group_mean; case 'median' for idx = 1:ngroups group_x = x(find (grp_idx == idx), :); group_mean(idx,:) = median (group_x, 1, 'omitnan'); endfor varargout{fcn_idx} = group_mean; case 'sem' for idx = 1:ngroups group_x = x(find (grp_idx == idx), :); group_sem(idx,:) = std (group_x, 0, 1, 'omitnan') / ... sqrt (size (group_x, 1) - sum (isnan (group_x), 1)); endfor varargout{fcn_idx} = group_sem; case 'std' for idx = 1:ngroups group_x = x(find (grp_idx == idx), :); group_std(idx,:) = std (group_x, 0, 1, 'omitnan'); endfor varargout{fcn_idx} = group_std; case 'var' for idx = 1:ngroups group_x = x(find (grp_idx == idx), :); group_var(idx,:) = var (group_x, 0, 1, 'omitnan'); endfor varargout{fcn_idx} = group_var; case 'min' for idx = 1:ngroups group_x = x(find (grp_idx == idx), :); group_min(idx,:) = nanmin (group_x); endfor varargout{fcn_idx} = group_min; case 'max' for idx = 1:ngroups group_x = x(find (grp_idx == idx), :); group_max(idx,:) = nanmax (group_x); endfor varargout{fcn_idx} = group_max; case 'range' func_handle = @(x) range (x, 1); for idx = 1:ngroups group_x = x(find (grp_idx == idx), :); group_range(idx,:) = range (group_x, 1); endfor varargout{fcn_idx} = group_range; case 'numel' for idx = 1:ngroups group_x = x(find (grp_idx == idx), :); group_numel(idx,:) = size (group_x, 1) - sum (isnan (group_x), 1); endfor varargout{fcn_idx} = group_numel; case 'meanci' ## Allocate as 3-D: [ngroups x c x 2] (lower, upper) group_meanci = NaN (ngroups, c, 2); for idx = 1:ngroups group_x = x(find (grp_idx == idx), :); m = mean (group_x, 1, 'omitnan'); n = size (group_x, 1) - sum (isnan (group_x), 1); s = std (group_x, 0, 1, 'omitnan') ./ sqrt (max (n,1)); ## Avoid invalid tinv calls for degenerate df df = max (n - 1, 0); tval = zeros (1, size (group_x, 2)); pos = (df > 0); if (any (pos)) tval(pos) = - tinv (alpha / 2, df(pos)); endif d = s .* tval; d(n < 2) = NaN; group_meanci(idx, :, 1) = m - d; group_meanci(idx, :, 2) = m + d; endfor ## MATLAB returns [ngroups x 2] when nvars == 1 ## Octave used canonical 3-D. if (c == 1) ## Reshape to [ngroups x 2] varargout{fcn_idx} = reshape (group_meanci, ngroups, 2); else varargout{fcn_idx} = group_meanci; endif case 'predci' ## Allocate as 3-D: [ngroups x c x 2] (lower, upper) group_predci = NaN (ngroups, c, 2); for idx = 1:ngroups group_x = x(find (grp_idx == idx), :); m = mean (group_x, 1, 'omitnan'); n = size (group_x, 1) - sum (isnan (group_x), 1); s = std (group_x, 0, 1, 'omitnan') .* sqrt (1 + (1 ./ max (n,1))); df = max (n - 1, 0); tval = zeros (1, size (group_x, 2)); pos = (df > 0); if (any (pos)) tval(pos) = - tinv (alpha / 2, df(pos)); endif d = s .* tval; d(n < 2) = NaN; group_predci(idx, :, 1) = m - d; group_predci(idx, :, 2) = m + d; endfor if (c == 1) varargout{fcn_idx} = reshape (group_predci, ngroups, 2); else varargout{fcn_idx} = group_predci; endif case 'gname' varargout{fcn_idx} = g_names; endswitch endfor endif endfunction %!demo %! load carsmall; %! [m, p, g] = grpstats (Weight, Model_Year, {'mean', 'predci', 'gname'}) %! n = length (m); %! errorbar ((1:n)',m,p(:,2)-m); %! set (gca, 'xtick', 1:n, 'xticklabel', g); %! title ('95% prediction intervals for mean weight by year'); %!demo %! load carsmall; %! [m, p, g] = grpstats ([Acceleration,Weight/1000],Cylinders, ... %! {'mean', 'meanci', 'gname'}, 0.05) %! [c, r] = size (m); %! errorbar ((1:c)'.*ones (c,r),m,p(:,[(1:r)])-m); %! set (gca, 'xtick', 1:c, 'xticklabel', g); %! title ('95% prediction intervals for mean weight by year'); %!demo %! ## Plot mean and 95% CI for a single grouping variable %! load carsmall; %! grpstats (Weight, Model_Year, 0.05); %! title ('Mean Weight by Model Year'); %!demo %! ## Plot mean and 95% CI for two grouping variables %! load carsmall; %! grpstats (Weight, {Origin, Cylinders}, 0.05); %! title ('Mean Weight by Origin and Number of Cylinders'); %!test %! load carsmall %! means = grpstats (Acceleration, Origin); %! assert_equal (means, [14.4377; 18.0500; 15.8867; 16.3778; 16.6000; 15.5000], 0.001); %!test %! load carsmall %! [grpMin, grpMax, grp] = grpstats (Acceleration, Origin, {'min', 'max', ... %! 'gname'}); %! assert_equal (grpMin, [8.0; 15.3; 13.9; 12.2; 15.7; 15.5]); %! assert_equal (grpMax, [22.2; 21.9; 18.2; 24.6; 17.5; 15.5]); %!test %! load carsmall %! [grpMin, grpMax, grp] = grpstats (Acceleration, Origin, {'min', 'max', ... %! 'gname'}); %! assert_equal (grp', {'USA', 'France', 'Japan', 'Germany', 'Sweden', 'Italy'}); %!test %! load carsmall %! [m, p, g] = grpstats ([Acceleration, Weight/1000], Cylinders, ... %! {'mean', 'meanci', 'gname'}, 0.05); %! ## check meanci lower bounds (first slice) with tolerance %! expected_lower = [15.9163; 15.6622; 10.7968]; %! expected_upper = [17.4249; 17.2907; 12.4845]; %! assert_equal (abs (p(:,1,1)), expected_lower, 1e-3); %! assert_equal (abs (p(:,1,2)), expected_upper, 1e-3); %!test %! [mC, g] = grpstats ([], []); %! assert_equal (isempty (mC), true); %! assert_equal (isempty (g), true); %!test %! ## column vector, no group %! x = [1; 2; 3; 4; 5]; %! m = grpstats (x); %! expected = 3; %! assert_equal (m, expected); %!test %! ## row vector, no group %! x = [1 2 3 4 5]; %! m = grpstats (x); %! expected = 3; %! assert_equal (m, expected); %!test %! ## matrix, no group %! x = [1 2; 3 4; 5 6]; %! m = grpstats (x); %! expected = [3 4]; %! assert_equal (m, expected); %!test %! ## vector, numeric groups %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! m = grpstats (x, g); %! expected = [15; 35; 55]; %! assert_equal (m, expected); %!test %! ## vector, cellstr groups %! x = [10; 20; 30; 40; 50; 60]; %! g = {'A'; 'A'; 'B'; 'B'; 'C'; 'C'}; %! m = grpstats (x, g); %! expected = [15; 35; 55]; %! assert_equal (m, expected); %!test %! ## matrix, numeric groups %! x = [1 10; 2 20; 3 30; 4 40; 5 50; 6 60]; %! g = [1; 1; 2; 2; 3; 3]; %! m = grpstats (x, g); %! expected = [1.5 15; 3.5 35; 5.5 55]; %! assert_equal (m, expected); %!test %! ## NaN handling %! x = [1; NaN; 3; 4; NaN; 6]; %! g = [1; 1; 2; 2; 3; 3]; %! m = grpstats (x, g); %! expected = [1; 3.5; 6]; %! assert_equal (m, expected); %!test %! ## single group %! x = [1; 2; 3; 4; 5]; %! g = ones (5, 1); %! m = grpstats (x, g); %! expected = 3; %! assert_equal (m, expected); %!test %! ## single statistic %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! m = grpstats (x, g, 'mean'); %! expected = [15; 35; 55]; %! assert_equal (m, expected); %!test %! ## single statistic %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! m = grpstats (x, g, 'median'); %! expected = [15; 35; 55]; %! assert_equal (m, expected); %!test %! ## single statistic %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! s = grpstats (x, g, 'std'); %! expected = [7.07106781186548; 7.07106781186548; 7.07106781186548]; %! assert_equal (s, expected, 1e-14); %!test %! ## single statistic %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! v = grpstats (x, g, 'var'); %! expected = [50; 50; 50]; %! assert_equal (v, expected); %!test %! ## single statistic %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! s = grpstats (x, g, 'sem'); %! expected = [5; 5; 5]; %! assert_equal (s, expected); %!test %! ## single statistic %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! mn = grpstats (x, g, 'min'); %! expected = [10; 30; 50]; %! assert_equal (mn, expected); %!test %! ## single statistic %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! mx = grpstats (x, g, 'max'); %! expected = [20; 40; 60]; %! assert_equal (mx, expected); %!test %! ## single statistic %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! r = grpstats (x, g, 'range'); %! expected = [10; 10; 10]; %! assert_equal (r, expected); %!test %! ## single statistic %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! n = grpstats (x, g, 'numel'); %! expected = [2; 2; 2]; %! assert_equal (n, expected); %!test %! ## single statistic %! x = [10; 20; 30; 40; 50; 60]; %! g = {'A'; 'A'; 'B'; 'B'; 'C'; 'C'}; %! names = grpstats (x, g, 'gname'); %! expected = {'A'; 'B'; 'C'}; %! assert_equal (names, expected); %!test %! ## single statistic (default alpha) %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! ci = grpstats (x, g, 'meanci'); %! expected = [-48.5310236808735 78.5310236808735; -28.5310236808735 ... %! 98.5310236808735; -8.53102368087348 118.531023680873]; %! assert_equal (ci, expected, 1e-12); %!test %! ## single statistic (default alpha) %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! ci = grpstats (x, g, 'predci'); %! expected = [-95.0389608721344 125.038960872134; -75.0389608721344 ... %! 145.038960872134; -55.0389608721344 165.038960872134]; %! assert_equal (ci, expected, 1e-12); %!test %! ## mean + std %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! [m, s] = grpstats (x, g, {'mean', 'std'}); %! expected_m = [15; 35; 55]; %! expected_s = [7.07106781186548; 7.07106781186548; 7.07106781186548]; %! assert_equal (m, expected_m, 1e-14); %! assert_equal (s, expected_s, 1e-14); %!test %! ## min + max + range %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! [mn, mx, r] = grpstats (x, g, {'min', 'max', 'range'}); %! expected_mn = [10; 30; 50]; %! expected_mx = [20; 40; 60]; %! expected_r = [10; 10; 10]; %! assert_equal (mn, expected_mn); %! assert_equal (mx, expected_mx); %! assert_equal (r, expected_r); %!test %! ## mean + median + numel + gname %! x = [10; 20; 30; 40; 50; 60]; %! g = {'A'; 'A'; 'B'; 'B'; 'C'; 'C'}; %! [m, med, n, names] = grpstats (x, g, {'mean', 'median', 'numel', 'gname'}); %! expected_m = [15; 35; 55]; %! expected_med = [15; 35; 55]; %! expected_n = [2; 2; 2]; %! expected_names = {'A'; 'B'; 'C'}; %! assert_equal (m, expected_m); %! assert_equal (med, expected_med); %! assert_equal (n, expected_n); %! assert_equal (names, expected_names); %!test %! ## all basic statistics %! x = [10; 20; 30; 40; 50; 60; 70; 80]; %! g = [1; 1; 2; 2; 2; 2; 3; 3]; %! [m, med, s, v, se, mn, mx, r, n] = grpstats (x, g, {'mean', 'median', ... %! 'std', 'var', 'sem', ... %! 'min', 'max', 'range', ... %! 'numel'}); %! expected_m = [15; 45; 75]; %! expected_med = [15; 45; 75]; %! expected_s = [7.07106781186548; 12.9099444873581; 7.07106781186548]; %! expected_v = [50; 166.666666666667; 50]; %! expected_se = [5; 6.45497224367903; 5]; %! expected_mn = [10; 30; 70]; %! expected_mx = [20; 60; 80]; %! expected_r = [10; 30; 10]; %! expected_n = [2; 4; 2]; %! assert_equal (m, expected_m); %! assert_equal (med, expected_med); %! assert_equal (s, expected_s, 1e-13); %! assert_equal (v, expected_v, 1e-12); %! assert_equal (se, expected_se, 1e-14); %! assert_equal (mn, expected_mn); %! assert_equal (mx, expected_mx); %! assert_equal (r, expected_r); %! assert_equal (n, expected_n); %!test %! ## meanci-alpha-0.1 %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! ci = grpstats (x, g, 'meanci', 0.1); %! expected = [-16.5687575733752 46.5687575733752; 3.4312424266248 ... %! 66.5687575733752; 23.4312424266248 86.5687575733752]; %! assert_equal (ci, expected, 1e-13); %!test %! ## predci-alpha-0.1 %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! ci = grpstats (x, g, 'predci', 0.1); %! expected = [-39.6786920489106 69.6786920489106; -19.6786920489106 ... %! 89.6786920489106; 0.321307951089366 109.678692048911]; %! assert_equal (ci, expected, 1e-12); %!test %! ## meanci-alpha-0.01 %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! ci = grpstats (x, g, 'meanci', 0.01); %! expected = [-303.283705814358 333.283705814358; -283.283705814358 ... %! 353.283705814358; -263.283705814358 373.283705814358]; %! assert_equal (ci, expected, 3e-8); %!test %! ## predci-alpha-0.01 %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! ci = grpstats (x, g, 'predci', 0.01); %! expected = [-536.283549691775 566.283549691775; -516.283549691775 ... %! 586.283549691775; -496.283549691775 606.283549691775]; %! assert_equal (ci, expected, 3e-8); %!test %! ## meanci-alpha-0.2 %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! ci = grpstats (x, g, 'meanci', 0.2); %! expected = [-0.388417685876263 30.3884176858763; 19.6115823141237 ... %! 50.3884176858763; 39.6115823141237 70.3884176858763]; %! assert_equal (ci, expected, 1e-13); %!test %! ## predci-alpha-0.2 %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! ci = grpstats (x, g, 'predci', 0.2); %! expected = [-11.6535212800292 41.6535212800292; 8.34647871997083 ... %! 61.6535212800292; 28.3464787199708 81.6535212800292]; %! assert_equal (ci, expected, 1e-13); %!test %! ## meanci, name-value alpha=0.2 %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! ci = grpstats (x, g, 'meanci', 'alpha', 0.2); %! expected = [-0.388417685876263 30.3884176858763; 19.6115823141237 ... %! 50.3884176858763; 39.6115823141237 70.3884176858763]; %! assert_equal (ci, expected, 1e-13); %!test %! ## meanci + predci, alpha=0.01 %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 2; 2; 3; 3]; %! [ci_m, ci_p] = grpstats (x, g, {'meanci', 'predci'}, 0.01); %! expected_m = [-303.283705814358 333.283705814358; -283.283705814358 ... %! 353.283705814358; -263.283705814358 373.283705814358]; %! expected_p = [-536.283549691775 566.283549691775; -516.283549691775 ... %! 586.283549691775; -496.283549691775 606.283549691775]; %! assert_equal (ci_m, expected_m, 3e-8); %! assert_equal (ci_p, expected_p, 3e-8); %!test %! ## matrix, mean+std+numel %! x = [1 10; 2 20; 3 30; 4 40; 5 50; 6 60]; %! g = [1; 1; 2; 2; 3; 3]; %! [m, s, n] = grpstats (x, g, {'mean', 'std', 'numel'}); %! expected_m = [1.5 15; 3.5 35; 5.5 55]; %! expected_s = [0.707106781186548 7.07106781186548; 0.707106781186548 ... %! 7.07106781186548; 0.707106781186548 7.07106781186548]; %! expected_n = [2 2; 2 2; 2 2]; %! assert_equal (m, expected_m); %! assert_equal (s, expected_s, 1e-14); %! assert_equal (n, expected_n); %!test %! ## matrix with NaN, mean+numel %! x = [1 10; NaN 20; 3 NaN; 4 40; 5 50; 6 60]; %! g = [1; 1; 2; 2; 3; 3]; %! [m, n] = grpstats (x, g, {'mean', 'numel'}); %! expected_m = [1 15; 3.5 40; 5.5 55]; %! expected_n = [1 2; 2 1; 2 2]; %! assert_equal (m, expected_m); %! assert_equal (n, expected_n); %!test %! ## 3-column matrix, mean+min+max %! x = [1 100 1000; 2 200 2000; 3 300 3000; 4 400 4000]; %! g = [1; 1; 2; 2]; %! [m, mn, mx] = grpstats (x, g, {'mean', 'min', 'max'}); %! expected_m = [1.5 150 1500; 3.5 350 3500]; %! expected_mn = [1 100 1000; 3 300 3000]; %! expected_mx = [2 200 2000; 4 400 4000]; %! assert_equal (m, expected_m); %! assert_equal (mn, expected_mn); %! assert_equal (mx, expected_mx); %!test %! ## one element per group %! x = [1; 2; 3]; %! g = [1; 2; 3]; %! [m, s, n] = grpstats (x, g, {'mean', 'std', 'numel'}); %! expected_m = [1; 2; 3]; %! expected_s = [0; 0; 0]; %! expected_n = [1; 1; 1]; %! assert_equal (m, expected_m); %! assert_equal (s, expected_s); %! assert_equal (n, expected_n); %!test %! ## group with all NaN %! x = [1; 2; NaN; NaN; 5; 6]; %! g = [1; 1; 2; 2; 3; 3]; %! [m, s, n] = grpstats (x, g, {'mean', 'std', 'numel'}); %! expected_m = [1.5; NaN; 5.5]; %! expected_s = [0.707106781186548; NaN; 0.707106781186548]; %! expected_n = [2; 0; 2]; %! assert_equal (m, expected_m); %! assert_equal (s, expected_s, 1e-14); %! assert_equal (n, expected_n); %!test %! ## unequal group sizes %! x = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]; %! g = [1; 1; 1; 1; 2; 2; 2; 3; 3; 3]; %! [m, v, n] = grpstats (x, g, {'mean', 'var', 'numel'}); %! expected_m = [2.5; 6; 9]; %! expected_v = [1.66666666666667; 1; 1]; %! expected_n = [4; 3; 3]; %! assert_equal (m, expected_m); %! assert_equal (v, expected_v, 1e-14); %! assert_equal (n, expected_n); %!test %! ## non-consecutive numeric groups %! x = [10; 20; 30; 40; 50; 60]; %! g = [1; 1; 5; 5; 10; 10]; %! [m, names] = grpstats (x, g, {'mean', 'gname'}); %! expected_m = [15; 35; 55]; %! expected_names = {'1'; '5'; '10'}; %! assert_equal (m, expected_m); %! assert_equal (names, expected_names); %!test %! ## unsorted string groups %! x = [30; 10; 40; 20; 60; 50]; %! g = {'C'; 'A'; 'C'; 'A'; 'B'; 'B'}; %! [m, names] = grpstats (x, g, {'mean', 'gname'}); %! expected_m = [35; 15; 55]; %! expected_names = {'C'; 'A'; 'B'}; %! assert_equal (m, expected_m); %! assert_equal (names, expected_names); %!test %! ## 20 groups, one element each %! x = (1:20)'; %! g = (1:20)'; %! [m, n] = grpstats (x, g, {'mean', 'numel'}); %! expected_m = (1:20)'; %! expected_n = ones (20, 1); %! assert_equal (m, expected_m); %! assert_equal (n, expected_n); %!test %! ## large sample meanci %! x = (1:50)'; %! g = [ones(25, 1); 2 * ones(25, 1)]; %! ci = grpstats (x, g, 'meanci'); %! expected = [9.96202357522388 16.0379764247761; 34.9620235752239 ... %! 41.0379764247761]; %! assert_equal (ci, expected, 1e-13); %!test %! ## large sample predci %! x = (1:50)'; %! g = [ones(25, 1); 2 * ones(25, 1)]; %! ci = grpstats (x, g, 'predci'); %! expected = [-2.49070107176829 28.4907010717683; 22.5092989282317 ... %! 53.4907010717683]; %! assert_equal (ci, expected, 2e-14); %!test %! Y = [5; 6; 7; 4; 9; 8]; %! X = [1; 2; 3; 4; 5; 6]; %! Group = categorical ({'A'; 'A'; 'B'; 'B'; 'C'; 'C'}); %! tbl = table (Y, X, Group); %! stats_tbl = grpstats (tbl, 'Group', {'mean', 'numel'}); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y', 'numel_Y', ... %! 'mean_X', 'numel_X'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'; 'C'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (stats_tbl.mean_Y, [5.5; 5.5; 8.5]); %! assert_equal (stats_tbl.numel_Y, [2; 2; 2]); %! assert_equal (stats_tbl.mean_X, [1.5; 3.5; 5.5]); %! assert_equal (stats_tbl.numel_X, [2; 2; 2]); %!test %! Y = [5; 6; 7; 4; 9; 8]; %! Group = categorical ({'A'; 'A'; 'B'; 'B'; 'C'; 'C'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'; 'C'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (stats_tbl.mean_Y, [5.5; 5.5; 8.5]); %!test %! Y = [10; 20; 30; 40]; %! X = [100; 200; 300; 400]; %! Z = [1000; 2000; 3000; 4000]; %! Group = categorical ({'A'; 'A'; 'B'; 'B'}); %! tbl = table (Y, X, Z, Group); %! stats_tbl = grpstats (tbl, 'Group', {'mean', 'numel'}); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y', 'numel_Y', 'mean_X', 'numel_X', 'mean_Z', 'numel_Z'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'}); %! assert_equal (stats_tbl.GroupCount, [2; 2]); %! assert_equal (stats_tbl.mean_Y, [15; 35]); %! assert_equal (stats_tbl.numel_Y, [2; 2]); %! assert_equal (stats_tbl.mean_X, [150; 350]); %! assert_equal (stats_tbl.numel_X, [2; 2]); %! assert_equal (stats_tbl.mean_Z, [1500; 3500]); %! assert_equal (stats_tbl.numel_Z, [2; 2]); %!test %! Y = [1; 2; 3; 4; 5; 6; 7; 8]; %! Group = categorical ({'A'; 'A'; 'A'; 'A'; 'B'; 'B'; 'B'; 'B'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'}); %! assert_equal (stats_tbl.GroupCount, [4; 4]); %! assert_equal (stats_tbl.mean_Y, [2.5; 6.5]); %!test %! Y = [1; 2; 3; 4; 5; 6; 7]; %! Group = categorical ({'A'; 'A'; 'A'; 'A'; 'A'; 'B'; 'B'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', {'mean', 'numel'}); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y', 'numel_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'}); %! assert_equal (stats_tbl.GroupCount, [5; 2]); %! assert_equal (stats_tbl.mean_Y, [3; 6.5]); %! assert_equal (stats_tbl.numel_Y, [5; 2]); %!test %! Y = [10; 20; 30]; %! Group = categorical ({'A'; 'B'; 'C'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'; 'C'}); %! assert_equal (stats_tbl.GroupCount, [1; 1; 1]); %! assert_equal (stats_tbl.mean_Y, [10; 20; 30]); %!test %! Y = [5; 5; 5; 5]; %! Group = categorical ({'A'; 'A'; 'B'; 'B'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'}); %! assert_equal (stats_tbl.GroupCount, [2; 2]); %! assert_equal (stats_tbl.mean_Y, [5; 5]); %!test %! Y = [1; NaN; 3; 4; NaN; 6]; %! X = [10; 20; NaN; 40; 50; NaN]; %! Group = categorical ({'A'; 'A'; 'B'; 'B'; 'C'; 'C'}); %! tbl = table (Y, X, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y', 'mean_X'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'; 'C'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (stats_tbl.mean_Y, [1; 3.5; 6]); %! assert_equal (stats_tbl.mean_X, [15; 40; 50]); %!test %! Y = [1; NaN; 3; 4; 5; 6]; %! Group = categorical ({'A'; 'A'; 'B'; 'B'; 'C'; 'C'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', {'mean', 'numel'}); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y', 'numel_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'; 'C'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (stats_tbl.mean_Y, [1; 3.5; 5.5]); %! assert_equal (stats_tbl.numel_Y, [1; 2; 2]); %!test %! Y = [100; 200; 300; 400; 500; 600]; %! Group = categorical ({'Group1'; 'Group1'; 'Group2'; 'Group2'; ... %! 'Group3'; 'Group3'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'Group1'; 'Group2'; 'Group3'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (stats_tbl.mean_Y, [150; 350; 550]); %!test %! Var1 = [1; 2; 3; 4]; %! Var2 = [10; 20; 30; 40]; %! Var3 = [100; 200; 300; 400]; %! Var4 = [1000; 2000; 3000; 4000]; %! Group = categorical ({'A'; 'A'; 'B'; 'B'}); %! tbl = table (Var1, Var2, Var3, Var4, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Var1', 'mean_Var2', 'mean_Var3', 'mean_Var4'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'}); %! assert_equal (stats_tbl.GroupCount, [2; 2]); %! assert_equal (stats_tbl.mean_Var1, [1.5; 3.5]); %! assert_equal (stats_tbl.mean_Var2, [15; 35]); %! assert_equal (stats_tbl.mean_Var3, [150; 350]); %! assert_equal (stats_tbl.mean_Var4, [1500; 3500]); %!test %! Y = [1.5; 2.5; 3.5; 4.5; 5.5; 6.5]; %! Group = categorical ({'A'; 'A'; 'B'; 'B'; 'C'; 'C'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'; 'C'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (stats_tbl.mean_Y, [2; 4; 6]); %!test %! Y = [-10; -20; 30; 40; 50; 60]; %! Group = categorical ({'A'; 'A'; 'B'; 'B'; 'C'; 'C'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'; 'C'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (stats_tbl.mean_Y, [-15; 35; 55]); %!test %! Y = [0; 0; 0; 0; 0; 0]; %! Group = categorical ({'A'; 'A'; 'B'; 'B'; 'C'; 'C'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'; 'C'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (stats_tbl.mean_Y, [0; 0; 0]); %!test %! Y = [1e6; 2e6; 3e6; 4e6; 5e6; 6e6]; %! Group = categorical ({'A'; 'A'; 'B'; 'B'; 'C'; 'C'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'; 'C'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (stats_tbl.mean_Y, [1.5e6; 3.5e6; 5.5e6]); %!test %! Y = (1:10)'; %! Group = categorical (repmat ({'A'; 'B'}, 5, 1)); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', {'mean', 'numel'}); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, ... %! {'Group', 'GroupCount', 'mean_Y', 'numel_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'}); %! assert_equal (stats_tbl.GroupCount, [5; 5]); %! assert_equal (stats_tbl.mean_Y, [5; 6]); %! assert_equal (stats_tbl.numel_Y, [5; 5]); %!test %! Y = (1:20)'; %! Group = categorical (repmat ({'A'; 'B'; 'C'; 'D'}, 5, 1)); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'; 'C'; 'D'}); %! assert_equal (stats_tbl.GroupCount, [5; 5; 5; 5]); %! assert_equal (stats_tbl.mean_Y, [9; 10; 11; 12]); %!test %! Y = [1; 2; 3; 4; 5]; %! Group = categorical ({'A'; 'B'; 'C'; 'D'; 'E'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'; 'C'; 'D'; 'E'}); %! assert_equal (stats_tbl.GroupCount, [1; 1; 1; 1; 1]); %! assert_equal (stats_tbl.mean_Y, [1; 2; 3; 4; 5]); %!test %! Score1 = [85; 90; 78; 92; 88; 76]; %! Score2 = [82; 88; 75; 90; 85; 73]; %! Group = categorical ({'High'; 'High'; 'Med'; 'Med'; 'Low'; 'Low'}); %! tbl = table (Score1, Score2, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Score1', 'mean_Score2'}); %! assert_equal (stats_tbl.Properties.RowNames, {'High'; 'Low'; 'Med'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (stats_tbl.mean_Score1, [87.5; 82; 85]); %! assert_equal (stats_tbl.mean_Score2, [85; 79; 82.5]); %!test %! Height = [170; 175; 165; 180; 160; 185]; %! Weight = [70; 75; 65; 80; 60; 85]; %! Category = categorical ({'M'; 'M'; 'F'; 'F'; 'M'; 'M'}); %! tbl = table (Height, Weight, Category); %! stats_tbl = grpstats (tbl, 'Category', {'mean', 'numel'}); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Category', 'GroupCount', ... %! 'mean_Height', 'numel_Height', 'mean_Weight', 'numel_Weight'}); %! assert_equal (stats_tbl.Properties.RowNames, {'F'; 'M'}); %! assert_equal (stats_tbl.GroupCount, [2; 4]); %! assert_equal (stats_tbl.mean_Height, [172.5; 172.5]); %! assert_equal (stats_tbl.numel_Height, [2; 4]); %! assert_equal (stats_tbl.mean_Weight, [72.5; 72.5]); %! assert_equal (stats_tbl.numel_Weight, [2; 4]); %!test %! Value = [10.5; 11.2; 9.8; 10.1; 11.5; 10.8]; %! Type = categorical ({'A'; 'A'; 'A'; 'B'; 'B'; 'B'}); %! tbl = table (Value, Type); %! stats_tbl = grpstats (tbl, 'Type', 'numel'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Type', 'GroupCount', ... %! 'numel_Value'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'}); %! assert_equal (stats_tbl.GroupCount, [3; 3]); %! assert_equal (stats_tbl.numel_Value, [3; 3]); %!test %! Data = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]; %! Label = categorical ({'A'; 'A'; 'A'; 'A'; 'A'; 'B'; 'B'; 'B'; 'B'; 'B'}); %! tbl = table (Data, Label); %! stats_tbl = grpstats (tbl, 'Label', {'mean', 'numel'}); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Label', 'GroupCount', ... %! 'mean_Data', 'numel_Data'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'}); %! assert_equal (stats_tbl.GroupCount, [5; 5]); %! assert_equal (stats_tbl.mean_Data, [3; 8]); %! assert_equal (stats_tbl.numel_Data, [5; 5]); %!test %! X1 = [1; 2; 3; 4]; %! X2 = [5; 6; 7; 8]; %! G = categorical ({'A'; 'A'; 'B'; 'B'}); %! tbl = table (X1, X2, G); %! stats_tbl = grpstats (tbl, 'G', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'G', 'GroupCount', ... %! 'mean_X1', 'mean_X2'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'}); %! assert_equal (stats_tbl.GroupCount, [2; 2]); %! assert_equal (stats_tbl.mean_X1, [1.5; 3.5]); %! assert_equal (stats_tbl.mean_X2, [5.5; 7.5]); %!test %! Measurement = [100; 150; 200; 250; 300; 350]; %! GroupVar = categorical ({'Control'; 'Control'; 'Treatment'; 'Treatment'; ... %! 'Placebo'; 'Placebo'}); %! tbl = table (Measurement, GroupVar); %! stats_tbl = grpstats (tbl, 'GroupVar', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'GroupVar', 'GroupCount', ... %! 'mean_Measurement'}); %! assert_equal (stats_tbl.Properties.RowNames, {'Control'; 'Placebo'; 'Treatment'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (stats_tbl.mean_Measurement, [125; 325; 225]); %!test %! Y = [NaN; NaN; 3; 4; 5; 6]; %! Group = categorical ({'A'; 'A'; 'B'; 'B'; 'C'; 'C'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', 'mean'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'; 'C'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (isequaln (stats_tbl.mean_Y, [NaN; 3.5; 5.5]), true); %!test %! Y = [1; 2; NaN; NaN; NaN; NaN]; %! Group = categorical ({'A'; 'A'; 'B'; 'B'; 'C'; 'C'}); %! tbl = table (Y, Group); %! stats_tbl = grpstats (tbl, 'Group', {'mean', 'numel'}); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Group', 'GroupCount', ... %! 'mean_Y', 'numel_Y'}); %! assert_equal (stats_tbl.Properties.RowNames, {'A'; 'B'; 'C'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (isequaln (stats_tbl.mean_Y, [1.5; NaN; NaN]), true); %! assert_equal (stats_tbl.numel_Y, [2; 0; 0]); %!test %! Val = [5.5; 6.5; 7.5; 8.5]; %! Cat = categorical ({'X'; 'X'; 'Y'; 'Y'}); %! tbl = table (Val, Cat); %! stats_tbl = grpstats (tbl, 'Cat', 'numel'); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Cat', 'GroupCount', ... %! 'numel_Val'}); %! assert_equal (stats_tbl.Properties.RowNames, {'X'; 'Y'}); %! assert_equal (stats_tbl.GroupCount, [2; 2]); %! assert_equal (stats_tbl.numel_Val, [2; 2]); %!test %! A = [1; 2; 3; 4; 5; 6]; %! B = [10; 20; 30; 40; 50; 60]; %! C = [100; 200; 300; 400; 500; 600]; %! Grp = categorical ({'G1'; 'G1'; 'G2'; 'G2'; 'G3'; 'G3'}); %! tbl = table (A, B, C, Grp); %! stats_tbl = grpstats (tbl, 'Grp', {'mean', 'numel'}); %! assert_equal (istable (stats_tbl), true); %! assert_equal (stats_tbl.Properties.VariableNames, {'Grp', 'GroupCount', ... %! 'mean_A', 'numel_A', 'mean_B', 'numel_B', 'mean_C', 'numel_C'}); %! assert_equal (stats_tbl.Properties.RowNames, {'G1'; 'G2'; 'G3'}); %! assert_equal (stats_tbl.GroupCount, [2; 2; 2]); %! assert_equal (stats_tbl.mean_A, [1.5; 3.5; 5.5]); %! assert_equal (stats_tbl.numel_A, [2; 2; 2]); %! assert_equal (stats_tbl.mean_B, [15; 35; 55]); %! assert_equal (stats_tbl.numel_B, [2; 2; 2]); %! assert_equal (stats_tbl.mean_C, [150; 350; 550]); %! assert_equal (stats_tbl.numel_C, [2; 2; 2]); %!test %! x = [1; NaN; 3; 4]; %! g = [1; 1; 2; 2]; %! muci = grpstats (x, g, 'meanci'); %! assert_equal (muci, [NaN, NaN; -2.8531, 9.8531], 1e-4); %!test %! x = [1; NaN; 3; 4; 5; 6]; %! g = [1; 1; 1; 2; 2; 2]; %! predci = grpstats (x, g, 'predci'); %! assert_equal (predci, [-20.0078, 24.0078; 0.0317, 9.9683], 1e-4); ## Test input validation %!error grpstats (ones (2, 2, 2)) %!error ... %! [a, b] = grpstats (table (1)) %!error ... %! grpstats (ones (6, 2), [1; 1; 1; 2; 2; 2], {'mean', 1.5}) %!error ... %! grpstats (ones (6, 2), [1; 1; 1; 2; 2; 2], 1.5) %!error ... %! grpstats (ones (6, 2), [1; 1; 1; 2; 2; 2], 'some_function') %!error ... %! grpstats (ones (6, 2), [1; 1; 1; 2; 2; 2], 'mean', 35) %!error ... %! grpstats ([1:4]', {'A'; 'B'; 'A'; 'B'}, 'predci', 'somename', -0.1); %!error ... %! grpstats (ones (6, 2), [1; 1; 1; 2; 2; 2], 'mean', 'VarNames', 3) %!error ... %! grpstats ({ones(6, 2)}, [], 0.05) %!error ... %! grpstats ([1:4]', {'A'; 'B'; 'A'; 'B'}, 'predci', 'alpha', -0.1); %!error ... %! grpstats (table ([1:5]'), {'Var_5'}) %!error ... %! grpstats (table ([1:5]'), {'Var1'}, [], 'DataVars', 'Var5') %!error ... %! grpstats (table ([1:5]', [1:5]'), {'Var1'}, [], 'VarNames', {'A', 'B'}) %!error ... %! grpstats ([1:5]', {'A'; 'B'; 'A'; 'B'}) %!error ... %! m = grpstats ([1:4]', {'A'; 'B'; 'A'; 'B'}, {'mean', 'std'}) statistics-release-1.9.2/inst/Descriptive_Statistics/harmmean.m000066400000000000000000000263711524624707500247760ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{m} =} harmmean (@var{x}) ## @deftypefnx {statistics} {@var{m} =} harmmean (@var{x}, "all") ## @deftypefnx {statistics} {@var{m} =} harmmean (@var{x}, @var{dim}) ## @deftypefnx {statistics} {@var{m} =} harmmean (@var{x}, @var{vecdim}) ## @deftypefnx {statistics} {@var{m} =} harmmean (@dots{}, @var{nanflag}) ## ## Compute the harmonic mean of @var{x}. ## ## @itemize ## @item If @var{x} is a vector, then @code{harmmean(@var{x})} returns the ## harmonic mean of the elements in @var{x} defined as ## @tex ## $$ {\rm harmmean}(x) = \frac{N}{\sum_{i=1}^N \frac{1}{x_i}} $$ ## ## @end tex ## @ifnottex ## ## @example ## harmmean (@var{x}) = N / SUM_i @var{x}(i)^-1 ## @end example ## ## @end ifnottex ## @noindent ## where @math{N} is the length of the @var{x} vector. ## ## @item If @var{x} is a matrix, then @code{harmmean(@var{x})} returns a row ## vector with the harmonic mean of each columns in @var{x}. ## ## @item If @var{x} is a multidimensional array, then @code{harmmean(@var{x})} ## operates along the first nonsingleton dimension of @var{x}. ## ## @item @var{x} must not contain any negative or complex values. ## @end itemize ## ## @code{harmmean(@var{x}, "all")} returns the harmonic mean of all the elements ## in @var{x}. If @var{x} contains any 0, then the returned value is 0. ## ## @code{harmmean(@var{x}, @var{dim})} returns the harmonic mean along the ## operating dimension @var{dim} of @var{x}. Calculating the harmonic mean of ## any subarray containing any 0 will return 0. ## ## @code{harmmean(@var{x}, @var{vecdim})} returns the harmonic mean over the ## dimensions specified in the vector @var{vecdim}. For example, if @var{x} is ## a 2-by-3-by-4 array, then @code{harmmean(@var{x}, [1 2])} returns a ## 1-by-1-by-4 array. Each element of the output array is the harmonic mean of ## the elements on the corresponding page of @var{x}. If @var{vecdim} indexes ## all dimensions of @var{x}, then it is equivalent to @code{harmmean (@var{x}, ## "all")}. Any dimension in @var{vecdim} greater than @code{ndims (@var{x})} ## is ignored. ## ## @code{harmmean(@dots{}, @var{nanflag})} specifies whether to exclude NaN ## values from the calculation, using any of the input argument combinations in ## previous syntaxes. By default, harmmean includes NaN values in the ## calculation (@var{nanflag} has the value "includenan"). To exclude NaN ## values, set the value of @var{nanflag} to "omitnan". ## ## @seealso{geomean, mean} ## @end deftypefn function m = harmmean (x, varargin) if (nargin < 1 || nargin > 3) print_usage (); endif if (! isnumeric (x) || ! isreal (x) || ! all (x(! isnan (x))(:) >= 0)) error ("harmmean: X must contain real nonnegative values."); endif ## Set initial conditions all_flag = false; omitnan = false; nvarg = numel (varargin); varg_chars = cellfun ('ischar', varargin); szx = size (x); ndx = ndims (x); if (nvarg > 1 && ! varg_chars(2:end)) ## Only first varargin can be numeric print_usage (); endif ## Process any other char arguments. if (any (varg_chars)) for i = varargin(varg_chars) switch (lower (i{:})) case 'all' all_flag = true; case 'omitnan' omitnan = true; case 'includenan' omitnan = false; otherwise print_usage (); endswitch endfor varargin(varg_chars) = []; nvarg = numel (varargin); endif ## Single numeric input argument, no dimensions given. if (nvarg == 0) if (all_flag) x = x(:); if (omitnan) x = x(! isnan (x)); endif m = length (x) ./ sum (1 ./ x); else if (ndx == 2 && isempty (x) && szx == [0,0]) m = NaN; else ## Find the first non-singleton dimension. (dim = find (szx != 1, 1)) || (dim = 1); n = szx(dim); is_nan = 0; if (omitnan) idx = isnan (x); n = sum (! idx, dim); is_nan = sum (idx, dim); x(idx) = 1; # remove NaNs by subtracting is_nan below endif m = n ./ (sum (1 ./ x, dim) - is_nan); endif endif else if (all_flag) error ("harmmean: dimension and 'all' flag are mutually exclusive."); endif ## Two numeric input arguments, dimensions given. Note scalar is vector! vecdim = varargin{1}; if (isempty (vecdim) || ! (isvector (vecdim) && all (vecdim > 0)) ... || any (rem (vecdim, 1))) error ("harmmean: DIM must be a positive integer scalar or vector."); endif if (isscalar (vecdim)) if (vecdim > ndx) m = x; else n = szx(vecdim); is_nan = 0; if (omitnan) nanx = isnan (x); n = sum (! nanx, vecdim); is_nan = sum (nanx, vecdim); x(nanx) = 1; # remove NaNs by subtracting is_nan below endif m = n ./ (sum (1 ./ x, vecdim) - is_nan); endif else vecdim = sort (vecdim); if (! all (diff (vecdim))) error (strcat ("harmmean: VECDIM must contain non-repeating", ... " positive integers.")); endif ## Ignore exceeding dimensions in VECDIM vecdim(find (vecdim > ndims (x))) = []; if (isempty (vecdim)) m = x; else ## Move vecdims to dim 1. ## Calculate permutation vector remdims = 1 : ndx; # All dimensions remdims(vecdim) = []; # Delete dimensions specified by vecdim nremd = numel (remdims); ## If all dimensions are given, it is similar to all flag if (nremd == 0) x = x(:); if (omitnan) x = x(! isnan (x)); endif m = length (x) ./ sum (1 ./ x); else ## Permute to bring vecdims to front perm = [vecdim, remdims]; x = permute (x, perm); ## Reshape to squash all vecdims in dim1 num_dim = prod (szx(vecdim)); szx(vecdim) = []; szx = [ones(1, length(vecdim)), szx]; szx(1) = num_dim; x = reshape (x, szx); ## Calculate mean on dim1 if (omitnan) nanx = isnan (x); n = sum (! nanx, 1); is_nan = sum (nanx, 1); x(nanx) = 1; # remove NaNs by subtracting is_nan below else n = szx(1); is_nan = 0; endif m = n ./ (sum (1 ./ x, 1) - is_nan); ## Inverse permute back to correct dimensions m = ipermute (m, perm); endif endif endif endif endfunction ## Test single input and optional arguments "all", DIM, "omitnan") %!test %! x = [0:10]; %! y = [x;x+5;x+10]; %! assert_equal (harmmean (x), 0); %! m = [0 8.907635160795225 14.30854471766802]; %! assert_equal (harmmean (y, 2), m', 4e-14); %! assert_equal (harmmean (y, 'all'), 0); %! y(2,4) = NaN; %! m(2) = 9.009855936313949; %! assert_equal (harmmean (y, 2), [0 NaN m(3)]', 4e-14); %! assert_equal (harmmean (y', 'omitnan'), m, 4e-14); %! z = y + 20; %! assert_equal (harmmean (z, 'all'), NaN); %! assert_equal (harmmean (z, 'all', 'includenan'), NaN); %! assert_equal (harmmean (z, 'all', 'omitnan'), 29.1108719858295, 4e-14); %! m = [24.59488458841874 NaN 34.71244385944397]; %! assert_equal (harmmean (z'), m, 4e-14); %! assert_equal (harmmean (z', 'includenan'), m, 4e-14); %! m(2) = 29.84104075528277; %! assert_equal (harmmean (z', 'omitnan'), m, 4e-14); %! assert_equal (harmmean (z, 2, 'omitnan'), m', 4e-14); ## Test dimension indexing with vecdim in n-dimensional arrays %!test %! x = repmat ([1:20;6:25], [5 2 6 3]); %! assert_equal (size (harmmean (x, [3 2])), [10 1 1 3]); %! assert_equal (size (harmmean (x, [1 2])), [1 1 6 3]); %! assert_equal (size (harmmean (x, [1 2 4])), [1 1 6]); %! assert_equal (size (harmmean (x, [1 4 3])), [1 40]); %! assert_equal (size (harmmean (x, [1 2 3 4])), [1 1]); ## Test results with vecdim in n-dimensional arrays and "omitnan" %!test %! x = repmat ([1:20;6:25], [5 2 6 3]); %! m = repmat ([5.559045930488016;13.04950789021461], [5 1 1 3]); %! assert_equal (harmmean (x, [3 2]), m, 4e-14); %! x(2,5,6,3) = NaN; %! m(2,3) = NaN; %! assert_equal (harmmean (x, [3 2]), m, 4e-14); %! m(2,3) = 13.06617961315406; %! assert_equal (harmmean (x, [3 2], 'omitnan'), m, 4e-14); ## Test results for pure Inf arrays and omitnan interactions %!test %! assert_equal (harmmean ([Inf, Inf]), Inf); %! assert_equal (harmmean ([Inf, Inf], 'all'), Inf); %! assert_equal (harmmean ([Inf, Inf], 2), Inf); %! assert_equal (harmmean ([NaN, Inf], 'omitnan'), Inf); %! assert_equal (harmmean ([NaN, Inf], 'includenan'), NaN); %! assert_equal (harmmean ([0, Inf]), 0); ## Test NaN propagation in the presence of zeros. %!test %! assert_equal (harmmean ([0, NaN]), NaN); %! assert_equal (harmmean ([0, NaN], 'all'), NaN); %! assert_equal (harmmean ([0, NaN], [1, 2]), NaN); %! assert_equal (harmmean ([0, NaN], 'omitnan'), 0); %! assert_equal (harmmean ([0, NaN], 'all', 'omitnan'), 0); ## Test default handling of empty arrays. %!test %! a = harmmean ([]); %! assert_equal (isnan (a), true); %! assert_equal (size (a), [1, 1]); %!assert_equal (harmmean (ones (2, 0, 3, 2)), ones (1, 0, 3, 2)) %!assert_equal (harmmean (ones (2, 0, 3, 2), [1, 2]), NaN (1, 1, 3, 2)) %!assert_equal (harmmean (ones (2, 0, 3, 2), 'all'), NaN) %!assert_equal (harmmean (ones (2, 0, 3, 2), 1), ones (1, 0, 3, 2)) %!assert_equal (harmmean (ones (2, 0, 3, 2), 2), NaN (2, 1, 3, 2)) %!assert_equal (harmmean (ones (2, 0, 3, 2), 3), ones (2, 0, 1, 2)) %!assert_equal (harmmean (ones (2, 0, 3, 2), 4), ones (2, 0, 3)) %!assert_equal (harmmean ([], 1), ones (1, 0)) %!assert_equal (harmmean ([], 2), ones (0, 1)) %!assert_equal (harmmean ([], 3), []) ## Test errors %!error harmmean ('char') %!error harmmean ([1 -1 3]) %!error ... %! harmmean (repmat ([1:20;6:25], [5 2 6 3 5]), -1) %!error ... %! harmmean (repmat ([1:20;6:25], [5 2 6 3 5]), 0) %!error ... %! harmmean (repmat ([1:20;6:25], [5 2 6 3 5]), [1 1]) %!error ... %! harmmean ([1, 2; 3, 4], 1, 'all') %!error ... %! harmmean ([1, 2; 3, 4], [1, 2], 'all') statistics-release-1.9.2/inst/Descriptive_Statistics/jackknife.m000066400000000000000000000125511524624707500251260ustar00rootroot00000000000000## Copyright (C) 2011 Alexander Klein ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{jackstat} =} jackknife (@var{E}, @var{x}) ## @deftypefnx {statistics} {@var{jackstat} =} jackknife (@var{E}, @var{x}, @dots{}) ## ## Compute jackknife estimates of a parameter taking one or more given samples ## as parameters. ## ## In particular, @var{E} is the estimator to be jackknifed as a function name, ## handle, or inline function, and @var{x} is the sample for which the estimate ## is to be taken. The @var{i}-th entry of @var{jackstat} will contain the ## value of the estimator on the sample @var{x} with its @var{i}-th row omitted. ## ## @example ## @group ## jackstat (@var{i}) = @var{E}(@var{x}(1 : @var{i} - 1, ## @var{i} + 1 : length(@var{x}))) ## @end group ## @end example ## ## Depending on the number of samples to be used, the estimator must have the ## appropriate form: ## @itemize ## @item ## If only one sample is used, then the estimator need not be concerned with ## cell arrays, for example jackknifing the standard deviation of a sample can ## be performed with @code{@var{jackstat} = jackknife (@@std, rand (100, 1))}. ## @item ## If, however, more than one sample is to be used, the samples must all be of ## equal size, and the estimator must address them as elements of a cell-array, ## in which they are aggregated in their order of appearance: ## @end itemize ## ## @example ## @group ## @var{jackstat} = jackknife (@@(x) std(x@{1@})/var(x@{2@}), ## rand (100, 1), randn (100, 1)) ## @end group ## @end example ## ## If all goes well, a theoretical value @var{P} for the parameter is already ## known, @var{n} is the sample size, ## ## @code{@var{t} = @var{n} * @var{E}(@var{x}) - (@var{n} - 1) * ## mean(@var{jackstat})} ## ## and ## ## @code{@var{v} = sumsq(@var{n} * @var{E}(@var{x}) - (@var{n} - 1) * ## @var{jackstat} - @var{t}) / (@var{n} * (@var{n} - 1))} ## ## then ## ## @code{(@var{t}-@var{P})/sqrt(@var{v})} should follow a t-distribution with ## @var{n}-1 degrees of freedom. ## ## Jackknifing is a well known method to reduce bias. ## Further details can be found in: ## @subheading References ## ## @enumerate ## @item ## Rupert G. Miller. The jackknife - a review. Biometrika (1974), 61(1):1-15. ## doi:10.1093/biomet/61.1.1 ## @item ## Rupert G. Miller. Jackknifing Variances. Ann. Math. Statist. (1968), ## Volume 39, Number 2, 567-582. doi:10.1214/aoms/1177698418 ## @end enumerate ## @end deftypefn function jackstat = jackknife (anEstimator, varargin) ## Convert function name to handle if necessary, or throw an error. if (! strcmp (typeinfo (anEstimator), 'function handle')) if (isascii (anEstimator)) anEstimator = str2func (anEstimator); else error (strcat ("jackknife: estimators must be passed as function", ... " names or handles.")); endif endif ## Simple jackknifing can be done with a single vector argument, and ## first and foremost with a function that does not care about cell-arrays. if (length (varargin) == 1 && isnumeric (varargin {1})) aSample = varargin{1}; g = length (aSample); jackstat = zeros (1, g); for k = 1:g jackstat(k) = anEstimator(aSample([1:k - 1,k + 1:g])); endfor ## More complicated input requires more work, however. else g = cellfun (@(x) length (x), varargin); if (any (g - g(1))) error ("jackknife: all passed data must be of equal length."); endif g = g(1); jackstat = zeros (1, g); for k = 1:g jackstat(k) = anEstimator(cellfun (@(x) x( [ 1 : k - 1, k + 1 : g ]), ... varargin, 'UniformOutput', false)); endfor endif endfunction %!demo %! rng (42); %! for k = 1:1000 %! x = rand (10, 1); %! s(k) = std (x); %! jackstat = jackknife (@std, x); %! j(k) = 10 * std (x) - 9 * mean (jackstat); %! endfor %! figure (); %! hist ([s', j'], 0:sqrt (1/12)/10:2*sqrt (1/12)) %!demo %! rng (42); %! for k = 1:1000 %! x = randn (1, 50); %! y = rand (1, 50); %! jackstat = jackknife (@(x) std (x{1})/std (x{2}), y, x); %! j(k) = 50 * std (y) / std (x) - 49 * mean (jackstat); %! v(k) = sumsq ((50 * std (y) / std (x) - 49 * jackstat) - j(k)) / (50 * 49); %! endfor %! t = (j - sqrt (1 / 12)) ./ sqrt (v); %! figure (); %! plot (sort (tcdf (t, 49)), ... %! '-;Almost linear mapping indicates good fit with t-distribution.;') ## Test output %!test %! ##Example from Quenouille, Table 1 %! d=[0.18 4.00 1.04 0.85 2.14 1.01 3.01 2.33 1.57 2.19]; %! jackstat = jackknife ( @(x) 1/mean (x), d ); %! assert_equal ( 10 / mean (d) - 9 * mean (jackstat), 0.5240, 1e-5 ); statistics-release-1.9.2/inst/Descriptive_Statistics/ksdensity.m000066400000000000000000000503331524624707500252160ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{f} =} ksdensity (@var{x}) ## @deftypefnx {statistics} {@var{f} =} ksdensity (@var{x}, @var{pts}) ## @deftypefnx {statistics} {[@var{f}, @var{xi}] =} ksdensity (@dots{}) ## @deftypefnx {statistics} {[@var{f}, @var{xi}, @var{bw}] =} ksdensity (@dots{}) ## @deftypefnx {statistics} {[@dots{}] =} ksdensity (@dots{}, @var{Name}, @var{Value}) ## ## Kernel smoothing density estimate. ## ## @code{@var{f} = ksdensity (@var{x})} computes a probability density estimate ## of the sample in the vector @var{x}, evaluated at 100 equally spaced points ## @var{xi} that span the range of the data. @code{[@var{f}, @var{xi}] = ## ksdensity (@var{x})} also returns those points. Both are row vectors, ## whichever way @var{x} itself lies. When called without output arguments, ## the estimate is plotted instead. ## ## @code{@var{f} = ksdensity (@var{x}, @var{pts})} evaluates the estimate at the ## values in @var{pts} instead; @var{f} is then the same size as @var{pts}. For ## @qcode{'Function'} equal to @qcode{'icdf'} the entries of @var{pts} are ## probabilities in @math{[0, 1]}. ## ## @code{[@var{f}, @var{xi}, @var{bw}] = ksdensity (@dots{})} additionally ## returns the bandwidth @var{bw} of the smoothing kernel. ## ## The following @qcode{Name-Value} pairs are supported: ## ## @multitable @columnfractions 0.18 0.82 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Kernel'} @tab The smoothing kernel: @qcode{'normal'} (default), ## @qcode{'box'}, @qcode{'triangle'}, @qcode{'epanechnikov'}, or a function ## handle @code{@@(z)} evaluating a kernel density at the standardized distance ## @var{z}. ## ## @item @qcode{'Bandwidth'} @tab The kernel bandwidth, a positive scalar. The ## default is the value that is optimal for estimating a normal density, ## @math{@var{bw} = @var{sigma} * (4 / (3 * @var{n})) ^ (1 / 5)}, with ## @var{sigma} a robust estimate of the standard deviation of @var{x}. ## ## @item @qcode{'Function'} @tab The function to estimate: @qcode{'pdf'} ## (default), @qcode{'cdf'}, @qcode{'icdf'}, @qcode{'survivor'}, or ## @qcode{'cumhazard'}. ## ## @item @qcode{'Weights'} @tab A vector of non-negative weights, one for each ## element of @var{x}. The default weights are all equal. ## ## @item @qcode{'NumPoints'} @tab The number of equally spaced points @var{xi} ## at which to evaluate the estimate when @var{pts} is not given. The default ## is @math{100}. ## @end multitable ## ## @seealso{hist, histc, ecdf} ## @end deftypefn function [f, xi, bw] = ksdensity (x, varargin) if (nargin < 1) print_usage (); endif if (! (isnumeric (x) && isreal (x) && isvector (x))) error ("ksdensity: X must be a vector of real values."); endif x = x(! isnan (x)); x = x(:)'; n = numel (x); if (n < 2) error ("ksdensity: X must contain at least two non-missing values."); endif ## Optional positional PTS argument (a numeric vector before any Name-Value). pts = []; ptssz = []; if (numel (varargin) >= 1 && ! ischar (varargin{1})) pts = varargin{1}; varargin(1) = []; if (! (isnumeric (pts) && isreal (pts) && isvector (pts))) error ("ksdensity: PTS must be a vector of real values."); endif ptssz = size (pts); pts = pts(:); endif ## Defaults and Name-Value parsing. kernel = 'normal'; bw = []; func = 'pdf'; weights = []; npoints = 100; support = 'unbounded'; bc = 'log'; if (mod (numel (varargin), 2) != 0) error ("ksdensity: optional arguments must be Name-Value pairs."); endif for k = 1:2:numel (varargin) if (! ischar (varargin{k})) error ("ksdensity: parameter names must be character vectors."); endif switch (lower (varargin{k})) case 'kernel' kernel = varargin{k+1}; case 'bandwidth' bw = varargin{k+1}; case 'function' func = lower (varargin{k+1}); case 'weights' weights = varargin{k+1}; case 'numpoints' npoints = varargin{k+1}; case 'support' support = varargin{k+1}; case 'boundarycorrection' bc = lower (varargin{k+1}); case 'censoring' error ("ksdensity: 'Censoring' is not yet supported."); otherwise error ("ksdensity: unknown parameter name '%s'.", varargin{k}); endswitch endfor ## Validate the kernel. if (ischar (kernel)) kernel = lower (kernel); if (! any (strcmp (kernel, {'normal', 'box', 'triangle', 'epanechnikov'}))) error ("ksdensity: unrecognised 'Kernel' value."); endif elseif (! is_function_handle (kernel)) error ("ksdensity: 'Kernel' must be a name or a function handle."); endif if (! any (strcmp (func, {'pdf', 'cdf', 'icdf', 'survivor', 'cumhazard'}))) error ("ksdensity: unrecognised 'Function' value."); endif ## Support and boundary-correction validation -> lower/upper bounds L, U. if (ischar (support)) switch (lower (support)) case 'unbounded' L = -Inf; U = Inf; case 'positive' L = 0; U = Inf; otherwise error ("ksdensity: unrecognised 'Support' value."); endswitch elseif (isnumeric (support) && isreal (support) && numel (support) == 2 && support(1) < support(2)) L = support(1); U = support(2); else error ("ksdensity: 'Support' must be 'unbounded', 'positive', or [L U]."); endif unbounded = (L == -Inf && U == Inf); if (! any (strcmp (bc, {'log', 'reflection'}))) error ("ksdensity: unrecognised 'BoundaryCorrection' value."); endif if (! unbounded && (any (x <= L) || any (x >= U))) error ("ksdensity: X must lie strictly inside the specified 'Support'."); endif ## Weights (normalised to sum to one). if (isempty (weights)) w = ones (1, n) / n; else if (! (isnumeric (weights) && isreal (weights) && isvector (weights) && numel (weights) == n && all (weights >= 0) && any (weights > 0))) error ("ksdensity: 'Weights' must be a non-negative vector with one element per X."); endif w = weights(:)' / sum (weights); endif ## Working space. With the default 'log' boundary correction a bounded ## support is mapped to the whole real line by a log/logit transform; the ## bandwidth and default grid are then formed in that space. The unbounded ## case and the 'reflection' correction work in the native space. dolog = (! unbounded && strcmp (bc, 'log')); if (dolog) xw = ksdensity_fwd_ (x, L, U); else xw = x; endif ## Bandwidth: robust normal-reference rule unless supplied (working space). ## Where the data is discrete enough that more than half of it sits on the ## median, the median absolute deviation is exactly zero and the rule has no ## scale to work from. MATLAB falls back to the range there, not to the ## standard deviation: the standard deviation of such data is small, and a ## bandwidth taken from it is narrow enough to leave a spike over each ## repeated value instead of a density. Measured against R2024a on setosa ## petal width and three constructed vectors. A constant vector has no ## range either, and takes a bandwidth of one. if (isempty (bw)) sigma = median (abs (xw - median (xw))) / 0.6745; if (sigma <= 0) sigma = max (xw) - min (xw); endif if (sigma <= 0) bw = 1; else bw = sigma * (4 / (3 * n)) ^ (1 / 5); endif elseif (! (isnumeric (bw) && isscalar (bw) && isreal (bw) && bw > 0)) error ("ksdensity: 'Bandwidth' must be a positive scalar."); endif ## Evaluation points. For 'icdf' the points are probabilities. A grid we ## generate ourselves is a row, as MATLAB's is whatever the data's shape. if (strcmp (func, 'icdf')) if (isempty (pts)) xi = ((1:npoints) - 0.5) / npoints; else xi = pts; endif f = ksdensity_icdf_ (xi(:), x, w, bw, kernel, L, U, bc); else if (isempty (pts)) if (dolog) gw = linspace (min (xw) - 3 * bw, max (xw) + 3 * bw, npoints); xi = ksdensity_inv_ (gw, L, U); else lo = min (x) - 3 * bw; hi = max (x) + 3 * bw; if (isfinite (L)) lo = max (lo, L); endif if (isfinite (U)) hi = min (hi, U); endif xi = linspace (lo, hi, npoints); endif else xi = pts; endif switch (func) case 'pdf' f = ksdensity_eval_ (xi(:), x, w, bw, kernel, L, U, bc, 'pdf'); case 'cdf' f = ksdensity_eval_ (xi(:), x, w, bw, kernel, L, U, bc, 'cdf'); case 'survivor' f = 1 - ksdensity_eval_ (xi(:), x, w, bw, kernel, L, U, bc, 'cdf'); case 'cumhazard' Fc = ksdensity_eval_ (xi(:), x, w, bw, kernel, L, U, bc, 'cdf'); f = -log (1 - Fc); endswitch endif ## F takes XI's orientation, whether XI is the grid generated above or the ## points the caller supplied. if (! isempty (pts)) xi = reshape (xi, ptssz); endif f = reshape (f, size (xi)); ## With no output requested, plot the estimate (as MATLAB does) and return ## nothing. if (nargout == 0) plot (xi, f); clear f xi bw endif endfunction ## Kernel pdf or cdf estimate at native query points Q, honouring the support ## bounds L, U and the boundary-correction method BC. WANT is 'pdf' or 'cdf'. function v = ksdensity_eval_ (q, x, w, h, kernel, L, U, bc, want) q = q(:); ispdf = strcmp (want, 'pdf'); if (L == -Inf && U == Inf) ## Unbounded: plain kernel sum. z = (q - x) / h; if (ispdf) v = (kernelpdf (z, kernel) * w(:)) / h; else v = kernelcdf (z, kernel) * w(:); endif elseif (strcmp (bc, 'reflection')) ## Reflect the sample across each finite bound (single reflection). a = x; aw = w; if (isfinite (L)) a = [a, 2 * L - x]; aw = [aw, w]; endif if (isfinite (U)) a = [a, 2 * U - x]; aw = [aw, w]; endif if (ispdf) v = (kernelpdf ((q - a) / h, kernel) * aw(:)) / h; v(q < L | q > U) = 0; else ## Integrate the reflected density from the lower edge of the support. v = (kernelcdf ((q - a) / h, kernel) ... - kernelcdf ((L - a) / h, kernel)) * aw(:); endif else ## Log/logit transform: estimate in the transformed space and map back. z = (ksdensity_fwd_ (q, L, U) - ksdensity_fwd_ (x, L, U)) / h; if (ispdf) g = (kernelpdf (z, kernel) * w(:)) / h; v = g .* ksdensity_logjac_ (q, L, U); elseif (isfinite (U) && ! isfinite (L)) ## upper bound only: t decreases v = 1 - kernelcdf (z, kernel) * w(:); else v = kernelcdf (z, kernel) * w(:); endif endif if (! ispdf) v = min (max (v, 0), 1); endif endfunction ## Forward transform mapping the support (L, U) onto the whole real line. function t = ksdensity_fwd_ (x, L, U) if (U == Inf) t = log (x - L); elseif (L == -Inf) t = log (U - x); else t = log ((x - L) ./ (U - x)); endif endfunction ## Inverse of ksdensity_fwd_. function x = ksdensity_inv_ (t, L, U) if (U == Inf) x = L + exp (t); elseif (L == -Inf) x = U - exp (t); else x = L + (U - L) ./ (1 + exp (-t)); endif endfunction ## |dt/dx| of the forward transform (the density change-of-variables factor). function j = ksdensity_logjac_ (x, L, U) if (U == Inf) j = 1 ./ (x - L); elseif (L == -Inf) j = 1 ./ (U - x); else j = (U - L) ./ ((x - L) .* (U - x)); endif endfunction ## Inverse cdf at probabilities P by monotone interpolation of the cdf over a ## fine grid spanning the support. function q = ksdensity_icdf_ (p, x, w, h, kernel, L, U, bc) if (! (L == -Inf && U == Inf) && strcmp (bc, 'log')) tx = ksdensity_fwd_ (x, L, U); grid = ksdensity_inv_ (linspace (min (tx) - 10 * h, ... max (tx) + 10 * h, 4000), L, U)'; else lo = min (x) - 10 * h; hi = max (x) + 10 * h; if (isfinite (L)) lo = max (lo, L); endif if (isfinite (U)) hi = min (hi, U); endif grid = linspace (lo, hi, 4000)'; endif F = ksdensity_eval_ (grid, x, w, h, kernel, L, U, bc, 'cdf'); ## Keep a strictly increasing (F, grid) relation for interp1. dF = diff (F); keep = [true; dF > 0]; q = interp1 (F(keep), grid(keep), p(:), 'linear', NA); q(p(:) <= min (F)) = grid(1); q(p(:) >= max (F)) = grid(end); endfunction %!demo %! ## Kernel density estimate of a small sample, with a histogram for reference %! x = [1 1.5 2 2 2.5 3 3.5 3.5 4 6]; %! [f, xi] = ksdensity (x); %! hist (x, 6, 6 / numel (x)); %! hold on; plot (xi, f, 'r-', 'LineWidth', 2); hold off; %!test # density integrates to ~1 over a wide grid (normal kernel) %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]; %! [f, xi] = ksdensity (x, "NumPoints", 4000); %! assert_equal (trapz (xi, f), 1, 5e-3); %!test # every named compact kernel integrates to ~1 %! x = randn (1, 200); %! for k = {"box", "triangle", "epanechnikov"} %! xi = linspace (-8, 8, 6000)'; %! f = ksdensity (x, xi, "Kernel", k{1}); %! assert_equal (trapz (xi, f), 1, 1e-2); %! endfor %!test # cdf is monotone from 0 to 1 and matches the analytic normal-kernel sum %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]; %! [F, xi] = ksdensity (x, "Function", "cdf", "NumPoints", 500); %! assert_equal (all (diff (F) >= -1e-12), true); %! assert_equal (F(1), 0, 5e-3); %! assert_equal (F(end), 1, 5e-3); %! [~, ~, bw] = ksdensity (x); %! Fdirect = mean (normcdf ((xi(:) - x) / bw), 2)'; %! assert_equal (F, Fdirect, 1e-12); ## Orientation, measured against R2024a 2026-08-17: the grid generated here is ## a row whatever the data's shape, while points supplied by the caller keep ## their own shape. %!test %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]; %! [f, xi] = ksdensity (x); %! assert_equal (size (f), [1, 100]); %! assert_equal (size (xi), [1, 100]); %!test %! ## a column of data still gives a row grid %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]'; %! [f, xi] = ksdensity (x, 'NumPoints', 500); %! assert_equal (size (f), [1, 500]); %! assert_equal (size (xi), [1, 500]); %!test %! ## supplied points keep their own orientation, and F follows them %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]; %! [f, xi] = ksdensity (x, [0 0.5 1 1.5 2]); %! assert_equal (size (f), [1, 5]); %! assert_equal (size (xi), [1, 5]); %! [f, xi] = ksdensity (x, [0; 0.5; 1; 1.5; 2]); %! assert_equal (size (f), [5, 1]); %! assert_equal (size (xi), [5, 1]); %!test # survivor and cumhazard are consistent with the cdf %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]; %! pts = [-1 0 1 2 3]'; %! F = ksdensity (x, pts, "Function", "cdf"); %! S = ksdensity (x, pts, "Function", "survivor"); %! H = ksdensity (x, pts, "Function", "cumhazard"); %! assert_equal (S, 1 - F, 1e-12); %! assert_equal (H, -log (1 - F), 1e-12); %!test # icdf inverts the cdf %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]; %! p = [0.1 0.25 0.5 0.75 0.9]'; %! q = ksdensity (x, p, "Function", "icdf"); %! Fq = ksdensity (x, q, "Function", "cdf"); %! assert_equal (Fq, p, 5e-3); %!test # evaluation at supplied points preserves shape %! x = randn (1, 50); %! pts = [-1 0 1]; %! f = ksdensity (x, pts); %! assert_equal (size (f), size (pts)); %!test # weights: a duplicated point equals a doubled weight %! x = [0 1 2 3]; %! pts = linspace (-2, 5, 40)'; %! f1 = ksdensity ([x, 3], pts, "Bandwidth", 0.5); %! f2 = ksdensity (x, pts, "Bandwidth", 0.5, "Weights", [1 1 1 2]); %! assert_equal (f1, f2, 1e-12); ## Where more than half the data sits on the median the median absolute ## deviation is exactly zero, and the rule falls back to the range rather than ## to the standard deviation, which is far too small on such data. %!test # MATLAB parity: the bandwidth when the robust scale vanishes %! load fisheriris %! pw = meas(strcmp (species, 'setosa'), 4); %! assert_equal (median (abs (pw - median (pw))), 0); %! [~, ~, bw] = ksdensity (pw); %! assert_equal (bw, 0.242194206816745, 1e-12); %! [~, ~, bw] = ksdensity ([1; 1; 1; 1; 1; 1; 1; 2; 3; 0]); %! assert_equal (bw, 2.004975185874807, 1e-12); %! [~, ~, bw] = ksdensity ([5; 5; 5; 5; 5; 5; 5; 5; 5; 9]); %! assert_equal (bw, 2.673300247833076, 1e-12); %!test # MATLAB parity: data with no spread at all takes a bandwidth of one %! [~, ~, bw] = ksdensity (repmat (2, 10, 1)); %! assert_equal (bw, 1); %!test # MATLAB parity: default bandwidth, grid size and range %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]; %! [~, xi, bw] = ksdensity (x); %! assert_equal (bw, 0.6396, 5e-4); %! assert_equal (numel (xi), 100); %! assert_equal ([xi(1), xi(end)], [-2.6187, 5.1187], 1e-3); %!test # MATLAB parity: pdf (normal and box kernels) and cdf at fixed points %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]; %! pts = [-0.5 0 0.5 1 1.5 2 2.5 3]; %! assert_equal (ksdensity (x, pts), ... %! [0.1214 0.2141 0.3051 0.3394 0.3061 0.2375 0.1697 0.1166], 2e-3); %! assert_equal (ksdensity (x, pts, "Kernel", "box"), ... %! [0.1505 0.2407 0.2708 0.3611 0.3009 0.2708 0.1805 0.1204], 2e-3); %! assert_equal (ksdensity (x, pts, "Function", "cdf"), ... %! [0.0710 0.1538 0.2850 0.4492 0.6128 0.7494 0.8506 0.9217], 2e-3); %!test # MATLAB parity: positive support (log) bandwidth, grid and pdf %! y = [0.2 0.5 0.7 1.1 1.4 2 2.6 3.3 4.1 5.5]; %! ypts = [0.1 0.3 0.6 1 2 3 4 5]; %! [~, xy, by] = ksdensity (y, "Support", "positive"); %! assert_equal (by, 0.7682, 5e-4); %! assert_equal ([xy(1), xy(end)], [0.0200, 55.1111], 1e-3); %! assert_equal (ksdensity (y, ypts, "Support", "positive"), ... %! [0.4300 0.4620 0.3620 0.2740 0.1570 0.0990 0.0660 0.0450], 2e-3); %!test # MATLAB parity: positive support with reflection boundary correction %! y = [0.2 0.5 0.7 1.1 1.4 2 2.6 3.3 4.1 5.5]; %! ypts = [0.1 0.3 0.6 1 2 3 4 5]; %! f = ksdensity (y, ypts, "Support", "positive", ... %! "BoundaryCorrection", "reflection"); %! assert_equal (f, [0.2920 0.2900 0.2810 0.2620 0.2010 0.1470 0.1070 0.0740], 2e-3); %!test # bounded support integrates to ~1 and vanishes outside the bounds %! y = [0.2 0.5 0.7 1.1 1.4 2 2.6 3.3]; %! [f, xi] = ksdensity (y, "Support", [0 4], "NumPoints", 4000); %! assert_equal (trapz (xi, f), 1, 5e-3); %! assert_equal (all (xi >= 0 & xi <= 4), true); %!test # reflection density is confined to the support %! y = [0.2 0.5 0.7 1.1 1.4 2 2.6 3.3]; %! f = ksdensity (y, [-1 -0.1 5], "Support", "positive", ... %! "BoundaryCorrection", "reflection"); %! assert_equal (f(1:2), [0 0]); ## Test input validation %!error ksdensity () %!error ksdensity (ones (3, 3)) %!error ... %! ksdensity (5) %!error ... %! ksdensity (1:10, "Kernel", "cosine") %!error ... %! ksdensity (1:10, "Function", "hazard") %!error ... %! ksdensity (1:10, "Bandwidth", -1) %!error ... %! ksdensity (1:10, "Weights", [1 2 3]) %!error ... %! ksdensity (1:10, "Support", "half") %!error ... %! ksdensity (1:10, "Support", [2 1]) %!error ... %! ksdensity ([-1 1 2 3], "Support", "positive") %!error ... %! ksdensity (1:10, "Support", "positive", "BoundaryCorrection", "linear") %!error ... %! ksdensity (1:10, "Censoring", ones (1, 10)) statistics-release-1.9.2/inst/Descriptive_Statistics/mvksdensity.m000066400000000000000000000266321524624707500255660ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{f} =} mvksdensity (@var{x}, @var{pts}, @var{Name}, @var{Value}) ## ## Multivariate kernel smoothing density estimate. ## ## @code{@var{f} = mvksdensity (@var{x}, @var{pts})} computes a probability ## density estimate of the sample in the @math{N*D} matrix @var{x}, evaluated at ## the points in the @math{M*D} matrix @var{pts}. Each row of @var{x} is a ## single @math{D}-dimensional observation, and each row of @var{pts} is a point ## at which to evaluate the estimate. The result @var{f} is an @math{M*1} ## vector, with one density value per row of @var{pts}. ## ## The density estimate uses a product kernel: the multivariate kernel is the ## product of the univariate kernels applied to each dimension, each with its ## own bandwidth. ## ## The following @qcode{Name-Value} pairs are supported: ## ## @multitable @columnfractions 0.18 0.82 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Bandwidth'} @tab The kernel bandwidth, either a positive scalar ## applied to every dimension or a @math{1*D} vector of positive values, one per ## dimension. The default is a diagonal normal-reference (Silverman) rule ## computed from @var{x}. ## ## @item @qcode{'Kernel'} @tab The smoothing kernel applied in each dimension: ## @qcode{'normal'} (default), @qcode{'box'}, @qcode{'triangle'}, or ## @qcode{'epanechnikov'}. ## ## @item @qcode{'Function'} @tab The function to estimate: @qcode{'pdf'} ## (default) or @qcode{'cdf'}. ## ## @item @qcode{'Weights'} @tab A vector of non-negative weights, one for each ## row of @var{x}. The default weights are all equal. ## @end multitable ## ## @seealso{ksdensity} ## @end deftypefn function f = mvksdensity (x, pts, varargin) if (nargin < 2) print_usage (); endif if (! (isnumeric (x) && isreal (x) && ismatrix (x) && ndims (x) == 2)) error ("mvksdensity: X must be a matrix of real values."); endif n = rows (x); d = columns (x); if (n < 2) error ("mvksdensity: X must contain at least two observations."); endif if (! (isnumeric (pts) && isreal (pts) && ismatrix (pts) && ndims (pts) == 2)) error ("mvksdensity: PTS must be a matrix of real values."); endif if (columns (pts) != d) error ("mvksdensity: PTS must have the same number of columns as X."); endif ## Defaults and Name-Value parsing. kernel = 'normal'; bw = []; func = 'pdf'; weights = []; if (mod (numel (varargin), 2) != 0) error ("mvksdensity: optional arguments must be Name-Value pairs."); endif for k = 1:2:numel (varargin) if (! ischar (varargin{k})) error ("mvksdensity: parameter names must be character vectors."); endif switch (lower (varargin{k})) case 'bandwidth' bw = varargin{k+1}; case 'kernel' kernel = varargin{k+1}; case 'function' func = lower (varargin{k+1}); case 'weights' weights = varargin{k+1}; otherwise error ("mvksdensity: unknown parameter name '%s'.", varargin{k}); endswitch endfor ## Validate the kernel. if (! (ischar (kernel) && any (strcmpi (kernel, ... {'normal', 'box', 'triangle', 'epanechnikov'})))) error ("mvksdensity: unrecognised 'Kernel' value."); endif kernel = lower (kernel); if (! any (strcmp (func, {'pdf', 'cdf'}))) error ("mvksdensity: unrecognised 'Function' value."); endif ## Bandwidth: normal-reference rule per dimension unless supplied. if (isempty (bw)) bw = default_bw (x, n, d); elseif (isnumeric (bw) && isreal (bw) && isscalar (bw) && bw > 0) bw = repmat (bw, 1, d); elseif (! (isnumeric (bw) && isreal (bw) && isvector (bw) && numel (bw) == d && all (bw > 0))) error (strcat ("mvksdensity: 'Bandwidth' must be a positive scalar", ... " or a vector with one element per column of X.")); else bw = bw(:)'; endif ## Weights (normalised to sum to one). if (isempty (weights)) w = ones (n, 1) / n; elseif (isnumeric (weights) && isreal (weights) && isvector (weights) && numel (weights) == n && all (weights >= 0) && any (weights > 0)) w = weights(:) / sum (weights); else error (strcat ("mvksdensity: 'Weights' must be a non-negative vector", ... " with one element per observation in X.")); endif ## Evaluate the product-kernel estimate at each query point. m = rows (pts); f = zeros (m, 1); ispdf = strcmp (func, 'pdf'); for j = 1:m ## Standardized distance from every observation to this query point. z = (pts(j,:) - x) ./ bw; if (ispdf) ## Product of the per-dimension kernels, normalised once by prod (bw). k = prod_dim (kernelpdf (z, kernel), d) ./ prod (bw); f(j) = sum (w .* k); else c = prod_dim (kernelcdf (z, kernel), d); f(j) = sum (w .* c); endif endfor endfunction ## Diagonal normal-reference (Silverman) bandwidth, one element per dimension. ## The per-column spread is a robust estimate of the standard deviation, the ## same rule ksdensity uses, and a column whose median absolute deviation is ## zero falls back to its RANGE rather than to its standard deviation. A ## column with no range either takes the whole bandwidth to ones, not just its ## own element: MATLAB abandons the rule for every dimension once one of them ## is degenerate, and the other columns' bandwidths change with it. Measured ## against R2024a, which agrees to 3.2e-14 on ordinary data and differed by 86% ## and 99% on the two degenerate cases before this. function bw = default_bw (x, n, d) sigma = median (abs (x - median (x, 1)), 1) / 0.6745; bad = ! (sigma > 0); if (any (bad)) r = max (x, [], 1) - min (x, [], 1); sigma(bad) = r(bad); endif if (any (! (sigma > 0))) bw = ones (1, d); return; endif bw = sigma * (4 / ((d + 2) * n)) ^ (1 / (d + 4)); endfunction ## Row-wise product across the D columns of a per-dimension kernel matrix. function p = prod_dim (k, d) if (d == 1) p = k; else p = prod (k, 2); endif endfunction %!demo %! ## Bivariate kernel density estimate over a grid, drawn as a contour plot. %! rng (42); %! x = [randn(60, 2); randn(40, 2) + 3]; %! [gx, gy] = meshgrid (linspace (-4, 7, 60)); %! f = mvksdensity (x, [gx(:), gy(:)]); %! contourf (gx, gy, reshape (f, size (gx))); %! hold on; plot (x(:,1), x(:,2), 'k.'); hold off; %! title ('Bivariate kernel density estimate'); %!shared X, pts %! X = [1 1; 2 1; 1 2; 3 2; 2 3; 4 3; 3 4; 5 4]; %! pts = [2 2; 3 3; 1 1; 4 4]; %!test ## MATLAB parity: default (robust normal-reference) bandwidth %! assert_equal (mvksdensity (X, pts), ... %! [0.0569998; 0.0520004; 0.0448930; 0.0382812], 1e-6); %!test ## MATLAB parity: fixed vector and scalar bandwidth (normal kernel) %! assert_equal (mvksdensity (X, pts, "Bandwidth", [1 1]), ... %! [0.0589; 0.0535; 0.0474; 0.0395], 1e-3); %! assert_equal (mvksdensity (X, pts, "Bandwidth", 1), ... %! mvksdensity (X, pts, "Bandwidth", [1 1])); %!test ## MATLAB parity: box and epanechnikov product kernels %! assert_equal (mvksdensity (X, pts, "Bandwidth", [1 1], "Kernel", "box"), ... %! [0.0521; 0.0417; 0.0313; 0.0313], 1e-3); %! assert_equal (mvksdensity (X, pts, "Bandwidth", [1 1], ... %! "Kernel", "epanechnikov"), [0.0585; 0.0523; 0.0411; 0.0383], 1e-3); %!test ## MATLAB parity: cumulative distribution and weighted estimate %! assert_equal (mvksdensity (X, pts, "Bandwidth", [1 1], "Function", "cdf"), ... %! [0.2144; 0.4504; 0.0520; 0.6893], 1e-3); %! assert_equal (mvksdensity (X, pts, "Bandwidth", [1 1], ... %! "Weights", [2 1 1 1 1 1 1 1]), [0.0588; 0.0479; 0.0598; 0.0351], 1e-3); %!test ## the density integrates to ~1 over a wide grid %! [gx, gy] = meshgrid (linspace (-6, 11, 220)); %! f = mvksdensity (X, [gx(:), gy(:)]); %! dx = gx(1,2) - gx(1,1); %! assert_equal (sum (f) * dx ^ 2, 1, 1e-2); ## MATLAB parity: the default bandwidth where a column's median absolute ## deviation is zero. The fallback is that column's RANGE, not its standard ## deviation, which is the same rule ksdensity follows. Before this the two ## differed by 86%. %!test %! xz = [1, 2.3; 1, 3.1; 1, 4.8; 1, 5.5; 1, 6.1; 3, 7.9; 5, 8.2; 9, 9.4]; %! f = mvksdensity (xz, [3, 4; 5, 6]); %! assert_equal (f, [0.006520360257529; 0.006588943731769], 1e-12); ## MATLAB parity: a column with no range at all takes the WHOLE bandwidth to ## ones, not merely its own element, so the other columns' bandwidths move ## with it. Before this the two differed by 99%. %!test %! xc = [2, 2.3; 2, 3.1; 2, 4.8; 2, 5.5; 2, 6.1; 2, 7.9; 2, 8.2; 2, 9.4]; %! f = mvksdensity (xc, [3, 4; 5, 6]); %! assert_equal (f, [0.024910428037417; 0.000582734811254], 1e-12); %! assert_equal (f, mvksdensity (xc, [3, 4; 5, 6], 'Bandwidth', [1, 1]), 1e-12); ## An ordinary column is untouched by either fallback. %!test %! xh = [1.1, 2.3; 2.4, 3.1; 3.9, 4.8; 4.2, 5.5; 5.7, 6.1; 6.3, 7.9; ... %! 7.1, 8.2; 8.8, 9.4]; %! assert_equal (mvksdensity (xh, [3, 4; 5, 6]), ... %! [0.014366107896069; 0.016985761603336], 1e-12); ## Three or more predictors take a default bandwidth here. R2024a cannot: ## its default path yields a two-element bandwidth whatever the dimension and ## it raises "Bandwidth must be a scalar or a vector with 3 elements" on any ## three-column call, healthy data included. Documented deviation. %!test %! x3 = [1.1, 5.2, 2.3; 2.4, 6.1, 3.1; 3.9, 4.4, 4.8; 4.2, 7.7, 5.5; ... %! 5.7, 5.9, 6.1; 6.3, 8.3, 7.9; 7.1, 6.6, 8.2; 8.8, 9.1, 9.4]; %! f = mvksdensity (x3, [3, 6, 4; 5, 7, 6]); %! assert_equal (size (f), [2, 1]); %! assert_equal (all (f > 0), true); ## Test input validation %!error mvksdensity (ones (3, 2)) %!error ... %! mvksdensity (ones (2, 2, 2), [1 1]) %!error ... %! mvksdensity ([1 2], [1 1]) %!error ... %! mvksdensity ([1 1; 2 2], [1 1 1]) %!error ... %! mvksdensity ([1 1; 2 2], [1 1], "Bandwidth") %!error ... %! mvksdensity ([1 1; 2 2], [1 1], "Kernel", "cosine") %!error ... %! mvksdensity ([1 1; 2 2], [1 1], "Function", "icdf") %!error ... %! mvksdensity ([1 1; 2 2], [1 1], "Bandwidth", [1 2 3]) %!error ... %! mvksdensity ([1 1; 2 2], [1 1], "Bandwidth", -1) %!error ... %! mvksdensity ([1 1; 2 2], [1 1], "Weights", [1 2 3]) statistics-release-1.9.2/inst/Descriptive_Statistics/nancov.m000066400000000000000000000143611524624707500244660ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{c} =} nancov (@var{x}) ## @deftypefnx {statistics} {@var{c} =} nancov (@var{x}, @var{y}) ## @deftypefnx {statistics} {@var{c} =} nancov (@dots{}, @var{normalization}) ## @deftypefnx {statistics} {@var{c} =} nancov (@dots{}, @var{method}) ## ## Compute the covariance matrix while ignoring NaN values. ## ## @code{@var{c} = nancov (@var{x})} returns the covariance matrix of the ## columns of @var{x}, treating each row as an observation, after removing ## @qcode{NaN} values. If @var{x} is a vector, the scalar variance of its ## non-@qcode{NaN} elements is returned. ## ## @code{@var{c} = nancov (@var{x}, @var{y})}, where @var{x} and @var{y} are of ## equal length, is equivalent to @code{nancov ([@var{x}(:), @var{y}(:)])} and ## returns the 2-by-2 covariance matrix. ## ## @code{@var{c} = nancov (@dots{}, @var{normalization})} specifies the ## normalization. When @var{normalization} is 0 (default), the covariance is ## normalized by @math{N-1}, where @math{N} is the number of observations used. ## When it is 1, it is normalized by @math{N}. ## ## @code{@var{c} = nancov (@dots{}, @var{method})} selects how @qcode{NaN} ## values are handled. With @qcode{"complete"} (the default), any row of the ## data that contains a @qcode{NaN} value is removed before the covariance is ## computed. With @qcode{"pairwise"}, each element @code{@var{c}(i,j)} is ## computed using all rows in which both column @var{i} and column @var{j} are ## non-@qcode{NaN}; the resulting matrix may fail to be positive semidefinite. ## ## @seealso{cov, nanvar, nanstd, nanmean} ## @end deftypefn function c = nancov (varargin) if (nargin < 1) print_usage (); endif x = varargin{1}; if (! (isnumeric (x) || islogical (x)) || ! isreal (x)) error ("nancov: X must be a real numeric matrix or vector."); endif args = varargin(2:end); ## Separate a trailing method string from the numeric arguments method = 'complete'; strmask = cellfun (@ischar, args); if (any (strmask)) sopt = args(strmask); if (numel (sopt) > 1) error ("nancov: only one METHOD option may be specified."); endif method = lower (sopt{1}); if (! any (strcmp (method, {'complete', 'pairwise'}))) error ("nancov: METHOD must be 'complete' or 'pairwise'."); endif args = args(! strmask); endif ## Remaining numeric arguments: an optional Y and/or a normalization flag y = []; nrm = 0; if (numel (args) == 1) a = args{1}; if (isscalar (a) && (a == 0 || a == 1)) nrm = a; else y = a; endif elseif (numel (args) == 2) y = args{1}; nrm = args{2}; if (! (isscalar (nrm) && (nrm == 0 || nrm == 1))) error ("nancov: normalization flag must be 0 or 1."); endif elseif (numel (args) > 2) error ("nancov: too many input arguments."); endif ## Assemble the data matrix (observations in rows, variables in columns) if (! isempty (y)) if (! (isnumeric (y) || islogical (y)) || ! isreal (y)) error ("nancov: Y must be a real numeric matrix or vector."); endif if (numel (x) != numel (y)) error ("nancov: X and Y must have the same number of elements."); endif X = [x(:), y(:)]; elseif (isvector (x)) X = x(:); else X = x; endif p = columns (X); if (strcmp (method, 'complete')) good = all (! isnan (X), 2); Xc = X(good, :); n = rows (Xc); if (n == 0) c = NaN (p, p); return; endif Xd = Xc - mean (Xc, 1); if (nrm == 1 || n == 1) d = n; else d = n - 1; endif c = (Xd' * Xd) / d; else c = zeros (p, p); for i = 1:p for j = i:p rc = ! isnan (X(:,i)) & ! isnan (X(:,j)); xi = X(rc, i); xj = X(rc, j); n = numel (xi); if (n == 0) cij = NaN; else xi -= mean (xi); xj -= mean (xj); if (nrm == 1 || n == 1) d = n; else d = n - 1; endif cij = sum (xi .* xj) / d; endif c(i,j) = cij; c(j,i) = cij; endfor endfor endif endfunction %!demo %! ## Covariance matrix of a data set with missing values (complete-case). %! %! x = [1 2 3; 4 5 NaN; 7 NaN 9; 10 11 12; NaN 14 15] %! c = nancov (x) %!demo %! ## The same data set using pairwise deletion of missing values. %! %! x = [1 2 3; 4 5 NaN; 7 NaN 9; 10 11 12; NaN 14 15] %! c = nancov (x, 'pairwise') ## Test output %!assert_equal (nancov ([1 2 3; 4 5 NaN; 7 NaN 9; 10 11 12; NaN 14 15]), ... %! 40.5 * ones (3)) %!assert_equal (nancov ([1 2 3; 4 5 NaN; 7 NaN 9; 10 11 12; NaN 14 15], 1), ... %! 20.25 * ones (3)) %!assert_equal (nancov ([1 2 3; 4 5 NaN; 7 NaN 9; 10 11 12; NaN 14 15], ... %! 'pairwise'), [15, 21, 21; 21, 30, 39; 21, 39, 26.25]) %!assert_equal (nancov ([1 2 3; 4 5 NaN; 7 NaN 9; 10 11 12; NaN 14 15], 1, ... %! 'pairwise'), [11.25, 14, 14; 14, 22.5, 26; 14, 26, 19.6875]) %!assert_equal (nancov ([1 2 3 NaN 5]', [2 NaN 6 8 10]'), [4, 8; 8, 16]) %!assert_equal (nancov ([1 2 3 4 5]'), 2.5) %!assert_equal (nancov (5), 0) %!assert_equal (nancov (NaN (3, 2)), NaN (2, 2)) ## Test input validation %!error nancov () %!error nancov ({1}) %!error ... %! nancov ([1 2; 3 4], 'bogus') %!error ... %! nancov ([1 2 3], [1 2]) %!error nancov ([1 2], [3 4], 0, 1) statistics-release-1.9.2/inst/Descriptive_Statistics/nanmax.m000066400000000000000000000202541524624707500244620ustar00rootroot00000000000000## Copyright (C) 2001 Paul Kienzle ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{v} =} nanmax (@var{x}) ## @deftypefnx {statistics} {@var{v} =} nanmax (@var{x}, [], @var{dim}) ## @deftypefnx {statistics} {[@var{v}, @var{idx}] =} nanmax (@dots{}) ## @deftypefnx {statistics} {@var{v} =} nanmax (@var{x}, [], @qcode{'all'}) ## @deftypefnx {statistics} {@var{v} =} nanmax (@var{x}, [], @var{vecdim}) ## @deftypefnx {statistics} {@var{v} =} nanmax (@var{x}, @var{y}) ## ## Find the maximum while ignoring NaN values. ## ## @code{@var{v} = nanmax (@var{x})} returns the maximum of @var{x}, after ## removing @qcode{NaN} values. If @var{x} is a vector, a scalar maximum value ## is returned. If @var{x} is a matrix, a row vector of column maxima is ## returned. If @var{x} is a multidimensional array, the @code{nanmax} operates ## along the first nonsingleton dimension. If all values in a column are ## @qcode{NaN}, the maximum is returned as @qcode{NaN} rather than @qcode{[]}. ## ## @code{@var{v} = nanmax (@var{x}, [], @var{dim})} operates along the dimension ## @var{dim} of @var{x}. ## ## @code{[@var{v}, @var{idx}] = nanmax (@dots{})} also returns the row indices ## of the maximum values for each column in the vector @var{idx}. When @var{x} ## is a vector, then @var{idx} is a scalar value as @var{v}. ## ## @code{@var{v} = nanmax (@var{x}, [], @qcode{'all'})} returns the maximum of ## all elements of @var{x}, after removing @qcode{NaN} values. It is the ## equivalent of @code{nanmax (@var{x}(:))}. The optional flag @qcode{'all'} ## cannot be used together with @var{dim} or @var{vecdim} input arguments. ## ## @code{@var{v} = nanmax (@var{x}, [], @var{vecdim})} returns the maximum over ## the dimensions specified in the vector @var{vecdim}. Each element of ## @var{vecdim} represents a dimension of the input array @var{x} and the output ## @var{v} has length 1 in the specified operating dimensions. The lengths of ## the other dimensions are the same for @var{x} and @var{y}. For example, if ## @var{x} is a 2-by-3-by-4 array, then @code{nanmax (@var{x}, [1 2])} returns a ## 1-by-1-by-4 array. Each element of the output array is the maximum of the ## elements on the corresponding page of @var{x}. If @var{vecdim} indexes all ## dimensions of @var{x}, then it is equivalent to ## @code{nanmax (@var{x}, @qcode{'all'})}. Any dimension in @var{vecdim} ## greater than @code{ndims (@var{x})} is ignored. ## ## @seealso{max, nanmin, nansum} ## @end deftypefn function [v, idx] = nanmax (x, y, dim) if (nargin < 1 || nargin > 3) print_usage; elseif (nargin == 1 || (nargin == 2 && isempty (y))) nanvals = isnan (x); x(nanvals) = -Inf; [v, idx] = max (x); v(all (nanvals)) = NaN; elseif (nargin == 3 && strcmpi (dim, 'all') && isempty (y)) x = x(:); nanvals = isnan (x); x(nanvals) = -Inf; [v, idx] = max (x); v(all (nanvals)) = NaN; elseif (nargin == 3 && isempty (y)) if (isscalar (dim)) nanvals = isnan (x); x(nanvals) = -Inf; [v, idx] = max (x, [], dim); v(all (nanvals, dim)) = NaN; else vecdim = sort (dim); if (! all (diff (vecdim))) error ("nanmax: VECDIM must contain non-repeating positive integers."); endif ## Ignore dimensions in VECDIM larger than actual array vecdim(find (vecdim > ndims (x))) = []; if (isempty (vecdim)) v = x; if (nargout > 1) idx = reshape ([1:numel(x)], size (x)); endif else ## Calculate permutation vector szx = size (x); remdims = 1:ndims (x); # All dimensions remdims(vecdim) = []; # Delete dimensions specified by vecdim nremd = numel (remdims); ## If all dimensions are given, it is equivalent to 'all' flag if (nremd == 0) x = x(:); nanvals = isnan (x); x(nanvals) = -Inf; [v, idx] = max (x); v(all (nanvals)) = NaN; else ## Permute to push vecdims to back perm = [remdims, vecdim]; x = permute (x, perm); ## Reshape to squash all vecdims in final dimension sznew = [szx(remdims), prod(szx(vecdim))]; x = reshape (x, sznew); ## Calculate nanmax on final dimension dim = nremd + 1; nanvals = isnan (x); x(nanvals) = -Inf; [v, idx] = max (x, [], dim); v(all (nanvals, dim)) = NaN; ## Inverse permute back to correct dimensions v = ipermute (v, perm); idx = ipermute (idx, perm); endif endif endif else if (nargout > 1) error ("nanmax: a second output is not supported with this syntax."); endif Xnan = isnan (x); Ynan = isnan (y); x(Xnan) = -Inf; y(Ynan) = -Inf; v = max (x, y); v(Xnan & Ynan) = NaN; endif endfunction %!demo %! ## Find the column maximum values and their indices %! ## for matrix data with missing values. %! %! x = magic (3); %! x([1, 6:9]) = NaN %! [y, ind] = nanmax (x) %!demo %! ## Find the maximum of all the values in an array, ignoring missing values. %! ## Create a 2-by-5-by-3 array x with some missing values. %! %! x = reshape (1:30, [2, 5, 3]); %! x([10:12, 25]) = NaN %! %! ## Find the maximum of the elements of x. %! %! y = nanmax (x, [], 'all') ## Test output %!assert_equal (nanmax ([2, 4, NaN, 7]), 7) %!assert_equal (nanmax ([2, 4, NaN, Inf]), Inf) %!assert_equal (nanmax ([1, NaN, 3; NaN, 5, 6; 7, 8, NaN]), [7, 8, 6]) %!assert_equal (nanmax ([1, NaN, 3; NaN, 5, 6; 7, 8, NaN]'), [3, 6, 8]) %!assert_equal (nanmax (single ([1, NaN, 3; NaN, 5, 6; 7, 8, NaN])), single ([7, 8, 6])) %!shared x, y %! x(:,:,1) = [1.77, -0.005, NaN, -2.95; NaN, 0.34, NaN, 0.19]; %! x(:,:,2) = [1.77, -0.005, NaN, -2.95; NaN, 0.34, NaN, 0.19] + 5; %! y = x; %! y(2,3,1) = 0.51; %!assert_equal (nanmax (x, [], [1, 2])(:), [1.77;6.77]) %!assert_equal (nanmax (x, [], [1, 3])(:), [6.77;5.34;NaN;5.19]) %!assert_equal (nanmax (x, [], [2, 3])(:), [6.77;5.34]) %!assert_equal (nanmax (x, [], [1, 2, 3]), 6.77) %!assert_equal (nanmax (x, [], 'all'), 6.77) %!assert_equal (nanmax (y, [], [1, 3])(:), [6.77;5.34;0.51;5.19]) %!assert_equal (nanmax (x(1,:,1), x(2,:,1)), [1.77, 0.34, NaN, 0.19]) %!assert_equal (nanmax (x(1,:,2), x(2,:,2)), [6.77, 5.34, NaN, 5.19]) %!assert_equal (nanmax (y(1,:,1), y(2,:,1)), [1.77, 0.34, 0.51, 0.19]) %!assert_equal (nanmax (y(1,:,2), y(2,:,2)), [6.77, 5.34, NaN, 5.19]) ## Test dimension indexing with vecdim in N-dimensional arrays %!test %! xx = repmat ([1:20;6:25], [5 2 6 3]); %! assert_equal (size (nanmax (xx, [], [3, 2])), [10, 1, 1, 3]); %! assert_equal (size (nanmax (xx, [], [1, 2])), [1, 1, 6, 3]); %! assert_equal (size (nanmax (xx, [], [1, 2, 4])), [1, 1, 6]); %! assert_equal (size (nanmax (xx, [], [1, 4, 3])), [1, 40]); %! assert_equal (size (nanmax (xx, [], [1, 2, 3, 4])), [1, 1]); ## Test exceeding dimensions %!assert_equal (nanmax (ones (2), [], 3), ones (2, 2)) %!assert_equal (nanmax (ones (2, 2, 2), [], 99), ones (2, 2, 2)) %!assert_equal (nanmax (magic (3), [], 3), magic (3)) %!assert_equal (nanmax (magic (3), [], [1, 3]), [8, 9, 7]) %!assert_equal (nanmax (magic (3), [], [1, 99]), [8, 9, 7]) ## Test comparisons %!assert_equal (nanmax (ones (2), 3), 3 * ones (2,2)) ## Test input validation %!error ... %! nanmax (y, [], [1, 1, 2]) %!error ... %! [v, idx] = nanmax (x, y, [1 2]) statistics-release-1.9.2/inst/Descriptive_Statistics/nanmean.m000066400000000000000000000134511524624707500246160ustar00rootroot00000000000000## Copyright (C) 2025 Leonardo Araujos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{s} =} nanmean (@var{x}) ## @deftypefnx {statistics} {@var{s} =} nanmean (@var{x}, @qcode{'all'}) ## @deftypefnx {statistics} {@var{s} =} nanmean (@var{x}, @var{dim}) ## @deftypefnx {statistics} {@var{s} =} nanmean (@var{x}, @var{vecdim}) ## ## Compute the mean while ignoring NaN values. ## ## @code{@var{s} = nanmean (@var{x})} returns the mean of @var{x} after removing ## @qcode{NaN} values. If @var{x} is a vector, a scalar value is returned. If ## @var{x} is a matrix, a row vector of column means is returned. If @var{x} is ## a multidimensional array, @code{nanmean} operates along the first ## nonsingleton dimension. If all values along a dimension are @qcode{NaN}, the ## mean is returned returned as @qcode{NaN}. ## ## @code{@var{s} = nanmean (@var{x}, @qcode{'all'})} returns the mean of all ## elements of @var{x}, after removing @qcode{NaN} values. It is the equivalent ## of @code{nanmean (@var{x}(:))}. ## ## @code{@var{s} = nanmean (@var{x}, @var{dim})} operates along the dimension ## @var{dim} of @var{x}. ## ## @code{@var{s} = nanmean (@var{x}, @var{vecdim})} returns the mean over the ## dimensions specified in the vector @var{vecdim}. Each element of ## @var{vecdim} represents a dimension of the input array @var{x} and the output ## @var{s} has length 1 in the specified operating dimensions. The lengths of ## the other dimensions are the same for @var{x} and @var{y}. For example, if ## @var{x} is a 2-by-3-by-4 array, then @code{nanmean (@var{x}, [1 2])} returns ## a ## 1-by-1-by-4 array. Each element of the output array is the mean of the ## elements on the corresponding page of @var{x}. If @var{vecdim} indexes all ## dimensions of @var{x}, then it is equivalent to ## @code{nanmean (@var{x}, @qcode{'all'})}. Any dimension in @var{vecdim} ## greater than @code{ndims (@var{x})} is ignored. ## ## @code{nanmean} primarily operates on @qcode{single} and @qcode{double} ## numeric types, since they support @qcode{NaN} values, while preserving the ## data type. Nevertheless, it can also operate on integer types by treating ## them as @qcode{double} types. To avoid overflow on very large @qcode{int64} ## and @qcode{uint64} values, use the @code{mean} function, which applies ## special handling for such cases. ## ## @seealso{mean, nansum, nanmin, nanmax} ## @end deftypefn function y = nanmean (x, dim) if (nargin < 1 || nargin > 2) print_usage (); elseif (! isnumeric (x)) error ("nanmean: X must be numeric."); elseif (isempty (x)) y = NaN; else ## Determine the first nonsingleton dimension to operate on sx = size (x); if (nargin < 2) dim = find (sx != 1, 1); if (isempty (dim)) # scalar dim = 1; endif else if (isscalar (dim)) if (! isnumeric (dim) || fix (dim) != dim || dim <= 0) error ("nanmean: DIM must be a positive integer."); endif elseif (isvector (dim)) if (ischar (dim)) if (strcmpi (dim, 'all')) x = x(:); dim = 1; else error ("nanmean: invalid option."); endif else if (! isnumeric (dim) || any (fix (dim) != dim) || any (dim <= 0)) error ("nanmean: VECDIM must be a vector of positive integers."); endif endif endif endif na = isnan (x); x(na) = 0; na = ! na; if (isscalar (dim)) y = sum (x, dim) ./ sum (na, dim); else for i = numel (dim):-1:1, x = sum (x, dim(i)); na = sum (na, dim(i)); endfor y = x ./ na; endif endif endfunction %!demo %! ## Find the column means for a matrix with missing values., %! %! x = magic (3); %! x([1, 4, 7:9]) = NaN %! y = nanmean (x) %!demo %! ## Find the row means for a matrix with missing values., %! %! x = magic (3); %! x([1, 4, 7:9]) = NaN %! y = nanmean (x, 2) %!demo %! ## Find the mean of all the values in a multidimensional array %! ## with missing values. %! %! x = reshape (1:30, [2, 5, 3]); %! x([10:12, 25]) = NaN %! y = nanmean (x, 'all') %!demo %! ## Find the mean of a multidimensional array with missing values over %! ## multiple dimensions. %! %! x = reshape (1:30, [2, 5, 3]); %! x([10:12, 25]) = NaN %! y = nanmean (x, [2, 3]) ## Test output %!assert_equal (nanmean ([]), NaN) %!assert_equal (nanmean (NaN), NaN) %!assert_equal (nanmean (NaN (3)), [NaN, NaN, NaN]) %!assert_equal (nanmean ([3 2 NaN 7]), 4) %!assert_equal (nanmean ([2 4 NaN Inf]), Inf) %!assert_equal (nanmean ([1 NaN 3; NaN 4 6; 7 8 NaN]), [4 6 4.5]) %!assert_equal (nanmean ([1 NaN 3; NaN 5 6; 7 8 NaN], 2), [2; 5.5; 7.5]) %!assert_equal (nanmean (uint8 ([2 4 1 7])), 3.5) %!test %! x = magic (3); %! x([1 6:9]) = NaN; %! assert_equal (nanmean (x), [3.5, 3, NaN]) %! assert_equal (nanmean (x, 2), [1; 4; 4]) %!test %! x = reshape (1:24, [2, 4, 3]); %! x([5:6, 20]) = NaN; %! assert_equal (nanmean (x, 'all'), 269/21) %!test %! x = reshape (1:24,[2, 4, 3]); %! x([5:6, 20]) = NaN; %! assert_equal (squeeze (nanmean (x, [1, 2])), [25/6; 100/8; 144/7]) %! assert_equal (nanmean (x, [2, 3]), [139/11; 13]) statistics-release-1.9.2/inst/Descriptive_Statistics/nanmedian.m000066400000000000000000000137161524624707500251370ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{m} =} nanmedian (@var{x}) ## @deftypefnx {statistics} {@var{m} =} nanmedian (@var{x}, @qcode{'all'}) ## @deftypefnx {statistics} {@var{m} =} nanmedian (@var{x}, @var{dim}) ## @deftypefnx {statistics} {@var{m} =} nanmedian (@var{x}, @var{vecdim}) ## ## Compute the median while ignoring NaN values. ## ## @code{@var{m} = nanmedian (@var{x})} returns the median of @var{x}, after ## removing @qcode{NaN} values. If @var{x} is a vector, a scalar value is ## returned. If @var{x} is a matrix, a row vector of column medians is ## returned. If @var{x} is a multidimensional array, @code{nanmedian} operates ## along the first nonsingleton dimension. If all values along a dimension are ## @qcode{NaN}, the median is returned as @qcode{NaN}. ## ## @code{@var{m} = nanmedian (@var{x}, @qcode{'all'})} returns the median of all ## elements of @var{x}, after removing @qcode{NaN} values. It is the equivalent ## of @code{nanmedian (@var{x}(:))}. ## ## @code{@var{m} = nanmedian (@var{x}, @var{dim})} operates along the dimension ## @var{dim} of @var{x}. ## ## @code{@var{m} = nanmedian (@var{x}, @var{vecdim})} returns the median over ## the dimensions specified in the vector @var{vecdim}. Each element of ## @var{vecdim} represents a dimension of the input array @var{x} and the output ## @var{m} has length 1 in the specified operating dimensions. If @var{vecdim} ## indexes all dimensions of @var{x}, then it is equivalent to ## @code{nanmedian (@var{x}, @qcode{'all'})}. Any dimension in @var{vecdim} ## greater than @code{ndims (@var{x})} is ignored. ## ## @seealso{median, nanmean, nansum} ## @end deftypefn function m = nanmedian (x, dim) if (nargin < 1 || nargin > 2) print_usage (); endif if (! isnumeric (x) && ! islogical (x)) error ("nanmedian: X must be numeric."); endif if (isempty (x)) m = NaN; return; endif ## Operating dimension(s) if (nargin < 2) dim = find (size (x) != 1, 1); if (isempty (dim)) dim = 1; endif elseif (ischar (dim) && strcmpi (dim, 'all')) m = __colmedian__ (x(:).'); return; elseif (isscalar (dim)) if (! isnumeric (dim) || fix (dim) != dim || dim <= 0) error ("nanmedian: DIM must be a positive integer."); endif elseif (isnumeric (dim) && isvector (dim)) if (any (fix (dim) != dim) || any (dim <= 0)) error ("nanmedian: VECDIM must be a vector of positive integers."); endif dim = sort (dim); if (! all (diff (dim))) error ("nanmedian: VECDIM must contain non-repeating positive integers."); endif else error ("nanmedian: invalid DIM argument."); endif ## Ignore dimensions larger than the array (they are singleton) vecdim = dim; vecdim(vecdim > ndims (x)) = []; if (isempty (vecdim)) m = x; return; endif szx = size (x); remdims = 1:ndims (x); remdims(vecdim) = []; if (isempty (remdims)) m = __colmedian__ (x(:).'); return; endif ## Permute the operating dimensions to the back and squash them together perm = [remdims, vecdim]; xp = permute (x, perm); R = prod (szx(remdims)); K = prod (szx(vecdim)); xr = reshape (xp, R, K); mr = __colmedian__ (xr); ## Restore the original layout with the operating dimensions collapsed m = reshape (mr, [szx(remdims), ones(1, numel (vecdim))]); m = ipermute (m, perm); endfunction ## Median over the second dimension of a 2-D matrix, ignoring NaN values. function m = __colmedian__ (M) R = rows (M); Ms = sort (M, 2); # NaN values are sorted to the end n = sum (! isnan (M), 2); m = NaN (R, 1); v = find (n > 0); nn = n(v); iL = floor ((nn + 1) / 2); iU = ceil ((nn + 1) / 2); linL = v + (iL - 1) * R; linU = v + (iU - 1) * R; m(v) = (Ms(linL) + Ms(linU)) / 2; endfunction %!demo %! ## Find the column medians for a matrix with missing values. %! %! x = magic (3); %! x([1, 6:9]) = NaN %! m = nanmedian (x) %!demo %! ## Find the median of all elements, ignoring missing values. %! %! x = reshape (1:30, [2, 5, 3]); %! x([10:12, 25]) = NaN %! m = nanmedian (x, 'all') ## Test output %!assert_equal (nanmedian ([]), NaN) %!assert_equal (nanmedian (NaN), NaN) %!assert_equal (nanmedian (5), 5) %!assert_equal (nanmedian ([2, 4, NaN, 8]), 4) %!assert_equal (nanmedian ([1 2 NaN; 4 NaN 6; 7 8 9; 10 11 12]), [5.5, 8, 9]) %!assert_equal (nanmedian ([1 2 NaN; 4 NaN 6; 7 8 9; 10 11 12], 2), ... %! [1.5; 5; 8; 11]) %!assert_equal (nanmedian ([1 NaN; NaN NaN; 3 NaN]), [2, NaN]) %!assert_equal (nanmedian (reshape (1:12, [2, 3, 2])(:)), 6.5) %!test %! x = reshape (1:24, [2, 4, 3]); %! x([5:6, 20]) = NaN; %! assert_equal (nanmedian (x, 'all'), nanmedian (x(! isnan (x))(:))); %! assert_equal (nanmedian (x, [1, 2, 3]), nanmedian (x, 'all')); %!test %! x = magic (4); %! x([1, 6, 11, 16]) = NaN; %! assert_equal (nanmedian (x, 2), [3; 8; 9; 14]); ## Test input validation %!error nanmedian () %!error nanmedian ({3}) %!error nanmedian (ones (3), 0) %!error nanmedian (ones (3), 1.5) %!error ... %! nanmedian (ones (3, 3, 3), [2, 2, 3]) statistics-release-1.9.2/inst/Descriptive_Statistics/nanmin.m000066400000000000000000000204021524624707500244530ustar00rootroot00000000000000## Copyright (C) 2001 Paul Kienzle ## Copyright (C) 2003 Alois Schloegl ## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{v} =} nanmin (@var{x}) ## @deftypefnx {statistics} {@var{v} =} nanmin (@var{x}, [], @var{dim}) ## @deftypefnx {statistics} {[@var{v}, @var{idx}] =} nanmin (@dots{}) ## @deftypefnx {statistics} {@var{v} =} nanmin (@var{x}, [], @qcode{'all'}) ## @deftypefnx {statistics} {@var{v} =} nanmin (@var{x}, [], @var{vecdim}) ## @deftypefnx {statistics} {@var{v} =} nanmin (@var{x}, @var{y}) ## ## Find the minimum while ignoring NaN values. ## ## @code{@var{v} = nanmin (@var{x})} returns the minimum of @var{x}, after ## removing @qcode{NaN} values. If @var{x} is a vector, a scalar minimum value ## is returned. If @var{x} is a matrix, a row vector of column minima is ## returned. If @var{x} is a multidimensional array, the @code{nanmin} operates ## along the first nonsingleton dimension. If all values in a column are ## @qcode{NaN}, the minimum is returned as @qcode{NaN} rather than @qcode{[]}. ## ## @code{@var{v} = nanmin (@var{x}, [], @var{dim})} operates along the dimension ## @var{dim} of @var{x}. ## ## @code{[@var{v}, @var{idx}] = nanmin (@dots{})} also returns the row indices ## of the minimum values for each column in the vector @var{idx}. When @var{x} ## is a vector, then @var{idx} is a scalar value as @var{v}. ## ## @code{@var{v} = nanmin (@var{x}, [], @qcode{'all'})} returns the minimum of ## all elements of @var{x}, after removing @qcode{NaN} values. It is the ## equivalent of @code{nanmin (@var{x}(:))}. The optional flag @qcode{'all'} ## cannot be used together with @var{dim} or @var{vecdim} input arguments. ## ## @code{@var{v} = nanmin (@var{x}, [], @var{vecdim})} returns the minimum over ## the dimensions specified in the vector @var{vecdim}. Each element of ## @var{vecdim} represents a dimension of the input array @var{x} and the output ## @var{v} has length 1 in the specified operating dimensions. The lengths of ## the other dimensions are the same for @var{x} and @var{y}. For example, if ## @var{x} is a 2-by-3-by-4 array, then @code{nanmin (@var{x}, [1 2])} returns a ## 1-by-1-by-4 array. Each element of the output array is the minimum of the ## elements on the corresponding page of @var{x}. If @var{vecdim} indexes all ## dimensions of @var{x}, then it is equivalent to ## @code{nanmin (@var{x}, @qcode{'all'})}. Any dimension in @var{vecdim} ## greater than @code{ndims (@var{x})} is ignored. ## ## @seealso{min, nanmax, nansum} ## @end deftypefn function [v, idx] = nanmin (x, y, dim) if (nargin < 1 || nargin > 3) print_usage; elseif (nargin == 1 || (nargin == 2 && isempty (y))) nanvals = isnan (x); x(nanvals) = Inf; [v, idx] = min (x); v(all (nanvals)) = NaN; elseif (nargin == 3 && strcmpi (dim, 'all') && isempty (y)) x = x(:); nanvals = isnan (x); x(nanvals) = Inf; [v, idx] = min (x); v(all (nanvals)) = NaN; elseif (nargin == 3 && isempty (y)) if (isscalar (dim)) nanvals = isnan (x); x(nanvals) = Inf; [v, idx] = min (x, [], dim); v(all (nanvals, dim)) = NaN; else vecdim = sort (dim); if (! all (diff (vecdim))) error ("nanmin: VECDIM must contain non-repeating positive integers."); endif ## Ignore dimensions in VECDIM larger than actual array vecdim(find (vecdim > ndims (x))) = []; if (isempty (vecdim)) v = x; if (nargout > 1) idx = reshape ([1:numel(x)], size (x)); endif else ## Calculate permutation vector szx = size (x); remdims = 1:ndims (x); # All dimensions remdims(vecdim) = []; # Delete dimensions specified by vecdim nremd = numel (remdims); ## If all dimensions are given, it is equivalent to 'all' flag if (nremd == 0) x = x(:); nanvals = isnan (x); x(nanvals) = Inf; [v, idx] = min (x); v(all (nanvals)) = NaN; else ## Permute to push vecdims to back perm = [remdims, vecdim]; x = permute (x, perm); ## Reshape to squash all vecdims in final dimension sznew = [szx(remdims), prod(szx(vecdim))]; x = reshape (x, sznew); ## Calculate nanmin on final dimension dim = nremd + 1; nanvals = isnan (x); x(nanvals) = Inf; [v, idx] = min (x, [], dim); v(all (nanvals, dim)) = NaN; ## Inverse permute back to correct dimensions v = ipermute (v, perm); idx = ipermute (idx, perm); endif endif endif else if (nargout > 1) error ("nanmin: a second output is not supported with this syntax."); endif Xnan = isnan (x); Ynan = isnan (y); x(Xnan) = Inf; y(Ynan) = Inf; v = min (x, y); v(Xnan & Ynan) = NaN; endif endfunction %!demo %! ## Find the column minimum values and their indices %! ## for matrix data with missing values. %! %! x = magic (3); %! x([1, 6:9]) = NaN %! [y, ind] = nanmin (x) %!demo %! ## Find the minimum of all the values in an array, ignoring missing values. %! ## Create a 2-by-5-by-3 array x with some missing values. %! %! x = reshape (1:30, [2, 5, 3]); %! x([10:12, 25]) = NaN %! %! ## Find the minimum of the elements of x. %! %! y = nanmin (x, [], 'all') ## Test output %!assert_equal (nanmin ([2, 4, NaN, 7]), 2) %!assert_equal (nanmin ([2, 4, NaN, -Inf]), -Inf) %!assert_equal (nanmin ([1, NaN, 3; NaN, 5, 6; 7, 8, NaN]), [1, 5, 3]) %!assert_equal (nanmin ([1, NaN, 3; NaN, 5, 6; 7, 8, NaN]'), [1, 5, 7]) %!assert_equal (nanmin (single ([1, NaN, 3; NaN, 5, 6; 7, 8, NaN])), single ([1, 5, 3])) %!shared x, y %! x(:,:,1) = [1.77, -0.005, NaN, -2.95; NaN, 0.34, NaN, 0.19]; %! x(:,:,2) = [1.77, -0.005, NaN, -2.95; NaN, 0.34, NaN, 0.19] + 5; %! y = x; %! y(2,3,1) = 0.51; %!assert_equal (nanmin (x, [], [1, 2])(:), [-2.95; 2.05]) %!assert_equal (nanmin (x, [], [1, 3])(:), [1.77; -0.005; NaN; -2.95]) %!assert_equal (nanmin (x, [], [2, 3])(:), [-2.95; 0.19]) %!assert_equal (nanmin (x, [], [1, 2, 3]), -2.95) %!assert_equal (nanmin (x, [], 'all'), -2.95) %!assert_equal (nanmin (y, [], [1, 3])(:), [1.77; -0.005; 0.51; -2.95]) %!assert_equal (nanmin (x(1,:,1), x(2,:,1)), [1.77, -0.005, NaN, -2.95]) %!assert_equal (nanmin (x(1,:,2), x(2,:,2)), [6.77, 4.995, NaN, 2.05]) %!assert_equal (nanmin (y(1,:,1), y(2,:,1)), [1.77, -0.005, 0.51, -2.95]) %!assert_equal (nanmin (y(1,:,2), y(2,:,2)), [6.77, 4.995, NaN, 2.05]) ## Test dimension indexing with vecdim in N-dimensional arrays %!test %! xx = repmat ([1:20;6:25], [5 2 6 3]); %! assert_equal (size (nanmin (xx, [], [3, 2])), [10, 1, 1, 3]); %! assert_equal (size (nanmin (xx, [], [1, 2])), [1, 1, 6, 3]); %! assert_equal (size (nanmin (xx, [], [1, 2, 4])), [1, 1, 6]); %! assert_equal (size (nanmin (xx, [], [1, 4, 3])), [1, 40]); %! assert_equal (size (nanmin (xx, [], [1, 2, 3, 4])), [1, 1]); ## Test exceeding dimensions %!assert_equal (nanmin (ones (2), [], 3), ones (2, 2)) %!assert_equal (nanmin (ones (2, 2, 2), [], 99), ones (2, 2, 2)) %!assert_equal (nanmin (magic (3), [], 3), magic (3)) %!assert_equal (nanmin (magic (3), [], [1, 3]), [3, 1, 2]) %!assert_equal (nanmin (magic (3), [], [1, 99]), [3, 1, 2]) ## Test comparisons %!assert_equal (nanmin (ones (2), 3), ones (2,2)) ## Test input validation %!error ... %! nanmin (y, [], [1, 1, 2]) %!error ... %! [v, idx] = nanmin (x, y, [1 2]) statistics-release-1.9.2/inst/Descriptive_Statistics/nanstd.m000066400000000000000000000100471524624707500244660ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{s} =} nanstd (@var{x}) ## @deftypefnx {statistics} {@var{s} =} nanstd (@var{x}, @var{w}) ## @deftypefnx {statistics} {@var{s} =} nanstd (@var{x}, @var{w}, @qcode{'all'}) ## @deftypefnx {statistics} {@var{s} =} nanstd (@var{x}, @var{w}, @var{dim}) ## @deftypefnx {statistics} {@var{s} =} nanstd (@var{x}, @var{w}, @var{vecdim}) ## ## Compute the standard deviation while ignoring NaN values. ## ## @code{@var{s} = nanstd (@var{x})} returns the standard deviation of @var{x}, ## after removing @qcode{NaN} values. If @var{x} is a vector, a scalar value is ## returned. If @var{x} is a matrix, a row vector of column standard deviations ## is returned. If @var{x} is a multidimensional array, @code{nanstd} operates ## along the first nonsingleton dimension. If a dimension contains fewer than ## two non-@qcode{NaN} values, the standard deviation is returned as 0 for a ## single value and as @qcode{NaN} when all values are @qcode{NaN}. ## ## @code{@var{s} = nanstd (@var{x}, @var{w})} specifies the normalization. When ## @var{w} is 0 (default), the standard deviation is normalized by @math{N-1}, ## where @math{N} is the number of non-@qcode{NaN} observations. When @var{w} ## is 1, it is normalized by @math{N}. @var{w} may also be a vector of ## nonnegative weights whose length matches the operating dimension, in which ## case the weighted standard deviation normalized by the sum of the weights is ## returned. ## ## @code{@var{s} = nanstd (@var{x}, @var{w}, @qcode{'all'})} returns the ## standard deviation of all elements of @var{x}, after removing @qcode{NaN} ## values. Use an empty value, @code{@var{w} = []}, to pass the default ## normalization. ## ## @code{@var{s} = nanstd (@var{x}, @var{w}, @var{dim})} operates along the ## dimension @var{dim} of @var{x}. ## ## @code{@var{s} = nanstd (@var{x}, @var{w}, @var{vecdim})} returns the standard ## deviation over the dimensions specified in the vector @var{vecdim}. A weight ## vector is not supported together with @qcode{'all'} or @var{vecdim}. Any ## dimension in @var{vecdim} greater than @code{ndims (@var{x})} is ignored. ## ## @seealso{std, nanvar, nanmean, nansum} ## @end deftypefn function s = nanstd (x, varargin) if (nargin < 1 || nargin > 3) print_usage (); elseif (! isnumeric (x) && ! islogical (x)) error ("nanstd: X must be numeric."); endif s = sqrt (nanvar (x, varargin{:})); endfunction %!demo %! ## Find the column standard deviations for a matrix with missing values. %! %! x = magic (3); %! x([1, 6:9]) = NaN %! s = nanstd (x) %!demo %! ## Find the row standard deviations, normalized by N instead of N-1. %! %! x = magic (3); %! x([1, 6:9]) = NaN %! s = nanstd (x, 1, 2) ## Test output %!assert_equal (nanstd ([]), NaN) %!assert_equal (nanstd (NaN), NaN) %!assert_equal (nanstd (5), 0) %!assert_equal (nanstd ([1 2 NaN; 4 NaN 6; 7 8 9; 10 11 12]), ... %! sqrt ([15, 21, 9]), 1e-14) %!assert_equal (nanstd ([1 2 NaN; 4 NaN 6; 7 8 9; 10 11 12], 1), ... %! sqrt ([11.25, 14, 6]), 1e-14) %!assert_equal (nanstd ([1 2 NaN; 4 NaN 6; 7 8 9; 10 11 12], 0, 2), ... %! sqrt ([0.5; 2; 1; 1]), 1e-14) %!assert_equal (nanstd (NaN (2, 3)), [NaN, NaN, NaN]) ## Test input validation %!error nanstd () %!error nanstd ({3}) statistics-release-1.9.2/inst/Descriptive_Statistics/nansum.m000066400000000000000000000156451524624707500245110ustar00rootroot00000000000000## Copyright (C) 2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{s} =} nansum (@var{x}) ## @deftypefnx {statistics} {@var{s} =} nanmax (@var{x}, @qcode{'all'}) ## @deftypefnx {statistics} {@var{s} =} nanmax (@var{x}, @var{dim}) ## @deftypefnx {statistics} {@var{s} =} nanmax (@var{x}, @var{vecdim}) ## ## Compute the sum while ignoring NaN values. ## ## @code{@var{s} = nansum (@var{x})} returns the sum of @var{x}, after removing ## @qcode{NaN} values. If @var{x} is a vector, a scalar value is returned. If ## @var{x} is a matrix, a row vector of column sums is returned. If @var{x} is ## a multidimensional array, the @code{nansum} operates along the first ## nonsingleton dimension. If all values along a dimension are @qcode{NaN}, the ## sum is returned returned as 0. ## ## @code{@var{s} = nansum (@var{x}, @qcode{'all'})} returns the sum of all ## elements of @var{x}, after removing @qcode{NaN} values. It is the equivalent ## of @code{nansum (@var{x}(:))}. ## ## @code{@var{s} = nansum (@var{x}, @var{dim})} operates along the dimension ## @var{dim} of @var{x}. ## ## @code{@var{s} = nansum (@var{x}, @var{vecdim})} returns the sum over the ## dimensions specified in the vector @var{vecdim}. Each element of ## @var{vecdim} represents a dimension of the input array @var{x} and the output ## @var{s} has length 1 in the specified operating dimensions. The lengths of ## the other dimensions are the same for @var{x} and @var{y}. For example, if ## @var{x} is a 2-by-3-by-4 array, then @code{nanmax (@var{x}, [1 2])} returns a ## 1-by-1-by-4 array. Each element of the output array is the maximum of the ## elements on the corresponding page of @var{x}. If @var{vecdim} indexes all ## dimensions of @var{x}, then it is equivalent to ## @code{nanmax (@var{x}, @qcode{'all'})}. Any dimension in @var{vecdim} ## greater than @code{ndims (@var{x})} is ignored. ## ## @seealso{sum, nanmin, nanmax} ## @end deftypefn function s = nansum (x, dim) if (nargin < 1 || nargin > 2) print_usage (); elseif (! isnumeric (x)) error ("nansum: X must be numeric."); elseif (isempty (x)) s = 0; elseif (nargin == 1) nanvals = isnan (x); x(nanvals) = 0; s = sum (x); s(all (nanvals)) = 0; elseif (nargin == 2 && strcmpi (dim, 'all')) x = x(:); nanvals = isnan (x); x(nanvals) = 0; s = sum (x); s(all (nanvals)) = 0; else # DIM must be a numeric scalar or vector if (isscalar (dim)) if (! isnumeric (dim) || fix (dim) != dim || dim <= 0) error ("nansum: DIM must be a positive integer."); endif nanvals = isnan (x); x(nanvals) = 0; s = sum (x, dim); s(all (nanvals, dim)) = 0; else if (! isvector (dim) || any (fix (dim) != dim) || any (dim <= 0)) error ("nansum: VECDIM must be a vector of positive integer."); endif vecdim = sort (dim); if (! all (diff (vecdim))) error ("nansum: VECDIM must contain non-repeating positive integers."); endif ## Ignore dimensions in VECDIM larger than actual array vecdim(find (vecdim > ndims (x))) = []; if (isempty (vecdim)) s = x; else ## Calculate permutation vector szx = size (x); remdims = 1:ndims (x); # All dimensions remdims(vecdim) = []; # Delete dimensions specified by vecdim nremd = numel (remdims); ## If all dimensions are given, it is equivalent to 'all' flag if (nremd == 0) x = x(:); nanvals = isnan (x); x(nanvals) = 0; s = sum (x); s(all (nanvals)) = 0; else ## Permute to push vecdims to back perm = [remdims, vecdim]; x = permute (x, perm); ## Reshape to squash all vecdims in final dimension sznew = [szx(remdims), prod(szx(vecdim))]; x = reshape (x, sznew); ## Calculate nansum on final dimension dim = nremd + 1; nanvals = isnan (x); x(nanvals) = 0; s = sum (x, dim); s(all (nanvals, dim)) = 0; ## Inverse permute back to correct dimensions s = ipermute (s, perm); endif endif endif endif endfunction %!demo %! ## Find the column sums for a matrix with missing values., %! %! x = magic (3); %! x([1, 4, 7:9]) = NaN %! s = nansum (x) %!demo %! ## Find the row sums for a matrix with missing values., %! %! x = magic (3); %! x([1, 4, 7:9]) = NaN %! s = nansum (x, 2) %!demo %! ## Find the sum of all the values in a multidimensional array %! ## with missing values. %! %! x = reshape (1:30, [2, 5, 3]); %! x([10:12, 25]) = NaN %! s = nansum (x, 'all') %!demo %! ## Find the sum of a multidimensional array with missing values over %! ## multiple dimensions. %! %! x = reshape (1:30, [2, 5, 3]); %! x([10:12, 25]) = NaN %! s = nansum (x, [2, 3]) ## Test output %!assert_equal (nansum ([]), 0) %!assert_equal (nansum (NaN), 0) %!assert_equal (nansum (NaN (3)), [0, 0, 0]) %!assert_equal (nansum ([2 4 NaN 7]), 13) %!assert_equal (nansum ([2 4 NaN Inf]), Inf) %!assert_equal (nansum ([1 NaN 3; NaN 5 6; 7 8 NaN]), [8 13 9]) %!assert_equal (nansum ([1 NaN 3; NaN 5 6; 7 8 NaN], 2), [4; 11; 15]) %!assert_equal (nansum (uint8 ([2 4 1 7])), 14) %!test %! x = magic (3); %! x([1 6:9]) = NaN; %! assert_equal (nansum (x), [7, 6, 0]) %! assert_equal (nansum (x, 2), [1; 8; 4]) %!test %! x = reshape (1:24, [2, 4, 3]); %! x([5:6, 20]) = NaN; %! assert_equal (nansum (x, 'all'), 269) %!test %! x = reshape (1:24,[2, 4, 3]); %! x([5:6, 20]) = NaN; %! assert_equal (squeeze (nansum (x, [1, 2])), [25; 100; 144]) %! assert_equal (nansum (x, [2, 3]), [139; 130]) ## Test input validation %!error nansum ({3}) %!error nansum (ones (3), 0) %!error nansum (ones (3), 1.5) %!error nansum (ones (3), 1.5) %!error ... %! nansum (ones (3, 3, 3), [2, 2.5]) %!error ... %! nansum (ones (3, 3, 3), [-1, 2]) %!error ... %! nansum (ones (3, 3, 3), [2, 2, 3]) statistics-release-1.9.2/inst/Descriptive_Statistics/nanvar.m000066400000000000000000000161341524624707500244670ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{v} =} nanvar (@var{x}) ## @deftypefnx {statistics} {@var{v} =} nanvar (@var{x}, @var{w}) ## @deftypefnx {statistics} {@var{v} =} nanvar (@var{x}, @var{w}, @qcode{'all'}) ## @deftypefnx {statistics} {@var{v} =} nanvar (@var{x}, @var{w}, @var{dim}) ## @deftypefnx {statistics} {@var{v} =} nanvar (@var{x}, @var{w}, @var{vecdim}) ## ## Compute the variance while ignoring NaN values. ## ## @code{@var{v} = nanvar (@var{x})} returns the variance of @var{x}, after ## removing @qcode{NaN} values. If @var{x} is a vector, a scalar value is ## returned. If @var{x} is a matrix, a row vector of column variances is ## returned. If @var{x} is a multidimensional array, @code{nanvar} operates ## along the first nonsingleton dimension. If a dimension contains fewer than ## two non-@qcode{NaN} values, the variance is returned as 0 for a single value ## and as @qcode{NaN} when all values are @qcode{NaN}. ## ## @code{@var{v} = nanvar (@var{x}, @var{w})} specifies the normalization. When ## @var{w} is 0 (default), the variance is normalized by @math{N-1}, where ## @math{N} is the number of non-@qcode{NaN} observations. When @var{w} is 1, ## it is normalized by @math{N}. @var{w} may also be a vector of nonnegative ## weights whose length matches the operating dimension, in which case the ## weighted variance normalized by the sum of the weights is returned. ## ## @code{@var{v} = nanvar (@var{x}, @var{w}, @qcode{'all'})} returns the ## variance of all elements of @var{x}, after removing @qcode{NaN} values. Use ## an empty value, @code{@var{w} = []}, to pass the default normalization. ## ## @code{@var{v} = nanvar (@var{x}, @var{w}, @var{dim})} operates along the ## dimension @var{dim} of @var{x}. ## ## @code{@var{v} = nanvar (@var{x}, @var{w}, @var{vecdim})} returns the variance ## over the dimensions specified in the vector @var{vecdim}. A weight vector is ## not supported together with @qcode{'all'} or @var{vecdim}. Any dimension in ## @var{vecdim} greater than @code{ndims (@var{x})} is ignored. ## ## @seealso{var, nanstd, nanmean, nansum} ## @end deftypefn function v = nanvar (x, w, dim) if (nargin < 1 || nargin > 3) print_usage (); endif if (! isnumeric (x) && ! islogical (x)) error ("nanvar: X must be numeric."); endif if (isempty (x)) v = NaN; return; endif ## Normalization / weight argument if (nargin < 2 || isempty (w)) w = 0; endif if (! isnumeric (w) || ! isreal (w)) error ("nanvar: W must be 0, 1, or a vector of nonnegative weights."); endif wvec = false; if (isscalar (w)) if (w != 0 && w != 1) error ("nanvar: W must be 0 or 1 when it is a scalar."); endif elseif (isvector (w)) if (any (w < 0) || any (isnan (w))) error ("nanvar: weight vector W must contain nonnegative values."); endif wvec = true; else error ("nanvar: W must be 0, 1, or a vector of nonnegative weights."); endif ## Operating dimension dimall = false; if (nargin < 3) dim = find (size (x) != 1, 1); if (isempty (dim)) dim = 1; endif elseif (ischar (dim) && strcmpi (dim, 'all')) dimall = true; elseif (isscalar (dim)) if (! isnumeric (dim) || fix (dim) != dim || dim <= 0) error ("nanvar: DIM must be a positive integer."); endif elseif (isnumeric (dim) && isvector (dim)) if (any (fix (dim) != dim) || any (dim <= 0)) error ("nanvar: VECDIM must be a vector of positive integers."); endif dim = sort (dim); if (! all (diff (dim))) error ("nanvar: VECDIM must contain non-repeating positive integers."); endif else error ("nanvar: invalid DIM argument."); endif if (dimall) dimarg = 'all'; else dimarg = dim; endif if (wvec) ## Weighted variance (biased, normalized by the sum of the weights) if (dimall || ! isscalar (dim)) error ("nanvar: a weight vector is supported only with a scalar DIM."); endif if (numel (w) != size (x, dim)) error ("nanvar: the length of W must match the operating dimension."); endif wsz = ones (1, max (ndims (x), dim)); wsz(dim) = numel (w); wr = reshape (w(:), wsz); mask = ! isnan (x); we = wr .* mask; sumw = sum (we, dim); x0 = x; x0(! mask) = 0; mu = sum (we .* x0, dim) ./ sumw; t = (x0 - mu) .^ 2; v = sum (we .* t, dim) ./ sumw; else mask = ! isnan (x); n = sum (mask, dimarg); x0 = x; x0(! mask) = 0; xm = sum (x0, dimarg) ./ n; d2 = (x - xm) .^ 2; d2(isnan (d2)) = 0; ss = sum (d2, dimarg); if (w == 1) v = ss ./ n; else v = ss ./ (n - 1); endif v(n == 1) = 0; v(n == 0) = NaN; endif endfunction %!demo %! ## Find the column variances for a matrix with missing values. %! %! x = magic (3); %! x([1, 6:9]) = NaN %! v = nanvar (x) %!demo %! ## Find the row variances, normalized by N instead of N-1. %! %! x = magic (3); %! x([1, 6:9]) = NaN %! v = nanvar (x, 1, 2) ## Test output %!assert_equal (nanvar ([]), NaN) %!assert_equal (nanvar (NaN), NaN) %!assert_equal (nanvar (5), 0) %!assert_equal (nanvar ([2, 4, NaN, 8]), 9.333333333333334, 1e-14) %!assert_equal (nanvar ([1 2 NaN; 4 NaN 6; 7 8 9; 10 11 12]), [15, 21, 9]) %!assert_equal (nanvar ([1 2 NaN; 4 NaN 6; 7 8 9; 10 11 12], 1), [11.25, 14, 6]) %!assert_equal (nanvar ([1 2 NaN; 4 NaN 6; 7 8 9; 10 11 12], 0, 2), ... %! [0.5; 2; 1; 1]) %!assert_equal (nanvar ([1 2 NaN; 4 NaN 6; 7 8 9; 10 11 12], [1 2 3 4]'), ... %! [9, 8.4375, 50/9], 1e-13) %!assert_equal (nanvar (NaN (2, 3)), [NaN, NaN, NaN]) %!test %! x = reshape (1:24, [2, 4, 3]); %! x([5:6, 20]) = NaN; %! assert_equal (nanvar (x, 0, 'all'), nanvar (x(! isnan (x))(:)), 1e-12) ## Test input validation %!error nanvar () %!error nanvar ({3}) %!error nanvar (ones (3), 2) %!error ... %! nanvar (ones (1, 3), [1, -1, 2]) %!error nanvar (ones (3), 0, 1.5) %!error ... %! nanvar (ones (3, 3, 3), 0, [2, 2, 3]) %!error ... %! nanvar (ones (2, 3), [1, 2], 'all') %!error ... %! nanvar (ones (2, 3), [1, 2, 3], 1) statistics-release-1.9.2/inst/Descriptive_Statistics/partialcorr.m000066400000000000000000000332141524624707500255220ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program. If not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{rho} =} partialcorr (@var{x}) ## @deftypefnx {statistics} {@var{rho} =} partialcorr (@var{x}, @var{z}) ## @deftypefnx {statistics} {@var{rho} =} partialcorr (@var{x}, @var{y}, @var{z}) ## @deftypefnx {statistics} {[@var{rho}, @var{pval}] =} partialcorr (@dots{}) ## @deftypefnx {statistics} {[@dots{}] =} partialcorr (@dots{}, @var{Name}, @var{Value}) ## ## Linear or rank partial correlation coefficients. ## ## @code{@var{rho} = partialcorr (@var{x})} returns the sample linear partial ## correlation coefficients between pairs of variables in the @math{n}-by-@math{p} ## matrix @var{x}, controlling for the remaining columns of @var{x}. Each element ## @code{@var{rho}(i,j)} is the partial correlation between @code{@var{x}(:,i)} ## and @code{@var{x}(:,j)}, adjusted for the other @math{p-2} columns. @var{rho} ## is a symmetric @math{p}-by-@math{p} matrix with ones on the diagonal. ## ## @code{@var{rho} = partialcorr (@var{x}, @var{z})} controls instead for the ## variables in the @math{n}-by-@math{q} matrix @var{z}, returning the ## @math{p}-by-@math{p} partial correlations among the columns of @var{x}. ## ## @code{@var{rho} = partialcorr (@var{x}, @var{y}, @var{z})} returns the ## @math{p1}-by-@math{p2} matrix of partial correlations between the columns of ## the @math{n}-by-@math{p1} matrix @var{x} and the @math{n}-by-@math{p2} matrix ## @var{y}, controlling for @var{z}. Element @code{@var{rho}(i,j)} is the partial ## correlation between @code{@var{x}(:,i)} and @code{@var{y}(:,j)}. ## ## @code{[@var{rho}, @var{pval}] = partialcorr (@dots{})} also returns @var{pval}, ## a matrix of p-values for testing the hypothesis of no partial correlation ## against the alternative selected by @qcode{'Tail'}. ## ## A coefficient is @code{NaN} where the controlling variables explain either ## of the two variables completely, since the partial correlation is then ## undefined: no variation is left to correlate. This covers a controlling ## variable that duplicates one of them and any set of them that spans it. ## ## The following @var{Name}/@var{Value} pairs are accepted: ## ## @table @asis ## @item @qcode{'Type'} ## @qcode{'Pearson'} (default) for linear partial correlation, or ## @qcode{'Spearman'} for rank partial correlation (computed on the ranks of the ## data). @qcode{'Kendall'} is @emph{not} supported and raises an error, as in ## @sc{matlab}. ## ## @item @qcode{'Rows'} ## @qcode{'all'} (default) uses all rows regardless of missing values (any ## @code{NaN} yields a @code{NaN} result); @qcode{'complete'} uses only the rows ## with no missing values across all supplied variables; @qcode{'pairwise'} uses, ## for each computed coefficient, the rows with no missing values among just the ## variables involved in that coefficient. ## ## @item @qcode{'Tail'} ## The alternative hypothesis for @var{pval}: @qcode{'both'} (default, nonzero ## correlation), @qcode{'right'} (greater than zero), or @qcode{'left'} (less than ## zero). ## @end table ## ## The partial correlation is computed by regressing each of the two variables on ## the controlling variables (with an intercept) and correlating the residuals. ## The p-value uses a Student's @math{t} statistic with @math{n - 2 - k} degrees ## of freedom, where @math{k} is the number of controlling variables and @math{n} ## the number of observations used. ## ## @seealso{partialcorri, corr, corrcoef, tiedrank} ## @end deftypefn function [rho, pval] = partialcorr (varargin) if (nargin < 1) print_usage (); endif ## Separate the leading numeric matrices from the Name/Value options. nmat = 0; while (nmat < numel (varargin) && isnumeric (varargin{nmat+1})) nmat += 1; endwhile if (nmat < 1 || nmat > 3) error ("partialcorr: invalid number of input matrices."); endif args = varargin(1:nmat); [Type, Rows, Tail, rem] = parsePairedArguments ({'Type', 'Rows', 'Tail'}, ... {'pearson', 'all', 'both'}, varargin(nmat+1:end)); if (! isempty (rem)) error ("partialcorr: unknown or unpaired optional argument."); endif if (! (ischar (Type) && ischar (Rows) && ischar (Tail))) error ("partialcorr: 'Type', 'Rows', and 'Tail' values must be strings."); endif Type = lower (Type); Rows = lower (Rows); Tail = lower (Tail); if (strcmp (Type, 'kendall')) error ("partialcorr: cannot compute Kendall's partial rank correlation."); elseif (! any (strcmp (Type, {'pearson', 'spearman'}))) error ("partialcorr: '%s' is not a valid 'Type'.", Type); endif if (! any (strcmp (Rows, {'all', 'complete', 'pairwise'}))) error ("partialcorr: '%s' is not a valid 'Rows' option.", Rows); endif if (! any (strcmp (Tail, {'both', 'right', 'left'}))) error ("partialcorr: '%s' is not a valid 'Tail' option.", Tail); endif ## Assign the roles of the input matrices. ## 1 matrix : pairs of columns of X, controlling for the remaining columns. ## 2 matrix : pairs of columns of X, controlling for Z. ## 3 matrix : columns of X vs columns of Y, controlling for Z. A = args{1}; useRemaining = (nmat == 1); square = (nmat <= 2); if (nmat == 3) B = args{2}; else B = A; endif if (nmat >= 2) Zc = args{end}; else Zc = []; endif check_matrix ("partialcorr", A, "X"); n = rows (A); if (nmat == 3) check_matrix ("partialcorr", B, "Y"); if (rows (B) != n) error ("partialcorr: X and Y must have the same number of rows."); endif endif if (! isempty (Zc)) check_matrix ("partialcorr", Zc, "Z"); if (rows (Zc) != n) error ("partialcorr: Z must have the same number of rows as X."); endif endif p1 = columns (A); p2 = columns (B); ## Rows with no missing value across every supplied variable. completerows = ! any (isnan ([A, B, Zc]), 2); rho = NaN (p1, p2); pval = NaN (p1, p2); for i = 1:p1 for j = 1:p2 if (square && j < i) continue; # filled below by symmetry endif a = A(:,i); b = B(:,j); if (useRemaining) C = A(:, setdiff (1:p1, [i, j])); else C = Zc; endif [r, nu] = resid_partial (a, b, C, Type, Rows, completerows); rho(i,j) = r; pval(i,j) = student_pval (r, nu - 2 - columns (C), Tail); if (square) rho(j,i) = rho(i,j); pval(j,i) = pval(i,j); endif endfor endfor endfunction ## Validate that a matrix input is real, numeric, and 2-D. function check_matrix (fname, x, label) if (! (isnumeric (x) && isreal (x) && ismatrix (x) && ndims (x) == 2)) error ("%s: %s must be a real numeric matrix.", fname, label); endif endfunction ## Partial correlation of a and b controlling for C, with NaN handling. function [r, nu] = resid_partial (a, b, C, Type, Rows, completerows) n = numel (a); switch (Rows) case 'all' idx = true (n, 1); case 'complete' idx = completerows; case 'pairwise' idx = ! any (isnan ([a, b, C]), 2); endswitch nu = sum (idx); ## With 'all', any missing value propagates to a NaN coefficient. if (strcmp (Rows, 'all') && any (isnan ([a, b, C])(:))) r = NaN; nu = n; return; endif a = a(idx); b = b(idx); C = C(idx,:); if (strcmp (Type, 'spearman')) a = tiedrank (a); b = tiedrank (b); for c = 1:columns (C) C(:,c) = tiedrank (C(:,c)); endfor endif m = numel (a); M = [ones(m, 1), C]; ra = a - M * (M \ a); rb = b - M * (M \ b); sa = sqrt (sum (ra .^ 2)); sb = sqrt (sum (rb .^ 2)); ## A residual at or below the precision the column is held in counts as ## zero. Scale by the vector's magnitude, not one element of it, so a large ## constant offset raises the tolerance as it raises the rounding error. tol_a = m * eps (norm (a)); tol_b = m * eps (norm (b)); if (sa <= tol_a || sb <= tol_b) r = NaN; else r = sum (ra .* rb) / (sa * sb); r = max (-1, min (1, r)); endif endfunction ## p-value from a Student's t statistic for a correlation r with DOF degrees ## of freedom, under the requested tail. function p = student_pval (r, dof, Tail) if (dof <= 0 || isnan (r)) p = NaN; return; endif t = r .* sqrt (dof ./ (1 - r .^ 2)); switch (Tail) case 'both' p = 2 * tcdf (-abs (t), dof); case 'right' p = tcdf (-t, dof); case 'left' p = tcdf (t, dof); endswitch endfunction %!demo %! ## Partial correlations among four variables, each pair adjusted for the %! ## other two. %! x = [0.42 1.30 -0.85 0.11; 1.15 -0.47 0.33 1.82; -0.98 0.55 1.21 -0.34; ... %! 0.63 2.10 -0.19 0.48; 1.88 -1.02 0.74 0.05; -0.31 0.86 -1.44 1.29; ... %! 0.77 0.14 0.58 -0.71; -1.52 1.77 0.02 0.94; 0.29 -0.63 1.36 0.37]; %! rho = partialcorr (x) ## shared test data %!shared D, X, Y, Z %! D = [ 0.42 1.30 -0.85 0.11 2.04 %! 1.15 -0.47 0.33 1.82 -0.62 %! -0.98 0.55 1.21 -0.34 0.77 %! 0.63 2.10 -0.19 0.48 -1.15 %! 1.88 -1.02 0.74 0.05 0.39 %! -0.31 0.86 -1.44 1.29 0.92 %! 0.77 0.14 0.58 -0.71 1.63 %! -1.52 1.77 0.02 0.94 -0.28 %! 0.29 -0.63 1.36 0.37 0.51 %! 2.01 0.48 -0.77 -1.08 0.14 %! -0.44 1.05 0.91 0.66 -0.83 %! 0.90 -0.29 -0.36 1.47 1.22]; %! X = D(:,1:2); %! Y = D(:,3); %! Z = D(:,4:5); ## single-matrix form: symmetric, unit diagonal, controls for remaining columns %!test %! rho = partialcorr (D); %! assert_equal (diag (rho), ones (5, 1), 1e-12); %! assert_equal (rho, rho', 1e-12); %! assert_equal (rho(1,2), -0.8399, 1e-4); %! assert_equal (rho(1,5), -0.6172, 1e-4); %! assert_equal (rho(3,4), -0.6763, 1e-4); ## single-matrix form p-values: zero on the diagonal, symmetric off-diagonal %!test %! [rho, p] = partialcorr (D); %! assert_equal (diag (p), zeros (5, 1), 1e-12); %! assert_equal (p, p', 1e-12); %! assert_equal (p(1,2), 0.0046, 1e-4); %! assert_equal (p(1,5), 0.0766, 1e-4); ## two-matrix form: pairs of X controlling for Z, with p-values %!test %! [rho, p] = partialcorr (X, Z); %! assert_equal (rho, [1 -0.5888; -0.5888 1], 1e-4); %! assert_equal (p, [0 0.0733; 0.0733 0], 1e-4); ## three-matrix form: X vs Y controlling for Z, with p-values %!test %! [r, p] = partialcorr (X, Y, Z); %! assert_equal (r, [-0.2073; -0.5273], 1e-4); %! assert_equal (p, [0.5656; 0.1173], 1e-4); ## Spearman rank partial correlation %!test %! r = partialcorr (X, Y, Z, 'Type', 'Spearman'); %! assert_equal (r, [-0.3079; -0.4984], 1e-4); ## one-sided p-values %!test %! [~, pr] = partialcorr (X, Y, Z, 'Tail', 'right'); %! [~, pl] = partialcorr (X, Y, Z, 'Tail', 'left'); %! assert_equal (pr, [0.7172; 0.9414], 1e-4); %! assert_equal (pl, [0.2828; 0.0586], 1e-4); ## NaN handling: 'all' propagates, 'complete'/'pairwise' delete rows %!test %! DN = D; %! DN(3,2) = NaN; %! assert_equal (all (isnan (partialcorr (DN)), 'all'), true); %! rc = partialcorr (DN, 'Rows', 'complete'); %! assert_equal (rc(1,2), -0.8311, 1e-4); %! assert_equal (rc(4,5), -0.6163, 1e-4); ## 'pairwise' differs from 'complete' when entries use different columns %!test %! XN = X; %! XN(3,1) = NaN; %! rc = partialcorr (XN, Y, Z, 'Rows', 'complete'); %! [rp, pp] = partialcorr (XN, Y, Z, 'Rows', 'pairwise'); %! assert_equal (rc, [-0.0116; -0.5849], 1e-4); %! assert_equal (rp, [-0.0116; -0.5273], 1e-4); %! assert_equal (pp, [0.9764; 0.1173], 1e-4); ## a controlling variable that perfectly explains a column of X collapses ## its residual variance, so rho and pval are NaN %!test %! X = [1 2; 3 4; 5 5]; %! Z = [2; 4; 6]; %! [rho, pval] = partialcorr (X, Z); %! assert_equal (isnan (rho(1,:)), [true, true]); %! assert_equal (isnan (rho(:,1)), [true; true]); %! assert_equal (rho(2,2), 1); %! assert_equal (all (isnan (pval(:))), true); ## the tolerance keeps a residual that is small but genuinely resolvable %!test %! Z = [1; 2; 3; 4; 5; 6]; %! X = [Z + 1e-12 * [1; -2; 1; 0; 0; 0], [3; 1; 4; 1; 5; 9]]; %! rho = partialcorr (X, Z); %! assert_equal (isnan (rho(1,2)), false); ## and discards one that is below the precision the data is held in %!test %! Z = [1; 2; 3; 4; 5; 6]; %! X = [Z + 1e-16 * [1; -2; 1; 0; 0; 0], [3; 1; 4; 1; 5; 9]]; %! rho = partialcorr (X, Z); %! assert_equal (isnan (rho(1,2)), true); ## the tolerance follows the data, so a constant offset large enough to cost ## the column its low-order bits collapses the residual with it %!test %! Z = [1; 2; 3; 4; 5; 6]; %! v = 1e-3 * [1; -2; 1; 0; 0; 0]; %! Y = [3; 1; 4; 1; 5; 9]; %! assert_equal (isnan (partialcorr ([Z + v, Y], Z)(1,2)), false); %! assert_equal (isnan (partialcorr ([1e12 + Z + v, Y], Z)(1,2)), true); ## input validation %!error ... %! partialcorr (ones (5), ones (5), ones (5), ones (5)) %!error ... %! partialcorr (ones (10, 3), 'Type', 'Kendall') %!error partialcorr (ones (10, 3), 'Type', 'foo') %!error partialcorr (ones (10, 3), 'Rows', 'foo') %!error ... %! partialcorr (ones (10, 2), ones (8, 1), ones (10, 2)) statistics-release-1.9.2/inst/Descriptive_Statistics/partialcorri.m000066400000000000000000000161031524624707500256710ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program. If not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{rho} =} partialcorri (@var{y}, @var{x}) ## @deftypefnx {statistics} {@var{rho} =} partialcorri (@var{y}, @var{x}, @var{z}) ## @deftypefnx {statistics} {[@var{rho}, @var{pval}] =} partialcorri (@dots{}) ## @deftypefnx {statistics} {[@dots{}] =} partialcorri (@dots{}, @var{Name}, @var{Value}) ## ## Partial correlation of each response with each predictor, adjusting for the ## remaining predictors. ## ## @code{@var{rho} = partialcorri (@var{y}, @var{x})} returns the sample partial ## correlation coefficients between the columns of the @math{n}-by-@math{p} ## response matrix @var{y} and the columns of the @math{n}-by-@math{q} predictor ## matrix @var{x}. Element @code{@var{rho}(i,j)} is the partial correlation ## between @code{@var{y}(:,i)} and @code{@var{x}(:,j)}, adjusted for the other ## columns of @var{x} (that is, all columns of @var{x} except the @math{j}-th). ## @var{rho} is a @math{p}-by-@math{q} matrix. ## ## @code{@var{rho} = partialcorri (@var{y}, @var{x}, @var{z})} additionally ## controls for the variables in the @math{n}-by-@math{r} matrix @var{z}, so that ## @code{@var{rho}(i,j)} is adjusted for both the other columns of @var{x} and all ## columns of @var{z}. ## ## @code{[@var{rho}, @var{pval}] = partialcorri (@dots{})} also returns @var{pval}, ## a matrix of p-values for testing the hypothesis of no partial correlation ## against the alternative selected by @qcode{'Tail'}. ## ## The @qcode{'Type'}, @qcode{'Rows'}, and @qcode{'Tail'} @var{Name}/@var{Value} ## options are accepted with the same meaning as in @code{partialcorr}. ## @qcode{'Kendall'} is @emph{not} supported and raises an error, as in ## @sc{matlab}. ## ## @seealso{partialcorr, corr, corrcoef, tiedrank} ## @end deftypefn function [rho, pval] = partialcorri (varargin) if (nargin < 2) print_usage (); endif ## Separate the leading numeric matrices from the Name/Value options. nmat = 0; while (nmat < numel (varargin) && isnumeric (varargin{nmat+1})) nmat += 1; endwhile if (nmat < 2 || nmat > 3) error ("partialcorri: invalid number of input matrices."); endif Yv = varargin{1}; Xv = varargin{2}; if (nmat == 3) Zc = varargin{3}; else Zc = []; endif ## Parse and validate options (errors on 'Kendall' under this function's name). [Type, Rows, Tail, rem] = parsePairedArguments ({'Type', 'Rows', 'Tail'}, ... {'pearson', 'all', 'both'}, varargin(nmat+1:end)); if (! isempty (rem)) error ("partialcorri: unknown or unpaired optional argument."); endif if (! (ischar (Type) && ischar (Rows) && ischar (Tail))) error ("partialcorri: 'Type', 'Rows', and 'Tail' values must be strings."); endif Type = lower (Type); Rows = lower (Rows); Tail = lower (Tail); if (strcmp (Type, 'kendall')) error ("partialcorri: cannot compute Kendall's partial rank correlation."); elseif (! any (strcmp (Type, {'pearson', 'spearman'}))) error ("partialcorri: '%s' is not a valid 'Type'.", Type); endif if (! any (strcmp (Rows, {'all', 'complete', 'pairwise'}))) error ("partialcorri: '%s' is not a valid 'Rows' option.", Rows); endif if (! any (strcmp (Tail, {'both', 'right', 'left'}))) error ("partialcorri: '%s' is not a valid 'Tail' option.", Tail); endif if (! (isnumeric (Yv) && isreal (Yv) && ismatrix (Yv))) error ("partialcorri: Y must be a real numeric matrix."); endif if (! (isnumeric (Xv) && isreal (Xv) && ismatrix (Xv))) error ("partialcorri: X must be a real numeric matrix."); endif n = rows (Yv); if (rows (Xv) != n) error ("partialcorri: X and Y must have the same number of rows."); endif if (! isempty (Zc) && rows (Zc) != n) error ("partialcorri: Z must have the same number of rows as Y."); endif qx = columns (Xv); py = columns (Yv); rho = NaN (py, qx); pval = NaN (py, qx); ## Each predictor column in turn: adjust for the other predictors (and Z), then ## reuse partialcorr's cross form (Y vs the single predictor, controlling for ## the rest). This shares the ranking, NaN, and p-value machinery. for j = 1:qx C = [Xv(:, [1:j-1, j+1:qx]), Zc]; [rj, pj] = partialcorr (Yv, Xv(:,j), C, ... 'Type', Type, 'Rows', Rows, 'Tail', Tail); rho(:,j) = rj; pval(:,j) = pj; endfor endfunction %!demo %! ## Partial correlation of a response with each of two predictors, each %! ## adjusted for the other predictor. %! y = [-0.85; 0.33; 1.21; -0.19; 0.74; -1.44; 0.58; 0.02; 1.36]; %! x = [0.42 1.30; 1.15 -0.47; -0.98 0.55; 0.63 2.10; 1.88 -1.02; ... %! -0.31 0.86; 0.77 0.14; -1.52 1.77; 0.29 -0.63]; %! rho = partialcorri (y, x) ## shared test data %!shared D, X, Y, Z %! D = [ 0.42 1.30 -0.85 0.11 2.04 %! 1.15 -0.47 0.33 1.82 -0.62 %! -0.98 0.55 1.21 -0.34 0.77 %! 0.63 2.10 -0.19 0.48 -1.15 %! 1.88 -1.02 0.74 0.05 0.39 %! -0.31 0.86 -1.44 1.29 0.92 %! 0.77 0.14 0.58 -0.71 1.63 %! -1.52 1.77 0.02 0.94 -0.28 %! 0.29 -0.63 1.36 0.37 0.51 %! 2.01 0.48 -0.77 -1.08 0.14 %! -0.44 1.05 0.91 0.66 -0.83 %! 0.90 -0.29 -0.36 1.47 1.22]; %! X = D(:,1:2); %! Y = D(:,3); %! Z = D(:,4:5); ## single response, two predictors, controlling for Z, with p-values %!test %! [r, p] = partialcorri (Y, X, Z); %! assert_equal (r, [-0.7539, -0.8212], 1e-4); %! assert_equal (p, [0.0189, 0.0066], 1e-4); ## matches the corresponding entries of the symmetric partialcorr matrix %!test %! rho = partialcorr (D); %! assert_equal (partialcorri (Y, X, Z), rho(3,1:2), 1e-12); ## Spearman rank partial correlation %!test %! r = partialcorri (Y, X, Z, 'Type', 'Spearman'); %! assert_equal (r, [-0.8381, -0.8677], 1e-4); ## multiple responses, with and without an extra control set %!test %! r1 = partialcorri ([D(:,3), D(:,4)], X, D(:,5)); %! assert_equal (r1, [-0.5444, -0.6714; -0.3517, -0.2830], 1e-4); %! r2 = partialcorri ([D(:,3), D(:,4)], X); %! assert_equal (r2, [-0.4740, -0.5784; -0.3098, -0.1934], 1e-4); ## input validation %!error ... %! partialcorri (ones (5), ones (5), ones (5), ones (5)) %!error ... %! partialcorri (ones (10, 1), ones (10, 2), 'Type', 'Kendall') %!error ... %! partialcorri (ones (10, 1), ones (8, 2)) statistics-release-1.9.2/inst/Descriptive_Statistics/private/000077500000000000000000000000001524624707500244715ustar00rootroot00000000000000statistics-release-1.9.2/inst/Descriptive_Statistics/private/kernelcdf.m000066400000000000000000000040371524624707500266100ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{c} =} kernelcdf (@var{z}, @var{kernel}) ## ## Cumulative distribution function of the unit-variance smoothing kernel, ## evaluated at the standardized distances @var{z}. For a named kernel this is ## the canonical cdf evaluated at @math{s*z}; @var{kernel} may instead be a ## function handle, whose cumulative is computed numerically over a fine grid. ## Internal helper shared by @code{ksdensity} and @code{mvksdensity}; not ## intended to be called directly. ## ## @end deftypefn function c = kernelcdf (z, kernel) if (is_function_handle (kernel)) ## Numeric cumulative for a custom kernel over a fine grid. g = linspace (-1e3, 1e3, 200001)'; C = cumtrapz (g, kernel (g)); C = C / C(end); c = reshape (interp1 (g, C, z(:), 'linear', 'extrap'), size (z)); return; endif z = kernelscale (kernel) * z; switch (kernel) case 'normal' c = 0.5 * erfc (-z / sqrt (2)); case 'box' c = min (max ((z + 1) / 2, 0), 1); case 'triangle' zc = max (min (z, 1), -1); c = (zc < 0) .* (0.5 * (zc + 1) .^ 2) ... + (zc >= 0) .* (0.5 + zc - 0.5 * zc .^ 2); case 'epanechnikov' zc = max (min (z, 1), -1); c = 0.75 * zc - 0.25 * zc .^ 3 + 0.5; endswitch endfunction statistics-release-1.9.2/inst/Descriptive_Statistics/private/kernelpdf.m000066400000000000000000000033361524624707500266260ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{k} =} kernelpdf (@var{z}, @var{kernel}) ## ## Unit-variance smoothing kernel density @math{K(z)} evaluated at the ## standardized distances @var{z}. For a named compact kernel @math{K(z) = ## s * K0(s*z)} with @math{K0} the canonical form and @math{s} its standard ## deviation; @var{kernel} may instead be a function handle, which is applied as ## supplied. Internal helper shared by @code{ksdensity} and @code{mvksdensity}; ## not intended to be called directly. ## ## @end deftypefn function k = kernelpdf (z, kernel) if (is_function_handle (kernel)) k = kernel (z); return; endif s = kernelscale (kernel); z = s * z; switch (kernel) case 'normal' k = exp (-0.5 * z .^ 2) / sqrt (2 * pi); case 'box' k = 0.5 * (abs (z) <= 1); case 'triangle' k = max (1 - abs (z), 0); case 'epanechnikov' k = 0.75 * max (1 - z .^ 2, 0); endswitch k = s * k; endfunction statistics-release-1.9.2/inst/Descriptive_Statistics/private/kernelscale.m000066400000000000000000000027331524624707500271440ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{s} =} kernelscale (@var{kernel}) ## ## Standardized-distance scale of a named smoothing kernel, i.e. the standard ## deviation of its canonical form. The compact kernels are rescaled by this ## factor so that each has unit variance and the bandwidth is comparable across ## kernels (as in MATLAB). Internal helper shared by @code{ksdensity} and ## @code{mvksdensity}; not intended to be called directly. ## ## @end deftypefn function s = kernelscale (kernel) switch (kernel) case 'box' s = 1 / sqrt (3); case 'triangle' s = 1 / sqrt (6); case 'epanechnikov' s = 1 / sqrt (5); otherwise s = 1; endswitch endfunction statistics-release-1.9.2/inst/Descriptive_Statistics/tabulate.m000066400000000000000000000360461524624707500250070ustar00rootroot00000000000000## Copyright (C) 2003 Alberto Terruzzi ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} tabulate (@var{x}) ## @deftypefnx {statistics} {@var{tbl} =} tabulate (@var{x}) ## ## Create a frequency table of unique values in vector @var{x}. ## ## @code{tabulate (x)} displays a frequency table of the data in the vector ## @var{x}. The input @var{x} can be a numeric vector, a logical vector, a ## character matrix, a cell vector of character vectors, a categorical vector, ## or a string vector. ## ## The table displays the value, the number of instances (count), and the ## percentage of that value in @var{x}. If no output argument is requested, ## the table is displayed in the command window. ## ## @code{@var{tbl} = tabulate (@var{x})} returns the frequency table, ## @var{tbl}, as a numeric matrix when @var{x} is numeric and as a cell array ## otherwise. ## ## If @var{x} is numeric, any missing values (@qcode{NaNs}) are ignored. ## Similarly, undefined elements in categorical arrays and missing elements in ## string arrays are ignored. ## ## If all the elements of @var{x} are positive integers, then the frequency ## table includes 0 counts for the integers between 1 and @qcode{max (@var{x})} ## that do not appear in @var{x}. ## ## For categorical arrays, the frequency table includes 0 counts for any ## categories that are defined but do not appear in @var{x}. ## ## Missing values are not tabulated. The percentage column is ## @qcode{count / total * 100} taken literally, so a category with no ## observations out of none at all is @qcode{NaN} rather than zero: there is no ## total to take a proportion of. A @qcode{categorical} carries its categories ## independently of its data, so an all-undefined one still tabulates every ## category with a zero count; a string array has no levels beyond those its ## data carries, so an all-missing one tabulates to an empty table. MATLAB ## agrees on the categorical case but returns a malformed @math{1}-by-@math{2} ## cell for the all-missing string, lacking the label column its own ## documentation describes, while returning a well-formed ## @math{0}-by-@math{3} for an empty string array. This implementation returns ## the empty table in both. ## ## @seealso{bar, pareto} ## @end deftypefn function tbl = tabulate (x) ## Check input for being numeric, logical, categorical, or text ## All types but char array must be vectors if (ischar (x) && ! ismatrix (x)) error ("tabulate: char array input must be a matrix."); elseif (! isvector (x) && ! isempty (x) && ! ischar (x)) error ("tabulate: input must be a vector except for char array."); elseif (! (isnumeric (x) || islogical (x) || iscellstr (x) || iscategorical (x) || isa (x, 'string')) && ! ischar (x)) error (strcat ("tabulate: X must be numeric, logical, categorical,", ... " cellstring, string, or a char array.")); endif ## Ensure vector input if (! ischar (x)) x = x(:); endif if (iscategorical (x)) ## For categorical, we report ALL categories, even if count is 0 vals = categories (x); nc = length (vals); ## Count occurrences xi = double (x); ## Filter out undefined valid_mask = ! isnan (xi) & (xi >= 1) & (xi <= nc); xi = xi(valid_mask); if (isempty (xi)) counts = zeros (nc, 1); else counts = accumarray (xi, 1, [nc, 1]); endif ## The percentage is computed literally, so a category with no ## observations out of none at all is NaN rather than zero: there is no ## total to take a proportion of. MATLAB does the same. total = sum (counts); percents = 100 * counts ./ total; ## Output format: Cell array out = cell (length (vals), 3); out(:,1) = vals; out(:,2) = num2cell (counts); out(:,3) = num2cell (percents); elseif (isa (x, 'string')) ## Handle string arrays x(ismissing (x)) = []; if (isempty (x)) out = cell (0, 3); else ## Convert to cellstr and use grp2idx which is robust [idx, vals] = grp2idx (cellstr (x)); counts = accumarray (idx, 1); total = sum (counts); percents = 100 * counts ./ total; out = cell (length (vals), 3); out(:,1) = vals; out(:,2) = num2cell (counts); out(:,3) = num2cell (percents); endif elseif (islogical (x)) ## Handle logical arrays [vals, ~, idx] = unique (x); if (isempty (x)) counts = []; percents = []; else counts = accumarray (idx, 1); total = sum (counts); percents = 100 * counts ./ total; endif vals_cell = cellstr (num2str (double (vals))); out = cell (length (vals), 3); out(:, 1) = vals_cell; out(:, 2) = num2cell (counts); out(:, 3) = num2cell (percents); elseif (isnumeric (x)) ## Handle numeric ## Remove missing values (NaNs) if numeric x(isnan (x)) = []; ## Handle positive integers separately if (! isempty (x) && all (x == fix (x)) && all (x > 0)) max_val = max (x); vals = (1:max_val)'; [counts, ~] = hist (x, vals); counts = counts(:); else [vals, ~, idx] = unique (x); if (isempty (x)) counts = []; else counts = accumarray (idx, 1); endif endif if (isempty (counts)) percents = []; else percents = 100 * counts ./ sum (counts); endif ## Output format: Numeric Matrix out = [vals, counts, percents]; else ## Handle char and cellstr if (ischar (x)) x = cellstr (x); endif [idx, vals] = grp2idx (x); if (isempty (idx)) counts = []; else counts = accumarray (idx, 1); endif if (isempty (counts)) percents = []; else percents = 100 * counts ./ sum (counts); endif out = cell (length (vals), 3); out(:,1) = vals; out(:,2) = num2cell (counts); out(:,3) = num2cell (percents); endif if (nargout == 0) ## Use table for display if no output requested if (isempty (out)) ## Handle empty case disp (' Value Count Percent'); return; endif if (isnumeric (out)) ## Numeric matrix case Value = out(:,1); Count = out(:,2); Percent = out(:,3); else ## Cell array case Value = out(:,1); Count = cell2mat (out(:,2)); Percent = cell2mat (out(:,3)); endif t = table (Value, Count, Percent, 'VariableNames', {'Value', 'Count', ... 'Percent'}); disp (t); else tbl = out; endif endfunction %!demo %! ## Generate a frequency table for a vector of data in a cell array %! load patients %! %! ## Display the first seven entries of the Gender variable %! gender = Gender(1:7) %! %! ## Compute the frequency table that shows the number and %! ## percentage of Male and Female patients %! tabulate (Gender) %!demo %! ## Create a frequency table for a vector of positive integers %! load patients %! %! ## Display the first seven entries of the Gender variable %! height = Height(1:7) %! %! ## Create a frequency table that shows, in its second and third columns, %! ## the number and percentage of patients with a particular height. %! table = tabulate (Height); %! %! ## Display the first and last seven entries of the frequency table %! first = table(1:7,:) %! %! last = table(end-6:end,:) %!demo %! ## Create a frequency table from a character array %! load carsmall %! %! ## Tabulate the data in the Origin variable, which shows the %! ## country of origin of each car in the data set %! tabulate (Origin) %!demo %! ## Create a frequency table from a numeric vector with NaN values %! load carsmall %! %! ## The carsmall dataset contains measurements of 100 cars %! total_cars = length (MPG) %! ## For six cars, the MPG value is missing %! missingMPG = length (MPG(isnan (MPG))) %! %! ## Create a frequency table using MPG %! tabulate (MPG) %! table = tabulate (MPG); %! %! ## Only 94 cars were used %! valid_cars = sum (table(:,2)) %!test %! load patients %! table = tabulate (Gender); %! assert_equal (table{1,1}, "Male"); %! assert_equal (table{2,1}, "Female"); %! assert_equal (table{1,2}, 47); %! assert_equal (table{2,2}, 53); %!test %! load patients %! table = tabulate (Height); %! assert_equal (table(end-4,:), [68, 15, 15]); %! assert_equal (table(end-3,:), [69, 8, 8]); %! assert_equal (table(end-2,:), [70, 11, 11]); %! assert_equal (table(end-1,:), [71, 10, 10]); %! assert_equal (table(end,:), [72, 4, 4]); %!test %! ## Test numeric vector including NaNs %! x = [1; 1; 2; 3; 1; NaN; 2]; %! tbl = tabulate (x); %! assert_equal (isnumeric (tbl), true); %! assert_equal (size (tbl), [3, 3]); %! assert_equal (tbl(:,1), [1; 2; 3]); %! assert_equal (tbl(:,2), [3; 2; 1]); %! assert_equal (tbl(:,3), [50; 33.3333; 16.6667], 3e-4); %!test %! ## Test positive integers with gaps %! x = [1; 3; 3]; %! tbl = tabulate (x); %! assert_equal (isnumeric (tbl), true); %! assert_equal (size (tbl), [3, 3]); %! assert_equal (tbl(:,1), [1; 2; 3]); %! assert_equal (tbl(:,2), [1; 0; 2]); %! assert_equal (tbl(:,3), [33.3333; 0; 66.6667], 3e-4); %!test %! ## Test logical inputs (should return cell array with '0'/'1') %! x = [true; false; true; true]; %! tbl = tabulate (x); %! assert_equal (iscell (tbl), true); %! assert_equal (size (tbl), [2, 3]); %! assert_equal (tbl(:,1), {'0'; '1'}); %! assert_equal ([tbl{:,2}]', [1; 3]); %! assert_equal ([tbl{:,3}]', [25; 75]); %!test %! ## Test character array %! x = ['a'; 'b'; 'a']; %! tbl = tabulate (x); %! assert_equal (iscell (tbl), true); %! assert_equal (size (tbl), [2, 3]); %! assert_equal (tbl(:,1), {'a'; 'b'}); %! assert_equal ([tbl{:,2}]', [2; 1]); %!test %! ## Test cell array of character vectors %! x = {'a', 'b', 'a'}; %! tbl = tabulate (x); %! assert_equal (iscell (tbl), true); %! assert_equal (size (tbl), [2, 3]); %! assert_equal (tbl(:,1), {'a'; 'b'}); %! assert_equal ([tbl{:,2}]', [2; 1]); %!test %! ## Test string array with missing values %! x = string ({'a', 'b', 'a'}); %! x(4) = missing; %! tbl = tabulate (x); %! assert_equal (iscell (tbl), true); %! assert_equal (size (tbl), [2, 3]); %! assert_equal (tbl(:,1), {'a'; 'b'}); %! assert_equal ([tbl{:,2}]', [2; 1]); %!test %! ## Test categorical array with undefined values and vacuous levels %! x = categorical ({'a', 'a', 'b'}, {'a', 'b', 'c'}); %! tbl = tabulate (x); %! assert_equal (iscell (tbl), true); %! assert_equal (size (tbl), [3, 3]); %! assert_equal (tbl(:,1), {'a'; 'b'; 'c'}); %! assert_equal ([tbl{:,2}]', [2; 1; 0]); %! assert_equal ([tbl{:,3}]', [66.6667; 33.3333; 0], 1e-3); %!test %! ## Test empty input %! tbl = tabulate ([]); %! assert_equal (isempty (tbl), true); %!test %! ## fisheriris (Categorical/CellStr) %! load fisheriris; %! tbl = tabulate (species); %! assert_equal (size (tbl), [3, 3]); %! assert_equal (tbl(:,1), {'setosa'; 'versicolor'; 'virginica'}); %! assert_equal ([tbl{:,2}]', [50; 50; 50]); %! assert_equal ([tbl{:,3}]', [33.3333; 33.3333; 33.3333], 1e-4); %!test %! ## carsmall (Char/CellStr) %! load carsmall; %! tbl = tabulate (Origin); %! origins = tbl(:,1); %! counts = [tbl{:,2}]; %! assert_equal (counts(strcmp (origins, 'USA')), 69); %! assert_equal (counts(strcmp (origins, 'Japan')), 15); %! assert_equal (counts(strcmp (origins, 'Germany')), 9); %! assert_equal (counts(strcmp (origins, 'France')), 4); %! assert_equal (counts(strcmp (origins, 'Sweden')), 2); %! assert_equal (counts(strcmp (origins, 'Italy')), 1); %!test %! ## patients (Logical) %! load patients; %! tbl = tabulate (Smoker); %! assert_equal (size (tbl), [2, 3]); %! assert_equal (tbl(:,1), {'0'; '1'}); %! assert_equal ([tbl{:,2}]', [66; 34]); %!test %! ## patients (String) %! load patients; %! tbl = tabulate (Gender); %! vals = tbl(:,1); %! counts = [tbl{:,2}]; %! assert_equal (counts(strcmp (vals, 'Male')), 47); %! assert_equal (counts(strcmp (vals, 'Female')), 53); ## Test categorical with all undefined values. The categories survive with ## zero counts, and the percentages are NaN: there is no total to take a ## proportion of. Values are R2024a's, measured 2026-08-17. %!test %! x = categorical ({'a','b','c'}); %! x(:) = categorical (missing); %! tbl = tabulate (x); %! assert_equal (iscell (tbl), true); %! assert_equal (size (tbl), [3, 3]); %! assert_equal ([tbl{:,2}]', [0; 0; 0]); %! assert_equal ([tbl{:,3}]', [NaN; NaN; NaN]); %!test %! ## a zero count against a nonzero total is 0 per cent, not NaN %! x = categorical ({'a','b','c','a'}); %! x(2) = categorical (missing); %! tbl = tabulate (x); %! assert_equal ([tbl{:,2}]', [2; 0; 1]); %! assert_equal ([tbl{:,3}]', [200/3; 0; 100/3], 1e-12); ## Test categorical with defined categories but no data %!test %! x = categorical ({}, {'low','med','high'}); %! tbl = tabulate (x); %! assert_equal (iscell (tbl), true); %! assert_equal ([tbl{:,2}]', [0; 0; 0]); %! assert_equal ([tbl{:,3}]', [NaN; NaN; NaN]); ## Test string array with all missing values. A string array has no levels ## beyond those its data carries, so an all-missing one tabulates to nothing. ## MATLAB returns a malformed 1-by-2 cell here, without the label column its ## own documentation describes, and returns a well-formed 0-by-3 for an empty ## string array; this is a deliberate divergence. Measured 2026-08-17. %!test %! x = string ({'a','b'}); %! x(:) = missing; %! tbl = tabulate (x); %! assert_equal (iscell (tbl), true); %! assert_equal (isempty (tbl), true); ## Test 2D character matrices. %!test %! x = ['yes'; 'no'; 'yes']; %! tbl = tabulate (x); %! assert_equal (iscell (tbl), true); %! assert_equal (size (tbl), [2, 3]); %! assert_equal (tbl(:,1), {'yes'; 'no'}); %! assert_equal ([tbl{:,2}]', [2; 1]); %! assert_equal ([tbl{:,3}]', [66.6667; 33.3333], 1e-4); ## Test input validation %!error ... %! tabulate (repmat ('a', 3, 3, 3)) %!error ... %! tabulate ([3, 3; 3, 3]) %!error ... %! tabulate ({'3', '3'; '3', '3'}) %!error ... %! tabulate ([true, true; false, true]) %!error ... %! tabulate (categorical ([true, true; false, true])) %!error ... %! tabulate (string ({'a', 'b'; 'a', 'c'})) %!error ... %! tabulate ({3, 3, 3, 3}) statistics-release-1.9.2/inst/Descriptive_Statistics/trimmean.m000066400000000000000000000317721524624707500250230ustar00rootroot00000000000000## Copyright (C) 2001 Paul Kienzle ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{m} =} trimmean (@var{x}, @var{p}) ## @deftypefnx {statistics} {@var{m} =} trimmean (@var{x}, @var{p}, @var{flag}) ## @deftypefnx {statistics} {@var{m} =} trimmean (@dots{}, @qcode{'all'}) ## @deftypefnx {statistics} {@var{m} =} trimmean (@dots{}, @var{dim}) ## @deftypefnx {statistics} {@var{m} =} trimmean (@dots{}, @var{vecdim}) ## ## Compute the trimmed mean. ## ## The trimmed mean of @var{x} is defined as the mean of @var{x} excluding the ## highest and lowest @math{k} data values of @var{x}, calculated as ## @qcode{@var{k} = n * (@var{p} / 100) / 2)}, where @var{n} is the sample size. ## ## @code{@var{m} = trimmean (@var{x}, @var{p})} returns the mean of @var{x} ## after removing the outliers in @var{x} defined by @var{p} percent. ## @itemize ## @item If @var{x} is a vector, then @code{trimmean (@var{x}, @var{p})} is the ## mean of all the values of @var{x}, computed after removing the outliers. ## @item If @var{x} is a matrix, then @code{trimmean (@var{x}, @var{p})} is a ## row vector of column means, computed after removing the outliers. ## @item If @var{x} is a multidimensional array, then @code{trimmean} operates ## along the first nonsingleton dimension of @var{x}. ## @end itemize ## ## To specify the operating dimension(s) when @var{x} is a matrix or a ## multidimensional array, use the @var{dim} or @var{vecdim} input argument. ## ## @code{trimmean} treats @qcode{NaN} values in @var{x} as missing values and ## removes them. ## ## @code{@var{m} = trimmean (@var{x}, @var{p}, @var{flag})} specifies how to ## trim when @math{k}, i.e. half the number of outliers, is not an integer. ## @var{flag} can be specified as one of the following values: ## @multitable @columnfractions 0.2 0.75 ## @headitem Value @tab Description ## @item @qcode{'round'} @tab Round @math{k} to the nearest integer. This ## is the default. ## @item @qcode{'floor'} @tab Round @math{k} down to the next smaller ## integer. ## @item @qcode{'weighted'} @tab If @math{k = i + f}, where @math{i} is an ## integer and @math{f} is a fraction, compute a weighted mean with weight ## @math{(1 - f)} for the @math{(i + 1)}-th and @math{(n - i)}-th values, and ## full weight for the values between them. ## @end multitable ## ## @code{@var{m} = trimmean (@dots{}, @qcode{'all'})} returns the trimmed mean ## of all the values in @var{x} using any of the input argument combinations in ## the previous syntaxes. ## ## @code{@var{m} = trimmean (@dots{}, @var{dim})} returns the trimmed mean along ## the operating dimension @var{dim} specified as a positive integer scalar. If ## not specified, then the default value is the first nonsingleton dimension of ## @var{x}, i.e. whose size does not equal 1. If @var{dim} is greater than ## @qcode{ndims (@var{X})} or if @qcode{size (@var{x}, @var{dim})} is 1, then ## @code{trimmean} returns @var{x}. ## ## @code{@var{m} = trimmean (@dots{}, @var{vecdim})} returns the trimmed mean ## over the dimensions specified in the vector @var{vecdim}. For example, if ## @var{x} is a 2-by-3-by-4 array, then @code{mean (@var{x}, [1 2])} returns a ## 1-by-1-by-4 array. Each element of the output array is the mean of the ## elements on the corresponding page of @var{x}. If @var{vecdim} indexes all ## dimensions of @var{x}, then it is equivalent to @code{mean (@var{x}, "all")}. ## Any dimension in @var{vecdim} greater than @code{ndims (@var{x})} is ignored. ## ## @seealso{mean} ## @end deftypefn function m = trimmean (x, p, varargin) if (nargin < 2 || nargin > 4) print_usage; endif ## A non-scalar percentage used to slip through, because each half of the ## range test was then itself non-scalar and neither branch was taken: the ## trimming ran on a colon built from a vector and returned an untrimmed ## mean behind a warning. if (! isscalar (p) || ! isreal (p) || p < 0 || p >= 100) error ("trimmean: PERCENT must be a real scalar in the range [0, 100)."); endif ## Parse extra arguments if (nargin < 3) flag = []; dim = []; elseif (nargin < 4) if (ischar (varargin{1}) && ! strcmpi (varargin{1}, 'all')) flag = varargin{1}; dim = []; elseif (isnumeric (varargin{1}) || strcmpi (varargin{1}, 'all')) flag = []; dim = varargin{1}; endif else flag = varargin{1}; dim = varargin{2}; endif ## Get size of X szx = size (x); ndx = ndims (x); ## Handle special case X = [] if (isequal (szx, [0, 0])) m = NaN (class (x)); return endif ## Check FLAG if (isempty (flag)) flag = 'round'; endif if (! any (strcmpi (flag, {'round', 'floor', 'weighted'}))) error ("trimmean: invalid FLAG argument."); endif ## Check DIM if (isempty (dim)) (dim = find (szx != 1, 1)) || (dim = 1); endif if (strcmpi (dim, 'all')) x = x(:); dim = 1; szx = size (x); ndx = 2; endif if (! (isvector (dim) && all (dim > 0) && all (rem (dim, 1) == 0))) error ("trimmean: DIM must be a positive integer scalar or vector."); endif vecdim_flag = false; if (numel (dim) > 1) dim = sort (dim); if (! all (diff (dim))) error ("trimmean: VECDIM must contain non-repeating positive integers."); endif vecdim_flag = true; endif ## If DIM is a scalar greater than ndims, return X if (isscalar (dim) && dim > ndx) m = x; return endif ## If DIM is a scalar and size (x, dim) == 1, return X if (isscalar (dim) && size (x, dim) == 1) m = x; return endif ## If DIM is a vector, ignore any value > ndims (x) if (numel (dim) > 1) dim(dim > ndx) = []; endif ## Permute dim to simplify all operations along dim1. At func. end ipermute. if (numel (dim) > 1 || dim != 1) perm = 1:ndx; if (! vecdim_flag) ## Move dim to dim 1 perm([1, dim]) = [dim, 1]; x = permute (x, perm); szx([1, dim]) = szx([dim, 1]); dim = 1; else ## Move vecdims to front perm(dim) = []; perm = [dim, perm]; x = permute (x, perm); ## Reshape all vecdims into dim1 num_dim = prod (szx(dim)); szx(dim) = []; szx = [num_dim, ones(1, numel(dim)-1), szx]; x = reshape (x, szx); dim = 1; endif perm_flag = true; else perm_flag = false; endif ## Create output matrix sizem = size (x); sizem(dim) = 1; ## Sort X along 1st dimensions x = sort (x, 1); ## No missing data, all columns have the same length if (! any (isnan (x(:)))) if (isempty (x)) n = 0; else n = size (x, 1); endif m = trim (x, n, p, flag, sizem); m = reshape (m, sizem); ## With missing data, each column is computed separately else m = NaN (sizem, class (x)); for j = 1:prod (sizem(2:end)) n = find (! isnan (x(:,j)), 1, 'last'); m(j) = trim (x(:,j), n, p, flag, [1, 1]); endfor endif ## Inverse permute back to correct dimensions (if necessary) if (perm_flag) m = ipermute (m, perm); endif endfunction ## Help function for handling different flags function m = trim (x, n, p, flag, sizem) switch (lower (flag)) case 'round' k = n * p / 200; k0 = round (k - eps (k)); if (! isempty (n) && n > 0 && k0 < n / 2) m = mean (x((k0+1):(n-k0),:), 1); else m = NaN (sizem, class (x)); endif case 'floor' k0 = floor (n * p / 200); if (! isempty (n) && n > 0 && k0 < n / 2) m = mean (x((k0+1):(n-k0),:), 1); else m = NaN (sizem, class (x)); endif case 'weighted' k = n * p / 200; k0 = floor (k); fr = 1 + k0 - k; if (! isempty (n) && n > 0 && (k0 < n / 2 || fr > 0)) m = (sum (x((k0+2):(n-k0-1),:),1) + fr * x(k0+1,:) + fr * x(n-k0,:)) ... / (max (0, n - 2 * k0 - 2) + 2 * fr); else m = NaN (sizem, class (x)); endif endswitch endfunction ## Test output %!test %! x = reshape (1:40, [5, 4, 2]); %! x([3, 37]) = -100; %! assert_equal (trimmean (x, 10, 'all'), 19.4722, 1e-4); %!test %! x = reshape (1:40, [5, 4, 2]); %! x([3, 37]) = -100; %! out = trimmean (x, 10, [1, 2]); %! assert_equal (out(1,1,1), 10.3889, 1e-4); %! assert_equal (out(1,1,2), 29.6111, 1e-4); %!test %! x = reshape (1:40, [5, 4, 2]); %! x([3, 37]) = -100; %! x([4, 38]) = NaN; %! assert_equal (trimmean (x, 10, 'all'), 19.3824, 1e-4); %!test %! x = reshape (1:40, [5, 4, 2]); %! x([3, 37]) = -100; %! out = trimmean (x, 10, 1); %! assert_equal (out(:,:,1), [-17.6, 8, 13, 18]); %! assert_equal (out(:,:,2), [23, 28, 33, 10.6]); %!test %! x = reshape (1:40, [5, 4, 2]); %! x([3, 37]) = -100; %! x([4, 38]) = NaN; %! out = trimmean (x, 10, 1); %! assert_equal (out(:,:,1), [-23, 8, 13, 18]); %! assert_equal (out(:,:,2), [23, 28, 33, 3.75]); %!test %! x = reshape (1:40, [5, 4, 2]); %! x([3, 37]) = -100; %! out = trimmean (x, 10, 2); %! assert_equal (out(:,:,1), [8.5; 9.5; -15.25; 11.5; 12.5]); %! assert_equal (out(:,:,2), [28.5; -4.75; 30.5; 31.5; 32.5]); %!test %! x = reshape (1:40, [5, 4, 2]); %! x([3, 37]) = -100; %! x([4, 38]) = NaN; %! out = trimmean (x, 10, 2); %! assert_equal (out(:,:,1), [8.5; 9.5; -15.25; 14; 12.5]); %! assert_equal (out(:,:,2), [28.5; -4.75; 28; 31.5; 32.5]); %!test %! x = reshape (1:40, [5, 4, 2]); %! x([3, 37]) = -100; %! out = trimmean (x, 10, [1, 2, 3]); %! assert_equal (out, trimmean (x, 10, 'all')); ## Test N-D array with NaNs %!test %! x = reshape (1:40, [5, 4, 2]); %! x([3, 37]) = -100; %! x([4, 38]) = NaN; %! out = trimmean (x, 10, [1, 2]); %! assert_equal (out(1,1,1), 10.7647, 1e-4); %! assert_equal (out(1,1,2), 29.1176, 1e-4); %!test %! x = reshape (1:40, [5, 4, 2]); %! x([3, 37]) = -100; %! x([4, 38]) = NaN; %! out = trimmean (x, 10, [1, 3]); %! assert_equal (out, [2.5556, 18, 23, 11.6667], 1e-4); %!test %! x = reshape (1:40, [5, 4, 2]); %! x([3, 37]) = -100; %! x([4, 38]) = NaN; %! out = trimmean (x, 10, [2, 3]); %! assert_equal (out, [18.5; 2.3750; 3.2857; 24; 22.5], 1e-4); %!test %! x = reshape (1:40, [5, 4, 2]); %! x([3, 37]) = -100; %! x([4, 38]) = NaN; %! out = trimmean (x, 10, [1, 2, 3]); %! assert_equal (out, trimmean (x, 10, 'all')); %!test %! x = reshape (1:40, [5, 4, 2]); %! x([3, 37]) = -100; %! x([4, 38]) = NaN; %! out = trimmean (x, 10, [2, 3, 5]); %! assert_equal (out, [18.5; 2.3750; 3.2857; 24; 22.5], 1e-4); ## Test bug reported in PR #381 %!assert_equal (trimmean ([1, 2, 3, 4, 5], 40), 3) ## Test special cases %!assert_equal (trimmean (reshape (1:40, [5, 4, 2]), 10, 4), reshape (1:40, [5, 4, 2])) %!assert_equal (trimmean ([], 10), NaN) %!assert_equal (trimmean ([1;2;3;4;5], 10, 2), [1;2;3;4;5]) %!test %! ## Row vector with explicit dimension 1 %! assert_equal (trimmean ([1, 2, 3], 10, 1), [1, 2, 3]); %!test %! ## Row vector with explicit dimension 2 %! assert_equal (trimmean ([1, 2, 3], 10, 2), 2); %!test %! ## Empty array with non-operating dimension preserved (dim=1) %! assert_equal (trimmean (zeros (0, 5), 10, 1), NaN (1, 5)); %!test %! ## Empty array with non-operating dimension preserved (dim=2) %! assert_equal (trimmean (zeros (2, 0), 10, 2), NaN (2, 1)); ## Test input validation %!error trimmean (1) %!error trimmean (1,2,3,4,5) %!error ... %! trimmean ([1 2 3 4], -10) %!error ... %! trimmean ([1 2 3 4], 100) %!error ... %! trimmean ([1 2 3 4], [10, 20]) %!error ... %! trimmean ([1 2 3 4], []) %!error ... %! trimmean ([1 2 3 4], 10 + 2i) %!error trimmean ([1 2 3 4], 10, 'flag') %!error trimmean ([1 2 3 4], 10, 'flag', 1) %!error ... %! trimmean ([1 2 3 4], 10, -1) %!error ... %! trimmean ([1 2 3 4], 10, 'floor', -1) %!error ... %! trimmean (reshape (1:40, [5, 4, 2]), 10, [-1, 2]) %!error ... %! trimmean (reshape (1:40, [5, 4, 2]), 10, [1, 2, 2]) statistics-release-1.9.2/inst/Dimensionality_Reduction/000077500000000000000000000000001524624707500233305ustar00rootroot00000000000000statistics-release-1.9.2/inst/Dimensionality_Reduction/ReconstructionICA.m000066400000000000000000000431061524624707500270500ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftp {statistics} {} ReconstructionICA ## ## Reconstruction independent component analysis (RICA) feature-extraction model. ## ## A @qcode{ReconstructionICA} object stores the transformation learned by ## @code{rica} for extracting features from data. Create one with @code{rica}; ## apply it to data with the @code{transform} method. ## ## @seealso{rica, sparsefilt} ## @end deftp classdef ReconstructionICA properties (SetAccess = protected) ## -*- texinfo -*- ## @deftp {ReconstructionICA} {property} ModelParameters ## ## Options the fit used ## ## A scalar structure holding the options the fit ran with: ## @qcode{IterationLimit}, @qcode{Lambda}, @qcode{Standardize}, ## @qcode{ContrastFcn}, @qcode{InitialTransformWeights}, ## @qcode{GradientTolerance}, @qcode{StepTolerance}, @qcode{Solver} and ## @qcode{NonGaussianityIndicator}. ## This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {ReconstructionICA} {property} NumPredictors ## ## Number of input predictors ## ## A positive integer @math{P}, the number of columns of the training ## data. This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {ReconstructionICA} {property} NumLearnedFeatures ## ## Number of learned features ## ## A positive integer @math{Q}, the number of features the learned ## transformation produces. This property is read-only. ## ## @end deftp NumLearnedFeatures = []; ## -*- texinfo -*- ## @deftp {ReconstructionICA} {property} Mu ## ## Predictor means used when standardizing ## ## A column vector with one entry per predictor, the mean of each ## column of the training data. It is empty unless @qcode{'Standardize'} ## was true. This property is read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {ReconstructionICA} {property} Sigma ## ## Predictor standard deviations used when standardizing ## ## A column vector with one entry per predictor, the standard deviation ## of each column of the training data. It is empty unless ## @qcode{'Standardize'} was true. This property is read-only. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {ReconstructionICA} {property} FitInfo ## ## History of the fit ## ## A scalar structure with the fields @qcode{Iteration} and ## @qcode{Objective}, both column vectors of the same length. ## @qcode{Iteration} counts from zero and @qcode{Objective(1)} is the ## objective at the starting weights, so the last entry of each is the ## solution the fit returned. This property is read-only. ## ## The trajectory is this implementation's own. The default ## @qcode{'quasinewton'} solver minimises through Octave's @code{fminunc}, ## and @qcode{'Solver', 'lbfgs'} selects the limited-memory BFGS solver ## MATLAB uses. Either way the steps taken from the same starting weights ## differ from MATLAB's, so the length of the history and the iteration ## counts differ, and on an objective this far from convex the optimum ## reached need not be MATLAB's either. ## ## @end deftp FitInfo = []; ## -*- texinfo -*- ## @deftp {ReconstructionICA} {property} TransformWeights ## ## Learned feature transformation weights ## ## A @math{P}-by-@math{Q} matrix of learned weights, its columns of unit ## length. The @code{transform} method applies it to data. This property ## is read-only. ## ## @end deftp TransformWeights = []; ## -*- texinfo -*- ## @deftp {ReconstructionICA} {property} InitialTransformWeights ## ## Starting feature transformation weights ## ## A @math{P}-by-@math{Q} matrix, the weights the fit started from. It ## is the matrix given as @qcode{'InitialTransformWeights'} when one was ## given, and the random start the fit drew otherwise. This property is ## read-only. ## ## @end deftp InitialTransformWeights = []; ## -*- texinfo -*- ## @deftp {ReconstructionICA} {property} NonGaussianityIndicator ## ## Non-Gaussianity of each learned feature ## ## A @math{Q}-by-1 vector of @math{+1} and @math{-1}, one per learned ## feature: @math{+1} where the feature is taken to be super-Gaussian and ## @math{-1} where it is taken to be sub-Gaussian. The entry sets the ## sign its feature's contrast term carries in the objective, so the fit ## seeks a sparse feature where the entry is @math{+1} and a spread one ## where it is @math{-1}. The default is all @math{+1}. This property is ## read-only. ## ## @end deftp NonGaussianityIndicator = []; endproperties methods ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} ReconstructionICA (@var{X}, @var{Q}, @dots{}) ## Fit a reconstruction ICA model. This constructor is invoked by ## @code{rica}; see @code{help rica} for the arguments. ## @end deftypefn function this = ReconstructionICA (X, Q, varargin) if (nargin == 0) return; endif [n, p] = size (X); ## Options opts = struct ("IterationLimit", 1000, "Lambda", 1, ... "Standardize", false, "ContrastFcn", "logcosh", ... "InitialTransformWeights", [], ... "GradientTolerance", 1e-6, "StepTolerance", 1e-6, ... "Solver", "quasinewton", ... "NonGaussianityIndicator", []); for k = 1:2:numel (varargin) name = varargin{k}; val = varargin{k+1}; switch (lower (name)) case 'iterationlimit' opts.IterationLimit = val; case 'lambda' opts.Lambda = val; case 'standardize' opts.Standardize = val; case 'contrastfcn' opts.ContrastFcn = lower (val); case 'initialtransformweights' opts.InitialTransformWeights = val; case 'gradienttolerance' opts.GradientTolerance = val; case 'steptolerance' opts.StepTolerance = val; case 'nongaussianityindicator' opts.NonGaussianityIndicator = val; case 'solver' if (! ischar (val) || ... ! any (strcmpi (val, {'quasinewton', 'lbfgs'}))) error ("rica: 'Solver' must be 'quasinewton' or 'lbfgs'."); endif opts.Solver = lower (val); otherwise error ("rica: unknown parameter name '%s'.", name); endswitch endfor if (! any (strcmp (opts.ContrastFcn, {'logcosh', 'exp', 'sqrt'}))) error ("rica: 'ContrastFcn' must be 'logcosh', 'exp', or 'sqrt'."); endif ## One sign per learned feature, all of them super-Gaussian by default if (isempty (opts.NonGaussianityIndicator)) opts.NonGaussianityIndicator = ones (Q, 1); else sigma = opts.NonGaussianityIndicator; if (! (isnumeric (sigma) && isreal (sigma) && isvector (sigma) && numel (sigma) == Q && all (abs (sigma(:)) == 1))) error (strcat ("rica: 'NonGaussianityIndicator' must be a real", ... " vector of Q elements, each +1 or -1.")); endif opts.NonGaussianityIndicator = sigma(:); endif ## Standardize (center and scale) the data if requested if (opts.Standardize) ## MU and SIGMA are held as columns, one entry per predictor, as ## MATLAB holds them, and transposed where the data is scaled. this.Mu = mean (X)'; this.Sigma = std (X)'; sig = this.Sigma'; sig(sig == 0) = 1; X = (X - this.Mu') ./ sig; endif ## Initial weights if (isempty (opts.InitialTransformWeights)) W0 = randn (p, Q); else W0 = opts.InitialTransformWeights; endif this.InitialTransformWeights = W0; ## Minimize the RICA objective lambda = opts.Lambda; sigma = opts.NonGaussianityIndicator; ofun = @(wv) __rica_objective__ (wv, X, p, Q, lambda, ... opts.ContrastFcn, sigma); if (strcmpi (opts.Solver, 'lbfgs')) ## LossTolerance is switched off because MATLAB has no such option ## here: the fit stops on the gradient or the step, never on the ## objective's own value. ## The history is sized to the problem rather than left at the ## engine's default ten. A transform carries only p * Q parameters, ## and once the stored pairs reach that count the recursion is full ## BFGS, which converges further here and in fewer iterations. The ## cap bounds the memory when Q is large. lbopts = struct ("IterationLimit", opts.IterationLimit, ... "GradientTolerance", opts.GradientTolerance, ... "StepTolerance", opts.StepTolerance, ... "LossTolerance", -Inf, ... "HistorySize", min (max (10, numel (W0)), 50)); [wv, lbinfo] = __lbfgs__ (ofun, W0(:), lbopts); fval = lbinfo.Fval; ## The engine records from the first step, where MATLAB's trajectory ## starts at the initial weights. f0 = ofun (W0(:)); obj = [f0; lbinfo.History.Fval]; else objective_history ("reset"); fmopts = optimset ("GradObj", "on", "MaxIter", opts.IterationLimit, ... "TolFun", 1e-10, "TolX", 1e-10, "Display", "off", ... "OutputFcn", @objective_history_fcn); [wv, fval, ~, output] = fminunc (ofun, W0(:), fmopts); obj = objective_history ("get"); endif W = reshape (wv, p, Q); W = W ./ sqrt (sum (W .^ 2, 1)); ## unit-length columns this.TransformWeights = W; this.NumPredictors = p; this.NumLearnedFeatures = Q; this.NonGaussianityIndicator = opts.NonGaussianityIndicator; ## MATLAB reports the whole trajectory: a column with the objective at ## the starting weights first, one entry per iteration after it, and ## Iteration the matching 0-based index. fminunc does not call its ## OutputFcn for the step it returns on, so the final value is appended. if (isempty (obj) || obj(end) != fval) obj(end+1, 1) = fval; endif this.FitInfo = struct ("Iteration", (0:numel (obj) - 1)', ... "Objective", obj); this.ModelParameters = opts; endfunction ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Z} =} transform (@var{Mdl}, @var{X}) ## Transform data @var{X} into the learned feature space, returning the ## @math{N * Q} matrix @var{Z} of features. ## @end deftypefn function Z = transform (this, X) if (nargin != 2) print_usage (); endif if (! isempty (this.Mu)) sig = this.Sigma'; sig(sig == 0) = 1; X = (X - this.Mu') ./ sig; endif Z = X * this.TransformWeights; endfunction endmethods endclassdef ## RICA objective and gradient with respect to the raw (un-normalized) weights. ## SQRT_EPS smooths abs (z) at the origin for the 'sqrt' contrast; the value is ## MATLAB's own, measured on R2024a against a fixture carrying a zero, which is ## the only place it is visible. function [f, g] = __rica_objective__ (wv, X, p, Q, lambda, contrast, sigma) SQRT_EPS = 1e-8; W = reshape (wv, p, Q); nrm = sqrt (sum (W .^ 2, 1)); Wn = W ./ nrm; Z = X * Wn; E = X * (Wn * Wn') - X; switch (contrast) case 'logcosh' C = 0.5 * log (cosh (2 * Z)); case 'exp' C = -exp (-Z .^ 2 / 2); case 'sqrt' C = sqrt (Z .^ 2 + SQRT_EPS); endswitch f = lambda * sum (E(:) .^ 2) + sum (C * sigma); if (nargout > 1) switch (contrast) case 'logcosh' dC = tanh (2 * Z); case 'exp' dC = Z .* exp (-Z .^ 2 / 2); case 'sqrt' dC = Z ./ sqrt (Z .^ 2 + SQRT_EPS); endswitch gWn = lambda * 2 * (X' * E * Wn + E' * X * Wn) + X' * (dC .* sigma'); g = zeros (p, Q); for j = 1:Q wnj = Wn(:,j); g(:,j) = (gWn(:,j) - wnj * (wnj' * gWn(:,j))) / nrm(j); endfor g = g(:); endif endfunction %!shared X, W0 %! X = reshape (mod ((1:60)*7, 13), 12, 5) - 6; %! W0 = reshape (mod ((1:15)*3, 7), 5, 3) - 3; %!test %! ## Construct the object directly and check its properties. %! Mdl = ReconstructionICA (X, 3, "InitialTransformWeights", W0, ... %! "Lambda", 1, "IterationLimit", 500); %! assert_equal (isa (Mdl, "ReconstructionICA"), true); %! assert_equal (Mdl.NumPredictors, 5); %! assert_equal (Mdl.NumLearnedFeatures, 3); %! assert_equal (size (Mdl.TransformWeights), [5, 3]); %! assert_equal (Mdl.InitialTransformWeights, W0); %! assert_equal (isfield (Mdl.FitInfo, "Objective"), true); %!test %! ## The transform method projects onto the (unit-column) weights. %! Mdl = ReconstructionICA (X, 3, "InitialTransformWeights", W0, ... %! "IterationLimit", 500); %! Z = transform (Mdl, X); %! assert_equal (Z, X * Mdl.TransformWeights, 1e-12); %! assert_equal (sqrt (sum (Mdl.TransformWeights .^ 2, 1)), [1, 1, 1], 1e-10); %!test %! ## The transform method standardizes new data when the model does. %! Mdl = ReconstructionICA (X, 2, "Standardize", true, "IterationLimit", 100, ... %! "InitialTransformWeights", W0(:,1:2)); %! Z = transform (Mdl, X); %! Xs = (X - Mdl.Mu') ./ Mdl.Sigma'; %! assert_equal (Z, Xs * Mdl.TransformWeights, 1e-12); %!test %! ## Mu and Sigma are columns, one entry per predictor, as MATLAB reports them %! Mdl = ReconstructionICA (X, 2, "Standardize", true, "IterationLimit", 100, ... %! "InitialTransformWeights", W0(:,1:2)); %! assert_equal (size (Mdl.Mu), [5, 1]); %! assert_equal (size (Mdl.Sigma), [5, 1]); %! assert_equal (Mdl.Mu, mean (X)', 1e-12); %! assert_equal (Mdl.Sigma, std (X)', 1e-12); %!test %! ## The default constructor returns an empty model. %! Mdl = ReconstructionICA (); %! assert_equal (isempty (Mdl.TransformWeights), true); %!test %! ## NonGaussianityIndicator defaults to one per feature, all +1. %! Mdl = ReconstructionICA (X, 3, "InitialTransformWeights", W0, ... %! "IterationLimit", 100); %! assert_equal (Mdl.NonGaussianityIndicator, [1; 1; 1]); %!test %! ## A given indicator is held as a column, whatever shape it arrives in. %! Mdl = ReconstructionICA (X, 3, "InitialTransformWeights", W0, ... %! "IterationLimit", 100, ... %! "NonGaussianityIndicator", [1, -1, 1]); %! assert_equal (Mdl.NonGaussianityIndicator, [1; -1; 1]); %!test %! ## The indicator signs each feature's contrast term, so it moves the fit. %! args = {"InitialTransformWeights", W0, "IterationLimit", 200}; %! Mdl = ReconstructionICA (X, 3, args{:}); %! Neg = ReconstructionICA (X, 3, args{:}, ... %! "NonGaussianityIndicator", [-1; 1; 1]); %! assert_equal (isequal (Mdl.TransformWeights, Neg.TransformWeights), false); %!test %! ## All +1 is the default, so it must reproduce the default fit exactly. %! args = {"InitialTransformWeights", W0, "IterationLimit", 200}; %! Mdl = ReconstructionICA (X, 3, args{:}); %! Pos = ReconstructionICA (X, 3, args{:}, ... %! "NonGaussianityIndicator", [1; 1; 1]); %! assert_equal (Pos.TransformWeights, Mdl.TransformWeights); %!test %! ## Objective against MATLAB R2024a: 801.1816174574 and 197.5588279632. %! t = (1:80)'; %! Xr = double ([mod(t*7,11)-5, mod(t*13,17)-8, mod(t*5,7)-3]); %! args = {"InitialTransformWeights", [1, 0; 0, 1; 1, 1], "Lambda", 1, ... %! "ContrastFcn", "logcosh", "IterationLimit", 1000, ... %! "Solver", "lbfgs", "GradientTolerance", 1e-10, ... %! "StepTolerance", 1e-10}; %! Mdl = ReconstructionICA (Xr, 2, args{:}); %! Neg = ReconstructionICA (Xr, 2, args{:}, ... %! "NonGaussianityIndicator", [-1; 1]); %! assert_equal (Mdl.FitInfo.Objective(end), 801.1816174574, 1e-6); %! assert_equal (Neg.FitInfo.Objective(end), 197.5588279632, 1e-6); %!error ... %! ReconstructionICA (ones (6, 5), 2, "NonGaussianityIndicator", [1; 1; 1]) %!error ... %! ReconstructionICA (ones (6, 5), 2, "NonGaussianityIndicator", [1; 0]) %!error ... %! ReconstructionICA (ones (6, 5), 2, "NonGaussianityIndicator", {1, -1}) %!error ... %! ReconstructionICA (ones (6, 5), 2, "ContrastFcn", "bogus") %!error ... %! ReconstructionICA (ones (6, 5), 2, "bogus", 1) statistics-release-1.9.2/inst/Dimensionality_Reduction/SparseFiltering.m000066400000000000000000000303321524624707500266100ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftp {statistics} {} SparseFiltering ## ## Sparse filtering feature-extraction model. ## ## A @qcode{SparseFiltering} object stores the transformation learned by ## @code{sparsefilt} for extracting features from data. Create one with ## @code{sparsefilt}; apply it to data with the @code{transform} method. ## ## @seealso{sparsefilt, rica} ## @end deftp classdef SparseFiltering properties (SetAccess = protected) ## -*- texinfo -*- ## @deftp {SparseFiltering} {property} ModelParameters ## ## Options the fit used ## ## A scalar structure holding the options the fit ran with: ## @qcode{IterationLimit}, @qcode{Lambda}, @qcode{Standardize}, ## @qcode{InitialTransformWeights}, @qcode{GradientTolerance}, ## @qcode{StepTolerance} and @qcode{Solver}. ## This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {SparseFiltering} {property} NumPredictors ## ## Number of input predictors ## ## A positive integer @math{P}, the number of columns of the training ## data. This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {SparseFiltering} {property} NumLearnedFeatures ## ## Number of learned features ## ## A positive integer @math{Q}, the number of features the learned ## transformation produces. This property is read-only. ## ## @end deftp NumLearnedFeatures = []; ## -*- texinfo -*- ## @deftp {SparseFiltering} {property} Mu ## ## Predictor means used when standardizing ## ## A column vector with one entry per predictor, the mean of each ## column of the training data. It is empty unless @qcode{'Standardize'} ## was true. This property is read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {SparseFiltering} {property} Sigma ## ## Predictor standard deviations used when standardizing ## ## A column vector with one entry per predictor, the standard deviation ## of each column of the training data. It is empty unless ## @qcode{'Standardize'} was true. This property is read-only. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {SparseFiltering} {property} FitInfo ## ## History of the fit ## ## A scalar structure with the fields @qcode{Iteration} and ## @qcode{Objective}, both column vectors of the same length. ## @qcode{Iteration} counts from zero and @qcode{Objective(1)} is the ## objective at the starting weights, so the last entry of each is the ## solution the fit returned. This property is read-only. ## ## The trajectory is this implementation's own. The default ## @qcode{'quasinewton'} solver minimises through Octave's @code{fminunc}, ## and @qcode{'Solver', 'lbfgs'} selects the limited-memory BFGS solver ## MATLAB uses. Either way the steps taken from the same starting weights ## differ from MATLAB's, so the length of the history and the iteration ## counts differ, and on an objective this far from convex the optimum ## reached need not be MATLAB's either. ## ## @end deftp FitInfo = []; ## -*- texinfo -*- ## @deftp {SparseFiltering} {property} TransformWeights ## ## Learned feature transformation weights ## ## A @math{P}-by-@math{Q} matrix of learned weights. The @code{transform} ## method applies it to data. This property is read-only. ## ## @end deftp TransformWeights = []; ## -*- texinfo -*- ## @deftp {SparseFiltering} {property} InitialTransformWeights ## ## Starting feature transformation weights ## ## A @math{P}-by-@math{Q} matrix, the weights the fit started from. It ## is the matrix given as @qcode{'InitialTransformWeights'} when one was ## given, and the random start the fit drew otherwise. This property is ## read-only. ## ## @end deftp InitialTransformWeights = []; endproperties methods ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} SparseFiltering (@var{X}, @var{Q}, @dots{}) ## Fit a sparse filtering model. This constructor is invoked by ## @code{sparsefilt}; see @code{help sparsefilt} for the arguments. ## @end deftypefn function this = SparseFiltering (X, Q, varargin) if (nargin == 0) return; endif [n, p] = size (X); opts = struct ("IterationLimit", 1000, "Lambda", 1, ... "Standardize", false, ... "InitialTransformWeights", [], ... "GradientTolerance", 1e-6, "StepTolerance", 1e-6, ... "Solver", "quasinewton"); for k = 1:2:numel (varargin) name = varargin{k}; val = varargin{k+1}; switch (lower (name)) case 'iterationlimit' opts.IterationLimit = val; case 'lambda' opts.Lambda = val; case 'standardize' opts.Standardize = val; case 'initialtransformweights' opts.InitialTransformWeights = val; case 'gradienttolerance' opts.GradientTolerance = val; case 'steptolerance' opts.StepTolerance = val; case 'solver' if (! ischar (val) || ... ! any (strcmpi (val, {'quasinewton', 'lbfgs'}))) error ("sparsefilt: 'Solver' must be 'quasinewton' or 'lbfgs'."); endif opts.Solver = lower (val); otherwise error ("sparsefilt: unknown parameter name '%s'.", name); endswitch endfor if (opts.Standardize) ## MU and SIGMA are held as columns, one entry per predictor, as ## MATLAB holds them, and transposed where the data is scaled. this.Mu = mean (X)'; this.Sigma = std (X)'; sig = this.Sigma'; sig(sig == 0) = 1; X = (X - this.Mu') ./ sig; endif if (isempty (opts.InitialTransformWeights)) W0 = randn (p, Q); else W0 = opts.InitialTransformWeights; endif this.InitialTransformWeights = W0; lambda = opts.Lambda; ofun = @(wv) __sparsefilt_objective__ (wv, X, p, Q, lambda); if (strcmpi (opts.Solver, 'lbfgs')) ## LossTolerance is switched off because MATLAB has no such option ## here: the fit stops on the gradient or the step, never on the ## objective's own value. ## The history is sized to the problem rather than left at the ## engine's default ten. A transform carries only p * Q parameters, ## and once the stored pairs reach that count the recursion is full ## BFGS, which converges further here and in fewer iterations. The ## cap bounds the memory when Q is large. lbopts = struct ("IterationLimit", opts.IterationLimit, ... "GradientTolerance", opts.GradientTolerance, ... "StepTolerance", opts.StepTolerance, ... "LossTolerance", -Inf, ... "HistorySize", min (max (10, numel (W0)), 50)); [wv, lbinfo] = __lbfgs__ (ofun, W0(:), lbopts); fval = lbinfo.Fval; ## The engine records from the first step, where MATLAB's trajectory ## starts at the initial weights. f0 = ofun (W0(:)); obj = [f0; lbinfo.History.Fval]; else objective_history ("reset"); fmopts = optimset ("GradObj", "on", "MaxIter", opts.IterationLimit, ... "TolFun", 1e-10, "TolX", 1e-10, "Display", "off", ... "OutputFcn", @objective_history_fcn); [wv, fval, ~, output] = fminunc (ofun, W0(:), fmopts); obj = objective_history ("get"); endif this.TransformWeights = reshape (wv, p, Q); this.NumPredictors = p; this.NumLearnedFeatures = Q; ## MATLAB reports the whole trajectory: a column with the objective at ## the starting weights first, one entry per iteration after it, and ## Iteration the matching 0-based index. fminunc does not call its ## OutputFcn for the step it returns on, so the final value is appended. if (isempty (obj) || obj(end) != fval) obj(end+1, 1) = fval; endif this.FitInfo = struct ("Iteration", (0:numel (obj) - 1)', ... "Objective", obj); this.ModelParameters = opts; endfunction ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Z} =} transform (@var{Mdl}, @var{X}) ## Transform data @var{X} into the learned feature space, returning the ## @math{N * Q} matrix @var{Z} of sparse features. ## @end deftypefn function Z = transform (this, X) if (nargin != 2) print_usage (); endif if (! isempty (this.Mu)) sig = this.Sigma'; sig(sig == 0) = 1; X = (X - this.Mu') ./ sig; endif Z = __sparsefilt_features__ (X, this.TransformWeights); endfunction endmethods endclassdef ## Soft-absolute features, normalized across examples then across features. function F = __sparsefilt_features__ (X, W) A = X * W; F = sqrt (A .^ 2 + 1e-8); F = F ./ sqrt (sum (F .^ 2, 1) + 1e-8); ## per-feature (column) normalization F = F ./ sqrt (sum (F .^ 2, 2) + 1e-8); ## per-example (row) normalization endfunction ## Sparse filtering objective (sum of features + L2 penalty) and its gradient. function [f, g] = __sparsefilt_objective__ (wv, X, p, Q, lambda) W = reshape (wv, p, Q); A = X * W; F = sqrt (A .^ 2 + 1e-8); cn = sqrt (sum (F .^ 2, 1) + 1e-8); Ft = F ./ cn; rn = sqrt (sum (Ft .^ 2, 2) + 1e-8); Fh = Ft ./ rn; f = sum (Fh(:)) + lambda * sum (W(:) .^ 2); if (nargout > 1) dFh = ones (size (Fh)); dFt = (dFh - sum (dFh .* Fh, 2) .* Fh) ./ rn; dF = (dFt - sum (dFt .* Ft, 1) .* Ft) ./ cn; dA = dF .* (A ./ F); g = X' * dA + 2 * lambda * W; g = g(:); endif endfunction %!shared X, W0 %! X = reshape (mod ((1:60)*7, 13), 12, 5) - 6; %! W0 = reshape (mod ((1:15)*3, 7), 5, 3) - 3; %!test %! ## Construct the object directly and check its properties. %! Mdl = SparseFiltering (X, 3, "InitialTransformWeights", W0, ... %! "Lambda", 1, "IterationLimit", 500); %! assert_equal (isa (Mdl, "SparseFiltering"), true); %! assert_equal (Mdl.NumPredictors, 5); %! assert_equal (Mdl.NumLearnedFeatures, 3); %! assert_equal (size (Mdl.TransformWeights), [5, 3]); %! assert_equal (Mdl.InitialTransformWeights, W0); %! assert_equal (isfield (Mdl.FitInfo, "Objective"), true); %!test %! ## The transform method returns nonnegative features in [0, 1]. %! Mdl = SparseFiltering (X, 3, "InitialTransformWeights", W0, ... %! "IterationLimit", 500); %! Z = transform (Mdl, X); %! assert_equal (size (Z), [12, 3]); %! assert_equal (all (Z(:) >= 0) && all (Z(:) <= 1 + 1e-12), true); %!test %! ## The transform method standardizes new data when the model does. %! Mdl = SparseFiltering (X, 2, "Standardize", true, "IterationLimit", 100, ... %! "InitialTransformWeights", W0(:,1:2)); %! Z = transform (Mdl, X); %! assert_equal (size (Z), [12, 2]); %! assert_equal (isempty (Mdl.Mu), false); %! assert_equal (size (Mdl.Mu), [5, 1]); %! assert_equal (size (Mdl.Sigma), [5, 1]); %!test %! ## The default constructor returns an empty model. %! Mdl = SparseFiltering (); %! assert_equal (isempty (Mdl.TransformWeights), true); %!error ... %! SparseFiltering (ones (6, 5), 2, "bogus", 1) statistics-release-1.9.2/inst/Dimensionality_Reduction/canoncorr.m000066400000000000000000000153331524624707500254770ustar00rootroot00000000000000## Copyright (C) 2016-2019 by Nir Krakauer ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{A}, @var{B}, @var{r}, @var{U}, @var{V}] =} canoncorr (@var{X}, @var{Y}) ## ## Canonical correlation analysis. ## ## Given @var{X} (size @var{k}*@var{m}) and @var{Y} (@var{k}*@var{n}), returns ## projection matrices of canonical coefficients @var{A} (size @var{m}*@var{d}, ## where @var{d} is the smallest of @var{m}, @var{n}, @var{d}) and @var{B} ## (size @var{m}*@var{d}); the canonical correlations @var{r} (1*@var{d}, ## arranged in decreasing order); the canonical variables @var{U}, @var{V} ## (both @var{k}*@var{d}, with orthonormal columns); and @var{stats}, ## a structure containing results from Bartlett's chi-square and Rao's F tests ## of significance. ## ## @seealso{princomp} ## @end deftypefn function [A,B,r,U,V,stats] = canoncorr (X,Y) k = size (X, 1); # should also be size (Y, 1) m = size (X, 2); n = size (Y, 2); X = center (X); Y = center (Y); ## Factor with column pivoting and work in the numerical rank. Without this ## a rank-deficient input is solved against a singular triangular factor: ## the coefficients run away to 1e15 and the canonical correlations come back ## wrong rather than merely imprecise. [Qx, Rx, px] = qr (X, 0); rankX = rank_of (Rx, k, m); if (rankX == 0) error ("canoncorr: X must contain at least one non-constant column."); elseif (rankX < m) warning ("canoncorr:NotFullRank", "canoncorr: X is not full rank."); Qx = Qx(:, 1:rankX); Rx = Rx(1:rankX, 1:rankX); endif [Qy, Ry, py] = qr (Y, 0); rankY = rank_of (Ry, k, n); if (rankY == 0) error ("canoncorr: Y must contain at least one non-constant column."); elseif (rankY < n) warning ("canoncorr:NotFullRank", "canoncorr: Y is not full rank."); Qy = Qy(:, 1:rankY); Ry = Ry(1:rankY, 1:rankY); endif d = min (rankX, rankY); [U S V] = svd (Qx' * Qy, 'econ'); A = Rx \ U(:, 1:d); B = Ry \ V(:, 1:d); ## A, B are scaled to make the covariance matrices of the outputs U, V ## identity matrices f = sqrt (k-1); A .*= f; B .*= f; ## Put the coefficients back to their full height in the original column ## order, the dropped columns contributing nothing. A(px, :) = [A; zeros(m - rankX, d)]; B(py, :) = [B; zeros(n - rankY, d)]; if (nargout > 2) r = max (0, min (diag (S)(1:d), 1))'; endif if (nargout > 3) U = X * A; endif if (nargout > 4) V = Y * B; endif if (nargout > 5) ## The degrees of freedom count the dimensions actually fitted, which is ## the rank rather than the number of columns supplied. Wilks = fliplr (cumprod (fliplr ((1 - r .^ 2)))); chisq = - (k - 1 - (rankX + rankY + 1)/2) * log (Wilks); df1 = (rankX - (1:d) + 1) .* (rankY - (1:d) + 1); pChisq = 1 - chi2cdf (chisq, df1); s = sqrt ((df1.^2 - 4) ./ ((rankX - (1:d) + 1).^2 + ... (rankY - (1:d) + 1).^2 - 5)); df2 = (k - 1 - (rankX + rankY + 1)/2) * s - df1/2 + 1; ls = Wilks .^ (1 ./ s); F = (1 ./ ls - 1) .* (df2 ./ df1); pF = 1 - fcdf (F, df1, df2); stats.Wilks = Wilks; stats.df1 = df1; stats.df2 = df2; stats.F = F; stats.pF = pF; stats.chisq = chisq; stats.pChisq = pChisq; endif endfunction ## Numerical rank of a triangular QR factor, on the scale of its leading entry. function rk = rank_of (R, nrows, ncols) if (isempty (R)) rk = 0; else rk = sum (abs (diag (R)) > eps (abs (R(1))) * max (nrows, ncols)); endif endfunction %!shared X, Y, A, B, r, U, V, k, Cuv %! k = 10; %! X = [1:k; sin(1:k); cos(1:k)]'; %! Y = [tan(1:k); tanh((1:k)/k)]'; %! [A, B, r, U, V, stats] = canoncorr (X, Y); %! Cuv = (U' * V) / (k - 1); %!assert_equal (diag (Cuv)', r, 10 * eps); %!assert_equal (diag (diag (Cuv)), Cuv, 2 * eps); %!assert_equal (r, [0.99590, 0.26754], 1E-5); %!assert_equal (U, center (X) * A, 10 * eps); %!assert_equal (V, center (Y) * B, 10 * eps); %!assert_equal (cov (U), eye (size (U, 2)), 10 * eps); %!assert_equal (cov (V), eye (size (V, 2)), 10 * eps); %! rand ('state', 1); [A, B, r] = canoncorr (rand (5, 10), rand (5, 20)); %! ## Four, not five: centring a five-row matrix leaves rank at most four, so %! ## there is no fifth canonical correlation to report. The count used to %! ## come from the number of rows rather than the rank. %!assert_equal (r, ones (1, 4), 10*eps); ## Rank-deficient input is reduced to its rank rather than solved against a ## singular factor. Verified against MATLAB R2024a. %!test %! Xr = [X(:,1), X(:,1), X(:,2)]; %! warning ("off", "canoncorr:NotFullRank", "local"); %! [Ar, Br, rr] = canoncorr (Xr, Y); %! ## the duplicated column contributes nothing %! assert_equal (Ar(2,:), zeros (1, columns (Ar))); %! ## and the coefficients stay finite, where they used to reach 1e15 %! assert_equal (all (isfinite (Ar(:))), true); %! assert_equal (max (abs (Ar(:))) < 1e3, true); %! ## dropping the duplicate leaves the same fit as never having had it %! [A2, B2, r2] = canoncorr (X(:,1:2), Y); %! assert_equal (rr, r2, 1e-12); %! assert_equal (Ar([1, 3], :), A2, 1e-10); %!test # a constant column carries no information and is dropped %! Xc = [X(:,1:2), ones(rows (X), 1)]; %! warning ("off", "canoncorr:NotFullRank", "local"); %! [Ac, Bc, rc] = canoncorr (Xc, Y); %! assert_equal (Ac(3,:), zeros (1, columns (Ac))); %! [A2, B2, r2] = canoncorr (X(:,1:2), Y); %! assert_equal (rc, r2, 1e-12); %!test # the deficiency is reported %! Xr = [X(:,1), X(:,1), X(:,2)]; %! fail ("canoncorr (Xr, Y)", "warning", "X is not full rank"); %!test # the returned coefficients satisfy the definition whatever the rank %! Xr = [X(:,1), X(:,1), X(:,2)]; %! warning ("off", "canoncorr:NotFullRank", "local"); %! [Ar, Br, rr, Ur, Vr] = canoncorr (Xr, Y); %! kk = rows (Xr); %! assert_equal ((Ur' * Ur) / (kk - 1), eye (numel (rr)), 1e-10); %! assert_equal ((Vr' * Vr) / (kk - 1), eye (numel (rr)), 1e-10); %! assert_equal ((Ur' * Vr) / (kk - 1), diag (rr), 1e-10); %!error ... %! canoncorr (ones (10, 2), [tan(1:10); tanh((1:10)/10)]') statistics-release-1.9.2/inst/Dimensionality_Reduction/cmdscale.m000066400000000000000000000217301524624707500252640ustar00rootroot00000000000000## Copyright (C) 2014 JD Walsh ## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Y} =} cmdscale (@var{D}) ## @deftypefnx {statistics} {[@var{Y}, @var{e}] =} cmdscale (@var{D}) ## @deftypefnx {statistics} {@var{Y} =} cmdscale (@var{D}, @var{p}) ## @deftypefnx {statistics} {[@var{Y}, @var{e}] =} cmdscale (@var{D}, @var{p}) ## ## Classical multidimensional scaling of a matrix. ## ## Takes an @var{n} by @var{n} distance (or difference, similarity, or ## dissimilarity) matrix @var{D}. Returns @var{Y}, a matrix of @var{n} points ## with coordinates in @var{p} dimensional space which approximate those ## distances (or differences, similarities, or dissimilarities). Also returns ## the eigenvalues @var{e} of ## @code{@var{B} = -1/2 * @var{J} * (@var{D}.^2) * @var{J}}, where ## @code{J = eye(@var{n}) - ones(@var{n},@var{n})/@var{n}}. @var{p}, the number ## of columns of @var{Y}, is equal to the number of positive real eigenvalues of ## @var{B}. ## ## The optional argument @var{p} is a positive integer between 1 and @var{n} ## that specifies the maximum dimensionality of the desired embedding @var{Y}. ## If specified, @var{Y} will have at most @var{p} columns, and the returned ## eigenvalues @var{e} will be a vector of exactly length @var{p}. Specifying ## @var{p} can be useful for reducing dimensions for visualization (e.g., ## @code{@var{p} = ## 2}). ## ## @var{D} can be a full or sparse matrix or a vector of length ## @code{@var{n}*(@var{n}-1)/2} containing the upper triangular elements (like ## the output of the @code{pdist} function). It must be symmetric with ## non-negative entries whose values are further restricted by the type of ## matrix being represented: ## ## * If @var{D} is either a distance, dissimilarity, or difference matrix, then ## it must have zero entries along the main diagonal. In this case the points ## @var{Y} equal or approximate the distances given by @var{D}. ## ## * If @var{D} is a similarity matrix, the elements must all be less than or ## equal to one, with ones along the main diagonal. In this case the points ## @var{Y} equal or approximate the distances given by ## @code{@var{D} = sqrt(ones(@var{n},@var{n})-@var{D})}. ## ## @var{D} is a Euclidean matrix if and only if @var{B} is positive ## semi-definite. When this is the case, then @var{Y} is an exact representation ## of the distances given in @var{D}. If @var{D} is non-Euclidean, @var{Y} only ## approximates the distance given in @var{D}. The approximation used by ## @code{cmdscale} minimizes the statistical loss function known as ## @var{strain}. ## ## The returned @var{Y} is an @var{n} by @var{p} matrix showing possible ## coordinates of the points in @var{p} dimensional space ## (@code{@var{p} < @var{n}}). The columns correspond to the positive ## eigenvalues of @var{B} in descending order. A translation, rotation, or ## reflection of the coordinates given by @var{Y} will satisfy the same distance ## matrix up to the limits of machine precision. ## ## For any @code{@var{k} <= @var{p}}, if the largest @var{k} positive ## eigenvalues of @var{B} are significantly greater in absolute magnitude than ## its other eigenvalues, the first @var{k} columns of @var{Y} provide a ## @var{k}-dimensional reduction of @var{Y} which approximates the distances ## given by @var{D}. The optional return @var{e} can be used to consider various ## values of @var{k}, or to evaluate the accuracy of specific dimension ## reductions (e.g., @code{@var{k} = 2}). ## ## Reference: Ingwer Borg and Patrick J.F. Groenen (2005), Modern ## Multidimensional Scaling, Second Edition, Springer, ISBN: 978-0-387-25150-9 ## (Print) 978-0-387-28981-6 (Online) ## ## @seealso{pdist} ## @end deftypefn function [Y, e] = cmdscale (D, p) ## Check for matrix input and valid number of arguments if (nargin < 1 || nargin > 2 || ! isnumeric (D) || ! ismatrix (D)) error ("cmdscale: input must be a numeric vector or matrix."); endif ## If vector, convert to matrix; otherwise, check for square symmetric input if (isvector (D)) D = squareform (D); elseif (! issquare (D) || norm (D - D', 1) > 0) error ("cmdscale: matrix input must be square symmetric."); endif n = size (D, 1); if (nargin > 1 && ! isempty (p)) if (! isscalar (p) || ! isreal (p) || p != round (p) || p < 1 || p > n) error ("cmdscale: p must be an integer between 1 and %d.", n); endif endif ## Check for valid format and if similarity matrix, convert if (any (any (D < 0))) error ("cmdscale: entries must be nonnegative."); elseif (trace (D) != 0) if (! all (diag (D) == 1) || ! all (D(:) <= 1)) error ("cmdscale: input must be a distance vector or matrix."); endif D = sqrt (ones (n,n) - D); endif ## Build centering matrix, perform double centering J = eye (n) - ones (n, n) / n; B = -1 / 2 * J * (D .^ 2) * J; B = (B + B') / 2; ## extract and sort eigenpairs [Q, e] = eig (B); etmp = diag (e); [etmp, ord] = sort (etmp, 'descend'); Q = Q(:, ord); ## determine total strictly positive eigenvalues tol = n * max (abs (etmp)) * eps; n_pos = sum (etmp > tol); if (n_pos == n) n_pos = n - 1; endif if (nargin > 1 && ! isempty (p)) ## if p is given, eigenvalue output length is exactly p e = etmp(1:p); k = min (p, n_pos); else e = etmp; k = n_pos; endif ## build output matrix Y by safely slicing the top k elements etmp = etmp(1:k); Q = Q(:, 1:k); Y = Q * diag (sqrt (etmp)); [~, maxind] = max (abs (Y), [], 1); d = size (Y, 2); idx = maxind + (0 : n : (d - 1) * n); colsign = sign (Y(idx)); colsign(colsign == 0) = 1; Y = Y .* colsign; endfunction %!test %! m = randi (100) + 1; %! n = randi (100) + 1; %! X = rand (m, n); %! D = pdist (X); %! assert_equal (norm (pdist (cmdscale (D))), norm (D), sqrt (eps)); %! assert_equal (norm (pdist (cmdscale (squareform (D)))), norm (D), sqrt (eps)); %!test %! ## test output %! X = [ %! 0.8147, 0.1576, 0.6557, 0.7060; %! 0.9058, 0.9706, 0.0357, 0.0318; %! 0.1270, 0.9572, 0.8491, 0.2769; %! 0.9134, 0.4854, 0.9340, 0.0462; %! 0.6324, 0.8003, 0.6787, 0.0971 %! ]; %! D = pdist (X); %! p = 2; %! [Y, e] = cmdscale (D, p); %! expected_Y = [ %! 0.635444598081665, -0.209808014423477; %! -0.558655450609184, -0.457908993032377; %! -0.158680352453745, 0.622280326562354; %! 0.222509398493731, -0.047804408953240; %! -0.140618193512467, 0.093241089846740 %! ]; %! expected_e = [0.810349112746116; 0.651912015993974]; %! assert_equal (Y, expected_Y, 1e-14); %! assert_equal (e, expected_e, 1e-14); %!test %! ## basic dimentionality reduction %! D = [0 2 3; 2 0 4; 3 4 0]; %! [Y, e] = cmdscale (D, 2); %! assert_equal (size (Y, 2), 2); %! assert_equal (length (e), 2); %!test %! ## oversized dimension %! X = [0 0; 1 0; 0 1; 1 1]; %! D = pdist (X); %! [Y, e] = cmdscale (D, 3); %! assert_equal (size (Y, 2), 2); %! assert_equal (length (e), 3); %!test %! ## non euclidean distance. %! X = [1 2; 3 4; 5 6; 7 8; 9 10]; %! D = pdist (X, 'cityblock'); %! [Y, e] = cmdscale (D, 2); %! assert_equal (size (Y, 2), 1); %! assert_equal (length (e), 2); %!test %! ## compatability with p %! X = rand (10, 4); %! D = pdist (X); %! [Y, e] = cmdscale (D, 3); %! assert_equal (size (Y, 2), 3); %! assert_equal (length (e), 3); %! assert_equal (size (Y, 1), 10); %!test %! ## sign convention. %! rng (0, 'twister'); %! X = rand (10, 3); %! D = pdist (X); %! Y = cmdscale (D); %! [~, maxind] = max (abs (Y), [], 1); %! d = size (Y, 2); %! n = size (Y, 1); %! idx = maxind + (0 : n : (d - 1) * n); %! assert_equal (all (Y(idx) >= 0), true); %!test %! ## testing with p = n and without p %! rng (1, 'twister'); %! X = rand (10, 4); %! D = pdist (X); %! n_points = size (X, 1); %! [Y1, e1] = cmdscale (D); %! [Y2, e2] = cmdscale (D, n_points); %! assert_equal (size (Y1, 2), size (Y2, 2)); %!error cmdscale ({'not', 'a', 'matrix'}) %!error cmdscale (rand (3, 4)) %!error cmdscale (-ones (3)) %!error

cmdscale (eye (3), 0) %!error

cmdscale (eye (3), 4) %!error

cmdscale (eye (3), 1.5) %!error

cmdscale (eye (3), [1, 2]) %!error

cmdscale (eye (3), 2 + 1i) statistics-release-1.9.2/inst/Dimensionality_Reduction/doc-cache000066400000000000000000001460521524624707500250710ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 38 # name: # type: sq_string # elements: 1 # length: 17 ReconstructionICA # name: # type: sq_string # elements: 1 # length: 309 statistics: ReconstructionICA Reconstruction independent component analysis (RICA) feature-extraction model. A ReconstructionICA object stores the transformation learned by rica for extracting features from data. Create one with rica ; apply it to data with the transform method. See also: rica, sparsefilt # name: # type: sq_string # elements: 1 # length: 78 Reconstruction independent component analysis (RICA) feature-extraction model. # name: # type: sq_string # elements: 1 # length: 25 ReconstructionICA.FitInfo # name: # type: sq_string # elements: 1 # length: 777 ReconstructionICA: property FitInfo History of the fit A scalar structure with the fields Iteration and Objective , both column vectors of the same length. Iteration counts from zero and Objective(1) is the objective at the starting weights, so the last entry of each is the solution the fit returned. This property is read-only. The trajectory is this implementation’s own. The default 'quasinewton' solver minimises through Octave’s fminunc , and 'Solver', 'lbfgs' selects the limited-memory BFGS solver MATLAB uses. Either way the steps taken from the same starting weights differ from MATLAB’s, so the length of the history and the iteration counts differ, and on an objective this far from convex the optimum reached need not be MATLAB’s either. # name: # type: sq_string # elements: 1 # length: 18 History of the fit # name: # type: sq_string # elements: 1 # length: 41 ReconstructionICA.InitialTransformWeights # name: # type: sq_string # elements: 1 # length: 291 ReconstructionICA: property InitialTransformWeights Starting feature transformation weights A P -by- Q matrix, the weights the fit started from. It is the matrix given as 'InitialTransformWeights' when one was given, and the random start the fit drew otherwise. This property is read-only. # name: # type: sq_string # elements: 1 # length: 39 Starting feature transformation weights # name: # type: sq_string # elements: 1 # length: 33 ReconstructionICA.ModelParameters # name: # type: sq_string # elements: 1 # length: 304 ReconstructionICA: property ModelParameters Options the fit used A scalar structure holding the options the fit ran with: IterationLimit , Lambda , Standardize , ContrastFcn , InitialTransformWeights , GradientTolerance , StepTolerance , Solver and NonGaussianityIndicator . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Options the fit used # name: # type: sq_string # elements: 1 # length: 20 ReconstructionICA.Mu # name: # type: sq_string # elements: 1 # length: 235 ReconstructionICA: property Mu Predictor means used when standardizing A column vector with one entry per predictor, the mean of each column of the training data. It is empty unless 'Standardize' was true. This property is read-only. # name: # type: sq_string # elements: 1 # length: 39 Predictor means used when standardizing # name: # type: sq_string # elements: 1 # length: 41 ReconstructionICA.NonGaussianityIndicator # name: # type: sq_string # elements: 1 # length: 472 ReconstructionICA: property NonGaussianityIndicator Non-Gaussianity of each learned feature A Q -by-1 vector of +1 and -1 , one per learned feature: +1 where the feature is taken to be super-Gaussian and -1 where it is taken to be sub-Gaussian. The entry sets the sign its feature’s contrast term carries in the objective, so the fit seeks a sparse feature where the entry is +1 and a spread one where it is -1 . The default is all +1 . This property is read-only. # name: # type: sq_string # elements: 1 # length: 39 Non-Gaussianity of each learned feature # name: # type: sq_string # elements: 1 # length: 36 ReconstructionICA.NumLearnedFeatures # name: # type: sq_string # elements: 1 # length: 186 ReconstructionICA: property NumLearnedFeatures Number of learned features A positive integer Q , the number of features the learned transformation produces. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Number of learned features # name: # type: sq_string # elements: 1 # length: 31 ReconstructionICA.NumPredictors # name: # type: sq_string # elements: 1 # length: 165 ReconstructionICA: property NumPredictors Number of input predictors A positive integer P , the number of columns of the training data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Number of input predictors # name: # type: sq_string # elements: 1 # length: 35 ReconstructionICA.ReconstructionICA # name: # type: sq_string # elements: 1 # length: 159 statistics: Mdl = ReconstructionICA ( X , Q , …) Fit a reconstruction ICA model. This constructor is invoked by rica ; see help rica for the arguments. # name: # type: sq_string # elements: 1 # length: 31 Fit a reconstruction ICA model. # name: # type: sq_string # elements: 1 # length: 23 ReconstructionICA.Sigma # name: # type: sq_string # elements: 1 # length: 266 ReconstructionICA: property Sigma Predictor standard deviations used when standardizing A column vector with one entry per predictor, the standard deviation of each column of the training data. It is empty unless 'Standardize' was true. This property is read-only. # name: # type: sq_string # elements: 1 # length: 53 Predictor standard deviations used when standardizing # name: # type: sq_string # elements: 1 # length: 34 ReconstructionICA.TransformWeights # name: # type: sq_string # elements: 1 # length: 220 ReconstructionICA: property TransformWeights Learned feature transformation weights A P -by- Q matrix of learned weights, its columns of unit length. The transform method applies it to data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Learned feature transformation weights # name: # type: sq_string # elements: 1 # length: 27 ReconstructionICA.transform # name: # type: sq_string # elements: 1 # length: 135 statistics: Z = transform ( Mdl , X ) Transform data X into the learned feature space, returning the N × Q matrix Z of features. # name: # type: sq_string # elements: 1 # length: 90 Transform data X into the learned feature space, returning the N * Q matrix Z of features. # name: # type: sq_string # elements: 1 # length: 15 SparseFiltering # name: # type: sq_string # elements: 1 # length: 281 statistics: SparseFiltering Sparse filtering feature-extraction model. A SparseFiltering object stores the transformation learned by sparsefilt for extracting features from data. Create one with sparsefilt ; apply it to data with the transform method. See also: sparsefilt, rica # name: # type: sq_string # elements: 1 # length: 42 Sparse filtering feature-extraction model. # name: # type: sq_string # elements: 1 # length: 23 SparseFiltering.FitInfo # name: # type: sq_string # elements: 1 # length: 775 SparseFiltering: property FitInfo History of the fit A scalar structure with the fields Iteration and Objective , both column vectors of the same length. Iteration counts from zero and Objective(1) is the objective at the starting weights, so the last entry of each is the solution the fit returned. This property is read-only. The trajectory is this implementation’s own. The default 'quasinewton' solver minimises through Octave’s fminunc , and 'Solver', 'lbfgs' selects the limited-memory BFGS solver MATLAB uses. Either way the steps taken from the same starting weights differ from MATLAB’s, so the length of the history and the iteration counts differ, and on an objective this far from convex the optimum reached need not be MATLAB’s either. # name: # type: sq_string # elements: 1 # length: 18 History of the fit # name: # type: sq_string # elements: 1 # length: 39 SparseFiltering.InitialTransformWeights # name: # type: sq_string # elements: 1 # length: 289 SparseFiltering: property InitialTransformWeights Starting feature transformation weights A P -by- Q matrix, the weights the fit started from. It is the matrix given as 'InitialTransformWeights' when one was given, and the random start the fit drew otherwise. This property is read-only. # name: # type: sq_string # elements: 1 # length: 39 Starting feature transformation weights # name: # type: sq_string # elements: 1 # length: 31 SparseFiltering.ModelParameters # name: # type: sq_string # elements: 1 # length: 262 SparseFiltering: property ModelParameters Options the fit used A scalar structure holding the options the fit ran with: IterationLimit , Lambda , Standardize , InitialTransformWeights , GradientTolerance , StepTolerance and Solver . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Options the fit used # name: # type: sq_string # elements: 1 # length: 18 SparseFiltering.Mu # name: # type: sq_string # elements: 1 # length: 233 SparseFiltering: property Mu Predictor means used when standardizing A column vector with one entry per predictor, the mean of each column of the training data. It is empty unless 'Standardize' was true. This property is read-only. # name: # type: sq_string # elements: 1 # length: 39 Predictor means used when standardizing # name: # type: sq_string # elements: 1 # length: 34 SparseFiltering.NumLearnedFeatures # name: # type: sq_string # elements: 1 # length: 184 SparseFiltering: property NumLearnedFeatures Number of learned features A positive integer Q , the number of features the learned transformation produces. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Number of learned features # name: # type: sq_string # elements: 1 # length: 29 SparseFiltering.NumPredictors # name: # type: sq_string # elements: 1 # length: 163 SparseFiltering: property NumPredictors Number of input predictors A positive integer P , the number of columns of the training data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Number of input predictors # name: # type: sq_string # elements: 1 # length: 21 SparseFiltering.Sigma # name: # type: sq_string # elements: 1 # length: 264 SparseFiltering: property Sigma Predictor standard deviations used when standardizing A column vector with one entry per predictor, the standard deviation of each column of the training data. It is empty unless 'Standardize' was true. This property is read-only. # name: # type: sq_string # elements: 1 # length: 53 Predictor standard deviations used when standardizing # name: # type: sq_string # elements: 1 # length: 31 SparseFiltering.SparseFiltering # name: # type: sq_string # elements: 1 # length: 167 statistics: Mdl = SparseFiltering ( X , Q , …) Fit a sparse filtering model. This constructor is invoked by sparsefilt ; see help sparsefilt for the arguments. # name: # type: sq_string # elements: 1 # length: 29 Fit a sparse filtering model. # name: # type: sq_string # elements: 1 # length: 32 SparseFiltering.TransformWeights # name: # type: sq_string # elements: 1 # length: 190 SparseFiltering: property TransformWeights Learned feature transformation weights A P -by- Q matrix of learned weights. The transform method applies it to data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Learned feature transformation weights # name: # type: sq_string # elements: 1 # length: 25 SparseFiltering.transform # name: # type: sq_string # elements: 1 # length: 142 statistics: Z = transform ( Mdl , X ) Transform data X into the learned feature space, returning the N × Q matrix Z of sparse features. # name: # type: sq_string # elements: 1 # length: 97 Transform data X into the learned feature space, returning the N * Q matrix Z of sparse features. # name: # type: sq_string # elements: 1 # length: 9 canoncorr # name: # type: sq_string # elements: 1 # length: 534 statistics: [ A , B , r , U , V ] = canoncorr ( X , Y ) Canonical correlation analysis. Given X (size k * m ) and Y ( k * n ), returns projection matrices of canonical coefficients A (size m * d , where d is the smallest of m , n , d ) and B (size m * d ); the canonical correlations r (1* d , arranged in decreasing order); the canonical variables U , V (both k * d , with orthonormal columns); and stats , a structure containing results from Bartlett’s chi-square and Rao’s F tests of significance. See also: princomp # name: # type: sq_string # elements: 1 # length: 31 Canonical correlation analysis. # name: # type: sq_string # elements: 1 # length: 8 cmdscale # name: # type: sq_string # elements: 1 # length: 2898 statistics: Y = cmdscale ( D ) statistics: [ Y , e ] = cmdscale ( D ) statistics: Y = cmdscale ( D , p ) statistics: [ Y , e ] = cmdscale ( D , p ) Classical multidimensional scaling of a matrix. Takes an n by n distance (or difference, similarity, or dissimilarity) matrix D . Returns Y , a matrix of n points with coordinates in p dimensional space which approximate those distances (or differences, similarities, or dissimilarities). Also returns the eigenvalues e of B = -1/2 * J * ( D .^2) * J , where J = eye( n ) - ones( n , n )/ n . p , the number of columns of Y , is equal to the number of positive real eigenvalues of B . The optional argument p is a positive integer between 1 and n that specifies the maximum dimensionality of the desired embedding Y . If specified, Y will have at most p columns, and the returned eigenvalues e will be a vector of exactly length p . Specifying p can be useful for reducing dimensions for visualization (e.g., p = 2 ). D can be a full or sparse matrix or a vector of length n *( n -1)/2 containing the upper triangular elements (like the output of the pdist function). It must be symmetric with non-negative entries whose values are further restricted by the type of matrix being represented: * If D is either a distance, dissimilarity, or difference matrix, then it must have zero entries along the main diagonal. In this case the points Y equal or approximate the distances given by D . * If D is a similarity matrix, the elements must all be less than or equal to one, with ones along the main diagonal. In this case the points Y equal or approximate the distances given by D = sqrt(ones( n , n )- D ) . D is a Euclidean matrix if and only if B is positive semi-definite. When this is the case, then Y is an exact representation of the distances given in D . If D is non-Euclidean, Y only approximates the distance given in D . The approximation used by cmdscale minimizes the statistical loss function known as strain . The returned Y is an n by p matrix showing possible coordinates of the points in p dimensional space ( p < n ). The columns correspond to the positive eigenvalues of B in descending order. A translation, rotation, or reflection of the coordinates given by Y will satisfy the same distance matrix up to the limits of machine precision. For any k <= p , if the largest k positive eigenvalues of B are significantly greater in absolute magnitude than its other eigenvalues, the first k columns of Y provide a k -dimensional reduction of Y which approximates the distances given by D . The optional return e can be used to consider various values of k , or to evaluate the accuracy of specific dimension reductions (e.g., k = 2 ). Reference: Ingwer Borg and Patrick J.F. Groenen (2005), Modern Multidimensional Scaling, Second Edition, Springer, ISBN: 978-0-387-25150-9 (Print) 978-0-387-28981-6 (Online) See also: pdist # name: # type: sq_string # elements: 1 # length: 47 Classical multidimensional scaling of a matrix. # name: # type: sq_string # elements: 1 # length: 8 factoran # name: # type: sq_string # elements: 1 # length: 4886 statistics: lambda = factoran ( X , m ) statistics: [ lambda , psi ] = factoran ( X , m ) statistics: [ lambda , psi , T ] = factoran ( X , m ) statistics: [ lambda , psi , T , stats ] = factoran ( X , m ) statistics: [ lambda , psi , T , stats , F ] = factoran ( X , m ) statistics: […] = factoran (…, Name , Value ) Common factor analysis. lambda = factoran ( X , m ) fits the common factor model with m common factors to the N -by- P data matrix X , whose rows are observations and columns are variables, and returns the P -by- m matrix lambda of factor loadings. The model is x = mu + lambda * f + e where f are the common factors and e the variable-specific errors, uncorrelated with each other and with f . The analysis is carried out on the correlation matrix, so the loadings are in standardized units and a variable’s communality sum ( lambda (i,:) .^ 2) and its specific variance psi (i) sum to one. [ lambda , psi ] = factoran (…) also returns the P -by-1 vector psi of specific variances. [ lambda , psi , T ] = factoran (…) also returns the m -by- m rotation matrix T that was applied to the loadings. It is the identity when 'Rotate' is 'none' . [ lambda , psi , T , stats ] = factoran (…) also returns a structure stats with the fields Field Description loglike the maximized log-likelihood, up to a constant. dfe the error degrees of freedom, (( p - m )^2 - p - m ) / 2 . chisq the likelihood ratio statistic testing m common factors against an unrestricted covariance. p the significance of chisq . The last two are present only when they can be computed: they are omitted when the degrees of freedom are not positive, when a specific variance has reached its lower bound (a Heywood case, where the likelihood is on the boundary), and for the 'paf' extraction, which does not maximize a likelihood. Test for them with isfield before using them. [ lambda , psi , T , stats , F ] = factoran (…) also returns the N -by- m matrix F of predicted factor scores. Scores are not available from a covariance matrix, only from data. Name-Value pairs Name Value 'Extraction' How the loadings are estimated, either 'ml' (default) for maximum likelihood or 'paf' for principal axis factoring. An Octave extension ; MATLAB fits by maximum likelihood only. See the note below. 'Xtype' Whether X holds 'data' (default) or a 'covariance' (or correlation) matrix. 'Nobs' The number of observations behind a covariance matrix. Required for stats when 'Xtype' is 'covariance' . 'Delta' The lower bound on the specific variances, a scalar in [0, 1) (default 0.005 ). Bounding them away from zero keeps the likelihood finite; a solution that reaches the bound is a Heywood case and is reported by a warning. 'Rotate' The rotation applied to the loadings, passed to rotatefactors : 'varimax' (default), 'none' , 'quartimax' , 'equamax' , 'parsimax' , 'orthomax' , or 'promax' . 'Normalize' Whether the rotation normalizes the rows of the loadings (Kaiser normalization), 'on' (default) or 'off' . 'Power' The exponent of the 'promax' target, a scalar not less than 1 (default 4). 'Scores' How F is predicted, either 'wls' (default, also named 'Bartlett' ) or 'regression' (also named 'Thomson' ). 'Maxit' The iteration limit of the extraction (default 500 ). 'Tolerance' The convergence tolerance of the extraction (default 1e-8 ). Choosing the extraction The two extractions fit the same model but estimate it differently, and they answer to different circumstances. 'ml' maximizes the likelihood of a multivariate normal, and is what MATLAB’s factoran does. Use it when you want the likelihood ratio test in stats to decide how many factors the data support, and when the data are plausibly normal. It can fail to converge, or push a specific variance to zero, when the model asks for more factors than the data hold. 'paf' iterates communalities on the reduced correlation matrix. It makes no distributional assumption and is stable where maximum likelihood struggles, which is why it remains available here, but it provides no likelihood and therefore no test: stats carries only dfe . The two agree closely when the model fits the data well and diverge when it does not, so a large difference between them is itself informative. Number of factors m must leave the model identified, that is ( p - m )^2 >= p + m . With six variables at most three factors can be fitted, and only the smaller counts leave degrees of freedom to test. References Lawley, D. N., and Maxwell, A. E., Factor Analysis as a Statistical Method , 2nd Edition, Butterworths, 1971. Joreskog, K. G., "Some contributions to maximum likelihood factor analysis", Psychometrika 32(4), 443-482, 1967. Harman, H. H., Modern Factor Analysis , 3rd Edition, University of Chicago Press, 1976. See also: rotatefactors, pca, pcacov, princomp, barttest # name: # type: sq_string # elements: 1 # length: 23 Common factor analysis. # name: # type: sq_string # elements: 1 # length: 7 mdscale # name: # type: sq_string # elements: 1 # length: 3389 statistics: Y = mdscale ( D , p ) statistics: [ Y , stress ] = mdscale ( D , p ) statistics: [ Y , stress , disparities ] = mdscale ( D , p ) statistics: […] = mdscale (…, Name , Value ) Nonclassical (metric and nonmetric) multidimensional scaling. Y = mdscale ( D , p ) takes a matrix of dissimilarities D and returns a configuration Y of n points in p dimensions (an n × p matrix) whose interpoint distances approximate D , by minimizing a stress criterion. D may be given either as a full n × n symmetric matrix with zero diagonal, or as the vector of the n ( n - 1) / 2 upper-triangle dissimilarities returned by pdist . [ Y , stress , disparities ] = mdscale (…) also returns the final value of the stress criterion and the disparities (the transformed dissimilarities the distances are fitted to). For the nonmetric criteria the disparities are the monotone (isotonic) regression of the dissimilarities onto the distances; for the metric criteria they are the dissimilarities themselves. Name/Value pairs: 'Criterion' The goodness-of-fit criterion to minimize, one of: 'stress' (default) Kruskal’s normalized stress-1, sqrt (sum ((d - dhat)^2) / sum (d^2)) , computed from disparities dhat (nonmetric). 'sstress' Squared stress, sqrt (sum ((d^2 - dhat^2)^2) / sum (d^4)) (nonmetric). 'metricstress' Metric stress, sqrt (sum ((d - delta)^2) / sum (delta^2)) , fitting the dissimilarities delta directly. 'metricsstress' Metric squared stress, sqrt (sum ((d^2 - delta^2)^2) / sum (delta^4)) . 'sammon' Sammon’s nonlinear mapping criterion, (1 / sum (delta)) sum ((d - delta)^2 / delta) . 'strain' The classical scaling criterion; equivalent to cmdscale . 'Weights' A matrix or vector of nonnegative weights, the same size as D , weighting each dissimilarity in the criterion. 'Start' The initial configuration: 'cmdscale' (default, classical scaling), 'random' , or an explicit n × p matrix. 'Replicates' The number of times to repeat the minimization from different starting points, keeping the best (lowest-stress) result. The default is 1. 'Options' A structure of algorithm options (as returned by statset ) whose MaxIter , TolFun , and TolX fields control the iterative minimization. Non-uniqueness of the solution A stress-minimizing configuration is defined only up to a translation, rotation, and reflection, because these leave all interpoint distances (and hence the stress) unchanged. mdscale removes this ambiguity by returning Y centred at the origin and rotated to its principal axes (with the largest-magnitude coordinate on each axis made positive), matching the convention used by MATLAB. Beyond that rigid ambiguity, the nonmetric criteria ( 'stress' and 'sstress' ) are non-convex and typically have several local minima; the one reached depends on the starting configuration and the details of the optimizer. As a result the returned configuration for these criteria may differ from the one another program (including MATLAB) reports even when the stress value agrees, and different runs may find configurations with slightly different stress. Use 'Replicates' with a 'random' start to search for a lower-stress solution. The metric criteria and 'strain' have an essentially unique solution and are reproducible up to the rigid ambiguity above. See also: cmdscale, pdist, squareform, procrustes, statset # name: # type: sq_string # elements: 1 # length: 61 Nonclassical (metric and nonmetric) multidimensional scaling. # name: # type: sq_string # elements: 1 # length: 4 nnmf # name: # type: sq_string # elements: 1 # length: 1713 statistics: [ W , H ] = nnmf ( A , K ) statistics: [ W , H , D ] = nnmf ( A , K ) statistics: […] = nnmf (…, Name , Value ) Nonnegative matrix factorization. [ W , H ] = nnmf ( A , K ) factors the nonnegative N × M matrix A into nonnegative factors W ( N × K ) and H ( K × M ) whose product approximates A , by minimizing the root-mean-square residual between A and W * H . K , the number of factors, is typically smaller than N and M . [ W , H , D ] = nnmf (…) also returns the root-mean-square residual D , that is norm ( A - W * H , "fro") / sqrt (N * M) . The factorization is not unique: the returned factors are normalized so that the rows of H have unit length, and the columns of W (and the corresponding rows of H ) are ordered by decreasing length of the columns of W . Because the objective is not convex, the iteration converges to a local minimum that depends on the starting point; use 'Replicates' to try several random starts and keep the best. Name/Value pairs: 'Algorithm' 'als' (default) for alternating least squares, or 'mult' for multiplicative updates. Alternating least squares usually converges faster and more reliably; multiplicative updates are more sensitive to the starting point. 'W0' An N × K initial value for W . 'H0' A K × M initial value for H . 'Replicates' The number of times to repeat the factorization from new random starting points, keeping the result with the smallest residual. The default is 1. Ignored for a starting point fixed by both 'W0' and 'H0' . 'Options' A structure of algorithm options (as returned by statset ) whose MaxIter , TolFun , and TolX fields control the iteration. See also: pca, statset # name: # type: sq_string # elements: 1 # length: 33 Nonnegative matrix factorization. # name: # type: sq_string # elements: 1 # length: 3 pca # name: # type: sq_string # elements: 1 # length: 2882 statistics: coeff = pca ( x ) statistics: coeff = pca ( x , Name , Value ) statistics: [ coeff , score , latent ] = pca (…) statistics: [ coeff , score , latent , tsquared ] = pca (…) statistics: [ coeff , score , latent , tsquared , explained , mu ] = pca (…) Performs a principal component analysis on a data matrix. A principal component analysis of a data matrix of N observations in a D dimensional space returns a D×D transformation matrix, to perform a change of basis on the data. The first component of the new basis is the direction that maximizes the variance of the projected data. Input argument: x : a N×D data matrix The following Name , Value pair arguments can be used: 'Algorithm' defines the algorithm to use: 'svd' (default), for singular value decomposition 'eig' for eigenvalue decomposition 'Centered' is a boolean indicator for centering the observation data. It is true by default. 'Economy' is a boolean indicator for the economy size output. It is true by default. Hence, pca returns only the elements of latent that are not necessarily zero, and the corresponding columns of coeff and score , that is, when N <= D , only the first N - 1 . 'NumComponents' defines the number of components k to return. If k < p , then only the first k columns of coeff and score are returned. 'Rows' defines how to handle missing values: 'complete' (default), missing values are removed before computation. 'pairwise' (only valid when 'Algorithm' is 'eig' ), the covariance of rows with missing data is computed using the available data, but the covariance matrix could be not positive definite, which triggers the termination of pca . 'all' , missing values are not allowed, pca terminates with an error if there are any. 'Weights' defines observation weights as a vector of positive values of length N . 'VariableWeights' defines variable weights: a vector of positive values of length D . the string 'variance' to use the sample variance as weights. Return values: coeff : the principal component coefficients, a D×D transformation matrix score : the principal component scores, the representation of x in the principal component space latent : the principal component variances, i.e., the eigenvalues of the covariance matrix of x tsquared : Hotelling’s T-squared Statistic for each observation in x explained : the percentage of the variance explained by each principal component mu : the estimated mean of each variable of x , it is zero if the data are not centered Matlab compatibility note: the alternating least square method ’als’ and associated options ’Coeff0’, ’Score0’, and ’Options’ are not yet implemented References Jolliffe, I. T., Principal Component Analysis, 2nd Edition, Springer, 2002 See also: barttest, factoran, pcacov, pcares # name: # type: sq_string # elements: 1 # length: 57 Performs a principal component analysis on a data matrix. # name: # type: sq_string # elements: 1 # length: 6 pcacov # name: # type: sq_string # elements: 1 # length: 1336 statistics: coeff = pcacov ( K ) statistics: [ coeff , latent ] = pcacov ( K ) statistics: [ coeff , latent , explained ] = pcacov ( K ) Perform principal component analysis on covariance matrix coeff = pcacov ( K ) performs principal component analysis on the square covariance matrix K and returns the principal component coefficients, also known as loadings. The columns are in order of decreasing component variance. [ coeff , latent ] = pcacov ( K ) also returns a vector with the principal component variances, i.e. the eigenvalues of K . latent has a length of size ( coeff , 1) . [ coeff , latent , explained ] = pcacov ( K ) also returns a vector with the percentage of the total variance explained by each principal component. explained has the same size as latent . The entries in explained range from 0 (none of the variance is explained) to 100 (all of the variance is explained). pcacov does not standardize K to have unit variances. In order to perform principal component analysis on standardized variables, use the correlation matrix R = K ./ ( SD * SD ') , where SD = sqrt (diag ( K )) , in place of K . To perform principal component analysis directly on the data matrix, use pca . References Jolliffe, I. T., Principal Component Analysis, 2nd Edition, Springer, 2002 See also: barttest, factoran, pcares, pca # name: # type: sq_string # elements: 1 # length: 57 Perform principal component analysis on covariance matrix # name: # type: sq_string # elements: 1 # length: 6 pcares # name: # type: sq_string # elements: 1 # length: 1174 statistics: residuals = pcares ( x , ndim ) statistics: [ residuals , reconstructed ] = pcares ( x , ndim ) Calculate residuals from principal component analysis. residuals = pcares ( x , ndim ) returns the residuals obtained by retaining ndim principal components of the N×D matrix x . Rows of x correspond to observations, columns of x correspond to variables. ndim is a scalar and must be less than or equal to D . residuals is a matrix of the same size as x . Use the data matrix, not the covariance matrix, with this function. [ residuals , reconstructed ] = pcares ( x , ndim ) returns the reconstructed observations, i.e. the approximation to x obtained by retaining its first ndim principal components. pcares does not normalize the columns of x . Use pcares (zscore ( x ), ndim ) in order to perform the principal components analysis based on standardized variables, i.e. based on correlations. Use pcacov in order to perform principal components analysis directly on a covariance or correlation matrix without constructing residuals. References Jolliffe, I. T., Principal Component Analysis, 2nd Edition, Springer, 2002 See also: factoran, pcacov, pca # name: # type: sq_string # elements: 1 # length: 54 Calculate residuals from principal component analysis. # name: # type: sq_string # elements: 1 # length: 4 ppca # name: # type: sq_string # elements: 1 # length: 2268 statistics: coeff = ppca ( Y , K ) statistics: [ coeff , score ] = ppca ( Y , K ) statistics: [ coeff , score , pcvar ] = ppca ( Y , K ) statistics: [ coeff , score , pcvar , mu ] = ppca ( Y , K ) statistics: [ coeff , score , pcvar , mu , v ] = ppca ( Y , K ) statistics: [ coeff , score , pcvar , mu , v , S ] = ppca ( Y , K ) statistics: […] = ppca (…, Name , Value ) Probabilistic principal component analysis. coeff = ppca ( Y , K ) fits a probabilistic principal component analysis (PPCA) model with K components to the N × P data matrix Y (rows are observations, columns are variables) and returns the P × K matrix coeff of orthonormal principal component coefficients, ordered by decreasing component variance. Y may contain NaN values marking missing observations; the model is fitted by an expectation-maximization algorithm that accounts for them. K must be a positive integer smaller than P . [ coeff , score , pcvar , mu , v , S ] = ppca (…) returns further outputs: score The N × K principal component scores (the data projected onto the components; missing entries are reconstructed from the model before projection). pcvar A K × 1 vector of the principal component variances (the variance explained by each component). mu A 1 × P vector of the estimated mean of Y . v The estimated residual (isotropic noise) variance. S A structure with the fitted model details: the loadings W , the expected scores Xexp , the reconstruction Recon , the number of iterations NumIter , and the root-mean-square residual RMSResid . Name/Value pairs control the fit: 'W0' A P × K initial value for the loadings used by the expectation-maximization algorithm (missing-data case). 'Options' A structure of algorithm options, as returned by statset , whose MaxIter , TolFun , and TolX fields set the maximum number of iterations and the convergence tolerances of the expectation-maximization algorithm. When Y has no missing values the model is fitted directly from the eigendecomposition of its covariance matrix; coeff , pcvar , and v are then the principal component directions, the leading variances, and the mean of the trailing variances, respectively. See also: pca, pcacov, pcares, factoran, barttest # name: # type: sq_string # elements: 1 # length: 43 Probabilistic principal component analysis. # name: # type: sq_string # elements: 1 # length: 8 princomp # name: # type: sq_string # elements: 1 # length: 1087 statistics: COEFF = princomp ( X ) statistics: [ COEFF , SCORE ] = princomp ( X ) statistics: [ COEFF , SCORE , latent ] = princomp ( X ) statistics: [ COEFF , SCORE , latent , tsquare ] = princomp ( X ) statistics: […] = princomp ( X , "econ") Performs a principal component analysis on a NxP data matrix X. COEFF : returns the principal component coefficients SCORE : returns the principal component scores, the representation of X in the principal component space LATENT : returns the principal component variances, i.e., the eigenvalues of the covariance matrix X. TSQUARE : returns Hotelling’s T-squared Statistic for each observation in X [...] = princomp(X,’econ’) returns only the elements of latent that are not necessarily zero, and the corresponding columns of COEFF and SCORE, that is, when n <= p, only the first n-1. This can be significantly faster when p is much larger than n. In this case the svd will be applied on the transpose of the data matrix X References Jolliffe, I. T., Principal Component Analysis, 2nd Edition, Springer, 2002 # name: # type: sq_string # elements: 1 # length: 63 Performs a principal component analysis on a NxP data matrix X. # name: # type: sq_string # elements: 1 # length: 10 procrustes # name: # type: sq_string # elements: 1 # length: 2268 statistics: d = procrustes ( X , Y ) statistics: d = procrustes ( X , Y , param1 , value1 , …) statistics: [ d , Z ] = procrustes (…) statistics: [ d , Z , transform ] = procrustes (…) Procrustes Analysis. d = procrustes ( X , Y ) computes a linear transformation of the points in the matrix Y to best conform them to the points in the matrix X by minimizing the sum of squared errors, as the goodness of fit criterion, which is returned in d as a dissimilarity measure. d is standardized by a measure of the scale of X , given by sum (sum ((X - repmat (mean (X, 1), size (X, 1), 1)) .^ 2, 1)) i.e., the sum of squared elements of a centered version of X . However, if X comprises repetitions of the same point, the sum of squared errors is not standardized. X and Y must have the same number of points (rows) and procrustes matches the i -th point in Y to the i -th point in X . Points in Y can have smaller dimensions (columns) than those in X , but not the opposite. Missing dimensions in Y are added with padding columns of zeros as necessary to match the the dimensions in X . [ d , Z ] = procrustes ( X , Y ) also returns the transformed values in Y . [ d , Z , transform ] = procrustes ( X , Y ) also returns the transformation that maps Y to Z . transform is a structure with fields: c the translation component T the orthogonal rotation and reflection component b the scale component So that Z = transform . b * Y * transform . T + transform . c procrustes can take two optional parameters as Name-Value pairs. […] = procrustes (…, 'Scaling' , false ) computes a transformation that does not include scaling, that is transform . b = 1. Setting 'Scaling' to true includes a scaling component, which is the default. […] = procrustes (…, 'Reflection' , false ) computes a transformation that does not include a reflection component, that is transform . T = 1. Setting 'Reflection' to true forces the solution to include a reflection component in the computed transformation, that is transform . T = -1. […] = procrustes (…, 'Reflection' , 'best' ) computes the best fit procrustes solution, which may or may not include a reflection component, which is the default. See also: cmdscale # name: # type: sq_string # elements: 1 # length: 20 Procrustes Analysis. # name: # type: sq_string # elements: 1 # length: 4 rica # name: # type: sq_string # elements: 1 # length: 2771 statistics: Mdl = rica ( X , Q ) statistics: Mdl = rica ( X , Q , Name , Value ) Reconstruction independent component analysis (RICA) for feature extraction. Mdl = rica ( X , Q ) learns Q features from the N × P data matrix X (rows are observations, columns are predictors) and returns a ReconstructionICA object Mdl . Apply the learned transformation to data with transform ( Mdl , X ) , which returns X * Mdl .TransformWeights . The P × Q weight matrix (with unit-length columns) minimizes the objective Lambda * || X * W * W ' - X ||_F^2 + sum (sum ( g ( X * W ))) over the transformation weights W , combining a reconstruction cost with a sparsity contrast g applied elementwise and selected by 'ContrastFcn' . Name/Value pairs: 'IterationLimit' Maximum number of iterations (default 1000). 'Lambda' Weight of the reconstruction term (default 1). 'Standardize' Logical; center and scale each predictor before fitting (default false ). 'ContrastFcn' The sparsity contrast g applied to each element z of X × W : 'logcosh' (default), which is 0.5 × log (cosh (2 × z)) ; 'exp' , which is -exp (-z^2 / 2) ; or 'sqrt' , which is sqrt (z^2 + 1e-8) , a smooth stand-in for abs (z) . 'InitialTransformWeights' A P × Q initial value for the weights. The default is random. 'NonGaussianityIndicator' A Q -element vector of +1 and -1 , one per learned feature, setting the sign its contrast term carries in the objective: +1 seeks a super-Gaussian (sparse) feature and -1 a sub-Gaussian (spread) one. The default is all +1 . 'GradientTolerance' , 'StepTolerance' Stop once the gradient’s or the step’s infinity norm falls to or below the given value (default 1e-6 each). They govern the fit only under 'Solver', 'lbfgs' ; the 'quasinewton' solver runs to its own tighter internal tolerances and records these without acting on them. 'Solver' 'quasinewton' (default) minimizes through Octave’s fminunc , which carries a full inverse Hessian. 'lbfgs' selects the limited-memory BFGS solver MATLAB uses, holding as many curvature pairs as the transform has parameters, and is several times faster here. It stops where 'GradientTolerance' and 'StepTolerance' say to, so a value tighter than the default carries it further. Note on reproducibility The RICA objective is not convex and is minimized by a quasi-Newton solver, so the learned weights depend on the starting point and the solver, and are only defined up to a permutation and sign of the feature columns. Different runs (or different software, including MATLAB) may return different weights that nonetheless describe an equally valid feature transformation. Fix 'InitialTransformWeights' for a reproducible result. See also: ReconstructionICA, sparsefilt, pca # name: # type: sq_string # elements: 1 # length: 76 Reconstruction independent component analysis (RICA) for feature extraction. # name: # type: sq_string # elements: 1 # length: 13 rotatefactors # name: # type: sq_string # elements: 1 # length: 2795 statistics: B = rotatefactors ( A ) statistics: B = rotatefactors ( A , Name , Value , …) statistics: [ B , T ] = rotatefactors (…) Rotate a factor-loading matrix. B = rotatefactors ( A ) rotates the D × M factor loadings matrix A ( D observed variables, M factors) to the 'varimax' criterion and returns the rotated loadings B , the same size as A . [ B , T ] = rotatefactors (…) also returns the M × M rotation matrix T , so that B = A * T . For the orthogonal methods T is orthonormal ( T ' * T is the identity); for the oblique methods ( 'promax' and oblique 'procrustes' ) it is a general invertible matrix. The rotation is controlled by Name / Value pairs: 'Method' The rotation criterion, one of: 'varimax' (default) Orthomax with a criterion coefficient of 1; maximizes the variance of the squared loadings within each factor. 'quartimax' Orthomax with a coefficient of 0; simplifies the description of each variable. 'equamax' Orthomax with a coefficient of M / 2 . 'parsimax' Orthomax with a coefficient of D (M - 1) / (D + M - 2) . 'orthomax' General orthomax with the coefficient given by 'Coeff' . 'promax' Oblique rotation obtained by fitting an oblique transformation to a target built from a 'varimax' solution raised to the power 'Power' . 'procrustes' Rotation towards the 'Target' matrix, either orthogonal or oblique according to 'Type' . 'Normalize' 'on' (default) applies Kaiser normalization (each row of A is scaled to unit length before the orthomax rotation and unscaled afterwards); 'off' disables it. Ignored by 'procrustes' . 'Reltol' Relative convergence tolerance for the iterative orthomax rotation. The default is sqrt (eps) . 'Maxit' Maximum number of iterations for the iterative orthomax rotation. The default is 250. 'Coeff' The orthomax coefficient used when 'Method' is 'orthomax' . The default is 1 (equivalent to 'varimax' ). 'Power' The power used to build the 'promax' target, a scalar greater than or equal to 1. The default is 4. 'Target' The target loadings matrix for 'procrustes' , the same size as A . Required for that method. 'Type' 'oblique' (default) or 'orthogonal' , selecting the kind of 'procrustes' rotation. The default follows MATLAB, whose 'procrustes' rotation is oblique unless told otherwise. Note on the orthomax family: for coefficients up to 1 ( 'varimax' , 'quartimax' , and small 'orthomax' ) the rotation follows the same successive-SVD iteration as MATLAB and stops at the same relative tolerance. For larger coefficients ( 'equamax' , 'parsimax' ) that iteration does not converge, so a monotonically convergent pairwise algorithm is used instead; it reaches the same optimum as MATLAB to that solution’s own convergence precision. See also: factoran, pca, pcacov, procrustes # name: # type: sq_string # elements: 1 # length: 31 Rotate a factor-loading matrix. # name: # type: sq_string # elements: 1 # length: 10 sparsefilt # name: # type: sq_string # elements: 1 # length: 2209 statistics: Mdl = sparsefilt ( X , Q ) statistics: Mdl = sparsefilt ( X , Q , Name , Value ) Sparse filtering for feature extraction. Mdl = sparsefilt ( X , Q ) learns Q features from the N × P data matrix X (rows are observations, columns are predictors) and returns a SparseFiltering object Mdl . Apply the learned transformation to data with transform ( Mdl , X ) . The N × Q features returned by transform are the soft-absolute activations sqrt (( X * W ) .^ 2 + 1e-8) , normalized first across observations (each feature) and then across features (each observation). The P × Q weight matrix W minimizes the sum of those features plus an L2 penalty Lambda * || W ||_F^2 , driving the features to be sparse. Name/Value pairs: 'IterationLimit' Maximum number of iterations (default 1000). 'Lambda' Weight of the L2 penalty on the transform weights (default 1). 'Standardize' Logical; center and scale each predictor before fitting (default false ). 'InitialTransformWeights' A P × Q initial value for the weights. The default is random. 'GradientTolerance' , 'StepTolerance' Stop once the gradient’s or the step’s infinity norm falls to or below the given value (default 1e-6 each). They govern the fit only under 'Solver', 'lbfgs' ; the 'quasinewton' solver runs to its own tighter internal tolerances and records these without acting on them. 'Solver' 'quasinewton' (default) minimizes through Octave’s fminunc , which carries a full inverse Hessian. 'lbfgs' selects the limited-memory BFGS solver MATLAB uses, holding as many curvature pairs as the transform has parameters, and is several times faster here. It stops where 'GradientTolerance' and 'StepTolerance' say to, so a value tighter than the default carries it further. Note on reproducibility The sparse filtering objective is not convex and is minimized by a quasi-Newton solver, so the learned weights depend on the starting point and the solver. Different runs (or different software, including MATLAB) may return different weights that nonetheless describe an equally valid feature transformation. Fix 'InitialTransformWeights' for a reproducible result. See also: SparseFiltering, rica, pca # name: # type: sq_string # elements: 1 # length: 40 Sparse filtering for feature extraction. # name: # type: sq_string # elements: 1 # length: 4 tsne # name: # type: sq_string # elements: 1 # length: 2948 statistics: Y = tsne ( X ) statistics: [ Y , loss ] = tsne ( X ) statistics: […] = tsne (…, Name , Value ) t-distributed stochastic neighbor embedding (t-SNE). Y = tsne ( X ) embeds the N × P data matrix X (rows are observations) into a low-dimensional space and returns the N × NumDimensions matrix Y of embedded points, whose pairwise (Student-t) affinities approximate the Gaussian affinities of the rows of X . [ Y , loss ] = tsne (…) also returns the Kullback-Leibler divergence loss between the two affinity distributions at the returned embedding. Name/Value pairs: 'Algorithm' 'exact' (default) forms the affinities and the gradient over every pair of points, which costs O(N^2) in time and memory at each iteration. 'barneshut' approximates both: the high-dimensional affinities are kept only over each point’s 3 × Perplexity nearest neighbours, and the repulsive part of the gradient is summed over a space-partitioning tree of the embedding, giving O(N log N) . Use it when N is large enough that the exact algorithm is slow or cannot allocate; on this machine the two cost the same at about N = 500 and 'barneshut' is eight times faster at N = 2000 . The two do not return the same embedding, and their loss values are not comparable either: the divergence is summed over the pairs that carry an affinity, and 'barneshut' keeps far fewer of them. 'Theta' The tree opening criterion for 'barneshut' , a non-negative scalar (default 0.5). A cell of the tree is collapsed to its centre of mass when its width is smaller than Theta times its distance from the point being pushed, so a larger value is faster and coarser. 0 collapses nothing and makes the repulsion exact, at O(N^2) ; note that this still leaves the affinities sparse, so it does not reproduce 'exact' . Ignored by 'exact' . 'Distance' The distance metric used for the high-dimensional affinities, as accepted by pdist (default 'euclidean' ). 'NumDimensions' The dimension of the embedding Y (default min (P, 2) ). 'NumPCAComponents' If positive, reduce X to this many principal components before embedding (default 0, no reduction). 'Standardize' Logical; center and scale each column of X before embedding (default false ). 'Perplexity' The effective number of local neighbors (default 30). It must be smaller than N . 'Exaggeration' Tightness factor applied to the high-dimensional affinities for the first 100 iterations (default 4, no less than 1). 'LearnRate' The learning rate of the optimization (default 500). 'InitialY' An N × NumDimensions initial embedding (default 1e-4 * randn ). 'Options' A structure (as returned by statset ) whose MaxIter (default 1000) and TolFun (default 1e-10 ) fields control the optimization. The embedding is not unique: it depends on the initial configuration and the random state. Set 'InitialY' (or the random seed) for a reproducible result. See also: pca, pdist, statset # name: # type: sq_string # elements: 1 # length: 52 t-distributed stochastic neighbor embedding (t-SNE). statistics-release-1.9.2/inst/Dimensionality_Reduction/factoran.m000066400000000000000000000733451524624707500253170ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{lambda} =} factoran (@var{X}, @var{m}) ## @deftypefnx {statistics} {[@var{lambda}, @var{psi}] =} factoran (@var{X}, @var{m}) ## @deftypefnx {statistics} {[@var{lambda}, @var{psi}, @var{T}] =} factoran (@var{X}, @var{m}) ## @deftypefnx {statistics} {[@var{lambda}, @var{psi}, @var{T}, @var{stats}] =} factoran (@var{X}, @var{m}) ## @deftypefnx {statistics} {[@var{lambda}, @var{psi}, @var{T}, @var{stats}, @var{F}] =} factoran (@var{X}, @var{m}) ## @deftypefnx {statistics} {[@dots{}] =} factoran (@dots{}, @var{Name}, @var{Value}) ## ## Common factor analysis. ## ## @code{@var{lambda} = factoran (@var{X}, @var{m})} fits the common factor ## model with @var{m} common factors to the @math{N}-by-@math{P} data matrix ## @var{X}, whose rows are observations and columns are variables, and returns ## the @math{P}-by-@var{m} matrix @var{lambda} of factor loadings. The model is ## ## @example ## @var{x} = @var{mu} + @var{lambda} * @var{f} + @var{e} ## @end example ## ## @noindent ## where @var{f} are the common factors and @var{e} the variable-specific ## errors, uncorrelated with each other and with @var{f}. The analysis is ## carried out on the correlation matrix, so the loadings are in standardized ## units and a variable's communality @code{sum (@var{lambda}(i,:) .^ 2)} and ## its specific variance @code{@var{psi}(i)} sum to one. ## ## @code{[@var{lambda}, @var{psi}] = factoran (@dots{})} also returns the ## @math{P}-by-1 vector @var{psi} of specific variances. ## ## @code{[@var{lambda}, @var{psi}, @var{T}] = factoran (@dots{})} also returns ## the @var{m}-by-@var{m} rotation matrix @var{T} that was applied to the ## loadings. It is the identity when @qcode{'Rotate'} is @qcode{'none'}. ## ## @code{[@var{lambda}, @var{psi}, @var{T}, @var{stats}] = factoran (@dots{})} ## also returns a structure @var{stats} with the fields ## ## @multitable @columnfractions 0.15 0.8 ## @headitem Field @tab Description ## @item @qcode{loglike} @tab the maximized log-likelihood, up to a constant. ## @item @qcode{dfe} @tab the error degrees of freedom, ## @code{((@var{p} - @var{m})^2 - @var{p} - @var{m}) / 2}. ## @item @qcode{chisq} @tab the likelihood ratio statistic testing @var{m} ## common factors against an unrestricted covariance. ## @item @qcode{p} @tab the significance of @var{chisq}. ## @end multitable ## ## @noindent ## The last two are present only when they can be computed: they are omitted ## when the degrees of freedom are not positive, when a specific variance has ## reached its lower bound (a Heywood case, where the likelihood is on the ## boundary), and for the @qcode{'paf'} extraction, which does not maximize a ## likelihood. Test for them with @code{isfield} before using them. ## ## @code{[@var{lambda}, @var{psi}, @var{T}, @var{stats}, @var{F}] = factoran ## (@dots{})} also returns the @math{N}-by-@var{m} matrix @var{F} of predicted ## factor scores. Scores are not available from a covariance matrix, only from ## data. ## ## @subheading Name-Value pairs ## ## @multitable @columnfractions 0.18 0.77 ## @headitem Name @tab Value ## @item @qcode{'Extraction'} @tab How the loadings are estimated, either ## @qcode{'ml'} (default) for maximum likelihood or @qcode{'paf'} for principal ## axis factoring. @strong{An Octave extension}; MATLAB fits by maximum ## likelihood only. See the note below. ## ## @item @qcode{'Xtype'} @tab Whether @var{X} holds @qcode{'data'} (default) or ## a @qcode{'covariance'} (or correlation) matrix. ## ## @item @qcode{'Nobs'} @tab The number of observations behind a covariance ## matrix. Required for @var{stats} when @qcode{'Xtype'} is ## @qcode{'covariance'}. ## ## @item @qcode{'Delta'} @tab The lower bound on the specific variances, a ## scalar in @code{[0, 1)} (default @code{0.005}). Bounding them away from zero ## keeps the likelihood finite; a solution that reaches the bound is a Heywood ## case and is reported by a warning. ## ## @item @qcode{'Rotate'} @tab The rotation applied to the loadings, passed to ## @code{rotatefactors}: @qcode{'varimax'} (default), @qcode{'none'}, ## @qcode{'quartimax'}, @qcode{'equamax'}, @qcode{'parsimax'}, ## @qcode{'orthomax'}, or @qcode{'promax'}. ## ## @item @qcode{'Normalize'} @tab Whether the rotation normalizes the rows of ## the loadings (Kaiser normalization), @qcode{'on'} (default) or @qcode{'off'}. ## ## @item @qcode{'Power'} @tab The exponent of the @qcode{'promax'} target, a ## scalar not less than 1 (default 4). ## ## @item @qcode{'Scores'} @tab How @var{F} is predicted, either @qcode{'wls'} ## (default, also named @qcode{'Bartlett'}) or @qcode{'regression'} (also named ## @qcode{'Thomson'}). ## ## @item @qcode{'Maxit'} @tab The iteration limit of the extraction (default ## @code{500}). ## ## @item @qcode{'Tolerance'} @tab The convergence tolerance of the extraction ## (default @code{1e-8}). ## @end multitable ## ## @subheading Choosing the extraction ## ## The two extractions fit the same model but estimate it differently, and they ## answer to different circumstances. ## ## @qcode{'ml'} maximizes the likelihood of a multivariate normal, and is what ## MATLAB's @code{factoran} does. Use it when you want the likelihood ratio ## test in @var{stats} to decide how many factors the data support, and when ## the data are plausibly normal. It can fail to converge, or push a specific ## variance to zero, when the model asks for more factors than the data hold. ## ## @qcode{'paf'} iterates communalities on the reduced correlation matrix. It ## makes no distributional assumption and is stable where maximum likelihood ## struggles, which is why it remains available here, but it provides no ## likelihood and therefore no test: @var{stats} carries only @qcode{dfe}. ## ## The two agree closely when the model fits the data well and diverge when it ## does not, so a large difference between them is itself informative. ## ## @subheading Number of factors ## ## @var{m} must leave the model identified, that is ## @code{(@var{p} - @var{m})^2 >= @var{p} + @var{m}}. With six variables at ## most three factors can be fitted, and only the smaller counts leave degrees ## of freedom to test. ## ## @subheading References ## @enumerate ## @item ## Lawley, D. N., and Maxwell, A. E., @cite{Factor Analysis as a Statistical ## Method}, 2nd Edition, Butterworths, 1971. ## ## @item ## Joreskog, K. G., "Some contributions to maximum likelihood factor analysis", ## @cite{Psychometrika} 32(4), 443-482, 1967. ## ## @item ## Harman, H. H., @cite{Modern Factor Analysis}, 3rd Edition, University of ## Chicago Press, 1976. ## @end enumerate ## ## @seealso{rotatefactors, pca, pcacov, princomp, barttest} ## @end deftypefn function [lambda, psi, T, stats, F] = factoran (X, m, varargin) if (nargin < 2) print_usage (); endif ## ------------------------------------------------------------------ ## ## Options ## ------------------------------------------------------------------ ## extraction = "ml"; xtype = "data"; nobs = []; delta = 0.005; rotate = "varimax"; normalize = "on"; power = 4; scores = "wls"; maxit = 500; tolerance = 1e-8; if (mod (numel (varargin), 2) != 0) error ("factoran: Name-Value arguments must come in pairs."); endif for k = 1:2:numel (varargin) name = varargin{k}; if (! ischar (name)) error ("factoran: parameter name must be a character vector."); endif val = varargin{k+1}; switch (lower (name)) case "extraction" extraction = check_choice ("Extraction", val, {"ml", "paf"}); case "xtype" xtype = check_choice ("Xtype", val, {"data", "covariance"}); case "nobs" nobs = val; case "delta" delta = val; case "rotate" rotate = check_choice ("Rotate", val, {"none", "varimax", ... "quartimax", "equamax", "parsimax", "orthomax", "promax"}); case "normalize" normalize = check_choice ("Normalize", val, {"on", "off"}); case "power" power = val; case "scores" scores = check_choice ("Scores", val, ... {"wls", "bartlett", "regression", "thomson"}); case "maxit" maxit = val; case "tolerance" tolerance = val; otherwise error ("factoran: unknown parameter name '%s'.", name); endswitch endfor if (! (isscalar (delta) && isnumeric (delta) && isreal (delta) ... && delta >= 0 && delta < 1)) error ("factoran: DELTA must be a scalar in the range [0, 1)."); endif if (! (isscalar (maxit) && isnumeric (maxit) && maxit >= 1)) error ("factoran: MAXIT must be a positive integer."); endif if (! (isscalar (tolerance) && isnumeric (tolerance) && tolerance > 0)) error ("factoran: TOLERANCE must be a positive scalar."); endif ## The two spellings of each score predictor are the same thing if (strcmp (scores, "bartlett")) scores = "wls"; elseif (strcmp (scores, "thomson")) scores = "regression"; endif ## ------------------------------------------------------------------ ## ## Data or covariance ## ------------------------------------------------------------------ ## if (! (isnumeric (X) && isreal (X) && ismatrix (X) && ndims (X) == 2)) error ("factoran: X must be a real numeric matrix."); endif isdata = strcmp (xtype, "data"); if (isdata) if (any (isnan (X(:)))) error ("factoran: X must not contain missing values."); endif [n, p] = size (X); if (n < 2) error ("factoran: X must have at least two observations."); endif S = corr_from_data (X); else [p, pc] = size (X); if (p != pc) error ("factoran: a covariance matrix must be square."); endif if (any (any (abs (X - X') > sqrt (eps) * max (1, max (abs (X(:))))))) error ("factoran: a covariance matrix must be symmetric."); endif d = sqrt (diag (X)); if (any (d <= 0)) error ("factoran: a covariance matrix must have positive diagonal."); endif S = X ./ (d * d'); S = (S + S') / 2; n = nobs; endif if (! (isscalar (m) && isnumeric (m) && isreal (m) && m >= 0 ... && m == fix (m))) error ("factoran: M must be a non-negative integer."); endif dfe = ((p - m) ^ 2 - p - m) / 2; if (dfe < 0) error (strcat ("factoran: M is too large for %d variables; the model", ... " is not identified beyond %d factors."), ... p, max_factors (p)); endif ## ------------------------------------------------------------------ ## ## Extraction ## ------------------------------------------------------------------ ## heywood = false; if (m == 0) ## No common factors: the model is a diagonal covariance, which on a ## correlation matrix is the identity, so the fit tests independence. L = zeros (p, 0); psi = ones (p, 1); fmin = -log (det (S)); converged = true; elseif (strcmp (extraction, "ml")) [L, psi, fmin, converged] = ml_extract (S, m, delta, maxit, tolerance); if (! converged) warning (strcat ("factoran: maximum likelihood did not converge in", ... " %d iterations."), maxit); endif heywood = any (psi <= delta * (1 + 1e-8)); else [L, psi, converged] = paf_extract (S, m, maxit, tolerance); if (! converged) warning (strcat ("factoran: principal axis factoring did not", ... " converge in %d iterations."), maxit); endif fmin = NaN; heywood = any (psi <= 0); endif if (heywood) warning (strcat ("factoran: some specific variances are at their lower", ... " bound; the fit is a Heywood case.")); endif ## ------------------------------------------------------------------ ## ## Statistics. chisq and p exist only for a likelihood fit that is inside ## the parameter space with degrees of freedom to spare. ## ------------------------------------------------------------------ ## if (nargout > 3) stats = struct ("loglike", -fmin, "dfe", dfe); if ((strcmp (extraction, "ml") || m == 0) && dfe > 0 && ! heywood) if (isempty (n)) warning (strcat ("factoran: NOBS is needed to test the fit when X", ... " is a covariance matrix.")); else stats.chisq = (n - 1 - (2 * p + 5) / 6 - 2 * m / 3) * fmin; stats.p = 1 - chi2cdf (stats.chisq, dfe); endif endif endif ## ------------------------------------------------------------------ ## ## Rotation ## ------------------------------------------------------------------ ## if (strcmp (rotate, "none") || m <= 1) lambda = L; T = eye (m); else rargs = {"Method", rotate, "Normalize", normalize}; if (strcmp (rotate, "promax")) rargs = [rargs, {"Power", power}]; endif [lambda, T] = rotatefactors (L, rargs{:}); endif ## ------------------------------------------------------------------ ## ## Scores ## ------------------------------------------------------------------ ## if (nargout > 4) if (! isdata) error ("factoran: factor scores need data, not a covariance matrix."); endif Z = zscore_cols (X); if (strcmp (scores, "wls")) ## Bartlett: weighted least squares in the specific variances W = lambda ./ psi; F = Z * W / (lambda' * W); else ## Thomson: regression of the factors on the variables Sigma = lambda * lambda' + diag (psi); F = Z * (Sigma \ lambda); endif endif endfunction ## Largest number of factors leaving the model identified function mx = max_factors (p) mx = 0; for k = 1:p if (((p - k) ^ 2 - p - k) / 2 >= 0) mx = k; endif endfor endfunction ## Validate a string-valued option against its accepted values function out = check_choice (name, val, choices) if (! ischar (val)) error ("factoran: %s must be a character vector.", name); endif idx = find (strcmpi (val, choices), 1); if (isempty (idx)) error ("factoran: '%s' is not a valid value for %s.", val, name); endif out = choices{idx}; endfunction ## Correlation matrix of the columns of X function S = corr_from_data (X) Z = zscore_cols (X); S = (Z' * Z) / (rows (X) - 1); S = (S + S') / 2; S(1:(columns (S) + 1):end) = 1; endfunction ## Centre and scale the columns to unit variance function Z = zscore_cols (X) s = std (X, 0, 1); if (any (s == 0)) error ("factoran: X must not have a constant column."); endif Z = (X - mean (X, 1)) ./ s; endfunction ## The profile discrepancy of the maximum likelihood factor model at a given ## vector of specific variances, and the loadings it implies. Concentrating ## the likelihood on PSI this way is Joreskog's formulation: for a fixed PSI ## the loadings follow from the eigenvectors of the scaled correlation matrix, ## so only PSI has to be searched over. function [f, L] = ml_discrepancy (psi, S, m) sq = sqrt (psi(:)); Sstar = S ./ (sq * sq'); Sstar = (Sstar + Sstar') / 2; [V, D] = eig (Sstar); [theta, idx] = sort (diag (D), "descend"); V = V(:, idx); theta = max (theta, eps); f = sum (theta(m+1:end) - log (theta(m+1:end)) - 1); if (nargout > 1) L = (sq * ones (1, m)) .* V(:, 1:m) .* sqrt (max (theta(1:m)' - 1, 0)); endif endfunction ## Maximum likelihood extraction: minimize the profile discrepancy over the ## specific variances, held inside [DELTA, 1] by a logistic reparametrization ## so the search itself is unconstrained. function [L, psi, fmin, converged] = ml_extract (S, m, delta, maxit, tol) p = rows (S); ## Joreskog's starting values Sinv = pinv (S); psi0 = (1 - 0.5 * m / p) ./ diag (Sinv); psi0 = min (max (psi0, delta + 1e-6), 1 - 1e-6); u0 = logit ((psi0 - delta) / (1 - delta)); obj = @(u) ml_discrepancy (delta + (1 - delta) * sigmoid (u), S, m); opts = optimset ("MaxIter", maxit * 20, "MaxFunEvals", maxit * 200, ... "TolX", tol, "TolFun", tol); [uhat, fmin, flag] = fminsearch (obj, u0, opts); converged = (flag == 1); psi = delta + (1 - delta) * sigmoid (uhat); psi = min (max (psi(:), delta), 1); ## Polish on the stationarity condition of the model, that the fitted ## covariance reproduces the unit diagonal. The simplex search lands close ## to the optimum but leaves the condition satisfied only to about 1e-9, ## which shows up as the communalities and the specific variances not quite ## summing to one; iterating the condition drives it to machine precision. for it = 1:maxit [~, L] = ml_discrepancy (psi, S, m); psinew = min (max (1 - sum (L .^ 2, 2), delta), 1); if (max (abs (psinew - psi)) < eps * 8) psi = psinew; break; endif psi = psinew; endfor [fmin, L] = ml_discrepancy (psi, S, m); L = sign_convention (L); endfunction function y = sigmoid (u) y = 1 ./ (1 + exp (-u)); endfunction function u = logit (y) y = min (max (y, 1e-12), 1 - 1e-12); u = log (y ./ (1 - y)); endfunction ## Principal axis factoring: iterate the communalities on the reduced ## correlation matrix until they stop moving. function [L, psi, converged] = paf_extract (S, m, maxit, tol) p = rows (S); h2 = ones (p, 1); converged = false; L = zeros (p, m); for it = 1:maxit Rstar = S - diag (1 - h2); [V, D] = eig ((Rstar + Rstar') / 2); [ev, idx] = sort (diag (D), "descend"); V = V(:, idx); L = V(:, 1:m) .* sqrt (max (ev(1:m)', 0)); h2new = sum (L .^ 2, 2); if (max (abs (h2new - h2)) < tol) converged = true; h2 = h2new; break; endif h2 = h2new; endfor psi = max (1 - h2, 0); L = sign_convention (L); endfunction ## Make each column's largest-magnitude loading positive, so that a solution ## does not change sign between runs for no reason. function L = sign_convention (L) [~, idx] = max (abs (L), [], 1); for j = 1:columns (L) if (L(idx(j), j) < 0) L(:, j) = -L(:, j); endif endfor endfunction %!demo %! ## Six measured variables built from two underlying factors, plus noise. %! ## Factor analysis recovers the structure without being told it: the first %! ## three variables load on one factor and the last three on the other. %! rng (42); %! F = randn (300, 2); %! X = F * [0.8 0.1; 0.7 0.2; 0.75 0.15; 0.15 0.8; 0.2 0.7; 0.1 0.75]' ... %! + 0.6 * randn (300, 6); %! lambda = factoran (X, 2); %! printf ("loadings on the two rotated factors:\n"); %! disp (round (lambda * 1000) / 1000); %!demo %! ## How many factors do the data support? The likelihood ratio test in %! ## stats answers it. These data were built from two factors, and the test %! ## rejects one factor while accepting two. Three factors leave no degrees %! ## of freedom, so there is nothing left to test with. %! rng (42); %! F = randn (300, 2); %! X = F * [0.8 0.1; 0.7 0.2; 0.75 0.15; 0.15 0.8; 0.2 0.7; 0.1 0.75]' ... %! + 0.6 * randn (300, 6); %! for m = 1:3 %! [~, ~, ~, stats] = factoran (X, m); %! if (isfield (stats, "p")) %! printf ("%d factor(s): chisq = %8.3f, dfe = %d, p = %.4f\n", ... %! m, stats.chisq, stats.dfe, stats.p); %! else %! printf ("%d factor(s): nothing to test against (dfe = %d)\n", ... %! m, stats.dfe); %! endif %! endfor %!demo %! ## Rotation decides how a fit is presented, not how good it is. The %! ## unrotated solution puts most of the variance on a general first factor; %! ## varimax turns it so each variable loads mainly on one factor, which is %! ## easier to read. The specific variances are untouched either way. %! rng (42); %! F = randn (300, 2); %! X = F * [0.8 0.1; 0.7 0.2; 0.75 0.15; 0.15 0.8; 0.2 0.7; 0.1 0.75]' ... %! + 0.6 * randn (300, 6); %! [Lnone, psi_none] = factoran (X, 2, "Rotate", "none"); %! [Lvari, psi_vari, T] = factoran (X, 2, "Rotate", "varimax"); %! printf ("unrotated:\n"); disp (round (Lnone * 100) / 100); %! printf ("varimax:\n"); disp (round (Lvari * 100) / 100); %! printf ("the rotation matrix takes one to the other: %d\n", ... %! max (max (abs (Lnone * T - Lvari))) < 1e-10); %! printf ("specific variances unchanged: %d\n", ... %! max (abs (psi_none - psi_vari)) < 1e-10); %!demo %! ## Factor scores place each observation on the factors, so they can be %! ## plotted or used as inputs downstream. The two predictors optimise %! ## different things and are not equal, but they agree closely on the %! ## ordering of observations. %! rng (42); %! F = randn (300, 2); %! X = F * [0.8 0.1; 0.7 0.2; 0.75 0.15; 0.15 0.8; 0.2 0.7; 0.1 0.75]' ... %! + 0.6 * randn (300, 6); %! [~, ~, ~, ~, Fwls] = factoran (X, 2, "Scores", "wls"); %! [~, ~, ~, ~, Freg] = factoran (X, 2, "Scores", "regression"); %! printf ("first three observations, weighted least squares:\n"); %! disp (round (Fwls(1:3,:) * 1000) / 1000); %! printf ("first three observations, regression:\n"); %! disp (round (Freg(1:3,:) * 1000) / 1000); %! printf ("the two agree on factor 1 to a correlation of %.4f\n", ... %! corr (Fwls(:,1), Freg(:,1))); %!demo %! ## Two ways to estimate the same model. Maximum likelihood is the default %! ## and is what MATLAB does; principal axis factoring is an Octave %! ## extension that assumes no distribution. They agree closely when the %! ## model fits, so a large gap between them is a warning about the fit. %! ## Only maximum likelihood carries a test. %! rng (42); %! F = randn (300, 2); %! X = F * [0.8 0.1; 0.7 0.2; 0.75 0.15; 0.15 0.8; 0.2 0.7; 0.1 0.75]' ... %! + 0.6 * randn (300, 6); %! Lml = factoran (X, 2, "Extraction", "ml"); %! Lpaf = factoran (X, 2, "Extraction", "paf"); %! printf ("largest loading difference between the extractions: %.4f\n", ... %! max (abs (abs (Lml(:)) - abs (Lpaf(:))))); %! [~, ~, ~, sml] = factoran (X, 2, "Extraction", "ml"); %! [~, ~, ~, spaf] = factoran (X, 2, "Extraction", "paf"); %! printf ("ml reports: %s\n", strjoin (fieldnames (sml)', ", ")); %! printf ("paf reports: %s\n", strjoin (fieldnames (spaf)', ", ")); ## Reference values below are MATLAB R2024a's, on data generated by a ## deterministic stream so that both engines see identical bytes. %!shared X, Lref, Pref, Tref %! s = 7; u = zeros (3200, 1); %! for k = 1:3200 %! s = mod (16807 * s, 2147483647); %! u(k) = s / 2147483647; %! endfor %! u = min (max (u, 1e-12), 1 - 1e-12); %! a = u(1:2:end); b = u(2:2:end); r = sqrt (-2 * log (a)); %! z = zeros (3200, 1); %! z(1:2:end) = r .* cos (2 * pi * b); %! z(2:2:end) = r .* sin (2 * pi * b); %! Z = reshape (z(1:1600), 200, 8); %! X = Z(:,1:2) * [0.7 0.1; 0.6 0.2; 0.65 0.15; ... %! 0.15 0.7; 0.2 0.65; 0.1 0.6]' + 0.75 * Z(:,3:8); %! Lref = [0.23744646899123, 0.62572191026907; ... %! 0.1948126299518, 0.57314527421474; ... %! 0.038102881086025, 0.63721347868091; ... %! 0.76535682403077, 0.15782050760078; ... %! 0.54869037618166, 0.1041708573065; ... %! 0.52204369118599, 0.15800651296731]; %! Pref = [0.55209126537284; 0.63355253385654; 0.59250715304029; ... %! 0.38932161929025; 0.68808730357355; 0.70250432635276]; %! Tref = [0.78348287808137, 0.62141337268628; ... %! -0.62141337268628, 0.78348287808137]; %!test # the maximum likelihood fit, its rotation and its statistics %! [L, psi, T, stats] = factoran (X, 2); %! assert_equal (L, Lref, 1e-6); %! assert_equal (psi, Pref, 1e-6); %! assert_equal (T, Tref, 1e-6); %! assert_equal (stats.loglike, -0.0043677316938693, 1e-9); %! assert_equal (stats.dfe, 4); %! assert_equal (stats.chisq, 0.8509797250222, 1e-6); %! assert_equal (stats.p, 0.93148578937045, 1e-8); %!test # a single factor, where the model does not fit and the test says so %! [L, psi, T, stats] = factoran (X, 1); %! assert_equal (L, [0.58734985138995; 0.52528039923063; 0.42927173838555; ... %! 0.5943390974845; 0.47167200636347; ... %! 0.49418848792238], 1e-6); %! assert_equal (stats.dfe, 9); %! assert_equal (stats.chisq, 51.130175056732, 1e-5); %! assert_equal (stats.p < 1e-6, true); %! assert_equal (T, 1); %!test # rotation changes the loadings but not the fit %! [Ln, psin, Tn] = factoran (X, 2, "Rotate", "none"); %! assert_equal (Ln, [0.57486720553951, 0.34268999200789; ... %! 0.50879249789022, 0.32799033558027; ... %! 0.42582593184473, 0.47556821038442; ... %! 0.69771574115811, -0.3519532998141; ... %! 0.49462267888081, -0.25934745412885; ... %! 0.50719965378403, -0.20060953329425], 1e-6); %! assert_equal (Tn, eye (2)); %! assert_equal (psin, Pref, 1e-6); %! [~, psiv] = factoran (X, 2, "Rotate", "varimax"); %! assert_equal (psiv, psin, 1e-10); %!test # the rotation matrix is the one that was applied %! [Ln, ~, ~] = factoran (X, 2, "Rotate", "none"); %! [Lv, ~, T] = factoran (X, 2, "Rotate", "varimax"); %! assert_equal (Ln * T, Lv, 1e-8); %!test # quartimax, another orthogonal rotation %! L = factoran (X, 2, "Rotate", "quartimax"); %! assert_equal (L, [0.24417499900219, 0.62312703719984; ... %! 0.20097710825146, 0.57101284407823; ... %! 0.044966808556795, 0.63676591702752; ... %! 0.76701294807206, 0.14956442825637; ... %! 0.54978098993064, 0.098252529419005; ... %! 0.52371594497243, 0.15237218456401], 1e-5); %!test # communality and specific variance partition the unit variance %! [L, psi] = factoran (X, 2); %! assert_equal (sum (L .^ 2, 2) + psi, ones (6, 1), 1e-10); %!test # both score predictors, and the default is the weighted least squares %! [~, ~, ~, ~, Fw] = factoran (X, 2, "Scores", "wls"); %! assert_equal (Fw(1:2,:), [0.8685910477393, 3.3629807100209; ... %! -0.03713794537533, -1.9311992150794], 1e-5); %! [~, ~, ~, ~, Fr] = factoran (X, 2, "Scores", "regression"); %! assert_equal (Fr(1:2,:), [0.94397703443356, 2.2276228684448; ... %! -0.22623256144569, -1.2312105486235], 1e-5); %! [~, ~, ~, ~, Fd] = factoran (X, 2); %! assert_equal (Fd, Fw, 1e-12); %! [~, ~, ~, ~, Fb] = factoran (X, 2, "Scores", "Bartlett"); %! assert_equal (Fb, Fw, 1e-12); %! [~, ~, ~, ~, Ft] = factoran (X, 2, "Scores", "Thomson"); %! assert_equal (Ft, Fr, 1e-12); %!test # a correlation matrix gives the same fit as the data behind it %! [L, psi, T, stats] = factoran (corr (X), 2, "Xtype", "covariance", ... %! "Nobs", 200); %! assert_equal (L, Lref, 1e-6); %! assert_equal (psi, Pref, 1e-6); %! assert_equal (stats.chisq, 0.85097972502237, 1e-6); %!test # principal axis factoring is available and agrees where the fit is good %! Lml = factoran (X, 2, "Extraction", "ml"); %! Lpaf = factoran (X, 2, "Extraction", "paf"); %! assert_equal (size (Lpaf), [6, 2]); %! assert_equal (max (abs (abs (Lml(:)) - abs (Lpaf(:)))) < 0.15, true); %!test # but it reports no likelihood, so no test comes with it %! [~, ~, ~, s] = factoran (X, 2, "Extraction", "paf"); %! assert_equal (isnan (s.loglike), true); %! assert_equal (s.dfe, 4); %! assert_equal (isfield (s, "chisq"), false); %! assert_equal (isfield (s, "p"), false); %!test # the degrees of freedom follow the variable and factor counts %! for m = 1:3 %! [~, ~, ~, s] = factoran (X, m); %! assert_equal (s.dfe, ((6 - m) ^ 2 - 6 - m) / 2); %! endfor %!test # with no degrees of freedom there is nothing to test %! [~, ~, ~, s] = factoran (X, 3); %! assert_equal (s.dfe, 0); %! assert_equal (isfield (s, "chisq"), false); %!test # no common factors is a model too, and it tests independence %! [L, psi, T, stats] = factoran (X, 0); %! assert_equal (size (L), [6, 0]); %! assert_equal (psi, ones (6, 1)); %! assert_equal (stats.dfe, 15); %! assert_equal (stats.loglike, log (det (corr (X))), 1e-10); %! assert_equal (stats.p < 1e-6, true); %!test # the loadings do not change sign between runs %! L1 = factoran (X, 2); %! L2 = factoran (X, 2); %! assert_equal (L1, L2); ## Test input validation %!error factoran (1) %!error ... %! factoran (rand (20, 5), 1.5) %!error ... %! factoran (rand (20, 5), -1) %!error factoran (rand (30, 6), 4) %!error factoran ("abc", 1) %!error ... %! factoran ([rand(20, 4); NaN(1, 4)], 1) %!error ... %! factoran ([rand(20, 3), ones(20, 1)], 1) %!error ... %! factoran (rand (1, 5), 1) %!error ... %! factoran (rand (20, 5), 1, "Rotate") %!error ... %! factoran (rand (20, 5), 1, "bogus", 1) %!error <'nosuch' is not a valid value for Extraction.> ... %! factoran (rand (20, 5), 1, "Extraction", "nosuch") %!error <'nosuch' is not a valid value for Rotate.> ... %! factoran (rand (20, 5), 1, "Rotate", "nosuch") %!error <'nosuch' is not a valid value for Scores.> ... %! factoran (rand (20, 5), 1, "Scores", "nosuch") %!error ... %! factoran (rand (20, 5), 1, "Delta", 1) %!error ... %! factoran (rand (5, 4), 1, "Xtype", "covariance") %!error ... %! [a, b, c, d, e] = factoran (corr (rand (20, 5)), 1, "Xtype", "covariance"); statistics-release-1.9.2/inst/Dimensionality_Reduction/mdscale.m000066400000000000000000000546101524624707500251240ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Y} =} mdscale (@var{D}, @var{p}) ## @deftypefnx {statistics} {[@var{Y}, @var{stress}] =} mdscale (@var{D}, @var{p}) ## @deftypefnx {statistics} {[@var{Y}, @var{stress}, @var{disparities}] =} mdscale (@var{D}, @var{p}) ## @deftypefnx {statistics} {[@dots{}] =} mdscale (@dots{}, @var{Name}, @var{Value}) ## ## Nonclassical (metric and nonmetric) multidimensional scaling. ## ## @code{@var{Y} = mdscale (@var{D}, @var{p})} takes a matrix of dissimilarities ## @var{D} and returns a configuration @var{Y} of @var{n} points in @var{p} ## dimensions (an @math{@var{n} * @var{p} } matrix) whose interpoint distances ## approximate @var{D}, by minimizing a stress criterion. @var{D} may be given ## either as a full @math{@var{n} * @var{n}} symmetric matrix with zero diagonal, ## or as the vector of the @math{@var{n} (@var{n} - 1) / 2} upper-triangle ## dissimilarities returned by @code{pdist}. ## ## @code{[@var{Y}, @var{stress}, @var{disparities}] = mdscale (@dots{})} also ## returns the final value of the @var{stress} criterion and the ## @var{disparities} (the transformed dissimilarities the distances are fitted ## to). For the nonmetric criteria the disparities are the monotone ## (isotonic) regression of the dissimilarities onto the distances; for the ## metric criteria they are the dissimilarities themselves. ## ## Name/Value pairs: ## ## @table @asis ## @item @qcode{'Criterion'} ## The goodness-of-fit criterion to minimize, one of: ## ## @table @asis ## @item @qcode{'stress'} (default) ## Kruskal's normalized stress-1, @math{sqrt (sum ((d - dhat)^2) / sum (d^2))}, ## computed from disparities @var{dhat} (nonmetric). ## ## @item @qcode{'sstress'} ## Squared stress, @math{sqrt (sum ((d^2 - dhat^2)^2) / sum (d^4))} (nonmetric). ## ## @item @qcode{'metricstress'} ## Metric stress, @math{sqrt (sum ((d - delta)^2) / sum (delta^2))}, fitting the ## dissimilarities @var{delta} directly. ## ## @item @qcode{'metricsstress'} ## Metric squared stress, @math{sqrt (sum ((d^2 - delta^2)^2) / sum (delta^4))}. ## ## @item @qcode{'sammon'} ## Sammon's nonlinear mapping criterion, ## @math{(1 / sum (delta)) sum ((d - delta)^2 / delta)}. ## ## @item @qcode{'strain'} ## The classical scaling criterion; equivalent to @code{cmdscale}. ## @end table ## ## @item @qcode{'Weights'} ## A matrix or vector of nonnegative weights, the same size as @var{D}, weighting ## each dissimilarity in the criterion. ## ## @item @qcode{'Start'} ## The initial configuration: @qcode{'cmdscale'} (default, classical scaling), ## @qcode{'random'}, or an explicit @math{@var{n} * @var{p} } matrix. ## ## @item @qcode{'Replicates'} ## The number of times to repeat the minimization from different starting points, ## keeping the best (lowest-stress) result. The default is 1. ## ## @item @qcode{'Options'} ## A structure of algorithm options (as returned by @code{statset}) whose ## @qcode{MaxIter}, @qcode{TolFun}, and @qcode{TolX} fields control the iterative ## minimization. ## @end table ## ## @subheading Non-uniqueness of the solution ## ## A stress-minimizing configuration is defined only up to a translation, ## rotation, and reflection, because these leave all interpoint distances (and ## hence the stress) unchanged. @code{mdscale} removes this ambiguity by ## returning @var{Y} centred at the origin and rotated to its principal axes ## (with the largest-magnitude coordinate on each axis made positive), matching ## the convention used by MATLAB. ## ## Beyond that rigid ambiguity, the @strong{nonmetric} criteria (@qcode{'stress'} ## and @qcode{'sstress'}) are non-convex and typically have several local minima; ## the one reached depends on the starting configuration and the details of the ## optimizer. As a result the returned configuration for these criteria may ## differ from the one another program (including MATLAB) reports even when the ## @strong{stress value} agrees, and different runs may find configurations with ## slightly different stress. Use @qcode{'Replicates'} with a @qcode{'random'} ## start to search for a lower-stress solution. The metric criteria and ## @qcode{'strain'} have an essentially unique solution and are reproducible up to ## the rigid ambiguity above. ## ## @seealso{cmdscale, pdist, squareform, procrustes, statset} ## @end deftypefn function [Y, stress, disparities] = mdscale (D, p, varargin) ## Input validation if (nargin < 2) print_usage (); endif ## Resolve D into the vector of dissimilarities DELTA and the count N if (isvector (D)) delta = D(:); m = numel (delta); n = (1 + sqrt (1 + 8 * m)) / 2; if (n != fix (n)) error (strcat ("mdscale: the length of a dissimilarity vector D must", ... " be N*(N-1)/2 for some integer N.")); endif elseif (ismatrix (D) && ndims (D) == 2 && rows (D) == columns (D)) n = rows (D); delta = D(tril (true (n), -1)); m = numel (delta); else error ("mdscale: D must be a dissimilarity vector or a square matrix."); endif if (! (isnumeric (delta) && isreal (delta)) || any (delta < 0)) error ("mdscale: D must contain real, nonnegative dissimilarities."); endif if (! (isscalar (p) && isnumeric (p) && p >= 1 && p == fix (p) && p < n)) error ("mdscale: P must be a positive integer smaller than N."); endif ## Defaults and Name/Value parsing Criterion = "stress"; Weights = []; Start = "cmdscale"; Replicates = 1; opts = statset ("mdscale"); args = varargin; if (mod (numel (args), 2) != 0) error ("mdscale: Name/Value arguments must come in pairs."); endif for k = 1:2:numel (args) name = args{k}; val = args{k+1}; if (! (ischar (name) && isrow (name))) error ("mdscale: parameter names must be character vectors."); endif switch (lower (name)) case 'criterion' Criterion = lower (val); case 'weights' Weights = val; case 'start' Start = val; case 'replicates' Replicates = val; case 'options' if (! isstruct (val)) error ("mdscale: 'Options' must be a structure."); endif opts = statset (opts, val); otherwise error ("mdscale: unknown parameter name '%s'.", name); endswitch endfor critlist = {'stress', 'sstress', 'metricstress', 'metricsstress', ... 'sammon', 'strain'}; if (! (ischar (Criterion) && any (strcmp (Criterion, critlist)))) error ("mdscale: unknown 'Criterion' '%s'.", Criterion); endif nonmetric = any (strcmp (Criterion, {'stress', 'sstress'})); ## Weights vector aligned with DELTA if (isempty (Weights)) w = ones (m, 1); elseif (isvector (Weights) && numel (Weights) == m) w = Weights(:); elseif (ismatrix (Weights) && isequal (size (Weights), [n, n])) w = Weights(tril (true (n), -1)); else error ("mdscale: 'Weights' must match the size of D."); endif if (! (isnumeric (w) && isreal (w)) || any (w < 0)) error ("mdscale: 'Weights' must be real and nonnegative."); endif if (! (isscalar (Replicates) && Replicates >= 1 && Replicates == fix (Replicates))) error ("mdscale: 'Replicates' must be a positive integer."); endif ## The 'strain' criterion is classical scaling: a direct eigen-solution. if (strcmp (Criterion, "strain")) Y = classical_scaling (delta, n, p); Y = orient (Y); stress = strain_value (delta, n, Y); disparities = delta; return; endif ## Pair index list, in pdist (upper-triangle) order P = zeros (m, 2); r = 1; for i = 1:n-1 for j = i+1:n P(r,:) = [i, j]; r++; endfor endfor ## Map the criterion name to the id used by crit_grad/eval_stress critmap = {'metricstress', 'metricsstress', 'sammon', 'stress', 'sstress'}; critid = find (strcmp (Criterion, critmap)); ## Run the minimization once per replicate, keeping the best result bestY = []; bestf = Inf; for rep = 1:Replicates Y0 = initial_config (Start, delta, n, p, rep); [Yr, fr] = minimize_stress (Y0, critid, delta, w, n, p, P, nonmetric, opts); if (fr < bestf) bestf = fr; bestY = Yr; endif endfor ## A non-finite criterion value compares false against every bound, so a ## replicate returning one leaves bestY empty rather than raising. Catch it ## here: otherwise the empty configuration reaches eval_stress and fails as ## an out-of-bound index, naming nothing that led to it. if (isempty (bestY)) error (strcat ("mdscale: no replicate produced a finite criterion", ... " value; the optimization did not converge.")); endif Y = orient (bestY); [stress, disparities] = eval_stress (Y, critid, delta, w, n, p, P, nonmetric); endfunction ## --------------------------------------------------------------------------- ## Build the starting configuration. function Y0 = initial_config (Start, delta, n, p, rep) if (ischar (Start)) switch (lower (Start)) case 'cmdscale' if (rep == 1) Y0 = classical_scaling (delta, n, p); else Y0 = randn (n, p); endif case 'random' Y0 = randn (n, p); otherwise error ("mdscale: unknown 'Start' '%s'.", Start); endswitch elseif (isnumeric (Start) && isequal (size (Start), [n, p])) Y0 = Start; if (rep > 1) Y0 += randn (n, p); endif else error ("mdscale: 'Start' must be 'cmdscale', 'random', or an N-by-P matrix."); endif endfunction ## --------------------------------------------------------------------------- ## Classical scaling (the 'strain' solution and default start). function Y = classical_scaling (delta, n, p) Dsq = squareform (delta) .^ 2; J = eye (n) - ones (n) / n; B = -0.5 * J * Dsq * J; [V, L] = eig ((B + B') / 2); [ev, idx] = sort (diag (L), "descend"); V = V(:,idx); ev = max (ev(1:p), 0); Y = V(:,1:p) .* sqrt (ev)'; endfunction ## --------------------------------------------------------------------------- ## The 'strain' criterion value. function s = strain_value (delta, n, Y) Dsq = squareform (delta) .^ 2; s = sqrt (sum (sumsq (-0.5 * Dsq - Y * Y'))); endfunction ## --------------------------------------------------------------------------- ## Centre at the origin and rotate to principal axes with a sign convention. function Y = orient (Y) Y = Y - mean (Y); [~, ~, V] = svd (Y, 0); Y = Y * V; for j = 1:columns (Y) [~, im] = max (abs (Y(:,j))); if (Y(im,j) < 0) Y(:,j) = -Y(:,j); endif endfor endfunction ## --------------------------------------------------------------------------- ## Isotonic (monotone) regression of X in the order of DELTA, with primary ## tie handling (ties in DELTA broken by X), by pool-adjacent-violators. function dhat = pav (x, delta) [~, ord] = sortrows ([delta, x]); xo = x(ord); m = numel (xo); vals = xo; wts = ones (m, 1); cnt = ones (m, 1); K = m; k = 1; while (k < K) if (vals(k) > vals(k+1)) vals(k) = (wts(k) * vals(k) + wts(k+1) * vals(k+1)) / (wts(k) + wts(k+1)); wts(k) += wts(k+1); cnt(k) += cnt(k+1); vals(k+1) = []; wts(k+1) = []; cnt(k+1) = []; K--; if (k > 1) k--; endif else k++; endif endwhile xexp = zeros (m, 1); pos = 1; for b = 1:K xexp(pos:pos+cnt(b)-1) = vals(b); pos += cnt(b); endfor dhat = zeros (m, 1); dhat(ord) = xexp; endfunction ## --------------------------------------------------------------------------- ## Criterion value and gradient at a configuration. For the nonmetric criteria ## the disparities are recomputed and held fixed for the gradient (Kruskal). function [f, g] = crit_grad (Yv, critid, delta, w, n, p, P, nonmetric) Y = reshape (Yv, n, p); dif = Y(P(:,1),:) - Y(P(:,2),:); d = sqrt (sum (dif .^ 2, 2)); d(d == 0) = eps; switch (critid) case 1 ## metricstress T = sum (w .* delta .^ 2); res = d - delta; N = sum (w .* res .^ 2); f = sqrt (N / T); dfdd = (w .* res) / (f * T); case 2 ## metricsstress T = sum (w .* delta .^ 4); res = d .^ 2 - delta .^ 2; N = sum (w .* res .^ 2); f = sqrt (N / T); dfdd = (2 * w .* d .* res) / (f * T); case 3 ## sammon S = sum (w .* delta); res = d - delta; f = sum (w .* res .^ 2 ./ delta) / S; dfdd = (2 * w .* res ./ delta) / S; case 4 ## stress (nonmetric) dh = pav (d, delta); res = d - dh; T = sum (w .* d .^ 2); N = sum (w .* res .^ 2); f = sqrt (N / T); ## A nonmetric fit needs only monotonicity, so distances already ordered ## like the dissimilarities give res == 0 exactly, hence f == 0 and a 0/0 ## gradient. That configuration is the global minimum: the gradient is ## zero there. Only exact zero is singular -- the expression has a finite ## limit for any res != 0, however small. if (f == 0) dfdd = zeros (size (d)); else dfdd = (w .* res * T - N * (w .* d)) / (f * T ^ 2); endif case 5 ## sstress (nonmetric) dh = pav (d, delta); res = d .^ 2 - dh .^ 2; T = sum (w .* d .^ 4); N = sum (w .* res .^ 2); f = sqrt (N / T); ## Zero stress is the global minimum; see the note in case 4. if (f == 0) dfdd = zeros (size (d)); else dfdd = (2 * w .* d .* res * T - N * (2 * w .* d .^ 3)) / (f * T ^ 2); endif endswitch gg = (dfdd ./ d) .* dif; g = zeros (n, p); for k = 1:size (P, 1) g(P(k,1),:) += gg(k,:); g(P(k,2),:) -= gg(k,:); endfor g = g(:); endfunction ## --------------------------------------------------------------------------- ## Gradient descent with backtracking (Armijo) line search. function [Y, f] = minimize_stress (Y0, critid, delta, w, n, p, P, nonmetric, opts) Yv = Y0(:); [f, g] = crit_grad (Yv, critid, delta, w, n, p, P, nonmetric); al = 1; for it = 1:opts.MaxIter al = min (al * 2, 1e3); gg = g' * g; fn = f; Yn = Yv; for ls = 1:60 Yn = Yv - al * g; fn = crit_grad (Yn, critid, delta, w, n, p, P, nonmetric); if (fn < f - 1e-4 * al * gg) break; endif al /= 2; endfor step = max (abs (Yv - Yn)); Yv = Yn; [f, g] = crit_grad (Yv, critid, delta, w, n, p, P, nonmetric); if (abs (fn - f) <= opts.TolFun * max (1, f) && step <= opts.TolX) break; endif endfor Y = reshape (Yv, n, p); endfunction ## --------------------------------------------------------------------------- ## Final stress value and disparities at the returned configuration. function [stress, disparities] = eval_stress (Y, critid, delta, w, n, p, P, nonmetric) dif = Y(P(:,1),:) - Y(P(:,2),:); d = sqrt (sum (dif .^ 2, 2)); if (nonmetric) disparities = pav (d, delta); else disparities = delta; endif dd = disparities; switch (critid) case {1} stress = sqrt (sum (w .* (d - dd) .^ 2) / sum (w .* dd .^ 2)); case {2} stress = sqrt (sum (w .* (d.^2 - dd.^2) .^ 2) / sum (w .* dd .^ 4)); case 3 stress = sum (w .* (d - dd) .^ 2 ./ dd) / sum (w .* dd); case 4 stress = sqrt (sum (w .* (d - dd) .^ 2) / sum (w .* d .^ 2)); case 5 stress = sqrt (sum (w .* (d.^2 - dd.^2) .^ 2) / sum (w .* d .^ 4)); endswitch endfunction %!demo %! ## Recover a 2-D map of 8 points from their pairwise distances. %! rng = [0 0; 5 0; 5 4; 0 4; 8 2; -3 2; 2 7; 2 -3]; %! D = pdist (rng); %! [Y, stress] = mdscale (D, 2, 'Criterion', 'metricstress'); %! stress %! ## Y reproduces the pairwise distances closely. %! max (abs (pdist (Y)' - D')) ## Reference values below are from MATLAB R2023b, for ## C = [0 0 0; 5 1 2; 2 6 1; 1 2 7; 7 3 4; 3 8 5; 6 4 9; 4 9 3]; ## D = pdist (C); Y0 = [1 -1;-1 1;2 2;-2 -2;3 1;1 3;-3 -1;-1 -3]; ## The metric criteria and 'strain' reproduce MATLAB's configuration; the ## nonmetric criteria reproduce its stress value (see the non-uniqueness note). %!shared C, D, Y0, opt %! C = [0 0 0; 5 1 2; 2 6 1; 1 2 7; 7 3 4; 3 8 5; 6 4 9; 4 9 3]; %! D = pdist (C); %! Y0 = [1 -1; -1 1; 2 2; -2 -2; 3 1; 1 3; -3 -1; -1 -3]; %! opt = struct ("MaxIter", 1000, "TolFun", 1e-10, "TolX", 1e-10); %!test %! [Y, s] = mdscale (D, 2, "Criterion", "metricstress", "Start", Y0, "Options", opt); %! Yref = [ 6.810767239317778, -0.733620649959338; ... %! 3.071301239764592, 1.094132882710339; ... %! 1.065313739387755, -3.760130869234625; ... %! 0.623478915333739, 4.891508042741564; ... %! -0.674391648191895, 1.443596654184263; ... %! -3.493671563292435, -2.348293046078109; ... %! -4.559656269176627, 3.674896334304512; ... %! -2.843141653142908, -4.262089348668606]; %! assert_equal (Y, Yref, 1e-4); %! assert_equal (s, 0.144654108154698, 1e-8); %!test %! [Y, s] = mdscale (D, 2, "Criterion", "metricsstress", "Start", Y0, "Options", opt); %! Yref = [ 6.635327214692762, 0.792856204849063; ... %! 3.228563095536443, -1.334104353307981; ... %! 1.003358563030293, 3.760704781755182; ... %! 0.699645585641474, -3.784555012990314; ... %! -1.079052104978016, -2.376723256450389; ... %! -3.223960839101638, 2.563657848313004; ... %! -4.268551283289842, -3.733948071746559; ... %! -2.995330231531478, 4.112111859577994]; %! assert_equal (Y, Yref, 1e-4); %! assert_equal (s, 0.190937534497622, 1e-8); %!test %! [Y, s] = mdscale (D, 2, "Criterion", "sammon", "Start", Y0, "Options", opt); %! Yref = [ 6.933279805420950, -0.419508440326684; ... %! 3.008163425596839, 1.373357387532562; ... %! 1.273453983125238, -3.707332917059666; ... %! 0.101742369038229, 5.027798678525714; ... %! -0.450738847427944, 1.372181450811530; ... %! -3.390706238135306, -2.442042299704407; ... %! -4.908058626481512, 3.317873521951261; ... %! -2.567135871136496, -4.522327381730312]; %! assert_equal (Y, Yref, 1e-4); %! assert_equal (s, 0.023119816166763, 1e-8); %!test %! ## 'strain' is classical scaling: configuration and value are reproducible. %! [Y, s] = mdscale (D, 2, "Criterion", "strain", "Start", Y0, "Options", opt); %! Yref = [ 6.572163580404339, -0.496338025399502; ... %! 2.719997306646783, 1.496763413554223; ... %! 1.036596577326041, -3.598999625596409; ... %! 0.538361534877868, 2.777882896586096; ... %! -0.658048085951181, 1.901764558503386; ... %! -3.170613455894927, -2.220122324409574; ... %! -4.005398518313616, 4.052005597122571; ... %! -3.033058939095310, -3.912956490360791]; %! assert_equal (Y, Yref, 1e-3); %! assert_equal (s, 1.930074461959919e+02, 1e-6); %!test %! ## Nonmetric 'stress': the stress value matches MATLAB (configuration is %! ## only defined up to the local minimum -- see the non-uniqueness note). %! [Y, s, dsp] = mdscale (D, 2, "Criterion", "stress", "Start", Y0, "Options", opt); %! assert_equal (s, 0.078131057459269, 1e-6); %! assert_equal (size (Y), [8, 2]); %! assert_equal (numel (dsp), 28); %! ## disparities are monotone in the dissimilarities %! [~, ord] = sort (D(:)); %! assert_equal (all (diff (dsp(ord)) >= -1e-9), true); %!test %! ## Nonmetric 'sstress' stress value. %! [Y, s] = mdscale (D, 2, "Criterion", "sstress", "Start", Y0, "Options", opt); %! assert_equal (s <= 0.089488341028430 + 1e-6, true); %!test %! ## Near-collinear data: the nonmetric fit is perfect, so the residuals are %! ## exactly zero and the criterion gradient is 0/0. R2024a returns %! ## 1.122356541711999e-16 here and does not raise. %! A = [1, 2; 2, 4.1; 3, 5.9; 4, 8.2; 5, 9.8; 6, 12.1; 7, 13.9; 8, 16.2]; %! [Y, s] = mdscale (pdist (A), 2, 'Criterion', 'stress'); %! assert_equal (size (Y), [8, 2]); %! assert_equal (s < 1e-9, true); %!test %! ## The same degeneracy under 'sstress'; R2024a returns 2.0818624071994747e-16. %! A = [1, 2; 2, 4.1; 3, 5.9; 4, 8.2; 5, 9.8; 6, 12.1; 7, 13.9; 8, 16.2]; %! [Y, s] = mdscale (pdist (A), 2, 'Criterion', 'sstress'); %! assert_equal (size (Y), [8, 2]); %! assert_equal (s < 1e-9, true); %!test %! ## The default criterion is 'stress'. No other block calls mdscale without %! ## naming a criterion, so this is the only cover of the default path. %! A = [1, 2; 2, 4.1; 3, 5.9; 4, 8.2; 5, 9.8; 6, 12.1; 7, 13.9; 8, 16.2]; %! [Y1, s1] = mdscale (pdist (A), 2); %! [Y2, s2] = mdscale (pdist (A), 2, 'Criterion', 'stress'); %! assert_equal (s1, s2, 1e-12); %!test %! ## Accepts a square dissimilarity matrix as well as a vector. %! [Y1, s1] = mdscale (D, 2, "Criterion", "metricstress", "Start", Y0, "Options", opt); %! [Y2, s2] = mdscale (squareform (D), 2, "Criterion", "metricstress", ... %! "Start", Y0, "Options", opt); %! assert_equal (Y1, Y2, 1e-12); %!test %! ## Returned configuration is centred and on principal axes. %! Y = mdscale (D, 2, "Criterion", "metricstress", "Start", Y0, "Options", opt); %! assert_equal (mean (Y), [0, 0], 1e-10); %! c = cov (Y); %! assert_equal (c(1,2), 0, 1e-8); ## Test input validation %!error mdscale (D) %!error mdscale (1:4, 2) %!error ... %! mdscale (ones (3, 4), 2) %!error ... %! mdscale ([1, -2, 3], 1) %!error mdscale (D, 8) %!error mdscale (D, 0) %!error mdscale (D, 2, "Criterion", "foo") %!error mdscale (D, 2, "bogus", 1) %!error ... %! mdscale (D, 2, "Weights", [1, 2, 3]) %!error ... %! mdscale (D, 2, "Replicates", 0) %!error mdscale (D, 2, "Start", "bad") %!error mdscale (D, 2, "Options", 5) statistics-release-1.9.2/inst/Dimensionality_Reduction/nnmf.m000066400000000000000000000242741524624707500244550ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{W}, @var{H}] =} nnmf (@var{A}, @var{K}) ## @deftypefnx {statistics} {[@var{W}, @var{H}, @var{D}] =} nnmf (@var{A}, @var{K}) ## @deftypefnx {statistics} {[@dots{}] =} nnmf (@dots{}, @var{Name}, @var{Value}) ## ## Nonnegative matrix factorization. ## ## @code{[@var{W}, @var{H}] = nnmf (@var{A}, @var{K})} factors the nonnegative ## @math{N * M} matrix @var{A} into nonnegative factors @var{W} (@math{N * ## @var{K}}) and @var{H} (@math{@var{K} * M}) whose product approximates ## @var{A}, by minimizing the root-mean-square residual between @var{A} and ## @code{@var{W} * @var{H}}. @var{K}, the number of factors, is typically smaller ## than @math{N} and @math{M}. ## ## @code{[@var{W}, @var{H}, @var{D}] = nnmf (@dots{})} also returns the ## root-mean-square residual @var{D}, that is @code{norm (@var{A} - @var{W} * ## @var{H}, "fro") / sqrt (N * M)}. ## ## The factorization is not unique: the returned factors are normalized so that ## the rows of @var{H} have unit length, and the columns of @var{W} (and the ## corresponding rows of @var{H}) are ordered by decreasing length of the ## columns of @var{W}. Because the objective is not convex, the iteration ## converges to a local minimum that depends on the starting point; use ## @qcode{'Replicates'} to try several random starts and keep the best. ## ## Name/Value pairs: ## ## @table @asis ## @item @qcode{'Algorithm'} ## @qcode{'als'} (default) for alternating least squares, or @qcode{'mult'} for ## multiplicative updates. Alternating least squares usually converges faster ## and more reliably; multiplicative updates are more sensitive to the starting ## point. ## ## @item @qcode{'W0'} ## An @math{N * @var{K}} initial value for @var{W}. ## ## @item @qcode{'H0'} ## A @math{@var{K} * M} initial value for @var{H}. ## ## @item @qcode{'Replicates'} ## The number of times to repeat the factorization from new random starting ## points, keeping the result with the smallest residual. The default is 1. ## Ignored for a starting point fixed by both @qcode{'W0'} and @qcode{'H0'}. ## ## @item @qcode{'Options'} ## A structure of algorithm options (as returned by @code{statset}) whose ## @qcode{MaxIter}, @qcode{TolFun}, and @qcode{TolX} fields control the iteration. ## @end table ## ## @seealso{pca, statset} ## @end deftypefn function [W, H, D] = nnmf (A, K, varargin) ## Input validation if (nargin < 2) print_usage (); endif if (! (isnumeric (A) && ismatrix (A) && ndims (A) == 2)) error ("nnmf: A must be a numeric matrix."); endif if (! (isreal (A) && all (isfinite (A(:))))) error ("nnmf: A must be real and finite."); endif [n, m] = size (A); if (! (isscalar (K) && isnumeric (K) && K >= 1 && K == fix (K))) error ("nnmf: K must be a positive integer."); endif ## Defaults and Name/Value parsing algorithm = "als"; W0 = []; H0 = []; Replicates = 1; opts = statset ("nnmf"); args = varargin; if (mod (numel (args), 2) != 0) error ("nnmf: Name/Value arguments must come in pairs."); endif for k = 1:2:numel (args) name = args{k}; val = args{k+1}; if (! (ischar (name) && isrow (name))) error ("nnmf: parameter names must be character vectors."); endif switch (lower (name)) case 'algorithm' algorithm = lower (val); case 'w0' W0 = val; case 'h0' H0 = val; case 'replicates' Replicates = val; case 'options' if (! isstruct (val)) error ("nnmf: 'Options' must be a structure."); endif opts = statset (opts, val); otherwise error ("nnmf: unknown parameter name '%s'.", name); endswitch endfor if (! any (strcmp (algorithm, {'als', 'mult'}))) error ("nnmf: 'Algorithm' must be 'als' or 'mult'."); endif if (! isempty (W0) && ! isequal (size (W0), [n, K])) error ("nnmf: 'W0' must be an %d-by-%d matrix.", n, K); endif if (! isempty (H0) && ! isequal (size (H0), [K, m])) error ("nnmf: 'H0' must be an %d-by-%d matrix.", K, m); endif if (! (isscalar (Replicates) && Replicates >= 1 && Replicates == fix (Replicates))) error ("nnmf: 'Replicates' must be a positive integer."); endif ## With both factors fixed there is a single starting point to run fixedStart = ! isempty (W0) && ! isempty (H0); if (fixedStart) Replicates = 1; endif ## Run the factorization once per replicate, keeping the best bestD = Inf; bestW = []; bestH = []; scal = sqrt (mean (A(:)) / K); for rep = 1:Replicates if (isempty (W0)) W = rand (n, K) * scal; else W = W0; endif if (isempty (H0)) H = rand (K, m) * scal; else H = H0; endif [W, H, d] = factorize (A, W, H, algorithm, opts, n, m); if (d < bestD) bestD = d; bestW = W; bestH = H; endif endfor ## Normalize: unit-length rows of H, columns ordered by decreasing W length hlen = sqrt (sum (bestH .^ 2, 2)); hlen(hlen == 0) = 1; H = bestH ./ hlen; W = bestW .* hlen'; [~, idx] = sort (sqrt (sum (W .^ 2, 1)), "descend"); W = W(:,idx); H = H(idx,:); D = bestD; endfunction ## --------------------------------------------------------------------------- ## Iterate one algorithm from a starting point to convergence. function [W, H, dnorm] = factorize (A, W, H, algorithm, opts, n, m) sqrteps = sqrt (eps); dnorm0 = Inf; for iter = 1:opts.MaxIter Wp = W; Hp = H; if (strcmp (algorithm, "als")) H = max (0, W \ A); W = max (0, A / H); else H = H .* (W' * A) ./ ((W' * W) * H + eps); W = W .* (A * H') ./ (W * (H * H') + eps); endif dnorm = norm (A - W * H, "fro") / sqrt (n * m); if (iter > 1) dw = max (max (abs (W - Wp))) / (sqrteps + max (max (abs (Wp)))); dh = max (max (abs (H - Hp))) / (sqrteps + max (max (abs (Hp)))); if (max (dw, dh) <= opts.TolX) break; endif if (dnorm0 - dnorm <= opts.TolFun * max (1, dnorm0)) break; endif endif dnorm0 = dnorm; endfor endfunction %!demo %! ## Factor a nonnegative matrix into two rank-2 nonnegative factors. %! A = [1, 2, 3; 2, 4, 6; 3, 5, 7; 4, 8, 12]; %! [W, H, D] = nnmf (A, 2); %! D %! ## W * H approximates A. %! W * H ## Reference values below are from MATLAB R2023b for ## A = reshape (mod ((1:20)*7, 13), 5, 4) + 1, with fixed starting factors. %!shared A, W0, H0, opt %! A = reshape (mod ((1:20)*7, 13), 5, 4) + 1; %! W0 = reshape (mod ((1:10)*3, 7), 5, 2) + 1; %! H0 = reshape (mod ((1:8)*3, 7), 2, 4) + 1; %! opt = struct ("MaxIter", 500, "TolFun", 1e-12, "TolX", 1e-12); %!test %! [W, H, D] = nnmf (A, 2, "Algorithm", "als", "W0", W0, "H0", H0, "Options", opt); %! Wref = [11.808229123411982, 9.049993647167147; ... %! 3.212266148150507, 12.092875848976247; ... %! 13.853561873197318, 1.116057457895120; ... %! 4.618299368228214, 13.176478328920602; ... %! 15.259595093275024, 2.199659937839474]; %! Href = [0.657913952127586, 0.187993469755446, 0.140048330155150, 0.715677407877266; ... %! 0, 0.730839581652543, 0.680241675322826, 0.056078240378338]; %! assert_equal (W, Wref, 1e-4); %! assert_equal (H, Href, 1e-5); %! assert_equal (D, 1.882172868745145, 1e-8); %!test %! [W, H, D] = nnmf (A, 2, "Algorithm", "mult", "W0", W0, "H0", H0, "Options", opt); %! Wref = [11.221742977568461, 8.713876463682844; ... %! 2.176215667360621, 12.316408085448018; ... %! 14.030193052240865, 0.411019822854590; ... %! 3.511868529923804, 13.360184914158390; ... %! 15.365861312580710, 1.454761030110911]; %! Href = [0.647958006689051, 0.222457058550383, 0.172654918585345, 0.707710080299095; ... %! 0.057102252901681, 0.727300231504821, 0.673915159707622, 0.116670748188381]; %! assert_equal (W, Wref, 1e-3); %! assert_equal (H, Href, 1e-4); %! assert_equal (D, 1.882172868753092, 1e-8); %!test %! ## Factors are nonnegative, correctly sized, and H rows are unit length. %! [W, H] = nnmf (A, 2, "W0", W0, "H0", H0, "Options", opt); %! assert_equal (size (W), [5, 2]); %! assert_equal (size (H), [2, 4]); %! assert_equal (all (W(:) >= 0) && all (H(:) >= 0), true); %! assert_equal (sqrt (sum (H .^ 2, 2)), [1; 1], 1e-10); %!test %! ## Columns of W are ordered by decreasing length. %! [W, H] = nnmf (A, 2, "W0", W0, "H0", H0, "Options", opt); %! assert_equal (norm (W(:,1)) >= norm (W(:,2)), true); %!test %! ## The residual D matches the direct computation. %! [W, H, D] = nnmf (A, 2, "W0", W0, "H0", H0, "Options", opt); %! assert_equal (D, norm (A - W * H, "fro") / sqrt (numel (A)), 1e-12); ## Test input validation %!error nnmf (ones (4, 3)) %!error nnmf ({1, 2}, 1) %!error nnmf ([1, Inf; 2, 3], 1) %!error nnmf ([1+2i, 3; 4, 5], 1) %!error nnmf (ones (4, 3), 0) %!error nnmf (ones (4, 3), 1.5) %!error ... %! nnmf (ones (4, 3), 2, "Algorithm", "foo") %!error ... %! nnmf (ones (4, 3), 2, "W0", ones (3, 3)) %!error ... %! nnmf (ones (4, 3), 2, "H0", ones (2, 2)) %!error ... %! nnmf (ones (4, 3), 2, "Replicates", 0) %!error nnmf (ones (4, 3), 2, "bogus", 1) %!error nnmf (ones (4, 3), 2, "Options", 5) statistics-release-1.9.2/inst/Dimensionality_Reduction/pca.m000066400000000000000000000601141524624707500242530ustar00rootroot00000000000000## Copyright (C) 2013-2019 Fernando Damian Nieuwveldt ## Copyright (C) 2021 Stefano Guidoni ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License ## as published by the Free Software Foundation; either version 3 ## of the License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{coeff} =} pca (@var{x}) ## @deftypefnx {statistics} {@var{coeff} =} pca (@var{x}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{coeff}, @var{score}, @var{latent}] =} pca (@dots{}) ## @deftypefnx {statistics} {[@var{coeff}, @var{score}, @var{latent}, @var{tsquared}] =} pca (@dots{}) ## @deftypefnx {statistics} {[@var{coeff}, @var{score}, @var{latent}, @var{tsquared}, @var{explained}, @var{mu}] =} pca (@dots{}) ## ## Performs a principal component analysis on a data matrix. ## ## A principal component analysis of a data matrix of @math{N} observations in a ## @math{D} dimensional space returns a @math{D*D} transformation matrix, to ## perform a change of basis on the data. The first component of the new basis ## is the direction that maximizes the variance of the projected data. ## ## Input argument: ## @itemize @bullet ## @item ## @var{x} : a @math{N*D} data matrix ## @end itemize ## ## The following @var{Name}, @var{Value} pair arguments can be used: ## @itemize @bullet ## @item ## @qcode{'Algorithm'} defines the algorithm to use: ## @itemize ## @item @qcode{'svd'} (default), for singular value decomposition ## @item @qcode{'eig'} for eigenvalue decomposition ## @end itemize ## ## @item ## @qcode{'Centered'} is a boolean indicator for centering the observation data. ## It is @code{true} by default. ## @item ## ## @qcode{'Economy'} is a boolean indicator for the economy size output. It is ## @code{true} by default. Hence, @code{pca} returns only the elements of ## @var{latent} that are not necessarily zero, and the corresponding columns of ## @var{coeff} and @var{score}, that is, when @math{N <= D}, only the first ## @math{N - 1}. ## ## @item ## @qcode{'NumComponents'} defines the number of components @math{k} to return. ## If @math{k < p}, then only the first @math{k} columns of @var{coeff} and ## @var{score} are returned. ## ## @item ## @qcode{'Rows'} defines how to handle missing values: ## @itemize ## @item @qcode{'complete'} (default), missing values are removed before ## computation. ## @item @qcode{'pairwise'} (only valid when @qcode{'Algorithm'} is ## @qcode{'eig'}), the covariance of rows with missing data is computed using ## the available data, but the covariance matrix could be not positive definite, ## which triggers the termination of @code{pca}. ## @item @qcode{'all'}, missing values are not allowed, @code{pca} ## terminates with an error if there are any. ## @end itemize ## ## @item ## @qcode{'Weights'} defines observation weights as a vector of positive values ## of length @math{N}. ## ## @item ## @qcode{'VariableWeights'} defines variable weights: ## @itemize ## @item a @var{vector} of positive values of length @math{D}. ## @item the string @qcode{'variance'} to use the sample variance as weights. ## @end itemize ## @end itemize ## ## Return values: ## @itemize @bullet ## @item ## @var{coeff} : the principal component coefficients, a @math{D*D} ## transformation matrix ## @item ## @var{score} : the principal component scores, the representation of @var{x} ## in the principal component space ## @item ## @var{latent} : the principal component variances, i.e., the eigenvalues of ## the covariance matrix of @var{x} ## @item ## @var{tsquared} : Hotelling's T-squared Statistic for each observation in ## @var{x} ## @item ## @var{explained} : the percentage of the variance explained by each principal ## component ## @item ## @var{mu} : the estimated mean of each variable of @var{x}, it is zero if the ## data are not centered ## @end itemize ## ## Matlab compatibility note: the alternating least square method 'als' and ## associated options 'Coeff0', 'Score0', and 'Options' are not yet implemented ## ## @subheading References ## @enumerate ## @item ## Jolliffe, I. T., Principal Component Analysis, 2nd Edition, Springer, 2002 ## @end enumerate ## ## @seealso{barttest, factoran, pcacov, pcares} ## @end deftypefn function [coeff, score, latent, tsquared, explained, mu] = pca (x, varargin) if (nargin < 1) print_usage (); endif [nobs, nvars] = size (x); ## default options optAlgorithmS = 'svd'; optCenteredB = true; optEconomyB = true; optNumComponentsI = nvars; optWeights = []; optVariableWeights = []; optRowsB = false; TF = []; ## parse parameters pair_index = 1; while (pair_index <= (nargin - 1)) switch (lower (varargin{pair_index})) ## decomposition algorithm: singular value decomposition, eigenvalue ## decomposition or (currently unavailable) alternating least square case 'algorithm' optAlgorithmS = varargin{pair_index + 1}; switch (optAlgorithmS) case {'svd', 'eig'} ; case 'als' error ("pca: alternating least square algorithm not implemented."); otherwise error ("pca: invalid algorithm '%s'.", optAlgorithmS); endswitch ## centering of the columns, around the mean case 'centered' if (isbool (varargin{pair_index + 1})) optCenteredB = varargin{pair_index + 1}; else error ("pca: 'centered' requires a boolean value."); endif ## limit the size of the output to the degrees of freedom, when a smaller ## number than the number of variables case 'economy' if (isbool (varargin{pair_index + 1})) optEconomyB = varargin{pair_index + 1}; else error ("pca: 'economy' requires a boolean value."); endif ## choose the number of components to show case 'numcomponents' optNumComponentsI = varargin{pair_index + 1}; if ((! isscalar (optNumComponentsI)) || (! isnumeric (optNumComponentsI)) || optNumComponentsI != floor (optNumComponentsI) || optNumComponentsI <= 0 || optNumComponentsI > nvars) error (strcat ("pca: the number of components must be a positive", ... " integernumber smaller or equal to the number of", ... " variables.")); endif ## observation weights: some observations can be more accurate than others case 'weights' optWeights = varargin{pair_index + 1}; if ((! isvector (optWeights)) || length (optWeights) != nobs || any (optWeights < 0)) error ("pca: weights must be a numerical array of positive numbers."); endif if (rows (optWeights) == 1 ) optWeights = transpose (optWeights); endif ## variable weights: weights used for the variables case 'variableweights' optVariableWeights = varargin{pair_index + 1}; if (ischar (optVariableWeights) && strcmpi (optVariableWeights, 'variance')) optVariableWeights = 'variance'; # take care of this later elseif ((! isvector (optVariableWeights)) || length (optVariableWeights) != nvars || (! isnumeric (optVariableWeights)) || any (optVariableWeights < 0)) error (strcat ("pca: variable weights must be a numerical array", ... " of positive numbers or the string 'variance',")); else optVariableWeights = 1 ./ sqrt (optVariableWeights); ## it is used as a row vector if (columns (optVariableWeights) == 1 ) optVariableWeights = transpose (optVariableWeights); endif endif ## rows: policy for missing values case 'rows' switch (varargin{pair_index + 1}) case 'complete' optRowsB = false; case 'pairwise' optRowsB = true; case 'all' if (any (isnan (x), 'all')) error (strcat ("pca: when all rows are requested the", ... " dataset cannot include NaN values")); endif otherwise error ("pca: %s is an invalid value for rows", ... varargin{pair_index + 1}); endswitch case {'coeff0', 'score0', 'options'} error (strcat ("pca: parameter %s is only valid with the 'als'", ... " method, which is not yet implemented."), ... varargin{pair_index}); otherwise error ("pca: unknown property '%s'.", varargin{pair_index}); endswitch pair_index += 2; endwhile ## Preparing the dataset according to the chosen policy for missing values if (optRowsB) if (! strcmp (optAlgorithmS, 'eig')) optAlgorithmS = 'eig'; warning (strcat ("pca: setting algorithm to 'eig' because", ... " 'rows' option is set to 'pairwise'.")); endif TF = isnan (x); missingRows = zeros (nobs, 1); nmissing = 0; else ## "complete": remove all the rows with missing values TF = isnan (x); missingRows = any (TF, 2); nmissing = sum (missingRows); endif ## indices of the available rows ridcs = find (missingRows == 0); ## Center the columns to mean zero if requested if (optCenteredB) if (isempty (optWeights) && nmissing == 0 && ! optRowsB) ## no weights and no missing values mu = mean (x); elseif (nmissing == 0 && ! optRowsB) ## weighted observations: some observations are more valuable, i.e. they ## can be trusted more mu = sum (optWeights .* x) ./ sum (optWeights); else ## missing values: the mean is computed column by column mu = zeros (1, nvars); if (isempty (optWeights)) for iter = 1 : nvars valid = ! TF(:, iter); mu(iter) = mean (x(valid, iter)); endfor else ## weighted mean with missing data for iter = 1 : nvars valid = ! TF(:, iter); w = optWeights(valid); mu(iter) = sum (x(valid, iter) .* w) ./ sum (w); endfor endif endif Xc = x - mu; else Xc = x; ## The mean of the variables of the original dataset: ## return zero if the dataset is not centered mu = zeros (1, nvars); endif ## Change the columns according to the variable weights if (! isempty (optVariableWeights)) if (ischar (optVariableWeights)) if (isempty (optWeights)) sqrtBias = 1; # see below optVariableWeights = std (x); else ## unbiased variance estimation: the bias when using reliability weights ## is 1 - var(weights) / std(weights)^2 sqrtBias = sqrt (1 - (sumsq (optWeights) / sum (optWeights) ^ 2)); optVariableWeights = std (x, optWeights) / sqrtBias; endif endif Xc = Xc ./ optVariableWeights; endif ## Compute the observation weight matrix if (isempty (optWeights)) Wd = eye (nobs - nmissing); else Wd = diag (optWeights) ./ sum (optWeights); endif ## Compute the coefficients switch (optAlgorithmS) case 'svd' ## Check if there are more variables than observations if (nvars <= nobs) [U, S, coeff] = svd (sqrt (Wd) * Xc(ridcs,:), 'econ'); else ## Calculate the svd on the transpose matrix, much faster if (optEconomyB) [coeff, S, V] = svd (Xc(ridcs,:)' * sqrt (Wd), 'econ'); else [coeff, S, V] = svd (Xc(ridcs,:)' * sqrt (Wd)); endif endif case 'eig' ## this method requires the computation of the sample covariance matrix if (optRowsB) ## pairwise: ## in this case the degrees of freedom for each element of the matrix ## are equal to the number of valid rows for the couple of columns ## used to compute the element Xpairwise = Xc; Xpairwise(isnan (Xc)) = 0; Ndegrees = (nobs - 1) * ones (nvars, nvars); for i_iter = 1 : nvars for j_iter = i_iter : nvars Ndegrees(i_iter, j_iter) = Ndegrees(i_iter, j_iter) - ... sum (any (TF(:,[i_iter j_iter]), 2)); Ndegrees(j_iter, i_iter) = Ndegrees(i_iter, j_iter); endfor endfor Mcov = Xpairwise' * Wd * Xpairwise ./ Ndegrees; else ## the degrees of freedom are not really important here ndegrees = nobs - nmissing - 1; Mcov = Xc(ridcs, :)' * Wd * Xc(ridcs, :) / ndegrees; endif [coeff, S] = eigs (Mcov, nvars); endswitch ## Change the coefficients according to the variable weights if (! isempty (optVariableWeights)) coeff = coeff .* transpose (optVariableWeights); endif ## MATLAB compatibility: the sign convention is that the ## greatest absolute value for each column is positive switchSignV = find (max (coeff) < abs (min (coeff))); if (! isempty (switchSignV)) coeff(:, switchSignV) = -1 * coeff(:, switchSignV); endif ## Compute the scores if (nargout > 1) ## This is for the score when using variable weights, it is not really ## a new definition of Xc if (! isempty (optVariableWeights)) Xc = Xc ./ optVariableWeights; endif ## Get the Scores score = Xc(ridcs,:) * coeff; ## Get the rank of the score matrix r = rank (score); ## If there is missing data, put it back if (nmissing) scoretmp = zeros (nobs, nvars); scoretmp(! missingRows, :) = score; scoretmp(missingRows, :) = NaN; score = scoretmp; endif ## Only use the first r columns, pad rest with zeros if economy != true score = score(:, 1:r) ; if (! optEconomyB) score = [score, (zeros (nobs , nvars-r))]; else coeff = coeff(: , 1:r); endif endif ## Compute the variances if (nargout > 2) ## degrees of freedom: n - 1 for centered data if (optCenteredB) dof = size (Xc(ridcs,:), 1) - 1; else dof = size (Xc(ridcs,:), 1); endif ## This is the same as the eigenvalues of the covariance matrix of x if (strcmp (optAlgorithmS, 'eig')) latent = diag (S, 0); else latent = (diag (S'*S) / dof)(1:r); endif ## If observation weights were used, we need to scale back these values if (! isempty (optWeights)) latent = latent .* sum (optWeights(ridcs)); endif if (! optEconomyB) latent= [latent; (zeros (nvars - r, 1))]; endif endif ## Compute the Hotelling T-square statistics ## MATLAB compatibility: when using weighted observations the T-square ## statistics differ by some rounding error if (nargout > 3) ## Calculate the Hotelling T-Square statistic for the observations ## formally: tsquared = sumsq (zscore (score(:, 1:r)),2); ## No component counts at one degree of freedom or fewer: two centered ## observations define a single component passing exactly through both, so ## no Mahalanobis distance is estimable and T-square is 0 -- where the ## formula returns 0.5 for every such pair, whatever the data. Uncentered ## the same two observations carry two degrees of freedom and do count. ## Measured against R2024a, which reports 0 and 2 respectively. dof = numel (ridcs) - optCenteredB; tsquared = NaN (nobs, 1); if (dof <= 1) tsquared(ridcs) = 0; elseif (! isempty (optWeights)) if (r > 0) standardized_scores = score(ridcs, 1:r) ./ sqrt (latent(1:r)'); tsquared(ridcs) = sum (standardized_scores .^ 2, 2); endif else if (r > 0) tsquared(ridcs) = mahal (score(ridcs, 1:r), score(ridcs, 1:r)); endif endif endif ## Compute the variance explained by each principal component if (nargout > 4) explained = 100 * latent / sum (latent); endif ## When a number of components is chosen, the coefficients and score matrix ## only show that number of columns if (optNumComponentsI != nvars) coeff = coeff(:, 1:optNumComponentsI); endif if (optNumComponentsI != nvars && nargout > 1) score = score(:, 1:optNumComponentsI); endif endfunction %!shared COEFF,SCORE,latent,tsquare,m,x,R,V,lambda,i,S,F ## NIST Engineering Statistics Handbook example (6.5.5.2) %!test %! x = [7, 4, 3; 4, 1, 8; 6, 3, 5; 8, 6, 1; 8, 5, 7; ... %! 7, 2, 9; 5, 3, 3; 9, 5, 8; 7, 4, 5; 8, 2, 2]; %! R = corrcoef (x); %! [V, lambda] = eig (R); %! [~, i] = sort (diag (lambda), 'descend'); #arrange largest PC first %! S = V(:, i) * diag (sqrt (diag (lambda)(i))); %!assert_equal (diag (S(:, 1:2) * S(:, 1:2)'), [0.8662; 0.8420; 0.9876], 1E-4); %! B = V(:, i) * diag ( 1./ sqrt (diag (lambda)(i))); %! F = zscore (x) * B; %! [COEFF, SCORE, latent, tsquare] = pca (zscore (x, 1)); %!assert_equal (tsquare, sumsq (F, 2), 1E4*eps); %!test %! x = [1, 2, 3; 2, 1, 3]'; %! [COEFF, SCORE, latent, tsquare] = pca (x, 'Economy', false); %! m = [sqrt(2), sqrt(2); sqrt(2), -sqrt(2); -2*sqrt(2), 0] / 2; %! m(:,1) = m(:,1) * sign (COEFF(1,1)); %! m(:,2) = m(:,2) * sign (COEFF(1,2)); %!assert_equal (COEFF, m(1:2,:), 10*eps); %!assert_equal (SCORE, -m, 10*eps); %!assert_equal (latent, [1.5;.5], 10*eps); %!assert_equal (tsquare, [4;4;4]/3, 10*eps); ## Test with observation weights (using Matlab's results as a reference) %! [COEFF, SCORE, latent, tsquare] = pca (x, 'Economy', false, 'weights', ... %! [1 2 1], 'variableweights', ... %! 'variance'); %!assert_equal (COEFF, [0.632455532033676, -0.632455532033676; ... %! 0.741619848709566, 0.741619848709566], 10 * eps); %!assert_equal (SCORE, [-0.622019449426284, 0.959119380657905; ... %! -0.505649896847432, -0.505649896847431; %! 1.633319243121148, 0.052180413036957], 10 * eps); %!assert_equal (latent, [1.783001790889027; 0.716998209110974], 10 * eps); %!test assert_equal (tsquare, [1.5; 0.5; 1.5], 10 * eps); %!test %! x = [1,2,3;2,1,3]'; %! [COEFF, SCORE, latent, tsquare] = pca (x, 'Economy', false, 'weights', ... %! [2 1 2], 'variableweights', ... %! 'variance'); %! COEFF_exp = [0.7906, 0.7906; 0.6614, -0.6614]; %! SCORE_exp = [-0.7836, -0.4813; -0.9071, 0.9071; 1.2372, 0.0277]; %! latent_exp = [2.5562; 0.6438]; %! tsquare_exp = [0.6000; 1.6000; 0.6000]; %! assert_equal (COEFF, COEFF_exp, 1e-4); %! assert_equal (SCORE, SCORE_exp, 1e-4); %! assert_equal (latent, latent_exp, 1e-4); %! assert_equal (tsquare, tsquare_exp, 1e-4); %!test %! x = [1,2,3;2,1,3]'; %! [COEFF, SCORE, latent, tsquare] = pca (x, 'Economy', false, 'weights', ... %! [1 3 2], 'variableweights', ... %! 'variance'); %! COEFF_exp = [0.6216, -0.6216; 0.8118, 0.8118]; %! SCORE_exp = [-0.8358, 1.0411; -0.6473, -0.3792; 1.3889, 0.0482]; %! latent_exp = [2.9067; 0.7599]; %! tsquare_exp = [1.6667; 0.3333; 0.6667]; %! assert_equal (COEFF, COEFF_exp, 1e-4); %! assert_equal (SCORE, SCORE_exp, 1e-4); %! assert_equal (latent, latent_exp, 1e-4); %! assert_equal (tsquare, tsquare_exp, 1e-4); %!test %! x = [1,2,3;2,1,3]'; %! [COEFF, SCORE, latent, tsquare] = pca (x, 'Economy', false, 'weights', ... %! [1 0.5 1.5], 'variableweights', ... %! 'variance'); %! COEFF_exp = [0.8118, 0.8118; 0.6742, -0.6742]; %! SCORE_exp = [-0.9657, -0.4713; -1.0915, 0.8862; 1.0076, 0.0188]; %! latent_exp = [1.5257; 0.3077]; %! tsquare_exp = [1.3333; 3.3333; 0.6667]; %! assert_equal (COEFF, COEFF_exp, 1e-4); %! assert_equal (SCORE, SCORE_exp, 1e-4); %! assert_equal (latent, latent_exp, 1e-4); %! assert_equal (tsquare, tsquare_exp, 1e-4); %!test %! x = [1,2,3;2,1,3]'; %! [COEFF, SCORE, latent, tsquare] = pca (x, 'Economy', true, 'weights', ... %! [2 1 2], 'variableweights', ... %! 'variance'); %! COEFF_exp = [0.7906, 0.7906; 0.6614, -0.6614]; %! SCORE_exp = [-0.7836, -0.4813; -0.9071, 0.9071; 1.2372, 0.0277]; %! latent_exp = [2.5562; 0.6438]; %! tsquare_exp = [0.6000; 1.6000; 0.6000]; %! assert_equal (COEFF, COEFF_exp, 1e-4); %! assert_equal (SCORE, SCORE_exp, 1e-4); %! assert_equal (latent, latent_exp, 1e-4); %! assert_equal (tsquare, tsquare_exp, 1e-4); %!test %! x = [1,2,3;2,1,3]'; %! [COEFF, SCORE, latent, tsquare] = pca (x, 'Economy', true, 'weights', ... %! [1 3 2], 'variableweights', ... %! 'variance'); %! COEFF_exp = [0.6216, -0.6216; 0.8118, 0.8118]; %! SCORE_exp = [-0.8358, 1.0411; -0.6473, -0.3792; 1.3889, 0.0482]; %! latent_exp = [2.9067; 0.7599]; %! tsquare_exp = [1.6667; 0.3333; 0.6667]; %! assert_equal (COEFF, COEFF_exp, 1e-4); %! assert_equal (SCORE, SCORE_exp, 1e-4); %! assert_equal (latent, latent_exp, 1e-4); %! assert_equal (tsquare, tsquare_exp, 1e-4); %!test %! x = [1,2,3;2,1,3]'; %! [COEFF, SCORE, latent, tsquare] = pca (x, 'Economy', true, 'weights', ... %! [1 0.5 1.5], 'variableweights', ... %! 'variance'); %! COEFF_exp = [0.8118, 0.8118; 0.6742, -0.6742]; %! SCORE_exp = [-0.9657, -0.4713; -1.0915, 0.8862; 1.0076, 0.0188]; %! latent_exp = [1.5257; 0.3077]; %! tsquare_exp = [1.3333; 3.3333; 0.6667]; %! assert_equal (COEFF, COEFF_exp, 1e-4); %! assert_equal (SCORE, SCORE_exp, 1e-4); %! assert_equal (latent, latent_exp, 1e-4); %! assert_equal (tsquare, tsquare_exp, 1e-4); %!test %! x = x'; %! [COEFF, SCORE, latent, tsquare] = pca (x, 'Economy', false); %! m = [sqrt(2), sqrt(2), 0; -sqrt(2), sqrt(2), 0; 0, 0, 2] / 2; %! m(:,1) = m(:,1) * sign (COEFF(1,1)); %! m(:,2) = m(:,2) * sign (COEFF(1,2)); %! m(:,3) = m(:,3) * sign (COEFF(3,3)); %!assert_equal (COEFF, m, 10*eps); %!assert_equal (SCORE(:,1), -m(1:2,1), 10*eps); %!assert_equal (SCORE(:,2:3), zeros (2), 10*eps); %!assert_equal (latent, [1;0;0], 10*eps); %!## two centered observations carry one degree of freedom, so no component %!## counts and T-square is 0, as R2024a returns it %!assert_equal (tsquare, [0;0], 10*eps) %!test %! [COEFF, SCORE, latent, tsquare] = pca (x); %!assert_equal (COEFF, m(:, 1), 10*eps); %!assert_equal (SCORE, -m(1:2,1), 10*eps); %!assert_equal (latent, [1], 10*eps); %!assert_equal (tsquare, [0;0], 10*eps) %!test %! ## Complex missing data test %! x = [ 0.8147 0.2785 0.9575 %! 0.9058 0.5469 0.9649 %! 0.1270 0.9575 0.1576 %! 0.9134 0.9649 0.9706 %! 0.6324 0.1576 0.9572 %! 0.0975 0.9706 0.4854 %! 0.2785 0.9575 0.8003 %! 0.5469 0.1419 0.1419 ]; %! x_nan = x; %! x_nan(2, 3) = NaN; %! x_nan(5, 1) = NaN; %! [COEFF, SCORE, latent, tsquare] = pca (x_nan, 'Economy', false); %! %! ## Verify NaNs are correctly placed in SCORE and tsquare %! assert_equal (all (isnan (SCORE(2, :))), true); %! assert_equal (all (isnan (SCORE(5, :))), true); %! assert_equal (isnan (tsquare(2)), true); %! assert_equal (isnan (tsquare(5)), true); %! %! ## Verify other rows do not have NaNs %! assert_equal (any (isnan (SCORE([1 3 4 6 7 8], :))(:)), false); %! assert_equal (any (isnan (tsquare([1 3 4 6 7 8]))), false); %!error pca ([1 2; 3 4], 'Algorithm', 'xxx') %!error pca ([1 NaN; 3 4], 'Rows', 'all') %!error <'centered' requires a boolean value> pca ([1 2; 3 4], 'Centered', 'xxx') %!error pca ([1 2; 3 4], 'NumComponents', -4) %!error pca ([1 2; 3 4], 'Rows', 1) %!error pca ([1 2; 3 4], 'Weights', [1 2 3]) %!error pca ([1 2; 3 4], 'Weights', [-1 2]) %!error pca ([1 2; 3 4], 'VariableWeights', [-1 2]) %!error pca ([1 2; 3 4], 'VariableWeights', 'xxx') %!error pca ([1 2; 3 4], 'XXX', 1) statistics-release-1.9.2/inst/Dimensionality_Reduction/pcacov.m000066400000000000000000000116261524624707500247670ustar00rootroot00000000000000## Copyright (C) 2013-2019 Fernando Damian Nieuwveldt ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License ## as published by the Free Software Foundation; either version 3 ## of the License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{coeff} =} pcacov (@var{K}) ## @deftypefnx {statistics} {[@var{coeff}, @var{latent}] =} pcacov (@var{K}) ## @deftypefnx {statistics} {[@var{coeff}, @var{latent}, @var{explained}] =} pcacov (@var{K}) ## ## Perform principal component analysis on covariance matrix ## ## @code{@var{coeff} = pcacov (@var{K})} performs principal component analysis ## on the square covariance matrix @var{K} and returns the principal component ## coefficients, also known as loadings. The columns are in order of decreasing ## component variance. ## ## @code{[@var{coeff}, @var{latent}] = pcacov (@var{K})} also returns a vector ## with the principal component variances, i.e. the eigenvalues of @var{K}. ## @var{latent} has a length of @qcode{size (@var{coeff}, 1)}. ## ## @code{[@var{coeff}, @var{latent}, @var{explained}] = pcacov (@var{K})} also ## returns a vector with the percentage of the total variance explained by each ## principal component. @var{explained} has the same size as @var{latent}. ## The entries in @var{explained} range from 0 (none of the variance is ## explained) to 100 (all of the variance is explained). ## ## @code{pcacov} does not standardize @var{K} to have unit variances. In order ## to perform principal component analysis on standardized variables, use the ## correlation matrix @qcode{@var{R} = @var{K} ./ (@var{SD} * @var{SD}')}, where ## @qcode{@var{SD} = sqrt (diag (@var{K}))}, in place of @var{K}. To perform ## principal component analysis directly on the data matrix, use @code{pca}. ## ## @subheading References ## @enumerate ## @item ## Jolliffe, I. T., Principal Component Analysis, 2nd Edition, Springer, 2002 ## @end enumerate ## ## @seealso{barttest, factoran, pcares, pca} ## @end deftypefn function [coeff, latent, explained] = pcacov (K) ## Check X being a square matrix if (ndims (K) != 2 || size (K, 1) != size (K, 2)) error ("pcacov: K must be a square matrix."); endif ## Check X being a symmetric matrix if (! issymmetric (K)) error ("pcacov: K must be a symmetric matrix."); endif [U, S, V] = svd (K); s_vals = diag (S); col_dot_prod = sum (real (conj (U) .* V), 1); tol = size (K, 1) * eps (max (s_vals)); is_negative = col_dot_prod < -0.9; is_significant = s_vals' > tol; ## Check for positive semi-definiteness if (any (is_negative & is_significant)) error ("pcacov: K must be a positive semi-definite matrix."); endif ## Force a sign convention on the coefficients so that ## the largest element in each column has a positive sign [row, col] = size (U); [~, m_ind] = max (abs (U), [], 1); csign = sign (U(m_ind + (0:row:(col - 1) * row))); coeff = bsxfun (@times, U, csign); ## Compute extra output arguments if (nargout > 1) latent = diag (S); endif if (nargout > 2) explained = 100 * latent ./ sum (latent); endif endfunction %!demo %! x = [ 7 26 6 60; %! 1 29 15 52; %! 11 56 8 20; %! 11 31 8 47; %! 7 52 6 33; %! 11 55 9 22; %! 3 71 17 6; %! 1 31 22 44; %! 2 54 18 22; %! 21 47 4 26; %! 1 40 23 34; %! 11 66 9 12; %! 10 68 8 12 %! ]; %! Kxx = cov (x); %! [coeff, latent, explained] = pcacov (Kxx) ## Test output %!test %! load hald %! Kxx = cov (ingredients); %! [coeff,latent,explained] = pcacov (Kxx); %! c_out = [-0.0678, -0.6460, 0.5673, 0.5062; ... %! -0.6785, -0.0200, -0.5440, 0.4933; ... %! 0.0290, 0.7553, 0.4036, 0.5156; ... %! 0.7309, -0.1085, -0.4684, 0.4844]; %! l_out = [517.7969; 67.4964; 12.4054; 0.2372]; %! e_out = [ 86.5974; 11.2882; 2.0747; 0.0397]; %! assert_equal (coeff, c_out, 1e-4); %! assert_equal (latent, l_out, 1e-4); %! assert_equal (explained, e_out, 1e-4); ## Test input validation %!error pcacov (ones (2, 3)) %!error pcacov (ones (3, 3, 3)) %!error pcacov ([1, 2; 0, 1]) %!error pcacov ([10, 0; 0, -1]) statistics-release-1.9.2/inst/Dimensionality_Reduction/pcares.m000066400000000000000000000102631524624707500247650ustar00rootroot00000000000000## Copyright (C) 2013-2019 Fernando Damian Nieuwveldt ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License ## as published by the Free Software Foundation; either version 3 ## of the License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{residuals} =} pcares (@var{x}, @var{ndim}) ## @deftypefnx {statistics} {[@var{residuals}, @var{reconstructed}] =} pcares (@var{x}, @var{ndim}) ## ## Calculate residuals from principal component analysis. ## ## @code{@var{residuals} = pcares (@var{x}, @var{ndim})} returns the residuals ## obtained by retaining @var{ndim} principal components of the @math{N*D} ## matrix @var{x}. Rows of @var{x} correspond to observations, columns of ## @var{x} correspond to variables. @var{ndim} is a scalar and must be less ## than or equal to @math{D}. @var{residuals} is a matrix of the same size as ## @var{x}. Use the data matrix, not the covariance matrix, with this function. ## ## @code{[@var{residuals}, @var{reconstructed}] = pcares (@var{x}, @var{ndim})} ## returns the reconstructed observations, i.e. the approximation to @var{x} ## obtained by retaining its first @var{ndim} principal components. ## ## @code{pcares} does not normalize the columns of @var{x}. Use ## @qcode{pcares (zscore (@var{x}), @var{ndim})} in order to perform the ## principal components analysis based on standardized variables, i.e. based on ## correlations. Use @code{pcacov} in order to perform principal components ## analysis directly on a covariance or correlation matrix without constructing ## residuals. ## ## @subheading References ## @enumerate ## @item ## Jolliffe, I. T., Principal Component Analysis, 2nd Edition, Springer, 2002 ## @end enumerate ## ## @seealso{factoran, pcacov, pca} ## @end deftypefn function [residuals, reconstructed] = pcares (x, ndim) ## Check input arguments if (nargin < 2) error ("pcares: too few input arguments."); endif if (ndim > size (x, 2)) error ("pcares: NDIM must be less than or equal to the column of X."); endif ## Mean center data Xcentered = bsxfun (@minus, x, mean (x)); ## Apply svd to get the principal component coefficients [U, S, V] = svd (Xcentered); ## Use only the first ndim PCA components v = V(:,1:ndim); ## Calculate the residuals residuals = Xcentered - Xcentered * (v * v'); ## Compute extra output arguments if (nargout > 1) ## Reconstructed data using ndim PCA components reconstructed = x - residuals; endif endfunction %!demo %! x = [ 7 26 6 60; %! 1 29 15 52; %! 11 56 8 20; %! 11 31 8 47; %! 7 52 6 33; %! 11 55 9 22; %! 3 71 17 6; %! 1 31 22 44; %! 2 54 18 22; %! 21 47 4 26; %! 1 40 23 34; %! 11 66 9 12; %! 10 68 8 12]; %! %! ## As we increase the number of principal components, the norm %! ## of the residuals matrix will decrease %! r1 = pcares (x,1); %! n1 = norm (r1) %! r2 = pcares (x,2); %! n2 = norm (r2) %! r3 = pcares (x,3); %! n3 = norm (r3) %! r4 = pcares (x,4); %! n4 = norm (r4) ## Test output %!test %! load hald %! r1 = pcares (ingredients,1); %! r2 = pcares (ingredients,2); %! r3 = pcares (ingredients,3); %! assert_equal (r1(1,:), [2.0350, 2.8304, -6.8378, 3.0879], 1e-4); %! assert_equal (r2(1,:), [-2.4037, 2.6930, -1.6482, 2.3425], 1e-4); %! assert_equal (r3(1,:), [ 0.2008, 0.1957, 0.2045, 0.1921], 1e-4); ## Test input validation %!error pcares (ones (20, 3)) %!error ... %! pcares (ones (30, 2), 3) statistics-release-1.9.2/inst/Dimensionality_Reduction/ppca.m000066400000000000000000000305511524624707500244350ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{coeff} =} ppca (@var{Y}, @var{K}) ## @deftypefnx {statistics} {[@var{coeff}, @var{score}] =} ppca (@var{Y}, @var{K}) ## @deftypefnx {statistics} {[@var{coeff}, @var{score}, @var{pcvar}] =} ppca (@var{Y}, @var{K}) ## @deftypefnx {statistics} {[@var{coeff}, @var{score}, @var{pcvar}, @var{mu}] =} ppca (@var{Y}, @var{K}) ## @deftypefnx {statistics} {[@var{coeff}, @var{score}, @var{pcvar}, @var{mu}, @var{v}] =} ppca (@var{Y}, @var{K}) ## @deftypefnx {statistics} {[@var{coeff}, @var{score}, @var{pcvar}, @var{mu}, @var{v}, @var{S}] =} ppca (@var{Y}, @var{K}) ## @deftypefnx {statistics} {[@dots{}] =} ppca (@dots{}, @var{Name}, @var{Value}) ## ## Probabilistic principal component analysis. ## ## @code{@var{coeff} = ppca (@var{Y}, @var{K})} fits a probabilistic principal ## component analysis (PPCA) model with @var{K} components to the @math{N * P} ## data matrix @var{Y} (rows are observations, columns are variables) and returns ## the @math{P * @var{K}} matrix @var{coeff} of orthonormal principal component ## coefficients, ordered by decreasing component variance. @var{Y} may contain ## @code{NaN} values marking missing observations; the model is fitted by an ## expectation-maximization algorithm that accounts for them. @var{K} must be a ## positive integer smaller than @math{P}. ## ## @code{[@var{coeff}, @var{score}, @var{pcvar}, @var{mu}, @var{v}, @var{S}] = ## ppca (@dots{})} returns further outputs: ## ## @table @var ## @item score ## The @math{N * @var{K}} principal component scores (the data projected onto the ## components; missing entries are reconstructed from the model before ## projection). ## ## @item pcvar ## A @math{@var{K} * 1} vector of the principal component variances (the variance ## explained by each component). ## ## @item mu ## A @math{1 * P} vector of the estimated mean of @var{Y}. ## ## @item v ## The estimated residual (isotropic noise) variance. ## ## @item S ## A structure with the fitted model details: the loadings @qcode{W}, the ## expected scores @qcode{Xexp}, the reconstruction @qcode{Recon}, the number of ## iterations @qcode{NumIter}, and the root-mean-square residual @qcode{RMSResid}. ## @end table ## ## Name/Value pairs control the fit: ## ## @table @asis ## @item @qcode{'W0'} ## A @math{P * @var{K}} initial value for the loadings used by the ## expectation-maximization algorithm (missing-data case). ## ## @item @qcode{'Options'} ## A structure of algorithm options, as returned by @code{statset}, whose ## @qcode{MaxIter}, @qcode{TolFun}, and @qcode{TolX} fields set the maximum number ## of iterations and the convergence tolerances of the ## expectation-maximization algorithm. ## @end table ## ## When @var{Y} has no missing values the model is fitted directly from the ## eigendecomposition of its covariance matrix; @var{coeff}, @var{pcvar}, and ## @var{v} are then the principal component directions, the leading variances, and ## the mean of the trailing variances, respectively. ## ## @seealso{pca, pcacov, pcares, factoran, barttest} ## @end deftypefn function [coeff, score, pcvar, mu, v, S] = ppca (Y, K, varargin) ## Input validation if (nargin < 2) print_usage (); endif if (! (isnumeric (Y) && ismatrix (Y) && ndims (Y) == 2)) error ("ppca: Y must be a numeric matrix."); endif if (! isreal (Y)) error ("ppca: Y must be real."); endif [n, p] = size (Y); if (! (isnumeric (K) && isscalar (K) && isreal (K) && K >= 1 ... && K == fix (K))) error ("ppca: K must be a positive integer."); endif if (K >= p) error ("ppca: K must be smaller than the number of variables."); endif ## Defaults and Name/Value / Options parsing opts = statset ("ppca"); W0 = []; args = varargin; if (mod (numel (args), 2) != 0) error ("ppca: Name/Value arguments must come in pairs."); endif for k = 1:2:numel (args) name = args{k}; val = args{k+1}; if (! (ischar (name) && isrow (name))) error ("ppca: parameter names must be character vectors."); endif switch (lower (name)) case 'w0' W0 = val; case 'options' if (! isstruct (val)) error ("ppca: 'Options' must be a structure."); endif opts = statset (opts, val); otherwise error ("ppca: unknown parameter name '%s'.", name); endswitch endfor if (! isempty (W0) && ! isequal (size (W0), [p, K])) error ("ppca: 'W0' must be a %d-by-%d matrix.", p, K); endif ## Fit the model if (any (isnan (Y(:)))) [coeff, W, ex, mu, v, numiter] = ppca_em (Y, K, W0, opts); else [coeff, W, ex, mu, v, numiter] = ppca_full (Y, K); endif ## Sign convention: the element of largest magnitude in each column is positive for j = 1:K [~, im] = max (abs (coeff(:,j))); if (coeff(im,j) < 0) coeff(:,j) = -coeff(:,j); endif endfor ## Reconstruct missing entries from the model and project onto the components obs = ! isnan (Y); Yhat = Y; Recon = ex * W' + mu; Yhat(! obs) = Recon(! obs); score = (Yhat - mu) * coeff; pcvar = var (score, 0)'(:); if (nargout > 5) resid = Yhat - (score * coeff' + mu); S = struct ("W", coeff .* sqrt (max (pcvar - v, 0))', ... "Xexp", score, "Recon", score * coeff' + mu, ... "NumIter", numiter, ... "RMSResid", sqrt (mean (resid(obs) .^ 2))); endif endfunction ## Direct fit for fully observed data via the covariance eigendecomposition. function [coeff, W, ex, mu, v, numiter] = ppca_full (Y, K) [n, p] = size (Y); mu = mean (Y); C = cov (Y); [U, L] = eig ((C + C') / 2); [lambda, idx] = sort (diag (L), "descend"); U = U(:,idx); coeff = U(:,1:K); v = mean (lambda(K+1:end)); ## ML loadings and expected scores (posterior mean) for the S struct W = coeff .* sqrt (max (lambda(1:K) - v, 0))'; M = W' * W + v * eye (K); ex = (Y - mu) * W / M; numiter = 0; endfunction ## Expectation-maximization fit for data with missing (NaN) entries. function [coeff, W, ex, mu, v, iter] = ppca_em (Y, K, W0, opts) [n, p] = size (Y); obs = ! isnan (Y); ## Initialize the mean, a filled data matrix, and the loadings mu = zeros (1, p); for j = 1:p mu(j) = mean (Y(obs(:,j), j)); endfor Yf = Y; for j = 1:p Yf(! obs(:,j), j) = mu(j); endfor if (isempty (W0)) [~, Sv, V] = svd (Yf - mu, 0); W = V(:,1:K) * Sv(1:K,1:K) / sqrt (n); else W = W0; endif v = 1; ex = zeros (n, K); Exx = zeros (K, K, n); for iter = 1:opts.MaxIter Wold = W; muold = mu; vold = v; ## E-step: expected scores and second moments per observation for i = 1:n o = obs(i,:); Wo = W(o,:); yo = Y(i,o) - mu(o); Minv = inv (v * eye (K) + Wo' * Wo); ex(i,:) = (Minv * Wo' * yo')'; Exx(:,:,i) = v * Minv + ex(i,:)' * ex(i,:); endfor ## M-step: update the mean and loadings per variable, then the noise for j = 1:p r = obs(:,j); exj = ex(r,:); yj = Y(r,j); ZtZ = [sum(Exx(:,:,r), 3), sum(exj, 1)'; sum(exj, 1), sum(r)]; sol = ZtZ \ [exj' * yj; sum(yj)]; W(j,:) = sol(1:K)'; mu(j) = sol(K+1); endfor num = 0; cnt = 0; for i = 1:n o = obs(i,:); Wo = W(o,:); yo = Y(i,o) - mu(o); num += yo * yo' - 2 * ex(i,:) * Wo' * yo' ... + trace (Wo' * Wo * Exx(:,:,i)); cnt += sum (o); endfor v = num / cnt; if (max (abs (W(:) - Wold(:))) < opts.TolX ... && max (abs (mu - muold)) < opts.TolX && abs (v - vold) < opts.TolX) break; endif endfor ## Orthonormalize the loadings into principal component directions [Uw, ~, ~] = svd (W, 0); coeff = Uw(:,1:K); endfunction %!demo %! ## Fit a two-component PPCA model and reconstruct the data. %! Y = [ 1.0, 2.0, 0.5; 2.1, 3.9, 1.2; ... %! -1.0, -2.2, -0.4; -2.0, -3.8, -1.1; ... %! 0.5, 1.1, 0.9; 1.6, 2.8, -0.2]; %! [coeff, score, pcvar, mu, v] = ppca (Y, 2); %! coeff %! pcvar %! ## The scores reconstruct the data through the coefficients. %! max (abs (vec (score * coeff' + mu - Y))) ## Reference values below are from MATLAB R2023b for ## Y = reshape (mod ((1:40)*7, 17), 10, 4) - 8. %!test %! Y = reshape (mod ((1:40)*7, 17), 10, 4) - 8; %! opt = struct ("MaxIter", 2000, "TolFun", 1e-12, "TolX", 1e-12); %! [coeff, score, pcvar, mu, v] = ppca (Y, 2, "Options", opt); %! coeff_ref = [ 0.543716564044483, -0.391810693220729; ... %! 0.599330273587830, 0.314784124203745; ... %! 0.302509942024958, 0.761619894252268; ... %! -0.503649934101907, 0.409060475365608]; %! assert_equal (coeff, coeff_ref, 1e-6); %! assert_equal (pcvar, [49.558237292069599; 30.960532427249241], 1e-4); %! assert_equal (mu, [-0.1, 0.2, 0.5, -0.9], 1e-10); %! assert_equal (v, 10.623960883864141, 1e-4); %!test %! Y = reshape (mod ((1:40)*7, 17), 10, 4) - 8; %! [coeff, score] = ppca (Y, 2); %! score_ref = [ -2.225140840148114, 4.921963749319239; ... %! 7.787588722827117, -7.324026640834169; ... %! -5.050861878994857, 1.641002156881092; ... %! 10.104537612540948, 2.342550721115434; ... %! -7.876582917841600, -1.639959435557055; ... %! -1.283233827199200, 6.015617613465288; ... %! -1.459120725735530, -11.581703199267448; ... %! -4.108954866045941, 2.734656021027141; ... %! 11.046444625489862, 3.436204585261484; ... %! -6.934675904892686, -0.546305571411006]; %! assert_equal (score, score_ref, 1e-4); %!test %! ## coeff is orthonormal and score is the projection of the centred data. %! Y = reshape (mod ((1:40)*7, 17), 10, 4) - 8; %! [coeff, score, pcvar, mu] = ppca (Y, 2); %! assert_equal (coeff' * coeff, eye (2), 1e-10); %! assert_equal (score, (Y - mu) * coeff, 1e-10); %!test %! ## Missing-data (NaN) case fitted by expectation-maximization. %! Y = reshape (mod ((1:40)*7, 17), 10, 4) - 8; %! Y(3,2) = NaN; %! Y(7,4) = NaN; %! opt = struct ("MaxIter", 5000, "TolFun", 1e-12, "TolX", 1e-12); %! [coeff, score, pcvar, mu, v] = ppca (Y, 2, "Options", opt); %! coeff_ref = [ 0.489878122099491, -0.455706110813571; ... %! 0.631344835584810, 0.254329922477782; ... %! 0.331227809703440, 0.763974559487245; ... %! -0.501708343709497, 0.379461596944783]; %! assert_equal (coeff, coeff_ref, 1e-4); %! assert_equal (pcvar, [49.932943652722741; 28.917799526089862], 1e-3); %! assert_equal (mu, [-0.099999999999924, 0.225205294055629, ... %! 0.500000000000008, -0.683793705409387], 1e-4); %! assert_equal (v, 10.684669221332555, 1e-3); %!test %! ## Output sizes. %! Y = reshape (mod ((1:40)*7, 17), 10, 4) - 8; %! [coeff, score, pcvar, mu, v, S] = ppca (Y, 3); %! assert_equal (size (coeff), [4, 3]); %! assert_equal (size (score), [10, 3]); %! assert_equal (size (pcvar), [3, 1]); %! assert_equal (size (mu), [1, 4]); %! assert_equal (isscalar (v), true); %! assert_equal (isfield (S, "W") && isfield (S, "Recon"), true); ## Test input validation %!error ppca (ones (5, 3)) %!error ppca ({1, 2}, 1) %!error ppca ([1+2i, 3; 4, 5], 1) %!error ppca (ones (5, 3), 0) %!error ppca (ones (5, 3), 1.5) %!error ppca (ones (5, 3), 3) %!error ppca (ones (5, 3), 1, "W0") %!error ppca (ones (5, 3), 1, "foo", 1) %!error ppca (ones (5, 3), 1, "W0", ones (2, 2)) %!error ppca (ones (5, 3), 1, "Options", 5) statistics-release-1.9.2/inst/Dimensionality_Reduction/princomp.m000066400000000000000000000127271524624707500253460ustar00rootroot00000000000000## Copyright (C) 2013-2019 Fernando Damian Nieuwveldt ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License ## as published by the Free Software Foundation; either version 3 ## of the License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{COEFF} =} princomp (@var{X}) ## @deftypefnx {statistics} {[@var{COEFF}, @var{SCORE}] =} princomp (@var{X}) ## @deftypefnx {statistics} {[@var{COEFF}, @var{SCORE}, @var{latent}] =} princomp (@var{X}) ## @deftypefnx {statistics} {[@var{COEFF}, @var{SCORE}, @var{latent}, @var{tsquare}] =} princomp (@var{X}) ## @deftypefnx {statistics} {[@dots{}] =} princomp (@var{X}, "econ") ## ## Performs a principal component analysis on a NxP data matrix X. ## ## @itemize @bullet ## @item ## @var{COEFF} : returns the principal component coefficients ## @item ## @var{SCORE} : returns the principal component scores, the representation of X ## in the principal component space ## @item ## @var{LATENT} : returns the principal component variances, i.e., the ## eigenvalues of the covariance matrix X. ## @item ## @var{TSQUARE} : returns Hotelling's T-squared Statistic for each observation ## in X ## @item ## [...] = princomp(X,'econ') returns only the elements of latent that are not ## necessarily zero, and the corresponding columns of COEFF and SCORE, that is, ## when n <= p, only the first n-1. This can be significantly faster when p is ## much larger than n. In this case the svd will be applied on the transpose of ## the data matrix X ## ## @end itemize ## ## @subheading References ## ## @enumerate ## @item ## Jolliffe, I. T., Principal Component Analysis, 2nd Edition, Springer, 2002 ## ## @end enumerate ## @end deftypefn function [COEFF, SCORE, latent, tsquare] = princomp (X, varargin) if (nargin < 1 || nargin > 2) print_usage (); endif if (nargin == 2 && ! strcmpi (varargin{:}, 'econ')) error (strcat ("princomp: if a second input argument is present,", ... " it must be the string 'econ'.")); endif [nobs nvars] = size (X); # Center the columns to mean zero Xcentered = bsxfun (@minus,X,mean (X)); # Check if there are more variables then observations if nvars <= nobs [U,S,COEFF] = svd (Xcentered, 'econ'); else # Calculate the svd on the transpose matrix, much faster if (nargin == 2 && strcmpi (varargin{:} , 'econ')) [COEFF,S,V] = svd (Xcentered' , 'econ'); else [COEFF,S,V] = svd (Xcentered'); endif endif if nargout > 1 # Get the Scores SCORE = Xcentered * COEFF; # Get the rank of the SCORE matrix r = rank (SCORE); # Only use the first r columns, pad rest with zeros if economy != 'econ' SCORE = SCORE(:,1:r) ; if ! (nargin == 2 && strcmpi (varargin{:} , 'econ')) SCORE = [SCORE, zeros(nobs , nvars-r)]; else COEFF = COEFF(: , 1:r); endif endif if nargout > 2 # This is the same as the eigenvalues of the covariance matrix of X latent = (diag (S) .^ 2 / (size (Xcentered, 1) - 1))(1:r); if ! (nargin == 2 && strcmpi (varargin{:} , 'econ')) latent= [latent; zeros(nvars-r,1)]; endif endif if (nargout > 3) # Calculate the Hotelling T-Square statistic for the observations tsquare = sumsq (zscore (SCORE(:,1:r)), 2); endif endfunction %!shared COEFF,SCORE,latent,tsquare,m,x,R,V,lambda,i,S,F #NIST Engineering Statistics Handbook example (6.5.5.2) %!test %! x=[7 4 3 %! 4 1 8 %! 6 3 5 %! 8 6 1 %! 8 5 7 %! 7 2 9 %! 5 3 3 %! 9 5 8 %! 7 4 5 %! 8 2 2]; %! R = corrcoef (x); %! [V, lambda] = eig (R); %! [~, i] = sort (diag (lambda), 'descend'); #arrange largest PC first %! S = V(:, i) * diag (sqrt (diag (lambda)(i))); %! ## contribution of first 2 PCs to each original variable %!assert_equal (diag (S(:, 1:2)*S(:, 1:2)'), [0.8662; 0.8420; 0.9876], 1E-4); %! B = V(:, i) * diag ( 1./ sqrt (diag (lambda)(i))); %! F = zscore (x)*B; %! [COEFF,SCORE,latent,tsquare] = princomp (zscore (x, 1)); %!assert_equal (tsquare,sumsq (F, 2),1E4*eps); %!test %! x=[1,2,3;2,1,3]'; %! [COEFF,SCORE,latent,tsquare] = princomp (x); %! m=[sqrt(2),sqrt(2);sqrt(2),-sqrt(2);-2*sqrt(2),0]/2; %! m(:,1) = m(:,1)*sign (COEFF(1,1)); %! m(:,2) = m(:,2)*sign (COEFF(1,2)); %!assert_equal (COEFF,m(1:2,:),10*eps); %!assert_equal (SCORE,-m,10*eps); %!assert_equal (latent,[1.5;.5],10*eps); %!assert_equal (tsquare,[4;4;4]/3,10*eps); %!test %! x=x'; %! [COEFF,SCORE,latent,tsquare] = princomp (x); %! m=[sqrt(2),sqrt(2),0;-sqrt(2),sqrt(2),0;0,0,2]/2; %! m(:,1) = m(:,1)*sign (COEFF(1,1)); %! m(:,2) = m(:,2)*sign (COEFF(1,2)); %! m(:,3) = m(:,3)*sign (COEFF(3,3)); %!assert_equal (COEFF,m,10*eps); %!assert_equal (SCORE(:,1),-m(1:2,1),10*eps); %!assert_equal (SCORE(:,2:3),zeros (2),10*eps); %!assert_equal (latent,[1;0;0],10*eps); %!assert_equal (tsquare,[0.5;0.5],10*eps) %!test %! [COEFF,SCORE,latent,tsquare] = princomp (x, 'econ'); %!assert_equal (COEFF,m(:, 1),10*eps); %!assert_equal (SCORE,-m(1:2,1),10*eps); %!assert_equal (latent,[1],10*eps); %!assert_equal (tsquare,[0.5;0.5],10*eps) statistics-release-1.9.2/inst/Dimensionality_Reduction/private/000077500000000000000000000000001524624707500250025ustar00rootroot00000000000000statistics-release-1.9.2/inst/Dimensionality_Reduction/private/objective_history.m000066400000000000000000000034521524624707500307170ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{out} =} objective_history (@var{action}, @var{val}) ## ## Collect the objective values @code{fminunc} walks through. ## ## @var{action} is @qcode{'reset'} to begin a fit, @qcode{'add'} to append ## @var{val}, or @qcode{'get'} to return everything gathered since the reset. ## Only @qcode{'get'} returns anything, the others leaving @var{out} empty. ## ## @code{fminunc} offers no history of its own and its @code{OutputFcn} cannot ## return one, so the values are gathered here and reported as ## @code{FitInfo.Objective}, which would otherwise hold the final value alone. ## One fit runs at a time, which is what lets a single buffer serve both ## classes. ## ## @end deftypefn function out = objective_history (action, val) persistent buf out = []; switch (action) case 'reset' buf = zeros (0, 1); case 'add' buf(end+1, 1) = val; case 'get' out = buf; otherwise error ("objective_history: unknown action '%s'.", action); endswitch endfunction statistics-release-1.9.2/inst/Dimensionality_Reduction/private/objective_history_fcn.m000066400000000000000000000026461524624707500315510ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{stop} =} objective_history_fcn (@var{x}, @var{optimValues}, @var{state}) ## ## Append the current objective to the history, as @code{fminunc}'s ## @code{OutputFcn}. ## ## @var{stop} is always false, so a fit is never halted from here, and @var{x} ## is unused. The @qcode{'init'} call carries the objective at the starting ## weights, which is the first entry of the history MATLAB reports. ## ## @end deftypefn function stop = objective_history_fcn (x, optimValues, state) stop = false; if (any (strcmp (state, {"init", "iter"}))) objective_history ("add", optimValues.fval); endif endfunction statistics-release-1.9.2/inst/Dimensionality_Reduction/procrustes.m000066400000000000000000000312631524624707500257240ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/OR ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, OR (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{d} =} procrustes (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{d} =} procrustes (@var{X}, @var{Y}, @var{param1}, @var{value1}, @dots{}) ## @deftypefnx {statistics} {[@var{d}, @var{Z}] =} procrustes (@dots{}) ## @deftypefnx {statistics} {[@var{d}, @var{Z}, @var{transform}] =} procrustes (@dots{}) ## ## Procrustes Analysis. ## ## @code{@var{d} = procrustes (@var{X}, @var{Y})} computes a linear ## transformation of the points in the matrix @var{Y} to best conform them to ## the points in the matrix @var{X} by minimizing the sum of squared errors, as ## the goodness of fit criterion, which is returned in @var{d} as a ## dissimilarity measure. @var{d} is standardized by a measure of the scale of ## @var{X}, given by ## @itemize ## @item @qcode{sum (sum ((X - repmat (mean (X, 1), size (X, 1), 1)) .^ 2, 1))} ## @end itemize ## i.e., the sum of squared elements of a centered version of @var{X}. However, ## if @var{X} comprises repetitions of the same point, the sum of squared errors ## is not standardized. ## ## @var{X} and @var{Y} must have the same number of points (rows) and ## @qcode{procrustes} matches the @math{i}-th point in @var{Y} to the ## @math{i}-th point in @var{X}. Points in @var{Y} can have smaller dimensions ## (columns) than those in @var{X}, but not the opposite. Missing dimensions in ## @var{Y} are added with padding columns of zeros as necessary to match the ## the dimensions in @var{X}. ## ## @code{[@var{d}, @var{Z}] = procrustes (@var{X}, @var{Y})} also returns the ## transformed values in @var{Y}. ## ## @code{[@var{d}, @var{Z}, @var{transform}] = procrustes (@var{X}, @var{Y})} ## also returns the transformation that maps @var{Y} to @var{Z}. ## ## @var{transform} is a structure with fields: ## ## @multitable @columnfractions 0.1 0.8 ## @item @qcode{c} @tab the translation component ## @item @qcode{T} @tab the orthogonal rotation and reflection ## component ## @item @qcode{b} @tab the scale component ## @end multitable ## ## So that @code{@var{Z} = @var{transform}.@qcode{b} * @var{Y} * ## @var{transform}.@qcode{T} + @var{transform}.@qcode{c}} ## ## @qcode{procrustes} can take two optional parameters as Name-Value pairs. ## ## @code{[@dots{}] = procrustes (@dots{}, @qcode{'Scaling'}, @qcode{false})} ## computes a transformation that does not include scaling, that is ## @var{transform}.@qcode{b} = 1. Setting @qcode{'Scaling'} to @qcode{true} ## includes a scaling component, which is the default. ## ## @code{[@dots{}] = procrustes (@dots{}, @qcode{'Reflection'}, @qcode{false})} ## computes a transformation that does not include a reflection component, that ## is @var{transform}.@qcode{T} = 1. Setting @qcode{'Reflection'} to ## @qcode{true} forces the solution to include a reflection component in the ## computed transformation, that is @var{transform}.@qcode{T} = -1. ## ## @code{[@dots{}] = procrustes (@dots{}, @qcode{'Reflection'}, @qcode{'best'})} ## computes the best fit procrustes solution, which may or may not include a ## reflection component, which is the default. ## ## @seealso{cmdscale} ## @end deftypefn function [d, Z, transform] = procrustes (X, Y, varargin) if (nargin < 2 || nargin > 6) print_usage (); endif ## Check X and Y for appropriate input if (isempty (X) || ! ismatrix (X) || ndims (X) != 2 || ... isempty (Y) || ! ismatrix (Y) || ndims (Y) != 2) error ("procrustes: X and Y must be 2-dimensional matrices."); endif if (any (isnan (X(:))) || any (isinf (X(:))) || iscomplex (X) || ... any (isnan (Y(:))) || any (isinf (Y(:))) || iscomplex (Y)) error ("procrustes: values in X and Y must be real."); endif [Xp, Xd] = size (X); [Yp, Yd] = size (Y); if (Yp != Xp) error ("procrustes: X and Y must have equal number of rows."); elseif (Yd > Xd) error ("procrustes: X must have at least as many columns as Y."); endif ## Add defaults and parse optional arguments scaling = true; reflection = 'best'; if (nargin > 2) params = numel (varargin); if ((params / 2) != fix (params / 2)) error ("procrustes: optional arguments must be in Name-Value pairs.") endif for idx = 1:2:params name = varargin{idx}; value = varargin{idx+1}; switch (lower (name)) case 'scaling' scaling = value; if (! (isscalar (scaling) && islogical (scaling))) error ("procrustes: invalid value for scaling."); endif case 'reflection' reflection = value; if (! (strcmpi (reflection, 'best') || islogical (reflection))) error ("procrustes: invalid value for reflection."); endif otherwise error ("procrustes: invalid name for optional arguments."); endswitch endfor endif ## Center at the origin. Xmu = mean (X, 1); Ymu = mean (Y, 1); X_0 = X - repmat (Xmu, Xp, 1); Y_0 = Y - repmat (Ymu, Xp, 1); ## Get centroid size and check for X or Y having identical points Xsumsq = sum (X_0 .^ 2, 1); Ysumsq = sum (Y_0 .^ 2, 1); constX = all (Xsumsq <= abs (eps (class (X)) * Xp * Xmu) .^ 2); constY = all (Ysumsq <= abs (eps (class (X)) * Xp * Ymu) .^ 2); Xsumsq = sum (Xsumsq); Ysumsq = sum (Ysumsq); if (! constX && ! constY) ## Scale to "centered" Frobenius norm. normX = sqrt (Xsumsq); normY = sqrt (Ysumsq); X_0 = X_0 / normX; Y_0 = Y_0 / normY; ## Fix dimension space (if necessary) if (Yd < Xd) Y_0 = [Y_0 zeros(Xp, Xd-Yd)]; endif ## Find optimal rotation matrix of Y A = X_0' * Y_0; [U, S, V] = svd (A); T = V * U'; ## Handle reflection only if 'true' or 'false' was given if (! strcmpi (reflection, 'best')) is_reflection = (det (T) < 0); ## Force a reflection if data and reflection option disagree if (reflection != is_reflection) V(:,end) = -V(:,end); S(end,end) = -S(end,end); T = V * U'; endif endif ## Apply scaling (if requested) traceTA = sum (diag (S)); if (scaling) b = traceTA * normX / normY; d = 1 - traceTA .^ 2; if (nargout > 1) Z = normX * traceTA * Y_0 * T + repmat (Xmu, Xp, 1); endif else b = 1; d = 1 + Ysumsq / Xsumsq - 2 * traceTA * normY / normX; if (nargout > 1) Z = normY * Y_0 * T + repmat (Xmu, Xp, 1); endif endif ## 3rd output argument if (nargout > 2) if (Yd < Xd) T = T(1:Yd,:); endif c = Xmu - b * Ymu * T; transform = struct ('T', T, 'b', b, 'c', repmat (c, Xp, 1)); endif ## Special cases elseif constX # Identical points in X d = 0; Z = repmat (Xmu, Xp, 1); T = eye (Yd, Xd); transform = struct ('T', T, 'b', 0, 'c', Z); else # Identical points in Y d = 1; Z = repmat (Xmu, Xp, 1); T = eye (Yd, Xd); transform = struct ('T', T, 'b', 0, 'c', Z); endif endfunction %!demo %! ## Create some random points in two dimensions %! rng (42); %! n = 10; %! X = normrnd (0, 1, [n, 2]); %! %! ## Those same points, rotated, scaled, translated, plus some noise %! S = [0.5, -sqrt(3)/2; sqrt(3)/2, 0.5]; # rotate 60 degrees %! Y = normrnd (0.5*X*S + 2, 0.05, n, 2); %! %! ## Conform Y to X, plot original X and Y, and transformed Y %! [d, Z] = procrustes (X, Y); %! plot (X(:,1), X(:,2), 'rx', Y(:,1), Y(:,2), 'b.', Z(:,1), Z(:,2), 'bx'); %!demo %! ## Find Procrustes distance and plot superimposed shape %! %! X = [40 88; 51 88; 35 78; 36 75; 39 72; 44 71; 48 71; 52 74; 55 77]; %! Y = [36 43; 48 42; 31 26; 33 28; 37 30; 40 31; 45 30; 48 28; 51 24]; %! plot (X(:,1),X(:,2),'x'); %! hold on %! plot (Y(:,1),Y(:,2),'o'); %! xlim ([0 100]); %! ylim ([0 100]); %! legend ('Target shape (X)', 'Source shape (Y)'); %! [d, Z] = procrustes (X, Y) %! plot (Z(:,1), Z(:,2), 's'); %! legend ('Target shape (X)', 'Source shape (Y)', 'Transformed shape (Z)'); %! hold off %!demo %! ## Apply Procrustes transformation to larger set of points %! %! ## Create matrices with landmark points for two triangles %! X = [5, 0; 5, 5; 8, 5]; # target %! Y = [0, 0; 1, 0; 1, 1]; # source %! %! ## Create a matrix with more points on the source triangle %! Y_mp = [linspace(Y(1,1),Y(2,1),10)', linspace(Y(1,2),Y(2,2),10)'; ... %! linspace(Y(2,1),Y(3,1),10)', linspace(Y(2,2),Y(3,2),10)'; ... %! linspace(Y(3,1),Y(1,1),10)', linspace(Y(3,2),Y(1,2),10)']; %! %! ## Plot both shapes, including the larger set of points for the source shape %! plot ([X(:,1); X(1,1)], [X(:,2); X(1,2)], 'bx-'); %! hold on %! plot ([Y(:,1); Y(1,1)], [Y(:,2); Y(1,2)], 'ro-', 'MarkerFaceColor', 'r'); %! plot (Y_mp(:,1), Y_mp(:,2), 'ro'); %! xlim ([-1 10]); %! ylim ([-1 6]); %! legend ('Target shape (X)', 'Source shape (Y)', ... %! 'More points on Y', 'Location', 'northwest'); %! hold off %! %! ## Obtain the Procrustes transformation %! [d, Z, transform] = procrustes (X, Y) %! %! ## Use the Procrustes transformation to superimpose the more points (Y_mp) %! ## on the source shape onto the target shape, and then visualize the results. %! Z_mp = transform.b * Y_mp * transform.T + transform.c(1,:); %! figure %! plot ([X(:,1); X(1,1)], [X(:,2); X(1,2)], 'bx-'); %! hold on %! plot ([Y(:,1); Y(1,1)], [Y(:,2); Y(1,2)], 'ro-', 'MarkerFaceColor', 'r'); %! plot (Y_mp(:,1), Y_mp(:,2), 'ro'); %! xlim ([-1 10]); %! ylim ([-1 6]); %! plot ([Z(:,1); Z(1,1)],[Z(:,2); Z(1,2)],'ks-','MarkerFaceColor','k'); %! plot (Z_mp(:,1),Z_mp(:,2),'ks'); %! legend ('Target shape (X)', 'Source shape (Y)', ... %! 'More points on Y', 'Transformed source shape (Z)', ... %! 'Transformed additional points', 'Location', 'northwest'); %! hold off %!demo %! ## Compare shapes without reflection %! %! T = [33, 93; 33, 87; 33, 80; 31, 72; 32, 65; 32, 58; 30, 72; ... %! 28, 72; 25, 69; 22, 64; 23, 59; 26, 57; 30, 57]; %! S = [48, 83; 48, 77; 48, 70; 48, 65; 49, 59; 49, 56; 50, 66; ... %! 52, 66; 56, 65; 58, 61; 57, 57; 54, 56; 51, 55]; %! plot (T(:,1), T(:,2), 'x-'); %! hold on %! plot (S(:,1), S(:,2), 'o-'); %! legend ('Target shape (d)', 'Source shape (b)'); %! hold off %! d_false = procrustes (T, S, 'reflection', false); %! printf ("Procrustes distance without reflection: %f\n", d_false); %! d_true = procrustes (T, S, 'reflection', true); %! printf ("Procrustes distance with reflection: %f\n", d_true); %! d_best = procrustes (T, S, 'reflection', 'best'); %! printf ("Procrustes distance with best fit: %f\n", d_true); ## Test input validation %!error procrustes (); %!error procrustes (1); %!error procrustes (1, 2, 3, 4, 5, 6, 7); %!error ... %! procrustes (ones (2, 2, 2), ones (2, 2, 2)); %!error ... %! procrustes ([1, 2; -3, 4; 2, 3], [1, 2; -3, 4; 2, 3+i]); %!error ... %! procrustes ([1, 2; -3, 4; 2, 3], [1, 2; -3, 4; 2, NaN]); %!error ... %! procrustes ([1, 2; -3, 4; 2, 3], [1, 2; -3, 4; 2, Inf]); %!error ... %! procrustes (ones (10 ,3), ones (11, 3)); %!error ... %! procrustes (ones (10 ,3), ones (10, 4)); %!error ... %! procrustes (ones (10 ,3), ones (10, 3), 'reflection'); %!error ... %! procrustes (ones (10 ,3), ones (10, 3), true); %!error ... %! procrustes (ones (10 ,3), ones (10, 3), 'scaling', 0); %!error ... %! procrustes (ones (10 ,3), ones (10, 3), 'scaling', [true true]); %!error ... %! procrustes (ones (10 ,3), ones (10, 3), 'reflection', 1); %!error ... %! procrustes (ones (10 ,3), ones (10, 3), 'reflection', 'some'); %!error ... %! procrustes (ones (10 ,3), ones (10, 3), 'param1', 'some'); statistics-release-1.9.2/inst/Dimensionality_Reduction/rica.m000066400000000000000000000300311524624707500244210ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} rica (@var{X}, @var{Q}) ## @deftypefnx {statistics} {@var{Mdl} =} rica (@var{X}, @var{Q}, @var{Name}, @var{Value}) ## ## Reconstruction independent component analysis (RICA) for feature extraction. ## ## @code{@var{Mdl} = rica (@var{X}, @var{Q})} learns @var{Q} features from the ## @math{N * P} data matrix @var{X} (rows are observations, columns are ## predictors) and returns a @qcode{ReconstructionICA} object @var{Mdl}. Apply ## the learned transformation to data with @code{transform (@var{Mdl}, @var{X})}, ## which returns @code{@var{X} * @var{Mdl}.TransformWeights}. ## ## The @math{P * @var{Q}} weight matrix (with unit-length columns) minimizes the ## objective ## ## @example ## @var{Lambda} * ||@var{X} * @var{W} * @var{W}' - @var{X}||_F^2 ## + sum (sum (@var{g} (@var{X} * @var{W}))) ## @end example ## ## @noindent ## over the transformation weights @var{W}, combining a reconstruction cost with ## a sparsity contrast @var{g} applied elementwise and selected by ## @qcode{'ContrastFcn'}. ## ## Name/Value pairs: ## ## @table @asis ## @item @qcode{'IterationLimit'} ## Maximum number of iterations (default 1000). ## ## @item @qcode{'Lambda'} ## Weight of the reconstruction term (default 1). ## ## @item @qcode{'Standardize'} ## Logical; center and scale each predictor before fitting (default ## @qcode{false}). ## ## @item @qcode{'ContrastFcn'} ## The sparsity contrast @var{g} applied to each element @math{z} of ## @math{@var{X} * @var{W}}: @qcode{'logcosh'} (default), which is ## @math{0.5 * log (cosh (2 * z))}; @qcode{'exp'}, which is ## @math{-exp (-z^2 / 2)}; or @qcode{'sqrt'}, which is ## @math{sqrt (z^2 + 1e-8)}, a smooth stand-in for @math{abs (z)}. ## ## @item @qcode{'InitialTransformWeights'} ## A @math{P * @var{Q}} initial value for the weights. The default is random. ## ## @item @qcode{'NonGaussianityIndicator'} ## A @var{Q}-element vector of @math{+1} and @math{-1}, one per learned ## feature, setting the sign its contrast term carries in the objective: ## @math{+1} seeks a super-Gaussian (sparse) feature and @math{-1} a ## sub-Gaussian (spread) one. The default is all @math{+1}. ## ## @item @qcode{'GradientTolerance'}, @qcode{'StepTolerance'} ## Stop once the gradient's or the step's infinity norm falls to or below the ## given value (default @qcode{1e-6} each). They govern the fit only under ## @qcode{'Solver', 'lbfgs'}; the @qcode{'quasinewton'} solver runs to its own ## tighter internal tolerances and records these without acting on them. ## ## @item @qcode{'Solver'} ## @qcode{'quasinewton'} (default) minimizes through Octave's @code{fminunc}, ## which carries a full inverse Hessian. @qcode{'lbfgs'} selects the ## limited-memory BFGS solver MATLAB uses, holding as many curvature pairs as ## the transform has parameters, and is several times faster here. It stops ## where @qcode{'GradientTolerance'} and @qcode{'StepTolerance'} say to, so a ## value tighter than the default carries it further. ## @end table ## ## @subheading Note on reproducibility ## ## The RICA objective is not convex and is minimized by a quasi-Newton solver, so ## the learned weights depend on the starting point and the solver, and are only ## defined up to a permutation and sign of the feature columns. Different runs ## (or different software, including MATLAB) may return different weights that ## nonetheless describe an equally valid feature transformation. Fix ## @qcode{'InitialTransformWeights'} for a reproducible result. ## ## @seealso{ReconstructionICA, sparsefilt, pca} ## @end deftypefn function Mdl = rica (X, Q, varargin) ## Input validation if (nargin < 2) print_usage (); endif if (! (isnumeric (X) && ismatrix (X) && ndims (X) == 2)) error ("rica: X must be a numeric matrix."); endif if (! (isreal (X) && all (isfinite (X(:))))) error ("rica: X must be real and finite."); endif [n, p] = size (X); if (! (isscalar (Q) && isnumeric (Q) && Q >= 1 && Q == fix (Q))) error ("rica: Q must be a positive integer."); endif if (mod (numel (varargin), 2) != 0) error ("rica: Name/Value arguments must come in pairs."); endif ## Validate a supplied initial-weights matrix up front for a clear message for k = 1:2:numel (varargin) if (ischar (varargin{k}) && strcmpi (varargin{k}, "InitialTransformWeights")) W0 = varargin{k+1}; if (! (isnumeric (W0) && isequal (size (W0), [p, Q]))) error ("rica: 'InitialTransformWeights' must be a %d-by-%d matrix.", p, Q); endif endif endfor Mdl = ReconstructionICA (X, Q, varargin{:}); endfunction %!demo %! ## Learn two features from data with the default (random) start. %! X = [1, 2, 3, 4; 2, 3, 4, 5; -1, 0, 1, 2; 3, 1, 4, 1; 0, 2, 1, 3]; %! Mdl = rica (X, 2, "IterationLimit", 200); %! Z = transform (Mdl, X) ## The RICA objective is verified against MATLAB R2023b for ## X = reshape (mod ((1:60)*7, 13), 12, 5) - 6 with fixed initial weights. %!shared X, W0 %! X = reshape (mod ((1:60)*7, 13), 12, 5) - 6; %! W0 = reshape (mod ((1:15)*3, 7), 5, 3) - 3; %!test %! Mdl = rica (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 1000); %! ## transform is X * TransformWeights, with unit-length weight columns %! assert_equal (transform (Mdl, X), X * Mdl.TransformWeights, 1e-12); %! assert_equal (sqrt (sum (Mdl.TransformWeights .^ 2, 1)), [1, 1, 1], 1e-10); %! assert_equal (size (Mdl.TransformWeights), [5, 3]); %!test %! ## The solver reaches a low objective value (MATLAB's is 194.5156). %! Mdl = rica (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 1000); %! assert_equal (Mdl.FitInfo.Objective(end) < 195, true); %!test %! ## The objective value stored in FitInfo matches a direct evaluation. %! Mdl = rica (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 1000); %! W = Mdl.TransformWeights; %! Z = X * W; %! f = sum (sumsq (X * (W * W') - X)) + sum (sum (0.5 * log (cosh (2 * Z)))); %! assert_equal (Mdl.FitInfo.Objective(end), f, 1e-6); %!test %! ## The 'exp' contrast objective matches a direct evaluation. %! Mdl = rica (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 200, ... %! "ContrastFcn", "exp"); %! W = Mdl.TransformWeights; %! Z = X * W; %! f = sum (sumsq (X * (W * W') - X)) + sum (sum (-exp (-Z .^ 2 / 2))); %! assert_equal (Mdl.FitInfo.Objective(end), f, 1e-6); %!test %! ## The 'sqrt' contrast objective matches a direct evaluation, the smoothing %! ## constant included. %! Mdl = rica (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 200, ... %! "ContrastFcn", "sqrt"); %! W = Mdl.TransformWeights; %! Z = X * W; %! f = sum (sumsq (X * (W * W') - X)) + sum (sum (sqrt (Z .^ 2 + 1e-8))); %! assert_equal (Mdl.FitInfo.Objective(end), f, 1e-6); %!test %! ## The smoothing constant is visible only where a projection is zero, so it %! ## is pinned there: one column of X projected onto a weight orthogonal to it. %! Xz = [1 0; -2 0; 3 0; 0 0]; %! Wz = [0; 1]; %! M = rica (Xz, 1, "InitialTransformWeights", Wz, "IterationLimit", 0, ... %! "Standardize", false, "ContrastFcn", "sqrt"); %! assert_equal (M.FitInfo.Objective(1), 14 + 4 * sqrt (1e-8), 1e-12); %!test %! ## Each contrast gives its own objective; they are not interchangeable. %! args = {"InitialTransformWeights", W0, "Standardize", false, ... %! "IterationLimit", 0}; %! f1 = rica (X, 3, args{:}, "ContrastFcn", "logcosh").FitInfo.Objective(1); %! f2 = rica (X, 3, args{:}, "ContrastFcn", "exp").FitInfo.Objective(1); %! f3 = rica (X, 3, args{:}, "ContrastFcn", "sqrt").FitInfo.Objective(1); %! assert_equal (f1 != f2 && f2 != f3 && f1 != f3, true); %!test %! ## Standardize centers and scales the data before transforming. %! Mdl = rica (X, 2, "Standardize", true, "IterationLimit", 100, ... %! "InitialTransformWeights", W0(:,1:2)); %! assert_equal (size (Mdl.Mu), [5, 1]); %! assert_equal (size (Mdl.Sigma), [5, 1]); %!test %! ## FitInfo carries the whole trajectory, not just its final value: two %! ## columns of equal length, Iteration the 0-based index, Objective the %! ## objective at the starting weights first and the solution last %! Mdl = rica (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 1000); %! it = Mdl.FitInfo.Iteration; %! ob = Mdl.FitInfo.Objective; %! assert_equal (columns (it), 1); %! assert_equal (size (ob), size (it)); %! assert_equal (it, (0:numel (ob) - 1)'); %! assert_equal (all (diff (ob) <= 1e-10), true); %! ## the first entry is the objective at the starting weights, which is what %! ## a fit allowed no iterations at all reports %! M0 = rica (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 0); %! assert_equal (numel (M0.FitInfo.Objective), 1); %! assert_equal (ob(1), M0.FitInfo.Objective(1), 1e-12); %!test %! ## a capped run stops with one entry per iteration plus the starting point %! Mdl = rica (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 5); %! assert_equal (numel (Mdl.FitInfo.Objective) <= 6, true); %! assert_equal (Mdl.FitInfo.Iteration(1), 0); ## Test input validation %!test %! ## 'lbfgs' reaches the optimum the default solver reaches %! args = {"InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 1000}; %! Mq = rica (X, 3, args{:}); %! Ml = rica (X, 3, args{:}, "Solver", "lbfgs"); %! assert_equal (Ml.FitInfo.Objective(end), Mq.FitInfo.Objective(end), 1e-6); %!test %! ## The trajectory starts at the initial weights whichever solver ran %! args = {"InitialTransformWeights", W0, "IterationLimit", 20}; %! Mq = rica (X, 3, args{:}); %! Ml = rica (X, 3, args{:}, "Solver", "lbfgs"); %! assert_equal (Ml.FitInfo.Iteration(1), 0); %! assert_equal (Ml.FitInfo.Objective(1), Mq.FitInfo.Objective(1), 1e-10); %!test %! ## The solver that ran is recorded %! Mdl = rica (X, 3, "InitialTransformWeights", W0, "Solver", "lbfgs"); %! assert_equal (Mdl.ModelParameters.Solver, "lbfgs"); %!error ... %! rica (X, 3, "Solver", "bogus") %!error ... %! rica (X, 3, "Solver", 5) %!error rica (ones (5, 3)) %!error rica ({1, 2}, 1) %!error rica ([1, Inf; 2, 3], 1) %!error rica (ones (5, 3), 0) %!error rica (ones (5, 3), 1.5) %!error ... %! rica (ones (4, 5), 2, "InitialTransformWeights", ones (3, 3)) %!test %! ## The NonGaussianityIndicator pair reaches the model through rica. %! Mdl = rica (X, 3, "InitialTransformWeights", W0, "IterationLimit", 100, ... %! "NonGaussianityIndicator", [1; -1; 1]); %! assert_equal (Mdl.NonGaussianityIndicator, [1; -1; 1]); %!error ... %! rica (ones (4, 5), 2, "ContrastFcn", "bogus", "InitialTransformWeights", ones (5, 2)) %!error ... %! rica (ones (4, 5), 2, "bogus", 1) statistics-release-1.9.2/inst/Dimensionality_Reduction/rotatefactors.m000066400000000000000000000471031524624707500263730ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{B} =} rotatefactors (@var{A}) ## @deftypefnx {statistics} {@var{B} =} rotatefactors (@var{A}, @var{Name}, @var{Value}, @dots{}) ## @deftypefnx {statistics} {[@var{B}, @var{T}] =} rotatefactors (@dots{}) ## ## Rotate a factor-loading matrix. ## ## @code{@var{B} = rotatefactors (@var{A})} rotates the @math{D * M} factor ## loadings matrix @var{A} (@math{D} observed variables, @math{M} factors) to ## the @qcode{'varimax'} criterion and returns the rotated loadings @var{B}, the ## same size as @var{A}. ## ## @code{[@var{B}, @var{T}] = rotatefactors (@dots{})} also returns the ## @math{M * M} rotation matrix @var{T}, so that @code{@var{B} = @var{A} * ## @var{T}}. For the orthogonal methods @var{T} is orthonormal ## (@code{@var{T}' * @var{T}} is the identity); for the oblique methods ## (@qcode{'promax'} and oblique @qcode{'procrustes'}) it is a general invertible ## matrix. ## ## The rotation is controlled by @var{Name}/@var{Value} pairs: ## ## @table @asis ## @item @qcode{'Method'} ## The rotation criterion, one of: ## ## @table @asis ## @item @qcode{'varimax'} (default) ## Orthomax with a criterion coefficient of 1; maximizes the variance of the ## squared loadings within each factor. ## ## @item @qcode{'quartimax'} ## Orthomax with a coefficient of 0; simplifies the description of each variable. ## ## @item @qcode{'equamax'} ## Orthomax with a coefficient of @math{M / 2}. ## ## @item @qcode{'parsimax'} ## Orthomax with a coefficient of @math{D (M - 1) / (D + M - 2)}. ## ## @item @qcode{'orthomax'} ## General orthomax with the coefficient given by @qcode{'Coeff'}. ## ## @item @qcode{'promax'} ## Oblique rotation obtained by fitting an oblique transformation to a target ## built from a @qcode{'varimax'} solution raised to the power @qcode{'Power'}. ## ## @item @qcode{'procrustes'} ## Rotation towards the @qcode{'Target'} matrix, either orthogonal or oblique ## according to @qcode{'Type'}. ## @end table ## ## @item @qcode{'Normalize'} ## @qcode{'on'} (default) applies Kaiser normalization (each row of @var{A} is ## scaled to unit length before the orthomax rotation and unscaled afterwards); ## @qcode{'off'} disables it. Ignored by @qcode{'procrustes'}. ## ## @item @qcode{'Reltol'} ## Relative convergence tolerance for the iterative orthomax rotation. The ## default is @code{sqrt (eps)}. ## ## @item @qcode{'Maxit'} ## Maximum number of iterations for the iterative orthomax rotation. The default ## is 250. ## ## @item @qcode{'Coeff'} ## The orthomax coefficient used when @qcode{'Method'} is @qcode{'orthomax'}. ## The default is 1 (equivalent to @qcode{'varimax'}). ## ## @item @qcode{'Power'} ## The power used to build the @qcode{'promax'} target, a scalar greater than or ## equal to 1. The default is 4. ## ## @item @qcode{'Target'} ## The target loadings matrix for @qcode{'procrustes'}, the same size as ## @var{A}. Required for that method. ## ## @item @qcode{'Type'} ## @qcode{'oblique'} (default) or @qcode{'orthogonal'}, selecting the kind of ## @qcode{'procrustes'} rotation. The default follows MATLAB, whose ## @qcode{'procrustes'} rotation is oblique unless told otherwise. ## @end table ## ## @strong{Note on the orthomax family:} for coefficients up to 1 ## (@qcode{'varimax'}, @qcode{'quartimax'}, and small @qcode{'orthomax'}) the ## rotation follows the same successive-SVD iteration as MATLAB and stops at the ## same relative tolerance. For larger coefficients (@qcode{'equamax'}, ## @qcode{'parsimax'}) that iteration does not converge, so a monotonically ## convergent pairwise algorithm is used instead; it reaches the same optimum as ## MATLAB to that solution's own convergence precision. ## ## @seealso{factoran, pca, pcacov, procrustes} ## @end deftypefn function [B, T] = rotatefactors (A, varargin) ## Input validation if (nargin < 1) print_usage (); endif if (! (isnumeric (A) && ismatrix (A) && ndims (A) == 2)) error ("rotatefactors: A must be a numeric matrix."); endif if (! isreal (A)) error ("rotatefactors: A must be real."); endif [d, m] = size (A); ## Parse Name/Value options optNames = {'Method', 'Normalize', 'Reltol', 'Maxit', 'Coeff', ... 'Power', 'Target', 'Type'}; dfValues = {'varimax', 'on', sqrt(eps), 250, 1, 4, [], 'oblique'}; [Method, Normalize, Reltol, Maxit, Coeff, Power, Target, Type, rem] = ... parsePairedArguments (optNames, dfValues, varargin(:)); if (! isempty (rem)) error ("rotatefactors: unknown or unpaired optional argument."); endif if (! (ischar (Method) && isrow (Method))) error ("rotatefactors: 'Method' must be a character vector."); endif Method = lower (Method); if (! any (strcmp (Method, {'varimax', 'quartimax', 'equamax', ... 'parsimax', 'orthomax', 'promax', 'procrustes'}))) error ("rotatefactors: unknown 'Method' '%s'.", Method); endif if (! (ischar (Normalize) && isrow (Normalize) ... && any (strcmpi (Normalize, {'on', 'off'})))) error ("rotatefactors: 'Normalize' must be 'on' or 'off'."); endif normalize = strcmpi (Normalize, 'on'); if (! (isnumeric (Reltol) && isscalar (Reltol) && isreal (Reltol) ... && Reltol > 0)) error ("rotatefactors: 'Reltol' must be a positive scalar."); endif if (! (isnumeric (Maxit) && isscalar (Maxit) && isreal (Maxit) ... && Maxit >= 1 && Maxit == fix (Maxit))) error ("rotatefactors: 'Maxit' must be a positive integer."); endif ## Dispatch on the requested method switch (Method) case 'varimax' [B, T] = orthomaxrotate (A, 1, normalize, Reltol, Maxit); case 'quartimax' [B, T] = orthomaxrotate (A, 0, normalize, Reltol, Maxit); case 'equamax' [B, T] = orthomaxrotate (A, m / 2, normalize, Reltol, Maxit); case 'parsimax' [B, T] = orthomaxrotate (A, d * (m - 1) / (d + m - 2), ... normalize, Reltol, Maxit); case 'orthomax' if (! (isnumeric (Coeff) && isscalar (Coeff) && isreal (Coeff))) error ("rotatefactors: 'Coeff' must be a real scalar."); endif [B, T] = orthomaxrotate (A, Coeff, normalize, Reltol, Maxit); case 'promax' if (! (isnumeric (Power) && isscalar (Power) && isreal (Power) ... && Power >= 1)) error ("rotatefactors: 'Power' must be a scalar >= 1."); endif [B, T] = promaxrotate (A, Power, normalize, Reltol, Maxit); case 'procrustes' if (isempty (Target)) error ("rotatefactors: 'Target' is required for 'procrustes'."); endif if (! (isnumeric (Target) && isreal (Target) ... && isequal (size (Target), [d, m]))) error (strcat ("rotatefactors: 'Target' must be a real matrix", ... " the same size as A.")); endif if (! (ischar (Type) && isrow (Type) ... && any (strcmpi (Type, {'orthogonal', 'oblique'})))) error ("rotatefactors: 'Type' must be 'orthogonal' or 'oblique'."); endif [B, T] = procrustesrotate (A, Target, lower (Type)); endswitch endfunction ## Orthomax criterion (to be maximized) of a loadings matrix L function v = orthomaxcrit (L, gamma, d) cs = sum (L .^ 2, 1); v = sum (sum (L .^ 4)) - (gamma / d) * sum (cs .^ 2); endfunction ## Orthomax rotation: successive-SVD iteration with a pairwise fallback for the ## coefficients where the SVD iteration fails to ascend the criterion. function [B, T] = orthomaxrotate (A, gamma, normalize, reltol, maxit) [d, m] = size (A); if (m < 2) B = A; T = eye (m); return; endif if (normalize) h = sqrt (sum (A .^ 2, 2)); An = A ./ h; else An = A; endif ## Successive-SVD (gradient-projection) iteration. Each step replaces T by ## the orthonormal polar factor of the criterion gradient. Whenever a step ## would fail to increase the criterion, the iteration is abandoned in favour ## of the always-monotone pairwise algorithm. T = eye (m); B = An; sPrev = 0; oscillated = false; for iter = 1:maxit D = diag (sum (B .^ 2, 1)); G = An' * (B .^ 3 - (gamma / d) * B * D); fCur = orthomaxcrit (B, gamma, d); [U, S, V] = svd (G); Tnew = U * V'; if (orthomaxcrit (An * Tnew, gamma, d) < fCur - 1e-12 * max (1, abs (fCur))) oscillated = true; break; endif T = Tnew; B = An * T; sCur = sum (diag (S)); if (iter > 1 && abs (sCur - sPrev) < reltol * sCur) break; endif sPrev = sCur; endfor if (oscillated) [B, T] = pairwiserotate (An, gamma, reltol, maxit); endif B = A * T; endfunction ## Pairwise (Jacobi) orthomax rotation: sweep over factor pairs, applying the ## planar rotation that maximizes the criterion, until every angle is negligible. function [B, T] = pairwiserotate (An, gamma, reltol, maxit) [n, m] = size (An); T = eye (m); B = An; for iter = 1:maxit converged = true; for p = 1:m-1 for q = p+1:m x = B(:,p); y = B(:,q); u = x .^ 2 - y .^ 2; v = 2 * x .* y; As = sum (u); Bs = sum (v); Cs = sum (u .^ 2 - v .^ 2); Ds = sum (2 * u .* v); num = Ds - (2 * gamma / n) * As * Bs; den = Cs - (gamma / n) * (As ^ 2 - Bs ^ 2); theta = atan2 (num, den) / 4; if (abs (theta) > reltol) converged = false; endif c = cos (theta); s = sin (theta); B(:,p) = c * x + s * y; B(:,q) = -s * x + c * y; R = eye (m); R(p,p) = c; R(q,q) = c; R(p,q) = -s; R(q,p) = s; T = T * R; endfor endfor if (converged) break; endif endfor endfunction ## Promax rotation: oblique fit to a power target built from a varimax solution. function [B, T] = promaxrotate (A, power, normalize, reltol, maxit) [d, m] = size (A); if (m < 2) B = A; T = eye (m); return; endif ## Start from a varimax (orthomax coefficient 1) rotation. [Bv, Tv] = orthomaxrotate (A, 1, normalize, reltol, maxit); ## Build the power target and fit an oblique transformation to it, normalized ## so the implied factor correlation matrix has a unit diagonal. BStar = sign (Bv) .* abs (Bv) .^ power; Q = Bv \ BStar; Q = Q .* sqrt (diag (inv (Q' * Q)))'; T = Tv * Q; B = A * T; endfunction ## Procrustes rotation towards a target, orthogonal or oblique. function [B, T] = procrustesrotate (A, Target, type) if (strcmp (type, 'orthogonal')) [U, S, V] = svd (A' * Target); T = U * V'; else ## Oblique: least-squares fit, columns normalized so the implied factor ## correlation matrix has a unit diagonal. T = A \ Target; T = T .* sqrt (diag (inv (T' * T)))'; endif B = A * T; endfunction %!demo %! ## Rotate a three-factor loading matrix to the varimax criterion and %! ## recover the rotation matrix. %! A = [ 0.8, 0.2, 0.1; 0.7, 0.3, 0.0; ... %! 0.1, 0.9, 0.2; 0.2, 0.8, 0.1; ... %! 0.1, 0.2, 0.9; 0.0, 0.1, 0.8]; %! [B, T] = rotatefactors (A, 'Method', 'varimax'); %! B %! ## T is orthonormal and reconstructs B from A. %! max (abs (vec (A * T - B))) ## Reference values below are from MATLAB R2023b for ## A = reshape (mod ((1:18)*5, 11), 6, 3) - 5. %!test %! A = reshape (mod ((1:18)*5, 11), 6, 3) - 5; %! B = rotatefactors (A, 'Method', 'varimax'); %! Bref = [ -0.590343326670958, -5.399542278328798, 2.120480592034490; ... %! 5.301191898681203, 1.349821193720584, -0.274494441363606; ... %! -1.789611374449118, -5.388780549360982, 0.870824505437868; ... %! 4.101923850903043, 1.360582922688400, -1.524150527960228; ... %! -2.988879422227277, -5.378018820393167, -0.378831581158754; ... %! 2.902655803124883, 1.371344651656216, -2.773806614556851]; %! assert_equal (B, Bref, 1e-10); %!test %! A = reshape (mod ((1:18)*5, 11), 6, 3) - 5; %! [B, T] = rotatefactors (A, 'Method', 'varimax'); %! assert_equal (A * T, B, 1e-12); %! assert_equal (T' * T, eye (3), 1e-12); %!test %! A = reshape (mod ((1:18)*5, 11), 6, 3) - 5; %! B = rotatefactors (A, 'Method', 'quartimax'); %! Bref = [ -1.079871632250411, 1.331921409298459, 5.573137591816050; ... %! 5.135643864717125, 1.382567861335108, -1.309071504386449; ... %! -1.872731015323443, -0.201056474219368, 5.427011593724494; ... %! 4.342784481644092, -0.150410022182719, -1.455197502478006; ... %! -2.665590398396474, -1.734034357737195, 5.280885595632937; ... %! 3.549925098571060, -1.683387905700547, -1.601323500569563]; %! assert_equal (B, Bref, 1e-10); %!test %! A = reshape (mod ((1:18)*5, 11), 6, 3) - 5; %! B = rotatefactors (A, 'Method', 'equamax'); %! Bref = [ -0.163528146620418, -5.392883888956456, 2.211348436021980; ... %! 4.856598298899028, 0.925912265845965, -2.357146461102300; ... %! -1.752970474391914, -5.249881056555078, 1.538129841051886; ... %! 3.267155971127525, 1.068915098247334, -3.030365056072396; ... %! -3.342412802163413, -5.106878224153711, 0.864911246081789; ... %! 1.677713643356028, 1.211917930648702, -3.703583651042488]; %! assert_equal (B, Bref, 1e-6); %!test %! A = reshape (mod ((1:18)*5, 11), 6, 3) - 5; %! B = rotatefactors (A, 'Method', 'parsimax'); %! Bref = [ -0.209779706669808, -5.381270194314001, 2.235603625524279; ... %! 4.824100779801576, 0.835974617312815, -2.455442547795920; ... %! -1.809538067356745, -5.214183987228857, 1.593065387896242; ... %! 3.224342419114638, 1.003060824397959, -3.097980785423958; ... %! -3.409296428043682, -5.047097780143712, 0.950527150268203; ... %! 1.624584058427703, 1.170147031483100, -3.740519023051994]; %! assert_equal (B, Bref, 1e-6); %!test %! ## orthomax with Coeff 1 equals varimax. %! A = reshape (mod ((1:18)*5, 11), 6, 3) - 5; %! B1 = rotatefactors (A, 'Method', 'orthomax', 'Coeff', 1); %! B2 = rotatefactors (A, 'Method', 'varimax'); %! assert_equal (B1, B2, 1e-12); %!test %! ## orthomax with Coeff 0 equals quartimax. %! A = reshape (mod ((1:18)*5, 11), 6, 3) - 5; %! B1 = rotatefactors (A, 'Method', 'orthomax', 'Coeff', 0); %! B2 = rotatefactors (A, 'Method', 'quartimax'); %! assert_equal (B1, B2, 1e-12); %!test %! ## Kaiser normalization 'off'. %! A = reshape (mod ((1:18)*5, 11), 6, 3) - 5; %! B = rotatefactors (A, 'Method', 'orthomax', 'Coeff', 0.5, 'Normalize', 'off'); %! Bref = [ -0.018655295494772, -5.634517554136197, 1.500621175407379; ... %! 5.076579678301866, 2.027215443018021, 0.344581365488290; ... %! -1.109307774177549, -5.634304393877173, 0.155081460160809; ... %! 3.985927199619089, 2.027428603277046, -1.000958349758279; ... %! -2.199960252860326, -5.634091233618149, -1.190458255085760; ... %! 2.895274720936312, 2.027641763536070, -2.346498065004848]; %! assert_equal (B, Bref, 1e-10); %!test %! A = reshape (mod ((1:18)*5, 11), 6, 3) - 5; %! [B, T] = rotatefactors (A, 'Method', 'promax', 'Power', 3); %! Bref = [ 1.224548928356429, -5.243438188632538, 2.071368824506429; ... %! 5.402710851790721, 0.067482009256478, -0.106948752887073; ... %! -0.495720593244403, -5.226782837470801, 0.630342425004671; ... %! 3.682441330189890, 0.084137360418215, -1.547975152388831; ... %! -2.215990114845235, -5.210127486309064, -0.810683974497087; ... %! 1.962171808589059, 0.100792711579952, -2.989001551890589]; %! assert_equal (B, Bref, 1e-10); %! assert_equal (A * T, B, 1e-10); %!test %! A = reshape (mod ((1:18)*5, 11), 6, 3) - 5; %! Target = reshape (mod ((1:18)*3, 7), 6, 3) - 3; %! [B, T] = rotatefactors (A, 'Method', 'procrustes', 'Target', Target, ... %! 'Type', 'orthogonal'); %! Bref = [ -1.222902114166934, 5.164166701208014, -2.415759239100699; ... %! 5.403127504320427, -0.893247624515622, -0.091224192807273; ... %! -2.313297220899559, 5.148157805250635, -1.070106153619982; ... %! 4.312732397587801, -0.909256520473001, 1.254428892673443; ... %! -3.403692327632185, 5.132148909293257, 0.275546931860735; ... %! 3.222337290855177, -0.925265416430380, 2.600081978154161]; %! assert_equal (B, Bref, 1e-10); %! assert_equal (T' * T, eye (3), 1e-12); %!test %! A = reshape (mod ((1:18)*5, 11), 6, 3) - 5; %! Target = reshape (mod ((1:18)*3, 7), 6, 3) - 3; %! ## 'oblique' is the default, so naming it must change nothing %! B = rotatefactors (A, 'Method', 'procrustes', 'Target', Target, ... %! 'Type', 'oblique'); %! assert_equal (B, rotatefactors (A, 'Method', 'procrustes', ... %! 'Target', Target), 1e-10); %! Bref = [ 0.000000000000002, -4.619308411881804, -10.370742303151150; ... %! 94.640566010004221, -97.005476649517561, -1.152304700350133; ... %! -31.546855336668067, 36.954467295054300, 0.000000000000000; ... %! 63.093710673336162, -55.431700942581458, 9.218437602801021; ... %! -63.093710673336133, 78.528243001990404, 10.370742303151154; ... %! 31.546855336668081, -13.857925235645340, 19.589179905952172]; %! assert_equal (B, Bref, 1e-8); %!test %! ## Single-factor input is returned unchanged. %! A = [1; 2; 3; 4]; %! [B, T] = rotatefactors (A); %! assert_equal (B, A); %! assert_equal (T, 1); %!test %! ## Default method is varimax. %! A = reshape (mod ((1:18)*5, 11), 6, 3) - 5; %! assert_equal (rotatefactors (A), rotatefactors (A, 'Method', 'varimax')); ## Test input validation %!error rotatefactors () %!error rotatefactors ({1, 2}) %!error rotatefactors (ones (2, 2, 2)) %!error rotatefactors ([1+2i; 3]) %!error rotatefactors (ones (3), 'Method', 'foo') %!error ... %! rotatefactors (ones (3), 'Method', 5) %!error ... %! rotatefactors (ones (3), 'Normalize', 'yes') %!error ... %! rotatefactors (ones (3), 'Reltol', -1) %!error ... %! rotatefactors (ones (3), 'Maxit', 2.5) %!error ... %! rotatefactors (ones (3), 'Method', 'orthomax', 'Coeff', [1, 2]) %!error ... %! rotatefactors (ones (3), 'Method', 'promax', 'Power', 0.5) %!error ... %! rotatefactors (ones (3), 'Method', 'procrustes') %!error ... %! rotatefactors (ones (6, 3), 'Method', 'procrustes', 'Target', ones (4, 2)) %!error ... %! rotatefactors (ones (6, 3), 'Method', 'procrustes', 'Target', ones (6, 3), 'Type', 'x') %!error ... %! rotatefactors (ones (3), 'Bogus', 1) statistics-release-1.9.2/inst/Dimensionality_Reduction/sparsefilt.m000066400000000000000000000225301524624707500256640ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} sparsefilt (@var{X}, @var{Q}) ## @deftypefnx {statistics} {@var{Mdl} =} sparsefilt (@var{X}, @var{Q}, @var{Name}, @var{Value}) ## ## Sparse filtering for feature extraction. ## ## @code{@var{Mdl} = sparsefilt (@var{X}, @var{Q})} learns @var{Q} features from ## the @math{N * P} data matrix @var{X} (rows are observations, columns are ## predictors) and returns a @qcode{SparseFiltering} object @var{Mdl}. Apply the ## learned transformation to data with @code{transform (@var{Mdl}, @var{X})}. ## ## The @math{N * @var{Q}} features returned by @code{transform} are the ## soft-absolute activations @code{sqrt ((@var{X} * @var{W}) .^ 2 + 1e-8)}, ## normalized first across observations (each feature) and then across features ## (each observation). The @math{P * @var{Q}} weight matrix @var{W} minimizes ## the sum of those features plus an L2 penalty @code{@var{Lambda} * ||@var{W} ## ||_F^2}, driving the features to be sparse. ## ## Name/Value pairs: ## ## @table @asis ## @item @qcode{'IterationLimit'} ## Maximum number of iterations (default 1000). ## ## @item @qcode{'Lambda'} ## Weight of the L2 penalty on the transform weights (default 1). ## ## @item @qcode{'Standardize'} ## Logical; center and scale each predictor before fitting (default ## @qcode{false}). ## ## @item @qcode{'InitialTransformWeights'} ## A @math{P * @var{Q}} initial value for the weights. The default is random. ## ## @item @qcode{'GradientTolerance'}, @qcode{'StepTolerance'} ## Stop once the gradient's or the step's infinity norm falls to or below the ## given value (default @qcode{1e-6} each). They govern the fit only under ## @qcode{'Solver', 'lbfgs'}; the @qcode{'quasinewton'} solver runs to its own ## tighter internal tolerances and records these without acting on them. ## ## @item @qcode{'Solver'} ## @qcode{'quasinewton'} (default) minimizes through Octave's @code{fminunc}, ## which carries a full inverse Hessian. @qcode{'lbfgs'} selects the ## limited-memory BFGS solver MATLAB uses, holding as many curvature pairs as ## the transform has parameters, and is several times faster here. It stops ## where @qcode{'GradientTolerance'} and @qcode{'StepTolerance'} say to, so a ## value tighter than the default carries it further. ## @end table ## ## @subheading Note on reproducibility ## ## The sparse filtering objective is not convex and is minimized by a ## quasi-Newton solver, so the learned weights depend on the starting point and ## the solver. Different runs (or different software, including MATLAB) may ## return different weights that nonetheless describe an equally valid feature ## transformation. Fix @qcode{'InitialTransformWeights'} for a reproducible ## result. ## ## @seealso{SparseFiltering, rica, pca} ## @end deftypefn function Mdl = sparsefilt (X, Q, varargin) ## Input validation if (nargin < 2) print_usage (); endif if (! (isnumeric (X) && ismatrix (X) && ndims (X) == 2)) error ("sparsefilt: X must be a numeric matrix."); endif if (! (isreal (X) && all (isfinite (X(:))))) error ("sparsefilt: X must be real and finite."); endif [n, p] = size (X); if (! (isscalar (Q) && isnumeric (Q) && Q >= 1 && Q == fix (Q))) error ("sparsefilt: Q must be a positive integer."); endif if (mod (numel (varargin), 2) != 0) error ("sparsefilt: Name/Value arguments must come in pairs."); endif for k = 1:2:numel (varargin) if (ischar (varargin{k}) && strcmpi (varargin{k}, "InitialTransformWeights")) W0 = varargin{k+1}; if (! (isnumeric (W0) && isequal (size (W0), [p, Q]))) error ("sparsefilt: 'InitialTransformWeights' must be a %d-by-%d matrix.", ... p, Q); endif endif endfor Mdl = SparseFiltering (X, Q, varargin{:}); endfunction %!demo %! ## Learn two sparse features from data with the default (random) start. %! X = [1, 2, 3, 4; 2, 3, 4, 5; -1, 0, 1, 2; 3, 1, 4, 1; 0, 2, 1, 3]; %! Mdl = sparsefilt (X, 2, "IterationLimit", 200); %! Z = transform (Mdl, X) ## The sparse filtering objective is verified against MATLAB R2023b for ## X = reshape (mod ((1:60)*7, 13), 12, 5) - 6 with fixed initial weights. %!shared X, W0 %! X = reshape (mod ((1:60)*7, 13), 12, 5) - 6; %! W0 = reshape (mod ((1:15)*3, 7), 5, 3) - 3; %!test %! Mdl = sparsefilt (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 1000); %! Z = transform (Mdl, X); %! ## features are in [0, 1] and each observation (row) is essentially unit length %! assert_equal (size (Z), [12, 3]); %! assert_equal (all (Z(:) >= 0) && all (Z(:) <= 1 + 1e-12), true); %! ## rows are unit length up to the 1e-8 normalization regularizer %! assert_equal (sqrt (sum (Z .^ 2, 2)), ones (12, 1), 1e-3); %!test %! ## The objective at the initial weights matches MATLAB (73.171833). %! A = X * W0; %! F = sqrt (A .^ 2 + 1e-8); %! F = F ./ sqrt (sum (F .^ 2, 1) + 1e-8); %! F = F ./ sqrt (sum (F .^ 2, 2) + 1e-8); %! f0 = sum (F(:)) + sum (W0(:) .^ 2); %! assert_equal (f0, 73.171833042485730, 1e-9); %!test %! ## The solver drives the objective well below its value at the start %! ## (73.17) -- reaching a good local minimum of the sparse filtering cost. %! Mdl = sparsefilt (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 1000); %! assert_equal (Mdl.FitInfo.Objective(end) < 20, true); %!test %! ## Lambda is an L2 penalty on the weights: FitInfo.Objective is %! ## sum(features) + Lambda * ||W||^2 at the solution. %! Mdl = sparsefilt (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 1000); %! W = Mdl.TransformWeights; %! Z = transform (Mdl, X); %! assert_equal (Mdl.FitInfo.Objective(end), sum (Z(:)) + sum (W(:) .^ 2), 1e-6); %!test %! ## FitInfo carries the whole trajectory, not just its final value %! Mdl = sparsefilt (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 1000); %! it = Mdl.FitInfo.Iteration; %! ob = Mdl.FitInfo.Objective; %! assert_equal (columns (it), 1); %! assert_equal (size (ob), size (it)); %! assert_equal (it, (0:numel (ob) - 1)'); %! assert_equal (all (diff (ob) <= 1e-10), true); %!test %! ## the comparison broadcasts over the history, as MATLAB's does %! Mdl = sparsefilt (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 1000); %! assert_equal (size (Mdl.FitInfo.Objective < 20), size (Mdl.FitInfo.Objective)); ## Test input validation %!test %! ## The lbfgs solver reaches a low objective value (MATLAB's is 12.2532). %! ## It does not land where the default solver lands: this objective is far %! ## from convex and the two take different steps from the same weights. %! ## Both tolerances are tightened because the step reaches 1e-6 here while %! ## the gradient is still well above it, so the default stops the fit %! ## early; that is StepTolerance doing exactly what it says. %! Mdl = sparsefilt (X, 3, "InitialTransformWeights", W0, "Lambda", 1, ... %! "Standardize", false, "IterationLimit", 1000, ... %! "Solver", "lbfgs", "GradientTolerance", 1e-8, ... %! "StepTolerance", 1e-8); %! assert_equal (Mdl.FitInfo.Objective(end) < 12.5, true); %!test %! ## The trajectory starts at the initial weights whichever solver ran %! args = {"InitialTransformWeights", W0, "IterationLimit", 20}; %! Mq = sparsefilt (X, 3, args{:}); %! Ml = sparsefilt (X, 3, args{:}, "Solver", "lbfgs"); %! assert_equal (Ml.FitInfo.Iteration(1), 0); %! assert_equal (Ml.FitInfo.Objective(1), Mq.FitInfo.Objective(1), 1e-10); %!test %! ## The solver that ran is recorded %! Mdl = sparsefilt (X, 3, "InitialTransformWeights", W0, "Solver", "lbfgs"); %! assert_equal (Mdl.ModelParameters.Solver, "lbfgs"); %!error ... %! sparsefilt (X, 3, "Solver", "bogus") %!error ... %! sparsefilt (X, 3, "Solver", 5) %!error sparsefilt (ones (5, 3)) %!error sparsefilt ({1, 2}, 1) %!error sparsefilt ([1, Inf; 2, 3], 1) %!error sparsefilt (ones (5, 3), 0) %!error sparsefilt (ones (5, 3), 1.5) %!error ... %! sparsefilt (ones (4, 5), 2, "InitialTransformWeights", ones (3, 3)) %!error ... %! sparsefilt (ones (4, 5), 2, "bogus", 1) statistics-release-1.9.2/inst/Dimensionality_Reduction/tsne.m000066400000000000000000000552661524624707500244750ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Y} =} tsne (@var{X}) ## @deftypefnx {statistics} {[@var{Y}, @var{loss}] =} tsne (@var{X}) ## @deftypefnx {statistics} {[@dots{}] =} tsne (@dots{}, @var{Name}, @var{Value}) ## ## t-distributed stochastic neighbor embedding (t-SNE). ## ## @code{@var{Y} = tsne (@var{X})} embeds the @math{N * P} data matrix @var{X} ## (rows are observations) into a low-dimensional space and returns the ## @math{N * @var{NumDimensions}} matrix @var{Y} of embedded points, whose ## pairwise (Student-t) affinities approximate the Gaussian affinities of the ## rows of @var{X}. ## ## @code{[@var{Y}, @var{loss}] = tsne (@dots{})} also returns the ## Kullback-Leibler divergence @var{loss} between the two affinity ## distributions at the returned embedding. ## ## Name/Value pairs: ## ## @table @asis ## @item @qcode{'Algorithm'} ## @qcode{'exact'} (default) forms the affinities and the gradient over every ## pair of points, which costs @math{O(N^2)} in time and memory at each ## iteration. @qcode{'barneshut'} approximates both: the high-dimensional ## affinities are kept only over each point's @math{3 * Perplexity} nearest ## neighbours, and the repulsive part of the gradient is summed over a ## space-partitioning tree of the embedding, giving @math{O(N log N)}. Use it ## when @math{N} is large enough that the exact algorithm is slow or cannot ## allocate; on this machine the two cost the same at about @math{N = 500} and ## @qcode{'barneshut'} is eight times faster at @math{N = 2000}. ## ## The two do not return the same embedding, and their @var{loss} values are ## not comparable either: the divergence is summed over the pairs that carry an ## affinity, and @qcode{'barneshut'} keeps far fewer of them. ## ## @item @qcode{'Theta'} ## The tree opening criterion for @qcode{'barneshut'}, a non-negative scalar ## (default 0.5). A cell of the tree is collapsed to its centre of mass when ## its width is smaller than @var{Theta} times its distance from the point ## being pushed, so a larger value is faster and coarser. @qcode{0} collapses ## nothing and makes the repulsion exact, at @math{O(N^2)}; note that this ## still leaves the affinities sparse, so it does not reproduce ## @qcode{'exact'}. Ignored by @qcode{'exact'}. ## ## @item @qcode{'Distance'} ## The distance metric used for the high-dimensional affinities, as accepted by ## @code{pdist} (default @qcode{'euclidean'}). ## ## @item @qcode{'NumDimensions'} ## The dimension of the embedding @var{Y} (default @code{min (P, 2)}). ## ## @item @qcode{'NumPCAComponents'} ## If positive, reduce @var{X} to this many principal components before embedding ## (default 0, no reduction). ## ## @item @qcode{'Standardize'} ## Logical; center and scale each column of @var{X} before embedding (default ## @qcode{false}). ## ## @item @qcode{'Perplexity'} ## The effective number of local neighbors (default 30). It must be smaller ## than @math{N}. ## ## @item @qcode{'Exaggeration'} ## Tightness factor applied to the high-dimensional affinities for the first 100 ## iterations (default 4, no less than 1). ## ## @item @qcode{'LearnRate'} ## The learning rate of the optimization (default 500). ## ## @item @qcode{'InitialY'} ## An @math{N * @var{NumDimensions}} initial embedding (default ## @code{1e-4 * randn}). ## ## @item @qcode{'Options'} ## A structure (as returned by @code{statset}) whose @qcode{MaxIter} (default ## 1000) and @qcode{TolFun} (default @code{1e-10}) fields control the ## optimization. ## @end table ## ## The embedding is not unique: it depends on the initial configuration and the ## random state. Set @qcode{'InitialY'} (or the random seed) for a reproducible ## result. ## ## @seealso{pca, pdist, statset} ## @end deftypefn function [Y, loss] = tsne (X, varargin) if (nargin < 1) print_usage (); endif if (! (isnumeric (X) && ismatrix (X) && ndims (X) == 2)) error ("tsne: X must be a numeric matrix."); endif if (! (isreal (X) && all (! isinf (X(:))))) error ("tsne: X must be real and finite."); endif [N, p] = size (X); ## Defaults and Name/Value parsing Algorithm = "exact"; Distance = "euclidean"; ydims = []; numPCA = 0; Standardize = false; Perplexity = 30; Exaggeration = 4; LearnRate = 500; InitialY = []; Theta = 0.5; opts = statset ("tsne"); if (mod (numel (varargin), 2) != 0) error ("tsne: Name/Value arguments must come in pairs."); endif for k = 1:2:numel (varargin) name = varargin{k}; val = varargin{k+1}; switch (lower (name)) case 'algorithm' Algorithm = lower (val); case 'distance' Distance = val; case 'numdimensions' ydims = val; case 'numpcacomponents' numPCA = val; case 'standardize' Standardize = val; case 'perplexity' Perplexity = val; case 'exaggeration' Exaggeration = val; case 'learnrate' LearnRate = val; case 'initialy' InitialY = val; case 'theta' Theta = val; case 'options' if (! isstruct (val)) error ("tsne: 'Options' must be a structure."); endif opts = statset (opts, val); otherwise error ("tsne: unknown parameter name '%s'.", name); endswitch endfor if (! any (strcmp (Algorithm, {'exact', 'barneshut'}))) error ("tsne: 'Algorithm' must be 'exact' or 'barneshut'."); endif if (! (isscalar (Theta) && isnumeric (Theta) && isreal (Theta) && isfinite (Theta) && Theta >= 0)) error ("tsne: 'Theta' must be a non-negative scalar."); endif if (! (isscalar (Perplexity) && Perplexity > 0 && Perplexity < N)) error ("tsne: 'Perplexity' must be a positive scalar smaller than N."); endif if (! (isscalar (Exaggeration) && Exaggeration >= 1)) error ("tsne: 'Exaggeration' must be a scalar not less than 1."); endif if (! (isscalar (LearnRate) && LearnRate > 0)) error ("tsne: 'LearnRate' must be a positive scalar."); endif ## An empty value falls back to the default below. Anything else has to be a ## dimension the embedding can actually have: a non-positive one used to ## return an N-by-0 embedding rather than complain, and a fractional or ## non-numeric one leaked an internal conversion error. if (! isempty (ydims)) if (! (isnumeric (ydims) && isscalar (ydims) && isreal (ydims) && isfinite (ydims) && ydims > 0 && fix (ydims) == ydims)) error ("tsne: 'NumDimensions' must be a positive integer."); elseif (ydims > p) error (strcat ("tsne: 'NumDimensions' must not be greater than the", ... " number of columns of X.")); endif endif ## Initial embedding if (isempty (InitialY)) if (isempty (ydims)) ydims = min (p, 2); endif Y = 1e-4 * randn (N, ydims); else if (! isequal (rows (InitialY), N)) error ("tsne: 'InitialY' must have N rows."); endif ydims = columns (InitialY); Y = InitialY; endif ## The Barnes-Hut tree is a 2^D-ary subdivision of the embedding, which is ## only built for the dimensions t-SNE is used in. if (strcmp (Algorithm, "barneshut") && ydims > 3) error (strcat ("tsne: 'NumDimensions' must not be greater than 3 for", ... " the 'barneshut' Algorithm.")); endif ## Standardize and optionally reduce with PCA if (Standardize) sig = std (X, 0, 1); sig(range (X, 1) == 0) = 1; X = (X - mean (X, 1)) ./ sig; endif if (numPCA > 0) [~, X] = pca (X, "Centered", false, "NumComponents", numPCA); endif ## High-dimensional affinities P. The exact algorithm forms them for every ## pair; Barnes-Hut keeps only each point's nearest neighbours, which is the ## half of the approximation that has nothing to do with the tree. if (strcmp (Algorithm, "exact")) D = squareform (pdist (X, Distance)) .^ 2; P = binary_search_variance (D, Perplexity, N); P(1:N+1:end) = 0; P = (P + P') / (2 * N); P = max (P, realmin); else P = neighbour_affinities (X, Distance, Perplexity, N); endif ## Optimize the embedding [Y, loss] = tsne_embedding (Y, P, Exaggeration, LearnRate, opts, N, ydims, ... Algorithm, Theta); endfunction ## --------------------------------------------------------------------------- ## Binary search for the per-point variance giving the target perplexity. function condP = binary_search_variance (D, perplexity, N) condP = zeros (N); beta = ones (N, 1); H = log (perplexity); for i = 1:N a = -Inf; c = Inf; for iter = 1:100 Pi = exp (-D(i,:) * beta(i)); Pi(i) = 0; si = max (sum (Pi), realmin); Pi = Pi / si; Hi = log (si) + beta(i) * sum (D(i,:) .* Pi); fval = Hi - H; if (abs (fval) < 1e-5) break; endif if (fval > 0) a = beta(i); if (isinf (c)) beta(i) = 2 * beta(i); else beta(i) = 0.5 * (beta(i) + c); endif else c = beta(i); if (isinf (a)) beta(i) = 0.5 * beta(i); else beta(i) = 0.5 * (a + beta(i)); endif endif endfor condP(i,:) = Pi; endfor endfunction ## --------------------------------------------------------------------------- ## Sparse high-dimensional affinities over each point's nearest neighbours. ## Barnes-Hut t-SNE keeps 3 * Perplexity of them, the count at which the ## conditional distribution has essentially no mass left, and runs the same ## per-point variance search over that row instead of over all N. function P = neighbour_affinities (X, Distance, perplexity, N) K = min (N - 1, max (1, round (3 * perplexity))); ## Neighbours a block of rows at a time. The exact algorithm forms the whole ## N-by-N distance matrix, which is the memory wall this path exists to get ## around, so the block is sized to a fixed budget rather than to N. A block ## against all of X is what pdist2 is for; knnsearch would serve too and is ## two orders of magnitude slower at this K. blk = max (1, min (N, floor (1e7 / N))); idx = zeros (N, K); D2 = zeros (N, K); for lo = 1:blk:N hi = min (lo + blk - 1, N); Db = pdist2 (X(lo:hi,:), X, Distance) .^ 2; ## A point is its own nearest neighbour. Removing it by index rather than ## by distance keeps a duplicated row, which is a neighbour at distance ## zero and belongs in the list. for r = lo:hi Db(r - lo + 1, r) = Inf; endfor [sv, so] = sort (Db, 2); idx(lo:hi,:) = so(:,1:K); D2(lo:hi,:) = sv(:,1:K); endfor ## The same per-point variance search the exact algorithm runs, over the ## neighbour row instead of over all N. condP = zeros (N, K); H = log (perplexity); for i = 1:N beta = 1; a = -Inf; c = Inf; for iter = 1:100 Pi = exp (-D2(i,:) * beta); si = max (sum (Pi), realmin); Pi = Pi / si; Hi = log (si) + beta * sum (D2(i,:) .* Pi); fval = Hi - H; if (abs (fval) < 1e-5) break; endif if (fval > 0) a = beta; if (isinf (c)) beta = 2 * beta; else beta = 0.5 * (beta + c); endif else c = beta; if (isinf (a)) beta = 0.5 * beta; else beta = 0.5 * (a + beta); endif endif endfor condP(i,:) = Pi; endfor rowi = repmat ((1:N)', 1, K); P = sparse (rowi(:), idx(:), condP(:), N, N); P = (P + P') / (2 * N); endfunction ## --------------------------------------------------------------------------- ## Gradient of the t-SNE cost and the low-dimensional affinities Q. function [grad, Q] = tsne_gradient (P, Y, N) sumY = sum (Y .^ 2, 2); num = 1 ./ (1 + sumY + sumY' - 2 * (Y * Y')); num(1:N+1:end) = 0; Q = max (num ./ sum (num(:)), realmin); L = num .* (P - Q); grad = 4 * (diag (sum (L, 1)) - L) * Y; endfunction ## --------------------------------------------------------------------------- ## Gradient of the t-SNE cost by Barnes-Hut summation. The attraction runs ## over the stored pairs alone and is formed here; the repulsion runs over ## every pair and is summed by the tree in __bhtsne__. The loss comes with it ## because both its terms are already to hand. function [grad, loss] = tsne_gradient_bh (P, Y, N, ydims, theta) [ii, jj, pv] = find (P); dY = Y(ii,:) - Y(jj,:); qn = 1 ./ (1 + sum (dY .^ 2, 2)); Fattr = zeros (N, ydims); w = pv .* qn; for m = 1:ydims Fattr(:,m) = accumarray (ii, w .* dY(:,m), [N, 1]); endfor [Frep, Z] = __bhtsne__ (Y, theta); grad = 4 * (Fattr - Frep / Z); if (nargout > 1) q = max (qn / Z, realmin); loss = sum (pv .* log (max (pv, realmin))) - sum (pv .* log (q)); endif endfunction ## --------------------------------------------------------------------------- ## Gradient descent with adaptive learning rate (Jacobi) and momentum. function [Y, loss] = tsne_embedding (Y, P, exaggeration, learnrate, opts, ... N, ydims, algorithm, theta) adp = ones (N, ydims); minRate = 0.01; momentums = [0.5, 0.8]; momentumChange = 250; exaggerationStop = 100; kk = 0.15; phi = 0.85; exact = strcmp (algorithm, "exact"); P = exaggeration * P; Ychange = zeros (N, ydims); Q = []; lossbh = []; for iter = 1:opts.MaxIter if (iter == exaggerationStop) P = P / exaggeration; exaggeration = 1; endif if (exact) [grad, Q] = tsne_gradient (P, Y, N); else [grad, lossbh] = tsne_gradient_bh (P, Y, N, ydims, theta); endif ops = sign (grad) != sign (Ychange); adp(ops) += kk; adp(! ops) *= phi; adpRate = learnrate * max (minRate, adp); if (iter < momentumChange) Ychange = momentums(1) * Ychange - adpRate .* grad; else Ychange = momentums(2) * Ychange - adpRate .* grad; endif Y = Y + Ychange; if (norm (grad, Inf) < opts.TolFun) break; endif endfor if (nargout > 1) if (exact) loss = P(:)' * log (P(:)) - P(:)' * log (Q(:)); else loss = lossbh; endif endif endfunction %!demo %! ## Embed a small five-dimensional data set into two dimensions. %! rng (42); %! X = [randn(20, 5); randn(20, 5) + 5]; %! Y = tsne (X, "Perplexity", 10); %! plot (Y(1:20,1), Y(1:20,2), "bo", Y(21:end,1), Y(21:end,2), "rx"); %! title ("t-SNE embedding"); ## Verified against MATLAB R2023b (exact algorithm) for ## X = reshape (mod ((1:60)*7, 13), 12, 5) - 6 with a fixed initial embedding. %!test %! X = reshape (mod ((1:60)*7, 13), 12, 5) - 6; %! Y0 = reshape (mod ((1:24)*3, 7), 12, 2) - 3; %! opt = struct ("MaxIter", 20, "TolFun", 0); %! [Y, loss] = tsne (X, "Algorithm", "exact", "Distance", "euclidean", ... %! "NumDimensions", 2, "Perplexity", 3, "Exaggeration", 1, ... %! "LearnRate", 100, "Standardize", false, "InitialY", Y0, ... %! "Options", opt); %! Yref = [-11.087464047262014, 26.776776669616648; ... %! 4.015876079561814, 4.314538936119215; ... %! -10.791976288780955, -28.890539694879799; ... %! -6.197553982245900, 11.136429372335288; ... %! -4.791764519072724, -29.162528702965172; ... %! -0.506807464921708, 13.209994463821561; ... %! -1.097171761767460, -24.062824347518902; ... %! 0.108706666197103, 21.076444386535410; ... %! 1.270590904095503, -17.709227609503525; ... %! 7.840024872376860, 19.937127571156172; ... %! 6.043642805354078, -19.531582453041814; ... %! 13.753383923495731, 22.678754729129128]; %! assert_equal (Y, Yref, 1e-8); %! assert_equal (loss, 0.298069770937047, 1e-10); %!test %! ## Output sizes and reproducibility with a fixed initial embedding. %! X = reshape (mod ((1:60)*7, 13), 12, 5) - 6; %! Y0 = reshape (mod ((1:24)*3, 7), 12, 2) - 3; %! Y1 = tsne (X, "Perplexity", 3, "InitialY", Y0, ... %! "Options", struct ("MaxIter", 50)); %! Y2 = tsne (X, "Perplexity", 3, "InitialY", Y0, ... %! "Options", struct ("MaxIter", 50)); %! assert_equal (size (Y1), [12, 2]); %! assert_equal (Y1, Y2, 1e-12); %!test %! ## NumDimensions controls the embedding dimension. %! X = reshape (mod ((1:60)*7, 13), 12, 5) - 6; %! Y = tsne (X, "NumDimensions", 3, "Perplexity", 3, ... %! "Options", struct ("MaxIter", 20)); %! assert_equal (size (Y), [12, 3]); %!test %! ## The Barnes-Hut tree reproduces the exact pairwise repulsion when nothing %! ## is collapsed, which is what pins the summation without an oracle. %! Y = reshape (mod ((1:60)*7, 13), 30, 2) - 6; %! [F, Z] = __bhtsne__ (Y, 0); %! n = rows (Y); %! Fe = zeros (n, 2); %! Ze = 0; %! for i = 1:n %! d = Y(i,:) - Y; %! q = 1 ./ (1 + sum (d .^ 2, 2)); %! q(i) = 0; %! Ze += sum (q); %! Fe(i,:) = sum ((q .^ 2) .* d, 1); %! endfor %! assert_equal (F, Fe, 1e-12); %! assert_equal (Z, Ze, 1e-10); %!test %! ## A larger Theta collapses cells, so it approximates rather than reproduces. %! Y = reshape (mod ((1:60)*7, 13), 30, 2) - 6; %! [F0, Z0] = __bhtsne__ (Y, 0); %! [F5, Z5] = __bhtsne__ (Y, 0.5); %! assert_equal (norm (F5 - F0, "fro") / norm (F0, "fro") < 0.1, true); %! assert_equal (abs (Z5 - Z0) / Z0 < 0.1, true); %! assert_equal (isequal (F5, F0), false); %!test %! ## The tree handles coincident points, which an embedding starts out full %! ## of and which a subdivision to one point per cell never separates. %! Y = zeros (12, 2); %! [F, Z] = __bhtsne__ (Y, 0.5); %! assert_equal (F, zeros (12, 2), 1e-12); %! assert_equal (Z, 132, 1e-10); %!test %! ## 'barneshut' returns an embedding of the right shape and a finite loss. %! X = [reshape(mod((1:100)*7, 13), 20, 5) - 6; ... %! reshape(mod((1:100)*7, 13), 20, 5) + 30]; %! [Y, loss] = tsne (X, "Algorithm", "barneshut", "Perplexity", 3, ... %! "NumDimensions", 2, "Options", struct ("MaxIter", 200)); %! assert_equal (size (Y), [40, 2]); %! assert_equal (all (isfinite (Y(:))), true); %! assert_equal (isfinite (loss) && loss > 0, true); %!test %! ## It optimizes: the divergence falls as the iterations run. %! X = [reshape(mod((1:100)*7, 13), 20, 5) - 6; ... %! reshape(mod((1:100)*7, 13), 20, 5) + 30]; %! args = {"Algorithm", "barneshut", "Perplexity", 3, ... %! "InitialY", reshape(mod((1:80)*3, 7), 40, 2) - 3}; %! [~, l1] = tsne (X, args{:}, "Options", struct ("MaxIter", 120)); %! [~, l2] = tsne (X, args{:}, "Options", struct ("MaxIter", 400)); %! assert_equal (l2 < l1, true); %!test %! ## It tracks the exact algorithm it approximates, from the same start. %! X = [reshape(mod((1:100)*7, 13), 20, 5) - 6; ... %! reshape(mod((1:100)*7, 13), 20, 5) + 30]; %! o = struct ("MaxIter", 400); %! args = {"Perplexity", 3, "InitialY", reshape(mod((1:80)*3, 7), 40, 2) - 3, ... %! "Options", o}; %! [~, lb] = tsne (X, args{:}, "Algorithm", "barneshut"); %! [~, le] = tsne (X, args{:}, "Algorithm", "exact"); %! assert_equal (abs (lb - le) / le < 0.3, true); %!test %! ## Theta reaches the fit: two values give two embeddings. %! X = reshape (mod ((1:100)*7, 13), 20, 5) - 6; %! o = struct ("MaxIter", 50); %! args = {"Algorithm", "barneshut", "Perplexity", 4, "NumDimensions", 2, ... %! "InitialY", reshape(mod((1:40)*3, 7), 20, 2) - 3, "Options", o}; %! Ya = tsne (X, args{:}, "Theta", 0); %! Yb = tsne (X, args{:}, "Theta", 0.8); %! assert_equal (isequal (Ya, Yb), false); %!test %! ## 'barneshut' takes the same NumDimensions the tree is built for. %! X = reshape (mod ((1:100)*7, 13), 20, 5) - 6; %! for d = 1:3 %! Y = tsne (X, "Algorithm", "barneshut", "NumDimensions", d, ... %! "Perplexity", 4, "Options", struct ("MaxIter", 20)); %! assert_equal (size (Y), [20, d]); %! endfor ## Test input validation %!error tsne () %!error tsne ({1, 2}) %!error tsne ([1, Inf; 2, 3]) %!error ... %! tsne (ones (5, 3), "Algorithm", "bogus") %!error ... %! tsne (ones (5, 3), "Algorithm", "barneshut", "Theta", -1) %!error ... %! tsne (ones (5, 3), "Algorithm", "barneshut", "Theta", [1, 2]) %!error ... %! tsne (ones (8, 5), "Algorithm", "barneshut", "NumDimensions", 4, "Perplexity", 2) %!error ... %! tsne (ones (5, 3), "Perplexity", 5) %!error ... %! tsne (ones (5, 3), "Perplexity", 2, "Exaggeration", 0) %!error ... %! tsne (ones (5, 3), "Perplexity", 2, "LearnRate", -1) %!error tsne (ones (5, 3), "bogus", 1) %!error ... %! tsne (ones (5, 3), "Perplexity", 2, "NumDimensions", 0) %!error ... %! tsne (ones (5, 3), "Perplexity", 2, "NumDimensions", -1) %!error ... %! tsne (ones (5, 3), "Perplexity", 2, "NumDimensions", 2.5) %!error ... %! tsne (ones (5, 3), "Perplexity", 2, "NumDimensions", [2, 3]) %!error ... %! tsne (ones (5, 3), "Perplexity", 2, "NumDimensions", "a") %!error ... %! tsne (ones (5, 3), "Perplexity", 2, "NumDimensions", true) %!error ... %! tsne (ones (5, 3), "Perplexity", 2, "NumDimensions", Inf) %!error ... %! tsne (ones (5, 3), "Perplexity", 2, "NumDimensions", 4) ## An empty NumDimensions falls back to the default, and an integer-typed one ## is accepted, both as in MATLAB. %!test %! X = [1 2 3; 2 4 6; 3 5 9; 4 8 11; 5 9 14; 6 11 17]; %! Y = tsne (X, "Perplexity", 2, "NumDimensions", []); %! assert_equal (columns (Y), 2); %! Y = tsne (X, "Perplexity", 2, "NumDimensions", int32 (2)); %! assert_equal (columns (Y), 2); %! Y = tsne (X, "Perplexity", 2, "NumDimensions", 3); %! assert_equal (columns (Y), 3); statistics-release-1.9.2/inst/Distribution_Classes/000077500000000000000000000000001524624707500224605ustar00rootroot00000000000000statistics-release-1.9.2/inst/Distribution_Classes/+prob/000077500000000000000000000000001524624707500234755ustar00rootroot00000000000000statistics-release-1.9.2/inst/Distribution_Classes/+prob/BetaDistribution.m000066400000000000000000001201551524624707500271320ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef BetaDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.BetaDistribution ## ## Beta probability distribution object. ## ## A @code{prob.BetaDistribution} object consists of parameters, a model ## description, and sample data for a beta probability distribution. ## ## The beta distribution is a family of continuous probability distributions ## defined on the interval @math{[0, 1]} in terms of two positive parameters, ## denoted by alpha @qcode{(@var{a})} and beta @qcode{(@var{b})}, that appear ## as exponents of the variable and its complement to 1, respectively, and ## control the shape of the distribution. ## ## There are several ways to create a @code{prob.BetaDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.BetaDistribution (@var{a}, @var{b})} ## to create a beta distribution with fixed parameter values @var{a} and ## @var{b}. ## @item Use the static method @qcode{prob.BetaDistribution.fit (@var{x}, ## @var{alpha}, @var{freq}, @var{options})} to fit a distribution to the data ## in @var{x} using the same input arguments as the @code{betafit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the beta distribution can be found at ## @url{https://en.wikipedia.org/wiki/Beta_distribution} ## ## @seealso{fitdist, makedist, betacdf, betainv, betapdf, betarnd, betafit, ## betalike, betastat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.BetaDistribution} {property} a ## ## First shape parameter ## ## A positive scalar value characterizing the shape of the beta ## distribution. You can access the @qcode{a} property using dot name ## assignment. ## ## @end deftp a ## -*- texinfo -*- ## @deftp {prob.BetaDistribution} {property} b ## ## Second shape parameter ## ## A positive scalar value characterizing the shape of the beta ## distribution. You can access the @qcode{b} property using dot name ## assignment. ## ## @end deftp b endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.BetaDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Beta'; ## -*- texinfo -*- ## @deftp {prob.BetaDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.BetaDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'a', 'b'}; ## -*- texinfo -*- ## @deftp {prob.BetaDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'First shape parameter', 'Second shape parameter'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'beta'; ParameterRange = [realmin, realmin; Inf, Inf]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.BetaDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{a} and @qcode{b} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.BetaDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.BetaDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.BetaDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only ## meaningful when the distribution was fitted to data. If the distribution ## object was created with fixed parameters, or a parameter of a fitted ## distribution is modified, then all elements of the variance-covariance ## are zero. This property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.BetaDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.BetaDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data} : a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens} : an empty array, since @qcode{prob.BetaDistribution} does ## not allow censoring. ## @item @qcode{frequency} : a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {@var{pd} =} BetaDistribution (@var{a}, @var{b}) ## @deftypefnx {prob.BetaDistribution} {@var{pd} =} BetaDistribution () ## ## Create a @code{prob.BetaDistribution} object. ## ## @var{a} and @var{b} are the distribution parameters, which the class help ## describes. Called with no arguments the parameters take their defaults, ## @var{a} 1 and @var{b} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = BetaDistribution (a, b) if (nargin == 0) a = 1; b = 1; endif checkparams (a, b); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [a, b]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'beta distribution'); endfunction function disp (this) __disp__ (this, 'beta distribution'); endfunction function this = set.a (this, a) checkparams (a, this.b); this.InputData = []; this.ParameterValues(1) = a; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function a = get.a (this) a = this.ParameterValues(1); endfunction function this = set.b (this, b) checkparams (this.a, b); this.InputData = []; this.ParameterValues(2) = b; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function b = get.b (this) b = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.BetaDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = betacdf (x, this.a, this.b); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= betacdf (lx, this.a, this.b); p(! (lb | ub)) /= diff (betacdf ([lx, ux], this.a, this.b)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = betacdf (this.Truncation(1), this.a, this.b); up = betacdf (this.Truncation(2), this.a, this.b); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = betainv (np, this.a, this.b); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = betainv (p, this.a, this.b); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = betastat (this.a, this.b); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = betacdf ([lx, ux], this.a, this.b); m = betainv (sum (Fa_b) / 2, this.a, this.b); else m = betainv (0.5, this.a, this.b); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood ## of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = betalike ([this.a, this.b], this.InputData.data, ... this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.BetaDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the ## confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = betapdf (x, this.a, this.b); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (betacdf ([lx, ux], this.a, this.b)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.BetaDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.BetaDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.BetaDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.BetaDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.BetaDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.BetaDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.BetaDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the beta distribution, @qcode{@var{pnum} = 1} selects the parameter ## @qcode{a} and @qcode{@var{pnum} = 2} selects the parameter @qcode{b}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.BetaDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.BetaDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.BetaDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{betarnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (betacdf ([lx, ux], this.a, this.b)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = betarnd (this.a, this.b, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = betarnd (this.a, this.b, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, ## and upper limit, @var{upper}. If @var{pd} is fitted to data with ## @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BetaDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = betastat (this.a, this.b); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) freq = []; else freq = varargin{2}; endif if (nargin < 4) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{3}; endif ## Fit data [phat, pci] = betafit (x, alpha, freq, options); [~, acov] = betalike (phat, x, freq); ## Create fitted distribution object pd = prob.BetaDistribution.makeFitted (phat, pci, acov, x, freq); endfunction function pd = makeFitted (phat, pci, acov, x, freq) a = phat(1); b = phat(2); pd = prob.BetaDistribution (a, b); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', [], 'freq', freq); endfunction endmethods endclassdef function checkparams (a, b) if (! (isscalar (a) && isnumeric (a) && isreal (a) && isfinite (a) && a > 0)) error ("BetaDistribution: A must be a positive real scalar.") endif if (! (isscalar (b) && isnumeric (b) && isreal (b) && isfinite (b) && b > 0)) error ("BetaDistribution: B must be a positive real scalar.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Beta distribution with %! ## parameters a = 2 and b = 5. Fit a Beta distribution to this data and plot %! ## a PDF of the fitted distribution superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('Beta', 'a', 2, 'b', 5) %! data = random (pd_fixed, 5000, 1); %! pd_fitted = fitdist (data, 'Beta') %! plot (pd_fitted) %! msg = 'Fitted Beta distribution with a = %0.2f and b = %0.2f'; %! title (sprintf (msg, pd_fitted.a, pd_fitted.b)) ## Test output %!shared pd, t %! pd = prob.BetaDistribution; %! t = truncate (pd, 0.2, 0.8); %!assert_equal (cdf (pd, [0:0.2:1]), [0, 0.2, 0.4, 0.6, 0.8, 1], 1e-4); %!assert_equal (cdf (t, [0:0.2:1]), [0, 0, 0.3333, 0.6667, 1, 1], 1e-4); %!assert_equal (cdf (pd, [-1, 1, NaN]), [0, 1, NaN], 1e-4); %!assert_equal (cdf (t, [-1, 1, NaN]), [0, 1, NaN], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0.2, 0.4, 0.6, 0.8, 1], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [0.2, 0.32, 0.44, 0.56, 0.68, 0.8], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 0.4, 0.6, 0.8, 1, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 0.44, 0.56, 0.68, 0.8, NaN], 1e-4); %!assert_equal (iqr (pd), 0.5, 1e-4); %!assert_equal (iqr (t), 0.3, 1e-4); %!assert_equal (mean (pd), 0.5); %!assert_equal (mean (t), 0.5, 1e-6); %!assert_equal (median (pd), 0.5); %!assert_equal (median (t), 0.5, 1e-6); %!assert_equal (pdf (pd, [0:0.2:1]), [1, 1, 1, 1, 1, 1], 1e-4); %!assert_equal (pdf (t, [0:0.2:1]), [0, 1.6667, 1.6667, 1.6667, 1.6667, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1, NaN]), [0, 1, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1, NaN]), [0, 0, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 0.2), false); %!assert_equal (any (random (t, 1000, 1) > 0.8), false); %!assert_equal (std (pd), 0.2887, 1e-4); %!assert_equal (std (t), 0.1732, 1e-4); %!assert_equal (var (pd), 0.0833, 1e-4); %!assert_equal (var (t), 0.0300, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [0.2; 0.5; 0.7; 0.3; 0.8; 0.4; 0.6; 0.55; 0.25; 0.75; ... %! 0.35; 0.65; 0.45; 0.15; 0.85; 0.5; 0.6; 0.3; 0.7; 0.4]; %! pd = fitdist (x, 'Beta'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 1]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.BetaDistribution' constructor %!error ... %! prob.BetaDistribution (0, 1) %!error ... %! prob.BetaDistribution (Inf, 1) %!error ... %! prob.BetaDistribution (i, 1) %!error ... %! prob.BetaDistribution ('a', 1) %!error ... %! prob.BetaDistribution ([1, 2], 1) %!error ... %! prob.BetaDistribution (NaN, 1) %!error ... %! prob.BetaDistribution (1, 0) %!error ... %! prob.BetaDistribution (1, -1) %!error ... %! prob.BetaDistribution (1, Inf) %!error ... %! prob.BetaDistribution (1, i) %!error ... %! prob.BetaDistribution (1, 'a') %!error ... %! prob.BetaDistribution (1, [1, 2]) %!error ... %! prob.BetaDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.BetaDistribution, 2, 'uper') %!error ... %! cdf (prob.BetaDistribution, 2, 3) ## 'paramci' method %!shared x %! randg ('seed', 1); %! x = betarnd (1, 1, [100, 1]); %!error ... %! paramci (prob.BetaDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.BetaDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.BetaDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.BetaDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.BetaDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.BetaDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.BetaDistribution.fit (x), 'parameter', 'a', 'alpha', {0.05}) %!error ... %! paramci (prob.BetaDistribution.fit (x), 'parameter', {'a', 'b', 'param'}) %!error ... %! paramci (prob.BetaDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'a', 'b', 'param'}) %!error ... %! paramci (prob.BetaDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.BetaDistribution.fit (x), 'alpha', 0.01, 'parameter', 'param') %!error ... %! paramci (prob.BetaDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.BetaDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.BetaDistribution.fit (x), 'alpha', 0.01, 'parameter', 'a', ... %! 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.BetaDistribution, 'Parent') %!error ... %! plot (prob.BetaDistribution, 'PlotType', 12) %!error ... %! plot (prob.BetaDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.BetaDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.BetaDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.BetaDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.BetaDistribution, 'Discrete', {true}) %!error ... %! plot (prob.BetaDistribution, 'Parent', 12) %!error ... %! plot (prob.BetaDistribution, 'Parent', 'hax') %!error ... %! plot (prob.BetaDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.BetaDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.BetaDistribution, 2) %!error ... %! proflik (prob.BetaDistribution.fit (x), 3) %!error ... %! proflik (prob.BetaDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.BetaDistribution.fit (x), {1}) %!error ... %! proflik (prob.BetaDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.BetaDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.BetaDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.BetaDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.BetaDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.BetaDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.BetaDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.BetaDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.BetaDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.BetaDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.BetaDistribution) %!error ... %! truncate (prob.BetaDistribution, 2) %!error ... %! truncate (prob.BetaDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.BetaDistribution (1, 1); %! pd(2) = prob.BetaDistribution (1, 3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/BinomialDistribution.m000066400000000000000000001242341524624707500300130ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef BinomialDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.BinomialDistribution ## ## Binomial probability distribution object. ## ## A @code{prob.BinomialDistribution} object consists of parameters, a model ## description, and sample data for a binomial probability distribution. ## ## The binomial distribution is a discrete probability distribution that ## models the number of successes in a sequence of @var{N} independent trials, ## each with a probability of success @var{p}. ## ## There are several ways to create a @code{prob.BinomialDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.BinomialDistribution (@var{N}, @var{p})} ## to create a binomial distribution with fixed parameter values @var{N} and ## @var{p}. ## @item Use the static method @qcode{prob.BinomialDistribution.fit (@var{x}, ## @var{ntrials}, @var{alpha})} to fit a distribution to the data in @var{x} ## using the same input arguments as the @code{binofit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Binomial_distribution} ## ## @seealso{fitdist, makedist, binocdf, binoinv, binopdf, binornd, binofit, ## binolike, binostat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.BinomialDistribution} {property} N ## ## Number of trials ## ## A positive integer value characterizing the number of trials in the ## binomial distribution. You can access the @qcode{N} property using dot ## name assignment. ## ## @end deftp N ## -*- texinfo -*- ## @deftp {prob.BinomialDistribution} {property} p ## ## Probability of success ## ## A scalar value in the range @math{[0, 1]} characterizing the probability ## of success in each trial of the binomial distribution. You can access ## the @qcode{p} property using dot name assignment. ## ## @end deftp p endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.BinomialDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Binomial'; ## -*- texinfo -*- ## @deftp {prob.BinomialDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.BinomialDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'N', 'p'}; ## -*- texinfo -*- ## @deftp {prob.BinomialDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Number of trials', 'Probability of success'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'bino'; ParameterRange = [realmin, realmin; Inf, 1]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.BinomialDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{N} and @qcode{p} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.BinomialDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.BinomialDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.BinomialDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only ## meaningful when the distribution was fitted to data. If the distribution ## object was created with fixed parameters, or a parameter of a fitted ## distribution is modified, then all elements of the variance-covariance ## are zero. This property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.BinomialDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.BinomialDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data} : a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens} : an empty array, since @qcode{prob.BinomialDistribution} ## does not allow censoring. ## @item @qcode{frequency} : a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {@var{pd} =} BinomialDistribution (@var{N}, @var{p}) ## @deftypefnx {prob.BinomialDistribution} {@var{pd} =} BinomialDistribution () ## ## Create a @code{prob.BinomialDistribution} object. ## ## @var{N} and @var{p} are the distribution parameters, which the class help ## describes. Called with no arguments the parameters take their defaults, ## @var{N} 1 and @var{p} 0.5. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = BinomialDistribution (N, p) if (nargin == 0) N = 1; p = 0.5; endif checkparams (N, p); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [N, p]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'binomial distribution'); endfunction function disp (this) __disp__ (this, 'binomial distribution'); endfunction function this = set.N (this, N) checkparams (N, this.p); this.InputData = []; this.ParameterValues(1) = N; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function N = get.N (this) N = this.ParameterValues(1); endfunction function this = set.p (this, p) checkparams (this.N, p); this.InputData = []; this.ParameterValues(2) = p; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function p = get.p (this) p = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.BinomialDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = binocdf (x, this.N, this.p); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= binocdf (lx - 1, this.N, this.p); p(! (lb | ub)) /= diff (binocdf ([lx-1, ux], this.N, this.p)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif umax = binoinv (1, this.N, this.p); if (this.IsTruncated && this.Truncation(2) >= umax) ## Find out of range p values is_nan = p < 0 | p > 1; ## Get lower and upper boundaries lx = ceil (this.Truncation(1)); ux = floor (this.Truncation(2)); ux = min (ux, umax); lp = binocdf (lx - 1, this.N, this.p); up = binocdf (ux, this.N, this.p); p = lp + p * (up - lp); p(is_nan) = NaN; endif x = binoinv (p, this.N, this.p); if (this.IsTruncated) x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif m = binostat (this.N, this.p); if (this.IsTruncated) lx = ceil (this.Truncation(1)); ux = floor (this.Truncation(2)); ux = min (ux, binoinv (1, this.N, this.p)); x = [lx:ux]; m = sum (x .* pdf (this, x)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = binocdf ([lx, ux], this.N, this.p); m = binoinv (sum (Fa_b) / 2, this.N, this.p); else m = binoinv (0.5, this.N, this.p); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood ## of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = binolike ([this.N, this.p], this.InputData.data); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.BinomialDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the ## confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = binopdf (x, this.N, this.p); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (binocdf ([lx-1, ux], this.N, this.p)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.BinomialDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.BinomialDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, true, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.BinomialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.BinomialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.BinomialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.BinomialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.BinomialDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the binomial distribution, @qcode{@var{pnum} = 1} selects the ## parameter @qcode{N} and @qcode{@var{pnum} = 2} selects the parameter ## @qcode{p}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.BinomialDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.BinomialDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.BinomialDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{binornd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (binocdf ([lx-1, ux], this.N, this.p)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = binornd (this.N, this.p, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = binornd (this.N, this.p, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, ## and upper limit, @var{upper}. If @var{pd} is fitted to data with ## @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); endif ## Check boundaries and constrain within support: Natural numbers lower = round (lower); upper = round (upper); lower(lower < 0) = 0; if (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BinomialDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) ## Calculate untruncated mean and variance [um, uv] = binostat (this.N, this.p); ## Calculate truncated mean m = mean (this); ## Get lower and upper boundaries lx = ceil (this.Truncation(1)); ux = floor (this.Truncation(2)); ux = min (ux, binoinv (1, this.N, this.p)); ## Handle infinite support on the right if (isequal (ux, Inf)) ratio = 1 / diff (binocdf ([lx-1, ux], this.N, this.p)); x = 0:lx-1; v = ratio * (uv + (um - m) ^ 2 - sum (((x - m) .^ 2) .* ... binopdf (x, this.N, this.p))); else x = lx:ux; v = sum (((x - m) .^ 2) .* pdf (this, x)); endif else [~, v] = binostat (this.N, this.p); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, ntrials, varargin) ## Check input arguments if (nargin < 3) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 4) freq = ones (size (x)); else freq = varargin{2}; endif ## Fit data [pshat, psci] = mle (x, 'distribution', 'binomial', 'alpha', alpha, ... 'ntrials', ntrials, 'frequency', freq); phat = [ntrials, pshat]; pci = [[ntrials; ntrials], psci(:)]; [~, acov] = binolike (phat, x, freq); ## Create fitted distribution object pd = prob.BinomialDistribution.makeFitted (phat, pci, acov, x, freq); endfunction function pd = makeFitted (phat, pci, acov, x, freq) N = phat(1); p = phat(2); pd = prob.BinomialDistribution (N, p); pd.ParameterCI = pci; pd.ParameterIsFixed = [true, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', [], 'freq', freq); endfunction endmethods endclassdef function checkparams (N, p) if (! (isscalar (N) && isnumeric (N) && isreal (N) && isfinite (N) && N > 0 && fix (N) == N)) error ("BinomialDistribution: N must be a positive integer scalar.") endif if (! (isscalar (p) && isnumeric (p) && isreal (p) && isfinite (p) && p >= 0 && p <= 1)) error (strcat ("BinomialDistribution: p must be a real", ... " scalar bounded in the range [0, 1].")) endif endfunction ## Test output %!shared pd, t, t_inf %! pd = prob.BinomialDistribution (5, 0.5); %! t = truncate (pd, 2, 4); %! t_inf = truncate (pd, 2, Inf); %!assert_equal (cdf (pd, [0:5]), [0.0312, 0.1875, 0.5, 0.8125, 0.9688, 1], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0.4, 0.8, 1, 1], 1e-4); %!assert_equal (cdf (t_inf, [0:5]), [0, 0, 0.3846, 0.7692, 0.9615, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4, NaN]), [0.1875, 0.5, 0.8125, 0.9688, NaN], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4, NaN]), [0, 0.4, 0.8, 1, NaN], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 2, 2, 3, 3, 5], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2, 2, 3, 3, 4], 1e-4); %!assert_equal (icdf (t_inf, [0:0.2:1]), [2, 2, 3, 3, 4, 5], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 2, 3, 3, 5, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2, 3, 3, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 1); %!assert_equal (iqr (t), 1); %!assert_equal (mean (pd), 2.5, 1e-10); %!assert_equal (mean (t), 2.8, 1e-10); %!assert_equal (mean (t_inf), 2.8846, 1e-4); ## The median is the smallest attainable value whose CDF reaches 0.5, so it is ## in the support: 2 for Binomial (5, 0.5), as R2024a returns. Measured ## 2026-08-17. %!assert_equal (median (pd), 2); %!assert_equal (median (prob.BinomialDistribution (7, 0.5)), 3); %!assert_equal (median (prob.BinomialDistribution (10, 0.5)), 5); %!assert_equal (icdf (pd, 0.5), 2); %!assert_equal (median (t), 3); %!assert_equal (pdf (pd, [0:5]), [0.0312, 0.1562, 0.3125, 0.3125, 0.1562, 0.0312], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 0.4, 0.4, 0.2, 0], 1e-4); %!assert_equal (pdf (t_inf, [0:5]), [0, 0, 0.3846, 0.3846, 0.1923, 0.0385], 1e-4); %!assert_equal (pdf (pd, [-1, 1.5, NaN]), [0, 0, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1.5, NaN]), [0, 0, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 1.1180, 1e-4); %!assert_equal (std (t), 0.7483, 1e-4); %!assert_equal (std (t_inf), 0.8470, 1e-4); %!assert_equal (var (pd), 1.2500, 1e-4); %!assert_equal (var (t), 0.5600, 1e-4); %!assert_equal (var (t_inf), 0.7175, 1e-4); %!test %! ## paramci reports one column per parameter, N held at its own value, as %! ## MATLAB does; it used to return a single 1x2 row, which stopped proflik. %! x = [3; 1; 4; 1; 5; 2; 6; 5; 3; 5; 2; 4; 1; 3; 2; 4; 6; 2; 3; 1]; %! pd = fitdist (x, 'Binomial', 'NTrials', 8); %! assert_equal (paramci (pd), [8, 0.317538; 8, 0.473975], 1e-6); %! assert_equal (paramci (pd, 'Parameter', 'p'), [0.317538; 0.473975], 1e-6); %! assert_equal (paramci (pd, 'Parameter', 'N'), [8; 8]); %!test %! ## The profile over p, the only estimated parameter, takes 101 grid values. %! ## OTHER reports the fixed N at its own value; MATLAB documents that and %! ## then returns 0 there, so this follows its documentation, not its code. %! x = [3; 1; 4; 1; 5; 2; 6; 5; 3; 5; 2; 4; 1; 3; 2; 4; 6; 2; 3; 1]; %! pd = fitdist (x, 'Binomial', 'NTrials', 8); %! [nlogL, param, other] = proflik (pd, 2); %! assert_equal (size (param), [1, 101]); %! assert_equal (size (other), [101, 1]); %! assert_equal (unique (other), 8); %! assert_equal (proflik (pd), nlogL); ## Test input validation ## 'prob.BinomialDistribution' constructor %!error ... %! prob.BinomialDistribution (Inf, 0.5) %!error ... %! prob.BinomialDistribution (i, 0.5) %!error ... %! prob.BinomialDistribution ('a', 0.5) %!error ... %! prob.BinomialDistribution ([1, 2], 0.5) %!error ... %! prob.BinomialDistribution (NaN, 0.5) %!error ... %! prob.BinomialDistribution (1, 1.01) %!error ... %! prob.BinomialDistribution (1, -0.01) %!error ... %! prob.BinomialDistribution (1, Inf) %!error ... %! prob.BinomialDistribution (1, i) %!error ... %! prob.BinomialDistribution (1, 'a') %!error ... %! prob.BinomialDistribution (1, [1, 2]) %!error ... %! prob.BinomialDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.BinomialDistribution, 2, 'uper') %!error ... %! cdf (prob.BinomialDistribution, 2, 3) ## 'paramci' method %!shared x %! rand ('seed', 2); %! x = binornd (5, 0.5, [1, 100]); %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), 'alpha') %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), 'alpha', 0) %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), 'alpha', 1) %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), 'alpha', [0.5 2]) %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), 'alpha', '') %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), 'alpha', {0.05}) %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), 'parameter', 'p', ... %! 'alpha', {0.05}) %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), ... %! 'parameter', {'N', 'p', 'param'}) %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), 'alpha', 0.01, ... %! 'parameter', {'N', 'p', 'param'}) %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), 'parameter', 'param') %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), 'alpha', 0.01, ... %! 'parameter', 'param') %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), 'NAME', 'value') %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), 'alpha', 0.01, ... %! 'NAME', 'value') %!error ... %! paramci (prob.BinomialDistribution.fit (x, 6), 'alpha', 0.01, ... %! 'parameter', 'p', 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.BinomialDistribution, 'Parent') %!error ... %! plot (prob.BinomialDistribution, 'PlotType', 12) %!error ... %! plot (prob.BinomialDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.BinomialDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.BinomialDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.BinomialDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.BinomialDistribution, 'Discrete', {true}) %!error ... %! plot (prob.BinomialDistribution, 'Parent', 12) %!error ... %! plot (prob.BinomialDistribution, 'Parent', 'hax') %!error ... %! plot (prob.BinomialDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.BinomialDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.BinomialDistribution, 2) %!error ... %! proflik (prob.BinomialDistribution.fit (x, 6), 3) %!error ... %! proflik (prob.BinomialDistribution.fit (x, 6), [1, 2]) %!error ... %! proflik (prob.BinomialDistribution.fit (x, 6), {1}) %!error ... %! proflik (prob.BinomialDistribution.fit (x, 6), 2, ones (2)) %!error ... %! proflik (prob.BinomialDistribution.fit (x, 6), 2, 'Display') %!error ... %! proflik (prob.BinomialDistribution.fit (x, 6), 2, 'Display', 1) %!error ... %! proflik (prob.BinomialDistribution.fit (x, 6), 2, 'Display', {1}) %!error ... %! proflik (prob.BinomialDistribution.fit (x, 6), 2, 'Display', {'on'}) %!error ... %! proflik (prob.BinomialDistribution.fit (x, 6), 2, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.BinomialDistribution.fit (x, 6), 2, 'Display', 'onnn') %!error ... %! proflik (prob.BinomialDistribution.fit (x, 6), 2, 'NAME', 'on') %!error ... %! proflik (prob.BinomialDistribution.fit (x, 6), 2, {'NAME'}, 'on') %!error ... %! proflik (prob.BinomialDistribution.fit (x, 6), 2, {[1 2 3]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.BinomialDistribution) %!error ... %! truncate (prob.BinomialDistribution, 2) %!error ... %! truncate (prob.BinomialDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.BinomialDistribution (1, 0.5); %! pd(2) = prob.BinomialDistribution (1, 0.6); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/BirnbaumSaundersDistribution.m000066400000000000000000001252021524624707500315210ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef BirnbaumSaundersDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.BirnbaumSaundersDistribution ## ## Birnbaum-Saunders probability distribution object. ## ## A @code{prob.BirnbaumSaundersDistribution} object consists of parameters, a ## model description, and sample data for a Birnbaum-Saunders probability ## distribution. ## ## The Birnbaum-Saunders distribution is a continuous probability distribution ## that models the time to failure of materials subjected to cyclic loading. ## It is defined by scale parameter @var{beta} and shape parameter ## @var{gamma}. ## ## There are several ways to create a @code{prob.BirnbaumSaundersDistribution} ## object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.BirnbaumSaundersDistribution (@var{beta}, ## @var{gamma})} to create a Birnbaum-Saunders distribution with fixed ## parameter values @var{beta} and @var{gamma}. ## @item Use the static method @qcode{prob.BirnbaumSaundersDistribution.fit ## (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options})} to fit a ## distribution to the data in @var{x} using the same input arguments as the ## @code{bisafit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the Birnbaum-Saunders distribution can be found ## at ## @url{https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution} ## ## @seealso{fitdist, makedist, bisacdf, bisainv, bisapdf, bisarnd, bisafit, ## bisalike, bisastat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.BirnbaumSaundersDistribution} {property} beta ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the ## Birnbaum-Saunders distribution. You can access the @qcode{beta} ## property using dot name assignment. ## ## @end deftp beta ## -*- texinfo -*- ## @deftp {prob.BirnbaumSaundersDistribution} {property} gamma ## ## Shape parameter ## ## A positive scalar value characterizing the shape of the ## Birnbaum-Saunders distribution. You can access the @qcode{gamma} ## property using dot name assignment. ## ## @end deftp gamma endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.BirnbaumSaundersDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Birnbaum-Saunders'; ## -*- texinfo -*- ## @deftp {prob.BirnbaumSaundersDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.BirnbaumSaundersDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'beta', 'gamma'}; ## -*- texinfo -*- ## @deftp {prob.BirnbaumSaundersDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Scale', 'Shape'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'bisa'; ParameterRange = [realmin, realmin; Inf, Inf]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.BirnbaumSaundersDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{beta} and @qcode{gamma} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.BirnbaumSaundersDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.BirnbaumSaundersDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.BirnbaumSaundersDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only meaningful ## when the distribution was fitted to data. If the distribution object was ## created with fixed parameters, or a parameter of a fitted distribution is ## modified, then all elements of the variance-covariance are zero. This ## property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.BirnbaumSaundersDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.BirnbaumSaundersDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {@var{pd} =} BirnbaumSaundersDistribution (@var{beta}, @var{gamma}) ## @deftypefnx {prob.BirnbaumSaundersDistribution} {@var{pd} =} BirnbaumSaundersDistribution () ## ## Create a @code{prob.BirnbaumSaundersDistribution} object. ## ## @var{beta} and @var{gamma} are the distribution parameters, which the ## class help describes. Called with no arguments the parameters take their ## defaults, @var{beta} 1 and @var{gamma} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = BirnbaumSaundersDistribution (beta, gamma) if (nargin == 0) beta = 1; gamma = 1; endif checkparams (beta, gamma); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [beta, gamma]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Birnbaum-Saunders distribution'); endfunction function disp (this) __disp__ (this, 'Birnbaum-Saunders distribution'); endfunction function this = set.beta (this, beta) checkparams (beta, this.gamma); this.InputData = []; this.ParameterValues(1) = beta; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function beta = get.beta (this) beta = this.ParameterValues(1); endfunction function this = set.gamma (this, gamma) checkparams (this.beta, gamma); this.InputData = []; this.ParameterValues(2) = gamma; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function gamma = get.gamma (this) gamma = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.BirnbaumSaundersDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = bisacdf (x, this.beta, this.gamma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= bisacdf (lx, this.beta, this.gamma); p(! (lb | ub)) /= diff (bisacdf ([lx, ux], this.beta, this.gamma)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = bisacdf (this.Truncation(1), this.beta, this.gamma); up = bisacdf (this.Truncation(2), this.beta, this.gamma); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = bisainv (np, this.beta, this.gamma); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = bisainv (p, this.beta, this.gamma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = bisastat (this.beta, this.gamma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = bisacdf ([lx, ux], this.beta, this.gamma); m = bisainv (sum (Fa_b) / 2, this.beta, this.gamma); else m = bisainv (0.5, this.beta, this.gamma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = bisalike ([this.beta, this.gamma], this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.BirnbaumSaundersDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the ## confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = bisapdf (x, this.beta, this.gamma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (bisacdf ([lx, ux], this.beta, this.gamma)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.BirnbaumSaundersDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.BirnbaumSaundersDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.BirnbaumSaundersDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.BirnbaumSaundersDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.BirnbaumSaundersDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.BirnbaumSaundersDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.BirnbaumSaundersDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the Birnbaum-Saunders distribution, @qcode{@var{pnum} = 1} selects ## the parameter @qcode{beta} and @qcode{@var{pnum} = 2} selects the ## parameter @qcode{gamma}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.BirnbaumSaundersDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.BirnbaumSaundersDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.BirnbaumSaundersDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{bisarnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (bisacdf ([lx, ux], this.beta, this.gamma)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = bisarnd (this.beta, this.gamma, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = bisarnd (this.beta, this.gamma, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, ## and upper limit, @var{upper}. If @var{pd} is fitted to data with ## @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BirnbaumSaundersDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = bisastat (this.beta, this.gamma); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{4}; endif ## Fit data [phat, pci] = bisafit (x, alpha, censor, freq, options); [~, acov] = bisalike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.BirnbaumSaundersDistribution.makeFitted ... (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) beta = phat(1); gamma = phat(2); pd = prob.BirnbaumSaundersDistribution (beta, gamma); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (beta, gamma) if (! (isscalar (beta) && isnumeric (beta) && isreal (beta) && isfinite (beta) && beta > 0)) error ("BirnbaumSaundersDistribution: BETA must be a positive real scalar.") endif if (! (isscalar (gamma) && isnumeric (gamma) && isreal (gamma) && isfinite (gamma) && gamma > 0)) error ("BirnbaumSaundersDistribution: GAMMA must be a positive real scalar.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Birnbaum-Saunders %! ## distribution with parameters β = 1 and γ = 0.5. Fit a Birnbaum-Saunders %! ## distribution to this data and plot a PDF of the fitted distribution %! ## superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('BirnbaumSaunders', 'beta', 1, 'gamma', 0.5) %! data = random (pd_fixed, 5000, 1); %! pd_fitted = fitdist (data, 'BirnbaumSaunders') %! plot (pd_fitted) %! msg = 'Fitted Birnbaum-Saunders distribution with beta = %0.2f and gamma = %0.2f'; %! title (sprintf (msg, pd_fitted.beta, pd_fitted.gamma)) ## Test output %!shared pd, t %! pd = prob.BirnbaumSaundersDistribution; %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.5, 0.7602, 0.8759, 0.9332, 0.9632], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.6687, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4, NaN]), [0.6585, 0.7602, 0.8759, 0.9332, NaN], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4, NaN]), [0, 0, 0.6687, 1, NaN], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0.4411, 0.7767, 1.2875, 2.2673, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.2293, 2.5073, 2.8567, 3.3210, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 0.7767, 1.2875, 2.2673, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.5073, 2.8567, 3.3210, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 1.4236, 1e-4); %!assert_equal (iqr (t), 0.8968, 1e-4); %!assert_equal (mean (pd), 1.5, eps); %!assert_equal (mean (t), 2.7723, 1e-4); %!assert_equal (median (pd), 1, 1e-4); %!assert_equal (median (t), 2.6711, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0, 0.3989, 0.1648, 0.0788, 0.0405, 0.0216], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 0.9528, 0.4559, 0.2340, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1.5, NaN]), [0, 0.2497, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1.5, NaN]), [0, 0, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 1.5, eps); %!assert_equal (std (t), 0.5528, 1e-4); %!assert_equal (var (pd), 2.25, eps); %!assert_equal (var (t), 0.3056, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [1.2; 0.4; 3.1; 0.7; 2.5; 1.8; 0.3; 4.2; 1.1; 0.9; ... %! 2.2; 0.6; 1.5; 3.7; 0.8; 2.9; 1.3; 0.5; 2.0; 1.6]; %! pd = fitdist (x, 'BirnbaumSaunders'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 1]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.BirnbaumSaundersDistribution' constructor %!error ... %! prob.BirnbaumSaundersDistribution (0, 1) %!error ... %! prob.BirnbaumSaundersDistribution (Inf, 1) %!error ... %! prob.BirnbaumSaundersDistribution (i, 1) %!error ... %! prob.BirnbaumSaundersDistribution ('beta', 1) %!error ... %! prob.BirnbaumSaundersDistribution ([1, 2], 1) %!error ... %! prob.BirnbaumSaundersDistribution (NaN, 1) %!error ... %! prob.BirnbaumSaundersDistribution (1, 0) %!error ... %! prob.BirnbaumSaundersDistribution (1, -1) %!error ... %! prob.BirnbaumSaundersDistribution (1, Inf) %!error ... %! prob.BirnbaumSaundersDistribution (1, i) %!error ... %! prob.BirnbaumSaundersDistribution (1, 'beta') %!error ... %! prob.BirnbaumSaundersDistribution (1, [1, 2]) %!error ... %! prob.BirnbaumSaundersDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.BirnbaumSaundersDistribution, 2, 'uper') %!error ... %! cdf (prob.BirnbaumSaundersDistribution, 2, 3) ## 'paramci' method %!shared x %! rand ('seed', 5); %! x = bisarnd (1, 1, [100, 1]); %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), 'parameter', ... %! 'beta', 'alpha', {0.05}) %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), ... %! 'parameter', {'beta', 'gamma', 'param'}) %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'beta', 'gamma', 'param'}) %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'param') %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), 'alpha', 0.01, ... %! 'NAME', 'value') %!error ... %! paramci (prob.BirnbaumSaundersDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'beta', 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.BirnbaumSaundersDistribution, 'Parent') %!error ... %! plot (prob.BirnbaumSaundersDistribution, 'PlotType', 12) %!error ... %! plot (prob.BirnbaumSaundersDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.BirnbaumSaundersDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.BirnbaumSaundersDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.BirnbaumSaundersDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.BirnbaumSaundersDistribution, 'Discrete', {true}) %!error ... %! plot (prob.BirnbaumSaundersDistribution, 'Parent', 12) %!error ... %! plot (prob.BirnbaumSaundersDistribution, 'Parent', 'hax') %!error ... %! plot (prob.BirnbaumSaundersDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.BirnbaumSaundersDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.BirnbaumSaundersDistribution, 2) %!error ... %! proflik (prob.BirnbaumSaundersDistribution.fit (x), 3) %!error ... %! proflik (prob.BirnbaumSaundersDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.BirnbaumSaundersDistribution.fit (x), {1}) %!error ... %! proflik (prob.BirnbaumSaundersDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.BirnbaumSaundersDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.BirnbaumSaundersDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.BirnbaumSaundersDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.BirnbaumSaundersDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.BirnbaumSaundersDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.BirnbaumSaundersDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.BirnbaumSaundersDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.BirnbaumSaundersDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.BirnbaumSaundersDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.BirnbaumSaundersDistribution) %!error ... %! truncate (prob.BirnbaumSaundersDistribution, 2) %!error ... %! truncate (prob.BirnbaumSaundersDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.BirnbaumSaundersDistribution (1, 1); %! pd(2) = prob.BirnbaumSaundersDistribution (1, 3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/BurrDistribution.m000066400000000000000000001270701524624707500271740ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef BurrDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.BurrDistribution ## ## Burr probability distribution object. ## ## A @code{prob.BurrDistribution} object consists of parameters, a model ## description, and sample data for a Burr probability distribution. ## ## The Burr distribution is a continuous probability distribution that models ## a non-negative random variable, commonly used to model household income. ## It is defined by a scale parameter @var{alpha} and two shape parameters ## @var{c} and @var{k}. ## ## There are several ways to create a @code{prob.BurrDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.BurrDistribution (@var{alpha}, @var{c}, ## @var{k})} to create a Burr distribution with fixed parameter values ## @var{alpha}, @var{c}, and @var{k}. ## @item Use the static method @qcode{prob.BurrDistribution.fit (@var{x}, ## @var{alpha}, @var{censor}, @var{freq}, @var{options})} to fit a ## distribution to the data in @var{x} using the same input arguments as the ## @code{burrfit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the Burr distribution can be found at ## @url{https://en.wikipedia.org/wiki/Burr_distribution} ## ## @seealso{fitdist, makedist, burrcdf, burrinv, burrpdf, burrrnd, burrfit, ## burrlike, burrstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.BurrDistribution} {property} alpha ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the Burr ## distribution. You can access the @qcode{alpha} property using dot name ## assignment. ## ## @end deftp alpha ## -*- texinfo -*- ## @deftp {prob.BurrDistribution} {property} c ## ## First shape parameter ## ## A positive scalar value characterizing the first shape parameter of the ## Burr distribution. You can access the @qcode{c} property using dot name ## assignment. ## ## @end deftp c ## -*- texinfo -*- ## @deftp {prob.BurrDistribution} {property} k ## ## Second shape parameter ## ## A positive scalar value characterizing the second shape parameter of the ## Burr distribution. You can access the @qcode{k} property using dot name ## assignment. ## ## @end deftp k endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.BurrDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Burr'; ## -*- texinfo -*- ## @deftp {prob.BurrDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 3; ## -*- texinfo -*- ## @deftp {prob.BurrDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{3*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'alpha', 'c', 'k'}; ## -*- texinfo -*- ## @deftp {prob.BurrDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{3*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Scale', '1st shape', '2nd shape'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'burr'; ParameterRange = [realmin, realmin, realmin; Inf, Inf, Inf]; ParameterLogCI = [false, true, false]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.BurrDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{3*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{alpha}, @qcode{c}, and ## @qcode{k} properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.BurrDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.BurrDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.BurrDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{3*3} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only meaningful ## when the distribution was fitted to data. If the distribution object was ## created with fixed parameters, or a parameter of a fitted distribution is ## modified, then all elements of the variance-covariance are zero. This ## property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.BurrDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*3} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.BurrDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: an empty array, since @qcode{prob.BurrDistribution} does ## not allow censoring. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {@var{pd} =} BurrDistribution (@var{alpha}, @var{c}, @var{k}) ## @deftypefnx {prob.BurrDistribution} {@var{pd} =} BurrDistribution () ## ## Create a @code{prob.BurrDistribution} object. ## ## @var{alpha}, @var{c} and @var{k} are the distribution parameters, which ## the class help describes. Called with no arguments the parameters take ## their defaults, @var{alpha} 1, @var{c} 1 and @var{k} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = BurrDistribution (alpha, c, k) if (nargin == 0) alpha = 1; c = 1; k = 1; endif checkparams (alpha, c, k); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [alpha, c, k]; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Burr distribution'); endfunction function disp (this) __disp__ (this, 'Burr distribution'); endfunction function this = set.alpha (this, alpha) checkparams (alpha, this.c, this.k); this.InputData = []; this.ParameterValues(1) = alpha; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function alpha = get.alpha (this) alpha = this.ParameterValues(1); endfunction function this = set.c (this, c) checkparams (this.alpha, c, this.k); this.InputData = []; this.ParameterValues(2) = c; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function c = get.c (this) c = this.ParameterValues(2); endfunction function this = set.k (this, k) checkparams (this.alpha, this.c, k); this.InputData = []; this.ParameterValues(3) = k; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function k = get.k (this) k = this.ParameterValues(3); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.BurrDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = burrcdf (x, this.alpha, this.c, this.k); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= burrcdf (lx, this.alpha, this.c, this.k); p(! (lb | ub)) /= diff (burrcdf ([lx, ux], this.alpha, this.c, this.k)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = burrcdf (this.Truncation(1), this.alpha, this.c, this.k); up = burrcdf (this.Truncation(2), this.alpha, this.c, this.k); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = burrinv (np, this.alpha, this.c, this.k); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = burrinv (p, this.alpha, this.c, this.k); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = burrstat (this.alpha, this.c, this.k); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = burrcdf ([lx, ux], this.alpha, this.c, this.k); m = burrinv (sum (Fa_b) / 2, this.alpha, this.c, this.k); else m = burrinv (0.5, this.alpha, this.c, this.k); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = burrlike ([this.alpha, this.c, this.k], this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.BurrDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the ## confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = burrpdf (x, this.alpha, this.c, this.k); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (burrcdf ([lx, ux], this.alpha, this.c, this.k)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.BurrDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.BurrDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.BurrDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.BurrDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.BurrDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.BurrDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.BurrDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the Burr distribution, @qcode{@var{pnum} = 1} selects the parameter ## @qcode{alpha}, @qcode{@var{pnum} = 2} selects the parameter @qcode{c}, ## and @qcode{@var{pnum} = 3} selects the parameter @qcode{k}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.BurrDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.BurrDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.BurrDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{burrrnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (burrcdf ([lx, ux], this.alpha, this.c, this.k)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = burrrnd (this.alpha, this.c, this.k, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = burrrnd (this.alpha, this.c, this.k, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, ## and upper limit, @var{upper}. If @var{pd} is fitted to data with ## @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.BurrDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = burrstat (this.alpha, this.c, this.k); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{4}; endif ## Fit data [phat, pci] = burrfit (x, alpha, censor, freq, options); [~, acov] = burrlike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.BurrDistribution.makeFitted (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) alpha = phat(1); c = phat(2); k = phat(3); pd = prob.BurrDistribution (alpha, c, k); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (alpha, c, k) if (! (isscalar (alpha) && isnumeric (alpha) && isreal (alpha) && isfinite (alpha) && alpha > 0)) error ("BurrDistribution: ALPHA must be a positive real scalar.") endif if (! (isscalar (c) && isnumeric (c) && isreal (c) && isfinite (c) && c > 0)) error ("BurrDistribution: C must be a positive real scalar.") endif if (! (isscalar (k) && isnumeric (k) && isreal (k) && isfinite (k) && k > 0)) error ("BurrDistribution: K must be a positive real scalar.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Burr type XII %! ## distribution with parameters alpha = 1, c = 2, and k = 1. Fit a Burr type %! ## XII distribution to this data and plot a PDF of the fitted distribution %! ## superimposed on a histogram of the data %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ('Burr', 'alpha', 1, 'c', 2, 'k', 1) %! data = random (pd, 5000, 1); %! pd = fitdist (data, 'Burr') %! plot (pd) %! msg = strcat ("Fitted Burr type XII distribution with", ... %! " alpha = %0.2f, c = %0.2f, and k = %0.2f"); %! title (sprintf (msg, pd.alpha, pd.c, pd.k)) ## Test output %!shared pd, t %! pd = prob.BurrDistribution; %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.5, 0.6667, 0.75, 0.8, 0.8333], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.625, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.6, 0.6667, 0.75, 0.8], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0, 0.625, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0.25, 0.6667, 1.5, 4, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.2609, 2.5714, 2.9474, 3.4118, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 0.6667, 1.5, 4, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.5714, 2.9474, 3.4118, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 2.6667, 1e-4); %!assert_equal (iqr (t), 0.9524, 1e-4); %!assert_equal (mean (pd), Inf); %!assert_equal (mean (t), 2.8312, 1e-4); %!assert_equal (median (pd), 1, 1e-4); %!assert_equal (median (t), 2.75, 1e-4); %!assert_equal (pdf (pd, [0:5]), [1, 0.25, 0.1111, 0.0625, 0.04, 0.0278], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 0.8333, 0.4687, 0.3, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0, 0.25, 0.1111, 0.0625, 0.04, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 0.8333, 0.4687, 0.3, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), Inf); %!assert_equal (std (t), 0.5674, 1e-4); %!assert_equal (var (pd), Inf); %!assert_equal (var (t), 0.3220, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. The %! ## sample is Burr, drawn from burrinv at fixed quantiles. %! x = [0.160128; 0.284747; 0.377964; 0.460566; 0.538816; 0.615882; ... %! 0.693889; 0.774597; 0.859727; 0.951190; 1.051310; 1.163160; ... %! 1.290990; 1.441150; 1.623690; 1.855920; 2.171240; 2.645750; ... %! 3.511880; 6.245000]; %! pd = fitdist (x, 'Burr'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 2]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); %!test %! ## The profiled-out parameters are passed to burrlike as frequencies, not as %! ## censoring: reading them as censoring marks every observation censored and %! ## the profile runs away as the second shape parameter goes to zero. %! x = [0.160128; 0.284747; 0.377964; 0.460566; 0.538816; 0.615882; ... %! 0.693889; 0.774597; 0.859727; 0.951190; 1.051310; 1.163160; ... %! 1.290990; 1.441150; 1.623690; 1.855920; 2.171240; 2.645750; ... %! 3.511880; 6.245000]; %! pd = fitdist (x, 'Burr'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (max (nlogL) <= -burrlike (pd.ParameterValues, x), true); %! [~, imax] = max (nlogL); %! assert_equal (-burrlike ([param(imax), other(imax,:)], x), nlogL(imax), 1e-6); ## Test input validation ## 'prob.BurrDistribution' constructor %!error ... %! prob.BurrDistribution (0, 1, 1) %!error ... %! prob.BurrDistribution (-1, 1, 1) %!error ... %! prob.BurrDistribution (Inf, 1, 1) %!error ... %! prob.BurrDistribution (i, 1, 1) %!error ... %! prob.BurrDistribution ('a', 1, 1) %!error ... %! prob.BurrDistribution ([1, 2], 1, 1) %!error ... %! prob.BurrDistribution (NaN, 1, 1) %!error ... %! prob.BurrDistribution (1, 0, 1) %!error ... %! prob.BurrDistribution (1, -1, 1) %!error ... %! prob.BurrDistribution (1, Inf, 1) %!error ... %! prob.BurrDistribution (1, i, 1) %!error ... %! prob.BurrDistribution (1, 'a', 1) %!error ... %! prob.BurrDistribution (1, [1, 2], 1) %!error ... %! prob.BurrDistribution (1, NaN, 1) %!error ... %! prob.BurrDistribution (1, 1, 0) %!error ... %! prob.BurrDistribution (1, 1, -1) %!error ... %! prob.BurrDistribution (1, 1, Inf) %!error ... %! prob.BurrDistribution (1, 1, i) %!error ... %! prob.BurrDistribution (1, 1, 'a') %!error ... %! prob.BurrDistribution (1, 1, [1, 2]) %!error ... %! prob.BurrDistribution (1, 1, NaN) ## 'cdf' method %!error ... %! cdf (prob.BurrDistribution, 2, 'uper') %!error ... %! cdf (prob.BurrDistribution, 2, 3) ## 'paramci' method %!shared x %! rand ('seed', 4); %! x = burrrnd (1, 1, 1, [1, 100]); %!error ... %! paramci (prob.BurrDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.BurrDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.BurrDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.BurrDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.BurrDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.BurrDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.BurrDistribution.fit (x), 'parameter', 'c', 'alpha', {0.05}) %!error ... %! paramci (prob.BurrDistribution.fit (x), 'parameter', {'alpha', 'c', 'k', 'param'}) %!error ... %! paramci (prob.BurrDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'alpha', 'c', 'k', 'param'}) %!error ... %! paramci (prob.BurrDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.BurrDistribution.fit (x), 'alpha', 0.01, 'parameter', 'param') %!error ... %! paramci (prob.BurrDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.BurrDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.BurrDistribution.fit (x), 'alpha', 0.01, 'parameter', 'c', ... %! 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.BurrDistribution, 'Parent') %!error ... %! plot (prob.BurrDistribution, 'PlotType', 12) %!error ... %! plot (prob.BurrDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.BurrDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.BurrDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.BurrDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.BurrDistribution, 'Discrete', {true}) %!error ... %! plot (prob.BurrDistribution, 'Parent', 12) %!error ... %! plot (prob.BurrDistribution, 'Parent', 'hax') %!error ... %! plot (prob.BurrDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.BurrDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.BurrDistribution, 2) %!error ... %! proflik (prob.BurrDistribution.fit (x), 4) %!error ... %! proflik (prob.BurrDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.BurrDistribution.fit (x), {1}) %!error ... %! proflik (prob.BurrDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.BurrDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.BurrDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.BurrDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.BurrDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.BurrDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.BurrDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.BurrDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.BurrDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.BurrDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.BurrDistribution) %!error ... %! truncate (prob.BurrDistribution, 2) %!error ... %! truncate (prob.BurrDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.BurrDistribution (1, 1, 1); %! pd(2) = prob.BurrDistribution (1, 3, 1); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/ExponentialDistribution.m000066400000000000000000001157551524624707500305570ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef ExponentialDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.ExponentialDistribution ## ## Exponential probability distribution object. ## ## A @code{prob.ExponentialDistribution} object consists of parameters, a model ## description, and sample data for a exponential probability distribution. ## ## The exponential distribution is a continuous probability distribution with ## mean parameter @var{mu} that models the time between events in a Poisson ## process. ## ## There are several ways to create a @code{prob.ExponentialDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.ExponentialDistribution (@var{mu})} ## to create a exponential distribution with fixed parameter value @var{mu}. ## @item Use the static method @qcode{prob.ExponentialDistribution.fit (@var{x}, ## @var{alpha}, @var{censor}, @var{freq}, @var{options})} to fit a ## distribution to the data in @var{x} using the same input arguments as the ## @code{expfit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the exponential distribution can be found at ## @url{https://en.wikipedia.org/wiki/Exponential_distribution} ## ## @seealso{fitdist, makedist, expcdf, expinv, exppdf, exprnd, expfit, ## explike, expstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.ExponentialDistribution} {property} mu ## ## Mean parameter ## ## A positive scalar value characterizing the mean of the ## exponential distribution. You can access the @qcode{mu} ## property using dot name assignment. ## ## @end deftp mu endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.ExponentialDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Exponential'; ## -*- texinfo -*- ## @deftp {prob.ExponentialDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 1; ## -*- texinfo -*- ## @deftp {prob.ExponentialDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{1*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'mu'}; ## -*- texinfo -*- ## @deftp {prob.ExponentialDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{1*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Mean'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'exp'; ParameterRange = [realmin; Inf]; ParameterLogCI = true; endproperties properties(GetAccess = public , SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.ExponentialDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{1*1} numeric vector containing the value of the distribution ## parameter. This property is read-only. You can change the distribution ## parameter by assigning a new value to the @qcode{mu} ## property. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.ExponentialDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.ExponentialDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.ExponentialDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A scalar numeric value containing the variance-covariance of the ## parameter estimate. The covariance matrix is only meaningful ## when the distribution was fitted to data. If the distribution object was ## created with fixed parameters, or a parameter of a fitted distribution is ## modified, then the variance-covariance is zero. This ## property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.ExponentialDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*1} logical vector specifying whether the parameter is fixed or ## estimated. @qcode{true} value corresponds to fixed parameter, ## @qcode{false} value corresponds to parameter estimate. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.ExponentialDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {@var{pd} =} ExponentialDistribution (@var{mu}) ## @deftypefnx {prob.ExponentialDistribution} {@var{pd} =} ExponentialDistribution () ## ## Create a @code{prob.ExponentialDistribution} object. ## ## @var{mu} is the distribution parameter, which the class help describes. ## Called with no arguments the parameter takes its default, @var{mu} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = ExponentialDistribution (mu) if (nargin == 0) mu = 1; endif checkparams (mu); this.InputData = []; this.IsTruncated = false; this.ParameterValues = mu; this.ParameterIsFixed = true; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'exponential distribution'); endfunction function disp (this) __disp__ (this, 'exponential distribution'); endfunction function this = set.mu (this, mu) checkparams (mu); this.InputData = []; this.ParameterValues(1) = mu; this.ParameterIsFixed = true; this.ParameterCovariance = zeros (this.NumParameters); endfunction function mu = get.mu (this) mu = this.ParameterValues(1); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.ExponentialDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = expcdf (x, this.mu); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= expcdf (lx, this.mu); p(! (lb | ub)) /= diff (expcdf ([lx, ux], this.mu)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = expcdf (this.Truncation(1), this.mu); up = expcdf (this.Truncation(2), this.mu); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = expinv (np, this.mu); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = expinv (p, this.mu); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = expstat (this.mu); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = expcdf ([lx, ux], this.mu); m = expinv (sum (Fa_b) / 2, this.mu); else m = this.mu .* log (2); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = explike (this.mu, this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.ExponentialDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the ## confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = exppdf (x, this.mu); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (expcdf ([lx, ux], this.mu)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.ExponentialDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.ExponentialDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.ExponentialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.ExponentialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.ExponentialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.ExponentialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.ExponentialDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the exponential distribution, @qcode{@var{pnum} = 1} selects the ## parameter @qcode{mu}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.ExponentialDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.ExponentialDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.ExponentialDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{betarnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (expcdf ([lx, ux], this.mu)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = exprnd (this.mu, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = exprnd (this.mu, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, ## and upper limit, @var{upper}. If @var{pd} is fitted to data with ## @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif ## Check boundaries and constrain within support [0, Inf) lower(lower < 0) = 0; this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = true; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExponentialDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = expstat (this.mu); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif ## Fit data [phat, pci] = expfit (x, alpha, censor, freq); [~, acov] = explike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.ExponentialDistribution.makeFitted ... (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) mu = phat(1); pd = prob.ExponentialDistribution (mu); pd.ParameterCI = pci; pd.ParameterIsFixed = false; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (mu) if (! (isscalar (mu) && isnumeric (mu) && isreal (mu) && isfinite (mu) && mu > 0)) error ("ExponentialDistribution: MU must be a positive real scalar.") endif endfunction ## Test output %!shared pd, t %! pd = prob.ExponentialDistribution (1); %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.6321, 0.8647, 0.9502, 0.9817, 0.9933], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.7311, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.7769, 0.8647, 0.9502, 0.9817], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0, 0.7311, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0.2231, 0.5108, 0.9163, 1.6094, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.1899, 2.4244, 2.7315, 3.1768, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 0.5108, 0.9163, 1.6094, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.4244, 2.7315, 3.1768, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 1.0986, 1e-4); %!assert_equal (iqr (t), 0.8020, 1e-4); %!assert_equal (mean (pd), 1); %!assert_equal (mean (t), 2.6870, 1e-4); %!assert_equal (median (pd), 0.6931, 1e-4); %!assert_equal (median (t), 2.5662, 1e-4); %!assert_equal (pdf (pd, [0:5]), [1, 0.3679, 0.1353, 0.0498, 0.0183, 0.0067], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 1.1565, 0.4255, 0.1565, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0, 0.3679, 0.1353, 0.0498, 0.0183, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 1.1565, 0.4255, 0.1565, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 1); %!assert_equal (std (t), 0.5253, 1e-4); %!assert_equal (var (pd), 1); %!assert_equal (var (t), 0.2759, 1e-4); %!test %! ## The default grid takes 101 values over the 98% confidence interval when %! ## the selected parameter is the only one estimated. Verified against MATLAB. %! x = [1.2; 0.4; 3.1; 0.7; 2.5; 1.8; 0.3; 4.2; 1.1; 0.9; ... %! 2.2; 0.6; 1.5; 3.7; 0.8; 2.9; 1.3; 0.5; 2.0; 1.6]; %! [nlogL, param, other] = proflik (prob.ExponentialDistribution.fit (x), 1); %! assert_equal (size (param), [1, 101]); %! assert_equal ([param(1), param(end)], [1.0457, 3.0048], 1e-4); %! assert_equal (size (other), [101, 0]); %!test %! ## PNUM defaults to the first parameter that is not fixed. %! x = [1.2; 0.4; 3.1; 0.7; 2.5; 1.8; 0.3; 4.2; 1.1; 0.9; ... %! 2.2; 0.6; 1.5; 3.7; 0.8; 2.9; 1.3; 0.5; 2.0; 1.6]; %! pdf = prob.ExponentialDistribution.fit (x); %! assert_equal (proflik (pdf), proflik (pdf, 1)); ## Test input validation ## 'prob.ExponentialDistribution' constructor %!error ... %! prob.ExponentialDistribution (0) %!error ... %! prob.ExponentialDistribution (-1) %!error ... %! prob.ExponentialDistribution (Inf) %!error ... %! prob.ExponentialDistribution (i) %!error ... %! prob.ExponentialDistribution ('a') %!error ... %! prob.ExponentialDistribution ([1, 2]) %!error ... %! prob.ExponentialDistribution (NaN) ## 'cdf' method %!error ... %! cdf (prob.ExponentialDistribution, 2, 'uper') %!error ... %! cdf (prob.ExponentialDistribution, 2, 3) ## 'paramci' method %!shared x %! x = exprnd (1, [100, 1]); %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'parameter', 'mu', ... %! 'alpha', {0.05}) %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'parameter', {'mu', 'param'}) %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'mu', 'param'}) %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'alpha', 0.01, 'parameter', 'parm') %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.ExponentialDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'mu', 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.ExponentialDistribution, 'Parent') %!error ... %! plot (prob.ExponentialDistribution, 'PlotType', 12) %!error ... %! plot (prob.ExponentialDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.ExponentialDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.ExponentialDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.ExponentialDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.ExponentialDistribution, 'Discrete', {true}) %!error ... %! plot (prob.ExponentialDistribution, 'Parent', 12) %!error ... %! plot (prob.ExponentialDistribution, 'Parent', 'hax') %!error ... %! plot (prob.ExponentialDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.ExponentialDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.ExponentialDistribution, 2) %!error ... %! proflik (prob.ExponentialDistribution.fit (x), 3) %!error ... %! proflik (prob.ExponentialDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.ExponentialDistribution.fit (x), {1}) %!error ... %! proflik (prob.ExponentialDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.ExponentialDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.ExponentialDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.ExponentialDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.ExponentialDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.ExponentialDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.ExponentialDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.ExponentialDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.ExponentialDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.ExponentialDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.ExponentialDistribution) %!error ... %! truncate (prob.ExponentialDistribution, 2) %!error ... %! truncate (prob.ExponentialDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.ExponentialDistribution (1); %! pd(2) = prob.ExponentialDistribution (3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!test %! ## negloglik returns the (positive) negative log-likelihood. %! xdat = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! pdfit = prob.ExponentialDistribution.fit (xdat'); %! assert_equal (negloglik (pdfit), -sum (log (pdf (pdfit, xdat'))), 1e-9); %! assert_equal (negloglik (pdfit) > 0, true); %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/ExtremeValueDistribution.m000066400000000000000000001220171524624707500306640ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef ExtremeValueDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.ExtremeValueDistribution ## ## Extreme value probability distribution object. ## ## A @code{prob.ExtremeValueDistribution} object consists of parameters, a model ## description, and sample data for an extreme value probability distribution. ## ## The extreme value distribution is also known as the Gumbel distribution for ## maxima, and it is a limiting distribution for the maximum of a large number ## of samples from a continuous distribution. It is defined by location ## parameter @var{mu} and scale parameter @var{sigma}. ## ## There are several ways to create a @code{prob.ExtremeValueDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with specified parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.ExtremeValueDistribution (@var{mu}, ## @var{sigma})} to create an extreme value distribution with specified ## parameter values. ## @item Use the static method @qcode{prob.ExtremeValueDistribution.fit (@var{x}, ## @var{alpha}, @var{censor}, @var{freq}, @var{options})} to fit a ## distribution to the data in @var{x} using the same input arguments as the ## @code{evfit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the ## constructor and the aforementioned static method. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## @seealso{fitdist, makedist, evcdf, evinv, evpdf, evrnd, evfit, ## evlike, evstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.ExtremeValueDistribution} {property} mu ## ## Location parameter ## ## A scalar value characterizing the location of the ## extreme value distribution. You can access the @qcode{mu} ## property using dot name assignment. ## ## @end deftp mu ## -*- texinfo -*- ## @deftp {prob.ExtremeValueDistribution} {property} sigma ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the ## extreme value distribution. You can access the @qcode{sigma} ## property using dot name assignment. ## ## @end deftp sigma endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.ExtremeValueDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Extreme Value'; ## -*- texinfo -*- ## @deftp {prob.ExtremeValueDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.ExtremeValueDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'mu', 'sigma'}; ## -*- texinfo -*- ## @deftp {prob.ExtremeValueDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Location', 'Scale'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'ev'; ParameterRange = [-Inf, realmin; Inf, Inf]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public , SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.ExtremeValueDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{mu} and @qcode{sigma} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.ExtremeValueDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.ExtremeValueDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.ExtremeValueDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only meaningful ## when the distribution was fitted to data. If the distribution object was ## created with fixed parameters, or a parameter of a fitted distribution is ## modified, then all elements of the variance-covariance are zero. This ## property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.ExtremeValueDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.ExtremeValueDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {@var{pd} =} ExtremeValueDistribution (@var{mu}, @var{sigma}) ## @deftypefnx {prob.ExtremeValueDistribution} {@var{pd} =} ExtremeValueDistribution () ## ## Create a @code{prob.ExtremeValueDistribution} object. ## ## @var{mu} and @var{sigma} are the distribution parameters, which the class ## help describes. Called with no arguments the parameters take their ## defaults, @var{mu} 0 and @var{sigma} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = ExtremeValueDistribution (mu, sigma) if (nargin == 0) mu = 0; sigma = 1; endif checkparams (mu, sigma); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [mu, sigma]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'extreme value distribution'); endfunction function disp (this) __disp__ (this, 'extreme value distribution'); endfunction function this = set.mu (this, mu) checkparams (mu, this.sigma); this.InputData = []; this.ParameterValues(1) = mu; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function mu = get.mu (this) mu = this.ParameterValues(1); endfunction function this = set.sigma (this, sigma) checkparams (this.mu, sigma); this.InputData = []; this.ParameterValues(2) = sigma; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function sigma = get.sigma (this) sigma = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.ExtremeValueDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = evcdf (x, this.mu, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= evcdf (lx, this.mu, this.sigma); p(! (lb | ub)) /= diff (evcdf ([lx, ux], this.mu, this.sigma)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = evcdf (this.Truncation(1), this.mu, this.sigma); up = evcdf (this.Truncation(2), this.mu, this.sigma); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = evinv (np, this.mu, this.sigma); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = evinv (p, this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = evstat (this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = evcdf ([lx, ux], this.mu, this.sigma); m = evinv (sum (Fa_b) / 2, this.mu, this.sigma); else m = evinv (0.5, this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = evlike ([this.mu, this.sigma], this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.ExtremeValueDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the ## confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = evpdf (x, this.mu, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (evcdf ([lx, ux], this.mu, this.sigma)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.ExtremeValueDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.ExtremeValueDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.ExtremeValueDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.ExtremeValueDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.ExtremeValueDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.ExtremeValueDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.ExtremeValueDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the extreme value distribution, @qcode{@var{pnum} = 1} selects the ## parameter @qcode{mu} and @qcode{@var{pnum} = 2} selects the parameter ## @qcode{sigma}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.ExtremeValueDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.ExtremeValueDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.ExtremeValueDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{betarnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (evcdf ([lx, ux], this.mu, this.sigma)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = evrnd (this.mu, this.sigma, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = evrnd (this.mu, this.sigma, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, ## and upper limit, @var{upper}. If @var{pd} is fitted to data with ## @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.ExtremeValueDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = evstat (this.mu, this.sigma); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{4}; endif ## Fit data [phat, pci] = evfit (x, alpha, censor, freq, options); [~, acov] = evlike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.ExtremeValueDistribution.makeFitted ... (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) mu = phat(1); sigma = phat(2); pd = prob.ExtremeValueDistribution (mu, sigma); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (mu, sigma) if (! (isscalar (mu) && isnumeric (mu) && isreal (mu) && isfinite (mu))) error ("ExtremeValueDistribution: MU must be a real scalar.") endif if (! (isscalar (sigma) && isnumeric (sigma) && isreal (sigma) && isfinite (sigma) && sigma > 0)) error ("ExtremeValueDistribution: SIGMA must be a positive real scalar.") endif endfunction ## Test output %!shared pd, t %! pd = prob.ExtremeValueDistribution (0, 1); %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0.6321, 0.9340, 0.9994, 1, 1, 1], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 1, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.9887, 0.9994, 1, 1], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0, 1, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [-Inf, -1.4999, -0.6717, -0.0874, 0.4759, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.0298, 2.0668, 2.1169, 2.1971, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, -0.6717, -0.0874, 0.4759, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.0668, 2.1169, 2.1971, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 1.5725, 1e-4); %!assert_equal (iqr (t), 0.1338, 1e-4); %!assert_equal (mean (pd), -0.5772, 1e-4); %!assert_equal (mean (t), 2.1206, 1e-4); %!assert_equal (median (pd), -0.3665, 1e-4); %!assert_equal (median (t), 2.0897, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0.3679, 0.1794, 0.0046, 0, 0, 0], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 7.3891, 0.0001, 0, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0.2546, 0.1794, 0.0046, 0, 0, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 7.3891, 0.0001, 0, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 1.2825, 1e-4); %!assert_equal (std (t), 0.1091, 1e-4); %!assert_equal (var (pd), 1.6449, 1e-4); %!assert_equal (var (t), 0.0119, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [0.3; -1.2; 0.8; 1.5; -0.4; 0.2; -0.9; 1.1; 0.6; -0.3; ... %! 1.8; -1.5; 0.4; 0.9; -0.7; 1.2; -0.2; 0.5; -1.1; 0.7]; %! pd = fitdist (x, 'ExtremeValue'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 1]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.ExtremeValueDistribution' constructor %!error ... %! prob.ExtremeValueDistribution (Inf, 1) %!error ... %! prob.ExtremeValueDistribution (i, 1) %!error ... %! prob.ExtremeValueDistribution ('a', 1) %!error ... %! prob.ExtremeValueDistribution ([1, 2], 1) %!error ... %! prob.ExtremeValueDistribution (NaN, 1) %!error ... %! prob.ExtremeValueDistribution (1, 0) %!error ... %! prob.ExtremeValueDistribution (1, -1) %!error ... %! prob.ExtremeValueDistribution (1, Inf) %!error ... %! prob.ExtremeValueDistribution (1, i) %!error ... %! prob.ExtremeValueDistribution (1, 'a') %!error ... %! prob.ExtremeValueDistribution (1, [1, 2]) %!error ... %! prob.ExtremeValueDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.ExtremeValueDistribution, 2, 'uper') %!error ... %! cdf (prob.ExtremeValueDistribution, 2, 3) ## 'paramci' method %!shared x %! rand ('seed', 1); %! x = evrnd (1, 1, [1000, 1]); %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), ... %! 'parameter', 'mu', 'alpha', {0.05}) %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), ... %! 'parameter', {'mu', 'sigma', 'param'}) %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'mu', 'sigma', 'param'}) %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'param') %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.ExtremeValueDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'mu', 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.ExtremeValueDistribution, 'Parent') %!error ... %! plot (prob.ExtremeValueDistribution, 'PlotType', 12) %!error ... %! plot (prob.ExtremeValueDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.ExtremeValueDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.ExtremeValueDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.ExtremeValueDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.ExtremeValueDistribution, 'Discrete', {true}) %!error ... %! plot (prob.ExtremeValueDistribution, 'Parent', 12) %!error ... %! plot (prob.ExtremeValueDistribution, 'Parent', 'hax') %!error ... %! plot (prob.ExtremeValueDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.ExtremeValueDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.ExtremeValueDistribution, 2) %!error ... %! proflik (prob.ExtremeValueDistribution.fit (x), 3) %!error ... %! proflik (prob.ExtremeValueDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.ExtremeValueDistribution.fit (x), {1}) %!error ... %! proflik (prob.ExtremeValueDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.ExtremeValueDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.ExtremeValueDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.ExtremeValueDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.ExtremeValueDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.ExtremeValueDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.ExtremeValueDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.ExtremeValueDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.ExtremeValueDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.ExtremeValueDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.ExtremeValueDistribution) %!error ... %! truncate (prob.ExtremeValueDistribution, 2) %!error ... %! truncate (prob.ExtremeValueDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.ExtremeValueDistribution (1, 1); %! pd(2) = prob.ExtremeValueDistribution (1, 3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/GammaDistribution.m000066400000000000000000001202211524624707500272730ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef GammaDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.GammaDistribution ## ## Gamma probability distribution object. ## ## A @code{prob.GammaDistribution} object consists of parameters, a model ## description, and sample data for a gamma probability distribution. ## ## The gamma distribution is a continuous probability distribution that models ## the time to failure of a process. It is defined by shape parameter @var{a} ## and scale parameter @var{b}. ## ## There are several ways to create a @code{prob.GammaDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.GammaDistribution (@var{a}, @var{b})} ## to create a gamma distribution with fixed parameter values @var{a} and ## @var{b}. ## @item Use the static method @qcode{prob.GammaDistribution.fit (@var{x}, ## @var{alpha}, @var{censor}, @var{freq}, @var{options})} to fit a ## distribution to the data in @var{x} using the same input arguments as the ## @code{gamfit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the gamma distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gamma_distribution} ## ## @seealso{fitdist, makedist, gamcdf, gaminv, gampdf, gamrnd, gamfit, ## gamlike, gamstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.GammaDistribution} {property} a ## ## Shape parameter ## ## A positive scalar value characterizing the shape of the ## gamma distribution. You can access the @qcode{a} ## property using dot name assignment. ## ## @end deftp a ## -*- texinfo -*- ## @deftp {prob.GammaDistribution} {property} b ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the ## gamma distribution. You can access the @qcode{b} ## property using dot name assignment. ## ## @end deftp b endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.GammaDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Gamma'; ## -*- texinfo -*- ## @deftp {prob.GammaDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.GammaDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'a', 'b'}; ## -*- texinfo -*- ## @deftp {prob.GammaDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Shape', 'Scale'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'gam'; ParameterRange = [realmin, realmin; Inf, Inf]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.GammaDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{a} and @qcode{b} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.GammaDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.GammaDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.GammaDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only meaningful ## when the distribution was fitted to data. If the distribution object was ## created with fixed parameters, or a parameter of a fitted distribution is ## modified, then all elements of the variance-covariance are zero. This ## property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.GammaDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.GammaDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {@var{pd} =} GammaDistribution (@var{a}, @var{b}) ## @deftypefnx {prob.GammaDistribution} {@var{pd} =} GammaDistribution () ## ## Create a @code{prob.GammaDistribution} object. ## ## @var{a} and @var{b} are the distribution parameters, which the class help ## describes. Called with no arguments the parameters take their defaults, ## @var{a} 1 and @var{b} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = GammaDistribution (a, b) if (nargin == 0) a = 1; b = 1; endif checkparams (a, b); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [a, b]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'gamma distribution'); endfunction function disp (this) __disp__ (this, 'gamma distribution'); endfunction function this = set.a (this, a) checkparams (a, this.b); this.InputData = []; this.ParameterValues(1) = a; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function a = get.a (this) a = this.ParameterValues(1); endfunction function this = set.b (this, b) checkparams (this.a, b); this.InputData = []; this.ParameterValues(2) = b; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function b = get.b (this) b = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.GammaDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = gamcdf (x, this.a, this.b); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= gamcdf (lx, this.a, this.b); p(! (lb | ub)) /= diff (gamcdf ([lx, ux], this.a, this.b)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = gamcdf (this.Truncation(1), this.a, this.b); up = gamcdf (this.Truncation(2), this.a, this.b); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = gaminv (np, this.a, this.b); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = gaminv (p, this.a, this.b); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = gamstat (this.a, this.b); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = gamcdf ([lx, ux], this.a, this.b); m = gaminv (sum (Fa_b) / 2, this.a, this.b); else m = gaminv (0.5, this.a, this.b); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = gamlike ([this.a, this.b], this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.GammaDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the ## confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = gampdf (x, this.a, this.b); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (gamcdf ([lx, ux], this.a, this.b)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.GammaDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.GammaDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.GammaDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.GammaDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.GammaDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.GammaDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.GammaDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the gamma distribution, @qcode{@var{pnum} = 1} selects the parameter ## @qcode{a} and @qcode{@var{pnum} = 2} selects the parameter @qcode{b}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.GammaDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.GammaDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.GammaDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{gamrnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (gamcdf ([lx, ux], this.a, this.b)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = gamrnd (this.a, this.b, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = gamrnd (this.a, this.b, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, ## and upper limit, @var{upper}. If @var{pd} is fitted to data with ## @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GammaDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = gamstat (this.a, this.b); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{4}; endif ## Fit data [phat, pci] = gamfit (x, alpha, censor, freq, options); [~, acov] = gamlike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.GammaDistribution.makeFitted (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) a = phat(1); b = phat(2); pd = prob.GammaDistribution (a, b); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (a, b) if (! (isscalar (a) && isnumeric (a) && isreal (a) && isfinite (a) && a > 0)) error ("GammaDistribution: A must be a positive real scalar.") endif if (! (isscalar (b) && isnumeric (b) && isreal (b) && isfinite (b) && b > 0)) error ("GammaDistribution: B must be a positive real scalar.") endif endfunction ## Test output %!shared pd, t %! pd = prob.GammaDistribution (1, 1); %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.6321, 0.8647, 0.9502, 0.9817, 0.9933], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.7311, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.7769, 0.8647, 0.9502, 0.9817], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0, 0.7311, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0.2231, 0.5108, 0.9163, 1.6094, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.1899, 2.4244, 2.7315, 3.1768, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 0.5108, 0.9163, 1.6094, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.4244, 2.7315, 3.1768, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 1.0986, 1e-4); %!assert_equal (iqr (t), 0.8020, 1e-4); %!assert_equal (mean (pd), 1); %!assert_equal (mean (t), 2.6870, 1e-4); %!assert_equal (median (pd), 0.6931, 1e-4); %!assert_equal (median (t), 2.5662, 1e-4); %!assert_equal (pdf (pd, [0:5]), [1, 0.3679, 0.1353, 0.0498, 0.0183, 0.0067], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 1.1565, 0.4255, 0.1565, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0, 0.3679, 0.1353, 0.0498, 0.0183, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 1.1565, 0.4255, 0.1565, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 1); %!assert_equal (std (t), 0.5253, 1e-4); %!assert_equal (var (pd), 1); %!assert_equal (var (t), 0.2759, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [1.2; 0.4; 3.1; 0.7; 2.5; 1.8; 0.3; 4.2; 1.1; 0.9; ... %! 2.2; 0.6; 1.5; 3.7; 0.8; 2.9; 1.3; 0.5; 2.0; 1.6]; %! pd = fitdist (x, 'Gamma'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 1]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.GammaDistribution' constructor %!error ... %! prob.GammaDistribution (0, 1) %!error ... %! prob.GammaDistribution (Inf, 1) %!error ... %! prob.GammaDistribution (i, 1) %!error ... %! prob.GammaDistribution ('a', 1) %!error ... %! prob.GammaDistribution ([1, 2], 1) %!error ... %! prob.GammaDistribution (NaN, 1) %!error ... %! prob.GammaDistribution (1, 0) %!error ... %! prob.GammaDistribution (1, -1) %!error ... %! prob.GammaDistribution (1, Inf) %!error ... %! prob.GammaDistribution (1, i) %!error ... %! prob.GammaDistribution (1, 'a') %!error ... %! prob.GammaDistribution (1, [1, 2]) %!error ... %! prob.GammaDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.GammaDistribution, 2, 'uper') %!error ... %! cdf (prob.GammaDistribution, 2, 3) ## 'paramci' method %!shared x %! x = gamrnd (1, 1, [100, 1]); %!error ... %! paramci (prob.GammaDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.GammaDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.GammaDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.GammaDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.GammaDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.GammaDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.GammaDistribution.fit (x), 'parameter', 'a', 'alpha', {0.05}) %!error ... %! paramci (prob.GammaDistribution.fit (x), 'parameter', {'a', 'b', 'param'}) %!error ... %! paramci (prob.GammaDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'a', 'b', 'param'}) %!error ... %! paramci (prob.GammaDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.GammaDistribution.fit (x), 'alpha', 0.01, 'parameter', 'param') %!error ... %! paramci (prob.GammaDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.GammaDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.GammaDistribution.fit (x), 'alpha', 0.01, 'parameter', 'a', ... %! 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.GammaDistribution, 'Parent') %!error ... %! plot (prob.GammaDistribution, 'PlotType', 12) %!error ... %! plot (prob.GammaDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.GammaDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.GammaDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.GammaDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.GammaDistribution, 'Discrete', {true}) %!error ... %! plot (prob.GammaDistribution, 'Parent', 12) %!error ... %! plot (prob.GammaDistribution, 'Parent', 'hax') %!error ... %! plot (prob.GammaDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.GammaDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.GammaDistribution, 2) %!error ... %! proflik (prob.GammaDistribution.fit (x), 3) %!error ... %! proflik (prob.GammaDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.GammaDistribution.fit (x), {1}) %!error ... %! proflik (prob.GammaDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.GammaDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.GammaDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.GammaDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.GammaDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.GammaDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.GammaDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.GammaDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.GammaDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.GammaDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.GammaDistribution) %!error ... %! truncate (prob.GammaDistribution, 2) %!error ... %! truncate (prob.GammaDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.GammaDistribution (1, 1); %! pd(2) = prob.GammaDistribution (1, 3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!test %! ## negloglik returns the (positive) negative log-likelihood. %! xdat = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! pdfit = prob.GammaDistribution.fit (xdat'); %! assert_equal (negloglik (pdfit), -sum (log (pdf (pdfit, xdat'))), 1e-9); %! assert_equal (negloglik (pdfit) > 0, true); %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/GeneralizedExtremeValueDistribution.m000066400000000000000000001306541524624707500330440ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef GeneralizedExtremeValueDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.GeneralizedExtremeValueDistribution ## ## Generalized extreme value probability distribution object. ## ## A @code{prob.GeneralizedExtremeValueDistribution} object consists of parameters, ## a model description, and sample data for a generalized extreme value ## probability distribution. ## ## The generalized extreme value distribution is a continuous probability ## distribution that models extreme values. It is defined by shape parameter ## @var{k}, scale parameter @var{sigma}, and location parameter @var{mu}. ## ## There are several ways to create a ## @code{prob.GeneralizedExtremeValueDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.GeneralizedExtremeValueDistribution ## (@var{k}, @var{sigma}, @var{mu})} to create a generalized extreme value ## distribution with fixed parameter values @var{k}, @var{sigma}, and ## @var{mu}. ## @item Use the static method @qcode{prob.GeneralizedExtremeValueDistribution.fit ## (@var{x}, @var{alpha}, @var{freq}, @var{options})} to fit a distribution to ## the data in @var{x} using the same input arguments as the @code{gevfit} ## function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the generalized extreme value distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution} ## ## @seealso{fitdist, makedist, gevcdf, gevinv, gevpdf, gevrnd, gevfit, ## gevlike, gevstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.GeneralizedExtremeValueDistribution} {property} k ## ## Shape parameter ## ## A scalar value characterizing the shape of the generalized extreme value ## distribution. You can access the @qcode{k} property using dot name ## assignment. ## ## @end deftp k ## -*- texinfo -*- ## @deftp {prob.GeneralizedExtremeValueDistribution} {property} sigma ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the generalized ## extreme value distribution. You can access the @qcode{sigma} property ## using dot name assignment. ## ## @end deftp sigma ## -*- texinfo -*- ## @deftp {prob.GeneralizedExtremeValueDistribution} {property} mu ## ## Location parameter ## ## A scalar value characterizing the location of the generalized extreme ## value distribution. You can access the @qcode{mu} property using dot name ## assignment. ## ## @end deftp mu endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.GeneralizedExtremeValueDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Generalized Extreme Value'; ## -*- texinfo -*- ## @deftp {prob.GeneralizedExtremeValueDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 3; ## -*- texinfo -*- ## @deftp {prob.GeneralizedExtremeValueDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{3*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'k', 'sigma', 'mu'}; ## -*- texinfo -*- ## @deftp {prob.GeneralizedExtremeValueDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{3*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Shape', 'Scale', 'Location'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'gev'; ParameterRange = [-Inf, realmin, -Inf; Inf, Inf, Inf]; ParameterLogCI = [false, true, false]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.GeneralizedExtremeValueDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{3*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{k}, @qcode{sigma}, and ## @qcode{mu} properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.GeneralizedExtremeValueDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.GeneralizedExtremeValueDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.GeneralizedExtremeValueDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{3*3} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only meaningful ## when the distribution was fitted to data. If the distribution object was ## created with fixed parameters, or a parameter of a fitted distribution is ## modified, then all elements of the variance-covariance are zero. This ## property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.GeneralizedExtremeValueDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*3} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.GeneralizedExtremeValueDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {@var{pd} =} GeneralizedExtremeValueDistribution (@var{k}, @var{sigma}, @var{mu}) ## @deftypefnx {prob.GeneralizedExtremeValueDistribution} {@var{pd} =} GeneralizedExtremeValueDistribution () ## ## Create a @code{prob.GeneralizedExtremeValueDistribution} object. ## ## @var{k}, @var{sigma} and @var{mu} are the distribution parameters, which ## the class help describes. Called with no arguments the parameters take ## their defaults, @var{k} 0, @var{sigma} 1 and @var{mu} 0. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = GeneralizedExtremeValueDistribution (k, sigma, mu) if (nargin == 0) k = 0; sigma = 1; mu = 0; endif checkparams (k, sigma, mu); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [k, sigma, mu]; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Generalized Extreme Value distribution'); endfunction function disp (this) __disp__ (this, 'Generalized Extreme Value distribution'); endfunction function this = set.k (this, k) checkparams (k, this.sigma, this.mu); this.InputData = []; this.ParameterValues(1) = k; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function k = get.k (this) k = this.ParameterValues(1); endfunction function this = set.sigma (this, sigma) checkparams (this.k, sigma, this.mu); this.InputData = []; this.ParameterValues(2) = sigma; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function sigma = get.sigma (this) sigma = this.ParameterValues(2); endfunction function this = set.mu (this, mu) checkparams (this.k, this.sigma, mu); this.InputData = []; this.ParameterValues(3) = mu; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function mu = get.mu (this) mu = this.ParameterValues(3); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.GeneralizedExtremeValueDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = gevcdf (x, this.k, this.sigma, this.mu); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= gevcdf (lx, this.k, this.sigma, this.mu); p(! (lb | ub)) /= diff (gevcdf ([lx, ux], this.k, this.sigma, this.mu)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = gevcdf (this.Truncation(1), this.k, this.sigma, this.mu); up = gevcdf (this.Truncation(2), this.k, this.sigma, this.mu); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = gevinv (np, this.k, this.sigma, this.mu); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = gevinv (p, this.k, this.sigma, this.mu); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = gevstat (this.k, this.sigma, this.mu); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = gevcdf ([lx, ux], this.k, this.sigma, this.mu); m = gevinv (sum (Fa_b) / 2, this.k, this.sigma, this.mu); else m = gevinv (0.5, this.k, this.sigma, this.mu); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = gevlike ([this.k, this.sigma, this.mu], ... this.InputData.data, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.GeneralizedExtremeValueDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the ## confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = gevpdf (x, this.k, this.sigma, this.mu); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (gevcdf ([lx, ux], this.k, this.sigma, this.mu)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.GeneralizedExtremeValueDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.GeneralizedExtremeValueDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.GeneralizedExtremeValueDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.GeneralizedExtremeValueDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.GeneralizedExtremeValueDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.GeneralizedExtremeValueDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.GeneralizedExtremeValueDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the generalized extreme value distribution, @qcode{@var{pnum} = 1} ## selects the parameter @qcode{k}, @qcode{@var{pnum} = 2} selects the ## parameter @qcode{sigma}, and @qcode{@var{pnum} = 3} selects the ## parameter @qcode{mu}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.GeneralizedExtremeValueDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.GeneralizedExtremeValueDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.GeneralizedExtremeValueDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{gevrnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (gevcdf ([lx, ux], this.k, this.sigma, this.mu)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = gevrnd (this.k, this.sigma, this.mu, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = gevrnd (this.k, this.sigma, this.mu, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, ## and upper limit, @var{upper}. If @var{pd} is fitted to data with ## @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedExtremeValueDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = gevstat (this.k, this.sigma, this.mu); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) freq = []; else freq = varargin{2}; endif if (nargin < 4) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{3}; endif ## Fit data [phat, pci] = gevfit (x, alpha, freq, options); [~, acov] = gevlike (phat, x, freq); ## Create fitted distribution object pd = prob.GeneralizedExtremeValueDistribution.makeFitted ... (phat, pci, acov, x, freq); endfunction function pd = makeFitted (phat, pci, acov, x, freq) k = phat(1); sigma = phat(2); mu = phat(3); pd = prob.GeneralizedExtremeValueDistribution (k, sigma, mu); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', [], 'freq', freq); endfunction endmethods endclassdef function checkparams (k, sigma, mu) if (! (isscalar (k) && isnumeric (k) && isreal (k) && isfinite (k))) error ("GeneralizedExtremeValueDistribution: K must be a real scalar.") endif if (! (isscalar (sigma) && isnumeric (sigma) && isreal (sigma) && isfinite (sigma) && sigma > 0)) error (strcat ("GeneralizedExtremeValueDistribution: SIGMA must be", ... " a positive real scalar.")) endif if (! (isscalar (mu) && isnumeric (mu) && isreal (mu) && isfinite (mu))) error ("GeneralizedExtremeValueDistribution: MU must be a real scalar.") endif endfunction ## Test output %!shared pd, t %! pd = prob.GeneralizedExtremeValueDistribution; %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0.3679, 0.6922, 0.8734, 0.9514, 0.9819, 0.9933], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.7195, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.8, 0.8734, 0.9514, 0.9819], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0, 0.7195, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [-Inf, -0.4759, 0.0874, 0.6717, 1.4999, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.1999, 2.4433, 2.7568, 3.2028, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 0.0874, 0.6717, 1.4999, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.4433, 2.7568, 3.2028, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 1.5725, 1e-4); %!assert_equal (iqr (t), 0.8164, 1e-4); %!assert_equal (mean (pd), 0.5772, 1e-4); %!assert_equal (mean (t), 2.7043, 1e-4); %!assert_equal (median (pd), 0.3665, 1e-4); %!assert_equal (median (t), 2.5887, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0.3679, 0.2546, 0.1182, 0.0474, 0.0180, 0.0067], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 1.0902, 0.4369, 0.1659, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0.1794, 0.2546, 0.1182, 0.0474, 0.0180, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 1.0902, 0.4369, 0.1659, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 1.2825, 1e-4); %!assert_equal (std (t), 0.5289, 1e-4); %!assert_equal (var (pd), 1.6449, 1e-4); %!assert_equal (var (t), 0.2798, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [0.3; -1.2; 0.8; 1.5; -0.4; 0.2; -0.9; 1.1; 0.6; -0.3; ... %! 1.8; -1.5; 0.4; 0.9; -0.7; 1.2; -0.2; 0.5; -1.1; 0.7]; %! pd = fitdist (x, 'GeneralizedExtremeValue'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 2]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.GeneralizedExtremeValueDistribution' constructor %!error ... %! prob.GeneralizedExtremeValueDistribution (Inf, 1, 1) %!error ... %! prob.GeneralizedExtremeValueDistribution (i, 1, 1) %!error ... %! prob.GeneralizedExtremeValueDistribution ('a', 1, 1) %!error ... %! prob.GeneralizedExtremeValueDistribution ([1, 2], 1, 1) %!error ... %! prob.GeneralizedExtremeValueDistribution (NaN, 1, 1) %!error ... %! prob.GeneralizedExtremeValueDistribution (1, 0, 1) %!error ... %! prob.GeneralizedExtremeValueDistribution (1, -1, 1) %!error ... %! prob.GeneralizedExtremeValueDistribution (1, Inf, 1) %!error ... %! prob.GeneralizedExtremeValueDistribution (1, i, 1) %!error ... %! prob.GeneralizedExtremeValueDistribution (1, 'a', 1) %!error ... %! prob.GeneralizedExtremeValueDistribution (1, [1, 2], 1) %!error ... %! prob.GeneralizedExtremeValueDistribution (1, NaN, 1) %!error ... %! prob.GeneralizedExtremeValueDistribution (1, 1, Inf) %!error ... %! prob.GeneralizedExtremeValueDistribution (1, 1, i) %!error ... %! prob.GeneralizedExtremeValueDistribution (1, 1, 'a') %!error ... %! prob.GeneralizedExtremeValueDistribution (1, 1, [1, 2]) %!error ... %! prob.GeneralizedExtremeValueDistribution (1, 1, NaN) ## 'cdf' method %!error ... %! cdf (prob.GeneralizedExtremeValueDistribution, 2, 'uper') %!error ... %! cdf (prob.GeneralizedExtremeValueDistribution, 2, 3) ## 'paramci' method %!shared x %! x = gevrnd (1, 1, 1, [1, 100]); %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), ... %! 'parameter', 'sigma', 'alpha', {0.05}) %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), ... %! 'parameter', {'k', 'sigma', 'mu', 'param'}) %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'k', 'sigma', 'mu', 'param'}) %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'param') %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), 'alpha', 0.01, ... %! 'NAME', 'value') %!error ... %! paramci (prob.GeneralizedExtremeValueDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'sigma', 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.GeneralizedExtremeValueDistribution, 'Parent') %!error ... %! plot (prob.GeneralizedExtremeValueDistribution, 'PlotType', 12) %!error ... %! plot (prob.GeneralizedExtremeValueDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.GeneralizedExtremeValueDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.GeneralizedExtremeValueDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.GeneralizedExtremeValueDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.GeneralizedExtremeValueDistribution, 'Discrete', {true}) %!error ... %! plot (prob.GeneralizedExtremeValueDistribution, 'Parent', 12) %!error ... %! plot (prob.GeneralizedExtremeValueDistribution, 'Parent', 'hax') %!error ... %! plot (prob.GeneralizedExtremeValueDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.GeneralizedExtremeValueDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution, 2) %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution.fit (x), 4) %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution.fit (x), {1}) %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution.fit (x), 1, ... %! 'Display', ['on'; 'on']) %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.GeneralizedExtremeValueDistribution.fit (x), 1, {[1 2 3 4]}, ... %! 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.GeneralizedExtremeValueDistribution) %!error ... %! truncate (prob.GeneralizedExtremeValueDistribution, 2) %!error ... %! truncate (prob.GeneralizedExtremeValueDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.GeneralizedExtremeValueDistribution (1, 1, 1); %! pd(2) = prob.GeneralizedExtremeValueDistribution (1, 3, 1); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/GeneralizedParetoDistribution.m000066400000000000000000001302231524624707500316600ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef GeneralizedParetoDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.GeneralizedParetoDistribution ## ## Generalized Pareto probability distribution object. ## ## A @code{prob.GeneralizedParetoDistribution} object consists of parameters, a ## model description, and sample data for a Generalized Pareto probability ## distribution. ## ## The Generalized Pareto distribution is a continuous probability ## distribution that models the tail behavior of other distributions, commonly ## used for extreme value analysis. It is defined by shape parameter @var{k}, ## scale parameter @var{sigma}, and location parameter @var{theta}. ## ## There are several ways to create a @code{prob.GeneralizedParetoDistribution} ## object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.GeneralizedParetoDistribution (@var{k}, ## @var{sigma}, @var{theta})} to create a Generalized Pareto distribution with ## fixed parameter values @var{k}, @var{sigma}, and @var{theta}. ## @item Use the static method @qcode{prob.GeneralizedParetoDistribution.fit ## (@var{x}, @var{theta}, @var{alpha}, @var{freq}, @var{options})} to fit a ## distribution to the data in @var{x} using the same input arguments as the ## @code{gpfit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the Generalized Pareto distribution can be found ## at ## @url{https://en.wikipedia.org/wiki/Generalized_Pareto_distribution} ## ## @seealso{fitdist, makedist, gpcdf, gpinv, gppdf, gprnd, gpfit, ## gplike, gpstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.GeneralizedParetoDistribution} {property} k ## ## Shape parameter ## ## A scalar value characterizing the shape of the Generalized Pareto ## distribution. You can access the @qcode{k} property using dot name ## assignment. ## ## @end deftp k ## -*- texinfo -*- ## @deftp {prob.GeneralizedParetoDistribution} {property} sigma ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the Generalized ## Pareto distribution. You can access the @qcode{sigma} property using dot ## name assignment. ## ## @end deftp sigma ## -*- texinfo -*- ## @deftp {prob.GeneralizedParetoDistribution} {property} theta ## ## Location parameter ## ## A scalar value characterizing the location of the Generalized Pareto ## distribution. You can access the @qcode{theta} property using dot name ## assignment. ## ## @end deftp theta endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.GeneralizedParetoDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Generalized Pareto'; ## -*- texinfo -*- ## @deftp {prob.GeneralizedParetoDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 3; ## -*- texinfo -*- ## @deftp {prob.GeneralizedParetoDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{3*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'k', 'sigma', 'theta'}; ## -*- texinfo -*- ## @deftp {prob.GeneralizedParetoDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{3*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Shape', 'Scale', 'Location'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'gp'; ParameterRange = [-Inf, realmin, -Inf; Inf, Inf, Inf]; ParameterLogCI = [false, true, false]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.GeneralizedParetoDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{3*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{k}, @qcode{sigma}, and ## @qcode{theta} properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.GeneralizedParetoDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.GeneralizedParetoDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.GeneralizedParetoDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{3*3} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only meaningful ## when the distribution was fitted to data. If the distribution object was ## created with fixed parameters, or a parameter of a fitted distribution is ## modified, then all elements of the variance-covariance are zero. This ## property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.GeneralizedParetoDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*3} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.GeneralizedParetoDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {@var{pd} =} GeneralizedParetoDistribution (@var{k}, @var{sigma}, @var{theta}) ## @deftypefnx {prob.GeneralizedParetoDistribution} {@var{pd} =} GeneralizedParetoDistribution () ## ## Create a @code{prob.GeneralizedParetoDistribution} object. ## ## @var{k}, @var{sigma} and @var{theta} are the distribution parameters, ## which the class help describes. Called with no arguments the parameters ## take their defaults, @var{k} 1, @var{sigma} 1 and @var{theta} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = GeneralizedParetoDistribution (k, sigma, theta) if (nargin == 0) k = 1; sigma = 1; theta = 1; endif checkparams (k, sigma, theta); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [k, sigma, theta]; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Generalized Pareto distribution'); endfunction function disp (this) __disp__ (this, 'Generalized Pareto distribution'); endfunction function this = set.k (this, k) checkparams (k, this.sigma, this.theta); this.InputData = []; this.ParameterValues(1) = k; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function k = get.k (this) k = this.ParameterValues(1); endfunction function this = set.sigma (this, sigma) checkparams (this.k, sigma, this.theta); this.InputData = []; this.ParameterValues(2) = sigma; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function sigma = get.sigma (this) sigma = this.ParameterValues(2); endfunction function this = set.theta (this, theta) checkparams (this.k, this.sigma, theta); this.InputData = []; this.ParameterValues(3) = theta; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function theta = get.theta (this) theta = this.ParameterValues(3); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.GeneralizedParetoDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = gpcdf (x, this.k, this.sigma, this.theta); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= gpcdf (lx, this.k, this.sigma, this.theta); p(! (lb | ub)) /= diff (gpcdf ([lx, ux], this.k, this.sigma, this.theta)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = gpcdf (this.Truncation(1), this.k, this.sigma, this.theta); up = gpcdf (this.Truncation(2), this.k, this.sigma, this.theta); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = gpinv (np, this.k, this.sigma, this.theta); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = gpinv (p, this.k, this.sigma, this.theta); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = gpstat (this.k, this.sigma, this.theta); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = gpcdf ([lx, ux], this.k, this.sigma, this.theta); m = gpinv (sum (Fa_b) / 2, this.k, this.sigma, this.theta); else m = gpinv (0.5, this.k, this.sigma, this.theta); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif ## GPLIKE assumes a zero location, so shift the data by THETA. nlogL = gplike ([this.k, this.sigma], ... this.InputData.data - this.theta, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.GeneralizedParetoDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the ## confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = gppdf (x, this.k, this.sigma, this.theta); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (gpcdf ([lx, ux], this.k, this.sigma, this.theta)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.GeneralizedParetoDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.GeneralizedParetoDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.GeneralizedParetoDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.GeneralizedParetoDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.GeneralizedParetoDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.GeneralizedParetoDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.GeneralizedParetoDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the Generalized Pareto distribution, @qcode{@var{pnum} = 1} selects ## the parameter @qcode{k}, @qcode{@var{pnum} = 2} selects the parameter ## @qcode{sigma}, and @qcode{@var{pnum} = 3} selects the parameter ## @qcode{theta}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.GeneralizedParetoDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.GeneralizedParetoDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.GeneralizedParetoDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{random} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (gpcdf ([lx, ux], this.k, this.sigma, this.theta)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = gprnd (this.k, this.sigma, this.theta, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = gprnd (this.k, this.sigma, this.theta, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, ## and upper limit, @var{upper}. If @var{pd} is fitted to data with ## @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.GeneralizedParetoDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = gpstat (this.k, this.sigma, this.theta); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, theta, varargin) ## Check input arguments if (nargin < 3) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 4) freq = []; else freq = varargin{2}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{3}; endif ## Fit data. GPFIT assumes a zero location, as MATLAB's does, so the ## known THETA is shifted out of the data and put back into the estimates. [phat, pci] = gpfit (x - theta, alpha, options, freq); phat = [phat, theta]; pci = [pci, [theta; theta]]; ## GPLIKE covers the two estimated parameters; THETA is fixed, so the ## covariance carries a zero row and column for it, as MATLAB's does. [~, acov] = gplike (phat(1:2), x - theta, freq); acov = [acov, [0; 0]; 0, 0, 0]; ## Create fitted distribution object pd = prob.GeneralizedParetoDistribution.makeFitted (phat, pci, acov, x, freq); endfunction function pd = makeFitted (phat, pci, acov, x, freq) k = phat(1); sigma = phat(2); theta = phat(3); pd = prob.GeneralizedParetoDistribution (k, sigma, theta); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false, true]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', [], 'freq', freq); endfunction endmethods endclassdef function checkparams (k, sigma, theta) if (! (isscalar (k) && isnumeric (k) && isreal (k) && isfinite (k))) error ("GeneralizedParetoDistribution: K must be a real scalar.") endif if (! (isscalar (sigma) && isnumeric (sigma) && isreal (sigma) && isfinite (sigma) && sigma > 0)) error ("GeneralizedParetoDistribution: SIGMA must be a positive real scalar.") endif if (! (isscalar (theta) && isnumeric (theta) && isreal (theta) && isfinite (theta))) error ("GeneralizedParetoDistribution: THETA must be a real scalar.") endif endfunction ## Test output %!shared pd, t %! pd = prob.GeneralizedParetoDistribution (1, 1, 1); %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0, 0.5, 0.6667, 0.75, 0.8], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.6667, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.3333, 0.5, 0.6667, 0.75], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0, 0.6667, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [1, 1.25, 1.6667, 2.5, 5, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.2222, 2.5, 2.8571, 3.3333, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 1.6667, 2.5, 5, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.5, 2.8571, 3.3333, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 2.6667, 1e-4); %!assert_equal (iqr (t), 0.9143, 1e-4); %!assert_equal (mean (pd), Inf); %!assert_equal (mean (t), 2.7726, 1e-4); %!assert_equal (median (pd), 2); %!assert_equal (median (t), 2.6667, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0, 1, 0.25, 0.1111, 0.0625, 0.04], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 1, 0.4444, 0.25, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0, 1, 0.25, 0.1111, 0.0625, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 1, 0.4444, 0.25, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), Inf); %!assert_equal (std (t), 0.5592, 1e-4); %!assert_equal (var (pd), Inf); %!assert_equal (var (t), 0.3128, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [1.2; 0.4; 3.1; 0.7; 2.5; 1.8; 0.3; 4.2; 1.1; 0.9; ... %! 2.2; 0.6; 1.5; 3.7; 0.8; 2.9; 1.3; 0.5; 2.0; 1.6] - 0.3 + 1e-8; %! pd = fitdist (x, 'GeneralizedPareto', 'theta', 0); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 2]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.GeneralizedParetoDistribution' constructor %!error ... %! prob.GeneralizedParetoDistribution (Inf, 1, 1) %!error ... %! prob.GeneralizedParetoDistribution (i, 1, 1) %!error ... %! prob.GeneralizedParetoDistribution ('a', 1, 1) %!error ... %! prob.GeneralizedParetoDistribution ([1, 2], 1, 1) %!error ... %! prob.GeneralizedParetoDistribution (NaN, 1, 1) %!error ... %! prob.GeneralizedParetoDistribution (1, 0, 1) %!error ... %! prob.GeneralizedParetoDistribution (1, -1, 1) %!error ... %! prob.GeneralizedParetoDistribution (1, Inf, 1) %!error ... %! prob.GeneralizedParetoDistribution (1, i, 1) %!error ... %! prob.GeneralizedParetoDistribution (1, 'a', 1) %!error ... %! prob.GeneralizedParetoDistribution (1, [1, 2], 1) %!error ... %! prob.GeneralizedParetoDistribution (1, NaN, 1) %!error ... %! prob.GeneralizedParetoDistribution (1, 1, Inf) %!error ... %! prob.GeneralizedParetoDistribution (1, 1, i) %!error ... %! prob.GeneralizedParetoDistribution (1, 1, 'a') %!error ... %! prob.GeneralizedParetoDistribution (1, 1, [1, 2]) %!error ... %! prob.GeneralizedParetoDistribution (1, 1, NaN) ## 'cdf' method %!error ... %! cdf (prob.GeneralizedParetoDistribution, 2, 'uper') %!error ... %! cdf (prob.GeneralizedParetoDistribution, 2, 3) ## 'paramci' method %!shared x %! x = gprnd (1, 1, 1, [1, 100]); %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), 'alpha') %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), 'alpha', 0) %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), 'alpha', 1) %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), 'alpha', [0.5 2]) %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), 'alpha', '') %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), 'alpha', {0.05}) %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), ... %! 'parameter', 'sigma', 'alpha', {0.05}) %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), ... %! 'parameter', {'k', 'sigma', 'theta', 'param'}) %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), 'alpha', 0.01, ... %! 'parameter', {'k', 'sigma', 'theta', 'param'}) %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), 'parameter', 'param') %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), 'alpha', 0.01, ... %! 'parameter', 'param') %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), 'NAME', 'value') %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), 'alpha', 0.01, ... %! 'NAME', 'value') %!error ... %! paramci (prob.GeneralizedParetoDistribution.fit (x, 1), 'alpha', 0.01, ... %! 'parameter', 'sigma', 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.GeneralizedParetoDistribution, 'Parent') %!error ... %! plot (prob.GeneralizedParetoDistribution, 'PlotType', 12) %!error ... %! plot (prob.GeneralizedParetoDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.GeneralizedParetoDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.GeneralizedParetoDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.GeneralizedParetoDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.GeneralizedParetoDistribution, 'Discrete', {true}) %!error ... %! plot (prob.GeneralizedParetoDistribution, 'Parent', 12) %!error ... %! plot (prob.GeneralizedParetoDistribution, 'Parent', 'hax') %!error ... %! plot (prob.GeneralizedParetoDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.GeneralizedParetoDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.GeneralizedParetoDistribution, 2) %!error ... %! proflik (prob.GeneralizedParetoDistribution.fit (x, 1), 3) %!error ... %! proflik (prob.GeneralizedParetoDistribution.fit (x, 1), [1, 2]) %!error ... %! proflik (prob.GeneralizedParetoDistribution.fit (x, 1), {1}) %!error ... %! proflik (prob.GeneralizedParetoDistribution.fit (x, 1), 1, ones (2)) %!error ... %! proflik (prob.GeneralizedParetoDistribution.fit (x, 1), 1, 'Display') %!error ... %! proflik (prob.GeneralizedParetoDistribution.fit (x, 1), 1, 'Display', 1) %!error ... %! proflik (prob.GeneralizedParetoDistribution.fit (x, 1), 1, 'Display', {1}) %!error ... %! proflik (prob.GeneralizedParetoDistribution.fit (x, 1), 1, 'Display', {'on'}) %!error ... %! proflik (prob.GeneralizedParetoDistribution.fit (x, 1), 1, ... %! 'Display', ['on'; 'on']) %!error ... %! proflik (prob.GeneralizedParetoDistribution.fit (x, 1), 1, 'Display', 'onnn') %!error ... %! proflik (prob.GeneralizedParetoDistribution.fit (x, 1), 1, 'NAME', 'on') %!error ... %! proflik (prob.GeneralizedParetoDistribution.fit (x, 1), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.GeneralizedParetoDistribution.fit (x, 1), 1, {[1 2 3 4]}, ... %! 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.GeneralizedParetoDistribution) %!error ... %! truncate (prob.GeneralizedParetoDistribution, 2) %!error ... %! truncate (prob.GeneralizedParetoDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.GeneralizedParetoDistribution (1, 1, 1); %! pd(2) = prob.GeneralizedParetoDistribution (1, 3, 1); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/HalfNormalDistribution.m000066400000000000000000001207771524624707500303140ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef HalfNormalDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.HalfNormalDistribution ## ## Half-normal probability distribution object. ## ## A @code{prob.HalfNormalDistribution} object consists of parameters, a model ## description, and sample data for a half-normal probability distribution. ## ## The half-normal distribution is a continuous probability distribution that ## models the time to failure of materials subjected to cyclic loading. It is ## defined by location parameter @var{mu} and scale parameter @var{sigma}. ## ## There are several ways to create a @code{prob.HalfNormalDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.HalfNormalDistribution (@var{mu}, ## @var{sigma})} to create a half-normal distribution with fixed parameter ## values @var{mu} and @var{sigma}. ## @item Use the static method @qcode{prob.HalfNormalDistribution.fit (@var{x}, ## @var{mu}, @var{freq})} to fit a distribution to the data in @var{x} using ## the same input arguments as the @code{hnfit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the half-normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Half-normal_distribution} ## ## @seealso{fitdist, makedist, hncdf, hninv, hnpdf, hnrnd, hnfit, ## hnlike, hnstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.HalfNormalDistribution} {property} mu ## ## Location parameter ## ## A scalar value characterizing the location of the ## half-normal distribution. You can access the @qcode{mu} ## property using dot name assignment. ## ## @end deftp mu ## -*- texinfo -*- ## @deftp {prob.HalfNormalDistribution} {property} sigma ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the ## half-normal distribution. You can access the @qcode{sigma} ## property using dot name assignment. ## ## @end deftp sigma endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.HalfNormalDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Half Normal'; ## -*- texinfo -*- ## @deftp {prob.HalfNormalDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.HalfNormalDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'mu', 'sigma'}; ## -*- texinfo -*- ## @deftp {prob.HalfNormalDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Location', 'Scale'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'hn'; ParameterRange = [-Inf, realmin; Inf, Inf]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.HalfNormalDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{mu} and @qcode{sigma} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.HalfNormalDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.HalfNormalDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.HalfNormalDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only ## meaningful when the distribution was fitted to data. If the distribution ## object was created with fixed parameters, or a parameter of a fitted ## distribution is modified, then all elements of the variance-covariance ## are zero. This property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.HalfNormalDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.HalfNormalDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {@var{pd} =} HalfNormalDistribution (@var{mu}, @var{sigma}) ## @deftypefnx {prob.HalfNormalDistribution} {@var{pd} =} HalfNormalDistribution () ## ## Create a @code{prob.HalfNormalDistribution} object. ## ## @var{mu} and @var{sigma} are the distribution parameters, which the class ## help describes. Called with no arguments the parameters take their ## defaults, @var{mu} 0 and @var{sigma} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = HalfNormalDistribution (mu, sigma) if (nargin == 0) mu = 0; sigma = 1; endif checkparams (mu, sigma); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [mu, sigma]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Half-normal distribution'); endfunction function disp (this) __disp__ (this, 'Half-normal distribution'); endfunction function this = set.mu (this, mu) checkparams (mu, this.sigma); this.InputData = []; this.ParameterValues(1) = mu; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function mu = get.mu (this) mu = this.ParameterValues(1); endfunction function this = set.sigma (this, sigma) checkparams (this.mu, sigma); this.InputData = []; this.ParameterValues(2) = sigma; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function sigma = get.sigma (this) sigma = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.HalfNormalDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = hncdf (x, this.mu, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= hncdf (lx, this.mu, this.sigma); p(! (lb | ub)) /= diff (hncdf ([lx, ux], this.mu, this.sigma)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = hncdf (this.Truncation(1), this.mu, this.sigma); up = hncdf (this.Truncation(2), this.mu, this.sigma); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = hninv (np, this.mu, this.sigma); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = hninv (p, this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = hnstat (this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = hncdf ([lx, ux], this.mu, this.sigma); m = hninv (sum (Fa_b) / 2, this.mu, this.sigma); else m = hninv (0.5, this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = hnlike ([this.mu, this.sigma], ... this.InputData.data, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.HalfNormalDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = hnpdf (x, this.mu, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (hncdf ([lx, ux], this.mu, this.sigma)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.HalfNormalDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.HalfNormalDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.HalfNormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.HalfNormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.HalfNormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.HalfNormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.HalfNormalDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the Half-normal distribution, @qcode{@var{pnum} = 1} selects ## the parameter @qcode{mu} and @qcode{@var{pnum} = 2} selects the ## parameter @qcode{sigma}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.HalfNormalDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.HalfNormalDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.HalfNormalDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{hnrnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (hncdf ([lx, ux], this.mu, this.sigma)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = hnrnd (this.mu, this.sigma, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = hnrnd (this.mu, this.sigma, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. If @var{pd} is fitted to data ## with @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.HalfNormalDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = hnstat (this.mu, this.sigma); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, mu, varargin) ## Check input arguments if (nargin < 3) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 4) freq = []; else freq = varargin{2}; endif ## Fit data [phat, pci] = hnfit (x, mu, alpha, freq); [~, acov] = hnlike (phat, x, freq); ## Create fitted distribution object pd = prob.HalfNormalDistribution.makeFitted (phat, pci, acov, x, freq); endfunction function pd = makeFitted (phat, pci, acov, x, freq) mu = phat(1); sigma = phat(2); pd = prob.HalfNormalDistribution (mu, sigma); pd.ParameterCI = pci; pd.ParameterIsFixed = [true, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', [], 'freq', freq); endfunction endmethods endclassdef function checkparams (mu, sigma) if (! (isscalar (mu) && isnumeric (mu) && isreal (mu) && isfinite (mu))) error ("HalfNormalDistribution: MU must be a real scalar.") endif if (! (isscalar (sigma) && isnumeric (sigma) && isreal (sigma) && isfinite (sigma) && sigma > 0)) error ("HalfNormalDistribution: SIGMA must be a positive real scalar.") endif endfunction ## Test output %!shared pd, t %! pd = prob.HalfNormalDistribution (0, 1); %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.6827, 0.9545, 0.9973, 0.9999, 1], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.9420, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.8664, 0.9545, 0.9973, 0.9999], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0, 0.9420, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0.2533, 0.5244, 0.8416, 1.2816, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.0923, 2.2068, 2.3607, 2.6064, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 0.5244, 0.8416, 1.2816, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.2068, 2.3607, 2.6064, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 0.8317, 1e-4); %!assert_equal (iqr (t), 0.4111, 1e-4); %!assert_equal (mean (pd), 0.7979, 1e-4); %!assert_equal (mean (t), 2.3706, 1e-4); %!assert_equal (median (pd), 0.6745, 1e-4); %!assert_equal (median (t), 2.2771, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0.7979, 0.4839, 0.1080, 0.0089, 0.0003, 0], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 2.3765, 0.1951, 0.0059, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0, 0.4839, 0.1080, 0.0089, 0.0003, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 2.3765, 0.1951, 0.0059, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 0.6028, 1e-4); %!assert_equal (std (t), 0.3310, 1e-4); %!assert_equal (var (pd), 0.3634, 1e-4); %!assert_equal (var (t), 0.1096, 1e-4); %!test %! ## A fixed parameter is skipped by the default PNUM and reported in OTHER at %! ## its own value. Verified against MATLAB. %! x = [1.2; 0.4; 3.1; 0.7; 2.5; 1.8; 0.3; 4.2; 1.1; 0.9; ... %! 2.2; 0.6; 1.5; 3.7; 0.8; 2.9; 1.3; 0.5; 2.0; 1.6]; %! pdh = prob.HalfNormalDistribution.fit (x, 0); %! [nlogL, param, other] = proflik (pdh, 2); %! assert_equal (size (param), [1, 101]); %! assert_equal (size (other), [101, 1]); %! assert_equal (unique (other), 0); %! assert_equal (proflik (pdh), nlogL); ## Test input validation ## 'prob.HalfNormalDistribution' constructor %!error ... %! prob.HalfNormalDistribution (Inf, 1) %!error ... %! prob.HalfNormalDistribution (i, 1) %!error ... %! prob.HalfNormalDistribution ('a', 1) %!error ... %! prob.HalfNormalDistribution ([1, 2], 1) %!error ... %! prob.HalfNormalDistribution (NaN, 1) %!error ... %! prob.HalfNormalDistribution (1, 0) %!error ... %! prob.HalfNormalDistribution (1, -1) %!error ... %! prob.HalfNormalDistribution (1, Inf) %!error ... %! prob.HalfNormalDistribution (1, i) %!error ... %! prob.HalfNormalDistribution (1, 'a') %!error ... %! prob.HalfNormalDistribution (1, [1, 2]) %!error ... %! prob.HalfNormalDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.HalfNormalDistribution, 2, 'uper') %!error ... %! cdf (prob.HalfNormalDistribution, 2, 3) ## 'paramci' method %!shared x %! x = hnrnd (1, 1, [1, 100]); %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1), 'alpha') %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1), 'alpha', 0) %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1), 'alpha', 1) %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1), 'alpha', [0.5 2]) %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1), 'alpha', '') %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1), 'alpha', {0.05}) %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1), 'parameter', 'sigma', ... %! 'alpha', {0.05}) %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1), ... %! 'parameter', {'mu', 'sigma', 'param'}) %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1), 'alpha', 0.01, ... %! 'parameter', {'mu', 'sigma', 'param'}) %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1), 'parameter', 'param') %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1), 'alpha', 0.01, ... %! 'parameter', 'param') %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1),'NAME', 'value') %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1), 'alpha', 0.01, ... %! 'NAME', 'value') %!error ... %! paramci (prob.HalfNormalDistribution.fit (x, 1), 'alpha', 0.01, ... %! 'parameter', 'sigma', 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.HalfNormalDistribution, 'Parent') %!error ... %! plot (prob.HalfNormalDistribution, 'PlotType', 12) %!error ... %! plot (prob.HalfNormalDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.HalfNormalDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.HalfNormalDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.HalfNormalDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.HalfNormalDistribution, 'Discrete', {true}) %!error ... %! plot (prob.HalfNormalDistribution, 'Parent', 12) %!error ... %! plot (prob.HalfNormalDistribution, 'Parent', 'hax') %!error ... %! plot (prob.HalfNormalDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.HalfNormalDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.HalfNormalDistribution, 2) %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), 3) %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), [1, 2]) %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), {1}) %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), 1) %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), 2, ones (2)) %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), 2, 'Display') %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), 2, 'Display', 1) %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), 2, 'Display', {1}) %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), 2, 'Display', {'on'}) %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), 2, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), 2, 'Display', 'onnn') %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), 2, 'NAME', 'on') %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), 2, {'NAME'}, 'on') %!error ... %! proflik (prob.HalfNormalDistribution.fit (x, 1), 2, {[1 2 3 4]}, ... %! 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.HalfNormalDistribution) %!error ... %! truncate (prob.HalfNormalDistribution, 2) %!error ... %! truncate (prob.HalfNormalDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.HalfNormalDistribution (1, 1); %! pd(2) = prob.HalfNormalDistribution (1, 3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/InverseGaussianDistribution.m000066400000000000000000001233161524624707500313670ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef InverseGaussianDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.InverseGaussianDistribution ## ## Inverse Gaussian probability distribution object. ## ## A @code{prob.InverseGaussianDistribution} object consists of parameters, a ## model description, and sample data for a Inverse Gaussian probability ## distribution. ## ## The Inverse Gaussian distribution is a continuous probability distribution, ## which is often used to model non-negative positively skewed data. Is is ## defined by mean parameter @var{mu} and shape parameter @var{lambda}. ## ## There are several ways to create a @code{prob.InverseGaussianDistribution} ## object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.InverseGaussianDistribution (@var{mu}, ## @var{lambda})} to create a Inverse Gaussian distribution with fixed ## parameter values @var{mu} and @var{lambda}. ## @item Use the static method @qcode{prob.InverseGaussianDistribution.fit ## (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options})} to fit a ## distribution to the data in @var{x} using the same input arguments as the ## @code{invgfit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the Inverse Gaussian distribution can be found at ## @url{https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution} ## ## @seealso{fitdist, makedist, invgcdf, invginv, invgpdf, invgrnd, invgfit, ## invglike, invgstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.InverseGaussianDistribution} {property} mu ## ## Mean parameter ## ## A positive scalar value characterizing the mean of the ## Inverse Gaussian distribution. You can access the @qcode{mu} ## property using dot name assignment. ## ## @end deftp mu ## -*- texinfo -*- ## @deftp {prob.InverseGaussianDistribution} {property} lambda ## ## Shape parameter ## ## A positive scalar value characterizing the shape of the ## Inverse Gaussian distribution. You can access the @qcode{lambda} ## property using dot name assignment. ## ## @end deftp lambda endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.InverseGaussianDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Inverse Gaussian'; ## -*- texinfo -*- ## @deftp {prob.InverseGaussianDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.InverseGaussianDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'mu', 'lambda'}; ## -*- texinfo -*- ## @deftp {prob.InverseGaussianDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Mean', 'Shape'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'invg'; ParameterRange = [realmin, realmin; Inf, Inf]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.InverseGaussianDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{mu} and @qcode{lambda} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.InverseGaussianDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.InverseGaussianDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.InverseGaussianDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only meaningful ## when the distribution was fitted to data. If the distribution object was ## created with fixed parameters, or a parameter of a fitted distribution is ## modified, then all elements of the variance-covariance are zero. This ## property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.InverseGaussianDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.InverseGaussianDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {@var{pd} =} InverseGaussianDistribution (@var{mu}, @var{lambda}) ## @deftypefnx {prob.InverseGaussianDistribution} {@var{pd} =} InverseGaussianDistribution () ## ## Create a @code{prob.InverseGaussianDistribution} object. ## ## @var{mu} and @var{lambda} are the distribution parameters, which the ## class help describes. Called with no arguments the parameters take their ## defaults, @var{mu} 1 and @var{lambda} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = InverseGaussianDistribution (mu, lambda) if (nargin == 0) mu = 1; lambda = 1; endif checkparams (mu, lambda); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [mu, lambda]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Inverse Gaussian distribution'); endfunction function disp (this) __disp__ (this, 'Inverse Gaussian distribution'); endfunction function this = set.mu (this, mu) checkparams (mu, this.lambda); this.InputData = []; this.ParameterValues(1) = mu; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function mu = get.mu (this) mu = this.ParameterValues(1); endfunction function this = set.lambda (this, lambda) checkparams (this.mu, lambda); this.InputData = []; this.ParameterValues(2) = lambda; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function lambda = get.lambda (this) lambda = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.InverseGaussianDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = invgcdf (x, this.mu, this.lambda); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= invgcdf (lx, this.mu, this.lambda); p(! (lb | ub)) /= diff (invgcdf ([lx, ux], this.mu, this.lambda)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = invgcdf (this.Truncation(1), this.mu, this.lambda); up = invgcdf (this.Truncation(2), this.mu, this.lambda); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = invginv (np, this.mu, this.lambda); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = invginv (p, this.mu, this.lambda); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = invgstat (this.mu, this.lambda); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = invgcdf ([lx, ux], this.mu, this.lambda); m = invginv (sum (Fa_b) / 2, this.mu, this.lambda); else m = invginv (0.5, this.mu, this.lambda); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = invglike ([this.mu, this.lambda], this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.InverseGaussianDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the ## confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = invgpdf (x, this.mu, this.lambda); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (invgcdf ([lx, ux], this.mu, this.lambda)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.InverseGaussianDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.InverseGaussianDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.InverseGaussianDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.InverseGaussianDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.InverseGaussianDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.InverseGaussianDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.InverseGaussianDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the Inverse Gaussian distribution, @qcode{@var{pnum} = 1} selects ## the parameter @qcode{mu} and @qcode{@var{pnum} = 2} selects the ## parameter @qcode{lambda}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.InverseGaussianDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.InverseGaussianDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.InverseGaussianDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{invgrnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (invgcdf ([lx, ux], this.mu, this.lambda)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = invgrnd (this.mu, this.lambda, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = invgrnd (this.mu, this.lambda, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, ## and upper limit, @var{upper}. If @var{pd} is fitted to data with ## @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.InverseGaussianDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = invgstat (this.mu, this.lambda); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{4}; endif ## Fit data [phat, pci] = invgfit (x, alpha, censor, freq, options); [~, acov] = invglike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.InverseGaussianDistribution.makeFitted ... (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) mu = phat(1); lambda = phat(2); pd = prob.InverseGaussianDistribution (mu, lambda); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (mu, lambda) if (! (isscalar (mu) && isnumeric (mu) && isreal (mu) && isfinite (mu) && mu > 0)) error ("InverseGaussianDistribution: MU must be a positive real scalar.") endif if (! (isscalar (lambda) && isnumeric (lambda) && isreal (lambda) && isfinite (lambda) && lambda > 0)) error ("InverseGaussianDistribution: LAMBDA must be a positive real scalar.") endif endfunction ## Test output %!shared pd, t %! pd = prob.InverseGaussianDistribution (1, 1); %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.6681, 0.8855, 0.9532, 0.9791, 0.9901], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.7234, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.8108, 0.8855, 0.9532, 0.9791], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0, 0.7234, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0.3320, 0.5411, 0.8483, 1.4479, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.1889, 2.4264, 2.7417, 3.1993, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 0.5411, 0.8483, 1.4479, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.4264, 2.7417, 3.1993, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 0.8643, 1e-4); %!assert_equal (iqr (t), 0.8222, 1e-4); %!assert_equal (mean (pd), 1); %!assert_equal (mean (t), 2.6953, 1e-4); %!assert_equal (median (pd), 0.6758, 1e-4); %!assert_equal (median (t), 2.5716, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0, 0.3989, 0.1098, 0.0394, 0.0162, 0.0072], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 1.1736, 0.4211, 0.1730, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0, 0.3989, 0.1098, 0.0394, 0.0162, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 1.1736, 0.4211, 0.1730, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 1); %!assert_equal (std (t), 0.5332, 1e-4); %!assert_equal (var (pd), 1); %!assert_equal (var (t), 0.2843, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [1.2; 0.4; 3.1; 0.7; 2.5; 1.8; 0.3; 4.2; 1.1; 0.9; ... %! 2.2; 0.6; 1.5; 3.7; 0.8; 2.9; 1.3; 0.5; 2.0; 1.6]; %! pd = fitdist (x, 'InverseGaussian'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 1]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.InverseGaussianDistribution' constructor %!error ... %! prob.InverseGaussianDistribution (0, 1) %!error ... %! prob.InverseGaussianDistribution (Inf, 1) %!error ... %! prob.InverseGaussianDistribution (i, 1) %!error ... %! prob.InverseGaussianDistribution ('a', 1) %!error ... %! prob.InverseGaussianDistribution ([1, 2], 1) %!error ... %! prob.InverseGaussianDistribution (NaN, 1) %!error ... %! prob.InverseGaussianDistribution (1, 0) %!error ... %! prob.InverseGaussianDistribution (1, -1) %!error ... %! prob.InverseGaussianDistribution (1, Inf) %!error ... %! prob.InverseGaussianDistribution (1, i) %!error ... %! prob.InverseGaussianDistribution (1, 'a') %!error ... %! prob.InverseGaussianDistribution (1, [1, 2]) %!error ... %! prob.InverseGaussianDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.InverseGaussianDistribution, 2, 'uper') %!error ... %! cdf (prob.InverseGaussianDistribution, 2, 3) ## 'paramci' method %!shared x %! x = invgrnd (1, 1, [1, 100]); %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), 'parameter', 'mu', ... %! 'alpha', {0.05}) %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), ... %! 'parameter', {'mu', 'lambda', 'param'}) %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'mu', 'lambda', 'param'}) %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'param') %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.InverseGaussianDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'mu', 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.InverseGaussianDistribution, 'Parent') %!error ... %! plot (prob.InverseGaussianDistribution, 'PlotType', 12) %!error ... %! plot (prob.InverseGaussianDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.InverseGaussianDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.InverseGaussianDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.InverseGaussianDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.InverseGaussianDistribution, 'Discrete', {true}) %!error ... %! plot (prob.InverseGaussianDistribution, 'Parent', 12) %!error ... %! plot (prob.InverseGaussianDistribution, 'Parent', 'hax') %!error ... %! plot (prob.InverseGaussianDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.InverseGaussianDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.InverseGaussianDistribution, 2) %!error ... %! proflik (prob.InverseGaussianDistribution.fit (x), 3) %!error ... %! proflik (prob.InverseGaussianDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.InverseGaussianDistribution.fit (x), {1}) %!error ... %! proflik (prob.InverseGaussianDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.InverseGaussianDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.InverseGaussianDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.InverseGaussianDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.InverseGaussianDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.InverseGaussianDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.InverseGaussianDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.InverseGaussianDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.InverseGaussianDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.InverseGaussianDistribution.fit (x), 1, {[1 2 3]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.InverseGaussianDistribution) %!error ... %! truncate (prob.InverseGaussianDistribution, 2) %!error ... %! truncate (prob.InverseGaussianDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.InverseGaussianDistribution (1, 1); %! pd(2) = prob.InverseGaussianDistribution (1, 3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/KernelDistribution.m000066400000000000000000000734311524624707500275030ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef KernelDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.KernelDistribution ## ## Kernel probability distribution object. ## ## A @code{prob.KernelDistribution} object consists of a nonparametric kernel ## smoothing density estimate fitted to sample data, together with a model ## description. Unlike the parametric distribution objects, it has no ## estimated parameters; the fitted distribution is defined entirely by the ## data, the smoothing kernel, and the bandwidth. ## ## A @code{prob.KernelDistribution} object can only be created by fitting a kernel ## smoothing distribution to data with the @code{fitdist} function. Unlike ## the parametric distributions, it cannot be created with the @code{makedist} ## function, since it is not parametric and requires data. ## ## Further information about the kernel density estimation can be found at ## @url{https://en.wikipedia.org/wiki/Kernel_density_estimation} ## ## @seealso{fitdist, ksdensity, mvksdensity} ## @end deftp properties (GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.KernelDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Kernel'; endproperties properties (Dependent = true) ## -*- texinfo -*- ## @deftp {prob.KernelDistribution} {property} Kernel ## ## Kernel smoothing function ## ## A character vector specifying the type of smoothing kernel used for the ## density estimate. It is one of @qcode{'normal'}, @qcode{'box'}, ## @qcode{'triangle'}, or @qcode{'epanechnikov'}. You can access the ## @qcode{Kernel} property using dot name assignment. ## ## @end deftp Kernel ## -*- texinfo -*- ## @deftp {prob.KernelDistribution} {property} Bandwidth ## ## Bandwidth of the smoothing kernel ## ## A positive scalar value specifying the bandwidth of the smoothing kernel. ## You can access the @qcode{Bandwidth} property using dot name assignment. ## ## @end deftp Bandwidth endproperties properties (GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'kernel'; NumParameters = 0; ParameterNames = {}; ParameterDescription = {}; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.KernelDistribution} {property} Support ## ## Support of the probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{range}: either the character vector @qcode{'unbounded'} or ## @qcode{'positive'}, or a two-element numeric vector @math{[L, U]} with the ## lower and upper bounds of the support. ## @item @qcode{closedbound}: a two-element logical vector specifying whether ## each bound is closed. ## @item @qcode{iscontinuous}: a logical scalar, always @qcode{true} for a ## kernel distribution. ## @end itemize ## ## This property is read-only. ## ## @end deftp Support ## -*- texinfo -*- ## @deftp {prob.KernelDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.KernelDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.KernelDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: an empty array, since censoring is not supported for ## a kernel distribution. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties (GetAccess = public, SetAccess = protected, Hidden) KernelName BandwidthValue endproperties methods (Hidden) ## -*- texinfo -*- ## @deftypefn {prob.KernelDistribution} {@var{pd} =} KernelDistribution (@var{data}, @var{kernel}, @var{bw}, @var{support}, @var{freq}) ## @deftypefnx {prob.KernelDistribution} {@var{pd} =} KernelDistribution () ## ## Create a @code{prob.KernelDistribution} object. ## ## @var{data}, @var{kernel}, @var{bw}, @var{support} and @var{freq} are the ## distribution parameters, which the class help describes. Called with no ## arguments it fits the data @code{[0; 1]} with a normal kernel over an ## unbounded support, taking the bandwidth from the data. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = KernelDistribution (data, kernel, bw, support, freq) if (nargin == 0) data = [0; 1]; kernel = 'normal'; support = make_support ('unbounded'); freq = []; bw = default_bandwidth (data, kernel, support, freq); endif this.KernelName = kernel; this.BandwidthValue = bw; this.Support = support; this.IsTruncated = false; this.Truncation = []; this.InputData = struct ('data', data(:), 'cens', [], 'freq', freq); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'kernel distribution'); endfunction function disp (this) __disp__ (this, 'kernel distribution'); endfunction function this = set.Kernel (this, kernel) if (! (ischar (kernel) && isrow (kernel) && any (strcmpi (kernel, ... {'normal', 'box', 'triangle', 'epanechnikov'})))) error (strcat ("KernelDistribution: 'Kernel' must be 'normal',", ... " 'box', 'triangle', or 'epanechnikov'.")); endif this.KernelName = lower (kernel); endfunction function kernel = get.Kernel (this) kernel = this.KernelName; endfunction function this = set.Bandwidth (this, bw) if (! (isnumeric (bw) && isscalar (bw) && isreal (bw) && bw > 0)) error ("KernelDistribution: 'Bandwidth' must be a positive scalar."); endif this.BandwidthValue = bw; endfunction function bw = get.Bandwidth (this) bw = this.BandwidthValue; endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {prob.KernelDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.KernelDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @var{x} must be @qcode{double} or @qcode{single}; integer, logical, and ## character arrays are rejected. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif if (! isfloat (x)) error ("cdf: X must be double or single."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = basecdf (this, x); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; Fab = basecdf (this, [lx, ux]); p(! (lb | ub)) -= Fab(1); p(! (lb | ub)) /= diff (Fab); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.KernelDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @var{p} must be @qcode{double} or @qcode{single}; integer, logical, and ## character arrays are rejected. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (! isfloat (p)) error ("icdf: P must be double or single."); endif if (this.IsTruncated) Fab = basecdf (this, this.Truncation); lp = Fab(1); up = Fab(2); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = baseicdf (this, np); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = baseicdf (this, p); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.KernelDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.KernelDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated || ! is_unbounded (this)) [lb, ub] = integration_limits (this); fm = @(x) x .* pdf (this, x); m = integral (fm, lb, ub, 'ArrayValued', 1); else w = weights (this); m = sum (w .* this.InputData.data(:)'); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.KernelDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif m = icdf (this, 0.5); endfunction ## -*- texinfo -*- ## @deftypefn {prob.KernelDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif data = this.InputData.data(:); f = pdf (this, data); if (isempty (this.InputData.freq)) nlogL = - sum (log (f)); else nlogL = - sum (this.InputData.freq(:) .* log (f)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.KernelDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability density function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @var{x} must be @qcode{double} or @qcode{single}; integer, logical, and ## character arrays are rejected. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif if (! isfloat (x)) error ("pdf: X must be double or single."); endif y = basepdf (this, x); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (basecdf (this, [lx, ux])); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.KernelDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.KernelDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.KernelDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}, superimposed over a histogram ## of the data used to fit it. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.82 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF) ## superimposed on a histogram of the data. @qcode{'cdf'} plots the ## cumulative distribution function (CDF) superimposed over an empirical ## CDF. @qcode{'probability'} plots a probability plot using a CDF of the ## data and a CDF of the fitted probability distribution. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If not ## specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.KernelDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.KernelDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.KernelDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.KernelDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{random} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif u = unifrnd (0, 1, varargin{:}); r = icdf (this, u); endfunction ## -*- texinfo -*- ## @deftypefn {prob.KernelDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif s = sqrt (var (this)); endfunction ## -*- texinfo -*- ## @deftypefn {prob.KernelDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; endfunction ## -*- texinfo -*- ## @deftypefn {prob.KernelDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated || ! is_unbounded (this)) [lb, ub] = integration_limits (this); m = mean (this); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, lb, ub, 'ArrayValued', 1); else w = weights (this); data = this.InputData.data(:)'; mu = sum (w .* data); v = sum (w .* (data - mu) .^ 2) + this.Bandwidth ^ 2; endif endfunction endmethods methods (Access = private) ## Untruncated PDF, delegating to the shipped ksdensity engine. The query ## values are flattened to a vector for ksdensity and reshaped back, since ## ksdensity only accepts vector query points. function y = basepdf (this, x) ## ksdensity reads an empty query as "no points given" and falls back to ## its default grid, so short-circuit before it is called. if (isempty (x)) y = zeros (size (x)); return; endif y = reshape (ksdensity (this.InputData.data, x(:), ksargs (this){:}, ... 'Function', 'pdf'), size (x)); endfunction ## Untruncated CDF, delegating to the shipped ksdensity engine. function p = basecdf (this, x) if (isempty (x)) p = zeros (size (x)); return; endif p = reshape (ksdensity (this.InputData.data, x(:), ksargs (this){:}, ... 'Function', 'cdf'), size (x)); endfunction ## Untruncated inverse CDF, delegating to the shipped ksdensity engine. function x = baseicdf (this, p) if (isempty (p)) x = zeros (size (p)); return; endif x = reshape (ksdensity (this.InputData.data, p(:), ksargs (this){:}, ... 'Function', 'icdf'), size (p)); endfunction ## Common Name-Value arguments forwarded to ksdensity. function args = ksargs (this) args = {'Kernel', this.Kernel, 'Bandwidth', this.Bandwidth, ... 'Support', support_arg(this.Support)}; if (! isempty (this.InputData.freq)) args = [args, {'Weights', this.InputData.freq}]; endif endfunction ## Normalised weights of the sample points. function w = weights (this) n = numel (this.InputData.data); if (isempty (this.InputData.freq)) w = ones (1, n) / n; else f = this.InputData.freq(:)'; w = f / sum (f); endif endfunction ## Finite integration limits for numeric moment computation. function [lb, ub] = integration_limits (this) if (this.IsTruncated) lb = this.Truncation(1); ub = this.Truncation(2); else lb = baseicdf (this, 1e-10); ub = baseicdf (this, 1 - 1e-10); endif endfunction ## True for an unbounded (whole real line) support. function tf = is_unbounded (this) tf = ischar (this.Support.range) ... && strcmp (this.Support.range, 'unbounded'); endfunction endmethods methods (Static, Hidden) function pd = fit (x, kernel, support, width, freq) if (nargin < 2 || isempty (kernel)) kernel = 'normal'; endif if (nargin < 3 || isempty (support)) support = 'unbounded'; endif if (nargin < 4) width = []; endif if (nargin < 5) freq = []; endif ## A trivial (all-ones) frequency vector is treated as no weighting if (! isempty (freq) && all (freq(:) == 1)) freq = []; endif ## Validate the kernel if (! (ischar (kernel) && isrow (kernel) && any (strcmpi (kernel, ... {'normal', 'box', 'triangle', 'epanechnikov'})))) error (strcat ("KernelDistribution: 'Kernel' must be 'normal',", ... " 'box', 'triangle', or 'epanechnikov'.")); endif kernel = lower (kernel); S = make_support (support); ## Bandwidth: use the supplied value or the ksdensity default rule if (isempty (width)) bw = default_bandwidth (x, kernel, S, freq); elseif (isnumeric (width) && isscalar (width) && isreal (width) && width > 0) bw = width; else error ("KernelDistribution: 'Width' must be a positive scalar."); endif ## Create fitted distribution object pd = prob.KernelDistribution (x, kernel, bw, S, freq); endfunction endmethods endclassdef ## Build the Support structure from a keyword or a two-element vector. function S = make_support (support) if (ischar (support) && isrow (support)) switch (lower (support)) case 'unbounded' S = struct ('range', 'unbounded', 'closedbound', ... [false, false], 'iscontinuous', true); case 'positive' S = struct ('range', 'positive', 'closedbound', ... [false, false], 'iscontinuous', true); otherwise error (strcat ("KernelDistribution: 'Support' must be", ... " 'unbounded', 'positive', or [L U].")); endswitch elseif (isnumeric (support) && isreal (support) && numel (support) == 2 && support(1) < support(2)) S = struct ('range', [support(1), support(2)], 'closedbound', ... [false, false], 'iscontinuous', true); else error (strcat ("KernelDistribution: 'Support' must be", ... " 'unbounded', 'positive', or [L U].")); endif endfunction ## Convert a Support structure to the argument expected by ksdensity. function arg = support_arg (S) if (ischar (S.range)) arg = S.range; else arg = S.range; endif endfunction ## Default bandwidth via the ksdensity normal-reference rule. function bw = default_bandwidth (x, kernel, S, freq) args = {'Kernel', kernel, 'Support', support_arg(S)}; if (! isempty (freq)) args = [args, {'Weights', freq}]; endif [~, ~, bw] = ksdensity (x, args{:}); endfunction %!demo %! ## Fit a kernel distribution to a sample and plot its PDF over a histogram. %! load patients %! pd = fitdist (Weight, 'Kernel'); %! plot (pd) %! title ('Kernel distribution fitted to patient weights') ## Test output %!shared x, pd, pdbox, t %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]'; %! pd = fitdist (x, 'Kernel'); %! pdbox = fitdist (x, 'Kernel', 'Kernel', 'box', 'Width', 0.5); %! t = truncate (pd, 0, 3); %!assert_equal (pd.DistributionName, 'Kernel'); %!assert_equal (pd.Kernel, 'normal'); %!assert_equal (pd.NumParameters, 0); %!assert_equal (pd.ParameterNames, {}); %!assert_equal (pd.IsTruncated, false); %!assert_equal (pd.Bandwidth, 0.639566, 1e-4); %!assert_equal (pd.Support.range, 'unbounded'); %!assert_equal (pdf (pd, [-1 0 0.5 1 1.5 2 2.5 3]), ... %! [0.0589 0.2141 0.3051 0.3394 0.3061 0.2375 0.1697 0.1166], 2e-3); %!assert_equal (cdf (pd, [-1 0 0.5 1 1.5 2 2.5 3]), ... %! [0.0272 0.1538 0.2850 0.4492 0.6128 0.7494 0.8506 0.9217], 2e-3); %!assert_equal (icdf (pd, [0.1 0.25 0.5 0.75 0.9]), ... %! [-0.2909 0.3820 1.1504 2.0027 2.8265], 5e-3); %!assert_equal (size (pdf (pd, [])), [0, 0]) %!error pdf (pd, int32 (1)) %!error pdf (pd, true) %!error pdf (pd, 'a') %!error cdf (pd, int32 (1)) %!error icdf (pd, int32 (1)) %!assert_equal (size (cdf (pd, [])), [0, 0]) %!assert_equal (size (icdf (pd, [])), [0, 0]) %!assert_equal (size (random (pd, -1)), [0, 0]) %!assert_equal (size (random (pd, 2, -1, 5)), [2, 0, 5]) %!assert_equal (mean (pd), 1.2067, 1e-4); %!assert_equal (std (pd), 1.1918, 1e-3); %!assert_equal (var (pd), 1.4203, 1e-3); %!assert_equal (median (pd), 1.1504, 5e-3); %!assert_equal (iqr (pd), 1.6207, 5e-3); %!assert_equal (negloglik (pd), 21.5835, 1e-3); %!assert_equal (pdbox.Kernel, 'box'); %!assert_equal (pdbox.Bandwidth, 0.5); %!assert_equal (pdf (pdbox, [0 1 2]), [0.1925 0.3464 0.2309], 2e-3); %!assert_equal (pdf (t, [-1 0 1 2 3 4]), ... %! [0 0.2788 0.4420 0.3093 0.1518 0], 2e-3); %!assert_equal (cdf (t, [-1 0 1 2 3 4]), ... %! [0 0 0.3846 0.7755 1 1], 2e-3); %!assert_equal (mean (t), 1.3293, 1e-3); %!test ## positive support (log boundary correction) %! y = [0.2 0.5 0.7 1.1 1.4 2 2.6 3.3 4.1 5.5]'; %! pdpos = fitdist (y, 'Kernel', 'Support', 'positive'); %! assert_equal (pdpos.Bandwidth, 0.768201, 1e-4); %! assert_equal (pdpos.Support.range, 'positive'); %! assert_equal (pdf (pdpos, [0.1 0.5 1 2 3 5]), ... %! [0.4302 0.3919 0.2738 0.1572 0.0999 0.0463], 2e-3); %!test ## random values honour truncation bounds %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]'; %! pd = fitdist (x, 'Kernel'); %! t = truncate (pd, 0, 3); %! r = random (t, 1000, 1); %! assert_equal (all (r >= 0 & r <= 3), true); %! assert_equal (size (random (pd, 10, 5)), [10, 5]); ## Test input validation %!error ... %! cdf (fitdist ([1 2 3 4 5]', 'Kernel'), 2, 'uper') %!error ... %! truncate (fitdist ([1 2 3 4 5]', 'Kernel')) %!error ... %! truncate (fitdist ([1 2 3 4 5]', 'Kernel'), 4, 2) %!error ... %! fitdist ([1 2 3 4 5]', 'Kernel', 'Kernel', 'cosine') %!error ... %! fitdist ([1 2 3 4 5]', 'Kernel', 'Support', 'half') %!error ... %! fitdist ([1 2 3 4 5]', 'Kernel', 'Width', -1) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.KernelDistribution (); %! pd(2) = prob.KernelDistribution (); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error pdf (pd, 1) %!error plot (pd) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/LogisticDistribution.m000066400000000000000000001200561524624707500300340ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef LogisticDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.LogisticDistribution ## ## Logistic probability distribution object. ## ## A @code{prob.LogisticDistribution} object consists of parameters, a model ## description, and sample data for a logistic probability distribution. ## ## The logistic distribution is a continuous probability distribution, which ## is commonly used in logistic regression and feedforward neural networks. ## It is defined by location parameter @var{mu} and scale parameter ## @var{sigma}. ## ## There are several ways to create a @code{prob.LogisticDistribution} ## object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.LogisticDistribution (@var{mu}, ## @var{sigma})} to create a logistic distribution with fixed parameter values ## @var{mu} and @var{sigma}. ## @item Use the static method @qcode{prob.LogisticDistribution.fit (@var{x}, ## @var{alpha}, @var{censor}, @var{freq}, @var{options})} to fit a ## distribution to the data in @var{x} using the same input arguments as the ## @code{logifit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the logistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Logistic_distribution} ## ## @seealso{fitdist, makedist, logicdf, logiinv, logipdf, logirnd, logifit, ## logilike, logistat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.LogisticDistribution} {property} mu ## ## Location parameter ## ## A scalar value characterizing the location of the ## logistic distribution. You can access the @qcode{mu} ## property using dot name assignment. ## ## @end deftp mu ## -*- texinfo -*- ## @deftp {prob.LogisticDistribution} {property} sigma ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the ## logistic distribution. You can access the @qcode{sigma} ## property using dot name assignment. ## ## @end deftp sigma endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.LogisticDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Logistic'; ## -*- texinfo -*- ## @deftp {prob.LogisticDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.LogisticDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'mu', 'sigma'}; ## -*- texinfo -*- ## @deftp {prob.LogisticDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Location', 'Scale'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'logi'; ParameterRange = [-Inf, realmin; Inf, Inf]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.LogisticDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{mu} and @qcode{sigma} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.LogisticDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.LogisticDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.LogisticDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only meaningful ## when the distribution was fitted to data. If the distribution object was ## created with fixed parameters, or a parameter of a fitted distribution is ## modified, then all elements of the variance-covariance are zero. This ## property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.LogisticDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.LogisticDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {@var{pd} =} LogisticDistribution (@var{mu}, @var{sigma}) ## @deftypefnx {prob.LogisticDistribution} {@var{pd} =} LogisticDistribution () ## ## Create a @code{prob.LogisticDistribution} object. ## ## @var{mu} and @var{sigma} are the distribution parameters, which the class ## help describes. Called with no arguments the parameters take their ## defaults, @var{mu} 0 and @var{sigma} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = LogisticDistribution (mu, sigma) if (nargin == 0) mu = 0; sigma = 1; endif checkparams (mu, sigma); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [mu, sigma]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'logistic distribution'); endfunction function disp (this) __disp__ (this, 'logistic distribution'); endfunction function this = set.mu (this, mu) checkparams (mu, this.sigma); this.InputData = []; this.ParameterValues(1) = mu; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function mu = get.mu (this) mu = this.ParameterValues(1); endfunction function this = set.sigma (this, sigma) checkparams (this.mu, sigma); this.InputData = []; this.ParameterValues(2) = sigma; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function sigma = get.sigma (this) sigma = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.LogisticDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = logicdf (x, this.mu, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= logicdf (lx, this.mu, this.sigma); p(! (lb | ub)) /= diff (logicdf ([lx, ux], this.mu, this.sigma)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = logicdf (this.Truncation(1), this.mu, this.sigma); up = logicdf (this.Truncation(2), this.mu, this.sigma); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = logiinv (np, this.mu, this.sigma); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = logiinv (p, this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = logistat (this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = logicdf ([lx, ux], this.mu, this.sigma); m = logiinv (sum (Fa_b) / 2, this.mu, this.sigma); else m = this.mu; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = logilike ([this.mu, this.sigma], this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.LogisticDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the ## confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = logipdf (x, this.mu, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (logicdf ([lx, ux], this.mu, this.sigma)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.LogisticDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.LogisticDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.LogisticDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.LogisticDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.LogisticDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.LogisticDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.LogisticDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the logistic distribution, @qcode{@var{pnum} = 1} selects ## the parameter @qcode{mu} and @qcode{@var{pnum} = 2} selects the ## parameter @qcode{sigma}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.LogisticDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.LogisticDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.LogisticDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{bisarnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = logicdf (this.Truncation(1), this.mu, this.sigma); up = logicdf (this.Truncation(2), this.mu, this.sigma); u = unifrnd (lp, up, varargin{:}); r = - log (1 ./ u - 1) .* this.sigma + this.mu; else r = logirnd (this.mu, this.sigma, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, ## and upper limit, @var{upper}. If @var{pd} is fitted to data with ## @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LogisticDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = logistat (this.mu, this.sigma); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{4}; endif ## Fit data [phat, pci] = logifit (x, alpha, censor, freq, options); [~, acov] = logilike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.LogisticDistribution.makeFitted ... (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) mu = phat(1); sigma = phat(2); pd = prob.LogisticDistribution (mu, sigma); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (mu, sigma) if (! (isscalar (mu) && isnumeric (mu) && isreal (mu) && isfinite (mu))) error ("LogisticDistribution: MU must be a finite real scalar.") endif if (! (isscalar (sigma) && isnumeric (sigma) && isreal (sigma) && isfinite (sigma) && sigma > 0)) error ("LogisticDistribution: SIGMA must be a positive real scalar.") endif endfunction ## Test output %!shared pd, t %! pd = prob.LogisticDistribution (0, 1); %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0.5, 0.7311, 0.8808, 0.9526, 0.9820, 0.9933], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.7091, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.8176, 0.8808, 0.9526, 0.9820], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0, 0.7091, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [-Inf, -1.3863, -0.4055, 0.4055, 1.3863, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.2088, 2.4599, 2.7789, 3.2252, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, -0.4055, 0.4055, 1.3863, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.4599, 2.7789, 3.2252, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 2.1972, 1e-4); %!assert_equal (iqr (t), 0.8286, 1e-4); %!assert_equal (mean (pd), 0, 1e-4); %!assert_equal (mean (t), 2.7193, 1e-4); %!assert_equal (median (pd), 0); %!assert_equal (median (t), 2.6085, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0.25, 0.1966, 0.1050, 0.0452, 0.0177, 0.0066], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 1.0373, 0.4463, 0.1745, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0.1966, 0.1966, 0.1050, 0.0452, 0.0177, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 1.0373, 0.4463, 0.1745, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 1.8138, 1e-4); %!assert_equal (std (t), 0.5320, 1e-4); %!assert_equal (var (pd), 3.2899, 1e-4); %!assert_equal (var (t), 0.2830, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [0.3; -1.2; 0.8; 1.5; -0.4; 0.2; -0.9; 1.1; 0.6; -0.3; ... %! 1.8; -1.5; 0.4; 0.9; -0.7; 1.2; -0.2; 0.5; -1.1; 0.7]; %! pd = fitdist (x, 'Logistic'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 1]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.LogisticDistribution' constructor %!error ... %! prob.LogisticDistribution (Inf, 1) %!error ... %! prob.LogisticDistribution (i, 1) %!error ... %! prob.LogisticDistribution ('a', 1) %!error ... %! prob.LogisticDistribution ([1, 2], 1) %!error ... %! prob.LogisticDistribution (NaN, 1) %!error ... %! prob.LogisticDistribution (1, 0) %!error ... %! prob.LogisticDistribution (1, -1) %!error ... %! prob.LogisticDistribution (1, Inf) %!error ... %! prob.LogisticDistribution (1, i) %!error ... %! prob.LogisticDistribution (1, 'a') %!error ... %! prob.LogisticDistribution (1, [1, 2]) %!error ... %! prob.LogisticDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.LogisticDistribution, 2, 'uper') %!error ... %! cdf (prob.LogisticDistribution, 2, 3) ## 'paramci' method %!shared x %! x = logirnd (1, 1, [1, 100]); %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'parameter', 'mu', 'alpha', {0.05}) %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'parameter', {'mu', 'sigma', 'param'}) %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'mu', 'sigma', 'param'}) %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'alpha', 0.01, 'parameter', 'param') %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.LogisticDistribution.fit (x), 'alpha', 0.01, 'parameter', 'mu', ... %! 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.LogisticDistribution, 'Parent') %!error ... %! plot (prob.LogisticDistribution, 'PlotType', 12) %!error ... %! plot (prob.LogisticDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.LogisticDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.LogisticDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.LogisticDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.LogisticDistribution, 'Discrete', {true}) %!error ... %! plot (prob.LogisticDistribution, 'Parent', 12) %!error ... %! plot (prob.LogisticDistribution, 'Parent', 'hax') %!error ... %! plot (prob.LogisticDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.LogisticDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.LogisticDistribution, 2) %!error ... %! proflik (prob.LogisticDistribution.fit (x), 3) %!error ... %! proflik (prob.LogisticDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.LogisticDistribution.fit (x), {1}) %!error ... %! proflik (prob.LogisticDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.LogisticDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.LogisticDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.LogisticDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.LogisticDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.LogisticDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.LogisticDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.LogisticDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.LogisticDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.LogisticDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.LogisticDistribution) %!error ... %! truncate (prob.LogisticDistribution, 2) %!error ... %! truncate (prob.LogisticDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.LogisticDistribution (1, 1); %! pd(2) = prob.LogisticDistribution (1, 3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/LoglogisticDistribution.m000066400000000000000000001224041524624707500305350ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef LoglogisticDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.LoglogisticDistribution ## ## Log-logistic probability distribution object. ## ## A @code{prob.LoglogisticDistribution} object consists of parameters, a model ## description, and sample data for a log-logistic probability distribution. ## ## The log-logistic distribution is a continuous probability distribution that ## models non-negative random variables whose logarithm follows the logistic ## distribution. It is defined by location parameter @var{mu} and scale ## parameter @var{sigma}. ## ## There are several ways to create a @code{prob.LoglogisticDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.LoglogisticDistribution (@var{mu}, ## @var{sigma})} to create a log-logistic distribution with fixed parameter ## values @var{mu} and @var{sigma}. ## @item Use the static method @qcode{prob.LoglogisticDistribution.fit (@var{x}, ## @var{censor}, @var{freq}, @var{options})} to fit a distribution to the data ## in @var{x} using the same input arguments as the @code{loglfit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the log-logistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-logistic_distribution} ## ## @seealso{fitdist, makedist, loglcdf, loglinv, loglpdf, loglrnd, loglfit, ## logllike, loglstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.LoglogisticDistribution} {property} mu ## ## Mean of logarithmic values ## ## A scalar value characterizing the mean of the logarithmic values of the ## log-logistic distribution. You can access the @qcode{mu} ## property using dot name assignment. ## ## @end deftp mu ## -*- texinfo -*- ## @deftp {prob.LoglogisticDistribution} {property} sigma ## ## Scale of logarithmic values ## ## A positive scalar value characterizing the scale of the logarithmic ## values of the log-logistic distribution. You can access the @qcode{sigma} ## property using dot name assignment. ## ## @end deftp sigma endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.LoglogisticDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Log-Logistic'; ## -*- texinfo -*- ## @deftp {prob.LoglogisticDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.LoglogisticDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'mu', 'sigma'}; ## -*- texinfo -*- ## @deftp {prob.LoglogisticDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Mean of logarithmic values', ... 'Scale of logarithmic values'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'logl'; ParameterRange = [0, realmin; Inf, Inf]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.LoglogisticDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{mu} and @qcode{sigma} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.LoglogisticDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.LoglogisticDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.LoglogisticDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only ## meaningful when the distribution was fitted to data. If the distribution ## object was created with fixed parameters, or a parameter of a fitted ## distribution is modified, then all elements of the variance-covariance ## are zero. This property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.LoglogisticDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.LoglogisticDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {@var{pd} =} LoglogisticDistribution (@var{mu}, @var{sigma}) ## @deftypefnx {prob.LoglogisticDistribution} {@var{pd} =} LoglogisticDistribution () ## ## Create a @code{prob.LoglogisticDistribution} object. ## ## @var{mu} and @var{sigma} are the distribution parameters, which the class ## help describes. Called with no arguments the parameters take their ## defaults, @var{mu} 0 and @var{sigma} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = LoglogisticDistribution (mu, sigma) if (nargin == 0) mu = 0; sigma = 1; endif checkparams (mu, sigma); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [mu, sigma]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Log-Logistic distribution'); endfunction function disp (this) __disp__ (this, 'Log-Logistic distribution'); endfunction function this = set.mu (this, mu) checkparams (mu, this.sigma); this.InputData = []; this.ParameterValues(1) = mu; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function mu = get.mu (this) mu = this.ParameterValues(1); endfunction function this = set.sigma (this, sigma) checkparams (this.mu, sigma); this.InputData = []; this.ParameterValues(2) = sigma; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function sigma = get.sigma (this) sigma = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.LoglogisticDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = loglcdf (x, this.mu, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= loglcdf (lx, this.mu, this.sigma); p(! (lb | ub)) /= diff (loglcdf ([lx, ux], this.mu, this.sigma)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = loglcdf (this.Truncation(1), this.mu, this.sigma); up = loglcdf (this.Truncation(2), this.mu, this.sigma); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = loglinv (np, this.mu, this.sigma); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = loglinv (p, this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = loglstat (this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = loglcdf ([lx, ux], this.mu, this.sigma); m = loglinv (sum (Fa_b) / 2, this.mu, this.sigma); else m = exp (this.mu); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = logllike ([this.mu, this.sigma], this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.LoglogisticDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = loglpdf (x, this.mu, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (loglcdf ([lx, ux], this.mu, this.sigma)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.LoglogisticDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.LoglogisticDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.LoglogisticDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.LoglogisticDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.LoglogisticDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.LoglogisticDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.LoglogisticDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the Log-logistic distribution, @qcode{@var{pnum} = 1} selects ## the parameter @qcode{mu} and @qcode{@var{pnum} = 2} selects the ## parameter @qcode{sigma}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.LoglogisticDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.LoglogisticDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.LoglogisticDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{random} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif a = exp (this.mu); b = 1 / this.sigma; if (this.IsTruncated) lp = loglcdf (this.Truncation(1), this.mu, this.sigma); up = loglcdf (this.Truncation(2), this.mu, this.sigma); u = unifrnd (lp, up, varargin{:}); r = exp (this.mu) .* (u ./ (1 - u)) .^ (this.sigma); else r = loglrnd (this.mu, this.sigma, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. If @var{pd} is fitted to data ## with @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoglogisticDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = loglstat (this.mu, this.sigma); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{4}; endif ## Fit data [phat, pci] = loglfit (x, alpha, censor, freq, options); [~, acov] = logllike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.LoglogisticDistribution.makeFitted ... (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) mu = phat(1); sigma = phat(2); pd = prob.LoglogisticDistribution (mu, sigma); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (mu, sigma) if (! (isscalar (mu) && isnumeric (mu) && isreal (mu) && isfinite (mu) && mu >= 0)) error ("LoglogisticDistribution: MU must be a nonnegative real scalar.") endif if (! (isscalar (sigma) && isnumeric (sigma) && isreal (sigma) && isfinite (sigma) && sigma > 0)) error ("LoglogisticDistribution: SIGMA must be a positive real scalar.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Log-logistic %! ## distribution with parameters mu = 0 and sigma = 1. Fit a Log-logistic %! ## distribution to this data and plot a PDF of the fitted distribution %! ## superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('Loglogistic', 'mu', 0, 'sigma', 1) %! data = random (pd_fixed, 5000, 1); %! pd_fitted = fitdist (data, 'Loglogistic') %! plot (pd_fitted) %! msg = 'Fitted Log-logistic distribution with mu = %0.2f and sigma = %0.2f'; %! title (sprintf (msg, pd_fitted.mu, pd_fitted.sigma)) ## Test output %!shared pd, t %! pd = prob.LoglogisticDistribution; %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.5, 0.6667, 0.75, 0.8, 0.8333], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.625, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.6, 0.6667, 0.75, 0.8], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0, 0.625, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0.25, 0.6667, 1.5, 4, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.2609, 2.5714, 2.9474, 3.4118, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 0.6667, 1.5, 4, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.5714, 2.9474, 3.4118, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 2.6667, 1e-4); %!assert_equal (iqr (t), 0.9524, 1e-4); %!assert_equal (mean (pd), Inf); %!assert_equal (mean (t), 2.8312, 1e-4); %!assert_equal (median (pd), 1, 1e-4); %!assert_equal (median (t), 2.75, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0, 0.25, 0.1111, 0.0625, 0.04, 0.0278], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 0.8333, 0.4687, 0.3, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0, 0.25, 0.1111, 0.0625, 0.04, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 0.8333, 0.4687, 0.3, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), Inf); %!assert_equal (std (t), 0.5674, 1e-4); %!assert_equal (var (pd), Inf); %!assert_equal (var (t), 0.3220, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [1.2; 0.4; 3.1; 0.7; 2.5; 1.8; 0.3; 4.2; 1.1; 0.9; ... %! 2.2; 0.6; 1.5; 3.7; 0.8; 2.9; 1.3; 0.5; 2.0; 1.6]; %! pd = fitdist (x, 'Loglogistic'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 1]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.LoglogisticDistribution' constructor %!error ... %! prob.LoglogisticDistribution (Inf, 1) %!error ... %! prob.LoglogisticDistribution (i, 1) %!error ... %! prob.LoglogisticDistribution ('a', 1) %!error ... %! prob.LoglogisticDistribution ([1, 2], 1) %!error ... %! prob.LoglogisticDistribution (NaN, 1) %!error ... %! prob.LoglogisticDistribution (1, 0) %!error ... %! prob.LoglogisticDistribution (1, -1) %!error ... %! prob.LoglogisticDistribution (1, Inf) %!error ... %! prob.LoglogisticDistribution (1, i) %!error ... %! prob.LoglogisticDistribution (1, 'a') %!error ... %! prob.LoglogisticDistribution (1, [1, 2]) %!error ... %! prob.LoglogisticDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.LoglogisticDistribution, 2, 'uper') %!error ... %! cdf (prob.LoglogisticDistribution, 2, 3) ## 'paramci' method %!shared x %! x = loglrnd (1, 1, [1, 100]); %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'parameter', 'mu', 'alpha', {0.05}) %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'parameter', {'mu', 'sigma', 'pa'}) %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'mu', 'sigma', 'param'}) %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'alpha', 0.01, 'parameter', 'parm') %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.LoglogisticDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'mu', 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.LoglogisticDistribution, 'Parent') %!error ... %! plot (prob.LoglogisticDistribution, 'PlotType', 12) %!error ... %! plot (prob.LoglogisticDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.LoglogisticDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.LoglogisticDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.LoglogisticDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.LoglogisticDistribution, 'Discrete', {true}) %!error ... %! plot (prob.LoglogisticDistribution, 'Parent', 12) %!error ... %! plot (prob.LoglogisticDistribution, 'Parent', 'hax') %!error ... %! plot (prob.LoglogisticDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.LoglogisticDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.LoglogisticDistribution, 2) %!error ... %! proflik (prob.LoglogisticDistribution.fit (x), 3) %!error ... %! proflik (prob.LoglogisticDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.LoglogisticDistribution.fit (x), {1}) %!error ... %! proflik (prob.LoglogisticDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.LoglogisticDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.LoglogisticDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.LoglogisticDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.LoglogisticDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.LoglogisticDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.LoglogisticDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.LoglogisticDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.LoglogisticDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.LoglogisticDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.LoglogisticDistribution) %!error ... %! truncate (prob.LoglogisticDistribution, 2) %!error ... %! truncate (prob.LoglogisticDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.LoglogisticDistribution (1, 1); %! pd(2) = prob.LoglogisticDistribution (1, 3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/LognormalDistribution.m000066400000000000000000001217311524624707500302120ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef LognormalDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.LognormalDistribution ## ## Lognormal probability distribution object. ## ## A @code{prob.LognormalDistribution} object consists of parameters, a model ## description, and sample data for a lognormal probability distribution. ## ## The lognormal distribution is a continuous probability distribution whose ## logarithm is normally distributed. It is defined by mean parameter ## @var{mu} and standard deviation parameter @var{sigma} of the logarithmic ## values. ## ## There are several ways to create a @code{prob.LognormalDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.LognormalDistribution (@var{mu}, ## @var{sigma})} to create a lognormal distribution with fixed parameter ## values @var{mu} and @var{sigma}. ## @item Use the static method @qcode{prob.LognormalDistribution.fit (@var{x}, ## @var{censor}, @var{freq}, @var{options})} to fit a distribution to the ## data in @var{x} using the same input arguments as the @code{lognfit} ## function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the lognormal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-normal_distribution} ## ## @seealso{fitdist, makedist, logncdf, logninv, lognpdf, lognrnd, lognfit, ## lognlike, lognstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.LognormalDistribution} {property} mu ## ## Mean of logarithmic values ## ## A scalar value characterizing the mean of the logarithmic values of the ## lognormal distribution. You can access the @qcode{mu} ## property using dot name assignment. ## ## @end deftp mu ## -*- texinfo -*- ## @deftp {prob.LognormalDistribution} {property} sigma ## ## Standard deviation of logarithmic values ## ## A positive scalar value characterizing the standard deviation of the ## logarithmic values of the lognormal distribution. You can access the ## @qcode{sigma} property using dot name assignment. ## ## @end deftp sigma endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.LognormalDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Lognormal'; ## -*- texinfo -*- ## @deftp {prob.LognormalDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.LognormalDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'mu', 'sigma'}; ## -*- texinfo -*- ## @deftp {prob.LognormalDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Mean of logarithmic values', ... 'Standard deviation of logarithmic values'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'logn'; ParameterRange = [-Inf, realmin; Inf, Inf]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.LognormalDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{mu} and @qcode{sigma} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.LognormalDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.LognormalDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.LognormalDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only ## meaningful when the distribution was fitted to data. If the distribution ## object was created with fixed parameters, or a parameter of a fitted ## distribution is modified, then all elements of the variance-covariance ## are zero. This property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.LognormalDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.LognormalDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {@var{pd} =} LognormalDistribution (@var{mu}, @var{sigma}) ## @deftypefnx {prob.LognormalDistribution} {@var{pd} =} LognormalDistribution () ## ## Create a @code{prob.LognormalDistribution} object. ## ## @var{mu} and @var{sigma} are the distribution parameters, which the class ## help describes. Called with no arguments the parameters take their ## defaults, @var{mu} 0 and @var{sigma} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = LognormalDistribution (mu, sigma) if (nargin == 0) mu = 0; sigma = 1; endif checkparams (mu, sigma); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [mu, sigma]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Lognormal distribution'); endfunction function disp (this) __disp__ (this, 'Lognormal distribution'); endfunction function this = set.mu (this, mu) checkparams (mu, this.sigma); this.InputData = []; this.ParameterValues(1) = mu; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function mu = get.mu (this) mu = this.ParameterValues(1); endfunction function this = set.sigma (this, sigma) checkparams (this.mu, sigma); this.InputData = []; this.ParameterValues(2) = sigma; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function sigma = get.sigma (this) sigma = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.LognormalDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = logncdf (x, this.mu, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= logncdf (lx, this.mu, this.sigma); p(! (lb | ub)) /= diff (logncdf ([lx, ux], this.mu, this.sigma)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = logncdf (this.Truncation(1), this.mu, this.sigma); up = logncdf (this.Truncation(2), this.mu, this.sigma); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = logninv (np, this.mu, this.sigma); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = logninv (p, this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = lognstat (this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = logncdf ([lx, ux], this.mu, this.sigma); m = logninv (sum (Fa_b) / 2, this.mu, this.sigma); else m = logninv (0.5, this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = lognlike ([this.mu, this.sigma], this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.LognormalDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = lognpdf (x, this.mu, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (logncdf ([lx, ux], this.mu, this.sigma)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.LognormalDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.LognormalDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.LognormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.LognormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.LognormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.LognormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.LognormalDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the Lognormal distribution, @qcode{@var{pnum} = 1} selects ## the parameter @qcode{mu} and @qcode{@var{pnum} = 2} selects the ## parameter @qcode{sigma}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.LognormalDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.LognormalDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.LognormalDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{lognrnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = logncdf (this.Truncation(1), this.mu, this.sigma); up = logncdf (this.Truncation(2), this.mu, this.sigma); u = unifrnd (lp, up, varargin{:}); r = exp (this.mu + this.sigma .* (-sqrt (2) * erfcinv (2 * u))); else r = lognrnd (this.mu, this.sigma, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. If @var{pd} is fitted to data ## with @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LognormalDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = lognstat (this.mu, this.sigma); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{4}; endif ## Fit data [phat, pci] = lognfit (x, alpha, censor, freq, options); [~, acov] = lognlike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.LognormalDistribution.makeFitted ... (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) mu = phat(1); sigma = phat(2); pd = prob.LognormalDistribution (mu, sigma); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (mu, sigma) if (! (isscalar (mu) && isnumeric (mu) && isreal (mu) && isfinite (mu))) error ("LognormalDistribution: MU must be a real scalar.") endif if (! (isscalar (sigma) && isnumeric (sigma) && isreal (sigma) && isfinite (sigma) && sigma > 0)) error ("LognormalDistribution: SIGMA must be a positive real scalar.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Lognormal distribution with %! ## parameters mu = 0 and sigma = 1. Fit a Lognormal distribution to this data and plot %! ## a PDF of the fitted distribution superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('Lognormal', 'mu', 0, 'sigma', 1) %! data = random (pd_fixed, 5000, 1); %! pd_fitted = fitdist (data, 'Lognormal') %! plot (pd_fitted) %! msg = 'Fitted Lognormal distribution with mu = %0.2f and sigma = %0.2f'; %! title (sprintf (msg, pd_fitted.mu, pd_fitted.sigma)) ## Test output %!shared pd, t %! pd = prob.LognormalDistribution; %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.5, 0.7559, 0.8640, 0.9172, 0.9462], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.6705, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.6574, 0.7559, 0.8640, 0.9172], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0, 0.6705, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0.4310, 0.7762, 1.2883, 2.3201, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.2256, 2.5015, 2.8517, 3.3199, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 0.7762, 1.2883, 2.3201, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.5015, 2.8517, 3.3199, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 1.4536, 1e-4); %!assert_equal (iqr (t), 0.8989, 1e-4); %!assert_equal (mean (pd), 1.6487, 1e-4); %!assert_equal (mean (t), 2.7692, 1e-4); %!assert_equal (median (pd), 1, 1e-4); %!assert_equal (median (t), 2.6653, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0, 0.3989, 0.1569, 0.0727, 0.0382, 0.0219], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 0.9727, 0.4509, 0.2366, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0, 0.3989, 0.1569, 0.0727, 0.0382, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 0.9727, 0.4509, 0.2366, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 2.1612, 1e-4); %!assert_equal (std (t), 0.5540, 1e-4); %!assert_equal (var (pd), 4.6708, 1e-4); %!assert_equal (var (t), 0.3069, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [1.2; 0.4; 3.1; 0.7; 2.5; 1.8; 0.3; 4.2; 1.1; 0.9; ... %! 2.2; 0.6; 1.5; 3.7; 0.8; 2.9; 1.3; 0.5; 2.0; 1.6]; %! pd = fitdist (x, 'Lognormal'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 1]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.LognormalDistribution' constructor %!error ... %! prob.LognormalDistribution (Inf, 1) %!error ... %! prob.LognormalDistribution (i, 1) %!error ... %! prob.LognormalDistribution ('a', 1) %!error ... %! prob.LognormalDistribution ([1, 2], 1) %!error ... %! prob.LognormalDistribution (NaN, 1) %!error ... %! prob.LognormalDistribution (1, 0) %!error ... %! prob.LognormalDistribution (1, -1) %!error ... %! prob.LognormalDistribution (1, Inf) %!error ... %! prob.LognormalDistribution (1, i) %!error ... %! prob.LognormalDistribution (1, 'a') %!error ... %! prob.LognormalDistribution (1, [1, 2]) %!error ... %! prob.LognormalDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.LognormalDistribution, 2, 'uper') %!error ... %! cdf (prob.LognormalDistribution, 2, 3) ## 'paramci' method %!shared x %! randn ('seed', 1); %! x = lognrnd (1, 1, [1, 100]); %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'parameter', 'mu', 'alpha', {0.05}) %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'parameter', {'mu', 'sigma', 'parm'}) %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'mu', 'sigma', 'param'}) %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'alpha', 0.01, 'parameter', 'param') %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.LognormalDistribution.fit (x), 'alpha', 0.01, 'parameter', 'mu', ... %! 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.LognormalDistribution, 'Parent') %!error ... %! plot (prob.LognormalDistribution, 'PlotType', 12) %!error ... %! plot (prob.LognormalDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.LognormalDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.LognormalDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.LognormalDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.LognormalDistribution, 'Discrete', {true}) %!error ... %! plot (prob.LognormalDistribution, 'Parent', 12) %!error ... %! plot (prob.LognormalDistribution, 'Parent', 'hax') %!error ... %! plot (prob.LognormalDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.LognormalDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.LognormalDistribution, 2) %!error ... %! proflik (prob.LognormalDistribution.fit (x), 3) %!error ... %! proflik (prob.LognormalDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.LognormalDistribution.fit (x), {1}) %!error ... %! proflik (prob.LognormalDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.LognormalDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.LognormalDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.LognormalDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.LognormalDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.LognormalDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.LognormalDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.LognormalDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.LognormalDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.LognormalDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.LognormalDistribution) %!error ... %! truncate (prob.LognormalDistribution, 2) %!error ... %! truncate (prob.LognormalDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.LognormalDistribution (1, 1); %! pd(2) = prob.LognormalDistribution (1, 3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/LoguniformDistribution.m000066400000000000000000000643631524624707500304100ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef LoguniformDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.LoguniformDistribution ## ## Log-uniform probability distribution object. ## ## A @code{prob.LoguniformDistribution} object consists of parameters and a model ## description for a log-uniform probability distribution. ## ## The log-uniform distribution is a continuous probability distribution that ## is constant between locations @var{Lower} and @var{Upper} on a logarithmic ## scale. ## ## There are several ways to create a @code{prob.LoguniformDistribution} object. ## ## @itemize ## @item Create a distribution with specified parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.LoguniformDistribution (@var{Lower}, ## @var{Upper})} to create a log-uniform distribution with specified parameter ## values @var{Lower} and @var{Upper}. ## @end itemize ## ## It is highly recommended to use @code{makedist} function to create ## probability distribution objects, instead of the class constructor. ## ## Further information about the log-uniform distribution can be found at ## @url{https://en.wikipedia.org/wiki/Reciprocal_distribution} ## ## @seealso{makedist} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.LoguniformDistribution} {property} Lower ## ## Lower limit ## ## A positive scalar value characterizing the lower limit of the ## log-uniform distribution. You can access the @qcode{Lower} ## property using dot name assignment. ## ## @end deftp Lower ## -*- texinfo -*- ## @deftp {prob.LoguniformDistribution} {property} Upper ## ## Upper limit ## ## A positive scalar value characterizing the upper limit of the ## log-uniform distribution. You can access the @qcode{Upper} ## property using dot name assignment. ## ## @end deftp Upper endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.LoguniformDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Loguniform'; ## -*- texinfo -*- ## @deftp {prob.LoguniformDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.LoguniformDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'Lower', 'Upper'}; ## -*- texinfo -*- ## @deftp {prob.LoguniformDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Lower limit', 'Upper limit'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'logu'; ParameterRange = [realmin, realmin; Inf, Inf]; ParameterLogCI = [false, false]; endproperties properties(GetAccess = public , SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.LoguniformDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{Lower} and @qcode{Upper} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.LoguniformDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.LoguniformDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.LoguniformDistribution} {@var{pd} =} LoguniformDistribution (@var{Lower}, @var{Upper}) ## @deftypefnx {prob.LoguniformDistribution} {@var{pd} =} LoguniformDistribution () ## ## Create a @code{prob.LoguniformDistribution} object. ## ## @var{Lower} and @var{Upper} are the distribution parameters, which the ## class help describes. Called with no arguments the parameters take their ## defaults, @var{Lower} 1 and @var{Upper} 4. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = LoguniformDistribution (Lower, Upper) if (nargin == 0) Lower = 1; Upper = 4; endif checkparams (Lower, Upper); this.IsTruncated = false; this.ParameterValues = [Lower, Upper]; this.Truncation = []; endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Log-uniform distribution'); endfunction function disp (this) __disp__ (this, 'Log-uniform distribution'); endfunction function this = set.Lower (this, Lower) checkparams (Lower, this.Upper); this.ParameterValues(1) = Lower; endfunction function Lower = get.Lower (this) Lower = this.ParameterValues(1); endfunction function this = set.Upper (this, Upper) checkparams (this.Lower, Upper); this.ParameterValues(2) = Upper; endfunction function Upper = get.Upper (this) Upper = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.LoguniformDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.LoguniformDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## ## @var{x} must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif if (! isfloat (x)) error ("cdf: X must be double or single."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations d = log (this.Upper / this.Lower); p = log (x / this.Lower) / d; p(xthis.Upper) = 1; if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= log (lx / this.Lower) / d; p(! (lb | ub)) /= diff (log ([lx, ux] ./ this.Lower) ./ d); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoguniformDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## ## @var{p} must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (! isfloat (p)) error ("icdf: P must be double or single."); endif ## Do the computations is_nan = p < 0 | p > 1 | isnan (p); is_val = p >= 0 & p <= 1; if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); d = log (this.Upper / this.Lower); lp = log (lx / this.Lower) / d; up = log (ux / this.Lower) / d; ## Adjust p values within range of p @ lower limit and p @ upper limit p = lp + (up - lp) .* p; is_nan = p < lp | p > up | isnan (p); is_val = p >= lp & p <= up; endif x = p; x(is_nan) = NaN; x(is_val) = (this.Upper .^ p(is_val)) ./ (this.Lower .^ (p(is_val) - 1)); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoguniformDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoguniformDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = (this.Upper - this.Lower) / log (this.Upper / this.Lower); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoguniformDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif m = icdf (this, 0.5); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoguniformDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## ## @var{x} must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif if (! isfloat (x)) error ("pdf: X must be double or single."); endif d = log (this.Upper / this.Lower); y = 1 ./ (x .* d); y(x < this.Lower | x > this.Upper) = 0; if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (log ([lx, ux] ./ this.Lower) ./ d); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoguniformDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.LoguniformDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.LoguniformDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). ## @qcode{'cdf'} plots the cumulative density function (CDF). ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoguniformDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.LoguniformDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.LoguniformDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.LoguniformDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{random} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif r = icdf (this, unifrnd (0, 1, varargin{:})); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoguniformDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoguniformDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; endfunction ## -*- texinfo -*- ## @deftypefn {prob.LoguniformDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else a = this.Lower; b = this.Upper; l = log (b / a); v = (b ^ 2 - a ^2) / (2 * l) - ((b - a) / l) ^ 2; endif endfunction endmethods endclassdef function checkparams (Lower, Upper) if (! (isscalar (Lower) && isnumeric (Lower) && isreal (Lower) && isfinite (Lower) && Lower > 0)) error ("LoguniformDistribution: LOWER must be a positive real scalar.") endif if (! (isscalar (Upper) && isnumeric (Upper) && isreal (Upper) && isfinite (Upper))) error ("LoguniformDistribution: UPPER must be a real scalar.") endif if (! (Lower < Upper)) error ("LoguniformDistribution: LOWER must be less than UPPER.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Log-uniform distribution with %! ## parameters Lower = 1 and Upper = 10. Plot a PDF of the distribution superimposed %! ## on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('Loguniform', 'Lower', 1, 'Upper', 10); %! data = random (pd_fixed, 5000, 1); %! plot (pd_fixed) %! hold on %! hist (data, 50) %! hold off %! msg = 'Log-uniform distribution with Lower = %0.2f and Upper = %0.2f'; %! title (sprintf (msg, pd_fixed.Lower, pd_fixed.Upper)) ## Test output %!shared pd, t %! pd = prob.LoguniformDistribution (1, 4); %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0, 1, 2, 3, 4, 5]), [0, 0, 0.5, 0.7925, 1, 1], 1e-4); %!assert_equal (cdf (t, [0, 1, 2, 3, 4, 5]), [0, 0, 0, 0.5850, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.2925, 0.5, 0.7925, 1], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0, 0.5850, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [1, 1.3195, 1.7411, 2.2974, 3.0314, 4], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.2974, 2.6390, 3.0314, 3.4822, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 1.7411, 2.2974, 3.0314, 4, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.6390, 3.0314, 3.4822, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 1.4142, 1e-4); %!assert_equal (iqr (t), 0.9852, 1e-4); %!assert_equal (mean (pd), 2.1640, 1e-4); %!assert_equal (mean (t), 2.8854, 1e-4); %!assert_equal (median (pd), 2); %!assert_equal (median (t), 2.8284, 1e-4); %!assert_equal (pdf (pd, [0, 1, 2, 3, 4, 5]), [0, 0.7213, 0.3607, 0.2404, 0.1803, 0], 1e-4); %!assert_equal (pdf (t, [0, 1, 2, 3, 4, 5]), [0, 0, 0.7213, 0.4809, 0.3607, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1, 2, 3, 4, NaN]), [0, 0.7213, 0.3607, 0.2404, 0.1803, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1, 2, 3, 4, NaN]), [0, 0, 0.7213, 0.4809, 0.3607, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (size (random (pd)), [1, 1]) %!error pdf (pd, int32 (2)) %!error pdf (pd, true) %!error cdf (pd, int32 (2)) %!error icdf (pd, int32 (1)) %!assert_equal (size (random (pd, 3)), [3, 3]) %!assert_equal (size (random (pd, -1)), [0, 0]) %!assert_equal (size (random (pd, 2, -1, 5)), [2, 0, 5]) %!assert_equal (any (random (pd, 1000, 1) < 1), false); %!assert_equal (any (random (pd, 1000, 1) > 4), false); %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 0.8527, 1e-4); %!assert_equal (std (t), 0.5751, 1e-4); %!assert_equal (var (pd), 0.7270, 1e-4); %!assert_equal (var (t), 0.3307, 1e-4); ## Test input validation ## 'prob.LoguniformDistribution' constructor %!error ... %! prob.LoguniformDistribution (i, 1) %!error ... %! prob.LoguniformDistribution (Inf, 1) %!error ... %! prob.LoguniformDistribution ([1, 2], 1) %!error ... %! prob.LoguniformDistribution ('a', 1) %!error ... %! prob.LoguniformDistribution (NaN, 1) %!error ... %! prob.LoguniformDistribution (1, i) %!error ... %! prob.LoguniformDistribution (1, Inf) %!error ... %! prob.LoguniformDistribution (1, [1, 2]) %!error ... %! prob.LoguniformDistribution (1, 'a') %!error ... %! prob.LoguniformDistribution (1, NaN) %!error ... %! prob.LoguniformDistribution (2, 1) ## 'cdf' method %!error ... %! cdf (prob.LoguniformDistribution, 2, 'uper') %!error ... %! cdf (prob.LoguniformDistribution, 2, 3) ## 'plot' method %!error ... %! plot (prob.LoguniformDistribution, 'Parent') %!error ... %! plot (prob.LoguniformDistribution, 'PlotType', 12) %!error ... %! plot (prob.LoguniformDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.LoguniformDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.LoguniformDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.LoguniformDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.LoguniformDistribution, 'Discrete', {true}) %!error ... %! plot (prob.LoguniformDistribution, 'Parent', 12) %!error ... %! plot (prob.LoguniformDistribution, 'Parent', 'hax') %!error ... %! plot (prob.LoguniformDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.LoguniformDistribution, 'PlotType', 'probability') ## 'truncate' method %!error ... %! truncate (prob.LoguniformDistribution) %!error ... %! truncate (prob.LoguniformDistribution, 2) %!error ... %! truncate (prob.LoguniformDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.LoguniformDistribution (1, 4); %! pd(2) = prob.LoguniformDistribution (2, 5); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error pdf (pd, 1) %!error plot (pd) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/MultinomialDistribution.m000066400000000000000000000723451524624707500305600ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef MultinomialDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.MultinomialDistribution ## ## Multinomial probability distribution object. ## ## A @code{prob.MultinomialDistribution} object consists of parameters, a model ## description, and sample data for a multinomial probability distribution. ## ## The multinomial distribution is a discrete probability distribution that ## models the outcomes of n independent trials of a k-category system, where ## each trial has a probability of falling into each category. It is ## defined by the vector of probabilities for each outcome. ## ## There are several ways to create a @code{prob.MultinomialDistribution} object. ## ## @itemize ## @item Create a distribution with specified parameter values using the ## @code{makedist} function. ## @item Use the constructor ## @qcode{prob.MultinomialDistribution ## (@var{Probabilities})} ## to create a multinomial distribution with specified parameter values. ## @end itemize ## ## It is highly recommended to use the @code{makedist} function to create ## probability distribution objects, instead of the constructor. ## ## Further information about the multinomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Multinomial_distribution} ## ## @seealso{makedist, mnpdf, mnrnd} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.MultinomialDistribution} {property} Probabilities ## ## Outcome probabilities ## ## A row vector of probabilities for each outcome. You can access the ## @qcode{Probabilities} property using dot name assignment. ## ## @end deftp Probabilities endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.MultinomialDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Multinomial'; ## -*- texinfo -*- ## @deftp {prob.MultinomialDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 1; ## -*- texinfo -*- ## @deftp {prob.MultinomialDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{1*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'Probabilities'}; ## -*- texinfo -*- ## @deftp {prob.MultinomialDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{1*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Outcome probabilities'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'mn'; endproperties properties(GetAccess = public , SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.MultinomialDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{Probabilities} ## property. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.MultinomialDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.MultinomialDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.MultinomialDistribution} {@var{pd} =} MultinomialDistribution (@var{Probabilities}) ## @deftypefnx {prob.MultinomialDistribution} {@var{pd} =} MultinomialDistribution () ## ## Create a @code{prob.MultinomialDistribution} object. ## ## @var{Probabilities} is the distribution parameter, which the class help ## describes. Called with no arguments the parameter takes its default, ## @var{Probabilities} @code{[0.5, 0.5]}. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = MultinomialDistribution (Probabilities) if (nargin == 0) Probabilities = [0.5, 0.5]; endif checkparams (Probabilities); this.IsTruncated = false; this.ParameterValues = {Probabilities(:)'}; endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'multinomial distribution'); endfunction function disp (this) __disp__ (this, 'multinomial distribution'); endfunction function this = set.Probabilities (this, Probabilities) checkparams (Probabilities); this.ParameterValues = {Probabilities(:)'}; endfunction function Probabilities = get.Probabilities (this) Probabilities = this.ParameterValues{1}; endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.MultinomialDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.MultinomialDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @var{x} must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted ## to @qcode{double}, so the result is always a probability. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif if (! isnumeric (x)) error ("cdf: X must be double, single, or integer."); endif if (isinteger (x)) x = double (x); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Check input data if (! isreal (x)) error ("cdf: X must be real."); endif probs = this.Probabilities; ## Check for truncation and normalize truncated probabilities vector if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); probs = this.Probabilities([lx:ux]); probs = probs .* (1 / sum (probs)); x = x - lx + 1; endif ## Do the computations is_nan = isnan (x); sz = size (x); xf = floor (x); pk = length (probs); ## Create cumulative probability vector pc = cumsum (probs); pc(end) = 1; # Force last element to 1 xf(xf > pk) = pk; xf(xf < 1) = 1; xf(is_nan) = 1; p = pc(xf); p(x < 1) = 0; p(is_nan) = NaN; p = reshape (p, sz); ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.MultinomialDistribution} {@var{p} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{p} = icdf (@var{pd}, @var{x})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{x}. ## ## @var{p} must be @qcode{double} or @qcode{single}; integer, logical, and ## character arrays are rejected. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (! isfloat (p)) error ("icdf: P must be double or single."); endif probs = this.Probabilities; ## Do the computations sz = size (p); p = p(:); x = zeros (numel (p), 1); pc = cumsum (this.Probabilities); pc = [0 pc(1:(end-1))]; is_one = p == 0; is_nan = isnan (p) | p > 1 | p < 0; for i = 1:length (pc) x(p > pc(i)) = i; endfor x(is_one) = 1; x(is_nan) = NaN; ## Check for truncation and clip edges if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); lp = pc(lx); up = pc(ux); lb = p >= 0 & p <= lp; ub = p <= 1 & p >= up; x(lb) = lx; x(ub) = ux; endif x = reshape (x, sz); endfunction ## -*- texinfo -*- ## @deftypefn {prob.MultinomialDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.MultinomialDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif probs = this.Probabilities; if (this.IsTruncated) x = 1:numel (probs); w = x >= this.Truncation(1) & x <= this.Truncation(2); x = x(w); y = pdf (this, x); m = sum (y .* x); else m = sum (this.Probabilities .* (1:numel (probs))); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.MultinomialDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif m = icdf (this, 0.5); endfunction ## -*- texinfo -*- ## @deftypefn {prob.MultinomialDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @var{x} must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted ## to @qcode{double}, so the result is always a probability. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif if (! isnumeric (x)) error ("pdf: X must be double, single, or integer."); endif if (isinteger (x)) x = double (x); endif probs = this.Probabilities; size_x = size (x); is_nan = isnan (x); if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); is_out = x < lx | x > ux | (x-floor (x)) > 0 | is_nan; tprobs = probs([lx:ux]); tprobs = tprobs .* (1 / sum (tprobs)); probs([lx:ux]) = tprobs; copy_x = x; copy_x(is_out) = 1; y = probs(copy_x); y(is_out) = 0; y(is_nan) = NaN; y = reshape (y, size_x); return else is_out = x < 1 | x > length (probs) | (x-floor (x)) > 0 | is_nan; copy_x = x; copy_x(is_out) = 1; y = probs(copy_x); y(is_out) = 0; y(is_nan) = NaN; y = reshape (y, size_x); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.MultinomialDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.MultinomialDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.MultinomialDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd}} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, true, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.MultinomialDistribution} {@var{y} =} random (@var{pd}) ## @deftypefnx {prob.MultinomialDistribution} {@var{y} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.MultinomialDistribution} {@var{y} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.MultinomialDistribution} {@var{y} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{mnrnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif u = unifrnd (0, 1, varargin{:}); sz = size (u); if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); tprobs = pdf (this, [1:numel(this.Probabilities)]); tprobs = tprobs([lx:ux]); cp = cumsum (tprobs); else cp = cumsum (this.Probabilities); endif bins = min ([0, cp], 1); bins(end) = 1; [~, r] = histc (u(:), bins); r = reshape (r, sz); if (this.IsTruncated) r = r + lx - 1; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.MultinomialDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.MultinomialDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd})} returns a probability distribution ## @var{t}, which is the probability distribution @var{pd} truncated to the ## specified interval with lower limit, @var{lower}, and upper limit, ## @var{upper}. If @var{pd} is fitted to data with @code{fitdist}, the ## returned probability distribution @var{t} is not fitted, does not contain ## any data or estimated values, and it is as it has been created with the ## @var{makedist} function, but it includes the truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: is_nan input argument."); endif ## Constrain within the length of Probabilities vector lower = round (lower); upper = round (upper); k = numel (this.Probabilities); lower(lower < 1) = 1; upper(upper > k) = k; if (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; endfunction ## -*- texinfo -*- ## @deftypefn {prob.MultinomialDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif probs = this.Probabilities; if (this.IsTruncated) x = 1:numel (probs); w = x >= this.Truncation(1) & x <= this.Truncation(2); x = x(w); y = pdf (this, x); m = sum (y .* x); v = sum (y .* (x - m) .^ 2); else v = sum (probs .* (1:numel (probs)) .^ 2) - mean (this) ^ 2; endif endfunction endmethods endclassdef function checkparams (Probabilities) if (! (isvector (Probabilities) && isnumeric (Probabilities) && isreal (Probabilities) && isfinite (Probabilities) && abs (sum (Probabilities) - 1) < eps * 100)) error (strcat ("MultinomialDistribution: PROBABILITIES must be a vector", " of positive real scalars that sum up to 1.")) endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Multinomial distribution %! ## with parameters Probabilities = [0.1, 0.2, 0.3, 0.2, 0.1, 0.1]. Create %! ## the distribution and plot the PDF superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! probs = [0.1, 0.2, 0.3, 0.2, 0.1, 0.1]; %! pd = makedist ('Multinomial', 'Probabilities', probs); %! data = random (pd, 5000, 1); %! hist (data, length (probs)); %! hold on %! x = 1:length (probs); %! y = pdf (pd, x) * 5000; %! stem (x, y, 'r', 'LineWidth', 2); %! hold off %! msg = 'Multinomial distribution with Probabilities = [%s]'; %! probs_str = num2str (probs, '%0.1f '); %! title (sprintf (msg, probs_str)) ## Test output %!shared pd, t %! pd = prob.MultinomialDistribution ([0.1, 0.2, 0.3, 0.2, 0.1, 0.1]); %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [2, 3, 4]), [0.3, 0.6, 0.8], eps); %!assert_equal (cdf (t, [2, 3, 4]), [0.2857, 0.7143, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.1, 0.3, 0.6, 0.8], eps); %!assert_equal (cdf (pd, [1.5, 2-eps, 3, 4]), [0.1, 0.1, 0.6, 0.8], eps); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0.2857, 0.7143, 1], 1e-4); %!assert_equal (cdf (t, [1.5, 2-eps, 3, 4]), [0, 0, 0.7143, 1], 1e-4); %!assert_equal (cdf (pd, [1, 2.5, 4, 6]), [0.1, 0.3, 0.8, 1], eps); %!assert_equal (icdf (pd, [0, 0.2857, 0.7143, 1]), [1, 2, 4, 6]); %!assert_equal (icdf (t, [0, 0.2857, 0.7143, 1]), [2, 2, 4, 4]); %!assert_equal (icdf (t, [0, 0.35, 0.7143, 1]), [2, 3, 4, 4]); %!assert_equal (icdf (t, [0, 0.35, 0.7143, 1, NaN]), [2, 3, 4, 4, NaN]); %!assert_equal (icdf (t, [-0.5, 0, 0.35, 0.7143, 1, NaN]), [NaN, 2, 3, 4, 4, NaN]); %!assert_equal (icdf (pd, [-0.5, 0, 0.35, 0.7143, 1, NaN]), [NaN, 1, 3, 4, 6, NaN]); %!assert_equal (iqr (pd), 2); %!assert_equal (iqr (t), 2); %!assert_equal (mean (pd), 3.3, 1e-14); %!assert_equal (mean (t), 3, eps); %!assert_equal (median (pd), 3); %!assert_equal (median (t), 3); %!assert_equal (pdf (pd, [-5, 1, 2.5, 4, 6, NaN, 9]), [0, 0.1, 0, 0.2, 0.1, NaN, 0]); %!assert_equal (pdf (pd, [-5, 1, 2, 3, 4, 6, NaN, 9]), ... %! [0, 0.1, 0.2, 0.3, 0.2, 0.1, NaN, 0]); %!assert_equal (pdf (t, [-5, 1, 2, 3, 4, 6, NaN, 0]), ... %! [0, 0, 0.2857, 0.4286, 0.2857, 0, NaN, 0], 1e-4); %!assert_equal (pdf (t, [-5, 1, 2, 4, 6, NaN, 0]), ... %! [0, 0, 0.2857, 0.2857, 0, NaN, 0], 1e-4); %!assert_equal (size (random (pd)), [1, 1]) %!assert_equal (class (pdf (pd, int32 (2))), 'double') %!assert_equal (class (cdf (pd, int32 (2))), 'double') %!assert_equal (pdf (pd, int32 (2)), pdf (pd, 2)) %!error pdf (pd, true) %!error pdf (pd, 'a') %!error icdf (pd, int32 (1)) %!assert_equal (size (random (pd, 3)), [3, 3]) %!assert_equal (size (random (pd, -1)), [0, 0]) %!assert_equal (size (random (pd, 2, -1, 5)), [2, 0, 5]) %!assert_equal (unique (random (pd, 1000, 5)), [1, 2, 3, 4, 5, 6]'); %!assert_equal (unique (random (t, 1000, 5)), [2, 3, 4]'); %!assert_equal (std (pd), 1.4177, 1e-4); %!assert_equal (std (t), 0.7559, 1e-4); %!assert_equal (var (pd), 2.0100, 1e-4); %!assert_equal (var (t), 0.5714, 1e-4); ## Test input validation ## 'prob.MultinomialDistribution' constructor %!error ... %! prob.MultinomialDistribution (0) %!error ... %! prob.MultinomialDistribution (-1) %!error ... %! prob.MultinomialDistribution (Inf) %!error ... %! prob.MultinomialDistribution (i) %!error ... %! prob.MultinomialDistribution ('a') %!error ... %! prob.MultinomialDistribution ([1, 2]) %!error ... %! prob.MultinomialDistribution (NaN) ## 'cdf' method %!error ... %! cdf (prob.MultinomialDistribution, 2, 'uper') %!error ... %! cdf (prob.MultinomialDistribution, 2, 3) %!error ... %! cdf (prob.MultinomialDistribution, i) ## 'plot' method %!error ... %! plot (prob.MultinomialDistribution, 'Parent') %!error ... %! plot (prob.MultinomialDistribution, 'PlotType', 12) %!error ... %! plot (prob.MultinomialDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.MultinomialDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.MultinomialDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.MultinomialDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.MultinomialDistribution, 'Discrete', {true}) %!error ... %! plot (prob.MultinomialDistribution, 'Parent', 12) %!error ... %! plot (prob.MultinomialDistribution, 'Parent', 'hax') %!error ... %! plot (prob.MultinomialDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.MultinomialDistribution, 'PlotType', 'probability') ## 'truncate' method %!error ... %! truncate (prob.MultinomialDistribution) %!error ... %! truncate (prob.MultinomialDistribution, 2) %!error ... %! truncate (prob.MultinomialDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.MultinomialDistribution ([0.1, 0.2, 0.3, 0.4]); %! pd(2) = prob.MultinomialDistribution ([0.1, 0.2, 0.3, 0.4]); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error pdf (pd, 1) %!error plot (pd) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) ## ParameterValues holds one entry per parameter, as MATLAB reports it. %!test %! pd = makedist ('Multinomial', 'Probabilities', [0.2, 0.3, 0.5]); %! assert_equal (iscell (pd.ParameterValues), true); %! assert_equal (size (pd.ParameterValues), [1, 1]); %! assert_equal (pd.ParameterValues{1}, [0.2, 0.3, 0.5]); %! assert_equal (numel (pd.ParameterValues), pd.NumParameters); %! assert_equal (size (pd.ParameterValues), size (pd.ParameterNames)); ## The entry is a row whichever orientation the parameter was given in. %!test %! pd = makedist ('Multinomial', 'Probabilities', [0.2; 0.3; 0.5]); %! assert_equal (size (pd.ParameterValues{1}), [1, 3]); %! pd.Probabilities = [0.5; 0.5]; %! assert_equal (pd.ParameterValues{1}, [0.5, 0.5]); statistics-release-1.9.2/inst/Distribution_Classes/+prob/NakagamiDistribution.m000066400000000000000000001225021524624707500277650ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef NakagamiDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.NakagamiDistribution ## ## Nakagami probability distribution object. ## ## A @code{prob.NakagamiDistribution} object consists of parameters, a model ## description, and sample data for a Nakagami probability distribution. ## ## The Nakagami distribution is a continuous probability distribution that ## models the amplitude of received signals after maximum ratio diversity ## combining. It is defined by shape parameter @var{mu} and spread parameter ## @var{omega}. ## ## There are several ways to create a @code{prob.NakagamiDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.NakagamiDistribution (@var{mu}, ## @var{omega})} to create a Nakagami distribution with fixed parameter ## values @var{mu} and @var{omega}. ## @item Use the static method @qcode{prob.NakagamiDistribution.fit (@var{x}, ## @var{censor}, @var{freq}, @var{options})} to fit a distribution to the data ## in @var{x} using the same input arguments as the @code{nakafit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the Nakagami distribution can be found at ## @url{https://en.wikipedia.org/wiki/Nakagami_distribution} ## ## @seealso{fitdist, makedist, nakacdf, nakainv, nakapdf, nakarnd, nakafit, ## nakalike, nakastat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.NakagamiDistribution} {property} mu ## ## Shape parameter ## ## A positive scalar value characterizing the shape of the ## Nakagami distribution. You can access the @qcode{mu} ## property using dot name assignment. ## ## @end deftp mu ## -*- texinfo -*- ## @deftp {prob.NakagamiDistribution} {property} omega ## ## Spread parameter ## ## A positive scalar value characterizing the spread of the ## Nakagami distribution. You can access the @qcode{omega} ## property using dot name assignment. ## ## @end deftp omega endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.NakagamiDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Nakagami'; ## -*- texinfo -*- ## @deftp {prob.NakagamiDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.NakagamiDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'mu', 'omega'}; ## -*- texinfo -*- ## @deftp {prob.NakagamiDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Shape', 'Spread'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'naka'; ParameterRange = [0.5, realmin; Inf, Inf]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public , SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.NakagamiDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{mu} and @qcode{omega} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.NakagamiDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.NakagamiDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.NakagamiDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only ## meaningful when the distribution was fitted to data. If the distribution ## object was created with fixed parameters, or a parameter of a fitted ## distribution is modified, then all elements of the variance-covariance ## are zero. This property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.NakagamiDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.NakagamiDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {@var{pd} =} NakagamiDistribution (@var{mu}, @var{omega}) ## @deftypefnx {prob.NakagamiDistribution} {@var{pd} =} NakagamiDistribution () ## ## Create a @code{prob.NakagamiDistribution} object. ## ## @var{mu} and @var{omega} are the distribution parameters, which the class ## help describes. Called with no arguments the parameters take their ## defaults, @var{mu} 1 and @var{omega} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = NakagamiDistribution (mu, omega) if (nargin == 0) mu = 1; omega = 1; endif checkparams (mu, omega); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [mu, omega]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Nakagami distribution'); endfunction function disp (this) __disp__ (this, 'Nakagami distribution'); endfunction function this = set.mu (this, mu) checkparams (mu, this.omega); this.InputData = []; this.ParameterValues(1) = mu; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function mu = get.mu (this) mu = this.ParameterValues(1); endfunction function this = set.omega (this, omega) checkparams (this.mu, omega); this.InputData = []; this.ParameterValues(2) = omega; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function omega = get.omega (this) omega = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.NakagamiDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = nakacdf (x, this.mu, this.omega); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= nakacdf (lx, this.mu, this.omega); p(! (lb | ub)) /= diff (nakacdf ([lx, ux], this.mu, this.omega)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = nakacdf (this.Truncation(1), this.mu, this.omega); up = nakacdf (this.Truncation(2), this.mu, this.omega); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = nakainv (np, this.mu, this.omega); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = nakainv (p, this.mu, this.omega); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = nakastat (this.mu, this.omega); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = nakacdf ([lx, ux], this.mu, this.omega); m = nakainv (sum (Fa_b) / 2, this.mu, this.omega); else m = nakainv (0.5, this.mu, this.omega); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = nakalike ([this.mu, this.omega], this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.NakagamiDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = nakapdf (x, this.mu, this.omega); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (nakacdf ([lx, ux], this.mu, this.omega)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.NakagamiDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.NakagamiDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.NakagamiDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.NakagamiDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.NakagamiDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.NakagamiDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.NakagamiDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the Nakagami distribution, @qcode{@var{pnum} = 1} selects ## the parameter @qcode{mu} and @qcode{@var{pnum} = 2} selects the ## parameter @qcode{omega}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.NakagamiDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.NakagamiDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.NakagamiDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{betarnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (nakacdf ([lx, ux], this.mu, this.omega)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = nakarnd (this.mu, this.omega, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = nakarnd (this.mu, this.omega, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. If @var{pd} is fitted to data ## with @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); endif ## Check boundaries and constrain within support: Natural numbers lower = round (lower); upper = round (upper); lower(lower < 0) = 0; if (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NakagamiDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = nakastat (this.mu, this.omega); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{4}; endif ## Fit data [phat, pci] = nakafit (x, alpha, censor, freq, options); [~, acov] = nakalike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.NakagamiDistribution.makeFitted (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) mu = phat(1); omega = phat(2); pd = prob.NakagamiDistribution (mu, omega); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (mu, omega) if (! (isscalar (mu) && isnumeric (mu) && isreal (mu) && isfinite (mu) && mu >= 0.5)) error ("NakagamiDistribution: MU must be a real scalar of at least 0.5.") endif if (! (isscalar (omega) && isnumeric (omega) && isreal (omega) && isfinite (omega) && omega > 0)) error ("NakagamiDistribution: OMEGA must be a positive real scalar.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Nakagami distribution with %! ## parameters mu = 1 and omega = 1. Fit a Nakagami distribution to this data and plot %! ## a PDF of the fitted distribution superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('Nakagami', 'mu', 1, 'omega', 1) %! data = random (pd_fixed, 5000, 1); %! pd_fitted = fitdist (data, 'Nakagami') %! plot (pd_fitted) %! msg = 'Fitted Nakagami distribution with mu = %0.2f and omega = %0.2f'; %! title (sprintf (msg, pd_fitted.mu, pd_fitted.omega)) ## Test output %!shared pd, t %! pd = prob.NakagamiDistribution; %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.6321, 0.9817, 0.9999, 1, 1], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.9933, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.8946, 0.9817, 0.9999, 1], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0, 0.9933, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0.4724, 0.7147, 0.9572, 1.2686, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.0550, 2.1239, 2.2173, 2.3684, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 0.7147, 0.9572, 1.2686, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.1239, 2.2173, 2.3684, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 0.6411, 1e-4); %!assert_equal (iqr (t), 0.2502, 1e-4); %!assert_equal (mean (pd), 0.8862, 1e-4); %!assert_equal (mean (t), 2.2263, 1e-4); %!assert_equal (median (pd), 0.8326, 1e-4); %!assert_equal (median (t), 2.1664, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0, 0.7358, 0.0733, 0.0007, 0, 0], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 4, 0.0404, 0, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0, 0.7358, 0.0733, 0.0007, 0, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 4, 0.0404, 0, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 0.4633, 1e-4); %!assert_equal (std (t), 0.2083, 1e-4); %!assert_equal (var (pd), 0.2146, 1e-4); %!assert_equal (var (t), 0.0434, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [1.2; 0.4; 3.1; 0.7; 2.5; 1.8; 0.3; 4.2; 1.1; 0.9; ... %! 2.2; 0.6; 1.5; 3.7; 0.8; 2.9; 1.3; 0.5; 2.0; 1.6]; %! pd = fitdist (x, 'Nakagami'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 1]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.NakagamiDistribution' constructor %!error ... %! prob.NakagamiDistribution (Inf, 1) %!error ... %! prob.NakagamiDistribution (i, 1) %!error ... %! prob.NakagamiDistribution ('a', 1) %!error ... %! prob.NakagamiDistribution ([1, 2], 1) %!error ... %! prob.NakagamiDistribution (NaN, 1) %!error ... %! prob.NakagamiDistribution (1, 0) %!error ... %! prob.NakagamiDistribution (1, -1) %!error ... %! prob.NakagamiDistribution (1, Inf) %!error ... %! prob.NakagamiDistribution (1, i) %!error ... %! prob.NakagamiDistribution (1, 'a') %!error ... %! prob.NakagamiDistribution (1, [1, 2]) %!error ... %! prob.NakagamiDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.NakagamiDistribution, 2, 'uper') %!error ... %! cdf (prob.NakagamiDistribution, 2, 3) ## 'paramci' method %!shared x %! x = nakarnd (1, 0.5, [1, 100]); %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'parameter', 'mu', 'alpha', {0.05}) %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'parameter', {'mu', 'omega', 'param'}) %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'mu', 'omega', 'param'}) %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'alpha', 0.01, 'parameter', 'param') %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.NakagamiDistribution.fit (x), 'alpha', 0.01, 'parameter', 'mu', ... %! 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.NakagamiDistribution, 'Parent') %!error ... %! plot (prob.NakagamiDistribution, 'PlotType', 12) %!error ... %! plot (prob.NakagamiDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.NakagamiDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.NakagamiDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.NakagamiDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.NakagamiDistribution, 'Discrete', {true}) %!error ... %! plot (prob.NakagamiDistribution, 'Parent', 12) %!error ... %! plot (prob.NakagamiDistribution, 'Parent', 'hax') %!error ... %! plot (prob.NakagamiDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.NakagamiDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.NakagamiDistribution, 2) %!error ... %! proflik (prob.NakagamiDistribution.fit (x), 3) %!error ... %! proflik (prob.NakagamiDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.NakagamiDistribution.fit (x), {1}) %!error ... %! proflik (prob.NakagamiDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.NakagamiDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.NakagamiDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.NakagamiDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.NakagamiDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.NakagamiDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.NakagamiDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.NakagamiDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.NakagamiDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.NakagamiDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.NakagamiDistribution) %!error ... %! truncate (prob.NakagamiDistribution, 2) %!error ... %! truncate (prob.NakagamiDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.NakagamiDistribution (1, 0.5); %! pd(2) = prob.NakagamiDistribution (1, 0.6); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/NegativeBinomialDistribution.m000066400000000000000000001271661524624707500315050ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef NegativeBinomialDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.NegativeBinomialDistribution ## ## Negative binomial probability distribution object. ## ## A @code{prob.NegativeBinomialDistribution} object consists of parameters, a ## model description, and sample data for a negative binomial probability ## distribution. ## ## The negative binomial distribution is a discrete probability distribution ## that models the number of failures in a sequence of independent and ## identically distributed Bernoulli trials before a specified (non-random) ## number of successes occurs. It is defined by the number of successes ## @var{R} and the probability of success @var{P}. ## ## There are several ways to create a @code{prob.NegativeBinomialDistribution} ## object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.NegativeBinomialDistribution (@var{R}, ## @var{P})} to create a negative binomial distribution with fixed parameter ## values @var{R} and @var{P}. ## @item Use the static method ## @qcode{prob.NegativeBinomialDistribution.fit ## (@var{x}, ## @var{freq}, @var{options})} to fit a distribution to the data in @var{x} ## using the same input arguments as the @code{nbinfit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the negative binomial distribution can be found ## at @url{https://en.wikipedia.org/wiki/Negative_binomial_distribution} ## ## @seealso{fitdist, makedist, nbincdf, nbininv, nbinpdf, nbinrnd, nbinfit, ## nbinlike, nbinstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.NegativeBinomialDistribution} {property} R ## ## Number of successes ## ## A scalar value characterizing the number of successes in the ## negative binomial distribution. You can access the @qcode{R} ## property using dot name assignment. ## ## @end deftp R ## -*- texinfo -*- ## @deftp {prob.NegativeBinomialDistribution} {property} P ## ## Probability of success ## ## A scalar value characterizing the probability of success in the ## negative binomial distribution. You can access the @qcode{P} ## property using dot name assignment. ## ## @end deftp P endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.NegativeBinomialDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Negative Binomial'; ## -*- texinfo -*- ## @deftp {prob.NegativeBinomialDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.NegativeBinomialDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'R', 'P'}; ## -*- texinfo -*- ## @deftp {prob.NegativeBinomialDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Number of successes', 'Probability of success'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'nbin'; ParameterRange = [realmin, realmin; Inf, 1]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.NegativeBinomialDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{R} and @qcode{P} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.NegativeBinomialDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.NegativeBinomialDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.NegativeBinomialDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only ## meaningful when the distribution was fitted to data. If the distribution ## object was created with fixed parameters, or a parameter of a fitted ## distribution is modified, then all elements of the variance-covariance ## are zero. This property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.NegativeBinomialDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.NegativeBinomialDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {@var{pd} =} NegativeBinomialDistribution (@var{R}, @var{P}) ## @deftypefnx {prob.NegativeBinomialDistribution} {@var{pd} =} NegativeBinomialDistribution () ## ## Create a @code{prob.NegativeBinomialDistribution} object. ## ## @var{R} and @var{P} are the distribution parameters, which the class help ## describes. Called with no arguments the parameters take their defaults, ## @var{R} 1 and @var{P} 0.5. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = NegativeBinomialDistribution (R, P) if (nargin == 0) R = 1; P = 0.5; endif checkparams (R, P); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [R, P]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'negative binomial distribution'); endfunction function disp (this) __disp__ (this, 'negative binomial distribution'); endfunction function this = set.R (this, R) checkparams (R, this.P); this.InputData = []; this.ParameterValues(1) = R; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function R = get.R (this) R = this.ParameterValues(1); endfunction function this = set.P (this, P) checkparams (this.R, P); this.InputData = []; this.ParameterValues(2) = P; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function P = get.P (this) P = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.NegativeBinomialDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = nbincdf (x, this.R, this.P); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= nbincdf (lx - 1, this.R, this.P); p(! (lb | ub)) /= diff (nbincdf ([lx-1, ux], this.R, this.P)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif umax = nbininv (1, this.R, this.P); if (this.IsTruncated) ## Find out of range p values is_nan = p < 0 | p > 1; ## Get lower and upper boundaries lx = ceil (this.Truncation(1)); ux = floor (this.Truncation(2)); ux = min (ux, umax); lp = nbincdf (lx - 1, this.R, this.P); up = nbincdf (ux, this.R, this.P); p = lp + p * (up - lp); p(is_nan) = NaN; endif x = nbininv (p, this.R, this.P); if (this.IsTruncated) x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif [um, uv] = nbinstat (this.R, this.P); if (this.IsTruncated) lx = ceil (this.Truncation(1)); ux = floor (this.Truncation(2)); ux = min (ux, nbininv (1, this.R, this.P)); ## Handle infinite support on the right if (isequal (ux, Inf)) ratio = 1 / diff (nbincdf ([lx-1, ux], this.R, this.P)); x = 0:lx-1; m = ratio * (um - sum (x .* nbinpdf (x, this.R, this.P))); else x = lx:ux; m = sum (x .* pdf (this, x)); endif else m = um; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = nbincdf ([lx, ux], this.R, this.P); m = nbininv (sum (Fa_b) / 2, this.R, this.P); else m = nbininv (0.5, this.R, this.P); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = nbinlike ([this.R, this.P], this.InputData.data); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.NegativeBinomialDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = nbinpdf (x, this.R, this.P); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (nbincdf ([lx-1, ux], this.R, this.P)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.NegativeBinomialDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.NegativeBinomialDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, true, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.NegativeBinomialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.NegativeBinomialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.NegativeBinomialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.NegativeBinomialDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.NegativeBinomialDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the negative binomial distribution, @qcode{@var{pnum} = 1} selects ## the parameter @qcode{R} and @qcode{@var{pnum} = 2} selects the ## parameter @qcode{P}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.NegativeBinomialDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.NegativeBinomialDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.NegativeBinomialDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{nbindrnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (nbincdf ([lx-1, ux], this.R, this.P)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = nbinrnd (this.R, this.P, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = nbinrnd (this.R, this.P, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. If @var{pd} is fitted to data ## with @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); endif ## Check boundaries and constrain within support: Natural numbers lower = round (lower); upper = round (upper); lower(lower < 0) = 0; if (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NegativeBinomialDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) ## Calculate untruncated mean and variance [um, uv] = nbinstat (this.R, this.P); ## Calculate truncated mean m = mean (this); ## Get lower and upper boundaries lx = ceil (this.Truncation(1)); ux = floor (this.Truncation(2)); ux = min (ux, nbininv (1, this.R, this.P)); ## Handle infinite support on the right if (isequal (ux, Inf)) ratio = 1 / diff (nbincdf ([lx-1, ux], this.R, this.P)); x = 0:lx-1; v = ratio * (uv + (um - m) ^ 2 - sum (((x - m) .^ 2) .* ... nbinpdf (x, this.R, this.P))); else x = lx:ux; v = sum (((x - m) .^ 2) .* pdf (this, x)); endif else [~, v] = nbinstat (this.R, this.P); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) freq = []; else freq = varargin{2}; endif if (nargin < 4) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{3}; endif ## Fit data [phat, pci] = nbinfit (x, alpha, freq, options); [~, acov] = nbinlike (phat, x, freq); ## Create fitted distribution object pd = prob.NegativeBinomialDistribution.makeFitted (phat, pci, acov, x, freq); endfunction function pd = makeFitted (phat, pci, acov, x, freq) R = phat(1); P = phat(2); pd = prob.NegativeBinomialDistribution (R, P); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', [], 'freq', freq); endfunction endmethods endclassdef function checkparams (R, P) if (! (isscalar (R) && isnumeric (R) && isreal (R) && isfinite (R) && R > 0)) error ("NegativeBinomialDistribution: R must be a positive scalar.") endif if (! (isscalar (P) && isnumeric (P) && isreal (P) && isfinite (P) && P > 0 && P <= 1)) error (strcat ("NegativeBinomialDistribution: P must be a real", ... " scalar bounded in the range (0, 1].")) endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Negative Binomial %! ## distribution with parameters R = 5 and P = 0.5. Fit a Negative Binomial %! ## distribution to this data and plot a PDF of the fitted distribution %! ## superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('NegativeBinomial', 'R', 5, 'P', 0.5) %! data = random (pd_fixed, 5000, 1); %! pd_fitted = fitdist (data, 'NegativeBinomial') %! plot (pd_fitted) %! msg = 'Fitted Negative Binomial distribution with R = %0.2f and P = %0.2f'; %! title (sprintf (msg, pd_fitted.R, pd_fitted.P)) ## Test output %!shared pd, t, t_inf %! pd = prob.NegativeBinomialDistribution (5, 0.5); %! t = truncate (pd, 2, 4); %! t_inf = truncate (pd, 2, Inf); %!assert_equal (cdf (pd, [0:5]), [0.0312, 0.1094, 0.2266, 0.3633, 0.5, 0.6230], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0.3, 0.65, 1, 1], 1e-4); %!assert_equal (cdf (t_inf, [0:5]), [0, 0, 0.1316, 0.2851, 0.4386, 0.5768], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.1094, 0.2266, 0.3633, 0.5000], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0.3, 0.65, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 2, 4, 5, 7, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2, 3, 3, 4, 4], 1e-4); %!assert_equal (icdf (t_inf, [0:0.2:1]), [2, 3, 4, 6, 8, Inf], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 4, 5, 7, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 3, 3, 4, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 4); %!assert_equal (iqr (t), 2); %!assert_equal (mean (pd), 5); %!assert_equal (mean (t), 3.0500, 1e-4); %!assert_equal (mean (t_inf), 5.5263, 1e-4); %!assert_equal (median (pd), 4); %!assert_equal (median (t), 3); %!assert_equal (pdf (pd, [0:5]), [0.0312, 0.0781, 0.1172, 0.1367, 0.1367, 0.1230], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 0.3, 0.35, 0.35, 0], 1e-4); %!assert_equal (pdf (t_inf, [0:5]), [0, 0, 0.1316, 0.1535, 0.1535, 0.1382], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0, 0.0781, 0.1172, 0.1367, 0.1367, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 0.3, 0.35, 0.35, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 3.1623, 1e-4); %!assert_equal (std (t), 0.8047, 1e-4); %!assert_equal (std (t_inf), 2.9445, 1e-4); %!assert_equal (var (pd), 10); %!assert_equal (var (t), 0.6475, 1e-4); %!assert_equal (var (t_inf), 8.6704, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [8; 2; 14; 1; 5; 22; 6; 5; 3; 9; 2; 17; 1; 3; 2; 11; 6; 2; 30; 1]; %! pd = fitdist (x, 'NegativeBinomial'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 1]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.NegativeBinomialDistribution' constructor %!error ... %! prob.NegativeBinomialDistribution (Inf, 1) %!error ... %! prob.NegativeBinomialDistribution (i, 1) %!error ... %! prob.NegativeBinomialDistribution ('a', 1) %!error ... %! prob.NegativeBinomialDistribution ([1, 2], 1) %!error ... %! prob.NegativeBinomialDistribution (NaN, 1) %!error ... %! prob.NegativeBinomialDistribution (1, 0) %!error ... %! prob.NegativeBinomialDistribution (1, -1) %!error ... %! prob.NegativeBinomialDistribution (1, Inf) %!error ... %! prob.NegativeBinomialDistribution (1, i) %!error ... %! prob.NegativeBinomialDistribution (1, 'a') %!error ... %! prob.NegativeBinomialDistribution (1, [1, 2]) %!error ... %! prob.NegativeBinomialDistribution (1, NaN) %!error ... %! prob.NegativeBinomialDistribution (1, 1.2) ## 'cdf' method %!error ... %! cdf (prob.NegativeBinomialDistribution, 2, 'uper') %!error ... %! cdf (prob.NegativeBinomialDistribution, 2, 3) ## 'paramci' method %!shared x %! x = nbinrnd (1, 0.5, [1, 100]); %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), 'parameter', 'R', ... %! 'alpha', {0.05}) %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), ... %! 'parameter', {'R', 'P', 'param'}) %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'R', 'P', 'param'}) %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'param') %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), 'alpha', 0.01, ... %! 'NAME', 'value') %!error ... %! paramci (prob.NegativeBinomialDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'R', 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.NegativeBinomialDistribution, 'Parent') %!error ... %! plot (prob.NegativeBinomialDistribution, 'PlotType', 12) %!error ... %! plot (prob.NegativeBinomialDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.NegativeBinomialDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.NegativeBinomialDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.NegativeBinomialDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.NegativeBinomialDistribution, 'Discrete', {true}) %!error ... %! plot (prob.NegativeBinomialDistribution, 'Parent', 12) %!error ... %! plot (prob.NegativeBinomialDistribution, 'Parent', 'hax') %!error ... %! plot (prob.NegativeBinomialDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.NegativeBinomialDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.NegativeBinomialDistribution, 2) %!error ... %! proflik (prob.NegativeBinomialDistribution.fit (x), 3) %!error ... %! proflik (prob.NegativeBinomialDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.NegativeBinomialDistribution.fit (x), {1}) %!error ... %! proflik (prob.NegativeBinomialDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.NegativeBinomialDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.NegativeBinomialDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.NegativeBinomialDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.NegativeBinomialDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.NegativeBinomialDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.NegativeBinomialDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.NegativeBinomialDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.NegativeBinomialDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.NegativeBinomialDistribution.fit (x), 1, {[1 2 3]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.NegativeBinomialDistribution) %!error ... %! truncate (prob.NegativeBinomialDistribution, 2) %!error ... %! truncate (prob.NegativeBinomialDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.NegativeBinomialDistribution (1, 0.5); %! pd(2) = prob.NegativeBinomialDistribution (1, 0.6); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/NormalDistribution.m000066400000000000000000001243521524624707500275120ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef NormalDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.NormalDistribution ## ## Normal probability distribution object. ## ## A @code{prob.NormalDistribution} object consists of parameters, a model ## description, and sample data for a normal probability distribution. ## ## The normal distribution is a continuous probability distribution that is ## symmetric about the mean, @var{mu}, showing that data near the mean are ## more frequent in occurrence than data far from the mean. It is defined by ## location parameter @var{mu} and scale parameter @var{sigma}. ## ## There are several ways to create a @code{prob.NormalDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor ## @qcode{prob.NormalDistribution (@var{mu}, ## @var{sigma})} ## to create a normal distribution with fixed parameter values @var{mu} and ## @var{sigma}. ## @item Use the static method @qcode{prob.NormalDistribution.fit (@var{x}, ## @var{censor}, @var{freq}, @var{options})} to fit a distribution to data ## @var{x}. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Normal_distribution} ## ## @seealso{fitdist, makedist, normcdf, norminv, normpdf, normrnd, normfit, ## normlike, normstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.NormalDistribution} {property} mu ## ## Location parameter ## ## A scalar value characterizing the location of the normal distribution. ## You can access the @qcode{mu} property using dot name assignment. ## ## @end deftp mu ## -*- texinfo -*- ## @deftp {prob.NormalDistribution} {property} sigma ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the normal ## distribution. You can access the @qcode{sigma} property using dot name ## assignment. ## ## @end deftp sigma endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.NormalDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Normal'; ## -*- texinfo -*- ## @deftp {prob.NormalDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.NormalDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'mu', 'sigma'}; ## -*- texinfo -*- ## @deftp {prob.NormalDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Mean', 'Standard Deviation'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'norm'; ParameterRange = [-Inf, realmin; Inf, Inf]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.NormalDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{mu} and @qcode{sigma} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.NormalDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.NormalDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.NormalDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only meaningful ## when the distribution was fitted to data. If the distribution object was ## created with fixed parameters, or a parameter of a fitted distribution is ## modified, then all elements of the variance-covariance are zero. This ## property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.NormalDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.NormalDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {@var{pd} =} NormalDistribution (@var{mu}, @var{sigma}) ## @deftypefnx {prob.NormalDistribution} {@var{pd} =} NormalDistribution () ## ## Create a @code{prob.NormalDistribution} object. ## ## @var{mu} and @var{sigma} are the distribution parameters, which the class ## help describes. Called with no arguments the parameters take their ## defaults, @var{mu} 0 and @var{sigma} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = NormalDistribution (mu, sigma) if (nargin == 0) mu = 0; sigma = 1; endif checkparams (mu, sigma); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [mu, sigma]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'normal distribution'); endfunction function disp (this) __disp__ (this, 'normal distribution'); endfunction function this = set.mu (this, mu) checkparams (mu, this.sigma); this.InputData = []; this.ParameterValues(1) = mu; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function mu = get.mu (this) mu = this.ParameterValues(1); endfunction function this = set.sigma (this, sigma) checkparams (this.mu, sigma); this.InputData = []; this.ParameterValues(2) = sigma; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function sigma = get.sigma (this) sigma = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.NormalDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = normcdf (x, this.mu, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= normcdf (lx, this.mu, this.sigma); p(! (lb | ub)) /= diff (normcdf ([lx, ux], this.mu, this.sigma)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = normcdf (this.Truncation(1), this.mu, this.sigma); up = normcdf (this.Truncation(2), this.mu, this.sigma); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = norminv (np, this.mu, this.sigma); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = norminv (p, this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = normstat (this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = normcdf ([lx, ux], this.mu, this.sigma); m = norminv (sum (Fa_b) / 2, this.mu, this.sigma); else m = norminv (0.5, this.mu, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = normlike ([this.mu, this.sigma], this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.NormalDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = normpdf (x, this.mu, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (normcdf ([lx, ux], this.mu, this.sigma)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.NormalDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.NormalDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.NormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.NormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.NormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.NormalDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.NormalDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the normal distribution, @qcode{@var{pnum} = 1} selects the ## parameter @qcode{mu} and @qcode{@var{pnum} = 2} selects the parameter ## @qcode{sigma}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.NormalDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.NormalDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.NormalDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{normrnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = normcdf (this.Truncation(1), this.mu, this.sigma); up = normcdf (this.Truncation(2), this.mu, this.sigma); u = unifrnd (lp, up, varargin{:}); r = this.mu + this.sigma .* (-sqrt (2) * erfcinv (2 * u)); else r = normrnd (this.mu, this.sigma, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. If @var{pd} is fitted to data ## with @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.NormalDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = normstat (this.mu, this.sigma); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{4}; endif ## Fit data [muhat, sigmahat, muci, sigmaci] = normfit ... (x, alpha, censor, freq, options); phat = [muhat, sigmahat]; pci = [muci(:), sigmaci(:)]; [~, acov] = normlike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.NormalDistribution.makeFitted ... (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) mu = phat(1); sigma = phat(2); pd = prob.NormalDistribution (mu, sigma); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (mu, sigma) if (! (isscalar (mu) && isnumeric (mu) && isreal (mu) && isfinite (mu))) error ("NormalDistribution: MU must be a real scalar.") endif if (! (isscalar (sigma) && isnumeric (sigma) && isreal (sigma) && isfinite (sigma) && sigma > 0)) error ("NormalDistribution: SIGMA must be a positive real scalar.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Normal distribution with %! ## parameters mu = 0 and sigma = 1. Fit a Normal distribution to this data and plot %! ## a PDF of the fitted distribution superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('Normal', 'mu', 0, 'sigma', 1) %! data = random (pd_fixed, 5000, 1); %! pd_fitted = fitdist (data, 'Normal') %! plot (pd_fitted) %! msg = 'Fitted Normal distribution with mu = %0.2f and sigma = %0.2f'; %! title (sprintf (msg, pd_fitted.mu, pd_fitted.sigma)) ## Test output %!shared pd, t %! pd = prob.NormalDistribution; %! t = truncate (pd, -2, 2); %!assert_equal (cdf (pd, [0:5]), [0.5, 0.8413, 0.9772, 0.9987, 1, 1], 1e-4); %!assert_equal (cdf (t, [0:5]), [0.5, 0.8576, 1, 1, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.9332, 0.9772, 0.9987, 1], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0.9538, 1, 1, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [-Inf, -0.8416, -0.2533, 0.2533, 0.8416, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [-2, -0.7938, -0.2416, 0.2416, 0.7938, 2], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, -0.2533, 0.2533, 0.8416, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, -0.2416, 0.2416, 0.7938, 2, NaN], 1e-4); %!assert_equal (iqr (pd), 1.3490, 1e-4); %!assert_equal (iqr (t), 1.2782, 1e-4); %!assert_equal (mean (pd), 0); %!assert_equal (mean (t), 0, 3e-16); %!assert_equal (median (pd), 0); %!assert_equal (median (t), 0, 3e-16); %!assert_equal (pdf (pd, [0:5]), [0.3989, 0.2420, 0.0540, 0.0044, 0.0001, 0], 1e-4); %!assert_equal (pdf (t, [0:5]), [0.4180, 0.2535, 0.0566, 0, 0, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0.2420, 0.2420, 0.0540, 0.0044, 0.0001, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0.2535, 0.2535, 0.0566, 0, 0, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < -2), false); %!assert_equal (any (random (t, 1000, 1) > 2), false); %!assert_equal (std (pd), 1); %!assert_equal (std (t), 0.8796, 1e-4); %!assert_equal (var (pd), 1); %!assert_equal (var (t), 0.7737, 1e-4); %!test %! ## With a further parameter to profile out the default grid takes 21 values. %! z = [0.3; -1.2; 0.8; 1.5; -0.4; 0.2; -0.9; 1.1; 0.6; -0.3; ... %! 1.8; -1.5; 0.4; 0.9; -0.7; 1.2; -0.2; 0.5; -1.1; 0.7]; %! [nlogL, param, other] = proflik (prob.NormalDistribution.fit (z), 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 1]); %!test %! ## OTHER holds the profiled-out parameter maximizing the likelihood at each %! ## value of PARAM. Verified against MATLAB. %! z = [0.3; -1.2; 0.8; 1.5; -0.4; 0.2; -0.9; 1.1; 0.6; -0.3; ... %! 1.8; -1.5; 0.4; 0.9; -0.7; 1.2; -0.2; 0.5; -1.1; 0.7]; %! [nlogL, param, other] = proflik (prob.NormalDistribution.fit (z), 1, [-0.2, 0, 0.2]); %! assert_equal (other, [0.9937; 0.9346; 0.9162], 1e-4); ## Test input validation ## 'prob.NormalDistribution' constructor %!error ... %! prob.NormalDistribution (Inf, 1) %!error ... %! prob.NormalDistribution (i, 1) %!error ... %! prob.NormalDistribution ('a', 1) %!error ... %! prob.NormalDistribution ([1, 2], 1) %!error ... %! prob.NormalDistribution (NaN, 1) %!error ... %! prob.NormalDistribution (1, 0) %!error ... %! prob.NormalDistribution (1, -1) %!error ... %! prob.NormalDistribution (1, Inf) %!error ... %! prob.NormalDistribution (1, i) %!error ... %! prob.NormalDistribution (1, 'a') %!error ... %! prob.NormalDistribution (1, [1, 2]) %!error ... %! prob.NormalDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.NormalDistribution, 2, 'uper') %!error ... %! cdf (prob.NormalDistribution, 2, 3) ## 'paramci' method %!shared x %! x = normrnd (1, 1, [1, 100]); %!error ... %! paramci (prob.NormalDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.NormalDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.NormalDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.NormalDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.NormalDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.NormalDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.NormalDistribution.fit (x), 'parameter', 'mu', 'alpha', {0.05}) %!error ... %! paramci (prob.NormalDistribution.fit (x), 'parameter', {'mu', 'sigma', 'param'}) %!error ... %! paramci (prob.NormalDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'mu', 'sigma', 'param'}) %!error ... %! paramci (prob.NormalDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.NormalDistribution.fit (x), 'alpha', 0.01, 'parameter', 'param') %!error ... %! paramci (prob.NormalDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.NormalDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.NormalDistribution.fit (x), 'alpha', 0.01, 'parameter', 'mu', ... %! 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.NormalDistribution, 'Parent') %!error ... %! plot (prob.NormalDistribution, 'PlotType', 12) %!error ... %! plot (prob.NormalDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.NormalDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.NormalDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.NormalDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.NormalDistribution, 'Discrete', {true}) %!error ... %! plot (prob.NormalDistribution, 'Parent', 12) %!error ... %! plot (prob.NormalDistribution, 'Parent', 'hax') %!error ... %! plot (prob.NormalDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.NormalDistribution, 'PlotType', 'probability') ## 'proflik' method %!test %! ## Profile log-likelihood values verified against MATLAB (nuisance %! ## parameters are profiled out, not held fixed). %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! pd = prob.NormalDistribution.fit (x'); %! [ll, param] = proflik (pd, 1, 2.6:0.2:4.2); %! ref = [-20.3706543783445, -19.2807332475991, -18.3460903001355, ... %! -17.6848714616926, -17.4098041938949, -17.5763530516294, ... %! -18.1502877476524, -19.0282120496015, -20.0892153912834]; %! assert_equal (ll, ref, 1e-6); %! assert_equal (param, 2.6:0.2:4.2, 1e-12); %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! pd = prob.NormalDistribution.fit (x'); %! ll = proflik (pd, 2, 0.7:0.1:1.6); %! ref = [-19.7905304181301, -18.3358679076856, -17.6533683093276, ... %! -17.4185123984561, -17.4530093494964, -17.6534891355391, ... %! -17.9574383057938, -18.3257710746453, -18.7333992513096, ... %! -19.1638879806549]; %! assert_equal (ll, ref, 1e-6); %!test %! ## Default grid spans the 98% CI with 21 points (matching MATLAB). %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! pd = prob.NormalDistribution.fit (x'); %! [ll, param] = proflik (pd, 1); %! assert_equal (numel (param), 21); %! assert_equal ([param(1), param(end)], ... %! [2.57917008770431, 4.27082991229569], 1e-6); %!error ... %! proflik (prob.NormalDistribution, 2) %!error ... %! proflik (prob.NormalDistribution.fit (x), 3) %!error ... %! proflik (prob.NormalDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.NormalDistribution.fit (x), {1}) %!error ... %! proflik (prob.NormalDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.NormalDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.NormalDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.NormalDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.NormalDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.NormalDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.NormalDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.NormalDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.NormalDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.NormalDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.NormalDistribution) %!error ... %! truncate (prob.NormalDistribution, 2) %!error ... %! truncate (prob.NormalDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.NormalDistribution (1, 1); %! pd(2) = prob.NormalDistribution (1, 3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!test %! ## negloglik returns the (positive) negative log-likelihood. %! xdat = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! pdfit = prob.NormalDistribution.fit (xdat'); %! assert_equal (negloglik (pdfit), -sum (log (pdf (pdfit, xdat'))), 1e-9); %! assert_equal (negloglik (pdfit) > 0, true); %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/PiecewiseLinearDistribution.m000066400000000000000000000724341524624707500313350ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef PiecewiseLinearDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.PiecewiseLinearDistribution ## ## Piecewise linear probability distribution object. ## ## A @code{prob.PiecewiseLinearDistribution} object consists of parameters, a model ## description, and sample data for a piecewise linear probability ## distribution. ## ## The piecewise linear distribution is a continuous probability distribution ## that is defined by a set of points where the cumulative distribution ## function (CDF) changes slope. It is defined by a vector of @math{x} values ## and a corresponding vector of CDF values @var{Fx}. ## ## There are several ways to create a @code{prob.PiecewiseLinearDistribution} ## object. ## ## @itemize ## @item Create a distribution with specified parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.PiecewiseLinearDistribution (@var{x}, ## @var{Fx})} to create a piecewise linear distribution with specified ## parameter values @var{x} and @var{Fx}. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the piecewise linear distribution can be found at ## @url{https://en.wikipedia.org/wiki/Piecewise_linear_function} ## ## @seealso{makedist, plcdf, plinv, plpdf, plrnd, plstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.PiecewiseLinearDistribution} {property} x ## ## Vector of x values ## ## A numeric row vector of @math{x} values at which the CDF changes slope, ## reported as a row whichever way it was given. You can access the ## @qcode{x} property using dot name assignment. ## ## @end deftp x ## -*- texinfo -*- ## @deftp {prob.PiecewiseLinearDistribution} {property} Fx ## ## Vector of CDF values ## ## A numeric row vector of CDF values that correspond to each value in ## @math{x}, reported as a row whichever way it was given. You can access ## the @qcode{Fx} property using dot name assignment. ## ## @end deftp Fx endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.PiecewiseLinearDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Piecewise Linear'; ## -*- texinfo -*- ## @deftp {prob.PiecewiseLinearDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.PiecewiseLinearDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'x', 'Fx'}; ## -*- texinfo -*- ## @deftp {prob.PiecewiseLinearDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'x', 'cdf = F(x)'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'pl'; ParameterRange = [-Inf, Inf; -Inf, Inf; 0, 1; 0, 1]; ParameterLogCI = [false, false, false, false]; endproperties properties(GetAccess = public , SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.PiecewiseLinearDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{x} and @qcode{Fx} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.PiecewiseLinearDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.PiecewiseLinearDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.PiecewiseLinearDistribution} {@var{pd} =} PiecewiseLinearDistribution (@var{x}, @var{Fx}) ## @deftypefnx {prob.PiecewiseLinearDistribution} {@var{pd} =} PiecewiseLinearDistribution () ## ## Create a @code{prob.PiecewiseLinearDistribution} object. ## ## @var{x} and @var{Fx} are the distribution parameters, which the class ## help describes. Called with no arguments the parameters take their ## defaults, @var{x} @code{[0; 1]} and @var{Fx} @code{[0; 1]}. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = PiecewiseLinearDistribution (x, Fx) if (nargin == 0) x = [0; 1]; Fx = [0; 1]; else x = x(:); Fx = Fx(:); endif checkparams (x, Fx); this.IsTruncated = false; this.ParameterValues = {x(:)', Fx(:)'}; endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Piecewise Linear distribution'); endfunction function disp (this) __disp__ (this, 'Piecewise Linear distribution'); endfunction ## X and FX are reported as rows, as MATLAB reports them, whichever way ## they were given; ParameterValues holds one entry per parameter, so ## each is a row of its own. function this = set.x (this, x) checkparams (x(:), this.Fx(:)); this.ParameterValues{1} = x(:)'; endfunction function x = get.x (this) x = this.ParameterValues{1}; endfunction function this = set.Fx (this, Fx) checkparams (this.x(:), Fx(:)); this.ParameterValues{2} = Fx(:)'; endfunction function Fx = get.Fx (this) Fx = this.ParameterValues{2}; endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.PiecewiseLinearDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.PiecewiseLinearDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = plcdf (x, this.x, this.Fx); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= plcdf (lx, this.x, this.Fx); p(! (lb | ub)) /= diff (plcdf ([lx, ux], this.x, this.Fx)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PiecewiseLinearDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = plcdf (this.Truncation(1), this.x, this.Fx); up = plcdf (this.Truncation(2), this.x, this.Fx); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = plinv (np, this.x, this.Fx); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = plinv (p, this.x, this.Fx); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PiecewiseLinearDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.PiecewiseLinearDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) m = truncated_moments (this.x, this.Fx, this.Truncation(1), ... this.Truncation(2)); else m = plstat (this.x, this.Fx); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PiecewiseLinearDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = plcdf ([lx, ux], this.x, this.Fx); m = plinv (sum (Fa_b) / 2, this.x, this.Fx); else m = plinv (0.5, this.x, this.Fx); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PiecewiseLinearDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = plpdf (x, this.x, this.Fx); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (plcdf ([lx, ux], this.x, this.Fx)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PiecewiseLinearDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.PiecewiseLinearDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.PiecewiseLinearDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PiecewiseLinearDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.PiecewiseLinearDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.PiecewiseLinearDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.PiecewiseLinearDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{betarnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) x = this.x(:)'; Fx = this.Fx(:)'; lp = plcdf (this.Truncation(1), x, Fx); up = plcdf (this.Truncation(2), x, Fx); u = unifrnd (lp, up, varargin{:}); r = zeros (size (u)); [~, bin] = histc (u(:)', Fx); r0 = x(bin); dx = diff (x); dF = diff (Fx); dr = (u(:)' - Fx(bin)) .* dx(bin) ./ dF(bin); r(:) = r0 + dr; else r = plrnd (this.x, this.Fx, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PiecewiseLinearDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.PiecewiseLinearDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd})} returns a probability distribution ## @var{t}, which is the probability distribution @var{pd} truncated to the ## specified interval with lower limit, @var{lower}, and upper limit, ## @var{upper}. If @var{pd} is fitted to data with @code{fitdist}, the ## returned probability distribution @var{t} is not fitted, does not contain ## any data or estimated values, and it is as it has been created with the ## @var{makedist} function, but it includes the truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; endfunction ## -*- texinfo -*- ## @deftypefn {prob.PiecewiseLinearDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) [m, m2] = truncated_moments (this.x, this.Fx, this.Truncation(1), ... this.Truncation(2)); v = m2 - m ^ 2; else [~, v] = plstat (this.x, this.Fx); endif endfunction endmethods endclassdef ## First and second raw moments over the truncation interval, taken segment by ## segment. The pdf of a piecewise-linear cdf is piecewise constant, so ## x^k * pdf(x) is a polynomial on each segment and integrates exactly; ## adaptive quadrature instead sees a jump at every knot and costs four to ## five digits, which is what this replaced. function [m1, m2] = truncated_moments (x, Fx, lx, ux) x = x(:); Fx = Fx(:); Z = plcdf (ux, x, Fx) - plcdf (lx, x, Fx); m1 = 0; m2 = 0; for i = 1:numel (x) - 1 a = max (x(i), lx); b = min (x(i+1), ux); if (b <= a) continue; endif d = (Fx(i+1) - Fx(i)) / (x(i+1) - x(i)); m1 += d * (b ^ 2 - a ^ 2) / 2; m2 += d * (b ^ 3 - a ^ 3) / 3; endfor m1 /= Z; m2 /= Z; endfunction function checkparams (x, Fx) if (! (isvector (x) && isnumeric (x) && isreal (x) && isfinite (x))) error ("PiecewiseLinearDistribution: X must be a real vector.") endif if (! (isvector (Fx) && isnumeric (Fx) && isreal (Fx) && isfinite (Fx))) error ("PiecewiseLinearDistribution: Fx must be a real vector.") endif if (! isvector (x) || ! isvector (Fx) || ! isequal (size (x), size (Fx))) error (strcat ("PiecewiseLinearDistribution: X and FX must", ... " be vectors of equal size.")); endif if (length (x) < 2 || length (Fx) < 2) error (strcat ("PiecewiseLinearDistribution: X and FX must", ... " be at least two-elements long.")); endif if (any (Fx < 0) || any (Fx > 1)) error (strcat ("PiecewiseLinearDistribution: FX must be", ... " bounded in the range [0, 1].")); endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Beta distribution with %! ## parameters a = 2 and b = 5 scaled to [0,10]. %! ## Compute empirical CDF, subsample, create prob.PiecewiseLinearDistribution, %! ## and plot the PDF superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! data = betarnd (2, 5, 5000, 1) * 10; %! [f, x] = ecdf (data); %! f = f(1:5:end); %! x = x(1:5:end); %! pd = prob.PiecewiseLinearDistribution (x, f); %! [counts, centers] = hist (data, 50); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on %! vals = min (data):0.1:max (data); %! y = pdf (pd, vals); %! plot (vals, y, '-r', 'LineWidth', 2) %! hold off %! title ('Piecewise Linear approximation to scaled Beta(2,5) data') %! legend ('Histogram', 'Piecewise PDF') ## Test output %!shared pd, t %! load patients %! [f, x] = ecdf (Weight); %! f = f(1:5:end); %! x = x(1:5:end); %! pd = prob.PiecewiseLinearDistribution (x, f); %! t = truncate (pd, 130, 180); %!assert_equal (cdf (pd, [120, 130, 140, 150, 200]), [0.0767, 0.25, 0.4629, 0.5190, 0.9908], 1e-4); %!assert_equal (cdf (t, [120, 130, 140, 150, 200]), [0, 0, 0.4274, 0.5403, 1], 1e-4); %!assert_equal (cdf (pd, [100, 250, NaN]), [0, 1, NaN], 1e-4); %!assert_equal (cdf (t, [115, 290, NaN]), [0, 1, NaN], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [111, 127.5, 136.62, 169.67, 182.17, 202], 1e-2); %!assert_equal (icdf (t, [0:0.2:1]), [130, 134.15, 139.26, 162.5, 173.99, 180], 1e-2); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NA, 136.62, 169.67, 182.17, 202, NA], 1e-2); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NA, 139.26, 162.5, 173.99, 180, NA], 1e-2); %!assert_equal (iqr (pd), 50.0833, 1e-4); %!assert_equal (iqr (t), 36.8077, 1e-4); %!assert_equal (mean (pd), 153.61, 1e-10); %!assert_equal (mean (t), 152.30321285140542, 1e-10); %!assert_equal (median (pd), 142, 1e-10); %!assert_equal (median (t), 141.9462, 1e-4); %!assert_equal (pdf (pd, [120, 130, 140, 150, 200]), [0.0133, 0.0240, 0.0186, 0.0024, 0.0004], 6e-3); %!assert_equal (pdf (t, [120, 130, 140, 150, 200]), [0, 0.0482, 0.0373, 0.0048, 0], 1e-4); %!assert_equal (pdf (pd, [100, 250, NaN]), [0, 0, NaN], 1e-4); %!assert_equal (pdf (t, [100, 250, NaN]), [0, 0, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 130), false); %!assert_equal (any (random (t, 1000, 1) > 180), false); %!assert_equal (std (pd), 26.5196, 1e-4); %!assert_equal (std (t), 18.293981947282326, 1e-10); %!assert_equal (var (pd), 703.2879, 1e-4); %!assert_equal (var (t), 334.66977548749168, 1e-10); ## Test input validation ## 'prob.PiecewiseLinearDistribution' constructor %!error ... %! prob.PiecewiseLinearDistribution ([0, i], [0, 1]) %!error ... %! prob.PiecewiseLinearDistribution ([0, Inf], [0, 1]) %!error ... %! prob.PiecewiseLinearDistribution (['a', 'c'], [0, 1]) %!error ... %! prob.PiecewiseLinearDistribution ([NaN, 1], [0, 1]) %!error ... %! prob.PiecewiseLinearDistribution ([0, 1], [0, i]) %!error ... %! prob.PiecewiseLinearDistribution ([0, 1], [0, Inf]) %!error ... %! prob.PiecewiseLinearDistribution ([0, 1], ['a', 'c']) %!error ... %! prob.PiecewiseLinearDistribution ([0, 1], [NaN, 1]) %!error ... %! prob.PiecewiseLinearDistribution ([0, 1], [0, 0.5, 1]) %!error ... %! prob.PiecewiseLinearDistribution ([0], [1]) %!error ... %! prob.PiecewiseLinearDistribution ([0, 0.5, 1], [0, 1, 1.5]) ## 'cdf' method %!error ... %! cdf (prob.PiecewiseLinearDistribution, 2, 'uper') %!error ... %! cdf (prob.PiecewiseLinearDistribution, 2, 3) ## 'plot' method %!error ... %! plot (prob.PiecewiseLinearDistribution, 'Parent') %!error ... %! plot (prob.PiecewiseLinearDistribution, 'PlotType', 12) %!error ... %! plot (prob.PiecewiseLinearDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.PiecewiseLinearDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.PiecewiseLinearDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.PiecewiseLinearDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.PiecewiseLinearDistribution, 'Discrete', {true}) %!error ... %! plot (prob.PiecewiseLinearDistribution, 'Parent', 12) %!error ... %! plot (prob.PiecewiseLinearDistribution, 'Parent', 'hax') %!error ... %! plot (prob.PiecewiseLinearDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.PiecewiseLinearDistribution, 'PlotType', 'probability') ## 'truncate' method %!error ... %! truncate (prob.PiecewiseLinearDistribution) %!error ... %! truncate (prob.PiecewiseLinearDistribution, 2) %!error ... %! truncate (prob.PiecewiseLinearDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.PiecewiseLinearDistribution (); %! pd(2) = prob.PiecewiseLinearDistribution (); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error pdf (pd, 1) %!error plot (pd) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) ## ParameterValues holds one entry per parameter, as MATLAB reports it. %!test %! pd = makedist ('PiecewiseLinear', 'x', [0, 1, 2], 'Fx', [0, 0.5, 1]); %! assert_equal (iscell (pd.ParameterValues), true); %! assert_equal (size (pd.ParameterValues), [1, 2]); %! assert_equal (pd.ParameterValues{1}, [0, 1, 2]); %! assert_equal (pd.ParameterValues{2}, [0, 0.5, 1]); %! assert_equal (numel (pd.ParameterValues), pd.NumParameters); %! assert_equal (size (pd.ParameterValues), size (pd.ParameterNames)); ## Each entry is a row whichever orientation the parameter was given in. %!test %! pd = makedist ('PiecewiseLinear', 'x', [0; 1; 2], 'Fx', [0; 0.5; 1]); %! assert_equal (size (pd.ParameterValues{1}), [1, 3]); %! assert_equal (size (pd.ParameterValues{2}), [1, 3]); %! pd.x = [0; 2; 4]; %! assert_equal (pd.ParameterValues{1}, [0, 2, 4]); %! assert_equal (pd.x, [0, 2, 4]); statistics-release-1.9.2/inst/Distribution_Classes/+prob/PoissonDistribution.m000066400000000000000000001176551524624707500277240ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef PoissonDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.PoissonDistribution ## ## Poisson probability distribution object. ## ## A @code{prob.PoissonDistribution} object consists of parameters, a model ## description, and sample data for a Poisson probability distribution. ## ## The Poisson distribution is a discrete probability distribution that ## models the number of events occurring in a fixed interval of time or space, ## given a constant average rate of occurrence. It is defined by the rate ## parameter @var{lambda}. ## ## There are several ways to create a @code{prob.PoissonDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.PoissonDistribution (@var{lambda})} ## to create a Poisson distribution with fixed parameter value @var{lambda}. ## @item Use the static method @qcode{prob.PoissonDistribution.fit (@var{x}, ## @var{freq})} to fit a distribution to the data in @var{x} using ## the same input arguments as the @code{poissfit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the Poisson distribution can be found at ## @url{https://en.wikipedia.org/wiki/Poisson_distribution} ## ## @seealso{fitdist, makedist, poisscdf, poissinv, poisspdf, poissrnd, ## poissfit, poisslike, poisstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.PoissonDistribution} {property} lambda ## ## Rate parameter ## ## A positive scalar value characterizing the rate of the ## Poisson distribution. You can access the @qcode{lambda} ## property using dot name assignment. ## ## @end deftp lambda endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.PoissonDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Poisson'; ## -*- texinfo -*- ## @deftp {prob.PoissonDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 1; ## -*- texinfo -*- ## @deftp {prob.PoissonDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{1*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'lambda'}; ## -*- texinfo -*- ## @deftp {prob.PoissonDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{1*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Rate'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'poiss'; ParameterRange = [realmin; Inf]; ParameterLogCI = true; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.PoissonDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{1*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{lambda} property. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.PoissonDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. The first element contains the lower boundary, ## the second element contains the upper boundary. This property is ## read-only. You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.PoissonDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.PoissonDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{1*1} numeric matrix containing the variance of the parameter ## estimate. This matrix is only meaningful when the distribution was fitted ## to data. If the distribution object was created with fixed parameters, ## or a parameter of a fitted distribution is modified, then the ## variance is zero. This property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.PoissonDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A logical scalar specifying whether the parameter is fixed or estimated. ## A @qcode{true} value corresponds to a fixed parameter, a @qcode{false} ## value corresponds to a parameter estimate. This property is read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.PoissonDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {@var{pd} =} PoissonDistribution (@var{lambda}) ## @deftypefnx {prob.PoissonDistribution} {@var{pd} =} PoissonDistribution () ## ## Create a @code{prob.PoissonDistribution} object. ## ## @var{lambda} is the distribution parameter, which the class help ## describes. Called with no arguments the parameter takes its default, ## @var{lambda} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = PoissonDistribution (lambda) if (nargin == 0) lambda = 1; endif checkparams (lambda); this.InputData = []; this.IsTruncated = false; this.ParameterValues = lambda; this.ParameterIsFixed = true; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Poisson distribution'); endfunction function disp (this) __disp__ (this, 'Poisson distribution'); endfunction function this = set.lambda (this, lambda) checkparams (lambda); this.InputData = []; this.ParameterValues(1) = lambda; this.ParameterIsFixed = true; this.ParameterCovariance = zeros (this.NumParameters); endfunction function lambda = get.lambda (this) lambda = this.ParameterValues(1); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.PoissonDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = poisscdf (x, this.lambda); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= poisscdf (lx - 1, this.lambda); p(! (lb | ub)) /= diff (poisscdf ([lx-1, ux], this.lambda)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif umax = poissinv (1, this.lambda); if (this.IsTruncated) ## Find out of range p values is_nan = p < 0 | p > 1; ## Get lower and upper boundaries lx = ceil (this.Truncation(1)); ux = floor (this.Truncation(2)); ux = min (ux, umax); lp = poisscdf (lx - 1, this.lambda); up = poisscdf (ux, this.lambda); p = lp + p * (up - lp); p(is_nan) = NaN; endif x = poissinv (p, this.lambda); if (this.IsTruncated) x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif [um, uv] = poisstat (this.lambda); if (this.IsTruncated) lx = ceil (this.Truncation(1)); ux = floor (this.Truncation(2)); ux = min (ux, poissinv (1, this.lambda)); ## Handle infinite support on the right if (isequal (ux, Inf)) ratio = 1 / diff (poisscdf ([lx-1, ux], this.lambda)); x = 0:lx-1; m = ratio * (um - sum (x .* poisspdf (x, this.lambda))); else x = lx:ux; m = sum (x .* pdf (this, x)); endif else m = um; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = poisscdf ([lx-1, ux], this.lambda); m = poissinv (sum (Fa_b) / 2, this.lambda); else m = poissinv (0.5, this.lambda); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = poisslike (this.lambda, this.InputData.data, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.PoissonDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = poisspdf (x, this.lambda); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (poisscdf ([lx-1, ux], this.lambda)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.PoissonDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.PoissonDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. ## ## @item @qcode{'Parent'} @tab An axes graphics object for the plot. ## If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, true, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.PoissonDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.PoissonDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.PoissonDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.PoissonDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.PoissonDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the Poisson distribution, @qcode{@var{pnum} = 1} selects the ## parameter @qcode{lambda}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the user-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.PoissonDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.PoissonDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.PoissonDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{poissrnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (poisscdf ([lx, ux], this.lambda)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = poissrnd (this.lambda, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = poissrnd (this.lambda, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. If @var{pd} is fitted to data ## with @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); endif ## Check boundaries and constrain within support: Natural numbers lower = round (lower); upper = round (upper); lower(lower < 0) = 0; if (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = true; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.PoissonDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) ## Calculate untruncated mean and variance [um, uv] = poisstat (this.lambda); ## Calculate truncated mean m = mean (this); ## Get lower and upper boundaries lx = ceil (this.Truncation(1)); ux = floor (this.Truncation(2)); ux = min (ux, poissinv (1, this.lambda)); ## Handle infinite support on the right if (isequal (ux, Inf)) ratio = 1 / diff (poisscdf ([lx-1, ux], this.lambda)); x = 0:lx-1; v = ratio * (uv + (um - m) ^ 2 - sum (((x - m) .^ 2) .* ... poisspdf (x, this.lambda))); else x = lx:ux; v = sum (((x - m) .^ 2) .* pdf (this, x)); endif else [~, v] = poisstat (this.lambda); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) freq = []; else freq = varargin{2}; endif ## Fit data [phat, pci] = poissfit (x, alpha, freq); [~, acov] = poisslike (phat, x, freq); ## Create fitted distribution object pd = prob.PoissonDistribution.makeFitted ... (phat, pci, acov, x, freq); endfunction function pd = makeFitted (phat, pci, acov, x, freq) lambda = phat(1); pd = prob.PoissonDistribution (lambda); pd.ParameterCI = pci; pd.ParameterIsFixed = false; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', [], 'freq', freq); endfunction endmethods endclassdef function checkparams (lambda) if (! (isscalar (lambda) && isnumeric (lambda) && isreal (lambda) && isfinite (lambda) && lambda > 0)) error ("PoissonDistribution: LAMBDA must be a positive real scalar.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Poisson distribution with %! ## parameter lambda = 5. Fit a Poisson distribution to this data and plot %! ## a PDF of the fitted distribution superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('Poisson', 'lambda', 5) %! data = random (pd_fixed, 5000, 1); %! pd_fitted = fitdist (data, 'Poisson') %! plot (pd_fitted) %! msg = 'Fitted Poisson distribution with lambda = %0.2f'; %! title (sprintf (msg, pd_fitted.lambda)) ## Test output %!shared pd, t, t_inf %! pd = prob.PoissonDistribution; %! t = truncate (pd, 2, 4); %! t_inf = truncate (pd, 2, Inf); %!assert_equal (cdf (pd, [0:5]), [0.3679, 0.7358, 0.9197, 0.9810, 0.9963, 0.9994], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0.7059, 0.9412, 1, 1], 1e-4); %!assert_equal (cdf (t_inf, [0:5]), [0, 0, 0.6961, 0.9281, 0.9861, 0.9978], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4]), [0.7358, 0.9197, 0.9810, 0.9963], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4]), [0, 0.7059, 0.9412, 1], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0, 1, 1, 2, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2, 2, 2, 3, 4], 1e-4); %!assert_equal (icdf (t_inf, [0:0.2:1]), [2, 2, 2, 2, 3, Inf], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 1, 1, 2, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2, 2, 3, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 2); %!assert_equal (iqr (t), 1); %!assert_equal (mean (pd), 1); %!assert_equal (mean (t), 2.3529, 1e-4); %!assert_equal (mean (t_inf), 2.3922, 1e-4); %!assert_equal (median (pd), 1); %!assert_equal (median (t), 2); %!assert_equal (median (t_inf), 2); %!assert_equal (pdf (pd, [0:5]), [0.3679, 0.3679, 0.1839, 0.0613, 0.0153, 0.0031], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 0.7059, 0.2353, 0.0588, 0], 1e-4); %!assert_equal (pdf (t_inf, [0:5]), [0, 0, 0.6961, 0.2320, 0.0580, 0.0116], 1e-4); %!assert_equal (pdf (pd, [-1, 1:4, NaN]), [0, 0.3679, 0.1839, 0.0613, 0.0153, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1:4, NaN]), [0, 0, 0.7059, 0.2353, 0.0588, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 1); %!assert_equal (std (t), 0.5882, 1e-4); %!assert_equal (std (t_inf), 0.6738, 1e-4); %!assert_equal (var (pd), 1); %!assert_equal (var (t), 0.3460, 1e-4); %!assert_equal (var (t_inf), 0.4540, 1e-4); %!test %! ## The profile over the first free parameter: 101 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [3; 1; 4; 1; 5; 2; 6; 5; 3; 5; 2; 4; 1; 3; 2; 4; 6; 2; 3; 1]; %! pd = fitdist (x, 'Poisson'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 101]); %! assert_equal (size (other), [101, 0]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.PoissonDistribution' constructor %!error ... %! prob.PoissonDistribution (0) %!error ... %! prob.PoissonDistribution (-1) %!error ... %! prob.PoissonDistribution (Inf) %!error ... %! prob.PoissonDistribution (i) %!error ... %! prob.PoissonDistribution ('a') %!error ... %! prob.PoissonDistribution ([1, 2]) %!error ... %! prob.PoissonDistribution (NaN) ## 'cdf' method %!error ... %! cdf (prob.PoissonDistribution, 2, 'uper') %!error ... %! cdf (prob.PoissonDistribution, 2, 3) ## 'paramci' method %!shared x %! x = poissrnd (1, [1, 100]); %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'parameter', 'lambda', 'alpha', {0.05}) %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'parameter', {'lambda', 'param'}) %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'lambda', 'param'}) %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'alpha', 0.01, 'parameter', 'param') %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.PoissonDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'lambda', 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.PoissonDistribution, 'Parent') %!error ... %! plot (prob.PoissonDistribution, 'PlotType', 12) %!error ... %! plot (prob.PoissonDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.PoissonDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.PoissonDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.PoissonDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.PoissonDistribution, 'Discrete', {true}) %!error ... %! plot (prob.PoissonDistribution, 'Parent', 12) %!error ... %! plot (prob.PoissonDistribution, 'Parent', 'hax') %!error ... %! plot (prob.PoissonDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.PoissonDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.PoissonDistribution, 2) %!error ... %! proflik (prob.PoissonDistribution.fit (x), 3) %!error ... %! proflik (prob.PoissonDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.PoissonDistribution.fit (x), {1}) %!error ... %! proflik (prob.PoissonDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.PoissonDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.PoissonDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.PoissonDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.PoissonDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.PoissonDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.PoissonDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.PoissonDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.PoissonDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.PoissonDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.PoissonDistribution) %!error ... %! truncate (prob.PoissonDistribution, 2) %!error ... %! truncate (prob.PoissonDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.PoissonDistribution (1); %! pd(2) = prob.PoissonDistribution (3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/ProbabilityDistribution.m000066400000000000000000000676121524624707500305470ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftp {statistics} prob.ProbabilityDistribution ## ## Abstract base class of the probability distribution objects. ## ## It holds the behaviour every distribution object shares -- how it is ## displayed, how its parameter confidence intervals are computed, how it is ## plotted and how a profile likelihood is taken -- as protected methods, so ## the 29 distribution classes inherit one implementation and no part of it is ## reachable from outside them. ## ## These helpers were ordinary files in a @file{private} directory until the ## classes moved into the @code{prob} namespace. Octave does not resolve a ## @file{private} directory from inside a package directory, for a classdef or ## for a plain function, so the only way to keep them out of the public ## interface is to make them protected methods of a shared base. ## ## @end deftp classdef (Abstract) ProbabilityDistribution methods (Access = protected) function __disp__ (pd, distname) if (isscalar (pd)) ## Handle special case of prob.PiecewiseLinearDistribution if (isa (pd, 'prob.PiecewiseLinearDistribution')) ## Print distribution header fprintf (" %s\n\n", pd.DistributionName); ## Print parameter values for i = 1:numel (pd.x) fprintf ("F(%g) = %g\n", pd.x(i), pd.Fx(i)); endfor ## Print truncation interval if applicable if (pd.IsTruncated) fprintf (" Truncated to the interval [%g, %g]\n\n", pd.Truncation); else fprintf ("\n"); endif ## Handle special case of prob.MultinomialDistribution elseif (isa (pd, 'prob.MultinomialDistribution')) ## Print distribution header fprintf (" %s\n\n", pd.DistributionName); ## Print parameter values fprintf (" Probabilities:\n"); fprintf (" %0.4f", pd.Probabilities); fprintf ("\n\n"); ## Print truncation interval if applicable if (pd.IsTruncated) fprintf (" Truncated to the interval [%g, %g]\n\n", pd.Truncation); endif ## Handle special case of prob.KernelDistribution elseif (isa (pd, 'prob.KernelDistribution')) ## Print distribution header fprintf (" %s\n\n", pd.DistributionName); ## Print kernel, bandwidth, and support fprintf (" Kernel = %s\n", pd.Kernel); fprintf (" Bandwidth = %g\n", pd.Bandwidth); if (ischar (pd.Support.range)) fprintf (" Support = %s\n", pd.Support.range); else fprintf (" Support = [%g, %g]\n", pd.Support.range); endif ## Print truncation interval if applicable if (pd.IsTruncated) fprintf (" Truncated to the interval [%g, %g]\n\n", pd.Truncation); else fprintf ("\n"); endif ## Handle all other cases else ## Get required length for parameter values PVlen = max (arrayfun (@(x) numel (sprintf ("%g", x)), ... pd.ParameterValues)); PVstr = sprintf ("%%%dg", PVlen); ## Prepare template for fitted and not fitted distributions pat1 = [' %+7s = ', PVstr, " [%g, %g]\n"]; pat2 = [' %+7s = ', PVstr, "\n"]; ## Grad distributions that are non fittable if (any (strcmpi (pd.DistributionCode, {'unif', 'tri', 'logu'}))) fitted = false; ParameterIsFixed = true; elseif (all (pd.ParameterIsFixed)) fitted = false; ParameterIsFixed = pd.ParameterIsFixed; else fitted = true; ParameterIsFixed = pd.ParameterIsFixed; endif ## Print distribution header fprintf (" %s\n\n", pd.DistributionName); fprintf (" %s\n", distname); ## Print parameter values for i = 1:pd.NumParameters if (fitted && ! ParameterIsFixed(i)) fprintf (pat1, pd.ParameterNames{i}, pd.ParameterValues(i), ... pd.ParameterCI(1,i), pd.ParameterCI(2,i)); else fprintf (pat2, pd.ParameterNames{i}, pd.ParameterValues(i)); endif endfor ## Print truncation interval if applicable if (pd.IsTruncated) fprintf (" Truncated to the interval [%g, %g]\n\n", pd.Truncation); else fprintf ("\n"); endif endif else fprintf ("%dx%d %s array\n", size (pd), class (pd)); endif endfunction function ci = __paramci__ (pd, varargin) ## Get Distribution specific info distname = pd.DistributionCode; parnames = pd.ParameterNames; ## Add defaults and parse optional arguments alpha = 0.05; param = logical ([1:numel(parnames)]); if (mod (numel (varargin), 2) != 0) error ("paramci: optional arguments must be in NAME-VALUE pairs."); endif while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'alpha' alpha = varargin{2}; if (! isscalar (alpha) || ! isnumeric (alpha) || ... alpha <= 0 || alpha >= 1) error ("paramci: invalid VALUE for 'Alpha' argument."); endif case 'parameter' if (! isvector (varargin{2}) || ((iscellstr (varargin{2}) || isnumeric (varargin{2})) && numel (varargin{2}) > numel (parnames))) error ("paramci: invalid VALUE size for 'Parameter' argument."); endif if (iscellstr (varargin{2})) tmp = cellfun (@(x) strcmpi (x, parnames), varargin{2}, ... 'UniformOutput', false); param = or (tmp{:}); elseif (isnumeric (varargin{2})) param = ismember (parnames, varargin{2}); else # assume it is a character vector param = strcmpi (varargin{2}, parnames); endif if (! any (param)) error ("paramci: unknown distribution parameter."); endif case {'type', 'logflag'} printf ("paramci: '%s' argument not supported yet.", varargin{1}); otherwise error ("paramci: invalid NAME for optional argument."); endswitch varargin([1:2]) = []; endwhile ## Get confidence intervals for all parameters from selected distribution if (strcmpi (distname, 'bino')) ntrials = pd.N; [~, ci] = mle (pd.InputData.data, 'distribution', distname, ... 'alpha', alpha, 'ntrials', ntrials, ... 'frequency', pd.InputData.freq); elseif (strcmpi (distname, 'gp')) theta = pd.theta; [~, ci] = mle (pd.InputData.data, 'distribution', distname, ... 'alpha', alpha, 'theta', theta, ... 'frequency', pd.InputData.freq); elseif (strcmpi (distname, 'hn')) mu = pd.mu; [~, ci] = mle (pd.InputData.data, 'distribution', distname, ... 'alpha', alpha, 'mu', mu, 'frequency', pd.InputData.freq); elseif (! pd.CensoringAllowed) [~, ci] = mle (pd.InputData.data, 'distribution', distname, ... 'alpha', alpha, 'frequency', pd.InputData.freq); else [~, ci] = mle (pd.InputData.data, 'distribution', distname, ... 'alpha', alpha, 'censoring', pd.InputData.cens, ... 'frequency', pd.InputData.freq); endif ## MLE reports intervals only for the estimated parameters of some ## distributions. Report one column per parameter, as MATLAB does, holding a ## fixed parameter at its own value. if (columns (ci) != numel (pd.ParameterValues)) fullci = [pd.ParameterValues; pd.ParameterValues]; fullci(:, ! pd.ParameterIsFixed) = ci; ci = fullci; endif ## Return ci only for requested parameters ci(:, ! param) = []; endfunction function h = __plot__ (pd, DistType, varargin) ## Add defaults (Discrete is passed by the calling method) ax = []; PlotType = 'pdf'; Discrete = DistType; ## Parse optional arguments if (mod (numel (varargin), 2) != 0) error ("plot: optional arguments must be in NAME-VALUE pairs."); endif while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'plottype' ValidTypes = {'pdf', 'cdf', 'probability'}; try selected_T = strcmpi (varargin{2}, ValidTypes); catch error ("plot: invalid VALUE size for 'Parameter' argument."); end_try_catch if (! any (selected_T) || sum (selected_T) > 1) error ("plot: invalid VALUE for 'PlotType' argument."); endif PlotType = ValidTypes{selected_T}; case 'discrete' if (! (islogical (varargin{2}) && isscalar (varargin{2}))) error ("plot: invalid VALUE for 'Discrete' argument."); endif ## Only for discrete distributions this can be changed by the user if (DistType) Discrete = varargin{2}; endif case 'parent' if (! isaxes (varargin{2})) error ("plot: invalid VALUE for 'Parent' argument."); endif ax = varargin{2}; otherwise error ("plot: invalid NAME for optional argument."); endswitch varargin([1:2]) = []; endwhile ## Check for invalid cases of probability type before creating new axes if (strcmpi (PlotType, 'probability')) if (! isprop (pd, 'InputData')) msg = 'plot: ''probability'' PlotType is not supported for ''%s''.'; error (sprintf (msg, pd.DistributionName)); endif if (isempty (pd.InputData)) error ("plot: no fitted DATA to plot a probability plot."); endif endif ## Get current axes or create new ones if (isempty (ax)) ax = gca (); endif ## Switch to PlotType switch (PlotType) case 'pdf' h = plot_pdf (pd, ax, DistType, Discrete); case 'cdf' h = plot_cdf (pd, ax, DistType, Discrete); case 'probability' h = plot_prob (pd, ax, DistType, Discrete); endswitch endfunction function [nlogl, param, other] = __proflik__ (pd, pnum, varargin) ## Default to the first non-fixed parameter npvec = find (pd.ParameterIsFixed == false); if (nargin < 2 || isempty (pnum)) pnum = npvec(1); endif ## Check for non-fixed pnum if (! (isnumeric (pnum) && isscalar (pnum) && ismember (pnum, npvec))) error (strcat ("proflik: PNUM must be a scalar number", ... " indexing a non-fixed parameter.")); endif ## Add defaults and parse optional arguments param = []; Display = false; while (numel (varargin) > 0) if (isnumeric (varargin{1})) if (! isvector (varargin{1})) error ("proflik: SETPARAM must be a numeric vector."); endif param = varargin{1}; varargin(1) = []; elseif (ischar (varargin{1})) if (strcmpi (varargin{1}, 'display')) if (numel (varargin) < 2) error ("proflik: missing VALUE for 'Display' argument."); endif if (! ischar (varargin{2})) error ("proflik: invalid VALUE type for 'Display' argument."); endif if (size (varargin{2}, 1) != 1) error ("proflik: invalid VALUE size for 'Display' argument."); endif if (strcmpi (varargin{2}, 'off')) Display = false; elseif (strcmpi (varargin{2}, 'on')) Display = true; else error ("proflik: invalid VALUE for 'Display' argument."); endif varargin([1:2]) = []; else error ("proflik: invalid NAME for optional arguments."); endif else error ("proflik: invalid optional argument."); endif endwhile ## Optimal parameter values and the free parameters to profile out (the ## non-fixed parameters other than the selected one) optpar = pd.ParameterValues; fname = sprintf ("%slike", pd.DistributionCode); freeidx = npvec(npvec != pnum); ## Create parameter vector pname = pd.ParameterNames{pnum}; if (isempty (param)) ## Default range: equally spaced values over the 98% confidence interval, ## restricted to the non-fixed range. MATLAB takes 101 values when the ## selected parameter is the only one estimated and 21 when the others must ## be profiled out at each of them. ci = paramci (pd, "Alpha", 0.02); if (any (isnan (ci(:, pnum)))) error (strcat ("proflik: no confidence interval is defined for '%s',", ... " so the default range cannot be built; supply", ... " SETPARAM instead."), pd.ParameterNames{pnum}); endif lower = max (ci(1, pnum), pd.ParameterRange(1, pnum)); upper = min (ci(2, pnum), pd.ParameterRange(2, pnum)); if (isempty (freeidx)) param = linspace (lower, upper, 101); else param = linspace (lower, upper, 21); endif else ## Restrict user defined parameter range within non-fixed range param(param < pd.ParameterRange(1,pnum)) = []; param(param > pd.ParameterRange(2,pnum)) = []; endif ## Compute the profile log likelihood: at each value of the selected ## parameter, maximize the log likelihood over the remaining free parameters params = pd.ParameterValues; opts = optimset ("Display", "off", "TolX", 1e-6, "TolFun", 1e-6); nlogl = zeros (1, numel (param)); ## Each row of OTHER holds every parameter but the selected one, at the ## values maximizing the likelihood; a fixed parameter keeps its own value otheridx = [1:numel(params)]; otheridx(pnum) = []; other = zeros (numel (param), numel (otheridx)); for i = 1:numel (param) p0 = params; p0(pnum) = param(i); if (isempty (freeidx)) nlogl(i) = - like_value (fname, p0, pd); else objfun = @(pf) like_free (pf, p0, freeidx, fname, pd); [pfhat, fval] = fminsearch (objfun, params(freeidx), opts); nlogl(i) = - fval; p0(freeidx) = pfhat; endif other(i,:) = p0(otheridx); endfor optnll = - like_value (fname, optpar, pd); ## Plot the profile log likelihood against the selected parameter, marking ## the estimate and the 95% profile-likelihood confidence threshold if (Display) nll_conf = optnll - 0.5 * chi2inv (0.95, 1); plot (optpar(pnum), optnll, 'ok;Estimate;', ... param, nlogl, '-r;Profile log likelihood;', ... param, repmat (nll_conf, size (param)), ':b;95% confidence;'); xlabel (pname); ylabel ('log likelihood'); xlim ([param(1), param(end)]); endif endfunction endmethods endclassdef function x = expand_freq (data, freq) x = []; for i = 1:numel (freq) x = [x, repmat(data(i), 1, freq(i))]; endfor endfunction function [lb, ub, xmin, xmax] = compute_boundaries (pd) ## Compute moments to determine plot boundaries m = mean (pd); s = std (pd); lb = m - 3 * s; ub = m + 3 * s; xmin = m - 3.5 * s; xmax = m + 3.5 * s; ## Fix boundaries for specific distributions PD = {'bino', 'bisa', 'exp', 'gam', 'invg', 'logl', 'logn', ... 'naka', 'nbin', 'poiss', 'rayl', 'rice', 'wbl'}; if (strcmpi (pd.DistributionCode, 'beta')) lb = xmin = 0; ub = xmax = 1; elseif (strcmpi (pd.DistributionCode, 'burr')) lb = xmin = 0; ub = xmax = m + 3 * iqr (pd); elseif (any (strcmpi (pd.DistributionCode, PD))) lb = max (m - 3 * s, 0); xmin = max (m - 3.5 * s, 0); elseif (strcmpi (pd.DistributionCode, 'gev')) elseif (strcmpi (pd.DistributionCode, 'gp')) elseif (strcmpi (pd.DistributionCode, 'hn')) lb = max (m - 3 * s, m); xmin = max (m - 3.5 * s, m); elseif (strcmpi (pd.DistributionCode, 'kernel')) ## Clamp the plotting range to a bounded kernel support if (ischar (pd.Support.range)) if (strcmp (pd.Support.range, 'positive')) lb = xmin = max (m - 3 * s, 0); endif else lb = xmin = max (m - 3 * s, pd.Support.range(1)); ub = xmax = min (m + 3 * s, pd.Support.range(2)); endif endif endfunction function h = plot_pdf (pd, ax, DistType, Discrete) ## Handle special case of multinomial distribution if (strcmpi (pd.DistributionCode, 'mn')) y = pd.ParameterValues{1}(:); x = [1:numel(y)]'; if (Discrete) h = stem (ax, x, y, 'color', 'b'); else h = plot (ax, x, y, ';;b-'); endif xlim (ax, [0.5, max(x)+0.5]); xlabel ('Data'); ylabel ('Probability'); return endif ## Handle special case of piecewise linear distribution if (strcmpi (pd.DistributionCode, 'pl')) x = pd.ParameterValues{1}(:); y = pd.ParameterValues{2}(:); h = plot (ax, x, y, ';;b-'); xgap = (x(end) - x(1)) * 0.1; xlim (ax, [x(1)-xgap, x(end)+xgap]); xlabel ('Data'); ylabel ('Probability'); return endif ## Handle special case of triangular distribution if (strcmpi (pd.DistributionCode, 'tri')) lb = pd.A; ub = pd.C; xmin = lb - (ub - lb) * 0.1; xmax = ub + (ub - lb) * 0.1; x = [lb:(ub-lb)/100:ub]'; y = pdf (pd, x); h = plot (ax, x, y, ';;r-', 'linewidth', 2); xlim (ax, [xmin, xmax]); xlabel ('Data'); ylabel ('PDF'); return endif ## Handle special case of log-uniform and uniform distributions if (any (strcmpi (pd.DistributionCode, {'logu', 'unif'}))) lb = pd.Lower; ub = pd.Upper; xmin = lb - (ub - lb) * 0.1; xmax = ub + (ub - lb) * 0.1; x = [lb:(ub-lb)/100:ub]'; y = pdf (pd, x); h = plot (ax, x, y, ';;r-', 'linewidth', 2); xlim (ax, [xmin, xmax]); xlabel ('Data'); ylabel ('PDF'); return endif ## Check for fitted distribution if (isempty (pd.InputData)) # fixed parameters, no data ## Compute plot boundaries [lb, ub, xmin, xmax] = compute_boundaries (pd); ## Compute stem or line for PDF if (DistType) x = [floor(lb):ceil(ub)]'; y = pdf (pd, x); else x = [lb:(ub-lb)/100:ub]'; y = pdf (pd, x); endif ## Plot if (Discrete) xlim (ax, [min(x)-0.5, max(x)+0.5]); h = stem (ax, x, y, 'color', 'r'); #xlim (ax, [min(x)-0.5, max(x)+0.5]); # before Octave 11 this emits an error xlabel ('Data'); ylabel ('Probability'); else h = plot (ax, x, y, ';;r-', 'linewidth', 2); xlim (ax, [xmin, xmax]); xlabel ('Data'); ylabel ('PDF'); endif else # fitted distribution, data available ## Expand frequency vector (if necessary) if (any (pd.InputData.freq != 1)) x = expand_freq (pd.InputData.data, pd.InputData.freq); else x = pd.InputData.data; endif ## Keep data within plotting boundaries [lb, ub, xmin, xmax] = compute_boundaries (pd); x(x < lb | x > ub) = []; ## Compute the patch or histogram for data xsize = numel (x); if (DistType) binwidth = 1; xmin = min (x) - 1; xmax = max (x) + 1; [binsize, bincenter] = hist (x, [xmin:xmax]); else nbins = ceil (sqrt (xsize)); [binsize, bincenter] = hist (x, nbins); binwidth = max (diff (bincenter)); xmin = min (x) - binwidth / 2; xmax = max (x) + binwidth / 2; endif ## Compute stem or line for PDF if (Discrete) x = [min(x):max(x)]'; y = pdf (pd, x); else x = [xmin:(xmax-xmin)/100:xmax]'; y = pdf (pd, x); endif ## Normalize density line y = xsize * y * binwidth; ## Plot if (DistType) h(2) = patch (ax, bincenter, binsize, 1, 'facecolor', 'b'); xlim (ax, [xmin, xmax]); hold on; if (Discrete) h(1) = stem (ax, x, y, 'color', 'r'); else h(1) = plot (ax, x, y, ';;r-'); endif xlabel ('Data'); ylabel ('Probability'); hold off; else h(2) = bar (ax, bincenter, binsize, 1, 'facecolor', 'b'); hold on; h(1) = plot (ax, x, y, ';;r-', 'linewidth', 2); xlim (ax, [xmin, xmax]); xlabel ('Data'); ylabel ('PDF'); hold off; endif endif endfunction function h = plot_cdf (pd, ax, DistType, Discrete) ## Handle special case of multinomial distribution if (strcmpi (pd.DistributionCode, 'mn')) y = pd.ParameterValues{1}(:); x = [1:numel(y)]'; xlim (ax, [0.5, max(x)+0.5]); if (Discrete) h = stem (ax, x, y, 'color', 'b'); else h = plot (ax, x, y, ';;b-'); endif xlabel ('Data'); ylabel ('Probability'); return endif ## Handle special case of piecewise linear distribution if (strcmpi (pd.DistributionCode, 'pl')) x = pd.ParameterValues{1}(:); y = pd.ParameterValues{2}(:); h = plot (ax, x, y, ';;b-'); xgap = (x(end) - x(1)) * 0.1; xlim (ax, [x(1)-xgap, x(end)+xgap]); xlabel ('Data'); ylabel ('Probability'); return endif ## Handle special case of triangular distribution if (strcmpi (pd.DistributionCode, 'tri')) lb = pd.A; ub = pd.C; xmin = lb - (ub - lb) * 0.1; xmax = ub + (ub - lb) * 0.1; x = [lb:(ub-lb)/100:ub]'; y = pdf (pd, x); h = plot (ax, x, y, ';;r-', 'linewidth', 2); xlim (ax, [xmin, xmax]); xlabel ('Data'); ylabel ('PDF'); return endif ## Handle special case of log-uniform and uniform distributions if (any (strcmpi (pd.DistributionCode, {'logu', 'unif'}))) lb = pd.Lower; ub = pd.Upper; xmin = lb - (ub - lb) * 0.1; xmax = ub + (ub - lb) * 0.1; x = [lb:(ub-lb)/100:ub]'; y = pdf (pd, x); h = plot (ax, x, y, ';;r-', 'linewidth', 2); xlim (ax, [xmin, xmax]); xlabel ('Data'); ylabel ('PDF'); return endif ## Compute plot boundaries [lb, ub, xmin, xmax] = compute_boundaries (pd); ## Check for fitted distribution if (isempty (pd.InputData)) # fixed parameters, no data ## Compute stem or line for PDF if (DistType) x = [floor(lb):ceil(ub)]'; p = cdf (pd, x); else x = [lb:(ub-lb)/100:ub]'; p = cdf (pd, x); endif ## Plot if (Discrete) h = stairs (ax, x, p, 'color', 'r'); xlim (ax, [lb-0.5, ub+0.5]); ylim (ax, [0, 1]); xlabel ('Data'); ylabel ('CDF'); else h = plot (ax, x, p, ';;r-', 'linewidth', 2); xlim (ax, [xmin, xmax]); ylim (ax, [0, 1]); xlabel ('Data'); ylabel ('CDF'); endif else # fitted distribution, data available ## Expand frequency vector (if necessary) if (any (pd.InputData.freq != 1)) x = expand_freq (pd.InputData.data, pd.InputData.freq); else x = pd.InputData.data; endif ## Compute the stairs for data [yy, xx, ~, ~, eid] = cdfcalc (x); n = length (xx); ## Create vectors for plotting nidx = reshape (repmat (1:n, 2, 1), 2*n, 1); xCDF = [-Inf; xx(nidx); Inf]; yCDF = [0; 0; yy(1+nidx)]; ## Compute stairs or line for CDF if (DistType) x = [min(x):max(x)]'; p = cdf (pd, x); else x = [xmin:(xmax-xmin)/100:xmax]'; p = cdf (pd, x); endif ## Plot if (DistType) h(2) = plot (ax, xCDF, yCDF, ';;b-'); xlim (ax, [xmin, xmax]); ylim (ax, [0, 1]); hold on; if (Discrete) h(1) = stem (ax, x, p, 'color', 'r'); else h(1) = plot (ax, x, p, ';;r-'); endif xlabel ('Data'); ylabel ('CDF'); hold off; else h(2) = plot (ax, xCDF, yCDF, ';;b-'); xlim (ax, [xmin, xmax]); ylim (ax, [0, 1]); hold on; h(1) = plot (ax, x, p, ';;r-', 'linewidth', 2); xlabel ('Data'); ylabel ('CDF'); hold off; endif endif endfunction function h = plot_prob (pd, ax, DistType, Discrete) ## Expand frequency vector (if necessary) if (any (pd.InputData.freq != 1)) x = expand_freq (pd.InputData.data, pd.InputData.freq); else x = pd.InputData.data; endif ## Compute the probabilities for data n = rows (x); y = icdf (pd, ([1:n]' - 0.5) / n); x = sort (x); ## Plot reference line X = Y = [x(1); x(end)]; h(2) = line (ax, X, Y, 'LineStyle', '-.', 'Marker', 'none', 'color', 'red'); hold on; h(1) = plot (ax, x, y, 'LineStyle', 'none', 'Marker', '+', 'color', 'blue'); ## Plot labels ylabel 'Probability' xlabel 'Data' ## Plot grid p = [0.001, 0.005, 0.01, 0.02, 0.05, 0.10, 0.25, 0.5, ... 0.75, 0.90, 0.95, 0.98, 0.99, 0.995, 0.999]; label = {'0.001', '0.005', '0.01', '0.02', '0.05', '0.10', '0.25', '0.50', ... '0.75', '0.90', '0.95', '0.98', '0.99', '0.995', '0.999'}; tick = icdf (pd, p); set (ax, 'ytick', tick, 'yticklabel', label); ## Compute plot boundaries [~, ~, xmin, xmax] = compute_boundaries (pd); ## Set view range with a bit of space around data ymin = icdf (pd, 0.25 ./ n); ymax = icdf (pd, (n - 0.25) ./ n); set (ax, 'ylim', [ymin, ymax], 'xlim', [xmin, xmax]); grid (ax, 'on'); box (ax, 'off'); hold off; endfunction ## Evaluate the family negative log likelihood at a full parameter vector. ## The family function takes (params, x, censor, freq) or (params, x, freq); ## dispatch on what it declares rather than on the distribution's own ## CensoringAllowed, since Burr does not allow censoring yet still takes the ## four-argument form, and passing the frequencies positionally into the ## censoring slot marks every observation censored. function nll = like_value (fname, params, pd) if (strcmp (fname, 'gplike')) ## GPLIKE takes the two estimated parameters and assumes a zero location, ## as MATLAB's does, so the fixed THETA is shifted out of the data instead ## of being passed with them. nll = feval (fname, params(1:2), pd.InputData.data - params(3), ... pd.InputData.freq); elseif (nargin (fname) > 3) nll = feval (fname, params, pd.InputData.data, ... pd.InputData.cens, pd.InputData.freq); else nll = feval (fname, params, pd.InputData.data, pd.InputData.freq); endif endfunction ## Negative log likelihood as a function of the free parameters only. The ## search is unconstrained, so parameters outside their own range are rejected ## here: the family likelihood may well return a finite value there, and for ## some families it grows without bound as they run away. function nll = like_free (pf, p0, freeidx, fname, pd) if (any (pf(:)' < pd.ParameterRange(1,freeidx)) || any (pf(:)' > pd.ParameterRange(2,freeidx))) nll = Inf; return; endif p = p0; p(freeidx) = pf; nll = like_value (fname, p, pd); if (! isreal (nll) || ! isfinite (nll)) nll = Inf; endif endfunction %!test ## A no-op test to avoid reporting this abstact class in the pkg test summary ## as a file with no tests, since an abstract class is by definition uncallable ## and thus untestable except through its consumers. statistics-release-1.9.2/inst/Distribution_Classes/+prob/RayleighDistribution.m000066400000000000000000001151651524624707500300300ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef RayleighDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.RayleighDistribution ## ## Rayleigh probability distribution object. ## ## A @code{prob.RayleighDistribution} object consists of parameters, a model ## description, and sample data for a Rayleigh probability distribution. ## ## The Rayleigh distribution is a continuous probability distribution for ## nonnegative random variables. It is often used to model the magnitude of ## a vector in two dimensions where the components are normally distributed ## with zero mean and equal variance. It is defined by scale parameter ## @var{B}. ## ## @var{B} is the @math{sigma} of the usual mathematical notation. The ## @code{rayl*} functions name the same quantity @var{sigma}; this class ## follows MATLAB. ## ## There are several ways to create a @code{prob.RayleighDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.RayleighDistribution (@var{B})} ## to create a Rayleigh distribution with fixed parameter value @var{B}. ## @item Use the static method @qcode{prob.RayleighDistribution.fit (@var{x}, ## @var{censor}, @var{freq})} to fit a distribution to the data in @var{x} ## using the same input arguments as the @code{raylfit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the Rayleigh distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rayleigh_distribution} ## ## @seealso{fitdist, makedist, raylcdf, raylinv, raylpdf, raylrnd, raylfit, ## rayllike, raylstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.RayleighDistribution} {property} B ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the ## Rayleigh distribution. You can access the @qcode{B} ## property using dot name assignment. ## ## @end deftp B endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.RayleighDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Rayleigh'; ## -*- texinfo -*- ## @deftp {prob.RayleighDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 1; ## -*- texinfo -*- ## @deftp {prob.RayleighDistribution} {property} ParameterNames ## ## Names of parameters ## ## A cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'B'}; ## -*- texinfo -*- ## @deftp {prob.RayleighDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Scale'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'rayl'; ParameterRange = [realmin; Inf]; ParameterLogCI = true; endproperties properties(GetAccess = public , SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.RayleighDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{B} ## property. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.RayleighDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.RayleighDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.RayleighDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only ## meaningful when the distribution was fitted to data. If the distribution ## object was created with fixed parameters, or a parameter of a fitted ## distribution is modified, then all elements of the variance-covariance ## are zero. This property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.RayleighDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.RayleighDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {@var{pd} =} RayleighDistribution (@var{B}) ## @deftypefnx {prob.RayleighDistribution} {@var{pd} =} RayleighDistribution () ## ## Create a @code{prob.RayleighDistribution} object. ## ## @var{B} is the distribution parameter, which the class help describes. ## Called with no arguments the parameter takes its default, @var{B} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = RayleighDistribution (B) if (nargin == 0) B = 1; endif checkparams (B); this.InputData = []; this.IsTruncated = false; this.ParameterValues = B; this.ParameterIsFixed = true; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Rayleigh distribution'); endfunction function disp (this) __disp__ (this, 'Rayleigh distribution'); endfunction function this = set.B (this, B) checkparams (B); this.InputData = []; this.ParameterValues(1) = B; this.ParameterIsFixed = true; this.ParameterCovariance = zeros (this.NumParameters); endfunction function B = get.B (this) B = this.ParameterValues(1); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.RayleighDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = raylcdf (x, this.B); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= raylcdf (lx, this.B); p(! (lb | ub)) /= diff (raylcdf ([lx, ux], this.B)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = raylcdf (this.Truncation(1), this.B); up = raylcdf (this.Truncation(2), this.B); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = raylinv (np, this.B); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = raylinv (p, this.B); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = raylstat (this.B); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = raylcdf ([lx, ux], this.B); m = raylinv (sum (Fa_b) / 2, this.B); else m = this.B .* sqrt (2 * log (2)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = rayllike (this.B, this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.RayleighDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = raylpdf (x, this.B); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (raylcdf ([lx, ux], this.B)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.RayleighDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.RayleighDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.RayleighDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.RayleighDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.RayleighDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.RayleighDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.RayleighDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the Rayleigh distribution, @qcode{@var{pnum} = 1} selects ## the parameter @qcode{B}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.RayleighDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.RayleighDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.RayleighDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{betarnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = raylcdf (this.Truncation(1), this.B); up = raylcdf (this.Truncation(2), this.B); u = unifrnd (lp, up, varargin{:}); r = sqrt (-2 .* log (1 - u) .* this.B .^ 2); else r = raylrnd (this.B, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. If @var{pd} is fitted to data ## with @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif ## Check boundaries and constrain within support [0, Inf) lower(lower < 0) = 0; this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = true; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.RayleighDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = raylstat (this.B); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif ## Fit data [phat, pci] = raylfit (x, alpha, censor, freq); [~, acov] = rayllike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.RayleighDistribution.makeFitted ... (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) B = phat(1); pd = prob.RayleighDistribution (B); pd.ParameterCI = pci; pd.ParameterIsFixed = false; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (B) if (! (isscalar (B) && isnumeric (B) && isreal (B) && isfinite (B) && B > 0)) error ("RayleighDistribution: SIGMA must be a positive real scalar.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Rayleigh distribution with %! ## parameter B = 2. Fit a Rayleigh distribution to this data and plot %! ## a PDF of the fitted distribution superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('Rayleigh', 'B', 2) %! data = random (pd_fixed, 5000, 1); %! pd_fitted = fitdist (data, 'Rayleigh') %! plot (pd_fitted) %! msg = 'Fitted Rayleigh distribution with B = %0.2f'; %! title (sprintf (msg, pd_fitted.B)) ## Test output %!shared pd, t %! pd = prob.RayleighDistribution; %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.3935, 0.8647, 0.9889, 0.9997, 1], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.9202, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4, NaN]), [0.6753, 0.8647, 0.9889, 0.9997, NaN], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4, NaN]), [0, 0, 0.9202, 1, NaN], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0.6680, 1.0108, 1.3537, 1.7941, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.1083, 2.2402, 2.4135, 2.6831, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 1.0108, 1.3537, 1.7941, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.2402, 2.4135, 2.6831, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 0.9066, 1e-4); %!assert_equal (iqr (t), 0.4609, 1e-4); %!assert_equal (mean (pd), 1.2533, 1e-4); %!assert_equal (mean (t), 2.4169, 1e-4); %!assert_equal (median (pd), 1.1774, 1e-4); %!assert_equal (median (t), 2.3198, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0, 0.6065, 0.2707, 0.0333, 0.0013, 0], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 2.0050, 0.2469, 0.0099, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1.5, NaN]), [0, 0.4870, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1.5, NaN]), [0, 0, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 0.6551, 1e-4); %!assert_equal (std (t), 0.3591, 1e-4); %!assert_equal (var (pd), 0.4292, 1e-4); %!assert_equal (var (t), 0.1290, 1e-4); %!test %! ## The profile over the first free parameter: 101 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [1.2; 0.4; 3.1; 0.7; 2.5; 1.8; 0.3; 4.2; 1.1; 0.9; ... %! 2.2; 0.6; 1.5; 3.7; 0.8; 2.9; 1.3; 0.5; 2.0; 1.6]; %! pd = fitdist (x, 'Rayleigh'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 101]); %! assert_equal (size (other), [101, 0]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.RayleighDistribution' constructor %!error ... %! prob.RayleighDistribution (0) %!error ... %! prob.RayleighDistribution (-1) %!error ... %! prob.RayleighDistribution (Inf) %!error ... %! prob.RayleighDistribution (i) %!error ... %! prob.RayleighDistribution ('a') %!error ... %! prob.RayleighDistribution ([1, 2]) %!error ... %! prob.RayleighDistribution (NaN) ## 'cdf' method %!error ... %! cdf (prob.RayleighDistribution, 2, 'uper') %!error ... %! cdf (prob.RayleighDistribution, 2, 3) ## 'paramci' method %!shared x %! x = raylrnd (1, [1, 100]); %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'parameter', 'B', 'alpha', {0.05}) %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'parameter', {'B', 'param'}) %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'B', 'param'}) %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'alpha', 0.01, 'parameter', 'param') %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.RayleighDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'B', 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.RayleighDistribution, 'Parent') %!error ... %! plot (prob.RayleighDistribution, 'PlotType', 12) %!error ... %! plot (prob.RayleighDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.RayleighDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.RayleighDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.RayleighDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.RayleighDistribution, 'Discrete', {true}) %!error ... %! plot (prob.RayleighDistribution, 'Parent', 12) %!error ... %! plot (prob.RayleighDistribution, 'Parent', 'hax') %!error ... %! plot (prob.RayleighDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.RayleighDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.RayleighDistribution, 2) %!error ... %! proflik (prob.RayleighDistribution.fit (x), 3) %!error ... %! proflik (prob.RayleighDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.RayleighDistribution.fit (x), {1}) %!error ... %! proflik (prob.RayleighDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.RayleighDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.RayleighDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.RayleighDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.RayleighDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.RayleighDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.RayleighDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.RayleighDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.RayleighDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.RayleighDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.RayleighDistribution) %!error ... %! truncate (prob.RayleighDistribution, 2) %!error ... %! truncate (prob.RayleighDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.RayleighDistribution (1); %! pd(2) = prob.RayleighDistribution (3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/RicianDistribution.m000066400000000000000000001223731524624707500274700ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef RicianDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.RicianDistribution ## ## Rician probability distribution object. ## ## A @code{prob.RicianDistribution} object consists of parameters, a model ## description, and sample data for a Rician probability distribution. ## ## The Rician distribution is a continuous probability distribution that ## models the magnitude of a signal in the presence of Gaussian noise. It is ## defined by noncentrality parameter @var{s} and scale parameter @var{sigma}. ## ## There are several ways to create a @code{prob.RicianDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.RicianDistribution (@var{s}, @var{sigma})} ## to create a Rician distribution with fixed parameter values @var{s} and ## @var{sigma}. ## @item Use the static method @qcode{prob.RicianDistribution.fit (@var{x}, ## @var{censor}, @var{freq}, @var{options})} to fit a distribution to data ## @var{x}. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the Rician distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rice_distribution} ## ## @seealso{fitdist, makedist, ricecdf, riceinv, ricepdf, ricernd, ricefit, ## ricelike, ricestat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.RicianDistribution} {property} s ## ## Noncentrality parameter ## ## A non-negative scalar value characterizing the noncentrality of the ## Rician distribution. You can access the @qcode{s} property using dot ## name assignment. ## ## @end deftp s ## -*- texinfo -*- ## @deftp {prob.RicianDistribution} {property} sigma ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the Rician ## distribution. You can access the @qcode{sigma} property using dot name ## assignment. ## ## @end deftp sigma endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.RicianDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Rician'; ## -*- texinfo -*- ## @deftp {prob.RicianDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.RicianDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'s', 'sigma'}; ## -*- texinfo -*- ## @deftp {prob.RicianDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Noncentrality', 'Scale'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'rice'; ParameterRange = [0, realmin; Inf, Inf]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.RicianDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{s} and @qcode{sigma} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.RicianDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.RicianDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.RicianDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only ## meaningful when the distribution was fitted to data. If the distribution ## object was created with fixed parameters, or a parameter of a fitted ## distribution is modified, then all elements of the variance-covariance ## are zero. This property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.RicianDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.RicianDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {@var{pd} =} RicianDistribution (@var{s}, @var{sigma}) ## @deftypefnx {prob.RicianDistribution} {@var{pd} =} RicianDistribution () ## ## Create a @code{prob.RicianDistribution} object. ## ## @var{s} and @var{sigma} are the distribution parameters, which the class ## help describes. Called with no arguments the parameters take their ## defaults, @var{s} 1 and @var{sigma} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = RicianDistribution (s, sigma) if (nargin == 0) s = 1; sigma = 1; endif checkparams (s, sigma); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [s, sigma]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Rician distribution'); endfunction function disp (this) __disp__ (this, 'Rician distribution'); endfunction function this = set.s (this, s) checkparams (s, this.sigma); this.InputData = []; this.ParameterValues(1) = s; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function s = get.s (this) s = this.ParameterValues(1); endfunction function this = set.sigma (this, sigma) checkparams (this.s, sigma); this.InputData = []; this.ParameterValues(2) = sigma; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function sigma = get.sigma (this) sigma = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.RicianDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = ricecdf (x, this.s, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= ricecdf (lx, this.s, this.sigma); p(! (lb | ub)) /= diff (ricecdf ([lx, ux], this.s, this.sigma)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = ricecdf (this.Truncation(1), this.s, this.sigma); up = ricecdf (this.Truncation(2), this.s, this.sigma); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = riceinv (np, this.s, this.sigma); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = riceinv (p, this.s, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = ricestat (this.s, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = ricecdf ([lx, ux], this.s, this.sigma); m = riceinv (sum (Fa_b) / 2, this.s, this.sigma); else m = riceinv (0.5, this.s, this.sigma); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = ricelike ([this.s, this.sigma], this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.RicianDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = ricepdf (x, this.s, this.sigma); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (ricecdf ([lx, ux], this.s, this.sigma)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.RicianDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.RicianDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.RicianDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.RicianDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.RicianDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.RicianDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.RicianDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the Rician distribution, @qcode{@var{pnum} = 1} selects the ## parameter @qcode{s} and @qcode{@var{pnum} = 2} selects the parameter ## @qcode{sigma}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.RicianDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.RicianDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.RicianDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{ricernd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (ricecdf ([lx, ux], this.s, this.sigma)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = ricernd (this.s, this.sigma, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = ricernd (this.s, this.sigma, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. If @var{pd} is fitted to data ## with @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.RicianDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = ricestat (this.s, this.sigma); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{4}; endif ## Fit data [phat, pci] = ricefit (x, alpha, censor, freq, options); [~, acov] = ricelike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.RicianDistribution.makeFitted ... (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) s = phat(1); sigma = phat(2); pd = prob.RicianDistribution (s, sigma); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (s, sigma) if (! (isscalar (s) && isnumeric (s) && isreal (s) && isfinite (s) && s >= 0 )) error ("RicianDistribution: S must be a non-negative real scalar.") endif if (! (isscalar (sigma) && isnumeric (sigma) && isreal (sigma) && isfinite (sigma) && sigma > 0)) error ("RicianDistribution: SIGMA must be a positive real scalar.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Rician distribution with %! ## parameters s = 2 and sigma = 1. Fit a Rician distribution to this data and %! ## plot a PDF of the fitted distribution superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('Rician', 's', 2, 'sigma', 1) %! data = random (pd_fixed, 5000, 1); %! pd_fitted = fitdist (data, 'Rician') %! plot (pd_fitted) %! msg = 'Fitted Rician distribution with s = %0.2f and sigma = %0.2f'; %! title (sprintf (msg, pd_fitted.s, pd_fitted.sigma)) ## Test output %!shared pd, t %! pd = prob.RicianDistribution; %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.2671, 0.7310, 0.9563, 0.9971, 0.9999], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.8466, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4, NaN]), [0.5120, 0.7310, 0.9563, 0.9971, NaN], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4, NaN]), [0, 0, 0.8466, 1, NaN], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0.8501, 1.2736, 1.6863, 2.2011, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.1517, 2.3296, 2.5545, 2.8868, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 1.2736, 1.6863, 2.2011, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.3296, 2.5545, 2.8868, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 1.0890, 1e-4); %!assert_equal (iqr (t), 0.5928, 1e-4); %!assert_equal (mean (pd), 1.5486, 1e-4); %!assert_equal (mean (t), 2.5380, 1e-4); %!assert_equal (median (pd), 1.4755, 1e-4); %!assert_equal (median (t), 2.4341, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0, 0.4658, 0.3742, 0.0987, 0.0092, 0.0003], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 1.4063, 0.3707, 0.0346, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1.5, NaN]), [0, 0.4864, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1.5, NaN]), [0, 0, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 0.7758, 1e-4); %!assert_equal (std (t), 0.4294, 1e-4); %!assert_equal (var (pd), 0.6019, 1e-4); %!assert_equal (var (t), 0.1844, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. The %! ## sample is Rician: fitting arbitrary positive data drives s to its own %! ## boundary, and the confidence interval it profiles over degenerates. %! x = [0.584700; 0.962174; 1.201400; 1.388590; 1.548120; 1.690860; ... %! 1.822800; 1.947740; 2.068370; 2.186810; 2.304930; 2.424520; ... %! 2.547550; 2.676370; 2.814120; 2.965420; 3.137930; 3.346330; ... %! 3.626640; 4.133390]; %! pd = fitdist (x, 'Rician'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 1]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.RicianDistribution' constructor %!error ... %! prob.RicianDistribution (-eps, 1) %!error ... %! prob.RicianDistribution (-1, 1) %!error ... %! prob.RicianDistribution (Inf, 1) %!error ... %! prob.RicianDistribution (i, 1) %!error ... %! prob.RicianDistribution ('a', 1) %!error ... %! prob.RicianDistribution ([1, 2], 1) %!error ... %! prob.RicianDistribution (NaN, 1) %!error ... %! prob.RicianDistribution (1, 0) %!error ... %! prob.RicianDistribution (1, -1) %!error ... %! prob.RicianDistribution (1, Inf) %!error ... %! prob.RicianDistribution (1, i) %!error ... %! prob.RicianDistribution (1, 'a') %!error ... %! prob.RicianDistribution (1, [1, 2]) %!error ... %! prob.RicianDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.RicianDistribution, 2, 'uper') %!error ... %! cdf (prob.RicianDistribution, 2, 3) ## 'paramci' method %!shared x %! x = gevrnd (1, 1, 1, [1, 100]); %!error ... %! paramci (prob.RicianDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.RicianDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.RicianDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.RicianDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.RicianDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.RicianDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.RicianDistribution.fit (x), 'parameter', 's', 'alpha', {0.05}) %!error ... %! paramci (prob.RicianDistribution.fit (x), 'parameter', {'s', 'sigma', 'param'}) %!error ... %! paramci (prob.RicianDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'s', 'sigma', 'param'}) %!error ... %! paramci (prob.RicianDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.RicianDistribution.fit (x), 'alpha', 0.01, 'parameter', 'param') %!error ... %! paramci (prob.RicianDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.RicianDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.RicianDistribution.fit (x), 'alpha', 0.01, 'parameter', 's', ... %! 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.RicianDistribution, 'Parent') %!error ... %! plot (prob.RicianDistribution, 'PlotType', 12) %!error ... %! plot (prob.RicianDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.RicianDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.RicianDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.RicianDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.RicianDistribution, 'Discrete', {true}) %!error ... %! plot (prob.RicianDistribution, 'Parent', 12) %!error ... %! plot (prob.RicianDistribution, 'Parent', 'hax') %!error ... %! plot (prob.RicianDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.RicianDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.RicianDistribution, 2) %!error ... %! proflik (prob.RicianDistribution.fit (x), 3) %!error ... %! proflik (prob.RicianDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.RicianDistribution.fit (x), {1}) %!error ... %! proflik (prob.RicianDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.RicianDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.RicianDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.RicianDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.RicianDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.RicianDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.RicianDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.RicianDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.RicianDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.RicianDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.RicianDistribution) %!error ... %! truncate (prob.RicianDistribution, 2) %!error ... %! truncate (prob.RicianDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.RicianDistribution (1, 1); %! pd(2) = prob.RicianDistribution (1, 3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/StableDistribution.m000066400000000000000000001026551524624707500274760ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef StableDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.StableDistribution ## ## Stable probability distribution object. ## ## A @code{prob.StableDistribution} object consists of parameters, a model ## description, and sample data for a stable probability distribution. ## ## The stable distribution is a continuous probability distribution family ## closed under linear combinations, generalizing the normal, Cauchy, and Levy ## distributions. It is parameterized, in the Nolan @qcode{S0} ## parameterization, by a tail index (first shape parameter) @var{alpha} in ## @math{(0, 2]}, a skewness (second shape parameter) @var{beta} in ## @math{[-1, 1]}, a scale parameter @var{gam} greater than zero, and a ## location parameter @var{delta}. ## ## There are several ways to create a @code{prob.StableDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.StableDistribution (@var{alpha}, ## @var{beta}, @var{gam}, @var{delta})} to create a stable distribution with ## fixed parameter values @var{alpha}, @var{beta}, @var{gam}, and @var{delta}. ## @item Use the static method @qcode{prob.StableDistribution.fit (@var{x}, ## @var{alpha}, @var{freq}, @var{options})} to fit a distribution to the data ## in @var{x} using the same input arguments as the @code{stblfit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Fitting is by maximum likelihood. Because the stable density has no closed ## form, it is evaluated by numerical inversion of the characteristic function, ## which makes fitting considerably slower than for the closed-form ## distributions. ## ## Further information about the stable distribution can be found at ## @url{https://en.wikipedia.org/wiki/Stable_distribution} ## ## @seealso{fitdist, makedist, stblpdf, stblcdf, stblinv, stblrnd, stblfit, ## stbllike} ## @end deftp properties (Dependent = true) ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} alpha ## ## Tail index (first shape parameter) ## ## A scalar value in the range @math{(0, 2]} characterizing the tail ## behaviour of the stable distribution. You can access the @qcode{alpha} ## property using dot name assignment. ## ## @end deftp alpha ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} beta ## ## Skewness (second shape parameter) ## ## A scalar value in the range @math{[-1, 1]} characterizing the skewness of ## the stable distribution. You can access the @qcode{beta} property using ## dot name assignment. ## ## @end deftp beta ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} gam ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the stable ## distribution. You can access the @qcode{gam} property using dot name ## assignment. ## ## @end deftp gam ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} delta ## ## Location parameter ## ## A scalar value characterizing the location of the stable distribution. ## You can access the @qcode{delta} property using dot name assignment. ## ## @end deftp delta endproperties properties (GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Stable'; ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 4; ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{4*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {"alpha", "beta", "gam", "delta"}; ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{4*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {"First shape parameter", ... "Second shape parameter", "Scale", "Location"}; endproperties properties (GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = "stbl"; ParameterRange = [realmin, -1, realmin, -Inf; 2, 1, Inf, Inf]; ParameterLogCI = [false, false, false, false]; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{4*1} numeric vector containing the values of the distribution ## parameters, matching the order in @qcode{ParameterNames}. This property ## is read-only; use dot name assignment on the @qcode{alpha}, @qcode{beta}, ## @qcode{gam}, and @qcode{delta} properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} Truncation ## ## Truncation interval ## ## A two-element numeric vector with the truncation interval, if the ## distribution is truncated. This property is read-only. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} IsTruncated ## ## Flag for truncated distribution ## ## A logical scalar that is true when the distribution is truncated. This ## property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{4*4} numeric matrix containing the variance-covariance of the ## distribution parameters. This property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} ParameterIsFixed ## ## Flags for fixed parameters ## ## A @math{4*1} logical vector specifying which parameters are held fixed ## rather than estimated. This property is read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.StableDistribution} {property} InputData ## ## Data used for fitting the distribution ## ## A structure containing the data used to fit the distribution. It is empty ## unless the distribution was fitted with @code{fitdist} or the static ## @code{fit} method. This property is read-only. ## ## @end deftp InputData endproperties properties (GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods (Hidden) ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {@var{pd} =} StableDistribution (@var{alpha}, @var{beta}, @var{gam}, @var{delta}) ## @deftypefnx {prob.StableDistribution} {@var{pd} =} StableDistribution () ## ## Create a @code{prob.StableDistribution} object. ## ## @var{alpha}, @var{beta}, @var{gam} and @var{delta} are the distribution ## parameters, which the class help describes. Called with no arguments the ## parameters take their defaults, @var{alpha} 2, @var{beta} 0, @var{gam} 1 ## and @var{delta} 0. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = StableDistribution (alpha, beta, gam, delta) if (nargin == 0) alpha = 2; beta = 0; gam = 1; delta = 0; endif checkparams (alpha, beta, gam, delta); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [alpha, beta, gam, delta]; this.ParameterIsFixed = [true, true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) __disp__ (this, "Stable distribution"); endfunction function disp (this) __disp__ (this, "Stable distribution"); endfunction function this = set.alpha (this, alpha) checkparams (alpha, this.beta, this.gam, this.delta); this.InputData = []; this.ParameterValues(1) = alpha; this.ParameterIsFixed = [true, true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function alpha = get.alpha (this) alpha = this.ParameterValues(1); endfunction function this = set.beta (this, beta) checkparams (this.alpha, beta, this.gam, this.delta); this.InputData = []; this.ParameterValues(2) = beta; this.ParameterIsFixed = [true, true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function beta = get.beta (this) beta = this.ParameterValues(2); endfunction function this = set.gam (this, gam) checkparams (this.alpha, this.beta, gam, this.delta); this.InputData = []; this.ParameterValues(3) = gam; this.ParameterIsFixed = [true, true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function gam = get.gam (this) gam = this.ParameterValues(3); endfunction function this = set.delta (this, delta) checkparams (this.alpha, this.beta, this.gam, delta); this.InputData = []; this.ParameterValues(4) = delta; this.ParameterIsFixed = [true, true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function delta = get.delta (this) delta = this.ParameterValues(4); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.StableDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{"upper"}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. The optional @qcode{"upper"} flag computes the upper tail ## probability. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif if (nargin > 2 && strcmpi (uflag, "upper")) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, "upper")) error ("cdf: invalid argument for upper tail."); else utail = false; endif p = stblcdf (x, this.alpha, this.beta, this.gam, this.delta); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= stblcdf (lx, this.alpha, this.beta, this.gam, ... this.delta); p(! (lb | ub)) /= diff (stblcdf ([lx, ux], this.alpha, this.beta, ... this.gam, this.delta)); endif if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = stblcdf (this.Truncation(1), this.alpha, this.beta, this.gam, ... this.delta); up = stblcdf (this.Truncation(2), this.alpha, this.beta, this.gam, ... this.delta); p(p < 0 | p > 1) = NaN; np = lp + (up - lp) .* p; x = stblinv (np, this.alpha, this.beta, this.gam, this.delta); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = stblinv (p, this.alpha, this.beta, this.gam, this.delta); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. The mean is @qcode{NaN} for ## @code{@var{alpha} <= 1}, where it is undefined. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); elseif (this.alpha == 2) m = this.delta; elseif (this.alpha > 1) m = this.delta - this.beta .* this.gam .* tan (pi .* this.alpha ./ 2); else m = NaN; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = stblcdf ([lx, ux], this.alpha, this.beta, this.gam, this.delta); m = stblinv (sum (Fa_b) / 2, this.alpha, this.beta, this.gam, ... this.delta); else m = stblinv (0.5, this.alpha, this.beta, this.gam, this.delta); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. It ## returns an empty value when @var{pd} is not fitted to data. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = stbllike ([this.alpha, this.beta, this.gam, this.delta], ... this.InputData.data, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.StableDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes the ## confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise the parameter values are returned in both rows. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability density function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = stblpdf (x, this.alpha, this.beta, this.gam, this.delta); if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); y(x < lx | x > ux) = 0; y /= diff (stblcdf ([lx, ux], this.alpha, this.beta, this.gam, ... this.delta)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.StableDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.StableDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots the probability density function (PDF) of ## the probability distribution object @var{pd}. Name-value pair arguments ## select the plotted function and its appearance, as documented in ## @code{__plot__}. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.StableDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.StableDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.StableDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.StableDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.StableDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the stable distribution, @qcode{@var{pnum} = 1} selects the tail index ## @qcode{alpha}, @qcode{@var{pnum} = 2} selects the skewness @qcode{beta}, ## @qcode{@var{pnum} = 3} selects the scale @qcode{gam}, and ## @qcode{@var{pnum} = 4} selects the location @qcode{delta}. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.StableDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.StableDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.StableDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}, following the size conventions of ## @code{stblrnd}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (stblcdf ([lx, ux], this.alpha, this.beta, ... this.gam, this.delta)); nsize = fix (2 * ratio * ps); r = stblrnd (this.alpha, this.beta, this.gam, this.delta, nsize, 1); r(r < lx | r > ux) = []; while (numel (r) < ps) r = [r; stblrnd(this.alpha, this.beta, this.gam, this.delta, ... nsize, 1)]; r(r < lx | r > ux) = []; endwhile idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = stblrnd (this.alpha, this.beta, this.gam, this.delta, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. It is @qcode{NaN} for ## @code{@var{alpha} < 2}, where the variance is infinite. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif s = sqrt (var (this)); endfunction ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns ## the probability distribution @var{pd} truncated to the interval with ## lower limit @var{lower} and upper limit @var{upper}. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.StableDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the probability ## distribution object, @var{pd}. It is @qcode{NaN} for @code{@var{alpha} < ## 2}, where the variance is infinite. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); elseif (this.alpha == 2) v = 2 .* this.gam .^ 2; else v = NaN; endif endfunction endmethods methods (Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) freq = []; else freq = varargin{2}; endif if (nargin < 4) options.Display = "off"; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{3}; endif ## Fit data [phat, pci] = stblfit (x, alpha, freq, options); [~, acov] = stbllike (phat, x, freq); ## Create fitted distribution object pd = prob.StableDistribution.makeFitted (phat, pci, acov, x, freq); endfunction function pd = makeFitted (phat, pci, acov, x, freq) alpha = phat(1); beta = phat(2); gam = phat(3); delta = phat(4); pd = prob.StableDistribution (alpha, beta, gam, delta); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false, false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ("data", x, "cens", [], "freq", freq); endfunction endmethods endclassdef function checkparams (alpha, beta, gam, delta) if (! (isscalar (alpha) && isnumeric (alpha) && isreal (alpha) ... && alpha > 0 && alpha <= 2)) error ("StableDistribution: ALPHA must be a real scalar in (0, 2]."); endif if (! (isscalar (beta) && isnumeric (beta) && isreal (beta) ... && beta >= -1 && beta <= 1)) error ("StableDistribution: BETA must be a real scalar in [-1, 1]."); endif if (! (isscalar (gam) && isnumeric (gam) && isreal (gam) ... && isfinite (gam) && gam > 0)) error ("StableDistribution: GAM must be a positive real scalar."); endif if (! (isscalar (delta) && isnumeric (delta) && isreal (delta) ... && isfinite (delta))) error ("StableDistribution: DELTA must be a real scalar."); endif endfunction %!demo %! ## Create a stable distribution and plot its pdf %! pd = makedist ("Stable", "alpha", 1.5, "beta", 0.5, "gam", 1, "delta", 0); %! plot (pd); %! title ("Stable distribution, alpha = 1.5, beta = 0.5"); ## Test output against MATLAB (created via makedist) %!shared pd %! pd = makedist ("Stable", "alpha", 1.5, "beta", 0.5, "gam", 1, "delta", 0); %!test %! assert_equal (pd.alpha, 1.5); %! assert_equal (pd.beta, 0.5); %! assert_equal (pd.gam, 1); %! assert_equal (pd.delta, 0); %! assert_equal (pd.DistributionName, "Stable"); %! assert_equal (pd.NumParameters, 4); %!test %! x = -5:5; %! exp_p = [0.00961772128347771, 0.0143422747723476, 0.0257902242195547, ... %! 0.0657154294128386, 0.201576145758624, 0.462186560100778, ... %! 0.712063555515659, 0.855535196378772, 0.921201224725992, ... %! 0.951409668616683, 0.966845678836178]; %! assert_equal (cdf (pd, x), exp_p, 1e-8); %!test %! assert_equal (icdf (pd, [0.1, 0.5, 0.9]), ... %! [-1.63127009138493, 0.133853042315326, 2.58231785139714], 1e-6); %!test # mean, variance, median of a skewed stable (alpha = 1.5) %! assert_equal (mean (pd), 0.5, 1e-12); %! assert_equal (isnan (var (pd)), true); %! assert_equal (median (pd), 0.133853042315326, 1e-6); ## alpha = 2 is the normal distribution with variance 2*gam^2 %!test %! pn = makedist ("Stable", "alpha", 2, "beta", 0, "gam", 1, "delta", 0); %! assert_equal (mean (pn), 0); %! assert_equal (var (pn), 2, 1e-12); %! assert_equal (std (pn), sqrt (2), 1e-12); %! assert_equal (pdf (pn, 0:2), normpdf (0:2, 0, sqrt (2)), 1e-12); ## alpha <= 1 has no finite mean %!test %! pc = makedist ("Stable", "alpha", 0.8, "beta", 0.5, "gam", 1, "delta", 0); %! assert_equal (isnan (mean (pc)), true); %! assert_equal (isnan (var (pc)), true); %! assert_equal (median (pc), 0.250487323305453, 1e-5); ## Scale and location, and a truncated distribution %!test %! ps = makedist ("Stable", "alpha", 1.5, "beta", 0.5, "gam", 2, "delta", 3); %! assert_equal (mean (ps), 4, 1e-12); %! assert_equal (median (ps), 3.26770608463065, 1e-6); %!test # truncation renormalizes and bounds the support %! pt = truncate (pd, -1, 3); %! assert_equal (pt.IsTruncated, true); %! assert_equal (cdf (pt, [-2, 3]), [0, 1]); %! assert_equal (isfinite (mean (pt)), true); %! r = random (pt, 100, 1); %! assert_equal (all (r >= -1 & r <= 3, 'all'), true); %!test %! ## The profile over alpha: 21 grid values, one row of OTHER per value, and %! ## the likelihood peaking at the fitted estimate. The sample is stable. %! x = [-4.481370; -2.439510; -1.787770; -1.391000; -1.095410; -0.852136; ... %! -0.639452; -0.445603; -0.263227; -0.087082; 0.087082; 0.263227; ... %! 0.445603; 0.639452; 0.852136; 1.095410; 1.391000; 1.787770; ... %! 2.439510; 4.481370]; %! pd = fitdist (x, 'Stable'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 3]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation %!error ... %! proflik (fitdist ([0.3; -1.2; 0.8; 1.5; -0.4; 0.2; -0.9; 1.1; 0.6; -0.3; ... %! 1.8; -1.5; 0.4; 0.9; -0.7; 1.2; -0.2; 0.5; -1.1; 0.7], ... %! 'Stable'), 1) %!error ... %! prob.StableDistribution (2.5, 0, 1, 0) %!error ... %! prob.StableDistribution (1.5, 2, 1, 0) %!error ... %! prob.StableDistribution (1.5, 0, 0, 0) %!error ... %! prob.StableDistribution (1.5, 0, 1, Inf) %!error ... %! cdf ([pd, pd], 1) statistics-release-1.9.2/inst/Distribution_Classes/+prob/TriangularDistribution.m000066400000000000000000000675471524624707500304060ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef TriangularDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.TriangularDistribution ## ## Triangular probability distribution object. ## ## A @code{prob.TriangularDistribution} object consists of parameters, a model ## description, and sample data for a triangular probability distribution. ## ## The triangular distribution uses the following parameters. ## ## @multitable @columnfractions 0.25 0.48 0.27 ## @headitem @var{Parameter} @tab @var{Description} @tab @var{Support} ## ## @item @qcode{A} @tab Lower limit @tab @math{-Inf < A < Inf} ## @item @qcode{B} @tab Peak location @tab @math{A <= B <= C} ## @item @qcode{C} @tab Upper limit @tab @math{C > A} ## @end multitable ## ## There are several ways to create a @code{prob.TriangularDistribution} object. ## ## @itemize ## @item Create a distribution with specified parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.TriangularDistribution (@var{A}, @var{B}, ## @var{C})} to create a triangular distribution with specified parameter ## values @var{A}, @var{B}, and @var{C}. ## @end itemize ## ## It is highly recommended to use @code{makedist} function to create ## probability distribution objects, instead of the constructor. ## ## Further information about the triangular distribution can be found ## at @url{https://en.wikipedia.org/wiki/Triangular_distribution} ## ## @seealso{makedist, tricdf, triinv, tripdf, trirnd, tristat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.TriangularDistribution} {property} A ## ## Lower limit parameter ## ## A scalar value characterizing the lower limit of the ## triangular distribution. You can access the @qcode{A} ## property using dot name assignment. ## ## @end deftp A ## -*- texinfo -*- ## @deftp {prob.TriangularDistribution} {property} B ## ## Peak location parameter ## ## A scalar value characterizing the peak location of the ## triangular distribution. You can access the @qcode{B} ## property using dot name assignment. ## ## @end deftp B ## -*- texinfo -*- ## @deftp {prob.TriangularDistribution} {property} C ## ## Upper limit parameter ## ## A scalar value characterizing the upper limit of the ## triangular distribution. You can access the @qcode{C} ## property using dot name assignment. ## ## @end deftp C endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.TriangularDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Triangular'; ## -*- texinfo -*- ## @deftp {prob.TriangularDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 3; ## -*- texinfo -*- ## @deftp {prob.TriangularDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{3*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'A', 'B', 'C'}; ## -*- texinfo -*- ## @deftp {prob.TriangularDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{3*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Lower limit', 'Peak location', 'Upper limit'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'tri'; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.TriangularDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{3*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{A}, @qcode{B}, and ## @qcode{C} properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.TriangularDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.TriangularDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.TriangularDistribution} {@var{pd} =} TriangularDistribution (@var{A}, @var{B}, @var{C}) ## @deftypefnx {prob.TriangularDistribution} {@var{pd} =} TriangularDistribution () ## ## Create a @code{prob.TriangularDistribution} object. ## ## @var{A}, @var{B} and @var{C} are the distribution parameters, which the ## class help describes. Called with no arguments the parameters take their ## defaults, @var{A} 0, @var{B} 0.5 and @var{C} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = TriangularDistribution (A, B, C) if (nargin == 0) A = 0; B = 0.5; C = 1; endif checkparams (A, B, C); this.IsTruncated = false; this.ParameterValues = [A, B, C]; endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Triangular distribution'); endfunction function disp (this) __disp__ (this, 'Triangular distribution'); endfunction function this = set.A (this, A) checkparams (A, this.B, this.C); this.ParameterValues(1) = A; endfunction function A = get.A (this) A = this.ParameterValues(1); endfunction function this = set.B (this, B) checkparams (this.A, B, this.C); this.ParameterValues(2) = B; endfunction function B = get.B (this) B = this.ParameterValues(2); endfunction function this = set.C (this, C) checkparams (this.A, this.B, C); this.ParameterValues(3) = C; endfunction function C = get.C (this) C = this.ParameterValues(3); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.TriangularDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.TriangularDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = tricdf (x, this.A, this.B, this.C); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= tricdf (lx, this.A, this.B, this.C); p(! (lb | ub)) /= diff (tricdf ([lx, ux], this.A, this.B, this.C)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.TriangularDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = tricdf (this.Truncation(1), this.A, this.B, this.C); up = tricdf (this.Truncation(2), this.A, this.B, this.C); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = triinv (np, this.A, this.B, this.C); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = triinv (p, this.A, this.B, this.C); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.TriangularDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.TriangularDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = tristat (this.A, this.B, this.C); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.TriangularDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = tricdf ([lx, ux], this.A, this.B, this.C); m = triinv (sum (Fa_b) / 2, this.A, this.B, this.C); else m = triinv (0.5, this.A, this.B, this.C); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.TriangularDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability density function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = tripdf (x, this.A, this.B, this.C); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (tricdf ([lx, ux], this.A, this.B, this.C)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.TriangularDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.TriangularDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.TriangularDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). ## @qcode{'cdf'} plots the cumulative density function (CDF). ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, this option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for the plot. ## If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.TriangularDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.TriangularDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.TriangularDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.TriangularDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{trirnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (tricdf ([lx, ux], this.A, this.B, this.C)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = trirnd (this.A, this.B, this.C, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = trirnd (this.A, this.B, this.C, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.TriangularDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.TriangularDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif ## Check boundaries and constrain within support [A, C] lower(lower < this.A) = this.A; upper(upper > this.C) = this.C; this.Truncation = [lower, upper]; this.IsTruncated = true; endfunction ## -*- texinfo -*- ## @deftypefn {prob.TriangularDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = tristat (this.A, this.B, this.C); endif endfunction endmethods endclassdef function checkparams (A, B, C) if (! (isscalar (A) && isnumeric (A) && isreal (A) && isfinite (A))) error ("TriangularDistribution: lower limit A must be a real scalar.") endif if (! (isscalar (B) && isnumeric (B) && isreal (B) && isfinite (B))) error ("TriangularDistribution: mode B must be a real scalar.") endif if (! (isscalar (C) && isnumeric (C) && isreal (C) && isfinite (C))) error ("TriangularDistribution: upper limit C must be a real scalar.") endif if (! (A < C)) error (strcat ("TriangularDistribution: lower limit A must", ... " be less than upper limit C.")) endif if (! (A <= B && B <= C)) error (strcat ("TriangularDistribution: mode B must be within", ... " lower limit A and upper limit C.")) endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Triangular distribution %! ## with parameters A = 0, B = 1, C = 2. Fit a Triangular distribution to %! ## this data and plot a PDF of the fitted distribution superimposed on a %! ## histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('Triangular', 'A', 0, 'B', 1, 'C', 2); %! data = random (pd_fixed, 5000, 1); %! A = min (data); %! C = mean (data); %! B = max (data); %! %! [counts, centers] = hist (data, 50); %! bin_width = centers(2) - centers(1); %! normalized_counts = counts / (sum (counts) * bin_width); %! bar (centers, normalized_counts, 1); %! hold on; %! %! x = linspace (A, B, 100); %! y = (2 * (x - A) / (C - A) .* (x <= C)) + (2 * (B - x) / (B - C) .* (x > C)); %! %! plot (x, y, 'r-', 'LineWidth', 2); %! %! msg = sprintf ("Fitted Triangular distribution with A = %0.2f, C = %0.2f, B = %0.2f", A, C, B); %! title (msg); %! %! hold off; ## Test output %!shared pd, t %! pd = prob.TriangularDistribution (0, 3, 5); %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.0667, 0.2667, 0.6000, 0.9000, 1], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.5263, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4, NaN]), [0.1500, 0.2667, 0.6, 0.9, NaN], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4, NaN]), [0, 0, 0.5263, 1, NaN], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 1.7321, 2.4495, 3, 3.5858, 5], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.4290, 2.7928, 3.1203, 3.4945, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 2.4495, 3, 3.5858, 5, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.7928, 3.1203, 3.4945, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 1.4824, 1e-4); %!assert_equal (iqr (t), 0.8678, 1e-4); %!assert_equal (mean (pd), 2.6667, 1e-4); %!assert_equal (mean (t), 2.9649, 1e-4); %!assert_equal (median (pd), 2.7386, 1e-4); %!assert_equal (median (t), 2.9580, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0, 0.1333, 0.2667, 0.4, 0.2, 0], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 0.4211, 0.6316, 0.3158, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1.5, NaN]), [0, 0.2, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1.5, NaN]), [0, 0, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 1.0274, 1e-4); %!assert_equal (std (t), 0.5369, 1e-4); %!assert_equal (var (pd), 1.0556, 1e-4); %!assert_equal (var (t), 0.2882, 1e-4); ## Test input validation ## 'prob.TriangularDistribution' constructor %!error ... %! prob.TriangularDistribution (i, 1, 2) %!error ... %! prob.TriangularDistribution (Inf, 1, 2) %!error ... %! prob.TriangularDistribution ([1, 2], 1, 2) %!error ... %! prob.TriangularDistribution ('a', 1, 2) %!error ... %! prob.TriangularDistribution (NaN, 1, 2) %!error ... %! prob.TriangularDistribution (1, i, 2) %!error ... %! prob.TriangularDistribution (1, Inf, 2) %!error ... %! prob.TriangularDistribution (1, [1, 2], 2) %!error ... %! prob.TriangularDistribution (1, 'a', 2) %!error ... %! prob.TriangularDistribution (1, NaN, 2) %!error ... %! prob.TriangularDistribution (1, 2, i) %!error ... %! prob.TriangularDistribution (1, 2, Inf) %!error ... %! prob.TriangularDistribution (1, 2, [1, 2]) %!error ... %! prob.TriangularDistribution (1, 2, 'a') %!error ... %! prob.TriangularDistribution (1, 2, NaN) %!error ... %! prob.TriangularDistribution (1, 1, 1) %!error ... %! prob.TriangularDistribution (1, 0.5, 2) ## 'cdf' method %!error ... %! cdf (prob.TriangularDistribution, 2, 'uper') %!error ... %! cdf (prob.TriangularDistribution, 2, 3) ## 'plot' method %!error ... %! plot (prob.TriangularDistribution, 'Parent') %!error ... %! plot (prob.TriangularDistribution, 'PlotType', 12) %!error ... %! plot (prob.TriangularDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.TriangularDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.TriangularDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.TriangularDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.TriangularDistribution, 'Discrete', {true}) %!error ... %! plot (prob.TriangularDistribution, 'Parent', 12) %!error ... %! plot (prob.TriangularDistribution, 'Parent', 'hax') %!error ... %! plot (prob.TriangularDistribution, 'invalidNAME', 'pdf') %!error <'probability' PlotType is not supported for 'Triangular'.> ... %! plot (prob.TriangularDistribution, 'PlotType', 'probability') ## 'truncate' method %!error ... %! truncate (prob.TriangularDistribution) %!error ... %! truncate (prob.TriangularDistribution, 2) %!error ... %! truncate (prob.TriangularDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.TriangularDistribution (0, 1, 2); %! pd(2) = prob.TriangularDistribution (0, 1, 2); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error pdf (pd, 1) %!error plot (pd) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/UniformDistribution.m000066400000000000000000000641521524624707500277020ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef UniformDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.UniformDistribution ## ## Continuous uniform probability distribution object. ## ## A @code{prob.UniformDistribution} object consists of parameters, a model ## description, and sample data for a uniform probability distribution. ## ## The uniform distribution is a continuous probability distribution that ## models random variables that are equally likely to take any value within a ## specified interval defined by the lower limit @var{Lower} and upper limit ## @var{Upper}. ## ## There are several ways to create a @code{prob.UniformDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.UniformDistribution (@var{Lower}, ## @var{Upper})} to create a uniform distribution with fixed parameter ## values @var{Lower} and @var{Upper}. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor. ## ## Further information about the continuous uniform distribution can be found ## at @url{https://en.wikipedia.org/wiki/Continuous_uniform_distribution} ## ## @seealso{fitdist, makedist, unifcdf, unifinv, unifpdf, unifrnd, unifit, ## unifstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.UniformDistribution} {property} Lower ## ## Lower limit parameter ## ## A scalar value characterizing the lower bound of the uniform ## distribution. You can access the @qcode{Lower} property using dot ## name assignment. ## ## @end deftp Lower ## -*- texinfo -*- ## @deftp {prob.UniformDistribution} {property} Upper ## ## Upper limit parameter ## ## A scalar value characterizing the upper bound of the uniform ## distribution. You can access the @qcode{Upper} property using dot ## name assignment. ## ## @end deftp Upper endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.UniformDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Uniform'; ## -*- texinfo -*- ## @deftp {prob.UniformDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.UniformDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'Lower', 'Upper'}; ## -*- texinfo -*- ## @deftp {prob.UniformDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Lower limit', 'Upper limit'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = false; DistributionCode = 'unif'; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.UniformDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{Lower} and @qcode{Upper} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.UniformDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.UniformDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.UniformDistribution} {@var{pd} =} UniformDistribution (@var{Lower}, @var{Upper}) ## @deftypefnx {prob.UniformDistribution} {@var{pd} =} UniformDistribution () ## ## Create a @code{prob.UniformDistribution} object. ## ## @var{Lower} and @var{Upper} are the distribution parameters, which the ## class help describes. Called with no arguments the parameters take their ## defaults, @var{Lower} 0 and @var{Upper} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = UniformDistribution (Lower, Upper) if (nargin == 0) Lower = 0; Upper = 1; endif checkparams (Lower, Upper); this.IsTruncated = false; this.ParameterValues = [Lower, Upper]; endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Uniform distribution (continuous)'); endfunction function disp (this) __disp__ (this, 'Uniform distribution (continuous)'); endfunction function this = set.Lower (this, Lower) checkparams (Lower, this.Upper); this.ParameterValues(1) = Lower; endfunction function Lower = get.Lower (this) Lower = this.ParameterValues(1); endfunction function this = set.Upper (this, Upper) checkparams (this.Lower, Upper); this.ParameterValues(2) = Upper; endfunction function Upper = get.Upper (this) Upper = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.UniformDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.UniformDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = unifcdf (x, this.Lower, this.Upper); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= unifcdf (lx, this.Lower, this.Upper); p(! (lb | ub)) /= diff (unifcdf ([lx, ux], this.Lower, this.Upper)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.UniformDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = unifcdf (this.Truncation(1), this.Lower, this.Upper); up = unifcdf (this.Truncation(2), this.Lower, this.Upper); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = unifinv (np, this.Lower, this.Upper); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = unifinv (p, this.Lower, this.Upper); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.UniformDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.UniformDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = unifstat (this.Lower, this.Upper); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.UniformDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = unifcdf ([lx, ux], this.Lower, this.Upper); m = unifinv (sum (Fa_b) / 2, this.Lower, this.Upper); else m = unifstat (this.Lower, this.Upper); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.UniformDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = unifpdf (x, this.Lower, this.Upper); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (unifcdf ([lx, ux], this.Lower, this.Upper)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.UniformDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.UniformDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.UniformDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.UniformDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.UniformDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.UniformDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.UniformDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{unifrnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) r = unifrnd (this.Truncation(1), this.Truncation(2), varargin{:}); else r = unifrnd (this.Lower, this.Upper, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.UniformDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.UniformDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. If @var{pd} is fitted to data ## with @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif ## Check boundaries and constrain within support [Lower, Upper] lower(lower < this.Lower) = this.Lower; upper(upper > this.Upper) = this.Upper; this.Truncation = [lower, upper]; this.IsTruncated = true; endfunction ## -*- texinfo -*- ## @deftypefn {prob.UniformDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = unifstat (this.Lower, this.Upper); endif endfunction endmethods endclassdef function checkparams (Lower, Upper) if (! (isscalar (Lower) && isnumeric (Lower) && isreal (Lower) && isfinite (Lower))) error ("UniformDistribution: LOWER must be a real scalar.") endif if (! (isscalar (Upper) && isnumeric (Upper) && isreal (Upper) && isfinite (Upper))) error ("UniformDistribution: UPPER must be a real scalar.") endif if (! (Lower < Upper)) error ("UniformDistribution: LOWER must be less than UPPER.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Uniform distribution with %! ## parameters Lower = 0 and Upper = 10. Create a Uniform distribution with these %! ## parameters and plot its PDF superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ('Uniform', 'Lower', 0, 'Upper', 10); %! data = random (pd, 5000, 1); %! %! x = linspace (pd.Lower - 1, pd.Upper + 1, 500); %! y = pdf (pd, x); %! plot (x, y, 'r-', 'LineWidth', 2); %! hold on; %! %! [counts, centers] = hist (data, 50); %! bin_width = centers(2) - centers(1); %! normalized_counts = counts / (sum (counts) * bin_width); %! bar (centers, normalized_counts, 1); %! %! msg = 'Uniform distribution with Lower = %0.2f and Upper = %0.2f'; %! title (sprintf (msg, pd.Lower, pd.Upper)); %! legend ('PDF', 'Histogram', 'location', 'northeast'); %! %! hold off; ## Test output %!shared pd, t %! pd = prob.UniformDistribution (0, 5); %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.2, 0.4, 0.6, 0.8, 1], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.5, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4, NaN]), [0.3, 0.4, 0.6, 0.8, NaN], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4, NaN]), [0, 0, 0.5, 1, NaN], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 1, 2, 3, 4, 5], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.4, 2.8, 3.2, 3.6, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 2, 3, 4, 5, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.8, 3.2, 3.6, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 2.5, 1e-14); %!assert_equal (iqr (t), 1, 1e-14); %!assert_equal (mean (pd), 2.5, 1e-14); %!assert_equal (mean (t), 3, 1e-14); %!assert_equal (median (pd), 2.5, 1e-14); %!assert_equal (median (t), 3, 1e-14); %!assert_equal (pdf (pd, [0:5]), [0.2, 0.2, 0.2, 0.2, 0.2, 0.2], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 0.5, 0.5, 0.5, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1.5, NaN]), [0, 0.2, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1.5, NaN]), [0, 0, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 1.4434, 1e-4); %!assert_equal (std (t), 0.5774, 1e-4); %!assert_equal (var (pd), 2.0833, 1e-4); %!assert_equal (var (t), 0.3333, 1e-4); ## Test input validation ## 'prob.UniformDistribution' constructor %!error ... %! prob.UniformDistribution (i, 1) %!error ... %! prob.UniformDistribution (Inf, 1) %!error ... %! prob.UniformDistribution ([1, 2], 1) %!error ... %! prob.UniformDistribution ('a', 1) %!error ... %! prob.UniformDistribution (NaN, 1) %!error ... %! prob.UniformDistribution (1, i) %!error ... %! prob.UniformDistribution (1, Inf) %!error ... %! prob.UniformDistribution (1, [1, 2]) %!error ... %! prob.UniformDistribution (1, 'a') %!error ... %! prob.UniformDistribution (1, NaN) %!error ... %! prob.UniformDistribution (2, 1) ## 'cdf' method %!error ... %! cdf (prob.UniformDistribution, 2, 'uper') %!error ... %! cdf (prob.UniformDistribution, 2, 3) ## 'plot' method %!error ... %! plot (prob.UniformDistribution, 'Parent') %!error ... %! plot (prob.UniformDistribution, 'PlotType', 12) %!error ... %! plot (prob.UniformDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.UniformDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.UniformDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.UniformDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.UniformDistribution, 'Discrete', {true}) %!error ... %! plot (prob.UniformDistribution, 'Parent', 12) %!error ... %! plot (prob.UniformDistribution, 'Parent', 'hax') %!error ... %! plot (prob.UniformDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.UniformDistribution, 'PlotType', 'probability') ## 'truncate' method %!error ... %! truncate (prob.UniformDistribution) %!error ... %! truncate (prob.UniformDistribution, 2) %!error ... %! truncate (prob.UniformDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.UniformDistribution (0, 1); %! pd(2) = prob.UniformDistribution (0, 2); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error pdf (pd, 1) %!error plot (pd) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/WeibullDistribution.m000066400000000000000000001221231524624707500276570ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef WeibullDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.WeibullDistribution ## ## Weibull probability distribution object. ## ## A @code{prob.WeibullDistribution} object consists of parameters, a model ## description, and sample data for a Weibull probability distribution. ## ## The Weibull distribution is a continuous probability distribution that ## models the time to failure of materials or the lifetime of mechanical ## systems. It is defined by scale parameter @var{A} and shape ## parameter @var{B}. ## ## @var{A} is the @math{lambda} of the usual mathematical notation and ## @var{B} is its @math{k}. The @code{wbl*} functions name the same two ## quantities @var{lambda} and @var{k}; this class follows MATLAB. ## ## There are several ways to create a @code{prob.WeibullDistribution} object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.WeibullDistribution (@var{A}, ## @var{B})} to create a Weibull distribution with fixed parameter ## values @var{A} and @var{B}. ## @item Use the static method @qcode{prob.WeibullDistribution.fit (@var{x}, ## @var{alpha}, @var{censor}, @var{freq})} to fit a distribution to the ## data in @var{x} using the same input arguments as the @code{wblfit} ## function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the Weibull distribution can be found at ## @url{https://en.wikipedia.org/wiki/Weibull_distribution} ## ## @seealso{fitdist, makedist, wblcdf, wblinv, wblpdf, wblrnd, wblfit, ## wbllike, wblstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.WeibullDistribution} {property} A ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the ## Weibull distribution. You can access the @qcode{A} ## property using dot name assignment. ## ## @end deftp A ## -*- texinfo -*- ## @deftp {prob.WeibullDistribution} {property} B ## ## Shape parameter ## ## A positive scalar value characterizing the shape of the ## Weibull distribution. You can access the @qcode{B} ## property using dot name assignment. ## ## @end deftp B endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.WeibullDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 'Weibull'; ## -*- texinfo -*- ## @deftp {prob.WeibullDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 2; ## -*- texinfo -*- ## @deftp {prob.WeibullDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'A', 'B'}; ## -*- texinfo -*- ## @deftp {prob.WeibullDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{2*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Scale', 'Shape'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'wbl'; ParameterRange = [realmin, realmin; Inf, Inf]; ParameterLogCI = [true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.WeibullDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{2*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{A} and @qcode{B} ## properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.WeibullDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.WeibullDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.WeibullDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{2*2} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only ## meaningful when the distribution was fitted to data. If the distribution ## object was created with fixed parameters, or a parameter of a fitted ## distribution is modified, then all elements of the variance-covariance ## are zero. This property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.WeibullDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*2} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.WeibullDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {@var{pd} =} WeibullDistribution (@var{A}, @var{B}) ## @deftypefnx {prob.WeibullDistribution} {@var{pd} =} WeibullDistribution () ## ## Create a @code{prob.WeibullDistribution} object. ## ## @var{A} and @var{B} are the distribution parameters, which the class help ## describes. Called with no arguments the parameters take their defaults, ## @var{A} 1 and @var{B} 1. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = WeibullDistribution (A, B) if (nargin == 0) A = 1; B = 1; endif checkparams (A, B); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [A, B]; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 'Weibull distribution'); endfunction function disp (this) __disp__ (this, 'Weibull distribution'); endfunction function this = set.A (this, A) checkparams (A, this.B); this.InputData = []; this.ParameterValues(1) = A; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function A = get.A (this) A = this.ParameterValues(1); endfunction function this = set.B (this, B) checkparams (this.A, B); this.InputData = []; this.ParameterValues(2) = B; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function B = get.B (this) B = this.ParameterValues(2); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.WeibullDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = wblcdf (x, this.A, this.B); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= wblcdf (lx, this.A, this.B); p(! (lb | ub)) /= diff (wblcdf ([lx, ux], this.A, this.B)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = wblcdf (this.Truncation(1), this.A, this.B); up = wblcdf (this.Truncation(2), this.A, this.B); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = wblinv (np, this.A, this.B); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = wblinv (p, this.A, this.B); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = wblstat (this.A, this.B); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = wblcdf ([lx, ux], this.A, this.B); m = wblinv (sum (Fa_b) / 2, this.A, this.B); else m = wblinv (0.5, this.A, this.B); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = wbllike ([this.A, this.B], this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.WeibullDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = wblpdf (x, this.A, this.B); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (wblcdf ([lx, ux], this.A, this.B)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.WeibullDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.WeibullDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.WeibullDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.WeibullDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.WeibullDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.WeibullDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.WeibullDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the Weibull distribution, @qcode{@var{pnum} = 1} selects the ## parameter @qcode{A} and @qcode{@var{pnum} = 2} selects the ## parameter @qcode{B}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.WeibullDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.WeibullDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.WeibullDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{wblrnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (wblcdf ([lx, ux], this.A, this.B)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = wblrnd (this.A, this.B, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = wblrnd (this.A, this.B, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. If @var{pd} is fitted to data ## with @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.WeibullDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = wblstat (this.A, this.B); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{4}; endif ## Fit data [phat, pci] = wblfit (x, alpha, censor, freq, options); [~, acov] = wbllike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.WeibullDistribution.makeFitted ... (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) A = phat(1); B = phat(2); pd = prob.WeibullDistribution (A, B); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (A, B) if (! (isscalar (A) && isnumeric (A) && isreal (A) && isfinite (A) && A > 0 )) error ("WeibullDistribution: LAMBDA must be a positive real scalar.") endif if (! (isscalar (B) && isnumeric (B) && isreal (B) && isfinite (B) && B > 0)) error ("WeibullDistribution: K must be a positive real scalar.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a Weibull distribution with %! ## parameters A = 1 and B = 2. Fit a Weibull distribution to this data and plot %! ## a PDF of the fitted distribution superimposed on a histogram of a data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('Weibull', 'A', 1, 'B', 2) %! data = random (pd_fixed, 5000, 1); %! pd_fitted = fitdist (data, 'Weibull') %! plot (pd_fitted) %! msg = 'Fitted Weibull distribution with A = %0.2f and B = %0.2f'; %! title (sprintf (msg, pd_fitted.A, pd_fitted.B)) ## Test output %!shared pd, t %! pd = prob.WeibullDistribution; %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0, 0.6321, 0.8647, 0.9502, 0.9817, 0.9933], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.7311, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4, NaN]), [0.7769, 0.8647, 0.9502, 0.9817, NaN], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4, NaN]), [0, 0, 0.7311, 1, NaN], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [0, 0.2231, 0.5108, 0.9163, 1.6094, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.1899, 2.4244, 2.7315, 3.1768, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, 0.5108, 0.9163, 1.6094, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.4244, 2.7315, 3.1768, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 1.0986, 1e-4); %!assert_equal (iqr (t), 0.8020, 1e-4); %!assert_equal (mean (pd), 1, 1e-14); %!assert_equal (mean (t), 2.6870, 1e-4); %!assert_equal (median (pd), 0.6931, 1e-4); %!assert_equal (median (t), 2.5662, 1e-4); %!assert_equal (pdf (pd, [0:5]), [1, 0.3679, 0.1353, 0.0498, 0.0183, 0.0067], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 1.1565, 0.4255, 0.1565, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1.5, NaN]), [0, 0.2231, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1.5, NaN]), [0, 0, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 1, 1e-14); %!assert_equal (std (t), 0.5253, 1e-4); %!assert_equal (var (pd), 1, 1e-14); %!assert_equal (var (t), 0.2759, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [1.2; 0.4; 3.1; 0.7; 2.5; 1.8; 0.3; 4.2; 1.1; 0.9; ... %! 2.2; 0.6; 1.5; 3.7; 0.8; 2.9; 1.3; 0.5; 2.0; 1.6]; %! pd = fitdist (x, 'Weibull'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 1]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.WeibullDistribution' constructor %!error ... %! prob.WeibullDistribution (0, 1) %!error ... %! prob.WeibullDistribution (-1, 1) %!error ... %! prob.WeibullDistribution (Inf, 1) %!error ... %! prob.WeibullDistribution (i, 1) %!error ... %! prob.WeibullDistribution ('a', 1) %!error ... %! prob.WeibullDistribution ([1, 2], 1) %!error ... %! prob.WeibullDistribution (NaN, 1) %!error ... %! prob.WeibullDistribution (1, 0) %!error ... %! prob.WeibullDistribution (1, -1) %!error ... %! prob.WeibullDistribution (1, Inf) %!error ... %! prob.WeibullDistribution (1, i) %!error ... %! prob.WeibullDistribution (1, 'a') %!error ... %! prob.WeibullDistribution (1, [1, 2]) %!error ... %! prob.WeibullDistribution (1, NaN) ## 'cdf' method %!error ... %! cdf (prob.WeibullDistribution, 2, 'uper') %!error ... %! cdf (prob.WeibullDistribution, 2, 3) ## 'paramci' method %!shared x %! x = wblrnd (1, 1, [1, 100]); %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'parameter', 'B', 'alpha', {0.05}) %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'parameter', {'A', 'B', 'param'}) %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'A', 'B', 'param'}) %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'alpha', 0.01, 'parameter', 'param') %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.WeibullDistribution.fit (x), 'alpha', 0.01, 'parameter', 'B', ... %! 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.WeibullDistribution, 'Parent') %!error ... %! plot (prob.WeibullDistribution, 'PlotType', 12) %!error ... %! plot (prob.WeibullDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.WeibullDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.WeibullDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.WeibullDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.WeibullDistribution, 'Discrete', {true}) %!error ... %! plot (prob.WeibullDistribution, 'Parent', 12) %!error ... %! plot (prob.WeibullDistribution, 'Parent', 'hax') %!error ... %! plot (prob.WeibullDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.WeibullDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.WeibullDistribution, 2) %!error ... %! proflik (prob.WeibullDistribution.fit (x), 3) %!error ... %! proflik (prob.WeibullDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.WeibullDistribution.fit (x), {1}) %!error ... %! proflik (prob.WeibullDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.WeibullDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.WeibullDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.WeibullDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.WeibullDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.WeibullDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.WeibullDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.WeibullDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.WeibullDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.WeibullDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.WeibullDistribution) %!error ... %! truncate (prob.WeibullDistribution, 2) %!error ... %! truncate (prob.WeibullDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.WeibullDistribution (1, 1); %! pd(2) = prob.WeibullDistribution (1, 3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/+prob/tLocationScaleDistribution.m000066400000000000000000001306101524624707500311600ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef tLocationScaleDistribution < prob.ProbabilityDistribution ## -*- texinfo -*- ## @deftp {statistics} prob.tLocationScaleDistribution ## ## Location-Scale Student's T probability distribution object. ## ## A @code{prob.tLocationScaleDistribution} object consists of parameters, a model ## description, and sample data for a location-scale Student's T probability ## distribution. ## ## The location-scale Student's T distribution is a continuous probability ## distribution that generalizes the standard Student's T distribution by ## including location and scale parameters. It is defined by location ## parameter ## @var{mu}, scale parameter @var{sigma}, and degrees of freedom @var{nu}. ## ## There are several ways to create a @code{prob.tLocationScaleDistribution} ## object. ## ## @itemize ## @item Fit a distribution to data using the @code{fitdist} function. ## @item Create a distribution with fixed parameter values using the ## @code{makedist} function. ## @item Use the constructor @qcode{prob.tLocationScaleDistribution (@var{mu}, ## @var{sigma}, @var{nu})} to create a location-scale Student's T distribution ## with fixed parameter values @var{mu}, @var{sigma}, and @var{nu}. ## @item Use the static method @qcode{prob.tLocationScaleDistribution.fit (@var{x}, ## @var{censor}, @var{freq}, @var{options})} to fit a distribution to the data ## in @var{x} using the same input arguments as the @code{tlsfit} function. ## @end itemize ## ## It is highly recommended to use @code{fitdist} and @code{makedist} ## functions to create probability distribution objects, instead of the class ## constructor or the aforementioned static method. ## ## Further information about the location-scale Student's T distribution can ## be found at ## @url{https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution} ## ## @seealso{fitdist, makedist, tlscdf, tlsinv, tlspdf, tlsrnd, tlsfit, ## tlslike, tlsstat} ## @end deftp properties(Dependent = true) ## -*- texinfo -*- ## @deftp {prob.tLocationScaleDistribution} {property} mu ## ## Location parameter ## ## A scalar value characterizing the location of the ## location-scale Student's T distribution. You can access the @qcode{mu} ## property using dot name assignment. ## ## @end deftp mu ## -*- texinfo -*- ## @deftp {prob.tLocationScaleDistribution} {property} sigma ## ## Scale parameter ## ## A positive scalar value characterizing the scale of the location-scale ## Student's T distribution. You can access the @qcode{sigma} property using ## dot name assignment. ## ## @end deftp sigma ## -*- texinfo -*- ## @deftp {prob.tLocationScaleDistribution} {property} nu ## ## Degrees of freedom ## ## A positive scalar value characterizing the degrees of freedom of the ## location-scale Student's T distribution. You can access the @qcode{nu} ## property using dot name assignment. ## ## @end deftp nu endproperties properties(GetAccess = public, Constant = true) ## -*- texinfo -*- ## @deftp {prob.tLocationScaleDistribution} {property} DistributionName ## ## Probability distribution name ## ## A character vector specifying the name of the probability distribution ## object. This property is read-only. ## ## @end deftp DistributionName = 't Location-Scale'; ## -*- texinfo -*- ## @deftp {prob.tLocationScaleDistribution} {property} NumParameters ## ## Number of parameters ## ## A scalar integer value specifying the number of parameters characterizing ## the probability distribution. This property is read-only. ## ## @end deftp NumParameters = 3; ## -*- texinfo -*- ## @deftp {prob.tLocationScaleDistribution} {property} ParameterNames ## ## Names of parameters ## ## A @math{3*1} cell array of character vectors with each element containing ## the name of a distribution parameter. This property is read-only. ## ## @end deftp ParameterNames = {'mu', 'sigma', 'nu'}; ## -*- texinfo -*- ## @deftp {prob.tLocationScaleDistribution} {property} ParameterDescription ## ## Description of parameters ## ## A @math{3*1} cell array of character vectors with each element containing ## a short description of a distribution parameter. This property is ## read-only. ## ## @end deftp ParameterDescription = {'Location', 'Scale', 'Degrees of Freedom'}; endproperties properties(GetAccess = public, Constant = true, Hidden) CensoringAllowed = true; DistributionCode = 'tls'; ParameterRange = [-Inf, realmin, realmin; Inf, Inf, Inf]; ParameterLogCI = [false, true, true]; endproperties properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {prob.tLocationScaleDistribution} {property} ParameterValues ## ## Distribution parameter values ## ## A @math{3*1} numeric vector containing the values of the distribution ## parameters. This property is read-only. You can change the distribution ## parameters by assigning new values to the @qcode{mu}, @qcode{sigma}, and ## @qcode{nu} properties. ## ## @end deftp ParameterValues ## -*- texinfo -*- ## @deftp {prob.tLocationScaleDistribution} {property} Truncation ## ## Truncation interval ## ## A @math{1*2} numeric vector specifying the truncation interval for the ## probability distribution. First element contains the lower boundary, ## second element contains the upper boundary. This property is read-only. ## You can only truncate a probability distribution with the ## @qcode{truncate} method. ## ## @end deftp Truncation ## -*- texinfo -*- ## @deftp {prob.tLocationScaleDistribution} {property} IsTruncated ## ## Flag for truncated probability distribution ## ## A logical scalar value specifying whether a probability distribution is ## truncated or not. This property is read-only. ## ## @end deftp IsTruncated ## -*- texinfo -*- ## @deftp {prob.tLocationScaleDistribution} {property} ParameterCovariance ## ## Covariance matrix of the parameter estimates ## ## A @math{3*3} numeric matrix containing the variance-covariance of the ## parameter estimates. Diagonal elements contain the variance of each ## estimated parameter, and non-diagonal elements contain the covariance ## between the parameter estimates. The covariance matrix is only ## meaningful when the distribution was fitted to data. If the distribution ## object was created with fixed parameters, or a parameter of a fitted ## distribution is modified, then all elements of the variance-covariance ## are zero. This property is read-only. ## ## @end deftp ParameterCovariance ## -*- texinfo -*- ## @deftp {prob.tLocationScaleDistribution} {property} ParameterIsFixed ## ## Flag for fixed parameters ## ## A @math{1*3} logical vector specifying which parameters are fixed and ## which are estimated. @qcode{true} values correspond to fixed parameters, ## @qcode{false} values correspond to parameter estimates. This property is ## read-only. ## ## @end deftp ParameterIsFixed ## -*- texinfo -*- ## @deftp {prob.tLocationScaleDistribution} {property} InputData ## ## Data used for fitting a probability distribution ## ## A scalar structure containing the following fields: ## @itemize ## @item @qcode{data}: a numeric vector containing the data used for ## distribution fitting. ## @item @qcode{cens}: a numeric vector of logical values indicating ## censoring information corresponding to the elements of the data used for ## distribution fitting. If no censoring vector was used for distribution ## fitting, then this field defaults to an empty array. ## @item @qcode{freq}: a numeric vector of non-negative integer values ## containing the frequency information corresponding to the elements of the ## data used for distribution fitting. If no frequency vector was used for ## distribution fitting, then this field defaults to an empty array. ## @end itemize ## ## @end deftp InputData endproperties properties(GetAccess = public, SetAccess = protected, Hidden) ParameterCI endproperties methods(Hidden) ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {@var{pd} =} tLocationScaleDistribution (@var{mu}, @var{sigma}, @var{nu}) ## @deftypefnx {prob.tLocationScaleDistribution} {@var{pd} =} tLocationScaleDistribution () ## ## Create a @code{prob.tLocationScaleDistribution} object. ## ## @var{mu}, @var{sigma} and @var{nu} are the distribution parameters, which ## the class help describes. Called with no arguments the parameters take ## their defaults, @var{mu} 0, @var{sigma} 1 and @var{nu} 5. ## ## @code{makedist} is the usual way to create a distribution object. ## ## @end deftypefn function this = tLocationScaleDistribution (mu, sigma, nu) if (nargin == 0) mu = 0; sigma = 1; nu = 5; endif checkparams (mu, sigma, nu); this.InputData = []; this.IsTruncated = false; this.ParameterValues = [mu, sigma, nu]; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function display (this) fprintf ("%s =\n", inputname (1)); __disp__ (this, 't Location-Scale distribution'); endfunction function disp (this) __disp__ (this, 't Location-Scale distribution'); endfunction function this = set.mu (this, mu) checkparams (mu, this.sigma, this.nu); this.InputData = []; this.ParameterValues(1) = mu; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function mu = get.mu (this) mu = this.ParameterValues(1); endfunction function this = set.sigma (this, sigma) checkparams (this.mu, sigma, this.nu); this.InputData = []; this.ParameterValues(2) = sigma; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function sigma = get.sigma (this) sigma = this.ParameterValues(2); endfunction function this = set.nu (this, nu) checkparams (this.mu, this.sigma, nu); this.InputData = []; this.ParameterValues(3) = nu; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction function nu = get.nu (this) nu = this.ParameterValues(3); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {@var{p} =} cdf (@var{pd}, @var{x}) ## @deftypefnx {prob.tLocationScaleDistribution} {@var{p} =} cdf (@var{pd}, @var{x}, @qcode{'upper'}) ## ## Compute the cumulative distribution function (CDF). ## ## @code{@var{p} = cdf (@var{pd}, @var{x})} computes the CDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of ## the CDF of the probability distribution object, @var{pd}, evaluated at ## the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x, uflag) if (! isscalar (this)) error ("cdf: requires a scalar probability distribution."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) utail = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("cdf: invalid argument for upper tail."); else utail = false; endif ## Do the computations p = tlscdf (x, this.mu, this.sigma, this.nu); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; p(lb) = 0; p(ub) = 1; p(! (lb | ub)) -= tlscdf (lx, this.mu, this.sigma, this.nu); p(! (lb | ub)) /= diff (tlscdf ([lx, ux], this.mu, this.sigma, this.nu)); endif ## Apply uflag if (utail) p = 1 - p; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {@var{x} =} icdf (@var{pd}, @var{p}) ## ## Compute the inverse cumulative distribution function (iCDF). ## ## @code{@var{x} = icdf (@var{pd}, @var{p})} computes the quantile (the ## inverse of the CDF) of the probability distribution object, @var{pd}, ## evaluated at the values in @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (! isscalar (this)) error ("icdf: requires a scalar probability distribution."); endif if (this.IsTruncated) lp = tlscdf (this.Truncation(1), this.mu, this.sigma, this.nu); up = tlscdf (this.Truncation(2), this.mu, this.sigma, this.nu); ## Adjust p values within range of p @ lower limit and p @ upper limit is_nan = p < 0 | p > 1; p(is_nan) = NaN; np = lp + (up - lp) .* p; x = tlsinv (np, this.mu, this.sigma, this.nu); x(x < this.Truncation(1)) = this.Truncation(1); x(x > this.Truncation(2)) = this.Truncation(2); else x = tlsinv (p, this.mu, this.sigma, this.nu); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {@var{r} =} iqr (@var{pd}) ## ## Compute the interquartile range of a probability distribution. ## ## @code{@var{r} = iqr (@var{pd})} computes the interquartile range of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function r = iqr (this) if (! isscalar (this)) error ("iqr: requires a scalar probability distribution."); endif r = diff (icdf (this, [0.25, 0.75])); endfunction ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {@var{m} =} mean (@var{pd}) ## ## Compute the mean of a probability distribution. ## ## @code{@var{m} = mean (@var{pd})} computes the mean of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = mean (this) if (! isscalar (this)) error ("mean: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); else m = tlsstat (this.mu, this.sigma, this.nu); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {@var{m} =} median (@var{pd}) ## ## Compute the median of a probability distribution. ## ## @code{@var{m} = median (@var{pd})} computes the median of the probability ## distribution object, @var{pd}. ## ## @end deftypefn function m = median (this) if (! isscalar (this)) error ("median: requires a scalar probability distribution."); endif if (this.IsTruncated) lx = this.Truncation(1); ux = this.Truncation(2); Fa_b = tlscdf ([lx, ux], this.mu, this.sigma, this.nu); m = tlsinv (sum (Fa_b) / 2, this.mu, this.sigma, this.nu); else m = tlsstat (this.mu, this.sigma, this.nu); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {@var{nlogL} =} negloglik (@var{pd}) ## ## Compute the negative loglikelihood of a probability distribution. ## ## @code{@var{nlogL} = negloglik (@var{pd})} computes the negative ## loglikelihood of the probability distribution object, @var{pd}. ## ## @end deftypefn function nlogL = negloglik (this) if (! isscalar (this)) error ("negloglik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) nlogL = []; return endif nlogL = tlslike ([this.mu, this.sigma, this.nu], this.InputData.data, ... this.InputData.cens, this.InputData.freq); endfunction ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {@var{ci} =} paramci (@var{pd}) ## @deftypefnx {prob.tLocationScaleDistribution} {@var{ci} =} paramci (@var{pd}, @var{Name}, @var{Value}) ## ## Compute the confidence intervals for probability distribution parameters. ## ## @code{@var{ci} = paramci (@var{pd})} computes the lower and upper ## boundaries of the 95% confidence interval for each parameter of the ## probability distribution object, @var{pd}. ## ## @code{@var{ci} = paramci (@var{pd}, @var{Name}, @var{Value})} computes ## the confidence intervals with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab A scalar value in the range @math{(0,1)} ## specifying the significance level for the confidence interval. The ## default value 0.05 corresponds to a 95% confidence interval. ## ## @item @qcode{'Parameter'} @tab A character vector or a cell array of ## character vectors specifying the parameter names for which to compute ## confidence intervals. By default, @code{paramci} computes confidence ## intervals for all distribution parameters. ## @end multitable ## ## @code{paramci} is meaningful only when @var{pd} is fitted to data, ## otherwise an empty array, @qcode{[]}, is returned. ## ## @end deftypefn function ci = paramci (this, varargin) if (! isscalar (this)) error ("paramci: requires a scalar probability distribution."); endif if (isempty (this.InputData)) ci = [this.ParameterValues; this.ParameterValues]; else ci = __paramci__ (this, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {@var{y} =} pdf (@var{pd}, @var{x}) ## ## Compute the probability distribution function (PDF). ## ## @code{@var{y} = pdf (@var{pd}, @var{x})} computes the PDF of the ## probability distribution object, @var{pd}, evaluated at the values in ## @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (! isscalar (this)) error ("pdf: requires a scalar probability distribution."); endif y = tlspdf (x, this.mu, this.sigma, this.nu); if (this.IsTruncated) lx = this.Truncation(1); lb = x < lx; ux = this.Truncation(2); ub = x > ux; y(lb | ub) = 0; y(! (lb | ub)) /= diff (tlscdf ([lx, ux], this.mu, this.sigma, this.nu)); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {} plot (@var{pd}) ## @deftypefnx {prob.tLocationScaleDistribution} {} plot (@var{pd}, @var{Name}, @var{Value}) ## @deftypefnx {prob.tLocationScaleDistribution} {@var{h} =} plot (@dots{}) ## ## Plot a probability distribution object. ## ## @code{plot (@var{pd})} plots a probability density function (PDF) of the ## probability distribution object @var{pd}. If @var{pd} contains data, ## which have been fitted by @code{fitdist}, the PDF is superimposed over a ## histogram of the data. ## ## @code{plot (@var{pd}, @var{Name}, @var{Value})} specifies additional ## options with the @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PlotType'} @tab A character vector specifying the plot ## type. @qcode{'pdf'} plots the probability density function (PDF). When ## @var{pd} is fit to data, the PDF is superimposed on a histogram of the ## data. @qcode{'cdf'} plots the cumulative density function (CDF). When ## @var{pd} is fit to data, the CDF is superimposed over an empirical CDF. ## @qcode{'probability'} plots a probability plot using a CDF of the data ## and a CDF of the fitted probability distribution. This option is ## available only when @var{pd} is fitted to data. ## ## @item @qcode{'Discrete'} @tab A logical scalar to specify whether to ## plot the PDF or CDF of a discrete distribution object as a line plot or a ## stem plot, by specifying @qcode{false} or @qcode{true}, respectively. By ## default, it is @qcode{true} for discrete distributions and @qcode{false} ## for continuous distributions. When @var{pd} is a continuous distribution ## object, option is ignored. ## ## @item @qcode{'Parent'} @tab An axes graphics object for plot. If ## not specified, the @code{plot} function plots into the current axes or ## creates a new axes object if one does not exist. ## @end multitable ## ## @code{@var{h} = plot (@dots{})} returns a graphics handle to the plotted ## objects. ## ## @end deftypefn function [varargout] = plot (this, varargin) if (! isscalar (this)) error ("plot: requires a scalar probability distribution."); endif h = __plot__ (this, false, varargin{:}); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}) ## @deftypefnx {prob.tLocationScaleDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.tLocationScaleDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}) ## @deftypefnx {prob.tLocationScaleDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}, @var{pnum}, @var{setparam}, @qcode{'Display'}, @var{display}) ## @deftypefnx {prob.tLocationScaleDistribution} {[@var{nlogL}, @var{param}] =} proflik (@var{pd}) ## @deftypefnx {prob.tLocationScaleDistribution} {[@var{nlogL}, @var{param}, @var{other}] =} proflik (@dots{}) ## ## Profile likelihood function for a probability distribution object. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum})} ## returns a vector @var{nlogL} of negative loglikelihood values and a ## vector @var{param} of corresponding parameter values for the parameter in ## the position indicated by @var{pnum}. By default, @code{proflik} uses ## the lower and upper bounds of the 98% confidence interval and computes ## 101 equispaced values for the selected parameter when it is the only one ## being estimated, and 21 values otherwise. @var{pd} must be fitted to ## data. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @qcode{'Display'}, @qcode{'on'})} also plots the profile likelihood ## against the default range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam})} defines a user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd}, @var{pnum}, ## @var{setparam}, @qcode{'Display'}, @qcode{'on'})} also plots the profile ## likelihood against the user-defined range of the selected parameter. ## ## @code{[@var{nlogL}, @var{param}] = proflik (@var{pd})} selects the ## first parameter that is not fixed. ## ## @code{[@var{nlogL}, @var{param}, @var{other}] = proflik (@dots{})} also ## returns a matrix @var{other} holding, in each row, the values of the ## remaining parameters that maximize the likelihood at the corresponding ## value of @var{param}. A fixed parameter keeps its own value. ## ## For the location-scale Student's T distribution, @qcode{@var{pnum} = 1} ## selects the parameter @qcode{mu}, @qcode{@var{pnum} = 2} selects the ## parameter @qcode{sigma}, and @qcode{@var{pnum} = 3} selects the ## parameter @qcode{nu}. ## ## When opted to display the profile likelihood plot, @code{proflik} also ## plots the baseline loglikelihood computed at the lower bound of the 95% ## confidence interval and estimated maximum likelihood. The latter might ## not be observable if it is outside of the used-defined range of parameter ## values. ## ## @end deftypefn function [varargout] = proflik (this, pnum, varargin) if (! isscalar (this)) error ("proflik: requires a scalar probability distribution."); endif if (isempty (this.InputData)) error ("proflik: no fitted data available."); endif if (nargin < 2) pnum = []; endif [varargout{1:nargout}] = __proflik__ (this, pnum, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {@var{r} =} random (@var{pd}) ## @deftypefnx {prob.tLocationScaleDistribution} {@var{r} =} random (@var{pd}, @var{rows}) ## @deftypefnx {prob.tLocationScaleDistribution} {@var{r} =} random (@var{pd}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {prob.tLocationScaleDistribution} {@var{r} =} random (@var{pd}, [@var{sz}]) ## ## Generate random arrays from the probability distribution object. ## ## @code{@var{r} = random (@var{pd})} returns a random number from the ## distribution object @var{pd}. ## ## When called with a single size argument, @code{tlsrnd} returns a square ## matrix with the dimension specified. When called with more than one ## scalar argument, the first two arguments are taken as the number of rows ## and columns and any further arguments specify additional matrix ## dimensions. The size may also be specified with a row vector of ## dimensions, @var{sz}. ## ## @end deftypefn function r = random (this, varargin) if (! isscalar (this)) error ("random: requires a scalar probability distribution."); endif if (this.IsTruncated) sz = [varargin{:}]; ps = prod (sz); ## Get an estimate of how many more random numbers we need to randomly ## pick the appropriate size from lx = this.Truncation(1); ux = this.Truncation(2); ratio = 1 / diff (tlscdf ([lx, ux], this.mu, this.sigma, this.nu)); nsize = fix (2 * ratio * ps); # times 2 to be on the safe side ## Generate the numbers and remove out-of-bound random samples r = tlsrnd (this.mu, this.sigma, this.nu, nsize, 1); r(r < lx | r > ux) = []; ## Randomly select the required size and reshape to requested dimensions idx = randperm (numel (r), ps); r = reshape (r(idx), sz); else r = tlsrnd (this.mu, this.sigma, this.nu, varargin{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {@var{s} =} std (@var{pd}) ## ## Compute the standard deviation of a probability distribution. ## ## @code{@var{s} = std (@var{pd})} computes the standard deviation of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function s = std (this) if (! isscalar (this)) error ("std: requires a scalar probability distribution."); endif v = var (this); s = sqrt (v); endfunction ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {@var{t} =} truncate (@var{pd}, @var{lower}, @var{upper}) ## ## Truncate a probability distribution. ## ## @code{@var{t} = truncate (@var{pd}, @var{lower}, @var{upper})} returns a ## probability distribution @var{t}, which is the probability distribution ## @var{pd} truncated to the specified interval with lower limit, ## @var{lower}, and upper limit, @var{upper}. If @var{pd} is fitted to data ## with @code{fitdist}, the returned probability distribution @var{t} is not ## fitted, does not contain any data or estimated values, and it is as it ## has been created with the @var{makedist} function, but it includes the ## truncation interval. ## ## @end deftypefn function this = truncate (this, lower, upper) if (! isscalar (this)) error ("truncate: requires a scalar probability distribution."); endif if (nargin < 3) error ("truncate: missing input argument."); elseif (lower >= upper) error ("truncate: invalid lower upper limits."); endif this.Truncation = [lower, upper]; this.IsTruncated = true; this.InputData = []; this.ParameterIsFixed = [true, true, true]; this.ParameterCovariance = zeros (this.NumParameters); endfunction ## -*- texinfo -*- ## @deftypefn {prob.tLocationScaleDistribution} {@var{v} =} var (@var{pd}) ## ## Compute the variance of a probability distribution. ## ## @code{@var{v} = var (@var{pd})} computes the variance of the ## probability distribution object, @var{pd}. ## ## @end deftypefn function v = var (this) if (! isscalar (this)) error ("var: requires a scalar probability distribution."); endif if (this.IsTruncated) fm = @(x) x .* pdf (this, x); m = integral (fm, this.Truncation(1), this.Truncation(2)); fv = @(x) ((x - m) .^ 2) .* pdf (this, x); v = integral (fv, this.Truncation(1), this.Truncation(2)); else [~, v] = tlsstat (this.mu, this.sigma, this.nu); endif endfunction endmethods methods(Static, Hidden) function pd = fit (x, varargin) ## Check input arguments if (nargin < 2) alpha = 0.05; else alpha = varargin{1}; endif if (nargin < 3) censor = []; else censor = varargin{2}; endif if (nargin < 4) freq = []; else freq = varargin{3}; endif if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else options = varargin{4}; endif ## Fit data [phat, pci] = tlsfit (x, alpha, censor, freq, options); [~, acov] = tlslike (phat, x, censor, freq); ## Create fitted distribution object pd = prob.tLocationScaleDistribution.makeFitted ... (phat, pci, acov, x, censor, freq); endfunction function pd = makeFitted (phat, pci, acov, x, censor, freq) mu = phat(1); sigma = phat(2); nu = phat(3); pd = prob.tLocationScaleDistribution (mu, sigma, nu); pd.ParameterCI = pci; pd.ParameterIsFixed = [false, false, false]; pd.ParameterCovariance = acov; pd.InputData = struct ('data', x, 'cens', censor, 'freq', freq); endfunction endmethods endclassdef function checkparams (mu, sigma, nu) if (! (isscalar (mu) && isnumeric (mu) && isreal (mu) && isfinite (mu))) error ("tLocationScaleDistribution: MU must be a real scalar.") endif if (! (isscalar (sigma) && isnumeric (sigma) && isreal (sigma) && isfinite (sigma) && sigma > 0)) error ("tLocationScaleDistribution: SIGMA must be a positive real scalar.") endif if (! (isscalar (nu) && isnumeric (nu) && isreal (nu) && isfinite (nu) && nu > 0)) error ("tLocationScaleDistribution: NU must be a positive real scalar.") endif endfunction %!demo %! ## Generate a data set of 5000 random samples from a t Location-Scale distribution %! ## with parameters mu = 0, sigma = 1, and nu = 5. Fit a t Location-Scale %! ## distribution to this data and plot a PDF of the fitted distribution %! ## superimposed on a histogram of the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ('tLocationScale', 'mu', 0, 'sigma', 1, 'nu', 5); %! data = random (pd_fixed, 5000, 1); %! pd_fitted = fitdist (data, 'tLocationScale'); %! plot (pd_fitted); %! msg = 'Fitted t Location-Scale distribution with mu = %0.2f, sigma = %0.2f, nu = %0.2f'; %! title (sprintf (msg, pd_fitted.mu, pd_fitted.sigma, pd_fitted.nu)); ## Test output %!shared pd, t %! pd = prob.tLocationScaleDistribution; %! t = truncate (pd, 2, 4); %!assert_equal (cdf (pd, [0:5]), [0.5, 0.8184, 0.9490, 0.9850, 0.9948, 0.9979], 1e-4); %!assert_equal (cdf (t, [0:5]), [0, 0, 0, 0.7841, 1, 1], 1e-4); %!assert_equal (cdf (pd, [1.5, 2, 3, 4, NaN]), [0.9030, 0.9490, 0.9850, 0.9948, NaN], 1e-4); %!assert_equal (cdf (t, [1.5, 2, 3, 4, NaN]), [0, 0, 0.7841, 1, NaN], 1e-4); %!assert_equal (icdf (pd, [0:0.2:1]), [-Inf, -0.9195, -0.2672, 0.2672, 0.9195, Inf], 1e-4); %!assert_equal (icdf (t, [0:0.2:1]), [2, 2.1559, 2.3533, 2.6223, 3.0432, 4], 1e-4); %!assert_equal (icdf (pd, [-1, 0.4:0.2:1, NaN]), [NaN, -0.2672, 0.2672, 0.9195, Inf, NaN], 1e-4); %!assert_equal (icdf (t, [-1, 0.4:0.2:1, NaN]), [NaN, 2.3533, 2.6223, 3.0432, 4, NaN], 1e-4); %!assert_equal (iqr (pd), 1.4534, 1e-4); %!assert_equal (iqr (t), 0.7139, 1e-4); %!assert_equal (mean (pd), 0, eps); %!assert_equal (mean (t), 2.6099, 1e-4); %!assert_equal (median (pd), 0, eps); %!assert_equal (median (t), 2.4758, 1e-4); %!assert_equal (pdf (pd, [0:5]), [0.3796, 0.2197, 0.0651, 0.0173, 0.0051, 0.0018], 1e-4); %!assert_equal (pdf (t, [0:5]), [0, 0, 1.4209, 0.3775, 0.1119, 0], 1e-4); %!assert_equal (pdf (pd, [-1, 1.5, NaN]), [0.2197, 0.1245, NaN], 1e-4); %!assert_equal (pdf (t, [-1, 1.5, NaN]), [0, 0, NaN], 1e-4); %!assert_equal (isequal (size (random (pd, 100, 50)), [100, 50]), true) %!assert_equal (any (random (t, 1000, 1) < 2), false); %!assert_equal (any (random (t, 1000, 1) > 4), false); %!assert_equal (std (pd), 1.2910, 1e-4); %!assert_equal (std (t), 0.4989, 1e-4); %!assert_equal (var (pd), 1.6667, 1e-4); %!assert_equal (var (t), 0.2489, 1e-4); %!test %! ## The profile over the first free parameter: 21 grid values, one row of %! ## OTHER per value, and the likelihood peaking at the fitted estimate. %! x = [0.3; -1.2; 0.8; 1.5; -0.4; 0.2; -0.9; 1.1; 0.6; -0.3; ... %! 1.8; -1.5; 0.4; 0.9; -0.7; 1.2; -0.2; 0.5; -1.1; 0.7]; %! pd = fitdist (x, 'tLocationScale'); %! [nlogL, param, other] = proflik (pd, 1); %! assert_equal (size (param), [1, 21]); %! assert_equal (size (other), [21, 2]); %! assert_equal (proflik (pd), nlogL); %! [~, imax] = max (nlogL); %! assert_equal (abs (param(imax) - pd.ParameterValues(1)) <= param(2) - param(1), true); ## Test input validation ## 'prob.tLocationScaleDistribution' constructor %!error ... %! prob.tLocationScaleDistribution (i, 1, 1) %!error ... %! prob.tLocationScaleDistribution (Inf, 1, 1) %!error ... %! prob.tLocationScaleDistribution ([1, 2], 1, 1) %!error ... %! prob.tLocationScaleDistribution ('a', 1, 1) %!error ... %! prob.tLocationScaleDistribution (NaN, 1, 1) %!error ... %! prob.tLocationScaleDistribution (0, 0, 1) %!error ... %! prob.tLocationScaleDistribution (0, -1, 1) %!error ... %! prob.tLocationScaleDistribution (0, Inf, 1) %!error ... %! prob.tLocationScaleDistribution (0, i, 1) %!error ... %! prob.tLocationScaleDistribution (0, 'a', 1) %!error ... %! prob.tLocationScaleDistribution (0, [1, 2], 1) %!error ... %! prob.tLocationScaleDistribution (0, NaN, 1) %!error ... %! prob.tLocationScaleDistribution (0, 1, 0) %!error ... %! prob.tLocationScaleDistribution (0, 1, -1) %!error ... %! prob.tLocationScaleDistribution (0, 1, Inf) %!error ... %! prob.tLocationScaleDistribution (0, 1, i) %!error ... %! prob.tLocationScaleDistribution (0, 1, 'a') %!error ... %! prob.tLocationScaleDistribution (0, 1, [1, 2]) %!error ... %! prob.tLocationScaleDistribution (0, 1, NaN) ## 'cdf' method %!error ... %! cdf (prob.tLocationScaleDistribution, 2, 'uper') %!error ... %! cdf (prob.tLocationScaleDistribution, 2, 3) ## 'paramci' method %!shared x %! x = tlsrnd (0, 1, 1, [1, 100]); %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), 'alpha') %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), 'alpha', 0) %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), 'alpha', 1) %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), 'alpha', [0.5 2]) %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), 'alpha', '') %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), 'alpha', {0.05}) %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), 'parameter', 'mu', ... %! 'alpha', {0.05}) %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), ... %! 'parameter', {'mu', 'sigma', 'nu', 'param'}) %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', {'mu', 'sigma', 'nu', 'param'}) %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), 'parameter', 'param') %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'param') %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), 'NAME', 'value') %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), 'alpha', 0.01, 'NAME', 'value') %!error ... %! paramci (prob.tLocationScaleDistribution.fit (x), 'alpha', 0.01, ... %! 'parameter', 'mu', 'NAME', 'value') ## 'plot' method %!error ... %! plot (prob.tLocationScaleDistribution, 'Parent') %!error ... %! plot (prob.tLocationScaleDistribution, 'PlotType', 12) %!error ... %! plot (prob.tLocationScaleDistribution, 'PlotType', {'pdf', 'cdf'}) %!error ... %! plot (prob.tLocationScaleDistribution, 'PlotType', 'pdfcdf') %!error ... %! plot (prob.tLocationScaleDistribution, 'Discrete', 'pdfcdf') %!error ... %! plot (prob.tLocationScaleDistribution, 'Discrete', [1, 0]) %!error ... %! plot (prob.tLocationScaleDistribution, 'Discrete', {true}) %!error ... %! plot (prob.tLocationScaleDistribution, 'Parent', 12) %!error ... %! plot (prob.tLocationScaleDistribution, 'Parent', 'hax') %!error ... %! plot (prob.tLocationScaleDistribution, 'invalidNAME', 'pdf') %!error ... %! plot (prob.tLocationScaleDistribution, 'PlotType', 'probability') ## 'proflik' method %!error ... %! proflik (prob.tLocationScaleDistribution, 2) %!error ... %! proflik (prob.tLocationScaleDistribution.fit (x), 4) %!error ... %! proflik (prob.tLocationScaleDistribution.fit (x), [1, 2]) %!error ... %! proflik (prob.tLocationScaleDistribution.fit (x), {1}) %!error ... %! proflik (prob.tLocationScaleDistribution.fit (x), 1, ones (2)) %!error ... %! proflik (prob.tLocationScaleDistribution.fit (x), 1, 'Display') %!error ... %! proflik (prob.tLocationScaleDistribution.fit (x), 1, 'Display', 1) %!error ... %! proflik (prob.tLocationScaleDistribution.fit (x), 1, 'Display', {1}) %!error ... %! proflik (prob.tLocationScaleDistribution.fit (x), 1, 'Display', {'on'}) %!error ... %! proflik (prob.tLocationScaleDistribution.fit (x), 1, 'Display', ['on'; 'on']) %!error ... %! proflik (prob.tLocationScaleDistribution.fit (x), 1, 'Display', 'onnn') %!error ... %! proflik (prob.tLocationScaleDistribution.fit (x), 1, 'NAME', 'on') %!error ... %! proflik (prob.tLocationScaleDistribution.fit (x), 1, {'NAME'}, 'on') %!error ... %! proflik (prob.tLocationScaleDistribution.fit (x), 1, {[1 2 3 4]}, 'Display', 'on') ## 'truncate' method %!error ... %! truncate (prob.tLocationScaleDistribution) %!error ... %! truncate (prob.tLocationScaleDistribution, 2) %!error ... %! truncate (prob.tLocationScaleDistribution, 4, 2) ## Catch errors when using array of probability objects with available methods %!shared pd %! pd = prob.tLocationScaleDistribution (0, 1, 1); %! pd(2) = prob.tLocationScaleDistribution (0, 1, 3); %!error cdf (pd, 1) %!error icdf (pd, 0.5) %!error iqr (pd) %!error mean (pd) %!error median (pd) %!error negloglik (pd) %!error paramci (pd) %!error pdf (pd, 1) %!error plot (pd) %!error proflik (pd, 2) %!error random (pd) %!error std (pd) %!error ... %! truncate (pd, 2, 4) %!error var (pd) statistics-release-1.9.2/inst/Distribution_Classes/doc-cache000066400000000000000000022131731524624707500242220ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 792 # name: # type: sq_string # elements: 1 # length: 11 paretotails # name: # type: sq_string # elements: 1 # length: 1277 statistics: paretotails Piecewise distribution with generalized Pareto tails. A paretotails object is a piecewise probability distribution fit to sample data. A generalized Pareto distribution (GPD) is fit to each tail of the data, below a lower quantile and above an upper quantile, while the middle of the distribution is described by the empirical cumulative distribution function of the data. This gives a smooth model for the tails, useful for extreme value analysis, together with a nonparametric description of the central region. Create a paretotails object with the constructor pt = paretotails ( x , pl , pu ) , where x is the sample data and pl and pu are the cumulative probabilities at the lower and upper tail boundaries. Data at or below the pl quantile form the lower tail, data at or above the pu quantile form the upper tail, and the rest form the middle segment. Query the fitted object with the methods cdf , pdf , icdf , random , boundary , nsegments , segment , lowerparams , and upperparams . Note: the kernel-smoothed middle option of MATLAB ( paretotails ( x , pl , pu , "kernel") ) is not yet supported; only the default empirical ( "ecdf" ) middle is available. See also: gpfit, gpcdf, gppdf, gpinv, ecdf, fitdist, GeneralizedParetoDistribution # name: # type: sq_string # elements: 1 # length: 53 Piecewise distribution with generalized Pareto tails. # name: # type: sq_string # elements: 1 # length: 25 paretotails.NumParameters # name: # type: sq_string # elements: 1 # length: 109 paretotails: property NumParameters Number of estimated parameters (two per fitted generalized Pareto tail). # name: # type: sq_string # elements: 1 # length: 72 Number of estimated parameters (two per fitted generalized Pareto tail). # name: # type: sq_string # elements: 1 # length: 23 paretotails.NumSegments # name: # type: sq_string # elements: 1 # length: 139 paretotails: property NumSegments Number of segments in the piecewise distribution (a lower tail, a middle, and an upper tail give three). # name: # type: sq_string # elements: 1 # length: 104 Number of segments in the piecewise distribution (a lower tail, a middle, and an upper tail give three). # name: # type: sq_string # elements: 1 # length: 20 paretotails.boundary # name: # type: sq_string # elements: 1 # length: 148 paretotails: [ p , q ] = boundary ( pt ) Boundary probabilities p and quantiles q of the segments of the paretotails object pt , as column vectors. # name: # type: sq_string # elements: 1 # length: 105 Boundary probabilities p and quantiles q of the segments of the paretotails object pt, as column vectors. # name: # type: sq_string # elements: 1 # length: 15 paretotails.cdf # name: # type: sq_string # elements: 1 # length: 125 paretotails: p = cdf ( pt , x ) Cumulative distribution function of the paretotails object pt evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 91 Cumulative distribution function of the paretotails object pt evaluated at the values in x. # name: # type: sq_string # elements: 1 # length: 16 paretotails.icdf # name: # type: sq_string # elements: 1 # length: 158 paretotails: x = icdf ( pt , p ) Inverse cumulative distribution function (quantile function) of the paretotails object pt evaluated at the probabilities p . # name: # type: sq_string # elements: 1 # length: 123 Inverse cumulative distribution function (quantile function) of the paretotails object pt evaluated at the probabilities p. # name: # type: sq_string # elements: 1 # length: 23 paretotails.lowerparams # name: # type: sq_string # elements: 1 # length: 151 paretotails: params = lowerparams ( pt ) Shape and scale parameters [ k , sigma ] of the generalized Pareto distribution fit to the lower tail of pt . # name: # type: sq_string # elements: 1 # length: 105 Shape and scale parameters [k, sigma] of the generalized Pareto distribution fit to the lower tail of pt. # name: # type: sq_string # elements: 1 # length: 21 paretotails.nsegments # name: # type: sq_string # elements: 1 # length: 84 paretotails: n = nsegments ( pt ) Number of segments in the paretotails object pt . # name: # type: sq_string # elements: 1 # length: 48 Number of segments in the paretotails object pt. # name: # type: sq_string # elements: 1 # length: 23 paretotails.paretotails # name: # type: sq_string # elements: 1 # length: 553 paretotails: pt = paretotails ( x , pl , pu ) paretotails: pt = paretotails ( x , pl , pu , cdffun ) Fit a piecewise distribution with generalized Pareto tails to x . pl and pu are the cumulative probabilities of the lower and upper tail boundaries, with 0 <= pl < pu <= 1 . A generalized Pareto distribution is fit by maximum likelihood to the exceedances in each tail; the middle segment uses the empirical cumulative distribution of x . cdffun selects the middle segment and defaults to "ecdf" ; the "kernel" option of MATLAB is not yet supported. # name: # type: sq_string # elements: 1 # length: 64 Fit a piecewise distribution with generalized Pareto tails to x. # name: # type: sq_string # elements: 1 # length: 15 paretotails.pdf # name: # type: sq_string # elements: 1 # length: 121 paretotails: y = pdf ( pt , x ) Probability density function of the paretotails object pt evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 87 Probability density function of the paretotails object pt evaluated at the values in x. # name: # type: sq_string # elements: 1 # length: 18 paretotails.random # name: # type: sq_string # elements: 1 # length: 233 paretotails: r = random ( pt ) paretotails: r = random ( pt , sz ) paretotails: r = random ( pt , m , n , …) Random values drawn from the paretotails object pt , by inverse transform sampling. The size arguments follow rand . # name: # type: sq_string # elements: 1 # length: 82 Random values drawn from the paretotails object pt, by inverse transform sampling. # name: # type: sq_string # elements: 1 # length: 19 paretotails.segment # name: # type: sq_string # elements: 1 # length: 249 paretotails: s = segment ( pt , x , p ) Segment indices for the paretotails object pt . Supply the data values in x (with p empty) or the cumulative probabilities in p (with x empty). Segment 1 is the lower tail, 2 the middle, and 3 the upper tail. # name: # type: sq_string # elements: 1 # length: 46 Segment indices for the paretotails object pt. # name: # type: sq_string # elements: 1 # length: 23 paretotails.upperparams # name: # type: sq_string # elements: 1 # length: 151 paretotails: params = upperparams ( pt ) Shape and scale parameters [ k , sigma ] of the generalized Pareto distribution fit to the upper tail of pt . # name: # type: sq_string # elements: 1 # length: 105 Shape and scale parameters [k, sigma] of the generalized Pareto distribution fit to the upper tail of pt. # name: # type: sq_string # elements: 1 # length: 21 prob.BetaDistribution # name: # type: sq_string # elements: 1 # length: 1388 statistics: prob.BetaDistribution Beta probability distribution object. A prob.BetaDistribution object consists of parameters, a model description, and sample data for a beta probability distribution. The beta distribution is a family of continuous probability distributions defined on the interval [0, 1] in terms of two positive parameters, denoted by alpha ( a ) and beta ( b ) , that appear as exponents of the variable and its complement to 1, respectively, and control the shape of the distribution. There are several ways to create a prob.BetaDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.BetaDistribution ( a , b ) to create a beta distribution with fixed parameter values a and b . Use the static method prob.BetaDistribution.fit ( x , alpha , freq , options ) to fit a distribution to the data in x using the same input arguments as the betafit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the beta distribution can be found at https://en.wikipedia.org/wiki/Beta_distribution See also: fitdist, makedist, betacdf, betainv, betapdf, betarnd, betafit, betalike, betastat # name: # type: sq_string # elements: 1 # length: 37 Beta probability distribution object. # name: # type: sq_string # elements: 1 # length: 38 prob.BetaDistribution.BetaDistribution # name: # type: sq_string # elements: 1 # length: 350 prob.BetaDistribution: pd = BetaDistribution ( a , b ) prob.BetaDistribution: pd = BetaDistribution () Create a prob.BetaDistribution object. a and b are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, a 1 and b 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 38 Create a prob.BetaDistribution object. # name: # type: sq_string # elements: 1 # length: 38 prob.BetaDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 187 prob.BetaDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 31 prob.BetaDistribution.InputData # name: # type: sq_string # elements: 1 # length: 569 prob.BetaDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : an empty array, since prob.BetaDistribution does not allow censoring. frequency : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 33 prob.BetaDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 207 prob.BetaDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 35 prob.BetaDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 200 prob.BetaDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 41 prob.BetaDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 618 prob.BetaDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 42 prob.BetaDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 230 prob.BetaDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 38 prob.BetaDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 286 prob.BetaDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 36 prob.BetaDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 207 prob.BetaDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 37 prob.BetaDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 282 prob.BetaDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the a and b properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 32 prob.BetaDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 350 prob.BetaDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 23 prob.BetaDistribution.a # name: # type: sq_string # elements: 1 # length: 189 prob.BetaDistribution: property a First shape parameter A positive scalar value characterizing the shape of the beta distribution. You can access the a property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 21 First shape parameter # name: # type: sq_string # elements: 1 # length: 23 prob.BetaDistribution.b # name: # type: sq_string # elements: 1 # length: 190 prob.BetaDistribution: property b Second shape parameter A positive scalar value characterizing the shape of the beta distribution. You can access the b property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 22 Second shape parameter # name: # type: sq_string # elements: 1 # length: 25 prob.BetaDistribution.cdf # name: # type: sq_string # elements: 1 # length: 399 prob.BetaDistribution: p = cdf ( pd , x ) prob.BetaDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 26 prob.BetaDistribution.icdf # name: # type: sq_string # elements: 1 # length: 248 prob.BetaDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 25 prob.BetaDistribution.iqr # name: # type: sq_string # elements: 1 # length: 195 prob.BetaDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 26 prob.BetaDistribution.mean # name: # type: sq_string # elements: 1 # length: 167 prob.BetaDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.BetaDistribution.median # name: # type: sq_string # elements: 1 # length: 175 prob.BetaDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.BetaDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 221 prob.BetaDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 29 prob.BetaDistribution.paramci # name: # type: sq_string # elements: 1 # length: 984 prob.BetaDistribution: ci = paramci ( pd ) prob.BetaDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 25 prob.BetaDistribution.pdf # name: # type: sq_string # elements: 1 # length: 208 prob.BetaDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 26 prob.BetaDistribution.plot # name: # type: sq_string # elements: 1 # length: 1536 prob.BetaDistribution: plot ( pd ) prob.BetaDistribution: plot ( pd , Name , Value ) prob.BetaDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 29 prob.BetaDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2105 prob.BetaDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.BetaDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.BetaDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.BetaDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.BetaDistribution: [ nlogL , param ] = proflik ( pd ) prob.BetaDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the beta distribution, pnum = 1 selects the parameter a and pnum = 2 selects the parameter b . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 28 prob.BetaDistribution.random # name: # type: sq_string # elements: 1 # length: 698 prob.BetaDistribution: r = random ( pd ) prob.BetaDistribution: r = random ( pd , rows ) prob.BetaDistribution: r = random ( pd , rows , cols , …) prob.BetaDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, betarnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 25 prob.BetaDistribution.std # name: # type: sq_string # elements: 1 # length: 193 prob.BetaDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 30 prob.BetaDistribution.truncate # name: # type: sq_string # elements: 1 # length: 543 prob.BetaDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 25 prob.BetaDistribution.var # name: # type: sq_string # elements: 1 # length: 173 prob.BetaDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 25 prob.BinomialDistribution # name: # type: sq_string # elements: 1 # length: 1295 statistics: prob.BinomialDistribution Binomial probability distribution object. A prob.BinomialDistribution object consists of parameters, a model description, and sample data for a binomial probability distribution. The binomial distribution is a discrete probability distribution that models the number of successes in a sequence of N independent trials, each with a probability of success p . There are several ways to create a prob.BinomialDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.BinomialDistribution ( N , p ) to create a binomial distribution with fixed parameter values N and p . Use the static method prob.BinomialDistribution.fit ( x , ntrials , alpha ) to fit a distribution to the data in x using the same input arguments as the binofit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the binomial distribution can be found at https://en.wikipedia.org/wiki/Binomial_distribution See also: fitdist, makedist, binocdf, binoinv, binopdf, binornd, binofit, binolike, binostat # name: # type: sq_string # elements: 1 # length: 41 Binomial probability distribution object. # name: # type: sq_string # elements: 1 # length: 46 prob.BinomialDistribution.BinomialDistribution # name: # type: sq_string # elements: 1 # length: 372 prob.BinomialDistribution: pd = BinomialDistribution ( N , p ) prob.BinomialDistribution: pd = BinomialDistribution () Create a prob.BinomialDistribution object. N and p are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, N 1 and p 0.5. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 42 Create a prob.BinomialDistribution object. # name: # type: sq_string # elements: 1 # length: 42 prob.BinomialDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 191 prob.BinomialDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 35 prob.BinomialDistribution.InputData # name: # type: sq_string # elements: 1 # length: 577 prob.BinomialDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : an empty array, since prob.BinomialDistribution does not allow censoring. frequency : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 37 prob.BinomialDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 211 prob.BinomialDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 27 prob.BinomialDistribution.N # name: # type: sq_string # elements: 1 # length: 204 prob.BinomialDistribution: property N Number of trials A positive integer value characterizing the number of trials in the binomial distribution. You can access the N property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 16 Number of trials # name: # type: sq_string # elements: 1 # length: 39 prob.BinomialDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 204 prob.BinomialDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 45 prob.BinomialDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 622 prob.BinomialDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 46 prob.BinomialDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 234 prob.BinomialDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 42 prob.BinomialDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 290 prob.BinomialDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 40 prob.BinomialDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 211 prob.BinomialDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 41 prob.BinomialDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 286 prob.BinomialDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the N and p properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 36 prob.BinomialDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 354 prob.BinomialDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 29 prob.BinomialDistribution.cdf # name: # type: sq_string # elements: 1 # length: 407 prob.BinomialDistribution: p = cdf ( pd , x ) prob.BinomialDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 30 prob.BinomialDistribution.icdf # name: # type: sq_string # elements: 1 # length: 252 prob.BinomialDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 29 prob.BinomialDistribution.iqr # name: # type: sq_string # elements: 1 # length: 199 prob.BinomialDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 30 prob.BinomialDistribution.mean # name: # type: sq_string # elements: 1 # length: 171 prob.BinomialDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.BinomialDistribution.median # name: # type: sq_string # elements: 1 # length: 179 prob.BinomialDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 35 prob.BinomialDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 225 prob.BinomialDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 27 prob.BinomialDistribution.p # name: # type: sq_string # elements: 1 # length: 240 prob.BinomialDistribution: property p Probability of success A scalar value in the range [0, 1] characterizing the probability of success in each trial of the binomial distribution. You can access the p property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 22 Probability of success # name: # type: sq_string # elements: 1 # length: 33 prob.BinomialDistribution.paramci # name: # type: sq_string # elements: 1 # length: 992 prob.BinomialDistribution: ci = paramci ( pd ) prob.BinomialDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 29 prob.BinomialDistribution.pdf # name: # type: sq_string # elements: 1 # length: 212 prob.BinomialDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 30 prob.BinomialDistribution.plot # name: # type: sq_string # elements: 1 # length: 1548 prob.BinomialDistribution: plot ( pd ) prob.BinomialDistribution: plot ( pd , Name , Value ) prob.BinomialDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 33 prob.BinomialDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2133 prob.BinomialDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.BinomialDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.BinomialDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.BinomialDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.BinomialDistribution: [ nlogL , param ] = proflik ( pd ) prob.BinomialDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the binomial distribution, pnum = 1 selects the parameter N and pnum = 2 selects the parameter p . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 32 prob.BinomialDistribution.random # name: # type: sq_string # elements: 1 # length: 714 prob.BinomialDistribution: r = random ( pd ) prob.BinomialDistribution: r = random ( pd , rows ) prob.BinomialDistribution: r = random ( pd , rows , cols , …) prob.BinomialDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, binornd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 29 prob.BinomialDistribution.std # name: # type: sq_string # elements: 1 # length: 197 prob.BinomialDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.BinomialDistribution.truncate # name: # type: sq_string # elements: 1 # length: 547 prob.BinomialDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 29 prob.BinomialDistribution.var # name: # type: sq_string # elements: 1 # length: 177 prob.BinomialDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.BirnbaumSaundersDistribution # name: # type: sq_string # elements: 1 # length: 1455 statistics: prob.BirnbaumSaundersDistribution Birnbaum-Saunders probability distribution object. A prob.BirnbaumSaundersDistribution object consists of parameters, a model description, and sample data for a Birnbaum-Saunders probability distribution. The Birnbaum-Saunders distribution is a continuous probability distribution that models the time to failure of materials subjected to cyclic loading. It is defined by scale parameter beta and shape parameter gamma . There are several ways to create a prob.BirnbaumSaundersDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.BirnbaumSaundersDistribution ( beta , gamma ) to create a Birnbaum-Saunders distribution with fixed parameter values beta and gamma . Use the static method prob.BirnbaumSaundersDistribution.fit ( x , alpha , censor , freq , options ) to fit a distribution to the data in x using the same input arguments as the bisafit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the Birnbaum-Saunders distribution can be found at https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution See also: fitdist, makedist, bisacdf, bisainv, bisapdf, bisarnd, bisafit, bisalike, bisastat # name: # type: sq_string # elements: 1 # length: 50 Birnbaum-Saunders probability distribution object. # name: # type: sq_string # elements: 1 # length: 62 prob.BirnbaumSaundersDistribution.BirnbaumSaundersDistribution # name: # type: sq_string # elements: 1 # length: 431 prob.BirnbaumSaundersDistribution: pd = BirnbaumSaundersDistribution ( beta , gamma ) prob.BirnbaumSaundersDistribution: pd = BirnbaumSaundersDistribution () Create a prob.BirnbaumSaundersDistribution object. beta and gamma are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, beta 1 and gamma 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 50 Create a prob.BirnbaumSaundersDistribution object. # name: # type: sq_string # elements: 1 # length: 50 prob.BirnbaumSaundersDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 199 prob.BirnbaumSaundersDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 43 prob.BirnbaumSaundersDistribution.InputData # name: # type: sq_string # elements: 1 # length: 749 prob.BirnbaumSaundersDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 45 prob.BirnbaumSaundersDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 219 prob.BirnbaumSaundersDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 47 prob.BirnbaumSaundersDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 212 prob.BirnbaumSaundersDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 53 prob.BirnbaumSaundersDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 631 prob.BirnbaumSaundersDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 54 prob.BirnbaumSaundersDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 242 prob.BirnbaumSaundersDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 50 prob.BirnbaumSaundersDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 298 prob.BirnbaumSaundersDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 48 prob.BirnbaumSaundersDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 219 prob.BirnbaumSaundersDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 49 prob.BirnbaumSaundersDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 301 prob.BirnbaumSaundersDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the beta and gamma properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 44 prob.BirnbaumSaundersDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 362 prob.BirnbaumSaundersDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 38 prob.BirnbaumSaundersDistribution.beta # name: # type: sq_string # elements: 1 # length: 214 prob.BirnbaumSaundersDistribution: property beta Scale parameter A positive scalar value characterizing the scale of the Birnbaum-Saunders distribution. You can access the beta property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 37 prob.BirnbaumSaundersDistribution.cdf # name: # type: sq_string # elements: 1 # length: 423 prob.BirnbaumSaundersDistribution: p = cdf ( pd , x ) prob.BirnbaumSaundersDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 39 prob.BirnbaumSaundersDistribution.gamma # name: # type: sq_string # elements: 1 # length: 216 prob.BirnbaumSaundersDistribution: property gamma Shape parameter A positive scalar value characterizing the shape of the Birnbaum-Saunders distribution. You can access the gamma property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Shape parameter # name: # type: sq_string # elements: 1 # length: 38 prob.BirnbaumSaundersDistribution.icdf # name: # type: sq_string # elements: 1 # length: 260 prob.BirnbaumSaundersDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 37 prob.BirnbaumSaundersDistribution.iqr # name: # type: sq_string # elements: 1 # length: 207 prob.BirnbaumSaundersDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 38 prob.BirnbaumSaundersDistribution.mean # name: # type: sq_string # elements: 1 # length: 179 prob.BirnbaumSaundersDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 40 prob.BirnbaumSaundersDistribution.median # name: # type: sq_string # elements: 1 # length: 187 prob.BirnbaumSaundersDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 43 prob.BirnbaumSaundersDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 233 prob.BirnbaumSaundersDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 41 prob.BirnbaumSaundersDistribution.paramci # name: # type: sq_string # elements: 1 # length: 1008 prob.BirnbaumSaundersDistribution: ci = paramci ( pd ) prob.BirnbaumSaundersDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 37 prob.BirnbaumSaundersDistribution.pdf # name: # type: sq_string # elements: 1 # length: 220 prob.BirnbaumSaundersDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 38 prob.BirnbaumSaundersDistribution.plot # name: # type: sq_string # elements: 1 # length: 1572 prob.BirnbaumSaundersDistribution: plot ( pd ) prob.BirnbaumSaundersDistribution: plot ( pd , Name , Value ) prob.BirnbaumSaundersDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 41 prob.BirnbaumSaundersDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2197 prob.BirnbaumSaundersDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.BirnbaumSaundersDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.BirnbaumSaundersDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.BirnbaumSaundersDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.BirnbaumSaundersDistribution: [ nlogL , param ] = proflik ( pd ) prob.BirnbaumSaundersDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the Birnbaum-Saunders distribution, pnum = 1 selects the parameter beta and pnum = 2 selects the parameter gamma . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 40 prob.BirnbaumSaundersDistribution.random # name: # type: sq_string # elements: 1 # length: 746 prob.BirnbaumSaundersDistribution: r = random ( pd ) prob.BirnbaumSaundersDistribution: r = random ( pd , rows ) prob.BirnbaumSaundersDistribution: r = random ( pd , rows , cols , …) prob.BirnbaumSaundersDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, bisarnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 37 prob.BirnbaumSaundersDistribution.std # name: # type: sq_string # elements: 1 # length: 205 prob.BirnbaumSaundersDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 42 prob.BirnbaumSaundersDistribution.truncate # name: # type: sq_string # elements: 1 # length: 555 prob.BirnbaumSaundersDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 37 prob.BirnbaumSaundersDistribution.var # name: # type: sq_string # elements: 1 # length: 185 prob.BirnbaumSaundersDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 21 prob.BurrDistribution # name: # type: sq_string # elements: 1 # length: 1334 statistics: prob.BurrDistribution Burr probability distribution object. A prob.BurrDistribution object consists of parameters, a model description, and sample data for a Burr probability distribution. The Burr distribution is a continuous probability distribution that models a non-negative random variable, commonly used to model household income. It is defined by a scale parameter alpha and two shape parameters c and k . There are several ways to create a prob.BurrDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.BurrDistribution ( alpha , c , k ) to create a Burr distribution with fixed parameter values alpha , c , and k . Use the static method prob.BurrDistribution.fit ( x , alpha , censor , freq , options ) to fit a distribution to the data in x using the same input arguments as the burrfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the Burr distribution can be found at https://en.wikipedia.org/wiki/Burr_distribution See also: fitdist, makedist, burrcdf, burrinv, burrpdf, burrrnd, burrfit, burrlike, burrstat # name: # type: sq_string # elements: 1 # length: 37 Burr probability distribution object. # name: # type: sq_string # elements: 1 # length: 38 prob.BurrDistribution.BurrDistribution # name: # type: sq_string # elements: 1 # length: 375 prob.BurrDistribution: pd = BurrDistribution ( alpha , c , k ) prob.BurrDistribution: pd = BurrDistribution () Create a prob.BurrDistribution object. alpha , c and k are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, alpha 1, c 1 and k 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 38 Create a prob.BurrDistribution object. # name: # type: sq_string # elements: 1 # length: 38 prob.BurrDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 187 prob.BurrDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 31 prob.BurrDistribution.InputData # name: # type: sq_string # elements: 1 # length: 564 prob.BurrDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : an empty array, since prob.BurrDistribution does not allow censoring. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 33 prob.BurrDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 207 prob.BurrDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 35 prob.BurrDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 200 prob.BurrDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 41 prob.BurrDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 619 prob.BurrDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 3×3 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 42 prob.BurrDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 230 prob.BurrDistribution: property ParameterDescription Description of parameters A 3×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 38 prob.BurrDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 286 prob.BurrDistribution: property ParameterIsFixed Flag for fixed parameters A 1×3 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 36 prob.BurrDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 207 prob.BurrDistribution: property ParameterNames Names of parameters A 3×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 37 prob.BurrDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 292 prob.BurrDistribution: property ParameterValues Distribution parameter values A 3×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the alpha , c , and k properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 32 prob.BurrDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 350 prob.BurrDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 27 prob.BurrDistribution.alpha # name: # type: sq_string # elements: 1 # length: 191 prob.BurrDistribution: property alpha Scale parameter A positive scalar value characterizing the scale of the Burr distribution. You can access the alpha property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 23 prob.BurrDistribution.c # name: # type: sq_string # elements: 1 # length: 205 prob.BurrDistribution: property c First shape parameter A positive scalar value characterizing the first shape parameter of the Burr distribution. You can access the c property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 21 First shape parameter # name: # type: sq_string # elements: 1 # length: 25 prob.BurrDistribution.cdf # name: # type: sq_string # elements: 1 # length: 399 prob.BurrDistribution: p = cdf ( pd , x ) prob.BurrDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 26 prob.BurrDistribution.icdf # name: # type: sq_string # elements: 1 # length: 248 prob.BurrDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 25 prob.BurrDistribution.iqr # name: # type: sq_string # elements: 1 # length: 195 prob.BurrDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 23 prob.BurrDistribution.k # name: # type: sq_string # elements: 1 # length: 207 prob.BurrDistribution: property k Second shape parameter A positive scalar value characterizing the second shape parameter of the Burr distribution. You can access the k property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 22 Second shape parameter # name: # type: sq_string # elements: 1 # length: 26 prob.BurrDistribution.mean # name: # type: sq_string # elements: 1 # length: 167 prob.BurrDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.BurrDistribution.median # name: # type: sq_string # elements: 1 # length: 175 prob.BurrDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.BurrDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 221 prob.BurrDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 29 prob.BurrDistribution.paramci # name: # type: sq_string # elements: 1 # length: 984 prob.BurrDistribution: ci = paramci ( pd ) prob.BurrDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 25 prob.BurrDistribution.pdf # name: # type: sq_string # elements: 1 # length: 208 prob.BurrDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 26 prob.BurrDistribution.plot # name: # type: sq_string # elements: 1 # length: 1536 prob.BurrDistribution: plot ( pd ) prob.BurrDistribution: plot ( pd , Name , Value ) prob.BurrDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 29 prob.BurrDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2146 prob.BurrDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.BurrDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.BurrDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.BurrDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.BurrDistribution: [ nlogL , param ] = proflik ( pd ) prob.BurrDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the Burr distribution, pnum = 1 selects the parameter alpha , pnum = 2 selects the parameter c , and pnum = 3 selects the parameter k . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 28 prob.BurrDistribution.random # name: # type: sq_string # elements: 1 # length: 698 prob.BurrDistribution: r = random ( pd ) prob.BurrDistribution: r = random ( pd , rows ) prob.BurrDistribution: r = random ( pd , rows , cols , …) prob.BurrDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, burrrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 25 prob.BurrDistribution.std # name: # type: sq_string # elements: 1 # length: 193 prob.BurrDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 30 prob.BurrDistribution.truncate # name: # type: sq_string # elements: 1 # length: 543 prob.BurrDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 25 prob.BurrDistribution.var # name: # type: sq_string # elements: 1 # length: 173 prob.BurrDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.ExponentialDistribution # name: # type: sq_string # elements: 1 # length: 1296 statistics: prob.ExponentialDistribution Exponential probability distribution object. A prob.ExponentialDistribution object consists of parameters, a model description, and sample data for a exponential probability distribution. The exponential distribution is a continuous probability distribution with mean parameter mu that models the time between events in a Poisson process. There are several ways to create a prob.ExponentialDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.ExponentialDistribution ( mu ) to create a exponential distribution with fixed parameter value mu . Use the static method prob.ExponentialDistribution.fit ( x , alpha , censor , freq , options ) to fit a distribution to the data in x using the same input arguments as the expfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the exponential distribution can be found at https://en.wikipedia.org/wiki/Exponential_distribution See also: fitdist, makedist, expcdf, expinv, exppdf, exprnd, expfit, explike, expstat # name: # type: sq_string # elements: 1 # length: 44 Exponential probability distribution object. # name: # type: sq_string # elements: 1 # length: 45 prob.ExponentialDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 194 prob.ExponentialDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 52 prob.ExponentialDistribution.ExponentialDistribution # name: # type: sq_string # elements: 1 # length: 365 prob.ExponentialDistribution: pd = ExponentialDistribution ( mu ) prob.ExponentialDistribution: pd = ExponentialDistribution () Create a prob.ExponentialDistribution object. mu is the distribution parameter, which the class help describes. Called with no arguments the parameter takes its default, mu 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 45 Create a prob.ExponentialDistribution object. # name: # type: sq_string # elements: 1 # length: 38 prob.ExponentialDistribution.InputData # name: # type: sq_string # elements: 1 # length: 744 prob.ExponentialDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 40 prob.ExponentialDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 214 prob.ExponentialDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 42 prob.ExponentialDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 207 prob.ExponentialDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 48 prob.ExponentialDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 454 prob.ExponentialDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A scalar numeric value containing the variance-covariance of the parameter estimate. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then the variance-covariance is zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 49 prob.ExponentialDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 237 prob.ExponentialDistribution: property ParameterDescription Description of parameters A 1×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 45 prob.ExponentialDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 284 prob.ExponentialDistribution: property ParameterIsFixed Flag for fixed parameters A 1×1 logical vector specifying whether the parameter is fixed or estimated. true value corresponds to fixed parameter, false value corresponds to parameter estimate. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 43 prob.ExponentialDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 214 prob.ExponentialDistribution: property ParameterNames Names of parameters A 1×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 44 prob.ExponentialDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 280 prob.ExponentialDistribution: property ParameterValues Distribution parameter values A 1×1 numeric vector containing the value of the distribution parameter. This property is read-only. You can change the distribution parameter by assigning a new value to the mu property. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 39 prob.ExponentialDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 357 prob.ExponentialDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 32 prob.ExponentialDistribution.cdf # name: # type: sq_string # elements: 1 # length: 413 prob.ExponentialDistribution: p = cdf ( pd , x ) prob.ExponentialDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 33 prob.ExponentialDistribution.icdf # name: # type: sq_string # elements: 1 # length: 255 prob.ExponentialDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 32 prob.ExponentialDistribution.iqr # name: # type: sq_string # elements: 1 # length: 202 prob.ExponentialDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.ExponentialDistribution.mean # name: # type: sq_string # elements: 1 # length: 174 prob.ExponentialDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 35 prob.ExponentialDistribution.median # name: # type: sq_string # elements: 1 # length: 182 prob.ExponentialDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.ExponentialDistribution.mu # name: # type: sq_string # elements: 1 # length: 197 prob.ExponentialDistribution: property mu Mean parameter A positive scalar value characterizing the mean of the exponential distribution. You can access the mu property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 14 Mean parameter # name: # type: sq_string # elements: 1 # length: 38 prob.ExponentialDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 228 prob.ExponentialDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 36 prob.ExponentialDistribution.paramci # name: # type: sq_string # elements: 1 # length: 998 prob.ExponentialDistribution: ci = paramci ( pd ) prob.ExponentialDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 32 prob.ExponentialDistribution.pdf # name: # type: sq_string # elements: 1 # length: 215 prob.ExponentialDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 33 prob.ExponentialDistribution.plot # name: # type: sq_string # elements: 1 # length: 1557 prob.ExponentialDistribution: plot ( pd ) prob.ExponentialDistribution: plot ( pd , Name , Value ) prob.ExponentialDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 36 prob.ExponentialDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2118 prob.ExponentialDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.ExponentialDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.ExponentialDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.ExponentialDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.ExponentialDistribution: [ nlogL , param ] = proflik ( pd ) prob.ExponentialDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the exponential distribution, pnum = 1 selects the parameter mu . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 35 prob.ExponentialDistribution.random # name: # type: sq_string # elements: 1 # length: 726 prob.ExponentialDistribution: r = random ( pd ) prob.ExponentialDistribution: r = random ( pd , rows ) prob.ExponentialDistribution: r = random ( pd , rows , cols , …) prob.ExponentialDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, betarnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 32 prob.ExponentialDistribution.std # name: # type: sq_string # elements: 1 # length: 200 prob.ExponentialDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 37 prob.ExponentialDistribution.truncate # name: # type: sq_string # elements: 1 # length: 550 prob.ExponentialDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.ExponentialDistribution.var # name: # type: sq_string # elements: 1 # length: 180 prob.ExponentialDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 29 prob.ExtremeValueDistribution # name: # type: sq_string # elements: 1 # length: 1410 statistics: prob.ExtremeValueDistribution Extreme value probability distribution object. A prob.ExtremeValueDistribution object consists of parameters, a model description, and sample data for an extreme value probability distribution. The extreme value distribution is also known as the Gumbel distribution for maxima, and it is a limiting distribution for the maximum of a large number of samples from a continuous distribution. It is defined by location parameter mu and scale parameter sigma . There are several ways to create a prob.ExtremeValueDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with specified parameter values using the makedist function. Use the constructor prob.ExtremeValueDistribution ( mu , sigma ) to create an extreme value distribution with specified parameter values. Use the static method prob.ExtremeValueDistribution.fit ( x , alpha , censor , freq , options ) to fit a distribution to the data in x using the same input arguments as the evfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the constructor and the aforementioned static method. Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution See also: fitdist, makedist, evcdf, evinv, evpdf, evrnd, evfit, evlike, evstat # name: # type: sq_string # elements: 1 # length: 46 Extreme value probability distribution object. # name: # type: sq_string # elements: 1 # length: 46 prob.ExtremeValueDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 195 prob.ExtremeValueDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 54 prob.ExtremeValueDistribution.ExtremeValueDistribution # name: # type: sq_string # elements: 1 # length: 405 prob.ExtremeValueDistribution: pd = ExtremeValueDistribution ( mu , sigma ) prob.ExtremeValueDistribution: pd = ExtremeValueDistribution () Create a prob.ExtremeValueDistribution object. mu and sigma are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, mu 0 and sigma 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 46 Create a prob.ExtremeValueDistribution object. # name: # type: sq_string # elements: 1 # length: 39 prob.ExtremeValueDistribution.InputData # name: # type: sq_string # elements: 1 # length: 745 prob.ExtremeValueDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 41 prob.ExtremeValueDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 215 prob.ExtremeValueDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 43 prob.ExtremeValueDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 208 prob.ExtremeValueDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 49 prob.ExtremeValueDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 627 prob.ExtremeValueDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 50 prob.ExtremeValueDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 238 prob.ExtremeValueDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 46 prob.ExtremeValueDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 294 prob.ExtremeValueDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 44 prob.ExtremeValueDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 215 prob.ExtremeValueDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 45 prob.ExtremeValueDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 295 prob.ExtremeValueDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the mu and sigma properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 40 prob.ExtremeValueDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 358 prob.ExtremeValueDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 33 prob.ExtremeValueDistribution.cdf # name: # type: sq_string # elements: 1 # length: 415 prob.ExtremeValueDistribution: p = cdf ( pd , x ) prob.ExtremeValueDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 34 prob.ExtremeValueDistribution.icdf # name: # type: sq_string # elements: 1 # length: 256 prob.ExtremeValueDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 33 prob.ExtremeValueDistribution.iqr # name: # type: sq_string # elements: 1 # length: 203 prob.ExtremeValueDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.ExtremeValueDistribution.mean # name: # type: sq_string # elements: 1 # length: 175 prob.ExtremeValueDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 36 prob.ExtremeValueDistribution.median # name: # type: sq_string # elements: 1 # length: 183 prob.ExtremeValueDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.ExtremeValueDistribution.mu # name: # type: sq_string # elements: 1 # length: 199 prob.ExtremeValueDistribution: property mu Location parameter A scalar value characterizing the location of the extreme value distribution. You can access the mu property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 18 Location parameter # name: # type: sq_string # elements: 1 # length: 39 prob.ExtremeValueDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 229 prob.ExtremeValueDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 37 prob.ExtremeValueDistribution.paramci # name: # type: sq_string # elements: 1 # length: 1000 prob.ExtremeValueDistribution: ci = paramci ( pd ) prob.ExtremeValueDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 33 prob.ExtremeValueDistribution.pdf # name: # type: sq_string # elements: 1 # length: 216 prob.ExtremeValueDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 34 prob.ExtremeValueDistribution.plot # name: # type: sq_string # elements: 1 # length: 1560 prob.ExtremeValueDistribution: plot ( pd ) prob.ExtremeValueDistribution: plot ( pd , Name , Value ) prob.ExtremeValueDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 37 prob.ExtremeValueDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2167 prob.ExtremeValueDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.ExtremeValueDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.ExtremeValueDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.ExtremeValueDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.ExtremeValueDistribution: [ nlogL , param ] = proflik ( pd ) prob.ExtremeValueDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the extreme value distribution, pnum = 1 selects the parameter mu and pnum = 2 selects the parameter sigma . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 36 prob.ExtremeValueDistribution.random # name: # type: sq_string # elements: 1 # length: 730 prob.ExtremeValueDistribution: r = random ( pd ) prob.ExtremeValueDistribution: r = random ( pd , rows ) prob.ExtremeValueDistribution: r = random ( pd , rows , cols , …) prob.ExtremeValueDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, betarnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 35 prob.ExtremeValueDistribution.sigma # name: # type: sq_string # elements: 1 # length: 208 prob.ExtremeValueDistribution: property sigma Scale parameter A positive scalar value characterizing the scale of the extreme value distribution. You can access the sigma property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 33 prob.ExtremeValueDistribution.std # name: # type: sq_string # elements: 1 # length: 201 prob.ExtremeValueDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 38 prob.ExtremeValueDistribution.truncate # name: # type: sq_string # elements: 1 # length: 551 prob.ExtremeValueDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.ExtremeValueDistribution.var # name: # type: sq_string # elements: 1 # length: 181 prob.ExtremeValueDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 22 prob.GammaDistribution # name: # type: sq_string # elements: 1 # length: 1263 statistics: prob.GammaDistribution Gamma probability distribution object. A prob.GammaDistribution object consists of parameters, a model description, and sample data for a gamma probability distribution. The gamma distribution is a continuous probability distribution that models the time to failure of a process. It is defined by shape parameter a and scale parameter b . There are several ways to create a prob.GammaDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.GammaDistribution ( a , b ) to create a gamma distribution with fixed parameter values a and b . Use the static method prob.GammaDistribution.fit ( x , alpha , censor , freq , options ) to fit a distribution to the data in x using the same input arguments as the gamfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the gamma distribution can be found at https://en.wikipedia.org/wiki/Gamma_distribution See also: fitdist, makedist, gamcdf, gaminv, gampdf, gamrnd, gamfit, gamlike, gamstat # name: # type: sq_string # elements: 1 # length: 38 Gamma probability distribution object. # name: # type: sq_string # elements: 1 # length: 39 prob.GammaDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 188 prob.GammaDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 40 prob.GammaDistribution.GammaDistribution # name: # type: sq_string # elements: 1 # length: 355 prob.GammaDistribution: pd = GammaDistribution ( a , b ) prob.GammaDistribution: pd = GammaDistribution () Create a prob.GammaDistribution object. a and b are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, a 1 and b 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 39 Create a prob.GammaDistribution object. # name: # type: sq_string # elements: 1 # length: 32 prob.GammaDistribution.InputData # name: # type: sq_string # elements: 1 # length: 738 prob.GammaDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 34 prob.GammaDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 208 prob.GammaDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 36 prob.GammaDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 201 prob.GammaDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 42 prob.GammaDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 620 prob.GammaDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 43 prob.GammaDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 231 prob.GammaDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 39 prob.GammaDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 287 prob.GammaDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 37 prob.GammaDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 208 prob.GammaDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 38 prob.GammaDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 283 prob.GammaDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the a and b properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 33 prob.GammaDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 351 prob.GammaDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 24 prob.GammaDistribution.a # name: # type: sq_string # elements: 1 # length: 185 prob.GammaDistribution: property a Shape parameter A positive scalar value characterizing the shape of the gamma distribution. You can access the a property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Shape parameter # name: # type: sq_string # elements: 1 # length: 24 prob.GammaDistribution.b # name: # type: sq_string # elements: 1 # length: 185 prob.GammaDistribution: property b Scale parameter A positive scalar value characterizing the scale of the gamma distribution. You can access the b property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 26 prob.GammaDistribution.cdf # name: # type: sq_string # elements: 1 # length: 401 prob.GammaDistribution: p = cdf ( pd , x ) prob.GammaDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 27 prob.GammaDistribution.icdf # name: # type: sq_string # elements: 1 # length: 249 prob.GammaDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 26 prob.GammaDistribution.iqr # name: # type: sq_string # elements: 1 # length: 196 prob.GammaDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 27 prob.GammaDistribution.mean # name: # type: sq_string # elements: 1 # length: 168 prob.GammaDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 29 prob.GammaDistribution.median # name: # type: sq_string # elements: 1 # length: 176 prob.GammaDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.GammaDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 222 prob.GammaDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 30 prob.GammaDistribution.paramci # name: # type: sq_string # elements: 1 # length: 986 prob.GammaDistribution: ci = paramci ( pd ) prob.GammaDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 26 prob.GammaDistribution.pdf # name: # type: sq_string # elements: 1 # length: 209 prob.GammaDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 27 prob.GammaDistribution.plot # name: # type: sq_string # elements: 1 # length: 1539 prob.GammaDistribution: plot ( pd ) prob.GammaDistribution: plot ( pd , Name , Value ) prob.GammaDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 30 prob.GammaDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2112 prob.GammaDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.GammaDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.GammaDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.GammaDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.GammaDistribution: [ nlogL , param ] = proflik ( pd ) prob.GammaDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the gamma distribution, pnum = 1 selects the parameter a and pnum = 2 selects the parameter b . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 29 prob.GammaDistribution.random # name: # type: sq_string # elements: 1 # length: 701 prob.GammaDistribution: r = random ( pd ) prob.GammaDistribution: r = random ( pd , rows ) prob.GammaDistribution: r = random ( pd , rows , cols , …) prob.GammaDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, gamrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 26 prob.GammaDistribution.std # name: # type: sq_string # elements: 1 # length: 194 prob.GammaDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.GammaDistribution.truncate # name: # type: sq_string # elements: 1 # length: 544 prob.GammaDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 26 prob.GammaDistribution.var # name: # type: sq_string # elements: 1 # length: 174 prob.GammaDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 40 prob.GeneralizedExtremeValueDistribution # name: # type: sq_string # elements: 1 # length: 1496 statistics: prob.GeneralizedExtremeValueDistribution Generalized extreme value probability distribution object. A prob.GeneralizedExtremeValueDistribution object consists of parameters, a model description, and sample data for a generalized extreme value probability distribution. The generalized extreme value distribution is a continuous probability distribution that models extreme values. It is defined by shape parameter k , scale parameter sigma , and location parameter mu . There are several ways to create a prob.GeneralizedExtremeValueDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.GeneralizedExtremeValueDistribution ( k , sigma , mu ) to create a generalized extreme value distribution with fixed parameter values k , sigma , and mu . Use the static method prob.GeneralizedExtremeValueDistribution.fit ( x , alpha , freq , options ) to fit a distribution to the data in x using the same input arguments as the gevfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the generalized extreme value distribution can be found at https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution See also: fitdist, makedist, gevcdf, gevinv, gevpdf, gevrnd, gevfit, gevlike, gevstat # name: # type: sq_string # elements: 1 # length: 58 Generalized extreme value probability distribution object. # name: # type: sq_string # elements: 1 # length: 57 prob.GeneralizedExtremeValueDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 206 prob.GeneralizedExtremeValueDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 76 prob.GeneralizedExtremeValueDistribution.GeneralizedExtremeValueDistribution # name: # type: sq_string # elements: 1 # length: 473 prob.GeneralizedExtremeValueDistribution: pd = GeneralizedExtremeValueDistribution ( k , sigma , mu ) prob.GeneralizedExtremeValueDistribution: pd = GeneralizedExtremeValueDistribution () Create a prob.GeneralizedExtremeValueDistribution object. k , sigma and mu are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, k 0, sigma 1 and mu 0. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 57 Create a prob.GeneralizedExtremeValueDistribution object. # name: # type: sq_string # elements: 1 # length: 50 prob.GeneralizedExtremeValueDistribution.InputData # name: # type: sq_string # elements: 1 # length: 756 prob.GeneralizedExtremeValueDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 52 prob.GeneralizedExtremeValueDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 226 prob.GeneralizedExtremeValueDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 54 prob.GeneralizedExtremeValueDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 219 prob.GeneralizedExtremeValueDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 60 prob.GeneralizedExtremeValueDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 638 prob.GeneralizedExtremeValueDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 3×3 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 61 prob.GeneralizedExtremeValueDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 249 prob.GeneralizedExtremeValueDistribution: property ParameterDescription Description of parameters A 3×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 57 prob.GeneralizedExtremeValueDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 305 prob.GeneralizedExtremeValueDistribution: property ParameterIsFixed Flag for fixed parameters A 1×3 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 55 prob.GeneralizedExtremeValueDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 226 prob.GeneralizedExtremeValueDistribution: property ParameterNames Names of parameters A 3×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 56 prob.GeneralizedExtremeValueDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 312 prob.GeneralizedExtremeValueDistribution: property ParameterValues Distribution parameter values A 3×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the k , sigma , and mu properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 51 prob.GeneralizedExtremeValueDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 369 prob.GeneralizedExtremeValueDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 44 prob.GeneralizedExtremeValueDistribution.cdf # name: # type: sq_string # elements: 1 # length: 437 prob.GeneralizedExtremeValueDistribution: p = cdf ( pd , x ) prob.GeneralizedExtremeValueDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 45 prob.GeneralizedExtremeValueDistribution.icdf # name: # type: sq_string # elements: 1 # length: 267 prob.GeneralizedExtremeValueDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 44 prob.GeneralizedExtremeValueDistribution.iqr # name: # type: sq_string # elements: 1 # length: 214 prob.GeneralizedExtremeValueDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 42 prob.GeneralizedExtremeValueDistribution.k # name: # type: sq_string # elements: 1 # length: 214 prob.GeneralizedExtremeValueDistribution: property k Shape parameter A scalar value characterizing the shape of the generalized extreme value distribution. You can access the k property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Shape parameter # name: # type: sq_string # elements: 1 # length: 45 prob.GeneralizedExtremeValueDistribution.mean # name: # type: sq_string # elements: 1 # length: 186 prob.GeneralizedExtremeValueDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 47 prob.GeneralizedExtremeValueDistribution.median # name: # type: sq_string # elements: 1 # length: 194 prob.GeneralizedExtremeValueDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 43 prob.GeneralizedExtremeValueDistribution.mu # name: # type: sq_string # elements: 1 # length: 222 prob.GeneralizedExtremeValueDistribution: property mu Location parameter A scalar value characterizing the location of the generalized extreme value distribution. You can access the mu property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 18 Location parameter # name: # type: sq_string # elements: 1 # length: 50 prob.GeneralizedExtremeValueDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 240 prob.GeneralizedExtremeValueDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 48 prob.GeneralizedExtremeValueDistribution.paramci # name: # type: sq_string # elements: 1 # length: 1022 prob.GeneralizedExtremeValueDistribution: ci = paramci ( pd ) prob.GeneralizedExtremeValueDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 44 prob.GeneralizedExtremeValueDistribution.pdf # name: # type: sq_string # elements: 1 # length: 227 prob.GeneralizedExtremeValueDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 45 prob.GeneralizedExtremeValueDistribution.plot # name: # type: sq_string # elements: 1 # length: 1593 prob.GeneralizedExtremeValueDistribution: plot ( pd ) prob.GeneralizedExtremeValueDistribution: plot ( pd , Name , Value ) prob.GeneralizedExtremeValueDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 48 prob.GeneralizedExtremeValueDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2282 prob.GeneralizedExtremeValueDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.GeneralizedExtremeValueDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.GeneralizedExtremeValueDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.GeneralizedExtremeValueDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.GeneralizedExtremeValueDistribution: [ nlogL , param ] = proflik ( pd ) prob.GeneralizedExtremeValueDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the generalized extreme value distribution, pnum = 1 selects the parameter k , pnum = 2 selects the parameter sigma , and pnum = 3 selects the parameter mu . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 47 prob.GeneralizedExtremeValueDistribution.random # name: # type: sq_string # elements: 1 # length: 773 prob.GeneralizedExtremeValueDistribution: r = random ( pd ) prob.GeneralizedExtremeValueDistribution: r = random ( pd , rows ) prob.GeneralizedExtremeValueDistribution: r = random ( pd , rows , cols , …) prob.GeneralizedExtremeValueDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, gevrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 46 prob.GeneralizedExtremeValueDistribution.sigma # name: # type: sq_string # elements: 1 # length: 231 prob.GeneralizedExtremeValueDistribution: property sigma Scale parameter A positive scalar value characterizing the scale of the generalized extreme value distribution. You can access the sigma property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 44 prob.GeneralizedExtremeValueDistribution.std # name: # type: sq_string # elements: 1 # length: 212 prob.GeneralizedExtremeValueDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 49 prob.GeneralizedExtremeValueDistribution.truncate # name: # type: sq_string # elements: 1 # length: 562 prob.GeneralizedExtremeValueDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 44 prob.GeneralizedExtremeValueDistribution.var # name: # type: sq_string # elements: 1 # length: 192 prob.GeneralizedExtremeValueDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.GeneralizedParetoDistribution # name: # type: sq_string # elements: 1 # length: 1501 statistics: prob.GeneralizedParetoDistribution Generalized Pareto probability distribution object. A prob.GeneralizedParetoDistribution object consists of parameters, a model description, and sample data for a Generalized Pareto probability distribution. The Generalized Pareto distribution is a continuous probability distribution that models the tail behavior of other distributions, commonly used for extreme value analysis. It is defined by shape parameter k , scale parameter sigma , and location parameter theta . There are several ways to create a prob.GeneralizedParetoDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.GeneralizedParetoDistribution ( k , sigma , theta ) to create a Generalized Pareto distribution with fixed parameter values k , sigma , and theta . Use the static method prob.GeneralizedParetoDistribution.fit ( x , theta , alpha , freq , options ) to fit a distribution to the data in x using the same input arguments as the gpfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the Generalized Pareto distribution can be found at https://en.wikipedia.org/wiki/Generalized_Pareto_distribution See also: fitdist, makedist, gpcdf, gpinv, gppdf, gprnd, gpfit, gplike, gpstat # name: # type: sq_string # elements: 1 # length: 51 Generalized Pareto probability distribution object. # name: # type: sq_string # elements: 1 # length: 51 prob.GeneralizedParetoDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 200 prob.GeneralizedParetoDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 64 prob.GeneralizedParetoDistribution.GeneralizedParetoDistribution # name: # type: sq_string # elements: 1 # length: 452 prob.GeneralizedParetoDistribution: pd = GeneralizedParetoDistribution ( k , sigma , theta ) prob.GeneralizedParetoDistribution: pd = GeneralizedParetoDistribution () Create a prob.GeneralizedParetoDistribution object. k , sigma and theta are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, k 1, sigma 1 and theta 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 51 Create a prob.GeneralizedParetoDistribution object. # name: # type: sq_string # elements: 1 # length: 44 prob.GeneralizedParetoDistribution.InputData # name: # type: sq_string # elements: 1 # length: 750 prob.GeneralizedParetoDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 46 prob.GeneralizedParetoDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 220 prob.GeneralizedParetoDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 48 prob.GeneralizedParetoDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 213 prob.GeneralizedParetoDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 54 prob.GeneralizedParetoDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 632 prob.GeneralizedParetoDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 3×3 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 55 prob.GeneralizedParetoDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 243 prob.GeneralizedParetoDistribution: property ParameterDescription Description of parameters A 3×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 51 prob.GeneralizedParetoDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 299 prob.GeneralizedParetoDistribution: property ParameterIsFixed Flag for fixed parameters A 1×3 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 49 prob.GeneralizedParetoDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 220 prob.GeneralizedParetoDistribution: property ParameterNames Names of parameters A 3×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 50 prob.GeneralizedParetoDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 309 prob.GeneralizedParetoDistribution: property ParameterValues Distribution parameter values A 3×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the k , sigma , and theta properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 45 prob.GeneralizedParetoDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 363 prob.GeneralizedParetoDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 38 prob.GeneralizedParetoDistribution.cdf # name: # type: sq_string # elements: 1 # length: 425 prob.GeneralizedParetoDistribution: p = cdf ( pd , x ) prob.GeneralizedParetoDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 39 prob.GeneralizedParetoDistribution.icdf # name: # type: sq_string # elements: 1 # length: 261 prob.GeneralizedParetoDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 38 prob.GeneralizedParetoDistribution.iqr # name: # type: sq_string # elements: 1 # length: 208 prob.GeneralizedParetoDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 36 prob.GeneralizedParetoDistribution.k # name: # type: sq_string # elements: 1 # length: 201 prob.GeneralizedParetoDistribution: property k Shape parameter A scalar value characterizing the shape of the Generalized Pareto distribution. You can access the k property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Shape parameter # name: # type: sq_string # elements: 1 # length: 39 prob.GeneralizedParetoDistribution.mean # name: # type: sq_string # elements: 1 # length: 180 prob.GeneralizedParetoDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 41 prob.GeneralizedParetoDistribution.median # name: # type: sq_string # elements: 1 # length: 188 prob.GeneralizedParetoDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 44 prob.GeneralizedParetoDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 234 prob.GeneralizedParetoDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 42 prob.GeneralizedParetoDistribution.paramci # name: # type: sq_string # elements: 1 # length: 1010 prob.GeneralizedParetoDistribution: ci = paramci ( pd ) prob.GeneralizedParetoDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 38 prob.GeneralizedParetoDistribution.pdf # name: # type: sq_string # elements: 1 # length: 221 prob.GeneralizedParetoDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 39 prob.GeneralizedParetoDistribution.plot # name: # type: sq_string # elements: 1 # length: 1575 prob.GeneralizedParetoDistribution: plot ( pd ) prob.GeneralizedParetoDistribution: plot ( pd , Name , Value ) prob.GeneralizedParetoDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 42 prob.GeneralizedParetoDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2242 prob.GeneralizedParetoDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.GeneralizedParetoDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.GeneralizedParetoDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.GeneralizedParetoDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.GeneralizedParetoDistribution: [ nlogL , param ] = proflik ( pd ) prob.GeneralizedParetoDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the Generalized Pareto distribution, pnum = 1 selects the parameter k , pnum = 2 selects the parameter sigma , and pnum = 3 selects the parameter theta . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 41 prob.GeneralizedParetoDistribution.random # name: # type: sq_string # elements: 1 # length: 749 prob.GeneralizedParetoDistribution: r = random ( pd ) prob.GeneralizedParetoDistribution: r = random ( pd , rows ) prob.GeneralizedParetoDistribution: r = random ( pd , rows , cols , …) prob.GeneralizedParetoDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, random returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 40 prob.GeneralizedParetoDistribution.sigma # name: # type: sq_string # elements: 1 # length: 218 prob.GeneralizedParetoDistribution: property sigma Scale parameter A positive scalar value characterizing the scale of the Generalized Pareto distribution. You can access the sigma property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 38 prob.GeneralizedParetoDistribution.std # name: # type: sq_string # elements: 1 # length: 206 prob.GeneralizedParetoDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 40 prob.GeneralizedParetoDistribution.theta # name: # type: sq_string # elements: 1 # length: 215 prob.GeneralizedParetoDistribution: property theta Location parameter A scalar value characterizing the location of the Generalized Pareto distribution. You can access the theta property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 18 Location parameter # name: # type: sq_string # elements: 1 # length: 43 prob.GeneralizedParetoDistribution.truncate # name: # type: sq_string # elements: 1 # length: 556 prob.GeneralizedParetoDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 38 prob.GeneralizedParetoDistribution.var # name: # type: sq_string # elements: 1 # length: 186 prob.GeneralizedParetoDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 27 prob.HalfNormalDistribution # name: # type: sq_string # elements: 1 # length: 1340 statistics: prob.HalfNormalDistribution Half-normal probability distribution object. A prob.HalfNormalDistribution object consists of parameters, a model description, and sample data for a half-normal probability distribution. The half-normal distribution is a continuous probability distribution that models the time to failure of materials subjected to cyclic loading. It is defined by location parameter mu and scale parameter sigma . There are several ways to create a prob.HalfNormalDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.HalfNormalDistribution ( mu , sigma ) to create a half-normal distribution with fixed parameter values mu and sigma . Use the static method prob.HalfNormalDistribution.fit ( x , mu , freq ) to fit a distribution to the data in x using the same input arguments as the hnfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the half-normal distribution can be found at https://en.wikipedia.org/wiki/Half-normal_distribution See also: fitdist, makedist, hncdf, hninv, hnpdf, hnrnd, hnfit, hnlike, hnstat # name: # type: sq_string # elements: 1 # length: 44 Half-normal probability distribution object. # name: # type: sq_string # elements: 1 # length: 44 prob.HalfNormalDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 193 prob.HalfNormalDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 50 prob.HalfNormalDistribution.HalfNormalDistribution # name: # type: sq_string # elements: 1 # length: 395 prob.HalfNormalDistribution: pd = HalfNormalDistribution ( mu , sigma ) prob.HalfNormalDistribution: pd = HalfNormalDistribution () Create a prob.HalfNormalDistribution object. mu and sigma are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, mu 0 and sigma 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 44 Create a prob.HalfNormalDistribution object. # name: # type: sq_string # elements: 1 # length: 37 prob.HalfNormalDistribution.InputData # name: # type: sq_string # elements: 1 # length: 743 prob.HalfNormalDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 39 prob.HalfNormalDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 213 prob.HalfNormalDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 41 prob.HalfNormalDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 206 prob.HalfNormalDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 47 prob.HalfNormalDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 625 prob.HalfNormalDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 48 prob.HalfNormalDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 236 prob.HalfNormalDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 44 prob.HalfNormalDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 292 prob.HalfNormalDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 42 prob.HalfNormalDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 213 prob.HalfNormalDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 43 prob.HalfNormalDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 293 prob.HalfNormalDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the mu and sigma properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 38 prob.HalfNormalDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 356 prob.HalfNormalDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 31 prob.HalfNormalDistribution.cdf # name: # type: sq_string # elements: 1 # length: 411 prob.HalfNormalDistribution: p = cdf ( pd , x ) prob.HalfNormalDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 32 prob.HalfNormalDistribution.icdf # name: # type: sq_string # elements: 1 # length: 254 prob.HalfNormalDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 31 prob.HalfNormalDistribution.iqr # name: # type: sq_string # elements: 1 # length: 201 prob.HalfNormalDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.HalfNormalDistribution.mean # name: # type: sq_string # elements: 1 # length: 173 prob.HalfNormalDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.HalfNormalDistribution.median # name: # type: sq_string # elements: 1 # length: 181 prob.HalfNormalDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 30 prob.HalfNormalDistribution.mu # name: # type: sq_string # elements: 1 # length: 195 prob.HalfNormalDistribution: property mu Location parameter A scalar value characterizing the location of the half-normal distribution. You can access the mu property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 18 Location parameter # name: # type: sq_string # elements: 1 # length: 37 prob.HalfNormalDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 227 prob.HalfNormalDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 35 prob.HalfNormalDistribution.paramci # name: # type: sq_string # elements: 1 # length: 996 prob.HalfNormalDistribution: ci = paramci ( pd ) prob.HalfNormalDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 31 prob.HalfNormalDistribution.pdf # name: # type: sq_string # elements: 1 # length: 214 prob.HalfNormalDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 32 prob.HalfNormalDistribution.plot # name: # type: sq_string # elements: 1 # length: 1554 prob.HalfNormalDistribution: plot ( pd ) prob.HalfNormalDistribution: plot ( pd , Name , Value ) prob.HalfNormalDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 35 prob.HalfNormalDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2153 prob.HalfNormalDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.HalfNormalDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.HalfNormalDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.HalfNormalDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.HalfNormalDistribution: [ nlogL , param ] = proflik ( pd ) prob.HalfNormalDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the Half-normal distribution, pnum = 1 selects the parameter mu and pnum = 2 selects the parameter sigma . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 34 prob.HalfNormalDistribution.random # name: # type: sq_string # elements: 1 # length: 720 prob.HalfNormalDistribution: r = random ( pd ) prob.HalfNormalDistribution: r = random ( pd , rows ) prob.HalfNormalDistribution: r = random ( pd , rows , cols , …) prob.HalfNormalDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, hnrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 33 prob.HalfNormalDistribution.sigma # name: # type: sq_string # elements: 1 # length: 204 prob.HalfNormalDistribution: property sigma Scale parameter A positive scalar value characterizing the scale of the half-normal distribution. You can access the sigma property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 31 prob.HalfNormalDistribution.std # name: # type: sq_string # elements: 1 # length: 199 prob.HalfNormalDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 36 prob.HalfNormalDistribution.truncate # name: # type: sq_string # elements: 1 # length: 549 prob.HalfNormalDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.HalfNormalDistribution.var # name: # type: sq_string # elements: 1 # length: 179 prob.HalfNormalDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.InverseGaussianDistribution # name: # type: sq_string # elements: 1 # length: 1425 statistics: prob.InverseGaussianDistribution Inverse Gaussian probability distribution object. A prob.InverseGaussianDistribution object consists of parameters, a model description, and sample data for a Inverse Gaussian probability distribution. The Inverse Gaussian distribution is a continuous probability distribution, which is often used to model non-negative positively skewed data. Is is defined by mean parameter mu and shape parameter lambda . There are several ways to create a prob.InverseGaussianDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.InverseGaussianDistribution ( mu , lambda ) to create a Inverse Gaussian distribution with fixed parameter values mu and lambda . Use the static method prob.InverseGaussianDistribution.fit ( x , alpha , censor , freq , options ) to fit a distribution to the data in x using the same input arguments as the invgfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the Inverse Gaussian distribution can be found at https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution See also: fitdist, makedist, invgcdf, invginv, invgpdf, invgrnd, invgfit, invglike, invgstat # name: # type: sq_string # elements: 1 # length: 49 Inverse Gaussian probability distribution object. # name: # type: sq_string # elements: 1 # length: 49 prob.InverseGaussianDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 198 prob.InverseGaussianDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 42 prob.InverseGaussianDistribution.InputData # name: # type: sq_string # elements: 1 # length: 748 prob.InverseGaussianDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 60 prob.InverseGaussianDistribution.InverseGaussianDistribution # name: # type: sq_string # elements: 1 # length: 423 prob.InverseGaussianDistribution: pd = InverseGaussianDistribution ( mu , lambda ) prob.InverseGaussianDistribution: pd = InverseGaussianDistribution () Create a prob.InverseGaussianDistribution object. mu and lambda are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, mu 1 and lambda 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 49 Create a prob.InverseGaussianDistribution object. # name: # type: sq_string # elements: 1 # length: 44 prob.InverseGaussianDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 218 prob.InverseGaussianDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 46 prob.InverseGaussianDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 211 prob.InverseGaussianDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 52 prob.InverseGaussianDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 630 prob.InverseGaussianDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 53 prob.InverseGaussianDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 241 prob.InverseGaussianDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 49 prob.InverseGaussianDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 297 prob.InverseGaussianDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 47 prob.InverseGaussianDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 218 prob.InverseGaussianDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 48 prob.InverseGaussianDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 299 prob.InverseGaussianDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the mu and lambda properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 43 prob.InverseGaussianDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 361 prob.InverseGaussianDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 36 prob.InverseGaussianDistribution.cdf # name: # type: sq_string # elements: 1 # length: 421 prob.InverseGaussianDistribution: p = cdf ( pd , x ) prob.InverseGaussianDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 37 prob.InverseGaussianDistribution.icdf # name: # type: sq_string # elements: 1 # length: 259 prob.InverseGaussianDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 36 prob.InverseGaussianDistribution.iqr # name: # type: sq_string # elements: 1 # length: 206 prob.InverseGaussianDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 39 prob.InverseGaussianDistribution.lambda # name: # type: sq_string # elements: 1 # length: 216 prob.InverseGaussianDistribution: property lambda Shape parameter A positive scalar value characterizing the shape of the Inverse Gaussian distribution. You can access the lambda property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Shape parameter # name: # type: sq_string # elements: 1 # length: 37 prob.InverseGaussianDistribution.mean # name: # type: sq_string # elements: 1 # length: 178 prob.InverseGaussianDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 39 prob.InverseGaussianDistribution.median # name: # type: sq_string # elements: 1 # length: 186 prob.InverseGaussianDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 35 prob.InverseGaussianDistribution.mu # name: # type: sq_string # elements: 1 # length: 206 prob.InverseGaussianDistribution: property mu Mean parameter A positive scalar value characterizing the mean of the Inverse Gaussian distribution. You can access the mu property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 14 Mean parameter # name: # type: sq_string # elements: 1 # length: 42 prob.InverseGaussianDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 232 prob.InverseGaussianDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 40 prob.InverseGaussianDistribution.paramci # name: # type: sq_string # elements: 1 # length: 1006 prob.InverseGaussianDistribution: ci = paramci ( pd ) prob.InverseGaussianDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 36 prob.InverseGaussianDistribution.pdf # name: # type: sq_string # elements: 1 # length: 219 prob.InverseGaussianDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 37 prob.InverseGaussianDistribution.plot # name: # type: sq_string # elements: 1 # length: 1569 prob.InverseGaussianDistribution: plot ( pd ) prob.InverseGaussianDistribution: plot ( pd , Name , Value ) prob.InverseGaussianDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 40 prob.InverseGaussianDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2189 prob.InverseGaussianDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.InverseGaussianDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.InverseGaussianDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.InverseGaussianDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.InverseGaussianDistribution: [ nlogL , param ] = proflik ( pd ) prob.InverseGaussianDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the Inverse Gaussian distribution, pnum = 1 selects the parameter mu and pnum = 2 selects the parameter lambda . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 39 prob.InverseGaussianDistribution.random # name: # type: sq_string # elements: 1 # length: 742 prob.InverseGaussianDistribution: r = random ( pd ) prob.InverseGaussianDistribution: r = random ( pd , rows ) prob.InverseGaussianDistribution: r = random ( pd , rows , cols , …) prob.InverseGaussianDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, invgrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 36 prob.InverseGaussianDistribution.std # name: # type: sq_string # elements: 1 # length: 204 prob.InverseGaussianDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 41 prob.InverseGaussianDistribution.truncate # name: # type: sq_string # elements: 1 # length: 554 prob.InverseGaussianDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 36 prob.InverseGaussianDistribution.var # name: # type: sq_string # elements: 1 # length: 184 prob.InverseGaussianDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 23 prob.KernelDistribution # name: # type: sq_string # elements: 1 # length: 840 statistics: prob.KernelDistribution Kernel probability distribution object. A prob.KernelDistribution object consists of a nonparametric kernel smoothing density estimate fitted to sample data, together with a model description. Unlike the parametric distribution objects, it has no estimated parameters; the fitted distribution is defined entirely by the data, the smoothing kernel, and the bandwidth. A prob.KernelDistribution object can only be created by fitting a kernel smoothing distribution to data with the fitdist function. Unlike the parametric distributions, it cannot be created with the makedist function, since it is not parametric and requires data. Further information about the kernel density estimation can be found at https://en.wikipedia.org/wiki/Kernel_density_estimation See also: fitdist, ksdensity, mvksdensity # name: # type: sq_string # elements: 1 # length: 39 Kernel probability distribution object. # name: # type: sq_string # elements: 1 # length: 33 prob.KernelDistribution.Bandwidth # name: # type: sq_string # elements: 1 # length: 218 prob.KernelDistribution: property Bandwidth Bandwidth of the smoothing kernel A positive scalar value specifying the bandwidth of the smoothing kernel. You can access the Bandwidth property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 33 Bandwidth of the smoothing kernel # name: # type: sq_string # elements: 1 # length: 40 prob.KernelDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 189 prob.KernelDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 33 prob.KernelDistribution.InputData # name: # type: sq_string # elements: 1 # length: 572 prob.KernelDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : an empty array, since censoring is not supported for a kernel distribution. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 35 prob.KernelDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 209 prob.KernelDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 30 prob.KernelDistribution.Kernel # name: # type: sq_string # elements: 1 # length: 285 prob.KernelDistribution: property Kernel Kernel smoothing function A character vector specifying the type of smoothing kernel used for the density estimate. It is one of 'normal' , 'box' , 'triangle' , or 'epanechnikov' . You can access the Kernel property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 25 Kernel smoothing function # name: # type: sq_string # elements: 1 # length: 42 prob.KernelDistribution.KernelDistribution # name: # type: sq_string # elements: 1 # length: 479 prob.KernelDistribution: pd = KernelDistribution ( data , kernel , bw , support , freq ) prob.KernelDistribution: pd = KernelDistribution () Create a prob.KernelDistribution object. data , kernel , bw , support and freq are the distribution parameters, which the class help describes. Called with no arguments it fits the data [0; 1] with a normal kernel over an unbounded support, taking the bandwidth from the data. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 40 Create a prob.KernelDistribution object. # name: # type: sq_string # elements: 1 # length: 31 prob.KernelDistribution.Support # name: # type: sq_string # elements: 1 # length: 472 prob.KernelDistribution: property Support Support of the probability distribution A scalar structure containing the following fields: range : either the character vector 'unbounded' or 'positive' , or a two-element numeric vector [L, U] with the lower and upper bounds of the support. closedbound : a two-element logical vector specifying whether each bound is closed. iscontinuous : a logical scalar, always true for a kernel distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 39 Support of the probability distribution # name: # type: sq_string # elements: 1 # length: 34 prob.KernelDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 352 prob.KernelDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 27 prob.KernelDistribution.cdf # name: # type: sq_string # elements: 1 # length: 486 prob.KernelDistribution: p = cdf ( pd , x ) prob.KernelDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . x must be double or single ; integer, logical, and character arrays are rejected. # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 28 prob.KernelDistribution.icdf # name: # type: sq_string # elements: 1 # length: 333 prob.KernelDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . p must be double or single ; integer, logical, and character arrays are rejected. # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 27 prob.KernelDistribution.iqr # name: # type: sq_string # elements: 1 # length: 197 prob.KernelDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.KernelDistribution.mean # name: # type: sq_string # elements: 1 # length: 169 prob.KernelDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 30 prob.KernelDistribution.median # name: # type: sq_string # elements: 1 # length: 177 prob.KernelDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.KernelDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 223 prob.KernelDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 27 prob.KernelDistribution.pdf # name: # type: sq_string # elements: 1 # length: 288 prob.KernelDistribution: y = pdf ( pd , x ) Compute the probability density function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . x must be double or single ; integer, logical, and character arrays are rejected. # name: # type: sq_string # elements: 1 # length: 47 Compute the probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 28 prob.KernelDistribution.plot # name: # type: sq_string # elements: 1 # length: 1032 prob.KernelDistribution: plot ( pd ) prob.KernelDistribution: plot ( pd , Name , Value ) prob.KernelDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd , superimposed over a histogram of the data used to fit it. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF) superimposed on a histogram of the data. 'cdf' plots the cumulative distribution function (CDF) superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 30 prob.KernelDistribution.random # name: # type: sq_string # elements: 1 # length: 705 prob.KernelDistribution: r = random ( pd ) prob.KernelDistribution: r = random ( pd , rows ) prob.KernelDistribution: r = random ( pd , rows , cols , …) prob.KernelDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, random returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 27 prob.KernelDistribution.std # name: # type: sq_string # elements: 1 # length: 195 prob.KernelDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.KernelDistribution.truncate # name: # type: sq_string # elements: 1 # length: 302 prob.KernelDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 27 prob.KernelDistribution.var # name: # type: sq_string # elements: 1 # length: 175 prob.KernelDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 25 prob.LogisticDistribution # name: # type: sq_string # elements: 1 # length: 1356 statistics: prob.LogisticDistribution Logistic probability distribution object. A prob.LogisticDistribution object consists of parameters, a model description, and sample data for a logistic probability distribution. The logistic distribution is a continuous probability distribution, which is commonly used in logistic regression and feedforward neural networks. It is defined by location parameter mu and scale parameter sigma . There are several ways to create a prob.LogisticDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.LogisticDistribution ( mu , sigma ) to create a logistic distribution with fixed parameter values mu and sigma . Use the static method prob.LogisticDistribution.fit ( x , alpha , censor , freq , options ) to fit a distribution to the data in x using the same input arguments as the logifit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the logistic distribution can be found at https://en.wikipedia.org/wiki/Logistic_distribution See also: fitdist, makedist, logicdf, logiinv, logipdf, logirnd, logifit, logilike, logistat # name: # type: sq_string # elements: 1 # length: 41 Logistic probability distribution object. # name: # type: sq_string # elements: 1 # length: 42 prob.LogisticDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 191 prob.LogisticDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 35 prob.LogisticDistribution.InputData # name: # type: sq_string # elements: 1 # length: 741 prob.LogisticDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 37 prob.LogisticDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 211 prob.LogisticDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 46 prob.LogisticDistribution.LogisticDistribution # name: # type: sq_string # elements: 1 # length: 385 prob.LogisticDistribution: pd = LogisticDistribution ( mu , sigma ) prob.LogisticDistribution: pd = LogisticDistribution () Create a prob.LogisticDistribution object. mu and sigma are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, mu 0 and sigma 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 42 Create a prob.LogisticDistribution object. # name: # type: sq_string # elements: 1 # length: 39 prob.LogisticDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 204 prob.LogisticDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 45 prob.LogisticDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 623 prob.LogisticDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 46 prob.LogisticDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 234 prob.LogisticDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 42 prob.LogisticDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 290 prob.LogisticDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 40 prob.LogisticDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 211 prob.LogisticDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 41 prob.LogisticDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 291 prob.LogisticDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the mu and sigma properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 36 prob.LogisticDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 354 prob.LogisticDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 29 prob.LogisticDistribution.cdf # name: # type: sq_string # elements: 1 # length: 407 prob.LogisticDistribution: p = cdf ( pd , x ) prob.LogisticDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 30 prob.LogisticDistribution.icdf # name: # type: sq_string # elements: 1 # length: 252 prob.LogisticDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 29 prob.LogisticDistribution.iqr # name: # type: sq_string # elements: 1 # length: 199 prob.LogisticDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 30 prob.LogisticDistribution.mean # name: # type: sq_string # elements: 1 # length: 171 prob.LogisticDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.LogisticDistribution.median # name: # type: sq_string # elements: 1 # length: 179 prob.LogisticDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.LogisticDistribution.mu # name: # type: sq_string # elements: 1 # length: 190 prob.LogisticDistribution: property mu Location parameter A scalar value characterizing the location of the logistic distribution. You can access the mu property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 18 Location parameter # name: # type: sq_string # elements: 1 # length: 35 prob.LogisticDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 225 prob.LogisticDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.LogisticDistribution.paramci # name: # type: sq_string # elements: 1 # length: 992 prob.LogisticDistribution: ci = paramci ( pd ) prob.LogisticDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 29 prob.LogisticDistribution.pdf # name: # type: sq_string # elements: 1 # length: 212 prob.LogisticDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 30 prob.LogisticDistribution.plot # name: # type: sq_string # elements: 1 # length: 1548 prob.LogisticDistribution: plot ( pd ) prob.LogisticDistribution: plot ( pd , Name , Value ) prob.LogisticDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 33 prob.LogisticDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2138 prob.LogisticDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.LogisticDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.LogisticDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.LogisticDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.LogisticDistribution: [ nlogL , param ] = proflik ( pd ) prob.LogisticDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the logistic distribution, pnum = 1 selects the parameter mu and pnum = 2 selects the parameter sigma . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 32 prob.LogisticDistribution.random # name: # type: sq_string # elements: 1 # length: 714 prob.LogisticDistribution: r = random ( pd ) prob.LogisticDistribution: r = random ( pd , rows ) prob.LogisticDistribution: r = random ( pd , rows , cols , …) prob.LogisticDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, bisarnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 31 prob.LogisticDistribution.sigma # name: # type: sq_string # elements: 1 # length: 199 prob.LogisticDistribution: property sigma Scale parameter A positive scalar value characterizing the scale of the logistic distribution. You can access the sigma property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 29 prob.LogisticDistribution.std # name: # type: sq_string # elements: 1 # length: 197 prob.LogisticDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.LogisticDistribution.truncate # name: # type: sq_string # elements: 1 # length: 547 prob.LogisticDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 29 prob.LogisticDistribution.var # name: # type: sq_string # elements: 1 # length: 177 prob.LogisticDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.LoglogisticDistribution # name: # type: sq_string # elements: 1 # length: 1400 statistics: prob.LoglogisticDistribution Log-logistic probability distribution object. A prob.LoglogisticDistribution object consists of parameters, a model description, and sample data for a log-logistic probability distribution. The log-logistic distribution is a continuous probability distribution that models non-negative random variables whose logarithm follows the logistic distribution. It is defined by location parameter mu and scale parameter sigma . There are several ways to create a prob.LoglogisticDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.LoglogisticDistribution ( mu , sigma ) to create a log-logistic distribution with fixed parameter values mu and sigma . Use the static method prob.LoglogisticDistribution.fit ( x , censor , freq , options ) to fit a distribution to the data in x using the same input arguments as the loglfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the log-logistic distribution can be found at https://en.wikipedia.org/wiki/Log-logistic_distribution See also: fitdist, makedist, loglcdf, loglinv, loglpdf, loglrnd, loglfit, logllike, loglstat # name: # type: sq_string # elements: 1 # length: 45 Log-logistic probability distribution object. # name: # type: sq_string # elements: 1 # length: 45 prob.LoglogisticDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 194 prob.LoglogisticDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 38 prob.LoglogisticDistribution.InputData # name: # type: sq_string # elements: 1 # length: 744 prob.LoglogisticDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 40 prob.LoglogisticDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 214 prob.LoglogisticDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 52 prob.LoglogisticDistribution.LoglogisticDistribution # name: # type: sq_string # elements: 1 # length: 400 prob.LoglogisticDistribution: pd = LoglogisticDistribution ( mu , sigma ) prob.LoglogisticDistribution: pd = LoglogisticDistribution () Create a prob.LoglogisticDistribution object. mu and sigma are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, mu 0 and sigma 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 45 Create a prob.LoglogisticDistribution object. # name: # type: sq_string # elements: 1 # length: 42 prob.LoglogisticDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 207 prob.LoglogisticDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 48 prob.LoglogisticDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 626 prob.LoglogisticDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 49 prob.LoglogisticDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 237 prob.LoglogisticDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 45 prob.LoglogisticDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 293 prob.LoglogisticDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 43 prob.LoglogisticDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 214 prob.LoglogisticDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 44 prob.LoglogisticDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 294 prob.LoglogisticDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the mu and sigma properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 39 prob.LoglogisticDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 357 prob.LoglogisticDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 32 prob.LoglogisticDistribution.cdf # name: # type: sq_string # elements: 1 # length: 413 prob.LoglogisticDistribution: p = cdf ( pd , x ) prob.LoglogisticDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 33 prob.LoglogisticDistribution.icdf # name: # type: sq_string # elements: 1 # length: 255 prob.LoglogisticDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 32 prob.LoglogisticDistribution.iqr # name: # type: sq_string # elements: 1 # length: 202 prob.LoglogisticDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.LoglogisticDistribution.mean # name: # type: sq_string # elements: 1 # length: 174 prob.LoglogisticDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 35 prob.LoglogisticDistribution.median # name: # type: sq_string # elements: 1 # length: 182 prob.LoglogisticDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.LoglogisticDistribution.mu # name: # type: sq_string # elements: 1 # length: 227 prob.LoglogisticDistribution: property mu Mean of logarithmic values A scalar value characterizing the mean of the logarithmic values of the log-logistic distribution. You can access the mu property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 26 Mean of logarithmic values # name: # type: sq_string # elements: 1 # length: 38 prob.LoglogisticDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 228 prob.LoglogisticDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 36 prob.LoglogisticDistribution.paramci # name: # type: sq_string # elements: 1 # length: 998 prob.LoglogisticDistribution: ci = paramci ( pd ) prob.LoglogisticDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 32 prob.LoglogisticDistribution.pdf # name: # type: sq_string # elements: 1 # length: 215 prob.LoglogisticDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 33 prob.LoglogisticDistribution.plot # name: # type: sq_string # elements: 1 # length: 1557 prob.LoglogisticDistribution: plot ( pd ) prob.LoglogisticDistribution: plot ( pd , Name , Value ) prob.LoglogisticDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 36 prob.LoglogisticDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2160 prob.LoglogisticDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.LoglogisticDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.LoglogisticDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.LoglogisticDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.LoglogisticDistribution: [ nlogL , param ] = proflik ( pd ) prob.LoglogisticDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the Log-logistic distribution, pnum = 1 selects the parameter mu and pnum = 2 selects the parameter sigma . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 35 prob.LoglogisticDistribution.random # name: # type: sq_string # elements: 1 # length: 725 prob.LoglogisticDistribution: r = random ( pd ) prob.LoglogisticDistribution: r = random ( pd , rows ) prob.LoglogisticDistribution: r = random ( pd , rows , cols , …) prob.LoglogisticDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, random returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 34 prob.LoglogisticDistribution.sigma # name: # type: sq_string # elements: 1 # length: 244 prob.LoglogisticDistribution: property sigma Scale of logarithmic values A positive scalar value characterizing the scale of the logarithmic values of the log-logistic distribution. You can access the sigma property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 27 Scale of logarithmic values # name: # type: sq_string # elements: 1 # length: 32 prob.LoglogisticDistribution.std # name: # type: sq_string # elements: 1 # length: 200 prob.LoglogisticDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 37 prob.LoglogisticDistribution.truncate # name: # type: sq_string # elements: 1 # length: 550 prob.LoglogisticDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.LoglogisticDistribution.var # name: # type: sq_string # elements: 1 # length: 180 prob.LoglogisticDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 26 prob.LognormalDistribution # name: # type: sq_string # elements: 1 # length: 1355 statistics: prob.LognormalDistribution Lognormal probability distribution object. A prob.LognormalDistribution object consists of parameters, a model description, and sample data for a lognormal probability distribution. The lognormal distribution is a continuous probability distribution whose logarithm is normally distributed. It is defined by mean parameter mu and standard deviation parameter sigma of the logarithmic values. There are several ways to create a prob.LognormalDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.LognormalDistribution ( mu , sigma ) to create a lognormal distribution with fixed parameter values mu and sigma . Use the static method prob.LognormalDistribution.fit ( x , censor , freq , options ) to fit a distribution to the data in x using the same input arguments as the lognfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the lognormal distribution can be found at https://en.wikipedia.org/wiki/Log-normal_distribution See also: fitdist, makedist, logncdf, logninv, lognpdf, lognrnd, lognfit, lognlike, lognstat # name: # type: sq_string # elements: 1 # length: 42 Lognormal probability distribution object. # name: # type: sq_string # elements: 1 # length: 43 prob.LognormalDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 192 prob.LognormalDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 36 prob.LognormalDistribution.InputData # name: # type: sq_string # elements: 1 # length: 742 prob.LognormalDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 38 prob.LognormalDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 212 prob.LognormalDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 48 prob.LognormalDistribution.LognormalDistribution # name: # type: sq_string # elements: 1 # length: 390 prob.LognormalDistribution: pd = LognormalDistribution ( mu , sigma ) prob.LognormalDistribution: pd = LognormalDistribution () Create a prob.LognormalDistribution object. mu and sigma are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, mu 0 and sigma 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 43 Create a prob.LognormalDistribution object. # name: # type: sq_string # elements: 1 # length: 40 prob.LognormalDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 205 prob.LognormalDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 46 prob.LognormalDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 624 prob.LognormalDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 47 prob.LognormalDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 235 prob.LognormalDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 43 prob.LognormalDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 291 prob.LognormalDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 41 prob.LognormalDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 212 prob.LognormalDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 42 prob.LognormalDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 292 prob.LognormalDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the mu and sigma properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 37 prob.LognormalDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 355 prob.LognormalDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 30 prob.LognormalDistribution.cdf # name: # type: sq_string # elements: 1 # length: 409 prob.LognormalDistribution: p = cdf ( pd , x ) prob.LognormalDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 31 prob.LognormalDistribution.icdf # name: # type: sq_string # elements: 1 # length: 253 prob.LognormalDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 30 prob.LognormalDistribution.iqr # name: # type: sq_string # elements: 1 # length: 200 prob.LognormalDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.LognormalDistribution.mean # name: # type: sq_string # elements: 1 # length: 172 prob.LognormalDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.LognormalDistribution.median # name: # type: sq_string # elements: 1 # length: 180 prob.LognormalDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 29 prob.LognormalDistribution.mu # name: # type: sq_string # elements: 1 # length: 222 prob.LognormalDistribution: property mu Mean of logarithmic values A scalar value characterizing the mean of the logarithmic values of the lognormal distribution. You can access the mu property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 26 Mean of logarithmic values # name: # type: sq_string # elements: 1 # length: 36 prob.LognormalDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 226 prob.LognormalDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.LognormalDistribution.paramci # name: # type: sq_string # elements: 1 # length: 994 prob.LognormalDistribution: ci = paramci ( pd ) prob.LognormalDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 30 prob.LognormalDistribution.pdf # name: # type: sq_string # elements: 1 # length: 213 prob.LognormalDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 31 prob.LognormalDistribution.plot # name: # type: sq_string # elements: 1 # length: 1551 prob.LognormalDistribution: plot ( pd ) prob.LognormalDistribution: plot ( pd , Name , Value ) prob.LognormalDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 34 prob.LognormalDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2145 prob.LognormalDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.LognormalDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.LognormalDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.LognormalDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.LognormalDistribution: [ nlogL , param ] = proflik ( pd ) prob.LognormalDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the Lognormal distribution, pnum = 1 selects the parameter mu and pnum = 2 selects the parameter sigma . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 33 prob.LognormalDistribution.random # name: # type: sq_string # elements: 1 # length: 718 prob.LognormalDistribution: r = random ( pd ) prob.LognormalDistribution: r = random ( pd , rows ) prob.LognormalDistribution: r = random ( pd , rows , cols , …) prob.LognormalDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, lognrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 32 prob.LognormalDistribution.sigma # name: # type: sq_string # elements: 1 # length: 265 prob.LognormalDistribution: property sigma Standard deviation of logarithmic values A positive scalar value characterizing the standard deviation of the logarithmic values of the lognormal distribution. You can access the sigma property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 40 Standard deviation of logarithmic values # name: # type: sq_string # elements: 1 # length: 30 prob.LognormalDistribution.std # name: # type: sq_string # elements: 1 # length: 198 prob.LognormalDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 35 prob.LognormalDistribution.truncate # name: # type: sq_string # elements: 1 # length: 548 prob.LognormalDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 30 prob.LognormalDistribution.var # name: # type: sq_string # elements: 1 # length: 178 prob.LognormalDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 27 prob.LoguniformDistribution # name: # type: sq_string # elements: 1 # length: 944 statistics: prob.LoguniformDistribution Log-uniform probability distribution object. A prob.LoguniformDistribution object consists of parameters and a model description for a log-uniform probability distribution. The log-uniform distribution is a continuous probability distribution that is constant between locations Lower and Upper on a logarithmic scale. There are several ways to create a prob.LoguniformDistribution object. Create a distribution with specified parameter values using the makedist function. Use the constructor prob.LoguniformDistribution ( Lower , Upper ) to create a log-uniform distribution with specified parameter values Lower and Upper . It is highly recommended to use makedist function to create probability distribution objects, instead of the class constructor. Further information about the log-uniform distribution can be found at https://en.wikipedia.org/wiki/Reciprocal_distribution See also: makedist # name: # type: sq_string # elements: 1 # length: 44 Log-uniform probability distribution object. # name: # type: sq_string # elements: 1 # length: 44 prob.LoguniformDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 193 prob.LoguniformDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 39 prob.LoguniformDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 213 prob.LoguniformDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 50 prob.LoguniformDistribution.LoguniformDistribution # name: # type: sq_string # elements: 1 # length: 404 prob.LoguniformDistribution: pd = LoguniformDistribution ( Lower , Upper ) prob.LoguniformDistribution: pd = LoguniformDistribution () Create a prob.LoguniformDistribution object. Lower and Upper are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, Lower 1 and Upper 4. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 44 Create a prob.LoguniformDistribution object. # name: # type: sq_string # elements: 1 # length: 33 prob.LoguniformDistribution.Lower # name: # type: sq_string # elements: 1 # length: 206 prob.LoguniformDistribution: property Lower Lower limit A positive scalar value characterizing the lower limit of the log-uniform distribution. You can access the Lower property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 11 Lower limit # name: # type: sq_string # elements: 1 # length: 41 prob.LoguniformDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 206 prob.LoguniformDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 48 prob.LoguniformDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 236 prob.LoguniformDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 42 prob.LoguniformDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 213 prob.LoguniformDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 43 prob.LoguniformDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 296 prob.LoguniformDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the Lower and Upper properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 38 prob.LoguniformDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 356 prob.LoguniformDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 33 prob.LoguniformDistribution.Upper # name: # type: sq_string # elements: 1 # length: 206 prob.LoguniformDistribution: property Upper Upper limit A positive scalar value characterizing the upper limit of the log-uniform distribution. You can access the Upper property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 11 Upper limit # name: # type: sq_string # elements: 1 # length: 31 prob.LoguniformDistribution.cdf # name: # type: sq_string # elements: 1 # length: 494 prob.LoguniformDistribution: p = cdf ( pd , x ) prob.LoguniformDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . x must be double or single ; integer, logical, and character arrays are rejected. # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 32 prob.LoguniformDistribution.icdf # name: # type: sq_string # elements: 1 # length: 337 prob.LoguniformDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . p must be double or single ; integer, logical, and character arrays are rejected. # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 31 prob.LoguniformDistribution.iqr # name: # type: sq_string # elements: 1 # length: 201 prob.LoguniformDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.LoguniformDistribution.mean # name: # type: sq_string # elements: 1 # length: 173 prob.LoguniformDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.LoguniformDistribution.median # name: # type: sq_string # elements: 1 # length: 181 prob.LoguniformDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.LoguniformDistribution.pdf # name: # type: sq_string # elements: 1 # length: 297 prob.LoguniformDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . x must be double or single ; integer, logical, and character arrays are rejected. # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 32 prob.LoguniformDistribution.plot # name: # type: sq_string # elements: 1 # length: 1126 prob.LoguniformDistribution: plot ( pd ) prob.LoguniformDistribution: plot ( pd , Name , Value ) prob.LoguniformDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). 'cdf' plots the cumulative density function (CDF). 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 34 prob.LoguniformDistribution.random # name: # type: sq_string # elements: 1 # length: 721 prob.LoguniformDistribution: r = random ( pd ) prob.LoguniformDistribution: r = random ( pd , rows ) prob.LoguniformDistribution: r = random ( pd , rows , cols , …) prob.LoguniformDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, random returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 31 prob.LoguniformDistribution.std # name: # type: sq_string # elements: 1 # length: 199 prob.LoguniformDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 36 prob.LoguniformDistribution.truncate # name: # type: sq_string # elements: 1 # length: 306 prob.LoguniformDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.LoguniformDistribution.var # name: # type: sq_string # elements: 1 # length: 179 prob.LoguniformDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.MultinomialDistribution # name: # type: sq_string # elements: 1 # length: 1083 statistics: prob.MultinomialDistribution Multinomial probability distribution object. A prob.MultinomialDistribution object consists of parameters, a model description, and sample data for a multinomial probability distribution. The multinomial distribution is a discrete probability distribution that models the outcomes of n independent trials of a k-category system, where each trial has a probability of falling into each category. It is defined by the vector of probabilities for each outcome. There are several ways to create a prob.MultinomialDistribution object. Create a distribution with specified parameter values using the makedist function. Use the constructor prob.MultinomialDistribution ( Probabilities ) to create a multinomial distribution with specified parameter values. It is highly recommended to use the makedist function to create probability distribution objects, instead of the constructor. Further information about the multinomial distribution can be found at https://en.wikipedia.org/wiki/Multinomial_distribution See also: makedist, mnpdf, mnrnd # name: # type: sq_string # elements: 1 # length: 44 Multinomial probability distribution object. # name: # type: sq_string # elements: 1 # length: 45 prob.MultinomialDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 194 prob.MultinomialDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 40 prob.MultinomialDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 214 prob.MultinomialDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 52 prob.MultinomialDistribution.MultinomialDistribution # name: # type: sq_string # elements: 1 # length: 408 prob.MultinomialDistribution: pd = MultinomialDistribution ( Probabilities ) prob.MultinomialDistribution: pd = MultinomialDistribution () Create a prob.MultinomialDistribution object. Probabilities is the distribution parameter, which the class help describes. Called with no arguments the parameter takes its default, Probabilities [0.5, 0.5] . makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 45 Create a prob.MultinomialDistribution object. # name: # type: sq_string # elements: 1 # length: 42 prob.MultinomialDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 207 prob.MultinomialDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 49 prob.MultinomialDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 237 prob.MultinomialDistribution: property ParameterDescription Description of parameters A 1×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 43 prob.MultinomialDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 214 prob.MultinomialDistribution: property ParameterNames Names of parameters A 1×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 44 prob.MultinomialDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 283 prob.MultinomialDistribution: property ParameterValues Distribution parameter values A numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the Probabilities property. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 42 prob.MultinomialDistribution.Probabilities # name: # type: sq_string # elements: 1 # length: 193 prob.MultinomialDistribution: property Probabilities Outcome probabilities A row vector of probabilities for each outcome. You can access the Probabilities property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 21 Outcome probabilities # name: # type: sq_string # elements: 1 # length: 39 prob.MultinomialDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 357 prob.MultinomialDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 32 prob.MultinomialDistribution.cdf # name: # type: sq_string # elements: 1 # length: 582 prob.MultinomialDistribution: p = cdf ( pd , x ) prob.MultinomialDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . x must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 33 prob.MultinomialDistribution.icdf # name: # type: sq_string # elements: 1 # length: 338 prob.MultinomialDistribution: p = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). p = icdf ( pd , x ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in x . p must be double or single ; integer, logical, and character arrays are rejected. # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 32 prob.MultinomialDistribution.iqr # name: # type: sq_string # elements: 1 # length: 202 prob.MultinomialDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.MultinomialDistribution.mean # name: # type: sq_string # elements: 1 # length: 174 prob.MultinomialDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 35 prob.MultinomialDistribution.median # name: # type: sq_string # elements: 1 # length: 182 prob.MultinomialDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.MultinomialDistribution.pdf # name: # type: sq_string # elements: 1 # length: 384 prob.MultinomialDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . x must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 33 prob.MultinomialDistribution.plot # name: # type: sq_string # elements: 1 # length: 1555 prob.MultinomialDistribution: plot ( pd ) prob.MultinomialDistribution: plot ( pd , Name , Value ) prob.MultinomialDistribution: h = plot (…) Plot a probability distribution object. plot ( pd plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 35 prob.MultinomialDistribution.random # name: # type: sq_string # elements: 1 # length: 724 prob.MultinomialDistribution: y = random ( pd ) prob.MultinomialDistribution: y = random ( pd , rows ) prob.MultinomialDistribution: y = random ( pd , rows , cols , …) prob.MultinomialDistribution: y = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, mnrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 32 prob.MultinomialDistribution.std # name: # type: sq_string # elements: 1 # length: 200 prob.MultinomialDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 37 prob.MultinomialDistribution.truncate # name: # type: sq_string # elements: 1 # length: 534 prob.MultinomialDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.MultinomialDistribution.var # name: # type: sq_string # elements: 1 # length: 190 prob.MultinomialDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 25 prob.NakagamiDistribution # name: # type: sq_string # elements: 1 # length: 1353 statistics: prob.NakagamiDistribution Nakagami probability distribution object. A prob.NakagamiDistribution object consists of parameters, a model description, and sample data for a Nakagami probability distribution. The Nakagami distribution is a continuous probability distribution that models the amplitude of received signals after maximum ratio diversity combining. It is defined by shape parameter mu and spread parameter omega . There are several ways to create a prob.NakagamiDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.NakagamiDistribution ( mu , omega ) to create a Nakagami distribution with fixed parameter values mu and omega . Use the static method prob.NakagamiDistribution.fit ( x , censor , freq , options ) to fit a distribution to the data in x using the same input arguments as the nakafit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the Nakagami distribution can be found at https://en.wikipedia.org/wiki/Nakagami_distribution See also: fitdist, makedist, nakacdf, nakainv, nakapdf, nakarnd, nakafit, nakalike, nakastat # name: # type: sq_string # elements: 1 # length: 41 Nakagami probability distribution object. # name: # type: sq_string # elements: 1 # length: 42 prob.NakagamiDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 191 prob.NakagamiDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 35 prob.NakagamiDistribution.InputData # name: # type: sq_string # elements: 1 # length: 741 prob.NakagamiDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 37 prob.NakagamiDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 211 prob.NakagamiDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 46 prob.NakagamiDistribution.NakagamiDistribution # name: # type: sq_string # elements: 1 # length: 385 prob.NakagamiDistribution: pd = NakagamiDistribution ( mu , omega ) prob.NakagamiDistribution: pd = NakagamiDistribution () Create a prob.NakagamiDistribution object. mu and omega are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, mu 1 and omega 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 42 Create a prob.NakagamiDistribution object. # name: # type: sq_string # elements: 1 # length: 39 prob.NakagamiDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 204 prob.NakagamiDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 45 prob.NakagamiDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 623 prob.NakagamiDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 46 prob.NakagamiDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 234 prob.NakagamiDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 42 prob.NakagamiDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 290 prob.NakagamiDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 40 prob.NakagamiDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 211 prob.NakagamiDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 41 prob.NakagamiDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 291 prob.NakagamiDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the mu and omega properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 36 prob.NakagamiDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 354 prob.NakagamiDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 29 prob.NakagamiDistribution.cdf # name: # type: sq_string # elements: 1 # length: 407 prob.NakagamiDistribution: p = cdf ( pd , x ) prob.NakagamiDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 30 prob.NakagamiDistribution.icdf # name: # type: sq_string # elements: 1 # length: 252 prob.NakagamiDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 29 prob.NakagamiDistribution.iqr # name: # type: sq_string # elements: 1 # length: 199 prob.NakagamiDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 30 prob.NakagamiDistribution.mean # name: # type: sq_string # elements: 1 # length: 171 prob.NakagamiDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.NakagamiDistribution.median # name: # type: sq_string # elements: 1 # length: 179 prob.NakagamiDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.NakagamiDistribution.mu # name: # type: sq_string # elements: 1 # length: 193 prob.NakagamiDistribution: property mu Shape parameter A positive scalar value characterizing the shape of the Nakagami distribution. You can access the mu property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Shape parameter # name: # type: sq_string # elements: 1 # length: 35 prob.NakagamiDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 225 prob.NakagamiDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.NakagamiDistribution.omega # name: # type: sq_string # elements: 1 # length: 201 prob.NakagamiDistribution: property omega Spread parameter A positive scalar value characterizing the spread of the Nakagami distribution. You can access the omega property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 16 Spread parameter # name: # type: sq_string # elements: 1 # length: 33 prob.NakagamiDistribution.paramci # name: # type: sq_string # elements: 1 # length: 992 prob.NakagamiDistribution: ci = paramci ( pd ) prob.NakagamiDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 29 prob.NakagamiDistribution.pdf # name: # type: sq_string # elements: 1 # length: 212 prob.NakagamiDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 30 prob.NakagamiDistribution.plot # name: # type: sq_string # elements: 1 # length: 1548 prob.NakagamiDistribution: plot ( pd ) prob.NakagamiDistribution: plot ( pd , Name , Value ) prob.NakagamiDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 33 prob.NakagamiDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2138 prob.NakagamiDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.NakagamiDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.NakagamiDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.NakagamiDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.NakagamiDistribution: [ nlogL , param ] = proflik ( pd ) prob.NakagamiDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the Nakagami distribution, pnum = 1 selects the parameter mu and pnum = 2 selects the parameter omega . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 32 prob.NakagamiDistribution.random # name: # type: sq_string # elements: 1 # length: 714 prob.NakagamiDistribution: r = random ( pd ) prob.NakagamiDistribution: r = random ( pd , rows ) prob.NakagamiDistribution: r = random ( pd , rows , cols , …) prob.NakagamiDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, betarnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 29 prob.NakagamiDistribution.std # name: # type: sq_string # elements: 1 # length: 197 prob.NakagamiDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.NakagamiDistribution.truncate # name: # type: sq_string # elements: 1 # length: 547 prob.NakagamiDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 29 prob.NakagamiDistribution.var # name: # type: sq_string # elements: 1 # length: 177 prob.NakagamiDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.NegativeBinomialDistribution # name: # type: sq_string # elements: 1 # length: 1521 statistics: prob.NegativeBinomialDistribution Negative binomial probability distribution object. A prob.NegativeBinomialDistribution object consists of parameters, a model description, and sample data for a negative binomial probability distribution. The negative binomial distribution is a discrete probability distribution that models the number of failures in a sequence of independent and identically distributed Bernoulli trials before a specified (non-random) number of successes occurs. It is defined by the number of successes R and the probability of success P . There are several ways to create a prob.NegativeBinomialDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.NegativeBinomialDistribution ( R , P ) to create a negative binomial distribution with fixed parameter values R and P . Use the static method prob.NegativeBinomialDistribution.fit ( x , freq , options ) to fit a distribution to the data in x using the same input arguments as the nbinfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the negative binomial distribution can be found at https://en.wikipedia.org/wiki/Negative_binomial_distribution See also: fitdist, makedist, nbincdf, nbininv, nbinpdf, nbinrnd, nbinfit, nbinlike, nbinstat # name: # type: sq_string # elements: 1 # length: 50 Negative binomial probability distribution object. # name: # type: sq_string # elements: 1 # length: 50 prob.NegativeBinomialDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 199 prob.NegativeBinomialDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 43 prob.NegativeBinomialDistribution.InputData # name: # type: sq_string # elements: 1 # length: 749 prob.NegativeBinomialDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 45 prob.NegativeBinomialDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 219 prob.NegativeBinomialDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 62 prob.NegativeBinomialDistribution.NegativeBinomialDistribution # name: # type: sq_string # elements: 1 # length: 412 prob.NegativeBinomialDistribution: pd = NegativeBinomialDistribution ( R , P ) prob.NegativeBinomialDistribution: pd = NegativeBinomialDistribution () Create a prob.NegativeBinomialDistribution object. R and P are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, R 1 and P 0.5. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 50 Create a prob.NegativeBinomialDistribution object. # name: # type: sq_string # elements: 1 # length: 47 prob.NegativeBinomialDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 212 prob.NegativeBinomialDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 35 prob.NegativeBinomialDistribution.P # name: # type: sq_string # elements: 1 # length: 223 prob.NegativeBinomialDistribution: property P Probability of success A scalar value characterizing the probability of success in the negative binomial distribution. You can access the P property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 22 Probability of success # name: # type: sq_string # elements: 1 # length: 53 prob.NegativeBinomialDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 631 prob.NegativeBinomialDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 54 prob.NegativeBinomialDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 242 prob.NegativeBinomialDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 50 prob.NegativeBinomialDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 298 prob.NegativeBinomialDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 48 prob.NegativeBinomialDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 219 prob.NegativeBinomialDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 49 prob.NegativeBinomialDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 294 prob.NegativeBinomialDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the R and P properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 35 prob.NegativeBinomialDistribution.R # name: # type: sq_string # elements: 1 # length: 217 prob.NegativeBinomialDistribution: property R Number of successes A scalar value characterizing the number of successes in the negative binomial distribution. You can access the R property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 19 Number of successes # name: # type: sq_string # elements: 1 # length: 44 prob.NegativeBinomialDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 362 prob.NegativeBinomialDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 37 prob.NegativeBinomialDistribution.cdf # name: # type: sq_string # elements: 1 # length: 423 prob.NegativeBinomialDistribution: p = cdf ( pd , x ) prob.NegativeBinomialDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 38 prob.NegativeBinomialDistribution.icdf # name: # type: sq_string # elements: 1 # length: 260 prob.NegativeBinomialDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 37 prob.NegativeBinomialDistribution.iqr # name: # type: sq_string # elements: 1 # length: 207 prob.NegativeBinomialDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 38 prob.NegativeBinomialDistribution.mean # name: # type: sq_string # elements: 1 # length: 179 prob.NegativeBinomialDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 40 prob.NegativeBinomialDistribution.median # name: # type: sq_string # elements: 1 # length: 187 prob.NegativeBinomialDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 43 prob.NegativeBinomialDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 233 prob.NegativeBinomialDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 41 prob.NegativeBinomialDistribution.paramci # name: # type: sq_string # elements: 1 # length: 1008 prob.NegativeBinomialDistribution: ci = paramci ( pd ) prob.NegativeBinomialDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 37 prob.NegativeBinomialDistribution.pdf # name: # type: sq_string # elements: 1 # length: 220 prob.NegativeBinomialDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 38 prob.NegativeBinomialDistribution.plot # name: # type: sq_string # elements: 1 # length: 1572 prob.NegativeBinomialDistribution: plot ( pd ) prob.NegativeBinomialDistribution: plot ( pd , Name , Value ) prob.NegativeBinomialDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 41 prob.NegativeBinomialDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2190 prob.NegativeBinomialDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.NegativeBinomialDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.NegativeBinomialDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.NegativeBinomialDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.NegativeBinomialDistribution: [ nlogL , param ] = proflik ( pd ) prob.NegativeBinomialDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the negative binomial distribution, pnum = 1 selects the parameter R and pnum = 2 selects the parameter P . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 40 prob.NegativeBinomialDistribution.random # name: # type: sq_string # elements: 1 # length: 747 prob.NegativeBinomialDistribution: r = random ( pd ) prob.NegativeBinomialDistribution: r = random ( pd , rows ) prob.NegativeBinomialDistribution: r = random ( pd , rows , cols , …) prob.NegativeBinomialDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, nbindrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 37 prob.NegativeBinomialDistribution.std # name: # type: sq_string # elements: 1 # length: 205 prob.NegativeBinomialDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 42 prob.NegativeBinomialDistribution.truncate # name: # type: sq_string # elements: 1 # length: 555 prob.NegativeBinomialDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 37 prob.NegativeBinomialDistribution.var # name: # type: sq_string # elements: 1 # length: 185 prob.NegativeBinomialDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 23 prob.NormalDistribution # name: # type: sq_string # elements: 1 # length: 1317 statistics: prob.NormalDistribution Normal probability distribution object. A prob.NormalDistribution object consists of parameters, a model description, and sample data for a normal probability distribution. The normal distribution is a continuous probability distribution that is symmetric about the mean, mu , showing that data near the mean are more frequent in occurrence than data far from the mean. It is defined by location parameter mu and scale parameter sigma . There are several ways to create a prob.NormalDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.NormalDistribution ( mu , sigma ) to create a normal distribution with fixed parameter values mu and sigma . Use the static method prob.NormalDistribution.fit ( x , censor , freq , options ) to fit a distribution to data x . It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the normal distribution can be found at https://en.wikipedia.org/wiki/Normal_distribution See also: fitdist, makedist, normcdf, norminv, normpdf, normrnd, normfit, normlike, normstat # name: # type: sq_string # elements: 1 # length: 39 Normal probability distribution object. # name: # type: sq_string # elements: 1 # length: 40 prob.NormalDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 189 prob.NormalDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 33 prob.NormalDistribution.InputData # name: # type: sq_string # elements: 1 # length: 739 prob.NormalDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 35 prob.NormalDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 209 prob.NormalDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 42 prob.NormalDistribution.NormalDistribution # name: # type: sq_string # elements: 1 # length: 375 prob.NormalDistribution: pd = NormalDistribution ( mu , sigma ) prob.NormalDistribution: pd = NormalDistribution () Create a prob.NormalDistribution object. mu and sigma are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, mu 0 and sigma 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 40 Create a prob.NormalDistribution object. # name: # type: sq_string # elements: 1 # length: 37 prob.NormalDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 202 prob.NormalDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 43 prob.NormalDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 621 prob.NormalDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 44 prob.NormalDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 232 prob.NormalDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 40 prob.NormalDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 288 prob.NormalDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 38 prob.NormalDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 209 prob.NormalDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 39 prob.NormalDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 289 prob.NormalDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the mu and sigma properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 34 prob.NormalDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 352 prob.NormalDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 27 prob.NormalDistribution.cdf # name: # type: sq_string # elements: 1 # length: 403 prob.NormalDistribution: p = cdf ( pd , x ) prob.NormalDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 28 prob.NormalDistribution.icdf # name: # type: sq_string # elements: 1 # length: 250 prob.NormalDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 27 prob.NormalDistribution.iqr # name: # type: sq_string # elements: 1 # length: 197 prob.NormalDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.NormalDistribution.mean # name: # type: sq_string # elements: 1 # length: 169 prob.NormalDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 30 prob.NormalDistribution.median # name: # type: sq_string # elements: 1 # length: 177 prob.NormalDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 26 prob.NormalDistribution.mu # name: # type: sq_string # elements: 1 # length: 186 prob.NormalDistribution: property mu Location parameter A scalar value characterizing the location of the normal distribution. You can access the mu property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 18 Location parameter # name: # type: sq_string # elements: 1 # length: 33 prob.NormalDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 223 prob.NormalDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.NormalDistribution.paramci # name: # type: sq_string # elements: 1 # length: 988 prob.NormalDistribution: ci = paramci ( pd ) prob.NormalDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 27 prob.NormalDistribution.pdf # name: # type: sq_string # elements: 1 # length: 210 prob.NormalDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 28 prob.NormalDistribution.plot # name: # type: sq_string # elements: 1 # length: 1542 prob.NormalDistribution: plot ( pd ) prob.NormalDistribution: plot ( pd , Name , Value ) prob.NormalDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 31 prob.NormalDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2124 prob.NormalDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.NormalDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.NormalDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.NormalDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.NormalDistribution: [ nlogL , param ] = proflik ( pd ) prob.NormalDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the normal distribution, pnum = 1 selects the parameter mu and pnum = 2 selects the parameter sigma . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 30 prob.NormalDistribution.random # name: # type: sq_string # elements: 1 # length: 706 prob.NormalDistribution: r = random ( pd ) prob.NormalDistribution: r = random ( pd , rows ) prob.NormalDistribution: r = random ( pd , rows , cols , …) prob.NormalDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, normrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 29 prob.NormalDistribution.sigma # name: # type: sq_string # elements: 1 # length: 195 prob.NormalDistribution: property sigma Scale parameter A positive scalar value characterizing the scale of the normal distribution. You can access the sigma property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 27 prob.NormalDistribution.std # name: # type: sq_string # elements: 1 # length: 195 prob.NormalDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.NormalDistribution.truncate # name: # type: sq_string # elements: 1 # length: 545 prob.NormalDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 27 prob.NormalDistribution.var # name: # type: sq_string # elements: 1 # length: 175 prob.NormalDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.PiecewiseLinearDistribution # name: # type: sq_string # elements: 1 # length: 1184 statistics: prob.PiecewiseLinearDistribution Piecewise linear probability distribution object. A prob.PiecewiseLinearDistribution object consists of parameters, a model description, and sample data for a piecewise linear probability distribution. The piecewise linear distribution is a continuous probability distribution that is defined by a set of points where the cumulative distribution function (CDF) changes slope. It is defined by a vector of x values and a corresponding vector of CDF values Fx . There are several ways to create a prob.PiecewiseLinearDistribution object. Create a distribution with specified parameter values using the makedist function. Use the constructor prob.PiecewiseLinearDistribution ( x , Fx ) to create a piecewise linear distribution with specified parameter values x and Fx . It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the piecewise linear distribution can be found at https://en.wikipedia.org/wiki/Piecewise_linear_function See also: makedist, plcdf, plinv, plpdf, plrnd, plstat # name: # type: sq_string # elements: 1 # length: 49 Piecewise linear probability distribution object. # name: # type: sq_string # elements: 1 # length: 49 prob.PiecewiseLinearDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 198 prob.PiecewiseLinearDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 35 prob.PiecewiseLinearDistribution.Fx # name: # type: sq_string # elements: 1 # length: 244 prob.PiecewiseLinearDistribution: property Fx Vector of CDF values A numeric row vector of CDF values that correspond to each value in x , reported as a row whichever way it was given. You can access the Fx property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 20 Vector of CDF values # name: # type: sq_string # elements: 1 # length: 44 prob.PiecewiseLinearDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 218 prob.PiecewiseLinearDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 46 prob.PiecewiseLinearDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 211 prob.PiecewiseLinearDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 53 prob.PiecewiseLinearDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 241 prob.PiecewiseLinearDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 47 prob.PiecewiseLinearDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 218 prob.PiecewiseLinearDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 48 prob.PiecewiseLinearDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 294 prob.PiecewiseLinearDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the x and Fx properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 60 prob.PiecewiseLinearDistribution.PiecewiseLinearDistribution # name: # type: sq_string # elements: 1 # length: 419 prob.PiecewiseLinearDistribution: pd = PiecewiseLinearDistribution ( x , Fx ) prob.PiecewiseLinearDistribution: pd = PiecewiseLinearDistribution () Create a prob.PiecewiseLinearDistribution object. x and Fx are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, x [0; 1] and Fx [0; 1] . makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 49 Create a prob.PiecewiseLinearDistribution object. # name: # type: sq_string # elements: 1 # length: 43 prob.PiecewiseLinearDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 361 prob.PiecewiseLinearDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 36 prob.PiecewiseLinearDistribution.cdf # name: # type: sq_string # elements: 1 # length: 421 prob.PiecewiseLinearDistribution: p = cdf ( pd , x ) prob.PiecewiseLinearDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 37 prob.PiecewiseLinearDistribution.icdf # name: # type: sq_string # elements: 1 # length: 259 prob.PiecewiseLinearDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 36 prob.PiecewiseLinearDistribution.iqr # name: # type: sq_string # elements: 1 # length: 206 prob.PiecewiseLinearDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 37 prob.PiecewiseLinearDistribution.mean # name: # type: sq_string # elements: 1 # length: 178 prob.PiecewiseLinearDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 39 prob.PiecewiseLinearDistribution.median # name: # type: sq_string # elements: 1 # length: 186 prob.PiecewiseLinearDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 36 prob.PiecewiseLinearDistribution.pdf # name: # type: sq_string # elements: 1 # length: 219 prob.PiecewiseLinearDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 37 prob.PiecewiseLinearDistribution.plot # name: # type: sq_string # elements: 1 # length: 1569 prob.PiecewiseLinearDistribution: plot ( pd ) prob.PiecewiseLinearDistribution: plot ( pd , Name , Value ) prob.PiecewiseLinearDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 39 prob.PiecewiseLinearDistribution.random # name: # type: sq_string # elements: 1 # length: 742 prob.PiecewiseLinearDistribution: r = random ( pd ) prob.PiecewiseLinearDistribution: r = random ( pd , rows ) prob.PiecewiseLinearDistribution: r = random ( pd , rows , cols , …) prob.PiecewiseLinearDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, betarnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 36 prob.PiecewiseLinearDistribution.std # name: # type: sq_string # elements: 1 # length: 204 prob.PiecewiseLinearDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 41 prob.PiecewiseLinearDistribution.truncate # name: # type: sq_string # elements: 1 # length: 538 prob.PiecewiseLinearDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 36 prob.PiecewiseLinearDistribution.var # name: # type: sq_string # elements: 1 # length: 194 prob.PiecewiseLinearDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.PiecewiseLinearDistribution.x # name: # type: sq_string # elements: 1 # length: 233 prob.PiecewiseLinearDistribution: property x Vector of x values A numeric row vector of x values at which the CDF changes slope, reported as a row whichever way it was given. You can access the x property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 18 Vector of x values # name: # type: sq_string # elements: 1 # length: 24 prob.PoissonDistribution # name: # type: sq_string # elements: 1 # length: 1336 statistics: prob.PoissonDistribution Poisson probability distribution object. A prob.PoissonDistribution object consists of parameters, a model description, and sample data for a Poisson probability distribution. The Poisson distribution is a discrete probability distribution that models the number of events occurring in a fixed interval of time or space, given a constant average rate of occurrence. It is defined by the rate parameter lambda . There are several ways to create a prob.PoissonDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.PoissonDistribution ( lambda ) to create a Poisson distribution with fixed parameter value lambda . Use the static method prob.PoissonDistribution.fit ( x , freq ) to fit a distribution to the data in x using the same input arguments as the poissfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the Poisson distribution can be found at https://en.wikipedia.org/wiki/Poisson_distribution See also: fitdist, makedist, poisscdf, poissinv, poisspdf, poissrnd, poissfit, poisslike, poisstat # name: # type: sq_string # elements: 1 # length: 40 Poisson probability distribution object. # name: # type: sq_string # elements: 1 # length: 41 prob.PoissonDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 190 prob.PoissonDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 34 prob.PoissonDistribution.InputData # name: # type: sq_string # elements: 1 # length: 740 prob.PoissonDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 36 prob.PoissonDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 210 prob.PoissonDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 38 prob.PoissonDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 203 prob.PoissonDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 44 prob.PoissonDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 422 prob.PoissonDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 1×1 numeric matrix containing the variance of the parameter estimate. This matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then the variance is zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 45 prob.PoissonDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 233 prob.PoissonDistribution: property ParameterDescription Description of parameters A 1×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 41 prob.PoissonDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 278 prob.PoissonDistribution: property ParameterIsFixed Flag for fixed parameters A logical scalar specifying whether the parameter is fixed or estimated. A true value corresponds to a fixed parameter, a false value corresponds to a parameter estimate. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 39 prob.PoissonDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 210 prob.PoissonDistribution: property ParameterNames Names of parameters A 1×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 40 prob.PoissonDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 282 prob.PoissonDistribution: property ParameterValues Distribution parameter values A 1×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the lambda property. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 44 prob.PoissonDistribution.PoissonDistribution # name: # type: sq_string # elements: 1 # length: 357 prob.PoissonDistribution: pd = PoissonDistribution ( lambda ) prob.PoissonDistribution: pd = PoissonDistribution () Create a prob.PoissonDistribution object. lambda is the distribution parameter, which the class help describes. Called with no arguments the parameter takes its default, lambda 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 41 Create a prob.PoissonDistribution object. # name: # type: sq_string # elements: 1 # length: 35 prob.PoissonDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 361 prob.PoissonDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. The first element contains the lower boundary, the second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 28 prob.PoissonDistribution.cdf # name: # type: sq_string # elements: 1 # length: 405 prob.PoissonDistribution: p = cdf ( pd , x ) prob.PoissonDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 29 prob.PoissonDistribution.icdf # name: # type: sq_string # elements: 1 # length: 251 prob.PoissonDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 28 prob.PoissonDistribution.iqr # name: # type: sq_string # elements: 1 # length: 198 prob.PoissonDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.PoissonDistribution.lambda # name: # type: sq_string # elements: 1 # length: 197 prob.PoissonDistribution: property lambda Rate parameter A positive scalar value characterizing the rate of the Poisson distribution. You can access the lambda property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 14 Rate parameter # name: # type: sq_string # elements: 1 # length: 29 prob.PoissonDistribution.mean # name: # type: sq_string # elements: 1 # length: 170 prob.PoissonDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.PoissonDistribution.median # name: # type: sq_string # elements: 1 # length: 178 prob.PoissonDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.PoissonDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 224 prob.PoissonDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.PoissonDistribution.paramci # name: # type: sq_string # elements: 1 # length: 990 prob.PoissonDistribution: ci = paramci ( pd ) prob.PoissonDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 28 prob.PoissonDistribution.pdf # name: # type: sq_string # elements: 1 # length: 211 prob.PoissonDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 29 prob.PoissonDistribution.plot # name: # type: sq_string # elements: 1 # length: 1485 prob.PoissonDistribution: plot ( pd ) prob.PoissonDistribution: plot ( pd , Name , Value ) prob.PoissonDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. 'Parent' An axes graphics object for the plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 32 prob.PoissonDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2094 prob.PoissonDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.PoissonDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.PoissonDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.PoissonDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.PoissonDistribution: [ nlogL , param ] = proflik ( pd ) prob.PoissonDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the Poisson distribution, pnum = 1 selects the parameter lambda . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the user-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 31 prob.PoissonDistribution.random # name: # type: sq_string # elements: 1 # length: 711 prob.PoissonDistribution: r = random ( pd ) prob.PoissonDistribution: r = random ( pd , rows ) prob.PoissonDistribution: r = random ( pd , rows , cols , …) prob.PoissonDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, poissrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 28 prob.PoissonDistribution.std # name: # type: sq_string # elements: 1 # length: 196 prob.PoissonDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.PoissonDistribution.truncate # name: # type: sq_string # elements: 1 # length: 546 prob.PoissonDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.PoissonDistribution.var # name: # type: sq_string # elements: 1 # length: 176 prob.PoissonDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.ProbabilityDistribution # name: # type: sq_string # elements: 1 # length: 768 statistics: prob.ProbabilityDistribution Abstract base class of the probability distribution objects. It holds the behaviour every distribution object shares – how it is displayed, how its parameter confidence intervals are computed, how it is plotted and how a profile likelihood is taken – as protected methods, so the 29 distribution classes inherit one implementation and no part of it is reachable from outside them. These helpers were ordinary files in a private directory until the classes moved into the prob namespace. Octave does not resolve a private directory from inside a package directory, for a classdef or for a plain function, so the only way to keep them out of the public interface is to make them protected methods of a shared base. # name: # type: sq_string # elements: 1 # length: 60 Abstract base class of the probability distribution objects. # name: # type: sq_string # elements: 1 # length: 25 prob.RayleighDistribution # name: # type: sq_string # elements: 1 # length: 1522 statistics: prob.RayleighDistribution Rayleigh probability distribution object. A prob.RayleighDistribution object consists of parameters, a model description, and sample data for a Rayleigh probability distribution. The Rayleigh distribution is a continuous probability distribution for nonnegative random variables. It is often used to model the magnitude of a vector in two dimensions where the components are normally distributed with zero mean and equal variance. It is defined by scale parameter B . B is the sigma of the usual mathematical notation. The rayl* functions name the same quantity sigma ; this class follows MATLAB. There are several ways to create a prob.RayleighDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.RayleighDistribution ( B ) to create a Rayleigh distribution with fixed parameter value B . Use the static method prob.RayleighDistribution.fit ( x , censor , freq ) to fit a distribution to the data in x using the same input arguments as the raylfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the Rayleigh distribution can be found at https://en.wikipedia.org/wiki/Rayleigh_distribution See also: fitdist, makedist, raylcdf, raylinv, raylpdf, raylrnd, raylfit, rayllike, raylstat # name: # type: sq_string # elements: 1 # length: 41 Rayleigh probability distribution object. # name: # type: sq_string # elements: 1 # length: 27 prob.RayleighDistribution.B # name: # type: sq_string # elements: 1 # length: 191 prob.RayleighDistribution: property B Scale parameter A positive scalar value characterizing the scale of the Rayleigh distribution. You can access the B property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 42 prob.RayleighDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 191 prob.RayleighDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 35 prob.RayleighDistribution.InputData # name: # type: sq_string # elements: 1 # length: 741 prob.RayleighDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 37 prob.RayleighDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 211 prob.RayleighDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 39 prob.RayleighDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 204 prob.RayleighDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 45 prob.RayleighDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 613 prob.RayleighDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 46 prob.RayleighDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 224 prob.RayleighDistribution: property ParameterDescription Description of parameters A cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 42 prob.RayleighDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 280 prob.RayleighDistribution: property ParameterIsFixed Flag for fixed parameters A logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 40 prob.RayleighDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 201 prob.RayleighDistribution: property ParameterNames Names of parameters A cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 41 prob.RayleighDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 268 prob.RayleighDistribution: property ParameterValues Distribution parameter values A numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the B property. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 46 prob.RayleighDistribution.RayleighDistribution # name: # type: sq_string # elements: 1 # length: 347 prob.RayleighDistribution: pd = RayleighDistribution ( B ) prob.RayleighDistribution: pd = RayleighDistribution () Create a prob.RayleighDistribution object. B is the distribution parameter, which the class help describes. Called with no arguments the parameter takes its default, B 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 42 Create a prob.RayleighDistribution object. # name: # type: sq_string # elements: 1 # length: 36 prob.RayleighDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 354 prob.RayleighDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 29 prob.RayleighDistribution.cdf # name: # type: sq_string # elements: 1 # length: 407 prob.RayleighDistribution: p = cdf ( pd , x ) prob.RayleighDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 30 prob.RayleighDistribution.icdf # name: # type: sq_string # elements: 1 # length: 252 prob.RayleighDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 29 prob.RayleighDistribution.iqr # name: # type: sq_string # elements: 1 # length: 199 prob.RayleighDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 30 prob.RayleighDistribution.mean # name: # type: sq_string # elements: 1 # length: 171 prob.RayleighDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.RayleighDistribution.median # name: # type: sq_string # elements: 1 # length: 179 prob.RayleighDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 35 prob.RayleighDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 225 prob.RayleighDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.RayleighDistribution.paramci # name: # type: sq_string # elements: 1 # length: 992 prob.RayleighDistribution: ci = paramci ( pd ) prob.RayleighDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 29 prob.RayleighDistribution.pdf # name: # type: sq_string # elements: 1 # length: 212 prob.RayleighDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 30 prob.RayleighDistribution.plot # name: # type: sq_string # elements: 1 # length: 1548 prob.RayleighDistribution: plot ( pd ) prob.RayleighDistribution: plot ( pd , Name , Value ) prob.RayleighDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 33 prob.RayleighDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2096 prob.RayleighDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.RayleighDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.RayleighDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.RayleighDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.RayleighDistribution: [ nlogL , param ] = proflik ( pd ) prob.RayleighDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the Rayleigh distribution, pnum = 1 selects the parameter B . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 32 prob.RayleighDistribution.random # name: # type: sq_string # elements: 1 # length: 714 prob.RayleighDistribution: r = random ( pd ) prob.RayleighDistribution: r = random ( pd , rows ) prob.RayleighDistribution: r = random ( pd , rows , cols , …) prob.RayleighDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, betarnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 29 prob.RayleighDistribution.std # name: # type: sq_string # elements: 1 # length: 197 prob.RayleighDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.RayleighDistribution.truncate # name: # type: sq_string # elements: 1 # length: 547 prob.RayleighDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 29 prob.RayleighDistribution.var # name: # type: sq_string # elements: 1 # length: 177 prob.RayleighDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 23 prob.RicianDistribution # name: # type: sq_string # elements: 1 # length: 1258 statistics: prob.RicianDistribution Rician probability distribution object. A prob.RicianDistribution object consists of parameters, a model description, and sample data for a Rician probability distribution. The Rician distribution is a continuous probability distribution that models the magnitude of a signal in the presence of Gaussian noise. It is defined by noncentrality parameter s and scale parameter sigma . There are several ways to create a prob.RicianDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.RicianDistribution ( s , sigma ) to create a Rician distribution with fixed parameter values s and sigma . Use the static method prob.RicianDistribution.fit ( x , censor , freq , options ) to fit a distribution to data x . It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the Rician distribution can be found at https://en.wikipedia.org/wiki/Rice_distribution See also: fitdist, makedist, ricecdf, riceinv, ricepdf, ricernd, ricefit, ricelike, ricestat # name: # type: sq_string # elements: 1 # length: 39 Rician probability distribution object. # name: # type: sq_string # elements: 1 # length: 40 prob.RicianDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 189 prob.RicianDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 33 prob.RicianDistribution.InputData # name: # type: sq_string # elements: 1 # length: 739 prob.RicianDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 35 prob.RicianDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 209 prob.RicianDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 37 prob.RicianDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 202 prob.RicianDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 43 prob.RicianDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 621 prob.RicianDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 44 prob.RicianDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 232 prob.RicianDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 40 prob.RicianDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 288 prob.RicianDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 38 prob.RicianDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 209 prob.RicianDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 39 prob.RicianDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 288 prob.RicianDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the s and sigma properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 42 prob.RicianDistribution.RicianDistribution # name: # type: sq_string # elements: 1 # length: 372 prob.RicianDistribution: pd = RicianDistribution ( s , sigma ) prob.RicianDistribution: pd = RicianDistribution () Create a prob.RicianDistribution object. s and sigma are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, s 1 and sigma 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 40 Create a prob.RicianDistribution object. # name: # type: sq_string # elements: 1 # length: 34 prob.RicianDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 352 prob.RicianDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 27 prob.RicianDistribution.cdf # name: # type: sq_string # elements: 1 # length: 403 prob.RicianDistribution: p = cdf ( pd , x ) prob.RicianDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 28 prob.RicianDistribution.icdf # name: # type: sq_string # elements: 1 # length: 250 prob.RicianDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 27 prob.RicianDistribution.iqr # name: # type: sq_string # elements: 1 # length: 197 prob.RicianDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.RicianDistribution.mean # name: # type: sq_string # elements: 1 # length: 169 prob.RicianDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 30 prob.RicianDistribution.median # name: # type: sq_string # elements: 1 # length: 177 prob.RicianDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.RicianDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 223 prob.RicianDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.RicianDistribution.paramci # name: # type: sq_string # elements: 1 # length: 988 prob.RicianDistribution: ci = paramci ( pd ) prob.RicianDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 27 prob.RicianDistribution.pdf # name: # type: sq_string # elements: 1 # length: 210 prob.RicianDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 28 prob.RicianDistribution.plot # name: # type: sq_string # elements: 1 # length: 1542 prob.RicianDistribution: plot ( pd ) prob.RicianDistribution: plot ( pd , Name , Value ) prob.RicianDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 31 prob.RicianDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2123 prob.RicianDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.RicianDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.RicianDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.RicianDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.RicianDistribution: [ nlogL , param ] = proflik ( pd ) prob.RicianDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the Rician distribution, pnum = 1 selects the parameter s and pnum = 2 selects the parameter sigma . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 30 prob.RicianDistribution.random # name: # type: sq_string # elements: 1 # length: 706 prob.RicianDistribution: r = random ( pd ) prob.RicianDistribution: r = random ( pd , rows ) prob.RicianDistribution: r = random ( pd , rows , cols , …) prob.RicianDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, ricernd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 25 prob.RicianDistribution.s # name: # type: sq_string # elements: 1 # length: 207 prob.RicianDistribution: property s Noncentrality parameter A non-negative scalar value characterizing the noncentrality of the Rician distribution. You can access the s property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 23 Noncentrality parameter # name: # type: sq_string # elements: 1 # length: 29 prob.RicianDistribution.sigma # name: # type: sq_string # elements: 1 # length: 195 prob.RicianDistribution: property sigma Scale parameter A positive scalar value characterizing the scale of the Rician distribution. You can access the sigma property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 27 prob.RicianDistribution.std # name: # type: sq_string # elements: 1 # length: 195 prob.RicianDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.RicianDistribution.truncate # name: # type: sq_string # elements: 1 # length: 545 prob.RicianDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 27 prob.RicianDistribution.var # name: # type: sq_string # elements: 1 # length: 175 prob.RicianDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 23 prob.StableDistribution # name: # type: sq_string # elements: 1 # length: 1774 statistics: prob.StableDistribution Stable probability distribution object. A prob.StableDistribution object consists of parameters, a model description, and sample data for a stable probability distribution. The stable distribution is a continuous probability distribution family closed under linear combinations, generalizing the normal, Cauchy, and Levy distributions. It is parameterized, in the Nolan S0 parameterization, by a tail index (first shape parameter) alpha in (0, 2] , a skewness (second shape parameter) beta in [-1, 1] , a scale parameter gam greater than zero, and a location parameter delta . There are several ways to create a prob.StableDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.StableDistribution ( alpha , beta , gam , delta ) to create a stable distribution with fixed parameter values alpha , beta , gam , and delta . Use the static method prob.StableDistribution.fit ( x , alpha , freq , options ) to fit a distribution to the data in x using the same input arguments as the stblfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Fitting is by maximum likelihood. Because the stable density has no closed form, it is evaluated by numerical inversion of the characteristic function, which makes fitting considerably slower than for the closed-form distributions. Further information about the stable distribution can be found at https://en.wikipedia.org/wiki/Stable_distribution See also: fitdist, makedist, stblpdf, stblcdf, stblinv, stblrnd, stblfit, stbllike # name: # type: sq_string # elements: 1 # length: 39 Stable probability distribution object. # name: # type: sq_string # elements: 1 # length: 40 prob.StableDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 189 prob.StableDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 33 prob.StableDistribution.InputData # name: # type: sq_string # elements: 1 # length: 260 prob.StableDistribution: property InputData Data used for fitting the distribution A structure containing the data used to fit the distribution. It is empty unless the distribution was fitted with fitdist or the static fit method. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Data used for fitting the distribution # name: # type: sq_string # elements: 1 # length: 35 prob.StableDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 173 prob.StableDistribution: property IsTruncated Flag for truncated distribution A logical scalar that is true when the distribution is truncated. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Flag for truncated distribution # name: # type: sq_string # elements: 1 # length: 37 prob.StableDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 202 prob.StableDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 43 prob.StableDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 222 prob.StableDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 4×4 numeric matrix containing the variance-covariance of the distribution parameters. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 44 prob.StableDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 232 prob.StableDistribution: property ParameterDescription Description of parameters A 4×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 40 prob.StableDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 200 prob.StableDistribution: property ParameterIsFixed Flags for fixed parameters A 4×1 logical vector specifying which parameters are held fixed rather than estimated. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Flags for fixed parameters # name: # type: sq_string # elements: 1 # length: 38 prob.StableDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 209 prob.StableDistribution: property ParameterNames Names of parameters A 4×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 39 prob.StableDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 303 prob.StableDistribution: property ParameterValues Distribution parameter values A 4×1 numeric vector containing the values of the distribution parameters, matching the order in ParameterNames . This property is read-only; use dot name assignment on the alpha , beta , gam , and delta properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 42 prob.StableDistribution.StableDistribution # name: # type: sq_string # elements: 1 # length: 425 prob.StableDistribution: pd = StableDistribution ( alpha , beta , gam , delta ) prob.StableDistribution: pd = StableDistribution () Create a prob.StableDistribution object. alpha , beta , gam and delta are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, alpha 2, beta 0, gam 1 and delta 0. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 40 Create a prob.StableDistribution object. # name: # type: sq_string # elements: 1 # length: 34 prob.StableDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 187 prob.StableDistribution: property Truncation Truncation interval A two-element numeric vector with the truncation interval, if the distribution is truncated. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 29 prob.StableDistribution.alpha # name: # type: sq_string # elements: 1 # length: 234 prob.StableDistribution: property alpha Tail index (first shape parameter) A scalar value in the range (0, 2] characterizing the tail behaviour of the stable distribution. You can access the alpha property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 34 Tail index (first shape parameter) # name: # type: sq_string # elements: 1 # length: 28 prob.StableDistribution.beta # name: # type: sq_string # elements: 1 # length: 226 prob.StableDistribution: property beta Skewness (second shape parameter) A scalar value in the range [-1, 1] characterizing the skewness of the stable distribution. You can access the beta property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 33 Skewness (second shape parameter) # name: # type: sq_string # elements: 1 # length: 27 prob.StableDistribution.cdf # name: # type: sq_string # elements: 1 # length: 326 prob.StableDistribution: p = cdf ( pd , x ) prob.StableDistribution: p = cdf ( pd , x , "upper" ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . The optional "upper" flag computes the upper tail probability. # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 29 prob.StableDistribution.delta # name: # type: sq_string # elements: 1 # length: 192 prob.StableDistribution: property delta Location parameter A scalar value characterizing the location of the stable distribution. You can access the delta property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 18 Location parameter # name: # type: sq_string # elements: 1 # length: 27 prob.StableDistribution.gam # name: # type: sq_string # elements: 1 # length: 191 prob.StableDistribution: property gam Scale parameter A positive scalar value characterizing the scale of the stable distribution. You can access the gam property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 28 prob.StableDistribution.icdf # name: # type: sq_string # elements: 1 # length: 250 prob.StableDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 27 prob.StableDistribution.iqr # name: # type: sq_string # elements: 1 # length: 197 prob.StableDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.StableDistribution.mean # name: # type: sq_string # elements: 1 # length: 225 prob.StableDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . The mean is NaN for alpha <= 1 , where it is undefined. # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 30 prob.StableDistribution.median # name: # type: sq_string # elements: 1 # length: 177 prob.StableDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.StableDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 280 prob.StableDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . It returns an empty value when pd is not fitted to data. # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.StableDistribution.paramci # name: # type: sq_string # elements: 1 # length: 1002 prob.StableDistribution: ci = paramci ( pd ) prob.StableDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise the parameter values are returned in both rows. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 27 prob.StableDistribution.pdf # name: # type: sq_string # elements: 1 # length: 205 prob.StableDistribution: y = pdf ( pd , x ) Compute the probability density function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 47 Compute the probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 28 prob.StableDistribution.plot # name: # type: sq_string # elements: 1 # length: 378 prob.StableDistribution: plot ( pd ) prob.StableDistribution: plot ( pd , Name , Value ) prob.StableDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots the probability density function (PDF) of the probability distribution object pd . Name-value pair arguments select the plotted function and its appearance, as documented in __plot__ . # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 31 prob.StableDistribution.proflik # name: # type: sq_string # elements: 1 # length: 1909 prob.StableDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.StableDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.StableDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.StableDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.StableDistribution: [ nlogL , param ] = proflik ( pd ) prob.StableDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the stable distribution, pnum = 1 selects the tail index alpha , pnum = 2 selects the skewness beta , pnum = 3 selects the scale gam , and pnum = 4 selects the location delta . # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 30 prob.StableDistribution.random # name: # type: sq_string # elements: 1 # length: 397 prob.StableDistribution: r = random ( pd ) prob.StableDistribution: r = random ( pd , rows ) prob.StableDistribution: r = random ( pd , rows , cols , …) prob.StableDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd , following the size conventions of stblrnd . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 27 prob.StableDistribution.std # name: # type: sq_string # elements: 1 # length: 253 prob.StableDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . It is NaN for alpha < 2 , where the variance is infinite. # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.StableDistribution.truncate # name: # type: sq_string # elements: 1 # length: 248 prob.StableDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns the probability distribution pd truncated to the interval with lower limit lower and upper limit upper . # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 27 prob.StableDistribution.var # name: # type: sq_string # elements: 1 # length: 233 prob.StableDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . It is NaN for alpha < 2 , where the variance is infinite. # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 27 prob.TriangularDistribution # name: # type: sq_string # elements: 1 # length: 1005 statistics: prob.TriangularDistribution Triangular probability distribution object. A prob.TriangularDistribution object consists of parameters, a model description, and sample data for a triangular probability distribution. The triangular distribution uses the following parameters. Parameter Description Support A Lower limit -Inf < A < Inf B Peak location A <= B <= C C Upper limit C > A There are several ways to create a prob.TriangularDistribution object. Create a distribution with specified parameter values using the makedist function. Use the constructor prob.TriangularDistribution ( A , B , C ) to create a triangular distribution with specified parameter values A , B , and C . It is highly recommended to use makedist function to create probability distribution objects, instead of the constructor. Further information about the triangular distribution can be found at https://en.wikipedia.org/wiki/Triangular_distribution See also: makedist, tricdf, triinv, tripdf, trirnd, tristat # name: # type: sq_string # elements: 1 # length: 43 Triangular probability distribution object. # name: # type: sq_string # elements: 1 # length: 29 prob.TriangularDistribution.A # name: # type: sq_string # elements: 1 # length: 198 prob.TriangularDistribution: property A Lower limit parameter A scalar value characterizing the lower limit of the triangular distribution. You can access the A property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 21 Lower limit parameter # name: # type: sq_string # elements: 1 # length: 29 prob.TriangularDistribution.B # name: # type: sq_string # elements: 1 # length: 202 prob.TriangularDistribution: property B Peak location parameter A scalar value characterizing the peak location of the triangular distribution. You can access the B property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 23 Peak location parameter # name: # type: sq_string # elements: 1 # length: 29 prob.TriangularDistribution.C # name: # type: sq_string # elements: 1 # length: 198 prob.TriangularDistribution: property C Upper limit parameter A scalar value characterizing the upper limit of the triangular distribution. You can access the C property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 21 Upper limit parameter # name: # type: sq_string # elements: 1 # length: 44 prob.TriangularDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 193 prob.TriangularDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 39 prob.TriangularDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 213 prob.TriangularDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 41 prob.TriangularDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 206 prob.TriangularDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 48 prob.TriangularDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 236 prob.TriangularDistribution: property ParameterDescription Description of parameters A 3×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 42 prob.TriangularDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 213 prob.TriangularDistribution: property ParameterNames Names of parameters A 3×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 43 prob.TriangularDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 294 prob.TriangularDistribution: property ParameterValues Distribution parameter values A 3×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the A , B , and C properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 50 prob.TriangularDistribution.TriangularDistribution # name: # type: sq_string # elements: 1 # length: 395 prob.TriangularDistribution: pd = TriangularDistribution ( A , B , C ) prob.TriangularDistribution: pd = TriangularDistribution () Create a prob.TriangularDistribution object. A , B and C are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, A 0, B 0.5 and C 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 44 Create a prob.TriangularDistribution object. # name: # type: sq_string # elements: 1 # length: 38 prob.TriangularDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 356 prob.TriangularDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 31 prob.TriangularDistribution.cdf # name: # type: sq_string # elements: 1 # length: 411 prob.TriangularDistribution: p = cdf ( pd , x ) prob.TriangularDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 32 prob.TriangularDistribution.icdf # name: # type: sq_string # elements: 1 # length: 254 prob.TriangularDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 31 prob.TriangularDistribution.iqr # name: # type: sq_string # elements: 1 # length: 201 prob.TriangularDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.TriangularDistribution.mean # name: # type: sq_string # elements: 1 # length: 173 prob.TriangularDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.TriangularDistribution.median # name: # type: sq_string # elements: 1 # length: 181 prob.TriangularDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.TriangularDistribution.pdf # name: # type: sq_string # elements: 1 # length: 209 prob.TriangularDistribution: y = pdf ( pd , x ) Compute the probability density function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 47 Compute the probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 32 prob.TriangularDistribution.plot # name: # type: sq_string # elements: 1 # length: 1135 prob.TriangularDistribution: plot ( pd ) prob.TriangularDistribution: plot ( pd , Name , Value ) prob.TriangularDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). 'cdf' plots the cumulative density function (CDF). 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, this option is ignored. 'Parent' An axes graphics object for the plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 34 prob.TriangularDistribution.random # name: # type: sq_string # elements: 1 # length: 721 prob.TriangularDistribution: r = random ( pd ) prob.TriangularDistribution: r = random ( pd , rows ) prob.TriangularDistribution: r = random ( pd , rows , cols , …) prob.TriangularDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, trirnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 31 prob.TriangularDistribution.std # name: # type: sq_string # elements: 1 # length: 199 prob.TriangularDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 36 prob.TriangularDistribution.truncate # name: # type: sq_string # elements: 1 # length: 306 prob.TriangularDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.TriangularDistribution.var # name: # type: sq_string # elements: 1 # length: 179 prob.TriangularDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 24 prob.UniformDistribution # name: # type: sq_string # elements: 1 # length: 1160 statistics: prob.UniformDistribution Continuous uniform probability distribution object. A prob.UniformDistribution object consists of parameters, a model description, and sample data for a uniform probability distribution. The uniform distribution is a continuous probability distribution that models random variables that are equally likely to take any value within a specified interval defined by the lower limit Lower and upper limit Upper . There are several ways to create a prob.UniformDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.UniformDistribution ( Lower , Upper ) to create a uniform distribution with fixed parameter values Lower and Upper . It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor. Further information about the continuous uniform distribution can be found at https://en.wikipedia.org/wiki/Continuous_uniform_distribution See also: fitdist, makedist, unifcdf, unifinv, unifpdf, unifrnd, unifit, unifstat # name: # type: sq_string # elements: 1 # length: 51 Continuous uniform probability distribution object. # name: # type: sq_string # elements: 1 # length: 41 prob.UniformDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 190 prob.UniformDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 36 prob.UniformDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 210 prob.UniformDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 30 prob.UniformDistribution.Lower # name: # type: sq_string # elements: 1 # length: 200 prob.UniformDistribution: property Lower Lower limit parameter A scalar value characterizing the lower bound of the uniform distribution. You can access the Lower property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 21 Lower limit parameter # name: # type: sq_string # elements: 1 # length: 38 prob.UniformDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 203 prob.UniformDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 45 prob.UniformDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 233 prob.UniformDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 39 prob.UniformDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 210 prob.UniformDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 40 prob.UniformDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 293 prob.UniformDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the Lower and Upper properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 35 prob.UniformDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 353 prob.UniformDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 44 prob.UniformDistribution.UniformDistribution # name: # type: sq_string # elements: 1 # length: 389 prob.UniformDistribution: pd = UniformDistribution ( Lower , Upper ) prob.UniformDistribution: pd = UniformDistribution () Create a prob.UniformDistribution object. Lower and Upper are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, Lower 0 and Upper 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 41 Create a prob.UniformDistribution object. # name: # type: sq_string # elements: 1 # length: 30 prob.UniformDistribution.Upper # name: # type: sq_string # elements: 1 # length: 200 prob.UniformDistribution: property Upper Upper limit parameter A scalar value characterizing the upper bound of the uniform distribution. You can access the Upper property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 21 Upper limit parameter # name: # type: sq_string # elements: 1 # length: 28 prob.UniformDistribution.cdf # name: # type: sq_string # elements: 1 # length: 405 prob.UniformDistribution: p = cdf ( pd , x ) prob.UniformDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 29 prob.UniformDistribution.icdf # name: # type: sq_string # elements: 1 # length: 251 prob.UniformDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 28 prob.UniformDistribution.iqr # name: # type: sq_string # elements: 1 # length: 198 prob.UniformDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 29 prob.UniformDistribution.mean # name: # type: sq_string # elements: 1 # length: 170 prob.UniformDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.UniformDistribution.median # name: # type: sq_string # elements: 1 # length: 178 prob.UniformDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.UniformDistribution.pdf # name: # type: sq_string # elements: 1 # length: 211 prob.UniformDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 29 prob.UniformDistribution.plot # name: # type: sq_string # elements: 1 # length: 1545 prob.UniformDistribution: plot ( pd ) prob.UniformDistribution: plot ( pd , Name , Value ) prob.UniformDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 31 prob.UniformDistribution.random # name: # type: sq_string # elements: 1 # length: 710 prob.UniformDistribution: r = random ( pd ) prob.UniformDistribution: r = random ( pd , rows ) prob.UniformDistribution: r = random ( pd , rows , cols , …) prob.UniformDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, unifrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 28 prob.UniformDistribution.std # name: # type: sq_string # elements: 1 # length: 196 prob.UniformDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.UniformDistribution.truncate # name: # type: sq_string # elements: 1 # length: 546 prob.UniformDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.UniformDistribution.var # name: # type: sq_string # elements: 1 # length: 176 prob.UniformDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 24 prob.WeibullDistribution # name: # type: sq_string # elements: 1 # length: 1472 statistics: prob.WeibullDistribution Weibull probability distribution object. A prob.WeibullDistribution object consists of parameters, a model description, and sample data for a Weibull probability distribution. The Weibull distribution is a continuous probability distribution that models the time to failure of materials or the lifetime of mechanical systems. It is defined by scale parameter A and shape parameter B . A is the lambda of the usual mathematical notation and B is its k . The wbl* functions name the same two quantities lambda and k ; this class follows MATLAB. There are several ways to create a prob.WeibullDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.WeibullDistribution ( A , B ) to create a Weibull distribution with fixed parameter values A and B . Use the static method prob.WeibullDistribution.fit ( x , alpha , censor , freq ) to fit a distribution to the data in x using the same input arguments as the wblfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the Weibull distribution can be found at https://en.wikipedia.org/wiki/Weibull_distribution See also: fitdist, makedist, wblcdf, wblinv, wblpdf, wblrnd, wblfit, wbllike, wblstat # name: # type: sq_string # elements: 1 # length: 40 Weibull probability distribution object. # name: # type: sq_string # elements: 1 # length: 26 prob.WeibullDistribution.A # name: # type: sq_string # elements: 1 # length: 189 prob.WeibullDistribution: property A Scale parameter A positive scalar value characterizing the scale of the Weibull distribution. You can access the A property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 26 prob.WeibullDistribution.B # name: # type: sq_string # elements: 1 # length: 189 prob.WeibullDistribution: property B Shape parameter A positive scalar value characterizing the shape of the Weibull distribution. You can access the B property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Shape parameter # name: # type: sq_string # elements: 1 # length: 41 prob.WeibullDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 190 prob.WeibullDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 34 prob.WeibullDistribution.InputData # name: # type: sq_string # elements: 1 # length: 740 prob.WeibullDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 36 prob.WeibullDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 210 prob.WeibullDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 38 prob.WeibullDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 203 prob.WeibullDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 44 prob.WeibullDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 622 prob.WeibullDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 2×2 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 45 prob.WeibullDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 233 prob.WeibullDistribution: property ParameterDescription Description of parameters A 2×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 41 prob.WeibullDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 289 prob.WeibullDistribution: property ParameterIsFixed Flag for fixed parameters A 1×2 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 39 prob.WeibullDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 210 prob.WeibullDistribution: property ParameterNames Names of parameters A 2×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 40 prob.WeibullDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 285 prob.WeibullDistribution: property ParameterValues Distribution parameter values A 2×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the A and B properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 35 prob.WeibullDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 353 prob.WeibullDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 44 prob.WeibullDistribution.WeibullDistribution # name: # type: sq_string # elements: 1 # length: 365 prob.WeibullDistribution: pd = WeibullDistribution ( A , B ) prob.WeibullDistribution: pd = WeibullDistribution () Create a prob.WeibullDistribution object. A and B are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, A 1 and B 1. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 41 Create a prob.WeibullDistribution object. # name: # type: sq_string # elements: 1 # length: 28 prob.WeibullDistribution.cdf # name: # type: sq_string # elements: 1 # length: 405 prob.WeibullDistribution: p = cdf ( pd , x ) prob.WeibullDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 29 prob.WeibullDistribution.icdf # name: # type: sq_string # elements: 1 # length: 251 prob.WeibullDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 28 prob.WeibullDistribution.iqr # name: # type: sq_string # elements: 1 # length: 198 prob.WeibullDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 29 prob.WeibullDistribution.mean # name: # type: sq_string # elements: 1 # length: 170 prob.WeibullDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.WeibullDistribution.median # name: # type: sq_string # elements: 1 # length: 178 prob.WeibullDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.WeibullDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 224 prob.WeibullDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 32 prob.WeibullDistribution.paramci # name: # type: sq_string # elements: 1 # length: 990 prob.WeibullDistribution: ci = paramci ( pd ) prob.WeibullDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 28 prob.WeibullDistribution.pdf # name: # type: sq_string # elements: 1 # length: 211 prob.WeibullDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 29 prob.WeibullDistribution.plot # name: # type: sq_string # elements: 1 # length: 1545 prob.WeibullDistribution: plot ( pd ) prob.WeibullDistribution: plot ( pd , Name , Value ) prob.WeibullDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 32 prob.WeibullDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2126 prob.WeibullDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.WeibullDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.WeibullDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.WeibullDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.WeibullDistribution: [ nlogL , param ] = proflik ( pd ) prob.WeibullDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the Weibull distribution, pnum = 1 selects the parameter A and pnum = 2 selects the parameter B . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 31 prob.WeibullDistribution.random # name: # type: sq_string # elements: 1 # length: 709 prob.WeibullDistribution: r = random ( pd ) prob.WeibullDistribution: r = random ( pd , rows ) prob.WeibullDistribution: r = random ( pd , rows , cols , …) prob.WeibullDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, wblrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 28 prob.WeibullDistribution.std # name: # type: sq_string # elements: 1 # length: 196 prob.WeibullDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 33 prob.WeibullDistribution.truncate # name: # type: sq_string # elements: 1 # length: 546 prob.WeibullDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 28 prob.WeibullDistribution.var # name: # type: sq_string # elements: 1 # length: 176 prob.WeibullDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. # name: # type: sq_string # elements: 1 # length: 31 prob.tLocationScaleDistribution # name: # type: sq_string # elements: 1 # length: 1588 statistics: prob.tLocationScaleDistribution Location-Scale Student’s T probability distribution object. A prob.tLocationScaleDistribution object consists of parameters, a model description, and sample data for a location-scale Student’s T probability distribution. The location-scale Student’s T distribution is a continuous probability distribution that generalizes the standard Student’s T distribution by including location and scale parameters. It is defined by location parameter mu , scale parameter sigma , and degrees of freedom nu . There are several ways to create a prob.tLocationScaleDistribution object. Fit a distribution to data using the fitdist function. Create a distribution with fixed parameter values using the makedist function. Use the constructor prob.tLocationScaleDistribution ( mu , sigma , nu ) to create a location-scale Student’s T distribution with fixed parameter values mu , sigma , and nu . Use the static method prob.tLocationScaleDistribution.fit ( x , censor , freq , options ) to fit a distribution to the data in x using the same input arguments as the tlsfit function. It is highly recommended to use fitdist and makedist functions to create probability distribution objects, instead of the class constructor or the aforementioned static method. Further information about the location-scale Student’s T distribution can be found at https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution See also: fitdist, makedist, tlscdf, tlsinv, tlspdf, tlsrnd, tlsfit, tlslike, tlsstat # name: # type: sq_string # elements: 1 # length: 59 Location-Scale Student's T probability distribution object. # name: # type: sq_string # elements: 1 # length: 48 prob.tLocationScaleDistribution.DistributionName # name: # type: sq_string # elements: 1 # length: 197 prob.tLocationScaleDistribution: property DistributionName Probability distribution name A character vector specifying the name of the probability distribution object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Probability distribution name # name: # type: sq_string # elements: 1 # length: 41 prob.tLocationScaleDistribution.InputData # name: # type: sq_string # elements: 1 # length: 747 prob.tLocationScaleDistribution: property InputData Data used for fitting a probability distribution A scalar structure containing the following fields: data : a numeric vector containing the data used for distribution fitting. cens : a numeric vector of logical values indicating censoring information corresponding to the elements of the data used for distribution fitting. If no censoring vector was used for distribution fitting, then this field defaults to an empty array. freq : a numeric vector of non-negative integer values containing the frequency information corresponding to the elements of the data used for distribution fitting. If no frequency vector was used for distribution fitting, then this field defaults to an empty array. # name: # type: sq_string # elements: 1 # length: 48 Data used for fitting a probability distribution # name: # type: sq_string # elements: 1 # length: 43 prob.tLocationScaleDistribution.IsTruncated # name: # type: sq_string # elements: 1 # length: 217 prob.tLocationScaleDistribution: property IsTruncated Flag for truncated probability distribution A logical scalar value specifying whether a probability distribution is truncated or not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Flag for truncated probability distribution # name: # type: sq_string # elements: 1 # length: 45 prob.tLocationScaleDistribution.NumParameters # name: # type: sq_string # elements: 1 # length: 210 prob.tLocationScaleDistribution: property NumParameters Number of parameters A scalar integer value specifying the number of parameters characterizing the probability distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of parameters # name: # type: sq_string # elements: 1 # length: 51 prob.tLocationScaleDistribution.ParameterCovariance # name: # type: sq_string # elements: 1 # length: 629 prob.tLocationScaleDistribution: property ParameterCovariance Covariance matrix of the parameter estimates A 3×3 numeric matrix containing the variance-covariance of the parameter estimates. Diagonal elements contain the variance of each estimated parameter, and non-diagonal elements contain the covariance between the parameter estimates. The covariance matrix is only meaningful when the distribution was fitted to data. If the distribution object was created with fixed parameters, or a parameter of a fitted distribution is modified, then all elements of the variance-covariance are zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Covariance matrix of the parameter estimates # name: # type: sq_string # elements: 1 # length: 52 prob.tLocationScaleDistribution.ParameterDescription # name: # type: sq_string # elements: 1 # length: 240 prob.tLocationScaleDistribution: property ParameterDescription Description of parameters A 3×1 cell array of character vectors with each element containing a short description of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Description of parameters # name: # type: sq_string # elements: 1 # length: 48 prob.tLocationScaleDistribution.ParameterIsFixed # name: # type: sq_string # elements: 1 # length: 296 prob.tLocationScaleDistribution: property ParameterIsFixed Flag for fixed parameters A 1×3 logical vector specifying which parameters are fixed and which are estimated. true values correspond to fixed parameters, false values correspond to parameter estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for fixed parameters # name: # type: sq_string # elements: 1 # length: 46 prob.tLocationScaleDistribution.ParameterNames # name: # type: sq_string # elements: 1 # length: 217 prob.tLocationScaleDistribution: property ParameterNames Names of parameters A 3×1 cell array of character vectors with each element containing the name of a distribution parameter. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Names of parameters # name: # type: sq_string # elements: 1 # length: 47 prob.tLocationScaleDistribution.ParameterValues # name: # type: sq_string # elements: 1 # length: 304 prob.tLocationScaleDistribution: property ParameterValues Distribution parameter values A 3×1 numeric vector containing the values of the distribution parameters. This property is read-only. You can change the distribution parameters by assigning new values to the mu , sigma , and nu properties. # name: # type: sq_string # elements: 1 # length: 29 Distribution parameter values # name: # type: sq_string # elements: 1 # length: 42 prob.tLocationScaleDistribution.Truncation # name: # type: sq_string # elements: 1 # length: 360 prob.tLocationScaleDistribution: property Truncation Truncation interval A 1×2 numeric vector specifying the truncation interval for the probability distribution. First element contains the lower boundary, second element contains the upper boundary. This property is read-only. You can only truncate a probability distribution with the truncate method. # name: # type: sq_string # elements: 1 # length: 19 Truncation interval # name: # type: sq_string # elements: 1 # length: 35 prob.tLocationScaleDistribution.cdf # name: # type: sq_string # elements: 1 # length: 419 prob.tLocationScaleDistribution: p = cdf ( pd , x ) prob.tLocationScaleDistribution: p = cdf ( pd , x , 'upper' ) Compute the cumulative distribution function (CDF). p = cdf ( pd , x ) computes the CDF of the probability distribution object, pd , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 51 Compute the cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 36 prob.tLocationScaleDistribution.icdf # name: # type: sq_string # elements: 1 # length: 258 prob.tLocationScaleDistribution: x = icdf ( pd , p ) Compute the inverse cumulative distribution function (iCDF). x = icdf ( pd , p ) computes the quantile (the inverse of the CDF) of the probability distribution object, pd , evaluated at the values in p . # name: # type: sq_string # elements: 1 # length: 60 Compute the inverse cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 35 prob.tLocationScaleDistribution.iqr # name: # type: sq_string # elements: 1 # length: 205 prob.tLocationScaleDistribution: r = iqr ( pd ) Compute the interquartile range of a probability distribution. r = iqr ( pd ) computes the interquartile range of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 62 Compute the interquartile range of a probability distribution. # name: # type: sq_string # elements: 1 # length: 36 prob.tLocationScaleDistribution.mean # name: # type: sq_string # elements: 1 # length: 177 prob.tLocationScaleDistribution: m = mean ( pd ) Compute the mean of a probability distribution. m = mean ( pd ) computes the mean of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 47 Compute the mean of a probability distribution. # name: # type: sq_string # elements: 1 # length: 38 prob.tLocationScaleDistribution.median # name: # type: sq_string # elements: 1 # length: 185 prob.tLocationScaleDistribution: m = median ( pd ) Compute the median of a probability distribution. m = median ( pd ) computes the median of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 49 Compute the median of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.tLocationScaleDistribution.mu # name: # type: sq_string # elements: 1 # length: 220 prob.tLocationScaleDistribution: property mu Location parameter A scalar value characterizing the location of the location-scale Student’s T distribution. You can access the mu property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 18 Location parameter # name: # type: sq_string # elements: 1 # length: 41 prob.tLocationScaleDistribution.negloglik # name: # type: sq_string # elements: 1 # length: 231 prob.tLocationScaleDistribution: nlogL = negloglik ( pd ) Compute the negative loglikelihood of a probability distribution. nlogL = negloglik ( pd ) computes the negative loglikelihood of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 65 Compute the negative loglikelihood of a probability distribution. # name: # type: sq_string # elements: 1 # length: 34 prob.tLocationScaleDistribution.nu # name: # type: sq_string # elements: 1 # length: 239 prob.tLocationScaleDistribution: property nu Degrees of freedom A positive scalar value characterizing the degrees of freedom of the location-scale Student’s T distribution. You can access the nu property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 18 Degrees of freedom # name: # type: sq_string # elements: 1 # length: 39 prob.tLocationScaleDistribution.paramci # name: # type: sq_string # elements: 1 # length: 1004 prob.tLocationScaleDistribution: ci = paramci ( pd ) prob.tLocationScaleDistribution: ci = paramci ( pd , Name , Value ) Compute the confidence intervals for probability distribution parameters. ci = paramci ( pd ) computes the lower and upper boundaries of the 95% confidence interval for each parameter of the probability distribution object, pd . ci = paramci ( pd , Name , Value ) computes the confidence intervals with additional options specified by Name-Value pair arguments listed below. Name Value 'Alpha' A scalar value in the range (0,1) specifying the significance level for the confidence interval. The default value 0.05 corresponds to a 95% confidence interval. 'Parameter' A character vector or a cell array of character vectors specifying the parameter names for which to compute confidence intervals. By default, paramci computes confidence intervals for all distribution parameters. paramci is meaningful only when pd is fitted to data, otherwise an empty array, [] , is returned. # name: # type: sq_string # elements: 1 # length: 73 Compute the confidence intervals for probability distribution parameters. # name: # type: sq_string # elements: 1 # length: 35 prob.tLocationScaleDistribution.pdf # name: # type: sq_string # elements: 1 # length: 218 prob.tLocationScaleDistribution: y = pdf ( pd , x ) Compute the probability distribution function (PDF). y = pdf ( pd , x ) computes the PDF of the probability distribution object, pd , evaluated at the values in x . # name: # type: sq_string # elements: 1 # length: 52 Compute the probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 36 prob.tLocationScaleDistribution.plot # name: # type: sq_string # elements: 1 # length: 1566 prob.tLocationScaleDistribution: plot ( pd ) prob.tLocationScaleDistribution: plot ( pd , Name , Value ) prob.tLocationScaleDistribution: h = plot (…) Plot a probability distribution object. plot ( pd ) plots a probability density function (PDF) of the probability distribution object pd . If pd contains data, which have been fitted by fitdist , the PDF is superimposed over a histogram of the data. plot ( pd , Name , Value ) specifies additional options with the Name-Value pair arguments listed below. Name Value 'PlotType' A character vector specifying the plot type. 'pdf' plots the probability density function (PDF). When pd is fit to data, the PDF is superimposed on a histogram of the data. 'cdf' plots the cumulative density function (CDF). When pd is fit to data, the CDF is superimposed over an empirical CDF. 'probability' plots a probability plot using a CDF of the data and a CDF of the fitted probability distribution. This option is available only when pd is fitted to data. 'Discrete' A logical scalar to specify whether to plot the PDF or CDF of a discrete distribution object as a line plot or a stem plot, by specifying false or true , respectively. By default, it is true for discrete distributions and false for continuous distributions. When pd is a continuous distribution object, option is ignored. 'Parent' An axes graphics object for plot. If not specified, the plot function plots into the current axes or creates a new axes object if one does not exist. h = plot (…) returns a graphics handle to the plotted objects. # name: # type: sq_string # elements: 1 # length: 39 Plot a probability distribution object. # name: # type: sq_string # elements: 1 # length: 39 prob.tLocationScaleDistribution.proflik # name: # type: sq_string # elements: 1 # length: 2236 prob.tLocationScaleDistribution: [ nlogL , param ] = proflik ( pd , pnum ) prob.tLocationScaleDistribution: [ nlogL , param ] = proflik ( pd , pnum , 'Display' , display ) prob.tLocationScaleDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam ) prob.tLocationScaleDistribution: [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , display ) prob.tLocationScaleDistribution: [ nlogL , param ] = proflik ( pd ) prob.tLocationScaleDistribution: [ nlogL , param , other ] = proflik (…) Profile likelihood function for a probability distribution object. [ nlogL , param ] = proflik ( pd , pnum ) returns a vector nlogL of negative loglikelihood values and a vector param of corresponding parameter values for the parameter in the position indicated by pnum . By default, proflik uses the lower and upper bounds of the 98% confidence interval and computes 101 equispaced values for the selected parameter when it is the only one being estimated, and 21 values otherwise. pd must be fitted to data. [ nlogL , param ] = proflik ( pd , pnum , 'Display' , 'on' ) also plots the profile likelihood against the default range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam ) defines a user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd , pnum , setparam , 'Display' , 'on' ) also plots the profile likelihood against the user-defined range of the selected parameter. [ nlogL , param ] = proflik ( pd ) selects the first parameter that is not fixed. [ nlogL , param , other ] = proflik (…) also returns a matrix other holding, in each row, the values of the remaining parameters that maximize the likelihood at the corresponding value of param . A fixed parameter keeps its own value. For the location-scale Student’s T distribution, pnum = 1 selects the parameter mu , pnum = 2 selects the parameter sigma , and pnum = 3 selects the parameter nu . When opted to display the profile likelihood plot, proflik also plots the baseline loglikelihood computed at the lower bound of the 95% confidence interval and estimated maximum likelihood. The latter might not be observable if it is outside of the used-defined range of parameter values. # name: # type: sq_string # elements: 1 # length: 66 Profile likelihood function for a probability distribution object. # name: # type: sq_string # elements: 1 # length: 38 prob.tLocationScaleDistribution.random # name: # type: sq_string # elements: 1 # length: 737 prob.tLocationScaleDistribution: r = random ( pd ) prob.tLocationScaleDistribution: r = random ( pd , rows ) prob.tLocationScaleDistribution: r = random ( pd , rows , cols , …) prob.tLocationScaleDistribution: r = random ( pd , [ sz ]) Generate random arrays from the probability distribution object. r = random ( pd ) returns a random number from the distribution object pd . When called with a single size argument, tlsrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . # name: # type: sq_string # elements: 1 # length: 64 Generate random arrays from the probability distribution object. # name: # type: sq_string # elements: 1 # length: 37 prob.tLocationScaleDistribution.sigma # name: # type: sq_string # elements: 1 # length: 229 prob.tLocationScaleDistribution: property sigma Scale parameter A positive scalar value characterizing the scale of the location-scale Student’s T distribution. You can access the sigma property using dot name assignment. # name: # type: sq_string # elements: 1 # length: 15 Scale parameter # name: # type: sq_string # elements: 1 # length: 35 prob.tLocationScaleDistribution.std # name: # type: sq_string # elements: 1 # length: 203 prob.tLocationScaleDistribution: s = std ( pd ) Compute the standard deviation of a probability distribution. s = std ( pd ) computes the standard deviation of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 61 Compute the standard deviation of a probability distribution. # name: # type: sq_string # elements: 1 # length: 58 prob.tLocationScaleDistribution.tLocationScaleDistribution # name: # type: sq_string # elements: 1 # length: 431 prob.tLocationScaleDistribution: pd = tLocationScaleDistribution ( mu , sigma , nu ) prob.tLocationScaleDistribution: pd = tLocationScaleDistribution () Create a prob.tLocationScaleDistribution object. mu , sigma and nu are the distribution parameters, which the class help describes. Called with no arguments the parameters take their defaults, mu 0, sigma 1 and nu 5. makedist is the usual way to create a distribution object. # name: # type: sq_string # elements: 1 # length: 48 Create a prob.tLocationScaleDistribution object. # name: # type: sq_string # elements: 1 # length: 40 prob.tLocationScaleDistribution.truncate # name: # type: sq_string # elements: 1 # length: 553 prob.tLocationScaleDistribution: t = truncate ( pd , lower , upper ) Truncate a probability distribution. t = truncate ( pd , lower , upper ) returns a probability distribution t , which is the probability distribution pd truncated to the specified interval with lower limit, lower , and upper limit, upper . If pd is fitted to data with fitdist , the returned probability distribution t is not fitted, does not contain any data or estimated values, and it is as it has been created with the makedist function, but it includes the truncation interval. # name: # type: sq_string # elements: 1 # length: 36 Truncate a probability distribution. # name: # type: sq_string # elements: 1 # length: 35 prob.tLocationScaleDistribution.var # name: # type: sq_string # elements: 1 # length: 183 prob.tLocationScaleDistribution: v = var ( pd ) Compute the variance of a probability distribution. v = var ( pd ) computes the variance of the probability distribution object, pd . # name: # type: sq_string # elements: 1 # length: 51 Compute the variance of a probability distribution. statistics-release-1.9.2/inst/Distribution_Classes/paretotails.m000066400000000000000000000445301524624707500251730ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef paretotails ## -*- texinfo -*- ## @deftp {statistics} paretotails ## ## Piecewise distribution with generalized Pareto tails. ## ## A @code{paretotails} object is a piecewise probability distribution fit to ## sample data. A generalized Pareto distribution (GPD) is fit to each tail ## of the data, below a lower quantile and above an upper quantile, while the ## middle of the distribution is described by the empirical cumulative ## distribution function of the data. This gives a smooth model for the ## tails, useful for extreme value analysis, together with a nonparametric ## description of the central region. ## ## Create a @code{paretotails} object with the constructor ## @code{@var{pt} = paretotails (@var{x}, @var{pl}, @var{pu})}, where @var{x} ## is the sample data and @var{pl} and @var{pu} are the cumulative ## probabilities at the lower and upper tail boundaries. Data at or below the ## @var{pl} quantile form the lower tail, data at or above the @var{pu} ## quantile form the upper tail, and the rest form the middle segment. ## ## Query the fitted object with the methods @code{cdf}, @code{pdf}, ## @code{icdf}, @code{random}, @code{boundary}, @code{nsegments}, ## @code{segment}, @code{lowerparams}, and @code{upperparams}. ## ## @strong{Note:} the kernel-smoothed middle option of @sc{matlab} ## (@code{paretotails (@var{x}, @var{pl}, @var{pu}, "kernel")}) is not yet ## supported; only the default empirical (@qcode{"ecdf"}) middle is available. ## ## @seealso{gpfit, gpcdf, gppdf, gpinv, ecdf, fitdist, ## GeneralizedParetoDistribution} ## @end deftp properties (SetAccess = private) ## -*- texinfo -*- ## @deftp {paretotails} {property} NumSegments ## ## Number of segments in the piecewise distribution (a lower tail, a middle, ## and an upper tail give three). ## ## @end deftp NumSegments = 3 ## -*- texinfo -*- ## @deftp {paretotails} {property} NumParameters ## ## Number of estimated parameters (two per fitted generalized Pareto tail). ## ## @end deftp NumParameters = 4 endproperties properties (Access = private) lowerP = [] # [k, sigma] of the lower-tail GPD, or [] if no tail upperP = [] # [k, sigma] of the upper-tail GPD, or [] if no tail pl = 0 # cumulative probability at the lower boundary pu = 1 # cumulative probability at the upper boundary ql = -Inf # lower boundary quantile qu = Inf # upper boundary quantile xknot = [] # middle-segment interpolation knots (x) pknot = [] # middle-segment interpolation knots (cumulative prob) nobs = 0 # number of (non-NaN) observations method = "ecdf" # description of the middle segment endproperties methods (Hidden) ## Custom display of the segment summary. function disp (this) printf (" Piecewise distribution with %d segments fit to %d ", ... this.NumSegments, this.nobs); printf ("observations:\n\n"); if (this.pl > 0) printf (" P = [0, %g]: lower tail, generalized Pareto ", this.pl); printf ("(k = %g, sigma = %g)\n", this.lowerP(1), this.lowerP(2)); endif printf (" P = [%g, %g]: middle, %s\n", this.pl, this.pu, this.method); if (this.pu < 1) printf (" P = [%g, 1]: upper tail, generalized Pareto ", this.pu); printf ("(k = %g, sigma = %g)\n", this.upperP(1), this.upperP(2)); endif endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {paretotails} {@var{pt} =} paretotails (@var{x}, @var{pl}, @var{pu}) ## @deftypefnx {paretotails} {@var{pt} =} paretotails (@var{x}, @var{pl}, @var{pu}, @var{cdffun}) ## ## Fit a piecewise distribution with generalized Pareto tails to @var{x}. ## ## @var{pl} and @var{pu} are the cumulative probabilities of the lower and ## upper tail boundaries, with @code{0 <= @var{pl} < @var{pu} <= 1}. A ## generalized Pareto distribution is fit by maximum likelihood to the ## exceedances in each tail; the middle segment uses the empirical ## cumulative distribution of @var{x}. ## ## @var{cdffun} selects the middle segment and defaults to @qcode{"ecdf"}; ## the @qcode{"kernel"} option of @sc{matlab} is not yet supported. ## ## @end deftypefn function this = paretotails (x, pl, pu, cdffun) if (nargin < 3) print_usage (); endif if (! (isnumeric (x) && isreal (x) && isvector (x))) error ("paretotails: X must be a numeric vector."); endif x = x(:); x(isnan (x)) = []; if (numel (x) < 2) error ("paretotails: X must contain at least two non-NaN values."); endif if (! (isnumeric (pl) && isscalar (pl) && isreal (pl) ... && pl >= 0 && pl <= 1)) error ("paretotails: PL must be a scalar in the range [0, 1]."); endif if (! (isnumeric (pu) && isscalar (pu) && isreal (pu) ... && pu >= 0 && pu <= 1)) error ("paretotails: PU must be a scalar in the range [0, 1]."); endif if (pl >= pu) error ("paretotails: PL must be less than PU."); endif if (nargin > 3) if (! ischar (cdffun)) error ("paretotails: CDFFUN must be 'ecdf' or 'kernel'."); endif switch (lower (cdffun)) case "ecdf" ## default case "kernel" error (strcat ("paretotails: the 'kernel' middle segment is", ... " not yet supported; use the default 'ecdf'.")); otherwise error ("paretotails: CDFFUN must be 'ecdf' or 'kernel'."); endswitch endif xs = sort (x); n = numel (xs); pp = ((1:n)' - 0.5) ./ n; this.pl = pl; this.pu = pu; this.nobs = n; this.ql = paretotails.quantile_pp (xs, pp, pl); this.qu = paretotails.quantile_pp (xs, pp, pu); ## Fit the generalized Pareto tails to the exceedances, which are already ## measured from their threshold and so have the zero location gpfit ## assumes. if (pl > 0) this.lowerP = gpfit (this.ql - xs(xs < this.ql)); endif if (pu < 1) this.upperP = gpfit (xs(xs > this.qu) - this.qu); endif ## Middle-segment interpolation knots: the interior data points, with the ## two boundary points prepended and appended so the piecewise-linear cdf ## maps [ql, qu] onto [pl, pu] exactly. mid = xs > this.ql & xs < this.qu; xk = xs(mid); pk = pp(mid); if (pl > 0) xk = [this.ql; xk]; pk = [pl; pk]; endif if (pu < 1) xk = [xk; this.qu]; pk = [pk; pu]; endif ## Drop any repeated abscissae so interp1 sees a strictly increasing grid keep = [true; diff(xk) > 0]; this.xknot = xk(keep); this.pknot = pk(keep); this.NumSegments = 1 + (pl > 0) + (pu < 1); this.NumParameters = 2 .* (pl > 0) + 2 .* (pu < 1); endfunction ## -*- texinfo -*- ## @deftypefn {paretotails} {@var{p} =} cdf (@var{pt}, @var{x}) ## ## Cumulative distribution function of the @code{paretotails} object ## @var{pt} evaluated at the values in @var{x}. ## ## @end deftypefn function p = cdf (this, x) if (nargin != 2) print_usage (); endif p = nan (size (x)); lo = x < this.ql; hi = x > this.qu; mid = ! lo & ! hi; if (any (lo(:))) p(lo) = this.pl .* (1 - gpcdf (this.ql - x(lo), ... this.lowerP(1), this.lowerP(2), 0)); endif if (any (hi(:))) p(hi) = this.pu + (1 - this.pu) .* gpcdf (x(hi) - this.qu, ... this.upperP(1), this.upperP(2), 0); endif if (any (mid(:))) p(mid) = interp1 (this.xknot, this.pknot, x(mid), "linear"); endif endfunction ## -*- texinfo -*- ## @deftypefn {paretotails} {@var{y} =} pdf (@var{pt}, @var{x}) ## ## Probability density function of the @code{paretotails} object @var{pt} ## evaluated at the values in @var{x}. ## ## @end deftypefn function y = pdf (this, x) if (nargin != 2) print_usage (); endif y = nan (size (x)); lo = x < this.ql; hi = x > this.qu; mid = ! lo & ! hi; if (any (lo(:))) y(lo) = this.pl .* gppdf (this.ql - x(lo), ... this.lowerP(1), this.lowerP(2), 0); endif if (any (hi(:))) y(hi) = (1 - this.pu) .* gppdf (x(hi) - this.qu, ... this.upperP(1), this.upperP(2), 0); endif if (any (mid(:))) ## The middle cdf is piecewise linear, so its density is the piecewise ## constant slope of the segment each point falls in. slope = diff (this.pknot) ./ diff (this.xknot); idx = paretotails.bin_index (this.xknot, x(mid)); y(mid) = slope(idx); endif endfunction ## -*- texinfo -*- ## @deftypefn {paretotails} {@var{x} =} icdf (@var{pt}, @var{p}) ## ## Inverse cumulative distribution function (quantile function) of the ## @code{paretotails} object @var{pt} evaluated at the probabilities ## @var{p}. ## ## @end deftypefn function x = icdf (this, p) if (nargin != 2) print_usage (); endif x = nan (size (p)); valid = p >= 0 & p <= 1; lo = valid & p < this.pl; hi = valid & p > this.pu; mid = valid & ! lo & ! hi; if (any (lo(:))) x(lo) = this.ql - gpinv (1 - p(lo) ./ this.pl, ... this.lowerP(1), this.lowerP(2), 0); endif if (any (hi(:))) x(hi) = this.qu + gpinv ((p(hi) - this.pu) ./ (1 - this.pu), ... this.upperP(1), this.upperP(2), 0); endif if (any (mid(:))) x(mid) = interp1 (this.pknot, this.xknot, p(mid), "linear"); endif endfunction ## -*- texinfo -*- ## @deftypefn {paretotails} {@var{r} =} random (@var{pt}) ## @deftypefnx {paretotails} {@var{r} =} random (@var{pt}, @var{sz}) ## @deftypefnx {paretotails} {@var{r} =} random (@var{pt}, @var{m}, @var{n}, @dots{}) ## ## Random values drawn from the @code{paretotails} object @var{pt}, by ## inverse transform sampling. The size arguments follow @code{rand}. ## ## @end deftypefn function r = random (this, varargin) ## Negative dimensions are treated as zero, as in core Octave and MATLAB szargs = cellfun (@(x) max (x, 0), varargin, 'UniformOutput', false); r = this.icdf (rand (szargs{:})); endfunction ## -*- texinfo -*- ## @deftypefn {paretotails} {[@var{p}, @var{q}] =} boundary (@var{pt}) ## ## Boundary probabilities @var{p} and quantiles @var{q} of the segments of ## the @code{paretotails} object @var{pt}, as column vectors. ## ## @end deftypefn function [p, q] = boundary (this) p = []; q = []; if (this.pl > 0) p = [p; this.pl]; q = [q; this.ql]; endif if (this.pu < 1) p = [p; this.pu]; q = [q; this.qu]; endif endfunction ## -*- texinfo -*- ## @deftypefn {paretotails} {@var{n} =} nsegments (@var{pt}) ## ## Number of segments in the @code{paretotails} object @var{pt}. ## ## @end deftypefn function n = nsegments (this) n = this.NumSegments; endfunction ## -*- texinfo -*- ## @deftypefn {paretotails} {@var{params} =} lowerparams (@var{pt}) ## ## Shape and scale parameters @code{[@var{k}, @var{sigma}]} of the ## generalized Pareto distribution fit to the lower tail of @var{pt}. ## ## @end deftypefn function params = lowerparams (this) if (isempty (this.lowerP)) error ("paretotails: the distribution has no lower tail."); endif params = this.lowerP(:)'; endfunction ## -*- texinfo -*- ## @deftypefn {paretotails} {@var{params} =} upperparams (@var{pt}) ## ## Shape and scale parameters @code{[@var{k}, @var{sigma}]} of the ## generalized Pareto distribution fit to the upper tail of @var{pt}. ## ## @end deftypefn function params = upperparams (this) if (isempty (this.upperP)) error ("paretotails: the distribution has no upper tail."); endif params = this.upperP(:)'; endfunction ## -*- texinfo -*- ## @deftypefn {paretotails} {@var{s} =} segment (@var{pt}, @var{x}, @var{p}) ## ## Segment indices for the @code{paretotails} object @var{pt}. Supply the ## data values in @var{x} (with @var{p} empty) or the cumulative ## probabilities in @var{p} (with @var{x} empty). Segment @code{1} is the ## lower tail, @code{2} the middle, and @code{3} the upper tail. ## ## @end deftypefn function s = segment (this, x, p) if (nargin < 3) p = []; endif if (nargin < 2) x = []; endif if (isempty (x) && ! isempty (p)) s = 2 .* ones (size (p)); s(p < this.pl) = 1; s(p > this.pu) = 3; else s = 2 .* ones (size (x)); s(x < this.ql) = 1; s(x > this.qu) = 3; endif endfunction endmethods methods (Static, Access = private) ## Empirical quantile by linear interpolation of the plotting positions ## (i-0.5)/n, clamped to the data range (the default of MATLAB's quantile). function q = quantile_pp (xs, pp, p) if (p <= pp(1)) q = xs(1); elseif (p >= pp(end)) q = xs(end); else q = interp1 (pp, xs, p, "linear"); endif endfunction ## Index of the interpolation bin containing each value of x, clamped to the ## valid range of the knot vector xk. function idx = bin_index (xk, x) idx = zeros (size (x)); for i = 1:numel (x) j = find (xk <= x(i), 1, "last"); if (isempty (j)) j = 1; elseif (j >= numel (xk)) j = numel (xk) - 1; endif idx(i) = j; endfor endfunction endmethods endclassdef %!demo %! ## Fit Pareto tails to a normal sample and compare the tail cdf to the data %! x = norminv (((1:100) - 0.5) / 100); %! pt = paretotails (x, 0.1, 0.9); %! lowerparams (pt) %! [p, q] = boundary (pt) %! cdf (pt, [-2.5, 0, 2.5]) ## Shared probe data and MATLAB reference values (x = norminv of 100 plotting ## positions; pl = 0.1, pu = 0.9). %!shared x, pt %! x = norminv (((1:100) - 0.5) / 100); %! pt = paretotails (x, 0.1, 0.9); %!test %! assert_equal (lowerparams (pt), ... %! [-0.381277950146653, 0.652296030248444], 1e-5); %! assert_equal (upperparams (pt), ... %! [-0.381277950146658, 0.652296030248448], 1e-5); %!test %! [p, q] = boundary (pt); %! assert_equal (p, [0.1; 0.9], 1e-12); %! assert_equal (q, [-1.28207227531929; 1.28207227531929], 1e-10); %!test %! assert_equal (nsegments (pt), 3); %! assert_equal (pt.NumSegments, 3); %!test %! xq = [-2.8, -2.3, -1.8, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 1.8, 2.3, 2.8]; %! ref = [0.000326503983434112, 0.00934245750239852, 0.0388388580428329, ... %! 0.0699512617277107, 0.158702922884361, 0.308553672872447, 0.5, ... %! 0.691446327127553, 0.84129707711564, 0.930048738272289, ... %! 0.961161141957167, 0.990657542497602, 0.999673496016566]; %! assert_equal (cdf (pt, xq), ref, 1e-5); %!test %! xq = [-2.8, -2.3, -1.8, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 1.8, 2.3, 2.8]; %! ref = [0.00443959395367343, 0.0353636322020254, 0.0853936076680005, ... %! 0.122892916352678, 0.243260728154123, 0.352775902406303, ... %! 0.398931835816165, 0.352775902406307, 0.243260728154123, ... %! 0.122892916352677, 0.0853936076680006, 0.0353636322020256, ... %! 0.00443959395367328]; %! assert_equal (pdf (pt, xq), ref, 1e-5); %!test %! pq = [0.005, 0.01, 0.02, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.98, ... %! 0.99, 0.995]; %! ref = [-2.44694213045116, -2.28179640154802, -2.06669489811039, ... %! -1.67939673030492, -1.28207227531929, -0.674573258334612, 0, ... %! 0.67457325833461, 1.28207227531929, 1.67939673030492, ... %! 2.06669489811039, 2.28179640154802, 2.44694213045116]; %! assert_equal (icdf (pt, pq), ref, 1e-5); %!test %! xq = [-2.8, -2.3, -1.8, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 1.8, 2.3, 2.8]; %! ref = [1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3]; %! assert_equal (segment (pt, xq, []), ref); ## cdf and icdf are inverses in the middle and tails %!test %! p = [0.02, 0.2, 0.5, 0.8, 0.98]; %! assert_equal (cdf (pt, icdf (pt, p)), p, 1e-6); ## random returns values of the requested size within the support %!test %! r = random (pt, 3, 4); %! assert_equal (size (r), [3, 4]); %! assert_equal (size (random (pt, -1)), [0, 0]); %! assert_equal (size (random (pt, 2, -1, 5)), [2, 0, 5]); ## Boundary probabilities outside [0,1] give NaN from icdf %!test %! assert_equal (icdf (pt, [-0.1, 1.1]), [NaN, NaN]); ## Test input validation %!error paretotails (1) %!error paretotails ("a", 0.1, 0.9) %!error ... %! paretotails (1, 0.1, 0.9) %!error ... %! paretotails (1:10, -0.1, 0.9) %!error ... %! paretotails (1:10, 0.1, 1.5) %!error paretotails (1:10, 0.9, 0.1) %!error ... %! paretotails (1:10, 0.1, 0.9, "kernel") %!error ... %! paretotails (1:10, 0.1, 0.9, "foo") statistics-release-1.9.2/inst/Distribution_Fitting/000077500000000000000000000000001524624707500224675ustar00rootroot00000000000000statistics-release-1.9.2/inst/Distribution_Fitting/betafit.m000066400000000000000000000253241524624707500242710ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} betafit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} betafit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} betafit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} betafit (@var{x}, @var{alpha}, @var{freq}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} betafit (@var{x}, @var{alpha}, @var{options}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} betafit (@var{x}, @var{alpha}, @var{freq}, @var{options}) ## ## Estimate parameters and confidence intervals for the Beta distribution. ## ## @code{@var{paramhat} = betafit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the Beta distribution given the data in vector ## @var{x}. @qcode{@var{paramhat}([1, 2])} corresponds to the @math{α} and ## @math{β} shape parameters, respectively. Missing values, @qcode{NaNs}, are ## ignored. ## ## @code{[@var{paramhat}, @var{paramci}] = betafit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = betafit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals of the estimated ## parameter. By default, the optional argument @var{alpha} is 0.05 ## corresponding to 95% confidence intervals. ## ## @code{[@dots{}] = betafit (@var{params}, @var{x}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## must contain non-negative integer frequencies for the corresponding elements ## in @var{x}. By default, or if left empty, ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@var{paramhat}, @var{paramci}] = betafit (@var{x}, @var{alpha}, ## @var{options})} specifies control parameters for the iterative algorithm used ## to compute ML estimates with the @code{fminsearch} function. @var{options} ## is a structure with the following fields and their default values: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## The Beta distribution is defined on the open interval @math{(0,1)}. However, ## @code{betafit} can also compute the unbounded beta likelihood function for ## data that include exact zeros or ones. In such cases, zeros and ones are ## treated as if they were values that have been left-censored at ## @qcode{sqrt (realmin)} or right-censored at @qcode{1 - eps/2}, respectively. ## ## Further information about the Beta distribution can be found at ## @url{https://en.wikipedia.org/wiki/Beta_distribution} ## ## @seealso{betacdf, betainv, betapdf, betarnd, betalike, betastat} ## @end deftypefn function [paramhat, paramci] = betafit (x, alpha, varargin) ## Check X for being a vector if (isempty (x)) phat = nan (1, 2, class (x)); pci = nan (2, 2, class (x)); return elseif (! isvector (x) || ! isreal (x)) error ("betafit: X must be a vector of real values."); endif ## Check that X contains values in the range [0,1] if (any (x < 0) || any (x > 1)) error ("betafit: X must be in the range [0,1]."); endif ## Check X being a constant vector if (min (x) == max (x)) error ("betafit: X must contain distinct values."); endif ## Check ALPHA if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("betafit: wrong value for ALPHA."); endif endif ## Add defaults freq = ones (size (x)); options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; ## Check extra arguments for FREQ vector and/or 'options' structure if (nargin > 2) if (numel (varargin) == 1 && isstruct (varargin{1})) options = varargin{1}; elseif (numel (varargin) == 1 && isnumeric (varargin{1})) freq = varargin{1}; elseif (numel (varargin) == 2) freq = varargin{1}; options = varargin{2}; endif if (isempty (freq)) freq = ones (size (x)); endif ## Check for valid freq vector if (! isequal (size (x), size (freq))) error ("betafit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("betafit: FREQ must not contain negative values."); elseif (any (fix (freq) != freq)) error ("betafit: FREQ must contain integer values."); endif ## Check for valid options structure if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("betafit: 'options' argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Remove missing values remove = isnan (x) | isnan (freq); x(remove) = []; freq(remove) = []; ## Expand frequency if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; endif ## Estimate initial parameters numx = length (x); tmp1 = prod ((1 - x) .^ (1 / numx)); tmp2 = prod (x .^ (1 / numx)); tmp3 = (1 - tmp1 - tmp2); ahat = 0.5 * (1 - tmp1) / tmp3; bhat = 0.5 * (1 - tmp2) / tmp3; init = log ([ahat, bhat]); ## Add tolerance for boundary conditions x_lo = sqrt (realmin (class (x))); x_hi = 1 - eps (class (x)) / 2; ## All values are strictly within the interval (0,1) if (all (x > x_lo) && all (x < x_hi)) sumlogx = sum (log (x)); sumlog1px = sum (log1p (-x)); paramhat = fminsearch (@cont_negloglike, init, options); paramhat = exp (paramhat); ## Find boundary elements and process them separately else num0 = sum (x < x_lo); num1 = sum (x > x_hi); x_ct = x(x > x_lo & x < x_hi); numx = length (x_ct); sumlogx = sum (log (x_ct)); sumlog1px = sum (log1p (-x_ct)); paramhat = fminsearch (@mixed_negloglike, init, options); paramhat = exp (paramhat); endif ## Compute confidence intervals if (nargout == 2) [~, acov] = betalike (paramhat,x); logphat = log (paramhat); serrlog = sqrt (diag (acov))' ./ paramhat; p_int = [alpha/2; 1-alpha/2]; paramci = exp (norminv ([p_int p_int], ... [logphat; logphat], [serrlog; serrlog])); endif ## Continuous Negative log-likelihood function function nll = cont_negloglike (params) params = exp (params); nll = numx * betaln (params(1), params(2)) - (params(1) - 1) ... * sumlogx - (params(2) - 1) * sumlog1px; endfunction ## Unbounded Negative log-likelihood function function nll = mixed_negloglike (params) params = exp (params); nll = numx * betaln (params(1), params(2)) - (params(1) - 1) ... * sumlogx - (params(2) - 1) * sumlog1px; ## Handle zeros if (num0 > 0) nll = nll - num0 * log (betainc (x_lo, params(1), params(2), 'lower')); endif ## Handle ones if (num1 > 0) nll = nll - num1 * log (betainc (x_hi, params(1), params(2), 'upper')); endif endfunction endfunction %!demo %! ## Sample 2 populations from different Beta distributions %! randg ('state', 42); %! r1 = betarnd (2, 5, 500, 1); %! r2 = betarnd (2, 2, 500, 1); %! r = [r1, r2]; %! %! ## Plot them normalized and fix their colors %! hist (r, 12, 15); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! hold on %! %! ## Estimate their shape parameters %! a_b_A = betafit (r(:,1)); %! a_b_B = betafit (r(:,2)); %! %! ## Plot their estimated PDFs %! x = [min(r(:)):0.01:max(r(:))]; %! y = betapdf (x, a_b_A(1), a_b_A(2)); %! plot (x, y, '-pr'); %! y = betapdf (x, a_b_B(1), a_b_B(2)); %! plot (x, y, '-sg'); %! ylim ([0, 4]) %! legend ({'Normalized HIST of sample 1 with α=2 and β=5', ... %! 'Normalized HIST of sample 2 with α=2 and β=2', ... %! sprintf("PDF for sample 1 with estimated α=%0.2f and β=%0.2f", ... %! a_b_A(1), a_b_A(2)), ... %! sprintf("PDF for sample 2 with estimated α=%0.2f and β=%0.2f", ... %! a_b_B(1), a_b_B(2))}) %! title ('Two population samples from different Beta distributions') %! hold off ## Test output %!test %! x = 0.01:0.02:0.99; %! [paramhat, paramci] = betafit (x); %! paramhat_out = [1.0199, 1.0199]; %! paramci_out = [0.6947, 0.6947; 1.4974, 1.4974]; %! assert_equal (paramhat, paramhat_out, 1e-4); %! assert_equal (paramci, paramci_out, 1e-4); %!test %! x = 0.01:0.02:0.99; %! [paramhat, paramci] = betafit (x, 0.01); %! paramci_out = [0.6157, 0.6157; 1.6895, 1.6895]; %! assert_equal (paramci, paramci_out, 1e-4); %!test %! x = 0.00:0.02:1; %! [paramhat, paramci] = betafit (x); %! paramhat_out = [0.0875, 0.1913]; %! paramci_out = [0.0822, 0.1490; 0.0931, 0.2455]; %! assert_equal (paramhat, paramhat_out, 1e-4); %! assert_equal (paramci, paramci_out, 1e-4); ## Test input validation %!error betafit ([0.2, 0.5+i]); %!error betafit (ones (2,2) * 0.5); %!error betafit ([0.5, 1.2]); %!error betafit ([0.1, 0.1]); %!error betafit ([0.01:0.1:0.99], 1.2); %!error ... %! betafit ([0.01:0.01:0.05], 0.05, [1, 2, 3, 2]); %!error ... %! betafit ([0.01:0.01:0.05], 0.05, [1, 2, 3, 2, -1]); %!error ... %! betafit ([0.01:0.01:0.05], 0.05, [1, 2, 3, 2, 1.5]); %!error ... %! betafit ([0.01:0.01:0.05], 0.05, struct ('option', 234)); %!error ... %! betafit ([0.01:0.01:0.05], 0.05, ones (1,5), struct ('option', 234)); statistics-release-1.9.2/inst/Distribution_Fitting/betalike.m000066400000000000000000000160161524624707500244310ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} betalike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{avar}] =} betalike (@var{params}, @var{x}) ## ## Negative log-likelihood for the Beta distribution. ## ## @code{@var{nlogL} = betalike (@var{params}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the Beta distribution ## with (1) shape parameter @math{α} and (2) shape parameter @math{β} given in ## the two-element vector @var{params}. Both parameters must be positive real ## numbers and the data in the range @math{[0,1]}. Out of range parameters or ## data return @qcode{NaN}. ## ## @code{[@var{nlogL}, @var{avar}] = betalike (@var{params}, @var{x})} returns ## the inverse of Fisher's information matrix, @var{avar}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{params} are their asymptotic variances. ## ## @code{[@dots{}] = betalike (@var{params}, @var{x}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## must contain non-negative integer frequencies for the corresponding elements ## in @var{x}. By default, or if left empty, ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## The Beta distribution is defined on the open interval @math{(0,1)}. However, ## @code{betafit} can also compute the unbounded beta likelihood function for ## data that include exact zeros or ones. In such cases, zeros and ones are ## treated as if they were values that have been left-censored at ## @qcode{sqrt (realmin)} or right-censored at @qcode{1 - eps/2}, respectively. ## ## Further information about the Beta distribution can be found at ## @url{https://en.wikipedia.org/wiki/Beta_distribution} ## ## @seealso{betacdf, betainv, betapdf, betarnd, betafit, betastat} ## @end deftypefn function [nlogL, avar] = betalike (params, x, freq) ## Check input arguments and add defaults if (nargin < 2) error ("betalike: function called with too few input arguments."); endif if (numel (params) != 2) error ("betalike: wrong parameters length."); endif if (nargin < 3 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("betalike: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("betalike: FREQ must not contain negative values."); elseif (any (fix (freq) != freq)) error ("betalike: FREQ must contain integer values."); endif ## Expand frequency if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; endif ## Get α and β parameters a = params(1); b = params(2); ## Force X to column vector x = x(:); ## Return NaN for out of range parameters or data. a(a <= 0) = NaN; b(b <= 0) = NaN; xmin = min (x); xmax = max (x); x(! (0 <= x & x <= 1)) = NaN; ## Add tolerance for boundary conditions x_lo = sqrt (realmin (class (x))); x_hi = 1 - eps (class (x)) / 2; ## All values are strictly within the interval (0,1) if (all (x > x_lo) && all (x < x_hi)) num0 = 0; num1 = 0; x_ct = x; numx = length (x_ct); ## Find boundary elements and process them separately else num0 = sum (x < x_lo); num1 = sum (x > x_hi); x_ct = x(x > x_lo & x < x_hi); numx = length (x_ct); endif ## Compute continuous log likelihood logx = log (x_ct); log1px = log1p (-x_ct); sumlogx = sum (logx); sumlog1px = sum (log1px); nlogL = numx * betaln (a, b) - (a - 1) * sumlogx - (b - 1) * sumlog1px; ## Include log likelihood for zeros if (num0 > 0) nlogL = nlogL - num0 * log (betainc (x_lo, a, b, 'lower')); endif ## Include log likelihood for ones if (num1 > 0) nlogL = nlogL - num1 * log (betainc (x_hi, a, b, 'upper')); endif ## Compute the asymptotic covariance if (nargout > 1) if (numel (x) < 2) error ("betalike: not enough data in X."); endif ## Compute the Jacobian of the likelihood for values (0,1) psiab = psi (a + b); psi_a = psi (a); psi_b = psi (b); J = [logx+psiab-psi_a, log1px+psiab-psi_b]; ## Add terms into the Jacobian for the zero and one values. if (num0 > 0 || num1 > 0) dd = sqrt (eps (class (x))); aa = a + a * dd * [1, -1]; bb = b + b * dd * [1, -1]; ad = 2 * a *dd; bd = 2 * b *dd; if (num0 > 0) da = diff (log (betainc (x_lo, aa, b, 'lower'))) / ad; db = diff (log (betainc (x_lo, a, bb, 'lower'))) / bd; J = [J; repmat([da, db], num0, 1)]; endif if num1 > 0 da = diff (log (betainc (x_hi, aa, b, 'upper'))) / ad; db = diff (log (betainc (x_hi, a, bb, 'upper'))) / bd; J = [J; repmat([da, db], num1, 1)]; endif endif ## Invert the inner product of the Jacobian to get the asymptotic covariance [~, R] = qr (J, 0); if (any (isnan (R(:)))) avar = [NaN, NaN; NaN, NaN]; else Rinv = R \ eye (2); avar = Rinv * Rinv'; endif endif endfunction ## Test output %!test %! x = 0.01:0.02:0.99; %! [nlogL, avar] = betalike ([2.3, 1.2], x); %! avar_out = [0.03691678, 0.02803056; 0.02803056, 0.03965629]; %! assert_equal (nlogL, 17.873477715879040, 3e-14); %! assert_equal (avar, avar_out, 1e-7); %!test %! x = 0.01:0.02:0.99; %! [nlogL, avar] = betalike ([1, 4], x); %! avar_out = [0.02793282, 0.02717274; 0.02717274, 0.03993361]; %! assert_equal (nlogL, 79.648061114839550, 1e-13); %! assert_equal (avar, avar_out, 1e-7); %!test %! x = 0.00:0.02:1; %! [nlogL, avar] = betalike ([1, 4], x); %! avar_out = [0.00000801564765, 0.00000131397245; ... %! 0.00000131397245, 0.00070827639442]; %! assert_equal (nlogL, 573.2008434477486, 1e-10); %! assert_equal (avar, avar_out, 1e-14); ## Test input validation %!error ... %! betalike ([12, 15]); %!error betalike ([12, 15, 3], [1:50]); %!error ... %! betalike ([12, 15], ones (10, 1), ones (8,1)) %!error ... %! betalike ([12, 15], ones (1, 8), [1 1 1 1 1 1 1 -1]) %!error ... %! betalike ([12, 15], ones (1, 8), [1 1 1 1 1 1 1 1.5]) statistics-release-1.9.2/inst/Distribution_Fitting/binofit.m000066400000000000000000000142771524624707500243120ustar00rootroot00000000000000## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{pshat} =} binofit (@var{x}, @var{n}) ## @deftypefnx {statistics} {[@var{pshat}, @var{psci}] =} binofit (@var{x}, @var{n}) ## @deftypefnx {statistics} {[@var{pshat}, @var{psci}] =} binofit (@var{x}, @var{n}, @var{alpha}) ## ## Estimate parameter and confidence intervals for the binomial distribution. ## ## @code{@var{pshat} = binofit (@var{x}, @var{n})} returns the maximum ## likelihood estimate (MLE) of the probability of success for the binomial ## distribution. @var{x} and @var{n} are scalars containing the number of ## successes and the number of trials, respectively. If @var{x} and @var{n} are ## vectors, @code{binofit} returns a vector of estimates whose @math{i}-th ## element is the parameter estimate for @var{x}(i) and @var{n}(i). A scalar ## value for @var{x} or @var{n} is expanded to the same size as the other input. ## ## @code{[@var{pshat}, @var{psci}] = binofit (@var{x}, @var{n}, @var{alpha})} ## also returns the @qcode{100 * (1 - @var{alpha})} percent confidence intervals ## of the estimated parameter. By default, the optional argument @var{alpha} ## is 0.05 corresponding to 95% confidence intervals. ## ## @code{binofit} treats a vector @var{x} as a collection of measurements from ## separate samples, and returns a vector of estimates. If you want to treat ## @var{x} as a single sample and compute a single parameter estimate and ## confidence interval, use @qcode{binofit (sum (@var{x}), sum (@var{n}))} when ## @var{n} is a vector, and ## @qcode{binofit (sum (@var{x}), @var{n} * length (@var{x}))} when @var{n} is a ## scalar. ## ## Further information about the binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Binomial_distribution} ## ## @seealso{binocdf, binoinv, binopdf, binornd, binolike, binostat} ## @end deftypefn function [pshat, psci] = binofit (x, n, alpha) ## Check input arguments if (nargin < 2) error ("binofit: function called with too few input arguments."); endif if (any (x < 0)) error ("binofit: X cannot have negative values."); endif if (! isvector (x)) error ("binofit: X must be a vector."); endif if (any (n < 0) || any (n != round (n)) || any (isinf (n))) error ("binofit: N must be a non-negative integer."); endif if (! (isscalar (n) || isequal (size (n), size (x)))) error ("binofit: N must be a scalar or the same size as X."); endif if (any (x > n)) error ("binofit: N must be at least as large as X."); endif if (nargin < 3 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("binofit: wrong value for ALPHA."); endif endif ## Compute pshat pshat = x ./ n; ## Compute lower confidence interval nu1 = 2 * x; nu2 = 2 * (n - x + 1); F = finv (alpha / 2, nu1, nu2); lb = (nu1 .* F) ./ (nu2 + nu1 .* F); x0 = find (x == 0); if (! isempty (x0)) lb(x0) = 0; endif ## Compute upper confidence interval nu1 = 2 * (x + 1); nu2 = 2 * (n - x); F = finv (1 - alpha / 2, nu1, nu2); ub = (nu1 .* F) ./ (nu2 + nu1 .* F); xn = find (x == n); if (! isempty (xn)) ub(xn) = 1; endif psci = [lb(:), ub(:)]; endfunction %!demo %! ## Sample 2 populations from different binomial distributions %! rng (42); %! r1 = binornd (50, 0.15, 1000, 1); %! r2 = binornd (100, 0.5, 1000, 1); %! r = [r1, r2]; %! %! ## Plot them normalized and fix their colors %! hist (r, 23, 0.35); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! hold on %! %! ## Estimate their probability of success %! pshatA = binofit (r(:,1), 50); %! pshatB = binofit (r(:,2), 100); %! %! ## Plot their estimated PDFs %! x = [min(r(:,1)):max(r(:,1))]; %! y = binopdf (x, 50, mean (pshatA)); %! plot (x, y, '-pg'); %! x = [min(r(:,2)):max(r(:,2))]; %! y = binopdf (x, 100, mean (pshatB)); %! plot (x, y, '-sc'); %! ylim ([0, 0.2]) %! legend ({'Normalized HIST of sample 1 with ps=0.15', ... %! 'Normalized HIST of sample 2 with ps=0.50', ... %! sprintf("PDF for sample 1 with estimated ps=%0.2f", ... %! mean (pshatA)), ... %! sprintf("PDF for sample 2 with estimated ps=%0.2f", ... %! mean (pshatB))}) %! title ('Two population samples from different binomial distributions') %! hold off ## Test output %!test %! x = 0:3; %! [pshat, psci] = binofit (x, 3); %! assert_equal (pshat, [0, 0.3333, 0.6667, 1], 1e-4); %! assert_equal (psci(1,:), [0, 0.7076], 1e-4); %! assert_equal (psci(2,:), [0.0084, 0.9057], 1e-4); %! assert_equal (psci(3,:), [0.0943, 0.9916], 1e-4); %! assert_equal (psci(4,:), [0.2924, 1.0000], 1e-4); ## Test input validation %!error ... %! binofit ([1 2 3 4]) %!error ... %! binofit ([-1, 4, 3, 2], [1, 2, 3, 3]) %!error binofit (ones (2), [1, 2, 3, 3]) %!error ... %! binofit ([1, 4, 3, 2], [1, 2, -1, 3]) %!error ... %! binofit ([1, 4, 3, 2], [5, 5, 5]) %!error ... %! binofit ([1, 4, 3, 2], [5, 3, 5, 5]) %!error binofit ([1, 2, 1], 3, 1.2); %!error binofit ([1, 2, 1], 3, 0); %!error binofit ([1, 2, 1], 3, 'alpha'); statistics-release-1.9.2/inst/Distribution_Fitting/binolike.m000066400000000000000000000132121524624707500244400ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} binolike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} binolike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} binolike (@var{params}, @var{x}, @var{freq}) ## ## Negative log-likelihood for the binomial distribution. ## ## @code{@var{nlogL} = binolike (@var{params}, @var{x})} returns the negative ## log likelihood of the binomial distribution with (1) parameter @var{n} and ## (2) parameter @var{ps}, given in the two-element vector @var{params}, where ## @var{n} is the number of trials and @var{ps} is the probability of success, ## given the number of successes in @var{x}. Unlike @code{binofit}, which ## handles each element in @var{x} independently, @code{binolike} returns the ## negative log likelihood of the entire vector @var{x}. ## ## @code{[@var{nlogL}, @var{acov}] = binolike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{params} are their asymptotic variances. ## ## @code{[@dots{}] = binolike (@var{params}, @var{x}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## typically contains integer frequencies for the corresponding elements in ## @var{x}, but it can contain any non-integer non-negative values. By default, ## or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Binomial_distribution} ## ## @seealso{binocdf, binoinv, binopdf, binornd, binofit, binostat} ## @end deftypefn function [nlogL, acov] = binolike (params, x, freq) ## Check input arguments if (nargin < 2) error ("binolike: function called with too few input arguments."); endif if (! isvector (x)) error ("binolike: X must be a vector."); endif if (length (params) != 2) error ("binolike: PARAMS must be a two-element vector."); endif if (params(1) < 0 || params(1) != round (params(1)) || isinf (params(1))) error (strcat ("binolike: number of trials, PARAMS(1), must be a", ... " finite non-negative integer.")); endif if (params(2) < 0 || params(2) > 1) error (strcat ("binolike: probability of success, PARAMS(2), must be", ... " in the range [0,1].")); endif ## Parse FREQ argument or add default if (nargin < 3 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("binolike: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("binolike: FREQ must not contain negative values."); endif ## Expand frequency vector (if necessary) if (! all (freq == 1)) ## Remove NaNs and zeros remove = isnan (freq) | freq == 0; x(remove) = []; freq(remove) = []; xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; endif if (any (x < 0)) error ("binolike: X cannot have negative values."); endif if (any (x > params(1))) error (strcat ("binolike: number of successes, X, must be at least", ... " as large as the number of trials, N.")); endif ## Compute negative log-likelihood and asymptotic covariance n = params(1); ps = params(2); numx = length (x); nlogL = -sum (log (binopdf (x, n, ps))); tmp = ps * (1 - ps) / (n * numx); acov = [0, 0; 0, tmp]; endfunction ## Test output %!assert_equal (binolike ([3, 0.333], [0:3]), 6.8302, 1e-4) %!assert_equal (binolike ([3, 0.333], 0), 1.2149, 1e-4) %!assert_equal (binolike ([3, 0.333], 1), 0.8109, 1e-4) %!assert_equal (binolike ([3, 0.333], 2), 1.5056, 1e-4) %!assert_equal (binolike ([3, 0.333], 3), 3.2988, 1e-4) %!test %! [nlogL, acov] = binolike ([3, 0.333], 3); %! assert_equal (acov(4), 0.0740, 1e-4) ## Test input validation %!error binolike (3.25) %!error binolike ([5, 0.2], ones (2)) %!error ... %! binolike ([1, 0.2, 3], [1, 3, 5, 7]) %!error binolike ([1.5, 0.2], 1) %!error binolike ([-1, 0.2], 1) %!error binolike ([Inf, 0.2], 1) %!error binolike ([5, 1.2], [3, 5]) %!error binolike ([5, -0.2], [3, 5]) %!error ... %! binolike ([5, 0.5], ones (10, 1), ones (8,1)) %!error ... %! binolike ([5, 0.5], ones (1, 8), [1 1 1 1 1 1 1 -1]) %!error binolike ([5, 0.2], [-1, 3]) %!error binolike ([5, 0.2], [3, 5, 7]) statistics-release-1.9.2/inst/Distribution_Fitting/bisafit.m000066400000000000000000000222731524624707500242740ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} bisafit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} bisafit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} bisafit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} bisafit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} bisafit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} bisafit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate mean and confidence intervals for the Birnbaum-Saunders ## distribution. ## ## @code{@var{muhat} = bisafit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the Birnbaum-Saunders distribution given the ## data in @var{x}. @qcode{@var{paramhat}(1)} is the scale parameter, ## @var{beta}, and @qcode{@var{paramhat}(2)} is the shape parameter, ## @var{gamma}. ## ## @code{[@var{paramhat}, @var{paramci}] = bisafit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = bisafit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = bisafit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = bisafit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@dots{}] = bisafit (@dots{}, @var{options})} specifies control ## parameters for the iterative algorithm used to compute ML estimates with the ## @code{fminsearch} function. @var{options} is a structure with the following ## fields and their default values: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## Further information about the Birnbaum-Saunders distribution can be found at ## @url{https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution} ## ## @seealso{bisacdf, bisainv, bisapdf, bisarnd, bisalike, bisastat} ## @end deftypefn function [paramhat, paramci] = bisafit (x, alpha, censor, freq, options) ## Check input arguments if (! isvector (x)) error ("bisafit: X must be a vector."); elseif (any (x <= 0)) error ("bisafit: X must contain only positive values."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("bisafit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("bisafit: X and CENSOR vectors mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("bisafit: X and FREQ vectors mismatch."); endif ## Get options structure or add defaults if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("bisafit: 'options' 5th argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Starting points as suggested by Birnbaum and Saunders x_uncensored = x(censor==0); xubar = mean (x_uncensored); xuinv = mean (1 ./ x_uncensored); beta = sqrt (xubar ./ xuinv); gamma = 2 .* sqrt (sqrt (xubar .* xuinv) - 1); x0 = [beta, gamma]; ## Minimize negative log-likelihood to estimate parameters f = @(params) bisalike (params, x, censor, freq); [paramhat, ~, err, output] = fminsearch (f, x0, options); ## Force positive parameter values paramhat = abs (paramhat); ## Handle errors if (err == 0) if (output.funcCount >= options.MaxFunEvals) warning ("bisafit: maximum number of function evaluations are exceeded."); elseif (output.iterations >= options.MaxIter) warning ("bisafit: maximum number of iterations are exceeded."); endif elseif (err < 0) error ("bisafit: no solution."); endif ## Compute CIs using a log normal approximation for parameters. if (nargout > 1) ## Compute asymptotic covariance [~, acov] = bisalike (paramhat, x, censor, freq); ## Get standard errors stderr = sqrt (diag (acov))'; stderr = stderr ./ paramhat; ## Apply log transform phatlog = log (paramhat); ## Compute normal quantiles z = norminv (alpha / 2); ## Compute CI paramci = [phatlog; phatlog] + [stderr; stderr] .* [z, z; -z, -z]; ## Inverse log transform paramci = exp (paramci); endif endfunction %!demo %! ## Sample 3 populations from different Birnbaum-Saunders distributions %! rng (42); %! r1 = bisarnd (1, 0.5, 2000, 1); %! r2 = bisarnd (2, 0.3, 2000, 1); %! r3 = bisarnd (4, 0.5, 2000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, 80, 4.2); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! ylim ([0, 1.1]); %! xlim ([0, 8]); %! hold on %! %! ## Estimate their α and β parameters %! beta_gammaA = bisafit (r(:,1)); %! beta_gammaB = bisafit (r(:,2)); %! beta_gammaC = bisafit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [0:0.1:8]; %! y = bisapdf (x, beta_gammaA(1), beta_gammaA(2)); %! plot (x, y, '-pr'); %! y = bisapdf (x, beta_gammaB(1), beta_gammaB(2)); %! plot (x, y, '-sg'); %! y = bisapdf (x, beta_gammaC(1), beta_gammaC(2)); %! plot (x, y, '-^c'); %! hold off %! legend ({'Normalized HIST of sample 1 with β=1 and γ=0.5', ... %! 'Normalized HIST of sample 2 with β=2 and γ=0.3', ... %! 'Normalized HIST of sample 3 with β=4 and γ=0.5', ... %! sprintf("PDF for sample 1 with estimated β=%0.2f and γ=%0.2f", ... %! beta_gammaA(1), beta_gammaA(2)), ... %! sprintf("PDF for sample 2 with estimated β=%0.2f and γ=%0.2f", ... %! beta_gammaB(1), beta_gammaB(2)), ... %! sprintf("PDF for sample 3 with estimated β=%0.2f and γ=%0.2f", ... %! beta_gammaC(1), beta_gammaC(2))}) %! title ('Three population samples from different Birnbaum-Saunders distributions') %! hold off ## Test output %!test %! paramhat = bisafit ([1:50]); %! paramhat_out = [16.2649, 1.0156]; %! assert_equal (paramhat, paramhat_out, 1e-4); %!test %! paramhat = bisafit ([1:5]); %! paramhat_out = [2.5585, 0.5839]; %! assert_equal (paramhat, paramhat_out, 1e-4); ## Test input validation %!error bisafit (ones (2,5)); %!error bisafit ([-1 2 3 4]); %!error bisafit ([1, 2, 3, 4, 5], 1.2); %!error bisafit ([1, 2, 3, 4, 5], 0); %!error bisafit ([1, 2, 3, 4, 5], 'alpha'); %!error ... %! bisafit ([1, 2, 3, 4, 5], 0.05, [1 1 0]); %!error ... %! bisafit ([1, 2, 3, 4, 5], [], [1 1 0 1 1]'); %!error ... %! bisafit ([1, 2, 3, 4, 5], 0.05, zeros (1,5), [1 1 0]); %!error ... %! bisafit ([1, 2, 3, 4, 5], [], [], [1 1 0 1 1]'); %!error ... %! bisafit ([1, 2, 3, 4, 5], 0.05, [], [], 2); statistics-release-1.9.2/inst/Distribution_Fitting/bisalike.m000066400000000000000000000154041524624707500244340ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} bisalike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} bisalike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} bisalike (@var{params}, @var{x}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} bisalike (@var{params}, @var{x}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the Birnbaum-Saunders distribution. ## ## @code{@var{nlogL} = bisalike (@var{params}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the Birnbaum-Saunders ## distribution with (1) scale parameter @var{beta} and (2) shape parameter ## @var{gamma} given in the two-element vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = bisalike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{params} are their asymptotic variances. ## ## @code{[@dots{}] = bisalike (@var{params}, @var{x}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = bisalike (@var{params}, @var{x}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the Birnbaum-Saunders distribution can be found at ## @url{https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution} ## ## @seealso{bisacdf, bisainv, bisapdf, bisarnd, bisafit, bisastat} ## @end deftypefn function [nlogL, acov] = bisalike (params, x, censor, freq) ## Check input arguments if (nargin < 2) error ("bisalike: function called with too few input arguments."); endif if (! isvector (x)) error ("bisalike: X must be a vector."); endif if (any (x < 0)) error ("bisalike: X cannot have negative values."); endif if (length (params) != 2) error ("bisalike: PARAMS must be a two-element vector."); endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("bisalike: X and CENSOR vector mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("bisalike: X and FREQ vector mismatch."); endif beta = params(1); gamma = params(2); z = (sqrt (x ./ beta) - sqrt (beta ./ x)) ./ gamma; w = (sqrt (x ./ beta) + sqrt (beta ./ x)) ./ gamma; L = -0.5 .* (z .^ 2 + log (2 .* pi)) + log (w) - log (2 .* x); n_censored = sum (freq .* censor); if (n_censored > 0) censored = (censor == 1); z_censored = z(censored); Scen = 0.5 * erfc (z_censored ./ sqrt (2)); L(censored) = log (Scen); endif nlogL = -sum (freq .* L); ## Compute asymptotic covariance if (nargout > 1) ## Compute first order central differences of the log-likelihood gradient dp = 0.0001 .* max (abs (params), 1); ngrad_p1 = bisa_ngrad (params + [dp(1), 0], x, censor, freq); ngrad_m1 = bisa_ngrad (params - [dp(1), 0], x, censor, freq); ngrad_p2 = bisa_ngrad (params + [0, dp(2)], x, censor, freq); ngrad_m2 = bisa_ngrad (params - [0, dp(2)], x, censor, freq); ## Compute negative Hessian by normalizing the differences by the increment nH = [(ngrad_p1(:) - ngrad_m1(:))./(2 * dp(1)), ... (ngrad_p2(:) - ngrad_m2(:))./(2 * dp(2))]; ## Force neg Hessian being symmetric nH = 0.5 .* (nH + nH'); ## Check neg Hessian is positive definite [R, p] = chol (nH); if (p > 0) warning ("bisalike: non positive definite Hessian matrix."); acov = NaN (2); return endif ## ACOV estimate is the negative inverse of the Hessian. Rinv = inv (R); acov = Rinv * Rinv; endif endfunction ## Helper function for computing negative gradient function ngrad = bisa_ngrad (params, x, censor, freq) beta = params(1); gamma = params(2); z = (sqrt (x ./ beta) - sqrt (beta ./ x)) ./ gamma; w = (sqrt (x ./ beta) + sqrt (beta ./ x)) ./ gamma; logphi = -0.5 .* (z .^ 2 + log (2 .* pi)); n_censored = sum (freq .* censor); if (n_censored > 0) censored = (censor == 1); z_censored = z(censored); Scen = 0.5 * erfc (z_censored ./ sqrt (2)); endif dL1 = (w .^ 2 - 1) .* 0.5 .* z ./ (w .* beta); dL2 = (z .^ 2 - 1) ./ gamma; if (n_censored > 0) phi_censored = exp (logphi(censored)); wcen = w(censored); d1Scen = phi_censored .* 0.5 .* wcen ./ beta; d2Scen = phi_censored .* z_censored ./ gamma; dL1(censored) = d1Scen ./ Scen; dL2(censored) = d2Scen ./ Scen; endif ngrad = -[sum(freq .* dL1), sum(freq .* dL2)]; endfunction ## Test results %!test %! nlogL = bisalike ([16.2649, 1.0156], [1:50]); %! assert_equal (nlogL, 215.5905, 1e-4); %!test %! nlogL = bisalike ([2.5585, 0.5839], [1:5]); %! assert_equal (nlogL, 8.9950, 1e-4); ## Test input validation %!error bisalike (3.25) %!error bisalike ([5, 0.2], ones (2)) %!error bisalike ([5, 0.2], [-1, 3]) %!error ... %! bisalike ([1, 0.2, 3], [1, 3, 5, 7]) %!error ... %! bisalike ([1.5, 0.2], [1:5], [0, 0, 0]) %!error ... %! bisalike ([1.5, 0.2], [1:5], [0, 0, 0, 0, 0], [1, 1, 1]) %!error ... %! bisalike ([1.5, 0.2], [1:5], [], [1, 1, 1]) statistics-release-1.9.2/inst/Distribution_Fitting/burrfit.m000066400000000000000000000352141524624707500243270ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} burrfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} burrfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} burrfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} burrfit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} burrfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} burrfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate mean and confidence intervals for the Burr type XII distribution. ## ## @code{@var{muhat} = burrfit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the Burr type XII distribution given the data ## in @var{x}. @qcode{@var{paramhat}(1)} is the scale parameter, @var{lambda}, ## @qcode{@var{paramhat}(2)} is the first shape parameter, @var{c}, and ## @qcode{@var{paramhat}(3)} is the second shape parameter, @var{k} ## ## @code{[@var{paramhat}, @var{paramci}] = burrfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = burrfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = burrfit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = burrfit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@dots{}] = burrfit (@dots{}, @var{options})} specifies control ## parameters for the iterative algorithm used to compute the maximum likelihood ## estimates. @var{options} is a structure with the following field and its ## default value: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## Further information about the Burr type XII distribution can be found at ## @url{https://en.wikipedia.org/wiki/Burr_distribution} ## ## @seealso{burrcdf, burrinv, burrpdf, burrrnd, burrlike, burrstat} ## @end deftypefn function [paramhat, paramci] = burrfit (x, alpha, censor, freq, options) ## Check input arguments if (! isvector (x)) error ("burrfit: X must be a vector."); elseif (any (x <= 0)) error ("burrfit: X must contain only positive values."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("burrfit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("burrfit: X and CENSOR vectors mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("burrfit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("burrfit: FREQ must not contain negative values."); endif ## Get options structure or add defaults if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("burrfit: 'options' 5th argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Force censoring vector into logical notc = ! censor; cens = ! notc; ## Check for identical data in X if (! isscalar (x) && max (abs (diff (x)) ./ x(2:end)) <= sqrt (eps)) warning ("burrfit: X must not contain identical data."); ## Return some sensical values for estimated parameters lambda = x(1); c = Inf; k = sum (notc .* freq) / sum (freq) / log (2); paramhat = [lambda, c, k]; if (nargout > 1) paramci = [paramhat; paramhat]; endif return endif ## Fit a Pareto distribution [paramhat_prt, nlogL_prt] = prtfit (x, cens, freq); ## Fit a Weibull distribution paramhat_wbl = wblfit (x, alpha, cens, freq); nlogL_wbl = wbllike (paramhat_wbl, x, cens, freq); ## Calculate the discriminator x_lambda = x ./ paramhat_wbl(1); x_lambdk = x_lambda .^ paramhat_wbl(2); discrimi = sum (freq .* (0.5 * x_lambdk .^ 2 - x_lambdk .* notc)); ## Compute Burr distribution if (discrimi > 0) ## Expand data (if necessary) if (any (freq != 1)) ## Preserve class x_expand = zeros (1, sum (freq), class (x)); id0 = 1; for idx = 1:numel (x) x_expand(id0:id0 + freq(idx) - 1) = x_lambda(idx); id0 += freq(idx); endfor else x_expand = x_lambda; endif ## Calculate median and 3rd quartile to estimate LAMBDA and C parameters Q = prctile (x_expand); xl_median = Q(3); xl_upperq = Q(4); ## Avoid median and upper quartile being too close together IRDdist = sqrt (eps (xl_median)) * xl_median; if ((xl_upperq - xl_median) < IRDdist) if (any (x_lambda > xl_upperq)) xl_upperq = min (x_lambda(x_lambda > xl_upperq)); elseif (any (x_lambda < xl_upperq)) xl_median = max (x_lambda(x_lambda < xl_median)); endif endif ## Compute starting LAMBDA and C, either directly or by minimization if (xl_median >= xl_upperq / xl_median) l0 = xl_median; c0 = log (3)/log (xl_upperq/xl_median); else l0 = 1; opts = optimset ('fzero'); opts = optimset (opts, 'Display', 'off'); cmax = log (realmax)/(2*log (xl_upperq/xl_median)); c0 = fzero (@(c)(xl_upperq/xl_median).^c-xl_median.^c-2, [0, cmax], opts); endif ## Calculate starting K from other starting parameters and scaled data k0 = exp (compute_logk (x_lambda, l0, c0, censor, freq)); ## Estimate parameters by minimizing the negative log-likelihood function f = @(params) burrlike (params, x_lambda, censor, freq); [paramhat, ~, err, output] = fminsearch (f, [l0, c0, k0], options); ## Force positive parameter values paramhat = abs (paramhat); ## Handle errors if (err == 0) if (output.funcCount >= options.MaxFunEvals) warning (strcat ("burrfit: maximum number of function", ... " evaluations are exceeded.")); elseif (output.iterations >= options.MaxIter) warning ("burrfit: maximum number of iterations are exceeded."); endif endif ## Scale back LAMBDA parameter paramhat(1) = paramhat(1) * paramhat_wbl(1); ## Compute negative log-likelihood with estimated parameters nlogL_burr = burrlike (paramhat, x, censor, freq); ## Check if fitting a Burr distribution is better than fitting a Pareto ## according to step 5 of the algorithmic implementation in Shao, 2004 if (paramhat(3) > 1e-6 && nlogL_burr < nlogL_prt) ## Compute CIs using a log normal approximation for phat. if (nargout > 1) ## Compute asymptotic covariance [~, acov] = burrlike (paramhat, x, censor, freq); ## Get standard errors stderr = sqrt (diag (acov))'; stderr = stderr ./ paramhat; ## Apply log transform phatlog = log (paramhat); ## Compute normal quantiles z = norminv (alpha / 2); ## Compute CI paramci = [phatlog; phatlog] + ... [stderr; stderr] .* [z, z, z; -z, -z, -z]; ## Inverse log transform paramci = exp (paramci); endif else if (nlogL_prt < nlogL_wbl) error ("burrfit: Pareto distribution fits better in X."); else error ("burrfit: Weibull distribution fits better in X."); endif endif else if (nlogL_prt < nlogL_wbl) error ("burrfit: Pareto distribution fits better in X."); else error ("burrfit: Weibull distribution fits better in X."); endif endif endfunction ## Helper function for fitting a Pareto distribution function [paramhat, nlogL] = prtfit (x, censor, freq) ## Force censoring vector into logical notc = ! censor; cens = ! notc; ## Compute MLE for x_m xm = x(notc); xm = min (xm); ## Handle case with all data censored if (all (cens)) paramhat = [max(x), NaN]; nlogL_prt = 0; return endif ## Compute some values logx = log (x); suml = sum (freq .* (logx - log (xm)) .* (x > xm)); sumf = sum (freq .* notc); ## Compute MLE for alpha a = sumf ./ suml; ## Add MLEs to returning vector paramhat = [xm, a]; ## Compute negative log-likelihood nlogL = a .* suml + sum (freq .* notc .* logx) - log (a) .* sumf; endfunction ## Helper function for computing K from X, LAMBDA, and C function logk = compute_logk (x, lambda, c, censor, freq) ## Force censoring vector into logical notc = ! censor; cens = ! notc; ## Precalculate some values xl = x ./ lambda; l1_xlc = log1p (xl .^ c); ## Avoid realmax overflow by approximation is_inf = isinf (l1_xlc); l1_xlc(is_inf) = c .* log (xl(is_inf)); if (sum (freq .* l1_xlc) < eps) lsxc = log (sum (freq .* (x .^ c))); if (isinf (lsxc)) [maxx, idx] = max (x); lsxc = c * log (freq(idx) * maxx); endif logk = log (sum (freq .* notc)) + c * log (lambda) - lsxc; else logk = log (sum (freq .* notc)) - log (sum (freq .* l1_xlc)); endif endfunction %!demo %! ## Sample 3 populations from different Burr type XII distributions %! rng (42); %! r1 = burrrnd (3.5, 2, 2.5, 10000, 1); %! r2 = burrrnd (1, 3, 1, 10000, 1); %! r3 = burrrnd (0.5, 2, 3, 10000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, [0.1:0.2:20], [18, 5, 3]); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! ylim ([0, 3]); %! xlim ([0, 5]); %! hold on %! %! ## Estimate their α and β parameters %! lambda_c_kA = burrfit (r(:,1)); %! lambda_c_kB = burrfit (r(:,2)); %! lambda_c_kC = burrfit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [0.01:0.15:15]; %! y = burrpdf (x, lambda_c_kA(1), lambda_c_kA(2), lambda_c_kA(3)); %! plot (x, y, '-pr'); %! y = burrpdf (x, lambda_c_kB(1), lambda_c_kB(2), lambda_c_kB(3)); %! plot (x, y, '-sg'); %! y = burrpdf (x, lambda_c_kC(1), lambda_c_kC(2), lambda_c_kC(3)); %! plot (x, y, '-^c'); %! hold off %! legend ({'Normalized HIST of sample 1 with λ=3.5, c=2, and k=2.5', ... %! 'Normalized HIST of sample 2 with λ=1, c=3, and k=1', ... %! 'Normalized HIST of sample 3 with λ=0.5, c=2, and k=3', ... %! sprintf("PDF for sample 1 with estimated λ=%0.2f, c=%0.2f, and k=%0.2f", ... %! lambda_c_kA(1), lambda_c_kA(2), lambda_c_kA(3)), ... %! sprintf("PDF for sample 2 with estimated λ=%0.2f, c=%0.2f, and k=%0.2f", ... %! lambda_c_kB(1), lambda_c_kB(2), lambda_c_kB(3)), ... %! sprintf("PDF for sample 3 with estimated λ=%0.2f, c=%0.2f, and k=%0.2f", ... %! lambda_c_kC(1), lambda_c_kC(2), lambda_c_kC(3))}) %! title ('Three population samples from different Burr type XII distributions') %! hold off ## Test output %!test %! l = 1; c = 2; k = 3; %! r = burrrnd (l, c, k, 100000, 1); %! lambda_c_kA = burrfit (r); %! assert_equal (lambda_c_kA(1), l, 0.2); %! assert_equal (lambda_c_kA(2), c, 0.2); %! assert_equal (lambda_c_kA(3), k, 0.3); %!test %! l = 0.5; c = 1; k = 3; %! r = burrrnd (l, c, k, 100000, 1); %! lambda_c_kA = burrfit (r); %! assert_equal (lambda_c_kA(1), l, 0.2); %! assert_equal (lambda_c_kA(2), c, 0.2); %! assert_equal (lambda_c_kA(3), k, 0.3); %!test %! l = 1; c = 3; k = 1; %! r = burrrnd (l, c, k, 100000, 1); %! lambda_c_kA = burrfit (r); %! assert_equal (lambda_c_kA(1), l, 0.2); %! assert_equal (lambda_c_kA(2), c, 0.2); %! assert_equal (lambda_c_kA(3), k, 0.3); %!test %! l = 3; c = 2; k = 1; %! r = burrrnd (l, c, k, 100000, 1); %! lambda_c_kA = burrfit (r); %! assert_equal (lambda_c_kA(1), l, 0.2); %! assert_equal (lambda_c_kA(2), c, 0.2); %! assert_equal (lambda_c_kA(3), k, 0.3); %!test %! l = 4; c = 2; k = 4; %! r = burrrnd (l, c, k, 100000, 1); %! lambda_c_kA = burrfit (r); %! assert_equal (lambda_c_kA(1), l, 0.2); %! assert_equal (lambda_c_kA(2), c, 0.2); %! assert_equal (lambda_c_kA(3), k, 0.3); ## Test input validation %!error burrfit (ones (2,5)); %!error burrfit ([-1 2 3 4]); %!error burrfit ([1, 2, 3, 4, 5], 1.2); %!error burrfit ([1, 2, 3, 4, 5], 0); %!error burrfit ([1, 2, 3, 4, 5], 'alpha'); %!error ... %! burrfit ([1, 2, 3, 4, 5], 0.05, [1 1 0]); %!error ... %! burrfit ([1, 2, 3, 4, 5], [], [1 1 0 1 1]'); %!error %! burrfit ([1, 2, 3, 4, 5], 0.05, [], [1, 1, 5]) %!error %! burrfit ([1, 2, 3, 4, 5], 0.05, [], [1, 5, 1, 1, -1]) %!error ... %! burrfit ([1:10], 0.05, [], [], 5) statistics-release-1.9.2/inst/Distribution_Fitting/burrlike.m000066400000000000000000000153701524624707500244720ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} burrlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} burrlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} burrlike (@var{params}, @var{x}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} burrlike (@var{params}, @var{x}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the Burr type XII distribution. ## ## @code{@var{nlogL} = burrlike (@var{params}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the Burr type XII ## distribution with (1) scale parameter @var{lambda}, (2) first shape parameter ## @var{c}, and (3) second shape parameter @var{k} given in the three-element ## vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = burrlike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{acov} are their asymptotic variances. ## ## @code{[@dots{}] = burrlike (@var{params}, @var{x}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = burrlike (@var{params}, @var{x}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the Burr type XII distribution can be found at ## @url{https://en.wikipedia.org/wiki/Burr_distribution} ## ## @seealso{burrcdf, burrinv, burrpdf, burrrnd, burrfit, burrstat} ## @end deftypefn function [nlogL, acov] = burrlike (params, x, censor, freq) ## Check input arguments if (nargin < 2) error ("burrlike: function called with too few input arguments."); endif if (! isvector (x)) error ("burrlike: X must be a vector."); endif if (any (x < 0)) error ("burrlike: X cannot have negative values."); endif if (length (params) != 3) error ("burrlike: PARAMS must be a three-element vector."); endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("burrlike: X and CENSOR vector mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("burrlike: X and FREQ vector mismatch."); endif ## Get parameters lambda = params(1); c = params(2); k = params(3); ## Precalculate some values xl = x ./ lambda; log_xl = log (xl); l1_xlc = log1p (xl .^ c); ## Avoid realmax overflow by approximation is_inf = isinf (l1_xlc); l1_xlc(is_inf) = c .* log (xl(is_inf)); ## Force censoring vector into logical notc = ! censor; cens = ! notc; ## Compute neg-loglikelihood likeL = zeros (size (x)); likeL(notc) = (c - 1) .* log_xl(notc) - (k + 1) .* l1_xlc(notc); likeL(cens) = -k .* l1_xlc(cens); nlogL = sum (freq(notc)) * log (lambda / k / c) - sum (freq .* likeL); ## Compute asymptotic covariance if (nargout > 1) ## Preallocate variables nH = zeros (3); d2V1 = zeros (size (x)); d2V2 = d2V1; ## Precalculate some more values xlc = xl .^ c; log_xl = log (xl); xlc1 = (1 + xlc); xlc1sq = xlc1.^2; invxlc1sq = (1 + 1./xlc).^2; ## Find realmax overflow is_inf = isinf (xlc); is_fin = ! is_inf; ## Compute each element of the negative Hessian d2V1(is_fin) = -((1 + c) ./ xlc(is_fin) + 1) ./ invxlc1sq(is_fin); d2V1(is_inf) = -1; d2V2(notc) = d2V1(notc) .* (k + 1) + 1; d2V2(cens) = d2V1(cens) .* k; nH(1,1) = c ./ lambda .^ 2 .* sum (freq .* d2V2); d2V1(is_fin) = xlc(is_fin) .* (c .* log_xl(is_fin) + xlc1(is_fin)) ... ./ xlc1sq(is_fin); d2V1(is_inf) = 1; d2V2(notc) = (k + 1) .* d2V1(notc) - 1; d2V2(cens) = k .* d2V1(cens); nH(1,2) = sum (freq ./ lambda .* d2V2); nH(2,1) = nH(1,2); d2V1(is_fin) = xlc(is_fin) .* log_xl(is_fin) .^ 2 ./ xlc1sq(is_fin); d2V1(is_inf) = 0; d2V2(notc) = d2V1(notc) .* k + d2V1(notc); d2V2(cens) = d2V1(cens) .* k; nH(2,2) = -(sum (freq(notc))) ./ c .^ 2 - sum (freq .* d2V2); d2V1(is_fin) = xlc(is_fin) ./ xlc1(is_fin); d2V1(is_inf) = 1; nH(1,3) = (c ./ lambda) .* sum (freq .* d2V1); nH(3,1) = nH(1,3); nH(2,3) = -sum (freq .* d2V1 .* log_xl); nH(3,2) = nH(2,3); nH(3,3) = -(sum (freq(notc))) ./ k .^ 2; nH = -nH; ## Check negative Hessian is positive definite [R, p] = chol (nH); if (p > 0) warning ("burrlike: non positive definite Hessian matrix."); acov = NaN (3); return endif ## ACOV estimate is the negative inverse of the Hessian. Rinv = inv (R); acov = Rinv * Rinv; endif endfunction ## Test output ## Test input validation %!error burrlike (3.25) %!error burrlike ([1, 2, 3], ones (2)) %!error burrlike ([1, 2, 3], [-1, 3]) %!error ... %! burrlike ([1, 2], [1, 3, 5, 7]) %!error ... %! burrlike ([1, 2, 3, 4], [1, 3, 5, 7]) %!error ... %! burrlike ([1, 2, 3], [1:5], [0, 0, 0]) %!error ... %! burrlike ([1, 2, 3], [1:5], [0, 0, 0, 0, 0], [1, 1, 1]) %!error ... %! burrlike ([1, 2, 3], [1:5], [], [1, 1, 1]) statistics-release-1.9.2/inst/Distribution_Fitting/copulafit.m000066400000000000000000000257141524624707500246440ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{rho} =} copulafit (@qcode{"Gaussian"}, @var{u}) ## @deftypefnx {statistics} {[@var{rho}, @var{nu}] =} copulafit (@qcode{"t"}, @var{u}) ## @deftypefnx {statistics} {[@var{param}, @var{ci}] =} copulafit (@var{family}, @var{u}) ## @deftypefnx {statistics} {[@dots{}] =} copulafit (@dots{}, @qcode{"alpha"}, @var{a}) ## ## Fit a copula to data. ## ## @code{copulafit (@var{family}, @var{u})} returns the maximum-likelihood ## estimate of the parameter of a copula of the family @var{family}, fit to the ## data in @var{u}. The rows of @var{u} are observations and its columns are ## variables; all entries must lie strictly inside the unit interval ## @math{(0,1)}, as produced for example by a probability-integral transform or ## by @code{ecdf}/@code{ksdensity}. ## ## @var{family} is the copula family name. It can be @qcode{"Gaussian"} for the ## Gaussian family, @qcode{"t"} for the Student's t family, @qcode{"Clayton"} ## for the Clayton family, @qcode{"Gumbel"} for the Gumbel-Hougaard family, or ## @qcode{"Frank"} for the Frank family. ## ## The returned value depends on the family: ## ## @itemize @bullet ## @item For @qcode{"Gaussian"}, @code{@var{rho} = copulafit ("Gaussian", ## @var{u})} returns the estimated linear correlation matrix @var{rho}, computed ## as the sample correlation of the normal scores @code{norminv (@var{u})}. The ## data may have two or more columns. ## ## @item For @qcode{"t"}, @code{copulafit ("t", @var{u})} returns the estimated ## correlation matrix @var{rho} and the degrees of freedom @var{nu} as ## @code{[@var{rho}, @var{nu}]}, obtained by maximizing the copula ## log-likelihood. Only bivariate data (two columns) are supported. ## ## @item For the Archimedean families @qcode{"Clayton"}, @qcode{"Gumbel"}, and ## @qcode{"Frank"}, @code{[@var{param}, @var{ci}] = copulafit (@var{family}, ## @var{u})} returns the scalar copula parameter @var{param} and, optionally, a ## two-element vector @var{ci} with the lower and upper confidence bounds. Only ## bivariate data are supported. ## @end itemize ## ## @code{copulafit (@dots{}, @qcode{"alpha"}, @var{a})} sets the significance ## level for the confidence interval to @var{a}, so that @var{ci} has coverage ## @code{100 * (1 - @var{a})} percent. The default is @code{@var{a} = 0.05}. ## The confidence interval is a Wald interval whose standard error is obtained ## from the outer-product-of-gradients estimate of the information. ## ## @seealso{copulastat, copulaparam, copulacdf, copulapdf, copularnd} ## @end deftypefn function varargout = copulafit (family, u, varargin) ## Check arguments if (nargin < 2) print_usage (); endif if (! ischar (family)) error (strcat ("copulafit: FAMILY must be one of 'Gaussian',", ... " 't', 'Clayton', 'Gumbel', and 'Frank'.")); endif if (! isnumeric (u) || ! isreal (u) || ! ismatrix (u) || isempty (u)) error ("copulafit: U must be a nonempty numeric matrix."); endif if (any (u(:) <= 0) || any (u(:) >= 1)) error ("copulafit: U must have all values in the open interval (0, 1)."); endif [n, d] = size (u); if (d < 2) error ("copulafit: U must have at least two columns."); endif ## Parse the 'alpha' option alpha = 0.05; if (numel (varargin) > 0) if (numel (varargin) != 2 || ! ischar (varargin{1}) || ... ! strcmpi (varargin{1}, 'alpha')) error ("copulafit: invalid optional argument."); endif alpha = varargin{2}; if (! (isnumeric (alpha) && isscalar (alpha) && isreal (alpha) ... && alpha > 0 && alpha < 1)) error ("copulafit: ALPHA must be a scalar in the range (0, 1)."); endif endif lower_family = lower (family); switch (lower_family) case 'gaussian' if (nargout > 1) error (strcat ("copulafit: the Gaussian copula fit returns only", ... " the correlation matrix.")); endif varargout{1} = correlation_of_scores (norminv (u)); case 't' if (d != 2) error (strcat ("copulafit: the t copula fit supports bivariate", ... " data only.")); endif if (nargout > 2) error (strcat ("copulafit: confidence intervals are not supported", ... " for the t copula fit.")); endif [rho, nu] = fit_t (u); varargout{1} = [1, rho; rho, 1]; varargout{2} = nu; case {'clayton', 'gumbel', 'frank'} if (d != 2) error (strcat ("copulafit: the %s copula fit supports bivariate", ... " data only."), family); endif [param, ci] = fit_archimedean (lower_family, u, alpha); varargout{1} = param; if (nargout > 1) varargout{2} = ci; endif otherwise error ("copulafit: unknown copula family '%s'.", family); endswitch endfunction ## Sample correlation matrix of the columns of z (mean-removed, unit-normalised) function R = correlation_of_scores (z) R = corr (z); ## Guard the diagonal against round-off so it is exactly one R(1 : (rows (R) + 1) : end) = 1; endfunction ## Maximum-likelihood fit of the bivariate Student's t copula. Optimises the ## correlation and the degrees of freedom jointly on an unconstrained scale ## (rho = tanh (a), nu = exp (b)). function [rho, nu] = fit_t (u) rho0 = corr (norminv (u))(1, 2); p0 = [atanh(rho0); log(4)]; opts = optimset ("TolX", 1e-8, "TolFun", 1e-8, ... "MaxFunEvals", 20000, "MaxIter", 20000); p = fminsearch (@(p) t_copula_nll (u, tanh (p(1)), exp (p(2))), p0, opts); rho = tanh (p(1)); nu = exp (p(2)); endfunction ## Negative log-likelihood of the bivariate t copula function v = t_copula_nll (u, rho, nu) if (abs (rho) >= 1 || nu <= 0) v = Inf; return; endif R = [1, rho; rho, 1]; t = tinv (u, nu); c = mvtpdf (t, R, nu) ./ (tpdf (t(:, 1), nu) .* tpdf (t(:, 2), nu)); if (any (! isfinite (c)) || any (c <= 0)) v = Inf; return; endif v = -sum (log (c)); endfunction ## Maximum-likelihood fit of a bivariate Archimedean copula with a Wald ## confidence interval whose standard error uses the outer-product-of-gradients ## (BHHH) estimate of the information. function [param, ci] = fit_archimedean (family, u, alpha) a0 = archimedean_start (family); opts = optimset ("TolX", 1e-10, "TolFun", 1e-10, ... "MaxFunEvals", 10000, "MaxIter", 10000); param = fminsearch (@(a) -sum (log (copulapdf (family, u, a))), a0, opts); ## Per-observation score by central difference, then OPG information h = max (1e-6, abs (param) .* 1e-5); logc = @(a) log (copulapdf (family, u, a)); score = (logc (param + h) - logc (param - h)) ./ (2 .* h); se = 1 ./ sqrt (sum (score .^ 2)); z = norminv (1 - alpha ./ 2); ci = [param - z .* se, param + z .* se]; endfunction ## A robust starting value for the Archimedean maximum-likelihood search function a0 = archimedean_start (family) switch (family) case 'clayton' a0 = 1; case 'gumbel' a0 = 1.5; case 'frank' a0 = 1; endswitch endfunction %!demo %! ## Fit a Clayton copula to data and recover a confidence interval %! rng (42); %! randg ('state', 42); %! u = copularnd ("Clayton", 2, 500); %! [alpha, ci] = copulafit ("Clayton", u) %!demo %! ## Fit a Gaussian copula and report the correlation matrix %! rng (42); %! randg ('state', 42); %! u = copularnd ("Gaussian", 0.6, 500); %! rho = copulafit ("Gaussian", u) ## Shared probe data (near-Gaussian bivariate sample), reference values from ## MATLAB's copulafit. %!shared u %! u = [0.08,0.12; 0.17,0.25; 0.23,0.19; 0.31,0.42; 0.39,0.35; 0.46,0.51; ... %! 0.52,0.48; 0.58,0.63; 0.64,0.59; 0.71,0.68; 0.77,0.82; 0.83,0.79; ... %! 0.88,0.91; 0.93,0.87; 0.97,0.95]; ## Gaussian: sample correlation of the normal scores (exact match to MATLAB) %!test %! rho = copulafit ("Gaussian", u); %! assert_equal (rho, [1, 0.979591430658725; 0.979591430658725, 1], 1e-12); ## Archimedean point estimates and Wald confidence intervals (match MATLAB) %!test %! [a, ci] = copulafit ("Clayton", u); %! assert_equal (a, 8.70842970823662, 1e-6); %! assert_equal (ci, [4.48832722027934, 12.9285321961939], 1e-5); %!test %! [a, ci] = copulafit ("Frank", u); %! assert_equal (a, 29.7092786222299, 1e-5); %! assert_equal (ci, [7.05441865208815, 52.3641385923717], 1e-4); %!test %! [a, ci] = copulafit ("Gumbel", u); %! assert_equal (a, 6.14597585483989, 1e-6); %! assert_equal (ci, [2.94928782240414, 9.34266388727563], 1e-5); ## The t copula fit recovers the correlation (its nu is weakly identified for ## near-Gaussian data, so only rho is checked tightly). %!test %! [rho, nu] = copulafit ("t", u); %! assert_equal (rho, [1, 0.98084; 0.98084, 1], 2e-3); %! assert_equal (nu > 10, true); ## The confidence level widens the interval as alpha shrinks %!test %! [~, ci95] = copulafit ("Clayton", u); %! [~, ci99] = copulafit ("Clayton", u, "alpha", 0.01); %! assert_equal (ci99(1) < ci95(1) && ci99(2) > ci95(2), true); ## Round trip: fit recovers a parameter close to the generating one %!test %! rng (42); %! u = copularnd ("Clayton", 2, 2000); %! a = copulafit ("Clayton", u); %! assert_equal (a, 2, 0.3); ## Test input validation %!error ... %! copulafit (5, [0.2, 0.3]) %!error ... %! copulafit ("Gaussian", "foo") %!error ... %! copulafit ("Gaussian", [0.2, 0.3; 1.2, 0.5]) %!error ... %! copulafit ("Gaussian", [0.2; 0.3; 0.4]) %!error ... %! [a, b] = copulafit ("Gaussian", [0.2, 0.3; 0.4, 0.5]); %!error ... %! copulafit ("t", [0.2, 0.3, 0.4; 0.5, 0.6, 0.7]) %!error ... %! copulafit ("Clayton", [0.2, 0.3, 0.4; 0.5, 0.6, 0.7]) %!error ... %! copulafit ("Clayton", [0.2, 0.3; 0.4, 0.5], "foo") %!error ... %! copulafit ("Clayton", [0.2, 0.3; 0.4, 0.5], "alpha", 2) %!error ... %! copulafit ("Foo", [0.2, 0.3; 0.4, 0.5]) statistics-release-1.9.2/inst/Distribution_Fitting/doc-cache000066400000000000000000003451161524624707500242320ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 53 # name: # type: sq_string # elements: 1 # length: 7 betafit # name: # type: sq_string # elements: 1 # length: 2122 statistics: paramhat = betafit ( x ) statistics: [ paramhat , paramci ] = betafit ( x ) statistics: [ paramhat , paramci ] = betafit ( x , alpha ) statistics: [ paramhat , paramci ] = betafit ( x , alpha , freq ) statistics: [ paramhat , paramci ] = betafit ( x , alpha , options ) statistics: [ paramhat , paramci ] = betafit ( x , alpha , freq , options ) Estimate parameters and confidence intervals for the Beta distribution. paramhat = betafit ( x ) returns the maximum likelihood estimates of the parameters of the Beta distribution given the data in vector x . paramhat ([1, 2]) corresponds to the α and β shape parameters, respectively. Missing values, NaNs , are ignored. [ paramhat , paramci ] = betafit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = betafit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals of the estimated parameter. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. […] = betafit ( params , x , freq ) accepts a frequency vector, freq , of the same size as x . freq must contain non-negative integer frequencies for the corresponding elements in x . By default, or if left empty, freq = ones (size ( x )) . [ paramhat , paramci ] = betafit ( x , alpha , options ) specifies control parameters for the iterative algorithm used to compute ML estimates with the fminsearch function. options is a structure with the following fields and their default values: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 The Beta distribution is defined on the open interval (0,1) . However, betafit can also compute the unbounded beta likelihood function for data that include exact zeros or ones. In such cases, zeros and ones are treated as if they were values that have been left-censored at sqrt (realmin) or right-censored at 1 - eps/2 , respectively. Further information about the Beta distribution can be found at https://en.wikipedia.org/wiki/Beta_distribution See also: betacdf, betainv, betapdf, betarnd, betalike, betastat # name: # type: sq_string # elements: 1 # length: 71 Estimate parameters and confidence intervals for the Beta distribution. # name: # type: sq_string # elements: 1 # length: 8 betalike # name: # type: sq_string # elements: 1 # length: 1513 statistics: nlogL = betalike ( params , x ) statistics: [ nlogL , avar ] = betalike ( params , x ) Negative log-likelihood for the Beta distribution. nlogL = betalike ( params , x ) returns the negative log likelihood of the data in x corresponding to the Beta distribution with (1) shape parameter α and (2) shape parameter β given in the two-element vector params . Both parameters must be positive real numbers and the data in the range [0,1] . Out of range parameters or data return NaN . [ nlogL , avar ] = betalike ( params , x ) returns the inverse of Fisher’s information matrix, avar . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of params are their asymptotic variances. […] = betalike ( params , x , freq ) accepts a frequency vector, freq , of the same size as x . freq must contain non-negative integer frequencies for the corresponding elements in x . By default, or if left empty, freq = ones (size ( x )) . The Beta distribution is defined on the open interval (0,1) . However, betafit can also compute the unbounded beta likelihood function for data that include exact zeros or ones. In such cases, zeros and ones are treated as if they were values that have been left-censored at sqrt (realmin) or right-censored at 1 - eps/2 , respectively. Further information about the Beta distribution can be found at https://en.wikipedia.org/wiki/Beta_distribution See also: betacdf, betainv, betapdf, betarnd, betafit, betastat # name: # type: sq_string # elements: 1 # length: 50 Negative log-likelihood for the Beta distribution. # name: # type: sq_string # elements: 1 # length: 7 binofit # name: # type: sq_string # elements: 1 # length: 1410 statistics: pshat = binofit ( x , n ) statistics: [ pshat , psci ] = binofit ( x , n ) statistics: [ pshat , psci ] = binofit ( x , n , alpha ) Estimate parameter and confidence intervals for the binomial distribution. pshat = binofit ( x , n ) returns the maximum likelihood estimate (MLE) of the probability of success for the binomial distribution. x and n are scalars containing the number of successes and the number of trials, respectively. If x and n are vectors, binofit returns a vector of estimates whose i -th element is the parameter estimate for x (i) and n (i). A scalar value for x or n is expanded to the same size as the other input. [ pshat , psci ] = binofit ( x , n , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals of the estimated parameter. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. binofit treats a vector x as a collection of measurements from separate samples, and returns a vector of estimates. If you want to treat x as a single sample and compute a single parameter estimate and confidence interval, use binofit (sum ( x ), sum ( n )) when n is a vector, and binofit (sum ( x ), n * length ( x )) when n is a scalar. Further information about the binomial distribution can be found at https://en.wikipedia.org/wiki/Binomial_distribution See also: binocdf, binoinv, binopdf, binornd, binolike, binostat # name: # type: sq_string # elements: 1 # length: 74 Estimate parameter and confidence intervals for the binomial distribution. # name: # type: sq_string # elements: 1 # length: 8 binolike # name: # type: sq_string # elements: 1 # length: 1375 statistics: nlogL = binolike ( params , x ) statistics: [ nlogL , acov ] = binolike ( params , x ) statistics: […] = binolike ( params , x , freq ) Negative log-likelihood for the binomial distribution. nlogL = binolike ( params , x ) returns the negative log likelihood of the binomial distribution with (1) parameter n and (2) parameter ps , given in the two-element vector params , where n is the number of trials and ps is the probability of success, given the number of successes in x . Unlike binofit , which handles each element in x independently, binolike returns the negative log likelihood of the entire vector x . [ nlogL , acov ] = binolike ( params , x ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of params are their asymptotic variances. […] = binolike ( params , x , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Further information about the binomial distribution can be found at https://en.wikipedia.org/wiki/Binomial_distribution See also: binocdf, binoinv, binopdf, binornd, binofit, binostat # name: # type: sq_string # elements: 1 # length: 54 Negative log-likelihood for the binomial distribution. # name: # type: sq_string # elements: 1 # length: 7 bisafit # name: # type: sq_string # elements: 1 # length: 2142 statistics: paramhat = bisafit ( x ) statistics: [ paramhat , paramci ] = bisafit ( x ) statistics: [ paramhat , paramci ] = bisafit ( x , alpha ) statistics: […] = bisafit ( x , alpha , censor ) statistics: […] = bisafit ( x , alpha , censor , freq ) statistics: […] = bisafit ( x , alpha , censor , freq , options ) Estimate mean and confidence intervals for the Birnbaum-Saunders distribution. muhat = bisafit ( x ) returns the maximum likelihood estimates of the parameters of the Birnbaum-Saunders distribution given the data in x . paramhat (1) is the scale parameter, beta , and paramhat (2) is the shape parameter, gamma . [ paramhat , paramci ] = bisafit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = bisafit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = bisafit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = bisafit ( x , alpha , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . […] = bisafit (…, options ) specifies control parameters for the iterative algorithm used to compute ML estimates with the fminsearch function. options is a structure with the following fields and their default values: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 Further information about the Birnbaum-Saunders distribution can be found at https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution See also: bisacdf, bisainv, bisapdf, bisarnd, bisalike, bisastat # name: # type: sq_string # elements: 1 # length: 78 Estimate mean and confidence intervals for the Birnbaum-Saunders distribution. # name: # type: sq_string # elements: 1 # length: 8 bisalike # name: # type: sq_string # elements: 1 # length: 1567 statistics: nlogL = bisalike ( params , x ) statistics: [ nlogL , acov ] = bisalike ( params , x ) statistics: […] = bisalike ( params , x , censor ) statistics: […] = bisalike ( params , x , censor , freq ) Negative log-likelihood for the Birnbaum-Saunders distribution. nlogL = bisalike ( params , x ) returns the negative log likelihood of the data in x corresponding to the Birnbaum-Saunders distribution with (1) scale parameter beta and (2) shape parameter gamma given in the two-element vector params . [ nlogL , acov ] = bisalike ( params , x ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of params are their asymptotic variances. […] = bisalike ( params , x , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = bisalike ( params , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Further information about the Birnbaum-Saunders distribution can be found at https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution See also: bisacdf, bisainv, bisapdf, bisarnd, bisafit, bisastat # name: # type: sq_string # elements: 1 # length: 63 Negative log-likelihood for the Birnbaum-Saunders distribution. # name: # type: sq_string # elements: 1 # length: 7 burrfit # name: # type: sq_string # elements: 1 # length: 2146 statistics: paramhat = burrfit ( x ) statistics: [ paramhat , paramci ] = burrfit ( x ) statistics: [ paramhat , paramci ] = burrfit ( x , alpha ) statistics: […] = burrfit ( x , alpha , censor ) statistics: […] = burrfit ( x , alpha , censor , freq ) statistics: […] = burrfit ( x , alpha , censor , freq , options ) Estimate mean and confidence intervals for the Burr type XII distribution. muhat = burrfit ( x ) returns the maximum likelihood estimates of the parameters of the Burr type XII distribution given the data in x . paramhat (1) is the scale parameter, lambda , paramhat (2) is the first shape parameter, c , and paramhat (3) is the second shape parameter, k [ paramhat , paramci ] = burrfit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = burrfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = burrfit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = burrfit ( x , alpha , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . […] = burrfit (…, options ) specifies control parameters for the iterative algorithm used to compute the maximum likelihood estimates. options is a structure with the following field and its default value: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 Further information about the Burr type XII distribution can be found at https://en.wikipedia.org/wiki/Burr_distribution See also: burrcdf, burrinv, burrpdf, burrrnd, burrlike, burrstat # name: # type: sq_string # elements: 1 # length: 74 Estimate mean and confidence intervals for the Burr type XII distribution. # name: # type: sq_string # elements: 1 # length: 8 burrlike # name: # type: sq_string # elements: 1 # length: 1571 statistics: nlogL = burrlike ( params , x ) statistics: [ nlogL , acov ] = burrlike ( params , x ) statistics: […] = burrlike ( params , x , censor ) statistics: […] = burrlike ( params , x , censor , freq ) Negative log-likelihood for the Burr type XII distribution. nlogL = burrlike ( params , x ) returns the negative log likelihood of the data in x corresponding to the Burr type XII distribution with (1) scale parameter lambda , (2) first shape parameter c , and (3) second shape parameter k given in the three-element vector params . [ nlogL , acov ] = burrlike ( params , x ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of acov are their asymptotic variances. […] = burrlike ( params , x , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = burrlike ( params , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Further information about the Burr type XII distribution can be found at https://en.wikipedia.org/wiki/Burr_distribution See also: burrcdf, burrinv, burrpdf, burrrnd, burrfit, burrstat # name: # type: sq_string # elements: 1 # length: 59 Negative log-likelihood for the Burr type XII distribution. # name: # type: sq_string # elements: 1 # length: 9 copulafit # name: # type: sq_string # elements: 1 # length: 1943 statistics: rho = copulafit ( "Gaussian" , u ) statistics: [ rho , nu ] = copulafit ( "t" , u ) statistics: [ param , ci ] = copulafit ( family , u ) statistics: […] = copulafit (…, "alpha" , a ) Fit a copula to data. copulafit ( family , u ) returns the maximum-likelihood estimate of the parameter of a copula of the family family , fit to the data in u . The rows of u are observations and its columns are variables; all entries must lie strictly inside the unit interval (0,1) , as produced for example by a probability-integral transform or by ecdf / ksdensity . family is the copula family name. It can be "Gaussian" for the Gaussian family, "t" for the Student’s t family, "Clayton" for the Clayton family, "Gumbel" for the Gumbel-Hougaard family, or "Frank" for the Frank family. The returned value depends on the family: For "Gaussian" , rho = copulafit ("Gaussian", u ) returns the estimated linear correlation matrix rho , computed as the sample correlation of the normal scores norminv ( u ) . The data may have two or more columns. For "t" , copulafit ("t", u ) returns the estimated correlation matrix rho and the degrees of freedom nu as [ rho , nu ] , obtained by maximizing the copula log-likelihood. Only bivariate data (two columns) are supported. For the Archimedean families "Clayton" , "Gumbel" , and "Frank" , [ param , ci ] = copulafit ( family , u ) returns the scalar copula parameter param and, optionally, a two-element vector ci with the lower and upper confidence bounds. Only bivariate data are supported. copulafit (…, "alpha" , a ) sets the significance level for the confidence interval to a , so that ci has coverage 100 * (1 - a ) percent. The default is a = 0.05 . The confidence interval is a Wald interval whose standard error is obtained from the outer-product-of-gradients estimate of the information. See also: copulastat, copulaparam, copulacdf, copulapdf, copularnd # name: # type: sq_string # elements: 1 # length: 21 Fit a copula to data. # name: # type: sq_string # elements: 1 # length: 5 evfit # name: # type: sq_string # elements: 1 # length: 2420 statistics: paramhat = evfit ( x ) statistics: [ paramhat , paramci ] = evfit ( x ) statistics: [ paramhat , paramci ] = evfit ( x , alpha ) statistics: […] = evfit ( x , alpha , censor ) statistics: […] = evfit ( x , alpha , censor , freq ) statistics: […] = evfit ( x , alpha , censor , freq , options ) Estimate parameters and confidence intervals for the extreme value distribution. paramhat = evfit ( x ) returns the maximum likelihood estimates of the parameters of the extreme value distribution (also known as the Gumbel or the type I generalized extreme value distribution) given the data in x . paramhat (1) is the location parameter, mu , and paramhat (2) is the scale parameter, sigma . [ paramhat , paramci ] = evfit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = evfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = evfit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = evfit ( x , alpha , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . […] = evfit (…, options ) specifies control parameters for the iterative algorithm used to compute the maximum likelihood estimates. options is a structure with the following field and its default value: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 The Gumbel distribution is used to model the distribution of the maximum (or the minimum) of a number of samples of various distributions. This version is suitable for modeling minima. For modeling maxima, use the alternative Gumbel fitting function, gumbelfit . Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution See also: evcdf, evinv, evpdf, evrnd, evlike, evstat, gumbelfit # name: # type: sq_string # elements: 1 # length: 80 Estimate parameters and confidence intervals for the extreme value distribution. # name: # type: sq_string # elements: 1 # length: 6 evlike # name: # type: sq_string # elements: 1 # length: 1860 statistics: nlogL = evlike ( params , x ) statistics: [ nlogL , acov ] = evlike ( params , x ) statistics: […] = evlike ( params , x , censor ) statistics: […] = evlike ( params , x , censor , freq ) Negative log-likelihood for the extreme value distribution. nlogL = evlike ( params , x ) returns the negative log likelihood of the data in x corresponding to the extreme value distribution (also known as the Gumbel or the type I generalized extreme value distribution) with (1) location parameter mu and (2) scale parameter sigma given in the two-element vector params . [ nlogL , acov ] = evlike ( params , x ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of acov are their asymptotic variances. […] = evlike ( params , x , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = evlike ( params , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . The Gumbel distribution is used to model the distribution of the maximum (or the minimum) of a number of samples of various distributions. This version is suitable for modeling minima. For modeling maxima, use the alternative Gumbel likelihood function, gumbellike . Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution See also: evcdf, evinv, evpdf, evrnd, evfit, evstat, gumbellike # name: # type: sq_string # elements: 1 # length: 59 Negative log-likelihood for the extreme value distribution. # name: # type: sq_string # elements: 1 # length: 6 expfit # name: # type: sq_string # elements: 1 # length: 2733 statistics: muhat = expfit ( x ) statistics: [ muhat , muci ] = expfit ( x ) statistics: [ muhat , muci ] = expfit ( x , alpha ) statistics: […] = expfit ( x , alpha , censor ) statistics: […] = expfit ( x , alpha , censor , freq ) Estimate mean and confidence intervals for the exponential distribution. muhat = expfit ( x ) returns the maximum likelihood estimate of the mean parameter, muhat , of the exponential distribution given the data in x . x is expected to be a non-negative vector. If x is an array, the mean will be computed for each column of x . If any elements of x are NaN, that vector’s mean will be returned as NaN. [ muhat , muci ] = expfit ( x ) returns the 95% confidence intervals for the parameter estimate. If x is a vector, muci is a two element column vector. If x is an array, each column of data will have a confidence interval returned as a two-row array. […] = evfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. Any invalid values for alpha will return NaN for both CI bounds. […] = expfit ( x , alpha , censor ) accepts a logical or numeric array, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. Any non-zero elements are regarded as 1 s. By default, or if left empty, censor = zeros (size ( x )) . […] = expfit ( x , alpha , censor , freq ) accepts a frequency array, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Matlab incompatibility: Matlab’s expfit produces unpredictable results for some cases with higher dimensions (specifically 1 x m x n x ... arrays). Octave’s implementation allows for n×D arrays, consistently performing calculations on individual column vectors. Additionally, censor and freq can be used with arrays of any size, whereas Matlab only allows their use when x is a vector. A common alternative parameterization of the exponential distribution is to use the parameter λ defined as the mean number of events in an interval as opposed to the parameter μ , which is the mean wait time for an event to occur. λ and μ are reciprocals, i.e. μ = 1 / λ . Further information about the exponential distribution can be found at https://en.wikipedia.org/wiki/Exponential_distribution See also: expcdf, expinv, explpdf, exprnd, explike, expstat # name: # type: sq_string # elements: 1 # length: 72 Estimate mean and confidence intervals for the exponential distribution. # name: # type: sq_string # elements: 1 # length: 7 explike # name: # type: sq_string # elements: 1 # length: 1724 statistics: nlogL = explike ( mu , x ) statistics: [ nlogL , avar ] = explike ( mu , x ) statistics: […] = explike ( mu , x , censor ) statistics: […] = explike ( mu , x , censor , freq ) Negative log-likelihood for the exponential distribution. nlogL = explike ( mu , x ) returns the negative log likelihood of the data in x corresponding to the exponential distribution with mean parameter mu . x must be a vector of non-negative values, otherwise NaN is returned. [ nlogL , avar ] = explike ( mu , x ) also returns the inverse of Fisher’s information matrix, avar . If the input mean parameter, mu , is the maximum likelihood estimate, avar is its asymptotic variance. […] = explike ( mu , x , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = explike ( mu , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . A common alternative parameterization of the exponential distribution is to use the parameter λ defined as the mean number of events in an interval as opposed to the parameter μ , which is the mean wait time for an event to occur. λ and μ are reciprocals, i.e. μ = 1 / λ . Further information about the exponential distribution can be found at https://en.wikipedia.org/wiki/Exponential_distribution See also: expcdf, expinv, exppdf, exprnd, expfit, expstat # name: # type: sq_string # elements: 1 # length: 57 Negative log-likelihood for the exponential distribution. # name: # type: sq_string # elements: 1 # length: 6 gamfit # name: # type: sq_string # elements: 1 # length: 2445 statistics: paramhat = gamfit ( x ) statistics: [ paramhat , paramci ] = gamfit ( x ) statistics: [ paramhat , paramci ] = gamfit ( x , alpha ) statistics: […] = gamfit ( x , alpha , censor ) statistics: […] = gamfit ( x , alpha , censor , freq ) statistics: […] = gamfit ( x , alpha , censor , freq , options ) Estimate parameters and confidence intervals for the Gamma distribution. paramhat = gamfit ( x ) returns the maximum likelihood estimates of the parameters of the Gamma distribution given the data in x . paramhat (1) is the shape parameter, a , and paramhat (2) is the scale parameter, b . [ paramhat , paramci ] = gamfit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = gamfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = gamfit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = gamfit ( x , alpha , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . […] = gamfit (…, options ) specifies control parameters for the iterative algorithm used to compute the maximum likelihood estimates. options is a structure with the following field and its default value: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 OCTAVE/MATLAB use the alternative parameterization given by the pair α, β , i.e. shape a and scale b . In Wikipedia, the two common parameterizations use the pairs k, θ , as shape and scale, and α, β , as shape and rate, respectively. The parameter names a and b used here (for MATLAB compatibility) correspond to the parameter notation k, θ instead of the α, β as reported in Wikipedia. Further information about the Gamma distribution can be found at https://en.wikipedia.org/wiki/Gamma_distribution See also: gamcdf, gampdf, gaminv, gamrnd, gamlike # name: # type: sq_string # elements: 1 # length: 72 Estimate parameters and confidence intervals for the Gamma distribution. # name: # type: sq_string # elements: 1 # length: 7 gamlike # name: # type: sq_string # elements: 1 # length: 1876 statistics: nlogL = gamlike ( params , x ) statistics: [ nlogL , acov ] = gamlike ( params , x ) statistics: […] = gamlike ( params , x , censor ) statistics: […] = gamlike ( params , x , censor , freq ) Negative log-likelihood for the Gamma distribution. nlogL = gamlike ( params , x ) returns the negative log likelihood of the data in x corresponding to the Gamma distribution with (1) shape parameter a and (2) scale parameter b given in the two-element vector params . [ nlogL , acov ] = gamlike ( params , x ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of acov are their asymptotic variances. […] = gamlike ( params , x , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = gamlike ( params , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . OCTAVE/MATLAB use the alternative parameterization given by the pair α, β , i.e. shape a and scale b . In Wikipedia, the two common parameterizations use the pairs k, θ , as shape and scale, and α, β , as shape and rate, respectively. The parameter names a and b used here (for MATLAB compatibility) correspond to the parameter notation k, θ instead of the α, β as reported in Wikipedia. Further information about the Gamma distribution can be found at https://en.wikipedia.org/wiki/Gamma_distribution See also: gamcdf, gampdf, gaminv, gamrnd, gamfit # name: # type: sq_string # elements: 1 # length: 51 Negative log-likelihood for the Gamma distribution. # name: # type: sq_string # elements: 1 # length: 6 geofit # name: # type: sq_string # elements: 1 # length: 1288 statistics: pshat = geofit ( x ) statistics: [ pshat , psci ] = geofit ( x ) statistics: [ pshat , psci ] = geofit ( x , alpha ) statistics: [ pshat , psci ] = geofit ( x , alpha , freq ) Estimate parameter and confidence intervals for the geometric distribution. pshat = geofit ( x ) returns the maximum likelihood estimate (MLE) of the probability of success for the geometric distribution. x must be a vector. [ pshat , psci ] = geofit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals of the estimated parameter. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = geofit ( x , alpha , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . The geometric distribution models the number of failures ( x ) of a Bernoulli trial with probability ps before the first success. Further information about the geometric distribution can be found at https://en.wikipedia.org/wiki/Geometric_distribution See also: geocdf, geoinv, geopdf, geornd, geostat # name: # type: sq_string # elements: 1 # length: 75 Estimate parameter and confidence intervals for the geometric distribution. # name: # type: sq_string # elements: 1 # length: 6 gevfit # name: # type: sq_string # elements: 1 # length: 2576 statistics: paramhat = gevfit ( x ) statistics: [ paramhat , paramci ] = gevfit ( x ) statistics: [ paramhat , paramci ] = gevfit ( x , alpha ) statistics: [ paramhat , paramci ] = gevfit ( x , alpha , freq ) statistics: [ paramhat , paramci ] = gevfit ( x , alpha , options ) statistics: [ paramhat , paramci ] = gevfit ( x , alpha , freq , options ) Estimate parameters and confidence intervals for the generalized extreme value (GEV) distribution. paramhat = gevfit ( x ) returns the maximum likelihood estimates of the parameters of the GEV distribution given the data in x . paramhat (1) is the shape parameter, k , and paramhat (2) is the scale parameter, sigma , and paramhat (3) is the location parameter, mu . [ paramhat , paramci ] = gevfit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = gevfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = gevfit ( params , x , freq ) accepts a frequency vector, freq , of the same size as x . freq must contain non-negative integer frequencies for the corresponding elements in x . By default, or if left empty, freq = ones (size ( x )) . [ paramhat , paramci ] = gevfit ( x , alpha , options ) specifies control parameters for the iterative algorithm used to compute ML estimates with the fminsearch function. options is a structure with the following fields and their default values: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 When k < 0 , the GEV is the type III extreme value distribution. When k > 0 , the GEV distribution is the type II, or Frechet, extreme value distribution. If W has a Weibull distribution as computed by the wblcdf function, then - W has a type III extreme value distribution and 1/ W has a type II extreme value distribution. In the limit as k approaches 0 , the GEV is the mirror image of the type I extreme value distribution as computed by the evcdf function. The mean of the GEV distribution is not finite when k >= 1 , and the variance is not finite when k >= 1/2 . The GEV distribution has positive density only for values of x such that k * ( x - mu ) / sigma > -1 . Further information about the generalized extreme value distribution can be found at https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution See also: gevcdf, gevinv, gevpdf, gevrnd, gevlike, gevstat # name: # type: sq_string # elements: 1 # length: 98 Estimate parameters and confidence intervals for the generalized extreme value (GEV) distribution. # name: # type: sq_string # elements: 1 # length: 11 gevfit_lmom # name: # type: sq_string # elements: 1 # length: 942 statistics: [ paramhat , paramci ] = gevfit_lmom ( data ) Find an estimator ( paramhat ) of the generalized extreme value (GEV) distribution fitting data using the method of L-moments. Arguments data is the vector of given values. Return values paramhat is the 3-parameter maximum-likelihood parameter vector [ k ; sigma ; mu ], where k is the shape parameter of the GEV distribution, sigma is the scale parameter of the GEV distribution, and mu is the location parameter of the GEV distribution. paramci has the approximate 95% confidence intervals of the parameter values (currently not implemented). Examples data = gevrnd (0.1, 1, 0, 100, 1); [pfit, pci] = gevfit_lmom (data); p1 = gevcdf (data,pfit(1),pfit(2),pfit(3)); [f, x] = ecdf (data); plot(data, p1, 's', x, f) References Ailliot, P.; Thompson, C. & Thomson, P. Mixed methods for fitting the GEV distribution, Water Resources Research, 2011, 47, W05551 See also: gevfit # name: # type: sq_string # elements: 1 # length: 124 Find an estimator (paramhat) of the generalized extreme value (GEV) distribution fitting data using the method of L-moments. # name: # type: sq_string # elements: 1 # length: 7 gevlike # name: # type: sq_string # elements: 1 # length: 2391 statistics: nlogL = gevlike ( params , x ) statistics: [ nlogL , acov ] = gevlike ( params , x ) statistics: [ nlogL , acov ] = gevlike ( params , x , freq ) Negative log-likelihood for the generalized extreme value (GEV) distribution. nlogL = gevlike ( params , x ) returns the negative log likelihood of the data in x corresponding to the GEV distribution with (1) shape parameter k , (2) scale parameter sigma , and (3) location parameter mu given in the three-element vector params . [ nlogL , acov ] = gevlike ( params , x ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of acov are their asymptotic variances. […] = gevlike ( params , x , freq ) accepts a frequency vector, freq , of the same size as x . freq must contain non-negative integer frequencies for the corresponding elements in x . By default, or if left empty, freq = ones (size ( x )) . When k < 0 , the GEV is the type III extreme value distribution. When k > 0 , the GEV distribution is the type II, or Frechet, extreme value distribution. If W has a Weibull distribution as computed by the wblcdf function, then - W has a type III extreme value distribution and 1/ W has a type II extreme value distribution. In the limit as k approaches 0 , the GEV is the mirror image of the type I extreme value distribution as computed by the evcdf function. MATLAB compatibility. At exactly k = 0 the returned ACOV deviates from MATLAB’s deliberately. Both implementations agree to thirteen digits for any k != 0 , and both their k != 0 expressions converge on the values returned here as k approaches 0 ; MATLAB’s own k = 0 branch returns something else entirely, which its k != 0 branch therefore contradicts. The Gumbel-limit expressions used here are the limits of the general ones, so ACOV is continuous at 0 . This is independent of the sample size. The mean of the GEV distribution is not finite when k >= 1 , and the variance is not finite when k >= 1/2 . The GEV distribution has positive density only for values of x such that k * ( x - mu ) / sigma > -1 . Further information about the generalized extreme value distribution can be found at https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution See also: gevcdf, gevinv, gevpdf, gevrnd, gevfit, gevstat # name: # type: sq_string # elements: 1 # length: 77 Negative log-likelihood for the generalized extreme value (GEV) distribution. # name: # type: sq_string # elements: 1 # length: 5 gpfit # name: # type: sq_string # elements: 1 # length: 3224 statistics: paramhat = gpfit ( x ) statistics: [ paramhat , paramci ] = gpfit ( x ) statistics: [ paramhat , paramci ] = gpfit ( x , alpha ) statistics: [ paramhat , paramci ] = gpfit ( x , alpha , options ) statistics: [ paramhat , paramci ] = gpfit ( x , alpha , options , freq ) Estimate parameters and confidence intervals for the generalized Pareto distribution. paramhat = gpfit ( x ) returns the maximum likelihood estimates of the parameters of the generalized Pareto distribution given the data in x . paramhat (1) is the shape parameter, k , and paramhat (2) is the scale parameter, sigma . gpfit does not estimate the location parameter theta and assumes it to be zero, so x must not contain negative values. To fit data with a known nonzero theta , subtract it from x before calling gpfit ; the estimates of k and sigma are unchanged by the shift. [ paramhat , paramci ] = gpfit ( x ) returns the 95% confidence intervals for the estimated parameters k and sigma as a 2 -by- 2 matrix whose first row holds the lower bounds and whose second row holds the upper bounds. […] = gpfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = gpfit ( x , alpha , options ) specifies control parameters for the iterative algorithm used to compute ML estimates with the fminsearch function. options is a structure with the following fields and their default values: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 […] = gpfit ( x , alpha , options , freq ) accepts a vector of the same size as x giving the number of times each element of x was observed. This fourth argument is an Octave extension; MATLAB’s gpfit takes three inputs at most. When the shape parameter falls below -1 the likelihood is unbounded: the density at the upper endpoint of the support diverges as that endpoint closes onto the largest observation, so no maximum likelihood estimate exists and whatever is returned is an arbitrary point on that ridge. gpfit warns in this case and returns NaN confidence intervals. The estimate it does return always keeps every observation strictly inside the fitted support, since the likelihood is infinite outside it. This is a deliberate deviation: MATLAB has been measured returning parameters for such data under which the largest observation has zero density and its own gplike returns Inf . When k = 0 and theta = 0 , the Generalized Pareto is equivalent to the exponential distribution. When k > 0 and theta = k / k the Generalized Pareto is equivalent to the Pareto distribution. The mean of the Generalized Pareto is not finite when k >= 1 and the variance is not finite when k >= 1/2 . When k >= 0 , the Generalized Pareto has positive density for x > theta , or, when theta < 0 , for 0 <= ( x - theta ) / sigma <= -1 / k . Further information about the generalized Pareto distribution can be found at https://en.wikipedia.org/wiki/Generalized_Pareto_distribution See also: gpcdf, gpinv, gppdf, gprnd, gplike, gpstat # name: # type: sq_string # elements: 1 # length: 85 Estimate parameters and confidence intervals for the generalized Pareto distribution. # name: # type: sq_string # elements: 1 # length: 6 gplike # name: # type: sq_string # elements: 1 # length: 1966 statistics: nlogL = gplike ( params , x ) statistics: [ nlogL , acov ] = gplike ( params , x ) statistics: […] = gplike ( params , x , freq ) Negative log-likelihood for the generalized Pareto distribution. nlogL = gplike ( params , x ) returns the negative log-likelihood of the data in x corresponding to the generalized Pareto distribution with (1) shape parameter k and (2) scale parameter sigma given in the two-element vector params . gplike does not accept a location parameter theta and assumes it to be zero. If the location is known to be nonzero, subtract it from x before calling gplike . [ nlogL , acov ] = gplike ( params , x ) returns the inverse of Fisher’s information matrix, acov , a 2 -by- 2 matrix. If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of acov are their asymptotic variances. acov is based on the observed Fisher’s information, not the expected information. […] = gplike ( params , x , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . This third argument is an Octave extension; MATLAB’s gplike takes two inputs. When k = 0 and mu = 0 , the Generalized Pareto CDF is equivalent to the exponential distribution. When k > 0 and mu = k / k the Generalized Pareto is equivalent to the Pareto distribution. The mean of the Generalized Pareto is not finite when k >= 1 and the variance is not finite when k >= 1/2 . When k >= 0 , the Generalized Pareto has positive density for x > mu , or, when mu < 0 , for 0 <= ( x - mu ) / sigma <= -1 / k . Further information about the generalized Pareto distribution can be found at https://en.wikipedia.org/wiki/Generalized_Pareto_distribution See also: gpcdf, gpinv, gppdf, gprnd, gpfit, gpstat # name: # type: sq_string # elements: 1 # length: 64 Negative log-likelihood for the generalized Pareto distribution. # name: # type: sq_string # elements: 1 # length: 9 gumbelfit # name: # type: sq_string # elements: 1 # length: 2470 statistics: paramhat = gumbelfit ( x ) statistics: [ paramhat , paramci ] = gumbelfit ( x ) statistics: [ paramhat , paramci ] = gumbelfit ( x , alpha ) statistics: […] = gumbelfit ( x , alpha , censor ) statistics: […] = gumbelfit ( x , alpha , censor , freq ) statistics: […] = gumbelfit ( x , alpha , censor , freq , options ) Estimate parameters and confidence intervals for Gumbel distribution. paramhat = gumbelfit ( x ) returns the maximum likelihood estimates of the parameters of the Gumbel distribution (also known as the extreme value or the type I generalized extreme value distribution) given in x . paramhat (1) is the location parameter, mu , and paramhat (2) is the scale parameter, beta . [ paramhat , paramci ] = gumbelfit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = gumbelfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = gumbelfit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = gumbelfit ( x , alpha , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . […] = gumbelfit (…, options ) specifies control parameters for the iterative algorithm used to compute the maximum likelihood estimates. options is a structure with the following field and its default value: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 The Gumbel distribution is used to model the distribution of the maximum (or the minimum) of a number of samples of various distributions. This version is suitable for modeling maxima. For modeling minima, use the alternative extreme value fitting function, evfit . Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution See also: gumbelcdf, gumbelinv, gumbelpdf, gumbelrnd, gumbellike, gumbelstat, evfit # name: # type: sq_string # elements: 1 # length: 69 Estimate parameters and confidence intervals for Gumbel distribution. # name: # type: sq_string # elements: 1 # length: 10 gumbellike # name: # type: sq_string # elements: 1 # length: 1914 statistics: nlogL = gumbellike ( params , x ) statistics: [ nlogL , avar ] = gumbellike ( params , x ) statistics: […] = gumbellike ( params , x , censor ) statistics: […] = gumbellike ( params , x , censor , freq ) Negative log-likelihood for the extreme value distribution. nlogL = gumbellike ( params , x ) returns the negative log likelihood of the data in x corresponding to the Gumbel distribution (also known as the extreme value or the type I generalized extreme value distribution) with (1) location parameter mu and (2) scale parameter beta given in the two-element vector params . [ nlogL , acov ] = gumbellike ( params , x ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of acov are their asymptotic variances. […] = gumbellike ( params , x , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = gumbellike ( params , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . The Gumbel distribution is used to model the distribution of the maximum (or the minimum) of a number of samples of various distributions. This version is suitable for modeling maxima. For modeling minima, use the alternative extreme value likelihood function, evlike . Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution See also: gumbelcdf, gumbelinv, gumbelpdf, gumbelrnd, gumbelfit, gumbelstat, evlike # name: # type: sq_string # elements: 1 # length: 59 Negative log-likelihood for the extreme value distribution. # name: # type: sq_string # elements: 1 # length: 5 hnfit # name: # type: sq_string # elements: 1 # length: 1646 statistics: [ paramhat , paramci ] = hnfit ( x , mu ) statistics: [ paramhat , paramci ] = hnfit ( x , mu , alpha ) statistics: [ paramhat , paramci ] = hnfit ( x , mu , alpha , freq ) Estimate parameters and confidence intervals for the half-normal distribution. paramhat = hnfit ( x , mu ) returns the maximum likelihood estimates of the parameters of the half-normal distribution given the data in vector x and the location parameter mu . paramhat (1) is the location parameter, mu , and paramhat (2) is the scale parameter, sigma . Although mu is returned in the estimated paramhat , hnfit does not estimate the location parameter mu , and it must be assumed to be known, given as a fixed parameter in input argument mu . [ paramhat , paramci ] = hnfit ( x , mu ) returns the 95% confidence intervals for the estimated scale parameter sigma . The first column of paramci includes the location parameter mu without any confidence bounds. […] = hnfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals of the estimated scale parameter. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. […] = hnfit ( params , x , freq ) accepts a frequency vector, freq , of the same size as x . freq must contain non-negative integer frequencies for the corresponding elements in x . By default, or if left empty, freq = ones (size ( x )) . The half-normal CDF is only defined for x >= mu . Further information about the half-normal distribution can be found at https://en.wikipedia.org/wiki/Half-normal_distribution See also: hncdf, hninv, hnpdf, hnrnd, hnlike, hnstat # name: # type: sq_string # elements: 1 # length: 78 Estimate parameters and confidence intervals for the half-normal distribution. # name: # type: sq_string # elements: 1 # length: 6 hnlike # name: # type: sq_string # elements: 1 # length: 1220 statistics: nlogL = hnlike ( params , x ) statistics: [ nlogL , acov ] = hnlike ( params , x ) statistics: [ nlogL , acov ] = hnlike ( params , x , freq ) Negative log-likelihood for the half-normal distribution. nlogL = hnlike ( params , x ) returns the negative log likelihood of the data in x corresponding to the half-normal distribution with (1) location parameter mu and (2) scale parameter sigma given in the two-element vector params . [ nlogL , acov ] = hnlike ( params , x ) returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of acov are their asymptotic variances. […] = hnlike ( params , x , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . The half-normal CDF is only defined for x >= mu . Further information about the half-normal distribution can be found at https://en.wikipedia.org/wiki/Half-normal_distribution See also: hncdf, hninv, hnpdf, hnrnd, hnfit, hnstat # name: # type: sq_string # elements: 1 # length: 57 Negative log-likelihood for the half-normal distribution. # name: # type: sq_string # elements: 1 # length: 7 invgfit # name: # type: sq_string # elements: 1 # length: 2127 statistics: paramhat = invgfit ( x ) statistics: [ paramhat , paramci ] = invgfit ( x ) statistics: [ paramhat , paramci ] = invgfit ( x , alpha ) statistics: […] = invgfit ( x , alpha , censor ) statistics: […] = invgfit ( x , alpha , censor , freq ) statistics: […] = invgfit ( x , alpha , censor , freq , options ) Estimate mean and confidence intervals for the inverse Gaussian distribution. mu0 = invgfit ( x ) returns the maximum likelihood estimates of the parameters of the inverse Gaussian distribution given the data in x . paramhat (1) is the scale parameter, mu , and paramhat (2) is the shape parameter, lambda . [ paramhat , paramci ] = invgfit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = invgfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = invgfit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = invgfit ( x , alpha , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . […] = invgfit (…, options ) specifies control parameters for the iterative algorithm used to compute ML estimates with the fminsearch function. options is a structure with the following fields and their default values: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 Further information about the inverse Gaussian distribution can be found at https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution See also: invgcdf, invginv, invgpdf, invgrnd, invglike, invgstat # name: # type: sq_string # elements: 1 # length: 77 Estimate mean and confidence intervals for the inverse Gaussian distribution. # name: # type: sq_string # elements: 1 # length: 8 invglike # name: # type: sq_string # elements: 1 # length: 1554 statistics: nlogL = invglike ( params , x ) statistics: [ nlogL , acov ] = invglike ( params , x ) statistics: […] = invglike ( params , x , censor ) statistics: […] = invglike ( params , x , censor , freq ) Negative log-likelihood for the inverse Gaussian distribution. nlogL = invglike ( params , x ) returns the negative log likelihood of the data in x corresponding to the inverse Gaussian distribution with (1) scale parameter mu and (2) shape parameter lambda given in the two-element vector params . [ nlogL , acov ] = invglike ( params , x ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of params are their asymptotic variances. […] = invglike ( params , x , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = invglike ( params , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Further information about the inverse Gaussian distribution can be found at https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution See also: invgcdf, invginv, invgpdf, invgrnd, invgfit, invgstat # name: # type: sq_string # elements: 1 # length: 62 Negative log-likelihood for the inverse Gaussian distribution. # name: # type: sq_string # elements: 1 # length: 7 logifit # name: # type: sq_string # elements: 1 # length: 2090 statistics: paramhat = logifit ( x ) statistics: [ paramhat , paramci ] = logifit ( x ) statistics: [ paramhat , paramci ] = logifit ( x , alpha ) statistics: […] = logifit ( x , alpha , censor ) statistics: […] = logifit ( x , alpha , censor , freq ) statistics: […] = logifit ( x , alpha , censor , freq , options ) Estimate mean and confidence intervals for the logistic distribution. mu0 = logifit ( x ) returns the maximum likelihood estimates of the parameters of the logistic distribution given the data in x . paramhat (1) is the scale parameter, mu , and paramhat (2) is the shape parameter, s . [ paramhat , paramci ] = logifit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = logifit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = logifit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = logifit ( x , alpha , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . […] = logifit (…, options ) specifies control parameters for the iterative algorithm used to compute ML estimates with the fminsearch function. options is a structure with the following fields and their default values: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 Further information about the logistic distribution can be found at https://en.wikipedia.org/wiki/Logistic_distribution See also: logicdf, logiinv, logipdf, logirnd, logilike, logistat # name: # type: sq_string # elements: 1 # length: 69 Estimate mean and confidence intervals for the logistic distribution. # name: # type: sq_string # elements: 1 # length: 8 logilike # name: # type: sq_string # elements: 1 # length: 1524 statistics: nlogL = logilike ( params , x ) statistics: [ nlogL , acov ] = logilike ( params , x ) statistics: […] = logilike ( params , x , censor ) statistics: […] = logilike ( params , x , censor , freq ) Negative log-likelihood for the logistic distribution. nlogL = logilike ( params , x ) returns the negative log likelihood of the data in x corresponding to the logistic distribution with (1) location parameter mu and (2) scale parameter sigma given in the two-element vector params . [ nlogL , acov ] = logilike ( params , x ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of params are their asymptotic variances. […] = logilike ( params , x , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = logilike ( params , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Further information about the logistic distribution can be found at https://en.wikipedia.org/wiki/Logistic_distribution See also: logicdf, logiinv, logipdf, logirnd, logifit, logistat # name: # type: sq_string # elements: 1 # length: 54 Negative log-likelihood for the logistic distribution. # name: # type: sq_string # elements: 1 # length: 7 loglfit # name: # type: sq_string # elements: 1 # length: 2356 statistics: paramhat = loglfit ( x ) statistics: [ paramhat , paramci ] = loglfit ( x ) statistics: [ paramhat , paramci ] = loglfit ( x , alpha ) statistics: […] = loglfit ( x , alpha , censor ) statistics: […] = loglfit ( x , alpha , censor , freq ) statistics: […] = loglfit ( x , alpha , censor , freq , options ) Estimate mean and confidence intervals for the log-logistic distribution. mu0 = loglfit ( x ) returns the maximum likelihood estimates of the parameters of the log-logistic distribution given the data in x . paramhat (1) is the mean parameter, mu , and paramhat (2) is the scale parameter, sigma . [ paramhat , paramci ] = loglfit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = loglfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = loglfit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = loglfit ( x , alpha , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . […] = loglfit (…, options ) specifies control parameters for the iterative algorithm used to compute ML estimates with the fminsearch function. options is a structure with the following fields and their default values: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 Further information about the loglogistic distribution can be found at https://en.wikipedia.org/wiki/Log-logistic_distribution OCTAVE/MATLAB use an alternative parameterization given by the pair μ, σ , i.e. mu and sigma , in analogy with the logistic distribution. Their relation to the α and b parameters used in Wikipedia are given below: mu = log ( a ) sigma = 1 / a See also: loglcdf, loglinv, loglpdf, loglrnd, logllike, loglstat # name: # type: sq_string # elements: 1 # length: 73 Estimate mean and confidence intervals for the log-logistic distribution. # name: # type: sq_string # elements: 1 # length: 8 logllike # name: # type: sq_string # elements: 1 # length: 1779 statistics: nlogL = logllike ( params , x ) statistics: [ nlogL , acov ] = logllike ( params , x ) statistics: […] = logllike ( params , x , censor ) statistics: […] = logllike ( params , x , censor , freq ) Negative log-likelihood for the log-logistic distribution. nlogL = logllike ( params , x ) returns the negative log likelihood of the data in x corresponding to the log-logistic distribution with (1) scale parameter a and (2) shape parameter b given in the two-element vector params . [ nlogL , acov ] = logllike ( params , x ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of params are their asymptotic variances. […] = logllike ( params , x , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = logllike ( params , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Further information about the loglogistic distribution can be found at https://en.wikipedia.org/wiki/Log-logistic_distribution OCTAVE/MATLAB use an alternative parameterization given by the pair μ, σ , i.e. mu and sigma , in analogy with the logistic distribution. Their relation to the α and b parameters used in Wikipedia are given below: mu = log ( a ) sigma = 1 / a See also: loglcdf, loglinv, loglpdf, loglrnd, loglfit, loglstat # name: # type: sq_string # elements: 1 # length: 58 Negative log-likelihood for the log-logistic distribution. # name: # type: sq_string # elements: 1 # length: 7 lognfit # name: # type: sq_string # elements: 1 # length: 2488 statistics: paramhat = lognfit ( x ) statistics: [ paramhat , paramci ] = lognfit ( x ) statistics: [ paramhat , paramci ] = lognfit ( x , alpha ) statistics: […] = lognfit ( x , alpha , censor ) statistics: […] = lognfit ( x , alpha , censor , freq ) statistics: […] = lognfit ( x , alpha , censor , freq , options ) Estimate parameters and confidence intervals for the lognormal distribution. paramhat = lognfit ( x ) returns the maximum likelihood estimates of the parameters of the lognormal distribution given the data in vector x . paramhat ([1, 2]) corresponds to the mean and standard deviation, respectively, of the associated normal distribution. If a random variable follows this distribution, its logarithm is normally distributed with mean mu and standard deviation sigma . [ paramhat , paramci ] = lognfit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = lognfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = lognfit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = lognfit ( x , alpha , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . […] = lognfit (…, options ) specifies control parameters for the iterative algorithm used to compute ML estimates with the fminsearch function. options is a structure with the following fields and their default values: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 With no censor, the estimate of the standard deviation, paramhat (2) , is the square root of the unbiased estimate of the variance of log ( x ) . With censored data, the maximum likelihood estimate is returned. Further information about the lognormal distribution can be found at https://en.wikipedia.org/wiki/Log-normal_distribution See also: logncdf, logninv, lognpdf, lognrnd, lognlike, lognstat # name: # type: sq_string # elements: 1 # length: 76 Estimate parameters and confidence intervals for the lognormal distribution. # name: # type: sq_string # elements: 1 # length: 8 lognlike # name: # type: sq_string # elements: 1 # length: 1920 statistics: nlogL = lognlike ( params , x ) statistics: [ nlogL , avar ] = lognlike ( params , x ) statistics: […] = lognlike ( params , x , censor ) statistics: […] = lognlike ( params , x , censor , freq ) Negative log-likelihood for the lognormal distribution. nlogL = lognlike ( params , x ) returns the negative log-likelihood of the data in x corresponding to the lognormal distribution with (1) location parameter mu and (2) scale parameter sigma given in the two-element vector params , which correspond to the mean and standard deviation of the associated normal distribution. Missing values, NaNs , are ignored. Negative values of x are treated as missing values. If a random variable follows this distribution, its logarithm is normally distributed with mean mu and standard deviation sigma . [ nlogL , avar ] = lognlike ( params , x ) returns the inverse of Fisher’s information matrix, avar . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of avar are their asymptotic variances. avar is based on the observed Fisher’s information, not the expected information. […] = lognlike ( params , x , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = lognlike ( params , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Further information about the lognormal distribution can be found at https://en.wikipedia.org/wiki/Log-normal_distribution See also: logncdf, logninv, lognpdf, lognrnd, lognfit, lognstat # name: # type: sq_string # elements: 1 # length: 55 Negative log-likelihood for the lognormal distribution. # name: # type: sq_string # elements: 1 # length: 7 nakafit # name: # type: sq_string # elements: 1 # length: 2047 statistics: paramhat = nakafit ( x ) statistics: [ paramhat , paramci ] = nakafit ( x ) statistics: [ paramhat , paramci ] = nakafit ( x , alpha ) statistics: […] = nakafit ( x , alpha , censor ) statistics: […] = nakafit ( x , alpha , censor , freq ) statistics: […] = nakafit ( x , alpha , censor , freq , options ) Estimate mean and confidence intervals for the Nakagami distribution. mu0 = nakafit ( x ) returns the maximum likelihood estimates of the parameters of the Nakagami distribution given the data in x . paramhat (1) is the shape parameter, mu , and paramhat (2) is the spread parameter, omega . [ paramhat , paramci ] = nakafit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = nakafit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = nakafit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = nakafit ( params , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq must contain non-negative integer frequencies for the corresponding elements in x . By default, or if left empty, freq = ones (size ( x )) . […] = nakafit (…, options ) specifies control parameters for the iterative algorithm used to compute ML estimates with the fminsearch function. options is a structure with the following fields and their default values: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 Further information about the Nakagami distribution can be found at https://en.wikipedia.org/wiki/Nakagami_distribution See also: nakacdf, nakainv, nakapdf, nakarnd, nakalike, nakastat # name: # type: sq_string # elements: 1 # length: 69 Estimate mean and confidence intervals for the Nakagami distribution. # name: # type: sq_string # elements: 1 # length: 8 nakalike # name: # type: sq_string # elements: 1 # length: 1473 statistics: nlogL = nakalike ( params , x ) statistics: [ nlogL , acov ] = nakalike ( params , x ) statistics: […] = nakalike ( params , x , censor ) statistics: […] = nakalike ( params , x , censor , freq ) Negative log-likelihood for the Nakagami distribution. nlogL = nakalike ( params , x ) returns the negative log likelihood of the data in x corresponding to the Nakagami distribution with (1) shape parameter mu and (2) spread parameter omega given in the two-element vector params . [ nlogL , acov ] = nakalike ( params , x ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of params are their asymptotic variances. […] = nakalike ( params , x , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = nakalike ( params , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq must contain non-negative integer frequencies for the corresponding elements in x . By default, or if left empty, freq = ones (size ( x )) . Further information about the Nakagami distribution can be found at https://en.wikipedia.org/wiki/Nakagami_distribution See also: nakacdf, nakainv, nakapdf, nakarnd, nakafit, nakastat # name: # type: sq_string # elements: 1 # length: 54 Negative log-likelihood for the Nakagami distribution. # name: # type: sq_string # elements: 1 # length: 7 nbinfit # name: # type: sq_string # elements: 1 # length: 2988 statistics: paramhat = nbinfit ( x ) statistics: [ paramhat , paramci ] = nbinfit ( x ) statistics: [ paramhat , paramci ] = nbinfit ( x , alpha ) statistics: [ paramhat , paramci ] = nbinfit ( x , alpha , freq ) statistics: [ paramhat , paramci ] = nbinfit ( x , alpha , options ) statistics: [ paramhat , paramci ] = nbinfit ( x , alpha , freq , options ) Estimate parameter and confidence intervals for the negative binomial distribution. paramhat = nbinfit ( x ) returns the maximum likelihood estimates of the parameters of the negative binomial distribution given the data in vector x . paramhat (1) is the number of successes until the experiment is stopped, r , and paramhat (2) is the probability of success in each experiment, ps . [ paramhat , paramci ] = nbinfit ( x ) returns the 95% confidence intervals for the parameter estimates. [ paramhat , paramci ] = nbinfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals of the estimated parameter. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. […] = nbinlike ( params , x , freq ) accepts a frequency vector, freq , of the same size as x . freq must contain non-negative integer frequencies for the corresponding elements in x . By default, or if left empty, freq = ones (size ( x )) . [ paramhat , paramci ] = nbinfit ( x , alpha , options ) specifies control parameters for the iterative algorithm used to compute ML estimates with the fminsearch function. options is a structure with the following fields and their default values: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 When r is an integer, the negative binomial distribution is also known as the Pascal distribution and it models the number of failures in x before a specified number of successes is reached in a series of independent, identical trials. Its parameters are the probability of success in a single trial, ps , and the number of successes, r . A special case of the negative binomial distribution, when r = 1 , is the geometric distribution, which models the number of failures before the first success. r can also have non-integer positive values, in which form the negative binomial distribution, also known as the Polya distribution, has no interpretation in terms of repeated trials, but, like the Poisson distribution, it is useful in modeling count data. The negative binomial distribution is more general than the Poisson distribution because it has a variance that is greater than its mean, making it suitable for count data that do not meet the assumptions of the Poisson distribution. In the limit, as r increases to infinity, the negative binomial distribution approaches the Poisson distribution. Further information about the negative binomial distribution can be found at https://en.wikipedia.org/wiki/Negative_binomial_distribution See also: nbincdf, nbininv, nbinpdf, nbinrnd, nbinlike, nbinstat # name: # type: sq_string # elements: 1 # length: 83 Estimate parameter and confidence intervals for the negative binomial distribution. # name: # type: sq_string # elements: 1 # length: 8 nbinlike # name: # type: sq_string # elements: 1 # length: 2393 statistics: nlogL = nbinlike ( params , x ) statistics: [ nlogL , avar ] = nbinlike ( params , x ) statistics: [ nlogL , avar ] = nbinlike ( params , x , freq ) Negative log-likelihood for the negative binomial distribution. nlogL = nbinlike ( params , x ) returns the negative log likelihood of the negative binomial distribution with (1) parameter r and (2) parameter ps , given in the two-element vector params , where r is the number of successes until the experiment is stopped and ps is the probability of success in each experiment, given the number of failures in x . [ nlogL , avar ] = nbinlike ( params , x ) also returns the inverse of Fisher’s information matrix, avar . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of params are their asymptotic variances. […] = nbinlike ( params , x , freq ) accepts a frequency vector, freq , of the same size as x . freq must contain non-negative integer frequencies for the corresponding elements in x . By default, or if left empty, freq = ones (size ( x )) . When r is an integer, the negative binomial distribution is also known as the Pascal distribution and it models the number of failures in x before a specified number of successes is reached in a series of independent, identical trials. Its parameters are the probability of success in a single trial, ps , and the number of successes, r . A special case of the negative binomial distribution, when r = 1 , is the geometric distribution, which models the number of failures before the first success. r can also have non-integer positive values, in which form the negative binomial distribution, also known as the Polya distribution, has no interpretation in terms of repeated trials, but, like the Poisson distribution, it is useful in modeling count data. The negative binomial distribution is more general than the Poisson distribution because it has a variance that is greater than its mean, making it suitable for count data that do not meet the assumptions of the Poisson distribution. In the limit, as r increases to infinity, the negative binomial distribution approaches the Poisson distribution. Further information about the negative binomial distribution can be found at https://en.wikipedia.org/wiki/Negative_binomial_distribution See also: nbincdf, nbininv, nbinpdf, nbinrnd, nbinfit, nbinstat # name: # type: sq_string # elements: 1 # length: 63 Negative log-likelihood for the negative binomial distribution. # name: # type: sq_string # elements: 1 # length: 7 normfit # name: # type: sq_string # elements: 1 # length: 3291 statistics: muhat = normfit ( x ) statistics: [ muhat , sigmahat ] = normfit ( x ) statistics: [ muhat , sigmahat , muci ] = normfit ( x ) statistics: [ muhat , sigmahat , muci , sigmaci ] = normfit ( x ) statistics: […] = normfit ( x , alpha ) statistics: […] = normfit ( x , alpha , censor ) statistics: […] = normfit ( x , alpha , censor , freq ) statistics: […] = normfit ( x , alpha , censor , freq , options ) Estimate parameters and confidence intervals for the normal distribution. [ muhat , sigmahat ] = normfit ( x ) estimates the parameters of the normal distribution given the data in x . muhat is an estimate of the mean, and sigmahat is an estimate of the standard deviation. [ muhat , sigmahat , muci , sigmaci ] = normfit ( x ) returns the 95% confidence intervals for the mean and standard deviation estimates in the arrays muci and sigmaci , respectively. x can be a vector or a matrix. When x is a matrix, the parameter estimates and their confidence intervals are computed for each column. In this case, normfit supports only 2 input arguments, x and alpha . Optional arguments censor , freq , and options can be used only when x is a vector. alpha is a scalar value in the range (0,1) specifying the confidence level for the confidence intervals calculated as 100×(1 - alpha)% . By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. censor is a logical vector of the same length as x specifying whether each value in x is right-censored or not. 1 indicates observations that are right-censored and 0 indicates observations that are fully observed. With censoring, muhat and sigmahat are the maximum likelihood estimates (MLEs). If empty, the default is an array of 0s, meaning that all observations are fully observed. freq is a vector of the same length as x and it typically contains non-negative integer counts of the corresponding elements in x . If empty, the default is an array of 1s, meaning one observation per element of x . To obtain the weighted MLEs for a data set with censoring, specify weights of observations, normalized to the number of observations in x . However, when there is no censored data (default), the returned estimate for standard deviation is not exactly the WMLE. To compute the weighted MLE, multiply the value returned in sigmahat by sqrt ((sum ( freq ) - 1) / sum ( freq )) . The square root is needed because the factor corrects a variance, while sigmahat is a standard deviation. This correction is needed because normfit normally computes sigmahat using an unbiased variance estimator when there is no censored data. When there is censoring in the data, the correction is not needed, since normfit does not use the unbiased variance estimator in that case. options is a structure with the control parameters for fminsearch which is used internally to compute MLEs for censored data. By default, it uses the following options: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 Further information about the normal distribution can be found at https://en.wikipedia.org/wiki/Normal_distribution See also: normcdf, norminv, normpdf, normrnd, normlike, normstat # name: # type: sq_string # elements: 1 # length: 73 Estimate parameters and confidence intervals for the normal distribution. # name: # type: sq_string # elements: 1 # length: 8 normlike # name: # type: sq_string # elements: 1 # length: 1484 statistics: nlogL = normlike ( params , x ) statistics: [ nlogL , avar ] = normlike ( params , x ) statistics: […] = normlike ( params , x , censor ) statistics: […] = normlike ( params , x , censor , freq ) Negative log-likelihood for the normal distribution. nlogL = normlike ( params , x ) returns the negative log-likelihood for the normal distribution, evaluated at parameters params(1) = mean and params(2) = standard deviation, given x . nlogL is a scalar. [ nlogL , avar ] = normlike ( params , x ) returns the inverse of Fisher’s information matrix, avar . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of avar are their asymptotic variances. avar is based on the observed Fisher’s information, not the expected information. […] = normlike ( params , x , censor ) accepts a boolean vector of the same size as x that is 1 for observations that are right-censored and 0 for observations that are observed exactly. […] = normlike ( params , x , censor , freq ) accepts a frequency vector of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it may contain any non-integer non-negative values. Pass in [] for censor to use its default value. Further information about the normal distribution can be found at https://en.wikipedia.org/wiki/Normal_distribution See also: normcdf, norminv, normpdf, normrnd, normfit, normstat # name: # type: sq_string # elements: 1 # length: 52 Negative log-likelihood for the normal distribution. # name: # type: sq_string # elements: 1 # length: 8 poissfit # name: # type: sq_string # elements: 1 # length: 1304 statistics: lambdahat = poissfit ( x ) statistics: [ lambdahat , lambdaci ] = poissfit ( x ) statistics: [ lambdahat , lambdaci ] = poissfit ( x , alpha ) statistics: [ lambdahat , lambdaci ] = poissfit ( x , alpha , freq ) Estimate parameter and confidence intervals for the Poisson distribution. lambdahat = poissfit ( x ) returns the maximum likelihood estimate of the rate parameter, lambda , of the Poisson distribution given the data in x . x must be a vector of non-negative values. [ lambdahat , lambdaci ] = poissfit ( x ) returns the 95% confidence intervals for the parameter estimate. [ lambdahat , lambdaci ] = poissfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals of the estimated parameter. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = poissfit ( x , alpha , freq ) accepts a frequency vector or matrix, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x . freq cannot contain negative values. Further information about the Poisson distribution can be found at https://en.wikipedia.org/wiki/Poisson_distribution See also: poisscdf, poissinv, poisspdf, poissrnd, poisslike, poisstat # name: # type: sq_string # elements: 1 # length: 73 Estimate parameter and confidence intervals for the Poisson distribution. # name: # type: sq_string # elements: 1 # length: 9 poisslike # name: # type: sq_string # elements: 1 # length: 1124 statistics: nlogL = poisslike ( lambda , x ) statistics: [ nlogL , avar ] = poisslike ( lambda , x ) statistics: […] = poisslike ( lambda , x , freq ) Negative log-likelihood for the Poisson distribution. nlogL = poisslike ( lambda , x ) returns the negative log likelihood of the data in x corresponding to the Poisson distribution with rate parameter lambda . x must be a vector of non-negative values. [ nlogL , avar ] = poisslike ( lambda , x ) also returns the inverse of Fisher’s information matrix, avar . If the input rate parameter, lambda , is the maximum likelihood estimate, avar is its asymptotic variance. […] = poisslike ( lambda , x , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Further information about the Poisson distribution can be found at https://en.wikipedia.org/wiki/Poisson_distribution See also: poisscdf, poissinv, poisspdf, poissrnd, poissfit, poisstat # name: # type: sq_string # elements: 1 # length: 53 Negative log-likelihood for the Poisson distribution. # name: # type: sq_string # elements: 1 # length: 7 raylfit # name: # type: sq_string # elements: 1 # length: 1698 statistics: sigmaA = raylfit ( x ) statistics: [ sigmaA , sigmaci ] = raylfit ( x ) statistics: [ sigmaA , sigmaci ] = raylfit ( x , alpha ) statistics: [ sigmaA , sigmaci ] = raylfit ( x , alpha , censor ) statistics: [ sigmaA , sigmaci ] = raylfit ( x , alpha , censor , freq ) Estimate parameter and confidence intervals for the Rayleigh distribution. sigmaA = raylfit ( x ) returns the maximum likelihood estimate of the rate parameter, lambda , of the Rayleigh distribution given the data in x . x must be a vector of non-negative values. [ sigmaA , sigmaci ] = raylfit ( x ) returns the 95% confidence intervals for the parameter estimate. [ sigmaA , sigmaci ] = raylfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals of the estimated parameter. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = raylfit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = raylfit ( x , alpha , censor , freq ) accepts a frequency vector or matrix, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x . freq cannot contain negative values. Further information about the Rayleigh distribution can be found at https://en.wikipedia.org/wiki/Rayleigh_distribution The prob.RayleighDistribution class names this same parameter B , after MATLAB. See also: raylcdf, raylinv, raylpdf, raylrnd, rayllike, raylstat # name: # type: sq_string # elements: 1 # length: 74 Estimate parameter and confidence intervals for the Rayleigh distribution. # name: # type: sq_string # elements: 1 # length: 8 rayllike # name: # type: sq_string # elements: 1 # length: 1190 statistics: nlogL = rayllike ( sigma , x ) statistics: [ nlogL , acov ] = rayllike ( sigma , x ) statistics: […] = rayllike ( sigma , x , freq ) Negative log-likelihood for the Rayleigh distribution. nlogL = rayllike ( sigma , x ) returns the negative log likelihood of the data in x corresponding to the Rayleigh distribution with rate parameter sigma . x must be a vector of non-negative values. [ nlogL , acov ] = rayllike ( sigma , x ) also returns the inverse of Fisher’s information matrix, acov . If the input rate parameter, sigma , is the maximum likelihood estimate, acov is its asymptotic variance. […] = rayllike ( sigma , x , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Further information about the Rayleigh distribution can be found at https://en.wikipedia.org/wiki/Rayleigh_distribution The prob.RayleighDistribution class names this same parameter B , after MATLAB. See also: raylcdf, raylinv, raylpdf, raylrnd, raylfit, raylstat # name: # type: sq_string # elements: 1 # length: 54 Negative log-likelihood for the Rayleigh distribution. # name: # type: sq_string # elements: 1 # length: 7 ricefit # name: # type: sq_string # elements: 1 # length: 2102 statistics: paramhat = ricefit ( x ) statistics: [ paramhat , paramci ] = ricefit ( x ) statistics: [ paramhat , paramci ] = ricefit ( x , alpha ) statistics: […] = ricefit ( x , alpha , censor ) statistics: […] = ricefit ( x , alpha , censor , freq ) statistics: […] = ricefit ( x , alpha , censor , freq , options ) Estimate parameters and confidence intervals for the Rician distribution. paramhat = ricefit ( x ) returns the maximum likelihood estimates of the parameters of the Rician distribution given the data in x . paramhat (1) is the non-centrality (distance) parameter, s , and paramhat (2) is the scale parameter, sigma . [ paramhat , paramci ] = ricefit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = ricefit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = ricefit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = ricefit ( x , alpha , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . […] = ricefit (…, options ) specifies control parameters for the iterative algorithm used to compute the maximum likelihood estimates. options is a structure with the following field and its default value: options .Display = "off" options .MaxFunEvals = 1000 options .MaxIter = 500 options .TolX = 1e-6 Further information about the Rician distribution can be found at https://en.wikipedia.org/wiki/Rice_distribution See also: ricecdf, ricepdf, riceinv, ricernd, ricelike, ricestat # name: # type: sq_string # elements: 1 # length: 73 Estimate parameters and confidence intervals for the Rician distribution. # name: # type: sq_string # elements: 1 # length: 8 ricelike # name: # type: sq_string # elements: 1 # length: 1530 statistics: nlogL = ricelike ( params , x ) statistics: [ nlogL , acov ] = ricelike ( params , x ) statistics: […] = ricelike ( params , x , censor ) statistics: […] = ricelike ( params , x , censor , freq ) Negative log-likelihood for the Rician distribution. nlogL = ricelike ( params , x ) returns the negative log likelihood of the data in x corresponding to the Rician distribution with (1) non-centrality (distance) parameter s and (2) scale parameter sigma given in the two-element vector params . [ nlogL , acov ] = ricelike ( params , x ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of params are their asymptotic variances. […] = ricelike ( params , x , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = ricelike ( params , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Further information about the Rician distribution can be found at https://en.wikipedia.org/wiki/Rice_distribution See also: ricecdf, riceinv, ricepdf, ricernd, ricefit, ricestat # name: # type: sq_string # elements: 1 # length: 52 Negative log-likelihood for the Rician distribution. # name: # type: sq_string # elements: 1 # length: 7 stblfit # name: # type: sq_string # elements: 1 # length: 2563 statistics: paramhat = stblfit ( x ) statistics: [ paramhat , paramci ] = stblfit ( x ) statistics: [ paramhat , paramci ] = stblfit ( x , alpha ) statistics: [ paramhat , paramci ] = stblfit ( x , alpha , freq ) statistics: [ paramhat , paramci ] = stblfit ( x , alpha , options ) statistics: [ paramhat , paramci ] = stblfit ( x , alpha , freq , options ) Estimate parameters and confidence intervals for the stable distribution. paramhat = stblfit ( x ) returns the maximum likelihood estimates of the parameters of the stable distribution, in the Nolan S0 parameterization, given the data in x . paramhat (1) is the tail index alpha , paramhat (2) is the skewness beta , paramhat (3) is the scale gam , and paramhat (4) is the location delta . [ paramhat , paramci ] = stblfit ( x ) returns the 95% confidence intervals for the parameter estimates. The intervals are Wald intervals from the observed Fisher information. […] = stblfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default value. […] = stblfit ( x , alpha , freq ) accepts a frequency vector, freq , of the same size as x . freq must contain non-negative integer frequencies for the corresponding elements in x . By default, or if left empty, freq = ones (size ( x )) . [ paramhat , paramci ] = stblfit ( x , alpha , options ) specifies control parameters for the iterative algorithm used to compute the ML estimates with the fminsearch function. options is a structure with the following fields and their default values: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 The stable density has no closed form; it is evaluated by numerical inversion of the characteristic function, which makes fitting considerably slower than for the closed-form distributions. Censoring is not supported. The estimates are the maximum-likelihood estimates under the mathematically exact density. MATLAB fits an interpolation-based approximation of the stable density, whose maximum-likelihood estimates deviate from the exact ones by about 10^{-2} (and the resulting confidence intervals by up to roughly 20%); stblfit returns the exact (more accurate) estimates. Further information about the stable distribution can be found at https://en.wikipedia.org/wiki/Stable_distribution See also: stbllike, stblpdf, stblcdf, stblinv, stblrnd, fitdist, makedist # name: # type: sq_string # elements: 1 # length: 73 Estimate parameters and confidence intervals for the stable distribution. # name: # type: sq_string # elements: 1 # length: 8 stbllike # name: # type: sq_string # elements: 1 # length: 1314 statistics: nlogL = stbllike ( params , x ) statistics: [ nlogL , acov ] = stbllike ( params , x ) statistics: [ nlogL , acov ] = stbllike ( params , x , freq ) Negative log-likelihood for the stable distribution. nlogL = stbllike ( params , x ) returns the negative log-likelihood of the data in x corresponding to the stable distribution, in the Nolan S0 parameterization, with (1) tail index alpha , (2) skewness beta , (3) scale gam , and (4) location delta given in the four-element vector params . [ nlogL , acov ] = stbllike ( params , x ) also returns the inverse of the observed Fisher information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of acov are their asymptotic variances. acov is based on the numerically evaluated Hessian of the negative log-likelihood, since the stable density has no closed form. […] = stbllike ( params , x , freq ) accepts a frequency vector, freq , of the same size as x . freq must contain non-negative integer frequencies for the corresponding elements in x . By default, or if left empty, freq = ones (size ( x )) . Further information about the stable distribution can be found at https://en.wikipedia.org/wiki/Stable_distribution See also: stblfit, stblpdf, stblcdf, stblinv, stblrnd # name: # type: sq_string # elements: 1 # length: 52 Negative log-likelihood for the stable distribution. # name: # type: sq_string # elements: 1 # length: 6 tlsfit # name: # type: sq_string # elements: 1 # length: 2223 statistics: paramhat = tlsfit ( x ) statistics: [ paramhat , paramci ] = tlsfit ( x ) statistics: [ paramhat , paramci ] = tlsfit ( x , alpha ) statistics: […] = tlsfit ( x , alpha , censor ) statistics: […] = tlsfit ( x , alpha , censor , freq ) statistics: […] = tlsfit ( x , alpha , censor , freq , options ) Estimate parameters and confidence intervals for the Location-scale Student’s T distribution. muhat = tlsfit ( x ) returns the maximum likelihood estimates of the parameters of the location-scale T distribution given the data in x . paramhat (1) is the location parameter, mu , paramhat (2) is the scale parameter, sigma , and paramhat (3) is the degrees of freedom, nu . [ paramhat , paramci ] = tlsfit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = tlsfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = tlsfit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = tlsfit ( x , alpha , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . […] = tlsfit (…, options ) specifies control parameters for the iterative algorithm used to compute ML estimates with the fminsearch function. options is a structure with the following fields and their default values: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 Further information about the location-scale Student’s T distribution can be found at https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution See also: tlscdf, tlsinv, tlspdf, tlsrnd, tlslike, tlsstat # name: # type: sq_string # elements: 1 # length: 93 Estimate parameters and confidence intervals for the Location-scale Student's T distribution. # name: # type: sq_string # elements: 1 # length: 7 tlslike # name: # type: sq_string # elements: 1 # length: 1727 statistics: nlogL = tlslike ( params , x ) statistics: [ nlogL , acov ] = tlslike ( params , x ) statistics: […] = tlslike ( params , x , alpha , censor ) statistics: […] = tlslike ( params , x , alpha , censor , freq ) Negative log-likelihood for the location-scale Student’s T distribution. nlogL = tlslike ( params , x ) returns the negative log-likelihood of the x in x corresponding to the location-scale T distribution with (1) location parameter mu , (2) scale parameter sigma and (3) degrees of freedom nu given in the three-element vector params . [ nlogL , acov ] = tlslike ( params , x ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of acov are their asymptotic variances. acov is based on the observed Fisher’s information, not the expected information. […] = tlslike ( params , x , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = tlslike ( params , x , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but may contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Further information about the location-scale Student’s T distribution can be found at https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution See also: tlscdf, tlsinv, tlspdf, tlsrnd, tlsfit, tlsstat # name: # type: sq_string # elements: 1 # length: 72 Negative log-likelihood for the location-scale Student's T distribution. # name: # type: sq_string # elements: 1 # length: 7 unidfit # name: # type: sq_string # elements: 1 # length: 1189 statistics: Nhat = unidfit ( x ) statistics: [ Nhat , Nci ] = unidfit ( x ) statistics: [ Nhat , Nci ] = unidfit ( x , alpha ) statistics: [ Nhat , Nci ] = unidfit ( x , alpha , freq ) Estimate parameter and confidence intervals for the discrete uniform distribution. Nhat = unidfit ( x ) returns the maximum likelihood estimate (MLE) of the maximum observable value for the discrete uniform distribution. x must be a vector. [ Nhat , Nci ] = unidfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals of the estimated parameter. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = unidfit ( x , alpha , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Further information about the discrete uniform distribution can be found at https://en.wikipedia.org/wiki/Discrete_uniform_distribution See also: unidcdf, unidinv, unidpdf, unidrnd, unidstat # name: # type: sq_string # elements: 1 # length: 82 Estimate parameter and confidence intervals for the discrete uniform distribution. # name: # type: sq_string # elements: 1 # length: 6 unifit # name: # type: sq_string # elements: 1 # length: 1961 statistics: ahat = unifit ( x ) statistics: [ ahat , bhat ] = unifit ( x ) statistics: [ ahat , bhat , aci , bci ] = unifit ( x ) statistics: […] = unifit ( x , alpha ) statistics: […] = unifit ( x , alpha , freq ) Estimate parameters and confidence intervals for the continuous uniform distribution. [ ahat , bhat ] = unifit ( x ) returns the maximum likelihood estimates of the lower and upper endpoints, a and b , of the continuous uniform distribution given the data in x . Each estimate is returned as a separate output. x may be a vector, which is fitted as a single sample, or a matrix, which is fitted column by column. For a matrix of n columns ahat and bhat are 1 -by- n row vectors and aci and bci are 2 -by- n . [ ahat , bhat , aci , bci ] = unifit ( x ) also returns the 95% confidence intervals of the two estimates, one column per column of x , with the lower bound in the first row and the upper bound in the second. ahat is the upper bound of aci and bhat the lower bound of bci , since no sample can fall outside the fitted range. […] = unifit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals of the estimated parameters. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = unifit ( x , alpha , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . This third argument is an Octave extension; MATLAB’s unifit takes two inputs at most, and freq is accepted for a vector x only. Further information about the continuous uniform distribution can be found at https://en.wikipedia.org/wiki/Continuous_uniform_distribution See also: unifcdf, unifinv, unifpdf, unifrnd, unifstat # name: # type: sq_string # elements: 1 # length: 85 Estimate parameters and confidence intervals for the continuous uniform distribution. # name: # type: sq_string # elements: 1 # length: 6 wblfit # name: # type: sq_string # elements: 1 # length: 2192 statistics: paramhat = wblfit ( x ) statistics: [ paramhat , paramci ] = wblfit ( x ) statistics: [ paramhat , paramci ] = wblfit ( x , alpha ) statistics: […] = wblfit ( x , alpha , censor ) statistics: […] = wblfit ( x , alpha , censor , freq ) statistics: […] = wblfit ( x , alpha , censor , freq , options ) Estimate parameters and confidence intervals for the Weibull distribution. muhat = wblfit ( x ) returns the maximum likelihood estimates of the parameters of the Weibull distribution given the data in x . paramhat (1) is the scale parameter, lambda , and paramhat (2) is the shape parameter, k . [ paramhat , paramci ] = wblfit ( x ) returns the 95% confidence intervals for the parameter estimates. […] = wblfit ( x , alpha ) also returns the 100 * (1 - alpha ) percent confidence intervals for the parameter estimates. By default, the optional argument alpha is 0.05 corresponding to 95% confidence intervals. Pass in [] for alpha to use the default values. […] = wblfit ( x , alpha , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = wblfit ( x , alpha , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but it can contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . […] = wblfit (…, options ) specifies control parameters for the iterative algorithm used to compute the maximum likelihood estimates. options is a structure with the following field and its default value: options .Display = "off" options .MaxFunEvals = 400 options .MaxIter = 200 options .TolX = 1e-6 Further information about the Weibull distribution can be found at https://en.wikipedia.org/wiki/Weibull_distribution The prob.WeibullDistribution class names these same two parameters A and B , after MATLAB. lambda is its A and k is its B . See also: wblcdf, wblinv, wblpdf, wblrnd, wbllike, wblstat # name: # type: sq_string # elements: 1 # length: 74 Estimate parameters and confidence intervals for the Weibull distribution. # name: # type: sq_string # elements: 1 # length: 7 wbllike # name: # type: sq_string # elements: 1 # length: 1739 statistics: nlogL = wbllike ( params , x ) statistics: [ nlogL , acov ] = wbllike ( params , x ) statistics: […] = wbllike ( params , x , alpha , censor ) statistics: […] = wbllike ( params , x , alpha , censor , freq ) Negative log-likelihood for the Weibull distribution. nlogL = wbllike ( params , data ) returns the negative log-likelihood of the data in x corresponding to the Weibull distribution with (1) scale parameter lambda and (2) shape parameter k given in the two-element vector params . [ nlogL , acov ] = wbllike ( params , data ) also returns the inverse of Fisher’s information matrix, acov . If the input parameter values in params are the maximum likelihood estimates, the diagonal elements of acov are their asymptotic variances. acov is based on the observed Fisher’s information, not the expected information. […] = wbllike ( params , data , censor ) accepts a boolean vector, censor , of the same size as x with 1 s for observations that are right-censored and 0 s for observations that are observed exactly. By default, or if left empty, censor = zeros (size ( x )) . […] = wbllike ( params , data , censor , freq ) accepts a frequency vector, freq , of the same size as x . freq typically contains integer frequencies for the corresponding elements in x , but may contain any non-integer non-negative values. By default, or if left empty, freq = ones (size ( x )) . Further information about the Weibull distribution can be found at https://en.wikipedia.org/wiki/Weibull_distribution The prob.WeibullDistribution class names these same two parameters A and B , after MATLAB. lambda is its A and k is its B . See also: wblcdf, wblinv, wblpdf, wblrnd, wblfit, wblstat # name: # type: sq_string # elements: 1 # length: 53 Negative log-likelihood for the Weibull distribution. statistics-release-1.9.2/inst/Distribution_Fitting/evfit.m000066400000000000000000000335501524624707500237700ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## Copyright (C) 2022 Andrew Penn ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} evfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} evfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} evfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} evfit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} evfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} evfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate parameters and confidence intervals for the extreme value ## distribution. ## ## @code{@var{paramhat} = evfit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the extreme value distribution (also known as ## the Gumbel or the type I generalized extreme value distribution) given the ## data in @var{x}. @qcode{@var{paramhat}(1)} is the location parameter, ## @var{mu}, and @qcode{@var{paramhat}(2)} is the scale parameter, @var{sigma}. ## ## @code{[@var{paramhat}, @var{paramci}] = evfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = evfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = evfit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = evfit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@dots{}] = evfit (@dots{}, @var{options})} specifies control ## parameters for the iterative algorithm used to compute the maximum likelihood ## estimates. @var{options} is a structure with the following field and its ## default value: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## The Gumbel distribution is used to model the distribution of the maximum (or ## the minimum) of a number of samples of various distributions. This version ## is suitable for modeling minima. For modeling maxima, use the alternative ## Gumbel fitting function, @code{gumbelfit}. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## @seealso{evcdf, evinv, evpdf, evrnd, evlike, evstat, gumbelfit} ## @end deftypefn function [paramhat, paramci] = evfit (x, alpha, censor, freq, options) ## Check X for being a double precision vector if (! isvector (x) || ! isa (x, 'double')) error ("evfit: X must be a double-precision vector."); endif ## Check that X does not contain missing values (NaNs) if (any (isnan (x))) error ("evfit: X must NOT contain missing values (NaNs)."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("evfit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("evfit: X and CENSOR vectors mismatch."); endif ## Parse FREQ argument or add default if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("evfit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("evfit: FREQ must not contain negative values."); endif ## Get options structure or add defaults if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("evfit: 'options' 5th argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Remove zeros and NaNs from frequency vector (if necessary) if (! all (freq == 1)) remove = freq == 0 | isnan (freq); x(remove) = []; censor(remove) = []; freq(remove) = []; endif ## If X is a column vector, make X, CENSOR, and FREQ row vectors if (size (x, 1) > 1) x = x(:)'; censor = censor(:)'; freq = freq(:)'; endif ## Censor x and get number of samples sample_size = sum (freq); censored_sample_size = sum (freq .* censor); uncensored_sample_size = sample_size - censored_sample_size; x_range = range (x); x_max = max (x); ## Check cases that cannot make a fit. ## 1. All observations are censored if (sample_size == 0 || uncensored_sample_size == 0 || ! isfinite (x_range)) paramhat = NaN (1, 2); paramci = NaN (2, 2); return endif ## 2. Constant x in X if (censored_sample_size == 0 && x_range == 0) paramhat = [x(1), 0]; if (sample_size == 1) paramci = [-Inf, 0; Inf, Inf]; else paramci = [paramhat, paramhat]; endif return elseif (censored_sample_size == 0 && x_range != 0) ## Data can fit, so preprocess them to make likelihood eqn more stable. ## Shift x to max(x) == 0, min(x) = -1. x_0 = (x - x_max) ./ x_range; ## Get a rough initial estimate for scale parameter initial_sigma_parm = (sqrt (6) * std (x_0)) / pi; uncensored_weights = sum (freq .* x_0) ./ sample_size; endif ## 3. All uncensored observations are equal and greater than all censored ones uncensored_x_range = range (x(censor == 0)); uncensored_x = x(censor == 0); if (censored_sample_size > 0 && uncensored_x_range == 0 ... && uncensored_x(1) >= x_max) paramhat = [uncensored_x(1), 0]; if uncensored_sample_size == 1 paramci = [-Inf, 0; Inf, Inf]; else paramci = [paramhat; paramhat]; endif return else ## Data can fit, so preprocess them to make likelihood eqn more stable. ## Shift x to max(x) == 0, min(x) = -1. x_0 = (x - x_max) ./ x_range; ## Get a rough initial estimate for scale parameter if (uncensored_x_range > 0) [F_y, y] = ecdf (x_0, 'censoring', censor', 'frequency', freq'); pmid = (F_y(1:(end-1)) + F_y(2:end)) / 2; linefit = polyfit (log (- log (1 - pmid)), y(2:end), 1); initial_sigma_parm = linefit(1); else initial_sigma_parm = 1; endif uncensored_weights = sum (freq .* x_0 .* (1 - censor)) ./ ... uncensored_sample_size; endif ## Find lower and upper boundaries for bracketing the likelihood equation for ## the extreme value scale parameter if (evscale_lkeq (initial_sigma_parm, x_0, freq, uncensored_weights) > 0) upper = initial_sigma_parm; lower = 0.5 * upper; while (evscale_lkeq (lower, x_0, freq, uncensored_weights) > 0) upper = lower; lower = 0.5 * upper; if (lower <= realmin ('double')) error ("evfit: no solution for maximum likelihood estimates."); endif endwhile boundaries = [lower, upper]; else lower = initial_sigma_parm; upper = 2 * lower; while (evscale_lkeq (upper, x_0, freq, uncensored_weights) < 0) lower = upper; upper = 2 * lower; if (upper > realmax ('double')) error ("evfit: no solution for maximum likelihood estimates."); endif endwhile boundaries = [lower, upper]; endif ## Compute maximum likelihood for scale parameter as the root of the equation ## Custom code for finding the value within the boundaries [lower, upper] that ## evscale_lkeq function returns zero ## First check that there is a root within the boundaries new_lo = boundaries(1); new_up = boundaries(2); v_lower = evscale_lkeq (new_lo, x_0, freq, uncensored_weights); v_upper = evscale_lkeq (new_up, x_0, freq, uncensored_weights); if (! (sign (v_lower) * sign (v_upper) <= 0)) error ("evfit: no solution for maximum likelihood estimates."); endif ## Get a value at mid boundary range old_sigma = new_lo; new_sigma = (new_lo + new_up) / 2; new_fzero = evscale_lkeq (new_sigma, x_0, freq, uncensored_weights); ## Start searching cur_iter = 0; max_iter = 1e+3; while (cur_iter < max_iter && abs (old_sigma - new_sigma) > options.TolX) cur_iter++; if (new_fzero < 0) old_sigma = new_sigma; new_lo = new_sigma; new_sigma = (new_lo + new_up) / 2; new_fzero = evscale_lkeq (new_sigma, x_0, freq, uncensored_weights); else old_sigma = new_sigma; new_up = new_sigma; new_sigma = (new_lo + new_up) / 2; new_fzero = evscale_lkeq (new_sigma, x_0, freq, uncensored_weights); endif endwhile ## Check for maximum number of iterations if (cur_iter == max_iter) warning (strcat ("evfit: maximum number of function ", ... " evaluations (1e+4) has been reached.")); endif ## Compute MU muhat = new_sigma .* log (sum (freq .* exp (x_0 ./ new_sigma)) ./ ... uncensored_sample_size); ## Transform MU and SIGMA back to original location and scale paramhat = [(x_range*muhat)+x_max, x_range*new_sigma]; ## Compute the CI for MU and SIGMA if (nargout == 2) probs = [alpha/2; 1-alpha/2]; [~, acov] = evlike (paramhat, x, censor, freq); transfhat = [paramhat(1), log(paramhat(2))]; se = sqrt (diag (acov))'; se(2) = se(2) ./ paramhat(2); paramci = norminv ([probs, probs], [transfhat; transfhat], [se; se]); paramci(:,2) = exp (paramci(:,2)); endif endfunction ## Likelihood equation for the extreme value scale parameter. function v = evscale_lkeq (sigma, x, freq, x_weighted_uncensored) freq = freq .* exp (x ./ sigma); v = sigma + x_weighted_uncensored - sum (x .* freq) / sum (freq); endfunction %!demo %! ## Sample 3 populations from different extreme value distributions %! rng (42); %! r1 = evrnd (2, 5, 400, 1); %! r2 = evrnd (-5, 3, 400, 1); %! r3 = evrnd (14, 8, 400, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, 25, 0.4); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! ylim ([0, 0.28]) %! xlim ([-30, 30]); %! hold on %! %! ## Estimate their MU and SIGMA parameters %! mu_sigmaA = evfit (r(:,1)); %! mu_sigmaB = evfit (r(:,2)); %! mu_sigmaC = evfit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [min(r(:)):max(r(:))]; %! y = evpdf (x, mu_sigmaA(1), mu_sigmaA(2)); %! plot (x, y, '-pr'); %! y = evpdf (x, mu_sigmaB(1), mu_sigmaB(2)); %! plot (x, y, '-sg'); %! y = evpdf (x, mu_sigmaC(1), mu_sigmaC(2)); %! plot (x, y, '-^c'); %! legend ({'Normalized HIST of sample 1 with μ=2 and σ=5', ... %! 'Normalized HIST of sample 2 with μ=-5 and σ=3', ... %! 'Normalized HIST of sample 3 with μ=14 and σ=8', ... %! sprintf("PDF for sample 1 with estimated μ=%0.2f and σ=%0.2f", ... %! mu_sigmaA(1), mu_sigmaA(2)), ... %! sprintf("PDF for sample 2 with estimated μ=%0.2f and σ=%0.2f", ... %! mu_sigmaB(1), mu_sigmaB(2)), ... %! sprintf("PDF for sample 3 with estimated μ=%0.2f and σ=%0.2f", ... %! mu_sigmaC(1), mu_sigmaC(2))}) %! title ('Three population samples from different extreme value distributions') %! hold off ## Test output %!test %! x = 1:50; %! [paramhat, paramci] = evfit (x); %! paramhat_out = [32.6811, 13.0509]; %! paramci_out = [28.8504, 10.5294; 36.5118, 16.1763]; %! assert_equal (paramhat, paramhat_out, 1e-4); %! assert_equal (paramci, paramci_out, 1e-4); %!test %! x = 1:50; %! [paramhat, paramci] = evfit (x, 0.01); %! paramci_out = [27.6468, 9.8426; 37.7155, 17.3051]; %! assert_equal (paramci, paramci_out, 1e-4); ## Test input validation %!error evfit (ones (2,5)); %!error evfit (single (ones (1,5))); %!error evfit ([1, 2, 3, 4, NaN]); %!error evfit ([1, 2, 3, 4, 5], 1.2); %!error %! evfit ([1 2 3], 0.05, [], [1 5]) %!error %! evfit ([1 2 3], 0.05, [], [1 5 -1]) %!error ... %! evfit ([1:10], 0.05, [], [], 5) statistics-release-1.9.2/inst/Distribution_Fitting/evlike.m000066400000000000000000000136451524624707500241350ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} evlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} evlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} evlike (@var{params}, @var{x}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} evlike (@var{params}, @var{x}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the extreme value distribution. ## ## @code{@var{nlogL} = evlike (@var{params}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the extreme value ## distribution (also known as the Gumbel or the type I generalized extreme ## value distribution) with (1) location parameter @var{mu} and (2) scale ## parameter @var{sigma} given in the two-element vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = evlike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{acov} are their asymptotic variances. ## ## @code{[@dots{}] = evlike (@var{params}, @var{x}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = evlike (@var{params}, @var{x}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## The Gumbel distribution is used to model the distribution of the maximum (or ## the minimum) of a number of samples of various distributions. This version ## is suitable for modeling minima. For modeling maxima, use the alternative ## Gumbel likelihood function, @code{gumbellike}. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## @seealso{evcdf, evinv, evpdf, evrnd, evfit, evstat, gumbellike} ## @end deftypefn function [nlogL, acov] = evlike (params, x, censor, freq) ## Check input arguments and add defaults if (nargin < 2) error ("evlike: function called with too few input arguments."); endif if (numel (params) != 2) error ("evlike: wrong parameters length."); endif if (! isvector (x)) error ("evlike: X must be a vector."); endif if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("evlike: X and CENSOR vectors mismatch."); endif if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (isequal (size (x), size (freq))) nulls = find (freq == 0); if (numel (nulls) > 0) x(nulls) = []; censor(nulls) = []; freq(nulls) = []; endif else error ("evlike: X and FREQ vectors mismatch."); endif ## Get mu and sigma values mu = params(1); sigma = params(2); ## sigma must be positive, otherwise make it NaN if (sigma <= 0) sigma = NaN; endif ## Compute the individual log-likelihood terms z = (x - mu) ./ sigma; expz = exp (z); L = (z - log (sigma)) .* (1 - censor) - expz; ## Force a log(0)==-Inf for X from extreme right tail L(z == Inf) = -Inf; ## Neg-log-likelihood is the sum of the individual contributions nlogL = -sum (freq .* L); ## Compute the negative hessian at the parameter values. ## Invert to get the observed information matrix. if (nargout == 2) unc = (1 - censor); nH11 = sum (freq .* expz); nH12 = sum (freq .* ((z + 1) .* expz - unc)); nH22 = sum (freq .* (z .* (z+2) .* expz - ((2 .* z + 1) .* unc))); acov = (sigma .^ 2) * ... [nH22 -nH12; -nH12 nH11] / (nH11 * nH22 - nH12 * nH12); endif endfunction ## Test output %!test %! x = 1:50; %! [nlogL, acov] = evlike ([2.3, 1.2], x); %! avar_out = [-1.2778e-13, 3.1859e-15; 3.1859e-15, -7.9430e-17]; %! assert_equal (nlogL, 3.242264755689906e+17, 1e-14); %! assert_equal (acov, avar_out, 1e-3); %!test %! x = 1:50; %! [nlogL, acov] = evlike ([2.3, 1.2], x * 0.5); %! avar_out = [-7.6094e-05, 3.9819e-06; 3.9819e-06, -2.0836e-07]; %! assert_equal (nlogL, 481898704.0472211, 1e-6); %! assert_equal (acov, avar_out, 1e-3); %!test %! x = 1:50; %! [nlogL, acov] = evlike ([21, 15], x); %! avar_out = [11.73913876598908, -5.9546128523121216; ... %! -5.954612852312121, 3.708060045170236]; %! assert_equal (nlogL, 223.7612479380652, 1e-13); %! assert_equal (acov, avar_out, 1e-14); ## Test input validation %!error evlike ([12, 15]) %!error evlike ([12, 15, 3], [1:50]) %!error evlike ([12, 3], ones (10, 2)) %!error ... %! evlike ([12, 15], [1:50], [1, 2, 3]) %!error ... %! evlike ([12, 15], [1:50], [], [1, 2, 3]) statistics-release-1.9.2/inst/Distribution_Fitting/expfit.m000066400000000000000000000317251524624707500241540ustar00rootroot00000000000000## Copyright (C) 2021 Nicholas R. Jankowski ## Copyright (C) 2023 Andreas Bertsatos ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{muhat} =} expfit (@var{x}) ## @deftypefnx {statistics} {[@var{muhat}, @var{muci}] =} expfit (@var{x}) ## @deftypefnx {statistics} {[@var{muhat}, @var{muci}] =} expfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} expfit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} expfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## ## Estimate mean and confidence intervals for the exponential distribution. ## ## @code{@var{muhat} = expfit (@var{x})} returns the maximum likelihood estimate ## of the mean parameter, @var{muhat}, of the exponential distribution given the ## data in @var{x}. @var{x} is expected to be a non-negative vector. If @var{x} ## is an array, the mean will be computed for each column of @var{x}. If any ## elements of @var{x} are NaN, that vector's mean will be returned as NaN. ## ## @code{[@var{muhat}, @var{muci}] = expfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimate. If @var{x} is a vector, ## @var{muci} is a two element column vector. If @var{x} is an array, each ## column of data will have a confidence interval returned as a two-row array. ## ## @code{[@dots{}] = evfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. Any invalid values for @var{alpha} ## will return NaN for both CI bounds. ## ## @code{[@dots{}] = expfit (@var{x}, @var{alpha}, @var{censor})} accepts a ## logical or numeric array, @var{censor}, of the same size as @var{x} with ## @qcode{1}s for observations that are right-censored and @qcode{0}s for ## observations that are observed exactly. Any non-zero elements are regarded ## as @qcode{1}s. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = expfit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency array, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Matlab incompatibility: Matlab's @code{expfit} produces unpredictable results ## for some cases with higher dimensions (specifically 1 x m x n x ... arrays). ## Octave's implementation allows for @math{n*D} arrays, consistently performing ## calculations on individual column vectors. Additionally, @var{censor} and ## @var{freq} can be used with arrays of any size, whereas Matlab only allows ## their use when @var{x} is a vector. ## ## A common alternative parameterization of the exponential distribution is to ## use the parameter @math{λ} defined as the mean number of events in an ## interval as opposed to the parameter @math{μ}, which is the mean wait time ## for an event to occur. @math{λ} and @math{μ} are reciprocals, ## i.e. @math{μ = 1 / λ}. ## ## Further information about the exponential distribution can be found at ## @url{https://en.wikipedia.org/wiki/Exponential_distribution} ## ## @seealso{expcdf, expinv, explpdf, exprnd, explike, expstat} ## @end deftypefn function [muhat, muci] = expfit (x, alpha = 0.05, censor = [], freq = []) ## Check arguments if (nargin == 0 || nargin > 4 || nargout > 2) print_usage (); endif if (! (isnumeric (x) || islogical (x))) x = double (x); endif ## Guarantee working with column vectors if (isvector (x)) x = x(:); endif if (any (x(:) < 0)) error ("expfit: X cannot be negative."); endif sz_s = size (x); if (isempty (alpha)) alpha = 0.05; elseif (! (isscalar (alpha))) error ("expfit: ALPHA must be a scalar quantity."); endif if (isempty (censor) && isempty (freq)) ## Simple case without freq or censor, shortcut other validations muhat = mean (x, 1); if (nargout == 2) X = sum (x, 1); muci = [2*X./chi2inv(1 - alpha / 2, 2 * sz_s(1));... 2*X./chi2inv(alpha / 2, 2 * sz_s(1))]; endif else ## Input validation for censor and freq if (isempty (censor)) ## Expand to full censor with values that don't affect results censor = zeros (sz_s); elseif (! (isnumeric (censor) || islogical (censor))) ## Check for incorrect freq type error ("expfit: CENSOR must be a numeric or logical array.") elseif (isvector (censor)) ## Guarantee working with a column vector censor = censor(:); endif if (isempty (freq)) ## Expand to full censor with values that don't affect results freq = ones (sz_s); elseif (! (isnumeric (freq) || islogical (freq))) ## Check for incorrect freq type error ("expfit: FREQ must be a numeric or logical array.") elseif (isvector (freq)) ## Guarantee working with a column vector freq = freq(:); endif ## Check that size of censor and freq match x if (! (isequal (size (censor), sz_s))) error ("expfit: X and CENSOR vectors mismatch."); elseif (! isequal (size (freq), sz_s)) error ("expfit: X and FREQ vectors mismatch."); endif ## Trivial case where censor and freq have no effect if (all (censor(:) == 0 & freq(:) == 1)) muhat = mean (x, 1); if (nargout == 2) X = sum (x, 1); muci = [2*X./chi2inv(1 - alpha / 2, 2 * sz_s(1));... 2*X./chi2inv(alpha / 2, 2 * sz_s(1))]; endif ## No censoring, just adjust sample counts for freq elseif (all (censor(:) == 0)) X = sum (x.*freq, 1); n = sum (freq, 1); muhat = X ./ n; if (nargout == 2) muci = [2*X./chi2inv(1 - alpha / 2, 2 * n);... 2*X./chi2inv(alpha / 2, 2 * n)]; endif ## Censoring, but no sample counts adjustment elseif (all (freq(:) == 1)) censor = logical (censor); # convert any numeric censor'x to 0s and 1s X = sum (x, 1); r = sz_s(1) - sum (censor, 1); muhat = X ./ r; if (nargout == 2) muci = [2*X./chi2inv(1 - alpha / 2, 2 * r);... 2*X./chi2inv(alpha / 2, 2 * r)]; endif ## Both censoring and sample count adjustment else censor = logical (censor); # convert any numeric censor'x to 0s and 1s X = sum (x .* freq , 1); r = sum (freq .* (! censor), 1); muhat = X ./ r; if (nargout == 2) muci = [2*X./chi2inv(1 - alpha / 2, 2 * r);... 2*X./chi2inv(alpha / 2, 2 * r)]; endif endif ## compatibility check, NaN for columns where all censor's or freq's remove ## all samples null_columns = all (censor) | ! all (freq); muhat(null_columns) = NaN; if (nargout == 2) muci(:,null_columns) = NaN; endif endif endfunction %!demo %! ## Sample 3 populations from 3 different exponential distributions %! rande ('state', 42); %! r1 = exprnd (2, 4000, 1); %! r2 = exprnd (5, 4000, 1); %! r3 = exprnd (12, 4000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, 48, 0.52); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! hold on %! %! ## Estimate their mu parameter %! muhat = expfit (r); %! %! ## Plot their estimated PDFs %! x = [0:max(r(:))]; %! y = exppdf (x, muhat(1)); %! plot (x, y, '-pr'); %! y = exppdf (x, muhat(2)); %! plot (x, y, '-sg'); %! y = exppdf (x, muhat(3)); %! plot (x, y, '-^c'); %! ylim ([0, 0.6]) %! xlim ([0, 40]) %! legend ({'Normalized HIST of sample 1 with μ=2', ... %! 'Normalized HIST of sample 2 with μ=5', ... %! 'Normalized HIST of sample 3 with μ=12', ... %! sprintf("PDF for sample 1 with estimated μ=%0.2f", muhat(1)), ... %! sprintf("PDF for sample 2 with estimated μ=%0.2f", muhat(2)), ... %! sprintf("PDF for sample 3 with estimated μ=%0.2f", muhat(3))}) %! title ('Three population samples from different exponential distributions') %! hold off ## Tests for mean %!assert_equal (expfit (1), 1) %!assert_equal (expfit (1:3), 2) %!assert_equal (expfit ([1:3]'), 2) %!assert_equal (expfit (1:3, []), 2) %!assert_equal (expfit (1:3, [], [], []), 2) %!assert_equal (expfit (magic (3)), [5 5 5]) %!assert_equal (expfit (cat (3, magic (3), 2*magic (3))), cat (3,[5 5 5], [10 10 10])) %!assert_equal (expfit (1:3, 0.1, [0 0 0], [1 1 1]), 2) %!assert_equal (expfit ([1:3]', 0.1, [0 0 0]', [1 1 1]'), 2) %!assert_equal (expfit (1:3, 0.1, [0 0 0]', [1 1 1]'), 2) %!assert_equal (expfit (1:3, 0.1, [1 0 0], [1 1 1]), 3) %!assert_equal (expfit (1:3, 0.1, [0 0 0], [4 1 1]), 1.5) %!assert_equal (expfit (1:3, 0.1, [1 0 0], [4 1 1]), 4.5) %!assert_equal (expfit (1:3, 0.1, [1 0 1], [4 1 1]), 9) %!assert_equal (expfit (1:3, 0.1, [], [-1 1 1]), 4) %!assert_equal (expfit (1:3, 0.1, [], [0.5 1 1]), 2.2) %!assert_equal (expfit (1:3, 0.1, [1 1 1]), NaN) %!assert_equal (expfit (1:3, 0.1, [], [0 0 0]), NaN) %!assert_equal (expfit (reshape (1:9, [3 3])), [2 5 8]) %!assert_equal (expfit (reshape (1:9, [3 3]), [], eye (3)), [3 7.5 12]) %!assert_equal (expfit (reshape (1:9, [3 3]), [], 2*eye (3)), [3 7.5 12]) %!assert_equal (expfit (reshape (1:9, [3 3]), [], [], [2 2 2; 1 1 1; 1 1 1]), ... %! [1.75 4.75 7.75]) %!assert_equal (expfit (reshape (1:9, [3 3]), [], [], [2 2 2; 1 1 1; 1 1 1]), ... %! [1.75 4.75 7.75]) %!assert_equal (expfit (reshape (1:9, [3 3]), [], eye (3), [2 2 2; 1 1 1; 1 1 1]), ... %! [3.5 19/3 31/3]) ## Tests for confidence intervals %!assert_equal ([~,muci] = expfit (1:3, 0), [0; Inf]) %!assert_equal ([~,muci] = expfit (1:3, 2), [Inf; 0]) %!assert_equal ([~,muci] = expfit (1:3, 0.1, [1 1 1]), [NaN; NaN]) %!assert_equal ([~,muci] = expfit (1:3, 0.1, [], [0 0 0]), [NaN; NaN]) %!assert_equal ([~,muci] = expfit (1:3, -1), [NaN; NaN]) %!assert_equal ([~,muci] = expfit (1:3, 5), [NaN; NaN]) #!assert_equal ([~,muci] = expfit ([1:3;1:3], -1), NaN (2, 3)] #!assert_equal ([~,muci] = expfit ([1:3;1:3], 5), NaN (2, 3)] %!assert_equal ([~,muci] = expfit (1:3), [0.830485728373393; 9.698190330474096], ... %! 1000*eps) %!assert_equal ([~,muci] = expfit (1:3, 0.1), ... %! [0.953017262058213; 7.337731146400207], 1000*eps) %!assert_equal ([~,muci] = expfit ([1:3;2:4]), ... %! [0.538440777613095, 0.897401296021825, 1.256361814430554; ... %! 12.385982973214016, 20.643304955356694, 28.900626937499371], ... %! 1000*eps) %!assert_equal ([~,muci] = expfit ([1:3;2:4], [], [1 1 1; 0 0 0]), ... %! 100*[0.008132550920455, 0.013554251534091, 0.018975952147727; ... %! 1.184936706156216, 1.974894510260360, 2.764852314364504], ... %! 1000*eps) %!assert_equal ([~,muci] = expfit ([1:3;2:4], [], [], [3 3 3; 1 1 1]), ... %! [0.570302756652583, 1.026544961974649, 1.482787167296715; ... %! 4.587722594914109, 8.257900670845396, 11.928078746776684], ... %! 1000*eps) %!assert_equal ([~,muci] = expfit ([1:3;2:4], [], [0 0 0; 1 1 1], [3 3 3; 1 1 1]), ... %! [0.692071440311161, 1.245728592560089, 1.799385744809018; ... %! 8.081825275395081, 14.547285495711145, 21.012745716027212], ... %! 1000*eps) %!test %! x = reshape (1:8, [4 2]); %! x(4) = NaN; %! [muhat,muci] = expfit (x); %! assert_equal ({muhat, muci}, {[NaN, 6.5], ... %! [NaN, 2.965574334593430;NaN, 23.856157493553368]}, 1000*eps); %!test %! x = magic (3); %! censor = [0 1 0; 0 1 0; 0 1 0]; %! freq = [1 1 0; 1 1 0; 1 1 0]; %! [muhat,muci] = expfit (x, [], censor, freq); %! assert_equal ({muhat, muci}, {[5 NaN NaN], ... %! [[2.076214320933482; 24.245475826185242],NaN(2)]}, 1000*eps); ## Test input validation %!error expfit () %!error expfit (1,2,3,4,5) %!error [a b censor] = expfit (1) %!error expfit (1, [1 2]) %!error expfit ([-1 2 3 4 5]) %!error expfit ([1:5], [], 'test') %!error expfit ([1:5], [], [], 'test') %!error expfit ([1:5], [], [0 0 0 0]) %!error expfit ([1:5], [], [], [1 1 1 1]) statistics-release-1.9.2/inst/Distribution_Fitting/explike.m000066400000000000000000000123601524624707500243100ustar00rootroot00000000000000## Copyright (C) 2021 Nir Krakauer ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} explike (@var{mu}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{avar}] =} explike (@var{mu}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} explike (@var{mu}, @var{x}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} explike (@var{mu}, @var{x}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the exponential distribution. ## ## @code{@var{nlogL} = explike (@var{mu}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the exponential ## distribution with mean parameter @var{mu}. @var{x} must be a vector of ## non-negative values, otherwise @qcode{NaN} is returned. ## ## @code{[@var{nlogL}, @var{avar}] = explike (@var{mu}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{avar}. If the input ## mean parameter, @var{mu}, is the maximum likelihood estimate, @var{avar} is ## its asymptotic variance. ## ## @code{[@dots{}] = explike (@var{mu}, @var{x}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = explike (@var{mu}, @var{x}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## A common alternative parameterization of the exponential distribution is to ## use the parameter @math{λ} defined as the mean number of events in an ## interval as opposed to the parameter @math{μ}, which is the mean wait time ## for an event to occur. @math{λ} and @math{μ} are reciprocals, ## i.e. @math{μ = 1 / λ}. ## ## Further information about the exponential distribution can be found at ## @url{https://en.wikipedia.org/wiki/Exponential_distribution} ## ## @seealso{expcdf, expinv, exppdf, exprnd, expfit, expstat} ## @end deftypefn function [nlogL, avar] = explike (mu, x, censor, freq) ## Check input arguments if (nargin < 2) error ("explike: function called with too few input arguments."); endif if (! isvector (x)) error ("explike: X must be a vector."); endif if (numel (mu) != 1) error ("explike: MU must be a scalar."); endif ## Return NaNs for non-positive MU or negative values in X if (mu <= 0 || any (x(:) < 0)) nlogL = NaN; if (nargout > 1) avar = NaN; endif return endif if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("explike: X and CENSOR vectors mismatch."); endif if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (isequal (size (x), size (freq))) nulls = find (freq == 0); if (numel (nulls) > 0) x(nulls) = []; censor(nulls) = []; freq(nulls) = []; endif else error ("explike: X and FREQ vectors mismatch."); endif ## Start processing numx = numel (x); sumz = sum (x .* freq) / mu; numc = numx - sum (freq .* censor); ## Calculate negative log likelihood nlogL = sumz + numc * log (mu); ## Optionally calculate the inverse (reciprocal) of the second derivative ## of the negative log likelihood with respect to parameter if (nargout > 1) avar = (mu ^ 2) ./ (2 * sumz - numc); endif endfunction %!test %! x = 12; %! beta = 5; %! [L, V] = explike (beta, x); %! expected_L = 4.0094; %! expected_V = 6.5789; %! assert_equal (L, expected_L, 0.001); %! assert_equal (V, expected_V, 0.001); %!test %! x = 1:5; %! beta = 2; %! [L, V] = explike (beta, x); %! expected_L = 10.9657; %! expected_V = 0.4; %! assert_equal (L, expected_L, 0.001); %! assert_equal (V, expected_V, 0.001); ## Test input validation %!error explike () %!error explike (2) %!error explike ([12, 3], [1:50]) %!error explike (3, ones (10, 2)) %!error ... %! explike (3, [1:50], [1, 2, 3]) %!error ... %! explike (3, [1:50], [], [1, 2, 3]) statistics-release-1.9.2/inst/Distribution_Fitting/gamfit.m000066400000000000000000000401461524624707500241210ustar00rootroot00000000000000## Copyright (C) 2019 Nir Krakauer ## Copyright (C) 2023-2024 Andreas Bertsatos ## Based on previous work by Martijn van Oosterhout ## originally granted to the public domain. ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} gamfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} gamfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} gamfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} gamfit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} gamfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} gamfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate parameters and confidence intervals for the Gamma distribution. ## ## @code{@var{paramhat} = gamfit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the Gamma distribution given the data in ## @var{x}. @qcode{@var{paramhat}(1)} is the shape parameter, @var{a}, and ## @qcode{@var{paramhat}(2)} is the scale parameter, @var{b}. ## ## @code{[@var{paramhat}, @var{paramci}] = gamfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = gamfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = gamfit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = gamfit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@dots{}] = gamfit (@dots{}, @var{options})} specifies control ## parameters for the iterative algorithm used to compute the maximum likelihood ## estimates. @var{options} is a structure with the following field and its ## default value: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## OCTAVE/MATLAB use the alternative parameterization given by the pair ## @math{α, β}, i.e. shape @var{a} and scale @var{b}. In Wikipedia, the two ## common parameterizations use the pairs @math{k, θ}, as shape and scale, and ## @math{α, β}, as shape and rate, respectively. The parameter names @var{a} ## and @var{b} used here (for MATLAB compatibility) correspond to the parameter ## notation @math{k, θ} instead of the @math{α, β} as reported in Wikipedia. ## ## Further information about the Gamma distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gamma_distribution} ## ## @seealso{gamcdf, gampdf, gaminv, gamrnd, gamlike} ## @end deftypefn function [paramhat, paramci] = gamfit (x, alpha, censor, freq, options) ## Check input arguments if (! isvector (x)) error ("gamfit: X must be a vector."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("gamfit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("gamfit: X and CENSOR vectors mismatch."); endif ## Parse FREQ argument or add default if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("gamfit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("gamfit: FREQ must not contain negative values."); endif ## Get options structure or add defaults if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("gamfit: 'options' 5th argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Remove zeros and NaNs from frequency vector (if necessary) if (! all (freq == 1)) remove = freq == 0 | isnan (freq); x(remove) = []; censor(remove) = []; freq(remove) = []; endif ## Get sample size and data type cls = class (x); szx = sum (freq); ncen = sum (freq .* censor); nunc = szx - ncen; ## Check for illegal value in X if (ncen == 0 && any (x < 0)) error ("gamfit: X cannot contain negative values."); endif if (ncen > 0 && any (x <= 0)) error ("gamfit: X must contain positive values when censored."); endif ## Handle ill-conditioned cases: no data or all censored if (szx == 0 || nunc == 0 || any (! isfinite (x))) paramhat = nan (1, 2, cls); paramci = nan (2, cls); return endif ## Check for identical data in X if (! isscalar (x) && max (abs (diff (x)) ./ x(2:end)) <= sqrt (eps)) paramhat = cast ([Inf, 0], cls); paramci = cast ([Inf, 0; Inf, 0], cls); return endif ## When CENSOR and FREQ are default if (all (censor == 0) && all (freq == 1)) ## Optimize with respect to log(a), since both A and B must be positive meanx = mean (x); x0 = 0; ## Minimize negative log-likelihood to estimate parameters f = @(loga) gamfit_search (loga, meanx, x); [loga, ~, err, output] = fminsearch (f, x0, options); ## Inverse log(a) a = exp (loga); b = meanx / a; paramhat = [a, b]; ## Handle errors if (err == 0) if (output.funcCount >= options.MaxFunEvals) warning (strcat ("gamfit: maximum number of function", ... " evaluations are exceeded.")); elseif (output.iterations >= options.MaxIter) warning ("gamfit: maximum number of iterations are exceeded."); endif elseif (err < 0) error ("gamfit: NoSolution."); endif endif ## No censoring if (all (censor == 0)) ## Scale data to allow parameter estimation ## for extremely large or small values scale = sum (freq .* x) / szx; ## Check for all data being ~zero if (scale < realmin (cls)) paramhat = cast ([NaN, 0], cls); paramci = cast ([NaN, 0; NaN, 0], cls); return endif scaledx = x / scale; ## Use Method of Moments for initial estimates meansqx = sum (freq .* (scaledx - 1) .^ 2) / szx; b = meansqx * szx / (szx - 1); a = 1 / b; ## Ensure that MLEs is possible, otherwise return initial estimates if (any (scaledx == 0)) paramhat = [a, b*scale]; paramci = nan (2, cls); warning ("gamfit: X contains zeros."); return ## Compute MLEs else ## Bracket the root of the scale parameter likelihood equation sumlogx = sum (freq .* log (scaledx)); bracket = sumlogx / szx; if (lkeqn (a, bracket) > 0) upper = a; lower = 0.5 * upper; while (lkeqn (lower, bracket) > 0) upper = lower; lower = 0.5 * upper; if (lower < realmin (cls)) error ("gamfit: no solution"); endif endwhile else lower = a; upper = 2 * lower; while (lkeqn (upper, bracket) < 0) lower = upper; upper = 2 * lower; if (upper > realmax (cls)) error ("gamfit: no solution"); endif endwhile endif bounds = [lower upper]; ## Find the root of the likelihood equation. opts = optimset ('fzero'); opts = optimset (opts, 'Display', 'off'); f = @(a) lkeqn (a, bracket); [a, lkeqnval, err] = fzero (f, bounds, opts); ## Rescale B paramhat = [a, (1/a)*scale]; endif ## With censoring else ## Get uncensored data notc = ! censor; xunc = x(notc); freq_notc = freq(notc); ## Ensure that MLEs is possible and get initial estimates xuncbar = sum (freq_notc .* xunc) / nunc; s2unc = sum (freq_notc .* (xunc - xuncbar) .^ 2) / nunc; if s2unc <= 100.*eps (xuncbar.^2) ## When all uncensored observations are equal and greater than all ## the censored observations, the likelihood surface becomes infinite if (max (xunc) == max (x)) paramhat = cast ([Inf, 0], cls); if (nunc > 1) paramci = cast ([Inf, 0; Inf, 0], cls); else paramci = cast ([0, 0; Inf, Inf], cls); endif return endif ## Set some default parameter estimates. x0 = [2, xuncbar./2]; else ## Fit a Weibull distribution and equate the parameter estimates ## into a Gamma distribution wblphat = wblfit (x, alpha, censor, freq); [m, v] = wblstat (wblphat(1), wblphat(2)); x0 = [m.*m./v, v./m]; endif ## Minimize negative log-likelihood to estimate parameters f = @(params) gamlike (params, x, censor, freq); [paramhat, ~, err, output] = fminsearch (f, x0, options); ## Force positive parameter values paramhat = abs (paramhat); ## Handle errors if (err == 0) if (output.funcCount >= options.MaxFunEvals) warning (strcat ("gamfit: maximum number of function", ... " evaluations are exceeded.")); elseif (output.iterations >= options.MaxIter) warning ("gamfit: maximum number of iterations are exceeded."); endif elseif (err < 0) error ("gamfit: no solution."); endif endif ## Compute CIs using a log normal approximation for parameters. if (nargout > 1) ## Compute asymptotic covariance [~, acov] = gamlike (paramhat, x, censor, freq); ## Get standard errors stderr = sqrt (diag (acov))'; stderr = stderr ./ paramhat; ## Apply log transform phatlog = log (paramhat); ## Compute normal quantiles z = probit (alpha / 2); ## Compute CI paramci = [phatlog; phatlog] + [stderr; stderr] .* [z, z; -z, -z]; ## Inverse log transform paramci = exp (paramci); endif endfunction ## Helper function so we only have to minimize for one variable. function nlogL = gamfit_search (loga, meanx, x) a = exp (loga); b = meanx / a; nlogL = gamlike ([a, b], x); endfunction ## Helper function for MLE with no censoring function v = lkeqn (a, bracket) v = -bracket - log (a) + psi (a); endfunction %!demo %! ## Sample 3 populations from different Gamma distributions %! randg ('state', 42); %! r1 = gamrnd (1, 2, 2000, 1); %! r2 = gamrnd (2, 2, 2000, 1); %! r3 = gamrnd (7.5, 1, 2000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, 75, 4); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! ylim ([0, 0.62]); %! xlim ([0, 12]); %! hold on %! %! ## Estimate their α and β parameters %! a_bA = gamfit (r(:,1)); %! a_bB = gamfit (r(:,2)); %! a_bC = gamfit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [0.01,0.1:0.2:18]; %! y = gampdf (x, a_bA(1), a_bA(2)); %! plot (x, y, '-pr'); %! y = gampdf (x, a_bB(1), a_bB(2)); %! plot (x, y, '-sg'); %! y = gampdf (x, a_bC(1), a_bC(2)); %! plot (x, y, '-^c'); %! hold off %! legend ({'Normalized HIST of sample 1 with α=1 and β=2', ... %! 'Normalized HIST of sample 2 with α=2 and β=2', ... %! 'Normalized HIST of sample 3 with α=7.5 and β=1', ... %! sprintf("PDF for sample 1 with estimated α=%0.2f and β=%0.2f", ... %! a_bA(1), a_bA(2)), ... %! sprintf("PDF for sample 2 with estimated α=%0.2f and β=%0.2f", ... %! a_bB(1), a_bB(2)), ... %! sprintf("PDF for sample 3 with estimated α=%0.2f and β=%0.2f", ... %! a_bC(1), a_bC(2))}) %! title ('Three population samples from different Gamma distributions') %! hold off ## Test output %!shared x %! x = [1.2 1.6 1.7 1.8 1.9 2.0 2.2 2.6 3.0 3.5 4.0 4.8 5.6 6.6 7.6]; %!test %! [paramhat, paramci] = gamfit (x); %! assert_equal (paramhat, [3.4248, 0.9752], 1e-4); %! assert_equal (paramci, [1.7287, 0.4670; 6.7852, 2.0366], 1e-4); %!test %! [paramhat, paramci] = gamfit (x, 0.01); %! assert_equal (paramhat, [3.4248, 0.9752], 1e-4); %! assert_equal (paramci, [1.3945, 0.3705; 8.4113, 2.5668], 1e-4); %!test %! freq = [1 1 1 1 2 1 1 1 1 2 1 1 1 1 2]; %! [paramhat, paramci] = gamfit (x, [], [], freq); %! assert_equal (paramhat, [3.3025, 1.0615], 1e-4); %! assert_equal (paramci, [1.7710, 0.5415; 6.1584, 2.0806], 1e-4); %!test %! [paramhat, paramci] = gamfit (x, [], [], [1:15]); %! assert_equal (paramhat, [4.4484, 0.9689], 1e-4); %! assert_equal (paramci, [3.4848, 0.7482; 5.6785, 1.2546], 1e-4); %!test %! [paramhat, paramci] = gamfit (x, 0.01, [], [1:15]); %! assert_equal (paramhat, [4.4484, 0.9689], 1e-4); %! assert_equal (paramci, [3.2275, 0.6899; 6.1312, 1.3608], 1e-4); %!test %! cens = [0 0 0 0 1 0 0 0 0 0 0 0 0 0 0]; %! [paramhat, paramci] = gamfit (x, [], cens, [1:15]); %! assert_equal (paramhat, [4.7537, 0.9308], 1e-4); %! assert_equal (paramci, [3.7123, 0.7162; 6.0872, 1.2097], 1e-4); %!test %! cens = [0 0 0 0 1 0 0 0 0 0 0 0 0 0 0]; %! freq = [1 1 1 1 2 1 1 1 1 2 1 1 1 1 2]; %! [paramhat, paramci] = gamfit (x, [], cens, freq); %! assert_equal (paramhat, [3.4736, 1.0847], 1e-4); %! assert_equal (paramci, [1.8286, 0.5359; 6.5982, 2.1956], 1e-4); ## Test edge cases %!test %! [paramhat, paramci] = gamfit ([1 1 1 1 1 1]); %! assert_equal (paramhat, [Inf, 0]); %! assert_equal (paramci, [Inf, 0; Inf, 0]); %!test %! [paramhat, paramci] = gamfit ([1 1 1 1 1 1], [], [1 1 1 1 1 1]); %! assert_equal (paramhat, [NaN, NaN]); %! assert_equal (paramci, [NaN, NaN; NaN, NaN]); %!test %! [paramhat, paramci] = gamfit ([1 1 1 1 1 1], [], [], [1 1 1 1 1 1]); %! assert_equal (paramhat, [Inf, 0]); %! assert_equal (paramci, [Inf, 0; Inf, 0]); ## Test class of input preserved %!assert_equal (class (gamfit (single (x))), "single") ## Test input validation %!error gamfit (ones (2)) %!error gamfit (x, 1) %!error gamfit (x, -1) %!error gamfit (x, {0.05}) %!error gamfit (x, 'a') %!error gamfit (x, i) %!error gamfit (x, [0.01 0.02]) %!error %! gamfit ([1 2 3], 0.05, [], [1 5]) %!error %! gamfit ([1 2 3], 0.05, [], [1 5 -1]) %!error ... %! gamfit ([1:10], 0.05, [], [], 5) %!error gamfit ([1 2 3 -4]) %!error ... %! gamfit ([1 2 0], [], [1 0 0]) statistics-release-1.9.2/inst/Distribution_Fitting/gamlike.m000066400000000000000000000247041524624707500242650ustar00rootroot00000000000000## Copyright (C) 2022-2024 Andreas Bertsatos ## Based on previous work by Martijn van Oosterhout ## originally granted to the public domain. ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} gamlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} gamlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} gamlike (@var{params}, @var{x}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} gamlike (@var{params}, @var{x}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the Gamma distribution. ## ## @code{@var{nlogL} = gamlike (@var{params}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the Gamma distribution ## with (1) shape parameter @var{a} and (2) scale parameter @var{b} given in the ## two-element vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = gamlike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{acov} are their asymptotic variances. ## ## @code{[@dots{}] = gamlike (@var{params}, @var{x}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = gamlike (@var{params}, @var{x}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## OCTAVE/MATLAB use the alternative parameterization given by the pair ## @math{α, β}, i.e. shape @var{a} and scale @var{b}. In Wikipedia, the two ## common parameterizations use the pairs @math{k, θ}, as shape and scale, and ## @math{α, β}, as shape and rate, respectively. The parameter names @var{a} ## and @var{b} used here (for MATLAB compatibility) correspond to the parameter ## notation @math{k, θ} instead of the @math{α, β} as reported in Wikipedia. ## ## Further information about the Gamma distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gamma_distribution} ## ## @seealso{gamcdf, gampdf, gaminv, gamrnd, gamfit} ## @end deftypefn function [nlogL, acov] = gamlike (params, x, censor, freq) ## Check input arguments and add defaults if (nargin < 2) error ("gamlike: function called with too few input arguments."); endif if (numel (params) != 2) error ("gamlike: wrong parameters length."); endif if (! isvector (x)) error ("gamlike: X must be a vector."); endif if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("gamlike: X and CENSOR vectors mismatch."); endif if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (isequal (size (x), size (freq))) nulls = find (freq == 0); if (numel (nulls) > 0) x(nulls) = []; censor(nulls) = []; freq(nulls) = []; endif else error ("gamlike: X and FREQ vectors mismatch."); endif ## Get K and THETA values a = params(1); b = params(2); ## Parameters K and THETA must be positive, otherwise make them NaN a(a <= 0) = NaN; b(b <= 0) = NaN; ## Data in X must be positive, otherwise make it NaN x(x <= 0) = NaN; ## Compute the individual log-likelihood terms z = x ./ b; L = (a - 1) .* log (z) - z - gammaln (a) - log (b); n_censored = sum (freq .* censor); if (n_censored > 0) z_censored = z(logical (censor)); Scen = gammainc (z_censored, a, 'upper'); L(logical (censor)) = log (Scen); endif ## Force a log(0)==-Inf for X from extreme right tail L(z == Inf) = -Inf; ## Neg-log-likelihood is the sum of the individual contributions nlogL = -sum (freq .* L); ## Compute the negative hessian at the parameter values. ## Invert to get the observed information matrix. if (nargout == 2) ## Calculate all data dL11 = -psi (1, a) * ones (size (z), 'like', z); dL12 = -(1 ./ b) * ones (size (z), 'like', z); dL22 = -(2 .* z - a) ./ (b .^ 2); ## Calculate censored data if (n_censored > 0) ## Compute derivatives [y, dy, d2y] = dgammainc (z_censored, a); dlnS = dy ./ y; d2lnS = d2y ./ y - dlnS.*dlnS; #[dlnS,d2lnS] = dlngamsf(z_censored,a); logzcen = log (z_censored); tmp = exp (a .* logzcen - z_censored - gammaln (a) - log (b)) ./ Scen; dL11(logical (censor)) = d2lnS; dL12(logical (censor)) = tmp .* (logzcen - dlnS - psi (0,a)); dL22(logical (censor)) = tmp .* ((z_censored-1-a)./b - tmp); endif nH11 = -sum (freq .* dL11); nH12 = -sum (freq .* dL12); nH22 = -sum (freq .* dL22); nH = [nH11 nH12; nH12 nH22]; if (any (isnan (nH(:)))) acov = nan (2, 'like', nH); else acov = inv (nH); endif endif endfunction ## Compute the incomplete Gamma function with its 1st and 2nd derivatives function [y, dy, d2y] = dgammainc (x, a) ## Initialize return variables y = nan (size (x)); dy = y; d2y = y; ## Use approximation for K > 2^20 ulim = 2^20; is_lim = find (a > ulim); if (! isempty (is_lim)) x(is_lim) = max (ulim - 1/3 + sqrt (ulim ./ a(is_lim)) .* ... (x(is_lim) - (a(is_lim) - 1/3)), 0); a(is_lim) = ulim; endif ## For x < a+1 is_lo = find (x < a + 1 & x != 0); if (! isempty (is_lo)) x_lo = x(is_lo); k_lo = a(is_lo); k_1 = k_lo; step = 1; d1st = 0; d2st = 0; stsum = step; d1sum = d1st; d2sum = d2st; while norm (step, 'inf') >= 100 * eps (norm (stsum, 'inf')) k_1 += 1; step = step .* x_lo ./ k_1; d1st = (d1st .* x_lo - step) ./ k_1; d2st = (d2st .* x_lo - 2 .* d1st) ./ k_1; stsum = stsum + step; d1sum = d1sum + d1st; d2sum = d2sum + d2st; endwhile fklo = exp (-x_lo + k_lo .* log (x_lo) - gammaln (k_lo + 1)); y_lo = fklo .* stsum; ## Fix very small a y_lo(x_lo > 0 & y_lo > 1) = 1; ## Compute 1st derivative dlogfklo = (log (x_lo) - psi (k_lo + 1)); d1fklo = fklo .* dlogfklo; d1y_lo = d1fklo .* stsum + fklo .* d1sum; ## Compute 2nd derivative d2fklo = d1fklo .* dlogfklo - fklo .* psi (1, k_lo + 1); d2y_lo = d2fklo .* stsum + 2 .* d1fklo .* d1sum + fklo .* d2sum; ## Considering the upper tail y(is_lo) = 1 - y_lo; dy(is_lo) = -d1y_lo; d2y(is_lo) = -d2y_lo; endif ## For x >= a+1 is_hi = find (x >= a+1); if (! isempty (is_hi)) x_hi = x(is_hi); k_hi = a(is_hi); zc = 0; k0 = 0; k1 = k_hi; x0 = 1; x1 = x_hi; d1k0 = 0; d1k1 = 1; d1x0 = 0; d1x1 = 0; d2k0 = 0; d2k1 = 0; d2x0 = 0; d2x2 = 0; kx = k_hi ./ x_hi; d1kx = 1 ./ x_hi; d2kx = 0; start = 1; while norm (d2kx - start, 'Inf') > 100 * eps (norm (d2kx, 'Inf')) rescale = 1 ./ x1; zc += 1; n_k = zc - k_hi; d2k0 = (d2k1 + d2k0 .* n_k - 2 .* d1k0) .* rescale; d2x0 = (d2x2 + d2x0 .* n_k - 2 .* d1x0) .* rescale; d1k0 = (d1k1 + d1k0 .* n_k - k0) .* rescale; d1x0 = (d1x1 + d1x0 .* n_k - x0) .* rescale; k0 = (k1 + k0 .* n_k) .* rescale; x0 = 1 + (x0 .* n_k) .* rescale; nrescale = zc .* rescale; d2k1 = d2k0 .* x_hi + d2k1 .* nrescale; d2x2 = d2x0 .* x_hi + d2x2 .* nrescale; d1k1 = d1k0 .* x_hi + d1k1 .* nrescale; d1x1 = d1x0 .* x_hi + d1x1 .* nrescale; k1 = k0 .* x_hi + k1 .* nrescale; x1 = x0 .* x_hi + zc; start = d2kx; kx = k1 ./ x1; d1kx = (d1k1 - kx .* d1x1) ./ x1; d2kx = (d2k1 - d1kx .* d1x1 - kx .* d2x2 - d1kx .* d1x1) ./ x1; endwhile fkhi = exp (-x_hi + k_hi .* log (x_hi) - gammaln (k_hi + 1)); y_hi = fkhi .* kx; ## Compute 1st derivative dlogfkhi = (log (x_hi) - psi (k_hi + 1)); d1fkhi = fkhi .* dlogfkhi; d1y_hi = d1fkhi .* kx + fkhi .* d1kx; ## Compute 2nd derivative d2fkhi = d1fkhi .* dlogfkhi - fkhi .* psi (1, k_hi + 1); d2y_hi = d2fkhi .* kx + 2 .* d1fkhi .* d1kx + fkhi .* d2kx; ## Considering the upper tail y(is_hi) = y_hi; dy(is_hi) = d1y_hi; d2y(is_hi) = d2y_hi; endif ## Handle x == 0 is_x0 = find (x == 0); if (! isempty (is_x0)) ## Considering the upper tail y(is_x0) = 1; dy(is_x0) = 0; d2y(is_x0) = 0; endif ## Handle a == 0 is_k0 = find (a == 0); if (! isempty (is_k0)) is_k0x0 = find (a == 0 & x == 0); ## Considering the upper tail y(is_k0) = 0; dy(is_k0x0) = Inf; d2y(is_k0x0) = -Inf; endif endfunction ## Test output %!test %! [nlogL, acov] = gamlike ([2, 3], [2, 3, 4, 5, 6, 7, 8, 9]); %! assert_equal (nlogL, 19.4426, 1e-4); %! assert_equal (acov, [2.7819, -5.0073; -5.0073, 9.6882], 1e-4); %!test %! [nlogL, acov] = gamlike ([2, 3], [5:45]); %! assert_equal (nlogL, 305.8070, 1e-4); %! assert_equal (acov, [0.0423, -0.0087; -0.0087, 0.0167], 1e-4); %!test %! [nlogL, acov] = gamlike ([2, 13], [5:45]); %! assert_equal (nlogL, 163.2261, 1e-4); %! assert_equal (acov, [0.2362, -1.6631; -1.6631, 13.9440], 1e-4); ## Test input validation %!error ... %! gamlike ([12, 15]) %!error gamlike ([12, 15, 3], [1:50]) %!error gamlike ([12, 3], ones (10, 2)) %!error ... %! gamlike ([12, 15], [1:50], [1, 2, 3]) %!error ... %! gamlike ([12, 15], [1:50], [], [1, 2, 3]) statistics-release-1.9.2/inst/Distribution_Fitting/geofit.m000066400000000000000000000131571524624707500241310ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{pshat} =} geofit (@var{x}) ## @deftypefnx {statistics} {[@var{pshat}, @var{psci}] =} geofit (@var{x}) ## @deftypefnx {statistics} {[@var{pshat}, @var{psci}] =} geofit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@var{pshat}, @var{psci}] =} geofit (@var{x}, @var{alpha}, @var{freq}) ## ## Estimate parameter and confidence intervals for the geometric distribution. ## ## @code{@var{pshat} = geofit (@var{x})} returns the maximum likelihood estimate ## (MLE) of the probability of success for the geometric distribution. @var{x} ## must be a vector. ## ## @code{[@var{pshat}, @var{psci}] = geofit (@var{x}, @var{alpha})} also returns ## the @qcode{100 * (1 - @var{alpha})} percent confidence intervals of the ## estimated parameter. By default, the optional argument @var{alpha} is 0.05 ## corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = geofit (@var{x}, @var{alpha}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## typically contains integer frequencies for the corresponding elements in ## @var{x}, but it can contain any non-integer non-negative values. By default, ## or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## The geometric distribution models the number of failures (@var{x}) of a ## Bernoulli trial with probability @var{ps} before the first success. ## ## Further information about the geometric distribution can be found at ## @url{https://en.wikipedia.org/wiki/Geometric_distribution} ## ## @seealso{geocdf, geoinv, geopdf, geornd, geostat} ## @end deftypefn function [pshat, psci] = geofit (x, alpha, freq) ## Check input arguments if (nargin < 1) error ("geofit: function called with too few input arguments."); endif ## Check data in X if (any (x < 0)) error ("geofit: X cannot have negative values."); endif if (! isvector (x)) error ("geofit: X must be a vector."); endif ## Check ALPHA if (nargin < 2 || isempty (alpha)) alpha = 0.05; elseif (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("geofit: wrong value for ALPHA."); endif ## Check frequency vector if (nargin < 3 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("geofit: X and FREQ vector mismatch."); endif ## Expand frequency and censor vectors (if necessary) if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; freq = ones (size (x)); endif ## Compute PS estimate pshat = 1 ./ (1 + mean (x)); ## Compute confidence interval of PS if (nargout > 1) sz = numel (x); serr = pshat .* sqrt ((1 - pshat) ./ sz); psci = norminv ([alpha/2; 1-alpha/2], [pshat; pshat], [serr; serr]); endif endfunction %!demo %! ## Sample 2 populations from different geometric distributions %! rande ('state', 42); %! r1 = geornd (0.15, 1000, 1); %! r2 = geornd (0.5, 1000, 1); %! r = [r1, r2]; %! %! ## Plot them normalized and fix their colors %! hist (r, 0:0.5:20.5, 1); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! hold on %! %! ## Estimate their probability of success %! pshatA = geofit (r(:,1)); %! pshatB = geofit (r(:,2)); %! %! ## Plot their estimated PDFs %! x = [0:15]; %! y = geopdf (x, pshatA); %! plot (x, y, '-pg'); %! y = geopdf (x, pshatB); %! plot (x, y, '-sc'); %! xlim ([0, 15]) %! ylim ([0, 0.6]) %! legend ({'Normalized HIST of sample 1 with ps=0.15', ... %! 'Normalized HIST of sample 2 with ps=0.50', ... %! sprintf("PDF for sample 1 with estimated ps=%0.2f", ... %! mean (pshatA)), ... %! sprintf("PDF for sample 2 with estimated ps=%0.2f", ... %! mean (pshatB))}) %! title ('Two population samples from different geometric distributions') %! hold off ## Test output %!test %! x = 0:5; %! [pshat, psci] = geofit (x); %! assert_equal (pshat, 0.2857, 1e-4); %! assert_equal (psci, [0.092499; 0.478929], 1e-5); %!test %! x = 0:5; %! [pshat, psci] = geofit (x, [], [1 1 1 1 1 1]); %! assert_equal (pshat, 0.2857, 1e-4); %! assert_equal (psci, [0.092499; 0.478929], 1e-5); %!assert_equal (geofit ([1 1 2 3]), geofit ([1 2 3], [] ,[2 1 1])) ## Test input validation %!error geofit () %!error geofit (-1, [1 2 3 3]) %!error geofit (1, 0) %!error geofit (1, 1.2) %!error geofit (1, [0.02 0.05]) %!error ... %! geofit ([1.5, 0.2], [], [0, 0, 0, 0, 0]) %!error ... %! geofit ([1.5, 0.2], [], [1, 1, 1]) statistics-release-1.9.2/inst/Distribution_Fitting/gevfit.m000066400000000000000000000274751524624707500241500ustar00rootroot00000000000000## Copyright (C) 2012-2021 Nir Krakauer ## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} gevfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} gevfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} gevfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} gevfit (@var{x}, @var{alpha}, @var{freq}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} gevfit (@var{x}, @var{alpha}, @var{options}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} gevfit (@var{x}, @var{alpha}, @var{freq}, @var{options}) ## ## Estimate parameters and confidence intervals for the generalized extreme ## value (GEV) distribution. ## ## @code{@var{paramhat} = gevfit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the GEV distribution given the data in ## @var{x}. @qcode{@var{paramhat}(1)} is the shape parameter, @var{k}, and ## @qcode{@var{paramhat}(2)} is the scale parameter, @var{sigma}, and ## @qcode{@var{paramhat}(3)} is the location parameter, @var{mu}. ## ## @code{[@var{paramhat}, @var{paramci}] = gevfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = gevfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = gevfit (@var{params}, @var{x}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## must contain non-negative integer frequencies for the corresponding elements ## in @var{x}. By default, or if left empty, ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@var{paramhat}, @var{paramci}] = gevfit (@var{x}, @var{alpha}, ## @var{options})} specifies control parameters for the iterative algorithm used ## to compute ML estimates with the @code{fminsearch} function. @var{options} ## is a structure with the following fields and their default values: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## When @qcode{@var{k} < 0}, the GEV is the type III extreme value distribution. ## When @qcode{@var{k} > 0}, the GEV distribution is the type II, or Frechet, ## extreme value distribution. If @var{W} has a Weibull distribution as ## computed by the @code{wblcdf} function, then @qcode{-@var{W}} has a type III ## extreme value distribution and @qcode{1/@var{W}} has a type II extreme value ## distribution. In the limit as @var{k} approaches @qcode{0}, the GEV is the ## mirror image of the type I extreme value distribution as computed by the ## @code{evcdf} function. ## ## The mean of the GEV distribution is not finite when @qcode{@var{k} >= 1}, and ## the variance is not finite when @qcode{@var{k} >= 1/2}. The GEV distribution ## has positive density only for values of @var{x} such that ## @qcode{@var{k} * (@var{x} - @var{mu}) / @var{sigma} > -1}. ## ## Further information about the generalized extreme value distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution} ## ## @seealso{gevcdf, gevinv, gevpdf, gevrnd, gevlike, gevstat} ## @end deftypefn function [paramhat, paramci] = gevfit (x, alpha, varargin) ## Check X is vector if (! isvector (x)) error ("gevfit: X must be a vector."); endif ## Get X type and convert to double for computation is_type = class (x); if (strcmpi (is_type, 'single')) x = double (x); endif ## Check that X is not constant and does not contain NaNs sample_size = length (x); if (sample_size == 0 || any (isnan (x))) paramhat = NaN (1,3, is_type); paramci = NaN (2,3, is_type); warning ("gevfit: X contains NaNs."); return elseif (numel (unique (x)) == 1) paramhat = cast ([0, 0, unique(x)], is_type); if (length (x) == 1) paramci = cast ([-Inf, 0, -Inf; Inf, Inf, Inf], is_type); else paramci = [paramhat; paramhat]; endif warning ("gevfit: X is a constant vector."); return endif ## Check ALPHA if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("gevfit: wrong value for ALPHA."); endif endif ## Add defaults freq = []; options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; ## Check extra arguments for FREQ vector and/or 'options' structure if (nargin > 2) if (numel (varargin) == 1 && isstruct (varargin{1})) options = varargin{1}; elseif (numel (varargin) == 1 && isnumeric (varargin{1})) freq = varargin{1}; elseif (numel (varargin) == 2) freq = varargin{1}; options = varargin{2}; endif if (isempty (freq)) freq = ones (size (x)); endif ## Check for valid freq vector if (! isequal (size (x), size (freq))) error ("gevfit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("gevfit: FREQ must not contain negative values."); elseif (any (fix (freq) != freq)) error ("gevfit: FREQ must contain integer values."); endif ## Check for valid options structure if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("gevfit: 'options' argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Expand frequency if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; endif ## Force to column vector x = x(:); ## Compute initial parameters F = (0.5:1:(sample_size - 0.5))' ./ sample_size; k_0 = fminsearch (@(k) 1 - corr (x, gevinv (F, k, 1, 0)), 0); paramguess = [k_0, polyfit(gevinv(F,k_0,1,0),x',1)]; ## Check if x support initial parameters or fall back to unbounded evfit if (k_0 < 0 && (max (x) > - paramguess(2) / k_0 + paramguess(3)) || ... k_0 > 0 && (min (x) < - paramguess(2) / k_0 + paramguess(3))) paramguess = [evfit(x), 0]; paramguess = flip (paramguess); endif ## Minimize the negative log-likelihood according to initial parameters paramguess(2) = log (paramguess(2)); fhandle = @(paramguess) nll (paramguess, x); [paramhat, ~, exitflag, output] = fminsearch (fhandle, paramguess, options); paramhat(2) = exp (paramhat(2)); ## Display errors and warnings if any if (exitflag == 0) if (output.funcCount >= output.iterations) warning ("gevfit: maximum number of evaluations reached"); else warning ("gevfit: reached iteration limit"); endif elseif (exitflag == -1) error ("gevfit: No solution"); endif ## Return a row vector for Matlab compatibility paramhat = paramhat(:)'; ## Check for second output argument if (nargout > 1) [~, acov] = gevlike (paramhat, x); param_se = sqrt (diag (acov))'; if (any (iscomplex (param_se))) warning (strcat ("gevfit: Fisher information matrix not positive", ... " definite; parameter optimization likely did not", ... " converge")); paramci = NaN (2, 3, is_type); else p_vals = [alpha/2; 1-alpha/2]; k_ci = norminv (p_vals, paramhat(1), param_se(1)); s_ci = exp (norminv (p_vals, log (paramhat(2)), param_se(2) ./ paramhat(2))); m_ci = norminv (p_vals, paramhat(3), param_se(3)); paramci = [k_ci, s_ci, m_ci]; endif endif endfunction ## Negative log-likelihood for the GEV (log(sigma) parameterization) function out = nll (parms, x) k_0 = parms(1); log_sigma = parms(2); sigma = exp (log_sigma); mu = parms(3); n = numel (x); z = (x - mu) ./ sigma; if abs (k_0) > eps u = 1 + k_0.*z; if min (u) > 0 lnu = log1p (k_0 .* z); out = n * log_sigma + sum (exp ((-1 / k_0) * lnu)) + ... (1 + 1 / k_0) * sum (lnu); else out = Inf; endif else out = n * log_sigma + sum (exp (-z) + z); endif endfunction %!demo %! ## Sample 2 populations from 2 different exponential distributions %! rng (42); %! r1 = gevrnd (-0.5, 1, 2, 5000, 1); %! r2 = gevrnd (0, 1, -4, 5000, 1); %! r = [r1, r2]; %! %! ## Plot them normalized and fix their colors %! hist (r, 50, 5); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! hold on %! %! ## Estimate their k, sigma, and mu parameters %! k_sigma_muA = gevfit (r(:,1)); %! k_sigma_muB = gevfit (r(:,2)); %! %! ## Plot their estimated PDFs %! x = [-10:0.5:20]; %! y = gevpdf (x, k_sigma_muA(1), k_sigma_muA(2), k_sigma_muA(3)); %! plot (x, y, '-pr'); %! y = gevpdf (x, k_sigma_muB(1), k_sigma_muB(2), k_sigma_muB(3)); %! plot (x, y, '-sg'); %! ylim ([0, 0.7]) %! xlim ([-7, 5]) %! legend ({'Normalized HIST of sample 1 with k=-0.5, σ=1, μ=2', ... %! 'Normalized HIST of sample 2 with k=0, σ=1, μ=-4', %! sprintf("PDF for sample 1 with estimated k=%0.2f, σ=%0.2f, μ=%0.2f", ... %! k_sigma_muA(1), k_sigma_muA(2), k_sigma_muA(3)), ... %! sprintf("PDF for sample 3 with estimated k=%0.2f, σ=%0.2f, μ=%0.2f", ... %! k_sigma_muB(1), k_sigma_muB(2), k_sigma_muB(3))}) %! title ('Two population samples from different exponential distributions') %! hold off ## Test output %!test %! x = 1:50; %! [pfit, pci] = gevfit (x); %! pfit_out = [-0.4407, 15.1923, 21.5309]; %! pci_out = [-0.7532, 11.5878, 16.5686; -0.1282, 19.9183, 26.4926]; %! assert_equal (pfit, pfit_out, 1e-3); %! assert_equal (pci, pci_out, 1e-3); %!test %! x = 1:2:50; %! [pfit, pci] = gevfit (x); %! pfit_out = [-0.4434, 15.2024, 21.0532]; %! pci_out = [-0.8904, 10.3439, 14.0168; 0.0035, 22.3429, 28.0896]; %! assert_equal (pfit, pfit_out, 1e-3); %! assert_equal (pci, pci_out, 1e-3); ## Test input validation %!error gevfit (ones (2,5)); %!error gevfit ([1, 2, 3, 4, 5], 1.2); %!error gevfit ([1, 2, 3, 4, 5], 0); %!error gevfit ([1, 2, 3, 4, 5], 'alpha'); %!error ... %! gevfit ([1, 2, 3, 4, 5], 0.05, [1, 2, 3, 2]); %!error ... %! gevfit ([1, 2, 3, 4, 5], 0.05, [1, 2, 3, 2, -1]); %!error ... %! gevfit ([1, 2, 3, 4, 5], 0.05, [1, 2, 3, 2, 1.5]); %!error ... %! gevfit ([1, 2, 3, 4, 5], 0.05, struct ('option', 234)); %!error ... %! gevfit ([1, 2, 3, 4, 5], 0.05, ones (1,5), struct ('option', 234)); statistics-release-1.9.2/inst/Distribution_Fitting/gevfit_lmom.m000066400000000000000000000067211524624707500251630ustar00rootroot00000000000000## Copyright (C) 2012 Nir Krakauer ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{paramhat}, @var{paramci}] =} gevfit_lmom (@var{data}) ## ## Find an estimator (@var{paramhat}) of the generalized extreme value (GEV) ## distribution fitting @var{data} using the method of L-moments. ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{data} is the vector of given values. ## @end itemize ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{paramhat} is the 3-parameter maximum-likelihood parameter vector ## [@var{k}; @var{sigma}; @var{mu}], where @var{k} is the shape parameter of the ## GEV distribution, @var{sigma} is the scale parameter of the GEV distribution, ## and @var{mu} is the location parameter of the GEV distribution. ## @item ## @var{paramci} has the approximate 95% confidence intervals of the parameter ## values (currently not implemented). ## ## @end itemize ## ## @subheading Examples ## ## @example ## @group ## data = gevrnd (0.1, 1, 0, 100, 1); ## [pfit, pci] = gevfit_lmom (data); ## p1 = gevcdf (data,pfit(1),pfit(2),pfit(3)); ## [f, x] = ecdf (data); ## plot(data, p1, 's', x, f) ## @end group ## @end example ## @seealso{gevfit} ## @subheading References ## ## @enumerate ## @item ## Ailliot, P.; Thompson, C. & Thomson, P. Mixed methods for fitting the GEV ## distribution, Water Resources Research, 2011, 47, W05551 ## ## @end enumerate ## @end deftypefn function [paramhat, paramci] = gevfit_lmom (data) # Check arguments if (nargin < 1) print_usage; endif # find the L-moments data = sort (data(:))'; n = numel (data); L1 = mean (data); L2 = sum (data .* (2*(1:n) - n - 1)) / (2*nchoosek (n, 2)); # or mean(triu(data' - data, 1, 'pack')) / 2; b = bincoeff ((1:n) - 1, 2); L3 = sum (data .* (b - 2 * ((1:n) - 1) .* (n - (1:n)) + fliplr (b))) / (3*nchoosek (n, 3)); #match the moments to the GEV distribution #first find k based on L3/L2 f = @(k) (L3/L2 + 3)/2 - limdiv ((1 - 3^(k)), (1 - 2^(k))); k = fzero (f, 0); #next find sigma and mu given k if abs (k) < 1E-8 sigma = L2 / log (2); eg = 0.57721566490153286; %Euler-Mascheroni constant mu = L1 - sigma * eg; else sigma = -k*L2 / (gamma (1 - k) * (1 - 2^(k))); mu = L1 - sigma * ((gamma (1 - k) - 1) / k); endif paramhat = [k; sigma; mu]; if nargout > 1 paramci = NaN; endif endfunction #internal function to accurately evaluate (1 - 3^k)/(1 - 2^k) in the limit as k --> 0 function c = limdiv (a, b) # c = ifelse (abs(b) < 1E-8, log(3)/log(2), a ./ b); if abs (b) < 1E-8 c = log (3)/log (2); else c = a / b; endif endfunction %!xtest <31070> %! data = 1:50; %! [pfit, pci] = gevfit_lmom (data); %! expected_p = [-0.28 15.01 20.22]'; %! assert_equal (pfit, expected_p, 0.1); statistics-release-1.9.2/inst/Distribution_Fitting/gevlike.m000066400000000000000000000354641524624707500243070ustar00rootroot00000000000000## Copyright (C) 2012 Nir Krakauer ## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} gevlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} gevlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} gevlike (@var{params}, @var{x}, @var{freq}) ## ## Negative log-likelihood for the generalized extreme value (GEV) distribution. ## ## @code{@var{nlogL} = gevlike (@var{params}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the GEV distribution ## with (1) shape parameter @var{k}, (2) scale parameter @var{sigma}, and (3) ## location parameter @var{mu} given in the three-element vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = gevlike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{acov} are their asymptotic variances. ## ## @code{[@dots{}] = gevlike (@var{params}, @var{x}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## must contain non-negative integer frequencies for the corresponding elements ## in @var{x}. By default, or if left empty, ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## When @qcode{@var{k} < 0}, the GEV is the type III extreme value distribution. ## When @qcode{@var{k} > 0}, the GEV distribution is the type II, or Frechet, ## extreme value distribution. If @var{W} has a Weibull distribution as ## computed by the @code{wblcdf} function, then @qcode{-@var{W}} has a type III ## extreme value distribution and @qcode{1/@var{W}} has a type II extreme value ## distribution. In the limit as @var{k} approaches @qcode{0}, the GEV is the ## mirror image of the type I extreme value distribution as computed by the ## @code{evcdf} function. ## ## @strong{MATLAB compatibility.} At exactly @qcode{@var{k} = 0} the returned ## @var{ACOV} deviates from MATLAB's deliberately. Both implementations agree ## to thirteen digits for any @qcode{@var{k} != 0}, and both their ## @qcode{@var{k} != 0} expressions converge on the values returned here as ## @var{k} approaches @qcode{0}; MATLAB's own @qcode{@var{k} = 0} branch ## returns something else entirely, which its @qcode{@var{k} != 0} branch ## therefore contradicts. The Gumbel-limit expressions used here are the ## limits of the general ones, so @var{ACOV} is continuous at @qcode{0}. This ## is independent of the sample size. ## ## The mean of the GEV distribution is not finite when @qcode{@var{k} >= 1}, and ## the variance is not finite when @qcode{@var{k} >= 1/2}. The GEV distribution ## has positive density only for values of @var{x} such that ## @qcode{@var{k} * (@var{x} - @var{mu}) / @var{sigma} > -1}. ## ## Further information about the generalized extreme value distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution} ## ## @seealso{gevcdf, gevinv, gevpdf, gevrnd, gevfit, gevstat} ## @end deftypefn function [nlogL, acov] = gevlike (params, x, freq) ## Check input arguments if (nargin < 2) error ("gevlike: function called with too few input arguments."); endif if (! isvector (x)) error ("gevlike: X must be a vector."); endif if (length (params) != 3) error ("gevlike: PARAMS must be a three-element vector."); endif if (nargin < 3 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("gevlike: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("gevlike: FREQ must not contain negative values."); elseif (any (fix (freq) != freq)) error ("gevlike: FREQ must contain integer values."); endif ## Expand frequency if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; endif k = params(1); sigma = params(2); mu = params(3); ## Calculate negative log likelihood [nll, k_terms] = gevnll (x, k, sigma, mu); nlogL = sum (nll(:)); ## Optionally calculate the first and second derivatives of the negative log ## likelihood with respect to parameters if (nargout > 1) [Grad, kk_terms] = gevgrad (x, k, sigma, mu, k_terms); FIM = gevfim (x, k, sigma, mu, k_terms, kk_terms); acov = inv (FIM); endif endfunction ## Internal function to calculate negative log likelihood for gevlike function [nlogL, k_terms] = gevnll (x, k, sigma, mu) k_terms = []; a = (x - mu) ./ sigma; if (all (k == 0)) nlogL = exp (-a) + a + log (sigma); else aa = k .* a; ## Use a series expansion to find the log likelihood more accurately ## when k is small if (min (abs (aa)) < 1E-3 && max (abs (aa)) < 0.5) k_terms = 1; sgn = 1; i = 0; while 1 sgn = -sgn; i++; newterm = (sgn / (i + 1)) * (aa .^ i); k_terms = k_terms + newterm; if (max (abs (newterm)) <= eps) break endif endwhile nlogL = exp (-a .* k_terms) + a .* (k + 1) .* k_terms + log (sigma); else b = 1 + aa; nlogL = b .^ (-1 ./ k) + (1 + 1 ./ k) .* log (b) + log (sigma); nlogL(b <= 0) = Inf; endif endif endfunction ## Calculate the gradient of the negative log likelihood of x with respect ## to the parameters of the generalized extreme value distribution for gevlike function [G, kk_terms] = gevgrad (x, k, sigma, mu, k_terms) kk_terms = []; G = ones (3, 1); ## Use the expressions for first derivatives that are the limits as k --> 0 if (k == 0) a = (x - mu) ./ sigma; f = exp (-a) - 1; ## k g = a .* (1 + a .* f / 2); G(1) = sum (g(:)); ## sigma g = (a .* f + 1) ./ sigma; G(2) = sum (g(:)); ## mu g = f ./ sigma; G(3) = sum (g(:)); return endif a = (x - mu) ./ sigma; b = 1 + k .* a; ## Negative log likelihood is locally infinite if (any (b <= 0)) G(:) = 0; return endif ## k c = log (b); d = 1 ./ k + 1; ## Use a series expansion to find the gradient more accurately when k is small if (nargin > 4 && ! isempty (k_terms)) aa = k .* a; f = exp (-a .* k_terms); kk_terms = 0.5; sgn = 1; i = 0; while 1 sgn = -sgn; i++; newterm = (sgn * (i + 1) / (i + 2)) * (aa .^ i); kk_terms = kk_terms + newterm; if (max (abs (newterm)) <= eps) break endif endwhile g = a .* ((a .* kk_terms) .* (f - 1 - k) + k_terms); else g = (c ./ k - a ./ b) ./ (k .* b .^ (1/k)) - c ./ (k .^ 2) + a .* d ./ b; endif G(1) = sum (g(:)); ## sigma ## Use a series expansion to find the gradient more accurately when k is small if nargin > 4 && ! isempty (k_terms) g = (1 - a .* (a .* k .* kk_terms - k_terms) .* (f - k - 1)) ./ sigma; else g = (a .* b .^ (-d) - (k + 1) .* a ./ b + 1) ./ sigma; endif G(2) = sum (g(:)); ## mu ## Use a series expansion to find the gradient more accurately when k is small if (nargin > 4 && ! isempty (k_terms)) g = - (a .* k .* kk_terms - k_terms) .* (f - k - 1) ./ sigma; else g = (b .^ (-d) - (k + 1) ./ b) ./ sigma; endif G(3) = sum (g(:)); endfunction ## Internal function to calculate the Fisher information matrix for gevlike function ACOV = gevfim (x, k, sigma, mu, k_terms, kk_terms) ACOV = ones (3); ## Use the expressions for second derivatives that are the limits as k --> 0. ## MATLAB's k == 0 branch returns a different matrix, and is wrong: walking ## either library's k != 0 expressions toward zero converges on the values ## below, MATLAB's included. Deliberate deviation, see the docstring. if (k == 0) ## k, k a = (x - mu) ./ sigma; f = exp (-a); der = (a .^ 2) .* (a .* (a/4 - 2/3) .* f + 2/3 * a - 1); ACOV(1, 1) = sum (der(:)); ## sigma, sigma der = (sigma .^ -2) .* (a .* ((a - 2) .* f + 2) - 1); ACOV(2, 2) = sum (der(:)); ## mu, mu der = (sigma .^ -2) .* f; ACOV(3, 3) = sum (der(:)); ## k, sigma der = (-a ./ sigma) .* (a .* (1 - a/2) .* f - a + 1); ACOV(1, 2) = ACOV(2, 1) = sum (der(:)); ## k, mu der = (-1 ./ sigma) .* (a .* (1 - a/2) .* f - a + 1); ACOV(1, 3) = ACOV(3, 1) = sum (der(:)); ## sigma, mu der = (1 + (a - 1) .* f) ./ (sigma .^ 2); ACOV(2, 3) = ACOV(3, 2) = sum (der(:)); return endif ## General case z = 1 + k .* (x - mu) ./ sigma; ## k, k a = (x - mu) ./ sigma; b = k .* a + 1; c = log (b); d = 1 ./ k + 1; ## Use a series expansion to find the derivatives more accurately ## when k is small if (nargin > 5 && ! isempty (kk_terms)) aa = k .* a; f = exp (-a .* k_terms); kkk_terms = 2/3; sgn = 1; i = 0; while 1 sgn = -sgn; i++; newterm = (sgn * (i + 1) * (i + 2) / (i + 3)) * (aa .^ i); kkk_terms = kkk_terms + newterm; if (max (abs (newterm)) <= eps) break endif endwhile der = (a .^ 2) .* (a .* (a .* kk_terms .^ 2 - kkk_terms) .* ... f + a .* (1 + k) .* kkk_terms - 2 * kk_terms); else der = ((((c ./ k.^2) - (a ./ (k .* b))) .^ 2) ./ (b .^ (1 ./ k))) + ... ((-2*c ./ k.^3) + (2*a ./ (k.^2 .* b)) + ((a ./ b) .^ 2 ./ k)) ./ ... (b .^ (1 ./ k)) + 2*c ./ k.^3 - (2*a ./ (k.^2 .* b)) - (d .* (a ./ b) .^ 2); endif der(z <= 0) = 0; # no probability mass in this region ACOV(1, 1) = sum (der(:)); ## sigma, sigma ## Use a series expansion to find the derivatives more accurately ## when k is small if (nargin > 5 && ! isempty (kk_terms)) der = ((-2*a .* k_terms + 4 * a .^ 2 .* k .* kk_terms - a .^ 3 .* ... (k .^ 2) .* kkk_terms) .* (f - k - 1) + f .* ((a .* ... (k_terms - a .* k .* kk_terms)) .^ 2) - 1) ./ (sigma .^ 2); else der = (sigma .^ -2) .* (-2 * a .* b .^ (-d) + d .* k .* a .^ 2 .* ... (b .^ (-d-1)) + 2 .* d .* k .* a ./ b - d .* (k .* a ./ b) .^ 2 - 1); endif der(z <= 0) = 0; # no probability mass in this region ACOV(2, 2) = sum (der(:)); ## mu, mu ## Use a series expansion to find the derivatives more accurately ## when k is small if (nargin > 5 && ! isempty (kk_terms)) der = (f .* (a .* k .* kk_terms - k_terms) .^ 2 - a .* k .^ 2 .* ... kkk_terms .* (f - k - 1)) ./ (sigma .^ 2); else der = (d .* (sigma .^ -2)) .* (k .* (b .^ (-d-1)) - (k ./ b) .^ 2); endif der(z <= 0) = 0; # no probability mass in this region ACOV(3, 3) = sum (der(:)); ## k, mu ## Use a series expansion to find the derivatives more accurately ## when k is small if (nargin > 5 && ! isempty (kk_terms)) der = 2 * a .* kk_terms .* (f - 1 - k) - a .^ 2 .* k_terms .* ... kk_terms .* f + k_terms; der = -der ./ sigma; else der = ((b .^ (-d)) .* (c ./ k - a ./ b) ./ k - a .* (b .^ (-d-1)) + ... ((1 ./ k) - d) ./ b + a .* k .* d ./ (b .^ 2)) ./ sigma; endif der(z <= 0) = 0; # no probability mass in this region ACOV(1, 3) = ACOV(3, 1) = sum (der(:)); ## k, sigma der = a .* der; der(z <= 0) = 0; # no probability mass in this region ACOV(1, 2) = ACOV(2, 1) = sum (der(:)); ## sigma, mu ## Use a series expansion to find the derivatives more accurately ## when k is small if (nargin > 5 && ! isempty (kk_terms)) der = ((-k_terms + 3 * a .* k .* kk_terms - (a .* k) .^ 2 .* ... kkk_terms) .* (f - k - 1) + a .* (k_terms - a .* k .* ... kk_terms) .^ 2 .* f) ./ (sigma .^ 2); else der = (-(b .^ (-d)) + a .* k .* d .* (b .^ (-d-1)) + ... (d .* k ./ b) - a .* (k./b).^2 .* d) ./ (sigma .^ 2); endif der(z <= 0) = 0; # no probability mass in this region ACOV(2, 3) = ACOV(3, 2) = sum (der(:)); endfunction ## Test output %!test %! x = 1; %! k = 0.2; %! sigma = 0.3; %! mu = 0.5; %! [L, C] = gevlike ([k sigma mu], x); %! expected_L = 0.75942; %! expected_C = [-0.12547 1.77884 1.06731; 1.77884 16.40761 8.48877; 1.06731 8.48877 0.27979]; %! assert_equal (L, expected_L, 0.001); %! assert_equal (C, inv (expected_C), 0.001); %!test %! x = 1; %! k = 0; %! sigma = 0.3; %! mu = 0.5; %! [L, C] = gevlike ([k sigma mu], x); %! expected_L = 0.65157; %! expected_C = [0.090036 3.41229 2.047337; 3.412229 24.760027 12.510190; 2.047337 12.510190 2.098618]; %! assert_equal (L, expected_L, 0.001); %! assert_equal (C, inv (expected_C), 0.001); %!test %! ## ACOV is continuous at k = 0: the Gumbel-limit branch agrees with the %! ## general expressions evaluated just off zero. MATLAB's k = 0 branch does %! ## not, returning ACOV(1,1) = -0.3977 against the limit's -2.8133. %! [~, C0] = gevlike ([0, 0.3, 0.5], 1); %! [~, Ce] = gevlike ([1e-5, 0.3, 0.5], 1); %! assert_equal (C0, Ce, 1e-3); %! assert_equal (C0(1,1), -2.813275839387, 1e-9); %!test %! x = -5:-1; %! k = -0.2; %! sigma = 0.3; %! mu = 0.5; %! [L, C] = gevlike ([k sigma mu], x); %! expected_L = 3786.4; %! expected_C = [1.6802e-07, 4.6110e-06, 8.7297e-05; ... %! 4.6110e-06, 7.5693e-06, 1.2034e-05; ... %! 8.7297e-05, 1.2034e-05, -0.0019125]; %! assert_equal (L, expected_L, -0.001); %! assert_equal (C, expected_C, -0.001); %!test %! x = -5:0; %! k = -0.2; %! sigma = 0.3; %! mu = 0.5; %! [L, C] = gevlike ([k sigma mu], x, [1, 1, 1, 1, 1, 0]); %! expected_L = 3786.4; %! expected_C = [1.6802e-07, 4.6110e-06, 8.7297e-05; ... %! 4.6110e-06, 7.5693e-06, 1.2034e-05; ... %! 8.7297e-05, 1.2034e-05, -0.0019125]; %! assert_equal (L, expected_L, -0.001); %! assert_equal (C, expected_C, -0.001); ## Test input validation %!error gevlike (3.25) %!error gevlike ([1, 2, 3], ones (2)) %!error ... %! gevlike ([1, 2], [1, 3, 5, 7]) %!error ... %! gevlike ([1, 2, 3, 4], [1, 3, 5, 7]) %!error ... %! gevlike ([5, 0.2, 1], ones (10, 1), ones (8,1)) %!error ... %! gevlike ([5, 0.2, 1], ones (1, 8), [1 1 1 1 1 1 1 -1]) %!error ... %! gevlike ([5, 0.2, 1], ones (1, 8), [1 1 1 1 1 1 1 1.5]) statistics-release-1.9.2/inst/Distribution_Fitting/gpfit.m000066400000000000000000000360411524624707500237620ustar00rootroot00000000000000## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## Octave is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or (at ## your option) any later version. ## ## Octave is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Octave; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} gpfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} gpfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} gpfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} gpfit (@var{x}, @var{alpha}, @var{options}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} gpfit (@var{x}, @var{alpha}, @var{options}, @var{freq}) ## ## Estimate parameters and confidence intervals for the generalized Pareto ## distribution. ## ## @code{@var{paramhat} = gpfit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the generalized Pareto distribution given the ## data in @var{x}. @qcode{@var{paramhat}(1)} is the shape parameter, @var{k}, ## and @qcode{@var{paramhat}(2)} is the scale parameter, @var{sigma}. ## ## @code{gpfit} does not estimate the location parameter @var{theta} and assumes ## it to be zero, so @var{x} must not contain negative values. To fit data with ## a known nonzero @var{theta}, subtract it from @var{x} before calling ## @code{gpfit}; the estimates of @var{k} and @var{sigma} are unchanged by the ## shift. ## ## @code{[@var{paramhat}, @var{paramci}] = gpfit (@var{x})} returns the 95% ## confidence intervals for the estimated parameters @var{k} and @var{sigma} as ## a @math{2}-by-@math{2} matrix whose first row holds the lower bounds and ## whose second row holds the upper bounds. ## ## @code{[@dots{}] = gpfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = gpfit (@var{x}, @var{alpha}, @var{options})} ## specifies control parameters for the iterative algorithm used to compute ML ## estimates with the @code{fminsearch} function. @var{options} is a structure ## with the following fields and their default values: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## @code{[@dots{}] = gpfit (@var{x}, @var{alpha}, @var{options}, @var{freq})} ## accepts a vector of the same size as @var{x} giving the number of times each ## element of @var{x} was observed. This fourth argument is an Octave ## extension; MATLAB's @code{gpfit} takes three inputs at most. ## ## When the shape parameter falls below @math{-1} the likelihood is unbounded: ## the density at the upper endpoint of the support diverges as that endpoint ## closes onto the largest observation, so no maximum likelihood estimate ## exists and whatever is returned is an arbitrary point on that ridge. ## @code{gpfit} warns in this case and returns @code{NaN} confidence intervals. ## The estimate it does return always keeps every observation strictly inside ## the fitted support, since the likelihood is infinite outside it. This is a ## deliberate deviation: MATLAB has been measured returning parameters for such ## data under which the largest observation has zero density and its own ## @code{gplike} returns @code{Inf}. ## ## When @qcode{@var{k} = 0} and @qcode{@var{theta} = 0}, the Generalized Pareto ## is equivalent to the exponential distribution. When @qcode{@var{k} > 0} and ## @code{@var{theta} = @var{k} / @var{k}} the Generalized Pareto is equivalent ## to the Pareto distribution. The mean of the Generalized Pareto is not finite ## when @qcode{@var{k} >= 1} and the variance is not finite when ## @qcode{@var{k} >= 1/2}. When @qcode{@var{k} >= 0}, the Generalized Pareto ## has positive density for @qcode{@var{x} > @var{theta}}, or, when ## @qcode{@var{theta} < 0}, for ## @qcode{0 <= (@var{x} - @var{theta}) / @var{sigma} <= -1 / @var{k}}. ## ## Further information about the generalized Pareto distribution can be found at ## @url{https://en.wikipedia.org/wiki/Generalized_Pareto_distribution} ## ## @seealso{gpcdf, gpinv, gppdf, gprnd, gplike, gpstat} ## @end deftypefn function [paramhat, paramci] = gpfit (x, alpha, options, freq) ## Check for valid number of input arguments if (nargin < 1) error ("gpfit: function called with too few input arguments."); endif ## Check X for being a vector if (isempty (x)) paramhat = nan (1, 2, class (x)); paramci = nan (2, 2, class (x)); return elseif (! isvector (x) || ! isreal (x)) error ("gpfit: X must be a vector of real values."); endif ## The location parameter is assumed to be zero, so no observation may fall ## below it. Data with a known nonzero location is shifted by the caller. if (any (x < 0)) error ("gpfit: X must not contain negative values."); endif ## Parse ALPHA argument or add default if (nargin < 2 || isempty (alpha)) alpha = 0.05; elseif (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("gpfit: wrong value for ALPHA."); endif ## Parse FREQ argument or add default if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("gpfit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("gpfit: FREQ must not contain negative values."); endif ## Expand frequency vector (if necessary) if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; endif ## Get options structure or add defaults if (nargin < 3 || isempty (options)) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("gpfit: 'options' 3rd argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Non-finite data is not removed: it propagates into the estimates, as it ## does in MATLAB and in every iterative fitter of this package. Dropping ## missing observations belongs to the wrappers a user hands raw data to, ## FITDIST and MLE, not to the estimator. ## Get sample size, max and range of X x_max = max (x); x_size = length (x); x_range = range (x); ## Check for appropriate sample size or all observations being equal if (x_size == 0) paramhat = NaN (1, 2); paramci = NaN (2, 2); warning ("gpfit: X contains no data."); return elseif (x_range < realmin (class (x))) paramhat = cast ([NaN, 0], class (x)); paramci = [paramhat; paramhat]; warning ("gpfit: X contains constant data."); return endif ## Make an initial guess x_mean = mean (x); x_var = var (x); k0 = -0.5 .* (x_mean .^ 2 ./ x_var - 1); s0 = 0.5 .* x_mean .* (x_mean .^ 2 ./ x_var + 1); ## If initial guess fails, start with an exponential fit if (k0 < 0 && (x_max >= -s0 / k0)) k0 = 0; s0 = x_mean; endif paramhat = [k0, log(s0)]; ## Maximize the log-likelihood with respect to shape and log_scale. f = @(paramhat) negloglike (paramhat, x); [paramhat, ~, err, output] = fminsearch (f, paramhat, options); paramhat(2) = exp (paramhat(2)); ## Check output of fminsearch and produce warnings or errors if applicable if (err == 0) if (output.funcCount >= options.MaxFunEvals) warning ("gpfit: reached evaluation limit."); else warning ("gpfit: reached iteration limit."); endif elseif (err < 0) error ("gpfit: no solution."); endif ## Check if converged to boundaries if ((paramhat(1) < 0) && (x_max > -paramhat(2)/paramhat(1) - options.TolX)) warning (strcat ("gpfit: the fitted upper bound of the support has", ... " closed onto the largest observation, a boundary of", ... " the parameter space, so the estimates are unreliable", ... " and no confidence intervals are computed.")); reachedBnd = true; elseif (paramhat(1) <= -1 / 2) warning (strcat ("gpfit: the shape parameter has converged to", ... " K <= -1/2, where the maximum likelihood estimator is", ... " not regular, so standard errors and confidence", ... " intervals cannot be computed reliably.")); reachedBnd = true; else reachedBnd = false; endif ## If second output argument is requested if (nargout > 1) if (! reachedBnd) probs = [alpha/2; 1-alpha/2]; [~, acov] = gplike (paramhat, x); se = sqrt (diag (acov))'; ## Compute the CI for shape using a normal distribution for khat. kci = norminv (probs, paramhat(1), se(1)); ## Compute the CI for scale using a normal approximation for ## log(sigmahat), and transform back to the original scale. lnsigci = norminv (probs, log (paramhat(2)), se(2) ./ paramhat(2)); paramci = [kci, exp(lnsigci)]; else paramci = [NaN, NaN; NaN, NaN]; endif endif endfunction ## Negative log-likelihood for the GP function nll = negloglike (paramhat, data) shape = paramhat(1); log_scale = paramhat(2); scale = exp (log_scale); sample_size = numel (data); z = data ./ scale; if (abs (shape) > eps) if (shape > 0 || max (z) < -1 / shape) nll = sample_size * log_scale + (1 + 1/shape) * sum (log1p (shape .* z)); else nll = Inf; endif else nll = sample_size * log_scale + sum (z); endif endfunction %!demo %! ## Sample 2 populations from different generalized Pareto distributions %! ## Assume location parameter θ is known %! rng (42); %! theta = 0; %! r1 = gprnd (1, 2, theta, 20000, 1); %! r2 = gprnd (3, 1, theta, 20000, 1); %! r = [r1, r2]; %! %! ## Plot them normalized and fix their colors %! hist (r, [0.1:0.2:100], 5); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'r'); %! set (h(2), 'facecolor', 'c'); %! ylim ([0, 1]); %! xlim ([0, 5]); %! hold on %! %! ## Estimate their α and β parameters %! k_sigmaA = gpfit (r(:,1)); %! k_sigmaB = gpfit (r(:,2)); %! %! ## Plot their estimated PDFs %! x = [0.01, 0.1:0.2:18]; %! y = gppdf (x, k_sigmaA(1), k_sigmaA(2), theta); %! plot (x, y, '-pc'); %! y = gppdf (x, k_sigmaB(1), k_sigmaB(2), theta); %! plot (x, y, '-sr'); %! hold off %! legend ({'Normalized HIST of sample 1 with k=1 and σ=2', ... %! 'Normalized HIST of sample 2 with k=2 and σ=2', ... %! sprintf("PDF for sample 1 with estimated k=%0.2f and σ=%0.2f", ... %! k_sigmaA(1), k_sigmaA(2)), ... %! sprintf("PDF for sample 3 with estimated k=%0.2f and σ=%0.2f", ... %! k_sigmaB(1), k_sigmaB(2))}) %! title ('Two population samples from different generalized Pareto distributions') %! text (2, 0.7, 'Known location parameter θ = 0') %! hold off ## Test output ## Values below are R2024a's, measured 2026-08-17. The estimates agree to the ## convergence tolerance of fminsearch, which is what the 1e-4 covers. %!shared x %! x = [2.2196, 11.9301, 4.3673, 1.0949, 6.5626, ... %! 1.2109, 1.8576, 1.0039, 12.7917, 2.2590]; %!test %! [hat, ci] = gpfit (x); %! assert_equal (hat, [-0.163107819293798, 5.305483917184919], 1e-4); %! assert_equal (ci, [-1.174106637867584, 1.627748133572634; ... %! 0.847890999279987, 17.292699659699391], 1e-4); %!test %! [hat, ci] = gpfit (x, 0.10); %! assert_equal (ci, [-1.011564773958192, 1.968276868273005; ... %! 0.685349135370595, 14.300914698146826], 1e-4); %!test %! ## a known location is fitted by shifting the data, and only shifts the fit %! [hat, ci] = gpfit (x - 1); %! assert_equal (hat, [0.893710299404345, 1.322962458731574], 1e-6); %! assert_equal (ci, [-0.774991092191746, 0.243695078371714; ... %! 2.562411691000436, 7.182047659343478], 1e-5); %!assert_equal (size (gpfit (x)), [1, 2]) %!test %! [~, ci] = gpfit (x); %! assert_equal (size (ci), [2, 2]); %!test %! ## the default confidence level is 95% %! [h1, c1] = gpfit (x); %! [h2, c2] = gpfit (x, 0.05); %! assert_equal (h1, h2); %! assert_equal (c1, c2); %!test %! ## FREQ counts repeated observations %! assert_equal (gpfit (x, [], [], [2, ones(1,9)]), gpfit ([x(1), x]), 1e-10); %!test %! ## non-finite data propagates into the estimates instead of being dropped %! assert_equal (gpfit ([x, NaN]), [NaN, NaN]); %!test %! assert_equal (gpfit ([x, Inf]), [NaN, NaN]); %!test %! assert_equal (gpfit ([x, NaN, Inf]), [NaN, NaN]); %!test %! ## below a shape of -1 the likelihood is unbounded, but the estimate still %! ## keeps every observation strictly inside the fitted support %! xb = [1.2 2.3 0.5 3.1 2.2 1.8 0.9 2.7 1.1 3.3]; %! warning ('off', 'all'); %! p = gpfit (xb); %! warning ('on', 'all'); %! assert_equal (p(1) < -1, true); %! assert_equal (max (xb) < -p(2) / p(1), true); %! assert_equal (isfinite (gplike (p, xb)), true); %!test %! ## the confidence intervals are withheld there %! warning ('off', 'all'); %! [~, ci] = gpfit ([1.2 2.3 0.5 3.1 2.2 1.8 0.9 2.7 1.1 3.3]); %! warning ('on', 'all'); %! assert_equal (ci, [NaN, NaN; NaN, NaN]); %!warning ... %! gpfit ([1.2 2.3 0.5 3.1 2.2 1.8 0.9 2.7 1.1 3.3]); ## Test input validation %!error gpfit () %!error gpfit ([0.2, 0.5+i]); %!error gpfit (ones (2,2) * 0.5); %!error gpfit ([-1, 2, 3]); %!error gpfit ([0.01:0.1:0.99], 1.2); %!error gpfit ([0.01:0.1:0.99], i); %!error gpfit ([0.01:0.1:0.99], -1); %!error gpfit ([0.01:0.1:0.99], [0.05, 0.01]); %!error ... %! gpfit ([1 2 3], [], [], [1 5]) %!error ... %! gpfit ([1 2 3], [], [], [1 5 -1]) %!error ... %! gpfit ([1:10], 0.05, 5) statistics-release-1.9.2/inst/Distribution_Fitting/gplike.m000066400000000000000000000165501524624707500241270ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## Octave is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or (at ## your option) any later version. ## ## Octave is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Octave; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} gplike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} gplike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} gplike (@var{params}, @var{x}, @var{freq}) ## ## Negative log-likelihood for the generalized Pareto distribution. ## ## @code{@var{nlogL} = gplike (@var{params}, @var{x})} returns the negative ## log-likelihood of the data in @var{x} corresponding to the generalized Pareto ## distribution with (1) shape parameter @var{k} and (2) scale parameter ## @var{sigma} given in the two-element vector @var{params}. ## ## @code{gplike} does not accept a location parameter @var{theta} and assumes it ## to be zero. If the location is known to be nonzero, subtract it from ## @var{x} before calling @code{gplike}. ## ## @code{[@var{nlogL}, @var{acov}] = gplike (@var{params}, @var{x})} returns ## the inverse of Fisher's information matrix, @var{acov}, a ## @math{2}-by-@math{2} matrix. If the input parameter values in @var{params} ## are the maximum likelihood estimates, the diagonal elements of @var{acov} are ## their asymptotic variances. @var{acov} is based on the observed Fisher's ## information, not the expected information. ## ## @code{[@dots{}] = gplike (@var{params}, @var{x}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## typically contains integer frequencies for the corresponding elements in ## @var{x}, but it can contain any non-integer non-negative values. By default, ## or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. This third ## argument is an Octave extension; MATLAB's @code{gplike} takes two inputs. ## ## When @qcode{@var{k} = 0} and @qcode{@var{mu} = 0}, the Generalized Pareto CDF ## is equivalent to the exponential distribution. When @qcode{@var{k} > 0} and ## @code{@var{mu} = @var{k} / @var{k}} the Generalized Pareto is equivalent to ## the Pareto distribution. The mean of the Generalized Pareto is not finite ## when @qcode{@var{k} >= 1} and the variance is not finite when ## @qcode{@var{k} >= 1/2}. When @qcode{@var{k} >= 0}, the Generalized Pareto ## has positive density for @qcode{@var{x} > @var{mu}}, or, when ## @qcode{@var{mu} < 0}, for ## @qcode{0 <= (@var{x} - @var{mu}) / @var{sigma} <= -1 / @var{k}}. ## ## Further information about the generalized Pareto distribution can be found at ## @url{https://en.wikipedia.org/wiki/Generalized_Pareto_distribution} ## ## @seealso{gpcdf, gpinv, gppdf, gprnd, gpfit, gpstat} ## @end deftypefn function [nlogL, acov] = gplike (params, x, freq) ## Check input arguments if (nargin < 2) error ("gplike: function called with too few input arguments."); endif if (! isvector (x)) error ("gplike: X must be a vector."); endif if (numel (params) != 2) error ("gplike: PARAMS must be a two-element vector."); endif ## Parse FREQ argument or add default if (nargin < 3 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("gplike: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("gplike: FREQ must not contain negative values."); endif ## Expand frequency vector (if necessary) if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; endif ## Get K and SIGMA parameters. The location is assumed to be zero, the ## caller having shifted the data by any known value. k = params(1); sigma = params(2); ## Get sample size and sigma x sz = numel (x); z = x ./ sigma; ## For K > 0 if (abs (k) > eps) if (k > 0 || max (z) < -1 / k) sumLn = sum (log1p (k .* z)); nlogL = sz * log (sigma) + (1 + 1 / k) .* sumLn; if (nargout > 1) z_kz = z ./ (1 + k .* z); sumv = sum (z_kz); sumvsq = sum (z_kz .^ 2); nH11 = 2 * sumLn ./ k ^ 3 - ... 2 * sumv ./ k ^ 2 - (1 + 1 / k) .* sumvsq; nH12 = (-sumv + (k + 1) .* sumvsq) ./ sigma; nH22 = (-sz + 2 * (k + 1) .* sumv - ... k * (k + 1) .* sumvsq) ./ sigma ^ 2; acov = [nH22, -nH12; -nH12, nH11] / (nH11 * nH22 - nH12 * nH12); endif else ## The support of the GP when k<0 is 0 < y < abs(sigma/k) nlogL = Inf; if (nargout > 1) acov = [NaN, NaN; NaN, NaN]; endif endif else # For k = 0 nlogL = sz * log (sigma) + sum (z); if (nargout > 1) sumz = sum (z); sumzsq = sum (z .^ 2); sumzcb = sum (z .^ 3); nH11 = (2 / 3) * sumzcb - sumzsq; nH12 = (-sumz + sumzsq) ./ sigma; nH22 = (-sz + 2 * sumz) ./ sigma ^ 2; acov = [nH22, -nH12; -nH12, nH11] / (nH11 * nH22 - nH12 * nH12); endif endif endfunction ## Test output ## The first block's values are R2024a's, measured 2026-08-17. %!test %! k = 0.893710299404345; sigma = 1.322962458731574; %! x = [2.2196, 11.9301, 4.3673, 1.0949, 6.5626, ... %! 1.2109, 1.8576, 1.0039, 12.7917, 2.2590] - 1; %! [nlogL, acov] = gplike ([k, sigma], x); %! assert_equal (nlogL, 21.735838309709596, 1e-12); %! assert_equal (acov, [ 0.724871582460845, -0.735076013984010; ... %! -0.735076013984010, 1.303920812049488], 1e-12); %!test %! ## the covariance covers the two estimated parameters only %! [~, acov] = gplike ([2, 3], [1:10]); %! assert_equal (size (acov), [2, 2]); %!test %! ## a known location is handled by shifting the data %! assert_equal (gplike ([2, 3], ([1:10] + 2) - 2), ... %! gplike ([2, 3], [1:10]), 1e-14); %!assert_equal (gplike ([2, 3], 4), 3.047536764863501, 1e-14) %!assert_equal (gplike ([1, 2], 4), 2.890371757896165, 1e-14) %!assert_equal (gplike ([2, 3], [1:10]), 32.57864322725392, 1e-14) %!assert_equal (gplike ([1, 2], [1:10]), 31.65666282460443, 1e-14) %!assert_equal (gplike ([2, 3], [1:10], ones (1,10)), 32.57864322725392, 1e-14) %!assert_equal (gplike ([1, 2], [1:10], ones (1,10)), 31.65666282460443, 1e-14) %!assert_equal (gplike ([1, NaN], [1:10]), NaN) ## Test input validation %!error gplike () %!error gplike (1) %!error gplike ([1, 2], []) %!error gplike ([1, 2], ones (2)) %!error gplike (2, [1:10]) %!error gplike ([1, 2, 0], [1:10]) %!error ... %! gplike ([1, 2], ones (10, 1), ones (8,1)) %!error ... %! gplike ([1, 2], ones (1, 8), [1 1 1 1 1 1 1 -1]) statistics-release-1.9.2/inst/Distribution_Fitting/gumbelfit.m000066400000000000000000000221671524624707500246330ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} gumbelfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} gumbelfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} gumbelfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} gumbelfit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} gumbelfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} gumbelfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate parameters and confidence intervals for Gumbel distribution. ## ## @code{@var{paramhat} = gumbelfit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the Gumbel distribution (also known as ## the extreme value or the type I generalized extreme value distribution) given ## in @var{x}. @qcode{@var{paramhat}(1)} is the location parameter, @var{mu}, ## and @qcode{@var{paramhat}(2)} is the scale parameter, @var{beta}. ## ## @code{[@var{paramhat}, @var{paramci}] = gumbelfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = gumbelfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = gumbelfit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = gumbelfit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@dots{}] = gumbelfit (@dots{}, @var{options})} specifies control ## parameters for the iterative algorithm used to compute the maximum likelihood ## estimates. @var{options} is a structure with the following field and its ## default value: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## The Gumbel distribution is used to model the distribution of the maximum (or ## the minimum) of a number of samples of various distributions. This version ## is suitable for modeling maxima. For modeling minima, use the alternative ## extreme value fitting function, @code{evfit}. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## @seealso{gumbelcdf, gumbelinv, gumbelpdf, gumbelrnd, gumbellike, gumbelstat, ## evfit} ## @end deftypefn function [paramhat, paramci] = gumbelfit (x, alpha, censor, freq, options) ## Check X for being a double precision vector if (! isvector (x) || ! isa (x, 'double')) error ("gumbelfit: X must be a double-precision vector."); endif ## Check that X does not contain missing values (NaNs) if (any (isnan (x))) error ("gumbelfit: X must NOT contain missing values (NaNs)."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("gumbelfit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("gumbelfit: X and CENSOR vectors mismatch."); endif ## Parse FREQ argument or add default if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("gumbelfit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("gumbelfit: FREQ must not contain negative values."); endif ## Get options structure or add defaults if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("gumbelfit: 'options' 5th argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Remove zeros and NaNs from frequency vector (if necessary) if (! all (freq == 1)) remove = freq == 0 | isnan (freq); x(remove) = []; censor(remove) = []; freq(remove) = []; endif ## If X is a column vector, make X, CENSOR, and FREQ row vectors if (size (x, 1) > 1) x = x(:)'; censor = censor(:)'; freq = freq(:)'; endif ## Call evfit to do the actual computation on the negative X try [paramhat, paramci] = evfit (-x, alpha, censor, freq, options); catch error ("gumbelfit: no solution for maximum likelihood estimates."); end_try_catch ## Flip sign on estimated parameter MU paramhat(1) = -paramhat(1); ## Flip sign on confidence intervals of parameter MU paramci(:,1) = -flip (paramci(:,1)); endfunction %!demo %! ## Sample 3 populations from different Gumbel distributions %! rng (42); %! r1 = gumbelrnd (2, 5, 400, 1); %! r2 = gumbelrnd (-5, 3, 400, 1); %! r3 = gumbelrnd (14, 8, 400, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, 25, 0.32); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! ylim ([0, 0.28]) %! xlim ([-11, 50]); %! hold on %! %! ## Estimate their MU and BETA parameters %! mu_betaA = gumbelfit (r(:,1)); %! mu_betaB = gumbelfit (r(:,2)); %! mu_betaC = gumbelfit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [min(r(:)):max(r(:))]; %! y = gumbelpdf (x, mu_betaA(1), mu_betaA(2)); %! plot (x, y, '-pr'); %! y = gumbelpdf (x, mu_betaB(1), mu_betaB(2)); %! plot (x, y, '-sg'); %! y = gumbelpdf (x, mu_betaC(1), mu_betaC(2)); %! plot (x, y, '-^c'); %! legend ({'Normalized HIST of sample 1 with μ=2 and β=5', ... %! 'Normalized HIST of sample 2 with μ=-5 and β=3', ... %! 'Normalized HIST of sample 3 with μ=14 and β=8', ... %! sprintf("PDF for sample 1 with estimated μ=%0.2f and β=%0.2f", ... %! mu_betaA(1), mu_betaA(2)), ... %! sprintf("PDF for sample 2 with estimated μ=%0.2f and β=%0.2f", ... %! mu_betaB(1), mu_betaB(2)), ... %! sprintf("PDF for sample 3 with estimated μ=%0.2f and β=%0.2f", ... %! mu_betaC(1), mu_betaC(2))}) %! title ('Three population samples from different Gumbel distributions') %! hold off ## Test output %!test %! x = 1:50; %! [paramhat, paramci] = gumbelfit (x); %! paramhat_out = [18.3188, 13.0509]; %! paramci_out = [14.4882, 10.5294; 22.1495, 16.1763]; %! assert_equal (paramhat, paramhat_out, 1e-4); %! assert_equal (paramci, paramci_out, 1e-4); %!test %! x = 1:50; %! [paramhat, paramci] = gumbelfit (x, 0.01); %! paramci_out = [13.2845, 9.8426; 23.3532, 17.3051]; %! assert_equal (paramci, paramci_out, 1e-4); ## Test input validation %!error gumbelfit (ones (2,5)); %!error ... %! gumbelfit (single (ones (1,5))); %!error ... %! gumbelfit ([1, 2, 3, 4, NaN]); %!error gumbelfit ([1, 2, 3, 4, 5], 1.2); %!error ... %! gumbelfit ([1, 2, 3, 4, 5], 0.05, [1 1 0]); %!error ... %! gumbelfit ([1, 2, 3, 4, 5], 0.05, [], [1 1 0]); %!error %! gamfit ([1, 2, 3], 0.05, [], [1 5 -1]) %!error ... %! gumbelfit ([1, 2, 3, 4, 5], 0.05, [], [], 2); statistics-release-1.9.2/inst/Distribution_Fitting/gumbellike.m000066400000000000000000000137611524624707500247750ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} gumbellike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{avar}] =} gumbellike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} gumbellike (@var{params}, @var{x}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} gumbellike (@var{params}, @var{x}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the extreme value distribution. ## ## @code{@var{nlogL} = gumbellike (@var{params}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the Gumbel ## distribution (also known as the extreme value or the type I generalized ## extreme value distribution) with (1) location parameter @var{mu} and (2) ## scale parameter @var{beta} given in the two-element vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = gumbellike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{acov} are their asymptotic variances. ## ## @code{[@dots{}] = gumbellike (@var{params}, @var{x}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = gumbellike (@var{params}, @var{x}, @var{censor}, ## @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## The Gumbel distribution is used to model the distribution of the maximum (or ## the minimum) of a number of samples of various distributions. This version ## is suitable for modeling maxima. For modeling minima, use the alternative ## extreme value likelihood function, @code{evlike}. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## @seealso{gumbelcdf, gumbelinv, gumbelpdf, gumbelrnd, gumbelfit, gumbelstat, ## evlike} ## @end deftypefn function [nlogL, avar] = gumbellike (params, x, censor, freq) ## Check input arguments and add defaults if (nargin < 2) error ("gumbellike: too few input arguments."); endif if (numel (params) != 2) error ("gumbellike: wrong parameters length."); endif if (! isvector (x)) error ("gumbellike: X must be a vector."); endif if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("gumbellike: X and CENSOR vectors mismatch."); endif if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (isequal (size (x), size (freq))) nulls = find (freq == 0); if (numel (nulls) > 0) x(nulls) = []; censor(nulls) = []; freq(nulls) = []; endif else error ("gumbellike: X and FREQ vectors mismatch."); endif ## Get mu and sigma values mu = params(1); sigma = params(2); ## sigma must be positive, otherwise make it NaN if (sigma <= 0) sigma = NaN; endif ## Compute the individual log-likelihood terms. Force a log(0)==-Inf for ## x from extreme right tail, instead of getting exp(Inf-Inf)==NaN. z = (x - mu) ./ sigma; expz = exp (z); L = (z - log (sigma)) .* (1 - censor) - expz; L(z == Inf) = -Inf; ## Neg-log-like is the sum of the individual contributions nlogL = -sum (freq .* L); ## Compute the negative hessian at the parameter values. ## Invert to get the observed information matrix. if (nargout == 2) unc = (1-censor); nH11 = sum (freq .* expz); nH12 = sum (freq .* ((z + 1) .* expz - unc)); nH22 = sum (freq .* (z .* (z+2) .* expz - ((2 .* z + 1) .* unc))); avar = (sigma .^ 2) * ... [nH22 -nH12; -nH12 nH11] / (nH11 * nH22 - nH12 * nH12); endif endfunction ## Test output %!test %! x = 1:50; %! [nlogL, avar] = gumbellike ([2.3, 1.2], x); %! avar_out = [-1.2778e-13, 3.1859e-15; 3.1859e-15, -7.9430e-17]; %! assert_equal (nlogL, 3.242264755689906e+17, 1e-14); %! assert_equal (avar, avar_out, 1e-3); %!test %! x = 1:50; %! [nlogL, avar] = gumbellike ([2.3, 1.2], x * 0.5); %! avar_out = [-7.6094e-05, 3.9819e-06; 3.9819e-06, -2.0836e-07]; %! assert_equal (nlogL, 481898704.0472211, 1e-6); %! assert_equal (avar, avar_out, 1e-3); %!test %! x = 1:50; %! [nlogL, avar] = gumbellike ([21, 15], x); %! avar_out = [11.73913876598908, -5.9546128523121216; ... %! -5.954612852312121, 3.708060045170236]; %! assert_equal (nlogL, 223.7612479380652, 1e-13); %! assert_equal (avar, avar_out, 1e-14); ## Test input validation %!error gumbellike ([12, 15]); %!error gumbellike ([12, 15, 3], [1:50]); %!error gumbellike ([12, 3], ones (10, 2)); %!error gumbellike ([12, 15], [1:50], [1, 2, 3]); %!error gumbellike ([12, 15], [1:50], [], [1, 2, 3]); statistics-release-1.9.2/inst/Distribution_Fitting/hnfit.m000066400000000000000000000161611524624707500237620ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{paramhat}, @var{paramci}] =} hnfit (@var{x}, @var{mu}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} hnfit (@var{x}, @var{mu}, @var{alpha}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} hnfit (@var{x}, @var{mu}, @var{alpha}, @var{freq}) ## ## Estimate parameters and confidence intervals for the half-normal ## distribution. ## ## @code{@var{paramhat} = hnfit (@var{x}, @var{mu})} returns the maximum ## likelihood estimates of the parameters of the half-normal distribution given ## the data in vector @var{x} and the location parameter @var{mu}. ## @qcode{@var{paramhat}(1)} is the location parameter, @var{mu}, and ## @qcode{@var{paramhat}(2)} is the scale parameter, @var{sigma}. Although ## @var{mu} is returned in the estimated @var{paramhat}, @code{hnfit} does not ## estimate the location parameter @var{mu}, and it must be assumed to be known, ## given as a fixed parameter in input argument @var{mu}. ## ## @code{[@var{paramhat}, @var{paramci}] = hnfit (@var{x}, @var{mu})} returns ## the 95% confidence intervals for the estimated scale parameter @var{sigma}. ## The first column of @var{paramci} includes the location parameter @var{mu} ## without any confidence bounds. ## ## @code{[@dots{}] = hnfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals of the estimated ## scale parameter. By default, the optional argument @var{alpha} is 0.05 ## corresponding to 95% confidence intervals. ## ## @code{[@dots{}] = hnfit (@var{params}, @var{x}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## must contain non-negative integer frequencies for the corresponding elements ## in @var{x}. By default, or if left empty, ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## The half-normal CDF is only defined for @qcode{@var{x} >= @var{mu}}. ## ## Further information about the half-normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Half-normal_distribution} ## ## @seealso{hncdf, hninv, hnpdf, hnrnd, hnlike, hnstat} ## @end deftypefn function [paramhat, paramci] = hnfit (x, mu, alpha, freq) ## Check for valid number of input arguments if (nargin < 2) error ("hnfit: function called with too few input arguments."); endif ## Check X for being a vector if (isempty (x)) phat = nan (1, 2, class (x)); pci = nan (2, 2, class (x)); return elseif (! isvector (x) || ! isreal (x)) error ("hnfit: X must be a vector of real values."); endif ## Check for MU being a scalar real value if (! isscalar (mu) || ! isreal (mu)) error ("hnfit: MU must be a real scalar value."); endif ## Check X >= MU if (any (x < mu)) error ("hnfit: X cannot contain values less than MU."); endif ## Parse ALPHA argument or add default if (nargin < 3 || isempty (alpha)) alpha = 0.05; elseif (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("hnfit: wrong value for ALPHA."); endif ## Parse FREQ argument or add default if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("hnfit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("hnfit: FREQ must not contain negative values."); endif ## Expand frequency vector (if necessary) if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; endif ## Estimate parameters sz = numel (x); x = x - mu; sigmahat = sqrt (sum (x .* x) ./ sz); paramhat = [mu, sigmahat]; ## Compute confidence intervals if (nargout == 2) chi2cr = chi2inv ([alpha/2, 1-alpha/2], sz); shatlo = sigmahat * sqrt (sz / chi2inv (1 - alpha / 2, sz)); shathi = sigmahat * sqrt (sz / chi2inv (alpha / 2, sz)); paramci = [mu, shatlo; mu, shathi]; endif endfunction %!demo %! ## Sample 2 populations from different half-normal distributions %! rng (42); %! r1 = hnrnd (0, 5, 5000, 1); %! r2 = hnrnd (0, 2, 5000, 1); %! r = [r1, r2]; %! %! ## Plot them normalized and fix their colors %! hist (r, [0.5:20], 1); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! hold on %! %! ## Estimate their shape parameters %! mu_sigmaA = hnfit (r(:,1), 0); %! mu_sigmaB = hnfit (r(:,2), 0); %! %! ## Plot their estimated PDFs %! x = [0:0.2:10]; %! y = hnpdf (x, mu_sigmaA(1), mu_sigmaA(2)); %! plot (x, y, '-pr'); %! y = hnpdf (x, mu_sigmaB(1), mu_sigmaB(2)); %! plot (x, y, '-sg'); %! xlim ([0, 10]) %! ylim ([0, 0.5]) %! legend ({'Normalized HIST of sample 1 with μ=0 and σ=5', ... %! 'Normalized HIST of sample 2 with μ=0 and σ=2', ... %! sprintf("PDF for sample 1 with estimated μ=%0.2f and σ=%0.2f", ... %! mu_sigmaA(1), mu_sigmaA(2)), ... %! sprintf("PDF for sample 2 with estimated μ=%0.2f and σ=%0.2f", ... %! mu_sigmaB(1), mu_sigmaB(2))}) %! title ('Two population samples from different half-normal distributions') %! hold off ## Test output %!test %! x = 1:20; %! [paramhat, paramci] = hnfit (x, 0); %! assert_equal (paramhat, [0, 11.9791], 1e-4); %! assert_equal (paramci, [0, 9.1648; 0, 17.2987], 1e-4); %!test %! x = 1:20; %! [paramhat, paramci] = hnfit (x, 0, 0.01); %! assert_equal (paramci, [0, 8.4709; 0, 19.6487], 1e-4); ## Test input validation %!error hnfit () %!error hnfit (1) %!error hnfit ([0.2, 0.5+i], 0); %!error hnfit (ones (2,2) * 0.5, 0); %!error ... %! hnfit ([0.5, 1.2], [0, 1]); %!error ... %! hnfit ([0.5, 1.2], 5+i); %!error ... %! hnfit ([1:5], 2); %!error hnfit ([0.01:0.1:0.99], 0, 1.2); %!error hnfit ([0.01:0.1:0.99], 0, i); %!error hnfit ([0.01:0.1:0.99], 0, -1); %!error hnfit ([0.01:0.1:0.99], 0, [0.05, 0.01]); %!error %! hnfit ([1 2 3], 0, [], [1 5]) %!error %! hnfit ([1 2 3], 0, [], [1 5 -1]) statistics-release-1.9.2/inst/Distribution_Fitting/hnlike.m000066400000000000000000000114041524624707500241170ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} hnlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} hnlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} hnlike (@var{params}, @var{x}, @var{freq}) ## ## Negative log-likelihood for the half-normal distribution. ## ## @code{@var{nlogL} = hnlike (@var{params}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the half-normal ## distribution with (1) location parameter @var{mu} and (2) scale parameter ## @var{sigma} given in the two-element vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = hnlike (@var{params}, @var{x})} returns ## the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{acov} are their asymptotic variances. ## ## @code{[@dots{}] = hnlike (@var{params}, @var{x}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## typically contains integer frequencies for the corresponding elements in ## @var{x}, but it can contain any non-integer non-negative values. By default, ## or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## The half-normal CDF is only defined for @qcode{@var{x} >= @var{mu}}. ## ## Further information about the half-normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Half-normal_distribution} ## ## @seealso{hncdf, hninv, hnpdf, hnrnd, hnfit, hnstat} ## @end deftypefn function [nlogL, acov] = hnlike (params, x, freq) ## Check input arguments and add defaults if (nargin < 2) error ("hnlike: function called with too few input arguments."); endif if (numel (params) != 2) error ("hnlike: wrong parameters length."); endif ## Check X for being a vector if (isempty (x)) phat = nan (1, 2, class (x)); pci = nan (2, 2, class (x)); return elseif (! isvector (x) || ! isreal (x)) error ("hnlike: X must be a vector of real values."); endif ## Parse FREQ argument or add default if (nargin < 3 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("hnlike: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("hnlike: FREQ must not contain negative values."); endif ## Expand frequency vector (if necessary) if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; endif ## Get MU and SIGMA parameters mu = params(1); sigma = params(2); ## Force X to column vector x = x(:); ## Return NaN for out of range parameters or data. sigma(sigma <= 0) = NaN; x(x < mu) = NaN; z = (x - mu) ./ sigma; ## Sum up the individual log-likelihood terms nlogL = -sum (-0.5 .* z .* z - log (sqrt (pi ./ 2) .* sigma)); ## Compute asymptotic covariance (if requested) if (nargout == 2) nH = -sum (1 - 3 .* z .* z); avar = (sigma .^ 2) ./ nH; acov = [0, 0; 0, avar]; endif endfunction ## Test output %!test %! x = 1:20; %! paramhat = hnfit (x, 0); %! [nlogL, acov] = hnlike (paramhat, x); %! assert_equal (nlogL, 64.179177404891300, 1e-14); %!test %! x = 1:20; %! paramhat = hnfit (x, 0); %! [nlogL, acov] = hnlike (paramhat, x, ones (1, 20)); %! assert_equal (nlogL, 64.179177404891300, 1e-14); ## Test input validation %!error ... %! hnlike ([12, 15]); %!error hnlike ([12, 15, 3], [1:50]); %!error hnlike ([3], [1:50]); %!error ... %! hnlike ([0, 3], ones (2)); %!error ... %! hnlike ([0, 3], [1, 2, 3, 4, 5+i]); %!error ... %! hnlike ([1, 2], ones (10, 1), ones (8,1)) %!error ... %! hnlike ([1, 2], ones (1, 8), [1 1 1 1 1 1 1 -1]) statistics-release-1.9.2/inst/Distribution_Fitting/invgfit.m000066400000000000000000000225761524624707500243270ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} invgfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} invgfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} invgfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} invgfit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} invgfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} invgfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate mean and confidence intervals for the inverse Gaussian distribution. ## ## @code{@var{mu0} = invgfit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the inverse Gaussian distribution given the ## data in @var{x}. @qcode{@var{paramhat}(1)} is the scale parameter, @var{mu}, ## and @qcode{@var{paramhat}(2)} is the shape parameter, @var{lambda}. ## ## @code{[@var{paramhat}, @var{paramci}] = invgfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = invgfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = invgfit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = invgfit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@dots{}] = invgfit (@dots{}, @var{options})} specifies control ## parameters for the iterative algorithm used to compute ML estimates with the ## @code{fminsearch} function. @var{options} is a structure with the following ## fields and their default values: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## Further information about the inverse Gaussian distribution can be found at ## @url{https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution} ## ## @seealso{invgcdf, invginv, invgpdf, invgrnd, invglike, invgstat} ## @end deftypefn function [paramhat, paramci] = invgfit (x, alpha, censor, freq, options) ## Check input arguments if (! isvector (x)) error ("invgfit: X must be a vector."); elseif (any (x <= 0)) error ("invgfit: X must contain only positive values."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("invgfit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("invgfit: X and CENSOR vectors mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("invgfit: X and FREQ vectors mismatch."); endif ## Get options structure or add defaults if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("invgfit: 'options' 5th argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif n_censored = sum (freq .* censor); ## Compute parameters for uncensored data if (n_censored == 0) ## Expand frequency vector (MATLAB does not do this, in R2018 at least) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor xbar = mean (xf); paramhat = [xbar, (1 ./ mean (1 ./ x - 1 ./ xbar))]; else ## Use MLEs of the uncensored data as initial searching values x_uncensored = x(censor == 0); mu0 = mean (x_uncensored); lambda0 = 1 ./ mean (1 ./ x_uncensored - 1 ./ mu0); x0 = [mu0, lambda0]; ## Minimize negative log-likelihood to estimate parameters f = @(params) invglike (params, x, censor, freq); [paramhat, ~, err, output] = fminsearch (f, x0, options); ## Force positive parameter values paramhat = abs (paramhat); ## Handle errors if (err == 0) if (output.funcCount >= options.MaxFunEvals) msg = 'invgfit: maximum number of function evaluations are exceeded.'; warning (msg); elseif (output.iterations >= options.MaxIter) warning ("invgfit: maximum number of iterations are exceeded."); endif elseif (err < 0) error ("invgfit: no solution."); endif endif ## Compute CIs using a log normal approximation for parameters. if (nargout > 1) ## Compute asymptotic covariance [~, acov] = invglike (paramhat, x, censor, freq); ## Get standard errors stderr = sqrt (diag (acov))'; ## Get normal quantiles probs = [alpha/2; 1-alpha/2]; ## Compute CI paramci = norminv ([probs, probs], [paramhat; paramhat], [stderr; stderr]); endif endfunction %!demo %! ## Sample 3 populations from different inverse Gaussian distributions %! rng (42); %! r1 = invgrnd (1, 0.2, 2000, 1); %! r2 = invgrnd (1, 3, 2000, 1); %! r3 = invgrnd (3, 1, 2000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, [0.1:0.1:3.2], 9); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! ylim ([0, 3]); %! xlim ([0, 3]); %! hold on %! %! ## Estimate their MU and LAMBDA parameters %! mu_lambdaA = invgfit (r(:,1)); %! mu_lambdaB = invgfit (r(:,2)); %! mu_lambdaC = invgfit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [0:0.1:3]; %! y = invgpdf (x, mu_lambdaA(1), mu_lambdaA(2)); %! plot (x, y, '-pr'); %! y = invgpdf (x, mu_lambdaB(1), mu_lambdaB(2)); %! plot (x, y, '-sg'); %! y = invgpdf (x, mu_lambdaC(1), mu_lambdaC(2)); %! plot (x, y, '-^c'); %! hold off %! legend ({'Normalized HIST of sample 1 with μ=1 and λ=0.5', ... %! 'Normalized HIST of sample 2 with μ=2 and λ=0.3', ... %! 'Normalized HIST of sample 3 with μ=4 and λ=0.5', ... %! sprintf("PDF for sample 1 with estimated μ=%0.2f and λ=%0.2f", ... %! mu_lambdaA(1), mu_lambdaA(2)), ... %! sprintf("PDF for sample 2 with estimated μ=%0.2f and λ=%0.2f", ... %! mu_lambdaB(1), mu_lambdaB(2)), ... %! sprintf("PDF for sample 3 with estimated μ=%0.2f and λ=%0.2f", ... %! mu_lambdaC(1), mu_lambdaC(2))}) %! title ('Three population samples from different inverse Gaussian distributions') %! hold off ## Test output %!test %! paramhat = invgfit ([1:50]); %! paramhat_out = [25.5, 19.6973]; %! assert_equal (paramhat, paramhat_out, 1e-4); %!test %! paramhat = invgfit ([1:5]); %! paramhat_out = [3, 8.1081]; %! assert_equal (paramhat, paramhat_out, 1e-4); ## Test input validation %!error invgfit (ones (2,5)); %!error invgfit ([-1 2 3 4]); %!error invgfit ([1, 2, 3, 4, 5], 1.2); %!error invgfit ([1, 2, 3, 4, 5], 0); %!error invgfit ([1, 2, 3, 4, 5], 'alpha'); %!error ... %! invgfit ([1, 2, 3, 4, 5], 0.05, [1 1 0]); %!error ... %! invgfit ([1, 2, 3, 4, 5], [], [1 1 0 1 1]'); %!error ... %! invgfit ([1, 2, 3, 4, 5], 0.05, zeros (1,5), [1 1 0]); %!error ... %! invgfit ([1, 2, 3, 4, 5], [], [], [1 1 0 1 1]'); %!error ... %! invgfit ([1, 2, 3, 4, 5], 0.05, [], [], 2); statistics-release-1.9.2/inst/Distribution_Fitting/invglike.m000066400000000000000000000163611524624707500244640ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} invglike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} invglike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} invglike (@var{params}, @var{x}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} invglike (@var{params}, @var{x}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the inverse Gaussian distribution. ## ## @code{@var{nlogL} = invglike (@var{params}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the inverse Gaussian ## distribution with (1) scale parameter @var{mu} and (2) shape parameter ## @var{lambda} given in the two-element vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = invglike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{params} are their asymptotic variances. ## ## @code{[@dots{}] = invglike (@var{params}, @var{x}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = invglike (@var{params}, @var{x}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the inverse Gaussian distribution can be found at ## @url{https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution} ## ## @seealso{invgcdf, invginv, invgpdf, invgrnd, invgfit, invgstat} ## @end deftypefn function [nlogL, acov] = invglike (params, x, censor, freq) ## Check input arguments if (nargin < 2) error ("invglike: function called with too few input arguments."); endif if (! isvector (x)) error ("invglike: X must be a vector."); endif if (any (x < 0)) error ("invglike: X must have positive values."); endif if (length (params) != 2) error ("invglike: PARAMS must be a two-element vector."); endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("invglike: X and CENSOR vector mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("invglike: X and FREQ vector mismatch."); endif ## Get parameters mu = params(1); lambda = params(2); L = 0.5 .* log (lambda ./ (2 * pi)) - 1.5 .* log (x) ... -lambda .* (x ./ mu - 1) .^ 2 ./ (2 .* x); n_censored = sum (freq .* censor); ## Handle censored data if (n_censored > 0) censored = (censor == 1); x_censored = x(censored); sqrt_lx = sqrt (lambda ./ x_censored); z_censored = -(x_censored ./ mu - 1) .* sqrt_lx; w_censored = -(x_censored ./ mu + 1) .* sqrt_lx; Fz = 0.5 .* erfc (-z_censored ./ sqrt (2)); Fw = 0.5 .* erfc (-w_censored ./ sqrt (2)); S_censored = Fz - exp (2 .* lambda ./ mu) .* Fw; L(censored) = log (S_censored); endif ## Sum up the neg log likelihood nlogL = -sum (freq .* L); ## Compute asymptotic covariance if (nargout > 1) ## Compute first order central differences of the log-likelihood gradient dp = 0.0001 .* max (abs (params), 1); ngrad_p1 = invg_grad (params + [dp(1), 0], x, censor, freq); ngrad_m1 = invg_grad (params - [dp(1), 0], x, censor, freq); ngrad_p2 = invg_grad (params + [0, dp(2)], x, censor, freq); ngrad_m2 = invg_grad (params - [0, dp(2)], x, censor, freq); ## Compute negative Hessian by normalizing the differences by the increment nH = [(ngrad_p1(:) - ngrad_m1(:))./(2 * dp(1)), ... (ngrad_p2(:) - ngrad_m2(:))./(2 * dp(2))]; ## Force neg Hessian being symmetric nH = 0.5 .* (nH + nH'); ## Check neg Hessian is positive definite [R, p] = chol (nH); if (p > 0) warning ("invglike: non positive definite Hessian matrix."); acov = NaN (2); return endif ## ACOV estimate is the negative inverse of the Hessian. Rinv = inv (R); acov = Rinv * Rinv; endif endfunction ## Helper function for computing negative gradient function ngrad = invg_grad (params, x, censor, freq) mu = params(1); lambda = params(2); dL1 = lambda .* (x - mu) ./ mu .^ 3; dL2 = 1 ./ (2 .* lambda) - (x ./ mu - 1) .^ 2 ./ (2 .* x); n_censored = sum (freq .* censor); if (n_censored > 0) censored = (censor == 1); x_censored = x(censored); sqrt_lx = sqrt (lambda ./ x_censored); exp_lmu = exp (2 .* lambda ./ mu); z_censored = -(x_censored ./ mu - 1) .* sqrt_lx; w_censored = -(x_censored ./ mu + 1) .* sqrt_lx; Fw = 0.5 .* erfc (-w_censored ./ sqrt (2)); fz = exp (-0.5 .* z_censored .^ 2) ./ sqrt (2 .* pi); fw = exp (-0.5 .* w_censored .^ 2) ./ sqrt (2 .* pi); dS1cen = (fz - exp_lmu .* fw) .* (x_censored ./ mu .^ 2) .* sqrt_lx ... + 2 .* Fw .* exp_lmu .* lambda ./ mu .^ 2; dS2cen = 0.5 .* (fz .* z_censored - exp_lmu .* fw .* w_censored) ... ./ lambda - 2 .* Fw .* exp_lmu ./ mu; dL1(censored) = dS1cen ./ Scen; dL2(censored) = dS2cen ./ Scen; endif ngrad = -[sum(freq .* dL1), sum(freq .* dL2)]; endfunction ## Test results %!test %! nlogL = invglike ([25.5, 19.6973], [1:50]); %! assert_equal (nlogL, 219.1516, 1e-4); %!test %! nlogL = invglike ([3, 8.1081], [1:5]); %! assert_equal (nlogL, 9.0438, 1e-4); ## Test input validation %!error invglike (3.25) %!error invglike ([5, 0.2], ones (2)) %!error invglike ([5, 0.2], [-1, 3]) %!error ... %! invglike ([1, 0.2, 3], [1, 3, 5, 7]) %!error ... %! invglike ([1.5, 0.2], [1:5], [0, 0, 0]) %!error ... %! invglike ([1.5, 0.2], [1:5], [0, 0, 0, 0, 0], [1, 1, 1]) %!error ... %! invglike ([1.5, 0.2], [1:5], [], [1, 1, 1]) statistics-release-1.9.2/inst/Distribution_Fitting/logifit.m000066400000000000000000000225301524624707500243040ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} logifit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} logifit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} logifit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} logifit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} logifit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} logifit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate mean and confidence intervals for the logistic distribution. ## ## @code{@var{mu0} = logifit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the logistic distribution given the data in ## @var{x}. @qcode{@var{paramhat}(1)} is the scale parameter, @var{mu}, and ## @qcode{@var{paramhat}(2)} is the shape parameter, @var{s}. ## ## @code{[@var{paramhat}, @var{paramci}] = logifit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = logifit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = logifit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = logifit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@dots{}] = logifit (@dots{}, @var{options})} specifies control ## parameters for the iterative algorithm used to compute ML estimates with the ## @code{fminsearch} function. @var{options} is a structure with the following ## fields and their default values: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## Further information about the logistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Logistic_distribution} ## ## @seealso{logicdf, logiinv, logipdf, logirnd, logilike, logistat} ## @end deftypefn function [paramhat, paramci] = logifit (x, alpha, censor, freq, options) ## Check input arguments if (! isvector (x)) error ("logifit: X must be a vector."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("logifit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("logifit: X and CENSOR vectors mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("logifit: X and FREQ vectors mismatch."); endif ## Get options structure or add defaults if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("logifit: 'options' 5th argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Expand frequency and censor vectors (if necessary) if (! all (freq == 1)) xf = []; cf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; cf = [cf, repmat(censor(i), 1, freq(i))]; endfor x = xf; freq = ones (size (x)); censor = cf; endif ## Use MLEs of the uncensored data as initial searching values x_uncensored = x(censor == 0); mu0 = mean (x_uncensored); s0 = std (x_uncensored) .* sqrt (3) ./ pi; x0 = [mu0, s0]; ## Minimize negative log-likelihood to estimate parameters f = @(params) logilike (params, x, censor, freq); [paramhat, ~, err, output] = fminsearch (f, x0, options); ## Handle errors if (err == 0) if (output.funcCount >= options.MaxFunEvals) msg = 'logifit: maximum number of function evaluations are exceeded.'; warning (msg); elseif (output.iterations >= options.MaxIter) warning ("logifit: maximum number of iterations are exceeded."); endif elseif (err < 0) error ("logifit: no solution."); endif ## Compute CIs using a log normal approximation for parameters. if (nargout > 1) ## Compute asymptotic covariance [~, acov] = logilike (paramhat, x, censor, freq); ## Get standard errors se = sqrt (diag (acov))'; ## Get normal quantiles probs = [alpha/2; 1-alpha/2]; ## Compute muci using a normal approximation paramci(:,1) = norminv (probs, paramhat(1), se(1)); ## Compute sci using a normal approximation for log (s) and transform back paramci(:,2) = exp (norminv (probs, log (paramhat(2)), se(2) / paramhat(2))); endif endfunction %!demo %! ## Sample 3 populations from different logistic distributions %! rng (42); %! r1 = logirnd (2, 1, 2000, 1); %! r2 = logirnd (5, 2, 2000, 1); %! r3 = logirnd (9, 4, 2000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, [-6:20], 1); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! ylim ([0, 0.3]); %! xlim ([-5, 20]); %! hold on %! %! ## Estimate their MU and LAMBDA parameters %! mu_sA = logifit (r(:,1)); %! mu_sB = logifit (r(:,2)); %! mu_sC = logifit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [-5:0.5:20]; %! y = logipdf (x, mu_sA(1), mu_sA(2)); %! plot (x, y, '-pr'); %! y = logipdf (x, mu_sB(1), mu_sB(2)); %! plot (x, y, '-sg'); %! y = logipdf (x, mu_sC(1), mu_sC(2)); %! plot (x, y, '-^c'); %! hold off %! legend ({'Normalized HIST of sample 1 with μ=1 and s=0.5', ... %! 'Normalized HIST of sample 2 with μ=2 and s=0.3', ... %! 'Normalized HIST of sample 3 with μ=4 and s=0.5', ... %! sprintf("PDF for sample 1 with estimated μ=%0.2f and s=%0.2f", ... %! mu_sA(1), mu_sA(2)), ... %! sprintf("PDF for sample 2 with estimated μ=%0.2f and s=%0.2f", ... %! mu_sB(1), mu_sB(2)), ... %! sprintf("PDF for sample 3 with estimated μ=%0.2f and s=%0.2f", ... %! mu_sC(1), mu_sC(2))}) %! title ('Three population samples from different logistic distributions') %! hold off ## Test output %!test %! paramhat = logifit ([1:50]); %! paramhat_out = [25.5, 8.7724]; %! assert_equal (paramhat, paramhat_out, 1e-4); %!test %! paramhat = logifit ([1:5]); %! paramhat_out = [3, 0.8645]; %! assert_equal (paramhat, paramhat_out, 1e-4); %!test %! paramhat = logifit ([1:6], [], [], [1 1 1 1 1 0]); %! paramhat_out = [3, 0.8645]; %! assert_equal (paramhat, paramhat_out, 1e-4); %!test %! paramhat = logifit ([1:5], [], [], [1 1 1 1 2]); %! paramhat_out = logifit ([1:5, 5]); %! assert_equal (paramhat, paramhat_out, 1e-4); ## Test input validation %!error logifit (ones (2,5)); %!error logifit ([1, 2, 3, 4, 5], 1.2); %!error logifit ([1, 2, 3, 4, 5], 0); %!error logifit ([1, 2, 3, 4, 5], 'alpha'); %!error ... %! logifit ([1, 2, 3, 4, 5], 0.05, [1 1 0]); %!error ... %! logifit ([1, 2, 3, 4, 5], [], [1 1 0 1 1]'); %!error ... %! logifit ([1, 2, 3, 4, 5], 0.05, zeros (1,5), [1 1 0]); %!error ... %! logifit ([1, 2, 3, 4, 5], [], [], [1 1 0 1 1]'); %!error ... %! logifit ([1, 2, 3, 4, 5], 0.05, [], [], 2); statistics-release-1.9.2/inst/Distribution_Fitting/logilike.m000066400000000000000000000153751524624707500244570ustar00rootroot00000000000000## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} logilike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} logilike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} logilike (@var{params}, @var{x}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} logilike (@var{params}, @var{x}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the logistic distribution. ## ## @code{@var{nlogL} = logilike (@var{params}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the logistic ## distribution with (1) location parameter @var{mu} and (2) scale parameter ## @var{sigma} given in the two-element vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = logilike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{params} are their asymptotic variances. ## ## @code{[@dots{}] = logilike (@var{params}, @var{x}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = logilike (@var{params}, @var{x}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the logistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Logistic_distribution} ## ## @seealso{logicdf, logiinv, logipdf, logirnd, logifit, logistat} ## @end deftypefn function [nlogL, acov] = logilike (params, x, censor, freq) ## Check input arguments if (nargin < 2) error ("logilike: function called with too few input arguments."); endif if (! isvector (x)) error ("logilike: X must be a vector."); endif if (length (params) != 2) error ("logilike: PARAMS must be a two-element vector."); endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("logilike: X and CENSOR vector mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("logilike: X and FREQ vector mismatch."); endif ## Expand frequency and censor vectors (if necessary) if (! all (freq == 1)) xf = []; cf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; cf = [cf, repmat(censor(i), 1, freq(i))]; endfor x = xf; freq = ones (size (x)); censor = cf; endif ## Compute the negative loglikelihood nlogL = loginll (params, x, censor, freq); ## Compute the negative hessian and invert to get the information matrix if (nargout > 1) ei = zeros (1, 2); ej = zeros (1, 2); nH = zeros (2, 2); dp = (eps ^ (1/4)) .* max (abs (params), 1); for i = 1:2 ei(i) = dp(i); for j = 1:(i-1) ej(j) = dp(j); ## Four-point central difference for mixed second partials nH(i,j) = loginll (params+ei+ej, x, censor, freq) ... - loginll (params+ei-ej, x, censor, freq) ... - loginll (params-ei+ej, x, censor, freq) ... + loginll (params-ei-ej, x, censor, freq); ej(j) = 0; endfor ## Five-point central difference for pure second partial nH(i,i) = - loginll (params+2*ei, x, censor, freq) ... + 16 * loginll (params+ei, x, censor, freq) - 30 * nlogL ... + 16 * loginll (params-ei, x, censor, freq) ... - loginll (params-2*ei, x, censor, freq); ei(i) = 0; endfor ## Fill in the upper triangle nH = nH + triu (nH', 1); ## Normalize the second differences to get derivative estimates nH = nH ./ (4 .* dp(:) * dp(:)' + diag (8 * dp(:) .^ 2)); ## Check neg Hessian is positive definite [R, p] = chol (nH); if (p > 0) warning ("logilike: non positive definite Hessian matrix."); acov = NaN (2); return endif ## ACOV estimate is the negative inverse of the Hessian Rinv = inv (R); acov = Rinv * Rinv'; endif endfunction ## Helper function for computing negative loglikelihood function nlogL = loginll (params, x, censor, freq) ## Get parameters mu = params(1); sigma = params(2); ## Compute intermediate values z = (x - mu) ./ sigma; logclogitz = log (1 ./ (1 + exp (z))); k = (z > 700); if (any (k)) logclogitz(k) = z(k); endif L = z + 2 .* logclogitz - log (sigma); n_censored = sum (freq .* censor); ## Handle censored data if (n_censored > 0) censored = (censor == 1); L(censored) = logclogitz(censored); endif ## Sum up the neg log likelihood if (sigma < 0) nlogL = Inf; else nlogL = -sum (freq .* L); endif endfunction ## Test results %!test %! nlogL = logilike ([25.5, 8.7725], [1:50]); %! assert_equal (nlogL, 206.6769, 1e-4); %!test %! nlogL = logilike ([3, 0.8645], [1:5]); %! assert_equal (nlogL, 9.0699, 1e-4); ## Test input validation %!error logilike (3.25) %!error logilike ([5, 0.2], ones (2)) %!error ... %! logilike ([1, 0.2, 3], [1, 3, 5, 7]) %!error ... %! logilike ([1.5, 0.2], [1:5], [0, 0, 0]) %!error ... %! logilike ([1.5, 0.2], [1:5], [0, 0, 0, 0, 0], [1, 1, 1]) %!error ... %! logilike ([1.5, 0.2], [1:5], [], [1, 1, 1]) statistics-release-1.9.2/inst/Distribution_Fitting/loglfit.m000066400000000000000000000237021524624707500243110ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} loglfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} loglfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} loglfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} loglfit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} loglfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} loglfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate mean and confidence intervals for the log-logistic distribution. ## ## @code{@var{mu0} = loglfit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the log-logistic distribution given the data ## in @var{x}. @qcode{@var{paramhat}(1)} is the mean parameter, @var{mu}, and ## @qcode{@var{paramhat}(2)} is the scale parameter, @var{sigma}. ## ## @code{[@var{paramhat}, @var{paramci}] = loglfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = loglfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = loglfit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = loglfit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@dots{}] = loglfit (@dots{}, @var{options})} specifies control ## parameters for the iterative algorithm used to compute ML estimates with the ## @code{fminsearch} function. @var{options} is a structure with the following ## fields and their default values: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## Further information about the loglogistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-logistic_distribution} ## ## OCTAVE/MATLAB use an alternative parameterization given by the pair ## @math{μ, σ}, i.e. @var{mu} and @var{sigma}, in analogy with the logistic ## distribution. Their relation to the @math{α} and @math{b} parameters used ## in Wikipedia are given below: ## ## @itemize ## @item @qcode{@var{mu} = log (@var{a})} ## @item @qcode{@var{sigma} = 1 / @var{a}} ## @end itemize ## ## @seealso{loglcdf, loglinv, loglpdf, loglrnd, logllike, loglstat} ## @end deftypefn function [paramhat, paramci] = loglfit (x, alpha, censor, freq, options) ## Check input arguments if (! isvector (x)) error ("loglfit: X must be a vector."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("loglfit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("loglfit: X and CENSOR vectors mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("loglfit: X and FREQ vectors mismatch."); endif ## Get options structure or add defaults if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("loglfit: 'options' 5th argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Expand frequency and censor vectors (if necessary) if (! all (freq == 1)) xf = []; cf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; cf = [cf, repmat(censor(i), 1, freq(i))]; endfor x = xf; freq = ones (size (x)); censor = cf; endif ## Use MLEs of the uncensored data as initial searching values logx_uncensored = log (x(censor == 0)); a0 = mean (logx_uncensored); b0 = 1 ./ (std (logx_uncensored) .* sqrt (3) ./ pi); x0 = [a0, b0]; ## Minimize negative log-likelihood to estimate parameters f = @(params) logllike (params, x, censor, freq); [paramhat, ~, err, output] = fminsearch (f, x0, options); ## Force positive parameter values paramhat = abs (paramhat); ## Handle errors if (err == 0) if (output.funcCount >= options.MaxFunEvals) msg = 'loglfit: maximum number of function evaluations are exceeded.'; warning (msg); elseif (output.iterations >= options.MaxIter) warning ("loglfit: maximum number of iterations are exceeded."); endif elseif (err < 0) error ("loglfit: no solution."); endif ## Compute CIs using a log normal approximation for parameters. if (nargout > 1) ## Compute asymptotic covariance [~, acov] = logllike (paramhat, x, censor, freq); ## Get standard errors se = sqrt (diag (acov))'; ## Get normal quantiles probs = [alpha/2; 1-alpha/2]; ## Compute muci using a normal approximation paramci(:,1) = norminv (probs, paramhat(1), se(1)); ## Compute sci using a normal approximation for log (s) and transform back paramci(:,2) = exp (norminv (probs, log (paramhat(2)), se(2)/paramhat(2))); endif endfunction %!demo %! ## Sample 3 populations from different log-logistic distributions %! rng (42); %! r1 = loglrnd (0, 1, 2000, 1); %! r2 = loglrnd (0, 0.5, 2000, 1); %! r3 = loglrnd (0, 0.125, 2000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, [0.05:0.1:2.5], 10); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! ylim ([0, 3.5]); %! xlim ([0, 2.0]); %! hold on %! %! ## Estimate their MU and LAMBDA parameters %! a_bA = loglfit (r(:,1)); %! a_bB = loglfit (r(:,2)); %! a_bC = loglfit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [0.01:0.1:2.01]; %! y = loglpdf (x, a_bA(1), a_bA(2)); %! plot (x, y, '-pr'); %! y = loglpdf (x, a_bB(1), a_bB(2)); %! plot (x, y, '-sg'); %! y = loglpdf (x, a_bC(1), a_bC(2)); %! plot (x, y, '-^c'); %! legend ({'Normalized HIST of sample 1 with α=1 and β=1', ... %! 'Normalized HIST of sample 2 with α=1 and β=2', ... %! 'Normalized HIST of sample 3 with α=1 and β=8', ... %! sprintf("PDF for sample 1 with estimated α=%0.2f and β=%0.2f", ... %! a_bA(1), a_bA(2)), ... %! sprintf("PDF for sample 2 with estimated α=%0.2f and β=%0.2f", ... %! a_bB(1), a_bB(2)), ... %! sprintf("PDF for sample 3 with estimated α=%0.2f and β=%0.2f", ... %! a_bC(1), a_bC(2))}) %! title ('Three population samples from different log-logistic distributions') %! hold off ## Test output %!test %! [paramhat, paramci] = loglfit ([1:50]); %! paramhat_out = [3.09717, 0.468525]; %! paramci_out = [2.87261, 0.370616; 3.32174, 0.5923]; %! assert_equal (paramhat, paramhat_out, 1e-5); %! assert_equal (paramci, paramci_out, 1e-5); %!test %! paramhat = loglfit ([1:5]); %! paramhat_out = [1.01124, 0.336449]; %! assert_equal (paramhat, paramhat_out, 1e-5); %!test %! paramhat = loglfit ([1:6], [], [], [1 1 1 1 1 0]); %! paramhat_out = [1.01124, 0.336449]; %! assert_equal (paramhat, paramhat_out, 1e-4); %!test %! paramhat = loglfit ([1:5], [], [], [1 1 1 1 2]); %! paramhat_out = loglfit ([1:5, 5]); %! assert_equal (paramhat, paramhat_out, 1e-4); ## Test input validation %!error loglfit (ones (2,5)); %!error loglfit ([1, 2, 3, 4, 5], 1.2); %!error loglfit ([1, 2, 3, 4, 5], 0); %!error loglfit ([1, 2, 3, 4, 5], 'alpha'); %!error ... %! loglfit ([1, 2, 3, 4, 5], 0.05, [1 1 0]); %!error ... %! loglfit ([1, 2, 3, 4, 5], [], [1 1 0 1 1]'); %!error ... %! loglfit ([1, 2, 3, 4, 5], 0.05, zeros (1,5), [1 1 0]); %!error ... %! loglfit ([1, 2, 3, 4, 5], [], [], [1 1 0 1 1]'); %!error ... %! loglfit ([1, 2, 3, 4, 5], 0.05, [], [], 2); statistics-release-1.9.2/inst/Distribution_Fitting/logllike.m000066400000000000000000000163671524624707500244640ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} logllike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} logllike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} logllike (@var{params}, @var{x}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} logllike (@var{params}, @var{x}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the log-logistic distribution. ## ## @code{@var{nlogL} = logllike (@var{params}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the log-logistic ## distribution with (1) scale parameter @var{a} and (2) shape parameter @var{b} ## given in the two-element vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = logllike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{params} are their asymptotic variances. ## ## @code{[@dots{}] = logllike (@var{params}, @var{x}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = logllike (@var{params}, @var{x}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the loglogistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-logistic_distribution} ## ## OCTAVE/MATLAB use an alternative parameterization given by the pair ## @math{μ, σ}, i.e. @var{mu} and @var{sigma}, in analogy with the logistic ## distribution. Their relation to the @math{α} and @math{b} parameters used ## in Wikipedia are given below: ## ## @itemize ## @item @qcode{@var{mu} = log (@var{a})} ## @item @qcode{@var{sigma} = 1 / @var{a}} ## @end itemize ## ## @seealso{loglcdf, loglinv, loglpdf, loglrnd, loglfit, loglstat} ## @end deftypefn function [nlogL, acov] = logllike (params, x, censor, freq) ## Check input arguments if (nargin < 2) error ("logllike: function called with too few input arguments."); endif if (! isvector (x)) error ("logllike: X must be a vector."); endif if (length (params) != 2) error ("logllike: PARAMS must be a two-element vector."); endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("logllike: X and CENSOR vector mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("logllike: X and FREQ vector mismatch."); endif ## Expand frequency and censor vectors (if necessary) if (! all (freq == 1)) xf = []; cf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; cf = [cf, repmat(censor(i), 1, freq(i))]; endfor x = xf; freq = ones (size (x)); censor = cf; endif ## Compute the negative loglikelihood nlogL = loglnll (params, x, censor, freq); ## Compute the negative hessian and invert to get the information matrix if (nargout > 1) ei = zeros (1, 2); ej = zeros (1, 2); nH = zeros (2, 2); dp = (eps ^ (1/4)) .* max (abs (params), 1); for i = 1:2 ei(i) = dp(i); for j = 1:(i-1) ej(j) = dp(j); ## Four-point central difference for mixed second partials nH(i,j) = loglnll (params+ei+ej, x, censor, freq) ... - loglnll (params+ei-ej, x, censor, freq) ... - loglnll (params-ei+ej, x, censor, freq) ... + loglnll (params-ei-ej, x, censor, freq); ej(j) = 0; endfor ## Five-point central difference for pure second partial nH(i,i) = - loglnll (params+2*ei, x, censor, freq) ... + 16 * loglnll (params+ei, x, censor, freq) - 30 * nlogL ... + 16 * loglnll (params-ei, x, censor, freq) ... - loglnll (params-2*ei, x, censor, freq); ei(i) = 0; endfor ## Fill in the upper triangle nH = nH + triu (nH', 1); ## Normalize the second differences to get derivative estimates nH = nH ./ (4 .* dp(:) * dp(:)' + diag (8 * dp(:) .^ 2)); ## Check neg Hessian is positive definite [R, p] = chol (nH); if (p > 0) warning ("logllike: non positive definite Hessian matrix."); acov = NaN (2); return endif ## ACOV estimate is the negative inverse of the Hessian Rinv = inv (R); acov = Rinv * Rinv'; endif endfunction ## Helper function for computing negative loglikelihood function nlogL = loglnll (params, x, censor, freq) ## Get parameters mu = params(1); sigma = params(2); ## Compute intermediate values z = (log (x) - mu) ./ sigma; logclogitz = log (1 ./ (1 + exp (z))); k = (z > 708); if (any (k)) logclogitz(k) = z(k); endif L = z + 2 .* logclogitz - log (sigma) - log (x); n_censored = sum (freq .* censor); ## Handle censored data if (n_censored > 0) censored = (censor == 1); L(censored) = logclogitz(censored); endif ## Sum up the neg log likelihood nlogL = -sum (freq .* L); endfunction ## Test output %!test %! [nlogL, acov] = logllike ([3.09717, 0.468525], [1:50]); %! assert_equal (nlogL, 211.2965, 1e-4); %! assert_equal (acov, [0.0131, -0.0007; -0.0007, 0.0031], 1e-4); %!test %! [nlogL, acov] = logllike ([1.01124, 0.336449], [1:5]); %! assert_equal (nlogL, 9.2206, 1e-4); %! assert_equal (acov, [0.0712, -0.0032; -0.0032, 0.0153], 1e-4); ## Test input validation %!error logllike (3.25) %!error logllike ([5, 0.2], ones (2)) %!error ... %! logllike ([1, 0.2, 3], [1, 3, 5, 7]) %!error ... %! logllike ([1.5, 0.2], [1:5], [0, 0, 0]) %!error ... %! logllike ([1.5, 0.2], [1:5], [0, 0, 0, 0, 0], [1, 1, 1]) %!error ... %! logllike ([1.5, 0.2], [1:5], [], [1, 1, 1]) statistics-release-1.9.2/inst/Distribution_Fitting/lognfit.m000066400000000000000000000210021524624707500243020ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## Octave is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or (at ## your option) any later version. ## ## Octave is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Octave; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} lognfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} lognfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} lognfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} lognfit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} lognfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} lognfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate parameters and confidence intervals for the lognormal distribution. ## ## @code{@var{paramhat} = lognfit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the lognormal distribution given the data in ## vector @var{x}. @qcode{@var{paramhat}([1, 2])} corresponds to the mean and ## standard deviation, respectively, of the associated normal distribution. ## ## If a random variable follows this distribution, its logarithm is normally ## distributed with mean @var{mu} and standard deviation @var{sigma}. ## ## @code{[@var{paramhat}, @var{paramci}] = lognfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = lognfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = lognfit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = lognfit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@dots{}] = lognfit (@dots{}, @var{options})} specifies control ## parameters for the iterative algorithm used to compute ML estimates with the ## @code{fminsearch} function. @var{options} is a structure with the following ## fields and their default values: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## With no censor, the estimate of the standard deviation, ## @qcode{@var{paramhat}(2)}, is the square root of the unbiased estimate of the ## variance of @qcode{log (@var{x})}. With censored data, the maximum ## likelihood estimate is returned. ## ## Further information about the lognormal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-normal_distribution} ## ## @seealso{logncdf, logninv, lognpdf, lognrnd, lognlike, lognstat} ## @end deftypefn function [paramhat, paramci] = lognfit (x, alpha, censor, freq, options) ## Check X for valid data if (! isvector (x) || ! isnumeric (x) || any (x <= 0)) error ("lognfit: X must be a numeric vector of positive values."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("lognfit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = []; elseif (! isequal (size (x), size (censor))) error ("lognfit: X and CENSOR vectors mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = []; elseif (! isequal (size (x), size (freq))) error ("lognfit: X and FREQ vectors mismatch."); endif ## Check options structure or add defaults if (nargin > 4 && ! isempty (options)) if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("lognfit: 'options' 5th argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif else options = []; endif ## Fit a normal distribution to the logged data if (nargout <= 1) [muhat, sigmahat] = normfit (log (x), alpha, censor, freq, options); paramhat = [muhat, sigmahat]; else [muhat, sigmahat, muci, sigmaci] = normfit (log (x), alpha, ... censor, freq, options); paramhat = [muhat, sigmahat]; paramci = [muci, sigmaci]; endif endfunction %!demo %! ## Sample 3 populations from 3 different log-normal distributions %! rng (42); %! r1 = lognrnd (0, 0.25, 1000, 1); %! r2 = lognrnd (0, 0.5, 1000, 1); %! r3 = lognrnd (0, 1, 1000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, 30, 2); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! hold on %! %! ## Estimate their mu and sigma parameters %! mu_sigmaA = lognfit (r(:,1)); %! mu_sigmaB = lognfit (r(:,2)); %! mu_sigmaC = lognfit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [0:0.1:6]; %! y = lognpdf (x, mu_sigmaA(1), mu_sigmaA(2)); %! plot (x, y, '-pr'); %! y = lognpdf (x, mu_sigmaB(1), mu_sigmaB(2)); %! plot (x, y, '-sg'); %! y = lognpdf (x, mu_sigmaC(1), mu_sigmaC(2)); %! plot (x, y, '-^c'); %! ylim ([0, 2]) %! xlim ([0, 6]) %! hold off %! legend ({'Normalized HIST of sample 1 with mu=0, σ=0.25', ... %! 'Normalized HIST of sample 2 with mu=0, σ=0.5', ... %! 'Normalized HIST of sample 3 with mu=0, σ=1', ... %! sprintf("PDF for sample 1 with estimated mu=%0.2f and σ=%0.2f", ... %! mu_sigmaA(1), mu_sigmaA(2)), ... %! sprintf("PDF for sample 2 with estimated mu=%0.2f and σ=%0.2f", ... %! mu_sigmaB(1), mu_sigmaB(2)), ... %! sprintf("PDF for sample 3 with estimated mu=%0.2f and σ=%0.2f", ... %! mu_sigmaC(1), mu_sigmaC(2))}, 'location', 'northeast') %! title ('Three population samples from different log-normal distributions') %! hold off ## Test output %!test %! randn ('seed', 1); %! x = lognrnd (3, 5, [1000, 1]); %! [paramhat, paramci] = lognfit (x, 0.01); %! assert_equal (paramci(1,1) < 3, true); %! assert_equal (paramci(1,2) > 3, true); %! assert_equal (paramci(2,1) < 5, true); %! assert_equal (paramci(2,2) > 5, true); ## Test input validation %!error ... %! lognfit (ones (20,3)) %!error ... %! lognfit ({1, 2, 3, 4, 5}) %!error ... %! lognfit ([-1, 2, 3, 4, 5]) %!error lognfit (ones (20,1), 0) %!error lognfit (ones (20,1), -0.3) %!error lognfit (ones (20,1), 1.2) %!error lognfit (ones (20,1), [0.05, 0.1]) %!error lognfit (ones (20,1), 0.02+i) %!error ... %! lognfit (ones (20,1), [], zeros (15,1)) %!error ... %! lognfit (ones (20,1), [], zeros (20,1), ones (25,1)) %!error lognfit (ones (20,1), [], zeros (20,1), ones (20,1), 'options') statistics-release-1.9.2/inst/Distribution_Fitting/lognlike.m000066400000000000000000000142331524624707500244540ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## Octave is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or (at ## your option) any later version. ## ## Octave is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Octave; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} lognlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{avar}] =} lognlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} lognlike (@var{params}, @var{x}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} lognlike (@var{params}, @var{x}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the lognormal distribution. ## ## @code{@var{nlogL} = lognlike (@var{params}, @var{x})} returns the negative ## log-likelihood of the data in @var{x} corresponding to the lognormal ## distribution with (1) location parameter @var{mu} and (2) scale parameter ## @var{sigma} given in the two-element vector @var{params}, which correspond to ## the mean and standard deviation of the associated normal distribution. ## Missing values, @qcode{NaNs}, are ignored. Negative values of @var{x} are ## treated as missing values. ## ## If a random variable follows this distribution, its logarithm is normally ## distributed with mean @var{mu} and standard deviation @var{sigma}. ## ## @code{[@var{nlogL}, @var{avar}] = lognlike (@var{params}, @var{x})} ## returns the inverse of Fisher's information matrix, @var{avar}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{avar} are their asymptotic variances. @var{avar} ## is based on the observed Fisher's information, not the expected information. ## ## @code{[@dots{}] = lognlike (@var{params}, @var{x}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = lognlike (@var{params}, @var{x}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the lognormal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-normal_distribution} ## ## @seealso{logncdf, logninv, lognpdf, lognrnd, lognfit, lognstat} ## @end deftypefn function [nlogL, avar] = lognlike (params, x, censor, freq) ## Check input arguments if (nargin < 2) error ("lognlike: function called with too few input arguments."); endif if (! isvector (x)) error ("lognlike: X must be a vector."); endif if (numel (params) != 2) error ("lognlike: PARAMS must be a two-element vector."); endif if (nargin < 3 || isempty (censor)) censor = []; elseif (! isequal (size (x), size (censor))) error ("lognlike: X and CENSOR vectors mismatch."); endif if nargin < 4 || isempty (freq) freq = []; elseif (isequal (size (x), size (freq))) nulls = find (freq == 0); if (numel (nulls) > 0) x(nulls) = []; if (numel (censor) == numel (freq)) censor(nulls) = []; endif freq(nulls) = []; endif else error ("lognlike: X and FREQ vectors mismatch."); endif ## Treat negative data in X as missing values x(x < 0) = NaN; ## Calculate on log data logx = log (x); if (nargout <= 1) nlogL = normlike (params, logx, censor, freq); else [nlogL, avar] = normlike (params, logx, censor, freq); endif ## Compute censored and frequency if (isempty (freq)) freq = 1; endif if (isempty (censor)) censor = 0; endif nlogL = nlogL + sum (freq .* logx .* (1 - censor)); endfunction ## Test output %!test %! x = 1:50; %! [nlogL, avar] = lognlike ([0, 0.25], x); %! avar_out = [-5.4749e-03, 2.8308e-04; 2.8308e-04, -1.1916e-05]; %! assert_equal (nlogL, 3962.330333301793, 1e-10); %! assert_equal (avar, avar_out, 1e-7); %!test %! x = 1:50; %! [nlogL, avar] = lognlike ([0, 0.25], x * 0.5); %! avar_out = [-7.6229e-03, 4.8722e-04; 4.8722e-04, -2.6754e-05]; %! assert_equal (nlogL, 2473.183051225747, 1e-10); %! assert_equal (avar, avar_out, 1e-7); %!test %! x = 1:50; %! [nlogL, avar] = lognlike ([0, 0.5], x); %! avar_out = [-2.1152e-02, 2.2017e-03; 2.2017e-03, -1.8535e-04]; %! assert_equal (nlogL, 1119.072424020455, 1e-12); %! assert_equal (avar, avar_out, 1e-6); %!test %! x = 1:50; %! censor = ones (1, 50); %! censor([2, 4, 6, 8, 12, 14]) = 0; %! [nlogL, avar] = lognlike ([0, 0.5], x, censor); %! avar_out = [-1.9823e-02, 2.0370e-03; 2.0370e-03, -1.6618e-04]; %! assert_equal (nlogL, 1091.746371145497, 1e-12); %! assert_equal (avar, avar_out, 1e-6); %!test %! x = 1:50; %! censor = ones (1, 50); %! censor([2, 4, 6, 8, 12, 14]) = 0; %! [nlogL, avar] = lognlike ([0, 1], x, censor); %! avar_out = [-6.8634e-02, 1.3968e-02; 1.3968e-02, -2.1664e-03]; %! assert_equal (nlogL, 349.3969104144271, 1e-12); %! assert_equal (avar, avar_out, 1e-6); ## Test input validation %!error ... %! lognlike ([12, 15]); %!error lognlike ([12, 15], ones (2)); %!error ... %! lognlike ([12, 15, 3], [1:50]); %!error ... %! lognlike ([12, 15], [1:50], [1, 2, 3]); %!error ... %! lognlike ([12, 15], [1:50], [], [1, 2, 3]); statistics-release-1.9.2/inst/Distribution_Fitting/nakafit.m000066400000000000000000000223441524624707500242670ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} nakafit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} nakafit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} nakafit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} nakafit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} nakafit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} nakafit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate mean and confidence intervals for the Nakagami distribution. ## ## @code{@var{mu0} = nakafit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the Nakagami distribution given the data in ## @var{x}. @qcode{@var{paramhat}(1)} is the shape parameter, @var{mu}, and ## @qcode{@var{paramhat}(2)} is the spread parameter, @var{omega}. ## ## @code{[@var{paramhat}, @var{paramci}] = nakafit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = nakafit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = nakafit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = nakafit (@var{params}, @var{x}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} must contain non-negative integer frequencies for the ## corresponding elements in @var{x}. By default, or if left empty, ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@dots{}] = nakafit (@dots{}, @var{options})} specifies control ## parameters for the iterative algorithm used to compute ML estimates with the ## @code{fminsearch} function. @var{options} is a structure with the following ## fields and their default values: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## Further information about the Nakagami distribution can be found at ## @url{https://en.wikipedia.org/wiki/Nakagami_distribution} ## ## @seealso{nakacdf, nakainv, nakapdf, nakarnd, nakalike, nakastat} ## @end deftypefn function [paramhat, paramci] = nakafit (x, alpha, censor, freq, options) ## Check input arguments if (! isvector (x)) error ("nakafit: X must be a vector."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("nakafit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("nakafit: X and CENSOR vectors mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("nakafit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("nakafit: FREQ must not contain negative values."); elseif (any (fix (freq) != freq)) error ("nakafit: FREQ must contain integer values."); endif ## Get options structure or add defaults if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("nakafit: 'options' 5th argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Expand frequency and censor vectors (if necessary) if (! all (freq == 1)) xf = []; cf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; cf = [cf, repmat(censor(i), 1, freq(i))]; endfor x = xf; freq = ones (size (x)); censor = cf; endif ## Get parameter estimates from the Gamma distribution paramhat = gamfit (x .^ 2, alpha, censor, freq, options); ## Transform back to Nakagami parameters paramhat(2) = paramhat(1) .* paramhat(2); ## Compute CIs using a log normal approximation for parameters. if (nargout > 1) ## Compute asymptotic covariance [~, acov] = nakalike (paramhat, x, censor, freq); ## Get standard errors se = sqrt (diag (acov))'; ## Get normal quantiles probs = [alpha/2; 1-alpha/2]; ## Compute muci using a normal approximation paramci(:,1) = norminv (probs, paramhat(1), se(1)); ## Compute omegaci using a normal approximation for log (omega) paramci(:,2) = exp (norminv (probs, log (paramhat(2)), log (se(2)))); endif endfunction %!demo %! ## Sample 3 populations from different Nakagami distributions %! randg ('state', 42); %! r1 = nakarnd (0.5, 1, 2000, 1); %! r2 = nakarnd (5, 1, 2000, 1); %! r3 = nakarnd (2, 2, 2000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, [0.05:0.1:3.5], 10); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! ylim ([0, 2.5]); %! xlim ([0, 3.0]); %! hold on %! %! ## Estimate their MU and LAMBDA parameters %! mu_omegaA = nakafit (r(:,1)); %! mu_omegaB = nakafit (r(:,2)); %! mu_omegaC = nakafit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [0.01:0.1:3.01]; %! y = nakapdf (x, mu_omegaA(1), mu_omegaA(2)); %! plot (x, y, '-pr'); %! y = nakapdf (x, mu_omegaB(1), mu_omegaB(2)); %! plot (x, y, '-sg'); %! y = nakapdf (x, mu_omegaC(1), mu_omegaC(2)); %! plot (x, y, '-^c'); %! legend ({'Normalized HIST of sample 1 with μ=0.5 and ω=1', ... %! 'Normalized HIST of sample 2 with μ=5 and ω=1', ... %! 'Normalized HIST of sample 3 with μ=2 and ω=2', ... %! sprintf("PDF for sample 1 with estimated μ=%0.2f and ω=%0.2f", ... %! mu_omegaA(1), mu_omegaA(2)), ... %! sprintf("PDF for sample 2 with estimated μ=%0.2f and ω=%0.2f", ... %! mu_omegaB(1), mu_omegaB(2)), ... %! sprintf("PDF for sample 3 with estimated μ=%0.2f and ω=%0.2f", ... %! mu_omegaC(1), mu_omegaC(2))}) %! title ('Three population samples from different Nakagami distributions') %! hold off ## Test output %!test %! paramhat = nakafit ([1:50]); %! paramhat_out = [0.7355, 858.5]; %! assert_equal (paramhat, paramhat_out, 1e-4); %!test %! paramhat = nakafit ([1:5]); %! paramhat_out = [1.1740, 11]; %! assert_equal (paramhat, paramhat_out, 1e-4); %!test %! paramhat = nakafit ([1:6], [], [], [1 1 1 1 1 0]); %! paramhat_out = [1.1740, 11]; %! assert_equal (paramhat, paramhat_out, 1e-4); %!test %! paramhat = nakafit ([1:5], [], [], [1 1 1 1 2]); %! paramhat_out = nakafit ([1:5, 5]); %! assert_equal (paramhat, paramhat_out, 1e-4); ## Test input validation %!error nakafit (ones (2,5)); %!error nakafit ([1, 2, 3, 4, 5], 1.2); %!error nakafit ([1, 2, 3, 4, 5], 0); %!error nakafit ([1, 2, 3, 4, 5], 'alpha'); %!error ... %! nakafit ([1, 2, 3, 4, 5], 0.05, [1 1 0]); %!error ... %! nakafit ([1, 2, 3, 4, 5], [], [1 1 0 1 1]'); %!error ... %! nakafit ([1, 2, 3, 4, 5], 0.05, zeros (1,5), [1 1 0]); %!error ... %! nakafit ([1, 2, 3, 4, 5], [], [], [1 1 0 1 1]'); %!error ... %! nakafit ([1, 2, 3, 4, 5], [], [], [1 1 -1 1 1]); %!error ... %! nakafit ([1, 2, 3, 4, 5], [], [], [1 1 1.5 1 1]); %!error ... %! nakafit ([1, 2, 3, 4, 5], 0.05, [], [], 2); statistics-release-1.9.2/inst/Distribution_Fitting/nakalike.m000066400000000000000000000256741524624707500244420ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} nakalike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} nakalike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} nakalike (@var{params}, @var{x}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} nakalike (@var{params}, @var{x}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the Nakagami distribution. ## ## @code{@var{nlogL} = nakalike (@var{params}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the Nakagami ## distribution with (1) shape parameter @var{mu} and (2) spread parameter ## @var{omega} given in the two-element vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = nakalike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{params} are their asymptotic variances. ## ## @code{[@dots{}] = nakalike (@var{params}, @var{x}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = nakalike (@var{params}, @var{x}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} must contain non-negative integer frequencies for the ## corresponding elements in @var{x}. By default, or if left empty, ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the Nakagami distribution can be found at ## @url{https://en.wikipedia.org/wiki/Nakagami_distribution} ## ## @seealso{nakacdf, nakainv, nakapdf, nakarnd, nakafit, nakastat} ## @end deftypefn function [nlogL, acov] = nakalike (params, x, censor, freq) ## Check input arguments if (nargin < 2) error ("nakalike: function called with too few input arguments."); endif if (! isvector (x)) error ("nakalike: X must be a vector."); endif if (length (params) != 2) error ("nakalike: PARAMS must be a two-element vector."); endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("nakalike: X and CENSOR vector mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("nakalike: X and FREQ vector mismatch."); elseif (any (freq < 0)) error ("nakalike: FREQ must not contain negative values."); elseif (any (fix (freq) != freq)) error ("nakafit: FREQ must contain integer values."); endif ## Expand frequency and censor vectors (if necessary) if (! all (freq == 1)) xf = []; cf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; cf = [cf, repmat(censor(i), 1, freq(i))]; endfor x = xf; freq = ones (size (x)); censor = cf; endif ## Get parameters mu = params(1); omega = params(2); log_a = gammaln (mu); log_b = log (omega / mu); z = x .^ 2 ./ (omega / mu); log_z = log (z); L = (mu - 1) .* log_z - z - log_a - log_b + log (2 .* x); ## Handle censored data n_censored = sum (freq .* censor); if (n_censored > 0) censored = (censor == 1); z_censored = z(censored); [S, dS] = dgammainc (z_censored, mu); L(censored) = log (S); endif ## Sum up the neg log likelihood nlogL = -sum (freq .* L); ## Compute asymptotic covariance if (nargout > 1) ## Compute first order central differences of the log-likelihood gradient dp = 0.0001 .* max (abs (params), 1); ngrad_p1 = logl_grad (params + [dp(1), 0], x, censor, freq); ngrad_m1 = logl_grad (params - [dp(1), 0], x, censor, freq); ngrad_p2 = logl_grad (params + [0, dp(2)], x, censor, freq); ngrad_m2 = logl_grad (params - [0, dp(2)], x, censor, freq); ## Compute negative Hessian by normalizing the differences by the increment nH = [(ngrad_p1(:) - ngrad_m1(:))./(2 * dp(1)), ... (ngrad_p2(:) - ngrad_m2(:))./(2 * dp(2))]; ## Force neg Hessian being symmetric nH = 0.5 .* (nH + nH'); ## Check neg Hessian is positive definite [R, p] = chol (nH); if (p > 0) warning ("nakalike: non positive definite Hessian matrix."); acov = NaN (2); return endif ## ACOV estimate is the negative inverse of the Hessian. Rinv = inv (R); acov = Rinv * Rinv; endif endfunction ## Helper function for computing negative gradient function ngrad = logl_grad (params, x, censor, freq) mu = params(1); omega = params(2); ## Transform to Gamma parameters log_a = gammaln (mu); log_b = log (omega / mu); z = x .^ 2 ./ (omega / mu); log_z = log (z); dL1 = log_z - psi (mu); dL2 = (z - mu) ./ (omega / mu); ## Handle censored data n_censored = sum (freq .* censor); if (n_censored > 0) censored = (censor == 1); z_censored = z(censored); [S, dS] = dgammainc (z_censored, a); dL1(censored) = dS ./ S; tmp = mu .* log_z(censored) - log_b - z_censored - log_a; dL2(censored) = exp (tmp) ./ S; endif ngrad = -[sum(freq .* dL1), sum(freq .* dL2)]; ## Transform back to Nakagami parameters ngrad = ngrad * [1, 0; (-omega ./ (mu .^ 2)), (1 ./ mu)]; endfunction ## Compute the incomplete Gamma function with its 1st and 2nd derivatives function [y, dy, d2y] = dgammainc (x, k) ## Initialize return variables y = nan (size (x)); dy = y; d2y = y; ## Use approximation for K > 2^20 ulim = 2^20; is_lim = find (k > ulim); if (! isempty (is_lim)) x(is_lim) = max (ulim - 1/3 + sqrt (ulim ./ k(is_lim)) .* ... (x(is_lim) - (k(is_lim) - 1/3)), 0); k(is_lim) = ulim; endif ## For x < k+1 is_lo = find (x < k + 1 & x != 0); if (! isempty (is_lo)) x_lo = x(is_lo); k_lo = k(is_lo); k_1 = k_lo; step = 1; d1st = 0; d2st = 0; stsum = step; d1sum = d1st; d2sum = d2st; while norm (step, 'inf') >= 100 * eps (norm (stsum, 'inf')) k_1 += 1; step = step .* x_lo ./ k_1; d1st = (d1st .* x_lo - step) ./ k_1; d2st = (d2st .* x_lo - 2 .* d1st) ./ k_1; stsum = stsum + step; d1sum = d1sum + d1st; d2sum = d2sum + d2st; endwhile fklo = exp (-x_lo + k_lo .* log (x_lo) - gammaln (k_lo + 1)); y_lo = fklo .* stsum; ## Fix very small k y_lo(x_lo > 0 & y_lo > 1) = 1; ## Compute 1st derivative dlogfklo = (log (x_lo) - psi (k_lo + 1)); d1fklo = fklo .* dlogfklo; d1y_lo = d1fklo .* stsum + fklo .* d1sum; ## Compute 2nd derivative d2fklo = d1fklo .* dlogfklo - fklo .* psi (1, k_lo + 1); d2y_lo = d2fklo .* stsum + 2 .* d1fklo .* d1sum + fklo .* d2sum; ## Considering the upper tail y(is_lo) = 1 - y_lo; dy(is_lo) = -d1y_lo; d2y(is_lo) = -d2y_lo; endif ## For x >= k+1 is_hi = find (x >= k+1); if (! isempty (is_hi)) x_hi = x(is_hi); k_hi = k(is_hi); zc = 0; k0 = 0; k1 = k_hi; x0 = 1; x1 = x_hi; d1k0 = 0; d1k1 = 1; d1x0 = 0; d1x1 = 0; d2k0 = 0; d2k1 = 0; d2x0 = 0; d2x2 = 0; kx = k_hi ./ x_hi; d1kx = 1 ./ x_hi; d2kx = 0; start = 1; while norm (d2kx - start, 'Inf') > 100 * eps (norm (d2kx, 'Inf')) rescale = 1 ./ x1; zc += 1; n_k = zc - k_hi; d2k0 = (d2k1 + d2k0 .* n_k - 2 .* d1k0) .* rescale; d2x0 = (d2x2 + d2x0 .* n_k - 2 .* d1x0) .* rescale; d1k0 = (d1k1 + d1k0 .* n_k - k0) .* rescale; d1x0 = (d1x1 + d1x0 .* n_k - x0) .* rescale; k0 = (k1 + k0 .* n_k) .* rescale; x0 = 1 + (x0 .* n_k) .* rescale; nrescale = zc .* rescale; d2k1 = d2k0 .* x_hi + d2k1 .* nrescale; d2x2 = d2x0 .* x_hi + d2x2 .* nrescale; d1k1 = d1k0 .* x_hi + d1k1 .* nrescale; d1x1 = d1x0 .* x_hi + d1x1 .* nrescale; k1 = k0 .* x_hi + k1 .* nrescale; x1 = x0 .* x_hi + zc; start = d2kx; kx = k1 ./ x1; d1kx = (d1k1 - kx .* d1x1) ./ x1; d2kx = (d2k1 - d1kx .* d1x1 - kx .* d2x2 - d1kx .* d1x1) ./ x1; endwhile fkhi = exp (-x_hi + k_hi .* log (x_hi) - gammaln (k_hi + 1)); y_hi = fkhi .* kx; ## Compute 1st derivative dlogfkhi = (log (x_hi) - psi (k_hi + 1)); d1fkhi = fkhi .* dlogfkhi; d1y_hi = d1fkhi .* kx + fkhi .* d1kx; ## Compute 2nd derivative d2fkhi = d1fkhi .* dlogfkhi - fkhi .* psi (1, k_hi + 1); d2y_hi = d2fkhi .* kx + 2 .* d1fkhi .* d1kx + fkhi .* d2kx; ## Considering the upper tail y(is_hi) = y_hi; dy(is_hi) = d1y_hi; d2y(is_hi) = d2y_hi; endif ## Handle x == 0 is_x0 = find (x == 0); if (! isempty (is_x0)) ## Considering the upper tail y(is_x0) = 1; dy(is_x0) = 0; d2y(is_x0) = 0; endif ## Handle k == 0 is_k0 = find (k == 0); if (! isempty (is_k0)) is_k0x0 = find (k == 0 & x == 0); ## Considering the upper tail y(is_k0) = 0; dy(is_k0x0) = Inf; d2y(is_k0x0) = -Inf; endif endfunction ## Test output %!test %! nlogL = nakalike ([0.735504, 858.5], [1:50]); %! assert_equal (nlogL, 202.8689, 1e-4); %!test %! nlogL = nakalike ([1.17404, 11], [1:5]); %! assert_equal (nlogL, 8.6976, 1e-4); %!test %! nlogL = nakalike ([1.17404, 11], [1:5], [], [1, 1, 1, 1, 1]); %! assert_equal (nlogL, 8.6976, 1e-4); %!test %! nlogL = nakalike ([1.17404, 11], [1:6], [], [1, 1, 1, 1, 1, 0]); %! assert_equal (nlogL, 8.6976, 1e-4); ## Test input validation %!error nakalike (3.25) %!error nakalike ([5, 0.2], ones (2)) %!error ... %! nakalike ([1, 0.2, 3], [1, 3, 5, 7]) %!error ... %! nakalike ([1.5, 0.2], [1:5], [0, 0, 0]) %!error ... %! nakalike ([1.5, 0.2], [1:5], [0, 0, 0, 0, 0], [1, 1, 1]) %!error ... %! nakalike ([1.5, 0.2], [1:5], [], [1, 1, 1]) %!error ... %! nakalike ([1.5, 0.2], [1:5], [], [1, 1, 1, 1, -1]) statistics-release-1.9.2/inst/Distribution_Fitting/nbinfit.m000066400000000000000000000307251524624707500243050ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} nbinfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} nbinfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} nbinfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} nbinfit (@var{x}, @var{alpha}, @var{freq}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} nbinfit (@var{x}, @var{alpha}, @var{options}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} nbinfit (@var{x}, @var{alpha}, @var{freq}, @var{options}) ## ## Estimate parameter and confidence intervals for the negative binomial ## distribution. ## ## @code{@var{paramhat} = nbinfit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the negative binomial distribution given the ## data in vector @var{x}. @qcode{@var{paramhat}(1)} is the number of successes ## until the experiment is stopped, @var{r}, and @qcode{@var{paramhat}(2)} is ## the probability of success in each experiment, @var{ps}. ## ## @code{[@var{paramhat}, @var{paramci}] = nbinfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@var{paramhat}, @var{paramci}] = nbinfit (@var{x}, @var{alpha})} also ## returns the @qcode{100 * (1 - @var{alpha})} percent confidence intervals of ## the estimated parameter. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. ## ## @code{[@dots{}] = nbinlike (@var{params}, @var{x}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## must contain non-negative integer frequencies for the corresponding elements ## in @var{x}. By default, or if left empty, ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@var{paramhat}, @var{paramci}] = nbinfit (@var{x}, @var{alpha}, ## @var{options})} specifies control parameters for the iterative algorithm used ## to compute ML estimates with the @code{fminsearch} function. @var{options} ## is a structure with the following fields and their default values: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## When @var{r} is an integer, the negative binomial distribution is also known ## as the Pascal distribution and it models the number of failures in @var{x} ## before a specified number of successes is reached in a series of independent, ## identical trials. Its parameters are the probability of success in a single ## trial, @var{ps}, and the number of successes, @var{r}. A special case of the ## negative binomial distribution, when @qcode{@var{r} = 1}, is the geometric ## distribution, which models the number of failures before the first success. ## ## @var{r} can also have non-integer positive values, in which form the negative ## binomial distribution, also known as the Polya distribution, has no ## interpretation in terms of repeated trials, but, like the Poisson ## distribution, it is useful in modeling count data. The negative binomial ## distribution is more general than the Poisson distribution because it has a ## variance that is greater than its mean, making it suitable for count data ## that do not meet the assumptions of the Poisson distribution. In the limit, ## as @var{r} increases to infinity, the negative binomial distribution ## approaches the Poisson distribution. ## ## Further information about the negative binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Negative_binomial_distribution} ## ## @seealso{nbincdf, nbininv, nbinpdf, nbinrnd, nbinlike, nbinstat} ## @end deftypefn function [paramhat, paramci] = nbinfit (x, alpha, varargin) ## Check data in X if (any (x < 0)) error ("nbinfit: X cannot have negative values."); endif if (! isvector (x)) error ("nbinfit: X must be a vector."); endif if (any (x < 0) || any (x != round (x)) || any (isinf (x))) error ("nbinfit: X must be a non-negative integer."); endif ## Check ALPHA if (nargin < 2 || isempty (alpha)) alpha = 0.05; elseif (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("nbinfit: wrong value for ALPHA."); endif ## Add defaults freq = []; options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; ## Check extra arguments for FREQ vector and/or 'options' structure if (nargin > 2) if (numel (varargin) == 1 && isstruct (varargin{1})) options = varargin{1}; elseif (numel (varargin) == 1 && isnumeric (varargin{1})) freq = varargin{1}; elseif (numel (varargin) == 2) freq = varargin{1}; options = varargin{2}; endif if (isempty (freq)) freq = ones (size (x)); endif ## Check for valid freq vector if (! isequal (size (x), size (freq))) error ("nbinfit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("nbinfit: FREQ must not contain negative values."); elseif (any (fix (freq) != freq)) error ("nbinfit: FREQ must contain integer values."); endif ## Check for valid options structure if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("nbinfit: 'options' argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Expand frequency if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; endif ## Ensure that a negative binomial fit is valid. xbar = mean (x); varx = var (x); if (varx <= xbar) paramhat = cast ([Inf, 1.0], class (x)); paramci = cast ([Inf, 1; Inf, 1], class (x)); fprintf ("warning: nbinfit: mean exceeds variance.\n"); return endif ## Use Method of Moments estimates as starting point for MLEs. rhat = (xbar .* xbar) ./ (varx - xbar); ## Minimize negative log-likelihood to estimate parameters by parameterizing ## with mu=r(1-p)/p, so it becomes 1-parameter search for rhat. f = @(rhat) nbinfit_search (rhat, x, numel (x), sum (x), options.TolX); [rhat, ~, err, output] = fminsearch (f, rhat, options); ## Handle errors if (err == 0) if (output.funcCount >= options.MaxFunEvals) warning (strcat ("nbinfit: maximum number of function", ... " evaluations are exceeded.")); elseif (output.iterations >= options.MaxIter) warning ("nbinfit: maximum number of iterations are exceeded."); endif elseif (err < 0) error ("nbinfit: NoSolution."); endif ## Compute parameter estimates pshat = rhat ./ (xbar + rhat); paramhat = [rhat, pshat]; ## Compute confidence interval if (nargout > 1) [~, avar] = nbinlike (paramhat, x); ## Get standard errors sigma = sqrt (diag (avar)); ## Get normal quantiles probs = [alpha/2; 1-alpha/2]; ## Compute paramci using a normal approximation. The bounds are not ## restricted to the parameter space, as MATLAB does not restrict them: a ## bound outside it is what tells the caller the normal approximation has ## broken down, and clamping it away leaves an interval that looks ordinary ## and is not. paramci = norminv ([probs, probs], [paramhat; paramhat], [sigma'; sigma']); endif endfunction ## Helper function for minimizing the negative log-likelihood function nll = nbinfit_search (r, x, nx, sx, tol) if (r < tol) nll = Inf; else xbar = sx / nx; nll = -sum (gammaln (r +x )) + nx * gammaln (r) ... -nx * r * log (r / (xbar + r)) - sx * log (xbar / (xbar + r)); endif endfunction %!demo %! ## Sample 2 populations from different negative binomial distributions %! randg ('state', 42); %! randp ('state', 42); %! r1 = nbinrnd (2, 0.15, 5000, 1); %! r2 = nbinrnd (5, 0.2, 5000, 1); %! r = [r1, r2]; %! %! ## Plot them normalized and fix their colors %! hist (r, [0:51], 1); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! hold on %! %! ## Estimate their probability of success %! r_psA = nbinfit (r(:,1)); %! r_psB = nbinfit (r(:,2)); %! %! ## Plot their estimated PDFs %! x = [0:40]; %! y = nbinpdf (x, r_psA(1), r_psA(2)); %! plot (x, y, '-pg'); %! x = [min(r(:,2)):max(r(:,2))]; %! y = nbinpdf (x, r_psB(1), r_psB(2)); %! plot (x, y, '-sc'); %! ylim ([0, 0.1]) %! xlim ([0, 50]) %! legend ({'Normalized HIST of sample 1 with r=2 and ps=0.15', ... %! 'Normalized HIST of sample 2 with r=5 and ps=0.2', ... %! sprintf("PDF for sample 1 with estimated r=%0.2f and ps=%0.2f", ... %! r_psA(1), r_psA(2)), ... %! sprintf("PDF for sample 2 with estimated r=%0.2f and ps=%0.2f", ... %! r_psB(1), r_psB(2))}) %! title ('Two population samples from negative different binomial distributions') %! hold off ## Test output %!test %! [paramhat, paramci] = nbinfit ([1:50]); %! assert_equal (paramhat, [2.420857, 0.086704], 1e-6); %! assert_equal (paramci(:,1), [1.382702; 3.459012], 1e-6); %! assert_equal (paramci(:,2), [0.049676; 0.123732], 1e-6); %!test %! [paramhat, paramci] = nbinfit ([1:20]); %! assert_equal (paramhat, [3.588233, 0.254697], 1e-6); %! ## The interval is a normal approximation over a numerical Hessian, and %! ## agrees with R2024a to a few parts in a million rather than exactly. %! assert_equal (paramci(:,1), [0.451693; 6.724772], 1e-5); %! assert_equal (paramci(:,2), [0.081143; 0.428251], 1e-5); %!test %! [paramhat, paramci] = nbinfit ([1:10]); %! assert_equal (paramhat, [8.8067, 0.6156], 1e-4); %! assert_equal (paramci(:,1), [-13.0934; 30.7068], 1e-4); %! assert_equal (paramci(:,2), [0.0217; 1.2094], 1e-4); %!test %! [paramhat, paramci] = nbinfit ([1:10], 0.05, ones (1, 10)); %! assert_equal (paramhat, [8.8067, 0.6156], 1e-4); %! assert_equal (paramci(:,1), [-13.0934; 30.7068], 1e-4); %! assert_equal (paramci(:,2), [0.0217; 1.2094], 1e-4); %!test %! [paramhat, paramci] = nbinfit ([1:11], 0.05, [ones(1, 10), 0]); %! assert_equal (paramhat, [8.8067, 0.6156], 1e-4); %! assert_equal (paramci(:,1), [-13.0934; 30.7068], 1e-4); %! assert_equal (paramci(:,2), [0.0217; 1.2094], 1e-4); ## Values below are R2024a's, measured 2026-08-17. %!test %! ## a bound outside the parameter space is reported, not clamped away %! [~, paramci] = nbinfit ([1:10]); %! assert_equal (paramci(1,1) < 0, true); %! assert_equal (paramci(2,2) > 1, true); %! assert_equal (paramci(1,1), -13.093353591937408, 1e-5); %! assert_equal (paramci(2,2), 1.209414934623099, 1e-5); ## Test input validation %!error nbinfit ([-1 2 3 3]) %!error nbinfit (ones (2)) %!error nbinfit ([1 2 1.2 3]) %!error nbinfit ([1 2 3], 0) %!error nbinfit ([1 2 3], 1.2) %!error nbinfit ([1 2 3], [0.02 0.05]) %!error ... %! nbinfit ([1, 2, 3, 4, 5], 0.05, [1, 2, 3, 2]); %!error ... %! nbinfit ([1, 2, 3, 4, 5], 0.05, [1, 2, 3, 2, -1]); %!error ... %! nbinfit ([1, 2, 3, 4, 5], 0.05, [1, 2, 3, 2, 1.5]); %!error ... %! nbinfit ([1, 2, 3, 4, 5], 0.05, struct ('option', 234)); %!error ... %! nbinfit ([1, 2, 3, 4, 5], 0.05, ones (1,5), struct ('option', 234)); statistics-release-1.9.2/inst/Distribution_Fitting/nbinlike.m000066400000000000000000000155071524624707500244500ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} nbinlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{avar}] =} nbinlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{avar}] =} nbinlike (@var{params}, @var{x}, @var{freq}) ## ## Negative log-likelihood for the negative binomial distribution. ## ## @code{@var{nlogL} = nbinlike (@var{params}, @var{x})} returns the negative ## log likelihood of the negative binomial distribution with (1) parameter ## @var{r} and (2) parameter @var{ps}, given in the two-element vector ## @var{params}, where @var{r} is the number of successes until the experiment ## is stopped and @var{ps} is the probability of success in each experiment, ## given the number of failures in @var{x}. ## ## @code{[@var{nlogL}, @var{avar}] = nbinlike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{avar}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{params} are their asymptotic variances. ## ## @code{[@dots{}] = nbinlike (@var{params}, @var{x}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## must contain non-negative integer frequencies for the corresponding elements ## in @var{x}. By default, or if left empty, ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## When @var{r} is an integer, the negative binomial distribution is also known ## as the Pascal distribution and it models the number of failures in @var{x} ## before a specified number of successes is reached in a series of independent, ## identical trials. Its parameters are the probability of success in a single ## trial, @var{ps}, and the number of successes, @var{r}. A special case of the ## negative binomial distribution, when @qcode{@var{r} = 1}, is the geometric ## distribution, which models the number of failures before the first success. ## ## @var{r} can also have non-integer positive values, in which form the negative ## binomial distribution, also known as the Polya distribution, has no ## interpretation in terms of repeated trials, but, like the Poisson ## distribution, it is useful in modeling count data. The negative binomial ## distribution is more general than the Poisson distribution because it has a ## variance that is greater than its mean, making it suitable for count data ## that do not meet the assumptions of the Poisson distribution. In the limit, ## as @var{r} increases to infinity, the negative binomial distribution ## approaches the Poisson distribution. ## ## Further information about the negative binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Negative_binomial_distribution} ## ## @seealso{nbincdf, nbininv, nbinpdf, nbinrnd, nbinfit, nbinstat} ## @end deftypefn function [nlogL, avar] = nbinlike (params, x, freq) ## Check input arguments if (nargin < 2) error ("nbinlike: function called with too few input arguments."); endif if (! isvector (x)) error ("nbinlike: X must be a vector."); endif if (any (x < 0)) error ("nbinlike: X cannot have negative values."); endif if (any (x != fix (x))) error ("nbinlike: number of failures, X, must be integers."); endif if (length (params) != 2) error ("nbinlike: PARAMS must be a two-element vector."); endif if (params(1) <= 0) error (strcat ("nbinlike: number of successes, PARAMS(1), must be", ... " a real positive value.")); endif if (params(2) < 0 || params(2) > 1) error (strcat ("nbinlike: probability of success, PARAMS(2), must be", ... " in the range [0,1].")); endif if (nargin < 3 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("nbinlike: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("nbinlike: FREQ must not contain negative values."); elseif (any (fix (freq) != freq)) error ("nbinlike: FREQ must contain integer values."); endif ## Expand frequency if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; endif ## Compute negative log-likelihood and asymptotic variance r = params(1); ps = params(2); nx = numel (x); glnr = gammaln (r + x) - gammaln (x + 1) - gammaln (r); sumx = sum (x); nlogL = -(sum (glnr) + nx * r * log (ps)) - sumx * log (1 - ps); if (nargout == 2) dL11 = sum (psi (1, r + x) - psi (1, r)); dL12 = nx ./ ps; dL22 = -nx .*r ./ ps .^ 2 - sumx ./ (1 - ps) .^ 2; nH = -[dL11, dL12; dL12, dL22]; if (any (isnan (nH(:)))) avar = [NaN, NaN; NaN, NaN]; else avar = inv (nH); endif endif endfunction ## Test output %!assert_equal (nbinlike ([2.42086, 0.0867043], [1:50]), 205.5942, 1e-4) %!assert_equal (nbinlike ([3.58823, 0.254697], [1:20]), 63.6435, 1e-4) %!assert_equal (nbinlike ([8.80671, 0.615565], [1:10]), 24.7410, 1e-4) %!assert_equal (nbinlike ([22.1756, 0.831306], [1:8]), 17.9528, 1e-4) %!assert_equal (nbinlike ([22.1756, 0.831306], [1:9], [ones(1,8), 0]), 17.9528, 1e-4) ## Test input validation %!error nbinlike (3.25) %!error nbinlike ([5, 0.2], ones (2)) %!error nbinlike ([5, 0.2], [-1, 3]) %!error ... %! nbinlike ([1, 0.2, 3], [1, 3, 5, 7]) %!error nbinlike ([-5, 0.2], [1:15]) %!error nbinlike ([0, 0.2], [1:15]) %!error nbinlike ([5, 1.2], [3, 5]) %!error nbinlike ([5, -0.2], [3, 5]) %!error ... %! nbinlike ([5, 0.2], ones (10, 1), ones (8,1)) %!error ... %! nbinlike ([5, 0.2], ones (1, 8), [1 1 1 1 1 1 1 -1]) %!error ... %! nbinlike ([5, 0.2], ones (1, 8), [1 1 1 1 1 1 1 1.5]) statistics-release-1.9.2/inst/Distribution_Fitting/normfit.m000066400000000000000000000416021524624707500243260ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{muhat} =} normfit (@var{x}) ## @deftypefnx {statistics} {[@var{muhat}, @var{sigmahat}] =} normfit (@var{x}) ## @deftypefnx {statistics} {[@var{muhat}, @var{sigmahat}, @var{muci}] =} normfit (@var{x}) ## @deftypefnx {statistics} {[@var{muhat}, @var{sigmahat}, @var{muci}, @var{sigmaci}] =} normfit (@var{x}) ## @deftypefnx {statistics} {[@dots{}] =} normfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} normfit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} normfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} normfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate parameters and confidence intervals for the normal distribution. ## ## @code{[@var{muhat}, @var{sigmahat}] = normfit (@var{x})} estimates the ## parameters of the normal distribution given the data in @var{x}. @var{muhat} ## is an estimate of the mean, and @var{sigmahat} is an estimate of the standard ## deviation. ## ## @code{[@var{muhat}, @var{sigmahat}, @var{muci}, @var{sigmaci}] = normfit ## (@var{x})} returns the 95% confidence intervals for the mean and standard ## deviation estimates in the arrays @var{muci} and @var{sigmaci}, respectively. ## ## @itemize ## @item ## @var{x} can be a vector or a matrix. When @var{x} is a matrix, the parameter ## estimates and their confidence intervals are computed for each column. In ## this case, @code{normfit} supports only 2 input arguments, @var{x} and ## @var{alpha}. Optional arguments @var{censor}, @var{freq}, and @var{options} ## can be used only when @var{x} is a vector. ## ## @item ## @var{alpha} is a scalar value in the range @math{(0,1)} specifying the ## confidence level for the confidence intervals calculated as ## @math{100*(1 - alpha)%}. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @item ## @var{censor} is a logical vector of the same length as @var{x} specifying ## whether each value in @var{x} is right-censored or not. 1 indicates ## observations that are right-censored and 0 indicates observations that are ## fully observed. With censoring, @var{muhat} and @var{sigmahat} are the ## maximum likelihood estimates (MLEs). If empty, the default is an array of ## 0s, meaning that all observations are fully observed. ## ## @item ## @var{freq} is a vector of the same length as @var{x} and it typically ## contains non-negative integer counts of the corresponding elements in ## @var{x}. If empty, the default is an array of 1s, meaning one observation ## per element of @var{x}. To obtain the weighted MLEs for a data set with ## censoring, specify weights of observations, normalized to the number of ## observations in @var{x}. However, when there is no censored data (default), ## the returned estimate for standard deviation is not exactly the WMLE. To ## compute the weighted MLE, multiply the value returned in @var{sigmahat} by ## @code{sqrt ((sum (@var{freq}) - 1) / sum (@var{freq}))}. The square root is ## needed because the factor corrects a variance, while @var{sigmahat} is a ## standard deviation. This correction is needed because @code{normfit} ## normally computes @var{sigmahat} using an unbiased variance estimator when ## there is no censored data. When there is censoring ## in the data, the correction is not needed, since @code{normfit} does not use ## the unbiased variance estimator in that case. ## ## @item ## @var{options} is a structure with the control parameters for ## @code{fminsearch} which is used internally to compute MLEs for censored data. ## By default, it uses the following options: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## @end itemize ## ## Further information about the normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Normal_distribution} ## ## @seealso{normcdf, norminv, normpdf, normrnd, normlike, normstat} ## @end deftypefn function [muhat, sigmahat, muci, sigmaci] = normfit (x, alpha, censor, freq, options) ## Check for valid number of input arguments narginchk (1, 5); ## Check X for being a vector or a matrix if (ndims (x) != 2) error ("normfit: X must not be a multi-dimensional array."); endif if (! isvector (x)) if (nargin < 3) [n, ncols] = size (x); else error ("normfit: matrix data acceptable only under 2-arg syntax."); endif else n = numel (x); ncols = 1; endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("normfit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = 0; elseif (! isequal (size (x), size (censor))) error ("normfit: X and CENSOR vectors mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = 1; elseif (any (freq < 0)) error ("normfit: FREQ must not contain negative values."); elseif (isequal (size (x), size (freq))) n = sum (freq); is_zero = find (freq == 0); if (numel (is_zero) > 0) x(is_zero) = []; if (numel (censor) == numel (freq)) censor(is_zero) = []; endif freq(is_zero) = []; endif else error ("normfit: X and FREQ vectors mismatch."); endif ## Check options structure or add defaults if (nargin > 4 && ! isempty (options)) if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("normfit: 'options' 5th argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif else options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; endif ## Get number of censored and uncensored elements n_censored = sum (freq.*censor); % a scalar in all cases n_uncensored = n - n_censored; % a scalar in all cases ## Compute total sum in X totalsum = sum (freq.*x); ## Check cases that cannot make a fit. ## 1. Handle Infs and NaNs if (! isfinite (totalsum)) muhat = totalsum; sigmahat = NaN ('like', x); muci = NaN (2, 1, 'like', x); sigmaci = NaN (2, 1, 'like', x); return endif ## 2. All observations are censored or empty data if (n == 0 || n_uncensored == 0) muhat = NaN (1, ncols,'like',x); sigmahat = NaN (1, ncols,'like',x); muci = NaN (2, ncols,'like',x); sigmaci = NaN (2, ncols,'like',x); return endif ## 3. No censored values, compute parameter estimates explicitly. if (n_censored == 0) muhat = totalsum ./ n; if (n > 1) if numel (muhat) == 1 # X is a vector xc = x - muhat; else # X is a matrix xc = x - repmat (muhat, [n, 1]); endif sigmahat = sqrt (sum (conj (xc) .* xc .* freq) ./ (n - 1)); else sigmahat = zeros (1, ncols, 'like', x); endif if (nargout > 2) if (n > 1) paramhat = [muhat; sigmahat]; ci = norm_ci (paramhat, [], alpha, x, [], freq); muci = ci(:,:,1); sigmaci = ci(:,:,2); else muci = [-Inf; Inf] * ones (1, ncols, 'like', x); sigmaci = [0; Inf] * ones (1, ncols, 'like', x); endif endif return endif ## 4. All uncensored observations equal and greater than all the ## censored observations x_uncensored = x(censor == 0); range_x_uncensored = range (x_uncensored); if (range_x_uncensored < realmin (class (x))) if (x_uncensored(1) == max (x)) muhat = x_uncensored(1); sigmahat = zeros ('like',x); if (n_uncensored > 1) muci = [muhat; muhat]; sigmaci = zeros (2, 1, 'like', x); else muci = cast ([-Inf; Inf], 'like', x); sigmaci = cast ([0; Inf], 'like', x); endif return endif endif ## Get an initial estimate for parameters using the "least squares" method if (range_x_uncensored > 0) if (numel (freq) == numel (x)) [p,q] = ecdf (x, 'censoring', censor, 'frequency', freq); else [p,q] = ecdf (x, 'censoring', censor); endif pmid = (p(1:(end-1)) + p(2:end)) / 2; linefit = polyfit (-sqrt (2) * erfcinv (2 * pmid), q(2:end), 1); paramhat = linefit([2 1]); else # only one uncensored element in X paramhat = [x_uncensored(1) 1]; endif ## Optimize the parameters as doubles, regardless of input data type paramhat = cast (paramhat, 'double'); ## Search for parameter that minimizes the negative log likelihood function [paramhat, ~, err, output] = fminsearch ... (@(ph) norm_nlogl (ph, x, censor, freq), paramhat, options); ## Handle errors if (err == 0) if (output.funcCount >= options.MaxFunEvals) warning ("normfit: maximum number of function evaluations are exceeded."); elseif (output.iterations >= options.MaxIter) warning ("normfit: maximum number of iterations are exceeded."); endif elseif (err < 0) error ("normfit: NoSolution."); endif ## Make sure the outputs match the input data type muhat = cast (paramhat(1), 'like', x); sigmahat = cast (paramhat(2), 'like', x); if (nargout > 2) paramhat = paramhat(:); if (numel (freq) == numel (x)) [~, avar] = normlike (paramhat, x, censor, freq); else [~, avar] = normlike (paramhat, x, censor); endif ci = norm_ci (paramhat, avar, alpha, x, censor, freq); muci = ci(:,:,1); sigmaci = ci(:,:,2); endif endfunction ## Negative log-likelihood function and gradient for normal distribution. function [nlogL, avar] = norm_nlogl (params, x, censor, freq) ## Get mu and sigma values mu = params(1); sigma = params(2); ## Compute the individual log-likelihood terms. Force a log(0)==-Inf for ## data from extreme right tail, instead of getting exp(Inf-Inf)==NaN. z = (x - mu) ./ sigma; L = -0.5 .* z .^ 2 - log (sqrt (2 .* pi) .* sigma); if (any (censor)) censored = censor == 1; z_censor = z(censored); S_censor = 0.5 * erfc (z_censor / sqrt (2)); L(censored) = log (S_censor); endif ## Neg-log-like is the sum of the individual contributions nlogL = -sum (freq .* L); ## Compute the negative hessian at the parameter values. ## Invert to get the observed information matrix. if (nargout == 2) dL11 = -ones (size (z), class (z)); dL12 = -2 .* z; dL22 = 1 - 3 .* z .^ 2; if (any (censor)) dlogScen = exp (-0.5 .* z_censor .^ 2) ./ (sqrt (2 * pi) .* S_censor); d2logScen = dlogScen .* (dlogScen - z_censor); dL11(censored) = -d2logScen; dL12(censored) = -dlogScen - z_censor .* d2logScen; dL22(censored) = -z_censor .* (2 .* dlogScen + z_censor .* d2logScen); endif nH11 = -sum (freq .* dL11); nH12 = -sum (freq .* dL12); nH22 = -sum (freq .* dL22); avar = (sigma .^ 2) * [nH22, -nH12; -nH12, nH11] / ... (nH11 * nH22 - nH12 * nH12); endif endfunction ## Confidence intervals for normal distribution function ci = norm_ci (paramhat, cv, alpha, x, censor, freq) ## Check for missing input arguments if (nargin < 6 || isempty (freq)) freq = ones (size (x)); endif if (nargin < 5 || isempty (censor)) censor = false (size (x)); endif if (isvector (paramhat)) paramhat = paramhat(:); endif muhat = paramhat(1,:); sigmahat = paramhat(2,:); ## Get number of elements if (isempty (freq) || isequal (freq, 1)) if (isvector (x)) n = length (x); else n = size (x, 1); endif else n = sum (freq); endif ## Get number of censored and uncensored elements n_censored = sum (freq .* censor); n_uncensored = n - n_censored; ## Just in case if (any (censor) && (n == 0 || n_uncensored == 0 || ! isfinite (paramhat(1)))) ## X is a vector muci = NaN (2,1); sigmaci = NaN (2,1); ci = cast (cat (3, muci, sigmaci), 'like', x); return endif ## Get confidence intervals for each parameter if ((isempty (censor) || ! any (censor(:))) && ! isequal (cv,zeros (2,2))) ## Use exact formulas tcrit = tinv ([alpha/2, 1-alpha/2], n-1); muci = [muhat+tcrit(1)*sigmahat/sqrt(n); muhat+tcrit(2)*sigmahat/sqrt(n)]; chi2crit = chi2inv ([alpha/2, 1-alpha/2], n-1); sigmaci = [sigmahat*sqrt((n-1)./chi2crit(2)); ... sigmahat*sqrt((n-1)./chi2crit(1))]; else ## Use normal approximation probs = [alpha/2; 1-alpha/2]; se = sqrt (diag (cv))'; z = norminv (probs); ## Compute the CI for mu using a normal distribution for muhat. muci = muhat + se(1) .* z; ## Compute the CI for sigma using a normal approximation for ## log(sigmahat), and transform back to the original scale. logsigci = log (sigmahat) + (se(2) ./ sigmahat) .* z; sigmaci = exp (logsigci); endif ## Return as a single array ci = cat (3, muci, sigmaci); endfunction %!demo %! ## Sample 3 populations from 3 different normal distributions %! rng (42); %! r1 = normrnd (2, 5, 5000, 1); %! r2 = normrnd (5, 2, 5000, 1); %! r3 = normrnd (9, 4, 5000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, 15, 0.4); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! hold on %! %! ## Estimate their mu and sigma parameters %! [muhat, sigmahat] = normfit (r); %! %! ## Plot their estimated PDFs %! x = [min(r(:)):max(r(:))]; %! y = normpdf (x, muhat(1), sigmahat(1)); %! plot (x, y, '-pr'); %! y = normpdf (x, muhat(2), sigmahat(2)); %! plot (x, y, '-sg'); %! y = normpdf (x, muhat(3), sigmahat(3)); %! plot (x, y, '-^c'); %! ylim ([0, 0.5]) %! xlim ([-20, 20]) %! hold off %! legend ({'Normalized HIST of sample 1 with mu=2, σ=5', ... %! 'Normalized HIST of sample 2 with mu=5, σ=2', ... %! 'Normalized HIST of sample 3 with mu=9, σ=4', ... %! sprintf("PDF for sample 1 with estimated mu=%0.2f and σ=%0.2f", ... %! muhat(1), sigmahat(1)), ... %! sprintf("PDF for sample 2 with estimated mu=%0.2f and σ=%0.2f", ... %! muhat(2), sigmahat(2)), ... %! sprintf("PDF for sample 3 with estimated mu=%0.2f and σ=%0.2f", ... %! muhat(3), sigmahat(3))}, 'location', 'northwest') %! title ('Three population samples from different normal distributions') %! hold off ## Test output %!test %! load lightbulb %! idx = find (lightbulb(:,2) == 0); %! censoring = lightbulb(idx,3) == 1; %! [muHat, sigmaHat] = normfit (lightbulb(idx,1), [], censoring); %! assert_equal (muHat, 9496.59586737857, 1e-11); %! assert_equal (sigmaHat, 3064.021012796456, 2e-12); %!test %! randn ('seed', 234); %! x = normrnd (3, 5, [1000, 1]); %! [muHat, sigmaHat, muCI, sigmaCI] = normfit (x, 0.01); %! assert_equal (muCI(1) < 3, true); %! assert_equal (muCI(2) > 3, true); %! assert_equal (sigmaCI(1) < 5, true); %! assert_equal (sigmaCI(2) > 5, true); ## Test input validation %!error ... %! normfit (ones (3,3,3)) %!error ... %! normfit (ones (20,3), [], zeros (20,1)) %!error normfit (ones (20,1), 0) %!error normfit (ones (20,1), -0.3) %!error normfit (ones (20,1), 1.2) %!error normfit (ones (20,1), [0.05 0.1]) %!error normfit (ones (20,1), 0.02+i) %!error ... %! normfit (ones (20,1), [], zeros (15,1)) %!error ... %! normfit (ones (20,1), [], zeros (20,1), ones (25,1)) %!error ... %! normfit (ones (5,1), [], zeros (5,1), [1, 2, 1, 2, -1]') %!error normfit (ones (20,1), [], zeros (20,1), ones (20,1), 'options') statistics-release-1.9.2/inst/Distribution_Fitting/normlike.m000066400000000000000000000154541524624707500244760ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} normlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{avar}] =} normlike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} normlike (@var{params}, @var{x}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} normlike (@var{params}, @var{x}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the normal distribution. ## ## @code{@var{nlogL} = normlike (@var{params}, @var{x})} returns the negative ## log-likelihood for the normal distribution, evaluated at parameters ## @var{params(1)} = mean and @var{params(2)} = standard deviation, given ## @var{x}. @var{nlogL} is a scalar. ## ## @code{[@var{nlogL}, @var{avar}] = normlike (@var{params}, @var{x})} ## returns the inverse of Fisher's information matrix, @var{avar}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{avar} are their asymptotic variances. @var{avar} ## is based on the observed Fisher's information, not the expected information. ## ## @code{[@dots{}] = normlike (@var{params}, @var{x}, @var{censor})} accepts ## a boolean vector of the same size as @var{x} that is 1 for observations ## that are right-censored and 0 for observations that are observed exactly. ## ## @code{[@dots{}] = normlike (@var{params}, @var{x}, @var{censor}, ## @var{freq})} accepts a frequency vector of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it may contain any non-integer non-negative ## values. Pass in [] for @var{censor} to use its default value. ## ## Further information about the normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Normal_distribution} ## ## @seealso{normcdf, norminv, normpdf, normrnd, normfit, normstat} ## @end deftypefn function [nlogL, avar] = normlike (params, x, censor, freq) ## Check input arguments if (nargin < 2) error ("normlike: too few input arguments."); endif if (! isvector (x)) error ("normlike: X must be a vector."); endif if (numel (params) != 2) error ("normlike: PARAMS must be a two-element vector."); endif if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("normlike: X and CENSOR vectors mismatch."); endif if nargin < 4 || isempty (freq) freq = ones (size (x)); elseif (any (freq < 0)) error ("normlike: FREQ must not contain negative values."); elseif (isequal (size (x), size (freq))) nulls = find (freq == 0); if (numel (nulls) > 0) x(nulls) = []; censor(nulls) = []; freq(nulls) = []; endif else error ("normlike: X and FREQ vectors mismatch."); endif ## Get mu and sigma values mu = params(1); sigma = params(2); ## sigma must be positive, otherwise make it NaN if (sigma <= 0) sigma = NaN; endif ## Compute the individual log-likelihood terms. Force a log(0)==-Inf for ## x from extreme right tail, instead of getting exp(Inf-Inf)==NaN. z = (x - mu) ./ sigma; L = -0.5 .* z .^ 2 - log (sqrt (2 .* pi) .* sigma); if (any (censor)) censored = censor == 1; z_censor = z(censored); S_censor = 0.5 * erfc (z_censor / sqrt (2)); L(censored) = log (S_censor); endif ## Neg-log-like is the sum of the individual contributions nlogL = -sum (freq .* L); ## Compute the negative hessian at the parameter values. ## Invert to get the observed information matrix. if (nargout == 2) dL11 = -ones (size (z), class (z)); dL12 = -2 .* z; dL22 = 1 - 3 .* z .^ 2; if (any (censor)) dlogScen = exp (-0.5 .* z_censor .^ 2) ./ (sqrt (2 * pi) .* S_censor); d2logScen = dlogScen .* (dlogScen - z_censor); dL11(censored) = -d2logScen; dL12(censored) = -dlogScen - z_censor .* d2logScen; dL22(censored) = -z_censor .* (2 .* dlogScen + z_censor .* d2logScen); endif nH11 = -sum (freq .* dL11); nH12 = -sum (freq .* dL12); nH22 = -sum (freq .* dL22); avar = (sigma .^ 2) * [nH22, -nH12; -nH12, nH11] / ... (nH11 * nH22 - nH12 * nH12); endif endfunction ## Test input validation %!error normlike ([12, 15]); %!error normlike ([12, 15], ones (2)); %!error ... %! normlike ([12, 15, 3], [1:50]); %!error ... %! normlike ([12, 15], [1:50], [1, 2, 3]); %!error ... %! normlike ([12, 15], [1:50], [], [1, 2, 3]); %!error ... %! normlike ([12, 15], [1:5], [], [1, 2, 3, 2, -1]); ## Results compared with Matlab %!test %! x = 1:50; %! [nlogL, avar] = normlike ([2.3, 1.2], x); %! avar_out = [7.5767e-01, -1.8850e-02; -1.8850e-02, 4.8750e-04]; %! assert_equal (nlogL, 13014.95883783327, 1e-10); %! assert_equal (avar, avar_out, 1e-4); %!test %! x = 1:50; %! [nlogL, avar] = normlike ([2.3, 1.2], x * 0.5); %! avar_out = [3.0501e-01, -1.5859e-02; -1.5859e-02, 9.1057e-04]; %! assert_equal (nlogL, 2854.802587833265, 1e-10); %! assert_equal (avar, avar_out, 1e-4); %!test %! x = 1:50; %! [nlogL, avar] = normlike ([21, 15], x); %! avar_out = [5.460474308300396, -1.600790513833993; ... %! -1.600790513833993, 2.667984189723321]; %! assert_equal (nlogL, 206.738325604233, 1e-12); %! assert_equal (avar, avar_out, 1e-14); %!test %! x = 1:50; %! censor = ones (1, 50); %! censor([2, 4, 6, 8, 12, 14]) = 0; %! [nlogL, avar] = normlike ([2.3, 1.2], x, censor); %! avar_out = [3.0501e-01, -1.5859e-02; -1.5859e-02, 9.1057e-04]; %! assert_equal (nlogL, Inf); %! assert_equal (avar, [NaN, NaN; NaN, NaN]); %!test %! x = 1:50; %! censor = ones (1, 50); %! censor([2, 4, 6, 8, 12, 14]) = 0; %! [nlogL, avar] = normlike ([21, 15], x, censor); %! avar_out = [24.4824488866131, -10.6649544179636; ... %! -10.6649544179636, 6.22827849965737]; %! assert_equal (nlogL, 86.9254371829733, 1e-12); %! assert_equal (avar, avar_out, 8e-14); statistics-release-1.9.2/inst/Distribution_Fitting/poissfit.m000066400000000000000000000144031524624707500245070ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{lambdahat} =} poissfit (@var{x}) ## @deftypefnx {statistics} {[@var{lambdahat}, @var{lambdaci}] =} poissfit (@var{x}) ## @deftypefnx {statistics} {[@var{lambdahat}, @var{lambdaci}] =} poissfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@var{lambdahat}, @var{lambdaci}] =} poissfit (@var{x}, @var{alpha}, @var{freq}) ## ## Estimate parameter and confidence intervals for the Poisson distribution. ## ## @code{@var{lambdahat} = poissfit (@var{x})} returns the maximum likelihood ## estimate of the rate parameter, @var{lambda}, of the Poisson distribution ## given the data in @var{x}. @var{x} must be a vector of non-negative values. ## ## @code{[@var{lambdahat}, @var{lambdaci}] = poissfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimate. ## ## @code{[@var{lambdahat}, @var{lambdaci}] = poissfit (@var{x}, @var{alpha})} ## also returns the @qcode{100 * (1 - @var{alpha})} percent confidence intervals ## of the estimated parameter. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = poissfit (@var{x}, @var{alpha}, @var{freq})} accepts a ## frequency vector or matrix, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}. @var{freq} cannot contain negative values. ## ## Further information about the Poisson distribution can be found at ## @url{https://en.wikipedia.org/wiki/Poisson_distribution} ## ## @seealso{poisscdf, poissinv, poisspdf, poissrnd, poisslike, poisstat} ## @end deftypefn function [lambdahat, lambdaci] = poissfit (x, alpha, freq) ## Check input arguments if (any (x < 0)) error ("poissfit: X cannot have negative values."); endif if (nargin < 2 || isempty (alpha)) alpha = 0.05; elseif (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("poissfit: wrong value for ALPHA."); endif if (nargin < 3 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("poissfit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("poissfit: FREQ must not contain negative values."); endif if (isvector (x)) x = x(:); freq = freq(:); endif ## Compute lambdahat n = sum (freq, 1); lambdahat = double (sum (x .* freq) ./ n); ## Compute confidence intervals lambdasum = n .* lambdahat; ## Select elements for exact method or normal approximation k = (lambdasum < 100); if (any (k)) # exact method lb(k) = chi2inv (alpha / 2, 2 * lambdasum(k)) / 2; ub(k) = chi2inv (1 - alpha / 2, 2 * (lambdasum(k) + 1)) / 2; endif k = ! k; if (any (k)) # normal approximation lb(k) = norminv (alpha / 2, lambdasum(k), sqrt (lambdasum(k))); ub(k) = norminv (1 - alpha / 2, lambdasum(k), sqrt (lambdasum(k))); endif lambdaci = [lb; ub] / n; endfunction %!demo %! ## Sample 3 populations from 3 different Poisson distributions %! rng (42); %! randp ('state', 42); %! r1 = poissrnd (1, 1000, 1); %! r2 = poissrnd (4, 1000, 1); %! r3 = poissrnd (10, 1000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, [0:20], 1); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! hold on %! %! ## Estimate their lambda parameter %! lambdahat = poissfit (r); %! %! ## Plot their estimated PDFs %! x = [0:20]; %! y = poisspdf (x, lambdahat(1)); %! plot (x, y, '-pr'); %! y = poisspdf (x, lambdahat(2)); %! plot (x, y, '-sg'); %! y = poisspdf (x, lambdahat(3)); %! plot (x, y, '-^c'); %! xlim ([0, 20]) %! ylim ([0, 0.4]) %! legend ({'Normalized HIST of sample 1 with λ=1', ... %! 'Normalized HIST of sample 2 with λ=4', ... %! 'Normalized HIST of sample 3 with λ=10', ... %! sprintf("PDF for sample 1 with estimated λ=%0.2f", ... %! lambdahat(1)), ... %! sprintf("PDF for sample 2 with estimated λ=%0.2f", ... %! lambdahat(2)), ... %! sprintf("PDF for sample 3 with estimated λ=%0.2f", ... %! lambdahat(3))}) %! title ('Three population samples from different Poisson distributions') %! hold off ## Test output %!test %! x = [1 3 2 4 5 4 3 4]; %! [lhat, lci] = poissfit (x); %! assert_equal (lhat, 3.25) %! assert_equal (lci, [2.123007901949543; 4.762003010390628], 1e-14) %!test %! x = [1 3 2 4 5 4 3 4]; %! [lhat, lci] = poissfit (x, 0.01); %! assert_equal (lhat, 3.25) %! assert_equal (lci, [1.842572740234582; 5.281369033298528], 1e-14) %!test %! x = [1 2 3 4 5]; %! f = [1 1 2 3 1]; %! [lhat, lci] = poissfit (x, [], f); %! assert_equal (lhat, 3.25) %! assert_equal (lci, [2.123007901949543; 4.762003010390628], 1e-14) %!test %! x = [1 2 3 4 5]; %! f = [1 1 2 3 1]; %! [lhat, lci] = poissfit (x, 0.01, f); %! assert_equal (lhat, 3.25) %! assert_equal (lci, [1.842572740234582; 5.281369033298528], 1e-14) ## Test input validation %!error poissfit ([1 2 -1 3]) %!error poissfit ([1 2 3], 0) %!error poissfit ([1 2 3], 1.2) %!error poissfit ([1 2 3], [0.02 0.05]) %!error %! poissfit ([1 2 3], [], [1 5]) %!error %! poissfit ([1 2 3], [], [1 5 -1]) statistics-release-1.9.2/inst/Distribution_Fitting/poisslike.m000066400000000000000000000076361524624707500246630ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} poisslike (@var{lambda}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{avar}] =} poisslike (@var{lambda}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} poisslike (@var{lambda}, @var{x}, @var{freq}) ## ## Negative log-likelihood for the Poisson distribution. ## ## @code{@var{nlogL} = poisslike (@var{lambda}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the Poisson ## distribution with rate parameter @var{lambda}. @var{x} must be a vector of ## non-negative values. ## ## @code{[@var{nlogL}, @var{avar}] = poisslike (@var{lambda}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{avar}. If the input ## rate parameter, @var{lambda}, is the maximum likelihood estimate, @var{avar} ## is its asymptotic variance. ## ## @code{[@dots{}] = poisslike (@var{lambda}, @var{x}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## typically contains integer frequencies for the corresponding elements in ## @var{x}, but it can contain any non-integer non-negative values. By default, ## or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the Poisson distribution can be found at ## @url{https://en.wikipedia.org/wiki/Poisson_distribution} ## ## @seealso{poisscdf, poissinv, poisspdf, poissrnd, poissfit, poisstat} ## @end deftypefn function [nlogL, avar] = poisslike (lambda, x, freq) ## Check input arguments if (nargin < 2) error ("poisslike: function called with too few input arguments."); endif if (! isscalar (lambda) || ! isnumeric (lambda) || lambda <= 0) error ("poisslike: LAMBDA must be a positive scalar."); endif if (! isvector (x) || any (x < 0)) error ("poisslike: X must be a vector of non-negative values."); endif if (nargin < 3 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("poisslike: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("poisslike: FREQ must not contain negative values."); endif ## Compute negative log-likelihood and asymptotic covariance n = sum (freq); nlogL = - sum (freq .* log (poisspdf (x, lambda))); avar = lambda / n; endfunction ## Test output %!test %! x = [1 3 2 4 5 4 3 4]; %! [nlogL, avar] = poisslike (3.25, x); %! assert_equal (nlogL, 13.9533, 1e-4) %!test %! x = [1 2 3 4 5]; %! f = [1 1 2 3 1]; %! [nlogL, avar] = poisslike (3.25, x, f); %! assert_equal (nlogL, 13.9533, 1e-4) ## Test input validation %!error poisslike (1) %!error poisslike ([1 2 3], [1 2]) %!error ... %! poisslike (3.25, ones (10, 2)) %!error ... %! poisslike (3.25, [1 2 3 -4 5]) %!error ... %! poisslike (3.25, ones (10, 1), ones (8,1)) %!error ... %! poisslike (3.25, ones (1, 8), [1 1 1 1 1 1 1 -1]) statistics-release-1.9.2/inst/Distribution_Fitting/private/000077500000000000000000000000001524624707500241415ustar00rootroot00000000000000statistics-release-1.9.2/inst/Distribution_Fitting/private/__stable_pdf__.m000066400000000000000000000107211524624707500272170ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{y} =} __stable_pdf__ (@var{x}, @var{alpha}, @var{beta}, @var{gam}, @var{delta}) ## ## Fast stable probability density for the fitting functions. ## ## Computes the stable density in the Nolan @qcode{S0} parameterization at all ## the points in @var{x} by a single shared-grid inversion of the characteristic ## function. Unlike @code{stblpdf}, which invokes an adaptive @code{quadgk} per ## point, the characteristic function is evaluated once on a common quadrature ## grid and combined against every data point at once, which is roughly an order ## of magnitude faster. This makes it suitable as the inner density of the ## maximum-likelihood loop in @code{stblfit}/@code{stbllike}, where accuracy of a ## few parts in @math{10^{-8}} across the bulk is ample for the estimator. ## ## The closed forms are used for the normal (@var{alpha} equal to @code{2}) and ## the Cauchy (@var{alpha} equal to @code{1} with @var{beta} equal to @code{0}) ## special cases. The parameters must be valid scalars; no checking is done ## here, as this is a private helper. ## ## @seealso{stblpdf, stblfit, stbllike} ## @end deftypefn function y = __stable_pdf__ (x, alpha, beta, gam, delta) z = (x - delta) ./ gam; y = nan (size (z)); ok = ! isnan (z); zk = z(ok)(:); if (isempty (zk)) return; endif if (alpha == 2) ## Normal with variance 2 v = exp (-zk .^ 2 ./ 4) ./ (2 .* sqrt (pi)); elseif (alpha == 1 && beta == 0) ## Cauchy v = 1 ./ (pi .* (1 + zk .^ 2)); else v = cf_invert (zk, alpha, beta); endif y(ok) = max (v, 0) ./ gam; endfunction ## Density of the standard stable S(alpha, beta, 1, 0) at the points Z by ## Gil-Pelaez inversion of the characteristic function on a shared grid. function v = cf_invert (z, alpha, beta) ## Upper limit: |phi(t)| = exp (-t^alpha), so t^alpha = L makes it negligible. L = 30; # exp (-30) ~ 9e-14 T = L ^ (1 / alpha); ## Uniform grid. The inversion integrand is the slowly decaying envelope ## |phi(t)| = exp (-t^alpha) times the oscillation exp (-i t z), whose ## frequency |z| is constant in t; a uniform mesh therefore resolves it most ## efficiently. The node count scales with the peak total phase over [0, T], ## i.e. with the largest |z| in the data, with a floor that also covers the ## mild t^alpha cusp at the origin when alpha < 1. The driving |z| is capped: ## far-tail points carry negligible density, so leaving their oscillation ## under-resolved keeps N bounded for heavy-tailed samples without affecting ## the likelihood. zmax = min (max (abs (z)), 30); N = ceil (6 * (zmax + 1) * T) + 512; N = min (N, 200000); t = linspace (0, T, N)'; phi = __stable_cf__ (t, alpha, beta); ## Uniform trapezoidal weights dt = T / (N - 1); w = dt .* ones (N, 1); w(1) /= 2; w(end) /= 2; ## Vectorized inversion: real (exp (-i t z') .* phi) integrated over t, per z M = real (exp (-1i .* (t * z(:)')) .* phi); v = (w' * M)' ./ pi; endfunction ## Characteristic function of the standard stable S(alpha, beta, 1, 0) in the ## Nolan S0 parameterization. Local copy of Distribution_Functions/private/ ## __stable_cf__.m, which stblpdf and stblcdf need and a private/ directory ## cannot share across folders. A closed form -- keep the two identical. function phi = __stable_cf__ (t, alpha, beta) at = abs (t); st = sign (t); if (abs (alpha - 1) < eps) ## The |t|*log|t| term vanishes at the origin lt = at .* log (at); lt(at == 0) = 0; phi = exp (-at - 1i .* beta .* (2 ./ pi) .* st .* lt); else phi = exp (-at .^ alpha - 1i .* beta .* tan (pi .* alpha ./ 2) ... .* st .* (at - at .^ alpha)); endif endfunction statistics-release-1.9.2/inst/Distribution_Fitting/raylfit.m000066400000000000000000000170661524624707500243310ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{sigmaA} =} raylfit (@var{x}) ## @deftypefnx {statistics} {[@var{sigmaA}, @var{sigmaci}] =} raylfit (@var{x}) ## @deftypefnx {statistics} {[@var{sigmaA}, @var{sigmaci}] =} raylfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@var{sigmaA}, @var{sigmaci}] =} raylfit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@var{sigmaA}, @var{sigmaci}] =} raylfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## ## Estimate parameter and confidence intervals for the Rayleigh distribution. ## ## @code{@var{sigmaA} = raylfit (@var{x})} returns the maximum likelihood ## estimate of the rate parameter, @var{lambda}, of the Rayleigh distribution ## given the data in @var{x}. @var{x} must be a vector of non-negative values. ## ## @code{[@var{sigmaA}, @var{sigmaci}] = raylfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimate. ## ## @code{[@var{sigmaA}, @var{sigmaci}] = raylfit (@var{x}, @var{alpha})} ## also returns the @qcode{100 * (1 - @var{alpha})} percent confidence intervals ## of the estimated parameter. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = raylfit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = raylfit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency vector or matrix, @var{freq}, of the same size as ## @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}. @var{freq} cannot contain negative values. ## ## Further information about the Rayleigh distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rayleigh_distribution} ## ## The @code{prob.RayleighDistribution} class names this same parameter ## @qcode{B}, after MATLAB. ## @seealso{raylcdf, raylinv, raylpdf, raylrnd, rayllike, raylstat} ## @end deftypefn function [sigmaA, sigmaci] = raylfit (x, alpha, censor, freq) ## Check input arguments if (any (x < 0)) error ("raylfit: X cannot have negative values."); endif if (! isvector (x)) error ("raylfit: X must be a vector."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; elseif (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("raylfit: wrong value for ALPHA."); endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("raylfit: X and CENSOR vectors mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("raylfit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("raylfit: FREQ must not contain negative values."); endif ## Remove any censored data censored = censor == 1; freq(censored) = []; x(censored) = []; ## Expand frequency vector (if necessary) if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; endif ## Compute sigmaA sigmaA = sqrt (0.5 * mean (x .^ 2)); ## Compute confidence intervals (based on chi-squared) if (nargout > 1) sx = 2 * numel (x); ci = [1-alpha/2; alpha/2]; sigmaci = sqrt (sx * sigmaA .^ 2 ./ chi2inv (ci, sx)); endif endfunction %!demo %! ## Sample 3 populations from 3 different Rayleigh distributions %! rng (42); %! r1 = raylrnd (1, 1000, 1); %! r2 = raylrnd (2, 1000, 1); %! r3 = raylrnd (4, 1000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, [0.5:0.5:10.5], 2); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! hold on %! %! ## Estimate their lambda parameter %! sigmaA = raylfit (r(:,1)); %! sigmaB = raylfit (r(:,2)); %! sigmaC = raylfit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [0:0.1:10]; %! y = raylpdf (x, sigmaA); %! plot (x, y, '-pr'); %! y = raylpdf (x, sigmaB); %! plot (x, y, '-sg'); %! y = raylpdf (x, sigmaC); %! plot (x, y, '-^c'); %! xlim ([0, 10]) %! ylim ([0, 0.7]) %! legend ({'Normalized HIST of sample 1 with σ=1', ... %! 'Normalized HIST of sample 2 with σ=2', ... %! 'Normalized HIST of sample 3 with σ=4', ... %! sprintf("PDF for sample 1 with estimated σ=%0.2f", ... %! sigmaA), ... %! sprintf("PDF for sample 2 with estimated σ=%0.2f", ... %! sigmaB), ... %! sprintf("PDF for sample 3 with estimated σ=%0.2f", ... %! sigmaC)}) %! title ('Three population samples from different Rayleigh distributions') %! hold off ## Test output %!test %! x = [1 3 2 4 5 4 3 4]; %! [shat, sci] = raylfit (x); %! assert_equal (shat, 2.4495, 1e-4) %! assert_equal (sci, [1.8243; 3.7279], 1e-4) %!test %! x = [1 3 2 4 5 4 3 4]; %! [shat, sci] = raylfit (x, 0.01); %! assert_equal (shat, 2.4495, 1e-4) %! assert_equal (sci, [1.6738; 4.3208], 1e-4) %!test %! x = [1 2 3 4 5]; %! f = [1 1 2 3 1]; %! [shat, sci] = raylfit (x, [], [], f); %! assert_equal (shat, 2.4495, 1e-4) %! assert_equal (sci, [1.8243; 3.7279], 1e-4) %!test %! x = [1 2 3 4 5]; %! f = [1 1 2 3 1]; %! [shat, sci] = raylfit (x, 0.01, [], f); %! assert_equal (shat, 2.4495, 1e-4) %! assert_equal (sci, [1.6738; 4.3208], 1e-4) %!test %! x = [1 2 3 4 5 6]; %! c = [0 0 0 0 0 1]; %! f = [1 1 2 3 1 1]; %! [shat, sci] = raylfit (x, 0.01, c, f); %! assert_equal (shat, 2.4495, 1e-4) %! assert_equal (sci, [1.6738; 4.3208], 1e-4) ## Test input validation %!error raylfit (ones (2,5)); %!error raylfit ([1 2 -1 3]) %!error raylfit ([1 2 3], 0) %!error raylfit ([1 2 3], 1.2) %!error raylfit ([1 2 3], [0.02 0.05]) %!error ... %! raylfit ([1, 2, 3, 4, 5], 0.05, [1 1 0]); %!error ... %! raylfit ([1, 2, 3, 4, 5], [], [1 1 0 1 1]'); %!error ... %! raylfit ([1, 2, 3, 4, 5], 0.05, zeros (1,5), [1 1 0]); %!error ... %! raylfit ([1, 2, 3, 4, 5], [], [], [1 1 0 1 1]'); %!error %! raylfit ([1 2 3], [], [], [1 5]) %!error %! raylfit ([1 2 3], [], [], [1 5 -1]) statistics-release-1.9.2/inst/Distribution_Fitting/rayllike.m000066400000000000000000000121411524624707500244600ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} rayllike (@var{sigma}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} rayllike (@var{sigma}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} rayllike (@var{sigma}, @var{x}, @var{freq}) ## ## Negative log-likelihood for the Rayleigh distribution. ## ## @code{@var{nlogL} = rayllike (@var{sigma}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the Rayleigh ## distribution with rate parameter @var{sigma}. @var{x} must be a vector of ## non-negative values. ## ## @code{[@var{nlogL}, @var{acov}] = rayllike (@var{sigma}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## rate parameter, @var{sigma}, is the maximum likelihood estimate, @var{acov} ## is its asymptotic variance. ## ## @code{[@dots{}] = rayllike (@var{sigma}, @var{x}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## typically contains integer frequencies for the corresponding elements in ## @var{x}, but it can contain any non-integer non-negative values. By default, ## or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the Rayleigh distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rayleigh_distribution} ## ## The @code{prob.RayleighDistribution} class names this same parameter ## @qcode{B}, after MATLAB. ## @seealso{raylcdf, raylinv, raylpdf, raylrnd, raylfit, raylstat} ## @end deftypefn function [nlogL, acov] = rayllike (sigma, x, censor, freq) ## Check input arguments if (nargin < 2) error ("rayllike: function called with too few input arguments."); endif if (! isscalar (sigma) || ! isnumeric (sigma) || sigma <= 0) error ("rayllike: SIGMA must be a positive scalar."); endif if (! isvector (x) || any (x < 0)) error ("rayllike: X must be a vector of non-negative values."); endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("rayllike: X and CENSOR vectors mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("rayllike: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("rayllike: FREQ must not contain negative values."); endif ## Compute negative log-likelihood and asymptotic covariance zsq = (x / sigma) .^ 2; logfz = -2 * log (sigma) - zsq / 2 + log (x); dlogfz = (zsq - 2) / sigma; logS = - zsq / 2; dlogS = - zsq * 3 / sigma ^ 2; logfz(censor == 1) = logS(censor == 1); nlogL = - sum (freq .* logfz); if (nargout > 1) d2 = (2 - 3 * zsq) / sigma ^ 2; d2(censor == 1) = - 3 * zsq(censor == 1) / sigma ^ 2; acov = - sum (freq .* d2); endif endfunction ## Test output %!test %! x = [1 3 2 4 5 4 3 4]; %! [nlogL, acov] = rayllike (3.25, x); %! assert_equal (nlogL, 14.7442, 1e-4) %!test %! x = [1 2 3 4 5]; %! f = [1 1 2 3 1]; %! [nlogL, acov] = rayllike (3.25, x, [], f); %! assert_equal (nlogL, 14.7442, 1e-4) %!test %! x = [1 2 3 4 5 6]; %! f = [1 1 2 3 1 0]; %! [nlogL, acov] = rayllike (3.25, x, [], f); %! assert_equal (nlogL, 14.7442, 1e-4) %!test %! x = [1 2 3 4 5 6]; %! c = [0 0 0 0 0 1]; %! f = [1 1 2 3 1 0]; %! [nlogL, acov] = rayllike (3.25, x, c, f); %! assert_equal (nlogL, 14.7442, 1e-4) ## Test input validation %!error rayllike (1) %!error rayllike ([1 2 3], [1 2]) %!error ... %! rayllike (3.25, ones (10, 2)) %!error ... %! rayllike (3.25, [1 2 3 -4 5]) %!error ... %! rayllike (3.25, [1, 2, 3, 4, 5], [1 1 0]); %!error ... %! rayllike (3.25, [1, 2, 3, 4, 5], [1 1 0 1 1]'); %!error ... %! rayllike (3.25, [1, 2, 3, 4, 5], zeros (1,5), [1 1 0]); %!error ... %! rayllike (3.25, [1, 2, 3, 4, 5], [], [1 1 0 1 1]'); %!error ... %! rayllike (3.25, ones (1, 8), [], [1 1 1 1 1 1 1 -1]) statistics-release-1.9.2/inst/Distribution_Fitting/ricefit.m000066400000000000000000000256111524624707500242770ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} ricefit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} ricefit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} ricefit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} ricefit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} ricefit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} ricefit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate parameters and confidence intervals for the Rician distribution. ## ## @code{@var{paramhat} = ricefit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the Rician distribution given the data in ## @var{x}. @qcode{@var{paramhat}(1)} is the non-centrality (distance) ## parameter, @math{s}, and @qcode{@var{paramhat}(2)} is the scale parameter, ## @math{sigma}. ## ## @code{[@var{paramhat}, @var{paramci}] = ricefit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = ricefit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = ricefit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = ricefit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@dots{}] = ricefit (@dots{}, @var{options})} specifies control ## parameters for the iterative algorithm used to compute the maximum likelihood ## estimates. @var{options} is a structure with the following field and its ## default value: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 1000} ## @item @qcode{@var{options}.MaxIter = 500} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## Further information about the Rician distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rice_distribution} ## ## @seealso{ricecdf, ricepdf, riceinv, ricernd, ricelike, ricestat} ## @end deftypefn function [paramhat, paramci] = ricefit (x, alpha, censor, freq, options) ## Check input arguments if (! isvector (x)) error ("ricefit: X must be a vector."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("ricefit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("ricefit: X and CENSOR vectors mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("ricefit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("ricefit: FREQ cannot have negative values."); endif ## Get options structure or add defaults if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("ricefit: 'options' 5th argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Get sample size and data type cls = class (x); szx = sum (freq); ncen = sum (freq .* censor); nunc = szx - ncen; ## Check for illegal value in X if (any (x <= 0)) error ("ricefit: X must contain positive values."); endif ## Handle ill-conditioned cases: no data or all censored if (szx == 0 || nunc == 0 || any (! isfinite (x))) paramhat = nan (1, 2, cls); paramci = nan (2, cls); return endif ## Check for identical data in X if (! isscalar (x) && max (abs (diff (x)) ./ x(2:end)) <= sqrt (eps)) paramhat = cast ([Inf, 0], cls); paramci = cast ([Inf, 0; Inf, 0], cls); return endif ## Use 2nd and 4th Moment Estimators of uncensored data as starting point xsq_uncensored = x(censor == 0) .^ 2; meanxsq = mean (xsq_uncensored); meanx_4 = mean (xsq_uncensored .^ 2); if (meanxsq ^ 2 < meanx_4 && meanx_4 < 2 * meanxsq ^ 2) nu_4 = 2 * meanxsq ^ 2 - meanx_4; nusq = sqrt (nu_4); sigmasq = 0.5 * (meanxsq - nusq); params = cast ([sqrt(nusq), sqrt(sigmasq)], cls); else params = cast ([1, 1], cls); endif ## Minimize negative log-likelihood to estimate parameters f = @(params) ricelike (params, x, censor, freq); [paramhat, ~, err, output] = fminsearch (f, params, options); ## Force positive parameter values paramhat = abs (paramhat); ## Handle errors if (err == 0) if (output.funcCount >= options.MaxFunEvals) warning (strcat ("ricefit: maximum number of function", ... " evaluations are exceeded.")); elseif (output.iterations >= options.MaxIter) warning ("ricefit: maximum number of iterations are exceeded."); endif elseif (err < 0) error ("ricefit: no solution."); endif ## Compute CIs using a log normal approximation for parameters. if (nargout > 1) ## Compute asymptotic covariance [~, acov] = ricelike (paramhat, x, censor, freq); ## Get standard errors stderr = sqrt (diag (acov))'; stderr = stderr ./ paramhat; ## Apply log transform phatlog = log (paramhat); ## Compute normal quantiles z = probit (alpha / 2); ## Compute CI paramci = [phatlog; phatlog] + [stderr; stderr] .* [z, z; -z, -z]; ## Inverse log transform paramci = exp (paramci); endif endfunction %!demo %! ## Sample 3 populations from different Gamma distributions %! randg ('state', 42); %! randp ('state', 42); %! r1 = ricernd (1, 2, 3000, 1); %! r2 = ricernd (2, 4, 3000, 1); %! r3 = ricernd (7.5, 1, 3000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, 75, 4); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! ylim ([0, 0.7]); %! xlim ([0, 12]); %! hold on %! %! ## Estimate their α and β parameters %! s_sigmaA = ricefit (r(:,1)); %! s_sigmaB = ricefit (r(:,2)); %! s_sigmaC = ricefit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [0.01,0.1:0.2:18]; %! y = ricepdf (x, s_sigmaA(1), s_sigmaA(2)); %! plot (x, y, '-pr'); %! y = ricepdf (x, s_sigmaB(1), s_sigmaB(2)); %! plot (x, y, '-sg'); %! y = ricepdf (x, s_sigmaC(1), s_sigmaC(2)); %! plot (x, y, '-^c'); %! hold off %! legend ({'Normalized HIST of sample 1 with s=1 and σ=2', ... %! 'Normalized HIST of sample 2 with s=2 and σ=4', ... %! 'Normalized HIST of sample 3 with s=7.5 and σ=1', ... %! sprintf("PDF for sample 1 with estimated s=%0.2f and σ=%0.2f", ... %! s_sigmaA(1), s_sigmaA(2)), ... %! sprintf("PDF for sample 2 with estimated s=%0.2f and σ=%0.2f", ... %! s_sigmaB(1), s_sigmaB(2)), ... %! sprintf("PDF for sample 3 with estimated s=%0.2f and σ=%0.2f", ... %! s_sigmaC(1), s_sigmaC(2))}) %! title ('Three population samples from different Rician distributions') %! hold off ## Test output %!test %! [paramhat, paramci] = ricefit ([1:50]); %! assert_equal (paramhat, [15.3057, 17.6668], 1e-4); %! assert_equal (paramci, [9.5468, 11.7802; 24.5383, 26.4952], 1e-4); %!test %! [paramhat, paramci] = ricefit ([1:50], 0.01); %! assert_equal (paramhat, [15.3057, 17.6668], 1e-4); %! assert_equal (paramci, [8.2309, 10.3717; 28.4615, 30.0934], 1e-4); %!test %! [paramhat, paramci] = ricefit ([1:5]); %! assert_equal (paramhat, [2.3123, 1.6812], 1e-4); %! assert_equal (paramci, [1.0819, 0.6376; 4.9424, 4.4331], 1e-4); %!test %! [paramhat, paramci] = ricefit ([1:5], 0.01); %! assert_equal (paramhat, [2.3123, 1.6812], 1e-4); %! assert_equal (paramci, [0.8521, 0.4702; 6.2747, 6.0120], 1e-4); %!test %! freq = [1 1 1 1 5]; %! [paramhat, paramci] = ricefit ([1:5], [], [], freq); %! assert_equal (paramhat, [3.5181, 1.5565], 1e-4); %! assert_equal (paramci, [2.5893, 0.9049; 4.7801, 2.6772], 1e-4); %!test %! censor = [1 0 0 0 0]; %! [paramhat, paramci] = ricefit ([1:5], [], censor); %! assert_equal (paramhat, [3.2978, 1.1527], 1e-4); %! assert_equal (paramci, [2.3192, 0.5476; 4.6895, 2.4261], 1e-4); ## Test class of input preserved %!assert_equal (class (ricefit (single ([1:50]))), "single") ## Test input validation %!error ricefit (ones (2)) %!error ricefit ([1:50], 1) %!error ricefit ([1:50], -1) %!error ricefit ([1:50], {0.05}) %!error ricefit ([1:50], 'k') %!error ricefit ([1:50], i) %!error ricefit ([1:50], [0.01 0.02]) %!error ricefit ([1:50], [], [1 1]) %!error ricefit ([1:50], [], [], [1 1]) %!error ... %! ricefit ([1:5], [], [], [1, 1, 2, 1, -1]) %!error ricefit ([1 2 3 -4]) %!error ricefit ([1 2 0], [], [1 0 0]) statistics-release-1.9.2/inst/Distribution_Fitting/ricelike.m000066400000000000000000000206351524624707500244420ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} ricelike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} ricelike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} ricelike (@var{params}, @var{x}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} ricelike (@var{params}, @var{x}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the Rician distribution. ## ## @code{@var{nlogL} = ricelike (@var{params}, @var{x})} returns the negative ## log likelihood of the data in @var{x} corresponding to the Rician ## distribution with (1) non-centrality (distance) parameter @math{s} and (2) ## scale parameter @math{sigma} given in the two-element vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = ricelike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{params} are their asymptotic variances. ## ## @code{[@dots{}] = ricelike (@var{params}, @var{x}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = ricelike (@var{params}, @var{x}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the Rician distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rice_distribution} ## ## @seealso{ricecdf, riceinv, ricepdf, ricernd, ricefit, ricestat} ## @end deftypefn function [nlogL, acov] = ricelike (params, x, censor, freq) ## Check input arguments if (nargin < 2) error ("ricelike: function called with too few input arguments."); endif if (! isvector (x)) error ("ricelike: X must be a vector."); endif if (length (params) != 2) error ("ricelike: PARAMS must be a two-element vector."); endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("ricelike: X and CENSOR vector mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("ricelike: X and FREQ vector mismatch."); elseif (any (freq < 0)) error ("ricelike: FREQ must not contain negative values."); endif ## Expand frequency and censor vectors (if necessary) if (! all (freq == 1)) xf = []; cf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; cf = [cf, repmat(censor(i), 1, freq(i))]; endfor x = xf; freq = ones (size (x)); censor = cf; endif ## Get parameters nu = params(1); sigma = params(2); theta = nu ./ sigma; xsigma = x ./ sigma; xstheta = xsigma.*theta; I_0 = besseli (0, xstheta, 1); XNS = (xsigma .^ 2 + theta .^ 2) ./ 2; ## Compute log likelihood L = -XNS + log (I_0) + log (xsigma ./ sigma) + xstheta; ## Handle censored data n_censored = sum (freq .* censor); if (n_censored > 0) censored = (censor == 1); xsigma_censored = xsigma(censored); Q = marcumQ1 (theta, xsigma_censored); L(censored) = log (Q); endif ## Sum up the neg log likelihood nlogL = -sum (freq .* L); ## Compute asymptotic covariance if (nargout > 1) ## Compute first order central differences of the log-likelihood gradient dp = 0.0001 .* max (abs (params), 1); ngrad_p1 = rice_grad (params + [dp(1), 0], x, censor, freq); ngrad_m1 = rice_grad (params - [dp(1), 0], x, censor, freq); ngrad_p2 = rice_grad (params + [0, dp(2)], x, censor, freq); ngrad_m2 = rice_grad (params - [0, dp(2)], x, censor, freq); ## Compute negative Hessian by normalizing the differences by the increment nH = [(ngrad_p1(:) - ngrad_m1(:))./(2 * dp(1)), ... (ngrad_p2(:) - ngrad_m2(:))./(2 * dp(2))]; ## Force neg Hessian being symmetric nH = 0.5 .* (nH + nH'); ## Check neg Hessian is positive definite [R, p] = chol (nH); if (p > 0) warning ("ricelike: non positive definite Hessian matrix."); acov = NaN (2); return endif ## ACOV estimate is the negative inverse of the Hessian. Rinv = inv (R); acov = Rinv * Rinv; endif endfunction ## Helper function for computing negative gradient function ngrad = rice_grad (params, x, censor, freq) ## Get parameters nu = params(1); sigma = params(2); theta = nu ./ sigma; xsigma = x ./ sigma; xstheta = xsigma.*theta; I_0 = besseli (0, xstheta, 1); XNS = (xsigma .^ 2 + theta .^ 2) ./ 2; ## Compute derivatives I_1 = besseli (1, xstheta, 1); dII = I_1 ./ I_0; dL1 = (-theta + dII .* xsigma) ./ sigma; dL2 = -2 * (1 - XNS + dII .* xstheta) ./ sigma; ## Handle censored data n_censored = sum (freq .* censor); if (n_censored > 0) censored = (censor == 1); xsigma_censored = xsigma(censored); Q = marcumQ1 (theta, xsigma_censored); expt = exp (-XNS(censored) + xstheta(censored)); dQdtheta = xsigma_censored .* I_1(censored) .* expt; dQdz = -xsigma_censored .* I_0(censored) .* expt; dtheta1 = 1 ./ sigma; dtheta2 = -theta ./ sigma; dz2 = -xsigma_censored ./ sigma; dL1(censored) = dQdtheta .* dtheta1 ./ Q; dL2(censored) = (dQdtheta .* dtheta2 + dQdz .* dz2) ./ Q; endif ## Compute gradient ngrad = -[sum(freq .* dL1) sum(freq .* dL2)]; endfunction ## Marcum's "Q" function of order 1 function Q = marcumQ1 (a, b) ## Prepare output matrix if (isa (a, 'single') || isa (b, 'single')) Q = NaN (size (b), 'single'); else Q = NaN (size (b)); endif ## Force marginal cases Q(a != Inf & b == 0) = 1; Q(a != Inf & b == Inf) = 0; Q(a == Inf & b != Inf) = 1; z = isnan (Q) & a == 0 & b != Inf; if (any (z)) Q(z) = exp ((-b(z) .^ 2) ./ 2); endif ## Compute the remaining cases z = isnan (Q) & ! isnan (a) & ! isnan (b); if (any (z(:))) aa = (a(z) .^ 2) ./ 2; bb = (b(z) .^ 2) ./ 2; eA = exp (-aa); eB = bb .* exp (-bb); h = eA; d = eB .* h; s = d; j = (d > s.*eps (class (d))); k = 1; while (any (j)) eA = aa .* eA ./ k; h = h + eA; eB = bb .* eB ./ (k + 1); d = eB .* h; s(j) = s(j) + d(j); j = (d > s .* eps (class (d))); k = k + 1; endwhile Q(z) = 1 - s; endif endfunction ## Test output %!test %! nlogL = ricelike ([15.3057344, 17.6668458], [1:50]); %! assert_equal (nlogL, 204.5230311010569, 1e-12); %!test %! nlogL = ricelike ([2.312346885, 1.681228265], [1:5]); %! assert_equal (nlogL, 8.65562164930058, 1e-12); ## Test input validation %!error ricelike (3.25) %!error ricelike ([5, 0.2], ones (2)) %!error ... %! ricelike ([1, 0.2, 3], [1, 3, 5, 7]) %!error ... %! ricelike ([1.5, 0.2], [1:5], [0, 0, 0]) %!error ... %! ricelike ([1.5, 0.2], [1:5], [0, 0, 0, 0, 0], [1, 1, 1]) %!error ... %! ricelike ([1.5, 0.2], [1:5], [], [1, 1, 1]) %!error ... %! ricelike ([1.5, 0.2], [1:5], [], [1, 1, 1, 0, -1]) statistics-release-1.9.2/inst/Distribution_Fitting/stblfit.m000066400000000000000000000262411524624707500243210ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} stblfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} stblfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} stblfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} stblfit (@var{x}, @var{alpha}, @var{freq}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} stblfit (@var{x}, @var{alpha}, @var{options}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} stblfit (@var{x}, @var{alpha}, @var{freq}, @var{options}) ## ## Estimate parameters and confidence intervals for the stable distribution. ## ## @code{@var{paramhat} = stblfit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the stable distribution, in the Nolan ## @qcode{S0} parameterization, given the data in @var{x}. ## @qcode{@var{paramhat}(1)} is the tail index @var{alpha}, ## @qcode{@var{paramhat}(2)} is the skewness @var{beta}, @qcode{@var{paramhat}(3)} ## is the scale @var{gam}, and @qcode{@var{paramhat}(4)} is the location ## @var{delta}. ## ## @code{[@var{paramhat}, @var{paramci}] = stblfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. The intervals are Wald ## intervals from the observed Fisher information. ## ## @code{[@dots{}] = stblfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default value. ## ## @code{[@dots{}] = stblfit (@var{x}, @var{alpha}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## must contain non-negative integer frequencies for the corresponding elements ## in @var{x}. By default, or if left empty, ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@var{paramhat}, @var{paramci}] = stblfit (@var{x}, @var{alpha}, ## @var{options})} specifies control parameters for the iterative algorithm used ## to compute the ML estimates with the @code{fminsearch} function. ## @var{options} is a structure with the following fields and their default ## values: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## The stable density has no closed form; it is evaluated by numerical inversion ## of the characteristic function, which makes fitting considerably slower than ## for the closed-form distributions. Censoring is not supported. ## ## The estimates are the maximum-likelihood estimates under the mathematically ## exact density. MATLAB fits an interpolation-based approximation of the stable ## density, whose maximum-likelihood estimates deviate from the exact ones by ## about @math{10^{-2}} (and the resulting confidence intervals by up to roughly ## 20%); @code{stblfit} returns the exact (more accurate) estimates. ## ## Further information about the stable distribution can be found at ## @url{https://en.wikipedia.org/wiki/Stable_distribution} ## ## @seealso{stbllike, stblpdf, stblcdf, stblinv, stblrnd, fitdist, makedist} ## @end deftypefn function [paramhat, paramci] = stblfit (x, alpha, varargin) ## Check X is a vector if (! isvector (x)) error ("stblfit: X must be a vector."); endif ## Get X type and convert to double for computation is_type = class (x); if (strcmpi (is_type, "single")) x = double (x); endif ## Check that X does not contain NaNs and is not constant if (any (isnan (x))) error ("stblfit: X must not contain NaN values."); elseif (numel (unique (x)) == 1) error ("stblfit: X must contain at least two distinct values."); endif ## Check ALPHA (significance level) if (nargin < 2 || isempty (alpha)) alpha = 0.05; elseif (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("stblfit: wrong value for ALPHA."); endif ## Add defaults freq = []; options.Display = "off"; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; ## Check extra arguments for FREQ vector and/or 'options' structure if (nargin > 2) if (numel (varargin) == 1 && isstruct (varargin{1})) options = varargin{1}; elseif (numel (varargin) == 1 && isnumeric (varargin{1})) freq = varargin{1}; elseif (numel (varargin) == 2) freq = varargin{1}; options = varargin{2}; endif if (isempty (freq)) freq = ones (size (x)); endif if (! isequal (size (x), size (freq))) error ("stblfit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("stblfit: FREQ must not contain negative values."); elseif (any (fix (freq) != freq)) error ("stblfit: FREQ must contain integer values."); endif if (! isstruct (options) || ! isfield (options, "Display") || ! isfield (options, "MaxFunEvals") || ! isfield (options, "MaxIter") || ! isfield (options, "TolX")) error (strcat ("stblfit: 'options' argument must be a structure with", ... " 'Display', 'MaxFunEvals', 'MaxIter', and 'TolX'", ... " fields present.")); endif endif if (isempty (freq)) freq = ones (size (x)); endif ## Force column vectors and drop zero-frequency observations x = x(:); freq = freq(:); keep = freq > 0; x = x(keep); freq = freq(keep); ## Objective: negative log-likelihood in the unconstrained parameter space fhandle = @(u) stbl_nll (to_nat (u), x, freq); ## Initial estimate: pick the best of a small grid of quantile-scaled starts, ## then refine. The grid spans the tail index and skewness, while the scale ## and location are matched to the sample interquartile range and median. q = quantile (x, [0.25, 0.5, 0.75]); iqr_x = q(3) - q(1); best_nll = Inf; u0 = []; for a0 = [0.7, 1.1, 1.5, 1.9] for b0 = [-0.5, 0, 0.5] ## Standardized interquartile range for this shape siqr = diff (stblinv ([0.25, 0.75], a0, b0, 1, 0)); gam0 = iqr_x / siqr; delta0 = q(2) - gam0 * stblinv (0.5, a0, b0, 1, 0); p0 = [a0, b0, gam0, delta0]; nll0 = stbl_nll (p0, x, freq); if (nll0 < best_nll) best_nll = nll0; u0 = to_unc (p0); endif endfor endfor ## Minimize the negative log-likelihood [uhat, ~, exitflag, output] = fminsearch (fhandle, u0, options); paramhat = to_nat (uhat); ## Display warnings if the optimizer did not converge if (exitflag == 0) if (output.funcCount >= output.iterations) warning ("stblfit: maximum number of function evaluations reached."); else warning ("stblfit: reached iteration limit."); endif endif ## Return a row vector for MATLAB compatibility paramhat = paramhat(:)'; ## Check for second output argument if (nargout > 1) [~, acov] = stbllike (paramhat, x, freq); param_se = sqrt (diag (acov))'; if (any (! isfinite (param_se))) warning (strcat ("stblfit: could not compute confidence intervals;", ... " the Fisher information matrix is not positive", ... " definite.")); paramci = NaN (2, 4, is_type); else ## Linear Wald intervals for all four parameters (matching MATLAB, which ## does not place the scale parameter on a log scale here) p_vals = [alpha/2; 1-alpha/2]; a_ci = norminv (p_vals, paramhat(1), param_se(1)); b_ci = norminv (p_vals, paramhat(2), param_se(2)); g_ci = norminv (p_vals, paramhat(3), param_se(3)); d_ci = norminv (p_vals, paramhat(4), param_se(4)); paramci = [a_ci, b_ci, g_ci, d_ci]; endif endif endfunction ## Map the unconstrained vector U to the natural parameters function p = to_nat (u) p = [2 ./ (1 + exp (-u(1))), tanh(u(2)), exp(u(3)), u(4)]; endfunction ## Map the natural parameters P to the unconstrained vector function u = to_unc (p) u = [log(p(1) ./ (2 - p(1))), atanh(p(2)), log(p(3)), p(4)]; endfunction ## Negative log-likelihood of the stable density at parameter vector P function nll = stbl_nll (p, x, freq) y = __stable_pdf__ (x, p(1), p(2), p(3), p(4)); y(y <= 0) = realmin; nll = -sum (freq .* log (y)); if (! isreal (nll) || isnan (nll)) nll = Inf; endif endfunction %!demo %! ## Fit a stable distribution to simulated data %! rng (42); %! x = stblrnd (1.5, 0.5, 2, 1, 150, 1); %! [paramhat, paramci] = stblfit (x) ## Test output ## The stable density has no closed form, so fitting is by numerical inversion ## of the characteristic function and is slow; the sample sizes below are kept ## modest to bound the test time, with correspondingly loose tolerances. %!test # recovery + MATLAB parity %! # Our fast integrator reproduces the exact stblpdf MLE to ~2e-5. MATLAB's %! # stable density is a Nolan interpolation approximation, so its fit %! # deviates from the exact MLE by ~1e-2; we ship the exact (more accurate) %! # estimate and document the deviation (as for the copula family). %! rand ("seed", 2718); %! randn ("seed", 2718); %! x = stblrnd (1.5, 0.5, 2, 1, 150, 1); %! [phat, pci] = stblfit (x); %! ## Exact-density estimate on this sample (recovers the generating [1.5 0.5 2 1]) %! assert_equal (phat, [1.5469000, 0.4732298, 2.0097077, 1.1640279], 1e-3); %! ## MATLAB fitdist (x, 'Stable') on the same data agrees to ~1.5e-2 %! assert_equal (phat, [1.5449145, 0.4693139, 2.0000225, 1.1646526], 1.5e-2); %! ## Confidence intervals bracket the estimate; gam CI is positive %! assert_equal (all (pci(1,:) <= phat, 'all') ... %! && all (pci(2,:) >= phat, 'all'), true); %! assert_equal (pci(1,3) > 0, true); ## Test input validation %!error stblfit (ones (2, 2)) %!error stblfit ([1, 2, NaN, 4]) %!error ... %! stblfit ([2, 2, 2, 2]) %!error stblfit ([1, 2, 3, 4], 1.5) %!error stblfit ([1, 2, 3, 4], -0.5) %!error ... %! stblfit ([1, 2, 3, 4], 0.05, [1, 2, 3]) %!error ... %! stblfit ([1, 2, 3, 4], 0.05, [1, -1, 2, 1]) %!error ... %! stblfit ([1, 2, 3, 4], 0.05, [1, 1.5, 2, 1]) statistics-release-1.9.2/inst/Distribution_Fitting/stbllike.m000066400000000000000000000153461524624707500244670ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} stbllike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} stbllike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} stbllike (@var{params}, @var{x}, @var{freq}) ## ## Negative log-likelihood for the stable distribution. ## ## @code{@var{nlogL} = stbllike (@var{params}, @var{x})} returns the negative ## log-likelihood of the data in @var{x} corresponding to the stable ## distribution, in the Nolan @qcode{S0} parameterization, with (1) tail index ## @var{alpha}, (2) skewness @var{beta}, (3) scale @var{gam}, and (4) location ## @var{delta} given in the four-element vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = stbllike (@var{params}, @var{x})} also ## returns the inverse of the observed Fisher information matrix, @var{acov}. If ## the input parameter values in @var{params} are the maximum likelihood ## estimates, the diagonal elements of @var{acov} are their asymptotic ## variances. @var{acov} is based on the numerically evaluated Hessian of the ## negative log-likelihood, since the stable density has no closed form. ## ## @code{[@dots{}] = stbllike (@var{params}, @var{x}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} must ## contain non-negative integer frequencies for the corresponding elements in ## @var{x}. By default, or if left empty, @qcode{@var{freq} = ones (size ## (@var{x}))}. ## ## Further information about the stable distribution can be found at ## @url{https://en.wikipedia.org/wiki/Stable_distribution} ## ## @seealso{stblfit, stblpdf, stblcdf, stblinv, stblrnd} ## @end deftypefn function [nlogL, acov] = stbllike (params, x, freq) ## Check input arguments if (nargin < 2) error ("stbllike: function called with too few input arguments."); endif if (! isvector (x)) error ("stbllike: X must be a vector."); endif if (numel (params) != 4) error ("stbllike: PARAMS must be a four-element vector."); endif if (nargin < 3 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("stbllike: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("stbllike: FREQ must not contain negative values."); elseif (any (fix (freq) != freq)) error ("stbllike: FREQ must contain integer values."); endif ## Force column vectors x = x(:); freq = freq(:); ## Negative log-likelihood at the given parameters nlogL = stbl_nll (params, x, freq); ## Optionally return the asymptotic covariance from the observed Fisher ## information, computed as the inverse of the numeric Hessian if (nargout > 1) theta = params(:).'; hstep = (eps ^ (1/4)) .* max (abs (theta), 1); H = num_hessian (@(t) stbl_nll (t, x, freq), theta, hstep); H = (H + H') / 2; [~, notpd] = chol (H); if (notpd != 0) warning (strcat ("stbllike: Fisher information matrix not positive", ... " definite; returning NaN covariance.")); acov = NaN (4); else acov = inv (H); acov = (acov + acov') / 2; endif endif endfunction ## Negative log-likelihood of the stable density at parameter vector THETA. ## Out-of-range parameters return Inf so that the enclosing optimizers (e.g. ## the profile-likelihood search in proflik) stay within the feasible region. function nll = stbl_nll (theta, x, freq) alpha = theta(1); beta = theta(2); gam = theta(3); if (alpha <= 0 || alpha > 2 || abs (beta) > 1 || gam <= 0) nll = Inf; return endif y = __stable_pdf__ (x, theta(1), theta(2), theta(3), theta(4)); y(y <= 0) = realmin; nll = -sum (freq .* log (y)); endfunction ## Central finite-difference Hessian of NLLFUN at THETA with per-parameter step function H = num_hessian (nllfun, theta, hstep) p = numel (theta); H = zeros (p); f0 = nllfun (theta); for i = 1:p ei = zeros (1, p); ei(i) = hstep(i); fpi = nllfun (theta + ei); fmi = nllfun (theta - ei); H(i,i) = (fpi - 2 * f0 + fmi) / (hstep(i) ^ 2); for j = (i + 1):p ej = zeros (1, p); ej(j) = hstep(j); fpp = nllfun (theta + ei + ej); fpm = nllfun (theta + ei - ej); fmp = nllfun (theta - ei + ej); fmm = nllfun (theta - ei - ej); H(i,j) = (fpp - fpm - fmp + fmm) / (4 * hstep(i) * hstep(j)); H(j,i) = H(i,j); endfor endfor endfunction %!demo %! ## Negative log-likelihood of a stable fit to simulated data %! rng (42); %! x = stblrnd (1.5, 0.5, 1, 0, 150, 1); %! phat = stblfit (x); %! nlogL = stbllike (phat, x) ## Test output %!test # matches -sum (log (pdf)); stbllike uses a fast CF-inversion density %! # that differs from stblpdf by a few parts in 1e-5 %! x = [-2.3, -0.9, 0.1, 0.4, 1.2, 2.8, 5.1]; %! nlogL = stbllike ([1.5, 0.5, 1, 0], x); %! assert_equal (nlogL, - sum (log (stblpdf (x, 1.5, 0.5, 1, 0))), 1e-3); %!test # frequency weights replicate observations %! x = [-1, 0.5, 2]; %! f = [2, 1, 3]; %! xr = [-1, -1, 0.5, 2, 2, 2]; %! assert_equal (stbllike ([1.2, 0, 1, 0], x, f), ... %! stbllike ([1.2, 0, 1, 0], xr), 1e-6); %!test # acov is symmetric positive (co)variance at a sensible parameter %! rand ("seed", 1); %! x = stblrnd (1.6, 0, 1, 0, 150, 1); %! [~, acov] = stbllike ([1.6, 0, 1, 0], x); %! assert_equal (issymmetric (acov, 1e-10), true); %! assert_equal (all (diag (acov) > 0, 'all'), true); ## Test input validation %!error ... %! stbllike ([1.5, 0, 1, 0]) %!error stbllike ([1.5, 0, 1, 0], ones (2, 2)) %!error ... %! stbllike ([1.5, 0, 1], [1, 2, 3]) %!error ... %! stbllike ([1.5, 0, 1, 0], [1, 2, 3], [1, 2]) %!error ... %! stbllike ([1.5, 0, 1, 0], [1, 2, 3], [1, -1, 2]) %!error ... %! stbllike ([1.5, 0, 1, 0], [1, 2, 3], [1, 1.5, 2]) statistics-release-1.9.2/inst/Distribution_Fitting/tlsfit.m000066400000000000000000000252371524624707500241630ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} tlsfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} tlsfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} tlsfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} tlsfit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} tlsfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} tlsfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate parameters and confidence intervals for the Location-scale Student's ## T distribution. ## ## @code{@var{muhat} = tlsfit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the location-scale T distribution ## given the data in @var{x}. @qcode{@var{paramhat}(1)} is the location ## parameter, @math{mu}, @qcode{@var{paramhat}(2)} is the scale parameter, ## @math{sigma}, and @qcode{@var{paramhat}(3)} is the degrees of freedom, ## @math{nu}. ## ## @code{[@var{paramhat}, @var{paramci}] = tlsfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = tlsfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = tlsfit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = tlsfit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@dots{}] = tlsfit (@dots{}, @var{options})} specifies control ## parameters for the iterative algorithm used to compute ML estimates with the ## @code{fminsearch} function. @var{options} is a structure with the following ## fields and their default values: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## Further information about the location-scale Student's T distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution} ## ## @seealso{tlscdf, tlsinv, tlspdf, tlsrnd, tlslike, tlsstat} ## @end deftypefn function [paramhat, paramci] = tlsfit (x, alpha, censor, freq, options) ## Check input arguments if (! isvector (x)) error ("tlsfit: X must be a vector."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("tlsfit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("tlsfit: X and CENSOR vectors mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("tlsfit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("tlsfit: FREQ cannot have negative values."); endif ## Get options structure or add defaults if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("tlsfit: 'options' 5th argument must be a structure", ... " with 'Display', 'MaxFunEvals', 'MaxIter', and", ... " 'TolX' fields present.")); endif endif ## Starting points as robust estimators for MU and SIGMA, and method ## of moments for DF x_uncensored = x(censor==0); mu = median (x_uncensored); sigma = 1.253 * mad (x_uncensored); mom = kurtosis (x_uncensored); mom(mom < 4) = 4; nu = 2 * (2 * mom - 3) / (mom - 3); x0 = [mu, sigma, nu]; ## Minimize negative log-likelihood to estimate parameters f = @(params) tlslike (params, x, censor, freq); [paramhat, ~, err, output] = fminsearch (f, x0, options); ## Handle errors if (err == 0) if (output.funcCount >= options.MaxFunEvals) warning (strcat ("tlsfit: maximum number of function", ... " evaluations are exceeded.")); elseif (output.iterations >= options.MaxIter) warning ("tlsfit: maximum number of iterations are exceeded."); endif elseif (err < 0) error ("tlsfit: no solution."); endif ## Compute CIs using a log normal approximation for parameters. if (nargout > 1) ## Compute asymptotic covariance [~, acov] = tlslike (paramhat, x, censor, freq); ## Get standard errors stderr = sqrt (diag (acov))'; mu_se = stderr(1); sigma_se = stderr(2) ./ paramhat(2); df_se = stderr(3) ./ paramhat(3); ## Apply log transform to SIGMA and DF sigma_log = log (paramhat(2)); df_log = log (paramhat(3)); ## Compute normal quantiles z = norminv (alpha / 2); ## Compute CI muci = [paramhat(1); paramhat(1)] + [mu_se; mu_se] .* [z; -z]; sigmaci = [sigma_log; sigma_log] + [sigma_se; sigma_se] .* [z; -z]; dfci = [df_log; df_log] + [df_se; df_se] .* [z; -z]; ## Inverse log transform paramci = [muci, exp([sigmaci, dfci])]; endif endfunction %!demo %! ## Sample 3 populations from 3 different location-scale T distributions %! rng (42); %! randg ('state', 42); %! r1 = tlsrnd (-4, 3, 1, 2000, 1); %! r2 = tlsrnd (0, 3, 1, 2000, 1); %! r3 = tlsrnd (5, 5, 4, 2000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, [-21:21], [1, 1, 1]); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! ylim ([0, 0.25]); %! xlim ([-20, 20]); %! hold on %! %! ## Estimate their lambda parameter %! mu_sigma_nuA = tlsfit (r(:,1)); %! mu_sigma_nuB = tlsfit (r(:,2)); %! mu_sigma_nuC = tlsfit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [-20:0.1:20]; %! y = tlspdf (x, mu_sigma_nuA(1), mu_sigma_nuA(2), mu_sigma_nuA(3)); %! plot (x, y, '-pr'); %! y = tlspdf (x, mu_sigma_nuB(1), mu_sigma_nuB(2), mu_sigma_nuB(3)); %! plot (x, y, '-sg'); %! y = tlspdf (x, mu_sigma_nuC(1), mu_sigma_nuC(2), mu_sigma_nuC(3)); %! plot (x, y, '-^c'); %! hold off %! legend ({'Normalized HIST of sample 1 with μ=0, σ=2 and nu=1', ... %! 'Normalized HIST of sample 2 with μ=5, σ=2 and nu=1', ... %! 'Normalized HIST of sample 3 with μ=3, σ=4 and nu=3', ... %! sprintf("PDF for sample 1 with estimated μ=%0.2f, σ=%0.2f, and ν=%0.2f", ... %! mu_sigma_nuA(1), mu_sigma_nuA(2), mu_sigma_nuA(3)), ... %! sprintf("PDF for sample 2 with estimated μ=%0.2f, σ=%0.2f, and ν=%0.2f", ... %! mu_sigma_nuB(1), mu_sigma_nuB(2), mu_sigma_nuB(3)), ... %! sprintf("PDF for sample 3 with estimated μ=%0.2f, σ=%0.2f, and ν=%0.2f", ... %! mu_sigma_nuC(1), mu_sigma_nuC(2), mu_sigma_nuC(3))}) %! title ('Three population samples from different location-scale T distributions') %! hold off ## Test output %!test %! x = [-1.2352, -0.2741, 0.1726, 7.4356, 1.0392, 16.4165]; %! [paramhat, paramci] = tlsfit (x); %! paramhat_out = [0.035893, 0.862711, 0.649261]; %! paramci_out = [-0.949034, 0.154655, 0.181080; 1.02082, 4.812444, 2.327914]; %! assert_equal (paramhat, paramhat_out, 1e-6); %! assert_equal (paramci, paramci_out, 1e-5); %!test %! x = [-1.2352, -0.2741, 0.1726, 7.4356, 1.0392, 16.4165]; %! [paramhat, paramci] = tlsfit (x, 0.01); %! paramci_out = [-1.2585, 0.0901, 0.1212; 1.3303, 8.2591, 3.4771]; %! assert_equal (paramci, paramci_out, 1e-4); ## Test input validation %!error tlsfit (ones (2,5)); %!error tlsfit ([1, 2, 3, 4, 5], 1.2); %!error tlsfit ([1, 2, 3, 4, 5], 0); %!error tlsfit ([1, 2, 3, 4, 5], 'alpha'); %!error ... %! tlsfit ([1, 2, 3, 4, 5], 0.05, [1 1 0]); %!error ... %! tlsfit ([1, 2, 3, 4, 5], [], [1 1 0 1 1]'); %!error ... %! tlsfit ([1, 2, 3, 4, 5], 0.05, zeros (1,5), [1 1 0]); %!error ... %! tlsfit ([1, 2, 3, 4, 5], [], [], [1 1 0 1 1]'); %!error ... %! tlsfit ([1, 2, 3, 4, 5], [], [], [1 1 0 1 -1]); %!error ... %! tlsfit ([1, 2, 3, 4, 5], 0.05, [], [], 2); ## A fit that stops at a limit warns; it used to die reading an option that ## nothing set. %!warning ... %! opt = struct ('Display', 'off', 'MaxFunEvals', 1, 'MaxIter', 200, ... %! 'TolX', 1e-6); %! tlsfit ([1, 2, 3, 4, 5, 6, 7, 8], 0.05, [], [], opt); %!warning ... %! opt = struct ('Display', 'off', 'MaxFunEvals', 400, 'MaxIter', 1, ... %! 'TolX', 1e-6); %! tlsfit ([1, 2, 3, 4, 5, 6, 7, 8], 0.05, [], [], opt); statistics-release-1.9.2/inst/Distribution_Fitting/tlslike.m000066400000000000000000000161311524624707500243160ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR l PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} tlslike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} tlslike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} tlslike (@var{params}, @var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} tlslike (@var{params}, @var{x}, @var{alpha}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the location-scale Student's T distribution. ## ## @code{@var{nlogL} = tlslike (@var{params}, @var{x})} returns the negative ## log-likelihood of the x in @var{x} corresponding to the location-scale T ## distribution with (1) location parameter @math{mu}, (2) scale parameter ## @math{sigma} and (3) degrees of freedom @math{nu} given in the three-element ## vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = tlslike (@var{params}, @var{x})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{acov} are their asymptotic variances. @var{acov} ## is based on the observed Fisher's information, not the expected information. ## ## @code{[@dots{}] = tlslike (@var{params}, @var{x}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = tlslike (@var{params}, @var{x}, @var{censor}, ## @var{freq})} accepts a frequency vector, @var{freq}, of the same size as ## @var{x}. @var{freq} typically contains integer frequencies for the ## corresponding elements in @var{x}, but may contain any non-integer ## non-negative values. By default, or if left empty, ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the location-scale Student's T distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution} ## ## @seealso{tlscdf, tlsinv, tlspdf, tlsrnd, tlsfit, tlsstat} ## @end deftypefn function [nlogL, acov] = tlslike (params, x, censor, freq) ## Check input arguments and add defaults if (nargin < 2) error ("tlslike: too few input arguments."); endif if (numel (params) != 3) error ("tlslike: wrong parameters length."); endif if (! isvector (x)) error ("tlslike: X must be a vector."); endif if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("tlslike: X and CENSOR vectors mismatch."); endif if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (any (freq < 0)) error ("tlslike: FREQ cannot have negative values."); elseif (isequal (size (x), size (freq))) nulls = find (freq == 0); if (numel (nulls) > 0) x(nulls) = []; censor(nulls) = []; freq(nulls) = []; endif else error ("tlslike: X and FREQ vectors mismatch."); endif ## Compute the negative log-likelihood nlogL = tlsnll (x, params, censor, freq); ## Compute the negative hessian and invert to get the information matrix if (nargout > 1) ei = zeros (1, 3); ej = zeros (1, 3); nH = zeros (3, 3); dp = (eps ^ (1/4)) .* max (abs (params), 1); for i = 1:3 ei(i) = dp(i); for j = 1:(i-1) ej(j) = dp(j); ## Four-point central difference for mixed second partials nH(i,j) = tlsnll (x, params+ei+ej, censor, freq) ... - tlsnll (x, params+ei-ej, censor, freq) ... - tlsnll (x, params-ei+ej, censor, freq) ... + tlsnll (x, params-ei-ej, censor, freq); ej(j) = 0; endfor ## Five-point central difference for pure second partial nH(i,i) = - tlsnll (x, params+2*ei, censor, freq) ... + 16 * tlsnll (x, params+ei, censor, freq) - 30 * nlogL ... + 16 * tlsnll (x, params-ei, censor, freq) ... - tlsnll (x, params-2*ei, censor, freq); ei(i) = 0; endfor ## Fill in the upper triangle nH = nH + triu (nH', 1); ## Normalize the second differences to get derivative estimates nH = nH ./ (4 .* dp(:) * dp(:)' + diag (8 * dp(:) .^ 2)); ## Check neg Hessian is positive definite [R, p] = chol (nH); if (p > 0) warning ("tlslike: non positive definite Hessian matrix."); acov = NaN (3); return endif ## ACOV estimate is the negative inverse of the Hessian Rinv = inv (R); acov = Rinv * Rinv'; endif endfunction ## Internal function to calculate negative log likelihood for tlslike function nlogL = tlsnll (x, params, censor, freq) mu = params(1); sigma = params(2); sigma(sigma <= 0) = NaN; nu = params(3); nu(nu <= 0) = NaN; z = (x - mu) ./ sigma; w = nu + (z .^ 2); logw = log (w); L = - 0.5 .* (nu + 1) .* logw + gammaln (0.5 .* (nu + 1)) ... - gammaln (0.5 .* nu) + 0.5 .* nu .* log (nu) ... - log (sigma) - 0.5 .* log (pi); n_censored = sum (freq .* censor); if (n_censored > 0) censored = (censor == 1); if (nu < 1e7) # Use incomplete beta function S_censored = betainc (nu ./ w(censored), 0.5 .* nu, 0.5) ./ 2; S_censored(z(censored) < 0) = 1 - S_censored(z(censored) < 0); else # Use a normal approximation S_censored = log (0.5 * erfc (z(censored) ./ sqrt (2))); endif L(censored) = log (S_censored); endif nlogL = - sum (freq .* L); endfunction ## Test output %!test %! x = [-1.2352, -0.2741, 0.1726, 7.4356, 1.0392, 16.4165]; %! [nlogL, acov] = tlslike ([0.035893, 0.862711, 0.649261], x); %! acov_out = [0.2525, 0.0670, 0.0288; ... %! 0.0670, 0.5724, 0.1786; ... %! 0.0288, 0.1786, 0.1789]; %! assert_equal (nlogL, 17.9979636579, 1e-10); %! assert_equal (acov, acov_out, 1e-4); ## Test input validation %!error tlslike ([12, 15, 1]); %!error tlslike ([12, 15], [1:50]); %!error tlslike ([12, 3, 1], ones (10, 2)); %!error tlslike ([12, 15, 1], [1:50], [1, 2, 3]); %!error tlslike ([12, 15, 1], [1:50], [], [1, 2, 3]); %!error tlslike ([12, 15, 1], [1:3], [], [1, 2, -3]); statistics-release-1.9.2/inst/Distribution_Fitting/unidfit.m000066400000000000000000000127241524624707500243150ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Nhat} =} unidfit (@var{x}) ## @deftypefnx {statistics} {[@var{Nhat}, @var{Nci}] =} unidfit (@var{x}) ## @deftypefnx {statistics} {[@var{Nhat}, @var{Nci}] =} unidfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@var{Nhat}, @var{Nci}] =} unidfit (@var{x}, @var{alpha}, @var{freq}) ## ## Estimate parameter and confidence intervals for the discrete uniform ## distribution. ## ## @code{@var{Nhat} = unidfit (@var{x})} returns the maximum likelihood estimate ## (MLE) of the maximum observable value for the discrete uniform distribution. ## @var{x} must be a vector. ## ## @code{[@var{Nhat}, @var{Nci}] = unidfit (@var{x}, @var{alpha})} also ## returns the @qcode{100 * (1 - @var{alpha})} percent confidence intervals of ## the estimated parameter. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = unidfit (@var{x}, @var{alpha}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## typically contains integer frequencies for the corresponding elements in ## @var{x}, but it can contain any non-integer non-negative values. By default, ## or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the discrete uniform distribution can be found at ## @url{https://en.wikipedia.org/wiki/Discrete_uniform_distribution} ## ## @seealso{unidcdf, unidinv, unidpdf, unidrnd, unidstat} ## @end deftypefn function [Nhat, Nci] = unidfit (x, alpha, freq) ## Check input arguments if (nargin < 1) error ("unidfit: function called with too few input arguments."); endif ## Check data in X if (any (x < 0)) error ("unidfit: X cannot have negative values."); endif if (! isvector (x)) error ("unidfit: X must be a vector."); endif ## Check ALPHA if (nargin < 2 || isempty (alpha)) alpha = 0.05; elseif (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("unidfit: wrong value for ALPHA."); endif ## Check frequency vector if (nargin < 3 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("unidfit: X and FREQ vector mismatch."); elseif (any (freq < 0)) error ("unidfit: FREQ cannot have negative values."); endif ## Expand frequency and censor vectors (if necessary) if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; freq = ones (size (x)); endif ## Compute N estimate Nhat = max (x); ## Compute confidence interval of N if (nargout > 1) Nci = [Nhat; ceil(Nhat ./ alpha .^ (1 ./ numel (x)))]; endif endfunction %!demo %! ## Sample 2 populations from different discrete uniform distributions %! rng (42); %! r1 = unidrnd (5, 1000, 1); %! r2 = unidrnd (9, 1000, 1); %! r = [r1, r2]; %! %! ## Plot them normalized and fix their colors %! hist (r, 0:0.5:20.5, 1); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! hold on %! %! ## Estimate their probability of success %! NhatA = unidfit (r(:,1)); %! NhatB = unidfit (r(:,2)); %! %! ## Plot their estimated PDFs %! x = [0:10]; %! y = unidpdf (x, NhatA); %! plot (x, y, '-pg'); %! y = unidpdf (x, NhatB); %! plot (x, y, '-sc'); %! xlim ([0, 10]) %! ylim ([0, 0.4]) %! legend ({'Normalized HIST of sample 1 with N=5', ... %! 'Normalized HIST of sample 2 with N=9', ... %! sprintf("PDF for sample 1 with estimated N=%0.2f", NhatA), ... %! sprintf("PDF for sample 2 with estimated N=%0.2f", NhatB)}) %! title ('Two population samples from different discrete uniform distributions') %! hold off ## Test output %!test %! x = 0:5; %! [Nhat, Nci] = unidfit (x); %! assert_equal (Nhat, 5); %! assert_equal (Nci, [5; 9]); %!test %! x = 0:5; %! [Nhat, Nci] = unidfit (x, [], [1 1 1 1 1 1]); %! assert_equal (Nhat, 5); %! assert_equal (Nci, [5; 9]); %!assert_equal (unidfit ([1 1 2 3]), unidfit ([1 2 3], [] ,[2 1 1])) ## Test input validation %!error unidfit () %!error unidfit (-1, [1 2 3 3]) %!error unidfit (1, 0) %!error unidfit (1, 1.2) %!error unidfit (1, [0.02 0.05]) %!error ... %! unidfit ([1.5, 0.2], [], [0, 0, 0, 0, 0]) %!error ... %! unidfit ([1.5, 0.2], [], [1, 1, 1]) %!error ... %! unidfit ([1.5, 0.2], [], [1, -1]) statistics-release-1.9.2/inst/Distribution_Fitting/unifit.m000066400000000000000000000212331524624707500241440ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{ahat} =} unifit (@var{x}) ## @deftypefnx {statistics} {[@var{ahat}, @var{bhat}] =} unifit (@var{x}) ## @deftypefnx {statistics} {[@var{ahat}, @var{bhat}, @var{aci}, @var{bci}] =} unifit (@var{x}) ## @deftypefnx {statistics} {[@dots{}] =} unifit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} unifit (@var{x}, @var{alpha}, @var{freq}) ## ## Estimate parameters and confidence intervals for the continuous uniform ## distribution. ## ## @code{[@var{ahat}, @var{bhat}] = unifit (@var{x})} returns the maximum ## likelihood estimates of the lower and upper endpoints, @var{a} and @var{b}, ## of the continuous uniform distribution given the data in @var{x}. Each ## estimate is returned as a separate output. ## ## @var{x} may be a vector, which is fitted as a single sample, or a matrix, ## which is fitted column by column. For a matrix of @math{n} columns ## @var{ahat} and @var{bhat} are @math{1}-by-@math{n} row vectors and @var{aci} ## and @var{bci} are @math{2}-by-@math{n}. ## ## @code{[@var{ahat}, @var{bhat}, @var{aci}, @var{bci}] = unifit (@var{x})} also ## returns the 95% confidence intervals of the two estimates, one column per ## column of @var{x}, with the lower bound in the first row and the upper bound ## in the second. @var{ahat} is the upper bound of @var{aci} and @var{bhat} the ## lower bound of @var{bci}, since no sample can fall outside the fitted range. ## ## @code{[@dots{}] = unifit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals of the ## estimated parameters. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = unifit (@var{x}, @var{alpha}, @var{freq})} accepts a ## frequency vector, @var{freq}, of the same size as @var{x}. @var{freq} ## typically contains integer frequencies for the corresponding elements in ## @var{x}, but it can contain any non-integer non-negative values. By default, ## or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. This third ## argument is an Octave extension; MATLAB's @code{unifit} takes two inputs at ## most, and @var{freq} is accepted for a vector @var{x} only. ## ## Further information about the continuous uniform distribution can be found at ## @url{https://en.wikipedia.org/wiki/Continuous_uniform_distribution} ## ## @seealso{unifcdf, unifinv, unifpdf, unifrnd, unifstat} ## @end deftypefn function [ahat, bhat, aci, bci] = unifit (x, alpha, freq) ## Check input arguments if (nargin < 1) error ("unifit: function called with too few input arguments."); endif if (! isnumeric (x) || ! isreal (x)) error ("unifit: X must be a vector or matrix of real values."); endif ## Check ALPHA if (nargin < 2 || isempty (alpha)) alpha = 0.05; elseif (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("unifit: wrong value for ALPHA."); endif ## Check frequency vector if (nargin > 2 && ! isempty (freq)) if (! isvector (x)) error ("unifit: FREQ is supported for a vector X only."); elseif (! isequal (size (x), size (freq))) error ("unifit: X and FREQ vector mismatch."); elseif (any (freq < 0)) error ("unifit: FREQ cannot have negative values."); endif ## Expand frequency vector if (! all (freq == 1)) xf = []; for i = 1:numel (freq) xf = [xf, repmat(x(i), 1, freq(i))]; endfor x = xf; endif endif if (isempty (x)) ahat = []; bhat = []; aci = []; bci = []; return endif ## A vector is one sample whichever way it lies; a matrix is fitted by column if (isvector (x)) x = x(:); endif ## Compute A and B estimates ahat = min (x, [], 1); bhat = max (x, [], 1); ## Compute the confidence intervals of A and B. No sample can fall outside ## the fitted range, so AHAT bounds ACI from above and BHAT bounds BCI from ## below. if (nargout > 2) tmp = (bhat - ahat) ./ alpha .^ (1 ./ rows (x)); aci = [bhat - tmp; ahat]; bci = [bhat; ahat + tmp]; endif endfunction %!demo %! ## Sample 2 populations from different continuous uniform distributions %! rng (42); %! r1 = unifrnd (2, 5, 2000, 1); %! r2 = unifrnd (3, 9, 2000, 1); %! r = [r1, r2]; %! %! ## Plot them normalized and fix their colors %! hist (r, 0:0.5:10, 2); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! hold on %! %! ## Estimate their probability of success %! a_bA = unifit (r(:,1)); %! a_bB = unifit (r(:,2)); %! %! ## Plot their estimated PDFs %! x = [0:10]; %! y = unifpdf (x, a_bA(1), a_bA(2)); %! plot (x, y, '-pg'); %! y = unifpdf (x, a_bB(1), a_bB(2)); %! plot (x, y, '-sc'); %! xlim ([1, 10]) %! ylim ([0, 0.5]) %! legend ({'Normalized HIST of sample 1 with a=2 and b=5', ... %! 'Normalized HIST of sample 2 with a=3 and b=9', ... %! sprintf("PDF for sample 1 with estimated a=%0.2f and b=%0.2f", ... %! a_bA(1), a_bA(2)), ... %! sprintf("PDF for sample 2 with estimated a=%0.2f and b=%0.2f", ... %! a_bB(1), a_bB(2))}) %! title ('Two population samples from different continuous uniform distributions') %! hold off ## Test output ## Values below are R2024a's, measured 2026-08-17. %!test %! [ahat, bhat] = unifit (0:5); %! assert_equal (ahat, 0); %! assert_equal (bhat, 5); %!test %! [ahat, bhat, aci, bci] = unifit (0:5); %! assert_equal (aci, [-3.237744862210329; 0], 1e-12); %! assert_equal (bci, [5; 8.237744862210329], 1e-12); %!test %! [~, ~, aci, bci] = unifit (0:5, 0.10); %! assert_equal (aci, [-2.338996338110347; 0], 1e-12); %! assert_equal (bci, [5; 7.338996338110347], 1e-12); %!test %! ## a column vector is the same single sample as a row %! [ahat, bhat, aci, bci] = unifit ((0:5)'); %! assert_equal ([ahat, bhat], [0, 5]); %! assert_equal (aci, [-3.237744862210329; 0], 1e-12); %!test %! ## a matrix is fitted column by column %! [ahat, bhat, aci, bci] = unifit ([0 10; 1 11; 2 12; 3 13; 4 14; 5 15]); %! assert_equal (ahat, [0, 10]); %! assert_equal (bhat, [5, 15]); %! assert_equal (aci, [-3.237744862210329, 6.762255137789671; 0, 10], 1e-12); %! assert_equal (bci, [5, 15; 8.237744862210329, 18.237744862210327], 1e-12); %!test %! ## negative data is ordinary for a uniform distribution %! [ahat, bhat, aci, bci] = unifit ([-2, -1, 0, 1, 2]); %! assert_equal ([ahat, bhat], [-2, 2]); %! assert_equal (aci, [-5.282256812104322; -2], 1e-12); %! assert_equal (bci, [2; 5.282256812104322], 1e-12); %!test %! ## a one-element sample has no width %! [ahat, bhat, aci, bci] = unifit (5); %! assert_equal ([ahat, bhat], [5, 5]); %! assert_equal (aci, [5; 5]); %! assert_equal (bci, [5; 5]); %!test %! ## empty data gives empty estimates %! [ahat, bhat, aci, bci] = unifit ([]); %! assert_equal (isempty (ahat), true); %! assert_equal (isempty (bhat), true); %! assert_equal (isempty (aci), true); %!test %! ## the endpoints ignore NaN %! [ahat, bhat] = unifit ([0 1 NaN 3]); %! assert_equal ([ahat, bhat], [0, 3]); %!test %! ## FREQ counts repeated observations %! [a1, b1] = unifit ([1 1 2 3]); %! [a2, b2] = unifit ([1 2 3], [], [2 1 1]); %! assert_equal ([a1, b1], [a2, b2]); ## Test input validation %!error unifit () %!error unifit ({1, 2}) %!error unifit (1+2i) %!error unifit (1, 0) %!error unifit (1, 1.2) %!error unifit (1, [0.02 0.05]) %!error ... %! unifit ([1 2; 3 4], [], [1 1; 1 1]) %!error ... %! unifit ([1.5, 0.2], [], [0, 0, 0, 0, 0]) %!error ... %! unifit ([1.5, 0.2], [], [1, -1]) statistics-release-1.9.2/inst/Distribution_Fitting/wblfit.m000066400000000000000000000207531524624707500241430ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{paramhat} =} wblfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} wblfit (@var{x}) ## @deftypefnx {statistics} {[@var{paramhat}, @var{paramci}] =} wblfit (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} wblfit (@var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} wblfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@dots{}] =} wblfit (@var{x}, @var{alpha}, @var{censor}, @var{freq}, @var{options}) ## ## Estimate parameters and confidence intervals for the Weibull distribution. ## ## @code{@var{muhat} = wblfit (@var{x})} returns the maximum likelihood ## estimates of the parameters of the Weibull distribution given the data in ## @var{x}. @qcode{@var{paramhat}(1)} is the scale parameter, @math{lambda}, ## and @qcode{@var{paramhat}(2)} is the shape parameter, @math{k}. ## ## @code{[@var{paramhat}, @var{paramci}] = wblfit (@var{x})} returns the 95% ## confidence intervals for the parameter estimates. ## ## @code{[@dots{}] = wblfit (@var{x}, @var{alpha})} also returns the ## @qcode{100 * (1 - @var{alpha})} percent confidence intervals for the ## parameter estimates. By default, the optional argument @var{alpha} is ## 0.05 corresponding to 95% confidence intervals. Pass in @qcode{[]} for ## @var{alpha} to use the default values. ## ## @code{[@dots{}] = wblfit (@var{x}, @var{alpha}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = wblfit (@var{x}, @var{alpha}, @var{censor}, @var{freq})} ## accepts a frequency vector, @var{freq}, of the same size as @var{x}. ## @var{freq} typically contains integer frequencies for the corresponding ## elements in @var{x}, but it can contain any non-integer non-negative values. ## By default, or if left empty, @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @code{[@dots{}] = wblfit (@dots{}, @var{options})} specifies control ## parameters for the iterative algorithm used to compute the maximum likelihood ## estimates. @var{options} is a structure with the following field and its ## default value: ## @itemize ## @item @qcode{@var{options}.Display = "off"} ## @item @qcode{@var{options}.MaxFunEvals = 400} ## @item @qcode{@var{options}.MaxIter = 200} ## @item @qcode{@var{options}.TolX = 1e-6} ## @end itemize ## ## Further information about the Weibull distribution can be found at ## @url{https://en.wikipedia.org/wiki/Weibull_distribution} ## ## The @code{prob.WeibullDistribution} class names these same two parameters ## @qcode{A} and @qcode{B}, after MATLAB. @var{lambda} is its @qcode{A} and ## @var{k} is its @qcode{B}. ## @seealso{wblcdf, wblinv, wblpdf, wblrnd, wbllike, wblstat} ## @end deftypefn function [paramhat, paramci] = wblfit (x, alpha, censor, freq, options) ## Check input arguments if (! isvector (x)) error ("wblfit: X must be a vector."); elseif (any (x <= 0)) error ("wblfit: X must contain only positive values."); endif ## Check alpha if (nargin < 2 || isempty (alpha)) alpha = 0.05; else if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("wblfit: wrong value for ALPHA."); endif endif ## Check censor vector if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("wblfit: X and CENSOR vectors mismatch."); endif ## Check frequency vector if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error ("wblfit: X and FREQ vectors mismatch."); elseif (any (freq < 0)) error ("wblfit: FREQ cannot have negative values."); endif ## Get options structure or add defaults if (nargin < 5) options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; else if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("wblfit: 'options' 5th argument must be a", ... " structure with 'Display', 'MaxFunEvals',", ... " 'MaxIter', and 'TolX' fields present.")); endif endif ## Fit an extreme value distribution to the logged data, then transform to ## the Weibull parameter scales. [paramhatEV, paramciEV] = evfit (log (x), alpha, censor, freq, options); paramhat = [exp(paramhatEV(1)), 1./paramhatEV(2)]; if (nargout > 1) paramci = [exp(paramciEV(:,1)) 1./paramciEV([2 1],2)]; endif endfunction %!demo %! ## Sample 3 populations from 3 different Weibull distributions %! rande ('state', 42); %! r1 = wblrnd (2, 4, 2000, 1); %! r2 = wblrnd (5, 2, 2000, 1); %! r3 = wblrnd (1, 5, 2000, 1); %! r = [r1, r2, r3]; %! %! ## Plot them normalized and fix their colors %! hist (r, 30, [2.5 2.1 3.2]); %! h = findobj (gca, 'Type', 'patch'); %! set (h(1), 'facecolor', 'c'); %! set (h(2), 'facecolor', 'g'); %! set (h(3), 'facecolor', 'r'); %! ylim ([0, 2]); %! xlim ([0, 10]); %! hold on %! %! ## Estimate their lambda parameter %! lambda_kA = wblfit (r(:,1)); %! lambda_kB = wblfit (r(:,2)); %! lambda_kC = wblfit (r(:,3)); %! %! ## Plot their estimated PDFs %! x = [0:0.1:15]; %! y = wblpdf (x, lambda_kA(1), lambda_kA(2)); %! plot (x, y, '-pr'); %! y = wblpdf (x, lambda_kB(1), lambda_kB(2)); %! plot (x, y, '-sg'); %! y = wblpdf (x, lambda_kC(1), lambda_kC(2)); %! plot (x, y, '-^c'); %! hold off %! legend ({'Normalized HIST of sample 1 with λ=2 and k=4', ... %! 'Normalized HIST of sample 2 with λ=5 and k=2', ... %! 'Normalized HIST of sample 3 with λ=1 and k=5', ... %! sprintf("PDF for sample 1 with estimated λ=%0.2f and k=%0.2f", ... %! lambda_kA(1), lambda_kA(2)), ... %! sprintf("PDF for sample 2 with estimated λ=%0.2f and k=%0.2f", ... %! lambda_kB(1), lambda_kB(2)), ... %! sprintf("PDF for sample 3 with estimated λ=%0.2f and k=%0.2f", ... %! lambda_kC(1), lambda_kC(2))}) %! title ('Three population samples from different Weibull distributions') %! hold off ## Test output %!test %! x = 1:50; %! [paramhat, paramci] = wblfit (x); %! paramhat_out = [28.3636, 1.7130]; %! paramci_out = [23.9531, 1.3551; 33.5861, 2.1655]; %! assert_equal (paramhat, paramhat_out, 1e-4); %! assert_equal (paramci, paramci_out, 1e-4); %!test %! x = 1:50; %! [paramhat, paramci] = wblfit (x, 0.01); %! paramci_out = [22.7143, 1.2589; 35.4179, 2.3310]; %! assert_equal (paramci, paramci_out, 1e-4); ## Test input validation %!error wblfit (ones (2,5)); %!error wblfit ([-1 2 3 4]); %!error wblfit ([1, 2, 3, 4, 5], 1.2); %!error wblfit ([1, 2, 3, 4, 5], 0); %!error wblfit ([1, 2, 3, 4, 5], 'alpha'); %!error ... %! wblfit ([1, 2, 3, 4, 5], 0.05, [1 1 0]); %!error ... %! wblfit ([1, 2, 3, 4, 5], [], [1 1 0 1 1]'); %!error ... %! wblfit ([1, 2, 3, 4, 5], 0.05, zeros (1,5), [1 1 0]); %!error ... %! wblfit ([1, 2, 3, 4, 5], [], [], [1 1 0 -1 1]); %!error ... %! wblfit ([1, 2, 3, 4, 5], [], [], [1 1 0 1 1]'); %!error ... %! wblfit ([1, 2, 3, 4, 5], 0.05, [], [], 2); statistics-release-1.9.2/inst/Distribution_Fitting/wbllike.m000066400000000000000000000135551524624707500243070ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR l PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} wbllike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{acov}] =} wbllike (@var{params}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} wbllike (@var{params}, @var{x}, @var{alpha}, @var{censor}) ## @deftypefnx {statistics} {[@dots{}] =} wbllike (@var{params}, @var{x}, @var{alpha}, @var{censor}, @var{freq}) ## ## Negative log-likelihood for the Weibull distribution. ## ## @code{@var{nlogL} = wbllike (@var{params}, @var{data})} returns the negative ## log-likelihood of the data in @var{x} corresponding to the Weibull ## distribution with (1) scale parameter @math{lambda} and (2) shape parameter ## @math{k} given in the two-element vector @var{params}. ## ## @code{[@var{nlogL}, @var{acov}] = wbllike (@var{params}, @var{data})} also ## returns the inverse of Fisher's information matrix, @var{acov}. If the input ## parameter values in @var{params} are the maximum likelihood estimates, the ## diagonal elements of @var{acov} are their asymptotic variances. @var{acov} ## is based on the observed Fisher's information, not the expected information. ## ## @code{[@dots{}] = wbllike (@var{params}, @var{data}, @var{censor})} accepts a ## boolean vector, @var{censor}, of the same size as @var{x} with @qcode{1}s for ## observations that are right-censored and @qcode{0}s for observations that are ## observed exactly. By default, or if left empty, ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @code{[@dots{}] = wbllike (@var{params}, @var{data}, @var{censor}, ## @var{freq})} accepts a frequency vector, @var{freq}, of the same size as ## @var{x}. @var{freq} typically contains integer frequencies for the ## corresponding elements in @var{x}, but may contain any non-integer ## non-negative values. By default, or if left empty, ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## Further information about the Weibull distribution can be found at ## @url{https://en.wikipedia.org/wiki/Weibull_distribution} ## ## The @code{prob.WeibullDistribution} class names these same two parameters ## @qcode{A} and @qcode{B}, after MATLAB. @var{lambda} is its @qcode{A} and ## @var{k} is its @qcode{B}. ## @seealso{wblcdf, wblinv, wblpdf, wblrnd, wblfit, wblstat} ## @end deftypefn function [nlogL, acov] = wbllike (params, x, censor, freq) ## Check input arguments and add defaults if (nargin < 2) error ("wbllike: too few input arguments."); endif if (numel (params) != 2) error ("wbllike: wrong parameters length."); endif if (! isvector (x)) error ("wbllike: X must be a vector."); endif if (nargin < 3 || isempty (censor)) censor = zeros (size (x)); elseif (! isequal (size (x), size (censor))) error ("wbllike: X and CENSOR vectors mismatch."); endif if (nargin < 4 || isempty (freq)) freq = ones (size (x)); elseif (any (freq < 0)) error ("wbllike: FREQ cannot have negative values."); elseif (isequal (size (x), size (freq))) nulls = find (freq == 0); if (numel (nulls) > 0) x(nulls) = []; censor(nulls) = []; freq(nulls) = []; endif else error ("wbllike: X and FREQ vectors mismatch."); endif ## Get lambda and k parameter values l = params(1); k = params(2); ## Force NaNs for out of range parameters or x. l(l <= 0) = NaN; k(k <= 0) = NaN; x(x < 0) = NaN; ## Compute the individual log-likelihood terms z = x ./ l; logz = log (z); expz = exp (k .* logz); ilogL = ((k - 1) .* logz + log (k ./ l)) .* (1 - censor) - expz; ilogL(z == Inf) = -Inf; ## Sum up the individual log-likelihood contributions nlogL = -sum (freq .* ilogL); ## Compute the negative hessian and invert to get the information matrix. if (nargout > 1) ucen = (1 - censor); nH11 = sum (freq .* (k .* ((1 + k) .* expz - ucen))) ./ l .^ 2; nH12 = -sum (freq .* (((1 + k .* logz) .* expz - ucen))) ./ l; nH22 = sum (freq .* ((logz .^ 2) .* expz + ucen ./ k .^ 2)); acov = [nH22, -nH12; -nH12, nH11] / (nH11 * nH22 - nH12 * nH12); endif endfunction ## Test output %!test %! x = 1:50; %! [nlogL, acov] = wbllike ([2.3, 1.2], x); %! avar_out = [0.0250, 0.0062; 0.0062, 0.0017]; %! assert_equal (nlogL, 945.9589180651594, 1e-12); %! assert_equal (acov, avar_out, 1e-4); %!test %! x = 1:50; %! [nlogL, acov] = wbllike ([2.3, 1.2], x * 0.5); %! avar_out = [-0.3238, -0.1112; -0.1112, -0.0376]; %! assert_equal (nlogL, 424.9879809704742, 6e-14); %! assert_equal (acov, avar_out, 1e-4); %!test %! x = 1:50; %! [nlogL, acov] = wbllike ([21, 15], x); %! avar_out = [-0.00001236, -0.00001166; -0.00001166, -0.00001009]; %! assert_equal (nlogL, 1635190.328991511, 1e-8); %! assert_equal (acov, avar_out, 1e-8); ## Test input validation %!error wbllike ([12, 15]); %!error wbllike ([12, 15, 3], [1:50]); %!error wbllike ([12, 3], ones (10, 2)); %!error wbllike ([12, 15], [1:50], [1, 2, 3]); %!error wbllike ([12, 15], [1:50], [], [1, 2, 3]); %!error ... %! wbllike ([12, 15], [1:5], [], [1, 2, 3, -1, 0]); statistics-release-1.9.2/inst/Distribution_Functions/000077500000000000000000000000001524624707500230335ustar00rootroot00000000000000statistics-release-1.9.2/inst/Distribution_Functions/betacdf.m000066400000000000000000000157661524624707500246200ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} betacdf (@var{x}, @var{a}, @var{b}) ## @deftypefnx {statistics} {@var{p} =} betacdf (@var{x}, @var{a}, @var{b}, @qcode{'upper'}) ## ## Beta cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function of ## the Beta distribution with shape parameters @var{a} and @var{b}. The size of ## @var{p} is the common size of @var{x}, @var{a}, and @var{b}. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## @code{@var{p} = betacdf (@var{x}, @var{a}, @var{b}, "upper")} computes the ## upper tail probability of the Beta distribution with parameters @var{a} and ## @var{b}, at the values in @var{x}. ## ## Further information about the Beta distribution can be found at ## @url{https://en.wikipedia.org/wiki/Beta_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{betainv, betapdf, betarnd, betafit, betalike, betastat} ## @end deftypefn function p = betacdf (x, a, b, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("betacdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 3) if (! strcmpi (uflag, 'upper')) error ("betacdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, A, and B if (! isscalar (x) || ! isscalar (a) || ! isscalar (b)) [err, x, a, b] = common_size (x, a, b); if (err > 0) error ("betacdf: X, A, and B must be of common size or scalars."); endif endif ## Check for X, A, and B being double or single if (! (isfloat (x) && isfloat (a) && isfloat (b))) error ("betacdf: X, A, and B must be double or single."); endif ## Check for X, A, and B being reals if (iscomplex (x) || iscomplex (a) || iscomplex (b)) error ("betacdf: X, A, and B must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (a, 'single') || isa (b, 'single')) is_type = 'single'; else is_type = 'double'; endif ## Find valid values in parameters and data okPARAM = (0 < a & a < Inf) & (0 < b & b < Inf); okDATA = (okPARAM & (0 <= x & x <= 1)); all_OK = all (okDATA(:)); ## Force NaNs for out of range parameters. ## Fill in edges cases when X is outside 0 or 1. if (! all_OK) p = NaN (size (okDATA), is_type); if (uflag) p(okPARAM & x <= 0) = 1; p(okPARAM & x >= 1) = 0; else p(okPARAM & x < 0) = 0; p(okPARAM & x > 1) = 1; endif ## Remove the out of range/edge cases. Return, if there's nothing left. if (any (okDATA(:))) if (numel (x) > 1) x = x(okDATA); endif if (numel (a) > 1) a = a(okDATA); endif if (numel (b) > 1) b = b(okDATA); endif else return; endif endif ## Call betainc for the actual work if (uflag) pk = betainc (x, a, b, 'upper'); else pk = betainc (x, a, b); endif ## Relocate the values to the correct places if necessary. if all_OK p = pk; else p(okDATA) = pk; endif endfunction %!demo %! ## Plot various CDFs from the Beta distribution %! x = 0:0.005:1; %! p1 = betacdf (x, 0.5, 0.5); %! p2 = betacdf (x, 5, 1); %! p3 = betacdf (x, 1, 3); %! p4 = betacdf (x, 2, 2); %! p5 = betacdf (x, 2, 5); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c', x, p5, '-m') %! grid on %! legend ({'α = β = 0.5', 'α = 5, β = 1', 'α = 1, β = 3', ... %! 'α = 2, β = 2', 'α = 2, β = 5'}, 'location', 'northwest') %! title ('Beta CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y, x1, x2 %! x = [-1 0 0.5 1 2]; %! y = [0 0 0.75 1 1]; %!assert_equal (betacdf (x, ones (1, 5), 2 * ones (1, 5)), y) %!assert_equal (betacdf (x, 1, 2 * ones (1, 5)), y) %!assert_equal (betacdf (x, ones (1, 5), 2), y) %!assert_equal (betacdf (x, [0 1 NaN 1 1], 2), [NaN 0 NaN 1 1]) %!assert_equal (betacdf (x, 1, 2 * [0 1 NaN 1 1]), [NaN 0 NaN 1 1]) %!assert_equal (betacdf ([x(1:2) NaN x(4:5)], 1, 2), [y(1:2) NaN y(4:5)]) %! x1 = [0.1:0.2:0.9]; %!assert_equal (betacdf (x1, 2, 2), [0.028, 0.216, 0.5, 0.784, 0.972], 1e-14); %!assert_equal (betacdf (x1, 2, 2, 'upper'), 1 - [0.028, 0.216, 0.5, 0.784, 0.972],... %! 1e-14); %! x2 = [1, 2, 3]; %!assert_equal (betacdf (0.5, x2, x2), [0.5, 0.5, 0.5], 1e-14); %!assert_equal (betacdf ([x, NaN], 1, 2), [y, NaN]) ## Test class of input preserved %!assert_equal (betacdf (single ([x, NaN]), 1, 2), single ([y, NaN])) %!assert_equal (betacdf ([x, NaN], single (1), 2), single ([y, NaN])) %!assert_equal (betacdf ([x, NaN], 1, single (2)), single ([y, NaN])) ## Test input validation %!error betacdf () %!error betacdf (1) %!error betacdf (1, 2) %!error betacdf (1, 2, 3, 4, 5) %!error betacdf (1, 2, 3, 'tail') %!error betacdf (1, 2, 3, 4) %!error ... %! betacdf (ones (3), ones (2), ones (2)) %!error ... %! betacdf (ones (2), ones (3), ones (2)) %!error ... %! betacdf (ones (2), ones (2), ones (3)) %!error betacdf (int32 (2), 2, 2) %!error betacdf (true, 2, 2) %!error betacdf ('a', 2, 2) %!error betacdf (i, 2, 2) %!error betacdf (2, i, 2) %!error betacdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/betainv.m000066400000000000000000000146531524624707500246520ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} betainv (@var{p}, @var{a}, @var{b}) ## ## Inverse of the Beta distribution (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) ## of the Beta distribution with shape parameters @var{a} and @var{b}. The size ## of @var{x} is the common size of @var{x}, @var{a}, and @var{b}. A scalar ## input functions as a constant matrix of the same size as the other inputs. ## ## Further information about the Beta distribution can be found at ## @url{https://en.wikipedia.org/wiki/Beta_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{betacdf, betapdf, betarnd, betafit, betalike, betastat} ## @end deftypefn function x = betainv (p, a, b) ## Check for valid number of input arguments if (nargin < 3) error ("betainv: function called with too few input arguments."); endif ## Check for common size of P, A, and B if (! isscalar (p) || ! isscalar (a) || ! isscalar (b)) [retval, p, a, b] = common_size (p, a, b); if (retval > 0) error ("betainv: P, A, and B must be of common size or scalars."); endif endif ## Check for P, A, and B being double or single if (! (isfloat (p) && isfloat (a) && isfloat (b))) error ("betainv: P, A, and B must be double or single."); endif ## Check for P, A, and B being reals if (iscomplex (p) || iscomplex (a) || iscomplex (b)) error ("betainv: P, A, and B must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (a, 'single') || isa (b, 'single')) x = zeros (size (p), 'single'); else x = zeros (size (p)); endif k = (p < 0) | (p > 1) | ! (a > 0) | ! (b > 0) | isnan (p); x(k) = NaN; k = (p == 1) & (a > 0) & (b > 0); x(k) = 1; k = find ((p > 0) & (p < 1) & (a > 0) & (b > 0)); if (! isempty (k)) if (! isscalar (a) || ! isscalar (b)) a = a(k); b = b(k); y = a ./ (a + b); else y = a / (a + b) * ones (size (k)); endif p = p(k); if (isa (y, 'single')) myeps = eps ('single'); else myeps = eps; endif l = find (y < myeps); if (any (l)) y(l) = sqrt (myeps) * ones (length (l), 1); endif l = find (y > 1 - myeps); if (any (l)) y(l) = 1 - sqrt (myeps) * ones (length (l), 1); endif y_new = y; loopcnt = 0; do y_old = y_new; h = (betacdf (y_old, a, b) - p) ./ betapdf (y_old, a, b); y_new = y_old - h; ind = find (y_new <= myeps); if (any (ind)) y_new(ind) = y_old(ind) / 10; endif ind = find (y_new >= 1 - myeps); if (any (ind)) y_new(ind) = 1 - (1 - y_old(ind)) / 10; endif h = y_old - y_new; until (max (abs (h)) < sqrt (myeps) || ++loopcnt == 40) if (loopcnt == 40) warning ("betainv: calculation failed to converge for some values."); endif x(k) = y_new; endif endfunction %!demo %! ## Plot various iCDFs from the Beta distribution %! p = 0.001:0.001:0.999; %! x1 = betainv (p, 0.5, 0.5); %! x2 = betainv (p, 5, 1); %! x3 = betainv (p, 1, 3); %! x4 = betainv (p, 2, 2); %! x5 = betainv (p, 2, 5); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c', p, x5, '-m') %! grid on %! legend ({'α = β = 0.5', 'α = 5, β = 1', 'α = 1, β = 3', ... %! 'α = 2, β = 2', 'α = 2, β = 5'}, 'location', 'southeast') %! title ('Beta iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p %! p = [-1 0 0.75 1 2]; %!assert_equal (betainv (p, ones (1,5), 2*ones (1,5)), [NaN 0 0.5 1 NaN], eps) %!assert_equal (betainv (p, 1, 2*ones (1,5)), [NaN 0 0.5 1 NaN], eps) %!assert_equal (betainv (p, ones (1,5), 2), [NaN 0 0.5 1 NaN], eps) %!assert_equal (betainv (p, [1 0 NaN 1 1], 2), [NaN NaN NaN 1 NaN]) %!assert_equal (betainv (p, 1, 2*[1 0 NaN 1 1]), [NaN NaN NaN 1 NaN]) %!assert_equal (betainv ([p(1:2) NaN p(4:5)], 1, 2), [NaN 0 NaN 1 NaN]) ## Test class of input preserved %!assert_equal (betainv ([p, NaN], 1, 2), [NaN 0 0.5 1 NaN NaN], eps) %!assert_equal (betainv (single ([p, NaN]), 1, 2), single ([NaN 0 0.5 1 NaN NaN])) %!assert_equal (betainv ([p, NaN], single (1), 2), single ([NaN 0 0.5 1 NaN NaN]), eps ('single')) %!assert_equal (betainv ([p, NaN], 1, single (2)), single ([NaN 0 0.5 1 NaN NaN]), eps ('single')) ## Test input validation %!error betainv () %!error betainv (1) %!error betainv (1,2) %!error betainv (1,2,3,4) %!error ... %! betainv (ones (3), ones (2), ones (2)) %!error ... %! betainv (ones (2), ones (3), ones (2)) %!error ... %! betainv (ones (2), ones (2), ones (3)) %!error betainv (int32 (2), 2, 2) %!error betainv (true, 2, 2) %!error betainv ('a', 2, 2) %!error betainv (i, 2, 2) %!error betainv (2, i, 2) %!error betainv (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/betapdf.m000066400000000000000000000147601524624707500246260ustar00rootroot00000000000000## Copyright (C) 2010 Christos Dimitrakakis ## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} betapdf (@var{x}, @var{a}, @var{b}) ## ## Beta probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the Beta distribution with shape parameters @var{a} and @var{b}. The size ## of @var{y} is the common size of @var{x}, @var{a}, and @var{b}. A scalar ## input functions as a constant matrix of the same size as the other inputs. ## ## Further information about the Beta distribution can be found at ## @url{https://en.wikipedia.org/wiki/Beta_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{betacdf, betainv, betarnd, betafit, betalike, betastat} ## @end deftypefn function y = betapdf (x, a, b) ## Check for valid number of input arguments if (nargin < 3) error ("betapdf: function called with too few input arguments."); endif ## Check for common size of X, A, and B if (! isscalar (x) || ! isscalar (a) || ! isscalar (b)) [retval, x, a, b] = common_size (x, a, b); if (retval > 0) error ("betapdf: X, A, and B must be of common size or scalars."); endif endif ## Check for X, A, and B being double or single if (! (isfloat (x) && isfloat (a) && isfloat (b))) error ("betapdf: X, A, and B must be double or single."); endif ## Check for X, A, and B being reals if (iscomplex (x) || iscomplex (a) || iscomplex (b)) error ("betapdf: X, A, and B must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (a, 'single') || isa (b, 'single')); y = zeros (size (x), 'single'); else y = zeros (size (x)); endif k = ! (a > 0) | ! (b > 0) | isnan (x); y(k) = NaN; k = (x > 0) & (x < 1) & (a > 0) & (b > 0) & ((a != 1) | (b != 1)); if (isscalar (a) && isscalar (b)) y(k) = exp ((a - 1) * log (x(k)) + (b - 1) * log (1 - x(k)) + gammaln (a + b) - gammaln (a) - gammaln (b)); else y(k) = exp ((a(k) - 1) .* log (x(k)) + (b(k) - 1) .* log (1 - x(k)) + gammaln (a(k) + b(k)) - gammaln (a(k)) - gammaln (b(k))); endif ## Most important special cases when the density is finite. k = (x == 0) & (a == 1) & (b > 0) & (b != 1); if (isscalar (a) && isscalar (b)) y(k) = exp (gammaln (a + b) - gammaln (a) - gammaln (b)); else y(k) = exp (gammaln (a(k) + b(k)) - gammaln (a(k)) - gammaln (b(k))); endif k = (x == 1) & (b == 1) & (a > 0) & (a != 1); if (isscalar (a) && isscalar (b)) y(k) = exp (gammaln (a + b) - gammaln (a) - gammaln (b)); else y(k) = exp (gammaln (a(k) + b(k)) - gammaln (a(k)) - gammaln (b(k))); endif k = (x >= 0) & (x <= 1) & (a == 1) & (b == 1); y(k) = 1; ## Other special case when the density at the boundary is infinite. k = (x == 0) & (a < 1); y(k) = Inf; k = (x == 1) & (b < 1); y(k) = Inf; endfunction %!demo %! ## Plot various PDFs from the Beta distribution %! x = 0.001:0.001:0.999; %! y1 = betapdf (x, 0.5, 0.5); %! y2 = betapdf (x, 5, 1); %! y3 = betapdf (x, 1, 3); %! y4 = betapdf (x, 2, 2); %! y5 = betapdf (x, 2, 5); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c', x, y5, '-m') %! grid on %! ylim ([0, 2.5]) %! legend ({'α = β = 0.5', 'α = 5, β = 1', 'α = 1, β = 3', ... %! 'α = 2, β = 2', 'α = 2, β = 5'}, 'location', 'north') %! title ('Beta PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1 0 0.5 1 2]; %! y = [0 2 1 0 0]; %!assert_equal (betapdf (x, ones (1, 5), 2 * ones (1, 5)), y) %!assert_equal (betapdf (x, 1, 2 * ones (1, 5)), y) %!assert_equal (betapdf (x, ones (1, 5), 2), y) %!assert_equal (betapdf (x, [0 NaN 1 1 1], 2), [NaN NaN y(3:5)]) %!assert_equal (betapdf (x, 1, 2 * [0 NaN 1 1 1]), [NaN NaN y(3:5)]) %!assert_equal (betapdf ([x, NaN], 1, 2), [y, NaN]) ## Test class of input preserved %!assert_equal (betapdf (single ([x, NaN]), 1, 2), single ([y, NaN])) %!assert_equal (betapdf ([x, NaN], single (1), 2), single ([y, NaN])) %!assert_equal (betapdf ([x, NaN], 1, single (2)), single ([y, NaN])) ## Beta (1/2,1/2) == arcsine distribution %!test %! x = rand (10,1); %! y = 1 ./ (pi * sqrt (x .* (1 - x))); %! assert_equal (betapdf (x, 1/2, 1/2), y, 1e-12); ## Test large input values to betapdf %!assert_equal (betapdf (0.5, 1000, 1000), 35.678, 1e-3) ## Test input validation %!error betapdf () %!error betapdf (1) %!error betapdf (1,2) %!error betapdf (1,2,3,4) %!error ... %! betapdf (ones (3), ones (2), ones (2)) %!error ... %! betapdf (ones (2), ones (3), ones (2)) %!error ... %! betapdf (ones (2), ones (2), ones (3)) %!error betapdf (int32 (2), 2, 2) %!error betapdf (true, 2, 2) %!error betapdf ('a', 2, 2) %!error betapdf (i, 2, 2) %!error betapdf (2, i, 2) %!error betapdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/betarnd.m000066400000000000000000000163711524624707500246400ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} betarnd (@var{a}, @var{b}) ## @deftypefnx {statistics} {@var{r} =} betarnd (@var{a}, @var{b}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} betarnd (@var{a}, @var{b}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} betarnd (@var{a}, @var{b}, [@var{sz}]) ## ## Random arrays from the Beta distribution. ## ## @code{@var{r} = betarnd (@var{a}, @var{b})} returns an array of random ## numbers chosen from the Beta distribution with shape parameters @var{a} and ## @var{b}. The size of @var{r} is the common size of @var{a} and @var{b}. ## A scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## When called with a single size argument, @code{betarnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the Beta distribution can be found at ## @url{https://en.wikipedia.org/wiki/Beta_distribution} ## ## @seealso{betacdf, betainv, betapdf, betafit, betalike, betastat} ## @end deftypefn function r = betarnd (a, b, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("betarnd: function called with too few input arguments."); endif ## Check for common size of A and B if (! isscalar (a) || ! isscalar (b)) [retval, a, b] = common_size (a, b); if (retval > 0) error ("betarnd: A and B must be of common size or scalars."); endif endif ## Check for A and B being reals if (iscomplex (a) || iscomplex (b)) error ("betarnd: A and B must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (a); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("betarnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("betarnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (a) && ! isequal (size (a), size (ones (sz)))) error ("betarnd: A and B must be scalars or of size SZ."); endif ## Check for class type if (isa (a, 'single') || isa (b, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from Beta distribution if (isscalar (a) && isscalar (b)) if ((a > 0) && (a < Inf) && (b > 0) && (b < Inf)) tmpr = randg (a, sz, cls); r = tmpr ./ (tmpr + randg (b, sz, cls)); else r = NaN (sz, cls); endif else r = NaN (sz, cls); k = (a > 0) & (a < Inf) & (b > 0) & (b < Inf); tmpr = randg (a(k), cls); r(k) = tmpr ./ (tmpr + randg (b(k), cls)); endif endfunction ## Test output %!assert_equal (size (betarnd (2, 1/2)), [1 1]) %!assert_equal (size (betarnd (2 * ones (2, 1), 1/2)), [2, 1]) %!assert_equal (size (betarnd (2 * ones (2, 2), 1/2)), [2, 2]) %!assert_equal (size (betarnd (2, 1/2 * ones (2, 1))), [2, 1]) %!assert_equal (size (betarnd (1, 1/2 * ones (2, 2))), [2, 2]) %!assert_equal (size (betarnd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (betarnd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (betarnd (2, 1/2, 3)), [3, 3]) %!assert_equal (size (betarnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (betarnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (betarnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (betarnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (betarnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (betarnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (betarnd (1, 1, [])), [0, 0]) %!assert_equal (size (betarnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (betarnd (1, 1/2, -1)), [0, 0]) %!assert_equal (size (betarnd (1, 1/2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (betarnd (1, 1/2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (betarnd (1, 1)), "double") %!assert_equal (class (betarnd (1, single (0))), "single") %!assert_equal (class (betarnd (1, single ([0, 0]))), "single") %!assert_equal (class (betarnd (1, single (1), 2)), "single") %!assert_equal (class (betarnd (1, single ([1, 1]), 1, 2)), "single") %!assert_equal (class (betarnd (single (1), 1, 2)), "single") %!assert_equal (class (betarnd (single ([1, 1]), 1, 1, 2)), "single") ## Test input validation %!error betarnd () %!error betarnd (1) %!error ... %! betarnd (ones (3), ones (2)) %!error ... %! betarnd (ones (2), ones (3)) %!error betarnd (i, 2) %!error betarnd (1, i) %!error ... %! betarnd (1, 1/2, 1.2) %!error ... %! betarnd (1, 1/2, ones (2)) %!error ... %! betarnd (1, 1/2, [2 0 2.5]) %!error ... %! betarnd (1, 1/2, 2, 1.5, 5) %!error ... %! betarnd (2, 1/2 * ones (2), 3) %!error ... %! betarnd (2, 1/2 * ones (2), [3, 2]) %!error ... %! betarnd (2, 1/2 * ones (2), 3, 2) %!error ... %! betarnd (2 * ones (2), 1/2, 3) %!error ... %! betarnd (2 * ones (2), 1/2, [3, 2]) %!error ... %! betarnd (2 * ones (2), 1/2, 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/binocdf.m000066400000000000000000000165511524624707500246250ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} binocdf (@var{x}, @var{n}, @var{ps}) ## @deftypefnx {statistics} {@var{p} =} binocdf (@var{x}, @var{n}, @var{ps}, @qcode{'upper'}) ## ## Binomial cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the binomial distribution with parameters @var{n} and @var{ps}, ## where @var{n} is the number of trials and @var{ps} is the probability of ## success. The size of @var{p} is the common size of @var{x}, @var{n}, and ## @var{ps}. A scalar input functions as a constant matrix of the same size as ## the other inputs. ## ## @code{@var{p} = binocdf (@var{x}, @var{n}, @var{ps}, "upper")} computes the ## upper tail probability of the binomial distribution with parameters ## @var{n} and @var{ps}, at the values in @var{x}. ## ## Further information about the binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Binomial_distribution} ## ## Input arguments must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted to ## @qcode{double}, so the result is always a probability. MATLAB is ## inconsistent here: for several of the discrete distributions it returns the ## result in the integer class of the input, truncating a probability to ## @math{0} or @math{1}. ## ## @seealso{binoinv, binopdf, binornd, binofit, binolike, binostat, binotest} ## @end deftypefn function p = binocdf (x, n, ps, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("binocdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin == 4) if (strcmp (uflag, 'upper')) uflag = true; else error ("binocdf: invalid argument for upper tail."); endif else uflag = false; endif ## Check for common size of X, N, and PS if (! isscalar (x) || ! isscalar (n) || ! isscalar (ps)) [retval, x, n, ps] = common_size (x, n, ps); if (retval > 0) error ("binocdf: X, N, and PS must be of common size or scalars."); endif endif ## Check for X, N, and PS being double, single, or integer if (! (isnumeric (x) && isnumeric (n) && isnumeric (ps))) error ("binocdf: X, N, and PS must be double, single, or integer."); endif ## Integer input is promoted to double, so the result is a probability ## rather than a value truncated to the input's integer type. if (isinteger (x)) x = double (x); endif if (isinteger (n)) n = double (n); endif if (isinteger (ps)) ps = double (ps); endif ## Check for X, N, and PS being reals if (iscomplex (x) || iscomplex (n) || iscomplex (ps)) error ("binocdf: X, N, and PS must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (n, 'single') || isa (ps, 'single')); p = nan (size (x), 'single'); else p = nan (size (x)); endif k = (x >= n) & (n >= 0) & (n == fix (n) & (ps >= 0) & (ps <= 1)); p(k) = ! uflag; k = (x < 0) & (n >= 0) & (n == fix (n) & (ps >= 0) & (ps <= 1)); p(k) = uflag; k = (x >= 0) & (x < n) & (n == fix (n)) & (ps >= 0) & (ps <= 1); tmp = floor (x(k)); if (! uflag) if (isscalar (n) && isscalar (ps)) p(k) = betainc (1 - ps, n - tmp, tmp + 1); else p(k) = betainc (1 - ps(k), n(k) - tmp, tmp + 1); endif else if (isscalar (n) && isscalar (ps)); p(k) = betainc (ps, tmp + 1, n - tmp); else p(k) = betainc (ps(k), tmp + 1, n(k) - tmp); endif endif endfunction %!demo %! ## Plot various CDFs from the binomial distribution %! x = 0:40; %! p1 = binocdf (x, 20, 0.5); %! p2 = binocdf (x, 20, 0.7); %! p3 = binocdf (x, 40, 0.5); %! plot (x, p1, '*b', x, p2, '*g', x, p3, '*r') %! grid on %! legend ({'n = 20, ps = 0.5', 'n = 20, ps = 0.7', ... %! 'n = 40, ps = 0.5'}, 'location', 'southeast') %! title ('Binomial CDF') %! xlabel ('values in x (number of successes)') %! ylabel ('probability') ## Test output %!shared x, p, p1 %! x = [-1 0 1 2 3]; %! p = [0 1/4 3/4 1 1]; %! p1 = 1 - p; %!assert_equal (binocdf (x, 2 * ones (1, 5), 0.5 * ones (1, 5)), p, eps) %!assert_equal (binocdf (x, 2, 0.5 * ones (1, 5)), p, eps) %!assert_equal (binocdf (x, 2 * ones (1, 5), 0.5), p, eps) %!assert_equal (binocdf (x, 2 * [0 -1 NaN 1.1 1], 0.5), [0 NaN NaN NaN 1]) %!assert_equal (binocdf (x, 2, 0.5 * [0 -1 NaN 3 1]), [0 NaN NaN NaN 1]) %!assert_equal (binocdf ([x(1:2) NaN x(4:5)], 2, 0.5), [p(1:2) NaN p(4:5)], eps) %!assert_equal (binocdf (99, 100, 0.1, 'upper'), 1e-100, 1e-112); %!assert_equal (binocdf (x, 2 * ones (1, 5), 0.5*ones (1,5), 'upper'), p1, eps) %!assert_equal (binocdf (x, 2, 0.5 * ones (1, 5), 'upper'), p1, eps) %!assert_equal (binocdf (x, 2 * ones (1, 5), 0.5, 'upper'), p1, eps) %!assert_equal (binocdf (x, 2 * [0 -1 NaN 1.1 1], 0.5, 'upper'), [1 NaN NaN NaN 0]) %!assert_equal (binocdf (x, 2, 0.5 * [0 -1 NaN 3 1], 'upper'), [1 NaN NaN NaN 0]) %!assert_equal (binocdf ([x(1:2) NaN x(4:5)], 2, 0.5, 'upper'), [p1(1:2) NaN p1(4:5)]) %!assert_equal (binocdf ([x, NaN], 2, 0.5), [p, NaN], eps) ## Test class of input preserved %!assert_equal (binocdf (single ([x, NaN]), 2, 0.5), single ([p, NaN])) %!assert_equal (binocdf ([x, NaN], single (2), 0.5), single ([p, NaN])) %!assert_equal (binocdf ([x, NaN], 2, single (0.5)), single ([p, NaN])) ## Test input validation %!error binocdf () %!error binocdf (1) %!error binocdf (1, 2) %!error binocdf (1, 2, 3, 4, 5) %!error binocdf (1, 2, 3, 'tail') %!error binocdf (1, 2, 3, 4) %!error ... %! binocdf (ones (3), ones (2), ones (2)) %!error ... %! binocdf (ones (2), ones (3), ones (2)) %!error ... %! binocdf (ones (2), ones (2), ones (3)) %!error binocdf (true, 2, 2) %!error binocdf ('a', 2, 2) %!assert_equal (class (binocdf (int32 (2), 2, 2)), 'double') %!error binocdf (i, 2, 2) %!error binocdf (2, i, 2) %!error binocdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/binoinv.m000066400000000000000000000263131524624707500246620ustar00rootroot00000000000000## Copyright (C) 2016-2017 Lachlan Andrew ## Copyright (C) 2012-2016 Rik Wehbring ## Copyright (C) 1995-2012 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} binoinv (@var{p}, @var{n}, @var{ps}) ## ## Inverse of the Binomial cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the binomial distribution with parameters @var{n} and @var{ps}, where @var{n} ## is the number of trials and @var{ps} is the probability of success. The size ## of @var{x} is the common size of @var{p}, @var{n}, and @var{ps}. A scalar ## input functions as a constant matrix of the same size as the other inputs. ## ## Further information about the binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Binomial_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{binocdf, binopdf, binornd, binofit, binolike, binostat, binotest} ## @end deftypefn function x = binoinv (p, n, ps) ## Check for valid number of input arguments if (nargin < 3) error ("binoinv: function called with too few input arguments."); endif ## Check for common size of P, N, and PS if (! isscalar (n) || ! isscalar (ps)) [retval, p, n, ps] = common_size (p, n, ps); if (retval > 0) error ("binoinv: P, N, and PS must be of common size or scalars."); endif endif ## Check for P, N, and PS being double or single if (! (isfloat (p) && isfloat (n) && isfloat (ps))) error ("binoinv: P, N, and PS must be double or single."); endif ## Check for P, N, and PS being reals if (iscomplex (p) || iscomplex (n) || iscomplex (ps)) error ("binoinv: P, N, and PS must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (n, 'single') || isa (ps, 'single')); x = zeros (size (p), 'single'); else x = zeros (size (p)); endif k = (! (p >= 0) | ! (p <= 1) | ! (n >= 0) | (n != fix (n)) | ! (ps >= 0) | ... ! (ps <= 1)); x(k) = NaN; k = find ((p >= 0) & (p <= 1) & (n >= 0) & (n == fix (n) ... & (ps >= 0) & (ps <= 1))); if (! isempty (k)) pk = p(k)(:); if (isscalar (n) && isscalar (ps)) [xk, unfinished] = scalar_binoinv (pk, n, ps); if (! isempty (unfinished)) xk(unfinished) = bin_search_binoinv (pk(unfinished), n, ps); endif xk = tie_correct (xk, pk, n, ps); ## P of 1 attains N. The CDF saturates to 1 well below it, reading 1 at ## 206 for 500 trials at 0.25, so no search can locate it. xk(pk == 1) = n; else nk = n(k)(:); psk = ps(k)(:); [xk, unfinished] = vector_binoinv (pk, nk, psk); if (! isempty (unfinished)) xk(unfinished) = bin_search_binoinv (pk(unfinished), nk(unfinished), ... psk(unfinished)); endif xk = tie_correct (xk, pk, nk, psk); xk(pk == 1) = nk(pk == 1); endif x(k) = xk; endif endfunction ## Core algorithm to calculate the inverse binomial, for n and ps real scalars ## and x a column vector, and for which the output is not NaN or Inf. ## Compute CDF in batches of doubling size until CDF > p, or answer > 500 ## Return the locations of unfinished cases in k. function [m, k] = scalar_binoinv (p, n, ps) k = 1:length (p); m = zeros (size (p)); prev_limit = 0; limit = 10; cdf = 0; v = 0; do cdf = binocdf (prev_limit:limit-1, n, ps); r = bsxfun (@le, p(k), cdf); [v, m(k)] = max (r, [], 2); # find first instance of p <= cdf m(k) += prev_limit - 1; k = k(v == 0); prev_limit = limit; limit += limit; until (isempty (k) || limit >= 1000) endfunction ## Core algorithm to calculate the inverse binomial, for n, ps, and x column ## vectors, and for which the output is not NaN or Inf. ## Compute CDF in batches of doubling size until CDF > p, or answer > 500 ## Return the locations of unfinished cases in k. ## Calculates CDF by summing PDF, which is faster than calls to binocdf. function [m, k] = vector_binoinv (p, n, ps) k = 1:length (p); m = zeros (size (p)); prev_limit = 0; limit = 10; cdf = 0; v = 0; do xx = repmat (prev_limit:limit-1, [length(k), 1]); nn = kron (ones (1, limit-prev_limit), n(k)); pp = kron (ones (1, limit-prev_limit), ps(k)); pdf = binopdf (xx, nn, pp); pdf(:,1) += cdf(v==0, end); cdf = cumsum (pdf, 2); r = bsxfun (@le, p(k), cdf); [v, m(k)] = max (r, [], 2); # find first instance of p <= cdf m(k) += prev_limit - 1; k = k(v == 0); prev_limit = limit; limit += min (limit, max (1e4/numel (k), 10)); # limit memory use until (isempty (k) || limit >= 1000) endfunction ## Vectorized binary search. ## Can handle vectors n and ps, and is faster than the scalar case when the ## answer is large. ## Could be optimized to call binocdf only for a subset of the p at each stage, ## but care must be taken to handle both scalar and vector n, ps. Bookkeeping ## may cost more than the extra computations. function m = bin_search_binoinv (p, n, ps) ## binocdf returns a column, so a row P would broadcast into a full matrix. p = p(:); k = 1:length (p); lower = zeros (size (p)); limit = 500; # lower bound on point at which prev phase finished while (any (k) && limit < 1e100) cdf = binocdf (limit, n, ps); k = (p > cdf); lower(k) = limit; limit += limit; endwhile upper = max (2*lower, 1); k = find (lower != limit/2); # elements for which above loop finished for i = 1:ceil (log2 (max (lower))) mid = (upper + lower)/2; cdf = binocdf (floor (mid(:)), n, ps); r = (p <= cdf); upper(r) = mid(r); lower(! r) = mid(! r); endfor m = ceil (lower); m(p > binocdf (m(:), n, ps)) += 1; # fix off-by-one errors from binary search endfunction ## Step the answer back onto M-1 wherever P lies within the error of the CDF ## there. The CDF carries a relative error that grows with N, so an exactly ## attained probability reads short and sends the search past its answer: ## binocdf (2, 5, 0.5) is 0.49999999999999989 where the exact value, 16/32, ## is representable, and the shortfall reaches 2355 eps by 2001 trials. The ## slack is capped at half the probability of M so that it can never cross a ## real step of the distribution and round P to the wrong side of one. function m = tie_correct (m, p, n, ps) j = find (m > 0); if (isempty (j)) return; endif mj = m(j); if (isscalar (n)) nj = n; else nj = n(j); endif if (isscalar (ps)) psj = ps; else psj = ps(j); endif lo = binocdf (mj - 1, nj, psj); tol = min (32 * nj .* eps (lo), 0.5 * binopdf (mj, nj, psj)); m(j(p(j) <= lo + tol)) -= 1; endfunction %!demo %! ## Plot various iCDFs from the binomial distribution %! p = 0.001:0.001:0.999; %! x1 = binoinv (p, 20, 0.5); %! x2 = binoinv (p, 20, 0.7); %! x3 = binoinv (p, 40, 0.5); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r') %! grid on %! legend ({'n = 20, ps = 0.5', 'n = 20, ps = 0.7', ... %! 'n = 40, ps = 0.5'}, 'location', 'southeast') %! title ('Binomial iCDF') %! xlabel ('probability') %! ylabel ('values in x (number of successes)') ## Test output %!shared p %! p = [-1 0 0.5 1 2]; %!assert_equal (binoinv (p, 2*ones (1,5), 0.5*ones (1,5)), [NaN 0 1 2 NaN]) %!assert_equal (binoinv (p, 2, 0.5*ones (1,5)), [NaN 0 1 2 NaN]) %!assert_equal (binoinv (p, 2*ones (1,5), 0.5), [NaN 0 1 2 NaN]) %!assert_equal (binoinv (p, 2*[0 -1 NaN 1.1 1], 0.5), [NaN NaN NaN NaN NaN]) %!assert_equal (binoinv (p, 2, 0.5*[0 -1 NaN 3 1]), [NaN NaN NaN NaN NaN]) %!assert_equal (binoinv ([p(1:2) NaN p(4:5)], 2, 0.5), [NaN 0 NaN 2 NaN]) ## Test class of input preserved %!assert_equal (binoinv ([p, NaN], 2, 0.5), [NaN 0 1 2 NaN NaN]) %!assert_equal (binoinv (single ([p, NaN]), 2, 0.5), single ([NaN 0 1 2 NaN NaN])) %!assert_equal (binoinv ([p, NaN], single (2), 0.5), single ([NaN 0 1 2 NaN NaN])) %!assert_equal (binoinv ([p, NaN], 2, single (0.5)), single ([NaN 0 1 2 NaN NaN])) ## Test accuracy against a round trip through the CDF %!shared x %! x = magic (3) + 1; %!assert_equal (binoinv (binocdf (1:10, 11, 0.1), 11, 0.1), 1:10) %!assert_equal (binoinv (binocdf (1:10, 2*(1:10), 0.1), 2*(1:10), 0.1), 1:10) %!assert_equal (binoinv (binocdf (x, 2*x, 1./x), 2*x, 1./x), x) ## A symmetric binomial with an odd number of trials attains 0.5 exactly at ## its median, which the CDF reads short of by an error growing with N. %!assert_equal (binoinv (0.5, 5, 0.5), 2) %!assert_equal (binoinv (0.5, 7, 0.5), 3) %!assert_equal (binoinv (0.5, 501, 0.5), 250) %!assert_equal (binoinv (0.5, 2001, 0.5), 1000) %!assert_equal (binoinv (0.5, 100001, 0.5), 50000) ## P of 1 attains N even where the CDF saturates to 1 far below it. %!assert_equal (binoinv (1, 500, 0.25), 500) %!assert_equal (binoinv (1, [50, 500], [0.5, 0.1]), [50, 500]) ## Answers above 500 take the binary search, where the CDF is a column and P ## keeps the orientation it was given. %!assert_equal (binoinv ([0.5, 0.9], 2001, 0.5), [1000, 1029]) %!assert_equal (binoinv ([0.5; 0.9], 2001, 0.5), [1000; 1029]) %!assert_equal (binoinv ([NaN, 0.5], 2001, 0.5), [NaN, 1000]) %!assert_equal (binoinv ([2, 0.5], [2001, 2001], [0.5, 0.5]), [NaN, 1000]) ## Test input validation %!error binoinv () %!error binoinv (1) %!error binoinv (1,2) %!error binoinv (1,2,3,4) %!error ... %! binoinv (ones (3), ones (2), ones (2)) %!error ... %! binoinv (ones (2), ones (3), ones (2)) %!error ... %! binoinv (ones (2), ones (2), ones (3)) %!error binoinv (int32 (2), 2, 2) %!error binoinv (true, 2, 2) %!error binoinv ('a', 2, 2) %!error binoinv (i, 2, 2) %!error binoinv (2, i, 2) %!error binoinv (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/binopdf.m000066400000000000000000000316511524624707500246400ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2021 Nicholas R. Jankowski ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} binopdf (@var{x}, @var{n}, @var{ps}) ## ## Binomial probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the binomial distribution with parameters @var{n} and @var{ps}, where ## @var{n} is the number of trials and @var{ps} is the probability of success. ## The size of @var{y} is the common size of @var{x}, @var{n}, and @var{ps}. A ## scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## Matlab incompatibility: Octave's @code{binopdf} does not allow complex ## input values. Matlab 2021b returns values for complex inputs despite the ## documentation indicates integer and real value inputs are required. ## ## Further information about the binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Binomial_distribution} ## ## Input arguments must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted to ## @qcode{double}, so the result is always a probability. MATLAB is ## inconsistent here: for several of the discrete distributions it returns the ## result in the integer class of the input, truncating a probability to ## @math{0} or @math{1}. ## ## @seealso{binocdf, binoinv, binornd, binofit, binolike, binostat, binotest} ## @end deftypefn function y = binopdf (x, n, ps) ## Check for valid number of input arguments if (nargin < 3) error ("binopdf: function called with too few input arguments."); endif ## Check for common size of X, N, and PS if (! isscalar (x) || ! isscalar (n) || ! isscalar (ps)) [retval, x, n, ps] = common_size (x, n, ps); if (retval > 0) error ("binopdf: X, N, and PS must be of common size or scalars."); endif endif ## Check for X, N, and PS being double, single, or integer if (! (isnumeric (x) && isnumeric (n) && isnumeric (ps))) error ("binopdf: X, N, and PS must be double, single, or integer."); endif ## Integer input is promoted to double, so the result is a probability ## rather than a value truncated to the input's integer type. if (isinteger (x)) x = double (x); endif if (isinteger (n)) n = double (n); endif if (isinteger (ps)) ps = double (ps); endif ## Check for X, N, and PS being reals if (iscomplex (x) || iscomplex (n) || iscomplex (ps)) error ("binopdf: X, N, and PS must not be complex."); endif sz_x = size (x); # save original size for reshape later x = x(:); n = n(:); ps = ps(:); # columns for easier vectorization ## Initialize output, preserve class of output if any are singles if (isa (x, 'single') || isa (n, 'single') || isa (ps, 'single')); y = zeros (numel (x), 1, 'single'); else y = zeros (numel (x), 1); endif ## k - index of array locations needing calculation k = (x == fix (x)) & (n == fix (n)) & (n >= 0) & (ps >= 0) & (ps <= 1) ... & (x >= 0) & (x <= n); nx = n - x; q = 1 - ps; ## Catch special cases ahead of calculations: ## Matlab incompatibility: Matlab 2021b returns values for complex inputs ## despite documentation indicating integer and real value inputs required. ## Octave chooses to return an NaN instead. catch_special = (iscomplex (x) | iscomplex (n) | iscomplex (ps)); k(catch_special) = false; y(catch_special) = NaN; ## x = 0 and x = n cases where ps != 0 or 1, respectively ## remove them from k, use alternate calculation to avoid /0 catch_special = (x == 0)& (! catch_special); k(catch_special) = false; y(catch_special) = exp (n(catch_special) .* log (q(catch_special))); catch_special = (nx == 0) & (! catch_special); k(catch_special) = false; y(catch_special) = exp (n(catch_special) .* log (ps(catch_special))); ## Perform Loader pdf calculation on non-trivial elements if (any (k)) y(k) = loader_expansion (x(k), n(k), ps(k), nx(k), q(k)); endif ## Trivial case special outputs: ksp = ((ps == 0) & (x == 0)) | (ps == 1) & (x == n); y(ksp) = 1; ## Input NaN, n not pos int, or ps outside [0,1], ## set output to NaN (overrides 0 or 1) ksp = (n != fix (n)) | (n < 0) | (ps < 0) | (ps > 1) | isnan (x) ... | isnan (n) | isnan (ps); y(ksp) = NaN; y = reshape (y, sz_x); ## restore output to input shape endfunction function y = loader_expansion (x, n, ps, nx, q) ## Precalculated constants, d_n from n = 0 to 30 ## extended from Loader using octave symbolic vpa ## out to n = 30 d_n = [ 0.08106146679532725821967026359438236013860, 0.04134069595540929409382208140711750802535, 0.02767792568499833914878929274624466659538, 0.02079067210376509311152277176784865633309, 0.01664469118982119216319486537359339114739, 0.01387612882307074799874572702376290856175, 0.01189670994589177009505572411765943862013, 0.01041126526197209649747856713253462919952, 0.00925546218271273291772863663310013611743, 0.00833056343336287125646931865962855220929, 0.00757367548795184079497202421159508389293, 0.00694284010720952986566415266347536265992, 0.00640899418800420706843963108297831257520, 0.00595137011275884773562441604646945832642, 0.00555473355196280137103868995979228464907, 0.00520765591960964044071799685790189865099, 0.00490139594843473786071681819096755442865, 0.00462915374933402859242721316419232323878, 0.00438556024923232426828773634861946570116, 0.00416631969199692245746292338221831613633, 0.00396795421864085961728763680734281467287, 0.00378761806844443457786667706893349200129, 0.00362296022468309470738119836390285473489, 0.00347202138297876696294511542270952959204, 0.00333315563672809287580701911737271025035, 0.00320497022805503801118415655381541759643, 0.00308627868260877706325624133564397946129, 0.00297606398355040882602116255686080370692, 0.00287344936235246638755235148906672207372, 0.00277767492975269360359490376220667282839 ]; stored_dn = numel (d_n); ## Indices for precalculated vs to-be-calculated values n_precalc = (n > 0) & (n < stored_dn); x_precalc = (x > 0) & (x < stored_dn); nx_precalc = (nx > 0) & (nx < stored_dn); [delta_n, delta_x, delta_nx] = deal (zeros (size (x))); ## Fetch precalculated values delta_n(n_precalc) = d_n(n(n_precalc)); delta_x(x_precalc) = d_n(x(x_precalc)); delta_nx(nx_precalc) = d_n(nx(nx_precalc)); ## Calculate any other d(n) values delta_n(! n_precalc) = delta_fn (n(! n_precalc)); delta_x(! x_precalc) = delta_fn (x(! x_precalc)); delta_nx(! nx_precalc) = delta_fn (nx(! nx_precalc)); ## Calculate exp(log(pdf)); y = exp ((delta_n - delta_x - delta_nx - ... deviance (x, n .* ps) - ... deviance (nx, n .* q)) - ... 0.5 * (log (2*pi) + log (x) + log (1-x./n))); endfunction function y = delta_fn (n) ## Stirling formula error term approximations based on Loader paper. ## exact expression, n^n overflows to Inf for n > ~145: ## = log (n!*exp(n)/(sqrt(2pi*n)*n^n)); ## ## Rewritten to avoid overflow out to n> 1e305. accurate to ~10^-12 ## = n + gammaln (n+1) - (n+0.5) * log(n) - log(2*pi)/2; ## ## Approximated as: ## accurate to ~10^-16 for n=30. underflow to 0 at n~10^309 ## = 1/(12n)-1/(360n^3)+1/(1260n^5)- 1/(1680n.^7)+1/(1188n^9) + O(n^-11); ## ## Factored to reduced operation count. Used by Loader and in R: ## 25% faster than unfactored form. ## =(1/12-(1/360-(1/1260-(1/1680-(1/1188)/n^2)/n^2)/n^2)/n^2)/n; nn = n.^2; y = (0.08333333333333333333333333333333333333333 - ... (0.00277777777777777777777777777777777777778 - ... (0.00079365079365079365079365079365079365079 - ... (0.00059523809523809523809523809523809523810 - ... (0.00084175084175084175084175084175084175084)./nn)./nn)./nn)./nn)./n; endfunction function D = deviance (x, np) ## requires equal length column inputs epsilon = x ./ np; v = (epsilon - 1) ./ (epsilon + 1); vtest = abs (v) < 0.1; if (any (vtest)) ## For abs(v) < 0.1, do taylor expansion for higher precision. Expansion ## term: v^(2j+1)/(2j+1). For abs(v)< 0.1, term drops slowest for max ## abs(v) = 0.1. (n+1)th term is <= 10. jmax = 12; two_jpone = 2 * [1:jmax] + 1; # sum term 2*j+1 (row vector expansion) D = zeros (numel (epsilon), 1); ## D = (x-np)*v + 2*x*sum_over_j(v^2j+1 / 2j+1) D(vtest) = (x(vtest) - np(vtest)) .* v(vtest) + 2 .* x(vtest) .* ... sum (v(vtest).^(two_jpone) ./ two_jpone, 2); D(! vtest) = x(! vtest) .* (log (epsilon(! vtest)) - 1) + np(! vtest); else D = x.* (log (epsilon) - 1) + np; endif endfunction %!demo %! ## Plot various PDFs from the binomial distribution %! x = 0:40; %! y1 = binopdf (x, 20, 0.5); %! y2 = binopdf (x, 20, 0.7); %! y3 = binopdf (x, 40, 0.5); %! plot (x, y1, '*b', x, y2, '*g', x, y3, '*r') %! grid on %! ylim ([0, 0.25]) %! legend ({'n = 20, ps = 0.5', 'n = 20, ps = 0.7', ... %! 'n = 40, ps = 0.5'}, 'location', 'northeast') %! title ('Binomial PDF') %! xlabel ('values in x (number of successes)') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1 0 1 2 3]; %! y = [0 1/4 1/2 1/4 0]; %!assert_equal (binopdf (x, 2 * ones (1, 5), 0.5 * ones (1, 5)), y, eps) %!assert_equal (binopdf (x, 2, 0.5 * ones (1, 5)), y, eps) %!assert_equal (binopdf (x, 2 * ones (1, 5), 0.5), y, eps) %!assert_equal (binopdf (x, 2 * [0 -1 NaN 1.1 1], 0.5), [0 NaN NaN NaN 0]) %!assert_equal (binopdf (x, 2, 0.5 * [0 -1 NaN 3 1]), [0 NaN NaN NaN 0]) %!assert_equal (binopdf ([x, NaN], 2, 0.5), [y, NaN], eps) %!assert_equal (binopdf (cat (3, x, x), 2, 0.5), cat (3, y, y), eps) ## Test Special input values %!assert_equal (binopdf (1, 1, 1), 1) %!assert_equal (binopdf (0, 3, 0), 1) %!assert_equal (binopdf (2, 2, 1), 1) %!assert_equal (binopdf (1, 2, 1), 0) %!assert_equal (binopdf (0, 1.1, 0), NaN) %!assert_equal (binopdf (1, 2, -1), NaN) %!assert_equal (binopdf (1, 2, 1.5), NaN) ## Test empty inputs %!assert_equal (binopdf ([], 1, 1), []) %!assert_equal (binopdf (1, [], 1), []) %!assert_equal (binopdf (1, 1, []), []) %!assert_equal (binopdf (ones (1, 0), 2, .5), ones (1, 0)) %!assert_equal (binopdf (ones (0, 1), 2, .5), ones (0, 1)) %!assert_equal (binopdf (ones (0, 1, 2), 2, .5), ones (0, 1, 2)) %!assert_equal (binopdf (1, ones (0, 1, 2), .5), ones (0, 1, 2)) %!assert_equal (binopdf (1, 2, ones (0, 1, 2)), ones (0, 1, 2)) %!assert_equal (binopdf (ones (1, 0, 2), 2, .5), ones (1, 0, 2)) %!assert_equal (binopdf (ones (1, 2, 0), 2, .5), ones (1, 2, 0)) %!assert_equal (binopdf (ones (0, 1, 2), NaN, .5), ones (0, 1, 2)) %!assert_equal (binopdf (ones (0, 1, 2), 2, NaN), ones (0, 1, 2)) ## Test class of input preserved %!assert_equal (binopdf (single ([x, NaN]), 2, 0.5), single ([y, NaN])) %!assert_equal (binopdf ([x, NaN], single (2), 0.5), single ([y, NaN])) %!assert_equal (binopdf ([x, NaN], 2, single (0.5)), single ([y, NaN])) ## Test input validation %!error binopdf () %!error binopdf (1) %!error binopdf (1, 2) %!error binopdf (1, 2, 3, 4) %!error ... %! binopdf (ones (3), ones (2), ones (2)) %!error ... %! binopdf (ones (2), ones (3), ones (2)) %!error ... %! binopdf (ones (2), ones (2), ones (3)) %!error binopdf (true, 2, 2) %!error binopdf ('a', 2, 2) %!assert_equal (class (binopdf (int32 (2), 2, 2)), 'double') %!error binopdf (i, 2, 2) %!error binopdf (2, i, 2) %!error binopdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/binornd.m000066400000000000000000000175601524624707500246550ustar00rootroot00000000000000## Copyright (C) 2015 Michael Leitner ## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} binornd (@var{n}, @var{ps}) ## @deftypefnx {statistics} {@var{r} =} binornd (@var{n}, @var{ps}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} binornd (@var{n}, @var{ps}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} binornd (@var{n}, @var{ps}, [@var{sz}]) ## ## Random arrays from the Binomial distribution. ## ## @code{@var{r} = binornd (@var{n}, @var{ps})} returns a matrix of random ## samples from the binomial distribution with parameters @var{n} and @var{ps}, ## where @var{n} is the number of trials and @var{ps} is the probability of ## success. The size of @var{r} is the common size of @var{n} and @var{ps}. ## A scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## When called with a single size argument, @code{binornd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Binomial_distribution} ## ## @seealso{binocdf, binoinv, binopdf, binofit, binolike, binostat, binotest} ## @end deftypefn function r = binornd (n, ps, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("binornd: function called with too few input arguments."); endif ## Check for common size of N and PS if (! isscalar (n) || ! isscalar (ps)) [retval, n, ps] = common_size (n, ps); if (retval > 0) error ("binornd: N and PS must be of common size or scalars."); endif endif ## Check for N and PS being reals if (iscomplex (n) || iscomplex (ps)) error ("binornd: N and PS must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (n); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("binornd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("binornd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (n) && ! isequal (size (n), size (ones (sz)))) error ("binornd: N and PS must be scalars or of size SZ."); endif ## Check for class type if (isa (n, 'single') || isa (ps, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from binomial distribution if (isscalar (n) && isscalar (ps)) if ((n > 0) && (n < Inf) && (n == fix (n)) && (ps >= 0) && (ps <= 1)) nel = prod (sz); tmp = rand (n, nel); r = sum (tmp < ps, 1); r = reshape (r, sz); if (strcmp (cls, 'single')) r = single (r); endif elseif ((n == 0) && (ps >= 0) && (ps <= 1)) r = zeros (sz, cls); else r = NaN (sz, cls); endif else r = zeros (sz, cls); k = ! (n >= 0) | ! (n < Inf) | ! (n == fix (n)) | ! (ps >= 0) | ! (ps <= 1); r(k) = NaN; k = (n > 0) & (n < Inf) & (n == fix (n)) & (ps >= 0) & (ps <= 1); if (any (k(:))) L = sum (k(:)); ind = repelems ((1 : L), [(1 : L); n(k)(:)'])'; p_ext = ps(k)(ind)(:); r(k) = accumarray (ind, rand (sum (n(k)(:)), 1) < p_ext); endif endif endfunction ## Test output %!assert_equal (size (binornd (2, 1/2)), [1 1]) %!assert_equal (size (binornd (2 * ones (2, 1), 1/2)), [2, 1]) %!assert_equal (size (binornd (2 * ones (2, 2), 1/2)), [2, 2]) %!assert_equal (size (binornd (2, 1/2 * ones (2, 1))), [2, 1]) %!assert_equal (size (binornd (1, 1/2 * ones (2, 2))), [2, 2]) %!assert_equal (size (binornd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (binornd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (binornd (2, 1/2, 3)), [3, 3]) %!assert_equal (size (binornd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (binornd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (binornd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (binornd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (binornd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (binornd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (binornd (1, 1, [])), [0, 0]) %!assert_equal (size (binornd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (binornd (1, 1/2, -1)), [0, 0]) %!assert_equal (size (binornd (1, 1/2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (binornd (1, 1/2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (binornd (1, 1)), "double") %!assert_equal (class (binornd (1, single (0))), "single") %!assert_equal (class (binornd (1, single ([0, 0]))), "single") %!assert_equal (class (binornd (1, single (1), 2)), "single") %!assert_equal (class (binornd (1, single ([1, 1]), 1, 2)), "single") %!assert_equal (class (binornd (single (1), 1, 2)), "single") %!assert_equal (class (binornd (single ([1, 1]), 1, 1, 2)), "single") ## Test input validation %!error binornd () %!error binornd (1) %!error ... %! binornd (ones (3), ones (2)) %!error ... %! binornd (ones (2), ones (3)) %!error binornd (i, 2) %!error binornd (1, i) %!error ... %! binornd (1, 1/2, 1.2) %!error ... %! binornd (1, 1/2, ones (2)) %!error ... %! binornd (1, 1/2, [2 0 2.5]) %!error ... %! binornd (1, 1/2, 2, 1.5, 5) %!error ... %! binornd (2, 1/2 * ones (2), 3) %!error ... %! binornd (2, 1/2 * ones (2), [3, 2]) %!error ... %! binornd (2, 1/2 * ones (2), 3, 2) %!error ... %! binornd (2 * ones (2), 1/2, 3) %!error ... %! binornd (2 * ones (2), 1/2, [3, 2]) %!error ... %! binornd (2 * ones (2), 1/2, 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/bisacdf.m000066400000000000000000000171141524624707500246100ustar00rootroot00000000000000## Copyright (C) 1995-2015 Kurt Hornik ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 2018 John Donoghue ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} bisacdf (@var{x}, @var{beta}, @var{gamma}) ## @deftypefnx {statistics} {@var{p} =} bisacdf (@var{x}, @var{beta}, @var{gamma}, @qcode{'upper'}) ## ## Birnbaum-Saunders cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the Birnbaum-Saunders distribution with scale parameter @var{beta} ## and shape parameter @var{gamma}. The size of @var{p} is the common size of ## @var{x}, @var{beta} and @var{gamma}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## @code{@var{p} = bisacdf (@var{x}, @var{beta}, @var{gamma}, "upper")} ## computes the upper tail probability of the Birnbaum-Saunders distribution ## with parameters @var{beta} and @var{gamma}, at the values in @var{x}. ## ## Further information about the Birnbaum-Saunders distribution can be found at ## @url{https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{bisainv, bisapdf, bisarnd, bisafit, bisalike, bisastat} ## @end deftypefn function p = bisacdf (x, beta, gamma, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("bisacdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 3) if (! strcmpi (uflag, 'upper')) error ("bisacdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, BETA, and GAMMA if (! isscalar (x) || ! isscalar (beta) || ! isscalar (gamma)) [retval, x, beta, gamma] = common_size (x, beta, gamma); if (retval > 0) error (strcat ("bisacdf: X, BETA, and GAMMA must be of", ... " common size or scalars.")); endif endif ## Check for X, BETA, and GAMMA being double or single if (! (isfloat (x) && isfloat (beta) && isfloat (gamma))) error ("bisacdf: X, BETA, and GAMMA must be double or single."); endif ## Check for X, BETA, and GAMMA being reals if (iscomplex (x) || iscomplex (beta) || iscomplex (gamma)) error ("bisacdf: X, BETA, and GAMMA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (beta, 'single') || isa (gamma, 'single')) p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Force NaNs for out of range parameters. k = isnan (x) | ! (beta > 0) | ! (beta < Inf) ... | ! (gamma > 0) | ! (gamma < Inf); p(k) = NaN; ## Find valid values in parameters and data k = (x > 0) & (x <= Inf) & (beta > 0) & (beta < Inf) ... & (gamma > 0) & (gamma < Inf); xk = x(k); ## Compute Birnbaum-Saunders CDF if (isscalar (beta) && isscalar (gamma)) if (uflag) z = (-sqrt (xk ./ beta) + sqrt (beta ./ xk)) ./ gamma; else z = (sqrt (xk ./ beta) - sqrt (beta ./ xk)) ./ gamma; endif p(k) = 0.5 * erfc (-z ./ sqrt (2)); else if (uflag) z = (-sqrt (xk ./ beta(k)) + sqrt (beta(k) ./ xk)) ./ gamma(k); else z = (sqrt (xk ./ beta(k)) - sqrt (beta(k) ./ xk)) ./ gamma(k); endif p(k) = 0.5 * erfc (-z ./ sqrt (2)); endif endfunction %!demo %! ## Plot various CDFs from the Birnbaum-Saunders distribution %! x = 0.01:0.01:4; %! p1 = bisacdf (x, 1, 0.5); %! p2 = bisacdf (x, 1, 1); %! p3 = bisacdf (x, 1, 2); %! p4 = bisacdf (x, 1, 5); %! p5 = bisacdf (x, 1, 10); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c', x, p5, '-m') %! grid on %! legend ({'β = 1, γ = 0.5', 'β = 1, γ = 1', 'β = 1, γ = 2', ... %! 'β = 1, γ = 5', 'β = 1, γ = 10'}, 'location', 'southeast') %! title ('Birnbaum-Saunders CDF') %! xlabel ('values in x') %! ylabel ('probability') %!demo %! ## Plot various CDFs from the Birnbaum-Saunders distribution %! x = 0.01:0.01:6; %! p1 = bisacdf (x, 1, 0.3); %! p2 = bisacdf (x, 2, 0.3); %! p3 = bisacdf (x, 1, 0.5); %! p4 = bisacdf (x, 3, 0.5); %! p5 = bisacdf (x, 5, 0.5); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c', x, p5, '-m') %! grid on %! legend ({'β = 1, γ = 0.3', 'β = 2, γ = 0.3', 'β = 1, γ = 0.5', ... %! 'β = 3, γ = 0.5', 'β = 5, γ = 0.5'}, 'location', 'southeast') %! title ('Birnbaum-Saunders CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-1, 0, 1, 2, Inf]; %! y = [0, 0, 1/2, 0.76024993890652337, 1]; %!assert_equal (bisacdf (x, ones (1,5), ones (1,5)), y, eps) %!assert_equal (bisacdf (x, 1, 1), y, eps) %!assert_equal (bisacdf (x, 1, ones (1,5)), y, eps) %!assert_equal (bisacdf (x, ones (1,5), 1), y, eps) %!assert_equal (bisacdf (x, 1, 1), y, eps) %!assert_equal (bisacdf (x, 1, [1, 1, NaN, 1, 1]), [y(1:2), NaN, y(4:5)], eps) %!assert_equal (bisacdf (x, [1, 1, NaN, 1, 1], 1), [y(1:2), NaN, y(4:5)], eps) %!assert_equal (bisacdf ([x, NaN], 1, 1), [y, NaN], eps) ## Test class of input preserved %!assert_equal (bisacdf (single ([x, NaN]), 1, 1), single ([y, NaN]), eps ('single')) %!assert_equal (bisacdf ([x, NaN], 1, single (1)), single ([y, NaN]), eps ('single')) %!assert_equal (bisacdf ([x, NaN], single (1), 1), single ([y, NaN]), eps ('single')) ## Test input validation %!error bisacdf () %!error bisacdf (1) %!error bisacdf (1, 2) %!error ... %! bisacdf (1, 2, 3, 4, 5) %!error bisacdf (1, 2, 3, 'tail') %!error bisacdf (1, 2, 3, 4) %!error ... %! bisacdf (ones (3), ones (2), ones (2)) %!error ... %! bisacdf (ones (2), ones (3), ones (2)) %!error ... %! bisacdf (ones (2), ones (2), ones (3)) %!error bisacdf (int32 (2), 4, 3) %!error bisacdf (true, 4, 3) %!error bisacdf ('a', 4, 3) %!error bisacdf (i, 4, 3) %!error bisacdf (1, i, 3) %!error bisacdf (1, 4, i) statistics-release-1.9.2/inst/Distribution_Functions/bisainv.m000066400000000000000000000155671524624707500246620ustar00rootroot00000000000000## Copyright (C) 1995-2015 Kurt Hornik ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 2018 John Donoghue ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} bisainv (@var{p}, @var{beta}, @var{gamma}) ## ## Inverse of the Birnbaum-Saunders cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the Birnbaum-Saunders distribution with scale parameter @var{beta} and shape ## parameter @var{gamma}. The size of @var{x} is the common size of @var{p}, ## @var{beta}, and @var{gamma}. A scalar input functions as a constant matrix ## of the same size as the other inputs. ## ## Further information about the Birnbaum-Saunders distribution can be found at ## @url{https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{bisacdf, bisapdf, bisarnd, bisafit, bisalike, bisastat} ## @end deftypefn function x = bisainv (p, beta, gamma) ## Check for valid number of input arguments if (nargin < 3) error ("bisainv: function called with too few input arguments."); endif ## Check for common size of X, BETA, and GAMMA if (! isscalar (p) || ! isscalar (beta) || ! isscalar (gamma)) [retval, p, beta, gamma] = common_size (p, beta, gamma); if (retval > 0) error (strcat ("bisainv: P, BETA, and GAMMA must be of", ... " common size or scalars.")); endif endif ## Check for P, BETA, and GAMMA being double or single if (! (isfloat (p) && isfloat (beta) && isfloat (gamma))) error ("bisainv: P, BETA, and GAMMA must be double or single."); endif ## Check for X, BETA, and GAMMA being reals if (iscomplex (p) || iscomplex (beta) || iscomplex (gamma)) error ("bisainv: P, BETA, and GAMMA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (beta, 'single') || isa (gamma, 'single')) x = zeros (size (p), 'single'); else x = zeros (size (p)); endif ## Force NaNs for out of range parameters kn = isnan (p) | (p < 0) | (p > 1) | ! (beta > 0) | ! (beta < Inf) ... | ! (gamma > 0) | ! (gamma < Inf); x(kn) = NaN; ## Find valid values in parameters kv = (beta > 0) & (beta < Inf) & (gamma > 0) & (gamma < Inf); ## Handle edge cases k0 = (p == 0) & kv; x(k0) = 0; k1 = (p == 1) & kv; x(k1) = Inf; ## Handle all other valid cases k = (p > 0) & (p < 1) & kv; if (isscalar (beta) && isscalar (gamma)) z = -sqrt (2) .* erfcinv (2 .* p(k)) .* gamma; x(k) = 0.25 .* beta .* (z + sqrt (4 + z .^ 2)) .^ 2; else z = -sqrt (2) .* erfcinv (2 .* p(k)) .* gamma(k); x(k) = 0.25 .* beta(k) .* (z + sqrt (4 + z .^ 2)) .^ 2; endif endfunction %!demo %! ## Plot various iCDFs from the Birnbaum-Saunders distribution %! p = 0.001:0.001:0.999; %! x1 = bisainv (p, 1, 0.5); %! x2 = bisainv (p, 1, 1); %! x3 = bisainv (p, 1, 2); %! x4 = bisainv (p, 1, 5); %! x5 = bisainv (p, 1, 10); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c', p, x5, '-m') %! grid on %! ylim ([0, 10]) %! legend ({'β = 1, γ = 0.5', 'β = 1, γ = 1', 'β = 1, γ = 2', ... %! 'β = 1, γ = 5', 'β = 1, γ = 10'}, 'location', 'northwest') %! title ('Birnbaum-Saunders iCDF') %! xlabel ('probability') %! ylabel ('values in x') %!demo %! ## Plot various iCDFs from the Birnbaum-Saunders distribution %! p = 0.001:0.001:0.999; %! x1 = bisainv (p, 1, 0.3); %! x2 = bisainv (p, 2, 0.3); %! x3 = bisainv (p, 1, 0.5); %! x4 = bisainv (p, 3, 0.5); %! x5 = bisainv (p, 5, 0.5); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c', p, x5, '-m') %! grid on %! ylim ([0, 10]) %! legend ({'β = 1, γ = 0.3', 'β = 2, γ = 0.3', 'β = 1, γ = 0.5', ... %! 'β = 3, γ = 0.5', 'β = 5, γ = 0.5'}, 'location', 'northwest') %! title ('Birnbaum-Saunders iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p, y, f %! f = @(p,b,c) (b * (c * norminv (p) + sqrt (4 + (c * norminv (p))^2))^2) / 4; %! p = [-1, 0, 1/4, 1/2, 1, 2]; %! y = [NaN, 0, f(1/4, 1, 1), 1, Inf, NaN]; %!assert_equal (bisainv (p, ones (1,6), ones (1,6)), y) %!assert_equal (bisainv (p, 1, ones (1,6)), y) %!assert_equal (bisainv (p, ones (1,6), 1), y) %!assert_equal (bisainv (p, 1, 1), y) %!assert_equal (bisainv (p, 1, [1, 1, 1, NaN, 1, 1]), [y(1:3), NaN, y(5:6)]) %!assert_equal (bisainv (p, [1, 1, 1, NaN, 1, 1], 1), [y(1:3), NaN, y(5:6)]) %!assert_equal (bisainv ([p, NaN], 1, 1), [y, NaN]) ## Test class of input preserved %!assert_equal (bisainv (single ([p, NaN]), 1, 1), single ([y, NaN]), eps ('single')) %!assert_equal (bisainv ([p, NaN], 1, single (1)), single ([y, NaN]), eps ('single')) %!assert_equal (bisainv ([p, NaN], single (1), 1), single ([y, NaN]), eps ('single')) ## Test input validation %!error bisainv () %!error bisainv (1) %!error bisainv (1, 2) %!error bisainv (1, 2, 3, 4) %!error ... %! bisainv (ones (3), ones (2), ones (2)) %!error ... %! bisainv (ones (2), ones (3), ones (2)) %!error ... %! bisainv (ones (2), ones (2), ones (3)) %!error bisainv (int32 (2), 4, 3) %!error bisainv (true, 4, 3) %!error bisainv ('a', 4, 3) %!error bisainv (i, 4, 3) %!error bisainv (1, i, 3) %!error bisainv (1, 4, i) statistics-release-1.9.2/inst/Distribution_Functions/bisapdf.m000066400000000000000000000155051524624707500246270ustar00rootroot00000000000000## Copyright (C) 2018 John Donoghue ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 1995-2015 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} bisapdf (@var{x}, @var{beta}, @var{gamma}) ## ## Birnbaum-Saunders probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the Birnbaum-Saunders distribution with scale parameter @var{beta} and ## shape parameter @var{gamma}. The size of @var{y} is the common size of ## @var{x}, @var{beta}, and @var{gamma}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## Further information about the Birnbaum-Saunders distribution can be found at ## @url{https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{bisacdf, bisainv, bisarnd, bisafit, bisalike, bisastat} ## @end deftypefn function y = bisapdf (x, beta, gamma) ## Check for valid number of input arguments if (nargin < 3) error ("bisapdf: function called with too few input arguments."); endif ## Check for common size of X, BETA and GAMMA if (! isscalar (x) || ! isscalar (beta) || ! isscalar (gamma)) [retval, x, beta, gamma] = common_size (x, beta, gamma); if (retval > 0) error (strcat ("bisapdf: X, BETA, and GAMMA must be of", ... " common size or scalars.")); endif endif ## Check for X, BETA, and GAMMA being double or single if (! (isfloat (x) && isfloat (beta) && isfloat (gamma))) error ("bisapdf: X, BETA, and GAMMA must be double or single."); endif ## Check for X, BETA and GAMMA being reals if (iscomplex (x) || iscomplex (beta) || iscomplex (gamma)) error ("bisapdf: X, BETA, and GAMMA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (beta, 'single') || isa (gamma, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Force NaNs for out of range parameters. k = isnan (x) | ! (beta > 0) | ! (beta < Inf) ... | ! (gamma > 0) | ! (gamma < Inf); y(k) = NaN; ## Find valid values in parameters and data k = (x > 0) & (x < Inf) & (beta > 0) & (beta < Inf) ... & (gamma > 0) & (gamma < Inf); xk = x(k); if (isscalar (beta) && isscalar (gamma)) z = (sqrt (xk ./ beta) - sqrt (beta ./ xk)) ./ gamma; w = (sqrt (xk ./ beta) + sqrt (beta ./ xk)) ./ gamma; y(k) = (exp (-0.5 .* z .^ 2) ./ sqrt (2 .* pi)) .* w ./ (2.*xk); else z = (sqrt (xk ./ beta(k)) - sqrt (beta(k) ./ xk)) ./ gamma(k); w = (sqrt (xk ./ beta(k)) + sqrt (beta(k) ./ xk)) ./ gamma(k); y(k) = (exp (-0.5 .* z .^ 2) ./ sqrt (2 .* pi)) .* w ./ (2 .* xk); endif endfunction %!demo %! ## Plot various PDFs from the Birnbaum-Saunders distribution %! x = 0.01:0.01:4; %! y1 = bisapdf (x, 1, 0.5); %! y2 = bisapdf (x, 1, 1); %! y3 = bisapdf (x, 1, 2); %! y4 = bisapdf (x, 1, 5); %! y5 = bisapdf (x, 1, 10); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c', x, y5, '-m') %! grid on %! ylim ([0, 1.5]) %! legend ({'β = 1 ,γ = 0.5', 'β = 1, γ = 1', 'β = 1, γ = 2', ... %! 'β = 1, γ = 5', 'β = 1, γ = 10'}, 'location', 'northeast') %! title ('Birnbaum-Saunders PDF') %! xlabel ('values in x') %! ylabel ('density') %!demo %! ## Plot various PDFs from the Birnbaum-Saunders distribution %! x = 0.01:0.01:6; %! y1 = bisapdf (x, 1, 0.3); %! y2 = bisapdf (x, 2, 0.3); %! y3 = bisapdf (x, 1, 0.5); %! y4 = bisapdf (x, 3, 0.5); %! y5 = bisapdf (x, 5, 0.5); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c', x, y5, '-m') %! grid on %! ylim ([0, 1.5]) %! legend ({'β = 1, γ = 0.3', 'β = 2, γ = 0.3', 'β = 1, γ = 0.5', ... %! 'β = 3, γ = 0.5', 'β = 5, γ = 0.5'}, 'location', 'northeast') %! title ('Birnbaum-Saunders CDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1, 0, 1, 2, Inf]; %! y = [0, 0, 0.3989422804014327, 0.1647717335503959, 0]; %!assert_equal (bisapdf (x, ones (1,5), ones (1,5)), y, eps) %!assert_equal (bisapdf (x, 1, 1), y, eps) %!assert_equal (bisapdf (x, 1, ones (1,5)), y, eps) %!assert_equal (bisapdf (x, ones (1,5), 1), y, eps) %!assert_equal (bisapdf (x, 1, [1, 1, NaN, 1, 1]), [y(1:2), NaN, y(4:5)], eps) %!assert_equal (bisapdf (x, [1, 1, NaN, 1, 1], 1), [y(1:2), NaN, y(4:5)], eps) %!assert_equal (bisapdf ([x, NaN], 1, 1), [y, NaN], eps) ## Test class of input preserved %!assert_equal (bisapdf (single ([x, NaN]), 1, 1), single ([y, NaN]), eps ('single')) %!assert_equal (bisapdf ([x, NaN], 1, single (1)), single ([y, NaN]), eps ('single')) %!assert_equal (bisapdf ([x, NaN], single (1), 1), single ([y, NaN]), eps ('single')) ## Test input validation %!error bisapdf () %!error bisapdf (1) %!error bisapdf (1, 2) %!error bisapdf (1, 2, 3, 4) %!error ... %! bisapdf (ones (3), ones (2), ones (2)) %!error ... %! bisapdf (ones (2), ones (3), ones (2)) %!error ... %! bisapdf (ones (2), ones (2), ones (3)) %!error bisapdf (int32 (2), 4, 3) %!error bisapdf (true, 4, 3) %!error bisapdf ('a', 4, 3) %!error bisapdf (i, 4, 3) %!error bisapdf (1, i, 3) %!error bisapdf (1, 4, i) statistics-release-1.9.2/inst/Distribution_Functions/bisarnd.m000066400000000000000000000160671524624707500246450ustar00rootroot00000000000000## Copyright (C) 2018 John Donoghue ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 1995-2015 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} bisarnd (@var{beta}, @var{gamma}) ## @deftypefnx {statistics} {@var{r} =} bisarnd (@var{beta}, @var{gamma}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} bisarnd (@var{beta}, @var{gamma}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} bisarnd (@var{beta}, @var{gamma}, [@var{sz}]) ## ## Random arrays from the Birnbaum-Saunders distribution. ## ## @code{@var{r} = bisarnd (@var{beta}, @var{gamma})} returns an array of ## random numbers chosen from the Birnbaum-Saunders distribution with scale ## parameter @var{beta} and shape parameter @var{gamma}. The size of @var{r} is ## the common size of @var{beta} and @var{gamma}. A scalar input functions as a ## constant matrix of the same size as the other inputs. ## ## When called with a single size argument, @code{bisarnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the Birnbaum-Saunders distribution can be found at ## @url{https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution} ## ## @seealso{bisacdf, bisainv, bisapdf, bisafit, bisalike, bisastat} ## @end deftypefn function r = bisarnd (beta, gamma, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("bisarnd: function called with too few input arguments."); endif ## Check for common size of BETA and GAMMA if (! isscalar (beta) || ! isscalar (gamma)) [retval, beta, gamma] = common_size (beta, gamma); if (retval > 0) error ("bisarnd: BETA and GAMMA must be of common size or scalars."); endif endif ## Check for BETA and GAMMA being reals if (iscomplex (beta) || iscomplex (gamma)) error ("bisarnd: BETA and GAMMA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (beta); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("bisarnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("bisarnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (beta) && ! isequal (size (beta), size (ones (sz)))) error ("bisarnd: BETA and GAMMA must be scalars or of size SZ."); endif ## Check for class type if (isa (beta, 'single') || isa (gamma, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from Birnbaum-Saunders distribution if (isscalar (beta) && isscalar (gamma)) if ((beta > 0) && (beta < Inf) && (gamma > 0) && (gamma < Inf)) r = rand (sz, cls); y = gamma * norminv (r); r = beta * (y + sqrt (4 + y .^ 2)) .^ 2 / 4; else r = NaN (sz, cls); endif else r = NaN (sz, cls); k = (beta > 0) & (beta < Inf) & (gamma > 0) & (gamma < Inf); r(k) = rand (sum (k(:)),1); y = gamma(k) .* norminv (r(k)); r(k) = beta(k) .* (y + sqrt (4 + y.^2)).^2 / 4; endif endfunction ## Test output %!assert_equal (size (bisarnd (1, 1)), [1 1]) %!assert_equal (size (bisarnd (1, ones (2,1))), [2, 1]) %!assert_equal (size (bisarnd (1, ones (2,2))), [2, 2]) %!assert_equal (size (bisarnd (ones (2,1), 1)), [2, 1]) %!assert_equal (size (bisarnd (ones (2,2), 1)), [2, 2]) %!assert_equal (size (bisarnd (1, 1, 3)), [3, 3]) %!assert_equal (size (bisarnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (bisarnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (bisarnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (bisarnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (bisarnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (bisarnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (bisarnd (1, 1, [])), [0, 0]) %!assert_equal (size (bisarnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (bisarnd (1, 2, -1)), [0, 0]) %!assert_equal (size (bisarnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (bisarnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (bisarnd (1, 1)), "double") %!assert_equal (class (bisarnd (1, single (1))), "single") %!assert_equal (class (bisarnd (1, single ([1, 1]))), "single") %!assert_equal (class (bisarnd (single (1), 1)), "single") %!assert_equal (class (bisarnd (single ([1, 1]), 1)), "single") ## Test input validation %!error bisarnd () %!error bisarnd (1) %!error ... %! bisarnd (ones (3), ones (2)) %!error ... %! bisarnd (ones (2), ones (3)) %!error bisarnd (i, 2, 3) %!error bisarnd (1, i, 3) %!error ... %! bisarnd (1, 2, 1.2) %!error ... %! bisarnd (1, 2, ones (2)) %!error ... %! bisarnd (1, 2, [2 0 2.5]) %!error ... %! bisarnd (1, 2, 2, 1.5, 5) %!error ... %! bisarnd (2, ones (2), 3) %!error ... %! bisarnd (2, ones (2), [3, 2]) %!error ... %! bisarnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/burrcdf.m000066400000000000000000000166721524624707500246540ustar00rootroot00000000000000## Copyright (C) 1995-2015 Kurt Hornik ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} burrcdf (@var{x}, @var{lambda}, @var{c}, @var{k}) ## @deftypefnx {statistics} {@var{p} =} burrcdf (@var{x}, @var{lambda}, @var{c}, @var{k}, @qcode{'upper'}) ## ## Burr type XII cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the Burr type XII distribution with scale parameter @var{lambda}, ## first shape parameter @var{c}, and second shape parameter @var{k}. The size ## of @var{p} is the common size of @var{x}, @var{lambda}, @var{c}, and @var{k}. ## A scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## @code{@var{p} = burrcdf (@var{x}, @var{lambda}, @var{c}, @var{k}, "upper")} ## computes the upper tail probability of the Burr type XII distribution with ## parameters @var{lambda}, @var{c} and @var{k}, at the values in @var{x}. ## ## Further information about the Burr distribution can be found at ## @url{https://en.wikipedia.org/wiki/Burr_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{burrinv, burrpdf, burrrnd, burrfit, burrlike, burrstat} ## @end deftypefn function p = burrcdf (x, lambda, c, k, uflag) ## Check for valid number of input arguments if (nargin < 4) error ("burrcdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 4) if (! strcmpi (uflag, 'upper')) error ("burrcdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, LANBDA, C and K if (! isscalar (x) || ! isscalar (lambda) || ! isscalar (c) || ! isscalar (k)) [retval, x, lambda, c, k] = common_size (x, lambda, c, k); if (retval > 0) error ("burrcdf: X, LAMBDA, C, and K must be of common size or scalars."); endif endif ## Check for X, LAMBDA, C, and K being double or single if (! (isfloat (x) && isfloat (lambda) && isfloat (c) && isfloat (k))) error ("burrcdf: X, LAMBDA, C, and K must be double or single."); endif ## Check for X, LANBDA, C and K being reals if (iscomplex (x) || iscomplex (lambda) || iscomplex (c) || iscomplex (k)) error ("burrcdf: X, LAMBDA, C, and K must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (lambda, 'single') || isa (c, 'single') ... || isa (k, 'single')) p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Force NaNs for out of range parameters j = isnan (x) | ! (lambda > 0) | ! (c > 0) | ! (k > 0); p(j) = NaN; ## Find valid values in parameters and data j = (x > 0) & (lambda > 0) & (lambda < Inf) & (c > 0) & (c < Inf) ... & (k > 0) & (k < Inf); ## Compute Burr CDF if (isscalar (lambda) && isscalar (c) && isscalar (k)) if (uflag) p(j) = (1 + (x(j) / lambda) .^ c) .^ (-k); else p(j) = 1 - (1 + (x(j) / lambda) .^ c) .^ (-k); endif else if (uflag) p(j) = (1 + (x(j) ./ lambda(j)) .^ c(j)) .^ (-k(j)); else p(j) = 1 - (1 + (x(j) ./ lambda(j)) .^ c(j)) .^ (-k(j)); endif endif endfunction %!demo %! ## Plot various CDFs from the Burr type XII distribution %! x = 0.001:0.001:5; %! p1 = burrcdf (x, 1, 1, 1); %! p2 = burrcdf (x, 1, 1, 2); %! p3 = burrcdf (x, 1, 1, 3); %! p4 = burrcdf (x, 1, 2, 1); %! p5 = burrcdf (x, 1, 3, 1); %! p6 = burrcdf (x, 1, 0.5, 2); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', ... %! x, p4, '-c', x, p5, '-m', x, p6, '-k') %! grid on %! legend ({'λ = 1, c = 1, k = 1', 'λ = 1, c = 1, k = 2', ... %! 'λ = 1, c = 1, k = 3', 'λ = 1, c = 2, k = 1', ... %! 'λ = 1, c = 3, k = 1', 'λ = 1, c = 0.5, k = 2'}, ... %! 'location', 'southeast') %! title ('Burr type XII CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-1, 0, 1, 2, Inf]; %! y = [0, 0, 1/2, 2/3, 1]; %!assert_equal (burrcdf (x, ones (1,5), ones (1,5), ones (1,5)), y, eps) %!assert_equal (burrcdf (x, 1, 1, 1), y, eps) %!assert_equal (burrcdf (x, [1, 1, NaN, 1, 1], 1, 1), [y(1:2), NaN, y(4:5)], eps) %!assert_equal (burrcdf (x, 1, [1, 1, NaN, 1, 1], 1), [y(1:2), NaN, y(4:5)], eps) %!assert_equal (burrcdf (x, 1, 1, [1, 1, NaN, 1, 1]), [y(1:2), NaN, y(4:5)], eps) %!assert_equal (burrcdf ([x, NaN], 1, 1, 1), [y, NaN], eps) ## Test class of input preserved %!assert_equal (burrcdf (single ([x, NaN]), 1, 1, 1), single ([y, NaN]), eps ('single')) %!assert_equal (burrcdf ([x, NaN], single (1), 1, 1), single ([y, NaN]), eps ('single')) %!assert_equal (burrcdf ([x, NaN], 1, single (1), 1), single ([y, NaN]), eps ('single')) %!assert_equal (burrcdf ([x, NaN], 1, 1, single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error burrcdf () %!error burrcdf (1) %!error burrcdf (1, 2) %!error burrcdf (1, 2, 3) %!error ... %! burrcdf (1, 2, 3, 4, 5, 6) %!error burrcdf (1, 2, 3, 4, 'tail') %!error burrcdf (1, 2, 3, 4, 5) %!error ... %! burrcdf (ones (3), ones (2), ones (2), ones (2)) %!error ... %! burrcdf (ones (2), ones (3), ones (2), ones (2)) %!error ... %! burrcdf (ones (2), ones (2), ones (3), ones (2)) %!error ... %! burrcdf (ones (2), ones (2), ones (2), ones (3)) %!error burrcdf (int32 (2), 2, 3, 4) %!error burrcdf (true, 2, 3, 4) %!error burrcdf ('a', 2, 3, 4) %!error burrcdf (i, 2, 3, 4) %!error burrcdf (1, i, 3, 4) %!error burrcdf (1, 2, i, 4) %!error burrcdf (1, 2, 3, i) statistics-release-1.9.2/inst/Distribution_Functions/burrinv.m000066400000000000000000000154201524624707500247020ustar00rootroot00000000000000## Copyright (C) 1995-2015 Kurt Hornik ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} burrinv (@var{p}, @var{lambda}, @var{c}, @var{k}) ## ## Inverse of the Burr type XII cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the Burr type XII distribution with scale parameter @var{lambda}, first shape ## parameter @var{c}, and second shape parameter @var{k}. The size of @var{x} ## is the common size of @var{p}, @var{lambda}, @var{c}, and @var{k}. A scalar ## input functions as a constant matrix of the same size as the other inputs. ## ## Further information about the Burr distribution can be found at ## @url{https://en.wikipedia.org/wiki/Burr_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{burrcdf, burrpdf, burrrnd, burrfit, burrlike, burrstat} ## @end deftypefn function x = burrinv (p, lambda, c, k) ## Check for valid number of input arguments if (nargin < 4) error ("burrinv: function called with too few input arguments."); endif ## Check for common size of P, LANBDA, C and K if (! isscalar (p) || ! isscalar (lambda) || ! isscalar (c) || ! isscalar (k)) [retval, p, lambda, c, k] = common_size (p, lambda, c, k); if (retval > 0) error ("burrinv: P, LAMBDA, C, and K must be of common size or scalars."); endif endif ## Check for P, LAMBDA, C, and K being double or single if (! (isfloat (p) && isfloat (lambda) && isfloat (c) && isfloat (k))) error ("burrinv: P, LAMBDA, C, and K must be double or single."); endif ## Check for P, LANBDA, C and K being reals if (iscomplex (p) || iscomplex (lambda) || iscomplex (c) || iscomplex (k)) error ("burrinv: P, LAMBDA, C, and K must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (lambda, 'single') || isa (c, 'single') ... || isa (k, 'single')) x = zeros (size (p), 'single'); else x = zeros (size (p)); endif ## Force NaNs for out of range parameters j = isnan (p) | (p < 0) | (p > 1) | ! (lambda > 0) | ! (c > 0) | ! (k > 0); x(j) = NaN; ## Handle edge cases j = (p == 1) & (lambda > 0) & (lambda < Inf) & (c > 0) & (c < Inf) ... & (k > 0) & (k < Inf); x(j) = Inf; ## Handle all other valid cases j = (0 < p) & (p < 1) & (0 < lambda) & (lambda < Inf) & (0 < c) & (c < Inf) ... & (0 < k) & (k < Inf); if (isscalar (lambda) && isscalar (c) && isscalar (k)) x(j) = ((1 - p(j) / lambda).^(-1 / k) - 1).^(1 / c) ; else x(j) = ((1 - p(j) ./ lambda(j)).^(-1 ./ k(j)) - 1).^(1 ./ c(j)) ; endif endfunction %!demo %! ## Plot various iCDFs from the Burr type XII distribution %! p = 0.001:0.001:0.999; %! x1 = burrinv (p, 1, 1, 1); %! x2 = burrinv (p, 1, 1, 2); %! x3 = burrinv (p, 1, 1, 3); %! x4 = burrinv (p, 1, 2, 1); %! x5 = burrinv (p, 1, 3, 1); %! x6 = burrinv (p, 1, 0.5, 2); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', ... %! p, x4, '-c', p, x5, '-m', p, x6, '-k') %! grid on %! ylim ([0, 5]) %! legend ({'λ = 1, c = 1, k = 1', 'λ = 1, c = 1, k = 2', ... %! 'λ = 1, c = 1, k = 3', 'λ = 1, c = 2, k = 1', ... %! 'λ = 1, c = 3, k = 1', 'λ = 1, c = 0.5, k = 2'}, ... %! 'location', 'northwest') %! title ('Burr type XII iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p, y %! p = [-Inf, -1, 0, 1/2, 1, 2, Inf]; %! y = [NaN, NaN, 0, 1 , Inf, NaN, NaN]; %!assert_equal (burrinv (p, ones (1,7), ones (1,7), ones (1,7)), y, eps) %!assert_equal (burrinv (p, 1, 1, 1), y, eps) %!assert_equal (burrinv (p, [1, 1, 1, NaN, 1, 1, 1], 1, 1), [y(1:3), NaN, y(5:7)], eps) %!assert_equal (burrinv (p, 1, [1, 1, 1, NaN, 1, 1, 1], 1), [y(1:3), NaN, y(5:7)], eps) %!assert_equal (burrinv (p, 1, 1, [1, 1, 1, NaN, 1, 1, 1]), [y(1:3), NaN, y(5:7)], eps) %!assert_equal (burrinv ([p, NaN], 1, 1, 1), [y, NaN], eps) ## Test class of input preserved %!assert_equal (burrinv (single ([p, NaN]), 1, 1, 1), single ([y, NaN]), eps ('single')) %!assert_equal (burrinv ([p, NaN], single (1), 1, 1), single ([y, NaN]), eps ('single')) %!assert_equal (burrinv ([p, NaN], 1, single (1), 1), single ([y, NaN]), eps ('single')) %!assert_equal (burrinv ([p, NaN], 1, 1, single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error burrinv () %!error burrinv (1) %!error burrinv (1, 2) %!error burrinv (1, 2, 3) %!error ... %! burrinv (1, 2, 3, 4, 5) %!error ... %! burrinv (ones (3), ones (2), ones (2), ones (2)) %!error ... %! burrinv (ones (2), ones (3), ones (2), ones (2)) %!error ... %! burrinv (ones (2), ones (2), ones (3), ones (2)) %!error ... %! burrinv (ones (2), ones (2), ones (2), ones (3)) %!error burrinv (int32 (2), 2, 3, 4) %!error burrinv (true, 2, 3, 4) %!error burrinv ('a', 2, 3, 4) %!error burrinv (i, 2, 3, 4) %!error burrinv (1, i, 3, 4) %!error burrinv (1, 2, i, 4) %!error burrinv (1, 2, 3, i) statistics-release-1.9.2/inst/Distribution_Functions/burrpdf.m000066400000000000000000000151331524624707500246600ustar00rootroot00000000000000## Copyright (C) 1995-2015 Kurt Hornik ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} burrpdf (@var{x}, @var{lambda}, @var{c}, @var{k}) ## ## Burr type XII probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the Burr type XII distribution with scale parameter @var{lambda}, first ## shape parameter @var{c}, and second shape parameter @var{k}. The size of ## @var{y} is the common size of @var{x}, @var{lambda}, @var{c}, and @var{k}. ## A scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## Further information about the Burr distribution can be found at ## @url{https://en.wikipedia.org/wiki/Burr_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{burrcdf, burrinv, burrrnd, burrfit, burrlike, burrstat} ## @end deftypefn function y = burrpdf (x, lambda, c, k) ## Check for valid number of input arguments if (nargin < 4) error ("burrpdf: function called with too few input arguments."); endif ## Check for common size of X, LANBDA, C and K if (! isscalar (x) || ! isscalar (lambda) || ! isscalar (c) || ! isscalar (k)) [retval, x, lambda, c, k] = common_size (x, lambda, c, k); if (retval > 0) error ("burrpdf: X, LAMBDA, C, and K must be of common size or scalars."); endif endif ## Check for X, LAMBDA, C, and K being double or single if (! (isfloat (x) && isfloat (lambda) && isfloat (c) && isfloat (k))) error ("burrpdf: X, LAMBDA, C, and K must be double or single."); endif ## Check for X, LANBDA, C and K being reals if (iscomplex (x) || iscomplex (lambda) || iscomplex (c) || iscomplex (k)) error ("burrpdf: X, LAMBDA, C, and K must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (lambda, 'single') ... || isa (c, 'single') || isa (k, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Force NaNs for out of range parameters j = isnan (x) | ! (lambda > 0) | ! (c > 0) | ! (k > 0); y(j) = NaN; ## Find valid values in parameters and data j = (x >= 0) & (0 < lambda) & (lambda < Inf) & (0 < c) & (c < Inf) ... & (0 < k) & (k < Inf); ## Compute Burr PDF if (isscalar (lambda) && isscalar (c) && isscalar (k)) y(j) = (c * k / lambda) .* (x(j) / lambda) .^ (c - 1) ./ ... (1 + (x(j) / lambda) .^ c) .^ (k + 1); else y(j) = (c(j) .* k(j) ./ lambda(j) ) .* x(j).^(c(j) - 1) ./ ... (1 + (x(j) ./ lambda(j) ) .^ c(j)) .^ (k(j) + 1); endif endfunction %!demo %! ## Plot various PDFs from the Burr type XII distribution %! x = 0.001:0.001:3; %! y1 = burrpdf (x, 1, 1, 1); %! y2 = burrpdf (x, 1, 1, 2); %! y3 = burrpdf (x, 1, 1, 3); %! y4 = burrpdf (x, 1, 2, 1); %! y5 = burrpdf (x, 1, 3, 1); %! y6 = burrpdf (x, 1, 0.5, 2); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', ... %! x, y4, '-c', x, y5, '-m', x, y6, '-k') %! grid on %! ylim ([0, 2]) %! legend ({'λ = 1, c = 1, k = 1', 'λ = 1, c = 1, k = 2', ... %! 'λ = 1, c = 1, k = 3', 'λ = 1, c = 2, k = 1', ... %! 'λ = 1, c = 3, k = 1', 'λ = 1, c = 0.5, k = 2'}, ... %! 'location', 'northeast') %! title ('Burr type XII PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1, 0, 1, 2, Inf]; %! y = [0, 1, 1/4, 1/9, 0]; %!assert_equal (burrpdf (x, ones (1,5), ones (1,5), ones (1,5)), y) %!assert_equal (burrpdf (x, 1, 1, 1), y) %!assert_equal (burrpdf (x, [1, 1, NaN, 1, 1], 1, 1), [y(1:2), NaN, y(4:5)]) %!assert_equal (burrpdf (x, 1, [1, 1, NaN, 1, 1], 1), [y(1:2), NaN, y(4:5)]) %!assert_equal (burrpdf (x, 1, 1, [1, 1, NaN, 1, 1]), [y(1:2), NaN, y(4:5)]) %!assert_equal (burrpdf ([x, NaN], 1, 1, 1), [y, NaN]) ## Test class of input preserved %!assert_equal (burrpdf (single ([x, NaN]), 1, 1, 1), single ([y, NaN])) %!assert_equal (burrpdf ([x, NaN], single (1), 1, 1), single ([y, NaN])) %!assert_equal (burrpdf ([x, NaN], 1, single (1), 1), single ([y, NaN])) %!assert_equal (burrpdf ([x, NaN], 1, 1, single (1)), single ([y, NaN])) ## Test input validation %!error burrpdf () %!error burrpdf (1) %!error burrpdf (1, 2) %!error burrpdf (1, 2, 3) %!error ... %! burrpdf (1, 2, 3, 4, 5) %!error ... %! burrpdf (ones (3), ones (2), ones (2), ones (2)) %!error ... %! burrpdf (ones (2), ones (3), ones (2), ones (2)) %!error ... %! burrpdf (ones (2), ones (2), ones (3), ones (2)) %!error ... %! burrpdf (ones (2), ones (2), ones (2), ones (3)) %!error burrpdf (int32 (2), 2, 3, 4) %!error burrpdf (true, 2, 3, 4) %!error burrpdf ('a', 2, 3, 4) %!error burrpdf (i, 2, 3, 4) %!error burrpdf (1, i, 3, 4) %!error burrpdf (1, 2, i, 4) %!error burrpdf (1, 2, 3, i) statistics-release-1.9.2/inst/Distribution_Functions/burrrnd.m000066400000000000000000000162351524624707500246760ustar00rootroot00000000000000## Copyright (C) 1995-2015 Kurt Hornik ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} burrrnd (@var{lambda}, @var{c}, @var{k}) ## @deftypefnx {statistics} {@var{r} =} burrrnd (@var{lambda}, @var{c}, @var{k}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} burrrnd (@var{lambda}, @var{c}, @var{k}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} burrrnd (@var{lambda}, @var{c}, @var{k}, [@var{sz}]) ## ## Random arrays from the Burr type XII distribution. ## ## @code{@var{r} = burrrnd (@var{lambda}, @var{c}, @var{k})} returns an array of ## random numbers chosen from the Burr type XII distribution with scale ## parameter @var{lambda}, first shape parameter @var{c}, and second shape ## parameter @var{k}. The size of @var{r} is the common size of @var{lambda}, ## @var{c}, and @var{k}. LAMBDA scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## When called with a single size argument, @code{burrrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the Burr distribution can be found at ## @url{https://en.wikipedia.org/wiki/Burr_distribution} ## ## @seealso{burrcdf, burrinv, burrpdf, burrfit, burrlike, burrstat} ## @end deftypefn function r = burrrnd (lambda, c, k, varargin) ## Check for valid number of input arguments if (nargin < 3) error ("burrrnd: function called with too few input arguments."); endif ## Check for common size of LAMBDA, C, and K if (! isscalar (lambda) || ! isscalar (c) || ! isscalar (k)) [retval, lambda, c, k] = common_size (lambda, c, k); if (retval > 0) error ("burrrnd: LAMBDA, C, and K must be of common size or scalars."); endif endif ## Check for LAMBDA, C, and K being reals if (iscomplex (lambda) || iscomplex (c) || iscomplex (k)) error ("burrrnd: LAMBDA, C, and K must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 3) sz = size (lambda); elseif (nargin == 4) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("burrrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 4) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("burrrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (lambda) && ! isequal (size (lambda), size (ones (sz)))) error ("burrrnd: LAMBDA, C, and K must be scalars or of size SZ."); endif ## Check for class type if (isa (lambda, 'single') || isa (c, 'single') || isa (k, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from Burr type XII distribution lambda(lambda <= 0) = NaN; c(c <= 0) = NaN; k(k <= 0) = NaN; r = lambda .* (((1 - rand (sz, cls)) .^ (-(1./k))) - 1) .^ (1./c); endfunction ## Test output %!assert_equal (size (burrrnd (1, 1, 1)), [1 1]) %!assert_equal (size (burrrnd (ones (2,1), 1, 1)), [2, 1]) %!assert_equal (size (burrrnd (ones (2,2), 1, 1)), [2, 2]) %!assert_equal (size (burrrnd (1, ones (2,1), 1)), [2, 1]) %!assert_equal (size (burrrnd (1, ones (2,2), 1)), [2, 2]) %!assert_equal (size (burrrnd (1, 1, ones (2,1))), [2, 1]) %!assert_equal (size (burrrnd (1, 1, ones (2,2))), [2, 2]) %!assert_equal (size (burrrnd (1, 1, 1, 3)), [3, 3]) %!assert_equal (size (burrrnd (1, 1, 1, [4 1])), [4, 1]) %!assert_equal (size (burrrnd (1, 1, 1, 4, 1)), [4, 1]) %!assert_equal (size (burrrnd (1, 1, 1, [])), [0, 0]) %!assert_equal (size (burrrnd (1, 1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (burrrnd (1, 2, 3, -1)), [0, 0]) %!assert_equal (size (burrrnd (1, 2, 3, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (burrrnd (1, 2, 3, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (burrrnd (1,1,1)), "double") %!assert_equal (class (burrrnd (single (1),1,1)), "single") %!assert_equal (class (burrrnd (single ([1 1]),1,1)), "single") %!assert_equal (class (burrrnd (1,single (1),1)), "single") %!assert_equal (class (burrrnd (1,single ([1 1]),1)), "single") %!assert_equal (class (burrrnd (1,1,single (1))), "single") %!assert_equal (class (burrrnd (1,1,single ([1 1]))), "single") ## Test input validation %!error burrrnd () %!error burrrnd (1) %!error burrrnd (1, 2) %!error ... %! burrrnd (ones (3), ones (2), ones (2)) %!error ... %! burrrnd (ones (2), ones (3), ones (2)) %!error ... %! burrrnd (ones (2), ones (2), ones (3)) %!error burrrnd (i, 2, 3) %!error burrrnd (1, i, 3) %!error burrrnd (1, 2, i) %!error ... %! burrrnd (1, 2, 3, 1.2) %!error ... %! burrrnd (1, 2, 3, ones (2)) %!error ... %! burrrnd (1, 2, 3, [2 0 2.5]) %!error ... %! burrrnd (1, 2, 3, 2, 1.5, 5) %!error ... %! burrrnd (2, ones (2), 2, 3) %!error ... %! burrrnd (2, ones (2), 2, [3, 2]) %!error ... %! burrrnd (2, ones (2), 2, 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/bvncdf.m000066400000000000000000000231511524624707500244550ustar00rootroot00000000000000## Copyright (C) 2022-2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} bvncdf (@var{x}, @var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{p} =} bvncdf (@var{x}, [], @var{sigma}) ## ## Bivariate normal cumulative distribution function (CDF). ## ## @code{@var{p} = bvncdf (@var{x}, @var{mu}, @var{sigma})} will compute the ## bivariate normal cumulative distribution function of @var{x} given a mean ## parameter @var{mu} and a scale parameter @var{sigma}. ## ## @itemize ## @item @var{x} must be an @math{N*2} matrix with each variable as a column ## vector. ## @item @var{mu} can be either a scalar (common mean) or a two-element row ## vector (each element corresponds to a variable). If empty, a zero mean is ## assumed. ## @item @var{sigma} can be a scalar (common variance) or a @math{2*2} ## covariance matrix, which must be positive definite. ## @end itemize ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{mvncdf} ## @end deftypefn ## Code adapted from Thomas H. Jørgensen's work in BVNcdf.m function retrieved ## from https://www.tjeconomics.com/code/ function p = bvncdf (x, mu, sigma) ## Check input arguments and add defaults ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("bvncdf: X, MU, and SIGMA must be double or single."); endif if (size (x, 2) != 2) error (strcat ("bvncdf: X must be an Nx2 matrix with each variable", ... " as a column vector.")); endif if (isempty (mu)) mu = [0, 0]; elseif (isscalar (mu)) mu = [mu, mu]; elseif (numel (mu) == 2); mu = mu(:)'; else error ("bvncdf: MU must be a scalar or a two-element vector."); endif if (numel (sigma) == 1) sigma = sigma * ones (2,2); sigma(1,1) = 1; sigma(2,2) = 1; elseif (numel (sigma) != 4) error (strcat ("bvncdf: the covariance matrix must be either a", ... " scalar or a 2x2 matrix.")); endif ## Test for symmetric positive definite covariance matrix [~, err] = chol (sigma); if (err != 0) error (strcat ("bvncdf: the covariance matrix is not positive", ... " definite and/or symmetric.")); endif dh = (x(:,1) - mu(:,1)) / sqrt (sigma(1,1)); dk = (x(:,2) - mu(:,2)) / sqrt (sigma(2,2)); r = sigma(1,2) / sqrt (sigma(1,1) * sigma(2,2)); p = NaN (size (dh)); ## Handle special cases for infinite integration limits p(dh == Inf & dk == Inf) = 1; ## Both limits are infinite: P(X1 ≤ ∞, X2 ≤ ∞) = 1 p(dk == Inf & dh != Inf) = 0.5 * erfc (- dh(dk == Inf & dh != Inf) / sqrt (2)); ## x2 → +∞ (finite x1): P(X1 ≤ x1, X2 ≤ ∞) = P(X1 ≤ x1) = Φ(dh) p(dh == Inf & dk != Inf) = 0.5 * erfc (- dk(dh == Inf & dk != Inf) / sqrt (2)); ## x1 → +∞ (finite x2): P(X1 ≤ ∞, X2 ≤ x2) = P(X2 ≤ x2) = Φ(dk) p(dh == -Inf | dk == -Inf) = 0; ## x1 → -∞ or x2 → -∞: P(X1 ≤ -∞, X2 ≤ x2) = P(X1 ≤ x1, X2 ≤ -∞) = 0 ind = (dh > -Inf & dh < Inf & dk > -Inf & dk < Inf); ## For p(x1 < dh, x2 < dk, r) if (sum (ind) > 0) p(ind) = calculate_bvncdf (-dh(ind), -dk(ind), r); endif endfunction function p = calculate_bvncdf (dh,dk,r) if (abs (r) < 0.3) lg = 3; ## Gauss Legendre points and weights, n = 6 w = [0.1713244923791705, 0.3607615730481384, 0.4679139345726904]; x = [0.9324695142031522, 0.6612093864662647, 0.2386191860831970]; elseif (abs (r) < 0.75) lg = 6; ## Gauss Legendre points and weights, n = 12 w = [.04717533638651177, 0.1069393259953183, 0.1600783285433464, ... 0.2031674267230659, 0.2334925365383547, 0.2491470458134029]; x = [0.9815606342467191, 0.9041172563704750, 0.7699026741943050, ... 0.5873179542866171, 0.3678314989981802, 0.1252334085114692]; else lg = 10; ## Gauss Legendre points and weights, n = 20 w = [.01761400713915212, .04060142980038694, .06267204833410906, ... .08327674157670475, 0.1019301198172404, 0.1181945319615184, ... 0.1316886384491766, 0.1420961093183821, 0.1491729864726037, ... 0.1527533871307259]; x = [0.9931285991850949, 0.9639719272779138, 0.9122344282513259, ... 0.8391169718222188, 0.7463319064601508, 0.6360536807265150, ... 0.5108670019508271, 0.3737060887154196, 0.2277858511416451, ... 0.07652652113349733]; endif dim1 = ones (size (dh, 1), 1); dim2 = ones (1, lg); hk = dh .* dk; bvn = dim1 * 0; phi_dh = 0.5 * erfc (dh / sqrt (2)); phi_dk = 0.5 * erfc (dk / sqrt (2)); if (abs (r) < 0.925) hs = (dh .* dh + dk .* dk) / 2; asr = asin (r); sn1 = sin (asr * (1 - x) / 2); sn2 = sin (asr * (1 + x) / 2); bvn = sum ((dim1 * w) .* exp (((dim1 * sn1) .* (hk * dim2) - ... hs * dim2) ./ (1 - dim1 * (sn1 .^ 2))) + ... (dim1 * w) .* exp (((dim1 * sn2) .* (hk * dim2) - ... hs * dim2) ./ (1 - dim1 * (sn2 .^ 2))), 2) * ... asr / (4 * pi) + phi_dh .* phi_dk; else twopi = 2 * pi; if r < 0 dk = -dk; hk = -hk; endif if abs (r) < 1 as = (1 - r) * (1 + r); a = sqrt (as); bs = (dh - dk) .^ 2; c = (4 - hk) / 8; d = (12 - hk) / 16; asr = - (bs ./ as + hk) / 2; ind = asr > -100; bvn(ind) = a * exp (asr(ind)) .* (1 - (c(ind) .* (bs(ind) - as)) ... .* (1 - d(ind) .* bs(ind) / 5) /3 ... + (c(ind) .* d(ind)) .* as .^ 2 / 5 ); ind = hk > -100; b = sqrt (bs); phi_ba = 0.5 * erfc ((b/a) / sqrt (2)); sp = sqrt (twopi) * phi_ba; bvn(ind) = bvn(ind) - (exp (-hk(ind) / 2) .* sp(ind)) ... .* b(ind) .* (1 - c(ind) .* bs(ind) ... .* (1 - d(ind) .* bs(ind) / 5) /3); a = a/2; for is = -1:2:1 xs = (a + a * is * x) .^ 2; rs = sqrt (1 - xs); asr1 = - ((bs * dim2) ./ (dim1 * xs) + hk * dim2) / 2; ind1 = (asr1 > -100); sp1 = (1 + (c * dim2) .* (dim1 * xs) .* ... (1 + (d * dim2) .* (dim1 * xs))); ep1 = exp (- (hk * dim2) .* (1 - dim1 * rs) ./ ... (2 * (1 + dim1 * rs))) ./ (dim1 * rs); bvn = bvn + sum (a .* (dim1 * w) .* exp (asr1 .* ind1) ... .* (ep1 .* ind1 - sp1 .* ind1), 2); endfor bvn = -bvn/twopi; endif if (r > 0) tmp = max (dh, dk); bvn = bvn + 0.5 * erfc (tmp / sqrt (2)); elseif (r < 0) phi_dh = 0.5 * erfc (dh / sqrt (2)); phi_dk = 0.5 * erfc (dk / sqrt (2)); bvn = - bvn + max (0, phi_dh - phi_dk); endif endif p = max (0, min (1, bvn)); endfunction %!demo %! mu = [1, -1]; %! sigma = [0.9, 0.4; 0.4, 0.3]; %! [X1, X2] = meshgrid (linspace (-1, 3, 25)', linspace (-3, 1, 25)'); %! x = [X1(:), X2(:)]; %! p = bvncdf (x, mu, sigma); %! Z = reshape (p, 25, 25); %! surf (X1, X2, Z); %! title ('Bivariate Normal Distribution'); %! ylabel 'X1' %! xlabel 'X2' ## Test output %!test %! mu = [1, -1]; %! sigma = [0.9, 0.4; 0.4, 0.3]; %! [X1,X2] = meshgrid (linspace (-1, 3, 25)', linspace (-3, 1, 25)'); %! x = [X1(:), X2(:)]; %! p = bvncdf (x, mu, sigma); %! p_out = [0.00011878988774500, 0.00034404112322371, ... %! 0.00087682502191813, 0.00195221905058185, ... %! 0.00378235566873474, 0.00638175749734415, ... %! 0.00943764224329656, 0.01239164888125426, ... %! 0.01472750274376648, 0.01623228313374828]'; %! assert_equal (p([1:10]), p_out, 1e-16); %!test %! mu = [1, -1]; %! sigma = [0.9, 0.4; 0.4, 0.3]; %! [X1,X2] = meshgrid (linspace (-1, 3, 25)', linspace (-3, 1, 25)'); %! x = [X1(:), X2(:)]; %! p = bvncdf (x, mu, sigma); %! p_out = [0.8180695783608276, 0.8854485749482751, ... %! 0.9308108777385832, 0.9579855743025508, ... %! 0.9722897881414742, 0.9788150170059926, ... %! 0.9813597788804785, 0.9821977956568989, ... %! 0.9824283794464095, 0.9824809345614861]'; %! assert_equal (p([616:625]), p_out, 3e-16); %!test %! ## Test infinite limits %! mu = [0, 0]; %! sigma = [1 0.5; 0.5 1]; %! assert_equal (bvncdf ([Inf, Inf], mu, sigma), 1); %! assert_equal (bvncdf ([-Inf, 2], mu, sigma), 0); %! assert_equal (bvncdf ([1, -Inf], mu, sigma), 0); %! assert_equal (bvncdf ([0.5, Inf], mu, sigma), normcdf (0.5), eps); %! assert_equal (bvncdf ([Inf, 0.5], mu, sigma), normcdf (0.5), eps); %!error bvncdf (int32 ([0, 0]), [0, 0], eye (2)) %!error bvncdf ([true, true], [0, 0], eye (2)) %!error bvncdf ('ab', [0, 0], eye (2)) %!error bvncdf (randn (25,3), [], [1, 1; 1, 1]); %!error bvncdf (randn (25,2), [], [1, 1; 1, 1]); %!error bvncdf (randn (25,2), [], ones (3, 2)); statistics-release-1.9.2/inst/Distribution_Functions/bvtcdf.m000066400000000000000000000160121524624707500244610ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} bvtcdf (@var{x}, @var{rho}, @var{df}) ## @deftypefnx {statistics} {@var{p} =} bvtcdf (@var{x}, @var{rho}, @var{df}, @var{Tol}) ## ## Bivariate Student's t cumulative distribution function (CDF). ## ## @code{@var{p} = bvtcdf (@var{x}, @var{rho}, @var{df})} will compute the ## bivariate student's t cumulative distribution function of @var{x}, which must ## be an @math{N*2} matrix, given a correlation coefficient @var{rho}, which ## must be a scalar, and @var{df} degrees of freedom, which can be a scalar or a ## vector of positive numbers commensurate with @var{x}. ## ## @var{Tol} is the tolerance for numerical integration and by default ## @code{@var{Tol} = 1e-8}. ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{mvtcdf} ## @end deftypefn function p = bvtcdf (x, rho, df, TolFun) narginchk (3,4); if (nargin < 4) TolFun = 1e-8; endif ## Check for X, RHO, and DF being double or single if (! (isfloat (x) && isfloat (rho) && isfloat (df))) error ("bvtcdf: X, RHO, and DF must be double or single."); endif if (isa (x, 'single') || isa (rho, 'single') || isa (df, 'single')) is_type = 'single'; else is_type = 'double'; endif if (abs (rho) < 1) largeNu = 1e4; general = ! (fix (df) == df & df < largeNu); if (isscalar (df)) if general p = generalDF (x, rho, repmat (df, size (x, 1), 1), TolFun); else p = integerDF (x, rho, df); endif else p = zeros (size (x, 1), 1, is_type); ## For large of non-integer df if (any (general)) p(general) = generalDF (x(general,:), rho, df(general), TolFun); endif ## For small integer df for i = find (! general(:)') p(i) = integerDF (x(i,:), rho, df(i)); endfor endif elseif (rho == 1) p = tcdf (min (x, [], 2), df); p(any (isnan ( x), 2)) = NaN; else p = tcdf (x(:,1), df) - tcdf (-x(:,2), df); endif endfunction ## CDF for the bivariate t with integer degrees of freedom function p = integerDF (x, rho, df) x1 = x(:,1); x2 = x(:,2); tau = 1 - rho .^ 2; x1rx2 = x1 - rho * x2; x2rx1 = x2 - rho * x1; sx1r2 = sign (x1rx2); sx2r1 = sign (x2rx1); dfx1s = df + x1 .^ 2; dfx2s = df + x2 .^ 2; tdfx1x2 = tau * dfx2s ./ x1rx2 .^ 2; tdfx2x1 = tau * dfx1s ./ x2rx1 .^ 2; x_tdf12 = 1 ./ (1 + tdfx1x2); x_tdf21 = 1 ./ (1 + tdfx2x1); y_tdf12 = 1 ./ (1 + 1 ./ tdfx1x2); y_tdf21 = 1 ./ (1 + 1 ./ tdfx2x1); sqrtDF = sqrt (df); halfDF = df/2; if (fix (halfDF) == halfDF) # for even DF p1 = atan2 (sqrt (tau), -rho) ./ (2 * pi); c1 = x1 ./ (4 * sqrt (dfx1s)); c2 = x2 ./ (4 * sqrt (dfx2s)); beta12 = 2 *atan2 (sqrt (x_tdf12), sqrt (y_tdf12)) / pi; beta21 = 2 *atan2 (sqrt (x_tdf21), sqrt (y_tdf21)) / pi; p2 = (1 + sx1r2 .* beta12) .* c2 + (1 + sx2r1 .* beta21) .* c1; betaT12 = 2 * sqrt (x_tdf12 .* y_tdf12) / pi; betaT21 = 2 * sqrt (x_tdf21 .* y_tdf21) / pi; for j = 2:halfDF fact = df * (j - 1.5) / (j - 1); c2 = c2 .* fact ./ dfx2s; c1 = c1 .* fact ./ dfx1s; beta12 = beta12 + betaT12; beta21 = beta21 + betaT21; p2 = p2 + (1 + sx1r2 .* beta12) .* c2 + (1 + sx2r1 .* beta21) .* c1; fact = 2 * (j - 1) / (2 * (j - 1) + 1); betaT12 = fact * betaT12 .* y_tdf12; betaT21 = fact * betaT21 .* y_tdf21; endfor else # for odd DF x1x2p = x1.*x2; x1x2s = x1 + x2; t1 = sqrt (x1 .^ 2 - 2 * rho * x1x2p + x2 .^ 2 + tau * df); t2 = x1x2p + rho * df; t3 = x1x2p - df; p1 = atan2 (sqrtDF .* (-x1x2s .* t2 - t3 .* t1), ... t3 .* t2 - df .* x1x2s .* t1) ./ (2 * pi); p1 = p1 + (p1 < 0); p2 = 0; if (df > 1) c1 = sqrtDF .* x1 ./ (2 * pi .* dfx1s); c2 = sqrtDF .* x2 ./ (2 * pi .* dfx2s); betaT12 = sqrt (x_tdf12); betaT21 = sqrt (x_tdf21); beta12 = betaT12; beta21 = betaT21; p2 = (1 + sx1r2 .* beta12) .* c2 + (1 + sx2r1 .* beta21) .* c1; for j = 2:(halfDF - 0.5) fact = df * (j - 1) / (j - 0.5); c2 = fact * c2 ./ dfx2s; c1 = fact * c1 ./ dfx1s; fact = 1 - 0.5 / (j - 1); betaT12 = fact * betaT12 .* y_tdf12; betaT21 = fact * betaT21 .* y_tdf21; beta12 = beta12 + betaT12; beta21 = beta21 + betaT21; p2 = p2 + (1 + sx1r2 .* beta12) .* c2 + (1 + sx2r1 .* beta21) .* c1; endfor endif endif p = p1 + p2; ## Fix limit cases large = 1e10; p(x1 < -large | x2 < -large) = 0; p(x1 > large) = tcdf (x2(x1 > large), df); p(x2 > large) = tcdf (x1(x2 > large), df); endfunction ## CDF for the bivariate t with arbitrary degrees of freedom. function p = generalDF (x, rho, df, TolFun) n = size (x, 1); if (rho >= 0) p1 = tcdf (min (x, [], 2), df); p1(any (isnan (x), 2)) = NaN; else p1 = tcdf (x(:,1), df) - tcdf (-x(:,2), df); p1(p1 < 0) = 0; endif lo = asin (rho); hi = (sign (rho) + (rho == 0)) .* pi ./ 2; p2 = zeros (size (p1), class (rho)); for i = 1:n b1 = x(i,1); b2 = x(i,2); v = df(i); if (isfinite (b1) && isfinite (b2)) p2(i) = quadgk (@bvtIntegrand, lo, hi, 'AbsTol', TolFun, 'RelTol', 0); endif endfor p = p1 - p2 ./ (2 .* pi); function integrand = bvtIntegrand (theta) st = sin (theta); ct2 = cos (theta).^2; integrand = (1 ./ (1 + ((b1 * st - b2) .^ 2 ./ ct2 + b1 .^ 2) / v)) ... .^ (v / 2); endfunction endfunction ## Test output %!test %! x = [1, 2]; %! rho = [1, 0.5; 0.5, 1]; %! df = 4; %! assert_equal (bvtcdf (x, rho(2), df), mvtcdf (x, rho, df), 1e-14); %!test %! x = [3, 2;2, 4;1, 5]; %! rho = [1, 0.5; 0.5, 1]; %! df = 4; %! assert_equal (bvtcdf (x, rho(2), df), mvtcdf (x, rho, df), 1e-14); ## Test input validation %!error bvtcdf (int32 ([0, 0]), 0.5, 5) %!error bvtcdf ([true, true], 0.5, 5) %!error bvtcdf ('ab', 0.5, 5) statistics-release-1.9.2/inst/Distribution_Functions/cauchycdf.m000066400000000000000000000147661524624707500251600ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} cauchycdf (@var{x}, @var{x0}, @var{gamma}) ## @deftypefnx {statistics} {@var{p} =} cauchycdf (@var{x}, @var{x0}, @var{gamma}, @qcode{'upper'}) ## ## Cauchy cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the Cauchy distribution with location parameter @var{x0} and scale ## parameter @var{gamma}. The size of @var{p} is the common size of @var{x}, ## @var{x0}, and @var{gamma}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## @code{@var{p} = cauchycdf (@var{x}, @var{x0}, @var{gamma}, "upper")} computes ## the upper tail probability of the Cauchy distribution with parameters ## @var{x0} and @var{gamma}, at the values in @var{x}. ## ## Further information about the Cauchy distribution can be found at ## @url{https://en.wikipedia.org/wiki/Cauchy_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{cauchyinv, cauchypdf, cauchyrnd} ## @end deftypefn function p = cauchycdf (x, x0, gamma, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("cauchycdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 3) if (! strcmpi (uflag, 'upper')) error ("cauchycdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, X0, and GAMMA if (! isscalar (x) || ! isscalar (x0) || ! isscalar (gamma)) [retval, x, x0, gamma] = common_size (x, x0, gamma); if (retval > 0) error (strcat ("cauchycdf: X, X0, and GAMMA must be of", ... " common size or scalars.")); endif endif ## Check for X, X0, and GAMMA being double or single if (! (isfloat (x) && isfloat (x0) && isfloat (gamma))) error ("cauchycdf: X, X0, and GAMMA must be double or single."); endif ## Check for X, X0, and GAMMA being reals if (iscomplex (x) || iscomplex (x0) || iscomplex (gamma)) error ("cauchycdf: X, X0, and GAMMA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (x0, 'single') || isa (gamma, 'single')); p = NaN (size (x), 'single'); else p = NaN (size (x)); endif ## Find valid values in parameters and data k = ! isinf (x0) & (gamma > 0) & (gamma < Inf); ## Compute Cauchy CDF if (isscalar (x0) && isscalar (gamma)) if (uflag) p = 0.5 + atan ((-x(k) + x0) / gamma) / pi; else p = 0.5 + atan ((x(k) - x0) / gamma) / pi; endif else if (uflag) p(k) = 0.5 + atan ((-x(k) + x0(k)) ./ gamma(k)) / pi; else p(k) = 0.5 + atan ((x(k) - x0(k)) ./ gamma(k)) / pi; endif endif endfunction %!demo %! ## Plot various CDFs from the Cauchy distribution %! x = -5:0.01:5; %! p1 = cauchycdf (x, 0, 0.5); %! p2 = cauchycdf (x, 0, 1); %! p3 = cauchycdf (x, 0, 2); %! p4 = cauchycdf (x, -2, 1); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c') %! grid on %! xlim ([-5, 5]) %! legend ({'x0 = 0, γ = 0.5', 'x0 = 0, γ = 1', ... %! 'x0 = 0, γ = 2', 'x0 = -2, γ = 1'}, 'location', 'southeast') %! title ('Cauchy CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-1 0 0.5 1 2]; %! y = 1/pi * atan ((x-1) / 2) + 1/2; %!assert_equal (cauchycdf (x, ones (1,5), 2*ones (1,5)), y) %!assert_equal (cauchycdf (x, 1, 2*ones (1,5)), y) %!assert_equal (cauchycdf (x, ones (1,5), 2), y) %!assert_equal (cauchycdf (x, [-Inf 1 NaN 1 Inf], 2), [NaN y(2) NaN y(4) NaN]) %!assert_equal (cauchycdf (x, 1, 2*[0 1 NaN 1 Inf]), [NaN y(2) NaN y(4) NaN]) %!assert_equal (cauchycdf ([x(1:2) NaN x(4:5)], 1, 2), [y(1:2) NaN y(4:5)]) %!assert_equal (cauchycdf ([x, NaN], 1, 2), [y, NaN]) ## Test class of input preserved %!assert_equal (cauchycdf (single ([x, NaN]), 1, 2), single ([y, NaN]), eps ('single')) %!assert_equal (cauchycdf ([x, NaN], single (1), 2), single ([y, NaN]), eps ('single')) %!assert_equal (cauchycdf ([x, NaN], 1, single (2)), single ([y, NaN]), eps ('single')) ## Test input validation %!error cauchycdf () %!error cauchycdf (1) %!error ... %! cauchycdf (1, 2) %!error ... %! cauchycdf (1, 2, 3, 4, 5) %!error cauchycdf (1, 2, 3, 'tail') %!error cauchycdf (1, 2, 3, 4) %!error ... %! cauchycdf (ones (3), ones (2), ones (2)) %!error ... %! cauchycdf (ones (2), ones (3), ones (2)) %!error ... %! cauchycdf (ones (2), ones (2), ones (3)) %!error cauchycdf (int32 (2), 2, 2) %!error cauchycdf (true, 2, 2) %!error cauchycdf ('a', 2, 2) %!error cauchycdf (i, 2, 2) %!error cauchycdf (2, i, 2) %!error cauchycdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/cauchyinv.m000066400000000000000000000136021524624707500252040ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} cauchyinv (@var{p}, @var{x0}, @var{gamma}) ## ## Inverse of the Cauchy cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the Cauchy distribution with location parameter @var{x0} and scale parameter ## @var{gamma}. The size of @var{x} is the common size of @var{p}, @var{x0}, ## and @var{gamma}. A scalar input functions as a constant matrix of the same ## size as the other inputs. ## ## Further information about the Cauchy distribution can be found at ## @url{https://en.wikipedia.org/wiki/Cauchy_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{cauchycdf, cauchypdf, cauchyrnd} ## @end deftypefn function x = cauchyinv (p, x0, gamma) ## Check for valid number of input arguments if (nargin < 3) error ("cauchyinv: function called with too few input arguments."); endif ## Check for common size of P, X0, and GAMMA if (! isscalar (p) || ! isscalar (x0) || ! isscalar (gamma)) [retval, p, x0, gamma] = common_size (p, x0, gamma); if (retval > 0) error (strcat ("cauchyinv: P, X0, and GAMMA must be of", ... " common size or scalars.")); endif endif ## Check for P, X0, and GAMMA being double or single if (! (isfloat (p) && isfloat (x0) && isfloat (gamma))) error ("cauchyinv: P, X0, and GAMMA must be double or single."); endif ## Check for P, X0, and GAMMA being reals if (iscomplex (p) || iscomplex (x0) || iscomplex (gamma)) error ("cauchyinv: P, X0, and GAMMA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (x0, 'single') || isa (gamma, 'single')) x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ## Find valid values in parameters ok = ! isinf (x0) & (gamma > 0) & (gamma < Inf); ## Handle edge cases k0 = (p == 0) & ok; x(k0) = -Inf; k1 = (p == 1) & ok; x(k1) = Inf; ## Handle all other valid cases k = (p > 0) & (p < 1) & ok; if (isscalar (x0) && isscalar (gamma)) x(k) = x0 - gamma * cot (pi * p(k)); else x(k) = x0(k) - gamma(k) .* cot (pi * p(k)); endif endfunction %!demo %! ## Plot various iCDFs from the Cauchy distribution %! p = 0.001:0.001:0.999; %! x1 = cauchyinv (p, 0, 0.5); %! x2 = cauchyinv (p, 0, 1); %! x3 = cauchyinv (p, 0, 2); %! x4 = cauchyinv (p, -2, 1); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c') %! grid on %! ylim ([-5, 5]) %! legend ({'x0 = 0, γ = 0.5', 'x0 = 0, γ = 1', ... %! 'x0 = 0, γ = 2', 'x0 = -2, γ = 1'}, 'location', 'northwest') %! title ('Cauchy iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p %! p = [-1 0 0.5 1 2]; %!assert_equal (cauchyinv (p, ones (1,5), 2 * ones (1,5)), [NaN -Inf 1 Inf NaN], eps) %!assert_equal (cauchyinv (p, 1, 2 * ones (1,5)), [NaN -Inf 1 Inf NaN], eps) %!assert_equal (cauchyinv (p, ones (1,5), 2), [NaN -Inf 1 Inf NaN], eps) %!assert_equal (cauchyinv (p, [1 -Inf NaN Inf 1], 2), [NaN NaN NaN NaN NaN]) %!assert_equal (cauchyinv (p, 1, 2 * [1 0 NaN Inf 1]), [NaN NaN NaN NaN NaN]) %!assert_equal (cauchyinv ([p(1:2) NaN p(4:5)], 1, 2), [NaN -Inf NaN Inf NaN]) %!assert_equal (cauchyinv ([p, NaN], 1, 2), [NaN -Inf 1 Inf NaN NaN], eps) ## Test class of input preserved %!assert_equal (cauchyinv (single ([p, NaN]), 1, 2), ... %! single ([NaN -Inf 1 Inf NaN NaN]), eps ('single')) %!assert_equal (cauchyinv ([p, NaN], single (1), 2), ... %! single ([NaN -Inf 1 Inf NaN NaN]), eps ('single')) %!assert_equal (cauchyinv ([p, NaN], 1, single (2)), ... %! single ([NaN -Inf 1 Inf NaN NaN]), eps ('single')) ## Test input validation %!error cauchyinv () %!error cauchyinv (1) %!error ... %! cauchyinv (1, 2) %!error cauchyinv (1, 2, 3, 4) %!error ... %! cauchyinv (ones (3), ones (2), ones (2)) %!error ... %! cauchyinv (ones (2), ones (3), ones (2)) %!error ... %! cauchyinv (ones (2), ones (2), ones (3)) %!error cauchyinv (int32 (2), 4, 3) %!error cauchyinv (true, 4, 3) %!error cauchyinv ('a', 4, 3) %!error cauchyinv (i, 4, 3) %!error cauchyinv (1, i, 3) %!error cauchyinv (1, 4, i) statistics-release-1.9.2/inst/Distribution_Functions/cauchypdf.m000066400000000000000000000133151524624707500251620ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} cauchypdf (@var{x}, @var{x0}, @var{gamma}) ## ## Cauchy probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the Cauchy distribution with location parameter @var{x0} and scale ## parameter @var{gamma}. The size of @var{y} is the common size of @var{x}, ## @var{x0}, and @var{gamma}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Further information about the Cauchy distribution can be found at ## @url{https://en.wikipedia.org/wiki/Cauchy_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{cauchycdf, cauchyinv, cauchyrnd} ## @end deftypefn function y = cauchypdf (x, x0, gamma) ## Check for valid number of input arguments if (nargin < 3) error ("cauchypdf: function called with too few input arguments."); endif ## Check for common size of X, X0, and GAMMA if (! isscalar (x) || ! isscalar (x0) || ! isscalar (gamma)) [retval, x, x0, gamma] = common_size (x, x0, gamma); if (retval > 0) error (strcat ("cauchypdf: X, X0, and GAMMA must be of", ... " common size or scalars.")); endif endif ## Check for X, X0, and GAMMA being double or single if (! (isfloat (x) && isfloat (x0) && isfloat (gamma))) error ("cauchypdf: X, X0, and GAMMA must be double or single."); endif ## Check for X, X0, and GAMMA being reals if (iscomplex (x) || iscomplex (x0) || iscomplex (gamma)) error ("cauchypdf: X, X0, and GAMMA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (x0, 'single') || isa (gamma, 'single')) y = NaN (size (x), 'single'); else y = NaN (size (x)); endif ## Find valid values in parameters k = ! isinf (x0) & (gamma > 0) & (gamma < Inf); if (isscalar (x0) && isscalar (gamma)) y(k) = ((1 ./ (1 + ((x(k) - x0) / gamma) .^ 2)) / pi / gamma); else y(k) = ((1 ./ (1 + ((x(k) - x0(k)) ./ gamma(k)) .^ 2)) / pi ./ gamma(k)); endif endfunction %!demo %! ## Plot various PDFs from the Cauchy distribution %! x = -5:0.01:5; %! y1 = cauchypdf (x, 0, 0.5); %! y2 = cauchypdf (x, 0, 1); %! y3 = cauchypdf (x, 0, 2); %! y4 = cauchypdf (x, -2, 1); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c') %! grid on %! xlim ([-5, 5]) %! ylim ([0, 0.7]) %! legend ({'x0 = 0, γ = 0.5', 'x0 = 0, γ = 1', ... %! 'x0 = 0, γ = 2', 'x0 = -2, γ = 1'}, 'location', 'northeast') %! title ('Cauchy PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1 0 0.5 1 2]; %! y = 1/pi * ( 2 ./ ((x-1).^2 + 2^2) ); %!assert_equal (cauchypdf (x, ones (1,5), 2*ones (1,5)), y) %!assert_equal (cauchypdf (x, 1, 2*ones (1,5)), y) %!assert_equal (cauchypdf (x, ones (1,5), 2), y) %!assert_equal (cauchypdf (x, [-Inf 1 NaN 1 Inf], 2), [NaN y(2) NaN y(4) NaN]) %!assert_equal (cauchypdf (x, 1, 2*[0 1 NaN 1 Inf]), [NaN y(2) NaN y(4) NaN]) %!assert_equal (cauchypdf ([x, NaN], 1, 2), [y, NaN]) ## Test class of input preserved %!assert_equal (cauchypdf (single ([x, NaN]), 1, 2), single ([y, NaN]), eps ('single')) %!assert_equal (cauchypdf ([x, NaN], single (1), 2), single ([y, NaN]), eps ('single')) %!assert_equal (cauchypdf ([x, NaN], 1, single (2)), single ([y, NaN]), eps ('single')) ## Cauchy (0,1) == Student's T distribution with 1 DOF %!test %! x = rand (10, 1); %! assert_equal (cauchypdf (x, 0, 1), tpdf (x, 1), eps); ## Test input validation %!error cauchypdf () %!error cauchypdf (1) %!error ... %! cauchypdf (1, 2) %!error cauchypdf (1, 2, 3, 4) %!error ... %! cauchypdf (ones (3), ones (2), ones (2)) %!error ... %! cauchypdf (ones (2), ones (3), ones (2)) %!error ... %! cauchypdf (ones (2), ones (2), ones (3)) %!error cauchypdf (int32 (2), 4, 3) %!error cauchypdf (true, 4, 3) %!error cauchypdf ('a', 4, 3) %!error cauchypdf (i, 4, 3) %!error cauchypdf (1, i, 3) %!error cauchypdf (1, 4, i) statistics-release-1.9.2/inst/Distribution_Functions/cauchyrnd.m000066400000000000000000000156011524624707500251740ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} cauchyrnd (@var{x0}, @var{gamma}) ## @deftypefnx {statistics} {@var{r} =} cauchyrnd (@var{x0}, @var{gamma}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} cauchyrnd (@var{x0}, @var{gamma}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} cauchyrnd (@var{x0}, @var{gamma}, [@var{sz}]) ## ## Random arrays from the Cauchy distribution. ## ## @code{@var{r} = cauchyrnd (@var{x0}, @var{gamma})} returns an array of ## random numbers chosen from the Cauchy distribution with location parameter ## @var{x0} and scale parameter @var{gamma}. The size of @var{r} is the common ## size of @var{x0} and @var{gamma}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## When called with a single size argument, @code{cauchyrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the Cauchy distribution can be found at ## @url{https://en.wikipedia.org/wiki/Cauchy_distribution} ## ## @seealso{cauchycdf, cauchyinv, cauchypdf} ## @end deftypefn function r = cauchyrnd (x0, gamma, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("cauchyrnd: function called with too few input arguments."); endif ## Check for common size of X0 and GAMMA if (! isscalar (x0) || ! isscalar (gamma)) [retval, x0, gamma] = common_size (x0, gamma); if (retval > 0) error ("cauchyrnd: X0 and GAMMA must be of common size or scalars."); endif endif ## Check for X0 and GAMMA being reals if (iscomplex (x0) || iscomplex (gamma)) error ("cauchyrnd: X0 and GAMMA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (x0); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("cauchyrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("cauchyrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (x0) && ! isequal (size (x0), size (ones (sz)))) error ("cauchyrnd: X0 and GAMMA must be scalars or of size SZ."); endif ## Check for class type if (isa (x0, 'single') || isa (gamma, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from Cauchy distribution if (isscalar (x0) && isscalar (gamma)) if (! isinf (x0) && (gamma > 0) && (gamma < Inf)) r = x0 - cot (pi * rand (sz, cls)) * gamma; else r = NaN (sz, cls); endif else r = NaN (sz, cls); k = ! isinf (x0) & (gamma > 0) & (gamma < Inf); r(k) = x0(k)(:) - cot (pi * rand (sum (k(:)), 1, cls)) .* gamma(k)(:); endif endfunction ## Test output %!assert_equal (size (cauchyrnd (1, 1)), [1 1]) %!assert_equal (size (cauchyrnd (1, ones (2,1))), [2, 1]) %!assert_equal (size (cauchyrnd (1, ones (2,2))), [2, 2]) %!assert_equal (size (cauchyrnd (ones (2,1), 1)), [2, 1]) %!assert_equal (size (cauchyrnd (ones (2,2), 1)), [2, 2]) %!assert_equal (size (cauchyrnd (1, 1, 3)), [3, 3]) %!assert_equal (size (cauchyrnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (cauchyrnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (cauchyrnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (cauchyrnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (cauchyrnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (cauchyrnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (cauchyrnd (1, 1, [])), [0, 0]) %!assert_equal (size (cauchyrnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (cauchyrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (cauchyrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (cauchyrnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (cauchyrnd (1, 1)), "double") %!assert_equal (class (cauchyrnd (1, single (1))), "single") %!assert_equal (class (cauchyrnd (1, single ([1, 1]))), "single") %!assert_equal (class (cauchyrnd (single (1), 1)), "single") %!assert_equal (class (cauchyrnd (single ([1, 1]), 1)), "single") ## Test input validation %!error cauchyrnd () %!error cauchyrnd (1) %!error ... %! cauchyrnd (ones (3), ones (2)) %!error ... %! cauchyrnd (ones (2), ones (3)) %!error cauchyrnd (i, 2, 3) %!error cauchyrnd (1, i, 3) %!error ... %! cauchyrnd (1, 2, 1.2) %!error ... %! cauchyrnd (1, 2, ones (2)) %!error ... %! cauchyrnd (1, 2, [2 0 2.5]) %!error ... %! cauchyrnd (1, 2, 2, 1.5, 5) %!error ... %! cauchyrnd (2, ones (2), 3) %!error ... %! cauchyrnd (2, ones (2), [3, 2]) %!error ... %! cauchyrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/chi2cdf.m000066400000000000000000000125261524624707500245210ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} chi2cdf (@var{x}, @var{df}) ## @deftypefnx {statistics} {@var{p} =} chi2cdf (@var{x}, @var{df}, @qcode{'upper'}) ## ## Chi-squared cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the chi-squared distribution with @var{df} degrees of freedom. The ## chi-squared density function with @var{df} degrees of freedom is the same as ## a gamma density function with parameters @qcode{@var{df}/2} and @qcode{2}. ## ## The size of @var{p} is the common size of @var{x} and @var{df}. A scalar ## input functions as a constant matrix of the same size as the other input. ## ## @code{@var{p} = chi2cdf (@var{x}, @var{df}, "upper")} computes the upper tail ## probability of the chi-squared distribution with @var{df} degrees of freedom, ## at the values in @var{x}. ## ## Further information about the chi-squared distribution can be found at ## @url{https://en.wikipedia.org/wiki/Chi-squared_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{chi2inv, chi2pdf, chi2rnd, chi2stat} ## @end deftypefn function p = chi2cdf (x, df, uflag) ## Check for valid number of input arguments if (nargin < 2) error ("chi2cdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 2) if (! strcmpi (uflag, 'upper')) error ("chi2cdf: invalid argument for upper tail."); endif else uflag = []; endif ## Check for common size of X and DF if (! isscalar (x) || ! isscalar (df)) [err, x, df] = common_size (x, df); if (err > 0) error ("chi2cdf: X and DF must be of common size or scalars."); endif endif ## Check for X and DF being double or single if (! (isfloat (x) && isfloat (df))) error ("chi2cdf: X and DF must be double or single."); endif ## Check for X and DF being reals if (iscomplex (x) || iscomplex (df)) error ("chi2cdf: X and DF must not be complex."); endif ## Compute chi-squared CDF p = gamcdf (x, df/2, 2, uflag); endfunction %!demo %! ## Plot various CDFs from the chi-squared distribution %! x = 0:0.01:8; %! p1 = chi2cdf (x, 1); %! p2 = chi2cdf (x, 2); %! p3 = chi2cdf (x, 3); %! p4 = chi2cdf (x, 4); %! p5 = chi2cdf (x, 6); %! p6 = chi2cdf (x, 9); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', ... %! x, p4, '-c', x, p5, '-m', x, p6, '-y') %! grid on %! xlim ([0, 8]) %! legend ({'df = 1', 'df = 2', 'df = 3', ... %! 'df = 4', 'df = 6', 'df = 9'}, 'location', 'southeast') %! title ('Chi-squared CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, p, u %! x = [-1, 0, 0.5, 1, 2]; %! p = [0, (1 - exp (-x(2:end) / 2))]; %! u = [1, 0, NaN, 0.606530659712633, 0.367879441171442]; %!assert_equal (chi2cdf (x, 2 * ones (1,5)), p, eps) %!assert_equal (chi2cdf (x, 2), p, eps) %!assert_equal (chi2cdf (x, 2 * [1, 0, NaN, 1, 1]), [0, 1, NaN, p(4:5)], eps) %!assert_equal (chi2cdf (x, 2 * [1, 0, NaN, 1, 1], 'upper'), u, 3 * eps) %!assert_equal (chi2cdf ([x(1:2), NaN, x(4:5)], 2), [p(1:2), NaN, p(4:5)], eps) ## Test class of input preserved %!assert_equal (chi2cdf ([x, NaN], 2), [p, NaN], eps) %!assert_equal (chi2cdf (single ([x, NaN]), 2), single ([p, NaN]), eps ('single')) %!assert_equal (chi2cdf ([x, NaN], single (2)), single ([p, NaN]), eps ('single')) ## Test input validation %!error chi2cdf () %!error chi2cdf (1) %!error chi2cdf (1, 2, 3, 4) %!error chi2cdf (1, 2, 3) %!error chi2cdf (1, 2, 'uper') %!error ... %! chi2cdf (ones (3), ones (2)) %!error ... %! chi2cdf (ones (2), ones (3)) %!error chi2cdf (int32 (2), 2) %!error chi2cdf (true, 2) %!error chi2cdf ('a', 2) %!error chi2cdf (i, 2) %!error chi2cdf (2, i) statistics-release-1.9.2/inst/Distribution_Functions/chi2inv.m000066400000000000000000000107711524624707500245610ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} chi2inv (@var{p}, @var{df}) ## ## Inverse of the chi-squared cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the chi-squared distribution with @var{df} degrees of freedom. The size of ## @var{x} is the common size of @var{p} and @var{df}. A scalar input functions ## as a constant matrix of the same size as the other inputs. ## ## Further information about the chi-squared distribution can be found at ## @url{https://en.wikipedia.org/wiki/Chi-squared_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{chi2cdf, chi2pdf, chi2rnd, chi2stat} ## @end deftypefn function x = chi2inv (p, df) ## Check for valid number of input arguments if (nargin < 2) error ("chi2inv: function called with too few input arguments."); endif ## Check for common size of P and DF if (! isscalar (p) || ! isscalar (df)) [retval, p, df] = common_size (p, df); if (retval > 0) error ("chi2inv: P and DF must be of common size or scalars."); endif endif ## Check for P and DF being double or single if (! (isfloat (p) && isfloat (df))) error ("chi2inv: P and DF must be double or single."); endif ## Check for P and DF being reals if (iscomplex (p) || iscomplex (df)) error ("chi2inv: P and DF must not be complex."); endif ## Compute chi-squared iCDF x = gaminv (p, df/2, 2); endfunction %!demo %! ## Plot various iCDFs from the chi-squared distribution %! p = 0.001:0.001:0.999; %! x1 = chi2inv (p, 1); %! x2 = chi2inv (p, 2); %! x3 = chi2inv (p, 3); %! x4 = chi2inv (p, 4); %! x5 = chi2inv (p, 6); %! x6 = chi2inv (p, 9); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', ... %! p, x4, '-c', p, x5, '-m', p, x6, '-y') %! grid on %! ylim ([0, 8]) %! legend ({'df = 1', 'df = 2', 'df = 3', ... %! 'df = 4', 'df = 6', 'df = 9'}, 'location', 'northwest') %! title ('Chi-squared iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p %! p = [-1 0 0.3934693402873666 1 2]; %!assert_equal (chi2inv (p, 2*ones (1,5)), [NaN 0 1 Inf NaN], 5*eps) %!assert_equal (chi2inv (p, 2), [NaN 0 1 Inf NaN], 5*eps) %!assert_equal (chi2inv (p, 2*[0 1 NaN 1 1]), [NaN 0 NaN Inf NaN], 5*eps) %!assert_equal (chi2inv ([p(1:2) NaN p(4:5)], 2), [NaN 0 NaN Inf NaN], 5*eps) ## Test class of input preserved %!assert_equal (chi2inv ([p, NaN], 2), [NaN 0 1 Inf NaN NaN], 5*eps) %!assert_equal (chi2inv (single ([p, NaN]), 2), single ([NaN 0 1 Inf NaN NaN]), 5*eps ('single')) %!assert_equal (chi2inv ([p, NaN], single (2)), single ([NaN 0 1 Inf NaN NaN]), 5*eps ('single')) ## Test input validation %!error chi2inv () %!error chi2inv (1) %!error chi2inv (1,2,3) %!error ... %! chi2inv (ones (3), ones (2)) %!error ... %! chi2inv (ones (2), ones (3)) %!error chi2inv (int32 (2), 2) %!error chi2inv (true, 2) %!error chi2inv ('a', 2) %!error chi2inv (i, 2) %!error chi2inv (2, i) statistics-release-1.9.2/inst/Distribution_Functions/chi2pdf.m000066400000000000000000000105641524624707500245360ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} chi2pdf (@var{x}, @var{df}) ## ## Chi-squared probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the chi-squared distribution with @var{df} degrees of freedom. The size ## of @var{y} is the common size of @var{x} and @var{df}. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## Further information about the chi-squared distribution can be found at ## @url{https://en.wikipedia.org/wiki/Chi-squared_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{chi2cdf, chi2inv, chi2rnd, chi2stat} ## @end deftypefn function y = chi2pdf (x, df) ## Check for valid number of input arguments if (nargin < 2) error ("chi2pdf: function called with too few input arguments."); endif ## Check for common size of X and DF if (! isscalar (x) || ! isscalar (df)) [retval, x, df] = common_size (x, df); if (retval > 0) error ("chi2pdf: X and DF must be of common size or scalars."); endif endif ## Check for X and DF being double or single if (! (isfloat (x) && isfloat (df))) error ("chi2pdf: X and DF must be double or single."); endif ## Check for X and DF being reals if (iscomplex (x) || iscomplex (df)) error ("chi2pdf: X and DF must not be complex."); endif ## Compute chi-squared PDF y = gampdf (x, df/2, 2); endfunction %!demo %! ## Plot various PDFs from the chi-squared distribution %! x = 0:0.01:8; %! y1 = chi2pdf (x, 1); %! y2 = chi2pdf (x, 2); %! y3 = chi2pdf (x, 3); %! y4 = chi2pdf (x, 4); %! y5 = chi2pdf (x, 6); %! y6 = chi2pdf (x, 9); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', ... %! x, y4, '-c', x, y5, '-m', x, y6, '-y') %! grid on %! xlim ([0, 8]) %! ylim ([0, 0.5]) %! legend ({'df = 1', 'df = 2', 'df = 3', ... %! 'df = 4', 'df = 6', 'df = 9'}, 'location', 'northeast') %! title ('Chi-squared PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1 0 0.5 1 Inf]; %! y = [0, 1/2 * exp(-x(2:5)/2)]; %!assert_equal (chi2pdf (x, 2*ones (1,5)), y) %!assert_equal (chi2pdf (x, 2), y) %!assert_equal (chi2pdf (x, 2*[1 0 NaN 1 1]), [y(1) NaN NaN y(4:5)]) %!assert_equal (chi2pdf ([x, NaN], 2), [y, NaN]) ## Test for issue #203 (Github) %!assert_equal (chi2pdf (2, Inf), 0) ## Test class of input preserved %!assert_equal (chi2pdf (single ([x, NaN]), 2), single ([y, NaN])) %!assert_equal (chi2pdf ([x, NaN], single (2)), single ([y, NaN])) ## Test input validation %!error chi2pdf () %!error chi2pdf (1) %!error chi2pdf (1,2,3) %!error ... %! chi2pdf (ones (3), ones (2)) %!error ... %! chi2pdf (ones (2), ones (3)) %!error chi2pdf (int32 (2), 2) %!error chi2pdf (true, 2) %!error chi2pdf ('a', 2) %!error chi2pdf (i, 2) %!error chi2pdf (2, i) statistics-release-1.9.2/inst/Distribution_Functions/chi2rnd.m000066400000000000000000000131221524624707500245410ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} chi2rnd (@var{df}) ## @deftypefnx {statistics} {@var{r} =} chi2rnd (@var{df}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} chi2rnd (@var{df}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} chi2rnd (@var{df}, [@var{sz}]) ## ## Random arrays from the chi-squared distribution. ## ## @code{@var{r} = chi2rnd (@var{df})} returns an array of random numbers chosen ## from the chi-squared distribution with @var{df} degrees of freedom. The size ## of @var{r} is the size of @var{df}. ## ## When called with a single size argument, @code{chi2rnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the chi-squared distribution can be found at ## @url{https://en.wikipedia.org/wiki/Chi-squared_distribution} ## ## @seealso{chi2cdf, chi2inv, chi2pdf, chi2stat} ## @end deftypefn function r = chi2rnd (df, varargin) ## Check for valid number of input arguments if (nargin < 1) error ("chi2rnd: function called with too few input arguments."); endif ## Check for DF being reals if (iscomplex (df)) error ("chi2rnd: DF must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 1) sz = size (df); elseif (nargin == 2) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("chi2rnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 2) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("chi2rnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameter match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (df) && ! isequal (size (df), size (ones (sz)))) error ("chi2rnd: DF must be scalar or of size SZ."); endif ## Check for class type if (isa (df, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from chi-squared distribution if (isscalar (df)) if ((df > 0) && (df < Inf)) r = 2 * randg (df/2, sz, cls); else r = NaN (sz, cls); endif else r = NaN (sz, cls); k = (df > 0) | (df < Inf); r(k) = 2 * randg (df(k)/2, cls); endif endfunction ## Test output %!assert_equal (size (chi2rnd (2)), [1, 1]) %!assert_equal (size (chi2rnd (ones (2,1))), [2, 1]) %!assert_equal (size (chi2rnd (ones (2,2))), [2, 2]) %!assert_equal (size (chi2rnd (1, 3)), [3, 3]) %!assert_equal (size (chi2rnd (1, [4, 1])), [4, 1]) %!assert_equal (size (chi2rnd (1, 4, 1)), [4, 1]) %!assert_equal (size (chi2rnd (1, 4, 1)), [4, 1]) %!assert_equal (size (chi2rnd (1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (chi2rnd (1, 0, 1)), [0, 1]) %!assert_equal (size (chi2rnd (1, 1, 0)), [1, 0]) %!assert_equal (size (chi2rnd (1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (chi2rnd (1, [])), [0, 0]) %!assert_equal (size (chi2rnd (1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (chi2rnd (1, -1)), [0, 0]) %!assert_equal (size (chi2rnd (1, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (chi2rnd (1, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (chi2rnd (2)), "double") %!assert_equal (class (chi2rnd (single (2))), "single") %!assert_equal (class (chi2rnd (single ([2 2]))), "single") ## Test input validation %!error chi2rnd () %!error chi2rnd (i) %!error ... %! chi2rnd (1, 1.2) %!error ... %! chi2rnd (1, ones (2)) %!error ... %! chi2rnd (1, [2 0 2.5]) %!error ... %! chi2rnd (ones (2), ones (2)) %!error ... %! chi2rnd (1, 2, 1.5, 5) %!error chi2rnd (ones (2,2), 3) %!error chi2rnd (ones (2,2), [3, 2]) %!error chi2rnd (ones (2,2), 2, 3) statistics-release-1.9.2/inst/Distribution_Functions/copulacdf.m000066400000000000000000000242301524624707500251520ustar00rootroot00000000000000## Copyright (C) 2008 Arno Onken ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} copulacdf (@var{family}, @var{x}, @var{theta}) ## @deftypefnx {statistics} {@var{p} =} copulacdf ('t', @var{x}, @var{theta}, @var{df}) ## ## Copula family cumulative distribution functions (CDF). ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{family} is the copula family name. Currently, @var{family} can ## be @code{'Gaussian'} for the Gaussian family, @code{'t'} for the ## Student's t family, @code{'Clayton'} for the Clayton family, ## @code{'Gumbel'} for the Gumbel-Hougaard family, @code{'Frank'} for ## the Frank family, @code{'AMH'} for the Ali-Mikhail-Haq family, or ## @code{'FGM'} for the Farlie-Gumbel-Morgenstern family. ## ## @item ## @var{x} is the support where each row corresponds to an observation. ## ## @item ## @var{theta} is the parameter of the copula. For the Gaussian and ## Student's t copula, @var{theta} must be a correlation matrix. For ## bivariate copulas @var{theta} can also be a correlation coefficient. ## For the Clayton family, the Gumbel-Hougaard family, the Frank family, ## and the Ali-Mikhail-Haq family, @var{theta} must be a vector with the ## same number of elements as observations in @var{x} or be scalar. For ## the Farlie-Gumbel-Morgenstern family, @var{theta} must be a matrix of ## coefficients for the Farlie-Gumbel-Morgenstern polynomial where each ## row corresponds to one set of coefficients for an observation in ## @var{x}. A single row is expanded. The coefficients are in binary ## order. ## ## @item ## @var{df} is the degrees of freedom for the Student's t family. ## @var{df} must be a vector with the same number of elements as ## observations in @var{x} or be scalar. ## @end itemize ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{p} is the cumulative distribution of the copula at each row of ## @var{x} and corresponding parameter @var{theta}. ## @end itemize ## ## @subheading Examples ## ## @example ## @group ## x = [0.2:0.2:0.6; 0.2:0.2:0.6]; ## theta = [1; 2]; ## p = copulacdf ("Clayton", x, theta) ## @end group ## ## @group ## x = [0.2:0.2:0.6; 0.2:0.1:0.4]; ## theta = [0.2, 0.1, 0.1, 0.05]; ## p = copulacdf ("FGM", x, theta) ## @end group ## @end example ## ## @subheading References ## ## @enumerate ## @item ## Roger B. Nelsen. @cite{An Introduction to Copulas}. Springer, ## New York, second edition, 2006. ## @end enumerate ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{copulapdf, copularnd} ## @end deftypefn function p = copulacdf (family, x, theta, df) ## Check arguments if (nargin != 3 && (nargin != 4 || ! strcmpi (family, 't'))) print_usage (); endif if (! ischar (family)) error (strcat ("copulacdf: family must be one of 'Gaussian',", ... " 't', 'Clayton', 'Gumbel', 'Frank', 'AMH', and 'FGM'.")); endif ## Check for X and THETA being double or single if (! (isfloat (x) && isfloat (theta))) error ("copulacdf: X and THETA must be double or single."); endif if (! isempty (x) && ! ismatrix (x)) error ("copulacdf: X must be a numeric matrix."); endif [n, d] = size (x); lower_family = lower (family); ## Check family and copula parameters switch (lower_family) case {'gaussian', 't'} ## Family with a covariance matrix if (d == 2 && isscalar (theta)) ## Expand a scalar to a correlation matrix theta = [1, theta; theta, 1]; endif if (any (size (theta) != [d, d]) || any (diag (theta) != 1) || ... any (any (theta != theta')) || min (eig (theta)) <= 0) error ("copulacdf: THETA must be a correlation matrix."); endif if (nargin == 4) ## Student's t family if (! isscalar (df) && (! isvector (df) || length (df) != n)) error (strcat ("copulacdf: DF must be a vector with the same", ... " number of rows as X or be scalar.")); endif df = df(:); endif case {'clayton', 'gumbel', 'frank', 'amh'} ## Archimedian one parameter family if (! isvector (theta) || (! isscalar (theta) && length (theta) != n)) error (strcat ("copulacdf: THETA must be a vector with the same", ... " number of rows as X or be scalar.")); endif theta = theta(:); if (n > 1 && isscalar (theta)) theta = repmat (theta, n, 1); endif case {'fgm'} ## Exponential number of parameters if (! ismatrix (theta) || size (theta, 2) != (2 .^ d - d - 1) || ... (size (theta, 1) != 1 && size (theta, 1) != n)) error (strcat ("copulacdf: THETA must be a row vector of length", ... " 2^d-d-1 or a matrix of size N x (2^d-d-1).")); endif if (n > 1 && size (theta, 1) == 1) theta = repmat (theta, n, 1); endif otherwise error ("copulacdf: unknown copula family '%s'.", family); endswitch if (n == 0) ## Input is empty p = zeros (0, 1); else ## Truncate input to unit hypercube x(x < 0) = 0; x(x > 1) = 1; ## Compute the cumulative distribution function according to family switch (lower_family) case {'gaussian'} ## The Gaussian family p = mvncdf (norminv (x), zeros (1, d), theta); ## No parameter bounds check k = []; case {'t'} ## The Student's t family p = mvtcdf (tinv (x, df), theta, df); ## No parameter bounds check k = []; case {'clayton'} ## The Clayton family p = exp (-log (max (sum (x .^ (repmat (-theta, 1, d)), 2) ... - d + 1, 0)) ./ theta); ## Product copula at columns where theta == 0 k = find (theta == 0); if (any (k)) p(k) = prod (x(k, :), 2); endif ## Check bounds if (d > 2) k = find (! (theta >= 0) | ! (theta < inf)); else k = find (! (theta >= -1) | ! (theta < inf)); endif case {'gumbel'} ## The Gumbel-Hougaard family p = exp (-(sum ((-log (x)) .^ repmat (theta, 1, d), 2)) ... .^ (1 ./ theta)); ## Check bounds k = find (! (theta >= 1) | ! (theta < inf)); case {'frank'} ## The Frank family p = -log (1 + (prod (expm1 (repmat (-theta, 1, d) .* x), 2)) ./ ... (expm1 (-theta) .^ (d - 1))) ./ theta; ## Product copula at columns where theta == 0 k = find (theta == 0); if (any (k)) p(k) = prod (x(k, :), 2); endif ## Check bounds if (d > 2) k = find (! (theta > 0) | ! (theta < inf)); else k = find (! (theta > -inf) | ! (theta < inf)); endif case {'amh'} ## The Ali-Mikhail-Haq family p = (theta - 1) ./ (theta - prod ((1 + repmat (theta, 1, d) ... .* (x - 1)) ./ x, 2)); ## Check bounds if (d > 2) k = find (! (theta >= 0) | ! (theta < 1)); else k = find (! (theta >= -1) | ! (theta < 1)); endif case {'fgm'} ## The Farlie-Gumbel-Morgenstern family ## All binary combinations bcomb = logical (floor (mod (((0:(2 .^ d - 1))' * 2 .^ ... ((1 - d):0)), 2))); ecomb = ones (size (bcomb)); ecomb(bcomb) = -1; ## Summation over all combinations of order >= 2 bcomb = bcomb(sum (bcomb, 2) >= 2, end:-1:1); ## Linear constraints matrix ac = zeros (size (ecomb, 1), size (bcomb, 1)); ## Matrix to compute p ap = zeros (size (x, 1), size (bcomb, 1)); for i = 1:size (bcomb, 1) ac(:, i) = -prod (ecomb(:, bcomb(i, :)), 2); ap(:, i) = prod (1 - x(:, bcomb(i, :)), 2); endfor p = prod (x, 2) .* (1 + sum (ap .* theta, 2)); ## Check linear constraints k = false (n, 1); for i = 1:n k(i) = any (ac * theta(i, :)' > 1); endfor endswitch ## Out of bounds parameters if (any (k)) p(k) = NaN; endif endif endfunction ## Test output %!test %! x = [0.2:0.2:0.6; 0.2:0.2:0.6]; %! theta = [1; 2]; %! p = copulacdf ('Clayton', x, theta); %! expected_p = [0.1395; 0.1767]; %! assert_equal (p, expected_p, 0.001); %!test %! x = [0.2:0.2:0.6; 0.2:0.2:0.6]; %! p = copulacdf ('Gumbel', x, 2); %! expected_p = [0.1464; 0.1464]; %! assert_equal (p, expected_p, 0.001); %!test %! x = [0.2:0.2:0.6; 0.2:0.2:0.6]; %! theta = [1; 2]; %! p = copulacdf ('Frank', x, theta); %! expected_p = [0.0699; 0.0930]; %! assert_equal (p, expected_p, 0.001); %!test %! x = [0.2:0.2:0.6; 0.2:0.2:0.6]; %! theta = [0.3; 0.7]; %! p = copulacdf ('AMH', x, theta); %! expected_p = [0.0629; 0.0959]; %! assert_equal (p, expected_p, 0.001); %!test %! x = [0.2:0.2:0.6; 0.2:0.1:0.4]; %! theta = [0.2, 0.1, 0.1, 0.05]; %! p = copulacdf ('FGM', x, theta); %! expected_p = [0.0558; 0.0293]; %! assert_equal (p, expected_p, 0.001); ## Test input validation %!error copulacdf ('Clayton', int32 ([0, 0]), 2) %!error copulacdf ('Clayton', [true, true], 2) %!error copulacdf ('Clayton', 'ab', 2) statistics-release-1.9.2/inst/Distribution_Functions/copulapdf.m000066400000000000000000000356171524624707500252020ustar00rootroot00000000000000## Copyright (C) 2008 Arno Onken ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} copulapdf (@var{family}, @var{x}, @var{theta}) ## @deftypefnx {statistics} {@var{y} =} copulapdf ('t', @var{x}, @var{theta}, @var{df}) ## ## Copula family probability density functions (PDF). ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{family} is the copula family name. Currently, @var{family} can ## be @code{'Gaussian'} for the Gaussian family, @code{'t'} for the ## Student's t family, @code{'Clayton'} for the Clayton family, ## @code{'Gumbel'} for the Gumbel-Hougaard family, @code{'Frank'} for the ## Frank family, @code{'AMH'} for the Ali-Mikhail-Haq family, or ## @code{'FGM'} for the Farlie-Gumbel-Morgenstern family. The last two are ## Octave extensions that MATLAB does not provide. ## ## @item ## @var{x} is the support where each row corresponds to an observation. ## ## @item ## @var{theta} is the parameter of the copula. For the Gaussian and ## Student's t families it is the linear correlation matrix, and a scalar ## is expanded to a bivariate one. For the remaining families the elements ## of @var{theta} must be greater than or equal to @code{-1} for the ## Clayton family, greater than or equal to @code{1} for the ## Gumbel-Hougaard family, arbitrary for the Frank family, and greater ## than or equal to @code{-1} and lower than @code{1} for the ## Ali-Mikhail-Haq family. Moreover, @var{theta} must be non-negative ## for dimensions greater than @code{2}. @var{theta} must be a column ## vector with the same number of rows as @var{x} or be scalar. The ## Farlie-Gumbel-Morgenstern family instead takes one parameter for every ## subset of the variables of order two or more, so @var{theta} is a row ## vector of length @code{2^d-d-1} or a matrix with one such row per ## observation; parameter sets violating the family's linear constraints ## give @code{NaN}. ## ## @item ## @var{df} is the degrees of freedom of the Student's t family, and is ## required by it. It must be a vector with the same number of rows as ## @var{x} or be scalar. ## @end itemize ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{y} is the probability density of the copula at each row of ## @var{x} and corresponding parameter @var{theta}. ## @end itemize ## ## @subheading Examples ## ## @example ## @group ## x = [0.2:0.2:0.6; 0.2:0.2:0.6]; ## theta = [1; 2]; ## y = copulapdf ("Clayton", x, theta) ## @end group ## ## @group ## y = copulapdf ("Gumbel", x, 2) ## @end group ## @end example ## ## @subheading References ## ## @enumerate ## @item ## Roger B. Nelsen. @cite{An Introduction to Copulas}. Springer, ## New York, second edition, 2006. ## @end enumerate ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{copulacdf, copularnd} ## @end deftypefn function y = copulapdf (family, x, theta, df) ## Check arguments if (nargin != 3 && (nargin != 4 || ! strcmpi (family, 't'))) print_usage (); endif if (! ischar (family)) error (strcat ("copulapdf: family must be one of 'Gaussian',", ... " 't', 'Clayton', 'Gumbel', 'Frank', and 'AMH'.")); endif ## Check for X and THETA being double or single if (! (isfloat (x) && isfloat (theta))) error ("copulapdf: X and THETA must be double or single."); endif if (! isempty (x) && ! ismatrix (x)) error ("copulapdf: X must be a numeric matrix."); endif [n, d] = size (x); lower_family = lower (family); ## The Farlie-Gumbel-Morgenstern family carries one parameter per subset of ## the variables of order two or more, as it does in copulacdf. if (strcmp (lower_family, 'fgm')) if (! ismatrix (theta) || size (theta, 2) != (2 .^ d - d - 1) || ... (size (theta, 1) != 1 && size (theta, 1) != n)) error (strcat ("copulapdf: THETA must be a row vector of length", ... " 2^d-d-1 or a matrix of size N x (2^d-d-1).")); endif if (n > 1 && size (theta, 1) == 1) theta = repmat (theta, n, 1); endif endif ## The two elliptical families take a correlation matrix rather than a ## one-parameter THETA, and are validated the way copulacdf validates them. is_elliptical = any (strcmp (lower_family, {'gaussian', 't'})); if (is_elliptical) if (d == 2 && isscalar (theta)) ## Expand a scalar to a correlation matrix theta = [1, theta; theta, 1]; endif if (any (size (theta) != [d, d]) || any (diag (theta) != 1) || ... any (any (theta != theta')) || min (eig (theta)) <= 0) error ("copulapdf: THETA must be a correlation matrix."); endif if (nargin == 4) if (! isscalar (df) && (! isvector (df) || length (df) != n)) error (strcat ("copulapdf: DF must be a vector with the same", ... " number of rows as X or be scalar.")); endif df = df(:); endif elseif (! strcmp (lower_family, 'fgm') && (! isvector (theta) ... || (! isscalar (theta) && size (theta, 1) != n))) error (strcat ("copulapdf: THETA must be a column vector with the", ... " same number of rows as X or be scalar.")); endif if (n == 0) ## Input is empty y = zeros (0, 1); else if (n > 1 && isscalar (theta) && ! is_elliptical ... && ! strcmp (lower_family, 'fgm')) theta = repmat (theta, n, 1); endif ## Truncate input to unit hypercube x(x < 0) = 0; x(x > 1) = 1; ## Compute the density according to family lowerarg = lower_family; if (strcmp (lowerarg, 'gaussian')) ## The Gaussian family: the density of the correlated normal relative to ## the independent one, at the normal quantiles of X. z = norminv (x); y = exp (-0.5 * sum ((z * (inv (theta) - eye (d))) .* z, 2)) ... ./ sqrt (det (theta)); ## No parameter bounds check k = []; elseif (strcmp (lowerarg, 't')) ## The Student's t family: the multivariate t density at the t quantiles ## of X, divided by the univariate ones it would factor into. if (nargin < 4) error ("copulapdf: DF is required for the 't' copula family."); endif z = tinv (x, df); y = mvtpdf (z, theta, df) ./ prod (tpdf (z, df), 2); ## No parameter bounds check k = []; elseif (strcmp (lowerarg, 'clayton')) ## The Clayton family log_cdf = -log (max (sum (x .^ (repmat (-theta, 1, d)), 2) ... - d + 1, 0)) ./ theta; y = prod (repmat (theta, 1, d) .* repmat (0:(d - 1), n, 1) + 1, 2) ... .* exp ((1 + theta .* d) .* log_cdf - ... (theta + 1) .* sum (log (x), 2)); ## Product copula at columns where theta == 0 k = find (theta == 0); if (any (k)) y(k) = 1; endif ## Check theta if (d > 2) k = find (! (theta >= 0) | ! (theta < inf)); else k = find (! (theta >= -1) | ! (theta < inf)); endif elseif (strcmp (lowerarg, 'gumbel')) ## The Gumbel-Hougaard family g = sum ((-log (x)) .^ repmat (theta, 1, d), 2); c = exp (-g .^ (1 ./ theta)); y = ((prod (-log (x), 2)) .^ (theta - 1)) ./ prod (x, 2) .* c .* ... (g .^ (2 ./ theta - 2) + (theta - 1) .* g .^ (1 ./ theta - 2)); ## Check theta k = find (! (theta >= 1) | ! (theta < inf)); elseif (strcmp (lowerarg, 'frank')) ## The Frank family if (d != 2) error ("copulapdf: Frank copula PDF implemented as bivariate only."); endif y = (theta .* exp (theta .* (1 + sum (x, 2))) .* (exp (theta) - 1)) ./ ... (exp (theta) - exp (theta + theta .* x(:, 1)) + ... exp (theta .* sum (x, 2)) - exp (theta + theta .* x(:, 2))) .^ 2; ## Product copula at columns where theta == 0 k = find (theta == 0); if (any (k)) y(k) = 1; endif ## Check theta k = find (! (theta > -inf) | ! (theta < inf)); elseif (strcmp (lowerarg, 'amh')) ## The Ali-Mikhail-Haq family if (d != 2) error (strcat ("copulapdf: Ali-Mikhail-Haq copula PDF", ... " implemented as bivariate only.")); endif z = theta .* prod (x - 1, 2) - 1; y = (theta .* (1 - sum (x, 2) - prod (x, 2) - z) - 1) ./ (z .^ 3); ## Check theta k = find (! (theta >= -1) | ! (theta < 1)); elseif (strcmp (lowerarg, 'fgm')) ## The Farlie-Gumbel-Morgenstern family. Differentiating the ## distribution once in every variable turns each u_i (1 - u_i) factor ## into (1 - 2 u_i) and leaves the leading product at one. bcomb = logical (floor (mod (((0:(2 .^ d - 1))' * 2 .^ ... ((1 - d):0)), 2))); ecomb = ones (size (bcomb)); ecomb(bcomb) = -1; ## Summation over all combinations of order >= 2 bcomb = bcomb(sum (bcomb, 2) >= 2, end:-1:1); ## Linear constraints matrix ac = zeros (size (ecomb, 1), size (bcomb, 1)); ## Matrix to compute y ap = zeros (n, size (bcomb, 1)); for i = 1:size (bcomb, 1) ac(:, i) = -prod (ecomb(:, bcomb(i, :)), 2); ap(:, i) = prod (1 - 2 * x(:, bcomb(i, :)), 2); endfor y = 1 + sum (ap .* theta, 2); ## Check linear constraints k = false (n, 1); for i = 1:n k(i) = any (ac * theta(i, :)' > 1); endfor else error ("copulapdf: unknown copula family '%s'.", family); endif if (any (k)) y(k) = NaN; endif endif endfunction ## Test output %!test %! x = [0.2:0.2:0.6; 0.2:0.2:0.6]; %! theta = [1; 2]; %! y = copulapdf ('Clayton', x, theta); %! expected_p = [0.9872; 0.7295]; %! assert_equal (y, expected_p, 0.001); %!test %! x = [0.2:0.2:0.6; 0.2:0.2:0.6]; %! y = copulapdf ('Gumbel', x, 2); %! expected_p = [0.9468; 0.9468]; %! assert_equal (y, expected_p, 0.001); %!test %! x = [0.2, 0.6; 0.2, 0.6]; %! theta = [1; 2]; %! y = copulapdf ('Frank', x, theta); %! expected_p = [0.9378; 0.8678]; %! assert_equal (y, expected_p, 0.001); %!test %! x = [0.2, 0.6; 0.2, 0.6]; %! theta = [0.3; 0.7]; %! y = copulapdf ('AMH', x, theta); %! expected_p = [0.9540; 0.8577]; %! assert_equal (y, expected_p, 0.001); ## Test input validation %!error copulapdf ('Clayton', int32 ([0, 0]), 2) %!error copulapdf ('Clayton', [true, true], 2) %!error copulapdf ('Clayton', 'ab', 2) ## The Gaussian and Student's t families, verified against MATLAB R2024a. %!test %! x = [0.1, 0.2; 0.3, 0.6; 0.5, 0.4; 0.7, 0.9; 0.45, 0.55]; %! y = copulapdf ('Gaussian', x, 0.5); %! assert_equal (y, [1.60177371945198; 0.998741486235102; 1.14241401106385; ... %! 1.31299420633171; 1.13661012971028], 1e-12); %! y = copulapdf ('Gaussian', x, -0.3); %! assert_equal (y, [0.653989319149703; 1.07700187314878; 1.04496288532534; ... %! 0.76398020459843; 1.05211178116014], 1e-12); %!test # an uncorrelated Gaussian copula is the independence copula %! x = [0.1, 0.2; 0.3, 0.6; 0.5, 0.4; 0.7, 0.9; 0.45, 0.55]; %! assert_equal (copulapdf ('Gaussian', x, 0), ones (5, 1), 1e-12); %!test %! x = [0.1, 0.2; 0.3, 0.6; 0.5, 0.4; 0.7, 0.9; 0.45, 0.55]; %! y = copulapdf ('t', x, 0.5, 5); %! assert_equal (y, [1.66488234707408; 1.00205894407007; 1.24574194276063; ... %! 1.25091343011063; 1.24054754220011], 1e-12); %! y = copulapdf ('t', x, -0.3, 10); %! assert_equal (y, [0.644342340201519; 1.12031977908833; 1.09385787202575; ... %! 0.730314426588563; 1.10518920332876], 1e-12); %!test # both elliptical families take a correlation matrix beyond two columns %! R = [1, 0.4, 0.2; 0.4, 1, 0.3; 0.2, 0.3, 1]; %! x = [0.2, 0.4, 0.6; 0.5, 0.5, 0.5]; %! assert_equal (copulapdf ('Gaussian', x, R), ... %! [1.1162786093147; 1.14859096884849], 1e-12); %! assert_equal (copulapdf ('t', x, R, 8), ... %! [1.19326414606185; 1.37528246652052], 1e-12); %!test # the density integrates to one over the unit square %! g = ((1:40)' - 0.5) / 40; %! [A, B] = meshgrid (g, g); %! x = [A(:), B(:)]; %! assert_equal (sum (copulapdf ('Gaussian', x, 0.5)) / 1600, 1, 1e-3); %! assert_equal (sum (copulapdf ('t', x, 0.5, 6)) / 1600, 1, 1e-2); %!error ... %! copulapdf ('Gaussian', [0.2, 0.4], [1, 2; 2, 1]) %!error ... %! copulapdf ('t', [0.2, 0.4], 0.5) %!error copulapdf ('Gaussian', [0.2, 0.4], 0.5, 5) ## The Farlie-Gumbel-Morgenstern family, an Octave extension. Its density is ## checked against a finite difference of copulacdf, which is independent of it. %!test %! x = [0.35, 0.62]; %! h = 1e-5; %! for theta = [-1, -0.4, 0.7, 1] %! fd = (copulacdf ('FGM', [x(1)+h, x(2)+h], theta) ... %! - copulacdf ('FGM', [x(1)+h, x(2)-h], theta) ... %! - copulacdf ('FGM', [x(1)-h, x(2)+h], theta) ... %! + copulacdf ('FGM', [x(1)-h, x(2)-h], theta)) / (4 * h * h); %! assert_equal (copulapdf ('FGM', x, theta), fd, 1e-6); %! endfor %!test # the bivariate density in closed form %! x = [0.35, 0.62; 0.2, 0.3]; %! theta = 0.7; %! assert_equal (copulapdf ('FGM', x, theta), ... %! 1 + theta * (1 - 2 * x(:,1)) .* (1 - 2 * x(:,2)), 1e-14); %!test # a zero parameter gives the independence copula %! x = [0.35, 0.62; 0.2, 0.3; 0.9, 0.1]; %! assert_equal (copulapdf ('FGM', x, 0), ones (3, 1), 1e-14); %!test # one parameter per subset of order two or more beyond two variables %! x = [0.3, 0.5, 0.7]; %! theta = [0.1, 0.1, 0.1, 0.1]; %! assert_equal (copulapdf ('FGM', x, theta), 0.984, 1e-12); %!test # a parameter set outside the family's linear constraints gives NaN %! assert_equal (copulapdf ('FGM', [0.3, 0.5], 2), NaN); %!test # the density integrates to one over the unit square %! g = ((1:60)' - 0.5) / 60; %! [A, B] = meshgrid (g, g); %! x = [A(:), B(:)]; %! assert_equal (sum (copulapdf ('FGM', x, 0.7)) / 3600, 1, 1e-10); %!error ... %! copulapdf ('FGM', [0.3, 0.5, 0.7], [0.1, 0.1]) statistics-release-1.9.2/inst/Distribution_Functions/copularnd.m000066400000000000000000000363321524624707500252070ustar00rootroot00000000000000## Copyright (C) 2012 Arno Onken ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} copularnd (@var{family}, @var{theta}, @var{n}) ## @deftypefnx {statistics} {@var{r} =} copularnd (@var{family}, @var{theta}, @var{n}, @var{d}) ## @deftypefnx {statistics} {@var{r} =} copularnd ('t', @var{theta}, @var{df}, @var{n}) ## ## Random arrays from the copula family distributions. ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{family} is the copula family name. Currently, @var{family} can be ## @code{'Gaussian'} for the Gaussian family, @code{'t'} for the Student's t ## family, @code{'Clayton'} for the Clayton family, @code{'Frank'} for the ## Frank family, @code{'Gumbel'} for the Gumbel-Hougaard family, @code{'AMH'} ## for the Ali-Mikhail-Haq family, or @code{'FGM'} for the ## Farlie-Gumbel-Morgenstern family. The last two are Octave extensions that ## MATLAB does not provide. Every family but Clayton is generated as ## bivariate only. ## ## @item ## @var{theta} is the parameter of the copula. For the Gaussian and Student's t ## copula, @var{theta} must be a correlation matrix. For bivariate copulas ## @var{theta} can also be a correlation coefficient. For the Clayton, Frank ## and Gumbel-Hougaard families, @var{theta} must be a vector with the same ## number of elements as samples to be generated or be scalar. Values outside ## a family's range give @code{NaN} rows: at or above @code{1} for the ## Gumbel-Hougaard family, at or above @code{-1} for the bivariate Clayton ## family, and any finite value for the Frank family. The Ali-Mikhail-Haq ## family takes @var{theta} in @code{[-1, 1)} and the ## Farlie-Gumbel-Morgenstern family in @code{[-1, 1]}. ## ## @item ## @var{df} is the degrees of freedom for the Student's t family. @var{df} must ## be a vector with the same number of elements as samples to be generated or ## be scalar. ## ## @item ## @var{n} is the number of rows of the matrix to be generated. @var{n} must be ## a non-negative integer and corresponds to the number of samples to be ## generated. ## ## @item ## @var{d} is the number of columns of the matrix to be generated. @var{d} must ## be a positive integer and corresponds to the dimension of the copula. ## @end itemize ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{r} is a matrix of random samples from the copula with @var{n} samples ## of distribution dimension @var{d}. ## @end itemize ## ## @subheading Examples ## ## @example ## @group ## theta = 0.5; ## r = copularnd ("Gaussian", theta); ## @end group ## ## @group ## theta = 0.5; ## df = 2; ## r = copularnd ("t", theta, df); ## @end group ## ## @group ## theta = 0.5; ## n = 2; ## r = copularnd ("Clayton", theta, n); ## @end group ## @end example ## ## @subheading References ## ## @enumerate ## @item ## Roger B. Nelsen. @cite{An Introduction to Copulas}. Springer, New York, ## second edition, 2006. ## @end enumerate ## @end deftypefn function r = copularnd (family, theta, df, n) ## Check arguments if (nargin < 2) print_usage (); endif if (! ischar (family)) error (strcat ("copularnd: family must be one of 'Gaussian',", ... " 't', 'Clayton', 'Frank', 'Gumbel', 'AMH', and 'FGM'.")); endif lower_family = lower (family); ## Check family and copula parameters switch (lower_family) case {'gaussian'} ## Gaussian family if (isscalar (theta)) ## Expand a scalar to a correlation matrix theta = [1, theta; theta, 1]; endif if (! ismatrix (theta) || any (diag (theta) != 1) || ... any (any (theta != theta')) || min (eig (theta)) <= 0) error ("copularnd: THETA must be a correlation matrix."); endif if (nargin > 3) d = n; if (! isscalar (d) || d != size (theta, 1)) error ("copularnd: D must correspond to dimension of theta."); endif else d = size (theta, 1); endif if (nargin < 3) n = 1; else n = df; if (! isscalar (n) || (n < 0) || round (n) != n) error ("copularnd: N must be a non-negative integer."); endif endif case {'t'} ## Student's t family if (nargin < 3) print_usage (); endif if (isscalar (theta)) ## Expand a scalar to a correlation matrix theta = [1, theta; theta, 1]; endif if (! ismatrix (theta) || any (diag (theta) != 1) || ... any (any (theta != theta')) || min (eig (theta)) <= 0) error ("copularnd: THETA must be a correlation matrix."); endif if (! isscalar (df) && (! isvector (df) || length (df) != n)) error (strcat ("copularnd: DF must be a vector with the same", ... " number of rows as r or be scalar.")); endif df = df(:); if (nargin < 4) n = 1; else if (! isscalar (n) || (n < 0) || round (n) != n) error ("copularnd: N must be a non-negative integer."); endif endif case {'clayton', 'frank', 'gumbel', 'amh', 'fgm'} ## Archimedian one parameter family if (nargin < 4) ## Default is bivariate d = 2; else d = n; if (! isscalar (d) || (d < 2) || round (d) != d) error ("copularnd: D must be an integer greater than 1."); endif endif ## Only the Clayton family is available for more than two dimensions. if (d != 2 && ! strcmp (lower_family, 'clayton')) error (strcat ("copularnd: the '%s' copula is implemented as", ... " bivariate only."), family); endif if (nargin < 3) ## Default is one sample n = 1; else n = df; if (! isscalar (n) || (n < 0) || round (n) != n) error ("copularnd: N must be a non-negative integer."); endif endif if (! isvector (theta) || (! isscalar (theta) && size (theta, 1) != n)) error (strcat ("copularnd: THETA must be a column vector with", ... " the number of rows equal to N or be scalar.")); endif if (n > 1 && isscalar (theta)) theta = repmat (theta, n, 1); endif otherwise error ("copularnd: unknown copula family '%s'.", family); endswitch if (n == 0) ## Input is empty r = zeros (0, d); else ## Draw random samples according to family switch (lower_family) case {'gaussian'} ## The Gaussian family r = normcdf (mvnrnd (zeros (1, d), theta, n), 0, 1); ## No parameter bounds check k = []; case {'t'} ## The Student's t family r = tcdf (mvtrnd (theta, df, n), df); ## No parameter bounds check k = []; case {'clayton'} ## The Clayton family u = rand (n, d); if (d == 2) r = zeros (n, 2); ## Conditional distribution method for the bivariate case which also ## works for theta < 0 r(:, 1) = u(:, 1); r(:, 2) = (1 + u(:, 1) .^ (-theta) .* (u(:, 2) .^ ... (-theta ./ (1 + theta)) - 1)) .^ (-1 ./ theta); else ## Apply the algorithm by Marshall and Olkin: ## Frailty distribution for Clayton copula is gamma y = randg (1 ./ theta, n, 1); r = (1 - log (u) ./ repmat (y, 1, d)) .^ (-1 ./ repmat (theta, 1, d)); endif k = find (theta == 0); if (any (k)) ## Product copula at columns k r(k, :) = u(k, :); endif ## Continue argument check if (d == 2) k = find (! (theta >= -1) | ! (theta < inf)); else k = find (! (theta >= 0) | ! (theta < inf)); endif case {'frank'} ## The Frank family, by inverting the conditional distribution. It has ## a closed form here, which the Gumbel family below does not. u = rand (n, 2); e = exp (-theta); eu = exp (-theta .* u(:, 1)); w = 1 + u(:, 2) .* (1 - e) ./ (u(:, 2) .* (eu - 1) - eu); u2 = -log (w) ./ theta; r = [u(:, 1), u2]; ## Product copula at rows where theta == 0 k = find (theta == 0); if (any (k)) r(k, :) = u(k, :); endif ## Check bounds k = find (! (theta > -inf) | ! (theta < inf)); case {'gumbel'} ## The Gumbel-Hougaard family, by the algorithm of Marshall and Olkin. ## Its frailty is positive stable with index 1 / THETA, drawn by ## Kanter's method; the conditional distribution cannot be inverted in ## closed form as Clayton's and Frank's can. a = 1 ./ theta; U = pi * rand (n, 1); W = -log (rand (n, 1)); aU = sin ((1 - a) .* U) .* (sin (a .* U) .^ (a ./ (1 - a))) ... ./ (sin (U) .^ (1 ./ (1 - a))); V = (aU ./ W) .^ ((1 - a) ./ a); E = -log (rand (n, 2)); r = exp (- (E ./ V) .^ a); ## The independence copula at theta == 1, where the frailty degenerates k = find (theta == 1); if (any (k)) r(k, :) = exp (-E(k, :)); endif ## Check bounds k = find (! (theta >= 1) | ! (theta < inf)); case {'amh'} ## The Ali-Mikhail-Haq family. Its conditional distribution is a ## quadratic in 1 - V, so it inverts in closed form. u = rand (n, 2); w = u(:, 2); b = 1 - u(:, 1); qa = theta - theta .^ 2 .* b .^ 2 .* w; qb = 2 .* theta .* b .* w - (1 + theta); qc = 1 - w; z = w; lin = abs (qa) <= eps; z(! lin) = (-qb(! lin) - sqrt (qb(! lin) .^ 2 ... - 4 .* qa(! lin) .* qc(! lin))) ./ (2 .* qa(! lin)); z(lin) = -qc(lin) ./ qb(lin); r = [u(:, 1), 1 - z]; ## Check bounds k = find (! (theta >= -1) | ! (theta < 1)); case {'fgm'} ## The Farlie-Gumbel-Morgenstern family, likewise a quadratic. u = rand (n, 2); w = u(:, 2); A = theta .* (1 - 2 .* u(:, 1)); v = w; nz = A != 0; v(nz) = ((1 + A(nz)) - sqrt ((1 + A(nz)) .^ 2 ... - 4 .* A(nz) .* w(nz))) ./ (2 .* A(nz)); r = [u(:, 1), v]; ## Check bounds k = find (! (theta >= -1) | ! (theta <= 1)); endswitch ## Out of bounds parameters if (any (k)) r(k, :) = NaN; endif endif endfunction ## Test output %!test %! theta = 0.5; %! r = copularnd ('Gaussian', theta); %! assert_equal (size (r), [1, 2]); %! assert_equal (all ((all ((r >= 0) & (r <= 1)))(:)), true); %!test %! theta = 0.5; %! df = 2; %! r = copularnd ('t', theta, df); %! assert_equal (size (r), [1, 2]); %! assert_equal (all ((all ((r >= 0) & (r <= 1)))(:)), true); %!test %! theta = 0.5; %! r = copularnd ('Clayton', theta); %! assert_equal (size (r), [1, 2]); %! assert_equal (all ((all ((r >= 0) & (r <= 1)))(:)), true); %!test %! theta = 0.5; %! n = 2; %! r = copularnd ('Clayton', theta, n); %! assert_equal (size (r), [n, 2]); %! assert_equal (all ((all ((r >= 0) & (r <= 1)))(:)), true); %!test %! theta = [1; 2]; %! n = 2; %! d = 3; %! r = copularnd ('Clayton', theta, n, d); %! assert_equal (size (r), [n, d]); %! assert_equal (all ((all ((r >= 0) & (r <= 1)))(:)), true); ## The Frank and Gumbel-Hougaard families. A generator cannot be checked ## against MATLAB value for value, so the sample's rank correlation is checked ## against the rank correlation the family is defined to have. %!test %! rand ("seed", 7); %! for theta = [-5, -2, 2, 5, 10] %! r = copularnd ("Frank", theta, 4000); %! assert_equal (size (r), [4000, 2]); %! assert_equal (all (r(:) >= 0 & r(:) <= 1), true); %! rho = corr (tiedrank (r(:,1)), tiedrank (r(:,2))); %! assert_equal (rho, copulastat ("Frank", theta, "type", "Spearman"), 0.05); %! endfor %!test %! rand ("seed", 11); %! for theta = [1.5, 2, 3, 5] %! r = copularnd ("Gumbel", theta, 4000); %! assert_equal (size (r), [4000, 2]); %! assert_equal (all (r(:) >= 0 & r(:) <= 1), true); %! rho = corr (tiedrank (r(:,1)), tiedrank (r(:,2))); %! assert_equal (rho, copulastat ("Gumbel", theta, "type", "Spearman"), 0.05); %! endfor %!test # each family degenerates to independence at its own boundary %! rand ("seed", 13); %! r = copularnd ("Frank", 0, 3000); %! assert_equal (corr (tiedrank (r(:,1)), tiedrank (r(:,2))), 0, 0.06); %! r = copularnd ("Gumbel", 1, 3000); %! assert_equal (corr (tiedrank (r(:,1)), tiedrank (r(:,2))), 0, 0.06); %!test # a parameter outside the family's range gives NaN rows %! assert_equal (copularnd ("Gumbel", 0.5, 2), NaN (2, 2)); %! assert_equal (copularnd ("Frank", Inf, 2), NaN (2, 2)); %!test # the default is a single bivariate draw %! rand ("seed", 3); %! assert_equal (size (copularnd ("Frank", 3)), [1, 2]); %! assert_equal (size (copularnd ("Gumbel", 2)), [1, 2]); %! assert_equal (size (copularnd ("Gumbel", 2, 5)), [5, 2]); %!error ... %! copularnd ("Frank", 3, 5, 3) %!error ... %! copularnd ("Gumbel", 2, 5, 3) ## The Ali-Mikhail-Haq and Farlie-Gumbel-Morgenstern families, Octave ## extensions. As with the other generators the sample's rank correlation is ## checked against the one the family is defined to have. %!test %! rand ("seed", 21); %! for theta = [-0.9, -0.5, 0.5, 0.9] %! r = copularnd ("AMH", theta, 4000); %! assert_equal (size (r), [4000, 2]); %! assert_equal (all (r(:) >= 0 & r(:) <= 1), true); %! rho = corr (tiedrank (r(:,1)), tiedrank (r(:,2))); %! assert_equal (rho, copulastat ("AMH", theta, "type", "Spearman"), 0.05); %! endfor %!test %! rand ("seed", 23); %! for theta = [-1, -0.5, 0.5, 1] %! r = copularnd ("FGM", theta, 4000); %! assert_equal (size (r), [4000, 2]); %! assert_equal (all (r(:) >= 0 & r(:) <= 1), true); %! rho = corr (tiedrank (r(:,1)), tiedrank (r(:,2))); %! assert_equal (rho, copulastat ("FGM", theta, "type", "Spearman"), 0.05); %! endfor %!test # both are the independence copula at a zero parameter %! rand ("seed", 29); %! r = copularnd ("AMH", 0, 4000); %! assert_equal (corr (tiedrank (r(:,1)), tiedrank (r(:,2))), 0, 0.05); %! r = copularnd ("FGM", 0, 4000); %! assert_equal (corr (tiedrank (r(:,1)), tiedrank (r(:,2))), 0, 0.05); %!test # a parameter outside the family's range gives NaN rows %! assert_equal (copularnd ("AMH", 1, 2), NaN (2, 2)); %! assert_equal (copularnd ("AMH", -1.5, 2), NaN (2, 2)); %! assert_equal (copularnd ("FGM", 1.5, 2), NaN (2, 2)); %!error ... %! copularnd ("AMH", 0.5, 5, 3) %!error ... %! copularnd ("FGM", 0.5, 5, 3) statistics-release-1.9.2/inst/Distribution_Functions/doc-cache000066400000000000000000010133111524624707500245640ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 176 # name: # type: sq_string # elements: 1 # length: 7 betacdf # name: # type: sq_string # elements: 1 # length: 997 statistics: p = betacdf ( x , a , b ) statistics: p = betacdf ( x , a , b , 'upper' ) Beta cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function of the Beta distribution with shape parameters a and b . The size of p is the common size of x , a , and b . A scalar input functions as a constant matrix of the same size as the other inputs. p = betacdf ( x , a , b , "upper") computes the upper tail probability of the Beta distribution with parameters a and b , at the values in x . Further information about the Beta distribution can be found at https://en.wikipedia.org/wiki/Beta_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: betainv, betapdf, betarnd, betafit, betalike, betastat # name: # type: sq_string # elements: 1 # length: 44 Beta cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 betainv # name: # type: sq_string # elements: 1 # length: 802 statistics: x = betainv ( p , a , b ) Inverse of the Beta distribution (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the Beta distribution with shape parameters a and b . The size of x is the common size of x , a , and b . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Beta distribution can be found at https://en.wikipedia.org/wiki/Beta_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: betacdf, betapdf, betarnd, betafit, betalike, betastat # name: # type: sq_string # elements: 1 # length: 40 Inverse of the Beta distribution (iCDF). # name: # type: sq_string # elements: 1 # length: 7 betapdf # name: # type: sq_string # elements: 1 # length: 803 statistics: y = betapdf ( x , a , b ) Beta probability density function (PDF). For each element of x , compute the probability density function (PDF) of the Beta distribution with shape parameters a and b . The size of y is the common size of x , a , and b . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Beta distribution can be found at https://en.wikipedia.org/wiki/Beta_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: betacdf, betainv, betarnd, betafit, betalike, betastat # name: # type: sq_string # elements: 1 # length: 40 Beta probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 betarnd # name: # type: sq_string # elements: 1 # length: 1003 statistics: r = betarnd ( a , b ) statistics: r = betarnd ( a , b , rows ) statistics: r = betarnd ( a , b , rows , cols , …) statistics: r = betarnd ( a , b , [ sz ]) Random arrays from the Beta distribution. r = betarnd ( a , b ) returns an array of random numbers chosen from the Beta distribution with shape parameters a and b . The size of r is the common size of a and b . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, betarnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the Beta distribution can be found at https://en.wikipedia.org/wiki/Beta_distribution See also: betacdf, betainv, betapdf, betafit, betalike, betastat # name: # type: sq_string # elements: 1 # length: 41 Random arrays from the Beta distribution. # name: # type: sq_string # elements: 1 # length: 7 binocdf # name: # type: sq_string # elements: 1 # length: 1171 statistics: p = binocdf ( x , n , ps ) statistics: p = binocdf ( x , n , ps , 'upper' ) Binomial cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the binomial distribution with parameters n and ps , where n is the number of trials and ps is the probability of success. The size of p is the common size of x , n , and ps . A scalar input functions as a constant matrix of the same size as the other inputs. p = binocdf ( x , n , ps , "upper") computes the upper tail probability of the binomial distribution with parameters n and ps , at the values in x . Further information about the binomial distribution can be found at https://en.wikipedia.org/wiki/Binomial_distribution Input arguments must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. MATLAB is inconsistent here: for several of the discrete distributions it returns the result in the integer class of the input, truncating a probability to 0 or 1 . See also: binoinv, binopdf, binornd, binofit, binolike, binostat, binotest # name: # type: sq_string # elements: 1 # length: 48 Binomial cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 binoinv # name: # type: sq_string # elements: 1 # length: 915 statistics: x = binoinv ( p , n , ps ) Inverse of the Binomial cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the binomial distribution with parameters n and ps , where n is the number of trials and ps is the probability of success. The size of x is the common size of p , n , and ps . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the binomial distribution can be found at https://en.wikipedia.org/wiki/Binomial_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: binocdf, binopdf, binornd, binofit, binolike, binostat, binotest # name: # type: sq_string # elements: 1 # length: 64 Inverse of the Binomial cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 binopdf # name: # type: sq_string # elements: 1 # length: 1176 statistics: y = binopdf ( x , n , ps ) Binomial probability density function (PDF). For each element of x , compute the probability density function (PDF) of the binomial distribution with parameters n and ps , where n is the number of trials and ps is the probability of success. The size of y is the common size of x , n , and ps . A scalar input functions as a constant matrix of the same size as the other inputs. Matlab incompatibility: Octave’s binopdf does not allow complex input values. Matlab 2021b returns values for complex inputs despite the documentation indicates integer and real value inputs are required. Further information about the binomial distribution can be found at https://en.wikipedia.org/wiki/Binomial_distribution Input arguments must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. MATLAB is inconsistent here: for several of the discrete distributions it returns the result in the integer class of the input, truncating a probability to 0 or 1 . See also: binocdf, binoinv, binornd, binofit, binolike, binostat, binotest # name: # type: sq_string # elements: 1 # length: 44 Binomial probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 binornd # name: # type: sq_string # elements: 1 # length: 1093 statistics: r = binornd ( n , ps ) statistics: r = binornd ( n , ps , rows ) statistics: r = binornd ( n , ps , rows , cols , …) statistics: r = binornd ( n , ps , [ sz ]) Random arrays from the Binomial distribution. r = binornd ( n , ps ) returns a matrix of random samples from the binomial distribution with parameters n and ps , where n is the number of trials and ps is the probability of success. The size of r is the common size of n and ps . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, binornd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the binomial distribution can be found at https://en.wikipedia.org/wiki/Binomial_distribution See also: binocdf, binoinv, binopdf, binofit, binolike, binostat, binotest # name: # type: sq_string # elements: 1 # length: 45 Random arrays from the Binomial distribution. # name: # type: sq_string # elements: 1 # length: 7 bisacdf # name: # type: sq_string # elements: 1 # length: 1131 statistics: p = bisacdf ( x , beta , gamma ) statistics: p = bisacdf ( x , beta , gamma , 'upper' ) Birnbaum-Saunders cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the Birnbaum-Saunders distribution with scale parameter beta and shape parameter gamma . The size of p is the common size of x , beta and gamma . A scalar input functions as a constant matrix of the same size as the other inputs. p = bisacdf ( x , beta , gamma , "upper") computes the upper tail probability of the Birnbaum-Saunders distribution with parameters beta and gamma , at the values in x . Further information about the Birnbaum-Saunders distribution can be found at https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: bisainv, bisapdf, bisarnd, bisafit, bisalike, bisastat # name: # type: sq_string # elements: 1 # length: 57 Birnbaum-Saunders cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 bisainv # name: # type: sq_string # elements: 1 # length: 918 statistics: x = bisainv ( p , beta , gamma ) Inverse of the Birnbaum-Saunders cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the Birnbaum-Saunders distribution with scale parameter beta and shape parameter gamma . The size of x is the common size of p , beta , and gamma . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Birnbaum-Saunders distribution can be found at https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: bisacdf, bisapdf, bisarnd, bisafit, bisalike, bisastat # name: # type: sq_string # elements: 1 # length: 73 Inverse of the Birnbaum-Saunders cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 bisapdf # name: # type: sq_string # elements: 1 # length: 899 statistics: y = bisapdf ( x , beta , gamma ) Birnbaum-Saunders probability density function (PDF). For each element of x , compute the probability density function (PDF) of the Birnbaum-Saunders distribution with scale parameter beta and shape parameter gamma . The size of y is the common size of x , beta , and gamma . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Birnbaum-Saunders distribution can be found at https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: bisacdf, bisainv, bisarnd, bisafit, bisalike, bisastat # name: # type: sq_string # elements: 1 # length: 53 Birnbaum-Saunders probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 bisarnd # name: # type: sq_string # elements: 1 # length: 1127 statistics: r = bisarnd ( beta , gamma ) statistics: r = bisarnd ( beta , gamma , rows ) statistics: r = bisarnd ( beta , gamma , rows , cols , …) statistics: r = bisarnd ( beta , gamma , [ sz ]) Random arrays from the Birnbaum-Saunders distribution. r = bisarnd ( beta , gamma ) returns an array of random numbers chosen from the Birnbaum-Saunders distribution with scale parameter beta and shape parameter gamma . The size of r is the common size of beta and gamma . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, bisarnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the Birnbaum-Saunders distribution can be found at https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution See also: bisacdf, bisainv, bisapdf, bisafit, bisalike, bisastat # name: # type: sq_string # elements: 1 # length: 54 Random arrays from the Birnbaum-Saunders distribution. # name: # type: sq_string # elements: 1 # length: 7 burrcdf # name: # type: sq_string # elements: 1 # length: 1130 statistics: p = burrcdf ( x , lambda , c , k ) statistics: p = burrcdf ( x , lambda , c , k , 'upper' ) Burr type XII cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the Burr type XII distribution with scale parameter lambda , first shape parameter c , and second shape parameter k . The size of p is the common size of x , lambda , c , and k . A scalar input functions as a constant matrix of the same size as the other inputs. p = burrcdf ( x , lambda , c , k , "upper") computes the upper tail probability of the Burr type XII distribution with parameters lambda , c and k , at the values in x . Further information about the Burr distribution can be found at https://en.wikipedia.org/wiki/Burr_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: burrinv, burrpdf, burrrnd, burrfit, burrlike, burrstat # name: # type: sq_string # elements: 1 # length: 53 Burr type XII cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 burrinv # name: # type: sq_string # elements: 1 # length: 913 statistics: x = burrinv ( p , lambda , c , k ) Inverse of the Burr type XII cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the Burr type XII distribution with scale parameter lambda , first shape parameter c , and second shape parameter k . The size of x is the common size of p , lambda , c , and k . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Burr distribution can be found at https://en.wikipedia.org/wiki/Burr_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: burrcdf, burrpdf, burrrnd, burrfit, burrlike, burrstat # name: # type: sq_string # elements: 1 # length: 69 Inverse of the Burr type XII cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 burrpdf # name: # type: sq_string # elements: 1 # length: 894 statistics: y = burrpdf ( x , lambda , c , k ) Burr type XII probability density function (PDF). For each element of x , compute the probability density function (PDF) of the Burr type XII distribution with scale parameter lambda , first shape parameter c , and second shape parameter k . The size of y is the common size of x , lambda , c , and k . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Burr distribution can be found at https://en.wikipedia.org/wiki/Burr_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: burrcdf, burrinv, burrrnd, burrfit, burrlike, burrstat # name: # type: sq_string # elements: 1 # length: 49 Burr type XII probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 burrrnd # name: # type: sq_string # elements: 1 # length: 1137 statistics: r = burrrnd ( lambda , c , k ) statistics: r = burrrnd ( lambda , c , k , rows ) statistics: r = burrrnd ( lambda , c , k , rows , cols , …) statistics: r = burrrnd ( lambda , c , k , [ sz ]) Random arrays from the Burr type XII distribution. r = burrrnd ( lambda , c , k ) returns an array of random numbers chosen from the Burr type XII distribution with scale parameter lambda , first shape parameter c , and second shape parameter k . The size of r is the common size of lambda , c , and k . LAMBDA scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, burrrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the Burr distribution can be found at https://en.wikipedia.org/wiki/Burr_distribution See also: burrcdf, burrinv, burrpdf, burrfit, burrlike, burrstat # name: # type: sq_string # elements: 1 # length: 50 Random arrays from the Burr type XII distribution. # name: # type: sq_string # elements: 1 # length: 6 bvncdf # name: # type: sq_string # elements: 1 # length: 918 statistics: p = bvncdf ( x , mu , sigma ) statistics: p = bvncdf ( x , [], sigma ) Bivariate normal cumulative distribution function (CDF). p = bvncdf ( x , mu , sigma ) will compute the bivariate normal cumulative distribution function of x given a mean parameter mu and a scale parameter sigma . x must be an N×2 matrix with each variable as a column vector. mu can be either a scalar (common mean) or a two-element row vector (each element corresponds to a variable). If empty, a zero mean is assumed. sigma can be a scalar (common variance) or a 2×2 covariance matrix, which must be positive definite. Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: mvncdf # name: # type: sq_string # elements: 1 # length: 56 Bivariate normal cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 6 bvtcdf # name: # type: sq_string # elements: 1 # length: 842 statistics: p = bvtcdf ( x , rho , df ) statistics: p = bvtcdf ( x , rho , df , Tol ) Bivariate Student’s t cumulative distribution function (CDF). p = bvtcdf ( x , rho , df ) will compute the bivariate student’s t cumulative distribution function of x , which must be an N×2 matrix, given a correlation coefficient rho , which must be a scalar, and df degrees of freedom, which can be a scalar or a vector of positive numbers commensurate with x . Tol is the tolerance for numerical integration and by default Tol = 1e-8 . Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: mvtcdf # name: # type: sq_string # elements: 1 # length: 61 Bivariate Student's t cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 9 cauchycdf # name: # type: sq_string # elements: 1 # length: 1044 statistics: p = cauchycdf ( x , x0 , gamma ) statistics: p = cauchycdf ( x , x0 , gamma , 'upper' ) Cauchy cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the Cauchy distribution with location parameter x0 and scale parameter gamma . The size of p is the common size of x , x0 , and gamma . A scalar input functions as a constant matrix of the same size as the other inputs. p = cauchycdf ( x , x0 , gamma , "upper") computes the upper tail probability of the Cauchy distribution with parameters x0 and gamma , at the values in x . Further information about the Cauchy distribution can be found at https://en.wikipedia.org/wiki/Cauchy_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: cauchyinv, cauchypdf, cauchyrnd # name: # type: sq_string # elements: 1 # length: 46 Cauchy cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 9 cauchyinv # name: # type: sq_string # elements: 1 # length: 842 statistics: x = cauchyinv ( p , x0 , gamma ) Inverse of the Cauchy cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the Cauchy distribution with location parameter x0 and scale parameter gamma . The size of x is the common size of p , x0 , and gamma . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Cauchy distribution can be found at https://en.wikipedia.org/wiki/Cauchy_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: cauchycdf, cauchypdf, cauchyrnd # name: # type: sq_string # elements: 1 # length: 62 Inverse of the Cauchy cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 9 cauchypdf # name: # type: sq_string # elements: 1 # length: 823 statistics: y = cauchypdf ( x , x0 , gamma ) Cauchy probability density function (PDF). For each element of x , compute the probability density function (PDF) of the Cauchy distribution with location parameter x0 and scale parameter gamma . The size of y is the common size of x , x0 , and gamma . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Cauchy distribution can be found at https://en.wikipedia.org/wiki/Cauchy_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: cauchycdf, cauchyinv, cauchyrnd # name: # type: sq_string # elements: 1 # length: 42 Cauchy probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 9 cauchyrnd # name: # type: sq_string # elements: 1 # length: 1053 statistics: r = cauchyrnd ( x0 , gamma ) statistics: r = cauchyrnd ( x0 , gamma , rows ) statistics: r = cauchyrnd ( x0 , gamma , rows , cols , …) statistics: r = cauchyrnd ( x0 , gamma , [ sz ]) Random arrays from the Cauchy distribution. r = cauchyrnd ( x0 , gamma ) returns an array of random numbers chosen from the Cauchy distribution with location parameter x0 and scale parameter gamma . The size of r is the common size of x0 and gamma . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, cauchyrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the Cauchy distribution can be found at https://en.wikipedia.org/wiki/Cauchy_distribution See also: cauchycdf, cauchyinv, cauchypdf # name: # type: sq_string # elements: 1 # length: 43 Random arrays from the Cauchy distribution. # name: # type: sq_string # elements: 1 # length: 7 chi2cdf # name: # type: sq_string # elements: 1 # length: 1133 statistics: p = chi2cdf ( x , df ) statistics: p = chi2cdf ( x , df , 'upper' ) Chi-squared cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the chi-squared distribution with df degrees of freedom. The chi-squared density function with df degrees of freedom is the same as a gamma density function with parameters df /2 and 2 . The size of p is the common size of x and df . A scalar input functions as a constant matrix of the same size as the other input. p = chi2cdf ( x , df , "upper") computes the upper tail probability of the chi-squared distribution with df degrees of freedom, at the values in x . Further information about the chi-squared distribution can be found at https://en.wikipedia.org/wiki/Chi-squared_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: chi2inv, chi2pdf, chi2rnd, chi2stat # name: # type: sq_string # elements: 1 # length: 51 Chi-squared cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 chi2inv # name: # type: sq_string # elements: 1 # length: 819 statistics: x = chi2inv ( p , df ) Inverse of the chi-squared cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the chi-squared distribution with df degrees of freedom. The size of x is the common size of p and df . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the chi-squared distribution can be found at https://en.wikipedia.org/wiki/Chi-squared_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: chi2cdf, chi2pdf, chi2rnd, chi2stat # name: # type: sq_string # elements: 1 # length: 67 Inverse of the chi-squared cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 chi2pdf # name: # type: sq_string # elements: 1 # length: 800 statistics: y = chi2pdf ( x , df ) Chi-squared probability density function (PDF). For each element of x , compute the probability density function (PDF) of the chi-squared distribution with df degrees of freedom. The size of y is the common size of x and df . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the chi-squared distribution can be found at https://en.wikipedia.org/wiki/Chi-squared_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: chi2cdf, chi2inv, chi2rnd, chi2stat # name: # type: sq_string # elements: 1 # length: 47 Chi-squared probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 chi2rnd # name: # type: sq_string # elements: 1 # length: 897 statistics: r = chi2rnd ( df ) statistics: r = chi2rnd ( df , rows ) statistics: r = chi2rnd ( df , rows , cols , …) statistics: r = chi2rnd ( df , [ sz ]) Random arrays from the chi-squared distribution. r = chi2rnd ( df ) returns an array of random numbers chosen from the chi-squared distribution with df degrees of freedom. The size of r is the size of df . When called with a single size argument, chi2rnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the chi-squared distribution can be found at https://en.wikipedia.org/wiki/Chi-squared_distribution See also: chi2cdf, chi2inv, chi2pdf, chi2stat # name: # type: sq_string # elements: 1 # length: 48 Random arrays from the chi-squared distribution. # name: # type: sq_string # elements: 1 # length: 9 copulacdf # name: # type: sq_string # elements: 1 # length: 2092 statistics: p = copulacdf ( family , x , theta ) statistics: p = copulacdf (’t’, x , theta , df ) Copula family cumulative distribution functions (CDF). Arguments family is the copula family name. Currently, family can be 'Gaussian' for the Gaussian family, 't' for the Student’s t family, 'Clayton' for the Clayton family, 'Gumbel' for the Gumbel-Hougaard family, 'Frank' for the Frank family, 'AMH' for the Ali-Mikhail-Haq family, or 'FGM' for the Farlie-Gumbel-Morgenstern family. x is the support where each row corresponds to an observation. theta is the parameter of the copula. For the Gaussian and Student’s t copula, theta must be a correlation matrix. For bivariate copulas theta can also be a correlation coefficient. For the Clayton family, the Gumbel-Hougaard family, the Frank family, and the Ali-Mikhail-Haq family, theta must be a vector with the same number of elements as observations in x or be scalar. For the Farlie-Gumbel-Morgenstern family, theta must be a matrix of coefficients for the Farlie-Gumbel-Morgenstern polynomial where each row corresponds to one set of coefficients for an observation in x . A single row is expanded. The coefficients are in binary order. df is the degrees of freedom for the Student’s t family. df must be a vector with the same number of elements as observations in x or be scalar. Return values p is the cumulative distribution of the copula at each row of x and corresponding parameter theta . Examples x = [0.2:0.2:0.6; 0.2:0.2:0.6]; theta = [1; 2]; p = copulacdf ("Clayton", x, theta) x = [0.2:0.2:0.6; 0.2:0.1:0.4]; theta = [0.2, 0.1, 0.1, 0.05]; p = copulacdf ("FGM", x, theta) References Roger B. Nelsen. An Introduction to Copulas . Springer, New York, second edition, 2006. Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: copulapdf, copularnd # name: # type: sq_string # elements: 1 # length: 54 Copula family cumulative distribution functions (CDF). # name: # type: sq_string # elements: 1 # length: 9 copulapdf # name: # type: sq_string # elements: 1 # length: 2317 statistics: y = copulapdf ( family , x , theta ) statistics: y = copulapdf (’t’, x , theta , df ) Copula family probability density functions (PDF). Arguments family is the copula family name. Currently, family can be 'Gaussian' for the Gaussian family, 't' for the Student’s t family, 'Clayton' for the Clayton family, 'Gumbel' for the Gumbel-Hougaard family, 'Frank' for the Frank family, 'AMH' for the Ali-Mikhail-Haq family, or 'FGM' for the Farlie-Gumbel-Morgenstern family. The last two are Octave extensions that MATLAB does not provide. x is the support where each row corresponds to an observation. theta is the parameter of the copula. For the Gaussian and Student’s t families it is the linear correlation matrix, and a scalar is expanded to a bivariate one. For the remaining families the elements of theta must be greater than or equal to -1 for the Clayton family, greater than or equal to 1 for the Gumbel-Hougaard family, arbitrary for the Frank family, and greater than or equal to -1 and lower than 1 for the Ali-Mikhail-Haq family. Moreover, theta must be non-negative for dimensions greater than 2 . theta must be a column vector with the same number of rows as x or be scalar. The Farlie-Gumbel-Morgenstern family instead takes one parameter for every subset of the variables of order two or more, so theta is a row vector of length 2^d-d-1 or a matrix with one such row per observation; parameter sets violating the family’s linear constraints give NaN . df is the degrees of freedom of the Student’s t family, and is required by it. It must be a vector with the same number of rows as x or be scalar. Return values y is the probability density of the copula at each row of x and corresponding parameter theta . Examples x = [0.2:0.2:0.6; 0.2:0.2:0.6]; theta = [1; 2]; y = copulapdf ("Clayton", x, theta) y = copulapdf ("Gumbel", x, 2) References Roger B. Nelsen. An Introduction to Copulas . Springer, New York, second edition, 2006. Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: copulacdf, copularnd # name: # type: sq_string # elements: 1 # length: 50 Copula family probability density functions (PDF). # name: # type: sq_string # elements: 1 # length: 9 copularnd # name: # type: sq_string # elements: 1 # length: 2132 statistics: r = copularnd ( family , theta , n ) statistics: r = copularnd ( family , theta , n , d ) statistics: r = copularnd (’t’, theta , df , n ) Random arrays from the copula family distributions. Arguments family is the copula family name. Currently, family can be 'Gaussian' for the Gaussian family, 't' for the Student’s t family, 'Clayton' for the Clayton family, 'Frank' for the Frank family, 'Gumbel' for the Gumbel-Hougaard family, 'AMH' for the Ali-Mikhail-Haq family, or 'FGM' for the Farlie-Gumbel-Morgenstern family. The last two are Octave extensions that MATLAB does not provide. Every family but Clayton is generated as bivariate only. theta is the parameter of the copula. For the Gaussian and Student’s t copula, theta must be a correlation matrix. For bivariate copulas theta can also be a correlation coefficient. For the Clayton, Frank and Gumbel-Hougaard families, theta must be a vector with the same number of elements as samples to be generated or be scalar. Values outside a family’s range give NaN rows: at or above 1 for the Gumbel-Hougaard family, at or above -1 for the bivariate Clayton family, and any finite value for the Frank family. The Ali-Mikhail-Haq family takes theta in [-1, 1) and the Farlie-Gumbel-Morgenstern family in [-1, 1] . df is the degrees of freedom for the Student’s t family. df must be a vector with the same number of elements as samples to be generated or be scalar. n is the number of rows of the matrix to be generated. n must be a non-negative integer and corresponds to the number of samples to be generated. d is the number of columns of the matrix to be generated. d must be a positive integer and corresponds to the dimension of the copula. Return values r is a matrix of random samples from the copula with n samples of distribution dimension d . Examples theta = 0.5; r = copularnd ("Gaussian", theta); theta = 0.5; df = 2; r = copularnd ("t", theta, df); theta = 0.5; n = 2; r = copularnd ("Clayton", theta, n); References Roger B. Nelsen. An Introduction to Copulas . Springer, New York, second edition, 2006. # name: # type: sq_string # elements: 1 # length: 51 Random arrays from the copula family distributions. # name: # type: sq_string # elements: 1 # length: 5 evcdf # name: # type: sq_string # elements: 1 # length: 2204 statistics: p = evcdf ( x ) statistics: p = evcdf ( x , mu ) statistics: p = evcdf ( x , mu , sigma ) statistics: p = evcdf (…, 'upper' ) statistics: [ p , plo , pup ] = evcdf ( x , mu , sigma , pcov ) statistics: [ p , plo , pup ] = evcdf ( x , mu , sigma , pcov , alpha ) statistics: [ p , plo , pup ] = evcdf (…, 'upper' ) Extreme value cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the extreme value distribution (also known as the Gumbel or the type I generalized extreme value distribution) at the values in x with location parameter mu and scale parameter sigma . The size of p is the common size of x , mu and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Default values are mu = 0 and sigma = 1. When called with three output arguments, i.e. [ p , plo , pup ] , evcdf computes the confidence bounds for p when the input parameters mu and sigma are estimates. In such case, pcov , a 2×2 matrix containing the covariance matrix of the estimated parameters, is necessary. Optionally, alpha , which has a default value of 0.05, specifies the 100 * (1 - alpha ) percent confidence bounds. plo and pup are arrays of the same size as p containing the lower and upper confidence bounds. […] = evcdf (…, "upper") computes the upper tail probability of the extreme value distribution with parameters x0 and gamma , at the values in x . The Gumbel distribution is used to model the distribution of the maximum (or the minimum) of a number of samples of various distributions. This version is suitable for modeling minima. For modeling maxima, use the alternative Gumbel CDF, gumbelcdf . Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: evinv, evpdf, evrnd, evfit, evlike, evstat, gumbelcdf # name: # type: sq_string # elements: 1 # length: 53 Extreme value cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 5 evinv # name: # type: sq_string # elements: 1 # length: 1933 statistics: x = evinv ( p ) statistics: x = evinv ( p , mu ) statistics: x = evinv ( p , mu , sigma ) statistics: [ x , xlo , xup ] = evinv ( p , mu , sigma , pcov ) statistics: [ x , xlo , xup ] = evinv ( p , mu , sigma , pcov , alpha ) Inverse of the extreme value cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the extreme value distribution (also known as the Gumbel or the type I generalized extreme value distribution) with location parameter mu and scale parameter sigma . The size of x is the common size of p , mu and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Default values are mu = 0 and sigma = 1. When called with three output arguments, i.e. [ x , xlo , xup ] , evinv computes the confidence bounds for x when the input parameters mu and sigma are estimates. In such case, pcov , a 2×2 matrix containing the covariance matrix of the estimated parameters, is necessary. Optionally, alpha , which has a default value of 0.05, specifies the 100 * (1 - alpha ) percent confidence bounds. xlo and xup are arrays of the same size as x containing the lower and upper confidence bounds. The Gumbel distribution is used to model the distribution of the maximum (or the minimum) of a number of samples of various distributions. This version is suitable for modeling minima. For modeling maxima, use the alternative Gumbel iCDF, gumbelinv . Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: evcdf, evpdf, evrnd, evfit, evlike, evstat, gumbelinv # name: # type: sq_string # elements: 1 # length: 69 Inverse of the extreme value cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 5 evpdf # name: # type: sq_string # elements: 1 # length: 1288 statistics: y = evpdf ( x ) statistics: y = evpdf ( x , mu ) statistics: y = evpdf ( x , mu , sigma ) Extreme value probability density function (PDF). For each element of x , compute the probability density function (PDF) of the extreme value distribution (also known as the Gumbel or the type I generalized extreme value distribution) with location parameter mu and scale parameter sigma . The size of y is the common size of x , mu and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Default values are mu = 0 and sigma = 1. The Gumbel distribution is used to model the distribution of the maximum (or the minimum) of a number of samples of various distributions. This version is suitable for modeling minima. For modeling maxima, use the alternative Gumbel iCDF, gumbelinv . Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: evcdf, evinv, evrnd, evfit, evlike, evstat, gumbelpdf # name: # type: sq_string # elements: 1 # length: 49 Extreme value probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 5 evrnd # name: # type: sq_string # elements: 1 # length: 1386 statistics: r = evrnd ( mu , sigma ) statistics: r = evrnd ( mu , sigma , rows ) statistics: r = evrnd ( mu , sigma , rows , cols , …) statistics: r = evrnd ( mu , sigma , [ sz ]) Random arrays from the extreme value distribution. r = evrnd ( mu , sigma ) returns an array of random numbers chosen from the extreme value distribution (also known as the Gumbel or the type I generalized extreme value distribution) with location parameter mu and scale parameter sigma . The size of r is the common size of mu and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, evrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . The Gumbel distribution is used to model the distribution of the maximum (or the minimum) of a number of samples of various distributions. This version is suitable for modeling minima. For modeling maxima, use the alternative Gumbel iCDF, gumbelinv . Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution See also: evcdf, evinv, evpdf, evfit, evlike, evstat # name: # type: sq_string # elements: 1 # length: 50 Random arrays from the extreme value distribution. # name: # type: sq_string # elements: 1 # length: 6 expcdf # name: # type: sq_string # elements: 1 # length: 1993 statistics: p = expcdf ( x ) statistics: p = expcdf ( x , mu ) statistics: p = expcdf (…, 'upper' ) statistics: [ p , plo , pup ] = expcdf ( x , mu , pcov ) statistics: [ p , plo , pup ] = expcdf ( x , mu , pcov , alpha ) statistics: [ p , plo , pup ] = expcdf (…, 'upper' ) Exponential cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the exponential distribution with mean parameter mu . The size of p is the common size of x and mu . A scalar input functions as a constant matrix of the same size as the other inputs. Default value is mu = 1. A common alternative parameterization of the exponential distribution is to use the parameter λ defined as the mean number of events in an interval as opposed to the parameter μ , which is the mean wait time for an event to occur. λ and μ are reciprocals, i.e. μ = 1 / λ . When called with three output arguments, i.e. [ p , plo , pup ] , expcdf computes the confidence bounds for p when the input parameter mu is an estimate. In such case, pcov , a scalar value with the variance of the estimated parameter mu , is necessary. Optionally, alpha , which has a default value of 0.05, specifies the 100 * (1 - alpha ) percent confidence bounds. plo and pup are arrays of the same size as p containing the lower and upper confidence bounds. […] = expcdf (…, "upper") computes the upper tail probability of the exponential distribution with parameter mu , at the values in x . Further information about the exponential distribution can be found at https://en.wikipedia.org/wiki/Exponential_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: expinv, exppdf, exprnd, expfit, explike, expstat # name: # type: sq_string # elements: 1 # length: 51 Exponential cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 6 expinv # name: # type: sq_string # elements: 1 # length: 1740 statistics: x = expinv ( p ) statistics: x = expinv ( p , mu ) statistics: [ x , xlo , xup ] = expinv ( p , mu , pcov ) statistics: [ x , xlo , xup ] = expinv ( p , mu , pcov , alpha ) Inverse of the exponential cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the exponential distribution with mean mu . The size of x is the common size of p and mu . A scalar input functions as a constant matrix of the same size as the other inputs. Default value is mu = 1. A common alternative parameterization of the exponential distribution is to use the parameter λ defined as the mean number of events in an interval as opposed to the parameter μ , which is the mean wait time for an event to occur. λ and μ are reciprocals, i.e. μ = 1 / λ . When called with three output arguments, i.e. [ x , xlo , xup ] , expinv computes the confidence bounds for x when the input parameter mu is an estimate. In such case, pcov , a scalar value with the variance of the estimated parameter mu , is necessary. Optionally, alpha , which has a default value of 0.05, specifies the 100 * (1 - alpha ) percent confidence bounds. xlo and xup are arrays of the same size as x containing the lower and upper confidence bounds. Further information about the exponential distribution can be found at https://en.wikipedia.org/wiki/Exponential_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: expcdf, exppdf, exprnd, expfit, explike, expstat # name: # type: sq_string # elements: 1 # length: 67 Inverse of the exponential cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 6 exppdf # name: # type: sq_string # elements: 1 # length: 1145 statistics: y = exppdf ( x ) statistics: y = exppdf ( x , mu ) Exponential probability density function (PDF). For each element of x , compute the probability density function (PDF) of the exponential distribution with mean parameter mu . The size of y is the common size of x and mu . A scalar input functions as a constant matrix of the same size as the other inputs. Default value for mu = 1. A common alternative parameterization of the exponential distribution is to use the parameter λ defined as the mean number of events in an interval as opposed to the parameter μ , which is the mean wait time for an event to occur. λ and μ are reciprocals, i.e. μ = 1 / λ . Further information about the exponential distribution can be found at https://en.wikipedia.org/wiki/Exponential_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: expcdf, expinv, exprnd, expfit, explike, expstat # name: # type: sq_string # elements: 1 # length: 47 Exponential probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 6 exprnd # name: # type: sq_string # elements: 1 # length: 1181 statistics: r = exprnd ( mu ) statistics: r = exprnd ( mu , rows ) statistics: r = exprnd ( mu , rows , cols , …) statistics: r = exprnd ( mu , [ sz ]) Random arrays from the exponential distribution. r = exprnd ( mu ) returns an array of random numbers chosen from the exponential distribution with mean parameter mu . The size of r is the size of mu . When called with a single size argument, exprnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . A common alternative parameterization of the exponential distribution is to use the parameter λ defined as the mean number of events in an interval as opposed to the parameter μ , which is the mean wait time for an event to occur. λ and μ are reciprocals, i.e. μ = 1 / λ . Further information about the exponential distribution can be found at https://en.wikipedia.org/wiki/Exponential_distribution See also: expcdf, expinv, exppdf, expfit, explike, expstat # name: # type: sq_string # elements: 1 # length: 48 Random arrays from the exponential distribution. # name: # type: sq_string # elements: 1 # length: 4 fcdf # name: # type: sq_string # elements: 1 # length: 984 statistics: p = fcdf ( x , df1 , df2 ) statistics: p = fcdf ( x , df1 , df2 , 'upper' ) F -cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the F -distribution with df1 and df2 degrees of freedom. The size of p is the common size of x , df1 , and df2 . A scalar input functions as a constant matrix of the same size as the other inputs. p = fcdf ( x , df1 , df2 , "upper") computes the upper tail probability of the F -distribution with df1 and df2 degrees of freedom, at the values in x . Further information about the F -distribution can be found at https://en.wikipedia.org/wiki/F-distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: finv, fpdf, frnd, fstat # name: # type: sq_string # elements: 1 # length: 41 F-cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 4 finv # name: # type: sq_string # elements: 1 # length: 792 statistics: x = finv ( p , df1 , df2 ) Inverse of the F -cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the F -distribution with df1 and df2 degrees of freedom. The size of x is the common size of p , df1 , and df2 . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the F -distribution can be found at https://en.wikipedia.org/wiki/F-distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: fcdf, fpdf, frnd, fstat # name: # type: sq_string # elements: 1 # length: 57 Inverse of the F-cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 4 fpdf # name: # type: sq_string # elements: 1 # length: 773 statistics: y = fpdf ( x , df1 , df2 ) F -probability density function (PDF). For each element of x , compute the probability density function (PDF) of the F -distribution with df1 and df2 degrees of freedom. The size of y is the common size of x , df1 , and df2 . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the F -distribution can be found at https://en.wikipedia.org/wiki/F-distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: fcdf, finv, frnd, fstat # name: # type: sq_string # elements: 1 # length: 37 F-probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 4 frnd # name: # type: sq_string # elements: 1 # length: 974 statistics: r = frnd ( df1 , df2 ) statistics: r = frnd ( df1 , df2 , rows ) statistics: r = frnd ( df1 , df2 , rows , cols , …) statistics: r = frnd ( df1 , df2 , [ sz ]) Random arrays from the F -distribution. r = frnd ( df1 , df2 ) returns an array of random numbers chosen from the F -distribution with df1 and df2 degrees of freedom. The size of r is the common size of df1 and df2 . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, frnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the F -distribution can be found at https://en.wikipedia.org/wiki/F-distribution See also: fcdf, finv, fpdf, fstat # name: # type: sq_string # elements: 1 # length: 38 Random arrays from the F-distribution. # name: # type: sq_string # elements: 1 # length: 6 gamcdf # name: # type: sq_string # elements: 1 # length: 2180 statistics: p = gamcdf ( x , a ) statistics: p = gamcdf ( x , a , b ) statistics: p = gamcdf (…, 'upper' ) statistics: [ p , plo , pup ] = gamcdf ( x , a , b , pcov ) statistics: [ p , plo , pup ] = gamcdf ( x , a , b , pcov , alpha ) statistics: [ p , plo , pup ] = gamcdf (…, 'upper' ) Gamma cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the Gamma distribution with shape parameter a and scale parameter b . When called with only one parameter, then b defaults to 1. The size of p is the common size of x , a , and b . A scalar input functions as a constant matrix of the same size as the other inputs. When called with three output arguments, i.e. [ p , plo , pup ] , gamcdf computes the confidence bounds for p when the input parameters a and b are estimates. In such case, pcov , a 2×2 matrix containing the covariance matrix of the estimated parameters, is necessary. Optionally, alpha , which has a default value of 0.05, specifies the 100 * (1 - alpha ) percent confidence bounds. plo and pup are arrays of the same size as p containing the lower and upper confidence bounds. […] = gamcdf (…, "upper") computes the upper tail probability of the Gamma distribution with parameters a and b , at the values in x . OCTAVE/MATLAB use the alternative parameterization given by the pair α, β , i.e. shape a and scale b . In Wikipedia, the two common parameterizations use the pairs k, θ , as shape and scale, and α, β , as shape and rate, respectively. The parameter names a and b used here (for MATLAB compatibility) correspond to the parameter notation k, θ instead of the α, β as reported in Wikipedia. Further information about the Gamma distribution can be found at https://en.wikipedia.org/wiki/Gamma_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: gaminv, gampdf, gamrnd, gamfit, gamlike, gamstat # name: # type: sq_string # elements: 1 # length: 45 Gamma cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 6 gaminv # name: # type: sq_string # elements: 1 # length: 1231 statistics: x = gaminv ( p , a , b ) Inverse of the Gamma cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the Gamma distribution with shape parameter a and scale parameter b . The size of x is the common size of p , a , and b . A scalar input functions as a constant matrix of the same size as the other inputs. OCTAVE/MATLAB use the alternative parameterization given by the pair α, β , i.e. shape a and scale b . In Wikipedia, the two common parameterizations use the pairs k, θ , as shape and scale, and α, β , as shape and rate, respectively. The parameter names a and b used here (for MATLAB compatibility) correspond to the parameter notation k, θ instead of the α, β as reported in Wikipedia. Further information about the Gamma distribution can be found at https://en.wikipedia.org/wiki/Gamma_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: gamcdf, gampdf, gamrnd, gamfit, gamlike, gamstat # name: # type: sq_string # elements: 1 # length: 61 Inverse of the Gamma cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 6 gampdf # name: # type: sq_string # elements: 1 # length: 1210 statistics: y = gampdf ( x , a , b ) Gamma probability density function (PDF). For each element of x , compute the probability density function (PDF) of the Gamma distribution with shape parameter a and scale parameter b . The size of y is the common size of x , a and b . A scalar input functions as a constant matrix of the same size as the other inputs. OCTAVE/MATLAB use the alternative parameterization given by the pair α, β , i.e. shape a and scale b . In Wikipedia, the two common parameterizations use the pairs k, θ , as shape and scale, and α, β , as shape and rate, respectively. The parameter names a and b used here (for MATLAB compatibility) correspond to the parameter notation k, θ instead of the α, β as reported in Wikipedia. Further information about the Gamma distribution can be found at https://en.wikipedia.org/wiki/Gamma_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: gamcdf, gaminv, gamrnd, gamfit, gamlike, gamstat # name: # type: sq_string # elements: 1 # length: 41 Gamma probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 6 gamrnd # name: # type: sq_string # elements: 1 # length: 1407 statistics: r = gamrnd ( a , b ) statistics: r = gamrnd ( a , b , rows ) statistics: r = gamrnd ( a , b , rows , cols , …) statistics: r = gamrnd ( a , b , [ sz ]) Random arrays from the Gamma distribution. r = gamrnd ( a , b ) returns an array of random numbers chosen from the Gamma distribution with shape parameter a and scale parameter b . The size of r is the common size of a and b . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, gamrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . OCTAVE/MATLAB use the alternative parameterization given by the pair α, β , i.e. shape a and scale b . In Wikipedia, the two common parameterizations use the pairs k, θ , as shape and scale, and α, β , as shape and rate, respectively. The parameter names a and b used here (for MATLAB compatibility) correspond to the parameter notation k, θ instead of the α, β as reported in Wikipedia. Further information about the Gamma distribution can be found at https://en.wikipedia.org/wiki/Gamma_distribution See also: gamcdf, gaminv, gampdf, gamfit, gamlike, gamstat # name: # type: sq_string # elements: 1 # length: 42 Random arrays from the Gamma distribution. # name: # type: sq_string # elements: 1 # length: 6 geocdf # name: # type: sq_string # elements: 1 # length: 1200 statistics: p = geocdf ( x , ps ) statistics: p = geocdf ( x , ps , 'upper' ) Geometric cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the geometric distribution with probability of success parameter ps . The size of p is the common size of x and ps . A scalar input functions as a constant matrix of the same size as the other inputs. p = geocdf ( x , ps , "upper") computes the upper tail probability of the geometric distribution with parameter ps , at the values in x . The geometric distribution models the number of failures ( x ) of a Bernoulli trial with probability ps before the first success. Further information about the geometric distribution can be found at https://en.wikipedia.org/wiki/Geometric_distribution Input arguments must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. MATLAB is inconsistent here: for several of the discrete distributions it returns the result in the integer class of the input, truncating a probability to 0 or 1 . See also: geoinv, geopdf, geornd, geofit, geostat # name: # type: sq_string # elements: 1 # length: 49 Geometric cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 6 geoinv # name: # type: sq_string # elements: 1 # length: 960 statistics: x = geoinv ( p , ps ) Inverse of the geometric cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the geometric distribution with probability of success parameter ps . The size of x is the common size of p and ps . A scalar input functions as a constant matrix of the same size as the other inputs. The geometric distribution models the number of failures ( p ) of a Bernoulli trial with probability ps before the first success. Further information about the geometric distribution can be found at https://en.wikipedia.org/wiki/Geometric_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: geocdf, geopdf, geornd, geofit, geostat # name: # type: sq_string # elements: 1 # length: 65 Inverse of the geometric cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 6 geopdf # name: # type: sq_string # elements: 1 # length: 1009 statistics: y = geopdf ( x , ps ) Geometric probability density function (PDF). For each element of x , compute the probability density function (PDF) of the geometric distribution with probability of success parameter ps . The size of y is the common size of x and ps . A scalar input functions as a constant matrix of the same size as the other inputs. The geometric distribution models the number of failures ( x ) of a Bernoulli trial with probability ps before the first success. Further information about the geometric distribution can be found at https://en.wikipedia.org/wiki/Geometric_distribution Input arguments must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. MATLAB is inconsistent here: for several of the discrete distributions it returns the result in the integer class of the input, truncating a probability to 0 or 1 . See also: geocdf, geoinv, geornd, geofit, geostat # name: # type: sq_string # elements: 1 # length: 45 Geometric probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 6 geornd # name: # type: sq_string # elements: 1 # length: 1041 statistics: r = geornd ( ps ) statistics: r = geornd ( ps , rows ) statistics: r = geornd ( ps , rows , cols , …) statistics: r = geornd ( ps , [ sz ]) Random arrays from the geometric distribution. r = geornd ( ps ) returns an array of random numbers chosen from the Birnbaum-Saunders distribution with probability of success parameter ps . The size of r is the size of ps . When called with a single size argument, geornd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . The geometric distribution models the number of failures ( x ) of a Bernoulli trial with probability ps before the first success. Further information about the geometric distribution can be found at https://en.wikipedia.org/wiki/Geometric_distribution See also: geocdf, geoinv, geopdf, geofit, geostat # name: # type: sq_string # elements: 1 # length: 46 Random arrays from the geometric distribution. # name: # type: sq_string # elements: 1 # length: 6 gevcdf # name: # type: sq_string # elements: 1 # length: 1837 statistics: p = gevcdf ( x , k , sigma , mu ) statistics: p = gevcdf ( x , k , sigma , mu , 'upper' ) Generalized extreme value (GEV) cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the GEV distribution with shape parameter k , scale parameter sigma , and location parameter mu . The size of p is the common size of x , k , sigma , and mu . A scalar input functions as a constant matrix of the same size as the other inputs. […] = gevcdf ( x , k , sigma , mu , "upper") computes the upper tail probability of the GEV distribution with parameters k , sigma , and mu , at the values in x . When k < 0 , the GEV is the type III extreme value distribution. When k > 0 , the GEV distribution is the type II, or Frechet, extreme value distribution. If W has a Weibull distribution as computed by the wblcdf function, then - W has a type III extreme value distribution and 1/ W has a type II extreme value distribution. In the limit as k approaches 0 , the GEV is the mirror image of the type I extreme value distribution as computed by the evcdf function. The mean of the GEV distribution is not finite when k >= 1 , and the variance is not finite when k >= 1/2 . The GEV distribution has positive density only for values of x such that k * ( x - mu ) / sigma > -1 . Further information about the generalized extreme value distribution can be found at https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: gevinv, gevpdf, gevrnd, gevfit, gevlike, gevstat # name: # type: sq_string # elements: 1 # length: 71 Generalized extreme value (GEV) cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 6 gevinv # name: # type: sq_string # elements: 1 # length: 1621 statistics: x = gevinv ( p , k , sigma , mu ) Inverse of the generalized extreme value (GEV) cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the GEV distribution with shape parameter k , scale parameter sigma , and location parameter mu . The size of p is the common size of x , k , sigma , and mu . A scalar input functions as a constant matrix of the same size as the other inputs. When k < 0 , the GEV is the type III extreme value distribution. When k > 0 , the GEV distribution is the type II, or Frechet, extreme value distribution. If W has a Weibull distribution as computed by the wblcdf function, then - W has a type III extreme value distribution and 1/ W has a type II extreme value distribution. In the limit as k approaches 0 , the GEV is the mirror image of the type I extreme value distribution as computed by the evcdf function. The mean of the GEV distribution is not finite when k >= 1 , and the variance is not finite when k >= 1/2 . The GEV distribution has positive density only for values of x such that k * ( x - mu ) / sigma > -1 . Further information about the generalized extreme value distribution can be found at https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: gevcdf, gevpdf, gevrnd, gevfit, gevlike, gevstat # name: # type: sq_string # elements: 1 # length: 87 Inverse of the generalized extreme value (GEV) cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 6 gevpdf # name: # type: sq_string # elements: 1 # length: 1602 statistics: y = gevpdf ( x , k , sigma , mu ) Generalized extreme value (GEV) probability density function (PDF). For each element of x , compute the probability density function (PDF) of the GEV distribution with shape parameter k , scale parameter sigma , and location parameter mu . The size of y is the common size of x , k , sigma , and mu . A scalar input functions as a constant matrix of the same size as the other inputs. When k < 0 , the GEV is the type III extreme value distribution. When k > 0 , the GEV distribution is the type II, or Frechet, extreme value distribution. If W has a Weibull distribution as computed by the wblcdf function, then - W has a type III extreme value distribution and 1/ W has a type II extreme value distribution. In the limit as k approaches 0 , the GEV is the mirror image of the type I extreme value distribution as computed by the evcdf function. The mean of the GEV distribution is not finite when k >= 1 , and the variance is not finite when k >= 1/2 . The GEV distribution has positive density only for values of x such that k * ( x - mu ) / sigma > -1 . Further information about the generalized extreme value distribution can be found at https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: gevcdf, gevinv, gevrnd, gevfit, gevlike, gevstat # name: # type: sq_string # elements: 1 # length: 67 Generalized extreme value (GEV) probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 6 gevrnd # name: # type: sq_string # elements: 1 # length: 1833 statistics: r = gevrnd ( k , sigma , mu ) statistics: r = gevrnd ( k , sigma , mu , rows ) statistics: r = gevrnd ( k , sigma , mu , rows , cols , …) statistics: r = gevrnd ( k , sigma , mu , [ sz ]) Random arrays from the generalized extreme value (GEV) distribution. r = gevrnd ( k , sigma , mu returns an array of random numbers chosen from the GEV distribution with shape parameter k , scale parameter sigma , and location parameter mu . The size of r is the common size of k , sigma , and mu . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, gevrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . When k < 0 , the GEV is the type III extreme value distribution. When k > 0 , the GEV distribution is the type II, or Frechet, extreme value distribution. If W has a Weibull distribution as computed by the wblcdf function, then - W has a type III extreme value distribution and 1/ W has a type II extreme value distribution. In the limit as k approaches 0 , the GEV is the mirror image of the type I extreme value distribution as computed by the evcdf function. The mean of the GEV distribution is not finite when k >= 1 , and the variance is not finite when k >= 1/2 . The GEV distribution has positive density only for values of x such that k * ( x - mu ) / sigma > -1 . Further information about the generalized extreme value distribution can be found at https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution See also: gevcdf, gevinv, gevpdf, gevfit, gevlike, gevstat # name: # type: sq_string # elements: 1 # length: 68 Random arrays from the generalized extreme value (GEV) distribution. # name: # type: sq_string # elements: 1 # length: 5 gpcdf # name: # type: sq_string # elements: 1 # length: 1613 statistics: p = gpcdf ( x , k , sigma , theta ) statistics: p = gpcdf ( x , k , sigma , theta , 'upper' ) Generalized Pareto cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the generalized Pareto distribution with shape parameter k , scale parameter sigma , and location parameter theta . The size of p is the common size of x , k , sigma , and theta . A scalar input functions as a constant matrix of the same size as the other inputs. […] = gpcdf( x , k , sigma , theta , "upper") computes the upper tail probability of the generalized Pareto distribution with parameters k , sigma , and theta , at the values in x . When k = 0 and theta = 0 , the Generalized Pareto is equivalent to the exponential distribution. When k > 0 and theta = k / k the Generalized Pareto is equivalent τπ the Pareto distribution. The mean of the Generalized Pareto is not finite when k >= 1 and the variance is not finite when k >= 1/2 . When k >= 0 , the Generalized Pareto has positive density for x > theta , or, when theta < 0 , for 0 <= ( x - theta ) / sigma <= -1 / k . Further information about the generalized Pareto distribution can be found at https://en.wikipedia.org/wiki/Generalized_Pareto_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: gpinv, gppdf, gprnd, gpfit, gplike, gpstat # name: # type: sq_string # elements: 1 # length: 58 Generalized Pareto cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 5 gpinv # name: # type: sq_string # elements: 1 # length: 1374 statistics: x = gpinv ( p , k , sigma , theta ) Inverse of the generalized Pareto cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the generalized Pareto distribution with shape parameter k , scale parameter sigma , and location parameter theta . The size of x is the common size of p , k , sigma , and theta . A scalar input functions as a constant matrix of the same size as the other inputs. When k = 0 and theta = 0 , the Generalized Pareto is equivalent to the exponential distribution. When k > 0 and theta = k / k the Generalized Pareto is equivalent to the Pareto distribution. The mean of the Generalized Pareto is not finite when k >= 1 and the variance is not finite when k >= 1/2 . When k >= 0 , the Generalized Pareto has positive density for x > theta , or, when theta < 0 , for 0 <= ( x - theta ) / sigma <= -1 / k . Further information about the generalized Pareto distribution can be found at https://en.wikipedia.org/wiki/Generalized_Pareto_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: gpcdf, gppdf, gprnd, gpfit, gplike, gpstat # name: # type: sq_string # elements: 1 # length: 74 Inverse of the generalized Pareto cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 5 gppdf # name: # type: sq_string # elements: 1 # length: 1921 statistics: y = gppdf ( x , k , sigma , theta ) Generalized Pareto probability density function (PDF). For each element of x , compute the probability density function (PDF) of the generalized Pareto distribution with shape parameter k , scale parameter sigma , and location parameter theta . The size of y is the common size of p , k , sigma , and theta . A scalar input functions as a constant matrix of the same size as the other inputs. When k = 0 and theta = 0 , the Generalized Pareto is equivalent to the exponential distribution. When k > 0 and theta = k / k the Generalized Pareto is equivalent to the Pareto distribution. The mean of the Generalized Pareto is not finite when k >= 1 and the variance is not finite when k >= 1/2 . When k >= 0 , the Generalized Pareto has positive density for x > theta , or, when theta < 0 , for 0 <= ( x - theta ) / sigma <= -1 / k . Further information about the generalized Pareto distribution can be found at https://en.wikipedia.org/wiki/Generalized_Pareto_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. With a negative shape parameter the support is the closed interval [ theta , theta - sigma / k ] , and the density at its upper endpoint follows the limit of the density there: 0 for -1 < k < 0 , 1/ sigma at k = -1 , and unbounded for k < -1 . MATLAB returns 0 at that endpoint whatever the shape, which contradicts its own unifpdf : the generalized Pareto with k = -1 is the uniform distribution on [ theta , theta + sigma ] , for which MATLAB’s unifpdf returns 1/ sigma at the same point. This implementation returns the limit, and so agrees with unifpdf . See also: gpcdf, gpinv, gprnd, gpfit, gplike, gpstat # name: # type: sq_string # elements: 1 # length: 54 Generalized Pareto probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 5 gprnd # name: # type: sq_string # elements: 1 # length: 1595 statistics: r = gprnd ( k , sigma , theta ) statistics: r = gprnd ( k , sigma , theta , rows ) statistics: r = gprnd ( k , sigma , theta , rows , cols , …) statistics: r = gprnd ( k , sigma , theta , [ sz ]) Random arrays from the generalized Pareto distribution. r = gprnd ( k , sigma , theta ) returns an array of random numbers chosen from the generalized Pareto distribution with shape parameter k , scale parameter sigma , and location parameter theta . The size of r is the common size of k , sigma , and theta . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, gprnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . When k = 0 and theta = 0 , the Generalized Pareto is equivalent to the exponential distribution. When k > 0 and theta = k / k the Generalized Pareto is equivalent to the Pareto distribution. The mean of the Generalized Pareto is not finite when k >= 1 and the variance is not finite when k >= 1/2 . When k >= 0 , the Generalized Pareto has positive density for x > theta , or, when theta < 0 , for 0 <= ( x - theta ) / sigma <= -1 / k . Further information about the generalized Pareto distribution can be found at https://en.wikipedia.org/wiki/Generalized_Pareto_distribution See also: gpcdf, gpinv, gppdf, gpfit, gplike, gpstat # name: # type: sq_string # elements: 1 # length: 55 Random arrays from the generalized Pareto distribution. # name: # type: sq_string # elements: 1 # length: 9 gumbelcdf # name: # type: sq_string # elements: 1 # length: 2346 statistics: p = gumbelcdf ( x ) statistics: p = gumbelcdf ( x , mu ) statistics: p = gumbelcdf ( x , mu , beta ) statistics: p = gumbelcdf (…, 'upper' ) statistics: [ p , plo , pup ] = gumbelcdf ( x , mu , beta , pcov ) statistics: [ p , plo , pup ] = gumbelcdf ( x , mu , beta , pcov , alpha ) statistics: [ p , plo , pup ] = gumbelcdf (…, 'upper' ) Gumbel cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the Gumbel distribution (also known as the extreme value or the type I generalized extreme value distribution) with location parameter mu and scale parameter beta . The size of p is the common size of x , mu and beta . A scalar input functions as a constant matrix of the same size as the other inputs. Default values are mu = 0 and beta = 1. When called with three output arguments, i.e. [ p , plo , pup ] , gumbelcdf computes the confidence bounds for p when the input parameters mu and beta are estimates. In such case, pcov , a 2×2 matrix containing the covariance matrix of the estimated parameters, is necessary. Optionally, alpha , which has a default value of 0.05, specifies the 100 * (1 - alpha ) percent confidence bounds. plo and pup are arrays of the same size as p containing the lower and upper confidence bounds. […] = gumbelcdf (…, "upper") computes the upper tail probability of the Gumbel distribution with parameters mu and beta , at the values in x . The Gumbel distribution is used to model the distribution of the maximum (or the minimum) of a number of samples of various distributions. This version is suitable for modeling maxima. For modeling minima, use the alternative extreme value CDF, evcdf . […] = gumbelcdf (…, "upper") computes the upper tail probability of the extreme value (Gumbel) distribution. Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: gumbelinv, gumbelpdf, gumbelrnd, gumbelfit, gumbellike, gumbelstat, evcdf # name: # type: sq_string # elements: 1 # length: 46 Gumbel cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 9 gumbelinv # name: # type: sq_string # elements: 1 # length: 1966 statistics: x = gumbelinv ( p ) statistics: x = gumbelinv ( p , mu ) statistics: x = gumbelinv ( p , mu , beta ) statistics: [ x , xlo , xup ] = gumbelinv ( p , mu , beta , pcov ) statistics: [ x , xlo , xup ] = gumbelinv ( p , mu , beta , pcov , alpha ) Inverse of the Gumbel cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the Gumbel distribution (also known as the extreme value or the type I generalized extreme value distribution) with location parameter mu and scale parameter beta . The size of x is the common size of p , mu and beta . A scalar input functions as a constant matrix of the same size as the other inputs. Default values are mu = 0 and beta = 1. When called with three output arguments, i.e. [ x , xlo , xup ] , gumbelinv computes the confidence bounds for x when the input parameters mu and beta are estimates. In such case, pcov , a 2×2 matrix containing the covariance matrix of the estimated parameters, is necessary. Optionally, alpha , which has a default value of 0.05, specifies the 100 * (1 - alpha ) percent confidence bounds. xlo and xup are arrays of the same size as x containing the lower and upper confidence bounds. The Gumbel distribution is used to model the distribution of the maximum (or the minimum) of a number of samples of various distributions. This version is suitable for modeling maxima. For modeling minima, use the alternative extreme value iCDF, evinv . Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: gumbelcdf, gumbelpdf, gumbelrnd, gumbelfit, gumbellike, gumbelstat, evinv # name: # type: sq_string # elements: 1 # length: 62 Inverse of the Gumbel cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 9 gumbelpdf # name: # type: sq_string # elements: 1 # length: 1312 statistics: y = gumbelpdf ( x ) statistics: y = gumbelpdf ( x , mu ) statistics: y = gumbelpdf ( x , mu , beta ) Gumbel probability density function (PDF). For each element of x , compute the probability density function (PDF) of the Gumbel distribution (also known as the extreme value or the type I generalized extreme value distribution) with location parameter mu and scale parameter beta . The size of y is the common size of x , mu and beta . A scalar input functions as a constant matrix of the same size as the other inputs. Default values are mu = 0 and beta = 1. The Gumbel distribution is used to model the distribution of the maximum (or the minimum) of a number of samples of various distributions. This version is suitable for modeling maxima. For modeling minima, use the alternative extreme value iCDF, evpdf . Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: gumbelcdf, gumbelinv, gumbelrnd, gumbelfit, gumbellike, gumbelstat, evpdf # name: # type: sq_string # elements: 1 # length: 42 Gumbel probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 9 gumbelrnd # name: # type: sq_string # elements: 1 # length: 1430 statistics: r = gumbelrnd ( mu , beta ) statistics: r = gumbelrnd ( mu , beta , rows ) statistics: r = gumbelrnd ( mu , beta , rows , cols , …) statistics: r = gumbelrnd ( mu , beta , [ sz ]) Random arrays from the Gumbel distribution. r = gumbelrnd ( mu , beta ) returns an array of random numbers chosen from the Gumbel distribution (also known as the extreme value or the type I generalized extreme value distribution) with location parameter mu and scale parameter beta . The size of r is the common size of mu and beta . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, gumbelrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . The Gumbel distribution is used to model the distribution of the maximum (or the minimum) of a number of samples of various distributions. This version is suitable for modeling maxima. For modeling minima, use the alternative extreme value iCDF, evinv . Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution See also: gumbelcdf, gumbelinv, gumbelpdf, gumbelfit, gumbellike, gumbelstat, evrnd # name: # type: sq_string # elements: 1 # length: 43 Random arrays from the Gumbel distribution. # name: # type: sq_string # elements: 1 # length: 5 hncdf # name: # type: sq_string # elements: 1 # length: 1126 statistics: p = hncdf ( x , mu , sigma ) statistics: p = hncdf ( x , mu , sigma , 'upper' ) Half-normal cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the half-normal distribution with location parameter mu and scale parameter sigma . The size of p is the common size of x , mu and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. […] = hncdf ( x , mu , sigma , "upper") computes the upper tail probability of the half-normal distribution with parameters mu and sigma , at the values in x . The half-normal CDF is only defined for x >= mu . Further information about the half-normal distribution can be found at https://en.wikipedia.org/wiki/Half-normal_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: hninv, hnpdf, hnrnd, hnfit, hnlike, hnstat # name: # type: sq_string # elements: 1 # length: 51 Half-normal cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 5 hninv # name: # type: sq_string # elements: 1 # length: 869 statistics: x = hninv ( p , mu , sigma ) Inverse of the half-normal cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the half-normal distribution with location parameter mu and scale parameter sigma . The size of x is the common size of p , mu , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the half-normal distribution can be found at https://en.wikipedia.org/wiki/Half-normal_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: hncdf, hnpdf, hnrnd, hnfit, hnlike, hnstat # name: # type: sq_string # elements: 1 # length: 67 Inverse of the half-normal cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 5 hnpdf # name: # type: sq_string # elements: 1 # length: 901 statistics: y = hnpdf ( x , mu , sigma ) Half-normal probability density function (PDF). For each element of x , compute the probability density function (PDF) of the half-normal distribution with location parameter mu and scale parameter sigma . The size of y is the common size of x , mu , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. The half-normal CDF is only defined for x >= mu . Further information about the half-normal distribution can be found at https://en.wikipedia.org/wiki/Half-normal_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: hncdf, hninv, hnrnd, hnfit, hnlike, hnstat # name: # type: sq_string # elements: 1 # length: 47 Half-normal probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 5 hnrnd # name: # type: sq_string # elements: 1 # length: 1060 statistics: r = hnrnd ( mu , sigma ) statistics: r = hnrnd ( mu , sigma , rows ) statistics: r = hnrnd ( mu , sigma , rows , cols , …) statistics: r = hnrnd ( mu , sigma , [ sz ]) Random arrays from the half-normal distribution. r = hnrnd ( mu , sigma ) returns an array of random numbers chosen from the half-normal distribution with location parameter mu and scale parameter sigma . The size of r is the common size of mu and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, hnrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the half-normal distribution can be found at https://en.wikipedia.org/wiki/Half-normal_distribution See also: hncdf, hninv, hnpdf, hnfit, hnlike, hnstat # name: # type: sq_string # elements: 1 # length: 48 Random arrays from the half-normal distribution. # name: # type: sq_string # elements: 1 # length: 7 hygecdf # name: # type: sq_string # elements: 1 # length: 1422 statistics: p = hygecdf ( x , m , k , n ) statistics: p = hygecdf ( x , m , k , n , 'upper' ) Hypergeometric cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the hypergeometric distribution with parameters m , k , and n . The size of p is the common size of x , m , k , and n . A scalar input functions as a constant matrix of the same size as the other inputs. This is the cumulative probability of obtaining not more than x marked items when randomly drawing a sample of size n without replacement from a population of total size m containing k marked items. The parameters m , k , and n must be positive integers with k and n not greater than m . […] = hygecdf ( x , m , k , n , "upper") computes the upper tail probability of the hypergeometric distribution with parameters m , k , and n , at the values in x . Further information about the hypergeometric distribution can be found at https://en.wikipedia.org/wiki/Hypergeometric_distribution Input arguments must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. MATLAB is inconsistent here: for several of the discrete distributions it returns the result in the integer class of the input, truncating a probability to 0 or 1 . See also: hygeinv, hygepdf, hygernd, hygestat # name: # type: sq_string # elements: 1 # length: 54 Hypergeometric cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 hygeinv # name: # type: sq_string # elements: 1 # length: 1130 statistics: x = hygeinv ( p , m , k , n ) Inverse of the hypergeometric cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the hypergeometric distribution with parameters m , k , and n . The size of x is the common size of p , m , k , and n . A scalar input functions as a constant matrix of the same size as the other inputs. This is the number of drawn marked items x given a probability p , when randomly drawing a sample of size n without replacement from a population of total size m containing k marked items. The parameters m , k , and n must be positive integers with k and n not greater than m . Further information about the hypergeometric distribution can be found at https://en.wikipedia.org/wiki/Hypergeometric_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: hygecdf, hygepdf, hygernd, hygestat # name: # type: sq_string # elements: 1 # length: 70 Inverse of the hypergeometric cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 hygepdf # name: # type: sq_string # elements: 1 # length: 1581 statistics: y = hygepdf ( x , m , k , n ) statistics: y = hygepdf (…, 'vectorexpand' ) Hypergeometric probability density function (PDF). For each element of x , compute the probability density function (PDF) of the hypergeometric distribution with parameters m , k , and n . The size of y is the common size of x , m , k , and n . A scalar input functions as a constant matrix of the same size as the other inputs. This is the probability of obtaining x marked items when randomly drawing a sample of size n without replacement from a population of total size m containing k marked items. The parameters m , k , and n must be positive integers with k and n not greater than m . If the optional parameter vectorexpand is provided, x may be an array with size different from parameters m , k , and n (which must still be of a common size or scalar). Each element of x will be evaluated against each set of parameters m , k , and n in columnwise order. The output y will be an array of size r x s , where r = numel ( m ) , and s = numel ( x ) . Further information about the hypergeometric distribution can be found at https://en.wikipedia.org/wiki/Hypergeometric_distribution Input arguments must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. MATLAB is inconsistent here: for several of the discrete distributions it returns the result in the integer class of the input, truncating a probability to 0 or 1 . See also: hygecdf, hygeinv, hygernd, hygestat # name: # type: sq_string # elements: 1 # length: 50 Hypergeometric probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 hygernd # name: # type: sq_string # elements: 1 # length: 1139 statistics: r = hygernd ( m , k , n ) statistics: r = hygernd ( m , k , n , rows ) statistics: r = hygernd ( m , k , n , rows , cols , …) statistics: r = hygernd ( m , k , n , [ sz ]) Random arrays from the hypergeometric distribution. r = hygernd (( m , k , n returns an array of random numbers chosen from the hypergeometric distribution with parameters m , k , and n . The size of r is the common size of m , k , and n . A scalar input functions as a constant matrix of the same size as the other inputs. The parameters m , k , and n must be positive integers with k and n not greater than m . When called with a single size argument, hygernd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the hypergeometric distribution can be found at https://en.wikipedia.org/wiki/Hypergeometric_distribution See also: hygecdf, hygeinv, hygepdf, hygestat # name: # type: sq_string # elements: 1 # length: 51 Random arrays from the hypergeometric distribution. # name: # type: sq_string # elements: 1 # length: 7 invgcdf # name: # type: sq_string # elements: 1 # length: 1182 statistics: p = invgcdf ( x , mu , lambda ) statistics: p = invgcdf ( x , mu , lambda , 'upper' ) Inverse Gaussian cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the inverse Gaussian distribution with scale parameter mu and shape parameter lambda . The size of p is the common size of x , mu and lambda . A scalar input functions as a constant matrix of the same size as the other inputs. p = invgcdf ( x , mu , lambda , "upper") computes the upper tail probability of the inverse Gaussian distribution with parameters mu and lambda , at the values in x . The inverse Gaussian CDF is only defined for mu > 0 and lambda > 0 . Further information about the inverse Gaussian distribution can be found at https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: invginv, invgpdf, invgrnd, invgfit, invglike, invgstat # name: # type: sq_string # elements: 1 # length: 56 Inverse Gaussian cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 invginv # name: # type: sq_string # elements: 1 # length: 973 statistics: x = invginv ( p , mu , lambda ) Inverse of the inverse Gaussian cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the inverse Gaussian distribution with scale parameter mu and shape parameter lambda . The size of x is the common size of p , mu , and lambda . A scalar input functions as a constant matrix of the same size as the other inputs. The inverse Gaussian CDF is only defined for mu > 0 and lambda > 0 . Further information about the inverse Gaussian distribution can be found at https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: invgcdf, invgpdf, invgrnd, invgfit, invglike, invgstat # name: # type: sq_string # elements: 1 # length: 72 Inverse of the inverse Gaussian cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 invgpdf # name: # type: sq_string # elements: 1 # length: 954 statistics: y = invgpdf ( x , mu , lambda ) Inverse Gaussian probability density function (PDF). For each element of x , compute the probability density function (PDF) of the inverse Gaussian distribution with scale parameter mu and shape parameter lambda . The size of y is the common size of x , mu , and lambda . A scalar input functions as a constant matrix of the same size as the other inputs. The inverse Gaussian CDF is only defined for mu > 0 and lambda > 0 . Further information about the inverse Gaussian distribution can be found at https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: invgcdf, invginv, invgrnd, invgfit, invglike, invgstat # name: # type: sq_string # elements: 1 # length: 52 Inverse Gaussian probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 invgrnd # name: # type: sq_string # elements: 1 # length: 1181 statistics: r = invgrnd ( mu , lambda ) statistics: r = invgrnd ( mu , lambda , rows ) statistics: r = invgrnd ( mu , lambda , rows , cols , …) statistics: r = invgrnd ( mu , lambda , [ sz ]) Random arrays from the inverse Gaussian distribution. r = invgrnd ( mu , lambda ) returns an array of random numbers chosen from the inverse Gaussian distribution with location parameter mu and scale parameter lambda . The size of r is the common size of mu and lambda . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, invgrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . The inverse Gaussian CDF is only defined for mu > 0 and lambda > 0 . Further information about the inverse Gaussian distribution can be found at https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution See also: invgcdf, invginv, invgpdf, invgfit, invglike, invgstat # name: # type: sq_string # elements: 1 # length: 53 Random arrays from the inverse Gaussian distribution. # name: # type: sq_string # elements: 1 # length: 8 iwishpdf # name: # type: sq_string # elements: 1 # length: 914 statistics: y = iwishpdf ( W , Tau , df , log_y =false) Compute the probability density function of the inverse Wishart distribution. Inputs: A p x p matrix W where to find the PDF and the p x p positive definite scale matrix Tau and scalar degrees of freedom parameter df characterizing the inverse Wishart distribution. (For the density to be finite, need df > ( p - 1).) If the flag log_y is set, return the log probability density – this helps avoid underflow when the numerical value of the density is very small. Output: y is the probability density of Wishart( Sigma , df ) at W . Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: iwishrnd, wishpdf, wishrnd # name: # type: sq_string # elements: 1 # length: 77 Compute the probability density function of the inverse Wishart distribution. # name: # type: sq_string # elements: 1 # length: 8 iwishrnd # name: # type: sq_string # elements: 1 # length: 974 statistics: [ W , DI ] = iwishrnd ( Tau , df , DI , n =1) Return a random matrix sampled from the inverse Wishart distribution with given parameters. Inputs: the p × p positive definite matrix Tau and scalar degrees of freedom parameter df (and optionally the transposed Cholesky factor DI of Sigma = inv(Tau) ). df can be non-integer as long as df > d Output: a random p × p matrix W from the inverse Wishart( Tau , df ) distribution. ( inv(W) is from the Wishart( inv(Tau) , df ) distribution.) If n > 1, then W is p x p x n and holds n such random matrices. (Optionally, the transposed Cholesky factor DI of Sigma is also returned.) Averaged across many samples, the mean of W should approach Tau / ( df - p - 1). References Yu-Cheng Ku and Peter Bloomfield (2010), Generating Random Wishart Matrices with Fractional Degrees of Freedom in OX, http://www.gwu.edu/~forcpgm/YuChengKu-030510final-WishartYu-ChengKu.pdf See also: iwishpdf, wishpdf, wishrnd # name: # type: sq_string # elements: 1 # length: 91 Return a random matrix sampled from the inverse Wishart distribution with given parameters. # name: # type: sq_string # elements: 1 # length: 6 jsucdf # name: # type: sq_string # elements: 1 # length: 819 statistics: p = jsucdf ( x ) statistics: p = jsucdf ( x , alpha1 ) statistics: p = jsucdf ( x , alpha1 , alpha2 ) Johnson SU cumulative distribution function (CDF). For each element of x , return the cumulative distribution functions (CDF) at x of the Johnson SU distribution with shape parameters alpha1 and alpha2 . The size of p is the common size of the input arguments x , alpha1 , and alpha2 . A scalar input functions as a constant matrix of the same size as the other Default values are alpha1 = 1, alpha2 = 1. Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: jsupdf # name: # type: sq_string # elements: 1 # length: 50 Johnson SU cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 6 jsupdf # name: # type: sq_string # elements: 1 # length: 811 statistics: y = jsupdf ( x ) statistics: y = jsupdf ( x , alpha1 ) statistics: y = jsupdf ( x , alpha1 , alpha2 ) Johnson SU probability density function (PDF). For each element of x , compute the probability density function (PDF) at x of the Johnson SU distribution with shape parameters alpha1 and alpha2 . The size of p is the common size of the input arguments x , alpha1 , and alpha2 . A scalar input functions as a constant matrix of the same size as the other Default values are alpha1 = 1, alpha2 = 1. Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: jsucdf # name: # type: sq_string # elements: 1 # length: 46 Johnson SU probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 10 laplacecdf # name: # type: sq_string # elements: 1 # length: 1147 statistics: p = laplacecdf ( x , mu , beta ) statistics: p = laplacecdf ( x , mu , beta , 'upper' ) Laplace cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the Laplace distribution with location parameter mu and scale parameter (i.e. "diversity") beta . The size of p is the common size of x , mu , and beta . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be reals and beta > 0 . For beta <= 0 , NaN is returned. p = laplacecdf ( x , mu , beta , "upper") computes the upper tail probability of the Laplace distribution with parameters mu and beta , at the values in x . Further information about the Laplace distribution can be found at https://en.wikipedia.org/wiki/Laplace_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: laplaceinv, laplacepdf, laplacernd # name: # type: sq_string # elements: 1 # length: 47 Laplace cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 10 laplaceinv # name: # type: sq_string # elements: 1 # length: 945 statistics: x = laplaceinv ( p , mu , beta ) Inverse of the Laplace cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the Laplace distribution with location parameter mu and scale parameter (i.e. "diversity") beta . The size of x is the common size of p , mu , and beta . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be reals and beta > 0 . For beta <= 0 , NaN is returned. Further information about the Laplace distribution can be found at https://en.wikipedia.org/wiki/Laplace_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: laplacecdf, laplacepdf, laplacernd # name: # type: sq_string # elements: 1 # length: 63 Inverse of the Laplace cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 10 laplacepdf # name: # type: sq_string # elements: 1 # length: 926 statistics: y = laplacepdf ( x , mu , beta ) Laplace probability density function (PDF). For each element of x , compute the probability density function (PDF) of the Laplace distribution with location parameter mu and scale parameter (i.e. "diversity") beta . The size of y is the common size of x , mu , and beta . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be reals and beta > 0 . For beta <= 0 , NaN is returned. Further information about the Laplace distribution can be found at https://en.wikipedia.org/wiki/Laplace_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: laplacecdf, laplaceinv, laplacernd # name: # type: sq_string # elements: 1 # length: 43 Laplace probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 10 laplacernd # name: # type: sq_string # elements: 1 # length: 1138 statistics: r = laplacernd ( mu , beta ) statistics: r = laplacernd ( mu , beta , rows ) statistics: r = laplacernd ( mu , beta , rows , cols , …) statistics: r = laplacernd ( mu , beta , [ sz ]) Random arrays from the Laplace distribution. r = laplacernd ( mu , beta ) returns an array of random numbers chosen from the Laplace distribution with location parameter mu and scale parameter beta . The size of r is the common size of mu and beta . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be reals and beta > 0 . For beta <= 0 , NaN is returned. When called with a single size argument, laplacernd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the Laplace distribution can be found at https://en.wikipedia.org/wiki/Laplace_distribution See also: laplacecdf, laplaceinv, laplacepdf # name: # type: sq_string # elements: 1 # length: 44 Random arrays from the Laplace distribution. # name: # type: sq_string # elements: 1 # length: 7 logicdf # name: # type: sq_string # elements: 1 # length: 1152 statistics: p = logicdf ( x , mu , sigma ) statistics: p = logicdf ( x , mu , sigma , 'upper' ) Logistic cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the logistic distribution with location parameter mu and scale parameter sigma . The size of p is the common size of x , mu , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be reals and sigma > 0 . For sigma <= 0 , NaN is returned. p = logicdf ( x , mu , sigma , "upper") computes the upper tail probability of the logistic distribution with parameters mu and sigma , at the values in x . Further information about the logistic distribution can be found at https://en.wikipedia.org/wiki/Logistic_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: logiinv, logipdf, logirnd, logifit, logilike, logistat # name: # type: sq_string # elements: 1 # length: 48 Logistic cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 logiinv # name: # type: sq_string # elements: 1 # length: 952 statistics: x = logiinv ( p , mu , sigma ) Inverse of the logistic cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the logistic distribution with location parameter mu and scale parameter sigma . The size of p is the common size of x , mu , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be reals and sigma > 0 . For sigma <= 0 , NaN is returned. Further information about the logistic distribution can be found at https://en.wikipedia.org/wiki/Logistic_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: logicdf, logipdf, logirnd, logifit, logilike, logistat # name: # type: sq_string # elements: 1 # length: 64 Inverse of the logistic cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 logipdf # name: # type: sq_string # elements: 1 # length: 933 statistics: y = logipdf ( x , mu , sigma ) Logistic probability density function (PDF). For each element of x , compute the probability density function (PDF) of the logistic distribution with location parameter mu and scale parameter sigma . The size of p is the common size of x , mu , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be reals and sigma > 0 . For sigma <= 0 , NaN is returned. Further information about the logistic distribution can be found at https://en.wikipedia.org/wiki/Logistic_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: logicdf, logiinv, logirnd, logifit, logilike, logistat # name: # type: sq_string # elements: 1 # length: 44 Logistic probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 logirnd # name: # type: sq_string # elements: 1 # length: 1152 statistics: r = logirnd ( mu , sigma ) statistics: r = logirnd ( mu , sigma , rows ) statistics: r = logirnd ( mu , sigma , rows , cols , …) statistics: r = logirnd ( mu , sigma , [ sz ]) Random arrays from the logistic distribution. r = logirnd ( mu , sigma ) returns an array of random numbers chosen from the logistic distribution with location parameter mu and scale parameter sigma . The size of r is the common size of mu and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be reals and sigma > 0 . For sigma <= 0 , NaN is returned. When called with a single size argument, logirnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the logistic distribution can be found at https://en.wikipedia.org/wiki/Logistic_distribution See also: logcdf, logiinv, logipdf, logifit, logilike, logistat # name: # type: sq_string # elements: 1 # length: 45 Random arrays from the logistic distribution. # name: # type: sq_string # elements: 1 # length: 7 loglcdf # name: # type: sq_string # elements: 1 # length: 1541 statistics: p = loglcdf ( x , mu , sigma ) statistics: p = loglcdf ( x , mu , sigma , 'upper' ) Loglogistic cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the loglogistic distribution with mean parameter mu and scale parameter sigma . The size of p is the common size of x , mu , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Mean of logarithmic values mu must be a non-negative real value, scale parameter of logarithmic values sigma must be a positive real value and x is supported in the range [0,Inf) , otherwise NaN is returned. p = loglcdf ( x , mu , sigma , "upper") computes the upper tail probability of the log-logistic distribution with parameters mu and sigma , at the values in x . Further information about the loglogistic distribution can be found at https://en.wikipedia.org/wiki/Log-logistic_distribution OCTAVE/MATLAB use an alternative parameterization given by the pair μ, σ , i.e. mu and sigma , in analogy with the logistic distribution. Their relation to the α and b parameters used in Wikipedia are given below: mu = log ( a ) sigma = 1 / a Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: loglinv, loglpdf, loglrnd, loglfit, logllike, loglstat # name: # type: sq_string # elements: 1 # length: 51 Loglogistic cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 loglinv # name: # type: sq_string # elements: 1 # length: 1337 statistics: x = loglinv ( p , mu , sigma ) Inverse of the log-logistic cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the log-logistic distribution with mean parameter mu and scale parameter sigma . The size of x is the common size of p , mu , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Mean of logarithmic values mu must be a non-negative real value, scale parameter of logarithmic values sigma must be a positive real value and p is supported in the range [0,1] , otherwise NaN is returned. Further information about the loglogistic distribution can be found at https://en.wikipedia.org/wiki/Log-logistic_distribution OCTAVE/MATLAB use an alternative parameterization given by the pair μ, σ , i.e. mu and sigma , in analogy with the logistic distribution. Their relation to the α and b parameters used in Wikipedia are given below: mu = log ( a ) sigma = 1 / a Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: loglcdf, loglpdf, loglrnd, loglfit, logllike, loglstat # name: # type: sq_string # elements: 1 # length: 68 Inverse of the log-logistic cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 loglpdf # name: # type: sq_string # elements: 1 # length: 1316 statistics: y = loglpdf ( x , mu , sigma ) Loglogistic probability density function (PDF). For each element of x , compute the probability density function (PDF) of the loglogistic distribution with mean parameter mu and scale parameter sigma . The size of y is the common size of x , mu , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Mean of logarithmic values mu must be a non-negative real value, scale parameter of logarithmic values sigma must be a positive real value and x is supported in the range [0,Inf) , otherwise 0 is returned. Further information about the loglogistic distribution can be found at https://en.wikipedia.org/wiki/Log-logistic_distribution OCTAVE/MATLAB use an alternative parameterization given by the pair μ, σ , i.e. mu and sigma , in analogy with the logistic distribution. Their relation to the α and b parameters used in Wikipedia are given below: mu = log ( a ) sigma = 1 / a Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: loglcdf, loglinv, loglrnd, loglfit, logllike, loglstat # name: # type: sq_string # elements: 1 # length: 47 Loglogistic probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 loglrnd # name: # type: sq_string # elements: 1 # length: 1476 statistics: r = loglrnd ( mu , sigma ) statistics: r = loglrnd ( mu , sigma , rows ) statistics: r = loglrnd ( mu , sigma , rows , cols , …) statistics: r = loglrnd ( mu , sigma , [ sz ]) Random arrays from the loglogistic distribution. r = loglrnd ( mu , sigma ) returns an array of random numbers chosen from the loglogistic distribution with mean parameter mu and scale parameter sigma . The size of r is the common size of mu and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Mean of logarithmic values mu must be a non-negative real value and scale parameter of logarithmic values sigma must be a positive real value. When called with mu single size argument, loglrnd returns mu square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with mu row vector of dimensions, sz . Further information about the loglogistic distribution can be found at https://en.wikipedia.org/wiki/Log-logistic_distribution OCTAVE/MATLAB use an alternative parameterization given by the pair μ, σ , i.e. mu and sigma , in analogy with the logistic distribution. Their relation to the α and b parameters used in Wikipedia are given below: mu = log ( a ) sigma = 1 / a See also: loglcdf, loglinv, loglpdf, loglfit, logllike, loglstat # name: # type: sq_string # elements: 1 # length: 48 Random arrays from the loglogistic distribution. # name: # type: sq_string # elements: 1 # length: 7 logncdf # name: # type: sq_string # elements: 1 # length: 2160 statistics: p = logncdf ( x ) statistics: p = logncdf ( x , mu ) statistics: p = logncdf ( x , mu , sigma ) statistics: p = logncdf (…, 'upper' ) statistics: [ p , plo , pup ] = logncdf ( x , mu , sigma , pcov ) statistics: [ p , plo , pup ] = logncdf ( x , mu , sigma , pcov , alpha ) statistics: [ p , plo , pup ] = logncdf (…, 'upper' ) Lognormal cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the lognormal distribution with mean parameter mu and standard deviation parameter sigma , each corresponding to the associated normal distribution. The size of p is the common size of x , mu , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. If a random variable follows this distribution, its logarithm is normally distributed with mean mu and standard deviation sigma . Default parameter values are mu = 0 and sigma = 1 . Both parameters must be reals and sigma > 0 . For sigma <= 0 , NaN is returned. When called with three output arguments, i.e. [ p , plo , pup ] , logncdf computes the confidence bounds for p when the input parameters mu and sigma are estimates. In such case, pcov , a 2×2 matrix containing the covariance matrix of the estimated parameters, is necessary. Optionally, alpha , which has a default value of 0.05, specifies the 100 * (1 - alpha ) percent confidence bounds. plo and pup are arrays of the same size as p containing the lower and upper confidence bounds. […] = logncdf (…, "upper") computes the upper tail probability of the log-normal distribution with parameters mu and sigma , at the values in x . Further information about the lognormal distribution can be found at https://en.wikipedia.org/wiki/Log-normal_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: logninv, lognpdf, lognrnd, lognfit, lognlike, lognstat # name: # type: sq_string # elements: 1 # length: 49 Lognormal cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 logninv # name: # type: sq_string # elements: 1 # length: 1272 statistics: x = logninv ( p ) statistics: x = logninv ( p , mu ) statistics: x = logninv ( p , mu , sigma ) Inverse of the lognormal cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the lognormal distribution with mean parameter mu and standard deviation parameter sigma , each corresponding to the associated normal distribution. The size of x is the common size of p , mu , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. If a random variable follows this distribution, its logarithm is normally distributed with mean mu and standard deviation sigma . Default parameter values are mu = 0 and sigma = 1 . Both parameters must be reals and sigma > 0 . For sigma <= 0 , NaN is returned. Further information about the lognormal distribution can be found at https://en.wikipedia.org/wiki/Log-normal_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: logncdf, lognpdf, lognrnd, lognfit, lognlike, lognstat # name: # type: sq_string # elements: 1 # length: 65 Inverse of the lognormal cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 lognpdf # name: # type: sq_string # elements: 1 # length: 1253 statistics: y = lognpdf ( x ) statistics: y = lognpdf ( x , mu ) statistics: y = lognpdf ( x , mu , sigma ) Lognormal probability density function (PDF). For each element of x , compute the probability density function (PDF) of the lognormal distribution with mean parameter mu and standard deviation parameter sigma , each corresponding to the associated normal distribution. The size of y is the common size of p , mu , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. If a random variable follows this distribution, its logarithm is normally distributed with mean mu and standard deviation sigma . Default parameter values are mu = 0 and sigma = 1 . Both parameters must be reals and sigma > 0 . For sigma <= 0 , NaN is returned. Further information about the lognormal distribution can be found at https://en.wikipedia.org/wiki/Log-normal_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: logncdf, logninv, lognrnd, lognfit, lognlike, lognstat # name: # type: sq_string # elements: 1 # length: 45 Lognormal probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 lognrnd # name: # type: sq_string # elements: 1 # length: 1307 statistics: r = lognrnd ( mu , sigma ) statistics: r = lognrnd ( mu , sigma , rows ) statistics: r = lognrnd ( mu , sigma , rows , cols , …) statistics: r = lognrnd ( mu , sigma , [ sz ]) Random arrays from the lognormal distribution. r = lognrnd ( mu , sigma ) returns an array of random numbers chosen from the lognormal distribution with mean parameter mu and standard deviation parameter sigma , each corresponding to the associated normal distribution. The size of r is the common size of mu , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be reals and sigma > 0 . For sigma <= 0 , NaN is returned. Both parameters must be reals and sigma > 0 . For sigma <= 0 , NaN is returned. When called with a single size argument, lognrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the lognormal distribution can be found at https://en.wikipedia.org/wiki/Log-normal_distribution See also: logncdf, logninv, lognpdf, lognfit, lognlike, lognstat # name: # type: sq_string # elements: 1 # length: 46 Random arrays from the lognormal distribution. # name: # type: sq_string # elements: 1 # length: 5 mnpdf # name: # type: sq_string # elements: 1 # length: 1672 statistics: y = mnpdf ( x , pk ) Multinomial probability density function (PDF). Arguments x is vector with a single sample of a multinomial distribution with parameter pk or a matrix of random samples from multinomial distributions. In the latter case, each row of x is a sample from a multinomial distribution with the corresponding row of pk being its parameter. pk is a vector with the probabilities of the categories or a matrix with each row containing the probabilities of a multinomial sample. Return values y is a vector of probabilities of the random samples x from the multinomial distribution with corresponding parameter pk . The parameter n of the multinomial distribution is the sum of the elements of each row of x . The length of y is the number of columns of x . If a row of pk does not sum to 1 , then the corresponding element of y will be NaN . Examples x = [1, 4, 2]; pk = [0.2, 0.5, 0.3]; y = mnpdf (x, pk); x = [1, 4, 2; 1, 0, 9]; pk = [0.2, 0.5, 0.3; 0.1, 0.1, 0.8]; y = mnpdf (x, pk); References Wendy L. Martinez and Angel R. Martinez. Computational Statistics Handbook with MATLAB . Appendix E, pages 547-557, Chapman & Hall/CRC, 2001. Merran Evans, Nicholas Hastings and Brian Peacock. Statistical Distributions . pages 134-136, Wiley, New York, third edition, 2000. Input arguments must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. MATLAB is inconsistent here: for several of the discrete distributions it returns the result in the integer class of the input, truncating a probability to 0 or 1 . See also: mnrnd # name: # type: sq_string # elements: 1 # length: 47 Multinomial probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 5 mnrnd # name: # type: sq_string # elements: 1 # length: 1778 statistics: r = mnrnd ( n , pk ) statistics: r = mnrnd ( n , pk , s ) Random arrays from the multinomial distribution. Arguments n is the first parameter of the multinomial distribution. n can be scalar or a vector containing the number of trials of each multinomial sample. The elements of n must be non-negative integers. pk is the second parameter of the multinomial distribution. pk can be a vector with the probabilities of the categories or a matrix with each row containing the probabilities of a multinomial sample. If pk has more than one row and n is non-scalar, then the number of rows of pk must match the number of elements of n . s is the number of multinomial samples to be generated. s must be a non-negative integer. If s is specified, then n must be scalar and pk must be a vector. Return values r is a matrix of random samples from the multinomial distribution with corresponding parameters n and pk . Each row corresponds to one multinomial sample. The number of columns, therefore, corresponds to the number of columns of pk . If s is not specified, then the number of rows of r is the maximum of the number of elements of n and the number of rows of pk . If a row of pk does not sum to 1 , then the corresponding row of r will contain only NaN values. Examples n = 10; pk = [0.2, 0.5, 0.3]; r = mnrnd (n, pk); n = 10 * ones (3, 1); pk = [0.2, 0.5, 0.3]; r = mnrnd (n, pk); n = (1:2)'; pk = [0.2, 0.5, 0.3; 0.1, 0.1, 0.8]; r = mnrnd (n, pk); References Wendy L. Martinez and Angel R. Martinez. Computational Statistics Handbook with MATLAB . Appendix E, pages 547-557, Chapman & Hall/CRC, 2001. Merran Evans, Nicholas Hastings and Brian Peacock. Statistical Distributions . pages 134-136, Wiley, New York, third edition, 2000. See also: mnpdf # name: # type: sq_string # elements: 1 # length: 48 Random arrays from the multinomial distribution. # name: # type: sq_string # elements: 1 # length: 6 mvncdf # name: # type: sq_string # elements: 1 # length: 2831 statistics: p = mvncdf ( x ) statistics: p = mvncdf ( x , mu , sigma ) statistics: p = mvncdf ( x_lo , x_up , mu , sigma ) statistics: p = mvncdf (…, options ) statistics: [ p , err ] = mvncdf (…) Multivariate normal cumulative distribution function (CDF). p = mvncdf ( x ) returns the cumulative probability of the multivariate normal distribution evaluated at each row of x with zero mean and an identity covariance matrix. The rows of matrix x correspond to observations and its columns to variables. The return argument p is a column vector with the same number of rows as in x . p = mvncdf ( x , mu , sigma ) returns cumulative probability of the multivariate normal distribution evaluated at each row of x with mean mu and a covariance matrix sigma . mu can be either a scalar (the same of every variable) or a row vector with the same number of elements as the number of variables in x . sigma covariance matrix may be specified a row vector if it only contains variances along its diagonal and zero covariances of the diagonal. In such a case, the diagonal vector sigma must have the same number of elements as the number of variables (columns) in x . If you only want to specify sigma, you can pass an empty matrix for mu . The multivariate normal cumulative probability at x is defined as the probability that a random vector V , distributed as multivariate normal, will fall within the semi-infinite rectangle with upper limits defined by x . Pr{V(1)<=X(1), V(2)<=X(2), ... V(D)<=X(D)} . p = mvncdf ( x_lo , x_hi , mu , sigma ) returns the multivariate normal cumulative probability evaluated over the rectangle (hyper-rectangle for multivariate data in x ) with lower and upper limits defined by x_lo and x_hi , respectively. [ p , err ] = mvncdf (…) also returns an error estimate err in p . p = mvncdf (…, options ) specifies the structure, which controls specific parameters for the numerical integration used to compute p . The required fields are: 'TolFun' Maximum absolute error tolerance. Default is 1e-8 for D < 4, or 1e-4 for D >= 4. Note that for bivariate normal cdf, the Octave implementation has a precision of more than 1e-10. 'MaxFunEvals' Maximum number of integrand evaluations. Default is 1e7 for D > 4. 'Display' Display options. Choices are 'off' (default), 'iter' , which shows the probability and estimated error at each repetition, and 'final' , which shows the final probability and related error after the integrand has converged successfully. Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: bvncdf, mvnpdf, mvnrnd # name: # type: sq_string # elements: 1 # length: 59 Multivariate normal cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 6 mvnpdf # name: # type: sq_string # elements: 1 # length: 1879 statistics: y = mvnpdf ( x , mu , sigma ) Multivariate normal probability density function (PDF). y = mvnpdf ( x ) returns the probability density of the multivariate normal distribution with zero mean and identity covariance matrix, evaluated at each row of x . Rows of the N-by-D matrix x correspond to observations orpoints, and columns correspond to variables or coordinates. y is an N-by-1 vector. y = mvnpdf ( x , mu ) returns the density of the multivariate normal distribution with mean MU and identity covariance matrix, evaluated at each row of x . mu is a 1-by-D vector, or an N-by-D matrix, in which case the density is evaluated for each row of x with the corresponding row of mu . mu can also be a scalar value, which MVNPDF replicates to match the size of x . y = mvnpdf ( x , mu , sigma ) returns the density of the multivariate normal distribution with mean mu and covariance sigma , evaluated at each row of x . sigma is a D-by-D matrix, or an D-by-D-by-N array, in which case the density is evaluated for each row of x with the corresponding page of sigma , i.e., mvnpdf computes y(i) using x(i,:) and sigma(:,:,i) . If the covariance matrix is diagonal, containing variances along the diagonal and zero covariances off the diagonal, sigma may also be specified as a 1-by-D matrix or a 1-by-D-by-N array, containing just the diagonal. Pass in the empty matrix for mu to use its default value when you want to only specify sigma . If x is a 1-by-D vector, mvnpdf replicates it to match the leading dimension of mu or the trailing dimension of sigma . Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: mvncdf, mvnrnd # name: # type: sq_string # elements: 1 # length: 55 Multivariate normal probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 6 mvnrnd # name: # type: sq_string # elements: 1 # length: 1693 statistics: r = mvnrnd ( mu , sigma ) statistics: r = mvnrnd ( mu , sigma , n ) statistics: r = mvnrnd ( mu , sigma , n , T ) statistics: [ r , T ] = mvnrnd (…) Random vectors from the multivariate normal distribution. r = mvnrnd ( mu , sigma ) returns an N-by-D matrix r of random vectors chosen from the multivariate normal distribution with mean vector mu and covariance matrix sigma . mu is an N-by-D matrix, and mvnrnd generates each N of r using the corresponding N of mu . sigma is a D-by-D symmetric positive semi-definite matrix, or a D-by-D-by-N array. If sigma is an array, mvnrnd generates each N of r using the corresponding page of sigma , i.e., mvnrnd computes r(i,:) using mu(i,:) and sigma(:,:,i) . If the covariance matrix is diagonal, containing variances along the diagonal and zero covariances off the diagonal, sigma may also be specified as a 1-by-D matrix or a 1-by-D-by-N array, containing just the diagonal. If mu is a 1-by-D vector, mvnrnd replicates it to match the trailing dimension of SIGMA. r = mvnrnd ( mu , sigma , n ) returns a N-by-D matrix R of random vectors chosen from the multivariate normal distribution with 1-by-D mean vector mu , and D-by-D covariance matrix sigma . r = mvnrnd ( mu , sigma , n , T ) supplies the Cholesky factor T of sigma , so that sigma(:,:,J) == T(:,:,J) ’* T(:,:,J) if sigma is a 3D array or sigma == T ’* T if sigma is a matrix. No error checking is done on T . [ r , T ] = mvnrnd (…) returns the Cholesky factor T , so it can be re-used to make later calls more efficient, although there are greater efficiency gains when SIGMA can be specified as a diagonal instead. See also: mvncdf, mvnpdf # name: # type: sq_string # elements: 1 # length: 57 Random vectors from the multivariate normal distribution. # name: # type: sq_string # elements: 1 # length: 6 mvtcdf # name: # type: sq_string # elements: 1 # length: 2625 statistics: p = mvtcdf ( x , rho , df ) statistics: p = mvncdf ( x_lo , x_up , rho , df ) statistics: p = mvncdf (…, options ) statistics: [ p , err ] = mvncdf (…) Multivariate Student’s t cumulative distribution function (CDF). p = mvtcdf ( x , rho , df ) returns the cumulative probability of the multivariate student’s t distribution with correlation parameters rho and degrees of freedom df , evaluated at each row of x . The rows of the N×D matrix x correspond to sample observations and its columns correspond to variables or coordinates. The return argument p is a column vector with the same number of rows as in x . rho is a symmetric, positive definite, D×D correlation matrix. dF is a scalar or a vector with N elements. Note: mvtcdf computes the CDF for the standard multivariate Student’s t distribution, centered at the origin, with no scale parameters. If rho is a covariance matrix, i.e. diag( rho ) is not all ones, mvtcdf rescales rho to transform it to a correlation matrix. mvtcdf does not rescale x , though. The multivariate Student’s t cumulative probability at x is defined as the probability that a random vector T, distributed as multivariate normal, will fall within the semi-infinite rectangle with upper limits defined by x . Pr{T(1)<=X(1), T(2)<=X(2), ... T(D)<=X(D)} . p = mvtcdf ( x_lo , x_hi , rho , df ) returns the multivariate Student’s t cumulative probability evaluated over the rectangle (hyper-rectangle for multivariate data in x ) with lower and upper limits defined by x_lo and x_hi , respectively. [ p , err ] = mvtcdf (…) also returns an error estimate err in p . p = mvtcdf (…, options ) specifies the structure, which controls specific parameters for the numerical integration used to compute p . The required fields are: 'TolFun' Maximum absolute error tolerance. Default is 1e-8 for D < 4, or 1e-4 for D >= 4. 'MaxFunEvals' Maximum number of integrand evaluations when D >= 4 . Default is 1e7. Ignored when D < 4 . 'Display' Display options. Choices are 'off' (default), 'iter' , which shows the probability and estimated error at each repetition, and 'final' , which shows the final probability and related error after the integrand has converged successfully. Ignored when D < 4 . Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: bvtcdf, mvtpdf, mvtrnd # name: # type: sq_string # elements: 1 # length: 64 Multivariate Student's t cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 6 mvtpdf # name: # type: sq_string # elements: 1 # length: 1085 statistics: y = mvtpdf ( x , rho , df ) Multivariate Student’s t probability density function (PDF). Arguments x are the points at which to find the probability, where each row corresponds to an observation. ( N×D matrix) rho is the correlation matrix. ( D×D symmetric positive definite matrix) df is the degrees of freedom. (scalar or vector of length N ) The distribution is assumed to be centered (zero mean). Return values y is the probability density for each row of x . ( N×1 vector) Examples x = [1 2]; rho = [1.0 0.5; 0.5 1.0]; df = 4; y = mvtpdf (x, rho, df) References Michael Roth, On the Multivariate t Distribution, Technical report from Automatic Control at Linkoepings universitet, http://users.isy.liu.se/en/rt/roth/student.pdf Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: mvtcdf, mvtrnd # name: # type: sq_string # elements: 1 # length: 60 Multivariate Student's t probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 6 mvtrnd # name: # type: sq_string # elements: 1 # length: 1630 statistics: r = mvtrnd ( rho , df ) statistics: r = mvtrnd ( rho , df , n ) Random vectors from the multivariate Student’s t distribution. Arguments rho is the matrix of correlation coefficients. If there are any non-unit diagonal elements then rho will be normalized, so that the resulting covariance of the obtained samples r follows: cov (r) = df/(df-2) * rho ./ (sqrt (diag (rho) * diag (rho))) . In order to obtain samples distributed according to a standard multivariate student’s t-distribution, rho must be equal to the identity matrix. To generate multivariate student’s t-distribution samples r with arbitrary covariance matrix rho , the following scaling might be used: r = mvtrnd (rho, df, n) * diag (sqrt (diag (rho))) . df is the degrees of freedom for the multivariate t-distribution. df must be a vector with the same number of elements as samples to be generated or be scalar. n is the number of rows of the matrix to be generated. n must be a non-negative integer and corresponds to the number of samples to be generated. Return values r is a matrix of random samples from the multivariate t-distribution with n row samples. Examples rho = [1, 0.5; 0.5, 1]; df = 3; n = 10; r = mvtrnd (rho, df, n); rho = [1, 0.5; 0.5, 1]; df = [2; 3]; n = 2; r = mvtrnd (rho, df, 2); References Wendy L. Martinez and Angel R. Martinez. Computational Statistics Handbook with MATLAB . Appendix E, pages 547-557, Chapman & Hall/CRC, 2001. Samuel Kotz and Saralees Nadarajah. Multivariate t Distributions and Their Applications . Cambridge University Press, Cambridge, 2004. See also: mvtcdf, mvtpdf # name: # type: sq_string # elements: 1 # length: 62 Random vectors from the multivariate Student's t distribution. # name: # type: sq_string # elements: 1 # length: 7 nakacdf # name: # type: sq_string # elements: 1 # length: 1170 statistics: p = nakacdf ( x , mu , omega ) statistics: p = nakacdf ( x , mu , omega , 'upper' ) Nakagami cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the Nakagami distribution with shape parameter mu and spread parameter omega . The size of p is the common size of x , mu , and omega . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be positive reals and mu >= 0.5 . For mu < 0.5 or omega <= 0 , NaN is returned. p = nakacdf ( x , mu , omega , "upper") computes the upper tail probability of the Nakagami distribution with parameters mu and beta , at the values in x . Further information about the Nakagami distribution can be found at https://en.wikipedia.org/wiki/Nakagami_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: nakainv, nakapdf, nakarnd, nakafit, nakalike, nakastat # name: # type: sq_string # elements: 1 # length: 48 Nakagami cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 nakainv # name: # type: sq_string # elements: 1 # length: 971 statistics: x = nakacdf ( x , mu , omega ) Inverse of the Nakagami cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the Nakagami distribution with shape parameter mu and spread parameter omega . The size of x is the common size of x , mu , and omega . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be positive reals and mu >= 0.5 . For mu < 0.5 or omega <= 0 , NaN is returned. Further information about the Nakagami distribution can be found at https://en.wikipedia.org/wiki/Nakagami_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: nakacdf, nakapdf, nakarnd, nakafit, nakalike, nakastat # name: # type: sq_string # elements: 1 # length: 64 Inverse of the Nakagami cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 nakapdf # name: # type: sq_string # elements: 1 # length: 952 statistics: y = nakapdf ( x , mu , omega ) Nakagami probability density function (PDF). For each element of x , compute the probability density function (PDF) of the Nakagami distribution with shape parameter mu and spread parameter omega . The size of y is the common size of x , mu , and omega . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be positive reals and mu >= 0.5 . For mu < 0.5 or omega <= 0 , NaN is returned. Further information about the Nakagami distribution can be found at https://en.wikipedia.org/wiki/Nakagami_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: nakacdf, nakainv, nakarnd, nakafit, nakalike, nakastat # name: # type: sq_string # elements: 1 # length: 44 Nakagami probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 nakarnd # name: # type: sq_string # elements: 1 # length: 1172 statistics: r = nakarnd ( mu , omega ) statistics: r = nakarnd ( mu , omega , rows ) statistics: r = nakarnd ( mu , omega , rows , cols , …) statistics: r = nakarnd ( mu , omega , [ sz ]) Random arrays from the Nakagami distribution. r = nakarnd ( mu , omega ) returns an array of random numbers chosen from the Nakagami distribution with shape parameter mu and spread parameter omega . The size of r is the common size of mu and omega . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be positive reals and mu >= 0.5 . For mu < 0.5 or omega <= 0 , NaN is returned. When called with a single size argument, nakarnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the Nakagami distribution can be found at https://en.wikipedia.org/wiki/Nakagami_distribution See also: nakacdf, nakainv, nakapdf, nakafit, nakalike, nakastat # name: # type: sq_string # elements: 1 # length: 45 Random arrays from the Nakagami distribution. # name: # type: sq_string # elements: 1 # length: 7 nbincdf # name: # type: sq_string # elements: 1 # length: 2466 statistics: p = nbincdf ( x , r , ps ) statistics: p = nbincdf ( x , r , ps , 'upper' ) Negative binomial cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the negative binomial distribution with parameters r and ps , where r is the number of successes until the experiment is stopped and ps is the probability of success in each experiment, given the number of failures in x . The size of p is the common size of x , r , and ps . A scalar input functions as a constant matrix of the same size as the other inputs. The algorithm uses the cumulative sums of the binomial masses. p = nbincdf ( x , r , ps , "upper") computes the upper tail probability of the negative binomial distribution with parameters r and ps , at the values in x . When r is an integer, the negative binomial distribution is also known as the Pascal distribution and it models the number of failures in x before a specified number of successes is reached in a series of independent, identical trials. Its parameters are the probability of success in a single trial, ps , and the number of successes, r . A special case of the negative binomial distribution, when r = 1 , is the geometric distribution, which models the number of failures before the first success. r can also have non-integer positive values, in which form the negative binomial distribution, also known as the Polya distribution, has no interpretation in terms of repeated trials, but, like the Poisson distribution, it is useful in modeling count data. The negative binomial distribution is more general than the Poisson distribution because it has a variance that is greater than its mean, making it suitable for count data that do not meet the assumptions of the Poisson distribution. In the limit, as r increases to infinity, the negative binomial distribution approaches the Poisson distribution. Further information about the negative binomial distribution can be found at https://en.wikipedia.org/wiki/Negative_binomial_distribution Input arguments must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. MATLAB is inconsistent here: for several of the discrete distributions it returns the result in the integer class of the input, truncating a probability to 0 or 1 . See also: nbininv, nbinpdf, nbinrnd, nbinfit, nbinlike, nbinstat # name: # type: sq_string # elements: 1 # length: 57 Negative binomial cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 nbininv # name: # type: sq_string # elements: 1 # length: 2130 statistics: x = nbininv ( p , r , ps ) Inverse of the negative binomial cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the negative binomial distribution with parameters r and ps , where r is the number of successes until the experiment is stopped and ps is the probability of success in each experiment, given the probability in p . The size of x is the common size of p , r , and ps . A scalar input functions as a constant matrix of the same size as the other inputs. When r is an integer, the negative binomial distribution is also known as the Pascal distribution and it models the number of failures in x before a specified number of successes is reached in a series of independent, identical trials. Its parameters are the probability of success in a single trial, ps , and the number of successes, r . A special case of the negative binomial distribution, when r = 1 , is the geometric distribution, which models the number of failures before the first success. r can also have non-integer positive values, in which form the negative binomial distribution, also known as the Polya distribution, has no interpretation in terms of repeated trials, but, like the Poisson distribution, it is useful in modeling count data. The negative binomial distribution is more general than the Poisson distribution because it has a variance that is greater than its mean, making it suitable for count data that do not meet the assumptions of the Poisson distribution. In the limit, as r increases to infinity, the negative binomial distribution approaches the Poisson distribution. Further information about the negative binomial distribution can be found at https://en.wikipedia.org/wiki/Negative_binomial_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: nbincdf, nbinpdf, nbinrnd, nbinfit, nbinlike, nbinstat # name: # type: sq_string # elements: 1 # length: 73 Inverse of the negative binomial cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 nbinpdf # name: # type: sq_string # elements: 1 # length: 2191 statistics: y = nbinpdf ( x , r , ps ) Negative binomial probability density function (PDF). For each element of x , compute the probability density function (PDF) at x of the negative binomial distribution with parameters r and ps , where r is the number of successes until the experiment is stopped and ps is the probability of success in each experiment, given the number of failures in x . The size of y is the common size of x , r , and ps . A scalar input functions as a constant matrix of the same size as the other inputs. When r is an integer, the negative binomial distribution is also known as the Pascal distribution and it models the number of failures in x before a specified number of successes is reached in a series of independent, identical trials. Its parameters are the probability of success in a single trial, ps , and the number of successes, r . A special case of the negative binomial distribution, when r = 1 , is the geometric distribution, which models the number of failures before the first success. r can also have non-integer positive values, in which form the negative binomial distribution, also known as the Polya distribution, has no interpretation in terms of repeated trials, but, like the Poisson distribution, it is useful in modeling count data. The negative binomial distribution is more general than the Poisson distribution because it has a variance that is greater than its mean, making it suitable for count data that do not meet the assumptions of the Poisson distribution. In the limit, as r increases to infinity, the negative binomial distribution approaches the Poisson distribution. Further information about the negative binomial distribution can be found at https://en.wikipedia.org/wiki/Negative_binomial_distribution Input arguments must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. MATLAB is inconsistent here: for several of the discrete distributions it returns the result in the integer class of the input, truncating a probability to 0 or 1 . See also: nbincdf, nbininv, nbinrnd, nbinfit, nbinlike, nbinstat # name: # type: sq_string # elements: 1 # length: 53 Negative binomial probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 nbinrnd # name: # type: sq_string # elements: 1 # length: 2319 statistics: rnd = nbinrnd ( r , ps ) statistics: rnd = nbinrnd ( r , ps , rows ) statistics: rnd = nbinrnd ( r , ps , rows , cols , …) statistics: rnd = nbinrnd ( r , ps , [ sz ]) Random arrays from the negative binomial distribution. rnd = nbinrnd ( r , ps ) returns an array of random numbers chosen from the negative binomial distribution with parameters r and ps , where r is the number of successes until the experiment is stopped and ps is the probability of success in each experiment, given the number of failures in x . The size of rnd is the common size of r and ps . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, return a square matrix with the dimension specified. When called with more than one scalar argument the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a vector of dimensions sz . When r is an integer, the negative binomial distribution is also known as the Pascal distribution and it models the number of failures in x before a specified number of successes is reached in a series of independent, identical trials. Its parameters are the probability of success in a single trial, ps , and the number of successes, r . A special case of the negative binomial distribution, when r = 1 , is the geometric distribution, which models the number of failures before the first success. r can also have non-integer positive values, in which form the negative binomial distribution, also known as the Polya distribution, has no interpretation in terms of repeated trials, but, like the Poisson distribution, it is useful in modeling count data. The negative binomial distribution is more general than the Poisson distribution because it has a variance that is greater than its mean, making it suitable for count data that do not meet the assumptions of the Poisson distribution. In the limit, as r increases to infinity, the negative binomial distribution approaches the Poisson distribution. Further information about the negative binomial distribution can be found at https://en.wikipedia.org/wiki/Negative_binomial_distribution See also: nbincdf, nbininv, nbinpdf, nbinfit, nbinlike, nbinstat # name: # type: sq_string # elements: 1 # length: 54 Random arrays from the negative binomial distribution. # name: # type: sq_string # elements: 1 # length: 6 ncfcdf # name: # type: sq_string # elements: 1 # length: 1135 statistics: p = ncfcdf ( x , df1 , df2 , lambda ) statistics: p = ncfcdf ( x , df1 , df2 , lambda , 'upper' ) Noncentral F -cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the noncentral F -distribution with df1 and df2 degrees of freedom and noncentrality parameter lambda . The size of p is the common size of x , df1 , df2 , and lambda . A scalar input functions as a constant matrix of the same size as the other inputs. p = ncfcdf ( x , df1 , df2 , lambda , "upper") computes the upper tail probability of the noncentral F -distribution with parameters df1 , df2 , and lambda , at the values in x . Further information about the noncentral F -distribution can be found at https://en.wikipedia.org/wiki/Noncentral_F-distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: ncfinv, ncfpdf, ncfrnd, ncfstat, fcdf # name: # type: sq_string # elements: 1 # length: 52 Noncentral F-cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 6 ncfinv # name: # type: sq_string # elements: 1 # length: 970 statistics: x = ncfinv ( p , df1 , df2 , lambda ) Inverse of the noncentral F -cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the noncentral F -distribution with df1 and df2 degrees of freedom and noncentrality parameter lambda . The size of x is the common size of p , df1 , df2 , and lambda . A scalar input functions as a constant matrix of the same size as the other inputs. ncfinv uses Newton’s method to converge to the solution. Further information about the noncentral F -distribution can be found at https://en.wikipedia.org/wiki/Noncentral_F-distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: ncfcdf, ncfpdf, ncfrnd, ncfstat, finv # name: # type: sq_string # elements: 1 # length: 68 Inverse of the noncentral F-cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 6 ncfpdf # name: # type: sq_string # elements: 1 # length: 887 statistics: y = ncfpdf ( x , df1 , df2 , lambda ) Noncentral F -probability density function (PDF). For each element of x , compute the probability density function (PDF) of the noncentral F -distribution with df1 and df2 degrees of freedom and noncentrality parameter lambda . The size of y is the common size of x , df1 , df2 , and lambda . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the noncentral F -distribution can be found at https://en.wikipedia.org/wiki/Noncentral_F-distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: ncfcdf, ncfinv, ncfrnd, ncfstat, fpdf # name: # type: sq_string # elements: 1 # length: 48 Noncentral F-probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 6 ncfrnd # name: # type: sq_string # elements: 1 # length: 1281 statistics: r = ncfrnd ( df1 , df2 , lambda ) statistics: r = ncfrnd ( df1 , df2 , lambda , rows , cols , …) statistics: r = ncfrnd ( df1 , df2 , lambda , [ sz ]) Random arrays from the noncentral F -distribution. x = ncfrnd ( p , df1 , df2 , lambda ) returns an array of random numbers chosen from the noncentral F -distribution with df1 and df2 degrees of freedom and noncentrality parameter lambda . The size of r is the common size of df1 , df2 , and lambda . A scalar input functions as a constant matrix of the same size as the other input. ncfrnd generates values using the definition of a noncentral F random variable, as the ratio of a noncentral chi-squared distribution and a (central) chi-squared distribution. When called with a single size argument, ncfrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the noncentral F -distribution can be found at https://en.wikipedia.org/wiki/Noncentral_F-distribution See also: ncfcdf, ncfinv, ncfpdf, ncfstat, frnd, ncx2rnd, chi2rnd # name: # type: sq_string # elements: 1 # length: 49 Random arrays from the noncentral F-distribution. # name: # type: sq_string # elements: 1 # length: 6 nctcdf # name: # type: sq_string # elements: 1 # length: 1065 statistics: p = nctcdf ( x , df , mu ) statistics: p = nctcdf ( x , df , mu , 'upper' ) Noncentral t -cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the noncentral t -distribution with df degrees of freedom and noncentrality parameter mu . The size of p is the common size of x , df , and mu . A scalar input functions as a constant matrix of the same size as the other inputs. p = nctcdf ( x , df , mu , "upper") computes the upper tail probability of the noncentral t -distribution with parameters df and mu , at the values in x . Further information about the noncentral t -distribution can be found at https://en.wikipedia.org/wiki/Noncentral_t-distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: nctinv, nctpdf, nctrnd, nctstat, tcdf # name: # type: sq_string # elements: 1 # length: 52 Noncentral t-cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 6 nctinv # name: # type: sq_string # elements: 1 # length: 937 statistics: x = ncx2inv ( p , df , mu ) Inverse of the non-central t -cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the noncentral t -distribution with df degrees of freedom and noncentrality parameter mu . The size of x is the common size of p , df , and mu . A scalar input functions as a constant matrix of the same size as the other inputs. nctinv uses Newton’s method to converge to the solution. Further information about the noncentral t -distribution can be found at https://en.wikipedia.org/wiki/Noncentral_t-distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: nctcdf, nctpdf, nctrnd, nctstat, tinv # name: # type: sq_string # elements: 1 # length: 69 Inverse of the non-central t-cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 6 nctpdf # name: # type: sq_string # elements: 1 # length: 852 statistics: y = nctpdf ( x , df , mu ) Noncentral t -probability density function (PDF). For each element of x , compute the probability density function (PDF) of the noncentral t -distribution with df degrees of freedom and noncentrality parameter mu . The size of y is the common size of x , df , and mu . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the noncentral t -distribution can be found at https://en.wikipedia.org/wiki/Noncentral_t-distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: nctcdf, nctinv, nctrnd, nctstat, tpdf # name: # type: sq_string # elements: 1 # length: 48 Noncentral t-probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 6 nctrnd # name: # type: sq_string # elements: 1 # length: 1216 statistics: r = nctrnd ( df , mu ) statistics: r = nctrnd ( df , mu , rows , cols , …) statistics: r = nctrnd ( df , mu , [ sz ]) Random arrays from the noncentral t -distribution. x = nctrnd ( p , df , mu ) returns an array of random numbers chosen from the noncentral t -distribution with df degrees of freedom and noncentrality parameter mu . The size of r is the common size of df and mu . A scalar input functions as a constant matrix of the same size as the other input. nctrnd generates values using the definition of a noncentral t random variable, as the ratio of a normal distribution with non-zero mean and the sqrt of a chi-squared distribution. When called with a single size argument, nctrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the noncentral t -distribution can be found at https://en.wikipedia.org/wiki/Noncentral_t-distribution See also: nctcdf, nctinv, nctpdf, nctstat, trnd, normrnd, chi2rnd # name: # type: sq_string # elements: 1 # length: 49 Random arrays from the noncentral t-distribution. # name: # type: sq_string # elements: 1 # length: 7 ncx2cdf # name: # type: sq_string # elements: 1 # length: 1145 statistics: p = ncx2cdf ( x , df , lambda ) statistics: p = ncx2cdf ( x , df , lambda , 'upper' ) Noncentral chi-squared cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the noncentral chi-squared distribution with df degrees of freedom and noncentrality parameter lambda . The size of p is the common size of x , df , and lambda . A scalar input functions as a constant matrix of the same size as the other inputs. p = ncx2cdf ( x , df , lambda , "upper") computes the upper tail probability of the noncentral chi-squared distribution with parameters df and lambda , at the values in x . Further information about the noncentral chi-squared distribution can be found at https://en.wikipedia.org/wiki/Noncentral_chi-squared_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: ncx2inv, ncx2pdf, ncx2rnd, ncx2stat, chi2cdf # name: # type: sq_string # elements: 1 # length: 62 Noncentral chi-squared cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 ncx2inv # name: # type: sq_string # elements: 1 # length: 985 statistics: x = ncx2inv ( p , df , lambda ) Inverse of the noncentral chi-squared cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the noncentral chi-squared distribution with df degrees of freedom and noncentrality parameter mu . The size of x is the common size of p , df , and mu . A scalar input functions as a constant matrix of the same size as the other inputs. ncx2inv uses Newton’s method to converge to the solution. Further information about the noncentral chi-squared distribution can be found at https://en.wikipedia.org/wiki/Noncentral_chi-squared_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: ncx2cdf, ncx2pdf, ncx2rnd, ncx2stat, chi2inv # name: # type: sq_string # elements: 1 # length: 78 Inverse of the noncentral chi-squared cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 ncx2pdf # name: # type: sq_string # elements: 1 # length: 914 statistics: y = ncx2pdf ( x , df , lambda ) Noncentral chi-squared probability distribution function (PDF). For each element of x , compute the probability density function (PDF) of the noncentral chi-squared distribution with df degrees of freedom and noncentrality parameter lambda . The size of y is the common size of x , df , and lambda . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the noncentral chi-squared distribution can be found at https://en.wikipedia.org/wiki/Noncentral_chi-squared_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: ncx2cdf, ncx2inv, ncx2rnd, ncx2stat, chi2pdf # name: # type: sq_string # elements: 1 # length: 63 Noncentral chi-squared probability distribution function (PDF). # name: # type: sq_string # elements: 1 # length: 7 ncx2rnd # name: # type: sq_string # elements: 1 # length: 1076 statistics: r = ncx2rnd ( df , lambda ) statistics: r = ncx2rnd ( df , lambda , rows , cols , …) statistics: r = ncx2rnd ( df , lambda , [ sz ]) Random arrays from the noncentral chi-squared distribution. r = ncx2rnd ( df , lambda ) returns an array of random numbers chosen from the noncentral chi-squared distribution with df degrees of freedom and noncentrality parameter lambda . The size of r is the common size of df and lambda . A scalar input functions as a constant matrix of the same size as the other input. When called with a single size argument, ncx2rnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the noncentral chi-squared distribution can be found at https://en.wikipedia.org/wiki/Noncentral_chi-squared_distribution See also: ncx2cdf, ncx2inv, ncx2pdf, ncx2stat # name: # type: sq_string # elements: 1 # length: 59 Random arrays from the noncentral chi-squared distribution. # name: # type: sq_string # elements: 1 # length: 7 normcdf # name: # type: sq_string # elements: 1 # length: 1967 statistics: p = normcdf ( x ) statistics: p = normcdf ( x , mu ) statistics: p = normcdf ( x , mu , sigma ) statistics: p = normcdf (…, 'upper' ) statistics: [ p , plo , pup ] = normcdf ( x , mu , sigma , pcov ) statistics: [ p , plo , pup ] = normcdf ( x , mu , sigma , pcov , alpha ) statistics: [ p , plo , pup ] = normcdf (…, 'upper' ) Normal cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the normal distribution with mean mu and standard deviation sigma . The size of p is the common size of x , mu and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Default values are mu = 0, sigma = 1. When called with three output arguments, i.e. [ p , plo , pup ] , normcdf computes the confidence bounds for p when the input parameters mu and sigma are estimates. In such case, pcov , a 2×2 matrix containing the covariance matrix of the estimated parameters, is necessary. Optionally, alpha , which has a default value of 0.05, specifies the 100 * (1 - alpha ) percent confidence bounds. plo and pup are arrays of the same size as p containing the lower and upper confidence bounds. […] = normcdf (…, "upper") computes the upper tail probability of the normal distribution with parameters mu and sigma , at the values in x . This can be used to compute a right-tailed p-value. To compute a two-tailed p-value, use 2 * normcdf (-abs ( x ), mu , sigma ) . Further information about the normal distribution can be found at https://en.wikipedia.org/wiki/Normal_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: norminv, normpdf, normrnd, normfit, normlike, normstat # name: # type: sq_string # elements: 1 # length: 46 Normal cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 norminv # name: # type: sq_string # elements: 1 # length: 1170 statistics: x = norminv ( p ) statistics: x = norminv ( p , mu ) statistics: x = norminv ( p , mu , sigma ) Inverse of the normal cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the normal distribution with mean mu and standard deviation sigma . The size of p is the common size of p , mu and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Default values are mu = 0, sigma = 1. The default values correspond to the standard normal distribution and computing its quantile function is also possible with the probit function, which is faster but it does not perform any input validation. Further information about the normal distribution can be found at https://en.wikipedia.org/wiki/Normal_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: normcdf, normpdf, normrnd, normfit, normlike, normstat, probit # name: # type: sq_string # elements: 1 # length: 62 Inverse of the normal cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 normpdf # name: # type: sq_string # elements: 1 # length: 935 statistics: y = normpdf ( x ) statistics: y = normpdf ( x , mu ) statistics: y = normpdf ( x , mu , sigma ) Normal probability density function (PDF). For each element of x , compute the probability density function (PDF) of the normal distribution with mean mu and standard deviation sigma . The size of y is the common size of p , mu and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Default values are mu = 0, sigma = 1. Further information about the normal distribution can be found at https://en.wikipedia.org/wiki/Normal_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: normcdf, norminv, normrnd, normfit, normlike, normstat # name: # type: sq_string # elements: 1 # length: 42 Normal probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 normrnd # name: # type: sq_string # elements: 1 # length: 1139 statistics: r = normrnd ( mu , sigma ) statistics: r = normrnd ( mu , sigma , rows ) statistics: r = normrnd ( mu , sigma , rows , cols , …) statistics: r = normrnd ( mu , sigma , [ sz ]) Random arrays from the normal distribution. r = normrnd ( mu , sigma ) returns an array of random numbers chosen from the normal distribution with mean mu and standard deviation sigma . The size of r is the common size of mu and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be finite real numbers and sigma > 0, otherwise NaN is returned. When called with a single size argument, normrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the normal distribution can be found at https://en.wikipedia.org/wiki/Normal_distribution See also: normcdf, norminv, normpdf, normfit, normlike, normstat # name: # type: sq_string # elements: 1 # length: 43 Random arrays from the normal distribution. # name: # type: sq_string # elements: 1 # length: 5 plcdf # name: # type: sq_string # elements: 1 # length: 1114 statistics: p = plcdf ( data , x , Fx ) statistics: p = plcdf ( data , x , Fx , 'upper' ) Piecewise linear cumulative distribution function (CDF). For each element of data , compute the cumulative distribution function (CDF) of the piecewise linear distribution with a vector of x values at which the CDF changes slope and a vector of CDF values Fx that correspond to each value in x . Both x and Fx must be vectors of the same size and at least 2-elements long. The size of p is the same as data . p = plcdf ( data , x , Fx , "upper") computes the upper tail probability of the piecewise linear distribution with parameters x and Fx , at the values in data . Further information about the piecewise linear distribution can be found at https://en.wikipedia.org/wiki/Piecewise_linear_function Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: plinv, plpdf, plrnd, plstat # name: # type: sq_string # elements: 1 # length: 56 Piecewise linear cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 5 plinv # name: # type: sq_string # elements: 1 # length: 895 statistics: data = plinv ( p , x , Fx ) Inverse of the piecewise linear distribution (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the piecewise linear distribution with a vector of x values at which the CDF changes slope and a vector of CDF values Fx that correspond to each value in x . Both x and Fx must be vectors of the same_p size and at least 2-elements long.. The size of data is the same_p as p . Further information about the piecewise linear distribution can be found at https://en.wikipedia.org/wiki/Piecewise_linear_function Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: plcdf, plpdf, plrnd, plstat # name: # type: sq_string # elements: 1 # length: 52 Inverse of the piecewise linear distribution (iCDF). # name: # type: sq_string # elements: 1 # length: 5 plpdf # name: # type: sq_string # elements: 1 # length: 1062 statistics: y = plpdf ( data , x , Fx ) Piecewise linear probability density function (PDF). For each element of data , compute the probability density function (PDF) of the piecewise linear distribution with a vector of x values at which the CDF changes slope and a vector of CDF values Fx that correspond to each value in x . Both x and Fx must be vectors of the same size and at least 2-elements long. The size of p is the same as data . Further information about the piecewise linear distribution can be found at https://en.wikipedia.org/wiki/Piecewise_linear_function Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. MATLAB also accepts integer input here, returning the result in the integer class of the input; Octave rejects it, as it does for every other continuous distribution. See also: plcdf, plinv, plrnd, plstat # name: # type: sq_string # elements: 1 # length: 52 Piecewise linear probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 5 plrnd # name: # type: sq_string # elements: 1 # length: 1041 statistics: r = plrnd ( x , Fx ) statistics: r = plrnd ( x , Fx , rows ) statistics: r = plrnd ( x , Fx , rows , cols , …) statistics: r = plrnd ( x , Fx , [ sz ]) Random arrays from the piecewise linear distribution. r = plrnd ( x , Fx ) returns a random number chosen from the piecewise linear distribution with a vector of x values at which the CDF changes slope and a vector of CDF values Fx that correspond to each value in x . Both x and Fx must be vectors of the same size and at least 2-elements long. When called with a single size argument, plrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the piecewise linear distribution can be found at https://en.wikipedia.org/wiki/Piecewise_linear_function See also: plcdf, plinv, plpdf, plstat # name: # type: sq_string # elements: 1 # length: 53 Random arrays from the piecewise linear distribution. # name: # type: sq_string # elements: 1 # length: 8 poisscdf # name: # type: sq_string # elements: 1 # length: 1091 statistics: p = poisscdf ( x , lambda ) statistics: p = poisscdf ( x , lambda , 'upper' ) Poisson cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the Poisson distribution with rate parameter lambda . The size of p is the common size of x and lambda . A scalar input functions as a constant matrix of the same size as the other inputs. p = poisscdf ( x , lambda , "upper") computes the upper tail probability of the Poisson distribution with parameter lambda , at the values in x . Further information about the Poisson distribution can be found at https://en.wikipedia.org/wiki/Poisson_distribution Input arguments must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. MATLAB is inconsistent here: for several of the discrete distributions it returns the result in the integer class of the input, truncating a probability to 0 or 1 . See also: poissinv, poisspdf, poissrnd, poissfit, poisslike, poisstat # name: # type: sq_string # elements: 1 # length: 47 Poisson cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 8 poissinv # name: # type: sq_string # elements: 1 # length: 837 statistics: x = poissinv ( p , lambda ) Inverse of the Poisson cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the Poisson distribution with rate parameter lambda . The size of x is the common size of p and lambda . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Poisson distribution can be found at https://en.wikipedia.org/wiki/Poisson_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: poisscdf, poisspdf, poissrnd, poissfit, poisslike, poisstat # name: # type: sq_string # elements: 1 # length: 63 Inverse of the Poisson cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 8 poisspdf # name: # type: sq_string # elements: 1 # length: 1175 statistics: y = poisspdf ( x , lambda ) Poisson probability density function (PDF). For each element of x , compute the probability density function (PDF) of the Poisson distribution with rate parameter lambda . The size of y is the common size of x and lambda . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Poisson distribution can be found at https://en.wikipedia.org/wiki/Poisson_distribution Input arguments must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. MATLAB is inconsistent here: for several of the discrete distributions it returns the result in the integer class of the input, truncating a probability to 0 or 1 . The density at an infinite abscissa is 0 , no proper distribution placing mass there. MATLAB returns NaN here, as it does for raylpdf and for no other density, which is an inconsistency there rather than a convention: it returns 0 at Inf for every other distribution of the same support. See also: poisscdf, poissinv, poissrnd, poissfit, poisslike, poisstat # name: # type: sq_string # elements: 1 # length: 43 Poisson probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 8 poissrnd # name: # type: sq_string # elements: 1 # length: 1116 statistics: r = poissrnd ( lambda ) statistics: r = poissrnd ( lambda , rows ) statistics: r = poissrnd ( lambda , rows , cols , …) statistics: r = poissrnd ( lambda , [ sz ]) Random arrays from the Poisson distribution. r = normrnd ( lambda ) returns an array of random numbers chosen from the Poisson distribution with rate parameter lambda . The size of r is the common size of lambda . A scalar input functions as a constant matrix of the same size as the other inputs. lambda must be a finite real number and greater or equal to 0, otherwise NaN is returned. When called with a single size argument, poissrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the Poisson distribution can be found at https://en.wikipedia.org/wiki/Poisson_distribution See also: poisscdf, poissinv, poisspdf, poissfit, poisslike, poisstat # name: # type: sq_string # elements: 1 # length: 44 Random arrays from the Poisson distribution. # name: # type: sq_string # elements: 1 # length: 7 raylcdf # name: # type: sq_string # elements: 1 # length: 1096 statistics: p = raylcdf ( x , sigma ) statistics: p = raylcdf ( x , sigma , 'upper' ) Rayleigh cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the Rayleigh distribution with scale parameter sigma . The size of p is the common size of x and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. p = raylcdf ( x , sigma , "upper") computes the upper tail probability of the Rayleigh distribution with parameter sigma , at the values in x . Further information about the Rayleigh distribution can be found at https://en.wikipedia.org/wiki/Rayleigh_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. The prob.RayleighDistribution class names this same parameter B , after MATLAB. See also: raylinv, raylpdf, raylrnd, raylfit, rayllike, raylstat # name: # type: sq_string # elements: 1 # length: 48 Rayleigh cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 raylinv # name: # type: sq_string # elements: 1 # length: 914 statistics: x = raylinv ( p , sigma ) Inverse of the Rayleigh cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the Rayleigh distribution with scale parameter sigma . The size of x is the common size of p and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Rayleigh distribution can be found at https://en.wikipedia.org/wiki/Rayleigh_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. The prob.RayleighDistribution class names this same parameter B , after MATLAB. See also: raylcdf, raylpdf, raylrnd, raylfit, rayllike, raylstat # name: # type: sq_string # elements: 1 # length: 64 Inverse of the Rayleigh cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 raylpdf # name: # type: sq_string # elements: 1 # length: 1185 statistics: y = raylpdf ( x , sigma ) Rayleigh probability density function (PDF). For each element of x , compute the probability density function (PDF) of the Rayleigh distribution with scale parameter sigma . The size of p is the common size of x and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Rayleigh distribution can be found at https://en.wikipedia.org/wiki/Rayleigh_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. The density at an infinite abscissa is 0 , no proper distribution placing mass there. MATLAB returns NaN here, as it does for poisspdf and for no other density, which is an inconsistency there rather than a convention: it returns 0 at Inf for every other distribution of the same support. The prob.RayleighDistribution class names this same parameter B , after MATLAB. See also: raylcdf, raylinv, raylrnd, raylfit, rayllike, raylstat # name: # type: sq_string # elements: 1 # length: 44 Rayleigh probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 raylrnd # name: # type: sq_string # elements: 1 # length: 1166 statistics: r = raylrnd ( sigma ) statistics: r = raylrnd ( sigma , rows ) statistics: r = raylrnd ( sigma , rows , cols , …) statistics: r = raylrnd ( sigma , [ sz ]) Random arrays from the Rayleigh distribution. r = raylrnd ( sigma ) returns an array of random numbers chosen from the Rayleigh distribution with scale parameter sigma . The size of r is the size of sigma . A scalar input functions as a constant matrix of the same size as the other inputs. sigma must be a finite real number greater than 0, otherwise NaN is returned. When called with a single size argument, raylrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the Rayleigh distribution can be found at https://en.wikipedia.org/wiki/Rayleigh_distribution The prob.RayleighDistribution class names this same parameter B , after MATLAB. See also: raylcdf, raylinv, raylpdf, raylfit, rayllike, raylstat # name: # type: sq_string # elements: 1 # length: 45 Random arrays from the Rayleigh distribution. # name: # type: sq_string # elements: 1 # length: 7 ricecdf # name: # type: sq_string # elements: 1 # length: 1070 statistics: p = ricecdf ( x , s , sigma ) statistics: p = ricecdf ( x , s , sigma , 'upper' ) Rician cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the Rician distribution with non-centrality (distance) parameter s and scale parameter sigma . The size of p is the common size of x , s , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. p = ricecdf ( x , s , sigma , "upper") computes the upper tail probability of the Rician distribution with parameters s and sigma , at the values in x . Further information about the Rician distribution can be found at https://en.wikipedia.org/wiki/Rice_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: riceinv, ricepdf, ricernd, ricefit, ricelike, ricestat # name: # type: sq_string # elements: 1 # length: 46 Rician cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 riceinv # name: # type: sq_string # elements: 1 # length: 855 statistics: x = riceinv ( p , s , sigma ) Inverse of the Rician distribution (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the Rician distribution with non-centrality (distance) parameter s and scale parameter sigma . The size of x is the common size of x , s , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Rician distribution can be found at https://en.wikipedia.org/wiki/Rice_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: ricecdf, ricepdf, ricernd, ricefit, ricelike, ricestat # name: # type: sq_string # elements: 1 # length: 42 Inverse of the Rician distribution (iCDF). # name: # type: sq_string # elements: 1 # length: 7 ricepdf # name: # type: sq_string # elements: 1 # length: 856 statistics: y = ricepdf ( x , s , sigma ) Rician probability density function (PDF). For each element of x , compute the probability density function (PDF) of the Rician distribution with non-centrality (distance) parameter s and scale parameter sigma . The size of y is the common size of x , s , and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Rician distribution can be found at https://en.wikipedia.org/wiki/Rice_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: ricecdf, riceinv, ricernd, ricefit, ricelike, ricestat # name: # type: sq_string # elements: 1 # length: 42 Rician probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 ricernd # name: # type: sq_string # elements: 1 # length: 1060 statistics: r = ricernd ( s , sigma ) statistics: r = ricernd ( s , sigma , rows ) statistics: r = ricernd ( s , sigma , rows , cols , …) statistics: r = ricernd ( s , sigma , [ sz ]) Random arrays from the Rician distribution. r = ricernd ( s , sigma ) returns an array of random numbers chosen from the Rician distribution with noncentrality parameter s and scale parameter sigma . The size of r is the common size of s and sigma . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, ricernd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the Rician distribution can be found at https://en.wikipedia.org/wiki/Rice_distribution See also: ricecdf, riceinv, ricepdf, ricefit, ricelike, ricestat # name: # type: sq_string # elements: 1 # length: 43 Random arrays from the Rician distribution. # name: # type: sq_string # elements: 1 # length: 7 stblcdf # name: # type: sq_string # elements: 1 # length: 1084 statistics: p = stblcdf ( x , alpha , beta , gam , delta ) Stable cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the stable distribution with tail index (first shape parameter) alpha , skewness (second shape parameter) beta , scale parameter gam , and location parameter delta , in the Nolan S0 parameterization. The size of p is the size of x . alpha must be in the range (0, 2] , beta in [-1, 1] , gam positive, and delta real. The parameters must be scalars. The cumulative probability has a closed form for alpha equal to 2 (normal) and for 1 with beta equal to 0 (Cauchy); otherwise it is computed by numerical inversion of the characteristic function (the Gil-Pelaez formula). Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: stblpdf, stblinv, stblrnd, makedist # name: # type: sq_string # elements: 1 # length: 46 Stable cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 stblinv # name: # type: sq_string # elements: 1 # length: 1034 statistics: x = stblinv ( p , alpha , beta , gam , delta ) Inverse of the stable cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the stable distribution with tail index (first shape parameter) alpha , skewness (second shape parameter) beta , scale parameter gam , and location parameter delta , in the Nolan S0 parameterization. The size of x is the size of p . alpha must be in the range (0, 2] , beta in [-1, 1] , gam positive, and delta real. The parameters must be scalars. The quantile has a closed form for alpha equal to 2 (normal) and for 1 with beta equal to 0 (Cauchy); otherwise it is found by numerical inversion of stblcdf . Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: stblcdf, stblpdf, stblrnd, makedist # name: # type: sq_string # elements: 1 # length: 62 Inverse of the stable cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 stblpdf # name: # type: sq_string # elements: 1 # length: 1036 statistics: y = stblpdf ( x , alpha , beta , gam , delta ) Stable probability density function (PDF). For each element of x , compute the probability density function (PDF) of the stable distribution with tail index (first shape parameter) alpha , skewness (second shape parameter) beta , scale parameter gam , and location parameter delta , in the Nolan S0 parameterization. The size of y is the size of x . alpha must be in the range (0, 2] , beta in [-1, 1] , gam positive, and delta real. The parameters must be scalars. The density has a closed form for alpha equal to 2 (normal) and for 1 with beta equal to 0 (Cauchy); otherwise it is computed by numerical inversion of the characteristic function. Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: stblcdf, stblinv, stblrnd, makedist # name: # type: sq_string # elements: 1 # length: 42 Stable probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 stblrnd # name: # type: sq_string # elements: 1 # length: 997 statistics: r = stblrnd ( alpha , beta , gam , delta ) statistics: r = stblrnd ( alpha , beta , gam , delta , m ) statistics: r = stblrnd ( alpha , beta , gam , delta , m , n , …) statistics: r = stblrnd ( alpha , beta , gam , delta , [ m , n , …]) Random arrays from the stable distribution. r = stblrnd ( alpha , beta , gam , delta ) returns a random value drawn from the stable distribution with tail index (first shape parameter) alpha , skewness (second shape parameter) beta , scale parameter gam , and location parameter delta , in the Nolan S0 parameterization. alpha must be in the range (0, 2] , beta in [-1, 1] , gam positive, and delta real. The parameters must be scalars. stblrnd ( alpha , beta , gam , delta , m , n , …) or stblrnd (…, [ m , n , …]) returns an m -by- n -by-… array, following the size conventions of rand . The values are generated with the Chambers-Mallows-Stuck method. See also: stblpdf, stblcdf, stblinv, makedist # name: # type: sq_string # elements: 1 # length: 43 Random arrays from the stable distribution. # name: # type: sq_string # elements: 1 # length: 4 tcdf # name: # type: sq_string # elements: 1 # length: 1007 statistics: p = tcdf ( x , df ) statistics: p = tcdf ( x , df , 'upper' ) Student’s T cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the Student’s T distribution with df degrees of freedom. The size of p is the common size of x and df . A scalar input functions as a constant matrix of the same size as the other input. p = tcdf ( x , df , "upper") computes the upper tail probability of the Student’s T distribution with df degrees of freedom, at the values in x . Further information about the Student’s T distribution can be found at https://en.wikipedia.org/wiki/Student%27s_t-distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: tinv, tpdf, trnd, tstat # name: # type: sq_string # elements: 1 # length: 51 Student's T cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 4 tinv # name: # type: sq_string # elements: 1 # length: 1007 statistics: x = tinv ( p , df ) Inverse of the Student’s T cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the Student’s T distribution with df degrees of freedom. The size of x is the common size of x and df . A scalar input functions as a constant matrix of the same size as the other input. This function is analogous to looking in a table for the t-value of a single-tailed distribution. For very large df (>10000), the inverse of the standard normal distribution is used. Further information about the Student’s T distribution can be found at https://en.wikipedia.org/wiki/Student%27s_t-distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: tcdf, tpdf, trnd, tstat # name: # type: sq_string # elements: 1 # length: 67 Inverse of the Student's T cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 6 tlscdf # name: # type: sq_string # elements: 1 # length: 1245 statistics: p = tlscdf ( x , mu , sigma , nu ) statistics: p = tlscdf ( x , mu , sigma , nu , 'upper' ) Location-scale Student’s T cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the location-scale Student’s T distribution with location parameter mu , scale parameter sigma , and nu degrees of freedom. The size of p is the common size of x , mu , sigma , and nu . A scalar input functions as a constant matrix of the same size as the other inputs. p = tlscdf ( x , mu , sigma , nu , "upper") computes the upper tail probability of the location-scale Student’s T distribution with parameters mu , sigma , and nu , at the values in x . Further information about the location-scale Student’s T distribution can be found at https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: tlsinv, tlspdf, tlsrnd, tlsfit, tlslike, tlsstat # name: # type: sq_string # elements: 1 # length: 66 Location-scale Student's T cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 6 tlsinv # name: # type: sq_string # elements: 1 # length: 1006 statistics: x = tlsinv ( p , mu , sigma , nu ) Inverse of the location-scale Student’s T cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the location-scale Student’s T distribution with location parameter mu , scale parameter sigma , and nu degrees of freedom. The size of x is the common size of p , mu , sigma , and nu . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the location-scale Student’s T distribution can be found at https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: tlscdf, tlspdf, tlsrnd, tlsfit, tlslike, tlsstat # name: # type: sq_string # elements: 1 # length: 82 Inverse of the location-scale Student's T cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 6 tlspdf # name: # type: sq_string # elements: 1 # length: 987 statistics: p = tlspdf ( x , mu , sigma , nu ) Location-scale Student’s T probability density function (PDF). For each element of x , compute the probability density function (PDF) of the location-scale Student’s T distribution with location parameter mu , scale parameter sigma , and nu degrees of freedom. The size of y is the common size of x , mu , sigma , and nu . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the location-scale Student’s T distribution can be found at https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: tlscdf, tlsinv, tlsrnd, tlsfit, tlslike, tlsstat # name: # type: sq_string # elements: 1 # length: 62 Location-scale Student's T probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 6 tlsrnd # name: # type: sq_string # elements: 1 # length: 1381 statistics: r = tlsrnd ( mu , sigma , nu ) statistics: r = tlsrnd ( mu , sigma , nu , rows ) statistics: r = tlsrnd ( mu , sigma , nu , rows , cols , …) statistics: r = tlsrnd ( mu , sigma , nu , [ sz ]) Random arrays from the location-scale Student’s T distribution. Return a matrix of random samples from the location-scale Student’s T distribution with location parameter mu , scale parameter sigma , and nu degrees of freedom. r = tlsrnd ( nu ) returns an array of random numbers chosen from the location-scale Student’s T distribution with location parameter mu , scale parameter sigma , and nu degrees of freedom. The size of r is the common size of mu , sigma , and nu . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, tlsrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the location-scale Student’s T distribution can be found at https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution See also: tlscdf, tlsinv, tlspdf, tlsfit, tlslike, tlsstat # name: # type: sq_string # elements: 1 # length: 63 Random arrays from the location-scale Student's T distribution. # name: # type: sq_string # elements: 1 # length: 4 tpdf # name: # type: sq_string # elements: 1 # length: 804 statistics: p = tpdf ( x , df ) Student’s T probability density function (PDF). For each element of x , compute the probability density function (PDF) of the Student’s T distribution with df degrees of freedom. The size of y is the common size of x and df . A scalar input functions as a constant matrix of the same size as the other input. Further information about the Student’s T distribution can be found at https://en.wikipedia.org/wiki/Student%27s_t-distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: tcdf, tinv, trnd, tstat # name: # type: sq_string # elements: 1 # length: 47 Student's T probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 6 tricdf # name: # type: sq_string # elements: 1 # length: 1725 statistics: p = tricdf ( x , a , b , c ) statistics: p = tricdf ( x , a , b , c , 'upper' ) Triangular cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the triangular distribution with lower limit parameter a , peak location (mode) parameter b , and upper limit parameter c . The size of p is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. p = tricdf ( x , a , b , c , "upper") computes the upper tail probability of the triangular distribution with parameters a , b , and c , at the values in x . Note that the order of the parameter input arguments has been changed after statistics version 1.6.3 in order to be MATLAB compatible with the parameters used in the TriangularDistribution probability distribution object. More specifically, the positions of the parameters b and c have been swapped. As a result, the naming conventions no longer coincide with those used in Wikipedia, in which b denotes the upper limit and c denotes the mode or peak parameter. Further information about the triangular distribution can be found at https://en.wikipedia.org/wiki/Triangular_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. MATLAB also accepts integer input here, returning the result in the integer class of the input; Octave rejects it, as it does for every other continuous distribution. See also: triinv, tripdf, trirnd, tristat # name: # type: sq_string # elements: 1 # length: 50 Triangular cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 6 triinv # name: # type: sq_string # elements: 1 # length: 1358 statistics: x = triinv ( p , a , b , c ) Inverse of the triangular cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the triangular distribution with lower limit parameter a , peak location (mode) parameter b , and upper limit parameter c . The size of x is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Note that the order of the parameter input arguments has been changed after statistics version 1.6.3 in order to be MATLAB compatible with the parameters used in the TriangularDistribution probability distribution object. More specifically, the positions of the parameters b and c have been swapped. As a result, the naming conventions no longer coincide with those used in Wikipedia, in which b denotes the upper limit and c denotes the mode or peak parameter. Further information about the triangular distribution can be found at https://en.wikipedia.org/wiki/Triangular_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: tricdf, tripdf, trirnd, tristat # name: # type: sq_string # elements: 1 # length: 66 Inverse of the triangular cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 6 tripdf # name: # type: sq_string # elements: 1 # length: 1507 statistics: y = tripdf ( x , a , b , c ) Triangular probability density function (PDF). For each element of x , compute the probability density function (PDF) of the triangular distribution with lower limit parameter a , peak location (mode) parameter b , and upper limit parameter c . The size of y is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Note that the order of the parameter input arguments has been changed after statistics version 1.6.3 in order to be MATLAB compatible with the parameters used in the TriangularDistribution probability distribution object. More specifically, the positions of the parameters b and c have been swapped. As a result, the naming conventions no longer coincide with those used in Wikipedia, in which b denotes the upper limit and c denotes the mode or peak parameter. Further information about the triangular distribution can be found at https://en.wikipedia.org/wiki/Triangular_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. MATLAB also accepts integer input here, returning the result in the integer class of the input; Octave rejects it, as it does for every other continuous distribution. See also: tricdf, triinv, trirnd, tristat # name: # type: sq_string # elements: 1 # length: 46 Triangular probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 6 trirnd # name: # type: sq_string # elements: 1 # length: 1547 statistics: r = trirnd ( a , b , c ) statistics: r = trirnd ( a , b , c , rows ) statistics: r = trirnd ( a , b , c , rows , cols , …) statistics: r = trirnd ( a , b , c , [ sz ]) Random arrays from the triangular distribution. r = trirnd ( sigma ) returns an array of random numbers chosen from the triangular distribution with lower limit parameter a , peak location (mode) parameter b , and upper limit parameter c . The size of r is the common size of a , b , and c . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, trirnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Note that the order of the parameter input arguments has been changed after statistics version 1.6.3 in order to be MATLAB compatible with the parameters used in the TriangularDistribution probability distribution object. More specifically, the positions of the parameters b and c have been swapped. As a result, the naming conventions no longer coincide with those used in Wikipedia, in which b denotes the upper limit and c denotes the mode or peak parameter. Further information about the triangular distribution can be found at https://en.wikipedia.org/wiki/Triangular_distribution See also: tricdf, triinv, tripdf, tristat # name: # type: sq_string # elements: 1 # length: 47 Random arrays from the triangular distribution. # name: # type: sq_string # elements: 1 # length: 4 trnd # name: # type: sq_string # elements: 1 # length: 1150 statistics: r = trnd ( df ) statistics: r = trnd ( df , rows ) statistics: r = trnd ( df , rows , cols , …) statistics: r = trnd ( df , [ sz ]) Random arrays from the Student’s T distribution. Return a matrix of random samples from the Students’s T distribution with df degrees of freedom. r = trnd ( df ) returns an array of random numbers chosen from the Student’s T distribution with df degrees of freedom. The size of r is the size of df . A scalar input functions as a constant matrix of the same size as the other inputs. df must be a finite real number greater than 0, otherwise NaN is returned. When called with a single size argument, trnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the Student’s T distribution can be found at https://en.wikipedia.org/wiki/Student%27s_t-distribution See also: tcdf, tinv, tpdf, tstat # name: # type: sq_string # elements: 1 # length: 48 Random arrays from the Student's T distribution. # name: # type: sq_string # elements: 1 # length: 7 unidcdf # name: # type: sq_string # elements: 1 # length: 1471 statistics: p = unidcdf ( x , N ) statistics: p = unidcdf ( x , N , 'upper' ) Discrete uniform cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of a discrete uniform distribution with parameter N , which corresponds to the maximum observable value. unidcdf assumes the integer values in the range [1,N] with equal probability. The size of p is the common size of x and N . A scalar input functions as a constant matrix of the same size as the other inputs. The maximum observable values in N must be positive integers, otherwise NaN is returned. […] = unidcdf ( x , N , "upper") computes the upper tail probability of the discrete uniform distribution with maximum observable value N , at the values in x . Warning: The underlying implementation uses the double class and will only be accurate for N < flintmax ( 2^{53} on IEEE 754 compatible systems). Further information about the discrete uniform distribution can be found at https://en.wikipedia.org/wiki/Discrete_uniform_distribution Input arguments must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. MATLAB is inconsistent here: for several of the discrete distributions it returns the result in the integer class of the input, truncating a probability to 0 or 1 . See also: unidinv, unidpdf, unidrnd, unidfit, unidstat # name: # type: sq_string # elements: 1 # length: 56 Discrete uniform cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 unidinv # name: # type: sq_string # elements: 1 # length: 1203 statistics: x = unidinv ( p , N ) Inverse of the discrete uniform cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the discrete uniform distribution with parameter N , which corresponds to the maximum observable value. unidinv assumes the integer values in the range [1,N] with equal probability. The size of x is the common size of p and N . A scalar input functions as a constant matrix of the same size as the other inputs. The maximum observable values in N must be positive integers, otherwise NaN is returned. Warning: The underlying implementation uses the double class and will only be accurate for N < flintmax ( 2^{53} on IEEE 754 compatible systems). Further information about the discrete uniform distribution can be found at https://en.wikipedia.org/wiki/Discrete_uniform_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: unidcdf, unidpdf, unidrnd, unidfit, unidstat # name: # type: sq_string # elements: 1 # length: 72 Inverse of the discrete uniform cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 unidpdf # name: # type: sq_string # elements: 1 # length: 1252 statistics: y = unidpdf ( x , N ) Discrete uniform probability density function (PDF). For each element of x , compute the probability density function (PDF) of the discrete uniform distribution with parameter N , which corresponds to the maximum observable value. unidpdf assumes the integer values in the range [1,N] with equal probability. The size of x is the common size of p and N . A scalar input functions as a constant matrix of the same size as the other inputs. The maximum observable values in N must be positive integers, otherwise NaN is returned. Warning: The underlying implementation uses the double class and will only be accurate for N < flintmax ( 2^{53} on IEEE 754 compatible systems). Further information about the discrete uniform distribution can be found at https://en.wikipedia.org/wiki/Discrete_uniform_distribution Input arguments must be double , single , or an integer type; logical and character arrays are rejected. Integer input is promoted to double , so the result is always a probability. MATLAB is inconsistent here: for several of the discrete distributions it returns the result in the integer class of the input, truncating a probability to 0 or 1 . See also: unidcdf, unidinv, unidrnd, unidfit, unidstat # name: # type: sq_string # elements: 1 # length: 52 Discrete uniform probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 unidrnd # name: # type: sq_string # elements: 1 # length: 1361 statistics: r = unidrnd ( N ) statistics: r = unidrnd ( N , rows ) statistics: r = unidrnd ( N , rows , cols , …) statistics: r = unidrnd ( N , [ sz ]) Random arrays from the discrete uniform distribution. r = unidrnd ( N ) returns an array of random numbers chosen from the discrete uniform distribution with parameter N , which corresponds to the maximum observable value. unidrnd assumes the integer values in the range [1,N] with equal probability. The size of r is the size of N . A scalar input functions as a constant matrix of the same size as the other inputs. The maximum observable values in N must be positive integers, otherwise NaN is returned. When called with a single size argument, unidrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Warning: The underlying implementation uses the double class and will only be accurate for N < flintmax ( 2^{53} on IEEE 754 compatible systems). Further information about the discrete uniform distribution can be found at https://en.wikipedia.org/wiki/Discrete_uniform_distribution See also: unidcdf, unidinv, unidpdf, unidfit, unidstat # name: # type: sq_string # elements: 1 # length: 53 Random arrays from the discrete uniform distribution. # name: # type: sq_string # elements: 1 # length: 7 unifcdf # name: # type: sq_string # elements: 1 # length: 1303 statistics: p = unifcdf ( x , a , b ) statistics: p = unifcdf ( x , a , b , 'upper' ) Continuous uniform cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the continuous uniform distribution with parameters a and b , which define the lower and upper bounds of the interval [ a , b ] . The size of p is the common size of x , a , and b . A scalar input functions as a constant matrix of the same size as the other inputs. […] = unifcdf ( x , a , b , "upper") computes the upper tail probability of the continuous uniform distribution with parameters a , and b , at the values in x . Further information about the continuous uniform distribution can be found at https://en.wikipedia.org/wiki/Continuous_uniform_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. MATLAB also accepts integer input here, returning the result in the integer class of the input; Octave rejects it, as it does for every other continuous distribution. See also: unifinv, unifpdf, unifrnd, unifit, unifstat # name: # type: sq_string # elements: 1 # length: 58 Continuous uniform cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 7 unifinv # name: # type: sq_string # elements: 1 # length: 929 statistics: x = unifinv ( p , a , b ) Inverse of the continuous uniform cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the continuous uniform distribution with parameters a and b , which define the lower and upper bounds of the interval [ a , b ] . The size of x is the common size of p , a , and b . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the continuous uniform distribution can be found at https://en.wikipedia.org/wiki/Continuous_uniform_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: unifcdf, unifpdf, unifrnd, unifit, unifstat # name: # type: sq_string # elements: 1 # length: 74 Inverse of the continuous uniform cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 7 unifpdf # name: # type: sq_string # elements: 1 # length: 1078 statistics: y = unifpdf ( x , a , b ) Continuous uniform probability density function (PDF). For each element of x , compute the probability density function (PDF) of the continuous uniform distribution with parameters a and b , which define the lower and upper bounds of the interval [ a , b ] . The size of y is the common size of x , a , and b . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the continuous uniform distribution can be found at https://en.wikipedia.org/wiki/Continuous_uniform_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. MATLAB also accepts integer input here, returning the result in the integer class of the input; Octave rejects it, as it does for every other continuous distribution. See also: unifcdf, unifinv, unifrnd, unifit, unifstat # name: # type: sq_string # elements: 1 # length: 54 Continuous uniform probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 7 unifrnd # name: # type: sq_string # elements: 1 # length: 1110 statistics: r = unifrnd ( a , b ) statistics: r = unifrnd ( a , b , rows ) statistics: r = unifrnd ( a , b , rows , cols , …) statistics: r = unifrnd ( a , b , [ sz ]) Random arrays from the continuous uniform distribution. r = unifrnd ( a , b ) returns an array of random numbers chosen from the continuous uniform distribution with parameters a and b , which define the lower and upper bounds of the interval [ a , b ] . The size of r is the common size of a and b . A scalar input functions as a constant matrix of the same size as the other inputs. When called with a single size argument, unifrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the continuous uniform distribution can be found at https://en.wikipedia.org/wiki/Continuous_uniform_distribution See also: unifcdf, unifinv, unifpdf, unifit, unifstat # name: # type: sq_string # elements: 1 # length: 55 Random arrays from the continuous uniform distribution. # name: # type: sq_string # elements: 1 # length: 5 vmcdf # name: # type: sq_string # elements: 1 # length: 1390 statistics: p = vmcdf ( x , mu , k ) statistics: p = vmcdf ( x , mu , k , 'upper' ) Von Mises probability density function (PDF). For each element of x , compute the cumulative distribution function (CDF) of the von Mises distribution with location parameter mu and concentration parameter k on the interval [-pi,pi] . The size of p is the common size of x , mu , and k . A scalar input functions as a constant matrix of the same same size as the other inputs. p = vmcdf ( x , mu , k , "upper") computes the upper tail probability of the von Mises distribution with parameters mu and k , at the values in x . Note: the CDF of the von Mises distribution is not analytic. Hence, it is calculated by integrating its probability density which is expressed as a series of Bessel functions. Balancing between performance and accuracy, the integration uses a step of 1e-5 on the interval [-pi,pi] , which results to an accuracy of about 10 significant digits. Further information about the von Mises distribution can be found at https://en.wikipedia.org/wiki/Von_Mises_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: vminv, vmpdf, vmrnd # name: # type: sq_string # elements: 1 # length: 45 Von Mises probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 5 vminv # name: # type: sq_string # elements: 1 # length: 1193 statistics: x = vminv ( p , mu , k ) Inverse of the von Mises cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the von Mises distribution with location parameter mu and concentration parameter k on the interval [-pi,pi] . The size of x is the common size of p , mu , and k . A scalar input functions as a constant matrix of the same size as the other inputs. Note: the quantile of the von Mises distribution is not analytic. Hence, it is approximated by a custom searching algorithm using its CDF until it converges up to a tolerance of 1e-5 or 100 iterations. As a result, balancing between performance and accuracy, the accuracy is about 5e-5 for k = 1 and it drops to 5e-5 as k increases. Further information about the von Mises distribution can be found at https://en.wikipedia.org/wiki/Von_Mises_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: vmcdf, vmpdf, vmrnd # name: # type: sq_string # elements: 1 # length: 65 Inverse of the von Mises cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 5 vmpdf # name: # type: sq_string # elements: 1 # length: 840 statistics: y = vmpdf ( x , mu , k ) Von Mises probability density function (PDF). For each element of x , compute the probability density function (PDF) of the von Mises distribution with location parameter mu and concentration parameter k on the interval [-pi, pi]. The size of y is the common size of x , mu , and k . A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the von Mises distribution can be found at https://en.wikipedia.org/wiki/Von_Mises_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: vmcdf, vminv, vmrnd # name: # type: sq_string # elements: 1 # length: 45 Von Mises probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 5 vmrnd # name: # type: sq_string # elements: 1 # length: 1113 statistics: r = vmrnd ( mu , k ) statistics: r = vmrnd ( mu , k , rows ) statistics: r = vmrnd ( mu , k , rows , cols , …) statistics: r = vmrnd ( mu , k , [ sz ]) Random arrays from the von Mises distribution. r = vmrnd ( mu , k ) returns an array of random angles chosen from a von Mises distribution with location parameter mu and concentration parameter k on the interval [-pi, pi]. The size of r is the common size of mu and k . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be finite real numbers and k > 0, otherwise NaN is returned. When called with a single size argument, vmrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the von Mises distribution can be found at https://en.wikipedia.org/wiki/Von_Mises_distribution See also: vmcdf, vminv, vmpdf # name: # type: sq_string # elements: 1 # length: 46 Random arrays from the von Mises distribution. # name: # type: sq_string # elements: 1 # length: 6 wblcdf # name: # type: sq_string # elements: 1 # length: 1879 statistics: p = wblcdf ( x ) statistics: p = wblcdf ( x , lambda ) statistics: p = wblcdf ( x , lambda , k ) statistics: p = wblcdf (…, 'upper' ) statistics: [ p , plo , pup ] = wblcdf ( x , lambda , k , pcov ) statistics: [ p , plo , pup ] = wblcdf ( x , lambda , k , pcov , alpha ) statistics: [ p , plo , pup ] = wblcdf (…, 'upper' ) Weibull cumulative distribution function (CDF). For each element of x , compute the cumulative distribution function (CDF) of the Weibull distribution with scale parameter lambda and shape parameter k . The size of p is the common size of x , lambda and k . A scalar input functions as a constant matrix of the same size as the other inputs. Default values are lambda = 1, k = 1. When called with three output arguments, [ p , plo , pup ] it computes the confidence bounds for p when the input parameters lambda and k are estimates. In such case, pcov , a 2-by-2 matrix containing the covariance matrix of the estimated parameters, is necessary. Optionally, alpha has a default value of 0.05, and specifies 100 * (1 - alpha )% confidence bounds. plo and pup are arrays of the same size as p containing the lower and upper confidence bounds. […] = wblcdf (…, "upper") computes the upper tail probability of the lognormal distribution. Further information about the Weibull distribution can be found at https://en.wikipedia.org/wiki/Weibull_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. The prob.WeibullDistribution class names these same two parameters A and B , after MATLAB. lambda is its A and k is its B . See also: wblinv, wblpdf, wblrnd, wblstat, wblplot # name: # type: sq_string # elements: 1 # length: 47 Weibull cumulative distribution function (CDF). # name: # type: sq_string # elements: 1 # length: 6 wblinv # name: # type: sq_string # elements: 1 # length: 1080 statistics: x = wblinv ( p ) statistics: x = wblinv ( p , lambda ) statistics: x = wblinv ( p , lambda , k ) Inverse of the Weibull cumulative distribution function (iCDF). For each element of p , compute the quantile (the inverse of the CDF) of the Weibull distribution with scale parameter lambda and shape parameter k . The size of x is the common size of p , lambda , and k . A scalar input functions as a constant matrix of the same size as the other inputs. Default values are lambda = 1, k = 1. Further information about the Weibull distribution can be found at https://en.wikipedia.org/wiki/Weibull_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. The prob.WeibullDistribution class names these same two parameters A and B , after MATLAB. lambda is its A and k is its B . See also: wblcdf, wblpdf, wblrnd, wblstat, wblplot # name: # type: sq_string # elements: 1 # length: 63 Inverse of the Weibull cumulative distribution function (iCDF). # name: # type: sq_string # elements: 1 # length: 6 wblpdf # name: # type: sq_string # elements: 1 # length: 1078 statistics: y = wblpdf ( x ) statistics: y = wblpdf ( x , lambda ) statistics: y = wblpdf ( x , lambda , k ) Weibull probability density function (PDF). For each element of x , compute the probability density function (PDF) of the Weibull distribution with scale parameter lambda and shape parameter k . The size of y is the common size of x , lambda , and k . A scalar input functions as a constant matrix of the same size as the other inputs. Default values are lambda = 1, k = 1. Further information about the Weibull distribution can be found at https://en.wikipedia.org/wiki/Weibull_distribution Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. The prob.WeibullDistribution class names these same two parameters A and B , after MATLAB. lambda is its A and k is its B . See also: wblcdf, wblinv, wblrnd, wblfit, wbllike, wblstat, wblplot # name: # type: sq_string # elements: 1 # length: 43 Weibull probability density function (PDF). # name: # type: sq_string # elements: 1 # length: 6 wblrnd # name: # type: sq_string # elements: 1 # length: 1227 statistics: r = wblrnd ( lambda , k ) statistics: r = wblrnd ( lambda , k , rows ) statistics: r = wblrnd ( lambda , k , rows , cols , …) statistics: r = wblrnd ( lambda , k , [ sz ]) Random arrays from the Weibull distribution. r = wblrnd ( lambda , k ) returns an array of random numbers chosen from the Weibull distribution with scale parameter lambda and shape parameter k . The size of r is the common size of lambda and k . A scalar input functions as a constant matrix of the same size as the other inputs. Both parameters must be positive reals. When called with a single size argument, wblrnd returns a square matrix with the dimension specified. When called with more than one scalar argument, the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a row vector of dimensions, sz . Further information about the Weibull distribution can be found at https://en.wikipedia.org/wiki/Weibull_distribution The prob.WeibullDistribution class names these same two parameters A and B , after MATLAB. lambda is its A and k is its B . See also: wblcdf, wblinv, wblpdf, wblfit, wbllike, wblstat, wblplot # name: # type: sq_string # elements: 1 # length: 44 Random arrays from the Weibull distribution. # name: # type: sq_string # elements: 1 # length: 7 wienrnd # name: # type: sq_string # elements: 1 # length: 421 statistics: r = wienrnd ( t , d , n ) Return a simulated realization of the d -dimensional Wiener Process on the interval [0, t ]. If d is omitted, d = 1 is used. The first column of the return matrix contains time, the remaining columns contain the Wiener process. The optional parameter n defines the number of summands used for simulating the process over an interval of length 1. If n is omitted, n = 1000 is used. # name: # type: sq_string # elements: 1 # length: 90 Return a simulated realization of the d-dimensional Wiener Process on the interval [0, t]. # name: # type: sq_string # elements: 1 # length: 7 wishpdf # name: # type: sq_string # elements: 1 # length: 892 statistics: y = wishpdf ( W , Sigma , df , log_y =false) Compute the probability density function of the Wishart distribution Inputs: A p x p matrix W where to find the PDF. The p x p positive definite matrix Sigma and scalar degrees of freedom parameter df characterizing the Wishart distribution. (For the density to be finite, need df > ( p - 1).) If the flag log_y is set, return the log probability density – this helps avoid underflow when the numerical value of the density is very small Output: y is the probability density of Wishart( Sigma , df ) at W . Input arguments must be double or single ; integer, logical, and character arrays are rejected. MATLAB accepts a character array and evaluates it at the character codes, which Octave deliberately does not, since a character array is an integer type and integers are refused too. See also: wishrnd, iwishpdf, iwishrnd # name: # type: sq_string # elements: 1 # length: 68 Compute the probability density function of the Wishart distribution # name: # type: sq_string # elements: 1 # length: 7 wishrnd # name: # type: sq_string # elements: 1 # length: 981 statistics: [ W , D ] = wishrnd ( Sigma , df , D , n =1) Return a random matrix sampled from the Wishart distribution with given parameters Inputs: the p × p positive definite matrix Sigma (or the lower-triangular Cholesky factor D of Sigma ) and scalar degrees of freedom parameter df . df can be non-integer as long as df > p - 1 Output: a random p × p matrix W from the Wishart( Sigma , df ) distribution. If n > 1, then W is p x p x n and holds n such random matrices. (Optionally, the lower-triangular Cholesky factor D of Sigma is also returned.) Averaged across many samples, the mean of W should approach df * Sigma , and the variance of each element W _ij should approach df *( Sigma _ij^2 + Sigma _ii* Sigma _jj) References Yu-Cheng Ku and Peter Bloomfield (2010), Generating Random Wishart Matrices with Fractional Degrees of Freedom in OX, http://www.gwu.edu/~forcpgm/YuChengKu-030510final-WishartYu-ChengKu.pdf See also: wishpdf, iwishpdf, iwishrnd # name: # type: sq_string # elements: 1 # length: 82 Return a random matrix sampled from the Wishart distribution with given parameters statistics-release-1.9.2/inst/Distribution_Functions/evcdf.m000066400000000000000000000231351524624707500243040ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} evcdf (@var{x}) ## @deftypefnx {statistics} {@var{p} =} evcdf (@var{x}, @var{mu}) ## @deftypefnx {statistics} {@var{p} =} evcdf (@var{x}, @var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{p} =} evcdf (@dots{}, @qcode{'upper'}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} evcdf (@var{x}, @var{mu}, @var{sigma}, @var{pcov}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} evcdf (@var{x}, @var{mu}, @var{sigma}, @var{pcov}, @var{alpha}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} evcdf (@dots{}, @qcode{'upper'}) ## ## Extreme value cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the extreme value distribution (also known as the Gumbel or the type ## I generalized extreme value distribution) at the values in @var{x} with ## location parameter @var{mu} and scale parameter @var{sigma}. The size of ## @var{p} is the common size of @var{x}, @var{mu} and @var{sigma}. A scalar ## input functions as a constant matrix of the same size as the other inputs. ## ## Default values are @var{mu} = 0 and @var{sigma} = 1. ## ## When called with three output arguments, i.e. @qcode{[@var{p}, @var{plo}, ## @var{pup}]}, @code{evcdf} computes the confidence bounds for @var{p} when the ## input parameters @var{mu} and @var{sigma} are estimates. In such case, ## @var{pcov}, a @math{2*2} matrix containing the covariance matrix of the ## estimated parameters, is necessary. Optionally, @var{alpha}, which has a ## default value of 0.05, specifies the @qcode{100 * (1 - @var{alpha})} percent ## confidence bounds. @var{plo} and @var{pup} are arrays of the same size as ## @var{p} containing the lower and upper confidence bounds. ## ## @code{[@dots{}] = evcdf (@dots{}, "upper")} computes the upper tail ## probability of the extreme value distribution with parameters @var{x0} and ## @var{gamma}, at the values in @var{x}. ## ## The Gumbel distribution is used to model the distribution of the maximum (or ## the minimum) of a number of samples of various distributions. This version ## is suitable for modeling minima. For modeling maxima, use the alternative ## Gumbel CDF, @code{gumbelcdf}. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{evinv, evpdf, evrnd, evfit, evlike, evstat, gumbelcdf} ## @end deftypefn function [varargout] = evcdf (x, varargin) ## Check for valid number of input arguments if (nargin < 1 || nargin > 6) error ("evcdf: invalid number of input arguments."); endif ## Check for 'upper' flag if (nargin > 1 && strcmpi (varargin{end}, 'upper')) uflag = true; varargin(end) = []; elseif (nargin > 1 && ischar (varargin{end}) && ... ! strcmpi (varargin{end}, 'upper')) error ("evcdf: invalid argument for upper tail."); else uflag = false; endif ## Get extra arguments (if they exist) or add defaults if (numel (varargin) > 0) mu = varargin{1}; else mu = 0; endif if (numel (varargin) > 1) sigma = varargin{2}; else sigma = 1; endif if (numel (varargin) > 2) pcov = varargin{3}; ## Check for valid covariance matrix 2x2 if (! isequal (size (pcov), [2, 2])) error ("evcdf: invalid size of covariance matrix."); endif else ## Check that cov matrix is provided if 3 output arguments are requested if (nargout > 1) error ("evcdf: covariance matrix is required for confidence bounds."); endif pcov = []; endif if (numel (varargin) > 3) alpha = varargin{4}; ## Check for valid alpha value if (! isnumeric (alpha) || numel (alpha) !=1 || alpha <= 0 || alpha >= 1) error ("evcdf: invalid value for alpha."); endif else alpha = 0.05; endif ## Check for common size of X, MU, and SIGMA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (sigma)) [err, x, mu, sigma] = common_size (x, mu, sigma); if (err > 0) error ("evcdf: X, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("evcdf: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma)) error ("evcdf: X, MU, and SIGMA must not be complex."); endif ## Return NaNs for out of range parameters. sigma(sigma <= 0) = NaN; ## Compute extreme value cdf z = (x - mu) ./ sigma; if (uflag) p = exp (-exp (z)); else p = -expm1 (-exp (z)); endif ## Check for appropriate class if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Prepare output varargout{1} = cast (p, is_class); if (nargout > 1) plo = NaN (size (z), is_class); pup = NaN (size (z), is_class); endif ## Check sigma if (isscalar (sigma)) if (sigma > 0) sigma_p = true (size (z)); else if (nargout == 3) varargout{2} = plo; varargout{3} = pup; endif return; endif else sigma_p = sigma > 0; endif ## Compute confidence bounds (if requested) if (nargout >= 2) zvar = (pcov(1,1) + 2 * pcov(1,2) * z(sigma_p) + ... pcov(2,2) * z(sigma_p) .^ 2) ./ (sigma .^ 2); if (any (zvar < 0)) error ("evcdf: bad covariance matrix."); endif normz = -probit (alpha / 2); halfwidth = normz * sqrt (zvar); zlo = z(sigma_p) - halfwidth; zup = z(sigma_p) + halfwidth; if (uflag) plo(sigma_p) = exp (-exp (zup)); pup(sigma_p) = exp (-exp (zlo)); else plo(sigma_p) = -expm1 (-exp (zlo)); pup(sigma_p) = -expm1 (-exp (zup)); endif varargout{2} = plo; varargout{3} = pup; endif endfunction %!demo %! ## Plot various CDFs from the extreme value distribution %! x = -10:0.01:10; %! p1 = evcdf (x, 0.5, 2); %! p2 = evcdf (x, 1.0, 2); %! p3 = evcdf (x, 1.5, 3); %! p4 = evcdf (x, 3.0, 4); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c') %! grid on %! legend ({'μ = 0.5, σ = 2', 'μ = 1.0, σ = 2', ... %! 'μ = 1.5, σ = 3', 'μ = 3.0, σ = 4'}, 'location', 'southeast') %! title ('Extreme value CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-Inf, 1, 2, Inf]; %! y = [0, 0.6321, 0.9340, 1]; %!assert_equal (evcdf (x, ones (1,4), ones (1,4)), y, 1e-4) %!assert_equal (evcdf (x, 1, ones (1,4)), y, 1e-4) %!assert_equal (evcdf (x, ones (1,4), 1), y, 1e-4) %!assert_equal (evcdf (x, [0, -Inf, NaN, Inf], 1), [0, 1, NaN, NaN], 1e-4) %!assert_equal (evcdf (x, 1, [Inf, NaN, -1, 0]), [NaN, NaN, NaN, NaN], 1e-4) %!assert_equal (evcdf ([x(1:2), NaN, x(4)], 1, 1), [y(1:2), NaN, y(4)], 1e-4) %!assert_equal (evcdf (x, 'upper'), [1, 0.0660, 0.0006, 0], 1e-4) ## Test class of input preserved %!assert_equal (evcdf ([x, NaN], 1, 1), [y, NaN], 1e-4) %!assert_equal (evcdf (single ([x, NaN]), 1, 1), single ([y, NaN]), 1e-4) %!assert_equal (evcdf ([x, NaN], single (1), 1), single ([y, NaN]), 1e-4) %!assert_equal (evcdf ([x, NaN], 1, single (1)), single ([y, NaN]), 1e-4) ## Test input validation %!error evcdf () %!error evcdf (1,2,3,4,5,6,7) %!error evcdf (1, 2, 3, 4, 'uper') %!error ... %! evcdf (ones (3), ones (2), ones (2)) %!error evcdf (2, 3, 4, [1, 2]) %!error ... %! [p, plo, pup] = evcdf (1, 2, 3) %!error [p, plo, pup] = ... %! evcdf (1, 2, 3, [1, 0; 0, 1], 0) %!error [p, plo, pup] = ... %! evcdf (1, 2, 3, [1, 0; 0, 1], 1.22) %!error [p, plo, pup] = ... %! evcdf (1, 2, 3, [1, 0; 0, 1], 'alpha', 'upper') %!error evcdf (int32 (2), 2, 2) %!error evcdf (true, 2, 2) %!error evcdf ('a', 2, 2) %!error evcdf (i, 2, 2) %!error evcdf (2, i, 2) %!error evcdf (2, 2, i) %!error ... %! [p, plo, pup] = evcdf (1, 2, 3, [1, 0; 0, -inf], 0.04) statistics-release-1.9.2/inst/Distribution_Functions/evinv.m000066400000000000000000000202011524624707500243330ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} evinv (@var{p}) ## @deftypefnx {statistics} {@var{x} =} evinv (@var{p}, @var{mu}) ## @deftypefnx {statistics} {@var{x} =} evinv (@var{p}, @var{mu}, @var{sigma}) ## @deftypefnx {statistics} {[@var{x}, @var{xlo}, @var{xup}] =} evinv (@var{p}, @var{mu}, @var{sigma}, @var{pcov}) ## @deftypefnx {statistics} {[@var{x}, @var{xlo}, @var{xup}] =} evinv (@var{p}, @var{mu}, @var{sigma}, @var{pcov}, @var{alpha}) ## ## Inverse of the extreme value cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the extreme value distribution (also known as the Gumbel or the type I ## generalized extreme value distribution) with location parameter @var{mu} and ## scale parameter @var{sigma}. The size of @var{x} is the common size of ## @var{p}, @var{mu} and @var{sigma}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## Default values are @var{mu} = 0 and @var{sigma} = 1. ## ## When called with three output arguments, i.e. @qcode{[@var{x}, @var{xlo}, ## @var{xup}]}, @code{evinv} computes the confidence bounds for @var{x} when the ## input parameters @var{mu} and @var{sigma} are estimates. In such case, ## @var{pcov}, a @math{2*2} matrix containing the covariance matrix of the ## estimated parameters, is necessary. Optionally, @var{alpha}, which has a ## default value of 0.05, specifies the @qcode{100 * (1 - @var{alpha})} percent ## confidence bounds. @var{xlo} and @var{xup} are arrays of the same size as ## @var{x} containing the lower and upper confidence bounds. ## ## The Gumbel distribution is used to model the distribution of the maximum (or ## the minimum) of a number of samples of various distributions. This version ## is suitable for modeling minima. For modeling maxima, use the alternative ## Gumbel iCDF, @code{gumbelinv}. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{evcdf, evpdf, evrnd, evfit, evlike, evstat, gumbelinv} ## @end deftypefn function [x, xlo, xup] = evinv (p, mu, sigma, pcov, alpha) ## Check for valid number of input arguments if (nargin < 1 || nargin > 5) error ("evinv: invalid number of input arguments."); endif ## Add defaults (if missing input arguments) if (nargin < 2) mu = 0; endif if (nargin < 3) sigma = 1; endif ## Check if PCOV is provided when confidence bounds are requested if (nargout > 2) if (nargin < 4) error ("evinv: covariance matrix is required for confidence bounds."); endif ## Check for valid covariance matrix 2x2 if (! isequal (size (pcov), [2, 2])) error ("evinv: invalid size of covariance matrix."); endif ## Check for valid alpha value if (nargin < 5) alpha = 0.05; elseif (! isnumeric (alpha) || numel (alpha) !=1 || alpha <= 0 || alpha >= 1) error ("evinv: invalid value for alpha."); endif endif ## Check for common size of P, MU, and SIGMA if (! isscalar (p) || ! isscalar (mu) || ! isscalar (sigma)) [err, p, mu, sigma] = common_size (p, mu, sigma); if (err > 0) error ("evinv: P, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for P, MU, and SIGMA being double or single if (! (isfloat (p) && isfloat (mu) && isfloat (sigma))) error ("evinv: P, MU, and SIGMA must be double or single."); endif ## Check for P, MU, and SIGMA being reals if (iscomplex (p) || iscomplex (mu) || iscomplex (sigma)) error ("evinv: P, MU, and SIGMA must not be complex."); endif ## Check for appropriate class if (isa (p, 'single') || isa (mu, 'single') || isa (sigma, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Compute inverse of type 1 extreme value cdf k = (eps <= p & p < 1); if (all (k(:))) q = log (-log (1 - p)); else q = zeros (size (p), is_class); q(k) = log (-log (1 - p(k))); ## Return -Inf for p = 0 and Inf for p = 1 q(p < eps) = -Inf; q(p == 1) = Inf; ## Return NaN for out of range values of P q(p < 0 | 1 < p | isnan (p)) = NaN; endif ## Return NaN for out of range values of SIGMA sigma(sigma <= 0) = NaN; x = sigma .* q + mu; ## Compute confidence bounds if requested. if (nargout >= 2) xvar = pcov(1,1) + 2 * pcov(1,2) * q + pcov(2,2) * q .^ 2; if (any (xvar < 0)) error ("evinv: bad covariance matrix."); endif z = -norminv (alpha / 2); halfwidth = z * sqrt (xvar); xlo = x - halfwidth; xup = x + halfwidth; endif endfunction %!demo %! ## Plot various iCDFs from the extreme value distribution %! p = 0.001:0.001:0.999; %! x1 = evinv (p, 0.5, 2); %! x2 = evinv (p, 1.0, 2); %! x3 = evinv (p, 1.5, 3); %! x4 = evinv (p, 3.0, 4); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c') %! grid on %! ylim ([-10, 10]) %! legend ({'μ = 0.5, σ = 2', 'μ = 1.0, σ = 2', ... %! 'μ = 1.5, σ = 3', 'μ = 3.0, σ = 4'}, 'location', 'northwest') %! title ('Extreme value iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p, x %! p = [0, 0.05, 0.5 0.95]; %! x = [-Inf, -2.9702, -0.3665, 1.0972]; %!assert_equal (evinv (p), x, 1e-4) %!assert_equal (evinv (p, zeros (1,4), ones (1,4)), x, 1e-4) %!assert_equal (evinv (p, 0, ones (1,4)), x, 1e-4) %!assert_equal (evinv (p, zeros (1,4), 1), x, 1e-4) %!assert_equal (evinv (p, [0, -Inf, NaN, Inf], 1), [-Inf, -Inf, NaN, Inf], 1e-4) %!assert_equal (evinv (p, 0, [Inf, NaN, -1, 0]), [-Inf, NaN, NaN, NaN], 1e-4) %!assert_equal (evinv ([p(1:2), NaN, p(4)], 0, 1), [x(1:2), NaN, x(4)], 1e-4) ## Test class of input preserved %!assert_equal (evinv ([p, NaN], 0, 1), [x, NaN], 1e-4) %!assert_equal (evinv (single ([p, NaN]), 0, 1), single ([x, NaN]), 1e-4) %!assert_equal (evinv ([p, NaN], single (0), 1), single ([x, NaN]), 1e-4) %!assert_equal (evinv ([p, NaN], 0, single (1)), single ([x, NaN]), 1e-4) ## Test input validation %!error evinv () %!error evinv (1,2,3,4,5,6) %!error ... %! evinv (ones (3), ones (2), ones (2)) %!error ... %! [p, plo, pup] = evinv (2, 3, 4, [1, 2]) %!error ... %! [p, plo, pup] = evinv (1, 2, 3) %!error [p, plo, pup] = ... %! evinv (1, 2, 3, [1, 0; 0, 1], 0) %!error [p, plo, pup] = ... %! evinv (1, 2, 3, [1, 0; 0, 1], 1.22) %!error evinv (int32 (2), 2, 2) %!error evinv (true, 2, 2) %!error evinv ('a', 2, 2) %!error evinv (i, 2, 2) %!error evinv (2, i, 2) %!error evinv (2, 2, i) %!error ... %! [p, plo, pup] = evinv (1, 2, 3, [-1, -10; -Inf, -Inf], 0.04) statistics-release-1.9.2/inst/Distribution_Functions/evpdf.m000066400000000000000000000117211524624707500243170ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} evpdf (@var{x}) ## @deftypefnx {statistics} {@var{y} =} evpdf (@var{x}, @var{mu}) ## @deftypefnx {statistics} {@var{y} =} evpdf (@var{x}, @var{mu}, @var{sigma}) ## ## Extreme value probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the extreme value distribution (also known as the Gumbel or the type I ## generalized extreme value distribution) with location parameter @var{mu} and ## scale parameter @var{sigma}. The size of @var{y} is the common size of ## @var{x}, @var{mu} and @var{sigma}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## Default values are @var{mu} = 0 and @var{sigma} = 1. ## ## The Gumbel distribution is used to model the distribution of the maximum (or ## the minimum) of a number of samples of various distributions. This version ## is suitable for modeling minima. For modeling maxima, use the alternative ## Gumbel iCDF, @code{gumbelinv}. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{evcdf, evinv, evrnd, evfit, evlike, evstat, gumbelpdf} ## @end deftypefn function y = evpdf (x, mu, sigma) ## Check for valid number of input arguments if (nargin < 1) error ("evpdf: function called with too few input arguments."); endif ## Add defaults (if missing input arguments) if (nargin < 2) mu = 0; endif if (nargin < 3) sigma = 1; endif ## Check for common size of X, MU, and SIGMA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (sigma)) [err, x, mu, sigma] = common_size (x, mu, sigma); if (err > 0) error ("evpdf: X, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("evpdf: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma)) error ("evpdf: X, MU, and SIGMA must not be complex."); endif ## Return NaNs for out of range parameters sigma(sigma <= 0) = NaN; ## Compute pdf of type 1 extreme value distribution z = (x - mu) ./ sigma; y = exp (z - exp (z)) ./ sigma; ## Force 0 for extreme right tail, instead of getting exp (Inf - Inf) = NaN y(z == Inf) = 0; endfunction %!demo %! ## Plot various PDFs from the Extreme value distribution %! x = -10:0.001:10; %! y1 = evpdf (x, 0.5, 2); %! y2 = evpdf (x, 1.0, 2); %! y3 = evpdf (x, 1.5, 3); %! y4 = evpdf (x, 3.0, 4); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c') %! grid on %! ylim ([0, 0.2]) %! legend ({'μ = 0.5, σ = 2', 'μ = 1.0, σ = 2', ... %! 'μ = 1.5, σ = 3', 'μ = 3.0, σ = 4'}, 'location', 'northeast') %! title ('Extreme value PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y0, y1 %! x = [-5, 0, 1, 2, 3]; %! y0 = [0.0067, 0.3679, 0.1794, 0.0046, 0]; %! y1 = [0.0025, 0.2546, 0.3679, 0.1794, 0.0046]; %!assert_equal (evpdf (x), y0, 1e-4) %!assert_equal (evpdf (x, zeros (1,5), ones (1,5)), y0, 1e-4) %!assert_equal (evpdf (x, ones (1,5), ones (1,5)), y1, 1e-4) ## Test input validation %!error evpdf () %!error ... %! evpdf (ones (3), ones (2), ones (2)) %!error evpdf (int32 (2), 2, 2) %!error evpdf (true, 2, 2) %!error evpdf ('a', 2, 2) %!error evpdf (i, 2, 2) %!error evpdf (2, i, 2) %!error evpdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/evrnd.m000066400000000000000000000153721524624707500243370ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} evrnd (@var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{r} =} evrnd (@var{mu}, @var{sigma}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} evrnd (@var{mu}, @var{sigma}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} evrnd (@var{mu}, @var{sigma}, [@var{sz}]) ## ## Random arrays from the extreme value distribution. ## ## @code{@var{r} = evrnd (@var{mu}, @var{sigma})} returns an array of random ## numbers chosen from the extreme value distribution (also known as the Gumbel ## or the type I generalized extreme value distribution) with location ## parameter @var{mu} and scale parameter @var{sigma}. The size of @var{r} is ## the common size of @var{mu} and @var{sigma}. A scalar input functions as a ## constant matrix of the same size as the other inputs. ## ## When called with a single size argument, @code{evrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## The Gumbel distribution is used to model the distribution of the maximum (or ## the minimum) of a number of samples of various distributions. This version ## is suitable for modeling minima. For modeling maxima, use the alternative ## Gumbel iCDF, @code{gumbelinv}. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## @seealso{evcdf, evinv, evpdf, evfit, evlike, evstat} ## @end deftypefn function r = evrnd (mu, sigma, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("evrnd: function called with too few input arguments."); endif ## Check for common size of MU and SIGMA if (! isscalar (mu) || ! isscalar (sigma)) [retval, mu, sigma] = common_size (mu, sigma); if (retval > 0) error ("evrnd: MU and SIGMA must be of common size or scalars."); endif endif ## Check for MU and SIGMA being reals if (iscomplex (mu) || iscomplex (sigma)) error ("evrnd: MU and SIGMA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (mu); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("evrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("evrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (mu) && ! isequal (size (mu), size (ones (sz)))) error ("evrnd: MU and SIGMA must be scalars or of size SZ."); endif ## Check for class type if (isa (mu, 'single') || isa (sigma, 'single')) cls = 'single'; else cls = 'double'; endif ## Return NaNs for out of range values of SIGMA sigma(sigma < 0) = NaN; ## Generate uniform random values, and apply the extreme value inverse CDF. r = log (-log (rand (sz, cls))) .* sigma + mu; endfunction ## Test output %!assert_equal (size (evrnd (1, 1)), [1 1]) %!assert_equal (size (evrnd (1, ones (2,1))), [2, 1]) %!assert_equal (size (evrnd (1, ones (2,2))), [2, 2]) %!assert_equal (size (evrnd (ones (2,1), 1)), [2, 1]) %!assert_equal (size (evrnd (ones (2,2), 1)), [2, 2]) %!assert_equal (size (evrnd (1, 1, 3)), [3, 3]) %!assert_equal (size (evrnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (evrnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (evrnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (evrnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (evrnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (evrnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (evrnd (1, 1, [])), [0, 0]) %!assert_equal (size (evrnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (evrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (evrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (evrnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (evrnd (1, 1)), "double") %!assert_equal (class (evrnd (1, single (1))), "single") %!assert_equal (class (evrnd (1, single ([1, 1]))), "single") %!assert_equal (class (evrnd (single (1), 1)), "single") %!assert_equal (class (evrnd (single ([1, 1]), 1)), "single") ## Test input validation %!error evrnd () %!error evrnd (1) %!error ... %! evrnd (ones (3), ones (2)) %!error ... %! evrnd (ones (2), ones (3)) %!error evrnd (i, 2, 3) %!error evrnd (1, i, 3) %!error ... %! evrnd (1, 2, 1.2) %!error ... %! evrnd (1, 2, ones (2)) %!error ... %! evrnd (1, 2, [2 0 2.5]) %!error ... %! evrnd (1, 2, 2, 1.5, 5) %!error ... %! evrnd (2, ones (2), 3) %!error ... %! evrnd (2, ones (2), [3, 2]) %!error ... %! evrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/expcdf.m000066400000000000000000000217031524624707500244650ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} expcdf (@var{x}) ## @deftypefnx {statistics} {@var{p} =} expcdf (@var{x}, @var{mu}) ## @deftypefnx {statistics} {@var{p} =} expcdf (@dots{}, @qcode{'upper'}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} expcdf (@var{x}, @var{mu}, @var{pcov}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} expcdf (@var{x}, @var{mu}, @var{pcov}, @var{alpha}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} expcdf (@dots{}, @qcode{'upper'}) ## ## Exponential cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the exponential distribution with mean parameter @var{mu}. The size ## of @var{p} is the common size of @var{x} and @var{mu}. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## Default value is @var{mu} = 1. ## ## A common alternative parameterization of the exponential distribution is to ## use the parameter @math{λ} defined as the mean number of events in an ## interval as opposed to the parameter @math{μ}, which is the mean wait time ## for an event to occur. @math{λ} and @math{μ} are reciprocals, ## i.e. @math{μ = 1 / λ}. ## ## When called with three output arguments, i.e. @qcode{[@var{p}, @var{plo}, ## @var{pup}]}, @code{expcdf} computes the confidence bounds for @var{p} when ## the input parameter @var{mu} is an estimate. In such case, @var{pcov}, a ## scalar value with the variance of the estimated parameter @var{mu}, is ## necessary. Optionally, @var{alpha}, which has a default value of 0.05, ## specifies the @qcode{100 * (1 - @var{alpha})} percent confidence bounds. ## @var{plo} and @var{pup} are arrays of the same size as @var{p} containing the ## lower and upper confidence bounds. ## ## @code{[@dots{}] = expcdf (@dots{}, "upper")} computes the upper tail ## probability of the exponential distribution with parameter @var{mu}, at the ## values in @var{x}. ## ## Further information about the exponential distribution can be found at ## @url{https://en.wikipedia.org/wiki/Exponential_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{expinv, exppdf, exprnd, expfit, explike, expstat} ## @end deftypefn function [varargout] = expcdf (x, varargin) ## Check for valid number of input arguments if (nargin < 1 || nargin > 5) error ("expcdf: invalid number of input arguments."); endif ## Check for "upper" flag if (nargin > 1 && strcmpi (varargin{end}, 'upper')) uflag = true; varargin(end) = []; elseif (nargin > 1 && ischar (varargin{end}) && ... ! strcmpi (varargin{end}, 'upper')) error ("expcdf: invalid argument for upper tail."); else uflag = false; endif ## Get extra arguments (if they exist) or add defaults if (numel (varargin) > 0) mu = varargin{1}; else mu = 1; endif if (numel (varargin) > 1) pcov = varargin{2}; ## Check for variance being a scalar if (! isscalar (pcov)) error ("expcdf: invalid size of variance, PCOV must be a scalar."); endif if (pcov < 0) error ("expcdf: variance, PCOV, cannot be negative."); endif else ## Check that cov matrix is provided if 3 output arguments are requested if (nargout > 1) error ("expcdf: variance, PCOV, is required for confidence bounds."); endif pcov = []; endif if (numel (varargin) > 2) alpha = varargin{3}; ## Check for valid alpha value if (! isnumeric (alpha) || numel (alpha) != 1 || alpha <= 0 || alpha >= 1) error ("expcdf: invalid value for alpha."); endif else alpha = 0.05; endif ## Check for common size of X and MU if (! isscalar (x) || ! isscalar (mu)) [err, x, mu] = common_size (x, mu); if (err > 0) error ("expcdf: X and MU must be of common size or scalars."); endif endif ## Check for X and MU being double or single if (! (isfloat (x) && isfloat (mu))) error ("expcdf: X and MU must be double or single."); endif ## Check for X and MU being reals if (iscomplex (x) || iscomplex (mu)) error ("expcdf: X and MU must not be complex."); endif ## Check for appropriate class if (isa (x, 'single') || isa (mu, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Return NaNs for out of range parameters. mu(mu <= 0) = NaN; ## Compute P value for exponential cdf z = x ./ mu; ## Force 0 for negative X z(z < 0) = 0; ## Check uflag if (uflag) p = exp (-z); else p = -expm1 (-z); endif ## Prepare output varargout{1} = cast (p, is_class); if (nargout > 1) plo = NaN (size (z), is_class); pup = NaN (size (z), is_class); endif ## Compute confidence bounds (if requested) if (nargout >= 2) ## Convert to log scale log_z = log (z); norm_z = -probit (alpha / 2); halfwidth = norm_z * sqrt (pcov ./ (mu .^ 2)); zlo = log_z - halfwidth; zup = log_z + halfwidth; ## Convert to original scale if (uflag) plo = exp (-exp (zup)); pup = exp (-exp (zlo)); else plo = - expm1 (-exp (zlo)); pup = - expm1 (-exp (zup)); endif ## Prepare output varargout{2} = plo; varargout{3} = pup; endif endfunction %!demo %! ## Plot various CDFs from the exponential distribution %! x = 0:0.01:5; %! p1 = expcdf (x, 2/3); %! p2 = expcdf (x, 1.0); %! p3 = expcdf (x, 2.0); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r') %! grid on %! legend ({'μ = 2/3', 'μ = 1', 'μ = 2'}, 'location', 'southeast') %! title ('Exponential CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, p %! x = [-1 0 0.5 1 Inf]; %! p = [0, 1 - exp(-x(2:end)/2)]; %!assert_equal (expcdf (x, 2 * ones (1, 5)), p, 1e-16) %!assert_equal (expcdf (x, 2), p, 1e-16) %!assert_equal (expcdf (x, 2 * [1, 0, NaN, 1, 1]), [0, NaN, NaN, p(4:5)], 1e-16) %!assert_equal (expcdf ([x, NaN], 2), [p, NaN], 1e-16) ## Test class of input preserved %!assert_equal (expcdf (single ([x, NaN]), 2), single ([p, NaN])) %!assert_equal (expcdf ([x, NaN], single (2)), single ([p, NaN])) ## Test values against MATLAB output %!test %! [p, plo, pup] = expcdf (1, 2, 3); %! assert_equal (p, 0.39346934028737, 1e-14); %! assert_equal (plo, 0.08751307220484, 1e-14); %! assert_equal (pup, 0.93476821257933, 1e-14); %!test %! [p, plo, pup] = expcdf (1, 2, 2, 0.1); %! assert_equal (p, 0.39346934028737, 1e-14); %! assert_equal (plo, 0.14466318041675, 1e-14); %! assert_equal (pup, 0.79808291849140, 1e-14); %!test %! [p, plo, pup] = expcdf (1, 2, 2, 0.1, 'upper'); %! assert_equal (p, 0.60653065971263, 1e-14); %! assert_equal (plo, 0.20191708150860, 1e-14); %! assert_equal (pup, 0.85533681958325, 1e-14); ## Test input validation %!error expcdf () %!error expcdf (1, 2 ,3 ,4 ,5, 6) %!error expcdf (1, 2, 3, 4, 'uper') %!error ... %! expcdf (ones (3), ones (2)) %!error ... %! expcdf (2, 3, [1, 2]) %!error ... %! [p, plo, pup] = expcdf (1, 2) %!error [p, plo, pup] = ... %! expcdf (1, 2, 3, 0) %!error [p, plo, pup] = ... %! expcdf (1, 2, 3, 1.22) %!error [p, plo, pup] = ... %! expcdf (1, 2, 3, 'alpha', 'upper') %!error expcdf (int32 (2), 2) %!error expcdf (true, 2) %!error expcdf ('a', 2) %!error expcdf (i, 2) %!error expcdf (2, i) %!error ... %! [p, plo, pup] = expcdf (1, 2, -1, 0.04) statistics-release-1.9.2/inst/Distribution_Functions/expinv.m000066400000000000000000000173411524624707500245300ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} expinv (@var{p}) ## @deftypefnx {statistics} {@var{x} =} expinv (@var{p}, @var{mu}) ## @deftypefnx {statistics} {[@var{x}, @var{xlo}, @var{xup}] =} expinv (@var{p}, @var{mu}, @var{pcov}) ## @deftypefnx {statistics} {[@var{x}, @var{xlo}, @var{xup}] =} expinv (@var{p}, @var{mu}, @var{pcov}, @var{alpha}) ## ## Inverse of the exponential cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the exponential distribution with mean @var{mu}. The size of @var{x} is the ## common size of @var{p} and @var{mu}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## Default value is @var{mu} = 1. ## ## A common alternative parameterization of the exponential distribution is to ## use the parameter @math{λ} defined as the mean number of events in an ## interval as opposed to the parameter @math{μ}, which is the mean wait time ## for an event to occur. @math{λ} and @math{μ} are reciprocals, ## i.e. @math{μ = 1 / λ}. ## ## When called with three output arguments, i.e. @qcode{[@var{x}, @var{xlo}, ## @var{xup}]}, @code{expinv} computes the confidence bounds for @var{x} when ## the input parameter @var{mu} is an estimate. In such case, @var{pcov}, a ## scalar value with the variance of the estimated parameter @var{mu}, is ## necessary. Optionally, @var{alpha}, which has a default value of 0.05, ## specifies the @qcode{100 * (1 - @var{alpha})} percent confidence bounds. ## @var{xlo} and @var{xup} are arrays of the same size as @var{x} containing the ## lower and upper confidence bounds. ## ## Further information about the exponential distribution can be found at ## @url{https://en.wikipedia.org/wiki/Exponential_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{expcdf, exppdf, exprnd, expfit, explike, expstat} ## @end deftypefn function [varargout] = expinv (p, varargin) ## Check for valid number of input arguments if (nargin < 1 || nargin > 4) error ("expinv: invalid number of input arguments."); endif ## Get extra arguments (if they exist) or add defaults if (numel (varargin) > 0) mu = varargin{1}; else mu = 1; endif if (numel (varargin) > 1) pcov = varargin{2}; ## Check for variance being a scalar if (! isscalar (pcov)) error ("expinv: invalid size of variance, PCOV must be a scalar."); endif if (pcov < 0) error ("expinv: variance, PCOV, cannot be negative."); endif else ## Check that cov matrix is provided if 3 output arguments are requested if (nargout > 1) error ("expinv: variance, PCOV, is required for confidence bounds."); endif pcov = []; endif if (numel (varargin) > 2) alpha = varargin{3}; ## Check for valid alpha value if (! isnumeric (alpha) || numel (alpha) != 1 || alpha <= 0 || alpha >= 1) error ("expinv: invalid value for alpha."); endif else alpha = 0.05; endif ## Check for common size of P and MU if (! isscalar (p) || ! isscalar (mu)) [retval, p, mu] = common_size (p, mu); if (retval > 0) error ("expinv: P and MU must be of common size or scalars."); endif endif ## Check for P and MU being double or single if (! (isfloat (p) && isfloat (mu))) error ("expinv: P and MU must be double or single."); endif ## Check for P and MU being reals if (iscomplex (p) || iscomplex (mu)) error ("expinv: P and MU must not be complex."); endif ## Check for appropriate class if (isa (p, 'single') || isa (mu, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Create output matrix if (isa (p, 'single') || isa (mu, 'single')) x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ## Handle edge cases k = (p == 1) & (mu > 0); x(k) = Inf; ## Handle valid cases k = (p >= 0) & (p < 1) & (mu > 0); if (isscalar (mu)) x(k) = - mu * log (1 - p(k)); else x(k) = - mu(k) .* log (1 - p(k)); endif ## Prepare output varargout{1} = cast (x, is_class); if (nargout > 1) xlo = NaN (size (z), is_class); xup = NaN (size (z), is_class); endif ## Compute confidence bounds (if requested) if (nargout >= 2) ## Convert to log scale log_x = log (x); z = -probit (alpha / 2); halfwidth = z * sqrt (pcov ./ (mu.^2)); ## Convert to original scale xlo = exp (log_x - halfwidth); xup = exp (log_x + halfwidth); ## Prepare output varargout{2} = plo; varargout{3} = pup; endif endfunction %!demo %! ## Plot various iCDFs from the exponential distribution %! p = 0.001:0.001:0.999; %! x1 = expinv (p, 2/3); %! x2 = expinv (p, 1.0); %! x3 = expinv (p, 2.0); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r') %! grid on %! ylim ([0, 5]) %! legend ({'μ = 2/3', 'μ = 1', 'μ = 2'}, 'location', 'northwest') %! title ('Exponential iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p %! p = [-1 0 0.3934693402873666 1 2]; %!assert_equal (expinv (p, 2*ones (1,5)), [NaN 0 1 Inf NaN], eps) %!assert_equal (expinv (p, 2), [NaN 0 1 Inf NaN], eps) %!assert_equal (expinv (p, 2*[1 0 NaN 1 1]), [NaN NaN NaN Inf NaN], eps) %!assert_equal (expinv ([p(1:2) NaN p(4:5)], 2), [NaN 0 NaN Inf NaN], eps) ## Test class of input preserved %!assert_equal (expinv ([p, NaN], 2), [NaN 0 1 Inf NaN NaN], eps) %!assert_equal (expinv (single ([p, NaN]), 2), single ([NaN 0 1 Inf NaN NaN]), eps) %!assert_equal (expinv ([p, NaN], single (2)), single ([NaN 0 1 Inf NaN NaN]), eps) ## Test input validation %!error expinv () %!error expinv (1, 2 ,3 ,4 ,5) %!error ... %! expinv (ones (3), ones (2)) %!error ... %! expinv (2, 3, [1, 2]) %!error ... %! [x, xlo, xup] = expinv (1, 2) %!error [x, xlo, xup] = ... %! expinv (1, 2, 3, 0) %!error [x, xlo, xup] = ... %! expinv (1, 2, 3, 1.22) %!error [x, xlo, xup] = ... %! expinv (1, 2, 3, [0.05, 0.1]) %!error expinv (int32 (2), 2) %!error expinv (true, 2) %!error expinv ('a', 2) %!error expinv (i, 2) %!error expinv (2, i) %!error ... %! [x, xlo, xup] = expinv (1, 2, -1, 0.04) statistics-release-1.9.2/inst/Distribution_Functions/exppdf.m000066400000000000000000000114251524624707500245020ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} exppdf (@var{x}) ## @deftypefnx {statistics} {@var{y} =} exppdf (@var{x}, @var{mu}) ## ## Exponential probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the exponential distribution with mean parameter @var{mu}. The size of ## @var{y} is the common size of @var{x} and @var{mu}. A scalar input functions ## as a constant matrix of the same size as the other inputs. ## ## Default value for @var{mu} = 1. ## ## A common alternative parameterization of the exponential distribution is to ## use the parameter @math{λ} defined as the mean number of events in an ## interval as opposed to the parameter @math{μ}, which is the mean wait time ## for an event to occur. @math{λ} and @math{μ} are reciprocals, ## i.e. @math{μ = 1 / λ}. ## ## Further information about the exponential distribution can be found at ## @url{https://en.wikipedia.org/wiki/Exponential_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{expcdf, expinv, exprnd, expfit, explike, expstat} ## @end deftypefn function y = exppdf (x, mu) ## Check for valid number of input arguments if (nargin < 1) error ("exppdf: function called with too few input arguments."); endif ## Add defaults (if missing input arguments) if (nargin < 2) mu = 0; endif ## Check for common size of X and MU if (! isscalar (x) || ! isscalar (mu)) [retval, x, mu] = common_size (x, mu); if (retval > 0) error ("exppdf: X and MU must be of common size or scalars."); endif endif ## Check for X and MU being double or single if (! (isfloat (x) && isfloat (mu))) error ("exppdf: X and MU must be double or single."); endif ## Check for X and MU being reals if (iscomplex (x) || iscomplex (mu)) error ("exppdf: X and MU must not be complex."); endif ## Check for appropriate class if (isa (x, 'single') || isa (mu, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif k = isnan (x) | ! (mu > 0); y(k) = NaN; k = (x >= 0) & (x < Inf) & (mu > 0); if (isscalar (mu)) y(k) = exp (-x(k) / mu) / mu; else y(k) = exp (-x(k) ./ mu(k)) ./ mu(k); endif endfunction %!demo %! ## Plot various PDFs from the exponential distribution %! x = 0:0.01:5; %! y1 = exppdf (x, 2/3); %! y2 = exppdf (x, 1.0); %! y3 = exppdf (x, 2.0); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r') %! grid on %! ylim ([0, 1.5]) %! legend ({'μ = 2/3', 'μ = 1', 'μ = 2'}, 'location', 'northeast') %! title ('Exponential PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x,y %! x = [-1 0 0.5 1 Inf]; %! y = gampdf (x, 1, 2); %!assert_equal (exppdf (x, 2*ones (1,5)), y) %!assert_equal (exppdf (x, 2*[1 0 NaN 1 1]), [y(1) NaN NaN y(4:5)]) %!assert_equal (exppdf ([x, NaN], 2), [y, NaN]) ## Test class of input preserved %!assert_equal (exppdf (single ([x, NaN]), 2), single ([y, NaN])) %!assert_equal (exppdf ([x, NaN], single (2)), single ([y, NaN])) ## Test input validation %!error exppdf () %!error exppdf (1,2,3) %!error ... %! exppdf (ones (3), ones (2)) %!error ... %! exppdf (ones (2), ones (3)) %!error exppdf (int32 (2), 2) %!error exppdf (true, 2) %!error exppdf ('a', 2) %!error exppdf (i, 2) %!error exppdf (2, i) statistics-release-1.9.2/inst/Distribution_Functions/exprnd.m000066400000000000000000000135731524624707500245220ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} exprnd (@var{mu}) ## @deftypefnx {statistics} {@var{r} =} exprnd (@var{mu}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} exprnd (@var{mu}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} exprnd (@var{mu}, [@var{sz}]) ## ## Random arrays from the exponential distribution. ## ## @code{@var{r} = exprnd (@var{mu})} returns an array of random numbers chosen ## from the exponential distribution with mean parameter @var{mu}. The size of ## @var{r} is the size of @var{mu}. ## ## When called with a single size argument, @code{exprnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## A common alternative parameterization of the exponential distribution is to ## use the parameter @math{λ} defined as the mean number of events in an ## interval as opposed to the parameter @math{μ}, which is the mean wait time ## for an event to occur. @math{λ} and @math{μ} are reciprocals, ## i.e. @math{μ = 1 / λ}. ## ## Further information about the exponential distribution can be found at ## @url{https://en.wikipedia.org/wiki/Exponential_distribution} ## ## @seealso{expcdf, expinv, exppdf, expfit, explike, expstat} ## @end deftypefn function r = exprnd (mu, varargin) ## Check for valid number of input arguments if (nargin < 1) error ("exprnd: function called with too few input arguments."); endif ## Check for MU being real if (iscomplex (mu)) error ("exprnd: MU must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 1) sz = size (mu); elseif (nargin == 2) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("exprnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 2) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("exprnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (mu) && ! isequal (size (mu), size (ones (sz)))) error ("exprnd: MU must be scalar or of size SZ."); endif ## Check for class type if (isa (mu, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from exponential distribution if (isscalar (mu)) if ((mu > 0) && (mu < Inf)) r = rande (sz, cls) * mu; else r = NaN (sz, cls); endif else r = NaN (sz, cls); k = (mu > 0) & (mu < Inf); r(k) = rande (sum (k(:)), 1, cls) .* mu(k)(:); endif endfunction ## Test output %!assert_equal (size (exprnd (2)), [1, 1]) %!assert_equal (size (exprnd (ones (2,1))), [2, 1]) %!assert_equal (size (exprnd (ones (2,2))), [2, 2]) %!assert_equal (size (exprnd (1, 3)), [3, 3]) %!assert_equal (size (exprnd (1, [4 1])), [4, 1]) %!assert_equal (size (exprnd (1, 4, 1)), [4, 1]) %!assert_equal (size (exprnd (1, 4, 1)), [4, 1]) %!assert_equal (size (exprnd (1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (exprnd (1, 0, 1)), [0, 1]) %!assert_equal (size (exprnd (1, 1, 0)), [1, 0]) %!assert_equal (size (exprnd (1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (exprnd (1, [])), [0, 0]) %!assert_equal (size (exprnd (1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (exprnd (1, -1)), [0, 0]) %!assert_equal (size (exprnd (1, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (exprnd (1, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (exprnd (2)), "double") %!assert_equal (class (exprnd (single (2))), "single") %!assert_equal (class (exprnd (single ([2 2]))), "single") ## Test input validation %!error exprnd () %!error exprnd (i) %!error ... %! exprnd (1, 1.2) %!error ... %! exprnd (1, ones (2)) %!error ... %! exprnd (1, [2 0 2.5]) %!error ... %! exprnd (ones (2), ones (2)) %!error ... %! exprnd (1, 2, 1.5, 5) %!error exprnd (ones (2,2), 3) %!error exprnd (ones (2,2), [3, 2]) %!error exprnd (ones (2,2), 2, 3) statistics-release-1.9.2/inst/Distribution_Functions/fcdf.m000066400000000000000000000164301524624707500241170ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} fcdf (@var{x}, @var{df1}, @var{df2}) ## @deftypefnx {statistics} {@var{p} =} fcdf (@var{x}, @var{df1}, @var{df2}, @qcode{'upper'}) ## ## @math{F}-cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the @math{F}-distribution with @var{df1} and @var{df2} degrees of ## freedom. The size of @var{p} is the common size of @var{x}, @var{df1}, and ## @var{df2}. A scalar input functions as a constant matrix of the same size as ## the other inputs. ## ## @code{@var{p} = fcdf (@var{x}, @var{df1}, @var{df2}, "upper")} computes the ## upper tail probability of the @math{F}-distribution with @var{df1} and ## @var{df2} degrees of freedom, at the values in @var{x}. ## ## Further information about the @math{F}-distribution can be found at ## @url{https://en.wikipedia.org/wiki/F-distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{finv, fpdf, frnd, fstat} ## @end deftypefn function p = fcdf (x, df1, df2, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("fcdf: function called with too few input arguments."); endif ## Check for "upper" flag if (nargin > 3 && strcmpi (uflag, 'upper')) notnan = ! isnan (x); x(notnan) = 1 ./ max (0, x(notnan)); tmp=df1; df1=df2; df2=tmp; elseif (nargin > 3 && ! strcmpi (uflag, 'upper')) error ("fcdf: invalid argument for upper tail."); endif ## Check for common size of X, DF1, and DF2 if (! isscalar (x) || ! isscalar (df1) || ! isscalar (df2)) [err, x, df1, df2] = common_size (x, df1, df2); if (err > 0) error ("fcdf: X, DF1, and DF2 must be of common size or scalars."); endif endif ## Check for X, DF1, and DF2 being double or single if (! (isfloat (x) && isfloat (df1) && isfloat (df2))) error ("fcdf: X, DF1, and DF2 must be double or single."); endif ## Check for X, DF1, and DF2 being reals if (iscomplex (x) || iscomplex (df1) || iscomplex (df2)) error ("fcdf: X, DF1, and DF2 must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (df1, 'single') || isa (df2, 'single')) p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Check X for NaNs while DFs <= 0 and make P = NaNs make_nan = (df1 <= 0 | df2 <= 0 | isnan (x) | isnan (df1) | isnan (df2)); p(make_nan) = NaN; ## Check remaining valid X for Inf values and make P = 1 is_inf = (x == Inf) & ! make_nan; if any (is_inf(:)) p(is_inf) = 1; make_nan = (make_nan | is_inf); endif ## Compute P when X > 0. k = find (x > 0 & ! make_nan & isfinite (df1) & isfinite (df2)); if (any (k)) k1 = (df2(k) <= x(k) .* df1(k)); if (any (k1)) kk = k(k1); xx = df2(kk) ./ (df2(kk) + x(kk) .* df1(kk)); p(kk) = betainc (xx, df2(kk)/2, df1(kk)/2, 'upper'); endif if (any (! k1)) kk = k(! k1); num = df1(kk) .* x(kk); xx = num ./ (num + df2(kk)); p(kk) = betainc (xx, df1(kk)/2, df2(kk)/2, 'lower'); endif endif if any (! isfinite (df1(:)) | ! isfinite (df2(:))) k = find (x > 0 & ! make_nan & isfinite (df1) & ! isfinite (df2) & df2 > 0); if (any (k)) p(k) = gammainc (df1(k) .* x(k) ./ 2, df1(k) ./ 2, 'lower'); endif k = find (x > 0 & ! make_nan & ! isfinite (df1) & df1 > 0 & isfinite (df2)); if (any (k)) p(k) = gammainc (df2(k) ./ x(k) ./ 2, df2(k) ./ 2, 'upper'); endif k = find (x > 0 & ! make_nan & ! isfinite (df1) & df1 > 0 & ... ! isfinite (df2) & df2 > 0); if (any (k)) if (nargin >= 4 && x(k) == 1) p(k) = 0; else p(k) = (x(k)>=1); endif endif endif endfunction %!demo %! ## Plot various CDFs from the F distribution %! x = 0.01:0.01:4; %! p1 = fcdf (x, 1, 2); %! p2 = fcdf (x, 2, 1); %! p3 = fcdf (x, 5, 2); %! p4 = fcdf (x, 10, 1); %! p5 = fcdf (x, 100, 100); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c', x, p5, '-m') %! grid on %! legend ({'df1 = 1, df2 = 2', 'df1 = 2, df2 = 1', ... %! 'df1 = 5, df2 = 2', 'df1 = 10, df2 = 1', ... %! 'df1 = 100, df2 = 100'}, 'location', 'southeast') %! title ('F CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-1, 0, 0.5, 1, 2, Inf]; %! y = [0, 0, 1/3, 1/2, 2/3, 1]; %!assert_equal (fcdf (x, 2*ones (1,6), 2*ones (1,6)), y, eps) %!assert_equal (fcdf (x, 2, 2*ones (1,6)), y, eps) %!assert_equal (fcdf (x, 2*ones (1,6), 2), y, eps) %!assert_equal (fcdf (x, [0 NaN Inf 2 2 2], 2), [NaN NaN 0.1353352832366127 y(4:6)], eps) %!assert_equal (fcdf (x, 2, [0 NaN Inf 2 2 2]), [NaN NaN 0.3934693402873666 y(4:6)], eps) %!assert_equal (fcdf ([x(1:2) NaN x(4:6)], 2, 2), [y(1:2) NaN y(4:6)], eps) ## Test class of input preserved %!assert_equal (fcdf ([x, NaN], 2, 2), [y, NaN], eps) %!assert_equal (fcdf (single ([x, NaN]), 2, 2), single ([y, NaN]), eps ('single')) %!assert_equal (fcdf ([x, NaN], single (2), 2), single ([y, NaN]), eps ('single')) %!assert_equal (fcdf ([x, NaN], 2, single (2)), single ([y, NaN]), eps ('single')) ## Test input validation %!error fcdf () %!error fcdf (1) %!error fcdf (1, 2) %!error fcdf (1, 2, 3, 4) %!error fcdf (1, 2, 3, 'tail') %!error ... %! fcdf (ones (3), ones (2), ones (2)) %!error ... %! fcdf (ones (2), ones (3), ones (2)) %!error ... %! fcdf (ones (2), ones (2), ones (3)) %!error fcdf (int32 (2), 2, 2) %!error fcdf (true, 2, 2) %!error fcdf ('a', 2, 2) %!error fcdf (i, 2, 2) %!error fcdf (2, i, 2) %!error fcdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/finv.m000066400000000000000000000153301524624707500241550ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} finv (@var{p}, @var{df1}, @var{df2}) ## ## Inverse of the @math{F}-cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the @math{F}-distribution with @var{df1} and @var{df2} degrees of freedom. ## The size of @var{x} is the common size of @var{p}, @var{df1}, and @var{df2}. ## A scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## Further information about the @math{F}-distribution can be found at ## @url{https://en.wikipedia.org/wiki/F-distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{fcdf, fpdf, frnd, fstat} ## @end deftypefn function x = finv (p, df1, df2) ## Check for valid number of input arguments if (nargin < 3) error ("finv: function called with too few input arguments."); endif ## Check for common size of P, DF1, and DF2 if (! isscalar (p) || ! isscalar (df1) || ! isscalar (df2)) [retval, p, df1, df2] = common_size (p, df1, df2); if (retval > 0) error ("finv: P, DF1, and DF2 must be of common size or scalars."); endif endif ## Check for P, DF1, and DF2 being double or single if (! (isfloat (p) && isfloat (df1) && isfloat (df2))) error ("finv: P, DF1, and DF2 must be double or single."); endif ## Check for P, DF1, and DF2 being reals if (iscomplex (p) || iscomplex (df1) || iscomplex (df2)) error ("finv: P, DF1, and DF2 must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (df1, 'single') || isa (df2, 'single')) x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ## Handle both DFs being INF kz = df1 == Inf & df2 == Inf; ## Limit DFs to 1.5e6 to avoid numerical issues df1(df1 > 1.5e6) = 1.5e6; df2(df2 > 1.5e6) = 1.5e6; k = p == 1 & df1 > 0 & df2 > 0; x(k) = Inf; ## Limit df2 to 1e6 unless it is Inf #k = (df2 > 1e6) & (df2 < Inf); #df2(k) = 1e6; k = (p >= 0) & (p < 1) & (df1 > 0) & (df1 < Inf) & (df2 > 0) & (df2 < Inf); if (isscalar (df1) && isscalar (df2)) x(k) = ((1 ./ betainv (1 - p(k), df2/2, df1/2) - 1) * df2 / df1); else x(k) = ((1 ./ betainv (1 - p(k), df2(k)/2, df1(k)/2) - 1) .* df2(k) ./ df1(k)); endif ## Handle case when DF2 is infinite k = p >= 0 & p < 1 & df1 > 0 & (df1 < Inf) & (df2 == Inf); x(k) = chi2inv (p(k), df1(k)) ./ df1(k); ## Force instances with df1 = df2 = INF to 0 for p = 0 and to 1 for 0 < p <= 1 x(kz & p > 0 & p <= 1) = 1; x(kz & p == 0) = 0; endfunction %!demo %! ## Plot various iCDFs from the F distribution %! p = 0.001:0.001:0.999; %! x1 = finv (p, 1, 1); %! x2 = finv (p, 2, 1); %! x3 = finv (p, 5, 2); %! x4 = finv (p, 10, 1); %! x5 = finv (p, 100, 100); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c', p, x5, '-m') %! grid on %! ylim ([0, 4]) %! legend ({'df1 = 1, df2 = 2', 'df1 = 2, df2 = 1', ... %! 'df1 = 5, df2 = 2', 'df1 = 10, df2 = 1', ... %! 'df1 = 100, df2 = 100'}, 'location', 'northwest') %! title ('F iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p %! p = [-1 0 0.5 1 2]; %!assert_equal (finv (p, 2*ones (1,5), 2*ones (1,5)), [NaN 0 1 Inf NaN]) %!assert_equal (finv (p, 2, 2*ones (1,5)), [NaN 0 1 Inf NaN]) %!assert_equal (finv (p, 2*ones (1,5), 2), [NaN 0 1 Inf NaN]) %!assert_equal (finv (p, [2 -Inf NaN Inf 2], 2), [NaN NaN NaN Inf NaN]) %!assert_equal (finv (p, 2, [2 -Inf NaN Inf 2]), [NaN NaN NaN Inf NaN]) %!assert_equal (finv ([p(1:2) NaN p(4:5)], 2, 2), [NaN 0 NaN Inf NaN]) ## Test for bug #66034 (savannah) %!assert_equal (finv (0.025, 10, 1e6), 0.3247, 1e-4) %!assert_equal (finv (0.025, 10, 1e7), 0.3247, 1e-4) %!assert_equal (finv (0.025, 10, 1e10), 0.3247, 1e-4) %!assert_equal (finv (0.025, 10, 1e255), 0.3247, 1e-4) %!assert_equal (finv (0.025, 10, Inf), 0.3247, 1e-4) ## Test for issue #203 (Github) %!test %! x = finv (0.35, Inf, 4); %! assert_equal (x, 0.9014, 1e-4) %!test %! x = finv (0, Inf, 4); %! assert_equal (x, 0) %!test %! x = finv (1, Inf, 4); %! assert_equal (x, Inf) %!test %! x = finv (0.35, 4, Inf); %! assert_equal (x, 0.6175, 1e-4) %!test %! x = finv (0, 4, Inf); %! assert_equal (x, 0) %!test %! x = finv (1, 4, Inf); %! assert_equal (x, Inf) %!test %! x = finv ([0, 0.000001, 0.35, 1, 1.2], Inf, Inf); %! assert_equal (x, [0, 1, 1, 1, NaN]); ## Test class of input preserved %!assert_equal (finv ([p, NaN], 2, 2), [NaN 0 1 Inf NaN NaN]) %!assert_equal (finv (single ([p, NaN]), 2, 2), single ([NaN 0 1 Inf NaN NaN])) %!assert_equal (finv ([p, NaN], single (2), 2), single ([NaN 0 1 Inf NaN NaN])) %!assert_equal (finv ([p, NaN], 2, single (2)), single ([NaN 0 1 Inf NaN NaN])) ## Test input validation %!error finv () %!error finv (1) %!error finv (1,2) %!error ... %! finv (ones (3), ones (2), ones (2)) %!error ... %! finv (ones (2), ones (3), ones (2)) %!error ... %! finv (ones (2), ones (2), ones (3)) %!error finv (int32 (2), 2, 2) %!error finv (true, 2, 2) %!error finv ('a', 2, 2) %!error finv (i, 2, 2) %!error finv (2, i, 2) %!error finv (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/fpdf.m000066400000000000000000000174451524624707500241430ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} fpdf (@var{x}, @var{df1}, @var{df2}) ## ## @math{F}-probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the @math{F}-distribution with @var{df1} and @var{df2} degrees of freedom. ## The size of @var{y} is the common size of @var{x}, @var{df1}, and @var{df2}. ## A scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## Further information about the @math{F}-distribution can be found at ## @url{https://en.wikipedia.org/wiki/F-distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{fcdf, finv, frnd, fstat} ## @end deftypefn function y = fpdf (x, df1, df2) ## Check for valid number of input arguments if (nargin < 3) error ("fpdf: function called with too few input arguments."); endif ## Check for common size of X, DF1, and DF2 if (! isscalar (x) ||! isscalar (df1) || ! isscalar (df2)) [retval, x, df1, df2] = common_size (x, df1, df2); if (retval > 0) error ("fpdf: X, DF1, and DF2 must be of common size or scalars."); endif endif ## Check for X, DF1, and DF2 being double or single if (! (isfloat (x) && isfloat (df1) && isfloat (df2))) error ("fpdf: X, DF1, and DF2 must be double or single."); endif ## Check for X, DF1, and DF2 being reals if (iscomplex (x) || iscomplex (df1) || iscomplex (df2)) error ("fpdf: X, DF1, and DF2 must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (df1, 'single') || isa (df2, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Handle both DFs being INF kz = df1 == Inf & df2 == Inf; ## Limit DFs to 5e10 to avoid numerical issues df1(df1 > 5e10) = 5e10; df2(df2 > 5e10) = 5e10; k = isnan (x) | ! (df1 > 0) | ! (df2 > 0); y(k) = NaN; #k = (x > 0) & (x < Inf) & (df1 > 0) & (df1 < Inf) & (df2 > 0) & (df2 < Inf); k = x > 0 & x < Inf & df1 > 0 & df2 > 0; if (isscalar (df1) && isscalar (df2)) tmp = df1 / df2 * x(k); y(k) = (exp ((df1/2 - 1) * log (tmp) ... - ((df1 + df2) / 2) * log (1 + tmp)) ... * (df1 / df2) ./ beta (df1/2, df2/2)); else tmp = df1(k) .* x(k) ./ df2(k); y(k) = (exp ((df1(k)/2 - 1) .* log (tmp) ... - ((df1(k) + df2(k)) / 2) .* log (1 + tmp)) ... .* (df1(k) ./ df2(k)) ./ beta (df1(k)/2, df2(k)/2)); endif ## Handle the origin, where the density has three regimes in DF1: it is ## unbounded for DF1 < 2, unity for DF1 = 2 (whatever DF2 is), and zero ## for DF1 > 2, which is the value Y already holds. The logarithmic form ## above cannot serve here, since it evaluates to 0 * -Inf at DF1 = 2. k = x == 0 & df1 > 0 & df2 > 0; y(k & df1 < 2) = Inf; y(k & df1 == 2) = 1; ## Force instances with df1 = df2 = INF to 0 for valid x y(kz & ! isnan (x)) = 0; endfunction %!demo %! ## Plot various PDFs from the F distribution %! x = 0.01:0.01:4; %! y1 = fpdf (x, 1, 1); %! y2 = fpdf (x, 2, 1); %! y3 = fpdf (x, 5, 2); %! y4 = fpdf (x, 10, 1); %! y5 = fpdf (x, 100, 100); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c', x, y5, '-m') %! grid on %! ylim ([0, 2.5]) %! legend ({'df1 = 1, df2 = 2', 'df1 = 2, df2 = 1', ... %! 'df1 = 5, df2 = 2', 'df1 = 10, df2 = 1', ... %! 'df1 = 100, df2 = 100'}, 'location', 'northeast') %! title ('F PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1, 0, 0.5, 1, 2]; %! y = [0, 1, 4/9, 1/4, 1/9]; %!assert_equal (fpdf (x, 2*ones (1,5), 2*ones (1,5)), y, eps) %!assert_equal (fpdf (x, 2, 2*ones (1,5)), y, eps) %!assert_equal (fpdf (x, 2*ones (1,5), 2), y, eps) %!assert_equal (fpdf (x, [0, NaN, Inf, 2, 2], 2), [NaN, NaN, 0.5413, y(4:5)], 1e-4) %!assert_equal (fpdf (x, 2, [0, NaN, Inf, 2, 2]), [NaN, NaN, 0.6065, y(4:5)], 1e-4) %!assert_equal (fpdf ([x, NaN], 2, 2), [y, NaN], eps) ## Test the density at the origin, unbounded for DF1 < 2 and unity at DF1 = 2 %!assert_equal (fpdf (0, 0.5, 2), Inf) %!assert_equal (fpdf (0, 1, 1), Inf) %!assert_equal (fpdf (0, 1, 7), Inf) %!assert_equal (fpdf (0, 1.9, 3), Inf) %!assert_equal (fpdf (0, 2, 1), 1) %!assert_equal (fpdf (0, 2, 2), 1) %!assert_equal (fpdf (0, 2, 5), 1) %!assert_equal (fpdf (0, 2, 100), 1) %!assert_equal (fpdf (0, 2, Inf), 1) %!assert_equal (fpdf (0, 2.1, 3), 0) %!assert_equal (fpdf (0, 3, 3), 0) %!assert_equal (fpdf (0, 10, 10), 0) %!assert_equal (fpdf (0, Inf, 4), 0) %!assert_equal (fpdf (0, Inf, Inf), 0) %!assert_equal (fpdf (0, [0.5, 1, 2, 3, Inf], 2), [Inf, Inf, 1, 0, 0]) %!assert_equal (fpdf (0, 0, 2), NaN) %!assert_equal (fpdf (0, 2, 0), NaN) %!assert_equal (fpdf (0, NaN, 2), NaN) %!assert_equal (fpdf (0, 2, NaN), NaN) %!assert_equal (fpdf (single (0), 2, 2), single (1)) %!test #F (x, 1, df1) == T distribution (sqrt (x), df1) / sqrt (x) %! rand ('seed', 1234); # for reproducibility %! xr = rand (10,1); %! xr = xr(x > 0.1 & x < 0.9); %! yr = tpdf (sqrt (xr), 2) ./ sqrt (xr); %! assert_equal (fpdf (xr, 1, 2), yr, 5*eps); ## Test for issue #203 (Github) %!test %! yy = fpdf (2, 4, Inf); %! assert_equal (yy, 0.1465, 1e-4) %!test %! yy = fpdf (2, 4, 1000000000000000); %! assert_equal (yy, 0.1465, 1e-4) %!test %! yy = fpdf (2, Inf, 4); %! assert_equal (yy, 0.1839, 1e-4) %!test %! yy = fpdf (2, 10000000000000000, 4); %! assert_equal (yy, 0.1839, 1e-4) %!test %! yy = fpdf (2, Inf, Inf); %! assert_equal (yy, 0) %!test %! yy = fpdf (NaN, Inf, Inf); %! assert_equal (yy, NaN) ## Test class of input preserved %!assert_equal (fpdf (single ([x, NaN]), 2, 2), single ([y, NaN]), eps ('single')) %!assert_equal (fpdf ([x, NaN], single (2), 2), single ([y, NaN]), eps ('single')) %!assert_equal (fpdf ([x, NaN], 2, single (2)), single ([y, NaN]), eps ('single')) ## Test input validation %!error fpdf () %!error fpdf (1) %!error fpdf (1,2) %!error ... %! fpdf (ones (3), ones (2), ones (2)) %!error ... %! fpdf (ones (2), ones (3), ones (2)) %!error ... %! fpdf (ones (2), ones (2), ones (3)) %!error fpdf (int32 (2), 2, 2) %!error fpdf (true, 2, 2) %!error fpdf ('a', 2, 2) %!error fpdf (i, 2, 2) %!error fpdf (2, i, 2) %!error fpdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/frnd.m000066400000000000000000000151041524624707500241430ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} frnd (@var{df1}, @var{df2}) ## @deftypefnx {statistics} {@var{r} =} frnd (@var{df1}, @var{df2}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} frnd (@var{df1}, @var{df2}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} frnd (@var{df1}, @var{df2}, [@var{sz}]) ## ## Random arrays from the @math{F}-distribution. ## ## @code{@var{r} = frnd (@var{df1}, @var{df2})} returns an array of random ## numbers chosen from the @math{F}-distribution with @var{df1} and @var{df2} ## degrees of freedom. The size of @var{r} is the common size of @var{df1} and ## @var{df2}. A scalar input functions as a constant matrix of the same size as ## the other inputs. ## ## When called with a single size argument, @code{frnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the @math{F}-distribution can be found at ## @url{https://en.wikipedia.org/wiki/F-distribution} ## ## @seealso{fcdf, finv, fpdf, fstat} ## @end deftypefn function r = frnd (df1, df2, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("frnd: function called with too few input arguments."); endif ## Check for common size of DF1 and DF2 if (! isscalar (df1) || ! isscalar (df2)) [retval, df1, df2] = common_size (df1, df2); if (retval > 0) error ("frnd: DF1 and DF2 must be of common size or scalars."); endif endif ## Check for DF1 and DF2 being reals if (iscomplex (df1) || iscomplex (df2)) error ("frnd: DF1 and DF2 must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (df1); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("frnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("frnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (df1) && ! isequal (size (df1), size (ones (sz)))) error ("frnd: DF1 and DF2 must be scalars or of size SZ."); endif ## Check for class type if (isa (df1, 'single') || isa (df2, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from F distribution if (isscalar (df1) && isscalar (df2)) if ((df1 > 0) && (df1 < Inf) && (df2 > 0) && (df2 < Inf)) r = df2/df1 * randg (df1/2, sz, cls) ./ randg (df2/2, sz, cls); else r = NaN (sz, cls); endif else r = NaN (sz, cls); k = (df1 > 0) & (df1 < Inf) & (df2 > 0) & (df2 < Inf); r(k) = df2(k) ./ df1(k) .* randg (df1(k)/2, cls) ./ randg (df2(k)/2, cls); endif endfunction ## Test output %!assert_equal (size (frnd (1, 1)), [1 1]) %!assert_equal (size (frnd (1, ones (2,1))), [2, 1]) %!assert_equal (size (frnd (1, ones (2,2))), [2, 2]) %!assert_equal (size (frnd (ones (2,1), 1)), [2, 1]) %!assert_equal (size (frnd (ones (2,2), 1)), [2, 2]) %!assert_equal (size (frnd (1, 1, 3)), [3, 3]) %!assert_equal (size (frnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (frnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (frnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (frnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (frnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (frnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (frnd (1, 1, [])), [0, 0]) %!assert_equal (size (frnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (frnd (1, 2, -1)), [0, 0]) %!assert_equal (size (frnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (frnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (frnd (1, 1)), "double") %!assert_equal (class (frnd (1, single (1))), "single") %!assert_equal (class (frnd (1, single ([1, 1]))), "single") %!assert_equal (class (frnd (single (1), 1)), "single") %!assert_equal (class (frnd (single ([1, 1]), 1)), "single") ## Test input validation %!error frnd () %!error frnd (1) %!error ... %! frnd (ones (3), ones (2)) %!error ... %! frnd (ones (2), ones (3)) %!error frnd (i, 2, 3) %!error frnd (1, i, 3) %!error ... %! frnd (1, 2, 1.2) %!error ... %! frnd (1, 2, ones (2)) %!error ... %! frnd (1, 2, [2 0 2.5]) %!error ... %! frnd (1, 2, 2, 1.5, 5) %!error ... %! frnd (2, ones (2), 3) %!error ... %! frnd (2, ones (2), [3, 2]) %!error ... %! frnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/gamcdf.m000066400000000000000000000322421524624707500244350ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} gamcdf (@var{x}, @var{a}) ## @deftypefnx {statistics} {@var{p} =} gamcdf (@var{x}, @var{a}, @var{b}) ## @deftypefnx {statistics} {@var{p} =} gamcdf (@dots{}, @qcode{'upper'}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} gamcdf (@var{x}, @var{a}, @var{b}, @var{pcov}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} gamcdf (@var{x}, @var{a}, @var{b}, @var{pcov}, @var{alpha}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} gamcdf (@dots{}, @qcode{'upper'}) ## ## Gamma cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the Gamma distribution with shape parameter @var{a} and scale ## parameter @var{b}. When called with only one parameter, then @var{b} ## defaults to 1. The size of @var{p} is the common size of @var{x}, @var{a}, ## and @var{b}. A scalar input functions as a constant matrix of the same ## size as the other inputs. ## ## When called with three output arguments, i.e. @qcode{[@var{p}, @var{plo}, ## @var{pup}]}, @code{gamcdf} computes the confidence bounds for @var{p} when ## the input parameters @var{a} and @var{b} are estimates. In such case, ## @var{pcov}, a @math{2*2} matrix containing the covariance matrix of the ## estimated parameters, is necessary. Optionally, @var{alpha}, which has a ## default value of 0.05, specifies the @qcode{100 * (1 - @var{alpha})} percent ## confidence bounds. @var{plo} and @var{pup} are arrays of the same size as ## @var{p} containing the lower and upper confidence bounds. ## ## @code{[@dots{}] = gamcdf (@dots{}, "upper")} computes the upper tail ## probability of the Gamma distribution with parameters @var{a} and ## @var{b}, at the values in @var{x}. ## ## OCTAVE/MATLAB use the alternative parameterization given by the pair ## @math{α, β}, i.e. shape @var{a} and scale @var{b}. In Wikipedia, the two ## common parameterizations use the pairs @math{k, θ}, as shape and scale, and ## @math{α, β}, as shape and rate, respectively. The parameter names @var{a} ## and @var{b} used here (for MATLAB compatibility) correspond to the parameter ## notation @math{k, θ} instead of the @math{α, β} as reported in Wikipedia. ## ## Further information about the Gamma distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gamma_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{gaminv, gampdf, gamrnd, gamfit, gamlike, gamstat} ## @end deftypefn function [varargout] = gamcdf (x, varargin) ## Check for valid number of input arguments if (nargin < 2 || nargin > 6) error ("gamcdf: invalid number of input arguments."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (varargin{end}, 'upper')) uflag = true; varargin(end) = []; elseif (nargin > 2 && ischar (varargin{end}) && ... ! strcmpi (varargin{end}, 'upper')) error ("gamcdf: invalid argument for upper tail."); elseif (nargin > 2 && isempty (varargin{end})) uflag = false; varargin(end) = []; else uflag = false; endif ## Get extra arguments (if they exist) or add defaults a = varargin{1}; if (numel (varargin) > 1) b = varargin{2}; else b = 1; endif if (numel (varargin) > 2) pcov = varargin{3}; ## Check for valid covariance matrix 2x2 if (! isequal (size (pcov), [2, 2])) error ("gamcdf: invalid size of covariance matrix."); endif else ## Check that cov matrix is provided if 3 output arguments are requested if (nargout > 1) error ("gamcdf: covariance matrix is required for confidence bounds."); endif pcov = []; endif if (numel (varargin) > 3) alpha = varargin{4}; ## Check for valid alpha value if (! isnumeric (alpha) || numel (alpha) !=1 || alpha <= 0 || alpha >= 1) error ("gamcdf: invalid value for alpha."); endif else alpha = 0.05; endif ## Check for common size of X, A, and B if (! isscalar (x) || ! isscalar (a) || ! isscalar (b)) [err, x, a, b] = common_size (x, a, b); if (err > 0) error ("gamcdf: X, A, and B must be of common size or scalars."); endif endif ## Check for X, A, and B being double or single if (! (isfloat (x) && isfloat (a) && isfloat (b))) error ("gamcdf: X, A, and B must be double or single."); endif ## Check for X, A, and B being reals if (iscomplex (x) || iscomplex (a) || iscomplex (b)) error ("gamcdf: X, A, and B must not be complex."); endif ## Prepare parameters so that gammainc returns NaN for out of range parameters a(a < 0) = NaN; b(b < 0) = NaN; ## Prepare data so that gammainc returns 0 for negative X x(x < 0) = 0; ## Compute gammainc z = x ./ b; if (uflag) p = gammainc (z, a, 'upper'); ## Fix NaNs to gammainc output when a == NaN p(isnan (a)) = NaN; else p = gammainc (z, a); ## Fix NaNs to gammainc output when a == NaN p(isnan (a)) = NaN; endif ## Check for appropriate class if (isa (x, 'single') || isa (a, 'single') || isa (b, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Prepare output varargout{1} = cast (p, is_class); if (nargout > 1) plo = NaN (size (z), is_class); pup = NaN (size (z), is_class); endif ## Compute confidence bounds (if requested) if (nargout >= 2) ## Approximate the variance of p on the logit scale logitp = log (p ./ (1 - p)); dp = 1 ./ (p .* (1 - p)); dk = dgammainc (z, a) .* dp; dt = -exp (a .* log (z) - z - gammaln (a) - log (b)) .* dp; varLogitp = pcov(1,1) .* dk .^ 2 + 2 .* pcov(1,2) .* dk .* dt + ... pcov(2,2) .* dt .^ 2; if (any (varLogitp(:) < 0)) error ("gamcdf: bad covariance matrix."); endif ## Use a normal approximation on the logit scale, then transform back to ## the original CDF scale halfwidth = -norminv (alpha / 2) * sqrt (varLogitp); explogitplo = exp (logitp - halfwidth); explogitpup = exp (logitp + halfwidth); plo = explogitplo ./ (1 + explogitplo); pup = explogitpup ./ (1 + explogitpup); varargout{2} = plo; varargout{3} = pup; endif endfunction ## Compute 1st derivative of the incomplete Gamma function function dy = dgammainc (x, a) ## Initialize return variables dy = nan (size (x)); ## Use approximation for A > 2^20 ulim = 2^20; is_lim = find (a > ulim); if (! isempty (is_lim)) x(is_lim) = max (ulim - 1/3 + sqrt (ulim ./ a(is_lim)) .* ... (x(is_lim) - (a(is_lim) - 1/3)), 0); a(is_lim) = ulim; endif ## For x < a+1 is_lo = find (x < a + 1 & x != 0); if (! isempty (is_lo)) x_lo = x(is_lo); k_lo = a(is_lo); k_1 = k_lo; step = 1; d1st = 0; stsum = step; d1sum = d1st; while norm (step, 'inf') >= 100 * eps (norm (stsum, 'inf')) k_1 += 1; step = step .* x_lo ./ k_1; d1st = (d1st .* x_lo - step) ./ k_1; stsum = stsum + step; d1sum = d1sum + d1st; endwhile fklo = exp (-x_lo + k_lo .* log (x_lo) - gammaln (k_lo + 1)); ## Compute 1st derivative dlogfklo = (log (x_lo) - psi (k_lo + 1)); d1fklo = fklo .* dlogfklo; d1y_lo = d1fklo .* stsum + fklo .* d1sum; dy(is_lo) = d1y_lo; endif ## For x >= a+1 is_hi = find (x >= a+1); if (! isempty (is_hi)) x_hi = x(is_hi); k_hi = a(is_hi); zc = 0; k0 = 0; k1 = k_hi; x0 = 1; x1 = x_hi; d1k0 = 0; d1k1 = 1; d1x0 = 0; d1x1 = 0; kx = k_hi ./ x_hi; d1kx = 1 ./ x_hi; d2kx = 0; start = 1; while norm (d2kx - start, 'Inf') > 100 * eps (norm (d2kx, 'Inf')) rescale = 1 ./ x1; zc += 1; n_k = zc - k_hi; d1k0 = (d1k1 + d1k0 .* n_k - k0) .* rescale; d1x0 = (d1x1 + d1x0 .* n_k - x0) .* rescale; k0 = (k1 + k0 .* n_k) .* rescale; x0 = 1 + (x0 .* n_k) .* rescale; nrescale = zc .* rescale; d1k1 = d1k0 .* x_hi + d1k1 .* nrescale; d1x1 = d1x0 .* x_hi + d1x1 .* nrescale; k1 = k0 .* x_hi + k1 .* nrescale; x1 = x0 .* x_hi + zc; start = d2kx; kx = k1 ./ x1; d1kx = (d1k1 - kx .* d1x1) ./ x1; endwhile fkhi = exp (-x_hi + k_hi .* log (x_hi) - gammaln (k_hi+1)); ## Compute 1st derivative dlogfkhi = (log (x_hi) - psi (k_hi + 1)); d1fkhi = fkhi .* dlogfkhi; d1y_hi = d1fkhi .* kx + fkhi .* d1kx; dy(is_hi) = -d1y_hi; endif ## Handle x == 0 is_x0 = find (x == 0); if (! isempty (is_x0)) dy(is_x0) = 0; endif ## Handle a == 0 is_k0 = find (a == 0); if (! isempty (is_k0)) is_k0x0 = find (a == 0 & x == 0); dy(is_k0x0) = -Inf; endif endfunction %!demo %! ## Plot various CDFs from the Gamma distribution %! x = 0:0.01:20; %! p1 = gamcdf (x, 1, 2); %! p2 = gamcdf (x, 2, 2); %! p3 = gamcdf (x, 3, 2); %! p4 = gamcdf (x, 5, 1); %! p5 = gamcdf (x, 9, 0.5); %! p6 = gamcdf (x, 7.5, 1); %! p7 = gamcdf (x, 0.5, 1); %! plot (x, p1, '-r', x, p2, '-g', x, p3, '-y', x, p4, '-m', ... %! x, p5, '-k', x, p6, '-b', x, p7, '-c') %! grid on %! legend ({'α = 1, β = 2', 'α = 2, β = 2', 'α = 3, β = 2', ... %! 'α = 5, β = 1', 'α = 9, β = 0.5', 'α = 7.5, β = 1', ... %! 'α = 0.5, β = 1'}, 'location', 'southeast') %! title ('Gamma CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y, u %! x = [-1, 0, 0.5, 1, 2, Inf]; %! y = [0, gammainc(x(2:end), 1)]; %! u = [0, NaN, NaN, 1, 0.1353352832366127, 0]; %!assert_equal (gamcdf (x, ones (1,6), ones (1,6)), y, eps) %!assert_equal (gamcdf (x, ones (1,6), ones (1,6), []), y, eps) %!assert_equal (gamcdf (x, 1, ones (1,6)), y, eps) %!assert_equal (gamcdf (x, ones (1,6), 1), y, eps) %!assert_equal (gamcdf (x, [0, -Inf, NaN, Inf, 1, 1], 1), [1, NaN, NaN, 0, y(5:6)], eps) %!assert_equal (gamcdf (x, [0, -Inf, NaN, Inf, 1, 1], 1, 'upper'), u, eps) %!assert_equal (gamcdf (x, 1, [0, -Inf, NaN, Inf, 1, 1]), [NaN, NaN, NaN, 0, y(5:6)], eps) %!assert_equal (gamcdf ([x(1:2), NaN, x(4:6)], 1, 1), [y(1:2), NaN, y(4:6)], eps) ## Test class of input preserved %!assert_equal (gamcdf ([x, NaN], 1, 1), [y, NaN]) %!assert_equal (gamcdf (single ([x, NaN]), 1, 1), single ([y, NaN]), eps ('single')) %!assert_equal (gamcdf ([x, NaN], single (1), 1), single ([y, NaN]), eps ('single')) %!assert_equal (gamcdf ([x, NaN], 1, single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error gamcdf () %!error gamcdf (1) %!error gamcdf (1, 2, 3, 4, 5, 6, 7) %!error gamcdf (1, 2, 3, 'uper') %!error gamcdf (1, 2, 3, 4, 5, 'uper') %!error gamcdf (2, 3, 4, [1, 2]) %!error ... %! [p, plo, pup] = gamcdf (1, 2, 3) %!error ... %! [p, plo, pup] = gamcdf (1, 2, 3, 'upper') %!error [p, plo, pup] = ... %! gamcdf (1, 2, 3, [1, 0; 0, 1], 0) %!error [p, plo, pup] = ... %! gamcdf (1, 2, 3, [1, 0; 0, 1], 1.22) %!error [p, plo, pup] = ... %! gamcdf (1, 2, 3, [1, 0; 0, 1], 'alpha', 'upper') %!error ... %! gamcdf (ones (3), ones (2), ones (2)) %!error ... %! gamcdf (ones (2), ones (3), ones (2)) %!error ... %! gamcdf (ones (2), ones (2), ones (3)) %!error gamcdf (int32 (2), 2, 2) %!error gamcdf (true, 2, 2) %!error gamcdf ('a', 2, 2) %!error gamcdf (i, 2, 2) %!error gamcdf (2, i, 2) %!error gamcdf (2, 2, i) %!error ... %! [p, plo, pup] = gamcdf (1, 2, 3, [1, 0; 0, -inf], 0.04) statistics-release-1.9.2/inst/Distribution_Functions/gaminv.m000066400000000000000000000163041524624707500244760ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} gaminv (@var{p}, @var{a}, @var{b}) ## ## Inverse of the Gamma cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the Gamma distribution with shape parameter @var{a} and scale parameter ## @var{b}. The size of @var{x} is the common size of @var{p}, @var{a}, ## and @var{b}. A scalar input functions as a constant matrix of the same ## size as the other inputs. ## ## OCTAVE/MATLAB use the alternative parameterization given by the pair ## @math{α, β}, i.e. shape @var{a} and scale @var{b}. In Wikipedia, the two ## common parameterizations use the pairs @math{k, θ}, as shape and scale, and ## @math{α, β}, as shape and rate, respectively. The parameter names @var{a} ## and @var{b} used here (for MATLAB compatibility) correspond to the parameter ## notation @math{k, θ} instead of the @math{α, β} as reported in Wikipedia. ## ## Further information about the Gamma distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gamma_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{gamcdf, gampdf, gamrnd, gamfit, gamlike, gamstat} ## @end deftypefn function x = gaminv (p, a, b) ## Check for valid number of input arguments if (nargin < 3) error ("gaminv: function called with too few input arguments."); endif ## Check for common size of P, Α, and Β if (! isscalar (p) || ! isscalar (a) || ! isscalar (b)) [retval, p, a, b] = common_size (p, a, b); if (retval > 0) error ("gaminv: P, Α, and Β must be of common size or scalars."); endif endif ## Check for P, Α, and Β being double or single if (! (isfloat (p) && isfloat (a) && isfloat (b))) error ("gaminv: P, Α, and Β must be double or single."); endif ## Check for P, Α, and Β being reals if (iscomplex (p) || iscomplex (a) || iscomplex (b)) error ("gaminv: P, Α, and Β must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (a, 'single') || isa (b, 'single')) x = zeros (size (p), 'single'); else x = zeros (size (p)); endif ## Force NaNs for out of range parameters is_nan = ((p < 0) | (p > 1) | isnan (p) ... | ! (a > 0) | ! (a < Inf) | ! (b > 0) | ! (b < Inf)); x(is_nan) = NaN; ## Handle edge cases is_inf = (p == 1) & (a > 0) & (a < Inf) & (b > 0) & (b < Inf); x(is_inf) = Inf; ## Handle all other valid cases is_valid = find ((p > 0) & (p < 1) & (a > 0) & ... (a < Inf) & (b > 0) & (b < Inf)); if (! isempty (is_valid)) if (! isscalar (a) || ! isscalar (b)) a = a(is_valid); b = b(is_valid); y = a .* b; else y = a * b * ones (size (is_valid)); endif p = p(is_valid); ## Call GAMMAINCINV to find a root of GAMMAINC q = gammaincinv (p, a); tol = sqrt (eps (ones (1, 1, class (q)))); check_cdf = ((abs (gammainc (q, a) - p) ./ p) > tol); ## Check for any cdf being far off from tolerance if (any (check_cdf(:))) warning ("gaminv: calculation failed to converge for some values."); endif x(is_valid) = q .* b; endif endfunction %!demo %! ## Plot various iCDFs from the Gamma distribution %! p = 0.001:0.001:0.999; %! x1 = gaminv (p, 1, 2); %! x2 = gaminv (p, 2, 2); %! x3 = gaminv (p, 3, 2); %! x4 = gaminv (p, 5, 1); %! x5 = gaminv (p, 9, 0.5); %! x6 = gaminv (p, 7.5, 1); %! x7 = gaminv (p, 0.5, 1); %! plot (p, x1, '-r', p, x2, '-g', p, x3, '-y', p, x4, '-m', ... %! p, x5, '-k', p, x6, '-b', p, x7, '-c') %! ylim ([0, 20]) %! grid on %! legend ({'α = 1, β = 2', 'α = 2, β = 2', 'α = 3, β = 2', ... %! 'α = 5, β = 1', 'α = 9, β = 0.5', 'α = 7.5, β = 1', ... %! 'α = 0.5, β = 1'}, 'location', 'northwest') %! title ('Gamma iCDF') %! xlabel ('probability') %! ylabel ('x') ## Test output %!shared p %! p = [-1 0 0.63212055882855778 1 2]; %!assert_equal (gaminv (p, ones (1,5), ones (1,5)), [NaN 0 1 Inf NaN], eps) %!assert_equal (gaminv (p, 1, ones (1,5)), [NaN 0 1 Inf NaN], eps) %!assert_equal (gaminv (p, ones (1,5), 1), [NaN 0 1 Inf NaN], eps) %!assert_equal (gaminv (p, [1 -Inf NaN Inf 1], 1), [NaN NaN NaN NaN NaN]) %!assert_equal (gaminv (p, 1, [1 -Inf NaN Inf 1]), [NaN NaN NaN NaN NaN]) %!assert_equal (gaminv ([p(1:2) NaN p(4:5)], 1, 1), [NaN 0 NaN Inf NaN]) %!assert_equal (gaminv ([p(1:2) NaN p(4:5)], 1, 1), [NaN 0 NaN Inf NaN]) ## Test for accuracy when p is small. Results compared to Matlab %!assert_equal (gaminv (1e-16, 1, 1), 1e-16, eps) %!assert_equal (gaminv (1e-16, 1, 2), 2e-16, eps) %!assert_equal (gaminv (1e-20, 3, 5), 1.957434012161815e-06, eps) %!assert_equal (gaminv (1e-15, 1, 1), 1e-15, eps) %!assert_equal (gaminv (1e-35, 1, 1), 1e-35, eps) ## Test class of input preserved %!assert_equal (gaminv ([p, NaN], 1, 1), [NaN 0 1 Inf NaN NaN], eps) %!assert_equal (gaminv (single ([p, NaN]), 1, 1), single ([NaN 0 1 Inf NaN NaN]), ... %! eps ('single')) %!assert_equal (gaminv ([p, NaN], single (1), 1), single ([NaN 0 1 Inf NaN NaN]), ... %! eps ('single')) %!assert_equal (gaminv ([p, NaN], 1, single (1)), single ([NaN 0 1 Inf NaN NaN]), ... %! eps ('single')) ## Test input validation %!error gaminv () %!error gaminv (1) %!error gaminv (1,2) %!error ... %! gaminv (ones (3), ones (2), ones (2)) %!error ... %! gaminv (ones (2), ones (3), ones (2)) %!error ... %! gaminv (ones (2), ones (2), ones (3)) %!error gaminv (int32 (2), 2, 2) %!error gaminv (true, 2, 2) %!error gaminv ('a', 2, 2) %!error gaminv (i, 2, 2) %!error gaminv (2, i, 2) %!error gaminv (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/gampdf.m000066400000000000000000000152121524624707500244500ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} gampdf (@var{x}, @var{a}, @var{b}) ## ## Gamma probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the Gamma distribution with shape parameter @var{a} and scale parameter ## @var{b}. The size of @var{y} is the common size of @var{x}, @var{a} and ## @var{b}. A scalar input functions as a constant matrix of the same size ## as the other inputs. ## ## OCTAVE/MATLAB use the alternative parameterization given by the pair ## @math{α, β}, i.e. shape @var{a} and scale @var{b}. In Wikipedia, the two ## common parameterizations use the pairs @math{k, θ}, as shape and scale, and ## @math{α, β}, as shape and rate, respectively. The parameter names @var{a} ## and @var{b} used here (for MATLAB compatibility) correspond to the parameter ## notation @math{k, θ} instead of the @math{α, β} as reported in Wikipedia. ## ## Further information about the Gamma distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gamma_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{gamcdf, gaminv, gamrnd, gamfit, gamlike, gamstat} ## @end deftypefn function y = gampdf (x, a, b) ## Check for valid number of input arguments if (nargin < 3) error ("gampdf: function called with too few input arguments."); endif ## Check for common size of X, A, and B if (! isscalar (a) || ! isscalar (b)) [retval, x, a, b] = common_size (x, a, b); if (retval > 0) error ("gampdf: X, A, and B must be of common size or scalars."); endif endif ## Check for X, A, and B being double or single if (! (isfloat (x) && isfloat (a) && isfloat (b))) error ("gampdf: X, A, and B must be double or single."); endif ## Check for X, A, and B being reals if (iscomplex (x) || iscomplex (a) || iscomplex (b)) error ("gampdf: X, A, and B must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (a, 'single') || isa (b, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Force NaNs for out of range parameters is_nan = ! (a > 0) | ! (b > 0) | isnan (x); y(is_nan) = NaN; ## Handle all other valid cases v = x >= 0 & a > 0 & a <= 1 & b > 0 & b < Inf; if (isscalar (a) && isscalar (b)) y(v) = (x(v) .^ (a - 1)) ... .* exp (- x(v) / b) / gamma (a) / (b ^ a); else y(v) = (x(v) .^ (a(v) - 1)) ... .* exp (- x(v) ./ b(v)) ./ gamma (a(v)) ./ (b(v) .^ a(v)); endif v = x >= 0 & a > 1 & a < Inf & b > 0 & b < Inf; if (isscalar (a) && isscalar (b)) y(v) = exp (- a * log (b) + (a-1) * log (x(v)) - x(v) / b - gammaln (a)); else y(v) = exp (- a(v) .* log (b(v)) + (a(v)-1) .* log (x(v)) - x(v) ./ b(v) - gammaln (a(v))); endif ## The density at an infinite abscissa is zero: no proper distribution ## places mass there. The expressions above reach it as Inf - Inf. y(isinf (x) & a > 0 & a < Inf & b > 0 & b < Inf) = 0; endfunction %!demo %! ## Plot various PDFs from the Gamma distribution %! x = 0:0.01:20; %! y1 = gampdf (x, 1, 2); %! y2 = gampdf (x, 2, 2); %! y3 = gampdf (x, 3, 2); %! y4 = gampdf (x, 5, 1); %! y5 = gampdf (x, 9, 0.5); %! y6 = gampdf (x, 7.5, 1); %! y7 = gampdf (x, 0.5, 1); %! plot (x, y1, '-r', x, y2, '-g', x, y3, '-y', x, y4, '-m', ... %! x, y5, '-k', x, y6, '-b', x, y7, '-c') %! grid on %! ylim ([0,0.5]) %! legend ({'α = 1, β = 2', 'α = 2, β = 2', 'α = 3, β = 2', ... %! 'α = 5, β = 1', 'α = 9, β = 0.5', 'α = 7.5, β = 1', ... %! 'α = 0.5, β = 1'}, 'location', 'northeast') %! title ('Gamma PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1 0 0.5 1 Inf]; %! y = [0 exp(-x(2:end))]; %!assert_equal (gampdf (x, ones (1,5), ones (1,5)), y) %!assert_equal (gampdf (x, 1, ones (1,5)), y) %!assert_equal (gampdf (x, ones (1,5), 1), y) %!assert_equal (gampdf (x, [0 -Inf NaN Inf 1], 1), [NaN NaN NaN 0 y(5)]) %!assert_equal (gampdf (x, [0 Inf NaN Inf 1], 1), [NaN 0 NaN 0 y(5)]) %!assert_equal (gampdf (x, 1, [0 -Inf NaN Inf 1]), [NaN NaN NaN 0 y(5)]) %!assert_equal (gampdf ([x, NaN], 1, 1), [y, NaN]) ## Test for issue #203 (Github) %!assert_equal (gampdf (2, Inf, 4), 0) %!assert_equal (gampdf (2, 4, Inf), 0) %!assert_equal (gampdf (2, Inf, Inf), 0) ## Test class of input preserved %!assert_equal (gampdf (single ([x, NaN]), 1, 1), single ([y, NaN])) %!assert_equal (gampdf ([x, NaN], single (1), 1), single ([y, NaN])) %!assert_equal (gampdf ([x, NaN], 1, single (1)), single ([y, NaN])) ## Test input validation %!error gampdf () %!error gampdf (1) %!error gampdf (1,2) %!error ... %! gampdf (ones (3), ones (2), ones (2)) %!error ... %! gampdf (ones (2), ones (3), ones (2)) %!error ... %! gampdf (ones (2), ones (2), ones (3)) %!error gampdf (int32 (2), 2, 2) %!error gampdf (true, 2, 2) %!error gampdf ('a', 2, 2) %!error gampdf (i, 2, 2) %!error gampdf (2, i, 2) %!error gampdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/gamrnd.m000066400000000000000000000160071524624707500244650ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} gamrnd (@var{a}, @var{b}) ## @deftypefnx {statistics} {@var{r} =} gamrnd (@var{a}, @var{b}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} gamrnd (@var{a}, @var{b}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} gamrnd (@var{a}, @var{b}, [@var{sz}]) ## ## Random arrays from the Gamma distribution. ## ## @code{@var{r} = gamrnd (@var{a}, @var{b})} returns an array of random ## numbers chosen from the Gamma distribution with shape parameter @var{a} and ## scale parameter @var{b}. The size of @var{r} is the common size of ## @var{a} and @var{b}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## When called with a single size argument, @code{gamrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## OCTAVE/MATLAB use the alternative parameterization given by the pair ## @math{α, β}, i.e. shape @var{a} and scale @var{b}. In Wikipedia, the two ## common parameterizations use the pairs @math{k, θ}, as shape and scale, and ## @math{α, β}, as shape and rate, respectively. The parameter names @var{a} ## and @var{b} used here (for MATLAB compatibility) correspond to the parameter ## notation @math{k, θ} instead of the @math{α, β} as reported in Wikipedia. ## ## Further information about the Gamma distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gamma_distribution} ## ## @seealso{gamcdf, gaminv, gampdf, gamfit, gamlike, gamstat} ## @end deftypefn function r = gamrnd (a, b, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("gamrnd: function called with too few input arguments."); endif ## Check for common size of A and B if (! isscalar (a) || ! isscalar (b)) [retval, a, b] = common_size (a, b); if (retval > 0) error ("gamrnd: A and B must be of common size or scalars."); endif endif ## Check for A and B being reals if (iscomplex (a) || iscomplex (b)) error ("gamrnd: A and B must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (a); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("gamrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("gamrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (a) && ! isequal (size (a), size (ones (sz)))) error ("gamrnd: A and B must be scalars or of size SZ."); endif ## Check for class type if (isa (a, 'single') || isa (b, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from Gamma distribution if (isscalar (a) && isscalar (b)) if ((a > 0) && (a < Inf) && (b > 0) && (b < Inf)) r = b * randg (a, sz, cls); else r = NaN (sz, cls); endif else r = NaN (sz, cls); valid = (a > 0) & (a < Inf) & (b > 0) & (b < Inf); r(valid) = b(valid) .* randg (a(valid), cls); endif endfunction ## Test output %!assert_equal (size (gamrnd (1, 1)), [1 1]) %!assert_equal (size (gamrnd (1, ones (2,1))), [2, 1]) %!assert_equal (size (gamrnd (1, ones (2,2))), [2, 2]) %!assert_equal (size (gamrnd (ones (2,1), 1)), [2, 1]) %!assert_equal (size (gamrnd (ones (2,2), 1)), [2, 2]) %!assert_equal (size (gamrnd (1, 1, 3)), [3, 3]) %!assert_equal (size (gamrnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (gamrnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (gamrnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (gamrnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (gamrnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (gamrnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (gamrnd (1, 1, [])), [0, 0]) %!assert_equal (size (gamrnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (gamrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (gamrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (gamrnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (gamrnd (1, 1)), "double") %!assert_equal (class (gamrnd (1, single (1))), "single") %!assert_equal (class (gamrnd (1, single ([1, 1]))), "single") %!assert_equal (class (gamrnd (single (1), 1)), "single") %!assert_equal (class (gamrnd (single ([1, 1]), 1)), "single") ## Test input validation %!error gamrnd () %!error gamrnd (1) %!error ... %! gamrnd (ones (3), ones (2)) %!error ... %! gamrnd (ones (2), ones (3)) %!error gamrnd (i, 2, 3) %!error gamrnd (1, i, 3) %!error ... %! gamrnd (1, 2, 1.2) %!error ... %! gamrnd (1, 2, ones (2)) %!error ... %! gamrnd (1, 2, [2 0 2.5]) %!error ... %! gamrnd (1, 2, 2, 1.5, 5) %!error ... %! gamrnd (2, ones (2), 3) %!error ... %! gamrnd (2, ones (2), [3, 2]) %!error ... %! gamrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/geocdf.m000066400000000000000000000146011524624707500244420ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} geocdf (@var{x}, @var{ps}) ## @deftypefnx {statistics} {@var{p} =} geocdf (@var{x}, @var{ps}, @qcode{'upper'}) ## ## Geometric cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the geometric distribution with probability of success parameter ## @var{ps}. The size of @var{p} is the common size of @var{x} and @var{ps}. ## A scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## @code{@var{p} = geocdf (@var{x}, @var{ps}, "upper")} computes the upper tail ## probability of the geometric distribution with parameter @var{ps}, at the ## values in @var{x}. ## ## The geometric distribution models the number of failures (@var{x}) of a ## Bernoulli trial with probability @var{ps} before the first success. ## ## Further information about the geometric distribution can be found at ## @url{https://en.wikipedia.org/wiki/Geometric_distribution} ## ## Input arguments must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted to ## @qcode{double}, so the result is always a probability. MATLAB is ## inconsistent here: for several of the discrete distributions it returns the ## result in the integer class of the input, truncating a probability to ## @math{0} or @math{1}. ## ## @seealso{geoinv, geopdf, geornd, geofit, geostat} ## @end deftypefn function p = geocdf (x, ps, uflag) ## Check for valid number of input arguments if (nargin < 2) error ("geocdf: function called with too few input arguments."); endif ## Check for common size of X and PS if (! isscalar (x) || ! isscalar (ps)) [retval, x, ps] = common_size (x, ps); if (retval > 0) error ("geocdf: X and PS must be of common size or scalars."); endif endif ## Check for X and PS being double, single, or integer if (! (isnumeric (x) && isnumeric (ps))) error ("geocdf: X and PS must be double, single, or integer."); endif ## Integer input is promoted to double, so the result is a probability ## rather than a value truncated to the input's integer type. if (isinteger (x)) x = double (x); endif if (isinteger (ps)) ps = double (ps); endif ## Check for X and PS being reals if (iscomplex (x) || iscomplex (ps)) error ("geocdf: X and PS must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (ps, 'single')) p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Return NaN for out of range parameters k = isnan (x) | ! (ps >= 0) | ! (ps <= 1); p(k) = NaN; ## Return 1 for valid range parameters when X = Inf k = (x == Inf) & (ps >= 0) & (ps <= 1); p(k) = 1; ## Return 0 for X < 0 x(x < 0) = -1; ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) uflag = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("geocdf: invalid argument for upper tail."); else uflag = false; endif ## Get valid instances k = (x >= 0) & (x < Inf) & (x == fix (x)) & (ps > 0) & (ps <= 1); ## Compute CDF if (uflag) if (any (k)) p(k) = betainc (ps(k), 1, (x(k)) + 1, 'upper'); endif else if (isscalar (ps)) p(k) = 1 - ((1 - ps) .^ (x(k) + 1)); else p(k) = 1 - ((1 - ps(k)) .^ (x(k) + 1)); endif endif endfunction %!demo %! ## Plot various CDFs from the geometric distribution %! x = 0:10; %! p1 = geocdf (x, 0.2); %! p2 = geocdf (x, 0.5); %! p3 = geocdf (x, 0.7); %! plot (x, p1, '*b', x, p2, '*g', x, p3, '*r') %! grid on %! xlim ([0, 10]) %! legend ({'ps = 0.2', 'ps = 0.5', 'ps = 0.7'}, 'location', 'southeast') %! title ('Geometric CDF') %! xlabel ('values in x (number of failures)') %! ylabel ('probability') ## Test output %!test %! p = geocdf ([1, 2, 3, 4], 0.25); %! assert_equal (p(1), 0.4375000000, 1e-14); %! assert_equal (p(2), 0.5781250000, 1e-14); %! assert_equal (p(3), 0.6835937500, 1e-14); %! assert_equal (p(4), 0.7626953125, 1e-14); %!test %! p = geocdf ([1, 2, 3, 4], 0.25, 'upper'); %! assert_equal (p(1), 0.5625000000, 1e-14); %! assert_equal (p(2), 0.4218750000, 1e-14); %! assert_equal (p(3), 0.3164062500, 1e-14); %! assert_equal (p(4), 0.2373046875, 1e-14); %!shared x, p %! x = [-1 0 1 Inf]; %! p = [0 0.5 0.75 1]; %!assert_equal (geocdf (x, 0.5*ones (1,4)), p) %!assert_equal (geocdf (x, 0.5), p) %!assert_equal (geocdf (x, 0.5*[-1 NaN 4 1]), [NaN NaN NaN p(4)]) %!assert_equal (geocdf ([x(1:2) NaN x(4)], 0.5), [p(1:2) NaN p(4)]) ## Test class of input preserved %!assert_equal (geocdf ([x, NaN], 0.5), [p, NaN]) %!assert_equal (geocdf (single ([x, NaN]), 0.5), single ([p, NaN])) %!assert_equal (geocdf ([x, NaN], single (0.5)), single ([p, NaN])) ## Test input validation %!error geocdf () %!error geocdf (1) %!error ... %! geocdf (ones (3), ones (2)) %!error ... %! geocdf (ones (2), ones (3)) %!error geocdf (true, 2) %!error geocdf ('a', 2) %!assert_equal (class (geocdf (int32 (2), 2)), 'double') %!error geocdf (i, 2) %!error geocdf (2, i) %!error geocdf (2, 3, 'tail') %!error geocdf (2, 3, 5) statistics-release-1.9.2/inst/Distribution_Functions/geoinv.m000066400000000000000000000114531524624707500245040ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} geoinv (@var{p}, @var{ps}) ## ## Inverse of the geometric cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the geometric distribution with probability of success parameter @var{ps}. ## The size of @var{x} is the common size of @var{p} and @var{ps}. A scalar ## input functions as a constant matrix of the same size as the other inputs. ## ## The geometric distribution models the number of failures (@var{p}) of a ## Bernoulli trial with probability @var{ps} before the first success. ## ## Further information about the geometric distribution can be found at ## @url{https://en.wikipedia.org/wiki/Geometric_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{geocdf, geopdf, geornd, geofit, geostat} ## @end deftypefn function x = geoinv (p, ps) ## Check for valid number of input arguments if (nargin < 2) error ("geoinv: function called with too few input arguments."); endif ## Check for common size of P and PS if (! isscalar (ps) || ! isscalar (ps)) [retval, p, ps] = common_size (p, ps); if (retval > 0) error ("geoinv: P and PS must be of common size or scalars."); endif endif ## Check for P and PS being double or single if (! (isfloat (p) && isfloat (ps))) error ("geoinv: P and PS must be double or single."); endif ## Check for P and PS being reals if (iscomplex (p) || iscomplex (ps)) error ("geoinv: P and PS must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (ps, 'single')) x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ## Handle edge cases k = (p == 1) & (ps >= 0) & (ps <= 1); x(k) = Inf; ## Get valid instances k = (p >= 0) & (p < 1) & (ps > 0) & (ps <= 1); ## Compute iCDF if (isscalar (ps)) x(k) = max (ceil (log (1 - p(k)) / log (1 - ps)) - 1, 0); else x(k) = max (ceil (log (1 - p(k)) ./ log (1 - ps(k))) - 1, 0); endif endfunction %!demo %! ## Plot various iCDFs from the geometric distribution %! p = 0.001:0.001:0.999; %! x1 = geoinv (p, 0.2); %! x2 = geoinv (p, 0.5); %! x3 = geoinv (p, 0.7); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r') %! grid on %! ylim ([0, 10]) %! legend ({'ps = 0.2', 'ps = 0.5', 'ps = 0.7'}, 'location', 'northwest') %! title ('Geometric iCDF') %! xlabel ('probability') %! ylabel ('values in x (number of failures)') ## Test output %!shared p %! p = [-1 0 0.75 1 2]; %!assert_equal (geoinv (p, 0.5*ones (1,5)), [NaN 0 1 Inf NaN]) %!assert_equal (geoinv (p, 0.5), [NaN 0 1 Inf NaN]) %!assert_equal (geoinv (p, 0.5*[1 -1 NaN 4 1]), [NaN NaN NaN NaN NaN]) %!assert_equal (geoinv ([p(1:2) NaN p(4:5)], 0.5), [NaN 0 NaN Inf NaN]) ## Test class of input preserved %!assert_equal (geoinv ([p, NaN], 0.5), [NaN 0 1 Inf NaN NaN]) %!assert_equal (geoinv (single ([p, NaN]), 0.5), single ([NaN 0 1 Inf NaN NaN])) %!assert_equal (geoinv ([p, NaN], single (0.5)), single ([NaN 0 1 Inf NaN NaN])) ## Test input validation %!error geoinv () %!error geoinv (1) %!error ... %! geoinv (ones (3), ones (2)) %!error ... %! geoinv (ones (2), ones (3)) %!error ... %! geoinv (int32 (2), 2) %!error ... %! geoinv (true, 2) %!error ... %! geoinv ('a', 2) %!error ... %! geoinv (i, 2) %!error ... %! geoinv (2, i) statistics-release-1.9.2/inst/Distribution_Functions/geopdf.m000066400000000000000000000115511524624707500244600ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} geopdf (@var{x}, @var{ps}) ## ## Geometric probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the geometric distribution with probability of success parameter @var{ps}. ## The size of @var{y} is the common size of @var{x} and @var{ps}. A scalar ## input functions as a constant matrix of the same size as the other inputs. ## ## The geometric distribution models the number of failures (@var{x}) of a ## Bernoulli trial with probability @var{ps} before the first success. ## ## Further information about the geometric distribution can be found at ## @url{https://en.wikipedia.org/wiki/Geometric_distribution} ## ## Input arguments must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted to ## @qcode{double}, so the result is always a probability. MATLAB is ## inconsistent here: for several of the discrete distributions it returns the ## result in the integer class of the input, truncating a probability to ## @math{0} or @math{1}. ## ## @seealso{geocdf, geoinv, geornd, geofit, geostat} ## @end deftypefn function y = geopdf (x, ps) ## Check for valid number of input arguments if (nargin < 2) error ("geopdf: function called with too few input arguments."); endif ## Check for common size of X and PS if (! isscalar (x) || ! isscalar (ps)) [retval, x, ps] = common_size (x, ps); if (retval > 0) error ("geopdf: X and PS must be of common size or scalars."); endif endif ## Check for X and PS being double, single, or integer if (! (isnumeric (x) && isnumeric (ps))) error ("geopdf: X and PS must be double, single, or integer."); endif ## Integer input is promoted to double, so the result is a probability ## rather than a value truncated to the input's integer type. if (isinteger (x)) x = double (x); endif if (isinteger (ps)) ps = double (ps); endif ## Check for X and PS being reals if (iscomplex (x) || iscomplex (ps)) error ("geopdf: X and PS must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (ps, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Return NaN for out of range parameters k = isnan (x) | ! (ps >= 0) | ! (ps <= 1); y(k) = NaN; ## The density at an infinite abscissa is zero: no proper distribution ## places mass there. y(isinf (x) & (ps >= 0) & (ps <= 1)) = 0; ## Get valid instances k = (x >= 0) & (x < Inf) & (x == fix (x)) & (ps > 0) & (ps <= 1); ## Compute CDF if (isscalar (ps)) y(k) = ps * ((1 - ps) .^ x(k)); else y(k) = ps(k) .* ((1 - ps(k)) .^ x(k)); endif endfunction %!demo %! ## Plot various PDFs from the geometric distribution %! x = 0:10; %! y1 = geopdf (x, 0.2); %! y2 = geopdf (x, 0.5); %! y3 = geopdf (x, 0.7); %! plot (x, y1, '*b', x, y2, '*g', x, y3, '*r') %! grid on %! ylim ([0, 0.8]) %! legend ({'ps = 0.2', 'ps = 0.5', 'ps = 0.7'}, 'location', 'northeast') %! title ('Geometric PDF') %! xlabel ('values in x (number of failures)') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1 0 1 Inf]; %! y = [0, 1/2, 1/4, 0]; %!assert_equal (geopdf (x, 0.5*ones (1,4)), y) %!assert_equal (geopdf (x, 0.5), y) %!assert_equal (geopdf (x, 0.5*[-1 NaN 4 1]), [NaN NaN NaN y(4)]) %!assert_equal (geopdf ([x, NaN], 0.5), [y, NaN]) ## Test class of input preserved %!assert_equal (geopdf (single ([x, NaN]), 0.5), single ([y, NaN]), 5*eps ('single')) %!assert_equal (geopdf ([x, NaN], single (0.5)), single ([y, NaN]), 5*eps ('single')) ## Test input validation %!error geopdf (true, 0.5) %!error geopdf ('a', 0.5) %!assert_equal (class (geopdf (int32 (2), 0.5)), 'double') %!error geopdf () %!error geopdf (1) %!error geopdf (1,2,3) %!error geopdf (ones (3), ones (2)) %!error geopdf (ones (2), ones (3)) %!error geopdf (i, 2) %!error geopdf (2, i) statistics-release-1.9.2/inst/Distribution_Functions/geornd.m000066400000000000000000000133641524624707500244760ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} geornd (@var{ps}) ## @deftypefnx {statistics} {@var{r} =} geornd (@var{ps}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} geornd (@var{ps}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} geornd (@var{ps}, [@var{sz}]) ## ## Random arrays from the geometric distribution. ## ## @code{@var{r} = geornd (@var{ps})} returns an array of random numbers chosen ## from the Birnbaum-Saunders distribution with probability of success parameter ## @var{ps}. The size of @var{r} is the size of @var{ps}. ## ## When called with a single size argument, @code{geornd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## The geometric distribution models the number of failures (@var{x}) of a ## Bernoulli trial with probability @var{ps} before the first success. ## ## Further information about the geometric distribution can be found at ## @url{https://en.wikipedia.org/wiki/Geometric_distribution} ## ## @seealso{geocdf, geoinv, geopdf, geofit, geostat} ## @end deftypefn function r = geornd (ps, varargin) ## Check for valid number of input arguments if (nargin < 1) error ("geornd: function called with too few input arguments."); endif ## Check for PS being reals if (iscomplex (ps)) error ("geornd: PS must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 1) sz = size (ps); elseif (nargin == 2) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("geornd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 2) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("geornd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameter match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (ps) && ! isequal (size (ps), size (ones (sz)))) error ("geornd: PS must be scalar or of size SZ."); endif ## Check for class type if (isa (ps, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from geometric distribution if (isscalar (ps)) if (ps > 0 && ps < 1); r = floor (- rande (sz, cls) ./ log (1 - ps)); elseif (ps == 0) r = Inf (sz, cls); elseif (ps == 1) r = zeros (sz, cls); elseif (ps < 0 || ps > 1) r = NaN (sz, cls); endif else r = floor (- rande (sz, cls) ./ log (1 - ps)); k = ! (ps >= 0) | ! (ps <= 1); r(k) = NaN; k = (ps == 0); r(k) = Inf; endif endfunction ## Test output %!assert_equal (size (geornd (0.5)), [1, 1]) %!assert_equal (size (geornd (0.5*ones (2,1))), [2, 1]) %!assert_equal (size (geornd (0.5*ones (2,2))), [2, 2]) %!assert_equal (size (geornd (0.5, 3)), [3, 3]) %!assert_equal (size (geornd (0.5, [4 1])), [4, 1]) %!assert_equal (size (geornd (0.5, 4, 1)), [4, 1]) %!assert_equal (size (geornd (0.5, [])), [0, 0]) %!assert_equal (size (geornd (0.5, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (geornd (1, -1)), [0, 0]) %!assert_equal (size (geornd (1, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (geornd (1, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (geornd (0.5)), "double") %!assert_equal (class (geornd (single (0.5))), "single") %!assert_equal (class (geornd (single ([0.5 0.5]))), "single") %!assert_equal (class (geornd (single (0))), "single") %!assert_equal (class (geornd (single (1))), "single") ## Test input validation %!error geornd () %!error geornd (i) %!error ... %! geornd (1, 1.2) %!error ... %! geornd (1, ones (2)) %!error ... %! geornd (1, [2 0 2.5]) %!error ... %! geornd (ones (2), ones (2)) %!error ... %! geornd (1, 2, 1.5, 5) %!error geornd (ones (2,2), 3) %!error geornd (ones (2,2), [3, 2]) %!error geornd (ones (2,2), 2, 3) statistics-release-1.9.2/inst/Distribution_Functions/gevcdf.m000066400000000000000000000201641524624707500244520ustar00rootroot00000000000000## Copyright (C) 2012 Nir Krakauer ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} gevcdf (@var{x}, @var{k}, @var{sigma}, @var{mu}) ## @deftypefnx {statistics} {@var{p} =} gevcdf (@var{x}, @var{k}, @var{sigma}, @var{mu}, @qcode{'upper'}) ## ## Generalized extreme value (GEV) cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the GEV distribution with shape parameter @var{k}, scale parameter ## @var{sigma}, and location parameter @var{mu}. The size of @var{p} is the ## common size of @var{x}, @var{k}, @var{sigma}, and @var{mu}. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## @code{[@dots{}] = gevcdf (@var{x}, @var{k}, @var{sigma}, @var{mu}, "upper")} ## computes the upper tail probability of the GEV distribution with parameters ## @var{k}, @var{sigma}, and @var{mu}, at the values in @var{x}. ## ## When @qcode{@var{k} < 0}, the GEV is the type III extreme value distribution. ## When @qcode{@var{k} > 0}, the GEV distribution is the type II, or Frechet, ## extreme value distribution. If @var{W} has a Weibull distribution as ## computed by the @code{wblcdf} function, then @qcode{-@var{W}} has a type III ## extreme value distribution and @qcode{1/@var{W}} has a type II extreme value ## distribution. In the limit as @var{k} approaches @qcode{0}, the GEV is the ## mirror image of the type I extreme value distribution as computed by the ## @code{evcdf} function. ## ## The mean of the GEV distribution is not finite when @qcode{@var{k} >= 1}, and ## the variance is not finite when @qcode{@var{k} >= 1/2}. The GEV distribution ## has positive density only for values of @var{x} such that ## @qcode{@var{k} * (@var{x} - @var{mu}) / @var{sigma} > -1}. ## ## Further information about the generalized extreme value distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{gevinv, gevpdf, gevrnd, gevfit, gevlike, gevstat} ## @end deftypefn function p = gevcdf (x, k, sigma, mu, uflag) ## Check for valid number of input arguments if (nargin < 4) error ("gevcdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 4) if (! strcmpi (uflag, 'upper')) error ("gevcdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, K, SIGMA, and MU if (! isscalar (x) || ! isscalar (k) || ! isscalar (sigma) || ! isscalar (mu)) [err, x, k, sigma, mu] = common_size (x, k, sigma, mu); if (err > 0) error ("gevcdf: X, K, SIGMA, and MU must be of common size or scalars."); endif endif ## Check for X, K, SIGMA, and MU being double or single if (! (isfloat (x) && isfloat (k) && isfloat (sigma) && isfloat (mu))) error ("gevcdf: X, K, SIGMA, and MU must be double or single."); endif ## Check for X, K, SIGMA, and MU being reals if (iscomplex (x) || iscomplex (k) || iscomplex (sigma) || iscomplex (mu)) error ("gevcdf: X, K, SIGMA, and MU must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (k, 'single') ... || isa (sigma, 'single') || isa (mu, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Prepare output p = zeros (size (x), is_class); ## Return NaN for out of range parameter SIGMA. sigma(sigma <= 0) = NaN; ## Calculate z z = (x - mu) ./ sigma; ## Process k == 0 k_0 = (abs (k) < eps); if (uflag) p(k_0) = -expm1 (-exp (-z(k_0))); else p(k_0) = exp (-exp (-z(k_0))); endif ## Process k != 0 k_0 = ! k_0; t = z .* k; if (uflag) p(k_0) = -expm1 (-exp (-(1 ./ k(k_0)) .* log1p (t(k_0)))); else p(k_0) = exp (-exp (-(1 ./ k(k_0)) .* log1p (t(k_0)))); endif ## Return 0 or 1 for 1 + k.*(x-mu)/sigma > 0 k_1 = k_0 & (t<=-1); t(k_1) = 0; if uflag == true p(k_1) = (k(k_1) >= 0); else p(k_1) = (k(k_1) < 0); endif endfunction %!demo %! ## Plot various CDFs from the generalized extreme value distribution %! x = -1:0.001:10; %! p1 = gevcdf (x, 1, 1, 1); %! p2 = gevcdf (x, 0.5, 1, 1); %! p3 = gevcdf (x, 1, 1, 5); %! p4 = gevcdf (x, 1, 2, 5); %! p5 = gevcdf (x, 1, 5, 5); %! p6 = gevcdf (x, 1, 0.5, 5); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', ... %! x, p4, '-c', x, p5, '-m', x, p6, '-k') %! grid on %! xlim ([-1, 10]) %! legend ({'k = 1, σ = 1, μ = 1', 'k = 0.5, σ = 1, μ = 1', ... %! 'k = 1, σ = 1, μ = 5', 'k = 1, σ = 2, μ = 5', ... %! 'k = 1, σ = 5, μ = 5', 'k = 1, σ = 0.5, μ = 5'}, ... %! 'location', 'southeast') %! title ('Generalized extreme value CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!test %! x = 0:0.5:2.5; %! sigma = 1:6; %! k = 1; %! mu = 0; %! p = gevcdf (x, k, sigma, mu); %! expected_p = [0.36788, 0.44933, 0.47237, 0.48323, 0.48954, 0.49367]; %! assert_equal (p, expected_p, 0.001); %!test %! x = -0.5:0.5:2.5; %! sigma = 0.5; %! k = 1; %! mu = 0; %! p = gevcdf (x, k, sigma, mu); %! expected_p = [0, 0.36788, 0.60653, 0.71653, 0.77880, 0.81873, 0.84648]; %! assert_equal (p, expected_p, 0.001); %!test # check for continuity for k near 0 %! x = 1; %! sigma = 0.5; %! k = -0.03:0.01:0.03; %! mu = 0; %! p = gevcdf (x, k, sigma, mu); %! expected_p = [0.88062, 0.87820, 0.87580, 0.87342, 0.87107, 0.86874, 0.86643]; %! assert_equal (p, expected_p, 0.001); ## Test input validation %!error gevcdf () %!error gevcdf (1) %!error gevcdf (1, 2) %!error gevcdf (1, 2, 3) %!error ... %! gevcdf (1, 2, 3, 4, 5, 6) %!error gevcdf (1, 2, 3, 4, 'tail') %!error gevcdf (1, 2, 3, 4, 5) %!error ... %! gevcdf (ones (3), ones (2), ones (2), ones (2)) %!error ... %! gevcdf (ones (2), ones (3), ones (2), ones (2)) %!error ... %! gevcdf (ones (2), ones (2), ones (3), ones (2)) %!error ... %! gevcdf (ones (2), ones (2), ones (2), ones (3)) %!error gevcdf (int32 (2), 2, 3, 4) %!error gevcdf (true, 2, 3, 4) %!error gevcdf ('a', 2, 3, 4) %!error gevcdf (i, 2, 3, 4) %!error gevcdf (1, i, 3, 4) %!error gevcdf (1, 2, i, 4) %!error gevcdf (1, 2, 3, i) statistics-release-1.9.2/inst/Distribution_Functions/gevinv.m000066400000000000000000000152601524624707500245130ustar00rootroot00000000000000## Copyright (C) 2012 Nir Krakauer ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} gevinv (@var{p}, @var{k}, @var{sigma}, @var{mu}) ## ## Inverse of the generalized extreme value (GEV) cumulative distribution ## function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the GEV distribution with shape parameter @var{k}, scale parameter ## @var{sigma}, and location parameter @var{mu}. The size of @var{p} is the ## common size of @var{x}, @var{k}, @var{sigma}, and @var{mu}. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## When @qcode{@var{k} < 0}, the GEV is the type III extreme value distribution. ## When @qcode{@var{k} > 0}, the GEV distribution is the type II, or Frechet, ## extreme value distribution. If @var{W} has a Weibull distribution as ## computed by the @code{wblcdf} function, then @qcode{-@var{W}} has a type III ## extreme value distribution and @qcode{1/@var{W}} has a type II extreme value ## distribution. In the limit as @var{k} approaches @qcode{0}, the GEV is the ## mirror image of the type I extreme value distribution as computed by the ## @code{evcdf} function. ## ## The mean of the GEV distribution is not finite when @qcode{@var{k} >= 1}, and ## the variance is not finite when @qcode{@var{k} >= 1/2}. The GEV distribution ## has positive density only for values of @var{x} such that ## @qcode{@var{k} * (@var{x} - @var{mu}) / @var{sigma} > -1}. ## ## Further information about the generalized extreme value distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{gevcdf, gevpdf, gevrnd, gevfit, gevlike, gevstat} ## @end deftypefn function x = gevinv (p, k, sigma, mu) ## Check for valid number of input arguments if (nargin < 4) error ("gevinv: function called with too few input arguments."); endif ## Check for common size of P, K, SIGMA, and MU [retval, p, k, sigma, mu] = common_size (p, k, sigma, mu); if (retval > 0) error ("gevinv: P, K, SIGMA, and MU must be of common size or scalars."); endif ## Check for P, K, SIGMA, and MU being double or single if (! (isfloat (p) && isfloat (k) && isfloat (sigma) && isfloat (mu))) error ("gevinv: P, K, SIGMA, and MU must be double or single."); endif ## Check for P, K, SIGMA, and MU being reals if (iscomplex (p) || iscomplex (k) || iscomplex (sigma) || iscomplex (mu)) error ("gevinv: P, K, SIGMA, and MU must not be complex."); endif is_neginf = p == 0; is_posinf = p == 1; is_nan = p < 0 | p > 1 | isnan (p); x = p; llP = log (-log (p)); kllP = k .* llP; ## Use the Taylor series expansion of the exponential to ## avoid roundoff error or dividing by zero when k is small ii = (abs (kllP) < 1E-4); x(ii) = mu(ii) - sigma(ii) .* llP(ii) .* (1 - kllP(ii) .* (1 - kllP(ii))); x(! ii) = mu(! ii) + (sigma(! ii) ./ k(! ii)) .* (exp (-kllP(! ii)) - 1); x(is_neginf) = -Inf; x(is_posinf) = Inf; x(is_nan) = NaN; endfunction %!demo %! ## Plot various iCDFs from the generalized extreme value distribution %! p = 0.001:0.001:0.999; %! x1 = gevinv (p, 1, 1, 1); %! x2 = gevinv (p, 0.5, 1, 1); %! x3 = gevinv (p, 1, 1, 5); %! x4 = gevinv (p, 1, 2, 5); %! x5 = gevinv (p, 1, 5, 5); %! x6 = gevinv (p, 1, 0.5, 5); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', ... %! p, x4, '-c', p, x5, '-m', p, x6, '-k') %! grid on %! ylim ([-1, 10]) %! legend ({'k = 1, σ = 1, μ = 1', 'k = 0.5, σ = 1, μ = 1', ... %! 'k = 1, σ = 1, μ = 5', 'k = 1, σ = 2, μ = 5', ... %! 'k = 1, σ = 5, μ = 5', 'k = 1, σ = 0.5, μ = 5'}, ... %! 'location', 'northwest') %! title ('Generalized extreme value iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!test %! p = 0.1:0.1:0.9; %! k = 0; %! sigma = 1; %! mu = 0; %! x = gevinv (p, k, sigma, mu); %! c = gevcdf (x, k, sigma, mu); %! assert_equal (c, p, 0.001); %!test %! p = 0.1:0.1:0.9; %! k = 1; %! sigma = 1; %! mu = 0; %! x = gevinv (p, k, sigma, mu); %! c = gevcdf (x, k, sigma, mu); %! assert_equal (c, p, 0.001); %!test %! p = 0.1:0.1:0.9; %! k = 0.3; %! sigma = 1; %! mu = 0; %! x = gevinv (p, k, sigma, mu); %! c = gevcdf (x, k, sigma, mu); %! assert_equal (c, p, 0.001); ## Test input validation %!error gevinv () %!error gevinv (1) %!error gevinv (1, 2) %!error gevinv (1, 2, 3) %!error ... %! gevinv (ones (3), ones (2), ones (2), ones (2)) %!error ... %! gevinv (ones (2), ones (3), ones (2), ones (2)) %!error ... %! gevinv (ones (2), ones (2), ones (3), ones (2)) %!error ... %! gevinv (ones (2), ones (2), ones (2), ones (3)) %!error gevinv (int32 (2), 2, 3, 4) %!error gevinv (true, 2, 3, 4) %!error gevinv ('a', 2, 3, 4) %!error gevinv (i, 2, 3, 4) %!error gevinv (1, i, 3, 4) %!error gevinv (1, 2, i, 4) %!error gevinv (1, 2, 3, i) statistics-release-1.9.2/inst/Distribution_Functions/gevpdf.m000066400000000000000000000154001524624707500244640ustar00rootroot00000000000000## Copyright (C) 2012 Nir Krakauer ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} gevpdf (@var{x}, @var{k}, @var{sigma}, @var{mu}) ## ## Generalized extreme value (GEV) probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the GEV distribution with shape parameter @var{k}, scale parameter ## @var{sigma}, and location parameter @var{mu}. The size of @var{y} is the ## common size of @var{x}, @var{k}, @var{sigma}, and @var{mu}. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## When @qcode{@var{k} < 0}, the GEV is the type III extreme value distribution. ## When @qcode{@var{k} > 0}, the GEV distribution is the type II, or Frechet, ## extreme value distribution. If @var{W} has a Weibull distribution as ## computed by the @code{wblcdf} function, then @qcode{-@var{W}} has a type III ## extreme value distribution and @qcode{1/@var{W}} has a type II extreme value ## distribution. In the limit as @var{k} approaches @qcode{0}, the GEV is the ## mirror image of the type I extreme value distribution as computed by the ## @code{evcdf} function. ## ## The mean of the GEV distribution is not finite when @qcode{@var{k} >= 1}, and ## the variance is not finite when @qcode{@var{k} >= 1/2}. The GEV distribution ## has positive density only for values of @var{x} such that ## @qcode{@var{k} * (@var{x} - @var{mu}) / @var{sigma} > -1}. ## ## Further information about the generalized extreme value distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{gevcdf, gevinv, gevrnd, gevfit, gevlike, gevstat} ## @end deftypefn function y = gevpdf (x, k, sigma, mu) ## Check for valid number of input arguments if (nargin < 4) error ("gevpdf: function called with too few input arguments."); endif ## Check for common size of X, K, SIGMA, and MU [retval, x, k, sigma, mu] = common_size (x, k, sigma, mu); if (retval > 0) error ("gevpdf: X, K, SIGMA, and MU must be of common size or scalars."); endif ## Check for X, K, SIGMA, and MU being double or single if (! (isfloat (x) && isfloat (k) && isfloat (sigma) && isfloat (mu))) error ("gevpdf: X, K, SIGMA, and MU must be double or single."); endif ## Check for X, K, SIGMA, and MU being reals if (iscomplex (x) || iscomplex (k) || iscomplex (sigma) || iscomplex (mu)) error ("gevpdf: X, K, SIGMA, and MU must not be complex."); endif z = 1 + k .* (x - mu) ./ sigma; ## Calculate generalized extreme value PDF y = exp (-(z .^ (-1 ./ k))) .* (z .^ (-1 - 1 ./ k)) ./ sigma; y(z <= 0) = 0; ## Use a different formula if k is very close to zero inds = (abs (k) < (eps^0.7)); if (any (inds)) z = (mu(inds) - x(inds)) ./ sigma(inds); y(inds) = exp (z - exp (z)) ./ sigma(inds); endif endfunction %!demo %! ## Plot various PDFs from the generalized extreme value distribution %! x = -1:0.001:10; %! y1 = gevpdf (x, 1, 1, 1); %! y2 = gevpdf (x, 0.5, 1, 1); %! y3 = gevpdf (x, 1, 1, 5); %! y4 = gevpdf (x, 1, 2, 5); %! y5 = gevpdf (x, 1, 5, 5); %! y6 = gevpdf (x, 1, 0.5, 5); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', ... %! x, y4, '-c', x, y5, '-m', x, y6, '-k') %! grid on %! xlim ([-1, 10]) %! ylim ([0, 1.1]) %! legend ({'k = 1, σ = 1, μ = 1', 'k = 0.5, σ = 1, μ = 1', ... %! 'k = 1, σ = 1, μ = 5', 'k = 1, σ = 2, μ = 5', ... %! 'k = 1, σ = 5, μ = 5', 'k = 1, σ = 0.5, μ = 5'}, ... %! 'location', 'northeast') %! title ('Generalized extreme value PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!test %! x = 0:0.5:2.5; %! sigma = 1:6; %! k = 1; %! mu = 0; %! y = gevpdf (x, k, sigma, mu); %! expected_y = [0.367879 0.143785 0.088569 0.063898 0.049953 0.040997]; %! assert_equal (y, expected_y, 0.001); %!test %! x = -0.5:0.5:2.5; %! sigma = 0.5; %! k = 1; %! mu = 0; %! y = gevpdf (x, k, sigma, mu); %! expected_y = [0 0.735759 0.303265 0.159229 0.097350 0.065498 0.047027]; %! assert_equal (y, expected_y, 0.001); %!test # check for continuity for k near 0 %! x = 1; %! sigma = 0.5; %! k = -0.03:0.01:0.03; %! mu = 0; %! y = gevpdf (x, k, sigma, mu); %! expected_y = [0.23820 0.23764 0.23704 0.23641 0.23576 0.23508 0.23438]; %! assert_equal (y, expected_y, 0.001); ## Test input validation %!error gevpdf () %!error gevpdf (1) %!error gevpdf (1, 2) %!error gevpdf (1, 2, 3) %!error ... %! gevpdf (ones (3), ones (2), ones (2), ones (2)) %!error ... %! gevpdf (ones (2), ones (3), ones (2), ones (2)) %!error ... %! gevpdf (ones (2), ones (2), ones (3), ones (2)) %!error ... %! gevpdf (ones (2), ones (2), ones (2), ones (3)) %!error gevpdf (int32 (2), 2, 3, 4) %!error gevpdf (true, 2, 3, 4) %!error gevpdf ('a', 2, 3, 4) %!error gevpdf (i, 2, 3, 4) %!error gevpdf (1, i, 3, 4) %!error gevpdf (1, 2, i, 4) %!error gevpdf (1, 2, 3, i) statistics-release-1.9.2/inst/Distribution_Functions/gevrnd.m000066400000000000000000000174241524624707500245060ustar00rootroot00000000000000## Copyright (C) 2012 Nir Krakauer ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} gevrnd (@var{k}, @var{sigma}, @var{mu}) ## @deftypefnx {statistics} {@var{r} =} gevrnd (@var{k}, @var{sigma}, @var{mu}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} gevrnd (@var{k}, @var{sigma}, @var{mu}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} gevrnd (@var{k}, @var{sigma}, @var{mu}, [@var{sz}]) ## ## Random arrays from the generalized extreme value (GEV) distribution. ## ## @code{@var{r} = gevrnd (@var{k}, @var{sigma}, @var{mu}} returns an array of ## random numbers chosen from the GEV distribution with shape parameter @var{k}, ## scale parameter @var{sigma}, and location parameter @var{mu}. The size of ## @var{r} is the common size of @var{k}, @var{sigma}, and @var{mu}. A scalar ## input functions as a constant matrix of the same size as the other inputs. ## ## When called with a single size argument, @code{gevrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## When @qcode{@var{k} < 0}, the GEV is the type III extreme value distribution. ## When @qcode{@var{k} > 0}, the GEV distribution is the type II, or Frechet, ## extreme value distribution. If @var{W} has a Weibull distribution as ## computed by the @code{wblcdf} function, then @qcode{-@var{W}} has a type III ## extreme value distribution and @qcode{1/@var{W}} has a type II extreme value ## distribution. In the limit as @var{k} approaches @qcode{0}, the GEV is the ## mirror image of the type I extreme value distribution as computed by the ## @code{evcdf} function. ## ## The mean of the GEV distribution is not finite when @qcode{@var{k} >= 1}, and ## the variance is not finite when @qcode{@var{k} >= 1/2}. The GEV distribution ## has positive density only for values of @var{x} such that ## @qcode{@var{k} * (@var{x} - @var{mu}) / @var{sigma} > -1}. ## ## Further information about the generalized extreme value distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution} ## ## @seealso{gevcdf, gevinv, gevpdf, gevfit, gevlike, gevstat} ## @end deftypefn function r = gevrnd (k, sigma, mu, varargin) ## Check for valid number of input arguments if (nargin < 3) error ("gevrnd: function called with too few input arguments."); endif ## Check for common size of K, SIGMA, and MU if (! isscalar (k) || ! isscalar (sigma) || ! isscalar (mu)) [retval, k, sigma, mu] = common_size (k, sigma, mu); if (retval > 0) error ("gevrnd: K, SIGMA, and MU must be of common size or scalars."); endif endif ## Check for K, SIGMA, and MU being reals if (iscomplex (k) || iscomplex (sigma) || iscomplex (mu)) error ("gevrnd: K, SIGMA, and MU must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 3) sz = size (k); elseif (nargin == 4) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("gevrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 4) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("gevrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (k) && ! isequal (size (k), size (ones (sz)))) error ("gevrnd: K, SIGMA, and MU must be scalars or of size SZ."); endif ## Check for class type if (isa (k, 'single') || isa (sigma, 'single') || isa (mu, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from Burr type XII distribution r = gevinv (rand (sz), k, sigma, mu); r = cast (r, cls); endfunction ## Test output %!assert_equal (size (gevrnd (1, 2, 1)), [1, 1]); %!assert_equal (size (gevrnd (ones (2, 1), 2, 1)), [2, 1]); %!assert_equal (size (gevrnd (ones (2, 2), 2, 1)), [2, 2]); %!assert_equal (size (gevrnd (1, 2 * ones (2, 1), 1)), [2, 1]); %!assert_equal (size (gevrnd (1, 2 * ones (2, 2), 1)), [2, 2]); %!assert_equal (size (gevrnd (1, 2, 1, 3)), [3, 3]); %!assert_equal (size (gevrnd (1, 2, 1, [4, 1])), [4, 1]); %!assert_equal (size (gevrnd (1, 2, 1, 4, 1)), [4, 1]); %!assert_equal (size (gevrnd (1, 2, 1, [])), [0, 0]) %!assert_equal (size (gevrnd (1, 2, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (gevrnd (1, 2, 3, -1)), [0, 0]) %!assert_equal (size (gevrnd (1, 2, 3, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (gevrnd (1, 2, 3, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (gevrnd (1,1,1)), "double") %!assert_equal (class (gevrnd (single (1),1,1)), "single") %!assert_equal (class (gevrnd (single ([1 1]),1,1)), "single") %!assert_equal (class (gevrnd (1,single (1),1)), "single") %!assert_equal (class (gevrnd (1,single ([1 1]),1)), "single") %!assert_equal (class (gevrnd (1,1,single (1))), "single") %!assert_equal (class (gevrnd (1,1,single ([1 1]))), "single") ## Test input validation %!error gevrnd () %!error gevrnd (1) %!error gevrnd (1, 2) %!error ... %! gevrnd (ones (3), ones (2), ones (2)) %!error ... %! gevrnd (ones (2), ones (3), ones (2)) %!error ... %! gevrnd (ones (2), ones (2), ones (3)) %!error gevrnd (i, 2, 3) %!error gevrnd (1, i, 3) %!error gevrnd (1, 2, i) %!error ... %! gevrnd (1, 2, 3, 1.2) %!error ... %! gevrnd (1, 2, 3, ones (2)) %!error ... %! gevrnd (1, 2, 3, [2 0 2.5]) %!error ... %! gevrnd (1, 2, 3, 2, 1.5, 5) %!error ... %! gevrnd (2, ones (2), 2, 3) %!error ... %! gevrnd (2, ones (2), 2, [3, 2]) %!error ... %! gevrnd (2, ones (2), 2, 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/gpcdf.m000066400000000000000000000310711524624707500242760ustar00rootroot00000000000000## Copyright (C) 1997-2015 Kurt Hornik ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 2018 John Donoghue ## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} gpcdf (@var{x}, @var{k}, @var{sigma}, @var{theta}) ## @deftypefnx {statistics} {@var{p} =} gpcdf (@var{x}, @var{k}, @var{sigma}, @var{theta}, @qcode{'upper'}) ## ## Generalized Pareto cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the generalized Pareto distribution with shape parameter @var{k}, ## scale parameter @var{sigma}, and location parameter @var{theta}. The size of ## @var{p} is the common size of @var{x}, @var{k}, @var{sigma}, and @var{theta}. ## A scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## @code{[@dots{}] = gpcdf(@var{x}, @var{k}, @var{sigma}, @var{theta}, "upper")} ## computes the upper tail probability of the generalized Pareto distribution ## with parameters @var{k}, @var{sigma}, and @var{theta}, at the values in ## @var{x}. ## ## When @qcode{@var{k} = 0} and @qcode{@var{theta} = 0}, the Generalized Pareto ## is equivalent to the exponential distribution. When @qcode{@var{k} > 0} and ## @code{@var{theta} = @var{k} / @var{k}} the Generalized Pareto is equivalent ## τπ the Pareto distribution. The mean of the Generalized Pareto is not finite ## when @qcode{@var{k} >= 1} and the variance is not finite when ## @qcode{@var{k} >= 1/2}. When @qcode{@var{k} >= 0}, the Generalized Pareto ## has positive density for @qcode{@var{x} > @var{theta}}, or, when ## @qcode{@var{theta} < 0}, for ## @qcode{0 <= (@var{x} - @var{theta}) / @var{sigma} <= -1 / @var{k}}. ## ## Further information about the generalized Pareto distribution can be found at ## @url{https://en.wikipedia.org/wiki/Generalized_Pareto_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{gpinv, gppdf, gprnd, gpfit, gplike, gpstat} ## @end deftypefn function p = gpcdf (x, k, sigma, theta, uflag) ## Check for valid number of input arguments if (nargin < 4) error ("gpcdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 4) if (! strcmpi (uflag, 'upper')) error ("gpcdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, K, SIGMA, and THETA if (! isscalar (x) || ! isscalar (k) || ! isscalar (sigma) || ! isscalar (theta)) [err, x, k, sigma, theta] = common_size (x, k, sigma, theta); if (err > 0) error ("gpcdf: X, K, SIGMA, and THETA must be of common size or scalars."); endif endif ## Check for X, K, SIGMA, and THETA being double or single if (! (isfloat (x) && isfloat (k) && isfloat (sigma) && isfloat (theta))) error ("gpcdf: X, K, SIGMA, and THETA must be double or single."); endif ## Check for X, K, SIGMA, and THETA being reals if (iscomplex (x) || iscomplex (k) || iscomplex (sigma) || iscomplex (theta)) error ("gpcdf: X, K, SIGMA, and THETA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (k, 'single') ... || isa (sigma, 'single') || isa (theta, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Prepare output p = zeros (size (x), is_class); ## Return NaNs for out of range values of sigma parameter sigma(sigma <= 0) = NaN; ## Calculate (x-theta)/sigma => 0 and force zero below that z = (x - theta) ./ sigma; z(z < 0) = 0; ## Compute cases for SHAPE == 0 kz = (abs (k) < eps (is_class)); if (uflag) p(kz) = exp (-z(kz)); else p(kz) = -expm1 (-z(kz)); endif ## For SHAPE < 0, calculate 0 <= x/sigma <= -1/k and force zero below that t = z .* k; kt = (t <= -1 & k < -eps (is_class)); t(kt) = 0; ## Compute cases for SHAPE != 0 kz = ! kz; if (uflag) p(kz) = exp ((-1 ./ k(kz)) .* log1p (t(kz))); else p(kz) = -expm1 ((-1 ./ k(kz)) .* log1p (t(kz))); endif if (uflag) p(kt) = 0; else p(kt) = 1; endif ## For SHAPE == NaN force p = NaN p(isnan (k)) = NaN; endfunction %!demo %! ## Plot various CDFs from the generalized Pareto distribution %! x = 0:0.001:5; %! p1 = gpcdf (x, 1, 1, 0); %! p2 = gpcdf (x, 5, 1, 0); %! p3 = gpcdf (x, 20, 1, 0); %! p4 = gpcdf (x, 1, 2, 0); %! p5 = gpcdf (x, 5, 2, 0); %! p6 = gpcdf (x, 20, 2, 0); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', ... %! x, p4, '-c', x, p5, '-m', x, p6, '-k') %! grid on %! xlim ([0, 5]) %! legend ({'k = 1, σ = 1, θ = 0', 'k = 5, σ = 1, θ = 0', ... %! 'k = 20, σ = 1, θ = 0', 'k = 1, σ = 2, θ = 0', ... %! 'k = 5, σ = 2, θ = 0', 'k = 20, σ = 2, θ = 0'}, ... %! 'location', 'northwest') %! title ('Generalized Pareto CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y1, y1u, y2, y2u, y3, y3u %! x = [-Inf, -1, 0, 1/2, 1, Inf]; %! y1 = [0, 0, 0, 0.3934693402873666, 0.6321205588285577, 1]; %! y1u = [1, 1, 1, 0.6065306597126334, 0.3678794411714423, 0]; %! y2 = [0, 0, 0, 1/3, 1/2, 1]; %! y2u = [1, 1, 1, 2/3, 1/2, 0]; %! y3 = [0, 0, 0, 1/2, 1, 1]; %! y3u = [1, 1, 1, 1/2, 0, 0]; %!assert_equal (gpcdf (x, zeros (1,6), ones (1,6), zeros (1,6)), y1, eps) %!assert_equal (gpcdf (x, 0, 1, zeros (1,6)), y1, eps) %!assert_equal (gpcdf (x, 0, ones (1,6), 0), y1, eps) %!assert_equal (gpcdf (x, zeros (1,6), 1, 0), y1, eps) %!assert_equal (gpcdf (x, 0, 1, 0), y1, eps) %!assert_equal (gpcdf (x, 0, 1, [0, 0, 0, NaN, 0, 0]), [y1(1:3), NaN, y1(5:6)], eps) %!assert_equal (gpcdf (x, 0, [1, 1, 1, NaN, 1, 1], 0), [y1(1:3), NaN, y1(5:6)], eps) %!assert_equal (gpcdf (x, [0, 0, 0, NaN, 0, 0], 1, 0), [y1(1:3), NaN, y1(5:6)], eps) %!assert_equal (gpcdf ([x(1:3), NaN, x(5:6)], 0, 1, 0), [y1(1:3), NaN, y1(5:6)], eps) %!assert_equal (gpcdf (x, zeros (1,6), ones (1,6), zeros (1,6), 'upper'), y1u, eps) %!assert_equal (gpcdf (x, 0, 1, zeros (1,6), 'upper'), y1u, eps) %!assert_equal (gpcdf (x, 0, ones (1,6), 0, 'upper'), y1u, eps) %!assert_equal (gpcdf (x, zeros (1,6), 1, 0, 'upper'), y1u, eps) %!assert_equal (gpcdf (x, 0, 1, 0, 'upper'), y1u, eps) %!assert_equal (gpcdf (x, ones (1,6), ones (1,6), zeros (1,6)), y2, eps) %!assert_equal (gpcdf (x, 1, 1, zeros (1,6)), y2, eps) %!assert_equal (gpcdf (x, 1, ones (1,6), 0), y2, eps) %!assert_equal (gpcdf (x, ones (1,6), 1, 0), y2, eps) %!assert_equal (gpcdf (x, 1, 1, 0), y2, eps) %!assert_equal (gpcdf (x, 1, 1, [0, 0, 0, NaN, 0, 0]), [y2(1:3), NaN, y2(5:6)], eps) %!assert_equal (gpcdf (x, 1, [1, 1, 1, NaN, 1, 1], 0), [y2(1:3), NaN, y2(5:6)], eps) %!assert_equal (gpcdf (x, [1, 1, 1, NaN, 1, 1], 1, 0), [y2(1:3), NaN, y2(5:6)], eps) %!assert_equal (gpcdf ([x(1:3), NaN, x(5:6)], 1, 1, 0), [y2(1:3), NaN, y2(5:6)], eps) %!assert_equal (gpcdf (x, ones (1,6), ones (1,6), zeros (1,6), 'upper'), y2u, eps) %!assert_equal (gpcdf (x, 1, 1, zeros (1,6), 'upper'), y2u, eps) %!assert_equal (gpcdf (x, 1, ones (1,6), 0, 'upper'), y2u, eps) %!assert_equal (gpcdf (x, ones (1,6), 1, 0, 'upper'), y2u, eps) %!assert_equal (gpcdf (x, 1, 1, 0, 'upper'), y2u, eps) %!assert_equal (gpcdf (x, 1, 1, [0, 0, 0, NaN, 0, 0], 'upper'), ... %! [y2u(1:3), NaN, y2u(5:6)], eps) %!assert_equal (gpcdf (x, 1, [1, 1, 1, NaN, 1, 1], 0, 'upper'), ... %! [y2u(1:3), NaN, y2u(5:6)], eps) %!assert_equal (gpcdf (x, [1, 1, 1, NaN, 1, 1], 1, 0, 'upper'), ... %! [y2u(1:3), NaN, y2u(5:6)], eps) %!assert_equal (gpcdf ([x(1:3), NaN, x(5:6)], 1, 1, 0, 'upper'), ... %! [y2u(1:3), NaN, y2u(5:6)], eps) %!assert_equal (gpcdf (x, -ones (1,6), ones (1,6), zeros (1,6)), y3, eps) %!assert_equal (gpcdf (x, -1, 1, zeros (1,6)), y3, eps) %!assert_equal (gpcdf (x, -1, ones (1,6), 0), y3, eps) %!assert_equal (gpcdf (x, -ones (1,6), 1, 0), y3, eps) %!assert_equal (gpcdf (x, -1, 1, 0), y3, eps) %!assert_equal (gpcdf (x, -1, 1, [0, 0, 0, NaN, 0, 0]), [y3(1:3), NaN, y3(5:6)], eps) %!assert_equal (gpcdf (x, -1, [1, 1, 1, NaN, 1, 1], 0), [y3(1:3), NaN, y3(5:6)], eps) %!assert_equal (gpcdf (x, [-1, -1, -1, NaN, -1, -1], 1, 0), [y3(1:3), NaN, y3(5:6)], eps) %!assert_equal (gpcdf ([x(1:3), NaN, x(5:6)], -1, 1, 0), [y3(1:3), NaN, y3(5:6)], eps) %!assert_equal (gpcdf (x, -ones (1,6), ones (1,6), zeros (1,6), 'upper'), y3u, eps) %!assert_equal (gpcdf (x, -1, 1, zeros (1,6), 'upper'), y3u, eps) %!assert_equal (gpcdf (x, -1, ones (1,6), 0, 'upper'), y3u, eps) %!assert_equal (gpcdf (x, -ones (1,6), 1, 0, 'upper'), y3u, eps) %!assert_equal (gpcdf (x, -1, 1, 0, 'upper'), y3u, eps) %!assert_equal (gpcdf (x, -1, 1, [0, 0, 0, NaN, 0, 0], 'upper'), ... %! [y3u(1:3), NaN, y3u(5:6)], eps) %!assert_equal (gpcdf (x, -1, [1, 1, 1, NaN, 1, 1], 0, 'upper'), ... %! [y3u(1:3), NaN, y3u(5:6)], eps) %!assert_equal (gpcdf (x, [-1, -1, -1, NaN, -1, -1], 1, 0, 'upper'), ... %! [y3u(1:3), NaN, y3u(5:6)], eps) %!assert_equal (gpcdf ([x(1:3), NaN, x(5:6)], -1, 1, 0, 'upper'), ... %! [y3u(1:3), NaN, y3u(5:6)], eps) ## Test class of input preserved %!assert_equal (gpcdf (single ([x, NaN]), 0, 1, 0), single ([y1, NaN]), eps ('single')) %!assert_equal (gpcdf ([x, NaN], 0, 1, single (0)), single ([y1, NaN]), eps ('single')) %!assert_equal (gpcdf ([x, NaN], 0, single (1), 0), single ([y1, NaN]), eps ('single')) %!assert_equal (gpcdf ([x, NaN], single (0), 1, 0), single ([y1, NaN]), eps ('single')) %!assert_equal (gpcdf (single ([x, NaN]), 1, 1, 0), single ([y2, NaN]), eps ('single')) %!assert_equal (gpcdf ([x, NaN], 1, 1, single (0)), single ([y2, NaN]), eps ('single')) %!assert_equal (gpcdf ([x, NaN], 1, single (1), 0), single ([y2, NaN]), eps ('single')) %!assert_equal (gpcdf ([x, NaN], single (1), 1, 0), single ([y2, NaN]), eps ('single')) %!assert_equal (gpcdf (single ([x, NaN]), -1, 1, 0), single ([y3, NaN]), eps ('single')) %!assert_equal (gpcdf ([x, NaN], -1, 1, single (0)), single ([y3, NaN]), eps ('single')) %!assert_equal (gpcdf ([x, NaN], -1, single (1), 0), single ([y3, NaN]), eps ('single')) %!assert_equal (gpcdf ([x, NaN], single (-1), 1, 0), single ([y3, NaN]), eps ('single')) ## Test input validation %!error gpcdf () %!error gpcdf (1) %!error gpcdf (1, 2) %!error gpcdf (1, 2, 3) %!error gpcdf (1, 2, 3, 4, 'tail') %!error gpcdf (1, 2, 3, 4, 5) %!error ... %! gpcdf (ones (3), ones (2), ones (2), ones (2)) %!error ... %! gpcdf (ones (2), ones (3), ones (2), ones (2)) %!error ... %! gpcdf (ones (2), ones (2), ones (3), ones (2)) %!error ... %! gpcdf (ones (2), ones (2), ones (2), ones (3)) %!error gpcdf (int32 (2), 2, 3, 4) %!error gpcdf (true, 2, 3, 4) %!error gpcdf ('a', 2, 3, 4) %!error gpcdf (i, 2, 3, 4) %!error gpcdf (1, i, 3, 4) %!error gpcdf (1, 2, i, 4) %!error gpcdf (1, 2, 3, i) statistics-release-1.9.2/inst/Distribution_Functions/gpinv.m000066400000000000000000000231641524624707500243420ustar00rootroot00000000000000## Copyright (C) 1997-2015 Kurt Hornik ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 2018 John Donoghue ## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} gpinv (@var{p}, @var{k}, @var{sigma}, @var{theta}) ## ## Inverse of the generalized Pareto cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the generalized Pareto distribution with shape parameter @var{k}, scale ## parameter @var{sigma}, and location parameter @var{theta}. The size of ## @var{x} is the common size of @var{p}, @var{k}, @var{sigma}, and @var{theta}. ## A scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## When @qcode{@var{k} = 0} and @qcode{@var{theta} = 0}, the Generalized Pareto ## is equivalent to the exponential distribution. When @qcode{@var{k} > 0} and ## @code{@var{theta} = @var{k} / @var{k}} the Generalized Pareto is equivalent ## to the Pareto distribution. The mean of the Generalized Pareto is not finite ## when @qcode{@var{k} >= 1} and the variance is not finite when ## @qcode{@var{k} >= 1/2}. When @qcode{@var{k} >= 0}, the Generalized Pareto ## has positive density for @qcode{@var{x} > @var{theta}}, or, when ## @qcode{@var{theta} < 0}, for ## @qcode{0 <= (@var{x} - @var{theta}) / @var{sigma} <= -1 / @var{k}}. ## ## Further information about the generalized Pareto distribution can be found at ## @url{https://en.wikipedia.org/wiki/Generalized_Pareto_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{gpcdf, gppdf, gprnd, gpfit, gplike, gpstat} ## @end deftypefn function x = gpinv (p, k, sigma, theta) ## Check for valid number of input arguments if (nargin < 4) error ("gpinv: function called with too few input arguments."); endif ## Check for common size of P, K, SIGMA, and THETA [retval, p, k, sigma, theta] = common_size (p, k, sigma, theta); if (retval > 0) error ("gpinv: P, K, SIGMA, and THETA must be of common size or scalars."); endif ## Check for P, K, SIGMA, and THETA being double or single if (! (isfloat (p) && isfloat (k) && isfloat (sigma) && isfloat (theta))) error ("gpinv: P, K, SIGMA, and THETA must be double or single."); endif ## Check for P, K, SIGMA, and THETA being reals if (iscomplex (p) || iscomplex (k) || iscomplex (sigma) || iscomplex (theta)) error ("gpinv: P, K, SIGMA, and THETA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (theta, 'single') ... || isa (sigma, 'single') || isa (k, 'single')) x = zeros (size (p), 'single'); else x = zeros (size (p)); endif ## Return NaNs for out of range values of sigma parameter kx = isnan (p) | ! (0 <= p) | ! (p <= 1) ... | ! (-Inf < theta) | ! (theta < Inf) ... | ! (sigma > 0) | ! (sigma < Inf) ... | ! (-Inf < k) | ! (k < Inf); x(kx) = NaN; kx = (0 <= p) & (p <= 1) & (-Inf < theta) & (theta < Inf) ... & (sigma > 0) & (sigma < Inf) & (-Inf < k) & (k < Inf); if (isscalar (theta) && isscalar (sigma) && isscalar (k)) if (k == 0) x(kx) = -log (1 - p(kx)); x(kx) = sigma * x(kx) + theta; elseif (k > 0) x(kx) = (1 - p(kx)).^(-k) - 1; x(kx) = (sigma / k) * x(kx) + theta; elseif (k < 0) x(kx) = (1 - p(kx)).^(-k) - 1; x(kx) = (sigma / k) * x(kx) + theta; endif else j = kx & (k == 0); if (any (j)) x(j) = -log (1 - p(j)); x(j) = sigma(j) .* x(j) + theta(j); endif j = kx & (k > 0); if (any (j)) x(j) = (1 - p(j)).^(-k(j)) - 1; x(j) = (sigma(j) ./ k(j)) .* x(j) + theta(j); endif j = kx & (k < 0); if (any (j)) x(j) = (1 - p(j)).^(-k(j)) - 1; x(j) = (sigma(j) ./ k(j)) .* x(j) + theta(j); endif endif endfunction %!demo %! ## Plot various iCDFs from the generalized Pareto distribution %! p = 0.001:0.001:0.999; %! x1 = gpinv (p, 1, 1, 0); %! x2 = gpinv (p, 5, 1, 0); %! x3 = gpinv (p, 20, 1, 0); %! x4 = gpinv (p, 1, 2, 0); %! x5 = gpinv (p, 5, 2, 0); %! x6 = gpinv (p, 20, 2, 0); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', ... %! p, x4, '-c', p, x5, '-m', p, x6, '-k') %! grid on %! ylim ([0, 5]) %! legend ({'k = 1, σ = 1, θ = 0', 'k = 5, σ = 1, θ = 0', ... %! 'k = 20, σ = 1, θ = 0', 'k = 1, σ = 2, θ = 0', ... %! 'k = 5, σ = 2, θ = 0', 'k = 20, σ = 2, θ = 0'}, ... %! 'location', 'southeast') %! title ('Generalized Pareto iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p, y1, y2, y3 %! p = [-1, 0, 1/2, 1, 2]; %! y1 = [NaN, 0, 0.6931471805599453, Inf, NaN]; %! y2 = [NaN, 0, 1, Inf, NaN]; %! y3 = [NaN, 0, 1/2, 1, NaN]; %!assert_equal (gpinv (p, zeros (1,5), ones (1,5), zeros (1,5)), y1) %!assert_equal (gpinv (p, 0, 1, zeros (1,5)), y1) %!assert_equal (gpinv (p, 0, ones (1,5), 0), y1) %!assert_equal (gpinv (p, zeros (1,5), 1, 0), y1) %!assert_equal (gpinv (p, 0, 1, 0), y1) %!assert_equal (gpinv (p, 0, 1, [0, 0, NaN, 0, 0]), [y1(1:2), NaN, y1(4:5)]) %!assert_equal (gpinv (p, 0, [1, 1, NaN, 1, 1], 0), [y1(1:2), NaN, y1(4:5)]) %!assert_equal (gpinv (p, [0, 0, NaN, 0, 0], 1, 0), [y1(1:2), NaN, y1(4:5)]) %!assert_equal (gpinv ([p(1:2), NaN, p(4:5)], 0, 1, 0), [y1(1:2), NaN, y1(4:5)]) %!assert_equal (gpinv (p, ones (1,5), ones (1,5), zeros (1,5)), y2) %!assert_equal (gpinv (p, 1, 1, zeros (1,5)), y2) %!assert_equal (gpinv (p, 1, ones (1,5), 0), y2) %!assert_equal (gpinv (p, ones (1,5), 1, 0), y2) %!assert_equal (gpinv (p, 1, 1, 0), y2) %!assert_equal (gpinv (p, 1, 1, [0, 0, NaN, 0, 0]), [y2(1:2), NaN, y2(4:5)]) %!assert_equal (gpinv (p, 1, [1, 1, NaN, 1, 1], 0), [y2(1:2), NaN, y2(4:5)]) %!assert_equal (gpinv (p, [1, 1, NaN, 1, 1], 1, 0), [y2(1:2), NaN, y2(4:5)]) %!assert_equal (gpinv ([p(1:2), NaN, p(4:5)], 1, 1, 0), [y2(1:2), NaN, y2(4:5)]) %!assert_equal (gpinv (p, -ones (1,5), ones (1,5), zeros (1,5)), y3) %!assert_equal (gpinv (p, -1, 1, zeros (1,5)), y3) %!assert_equal (gpinv (p, -1, ones (1,5), 0), y3) %!assert_equal (gpinv (p, -ones (1,5), 1, 0), y3) %!assert_equal (gpinv (p, -1, 1, 0), y3) %!assert_equal (gpinv (p, -1, 1, [0, 0, NaN, 0, 0]), [y3(1:2), NaN, y3(4:5)]) %!assert_equal (gpinv (p, -1, [1, 1, NaN, 1, 1], 0), [y3(1:2), NaN, y3(4:5)]) %!assert_equal (gpinv (p, -[1, 1, NaN, 1, 1], 1, 0), [y3(1:2), NaN, y3(4:5)]) %!assert_equal (gpinv ([p(1:2), NaN, p(4:5)], -1, 1, 0), [y3(1:2), NaN, y3(4:5)]) ## Test class of input preserved %!assert_equal (gpinv (single ([p, NaN]), 0, 1, 0), single ([y1, NaN])) %!assert_equal (gpinv ([p, NaN], 0, 1, single (0)), single ([y1, NaN])) %!assert_equal (gpinv ([p, NaN], 0, single (1), 0), single ([y1, NaN])) %!assert_equal (gpinv ([p, NaN], single (0), 1, 0), single ([y1, NaN])) %!assert_equal (gpinv (single ([p, NaN]), 1, 1, 0), single ([y2, NaN])) %!assert_equal (gpinv ([p, NaN], 1, 1, single (0)), single ([y2, NaN])) %!assert_equal (gpinv ([p, NaN], 1, single (1), 0), single ([y2, NaN])) %!assert_equal (gpinv ([p, NaN], single (1), 1, 0), single ([y2, NaN])) %!assert_equal (gpinv (single ([p, NaN]), -1, 1, 0), single ([y3, NaN])) %!assert_equal (gpinv ([p, NaN], -1, 1, single (0)), single ([y3, NaN])) %!assert_equal (gpinv ([p, NaN], -1, single (1), 0), single ([y3, NaN])) %!assert_equal (gpinv ([p, NaN], single (-1), 1, 0), single ([y3, NaN])) ## Test input validation %!error gpinv () %!error gpinv (1) %!error gpinv (1, 2) %!error gpinv (1, 2, 3) %!error ... %! gpinv (ones (3), ones (2), ones (2), ones (2)) %!error ... %! gpinv (ones (2), ones (3), ones (2), ones (2)) %!error ... %! gpinv (ones (2), ones (2), ones (3), ones (2)) %!error ... %! gpinv (ones (2), ones (2), ones (2), ones (3)) %!error gpinv (int32 (2), 2, 3, 4) %!error gpinv (true, 2, 3, 4) %!error gpinv ('a', 2, 3, 4) %!error gpinv (i, 2, 3, 4) %!error gpinv (1, i, 3, 4) %!error gpinv (1, 2, i, 4) %!error gpinv (1, 2, 3, i) statistics-release-1.9.2/inst/Distribution_Functions/gppdf.m000066400000000000000000000270011524624707500243110ustar00rootroot00000000000000## Copyright (C) 1997-2015 Kurt Hornik ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 2018 John Donoghue ## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} gppdf (@var{x}, @var{k}, @var{sigma}, @var{theta}) ## ## Generalized Pareto probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the generalized Pareto distribution with shape parameter @var{k}, scale ## parameter @var{sigma}, and location parameter @var{theta}. The size of ## @var{y} is the common size of @var{p}, @var{k}, @var{sigma}, and @var{theta}. ## A scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## When @qcode{@var{k} = 0} and @qcode{@var{theta} = 0}, the Generalized Pareto ## is equivalent to the exponential distribution. When @qcode{@var{k} > 0} and ## @code{@var{theta} = @var{k} / @var{k}} the Generalized Pareto is equivalent ## to the Pareto distribution. The mean of the Generalized Pareto is not finite ## when @qcode{@var{k} >= 1} and the variance is not finite when ## @qcode{@var{k} >= 1/2}. When @qcode{@var{k} >= 0}, the Generalized Pareto ## has positive density for @qcode{@var{x} > @var{theta}}, or, when ## @qcode{@var{theta} < 0}, for ## @qcode{0 <= (@var{x} - @var{theta}) / @var{sigma} <= -1 / @var{k}}. ## ## Further information about the generalized Pareto distribution can be found at ## @url{https://en.wikipedia.org/wiki/Generalized_Pareto_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## With a negative shape parameter the support is the closed interval ## @math{[@var{theta}, @var{theta} - @var{sigma}/@var{k}]}, and the density at ## its upper endpoint follows the limit of the density there: @math{0} for ## @math{-1 < @var{k} < 0}, @math{1/@var{sigma}} at @math{@var{k} = -1}, and ## unbounded for @math{@var{k} < -1}. MATLAB returns @math{0} at that endpoint ## whatever the shape, which contradicts its own @code{unifpdf}: the ## generalized Pareto with @math{@var{k} = -1} @emph{is} the uniform ## distribution on @math{[@var{theta}, @var{theta} + @var{sigma}]}, for which ## MATLAB's @code{unifpdf} returns @math{1/@var{sigma}} at the same point. This ## implementation returns the limit, and so agrees with @code{unifpdf}. ## ## @seealso{gpcdf, gpinv, gprnd, gpfit, gplike, gpstat} ## @end deftypefn function y = gppdf (x, k, sigma, theta) ## Check for valid number of input arguments if (nargin < 4) error ("gppdf: function called with too few input arguments."); endif ## Check for common size of X, K, SIGMA, and THETA if (! isscalar (x) || ! isscalar (k) || ! isscalar (sigma) || ! isscalar (theta)) [err, x, k, sigma, theta] = common_size (x, k, sigma, theta); if (err > 0) error ("gppdf: X, K, SIGMA, and THETA must be of common size or scalars."); endif endif ## Check for X, K, SIGMA, and THETA being double or single if (! (isfloat (x) && isfloat (k) && isfloat (sigma) && isfloat (theta))) error ("gppdf: X, K, SIGMA, and THETA must be double or single."); endif ## Check for X, K, SIGMA, and THETA being reals if (iscomplex (x) || iscomplex (k) || iscomplex (sigma) || iscomplex (theta)) error ("gppdf: X, K, SIGMA, and THETA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (theta, 'single') || isa (sigma, 'single') ... || isa (k, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Return NaNs for out of range values of sigma parameter ky = isnan (x) | ! (-Inf < theta) | ! (theta < Inf) | ... ! (sigma > 0) | ! (sigma < Inf) | ... ! (-Inf < k) | ! (k < Inf); y(ky) = NaN; ky = (-Inf < x) & (x < Inf) & (-Inf < theta) & (theta < Inf) & ... (sigma > 0) & (sigma < Inf) & (-Inf < k) & (k < Inf); if (isscalar (theta) && isscalar (sigma) && isscalar (k)) z = (x - theta) / sigma; j = ky & (k == 0) & (z >= 0); if (any (j)) y(j) = exp (-z(j)); endif j = ky & (k > 0) & (z >= 0); if (any (j)) y(j) = (k * z(j) + 1) .^ (-(k + 1) / k) ./ sigma; endif if (k < 0) j = ky & (k < 0) & (0 <= z) & (z <= -1. / k); if (any (j)) y(j) = (k * z(j) + 1) .^ (-(k + 1) / k) ./ sigma; endif endif else z = (x - theta) ./ sigma; j = ky & (k == 0) & (z >= 0); if (any (j)) y(j) = exp ( -z(j)); endif j = ky & (k > 0) & (z >= 0); if (any (j)) y(j) = (k(j) .* z(j) + 1) .^ (-(k(j) + 1) ./ k(j)) ... ./ sigma(j); endif if (any (k < 0)) j = ky & (k < 0) & (0 <= z) & (z <= -1 ./ k); if (any (j)) y(j) = (k(j) .* z(j) + 1) .^ (-(k(j) + 1) ./ k(j)) ... ./ sigma(j); endif endif endif endfunction %!demo %! ## Plot various PDFs from the generalized Pareto distribution %! x = 0:0.001:5; %! y1 = gppdf (x, 1, 1, 0); %! y2 = gppdf (x, 5, 1, 0); %! y3 = gppdf (x, 20, 1, 0); %! y4 = gppdf (x, 1, 2, 0); %! y5 = gppdf (x, 5, 2, 0); %! y6 = gppdf (x, 20, 2, 0); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', ... %! x, y4, '-c', x, y5, '-m', x, y6, '-k') %! grid on %! xlim ([0, 5]) %! ylim ([0, 1]) %! legend ({'k = 1, σ = 1, θ = 0', 'k = 5, σ = 1, θ = 0', ... %! 'k = 20, σ = 1, θ = 0', 'k = 1, σ = 2, θ = 0', ... %! 'k = 5, σ = 2, θ = 0', 'k = 20, σ = 2, θ = 0'}, ... %! 'location', 'northeast') %! title ('Generalized Pareto PDF') %! xlabel ('values in x') %! ylabel ('density') ## The upper endpoint of the support, measured against R2024a 2026-08-17, which ## returns 0 for all three shapes and so disagrees with its own unifpdf. %!test %! ## at k = -1 the distribution is uniform, and the density is 1/sigma %! assert_equal (gppdf (1, -1, 1, 0), 1); %! assert_equal (gppdf (1, -1, 1, 0), unifpdf (1, 0, 1)); %! assert_equal (gppdf (2, -1, 2, 0), 0.5); %! assert_equal (gppdf (2, -1, 2, 0), unifpdf (2, 0, 2)); %!test %! ## between -1 and 0 the density vanishes at the endpoint %! assert_equal (gppdf (2, -0.5, 1, 0), 0); %!test %! ## below -1 it diverges there %! assert_equal (gppdf (0.5, -2, 1, 0), Inf); %!test %! ## and the endpoint agrees with the values approaching it %! assert_equal (gppdf (1 - 1e-12, -1, 1, 0), 1); %! assert_equal (gppdf (1.5, -1, 1, 0), 0); ## Test output %!shared x, y1, y2, y3 %! x = [-Inf, -1, 0, 1/2, 1, Inf]; %! y1 = [0, 0, 1, 0.6065306597126334, 0.36787944117144233, 0]; %! y2 = [0, 0, 1, 4/9, 1/4, 0]; %! y3 = [0, 0, 1, 1, 1, 0]; %!assert_equal (gppdf (x, zeros (1,6), ones (1,6), zeros (1,6)), y1, eps) %!assert_equal (gppdf (x, 0, 1, zeros (1,6)), y1, eps) %!assert_equal (gppdf (x, 0, ones (1,6), 0), y1, eps) %!assert_equal (gppdf (x, zeros (1,6), 1, 0), y1, eps) %!assert_equal (gppdf (x, 0, 1, 0), y1, eps) %!assert_equal (gppdf (x, 0, 1, [0, 0, 0, NaN, 0, 0]), [y1(1:3), NaN, y1(5:6)]) %!assert_equal (gppdf (x, 0, [1, 1, 1, NaN, 1, 1], 0), [y1(1:3), NaN, y1(5:6)]) %!assert_equal (gppdf (x, [0, 0, 0, NaN, 0, 0], 1, 0), [y1(1:3), NaN, y1(5:6)]) %!assert_equal (gppdf ([x(1:3), NaN, x(5:6)], 0, 1, 0), [y1(1:3), NaN, y1(5:6)]) %!assert_equal (gppdf (x, ones (1,6), ones (1,6), zeros (1,6)), y2, eps) %!assert_equal (gppdf (x, 1, 1, zeros (1,6)), y2, eps) %!assert_equal (gppdf (x, 1, ones (1,6), 0), y2, eps) %!assert_equal (gppdf (x, ones (1,6), 1, 0), y2, eps) %!assert_equal (gppdf (x, 1, 1, 0), y2, eps) %!assert_equal (gppdf (x, 1, 1, [0, 0, 0, NaN, 0, 0]), [y2(1:3), NaN, y2(5:6)]) %!assert_equal (gppdf (x, 1, [1, 1, 1, NaN, 1, 1], 0), [y2(1:3), NaN, y2(5:6)]) %!assert_equal (gppdf (x, [1, 1, 1, NaN, 1, 1], 1, 0), [y2(1:3), NaN, y2(5:6)]) %!assert_equal (gppdf ([x(1:3), NaN, x(5:6)], 1, 1, 0), [y2(1:3), NaN, y2(5:6)]) %!assert_equal (gppdf (x, -ones (1,6), ones (1,6), zeros (1,6)), y3, eps) %!assert_equal (gppdf (x, -1, 1, zeros (1,6)), y3, eps) %!assert_equal (gppdf (x, -1, ones (1,6), 0), y3, eps) %!assert_equal (gppdf (x, -ones (1,6), 1, 0), y3, eps) %!assert_equal (gppdf (x, -1, 1, 0), y3, eps) %!assert_equal (gppdf (x, -1, 1, [0, 0, 0, NaN, 0, 0]), [y3(1:3), NaN, y3(5:6)]) %!assert_equal (gppdf (x, -1, [1, 1, 1, NaN, 1, 1], 0), [y3(1:3), NaN, y3(5:6)]) %!assert_equal (gppdf (x, [-1, -1, -1, NaN, -1, -1], 1, 0), [y3(1:3), NaN, y3(5:6)]) %!assert_equal (gppdf ([x(1:3), NaN, x(5:6)], -1, 1, 0), [y3(1:3), NaN, y3(5:6)]) ## Test class of input preserved %!assert_equal (gppdf (single ([x, NaN]), 0, 1, 0), single ([y1, NaN])) %!assert_equal (gppdf ([x, NaN], 0, 1, single (0)), single ([y1, NaN])) %!assert_equal (gppdf ([x, NaN], 0, single (1), 0), single ([y1, NaN])) %!assert_equal (gppdf ([x, NaN], single (0), 1, 0), single ([y1, NaN])) %!assert_equal (gppdf (single ([x, NaN]), 1, 1, 0), single ([y2, NaN])) %!assert_equal (gppdf ([x, NaN], 1, 1, single (0)), single ([y2, NaN])) %!assert_equal (gppdf ([x, NaN], 1, single (1), 0), single ([y2, NaN])) %!assert_equal (gppdf ([x, NaN], single (1), 1, 0), single ([y2, NaN])) %!assert_equal (gppdf (single ([x, NaN]), -1, 1, 0), single ([y3, NaN])) %!assert_equal (gppdf ([x, NaN], -1, 1, single (0)), single ([y3, NaN])) %!assert_equal (gppdf ([x, NaN], -1, single (1), 0), single ([y3, NaN])) %!assert_equal (gppdf ([x, NaN], single (-1), 1, 0), single ([y3, NaN])) ## Test input validation %!error gppdf (int32 (2), 1, 1, 0) %!error gppdf (true, 1, 1, 0) %!error gppdf ('a', 1, 1, 0) %!error gpcdf () %!error gpcdf (1) %!error gpcdf (1, 2) %!error gpcdf (1, 2, 3) %!error ... %! gpcdf (ones (3), ones (2), ones (2), ones (2)) %!error ... %! gpcdf (ones (2), ones (3), ones (2), ones (2)) %!error ... %! gpcdf (ones (2), ones (2), ones (3), ones (2)) %!error ... %! gpcdf (ones (2), ones (2), ones (2), ones (3)) %!error gpcdf (i, 2, 3, 4) %!error gpcdf (1, i, 3, 4) %!error gpcdf (1, 2, i, 4) %!error gpcdf (1, 2, 3, i) statistics-release-1.9.2/inst/Distribution_Functions/gprnd.m000066400000000000000000000225351524624707500243320ustar00rootroot00000000000000## Copyright (C) 1995-2015 Kurt Hornik ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} gprnd (@var{k}, @var{sigma}, @var{theta}) ## @deftypefnx {statistics} {@var{r} =} gprnd (@var{k}, @var{sigma}, @var{theta}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} gprnd (@var{k}, @var{sigma}, @var{theta}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} gprnd (@var{k}, @var{sigma}, @var{theta}, [@var{sz}]) ## ## Random arrays from the generalized Pareto distribution. ## ## @code{@var{r} = gprnd (@var{k}, @var{sigma}, @var{theta})} returns an array ## of random numbers chosen from the generalized Pareto distribution with shape ## parameter @var{k}, scale parameter @var{sigma}, and location parameter ## @var{theta}. The size of @var{r} is the common size of @var{k}, @var{sigma}, ## and @var{theta}. A scalar input functions as a constant matrix of the same ## size as the other inputs. ## ## When called with a single size argument, @code{gprnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## When @qcode{@var{k} = 0} and @qcode{@var{theta} = 0}, the Generalized Pareto ## is equivalent to the exponential distribution. When @qcode{@var{k} > 0} and ## @code{@var{theta} = @var{k} / @var{k}} the Generalized Pareto is equivalent ## to the Pareto distribution. The mean of the Generalized Pareto is not finite ## when @qcode{@var{k} >= 1} and the variance is not finite when ## @qcode{@var{k} >= 1/2}. When @qcode{@var{k} >= 0}, the Generalized Pareto ## has positive density for @qcode{@var{x} > @var{theta}}, or, when ## @qcode{@var{theta} < 0}, for ## @qcode{0 <= (@var{x} - @var{theta}) / @var{sigma} <= -1 / @var{k}}. ## ## Further information about the generalized Pareto distribution can be found at ## @url{https://en.wikipedia.org/wiki/Generalized_Pareto_distribution} ## ## @seealso{gpcdf, gpinv, gppdf, gpfit, gplike, gpstat} ## @end deftypefn function r = gprnd (k, sigma, theta, varargin) ## Check for valid number of input arguments if (nargin < 3) error ("gprnd: function called with too few input arguments."); endif ## Check for common size of K, SIGMA, and THETA if (! isscalar (k) || ! isscalar (sigma) || ! isscalar (theta)) [retval, k, sigma, theta] = common_size (k, sigma, theta); if (retval > 0) error ("gprnd: K, SIGMA, and THETA must be of common size or scalars."); endif endif ## Check for K, SIGMA, and THETA being reals if (iscomplex (k) || iscomplex (sigma) || iscomplex (theta)) error ("gprnd: K, SIGMA, and THETA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 3) sz = size (k); elseif (nargin == 4) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("gprnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 4) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("gprnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (k) && ! isequal (size (k), size (ones (sz)))) error ("gprnd: K, SIGMA, and THETA must be scalars or of size SZ."); endif ## Check for class type if (isa (k, 'single') || isa (sigma, 'single') || isa (theta, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from generalized Pareto distribution r = rand (sz, cls); ## Find valid parameters vr = (isfinite (r)) & (theta > -Inf) & (theta < Inf) ... & (sigma > 0) & (sigma < Inf) ... & (-Inf < k) & (k < Inf); ## Force invalid parameters to NaN r(! vr) = NaN; if (isscalar (k)) if (k == 0) r(vr) = theta - (sigma .* log (1 - r(vr))); else r(vr) = theta + ((sigma .* ((r(vr) .^ -k) - 1)) ./ k); endif else if (any (k == 0)) r(vr) = theta(vr) - (sigma(vr) .* log (1 - r(vr))); endif if (any (k < 0 | k > 0)) r(vr) = theta(vr) + ((sigma(vr) .* ((r(vr) .^ -k(vr)) - 1)) ./ k(vr)); endif endif endfunction ## Test output %!assert_equal (size (gprnd (0, 1, 0)), [1, 1]) %!assert_equal (size (gprnd (0, 1, zeros (2,1))), [2, 1]) %!assert_equal (size (gprnd (0, 1, zeros (2,2))), [2, 2]) %!assert_equal (size (gprnd (0, ones (2,1), 0)), [2, 1]) %!assert_equal (size (gprnd (0, ones (2,2), 0)), [2, 2]) %!assert_equal (size (gprnd (zeros (2,1), 1, 0)), [2, 1]) %!assert_equal (size (gprnd (zeros (2,2), 1, 0)), [2, 2]) %!assert_equal (size (gprnd (0, 1, 0, 3)), [3, 3]) %!assert_equal (size (gprnd (0, 1, 0, [4 1])), [4, 1]) %!assert_equal (size (gprnd (0, 1, 0, 4, 1)), [4, 1]) %!assert_equal (size (gprnd (1,1,0)), [1, 1]) %!assert_equal (size (gprnd (1, 1, zeros (2,1))), [2, 1]) %!assert_equal (size (gprnd (1, 1, zeros (2,2))), [2, 2]) %!assert_equal (size (gprnd (1, ones (2,1), 0)), [2, 1]) %!assert_equal (size (gprnd (1, ones (2,2), 0)), [2, 2]) %!assert_equal (size (gprnd (ones (2,1), 1, 0)), [2, 1]) %!assert_equal (size (gprnd (ones (2,2), 1, 0)), [2, 2]) %!assert_equal (size (gprnd (1, 1, 0, 3)), [3, 3]) %!assert_equal (size (gprnd (1, 1, 0, [4 1])), [4, 1]) %!assert_equal (size (gprnd (1, 1, 0, 4, 1)), [4, 1]) %!assert_equal (size (gprnd (-1, 1, 0)), [1, 1]) %!assert_equal (size (gprnd (-1, 1, zeros (2,1))), [2, 1]) %!assert_equal (size (gprnd (1, -1, zeros (2,2))), [2, 2]) %!assert_equal (size (gprnd (-1, ones (2,1), 0)), [2, 1]) %!assert_equal (size (gprnd (-1, ones (2,2), 0)), [2, 2]) %!assert_equal (size (gprnd (-ones (2,1), 1, 0)), [2, 1]) %!assert_equal (size (gprnd (-ones (2,2), 1, 0)), [2, 2]) %!assert_equal (size (gprnd (-1, 1, 0, 3)), [3, 3]) %!assert_equal (size (gprnd (-1, 1, 0, [4, 1])), [4, 1]) %!assert_equal (size (gprnd (-1, 1, 0, 4, 1)), [4, 1]) %!assert_equal (size (gprnd (-1, 1, 0, [])), [0, 0]) %!assert_equal (size (gprnd (-1, 1, 0, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (gprnd (1, 2, 3, -1)), [0, 0]) %!assert_equal (size (gprnd (1, 2, 3, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (gprnd (1, 2, 3, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (gprnd (0, 1, 0)), "double") %!assert_equal (class (gprnd (0, 1, single (0))), "single") %!assert_equal (class (gprnd (0, 1, single ([0, 0]))), "single") %!assert_equal (class (gprnd (0, single (1),0)), "single") %!assert_equal (class (gprnd (0, single ([1, 1]),0)), "single") %!assert_equal (class (gprnd (single (0), 1, 0)), "single") %!assert_equal (class (gprnd (single ([0, 0]), 1, 0)), "single") ## Test input validation %!error gprnd () %!error gprnd (1) %!error gprnd (1, 2) %!error ... %! gprnd (ones (3), ones (2), ones (2)) %!error ... %! gprnd (ones (2), ones (3), ones (2)) %!error ... %! gprnd (ones (2), ones (2), ones (3)) %!error gprnd (i, 2, 3) %!error gprnd (1, i, 3) %!error gprnd (1, 2, i) %!error ... %! gprnd (1, 2, 3, 1.2) %!error ... %! gprnd (1, 2, 3, ones (2)) %!error ... %! gprnd (1, 2, 3, [2 0 2.5]) %!error ... %! gprnd (1, 2, 3, 2, 1.5, 5) %!error ... %! gprnd (2, ones (2), 2, 3) %!error ... %! gprnd (2, ones (2), 2, [3, 2]) %!error ... %! gprnd (2, ones (2), 2, 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/gumbelcdf.m000066400000000000000000000236471524624707500251550ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} gumbelcdf (@var{x}) ## @deftypefnx {statistics} {@var{p} =} gumbelcdf (@var{x}, @var{mu}) ## @deftypefnx {statistics} {@var{p} =} gumbelcdf (@var{x}, @var{mu}, @var{beta}) ## @deftypefnx {statistics} {@var{p} =} gumbelcdf (@dots{}, @qcode{'upper'}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} gumbelcdf (@var{x}, @var{mu}, @var{beta}, @var{pcov}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} gumbelcdf (@var{x}, @var{mu}, @var{beta}, @var{pcov}, @var{alpha}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} gumbelcdf (@dots{}, @qcode{'upper'}) ## ## Gumbel cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the Gumbel distribution (also known as the extreme value or the type ## I generalized extreme value distribution) with location parameter @var{mu} ## and scale parameter @var{beta}. The size of @var{p} is the common size of ## @var{x}, @var{mu} and @var{beta}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## Default values are @var{mu} = 0 and @var{beta} = 1. ## ## When called with three output arguments, i.e. @code{[@var{p}, @var{plo}, ## @var{pup}]}, @code{gumbelcdf} computes the confidence bounds for @var{p} when ## the input parameters @var{mu} and @var{beta} are estimates. In such case, ## @var{pcov}, a @math{2*2} matrix containing the covariance matrix of the ## estimated parameters, is necessary. Optionally, @var{alpha}, which has a ## default value of 0.05, specifies the @qcode{100 * (1 - @var{alpha})} percent ## confidence bounds. @var{plo} and @var{pup} are arrays of the same size as ## @var{p} containing the lower and upper confidence bounds. ## ## @code{[@dots{}] = gumbelcdf (@dots{}, "upper")} computes the upper tail ## probability of the Gumbel distribution with parameters @var{mu} and ## @var{beta}, at the values in @var{x}. ## ## The Gumbel distribution is used to model the distribution of the maximum (or ## the minimum) of a number of samples of various distributions. This version ## is suitable for modeling maxima. For modeling minima, use the alternative ## extreme value CDF, @code{evcdf}. ## ## @code{[@dots{}] = gumbelcdf (@dots{}, "upper")} computes the upper tail ## probability of the extreme value (Gumbel) distribution. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{gumbelinv, gumbelpdf, gumbelrnd, gumbelfit, gumbellike, gumbelstat, ## evcdf} ## @end deftypefn function [varargout] = gumbelcdf (x, varargin) ## Check for valid number of input arguments if (nargin < 1 || nargin > 6) error ("gumbelcdf: invalid number of input arguments."); endif ## Check for 'upper' flag if (nargin > 1 && strcmpi (varargin{end}, 'upper')) uflag = true; varargin(end) = []; elseif (nargin > 1 && ischar (varargin{end}) && ... ! strcmpi (varargin{end}, 'upper')) error ("gumbelcdf: invalid argument for upper tail."); else uflag = false; endif ## Get extra arguments (if they exist) or add defaults if (numel (varargin) > 0) mu = varargin{1}; else mu = 0; endif if (numel (varargin) > 1) beta = varargin{2}; else beta = 1; endif if (numel (varargin) > 2) pcov = varargin{3}; ## Check for valid covariance matrix 2x2 if (! isequal (size (pcov), [2, 2])) error ("gumbelcdf: invalid size of covariance matrix."); endif else ## Check that cov matrix is provided if 3 output arguments are requested if (nargout > 1) error ("gumbelcdf: covariance matrix is required for confidence bounds."); endif pcov = []; endif if (numel (varargin) > 3) alpha = varargin{4}; ## Check for valid alpha value if (! isnumeric (alpha) || numel (alpha) !=1 || alpha <= 0 || alpha >= 1) error ("gumbelcdf: invalid value for alpha."); endif else alpha = 0.05; endif ## Check for common size of X, MU, and BETA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (beta)) [err, x, mu, beta] = common_size (x, mu, beta); if (err > 0) error ("gumbelcdf: X, MU, and BETA must be of common size or scalars."); endif endif ## Check for X, MU, and BETA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (beta))) error ("gumbelcdf: X, MU, and BETA must be double or single."); endif ## Check for X, MU, and BETA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (beta)) error ("gumbelcdf: X, MU, and BETA must not be complex."); endif ## Return NaNs for out of range parameters. beta(beta <= 0) = NaN; ## Compute extreme value cdf z = (x - mu) ./ beta; if (uflag) p = -expm1 (-exp (-z)); else p = exp (-exp (-z)); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (beta, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Prepare output varargout{1} = cast (p, is_class); if (nargout > 1) plo = NaN (size (z), is_class); pup = NaN (size (z), is_class); endif ## Check beta if (isscalar (beta)) if (beta > 0) sigma_p = true (size (z)); else if (nargout == 3) varargout{2} = plo; varargout{3} = pup; endif return; endif else sigma_p = beta > 0; endif ## Compute confidence bounds (if requested) if (nargout >= 2) zvar = (pcov(1,1) + 2 * pcov(1,2) * z(sigma_p) + ... pcov(2,2) * z(sigma_p) .^ 2) ./ (beta .^ 2); if (any (zvar < 0)) error ("gumbelcdf: bad covariance matrix."); endif normz = -norminv (alpha / 2); halfwidth = normz * sqrt (zvar); zlo = z(sigma_p) - halfwidth; zup = z(sigma_p) + halfwidth; if (uflag) plo(sigma_p) = -expm1 (-exp (-zup)); pup(sigma_p) = -expm1 (-exp (-zlo)); else plo(sigma_p) = exp (-exp (-zlo)); pup(sigma_p) = exp (-exp (-zup)); endif varargout{2} = plo; varargout{3} = pup; endif endfunction %!demo %! ## Plot various CDFs from the Gumbel distribution %! x = -5:0.01:20; %! p1 = gumbelcdf (x, 0.5, 2); %! p2 = gumbelcdf (x, 1.0, 2); %! p3 = gumbelcdf (x, 1.5, 3); %! p4 = gumbelcdf (x, 3.0, 4); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c') %! grid on %! legend ({'μ = 0.5, β = 2', 'μ = 1.0, β = 2', ... %! 'μ = 1.5, β = 3', 'μ = 3.0, β = 4'}, 'location', 'southeast') %! title ('Gumbel CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-Inf, 1, 2, Inf]; %! y = [0, 0.3679, 0.6922, 1]; %!assert_equal (gumbelcdf (x, ones (1,4), ones (1,4)), y, 1e-4) %!assert_equal (gumbelcdf (x, 1, ones (1,4)), y, 1e-4) %!assert_equal (gumbelcdf (x, ones (1,4), 1), y, 1e-4) %!assert_equal (gumbelcdf (x, [0, -Inf, NaN, Inf], 1), [0, 1, NaN, NaN], 1e-4) %!assert_equal (gumbelcdf (x, 1, [Inf, NaN, -1, 0]), [NaN, NaN, NaN, NaN], 1e-4) %!assert_equal (gumbelcdf ([x(1:2), NaN, x(4)], 1, 1), [y(1:2), NaN, y(4)], 1e-4) %!assert_equal (gumbelcdf (x, 'upper'), [1, 0.3078, 0.1266, 0], 1e-4) ## Test class of input preserved %!assert_equal (gumbelcdf ([x, NaN], 1, 1), [y, NaN], 1e-4) %!assert_equal (gumbelcdf (single ([x, NaN]), 1, 1), single ([y, NaN]), 1e-4) %!assert_equal (gumbelcdf ([x, NaN], single (1), 1), single ([y, NaN]), 1e-4) %!assert_equal (gumbelcdf ([x, NaN], 1, single (1)), single ([y, NaN]), 1e-4) ## Test input validation %!error gumbelcdf () %!error gumbelcdf (1,2,3,4,5,6,7) %!error gumbelcdf (1, 2, 3, 4, 'uper') %!error ... %! gumbelcdf (ones (3), ones (2), ones (2)) %!error gumbelcdf (2, 3, 4, [1, 2]) %!error ... %! [p, plo, pup] = gumbelcdf (1, 2, 3) %!error [p, plo, pup] = ... %! gumbelcdf (1, 2, 3, [1, 0; 0, 1], 0) %!error [p, plo, pup] = ... %! gumbelcdf (1, 2, 3, [1, 0; 0, 1], 1.22) %!error [p, plo, pup] = ... %! gumbelcdf (1, 2, 3, [1, 0; 0, 1], 'alpha', 'upper') %!error gumbelcdf (int32 (2), 2, 2) %!error gumbelcdf (true, 2, 2) %!error gumbelcdf ('a', 2, 2) %!error gumbelcdf (i, 2, 2) %!error gumbelcdf (2, i, 2) %!error gumbelcdf (2, 2, i) %!error ... %! [p, plo, pup] = gumbelcdf (1, 2, 3, [1, 0; 0, -inf], 0.04) statistics-release-1.9.2/inst/Distribution_Functions/gumbelinv.m000066400000000000000000000207301524624707500252030ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} gumbelinv (@var{p}) ## @deftypefnx {statistics} {@var{x} =} gumbelinv (@var{p}, @var{mu}) ## @deftypefnx {statistics} {@var{x} =} gumbelinv (@var{p}, @var{mu}, @var{beta}) ## @deftypefnx {statistics} {[@var{x}, @var{xlo}, @var{xup}] =} gumbelinv (@var{p}, @var{mu}, @var{beta}, @var{pcov}) ## @deftypefnx {statistics} {[@var{x}, @var{xlo}, @var{xup}] =} gumbelinv (@var{p}, @var{mu}, @var{beta}, @var{pcov}, @var{alpha}) ## ## Inverse of the Gumbel cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the Gumbel distribution (also known as the extreme value or the type I ## generalized extreme value distribution) with location parameter @var{mu} and ## scale parameter @var{beta}. The size of @var{x} is the common size of ## @var{p}, @var{mu} and @var{beta}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## Default values are @var{mu} = 0 and @var{beta} = 1. ## ## When called with three output arguments, i.e. @qcode{[@var{x}, @var{xlo}, ## @var{xup}]}, @code{gumbelinv} computes the confidence bounds for @var{x} when ## the input parameters @var{mu} and @var{beta} are estimates. In such case, ## @var{pcov}, a @math{2*2} matrix containing the covariance matrix of the ## estimated parameters, is necessary. Optionally, @var{alpha}, which has a ## default value of 0.05, specifies the @qcode{100 * (1 - @var{alpha})} percent ## confidence bounds. @var{xlo} and @var{xup} are arrays of the same size as ## @var{x} containing the lower and upper confidence bounds. ## ## The Gumbel distribution is used to model the distribution of the maximum (or ## the minimum) of a number of samples of various distributions. This version ## is suitable for modeling maxima. For modeling minima, use the alternative ## extreme value iCDF, @code{evinv}. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{gumbelcdf, gumbelpdf, gumbelrnd, gumbelfit, gumbellike, gumbelstat, ## evinv} ## @end deftypefn function [x, xlo, xup] = gumbelinv (p, mu, beta, pcov, alpha) ## Check for valid number of input arguments if (nargin < 1 || nargin > 5) error ("gumbelinv: invalid number of input arguments."); endif ## Add defaults (if missing input arguments) if (nargin < 2) mu = 0; endif if (nargin < 3) beta = 1; endif ## Check if PCOV is provided when confidence bounds are requested if (nargout > 2) if (nargin < 4) error ("gumbelinv: covariance matrix is required for confidence bounds."); endif ## Check for valid covariance matrix 2x2 if (! isequal (size (pcov), [2, 2])) error ("gumbelinv: invalid size of covariance matrix."); endif ## Check for valid alpha value if (nargin < 5) alpha = 0.05; elseif (! isnumeric (alpha) || numel (alpha) !=1 || alpha <= 0 || alpha >= 1) error ("gumbelinv: invalid value for alpha."); endif endif ## Check for common size of P, MU, and BETA if (! isscalar (p) || ! isscalar (mu) || ! isscalar (beta)) [err, p, mu, beta] = common_size (p, mu, beta); if (err > 0) error ("gumbelinv: P, MU, and BETA must be of common size or scalars."); endif endif ## Check for P, MU, and BETA being double or single if (! (isfloat (p) && isfloat (mu) && isfloat (beta))) error ("gumbelinv: P, MU, and BETA must be double or single."); endif ## Check for P, MU, and BETA being reals if (iscomplex (p) || iscomplex (mu) || iscomplex (beta)) error ("gumbelinv: P, MU, and BETA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (mu, 'single') || isa (beta, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Compute inverse of type 1 extreme value cdf k = (eps <= p & p < 1); if (all (k(:))) q = -log (-log (p)); else q = zeros (size (p), is_class); q(k) = -log (-log (p(k))); ## Return -Inf for p = 0 and Inf for p = 1 q(p < eps) = -Inf; q(p == 1) = Inf; ## Return NaN for out of range values of P q(p < 0 | 1 < p | isnan (p)) = NaN; endif ## Return NaN for out of range values of BETA beta(beta <= 0) = NaN; x = (beta .* q) + mu; ## Compute confidence bounds if requested. if (nargout >= 2) xvar = pcov(1,1) + 2 * pcov(1,2) * q + pcov(2,2) * q .^ 2; if (any (xvar < 0)) || any (isnan (xvar)) error ("gumbelinv: bad covariance matrix."); endif z = -norminv (alpha / 2); halfwidth = z * sqrt (xvar); xlo = x - halfwidth; xup = x + halfwidth; endif endfunction %!demo %! ## Plot various iCDFs from the Gumbel distribution %! p = 0.001:0.001:0.999; %! x1 = gumbelinv (p, 0.5, 2); %! x2 = gumbelinv (p, 1.0, 2); %! x3 = gumbelinv (p, 1.5, 3); %! x4 = gumbelinv (p, 3.0, 4); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c') %! grid on %! ylim ([-5, 20]) %! legend ({'μ = 0.5, β = 2', 'μ = 1.0, β = 2', ... %! 'μ = 1.5, β = 3', 'μ = 3.0, β = 4'}, 'location', 'northwest') %! title ('Gumbel iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p, x %! p = [0, 0.05, 0.5 0.95]; %! x = [-Inf, -1.0972, 0.3665, 2.9702]; %!assert_equal (gumbelinv (p), x, 1e-4) %!assert_equal (gumbelinv (p, zeros (1,4), ones (1,4)), x, 1e-4) %!assert_equal (gumbelinv (p, 0, ones (1,4)), x, 1e-4) %!assert_equal (gumbelinv (p, zeros (1,4), 1), x, 1e-4) %!assert_equal (gumbelinv (p, [0, -Inf, NaN, Inf], 1), [-Inf, -Inf, NaN, Inf], 1e-4) %!assert_equal (gumbelinv (p, 0, [Inf, NaN, -1, 0]), [-Inf, NaN, NaN, NaN], 1e-4) %!assert_equal (gumbelinv ([p(1:2), NaN, p(4)], 0, 1), [x(1:2), NaN, x(4)], 1e-4) ## Test class of input preserved %!assert_equal (gumbelinv ([p, NaN], 0, 1), [x, NaN], 1e-4) %!assert_equal (gumbelinv (single ([p, NaN]), 0, 1), single ([x, NaN]), 1e-4) %!assert_equal (gumbelinv ([p, NaN], single (0), 1), single ([x, NaN]), 1e-4) %!assert_equal (gumbelinv ([p, NaN], 0, single (1)), single ([x, NaN]), 1e-4) ## Test whether gumbelcdf is successfully inverted %! p = [0.05, 0.5, 0.95]; %! x = gumbelinv(p); %!assert_equal (gumbelcdf (x), p, 1e-4) ## Test input validation %!error gumbelinv () %!error gumbelinv (1,2,3,4,5,6) %!error ... %! gumbelinv (ones (3), ones (2), ones (2)) %!error ... %! [p, plo, pup] = gumbelinv (2, 3, 4, [1, 2]) %!error ... %! [p, plo, pup] = gumbelinv (1, 2, 3) %!error [p, plo, pup] = ... %! gumbelinv (1, 2, 3, [1, 0; 0, 1], 0) %!error [p, plo, pup] = ... %! gumbelinv (1, 2, 3, [1, 0; 0, 1], 1.22) %!error gumbelinv (int32 (2), 2, 2) %!error gumbelinv (true, 2, 2) %!error gumbelinv ('a', 2, 2) %!error gumbelinv (i, 2, 2) %!error gumbelinv (2, i, 2) %!error gumbelinv (2, 2, i) %!error ... %! [p, plo, pup] = gumbelinv (1, 2, 3, [-1, 10; -Inf, -Inf], 0.04) statistics-release-1.9.2/inst/Distribution_Functions/gumbelpdf.m000066400000000000000000000120251524624707500251560ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} gumbelpdf (@var{x}) ## @deftypefnx {statistics} {@var{y} =} gumbelpdf (@var{x}, @var{mu}) ## @deftypefnx {statistics} {@var{y} =} gumbelpdf (@var{x}, @var{mu}, @var{beta}) ## ## Gumbel probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the Gumbel distribution (also known as the extreme value or the type I ## generalized extreme value distribution) with location parameter @var{mu} and ## scale parameter @var{beta}. The size of @var{y} is the common size of ## @var{x}, @var{mu} and @var{beta}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## Default values are @var{mu} = 0 and @var{beta} = 1. ## ## The Gumbel distribution is used to model the distribution of the maximum (or ## the minimum) of a number of samples of various distributions. This version ## is suitable for modeling maxima. For modeling minima, use the alternative ## extreme value iCDF, @code{evpdf}. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{gumbelcdf, gumbelinv, gumbelrnd, gumbelfit, gumbellike, gumbelstat, ## evpdf} ## @end deftypefn function y = gumbelpdf (x, mu, beta) ## Check for valid number of input arguments if (nargin < 1) error ("gumbelpdf: too few input arguments."); endif ## Add defaults (if missing input arguments) if (nargin < 2) mu = 0; endif if (nargin < 3) beta = 1; endif ## Check for common size of X, MU, and BETA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (beta)) [err, x, mu, beta] = common_size (x, mu, beta); if (err > 0) error ("gumbelpdf: X, MU, and BETA must be of common size or scalars."); endif endif ## Check for X, MU, and BETA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (beta))) error ("gumbelpdf: X, MU, and BETA must be double or single."); endif ## Check for X, MU, and BETA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (beta)) error ("gumbelpdf: X, MU, and BETA must not be complex."); endif ## Return NaNs for out of range parameters beta(beta <= 0) = NaN; ## Compute pdf of type 1 extreme value distribution z = -(x - mu) ./ beta; y = exp (z - exp (z)) ./ beta; ## Force 0 for extreme right tail, instead of getting exp (Inf - Inf) = NaN y(z == Inf) = 0; endfunction %!demo %! ## Plot various PDFs from the Extreme value distribution %! x = -5:0.001:20; %! y1 = gumbelpdf (x, 0.5, 2); %! y2 = gumbelpdf (x, 1.0, 2); %! y3 = gumbelpdf (x, 1.5, 3); %! y4 = gumbelpdf (x, 3.0, 4); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c') %! grid on %! ylim ([0, 0.2]) %! legend ({'μ = 0.5, β = 2', 'μ = 1.0, β = 2', ... %! 'μ = 1.5, β = 3', 'μ = 3.0, β = 4'}, 'location', 'northeast') %! title ('Extreme value PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y0, y1 %! x = [-5, 0, 1, 2, 3]; %! y0 = [0, 0.3679, 0.2547, 0.1182, 0.0474]; %! y1 = [0, 0.1794, 0.3679, 0.2547, 0.1182]; %!assert_equal (gumbelpdf (x), y0, 1e-4) %!assert_equal (gumbelpdf (x, zeros (1,5), ones (1,5)), y0, 1e-4) %!assert_equal (gumbelpdf (x, ones (1,5), ones (1,5)), y1, 1e-4) ## Test input validation %!error gumbelpdf () %!error ... %! gumbelpdf (ones (3), ones (2), ones (2)) %!error gumbelpdf (int32 (2), 2, 2) %!error gumbelpdf (true, 2, 2) %!error gumbelpdf ('a', 2, 2) %!error gumbelpdf (i, 2, 2) %!error gumbelpdf (2, i, 2) %!error gumbelpdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/gumbelrnd.m000066400000000000000000000157531524624707500252030ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} gumbelrnd (@var{mu}, @var{beta}) ## @deftypefnx {statistics} {@var{r} =} gumbelrnd (@var{mu}, @var{beta}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} gumbelrnd (@var{mu}, @var{beta}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} gumbelrnd (@var{mu}, @var{beta}, [@var{sz}]) ## ## Random arrays from the Gumbel distribution. ## ## @code{@var{r} = gumbelrnd (@var{mu}, @var{beta})} returns an array of random ## numbers chosen from the Gumbel distribution (also known as the extreme value ## or the type I generalized extreme value distribution) with location ## parameter @var{mu} and scale parameter @var{beta}. The size of @var{r} is ## the common size of @var{mu} and @var{beta}. A scalar input functions as a ## constant matrix of the same size as the other inputs. ## ## When called with a single size argument, @code{gumbelrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## The Gumbel distribution is used to model the distribution of the maximum (or ## the minimum) of a number of samples of various distributions. This version ## is suitable for modeling maxima. For modeling minima, use the alternative ## extreme value iCDF, @code{evinv}. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## @seealso{gumbelcdf, gumbelinv, gumbelpdf, gumbelfit, gumbellike, gumbelstat, ## evrnd} ## @end deftypefn function r = gumbelrnd (mu, beta, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("gumbelrnd: function called with too few input arguments."); endif ## Check for common size of MU and BETA if (! isscalar (mu) || ! isscalar (beta)) [retval, mu, beta] = common_size (mu, beta); if (retval > 0) error ("gumbelrnd: MU and BETA must be of common size or scalars."); endif endif ## Check for MU and BETA being reals if (iscomplex (mu) || iscomplex (beta)) error ("gumbelrnd: MU and BETA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (mu); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("gumbelrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("gumbelrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (mu) && ! isequal (size (mu), size (ones (sz)))) error ("gumbelrnd: MU and BETA must be scalars or of size SZ."); endif ## Check for class type if (isa (mu, 'single') || isa (beta, 'single')) cls = 'single'; else cls = 'double'; endif ## Return NaNs for out of range values of BETA beta(beta < 0) = NaN; ## Generate uniform random values, and apply the extreme value inverse CDF. r = -log (-log (rand (sz, cls))) .* beta + mu; endfunction ## Test output %!assert_equal (size (gumbelrnd (1, 1)), [1 1]) %!assert_equal (size (gumbelrnd (1, ones (2,1))), [2, 1]) %!assert_equal (size (gumbelrnd (1, ones (2,2))), [2, 2]) %!assert_equal (size (gumbelrnd (ones (2,1), 1)), [2, 1]) %!assert_equal (size (gumbelrnd (ones (2,2), 1)), [2, 2]) %!assert_equal (size (gumbelrnd (1, 1, 3)), [3, 3]) %!assert_equal (size (gumbelrnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (gumbelrnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (gumbelrnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (gumbelrnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (gumbelrnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (gumbelrnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (gumbelrnd (1, 1, [])), [0, 0]) %!assert_equal (size (gumbelrnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (gumbelrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (gumbelrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (gumbelrnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (gumbelrnd (1, 1)), "double") %!assert_equal (class (gumbelrnd (1, single (1))), "single") %!assert_equal (class (gumbelrnd (1, single ([1, 1]))), "single") %!assert_equal (class (gumbelrnd (single (1), 1)), "single") %!assert_equal (class (gumbelrnd (single ([1, 1]), 1)), "single") ## Test input validation %!error gumbelrnd () %!error gumbelrnd (1) %!error ... %! gumbelrnd (ones (3), ones (2)) %!error ... %! gumbelrnd (ones (2), ones (3)) %!error gumbelrnd (i, 2, 3) %!error gumbelrnd (1, i, 3) %!error ... %! gumbelrnd (1, 2, 1.2) %!error ... %! gumbelrnd (1, 2, ones (2)) %!error ... %! gumbelrnd (1, 2, [2 0 2.5]) %!error ... %! gumbelrnd (1, 2, 2, 1.5, 5) %!error ... %! gumbelrnd (2, ones (2), 3) %!error ... %! gumbelrnd (2, ones (2), [3, 2]) %!error ... %! gumbelrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/hncdf.m000066400000000000000000000154501524624707500243000ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} hncdf (@var{x}, @var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{p} =} hncdf (@var{x}, @var{mu}, @var{sigma}, @qcode{'upper'}) ## ## Half-normal cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the half-normal distribution with location parameter @var{mu} and ## scale parameter @var{sigma}. The size of @var{p} is the common size of ## @var{x}, @var{mu} and @var{sigma}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## @code{[@dots{}] = hncdf (@var{x}, @var{mu}, @var{sigma}, "upper")} computes ## the upper tail probability of the half-normal distribution with parameters ## @var{mu} and @var{sigma}, at the values in @var{x}. ## ## The half-normal CDF is only defined for @qcode{@var{x} >= @var{mu}}. ## ## Further information about the half-normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Half-normal_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{hninv, hnpdf, hnrnd, hnfit, hnlike, hnstat} ## @end deftypefn function p = hncdf (x, mu, sigma, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("hncdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 3) if (! strcmpi (uflag, 'upper')) error ("hncdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, MU, and SIGMA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (sigma)) [err, x, mu, sigma] = common_size (x, mu, sigma); if (err > 0) error ("hncdf: X, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("hncdf: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma)) error ("hncdf: X, MU, and SIGMA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single')) is_class = 'single'; else is_class = 'double'; endif ## Prepare output p = zeros (size (x), is_class); ## Return NaNs for out of range values of SIGMA parameter sigma(sigma <= 0) = NaN; ## Calculate (x-mu)/sigma => 0 and force zero below that z = (x - mu) ./ sigma; z(z < 0) = 0; if (uflag) p = erfc (z./sqrt (2)); else p = erf (z./sqrt (2)); endif endfunction %!demo %! ## Plot various CDFs from the half-normal distribution %! x = 0:0.001:10; %! p1 = hncdf (x, 0, 1); %! p2 = hncdf (x, 0, 2); %! p3 = hncdf (x, 0, 3); %! p4 = hncdf (x, 0, 5); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c') %! grid on %! xlim ([0, 10]) %! legend ({'μ = 0, σ = 1', 'μ = 0, σ = 2', ... %! 'μ = 0, σ = 3', 'μ = 0, σ = 5'}, 'location', 'southeast') %! title ('Half-normal CDF') %! xlabel ('values in x') %! ylabel ('probability') %!demo %! ## Plot half-normal against normal cumulative distribution function %! x = -5:0.001:5; %! p1 = hncdf (x, 0, 1); %! p2 = normcdf (x); %! plot (x, p1, '-b', x, p2, '-g') %! grid on %! xlim ([-5, 5]) %! legend ({'half-normal with μ = 0, σ = 1', ... %! 'standard normal (μ = 0, σ = 1)'}, 'location', 'southeast') %! title ('Half-normal against standard normal CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, p1, p1u, y2, y2u, y3, y3u %! x = [-Inf, -1, 0, 1/2, 1, Inf]; %! p1 = [0, 0, 0, 0.3829, 0.6827, 1]; %! p1u = [1, 1, 1, 0.6171, 0.3173, 0]; %!assert_equal (hncdf (x, zeros (1,6), ones (1,6)), p1, 1e-4) %!assert_equal (hncdf (x, 0, 1), p1, 1e-4) %!assert_equal (hncdf (x, 0, ones (1,6)), p1, 1e-4) %!assert_equal (hncdf (x, zeros (1,6), 1), p1, 1e-4) %!assert_equal (hncdf (x, 0, [1, 1, 1, NaN, 1, 1]), [p1(1:3), NaN, p1(5:6)], 1e-4) %!assert_equal (hncdf (x, [0, 0, 0, NaN, 0, 0], 1), [p1(1:3), NaN, p1(5:6)], 1e-4) %!assert_equal (hncdf ([x(1:3), NaN, x(5:6)], 0, 1), [p1(1:3), NaN, p1(5:6)], 1e-4) %!assert_equal (hncdf (x, zeros (1,6), ones (1,6), 'upper'), p1u, 1e-4) %!assert_equal (hncdf (x, 0, 1, 'upper'), p1u, 1e-4) %!assert_equal (hncdf (x, 0, ones (1,6), 'upper'), p1u, 1e-4) %!assert_equal (hncdf (x, zeros (1,6), 1, 'upper'), p1u, 1e-4) ## Test class of input preserved %!assert_equal (class (hncdf (single ([x, NaN]), 0, 1)), "single") %!assert_equal (class (hncdf ([x, NaN], 0, single (1))), "single") %!assert_equal (class (hncdf ([x, NaN], single (0), 1)), "single") ## Test input validation %!error hncdf () %!error hncdf (1) %!error hncdf (1, 2) %!error hncdf (1, 2, 3, 'tail') %!error hncdf (1, 2, 3, 5) %!error ... %! hncdf (ones (3), ones (2), ones (2)) %!error ... %! hncdf (ones (2), ones (3), ones (2)) %!error ... %! hncdf (ones (2), ones (2), ones (3)) %!error hncdf (int32 (2), 2, 3) %!error hncdf (true, 2, 3) %!error hncdf ('a', 2, 3) %!error hncdf (i, 2, 3) %!error hncdf (1, i, 3) %!error hncdf (1, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/hninv.m000066400000000000000000000116701524624707500243400ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} hninv (@var{p}, @var{mu}, @var{sigma}) ## ## Inverse of the half-normal cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the half-normal distribution with location parameter @var{mu} and scale ## parameter @var{sigma}. The size of @var{x} is the common size of @var{p}, ## @var{mu}, and @var{sigma}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Further information about the half-normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Half-normal_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{hncdf, hnpdf, hnrnd, hnfit, hnlike, hnstat} ## @end deftypefn function x = hninv (p, mu, sigma) ## Check for valid number of input arguments if (nargin < 3) error ("hninv: function called with too few input arguments."); endif ## Check for common size of P, MU, and SIGMA if (! isscalar (p) || ! isscalar (mu) || ! isscalar (sigma)) [retval, p, mu, sigma] = common_size (p, mu, sigma); if (retval > 0) error ("hninv: P, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for P, MU, and SIGMA being double or single if (! (isfloat (p) && isfloat (mu) && isfloat (sigma))) error ("hninv: P, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (p) || iscomplex (mu) || iscomplex (sigma)) error ("hninv: P, MU, and SIGMA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (mu, 'single') || isa (sigma, 'single')); x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ## Return NaNs for out of range values of SIGMA parameter sigma(sigma <= 0) = NaN; ## Return NaNs for out of range P values p(p < 0 | 1 < p) = NaN; ## Calculate the quantile of half-normal distribution x = sqrt (2) * sigma .* erfinv (p) + mu; endfunction %!demo %! ## Plot various iCDFs from the half-normal distribution %! p = 0.001:0.001:0.999; %! x1 = hninv (p, 0, 1); %! x2 = hninv (p, 0, 2); %! x3 = hninv (p, 0, 3); %! x4 = hninv (p, 0, 5); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c') %! grid on %! ylim ([0, 10]) %! legend ({'μ = 0, σ = 1', 'μ = 0, σ = 2', ... %! 'μ = 0, σ = 3', 'μ = 0, σ = 5'}, 'location', 'northwest') %! title ('Half-normal iCDF') %! xlabel ('probability') %! ylabel ('x') ## Test output %!shared p, x %! p = [0, 0.3829, 0.6827, 1]; %! x = [0, 1/2, 1, Inf]; %!assert_equal (hninv (p, 0, 1), x, 1e-4); %!assert_equal (hninv (p, 5, 1), x + 5, 1e-4); %!assert_equal (hninv (p, 0, ones (1,4)), x, 1e-4); %!assert_equal (hninv (p, 0, [-1, 0, 1, 1]), [NaN, NaN, x(3:4)], 1e-4) ## Test class of input preserved %!assert_equal (class (hninv (single ([p, NaN]), 0, 1)), "single") %!assert_equal (class (hninv ([p, NaN], single (0), 1)), "single") %!assert_equal (class (hninv ([p, NaN], 0, single (1))), "single") ## Test input validation %!error hninv (1) %!error hninv (1, 2) %!error ... %! hninv (1, ones (2), ones (3)) %!error ... %! hninv (ones (2), 1, ones (3)) %!error ... %! hninv (ones (2), ones (3), 1) %!error hninv (int32 (2), 2, 3) %!error hninv (true, 2, 3) %!error hninv ('a', 2, 3) %!error hninv (i, 2, 3) %!error hninv (1, i, 3) %!error hninv (1, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/hnpdf.m000066400000000000000000000127051524624707500243150ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} hnpdf (@var{x}, @var{mu}, @var{sigma}) ## ## Half-normal probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the half-normal distribution with location parameter @var{mu} and scale ## parameter @var{sigma}. The size of @var{y} is the common size of @var{x}, ## @var{mu}, and @var{sigma}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## The half-normal CDF is only defined for @qcode{@var{x} >= @var{mu}}. ## ## Further information about the half-normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Half-normal_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{hncdf, hninv, hnrnd, hnfit, hnlike, hnstat} ## @end deftypefn function y = hnpdf (x, mu, sigma) ## Check for valid number of input arguments if (nargin < 3) error ("hnpdf: function called with too few input arguments."); endif ## Check for common size of X, MU, and SIGMA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (sigma)) [retval, x, mu, sigma] = common_size (x, mu, sigma); if (retval > 0) error ("hnpdf: X, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("hnpdf: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma)) error ("hnpdf: X, MU, and SIGMA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single')); y = NaN (size (x), 'single'); else y = NaN (size (x)); endif ## Return NaNs for out of range values of SIGMA parameter sigma(sigma <= 0) = NaN; ## Compute half-normal PDF z = (x - mu) ./ sigma; y = sqrt (2 / pi) ./ sigma .* exp (-0.5 * z .^ 2); ## Force zero for unsupported X y(z < 0) = 0; endfunction %!demo %! ## Plot various PDFs from the half-normal distribution %! x = 0:0.001:10; %! y1 = hnpdf (x, 0, 1); %! y2 = hnpdf (x, 0, 2); %! y3 = hnpdf (x, 0, 3); %! y4 = hnpdf (x, 0, 5); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c') %! grid on %! xlim ([0, 10]) %! ylim ([0, 0.9]) %! legend ({'μ = 0, σ = 1', 'μ = 0, σ = 2', ... %! 'μ = 0, σ = 3', 'μ = 0, σ = 5'}, 'location', 'northeast') %! title ('Half-normal PDF') %! xlabel ('values in x') %! ylabel ('density') %!demo %! ## Plot half-normal against normal probability density function %! x = -5:0.001:5; %! y1 = hnpdf (x, 0, 1); %! y2 = normpdf (x); %! plot (x, y1, '-b', x, y2, '-g') %! grid on %! xlim ([-5, 5]) %! ylim ([0, 0.9]) %! legend ({'half-normal with μ = 0, σ = 1', ... %! 'standard normal (μ = 0, σ = 1)'}, 'location', 'northeast') %! title ('Half-normal against standard normal PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-Inf, -1, 0, 1/2, 1, Inf]; %! y = [0, 0, 0.7979, 0.7041, 0.4839, 0]; %!assert_equal (hnpdf ([x, NaN], 0, 1), [y, NaN], 1e-4) %!assert_equal (hnpdf (x, 0, [-2, -1, 0, 1, 1, 1]), [nan(1,3), y([4:6])], 1e-4) ## Test class of input preserved %!assert_equal (class (hncdf (single ([x, NaN]), 0, 1)), "single") %!assert_equal (class (hncdf ([x, NaN], 0, single (1))), "single") %!assert_equal (class (hncdf ([x, NaN], single (0), 1)), "single") ## Test input validation %!error hnpdf () %!error hnpdf (1) %!error hnpdf (1, 2) %!error ... %! hnpdf (1, ones (2), ones (3)) %!error ... %! hnpdf (ones (2), 1, ones (3)) %!error ... %! hnpdf (ones (2), ones (3), 1) %!error hnpdf (int32 (2), 2, 3) %!error hnpdf (true, 2, 3) %!error hnpdf ('a', 2, 3) %!error hnpdf (i, 2, 3) %!error hnpdf (1, i, 3) %!error hnpdf (1, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/hnrnd.m000066400000000000000000000150341524624707500243250ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} hnrnd (@var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{r} =} hnrnd (@var{mu}, @var{sigma}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} hnrnd (@var{mu}, @var{sigma}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} hnrnd (@var{mu}, @var{sigma}, [@var{sz}]) ## ## Random arrays from the half-normal distribution. ## ## @code{@var{r} = hnrnd (@var{mu}, @var{sigma})} returns an array of random ## numbers chosen from the half-normal distribution with location parameter ## @var{mu} and scale parameter @var{sigma}. The size of @var{r} is the common ## size of @var{mu} and @var{sigma}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## When called with a single size argument, @code{hnrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the half-normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Half-normal_distribution} ## ## @seealso{hncdf, hninv, hnpdf, hnfit, hnlike, hnstat} ## @end deftypefn function r = hnrnd (mu, sigma, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("hnrnd: function called with too few input arguments."); endif ## Check for common size of MU, and SIGMA if (! isscalar (mu) || ! isscalar (sigma)) [retval, mu, sigma] = common_size (mu, sigma); if (retval > 0) error ("hnrnd: MU and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being reals if (iscomplex (mu) || iscomplex (sigma)) error ("hnrnd: MU and SIGMA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (mu); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("hnrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("hnrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (mu) && ! isequal (size (mu), size (ones (sz)))) error ("hnrnd: MU and SIGMA must be scalars or of size SZ."); endif ## Check for class type if (isa (mu, 'single') || isa (sigma, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from half-normal distribution r = abs (randn (sz, cls)) .* sigma + mu; ## Force output to NaN for invalid parameter SIGMA <= 0 k = (sigma <= 0); r(k) = NaN; endfunction ## Test output %!assert_equal (size (hnrnd (1, 1, 1)), [1, 1]) %!assert_equal (size (hnrnd (1, 1, 2)), [2, 2]) %!assert_equal (size (hnrnd (1, 1, [2, 1])), [2, 1]) %!assert_equal (size (hnrnd (1, zeros (2, 2))), [2, 2]) %!assert_equal (size (hnrnd (1, ones (2, 1))), [2, 1]) %!assert_equal (size (hnrnd (1, ones (2, 2))), [2, 2]) %!assert_equal (size (hnrnd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (hnrnd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (hnrnd (1, 1, 3)), [3, 3]) %!assert_equal (size (hnrnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (hnrnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (hnrnd (1, 1, [])), [0, 0]) %!assert_equal (size (hnrnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (hnrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (hnrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (hnrnd (1, 2, 2, -1, 5)), [2, 0, 5]) %!test %! r = hnrnd (1, [1, 0, -1]); %! assert_equal (r([2:3]), [NaN, NaN]) ## Test class of input preserved %!assert_equal (class (hnrnd (1, 0)), "double") %!assert_equal (class (hnrnd (1, single (0))), "single") %!assert_equal (class (hnrnd (1, single ([0, 0]))), "single") %!assert_equal (class (hnrnd (1, single (1))), "single") %!assert_equal (class (hnrnd (1, single ([1, 1]))), "single") %!assert_equal (class (hnrnd (single (1), 1)), "single") %!assert_equal (class (hnrnd (single ([1, 1]), 1)), "single") ## Test input validation %!error hnrnd () %!error hnrnd (1) %!error ... %! hnrnd (ones (3), ones (2)) %!error ... %! hnrnd (ones (2), ones (3)) %!error hnrnd (i, 2, 3) %!error hnrnd (1, i, 3) %!error ... %! hnrnd (1, 2, 1.2) %!error ... %! hnrnd (1, 2, ones (2)) %!error ... %! hnrnd (1, 2, [2 0 2.5]) %!error ... %! hnrnd (1, 2, 2, 1.5, 5) %!error ... %! hnrnd (2, ones (2), 3) %!error ... %! hnrnd (2, ones (2), [3, 2]) %!error ... %! hnrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/hygecdf.m000066400000000000000000000227231524624707500246300ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1997-2016 Kurt Hornik ## Copyright (C) 2022 Nicholas R. Jankowski ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} hygecdf (@var{x}, @var{m}, @var{k}, @var{n}) ## @deftypefnx {statistics} {@var{p} =} hygecdf (@var{x}, @var{m}, @var{k}, @var{n}, @qcode{'upper'}) ## ## Hypergeometric cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the hypergeometric distribution with parameters @var{m}, @var{k}, ## and @var{n}. The size of @var{p} is the common size of @var{x}, @var{m}, ## @var{k}, and @var{n}. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## This is the cumulative probability of obtaining not more than @var{x} marked ## items when randomly drawing a sample of size @var{n} without replacement from ## a population of total size @var{m} containing @var{k} marked items. The ## parameters @var{m}, @var{k}, and @var{n} must be positive integers with ## @var{k} and @var{n} not greater than @var{m}. ## ## @code{[@dots{}] = hygecdf (@var{x}, @var{m}, @var{k}, @var{n}, "upper")} ## computes the upper tail probability of the hypergeometric distribution with ## parameters @var{m}, @var{k}, and @var{n}, at the values in @var{x}. ## ## Further information about the hypergeometric distribution can be found at ## @url{https://en.wikipedia.org/wiki/Hypergeometric_distribution} ## ## Input arguments must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted to ## @qcode{double}, so the result is always a probability. MATLAB is ## inconsistent here: for several of the discrete distributions it returns the ## result in the integer class of the input, truncating a probability to ## @math{0} or @math{1}. ## ## @seealso{hygeinv, hygepdf, hygernd, hygestat} ## @end deftypefn function p = hygecdf (x, m, k, n, uflag) ## Check for valid number of input arguments if (nargin < 4) error ("hygecdf: function called with too few input arguments."); endif ## Check for common size of X, T, M, and N if (! isscalar (x) || ! isscalar (m) || ! isscalar (k) || ! isscalar (n)) [retval, x, m, k, n] = common_size (x, m, k, n); if (retval > 0) error ("hygecdf: X, T, M, and N must be of common size or scalars."); endif endif ## Check for X, T, M, and N being double, single, or integer if (! (isnumeric (x) && isnumeric (m) && isnumeric (k) && isnumeric (n))) error ("hygecdf: X, T, M, and N must be double, single, or integer."); endif ## Integer input is promoted to double, so the result is a probability ## rather than a value truncated to the input's integer type. if (isinteger (x)) x = double (x); endif if (isinteger (m)) m = double (m); endif if (isinteger (k)) k = double (k); endif if (isinteger (n)) n = double (n); endif ## Check for X, T, M, and N being reals if (iscomplex (x) || iscomplex (m) || iscomplex (k) || iscomplex (n)) error ("hygecdf: X, T, M, and N must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (m, 'single') || isa (k, 'single') || isa (n, 'single')) p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Check for "upper" flag if (nargin > 4 && strcmpi (uflag, 'upper')) x = n - floor (x) - 1; k = m - k; elseif (nargin > 4 && ! strcmpi (uflag, 'upper')) error ("hygecdf: invalid argument for upper tail."); endif ## Force 1 where required is_1 = (x >= n | x >= k); p(is_1) = 1; ## Force NaNs where required is_nan = (isnan (x) | isnan (m) | isnan (k) | isnan (n) | ... m < 0 | k < 0 | n < 0 | round (m) != m | round (k) != k | ... round (n) != n | n > m | k > m); p(is_nan) = NaN; ## Get values for which P = 0 is_0 = (m - k - n + x + 1 <= 0 | x < 0); ok = ! (is_1 | is_nan | is_0); ## Compute hypergeometric CDF if (any (ok(:))) ## For improved accuracy, compute the upper tail 1-p instead ## of the lower tail pfor x values that are larger than the mean lo = (x <= k .* n ./ m); ok_lo = ok & lo; if (any (ok_lo(:))) p(ok_lo) = localPDF (floor (x(ok_lo)), m(ok_lo), k(ok_lo), n(ok_lo)); endif ok_hi = ok & ! lo; if (any (ok_hi(:))) p(ok_hi) = 1 - localPDF (n(ok_hi) - floor (x(ok_hi)) - 1, ... m(ok_hi), m(ok_hi) - k(ok_hi), n(ok_hi)); endif endif endfunction function p = localPDF (x, m, k, n) HPDF = hygepdf (x, m, k, n); ## Compute hygecdf(x,m,k,n)/hygepdf(x,m,k,n) with a series ## whose terms can be computed recursively, backwards. xmax = max (x(:)); ybig = repmat ((0:xmax)', 1, length (x)); xbig = repmat (x(:)', xmax + 1, 1); mbig = repmat (m(:)', xmax + 1, 1); kbig = repmat (k(:)', xmax + 1, 1); nbig = repmat (n(:)', xmax + 1, 1); terms = ((ybig+1) .* (mbig-kbig-nbig+ybig+1)) ./ ((nbig-ybig) .* (kbig-ybig)); terms(ybig >= xbig) = 1; terms = flip (cumprod (flip (terms))); terms(ybig > xbig) = 0; ratio = sum (terms,1); ratio = reshape (ratio,size (x)); p = ratio.*HPDF; ## Correct round-off errors p(p > 1) = 1; endfunction %!demo %! ## Plot various CDFs from the hypergeometric distribution %! x = 0:60; %! p1 = hygecdf (x, 500, 50, 100); %! p2 = hygecdf (x, 500, 60, 200); %! p3 = hygecdf (x, 500, 70, 300); %! plot (x, p1, '*b', x, p2, '*g', x, p3, '*r') %! grid on %! xlim ([0, 60]) %! legend ({'m = 500, k = 50, n = 100', 'm = 500, k = 60, n = 200', ... %! 'm = 500, k = 70, n = 300'}, 'location', 'southeast') %! title ('Hypergeometric CDF') %! xlabel ('values in x (number of successes)') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-1 0 1 2 3]; %! y = [0 1/6 5/6 1 1]; %!assert_equal (hygecdf (x, 4*ones (1,5), 2, 2), y, 5*eps) %!assert_equal (hygecdf (x, 4, 2*ones (1,5), 2), y, 5*eps) %!assert_equal (hygecdf (x, 4, 2, 2*ones (1,5)), y, 5*eps) %!assert_equal (hygecdf (x, 4*[1 -1 NaN 1.1 1], 2, 2), [y(1) NaN NaN NaN y(5)], 5*eps) %!assert_equal (hygecdf (x, 4*[1 -1 NaN 1.1 1], 2, 2, 'upper'), ... %! [y(5) NaN NaN NaN y(1)], 5*eps) %!assert_equal (hygecdf (x, 4, 2*[1 -1 NaN 1.1 1], 2), [y(1) NaN NaN NaN y(5)], 5*eps) %!assert_equal (hygecdf (x, 4, 2*[1 -1 NaN 1.1 1], 2, 'upper'), ... %! [y(5) NaN NaN NaN y(1)], 5*eps) %!assert_equal (hygecdf (x, 4, 5, 2), [NaN NaN NaN NaN NaN]) %!assert_equal (hygecdf (x, 4, 2, 2*[1 -1 NaN 1.1 1]), [y(1) NaN NaN NaN y(5)], 5*eps) %!assert_equal (hygecdf (x, 4, 2, 2*[1 -1 NaN 1.1 1], 'upper'), ... %! [y(5) NaN NaN NaN y(1)], 5*eps) %!assert_equal (hygecdf (x, 4, 2, 5), [NaN NaN NaN NaN NaN]) %!assert_equal (hygecdf ([x(1:2) NaN x(4:5)], 4, 2, 2), [y(1:2) NaN y(4:5)], 5*eps) %!test %! p = hygecdf (x, 10, [1 2 3 4 5], 2, 'upper'); %! assert_equal (p, [1, 34/90, 2/30, 0, 0], 10*eps); %!test %! p = hygecdf (2*x, 10, [1 2 3 4 5], 2, 'upper'); %! assert_equal (p, [1, 34/90, 0, 0, 0], 10*eps); ## Test class of input preserved %!assert_equal (hygecdf ([x, NaN], 4, 2, 2), [y, NaN], 5*eps) %!assert_equal (hygecdf (single ([x, NaN]), 4, 2, 2), single ([y, NaN]), ... %! eps ('single')) %!assert_equal (hygecdf ([x, NaN], single (4), 2, 2), single ([y, NaN]), ... %! eps ('single')) %!assert_equal (hygecdf ([x, NaN], 4, single (2), 2), single ([y, NaN]), ... %! eps ('single')) %!assert_equal (hygecdf ([x, NaN], 4, 2, single (2)), single ([y, NaN]), ... %! eps ('single')) ## Test input validation %!error hygecdf () %!error hygecdf (1) %!error hygecdf (1,2) %!error hygecdf (1,2,3) %!error hygecdf (1,2,3,4,5) %!error hygecdf (1,2,3,4,'uper') %!error ... %! hygecdf (ones (2), ones (3), 1, 1) %!error ... %! hygecdf (1, ones (2), ones (3), 1) %!error ... %! hygecdf (1, 1, ones (2), ones (3)) %!error hygecdf (true, 2, 2, 2) %!error hygecdf ('a', 2, 2, 2) %!assert_equal (class (hygecdf (int32 (2), 2, 2, 2)), 'double') %!error hygecdf (i, 2, 2, 2) %!error hygecdf (2, i, 2, 2) %!error hygecdf (2, 2, i, 2) %!error hygecdf (2, 2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/hygeinv.m000066400000000000000000000170671524624707500246750ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1997-2016 Kurt Hornik ## Copyright (C) 2022 Nicholas R. Jankowski ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} hygeinv (@var{p}, @var{m}, @var{k}, @var{n}) ## ## Inverse of the hypergeometric cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the hypergeometric distribution with parameters @var{m}, @var{k}, and ## @var{n}. The size of @var{x} is the common size of @var{p}, @var{m}, @var{k}, ## and ## @var{n}. A scalar input functions as a constant matrix of the same size as ## the other inputs. ## ## This is the number of drawn marked items @var{x} given a probability @var{p}, ## when randomly drawing a sample of size @var{n} without replacement from a ## population of total size @var{m} containing @var{k} marked items. The ## parameters @var{m}, @var{k}, and @var{n} must be positive integers with ## @var{k} and @var{n} not greater than @var{m}. ## ## Further information about the hypergeometric distribution can be found at ## @url{https://en.wikipedia.org/wiki/Hypergeometric_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{hygecdf, hygepdf, hygernd, hygestat} ## @end deftypefn function x = hygeinv (p, m, k, n) ## Check for valid number of input arguments if (nargin < 4) error ("hygeinv: function called with too few input arguments."); endif ## Check for common size of P, T, M, and N if (! isscalar (p) || ! isscalar (m) || ! isscalar (k) || ! isscalar (n)) [retval, p, m, k, n] = common_size (p, m, k, n); if (retval > 0) error ("hygeinv: P, T, M, and N must be of common size or scalars."); endif endif ## Check for P, T, M, and N being double or single if (! (isfloat (p) && isfloat (m) && isfloat (k) && isfloat (n))) error ("hygeinv: P, T, M, and N must be double or single."); endif ## Check for P, T, M, and N being reals if (iscomplex (p) || iscomplex (m) || iscomplex (k) || iscomplex (n)) error ("hygeinv: P, T, M, and N must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (m, 'single') || isa (k, 'single') || isa (n, 'single')) x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ok = ((m >= 0) & (k >= 0) & (n > 0) & (k <= m) & (n <= m) & (m == fix (m)) & (k == fix (k)) & (n == fix (n))); if (isscalar (m)) if (ok) x = discrete_inv (p, 0 : n, hygepdf (0 : n, m, k, n)); x(p == 0) = 0; # Hack to return correct value for start of distribution endif else p_0 = (p == 0); x(ok & p_0) = 0; # set any p=0 to 0 if not already set to output NaN p_0 = (p == 1); x(ok & p_0) = n(ok & p_0); ok &= (p>0 & p<1); # remove 0's and p's outside (0,1), leave unfilled as NaN if (any (ok(:))) n = n(ok); v = 0 : max (n(:)); ## Manually perform discrete_inv to enable vectorizing with array input p_tmp = cumsum (hygepdf (v, m(ok), k(ok), n, 'vectorexpand'), 2); sz_p = size (p_tmp); end_locs = sub2ind (sz_p, [1 : numel(n)]', n(:) + 1); ## Manual row-wise vectorization of lookup, which returns index of element ## less than or equal to test value, zero if test value less than lowest ## number, and max index if greater than highest number. operated on ## flipped p_tmp, adjusting for different vector lengths in array rows. p_tmp = (p_tmp ./ p_tmp(end_locs))(:, end:-1:1) - p(ok)(:); p_tmp(p_tmp>=0) = NaN; [p_match, p_match_idx] = max (p_tmp, [], 2); p_match_idx(isnan (p_match)) = v(end) + 2; x(ok) = v(v(end) - p_match_idx + 3); endif endif endfunction %!demo %! ## Plot various iCDFs from the hypergeometric distribution %! p = 0.001:0.001:0.999; %! x1 = hygeinv (p, 500, 50, 100); %! x2 = hygeinv (p, 500, 60, 200); %! x3 = hygeinv (p, 500, 70, 300); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r') %! grid on %! ylim ([0, 60]) %! legend ({'m = 500, k = 50, n = 100', 'm = 500, k = 60, n = 200', ... %! 'm = 500, k = 70, n = 300'}, 'location', 'northwest') %! title ('Hypergeometric iCDF') %! xlabel ('probability') %! ylabel ('values in p (number of successes)') ## Test output %!shared p %! p = [-1 0 0.5 1 2]; %!assert_equal (hygeinv (p, 4*ones (1,5), 2*ones (1,5), 2*ones (1,5)), [NaN 0 1 2 NaN]) %!assert_equal (hygeinv (p, 4*ones (1,5), 2, 2), [NaN 0 1 2 NaN]) %!assert_equal (hygeinv (p, 4, 2*ones (1,5), 2), [NaN 0 1 2 NaN]) %!assert_equal (hygeinv (p, 4, 2, 2*ones (1,5)), [NaN 0 1 2 NaN]) %!assert_equal (hygeinv (p, 4*[1 -1 NaN 1.1 1], 2, 2), [NaN NaN NaN NaN NaN]) %!assert_equal (hygeinv (p, 4, 2*[1 -1 NaN 1.1 1], 2), [NaN NaN NaN NaN NaN]) %!assert_equal (hygeinv (p, 4, 5, 2), [NaN NaN NaN NaN NaN]) %!assert_equal (hygeinv (p, 4, 2, 2*[1 -1 NaN 1.1 1]), [NaN NaN NaN NaN NaN]) %!assert_equal (hygeinv (p, 4, 2, 5), [NaN NaN NaN NaN NaN]) %!assert_equal (hygeinv ([p(1:2) NaN p(4:5)], 4, 2, 2), [NaN 0 NaN 2 NaN]) ## Test class of input preserved %!assert_equal (hygeinv ([p, NaN], 4, 2, 2), [NaN 0 1 2 NaN NaN]) %!assert_equal (hygeinv (single ([p, NaN]), 4, 2, 2), single ([NaN 0 1 2 NaN NaN])) %!assert_equal (hygeinv ([p, NaN], single (4), 2, 2), single ([NaN 0 1 2 NaN NaN])) %!assert_equal (hygeinv ([p, NaN], 4, single (2), 2), single ([NaN 0 1 2 NaN NaN])) %!assert_equal (hygeinv ([p, NaN], 4, 2, single (2)), single ([NaN 0 1 2 NaN NaN])) ## Test input validation %!error hygeinv () %!error hygeinv (1) %!error hygeinv (1,2) %!error hygeinv (1,2,3) %!error ... %! hygeinv (ones (2), ones (3), 1, 1) %!error ... %! hygeinv (1, ones (2), ones (3), 1) %!error ... %! hygeinv (1, 1, ones (2), ones (3)) %!error hygeinv (int32 (2), 2, 2, 2) %!error hygeinv (true, 2, 2, 2) %!error hygeinv ('a', 2, 2, 2) %!error hygeinv (i, 2, 2, 2) %!error hygeinv (2, i, 2, 2) %!error hygeinv (2, 2, i, 2) %!error hygeinv (2, 2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/hygepdf.m000066400000000000000000000232331524624707500246420ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1996-2016 Kurt Hornik ## Copyright (C) 2022 Nicholas R. Jankowski ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} hygepdf (@var{x}, @var{m}, @var{k}, @var{n}) ## @deftypefnx {statistics} {@var{y} =} hygepdf (@dots{}, @qcode{'vectorexpand'}) ## ## Hypergeometric probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the hypergeometric distribution with parameters @var{m}, @var{k}, and ## @var{n}. The size of @var{y} is the common size of @var{x}, @var{m}, ## @var{k}, and @var{n}. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## This is the probability of obtaining @var{x} marked items when randomly ## drawing a sample of size @var{n} without replacement from a population of ## total size @var{m} containing @var{k} marked items. The parameters @var{m}, ## @var{k}, and @var{n} must be positive integers with @var{k} and @var{n} not ## greater than @var{m}. ## ## If the optional parameter @qcode{vectorexpand} is provided, @var{x} may be an ## array with size different from parameters @var{m}, @var{k}, and @var{n} ## (which must still be of a common size or scalar). Each element of @var{x} ## will be evaluated against each set of parameters @var{m}, @var{k}, and ## @var{n} in columnwise order. The output @var{y} will be an array of size ## @qcode{@var{r} x @var{s}}, where @qcode{@var{r} = numel (@var{m})}, and ## @qcode{@var{s} = numel (@var{x})}. ## ## Further information about the hypergeometric distribution can be found at ## @url{https://en.wikipedia.org/wiki/Hypergeometric_distribution} ## ## Input arguments must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted to ## @qcode{double}, so the result is always a probability. MATLAB is ## inconsistent here: for several of the discrete distributions it returns the ## result in the integer class of the input, truncating a probability to ## @math{0} or @math{1}. ## ## @seealso{hygecdf, hygeinv, hygernd, hygestat} ## @end deftypefn function y = hygepdf (x, m, k, n, vect_expand) ## Check for valid number of input arguments if (nargin < 4) error ("hygepdf: function called with too few input arguments."); endif ## Check for X, T, M, and N being double, single, or integer if (! (isnumeric (x) && isnumeric (m) && isnumeric (k) && isnumeric (n))) error ("hygepdf: X, T, M, and N must be double, single, or integer."); endif ## Integer input is promoted to double, so the result is a probability ## rather than a value truncated to the input's integer type. if (isinteger (x)) x = double (x); endif if (isinteger (m)) m = double (m); endif if (isinteger (k)) k = double (k); endif if (isinteger (n)) n = double (n); endif ## Check for X, T, M, and N being reals if (iscomplex (x) || iscomplex (m) || iscomplex (k) || iscomplex (n)) error ("hygepdf: X, T, M, and N must not be complex."); endif ## Check for 5th argument or add default if (nargin < 5) vect_expand = []; endif if strcmpi (vect_expand, 'vectorexpand') ## Expansion to improve vectorization of hyge calling functions. ## Project inputs over a 2D array with x(:) as a row vector and m,k,n as ## a column vector. each y(i,j) is hygepdf(x(j), m(i), k(i), n(i)) ## Following expansion, remainder of algorithm processes as normal. if (! isscalar (m) || ! isscalar (k) || ! isscalar (n)) [retval, m, k, n] = common_size (m, k, n); if (retval > 0) error ("hygepdf: T, M, and N must be of common size or scalars."); endif ## Ensure col vectors before expansion m = m(:); k = k(:); n = n(:); endif ## Expand x,m,k,n to arrays of size numel(m) x numel(x) sz = [numel(m), numel(x)]; x = x(:)'; # ensure row vector before expansion x = x(ones (sz(1), 1), :); m = m(:, ones (sz(2), 1)); k = k(:, ones (sz(2), 1)); n = n(:, ones (sz(2), 1)); else ## Check for common size of X, T, M, and N if (! isscalar (m) || ! isscalar (k) || ! isscalar (n)) [retval, x, m, k, n] = common_size (x, m, k, n); if (retval > 0) error ("hygepdf: X, T, M, and N must be of common size or scalars."); endif endif sz = size (x); endif ## Check for class type if (isa (x, 'single') || isa (m, 'single') || isa (k, 'single') || isa (n, 'single')) y = zeros (sz, 'single'); else y = zeros (sz); endif ## Everything in nel gives NaN nel = (isnan (x) | (m < 0) | (k < 0) | (n <= 0) | (k > m) | (n > m) | (m != fix (m)) | (k != fix (k)) | (n != fix (n))); ## Everything in zel gives 0 unless in nel zel = ((x != fix (x)) | (x < 0) | (x > k) | (n < x) | (n-x > m-k)); y(nel) = NaN; ok = ! nel & ! zel; if (any (ok(:))) if (isscalar (m)) y(ok) = exp (gammaln (k+1) - gammaln (k-x(ok)+1) - gammaln (x(ok)+1) + ... gammaln (m-k+1) - gammaln (m-k-n+x(ok)+1) - ... gammaln (n-x(ok)+1) - gammaln (m+1) + gammaln (m-n+1) + ... gammaln (n+1)); else y(ok) = exp (gammaln (k(ok)+1) - gammaln (k(ok)-x(ok)+1) - ... gammaln (x(ok)+1) + gammaln (m(ok)-k(ok)+1) - ... gammaln (m(ok)-k(ok)-n(ok)+x(ok)+1) - ... gammaln (n(ok)-x(ok)+1) - gammaln (m(ok)+1) + ... gammaln (m(ok)-n(ok)+1) + gammaln (n(ok)+1)); endif endif endfunction %!demo %! ## Plot various PDFs from the hypergeometric distribution %! x = 0:60; %! y1 = hygepdf (x, 500, 50, 100); %! y2 = hygepdf (x, 500, 60, 200); %! y3 = hygepdf (x, 500, 70, 300); %! plot (x, y1, '*b', x, y2, '*g', x, y3, '*r') %! grid on %! xlim ([0, 60]) %! ylim ([0, 0.18]) %! legend ({'m = 500, k = 50, μ = 100', 'm = 500, k = 60, μ = 200', ... %! 'm = 500, k = 70, μ = 300'}, 'location', 'northeast') %! title ('Hypergeometric PDF') %! xlabel ('values in x (number of successes)') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1 0 1 2 3]; %! y = [0 1/6 4/6 1/6 0]; %!assert_equal (hygepdf (x, 4 * ones (1, 5), 2, 2), y, 3 * eps) %!assert_equal (hygepdf (x, 4, 2 * ones (1, 5), 2), y, 3 * eps) %!assert_equal (hygepdf (x, 4, 2, 2 * ones (1, 5)), y, 3 * eps) %!assert_equal (hygepdf (x, 4 * [1, -1, NaN, 1.1, 1], 2, 2), [0, NaN, NaN, NaN, 0]) %!assert_equal (hygepdf (x, 4, 2 * [1, -1, NaN, 1.1, 1], 2), [0, NaN, NaN, NaN, 0]) %!assert_equal (hygepdf (x, 4, 5, 2), [NaN, NaN, NaN, NaN, NaN], 3 * eps) %!assert_equal (hygepdf (x, 4, 2, 2 * [1, -1, NaN, 1.1, 1]), [0, NaN, NaN, NaN, 0]) %!assert_equal (hygepdf (x, 4, 2, 5), [NaN, NaN, NaN, NaN, NaN], 3 * eps) %!assert_equal (hygepdf ([x, NaN], 4, 2, 2), [y, NaN], 3 * eps) ## Test class of input preserved %!assert_equal (hygepdf (single ([x, NaN]), 4, 2, 2), single ([y, NaN]), eps ('single')) %!assert_equal (hygepdf ([x, NaN], single (4), 2, 2), single ([y, NaN]), eps ('single')) %!assert_equal (hygepdf ([x, NaN], 4, single (2), 2), single ([y, NaN]), eps ('single')) %!assert_equal (hygepdf ([x, NaN], 4, 2, single (2)), single ([y, NaN]), eps ('single')) ## Test vector expansion %!test %! z = zeros (3,5); %! z([4,5,6,8,9,12]) = [1, 0.5, 1/6, 0.5, 2/3, 1/6]; %! assert_equal (hygepdf (x, 4, [0, 1, 2], 2, 'vectorexpand'), z, 3 * eps); %! assert_equal (hygepdf (x, 4, [0, 1, 2]', 2, 'vectorexpand'), z, 3 * eps); %! assert_equal (hygepdf (x', 4, [0, 1, 2], 2, 'vectorexpand'), z, 3 * eps); %! assert_equal (hygepdf (2, 4, [0 ,1, 2], 2, 'vectorexpand'), z(:,4), 3 * eps); %! assert_equal (hygepdf (x, 4, 1, 2, 'vectorexpand'), z(2,:), 3 *eps); %! assert_equal (hygepdf ([NaN, x], 4, [0 1 2]', 2, 'vectorexpand'), [NaN(3, 1), z], 3 * eps); ## Test input validation %!error hygepdf () %!error hygepdf (1) %!error hygepdf (1,2) %!error hygepdf (1,2,3) %!error ... %! hygepdf (1, ones (3), ones (2), ones (2)) %!error ... %! hygepdf (1, ones (2), ones (3), ones (2)) %!error ... %! hygepdf (1, ones (2), ones (2), ones (3)) %!error hygepdf (true, 2, 2, 2) %!error hygepdf ('a', 2, 2, 2) %!assert_equal (class (hygepdf (int32 (2), 2, 2, 2)), 'double') %!error hygepdf (i, 2, 2, 2) %!error hygepdf (2, i, 2, 2) %!error hygepdf (2, 2, i, 2) %!error hygepdf (2, 2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/hygernd.m000066400000000000000000000201741524624707500246550ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1997-2016 Kurt Hornik ## Copyright (C) 2022 Nicholas R. Jankowski ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} hygernd (@var{m}, @var{k}, @var{n}) ## @deftypefnx {statistics} {@var{r} =} hygernd (@var{m}, @var{k}, @var{n}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} hygernd (@var{m}, @var{k}, @var{n}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} hygernd (@var{m}, @var{k}, @var{n}, [@var{sz}]) ## ## Random arrays from the hypergeometric distribution. ## ## @code{@var{r} = hygernd ((@var{m}, @var{k}, @var{n}} returns an array of ## random numbers chosen from the hypergeometric distribution with parameters ## @var{m}, @var{k}, and @var{n}. The size of @var{r} is the common size of ## @var{m}, @var{k}, and @var{n}. A scalar input functions as a constant matrix ## of the same size as the other inputs. ## ## The parameters @var{m}, @var{k}, and @var{n} must be positive integers ## with @var{k} and @var{n} not greater than @var{m}. ## ## When called with a single size argument, @code{hygernd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the hypergeometric distribution can be found at ## @url{https://en.wikipedia.org/wiki/Hypergeometric_distribution} ## ## @seealso{hygecdf, hygeinv, hygepdf, hygestat} ## @end deftypefn function r = hygernd (m, k, n, varargin) ## Check for valid number of input arguments if (nargin < 3) error ("hygernd: function called with too few input arguments."); endif ## Check for common size of T, M, and N if (! isscalar (m) || ! isscalar (k) || ! isscalar (n)) [retval, m, k, n] = common_size (m, k, n); if (retval > 0) error ("hygernd: T, M, and N must be of common size or scalars."); endif endif ## Check for T, M, and N being reals if (iscomplex (m) || iscomplex (k) || iscomplex (n)) error ("hygernd: T, M, and N must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 3) sz = size (m); elseif (nargin == 4) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("hygernd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 4) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("hygernd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (m) && ! isequal (size (m), size (ones (sz)))) error ("hygernd: T, M, and N must be scalars or of size SZ."); endif ## Check for class type if (isa (m, 'single') || isa (k, 'single') || isa (n, 'single')) cls = 'single'; else cls = 'double'; endif ok = ((m >= 0) & (k >= 0) & (n > 0) & (k <= m) & (n <= m) & (m == fix (m)) & (k == fix (k)) & (n == fix (n))); ## Generate random sample from the hypergeometric distribution if (isscalar (m)) if (ok) v = 0:n; p = hygepdf (v, m, k, n); r = v(lookup (cumsum (p(1:end-1)) / sum (p), rand (sz)) + 1); r = reshape (r, sz); if (strcmp (cls, 'single')) r = single (r); endif else r = NaN (sz, cls); endif else r = NaN (sz, cls); n = n(ok); num_n = numel (n); v = 0 : max (n(:)); p = cumsum (hygepdf (v, m(ok), k(ok), n, 'vectorexpand'), 2); ## Manual row-wise vectorization of lookup, which returns index of element ## less than or equal to test value, zero if test value is less than lowest ## number, and max index if greater than highest number. end_locs = sub2ind (size (p), [1 : num_n]', n(:) + 1); p = (p ./ p(end_locs)) - rand (num_n, 1); p(p>=0) = NaN; # NaN values ignored by max [p_match, p_match_idx] = max (p, [], 2); p_match_idx(isnan (p_match)) = 0; # rand < min(p) gives NaN, reset to 0 r(ok) = v(p_match_idx + 1); endif endfunction ## Test output %!assert_equal (size (hygernd (4, 2, 2)), [1, 1]) %!assert_equal (size (hygernd (4 * ones (2, 1), 2,2)), [2, 1]) %!assert_equal (size (hygernd (4 * ones (2, 2), 2,2)), [2, 2]) %!assert_equal (size (hygernd (4, 2 * ones (2, 1), 2)), [2, 1]) %!assert_equal (size (hygernd (4, 2 * ones (2, 2), 2)), [2, 2]) %!assert_equal (size (hygernd (4, 2, 2 * ones (2, 1))), [2, 1]) %!assert_equal (size (hygernd (4, 2, 2 * ones (2, 2))), [2, 2]) %!assert_equal (size (hygernd (4, 2, 2, 3)), [3, 3]) %!assert_equal (size (hygernd (4, 2, 2, [4, 1])), [4, 1]) %!assert_equal (size (hygernd (4, 2, 2, 4, 1)), [4, 1]) %!assert_equal (size (hygernd (4, 2, 2, [])), [0, 0]) %!assert_equal (size (hygernd (4, 2, 2, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (hygernd (1, 2, 3, -1)), [0, 0]) %!assert_equal (size (hygernd (1, 2, 3, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (hygernd (1, 2, 3, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (hygernd (4, 2, 2)), "double") %!assert_equal (class (hygernd (single (4), 2, 2)), "single") %!assert_equal (class (hygernd (single ([4, 4]), 2, 2)), "single") %!assert_equal (class (hygernd (4, single (2), 2)), "single") %!assert_equal (class (hygernd (4, single ([2, 2]),2)), "single") %!assert_equal (class (hygernd (4, 2, single (2))), "single") %!assert_equal (class (hygernd (4, 2, single ([2, 2]))), "single") ## Test input validation %!error hygernd () %!error hygernd (1) %!error hygernd (1, 2) %!error ... %! hygernd (ones (3), ones (2), ones (2)) %!error ... %! hygernd (ones (2), ones (3), ones (2)) %!error ... %! hygernd (ones (2), ones (2), ones (3)) %!error hygernd (i, 2, 3) %!error hygernd (1, i, 3) %!error hygernd (1, 2, i) %!error ... %! hygernd (1, 2, 3, 1.2) %!error ... %! hygernd (1, 2, 3, ones (2)) %!error ... %! hygernd (1, 2, 3, [2 0 2.5]) %!error ... %! hygernd (1, 2, 3, 2, 1.5, 5) %!error ... %! hygernd (2, ones (2), 2, 3) %!error ... %! hygernd (2, ones (2), 2, [3, 2]) %!error ... %! hygernd (2, ones (2), 2, 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/invgcdf.m000066400000000000000000000161001524624707500246270ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} invgcdf (@var{x}, @var{mu}, @var{lambda}) ## @deftypefnx {statistics} {@var{p} =} invgcdf (@var{x}, @var{mu}, @var{lambda}, @qcode{'upper'}) ## ## Inverse Gaussian cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the inverse Gaussian distribution with scale parameter @var{mu} and ## shape parameter @var{lambda}. The size of @var{p} is the common size of ## @var{x}, @var{mu} and @var{lambda}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## @code{@var{p} = invgcdf (@var{x}, @var{mu}, @var{lambda}, "upper")} computes ## the upper tail probability of the inverse Gaussian distribution with ## parameters @var{mu} and @var{lambda}, at the values in @var{x}. ## ## The inverse Gaussian CDF is only defined for @qcode{@var{mu} > 0} and ## @qcode{@var{lambda} > 0}. ## ## Further information about the inverse Gaussian distribution can be found at ## @url{https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{invginv, invgpdf, invgrnd, invgfit, invglike, invgstat} ## @end deftypefn function p = invgcdf (x, mu, lambda, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("invgcdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 3) if (! strcmpi (uflag, 'upper')) error ("invgcdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, MU, and LAMBDA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (lambda)) [err, x, mu, lambda] = common_size (x, mu, lambda); if (err > 0) error ("invgcdf: X, MU, and LAMBDA must be of common size or scalars."); endif endif ## Check for X, MU, and LAMBDA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (lambda))) error ("invgcdf: X, MU, and LAMBDA must be double or single."); endif ## Check for X, MU, and LAMBDA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (lambda)) error ("invgcdf: X, MU, and LAMBDA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (lambda, 'single')) is_class = 'single'; else is_class = 'double'; endif ## Prepare output p = zeros (size (x), is_class); ## Return NaNs for out of range values of MU and LAMBDA parameters mu(mu <= 0) = NaN; lambda(lambda <= 0) = NaN; ## Check for valid support of X is_zero = (x <= 0); x(is_zero) = realmin; is_inf = (x == Inf); ## Calculate z1, z2 z1 = sqrt (lambda ./ x) .* (x ./ mu - 1); z2 = -sqrt (lambda ./ x) .* (x ./ mu + 1); ## Compute the CDF if the inverse Gaussian if (uflag) p = 0.5 .* erfc (z1 ./ sqrt (2)) - ... exp (2 .* lambda ./ mu) .* 0.5 .* erfc (-z2 ./ sqrt (2)); p(is_zero) = 1; p(is_inf) = 0; else p = 0.5 .* erfc (-z1 ./ sqrt (2)) + ... exp (2 .* lambda ./ mu) .* 0.5 .* erfc (-z2 ./ sqrt (2)); p(is_zero) = 0; p(is_inf) = 1; endif endfunction %!demo %! ## Plot various CDFs from the inverse Gaussian distribution %! x = 0:0.001:3; %! p1 = invgcdf (x, 1, 0.2); %! p2 = invgcdf (x, 1, 1); %! p3 = invgcdf (x, 1, 3); %! p4 = invgcdf (x, 3, 0.2); %! p5 = invgcdf (x, 3, 1); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c', x, p5, '-y') %! grid on %! xlim ([0, 3]) %! legend ({'μ = 1, σ = 0.2', 'μ = 1, σ = 1', 'μ = 1, σ = 3', ... %! 'μ = 3, σ = 0.2', 'μ = 3, σ = 1'}, 'location', 'southeast') %! title ('Inverse Gaussian CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, p1, p1u, y2, y2u, y3, y3u %! x = [-Inf, -1, 0, 1/2, 1, Inf]; %! p1 = [0, 0, 0, 0.3650, 0.6681, 1]; %! p1u = [1, 1, 1, 0.6350, 0.3319, 0]; %!assert_equal (invgcdf (x, ones (1,6), ones (1,6)), p1, 1e-4) %!assert_equal (invgcdf (x, 1, 1), p1, 1e-4) %!assert_equal (invgcdf (x, 1, ones (1,6)), p1, 1e-4) %!assert_equal (invgcdf (x, ones (1,6), 1), p1, 1e-4) %!assert_equal (invgcdf (x, 1, [1, 1, 1, NaN, 1, 1]), [p1(1:3), NaN, p1(5:6)], 1e-4) %!assert_equal (invgcdf (x, [1, 1, 1, NaN, 1, 1], 1), [p1(1:3), NaN, p1(5:6)], 1e-4) %!assert_equal (invgcdf ([x(1:3), NaN, x(5:6)], 1, 1), [p1(1:3), NaN, p1(5:6)], 1e-4) %!assert_equal (invgcdf (x, ones (1,6), ones (1,6), 'upper'), p1u, 1e-4) %!assert_equal (invgcdf (x, 1, 1, 'upper'), p1u, 1e-4) %!assert_equal (invgcdf (x, 1, ones (1,6), 'upper'), p1u, 1e-4) %!assert_equal (invgcdf (x, ones (1,6), 1, 'upper'), p1u, 1e-4) ## Test class of input preserved %!assert_equal (class (invgcdf (single ([x, NaN]), 1, 1)), "single") %!assert_equal (class (invgcdf ([x, NaN], 1, single (1))), "single") %!assert_equal (class (invgcdf ([x, NaN], single (1), 1)), "single") ## Test input validation %!error invgcdf () %!error invgcdf (1) %!error invgcdf (1, 2) %!error invgcdf (1, 2, 3, 'tail') %!error invgcdf (1, 2, 3, 5) %!error ... %! invgcdf (ones (3), ones (2), ones (2)) %!error ... %! invgcdf (ones (2), ones (3), ones (2)) %!error ... %! invgcdf (ones (2), ones (2), ones (3)) %!error invgcdf (int32 (2), 2, 3) %!error invgcdf (true, 2, 3) %!error invgcdf ('a', 2, 3) %!error invgcdf (i, 2, 3) %!error invgcdf (1, i, 3) %!error invgcdf (1, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/invginv.m000066400000000000000000000165221524624707500246770ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} invginv (@var{p}, @var{mu}, @var{lambda}) ## ## Inverse of the inverse Gaussian cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the inverse Gaussian distribution with scale parameter @var{mu} and shape ## parameter @var{lambda}. The size of @var{x} is the common size of @var{p}, ## @var{mu}, and @var{lambda}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## The inverse Gaussian CDF is only defined for @qcode{@var{mu} > 0} and ## @qcode{@var{lambda} > 0}. ## ## Further information about the inverse Gaussian distribution can be found at ## @url{https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{invgcdf, invgpdf, invgrnd, invgfit, invglike, invgstat} ## @end deftypefn function x = invginv (p, mu, lambda) ## Check for valid number of input arguments if (nargin < 3) error ("invginv: function called with too few input arguments."); endif ## Check for common size of P, MU, and LAMBDA if (! isscalar (p) || ! isscalar (mu) || ! isscalar (lambda)) [retval, p, mu, lambda] = common_size (p, mu, lambda); vec = true; if (retval > 0) error ("invginv: P, MU, and LAMBDA must be of common size or scalars."); endif else vec = false; endif ## Check for P, MU, and LAMBDA being double or single if (! (isfloat (p) && isfloat (mu) && isfloat (lambda))) error ("invginv: P, MU, and LAMBDA must be double or single."); endif ## Check for X, MU, and LAMBDA being reals if (iscomplex (p) || iscomplex (mu) || iscomplex (lambda)) error ("invginv: P, MU, and LAMBDA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (mu, 'single') || isa (lambda, 'single')); x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ## Return NaNs for out of range values of MU and LAMBDA parameters mu(mu <= 0) = NaN; lambda(lambda <= 0) = NaN; ## Find valid parameters and p-values (handle edges cases below) validmulambda = (mu > 0) & (lambda > 0) & (lambda < Inf); validp_values = (validmulambda & (p > 0) & (p < 1)); valid_all = all (validp_values(:)); valid_any = any (validp_values(:)); ## Handle edges cases here if (! valid_all) x(p == 0 & validmulambda) = 0; x(p == 1 & validmulambda) = Inf; ## Keep valid cases (if any left) if (valid_any) if (vec) p = p(validp_values); mu = mu(validp_values); lambda = lambda(validp_values); endif else return; endif endif ## Apply Newton's Method to find a root of p = invgcdf(x,mu,lambda) ## Choose a starting guess for x0. Use quantiles from a lognormal ## distribution with the same mean (==1) and variance (==lambda0) lambda0 = lambda ./ mu; lognorm = log (1 ./ lambda0 + 1); mulnorm = -0.5 .* lognorm; x0 = exp (mulnorm - sqrt (2 .* lognorm) .* erfcinv (2 * p)); ## Set maximum iterations and tolerance for Newton's Method mit = 500; tol = eps (class (x0)) .^ (3/4); ## Get quantiles F = invgcdf (x0, 1, lambda0); dF = F - p; for it = 1:mit ## Compute the Newton step f = invgpdf (x0, 1, lambda0); h = dF ./ f; x0_1 = max (x0/10, min (10 * x0, x0 - h)); ## Check if tolerance is reached complete = (abs (h) <= tol * x0); if (all (complete(:))) x0 = x0_1; break endif ## Check for increasing error unless tolerance is reached dFold = dF; for j = 1:25 F = invgcdf (x0_1, 1, lambda0); dF = F - p; worse = (abs (dF) > abs (dFold)) & ! complete; if (! any (worse(:))) break endif x0_1(worse) = (x0(worse) + x0_1(worse)) / 2; endfor ## Update for next step x0 = x0_1; endfor ## Issue a warning for exceeding iterations or not converging to tolerance notconv = (abs (dF./F) > tol.^(2/3)); if (it > mit || any (notconv(:))) warning (strcat ("invginv: Newton's Method did not converge", ... " or exceeded maximum iterations.")); endif ## Apply the scale factor if (valid_all) x = x0 .* mu; else x(validp_values) = x0 .* mu; endif endfunction %!demo %! ## Plot various iCDFs from the inverse Gaussian distribution %! p = 0.001:0.001:0.999; %! x1 = invginv (p, 1, 0.2); %! x2 = invginv (p, 1, 1); %! x3 = invginv (p, 1, 3); %! x4 = invginv (p, 3, 0.2); %! x5 = invginv (p, 3, 1); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c', p, x5, '-y') %! grid on %! ylim ([0, 3]) %! legend ({'μ = 1, σ = 0.2', 'μ = 1, σ = 1', 'μ = 1, σ = 3', ... %! 'μ = 3, σ = 0.2', 'μ = 3, σ = 1'}, 'location', 'northwest') %! title ('Inverse Gaussian iCDF') %! xlabel ('probability') %! ylabel ('x') ## Test output %!shared p, x %! p = [0, 0.3829, 0.6827, 1]; %! x = [0, 0.5207, 1.0376, Inf]; %!assert_equal (invginv (p, 1, 1), x, 1e-4); %!assert_equal (invginv (p, 1, ones (1,4)), x, 1e-4); %!assert_equal (invginv (p, 1, [-1, 0, 1, 1]), [NaN, NaN, x(3:4)], 1e-4) %!assert_equal (invginv (p, [-1, 0, 1, 1], 1), [NaN, NaN, x(3:4)], 1e-4) ## Test class of input preserved %!assert_equal (class (invginv (single ([p, NaN]), 0, 1)), "single") %!assert_equal (class (invginv ([p, NaN], single (0), 1)), "single") %!assert_equal (class (invginv ([p, NaN], 0, single (1))), "single") ## Test input validation %!error invginv (1) %!error invginv (1, 2) %!error ... %! invginv (1, ones (2), ones (3)) %!error ... %! invginv (ones (2), 1, ones (3)) %!error ... %! invginv (ones (2), ones (3), 1) %!error invginv (int32 (2), 2, 3) %!error invginv (true, 2, 3) %!error invginv ('a', 2, 3) %!error invginv (i, 2, 3) %!error invginv (1, i, 3) %!error invginv (1, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/invgpdf.m000066400000000000000000000127441524624707500246560ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} invgpdf (@var{x}, @var{mu}, @var{lambda}) ## ## Inverse Gaussian probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the inverse Gaussian distribution with scale parameter @var{mu} and shape ## parameter @var{lambda}. The size of @var{y} is the common size of @var{x}, ## @var{mu}, and @var{lambda}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## The inverse Gaussian CDF is only defined for @qcode{@var{mu} > 0} and ## @qcode{@var{lambda} > 0}. ## ## Further information about the inverse Gaussian distribution can be found at ## @url{https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{invgcdf, invginv, invgrnd, invgfit, invglike, invgstat} ## @end deftypefn function y = invgpdf (x, mu, lambda) ## Check for valid number of input arguments if (nargin < 3) error ("invgpdf: function called with too few input arguments."); endif ## Check for common size of X, MU, and LAMBDA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (lambda)) [retval, x, mu, lambda] = common_size (x, mu, lambda); if (retval > 0) error ("invgpdf: X, MU, and LAMBDA must be of common size or scalars."); endif endif ## Check for X, MU, and LAMBDA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (lambda))) error ("invgpdf: X, MU, and LAMBDA must be double or single."); endif ## Check for X, MU, and LAMBDA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (lambda)) error ("invgpdf: X, MU, and LAMBDA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (lambda, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Return NaNs for out of range values of MU and LAMBDA parameters mu(mu <= 0) = NaN; lambda(lambda <= 0) = NaN; ## Check for valid support of X is_zero = (x <= 0); x(is_zero) = realmin; ## Compute inverse Gaussian PDF y = sqrt (lambda ./ (2 .* pi .* x .^ 3)) .* ... exp (-0.5 .* lambda .* (x ./ mu - 2 + mu ./ x) ./ mu); ## Force zero for unsupported X but valid parameters k0 = is_zero & mu > 0 & lambda > 0; y(k0) = 0; ## Cast to appropriate class y = cast (y, is_class); endfunction %!demo %! ## Plot various PDFs from the inverse Gaussian distribution %! x = 0:0.001:3; %! y1 = invgpdf (x, 1, 0.2); %! y2 = invgpdf (x, 1, 1); %! y3 = invgpdf (x, 1, 3); %! y4 = invgpdf (x, 3, 0.2); %! y5 = invgpdf (x, 3, 1); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c', x, y5, '-y') %! grid on %! xlim ([0, 3]) %! ylim ([0, 3]) %! legend ({'μ = 1, σ = 0.2', 'μ = 1, σ = 1', 'μ = 1, σ = 3', ... %! 'μ = 3, σ = 0.2', 'μ = 3, σ = 1'}, 'location', 'northeast') %! title ('Inverse Gaussian PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-Inf, -1, 0, 1/2, 1, Inf]; %! y = [0, 0, 0, 0.8788, 0.3989, 0]; %!assert_equal (invgpdf ([x, NaN], 1, 1), [y, NaN], 1e-4) %!assert_equal (invgpdf (x, 1, [-2, -1, 0, 1, 1, 1]), [nan(1,3), y([4:6])], 1e-4) ## Test class of input preserved %!assert_equal (class (hncdf (single ([x, NaN]), 1, 1)), "single") %!assert_equal (class (hncdf ([x, NaN], 1, single (1))), "single") %!assert_equal (class (hncdf ([x, NaN], single (1), 1)), "single") ## Test input validation %!error invgpdf () %!error invgpdf (1) %!error invgpdf (1, 2) %!error ... %! invgpdf (1, ones (2), ones (3)) %!error ... %! invgpdf (ones (2), 1, ones (3)) %!error ... %! invgpdf (ones (2), ones (3), 1) %!error invgpdf (int32 (2), 2, 3) %!error invgpdf (true, 2, 3) %!error invgpdf ('a', 2, 3) %!error invgpdf (i, 2, 3) %!error invgpdf (1, i, 3) %!error invgpdf (1, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/invgrnd.m000066400000000000000000000163001524624707500246600ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} invgrnd (@var{mu}, @var{lambda}) ## @deftypefnx {statistics} {@var{r} =} invgrnd (@var{mu}, @var{lambda}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} invgrnd (@var{mu}, @var{lambda}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} invgrnd (@var{mu}, @var{lambda}, [@var{sz}]) ## ## Random arrays from the inverse Gaussian distribution. ## ## @code{@var{r} = invgrnd (@var{mu}, @var{lambda})} returns an array of random ## numbers chosen from the inverse Gaussian distribution with location parameter ## @var{mu} and scale parameter @var{lambda}. The size of @var{r} is the common ## size of @var{mu} and @var{lambda}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## When called with a single size argument, @code{invgrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## The inverse Gaussian CDF is only defined for @qcode{@var{mu} > 0} and ## @qcode{@var{lambda} > 0}. ## ## Further information about the inverse Gaussian distribution can be found at ## @url{https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution} ## ## @seealso{invgcdf, invginv, invgpdf, invgfit, invglike, invgstat} ## @end deftypefn function r = invgrnd (mu, lambda, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("invgrnd: function called with too few input arguments."); endif ## Check for common size of MU, and LAMBDA if (! isscalar (mu) || ! isscalar (lambda)) [retval, mu, lambda] = common_size (mu, lambda); vec = true; if (retval > 0) error ("invgrnd: MU and LAMBDA must be of common size or scalars."); endif else vec = false; endif ## Check for X, MU, and LAMBDA being reals if (iscomplex (mu) || iscomplex (lambda)) error ("invgrnd: MU and LAMBDA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (mu); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("invgrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("invgrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (mu) && ! isequal (size (mu), size (ones (sz)))) error ("invgrnd: MU and LAMBDA must be scalars or of size SZ."); endif ## Check for class type if (isa (mu, 'single') || isa (lambda, 'single')) cls = 'single'; else cls = 'double'; endif ## Expand parameters (if needed) if (! vec) mu = repmat (mu, sz); lambda = repmat (lambda, sz); endif ## Generate random sample from inverse Gaussian distribution v = randn (sz, cls); y = v .^ 2; r = mu + (mu .^ 2 .* y) ./ (2 .* lambda) - (mu ./ (2 .* lambda)) .* ... sqrt (4 * mu .* lambda .* y + mu .* mu .* y .* y); inver = (rand (sz) .* (mu + r) > mu); r(inver) = mu(inver) .^2 ./ r(inver); ## Force output to NaN for invalid parameters MU and LAMBDA k = (mu <= 0 | lambda <= 0); r(k) = NaN; endfunction ## Test results %!assert_equal (size (invgrnd (1, 1, 1)), [1, 1]) %!assert_equal (size (invgrnd (1, 1, 2)), [2, 2]) %!assert_equal (size (invgrnd (1, 1, [2, 1])), [2, 1]) %!assert_equal (size (invgrnd (1, zeros (2, 2))), [2, 2]) %!assert_equal (size (invgrnd (1, ones (2, 1))), [2, 1]) %!assert_equal (size (invgrnd (1, ones (2, 2))), [2, 2]) %!assert_equal (size (invgrnd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (invgrnd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (invgrnd (1, 1, 3)), [3, 3]) %!assert_equal (size (invgrnd (1, 1, [4 1])), [4, 1]) %!assert_equal (size (invgrnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (invgrnd (1, 1, [])), [0, 0]) %!assert_equal (size (invgrnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (invgrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (invgrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (invgrnd (1, 2, 2, -1, 5)), [2, 0, 5]) %!test %! r = invgrnd (1, [1, 0, -1]); %! assert_equal (r([2:3]), [NaN, NaN]) ## Test class of input preserved %!assert_equal (class (invgrnd (1, 0)), "double") %!assert_equal (class (invgrnd (1, single (0))), "single") %!assert_equal (class (invgrnd (1, single ([0, 0]))), "single") %!assert_equal (class (invgrnd (1, single (1))), "single") %!assert_equal (class (invgrnd (1, single ([1, 1]))), "single") %!assert_equal (class (invgrnd (single (1), 1)), "single") %!assert_equal (class (invgrnd (single ([1, 1]), 1)), "single") ## Test input validation %!error invgrnd () %!error invgrnd (1) %!error ... %! invgrnd (ones (3), ones (2)) %!error ... %! invgrnd (ones (2), ones (3)) %!error invgrnd (i, 2, 3) %!error invgrnd (1, i, 3) %!error ... %! invgrnd (1, 2, 1.2) %!error ... %! invgrnd (1, 2, ones (2)) %!error ... %! invgrnd (1, 2, [2 0 2.5]) %!error ... %! invgrnd (1, 2, 2, 1.5, 5) %!error ... %! invgrnd (2, ones (2), 3) %!error ... %! invgrnd (2, ones (2), [3, 2]) %!error ... %! invgrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/iwishpdf.m000066400000000000000000000070311524624707500250270ustar00rootroot00000000000000## Copyright (C) 2013 Nir Krakauer ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} iwishpdf (@var{W}, @var{Tau}, @var{df}, @var{log_y}=false) ## ## Compute the probability density function of the inverse Wishart distribution. ## ## Inputs: A @var{p} x @var{p} matrix @var{W} where to find the PDF and the ## @var{p} x @var{p} positive definite scale matrix @var{Tau} and scalar degrees ## of freedom parameter @var{df} characterizing the inverse Wishart ## distribution. (For the density to be finite, need @var{df} > (@var{p} - 1).) ## If the flag @var{log_y} is set, return the log probability density -- this ## helps avoid underflow when the numerical value of the density is very small. ## ## Output: @var{y} is the probability density of Wishart(@var{Sigma}, @var{df}) ## at @var{W}. ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{iwishrnd, wishpdf, wishrnd} ## @end deftypefn function y = iwishpdf (W, Tau, df, log_y=false) if (nargin < 3) print_usage (); endif ## Check for W, TAU, and DF being double or single if (! (isfloat (W) && isfloat (Tau) && isfloat (df))) error ("iwishpdf: W, TAU, and DF must be double or single."); endif p = size (Tau, 1); if (df <= (p - 1)) error ("iwishpdf: DF too small, no finite densities exist."); endif ## Calculate the logarithm of G_d(df/2), the multivariate gamma function g = (p * (p - 1) / 4) * log (pi); for i = 1:p g = g + log (gamma ((df + (1 - i)) / 2)); endfor C = chol (W); ## Use formulas for determinant of positive definite matrix for better ## efficiency and numerical accuracy logdet_W = 2*sum (log (diag (C))); logdet_Tau = 2*sum (log (diag (chol (Tau)))); y = -(df * p) / 2 * log (2) + (df / 2) * logdet_Tau - g ... -((df + p + 1) / 2) * logdet_W - trace (Tau * chol2inv (C)) / 2; if (! log_y) y = exp (y); endif endfunction ## Test results cross-checked against diwish function in R MCMCpack library %!assert_equal (iwishpdf (4, 3, 3.1), 0.04226595, 1E-7); %!assert_equal (iwishpdf ([2 -0.3;-0.3 4], [1 0.3;0.3 1], 4), 1.60166e-05, 1E-10); %!assert_equal (iwishpdf ([6 2 5; 2 10 -5; 5 -5 25], ... %! [9 5 5; 5 10 -8; 5 -8 22], 5.1), 4.946831e-12, 1E-17); ## Test input validation %!error iwishpdf (int32 (eye (2)), eye (2), 3) %!error iwishpdf (true (2), eye (2), 3) %!error iwishpdf (['ab'; 'cd'], eye (2), 3) %!error iwishpdf () %!error iwishpdf (1, 2) %!error iwishpdf (1, 2, 0) statistics-release-1.9.2/inst/Distribution_Functions/iwishrnd.m000066400000000000000000000057131524624707500250460ustar00rootroot00000000000000## Copyright (C) 2013 Nir Krakauer ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{W}, @var{DI}] =} iwishrnd (@var{Tau}, @var{df}, @var{DI}, @var{n}=1) ## ## Return a random matrix sampled from the inverse Wishart distribution with ## given parameters. ## ## Inputs: the @math{p * p} positive definite matrix @var{Tau} and scalar ## degrees of freedom parameter @var{df} (and optionally the transposed Cholesky ## factor @var{DI} of @var{Sigma} = @code{inv(Tau)}). ## ## @var{df} can be non-integer as long as @math{@var{df} > d} ## ## Output: a random @math{p * p} matrix @var{W} from the inverse ## Wishart(@var{Tau}, @var{df}) distribution. (@code{inv(W)} is from the ## Wishart(@code{inv(Tau)}, @var{df}) distribution.) If @var{n} > 1, ## then @var{W} is @var{p} x @var{p} x @var{n} and holds @var{n} such random ## matrices. (Optionally, the transposed Cholesky factor @var{DI} of @var{Sigma} ## is also returned.) ## ## Averaged across many samples, the mean of @var{W} should approach ## @var{Tau} / (@var{df} - @var{p} - 1). ## ## @subheading References ## ## @enumerate ## @item ## Yu-Cheng Ku and Peter Bloomfield (2010), Generating Random Wishart Matrices ## with Fractional Degrees of Freedom in OX, ## http://www.gwu.edu/~forcpgm/YuChengKu-030510final-WishartYu-ChengKu.pdf ## @end enumerate ## ## @seealso{iwishpdf, wishpdf, wishrnd} ## @end deftypefn function [W, DI] = iwishrnd (Tau, df, DI, n = 1) if (nargin < 2) print_usage (); endif if (nargin < 3 || isempty (DI)) try D = chol (inv (Tau)); catch error (strcat ("iwishrnd: Cholesky decomposition failed;", ... " TAU probably not positive definite.")); end_try_catch DI = D'; else D = DI'; endif w = wishrnd ([], df, D, n); if (n > 1) p = size (D, 1); W = nan (p, p, n); endif for i = 1:n W(:, :, i) = inv (w(:, :, i)); endfor endfunction %!assert_equal (size (iwishrnd (1,2,1)), [1, 1]); %!assert_equal (size (iwishrnd ([],2,1)), [1, 1]); %!assert_equal (size (iwishrnd ([3 1; 1 3], 2.00001, [], 1)), [2, 2]); %!assert_equal (size (iwishrnd (eye (2), 2, [], 3)), [2, 2, 3]); %% Test input validation %!error iwishrnd () %!error iwishrnd (1) %!error iwishrnd ([-3 1; 1 3],1) %!error iwishrnd ([1; 1],1) statistics-release-1.9.2/inst/Distribution_Functions/jsucdf.m000066400000000000000000000061221524624707500244700ustar00rootroot00000000000000## Copyright (C) 2006 Frederick (Rick) A Niles ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} jsucdf (@var{x}) ## @deftypefnx {statistics} {@var{p} =} jsucdf (@var{x}, @var{alpha1}) ## @deftypefnx {statistics} {@var{p} =} jsucdf (@var{x}, @var{alpha1}, @var{alpha2}) ## ## Johnson SU cumulative distribution function (CDF). ## ## For each element of @var{x}, return the cumulative distribution functions ## (CDF) at @var{x} of the Johnson SU distribution with shape parameters ## @var{alpha1} and @var{alpha2}. The size of @var{p} is the common size of the ## input arguments @var{x}, @var{alpha1}, and @var{alpha2}. A scalar input ## functions as a constant matrix of the same size as the other ## ## Default values are @var{alpha1} = 1, @var{alpha2} = 1. ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{jsupdf} ## @end deftypefn function p = jsucdf (x, alpha1, alpha2) if (nargin < 1 || nargin > 3) print_usage; endif if (nargin == 1) alpha1 = 1; alpha2 = 1; elseif (nargin == 2) alpha2 = 1; endif ## Check for X, ALPHA1, and ALPHA2 being double or single if (! (isfloat (x) && isfloat (alpha1) && isfloat (alpha2))) error ("jsucdf: X, ALPHA1, and ALPHA2 must be double or single."); endif if (! isscalar (x) || ! isscalar (alpha1) || ! isscalar (alpha2)) [retval, x, alpha1, alpha2] = common_size (x, alpha1, alpha2); if (retval > 0) error (strcat ("jsucdf: X, ALPHA1, and ALPHA2 must be of common", ... " size or scalars.")); endif endif one = ones (size (x)); p = stdnormal_cdf (alpha1 .* one + alpha2 .* log (x + sqrt (x .* x + one))); endfunction %!error jsucdf (int32 (2), 1, 1) %!error jsucdf (true, 1, 1) %!error jsucdf ('a', 1, 1) %!error jsucdf () %!error jsucdf (1, 2, 3, 4) %!error ... %! jsucdf (1, ones (2), ones (3)) statistics-release-1.9.2/inst/Distribution_Functions/jsupdf.m000066400000000000000000000061621524624707500245110ustar00rootroot00000000000000## Copyright (C) 2006 Frederick (Rick) A Niles ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} jsupdf (@var{x}) ## @deftypefnx {statistics} {@var{y} =} jsupdf (@var{x}, @var{alpha1}) ## @deftypefnx {statistics} {@var{y} =} jsupdf (@var{x}, @var{alpha1}, @var{alpha2}) ## ## Johnson SU probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## at @var{x} of the Johnson SU distribution with shape parameters @var{alpha1} ## and @var{alpha2}. The size of @var{p} is the common size of the input ## arguments @var{x}, @var{alpha1}, and @var{alpha2}. A scalar input functions ## as a constant matrix of the same size as the other ## ## Default values are @var{alpha1} = 1, @var{alpha2} = 1. ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{jsucdf} ## @end deftypefn function y = jsupdf (x, alpha1, alpha2) if (nargin < 1 || nargin > 3) print_usage; endif if (nargin == 1) alpha1 = 1; alpha2 = 1; elseif (nargin == 2) alpha2 = 1; endif ## Check for X, ALPHA1, and ALPHA2 being double or single if (! (isfloat (x) && isfloat (alpha1) && isfloat (alpha2))) error ("jsupdf: X, ALPHA1, and ALPHA2 must be double or single."); endif if (! isscalar (x) || ! isscalar (alpha1) || ! isscalar (alpha2)) [retval, x, alpha1, alpha2] = common_size (x, alpha1, alpha2); if (retval > 0) error (strcat ("jsupdf: X, ALPHA1, and ALPHA2 must be of common", ... " size or scalars.")); endif endif one = ones (size (x)); sr = sqrt (x .* x + one); y = (alpha2 ./ sr) .* ... stdnormal_pdf (alpha1 .* one + alpha2 .* log (x + sr)); endfunction %!error jsupdf (int32 (2), 1, 1) %!error jsupdf (true, 1, 1) %!error jsupdf ('a', 1, 1) %!error jsupdf () %!error jsupdf (1, 2, 3, 4) %!error ... %! jsupdf (1, ones (2), ones (3)) statistics-release-1.9.2/inst/Distribution_Functions/laplacecdf.m000066400000000000000000000145411524624707500252740ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} laplacecdf (@var{x}, @var{mu}, @var{beta}) ## @deftypefnx {statistics} {@var{p} =} laplacecdf (@var{x}, @var{mu}, @var{beta}, @qcode{'upper'}) ## ## Laplace cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the Laplace distribution with location parameter @var{mu} and scale ## parameter (i.e. "diversity") @var{beta}. The size of @var{p} is the common ## size of @var{x}, @var{mu}, and @var{beta}. A scalar input functions as a ## constant matrix of the same size as the other inputs. ## ## Both parameters must be reals and @qcode{@var{beta} > 0}. ## For @qcode{@var{beta} <= 0}, @qcode{NaN} is returned. ## ## @code{@var{p} = laplacecdf (@var{x}, @var{mu}, @var{beta}, "upper")} computes ## the upper tail probability of the Laplace distribution with parameters ## @var{mu} and @var{beta}, at the values in @var{x}. ## ## Further information about the Laplace distribution can be found at ## @url{https://en.wikipedia.org/wiki/Laplace_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{laplaceinv, laplacepdf, laplacernd} ## @end deftypefn function p = laplacecdf (x, mu, beta, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("laplacecdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 3) if (! strcmpi (uflag, 'upper')) error ("laplacecdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, MU, and BETA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (beta)) [retval, x, mu, beta] = common_size (x, mu, beta); if (retval > 0) error (strcat ("laplacecdf: X, MU, and BETA must be of", ... " common size or scalars.")); endif endif ## Check for X, MU, and BETA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (beta))) error ("laplacecdf: X, MU, and BETA must be double or single."); endif ## Check for X, MU, and BETA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (beta)) error ("laplacecdf: X, MU, and BETA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (beta, 'single')); p = NaN (size (x), 'single'); else p = NaN (size (x)); endif ## Find normal and edge cases k1 = (x == -Inf) & (beta > 0); k2 = (x == Inf) & (beta > 0); k = ! k1 & ! k2 & (beta > 0); ## Compute Laplace CDF if (uflag) p(k1) = 1; p(k2) = 0; p(k) = (1 + sign (-x(k) + mu(k)) .* ... (1 - exp (- abs (-x(k) + mu(k)) ./ beta(k)))) ./ 2; else p(k1) = 0; p(k2) = 1; p(k) = (1 + sign (x(k) - mu(k)) .* ... (1 - exp (- abs (x(k) - mu(k)) ./ beta(k)))) ./ 2; endif endfunction %!demo %! ## Plot various CDFs from the Laplace distribution %! x = -10:0.01:10; %! p1 = laplacecdf (x, 0, 1); %! p2 = laplacecdf (x, 0, 2); %! p3 = laplacecdf (x, 0, 4); %! p4 = laplacecdf (x, -5, 4); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c') %! grid on %! xlim ([-10, 10]) %! legend ({'μ = 0, β = 1', 'μ = 0, β = 2', ... %! 'μ = 0, β = 4', 'μ = -5, β = 4'}, 'location', 'southeast') %! title ('Laplace CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-Inf, -log(2), 0, log(2), Inf]; %! y = [0, 1/4, 1/2, 3/4, 1]; %!assert_equal (laplacecdf ([x, NaN], 0, 1), [y, NaN]) %!assert_equal (laplacecdf (x, 0, [-2, -1, 0, 1, 2]), [nan(1, 3), 0.75, 1]) ## Test class of input preserved %!assert_equal (laplacecdf (single ([x, NaN]), 0, 1), single ([y, NaN]), eps ('single')) %!assert_equal (laplacecdf ([x, NaN], single (0), 1), single ([y, NaN]), eps ('single')) %!assert_equal (laplacecdf ([x, NaN], 0, single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error laplacecdf () %!error laplacecdf (1) %!error ... %! laplacecdf (1, 2) %!error ... %! laplacecdf (1, 2, 3, 4, 5) %!error laplacecdf (1, 2, 3, 'tail') %!error laplacecdf (1, 2, 3, 4) %!error ... %! laplacecdf (ones (3), ones (2), ones (2)) %!error ... %! laplacecdf (ones (2), ones (3), ones (2)) %!error ... %! laplacecdf (ones (2), ones (2), ones (3)) %!error laplacecdf (int32 (2), 2, 2) %!error laplacecdf (true, 2, 2) %!error laplacecdf ('a', 2, 2) %!error laplacecdf (i, 2, 2) %!error laplacecdf (2, i, 2) %!error laplacecdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/laplaceinv.m000066400000000000000000000126021524624707500253300ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} laplaceinv (@var{p}, @var{mu}, @var{beta}) ## ## Inverse of the Laplace cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the Laplace distribution with location parameter @var{mu} and scale parameter ## (i.e. "diversity") @var{beta}. The size of @var{x} is the common size of ## @var{p}, @var{mu}, and @var{beta}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## Both parameters must be reals and @qcode{@var{beta} > 0}. ## For @qcode{@var{beta} <= 0}, @qcode{NaN} is returned. ## ## Further information about the Laplace distribution can be found at ## @url{https://en.wikipedia.org/wiki/Laplace_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{laplacecdf, laplacepdf, laplacernd} ## @end deftypefn function x = laplaceinv (p, mu, beta) ## Check for valid number of input arguments if (nargin < 3) error ("laplaceinv: function called with too few input arguments."); endif ## Check for common size of P, MU, and BETA if (! isscalar (p) || ! isscalar (mu) || ! isscalar (beta)) [retval, p, mu, beta] = common_size (p, mu, beta); if (retval > 0) error (strcat ("laplaceinv: P, MU, and BETA must be of", ... " common size or scalars.")); endif endif ## Check for P, MU, and BETA being double or single if (! (isfloat (p) && isfloat (mu) && isfloat (beta))) error ("laplaceinv: P, MU, and BETA must be double or single."); endif ## Check for X, MU, and BETA being reals if (iscomplex (p) || iscomplex (mu) || iscomplex (beta)) error ("laplaceinv: P, MU, and BETA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (mu, 'single') || isa (beta, 'single')); x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ## Compute Laplace iCDF k = (p >= 0) & (p <= 1) & (beta > 0); x(k) = mu(k) + beta(k) .* ((p(k) < 1/2) .* log (2 .* p(k)) - ... (p(k) > 1/2) .* log (2 .* (1 - p(k)))); endfunction %!demo %! ## Plot various iCDFs from the Laplace distribution %! p = 0.001:0.001:0.999; %! x1 = cauchyinv (p, 0, 1); %! x2 = cauchyinv (p, 0, 2); %! x3 = cauchyinv (p, 0, 4); %! x4 = cauchyinv (p, -5, 4); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c') %! grid on %! ylim ([-10, 10]) %! legend ({'μ = 0, β = 1', 'μ = 0, β = 2', ... %! 'μ = 0, β = 4', 'μ = -5, β = 4'}, 'location', 'northwest') %! title ('Laplace iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p, x %! p = [-1 0 0.5 1 2]; %! x = [NaN, -Inf, 0, Inf, NaN]; %!assert_equal (laplaceinv (p, 0, 1), x) %!assert_equal (laplaceinv (p, 0, [-2, -1, 0, 1, 2]), [nan(1, 3), Inf, NaN]) %!assert_equal (laplaceinv ([p, NaN], 0, 1), [x, NaN]) ## Test class of input preserved %!assert_equal (laplaceinv (single ([p, NaN]), 0, 1), single ([x, NaN])) %!assert_equal (laplaceinv ([p, NaN], single (0), 1), single ([x, NaN])) %!assert_equal (laplaceinv ([p, NaN], 0, single (1)), single ([x, NaN])) ## Test input validation %!error laplaceinv () %!error laplaceinv (1) %!error ... %! laplaceinv (1, 2) %!error laplaceinv (1, 2, 3, 4) %!error ... %! laplaceinv (1, ones (2), ones (3)) %!error ... %! laplaceinv (ones (2), 1, ones (3)) %!error ... %! laplaceinv (ones (2), ones (3), 1) %!error laplaceinv (int32 (2), 2, 3) %!error laplaceinv (true, 2, 3) %!error laplaceinv ('a', 2, 3) %!error laplaceinv (i, 2, 3) %!error laplaceinv (1, i, 3) %!error laplaceinv (1, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/laplacepdf.m000066400000000000000000000125221524624707500253060ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} laplacepdf (@var{x}, @var{mu}, @var{beta}) ## ## Laplace probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the Laplace distribution with location parameter @var{mu} and scale ## parameter (i.e. "diversity") @var{beta}. The size of @var{y} is the common ## size of @var{x}, @var{mu}, and @var{beta}. A scalar input functions as a ## constant matrix of the same size as the other inputs. ## ## Both parameters must be reals and @qcode{@var{beta} > 0}. ## For @qcode{@var{beta} <= 0}, @qcode{NaN} is returned. ## ## Further information about the Laplace distribution can be found at ## @url{https://en.wikipedia.org/wiki/Laplace_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{laplacecdf, laplaceinv, laplacernd} ## @end deftypefn function y = laplacepdf (x, mu, beta) ## Check for valid number of input arguments if (nargin < 3) error ("laplacepdf: function called with too few input arguments."); endif ## Check for common size of X, MU, and BETA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (beta)) [retval, x, mu, beta] = common_size (x, mu, beta); if (retval > 0) error (strcat ("laplacepdf: X, MU, and BETA must be of", ... " common size or scalars.")); endif endif ## Check for X, MU, and BETA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (beta))) error ("laplacepdf: X, MU, and BETA must be double or single."); endif ## Check for X, MU, and BETA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (beta)) error ("laplacepdf: X, MU, and BETA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (beta, 'single')); y = NaN (size (x), 'single'); else y = NaN (size (x)); endif ## Compute Laplace PDF k1 = ((x == -Inf) & (beta > 0)) | ((x == Inf) & (beta > 0)); y(k1) = 0; k = ! k1 & (beta > 0); y(k) = exp (- abs (x(k) - mu(k)) ./ beta(k)) ./ (2 .* beta(k)); endfunction %!demo %! ## Plot various PDFs from the Laplace distribution %! x = -10:0.01:10; %! y1 = laplacepdf (x, 0, 1); %! y2 = laplacepdf (x, 0, 2); %! y3 = laplacepdf (x, 0, 4); %! y4 = laplacepdf (x, -5, 4); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c') %! grid on %! xlim ([-10, 10]) %! ylim ([0, 0.6]) %! legend ({'μ = 0, β = 1', 'μ = 0, β = 2', ... %! 'μ = 0, β = 4', 'μ = -5, β = 4'}, 'location', 'northeast') %! title ('Laplace PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test results %!shared x, y %! x = [-Inf -log(2) 0 log(2) Inf]; %! y = [0, 1/4, 1/2, 1/4, 0]; %!assert_equal (laplacepdf ([x, NaN], 0, 1), [y, NaN]) %!assert_equal (laplacepdf (x, 0, [-2, -1, 0, 1, 2]), [nan(1, 3), 0.25, 0]) ## Test class of input preserved %!assert_equal (laplacepdf (single ([x, NaN]), 0, 1), single ([y, NaN])) %!assert_equal (laplacepdf ([x, NaN], single (0), 1), single ([y, NaN])) %!assert_equal (laplacepdf ([x, NaN], 0, single (1)), single ([y, NaN])) ## Test input validation %!error laplacepdf () %!error laplacepdf (1) %!error ... %! laplacepdf (1, 2) %!error laplacepdf (1, 2, 3, 4) %!error ... %! laplacepdf (1, ones (2), ones (3)) %!error ... %! laplacepdf (ones (2), 1, ones (3)) %!error ... %! laplacepdf (ones (2), ones (3), 1) %!error laplacepdf (int32 (2), 2, 3) %!error laplacepdf (true, 2, 3) %!error laplacepdf ('a', 2, 3) %!error laplacepdf (i, 2, 3) %!error laplacepdf (1, i, 3) %!error laplacepdf (1, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/laplacernd.m000066400000000000000000000156571524624707500253340ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} laplacernd (@var{mu}, @var{beta}) ## @deftypefnx {statistics} {@var{r} =} laplacernd (@var{mu}, @var{beta}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} laplacernd (@var{mu}, @var{beta}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} laplacernd (@var{mu}, @var{beta}, [@var{sz}]) ## ## Random arrays from the Laplace distribution. ## ## @code{@var{r} = laplacernd (@var{mu}, @var{beta})} returns an array of ## random numbers chosen from the Laplace distribution with location parameter ## @var{mu} and scale parameter @var{beta}. The size of @var{r} is the common ## size of @var{mu} and @var{beta}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## Both parameters must be reals and @qcode{@var{beta} > 0}. ## For @qcode{@var{beta} <= 0}, @qcode{NaN} is returned. ## ## When called with a single size argument, @code{laplacernd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the Laplace distribution can be found at ## @url{https://en.wikipedia.org/wiki/Laplace_distribution} ## ## @seealso{laplacecdf, laplaceinv, laplacepdf} ## @end deftypefn function r = laplacernd (mu, beta, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("laplacernd: function called with too few input arguments."); endif ## Check for common size of MU, and BETA if (! isscalar (mu) || ! isscalar (beta)) [retval, mu, beta] = common_size (mu, beta); if (retval > 0) error ("laplacernd: MU and BETA must be of common size or scalars."); endif endif ## Check for X, MU, and BETA being reals if (iscomplex (mu) || iscomplex (beta)) error ("laplacernd: MU and BETA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (mu); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("laplacernd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("laplacernd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (mu) && ! isequal (size (mu), size (ones (sz)))) error ("laplacernd: MU and BETA must be scalars or of size SZ."); endif ## Check for class type if (isa (mu, 'single') || isa (beta, 'single')) is_type = 'single'; else is_type = 'double'; endif ## Generate random sample from Laplace distribution tmp = rand (sz, is_type); r = ((tmp < 1/2) .* log (2 * tmp) - ... (tmp > 1/2) .* log (2 * (1 - tmp))) .* beta + mu; ## Force output to NaN for invalid parameter BETA <= 0 k = (beta <= 0); r(k) = NaN; endfunction ## Test output %!assert_equal (size (laplacernd (1, 1)), [1, 1]) %!assert_equal (size (laplacernd (1, ones (2, 1))), [2, 1]) %!assert_equal (size (laplacernd (1, ones (2, 2))), [2, 2]) %!assert_equal (size (laplacernd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (laplacernd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (laplacernd (1, 1, 3)), [3, 3]) %!assert_equal (size (laplacernd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (laplacernd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (laplacernd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (laplacernd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (laplacernd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (laplacernd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (laplacernd (1, 1, [])), [0, 0]) %!assert_equal (size (laplacernd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (laplacernd (1, 2, -1)), [0, 0]) %!assert_equal (size (laplacernd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (laplacernd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (laplacernd (1, 1)), "double") %!assert_equal (class (laplacernd (1, single (1))), "single") %!assert_equal (class (laplacernd (1, single ([1, 1]))), "single") %!assert_equal (class (laplacernd (single (1), 1)), "single") %!assert_equal (class (laplacernd (single ([1, 1]), 1)), "single") ## Test input validation %!error laplacernd () %!error laplacernd (1) %!error ... %! laplacernd (ones (3), ones (2)) %!error ... %! laplacernd (ones (2), ones (3)) %!error laplacernd (i, 2, 3) %!error laplacernd (1, i, 3) %!error ... %! laplacernd (1, 2, 1.2) %!error ... %! laplacernd (1, 2, ones (2)) %!error ... %! laplacernd (1, 2, [2 0 2.5]) %!error ... %! laplacernd (1, 2, 2, 1.5, 5) %!error ... %! laplacernd (2, ones (2), 3) %!error ... %! laplacernd (2, ones (2), [3, 2]) %!error ... %! laplacernd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/logicdf.m000066400000000000000000000140631524624707500246240ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} logicdf (@var{x}, @var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{p} =} logicdf (@var{x}, @var{mu}, @var{sigma}, @qcode{'upper'}) ## ## Logistic cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the logistic distribution with location parameter @var{mu} and scale ## parameter @var{sigma}. The size of @var{p} is the common size of @var{x}, ## @var{mu}, and @var{sigma}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Both parameters must be reals and @qcode{@var{sigma} > 0}. ## For @qcode{@var{sigma} <= 0}, @qcode{NaN} is returned. ## ## @code{@var{p} = logicdf (@var{x}, @var{mu}, @var{sigma}, "upper")} computes ## the upper tail probability of the logistic distribution with parameters ## @var{mu} and @var{sigma}, at the values in @var{x}. ## ## Further information about the logistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Logistic_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{logiinv, logipdf, logirnd, logifit, logilike, logistat} ## @end deftypefn function p = logicdf (x, mu, sigma, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("logicdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 3) if (! strcmpi (uflag, 'upper')) error ("logicdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, MU, and SIGMA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (sigma)) [retval, x, mu, sigma] = common_size (x, mu, sigma); if (retval > 0) error ("logicdf: X, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("logicdf: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma)) error ("logicdf: X, MU, and SIGMA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single')); p = NaN (size (x), 'single'); else p = NaN (size (x)); endif ## Find normal and edge cases k1 = (x == -Inf) & (sigma > 0); k2 = (x == Inf) & (sigma > 0); k = ! k1 & ! k2 & (sigma > 0); ## Compute logistic CDF if (uflag) p(k1) = 1; p(k2) = 0; p(k) = 1 ./ (1 + exp ((x(k) - mu(k)) ./ sigma(k))); else p(k1) = 0; p(k2) = 1; p(k) = 1 ./ (1 + exp (- (x(k) - mu(k)) ./ sigma(k))); endif endfunction %!demo %! ## Plot various CDFs from the logistic distribution %! x = -5:0.01:20; %! p1 = logicdf (x, 5, 2); %! p2 = logicdf (x, 9, 3); %! p3 = logicdf (x, 9, 4); %! p4 = logicdf (x, 6, 2); %! p5 = logicdf (x, 2, 1); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c', x, p5, '-m') %! grid on %! legend ({'μ = 5, σ = 2', 'μ = 9, σ = 3', 'μ = 9, σ = 4', ... %! 'μ = 6, σ = 2', 'μ = 2, σ = 1'}, 'location', 'southeast') %! title ('Logistic CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-Inf -log(3) 0 log(3) Inf]; %! y = [0, 1/4, 1/2, 3/4, 1]; %!assert_equal (logicdf ([x, NaN], 0, 1), [y, NaN], eps) %!assert_equal (logicdf (x, 0, [-2, -1, 0, 1, 2]), [nan(1, 3), 0.75, 1], eps) ## Test class of input preserved %!assert_equal (logicdf (single ([x, NaN]), 0, 1), single ([y, NaN]), eps ('single')) %!assert_equal (logicdf ([x, NaN], single (0), 1), single ([y, NaN]), eps ('single')) %!assert_equal (logicdf ([x, NaN], 0, single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error logicdf () %!error logicdf (1) %!error ... %! logicdf (1, 2) %!error logicdf (1, 2, 3, 'tail') %!error logicdf (1, 2, 3, 4) %!error ... %! logicdf (1, ones (2), ones (3)) %!error ... %! logicdf (ones (2), 1, ones (3)) %!error ... %! logicdf (ones (2), ones (3), 1) %!error logicdf (int32 (2), 2, 3) %!error logicdf (true, 2, 3) %!error logicdf ('a', 2, 3) %!error logicdf (i, 2, 3) %!error logicdf (1, i, 3) %!error logicdf (1, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/logiinv.m000066400000000000000000000125141524624707500246630ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} logiinv (@var{p}, @var{mu}, @var{sigma}) ## ## Inverse of the logistic cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the logistic distribution with location parameter @var{mu} and scale ## parameter @var{sigma}. The size of @var{p} is the common size of @var{x}, ## @var{mu}, and @var{sigma}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Both parameters must be reals and @qcode{@var{sigma} > 0}. ## For @qcode{@var{sigma} <= 0}, @qcode{NaN} is returned. ## ## Further information about the logistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Logistic_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{logicdf, logipdf, logirnd, logifit, logilike, logistat} ## @end deftypefn function x = logiinv (p, mu, sigma) ## Check for valid number of input arguments if (nargin < 3) error ("logiinv: function called with too few input arguments."); endif ## Check for common size of P, MU, and SIGMA if (! isscalar (p) || ! isscalar (mu) || ! isscalar (sigma)) [retval, p, mu, sigma] = common_size (p, mu, sigma); if (retval > 0) error ("logiinv: P, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for P, MU, and SIGMA being double or single if (! (isfloat (p) && isfloat (mu) && isfloat (sigma))) error ("logiinv: P, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (p) || iscomplex (mu) || iscomplex (sigma)) error ("logiinv: P, MU, and SIGMA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (mu, 'single') || isa (sigma, 'single')); x = NaN (size (p), 'single'); else x = NaN (size (p)); endif k = (p == 0) & (sigma > 0); x(k) = -Inf; k = (p == 1) & (sigma > 0); x(k) = Inf; k = (p > 0) & (p < 1) & (sigma > 0); x(k) = mu(k) + sigma(k) .* log (p(k) ./ (1 - p(k))); endfunction %!demo %! ## Plot various iCDFs from the logistic distribution %! p = 0.001:0.001:0.999; %! x1 = logiinv (p, 5, 2); %! x2 = logiinv (p, 9, 3); %! x3 = logiinv (p, 9, 4); %! x4 = logiinv (p, 6, 2); %! x5 = logiinv (p, 2, 1); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c', p, x5, '-m') %! grid on %! legend ({'μ = 5, σ = 2', 'μ = 9, σ = 3', 'μ = 9, σ = 4', ... %! 'μ = 6, σ = 2', 'μ = 2, σ = 1'}, 'location', 'southeast') %! title ('Logistic iCDF') %! xlabel ('probability') %! ylabel ('x') ## Test output %!test %! p = [0.01:0.01:0.99]; %! assert_equal (logiinv (p, 0, 1), log (p ./ (1-p)), 25*eps); %!shared p %! p = [-1 0 0.5 1 2]; %!assert_equal (logiinv (p, 0, 1), [NaN -Inf 0 Inf NaN]) %!assert_equal (logiinv (p, 0, [-1, 0, 1, 2, 3]), [NaN NaN 0 Inf NaN]) ## Test class of input preserved %!assert_equal (logiinv ([p, NaN], 0, 1), [NaN -Inf 0 Inf NaN NaN]) %!assert_equal (logiinv (single ([p, NaN]), 0, 1), single ([NaN -Inf 0 Inf NaN NaN])) %!assert_equal (logiinv ([p, NaN], single (0), 1), single ([NaN -Inf 0 Inf NaN NaN])) %!assert_equal (logiinv ([p, NaN], 0, single (1)), single ([NaN -Inf 0 Inf NaN NaN])) ## Test input validation %!error logiinv () %!error logiinv (1) %!error ... %! logiinv (1, 2) %!error ... %! logiinv (1, ones (2), ones (3)) %!error ... %! logiinv (ones (2), 1, ones (3)) %!error ... %! logiinv (ones (2), ones (3), 1) %!error logiinv (int32 (2), 2, 3) %!error logiinv (true, 2, 3) %!error logiinv ('a', 2, 3) %!error logiinv (i, 2, 3) %!error logiinv (1, i, 3) %!error logiinv (1, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/logipdf.m000066400000000000000000000123461524624707500246430ustar00rootroot00000000000000## Copyright (C) 1995-2017 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} logipdf (@var{x}, @var{mu}, @var{sigma}) ## ## Logistic probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the logistic distribution with location parameter @var{mu} and scale ## parameter @var{sigma}. The size of @var{p} is the common size of @var{x}, ## @var{mu}, and @var{sigma}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Both parameters must be reals and @qcode{@var{sigma} > 0}. ## For @qcode{@var{sigma} <= 0}, @qcode{NaN} is returned. ## ## Further information about the logistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Logistic_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{logicdf, logiinv, logirnd, logifit, logilike, logistat} ## @end deftypefn function y = logipdf (x, mu, sigma) ## Check for valid number of input arguments if (nargin < 3) error ("logipdf: function called with too few input arguments."); endif ## Check for common size of X, MU, and SIGMA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (sigma)) [retval, x, mu, sigma] = common_size (x, mu, sigma); if (retval > 0) error ("logipdf: X, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("logipdf: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma)) error ("logipdf: X, MU, and SIGMA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single')); y = NaN (size (x), 'single'); else y = NaN (size (x)); endif ## Compute logistic PDF k1 = ((x == -Inf) & (sigma > 0)) | ((x == Inf) & (sigma > 0)); y(k1) = 0; k = ! k1 & (sigma > 0); y(k) = (1 ./ (4 .* sigma(k))) .* ... (sech ((x(k) - mu(k)) ./ (2 .* sigma(k))) .^ 2); endfunction %!demo %! ## Plot various PDFs from the logistic distribution %! x = -5:0.01:20; %! y1 = logipdf (x, 5, 2); %! y2 = logipdf (x, 9, 3); %! y3 = logipdf (x, 9, 4); %! y4 = logipdf (x, 6, 2); %! y5 = logipdf (x, 2, 1); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c', x, y5, '-m') %! grid on %! ylim ([0, 0.3]) %! legend ({'μ = 5, σ = 2', 'μ = 9, σ = 3', 'μ = 9, σ = 4', ... %! 'μ = 6, σ = 2', 'μ = 2, σ = 1'}, 'location', 'northeast') %! title ('Logistic PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-Inf -log(4) 0 log(4) Inf]; %! y = [0, 0.16, 1/4, 0.16, 0]; %!assert_equal (logipdf ([x, NaN], 0, 1), [y, NaN], eps) %!assert_equal (logipdf (x, 0, [-2, -1, 0, 1, 2]), [nan(1, 3), y([4:5])], eps) ## Test class of input preserved %!assert_equal (logipdf (single ([x, NaN]), 0, 1), single ([y, NaN]), eps ('single')) %!assert_equal (logipdf ([x, NaN], single (0), 1), single ([y, NaN]), eps ('single')) %!assert_equal (logipdf ([x, NaN], 0, single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error logipdf () %!error logipdf (1) %!error ... %! logipdf (1, 2) %!error ... %! logipdf (1, ones (2), ones (3)) %!error ... %! logipdf (ones (2), 1, ones (3)) %!error ... %! logipdf (ones (2), ones (3), 1) %!error logipdf (int32 (2), 2, 3) %!error logipdf (true, 2, 3) %!error logipdf ('a', 2, 3) %!error logipdf (i, 2, 3) %!error logipdf (1, i, 3) %!error logipdf (1, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/logirnd.m000066400000000000000000000153511524624707500246540ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} logirnd (@var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{r} =} logirnd (@var{mu}, @var{sigma}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} logirnd (@var{mu}, @var{sigma}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} logirnd (@var{mu}, @var{sigma}, [@var{sz}]) ## ## Random arrays from the logistic distribution. ## ## @code{@var{r} = logirnd (@var{mu}, @var{sigma})} returns an array of ## random numbers chosen from the logistic distribution with location parameter ## @var{mu} and scale parameter @var{sigma}. The size of @var{r} is the common ## size ## of @var{mu} and @var{sigma}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Both parameters must be reals and @qcode{@var{sigma} > 0}. ## For @qcode{@var{sigma} <= 0}, @qcode{NaN} is returned. ## ## When called with a single size argument, @code{logirnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the logistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Logistic_distribution} ## ## @seealso{logcdf, logiinv, logipdf, logifit, logilike, logistat} ## @end deftypefn function r = logirnd (mu, sigma, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("logirnd: function called with too few input arguments."); endif ## Check for common size of MU, and SIGMA if (! isscalar (mu) || ! isscalar (sigma)) [retval, mu, sigma] = common_size (mu, sigma); if (retval > 0) error ("logirnd: MU and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being reals if (iscomplex (mu) || iscomplex (sigma)) error ("logirnd: MU and SIGMA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (mu); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("logirnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("logirnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (mu) && ! isequal (size (mu), size (ones (sz)))) error ("logirnd: MU and SIGMA must be scalars or of size SZ."); endif ## Check for class type if (isa (mu, 'single') || isa (sigma, 'single')) is_type = 'single'; else is_type = 'double'; endif ## Generate random sample from logistic distribution r = - log (1 ./ rand (sz, is_type) - 1) .* sigma + mu; ## Force output to NaN for invalid parameter SIGMA <= 0 k = (sigma <= 0); r(k) = NaN; endfunction ## Test output %!assert_equal (size (logirnd (1, 1)), [1, 1]) %!assert_equal (size (logirnd (1, ones (2, 1))), [2, 1]) %!assert_equal (size (logirnd (1, ones (2, 2))), [2, 2]) %!assert_equal (size (logirnd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (logirnd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (logirnd (1, 1, 3)), [3, 3]) %!assert_equal (size (logirnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (logirnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (logirnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (logirnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (logirnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (logirnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (logirnd (1, 1, [])), [0, 0]) %!assert_equal (size (logirnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (logirnd (1, 2, -1)), [0, 0]) %!assert_equal (size (logirnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (logirnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (logirnd (1, 1)), "double") %!assert_equal (class (logirnd (1, single (1))), "single") %!assert_equal (class (logirnd (1, single ([1, 1]))), "single") %!assert_equal (class (logirnd (single (1), 1)), "single") %!assert_equal (class (logirnd (single ([1, 1]), 1)), "single") ## Test input validation %!error logirnd () %!error logirnd (1) %!error ... %! logirnd (ones (3), ones (2)) %!error ... %! logirnd (ones (2), ones (3)) %!error logirnd (i, 2, 3) %!error logirnd (1, i, 3) %!error ... %! logirnd (1, 2, 1.2) %!error ... %! logirnd (1, 2, ones (2)) %!error ... %! logirnd (1, 2, [2 0 2.5]) %!error ... %! logirnd (1, 2, 2, 1.5, 5) %!error ... %! logirnd (2, ones (2), 3) %!error ... %! logirnd (2, ones (2), [3, 2]) %!error ... %! logirnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/loglcdf.m000066400000000000000000000163041524624707500246270ustar00rootroot00000000000000## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} loglcdf (@var{x}, @var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{p} =} loglcdf (@var{x}, @var{mu}, @var{sigma}, @qcode{'upper'}) ## ## Loglogistic cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the loglogistic distribution with mean parameter @var{mu} and scale ## parameter @var{sigma}. The size of @var{p} is the common size of @var{x}, ## @var{mu}, and @var{sigma}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Mean of logarithmic values @var{mu} must be a non-negative real value, scale ## parameter of logarithmic values @var{sigma} must be a positive real value and ## @var{x} is supported in the range @math{[0,Inf)}, otherwise @qcode{NaN} is ## returned. ## ## @code{@var{p} = loglcdf (@var{x}, @var{mu}, @var{sigma}, "upper")} computes ## the upper tail probability of the log-logistic distribution with parameters ## @var{mu} and @var{sigma}, at the values in @var{x}. ## ## Further information about the loglogistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-logistic_distribution} ## ## OCTAVE/MATLAB use an alternative parameterization given by the pair ## @math{μ, σ}, i.e. @var{mu} and @var{sigma}, in analogy with the logistic ## distribution. Their relation to the @math{α} and @math{b} parameters used ## in Wikipedia are given below: ## ## @itemize ## @item @qcode{@var{mu} = log (@var{a})} ## @item @qcode{@var{sigma} = 1 / @var{a}} ## @end itemize ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{loglinv, loglpdf, loglrnd, loglfit, logllike, loglstat} ## @end deftypefn function p = loglcdf (x, mu, sigma, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("loglcdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 3) if (! strcmpi (uflag, 'upper')) error ("loglcdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, MU, and SIGMA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (sigma)) [retval, x, mu, sigma] = common_size (x, mu, sigma); if (retval > 0) error ("loglcdf: X, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("loglcdf: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma)) error ("loglcdf: X, MU, and SIGMA must not be complex."); endif ## Check for invalid points mu(mu < 0) = NaN; sigma(sigma <= 0) = NaN; x(x <= 0) = realmin; ## Compute log-logistic CDF z = (log (x) - mu) ./ sigma; if (uflag) p = 1 ./ (1 + exp (z)); else p = 1 ./ (1 + exp (-z)); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single')); p = cast (p, 'single'); endif endfunction %!demo %! ## Plot various CDFs from the log-logistic distribution %! x = 0:0.001:2; %! p1 = loglcdf (x, log (1), 1/0.5); %! p2 = loglcdf (x, log (1), 1); %! p3 = loglcdf (x, log (1), 1/2); %! p4 = loglcdf (x, log (1), 1/4); %! p5 = loglcdf (x, log (1), 1/8); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c', x, p5, '-m') %! legend ({'σ = 2 (β = 0.5)', 'σ = 1 (β = 1)', 'σ = 0.5 (β = 2)', ... %! 'σ = 0.25 (β = 4)', 'σ = 0.125 (β = 8)'}, 'location', 'northwest') %! grid on %! title ('Log-logistic CDF') %! xlabel ('values in x') %! ylabel ('probability') %! text (0.05, 0.64, 'μ = 0 (α = 1), values of σ (β) as shown in legend') ## Test output %!shared out1, out2 %! out1 = [0, 0.5, 0.66666667, 0.75, 0.8, 0.83333333]; %! out2 = [0, 0.4174, 0.4745, 0.5082, 0.5321, 0.5506]; %!assert_equal (loglcdf ([0:5], 0, 1), out1, 1e-8) %!assert_equal (loglcdf ([0:5], 0, 1, 'upper'), 1 - out1, 1e-8) %!assert_equal (loglcdf ([0:5], 0, 1), out1, 1e-8) %!assert_equal (loglcdf ([0:5], 0, 1, 'upper'), 1 - out1, 1e-8) %!assert_equal (loglcdf ([0:5], 1, 3), out2, 1e-4) %!assert_equal (loglcdf ([0:5], 1, 3, 'upper'), 1 - out2, 1e-4) ## Test class of input preserved %!assert_equal (class (loglcdf (single (1), 2, 3)), "single") %!assert_equal (class (loglcdf (1, single (2), 3)), "single") %!assert_equal (class (loglcdf (1, 2, single (3))), "single") ## Test input validation %!error loglcdf (1) %!error loglcdf (1, 2) %!error ... %! loglcdf (1, 2, 3, 4) %!error ... %! loglcdf (1, 2, 3, 'uper') %!error ... %! loglcdf (1, ones (2), ones (3)) %!error ... %! loglcdf (1, ones (2), ones (3), 'upper') %!error ... %! loglcdf (ones (2), 1, ones (3)) %!error ... %! loglcdf (ones (2), 1, ones (3), 'upper') %!error ... %! loglcdf (ones (2), ones (3), 1) %!error ... %! loglcdf (ones (2), ones (3), 1, 'upper') %!error loglcdf (int32 (2), 2, 3) %!error loglcdf (true, 2, 3) %!error loglcdf ('a', 2, 3) %!error loglcdf (i, 2, 3) %!error loglcdf (i, 2, 3, 'upper') %!error loglcdf (1, i, 3) %!error loglcdf (1, i, 3, 'upper') %!error loglcdf (1, 2, i) %!error loglcdf (1, 2, i, 'upper') statistics-release-1.9.2/inst/Distribution_Functions/loglinv.m000066400000000000000000000134101524624707500246620ustar00rootroot00000000000000## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} loglinv (@var{p}, @var{mu}, @var{sigma}) ## ## Inverse of the log-logistic cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the log-logistic distribution with mean parameter @var{mu} and scale ## parameter @var{sigma}. The size of @var{x} is the common size of @var{p}, ## @var{mu}, and @var{sigma}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Mean of logarithmic values @var{mu} must be a non-negative real value, scale ## parameter of logarithmic values @var{sigma} must be a positive real value and ## @var{p} is supported in the range @math{[0,1]}, otherwise @qcode{NaN} is ## returned. ## ## Further information about the loglogistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-logistic_distribution} ## ## OCTAVE/MATLAB use an alternative parameterization given by the pair ## @math{μ, σ}, i.e. @var{mu} and @var{sigma}, in analogy with the logistic ## distribution. Their relation to the @math{α} and @math{b} parameters used ## in Wikipedia are given below: ## ## @itemize ## @item @qcode{@var{mu} = log (@var{a})} ## @item @qcode{@var{sigma} = 1 / @var{a}} ## @end itemize ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{loglcdf, loglpdf, loglrnd, loglfit, logllike, loglstat} ## @end deftypefn function x = loglinv (p, mu, sigma) ## Check for valid number of input arguments if (nargin < 3) error ("loglinv: function called with too few input arguments."); endif ## Check for common size of P, MU, and SIGMA if (! isscalar (p) || ! isscalar (mu) || ! isscalar (sigma)) [retval, p, mu, sigma] = common_size (p, mu, sigma); if (retval > 0) error ("loglinv: P, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for P, MU, and SIGMA being double or single if (! (isfloat (p) && isfloat (mu) && isfloat (sigma))) error ("loglinv: P, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (p) || iscomplex (mu) || iscomplex (sigma)) error ("loglinv: P, MU, and SIGMA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (mu, 'single') || isa (sigma, 'single')); x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ## Check for valid points k = (p >= 0) & (p <= 1) & (mu >= 0) & (sigma > 0); ## Compute the log-logistic iCDF x(k) = exp (logit (p(k)) .* sigma(k) + mu(k)); endfunction %!demo %! ## Plot various iCDFs from the log-logistic distribution %! p = 0.001:0.001:0.999; %! x1 = loglinv (p, log (1), 1/0.5); %! x2 = loglinv (p, log (1), 1); %! x3 = loglinv (p, log (1), 1/2); %! x4 = loglinv (p, log (1), 1/4); %! x5 = loglinv (p, log (1), 1/8); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c', p, x5, '-m') %! ylim ([0, 20]) %! grid on %! legend ({'σ = 2 (β = 0.5)', 'σ = 1 (β = 1)', 'σ = 0.5 (β = 2)', ... %! 'σ = 0.25 (β = 4)', 'σ = 0.125 (β = 8)'}, 'location', 'northwest') %! title ('Log-logistic iCDF') %! xlabel ('probability') %! ylabel ('x') %! text (0.03, 12.5, 'μ = 0 (α = 1), values of σ (β) as shown in legend') ## Test output %!shared p, out1, out2 %! p = [-1, 0, 0.2, 0.5, 0.8, 0.95, 1, 2]; %! out1 = [NaN, 0, 0.25, 1, 4, 19, Inf, NaN]; %! out2 = [NaN, 0, 0.0424732, 2.718282, 173.970037, 18644.695061, Inf, NaN]; %!assert_equal (loglinv (p, 0, 1), out1, 1e-8) %!assert_equal (loglinv (p, 0, 1), out1, 1e-8) %!assert_equal (loglinv (p, 1, 3), out2, 1e-6) ## Test class of input preserved %!assert_equal (class (loglinv (single (1), 2, 3)), "single") %!assert_equal (class (loglinv (1, single (2), 3)), "single") %!assert_equal (class (loglinv (1, 2, single (3))), "single") ## Test input validation %!error loglinv (1) %!error loglinv (1, 2) %!error ... %! loglinv (1, ones (2), ones (3)) %!error ... %! loglinv (ones (2), 1, ones (3)) %!error ... %! loglinv (ones (2), ones (3), 1) %!error loglinv (int32 (2), 2, 3) %!error loglinv (true, 2, 3) %!error loglinv ('a', 2, 3) %!error loglinv (i, 2, 3) %!error loglinv (1, i, 3) %!error loglinv (1, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/loglpdf.m000066400000000000000000000134051524624707500246430ustar00rootroot00000000000000## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} loglpdf (@var{x}, @var{mu}, @var{sigma}) ## ## Loglogistic probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the loglogistic distribution with mean parameter @var{mu} and scale ## parameter @var{sigma}. The size of @var{y} is the common size of @var{x}, ## @var{mu}, and @var{sigma}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Mean of logarithmic values @var{mu} must be a non-negative real value, scale ## parameter of logarithmic values @var{sigma} must be a positive real value and ## @var{x} is supported in the range @math{[0,Inf)}, otherwise 0 is returned. ## ## Further information about the loglogistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-logistic_distribution} ## ## OCTAVE/MATLAB use an alternative parameterization given by the pair ## @math{μ, σ}, i.e. @var{mu} and @var{sigma}, in analogy with the logistic ## distribution. Their relation to the @math{α} and @math{b} parameters used ## in Wikipedia are given below: ## ## @itemize ## @item @qcode{@var{mu} = log (@var{a})} ## @item @qcode{@var{sigma} = 1 / @var{a}} ## @end itemize ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{loglcdf, loglinv, loglrnd, loglfit, logllike, loglstat} ## @end deftypefn function y = loglpdf (x, mu, sigma) ## Check for valid number of input arguments if (nargin < 3) error ("loglpdf: function called with too few input arguments."); endif ## Check for common size of X, MU, and SIGMA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (sigma)) [retval, x, mu, sigma] = common_size (x, mu, sigma); if (retval > 0) error ("loglpdf: X, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("loglpdf: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma)) error ("loglpdf: X, MU, and SIGMA must not be complex."); endif ## Check for invalid points mu(mu < 0) = NaN; sigma(sigma <= 0) = NaN; ## Compute log-logistic PDF a = exp (mu); b = 1./ sigma; y = ((b ./ a) .* (x ./ a) .^ (b - 1)) ./ ((1 + (x ./ a) .^ b) .^ 2); y(x <= 0) = 0; ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single')); y = cast (y, 'single'); endif endfunction %!demo %! ## Plot various PDFs from the log-logistic distribution %! x = 0.001:0.001:2; %! y1 = loglpdf (x, log (1), 1/0.5); %! y2 = loglpdf (x, log (1), 1); %! y3 = loglpdf (x, log (1), 1/2); %! y4 = loglpdf (x, log (1), 1/4); %! y5 = loglpdf (x, log (1), 1/8); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c', x, y5, '-m') %! grid on %! ylim ([0,3]) %! legend ({'σ = 2 (β = 0.5)', 'σ = 1 (β = 1)', 'σ = 0.5 (β = 2)', ... %! 'σ = 0.25 (β = 4)', 'σ = 0.125 (β = 8)'}, 'location', 'northeast') %! title ('Log-logistic PDF') %! xlabel ('values in x') %! ylabel ('density') %! text (0.1, 2.8, 'μ = 0 (α = 1), values of σ (β) as shown in legend') ## Test output %!shared out1, out2 %! out1 = [0, 0, 1, 0.2500, 0.1111, 0.0625, 0.0400, 0.0278, 0]; %! out2 = [0, 0, 0.0811, 0.0416, 0.0278, 0.0207, 0.0165, 0]; %!assert_equal (loglpdf ([-1,0,realmin,1:5,Inf], 0, 1), out1, 1e-4) %!assert_equal (loglpdf ([-1,0,realmin,1:5,Inf], 0, 1), out1, 1e-4) %!assert_equal (loglpdf ([-1:5,Inf], 1, 3), out2, 1e-4) ## Test class of input preserved %!assert_equal (class (loglpdf (single (1), 2, 3)), "single") %!assert_equal (class (loglpdf (1, single (2), 3)), "single") %!assert_equal (class (loglpdf (1, 2, single (3))), "single") ## Test input validation %!error loglpdf (1) %!error loglpdf (1, 2) %!error ... %! loglpdf (1, ones (2), ones (3)) %!error ... %! loglpdf (ones (2), 1, ones (3)) %!error ... %! loglpdf (ones (2), ones (3), 1) %!error loglpdf (int32 (2), 2, 3) %!error loglpdf (true, 2, 3) %!error loglpdf ('a', 2, 3) %!error loglpdf (i, 2, 3) %!error loglpdf (1, i, 3) %!error loglpdf (1, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/loglrnd.m000066400000000000000000000161601524624707500246560ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} loglrnd (@var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{r} =} loglrnd (@var{mu}, @var{sigma}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} loglrnd (@var{mu}, @var{sigma}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} loglrnd (@var{mu}, @var{sigma}, [@var{sz}]) ## ## Random arrays from the loglogistic distribution. ## ## @code{@var{r} = loglrnd (@var{mu}, @var{sigma})} returns an array of random ## numbers chosen from the loglogistic distribution with mean parameter @var{mu} ## and scale parameter @var{sigma}. The size of @var{r} is the common size of ## @var{mu} and @var{sigma}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Mean of logarithmic values @var{mu} must be a non-negative real value and ## scale parameter of logarithmic values @var{sigma} must be a positive real ## value. ## ## When called with mu single size argument, @code{loglrnd} returns mu square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with mu row vector of dimensions, @var{sz}. ## ## Further information about the loglogistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-logistic_distribution} ## ## OCTAVE/MATLAB use an alternative parameterization given by the pair ## @math{μ, σ}, i.e. @var{mu} and @var{sigma}, in analogy with the logistic ## distribution. Their relation to the @math{α} and @math{b} parameters used ## in Wikipedia are given below: ## ## @itemize ## @item @qcode{@var{mu} = log (@var{a})} ## @item @qcode{@var{sigma} = 1 / @var{a}} ## @end itemize ## ## @seealso{loglcdf, loglinv, loglpdf, loglfit, logllike, loglstat} ## @end deftypefn function r = loglrnd (mu, sigma, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("loglrnd: function called with too few input arguments."); endif ## Check for common size of MU, and SIGMA if (! isscalar (mu) || ! isscalar (sigma)) [retval, mu, sigma] = common_size (mu, sigma); if (retval > 0) error ("loglrnd: MU and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being reals if (iscomplex (mu) || iscomplex (sigma)) error ("loglrnd: MU and SIGMA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (mu); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("loglrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("loglrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (mu) && ! isequal (size (mu), size (ones (sz)))) error ("loglrnd: MU and SIGMA must be scalars or of size SZ."); endif ## Check for class type if (isa (mu, 'single') || isa (sigma, 'single')) is_type = 'single'; else is_type = 'double'; endif ## Generate random sample from log-logistic distribution u = rand (sz, is_type); r = exp (mu) .* (u ./ (1 - u)) .^ (sigma); ## Force output to NaN for invalid parameters MU and SIGMA k = (mu < 0 | sigma <= 0); r(k) = NaN; endfunction ## Test output %!assert_equal (size (loglrnd (1, 1)), [1, 1]) %!assert_equal (size (loglrnd (1, ones (2, 1))), [2, 1]) %!assert_equal (size (loglrnd (1, ones (2, 2))), [2, 2]) %!assert_equal (size (loglrnd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (loglrnd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (loglrnd (1, 1, 3)), [3, 3]) %!assert_equal (size (loglrnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (loglrnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (loglrnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (loglrnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (loglrnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (loglrnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (loglrnd (1, 1, [])), [0, 0]) %!assert_equal (size (loglrnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (loglrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (loglrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (loglrnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (loglrnd (1, 1)), "double") %!assert_equal (class (loglrnd (1, single (1))), "single") %!assert_equal (class (loglrnd (1, single ([1, 1]))), "single") %!assert_equal (class (loglrnd (single (1), 1)), "single") %!assert_equal (class (loglrnd (single ([1, 1]), 1)), "single") ## Test input validation %!error loglrnd () %!error loglrnd (1) %!error ... %! loglrnd (ones (3), ones (2)) %!error ... %! loglrnd (ones (2), ones (3)) %!error loglrnd (i, 2, 3) %!error loglrnd (1, i, 3) %!error ... %! loglrnd (1, 2, 1.2) %!error ... %! loglrnd (1, 2, ones (2)) %!error ... %! loglrnd (1, 2, [2 0 2.5]) %!error ... %! loglrnd (1, 2, 2, 1.5, 5) %!error ... %! loglrnd (2, ones (2), 3) %!error ... %! loglrnd (2, ones (2), [3, 2]) %!error ... %! loglrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/logncdf.m000066400000000000000000000230451524624707500246310ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} logncdf (@var{x}) ## @deftypefnx {statistics} {@var{p} =} logncdf (@var{x}, @var{mu}) ## @deftypefnx {statistics} {@var{p} =} logncdf (@var{x}, @var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{p} =} logncdf (@dots{}, @qcode{'upper'}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} logncdf (@var{x}, @var{mu}, @var{sigma}, @var{pcov}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} logncdf (@var{x}, @var{mu}, @var{sigma}, @var{pcov}, @var{alpha}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} logncdf (@dots{}, @qcode{'upper'}) ## ## Lognormal cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the lognormal distribution with mean parameter @var{mu} and ## standard deviation parameter @var{sigma}, each corresponding to the ## associated normal distribution. The size of @var{p} is the common size of ## @var{x}, @var{mu}, and @var{sigma}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## If a random variable follows this distribution, its logarithm is normally ## distributed with mean @var{mu} and standard deviation @var{sigma}. ## ## Default parameter values are @qcode{@var{mu} = 0} and ## @qcode{@var{sigma} = 1}. Both parameters must be reals and ## @qcode{@var{sigma} > 0}. For @qcode{@var{sigma} <= 0}, @qcode{NaN} is ## returned. ## ## When called with three output arguments, i.e. @qcode{[@var{p}, @var{plo}, ## @var{pup}]}, @code{logncdf} computes the confidence bounds for @var{p} when ## the input parameters @var{mu} and @var{sigma} are estimates. In such case, ## @var{pcov}, a @math{2*2} matrix containing the covariance matrix of the ## estimated parameters, is necessary. Optionally, @var{alpha}, which has a ## default value of 0.05, specifies the @qcode{100 * (1 - @var{alpha})} percent ## confidence bounds. @var{plo} and @var{pup} are arrays of the same size as ## @var{p} containing the lower and upper confidence bounds. ## ## @code{[@dots{}] = logncdf (@dots{}, "upper")} computes the upper tail ## probability of the log-normal distribution with parameters @var{mu} and ## @var{sigma}, at the values in @var{x}. ## ## Further information about the lognormal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-normal_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{logninv, lognpdf, lognrnd, lognfit, lognlike, lognstat} ## @end deftypefn function [varargout] = logncdf (x, varargin) ## Check for valid number of input arguments if (nargin < 1 || nargin > 6) error ("logncdf: invalid number of input arguments."); endif ## Check for "upper" flag if (nargin > 1 && strcmpi (varargin{end}, 'upper')) uflag = true; varargin(end) = []; elseif (nargin > 1 && ischar (varargin{end}) && ... ! strcmpi (varargin{end}, 'upper')) error ("logncdf: invalid argument for upper tail."); elseif (nargin > 2 && isempty (varargin{end})) uflag = false; varargin(end) = []; else uflag = false; endif ## Get extra arguments (if they exist) or add defaults if (numel (varargin) > 0) mu = varargin{1}; else mu = 0; endif if (numel (varargin) > 1) sigma = varargin{2}; else sigma = 1; endif if (numel (varargin) > 2) pcov = varargin{3}; ## Check for valid covariance matrix 2x2 if (! isequal (size (pcov), [2, 2])) error ("logncdf: invalid size of covariance matrix."); endif else ## Check that cov matrix is provided if 3 output arguments are requested if (nargout > 1) error ("logncdf: covariance matrix is required for confidence bounds."); endif pcov = []; endif if (numel (varargin) > 3) alpha = varargin{4}; ## Check for valid alpha value if (! isnumeric (alpha) || numel (alpha) !=1 || alpha <= 0 || alpha >= 1) error ("logncdf: invalid value for alpha."); endif else alpha = 0.05; endif ## Check for common size of X, MU, and SIGMA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (sigma)) [err, x, mu, sigma] = common_size (x, mu, sigma); if (err > 0) error ("logncdf: X, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("logncdf: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma)) error ("logncdf: X, MU, and SIGMA must not be complex."); endif ## Return NaN for out of range parameters. sigma(sigma <= 0) = NaN; ## Negative data would create complex values, which erfc cannot handle. x(x < 0) = 0; ## Compute lognormal cdf z = (log (x) - mu) ./ sigma; if (uflag) z = -z; endif p = 0.5 * erfc (-z ./ sqrt (2)); ## Compute confidence bounds (if requested) if (nargout >= 2) zvar = (pcov(1,1) + 2 * pcov(1,2) * z + pcov(2,2) * z .^ 2) ./ (sigma .^ 2); if (any (zvar(:) < 0)) error ("logncdf: bad covariance matrix."); endif normz = -norminv (alpha / 2); halfwidth = normz * sqrt (zvar); zlo = z - halfwidth; zup = z + halfwidth; plo = 0.5 * erfc (-zlo ./ sqrt (2)); pup = 0.5 * erfc (-zup ./ sqrt (2)); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Prepare output varargout{1} = cast (p, is_class); if (nargout > 1) varargout{2} = cast (plo, is_class); varargout{3} = cast (pup, is_class); endif endfunction %!demo %! ## Plot various CDFs from the log-normal distribution %! x = 0:0.01:3; %! p1 = logncdf (x, 0, 1); %! p2 = logncdf (x, 0, 0.5); %! p3 = logncdf (x, 0, 0.25); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r') %! grid on %! legend ({'μ = 0, σ = 1', 'μ = 0, σ = 0.5', 'μ = 0, σ = 0.25'}, ... %! 'location', 'southeast') %! title ('Log-normal CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-1, 0, 1, e, Inf]; %! y = [0, 0, 0.5, 1/2+1/2*erf(1/2), 1]; %!assert_equal (logncdf (x, zeros (1,5), sqrt (2)*ones (1,5)), y, eps) %!assert_equal (logncdf (x, zeros (1,5), sqrt (2)*ones (1,5), []), y, eps) %!assert_equal (logncdf (x, 0, sqrt (2)*ones (1,5)), y, eps) %!assert_equal (logncdf (x, zeros (1,5), sqrt (2)), y, eps) %!assert_equal (logncdf (x, [0 1 NaN 0 1], sqrt (2)), [0 0 NaN y(4:5)], eps) %!assert_equal (logncdf (x, 0, sqrt (2)*[0 NaN Inf 1 1]), [NaN NaN y(3:5)], eps) %!assert_equal (logncdf ([x(1:3) NaN x(5)], 0, sqrt (2)), [y(1:3) NaN y(5)], eps) ## Test class of input preserved %!assert_equal (logncdf ([x, NaN], 0, sqrt (2)), [y, NaN], eps) %!assert_equal (logncdf (single ([x, NaN]), 0, sqrt (2)), single ([y, NaN]), eps ('single')) %!assert_equal (logncdf ([x, NaN], single (0), sqrt (2)), single ([y, NaN]), eps ('single')) %!assert_equal (logncdf ([x, NaN], 0, single (sqrt (2))), single ([y, NaN]), eps ('single')) ## Test input validation %!error logncdf () %!error logncdf (1,2,3,4,5,6,7) %!error logncdf (1, 2, 3, 4, 'uper') %!error ... %! logncdf (ones (3), ones (2), ones (2)) %!error logncdf (2, 3, 4, [1, 2]) %!error ... %! [p, plo, pup] = logncdf (1, 2, 3) %!error [p, plo, pup] = ... %! logncdf (1, 2, 3, [1, 0; 0, 1], 0) %!error [p, plo, pup] = ... %! logncdf (1, 2, 3, [1, 0; 0, 1], 1.22) %!error [p, plo, pup] = ... %! logncdf (1, 2, 3, [1, 0; 0, 1], 'alpha', 'upper') %!error logncdf (int32 (2), 2, 2) %!error logncdf (true, 2, 2) %!error logncdf ('a', 2, 2) %!error logncdf (i, 2, 2) %!error logncdf (2, i, 2) %!error logncdf (2, 2, i) %!error ... %! [p, plo, pup] =logncdf (1, 2, 3, [1, 0; 0, -inf], 0.04) statistics-release-1.9.2/inst/Distribution_Functions/logninv.m000066400000000000000000000131221524624707500246640ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} logninv (@var{p}) ## @deftypefnx {statistics} {@var{x} =} logninv (@var{p}, @var{mu}) ## @deftypefnx {statistics} {@var{x} =} logninv (@var{p}, @var{mu}, @var{sigma}) ## ## Inverse of the lognormal cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the lognormal distribution with mean parameter @var{mu} and standard ## deviation parameter @var{sigma}, each corresponding to the associated normal ## distribution. The size of @var{x} is the common size of @var{p}, @var{mu}, ## and @var{sigma}. A scalar input functions as a constant matrix of the same ## size as the other inputs. ## ## If a random variable follows this distribution, its logarithm is normally ## distributed with mean @var{mu} and standard deviation @var{sigma}. ## ## Default parameter values are @qcode{@var{mu} = 0} and ## @qcode{@var{sigma} = 1}. Both parameters must be reals and ## @qcode{@var{sigma} > 0}. For @qcode{@var{sigma} <= 0}, @qcode{NaN} is ## returned. ## ## Further information about the lognormal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-normal_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{logncdf, lognpdf, lognrnd, lognfit, lognlike, lognstat} ## @end deftypefn function x = logninv (p, mu = 0, sigma = 1) ## Check for valid number of input arguments if (nargin < 1 || nargin > 3) print_usage (); endif ## Check for common size of P, MU, and SIGMA if (! isscalar (p) || ! isscalar (mu) || ! isscalar (sigma)) [retval, p, mu, sigma] = common_size (p, mu, sigma); if (retval > 0) error ("logninv: X, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (p) && isfloat (mu) && isfloat (sigma))) error ("logninv: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (p) || iscomplex (mu) || iscomplex (sigma)) error ("logninv: X, MU, and SIGMA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (mu, 'single') || isa (sigma, 'single')) x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ## Compute lognormal iCDF k = ! (p >= 0) | ! (p <= 1) | ! (sigma > 0) | ! (sigma < Inf); x(k) = NaN; k = (p == 1) & (sigma > 0) & (sigma < Inf); x(k) = Inf; k = (p >= 0) & (p < 1) & (sigma > 0) & (sigma < Inf); if (isscalar (mu) && isscalar (sigma)) x(k) = exp (mu) .* exp (sigma .* (-sqrt (2) * erfcinv (2 * p(k)))); else x(k) = exp (mu(k)) .* exp (sigma(k) .* (-sqrt (2) * erfcinv (2 * p(k)))); endif endfunction %!demo %! ## Plot various iCDFs from the log-normal distribution %! p = 0.001:0.001:0.999; %! x1 = logninv (p, 0, 1); %! x2 = logninv (p, 0, 0.5); %! x3 = logninv (p, 0, 0.25); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r') %! grid on %! ylim ([0, 3]) %! legend ({'μ = 0, σ = 1', 'μ = 0, σ = 0.5', 'μ = 0, σ = 0.25'}, ... %! 'location', 'northwest') %! title ('Log-normal iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p %! p = [-1 0 0.5 1 2]; %!assert_equal (logninv (p, ones (1,5), ones (1,5)), [NaN 0 e Inf NaN], 2*eps) %!assert_equal (logninv (p, 1, ones (1,5)), [NaN 0 e Inf NaN], 2*eps) %!assert_equal (logninv (p, ones (1,5), 1), [NaN 0 e Inf NaN], 2*eps) %!assert_equal (logninv (p, [1 1 NaN 0 1], 1), [NaN 0 NaN Inf NaN]) %!assert_equal (logninv (p, 1, [1 0 NaN Inf 1]), [NaN NaN NaN NaN NaN]) %!assert_equal (logninv ([p(1:2) NaN p(4:5)], 1, 2), [NaN 0 NaN Inf NaN]) ## Test class of input preserved %!assert_equal (logninv ([p, NaN], 1, 1), [NaN 0 e Inf NaN NaN], 2*eps) %!assert_equal (logninv (single ([p, NaN]), 1, 1), single ([NaN 0 e Inf NaN NaN])) %!assert_equal (logninv ([p, NaN], single (1), 1), single ([NaN 0 e Inf NaN NaN])) %!assert_equal (logninv ([p, NaN], 1, single (1)), single ([NaN 0 e Inf NaN NaN])) ## Test input validation %!error logninv (int32 (2), 0, 1) %!error logninv (true, 0, 1) %!error logninv ('a', 0, 1) %!error logninv () %!error logninv (1,2,3,4) %!error logninv (ones (3), ones (2), ones (2)) %!error logninv (ones (2), ones (3), ones (2)) %!error logninv (ones (2), ones (2), ones (3)) %!error logninv (i, 2, 2) %!error logninv (2, i, 2) %!error logninv (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/lognpdf.m000066400000000000000000000130501524624707500246410ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} lognpdf (@var{x}) ## @deftypefnx {statistics} {@var{y} =} lognpdf (@var{x}, @var{mu}) ## @deftypefnx {statistics} {@var{y} =} lognpdf (@var{x}, @var{mu}, @var{sigma}) ## ## Lognormal probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the lognormal distribution with mean parameter @var{mu} and standard ## deviation parameter @var{sigma}, each corresponding to the associated normal ## distribution. The size of @var{y} is the common size of @var{p}, @var{mu}, ## and @var{sigma}. A scalar input functions as a constant matrix of the same ## size as the other inputs. ## ## If a random variable follows this distribution, its logarithm is normally ## distributed with mean @var{mu} and standard deviation @var{sigma}. ## ## Default parameter values are @qcode{@var{mu} = 0} and ## @qcode{@var{sigma} = 1}. Both parameters must be reals and ## @qcode{@var{sigma} > 0}. For @qcode{@var{sigma} <= 0}, @qcode{NaN} is ## returned. ## ## Further information about the lognormal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-normal_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{logncdf, logninv, lognrnd, lognfit, lognlike, lognstat} ## @end deftypefn function y = lognpdf (x, mu = 0, sigma = 1) ## Check for valid number of input arguments if (nargin < 1 || nargin > 3) print_usage (); endif ## Check for common size of P, MU, and SIGMA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (sigma)) [retval, x, mu, sigma] = common_size (x, mu, sigma); if (retval > 0) error ("lognpdf: X, MU, and SIGMA must be of common size or scalars"); endif endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("lognpdf: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("lognpdf: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma)) error ("lognpdf: X, MU, and SIGMA must not be complex"); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Compute lognormal PDF k = isnan (x) | ! (sigma > 0) | ! (sigma < Inf); y(k) = NaN; k = (x > 0) & (x < Inf) & (sigma > 0) & (sigma < Inf); if (isscalar (mu) && isscalar (sigma)) y(k) = normpdf (log (x(k)), mu, sigma) ./ x(k); else y(k) = normpdf (log (x(k)), mu(k), sigma(k)) ./ x(k); endif endfunction %!demo %! ## Plot various PDFs from the log-normal distribution %! x = 0:0.01:5; %! y1 = lognpdf (x, 0, 1); %! y2 = lognpdf (x, 0, 0.5); %! y3 = lognpdf (x, 0, 0.25); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r') %! grid on %! ylim ([0, 2]) %! legend ({'μ = 0, σ = 1', 'μ = 0, σ = 0.5', 'μ = 0, σ = 0.25'}, ... %! 'location', 'northeast') %! title ('Log-normal PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1 0 e Inf]; %! y = [0, 0, 1/(e*sqrt(2*pi)) * exp(-1/2), 0]; %!assert_equal (lognpdf (x, zeros (1,4), ones (1,4)), y, eps) %!assert_equal (lognpdf (x, 0, ones (1,4)), y, eps) %!assert_equal (lognpdf (x, zeros (1,4), 1), y, eps) %!assert_equal (lognpdf (x, [0 1 NaN 0], 1), [0 0 NaN y(4)], eps) %!assert_equal (lognpdf (x, 0, [0 NaN Inf 1]), [NaN NaN NaN y(4)], eps) %!assert_equal (lognpdf ([x, NaN], 0, 1), [y, NaN], eps) ## Test class of input preserved %!assert_equal (lognpdf (single ([x, NaN]), 0, 1), single ([y, NaN]), eps ('single')) %!assert_equal (lognpdf ([x, NaN], single (0), 1), single ([y, NaN]), eps ('single')) %!assert_equal (lognpdf ([x, NaN], 0, single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error lognpdf (int32 (2), 0, 1) %!error lognpdf (true, 0, 1) %!error lognpdf ('a', 0, 1) %!error lognpdf () %!error lognpdf (1,2,3,4) %!error lognpdf (ones (3), ones (2), ones (2)) %!error lognpdf (ones (2), ones (3), ones (2)) %!error lognpdf (ones (2), ones (2), ones (3)) %!error lognpdf (i, 2, 2) %!error lognpdf (2, i, 2) %!error lognpdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/lognrnd.m000066400000000000000000000160601524624707500246570ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} lognrnd (@var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{r} =} lognrnd (@var{mu}, @var{sigma}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} lognrnd (@var{mu}, @var{sigma}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} lognrnd (@var{mu}, @var{sigma}, [@var{sz}]) ## ## Random arrays from the lognormal distribution. ## ## @code{@var{r} = lognrnd (@var{mu}, @var{sigma})} returns an array of random ## numbers chosen from the lognormal distribution with mean parameter @var{mu} ## and standard deviation parameter @var{sigma}, each corresponding to the ## associated normal distribution. The size of @var{r} is the common size of ## @var{mu}, and @var{sigma}. A scalar input functions as a constant matrix of ## the same size as the other inputs. Both parameters must be reals and ## @qcode{@var{sigma} > 0}. For @qcode{@var{sigma} <= 0}, @qcode{NaN} is ## returned. ## ## Both parameters must be reals and @qcode{@var{sigma} > 0}. ## For @qcode{@var{sigma} <= 0}, @qcode{NaN} is returned. ## ## When called with a single size argument, @code{lognrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the lognormal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-normal_distribution} ## ## @seealso{logncdf, logninv, lognpdf, lognfit, lognlike, lognstat} ## @end deftypefn function r = lognrnd (mu, sigma, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("lognrnd: function called with too few input arguments."); endif ## Check for common size of P, MU, and SIGMA if (! isscalar (mu) || ! isscalar (sigma)) [retval, mu, sigma] = common_size (mu, sigma); if (retval > 0) error ("lognrnd: MU and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being reals if (iscomplex (mu) || iscomplex (sigma)) error ("lognrnd: MU and SIGMA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (mu); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("lognrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("lognrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (mu) && ! isequal (size (mu), size (ones (sz)))) error ("lognrnd: MU and SIGMA must be scalars or of size SZ."); endif ## Check for class type if (isa (mu, 'single') || isa (sigma, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from lognormal distribution if (isscalar (mu) && isscalar (sigma)) if ((sigma > 0) && (sigma < Inf)) r = exp (mu + sigma * randn (sz, cls)); else r = NaN (sz, cls); endif else r = exp (mu + sigma .* randn (sz, cls)); k = (sigma < 0) | (sigma == Inf); r(k) = NaN; endif endfunction ## Test output %!assert_equal (size (lognrnd (1, 1)), [1, 1]) %!assert_equal (size (lognrnd (1, ones (2, 1))), [2, 1]) %!assert_equal (size (lognrnd (1, ones (2, 2))), [2, 2]) %!assert_equal (size (lognrnd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (lognrnd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (lognrnd (1, 1, 3)), [3, 3]) %!assert_equal (size (lognrnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (lognrnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (lognrnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (lognrnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (lognrnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (lognrnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (lognrnd (1, 1, [])), [0, 0]) %!assert_equal (size (lognrnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (lognrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (lognrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (lognrnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (lognrnd (1, 1)), "double") %!assert_equal (class (lognrnd (1, single (1))), "single") %!assert_equal (class (lognrnd (1, single ([1, 1]))), "single") %!assert_equal (class (lognrnd (single (1), 1)), "single") %!assert_equal (class (lognrnd (single ([1, 1]), 1)), "single") ## Test input validation %!error lognrnd () %!error lognrnd (1) %!error ... %! lognrnd (ones (3), ones (2)) %!error ... %! lognrnd (ones (2), ones (3)) %!error lognrnd (i, 2, 3) %!error lognrnd (1, i, 3) %!error ... %! lognrnd (1, 2, 1.2) %!error ... %! lognrnd (1, 2, ones (2)) %!error ... %! lognrnd (1, 2, [2 0 2.5]) %!error ... %! lognrnd (1, 2, 2, 1.5, 5) %!error ... %! lognrnd (2, ones (2), 3) %!error ... %! lognrnd (2, ones (2), [3, 2]) %!error ... %! lognrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/mnpdf.m000066400000000000000000000121121524624707500243120ustar00rootroot00000000000000## Copyright (C) 2012 Arno Onken ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} mnpdf (@var{x}, @var{pk}) ## ## Multinomial probability density function (PDF). ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{x} is vector with a single sample of a multinomial distribution with ## parameter @var{pk} or a matrix of random samples from multinomial ## distributions. In the latter case, each row of @var{x} is a sample from a ## multinomial distribution with the corresponding row of @var{pk} being its ## parameter. ## ## @item ## @var{pk} is a vector with the probabilities of the categories or a matrix ## with each row containing the probabilities of a multinomial sample. ## @end itemize ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{y} is a vector of probabilities of the random samples @var{x} from the ## multinomial distribution with corresponding parameter @var{pk}. The parameter ## @var{n} of the multinomial distribution is the sum of the elements of each ## row of @var{x}. The length of @var{y} is the number of columns of @var{x}. ## If a row of @var{pk} does not sum to @code{1}, then the corresponding element ## of @var{y} will be @code{NaN}. ## @end itemize ## ## @subheading Examples ## ## @example ## @group ## x = [1, 4, 2]; ## pk = [0.2, 0.5, 0.3]; ## y = mnpdf (x, pk); ## @end group ## ## @group ## x = [1, 4, 2; 1, 0, 9]; ## pk = [0.2, 0.5, 0.3; 0.1, 0.1, 0.8]; ## y = mnpdf (x, pk); ## @end group ## @end example ## ## @subheading References ## ## @enumerate ## @item ## Wendy L. Martinez and Angel R. Martinez. @cite{Computational Statistics ## Handbook with MATLAB}. Appendix E, pages 547-557, Chapman & Hall/CRC, 2001. ## ## @item ## Merran Evans, Nicholas Hastings and Brian Peacock. @cite{Statistical ## Distributions}. pages 134-136, Wiley, New York, third edition, 2000. ## @end enumerate ## ## Input arguments must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted to ## @qcode{double}, so the result is always a probability. MATLAB is ## inconsistent here: for several of the discrete distributions it returns the ## result in the integer class of the input, truncating a probability to ## @math{0} or @math{1}. ## ## @seealso{mnrnd} ## @end deftypefn function y = mnpdf (x, pk) # Check arguments if (nargin != 2) print_usage (); endif ## Check for X and PK being double, single, or integer if (! (isnumeric (x) && isnumeric (pk))) error ("mnpdf: X and PK must be double, single, or integer."); endif ## Integer input is promoted to double, so the result is a probability ## rather than a value truncated to the input's integer type. if (isinteger (x)) x = double (x); endif if (isinteger (pk)) pk = double (pk); endif if (! ismatrix (x) || any (x(:) < 0 | round (x(:) != x(:)))) error ("mnpdf: X must be a matrix of non-negative integer values."); endif if (! ismatrix (pk) || any (pk(:) < 0)) error ("mnpdf: PK must be a non-empty matrix with rows of probabilities."); endif # Adjust input sizes if (! isvector (x) || ! isvector (pk)) if (isvector (x)) x = x(:)'; endif if (isvector (pk)) pk = pk(:)'; endif if (size (x, 1) == 1 && size (pk, 1) > 1) x = repmat (x, size (pk, 1), 1); elseif (size (x, 1) > 1 && size (pk, 1) == 1) pk = repmat (pk, size (x, 1), 1); endif endif # Continue argument check if (any (size (x) != size (pk))) error ("mnpdf: X and PK must have compatible sizes."); endif # Count total number of elements of each multinomial sample n = sum (x, 2); # Compute probability density function of the multinomial distribution t = x .* log (pk); t(x == 0) = 0; y = exp (gammaln (n+1) - sum (gammaln (x+1), 2) + sum (t, 2)); # Set invalid rows to NaN k = (abs (sum (pk, 2) - 1) > 1e-6); y(k) = NaN; endfunction %!test %! x = [1, 4, 2]; %! pk = [0.2, 0.5, 0.3]; %! y = mnpdf (x, pk); %! assert_equal (y, 0.11812, 0.001); %!test %! x = [1, 4, 2; 1, 0, 9]; %! pk = [0.2, 0.5, 0.3; 0.1, 0.1, 0.8]; %! y = mnpdf (x, pk); %! assert_equal (y, [0.11812; 0.13422], 0.001); ## Test input validation %!error mnpdf ([true, true], [0.3, 0.7]) %!error mnpdf ('ab', [0.3, 0.7]) %!assert_equal (class (mnpdf (int32 ([1, 2]), [0.3, 0.7])), 'double') statistics-release-1.9.2/inst/Distribution_Functions/mnrnd.m000066400000000000000000000140621524624707500243320ustar00rootroot00000000000000## Copyright (C) 2012 Arno Onken ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} mnrnd (@var{n}, @var{pk}) ## @deftypefnx {statistics} {@var{r} =} mnrnd (@var{n}, @var{pk}, @var{s}) ## ## Random arrays from the multinomial distribution. ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{n} is the first parameter of the multinomial distribution. @var{n} can ## be scalar or a vector containing the number of trials of each multinomial ## sample. The elements of @var{n} must be non-negative integers. ## ## @item ## @var{pk} is the second parameter of the multinomial distribution. @var{pk} ## can be a vector with the probabilities of the categories or a matrix with ## each row containing the probabilities of a multinomial sample. If @var{pk} ## has more than one row and @var{n} is non-scalar, then the number of rows of ## @var{pk} must match the number of elements of @var{n}. ## ## @item ## @var{s} is the number of multinomial samples to be generated. @var{s} must ## be a non-negative integer. If @var{s} is specified, then @var{n} must be ## scalar and @var{pk} must be a vector. ## @end itemize ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{r} is a matrix of random samples from the multinomial distribution with ## corresponding parameters @var{n} and @var{pk}. Each row corresponds to one ## multinomial sample. The number of columns, therefore, corresponds to the ## number of columns of @var{pk}. If @var{s} is not specified, then the number ## of rows of @var{r} is the maximum of the number of elements of @var{n} and ## the number of rows of @var{pk}. If a row of @var{pk} does not sum to ## @code{1}, then the corresponding row of @var{r} will contain only @code{NaN} ## values. ## @end itemize ## ## @subheading Examples ## ## @example ## @group ## n = 10; ## pk = [0.2, 0.5, 0.3]; ## r = mnrnd (n, pk); ## @end group ## ## @group ## n = 10 * ones (3, 1); ## pk = [0.2, 0.5, 0.3]; ## r = mnrnd (n, pk); ## @end group ## ## @group ## n = (1:2)'; ## pk = [0.2, 0.5, 0.3; 0.1, 0.1, 0.8]; ## r = mnrnd (n, pk); ## @end group ## @end example ## ## @subheading References ## ## @enumerate ## @item ## Wendy L. Martinez and Angel R. Martinez. @cite{Computational Statistics ## Handbook with MATLAB}. Appendix E, pages 547-557, Chapman & Hall/CRC, 2001. ## ## @item ## Merran Evans, Nicholas Hastings and Brian Peacock. @cite{Statistical ## Distributions}. pages 134-136, Wiley, New York, third edition, 2000. ## @end enumerate ## ## @seealso{mnpdf} ## @end deftypefn function r = mnrnd (n, pk, s) # Check arguments if (nargin == 3) if (! isscalar (n) || n < 0 || round (n) != n) error ("mnrnd: N must be a non-negative integer."); endif if (! isvector (pk) || any (pk < 0 | pk > 1)) error ("mnrnd: PK must be a vector of probabilities."); endif if (! isscalar (s) || s < 0 || round (s) != s) error ("mnrnd: S must be a non-negative integer."); endif elseif (nargin == 2) if (isvector (pk) && size (pk, 1) > 1) pk = pk'; endif if (! isvector (n) || any (n < 0 | round (n) != n) || size (n, 2) > 1) error ("mnrnd: N must be a non-negative integer column vector."); endif if (! ismatrix (pk) || isempty (pk) || any (pk < 0 | pk > 1)) error (strcat ("mnrnd: PK must be a non-empty matrix with", ... " rows of probabilities.")); endif if (! isscalar (n) && size (pk, 1) > 1 && length (n) != size (pk, 1)) error ("mnrnd: the length of N must match the number of rows of PK."); endif else print_usage (); endif # Adjust input sizes if (nargin == 3) n = n * ones (s, 1); pk = repmat (pk(:)', s, 1); elseif (nargin == 2) if (isscalar (n) && size (pk, 1) > 1) n = n * ones (size (pk, 1), 1); elseif (size (pk, 1) == 1) pk = repmat (pk, length (n), 1); endif endif sz = size (pk); # Upper bounds of categories ub = cumsum (pk, 2); # Make sure that the greatest upper bound is 1 gub = ub(:, end); ub(:, end) = 1; # Lower bounds of categories lb = [zeros(sz(1), 1) ub(:, 1:(end-1))]; # Draw multinomial samples r = zeros (sz); for i = 1:sz(1) # Draw uniform random numbers r_tmp = repmat (rand (n(i), 1), 1, sz(2)); # Compare the random numbers of r_tmp to the cumulated probabilities of pk # and count the number of samples for each category r(i, :) = sum (r_tmp <= repmat (ub(i, :), n(i), 1) & ... r_tmp > repmat (lb(i, :), n(i), 1), 1); endfor # Set invalid rows to NaN k = (abs (gub - 1) > 1e-6); r(k, :) = NaN; endfunction %!test %! n = 10; %! pk = [0.2, 0.5, 0.3]; %! r = mnrnd (n, pk); %! assert_equal (size (r), size (pk)); %! assert_equal (all ((all (r >= 0))(:)), true); %! assert_equal (all ((all (round (r) == r))(:)), true); %! assert_equal (all ((sum (r) == n)(:)), true); %!test %! n = 10 * ones (3, 1); %! pk = [0.2, 0.5, 0.3]; %! r = mnrnd (n, pk); %! assert_equal (size (r), [length(n), length(pk)]); %! assert_equal (all ((all (r >= 0))(:)), true); %! assert_equal (all ((all (round (r) == r))(:)), true); %! assert_equal (all ((all (sum (r, 2) == n))(:)), true); %!test %! n = (1:2)'; %! pk = [0.2, 0.5, 0.3; 0.1, 0.1, 0.8]; %! r = mnrnd (n, pk); %! assert_equal (size (r), size (pk)); %! assert_equal (all ((all (r >= 0))(:)), true); %! assert_equal (all ((all (round (r) == r))(:)), true); %! assert_equal (all ((all (sum (r, 2) == n))(:)), true); statistics-release-1.9.2/inst/Distribution_Functions/mvncdf.m000066400000000000000000000422651524624707500244770ustar00rootroot00000000000000## Copyright (C) 2008 Arno Onken ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} mvncdf (@var{x}) ## @deftypefnx {statistics} {@var{p} =} mvncdf (@var{x}, @var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{p} =} mvncdf (@var{x_lo}, @var{x_up}, @var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{p} =} mvncdf (@dots{}, @var{options}) ## @deftypefnx {statistics} {[@var{p}, @var{err}] =} mvncdf (@dots{}) ## ## Multivariate normal cumulative distribution function (CDF). ## ## @code{@var{p} = mvncdf (@var{x})} returns the cumulative probability of the ## multivariate normal distribution evaluated at each row of @var{x} with zero ## mean and an identity covariance matrix. The rows of matrix @var{x} ## correspond to observations and its columns to variables. The return argument ## @var{p} is a column vector with the same number of rows as in @var{x}. ## ## @code{@var{p} = mvncdf (@var{x}, @var{mu}, @var{sigma})} returns cumulative ## probability of the multivariate normal distribution evaluated at each row of ## @var{x} with mean @var{mu} and a covariance matrix @var{sigma}. @var{mu} can ## be either a scalar (the same of every variable) or a row vector with the same ## number of elements as the number of variables in @var{x}. @var{sigma} ## covariance matrix may be specified a row vector if it only contains variances ## along its diagonal and zero covariances of the diagonal. In such a case, the ## diagonal vector @var{sigma} must have the same number of elements as the ## number of variables (columns) in @var{x}. If you only want to specify sigma, ## you can pass an empty matrix for @var{mu}. ## ## The multivariate normal cumulative probability at @var{x} is defined as the ## probability that a random vector @math{V}, distributed as multivariate ## normal, will fall within the semi-infinite rectangle with upper limits ## defined by @var{x}. ## @itemize ## @item @math{Pr@{V(1)<=X(1), V(2)<=X(2), ... V(D)<=X(D)@}}. ## @end itemize ## ## @code{@var{p} = mvncdf (@var{x_lo}, @var{x_hi}, @var{mu}, @var{sigma})} ## returns the multivariate normal cumulative probability evaluated over the ## rectangle (hyper-rectangle for multivariate data in @var{x}) with lower and ## upper limits defined by @var{x_lo} and @var{x_hi}, respectively. ## ## @code{[@var{p}, @var{err}] = mvncdf (@dots{})} also returns an error estimate ## @var{err} in @var{p}. ## ## @code{@var{p} = mvncdf (@dots{}, @var{options})} specifies the structure, ## which controls specific parameters for the numerical integration used to ## compute @var{p}. The required fields are: ## ## @multitable @columnfractions 0.2 0.75 ## @item @qcode{'TolFun'} @tab Maximum absolute error tolerance. Default ## is 1e-8 for D < 4, or 1e-4 for D >= 4. Note that for bivariate normal cdf, ## the Octave implementation has a precision of more than 1e-10. ## ## @item @qcode{'MaxFunEvals'} @tab Maximum number of integrand ## evaluations. Default is 1e7 for D > 4. ## ## @item @qcode{'Display'} @tab Display options. Choices are @qcode{'off'} ## (default), @qcode{'iter'}, which shows the probability and estimated error at ## each repetition, and @qcode{'final'}, which shows the final probability and ## related error after the integrand has converged successfully. ## @end multitable ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{bvncdf, mvnpdf, mvnrnd} ## @end deftypefn function [p, err] = mvncdf (varargin) ## Check for valid number on input and output arguments narginchk (1,5); ## Check for X, MU, and SIGMA being double or single. The trailing ## 'options' struct, if present, is parsed separately below. numargs = varargin(! cellfun (@isstruct, varargin)); if (! all (cellfun (@isfloat, numargs))) error ("mvncdf: X, MU, and SIGMA must be double or single."); endif ## Check for 'options' structure and parse parameters or add defaults if (isstruct (varargin{end})) if (isfield (varargin{end}, 'TolFun')) TolFun = varargin{end}.TolFun; else error ("mvncdf: options structure missing 'TolFun' field."); endif if (isempty (TolFun) && size (varargin{1}, 2) < 4) TolFun = 1e-8; elseif (isempty (TolFun) && size (varargin{1}, 2) < 26) TolFun = 1e-4; endif if (isfield (varargin{end}, 'MaxFunEvals')) MaxFunEvals = varargin{end}.MaxFunEvals; else error ("mvncdf: options structure missing 'MaxFunEvals' field."); endif if (isempty (MaxFunEvals)) MaxFunEvals = 1e7; endif if (isfield (varargin{end}, 'Display')) Display = varargin{end}.Display; else error ("mvncdf: options structure missing 'Display' field."); endif DispOptions = {'off', 'final', 'iter'}; if (sum (any (strcmpi (Display, DispOptions))) == 0) error ("mvncdf: 'Display' field in 'options' has invalid value."); endif rem_nargin = nargin - 1; else if (size (varargin{1}, 2) < 4) TolFun = 1e-8; elseif (size (varargin{1}, 2) < 26) TolFun = 1e-4; endif MaxFunEvals = 1e7; Display = 'off'; rem_nargin = nargin; endif ## Check for X of X_lo and X_up if (rem_nargin < 4) # MVNCDF(X_UP,MU,SIGMA) x_up_Only = true; x_up = varargin{1}; ## Check for x being a matrix if (! ismatrix (x_up)) error ("mvncdf: X must be a matrix."); endif ## Create x_lo according to data type of x_lo x_lo = - Inf (size (x_up)); if isa (x_up, 'single') x_lo = single (x_lo); endif ## Check for mu and sigma arguments if (rem_nargin > 1) mu = varargin{2}; else mu = []; endif if (rem_nargin > 2) sigma = varargin{3}; else sigma = []; endif else # MVNCDF(X_LO,X_UP,MU,SIGMA) x_up_Only = false; x_lo = varargin{1}; x_up = varargin{2}; mu = varargin{3}; sigma = varargin{4}; ## Check for x_lo and x_up being matrices of the same size ## and that they define increasing limits if (! ismatrix (x_lo) || ! ismatrix (x_up)) error ("mvncdf: X_LO and X_UP must be matrices."); endif if (size (x_lo) != size (x_up)) error ("mvncdf: X_LO and X_UP must have the same size."); endif if (any (any (x_lo > x_up))) error ("mvncdf: X_LO and X_UP must define increasing limits."); endif endif ## Check if data is single or double class is_type = 'double'; if (isa (x_lo, 'single')) is_type = 'single'; endif ## Get size of data [n_x, d_x] = size (x_lo); ## Center data according to mu if (isempty (mu)) # already centered XLo0 = x_lo; XUp0 = x_up; elseif (isscalar (mu)) # mu is a scalar XLo0 = x_lo - mu; XUp0 = x_up - mu; elseif (isvector (mu)) # mu is a vector ## Get size of mu vector [n_mu, d_mu] = size (mu); if (d_mu != d_x) error ("mvncdf: wrong size of MU vector."); endif if (n_mu == 1 || n_mu == n_x) XLo0 = x_lo - mu; XUp0 = x_up - mu; else error ("mvncdf: wrong size of MU vector."); endif else error ("mvncdf: MU must be either empty, a scalar, or a vector."); endif ## Check how sigma was parsed if (isempty (sigma)) # already standardized ## If x_lo and x_up are column vectors, transpose them to row vectors if (d_x == 1) XLo0 = XLo0'; XUp0 = XUp0'; [n_x, d_x] = size (XUp0); endif sigmaIsDiag = true; sigma = ones (1, d_x); else ## Check if sigma parsed as diagonal vector if (size (sigma, 1) == 1 && size (sigma, 2) > 1) sigmaIsDiag = true; else sigmaIsDiag = false; endif ## If x_lo and x_up are column vectors, transpose them to row vectors if (d_x == 1) if (isequal (size (sigma), [1, n_x])) XLo0 = XLo0'; XUp0 = XUp0'; [n_x, d_x] = size (XUp0); elseif (! isscalar (mu)) error ("mvncdf: MU must be a scalar if SIGMA is a vector."); endif endif ## Check for sigma being a valid covariance matrix if (! sigmaIsDiag && (size (sigma, 1) != size (sigma, 2))) error ("mvncdf: covariance matrix SIGMA is not square."); elseif (! sigmaIsDiag && (! all (size (sigma) == [d_x, d_x]))) error (strcat ("mvncdf: covariance matrix SIGMA does", ... " not match dimensions in data.")); else ## If sigma is a covariance matrix check that it is positive semi-definite if (! sigmaIsDiag) [~, err] = chol (sigma); if (err != 0) error (strcat ("mvncdf: covariance matrix SIGMA must be", ... " positive semi-definite.")); endif else if (any (sigma <= 0)) error ("mvncdf: invalid SIGMA diagonal vector."); endif endif endif endif ## Standardize sigma and x data if (sigmaIsDiag) XLo0 = XLo0 ./ sqrt (sigma); XUp0 = XUp0 ./ sqrt (sigma); else s = sqrt (diag (sigma))'; XLo0 = XLo0 ./ s; XUp0 = XUp0 ./ s; Rho = sigma ./ (s * s'); endif ## Compute the cdf from standardized values. if (d_x == 1) p = normcdf (XUp0, 0, 1) - normcdf (XLo0, 0, 1); if (nargout > 1) err = NaN (size (p), is_type); endif elseif (sigmaIsDiag) p = prod (normcdf (XUp0, 0, 1) - normcdf (XLo0, 0, 1), 2); if (nargout > 1) err = NaN (size (p), is_type); endif elseif (d_x < 4) if (x_up_Only) # upper limit only if (d_x == 2) p = bvncdf (x_up, mu, sigma); else p = tvncdf (XUp0, Rho([2 3 6]), TolFun); endif else # lower and upper limits present ## Compute the probability over the rectangle as sums and differences ## of integrals over semi-infinite half-rectangles. For degenerate ## rectangles, force an exact zero by making each piece exactly zero. equalLimits = (XUp0 == XLo0); XUp0(equalLimits) = -Inf; XLo0(equalLimits) = -Inf; ## For bvncdf x_up(equalLimits) = -Inf; x_lo(equalLimits) = -Inf; p = zeros (n_x, 1, is_type); for i = 0:d_x k = nchoosek (1:d_x, i); for j = 1:size (k, 1) X = XUp0; X(:,k(j,:)) = XLo0(:,k(j,:)); if d_x == 2 x = x_up; x(:,k(j,:)) = x_lo(:,k(j,:)); p = p + (-1) ^ i * bvncdf (x, mu, sigma); else p = p + (-1) ^ i * tvncdf (X, Rho([2 3 6]), TolFun / 8); endif endfor endfor endif if (nargout > 1) err = repmat (cast (TolFun, is_type), size (p)); endif elseif (d_x < 26) p = zeros (n_x, 1, is_type); err = zeros (n_x, 1, is_type); for i = 1:n_x [p(i), err(i)] = __mvtcdfqmc__ ("mvncdf", XLo0(i,:), XUp0(i,:), ... Rho, Inf, TolFun, MaxFunEvals, ... Display); endfor else error ("mvncdf: too many dimensions in data (limit = 25 columns)."); endif ## Bound p in range [0, 1] p(p < 0) = 0; p(p > 1) = 1; endfunction ## function for computing a trivariate normal cdf function p = tvncdf (x, rho, tol) ## Get size of data n = size (x,1); ## Check if data is single or double class is_type = 'double'; if (isa (x, 'single') || isa (rho, 'single')) is_type = 'single'; endif ## Find a permutation that makes rho_32 == max(rho) [dum,imax] = max (abs (rho)); %#ok if imax == 1 % swap 1 and 3 rho_21 = rho(3); rho_31 = rho(2); rho_32 = rho(1); x = x(:,[3 2 1]); elseif imax == 2 % swap 1 and 2 rho_21 = rho(1); rho_31 = rho(3); rho_32 = rho(2); x = x(:,[2 1 3]); else % imax == 3 rho_21 = rho(1); rho_31 = rho(2); rho_32 = rho(3); endif phi = 0.5 * erfc (- x(:,1) / sqrt (2)); p1 = phi .* bvncdf (x(:,2:3), [], rho_32); if abs (rho_21) > 0 loLimit = 0; hiLimit = asin (rho_21); rho_j1 = rho_21; rho_k1 = rho_31; p2 = zeros (size (p1), is_type); for i = 1:n b1 = x(i,1); bj = x(i,2); bk = x(i,3); if isfinite (b1) && isfinite (bj) && ! isnan (bk) p2(i) = quadgk (@tvnIntegrand,loLimit,hiLimit,'AbsTol',tol/3,'RelTol',0); endif endfor else p2 = zeros (size (p1), is_type); endif if abs (rho_31) > 0 loLimit = 0; hiLimit = asin (rho_31); rho_j1 = rho_31; rho_k1 = rho_21; p3 = zeros (size (p1), is_type); for i = 1:n b1 = x(i,1); bj = x(i,3); bk = x(i,2); if isfinite (b1) && isfinite (bj) && ! isnan (bk) p3(i) = quadgk (@tvnIntegrand,loLimit,hiLimit,'AbsTol',tol/3,'RelTol',0); endif endfor else p3 = zeros (size (p1), is_type); endif p = cast (p1 + (p2 + p3) ./ (2 .* pi), is_type); function integrand = tvnIntegrand (theta) # Integrand is exp( -(b1.^2 + bj.^2 - 2*b1*bj*sin(theta))/(2*cos(theta).^2)) sintheta = sin (theta); cossqtheta = cos (theta) .^ 2; expon = ((b1 * sintheta - bj) .^ 2 ./ cossqtheta + b1 .^ 2) / 2; sinphi = sintheta .* rho_k1 ./ rho_j1; numeru = bk .* cossqtheta - b1 .* (sinphi - rho_32 .* sintheta) ... - bj .* (rho_32 - sintheta .* sinphi); denomu = sqrt (cossqtheta .* (cossqtheta - sinphi .* sinphi ... - rho_32 .* (rho_32 - 2 .* sintheta .* sinphi))); phi = 0.5 * erfc (- (numeru ./ denomu) / sqrt (2)); integrand = exp (- expon) .* phi; endfunction endfunction %!demo %! mu = [1, -1]; %! Sigma = [0.9, 0.4; 0.4, 0.3]; %! [X1, X2] = meshgrid (linspace (-1, 3, 25)', linspace (-3, 1, 25)'); %! X = [X1(:), X2(:)]; %! p = mvncdf (X, mu, Sigma); %! Z = reshape (p, 25, 25); %! surf (X1, X2, Z); %! title ('Bivariate Normal Distribution'); %! ylabel 'X1' %! xlabel 'X2' %!demo %! mu = [0, 0]; %! Sigma = [0.25, 0.3; 0.3, 1]; %! p = mvncdf ([0 0], [1 1], mu, Sigma); %! x1 = -3:.2:3; %! x2 = -3:.2:3; %! [X1, X2] = meshgrid (x1, x2); %! X = [X1(:), X2(:)]; %! p = mvnpdf (X, mu, Sigma); %! p = reshape (p, length (x2), length (x1)); %! contour (x1, x2, p, [0.0001, 0.001, 0.01, 0.05, 0.15, 0.25, 0.35]); %! xlabel ('x'); %! ylabel ('p'); %! title ('Probability over Rectangular Region'); %! line ([0, 0, 1, 1, 0], [1, 0, 0, 1, 1], 'Linestyle', '--', 'Color', 'k'); %!test %! fD = (-2:2)'; %! X = repmat (fD, 1, 4); %! p = mvncdf (X); %! assert_equal (p, [0; 0.0006; 0.0625; 0.5011; 0.9121], ones (5, 1) * 1e-4); %!test %! mu = [1, -1]; %! Sigma = [0.9, 0.4; 0.4, 0.3]; %! [X1,X2] = meshgrid (linspace (-1, 3, 25)', linspace (-3, 1, 25)'); %! X = [X1(:), X2(:)]; %! p = mvncdf (X, mu, Sigma); %! p_out = [0.00011878988774500, 0.00034404112322371, ... %! 0.00087682502191813, 0.00195221905058185, ... %! 0.00378235566873474, 0.00638175749734415, ... %! 0.00943764224329656, 0.01239164888125426, ... %! 0.01472750274376648, 0.01623228313374828]'; %! assert_equal (p([1:10]), p_out, 1e-16); %!test %! mu = [1, -1]; %! Sigma = [0.9, 0.4; 0.4, 0.3]; %! [X1,X2] = meshgrid (linspace (-1, 3, 25)', linspace (-3, 1, 25)'); %! X = [X1(:), X2(:)]; %! p = mvncdf (X, mu, Sigma); %! p_out = [0.8180695783608276, 0.8854485749482751, ... %! 0.9308108777385832, 0.9579855743025508, ... %! 0.9722897881414742, 0.9788150170059926, ... %! 0.9813597788804785, 0.9821977956568989, ... %! 0.9824283794464095, 0.9824809345614861]'; %! assert_equal (p([616:625]), p_out, 3e-16); %!test %! mu = [0, 0]; %! Sigma = [0.25, 0.3; 0.3, 1]; %! [p, err] = mvncdf ([0, 0], [1, 1], mu, Sigma); %! assert_equal (p, 0.2097424404755626, 1e-16); %! assert_equal (err, 1e-08); %!test %! x = [1 2]; %! mu = [0.5 1.5]; %! sigma = [1.0, 0.5; 0.5, 1.0]; %! p = mvncdf (x, mu, sigma); %! assert_equal (p, 0.546244443857090, 1e-15); %!test %! x = [1 2]; %! mu = [0.5 1.5]; %! sigma = [1.0, 0.5; 0.5, 1.0]; %! a = [-inf 0]; %! p = mvncdf (a, x, mu, sigma); %! assert_equal (p, 0.482672935215631, 1e-15); %!error mvncdf (int32 ([0, 0])) %!error mvncdf ([true, true]) %!error mvncdf ('ab') %!error p = mvncdf (randn (25,26), [], eye (26)); %!error p = mvncdf (randn (25,8), [], eye (9)); %!error p = mvncdf (randn (25,4), randn (25,5), [], eye (4)); %!error p = mvncdf (randn (25,4), randn (25,4), [2, 3; 2, 3], eye (4)); %!error p = mvncdf (randn (25,4), randn (25,4), ones (1, 5), eye (4)); %!error p = mvncdf ([-inf, 0], [1, 2], [0.5, 1.5], [1.0, 0.5; 0.5, 1.0], option) statistics-release-1.9.2/inst/Distribution_Functions/mvnpdf.m000066400000000000000000000214521524624707500245070ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} mvnpdf (@var{x}, @var{mu}, @var{sigma}) ## ## Multivariate normal probability density function (PDF). ## ## @code{@var{y} = mvnpdf (@var{x})} returns the probability density of the ## multivariate normal distribution with zero mean and identity covariance ## matrix, evaluated at each row of @var{x}. Rows of the N-by-D matrix @var{x} ## correspond to observations orpoints, and columns correspond to variables or ## coordinates. @var{y} is an N-by-1 vector. ## ## @code{@var{y} = mvnpdf (@var{x}, @var{mu})} returns the density of the ## multivariate normal distribution with mean MU and identity covariance matrix, ## evaluated at each row of @var{x}. @var{mu} is a 1-by-D vector, or an N-by-D ## matrix, in which case the density is evaluated for each row of @var{x} with ## the corresponding row of @var{mu}. @var{mu} can also be a scalar value, ## which MVNPDF replicates to match the size of @var{x}. ## ## @code{@var{y} = mvnpdf (@var{x}, @var{mu}, @var{sigma})} returns the density ## of the multivariate normal distribution with mean @var{mu} and covariance ## @var{sigma}, evaluated at each row of @var{x}. @var{sigma} is a D-by-D ## matrix, or an D-by-D-by-N array, in which case the density is evaluated for ## each row of @var{x} with the corresponding page of @var{sigma}, i.e., ## @code{mvnpdf} computes @var{y(i)} using @var{x(i,:)} and @var{sigma(:,:,i)}. ## If the covariance matrix is diagonal, containing variances along the diagonal ## and zero covariances off the diagonal, @var{sigma} may also be specified as a ## 1-by-D matrix or a 1-by-D-by-N array, containing just the diagonal. Pass in ## the empty matrix for @var{mu} to use its default value when you want to only ## specify @var{sigma}. ## ## If @var{x} is a 1-by-D vector, @code{mvnpdf} replicates it to match the ## leading dimension of @var{mu} or the trailing dimension of @var{sigma}. ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{mvncdf, mvnrnd} ## @end deftypefn function y = mvnpdf (x, mu, sigma) ## Check for valid number of input arguments if (nargin < 1) error ("mvnpdf: too few input arguments."); endif if (nargin < 2) mu = []; endif if (nargin < 3) sigma = []; endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("mvnpdf: X, MU, and SIGMA must be double or single."); endif ## Check for valid size of data [row, col] = size (x); if (col < 1) error ("mvnpdf: too few dimensions in X."); elseif (ndims (x) != 2) error ("mvnpdf: wrong dimensions in X."); endif ## Check for second input argument or assume zero mean if (nargin < 2 || isempty (mu)) xc = x; # already centered elseif (numel (mu) == 1) xc = x - mu; # mu is a scalar elseif (ndims (mu) == 2) [rm, cm] = size (mu); # mu is a vector if (cm != col) error ("mvnpdf: columns in X and MU mismatch."); elseif (rm == row) xc = x - mu; elseif (rm == 1 || row == 1) xc = bsxfun (@minus, x, mu); else error ("mvnpdf: rows in X and MU mismatch."); endif else error ("mvnpdf: wrong size of MU."); endif [row, col] = size (xc); ## Check for third input argument or assume identity covariance if (nargin < 2 || isempty (sigma)) ## already standardized if (col == 1 && row > 1) xRinv = xc'; # make row vector col == row; else xRinv = xc; col == row; endif lnSDS = 0; elseif (ndims (sigma) == 2) ## Single covariance matrix [rs, cs] = size (sigma); if (rs == 1 && cs > 1) rs = cs; # sigma passed as a diagonal is_diag = true; else is_diag = false; endif if (col == 1 && row > 1 && rs == row) xc = xc'; # make row vector col = row; endif ## Check sigma for correct size if (rs != cs) error ("mvnpdf: bad covariance matrix."); elseif (rs != col) error ("mvnpdf: covariance matrix mismatch."); else if (is_diag) ## Check sigma for invalid values if (any (sigma <= 0)) error ("mvnpdf: sigma diagonal contains negative or zero values."); endif R = sqrt (sigma); xRinv = bsxfun (@rdivide, xc, R); lnSDS = sum (log (R)); else ## Check for valid covariance matrix [R, err] = cholcov (sigma, 0); if (err != 0) error ("mvnpdf: invalid covariance matrix."); endif xRinv = xc / R; lnSDS = sum (log (diag (R))); endif endif elseif (ndims (sigma) == 3) ## Multiple covariance matrices sd = size (sigma); if (sd(1) == 1 && sd(2) > 1) sd(1) = sd(2); # sigma passed as a diagonal sigma = reshape (sigma, sd(2), sd(3))'; is_diag = true; else is_diag = false; endif if (col == 1 && row > 1 && sd(1) == row) xc = xc'; # make row vector [row, col] = size (xc); endif ## If X and MU are row vectors, match them with covariance if (row == 1) row = sd(3); xc = repmat (xc, row, 1); endif ## Check sigma for correct size if (sd(1) != sd(2)) error ("mvnpdf: bad multiple covariance matrix."); elseif (sd(1) != col || sd(2) != col) error ("mvnpdf: multiple covariance matrix mismatch."); elseif (sd(3) != row) error ("mvnpdf: multiple covariance pages mismatch."); else if (is_diag) ## Check sigma for invalid values if (any (any (sigma <= 0))) error ("mvnpdf: sigma diagonals contain negative or zero values."); endif R = sqrt (sigma); xRinv = xc ./ R; lnSDS = sum (log (R), 2); else ## Create arrays according to class type if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single')) xRinv = zeros (row, col,' single'); lnSDS = zeros (row, 1, 'single'); else xRinv = zeros (row, col); lnSDS = zeros (row, 1); endif for i = 1:row ## Check for valid covariance matrices [R, err] = cholcov (sigma(:,:,i), 0); if (err != 0) error ("mvnpdf:invalid multiple covariance matrix."); endif xRinv(i,:) = xc(i,:) / R; lnSDS(i) = sum (log (diag (R))); endfor endif endif else error ("mvnpdf: wrong dimensions in covariance matrix."); endif ## Compute the PDF y = exp (-0.5 * sum (xRinv .^ 2, 2) - lnSDS - col * log (2 * pi) / 2); endfunction %!demo %! mu = [1, -1]; %! sigma = [0.9, 0.4; 0.4, 0.3]; %! [X1, X2] = meshgrid (linspace (-1, 3, 25)', linspace (-3, 1, 25)'); %! x = [X1(:), X2(:)]; %! p = mvnpdf (x, mu, sigma); %! surf (X1, X2, reshape (p, 25, 25)); ## Input validation tests %!error mvnpdf (int32 ([0, 0]), [0, 0], eye (2)) %!error mvnpdf ([true, true], [0, 0], eye (2)) %!error mvnpdf ('ab', [0, 0], eye (2)) %!error y = mvnpdf (); %!error y = mvnpdf ([]); %!error y = mvnpdf (ones (3,3,3)); %!error ... %! y = mvnpdf (ones (10, 2), [4, 2, 3]); %!error ... %! y = mvnpdf (ones (10, 2), [4, 2; 3, 2]); %!error ... %! y = mvnpdf (ones (10, 2), ones (3, 3, 3)); ## Output validation tests %!shared x, mu, sigma %! x = [1, 2, 5, 4, 6]; %! mu = [2, 0, -1, 1, 4]; %! sigma = [2, 2, 2, 2, 2]; %!assert_equal (mvnpdf (x), 1.579343404440977e-20, 1e-30); %!assert_equal (mvnpdf (x, mu), 1.899325144348102e-14, 1e-25); %!assert_equal (mvnpdf (x, mu, sigma), 2.449062307156273e-09, 1e-20); statistics-release-1.9.2/inst/Distribution_Functions/mvnrnd.m000066400000000000000000000162631524624707500245250ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} mvnrnd (@var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{r} =} mvnrnd (@var{mu}, @var{sigma}, @var{n}) ## @deftypefnx {statistics} {@var{r} =} mvnrnd (@var{mu}, @var{sigma}, @var{n}, @var{T}) ## @deftypefnx {statistics} {[@var{r}, @var{T}] =} mvnrnd (@dots{}) ## ## Random vectors from the multivariate normal distribution. ## ## @code{@var{r} = mvnrnd (@var{mu}, @var{sigma})} returns an N-by-D matrix ## @var{r} of random vectors chosen from the multivariate normal distribution ## with mean vector @var{mu} and covariance matrix @var{sigma}. @var{mu} is an ## N-by-D matrix, and @code{mvnrnd} generates each N of @var{r} using the ## corresponding N of @var{mu}. @var{sigma} is a D-by-D symmetric positive ## semi-definite matrix, or a D-by-D-by-N array. If @var{sigma} is an array, ## @code{mvnrnd} generates each N of @var{r} using the corresponding page of ## @var{sigma}, i.e., @code{mvnrnd} computes @var{r(i,:)} using @var{mu(i,:)} ## and @var{sigma(:,:,i)}. If the covariance matrix is diagonal, containing ## variances along the diagonal and zero covariances off the diagonal, ## @var{sigma} may also be specified as a 1-by-D matrix or a 1-by-D-by-N array, ## containing just the diagonal. If @var{mu} is a 1-by-D vector, @code{mvnrnd} ## replicates it to match the trailing dimension of SIGMA. ## ## @code{@var{r} = mvnrnd (@var{mu}, @var{sigma}, @var{n})} returns a N-by-D ## matrix R of random vectors chosen from the multivariate normal distribution ## with 1-by-D mean vector @var{mu}, and D-by-D covariance matrix @var{sigma}. ## ## @code{@var{r} = mvnrnd (@var{mu}, @var{sigma}, @var{n}, @var{T})} supplies ## the Cholesky factor @var{T} of @var{sigma}, so that @var{sigma(:,:,J)} == ## @var{T(:,:,J)}'*@var{T(:,:,J)} if @var{sigma} is a 3D array or @var{sigma} == ## @var{T}'*@var{T} if @var{sigma} is a matrix. No error checking is done on ## @var{T}. ## ## @code{[@var{r}, @var{T}] = mvnrnd (@dots{})} returns the Cholesky factor ## @var{T}, so it can be re-used to make later calls more efficient, although ## there are greater efficiency gains when SIGMA can be specified as a diagonal ## instead. ## ## @seealso{mvncdf, mvnpdf} ## @end deftypefn function [r, T] = mvnrnd (mu, sigma, N, T) ## Check input arguments if (nargin < 2 || isempty (mu) || isempty (sigma)) error ("mvnrnd: too few input arguments."); elseif (ndims (mu) > 2) error ("mvnrnd: wrong size of MU."); elseif (ndims (sigma) > 3) error ("mvnrnd: wrong size of SIGMA."); endif ## Get data type if (isa (mu, 'single') || isa (sigma, 'single')) is_class = 'single'; else is_class = 'double'; endif ## Check whether sigma is passed as a diagonal or a matrix sd = size (sigma); if (sd(1) == 1 && sd(2) > 1) sd(1) = sd(2); is_diag = true; else is_diag = false; endif ## Get size of mean vector [rm, cm] = size (mu); ## Make sure MU is a row vector if (cm == 1 && rm == sd(1)) mu = mu'; [rm, cm] = size (mu); endif ## Check for valid N input argument if (nargin < 3 || isempty (N)) N_empty = true; else N_empty = false; ## If MU is a row vector, rep it out to match N if (rm == 1) rm = N; mu = repmat (mu, rm, 1); elseif (rm != N) error ("mvnrnd: size mismatch of N and MU."); endif endif ## For single covariance matrix if (ndims (sigma) == 2) ## Check sigma for correct size if (sd(1) != sd(2)) error ("mvnpdf: bad covariance matrix."); elseif (! sd(1) == cm) error ("mvnpdf: covariance matrix mismatch."); endif ## Check for Cholesky factor T if (nargin > 3) r = randn (rm, size (T, 1), is_class) * T + mu; elseif (is_diag) ## Check sigma for invalid values if (any (sigma <= 0)) error ("mvnpdf: SIGMA diagonal contains negative or zero values."); endif t = sqrt (sigma); if (nargout > 1) T = diag (t); endif r = bsxfun (@times, randn (rm, cm, is_class), t) + mu; else ## Compute a Cholesky factorization [T, err] = cholcov (sigma); if (err != 0) error ("mvnrnd: covariance matrix is not positive definite."); endif r = randn (rm, size (T, 1), is_class) * T + mu; endif endif ## For multiple covariance matrices if (ndims (sigma) == 3) ## If MU is a row vector, rep it out to match sigma if (rm == 1 && N_empty) rm = sd(3); mu = repmat (mu, rm, 1); endif ## Check sigma for correct size if (sd(1) != sd(2)) error ("mvnpdf: bad multiple covariance matrix."); elseif (sd(1) != cm) error ("mvnpdf: multiple covariance matrix mismatch."); elseif (sd(3) != rm) error ("mvnpdf: multiple covariance pages mismatch."); endif ## Check for Cholesky factor T if (nargin < 4) # T not present if (nargout > 1) T = zeros (sd, is_class); endif if (is_diag) sigma = reshape (sigma,sd(2),sd(3))'; ## Check sigma for invalid values if (any (sigma(:) <= 0)) error ("mvnpdf: SIGMA diagonals contain negative or zero values."); endif R = sqrt (sigma); r = bsxfun (@times, randn (rm, cm, is_class), R) + mu; if (nargout > 1) for i = 1:rm T(:,:,i) = diag (R(i,:)); endfor endif else r = zeros (rm, cm, is_class); for i = 1:rm [R, err] = cholcov (sigma(:,:,i)); if (err != 0) error (strcat ("mvnrnd: multiple covariance matrix", ... " is not positive definite.")); endif Rrows = size (R,1); r(i,:) = randn (1, Rrows, is_class) * R + mu(i,:); if (nargout > 1) T(1:Rrows,:,i) = R; endif endfor endif else # T present r = zeros (rm, cm, is_class); for i = 1:rm r(i,:) = randn (1, cm, is_class) * T(:,:,i) + mu(i,:); endfor endif endif endfunction ## Test input validation %!error mvnrnd () %!error mvnrnd ([2, 3, 4]) %!error mvnrnd (ones (2, 2, 2), ones (1, 2, 3, 4)) %!error mvnrnd (ones (1, 3), ones (1, 2, 3, 4)) ## Output validation tests %!assert_equal (size (mvnrnd ([2, 3, 4], [2, 2, 2])), [1, 3]) %!assert_equal (size (mvnrnd ([2, 3, 4], [2, 2, 2], 10)), [10, 3]) statistics-release-1.9.2/inst/Distribution_Functions/mvtcdf.m000066400000000000000000000362431524624707500245040ustar00rootroot00000000000000## Copyright (C) 2008 Arno Onken ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} mvtcdf (@var{x}, @var{rho}, @var{df}) ## @deftypefnx {statistics} {@var{p} =} mvncdf (@var{x_lo}, @var{x_up}, @var{rho}, @var{df}) ## @deftypefnx {statistics} {@var{p} =} mvncdf (@dots{}, @var{options}) ## @deftypefnx {statistics} {[@var{p}, @var{err}] =} mvncdf (@dots{}) ## ## Multivariate Student's t cumulative distribution function (CDF). ## ## @code{@var{p} = mvtcdf (@var{x}, @var{rho}, @var{df})} returns the cumulative ## probability of the multivariate student's t distribution with correlation ## parameters @var{rho} and degrees of freedom @var{df}, evaluated at each row ## of @var{x}. The rows of the @math{N*D} matrix @var{x} correspond to sample ## observations and its columns correspond to variables or coordinates. The ## return argument @var{p} is a column vector with the same number of rows as in ## @var{x}. ## ## @var{rho} is a symmetric, positive definite, @math{D*D} correlation matrix. ## @var{dF} is a scalar or a vector with @math{N} elements. ## ## Note: @code{mvtcdf} computes the CDF for the standard multivariate Student's ## t distribution, centered at the origin, with no scale parameters. If ## @var{rho} is a covariance matrix, i.e. @code{diag(@var{rho})} is not all ## ones, @code{mvtcdf} rescales @var{rho} to transform it to a correlation ## matrix. @code{mvtcdf} does not rescale @var{x}, though. ## ## The multivariate Student's t cumulative probability at @var{x} is defined as ## the probability that a random vector T, distributed as multivariate normal, ## will fall within the semi-infinite rectangle with upper limits defined by ## @var{x}. ## @itemize ## @item @math{Pr@{T(1)<=X(1), T(2)<=X(2), ... T(D)<=X(D)@}}. ## @end itemize ## ## @code{@var{p} = mvtcdf (@var{x_lo}, @var{x_hi}, @var{rho}, @var{df})} returns ## the multivariate Student's t cumulative probability evaluated over the ## rectangle (hyper-rectangle for multivariate data in @var{x}) with lower and ## upper limits defined by @var{x_lo} and @var{x_hi}, respectively. ## ## @code{[@var{p}, @var{err}] = mvtcdf (@dots{})} also returns an error estimate ## @var{err} in @var{p}. ## ## @code{@var{p} = mvtcdf (@dots{}, @var{options})} specifies the structure, ## which controls specific parameters for the numerical integration used to ## compute @var{p}. The required fields are: ## ## @multitable @columnfractions 0.2 0.75 ## @item @qcode{'TolFun'} @tab Maximum absolute error tolerance. Default ## is 1e-8 for D < 4, or 1e-4 for D >= 4. ## ## @item @qcode{'MaxFunEvals'} @tab Maximum number of integrand evaluations ## when @math{D >= 4}. Default is 1e7. Ignored when @math{D < 4}. ## ## @item @qcode{'Display'} @tab Display options. Choices are @qcode{'off'} ## (default), @qcode{'iter'}, which shows the probability and estimated error at ## each repetition, and @qcode{'final'}, which shows the final probability and ## related error after the integrand has converged successfully. Ignored when ## @math{D < 4}. ## @end multitable ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{bvtcdf, mvtpdf, mvtrnd} ## @end deftypefn function [p, err] = mvtcdf (varargin) ## Check for valid number on input and output arguments narginchk (3,5); ## Check for X, RHO, and DF being double or single. The trailing ## 'options' struct, if present, is parsed separately below. numargs = varargin(! cellfun (@isstruct, varargin)); if (! all (cellfun (@isfloat, numargs))) error ("mvtcdf: X, RHO, and DF must be double or single."); endif ## Check for 'options' structure and parse parameters or add defaults if (isstruct (varargin{end})) if (isfield (varargin{end}, 'TolFun')) TolFun = varargin{end}.TolFun; else error ("mvtcdf: options structure missing 'TolFun' field."); endif if (isempty (TolFun) && size (varargin{1}, 2) < 4) TolFun = 1e-8; elseif (isempty (TolFun) && size (varargin{1}, 2) < 26) TolFun = 1e-4; endif if (isfield (varargin{end}, 'MaxFunEvals')) MaxFunEvals = varargin{end}.MaxFunEvals; else error ("mvtcdf: options structure missing 'MaxFunEvals' field."); endif if (isempty (MaxFunEvals)) MaxFunEvals = 1e7; endif if (isfield (varargin{end}, 'Display')) Display = varargin{end}.Display; else error ("mvtcdf: options structure missing 'Display' field."); endif DispOptions = {'off', 'final', 'iter'}; if (sum (any (strcmpi (Display, DispOptions))) == 0) error ("mvtcdf: 'Display' field in 'options' has invalid value."); endif rem_nargin = nargin - 1; else if (size (varargin{1}, 2) < 4) TolFun = 1e-8; elseif (size (varargin{1}, 2) < 26) TolFun = 1e-4; endif MaxFunEvals = 1e7; Display = 'off'; rem_nargin = nargin; endif ## Check for X of X_lo and X_up if (rem_nargin < 4) # MVTCDF(X_UP,SIGMA,DF) x_up_Only = true; x_up = varargin{1}; ## Check for x being a matrix if (! ismatrix (x_up)) error ("mvtcdf: X must be a matrix."); endif ## Create x_lo according to data type of x_lo x_lo = - Inf (size (x_up)); if isa (x_up, 'single') x_lo = single (x_lo); endif ## Get SIGMA and DF arguments rho = varargin{2}; df = varargin{3}; else # MVNCDF(X_LO,X_UP,SIGMA,DF) x_up_Only = false; x_lo = varargin{1}; x_up = varargin{2}; rho = varargin{3}; df = varargin{4}; ## Check for x_lo and x_up being matrices of the same size ## and that they define increasing limits if (! ismatrix (x_lo) || ! ismatrix (x_up)) error ("mvtcdf: X_LO and X_UP must be matrices."); endif if (any (size (x_lo) != size (x_up))) error ("mvtcdf: X_LO and X_UP must be of the same size."); endif if (any (any (x_lo > x_up))) error ("mvtcdf: X_LO and X_UP must define increasing limits."); endif endif ## Check if data is single or double class is_type = 'double'; if (isa (x_up, 'single') || isa (x_lo, 'single') || ... isa (rho, 'single') || isa (df, 'single')) is_type = 'single'; endif ## Get size of data [n_x, d_x] = size (x_lo); if (d_x < 1) error ("mvtcdf: too few dimensions in data."); endif ## Force univariate column vector into a row vector if ((d_x == 1) && (size (rho, 1) == n_x)) x_lo = x_lo'; x_up = x_up'; [n_x, d_x] = size (x_up); endif ## Check rho sz = size (rho); if (sz(1) != sz(2)) error ("mvtcdf: correlation matrix RHO is not square."); elseif (! isequal (sz, [d_x, d_x])) error (strcat ("mvtcdf: correlation matrix RHO does not", ... " match dimensions in data.")); endif ## Standardize rho to correlation if necessary (not the data) s = sqrt (diag (rho)); if (any (s != 1)) rho = rho ./ (s * s'); endif ## Continue checking rho for being a valid correlation matrix [~, err] = cholcov (rho, 0); if (err != 0) error (strcat ("mvtcdf: correlation matrix RHO must be", ... " positive semi-definite.")); endif ## Check df if (! isscalar (df) && ! (isvector (df) && length (df) == n_x)) error (strcat ("mvtcdf: DF must be a scalar or a vector with", ... " the same samples as in data.")); endif if (any (df <= 0) || ! isreal (df)) error ("mvtcdf: DF must contain only positive real numbers."); endif ## Compute the cdf if (d_x == 1) p = tcdf (x_up, df) - tcdf (x_lo, df); if (nargout > 1) err = NaN (size (p), is_type); endif elseif (d_x < 4) if (x_up_Only) # upper limit only if (d_x == 2) p = bvtcdf (x_up, rho(2), df, TolFun); else p = tvtcdf (x_up, rho([2 3 6]), df, TolFun); endif else # lower and upper limits present ## Compute the probability over the rectangle as sums and differences ## of integrals over semi-infinite half-rectangles. For degenerate ## rectangles, force an exact zero by making each piece exactly zero. equalLimits = (x_lo == x_up); x_lo(equalLimits) = -Inf; x_up(equalLimits) = -Inf; p = zeros (n_x, 1, is_type); for i = 0:d_x k = nchoosek (1:d_x, i); for j = 1:size (k, 1) X = x_up; X(:,k(j,:)) = x_lo(:,k(j,:)); if d_x == 2 p = p + (-1)^i * bvtcdf (X, rho(2), df, TolFun/4); else p = p + (-1)^i * tvtcdf (X, rho([2 3 6]), df, TolFun/8); endif endfor endfor endif if (nargout > 1) err = repmat (cast (TolFun, is_type), size (p)); endif elseif (d_x < 26) p = zeros (n_x, 1, is_type); err = zeros (n_x, 1, is_type); if (isscalar (df)) df = repmat (df, n_x, 1); endif for i = 1:n_x [p(i), err(i)] = __mvtcdfqmc__ ("mvtcdf", x_lo(i,:), x_up(i,:), ... rho, df(i), TolFun, MaxFunEvals, ... Display); endfor else error ("mvtcdf: too many dimensions in data (limit = 25 columns)."); endif ## Bound p in range [0, 1] p(p < 0) = 0; p(p > 1) = 1; endfunction ## CDF for the trivariate Student's T function p = tvtcdf (x, rho, df, TolFun) n_x = size (x, 1); if (isscalar (df)) df = repmat (df, n_x, 1); endif ## Find a permutation that makes rho_23 == max(rho) [~,imax] = max (abs (rho)); if (imax == 1) # swap 1 and 3 rho_12 = rho(3); rho_13 = rho(2); rho_23 = rho(1); x = x(:,[3 2 1]); elseif (imax == 2) # swap 1 and 2 rho_12 = rho(1); rho_13 = rho(3); rho_23 = rho(2); x = x(:,[2 1 3]); else # x already in correct order rho_12 = rho(1); rho_13 = rho(2); rho_23 = rho(3); endif if (rho_23 >= 0) p1 = bvtcdf ([x(:,1) min(x(:,2:3), [], 2)], 0, df, TolFun / 4); p1(any (isnan (x), 2)) = NaN; else p1 = bvtcdf (x(:,1:2), 0, df, TolFun /4) - ... bvtcdf ([x(:,1) -x(:,3)], 0, df, TolFun / 4); p1(p1 < 0) = 0; endif if (abs (rho_23) < 1) lo = asin (rho_23); hi = (sign (rho_23) + (rho_23 == 0)) .* pi ./ 2; p2 = zeros (size (p1), class (p1)); for i = 1:n_x x1 = x(i,1); x2 = x(i,2); x3 = x(i,3); if (isfinite (x2) && isfinite (x3) && isnan (x1)) v = df(i); p2(i) = quadgk (@tvtIntegr1, lo, hi, 'AbsTol', TolFun / 4, 'RelTol', 0); endif endfor else p2 = zeros (class (p1)); endif if (abs (rho_12) > 0) lo = 0; hi = asin (rho_12); rj = rho_12; rk = rho_13; p3 = zeros (size (p1), class (p1)); for i = 1:n_x x1 = x(i,1); xj = x(i,2); xk = x(i,3); if (isfinite (x1) && isfinite (xj) && ! isnan (xk)) v = df(i); p3(i) = quadgk (@tvtIntegr2, lo, hi, 'AbsTol', TolFun / 4, 'RelTol', 0); endif endfor else p3 = zeros (class (p1)); endif if (abs (rho_13) > 0) lo = 0; hi = asin (rho_13); rj = rho_13; rk = rho_12; p4 = zeros (size (p1), class (p1)); for i = 1:n_x x1 = x(i,1); xj = x(i,3); xk = x(i,2); if (isfinite (x1) && isfinite (xj) && ! isnan (xk)) v = df(i); p4(i) = quadgk (@tvtIntegr2, lo, hi, 'AbsTol', TolFun / 4, 'RelTol', 0); endif endfor else p4 = zeros (class (p1)); endif if (isa (x, 'single') || isa (rho, 'single') || isa (df, 'single')) p = cast (p1 + (-p2 + p3 + p4) ./ (2 .* pi), 'single'); else p = cast (p1 + (-p2 + p3 + p4) ./ (2 .* pi), 'double'); endif ## Functions to compute the integrands function integrand = tvtIntegr1 (theta) st = sin (theta); c2t = cos (theta) .^ 2; w = sqrt (1 ./ (1 + ((x2 * st - x3) .^ 2 ./ c2t + x2 .^ 2) / v)); integrand = w .^ v .* TCDF (x1 .* w, v); endfunction function integrand = tvtIntegr2 (theta) st = sin (theta); c2t = cos (theta) .^ 2; w = sqrt (1 ./ (1 + ((x1 *st - xj) .^ 2 ./ c2t + x1 .^ 2) / v)); integrand = w .^ v .* TCDF (uk (st, c2t) .* w, v); endfunction function uk = uk (st, c2t) sinphi = st .* rk ./ rj; numeru = xk .* c2t - x1 .* (sinphi - rho_23 .* st) ... - xj .* (rho_23 - st .* sinphi); denomu = sqrt (c2t .* (c2t - sinphi .* sinphi ... - rho_23 .* (rho_23 - 2 .* st .* sinphi))); uk = numeru ./ denomu; endfunction endfunction ## CDF for Student's T function p = TCDF (x, df) p = betainc (df ./ (df + x .^ 2), df / 2, 0.5) / 2; reflect = (x > 0); p(reflect) = 1 - p(reflect); endfunction %!demo %! ## Compute the cdf of a multivariate Student's t distribution with %! ## correlation parameters rho = [1, 0.4; 0.4, 1] and 2 degrees of freedom. %! %! rho = [1, 0.4; 0.4, 1]; %! df = 2; %! [X1, X2] = meshgrid (linspace (-2, 2, 25)', linspace (-2, 2, 25)'); %! X = [X1(:), X2(:)]; %! p = mvtcdf (X, rho, df); %! surf (X1, X2, reshape (p, 25, 25)); %! title ('Bivariate Student''s t cumulative distribution function'); ## Test output against MATLAB R2018 %!test %! x = [1, 2]; %! rho = [1, 0.5; 0.5, 1]; %! df = 4; %! a = [-1, 0]; %! assert_equal (mvtcdf (a, x, rho, df), 0.294196905339283, 1e-14); %!test %! x = [1, 2;2, 4;1, 5]; %! rho = [1, 0.5; 0.5, 1]; %! df = 4; %! p =[0.790285178602166; 0.938703291727784; 0.81222737321336]; %! assert_equal (mvtcdf (x, rho, df), p, 1e-14); %!test %! x = [1, 2, 2, 4, 1, 5]; %! rho = eye (6); %! rho(rho == 0) = 0.5; %! df = 4; %! assert_equal (mvtcdf (x, rho, df), 0.6874, 1e-4); %!error mvtcdf (int32 ([0, 0]), eye (2), 5) %!error mvtcdf ([true, true], eye (2), 5) %!error mvtcdf ('ab', eye (2), 5) %!error mvtcdf (1) %!error mvtcdf (1, 2) %!error ... %! mvtcdf (1, [2, 3; 3, 2], 1) %!error ... %! mvtcdf ([2, 3, 4], ones (2), 1) %!error ... %! mvtcdf ([1, 2, 3], [2, 3], ones (2), 1) %!error ... %! mvtcdf ([2, 3], ones (2), [1, 2, 3]) %!error ... %! mvtcdf ([2, 3], [1, 0.5; 0.5, 1], [1, 2, 3]) statistics-release-1.9.2/inst/Distribution_Functions/mvtpdf.m000066400000000000000000000121161524624707500245120ustar00rootroot00000000000000## Copyright (C) 2015 Nir Krakauer ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} mvtpdf (@var{x}, @var{rho}, @var{df}) ## ## Multivariate Student's t probability density function (PDF). ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{x} are the points at which to find the probability, where each row ## corresponds to an observation. (@math{N*D} matrix) ## ## @item ## @var{rho} is the correlation matrix. (@math{D*D} symmetric positive ## definite matrix) ## ## @item ## @var{df} is the degrees of freedom. (scalar or vector of length @math{N}) ## ## @end itemize ## ## The distribution is assumed to be centered (zero mean). ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{y} is the probability density for each row of @var{x}. ## (@math{N*1} vector) ## @end itemize ## ## @subheading Examples ## ## @example ## @group ## x = [1 2]; ## rho = [1.0 0.5; 0.5 1.0]; ## df = 4; ## y = mvtpdf (x, rho, df) ## @end group ## @end example ## ## @subheading References ## ## @enumerate ## @item ## Michael Roth, On the Multivariate t Distribution, Technical report from ## Automatic Control at Linkoepings universitet, ## @url{http://users.isy.liu.se/en/rt/roth/student.pdf} ## @end enumerate ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{mvtcdf, mvtrnd} ## @end deftypefn function y = mvtpdf (x, rho, df) if (nargin != 3) print_usage (); endif ## Check for X, RHO, and DF being double or single if (! (isfloat (x) && isfloat (rho) && isfloat (df))) error ("mvtpdf: X, RHO, and DF must be double or single."); endif # Dimensions d = size (rho, 1); n = size (x, 1); # Check parameters if (size (x, 2) != d) error ("mvtpdf: x must have the same number of columns as rho."); endif if (! isscalar (df) && (! isvector (df) || numel (df) != n)) error (strcat ("mvtpdf: DF must be a scalar or a vector with the", ... " same number of rows as X.")); endif if (d < 1 || size (rho, 2) != d || ! issymmetric (rho)) error ("mvtpdf: SIGMA must be nonempty and symmetric."); endif try U = chol (rho); catch error ("mvtpdf: rho must be positive definite"); end_try_catch df = df(:); sqrt_det_sigma = prod (diag (U)); #square root of determinant of rho ## Scale factor for PDF c = (gamma ((df+d)/2) ./ gamma (df/2)) ./ (sqrt_det_sigma * (df*pi).^(d/2)); #note: sumsq(U' \ x') is equivalent to the quadratic form x*inv(rho)*x' y = c ./ ((1 + sumsq (U' \ x') ./ df') .^ ((df' + d)/2))'; endfunction %!demo %! ## Compute the pdf of a multivariate t distribution with correlation %! ## parameters rho = [1 .4; .4 1] and 2 degrees of freedom. %! %! rho = [1, 0.4; 0.4, 1]; %! df = 2; %! [X1, X2] = meshgrid (linspace (-2, 2, 25)', linspace (-2, 2, 25)'); %! X = [X1(:), X2(:)]; %! y = mvtpdf (X, rho, df); %! surf (X1, X2, reshape (y, 25, 25)); %! title ('Bivariate Student''s t probability density function'); ## Test results verified with R mvtnorm package dmvt function ## dmvt(x = c(0,0), rho = diag(2), log = FALSE) %!assert_equal (mvtpdf ([0 0], eye (2), 1), 0.1591549, 1E-7) ## dmvt(x = c(1,0), rho = matrix(c(1, 0.5, 0.5, 1), nrow=2, ncol=2), df = 2, log = FALSE) %!assert_equal (mvtpdf ([1 0], [1 0.5; 0.5 1], 2), 0.06615947, 1E-7) ## dmvt(x = c(1,0.4,0), rho = matrix(c(1, 0.5, 0.3, 0.5, 1, 0.6, 0.3, 0.6, ... ## 1), nrow=3, ncol=3), df = 5, log = FALSE); dmvt(x = c(1.2,0.5,0.5), ... ## rho = matrix(c(1, 0.5, 0.3, 0.5, 1, 0.6, 0.3, 0.6, 1), nrow=3, ncol=3), ... ## df = 6, log = FALSE); dmvt(x = c(1.4,0.6,1), rho = matrix(c(1, 0.5, 0.3,... ## 0.5, 1, 0.6, 0.3, 0.6, 1), nrow=3, ncol=3), df = 7, log = FALSE) %!assert_equal (mvtpdf ([1 0.4 0; 1.2 0.5 0.5; 1.4 0.6 1], ... %! [1 0.5 0.3; 0.5 1 0.6; 0.3 0.6 1], [5 6 7]), ... %! [0.04713313 0.03722421 0.02069011]', 1E-7) ## Test input validation %!error mvtpdf (int32 ([0, 0]), eye (2), 5) %!error mvtpdf ([true, true], eye (2), 5) %!error mvtpdf ('ab', eye (2), 5) statistics-release-1.9.2/inst/Distribution_Functions/mvtrnd.m000066400000000000000000000104351524624707500245260ustar00rootroot00000000000000## Copyright (C) 2012 Arno Onken ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} mvtrnd (@var{rho}, @var{df}) ## @deftypefnx {statistics} {@var{r} =} mvtrnd (@var{rho}, @var{df}, @var{n}) ## ## Random vectors from the multivariate Student's t distribution. ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{rho} is the matrix of correlation coefficients. If there are any ## non-unit diagonal elements then @var{rho} will be normalized, so that the ## resulting covariance of the obtained samples @var{r} follows: ## @code{cov (r) = df/(df-2) * rho ./ (sqrt (diag (rho) * diag (rho)))}. ## In order to obtain samples distributed according to a standard multivariate ## student's t-distribution, @var{rho} must be equal to the identity matrix. To ## generate multivariate student's t-distribution samples @var{r} with arbitrary ## covariance matrix @var{rho}, the following scaling might be used: ## @code{r = mvtrnd (rho, df, n) * diag (sqrt (diag (rho)))}. ## ## @item ## @var{df} is the degrees of freedom for the multivariate t-distribution. ## @var{df} must be a vector with the same number of elements as samples to be ## generated or be scalar. ## ## @item ## @var{n} is the number of rows of the matrix to be generated. @var{n} must be ## a non-negative integer and corresponds to the number of samples to be ## generated. ## @end itemize ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{r} is a matrix of random samples from the multivariate t-distribution ## with @var{n} row samples. ## @end itemize ## ## @subheading Examples ## ## @example ## @group ## rho = [1, 0.5; 0.5, 1]; ## df = 3; ## n = 10; ## r = mvtrnd (rho, df, n); ## @end group ## ## @group ## rho = [1, 0.5; 0.5, 1]; ## df = [2; 3]; ## n = 2; ## r = mvtrnd (rho, df, 2); ## @end group ## @end example ## ## @subheading References ## ## @enumerate ## @item ## Wendy L. Martinez and Angel R. Martinez. @cite{Computational Statistics ## Handbook with MATLAB}. Appendix E, pages 547-557, Chapman & Hall/CRC, 2001. ## ## @item ## Samuel Kotz and Saralees Nadarajah. @cite{Multivariate t Distributions and ## Their Applications}. Cambridge University Press, Cambridge, 2004. ## @end enumerate ## ## @seealso{mvtcdf, mvtpdf} ## @end deftypefn function r = mvtrnd (rho, df, n) # Check arguments if (nargin < 2) print_usage (); endif [jnk, p] = cholcov (rho); # This is a more robust check for positive definite if (! ismatrix (rho) || any (any (rho != rho')) || (p != 0)) error ("mvtrnd: SIGMA must be a positive definite matrix."); endif if (! isvector (df) || any (df <= 0)) error ("mvtrnd: DF must be a positive scalar or vector."); endif df = df(:); if (nargin > 2) if (! isscalar (n) || n < 0 | round (n) != n) error ("mvtrnd: N must be a non-negative integer.") endif if (isscalar (df)) df = df * ones (n, 1); else if (length (df) != n) error ("mvtrnd: N must match the length of DF.") endif endif else n = length (df); endif # Normalize rho if (any (diag (rho) != 1)) rho = rho ./ sqrt (diag (rho) * diag (rho)'); endif # Dimension d = size (rho, 1); # Draw samples y = mvnrnd (zeros (1, d), rho, n); u = repmat (chi2rnd (df), 1, d); r = y .* sqrt (repmat (df, 1, d) ./ u); endfunction %!test %! rho = [1, 0.5; 0.5, 1]; %! df = 3; %! n = 10; %! r = mvtrnd (rho, df, n); %! assert_equal (size (r), [10, 2]); %!test %! rho = [1, 0.5; 0.5, 1]; %! df = [2; 3]; %! n = 2; %! r = mvtrnd (rho, df, 2); %! assert_equal (size (r), [2, 2]); statistics-release-1.9.2/inst/Distribution_Functions/nakacdf.m000066400000000000000000000153651524624707500246120ustar00rootroot00000000000000## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 1995-2015 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} nakacdf (@var{x}, @var{mu}, @var{omega}) ## @deftypefnx {statistics} {@var{p} =} nakacdf (@var{x}, @var{mu}, @var{omega}, @qcode{'upper'}) ## ## Nakagami cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the Nakagami distribution with shape parameter @var{mu} and spread ## parameter @var{omega}. The size of @var{p} is the common size of @var{x}, ## @var{mu}, and @var{omega}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Both parameters must be positive reals and @qcode{@var{mu} >= 0.5}. For ## @qcode{@var{mu} < 0.5} or @qcode{@var{omega} <= 0}, @qcode{NaN} is returned. ## ## @code{@var{p} = nakacdf (@var{x}, @var{mu}, @var{omega}, "upper")} computes ## the upper tail probability of the Nakagami distribution with parameters ## @var{mu} and @var{beta}, at the values in @var{x}. ## ## Further information about the Nakagami distribution can be found at ## @url{https://en.wikipedia.org/wiki/Nakagami_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{nakainv, nakapdf, nakarnd, nakafit, nakalike, nakastat} ## @end deftypefn function p = nakacdf (x, mu, omega, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("nakacdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 3) if (! strcmpi (uflag, 'upper')) error ("nakacdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, MU, and OMEGA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (omega)) [retval, x, mu, omega] = common_size (x, mu, omega); if (retval > 0) error ("nakacdf: X, MU, and OMEGA must be of common size or scalars."); endif endif ## Check for X, MU, and OMEGA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (omega))) error ("nakacdf: X, MU, and OMEGA must be double or single."); endif ## Check for X, MU, and OMEGA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (omega)) error ("nakacdf: X, MU, and OMEGA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (omega, 'single')) p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Force invalid parameters and missing data to NaN k1 = isnan (x) | ! (mu >= 0.5) | ! (omega > 0); p(k1) = NaN; ## Find normal and edge cases k2 = (x == Inf) & (mu >= 0.5) & (mu < Inf) & (omega > 0) & (omega < Inf); k = (x > 0) & (x < Inf) & (mu >= 0.5) & (mu < Inf) ... & (omega > 0) & (omega < Inf); ## Compute Nakagami CDF if (uflag) p(k2) = 0; left = mu .* ones (size (x)); right = (mu ./ omega) .* x .^ 2; p(k) = gammainc (right(k), left(k), 'upper'); else p(k2) = 1; left = mu .* ones (size (x)); right = (mu ./ omega) .* x .^ 2; p(k) = gammainc (right(k), left(k)); endif endfunction %!demo %! ## Plot various CDFs from the Nakagami distribution %! x = 0:0.01:3; %! p1 = nakacdf (x, 0.5, 1); %! p2 = nakacdf (x, 1, 1); %! p3 = nakacdf (x, 1, 2); %! p4 = nakacdf (x, 1, 3); %! p5 = nakacdf (x, 2, 1); %! p6 = nakacdf (x, 2, 2); %! p7 = nakacdf (x, 5, 1); %! plot (x, p1, '-r', x, p2, '-g', x, p3, '-y', x, p4, '-m', ... %! x, p5, '-k', x, p6, '-b', x, p7, '-c') %! grid on %! xlim ([0, 3]) %! legend ({'μ = 0.5, ω = 1', 'μ = 1, ω = 1', 'μ = 1, ω = 2', ... %! 'μ = 1, ω = 3', 'μ = 2, ω = 1', 'μ = 2, ω = 2', ... %! 'μ = 5, ω = 1'}, 'location', 'southeast') %! title ('Nakagami CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-1, 0, 1, 2, Inf]; %! y = [0, 0, 0.63212055882855778, 0.98168436111126578, 1]; %!assert_equal (nakacdf (x, ones (1,5), ones (1,5)), y, eps) %!assert_equal (nakacdf (x, 1, 1), y, eps) %!assert_equal (nakacdf (x, [1, 1, NaN, 1, 1], 1), [y(1:2), NaN, y(4:5)]) %!assert_equal (nakacdf (x, 1, [1, 1, NaN, 1, 1]), [y(1:2), NaN, y(4:5)]) %!assert_equal (nakacdf ([x, NaN], 1, 1), [y, NaN], eps) ## Test class of input preserved %!assert_equal (nakacdf (single ([x, NaN]), 1, 1), single ([y, NaN]), eps ('single')) %!assert_equal (nakacdf ([x, NaN], single (1), 1), single ([y, NaN]), eps ('single')) %!assert_equal (nakacdf ([x, NaN], 1, single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error nakacdf () %!error nakacdf (1) %!error nakacdf (1, 2) %!error nakacdf (1, 2, 3, 'tail') %!error nakacdf (1, 2, 3, 4) %!error ... %! nakacdf (ones (3), ones (2), ones (2)) %!error ... %! nakacdf (ones (2), ones (3), ones (2)) %!error ... %! nakacdf (ones (2), ones (2), ones (3)) %!error nakacdf (int32 (2), 2, 2) %!error nakacdf (true, 2, 2) %!error nakacdf ('a', 2, 2) %!error nakacdf (i, 2, 2) %!error nakacdf (2, i, 2) %!error nakacdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/nakainv.m000066400000000000000000000141671524624707500246510ustar00rootroot00000000000000## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 1995-2015 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} nakacdf (@var{x}, @var{mu}, @var{omega}) ## ## Inverse of the Nakagami cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the Nakagami distribution with shape parameter @var{mu} and spread parameter ## @var{omega}. The size of @var{x} is the common size of @var{x}, @var{mu}, ## and @var{omega}. A scalar input functions as a constant matrix of the same ## size as the other inputs. ## ## Both parameters must be positive reals and @qcode{@var{mu} >= 0.5}. For ## @qcode{@var{mu} < 0.5} or @qcode{@var{omega} <= 0}, @qcode{NaN} is returned. ## ## Further information about the Nakagami distribution can be found at ## @url{https://en.wikipedia.org/wiki/Nakagami_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{nakacdf, nakapdf, nakarnd, nakafit, nakalike, nakastat} ## @end deftypefn function x = nakainv (p, mu, omega) ## Check for valid number of input arguments if (nargin < 3) error ("nakainv: function called with too few input arguments."); endif ## Check for common size of P, MU, and OMEGA if (! isscalar (p) || ! isscalar (mu) || ! isscalar (omega)) [retval, p, mu, omega] = common_size (p, mu, omega); if (retval > 0) error ("nakainv: P, MU, and OMEGA must be of common size or scalars."); endif endif ## Check for P, MU, and OMEGA being double or single if (! (isfloat (p) && isfloat (mu) && isfloat (omega))) error ("nakainv: P, MU, and OMEGA must be double or single."); endif ## Check for P, MU, and OMEGA being reals if (iscomplex (p) || iscomplex (mu) || iscomplex (omega)) error ("nakainv: P, MU, and OMEGA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (mu, 'single') || isa (omega, 'single')) x = zeros (size (p), 'single'); else x = zeros (size (p)); endif ## Force invalid parameters and missing data to NaN k = isnan (p) | ! (p >= 0) | ! (p <= 1) | ! (mu >= 0.5) | ! (omega > 0); x(k) = NaN; ## Handle edge cases k = (p == 1) & (mu >= 0.5) & (mu < Inf) & (omega > 0) & (omega < Inf); x(k) = Inf; ## Find normal cases k = (0 < p) & (p < 1) & (0.5 <= mu) & (mu < Inf) ... & (0 < omega) & (omega < Inf); ## Compute Nakagami iCDF if (isscalar (mu) && isscalar (omega)) m_gamma = mu; w_gamma = omega / mu; x(k) = gaminv (p(k), m_gamma, w_gamma); x(k) = sqrt (x(k)); else m_gamma = mu; w_gamma = omega ./ mu; x(k) = gaminv (p(k), m_gamma(k), w_gamma(k)); x(k) = sqrt (x(k)); endif endfunction %!demo %! ## Plot various iCDFs from the Nakagami distribution %! p = 0.001:0.001:0.999; %! x1 = nakainv (p, 0.5, 1); %! x2 = nakainv (p, 1, 1); %! x3 = nakainv (p, 1, 2); %! x4 = nakainv (p, 1, 3); %! x5 = nakainv (p, 2, 1); %! x6 = nakainv (p, 2, 2); %! x7 = nakainv (p, 5, 1); %! plot (p, x1, '-r', p, x2, '-g', p, x3, '-y', p, x4, '-m', ... %! p, x5, '-k', p, x6, '-b', p, x7, '-c') %! grid on %! ylim ([0, 3]) %! legend ({'μ = 0.5, ω = 1', 'μ = 1, ω = 1', 'μ = 1, ω = 2', ... %! 'μ = 1, ω = 3', 'μ = 2, ω = 1', 'μ = 2, ω = 2', ... %! 'μ = 5, ω = 1'}, 'location', 'northwest') %! title ('Nakagami iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p, y %! p = [-Inf, -1, 0, 1/2, 1, 2, Inf]; %! y = [NaN, NaN, 0, 0.83255461115769769, Inf, NaN, NaN]; %!assert_equal (nakainv (p, ones (1,7), ones (1,7)), y, eps) %!assert_equal (nakainv (p, 1, 1), y, eps) %!assert_equal (nakainv (p, [1, 1, 1, NaN, 1, 1, 1], 1), [y(1:3), NaN, y(5:7)], eps) %!assert_equal (nakainv (p, 1, [1, 1, 1, NaN, 1, 1, 1]), [y(1:3), NaN, y(5:7)], eps) %!assert_equal (nakainv ([p, NaN], 1, 1), [y, NaN], eps) ## Test class of input preserved %!assert_equal (nakainv (single ([p, NaN]), 1, 1), single ([y, NaN])) %!assert_equal (nakainv ([p, NaN], single (1), 1), single ([y, NaN])) %!assert_equal (nakainv ([p, NaN], 1, single (1)), single ([y, NaN])) ## Test input validation %!error nakainv () %!error nakainv (1) %!error nakainv (1, 2) %!error ... %! nakainv (ones (3), ones (2), ones (2)) %!error ... %! nakainv (ones (2), ones (3), ones (2)) %!error ... %! nakainv (ones (2), ones (2), ones (3)) %!error nakainv (int32 (2), 4, 3) %!error nakainv (true, 4, 3) %!error nakainv ('a', 4, 3) %!error nakainv (i, 4, 3) %!error nakainv (1, i, 3) %!error nakainv (1, 4, i) statistics-release-1.9.2/inst/Distribution_Functions/nakapdf.m000066400000000000000000000140201524624707500246120ustar00rootroot00000000000000## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 1995-2015 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} nakapdf (@var{x}, @var{mu}, @var{omega}) ## ## Nakagami probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the Nakagami distribution with shape parameter @var{mu} and spread ## parameter @var{omega}. The size of @var{y} is the common size of @var{x}, ## @var{mu}, and @var{omega}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Both parameters must be positive reals and @qcode{@var{mu} >= 0.5}. For ## @qcode{@var{mu} < 0.5} or @qcode{@var{omega} <= 0}, @qcode{NaN} is returned. ## ## Further information about the Nakagami distribution can be found at ## @url{https://en.wikipedia.org/wiki/Nakagami_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{nakacdf, nakainv, nakarnd, nakafit, nakalike, nakastat} ## @end deftypefn function y = nakapdf (x, mu, omega) ## Check for valid number of input arguments if (nargin < 3) error ("nakapdf: function called with too few input arguments."); endif ## Check for common size of X, MU, and OMEGA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (omega)) [retval, x, mu, omega] = common_size (x, mu, omega); if (retval > 0) error ("nakapdf: X, MU, and OMEGA must be of common size or scalars."); endif endif ## Check for X, MU, and OMEGA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (omega))) error ("nakapdf: X, MU, and OMEGA must be double or single."); endif ## Check for X, MU, and OMEGA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (omega)) error ("nakapdf: X, MU, and OMEGA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (omega, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Compute Nakagami PDF k = isnan (x) | ! (mu >= 0.5) | ! (omega > 0); y(k) = NaN; k = (0 < x) & (x < Inf) & (0.5 <= mu) & (mu < Inf) ... & (0 < omega) & (omega < Inf); if (isscalar (mu) && isscalar (omega)) y(k) = exp (log (2) + mu * log (mu) - log (gamma (mu)) - ... mu * log (omega) + (2 * mu-1) * ... log (x(k)) - (mu / omega) * x(k) .^ 2); else y(k) = exp (log (2) + mu(k) .* log (mu(k)) - log (gamma (mu(k))) - ... mu(k) .* log (omega(k)) + (2 * mu(k) - 1) ... .* log (x(k)) - (mu(k) ./ omega(k)) .* x(k) .^ 2); endif endfunction %!demo %! ## Plot various PDFs from the Nakagami distribution %! x = 0:0.01:3; %! y1 = nakapdf (x, 0.5, 1); %! y2 = nakapdf (x, 1, 1); %! y3 = nakapdf (x, 1, 2); %! y4 = nakapdf (x, 1, 3); %! y5 = nakapdf (x, 2, 1); %! y6 = nakapdf (x, 2, 2); %! y7 = nakapdf (x, 5, 1); %! plot (x, y1, '-r', x, y2, '-g', x, y3, '-y', x, y4, '-m', ... %! x, y5, '-k', x, y6, '-b', x, y7, '-c') %! grid on %! xlim ([0, 3]) %! ylim ([0, 2]) %! legend ({'μ = 0.5, ω = 1', 'μ = 1, ω = 1', 'μ = 1, ω = 2', ... %! 'μ = 1, ω = 3', 'μ = 2, ω = 1', 'μ = 2, ω = 2', ... %! 'μ = 5, ω = 1'}, 'location', 'northeast') %! title ('Nakagami PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1, 0, 1, 2, Inf]; %! y = [0, 0, 0.73575888234288467, 0.073262555554936715, 0]; %!assert_equal (nakapdf (x, ones (1,5), ones (1,5)), y, eps) %!assert_equal (nakapdf (x, 1, 1), y, eps) %!assert_equal (nakapdf (x, [1, 1, NaN, 1, 1], 1), [y(1:2), NaN, y(4:5)], eps) %!assert_equal (nakapdf (x, 1, [1, 1, NaN, 1, 1]), [y(1:2), NaN, y(4:5)], eps) %!assert_equal (nakapdf ([x, NaN], 1, 1), [y, NaN], eps) ## Test class of input preserved %!assert_equal (nakapdf (single ([x, NaN]), 1, 1), single ([y, NaN])) %!assert_equal (nakapdf ([x, NaN], single (1), 1), single ([y, NaN])) %!assert_equal (nakapdf ([x, NaN], 1, single (1)), single ([y, NaN])) ## Test input validation %!error nakapdf () %!error nakapdf (1) %!error nakapdf (1, 2) %!error ... %! nakapdf (ones (3), ones (2), ones (2)) %!error ... %! nakapdf (ones (2), ones (3), ones (2)) %!error ... %! nakapdf (ones (2), ones (2), ones (3)) %!error nakapdf (int32 (2), 4, 3) %!error nakapdf (true, 4, 3) %!error nakapdf ('a', 4, 3) %!error nakapdf (i, 4, 3) %!error nakapdf (1, i, 3) %!error nakapdf (1, 4, i) statistics-release-1.9.2/inst/Distribution_Functions/nakarnd.m000066400000000000000000000161011524624707500246260ustar00rootroot00000000000000## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 1995-2015 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} nakarnd (@var{mu}, @var{omega}) ## @deftypefnx {statistics} {@var{r} =} nakarnd (@var{mu}, @var{omega}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} nakarnd (@var{mu}, @var{omega}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} nakarnd (@var{mu}, @var{omega}, [@var{sz}]) ## ## Random arrays from the Nakagami distribution. ## ## @code{@var{r} = nakarnd (@var{mu}, @var{omega})} returns an array of random ## numbers chosen from the Nakagami distribution with shape parameter @var{mu} ## and spread parameter @var{omega}. The size of @var{r} is the common size of ## @var{mu} and @var{omega}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Both parameters must be positive reals and @qcode{@var{mu} >= 0.5}. For ## @qcode{@var{mu} < 0.5} or @qcode{@var{omega} <= 0}, @qcode{NaN} is returned. ## ## When called with a single size argument, @code{nakarnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the Nakagami distribution can be found at ## @url{https://en.wikipedia.org/wiki/Nakagami_distribution} ## ## @seealso{nakacdf, nakainv, nakapdf, nakafit, nakalike, nakastat} ## @end deftypefn function r = nakarnd (mu, omega, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("nakarnd: function called with too few input arguments."); endif ## Check for common size of MU and OMEGA if (! isscalar (mu) || ! isscalar (omega)) [retval, mu, omega] = common_size (mu, omega); if (retval > 0) error ("nakarnd: MU and OMEGA must be of common size or scalars."); endif endif ## Check for MU and OMEGA being reals if (iscomplex (mu) || iscomplex (omega)) error ("nakarnd: MU and OMEGA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (mu); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("nakarnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("nakarnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (mu) && ! isequal (size (mu), size (ones (sz)))) error ("nakarnd: MU and OMEGA must be scalars or of size SZ."); endif ## Check for class type if (isa (mu, 'single') || isa (omega, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from Nakagami distribution if (isscalar (mu) && isscalar (omega)) if ((0.5 <= mu) && (mu < Inf) && (0 < omega) && (omega < Inf)) m_gamma = mu; w_gamma = omega / mu; r = gamrnd (m_gamma, w_gamma, sz); r = sqrt (r); else r = NaN (sz, cls); endif else r = NaN (sz, cls); k = (0.5 <= mu) & (mu < Inf) & (0 < omega) & (omega < Inf); m_gamma = mu; w_gamma = omega ./ mu; r(k) = gamrnd (m_gamma(k), w_gamma(k)); r(k) = sqrt (r(k)); endif endfunction ## Test output %!assert_equal (size (nakarnd (1, 1)), [1, 1]) %!assert_equal (size (nakarnd (1, ones (2, 1))), [2, 1]) %!assert_equal (size (nakarnd (1, ones (2, 2))), [2, 2]) %!assert_equal (size (nakarnd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (nakarnd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (nakarnd (1, 1, 3)), [3, 3]) %!assert_equal (size (nakarnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (nakarnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (nakarnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (nakarnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (nakarnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (nakarnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (nakarnd (1, 1, [])), [0, 0]) %!assert_equal (size (nakarnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (nakarnd (1, 2, -1)), [0, 0]) %!assert_equal (size (nakarnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (nakarnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (nakarnd (1, 1)), "double") %!assert_equal (class (nakarnd (1, single (1))), "single") %!assert_equal (class (nakarnd (1, single ([1, 1]))), "single") %!assert_equal (class (nakarnd (single (1), 1)), "single") %!assert_equal (class (nakarnd (single ([1, 1]), 1)), "single") ## Test input validation %!error nakarnd () %!error nakarnd (1) %!error ... %! nakarnd (ones (3), ones (2)) %!error ... %! nakarnd (ones (2), ones (3)) %!error nakarnd (i, 2, 3) %!error nakarnd (1, i, 3) %!error ... %! nakarnd (1, 2, 1.2) %!error ... %! nakarnd (1, 2, ones (2)) %!error ... %! nakarnd (1, 2, [2 0 2.5]) %!error ... %! nakarnd (1, 2, 2, 1.5, 5) %!error ... %! nakarnd (2, ones (2), 3) %!error ... %! nakarnd (2, ones (2), [3, 2]) %!error ... %! nakarnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/nbincdf.m000066400000000000000000000231241524624707500246160ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} nbincdf (@var{x}, @var{r}, @var{ps}) ## @deftypefnx {statistics} {@var{p} =} nbincdf (@var{x}, @var{r}, @var{ps}, @qcode{'upper'}) ## ## Negative binomial cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the negative binomial distribution with parameters @var{r} and ## @var{ps}, where @var{r} is the number of successes until the experiment is ## stopped and @var{ps} is the probability of success in each experiment, given ## the number of failures in @var{x}. The size of @var{p} is the common size of ## @var{x}, @var{r}, and @var{ps}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## The algorithm uses the cumulative sums of the binomial masses. ## ## @code{@var{p} = nbincdf (@var{x}, @var{r}, @var{ps}, "upper")} computes the ## upper tail probability of the negative binomial distribution with parameters ## @var{r} and @var{ps}, at the values in @var{x}. ## ## When @var{r} is an integer, the negative binomial distribution is also known ## as the Pascal distribution and it models the number of failures in @var{x} ## before a specified number of successes is reached in a series of independent, ## identical trials. Its parameters are the probability of success in a single ## trial, @var{ps}, and the number of successes, @var{r}. A special case of the ## negative binomial distribution, when @qcode{@var{r} = 1}, is the geometric ## distribution, which models the number of failures before the first success. ## ## @var{r} can also have non-integer positive values, in which form the negative ## binomial distribution, also known as the Polya distribution, has no ## interpretation in terms of repeated trials, but, like the Poisson ## distribution, it is useful in modeling count data. The negative binomial ## distribution is more general than the Poisson distribution because it has a ## variance that is greater than its mean, making it suitable for count data ## that do not meet the assumptions of the Poisson distribution. In the limit, ## as @var{r} increases to infinity, the negative binomial distribution ## approaches the Poisson distribution. ## ## Further information about the negative binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Negative_binomial_distribution} ## ## Input arguments must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted to ## @qcode{double}, so the result is always a probability. MATLAB is ## inconsistent here: for several of the discrete distributions it returns the ## result in the integer class of the input, truncating a probability to ## @math{0} or @math{1}. ## ## @seealso{nbininv, nbinpdf, nbinrnd, nbinfit, nbinlike, nbinstat} ## @end deftypefn function p = nbincdf (x, r, ps, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("nbincdf: function called with too few input arguments."); endif ## Check for "upper" flag if (nargin == 4 && strcmpi (uflag, 'upper')) uflag = true; elseif (nargin == 4 && ! strcmpi (uflag, 'upper')) error ("nbincdf: invalid argument for upper tail."); else uflag = false; endif ## Check for R and PS being scalars scalarNPS = (isscalar (r) & isscalar (ps)); ## Check for common size of X, R, and PS if (! isscalar (x) || ! isscalar (r) || ! isscalar (ps)) [retval, x, r, ps] = common_size (x, r, ps); if (retval > 0) error ("nbincdf: X, R, and PS must be of common size or scalars."); endif endif ## Check for X, R, and PS being double, single, or integer if (! (isnumeric (x) && isnumeric (r) && isnumeric (ps))) error ("nbincdf: X, R, and PS must be double, single, or integer."); endif ## Integer input is promoted to double, so the result is a probability ## rather than a value truncated to the input's integer type. if (isinteger (x)) x = double (x); endif if (isinteger (r)) r = double (r); endif if (isinteger (ps)) ps = double (ps); endif ## Check for X, R, and PS being reals if (iscomplex (x) || iscomplex (r) || iscomplex (ps)) error ("nbincdf: X, R, and PS must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (r, 'single') || isa (ps, 'single')) p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Force NaN for out of range or missing parameters and missing data NaN is_nan = (isnan (x) | isnan (r) | (r <= 0) | (r == Inf) | (ps < 0) | (ps > 1)); p(is_nan) = NaN; ## Compute P for X >= 0 xf = floor (x); k = find (xf >= 0 & ! is_nan); ## Return 1 for positive infinite values of X, unless "upper" is given: p = 0 k1 = find (isinf (xf(k))); if (any (k1)) if (uflag) p(k(k1)) = 0; else p(k(k1)) = 1; endif k(k1) = []; endif ## Return 1 when X < 0 and "upper" is given k1 = (x < 0 & ! is_nan); if (any (k1)) if (uflag) p(k1) = 1; endif endif ## Accumulate probabilities up to the maximum value in X if (any (k)) if (uflag) p(k) = betainc (ps(k), r(k), xf(k) + 1, 'upper'); else max_val = max (xf(k)); if (scalarNPS) tmp = cumsum (nbinpdf (0:max_val, r(1), ps(1))); p(k) = tmp(xf(k) + 1); else idx = (0:max_val)'; compare = idx(:, ones (size (k))); index = xf(k); index = index(:); index = index(:, ones (size (idx)))'; n_big = r(k); n_big = n_big(:); n_big = n_big(:, ones (size (idx)))'; ps_big = ps(k); ps_big = ps_big(:); ps_big = ps_big(:, ones (size (idx)))'; p0 = nbinpdf (compare, n_big, ps_big); indicator = find (compare > index); p0(indicator) = zeros (size (indicator)); p(k) = sum (p0,1); endif endif endif ## Prevent round-off errors p(p > 1) = 1; endfunction %!demo %! ## Plot various CDFs from the negative binomial distribution %! x = 0:50; %! p1 = nbincdf (x, 2, 0.15); %! p2 = nbincdf (x, 5, 0.2); %! p3 = nbincdf (x, 4, 0.4); %! p4 = nbincdf (x, 10, 0.3); %! plot (x, p1, '*r', x, p2, '*g', x, p3, '*k', x, p4, '*m') %! grid on %! xlim ([0, 40]) %! legend ({'r = 2, ps = 0.15', 'r = 5, ps = 0.2', 'r = 4, p = 0.4', ... %! 'r = 10, ps = 0.3'}, 'location', 'southeast') %! title ('Negative binomial CDF') %! xlabel ('values in x (number of failures)') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-1 0 1 2 Inf]; %! y = [0 1/2 3/4 7/8 1]; %!assert_equal (nbincdf (x, ones (1,5), 0.5*ones (1,5)), y) %!assert_equal (nbincdf (x, 1, 0.5*ones (1,5)), y) %!assert_equal (nbincdf (x, ones (1,5), 0.5), y) %!assert_equal (nbincdf (x, ones (1,5), 0.5, 'upper'), 1 - y, eps) %!assert_equal (nbincdf ([x(1:3) 0 x(5)], [0 1 NaN 1.5 Inf], 0.5), ... %! [NaN 1/2 NaN nbinpdf(0,1.5,0.5) NaN], eps) %!assert_equal (nbincdf (x, 1, 0.5*[-1 NaN 4 1 1]), [NaN NaN NaN y(4:5)]) %!assert_equal (nbincdf ([x(1:2) NaN x(4:5)], 1, 0.5), [y(1:2) NaN y(4:5)]) ## A density that overflowed made the summed CDF read 1 from 308 failures on ## and NaN past the mode, where the value at the mode is 0.5 exactly. %!assert_equal (nbincdf (1000, 1001, 0.5), 0.5, 1e-11) %!assert (all (isfinite (nbincdf (0:2500, 1001, 0.5)))) %!assert (all (diff (nbincdf (0:2500, 1001, 0.5)) >= 0)) ## Test class of input preserved %!assert_equal (nbincdf ([x, NaN], 1, 0.5), [y, NaN]) %!assert_equal (nbincdf (single ([x, NaN]), 1, 0.5), single ([y, NaN])) %!assert_equal (nbincdf ([x, NaN], single (1), 0.5), single ([y, NaN])) %!assert_equal (nbincdf ([x, NaN], 1, single (0.5)), single ([y, NaN])) ## Test input validation %!error nbincdf () %!error nbincdf (1) %!error nbincdf (1, 2) %!error nbincdf (1, 2, 3, 4) %!error nbincdf (1, 2, 3, 'some') %!error ... %! nbincdf (ones (3), ones (2), ones (2)) %!error ... %! nbincdf (ones (2), ones (3), ones (2)) %!error ... %! nbincdf (ones (2), ones (2), ones (3)) %!error nbincdf (true, 2, 2) %!error nbincdf ('a', 2, 2) %!assert_equal (class (nbincdf (int32 (2), 2, 2)), 'double') %!error nbincdf (i, 2, 2) %!error nbincdf (2, i, 2) %!error nbincdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/nbininv.m000066400000000000000000000257571524624707500246740ustar00rootroot00000000000000## Copyright (C) 1995-2012 Kurt Hornik ## Copyright (C) 2012-2016 Rik Wehbring ## Copyright (C) 2016-2017 Lachlan Andrew ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} nbininv (@var{p}, @var{r}, @var{ps}) ## ## Inverse of the negative binomial cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the negative binomial distribution with parameters @var{r} and @var{ps}, ## where @var{r} is the number of successes until the experiment is stopped and ## @var{ps} is the probability of success in each experiment, given the ## probability in @var{p}. The size of @var{x} is the common size of @var{p}, ## @var{r}, and @var{ps}. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## When @var{r} is an integer, the negative binomial distribution is also known ## as the Pascal distribution and it models the number of failures in @var{x} ## before a specified number of successes is reached in a series of independent, ## identical trials. Its parameters are the probability of success in a single ## trial, @var{ps}, and the number of successes, @var{r}. A special case of the ## negative binomial distribution, when @qcode{@var{r} = 1}, is the geometric ## distribution, which models the number of failures before the first success. ## ## @var{r} can also have non-integer positive values, in which form the negative ## binomial distribution, also known as the Polya distribution, has no ## interpretation in terms of repeated trials, but, like the Poisson ## distribution, it is useful in modeling count data. The negative binomial ## distribution is more general than the Poisson distribution because it has a ## variance that is greater than its mean, making it suitable for count data ## that do not meet the assumptions of the Poisson distribution. In the limit, ## as @var{r} increases to infinity, the negative binomial distribution ## approaches the Poisson distribution. ## ## Further information about the negative binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Negative_binomial_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{nbincdf, nbinpdf, nbinrnd, nbinfit, nbinlike, nbinstat} ## @end deftypefn function x = nbininv (p, r, ps) ## Check for valid number of input arguments if (nargin < 3) error ("nbininv: function called with too few input arguments."); endif ## Check for common size of P, R, and PS if (! isscalar (p) || ! isscalar (r) || ! isscalar (ps)) [retval, p, r, ps] = common_size (p, r, ps); if (retval > 0) error ("nbininv: P, R, and PS must be of common size or scalars."); endif endif ## Check for P, R, and PS being double or single if (! (isfloat (p) && isfloat (r) && isfloat (ps))) error ("nbininv: P, R, and PS must be double or single."); endif ## Check for P, R, and PS being reals if (iscomplex (p) || iscomplex (r) || iscomplex (ps)) error ("nbininv: P, R, and PS must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (r, 'single') || isa (ps, 'single')) x = zeros (size (p), 'single'); else x = zeros (size (p)); endif k = (isnan (p) | (p < 0) | (p > 1) | isnan (r) | (r < 1) | (r == Inf) | isnan (ps) | (ps < 0) | (ps > 1)); x(k) = NaN; k = (p == 1) & (r > 0) & (r < Inf) & (ps >= 0) & (ps <= 1); x(k) = Inf; k = find ((p >= 0) & (p < 1) & (r > 0) & (r < Inf) & (ps > 0) & (ps <= 1)); if (! isempty (k)) pk = p(k)(:); if (isscalar (r) && isscalar (ps)) [m, unfinished] = scalar_nbininv (pk, r, ps); if (! isempty (unfinished)) m(unfinished) = bin_search_nbininv (pk(unfinished), r, ps); endif m = tie_correct (m, pk, r, ps); else rk = r(k)(:); psk = ps(k)(:); m = bin_search_nbininv (pk, rk, psk); m = tie_correct (m, pk, rk, psk); endif x(k) = m; endif endfunction ## Core algorithm to calculate the inverse negative binomial, for r and ps real ## scalars and y a column vector, and for which the output is not NaN or Inf. ## Compute CDF in batches of doubling size until CDF > p, or answer > 500. ## Return the locations of unfinished cases in k. function [m, k] = scalar_nbininv (p, r, ps) k = 1:length (p); m = zeros (size (p)); prev_limit = 0; limit = 10; do cdf = nbincdf (prev_limit:limit, r, ps); rr = bsxfun (@le, p(k), cdf); [v, m(k)] = max (rr, [], 2); # find first instance of p <= cdf m(k) += prev_limit - 1; k = k(v == 0); prev_limit = limit; limit += limit; until (isempty (k) || limit >= 1000) endfunction ## Vectorized binary search. ## Can handle vectors r and ps, and is faster than the scalar case when the ## answer is large. ## Could be optimized to call nbincdf only for a subset of the p at each stage, ## but care must be taken to handle both scalar and vector r,ps. Bookkeeping ## may cost more than the extra computations. function m = bin_search_nbininv (p, r, ps) k = 1:length (p); lower = zeros (size (p)); limit = 1; while (any (k) && limit < 1e100) cdf = nbincdf (limit, r, ps); k = (p > cdf); lower(k) = limit; limit += limit; endwhile upper = max (2*lower, 1); k = find (lower != limit/2); # elements for which above loop finished for i = 1:ceil (log2 (max (lower))) mid = (upper + lower)/2; cdf = nbincdf (floor (mid), r, ps); rr = (p <= cdf); upper(rr) = mid(rr); lower(! rr) = mid(! rr); endfor m = ceil (lower); m(p > nbincdf (m, r, ps)) += 1; # fix off-by-one errors from binary search endfunction ## Step the answer back onto M-1 wherever P lies within the error of the CDF ## there. The lower tail is a sum of M+1 densities, so its error grows with ## the answer, and an exactly attained probability reads short and sends the ## search past it: nbincdf (100, 101, 0.5) is 215 eps below the 0.5 that the ## symmetry of a fair coin makes exact, and the shortfall reaches 5616 eps by ## 2001 successes. The slack is capped at half the probability of M so that ## it can never cross a real step of the distribution and round P to the wrong ## side of one. function m = tie_correct (m, p, r, ps) j = find (m > 0); if (isempty (j)) return; endif mj = m(j); if (isscalar (r)) rj = r; else rj = r(j); endif if (isscalar (ps)) psj = ps; else psj = ps(j); endif lo = nbincdf (mj - 1, rj, psj); tol = min (32 .* (mj + 1) .* eps (lo), 0.5 .* nbinpdf (mj, rj, psj)); m(j(p(j) <= lo + tol)) -= 1; endfunction %!demo %! ## Plot various iCDFs from the negative binomial distribution %! p = 0.001:0.001:0.999; %! x1 = nbininv (p, 2, 0.15); %! x2 = nbininv (p, 5, 0.2); %! x3 = nbininv (p, 4, 0.4); %! x4 = nbininv (p, 10, 0.3); %! plot (p, x1, '-r', p, x2, '-g', p, x3, '-k', p, x4, '-m') %! grid on %! ylim ([0, 40]) %! legend ({'r = 2, ps = 0.15', 'r = 5, ps = 0.2', 'r = 4, p = 0.4', ... %! 'r = 10, ps = 0.3'}, 'location', 'northwest') %! title ('Negative binomial iCDF') %! xlabel ('probability') %! ylabel ('values in x (number of failures)') ## Test output %!shared p %! p = [-1 0 3/4 1 2]; %!assert_equal (nbininv (p, ones (1,5), 0.5*ones (1,5)), [NaN 0 1 Inf NaN]) %!assert_equal (nbininv (p, 1, 0.5*ones (1,5)), [NaN 0 1 Inf NaN]) %!assert_equal (nbininv (p, ones (1,5), 0.5), [NaN 0 1 Inf NaN]) %!assert_equal (nbininv (p, [1 0 NaN Inf 1], 0.5), [NaN NaN NaN NaN NaN]) %!assert_equal (nbininv (p, [1 0 1.5 Inf 1], 0.5), [NaN NaN 2 NaN NaN]) %!assert_equal (nbininv (p, 1, 0.5*[1 -Inf NaN Inf 1]), [NaN NaN NaN NaN NaN]) %!assert_equal (nbininv ([p(1:2) NaN p(4:5)], 1, 0.5), [NaN 0 NaN Inf NaN]) ## Test class of input preserved %!assert_equal (nbininv ([p, NaN], 1, 0.5), [NaN 0 1 Inf NaN NaN]) %!assert_equal (nbininv (single ([p, NaN]), 1, 0.5), single ([NaN 0 1 Inf NaN NaN])) %!assert_equal (nbininv ([p, NaN], single (1), 0.5), single ([NaN 0 1 Inf NaN NaN])) %!assert_equal (nbininv ([p, NaN], 1, single (0.5)), single ([NaN 0 1 Inf NaN NaN])) ## Test accuracy, to within +/- 1 since it is a discrete distribution %!shared y, tol %! y = magic (3) + 1; %! tol = 1; %!assert_equal (nbininv (nbincdf (1:10, 3, 0.1), 3, 0.1), 1:10, tol) %!assert_equal (nbininv (nbincdf (1:10, 3./(1:10), 0.1), 3./(1:10), 0.1), 1:10, tol) %!assert_equal (nbininv (nbincdf (y, 3./y, 1./y), 3./y, 1./y), y, tol) ## Test an exactly attained probability, where the CDF reads short. For r ## successes at ps = 0.5 the median is r-1 exactly, a fair coin over 2r-1 ## trials giving at least r successes half the time. %!assert_equal (nbininv (0.5, 26, 0.5), 25) %!assert_equal (nbininv (0.5, 101, 0.5), 100) %!assert_equal (nbininv (0.5, 251, 0.5), 250) %!assert_equal (nbininv (0.5, 2001, 0.5), 2000) %!assert_equal (nbininv ([0.5, 0.9], 101, 0.5), [100, 120]) %!assert_equal (nbininv ([0.5; 0.9], 101, 0.5), [100; 120]) %!assert_equal (nbininv ([0.5, 0.5], [101, 251], 0.5), [100, 250]) %!assert_equal (nbininv ([NaN, 0.5], 101, 0.5), [NaN, 100]) %!assert_equal (nbininv ([2, 0.5], [101, 101], [0.5, 0.5]), [NaN, 100]) ## Test input validation %!error nbininv () %!error nbininv (1) %!error nbininv (1, 2) %!error ... %! nbininv (ones (3), ones (2), ones (2)) %!error ... %! nbininv (ones (2), ones (3), ones (2)) %!error ... %! nbininv (ones (2), ones (2), ones (3)) %!error nbininv (int32 (2), 2, 2) %!error nbininv (true, 2, 2) %!error nbininv ('a', 2, 2) %!error nbininv (i, 2, 2) %!error nbininv (2, i, 2) %!error nbininv (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/nbinpdf.m000066400000000000000000000210671524624707500246370ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} nbinpdf (@var{x}, @var{r}, @var{ps}) ## ## Negative binomial probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## at @var{x} of the negative binomial distribution with parameters @var{r} and ## @var{ps}, where @var{r} is the number of successes until the experiment is ## stopped and @var{ps} is the probability of success in each experiment, given ## the number of failures in @var{x}. The size of @var{y} is the common size of ## @var{x}, @var{r}, and @var{ps}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## When @var{r} is an integer, the negative binomial distribution is also known ## as the Pascal distribution and it models the number of failures in @var{x} ## before a specified number of successes is reached in a series of independent, ## identical trials. Its parameters are the probability of success in a single ## trial, @var{ps}, and the number of successes, @var{r}. A special case of the ## negative binomial distribution, when @qcode{@var{r} = 1}, is the geometric ## distribution, which models the number of failures before the first success. ## ## @var{r} can also have non-integer positive values, in which form the negative ## binomial distribution, also known as the Polya distribution, has no ## interpretation in terms of repeated trials, but, like the Poisson ## distribution, it is useful in modeling count data. The negative binomial ## distribution is more general than the Poisson distribution because it has a ## variance that is greater than its mean, making it suitable for count data ## that do not meet the assumptions of the Poisson distribution. In the limit, ## as @var{r} increases to infinity, the negative binomial distribution ## approaches the Poisson distribution. ## ## Further information about the negative binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Negative_binomial_distribution} ## ## Input arguments must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted to ## @qcode{double}, so the result is always a probability. MATLAB is ## inconsistent here: for several of the discrete distributions it returns the ## result in the integer class of the input, truncating a probability to ## @math{0} or @math{1}. ## ## @seealso{nbincdf, nbininv, nbinrnd, nbinfit, nbinlike, nbinstat} ## @end deftypefn function y = nbinpdf (x, r, ps) ## Check for valid number of input arguments if (nargin < 3) error ("nbinpdf: function called with too few input arguments."); endif ## Check for common size of X, R, and PS if (! isscalar (x) || ! isscalar (r) || ! isscalar (ps)) [retval, x, r, ps] = common_size (x, r, ps); if (retval > 0) error ("nbinpdf: X, R, and PS must be of common size or scalars."); endif endif ## Check for X, R, and PS being double, single, or integer if (! (isnumeric (x) && isnumeric (r) && isnumeric (ps))) error ("nbinpdf: X, R, and PS must be double, single, or integer."); endif ## Integer input is promoted to double, so the result is a probability ## rather than a value truncated to the input's integer type. if (isinteger (x)) x = double (x); endif if (isinteger (r)) r = double (r); endif if (isinteger (ps)) ps = double (ps); endif ## Check for X, R, and PS being reals if (iscomplex (x) || iscomplex (r) || iscomplex (ps)) error ("nbinpdf: X, R, and PS must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (r, 'single') || isa (ps, 'single')) y = NaN (size (x), 'single'); else y = NaN (size (x)); endif ok = (x < Inf) & (x == fix (x)) & (r > 0) & (r < Inf) & (ps >= 0) & (ps <= 1); k = (x < 0) & ok; y(k) = 0; k = (x >= 0) & ok; if (isscalar (r) && isscalar (ps)) y(k) = bincoeff (-r, x(k)) .* (ps ^ r) .* ((ps - 1) .^ x(k)); else y(k) = bincoeff (-r(k), x(k)) .* (ps(k) .^ r(k)) .* ((ps(k) - 1) .^ x(k)); endif ## The three factors leave the range of a double long before their product ## does: at 1001 successes bincoeff is Inf from 308 failures on, where the ## density is an ordinary number peaking at 0.0089. Recompute those through ## the log gamma form, which holds the whole product in the exponent. b = k & ! isfinite (y); if (any (b(:))) xb = x(b); if (isscalar (r)) rb = r; else rb = r(b); endif if (isscalar (ps)) psb = ps; else psb = ps(b); endif t = xb .* log1p (-psb); t(xb == 0) = 0; y(b) = exp (gammaln (xb + rb) - gammaln (rb) - gammaln (xb + 1) ... + rb .* log (psb) + t); endif ## The density at an infinite abscissa is zero: no proper distribution ## places mass there. y(isinf (x) & (r > 0) & (r < Inf) & (ps >= 0) & (ps <= 1)) = 0; endfunction %!demo %! ## Plot various PDFs from the negative binomial distribution %! x = 0:40; %! y1 = nbinpdf (x, 2, 0.15); %! y2 = nbinpdf (x, 5, 0.2); %! y3 = nbinpdf (x, 4, 0.4); %! y4 = nbinpdf (x, 10, 0.3); %! plot (x, y1, '*r', x, y2, '*g', x, y3, '*k', x, y4, '*m') %! grid on %! xlim ([0, 40]) %! ylim ([0, 0.12]) %! legend ({'r = 2, ps = 0.15', 'r = 5, ps = 0.2', 'r = 4, p = 0.4', ... %! 'r = 10, ps = 0.3'}, 'location', 'northeast') %! title ('Negative binomial PDF') %! xlabel ('values in x (number of failures)') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1 0 1 2 Inf]; %! y = [0 1/2 1/4 1/8 0]; %!assert_equal (nbinpdf (x, ones (1,5), 0.5*ones (1,5)), y) %!assert_equal (nbinpdf (x, 1, 0.5*ones (1,5)), y) %!assert_equal (nbinpdf (x, ones (1,5), 0.5), y) %!assert_equal (nbinpdf (x, [0 1 NaN 1.5 Inf], 0.5), [NaN 1/2 NaN 1.875*0.5^1.5/4 NaN], eps) %!assert_equal (nbinpdf (Inf, 5, 0.4), 0) %!assert_equal (nbinpdf (x, 1, 0.5*[-1 NaN 4 1 1]), [NaN NaN NaN y(4:5)]) %!assert_equal (nbinpdf ([x, NaN], 1, 0.5), [y, NaN]) ## The three factors leave the range of a double before their product does: ## bincoeff (-1001, 1000) is Inf where the density peaks at 0.0089. ## The rescue path is exp (gammaln (...) - gammaln (...) - gammaln (...) + ...), ## so the value rides on the platform's lgamma and moves about 2e-12 relative ## between them. Assert the density, not the agreement of lgamma. %!assert_equal (nbinpdf (1000, 1001, 0.5), 0.0089195055729428853, -1e-10) %!assert_equal (nbinpdf (2000, 1001, 0.5), 1.2637737073869266e-76, -1e-10) %!assert (all (isfinite (nbinpdf (0:2500, 1001, 0.5)))) ## Test class of input preserved %!assert_equal (nbinpdf (single ([x, NaN]), 1, 0.5), single ([y, NaN])) %!assert_equal (nbinpdf ([x, NaN], single (1), 0.5), single ([y, NaN])) %!assert_equal (nbinpdf ([x, NaN], 1, single (0.5)), single ([y, NaN])) ## Test input validation %!error nbinpdf () %!error nbinpdf (1) %!error nbinpdf (1, 2) %!error ... %! nbinpdf (ones (3), ones (2), ones (2)) %!error ... %! nbinpdf (ones (2), ones (3), ones (2)) %!error ... %! nbinpdf (ones (2), ones (2), ones (3)) %!error nbinpdf (true, 2, 2) %!error nbinpdf ('a', 2, 2) %!assert_equal (class (nbinpdf (int32 (2), 2, 2)), 'double') %!error nbinpdf (i, 2, 2) %!error nbinpdf (2, i, 2) %!error nbinpdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/nbinrnd.m000066400000000000000000000203731524624707500246500ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{rnd} =} nbinrnd (@var{r}, @var{ps}) ## @deftypefnx {statistics} {@var{rnd} =} nbinrnd (@var{r}, @var{ps}, @var{rows}) ## @deftypefnx {statistics} {@var{rnd} =} nbinrnd (@var{r}, @var{ps}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{rnd} =} nbinrnd (@var{r}, @var{ps}, [@var{sz}]) ## ## Random arrays from the negative binomial distribution. ## ## @code{@var{rnd} = nbinrnd (@var{r}, @var{ps})} returns an array of random ## numbers chosen from the negative binomial distribution with parameters ## @var{r} and @var{ps}, where @var{r} is the number of successes until the ## experiment is stopped and @var{ps} is the probability of success in each ## experiment, given the number of failures in @var{x}. The size of @var{rnd} ## is the common size of @var{r} and @var{ps}. A scalar input functions as a ## constant matrix of the same size as the other inputs. ## ## When called with a single size argument, return a square matrix with ## the dimension specified. When called with more than one scalar argument the ## first two arguments are taken as the number of rows and columns and any ## further arguments specify additional matrix dimensions. The size may also ## be specified with a vector of dimensions @var{sz}. ## ## When @var{r} is an integer, the negative binomial distribution is also known ## as the Pascal distribution and it models the number of failures in @var{x} ## before a specified number of successes is reached in a series of independent, ## identical trials. Its parameters are the probability of success in a single ## trial, @var{ps}, and the number of successes, @var{r}. A special case of the ## negative binomial distribution, when @qcode{@var{r} = 1}, is the geometric ## distribution, which models the number of failures before the first success. ## ## @var{r} can also have non-integer positive values, in which form the negative ## binomial distribution, also known as the Polya distribution, has no ## interpretation in terms of repeated trials, but, like the Poisson ## distribution, it is useful in modeling count data. The negative binomial ## distribution is more general than the Poisson distribution because it has a ## variance that is greater than its mean, making it suitable for count data ## that do not meet the assumptions of the Poisson distribution. In the limit, ## as @var{r} increases to infinity, the negative binomial distribution ## approaches the Poisson distribution. ## ## Further information about the negative binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Negative_binomial_distribution} ## ## @seealso{nbincdf, nbininv, nbinpdf, nbinfit, nbinlike, nbinstat} ## @end deftypefn function rnd = nbinrnd (r, ps, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("nbinrnd: function called with too few input arguments."); endif ## Check for common size R and PS if (! isscalar (r) || ! isscalar (ps)) [retval, r, ps] = common_size (r, ps); if (retval > 0) error ("nbinrnd: R and PS must be of common size or scalars."); endif endif ## Check for R and PS being reals if (iscomplex (r) || iscomplex (ps)) error ("nbinrnd: R and PS must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (r); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) rnd = []; return; else error (strcat ("nbinrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("nbinrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (r) && ! isequal (size (r), size (ones (sz)))) error ("nbinrnd: R and PS must be scalars or of size SZ."); endif ## Check for class type if (isa (r, 'single') || isa (ps, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from negative binomial distribution if (isscalar (r) && isscalar (ps)) if ((r > 0) && (r < Inf) && (ps > 0) && (ps <= 1)) rnd = randp ((1 - ps) ./ ps .* randg (r, sz, cls), cls); elseif ((r > 0) && (r < Inf) && (ps == 0)) rnd = zeros (sz, cls); else rnd = NaN (sz, cls); endif else rnd = NaN (sz, cls); k = (r > 0) & (r < Inf) & (ps == 0); rnd(k) = 0; k = (r > 0) & (r < Inf) & (ps > 0) & (ps <= 1); rnd(k) = randp ((1 - ps(k)) ./ ps(k) .* randg (r(k), cls)); endif endfunction ## Test output %!assert_equal (size (nbinrnd (1, 0.5)), [1, 1]) %!assert_equal (size (nbinrnd (1, 0.5 * ones (2, 1))), [2, 1]) %!assert_equal (size (nbinrnd (1, 0.5 * ones (2, 2))), [2, 2]) %!assert_equal (size (nbinrnd (ones (2, 1), 0.5)), [2, 1]) %!assert_equal (size (nbinrnd (ones (2, 2), 0.5)), [2, 2]) %!assert_equal (size (nbinrnd (1, 0.5, 3)), [3, 3]) %!assert_equal (size (nbinrnd (1, 0.5, [4, 1])), [4, 1]) %!assert_equal (size (nbinrnd (1, 0.5, 4, 1)), [4, 1]) %!assert_equal (size (nbinrnd (1, 0.5, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (nbinrnd (1, 0.5, 0, 1)), [0, 1]) %!assert_equal (size (nbinrnd (1, 0.5, 1, 0)), [1, 0]) %!assert_equal (size (nbinrnd (1, 0.5, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (nbinrnd (1, 0.5, [])), [0, 0]) %!assert_equal (size (nbinrnd (1, 0.5, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (nbinrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (nbinrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (nbinrnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (nbinrnd (1, 0.5)), "double") %!assert_equal (class (nbinrnd (1, single (0.5))), "single") %!assert_equal (class (nbinrnd (1, single ([0.5, 0.5]))), "single") %!assert_equal (class (nbinrnd (single (1), 0.5)), "single") %!assert_equal (class (nbinrnd (single ([1, 1]), 0.5)), "single") ## Test input validation %!error nbinrnd () %!error nbinrnd (1) %!error ... %! nbinrnd (ones (3), ones (2)) %!error ... %! nbinrnd (ones (2), ones (3)) %!error nbinrnd (i, 2, 3) %!error nbinrnd (1, i, 3) %!error ... %! nbinrnd (1, 2, 1.2) %!error ... %! nbinrnd (1, 2, ones (2)) %!error ... %! nbinrnd (1, 2, [2 0 2.5]) %!error ... %! nbinrnd (1, 2, 2, 1.5, 5) %!error ... %! nbinrnd (2, ones (2), 3) %!error ... %! nbinrnd (2, ones (2), [3, 2]) %!error ... %! nbinrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/ncfcdf.m000066400000000000000000000243551524624707500244450ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} ncfcdf (@var{x}, @var{df1}, @var{df2}, @var{lambda}) ## @deftypefnx {statistics} {@var{p} =} ncfcdf (@var{x}, @var{df1}, @var{df2}, @var{lambda}, @qcode{'upper'}) ## ## Noncentral @math{F}-cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the noncentral @math{F}-distribution with @var{df1} and @var{df2} ## degrees of freedom and noncentrality parameter @var{lambda}. The size of ## @var{p} is the common size of @var{x}, @var{df1}, @var{df2}, and ## @var{lambda}. A scalar input functions as a constant matrix of the same size ## as the other inputs. ## ## @code{@var{p} = ncfcdf (@var{x}, @var{df1}, @var{df2}, @var{lambda}, ## "upper")} ## computes the upper tail probability of the noncentral @math{F}-distribution ## with parameters @var{df1}, @var{df2}, and @var{lambda}, at the values in ## @var{x}. ## ## Further information about the noncentral @math{F}-distribution can be found ## at @url{https://en.wikipedia.org/wiki/Noncentral_F-distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{ncfinv, ncfpdf, ncfrnd, ncfstat, fcdf} ## @end deftypefn function p = ncfcdf (x, df1, df2, lambda, uflag) ## Check for valid number of input arguments if (nargin < 4) error ("ncfcdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 4) if (! strcmpi (uflag, 'upper')) error ("ncfcdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, DF1, DF2, and LAMBDA [err, x, df1, df2, lambda] = common_size (x, df1, df2, lambda); if (err > 0) error ("ncfcdf: X, DF1, DF2, and LAMBDA must be of common size or scalars."); endif ## Check for X, DF1, DF2, and LAMBDA being double or single if (! (isfloat (x) && isfloat (df1) && isfloat (df2) && isfloat (lambda))) error ("ncfcdf: X, DF1, DF2, and LAMBDA must be double or single."); endif ## Check for X, DF1, DF2, and LAMBDA being reals if (iscomplex (x) || iscomplex (df1) || iscomplex (df2) || iscomplex (lambda)) error ("ncfcdf: X, DF1, DF2, and LAMBDA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (df1, 'single') || ... isa (df2, 'single') || isa (lambda, 'single')) p = zeros (size (x), 'single'); c_eps = eps ('single') .^ (3/4); else p = zeros (size (x)); c_eps = eps .^ (3/4); endif ## Find NaNs in input arguments (if any) and propagate them to p is_nan = isnan (x) | isnan (df1) | isnan (df1) | isnan (lambda); p(is_nan) = NaN; ## For "upper" option, force p = 1 for x <= 0, and p = 0 for x == Inf, ## otherwise, force p = 1 for x == Inf. if (uflag) p(x == Inf & ! is_nan) = 0; p(x <= 0 & ! is_nan) = 1; else p(x == Inf & ! is_nan) = 1; endif ## Find invalid values of parameters and propagate them to p as NaN k = (df1 <= 0 | df2 <= 0 | lambda < 0); p(k) = NaN; ## Compute central distribution (lambda == 0) k0 = (lambda==0); if (any (k0(:))) if (uflag) p(k0) = fcdf (x(k0), df1(k0), df2(k0), 'upper'); else p(k0) = fcdf (x(k0), df1(k0), df2(k0)); endif endif ## Check if there are remaining elements and reset variables k1 = ! (k0 | k | x == Inf | x <= 0 | is_nan); if (! any (k1(:))) return; else x = x(k1); df1 = df1(k1); df2 = df2(k1); lambda = lambda(k1); endif ## Prepare variables x = x(:); df1 = df1(:) / 2; df2 = df2(:) / 2; lambda = lambda(:) / 2; ## Value passed to Beta distribution function. tmp = df1 .* x ./ (df2 + df1 .* x); logtmp = log (tmp); nu2const = df2 .* log (1 - tmp) - localgammaln (df2); ## Sum the series. The general idea is that we are going to sum terms ## of the form 'poisspdf(j,lambda) .* betacdf(tmp,j+df1,df2)' j0 = floor (lambda(:)); ## Compute Poisson pdf and beta cdf at the starting point if (uflag) bcdf0 = betainc (tmp, j0 + df1, df2, 'upper'); else bcdf0 = betacdf (tmp, j0 + df1, df2); endif ppdf0 = exp (-lambda + j0 .* log (lambda) - localgammaln (j0 + 1)); ## Set up for loop over values less than j0 y = ppdf0 .* bcdf0; ppdf = ppdf0; bcdf = bcdf0; olddy = zeros (size (lambda)); delty = zeros (size (lambda)); j = j0 - 1; ok = j >= 0; while (any (ok)) ## Use recurrence relation to compute new pdf and cdf ppdf(ok) = ppdf(ok) .* (j(ok) + 1) ./ lambda(ok); if (uflag) bcdf(ok) = betainc (tmp(ok), j(ok) + df1(ok), df2(ok), 'upper'); else db = exp ((j + df1) .* logtmp + nu2const + ... localgammaln (j + df1 + df2) - localgammaln (j + df1 + 1)); bcdf(ok) = bcdf(ok) + db(ok); endif delty(ok) = ppdf(ok) .* bcdf(ok); y(ok) = y(ok) + delty(ok); ## Convergence test: change must be small and not increasing ok = ok & (delty > y*c_eps | abs (delty) > olddy); j = j - 1; ok = ok & j >= 0; olddy(ok) = abs (delty(ok)); endwhile ## Set up again for loop upward from j0 ppdf = ppdf0; bcdf = bcdf0; olddy = zeros (size (lambda)); j = j0 + 1; ok = true (size (j)); ## Set up for loop to avoid endless loop for jj = 1:5000 ppdf = ppdf .* lambda ./ j; if (uflag) bcdf = betainc (tmp, j + df1, df2, 'upper'); else bcdf = bcdf - exp ((j + df1 - 1) .* logtmp + nu2const + ... localgammaln (j + df1 + df2 - 1) - localgammaln (j + df1)); endif delty = ppdf.*bcdf; ## ok = indices not converged y(ok) = y(ok) + delty(ok); ## Convergence test: change must be small and not increasing ok = ok & (delty>y*c_eps | abs (delty)>olddy); ## Break if all indices converged if (! any (ok)) break; endif olddy(ok) = abs (delty(ok)); j = j + 1; endfor if (jj == 5000) warning ("ncfcdf: no convergence."); endif ## Save returning p-value p(k1) = y; endfunction function x = localgammaln (y) x = Inf (size (y), class (y)); x(! (y < 0)) = gammaln (y(! (y < 0))); endfunction %!demo %! ## Plot various CDFs from the noncentral F distribution %! x = 0:0.01:5; %! p1 = ncfcdf (x, 2, 5, 1); %! p2 = ncfcdf (x, 2, 5, 2); %! p3 = ncfcdf (x, 5, 10, 1); %! p4 = ncfcdf (x, 10, 20, 10); %! plot (x, p1, '-r', x, p2, '-g', x, p3, '-k', x, p4, '-m') %! grid on %! xlim ([0, 5]) %! legend ({'df1 = 2, df2 = 5, λ = 1', 'df1 = 2, df2 = 5, λ = 2', ... %! 'df1 = 5, df2 = 10, λ = 1', 'df1 = 10, df2 = 20, λ = 10'}, ... %! 'location', 'southeast') %! title ('Noncentral F CDF') %! xlabel ('values in x') %! ylabel ('probability') %!demo %! ## Compare the noncentral F CDF with LAMBDA = 10 to the F CDF with the %! ## same number of numerator and denominator degrees of freedom (5, 20) %! %! x = 0.01:0.1:10.01; %! p1 = ncfcdf (x, 5, 20, 10); %! p2 = fcdf (x, 5, 20); %! plot (x, p1, '-', x, p2, '-'); %! grid on %! xlim ([0, 10]) %! legend ({'Noncentral F(5,20,10)', 'F(5,20)'}, 'location', 'southeast') %! title ('Noncentral F vs F CDFs') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!test %! x = -2:0.1:2; %! p = ncfcdf (x, 10, 1, 3); %! assert_equal (p([1:21]), zeros (1, 21), 1e-76); %! assert_equal (p(22), 0.004530737275319753, 1e-14); %! assert_equal (p(30), 0.255842099135669, 1e-14); %! assert_equal (p(41), 0.4379890998457305, 1e-14); %!test %! p = ncfcdf (12, 10, 3, 2); %! assert_equal (p, 0.9582287900447416, 1e-14); %!test %! p = ncfcdf (2, 3, 2, 1); %! assert_equal (p, 0.5731985522994989, 1e-14); %!test %! p = ncfcdf (2, 3, 2, 1, 'upper'); %! assert_equal (p, 0.4268014477004823, 1e-14); %!test %! p = ncfcdf ([3, 6], 3, 2, 5, 'upper'); %! assert_equal (p, [0.530248523596927, 0.3350482341323044], 1e-14); ## Test input validation %!error ncfcdf () %!error ncfcdf (1) %!error ncfcdf (1, 2) %!error ncfcdf (1, 2, 3) %!error ncfcdf (1, 2, 3, 4, 'tail') %!error ncfcdf (1, 2, 3, 4, 5) %!error ... %! ncfcdf (ones (3), ones (2), ones (2), ones (2)) %!error ... %! ncfcdf (ones (2), ones (3), ones (2), ones (2)) %!error ... %! ncfcdf (ones (2), ones (2), ones (3), ones (2)) %!error ... %! ncfcdf (ones (2), ones (2), ones (2), ones (3)) %!error ncfcdf (int32 (2), 2, 2, 2) %!error ncfcdf (true, 2, 2, 2) %!error ncfcdf ('a', 2, 2, 2) %!error ncfcdf (i, 2, 2, 2) %!error ncfcdf (2, i, 2, 2) %!error ncfcdf (2, 2, i, 2) %!error ncfcdf (2, 2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/ncfinv.m000066400000000000000000000207221524624707500244770ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} ncfinv (@var{p}, @var{df1}, @var{df2}, @var{lambda}) ## ## Inverse of the noncentral @math{F}-cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the noncentral @math{F}-distribution with @var{df1} and @var{df2} degrees of ## freedom and noncentrality parameter @var{lambda}. The size of @var{x} is the ## common size of @var{p}, @var{df1}, @var{df2}, and @var{lambda}. A scalar ## input functions as a constant matrix of the same size as the other inputs. ## ## @code{ncfinv} uses Newton's method to converge to the solution. ## ## Further information about the noncentral @math{F}-distribution can be found ## at @url{https://en.wikipedia.org/wiki/Noncentral_F-distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{ncfcdf, ncfpdf, ncfrnd, ncfstat, finv} ## @end deftypefn function x = ncfinv (p, df1, df2, lambda) ## Check for valid number of input arguments if (nargin < 4) error ("ncfinv: function called with too few input arguments."); endif ## Check for common size of P, DF1, DF2, and LAMBDA [err, p, df1, df2, lambda] = common_size (p, df1, df2, lambda); if (err > 0) error ("ncfinv: P, DF1, DF2, and LAMBDA must be of common size or scalars."); endif ## Check for P, DF1, DF2, and LAMBDA being double or single if (! (isfloat (p) && isfloat (df1) && isfloat (df2) && isfloat (lambda))) error ("ncfinv: P, DF1, DF2, and LAMBDA must be double or single."); endif ## Check for P, DF1, DF2, and LAMBDA being reals if (iscomplex (p) || iscomplex (df1) || iscomplex (df2) || iscomplex (lambda)) error ("ncfinv: P, DF1, DF2, and LAMBDA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (df1, 'single') || ... isa (df2, 'single') || isa (lambda, 'single')) x = NaN (size (p), 'single'); crit = sqrt (eps ('single')); else x = NaN (size (p), 'double'); crit = sqrt (eps ('double')); endif ## For lambda == 0, call finv d0 = lambda == 0; if (any (d0(:))) x(d0) = finv (p(d0), df1(d0), df2(d0)); endif ## For lambda > 0 and valid dfs valid = df1 > 0 & df2 > 0 & lambda > 0; ## Force x = 0 for p == 0 ax = Inf for p ==1 x(p == 0 & valid) = 0; x(p == 1 & valid) = Inf; ## Find remaining valid cases within the range of 0 < p < 1 k = find (p > 0 & p < 1 & valid); ## Return if nothing left if isempty (k) return; endif ## Reset input variables to remaining cases p = p(k); df1 = df1(k); df2 = df2(k); lambda = lambda(k); ## Initialize counter count_limit = 100; count = 0; ## Start at the mean (if it exists) mu0 = df2.*(df1+lambda) ./ (df1.*max (1,df2-2)); next = mu0; prev = 0; F = ncfcdf (mu0, df1, df2, lambda); while (count < count_limit) count += 1; next = (F - p) ./ ncfpdf (mu0, df1, df2, lambda); ## Prevent oscillations if (length (next) == length (prev)) t = sign (next) == -sign (prev); next(t) = sign (next(t)) .* min (abs (next(t)), abs (prev(t))) / 2; endif ## Prepare for next step mu1 = max (mu0 / 5, min (5 * mu0, mu0 - next)); ## Check that next step improves, otherwise abort F1 = ncfcdf (mu1, df1, df2, lambda); while (true) worse = (abs (F1-p) > abs (F - p) * (1 + crit)) & ... (abs (mu0 - mu1) > crit * mu0); if (! any (worse)) break; endif mu1(worse) = 0.5 * (mu1(worse) + mu0(worse)); F1(worse) = ncfcdf (mu1(worse), df1(worse), df2(worse), lambda(worse)); endwhile x(k) = mu1; ## Find elements that are not converged yet next = mu0 - mu1; mask = (abs (next) > crit * abs (mu0)); if (! any (mask)) break; endif ## Save parameters for these elements only F = F1(mask); mu0 = mu1(mask); prev = next(mask); if (! all (mask)) df1 = df1(mask); df2 = df2(mask); lambda = lambda(mask); p = p(mask); k = k(mask); endif endwhile if (count == count_limit) warning ("ncfinv: did not converge."); endif endfunction %!demo %! ## Plot various iCDFs from the noncentral F distribution %! p = 0.001:0.001:0.999; %! x1 = ncfinv (p, 2, 5, 1); %! x2 = ncfinv (p, 2, 5, 2); %! x3 = ncfinv (p, 5, 10, 1); %! x4 = ncfinv (p, 10, 20, 10); %! plot (p, x1, '-r', p, x2, '-g', p, x3, '-k', p, x4, '-m') %! grid on %! ylim ([0, 5]) %! legend ({'df1 = 2, df2 = 5, λ = 1', 'df1 = 2, df2 = 5, λ = 2', ... %! 'df1 = 5, df2 = 10, λ = 1', 'df1 = 10, df2 = 20, λ = 10'}, ... %! 'location', 'northwest') %! title ('Noncentral F iCDF') %! xlabel ('probability') %! ylabel ('values in x') %!demo %! ## Compare the noncentral F iCDF with LAMBDA = 10 to the F iCDF with the %! ## same number of numerator and denominator degrees of freedom (5, 20) %! %! p = 0.001:0.001:0.999; %! x1 = ncfinv (p, 5, 20, 10); %! x2 = finv (p, 5, 20); %! plot (p, x1, '-', p, x2, '-'); %! grid on %! ylim ([0, 10]) %! legend ({'Noncentral F(5,20,10)', 'F(5,20)'}, 'location', 'northwest') %! title ('Noncentral F vs F quantile functions') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!test %! x = [0,0.1775,0.3864,0.6395,0.9564,1.3712,1.9471,2.8215,4.3679,8.1865,Inf]; %! assert_equal (ncfinv ([0:0.1:1], 2, 3, 1), x, 1e-4); %!test %! x = [0,0.7492,1.3539,2.0025,2.7658,3.7278,5.0324,6.9826,10.3955,18.7665,Inf]; %! assert_equal (ncfinv ([0:0.1:1], 2, 3, 5), x, 1e-4); %!test %! x = [0,0.2890,0.8632,1.5653,2.4088,3.4594,4.8442,6.8286,10.0983,17.3736,Inf]; %! assert_equal (ncfinv ([0:0.1:1], 1, 4, 3), x, 1e-4); %!test %! x = [0.078410, 0.212716, 0.288618, 0.335752, 0.367963, 0.391460]; %! assert_equal (ncfinv (0.05, [1, 2, 3, 4, 5, 6], 10, 3), x, 1e-6); %!test %! x = [0.2574, 0.2966, 0.3188, 0.3331, 0.3432, 0.3507]; %! assert_equal (ncfinv (0.05, 5, [1, 2, 3, 4, 5, 6], 3), x, 1e-4); %!test %! x = [1.6090, 1.8113, 1.9215, 1.9911, NaN, 2.0742]; %! assert_equal (ncfinv (0.05, 1, [1, 2, 3, 4, -1, 6], 10), x, 1e-4); %!test %! assert_equal (ncfinv (0.996, 3, 5, 8), 58.0912074080671, 4e-12); ## Test input validation %!error ncfinv () %!error ncfinv (1) %!error ncfinv (1, 2) %!error ncfinv (1, 2, 3) %!error ... %! ncfinv (ones (3), ones (2), ones (2), ones (2)) %!error ... %! ncfinv (ones (2), ones (3), ones (2), ones (2)) %!error ... %! ncfinv (ones (2), ones (2), ones (3), ones (2)) %!error ... %! ncfinv (ones (2), ones (2), ones (2), ones (3)) %!error ncfinv (int32 (2), 2, 2, 2) %!error ncfinv (true, 2, 2, 2) %!error ncfinv ('a', 2, 2, 2) %!error ncfinv (i, 2, 2, 2) %!error ncfinv (2, i, 2, 2) %!error ncfinv (2, 2, i, 2) %!error ncfinv (2, 2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/ncfpdf.m000066400000000000000000000406411524624707500244560ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} ncfpdf (@var{x}, @var{df1}, @var{df2}, @var{lambda}) ## ## Noncentral @math{F}-probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the noncentral @math{F}-distribution with @var{df1} and @var{df2} degrees ## of freedom and noncentrality parameter @var{lambda}. The size of @var{y} is ## the common size of @var{x}, @var{df1}, @var{df2}, and @var{lambda}. A scalar ## input functions as a constant matrix of the same size as the other inputs. ## ## Further information about the noncentral @math{F}-distribution can be found ## at @url{https://en.wikipedia.org/wiki/Noncentral_F-distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{ncfcdf, ncfinv, ncfrnd, ncfstat, fpdf} ## @end deftypefn function y = ncfpdf (x, df1, df2, lambda) ## Check for valid number of input arguments if (nargin < 4) error ("ncfpdf: function called with too few input arguments."); endif ## Check for common size of X, DF1, DF2, and LAMBDA [err, x, df1, df2, lambda] = common_size (x, df1, df2, lambda); if (err > 0) error ("ncfpdf: X, DF1, DF2, and LAMBDA must be of common size or scalars."); endif ## Check for X, DF1, DF2, and LAMBDA being double or single if (! (isfloat (x) && isfloat (df1) && isfloat (df2) && isfloat (lambda))) error ("ncfpdf: X, DF1, DF2, and LAMBDA must be double or single."); endif ## Check for X, DF1, DF2, and LAMBDA being reals if (iscomplex (x) || iscomplex (df1) || iscomplex (df2) || iscomplex (lambda)) error ("ncfpdf: X, DF1, DF2, and LAMBDA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (df1, 'single') || ... isa (df2, 'single') || isa (lambda, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Find NaNs in input arguments (if any) and propagate them to p is_nan = isnan (x) | isnan (df1) | isnan (df2) | isnan (lambda); y(is_nan) = NaN; ## Force invalid parameter cases to NaN k1 = df1 <= 0 | df2 <= 0 | lambda < 0; y(k1) = NaN; ## Handle edge cases where x == 0 k2 = x == 0 & df1 < 2 & ! k1; y(k2) = Inf; k3 = x == 0 & df1 == 2 & ! k1; if (any (k3(:))) y(k3) = exp (-lambda(k3) / 2); endif ## Handle central distribution where lambda == 0 k4 = lambda == 0 & ! k1 & x > 0; if any (k4(:)) y(k4) = fpdf (x(k4), df1(k4), df2(k4)); endif ## Handle normal cases td = find (x > 0 & ! (k1 | k4)); ## Return if finished all normal cases if (isempty (td)) return; endif ## Reset input variables to remaining cases and pre-divide df1, df2 and lambda x = x(td); df1 = df1(td) / 2; df2 = df2(td) / 2; lambda = lambda(td) / 2; ## Use z and scaled x for convenience z = df1 .* x ./ (df1 .* x + df2); z1 = df2 ./ (df1 .* x + df2); xs = lambda .* z; % Find max K at which we start the recursion series K = zeros (size (x)); termK = zeros (size (x)); rsum = zeros (size (x)); ## Handy constant lnsr2pi = 0.9189385332046727; ## Process integer and non-integer df2 separately df2int = df2 == floor (df2); if (any (df2int(:))) # integers smallx = xs <= df1 ./ df2; largex = xs >= df2 .* (df1 + df2 - 1) & ! smallx; K(df2int & largex) = df2(df2int & largex); ## Compute K idx = df2int & ! (smallx | largex); if (any (idx(:))) d = 0.5 * (1 - xs(idx) - df1(idx)); K(idx) = floor (d + sqrt (d .^ 2 + xs(idx) .* (df2(idx) + 1))); endif ## For K == df2 K_df2 = df2int & K == df2; idz1 = K_df2 & z < 0.9; termK(idz1) = (df1(idz1) + df2(idz1) - 1) .* log (z(idz1)); idz2 = K_df2 & ! idz1; termK(idz2) = (df1(idz2) + df2(idz2) - 1) .* log1p (-z1(idz2)); ## For K == 0 Kzero = df2int & (df1 + K) <= 1; termK(Kzero) = StirlingError (df1(Kzero) + df2(Kzero)) - ... StirlingError (df1(Kzero)) - StirlingError (df2(Kzero)) - ... BinoPoisson (df1(Kzero), ... (df1(Kzero) + df2(Kzero)) .* z(Kzero)) - ... BinoPoisson (df2(Kzero), ... (df1(Kzero) + df2(Kzero)) .* z1(Kzero)); ## For all other K K_all = df2int & ! (K_df2 | Kzero); termK(K_all) = StirlingError (df1(K_all) + df2(K_all) - 1) - ... StirlingError (df1(K_all) + K(K_all) -1) - ... StirlingError (df2(K_all) - K(K_all)) - ... BinoPoisson (df1(K_all) + K(K_all) - 1, ... (df1(K_all) + df2(K_all) - 1) .* z(K_all)) - ... BinoPoisson (df2(K_all) - K(K_all), ... (df1(K_all) + df2(K_all) - 1) .* z1(K_all)); ## Poisson density for the leading term x1 = lambda .* z1; smallk = df2int & K <= x1 * realmin; y(td(smallk)) = termK(smallk) - x1(smallk); otherk = df2int & ! smallk; y(td(otherk)) = termK(otherk) - lnsr2pi - 0.5 * log (K(otherk)) - ... StirlingError (K(otherk)) - ... BinoPoisson (K(otherk), x1(otherk)); ## Sum recursively downwards term = ones (size (x)); k = K; ok = df2int & k > 0; while (any (ok(:))) k(ok) = k(ok) - 1; term(ok) = term(ok) .* (k(ok) + 1) .* ... (k(ok) + df1(ok)) ./ (df2(ok) - k(ok)) ./ xs(ok); ok = ok & term >= eps (rsum); rsum(ok) = rsum(ok) + term(ok); endwhile ## Sum recursively upwards term = ones (size (x)); k = K; ok = df2int & k < df2; while any (ok(:)) term(ok) = term(ok) .* xs(ok) .* ... (df2(ok) - k(ok)) ./ (k(ok) + df1(ok)) ./ (k(ok) + 1); ok = ok & term >= eps (rsum); rsum(ok) = rsum(ok) + term(ok); k(ok) = k(ok) + 1; endwhile endif if (any (! df2int(:))) # non-integers ## Compute K largex = ! df2int & xs > df1 ./ (df1 + df2); d = 0.5 * (1 + xs(largex) - df1(largex)); K(largex) = floor (d + sqrt (d .^ 2 + xs(largex) .* ... (df1(largex) + df2(largex) - 1))); ## For K == 0 Kzero = ! df2int & (df1 + K) <= 1; termK(Kzero) = StirlingError (df1(Kzero) + df2(Kzero)) - ... StirlingError (df1(Kzero)) - ... StirlingError (df2(Kzero)) - ... BinoPoisson (df1(Kzero), ... (df1(Kzero) + df2(Kzero)) .* z(Kzero)) - ... BinoPoisson (df2(Kzero), ... (df1(Kzero) + df2(Kzero)) .* z1(Kzero)); ## For K != 0 K_all = ! df2int & ! Kzero; termK(K_all) = StirlingError (df1(K_all) + df2(K_all) + K(K_all) - 1) - ... StirlingError (df1(K_all) + K(K_all) - 1) - ... StirlingError (df2(K_all)) - ... BinoPoisson (df1(K_all) + K(K_all) - 1, ... (df1(K_all) + df2(K_all) + K(K_all) - 1) .* ... z(K_all)) - ... BinoPoisson (df2(K_all), ... (df1(K_all) + df2(K_all) + K(K_all) - 1) .* ... z1(K_all)); ## Poisson density for the leading term smallk = ! df2int & K <= lambda * realmin; y(td(smallk)) = termK(smallk) - lambda(smallk); K_all = ! df2int & ! smallk; y(td(K_all)) = termK(K_all) - lnsr2pi - 0.5 * log (K(K_all)) - ... StirlingError (K(K_all)) - ... BinoPoisson (K(K_all), lambda(K_all)); ## Sum recursively downwards term = ones (size (x)); k = K; ok = ! df2int & k > 0; while (any (ok(:))) k(ok) = k(ok) - 1; term(ok) = term(ok) .* (k(ok) + 1) .* (k(ok) + df1(ok)) ./ ... (k(ok) + df1(ok) + df2(ok)) ./ xs(ok); ok = ok & term >= eps (rsum); rsum(ok) = rsum(ok) + term(ok); endwhile ## Sum recursively upwards term = ones (size (x)); k = K; ok = ! df2int; while (any (ok(:))) term(ok) = term(ok) .* xs(ok) .* (k(ok) + df1(ok) + df2(ok)) ./ ... (k(ok) + df1(ok)) ./ (k(ok) + 1); ok = ok & term >= eps (rsum); rsum(ok) = rsum(ok) + term(ok); k(ok) = k(ok)+1; endwhile endif ## Compute density pi2 = 2 * pi; Kzero = (df1 + K) <= 1; y(td(Kzero)) = exp (y(td(Kzero))) .* (1 + rsum(Kzero)) .* ... sqrt (df1(Kzero) .* df2(Kzero) ./ ... (df1(Kzero) + df2(Kzero)) / pi2) ./ ... x(Kzero); K_df2 = ! Kzero & df2int & K == df2; y(td(K_df2)) = exp (y(td(K_df2))) .* (1 + rsum(K_df2)) .* ... df1(K_df2) .* z1(K_df2); idx = ! Kzero & df2int & ! K_df2; y(td(idx)) = exp (y(td(idx))) .* (1 + rsum(idx)) .* df1(idx) .* z1(idx) .* ... sqrt ((df1(idx) + df2(idx) - 1) ./ (df2(idx) - K(idx)) ./ ... (df1(idx) + K(idx) - 1) / pi2); idx = ! df2int & ! Kzero; y(td(idx)) = exp (y(td(idx))) .* (1 + rsum(idx)) .* df1(idx) .* z1(idx) .* ... sqrt ((df1(idx) + df2(idx) + K(idx) - 1) ./ ... df2(idx) ./ (df1(idx) + K(idx) - 1) / pi2); endfunction ## Error of Stirling-De Moivre approximation to n factorial. function lambda = StirlingError (n) is_class = class (n); lambda = zeros (size (n), is_class); nn = n .* n; ## Define S0=1/12 S1=1/360 S2=1/1260 S3=1/1680 S4=1/1188 S0 = 8.333333333333333e-02; S1 = 2.777777777777778e-03; S2 = 7.936507936507937e-04; S3 = 5.952380952380952e-04; S4 = 8.417508417508418e-04; ## Define lambda(n) for n<0:0.5:15 sfe=[ 0; 1.534264097200273e-01;... 8.106146679532726e-02; 5.481412105191765e-02;... 4.134069595540929e-02; 3.316287351993629e-02;... 2.767792568499834e-02; 2.374616365629750e-02;... 2.079067210376509e-02; 1.848845053267319e-02;... 1.664469118982119e-02; 1.513497322191738e-02;... 1.387612882307075e-02; 1.281046524292023e-02;... 1.189670994589177e-02; 1.110455975820868e-02;... 1.041126526197210e-02; 9.799416126158803e-03;... 9.255462182712733e-03; 8.768700134139385e-03;... 8.330563433362871e-03; 7.934114564314021e-03;... 7.573675487951841e-03; 7.244554301320383e-03;... 6.942840107209530e-03; 6.665247032707682e-03;... 6.408994188004207e-03; 6.171712263039458e-03;... 5.951370112758848e-03; 5.746216513010116e-03;... 5.554733551962801e-03]; k = find (n <= 15); if (any (k)) n1 = n(k); n2 = 2 * n1; if (all (n2 == round (n2))) lambda(k) = sfe(n2+1); else lnsr2pi = 0.9189385332046728; lambda(k) = gammaln (n1+1)-(n1+0.5).*log (n1)+n1-lnsr2pi; endif endif k = find (n > 15 & n <= 35); if (any (k)) lambda(k) = (S0 - (S1 - (S2 - (S3 - S4 ./ nn(k)) ./ nn(k)) ./ ... nn(k)) ./ nn(k)) ./ n(k); endif k = find (n > 35 & n <= 80); if (any (k)) lambda(k) = (S0 - (S1 - (S2 - S3 ./ nn(k)) ./ nn(k)) ./ nn(k)) ./ n(k); endif k = find (n > 80 & n <= 500); if (any (k)) lambda(k) = (S0 - (S1 - S2 ./ nn(k)) ./ nn(k)) ./ n(k); endif k = find (n > 500); if (any (k)) lambda(k) = (S0 - S1 ./ nn(k)) ./ n(k); endif endfunction ## Deviance term for binomial and Poisson probability calculation. function BP = BinoPoisson (x, np) if (isa (x,'single') || isa (np,'single')) BP = zeros (size (x), 'single'); else BP = zeros (size (x)); endif k = abs (x - np) < 0.1 * (x + np); if any (k(:)) s = (x(k) - np(k)) .* (x(k) - np(k)) ./ (x(k) + np(k)); v = (x(k) - np(k)) ./ (x(k) + np(k)); ej = 2 .* x(k) .* v; is_class = class (s); s1 = zeros (size (s), is_class); ok = true (size (s)); j = 0; while any (ok(:)) ej(ok) = ej(ok) .* v(ok) .* v(ok); j = j + 1; s1(ok) = s(ok) + ej(ok) ./ (2 * j + 1); ok = ok & s1 != s; s(ok) = s1(ok); endwhile BP(k) = s; endif k = ! k; if (any (k(:))) BP(k) = x(k) .* log (x(k) ./ np(k)) + np(k) - x(k); endif endfunction %!demo %! ## Plot various PDFs from the noncentral F distribution %! x = 0:0.01:5; %! y1 = ncfpdf (x, 2, 5, 1); %! y2 = ncfpdf (x, 2, 5, 2); %! y3 = ncfpdf (x, 5, 10, 1); %! y4 = ncfpdf (x, 10, 20, 10); %! plot (x, y1, '-r', x, y2, '-g', x, y3, '-k', x, y4, '-m') %! grid on %! xlim ([0, 5]) %! ylim ([0, 0.8]) %! legend ({'df1 = 2, df2 = 5, λ = 1', 'df1 = 2, df2 = 5, λ = 2', ... %! 'df1 = 5, df2 = 10, λ = 1', 'df1 = 10, df2 = 20, λ = 10'}, ... %! 'location', 'northeast') %! title ('Noncentral F PDF') %! xlabel ('values in x') %! ylabel ('density') %!demo %! ## Compare the noncentral F PDF with LAMBDA = 10 to the F PDF with the %! ## same number of numerator and denominator degrees of freedom (5, 20) %! %! x = 0.01:0.1:10.01; %! y1 = ncfpdf (x, 5, 20, 10); %! y2 = fpdf (x, 5, 20); %! plot (x, y1, '-', x, y2, '-'); %! grid on %! xlim ([0, 10]) %! ylim ([0, 0.8]) %! legend ({'Noncentral F(5,20,10)', 'F(5,20)'}, 'location', 'northeast') %! title ('Noncentral F vs F PDFs') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x1, df1, df2, lambda %! x1 = [-Inf, 2, NaN, 4, Inf]; %! df1 = [2, 0, -1, 1, 4]; %! df2 = [2, 4, 5, 6, 8]; %! lambda = [1, NaN, 3, -1, 2]; %!assert_equal (ncfpdf (x1, df1, df2, lambda), [0, NaN, NaN, NaN, NaN]); %!assert_equal (ncfpdf (x1, df1, df2, 1), [0, NaN, NaN, ... %! 0.05607937264237208, NaN], 1e-14); %!assert_equal (ncfpdf (x1, df1, df2, 3), [0, NaN, NaN, ... %! 0.080125760971946518, NaN], 1e-14); %!assert_equal (ncfpdf (x1, df1, df2, 2), [0, NaN, NaN, ... %! 0.0715902008258656, NaN], 1e-14); %!assert_equal (ncfpdf (x1, 3, 5, lambda), [0, NaN, NaN, NaN, NaN]); %!assert_equal (ncfpdf (2, df1, df2, lambda), [0.1254046999837947, NaN, NaN, ... %! NaN, 0.2152571783045893], 1e-14); %!assert_equal (ncfpdf (4, df1, df2, lambda), [0.05067089541001374, NaN, NaN, ... %! NaN, 0.05560846335398539], 1e-14); ## Test input validation %!error ncfpdf () %!error ncfpdf (1) %!error ncfpdf (1, 2) %!error ncfpdf (1, 2, 3) %!error ... %! ncfpdf (ones (3), ones (2), ones (2), ones (2)) %!error ... %! ncfpdf (ones (2), ones (3), ones (2), ones (2)) %!error ... %! ncfpdf (ones (2), ones (2), ones (3), ones (2)) %!error ... %! ncfpdf (ones (2), ones (2), ones (2), ones (3)) %!error ncfpdf (int32 (2), 2, 2, 2) %!error ncfpdf (true, 2, 2, 2) %!error ncfpdf ('a', 2, 2, 2) %!error ncfpdf (i, 2, 2, 2) %!error ncfpdf (2, i, 2, 2) %!error ncfpdf (2, 2, i, 2) %!error ncfpdf (2, 2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/ncfrnd.m000066400000000000000000000170431524624707500244700ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} ncfrnd (@var{df1}, @var{df2}, @var{lambda}) ## @deftypefnx {statistics} {@var{r} =} ncfrnd (@var{df1}, @var{df2}, @var{lambda}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} ncfrnd (@var{df1}, @var{df2}, @var{lambda}, [@var{sz}]) ## ## Random arrays from the noncentral @math{F}-distribution. ## ## @code{@var{x} = ncfrnd (@var{p}, @var{df1}, @var{df2}, @var{lambda})} returns ## an array of random numbers chosen from the noncentral @math{F}-distribution ## with ## @var{df1} and @var{df2} degrees of freedom and noncentrality parameter ## @var{lambda}. The size of @var{r} is the common size of @var{df1}, ## @var{df2}, and @var{lambda}. A scalar input functions as a constant matrix ## of the same size as the other input. ## ## @code{ncfrnd} generates values using the definition of a noncentral @math{F} ## random variable, as the ratio of a noncentral chi-squared distribution and a ## (central) chi-squared distribution. ## ## When called with a single size argument, @code{ncfrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the noncentral @math{F}-distribution can be found ## at @url{https://en.wikipedia.org/wiki/Noncentral_F-distribution} ## ## @seealso{ncfcdf, ncfinv, ncfpdf, ncfstat, frnd, ncx2rnd, chi2rnd} ## @end deftypefn function r = ncfrnd (df1, df2, lambda, varargin) ## Check for valid number of input arguments if (nargin < 3) error ("ncfrnd: function called with too few input arguments."); endif ## Check for common size of DF1, DF2, and LAMBDA if (! isscalar (df1) || ! isscalar (df2) || ! isscalar (lambda)) [retval, df1, df2, lambda] = common_size (df1, df2, lambda); if (retval > 0) error ("ncfrnd: DF1, DF2, and LAMBDA must be of common size or scalars."); endif endif ## Check for DF1, DF2, and LAMBDA being reals if (iscomplex (df1) || iscomplex (df2) || iscomplex (lambda)) error ("ncfrnd: DF1, DF2, and LAMBDA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 3) sz = size (df1); elseif (nargin == 4) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("ncfrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 4) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("ncfrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (df1) && ! isequal (size (df1), size (ones (sz)))) error ("ncfrnd: DF1, DF2, and LAMBDA must be scalars or of size SZ."); endif ## Check for class type if (isa (df1, 'single') || isa (df2, 'single') || isa (lambda, 'single')); cls = 'single'; else cls = 'double'; endif ## Return NaNs for out of range values of DF1, DF2, and LAMBDA df1(df1 <= 0) = NaN; df2(df2 <= 0) = NaN; lambda(lambda <= 0) = NaN; ## Generate random sample from noncentral F distribution r = (ncx2rnd (df1, lambda, sz) ./ df1) ./ ... (2 .* randg (df2 ./ 2, sz) ./ df2); ## Cast to appropriate class r = cast (r, cls); endfunction ## Test output %!assert_equal (size (ncfrnd (1, 1, 1)), [1, 1]) %!assert_equal (size (ncfrnd (1, ones (2, 1), 1)), [2, 1]) %!assert_equal (size (ncfrnd (1, ones (2, 2), 1)), [2, 2]) %!assert_equal (size (ncfrnd (ones (2, 1), 1, 1)), [2, 1]) %!assert_equal (size (ncfrnd (ones (2, 2), 1, 1)), [2, 2]) %!assert_equal (size (ncfrnd (1, 1, 1, 3)), [3, 3]) %!assert_equal (size (ncfrnd (1, 1, 1, [4, 1])), [4, 1]) %!assert_equal (size (ncfrnd (1, 1, 1, 4, 1)), [4, 1]) %!assert_equal (size (ncfrnd (1, 1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (ncfrnd (1, 1, 1, 0, 1)), [0, 1]) %!assert_equal (size (ncfrnd (1, 1, 1, 1, 0)), [1, 0]) %!assert_equal (size (ncfrnd (1, 1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (ncfrnd (1, 1, 1, [])), [0, 0]) %!assert_equal (size (ncfrnd (1, 1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (ncfrnd (1, 2, 3, -1)), [0, 0]) %!assert_equal (size (ncfrnd (1, 2, 3, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (ncfrnd (1, 2, 3, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (ncfrnd (1, 1, 1)), "double") %!assert_equal (class (ncfrnd (1, single (1), 1)), "single") %!assert_equal (class (ncfrnd (1, 1, single (1))), "single") %!assert_equal (class (ncfrnd (1, single ([1, 1]), 1)), "single") %!assert_equal (class (ncfrnd (1, 1, single ([1, 1]))), "single") %!assert_equal (class (ncfrnd (single (1), 1, 1)), "single") %!assert_equal (class (ncfrnd (single ([1, 1]), 1, 1)), "single") ## Test input validation %!error ncfrnd () %!error ncfrnd (1) %!error ncfrnd (1, 2) %!error ... %! ncfrnd (ones (3), ones (2), ones (2)) %!error ... %! ncfrnd (ones (2), ones (3), ones (2)) %!error ... %! ncfrnd (ones (2), ones (2), ones (3)) %!error ncfrnd (i, 2, 3) %!error ncfrnd (1, i, 3) %!error ncfrnd (1, 2, i) %!error ... %! ncfrnd (1, 2, 3, 1.2) %!error ... %! ncfrnd (1, 2, 3, ones (2)) %!error ... %! ncfrnd (1, 2, 3, [2 0 2.5]) %!error ... %! ncfrnd (1, 2, 3, 2, 1.5, 5) %!error ... %! ncfrnd (2, ones (2), 2, 3) %!error ... %! ncfrnd (2, ones (2), 2, [3, 2]) %!error ... %! ncfrnd (2, ones (2), 2, 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/nctcdf.m000066400000000000000000000313571524624707500244630ustar00rootroot00000000000000## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} nctcdf (@var{x}, @var{df}, @var{mu}) ## @deftypefnx {statistics} {@var{p} =} nctcdf (@var{x}, @var{df}, @var{mu}, @qcode{'upper'}) ## ## Noncentral @math{t}-cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the noncentral @math{t}-distribution with @var{df} degrees of ## freedom and noncentrality parameter @var{mu}. The size of @var{p} is the ## common size of @var{x}, @var{df}, and @var{mu}. A scalar input functions ## as a constant matrix of the same size as the other inputs. ## ## @code{@var{p} = nctcdf (@var{x}, @var{df}, @var{mu}, "upper")} computes ## the upper tail probability of the noncentral @math{t}-distribution with ## parameters @var{df} and @var{mu}, at the values in @var{x}. ## ## Further information about the noncentral @math{t}-distribution can be found ## at @url{https://en.wikipedia.org/wiki/Noncentral_t-distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{nctinv, nctpdf, nctrnd, nctstat, tcdf} ## @end deftypefn function p = nctcdf (x, df, mu, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("nctcdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 3) if (! strcmpi (uflag, 'upper')) error ("nctcdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, DF, and MU [err, x, df, mu] = common_size (x, df, mu); if (err > 0) error ("nctcdf: X, DF, and MU must be of common size or scalars."); endif ## Check for X, DF, and MU being double or single if (! (isfloat (x) && isfloat (df) && isfloat (mu))) error ("nctcdf: X, DF, and MU must be double or single."); endif ## Check for X, DF, and MU being reals if (iscomplex (x) || iscomplex (df) || iscomplex (mu)) error ("nctcdf: X, DF, and MU must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (df, 'single') || isa (mu, 'single')) p = zeros (size (x), 'single'); c_eps = eps ('single'); else p = zeros (size (x)); c_eps = eps; endif ## Find NaNs in input arguments (if any) and propagate them to p is_nan = isnan (x) | isnan (df) | isnan (mu); p(is_nan) = NaN; ## Find special cases for mu==0 and x<0; and x = Inf. case_Dinf = (df <= 0 | isinf (mu)) & ! is_nan; case_Dzero = mu == 0 & ! case_Dinf & ! is_nan; case_Xzero = x < 0 & ! case_Dzero & ! case_Dinf & ! is_nan; case_Xinf = x == Inf & ! case_Dzero & ! case_Dinf & ! is_nan; case_DFbig = df > 2e6 & ! case_Dzero & ! case_Dinf & ! case_Xinf & ! is_nan; flag_Dinf = any (case_Dinf(:)); flag_Dzero = any (case_Dzero(:)); flag_Xzero = any (case_Xzero(:)); flag_Xinf = any (case_Xinf(:)); flag_DFbig = any (case_DFbig(:)); ## Handle special cases if (flag_Dinf || flag_Dzero || flag_Xzero || flag_Xinf || flag_DFbig) if (flag_Dinf) p(case_Dinf) = NaN; endif if (flag_Dzero) if (uflag) p(case_Dzero) = tcdf (x(case_Dzero), df(case_Dzero), 'upper'); else p(case_Dzero) = tcdf (x(case_Dzero), df(case_Dzero)); endif endif if (flag_Xinf) if (uflag) p(case_Xinf) = 0; else p(case_Xinf) = 1; endif endif if (flag_DFbig) s = 1 - 1 ./ (4 * df); d = sqrt (1 + x .^ 2 ./ (2 * df)); if (uflag) p(case_DFbig) = normcdf (x(case_DFbig) .* s(case_DFbig), ... mu(case_DFbig), d(case_DFbig), 'upper'); else p(case_DFbig) = normcdf (x(case_DFbig) .* s(case_DFbig), ... mu(case_DFbig), d(case_DFbig)); endif endif fp = ! (case_Dinf | case_Dzero | case_Xzero | case_Xinf | case_DFbig); if (any (fp(:))) if (uflag) p(fp) = nctcdf (x(fp), df(fp), mu(fp), 'upper'); else p(fp) = nctcdf (x(fp), df(fp), mu(fp)); endif endif if (flag_Xzero) if (uflag) p(case_Xzero) = nctcdf (-x(case_Xzero), df(case_Xzero), ... -mu(case_Xzero)); else p(case_Xzero) = nctcdf (-x(case_Xzero), df(case_Xzero), ... -mu(case_Xzero), 'upper'); endif endif return endif ## Compute value for betainc function. x_square = x .^ 2; denom = df + x_square; P = x_square ./ denom; Q = df ./ denom; ## Initialize infinite sum. d_square = mu .^ 2; ## Compute probability P[TD<0] (first term) if (uflag) x_zero = x == 0 & ! is_nan; if (any (x_zero(:))) fx = normcdf (- mu, 0, 1, 'upper'); p(x_zero)= fx(x_zero); endif else p(! is_nan) = normcdf (- mu(! is_nan), 0, 1); endif ## Compute probability P[0 (abs (subtotal(TD)) + c_eps) * c_eps); if (! any (TD)) break; endif ## Update for next iteration jj = jj+2; E1(TD) = E1(TD) .* d_square(TD) ./ (jj(TD)); E2(TD) = E2(TD) .* d_square(TD) ./ (jj(TD) + 1); if (uflag) B1(TD) = betainc (P(TD), (jj(TD) + 1) / 2, df(TD) / 2, 'upper'); B2(TD) = betainc (P(TD), (jj(TD) + 2) / 2, df(TD) / 2, 'upper'); else B1(TD) = B1(TD) - R1(TD); B2(TD) = B2(TD) - R2(TD); R1(TD) = R1(TD) .* P(TD) .* (jj(TD)+df(TD)-1) ./ (jj(TD)+1); R2(TD) = R2(TD) .* P(TD) .* (jj(TD)+df(TD) ) ./ (jj(TD)+2); endif endwhile ## Go back to the peak and start looping downward as far as necessary. E1 = E10; E2 = E20; B1 = B10; B2 = B20; R1 = R10; R2 = R20; jj = j0; TD = (jj > 0); while (any (TD)) JJ = jj(TD); E1(TD) = E1(TD) .* (JJ ) ./ d_square(TD); E2(TD) = E2(TD) .* (JJ+1) ./ d_square(TD); R1(TD) = R1(TD) .* (JJ+1) ./ ((JJ+df(TD)-1) .* P(TD)); R2(TD) = R2(TD) .* (JJ+2) ./ ((JJ+df(TD)) .* P(TD)); if (uflag) B1(TD) = betainc (P(TD), (JJ - 1) / 2, df(TD) / 2, 'upper'); B2(TD) = betainc (P(TD), JJ / 2, df(TD) / 2, 'upper'); else B1(TD) = B1(TD) + R1(TD); B2(TD) = B2(TD) + R2(TD); endif twoterms = E1(TD) .* B1(TD) + E2(TD) .* B2(TD); subtotal(TD) = subtotal(TD) + twoterms; jj = jj - 2; TD(TD) = (abs (twoterms) > (abs (subtotal(TD)) + c_eps) * c_eps) & ... (jj(TD) > 0); endwhile p(x_notzero) = min (1, max (0, p(x_notzero) + subtotal / 2)); endif endfunction %!demo %! ## Plot various CDFs from the noncentral Τ distribution %! x = -5:0.01:5; %! p1 = nctcdf (x, 1, 0); %! p2 = nctcdf (x, 4, 0); %! p3 = nctcdf (x, 1, 2); %! p4 = nctcdf (x, 4, 2); %! plot (x, p1, '-r', x, p2, '-g', x, p3, '-k', x, p4, '-m') %! grid on %! xlim ([-5, 5]) %! legend ({'df = 1, μ = 0', 'df = 4, μ = 0', ... %! 'df = 1, μ = 2', 'df = 4, μ = 2'}, 'location', 'southeast') %! title ('Noncentral Τ CDF') %! xlabel ('values in x') %! ylabel ('probability') %!demo %! ## Compare the noncentral T CDF with MU = 1 to the T CDF %! ## with the same number of degrees of freedom (10). %! %! x = -5:0.1:5; %! p1 = nctcdf (x, 10, 1); %! p2 = tcdf (x, 10); %! plot (x, p1, '-', x, p2, '-') %! grid on %! xlim ([-5, 5]) %! legend ({'Noncentral T(10,1)', 'T(10)'}, 'location', 'southeast') %! title ('Noncentral T vs T CDFs') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!test %! x = -2:0.1:2; %! p = nctcdf (x, 10, 1); %! assert_equal (p(1), 0.003302485766631558, 1e-14); %! assert_equal (p(2), 0.004084668193532631, 1e-14); %! assert_equal (p(3), 0.005052800319478737, 1e-14); %! assert_equal (p(41), 0.8076115625303751, 1e-14); %!test %! p = nctcdf (12, 10, 3); %! assert_equal (p, 0.9997719343243797, 1e-14); %!test %! p = nctcdf (2, 3, 2); %! assert_equal (p, 0.4430757822176028, 1e-14); %!test %! p = nctcdf (2, 3, 2, 'upper'); %! assert_equal (p, 0.5569242177823971, 1e-14); %!test %! p = nctcdf ([3, 6], 3, 2, 'upper'); %! assert_equal (p, [0.3199728259444777, 0.07064855592441913], 1e-14); ## Test input validation %!error nctcdf () %!error nctcdf (1) %!error nctcdf (1, 2) %!error nctcdf (1, 2, 3, 'tail') %!error nctcdf (1, 2, 3, 4) %!error ... %! nctcdf (ones (3), ones (2), ones (2)) %!error ... %! nctcdf (ones (2), ones (3), ones (2)) %!error ... %! nctcdf (ones (2), ones (2), ones (3)) %!error nctcdf (int32 (2), 2, 2) %!error nctcdf (true, 2, 2) %!error nctcdf ('a', 2, 2) %!error nctcdf (i, 2, 2) %!error nctcdf (2, i, 2) %!error nctcdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/nctinv.m000066400000000000000000000166151524624707500245230ustar00rootroot00000000000000## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} ncx2inv (@var{p}, @var{df}, @var{mu}) ## ## Inverse of the non-central @math{t}-cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the noncentral @math{t}-distribution with @var{df} degrees of freedom and ## noncentrality parameter @var{mu}. The size of @var{x} is the common size ## of @var{p}, @var{df}, and @var{mu}. A scalar input functions as a ## constant matrix of the same size as the other inputs. ## ## @code{nctinv} uses Newton's method to converge to the solution. ## ## Further information about the noncentral @math{t}-distribution can be found ## at @url{https://en.wikipedia.org/wiki/Noncentral_t-distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{nctcdf, nctpdf, nctrnd, nctstat, tinv} ## @end deftypefn function x = nctinv (p, df, mu) ## Check for valid number of input arguments if (nargin < 3) error ("nctinv: function called with too few input arguments."); endif ## Check for common size of P, DF, and MU [err, p, df, mu] = common_size (p, df, mu); if (err > 0) error ("nctinv: P, DF, and MU must be of common size or scalars."); endif ## Check for P, DF, and MU being double or single if (! (isfloat (p) && isfloat (df) && isfloat (mu))) error ("nctinv: P, DF, and MU must be double or single."); endif ## Check for P, DF, and MU being reals if (iscomplex (p) || iscomplex (df) || iscomplex (mu)) error ("nctinv: P, DF, and MU must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (df, 'single') || isa (mu, 'single')) x = NaN (size (p), 'single'); crit = sqrt (eps ('single')); else x = NaN (size (p), 'double'); crit = sqrt (eps ('double')); endif ## For mu == 0, call chi2inv m0 = mu == 0; if (any (m0(:))) x(m0) = tinv (p(m0), df(m0)); ## If mu == 0 for all entries, then return if (all (m0(:))) return; endif endif ## For all valid entries valid = df > 0 & ! isnan (mu) & ! isinf (mu); ## Force x = -Inf for p == 0 and x = Inf for p == 1 x(p == 0 & valid) = -Inf; x(p == 1 & valid) = Inf; ## Find valid samples within the range of 0 < p < 1 k = find (p > 0 & p < 1 & valid); p_k = p(k); df_k = df(k); mu_k = mu(k); ## Initialize counter count_limit = 100; count = 0; ## Supply a starting guess for the iteration with norminv x_k = norminv (p_k, mu_k, 1); h_k = ones (size (x_k), class (x_k)); ## Start iteration with a break out loop F = nctcdf (x_k, df_k, mu_k); while (any (abs (h_k) > crit * abs (x_k)) && ... max (abs (h_k)) > crit && count < count_limit) count = count + 1; h_k = (F - p_k) ./ nctpdf (x_k, df_k, mu_k); ## Prevent Infs - NaNs infnan = isinf (h_k) | isnan (h_k); if (any (infnan(:))) h_k(infnan) = x_k(infnan) / 10; endif ## Prepare for next step xnew = max (-5 * abs (x_k), min (5 * abs (x_k), x_k - h_k)); ## Check that next step improves, otherwise abort Fnew = nctcdf (xnew, df_k, mu_k); while (true) worse = (abs (Fnew - p_k) > abs (F - p_k) * (1 + crit)) & ... (abs (x_k - xnew) > crit * abs (x_k)); if (! any (worse)) break; endif xnew(worse) = 0.5 * (xnew(worse) + x_k(worse)); Fnew(worse) = nctcdf (xnew(worse), df_k(worse), mu_k(worse)); endwhile x_k = xnew; F = Fnew; endwhile ## Return the converged value(s). x(k) = x_k; if (count == count_limit) warning ("nctinv: did not converge."); endif endfunction %!demo %! ## Plot various iCDFs from the noncentral T distribution %! p = 0.001:0.001:0.999; %! x1 = nctinv (p, 1, 0); %! x2 = nctinv (p, 4, 0); %! x3 = nctinv (p, 1, 2); %! x4 = nctinv (p, 4, 2); %! plot (p, x1, '-r', p, x2, '-g', p, x3, '-k', p, x4, '-m') %! grid on %! ylim ([-5, 5]) %! legend ({'df = 1, μ = 0', 'df = 4, μ = 0', ... %! 'df = 1, μ = 2', 'df = 4, μ = 2'}, 'location', 'northwest') %! title ('Noncentral T iCDF') %! xlabel ('probability') %! ylabel ('values in x') %!demo %! ## Compare the noncentral T iCDF with MU = 1 to the T iCDF %! ## with the same number of degrees of freedom (10). %! %! p = 0.001:0.001:0.999; %! x1 = nctinv (p, 10, 1); %! x2 = tinv (p, 10); %! plot (p, x1, '-', p, x2, '-'); %! grid on %! ylim ([-5, 5]) %! legend ({'Noncentral T(10,1)', 'T(10)'}, 'location', 'northwest') %! title ('Noncentral T vs T quantile functions') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!test %! x = [-Inf,-0.3347,0.1756,0.5209,0.8279,1.1424,1.5021,1.9633,2.6571,4.0845,Inf]; %! assert_equal (nctinv ([0:0.1:1], 2, 1), x, 1e-4); %!test %! x = [-Inf,1.5756,2.0827,2.5343,3.0043,3.5406,4.2050,5.1128,6.5510,9.6442,Inf]; %! assert_equal (nctinv ([0:0.1:1], 2, 3), x, 1e-4); %!test %! x = [-Inf,2.2167,2.9567,3.7276,4.6464,5.8455,7.5619,10.3327,15.7569,31.8159,Inf]; %! assert_equal (nctinv ([0:0.1:1], 1, 4), x, 1e-4); %!test %! x = [1.7791 1.9368 2.0239 2.0801 2.1195 2.1489]; %! assert_equal (nctinv (0.05, [1, 2, 3, 4, 5, 6], 4), x, 1e-4); %!test %! x = [-0.7755, 0.3670, 1.2554, 2.0239, 2.7348, 3.4154]; %! assert_equal (nctinv (0.05, 3, [1, 2, 3, 4, 5, 6]), x, 1e-4); %!test %! x = [-0.7183, 0.3624, 1.2878, 2.1195, -3.5413, 3.6430]; %! assert_equal (nctinv (0.05, 5, [1, 2, 3, 4, -1, 6]), x, 1e-4); %!test %! assert_equal (nctinv (0.996, 5, 8), 30.02610554063658, 2e-11); ## Test input validation %!error nctinv () %!error nctinv (1) %!error nctinv (1, 2) %!error ... %! nctinv (ones (3), ones (2), ones (2)) %!error ... %! nctinv (ones (2), ones (3), ones (2)) %!error ... %! nctinv (ones (2), ones (2), ones (3)) %!error nctinv (int32 (2), 2, 2) %!error nctinv (true, 2, 2) %!error nctinv ('a', 2, 2) %!error nctinv (i, 2, 2) %!error nctinv (2, i, 2) %!error nctinv (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/nctpdf.m000066400000000000000000000156551524624707500245030ustar00rootroot00000000000000## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} nctpdf (@var{x}, @var{df}, @var{mu}) ## ## Noncentral @math{t}-probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the noncentral @math{t}-distribution with @var{df} degrees of freedom and ## noncentrality parameter @var{mu}. The size of @var{y} is the common size ## of @var{x}, @var{df}, and @var{mu}. A scalar input functions as a ## constant matrix of the same size as the other inputs. ## ## Further information about the noncentral @math{t}-distribution can be found ## at @url{https://en.wikipedia.org/wiki/Noncentral_t-distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{nctcdf, nctinv, nctrnd, nctstat, tpdf} ## @end deftypefn function y = nctpdf (x, df, mu) ## Check for valid number of input arguments if (nargin < 3) error ("nctpdf: function called with too few input arguments."); endif ## Check for common size of X, DF, and MU [err, x, df, mu] = common_size (x, df, mu); if (err > 0) error ("nctpdf: X, DF, and MU must be of common size or scalars."); endif ## Check for X, DF, and MU being double or single if (! (isfloat (x) && isfloat (df) && isfloat (mu))) error ("nctpdf: X, DF, and MU must be double or single."); endif ## Check for X, DF, and MU being reals if (iscomplex (x) || iscomplex (df) || iscomplex (mu)) error ("nctpdf: X, DF, and MU must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (df, 'single') || isa (mu, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Find NaNs in input arguments (if any) and propagate them to p is_nan = isnan (x) | isnan (df) | isnan (mu); y(is_nan) = NaN; ## Force invalid parameter cases to NaN invalid = df <= 0 | ! isfinite (mu); y(invalid) = NaN; ## Use normal approximation for df > 1e6 bigDF = df > 1e6 & ! is_nan & ! invalid; if (any (bigDF(:))) s = 1 - 1 ./ (4 * df); d = sqrt (1 + x .^ 2 ./ (2 * df)); y(bigDF) = normpdf (x(bigDF) .* s(bigDF), mu(bigDF), d(bigDF)); endif ## For negative x use left tail cdf x_neg = find ((x < 0) & isfinite (x) & df <= 1e6 & ! is_nan & ! invalid); if (any (x_neg)) y(x_neg) = (df(x_neg) ./ x(x_neg)) .* ... (nctcdf (x(x_neg) .* sqrt ((df(x_neg) + 2) ./ df(x_neg)), ... df(x_neg) + 2, mu(x_neg)) - ... nctcdf (x(x_neg), df(x_neg), mu(x_neg))); endif ## For positive x reflect about zero and use left tail cdf x_pos = find ((x > 0) & isfinite (x) & df <= 1e6 & ! is_nan & ! invalid); if (any (x_pos)) y(x_pos) = (-df(x_pos) ./ x(x_pos)) .* ... (nctcdf (-x(x_pos) .* sqrt ((df(x_pos) + 2) ./ df(x_pos)), ... df(x_pos) + 2, -mu(x_pos)) - ... nctcdf (-x(x_pos), df(x_pos), -mu(x_pos))); endif ## For x == 0 use power series xzero = find ((x == 0) & df <= 1e6 & ! is_nan & ! invalid); if (any (xzero)) y(xzero) = exp (-0.5 * mu(xzero) .^ 2 - 0.5 * log (pi * df(xzero)) + ... gammaln (0.5 * (df(xzero) + 1)) - gammaln (0.5 * df(xzero))); endif endfunction %!demo %! ## Plot various PDFs from the noncentral T distribution %! x = -5:0.01:10; %! y1 = nctpdf (x, 1, 0); %! y2 = nctpdf (x, 4, 0); %! y3 = nctpdf (x, 1, 2); %! y4 = nctpdf (x, 4, 2); %! plot (x, y1, '-r', x, y2, '-g', x, y3, '-k', x, y4, '-m') %! grid on %! xlim ([-5, 10]) %! ylim ([0, 0.4]) %! legend ({'df = 1, μ = 0', 'df = 4, μ = 0', ... %! 'df = 1, μ = 2', 'df = 4, μ = 2'}, 'location', 'northeast') %! title ('Noncentral T PDF') %! xlabel ('values in x') %! ylabel ('density') %!demo %! ## Compare the noncentral T PDF with MU = 1 to the T PDF %! ## with the same number of degrees of freedom (10). %! %! x = -5:0.1:5; %! y1 = nctpdf (x, 10, 1); %! y2 = tpdf (x, 10); %! plot (x, y1, '-', x, y2, '-'); %! grid on %! xlim ([-5, 5]) %! ylim ([0, 0.4]) %! legend ({'Noncentral χ^2(4,2)', 'χ^2(4)'}, 'location', 'northwest') %! title ('Noncentral T vs T PDFs') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x1, df, mu %! x1 = [-Inf, 2, NaN, 4, Inf]; %! df = [2, 0, -1, 1, 4]; %! mu = [1, NaN, 3, -1, 2]; %!assert_equal (nctpdf (x1, df, mu), [0, NaN, NaN, 0.00401787561306999, 0], 1e-14); %!assert_equal (nctpdf (x1, df, 1), [0, NaN, NaN, 0.0482312135423008, 0], 1e-14); %!assert_equal (nctpdf (x1, df, 3), [0, NaN, NaN, 0.1048493126401585, 0], 1e-14); %!assert_equal (nctpdf (x1, df, 2), [0, NaN, NaN, 0.08137377919890307, 0], 1e-14); %!assert_equal (nctpdf (x1, 3, mu), [0, NaN, NaN, 0.001185305171654381, 0], 1e-14); %!assert_equal (nctpdf (2, df, mu), [0.1791097459405861, NaN, NaN, ... %! 0.0146500727180389, 0.3082302682110299], 1e-14); %!assert_equal (nctpdf (4, df, mu), [0.04467929612254971, NaN, NaN, ... %! 0.00401787561306999, 0.0972086534042828], 1e-14); ## Test input validation %!error nctpdf () %!error nctpdf (1) %!error nctpdf (1, 2) %!error ... %! nctpdf (ones (3), ones (2), ones (2)) %!error ... %! nctpdf (ones (2), ones (3), ones (2)) %!error ... %! nctpdf (ones (2), ones (2), ones (3)) %!error nctpdf (int32 (2), 2, 2) %!error nctpdf (true, 2, 2) %!error nctpdf ('a', 2, 2) %!error nctpdf (i, 2, 2) %!error nctpdf (2, i, 2) %!error nctpdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/nctrnd.m000066400000000000000000000153221524624707500245040ustar00rootroot00000000000000## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} nctrnd (@var{df}, @var{mu}) ## @deftypefnx {statistics} {@var{r} =} nctrnd (@var{df}, @var{mu}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} nctrnd (@var{df}, @var{mu}, [@var{sz}]) ## ## Random arrays from the noncentral @math{t}-distribution. ## ## @code{@var{x} = nctrnd (@var{p}, @var{df}, @var{mu})} returns an array of ## random numbers chosen from the noncentral @math{t}-distribution with @var{df} ## degrees of freedom and noncentrality parameter @var{mu}. The size of ## @var{r} is the common size of @var{df} and @var{mu}. A scalar input ## functions as a constant matrix of the same size as the other input. ## ## @code{nctrnd} generates values using the definition of a noncentral @math{t} ## random variable, as the ratio of a normal distribution with non-zero mean and ## the sqrt of a chi-squared distribution. ## ## When called with a single size argument, @code{nctrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the noncentral @math{t}-distribution can be found ## at @url{https://en.wikipedia.org/wiki/Noncentral_t-distribution} ## ## @seealso{nctcdf, nctinv, nctpdf, nctstat, trnd, normrnd, chi2rnd} ## @end deftypefn function r = nctrnd (df, mu, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("nctrnd: function called with too few input arguments."); endif ## Check for common size of DF and MU if (! isscalar (df) || ! isscalar (mu)) [retval, df, mu] = common_size (df, mu); if (retval > 0) error ("nctrnd: DF and MU must be of common size or scalars."); endif endif ## Check for DF and MU being reals if (iscomplex (df) || iscomplex (mu)) error ("nctrnd: DF and MU must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (df); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("nctrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("nctrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (df) && ! isequal (size (df), size (ones (sz)))) error ("nctrnd: DF and MU must be scalars or of size SZ."); endif ## Check for class type if (isa (df, 'single') || isa (mu, 'single')); cls = 'single'; else cls = 'double'; endif ## Return NaNs for out of range values of DF df(df <= 0) = NaN; ## Prevent Inf/Inf==NaN for the standardized chi-square in the denom. df(isinf (df)) = realmax; ## Generate random sample from noncentral F distribution r = (randn (sz) + mu) ./ sqrt (2 .* randg (df ./ 2, sz) ./ df); ## Cast to appropriate class r = cast (r, cls); endfunction ## Test output %!assert_equal (size (nctrnd (1, 1)), [1, 1]) %!assert_equal (size (nctrnd (1, ones (2, 1))), [2, 1]) %!assert_equal (size (nctrnd (1, ones (2, 2))), [2, 2]) %!assert_equal (size (nctrnd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (nctrnd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (nctrnd (1, 1, 3)), [3, 3]) %!assert_equal (size (nctrnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (nctrnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (nctrnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (nctrnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (nctrnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (nctrnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (nctrnd (1, 1, [])), [0, 0]) %!assert_equal (size (nctrnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (nctrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (nctrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (nctrnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (nctrnd (1, 1)), "double") %!assert_equal (class (nctrnd (1, single (1))), "single") %!assert_equal (class (nctrnd (1, single ([1, 1]))), "single") %!assert_equal (class (nctrnd (single (1), 1)), "single") %!assert_equal (class (nctrnd (single ([1, 1]), 1)), "single") ## Test input validation %!error nctrnd () %!error nctrnd (1) %!error ... %! nctrnd (ones (3), ones (2)) %!error ... %! nctrnd (ones (2), ones (3)) %!error nctrnd (i, 2) %!error nctrnd (1, i) %!error ... %! nctrnd (1, 2, 1.2) %!error ... %! nctrnd (1, 2, ones (2)) %!error ... %! nctrnd (1, 2, [2 0 2.5]) %!error ... %! nctrnd (1, 2, 2, 1.5, 5) %!error ... %! nctrnd (2, ones (2), 3) %!error ... %! nctrnd (2, ones (2), [3, 2]) %!error ... %! nctrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/ncx2cdf.m000066400000000000000000000247371524624707500245550ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} ncx2cdf (@var{x}, @var{df}, @var{lambda}) ## @deftypefnx {statistics} {@var{p} =} ncx2cdf (@var{x}, @var{df}, @var{lambda}, @qcode{'upper'}) ## ## Noncentral chi-squared cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the noncentral chi-squared distribution with @var{df} degrees of ## freedom and noncentrality parameter @var{lambda}. The size of @var{p} is the ## common size of @var{x}, @var{df}, and @var{lambda}. A scalar input functions ## as a constant matrix of the same size as the other inputs. ## ## @code{@var{p} = ncx2cdf (@var{x}, @var{df}, @var{lambda}, "upper")} computes ## the upper tail probability of the noncentral chi-squared distribution with ## parameters @var{df} and @var{lambda}, at the values in @var{x}. ## ## Further information about the noncentral chi-squared distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Noncentral_chi-squared_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{ncx2inv, ncx2pdf, ncx2rnd, ncx2stat, chi2cdf} ## @end deftypefn function p = ncx2cdf (x, df, lambda, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("ncx2cdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 3) if (! strcmpi (uflag, 'upper')) error ("ncx2cdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, DF, and LAMBDA [err, x, df, lambda] = common_size (x, df, lambda); if (err > 0) error ("ncx2cdf: X, DF, and LAMBDA must be of common size or scalars."); endif ## Check for X, DF, and LAMBDA being double or single if (! (isfloat (x) && isfloat (df) && isfloat (lambda))) error ("ncx2cdf: X, DF, and LAMBDA must be double or single."); endif ## Check for X, DF, and LAMBDA being reals if (iscomplex (x) || iscomplex (df) || iscomplex (lambda)) error ("ncx2cdf: X, DF, and LAMBDA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (df, 'single') || isa (lambda, 'single')) p = zeros (size (x), 'single'); c_eps = eps ('single'); c_min = realmin ('single'); else p = zeros (size (x)); c_eps = eps; c_min = realmin; endif ## Find NaNs in input arguments (if any) and propagate them to p is_nan = isnan (x) | isnan (df) | isnan (lambda); p(is_nan) = NaN; if (uflag) p(x == Inf & ! is_nan) = 0; p(x <= 0 & ! is_nan) = 1; else p(x == Inf & ! is_nan) = 1; endif ## Make P = NaN for negative values of noncentrality parameter and DF p(lambda < 0) = NaN; p(df < 0) = NaN; ## For DF == 0 at x == 0 k = df == 0 & x == 0 & lambda >= 0 & ! is_nan; if (uflag) p(k) = -expm1 (-lambda(k) / 2); else p(k) = exp (-lambda(k) / 2); endif ## Central chi2cdf k = df >= 0 & x > 0 & lambda == 0 & isfinite (x) & ! is_nan; if (uflag) p(k) = chi2cdf (x(k), df(k), 'upper'); else p(k) = chi2cdf (x(k), df(k)); endif ## Keep only valid samples td = find (df >= 0 & x > 0 & lambda > 0 & isfinite (x) & ! is_nan); lambda = lambda(td) / 2; df = df(td) / 2; x = x(td) / 2; ## Compute Chernoff bounds e0 = log (c_min); e1 = log (c_eps/4); t = 1 - (df + sqrt (df .^ 2 + 4 * lambda .* x)) ./ (2 * x); q = lambda .* t ./ (1 - t) - df .* log (1 - t) - t .* x; peq0 = x < lambda + df & q < e0; peq1 = x > lambda + df & q < e1; if (uflag) p(td(peq0)) = 1; else p(td(peq1)) = 1; endif td(peq0 | peq1) = []; x(peq0 | peq1) = []; df(peq0 | peq1) = []; lambda(peq0 | peq1) = []; ## Find index K of the maximal term in the summation series. ## K1 and K2 are lower and upper bounds for K, respectively. ## Indexing of terms in the summation series starts at 0. K1 = ceil ((sqrt ((df + x) .^ 2 + 4 * x .* lambda) - (df + x)) / 2); K = zeros (size (x)); k1above1 = find (K1 > 1); K2 = floor (lambda(k1above1) .* gammaincratio (x(k1above1), K1(k1above1))); fixK2 = isnan (K2) | isinf (K2); K2(fixK2) = K1(k1above1(fixK2)); K(k1above1) = K2; ## Find Poisson and Poisson*chi2cdf parts for the maximal terms in the ## summation series. if (uflag) k0 = (K==0 & df==0); K(k0) = 1; endif pois = poisspdf (K, lambda); if (uflag) full = pois .* gammainc (x, df + K, 'upper'); else full = pois .* gammainc (x, df + K); endif ## Sum the series. First go downward from K and then go upward. ## The term for K is added afterwards - it is not included in either sum. sumK = zeros (size (x)); ## Downward. poisspdf(k-1,lambda)/poisspdf(k,lambda) = k/lambda poisterm = pois; fullterm = full; keep = K > 0 & fullterm > 0; k = K; while any (keep) poisterm(keep) = poisterm(keep) .* k(keep) ./ lambda(keep); k(keep) = k(keep) - 1; if (uflag) fullterm(keep) = poisterm(keep) .* ... gammainc (x(keep), df(keep) + k(keep), 'upper'); else fullterm(keep) = poisterm(keep) .* ... gammainc (x(keep), df(keep) + k(keep)); endif sumK(keep) = sumK(keep) + fullterm(keep); keep = keep & k > 0 & fullterm > eps (sumK); endwhile ## Upward. poisspdf(k+1,lambda)/poisspdf(k,lambda) = lambda/(k+1) poisterm = pois; fullterm = full; keep = fullterm > 0; k = K; while any (keep) k(keep) = k(keep)+1; poisterm(keep) = poisterm(keep) .* lambda(keep) ./ k(keep); if (uflag) fullterm(keep) = poisterm(keep) .* ... gammainc (x(keep), df(keep) + k(keep), 'upper'); else fullterm(keep) = poisterm(keep) .* ... gammainc (x(keep), df(keep) + k(keep)); endif sumK(keep) = sumK(keep) + fullterm(keep); keep = keep & fullterm > eps (sumK); endwhile ## Get probabilities p(td) = full + sumK; p(p > 1) = 1; endfunction ## Ratio of incomplete gamma function values at S and S-1. function r = gammaincratio (x, s) ## Initialize r = zeros (size (s)); ## Finf small small = s < 2 | s <= x; ## For small S, use the ratio computed directly if (any (small(:))) r(small) = gammainc (x(small), s(small)) ./ ... gammainc (x(small), s(small) - 1); endif ## For large S, estimate numerator and denominator using 'scaledlower' option if (any (! small(:))) idx = find (! small); x = x(idx); s = s(idx); r(idx) = gammainc (x, s, 'scaledlower') ./ ... gammainc (x, s - 1, 'scaledlower') .* x ./ s; endif endfunction %!demo %! ## Plot various CDFs from the noncentral chi-squared distribution %! x = 0:0.1:10; %! p1 = ncx2cdf (x, 2, 1); %! p2 = ncx2cdf (x, 2, 2); %! p3 = ncx2cdf (x, 2, 3); %! p4 = ncx2cdf (x, 4, 1); %! p5 = ncx2cdf (x, 4, 2); %! p6 = ncx2cdf (x, 4, 3); %! plot (x, p1, '-r', x, p2, '-g', x, p3, '-k', ... %! x, p4, '-m', x, p5, '-c', x, p6, '-y') %! grid on %! xlim ([0, 10]) %! legend ({'df = 2, λ = 1', 'df = 2, λ = 2', ... %! 'df = 2, λ = 3', 'df = 4, λ = 1', ... %! 'df = 4, λ = 2', 'df = 4, λ = 3'}, 'location', 'southeast') %! title ('Noncentral chi-squared CDF') %! xlabel ('values in x') %! ylabel ('probability') %!demo %! ## Compare the noncentral chi-squared CDF with LAMBDA = 2 to the %! ## chi-squared CDF with the same number of degrees of freedom (4). %! %! x = 0:0.1:10; %! p1 = ncx2cdf (x, 4, 2); %! p2 = chi2cdf (x, 4); %! plot (x, p1, '-', x, p2, '-') %! grid on %! xlim ([0, 10]) %! legend ({'Noncentral χ^2(4,2)', 'χ^2(4)'}, 'location', 'northwest') %! title ('Noncentral chi-squared vs chi-squared CDFs') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!test %! x = -2:0.1:2; %! p = ncx2cdf (x, 10, 1); %! assert_equal (p([1:21]), zeros (1, 21), 3e-84); %! assert_equal (p(22), 1.521400636466575e-09, 1e-14); %! assert_equal (p(30), 6.665480510026046e-05, 1e-14); %! assert_equal (p(41), 0.002406447308399836, 1e-14); %!test %! p = ncx2cdf (12, 10, 3); %! assert_equal (p, 0.4845555602398649, 1e-14); %!test %! p = ncx2cdf (2, 3, 2); %! assert_equal (p, 0.2207330870741212, 1e-14); %!test %! p = ncx2cdf (2, 3, 2, 'upper'); %! assert_equal (p, 0.7792669129258789, 1e-14); %!test %! p = ncx2cdf ([3, 6], 3, 2, 'upper'); %! assert_equal (p, [0.6423318186400054, 0.3152299878943012], 1e-14); ## Test input validation %!error ncx2cdf () %!error ncx2cdf (1) %!error ncx2cdf (1, 2) %!error ncx2cdf (1, 2, 3, 'tail') %!error ncx2cdf (1, 2, 3, 4) %!error ... %! ncx2cdf (ones (3), ones (2), ones (2)) %!error ... %! ncx2cdf (ones (2), ones (3), ones (2)) %!error ... %! ncx2cdf (ones (2), ones (2), ones (3)) %!error ncx2cdf (int32 (2), 2, 2) %!error ncx2cdf (true, 2, 2) %!error ncx2cdf ('a', 2, 2) %!error ncx2cdf (i, 2, 2) %!error ncx2cdf (2, i, 2) %!error ncx2cdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/ncx2inv.m000066400000000000000000000176341524624707500246130ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} ncx2inv (@var{p}, @var{df}, @var{lambda}) ## ## Inverse of the noncentral chi-squared cumulative distribution function ## (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the noncentral chi-squared distribution with @var{df} degrees of freedom and ## noncentrality parameter @var{mu}. The size of @var{x} is the common size of ## @var{p}, @var{df}, and @var{mu}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## @code{ncx2inv} uses Newton's method to converge to the solution. ## ## Further information about the noncentral chi-squared distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Noncentral_chi-squared_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{ncx2cdf, ncx2pdf, ncx2rnd, ncx2stat, chi2inv} ## @end deftypefn function x = ncx2inv (p, df, lambda) ## Check for valid number of input arguments if (nargin < 3) error ("ncx2inv: function called with too few input arguments."); endif ## Check for common size of P, DF, and LAMBDA [err, p, df, lambda] = common_size (p, df, lambda); if (err > 0) error ("ncx2inv: P, DF, and LAMBDA must be of common size or scalars."); endif ## Check for P, DF, and LAMBDA being double or single if (! (isfloat (p) && isfloat (df) && isfloat (lambda))) error ("ncx2inv: P, DF, and LAMBDA must be double or single."); endif ## Check for P, DF, and LAMBDA being reals if (iscomplex (p) || iscomplex (df) || iscomplex (lambda)) error ("ncx2inv: P, DF, and LAMBDA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (df, 'single') || isa (lambda, 'single')) x = NaN (size (p), 'single'); crit = sqrt (eps ('single')); else x = NaN (size (p), 'double'); crit = sqrt (eps ('double')); endif ## For lambda == 0, call chi2inv d0 = lambda == 0; if (any (d0(:))) x(d0) = chi2inv (p(d0), df(d0)); ## If lambda == 0 for all entries, then return if (all (d0(:))) return; endif endif ## CDF with 0 d.d0. has a step at x=0. ## Check if CDF at x=0 exceeds the requested p. df0 = df==0 & lambda > 0; if (any (df0(:))) p0 = zeros (size (p)); p0(df0) = ncx2cdf (0, df(df0), lambda(df0)); df0 = df0 & p0 >= p; x(df0) = 0; endif valid = ! df0 & df > 0 & lambda > 0; ## Force x = 0 for p == 0 and x = Inf for p == 1 x(p == 0 & valid) = 0; x(p == 1 & valid) = Inf; ## Find valid samples within the range of 0 < p < 1 k = find (p > 0 & p < 1 & valid); pk = p(k); ## Initialize counter count_limit = 100; count = 0; ## Supply a starting guess for the iteration. mn = df(k) + lambda(k); variance = 2 * (df(k) + 2 * lambda(k)); temp = log (variance + mn .^ 2); mu = 2 * log (mn) - 0.5 * temp; sigma = -2 * log (mn) + temp; xk = exp (norminv (pk, mu, sigma)); F = ncx2cdf (xk, df(k), lambda(k)); h = ones (size (xk), class (xk)); ## Start iteration with a break out loop while (count < count_limit) count = count + 1; h = (F - pk) ./ ncx2pdf (xk, df(k), lambda(k)); xnew = max (xk / 50, min (5 * xk, xk - h)); newF = ncx2cdf (xnew, df(k), lambda(k)); while (true) worse = (abs (newF - pk) > abs (F - pk) * (1 + crit)) & ... (abs (xk - xnew) > crit * xk); if (! any (worse)) break; endif xnew(worse) = 0.5 * (xnew(worse) + xk(worse)); newF(worse) = ncx2cdf (xnew(worse), df(k(worse)), lambda(k(worse))); endwhile h = xk - xnew; x(k) = xnew; mask = (abs (h) > crit * abs (xk)); if (! any (mask)) break; endif k = k(mask); xk = xnew(mask); F = newF(mask); pk = pk(mask); endwhile if (count == count_limit) warning ("ncx2inv: did not converge."); endif endfunction %!demo %! ## Plot various iCDFs from the noncentral chi-squared distribution %! p = 0.001:0.001:0.999; %! x1 = ncx2inv (p, 2, 1); %! x2 = ncx2inv (p, 2, 2); %! x3 = ncx2inv (p, 2, 3); %! x4 = ncx2inv (p, 4, 1); %! x5 = ncx2inv (p, 4, 2); %! x6 = ncx2inv (p, 4, 3); %! plot (p, x1, '-r', p, x2, '-g', p, x3, '-k', ... %! p, x4, '-m', p, x5, '-c', p, x6, '-y') %! grid on %! ylim ([0, 10]) %! legend ({'df = 2, λ = 1', 'df = 2, λ = 2', ... %! 'df = 2, λ = 3', 'df = 4, λ = 1', ... %! 'df = 4, λ = 2', 'df = 4, λ = 3'}, 'location', 'northwest') %! title ('Noncentral chi-squared iCDF') %! xlabel ('probability') %! ylabel ('values in x') %!demo %! ## Compare the noncentral chi-squared CDF with LAMBDA = 2 to the %! ## chi-squared CDF with the same number of degrees of freedom (4). %! %! p = 0.001:0.001:0.999; %! x1 = ncx2inv (p, 4, 2); %! x2 = chi2inv (p, 4); %! plot (p, x1, '-', p, x2, '-'); %! grid on %! ylim ([0, 10]) %! legend ({'Noncentral χ^2(4,2)', 'χ^2(4)'}, 'location', 'northwest') %! title ('Noncentral chi-squared vs chi-squared quantile functions') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!test %! x = [0,0.3443,0.7226,1.1440,1.6220,2.1770,2.8436,3.6854,4.8447,6.7701,Inf]; %! assert_equal (ncx2inv ([0:0.1:1], 2, 1), x, 1e-4); %!test %! x = [0,0.8295,1.6001,2.3708,3.1785,4.0598,5.0644,6.2765,7.8763,10.4199,Inf]; %! assert_equal (ncx2inv ([0:0.1:1], 2, 3), x, 1e-4); %!test %! x = [0,0.5417,1.3483,2.1796,3.0516,4.0003,5.0777,6.3726,8.0748,10.7686,Inf]; %! assert_equal (ncx2inv ([0:0.1:1], 1, 4), x, 1e-4); %!test %! x = [0.1808, 0.6456, 1.1842, 1.7650, 2.3760, 3.0105]; %! assert_equal (ncx2inv (0.05, [1, 2, 3, 4, 5, 6], 4), x, 1e-4); %!test %! x = [0.4887, 0.6699, 0.9012, 1.1842, 1.5164, 1.8927]; %! assert_equal (ncx2inv (0.05, 3, [1, 2, 3, 4, 5, 6]), x, 1e-4); %!test %! x = [1.3941, 1.6824, 2.0103, 2.3760, NaN, 3.2087]; %! assert_equal (ncx2inv (0.05, 5, [1, 2, 3, 4, -1, 6]), x, 1e-4); %!test %! assert_equal (ncx2inv (0.996, 5, 8), 35.51298862765576, 3e-13); ## Test input validation %!error ncx2inv () %!error ncx2inv (1) %!error ncx2inv (1, 2) %!error ... %! ncx2inv (ones (3), ones (2), ones (2)) %!error ... %! ncx2inv (ones (2), ones (3), ones (2)) %!error ... %! ncx2inv (ones (2), ones (2), ones (3)) %!error ncx2inv (int32 (2), 2, 2) %!error ncx2inv (true, 2, 2) %!error ncx2inv ('a', 2, 2) %!error ncx2inv (i, 2, 2) %!error ncx2inv (2, i, 2) %!error ncx2inv (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/ncx2pdf.m000066400000000000000000000317771524624707500245740ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} ncx2pdf (@var{x}, @var{df}, @var{lambda}) ## ## Noncentral chi-squared probability distribution function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the noncentral chi-squared distribution with @var{df} degrees of freedom ## and noncentrality parameter @var{lambda}. The size of @var{y} is the common ## size of @var{x}, @var{df}, and @var{lambda}. A scalar input functions as a ## constant matrix of the same size as the other inputs. ## ## Further information about the noncentral chi-squared distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Noncentral_chi-squared_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{ncx2cdf, ncx2inv, ncx2rnd, ncx2stat, chi2pdf} ## @end deftypefn function y = ncx2pdf (x, df, lambda) ## Check for valid number of input arguments if (nargin < 3) error ("ncx2pdf: function called with too few input arguments."); endif ## Check for common size of X, DF, and LAMBDA [err, x, df, lambda] = common_size (x, df, lambda); if (err > 0) error ("ncx2pdf: X, DF, and LAMBDA must be of common size or scalars."); endif ## Check for X, DF, and LAMBDA being double or single if (! (isfloat (x) && isfloat (df) && isfloat (lambda))) error ("ncx2pdf: X, DF, and LAMBDA must be double or single."); endif ## Check for X, DF, and LAMBDA being reals if (iscomplex (x) || iscomplex (df) || iscomplex (lambda)) error ("ncx2pdf: X, DF, and LAMBDA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (df, 'single') || isa (lambda, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Find NaNs in input arguments (if any) and propagate them to p is_nan = isnan (x) | isnan (df) | isnan (lambda); y(is_nan) = NaN; ## Make input arguments column vectors and half DF x = x(:); df = df(:); df = df / 2; lambda = lambda(:); ## Handle special cases k1 = x == 0 & df == 1; y(k1) = 0.5 * exp (-0.5 * lambda(k1)); k2 = x == 0 & df < 1; y(k2) = Inf; y(lambda < 0) = NaN; y(df < 0) = NaN; k3 = lambda == 0 & df > 0; y(k3) = gampdf (x(k3), df(k3), 2); ## Handle normal cases td = find (x>0 & x0 & df>=0); ## Return if finished all normal cases if (isempty (td)) return; endif ## Reset input variables to remaining cases x = x(td); lambda = lambda(td); df = df(td) - 1; x_sqrt = sqrt (x); d_sqrt = sqrt (lambda); ## Upper Limit on density small_DF = df <= -0.5; large_DF = ! small_DF; ul = zeros (size (x)); ul(small_DF) = -0.5 * (lambda(small_DF) + x(small_DF)) + ... 0.5 * x_sqrt(small_DF) .* d_sqrt(small_DF) ./ ... (df(small_DF) + 1) + df(small_DF) .* ... (log (x(small_DF)) - log (2)) - log (2) - ... gammaln (df(small_DF) + 1); ul(large_DF) = -0.5 * (d_sqrt(large_DF) - x_sqrt(large_DF)) .^ 2 + ... df(large_DF) .* (log (x(large_DF)) - log (2)) - log (2) - ... gammaln (df(large_DF) + 1) + (df(large_DF) + 0.5) .* ... log ((df(large_DF) + 0.5) ./ (x_sqrt(large_DF) .* ... d_sqrt(large_DF) + df(large_DF) + 0.5)); ULunderflow = ul < log (realmin); y(td(ULunderflow)) = 0; td(ULunderflow) = []; ## Return if finished all normal cases if (isempty (td)) return; endif x(ULunderflow) = []; lambda(ULunderflow) = []; df(ULunderflow) = []; x_sqrt(ULunderflow) = []; d_sqrt(ULunderflow) = []; ## Try the scaled Bess function scaleB = besseli (df, d_sqrt .* x_sqrt, 1); use_SB = scaleB > 0 & scaleB < Inf; y(td(use_SB)) = exp (-log (2) -0.5 * (x_sqrt(use_SB) - ... d_sqrt(use_SB)) .^ 2 + df(use_SB) .* ... log (x_sqrt(use_SB) ./ d_sqrt(use_SB))) .* scaleB(use_SB); td(use_SB) = []; ## Return if finished all normal cases if (isempty (td)) return; endif x(use_SB) = []; lambda(use_SB) = []; df(use_SB) = []; x_sqrt(use_SB) = []; d_sqrt(use_SB) = []; ## Try the Bess function Bess = besseli (df, d_sqrt .* x_sqrt); useB = Bess > 0 & Bess < Inf; y(td(useB)) = exp (-log (2) - 0.5 * (x(useB) + lambda(useB)) + ... df(useB) .* log (x_sqrt(useB) ./ d_sqrt(useB))) .* Bess(useB); td(useB) = []; ## Return if finished all normal cases if isempty (td) return; endif x(useB) = []; lambda(useB) = []; df(useB) = []; ## If neither Bess function works, use recursion. When non-centrality ## parameter is very large, the initial values of the Poisson numbers used ## in the approximation are very small, smaller than epsilon. This would ## cause premature convergence. To avoid that, we start from the peak of the ## Poisson numbers, and go in both directions. lnsr2pi = 0.9189385332046727; % log(sqrt(2*pi)) dx = lambda .* x / 4; K = max (0, floor (0.5 * (sqrt (df .^ 2 + 4 * dx) - df))); lntK = zeros (size (K)); K0 = K == 0; lntK(K0) = -lnsr2pi -0.5 * (lambda(K0) + log (df(K0))) - ... StirlingError (df(K0)) - BinoPoisson (df(K0), x(K0) / 2); K0 = ! K0; lntK(K0) = -2 * lnsr2pi - 0.5 * (log (K(K0)) + log (df(K0) + K(K0))) - ... StirlingError (K(K0)) - StirlingError (df(K0) + K(K0)) - ... BinoPoisson (K(K0), lambda(K0) / 2) - ... BinoPoisson (df(K0) + K(K0), x(K0) / 2); sumK = ones (size (K)); keep = K>0; term = ones (size (K)); k = K; while (any (keep)) term(keep) = term(keep) .* (df(keep) + k(keep)) .* k(keep) ./ dx(keep); sumK(keep) = sumK(keep) + term(keep); keep = keep & k > 0 & term > eps (sumK); k = k - 1; endwhile keep = true (size (K)); term = ones (size (K)); k = K + 1; while (any (keep)) term(keep) = term(keep) ./ (df(keep) + k(keep)) ./ k(keep) .* dx(keep); sumK(keep) = sumK(keep) + term(keep); keep = keep & term > eps (sumK); k = k + 1; endwhile y(td) = 0.5 * exp (lntK + log (sumK)); endfunction ## Error of Stirling-De Moivre approximation to n factorial. function lambda = StirlingError (n) is_class = class (n); lambda = zeros (size (n), is_class); nn = n .* n; ## Define S0=1/12 S1=1/360 S2=1/1260 S3=1/1680 S4=1/1188 S0 = 8.333333333333333e-02; S1 = 2.777777777777778e-03; S2 = 7.936507936507937e-04; S3 = 5.952380952380952e-04; S4 = 8.417508417508418e-04; ## Define lambda(n) for n<0:0.5:15 sfe=[ 0; 1.534264097200273e-01;... 8.106146679532726e-02; 5.481412105191765e-02;... 4.134069595540929e-02; 3.316287351993629e-02;... 2.767792568499834e-02; 2.374616365629750e-02;... 2.079067210376509e-02; 1.848845053267319e-02;... 1.664469118982119e-02; 1.513497322191738e-02;... 1.387612882307075e-02; 1.281046524292023e-02;... 1.189670994589177e-02; 1.110455975820868e-02;... 1.041126526197210e-02; 9.799416126158803e-03;... 9.255462182712733e-03; 8.768700134139385e-03;... 8.330563433362871e-03; 7.934114564314021e-03;... 7.573675487951841e-03; 7.244554301320383e-03;... 6.942840107209530e-03; 6.665247032707682e-03;... 6.408994188004207e-03; 6.171712263039458e-03;... 5.951370112758848e-03; 5.746216513010116e-03;... 5.554733551962801e-03]; k = find (n <= 15); if (any (k)) n1 = n(k); n2 = 2 * n1; if (all (n2 == round (n2))) lambda(k) = sfe(n2+1); else lnsr2pi = 0.9189385332046728; lambda(k) = gammaln (n1+1)-(n1+0.5).*log (n1)+n1-lnsr2pi; endif endif k = find (n > 15 & n <= 35); if (any (k)) lambda(k) = (S0 - (S1 - (S2 - (S3 - S4 ./ nn(k)) ./ nn(k)) ./ ... nn(k)) ./ nn(k)) ./ n(k); endif k = find (n > 35 & n <= 80); if (any (k)) lambda(k) = (S0 - (S1 - (S2 - S3 ./ nn(k)) ./ nn(k)) ./ nn(k)) ./ n(k); endif k = find (n > 80 & n <= 500); if (any (k)) lambda(k) = (S0 - (S1 - S2 ./ nn(k)) ./ nn(k)) ./ n(k); endif k = find (n > 500); if (any (k)) lambda(k) = (S0 - S1 ./ nn(k)) ./ n(k); endif endfunction ## Deviance term for binomial and Poisson probability calculation. function BP = BinoPoisson (x, np) if (isa (x,'single') || isa (np,'single')) BP = zeros (size (x), 'single'); else BP = zeros (size (x)); endif k = abs (x - np) < 0.1 * (x + np); if any (k(:)) s = (x(k) - np(k)) .* (x(k) - np(k)) ./ (x(k) + np(k)); v = (x(k) - np(k)) ./ (x(k) + np(k)); ej = 2 .* x(k) .* v; is_class = class (s); s1 = zeros (size (s), is_class); ok = true (size (s)); j = 0; while any (ok(:)) ej(ok) = ej(ok) .* v(ok) .* v(ok); j = j + 1; s1(ok) = s(ok) + ej(ok) ./ (2 * j + 1); ok = ok & s1 != s; s(ok) = s1(ok); endwhile BP(k) = s; endif k = ! k; if (any (k(:))) BP(k) = x(k) .* log (x(k) ./ np(k)) + np(k) - x(k); endif endfunction %!demo %! ## Plot various PDFs from the noncentral chi-squared distribution %! x = 0:0.1:10; %! y1 = ncx2pdf (x, 2, 1); %! y2 = ncx2pdf (x, 2, 2); %! y3 = ncx2pdf (x, 2, 3); %! y4 = ncx2pdf (x, 4, 1); %! y5 = ncx2pdf (x, 4, 2); %! y6 = ncx2pdf (x, 4, 3); %! plot (x, y1, '-r', x, y2, '-g', x, y3, '-k', ... %! x, y4, '-m', x, y5, '-c', x, y6, '-y') %! grid on %! xlim ([0, 10]) %! ylim ([0, 0.32]) %! legend ({'df = 2, λ = 1', 'df = 2, λ = 2', ... %! 'df = 2, λ = 3', 'df = 4, λ = 1', ... %! 'df = 4, λ = 2', 'df = 4, λ = 3'}, 'location', 'northeast') %! title ('Noncentral chi-squared PDF') %! xlabel ('values in x') %! ylabel ('density') %!demo %! ## Compare the noncentral chi-squared PDF with LAMBDA = 2 to the %! ## chi-squared PDF with the same number of degrees of freedom (4). %! %! x = 0:0.1:10; %! y1 = ncx2pdf (x, 4, 2); %! y2 = chi2pdf (x, 4); %! plot (x, y1, '-', x, y2, '-'); %! grid on %! xlim ([0, 10]) %! ylim ([0, 0.32]) %! legend ({'Noncentral T(10,1)', 'T(10)'}, 'location', 'northwest') %! title ('Noncentral chi-squared vs chi-squared PDFs') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x1, df, d1 %! x1 = [-Inf, 2, NaN, 4, Inf]; %! df = [2, 0, -1, 1, 4]; %! d1 = [1, NaN, 3, -1, 2]; %!assert_equal (ncx2pdf (x1, df, d1), [0, NaN, NaN, NaN, 0]); %!assert_equal (ncx2pdf (x1, df, 1), [0, 0.07093996461786045, NaN, ... %! 0.06160064323277038, 0], 1e-14); %!assert_equal (ncx2pdf (x1, df, 3), [0, 0.1208364909271113, NaN, ... %! 0.09631299762429098, 0], 1e-14); %!assert_equal (ncx2pdf (x1, df, 2), [0, 0.1076346446244688, NaN, ... %! 0.08430464047296625, 0], 1e-14); %!assert_equal (ncx2pdf (x1, 2, d1), [0, NaN, NaN, NaN, 0]); %!assert_equal (ncx2pdf (2, df, d1), [0.1747201674611283, NaN, NaN, ... %! NaN, 0.1076346446244688], 1e-14); %!assert_equal (ncx2pdf (4, df, d1), [0.09355987820265799, NaN, NaN, ... %! NaN, 0.1192317192431485], 1e-14); ## Test input validation %!error ncx2pdf () %!error ncx2pdf (1) %!error ncx2pdf (1, 2) %!error ... %! ncx2pdf (ones (3), ones (2), ones (2)) %!error ... %! ncx2pdf (ones (2), ones (3), ones (2)) %!error ... %! ncx2pdf (ones (2), ones (2), ones (3)) %!error ncx2pdf (int32 (2), 2, 2) %!error ncx2pdf (true, 2, 2) %!error ncx2pdf ('a', 2, 2) %!error ncx2pdf (i, 2, 2) %!error ncx2pdf (2, i, 2) %!error ncx2pdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/ncx2rnd.m000066400000000000000000000155101524624707500245710ustar00rootroot00000000000000## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} ncx2rnd (@var{df}, @var{lambda}) ## @deftypefnx {statistics} {@var{r} =} ncx2rnd (@var{df}, @var{lambda}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} ncx2rnd (@var{df}, @var{lambda}, [@var{sz}]) ## ## Random arrays from the noncentral chi-squared distribution. ## ## @code{@var{r} = ncx2rnd (@var{df}, @var{lambda})} returns an array of random ## numbers chosen from the noncentral chi-squared distribution with @var{df} ## degrees of freedom and noncentrality parameter @var{lambda}. The size of ## @var{r} is the common size of @var{df} and @var{lambda}. A scalar input ## functions as a constant matrix of the same size as the other input. ## ## When called with a single size argument, @code{ncx2rnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the noncentral chi-squared distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Noncentral_chi-squared_distribution} ## ## @seealso{ncx2cdf, ncx2inv, ncx2pdf, ncx2stat} ## @end deftypefn function r = ncx2rnd (df, lambda, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("ncx2rnd: function called with too few input arguments."); endif ## Check for common size of DF and LAMBDA if (! isscalar (df) || ! isscalar (lambda)) [retval, df, lambda] = common_size (df, lambda); if (retval > 0) error ("ncx2rnd: DF and LAMBDA must be of common size or scalars."); endif endif ## Check for DF and LAMBDA being reals if (iscomplex (df) || iscomplex (lambda)) error ("ncx2rnd: DF and LAMBDA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (df); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("ncx2rnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("ncx2rnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (df) && ! isequal (size (df), size (ones (sz)))) error ("ncx2rnd: DF and LAMBDA must be scalars or of size SZ."); endif ## Check for class type if (isa (df, 'single') || isa (lambda, 'single')); cls = 'single'; else cls = 'double'; endif ## Return NaNs for out of range values of DF and LAMBDA df(df <= 0) = NaN; lambda(lambda <= 0) = NaN; ## Force DF and LAMBDA into the same size as SZ (if necessary) if (isscalar (df)) df = repmat (df, sz); endif if (isscalar (lambda)) lambda = repmat (lambda, sz); endif ## Generate random sample from noncentral chi-squared distribution r = randp (lambda ./ 2); r(r > 0) = 2 * randg (r(r > 0)); r(df > 0) += 2 * randg (df(df > 0) / 2); ## Cast to appropriate class r = cast (r, cls); endfunction ## Test output %!assert_equal (size (ncx2rnd (1, 1)), [1, 1]) %!assert_equal (size (ncx2rnd (1, ones (2, 1))), [2, 1]) %!assert_equal (size (ncx2rnd (1, ones (2, 2))), [2, 2]) %!assert_equal (size (ncx2rnd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (ncx2rnd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (ncx2rnd (1, 1, 3)), [3, 3]) %!assert_equal (size (ncx2rnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (ncx2rnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (ncx2rnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (ncx2rnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (ncx2rnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (ncx2rnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (ncx2rnd (1, 1, [])), [0, 0]) %!assert_equal (size (ncx2rnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (ncx2rnd (1, 2, -1)), [0, 0]) %!assert_equal (size (ncx2rnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (ncx2rnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (ncx2rnd (1, 1)), "double") %!assert_equal (class (ncx2rnd (1, single (1))), "single") %!assert_equal (class (ncx2rnd (1, single ([1, 1]))), "single") %!assert_equal (class (ncx2rnd (single (1), 1)), "single") %!assert_equal (class (ncx2rnd (single ([1, 1]), 1)), "single") ## Test input validation %!error ncx2rnd () %!error ncx2rnd (1) %!error ... %! ncx2rnd (ones (3), ones (2)) %!error ... %! ncx2rnd (ones (2), ones (3)) %!error ncx2rnd (i, 2) %!error ncx2rnd (1, i) %!error ... %! ncx2rnd (1, 2, 1.2) %!error ... %! ncx2rnd (1, 2, ones (2)) %!error ... %! ncx2rnd (1, 2, [2 0 2.5]) %!error ... %! ncx2rnd (1, 2, 2, 1.5, 5) %!error ... %! ncx2rnd (2, ones (2), 3) %!error ... %! ncx2rnd (2, ones (2), [3, 2]) %!error ... %! ncx2rnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/normcdf.m000066400000000000000000000242121524624707500246420ustar00rootroot00000000000000## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} normcdf (@var{x}) ## @deftypefnx {statistics} {@var{p} =} normcdf (@var{x}, @var{mu}) ## @deftypefnx {statistics} {@var{p} =} normcdf (@var{x}, @var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{p} =} normcdf (@dots{}, @qcode{'upper'}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} normcdf (@var{x}, @var{mu}, @var{sigma}, @var{pcov}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} normcdf (@var{x}, @var{mu}, @var{sigma}, @var{pcov}, @var{alpha}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} normcdf (@dots{}, @qcode{'upper'}) ## ## Normal cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the normal distribution with mean @var{mu} and standard deviation ## @var{sigma}. The size of @var{p} is the common size of @var{x}, @var{mu} and ## @var{sigma}. A scalar input functions as a constant matrix of the same size ## as the other inputs. ## ## Default values are @var{mu} = 0, @var{sigma} = 1. ## ## When called with three output arguments, i.e. @qcode{[@var{p}, @var{plo}, ## @var{pup}]}, @code{normcdf} computes the confidence bounds for @var{p} when ## the input parameters @var{mu} and @var{sigma} are estimates. In such case, ## @var{pcov}, a @math{2*2} matrix containing the covariance matrix of the ## estimated parameters, is necessary. Optionally, @var{alpha}, which has a ## default value of 0.05, specifies the @qcode{100 * (1 - @var{alpha})} percent ## confidence bounds. @var{plo} and @var{pup} are arrays of the same size as ## @var{p} containing the lower and upper confidence bounds. ## ## @code{[@dots{}] = normcdf (@dots{}, "upper")} computes the upper tail ## probability of the normal distribution with parameters @var{mu} and ## @var{sigma}, at the values in @var{x}. This can be used to compute a ## right-tailed p-value. To compute a two-tailed p-value, use ## @code{2 * normcdf (-abs (@var{x}), @var{mu}, @var{sigma})}. ## ## Further information about the normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Normal_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{norminv, normpdf, normrnd, normfit, normlike, normstat} ## @end deftypefn function [varargout] = normcdf (x, varargin) ## Check for valid number of input arguments if (nargin < 1 || nargin > 6) error ("normcdf: invalid number of input arguments."); endif ## Check for "upper" flag if (nargin > 1 && strcmpi (varargin{end}, 'upper')) uflag = true; varargin(end) = []; elseif (nargin > 1 && ischar (varargin{end}) && ... ! strcmpi (varargin{end}, 'upper')) error ("normcdf: invalid argument for upper tail."); else uflag = false; endif ## Get extra arguments (if they exist) or add defaults if (numel (varargin) > 0) mu = varargin{1}; else mu = 0; endif if (numel (varargin) > 1) sigma = varargin{2}; else sigma = 1; endif if (numel (varargin) > 2) pcov = varargin{3}; ## Check for valid covariance matrix 2x2 if (! isequal (size (pcov), [2, 2])) error ("normcdf: invalid size of covariance matrix."); endif else ## Check that cov matrix is provided if 3 output arguments are requested if (nargout > 1) error ("normcdf: covariance matrix is required for confidence bounds."); endif pcov = []; endif if (numel (varargin) > 3) alpha = varargin{4}; ## Check for valid alpha value if (! isnumeric (alpha) || numel (alpha) !=1 || alpha <= 0 || alpha >= 1) error ("normcdf: invalid value for alpha."); endif else alpha = 0.05; endif ## Check for common size of X, MU, and SIGMA if (! isscalar (mu) || ! isscalar (sigma)) [err, x, mu, sigma] = common_size (x, mu, sigma); if (err > 0) error ("normcdf: X, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("normcdf: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma)) error ("normcdf: X, MU, and SIGMA must not be complex."); endif ## Compute normal CDF z = (x - mu) ./ sigma; if (uflag) z = -z; endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Prepare output p = NaN (size (z), is_class); if (nargout > 1) plo = NaN (size (z), is_class); pup = NaN (size (z), is_class); endif ## Check SIGMA if (isscalar (sigma)) if (sigma > 0) sigma_p = true (size (z)); sigma_z = false (size (z)); elseif (sigma == 0) sigma_z = true (size (z)); sigma_p = false (size (z)); else if (nargout <= 1) varargout{1} = p; elseif (nargout == 3) varargout{1} = p; varargout{2} = plo; varargout{3} = pup; endif return; endif else sigma_p = sigma > 0; sigma_z = sigma == 0; endif ## Set edge cases when SIGMA = 0 if (uflag) p(sigma_z & x < mu) = 1; p(sigma_z & x >= mu) = 0; if (nargout > 1) plo(sigma_z & x < mu) = 1; plo(sigma_z & x >= mu) = 0; pup(sigma_z & x < mu) = 1; pup(sigma_z & x >= mu) = 0; endif else p(sigma_z & x < mu) = 0; p(sigma_z & x >= mu) = 1; if (nargout >= 2) plo(sigma_z & x < mu) = 0; plo(sigma_z & x >= mu) = 1; pup(sigma_z & x < mu) = 0; pup(sigma_z & x >= mu) = 1; endif endif ## Compute cases when SIGMA > 0 p(sigma_p) = 0.5 * erfc (-z(sigma_p) ./ sqrt (2)); varargout{1} = p; ## Compute confidence bounds (if requested) if (nargout >= 2) zvar = (pcov(1,1) + 2 * pcov(1,2) * z(sigma_p) + ... pcov(2,2) * z(sigma_p) .^ 2) ./ (sigma .^ 2); if (any (zvar < 0)) error ("normcdf: bad covariance matrix."); endif normz = -norminv (alpha / 2); halfwidth = normz * sqrt (zvar); zlo = z(sigma_p) - halfwidth; zup = z(sigma_p) + halfwidth; plo(sigma_p) = 0.5 * erfc (-zlo ./ sqrt (2)); pup(sigma_p) = 0.5 * erfc (-zup ./ sqrt (2)); varargout{2} = plo; varargout{3} = pup; endif endfunction %!demo %! ## Plot various CDFs from the normal distribution %! x = -5:0.01:5; %! p1 = normcdf (x, 0, 0.5); %! p2 = normcdf (x, 0, 1); %! p3 = normcdf (x, 0, 2); %! p4 = normcdf (x, -2, 0.8); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c') %! grid on %! xlim ([-5, 5]) %! legend ({'μ = 0, σ = 0.5', 'μ = 0, σ = 1', ... %! 'μ = 0, σ = 2', 'μ = -2, σ = 0.8'}, 'location', 'southeast') %! title ('Normal CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-Inf 1 2 Inf]; %! y = [0, 0.5, 1/2*(1+erf(1/sqrt(2))), 1]; %!assert_equal (normcdf (x, ones (1,4), ones (1,4)), y) %!assert_equal (normcdf (x, 1, ones (1,4)), y) %!assert_equal (normcdf (x, ones (1,4), 1), y) %!assert_equal (normcdf (x, [0, -Inf, NaN, Inf], 1), [0, 1, NaN, NaN]) %!assert_equal (normcdf (x, 1, [Inf, NaN, -1, 0]), [NaN, NaN, NaN, 1]) %!assert_equal (normcdf ([x(1:2), NaN, x(4)], 1, 1), [y(1:2), NaN, y(4)]) %!assert_equal (normcdf (x, 'upper'), [1, 0.1587, 0.0228, 0], 1e-4) ## Test class of input preserved %!assert_equal (normcdf ([x, NaN], 1, 1), [y, NaN]) %!assert_equal (normcdf (single ([x, NaN]), 1, 1), single ([y, NaN]), eps ('single')) %!assert_equal (normcdf ([x, NaN], single (1), 1), single ([y, NaN]), eps ('single')) %!assert_equal (normcdf ([x, NaN], 1, single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error normcdf () %!error normcdf (1,2,3,4,5,6,7) %!error normcdf (1, 2, 3, 4, 'uper') %!error ... %! normcdf (ones (3), ones (2), ones (2)) %!error normcdf (2, 3, 4, [1, 2]) %!error ... %! [p, plo, pup] = normcdf (1, 2, 3) %!error [p, plo, pup] = ... %! normcdf (1, 2, 3, [1, 0; 0, 1], 0) %!error [p, plo, pup] = ... %! normcdf (1, 2, 3, [1, 0; 0, 1], 1.22) %!error [p, plo, pup] = ... %! normcdf (1, 2, 3, [1, 0; 0, 1], 'alpha', 'upper') %!error normcdf (int32 (2), 2, 2) %!error normcdf (true, 2, 2) %!error normcdf ('a', 2, 2) %!error normcdf (i, 2, 2) %!error normcdf (2, i, 2) %!error normcdf (2, 2, i) %!error ... %! [p, plo, pup] =normcdf (1, 2, 3, [1, 0; 0, -inf], 0.04) statistics-release-1.9.2/inst/Distribution_Functions/norminv.m000066400000000000000000000137471524624707500247150ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} norminv (@var{p}) ## @deftypefnx {statistics} {@var{x} =} norminv (@var{p}, @var{mu}) ## @deftypefnx {statistics} {@var{x} =} norminv (@var{p}, @var{mu}, @var{sigma}) ## ## Inverse of the normal cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the normal distribution with mean @var{mu} and standard deviation ## @var{sigma}. The size of @var{p} is the common size of @var{p}, @var{mu} and ## @var{sigma}. A scalar input functions as a constant matrix of the same size ## as the other inputs. ## ## Default values are @var{mu} = 0, @var{sigma} = 1. ## ## The default values correspond to the standard normal distribution and ## computing its quantile function is also possible with the @code{probit} ## function, which is faster but it does not perform any input validation. ## ## Further information about the normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Normal_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{normcdf, normpdf, normrnd, normfit, normlike, normstat, probit} ## @end deftypefn function x = norminv (p, mu, sigma) ## Check for valid number of input arguments if (nargin < 1) error ("norminv: function called with too few input arguments."); endif ## Add defaults (if missing input arguments) if (nargin < 2) mu = 0; endif if (nargin < 3) sigma = 1; endif ## Check for common size of P, MU, and SIGMA if (! isscalar (p) || ! isscalar (mu) || ! isscalar (sigma)) [retval, p, mu, sigma] = common_size (p, mu, sigma); if (retval > 0) error ("norminv: P, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for P, MU, and SIGMA being double or single if (! (isfloat (p) && isfloat (mu) && isfloat (sigma))) error ("norminv: P, MU, and SIGMA must be double or single."); endif ## Check for P, MU, and SIGMA being reals if (iscomplex (p) || iscomplex (mu) || iscomplex (sigma)) error ("norminv: P, MU, and SIGMA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (mu, 'single') || isa (sigma, 'single')) x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ## Compute normal iCDF if (isscalar (mu) && isscalar (sigma)) if (isfinite (mu) && (sigma > 0) && (sigma < Inf)) x = mu + sigma * (-sqrt (2) * erfcinv (2 * p)); endif else k = isfinite (mu) & (sigma > 0) & (sigma < Inf); x(k) = mu(k) + sigma(k) .* (-sqrt (2) * erfcinv (2 * p(k))); endif endfunction %!demo %! ## Plot various iCDFs from the normal distribution %! p = 0.001:0.001:0.999; %! x1 = norminv (p, 0, 0.5); %! x2 = norminv (p, 0, 1); %! x3 = norminv (p, 0, 2); %! x4 = norminv (p, -2, 0.8); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c') %! grid on %! ylim ([-5, 5]) %! legend ({'μ = 0, σ = 0.5', 'μ = 0, σ = 1', ... %! 'μ = 0, σ = 2', 'μ = -2, σ = 0.8'}, 'location', 'northwest') %! title ('Normal iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p %! p = [-1 0 0.5 1 2]; %!assert_equal (norminv (p, ones (1,5), ones (1,5)), [NaN -Inf 1 Inf NaN]) %!assert_equal (norminv (p, 1, ones (1,5)), [NaN -Inf 1 Inf NaN]) %!assert_equal (norminv (p, ones (1,5), 1), [NaN -Inf 1 Inf NaN]) %!assert_equal (norminv (p, [1 -Inf NaN Inf 1], 1), [NaN NaN NaN NaN NaN]) %!assert_equal (norminv (p, 1, [1 0 NaN Inf 1]), [NaN NaN NaN NaN NaN]) %!assert_equal (norminv ([p(1:2) NaN p(4:5)], 1, 1), [NaN -Inf NaN Inf NaN]) %!assert_equal (norminv (p), probit (p)) %!assert_equal (norminv (0.31254), probit (0.31254)) ## Test class of input preserved %!assert_equal (norminv ([p, NaN], 1, 1), [NaN -Inf 1 Inf NaN NaN]) %!assert_equal (norminv (single ([p, NaN]), 1, 1), single ([NaN -Inf 1 Inf NaN NaN])) %!assert_equal (norminv ([p, NaN], single (1), 1), single ([NaN -Inf 1 Inf NaN NaN])) %!assert_equal (norminv ([p, NaN], 1, single (1)), single ([NaN -Inf 1 Inf NaN NaN])) ## Test input validation %!error norminv () %!error ... %! norminv (ones (3), ones (2), ones (2)) %!error ... %! norminv (ones (2), ones (3), ones (2)) %!error ... %! norminv (ones (2), ones (2), ones (3)) %!error norminv (int32 (2), 2, 2) %!error norminv (true, 2, 2) %!error norminv ('a', 2, 2) %!error norminv (i, 2, 2) %!error norminv (2, i, 2) %!error norminv (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/normpdf.m000066400000000000000000000134511524624707500246620ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} normpdf (@var{x}) ## @deftypefnx {statistics} {@var{y} =} normpdf (@var{x}, @var{mu}) ## @deftypefnx {statistics} {@var{y} =} normpdf (@var{x}, @var{mu}, @var{sigma}) ## ## Normal probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the normal distribution with mean @var{mu} and standard deviation ## @var{sigma}. The size of @var{y} is the common size of @var{p}, @var{mu} and ## @var{sigma}. A scalar input functions as a constant matrix of the same size ## as the other inputs. ## ## Default values are @var{mu} = 0, @var{sigma} = 1. ## ## Further information about the normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Normal_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{normcdf, norminv, normrnd, normfit, normlike, normstat} ## @end deftypefn function y = normpdf (x, mu, sigma) ## Check for valid number of input arguments if (nargin < 1 || nargin > 3) error ("normpdf: function called with too few input arguments."); endif ## Add defaults (if missing input arguments) if (nargin < 2) mu = 0; endif if (nargin < 3) sigma = 1; endif ## Check for common size of X, MU, and SIGMA if (! isscalar (x) || ! isscalar (mu) || ! isscalar (sigma)) [retval, x, mu, sigma] = common_size (x, mu, sigma); if (retval > 0) error ("normpdf: X, MU, and SIGMA must be of common size or scalars."); endif endif ## Check for X, MU, and SIGMA being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma))) error ("normpdf: X, MU, and SIGMA must be double or single."); endif ## Check for X, MU, and SIGMA being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma)) error ("normpdf: X, MU, and SIGMA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Compute normal PDF if (isscalar (mu) && isscalar (sigma)) if (isfinite (mu) && (sigma > 0) && (sigma < Inf)) y = stdnormal_pdf ((x - mu) / sigma) / sigma; else y = NaN (size (x), class (y)); endif else k = isinf (mu) | ! (sigma > 0) | ! (sigma < Inf); y(k) = NaN; k = ! isinf (mu) & (sigma > 0) & (sigma < Inf); y(k) = stdnormal_pdf ((x(k) - mu(k)) ./ sigma(k)) ./ sigma(k); endif endfunction function y = stdnormal_pdf (x) y = (2 * pi)^(- 1/2) * exp (- x .^ 2 / 2); endfunction %!demo %! ## Plot various PDFs from the normal distribution %! x = -5:0.01:5; %! y1 = normpdf (x, 0, 0.5); %! y2 = normpdf (x, 0, 1); %! y3 = normpdf (x, 0, 2); %! y4 = normpdf (x, -2, 0.8); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c') %! grid on %! xlim ([-5, 5]) %! ylim ([0, 0.9]) %! legend ({'μ = 0, σ = 0.5', 'μ = 0, σ = 1', ... %! 'μ = 0, σ = 2', 'μ = -2, σ = 0.8'}, 'location', 'northeast') %! title ('Normal PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-Inf, 1, 2, Inf]; %! y = 1 / sqrt (2 * pi) * exp (-(x - 1) .^ 2 / 2); %!assert_equal (normpdf (x, ones (1,4), ones (1,4)), y, eps) %!assert_equal (normpdf (x, 1, ones (1,4)), y, eps) %!assert_equal (normpdf (x, ones (1,4), 1), y, eps) %!assert_equal (normpdf (x, [0 -Inf NaN Inf], 1), [y(1) NaN NaN NaN], eps) %!assert_equal (normpdf (x, 1, [Inf NaN -1 0]), [NaN NaN NaN NaN], eps) %!assert_equal (normpdf ([x, NaN], 1, 1), [y, NaN], eps) ## Test class of input preserved %!assert_equal (normpdf (single ([x, NaN]), 1, 1), single ([y, NaN]), eps ('single')) %!assert_equal (normpdf ([x, NaN], single (1), 1), single ([y, NaN]), eps ('single')) %!assert_equal (normpdf ([x, NaN], 1, single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error normpdf () %!error ... %! normpdf (ones (3), ones (2), ones (2)) %!error ... %! normpdf (ones (2), ones (3), ones (2)) %!error ... %! normpdf (ones (2), ones (2), ones (3)) %!error normpdf (int32 (2), 2, 2) %!error normpdf (true, 2, 2) %!error normpdf ('a', 2, 2) %!error normpdf (i, 2, 2) %!error normpdf (2, i, 2) %!error normpdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/normrnd.m000066400000000000000000000155141524624707500246760ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} normrnd (@var{mu}, @var{sigma}) ## @deftypefnx {statistics} {@var{r} =} normrnd (@var{mu}, @var{sigma}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} normrnd (@var{mu}, @var{sigma}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} normrnd (@var{mu}, @var{sigma}, [@var{sz}]) ## ## Random arrays from the normal distribution. ## ## @code{@var{r} = normrnd (@var{mu}, @var{sigma})} returns an array of random ## numbers chosen from the normal distribution with mean @var{mu} and standard ## deviation @var{sigma}. The size of @var{r} is the common size of @var{mu} ## and @var{sigma}. A scalar input functions as a constant matrix of the same ## size as the other inputs. Both parameters must be finite real numbers and ## @var{sigma} > 0, otherwise NaN is returned. ## ## When called with a single size argument, @code{normrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Normal_distribution} ## ## @seealso{normcdf, norminv, normpdf, normfit, normlike, normstat} ## @end deftypefn function r = normrnd (mu, sigma, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("normrnd: function called with too few input arguments."); endif ## Check for common size of MU and SIGMA if (! isscalar (mu) || ! isscalar (sigma)) [retval, mu, sigma] = common_size (mu, sigma); if (retval > 0) error ("normrnd: MU and SIGMA must be of common size or scalars."); endif endif ## Check for MU and SIGMA being reals if (iscomplex (mu) || iscomplex (sigma)) error ("normrnd: MU and SIGMA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (mu); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("normrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("normrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (mu) && ! isequal (size (mu), size (ones (sz)))) error ("normrnd: MU and SIGMA must be scalars or of size SZ."); endif ## Check for class type if (isa (mu, 'single') || isa (sigma, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from normal distribution if (isscalar (mu) && isscalar (sigma)) if (isfinite (mu) && (sigma >= 0) && (sigma < Inf)) r = mu + sigma * randn (sz, cls); else r = NaN (sz, cls); endif else r = mu + sigma .* randn (sz, cls); k = ! isfinite (mu) | ! (sigma >= 0) | ! (sigma < Inf); r(k) = NaN; endif endfunction ## Test output %!assert_equal (size (normrnd (1, 1)), [1, 1]) %!assert_equal (size (normrnd (1, ones (2, 1))), [2, 1]) %!assert_equal (size (normrnd (1, ones (2, 2))), [2, 2]) %!assert_equal (size (normrnd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (normrnd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (normrnd (1, 1, 3)), [3, 3]) %!assert_equal (size (normrnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (normrnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (normrnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (normrnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (normrnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (normrnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (normrnd (1, 1, [])), [0, 0]) %!assert_equal (size (normrnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (normrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (normrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (normrnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (normrnd (1, 1)), "double") %!assert_equal (class (normrnd (1, single (1))), "single") %!assert_equal (class (normrnd (1, single ([1, 1]))), "single") %!assert_equal (class (normrnd (single (1), 1)), "single") %!assert_equal (class (normrnd (single ([1, 1]), 1)), "single") ## Test input validation %!error normrnd () %!error normrnd (1) %!error ... %! normrnd (ones (3), ones (2)) %!error ... %! normrnd (ones (2), ones (3)) %!error normrnd (i, 2, 3) %!error normrnd (1, i, 3) %!error ... %! normrnd (1, 2, 1.2) %!error ... %! normrnd (1, 2, ones (2)) %!error ... %! normrnd (1, 2, [2 0 2.5]) %!error ... %! normrnd (1, 2, 2, 1.5, 5) %!error ... %! normrnd (2, ones (2), 3) %!error ... %! normrnd (2, ones (2), [3, 2]) %!error ... %! normrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/plcdf.m000066400000000000000000000143351524624707500243070ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} plcdf (@var{data}, @var{x}, @var{Fx}) ## @deftypefnx {statistics} {@var{p} =} plcdf (@var{data}, @var{x}, @var{Fx}, @qcode{'upper'}) ## ## Piecewise linear cumulative distribution function (CDF). ## ## For each element of @var{data}, compute the cumulative distribution function ## (CDF) of the piecewise linear distribution with a vector of @var{x} values at ## which the CDF changes slope and a vector of CDF values @var{Fx} that ## correspond to each value in @var{x}. Both @var{x} and @var{Fx} must be ## vectors of the same size and at least 2-elements long. The size of @var{p} ## is the same as @var{data}. ## ## @code{@var{p} = plcdf (@var{data}, @var{x}, @var{Fx}, "upper")} computes ## the upper tail probability of the piecewise linear distribution with ## parameters @var{x} and @var{Fx}, at the values in @var{data}. ## ## Further information about the piecewise linear distribution can be found at ## @url{https://en.wikipedia.org/wiki/Piecewise_linear_function} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{plinv, plpdf, plrnd, plstat} ## @end deftypefn function p = plcdf (data, x, Fx, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("plcdf: function called with too few input arguments."); endif ## Check for "upper" flag if (nargin == 4 && strcmpi (uflag, 'upper')) uflag = true; elseif (nargin == 4 && ! strcmpi (uflag, 'upper')) error ("plcdf: invalid argument for upper tail."); else uflag = false; endif ## Check for common size of X and FX if (! isvector (x) || ! isvector (Fx) || ! isequal (size (x), size (Fx))) error ("plcdf: X and FX must be vectors of equal size."); endif ## Check for X and FX being at least 2-elements long if (length (x) < 2 || length (Fx) < 2) error ("plcdf: X and FX must be at least two-elements long."); endif ## Check for Fx being bounded in [0, 1] if (any (Fx < 0) || any (Fx > 1)) error ("plcdf: FX must be bounded in the range [0, 1]."); endif ## Check for DATA, X, and FX being double or single if (! (isfloat (data) && isfloat (x) && isfloat (Fx))) error ("plcdf: DATA, X, and FX must be double or single."); endif ## Check for DATA, X, and FX being reals if (iscomplex (data) || iscomplex (x) || iscomplex (Fx)) error ("plcdf: DATA, X, and FX must not be complex."); endif ## Check for class type if (isa (data, 'single') || isa (x, 'single') || isa (Fx, 'single')); p = zeros (size (data), 'single'); else p = zeros (size (data)); endif ## Find data within supported range support = (data >= x(1) & data <= x(end)); p(support) = interp1 (x, Fx, data(support), 'linear'); ## Force right side outside support to 1 and invalid data to NaN p(data > x(end)) = 1; p(isnan (data)) = NaN; ## Return upper tail (if requested) if (uflag) p = 1 - p; endif endfunction %!demo %! ## Plot various CDFs from the Piecewise linear distribution %! data = 0:0.01:10; %! x1 = [0, 1, 3, 4, 7, 10]; %! Fx1 = [0, 0.2, 0.5, 0.6, 0.7, 1]; %! x2 = [0, 2, 5, 6, 7, 8]; %! Fx2 = [0, 0.1, 0.3, 0.6, 0.9, 1]; %! p1 = plcdf (data, x1, Fx1); %! p2 = plcdf (data, x2, Fx2); %! plot (data, p1, '-b', data, p2, 'g') %! grid on %! ylim ([0, 1]) %! xlim ([0, 10]) %! legend ({'x1, Fx1', 'x2, Fx2'}, 'location', 'southeast') %! title ('Piecewise linear CDF') %! xlabel ('values in data') %! ylabel ('probability') ## Test output %!test %! data = 0:0.2:1; %! p = plcdf (data, [0, 1], [0, 1]); %! assert_equal (p, data); %!test %! data = 0:0.2:1; %! p = plcdf (data, [0, 2], [0, 1]); %! assert_equal (p, 0.5 * data); %!test %! data = 0:0.2:1; %! p = plcdf (data, [0, 1], [0, 0.5]); %! assert_equal (p, 0.5 * data); %!test %! data = 0:0.2:1; %! p = plcdf (data, [0, 0.5], [0, 1]); %! assert_equal (p, [0, 0.4, 0.8, 1, 1, 1]); %!test %! data = 0:0.2:1; %! p = plcdf (data, [0, 1], [0, 1], 'upper'); %! assert_equal (p, 1 - data); ## Test input validation %!error plcdf () %!error plcdf (1) %!error plcdf (1, 2) %!error plcdf (1, 2, 3, 'uper') %!error plcdf (1, 2, 3, 4) %!error ... %! plcdf (1, [0, 1, 2], [0, 1]) %!error ... %! plcdf (1, [0], [1]) %!error ... %! plcdf (1, [0, 1, 2], [0, 1, 1.5]) %!error ... %! plcdf (1, [0, 1, 2], [0, i, 1]) %!error ... %! plcdf (int32 (2), [0, 1, 2], [0, 0.5, 1]) %!error ... %! plcdf (true, [0, 1, 2], [0, 0.5, 1]) %!error ... %! plcdf ('a', [0, 1, 2], [0, 0.5, 1]) %!error ... %! plcdf (i, [0, 1, 2], [0, 0.5, 1]) %!error ... %! plcdf (1, [0, i, 2], [0, 0.5, 1]) %!error ... %! plcdf (1, [0, 1, 2], [0, 0.5i, 1]) statistics-release-1.9.2/inst/Distribution_Functions/plinv.m000066400000000000000000000132021524624707500243370ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{data} =} plinv (@var{p}, @var{x}, @var{Fx}) ## ## Inverse of the piecewise linear distribution (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) ## of the piecewise linear distribution with a vector of @var{x} values at ## which the CDF changes slope and a vector of CDF values @var{Fx} that ## correspond to each value in @var{x}. Both @var{x} and @var{Fx} must be ## vectors of the same_p size and at least 2-elements long.. The size of ## @var{data} is the same_p as @var{p}. ## ## Further information about the piecewise linear distribution can be found at ## @url{https://en.wikipedia.org/wiki/Piecewise_linear_function} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{plcdf, plpdf, plrnd, plstat} ## @end deftypefn function data = plinv (p, x, Fx) ## Check for valid number of input arguments if (nargin < 3) error ("plinv: function called with too few input arguments."); endif ## Check for common size of X and FX if (! isvector (x) || ! isvector (Fx) || ! isequal (size (x), size (Fx))) error ("plinv: X and FX must be vectors of equal size."); endif ## Check for X and FX being at least 2-elements long if (length (x) < 2 || length (Fx) < 2) error ("plinv: X and FX must be at least two-elements long."); endif ## Check for Fx being bounded in [0, 1] if (any (Fx < 0) || any (Fx > 1)) error ("plinv: FX must be bounded in the range [0, 1]."); endif ## Check for P, X, and FX being double or single if (! (isfloat (p) && isfloat (x) && isfloat (Fx))) error ("plinv: P, X, and FX must be double or single."); endif ## Check for P, X, and FX being reals if (iscomplex (p) || iscomplex (x) || iscomplex (Fx)) error ("plinv: P, X, and FX must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (x, 'single') || isa (Fx, 'single')); data = zeros (size (p), 'single'); else data = zeros (size (p)); endif ## Remove consecutive bins with almost zero probability pw_diff = diff (Fx); if any (pw_diff==0) zero_p = 2 * eps (Fx); same_p = pw_diff <= zero_p(1:end-1); remove = same_p(1:end-1) & same_p(2:end); while (any (remove)) idx = find (remove); same_p(idx) = []; Fx(idx+1) = []; x(idx+1) = []; pw_diff = diff (Fx); remove = same_p(1:end-1) & same_p(2:end); endwhile idx = find (pw_diff==0); Fx(idx+1) = Fx(idx) + eps (Fx(idx)); endif p(p < 0 | 1 < p) = NaN; data = interp1 (Fx, x, p, 'linear'); endfunction %!demo %! ## Plot various iCDFs from the Piecewise linear distribution %! p = 0.001:0.001:0.999; %! x1 = [0, 1, 3, 4, 7, 10]; %! Fx1 = [0, 0.2, 0.5, 0.6, 0.7, 1]; %! x2 = [0, 2, 5, 6, 7, 8]; %! Fx2 = [0, 0.1, 0.3, 0.6, 0.9, 1]; %! data1 = plinv (p, x1, Fx1); %! data2 = plinv (p, x2, Fx2); %! plot (p, data1, '-b', p, data2, '-g') %! grid on %! legend ({'x1, Fx1', 'x2, Fx2'}, 'location', 'northwest') %! title ('Piecewise linear iCDF') %! xlabel ('probability') %! ylabel ('values in data') ## Test output %!test %! p = 0:0.2:1; %! data = plinv (p, [0, 1], [0, 1]); %! assert_equal (data, p); %!test %! p = 0:0.2:1; %! data = plinv (p, [0, 2], [0, 1]); %! assert_equal (data, 2 * p); %!test %! p = 0:0.2:1; %! data_out = 1:6; %! data = plinv (p, [0, 1], [0, 0.5]); %! assert_equal (data, [0, 0.4, 0.8, NA, NA, NA]); %!test %! p = 0:0.2:1; %! data_out = 1:6; %! data = plinv (p, [0, 0.5], [0, 1]); %! assert_equal (data, [0:0.1:0.5]); ## Test input validation %!error plinv () %!error plinv (1) %!error plinv (1, 2) %!error ... %! plinv (1, [0, 1, 2], [0, 1]) %!error ... %! plinv (1, [0], [1]) %!error ... %! plinv (1, [0, 1, 2], [0, 1, 1.5]) %!error ... %! plinv (1, [0, 1, 2], [0, i, 1]) %!error ... %! plinv (int32 (2), [0, 1, 2], [0, 0.5, 1]) %!error ... %! plinv (true, [0, 1, 2], [0, 0.5, 1]) %!error ... %! plinv ('a', [0, 1, 2], [0, 0.5, 1]) %!error ... %! plinv (i, [0, 1, 2], [0, 0.5, 1]) %!error ... %! plinv (1, [0, i, 2], [0, 0.5, 1]) %!error ... %! plinv (1, [0, 1, 2], [0, 0.5i, 1]) statistics-release-1.9.2/inst/Distribution_Functions/plpdf.m000066400000000000000000000131521524624707500243200ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} plpdf (@var{data}, @var{x}, @var{Fx}) ## ## Piecewise linear probability density function (PDF). ## ## For each element of @var{data}, compute the probability density function ## (PDF) of the piecewise linear distribution with a vector of @var{x} values at ## which the CDF changes slope and a vector of CDF values @var{Fx} that ## correspond to each value in @var{x}. Both @var{x} and @var{Fx} must be ## vectors of the same size and at least 2-elements long. The size of @var{p} ## is the same as @var{data}. ## ## Further information about the piecewise linear distribution can be found at ## @url{https://en.wikipedia.org/wiki/Piecewise_linear_function} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## MATLAB also accepts integer input here, returning the result in the integer ## class of the input; Octave rejects it, as it does for every other continuous ## distribution. ## ## @seealso{plcdf, plinv, plrnd, plstat} ## @end deftypefn function y = plpdf (data, x, Fx) ## Check for valid number of input arguments if (nargin < 3) error ("plpdf: function called with too few input arguments."); endif ## Check for common size of X and FX if (! isvector (x) || ! isvector (Fx) || ! isequal (size (x), size (Fx))) error ("plpdf: X and FX must be vectors of equal size."); endif ## Check for X and FX being at least 2-elements long if (length (x) < 2 || length (Fx) < 2) error ("plpdf: X and FX must be at least two-elements long."); endif ## Check for Fx being bounded in [0, 1] if (any (Fx < 0) || any (Fx > 1)) error ("plpdf: FX must be bounded in the range [0, 1]."); endif ## Check for DATA, X, and FX being double or single if (! (isfloat (data) && isfloat (x) && isfloat (Fx))) error ("plpdf: DATA, X, and FX must be double or single."); endif ## Check for DATA, X, and FX being reals if (iscomplex (data) || iscomplex (x) || iscomplex (Fx)) error ("plpdf: DATA, X, and FX must not be complex."); endif ## Force DATA, X, and FX into row vectors data = data(:)'; x = x(:)'; Fx = Fx(:)'; ## Check for class type if (isa (data, 'single') || isa (x, 'single') || isa (Fx, 'single')); y = zeros (size (data), 'single'); else y = zeros (size (data)); endif ## Bin data according to X [~, bin] = histc (data, [-Inf, x, Inf]); ## Compute piecewise densities dense = diff (Fx) ./ diff (x); bin_d = [0, dense, 0]; ## Fix densities xlen = length (x); bin(bin > xlen) = xlen + 1; y(bin>0) = bin_d(bin(bin>0)); ## Force invalid data to NaN y(isnan (data)) = NaN; endfunction %!demo %! ## Plot various PDFs from the Piecewise linear distribution %! data = 0:0.01:10; %! x1 = [0, 1, 3, 4, 7, 10]; %! Fx1 = [0, 0.2, 0.5, 0.6, 0.7, 1]; %! x2 = [0, 2, 5, 6, 7, 8]; %! Fx2 = [0, 0.1, 0.3, 0.6, 0.9, 1]; %! y1 = plpdf (data, x1, Fx1); %! y2 = plpdf (data, x2, Fx2); %! plot (data, y1, '-b', data, y2, 'g') %! grid on %! ylim ([0, 0.6]) %! xlim ([0, 10]) %! legend ({'x1, Fx1', 'x2, Fx2'}, 'location', 'northeast') %! title ('Piecewise linear CDF') %! xlabel ('values in data') %! ylabel ('density') ## Test output %!shared x, Fx %! x = [0, 1, 3, 4, 7, 10]; %! Fx = [0, 0.2, 0.5, 0.6, 0.7, 1]; %!assert_equal (plpdf (0.5, x, Fx), 0.2, eps); %!assert_equal (plpdf (1.5, x, Fx), 0.15, eps); %!assert_equal (plpdf (3.5, x, Fx), 0.1, eps); %!assert_equal (plpdf (5, x, Fx), 0.1/3, eps); %!assert_equal (plpdf (8, x, Fx), 0.1, eps); ## Test input validation %!error plpdf () %!error plpdf (1) %!error plpdf (1, 2) %!error ... %! plpdf (1, [0, 1, 2], [0, 1]) %!error ... %! plpdf (1, [0], [1]) %!error ... %! plpdf (1, [0, 1, 2], [0, 1, 1.5]) %!error ... %! plpdf (1, [0, 1, 2], [0, i, 1]) %!error ... %! plpdf (int32 (2), [0, 1, 2], [0, 0.5, 1]) %!error ... %! plpdf (true, [0, 1, 2], [0, 0.5, 1]) %!error ... %! plpdf ('a', [0, 1, 2], [0, 0.5, 1]) %!error ... %! plpdf (i, [0, 1, 2], [0, 0.5, 1]) %!error ... %! plpdf (1, [0, i, 2], [0, 0.5, 1]) %!error ... %! plpdf (1, [0, 1, 2], [0, 0.5i, 1]) statistics-release-1.9.2/inst/Distribution_Functions/plrnd.m000066400000000000000000000142471524624707500243400ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} plrnd (@var{x}, @var{Fx}) ## @deftypefnx {statistics} {@var{r} =} plrnd (@var{x}, @var{Fx}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} plrnd (@var{x}, @var{Fx}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} plrnd (@var{x}, @var{Fx}, [@var{sz}]) ## ## Random arrays from the piecewise linear distribution. ## ## @code{@var{r} = plrnd (@var{x}, @var{Fx})} returns a random number chosen ## from the piecewise linear distribution with a vector of @var{x} values at ## which the CDF changes slope and a vector of CDF values @var{Fx} that ## correspond to each value in @var{x}. Both @var{x} and @var{Fx} must be ## vectors of the same size and at least 2-elements long. ## ## When called with a single size argument, @code{plrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the piecewise linear distribution can be found at ## @url{https://en.wikipedia.org/wiki/Piecewise_linear_function} ## ## @seealso{plcdf, plinv, plpdf, plstat} ## @end deftypefn function r = plrnd (x, Fx, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("plrnd: function called with too few input arguments."); endif ## Check for common size of X and FX if (! isvector (x) || ! isvector (Fx) || ! isequal (size (x), size (Fx))) error ("plrnd: X and FX must be vectors of equal size."); endif ## Check for X and FX being at least 2-elements long if (length (x) < 2 || length (Fx) < 2) error ("plrnd: X and FX must be at least two-elements long."); endif ## Check for Fx being bounded in [0, 1] if (any (Fx < 0) || any (Fx > 1)) error ("plrnd: FX must be bounded in the range [0, 1]."); endif ## Check for X and FX being reals if (iscomplex (x) || iscomplex (Fx)) error ("plrnd: X and FX must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = 1; elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("plrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("plrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Force X and FX into row vectors x = x(:)'; Fx = Fx(:)'; ## Check for class type if (isa (x, 'single') || isa (Fx, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from the piecewise linear distribution u = rand (sz); r = zeros (sz); [~, bin] = histc (u(:)', Fx); r0 = x(bin); dx = diff (x); dF = diff (Fx); dr = (u(:)' - Fx(bin)) .* dx(bin) ./ dF(bin); r(:) = r0 + dr; ## Cast to appropriate class r = cast (r, cls); endfunction ## Test output %!shared x, Fx %! x = [0, 1, 3, 4, 7, 10]; %! Fx = [0, 0.2, 0.5, 0.6, 0.7, 1]; %!assert_equal (size (plrnd (x, Fx)), [1, 1]) %!assert_equal (size (plrnd (x, Fx, 3)), [3, 3]) %!assert_equal (size (plrnd (x, Fx, [4, 1])), [4, 1]) %!assert_equal (size (plrnd (x, Fx, 4, 1)), [4, 1]) %!assert_equal (size (plrnd (x, Fx, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (plrnd (x, Fx, 0, 1)), [0, 1]) %!assert_equal (size (plrnd (x, Fx, 1, 0)), [1, 0]) %!assert_equal (size (plrnd (x, Fx, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (plrnd (x, Fx, [])), [0, 0]) %!assert_equal (size (plrnd (x, Fx, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (plrnd (x, Fx, -1)), [0, 0]) %!assert_equal (size (plrnd (x, Fx, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (plrnd (x, Fx, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (plrnd (x, Fx)), "double") %!assert_equal (class (plrnd (x, single (Fx))), "single") %!assert_equal (class (plrnd (single (x), Fx)), "single") ## Test input validation %!error plrnd () %!error plrnd (1) %!error ... %! plrnd ([0, 1, 2], [0, 1]) %!error ... %! plrnd ([0], [1]) %!error ... %! plrnd ([0, 1, 2], [0, 1, 1.5]) %!error ... %! plrnd ([0, 1, 2], [0, i, 1]) %!error ... %! plrnd ([0, i, 2], [0, 0.5, 1]) %!error ... %! plrnd ([0, i, 2], [0, 0.5i, 1]) %!error ... %! plrnd (x, Fx, 1.2) %!error ... %! plrnd (x, Fx, ones (2)) %!error ... %! plrnd (x, Fx, [2 0 2.5]) %!error ... %! plrnd (x, Fx, 2, 1.5, 5) statistics-release-1.9.2/inst/Distribution_Functions/poisscdf.m000066400000000000000000000143751524624707500250350ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} poisscdf (@var{x}, @var{lambda}) ## @deftypefnx {statistics} {@var{p} =} poisscdf (@var{x}, @var{lambda}, @qcode{'upper'}) ## ## Poisson cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the Poisson distribution with rate parameter @var{lambda}. The ## size of @var{p} is the common size of @var{x} and @var{lambda}. A scalar ## input functions as a constant matrix of the same size as the other inputs. ## ## @code{@var{p} = poisscdf (@var{x}, @var{lambda}, "upper")} computes the ## upper tail probability of the Poisson distribution with parameter ## @var{lambda}, at the values in @var{x}. ## ## Further information about the Poisson distribution can be found at ## @url{https://en.wikipedia.org/wiki/Poisson_distribution} ## ## Input arguments must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted to ## @qcode{double}, so the result is always a probability. MATLAB is ## inconsistent here: for several of the discrete distributions it returns the ## result in the integer class of the input, truncating a probability to ## @math{0} or @math{1}. ## ## @seealso{poissinv, poisspdf, poissrnd, poissfit, poisslike, poisstat} ## @end deftypefn function p = poisscdf (x, lambda, uflag) ## Check for valid number of input arguments if (nargin < 2) error ("poisscdf: function called with too few input arguments."); endif ## Check for "upper" flag if (nargin == 3 && strcmpi (uflag, 'upper')) uflag = true; elseif (nargin == 3 && ! strcmpi (uflag, 'upper')) error ("poisscdf: invalid argument for upper tail."); else uflag = false; endif ## Check for common size of X and LAMBDA if (! isscalar (x) || ! isscalar (lambda)) [retval, x, lambda] = common_size (x, lambda); if (retval > 0) error ("poisscdf: X and LAMBDA must be of common size or scalars."); endif endif ## Check for X and LAMBDA being double, single, or integer if (! (isnumeric (x) && isnumeric (lambda))) error ("poisscdf: X and LAMBDA must be double, single, or integer."); endif ## Integer input is promoted to double, so the result is a probability ## rather than a value truncated to the input's integer type. if (isinteger (x)) x = double (x); endif if (isinteger (lambda)) lambda = double (lambda); endif ## Check for X and LAMBDA being reals if (iscomplex (x) || iscomplex (lambda)) error ("poisscdf: X and LAMBDA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (lambda, 'single')) p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Force NaN for out of range parameters or missing data NaN is_nan = isnan (x) | isnan (lambda) | (lambda < 0) ... | (isinf (x) & isinf (lambda)); p(is_nan) = NaN; ## Compute P for X >= 0 x = floor (x); k = x >= 0 & ! is_nan & isfinite (lambda); ## Return 1 for positive infinite values of X, unless "upper" is given: p = 0 k1 = isinf (x) & lambda > 0 & isfinite (lambda); if (any (k1)) if (uflag) p(k1) = 0; else p(k1) = 1; endif endif ## Return 1 when X < 0 and "upper" is given k1 = x < 0 & lambda > 0 & isfinite (lambda); if (any (k1)) if (uflag) p(k1) = 1; endif endif ## Compute Poisson CDF for remaining cases x = x(k); lambda = lambda(k); if (uflag) p(k) = gammainc (lambda, x + 1); else p(k) = gammainc (lambda, x + 1, 'upper'); endif endfunction %!demo %! ## Plot various CDFs from the Poisson distribution %! x = 0:20; %! p1 = poisscdf (x, 1); %! p2 = poisscdf (x, 4); %! p3 = poisscdf (x, 10); %! plot (x, p1, '*b', x, p2, '*g', x, p3, '*r') %! grid on %! ylim ([0, 1]) %! legend ({'λ = 1', 'λ = 4', 'λ = 10'}, 'location', 'southeast') %! title ('Poisson CDF') %! xlabel ('values in x (number of occurrences)') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-1 0 1 2 Inf]; %! y = [0, gammainc(1, (x(2:4) +1), 'upper'), 1]; %!assert_equal (poisscdf (x, ones (1,5)), y) %!assert_equal (poisscdf (x, 1), y) %!assert_equal (poisscdf (x, [1 0 NaN 1 1]), [y(1) 1 NaN y(4:5)]) %!assert_equal (poisscdf ([x(1:2) NaN Inf x(5)], 1), [y(1:2) NaN 1 y(5)]) ## Test class of input preserved %!assert_equal (poisscdf ([x, NaN], 1), [y, NaN]) %!assert_equal (poisscdf (single ([x, NaN]), 1), single ([y, NaN]), eps ('single')) %!assert_equal (poisscdf ([x, NaN], single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error poisscdf () %!error poisscdf (1) %!error poisscdf (1, 2, 3) %!error poisscdf (1, 2, 'tail') %!error ... %! poisscdf (ones (3), ones (2)) %!error ... %! poisscdf (ones (2), ones (3)) %!error poisscdf (true, 2) %!error poisscdf ('a', 2) %!assert_equal (class (poisscdf (int32 (2), 2)), 'double') %!error poisscdf (i, 2) %!error poisscdf (2, i) statistics-release-1.9.2/inst/Distribution_Functions/poissinv.m000066400000000000000000000201601524624707500250620ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 2014 Mike Giles ## Copyright (C) 2016 Lachlan Andrew ## Copyright (C) 1995-2017 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} poissinv (@var{p}, @var{lambda}) ## ## Inverse of the Poisson cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the Poisson distribution with rate parameter @var{lambda}. The size of ## @var{x} is the common size of @var{p} and @var{lambda}. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## Further information about the Poisson distribution can be found at ## @url{https://en.wikipedia.org/wiki/Poisson_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{poisscdf, poisspdf, poissrnd, poissfit, poisslike, poisstat} ## @end deftypefn function x = poissinv (p, lambda) ## Check for valid number of input arguments if (nargin < 2) error ("poissinv: function called with too few input arguments."); endif ## Check for common size of P and LAMBDA if (! isscalar (p) || ! isscalar (lambda)) [retval, p, lambda] = common_size (p, lambda); if (retval > 0) error ("poissinv: P and LAMBDA must be of common size or scalars."); endif endif ## Check for P and LAMBDA being double or single if (! (isfloat (p) && isfloat (lambda))) error ("poissinv: P and LAMBDA must be double or single."); endif ## Check for P and LAMBDA being reals if (iscomplex (p) || iscomplex (lambda)) error ("poissinv: P and LAMBDA must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (lambda, 'single')) x = zeros (size (p), 'single'); else x = zeros (size (p)); endif ## Force NaN for out of range parameters or p-values k = (p < 0) | (p > 1) | isnan (p) | ! (lambda > 0); x(k) = NaN; k = (p == 1) & (lambda > 0); x(k) = Inf; k = (p > 0) & (p < 1) & (lambda > 0); if (any (k(:))) limit = 20; # After 'limit' iterations, use approx if (isscalar (lambda)) cdf = [(cumsum (poisspdf (0:limit-1,lambda))), 2]; y = p(:); # force to column r = bsxfun (@le, y(k), cdf); [~, x(k)] = max (r, [], 2); # find first instance of p <= cdf x(k) -= 1; else kk = find (k); cdf = exp (-lambda(kk)); for i = 1:limit m = find (cdf < p(kk)); if (isempty (m)) break; else x(kk(m)) += 1; cdf(m) += poisspdf (i, lambda(kk(m))); endif endfor endif ## Use Mike Giles's magic when x isn't < limit k &= (x == limit); if (any (k(:))) if (isscalar (lambda)) lam = repmat (lambda, size (p)); else lam = lambda; endif x(k) = analytic_approx (p(k), lam(k)); endif endif endfunction ## The following is based on Mike Giles's CUDA implementation, ## [http://people.maths.ox.ac.uk/gilesm/codes/poissinv/poissinv_cuda.h] ## which is copyright by the University of Oxford ## and is provided under the terms of the GNU GPLv3 license: ## http://www.gnu.org/licenses/gpl.html function x = analytic_approx (p, lambda) s = norminv (p, 0, 1) ./ sqrt (lambda); k = (s > -0.6833501) & (s < 1.777993); ## use polynomial approximations in central region if (any (k)) lam = lambda(k); if (isscalar (s)) sk = s; else sk = s(k); endif ## polynomial approximation to f^{-1}(s) - 1 rm = 2.82298751e-07; rm = -2.58136133e-06 + rm.*sk; rm = 1.02118025e-05 + rm.*sk; rm = -2.37996199e-05 + rm.*sk; rm = 4.05347462e-05 + rm.*sk; rm = -6.63730967e-05 + rm.*sk; rm = 0.000124762566 + rm.*sk; rm = -0.000256970731 + rm.*sk; rm = 0.000558953132 + rm.*sk; rm = -0.00133129194 + rm.*sk; rm = 0.00370367937 + rm.*sk; rm = -0.0138888706 + rm.*sk; rm = 0.166666667 + rm.*sk; rm = sk + sk.*(rm.*sk); ## polynomial approximation to correction c0(r) t = 1.86386867e-05; t = -0.000207319499 + t.*rm; t = 0.0009689451 + t.*rm; t = -0.00247340054 + t.*rm; t = 0.00379952985 + t.*rm; t = -0.00386717047 + t.*rm; t = 0.00346960934 + t.*rm; t = -0.00414125511 + t.*rm; t = 0.00586752093 + t.*rm; t = -0.00838583787 + t.*rm; t = 0.0132793933 + t.*rm; t = -0.027775536 + t.*rm; t = 0.333333333 + t.*rm; ## O(1/lam) correction y = -0.00014585224; y = 0.00146121529 + y.*rm; y = -0.00610328845 + y.*rm; y = 0.0138117964 + y.*rm; y = -0.0186988746 + y.*rm; y = 0.0168155118 + y.*rm; y = -0.013394797 + y.*rm; y = 0.0135698573 + y.*rm; y = -0.0155377333 + y.*rm; y = 0.0174065334 + y.*rm; y = -0.0198011178 + y.*rm; y ./= lam; x(k) = floor (lam + (y+t)+lam.*rm); endif k = ! k & (s > -sqrt (2)); if (any (k)) ## Newton iteration r = 1 + s(k); r2 = r + 1; while (any (abs (r - r2) > 1e-5)) t = log (r); r2 = r; s2 = sqrt (2 * ((1-r) + r.*t)); s2(r<1) *= -1; r = r2 - (s2 - s(k)) .* s2 ./ t; if (r < 0.1 * r2) r = 0.1 * r2; endif endwhile t = log (r); y = lambda(k) .* r + log (sqrt (2*r.*((1-r) + r.*t)) ./ abs (r-1)) ./ t; x(k) = floor (y - 0.0218 ./ (y + 0.065 * lambda(k))); endif endfunction %!demo %! ## Plot various iCDFs from the Poisson distribution %! p = 0.001:0.001:0.999; %! x1 = poissinv (p, 13); %! x2 = poissinv (p, 4); %! x3 = poissinv (p, 10); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r') %! grid on %! ylim ([0, 20]) %! legend ({'λ = 1', 'λ = 4', 'λ = 10'}, 'location', 'northwest') %! title ('Poisson iCDF') %! xlabel ('probability') %! ylabel ('values in x (number of occurrences)') ## Test output %!shared p %! p = [-1 0 0.5 1 2]; %!assert_equal (poissinv (p, ones (1,5)), [NaN 0 1 Inf NaN]) %!assert_equal (poissinv (p, 1), [NaN 0 1 Inf NaN]) %!assert_equal (poissinv (p, [1 0 NaN 1 1]), [NaN NaN NaN Inf NaN]) %!assert_equal (poissinv ([p(1:2) NaN p(4:5)], 1), [NaN 0 NaN Inf NaN]) ## Test class of input preserved %!assert_equal (poissinv ([p, NaN], 1), [NaN 0 1 Inf NaN NaN]) %!assert_equal (poissinv (single ([p, NaN]), 1), single ([NaN 0 1 Inf NaN NaN])) %!assert_equal (poissinv ([p, NaN], single (1)), single ([NaN 0 1 Inf NaN NaN])) ## Test input validation %!error poissinv () %!error poissinv (1) %!error ... %! poissinv (ones (3), ones (2)) %!error ... %! poissinv (ones (2), ones (3)) %!error poissinv (int32 (2), 2) %!error poissinv (true, 2) %!error poissinv ('a', 2) %!error poissinv (i, 2) %!error poissinv (2, i) statistics-release-1.9.2/inst/Distribution_Functions/poisspdf.m000066400000000000000000000125561524624707500250510ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} poisspdf (@var{x}, @var{lambda}) ## ## Poisson probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the Poisson distribution with rate parameter @var{lambda}. The size of ## @var{y} is the common size of @var{x} and @var{lambda}. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## Further information about the Poisson distribution can be found at ## @url{https://en.wikipedia.org/wiki/Poisson_distribution} ## ## Input arguments must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted to ## @qcode{double}, so the result is always a probability. MATLAB is ## inconsistent here: for several of the discrete distributions it returns the ## result in the integer class of the input, truncating a probability to ## @math{0} or @math{1}. ## ## ## The density at an infinite abscissa is @math{0}, no proper distribution ## placing mass there. MATLAB returns @qcode{NaN} here, as it does for ## @code{raylpdf} and for no other density, which is an inconsistency there rather ## than a convention: it returns @math{0} at @math{Inf} for every other ## distribution of the same support. ## @seealso{poisscdf, poissinv, poissrnd, poissfit, poisslike, poisstat} ## @end deftypefn function y = poisspdf (x, lambda) ## Check for valid number of input arguments if (nargin < 2) error ("poisspdf: function called with too few input arguments."); endif ## Check for common size of X and LAMBDA if (! isscalar (x) || ! isscalar (lambda)) [retval, x, lambda] = common_size (x, lambda); if (retval > 0) error ("poisspdf: X and LAMBDA must be of common size or scalars."); endif endif ## Check for X and LAMBDA being double, single, or integer if (! (isnumeric (x) && isnumeric (lambda))) error ("poisspdf: X and LAMBDA must be double, single, or integer."); endif ## Integer input is promoted to double, so the result is a probability ## rather than a value truncated to the input's integer type. if (isinteger (x)) x = double (x); endif if (isinteger (lambda)) lambda = double (lambda); endif ## Check for X and LAMBDA being reals if (iscomplex (x) || iscomplex (lambda)) error ("poisspdf: X and LAMBDA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (lambda, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Force NaN for out of range parameters or missing data NaN k = isnan (x) | ! (lambda > 0); y(k) = NaN; k = (x >= 0) & (x < Inf) & (x == fix (x)) & (lambda > 0); if (isscalar (lambda)) y(k) = exp (x(k) * log (lambda) - lambda - gammaln (x(k) + 1)); else y(k) = exp (x(k) .* log (lambda(k)) - lambda(k) - gammaln (x(k) + 1)); endif endfunction %!demo %! ## Plot various PDFs from the Poisson distribution %! x = 0:20; %! y1 = poisspdf (x, 1); %! y2 = poisspdf (x, 4); %! y3 = poisspdf (x, 10); %! plot (x, y1, '*b', x, y2, '*g', x, y3, '*r') %! grid on %! ylim ([0, 0.4]) %! legend ({'λ = 1', 'λ = 4', 'λ = 10'}, 'location', 'northeast') %! title ('Poisson PDF') %! xlabel ('values in x (number of occurrences)') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1 0 1 2 Inf]; %! y = [0, exp(-1)*[1 1 0.5], 0]; %!assert_equal (poisspdf (x, ones (1,5)), y, eps) %!assert_equal (poisspdf (x, 1), y, eps) %!assert_equal (poisspdf (x, [1 0 NaN 1 1]), [y(1) NaN NaN y(4:5)], eps) %!assert_equal (poisspdf ([x, NaN], 1), [y, NaN], eps) ## Test class of input preserved %!assert_equal (poisspdf (single ([x, NaN]), 1), single ([y, NaN]), eps ('single')) %!assert_equal (poisspdf ([x, NaN], single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error poisspdf () %!error poisspdf (1) %!error ... %! poisspdf (ones (3), ones (2)) %!error ... %! poisspdf (ones (2), ones (3)) %!error poisspdf (true, 2) %!error poisspdf ('a', 2) %!assert_equal (class (poisspdf (int32 (2), 2)), 'double') %!error poisspdf (i, 2) %!error poisspdf (2, i) statistics-release-1.9.2/inst/Distribution_Functions/poissrnd.m000066400000000000000000000140201524624707500250470ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} poissrnd (@var{lambda}) ## @deftypefnx {statistics} {@var{r} =} poissrnd (@var{lambda}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} poissrnd (@var{lambda}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} poissrnd (@var{lambda}, [@var{sz}]) ## ## Random arrays from the Poisson distribution. ## ## @code{@var{r} = normrnd (@var{lambda})} returns an array of random numbers ## chosen from the Poisson distribution with rate parameter @var{lambda}. The ## size of @var{r} is the common size of @var{lambda}. A scalar input functions ## as a constant matrix of the same size as the other inputs. @var{lambda} must ## be a finite real number and greater or equal to 0, otherwise @qcode{NaN} is ## returned. ## ## When called with a single size argument, @code{poissrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the Poisson distribution can be found at ## @url{https://en.wikipedia.org/wiki/Poisson_distribution} ## ## @seealso{poisscdf, poissinv, poisspdf, poissfit, poisslike, poisstat} ## @end deftypefn function r = poissrnd (lambda, varargin) ## Check for valid number of input arguments if (nargin < 1) error ("poissrnd: function called with too few input arguments."); endif ## Check for LAMBDA being real if (iscomplex (lambda)) error ("poissrnd: LAMBDA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 1) sz = size (lambda); elseif (nargin == 2) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("poissrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 2) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("poissrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (lambda) && ! isequal (size (lambda), size (ones (sz)))) error ("poissrnd: LAMBDA must be scalar or of size SZ."); endif ## Check for class type if (isa (lambda, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from Poisson distribution if (isscalar (lambda)) if (lambda >= 0 && lambda < Inf) r = randp (lambda, sz, cls); else r = NaN (sz, cls); endif else r = NaN (sz, cls); k = (lambda >= 0) & (lambda < Inf); r(k) = randp (lambda(k), cls); endif endfunction ## Test output %!assert_equal (size (poissrnd (2)), [1, 1]) %!assert_equal (size (poissrnd (ones (2, 1))), [2, 1]) %!assert_equal (size (poissrnd (ones (2, 2))), [2, 2]) %!assert_equal (size (poissrnd (1, 3)), [3, 3]) %!assert_equal (size (poissrnd (1, [4, 1])), [4, 1]) %!assert_equal (size (poissrnd (1, 4, 1)), [4, 1]) %!assert_equal (size (poissrnd (1, 4, 1)), [4, 1]) %!assert_equal (size (poissrnd (1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (poissrnd (1, 0, 1)), [0, 1]) %!assert_equal (size (poissrnd (1, 1, 0)), [1, 0]) %!assert_equal (size (poissrnd (1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (poissrnd (1, [])), [0, 0]) %!assert_equal (size (poissrnd (1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (poissrnd (1, -1)), [0, 0]) %!assert_equal (size (poissrnd (1, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (poissrnd (1, 2, -1, 5)), [2, 0, 5]) %!assert_equal (poissrnd (0, 1, 1), 0) %!assert_equal (poissrnd ([0, 0, 0], [1, 3]), [0 0 0]) ## Test class of input preserved %!assert_equal (class (poissrnd (2)), "double") %!assert_equal (class (poissrnd (single (2))), "single") %!assert_equal (class (poissrnd (single ([2 2]))), "single") ## Test input validation %!error poissrnd () %!error poissrnd (i) %!error ... %! poissrnd (1, 1.2) %!error ... %! poissrnd (1, ones (2)) %!error ... %! poissrnd (1, [2 0 2.5]) %!error ... %! poissrnd (ones (2), ones (2)) %!error ... %! poissrnd (1, 2, 1.5, 5) %!error poissrnd (ones (2,2), 3) %!error poissrnd (ones (2,2), [3, 2]) %!error poissrnd (ones (2,2), 2, 3) statistics-release-1.9.2/inst/Distribution_Functions/private/000077500000000000000000000000001524624707500245055ustar00rootroot00000000000000statistics-release-1.9.2/inst/Distribution_Functions/private/__mvtcdfqmc__.m000066400000000000000000000214711524624707500274500ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{p} =} __mvtcdfqmc__ (@var{fname}, @var{A}, @var{B}, @var{Rho}, @var{df}) ## @deftypefnx {Private Function} {@var{p} =} __mvtcdfqmc__ (@dots{}, @var{TolFun}) ## @deftypefnx {Private Function} {@var{p} =} __mvtcdfqmc__ (@dots{}, @var{TolFun}, @var{MaxFunEvals}) ## @deftypefnx {Private Function} {@var{p} =} __mvtcdfqmc__ (@dots{}, @var{TolFun}, @var{MaxFunEvals}, @var{Display}) ## @deftypefnx {Private Function} {[@var{p}, @var{err}] =} __mvtcdfqmc__ (@dots{}) ## @deftypefnx {Private Function} {[@var{p}, @var{err}, @var{FunEvals}] =} __mvtcdfqmc__ (@dots{}) ## ## Quasi-Monte-Carlo computation of the multivariate Student's T CDF. ## ## The QMC multivariate Student's t distribution is evaluated between the lower ## limit @var{A} and upper limit @var{B} of the hyper-rectangle with a ## correlation matrix @var{Rho} and degrees of freedom @var{df}. ## ## @var{fname} is the name of the calling function, which prefixes every error ## and warning this helper emits so that a user sees the function they called ## rather than this private one. ## ## @multitable @columnfractions 0.2 0.8 ## @item "TolFun" @tab --- Maximum absolute error tolerance. Default is 1e-4. ## @item "MaxFunEvals" @tab --- Maximum number of integrand evaluations. ## Default is 1e7 for D > 4. ## @item "Display" @tab --- Display options. Choices are "off" (default), ## "iter", which shows the probability and estimated error at each repetition, ## and "final", which shows the final probability and related error after the ## integrand has converged successfully. ## @end multitable ## ## @code{[@var{p}, @var{err}, @var{FunEvals}] = __mvtcdfqmc__ (@dots{})} returns ## the estimated probability, @var{p}, an estimate of the error, @var{err}, and ## the number of iterations until a successful convergence is met, unless the ## value in @var{MaxFunEvals} was reached. ## ## @seealso{mvtcdf, mvtpdf, mvtrnd} ## @end deftypefn function [p, err, FunEvals] = __mvtcdfqmc__ (fname, A, B, Rho, df, varargin) ## Check for input arguments narginchk (5,8); ## Add defaults TolFun = 1e-4; MaxFunEvals = 1e7; Display = 'off'; ## Parse optional arguments (TolFun, MaxFunEvals, Display) if (nargin > 5) TolFun = varargin{1}; if (! isscalar (TolFun) || ! isreal (TolFun)) error ("%s: TolFun must be a scalar.", fname); endif endif if (nargin > 6) MaxFunEvals = varargin{2}; if (! isscalar (MaxFunEvals) || ! isreal (MaxFunEvals)) error ("%s: MaxFunEvals must be a scalar.", fname); endif MaxFunEvals = floor (MaxFunEvals); endif if (nargin > 7) Display = varargin{3}; DispOptions = {'off', 'final', 'iter'}; if (sum (any (strcmpi (Display, DispOptions))) == 0) error ("%s: invalid value for 'Display' argument.", fname); endif endif ## Check if input is single or double class is_type = 'double'; if (isa (A, 'single') || isa (B, 'single') || isa (Rho, 'single')) is_type = 'single'; endif ## Check for appropriate lower upper limits and NaN values in data if (! all (A < B)) if (any (A > B)) error ("%s: inconsistent lower upper limits.", fname); elseif (any (isnan (A) | isnan (B))) warning ("%s: NaNs in data.", fname); p = NaN (is_type); err = NaN (is_type); else warning ("%s: zero distance between lower upper limits.", fname); p = zeros (is_type); err = zeros (is_type); endif FunEvals = 0; return; endif ## Ignore dimensions with infinite limits InfLim_idx = (A == -Inf) & (B == Inf); if (any (InfLim_idx)) if (all (InfLim_idx)) warning ("%s: infinite distance between lower upper limits.", fname); p = 1; err = 0; FunEvals = 0; return endif A(InfLim_idx) = []; B(InfLim_idx) = []; Rho(:,InfLim_idx) = []; Rho(InfLim_idx,:) = []; endif ## Get size of covariance matrix m = size (Rho, 1); ## Sort the order of integration according to increasing length of interval [~, ord] = sort (B - A); A = A(ord); B = B(ord); Rho = Rho(ord, ord); ## Check for highly correlated covariance matrix if any (any (abs (tril (Rho,-1)) > .999)) warning ("%s: highly correlated covariance matrix Rho.", fname); endif ## Scale the integration limits and the Cholesky factor of Rho C = chol (Rho); c = diag (C); A = A(:) ./ c; B = B(:) ./ c; C = C ./ repmat (c',m,1); ## Set repetitions fof Monte Carlo MCreps = 25; MCdims = m - isinf (df); ## Set initial output p = zeros (is_type); sigsq = Inf (is_type); FunEvals = 0; err = NaN; ## Initialize vector P = [31, 47, 73, 113, 173, 263, 397, 593, 907, 1361, 2053, 3079, 4621, ... 6947, 10427, 15641, 23473, 35221, 52837, 79259, 118891, 178349, ... 267523, 401287, 601942, 902933, 1354471, 2031713]; for i = 5:length (P); if ((FunEvals + 2*MCreps*P(i)) > MaxFunEvals) break; endif ## Compute the Niederreiter point set generator NRgen = 2 .^ ((1:MCdims) / (MCdims + 1)); ## Compute randomized quasi-Monte Carlo estimate with P points [THat,sigsqTHat] = estimate_mvtqmc (MCreps, P(i), NRgen, C, df, ... A, B, is_type); FunEvals = FunEvals + 2 * MCreps *P(i); ## Recursively update the estimate and the error estimate p = p + (THat - p) ./ (1 + sigsqTHat ./ sigsq); sigsq = sigsqTHat ./ (1 + sigsqTHat ./ sigsq); ## Compute a conservative estimate of error err = 3.5 * sqrt (sigsq); ## Display output for every iteration if (strcmpi (Display, 'iter')) printf ("%s: Probability estimate: %0.4f ", fname, p); printf ("Error estimate: %0.4e Iterations: %d\n", err, FunEvals); endif if (err < TolFun) if (strcmpi (Display, 'final')) printf ("%s: Successfully converged!\n", fname); printf ("Final probability estimate: %0.4f ",p); printf ("Final error estimate: %0.4e Iterations: %d\n", err, FunEvals); endif return endif endfor warning ("%s: Error tolerance did NOT converge!", fname); printf ("Error tolerance: %0.4f Total Iterations: %d\n", TolFun, MaxFunEvals); endfunction ## Randomized Quasi-Monte-Carlo estimate of the integral function [THat, sigsqTHat] = estimate_mvtqmc (MCreps, P, NRgen, C, df, A, ... B, is_type) qq = (1:P)' * NRgen; THat = zeros (MCreps,1,is_type); for rep = 1:MCreps ## Generate A new random lattice of P points. For MVT, this is in the ## m-dimensional unit hypercube, for MVN, in the (m-1)-dimensional unit ## hypercube. w = abs (2 * mod (qq + repmat (rand (size (NRgen), is_type), P, 1), 1) - 1); ## Compute the mean of the integrand over all P of the points, and all P ## of the antithetic points. THat(rep) = (F_qrsvn (A, B, C, df, w) + F_qrsvn (A, B, C, df, 1 - w)) ./ 2; endfor ## Return the MC mean and se^2 sigsqTHat = var (THat) ./ MCreps; THat = mean (THat); endfunction ## Integrand for computation of MVT probabilities function TBar = F_qrsvn (A, B, C, df, w) N = size (w, 1); # number of quasirandom points m = length (A); # number of dimensions if isinf (df) rho = 1; else rho = chi_inv (w(:,m), df) ./ sqrt (df); endif rA = norm_cdf (rho .* A(1)); # A is already scaled by diag(C) rB = norm_cdf (rho .* B(1)) - rA; # B is already scaled by diag(C) T = rB; Y = zeros (N, m, 'like', T); for i = 2:m z = min (max (rA + rB .* w(:,i-1), eps / 2), 1 - eps / 2); Y(:,i-1) = norm_inv (z); Ysum = Y * C(:,i); rA = norm_cdf (rho .* A(i) - Ysum); # A is already scaled by diag(C) rB = norm_cdf (rho .* B(i) - Ysum) - rA; # B is already scaled by diag(C) T = T .* rB; endfor TBar = sum (T, 1) ./ length (T); endfunction ## Normal cumulative distribution function function a = norm_cdf (b) a = 0.5 * erfc (- b ./ sqrt (2)); endfunction ## Inverse of normal cumulative distribution function function a = norm_inv (b) a = - sqrt (2) .* erfcinv (2 * b); endfunction ## Inverse of chi cumulative distribution function function a = chi_inv (b,df) a = sqrt (gammaincinv (b, df ./ 2) .* 2); endfunction statistics-release-1.9.2/inst/Distribution_Functions/private/__stable_cf__.m000066400000000000000000000033271524624707500274060ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{phi} =} __stable_cf__ (@var{t}, @var{alpha}, @var{beta}) ## ## Characteristic function of the standard stable distribution. ## ## Returns the characteristic function @var{phi} evaluated at the points @var{t} ## of the standard stable distribution @code{S(@var{alpha}, @var{beta}, 1, 0)} ## in the Nolan @qcode{S0} parameterization. This is a private helper for ## @code{stblpdf} and @code{stblcdf}, which recover the density and the ## cumulative probability by numerical inversion. ## ## @end deftypefn function phi = __stable_cf__ (t, alpha, beta) at = abs (t); st = sign (t); if (abs (alpha - 1) < eps) ## The |t|*log|t| term vanishes at the origin lt = at .* log (at); lt(at == 0) = 0; phi = exp (-at - 1i .* beta .* (2 ./ pi) .* st .* lt); else phi = exp (-at .^ alpha - 1i .* beta .* tan (pi .* alpha ./ 2) ... .* st .* (at - at .^ alpha)); endif endfunction statistics-release-1.9.2/inst/Distribution_Functions/private/__stable_checkparams__.m000066400000000000000000000035021524624707500312720ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{msg} =} __stable_checkparams__ (@var{alpha}, @var{beta}, @var{gam}, @var{delta}) ## ## Validate the parameters of a stable distribution. ## ## Returns an empty string if the tail index @var{alpha}, skewness @var{beta}, ## scale @var{gam}, and location @var{delta} are valid, otherwise a message body ## describing the first violation. The calling function prepends its own name. ## This is a private helper shared by the @code{stbl*} functions. ## ## @end deftypefn function msg = __stable_checkparams__ (alpha, beta, gam, delta) msg = ""; if (! (isscalar (alpha) && isreal (alpha) && alpha > 0 && alpha <= 2)) msg = "ALPHA must be a scalar in the range (0, 2]."; elseif (! (isscalar (beta) && isreal (beta) && beta >= -1 && beta <= 1)) msg = "BETA must be a scalar in the range [-1, 1]."; elseif (! (isscalar (gam) && isreal (gam) && gam > 0)) msg = "GAM must be a positive scalar."; elseif (! (isscalar (delta) && isreal (delta))) msg = "DELTA must be a real scalar."; endif endfunction statistics-release-1.9.2/inst/Distribution_Functions/raylcdf.m000066400000000000000000000130751524624707500246430ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} raylcdf (@var{x}, @var{sigma}) ## @deftypefnx {statistics} {@var{p} =} raylcdf (@var{x}, @var{sigma}, @qcode{'upper'}) ## ## Rayleigh cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the Rayleigh distribution with scale parameter @var{sigma}. The ## size of @var{p} is the common size of @var{x} and @var{sigma}. A scalar ## input functions as a constant matrix of the same size as the other inputs. ## ## @code{@var{p} = raylcdf (@var{x}, @var{sigma}, "upper")} computes the upper ## tail probability of the Rayleigh distribution with parameter @var{sigma}, at ## the values in @var{x}. ## ## Further information about the Rayleigh distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rayleigh_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## The @code{prob.RayleighDistribution} class names this same parameter ## @qcode{B}, after MATLAB. ## @seealso{raylinv, raylpdf, raylrnd, raylfit, rayllike, raylstat} ## @end deftypefn function p = raylcdf (x, sigma, uflag) ## Check for valid number of input arguments if (nargin < 2) error ("raylcdf: function called with too few input arguments."); endif ## Check for "upper" flag if (nargin == 3 && strcmpi (uflag, 'upper')) uflag = true; elseif (nargin == 3 && ! strcmpi (uflag, 'upper')) error ("raylcdf: invalid argument for upper tail."); else uflag = false; endif ## Check for common size of X and SIGMA if (! isscalar (x) || ! isscalar (sigma)) [retval, x, sigma] = common_size (x, sigma); if (retval > 0) error ("raylcdf: X and SIGMA must be of common size or scalars."); endif endif ## Check for X and SIGMA being double or single if (! (isfloat (x) && isfloat (sigma))) error ("raylcdf: X and SIGMA must be double or single."); endif ## Check for X and SIGMA being reals if (iscomplex (x) || iscomplex (sigma)) error ("raylcdf: X and SIGMA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (sigma, 'single')); p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Force 1 for upper flag and X <= 0 k0 = sigma > 0 & x <= 0; if (uflag && any (k0(:))) p(k0) = 1; endif ## Calculate Rayleigh CDF for valid parameter and data range k = sigma > 0 & x > 0; if (any (k(:))) if (uflag) p(k) = exp (-x(k) .^ 2 ./ (2 * sigma(k) .^ 2)); else p(k) = - expm1 (-x(k) .^ 2 ./ (2 * sigma(k) .^ 2)); endif endif ## Continue argument check p(! (k0 | k)) = NaN; endfunction %!demo %! ## Plot various CDFs from the Rayleigh distribution %! x = 0:0.01:10; %! p1 = raylcdf (x, 0.5); %! p2 = raylcdf (x, 1); %! p3 = raylcdf (x, 2); %! p4 = raylcdf (x, 3); %! p5 = raylcdf (x, 4); %! plot (x, p1, '-b', x, p2, 'g', x, p3, '-r', x, p4, '-m', x, p5, '-k') %! grid on %! ylim ([0, 1]) %! legend ({'σ = 0.5', 'σ = 1', 'σ = 2', ... %! 'σ = 3', 'σ = 4'}, 'location', 'southeast') %! title ('Rayleigh CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!test %! x = 0:0.5:2.5; %! sigma = 1:6; %! p = raylcdf (x, sigma); %! expected_p = [0.0000, 0.0308, 0.0540, 0.0679, 0.0769, 0.0831]; %! assert_equal (p, expected_p, 0.001); %!test %! x = 0:0.5:2.5; %! p = raylcdf (x, 0.5); %! expected_p = [0.0000, 0.3935, 0.8647, 0.9889, 0.9997, 1.0000]; %! assert_equal (p, expected_p, 0.001); %!shared x, p %! x = [-1, 0, 1, 2, Inf]; %! p = [0, 0, 0.39346934028737, 0.86466471676338, 1]; %!assert_equal (raylcdf (x, 1), p, 1e-14) %!assert_equal (raylcdf (x, 1, 'upper'), 1 - p, 1e-14) ## Test input validation %!error raylcdf () %!error raylcdf (1) %!error raylcdf (1, 2, 'uper') %!error raylcdf (1, 2, 3) %!error ... %! raylcdf (ones (3), ones (2)) %!error ... %! raylcdf (ones (2), ones (3)) %!error raylcdf (int32 (2), 2) %!error raylcdf (true, 2) %!error raylcdf ('a', 2) %!error raylcdf (i, 2) %!error raylcdf (2, i) statistics-release-1.9.2/inst/Distribution_Functions/raylinv.m000066400000000000000000000106761524624707500247070ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} raylinv (@var{p}, @var{sigma}) ## ## Inverse of the Rayleigh cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the Rayleigh distribution with scale parameter @var{sigma}. The size of ## @var{x} is the common size of @var{p} and @var{sigma}. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## Further information about the Rayleigh distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rayleigh_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## The @code{prob.RayleighDistribution} class names this same parameter ## @qcode{B}, after MATLAB. ## @seealso{raylcdf, raylpdf, raylrnd, raylfit, rayllike, raylstat} ## @end deftypefn function x = raylinv (p, sigma) ## Check for valid number of input arguments if (nargin < 2) error ("raylinv: function called with too few input arguments."); endif ## Check for common size of P and SIGMA if (! isscalar (p) || ! isscalar (sigma)) [retval, p, sigma] = common_size (p, sigma); if (retval > 0) error ("raylinv: P and SIGMA must be of common size or scalars."); endif endif ## Check for P and SIGMA being double or single if (! (isfloat (p) && isfloat (sigma))) error ("raylinv: P and SIGMA must be double or single."); endif ## Check for X and SIGMA being reals if (iscomplex (p) || iscomplex (sigma)) error ("raylinv: P and SIGMA must not be complex."); endif ## Calculate Rayleigh iCDF x = sqrt (-2 .* log (1 - p) .* sigma .^ 2); ## Check for valid parameter and support k = find (p == 1); if (any (k)) x(k) = Inf; endif k = find (! (p >= 0) | ! (p <= 1) | ! (sigma > 0)); if (any (k)) x(k) = NaN; endif endfunction %!demo %! ## Plot various iCDFs from the Rayleigh distribution %! p = 0.001:0.001:0.999; %! x1 = raylinv (p, 0.5); %! x2 = raylinv (p, 1); %! x3 = raylinv (p, 2); %! x4 = raylinv (p, 3); %! x5 = raylinv (p, 4); %! plot (p, x1, '-b', p, x2, 'g', p, x3, '-r', p, x4, '-m', p, x5, '-k') %! grid on %! ylim ([0, 10]) %! legend ({'σ = 0,5', 'σ = 1', 'σ = 2', ... %! 'σ = 3', 'σ = 4'}, 'location', 'northwest') %! title ('Rayleigh iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!test %! p = 0:0.1:0.5; %! sigma = 1:6; %! x = raylinv (p, sigma); %! expected_x = [0.0000, 0.9181, 2.0041, 3.3784, 5.0538, 7.0645]; %! assert_equal (x, expected_x, 0.001); %!test %! p = 0:0.1:0.5; %! x = raylinv (p, 0.5); %! expected_x = [0.0000, 0.2295, 0.3340, 0.4223, 0.5054, 0.5887]; %! assert_equal (x, expected_x, 0.001); ## Test input validation %!error raylinv () %!error raylinv (1) %!error ... %! raylinv (ones (3), ones (2)) %!error ... %! raylinv (ones (2), ones (3)) %!error raylinv (int32 (2), 2) %!error raylinv (true, 2) %!error raylinv ('a', 2) %!error raylinv (i, 2) %!error raylinv (2, i) statistics-release-1.9.2/inst/Distribution_Functions/raylpdf.m000066400000000000000000000117531524624707500246610ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} raylpdf (@var{x}, @var{sigma}) ## ## Rayleigh probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the Rayleigh distribution with scale parameter @var{sigma}. The size of ## @var{p} is the common size of @var{x} and @var{sigma}. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## Further information about the Rayleigh distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rayleigh_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## ## The density at an infinite abscissa is @math{0}, no proper distribution ## placing mass there. MATLAB returns @qcode{NaN} here, as it does for ## @code{poisspdf} and for no other density, which is an inconsistency there rather ## than a convention: it returns @math{0} at @math{Inf} for every other ## distribution of the same support. ## ## The @code{prob.RayleighDistribution} class names this same parameter ## @qcode{B}, after MATLAB. ## @seealso{raylcdf, raylinv, raylrnd, raylfit, rayllike, raylstat} ## @end deftypefn function y = raylpdf (x, sigma) ## Check for valid number of input arguments if (nargin < 2) error ("raylpdf: function called with too few input arguments."); endif ## Check for common size of X and SIGMA if (! isscalar (x) || ! isscalar (sigma)) [retval, x, sigma] = common_size (x, sigma); if (retval > 0) error ("raylpdf: X and SIGMA must be of common size or scalars."); endif endif ## Check for X and SIGMA being double or single if (! (isfloat (x) && isfloat (sigma))) error ("raylpdf: X and SIGMA must be double or single."); endif ## Check for X and SIGMA being reals if (iscomplex (x) || iscomplex (sigma)) error ("raylpdf: X and SIGMA must not be complex."); endif ## Calculate Rayleigh PDF y = x .* exp ((-x .^ 2) ./ (2 .* sigma .^ 2)) ./ (sigma .^ 2); ## Continue argument check k = find (! isfinite (x) | ! (sigma > 0)); if (any (k)) y(k) = NaN; endif k = x < 0; if (any (k)) y(k) = 0; endif ## The density at an infinite abscissa is zero: no proper distribution ## places mass there. MATLAB returns NaN, as it does for POISSPDF and ## nowhere else, which is an inconsistency there rather than a convention. y(isinf (x) & (sigma > 0)) = 0; endfunction %!demo %! ## Plot various PDFs from the Rayleigh distribution %! x = 0:0.01:10; %! y1 = raylpdf (x, 0.5); %! y2 = raylpdf (x, 1); %! y3 = raylpdf (x, 2); %! y4 = raylpdf (x, 3); %! y5 = raylpdf (x, 4); %! plot (x, y1, '-b', x, y2, 'g', x, y3, '-r', x, y4, '-m', x, y5, '-k') %! grid on %! ylim ([0, 1.25]) %! legend ({'σ = 0,5', 'σ = 1', 'σ = 2', ... %! 'σ = 3', 'σ = 4'}, 'location', 'northeast') %! title ('Rayleigh PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!test %! x = 0:0.5:2.5; %! sigma = 1:6; %! y = raylpdf (x, sigma); %! expected_y = [0.0000, 0.1212, 0.1051, 0.0874, 0.0738, 0.0637]; %! assert_equal (y, expected_y, 0.001); %!test %! x = 0:0.5:2.5; %! y = raylpdf (x, 0.5); %! expected_y = [0.0000, 1.2131, 0.5413, 0.0667, 0.0027, 0.0000]; %! assert_equal (y, expected_y, 0.001); ## Test input validation %!error raylpdf () %!error raylpdf (1) %!error ... %! raylpdf (ones (3), ones (2)) %!error ... %! raylpdf (ones (2), ones (3)) %!error raylpdf (int32 (2), 2) %!error raylpdf (true, 2) %!error raylpdf ('a', 2) %!error raylpdf (i, 2) %!error raylpdf (2, i) statistics-release-1.9.2/inst/Distribution_Functions/raylrnd.m000066400000000000000000000136421524624707500246720ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} raylrnd (@var{sigma}) ## @deftypefnx {statistics} {@var{r} =} raylrnd (@var{sigma}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} raylrnd (@var{sigma}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} raylrnd (@var{sigma}, [@var{sz}]) ## ## Random arrays from the Rayleigh distribution. ## ## @code{@var{r} = raylrnd (@var{sigma})} returns an array of random numbers ## chosen from the Rayleigh distribution with scale parameter @var{sigma}. The ## size of @var{r} is the size of @var{sigma}. A scalar input functions as a ## constant matrix of the same size as the other inputs. @var{sigma} must be a ## finite real number greater than 0, otherwise @qcode{NaN} is returned. ## ## When called with a single size argument, @code{raylrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the Rayleigh distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rayleigh_distribution} ## ## The @code{prob.RayleighDistribution} class names this same parameter ## @qcode{B}, after MATLAB. ## @seealso{raylcdf, raylinv, raylpdf, raylfit, rayllike, raylstat} ## @end deftypefn function r = raylrnd (sigma, varargin) ## Check for valid number of input arguments if (nargin < 1) error ("raylrnd: function called with too few input arguments."); endif ## Check for SIGMA being real if (iscomplex (sigma)) error ("raylrnd: SIGMA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 1) sz = size (sigma); elseif (nargin == 2) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("raylrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 2) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("raylrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (sigma) && ! isequal (size (sigma), size (ones (sz)))) error ("raylrnd: SIGMA must be scalar or of size SZ."); endif ## Generate random sample from Rayleigh distribution r = sqrt (-2 .* log (1 - rand (sz)) .* sigma .^ 2); ## Check for valid parameter k = find (! (sigma > 0)); if (any (k)) r(k) = NaN; endif ## Cast into appropriate class if (isa (sigma, 'single')) r = cast (r, 'single'); endif endfunction ## Test output %!assert_equal (size (raylrnd (2)), [1, 1]) %!assert_equal (size (raylrnd (ones (2, 1))), [2, 1]) %!assert_equal (size (raylrnd (ones (2, 2))), [2, 2]) %!assert_equal (size (raylrnd (1, 3)), [3, 3]) %!assert_equal (size (raylrnd (1, [4, 1])), [4, 1]) %!assert_equal (size (raylrnd (1, 4, 1)), [4, 1]) %!assert_equal (size (raylrnd (1, 4, 1)), [4, 1]) %!assert_equal (size (raylrnd (1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (raylrnd (1, 0, 1)), [0, 1]) %!assert_equal (size (raylrnd (1, 1, 0)), [1, 0]) %!assert_equal (size (raylrnd (1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (raylrnd (1, [])), [0, 0]) %!assert_equal (size (raylrnd (1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (raylrnd (1, -1)), [0, 0]) %!assert_equal (size (raylrnd (1, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (raylrnd (1, 2, -1, 5)), [2, 0, 5]) %!assert_equal (raylrnd (0, 1, 1), NaN) %!assert_equal (raylrnd ([0, 0, 0], [1, 3]), [NaN, NaN, NaN]) ## Test class of input preserved %!assert_equal (class (raylrnd (2)), "double") %!assert_equal (class (raylrnd (single (2))), "single") %!assert_equal (class (raylrnd (single ([2, 2]))), "single") ## Test input validation %!error raylrnd () %!error raylrnd (i) %!error ... %! raylrnd (1, 1.2) %!error ... %! raylrnd (1, ones (2)) %!error ... %! raylrnd (1, [2 0 2.5]) %!error ... %! raylrnd (ones (2), ones (2)) %!error ... %! raylrnd (1, 2, 1.5, 5) %!error raylrnd (ones (2,2), 3) %!error raylrnd (ones (2,2), [3, 2]) %!error raylrnd (ones (2,2), 2, 3) statistics-release-1.9.2/inst/Distribution_Functions/ricecdf.m000066400000000000000000000175001524624707500246130ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} ricecdf (@var{x}, @var{s}, @var{sigma}) ## @deftypefnx {statistics} {@var{p} =} ricecdf (@var{x}, @var{s}, @var{sigma}, @qcode{'upper'}) ## ## Rician cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the Rician distribution with non-centrality (distance) parameter ## @var{s} and scale parameter @var{sigma}. The size of @var{p} is the common ## size of @var{x}, @var{s}, and @var{sigma}. A scalar input functions as a ## constant matrix of the same size as the other inputs. ## ## @code{@var{p} = ricecdf (@var{x}, @var{s}, @var{sigma}, "upper")} computes ## the upper tail probability of the Rician distribution with parameters ## @var{s} and @var{sigma}, at the values in @var{x}. ## ## Further information about the Rician distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rice_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{riceinv, ricepdf, ricernd, ricefit, ricelike, ricestat} ## @end deftypefn function p = ricecdf (x, s, sigma, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("ricecdf: function called with too few input arguments."); endif ## Check for "upper" flag if (nargin == 4 && strcmpi (uflag, 'upper')) uflag = true; elseif (nargin == 4 && ! strcmpi (uflag, 'upper')) error ("ricecdf: invalid argument for upper tail."); else uflag = false; endif ## Check for common size of X, S, and SIGMA if (! isscalar (x) || ! isscalar (s) || ! isscalar (sigma)) [retval, x, s, sigma] = common_size (x, s, sigma); if (retval > 0) error ("ricecdf: X, S, and SIGMA must be of common size or scalars."); endif endif ## Check for X, S, and SIGMA being double or single if (! (isfloat (x) && isfloat (s) && isfloat (sigma))) error ("ricecdf: X, S, and SIGMA must be double or single."); endif ## Check for X, S, and SIGMA being reals if (iscomplex (x) || iscomplex (s) || iscomplex (sigma)) error ("ricecdf: X, S, and SIGMA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (s, 'single') || isa (sigma, 'single')); p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Force 1 for upper flag and X <= 0 k0 = s >= 0 & sigma > 0 & x < 0; if (uflag && any (k0(:))) p(k0) = 1; endif ## Calculate Rayleigh CDF for valid parameter and data range k = s >= 0 & sigma > 0 & x >= 0; if (any (k(:))) if (uflag) p(k) = marcumQ1 (s(k) ./ sigma(k), x(k) ./ sigma(k)); else p(k) = 1 - marcumQ1 (s(k) ./ sigma(k), x(k) ./ sigma(k)); endif endif ## Continue argument check p(! (k0 | k)) = NaN; endfunction ## Marcum's "Q" function of order 1 function Q = marcumQ1 (a, b) ## Prepare output matrix if (isa (a, 'single') || isa (b, 'single')) Q = NaN (size (b), 'single'); else Q = NaN (size (b)); endif ## Force marginal cases Q(a != Inf & b == 0) = 1; Q(a != Inf & b == Inf) = 0; Q(a == Inf & b != Inf) = 1; z = isnan (Q) & a == 0 & b != Inf; if (any (z)) Q(z) = exp ((-b(z) .^ 2) ./ 2); endif ## Compute the remaining cases z = isnan (Q) & ! isnan (a) & ! isnan (b); if (any (z(:))) aa = (a(z) .^ 2) ./ 2; bb = (b(z) .^ 2) ./ 2; eA = exp (-aa); eB = bb .* exp (-bb); h = eA; d = eB .* h; s = d; j = (d > s.*eps (class (d))); k = 1; while (any (j)) eA = aa .* eA ./ k; h = h + eA; eB = bb .* eB ./ (k + 1); d = eB .* h; s(j) = s(j) + d(j); j = (d > s .* eps (class (d))); k = k + 1; endwhile Q(z) = 1 - s; endif endfunction %!demo %! ## Plot various CDFs from the Rician distribution %! x = 0:0.01:10; %! p1 = ricecdf (x, 0, 1); %! p2 = ricecdf (x, 0.5, 1); %! p3 = ricecdf (x, 1, 1); %! p4 = ricecdf (x, 2, 1); %! p5 = ricecdf (x, 4, 1); %! plot (x, p1, '-b', x, p2, 'g', x, p3, '-r', x, p4, '-m', x, p5, '-k') %! grid on %! ylim ([0, 1]) %! xlim ([0, 8]) %! legend ({'s = 0, σ = 1', 's = 0.5, σ = 1', 's = 1, σ = 1', ... %! 's = 2, σ = 1', 's = 4, σ = 1'}, 'location', 'southeast') %! title ('Rician CDF') %! xlabel ('values in x') %! ylabel ('probability') %!demo %! ## Plot various CDFs from the Rician distribution %! x = 0:0.01:10; %! p1 = ricecdf (x, 0, 0.5); %! p2 = ricecdf (x, 0, 2); %! p3 = ricecdf (x, 0, 3); %! p4 = ricecdf (x, 2, 2); %! p5 = ricecdf (x, 4, 2); %! plot (x, p1, '-b', x, p2, 'g', x, p3, '-r', x, p4, '-m', x, p5, '-k') %! grid on %! ylim ([0, 1]) %! xlim ([0, 8]) %! legend ({'ν = 0, σ = 0.5', 'ν = 0, σ = 2', 'ν = 0, σ = 3', ... %! 'ν = 2, σ = 2', 'ν = 4, σ = 2'}, 'location', 'southeast') %! title ('Rician CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!test %! x = 0:0.5:2.5; %! s = 1:6; %! p = ricecdf (x, s, 1); %! expected_p = [0.0000, 0.0179, 0.0108, 0.0034, 0.0008, 0.0001]; %! assert_equal (p, expected_p, 0.001); %!test %! x = 0:0.5:2.5; %! sigma = 1:6; %! p = ricecdf (x, 1, sigma); %! expected_p = [0.0000, 0.0272, 0.0512, 0.0659, 0.0754, 0.0820]; %! assert_equal (p, expected_p, 0.001); %!test %! x = 0:0.5:2.5; %! p = ricecdf (x, 0, 1); %! expected_p = [0.0000, 0.1175, 0.3935, 0.6753, 0.8647, 0.9561]; %! assert_equal (p, expected_p, 0.001); %!test %! x = 0:0.5:2.5; %! p = ricecdf (x, 1, 1); %! expected_p = [0.0000, 0.0735, 0.2671, 0.5120, 0.7310, 0.8791]; %! assert_equal (p, expected_p, 0.001); %!shared x, p %! x = [-1, 0, 1, 2, Inf]; %! p = [0, 0, 0.26712019620318, 0.73098793996409, 1]; %!assert_equal (ricecdf (x, 1, 1), p, 1e-14) %!assert_equal (ricecdf (x, 1, 1, 'upper'), 1 - p, 1e-14) ## Test input validation %!error ricecdf () %!error ricecdf (1) %!error ricecdf (1, 2) %!error ricecdf (1, 2, 3, 'uper') %!error ricecdf (1, 2, 3, 4) %!error ... %! ricecdf (ones (3), ones (2), ones (2)) %!error ... %! ricecdf (ones (2), ones (3), ones (2)) %!error ... %! ricecdf (ones (2), ones (2), ones (3)) %!error ricecdf (int32 (2), 2, 3) %!error ricecdf (true, 2, 3) %!error ricecdf ('a', 2, 3) %!error ricecdf (i, 2, 3) %!error ricecdf (2, i, 3) %!error ricecdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/riceinv.m000066400000000000000000000126761524624707500246640ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} riceinv (@var{p}, @var{s}, @var{sigma}) ## ## Inverse of the Rician distribution (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) ## of the Rician distribution with non-centrality (distance) parameter @var{s} ## and scale parameter @var{sigma}. The size of @var{x} is the common size of ## @var{x}, @var{s}, and @var{sigma}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## Further information about the Rician distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rice_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{ricecdf, ricepdf, ricernd, ricefit, ricelike, ricestat} ## @end deftypefn function x = riceinv (p, s, sigma) ## Check for valid number of input arguments if (nargin < 3) error ("riceinv: function called with too few input arguments."); endif ## Check for common size of P, S, and B if (! isscalar (p) || ! isscalar (s) || ! isscalar (sigma)) [retval, p, s, sigma] = common_size (p, s, sigma); if (retval > 0) error ("riceinv: P, S, and B must be of common size or scalars."); endif endif ## Check for P, S, and B being double or single if (! (isfloat (p) && isfloat (s) && isfloat (sigma))) error ("riceinv: P, S, and B must be double or single."); endif ## Check for P, S, and B being reals if (iscomplex (p) || iscomplex (s) || iscomplex (sigma)) error ("riceinv: P, S, and B must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (s, 'single') || isa (sigma, 'single')) x = zeros (size (p), 'single'); else x = zeros (size (p)); endif k = s < 0 | sigma <= 0 | p < 0 | p > 1 | ... isnan (p) | isnan (s) | isnan (sigma); x(k) = NaN; k = ! k; x(k) = sigma(k) .* sqrt (ncx2inv (p(k), 2, (s(k) ./ sigma(k)) .^ 2)); endfunction %!demo %! ## Plot various iCDFs from the Rician distribution %! p = 0.001:0.001:0.999; %! x1 = riceinv (p, 0, 1); %! x2 = riceinv (p, 0.5, 1); %! x3 = riceinv (p, 1, 1); %! x4 = riceinv (p, 2, 1); %! x5 = riceinv (p, 4, 1); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-m', p, x5, '-k') %! grid on %! legend ({'s = 0, σ = 1', 's = 0.5, σ = 1', 's = 1, σ = 1', ... %! 's = 2, σ = 1', 's = 4, σ = 1'}, 'location', 'northwest') %! title ('Rician iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p %! p = [-1 0 0.75 1 2]; %!assert_equal (riceinv (p, ones (1,5), 2*ones (1,5)), [NaN 0 3.5354 Inf NaN], 1e-4) %!assert_equal (riceinv (p, 1, 2*ones (1,5)), [NaN 0 3.5354 Inf NaN], 1e-4) %!assert_equal (riceinv (p, ones (1,5), 2), [NaN 0 3.5354 Inf NaN], 1e-4) %!assert_equal (riceinv (p, [1 0 NaN 1 1], 2), [NaN 0 NaN Inf NaN]) %!assert_equal (riceinv (p, 1, 2*[1 0 NaN 1 1]), [NaN NaN NaN Inf NaN]) %!assert_equal (riceinv ([p(1:2) NaN p(4:5)], 1, 2), [NaN 0 NaN Inf NaN]) ## Test class of input preserved %!assert_equal (riceinv ([p, NaN], 1, 2), [NaN 0 3.5354 Inf NaN NaN], 1e-4) %!assert_equal (riceinv (single ([p, NaN]), 1, 2), ... %! single ([NaN 0 3.5354 Inf NaN NaN]), 1e-4) %!assert_equal (riceinv ([p, NaN], single (1), 2), ... %! single ([NaN 0 3.5354 Inf NaN NaN]), 1e-4) %!assert_equal (riceinv ([p, NaN], 1, single (2)), ... %! single ([NaN 0 3.5354 Inf NaN NaN]), 1e-4) ## Test input validation %!error riceinv () %!error riceinv (1) %!error riceinv (1,2) %!error riceinv (1,2,3,4) %!error ... %! riceinv (ones (3), ones (2), ones (2)) %!error ... %! riceinv (ones (2), ones (3), ones (2)) %!error ... %! riceinv (ones (2), ones (2), ones (3)) %!error riceinv (int32 (2), 2, 2) %!error riceinv (true, 2, 2) %!error riceinv ('a', 2, 2) %!error riceinv (i, 2, 2) %!error riceinv (2, i, 2) %!error riceinv (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/ricepdf.m000066400000000000000000000131261524624707500246300ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} ricepdf (@var{x}, @var{s}, @var{sigma}) ## ## Rician probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the Rician distribution with non-centrality (distance) parameter @var{s} ## and scale parameter @var{sigma}. The size of @var{y} is the common size of ## @var{x}, @var{s}, and @var{sigma}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## Further information about the Rician distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rice_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{ricecdf, riceinv, ricernd, ricefit, ricelike, ricestat} ## @end deftypefn function y = ricepdf (x, s, sigma) ## Check for valid number of input arguments if (nargin < 3) error ("ricepdf: function called with too few input arguments."); endif ## Check for common size of X, S, and SIGMA if (! isscalar (x) || ! isscalar (s) || ! isscalar (sigma)) [retval, x, s, sigma] = common_size (x, s, sigma); if (retval > 0) error ("ricepdf: X, S, and SIGMA must be of common size or scalars."); endif endif ## Check for X, S, and SIGMA being double or single if (! (isfloat (x) && isfloat (s) && isfloat (sigma))) error ("ricepdf: X, S, and SIGMA must be double or single."); endif ## Check for X, S, and SIGMA being reals if (iscomplex (x) || iscomplex (s) || iscomplex (sigma)) error ("ricepdf: X, S, and SIGMA must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (s, 'single') || isa (sigma, 'single')); y = zeros (size (x), 'single'); else y = zeros (size (x)); endif k = s < 0 | sigma <= 0 | x < 0 | isnan (x) | isnan (s) | isnan (sigma); y(k) = NaN; k = ! k; ## Do the math x_k = x(k); n_k = s(k); s_sq = sigma(k) .^ 2; x_s2 = x_k ./ s_sq; xnsq = (x_k .^ 2 + n_k .^ 2) ./ (2 .* s_sq); epxt = xnsq - x_s2 .* n_k; term = exp (-epxt); y(k) = x_s2 .* term .* besseli (0, x_s2 .* n_k, 1); ## Fix arithmetic overflow due to exponent y(epxt > (log (realmax (class (y))))) = 0; ## Fix x < 0 -> 0 y(x < 0) = 0; endfunction %!demo %! ## Plot various PDFs from the Rician distribution %! x = 0:0.01:8; %! y1 = ricepdf (x, 0, 1); %! y2 = ricepdf (x, 0.5, 1); %! y3 = ricepdf (x, 1, 1); %! y4 = ricepdf (x, 2, 1); %! y5 = ricepdf (x, 4, 1); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-m', x, y5, '-k') %! grid on %! ylim ([0, 0.65]) %! xlim ([0, 8]) %! legend ({'s = 0, σ = 1', 's = 0.5, σ = 1', 's = 1, σ = 1', ... %! 's = 2, σ = 1', 's = 4, σ = 1'}, 'location', 'northeast') %! title ('Rician PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1 0 0.5 1 2]; %! y = [0 0 0.1073 0.1978 0.2846]; %!assert_equal (ricepdf (x, ones (1, 5), 2 * ones (1, 5)), y, 1e-4) %!assert_equal (ricepdf (x, 1, 2 * ones (1, 5)), y, 1e-4) %!assert_equal (ricepdf (x, ones (1, 5), 2), y, 1e-4) %!assert_equal (ricepdf (x, [0 NaN 1 1 1], 2), [0 NaN y(3:5)], 1e-4) %!assert_equal (ricepdf (x, 1, 2 * [0 NaN 1 1 1]), [0 NaN y(3:5)], 1e-4) %!assert_equal (ricepdf ([x, NaN], 1, 2), [y, NaN], 1e-4) ## Test class of input preserved %!assert_equal (ricepdf (single ([x, NaN]), 1, 2), single ([y, NaN]), 1e-4) %!assert_equal (ricepdf ([x, NaN], single (1), 2), single ([y, NaN]), 1e-4) %!assert_equal (ricepdf ([x, NaN], 1, single (2)), single ([y, NaN]), 1e-4) ## Test input validation %!error ricepdf () %!error ricepdf (1) %!error ricepdf (1,2) %!error ricepdf (1,2,3,4) %!error ... %! ricepdf (ones (3), ones (2), ones (2)) %!error ... %! ricepdf (ones (2), ones (3), ones (2)) %!error ... %! ricepdf (ones (2), ones (2), ones (3)) %!error ricepdf (int32 (2), 2, 2) %!error ricepdf (true, 2, 2) %!error ricepdf ('a', 2, 2) %!error ricepdf (i, 2, 2) %!error ricepdf (2, i, 2) %!error ricepdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/ricernd.m000066400000000000000000000165171524624707500246510ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} ricernd (@var{s}, @var{sigma}) ## @deftypefnx {statistics} {@var{r} =} ricernd (@var{s}, @var{sigma}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} ricernd (@var{s}, @var{sigma}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} ricernd (@var{s}, @var{sigma}, [@var{sz}]) ## ## Random arrays from the Rician distribution. ## ## @code{@var{r} = ricernd (@var{s}, @var{sigma})} returns an array of random ## numbers chosen from the Rician distribution with noncentrality parameter ## @var{s} and scale parameter @var{sigma}. The size of @var{r} is the common ## size of @var{s} and @var{sigma}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## When called with a single size argument, @code{ricernd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the Rician distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rice_distribution} ## ## @seealso{ricecdf, riceinv, ricepdf, ricefit, ricelike, ricestat} ## @end deftypefn function r = ricernd (s, sigma, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("ricernd: function called with too few input arguments."); endif ## Check for common size of S and SIGMA if (! isscalar (s) || ! isscalar (sigma)) [retval, s, sigma] = common_size (s, sigma); if (retval > 0) error ("ricernd: S and SIGMA must be of common size or scalars."); endif endif ## Check for S and SIGMA being reals if (iscomplex (s) || iscomplex (sigma)) error ("ricernd: S and SIGMA must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (s); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("ricernd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("ricernd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (s) && ! isequal (size (s), size (ones (sz)))) error ("ricernd: S and SIGMA must be scalars or of size SZ."); endif ## Check for class type if (isa (s, 'single') || isa (sigma, 'single')) cls = 'single'; else cls = 'double'; endif ## Return NaNs for out of range values of S and SIGMA s(s < 0) = NaN; sigma(sigma <= 0) = NaN; ## Force S and SIGMA into the same size as SZ (if necessary) if (isscalar (s)) s = repmat (s, sz); endif if (isscalar (sigma)) sigma = repmat (sigma, sz); endif ## Generate random sample from the Rician distribution r = sigma .* sqrt (ncx2rnd (2, (s ./ sigma) .^ 2, sz)); ## Cast to appropriate class r = cast (r, cls); endfunction ## Test output %!assert_equal (size (ricernd (2, 1/2)), [1, 1]) %!assert_equal (size (ricernd (2 * ones (2, 1), 1/2)), [2, 1]) %!assert_equal (size (ricernd (2 * ones (2, 2), 1/2)), [2, 2]) %!assert_equal (size (ricernd (2, 1/2 * ones (2, 1))), [2, 1]) %!assert_equal (size (ricernd (1, 1/2 * ones (2, 2))), [2, 2]) %!assert_equal (size (ricernd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (ricernd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (ricernd (2, 1/2, 3)), [3, 3]) %!assert_equal (size (ricernd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (ricernd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (ricernd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (ricernd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (ricernd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (ricernd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (ricernd (1, 1, [])), [0, 0]) %!assert_equal (size (ricernd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (ricernd (1, 1/2, -1)), [0, 0]) %!assert_equal (size (ricernd (1, 1/2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (ricernd (1, 1/2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (ricernd (1, 1)), "double") %!assert_equal (class (ricernd (1, single (0))), "single") %!assert_equal (class (ricernd (1, single ([0, 0]))), "single") %!assert_equal (class (ricernd (1, single (1), 2)), "single") %!assert_equal (class (ricernd (1, single ([1, 1]), 1, 2)), "single") %!assert_equal (class (ricernd (single (1), 1, 2)), "single") %!assert_equal (class (ricernd (single ([1, 1]), 1, 1, 2)), "single") ## Test input validation %!error ricernd () %!error ricernd (1) %!error ... %! ricernd (ones (3), ones (2)) %!error ... %! ricernd (ones (2), ones (3)) %!error ricernd (i, 2) %!error ricernd (1, i) %!error ... %! ricernd (1, 1/2, 1.2) %!error ... %! ricernd (1, 1/2, ones (2)) %!error ... %! ricernd (1, 1/2, [2 0 2.5]) %!error ... %! ricernd (1, 1/2, 2, 1.5, 5) %!error ... %! ricernd (2, 1/2 * ones (2), 3) %!error ... %! ricernd (2, 1/2 * ones (2), [3, 2]) %!error ... %! ricernd (2, 1/2 * ones (2), 3, 2) %!error ... %! ricernd (2 * ones (2), 1/2, 3) %!error ... %! ricernd (2 * ones (2), 1/2, [3, 2]) %!error ... %! ricernd (2 * ones (2), 1/2, 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/stblcdf.m000066400000000000000000000135751524624707500246450ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} stblcdf (@var{x}, @var{alpha}, @var{beta}, @var{gam}, @var{delta}) ## ## Stable cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the stable distribution with tail index (first shape parameter) ## @var{alpha}, skewness (second shape parameter) @var{beta}, scale parameter ## @var{gam}, and location parameter @var{delta}, in the Nolan @qcode{S0} ## parameterization. The size of @var{p} is the size of @var{x}. ## ## @var{alpha} must be in the range @math{(0, 2]}, @var{beta} in @math{[-1, 1]}, ## @var{gam} positive, and @var{delta} real. The parameters must be scalars. ## ## The cumulative probability has a closed form for @var{alpha} equal to ## @code{2} (normal) and for @code{1} with @var{beta} equal to @code{0} ## (Cauchy); otherwise it is computed by numerical inversion of the ## characteristic function (the Gil-Pelaez formula). ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{stblpdf, stblinv, stblrnd, makedist} ## @end deftypefn function p = stblcdf (x, alpha, beta, gam, delta) if (nargin != 5) print_usage (); endif msg = __stable_checkparams__ (alpha, beta, gam, delta); if (! isempty (msg)) error ("stblcdf: %s", msg); endif ## Check for X, ALPHA, BETA, GAM, and DELTA being double or single if (! (isfloat (x) && isfloat (alpha) && isfloat (beta) && isfloat (gam) && isfloat (delta))) error ("stblcdf: X, ALPHA, BETA, GAM, and DELTA must be double or single."); endif if (! isreal (x)) error ("stblcdf: X must be real."); endif z = (x - delta) ./ gam; p = nan (size (z)); ok = ! isnan (z); if (alpha == 2) p(ok) = 0.5 .* erfc (-z(ok) ./ 2); elseif (alpha == 1 && beta == 0) p(ok) = 0.5 + atan (z(ok)) ./ pi; else zk = z(ok); v = zeros (numel (zk), 1); for i = 1:numel (zk) v(i) = 0.5 - (1 ./ pi) .* quadgk (@(t) imag (exp (-1i .* t .* zk(i)) ... .* __stable_cf__ (t, alpha, beta)) ./ t, 0, Inf, ... "AbsTol", 1e-12, "RelTol", 1e-10); endfor p(ok) = min (max (v, 0), 1); endif endfunction %!demo %! ## Stable cdf: Cauchy, a skewed stable, and the normal limit %! x = linspace (-6, 6, 200); %! plot (x, stblcdf (x, 1, 0, 1, 0), "-", ... %! x, stblcdf (x, 1.5, 0.5, 1, 0), "-", ... %! x, stblcdf (x, 2, 0, 1, 0), "-"); %! legend ("Cauchy", "alpha=1.5, beta=0.5", "normal", "location", "southeast"); ## Test output against MATLAB %!test %! x = -5:5; %! p = stblcdf (x, 1.5, 0.5, 1, 0); %! exp_p = [0.00961772128347771, 0.0143422747723476, 0.0257902242195547, ... %! 0.0657154294128386, 0.201576145758624, 0.462186560100778, ... %! 0.712063555515659, 0.855535196378772, 0.921201224725992, ... %! 0.951409668616683, 0.966845678836178]; %! assert_equal (p, exp_p, 1e-8); %!test %! x = -5:5; %! p = stblcdf (x, 0.8, 0.5, 1, 0); %! exp_p = [0.0427283102762096, 0.0504255041089544, 0.0624716830177048, ... %! 0.0849086757683013, 0.150275591315296, 0.431333711402679, ... %! 0.641248581720908, 0.74188789948898, 0.797926083610673, ... %! 0.833292740693924, 0.857610462691116]; %! assert_equal (p, exp_p, 1e-8); %!test # scaled and shifted (gam = 2, delta = 3) %! x = -5:5; %! p = stblcdf (x, 1.5, 0.5, 2, 3); %! exp_p = [0.0143422747723476, 0.0186030552365194, 0.0257902242195547, ... %! 0.0392075905274278, 0.0657154294128386, 0.116299801968237, ... %! 0.201576145758624, 0.321987153858349, 0.462186560100778, ... %! 0.598389078433622, 0.712063555515659]; %! assert_equal (p, exp_p, 1e-8); %!test # normal special case %! x = -5:5; %! assert_equal (stblcdf (x, 2, 0, 1, 0), normcdf (x, 0, sqrt (2)), 1e-12); %!test # Cauchy special case %! x = -5:5; %! assert_equal (stblcdf (x, 1, 0, 1, 0), 0.5 + atan (x) ./ pi, 1e-12); %!test # cdf is the integral of the pdf %! assert_equal (stblcdf (0.7, 1.3, -0.4, 1, 0) - ... %! stblcdf (-1.2, 1.3, -0.4, 1, 0), ... %! quadgk (@(x) stblpdf (x, 1.3, -0.4, 1, 0), -1.2, 0.7), 1e-8); ## Test input validation %!error stblcdf (int32 (2), 1.5, 0, 1, 0) %!error stblcdf (true, 1.5, 0, 1, 0) %!error stblcdf ('a', 1.5, 0, 1, 0) %!error stblcdf (1, 1.5, 0.5, 1) %!error ... %! stblcdf (1, 2.5, 0, 1, 0) %!error ... %! stblcdf (1, 1.5, 2, 1, 0) %!error stblcdf (1, 1.5, 0, 0, 0) %!error stblcdf (1, 1.5, 0, 1, 1i) %!error stblcdf (1i, 1.5, 0, 1, 0) statistics-release-1.9.2/inst/Distribution_Functions/stblinv.m000066400000000000000000000132471524624707500247010ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} stblinv (@var{p}, @var{alpha}, @var{beta}, @var{gam}, @var{delta}) ## ## Inverse of the stable cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the stable distribution with tail index (first shape parameter) @var{alpha}, ## skewness (second shape parameter) @var{beta}, scale parameter @var{gam}, and ## location parameter @var{delta}, in the Nolan @qcode{S0} parameterization. ## The size of @var{x} is the size of @var{p}. ## ## @var{alpha} must be in the range @math{(0, 2]}, @var{beta} in @math{[-1, 1]}, ## @var{gam} positive, and @var{delta} real. The parameters must be scalars. ## ## The quantile has a closed form for @var{alpha} equal to @code{2} (normal) and ## for @code{1} with @var{beta} equal to @code{0} (Cauchy); otherwise it is ## found by numerical inversion of @code{stblcdf}. ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{stblcdf, stblpdf, stblrnd, makedist} ## @end deftypefn function x = stblinv (p, alpha, beta, gam, delta) if (nargin != 5) print_usage (); endif msg = __stable_checkparams__ (alpha, beta, gam, delta); if (! isempty (msg)) error ("stblinv: %s", msg); endif ## Check for P, ALPHA, BETA, GAM, and DELTA being double or single if (! (isfloat (p) && isfloat (alpha) && isfloat (beta) && isfloat (gam) && isfloat (delta))) error ("stblinv: P, ALPHA, BETA, GAM, and DELTA must be double or single."); endif if (! (isreal (p) && all (p(:) >= 0 & p(:) <= 1 | isnan (p(:))))) error ("stblinv: P must contain real values in the range [0, 1]."); endif x = nan (size (p)); if (alpha == 2) x = delta + sqrt (2) .* gam .* (-sqrt (2) .* erfcinv (2 .* p)); elseif (alpha == 1 && beta == 0) x = delta + gam .* tan (pi .* (p - 0.5)); else for i = 1:numel (p) pp = p(i); if (isnan (pp)) continue; elseif (pp == 0) x(i) = -Inf; elseif (pp == 1) x(i) = Inf; else ## Root-find on the standardized cdf, started from the (heavy-tailed) ## Cauchy quantile so the search stays near the solution. z0 = tan (pi .* (pp - 0.5)); z = fzero (@(zz) stblcdf (zz, alpha, beta, 1, 0) - pp, z0); x(i) = delta + gam .* z; endif endfor endif endfunction %!demo %! ## Quantiles of a skewed stable distribution %! p = [0.1, 0.25, 0.5, 0.75, 0.9]; %! x = stblinv (p, 1.5, 0.5, 1, 0) ## Test output against MATLAB %!test %! p = [0.1, 0.25, 0.5, 0.75, 0.9]; %! x = stblinv (p, 1.5, 0.5, 1, 0); %! exp_x = [-1.63127009138493, -0.783313648587273, 0.133853042315326, ... %! 1.20341055131626, 2.58231785139714]; %! assert_equal (x, exp_x, 1e-6); %!test %! p = [0.1, 0.25, 0.5, 0.75, 0.9]; %! x = stblinv (p, 0.8, 0.5, 1, 0); %! exp_x = [-1.62200033048034, -0.553853652413272, 0.250487323305453, ... %! 2.11601429745393, 8.03924696271835]; %! assert_equal (x, exp_x, 1e-5); %!test # scaled and shifted (gam = 2, delta = 3) %! p = [0.1, 0.25, 0.5, 0.75, 0.9]; %! x = stblinv (p, 1.5, 0.5, 2, 3); %! exp_x = [-0.26254018276987, 1.43337270282545, 3.26770608463065, ... %! 5.40682110263252, 8.16463570279428]; %! assert_equal (x, exp_x, 1e-6); %!test # symmetric case (beta = 0): quantiles antisymmetric about delta %! p = [0.1, 0.25, 0.5, 0.75, 0.9]; %! x = stblinv (p, 1.5, 0, 1, 0); %! exp_x = [-2.06146263813919, -0.968933181710917, 0, ... %! 0.968933181710917, 2.06146263813919]; %! assert_equal (x, exp_x, 1e-6); %!test # normal and Cauchy special cases %! p = [0.1, 0.3, 0.5, 0.7, 0.9]; %! assert_equal (stblinv (p, 2, 0, 1, 0), norminv (p, 0, sqrt (2)), 1e-12); %! assert_equal (stblinv (p, 1, 0, 1, 0), tan (pi .* (p - 0.5)), 1e-12); %!test # inverts stblcdf %! x0 = [-3, -0.5, 0.8, 4]; %! p = stblcdf (x0, 1.4, 0.3, 1.5, -1); %! assert_equal (stblinv (p, 1.4, 0.3, 1.5, -1), x0, 1e-6); %!test # boundaries %! assert_equal (stblinv ([0, 1], 1.5, 0.5, 1, 0), [-Inf, Inf]); ## Test input validation %!error stblinv (int32 (2), 1.5, 0, 1, 0) %!error stblinv (true, 1.5, 0, 1, 0) %!error stblinv ('a', 1.5, 0, 1, 0) %!error stblinv (0.5, 1.5, 0.5, 1) %!error ... %! stblinv (0.5, 2.5, 0, 1, 0) %!error ... %! stblinv (1.2, 1.5, 0, 1, 0) %!error ... %! stblinv (0.5i, 1.5, 0, 1, 0) statistics-release-1.9.2/inst/Distribution_Functions/stblpdf.m000066400000000000000000000145411524624707500246540ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} stblpdf (@var{x}, @var{alpha}, @var{beta}, @var{gam}, @var{delta}) ## ## Stable probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the stable distribution with tail index (first shape parameter) ## @var{alpha}, skewness (second shape parameter) @var{beta}, scale parameter ## @var{gam}, and location parameter @var{delta}, in the Nolan @qcode{S0} ## parameterization. The size of @var{y} is the size of @var{x}. ## ## @var{alpha} must be in the range @math{(0, 2]}, @var{beta} in @math{[-1, 1]}, ## @var{gam} positive, and @var{delta} real. The parameters must be scalars. ## ## The density has a closed form for @var{alpha} equal to @code{2} (normal) and ## for @code{1} with @var{beta} equal to @code{0} (Cauchy); otherwise it is ## computed by numerical inversion of the characteristic function. ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{stblcdf, stblinv, stblrnd, makedist} ## @end deftypefn function y = stblpdf (x, alpha, beta, gam, delta) if (nargin != 5) print_usage (); endif msg = __stable_checkparams__ (alpha, beta, gam, delta); if (! isempty (msg)) error ("stblpdf: %s", msg); endif ## Check for X, ALPHA, BETA, GAM, and DELTA being double or single if (! (isfloat (x) && isfloat (alpha) && isfloat (beta) && isfloat (gam) && isfloat (delta))) error ("stblpdf: X, ALPHA, BETA, GAM, and DELTA must be double or single."); endif if (! isreal (x)) error ("stblpdf: X must be real."); endif ## Standardize z = (x - delta) ./ gam; y = nan (size (z)); ok = ! isnan (z); if (alpha == 2) ## Normal with variance 2 y(ok) = exp (-z(ok) .^ 2 ./ 4) ./ (2 .* sqrt (pi)); elseif (alpha == 1 && beta == 0) ## Cauchy y(ok) = 1 ./ (pi .* (1 + z(ok) .^ 2)); else zk = z(ok); v = zeros (numel (zk), 1); for i = 1:numel (zk) v(i) = (1 ./ pi) .* quadgk (@(t) real (exp (-1i .* t .* zk(i)) ... .* __stable_cf__ (t, alpha, beta)), 0, Inf, ... "AbsTol", 1e-12, "RelTol", 1e-10); endfor y(ok) = max (v, 0); endif ## Undo the scale (density transforms by 1 / gam) y = y ./ gam; endfunction %!demo %! ## Stable densities: Cauchy, a skewed stable, and the normal limit %! x = linspace (-6, 6, 200); %! plot (x, stblpdf (x, 1, 0, 1, 0), "-", ... %! x, stblpdf (x, 1.5, 0.5, 1, 0), "-", ... %! x, stblpdf (x, 2, 0, 1, 0), "-"); %! legend ("Cauchy", "alpha=1.5, beta=0.5", "normal"); ## Test output against MATLAB %!test %! x = -5:5; %! y = stblpdf (x, 1.5, 0.5, 1, 0); %! exp_y = [0.00330549826030791, 0.00673588721821526, 0.0190320671951022, ... %! 0.0729514702833168, 0.208194435543156, 0.284283800988578, ... %! 0.198573023913399, 0.0958317325744725, 0.0428461930184788, ... %! 0.0207819141087301, 0.0113306451818624]; %! assert_equal (y, exp_y, 1e-9); %!test %! x = -5:5; %! y = stblpdf (x, 0.8, 0.5, 1, 0); %! exp_y = [0.00634335874934447, 0.0093623044752761, 0.0155686941305108, ... %! 0.0326882516316453, 0.135673711418341, 0.298698147231422, ... %! 0.139071606104264, 0.0722555300098094, 0.0433943212392174, ... %! 0.0288186423689968, 0.020522989417733]; %! assert_equal (y, exp_y, 1e-9); %!test %! x = -5:5; %! y = stblpdf (x, 1.2, -0.5, 1, 0); %! exp_y = [0.0166464288580291, 0.0264743439008481, 0.0459799636082411, ... %! 0.0881296016218348, 0.177627321920986, 0.288106176914537, ... %! 0.196803906514695, 0.0520585692918225, 0.0173156565634881, ... %! 0.00836895075945555, 0.00490202402183536]; %! assert_equal (y, exp_y, 1e-9); %!test # scaled and shifted (gam = 2, delta = 3) %! x = -5:5; %! y = stblpdf (x, 1.5, 0.5, 2, 3); %! exp_y = [0.00336794360910763, 0.00537726811151198, 0.00951603359755111, ... %! 0.0184406959152125, 0.0364757351416584, 0.0666533040480966, ... %! 0.104097217771578, 0.134023248277231, 0.142141900494289, ... %! 0.127056343301115, 0.0992865119566997]; %! assert_equal (y, exp_y, 1e-9); %!test # normal special case (alpha = 2) %! x = -5:5; %! assert_equal (stblpdf (x, 2, 0, 1, 0), normpdf (x, 0, sqrt (2)), 1e-12); %!test # Cauchy special case (alpha = 1, beta = 0) %! x = -5:5; %! assert_equal (stblpdf (x, 1, 0, 1, 0), 1 ./ (pi .* (1 + x .^ 2)), 1e-12); %!test # Levy (alpha = 0.5, beta = 1): S0 support boundary at x = -1 %! x = -5:5; %! y = stblpdf (x, 0.5, 1, 1, 0); %! assert_equal (y(x < 0), zeros (1, 5), 1e-6); %! assert_equal (y(x == 0), 0.241970724519143, 1e-9); ## Test input validation %!error stblpdf (int32 (2), 1.5, 0, 1, 0) %!error stblpdf (true, 1.5, 0, 1, 0) %!error stblpdf ('a', 1.5, 0, 1, 0) %!error stblpdf (1, 1.5, 0.5, 1) %!error ... %! stblpdf (1, 2.5, 0, 1, 0) %!error ... %! stblpdf (1, 1.5, 2, 1, 0) %!error stblpdf (1, 1.5, 0, 0, 0) %!error stblpdf (1, 1.5, 0, 1, 1i) %!error stblpdf (1i, 1.5, 0, 1, 0) statistics-release-1.9.2/inst/Distribution_Functions/stblrnd.m000066400000000000000000000132271524624707500246660ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} stblrnd (@var{alpha}, @var{beta}, @var{gam}, @var{delta}) ## @deftypefnx {statistics} {@var{r} =} stblrnd (@var{alpha}, @var{beta}, @var{gam}, @var{delta}, @var{m}) ## @deftypefnx {statistics} {@var{r} =} stblrnd (@var{alpha}, @var{beta}, @var{gam}, @var{delta}, @var{m}, @var{n}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} stblrnd (@var{alpha}, @var{beta}, @var{gam}, @var{delta}, [@var{m}, @var{n}, @dots{}]) ## ## Random arrays from the stable distribution. ## ## @code{@var{r} = stblrnd (@var{alpha}, @var{beta}, @var{gam}, @var{delta})} ## returns a random value drawn from the stable distribution with tail index ## (first shape parameter) @var{alpha}, skewness (second shape parameter) ## @var{beta}, scale parameter @var{gam}, and location parameter @var{delta}, in ## the Nolan @qcode{S0} parameterization. ## ## @var{alpha} must be in the range @math{(0, 2]}, @var{beta} in @math{[-1, 1]}, ## @var{gam} positive, and @var{delta} real. The parameters must be scalars. ## ## @code{stblrnd (@var{alpha}, @var{beta}, @var{gam}, @var{delta}, @var{m}, ## @var{n}, @dots{})} or @code{stblrnd (@dots{}, [@var{m}, @var{n}, @dots{}])} ## returns an @var{m}-by-@var{n}-by-@dots{} array, following the size ## conventions of @code{rand}. ## ## The values are generated with the Chambers-Mallows-Stuck method. ## ## @seealso{stblpdf, stblcdf, stblinv, makedist} ## @end deftypefn function r = stblrnd (alpha, beta, gam, delta, varargin) if (nargin < 4) print_usage (); endif msg = __stable_checkparams__ (alpha, beta, gam, delta); if (! isempty (msg)) error ("stblrnd: %s", msg); endif ## Output size, following rand's conventions if (numel (varargin) == 0) sz = [1, 1]; elseif (numel (varargin) == 1) a = varargin{1}; if (isscalar (a)) sz = [a, a]; else sz = a(:)'; endif else sz = [varargin{:}]; endif if (! all (sz == fix (sz))) error ("stblrnd: dimensions must be integers."); endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Chambers-Mallows-Stuck: draw a uniform on (-pi/2, pi/2) and a unit ## exponential, form a standard S1 variate, then shift to S0 and scale. V = (rand (sz) - 0.5) .* pi; W = -log (rand (sz)); if (alpha == 1) X = (2 ./ pi) .* ((pi ./ 2 + beta .* V) .* tan (V) ... - beta .* log ((pi ./ 2 .* W .* cos (V)) ... ./ (pi ./ 2 + beta .* V))); r = gam .* X + delta; else ct = tan (pi .* alpha ./ 2); B = atan (beta .* ct) ./ alpha; S = (1 + beta .^ 2 .* ct .^ 2) .^ (1 ./ (2 .* alpha)); X = S .* sin (alpha .* (V + B)) ./ (cos (V)) .^ (1 ./ alpha) ... .* (cos (V - alpha .* (V + B)) ./ W) .^ ((1 - alpha) ./ alpha); r = gam .* (X - beta .* ct) + delta; endif endfunction %!demo %! ## Draw a large stable sample and overlay the theoretical density %! rng (42); %! r = stblrnd (1.5, 0.5, 1, 0, 1, 1e5); %! r = r(abs (r) < 15); %! hist (r, 100, 1); %! hold on; %! x = linspace (-15, 15, 400); %! plot (x, stblpdf (x, 1.5, 0.5, 1, 0), "r-", "linewidth", 2); %! hold off; ## The empirical cdf of a large sample matches stblcdf (seeded, repeatable) %!test %! rand ("state", 42); %! r = stblrnd (1.5, 0.5, 1, 0, 1, 200000); %! xs = [-2, -0.5, 0.5, 2]; %! ec = arrayfun (@(x) mean (r <= x), xs); %! assert_equal (ec, stblcdf (xs, 1.5, 0.5, 1, 0), 0.01); %!test # heavy-tailed alpha < 1, scaled and shifted %! rand ("state", 7); %! r = stblrnd (0.8, -0.3, 2, 1, 1, 200000); %! xs = [-3, 0, 1, 4]; %! ec = arrayfun (@(x) mean (r <= x), xs); %! assert_equal (ec, stblcdf (xs, 0.8, -0.3, 2, 1), 0.01); %!test # alpha = 1 with beta ~= 0 %! rand ("state", 99); %! r = stblrnd (1, 0.5, 1.5, -2, 1, 200000); %! xs = [-5, -2, 0, 3]; %! ec = arrayfun (@(x) mean (r <= x), xs); %! assert_equal (ec, stblcdf (xs, 1, 0.5, 1.5, -2), 0.01); %!test # alpha = 2 is normal with variance 2*gam^2 %! rand ("state", 1); %! r = stblrnd (2, 0, 1, 0, 1, 200000); %! assert_equal (mean (r), 0, 0.02); %! assert_equal (var (r), 2, 0.05); ## Size handling follows rand %!test %! assert_equal (size (stblrnd (1.5, 0.5, 1, 0, 3, 4)), [3, 4]); %! assert_equal (size (stblrnd (1.5, 0.5, 1, 0, [2, 5])), [2, 5]); %! assert_equal (isscalar (stblrnd (1.5, 0.5, 1, 0)), true); %! assert_equal (size (stblrnd (1.5, 0.5, 1, 0, -1)), [0, 0]); %! assert_equal (size (stblrnd (1.5, 0.5, 1, 0, 2, -1, 5)), [2, 0, 5]); ## Test input validation %!error stblrnd (1.5, 0.5, 1) %!error ... %! stblrnd (2.5, 0, 1, 0) %!error ... %! stblrnd (1.5, 2, 1, 0) %!error stblrnd (1.5, 0, 0, 0) %!error stblrnd (1.5, 0, 1, 1i) %!error ... %! stblrnd (1.5, 0, 1, 0, 2.5) statistics-release-1.9.2/inst/Distribution_Functions/tcdf.m000066400000000000000000000221421524624707500241320ustar00rootroot00000000000000## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 2013-2017 Julien Bect ## Copyright (C) 2022-2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} tcdf (@var{x}, @var{df}) ## @deftypefnx {statistics} {@var{p} =} tcdf (@var{x}, @var{df}, @qcode{'upper'}) ## ## Student's T cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the Student's T distribution with @var{df} degrees of freedom. The ## size of @var{p} is the common size of @var{x} and @var{df}. A scalar input ## functions as a constant matrix of the same size as the other input. ## ## @code{@var{p} = tcdf (@var{x}, @var{df}, "upper")} computes the upper tail ## probability of the Student's T distribution with @var{df} degrees of freedom, ## at the values in @var{x}. ## ## Further information about the Student's T distribution can be found at ## @url{https://en.wikipedia.org/wiki/Student%27s_t-distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{tinv, tpdf, trnd, tstat} ## @end deftypefn function p = tcdf (x, df, uflag) ## Check for valid number of input arguments if (nargin < 2) error ("tcdf: function called with too few input arguments."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) x = -x; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("tcdf: invalid argument for upper tail."); endif ## Check for common size of X and DF if (! isscalar (x) || ! isscalar (df)) [err, x, df] = common_size (x, df); if (err > 0) error ("tcdf: X and DF must be of common size or scalars."); endif endif ## Check for X and DF being double or single if (! (isfloat (x) && isfloat (df))) error ("tcdf: X and DF must be double or single."); endif ## Check for X and DF being reals if (iscomplex (x) || iscomplex (df)) error ("tcdf: X and DF must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (df, 'single')) p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Check for NaNs or DF <= 0 is_nan = isnan (x) | ! (df > 0); p(is_nan) = NaN; ## Check for Inf where DF > 0 (for -Inf is already 0) k = (x == Inf) & (df > 0); p(k) = 1; ## Find finite values in X where 0 < DF < Inf k = isfinite (x) & (df > 0) & (df < Inf); ## Process more efficiently small positive integer DF up to 1e3 ks = k & (fix (df) == df) & (df <= 1e3); if (sum (ks) == 1 && sum (k) == 1) if (isscalar (df)) p(ks) = tcdf_integer_df (x(ks), df); else vu = unique (df(ks)); for i = 1:numel (vu) ki = ks & (df == vu(i)); p(ki) = tcdf_integer_df (x(ki), vu(i)); endfor endif return; endif ## Distinguish between small and big abs(x) xx = x .^ 2; x_big_abs = (xx > df); ## Deal with the case "abs(x) big" kk = k & x_big_abs; if (isscalar (df)) p(kk) = betainc (df ./ (df + xx(kk)), df/2, 1/2) / 2; else p(kk) = betainc (df(kk) ./ (df(kk) + xx(kk)), df(kk)/2, 1/2) / 2; endif ## Deal with the case "abs(x) small" kk = k & ! x_big_abs; if (isscalar (df)) p(kk) = 0.5 * (1 - betainc (xx(kk) ./ (df + xx(kk)), 1/2, df/2)); else p(kk) = 0.5 * (1 - betainc (xx(kk) ./ (df(kk) + xx(kk)), 1/2, df(kk)/2)); endif ## For x > 0, F(x) = 1 - F(-|x|). k &= (x > 0); if (any (k(:))) p(k) = 1 - p(k); endif ## Special case for Cauchy distribution ## Use acot(-x) instead of the usual (atan x)/pi + 0.5 to avoid roundoff error xpos = (x > 0); c = (df == 1); p(c) = xpos(c) + acot (-x(c)) / pi; ## Special case for DF == Inf k = isfinite (x) & (df == Inf); p(k) = normcdf (x(k)); ## Make the result exact for the median p(x == 0 & ! is_nan) = 0.5; endfunction ## Compute the t distribution CDF efficiently (without calling betainc) ## for small positive integer DF up to 1e4 function p = tcdf_integer_df (x, df) if (df == 1) p = 0.5 + atan (x)/pi; elseif (df == 2) p = 0.5 + x ./ (2 * sqrt (2 + x .^ 2)); else xs = x ./ sqrt (df); xxf = 1 ./ (1 + xs .^ 2); u = s = 1; if mod (df, 2) ## odd DF m = (df - 1) / 2; for i = 2:m u .*= (1 - 1/(2*i - 1)) .* xxf; s += u; endfor p = 0.5 + (xs .* xxf .* s + atan (xs)) / pi; else ## even DF m = df / 2; for i = 1:(m - 1) u .*= (1 - 1/(2*i)) .* xxf; s += u; endfor p = 0.5 + (xs .* sqrt (xxf) .* s) / 2; endif endif endfunction %!demo %! ## Plot various CDFs from the Student's T distribution %! x = -5:0.01:5; %! p1 = tcdf (x, 1); %! p2 = tcdf (x, 2); %! p3 = tcdf (x, 5); %! p4 = tcdf (x, Inf); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-m') %! grid on %! xlim ([-5, 5]) %! ylim ([0, 1]) %! legend ({'df = 1', 'df = 2', ... %! 'df = 5', 'df = \infty'}, 'location', 'southeast') %! title ('Student''s T CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x,y %! x = [-Inf 0 1 Inf]; %! y = [0 1/2 3/4 1]; %!assert_equal (tcdf (x, ones (1,4)), y, eps) %!assert_equal (tcdf (x, 1), y, eps) %!assert_equal (tcdf (x, [0 1 NaN 1]), [NaN 1/2 NaN 1], eps) %!assert_equal (tcdf ([x(1:2) NaN x(4)], 1), [y(1:2) NaN y(4)], eps) %!assert_equal (tcdf (2, 3, 'upper'), 0.0697, 1e-4) %!assert_equal (tcdf (205, 5, 'upper'), 2.6206e-11, 1e-14) ## Test class of input preserved %!assert_equal (tcdf ([x, NaN], 1), [y, NaN], eps) %!assert_equal (tcdf (single ([x, NaN]), 1), single ([y, NaN]), eps ('single')) %!assert_equal (tcdf ([x, NaN], single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error tcdf () %!error tcdf (1) %!error tcdf (1, 2, 'uper') %!error tcdf (1, 2, 3) %!error ... %! tcdf (ones (3), ones (2)) %!error ... %! tcdf (ones (3), ones (2)) %!error ... %! tcdf (ones (3), ones (2), 'upper') %!error tcdf (int32 (2), 2) %!error tcdf (true, 2) %!error tcdf ('a', 2) %!error tcdf (i, 2) %!error tcdf (2, i) ## Check some reference values %!shared tol_rel %! tol_rel = 10 * eps; ## check accuracy for small positive values %!assert_equal (tcdf (10^(-10), 2.5), 0.50000000003618087, -tol_rel) %!assert_equal (tcdf (10^(-11), 2.5), 0.50000000000361809, -tol_rel) %!assert_equal (tcdf (10^(-12), 2.5), 0.50000000000036181, -tol_rel) %!assert_equal (tcdf (10^(-13), 2.5), 0.50000000000003618, -tol_rel) %!assert_equal (tcdf (10^(-14), 2.5), 0.50000000000000362, -tol_rel) %!assert_equal (tcdf (10^(-15), 2.5), 0.50000000000000036, -tol_rel) %!assert_equal (tcdf (10^(-16), 2.5), 0.50000000000000004, -tol_rel) ## check accuracy for large negative values %!assert_equal (tcdf (-10^1, 2.5), 2.2207478836537124e-03, -tol_rel) %!assert_equal (tcdf (-10^2, 2.5), 7.1916492116661878e-06, -tol_rel) %!assert_equal (tcdf (-10^3, 2.5), 2.2747463948307452e-08, -tol_rel) %!assert_equal (tcdf (-10^4, 2.5), 7.1933970159922115e-11, -tol_rel) %!assert_equal (tcdf (-10^5, 2.5), 2.2747519231756221e-13, -tol_rel) ## # Reference values obtained using Python 2.7.4 and mpmath 0.17 ## ## from mpmath import * ## ## mp.dps = 100 ## ## def F(x_in, nu_in): ## x = mpf(x_in); ## nu = mpf(nu_in); ## t = nu / (nu + x*x) ## a = nu / 2 ## b = mpf(0.5) ## F = betainc(a, b, 0, t, regularized=True) / 2 ## if (x > 0): ## F = 1 - F ## return F ## ## nu = 2.5 ## ## for i in range(1, 6): ## x = - power(mpf(10), mpf(i)) ## print "%%!assert_equal (tcdf (-10^%d, 2.5), %s, -eps)" \ ## % (i, nstr(F(x, nu), 17)) ## ## for i in range(10, 17): ## x = power(mpf(10), -mpf(i)) ## print "%%!assert_equal (tcdf (10^(-%d), 2.5), %s, -eps)" \ ## % (i, nstr(F(x, nu), 17)) statistics-release-1.9.2/inst/Distribution_Functions/tinv.m000066400000000000000000000125321524624707500241740ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} tinv (@var{p}, @var{df}) ## ## Inverse of the Student's T cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the Student's T distribution with @var{df} degrees of freedom. The size of ## @var{x} is the common size of @var{x} and @var{df}. A scalar input functions ## as a constant matrix of the same size as the other input. ## ## This function is analogous to looking in a table for the t-value of a ## single-tailed distribution. For very large @var{df} (>10000), the inverse of ## the standard normal distribution is used. ## ## Further information about the Student's T distribution can be found at ## @url{https://en.wikipedia.org/wiki/Student%27s_t-distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{tcdf, tpdf, trnd, tstat} ## @end deftypefn function x = tinv (p, df) ## Check for valid number of input arguments if (nargin < 2) error ("tinv: function called with too few input arguments."); endif ## Check for common size of P and DF if (! isscalar (p) || ! isscalar (df)) [retval, p, df] = common_size (p, df); if (retval > 0) error ("tinv: P and DF must be of common size or scalars."); endif endif ## Check for P and DF being double or single if (! (isfloat (p) && isfloat (df))) error ("tinv: P and DF must be double or single."); endif ## Check for P and DF being reals if (iscomplex (p) || iscomplex (df)) error ("tinv: P and DF must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (df, 'single')) x = NaN (size (p), 'single'); else x = NaN (size (p)); endif k = (p == 0) & (df > 0); x(k) = -Inf; k = (p == 1) & (df > 0); x(k) = Inf; if (isscalar (df)) k = (p > 0) & (p < 1); if ((df > 0) && (df < 10000)) x(k) = (sign (p(k) - 1/2) .* sqrt (df * (1 ./ betainv (2*min (p(k), 1 - p(k)), df/2, 1/2) - 1))); elseif (df >= 10000) ## For large df, use the quantiles of the standard normal x(k) = -sqrt (2) * erfcinv (2 * p(k)); endif else k = (p > 0) & (p < 1) & (df > 0) & (df < 10000); x(k) = (sign (p(k) - 1/2) .* sqrt (df(k) .* (1 ./ betainv (2*min (p(k), 1 - p(k)), df(k)/2, 1/2) - 1))); ## For large df, use the quantiles of the standard normal k = (p > 0) & (p < 1) & (df >= 10000); x(k) = -sqrt (2) * erfcinv (2 * p(k)); endif endfunction %!demo %! ## Plot various iCDFs from the Student's T distribution %! p = 0.001:0.001:0.999; %! x1 = tinv (p, 1); %! x2 = tinv (p, 2); %! x3 = tinv (p, 5); %! x4 = tinv (p, Inf); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-m') %! grid on %! xlim ([0, 1]) %! ylim ([-5, 5]) %! legend ({'df = 1', 'df = 2', ... %! 'df = 5', 'df = \infty'}, 'location', 'northwest') %! title ('Student''s T iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p %! p = [-1 0 0.5 1 2]; %!assert_equal (tinv (p, ones (1,5)), [NaN -Inf 0 Inf NaN]) %!assert_equal (tinv (p, 1), [NaN -Inf 0 Inf NaN], eps) %!assert_equal (tinv (p, [1 0 NaN 1 1]), [NaN NaN NaN Inf NaN], eps) %!assert_equal (tinv ([p(1:2) NaN p(4:5)], 1), [NaN -Inf NaN Inf NaN]) ## Test class of input preserved %!assert_equal (tinv ([p, NaN], 1), [NaN -Inf 0 Inf NaN NaN], eps) %!assert_equal (tinv (single ([p, NaN]), 1), single ([NaN -Inf 0 Inf NaN NaN]), eps ('single')) %!assert_equal (tinv ([p, NaN], single (1)), single ([NaN -Inf 0 Inf NaN NaN]), eps ('single')) ## Test input validation %!error tinv () %!error tinv (1) %!error ... %! tinv (ones (3), ones (2)) %!error ... %! tinv (ones (2), ones (3)) %!error tinv (int32 (2), 2) %!error tinv (true, 2) %!error tinv ('a', 2) %!error tinv (i, 2) %!error tinv (2, i) statistics-release-1.9.2/inst/Distribution_Functions/tlscdf.m000066400000000000000000000160311524624707500244710ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} tlscdf (@var{x}, @var{mu}, @var{sigma}, @var{nu}) ## @deftypefnx {statistics} {@var{p} =} tlscdf (@var{x}, @var{mu}, @var{sigma}, @var{nu}, @qcode{'upper'}) ## ## Location-scale Student's T cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the location-scale Student's T distribution with location parameter ## @var{mu}, scale parameter @var{sigma}, and @var{nu} degrees of freedom. The ## size of @var{p} is the common size of @var{x}, @var{mu}, @var{sigma}, and ## @var{nu}. A scalar input functions as a constant matrix of the same size as ## the other inputs. ## ## @code{@var{p} = tlscdf (@var{x}, @var{mu}, @var{sigma}, @var{nu}, "upper")} ## computes the upper tail probability of the location-scale Student's T ## distribution with parameters @var{mu}, @var{sigma}, and @var{nu}, at the ## values in @var{x}. ## ## Further information about the location-scale Student's T distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{tlsinv, tlspdf, tlsrnd, tlsfit, tlslike, tlsstat} ## @end deftypefn function p = tlscdf (x, mu, sigma, nu, uflag) ## Check for valid number of input arguments if (nargin < 4) error ("tlscdf: function called with too few input arguments."); endif ## Check for "upper" flag upper = false; if (nargin > 4 && strcmpi (uflag, 'upper')) upper = true; elseif (nargin > 4 && ! strcmpi (uflag, 'upper')) error ("tlscdf: invalid argument for upper tail."); endif ## Check for common size of X, MU, SIGMA, and NU if (! isscalar (x) || ! isscalar (mu) || ! isscalar (sigma) || ! isscalar (nu)) [err, x, mu, sigma, nu] = common_size (x, mu, sigma, nu); if (err > 0) error ("tlscdf: X, MU, SIGMA, and NU must be of common size or scalars."); endif endif ## Check for X, MU, SIGMA, and NU being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma) && isfloat (nu))) error ("tlscdf: X, MU, SIGMA, and NU must be double or single."); endif ## Check for X, MU, SIGMA, and NU being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma) || iscomplex (nu)) error ("tlscdf: X, MU, SIGMA, and NU must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single') || isa (nu, 'single')) cls = 'single'; else cls = 'double'; endif ## Force invalid SIGMA parameter to NaN sigma(sigma <= 0) = NaN; ## Call tcdf to do the work if (upper) p = tcdf ((x - mu) ./ sigma, nu, 'upper'); else p = tcdf ((x - mu) ./ sigma, nu); endif ## Force class type p = cast (p, cls); endfunction %!demo %! ## Plot various CDFs from the location-scale Student's T distribution %! x = -8:0.01:8; %! p1 = tlscdf (x, 0, 1, 1); %! p2 = tlscdf (x, 0, 2, 2); %! p3 = tlscdf (x, 3, 2, 5); %! p4 = tlscdf (x, -1, 3, Inf); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-m') %! grid on %! xlim ([-8, 8]) %! ylim ([0, 1]) %! legend ({'mu = 0, sigma = 1, nu = 1', 'mu = 0, sigma = 2, nu = 2', ... %! 'mu = 3, sigma = 2, nu = 5', 'mu = -1, sigma = 3, nu = \infty'}, ... %! 'location', 'northwest') %! title ('Location-scale Student''s T CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x,y %! x = [-Inf 0 1 Inf]; %! y = [0 1/2 3/4 1]; %!assert_equal (tlscdf (x, 0, 1, ones (1,4)), y, eps) %!assert_equal (tlscdf (x, 0, 1, 1), y, eps) %!assert_equal (tlscdf (x, 0, 1, [0 1 NaN 1]), [NaN 1/2 NaN 1], eps) %!assert_equal (tlscdf ([x(1:2) NaN x(4)], 0, 1, 1), [y(1:2) NaN y(4)], eps) %!assert_equal (tlscdf (2, 0, 1, 3, 'upper'), 0.0697, 1e-4) %!assert_equal (tlscdf (205, 0, 1, 5, 'upper'), 2.6206e-11, 1e-14) ## Test class of input preserved %!assert_equal (tlscdf ([x, NaN], 0, 1, 1), [y, NaN], eps) %!assert_equal (tlscdf (single ([x, NaN]), 0, 1, 1), single ([y, NaN]), eps ('single')) %!assert_equal (tlscdf ([x, NaN], single (0), 1, 1), single ([y, NaN]), eps ('single')) %!assert_equal (tlscdf ([x, NaN], 0, single (1), 1), single ([y, NaN]), eps ('single')) %!assert_equal (tlscdf ([x, NaN], 0, 1, single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error tlscdf () %!error tlscdf (1) %!error tlscdf (1, 2) %!error tlscdf (1, 2, 3) %!error tlscdf (1, 2, 3, 4, 'uper') %!error tlscdf (1, 2, 3, 4, 5) %!error ... %! tlscdf (ones (3), ones (2), 1, 1) %!error ... %! tlscdf (ones (3), 1, ones (2), 1) %!error ... %! tlscdf (ones (3), 1, 1, ones (2)) %!error ... %! tlscdf (ones (3), ones (2), 1, 1, 'upper') %!error ... %! tlscdf (ones (3), 1, ones (2), 1, 'upper') %!error ... %! tlscdf (ones (3), 1, 1, ones (2), 'upper') %!error tlscdf (int32 (2), 2, 1, 1) %!error tlscdf (true, 2, 1, 1) %!error tlscdf ('a', 2, 1, 1) %!error tlscdf (i, 2, 1, 1) %!error tlscdf (2, i, 1, 1) %!error tlscdf (2, 1, i, 1) %!error tlscdf (2, 1, 1, i) statistics-release-1.9.2/inst/Distribution_Functions/tlsinv.m000066400000000000000000000133401524624707500245310ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} tlsinv (@var{p}, @var{mu}, @var{sigma}, @var{nu}) ## ## Inverse of the location-scale Student's T cumulative distribution function ## (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the location-scale Student's T distribution with location parameter @var{mu}, ## scale parameter @var{sigma}, and @var{nu} degrees of freedom. The size of ## @var{x} is the common size of @var{p}, @var{mu}, @var{sigma}, and @var{nu}. ## A scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## Further information about the location-scale Student's T distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{tlscdf, tlspdf, tlsrnd, tlsfit, tlslike, tlsstat} ## @end deftypefn function x = tlsinv (p, mu, sigma, nu) ## Check for valid number of input arguments if (nargin < 4) error ("tlsinv: function called with too few input arguments."); endif ## Check for common size of P, MU, SIGMA, and NU if (! isscalar (p) || ! isscalar (mu) || ! isscalar (sigma) || ! isscalar (nu)) [retval, p, mu, sigma, nu] = common_size (p, mu, sigma, nu); if (retval > 0) error ("tlsinv: P, MU, SIGMA, and NU must be of common size or scalars."); endif endif ## Check for P, MU, SIGMA, and NU being double or single if (! (isfloat (p) && isfloat (mu) && isfloat (sigma) && isfloat (nu))) error ("tlsinv: P, MU, SIGMA, and NU must be double or single."); endif ## Check for P, MU, SIGMA, and NU being reals if (iscomplex (p) || iscomplex (mu) || iscomplex (sigma) || iscomplex (nu)) error ("tlsinv: P, MU, SIGMA, and NU must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (mu, 'single') || isa (sigma, 'single') || isa (nu, 'single')) cls = 'single'; else cls = 'double'; endif ## Force invalid SIGMA parameter to NaN sigma(sigma <= 0) = NaN; ## Call tinv to do the work x = tinv (p, nu) .* sigma + mu; ## Force class type x = cast (x, cls); endfunction %!demo %! ## Plot various iCDFs from the location-scale Student's T distribution %! p = 0.001:0.001:0.999; %! x1 = tlsinv (p, 0, 1, 1); %! x2 = tlsinv (p, 0, 2, 2); %! x3 = tlsinv (p, 3, 2, 5); %! x4 = tlsinv (p, -1, 3, Inf); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-m') %! grid on %! xlim ([0, 1]) %! ylim ([-8, 8]) %! legend ({'mu = 0, sigma = 1, nu = 1', 'mu = 0, sigma = 2, nu = 2', ... %! 'mu = 3, sigma = 2, nu = 5', 'mu = -1, sigma = 3, nu = \infty'}, ... %! 'location', 'southeast') %! title ('Location-scale Student''s T iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p %! p = [-1 0 0.5 1 2]; %!assert_equal (tlsinv (p, 0, 1, ones (1,5)), [NaN -Inf 0 Inf NaN]) %!assert_equal (tlsinv (p, 0, 1, 1), [NaN -Inf 0 Inf NaN], eps) %!assert_equal (tlsinv (p, 0, 1, [1 0 NaN 1 1]), [NaN NaN NaN Inf NaN], eps) %!assert_equal (tlsinv ([p(1:2) NaN p(4:5)], 0, 1, 1), [NaN -Inf NaN Inf NaN]) ## Test class of input preserved %!assert_equal (class (tlsinv ([p, NaN], 0, 1, 1)), "double") %!assert_equal (class (tlsinv (single ([p, NaN]), 0, 1, 1)), "single") %!assert_equal (class (tlsinv ([p, NaN], single (0), 1, 1)), "single") %!assert_equal (class (tlsinv ([p, NaN], 0, single (1), 1)), "single") %!assert_equal (class (tlsinv ([p, NaN], 0, 1, single (1))), "single") ## Test input validation %!error tlsinv () %!error tlsinv (1) %!error tlsinv (1, 2) %!error tlsinv (1, 2, 3) %!error ... %! tlsinv (ones (3), ones (2), 1, 1) %!error ... %! tlsinv (ones (2), 1, ones (3), 1) %!error ... %! tlsinv (ones (2), 1, 1, ones (3)) %!error tlsinv (int32 (2), 2, 3, 4) %!error tlsinv (true, 2, 3, 4) %!error tlsinv ('a', 2, 3, 4) %!error tlsinv (i, 2, 3, 4) %!error tlsinv (2, i, 3, 4) %!error tlsinv (2, 2, i, 4) %!error tlsinv (2, 2, 3, i) statistics-release-1.9.2/inst/Distribution_Functions/tlspdf.m000066400000000000000000000135531524624707500245140ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} tlspdf (@var{x}, @var{mu}, @var{sigma}, @var{nu}) ## ## Location-scale Student's T probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the location-scale Student's T distribution with location parameter ## @var{mu}, scale parameter @var{sigma}, and @var{nu} degrees of freedom. The ## size of @var{y} is the common size of @var{x}, @var{mu}, @var{sigma}, and ## @var{nu}. A scalar input functions as a constant matrix of the same size as ## the other inputs. ## ## Further information about the location-scale Student's T distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{tlscdf, tlsinv, tlsrnd, tlsfit, tlslike, tlsstat} ## @end deftypefn function y = tlspdf (x, mu, sigma, nu) ## Check for valid number of input arguments if (nargin < 4) error ("tlspdf: function called with too few input arguments."); endif ## Check for common size of X, MU, SIGMA, and NU if (! isscalar (x) || ! isscalar (mu) || ! isscalar (sigma) || ! isscalar (nu)) [err, x, mu, sigma, nu] = common_size (x, mu, sigma, nu); if (err > 0) error ("tlspdf: X, MU, SIGMA, and NU must be of common size or scalars."); endif endif ## Check for X, MU, SIGMA, and NU being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (sigma) && isfloat (nu))) error ("tlspdf: X, MU, SIGMA, and NU must be double or single."); endif ## Check for X, MU, SIGMA, and NU being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (sigma) || iscomplex (nu)) error ("tlspdf: X, MU, SIGMA, and NU must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (sigma, 'single') || isa (nu, 'single')) cls = 'single'; else cls = 'double'; endif ## Force invalid SIGMA parameter to NaN sigma(sigma <= 0) = NaN; ## Call tpdf to do the work y = tpdf ((x - mu) ./ sigma, nu) ./ sigma; ## Force class type y = cast (y, cls); endfunction %!demo %! ## Plot various PDFs from the Student's T distribution %! x = -8:0.01:8; %! y1 = tlspdf (x, 0, 1, 1); %! y2 = tlspdf (x, 0, 2, 2); %! y3 = tlspdf (x, 3, 2, 5); %! y4 = tlspdf (x, -1, 3, Inf); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-m') %! grid on %! xlim ([-8, 8]) %! ylim ([0, 0.41]) %! legend ({'mu = 0, sigma = 1, nu = 1', 'mu = 0, sigma = 2, nu = 2', ... %! 'mu = 3, sigma = 2, nu = 5', 'mu = -1, sigma = 3, nu = \infty'}, ... %! 'location', 'northwest') %! title ('Location-scale Student''s T PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!test %! x = rand (10,1); %! y = 1./(pi * (1 + x.^2)); %! assert_equal (tlspdf (x, 0, 1, 1), y, 5*eps); %! assert_equal (tlspdf (x+5, 5, 1, 1), y, 5*eps); %! assert_equal (tlspdf (x.*2, 0, 2, 1), y./2, 5*eps); %!shared x, y %! x = [-Inf 0 0.5 1 Inf]; %! y = 1./(pi * (1 + x.^2)); %!assert_equal (tlspdf (x, 0, 1, ones (1,5)), y, eps) %!assert_equal (tlspdf (x, 0, 1, 1), y, eps) %!assert_equal (tlspdf (x, 0, 1, [0 NaN 1 1 1]), [NaN NaN y(3:5)], eps) %!assert_equal (tlspdf (x, 0, 1, Inf), normpdf (x)) ## Test class of input preserved %!assert_equal (class (tlspdf ([x, NaN], 1, 1, 1)), "double") %!assert_equal (class (tlspdf (single ([x, NaN]), 1, 1, 1)), "single") %!assert_equal (class (tlspdf ([x, NaN], single (1), 1, 1)), "single") %!assert_equal (class (tlspdf ([x, NaN], 1, single (1), 1)), "single") %!assert_equal (class (tlspdf ([x, NaN], 1, 1, single (1))), "single") ## Test input validation %!error tlspdf () %!error tlspdf (1) %!error tlspdf (1, 2) %!error tlspdf (1, 2, 3) %!error ... %! tlspdf (ones (3), ones (2), 1, 1) %!error ... %! tlspdf (ones (2), 1, ones (3), 1) %!error ... %! tlspdf (ones (2), 1, 1, ones (3)) %!error tlspdf (int32 (2), 2, 1, 1) %!error tlspdf (true, 2, 1, 1) %!error tlspdf ('a', 2, 1, 1) %!error tlspdf (i, 2, 1, 1) %!error tlspdf (2, i, 1, 1) %!error tlspdf (2, 1, i, 1) %!error tlspdf (2, 1, 1, i) statistics-release-1.9.2/inst/Distribution_Functions/tlsrnd.m000066400000000000000000000173351524624707500245300ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} tlsrnd (@var{mu}, @var{sigma}, @var{nu}) ## @deftypefnx {statistics} {@var{r} =} tlsrnd (@var{mu}, @var{sigma}, @var{nu}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} tlsrnd (@var{mu}, @var{sigma}, @var{nu}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} tlsrnd (@var{mu}, @var{sigma}, @var{nu}, [@var{sz}]) ## ## Random arrays from the location-scale Student's T distribution. ## ## Return a matrix of random samples from the location-scale Student's T ## distribution with location parameter @var{mu}, scale parameter @var{sigma}, ## and @var{nu} degrees of freedom. ## ## @code{@var{r} = tlsrnd (@var{nu})} returns an array of random numbers chosen ## from the location-scale Student's T distribution with location parameter ## @var{mu}, scale parameter @var{sigma}, and @var{nu} degrees of freedom. The ## size of @var{r} is the common size of @var{mu}, @var{sigma}, and @var{nu}. A ## scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## When called with a single size argument, @code{tlsrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the location-scale Student's T distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution} ## ## @seealso{tlscdf, tlsinv, tlspdf, tlsfit, tlslike, tlsstat} ## @end deftypefn function r = tlsrnd (mu, sigma, nu, varargin) ## Check for valid number of input arguments if (nargin < 3) error ("tlsrnd: function called with too few input arguments."); endif ## Check for common size of MU, SIGMA, and NU if (! isscalar (mu) || ! isscalar (sigma) || ! isscalar (nu)) [retval, mu, sigma, nu] = common_size (mu, sigma, nu); if (retval > 0) error ("tlsrnd: MU, SIGMA, and NU must be of common size or scalars."); endif endif ## Check for NU being real if (iscomplex (mu) || iscomplex (sigma) || iscomplex (nu)) error ("tlsrnd: MU, SIGMA, and NU must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 3) sz = size (nu); elseif (nargin == 4) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("tlsrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 4) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("tlsrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (nu) && ! isequal (size (nu), size (ones (sz)))) error ("tlsrnd: MU, SIGMA, and NU must be scalar or of size SZ."); endif ## Check for class type if (isa (mu, 'single') || isa (sigma, 'single') || isa (nu, 'single')) cls = 'single'; else cls = 'double'; endif ## Call trnd to do the work r = mu + sigma .* trnd (nu, sz); ## Force class type r = cast (r, cls); endfunction ## Test output %!assert_equal (size (tlsrnd (1, 2, 3)), [1, 1]) %!assert_equal (size (tlsrnd (ones (2, 1), 2, 3)), [2, 1]) %!assert_equal (size (tlsrnd (ones (2, 2), 2, 3)), [2, 2]) %!assert_equal (size (tlsrnd (1, 2, 3, 3)), [3, 3]) %!assert_equal (size (tlsrnd (1, 2, 3, [4, 1])), [4, 1]) %!assert_equal (size (tlsrnd (1, 2, 3, 4, 1)), [4, 1]) %!assert_equal (size (tlsrnd (1, 2, 3, 4, 1)), [4, 1]) %!assert_equal (size (tlsrnd (1, 2, 3, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (tlsrnd (1, 2, 3, 0, 1)), [0, 1]) %!assert_equal (size (tlsrnd (1, 2, 3, 1, 0)), [1, 0]) %!assert_equal (size (tlsrnd (1, 2, 3, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (tlsrnd (1, 2, 3, [])), [0, 0]) %!assert_equal (size (tlsrnd (1, 2, 3, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (tlsrnd (1, 2, 3, -1)), [0, 0]) %!assert_equal (size (tlsrnd (1, 2, 3, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (tlsrnd (1, 2, 3, 2, -1, 5)), [2, 0, 5]) %!assert_equal (tlsrnd (1, 2, 0, 1, 1), NaN) %!assert_equal (tlsrnd (1, 2, [0, 0, 0], [1, 3]), [NaN, NaN, NaN]) ## Test class of input preserved %!assert_equal (class (tlsrnd (1, 2, 3)), "double") %!assert_equal (class (tlsrnd (single (1), 2, 3)), "single") %!assert_equal (class (tlsrnd (single ([1, 1]), 2, 3)), "single") %!assert_equal (class (tlsrnd (1, single (2), 3)), "single") %!assert_equal (class (tlsrnd (1, single ([2, 2]), 3)), "single") %!assert_equal (class (tlsrnd (1, 2, single (3))), "single") %!assert_equal (class (tlsrnd (1, 2, single ([3, 3]))), "single") ## Test input validation %!error tlsrnd () %!error tlsrnd (1) %!error tlsrnd (1, 2) %!error ... %! tlsrnd (ones (3), ones (2), 1) %!error ... %! tlsrnd (ones (2), 1, ones (3)) %!error ... %! tlsrnd (1, ones (2), ones (3)) %!error tlsrnd (i, 2, 3) %!error tlsrnd (1, i, 3) %!error tlsrnd (1, 2, i) %!error ... %! tlsrnd (1, 2, 3, 1.2) %!error ... %! tlsrnd (1, 2, 3, ones (2)) %!error ... %! tlsrnd (1, 2, 3, [2 0 2.5]) %!error ... %! tlsrnd (ones (2), 2, 3, ones (2)) %!error ... %! tlsrnd (1, 2, 3, 2, 1.5, 5) %!error ... %! tlsrnd (ones (2,2), 2, 3, 3) %!error ... %! tlsrnd (1, ones (2,2), 3, 3) %!error ... %! tlsrnd (1, 2, ones (2,2), 3) %!error ... %! tlsrnd (1, 2, ones (2,2), [3, 3]) %!error ... %! tlsrnd (1, 2, ones (2,2), 2, 3) statistics-release-1.9.2/inst/Distribution_Functions/tpdf.m000066400000000000000000000111711524624707500241470ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} tpdf (@var{x}, @var{df}) ## ## Student's T probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the Student's T distribution with @var{df} degrees of freedom. The size ## of @var{y} is the common size of @var{x} and @var{df}. A scalar input ## functions as a constant matrix of the same size as the other input. ## ## Further information about the Student's T distribution can be found at ## @url{https://en.wikipedia.org/wiki/Student%27s_t-distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{tcdf, tinv, trnd, tstat} ## @end deftypefn function y = tpdf (x, df) ## Check for valid number of input arguments if (nargin < 2) error ("tpdf: function called with too few input arguments."); endif ## Check for common size of X and DF if (! isscalar (x) || ! isscalar (df)) [retval, x, df] = common_size (x, df); if (retval > 0) error ("tpdf: X and DF must be of common size or scalars."); endif endif ## Check for X and DF being double or single if (! (isfloat (x) && isfloat (df))) error ("tpdf: X and DF must be double or single."); endif ## Check for X and DF being reals if (iscomplex (x) || iscomplex (df)) error ("tpdf: X and DF must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (df, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif k = isnan (x) | ! (df > 0); y(k) = NaN; k = isfinite (x) & (df > 0) & (df < Inf); kinf = isfinite (x) & isinf (df); if (any (k)) y(k) = exp (- (df(k) + 1) .* log (1 + x(k) .^ 2 ./ df(k)) / 2) ./ ... (sqrt (df(k)) .* beta (df(k)/2, 1/2)); endif if (any (kinf)) y(kinf) = normpdf (x(kinf)); endif endfunction %!demo %! ## Plot various PDFs from the Student's T distribution %! x = -5:0.01:5; %! y1 = tpdf (x, 1); %! y2 = tpdf (x, 2); %! y3 = tpdf (x, 5); %! y4 = tpdf (x, Inf); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-m') %! grid on %! xlim ([-5, 5]) %! ylim ([0, 0.41]) %! legend ({'df = 1', 'df = 2', ... %! 'df = 5', 'df = \infty'}, 'location', 'northeast') %! title ('Student''s T PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!test %! x = rand (10,1); %! y = 1./(pi * (1 + x.^2)); %! assert_equal (tpdf (x, 1), y, 5*eps); %!shared x, y %! x = [-Inf 0 0.5 1 Inf]; %! y = 1./(pi * (1 + x.^2)); %!assert_equal (tpdf (x, ones (1,5)), y, eps) %!assert_equal (tpdf (x, 1), y, eps) %!assert_equal (tpdf (x, [0 NaN 1 1 1]), [NaN NaN y(3:5)], eps) %!assert_equal (tpdf (x, Inf), normpdf (x)) ## Test class of input preserved %!assert_equal (tpdf ([x, NaN], 1), [y, NaN], eps) %!assert_equal (tpdf (single ([x, NaN]), 1), single ([y, NaN]), eps ('single')) %!assert_equal (tpdf ([x, NaN], single (1)), single ([y, NaN]), eps ('single')) ## Test input validation %!error tpdf () %!error tpdf (1) %!error ... %! tpdf (ones (3), ones (2)) %!error ... %! tpdf (ones (2), ones (3)) %!error tpdf (int32 (2), 2) %!error tpdf (true, 2) %!error tpdf ('a', 2) %!error tpdf (i, 2) %!error tpdf (2, i) statistics-release-1.9.2/inst/Distribution_Functions/tricdf.m000066400000000000000000000202241524624707500244640ustar00rootroot00000000000000## Copyright (C) 1997-2015 Kurt Hornik ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} tricdf (@var{x}, @var{a}, @var{b}, @var{c}) ## @deftypefnx {statistics} {@var{p} =} tricdf (@var{x}, @var{a}, @var{b}, @var{c}, @qcode{'upper'}) ## ## Triangular cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the triangular distribution with lower limit parameter @var{a}, peak ## location (mode) parameter @var{b}, and upper limit parameter @var{c}. The ## size of @var{p} is the common size of the input arguments. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## @code{@var{p} = tricdf (@var{x}, @var{a}, @var{b}, @var{c}, "upper")} ## computes the upper tail probability of the triangular distribution with ## parameters @var{a}, @var{b}, and @var{c}, at the values in @var{x}. ## ## Note that the order of the parameter input arguments has been changed after ## statistics version 1.6.3 in order to be MATLAB compatible with the parameters ## used in the TriangularDistribution probability distribution object. More ## specifically, the positions of the parameters @var{b} and @var{c} have been ## swapped. As a result, the naming conventions no longer coincide with those ## used in Wikipedia, in which @math{b} denotes the upper limit and @math{c} ## denotes the mode or peak parameter. ## ## Further information about the triangular distribution can be found at ## @url{https://en.wikipedia.org/wiki/Triangular_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## MATLAB also accepts integer input here, returning the result in the integer ## class of the input; Octave rejects it, as it does for every other continuous ## distribution. ## ## @seealso{triinv, tripdf, trirnd, tristat} ## @end deftypefn function p = tricdf (x, a, b, c, uflag) ## Check for valid number of input arguments if (nargin < 4) error ("tricdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 4) if (! strcmpi (uflag, 'upper')) error ("tricdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of A, B, and C if (! isscalar (x) || ! isscalar (a) || ! isscalar (b) || ! isscalar (c)) [retval, x, a, b, c] = common_size (x, a, b, c); if (retval > 0) error ("tricdf: X, A, B, and C must be of common size or scalars."); endif endif ## Check for X, A, B, and C being double or single if (! (isfloat (x) && isfloat (a) && isfloat (b) && isfloat (c))) error ("tricdf: X, A, B, and C must be double or single."); endif ## Check for X, BETA, and GAMMA being reals if (iscomplex (x) || iscomplex (a) || iscomplex (b) || iscomplex (c)) error ("tricdf: X, A, B, and C must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (a, 'single') || isa (b, 'single') ... || isa (c, 'single')) p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Force NaNs for out of range parameters. k = isnan (x) | ! (a < c) | ! (b >= a) | ! (b <= c); p(k) = NaN; ## Find valid values in parameters and data k = (a < c) & (a <= b) & (b <= c); k1 = (x <= a) & k; k2 = (x > a) & (x <= b) & k; k3 = (x > b) & (x < c) & k; k4 = (x >= c) & k; ## Compute triangular CDF if (uflag) p(k1) = 1; p(k2) = 1 - ((x(k2) - a(k2)) .^ 2) ./ ((c(k2) - a(k2)) .* (b(k2) - a(k2))); p(k3) = ((c(k3) - x(k3)) .^ 2) ./ ((c(k3) - a(k3)) .* (c(k3) - b(k3))); else p(k2) = ((x(k2) - a(k2)) .^ 2) ./ ((c(k2) - a(k2)) .* (b(k2) - a(k2))); p(k3) = 1 - ((c(k3) - x(k3)) .^ 2) ./ ((c(k3) - a(k3)) .* (c(k3) - b(k3))); p(k4) = 1; endif endfunction %!demo %! ## Plot various CDFs from the triangular distribution %! x = 0.001:0.001:10; %! p1 = tricdf (x, 3, 4, 6); %! p2 = tricdf (x, 1, 2, 5); %! p3 = tricdf (x, 2, 3, 9); %! p4 = tricdf (x, 2, 5, 9); %! plot (x, p1, '-b', x, p2, '-g', x, p3, '-r', x, p4, '-c') %! grid on %! xlim ([0, 10]) %! legend ({'a = 3, b = 4, c = 6', 'a = 1, b = 2, c = 5', ... %! 'a = 2, b = 3, c = 9', 'a = 2, b = 5, c = 9'}, ... %! 'location', 'southeast') %! title ('Triangular CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-1, 0, 0.1, 0.5, 0.9, 1, 2] + 1; %! y = [0, 0, 0.02, 0.5, 0.98, 1 1]; %!assert_equal (tricdf (x, ones (1,7), 1.5 * ones (1, 7), 2 * ones (1, 7)), y, eps) %!assert_equal (tricdf (x, 1 * ones (1, 7), 1.5, 2), y, eps) %!assert_equal (tricdf (x, 1 * ones (1, 7), 1.5, 2, 'upper'), 1 - y, eps) %!assert_equal (tricdf (x, 1, 1.5, 2 * ones (1, 7)), y, eps) %!assert_equal (tricdf (x, 1, 1.5 * ones (1, 7), 2), y, eps) %!assert_equal (tricdf (x, 1, 1.5, 2), y, eps) %!assert_equal (tricdf (x, [1, 1, NaN, 1, 1, 1, 1], 1.5, 2), ... %! [y(1:2), NaN, y(4:7)], eps) %!assert_equal (tricdf (x, 1, 1.5, 2*[1, 1, NaN, 1, 1, 1, 1]), ... %! [y(1:2), NaN, y(4:7)], eps) %!assert_equal (tricdf (x, 1, 1.5, 2*[1, 1, NaN, 1, 1, 1, 1]), ... %! [y(1:2), NaN, y(4:7)], eps) %!assert_equal (tricdf ([x, NaN], 1, 1.5, 2), [y, NaN], eps) ## Test class of input preserved %!assert_equal (tricdf (single ([x, NaN]), 1, 1.5, 2), ... %! single ([y, NaN]), eps ('single')) %!assert_equal (tricdf ([x, NaN], single (1), 1.5, 2), ... %! single ([y, NaN]), eps ('single')) %!assert_equal (tricdf ([x, NaN], 1, single (1.5), 2), ... %! single ([y, NaN]), eps ('single')) %!assert_equal (tricdf ([x, NaN], 1, 1.5, single (2)), ... %! single ([y, NaN]), eps ('single')) ## Test input validation %!error tricdf () %!error tricdf (1) %!error tricdf (1, 2) %!error tricdf (1, 2, 3) %!error ... %! tricdf (1, 2, 3, 4, 5, 6) %!error tricdf (1, 2, 3, 4, 'tail') %!error tricdf (1, 2, 3, 4, 5) %!error ... %! tricdf (ones (3), ones (2), ones (2), ones (2)) %!error ... %! tricdf (ones (2), ones (3), ones (2), ones (2)) %!error ... %! tricdf (ones (2), ones (2), ones (3), ones (2)) %!error ... %! tricdf (ones (2), ones (2), ones (2), ones (3)) %!error tricdf (int32 (2), 2, 3, 4) %!error tricdf (true, 2, 3, 4) %!error tricdf ('a', 2, 3, 4) %!error tricdf (i, 2, 3, 4) %!error tricdf (1, i, 3, 4) %!error tricdf (1, 2, i, 4) %!error tricdf (1, 2, 3, i) statistics-release-1.9.2/inst/Distribution_Functions/triinv.m000066400000000000000000000156401524624707500245320ustar00rootroot00000000000000## Copyright (B) 1995-2015 Kurt Hornik ## Copyright (B) 2016 Dag Lyberg ## Copyright (B) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} triinv (@var{p}, @var{a}, @var{b}, @var{c}) ## ## Inverse of the triangular cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the triangular distribution with lower limit parameter @var{a}, peak ## location (mode) parameter @var{b}, and upper limit parameter @var{c}. The ## size of @var{x} is the common size of the input arguments. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## Note that the order of the parameter input arguments has been changed after ## statistics version 1.6.3 in order to be MATLAB compatible with the parameters ## used in the TriangularDistribution probability distribution object. More ## specifically, the positions of the parameters @var{b} and @var{c} have been ## swapped. As a result, the naming conventions no longer coincide with those ## used in Wikipedia, in which @math{b} denotes the upper limit and @math{c} ## denotes the mode or peak parameter. ## ## Further information about the triangular distribution can be found at ## @url{https://en.wikipedia.org/wiki/Triangular_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{tricdf, tripdf, trirnd, tristat} ## @end deftypefn function x = triinv (p, a, b, c) ## Check for valid number of input arguments if (nargin < 4) error ("triinv: function called with too few input arguments."); endif ## Check for common size of P, A, B, and C if (! isscalar (p) || ! isscalar (a) || ! isscalar (b) || ! isscalar (c)) [retval, p, a, b, c] = common_size (p, a, b, c); if (retval > 0) error ("triinv: P, A, B, and C must be of common size or scalars."); endif endif ## Check for P, A, B, and C being double or single if (! (isfloat (p) && isfloat (a) && isfloat (b) && isfloat (c))) error ("triinv: P, A, B, and C must be double or single."); endif ## Check for P, A, B, and C being reals if (iscomplex (p) || iscomplex (a) || iscomplex (b) || iscomplex (c)) error ("triinv: P, A, B, and C must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (a, 'single') || isa (b, 'single') ... || isa (c, 'single')) x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ## Force zeros for within range parameters. k = (p >= 0) & (p <= 1) & (a < c) & (a <= b) & (b <= c); x(k) = 0; ## Compute triangular iCDF h = 2 ./ (c-a); w = b - a; area1 = h .* w / 2; j = k & (p <= area1); x(j) += (2 * p(j) .* (w(j) ./ h(j))) .^ 0.5 + a(j); w = c - b; j = k & (area1 < p) & (p < 1); x(j) += c(j) - (2 * (1 - p(j)) .* (w(j) ./ h(j))) .^ 0.5; j = k & (p == 1); x(j) = c(j); endfunction %!demo %! ## Plot various iCDFs from the triangular distribution %! p = 0.001:0.001:0.999; %! x1 = triinv (p, 3, 6, 4); %! x2 = triinv (p, 1, 5, 2); %! x3 = triinv (p, 2, 9, 3); %! x4 = triinv (p, 2, 9, 5); %! plot (p, x1, '-b', p, x2, '-g', p, x3, '-r', p, x4, '-c') %! grid on %! ylim ([0, 10]) %! legend ({'a = 3, b = 6, c = 4', 'a = 1, b = 5, c = 2', ... %! 'a = 2, b = 9, c = 3', 'a = 2, b = 9, c = 5'}, ... %! 'location', 'northwest') %! title ('Triangular CDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p, y %! p = [-1, 0, 0.02, 0.5, 0.98, 1, 2]; %! y = [NaN, 0, 0.1, 0.5, 0.9, 1, NaN] + 1; %!assert_equal (triinv (p, ones (1, 7), 1.5 * ones (1, 7), 2 * ones (1, 7)), y, eps) %!assert_equal (triinv (p, 1 * ones (1, 7), 1.5, 2), y, eps) %!assert_equal (triinv (p, 1, 1.5, 2 * ones (1, 7)), y, eps) %!assert_equal (triinv (p, 1, 1.5*ones (1,7), 2), y, eps) %!assert_equal (triinv (p, 1, 1.5, 2), y, eps) %!assert_equal (triinv (p, [1, 1, NaN, 1, 1, 1, 1], 1.5, 2), [y(1:2), NaN, y(4:7)], eps) %!assert_equal (triinv (p, 1, 1.5 * [1, 1, NaN, 1, 1, 1, 1], 2), [y(1:2), NaN, y(4:7)], eps) %!assert_equal (triinv (p, 1, 1.5, 2 * [1, 1, NaN, 1, 1, 1, 1]), [y(1:2), NaN, y(4:7)], eps) %!assert_equal (triinv ([p, NaN], 1, 1.5, 2), [y, NaN], eps) ## Test class of input preserved %!assert_equal (triinv (single ([p, NaN]), 1, 1.5, 2), single ([y, NaN]), eps ('single')) %!assert_equal (triinv ([p, NaN], single (1), 1.5, 2), single ([y, NaN]), eps ('single')) %!assert_equal (triinv ([p, NaN], 1, single (1.5), 2), single ([y, NaN]), eps ('single')) %!assert_equal (triinv ([p, NaN], 1, 1.5, single (2)), single ([y, NaN]), eps ('single')) ## Test input validation %!error triinv () %!error triinv (1) %!error triinv (1, 2) %!error triinv (1, 2, 3) %!error ... %! triinv (1, 2, 3, 4, 5) %!error ... %! triinv (ones (3), ones (2), ones (2), ones (2)) %!error ... %! triinv (ones (2), ones (3), ones (2), ones (2)) %!error ... %! triinv (ones (2), ones (2), ones (3), ones (2)) %!error ... %! triinv (ones (2), ones (2), ones (2), ones (3)) %!error triinv (int32 (2), 2, 3, 4) %!error triinv (true, 2, 3, 4) %!error triinv ('a', 2, 3, 4) %!error triinv (i, 2, 3, 4) %!error triinv (1, i, 3, 4) %!error triinv (1, 2, i, 4) %!error triinv (1, 2, 3, i) statistics-release-1.9.2/inst/Distribution_Functions/tripdf.m000066400000000000000000000160461524624707500245100ustar00rootroot00000000000000## Copyright (C) 1997-2015 Kurt Hornik ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} tripdf (@var{x}, @var{a}, @var{b}, @var{c}) ## ## Triangular probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the triangular distribution with lower limit parameter @var{a}, peak ## location (mode) parameter @var{b}, and upper limit parameter @var{c}. The ## size of @var{y} is the common size of the input arguments. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## Note that the order of the parameter input arguments has been changed after ## statistics version 1.6.3 in order to be MATLAB compatible with the parameters ## used in the TriangularDistribution probability distribution object. More ## specifically, the positions of the parameters @var{b} and @var{c} have been ## swapped. As a result, the naming conventions no longer coincide with those ## used in Wikipedia, in which @math{b} denotes the upper limit and @math{c} ## denotes the mode or peak parameter. ## ## Further information about the triangular distribution can be found at ## @url{https://en.wikipedia.org/wiki/Triangular_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## MATLAB also accepts integer input here, returning the result in the integer ## class of the input; Octave rejects it, as it does for every other continuous ## distribution. ## ## @seealso{tricdf, triinv, trirnd, tristat} ## @end deftypefn function y = tripdf (x, a, b, c) ## Check for valid number of input arguments if (nargin < 4) error ("tripdf: function called with too few input arguments."); endif ## Check for common size of X, A, B, and C if (! isscalar (x) || ! isscalar (a) || ! isscalar (b) || ! isscalar (c)) [retval, x, a, b, c] = common_size (x, a, b, c); if (retval > 0) error ("tripdf: X, A, B, and C must be of common size or scalars."); endif endif ## Check for X, A, B, and C being double or single if (! (isfloat (x) && isfloat (a) && isfloat (b) && isfloat (c))) error ("tripdf: X, A, B, and C must be double or single."); endif ## Check for X, A, B, and C being reals if (iscomplex (x) || iscomplex (a) || iscomplex (b) || iscomplex (c)) error ("tripdf: X, A, B, and C must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (a, 'single') || isa (b, 'single') ... || isa (c, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Force NaNs for out of range parameters. k = isnan (x) | ! (a < c) | ! (b >= a) | ! (b <= c); y(k) = NaN; k = (x >= a) & (x <= c) & (a < c) & (a <= b) & (b <= c); h = 2 ./ (c - a); j = k & (a <= x) & (x < b); y(j) = h(j) .* (x(j) - a(j)) ./ (b(j) - a(j)); j = k & (x == b); y(j) = h(j); j = k & (b < x) & (x <= c); y(j) = h(j) .* (c(j) - x(j)) ./ (c(j) - b(j)); endfunction %!demo %! ## Plot various CDFs from the triangular distribution %! x = 0.001:0.001:10; %! y1 = tripdf (x, 3, 4, 6); %! y2 = tripdf (x, 1, 2, 5); %! y3 = tripdf (x, 2, 3, 9); %! y4 = tripdf (x, 2, 5, 9); %! plot (x, y1, '-b', x, y2, '-g', x, y3, '-r', x, y4, '-c') %! grid on %! xlim ([0, 10]) %! legend ({'a = 3, b = 4, c = 6', 'a = 1, b = 2, c = 5', ... %! 'a = 2, b = 3, c = 9', 'a = 2, b = 5, c = 9'}, ... %! 'location', 'northeast') %! title ('Triangular CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y, deps %! x = [-1, 0, 0.1, 0.5, 0.9, 1, 2] + 1; %! y = [0, 0, 0.4, 2, 0.4, 0, 0]; %! deps = 2*eps; %!assert_equal (tripdf (x, ones (1,7), 1.5*ones (1,7), 2*ones (1,7)), y, deps) %!assert_equal (tripdf (x, 1*ones (1,7), 1.5, 2), y, deps) %!assert_equal (tripdf (x, 1, 1.5, 2*ones (1,7)), y, deps) %!assert_equal (tripdf (x, 1, 1.5*ones (1,7), 2), y, deps) %!assert_equal (tripdf (x, 1, 1.5, 2), y, deps) %!assert_equal (tripdf (x, [1, 1, NaN, 1, 1, 1, 1], 1.5, 2), [y(1:2), NaN, y(4:7)], deps) %!assert_equal (tripdf (x, 1, 1.5, 2*[1, 1, NaN, 1, 1, 1, 1]), [y(1:2), NaN, y(4:7)], deps) %!assert_equal (tripdf (x, 1, 1.5*[1, 1, NaN, 1, 1, 1, 1], 2), [y(1:2), NaN, y(4:7)], deps) %!assert_equal (tripdf ([x, NaN], 1, 1.5, 2), [y, NaN], deps) ## Test class of input preserved %!assert_equal (tripdf (single ([x, NaN]), 1, 1.5, 2), single ([y, NaN]), eps ('single')) %!assert_equal (tripdf ([x, NaN], single (1), 1.5, 2), single ([y, NaN]), eps ('single')) %!assert_equal (tripdf ([x, NaN], 1, 1.5, single (2)), single ([y, NaN]), eps ('single')) %!assert_equal (tripdf ([x, NaN], 1, single (1.5), 2), single ([y, NaN]), eps ('single')) ## Test input validation %!error tripdf () %!error tripdf (1) %!error tripdf (1, 2) %!error tripdf (1, 2, 3) %!error ... %! tripdf (1, 2, 3, 4, 5) %!error ... %! tripdf (ones (3), ones (2), ones (2), ones (2)) %!error ... %! tripdf (ones (2), ones (3), ones (2), ones (2)) %!error ... %! tripdf (ones (2), ones (2), ones (3), ones (2)) %!error ... %! tripdf (ones (2), ones (2), ones (2), ones (3)) %!error tripdf (int32 (2), 2, 3, 4) %!error tripdf (true, 2, 3, 4) %!error tripdf ('a', 2, 3, 4) %!error tripdf (i, 2, 3, 4) %!error tripdf (1, i, 3, 4) %!error tripdf (1, 2, i, 4) %!error tripdf (1, 2, 3, i) statistics-release-1.9.2/inst/Distribution_Functions/trirnd.m000066400000000000000000000203621524624707500245160ustar00rootroot00000000000000## Copyright (C) 1997-2015 Kurt Hornik ## Copyright (C) 2016 Dag Lyberg ## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} trirnd (@var{a}, @var{b}, @var{c}) ## @deftypefnx {statistics} {@var{r} =} trirnd (@var{a}, @var{b}, @var{c}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} trirnd (@var{a}, @var{b}, @var{c}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} trirnd (@var{a}, @var{b}, @var{c}, [@var{sz}]) ## ## Random arrays from the triangular distribution. ## ## @code{@var{r} = trirnd (@var{sigma})} returns an array of random numbers ## chosen from the triangular distribution with lower limit parameter @var{a}, ## peak location (mode) parameter @var{b}, and upper limit parameter @var{c}. ## The size of @var{r} is the common size of @var{a}, @var{b}, and @var{c}. A ## scalar input functions as a constant matrix of the same size as the other ## inputs. ## ## When called with a single size argument, @code{trirnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Note that the order of the parameter input arguments has been changed after ## statistics version 1.6.3 in order to be MATLAB compatible with the parameters ## used in the TriangularDistribution probability distribution object. More ## specifically, the positions of the parameters @var{b} and @var{c} have been ## swapped. As a result, the naming conventions no longer coincide with those ## used in Wikipedia, in which @math{b} denotes the upper limit and @math{c} ## denotes the mode or peak parameter. ## ## Further information about the triangular distribution can be found at ## @url{https://en.wikipedia.org/wiki/Triangular_distribution} ## ## @seealso{tricdf, triinv, tripdf, tristat} ## @end deftypefn function r = trirnd (a, b, c, varargin) ## Check for valid number of input arguments if (nargin < 3) error ("trirnd: function called with too few input arguments."); endif ## Check for common size of A, B, and C if (! isscalar (a) || ! isscalar (b) || ! isscalar (c)) [retval, a, b, c] = common_size (a, b, c); scalarABC = false; if (retval > 0) error ("trirnd: A, B, and C must be of common size or scalars."); endif else scalarABC = true; endif ## Check for A, B, and C being reals if (iscomplex (a) || iscomplex (b) || iscomplex (c)) error ("trirnd: A, B, and C must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 3) sz = size (a); elseif (nargin == 4) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("trirnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 4) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("trirnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (a) && ! isequal (size (a), size (ones (sz)))) error ("trirnd: A, B, and C must be scalar or of size SZ."); endif ## Check for class type if (isa (a, 'single') || isa (b, 'single') || isa (c, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from triangular distribution if (scalarABC) if ((-Inf < a) && (a < c) && (a <= b) && (b <= c) && (c < Inf)) w = c-a; left_width = b-a; right_width = c-b; h = 2 / w; left_area = h * left_width / 2; r = rand (sz, cls); idx = r < left_area; r(idx) = a + (r(idx) * w * left_width).^0.5; r(! idx) = c - ((1-r(! idx)) * w * right_width).^0.5; else r = NaN (sz, cls); endif else w = c-a; left_width = b-a; right_width = c-b; h = 2 ./ w; left_area = h .* left_width / 2; r = rand (sz, cls); k = r < left_area; r(k) = a(k) + (r(k) .* w(k) .* left_width(k)).^0.5; r(! k) = c(! k) - ((1-r(! k)) .* w(! k) .* right_width(! k)).^0.5; k = ! (-Inf < a) | ! (a < c) | ! (a <= b) | ! (b <= c) | ! (c < Inf); r(k) = NaN; endif endfunction ## Test results %!assert_equal (size (trirnd (1, 1.5, 2)), [1, 1]) %!assert_equal (size (trirnd (1 * ones (2, 1), 1.5, 2)), [2, 1]) %!assert_equal (size (trirnd (1 * ones (2, 2), 1.5, 2)), [2, 2]) %!assert_equal (size (trirnd (1, 1.5 * ones (2, 1), 2)), [2, 1]) %!assert_equal (size (trirnd (1, 1.5 * ones (2, 2), 2)), [2, 2]) %!assert_equal (size (trirnd (1, 1.5, 2 * ones (2, 1))), [2, 1]) %!assert_equal (size (trirnd (1, 1.5, 2 * ones (2, 2))), [2, 2]) %!assert_equal (size (trirnd (1, 1.5, 2, 3)), [3, 3]) %!assert_equal (size (trirnd (1, 1.5, 2, [4, 1])), [4, 1]) %!assert_equal (size (trirnd (1, 1.5, 2, 4, 1)), [4, 1]) %!assert_equal (size (trirnd (1, 1.5, 2, [])), [0, 0]) %!assert_equal (size (trirnd (1, 1.5, 2, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (trirnd (1, 5, 3, -1)), [0, 0]) %!assert_equal (size (trirnd (1, 5, 3, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (trirnd (1, 5, 3, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (trirnd (1, 1.5, 2)), "double") %!assert_equal (class (trirnd (single (1), 1.5, 2)), "single") %!assert_equal (class (trirnd (single ([1, 1]), 1.5, 2)), "single") %!assert_equal (class (trirnd (1, single (1.5), 2)), "single") %!assert_equal (class (trirnd (1, single ([1.5, 1.5]), 2)), "single") %!assert_equal (class (trirnd (1, 1.5, single (1.5))), "single") %!assert_equal (class (trirnd (1, 1.5, single ([2, 2]))), "single") ## Test input validation %!error trirnd () %!error trirnd (1) %!error trirnd (1, 2) %!error ... %! trirnd (ones (3), 5 * ones (2), ones (2)) %!error ... %! trirnd (ones (2), 5 * ones (3), ones (2)) %!error ... %! trirnd (ones (2), 5 * ones (2), ones (3)) %!error trirnd (i, 5, 3) %!error trirnd (1, 5+i, 3) %!error trirnd (1, 5, i) %!error ... %! trirnd (1, 5, 3, 1.2) %!error ... %! trirnd (1, 5, 3, ones (2)) %!error ... %! trirnd (1, 5, 3, [2 0 2.5]) %!error ... %! trirnd (1, 5, 3, 2, 1.5, 5) %!error ... %! trirnd (2, 5 * ones (2), 2, 3) %!error ... %! trirnd (2, 5 * ones (2), 2, [3, 2]) %!error ... %! trirnd (2, 5 * ones (2), 2, 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/trnd.m000066400000000000000000000137151524624707500241670ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} trnd (@var{df}) ## @deftypefnx {statistics} {@var{r} =} trnd (@var{df}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} trnd (@var{df}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} trnd (@var{df}, [@var{sz}]) ## ## Random arrays from the Student's T distribution. ## ## Return a matrix of random samples from the Students's T distribution with ## @var{df} degrees of freedom. ## ## @code{@var{r} = trnd (@var{df})} returns an array of random numbers chosen ## from the Student's T distribution with @var{df} degrees of freedom. The size ## of @var{r} is the size of @var{df}. A scalar input functions as a constant ## matrix of the same size as the other inputs. @var{df} must be a finite real ## number greater than 0, otherwise NaN is returned. ## ## When called with a single size argument, @code{trnd} returns a square matrix ## with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the Student's T distribution can be found at ## @url{https://en.wikipedia.org/wiki/Student%27s_t-distribution} ## ## @seealso{tcdf, tinv, tpdf, tstat} ## @end deftypefn function r = trnd (df, varargin) ## Check for valid number of input arguments if (nargin < 1) error ("trnd: function called with too few input arguments."); endif ## Check for DF being real if (iscomplex (df)) error ("trnd: DF must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 1) sz = size (df); elseif (nargin == 2) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("trnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 2) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("trnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (df) && ! isequal (size (df), size (ones (sz)))) error ("trnd: DF must be scalar or of size SZ."); endif ## Check for class type if (isa (df, 'single')) cls = 'single'; else cls = 'double'; endif if (isscalar (df)) if ((df > 0) && (df < Inf)) r = randn (sz, cls) ./ sqrt (2*randg (df/2, sz, cls) / df); elseif (isinf (df)) r = randn (sz, cls); else r = NaN (sz, cls); endif else r = NaN (sz, cls); k = (df > 0) & (df < Inf); kinf = isinf (df); r(k) = randn (sum (k(:)), 1, cls) ./ ... sqrt (2*randg (df(k)/2, cls) ./ df(k))(:); r(kinf) = randn (sum (kinf(:)), 1, cls); endif endfunction ## Test output %!assert_equal (size (trnd (2)), [1, 1]) %!assert_equal (size (trnd (ones (2, 1))), [2, 1]) %!assert_equal (size (trnd (ones (2, 2))), [2, 2]) %!assert_equal (size (trnd (1, 3)), [3, 3]) %!assert_equal (size (trnd (1, [4, 1])), [4, 1]) %!assert_equal (size (trnd (1, 4, 1)), [4, 1]) %!assert_equal (size (trnd (1, 4, 1)), [4, 1]) %!assert_equal (size (trnd (1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (trnd (1, 0, 1)), [0, 1]) %!assert_equal (size (trnd (1, 1, 0)), [1, 0]) %!assert_equal (size (trnd (1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (trnd (1, [])), [0, 0]) %!assert_equal (size (trnd (1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (trnd (1, -1)), [0, 0]) %!assert_equal (size (trnd (1, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (trnd (1, 2, -1, 5)), [2, 0, 5]) %!assert_equal (trnd (0, 1, 1), NaN) %!assert_equal (trnd ([0, 0, 0], [1, 3]), [NaN, NaN, NaN]) ## Test class of input preserved %!assert_equal (class (trnd (2)), "double") %!assert_equal (class (trnd (single (2))), "single") %!assert_equal (class (trnd (single ([2, 2]))), "single") ## Test input validation %!error trnd () %!error trnd (i) %!error ... %! trnd (1, 1.2) %!error ... %! trnd (1, ones (2)) %!error ... %! trnd (1, [2 0 2.5]) %!error ... %! trnd (ones (2), ones (2)) %!error ... %! trnd (1, 2, 1.5, 5) %!error trnd (ones (2,2), 3) %!error trnd (ones (2,2), [3, 2]) %!error trnd (ones (2,2), 2, 3) statistics-release-1.9.2/inst/Distribution_Functions/unidcdf.m000066400000000000000000000142471524624707500246350ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 2007-2016 David Bateman ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} unidcdf (@var{x}, @var{N}) ## @deftypefnx {statistics} {@var{p} =} unidcdf (@var{x}, @var{N}, @qcode{'upper'}) ## ## Discrete uniform cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of a discrete uniform distribution with parameter @var{N}, which ## corresponds to the maximum observable value. @code{unidcdf} assumes the ## integer values in the range @math{[1,N]} with equal probability. The size of ## @var{p} is the common size of @var{x} and @var{N}. A scalar input functions ## as a constant matrix of the same size as the other inputs. ## ## The maximum observable values in @var{N} must be positive integers, otherwise ## @qcode{NaN} is returned. ## ## @code{[@dots{}] = unidcdf (@var{x}, @var{N}, "upper")} computes the upper ## tail probability of the discrete uniform distribution with maximum observable ## value @var{N}, at the values in @var{x}. ## ## Warning: The underlying implementation uses the double class and will only ## be accurate for @var{N} < @code{flintmax} (@w{@math{2^{53}}} on ## IEEE 754 compatible systems). ## ## Further information about the discrete uniform distribution can be found at ## @url{https://en.wikipedia.org/wiki/Discrete_uniform_distribution} ## ## Input arguments must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted to ## @qcode{double}, so the result is always a probability. MATLAB is ## inconsistent here: for several of the discrete distributions it returns the ## result in the integer class of the input, truncating a probability to ## @math{0} or @math{1}. ## ## @seealso{unidinv, unidpdf, unidrnd, unidfit, unidstat} ## @end deftypefn function p = unidcdf (x, N, uflag) ## Check for valid number of input arguments if (nargin < 2) error ("unidcdf: function called with too few input arguments."); endif ## Check for "upper" flag if (nargin > 2 && strcmpi (uflag, 'upper')) uflag = true; elseif (nargin > 2 && ! strcmpi (uflag, 'upper')) error ("unidcdf: invalid argument for upper tail."); else uflag = false; endif ## Check for common size of X and N if (! isscalar (x) || ! isscalar (N)) [retval, x, N] = common_size (x, N); if (retval > 0) error ("unidcdf: X and N must be of common size or scalars."); endif endif ## Check for X and N being double, single, or integer if (! (isnumeric (x) && isnumeric (N))) error ("unidcdf: X and N must be double, single, or integer."); endif ## Integer input is promoted to double, so the result is a probability ## rather than a value truncated to the input's integer type. if (isinteger (x)) x = double (x); endif if (isinteger (N)) N = double (N); endif ## Check for X and N being reals if (iscomplex (x) || iscomplex (N)) error ("unidcdf: X and N must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (N, 'single')) p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Return 1 for X >= N p(x >= N) = 1; ## Floor X xf = floor (x); ## Compute uniform discrete CDF k = find (xf >= 1 & xf <= N); if any (k) p(k) = xf(k) ./ N(k); endif ## Check for NaNs or floored N <= 0 is_nan = isnan (x) | ! (N > 0 & N == fix (N)); if (any (is_nan(:))) p(is_nan) = NaN; endif p(N < 1 | round (N) != N) = NaN; if (uflag) # Compute upper tail p = 1 - unidcdf (x, N); endif endfunction %!demo %! ## Plot various CDFs from the discrete uniform distribution %! x = 0:10; %! p1 = unidcdf (x, 5); %! p2 = unidcdf (x, 9); %! plot (x, p1, '*b', x, p2, '*g') %! grid on %! xlim ([0, 10]) %! ylim ([0, 1]) %! legend ({'N = 5', 'N = 9'}, 'location', 'southeast') %! title ('Discrete uniform CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [0 1 2.5 10 11]; %! y = [0, 0.1 0.2 1.0 1.0]; %!assert_equal (unidcdf (x, 10*ones (1,5)), y) %!assert_equal (unidcdf (x, 10*ones (1,5), 'upper'), 1 - y) %!assert_equal (unidcdf (x, 10), y) %!assert_equal (unidcdf (x, 10, 'upper'), 1 - y) %!assert_equal (unidcdf (x, 10*[0 1 NaN 1 1]), [NaN 0.1 NaN y(4:5)]) %!assert_equal (unidcdf ([x(1:2) NaN Inf x(5)], 10), [y(1:2) NaN 1 y(5)]) ## Test class of input preserved %!assert_equal (unidcdf ([x, NaN], 10), [y, NaN]) %!assert_equal (unidcdf (single ([x, NaN]), 10), single ([y, NaN])) %!assert_equal (unidcdf ([x, NaN], single (10)), single ([y, NaN])) ## Test input validation %!error unidcdf () %!error unidcdf (1) %!error unidcdf (1, 2, 3) %!error unidcdf (1, 2, 'tail') %!error ... %! unidcdf (ones (3), ones (2)) %!error ... %! unidcdf (ones (2), ones (3)) %!error unidcdf (true, 2) %!error unidcdf ('a', 2) %!assert_equal (class (unidcdf (int32 (2), 2)), 'double') %!error unidcdf (i, 2) %!error unidcdf (2, i) statistics-release-1.9.2/inst/Distribution_Functions/unidinv.m000066400000000000000000000115441524624707500246720ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 2007-2016 David Bateman ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} unidinv (@var{p}, @var{N}) ## ## Inverse of the discrete uniform cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the discrete uniform distribution with parameter @var{N}, which corresponds ## to the maximum observable value. @code{unidinv} assumes the integer values ## in the range @math{[1,N]} with equal probability. The size of @var{x} is the ## common size of @var{p} and @var{N}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## The maximum observable values in @var{N} must be positive integers, otherwise ## @qcode{NaN} is returned. ## ## Warning: The underlying implementation uses the double class and will only ## be accurate for @var{N} < @code{flintmax} (@w{@math{2^{53}}} on ## IEEE 754 compatible systems). ## ## Further information about the discrete uniform distribution can be found at ## @url{https://en.wikipedia.org/wiki/Discrete_uniform_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{unidcdf, unidpdf, unidrnd, unidfit, unidstat} ## @end deftypefn function x = unidinv (p, N) ## Check for valid number of input arguments if (nargin < 2) error ("unidinv: function called with too few input arguments."); endif ## Check for common size of P and N if (! isscalar (p) || ! isscalar (N)) [retval, p, N] = common_size (p, N); if (retval > 0) error ("unidinv: P and N must be of common size or scalars."); endif endif ## Check for P and N being double or single if (! (isfloat (p) && isfloat (N))) error ("unidinv: P and N must be double or single."); endif ## Check for P and N being reals if (iscomplex (p) || iscomplex (N)) error ("unidinv: P and N must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (N, 'single')) x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ## For Matlab compatibility, unidinv(0) = NaN k = (p > 0) & (p <= 1) & (N > 0 & N == fix (N)); x(k) = floor (p(k) .* N(k)); endfunction %!demo %! ## Plot various iCDFs from the discrete uniform distribution %! p = 0.001:0.001:0.999; %! x1 = unidinv (p, 5); %! x2 = unidinv (p, 9); %! plot (p, x1, '-b', p, x2, '-g') %! grid on %! xlim ([0, 1]) %! ylim ([0, 10]) %! legend ({'N = 5', 'N = 9'}, 'location', 'northwest') %! title ('Discrete uniform iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p %! p = [-1 0 0.5 1 2]; %!assert_equal (unidinv (p, 10*ones (1,5)), [NaN NaN 5 10 NaN], eps) %!assert_equal (unidinv (p, 10), [NaN NaN 5 10 NaN], eps) %!assert_equal (unidinv (p, 10*[0 1 NaN 1 1]), [NaN NaN NaN 10 NaN], eps) %!assert_equal (unidinv ([p(1:2) NaN p(4:5)], 10), [NaN NaN NaN 10 NaN], eps) ## Test class of input preserved %!assert_equal (unidinv ([p, NaN], 10), [NaN NaN 5 10 NaN NaN], eps) %!assert_equal (unidinv (single ([p, NaN]), 10), single ([NaN NaN 5 10 NaN NaN]), eps) %!assert_equal (unidinv ([p, NaN], single (10)), single ([NaN NaN 5 10 NaN NaN]), eps) ## Test input validation %!error unidinv () %!error unidinv (1) %!error ... %! unidinv (ones (3), ones (2)) %!error ... %! unidinv (ones (2), ones (3)) %!error unidinv (int32 (2), 2) %!error unidinv (true, 2) %!error unidinv ('a', 2) %!error unidinv (i, 2) %!error unidinv (2, i) statistics-release-1.9.2/inst/Distribution_Functions/unidpdf.m000066400000000000000000000120201524624707500246350ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 2007-2016 David Bateman ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} unidpdf (@var{x}, @var{N}) ## ## Discrete uniform probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the discrete uniform distribution with parameter @var{N}, which ## corresponds to the maximum observable value. @code{unidpdf} assumes the ## integer values in the range @math{[1,N]} with equal probability. The size of ## @var{x} is the common size of @var{p} and @var{N}. A scalar input functions ## as a constant matrix of the same size as the other inputs. ## ## The maximum observable values in @var{N} must be positive integers, otherwise ## @qcode{NaN} is returned. ## ## Warning: The underlying implementation uses the double class and will only ## be accurate for @var{N} < @code{flintmax} (@w{@math{2^{53}}} on ## IEEE 754 compatible systems). ## ## Further information about the discrete uniform distribution can be found at ## @url{https://en.wikipedia.org/wiki/Discrete_uniform_distribution} ## ## Input arguments must be @qcode{double}, @qcode{single}, or an integer type; ## logical and character arrays are rejected. Integer input is promoted to ## @qcode{double}, so the result is always a probability. MATLAB is ## inconsistent here: for several of the discrete distributions it returns the ## result in the integer class of the input, truncating a probability to ## @math{0} or @math{1}. ## ## @seealso{unidcdf, unidinv, unidrnd, unidfit, unidstat} ## @end deftypefn function y = unidpdf (x, N) ## Check for valid number of input arguments if (nargin < 2) error ("unidpdf: function called with too few input arguments."); endif ## Check for common size of X and N if (! isscalar (x) || ! isscalar (N)) [retval, x, N] = common_size (x, N); if (retval > 0) error ("unidpdf: X and N must be of common size or scalars."); endif endif ## Check for X and N being double, single, or integer if (! (isnumeric (x) && isnumeric (N))) error ("unidpdf: X and N must be double, single, or integer."); endif ## Integer input is promoted to double, so the result is a probability ## rather than a value truncated to the input's integer type. if (isinteger (x)) x = double (x); endif if (isinteger (N)) N = double (N); endif ## Check for X and N being reals if (iscomplex (x) || iscomplex (N)) error ("unidpdf: X and N must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (N, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif k = isnan (x) | ! (N > 0 & N == fix (N)); y(k) = NaN; k = ! k & (x >= 1) & (x <= N) & (x == fix (x)); y(k) = 1 ./ N(k); endfunction %!demo %! ## Plot various PDFs from the discrete uniform distribution %! x = 0:10; %! y1 = unidpdf (x, 5); %! y2 = unidpdf (x, 9); %! plot (x, y1, '*b', x, y2, '*g') %! grid on %! xlim ([0, 10]) %! ylim ([0, 0.25]) %! legend ({'N = 5', 'N = 9'}, 'location', 'northeast') %! title ('Discrete uniform PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1 0 1 2 10 11]; %! y = [0 0 0.1 0.1 0.1 0]; %!assert_equal (unidpdf (x, 10*ones (1,6)), y) %!assert_equal (unidpdf (x, 10), y) %!assert_equal (unidpdf (x, 10*[0 NaN 1 1 1 1]), [NaN NaN y(3:6)]) %!assert_equal (unidpdf ([x, NaN], 10), [y, NaN]) ## Test class of input preserved %!assert_equal (unidpdf (single ([x, NaN]), 10), single ([y, NaN])) %!assert_equal (unidpdf ([x, NaN], single (10)), single ([y, NaN])) ## Test input validation %!error unidpdf () %!error unidpdf (1) %!error ... %! unidpdf (ones (3), ones (2)) %!error ... %! unidpdf (ones (2), ones (3)) %!error unidpdf (true, 2) %!error unidpdf ('a', 2) %!assert_equal (class (unidpdf (int32 (2), 2)), 'double') %!error unidpdf (i, 2) %!error unidpdf (2, i) statistics-release-1.9.2/inst/Distribution_Functions/unidrnd.m000066400000000000000000000142201524624707500246530ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 2005-2016 John W. Eaton ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} unidrnd (@var{N}) ## @deftypefnx {statistics} {@var{r} =} unidrnd (@var{N}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} unidrnd (@var{N}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} unidrnd (@var{N}, [@var{sz}]) ## ## Random arrays from the discrete uniform distribution. ## ## @code{@var{r} = unidrnd (@var{N})} returns an array of random numbers chosen ## from the discrete uniform distribution with parameter @var{N}, which ## corresponds to the maximum observable value. @code{unidrnd} assumes the ## integer values in the range @math{[1,N]} with equal probability. The size of ## @var{r} is the size of @var{N}. A scalar input functions as a constant ## matrix of the same size as the other inputs. ## ## The maximum observable values in @var{N} must be positive integers, otherwise ## @qcode{NaN} is returned. ## ## When called with a single size argument, @code{unidrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Warning: The underlying implementation uses the double class and will only ## be accurate for @var{N} < @code{flintmax} (@w{@math{2^{53}}} on ## IEEE 754 compatible systems). ## ## Further information about the discrete uniform distribution can be found at ## @url{https://en.wikipedia.org/wiki/Discrete_uniform_distribution} ## ## @seealso{unidcdf, unidinv, unidpdf, unidfit, unidstat} ## @end deftypefn function r = unidrnd (N, varargin) ## Check for valid number of input arguments if (nargin < 1) error ("unidrnd: function called with too few input arguments."); endif ## Check for N being real if (iscomplex (N)) error ("unidrnd: N must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 1) sz = size (N); elseif (nargin == 2) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("unidrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 2) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("unidrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (N) && ! isequal (size (N), size (ones (sz)))) error ("unidrnd: N must be scalar or of size SZ."); endif ## Check for class type if (isa (N, 'single')) cls = 'single'; else cls = 'double'; endif if (isscalar (N)) if (N > 0 && N == fix (N)) r = ceil (rand (sz, cls) * N); else r = NaN (sz, cls); endif else r = ceil (rand (sz, cls) .* N); k = ! (N > 0 & N == fix (N)); r(k) = NaN; endif endfunction ## Test output %!assert_equal (size (unidrnd (2)), [1, 1]) %!assert_equal (size (unidrnd (ones (2, 1))), [2, 1]) %!assert_equal (size (unidrnd (ones (2, 2))), [2, 2]) %!assert_equal (size (unidrnd (1, 3)), [3, 3]) %!assert_equal (size (unidrnd (1, [4, 1])), [4, 1]) %!assert_equal (size (unidrnd (1, 4, 1)), [4, 1]) %!assert_equal (size (unidrnd (1, 4, 1)), [4, 1]) %!assert_equal (size (unidrnd (1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (unidrnd (1, 0, 1)), [0, 1]) %!assert_equal (size (unidrnd (1, 1, 0)), [1, 0]) %!assert_equal (size (unidrnd (1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (unidrnd (1, [])), [0, 0]) %!assert_equal (size (unidrnd (1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (unidrnd (1, -1)), [0, 0]) %!assert_equal (size (unidrnd (1, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (unidrnd (1, 2, -1, 5)), [2, 0, 5]) %!assert_equal (unidrnd (0, 1, 1), NaN) %!assert_equal (unidrnd ([0, 0, 0], [1, 3]), [NaN, NaN, NaN]) ## Test class of input preserved %!assert_equal (class (unidrnd (2)), "double") %!assert_equal (class (unidrnd (single (2))), "single") %!assert_equal (class (unidrnd (single ([2, 2]))), "single") ## Test input validation %!error unidrnd () %!error unidrnd (i) %!error ... %! unidrnd (1, 1.2) %!error ... %! unidrnd (1, ones (2)) %!error ... %! unidrnd (1, [2 0 2.5]) %!error ... %! unidrnd (ones (2), ones (2)) %!error ... %! unidrnd (1, 2, 1.5, 5) %!error unidrnd (ones (2,2), 3) %!error unidrnd (ones (2,2), [3, 2]) %!error unidrnd (ones (2,2), 2, 3) statistics-release-1.9.2/inst/Distribution_Functions/unifcdf.m000066400000000000000000000153301524624707500246310ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} unifcdf (@var{x}, @var{a}, @var{b}) ## @deftypefnx {statistics} {@var{p} =} unifcdf (@var{x}, @var{a}, @var{b}, @qcode{'upper'}) ## ## Continuous uniform cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the continuous uniform distribution with parameters @var{a} and ## @var{b}, which define the lower and upper bounds of the interval ## @qcode{[@var{a}, @var{b}]}. The size of @var{p} is the common size of ## @var{x}, @var{a}, and @var{b}. A scalar input functions as a constant matrix ## of the same size as the other inputs. ## ## @code{[@dots{}] = unifcdf (@var{x}, @var{a}, @var{b}, "upper")} computes the ## upper tail probability of the continuous uniform distribution with parameters ## @var{a}, and @var{b}, at the values in @var{x}. ## ## Further information about the continuous uniform distribution can be found at ## @url{https://en.wikipedia.org/wiki/Continuous_uniform_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## MATLAB also accepts integer input here, returning the result in the integer ## class of the input; Octave rejects it, as it does for every other continuous ## distribution. ## ## @seealso{unifinv, unifpdf, unifrnd, unifit, unifstat} ## @end deftypefn function p = unifcdf (x, a, b, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("unifcdf: function called with too few input arguments."); endif ## Check for "upper" flag if (nargin > 3 && strcmpi (uflag, 'upper')) uflag = true; elseif (nargin > 3 && ! strcmpi (uflag, 'upper')) error ("unifcdf: invalid argument for upper tail."); else uflag = false; endif ## Check for common size of X, A, and B if (! isscalar (x) || ! isscalar (a) || ! isscalar (b)) [retval, x, a, b] = common_size (x, a, b); if (retval > 0) error ("unifcdf: X, A, and B must be of common size or scalars."); endif endif ## Check for X, A, and B being double or single if (! (isfloat (x) && isfloat (a) && isfloat (b))) error ("unifcdf: X, A, and B must be double or single."); endif ## Check for X, A, and B being reals if (iscomplex (x) || iscomplex (a) || iscomplex (b)) error ("unifcdf: X, A, and B must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (a, 'single') || isa (b, 'single')) p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Calculate continuous uniform CDF for valid parameter and data range k = find (x > a & x < b & a < b); if (uflag) p(x <= a & a < b) = 1; p(x >= b & a < b) = 0; if any (k) p(k) = (b(k)- x(k)) ./ (b(k) - a(k)); endif else p(x <= a & a < b) = 0; p(x >= b & a < b) = 1; if any (k) p(k) = (x(k) - a(k)) ./ (b(k) - a(k)); endif endif ## Continue argument check p(a >= b) = NaN; p(isnan (x) | isnan (a) | isnan (b)) = NaN; endfunction %!demo %! ## Plot various CDFs from the continuous uniform distribution %! x = 0:0.1:10; %! p1 = unifcdf (x, 2, 5); %! p2 = unifcdf (x, 3, 9); %! plot (x, p1, '-b', x, p2, '-g') %! grid on %! xlim ([0, 10]) %! ylim ([0, 1]) %! legend ({'a = 2, b = 5', 'a = 3, b = 9'}, 'location', 'southeast') %! title ('Continuous uniform CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-1 0 0.5 1 2] + 1; %! y = [0 0 0.5 1 1]; %!assert_equal (unifcdf (x, ones (1,5), 2*ones (1,5)), y) %!assert_equal (unifcdf (x, ones (1,5), 2*ones (1,5), 'upper'), 1 - y) %!assert_equal (unifcdf (x, 1, 2*ones (1,5)), y) %!assert_equal (unifcdf (x, 1, 2*ones (1,5), 'upper'), 1 - y) %!assert_equal (unifcdf (x, ones (1,5), 2), y) %!assert_equal (unifcdf (x, ones (1,5), 2, 'upper'), 1 - y) %!assert_equal (unifcdf (x, [2 1 NaN 1 1], 2), [NaN 0 NaN 1 1]) %!assert_equal (unifcdf (x, [2 1 NaN 1 1], 2, 'upper'), 1 - [NaN 0 NaN 1 1]) %!assert_equal (unifcdf (x, 1, 2*[0 1 NaN 1 1]), [NaN 0 NaN 1 1]) %!assert_equal (unifcdf (x, 1, 2*[0 1 NaN 1 1], 'upper'), 1 - [NaN 0 NaN 1 1]) %!assert_equal (unifcdf ([x(1:2) NaN x(4:5)], 1, 2), [y(1:2) NaN y(4:5)]) %!assert_equal (unifcdf ([x(1:2) NaN x(4:5)], 1, 2, 'upper'), 1 - [y(1:2) NaN y(4:5)]) ## Test class of input preserved %!assert_equal (unifcdf ([x, NaN], 1, 2), [y, NaN]) %!assert_equal (unifcdf (single ([x, NaN]), 1, 2), single ([y, NaN])) %!assert_equal (unifcdf ([x, NaN], single (1), 2), single ([y, NaN])) %!assert_equal (unifcdf ([x, NaN], 1, single (2)), single ([y, NaN])) ## Test input validation %!error unifcdf () %!error unifcdf (1) %!error unifcdf (1, 2) %!error unifcdf (1, 2, 3, 4) %!error unifcdf (1, 2, 3, 'tail') %!error ... %! unifcdf (ones (3), ones (2), ones (2)) %!error ... %! unifcdf (ones (2), ones (3), ones (2)) %!error ... %! unifcdf (ones (2), ones (2), ones (3)) %!error unifcdf (int32 (2), 2, 2) %!error unifcdf (true, 2, 2) %!error unifcdf ('a', 2, 2) %!error unifcdf (i, 2, 2) %!error unifcdf (2, i, 2) %!error unifcdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/unifinv.m000066400000000000000000000122701524624707500246710ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} unifinv (@var{p}, @var{a}, @var{b}) ## ## Inverse of the continuous uniform cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the continuous uniform distribution with parameters @var{a} and @var{b}, ## which define the lower and upper bounds of the interval ## @qcode{[@var{a}, @var{b}]}. The size of @var{x} is the common size of ## @var{p}, @var{a}, and @var{b}. A scalar input functions as a constant matrix ## of the same size as the other inputs. ## ## Further information about the continuous uniform distribution can be found at ## @url{https://en.wikipedia.org/wiki/Continuous_uniform_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{unifcdf, unifpdf, unifrnd, unifit, unifstat} ## @end deftypefn function x = unifinv (p, a, b) ## Check for valid number of input arguments if (nargin < 3) error ("unifinv: function called with too few input arguments."); endif ## Check for common size of P, A, and B if (! isscalar (p) || ! isscalar (a) || ! isscalar (b)) [retval, p, a, b] = common_size (p, a, b); if (retval > 0) error ("unifinv: P, A, and B must be of common size or scalars."); endif endif ## Check for P, A, and B being double or single if (! (isfloat (p) && isfloat (a) && isfloat (b))) error ("unifinv: P, A, and B must be double or single."); endif ## Check for P, A, and B being reals if (iscomplex (p) || iscomplex (a) || iscomplex (b)) error ("unifinv: P, A, and B must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (a, 'single') || isa (b, 'single')) x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ## Calculate continuous uniform iCDF for valid parameter and data range k = (p >= 0) & (p <= 1) & (a < b); x(k) = a(k) + p(k) .* (b(k) - a(k)); endfunction %!demo %! ## Plot various iCDFs from the continuous uniform distribution %! p = 0.001:0.001:0.999; %! x1 = unifinv (p, 2, 5); %! x2 = unifinv (p, 3, 9); %! plot (p, x1, '-b', p, x2, '-g') %! grid on %! xlim ([0, 1]) %! ylim ([0, 10]) %! legend ({'a = 2, b = 5', 'a = 3, b = 9'}, 'location', 'northwest') %! title ('Continuous uniform iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared p %! p = [-1 0 0.5 1 2]; %!assert_equal (unifinv (p, ones (1,5), 2*ones (1,5)), [NaN 1 1.5 2 NaN]) %!assert_equal (unifinv (p, 0, 1), [NaN 1 1.5 2 NaN] - 1) %!assert_equal (unifinv (p, 1, 2*ones (1,5)), [NaN 1 1.5 2 NaN]) %!assert_equal (unifinv (p, ones (1,5), 2), [NaN 1 1.5 2 NaN]) %!assert_equal (unifinv (p, [1 2 NaN 1 1], 2), [NaN NaN NaN 2 NaN]) %!assert_equal (unifinv (p, 1, 2*[1 0 NaN 1 1]), [NaN NaN NaN 2 NaN]) %!assert_equal (unifinv ([p(1:2) NaN p(4:5)], 1, 2), [NaN 1 NaN 2 NaN]) ## Test class of input preserved %!assert_equal (unifinv ([p, NaN], 1, 2), [NaN 1 1.5 2 NaN NaN]) %!assert_equal (unifinv (single ([p, NaN]), 1, 2), single ([NaN 1 1.5 2 NaN NaN])) %!assert_equal (unifinv ([p, NaN], single (1), 2), single ([NaN 1 1.5 2 NaN NaN])) %!assert_equal (unifinv ([p, NaN], 1, single (2)), single ([NaN 1 1.5 2 NaN NaN])) ## Test input validation %!error unifinv () %!error unifinv (1, 2) %!error ... %! unifinv (ones (3), ones (2), ones (2)) %!error ... %! unifinv (ones (2), ones (3), ones (2)) %!error ... %! unifinv (ones (2), ones (2), ones (3)) %!error unifinv (int32 (2), 2, 2) %!error unifinv (true, 2, 2) %!error unifinv ('a', 2, 2) %!error unifinv (i, 2, 2) %!error unifinv (2, i, 2) %!error unifinv (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/unifpdf.m000066400000000000000000000124461524624707500246530ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} unifpdf (@var{x}, @var{a}, @var{b}) ## ## Continuous uniform probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the continuous uniform distribution with parameters @var{a} and @var{b}, ## which define the lower and upper bounds of the interval ## @qcode{[@var{a}, @var{b}]}. The size of @var{y} is the common size of ## @var{x}, @var{a}, and @var{b}. A scalar input functions as a constant matrix ## of the same size as the other inputs. ## ## Further information about the continuous uniform distribution can be found at ## @url{https://en.wikipedia.org/wiki/Continuous_uniform_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## MATLAB also accepts integer input here, returning the result in the integer ## class of the input; Octave rejects it, as it does for every other continuous ## distribution. ## ## @seealso{unifcdf, unifinv, unifrnd, unifit, unifstat} ## @end deftypefn function y = unifpdf (x, a, b) ## Check for valid number of input arguments if (nargin < 3) error ("unifpdf: function called with too few input arguments."); endif ## Check for common size of X, A, and B if (! isscalar (x) || ! isscalar (a) || ! isscalar (b)) [retval, x, a, b] = common_size (x, a, b); if (retval > 0) error ("unifpdf: X, A, and B must be of common size or scalars."); endif endif ## Check for X, A, and B being double or single if (! (isfloat (x) && isfloat (a) && isfloat (b))) error ("unifpdf: X, A, and B must be double or single."); endif ## Check for X, A, and B being reals if (iscomplex (x) || iscomplex (a) || iscomplex (b)) error ("unifpdf: X, A, and B must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (a, 'single') || isa (b, 'single')) y = zeros (size (x), 'single'); else y = zeros (size (x)); endif ## Calculate continuous uniform PDF for valid parameter and data range k = isnan (x) | ! (a < b); y(k) = NaN; k = (x >= a) & (x <= b) & (a < b); y(k) = 1 ./ (b(k) - a(k)); endfunction %!demo %! ## Plot various PDFs from the continuous uniform distribution %! x = 0:0.001:10; %! y1 = unifpdf (x, 2, 5); %! y2 = unifpdf (x, 3, 9); %! plot (x, y1, '-b', x, y2, '-g') %! grid on %! xlim ([0, 10]) %! ylim ([0, 0.4]) %! legend ({'a = 2, b = 5', 'a = 3, b = 9'}, 'location', 'northeast') %! title ('Continuous uniform PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y %! x = [-1 0 0.5 1 2] + 1; %! y = [0 1 1 1 0]; %!assert_equal (unifpdf (x, ones (1,5), 2*ones (1,5)), y) %!assert_equal (unifpdf (x, 1, 2*ones (1,5)), y) %!assert_equal (unifpdf (x, ones (1,5), 2), y) %!assert_equal (unifpdf (x, [2 NaN 1 1 1], 2), [NaN NaN y(3:5)]) %!assert_equal (unifpdf (x, 1, 2*[0 NaN 1 1 1]), [NaN NaN y(3:5)]) %!assert_equal (unifpdf ([x, NaN], 1, 2), [y, NaN]) %!assert_equal (unifpdf (x, 0, 1), [1 1 0 0 0]) ## Test class of input preserved %!assert_equal (unifpdf (single ([x, NaN]), 1, 2), single ([y, NaN])) %!assert_equal (unifpdf (single ([x, NaN]), single (1), 2), single ([y, NaN])) %!assert_equal (unifpdf ([x, NaN], 1, single (2)), single ([y, NaN])) ## Test input validation %!error unifpdf () %!error unifpdf (1) %!error unifpdf (1, 2) %!error ... %! unifpdf (ones (3), ones (2), ones (2)) %!error ... %! unifpdf (ones (2), ones (3), ones (2)) %!error ... %! unifpdf (ones (2), ones (2), ones (3)) %!error unifpdf (int32 (2), 2, 2) %!error unifpdf (true, 2, 2) %!error unifpdf ('a', 2, 2) %!error unifpdf (i, 2, 2) %!error unifpdf (2, i, 2) %!error unifpdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/unifrnd.m000066400000000000000000000152541524624707500246650ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2019 Anthony Morast ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} unifrnd (@var{a}, @var{b}) ## @deftypefnx {statistics} {@var{r} =} unifrnd (@var{a}, @var{b}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} unifrnd (@var{a}, @var{b}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} unifrnd (@var{a}, @var{b}, [@var{sz}]) ## ## Random arrays from the continuous uniform distribution. ## ## @code{@var{r} = unifrnd (@var{a}, @var{b})} returns an array of random ## numbers chosen from the continuous uniform distribution with parameters ## @var{a} and @var{b}, which define the lower and upper bounds of the interval ## @qcode{[@var{a}, @var{b}]}. The size of @var{r} is the common size of ## @var{a} and @var{b}. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## When called with a single size argument, @code{unifrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the continuous uniform distribution can be found at ## @url{https://en.wikipedia.org/wiki/Continuous_uniform_distribution} ## ## @seealso{unifcdf, unifinv, unifpdf, unifit, unifstat} ## @end deftypefn function r = unifrnd (a, b, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("unifrnd: function called with too few input arguments."); endif ## Check for common size of A and B if (! isscalar (a) || ! isscalar (b)) [retval, a, b] = common_size (a, b); if (retval > 0) error ("unifrnd: A and B must be of common size or scalars."); endif endif ## Check for A and B being reals if (iscomplex (a) || iscomplex (b)) error ("unifrnd: A and B must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (a); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("unifrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("unifrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (a) && ! isequal (size (a), size (ones (sz)))) error ("unifrnd: A and B must be scalars or of size SZ."); endif ## Check for class type if (isa (a, 'single') || isa (b, 'single')) cls = 'single'; else cls = 'double'; endif if (isscalar (a) && isscalar (b)) if ((-Inf < a) && (a <= b) && (b < Inf)) r = a + (b - a) * rand (sz, cls); else r = NaN (sz, cls); endif else r = a + (b - a) .* rand (sz, cls); k = ! (-Inf < a) | ! (a <= b) | ! (b < Inf); r(k) = NaN; endif endfunction ## Test output %!assert_equal (size (unifrnd (1, 1)), [1 1]) %!assert_equal (size (unifrnd (1, ones (2,1))), [2, 1]) %!assert_equal (size (unifrnd (1, ones (2,2))), [2, 2]) %!assert_equal (size (unifrnd (ones (2,1), 1)), [2, 1]) %!assert_equal (size (unifrnd (ones (2,2), 1)), [2, 2]) %!assert_equal (size (unifrnd (1, 1, 3)), [3, 3]) %!assert_equal (size (unifrnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (unifrnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (unifrnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (unifrnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (unifrnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (unifrnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (unifrnd (1, 1, [])), [0, 0]) %!assert_equal (size (unifrnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (unifrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (unifrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (unifrnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (unifrnd (1, 1)), "double") %!assert_equal (class (unifrnd (1, single (1))), "single") %!assert_equal (class (unifrnd (1, single ([1, 1]))), "single") %!assert_equal (class (unifrnd (single (1), 1)), "single") %!assert_equal (class (unifrnd (single ([1, 1]), 1)), "single") ## Test input validation %!error unifrnd () %!error unifrnd (1) %!error ... %! unifrnd (ones (3), ones (2)) %!error ... %! unifrnd (ones (2), ones (3)) %!error unifrnd (i, 2, 3) %!error unifrnd (1, i, 3) %!error ... %! unifrnd (1, 2, 1.2) %!error ... %! unifrnd (1, 2, ones (2)) %!error ... %! unifrnd (1, 2, [2 0 2.5]) %!error ... %! unifrnd (1, 2, 2, 1.5, 5) %!error ... %! unifrnd (2, ones (2), 3) %!error ... %! unifrnd (2, ones (2), [3, 2]) %!error ... %! unifrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/vmcdf.m000066400000000000000000000144321524624707500243140ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} vmcdf (@var{x}, @var{mu}, @var{k}) ## @deftypefnx {statistics} {@var{p} =} vmcdf (@var{x}, @var{mu}, @var{k}, @qcode{'upper'}) ## ## Von Mises probability density function (PDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the von Mises distribution with location parameter @var{mu} and ## concentration parameter @var{k} on the interval @math{[-pi,pi]}. The size of ## @var{p} is the common size of @var{x}, @var{mu}, and @var{k}. A scalar input ## functions as a constant matrix of the same same size as the other inputs. ## ## @code{@var{p} = vmcdf (@var{x}, @var{mu}, @var{k}, "upper")} computes the ## upper tail probability of the von Mises distribution with parameters @var{mu} ## and @var{k}, at the values in @var{x}. ## ## Note: the CDF of the von Mises distribution is not analytic. Hence, it is ## calculated by integrating its probability density which is expressed as a ## series of Bessel functions. Balancing between performance and accuracy, the ## integration uses a step of @qcode{1e-5} on the interval @math{[-pi,pi]}, ## which results to an accuracy of about 10 significant digits. ## ## Further information about the von Mises distribution can be found at ## @url{https://en.wikipedia.org/wiki/Von_Mises_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{vminv, vmpdf, vmrnd} ## @end deftypefn function p = vmcdf (x, mu, k, uflag) ## Check for valid number of input arguments if (nargin < 3) error ("vmcdf: function called with too few input arguments."); endif ## Check for valid "upper" flag if (nargin > 3) if (! strcmpi (uflag, 'upper')) error ("vmcdf: invalid argument for upper tail."); else uflag = true; endif else uflag = false; endif ## Check for common size of X, MU, and K if (! isscalar (x) || ! isscalar (mu) || ! isscalar (k)) [retval, x, mu, k] = common_size (x, mu, k); if (retval > 0) error ("vmcdf: X, MU, and K must be of common size or scalars."); endif endif ## Check for X, MU, and K being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (k))) error ("vmcdf: X, MU, and K must be double or single."); endif ## Check for X, MU, and K being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (k)) error ("vmcdf: X, MU, and K must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (k, 'single')) p = zeros (size (x), 'single'); else p = zeros (size (x)); endif ## Evaluate Von Mises CDF by integrating from -PI to PI interval = linspace (-pi, pi, 1e5)'; # accurate to >10 significant digits f = exp (k .* cos (interval)) ./ (2 .* pi .* besseli (0, k)); c = cumtrapz (interval, f); p = diag (interp1 (interval, c, x - mu, 'spline'))'; ## Force Nan for negative K p(k < 0) = NaN; ## Apply upper flag (if required) if (uflag) p = 1 - p; endif endfunction %!demo %! ## Plot various CDFs from the von Mises distribution %! x1 = [-pi:0.1:pi]; %! p1 = vmcdf (x1, 0, 0.5); %! p2 = vmcdf (x1, 0, 1); %! p3 = vmcdf (x1, 0, 2); %! p4 = vmcdf (x1, 0, 4); %! plot (x1, p1, '-r', x1, p2, '-g', x1, p3, '-b', x1, p4, '-c') %! grid on %! xlim ([-pi, pi]) %! legend ({'μ = 0, k = 0.5', 'μ = 0, k = 1', ... %! 'μ = 0, k = 2', 'μ = 0, k = 4'}, 'location', 'northwest') %! title ('Von Mises CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, p0, p1 %! x = [-pi:pi/2:pi]; %! p0 = [0, 0.10975, 0.5, 0.89025, 1]; %! p1 = [0, 0.03752, 0.5, 0.99622, 1]; %!assert_equal (vmcdf (x, 0, 1), p0, 1e-5) %!assert_equal (vmcdf (x, 0, 1, 'upper'), 1 - p0, 1e-5) %!assert_equal (vmcdf (x, zeros (1,5), ones (1,5)), p0, 1e-5) %!assert_equal (vmcdf (x, zeros (1,5), ones (1,5), 'upper'), 1 - p0, 1e-5) %!assert_equal (vmcdf (x, 0, [1 2 3 4 5]), p1, 1e-5) %!assert_equal (vmcdf (x, 0, [1 2 3 4 5], 'upper'), 1 - p1, 1e-5) ## Test class of input preserved %!assert_equal (isa (vmcdf (single (pi), 0, 1), 'single'), true) %!assert_equal (isa (vmcdf (pi, single (0), 1), 'single'), true) %!assert_equal (isa (vmcdf (pi, 0, single (1)), 'single'), true) ## Test input validation %!error vmcdf () %!error vmcdf (1) %!error vmcdf (1, 2) %!error vmcdf (1, 2, 3, 'tail') %!error vmcdf (1, 2, 3, 4) %!error ... %! vmcdf (ones (3), ones (2), ones (2)) %!error ... %! vmcdf (ones (2), ones (3), ones (2)) %!error ... %! vmcdf (ones (2), ones (2), ones (3)) %!error vmcdf (int32 (2), 2, 2) %!error vmcdf (true, 2, 2) %!error vmcdf ('a', 2, 2) %!error vmcdf (i, 2, 2) %!error vmcdf (2, i, 2) %!error vmcdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/vminv.m000066400000000000000000000145101524624707500243510ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} vminv (@var{p}, @var{mu}, @var{k}) ## ## Inverse of the von Mises cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) of ## the von Mises distribution with location parameter @var{mu} and concentration ## parameter @var{k} on the interval @math{[-pi,pi]}. The size of @var{x} is ## the common size of @var{p}, @var{mu}, and @var{k}. A scalar input functions ## as a constant matrix of the same size as the other inputs. ## ## Note: the quantile of the von Mises distribution is not analytic. Hence, it ## is approximated by a custom searching algorithm using its CDF until it ## converges up to a tolerance of @qcode{1e-5} or 100 iterations. As a result, ## balancing between performance and accuracy, the accuracy is about ## @qcode{5e-5} for @qcode{@var{k} = 1} and it drops to @qcode{5e-5} as @var{k} ## increases. ## ## Further information about the von Mises distribution can be found at ## @url{https://en.wikipedia.org/wiki/Von_Mises_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{vmcdf, vmpdf, vmrnd} ## @end deftypefn function x = vminv (p, mu, k) ## Check for valid number of input arguments if (nargin < 3) error ("vminv: function called with too few input arguments."); endif ## Check for common size of P, MU, and K [err, p, mu, k] = common_size (p, mu, k); if (err > 0) error ("vminv: P, MU, and K must be of common size or scalars."); endif ## Check for P, MU, and K being double or single if (! (isfloat (p) && isfloat (mu) && isfloat (k))) error ("vminv: P, MU, and K must be double or single."); endif ## Check for P, MU, and K being reals if (iscomplex (p) || iscomplex (mu) || iscomplex (k)) error ("vminv: P, MU, and K must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (mu, 'single') || isa (k, 'single')) x = NaN (size (p), 'single'); else x = NaN (size (p), 'double'); endif ## Process edge cases p=0, p=0.5, p=1 p_0 = p < eps (class (x)) & k > 0 & isfinite (mu); x(p_0) = -pi + mu(p_0); p_5 = abs (p - 0.5) < eps (class (x)) & k > 0 & isfinite (mu); x(p_5) = mu(p_5); p_1 = 1 - p < eps (class (x)) & k > 0 & isfinite (mu); x(p_1) = pi + mu(p_1); ## Get remaining valid cases valc = p > 0 & p < 1 & ! p_5 & k > 0 & isfinite (mu); if (! any (valc)) return endif p = p(valc); mu = mu(valc); k = k(valc); ## Complement cases of p<0.5 to 1-p and keep track to invert them at the end comp = p < 0.5; p(comp) = 1 - p(comp); ## Initialize counter and threshold crit = 1e-5; count_limit = 100; count = 0; ## Supply a starting guess for the iteration by linear interpolation with k=0 x0 = 2 * pi .* p - pi; xz = zeros (size (p)); xa = xz; ## Compute p0 and compare to target p p0 = vmcdf (x0, 0, k); ## Solution is always 0 < x < x0. Search for x until p == p0 within threshold while (any (abs (p - p0) > crit) && count < count_limit) count = count + 1; xnew = xz + (abs (x0) - abs (xz)) .* 0.5; p0 = vmcdf (xnew, 0, k); ## Prepare for next step xdec = (p0 - p) > crit; xinc = (p - p0) > crit; if (any (xdec)) x0(xdec) = xnew(xdec); endif if (any (xinc)) xz(xinc) = xnew(xinc); endif endwhile ## Return the converged value(s). xnew(comp) = -xnew(comp); x(valc) = xnew + mu; if (count == count_limit) warning ("vminv: did not converge."); endif endfunction %!demo %! ## Plot various iCDFs from the von Mises distribution %! p1 = [0,0.005,0.01:0.01:0.1,0.15,0.2:0.1:0.8,0.85,0.9:0.01:0.99,0.995,1]; %! x1 = vminv (p1, 0, 0.5); %! x2 = vminv (p1, 0, 1); %! x3 = vminv (p1, 0, 2); %! x4 = vminv (p1, 0, 4); %! plot (p1, x1, '-r', p1, x2, '-g', p1, x3, '-b', p1, x4, '-c') %! grid on %! ylim ([-pi, pi]) %! legend ({'μ = 0, k = 0.5', 'μ = 0, k = 1', ... %! 'μ = 0, k = 2', 'μ = 0, k = 4'}, 'location', 'northwest') %! title ('Von Mises iCDF') %! xlabel ('probability') %! ylabel ('values in x') ## Test output %!shared x, p0, p1 %! x = [-pi:pi/2:pi]; %! p0 = [0, 0.10975, 0.5, 0.89025, 1]; %! p1 = [0, 0.03752, 0.5, 0.99622, 1]; %!assert_equal (vminv (p0, 0, 1), x, 5e-5) %!assert_equal (vminv (p0, zeros (1,5), ones (1,5)), x, 5e-5) %!assert_equal (vminv (p1, 0, [1 2 3 4 5]), x, [5e-5, 5e-4, 5e-5, 5e-4, 5e-5]) ## Test input validation %!error vminv () %!error vminv (1) %!error vminv (1, 2) %!error ... %! vminv (ones (3), ones (2), ones (2)) %!error ... %! vminv (ones (2), ones (3), ones (2)) %!error ... %! vminv (ones (2), ones (2), ones (3)) %!error vminv (int32 (2), 2, 2) %!error vminv (true, 2, 2) %!error vminv ('a', 2, 2) %!error vminv (i, 2, 2) %!error vminv (2, i, 2) %!error vminv (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/vmpdf.m000066400000000000000000000116351524624707500243330ustar00rootroot00000000000000## Copyright (C) 2009 Soren Hauberg ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} vmpdf (@var{x}, @var{mu}, @var{k}) ## ## Von Mises probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the von Mises distribution with location parameter @var{mu} and ## concentration parameter @var{k} on the interval [-pi, pi]. The size of ## @var{y} is the common size of @var{x}, @var{mu}, and @var{k}. A scalar input ## functions as a constant matrix of the same size as the other inputs. ## ## Further information about the von Mises distribution can be found at ## @url{https://en.wikipedia.org/wiki/Von_Mises_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{vmcdf, vminv, vmrnd} ## @end deftypefn function y = vmpdf (x, mu, k) ## Check for valid number of input arguments if (nargin < 3) error ("vmpdf: function called with too few input arguments."); endif ## Check for common size of X, MU, and K if (! isscalar (x) || ! isscalar (mu) || ! isscalar (k)) [retval, x, mu, k] = common_size (x, mu, k); if (retval > 0) error ("vmpdf: X, MU, and K must be of common size or scalars."); endif endif ## Check for X, MU, and K being double or single if (! (isfloat (x) && isfloat (mu) && isfloat (k))) error ("vmpdf: X, MU, and K must be double or single."); endif ## Check for X, MU, and K being reals if (iscomplex (x) || iscomplex (mu) || iscomplex (k)) error ("vmpdf: X, MU, and K must not be complex."); endif ## Evaluate Von Mises PDF Z = 2 .* pi .* besseli (0, k); y = exp (k .* cos (x - mu)) ./ Z; ## Force Nan for negative K y(k < 0) = NaN; ## Check for class type if (isa (x, 'single') || isa (mu, 'single') || isa (k, 'single')) y = cast (y, 'single'); else y = cast (y, 'double'); endif endfunction %!demo %! ## Plot various PDFs from the von Mises distribution %! x1 = [-pi:0.1:pi]; %! y1 = vmpdf (x1, 0, 0.5); %! y2 = vmpdf (x1, 0, 1); %! y3 = vmpdf (x1, 0, 2); %! y4 = vmpdf (x1, 0, 4); %! plot (x1, y1, '-r', x1, y2, '-g', x1, y3, '-b', x1, y4, '-c') %! grid on %! xlim ([-pi, pi]) %! ylim ([0, 0.8]) %! legend ({'μ = 0, k = 0.5', 'μ = 0, k = 1', ... %! 'μ = 0, k = 2', 'μ = 0, k = 4'}, 'location', 'northwest') %! title ('Von Mises PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x, y0, y1 %! x = [-pi:pi/2:pi]; %! y0 = [0.046245, 0.125708, 0.341710, 0.125708, 0.046245]; %! y1 = [0.046245, 0.069817, 0.654958, 0.014082, 0.000039]; %!assert_equal (vmpdf (x, 0, 1), y0, 1e-5) %!assert_equal (vmpdf (x, zeros (1,5), ones (1,5)), y0, 1e-6) %!assert_equal (vmpdf (x, 0, [1 2 3 4 5]), y1, 1e-6) ## Test class of input preserved %!assert_equal (isa (vmpdf (single (pi), 0, 1), 'single'), true) %!assert_equal (isa (vmpdf (pi, single (0), 1), 'single'), true) %!assert_equal (isa (vmpdf (pi, 0, single (1)), 'single'), true) ## Test input validation %!error vmpdf () %!error vmpdf (1) %!error vmpdf (1, 2) %!error ... %! vmpdf (ones (3), ones (2), ones (2)) %!error ... %! vmpdf (ones (2), ones (3), ones (2)) %!error ... %! vmpdf (ones (2), ones (2), ones (3)) %!error vmpdf (int32 (2), 2, 2) %!error vmpdf (true, 2, 2) %!error vmpdf ('a', 2, 2) %!error vmpdf (i, 2, 2) %!error vmpdf (2, i, 2) %!error vmpdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/vmrnd.m000066400000000000000000000166361524624707500243530ustar00rootroot00000000000000## Copyright (C) 2009 Soren Hauberg ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} vmrnd (@var{mu}, @var{k}) ## @deftypefnx {statistics} {@var{r} =} vmrnd (@var{mu}, @var{k}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} vmrnd (@var{mu}, @var{k}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} vmrnd (@var{mu}, @var{k}, [@var{sz}]) ## ## Random arrays from the von Mises distribution. ## ## @code{@var{r} = vmrnd (@var{mu}, @var{k})} returns an array of random angles ## chosen from a von Mises distribution with location parameter @var{mu} and ## concentration parameter @var{k} on the interval [-pi, pi]. The size of ## @var{r} is the common size of @var{mu} and @var{k}. A scalar input functions ## as a constant matrix of the same size as the other inputs. Both parameters ## must be finite real numbers and @var{k} > 0, otherwise NaN is returned. ## ## When called with a single size argument, @code{vmrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the von Mises distribution can be found at ## @url{https://en.wikipedia.org/wiki/Von_Mises_distribution} ## ## @seealso{vmcdf, vminv, vmpdf} ## @end deftypefn function r = vmrnd (mu, k, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("vmrnd: function called with too few input arguments."); endif ## Check for common size of MU and Κ if (! isscalar (mu) || ! isscalar (k)) [retval, mu, k] = common_size (mu, k); if (retval > 0) error ("vmrnd: MU and K must be of common size or scalars."); endif endif ## Check for MU and Κ being reals if (iscomplex (mu) || iscomplex (k)) error ("vmrnd: MU and K must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (mu); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("vmrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("vmrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (mu) && ! isequal (size (k), size (ones (sz)))) error ("vmrnd: MU and K must be scalars or of size SZ."); endif ## Check for class type if (isa (mu, 'single') || isa (k, 'single')) cls = 'single'; else cls = 'double'; endif ## Handle zero size dimensions if (any (sz == 0)) r = nan (sz, cls); return endif ## Simulate! if (all (k < 1e-6)) ## k is small: sample uniformly on circle r = mu + (2 * pi * rand (sz) - pi); else a = 1 + sqrt (1 + 4 .* k .^ 2); b = (a - sqrt (2 .* a)) ./ (2 .* k); r_tmp = (1 + b .^ 2) ./ (2 .* b); N = prod (sz); if (isscalar (k)) r_tmp = repmat (r_tmp, 1, N); k_tmp = repmat (k, 1, N); mu_rs = repmat (mu, 1, N); else r_tmp = reshape (r_tmp, 1, N); k_tmp = reshape (k, 1, N); mu_rs = reshape (mu, 1, N); endif notdone = true (N, 1); while (any (notdone)) u(:, notdone) = (rand (3, N))(:,notdone); z(notdone) = (cos (pi .* u(1, :)))(notdone); f(notdone) = ((1 + r_tmp(notdone) .* z(notdone)) ./ (r_tmp(notdone) + z(notdone))); c(notdone) = (k_tmp(notdone) .* (r_tmp(notdone) - f(notdone))); notdone = (u(2, :) >= c .* (2 - c)) & (log (c) - log (u(2, :)) + 1 - c < 0); #N = sum (notdone); endwhile r = mu_rs + sign (u(3, :) - 0.5) .* acos (f); r = reshape (r, sz); endif ## Cast to appropriate class r = cast (r, cls); endfunction ## Test output %!assert_equal (size (vmrnd (1, 1)), [1, 1]) %!assert_equal (size (vmrnd (1, ones (2, 1))), [2, 1]) %!assert_equal (size (vmrnd (1, ones (2, 2))), [2, 2]) %!assert_equal (size (vmrnd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (vmrnd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (vmrnd (1, 1, 3)), [3, 3]) %!assert_equal (size (vmrnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (vmrnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (vmrnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (vmrnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (vmrnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (vmrnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (vmrnd (1, 1, [])), [0, 0]) %!assert_equal (size (vmrnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (vmrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (vmrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (vmrnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (vmrnd (1, 1)), "double") %!assert_equal (class (vmrnd (1, single (1))), "single") %!assert_equal (class (vmrnd (1, single ([1, 1]))), "single") %!assert_equal (class (vmrnd (single (1), 1)), "single") %!assert_equal (class (vmrnd (single ([1, 1]), 1)), "single") ## Test input validation %!error vmrnd () %!error vmrnd (1) %!error ... %! vmrnd (ones (3), ones (2)) %!error ... %! vmrnd (ones (2), ones (3)) %!error vmrnd (i, 2, 3) %!error vmrnd (1, i, 3) %!error ... %! vmrnd (1, 2, 1.2) %!error ... %! vmrnd (1, 2, ones (2)) %!error ... %! vmrnd (1, 2, [2 0 2.5]) %!error ... %! vmrnd (1, 2, 2, 1.5, 5) %!error ... %! vmrnd (2, ones (2), 3) %!error ... %! vmrnd (2, ones (2), [3, 2]) %!error ... %! vmrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/wblcdf.m000066400000000000000000000231701524624707500244550ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} wblcdf (@var{x}) ## @deftypefnx {statistics} {@var{p} =} wblcdf (@var{x}, @var{lambda}) ## @deftypefnx {statistics} {@var{p} =} wblcdf (@var{x}, @var{lambda}, @var{k}) ## @deftypefnx {statistics} {@var{p} =} wblcdf (@dots{}, @qcode{'upper'}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} wblcdf (@var{x}, @var{lambda}, @var{k}, @var{pcov}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} wblcdf (@var{x}, @var{lambda}, @var{k}, @var{pcov}, @var{alpha}) ## @deftypefnx {statistics} {[@var{p}, @var{plo}, @var{pup}] =} wblcdf (@dots{}, @qcode{'upper'}) ## ## Weibull cumulative distribution function (CDF). ## ## For each element of @var{x}, compute the cumulative distribution function ## (CDF) of the Weibull distribution with scale parameter @var{lambda} and shape ## parameter @var{k}. The size of @var{p} is the common size of @var{x}, ## @var{lambda} and @var{k}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Default values are @var{lambda} = 1, @var{k} = 1. ## ## When called with three output arguments, @code{[@var{p}, @var{plo}, ## @var{pup}]} it computes the confidence bounds for @var{p} when the input ## parameters @var{lambda} and @var{k} are estimates. In such case, @var{pcov}, ## a 2-by-2 matrix containing the covariance matrix of the estimated parameters, ## is necessary. Optionally, @var{alpha} has a default value of 0.05, and ## specifies 100 * (1 - @var{alpha})% confidence bounds. @var{plo} and @var{pup} ## are arrays of the same size as @var{p} containing the lower and upper ## confidence bounds. ## ## @code{[@dots{}] = wblcdf (@dots{}, "upper")} computes the upper tail ## probability of the lognormal distribution. ## ## Further information about the Weibull distribution can be found at ## @url{https://en.wikipedia.org/wiki/Weibull_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## The @code{prob.WeibullDistribution} class names these same two parameters ## @qcode{A} and @qcode{B}, after MATLAB. @var{lambda} is its @qcode{A} and ## @var{k} is its @qcode{B}. ## @seealso{wblinv, wblpdf, wblrnd, wblstat, wblplot} ## @end deftypefn function [varargout] = wblcdf (x, varargin) ## Check for valid number of input arguments if (nargin < 1 || nargin > 6) error ("wblcdf: invalid number of input arguments."); endif ## Check for "upper" flag if (nargin > 1 && strcmpi (varargin{end}, 'upper')) uflag = true; varargin(end) = []; elseif (nargin > 1 && ischar (varargin{end}) && ... ! strcmpi (varargin{end}, 'upper')) error ("wblcdf: invalid argument for upper tail."); elseif (nargin > 1 && isempty (varargin{end})) uflag = false; varargin(end) = []; else uflag = false; endif ## Get extra arguments (if they exist) or add defaults if (numel (varargin) > 0) lambda = varargin{1}; else lambda = 1; endif if (numel (varargin) > 1) k = varargin{2}; else k = 1; endif if (numel (varargin) > 2) pcov = varargin{3}; ## Check for valid covariance matrix 2x2 if (! isequal (size (pcov), [2, 2])) error ("wblcdf: invalid size of covariance matrix."); endif else ## Check that cov matrix is provided if 3 output arguments are requested if (nargout > 1) error ("wblcdf: covariance matrix is required for confidence bounds."); endif pcov = []; endif if (numel (varargin) > 3) alpha = varargin{4}; ## Check for valid alpha value if (! isnumeric (alpha) || numel (alpha) !=1 || alpha <= 0 || alpha >= 1) error ("wblcdf: invalid value for alpha."); endif else alpha = 0.05; endif ## Check for common size of X, LAMBDA, and K if (! isscalar (x) || ! isscalar (lambda) || ! isscalar (k)) [err, x, lambda, k] = common_size (x, lambda, k); if (err > 0) error ("wblcdf: X, LAMBDA, and K must be of common size or scalars."); endif endif ## Check for X, LAMBDA, and K being double or single if (! (isfloat (x) && isfloat (lambda) && isfloat (k))) error ("wblcdf: X, LAMBDA, and K must be double or single."); endif ## Check for X, LAMBDA, and K being reals if (iscomplex (x) || iscomplex (lambda) || iscomplex (k)) error ("wblcdf: X, LAMBDA, and K must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (lambda, 'single') || isa (k, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Return NaN for out of range parameters. lambda(lambda <= 0) = NaN; k(k <= 0) = NaN; ## Force 0 for negative data x(x < 0) = 0; ## Compute z z = (x ./ lambda) .^ k; if (uflag) p = exp (-z); else p = -expm1 (-z); endif ## Compute confidence bounds (if requested) if (nargout >= 2) ## Work on log scale log_z = log (z); d_lambda = 1 ./ lambda; d_k = -1 ./ (k .^ 2); log_zvar = (pcov(1,1) .* d_lambda .^ 2 + ... 2 * pcov(1,2) .* d_lambda .* d_k .* log_z + ... pcov(2,2) .* (d_k .* log_z) .^ 2) .* (k .^ 2); if (any (log_zvar < 0)) error ("wblcdf: bad covariance matrix."); endif normz = -norminv (alpha / 2); halfwidth = normz * sqrt (log_zvar); zlo = log_z - halfwidth; zup = log_z + halfwidth; ## Convert back from log scale if uflag == true plo = exp (-exp (zup)); pup = exp (-exp (zlo)); else plo = -expm1 (-exp (zlo)); pup = -expm1 (-exp (zup)); endif endif ## Prepare output varargout{1} = cast (p, is_class); if (nargout > 1) varargout{2} = cast (plo, is_class); varargout{3} = cast (pup, is_class); endif endfunction %!demo %! ## Plot various CDFs from the Weibull distribution %! x = 0:0.001:2.5; %! p1 = wblcdf (x, 1, 0.5); %! p2 = wblcdf (x, 1, 1); %! p3 = wblcdf (x, 1, 1.5); %! p4 = wblcdf (x, 1, 5); %! plot (x, p1, '-b', x, p2, '-r', x, p3, '-m', x, p4, '-g') %! grid on %! legend ({'λ = 1, k = 0.5', 'λ = 1, k = 1', ... %! 'λ = 1, k = 1.5', 'λ = 1, k = 5'}, 'location', 'southeast') %! title ('Weibull CDF') %! xlabel ('values in x') %! ylabel ('probability') ## Test output %!shared x, y %! x = [-1 0 0.5 1 Inf]; %! y = [0, 1-exp(-x(2:4)), 1]; %!assert_equal (wblcdf (x, ones (1,5), ones (1,5)), y, 1e-16) %!assert_equal (wblcdf (x, ones (1,5), ones (1,5), 'upper'), 1 - y) %!assert_equal (wblcdf (x, 'upper'), 1 - y) %!assert_equal (wblcdf (x, 1, ones (1,5)), y, 1e-16) %!assert_equal (wblcdf (x, ones (1,5), 1), y, 1e-16) %!assert_equal (wblcdf (x, [0 1 NaN Inf 1], 1), [NaN 0 NaN 0 1]) %!assert_equal (wblcdf (x, [0 1 NaN Inf 1], 1, 'upper'), 1 - [NaN 0 NaN 0 1]) %!assert_equal (wblcdf (x, 1, [0 1 NaN Inf 1]), [NaN 0 NaN y(4:5)]) %!assert_equal (wblcdf (x, 1, [0 1 NaN Inf 1], 'upper'), 1 - [NaN 0 NaN y(4:5)]) %!assert_equal (wblcdf ([x(1:2) NaN x(4:5)], 1, 1), [y(1:2) NaN y(4:5)]) %!assert_equal (wblcdf ([x(1:2) NaN x(4:5)], 1, 1, 'upper'), 1 - [y(1:2) NaN y(4:5)]) ## Test class of input preserved %!assert_equal (wblcdf ([x, NaN], 1, 1), [y, NaN], 1e-16) %!assert_equal (wblcdf (single ([x, NaN]), 1, 1), single ([y, NaN])) %!assert_equal (wblcdf ([x, NaN], single (1), 1), single ([y, NaN])) %!assert_equal (wblcdf ([x, NaN], 1, single (1)), single ([y, NaN])) ## Test input validation %!error wblcdf () %!error wblcdf (1,2,3,4,5,6,7) %!error wblcdf (1, 2, 3, 4, 'uper') %!error ... %! wblcdf (ones (3), ones (2), ones (2)) %!error wblcdf (2, 3, 4, [1, 2]) %!error ... %! [p, plo, pup] = wblcdf (1, 2, 3) %!error [p, plo, pup] = ... %! wblcdf (1, 2, 3, [1, 0; 0, 1], 0) %!error [p, plo, pup] = ... %! wblcdf (1, 2, 3, [1, 0; 0, 1], 1.22) %!error [p, plo, pup] = ... %! wblcdf (1, 2, 3, [1, 0; 0, 1], 'alpha', 'upper') %!error wblcdf (int32 (2), 2, 2) %!error wblcdf (true, 2, 2) %!error wblcdf ('a', 2, 2) %!error wblcdf (i, 2, 2) %!error wblcdf (2, i, 2) %!error wblcdf (2, 2, i) %!error ... %! [p, plo, pup] =wblcdf (1, 2, 3, [1, 0; 0, -inf], 0.04) statistics-release-1.9.2/inst/Distribution_Functions/wblinv.m000066400000000000000000000140131524624707500245110ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} wblinv (@var{p}) ## @deftypefnx {statistics} {@var{x} =} wblinv (@var{p}, @var{lambda}) ## @deftypefnx {statistics} {@var{x} =} wblinv (@var{p}, @var{lambda}, @var{k}) ## ## Inverse of the Weibull cumulative distribution function (iCDF). ## ## For each element of @var{p}, compute the quantile (the inverse of the CDF) ## of the Weibull distribution with scale parameter @var{lambda} and shape ## parameter @var{k}. The size of @var{x} is the common size of @var{p}, ## @var{lambda}, and @var{k}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Default values are @var{lambda} = 1, @var{k} = 1. ## ## Further information about the Weibull distribution can be found at ## @url{https://en.wikipedia.org/wiki/Weibull_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## The @code{prob.WeibullDistribution} class names these same two parameters ## @qcode{A} and @qcode{B}, after MATLAB. @var{lambda} is its @qcode{A} and ## @var{k} is its @qcode{B}. ## @seealso{wblcdf, wblpdf, wblrnd, wblstat, wblplot} ## @end deftypefn function x = wblinv (p, varargin) ## Check for valid number of input arguments if (nargin < 1 || nargin > 3) error ("wblinv: invalid number of input arguments."); endif ## Get extra arguments (if they exist) or add defaults if (numel (varargin) > 0) lambda = varargin{1}; else lambda = 1; endif if (numel (varargin) > 1) k = varargin{2}; else k = 1; endif ## Check for common size of P, LAMBDA, and K if (! isscalar (p) || ! isscalar (lambda) || ! isscalar (k)) [retval, p, lambda, k] = common_size (p, lambda, k); if (retval > 0) error ("wblinv: P, LAMBDA, and K must be of common size or scalars."); endif endif ## Check for P, LAMBDA, and K being double or single if (! (isfloat (p) && isfloat (lambda) && isfloat (k))) error ("wblinv: P, LAMBDA, and K must be double or single."); endif ## Check for P, LAMBDA, and K being reals if (iscomplex (p) || iscomplex (lambda) || iscomplex (k)) error ("wblinv: P, LAMBDA, and K must not be complex."); endif ## Check for class type if (isa (p, 'single') || isa (lambda, 'single') || isa (k, 'single')) x = NaN (size (p), 'single'); else x = NaN (size (p)); endif ok = (lambda > 0) & (lambda < Inf) & (k > 0) & (k < Inf); pk = (p == 0) & ok; x(pk) = 0; pk = (p == 1) & ok; x(pk) = Inf; pk = (p > 0) & (p < 1) & ok; if (isscalar (lambda) && isscalar (k)) x(pk) = lambda * (- log (1 - p(pk))) .^ (1 / k); else x(pk) = lambda(pk) .* (- log (1 - p(pk))) .^ (1 ./ k(pk)); endif endfunction %!demo %! ## Plot various iCDFs from the Weibull distribution %! p = 0.001:0.001:0.999; %! x1 = wblinv (p, 1, 0.5); %! x2 = wblinv (p, 1, 1); %! x3 = wblinv (p, 1, 1.5); %! x4 = wblinv (p, 1, 5); %! plot (p, x1, '-b', p, x2, '-r', p, x3, '-m', p, x4, '-g') %! ylim ([0, 2.5]) %! grid on %! legend ({'λ = 1, k = 0.5', 'λ = 1, k = 1', ... %! 'λ = 1, k = 1.5', 'λ = 1, k = 5'}, 'location', 'northwest') %! title ('Weibull iCDF') %! xlabel ('probability') %! ylabel ('x') ## Test output %!shared p %! p = [-1 0 0.63212055882855778 1 2]; %!assert_equal (wblinv (p, ones (1,5), ones (1,5)), [NaN 0 1 Inf NaN], eps) %!assert_equal (wblinv (p, 1, ones (1,5)), [NaN 0 1 Inf NaN], eps) %!assert_equal (wblinv (p, ones (1,5), 1), [NaN 0 1 Inf NaN], eps) %!assert_equal (wblinv (p, [1 -1 NaN Inf 1], 1), [NaN NaN NaN NaN NaN]) %!assert_equal (wblinv (p, 1, [1 -1 NaN Inf 1]), [NaN NaN NaN NaN NaN]) %!assert_equal (wblinv ([p(1:2) NaN p(4:5)], 1, 1), [NaN 0 NaN Inf NaN]) ## Test class of input preserved %!assert_equal (wblinv ([p, NaN], 1, 1), [NaN 0 1 Inf NaN NaN], eps) %!assert_equal (wblinv (single ([p, NaN]), 1, 1), single ([NaN 0 1 Inf NaN NaN]), eps ('single')) %!assert_equal (wblinv ([p, NaN], single (1), 1), single ([NaN 0 1 Inf NaN NaN]), eps ('single')) %!assert_equal (wblinv ([p, NaN], 1, single (1)), single ([NaN 0 1 Inf NaN NaN]), eps ('single')) ## Test input validation %!error wblinv () %!error wblinv (1,2,3,4) %!error ... %! wblinv (ones (3), ones (2), ones (2)) %!error ... %! wblinv (ones (2), ones (3), ones (2)) %!error ... %! wblinv (ones (2), ones (2), ones (3)) %!error wblinv (int32 (2), 2, 2) %!error wblinv (true, 2, 2) %!error wblinv ('a', 2, 2) %!error wblinv (i, 2, 2) %!error wblinv (2, i, 2) %!error wblinv (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/wblpdf.m000066400000000000000000000131421524624707500244700ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} wblpdf (@var{x}) ## @deftypefnx {statistics} {@var{y} =} wblpdf (@var{x}, @var{lambda}) ## @deftypefnx {statistics} {@var{y} =} wblpdf (@var{x}, @var{lambda}, @var{k}) ## ## Weibull probability density function (PDF). ## ## For each element of @var{x}, compute the probability density function (PDF) ## of the Weibull distribution with scale parameter @var{lambda} and shape ## parameter @var{k}. The size of @var{y} is the common size of @var{x}, ## @var{lambda}, and @var{k}. A scalar input functions as a constant matrix of ## the same size as the other inputs. ## ## Default values are @var{lambda} = 1, @var{k} = 1. ## ## Further information about the Weibull distribution can be found at ## @url{https://en.wikipedia.org/wiki/Weibull_distribution} ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## The @code{prob.WeibullDistribution} class names these same two parameters ## @qcode{A} and @qcode{B}, after MATLAB. @var{lambda} is its @qcode{A} and ## @var{k} is its @qcode{B}. ## @seealso{wblcdf, wblinv, wblrnd, wblfit, wbllike, wblstat, wblplot} ## @end deftypefn function y = wblpdf (x, varargin) ## Check for valid number of input arguments if (nargin < 1 || nargin > 3) error ("wblpdf: invalid number of input arguments."); endif ## Get extra arguments (if they exist) or add defaults if (numel (varargin) > 0) lambda = varargin{1}; else lambda = 1; endif if (numel (varargin) > 1) k = varargin{2}; else k = 1; endif ## Check for common size of X, LAMBDA, and K if (! isscalar (lambda) || ! isscalar (k)) [retval, x, lambda, k] = common_size (x, lambda, k); if (retval > 0) error ("wblpdf: X, LAMBDA, and K must be of common size or scalars."); endif endif ## Check for X, LAMBDA, and K being double or single if (! (isfloat (x) && isfloat (lambda) && isfloat (k))) error ("wblpdf: X, LAMBDA, and K must be double or single."); endif ## Check for X, LAMBDA, and K being reals if (iscomplex (x) || iscomplex (lambda) || iscomplex (k)) error ("wblpdf: X, LAMBDA, and K must not be complex."); endif ## Check for class type if (isa (x, 'single') || isa (lambda, 'single') || isa (k, 'single')) y = NaN (size (x), 'single'); else y = NaN (size (x)); endif ok = ((lambda > 0) & (lambda < Inf) & (k > 0) & (k < Inf)); xk = (x < 0) & ok; y(xk) = 0; ## The density at an infinite abscissa is zero: no proper distribution ## places mass there. y(isinf (x) & (x > 0) & ok) = 0; xk = (x >= 0) & (x < Inf) & ok; if (isscalar (lambda) && isscalar (k)) y(xk) = (k * (lambda .^ -k) ... .* (x(xk) .^ (k - 1)) ... .* exp (- (x(xk) / lambda) .^ k)); else y(xk) = (k(xk) .* (lambda(xk) .^ -k(xk)) ... .* (x(xk) .^ (k(xk) - 1)) ... .* exp (- (x(xk) ./ lambda(xk)) .^ k(xk))); endif endfunction %!demo %! ## Plot various PDFs from the Weibull distribution %! x = 0:0.001:2.5; %! y1 = wblpdf (x, 1, 0.5); %! y2 = wblpdf (x, 1, 1); %! y3 = wblpdf (x, 1, 1.5); %! y4 = wblpdf (x, 1, 5); %! plot (x, y1, '-b', x, y2, '-r', x, y3, '-m', x, y4, '-g') %! grid on %! ylim ([0, 2.5]) %! legend ({'λ = 5, k = 0.5', 'λ = 9, k = 1', ... %! 'λ = 6, k = 1.5', 'λ = 2, k = 5'}, 'location', 'northeast') %! title ('Weibull PDF') %! xlabel ('values in x') %! ylabel ('density') ## Test output %!shared x,y %! x = [-1 0 0.5 1 Inf]; %! y = [0, exp(-x(2:4)), 0]; %!assert_equal (wblpdf (x, ones (1,5), ones (1,5)), y) %!assert_equal (wblpdf (x, 1, ones (1,5)), y) %!assert_equal (wblpdf (x, ones (1,5), 1), y) %!assert_equal (wblpdf (x, [0 NaN Inf 1 1], 1), [NaN NaN NaN y(4:5)]) %!assert_equal (wblpdf (x, 1, [0 NaN Inf 1 1]), [NaN NaN NaN y(4:5)]) %!assert_equal (wblpdf ([x, NaN], 1, 1), [y, NaN]) ## Test class of input preserved %!assert_equal (wblpdf (single ([x, NaN]), 1, 1), single ([y, NaN])) %!assert_equal (wblpdf ([x, NaN], single (1), 1), single ([y, NaN])) %!assert_equal (wblpdf ([x, NaN], 1, single (1)), single ([y, NaN])) ## Test input validation %!error wblpdf (int32 (2), 1, 1) %!error wblpdf (true, 1, 1) %!error wblpdf ('a', 1, 1) %!error wblpdf () %!error wblpdf (1,2,3,4) %!error wblpdf (ones (3), ones (2), ones (2)) %!error wblpdf (ones (2), ones (3), ones (2)) %!error wblpdf (ones (2), ones (2), ones (3)) %!error wblpdf (i, 2, 2) %!error wblpdf (2, i, 2) %!error wblpdf (2, 2, i) statistics-release-1.9.2/inst/Distribution_Functions/wblrnd.m000066400000000000000000000157151524624707500245120ustar00rootroot00000000000000## Copyright (C) 2012 Rik Wehbring ## Copyright (C) 1995-2016 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} wblrnd (@var{lambda}, @var{k}) ## @deftypefnx {statistics} {@var{r} =} wblrnd (@var{lambda}, @var{k}, @var{rows}) ## @deftypefnx {statistics} {@var{r} =} wblrnd (@var{lambda}, @var{k}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} wblrnd (@var{lambda}, @var{k}, [@var{sz}]) ## ## Random arrays from the Weibull distribution. ## ## @code{@var{r} = wblrnd (@var{lambda}, @var{k})} returns an array of random ## numbers chosen from the Weibull distribution with scale parameter ## @var{lambda} and shape parameter @var{k}. The size of @var{r} is the common ## size of @var{lambda} and @var{k}. A scalar input functions as a constant ## matrix of the same size as the other inputs. Both parameters must be ## positive reals. ## ## When called with a single size argument, @code{wblrnd} returns a square ## matrix with the dimension specified. When called with more than one scalar ## argument, the first two arguments are taken as the number of rows and columns ## and any further arguments specify additional matrix dimensions. The size may ## also be specified with a row vector of dimensions, @var{sz}. ## ## Further information about the Weibull distribution can be found at ## @url{https://en.wikipedia.org/wiki/Weibull_distribution} ## ## The @code{prob.WeibullDistribution} class names these same two parameters ## @qcode{A} and @qcode{B}, after MATLAB. @var{lambda} is its @qcode{A} and ## @var{k} is its @qcode{B}. ## @seealso{wblcdf, wblinv, wblpdf, wblfit, wbllike, wblstat, wblplot} ## @end deftypefn function r = wblrnd (lambda, k, varargin) ## Check for valid number of input arguments if (nargin < 2) error ("wblrnd: function called with too few input arguments."); endif ## Check for common size of LAMBDA and K if (! isscalar (lambda) || ! isscalar (k)) [retval, lambda, k] = common_size (lambda, k); if (retval > 0) error ("wblrnd: LAMBDA and K must be of common size or scalars."); endif endif ## Check for LAMBDA and K being reals if (iscomplex (lambda) || iscomplex (k)) error ("wblrnd: LAMBDA and K must not be complex."); endif ## Parse and check SIZE arguments if (nargin == 2) sz = size (lambda); elseif (nargin == 3) if (isscalar (varargin{1}) && varargin{1} == fix (varargin{1})) sz = [varargin{1}, varargin{1}]; elseif (isrow (varargin{1}) && all (varargin{1} == fix (varargin{1}))) sz = varargin{1}; elseif (isempty (varargin{1})) r = []; return; else error (strcat ("wblrnd: SZ must be a scalar or a row vector", ... " of integers.")); endif elseif (nargin > 3) notint = cellfun (@(x) (! isscalar (x) || x != fix (x)), varargin); if (any (notint)) error ("wblrnd: dimensions must be integers."); endif sz = [varargin{:}]; endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); ## Check that parameters match requested dimensions in size ## Use 'size (ones (sz))' to ignore any trailing singleton dimensions in SZ if (! isscalar (lambda) && ! isequal (size (lambda), size (ones (sz)))) error ("wblrnd: LAMBDA and K must be scalar or of size SZ."); endif ## Check for class type if (isa (lambda, 'single') || isa (k, 'single')) cls = 'single'; else cls = 'double'; endif ## Generate random sample from Weibull distribution if (isscalar (lambda) && isscalar (k)) if ((lambda > 0) && (lambda < Inf) && (k > 0) && (k < Inf)) r = lambda * rande (sz, cls) .^ (1/k); else r = NaN (sz, cls); endif else r = lambda .* rande (sz, cls) .^ (1./k); is_nan = (lambda <= 0) | (lambda == Inf) | (k <= 0) | (k == Inf); r(is_nan) = NaN; endif endfunction ## Test output %!assert_equal (size (wblrnd (1, 1)), [1, 1]) %!assert_equal (size (wblrnd (1, ones (2, 1))), [2, 1]) %!assert_equal (size (wblrnd (1, ones (2, 2))), [2, 2]) %!assert_equal (size (wblrnd (ones (2, 1), 1)), [2, 1]) %!assert_equal (size (wblrnd (ones (2, 2), 1)), [2, 2]) %!assert_equal (size (wblrnd (1, 1, 3)), [3, 3]) %!assert_equal (size (wblrnd (1, 1, [4, 1])), [4, 1]) %!assert_equal (size (wblrnd (1, 1, 4, 1)), [4, 1]) %!assert_equal (size (wblrnd (1, 1, 4, 1, 5)), [4, 1, 5]) %!assert_equal (size (wblrnd (1, 1, 0, 1)), [0, 1]) %!assert_equal (size (wblrnd (1, 1, 1, 0)), [1, 0]) %!assert_equal (size (wblrnd (1, 1, 1, 2, 0, 5)), [1, 2, 0, 5]) %!assert_equal (size (wblrnd (1, 1, [])), [0, 0]) %!assert_equal (size (wblrnd (1, 1, [2, 0, 2, 1])), [2, 0, 2]) %!assert_equal (size (wblrnd (1, 2, -1)), [0, 0]) %!assert_equal (size (wblrnd (1, 2, [2, -1, 2])), [2, 0, 2]) %!assert_equal (size (wblrnd (1, 2, 2, -1, 5)), [2, 0, 5]) ## Test class of input preserved %!assert_equal (class (wblrnd (1, 1)), "double") %!assert_equal (class (wblrnd (1, single (1))), "single") %!assert_equal (class (wblrnd (1, single ([1, 1]))), "single") %!assert_equal (class (wblrnd (single (1), 1)), "single") %!assert_equal (class (wblrnd (single ([1, 1]), 1)), "single") ## Test input validation %!error wblrnd () %!error wblrnd (1) %!error ... %! wblrnd (ones (3), ones (2)) %!error ... %! wblrnd (ones (2), ones (3)) %!error wblrnd (i, 2, 3) %!error wblrnd (1, i, 3) %!error ... %! wblrnd (1, 2, 1.2) %!error ... %! wblrnd (1, 2, ones (2)) %!error ... %! wblrnd (1, 2, [2 0 2.5]) %!error ... %! wblrnd (1, 2, 2, 1.5, 5) %!error ... %! wblrnd (2, ones (2), 3) %!error ... %! wblrnd (2, ones (2), [3, 2]) %!error ... %! wblrnd (2, ones (2), 3, 2) statistics-release-1.9.2/inst/Distribution_Functions/wienrnd.m000066400000000000000000000040631524624707500246620ustar00rootroot00000000000000## Copyright (C) 1995-2017 Friedrich Leisch ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} wienrnd (@var{t}, @var{d}, @var{n}) ## ## Return a simulated realization of the @var{d}-dimensional Wiener Process ## on the interval [0, @var{t}]. ## ## If @var{d} is omitted, @var{d} = 1 is used. The first column of the ## return matrix contains time, the remaining columns contain the Wiener ## process. ## ## The optional parameter @var{n} defines the number of summands used for ## simulating the process over an interval of length 1. If @var{n} is ## omitted, @var{n} = 1000 is used. ## @end deftypefn function r = wienrnd (t, d, n) if (nargin == 1) d = 1; n = 1000; elseif (nargin == 2) n = 1000; elseif (nargin > 3) print_usage (); endif if (! isscalar (t) || ! isscalar (d) || ! isscalar (n)) error ("wienrnd: T, D, and N must all be scalars."); endif if (! (fix (t) == t) || ! (fix (d) == d) || ! (fix (n) == n) || t <= 0 || d <= 0 || n <= 0) error ("wienrnd: T, D, and N must all be positive integers."); endif r = randn (n * t, d); r = cumsum (r) / sqrt (n); r = [((1: n*t)' / n), r]; endfunction %!error wienrnd (0) %!error wienrnd (1, 3, -50) %!error wienrnd (5, 0) %!error wienrnd (0.4, 3, 5) %!error wienrnd ([1 4], 3, 5) statistics-release-1.9.2/inst/Distribution_Functions/wishpdf.m000066400000000000000000000070031524624707500246550ustar00rootroot00000000000000## Copyright (C) 2013 Nir Krakauer ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} wishpdf (@var{W}, @var{Sigma}, @var{df}, @var{log_y}=false) ## ## Compute the probability density function of the Wishart distribution ## ## Inputs: A @var{p} x @var{p} matrix @var{W} where to find the PDF. The @var{p} ## x @var{p} positive definite matrix @var{Sigma} and scalar degrees of freedom ## parameter @var{df} characterizing the Wishart distribution. (For the density ## to be finite, need @var{df} > (@var{p} - 1).) ## ## If the flag @var{log_y} is set, return the log probability density -- this ## helps avoid underflow when the numerical value of the density is very small ## ## Output: @var{y} is the probability density of Wishart(@var{Sigma}, @var{df}) ## at @var{W}. ## ## Input arguments must be @qcode{double} or @qcode{single}; integer, logical, ## and character arrays are rejected. MATLAB accepts a character array and ## evaluates it at the character codes, which Octave deliberately does not, ## since a character array is an integer type and integers are refused too. ## ## @seealso{wishrnd, iwishpdf, iwishrnd} ## @end deftypefn function y = wishpdf (W, Sigma, df, log_y=false) if (nargin < 3) print_usage (); endif ## Check for W, SIGMA, and DF being double or single if (! (isfloat (W) && isfloat (Sigma) && isfloat (df))) error ("wishpdf: W, SIGMA, and DF must be double or single."); endif p = size (Sigma, 1); if (df <= (p - 1)) error ("wishpdf: DF too small, no finite densities exist."); endif ## calculate the logarithm of G_d(df/2), the multivariate gamma function g = (p * (p-1) / 4) * log (pi); for i = 1:p g = g + log (gamma ((df + (1 - i))/2)); endfor C = chol (Sigma); ## use formulas for determinant of positive definite matrix for better ## efficiency and numerical accuracy logdet_W = 2*sum (log (diag (chol (W)))); logdet_Sigma = 2*sum (log (diag (C))); y = -(df*p)/2 * log (2) - (df/2)*logdet_Sigma - g + ... ((df - p - 1)/2)*logdet_W - trace (chol2inv (C)*W)/2; if ! log_y y = exp (y); endif endfunction ##test results cross-checked against dwish function in R MCMCpack library %!assert_equal (wishpdf (4, 3, 3.1), 0.07702496, 1E-7); %!assert_equal (wishpdf ([2 -0.3;-0.3 4], [1 0.3;0.3 1], 4), 0.004529741, 1E-7); %!assert_equal (wishpdf ([6 2 5; 2 10 -5; 5 -5 25], [9 5 5; 5 10 -8; 5 -8 22], 5.1), 4.474865e-10, 1E-15); %% Test input validation %!error wishpdf (int32 (eye (2)), eye (2), 3) %!error wishpdf (true (2), eye (2), 3) %!error wishpdf (['ab'; 'cd'], eye (2), 3) %!error wishpdf () %!error wishpdf (1, 2) %!error wishpdf (1, 2, 0) %!error wishpdf (1, 2) statistics-release-1.9.2/inst/Distribution_Functions/wishrnd.m000066400000000000000000000100341524624707500246650ustar00rootroot00000000000000## Copyright (C) 2013-2019 Nir Krakauer ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{W}, @var{D}] =} wishrnd (@var{Sigma}, @var{df}, @var{D}, @var{n}=1) ## ## Return a random matrix sampled from the Wishart distribution with given ## parameters ## ## Inputs: the @math{p * p} positive definite matrix @var{Sigma} (or the ## lower-triangular Cholesky factor @var{D} of @var{Sigma}) and scalar degrees ## of freedom parameter @var{df}. ## ## @var{df} can be non-integer as long as @math{@var{df} > p - 1} ## ## Output: a random @math{p * p} matrix @var{W} from the ## Wishart(@var{Sigma}, @var{df}) distribution. If @var{n} > 1, then @var{W} is ## @var{p} x @var{p} x @var{n} and holds @var{n} such random matrices. ## (Optionally, the lower-triangular Cholesky factor @var{D} of @var{Sigma} is ## also returned.) ## ## Averaged across many samples, the mean of @var{W} should approach ## @var{df}*@var{Sigma}, and the variance of each element @var{W}_ij should ## approach @var{df}*(@var{Sigma}_ij^2 + @var{Sigma}_ii*@var{Sigma}_jj) ## ## @subheading References ## ## @enumerate ## @item ## Yu-Cheng Ku and Peter Bloomfield (2010), Generating Random Wishart Matrices ## with Fractional Degrees of Freedom in OX, ## http://www.gwu.edu/~forcpgm/YuChengKu-030510final-WishartYu-ChengKu.pdf ## @end enumerate ## ## @seealso{wishpdf, iwishpdf, iwishrnd} ## @end deftypefn function [W, D] = wishrnd (Sigma, df, D, n = 1) if (nargin < 2) print_usage (); endif if nargin < 3 || isempty (D) try D = chol (Sigma, 'lower'); catch error (strcat ("iwishrnd: Cholesky decomposition failed;", ... " SIGMA probably not positive definite.")); end_try_catch endif p = size (D, 1); ## Check for integer degrees of freedom. df_isint = (df == floor (df)); if (df < p) ## Truncate and warn only if the distribution is undefined. if (! df_isint && (df < (p - 1))) warning (strcat ("wishrnd: Wishart distribution undefined for", ... " non-integral df < p-1; truncating to floor(df).")); df = floor (df); df_isint = 1; ## Now it is an integer endif endif if (! df_isint) [ii, jj] = ind2sub ([p, p], 1:(p * p)); endif if (n > 1) W = nan (p, p, n); endif for i = 1:n if (df_isint) Z = D * randn (p, df); else Z = diag (sqrt (chi2rnd (df - (0:(p - 1))))); ##fill diagonal ## Note: chi2rnd(x) is equivalent to 2*randg(x/2), but the latter ## seems to offer no performance advantage Z(ii > jj) = randn (p * (p - 1) / 2, 1); #fill lower triangle Z = D * Z; endif W(:, :, i) = Z * Z'; endfor endfunction %!assert_equal (size (wishrnd (1,2)), [1, 1]); %!assert_equal (size (wishrnd (1,2,[])), [1, 1]); %!assert_equal (size (wishrnd (1,2,1)), [1, 1]); %!assert_equal (size (wishrnd ([],2,1)), [1, 1]); %!assert_equal (size (wishrnd ([3 1; 1 3], 2.00001, [], 1)), [2, 2]); %!assert_equal (size (wishrnd (eye (2), 2, [], 3)), [2, 2, 3]); %% Test input validation %!error wishrnd () %!error wishrnd (1) %!error wishrnd ([1; 1], 2) %% Test for non-integer df where p-1 < df < p (should not warn or truncate) %!test %! W = wishrnd (eye (3), 2.5); %! assert_equal (size (W), [3, 3]); %% Test that invalid non-integer df < p-1 triggers a warning %!warning wishrnd (eye (3), 1.5); statistics-release-1.9.2/inst/Distribution_Statistics/000077500000000000000000000000001524624707500232155ustar00rootroot00000000000000statistics-release-1.9.2/inst/Distribution_Statistics/betastat.m000066400000000000000000000101751524624707500252060ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} betastat (@var{a}, @var{b}) ## ## Compute statistics of the Beta distribution. ## ## @code{[@var{m}, @var{v}] = betastat (@var{a}, @var{b})} returns the mean ## and variance of the Beta distribution with shape parameters @var{a} and ## @var{b}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the Beta distribution can be found at ## @url{https://en.wikipedia.org/wiki/Beta_distribution} ## ## @seealso{betacdf, betainv, betapdf, betarnd, betafit, betalike} ## @end deftypefn function [m, v] = betastat (a, b) ## Check for valid number of input arguments if (nargin < 2) error ("betastat: function called with too few input arguments."); endif ## Check for A and B being numeric if (! (isnumeric (a) && isnumeric (b))) error ("betastat: A and B must be numeric."); endif ## Check for A and B being real if (iscomplex (a) || iscomplex (b)) error ("betastat: A and B must not be complex."); endif ## Check for common size of A and B if (! isscalar (a) || ! isscalar (b)) [retval, a, b] = common_size (a, b); if (retval > 0) error ("betastat: A and B must be of common size or scalars."); endif endif ## Catch invalid parameters k = find (! (a > 0 & b > 0)); ## Calculate moments a_b = a + b; m = a ./ (a_b); m(k) = NaN; if (nargout > 1) v = (a .* b) ./ ((a_b .^ 2) .* (a_b + 1)); v(k) = NaN; endif endfunction ## Input validation tests %!error betastat () %!error betastat (1) %!error betastat ({}, 2) %!error betastat (1, '') %!error betastat (i, 2) %!error betastat (1, i) %!error ... %! betastat (ones (3), ones (2)) %!error ... %! betastat (ones (2), ones (3)) ## Output validation tests %!test %! a = -2:6; %! b = 0.4:0.2:2; %! [m, v] = betastat (a, b); %! expected_m = [NaN NaN NaN 1/2 2/3.2 3/4.4 4/5.6 5/6.8 6/8]; %! expected_v = [NaN NaN NaN 0.0833, 0.0558, 0.0402, 0.0309, 0.0250, 0.0208]; %! assert_equal (m, expected_m, eps*100); %! assert_equal (v, expected_v, 0.001); %!test %! a = -2:1:6; %! [m, v] = betastat (a, 1.5); %! expected_m = [NaN NaN NaN 1/2.5 2/3.5 3/4.5 4/5.5 5/6.5 6/7.5]; %! expected_v = [NaN NaN NaN 0.0686, 0.0544, 0.0404, 0.0305, 0.0237, 0.0188]; %! assert_equal (m, expected_m); %! assert_equal (v, expected_v, 0.001); %!test %! a = [14 Inf 10 NaN 10]; %! b = [12 9 NaN Inf 12]; %! [m, v] = betastat (a, b); %! expected_m = [14/26 NaN NaN NaN 10/22]; %! expected_v = [168/18252 NaN NaN NaN 120/11132]; %! assert_equal (m, expected_m); %! assert_equal (v, expected_v); %!assert_equal (nthargout (1:2, @betastat, 5, []), {[], []}) %!assert_equal (nthargout (1:2, @betastat, [], 5), {[], []}) %!assert_equal (size (betastat (rand (10, 5, 4), rand (10, 5, 4))), [10 5 4]) %!assert_equal (size (betastat (rand (10, 5, 4), 7)), [10 5 4]) statistics-release-1.9.2/inst/Distribution_Statistics/binostat.m000066400000000000000000000104121524624707500252140ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2015 Carnë Draug ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} binostat (@var{n}, @var{ps}) ## ## Compute statistics of the binomial distribution. ## ## @code{[@var{m}, @var{v}] = binostat (@var{n}, @var{ps})} returns the mean and ## variance of the binomial distribution with parameters @var{n} and @var{ps}, ## where @var{n} is the number of trials and @var{ps} is the probability of ## success. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Binomial_distribution} ## ## @seealso{binocdf, binoinv, binopdf, binornd, binofit, binolike, binotest} ## @end deftypefn function [m, v] = binostat (n, ps) ## Check for valid number of input arguments if (nargin < 2) error ("binostat: function called with too few input arguments."); endif ## Check for N and PS being numeric if (! (isnumeric (n) && isnumeric (ps))) error ("binostat: N and PS must be numeric."); endif ## Check for N and PS being real if (iscomplex (n) || iscomplex (ps)) error ("binostat: N and PS must not be complex."); endif ## Check for common size of N and PS if (! isscalar (n) || ! isscalar (ps)) [retval, n, ps] = common_size (n, ps); if (retval > 0) error ("binostat: N and PS must be of common size or scalars."); endif endif ## Catch invalid parameters k = find (! (n > 0 & fix (n) == n & ps >= 0 & ps <= 1)); ## Calculate moments m = n .* ps; m(k) = NaN; if (nargout > 1) v = m .* (1 - ps); v(k) = NaN; endif endfunction ## Input validation tests %!error binostat () %!error binostat (1) %!error binostat ({}, 2) %!error binostat (1, '') %!error binostat (i, 2) %!error binostat (1, i) %!error ... %! binostat (ones (3), ones (2)) %!error ... %! binostat (ones (2), ones (3)) ## Output validation tests %!test %! n = 1:6; %! ps = 0:0.2:1; %! [m, v] = binostat (n, ps); %! expected_m = [0.00, 0.40, 1.20, 2.40, 4.00, 6.00]; %! expected_v = [0.00, 0.32, 0.72, 0.96, 0.80, 0.00]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); %!test %! n = 1:6; %! [m, v] = binostat (n, 0.5); %! expected_m = [0.50, 1.00, 1.50, 2.00, 2.50, 3.00]; %! expected_v = [0.25, 0.50, 0.75, 1.00, 1.25, 1.50]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); %!test %! n = [-Inf -3 5 0.5 3 NaN 100, Inf]; %! [m, v] = binostat (n, 0.5); %! assert_equal (isnan (m), [true true false true false true false false]) %! assert_equal (isnan (v), [true true false true false true false false]) %! assert_equal (m(end), Inf); %! assert_equal (v(end), Inf); %!assert_equal (nthargout (1:2, @binostat, 5, []), {[], []}) %!assert_equal (nthargout (1:2, @binostat, [], 5), {[], []}) %!assert_equal (size (binostat (randi (100, 10, 5, 4), rand (10, 5, 4))), [10 5 4]) %!assert_equal (size (binostat (randi (100, 10, 5, 4), 7)), [10 5 4]) statistics-release-1.9.2/inst/Distribution_Statistics/bisastat.m000066400000000000000000000075171524624707500252170ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} bisastat (@var{beta}, @var{gamma}) ## ## Compute statistics of the Birnbaum-Saunders distribution. ## ## @code{[@var{m}, @var{v}] = bisastat (@var{beta}, @var{gamma})} returns the ## mean and variance of the Birnbaum-Saunders distribution with scale parameter ## @var{beta} and shape parameter @var{gamma}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the Birnbaum-Saunders distribution can be found at ## @url{https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution} ## ## @seealso{bisacdf, bisainv, bisapdf, bisarnd, bisafit, bisalike} ## @end deftypefn function [m, v] = bisastat (beta, gamma) ## Check for valid number of input arguments if (nargin < 2) error ("bisastat: function called with too few input arguments."); endif ## Check for BETA and GAMMA being numeric if (! (isnumeric (beta) && isnumeric (gamma))) error ("bisastat: BETA and GAMMA must be numeric."); endif ## Check for BETA and GAMMA being real if (iscomplex (beta) || iscomplex (gamma)) error ("bisastat: BETA and GAMMA must not be complex."); endif ## Check for common size of BETA and GAMMA if (! isscalar (beta) || ! isscalar (gamma)) [retval, beta, gamma] = common_size (beta, gamma); if (retval > 0) error ("bisastat: BETA and GAMMA must be of common size or scalars."); endif endif ## Calculate moments m = beta .* (1 + ((gamma .^ 2) ./ 2)); v = ((beta .* gamma) .^ 2) .* (1 + ((5 .* (gamma .^ 2)) ./ 4)); ## Continue argument check beta = find (! (beta > 0) | ! (beta < Inf) | ! (gamma > 0) | ! (gamma < Inf)); if (any (beta)) m(beta) = NaN; v(beta) = NaN; endif endfunction ## Input validation tests %!error bisastat () %!error bisastat (1) %!error bisastat ({}, 2) %!error bisastat (1, '') %!error bisastat (i, 2) %!error bisastat (1, i) %!error ... %! bisastat (ones (3), ones (2)) %!error ... %! bisastat (ones (2), ones (3)) ## Output validation tests %!test %! beta = 1:6; %! gamma = 1:0.2:2; %! [m, v] = bisastat (beta, gamma); %! expected_m = [1.50, 3.44, 5.94, 9.12, 13.10, 18]; %! expected_v = [2.25, 16.128, 60.858, 172.032, 409.050, 864]; %! assert_equal (m, expected_m, 1e-2); %! assert_equal (v, expected_v, 1e-3); %!test %! beta = 1:6; %! [m, v] = bisastat (beta, 1.5); %! expected_m = [2.125, 4.25, 6.375, 8.5, 10.625, 12.75]; %! expected_v = [8.5781, 34.3125, 77.2031, 137.2500, 214.4531, 308.8125]; %! assert_equal (m, expected_m, 1e-3); %! assert_equal (v, expected_v, 1e-4); statistics-release-1.9.2/inst/Distribution_Statistics/burrstat.m000066400000000000000000000105611524624707500252440ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} burrstat (@var{lambda}, @var{c}, @var{k}) ## ## Compute statistics of the Burr type XII distribution. ## ## @code{[@var{m}, @var{v}] = burrstat (@var{lambda}, @var{c}, @var{k})} returns ## the mean and variance of the Burr type XII distribution with scale parameter ## @var{lambda}, first shape parameter @var{c}, and second shape parameter ## @var{k}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the same ## size as the other inputs. ## ## Further information about the Burr distribution can be found at ## @url{https://en.wikipedia.org/wiki/Burr_distribution} ## ## @seealso{gevcdf, gevinv, gevpdf, gevrnd, gevfit, gevlike} ## @end deftypefn function [m, v] = burrstat (lambda, c, k) ## Check for is_val number of input arguments if (nargin < 3) error ("burrstat: function called with too few input arguments."); endif ## Check for LAMBDA, C, and K being numeric if (! (isnumeric (lambda) && isnumeric (c) && isnumeric (k))) error ("burrstat: LAMBDA, C, and K must be numeric."); endif ## Check for LAMBDA, C, and K being real if (iscomplex (lambda) || iscomplex (c) || iscomplex (k)) error ("burrstat: LAMBDA, C, and K must not be complex."); endif ## Check for common size of LAMBDA, C, and K if (! isscalar (lambda) || ! isscalar (c) || ! isscalar (k)) [retval, lambda, c, k] = common_size (lambda, c, k); if (retval > 0) error ("burrstat: LAMBDA, C, and K must be of common size or scalars."); endif endif ## Preallocate mean annd variance m = v = nan (size (lambda)); ## Precalculate some values c_i = 1 ./ c; l_c = lambda .* c_i; kci = k - c_i; ## Find valid vases c_k = c .* k; is_val = lambda > 0 & c > 0 & k > 0; ## Calculate 1st moment is_inf = is_val & c_k <= 1; m(is_inf) = Inf; no_inf = ! is_inf; m(no_inf) = l_c(no_inf) .* beta (c_i(no_inf), kci(no_inf)); ## Calculate 2nd moment is_inf = is_val & c_k <= 2; v(is_inf) = Inf; no_inf = ! is_inf; v(no_inf) = 2 * lambda(no_inf) .* l_c(no_inf) .* ... beta (c_i(no_inf) .* 2, kci(no_inf) - c_i(no_inf)) ... - m(no_inf) .^ 2; endfunction ## Input validation tests %!error burrstat () %!error burrstat (1) %!error burrstat (1, 2) %!error burrstat ({}, 2, 3) %!error burrstat (1, '', 3) %!error burrstat (1, 2, '') %!error burrstat (i, 2, 3) %!error burrstat (1, i, 3) %!error burrstat (1, 2, i) %!error ... %! burrstat (ones (3), ones (2), 3) %!error ... %! burrstat (ones (2), 2, ones (3)) %!error ... %! burrstat (1, ones (2), ones (3)) ## Output validation tests %!test %! [m, v] = burrstat (1, 2, 5); %! assert_equal (m, 0.4295, 1e-4); %! assert_equal (v, 0.0655, 1e-4); %!test %! [m, v] = burrstat (1, 1, 1); %! assert_equal (m, Inf); %! assert_equal (v, Inf); %!test %! [m, v] = burrstat (2, 4, 1); %! assert_equal (m, 2.2214, 1e-4); %! assert_equal (v, 1.3484, 1e-4); statistics-release-1.9.2/inst/Distribution_Statistics/chi2stat.m000066400000000000000000000046201524624707500251160ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} chi2stat (@var{df}) ## ## Compute statistics of the chi-squared distribution. ## ## @code{[@var{m}, @var{v}] = chi2stat (@var{df})} returns the mean and ## variance of the chi-squared distribution with @var{df} degrees of freedom. ## ## The size of @var{m} (mean) and @var{v} (variance) is the same size of the ## input argument. ## ## Further information about the chi-squared distribution can be found at ## @url{https://en.wikipedia.org/wiki/Chi-squared_distribution} ## ## @seealso{chi2cdf, chi2inv, chi2pdf, chi2rnd} ## @end deftypefn function [m, v] = chi2stat (df) ## Check for valid number of input arguments if (nargin < 1) error ("chi2stat: function called with too few input arguments."); endif ## Check for DF being numeric if (! isnumeric (df)) error ("chi2stat: DF must be numeric."); endif ## Check for DF being real if (iscomplex (df)) error ("chi2stat: DF must not be complex."); endif ## Calculate moments m = df; v = 2 .* df; ## Continue argument check k = find (! (df > 0) | ! (df < Inf)); if (any (k)) m(k) = NaN; v(k) = NaN; endif endfunction ## Input validation tests %!error chi2stat () %!error chi2stat ({}) %!error chi2stat ('') %!error chi2stat (i) ## Output validation tests %!test %! df = 1:6; %! [m, v] = chi2stat (df); %! assert_equal (m, df); %! assert_equal (v, [2, 4, 6, 8, 10, 12], 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/copulaparam.m000066400000000000000000000263201524624707500257020ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{param} =} copulaparam (@var{family}, @var{r}) ## @deftypefnx {statistics} {@var{param} =} copulaparam (@dots{}, @qcode{"type"}, @var{type}) ## ## Copula parameter as a function of rank correlation. ## ## @code{@var{param} = copulaparam (@var{family}, @var{r})} returns the linear ## or copula parameter @var{param} corresponding to a copula of the family ## @var{family} that has Kendall's rank correlation @var{r}. It is the inverse ## of @code{copulastat}. ## ## @var{family} is the copula family name. It can be @qcode{"Gaussian"} for the ## Gaussian family, @qcode{"t"} for the Student's t family, @qcode{"Clayton"} ## for the Clayton family, @qcode{"Gumbel"} for the Gumbel-Hougaard family, ## @qcode{"Frank"} for the Frank family, @qcode{"AMH"} for the Ali-Mikhail-Haq ## family, or @qcode{"FGM"} for the Farlie-Gumbel-Morgenstern family. The last ## two are Octave extensions that MATLAB does not provide, and are treated as ## bivariate. Neither reaches the whole range of either rank correlation: the ## Ali-Mikhail-Haq family covers a Kendall's tau in ## @code{[(5-8*log (2))/3, 1/3]} and the Farlie-Gumbel-Morgenstern family one ## in @code{[-2/9, 2/9]}. ## ## For the Gaussian and Student's t families, @var{r} is a scalar rank ## correlation or a matrix of pairwise rank correlations, and @var{param} is the ## corresponding linear correlation of the same size. For the Clayton, ## Gumbel-Hougaard, and Frank families, @var{r} is a scalar rank correlation and ## @var{param} is the scalar copula parameter. The Gumbel-Hougaard family ## models positive dependence only, so @var{r} must be non-negative for that ## family. ## ## @code{@var{param} = copulaparam (@dots{}, @qcode{"type"}, @var{type})} ## selects the measure of rank correlation given in @var{r}. @var{type} can be ## @qcode{"Kendall"} (the default) for Kendall's tau, or @qcode{"Spearman"} for ## Spearman's rho. ## ## The Gaussian and Student's t families and the Kendall's tau of the Clayton ## and Gumbel-Hougaard families are inverted in closed form. The remaining ## cases are inverted numerically from @code{copulastat}. ## ## @strong{Note:} for the Archimedean families with @qcode{"Spearman"}, the ## underlying relationship is computed by exact numerical integration rather ## than the interpolated table used by @sc{matlab}, so results may differ from ## @sc{matlab} by up to about @math{10^{-4}}. See @code{copulastat}. ## ## @seealso{copulastat, copulafit, copulacdf, copulapdf, copularnd} ## @end deftypefn function param = copulaparam (family, r, varargin) ## Check arguments if (nargin < 2) print_usage (); endif if (! ischar (family)) error (strcat ("copulaparam: FAMILY must be one of 'Gaussian',", ... " 't', 'Clayton', 'Gumbel', 'Frank', 'AMH', and 'FGM'.")); endif if (! isnumeric (r) || ! isreal (r)) error ("copulaparam: R must be real."); endif ## Parse the 'type' option type = 'kendall'; if (numel (varargin) > 0) if (numel (varargin) != 2 || ! ischar (varargin{1}) || ... ! strcmpi (varargin{1}, 'type')) error ("copulaparam: invalid optional argument."); endif if (! ischar (varargin{2}) || ... ! any (strcmpi (varargin{2}, {'kendall', 'spearman'}))) error ("copulaparam: TYPE must be either 'Kendall' or 'Spearman'."); endif type = lower (varargin{2}); endif lower_family = lower (family); switch (lower_family) case {'gaussian', 't'} ## Elliptical families: closed-form inverse, applied elementwise if (any (abs (r(:)) > 1)) error ("copulaparam: R must be a correlation in the range -1 to 1."); endif if (strcmp (type, 'kendall')) param = sin (pi .* r ./ 2); else param = 2 .* sin (pi .* r ./ 6); endif case {'clayton', 'gumbel', 'frank'} ## Archimedean families: scalar rank correlation if (! isscalar (r)) error ("copulaparam: R must be a scalar for the %s family.", family); endif if (abs (r) >= 1) error ("copulaparam: R must be in the range -1 to 1."); endif if (strcmp (lower_family, 'gumbel') && r < 0) error (strcat ("copulaparam: R must be non-negative for the", ... " Gumbel family.")); endif switch (lower_family) case 'clayton' if (strcmp (type, 'kendall')) param = 2 .* r ./ (1 - r); else param = invert_stat (lower_family, r, type); endif case 'gumbel' if (strcmp (type, 'kendall')) param = 1 ./ (1 - r); else param = invert_stat (lower_family, r, type); endif case 'frank' param = invert_stat (lower_family, r, type); endswitch case {'amh', 'fgm'} ## Octave extensions that MATLAB does not have, both bivariate here. if (! isscalar (r)) error ("copulaparam: R must be a scalar for the %s family.", family); endif if (strcmp (lower_family, 'fgm')) ## Both measures are linear in the parameter, so the inverse is too. if (strcmp (type, 'kendall')) lim = 2 / 9; param = 9 .* r ./ 2; else lim = 1 / 3; param = 3 .* r; endif if (abs (r) > lim) error (strcat ("copulaparam: R must be in the range -%g to %g", ... " for the FGM family."), lim, lim); endif else ## The Ali-Mikhail-Haq family reaches only part of the range, and ## neither measure inverts in closed form. lo = copulastat ('AMH', -1, 'type', type); hi = copulastat ('AMH', 1 - eps, 'type', type); if (r < lo || r > hi) error (strcat ("copulaparam: R must be in the range %g to %g", ... " for the AMH family."), lo, hi); endif if (r == 0) param = 0; else param = fzero (@(a) copulastat ('AMH', a, 'type', type) - r, ... [-1, 1 - eps]); endif endif otherwise error ("copulaparam: unknown copula family '%s'.", family); endswitch endfunction ## Invert a rank correlation numerically from copulastat for an Archimedean ## family. The dependence measure is monotone increasing in the parameter, so ## we bracket the root and solve. function a = invert_stat (family, r, type) g = @(aa) copulastat (family, aa, 'type', type) - r; ## Parameter value at which the family is the independence copula if (strcmp (family, 'gumbel')) s0 = 1; else s0 = 0; endif if (r == 0) a = s0; return; elseif (r > 0) lo = s0; hi = s0 + 1; while (g (hi) < 0 && hi < 1e8) hi = s0 + (hi - s0) .* 2; endwhile else ## r < 0 (Clayton or Frank only) hi = s0; if (strcmp (family, 'clayton')) lo = -1 + 1e-8; else lo = s0 - 1; while (g (lo) > 0 && lo > -1e8) lo = s0 - (s0 - lo) .* 2; endwhile endif endif a = fzero (g, [lo, hi]); endfunction %!demo %! ## Copula parameter of a Gaussian copula with Kendall's tau 0.3 %! rho = copulaparam ("Gaussian", 0.3) %!demo %! ## copulaparam inverts copulastat %! alpha = copulaparam ("Clayton", 0.5) %! tau = copulastat ("Clayton", alpha) ## Test output against MATLAB %!test %! assert_equal (copulaparam ("Gaussian", 0.3), 0.453990499739547, 1e-14); %! assert_equal (copulaparam ("Gaussian", 0.3, "type", "Spearman"), ... %! 0.312868930080462, 1e-14); %! assert_equal (copulaparam ("t", 0.3), 0.453990499739547, 1e-14); %! assert_equal (copulaparam ("Clayton", 0.3), 0.857142857142857, 1e-14); %! assert_equal (copulaparam ("Frank", 0.3), 2.91743444592452, 1e-8); %! assert_equal (copulaparam ("Gumbel", 0.3), 1.42857142857143, 1e-13); ## copulaparam is the inverse of copulastat (round trip) %!test %! for tau = [0.1, 0.25, 0.5, 0.7] %! for fam = {"Clayton", "Gumbel", "Frank"} %! a = copulaparam (fam{1}, tau); %! assert_equal (copulastat (fam{1}, a), tau, 1e-8); %! endfor %! endfor %!test %! for rs = [0.1, 0.3, 0.6] %! for fam = {"Clayton", "Gumbel", "Frank"} %! a = copulaparam (fam{1}, rs, "type", "Spearman"); %! assert_equal (copulastat (fam{1}, a, "type", "Spearman"), rs, 1e-7); %! endfor %! endfor ## Negative dependence for the signed families %!test %! a = copulaparam ("Frank", -0.3); %! assert_equal (copulastat ("Frank", a), -0.3, 1e-8); %! a = copulaparam ("Clayton", -0.2); %! assert_equal (copulastat ("Clayton", a), -0.2, 1e-8); ## Elliptical families accept a correlation matrix and act elementwise %!test %! tau = [1, 0.3; 0.3, 1]; %! assert_equal (copulaparam ("Gaussian", tau), sin (pi .* tau ./ 2), 1e-14); ## Test input validation %!error ... %! copulaparam (5, 0.3) %!error copulaparam ("Gaussian", 2i) %!error ... %! copulaparam ("Gaussian", 0.3, "type", "Pearson") %!error ... %! copulaparam ("Gaussian", 0.3, "foo") %!error ... %! copulaparam ("Gaussian", 1.5) %!error ... %! copulaparam ("Clayton", [0.1, 0.2]) %!error ... %! copulaparam ("Clayton", 1) %!error ... %! copulaparam ("Gumbel", -0.3) %!error copulaparam ("Foo", 0.3) ## The Ali-Mikhail-Haq and Farlie-Gumbel-Morgenstern families, Octave ## extensions. Inverting copulastat must return the parameter it was given. %!test %! for ty = {'Kendall', 'Spearman'} %! for a = [-0.9, -0.5, -0.1, 0, 0.3, 0.7, 0.95] %! r = copulastat ('AMH', a, 'type', ty{1}); %! assert_equal (copulaparam ('AMH', r, 'type', ty{1}), a, 1e-9); %! endfor %! for a = [-1, -0.4, 0, 0.6, 1] %! r = copulastat ('FGM', a, 'type', ty{1}); %! assert_equal (copulaparam ('FGM', r, 'type', ty{1}), a, 1e-12); %! endfor %! endfor %!test # the FGM inverse is linear %! assert_equal (copulaparam ('FGM', 0.1), 0.45, 1e-14); %! assert_equal (copulaparam ('FGM', 0.1, 'type', 'Spearman'), 0.3, 1e-14); %!error ... %! copulaparam ('FGM', 0.5) %!error ... %! copulaparam ('AMH', 0.5) statistics-release-1.9.2/inst/Distribution_Statistics/copulastat.m000066400000000000000000000327301524624707500255570ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} copulastat (@var{family}, @var{param}) ## @deftypefnx {statistics} {@var{r} =} copulastat (@dots{}, @qcode{"type"}, @var{type}) ## ## Rank correlation for a copula family. ## ## @code{@var{r} = copulastat (@var{family}, @var{param})} returns Kendall's ## rank correlation @var{r} corresponding to a copula of the family ## @var{family} with linear or copula parameter @var{param}. ## ## @var{family} is the copula family name. It can be @qcode{"Gaussian"} for the ## Gaussian family, @qcode{"t"} for the Student's t family, @qcode{"Clayton"} ## for the Clayton family, @qcode{"Gumbel"} for the Gumbel-Hougaard family, ## @qcode{"Frank"} for the Frank family, @qcode{"AMH"} for the Ali-Mikhail-Haq ## family, or @qcode{"FGM"} for the Farlie-Gumbel-Morgenstern family. The last ## two are Octave extensions that MATLAB does not provide, and are treated as ## bivariate. Neither reaches the whole range of either rank correlation: the ## Ali-Mikhail-Haq family covers a Kendall's tau in ## @code{[(5-8*log (2))/3, 1/3]} and the Farlie-Gumbel-Morgenstern family one ## in @code{[-2/9, 2/9]}. ## ## For the Gaussian and Student's t families, @var{param} is a linear ## correlation coefficient @var{rho} in the range @math{[-1,1]}, or a ## @math{p}-by-@math{p} correlation matrix, in which case @var{r} has the same ## size and each element is computed elementwise. For the Clayton, ## Gumbel-Hougaard, and Frank families, @var{param} is the scalar copula ## parameter. ## ## @code{@var{r} = copulastat (@dots{}, @qcode{"type"}, @var{type})} selects the ## measure of rank correlation. @var{type} can be @qcode{"Kendall"} (the ## default) for Kendall's tau, or @qcode{"Spearman"} for Spearman's rho. ## ## The relationships are closed form for the Gaussian and Student's t families ## (@math{r = 2 \arcsin(\rho) / \pi} for Kendall's tau and ## @math{r = 6 \arcsin(\rho/2) / \pi} for Spearman's rho) and for the Kendall's ## tau of the Archimedean families. Spearman's rho of the Archimedean families ## has no closed form and is computed by accurate numerical integration of the ## copula. ## ## @strong{Note:} @sc{matlab} returns the Archimedean Spearman's rho by ## interpolating an internal precomputed table, whose values deviate from the ## true relationship by up to about @math{10^{-4}}. This implementation returns ## the mathematically exact value instead, so results for ## @code{copulastat (@var{family}, @var{param}, "type", "Spearman")} with an ## Archimedean @var{family} may differ from @sc{matlab} at that level. ## ## @seealso{copulaparam, copulafit, copulacdf, copulapdf, copularnd} ## @end deftypefn function r = copulastat (family, param, varargin) ## Check arguments if (nargin < 2) print_usage (); endif if (! ischar (family)) error (strcat ("copulastat: FAMILY must be one of 'Gaussian',", ... " 't', 'Clayton', 'Gumbel', 'Frank', 'AMH', and 'FGM'.")); endif if (! isnumeric (param) || ! isreal (param)) error ("copulastat: PARAM must be real."); endif ## Parse the 'type' option type = 'kendall'; if (numel (varargin) > 0) if (numel (varargin) != 2 || ! ischar (varargin{1}) || ... ! strcmpi (varargin{1}, 'type')) error ("copulastat: invalid optional argument."); endif if (! ischar (varargin{2}) || ... ! any (strcmpi (varargin{2}, {'kendall', 'spearman'}))) error ("copulastat: TYPE must be either 'Kendall' or 'Spearman'."); endif type = lower (varargin{2}); endif lower_family = lower (family); switch (lower_family) case {'gaussian', 't'} ## Elliptical families: closed form, applied elementwise if (any (abs (param(:)) > 1)) error ("copulastat: PARAM must be a correlation in the range -1 to 1."); endif if (strcmp (type, 'kendall')) r = 2 ./ pi .* asin (param); else r = 6 ./ pi .* asin (param ./ 2); endif case {'clayton', 'gumbel', 'frank'} ## Archimedean families: scalar copula parameter if (! isscalar (param)) error ("copulastat: PARAM must be a scalar for the %s family.", family); endif switch (lower_family) case 'clayton' if (param < -1) error (strcat ("copulastat: PARAM must be greater than or", ... " equal to -1 for the Clayton family.")); endif case 'gumbel' if (param < 1) error (strcat ("copulastat: PARAM must be greater than or", ... " equal to 1 for the Gumbel family.")); endif endswitch if (strcmp (type, 'kendall')) r = archimedean_kendall (lower_family, param); else r = archimedean_spearman (lower_family, param); endif case {'amh', 'fgm'} ## Octave extensions that MATLAB does not have. Both are bivariate here: ## the rank correlations are properties of a pair of variables, and the ## multivariate Farlie-Gumbel-Morgenstern family carries one parameter ## per subset rather than one overall. if (! isscalar (param)) error ("copulastat: PARAM must be a scalar for the %s family.", family); endif if (strcmp (lower_family, 'amh')) if (param < -1 || param >= 1) error (strcat ("copulastat: PARAM must be greater than or equal", ... " to -1 and less than 1 for the AMH family.")); endif if (strcmp (type, 'kendall')) r = amh_kendall (param); else r = amh_spearman (param); endif else if (param < -1 || param > 1) error (strcat ("copulastat: PARAM must be in the range -1 to 1", ... " for the FGM family.")); endif ## Both measures are linear in the parameter for this family if (strcmp (type, 'kendall')) r = 2 .* param ./ 9; else r = param ./ 3; endif endif otherwise error ("copulastat: unknown copula family '%s'.", family); endswitch endfunction ## Kendall's tau of the Ali-Mikhail-Haq copula. The limit at PARAM == 0 is ## 2/9 * PARAM, which is zero; the closed form is 0/0 there. function t = amh_kendall (a) if (a == 0) t = 0; else t = 1 - 2 .* ((1 - a) .^ 2 .* log (1 - a) + a) ./ (3 .* a .^ 2); endif endfunction ## Spearman's rho of the Ali-Mikhail-Haq copula, which needs the dilogarithm. function r = amh_spearman (a) if (a == 0) r = 0; else r = (12 .* (1 + a) .* dilog (a) - 24 .* (1 - a) .* log (1 - a)) ... ./ a .^ 2 - 3 .* (a + 12) ./ a; endif endfunction ## The dilogarithm, -int_0^z log (1-t)/t dt. Substituting t = z s moves the ## removable singularity off the end of the interval, which the direct form ## integrates badly for negative z. function d = dilog (z) if (z == 0) d = 0; else d = -integral (@(s) log (1 - z .* s) ./ s, 0, 1); endif endfunction ## Kendall's tau for the Archimedean families (closed form) function tau = archimedean_kendall (family, alpha) switch (family) case 'clayton' tau = alpha ./ (alpha + 2); case 'gumbel' tau = 1 - 1 ./ alpha; case 'frank' if (alpha == 0) tau = 0; else tau = 1 - 4 ./ alpha .* (1 - debye1 (alpha)); endif endswitch endfunction ## Spearman's rho for the Archimedean families by numerical integration of the ## copula: rho_s = 12 * integral over the unit square of C(u,v), minus 3. function rho = archimedean_spearman (family, alpha) if (family_is_independent (family, alpha)) rho = 0; return; endif [x, w] = gauss_legendre_unit (); [U, V] = meshgrid (x, x); switch (family) case 'clayton' C = (U .^ (-alpha) + V .^ (-alpha) - 1) .^ (-1 ./ alpha); case 'gumbel' C = exp (-((-log (U)) .^ alpha + (-log (V)) .^ alpha) .^ (1 ./ alpha)); case 'frank' C = -log (1 + (expm1 (-alpha .* U) .* expm1 (-alpha .* V)) ... ./ expm1 (-alpha)) ./ alpha; endswitch rho = 12 .* (w(:)' * C * w(:)) - 3; endfunction ## True when the parameter reduces the family to the independence copula function tf = family_is_independent (family, alpha) tf = ((strcmp (family, 'clayton') || strcmp (family, 'frank')) ... && alpha == 0) || (strcmp (family, 'gumbel') && alpha == 1); endfunction ## Debye function of the first order, D1(x) = (1/x) * integral_0^x t/(e^t-1) dt, ## valid for any real x (the removable singularity at t=0 is handled by expm1). function d = debye1 (x) d = integral (@(t) t ./ expm1 (t), 0, x) ./ x; endfunction ## Nodes and weights of a 64-point Gauss-Legendre rule mapped to [0,1], cached ## across calls (Golub-Welsch). function [x, w] = gauss_legendre_unit () persistent nodes weights if (isempty (nodes)) N = 64; beta = 0.5 ./ sqrt (1 - (2 .* (1:N-1)) .^ (-2)); T = diag (beta, 1) + diag (beta, -1); [V, D] = eig (T); [nodes, idx] = sort (diag (D)); weights = 2 .* (V(1, idx) .^ 2)'; nodes = (nodes + 1) ./ 2; weights = weights ./ 2; endif x = nodes; w = weights; endfunction %!demo %! ## Kendall's tau and Spearman's rho of a Gaussian copula with correlation 0.5 %! tau = copulastat ("Gaussian", 0.5) %! rho = copulastat ("Gaussian", 0.5, "type", "Spearman") %!demo %! ## Kendall's tau of a Clayton copula as its parameter grows %! alpha = [0.5, 1, 2, 5]; %! tau = arrayfun (@(a) copulastat ("Clayton", a), alpha) ## Test output against MATLAB %!test %! assert_equal (copulastat ("Gaussian", 0.5), 1/3, 1e-14); %! assert_equal (copulastat ("Gaussian", 0.5, "type", "Spearman"), ... %! 0.482583739530997, 1e-14); %! assert_equal (copulastat ("t", 0.5), 1/3, 1e-14); %! assert_equal (copulastat ("Clayton", 2), 0.5, 1e-14); %! assert_equal (copulastat ("Frank", 3), 0.307246959430723, 1e-12); %! assert_equal (copulastat ("Gumbel", 2), 0.5, 1e-14); ## Elliptical families accept a correlation matrix and act elementwise %!test %! rho = [1, 0.5; 0.5, 1]; %! assert_equal (copulastat ("Gaussian", rho), 2/pi .* asin (rho), 1e-14); ## Independence limits %!test %! assert_equal (copulastat ("Clayton", 0), 0, 1e-14); %! assert_equal (copulastat ("Frank", 0), 0, 1e-14); %! assert_equal (copulastat ("Gumbel", 1), 0, 1e-14); %! assert_equal (copulastat ("Clayton", 0, "type", "Spearman"), 0, 1e-14); %! assert_equal (copulastat ("Gumbel", 1, "type", "Spearman"), 0, 1e-14); ## Test input validation %!error ... %! copulastat (5, 0.5) %!error copulastat ("Gaussian", 2i) %!error ... %! copulastat ("Gaussian", 0.5, "type", "Pearson") %!error ... %! copulastat ("Gaussian", 0.5, "foo") %!error ... %! copulastat ("Gaussian", 1.5) %!error ... %! copulastat ("Clayton", [1, 2]) %!error ... %! copulastat ("Gumbel", 0.5) %!error copulastat ("Foo", 0.5) ## The Ali-Mikhail-Haq and Farlie-Gumbel-Morgenstern families, Octave ## extensions. Both rank correlations are checked against the integrals that ## define them, evaluated on copulacdf and copulapdf. %!test %! for a = [-0.9, -0.5, 0.5, 0.9] %! f = @(u, v) arrayfun (@(p, q) copulacdf ('AMH', [p, q], a), u, v); %! rho = 12 * integral2 (f, 0, 1, 0, 1, 'AbsTol', 1e-11) - 3; %! assert_equal (copulastat ('AMH', a, 'type', 'Spearman'), rho, 1e-8); %! endfor %!test %! for a = [-0.9, -0.5, 0.5, 0.9] %! f = @(u, v) arrayfun (@(p, q) copulacdf ('AMH', [p, q], a) ... %! * copulapdf ('AMH', [p, q], a), u, v); %! tau = 4 * integral2 (f, 0, 1, 0, 1, 'AbsTol', 1e-11) - 1; %! assert_equal (copulastat ('AMH', a), tau, 1e-8); %! endfor %!test # both measures are linear in the FGM parameter %! for a = [-1, -0.5, 0, 0.5, 1] %! assert_equal (copulastat ('FGM', a), 2 * a / 9, 1e-14); %! assert_equal (copulastat ('FGM', a, 'type', 'Spearman'), a / 3, 1e-14); %! endfor %!test # the independence copula at a zero parameter %! assert_equal (copulastat ('AMH', 0), 0); %! assert_equal (copulastat ('AMH', 0, 'type', 'Spearman'), 0); %! assert_equal (copulastat ('FGM', 0), 0); %!test # the extremes of the Ali-Mikhail-Haq range are the known ones %! assert_equal (copulastat ('AMH', -1), (5 - 8 * log (2)) / 3, 1e-12); %! assert_equal (copulastat ('AMH', 1 - eps), 1 / 3, 1e-9); %!error ... %! copulastat ('AMH', 1) %!error ... %! copulastat ('FGM', 1.5) statistics-release-1.9.2/inst/Distribution_Statistics/doc-cache000066400000000000000000001114211524624707500247460ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 36 # name: # type: sq_string # elements: 1 # length: 8 betastat # name: # type: sq_string # elements: 1 # length: 551 statistics: [ m , v ] = betastat ( a , b ) Compute statistics of the Beta distribution. [ m , v ] = betastat ( a , b ) returns the mean and variance of the Beta distribution with shape parameters a and b . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Beta distribution can be found at https://en.wikipedia.org/wiki/Beta_distribution See also: betacdf, betainv, betapdf, betarnd, betafit, betalike # name: # type: sq_string # elements: 1 # length: 44 Compute statistics of the Beta distribution. # name: # type: sq_string # elements: 1 # length: 8 binostat # name: # type: sq_string # elements: 1 # length: 644 statistics: [ m , v ] = binostat ( n , ps ) Compute statistics of the binomial distribution. [ m , v ] = binostat ( n , ps ) returns the mean and variance of the binomial distribution with parameters n and ps , where n is the number of trials and ps is the probability of success. The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the binomial distribution can be found at https://en.wikipedia.org/wiki/Binomial_distribution See also: binocdf, binoinv, binopdf, binornd, binofit, binolike, binotest # name: # type: sq_string # elements: 1 # length: 48 Compute statistics of the binomial distribution. # name: # type: sq_string # elements: 1 # length: 8 bisastat # name: # type: sq_string # elements: 1 # length: 647 statistics: [ m , v ] = bisastat ( beta , gamma ) Compute statistics of the Birnbaum-Saunders distribution. [ m , v ] = bisastat ( beta , gamma ) returns the mean and variance of the Birnbaum-Saunders distribution with scale parameter beta and shape parameter gamma . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Birnbaum-Saunders distribution can be found at https://en.wikipedia.org/wiki/Birnbaum%E2%80%93Saunders_distribution See also: bisacdf, bisainv, bisapdf, bisarnd, bisafit, bisalike # name: # type: sq_string # elements: 1 # length: 57 Compute statistics of the Birnbaum-Saunders distribution. # name: # type: sq_string # elements: 1 # length: 8 burrstat # name: # type: sq_string # elements: 1 # length: 636 statistics: [ m , v ] = burrstat ( lambda , c , k ) Compute statistics of the Burr type XII distribution. [ m , v ] = burrstat ( lambda , c , k ) returns the mean and variance of the Burr type XII distribution with scale parameter lambda , first shape parameter c , and second shape parameter k . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Burr distribution can be found at https://en.wikipedia.org/wiki/Burr_distribution See also: gevcdf, gevinv, gevpdf, gevrnd, gevfit, gevlike # name: # type: sq_string # elements: 1 # length: 53 Compute statistics of the Burr type XII distribution. # name: # type: sq_string # elements: 1 # length: 8 chi2stat # name: # type: sq_string # elements: 1 # length: 463 statistics: [ m , v ] = chi2stat ( df ) Compute statistics of the chi-squared distribution. [ m , v ] = chi2stat ( df ) returns the mean and variance of the chi-squared distribution with df degrees of freedom. The size of m (mean) and v (variance) is the same size of the input argument. Further information about the chi-squared distribution can be found at https://en.wikipedia.org/wiki/Chi-squared_distribution See also: chi2cdf, chi2inv, chi2pdf, chi2rnd # name: # type: sq_string # elements: 1 # length: 51 Compute statistics of the chi-squared distribution. # name: # type: sq_string # elements: 1 # length: 11 copulaparam # name: # type: sq_string # elements: 1 # length: 2130 statistics: param = copulaparam ( family , r ) statistics: param = copulaparam (…, "type" , type ) Copula parameter as a function of rank correlation. param = copulaparam ( family , r ) returns the linear or copula parameter param corresponding to a copula of the family family that has Kendall’s rank correlation r . It is the inverse of copulastat . family is the copula family name. It can be "Gaussian" for the Gaussian family, "t" for the Student’s t family, "Clayton" for the Clayton family, "Gumbel" for the Gumbel-Hougaard family, "Frank" for the Frank family, "AMH" for the Ali-Mikhail-Haq family, or "FGM" for the Farlie-Gumbel-Morgenstern family. The last two are Octave extensions that MATLAB does not provide, and are treated as bivariate. Neither reaches the whole range of either rank correlation: the Ali-Mikhail-Haq family covers a Kendall’s tau in [(5-8*log (2))/3, 1/3] and the Farlie-Gumbel-Morgenstern family one in [-2/9, 2/9] . For the Gaussian and Student’s t families, r is a scalar rank correlation or a matrix of pairwise rank correlations, and param is the corresponding linear correlation of the same size. For the Clayton, Gumbel-Hougaard, and Frank families, r is a scalar rank correlation and param is the scalar copula parameter. The Gumbel-Hougaard family models positive dependence only, so r must be non-negative for that family. param = copulaparam (…, "type" , type ) selects the measure of rank correlation given in r . type can be "Kendall" (the default) for Kendall’s tau, or "Spearman" for Spearman’s rho. The Gaussian and Student’s t families and the Kendall’s tau of the Clayton and Gumbel-Hougaard families are inverted in closed form. The remaining cases are inverted numerically from copulastat . Note: for the Archimedean families with "Spearman" , the underlying relationship is computed by exact numerical integration rather than the interpolated table used by MATLAB , so results may differ from MATLAB by up to about 10^{-4} . See copulastat . See also: copulastat, copulafit, copulacdf, copulapdf, copularnd # name: # type: sq_string # elements: 1 # length: 51 Copula parameter as a function of rank correlation. # name: # type: sq_string # elements: 1 # length: 10 copulastat # name: # type: sq_string # elements: 1 # length: 2252 statistics: r = copulastat ( family , param ) statistics: r = copulastat (…, "type" , type ) Rank correlation for a copula family. r = copulastat ( family , param ) returns Kendall’s rank correlation r corresponding to a copula of the family family with linear or copula parameter param . family is the copula family name. It can be "Gaussian" for the Gaussian family, "t" for the Student’s t family, "Clayton" for the Clayton family, "Gumbel" for the Gumbel-Hougaard family, "Frank" for the Frank family, "AMH" for the Ali-Mikhail-Haq family, or "FGM" for the Farlie-Gumbel-Morgenstern family. The last two are Octave extensions that MATLAB does not provide, and are treated as bivariate. Neither reaches the whole range of either rank correlation: the Ali-Mikhail-Haq family covers a Kendall’s tau in [(5-8*log (2))/3, 1/3] and the Farlie-Gumbel-Morgenstern family one in [-2/9, 2/9] . For the Gaussian and Student’s t families, param is a linear correlation coefficient rho in the range [-1,1] , or a p -by- p correlation matrix, in which case r has the same size and each element is computed elementwise. For the Clayton, Gumbel-Hougaard, and Frank families, param is the scalar copula parameter. r = copulastat (…, "type" , type ) selects the measure of rank correlation. type can be "Kendall" (the default) for Kendall’s tau, or "Spearman" for Spearman’s rho. The relationships are closed form for the Gaussian and Student’s t families ( r = 2 \arcsin(\rho) / \pi for Kendall’s tau and r = 6 \arcsin(\rho/2) / \pi for Spearman’s rho) and for the Kendall’s tau of the Archimedean families. Spearman’s rho of the Archimedean families has no closed form and is computed by accurate numerical integration of the copula. Note: MATLAB returns the Archimedean Spearman’s rho by interpolating an internal precomputed table, whose values deviate from the true relationship by up to about 10^{-4} . This implementation returns the mathematically exact value instead, so results for copulastat ( family , param , "type", "Spearman") with an Archimedean family may differ from MATLAB at that level. See also: copulaparam, copulafit, copulacdf, copulapdf, copularnd # name: # type: sq_string # elements: 1 # length: 37 Rank correlation for a copula family. # name: # type: sq_string # elements: 1 # length: 6 evstat # name: # type: sq_string # elements: 1 # length: 974 statistics: [ m , v ] = evstat ( mu , sigma ) Compute statistics of the extreme value distribution. [ m , v ] = evstat ( mu , sigma ) returns the mean and variance of the extreme value distribution (also known as the Gumbel or the type I generalized extreme value distribution) with location parameter mu and scale parameter sigma . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. The type 1 extreme value distribution is also known as the Gumbel distribution. This version is suitable for modeling minima. The mirror image of this distribution can be used to model maxima by negating x . If y has a Weibull distribution, then x = log ( y ) has the type 1 extreme value distribution. Further information about the Gumbel distribution can be found at https://en.wikipedia.org/wiki/Gumbel_distribution See also: evcdf, evinv, evpdf, evrnd, evfit, evlike # name: # type: sq_string # elements: 1 # length: 53 Compute statistics of the extreme value distribution. # name: # type: sq_string # elements: 1 # length: 7 expstat # name: # type: sq_string # elements: 1 # length: 751 statistics: [ m , v ] = expstat ( mu ) Compute statistics of the exponential distribution. [ m , v ] = expstat ( mu ) returns the mean and variance of the exponential distribution with mean parameter mu . The size of m (mean) and v (variance) is the same size of the input argument. A common alternative parameterization of the exponential distribution is to use the parameter λ defined as the mean number of events in an interval as opposed to the parameter μ , which is the mean wait time for an event to occur. λ and μ are reciprocals, i.e. μ = 1 / λ . Further information about the exponential distribution can be found at https://en.wikipedia.org/wiki/Exponential_distribution See also: expcdf, expinv, exppdf, exprnd, expfit, explike # name: # type: sq_string # elements: 1 # length: 51 Compute statistics of the exponential distribution. # name: # type: sq_string # elements: 1 # length: 5 fstat # name: # type: sq_string # elements: 1 # length: 518 statistics: [ m , v ] = fstat ( df1 , df2 ) Compute statistics of the F -distribution. [ m , v ] = fstat ( df1 , df2 ) returns the mean and variance of the F -distribution with df1 and df2 degrees of freedom. The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the F -distribution can be found at https://en.wikipedia.org/wiki/F-distribution See also: fcdf, finv, fpdf, frnd # name: # type: sq_string # elements: 1 # length: 41 Compute statistics of the F-distribution. # name: # type: sq_string # elements: 1 # length: 7 gamstat # name: # type: sq_string # elements: 1 # length: 959 statistics: [ m , v ] = gamstat ( a , b ) Compute statistics of the Gamma distribution. [ m , v ] = gamstat ( a , b ) returns the mean and variance of the Gamma distribution with shape parameter a and scale parameter b . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. OCTAVE/MATLAB use the alternative parameterization given by the pair α, β , i.e. shape a and scale b . In Wikipedia, the two common parameterizations use the pairs k, θ , as shape and scale, and α, β , as shape and rate, respectively. The parameter names a and b used here (for MATLAB compatibility) correspond to the parameter notation k, θ instead of the α, β as reported in Wikipedia. Further information about the Gamma distribution can be found at https://en.wikipedia.org/wiki/Gamma_distribution See also: gamcdf, gaminv, gampdf, gamrnd, gamfit, gamlike # name: # type: sq_string # elements: 1 # length: 45 Compute statistics of the Gamma distribution. # name: # type: sq_string # elements: 1 # length: 7 geostat # name: # type: sq_string # elements: 1 # length: 472 statistics: [ m , v ] = geostat ( ps ) Compute statistics of the geometric distribution. [ m , v ] = geostat ( ps ) returns the mean and variance of the geometric distribution with probability of success parameter ps . The size of m (mean) and v (variance) is the same size of the input argument. Further information about the geometric distribution can be found at https://en.wikipedia.org/wiki/Geometric_distribution See also: geocdf, geoinv, geopdf, geornd, geofit # name: # type: sq_string # elements: 1 # length: 49 Compute statistics of the geometric distribution. # name: # type: sq_string # elements: 1 # length: 7 gevstat # name: # type: sq_string # elements: 1 # length: 902 statistics: [ m , v ] = gevstat ( k , sigma , mu ) Compute statistics of the generalized extreme value distribution. [ m , v ] = gevstat ( k , sigma , mu ) returns the mean and variance of the generalized extreme value distribution with shape parameter k , scale parameter sigma , and location parameter mu . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. The mean of the GEV distribution is not finite when k >= 1 , and the variance is not finite when k >= 1/2 . The GEV distribution has positive density only for values of x such that k * ( x - mu ) / sigma > -1 . Further information about the generalized extreme value distribution can be found at https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution See also: gevcdf, gevinv, gevpdf, gevrnd, gevfit, gevlike # name: # type: sq_string # elements: 1 # length: 65 Compute statistics of the generalized extreme value distribution. # name: # type: sq_string # elements: 1 # length: 6 gpstat # name: # type: sq_string # elements: 1 # length: 1156 statistics: [ m , v ] = gpstat ( k , sigma , theta ) Compute statistics of the generalized Pareto distribution. [ m , v ] = gpstat ( k , sigma , theta ) returns the mean and variance of the generalized Pareto distribution with shape parameter k , scale parameter sigma , and location parameter theta . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. When k = 0 and theta = 0, the generalized Pareto distribution is equivalent to the exponential distribution. When k > 0 and theta = sigma / k , the generalized Pareto distribution is equivalent to the Pareto distribution. The mean of the generalized Pareto distribution is not finite when k >= 1 , and the variance is not finite when k >= 1/2 . When k >= 0 , the generalized Pareto distribution has positive density for x > theta , or, when k < 0 , for 0 <= ( x - theta ) / sigma <= -1 / k . Further information about the generalized Pareto distribution can be found at https://en.wikipedia.org/wiki/Generalized_Pareto_distribution See also: gpcdf, gpinv, gppdf, gprnd, gpfit, gplike # name: # type: sq_string # elements: 1 # length: 58 Compute statistics of the generalized Pareto distribution. # name: # type: sq_string # elements: 1 # length: 6 hnstat # name: # type: sq_string # elements: 1 # length: 613 statistics: [ m , v ] = hnstat ( mu , sigma ) Compute statistics of the half-normal distribution. [ m , v ] = hnstat ( mu , sigma ) returns the mean and variance of the half-normal distribution with non-centrality (distance) parameter mu and scale parameter sigma . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the half-normal distribution can be found at https://en.wikipedia.org/wiki/Half-normal_distribution See also: hncdf, hninv, hnpdf, hnrnd, hnfit, hnlike # name: # type: sq_string # elements: 1 # length: 51 Compute statistics of the half-normal distribution. # name: # type: sq_string # elements: 1 # length: 8 hygestat # name: # type: sq_string # elements: 1 # length: 940 statistics: [ mn , v ] = hygestat ( m , k , n ) Compute statistics of the hypergeometric distribution. [ mn , v ] = hygestat ( m , k , n ) returns the mean and variance of the hypergeometric distribution parameters m , k , and n . m is the total size of the population of the hypergeometric distribution. The elements of m must be positive natural numbers. k is the number of marked items of the hypergeometric distribution. The elements of k must be natural numbers. n is the size of the drawn sample of the hypergeometric distribution. The elements of n must be positive natural numbers. The size of mn (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the hypergeometric distribution can be found at https://en.wikipedia.org/wiki/Hypergeometric_distribution See also: hygecdf, hygeinv, hygepdf, hygernd # name: # type: sq_string # elements: 1 # length: 54 Compute statistics of the hypergeometric distribution. # name: # type: sq_string # elements: 1 # length: 8 invgstat # name: # type: sq_string # elements: 1 # length: 631 statistics: [ m , v ] = invgstat ( mu , lambda ) Compute statistics of the inverse Gaussian distribution. [ m , v ] = invgstat ( mu , lambda ) returns the mean and variance of the inverse Gaussian distribution with mean parameter mu and shape parameter lambda . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the inverse Gaussian distribution can be found at https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution See also: invgcdf, invginv, invgpdf, invgrnd, invgfit, invglike # name: # type: sq_string # elements: 1 # length: 56 Compute statistics of the inverse Gaussian distribution. # name: # type: sq_string # elements: 1 # length: 8 logistat # name: # type: sq_string # elements: 1 # length: 596 statistics: [ m , v ] = logistat ( mu , sigma ) Compute statistics of the logistic distribution. [ m , v ] = logistat ( mu , sigma ) returns the mean and variance of the logistic distribution with mean parameter mu and scale parameter sigma . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the logistic distribution can be found at https://en.wikipedia.org/wiki/Logistic_distribution See also: logicdf, logiinv, logipdf, logirnd, logifit, logilike # name: # type: sq_string # elements: 1 # length: 48 Compute statistics of the logistic distribution. # name: # type: sq_string # elements: 1 # length: 8 loglstat # name: # type: sq_string # elements: 1 # length: 857 statistics: [ m , v ] = loglstat ( mu , sigma ) Compute statistics of the loglogistic distribution. [ m , v ] = loglstat ( mu , sigma ) returns the mean and variance of the loglogistic distribution with mean parameter mu and scale parameter sigma . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the loglogistic distribution can be found at https://en.wikipedia.org/wiki/Log-logistic_distribution OCTAVE/MATLAB use an alternative parameterization given by the pair μ, σ , i.e. mu and sigma , in analogy with the logistic distribution. Their relation to the α and b parameters used in Wikipedia are given below: mu = log ( a ) sigma = 1 / a See also: logncdf, logninv, lognpdf, lognrnd, lognfit, lognlike # name: # type: sq_string # elements: 1 # length: 51 Compute statistics of the loglogistic distribution. # name: # type: sq_string # elements: 1 # length: 8 lognstat # name: # type: sq_string # elements: 1 # length: 672 statistics: [ m , v ] = lognstat ( mu , sigma ) Compute statistics of the lognormal distribution. [ m , v ] = lognstat ( mu , sigma ) returns the mean and variance of the lognormal distribution with mean parameter mu and standard deviation parameter sigma , each corresponding to the associated normal distribution. The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the lognormal distribution can be found at https://en.wikipedia.org/wiki/Log-normal_distribution See also: logncdf, logninv, lognpdf, lognrnd, lognfit, lognlike # name: # type: sq_string # elements: 1 # length: 49 Compute statistics of the lognormal distribution. # name: # type: sq_string # elements: 1 # length: 8 nakastat # name: # type: sq_string # elements: 1 # length: 596 statistics: [ m , v ] = nakastat ( mu , omega ) Compute statistics of the Nakagami distribution. [ m , v ] = nakastat ( mu , omega ) returns the mean and variance of the Nakagami distribution with shape parameter mu and spread parameter omega . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Nakagami distribution can be found at https://en.wikipedia.org/wiki/Normal_distribution See also: nakacdf, nakainv, nakapdf, nakarnd, nakafit, nakalike # name: # type: sq_string # elements: 1 # length: 48 Compute statistics of the Nakagami distribution. # name: # type: sq_string # elements: 1 # length: 8 nbinstat # name: # type: sq_string # elements: 1 # length: 760 statistics: [ m , v ] = nbinstat ( r , ps ) Compute statistics of the negative binomial distribution. [ m , v ] = nbinstat ( r , ps ) returns the mean and variance of the negative binomial distribution with parameters r and ps , where r is the number of successes until the experiment is stopped and ps is the probability of success in each experiment, given the number of failures in x . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the negative binomial distribution can be found at https://en.wikipedia.org/wiki/Negative_binomial_distribution See also: nbincdf, nbininv, nbinpdf, nbinrnd, nbinfit, nbinlike # name: # type: sq_string # elements: 1 # length: 57 Compute statistics of the negative binomial distribution. # name: # type: sq_string # elements: 1 # length: 7 ncfstat # name: # type: sq_string # elements: 1 # length: 636 statistics: [ m , v ] = ncfstat ( df1 , df1 , lambda ) Compute statistics for the noncentral F -distribution. [ m , v ] = ncfstat ( df1 , df1 , lambda ) returns the mean and variance of the noncentral F -distribution with df1 and df2 degrees of freedom and noncentrality parameter lambda . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the noncentral F -distribution can be found at https://en.wikipedia.org/wiki/Noncentral_F-distribution See also: ncfcdf, ncfinv, ncfpdf, ncfrnd, fstat # name: # type: sq_string # elements: 1 # length: 53 Compute statistics for the noncentral F-distribution. # name: # type: sq_string # elements: 1 # length: 7 nctstat # name: # type: sq_string # elements: 1 # length: 601 statistics: [ m , v ] = nctstat ( df , mu ) Compute statistics for the noncentral t -distribution. [ m , v ] = nctstat ( df , mu ) returns the mean and variance of the noncentral t -distribution with df degrees of freedom and noncentrality parameter mu . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the noncentral t -distribution can be found at https://en.wikipedia.org/wiki/Noncentral_t-distribution See also: nctcdf, nctinv, nctpdf, nctrnd, tstat # name: # type: sq_string # elements: 1 # length: 53 Compute statistics for the noncentral t-distribution. # name: # type: sq_string # elements: 1 # length: 8 ncx2stat # name: # type: sq_string # elements: 1 # length: 649 statistics: [ m , v ] = ncx2stat ( df , lambda ) Compute statistics for the noncentral chi-squared distribution. [ m , v ] = ncx2stat ( df , lambda ) returns the mean and variance of the noncentral chi-squared distribution with df degrees of freedom and noncentrality parameter lambda . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the noncentral chi-squared distribution can be found at https://en.wikipedia.org/wiki/Noncentral_chi-squared_distribution See also: ncx2cdf, ncx2inv, ncx2pdf, ncx2rnd # name: # type: sq_string # elements: 1 # length: 63 Compute statistics for the noncentral chi-squared distribution. # name: # type: sq_string # elements: 1 # length: 8 normstat # name: # type: sq_string # elements: 1 # length: 609 statistics: [ m , v ] = normstat ( mu , sigma ) Compute statistics of the normal distribution. [ m , v ] = normstat ( mu , sigma ) returns the mean and variance of the normal distribution with non-centrality (distance) parameter mu and scale parameter sigma . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the normal distribution can be found at https://en.wikipedia.org/wiki/Normal_distribution See also: normcdf, norminv, normpdf, normrnd, normfit, normlike # name: # type: sq_string # elements: 1 # length: 46 Compute statistics of the normal distribution. # name: # type: sq_string # elements: 1 # length: 6 plstat # name: # type: sq_string # elements: 1 # length: 579 statistics: [ m , v ] = plstat ( x , Fx ) Compute statistics of the piecewise linear distribution. [ m , v ] = plstat ( x , Fx ) returns the mean, m , and variance, v , of the piecewise linear distribution with a vector of x values at which the CDF changes slope and a vector of CDF values Fx that correspond to each value in x . Both x and Fx must be vectors of the same size and at least 2-elements long. Further information about the piecewise linear distribution can be found at https://en.wikipedia.org/wiki/Piecewise_linear_function See also: plcdf, plinv, plpdf, plrnd # name: # type: sq_string # elements: 1 # length: 56 Compute statistics of the piecewise linear distribution. # name: # type: sq_string # elements: 1 # length: 8 poisstat # name: # type: sq_string # elements: 1 # length: 481 statistics: [ m , v ] = poisstat ( lambda ) Compute statistics of the Poisson distribution. [ m , v ] = poisstat ( lambda ) returns the mean and variance of the Poisson distribution with rate parameter lambda . The size of m (mean) and v (variance) is the same size of the input argument. Further information about the Poisson distribution can be found at https://en.wikipedia.org/wiki/Poisson_distribution See also: poisscdf, poissinv, poisspdf, poissrnd, poissfit, poisslike # name: # type: sq_string # elements: 1 # length: 47 Compute statistics of the Poisson distribution. # name: # type: sq_string # elements: 1 # length: 8 raylstat # name: # type: sq_string # elements: 1 # length: 558 statistics: [ m , v ] = raylstat ( sigma ) Compute statistics of the Rayleigh distribution. [ m , v ] = raylstat ( sigma ) returns the mean and variance of the Rayleigh distribution with scale parameter sigma . The size of m (mean) and v (variance) is the same size of the input argument. Further information about the Rayleigh distribution can be found at https://en.wikipedia.org/wiki/Rayleigh_distribution The prob.RayleighDistribution class names this same parameter B , after MATLAB. See also: raylcdf, raylinv, raylpdf, raylrnd, raylfit, rayllike # name: # type: sq_string # elements: 1 # length: 48 Compute statistics of the Rayleigh distribution. # name: # type: sq_string # elements: 1 # length: 8 ricestat # name: # type: sq_string # elements: 1 # length: 604 statistics: [ m , v ] = ricestat ( s , sigma ) Compute statistics of the Rician distribution. [ m , v ] = ricestat ( s , sigma ) returns the mean and variance of the Rician distribution with non-centrality (distance) parameter s and scale parameter sigma . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Rician distribution can be found at https://en.wikipedia.org/wiki/Rice_distribution See also: ricecdf, riceinv, ricepdf, ricernd, ricefit, ricelike # name: # type: sq_string # elements: 1 # length: 46 Compute statistics of the Rician distribution. # name: # type: sq_string # elements: 1 # length: 7 tlsstat # name: # type: sq_string # elements: 1 # length: 734 statistics: [ m , v ] = tlsstat ( mu , sigma , nu ) Compute statistics of the location-scale Student’s T distribution. [ m , v ] = tlsstat ( mu , sigma , nu ) returns the mean and variance of the location-scale Student’s T distribution with location parameter mu , scale parameter sigma , and nu degrees of freedom. The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the location-scale Student’s T distribution can be found at https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution See also: tlscdf, tlsinv, tlspdf, tlsrnd, tlsfit, tlslike # name: # type: sq_string # elements: 1 # length: 66 Compute statistics of the location-scale Student's T distribution. # name: # type: sq_string # elements: 1 # length: 7 tristat # name: # type: sq_string # elements: 1 # length: 1077 statistics: [ m , v ] = tristat ( a , b , c ) Compute statistics of the Triangular distribution. [ m , v ] = tristat ( a , b , c ) returns the mean and variance of the Triangular distribution with lower limit parameter a , peak location (mode) parameter b , and upper limit parameter c . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Note that the order of the parameter input arguments has been changed after statistics version 1.6.3 in order to be MATLAB compatible with the parameters used in the TriangularDistribution probability distribution object. More specifically, the positions of the parameters b and c have been swapped. As a result, the naming conventions no longer coincide with those used in Wikipedia, in which b denotes the upper limit and c denotes the mode or peak parameter. Further information about the triangular distribution can be found at https://en.wikipedia.org/wiki/Triangular_distribution See also: tcdf, tinv, tpdf, trnd # name: # type: sq_string # elements: 1 # length: 50 Compute statistics of the Triangular distribution. # name: # type: sq_string # elements: 1 # length: 5 tstat # name: # type: sq_string # elements: 1 # length: 465 statistics: [ m , v ] = tstat ( df ) Compute statistics of the Student’s T distribution. [ m , v ] = tstat ( df ) returns the mean and variance of the Student’s T distribution with df degrees of freedom. The size of m (mean) and v (variance) is the same size of the input argument. Further information about the Student’s T distribution can be found at https://en.wikipedia.org/wiki/Student%27s_t-distribution See also: tcdf, tinv, tpdf, trnd # name: # type: sq_string # elements: 1 # length: 51 Compute statistics of the Student's T distribution. # name: # type: sq_string # elements: 1 # length: 8 unidstat # name: # type: sq_string # elements: 1 # length: 594 statistics: [ m , v ] = unidstat ( df ) Compute statistics of the discrete uniform cumulative distribution. [ m , v ] = unidstat ( df ) returns the mean and variance of the discrete uniform cumulative distribution with parameter N , which corresponds to the maximum observable value and must be a positive natural number. The size of m (mean) and v (variance) is the same size of the input argument. Further information about the discrete uniform distribution can be found at https://en.wikipedia.org/wiki/Discrete_uniform_distribution See also: unidcdf, unidinv, unidpdf, unidrnd, unidfit # name: # type: sq_string # elements: 1 # length: 67 Compute statistics of the discrete uniform cumulative distribution. # name: # type: sq_string # elements: 1 # length: 8 unifstat # name: # type: sq_string # elements: 1 # length: 674 statistics: [ m , v ] = unifstat ( df ) Compute statistics of the continuous uniform cumulative distribution. [ m , v ] = unifstat ( df ) returns the mean and variance of the continuous uniform cumulative distribution with parameters a and b , which define the lower and upper bounds of the interval [ a , b ] . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the continuous uniform distribution can be found at https://en.wikipedia.org/wiki/Continuous_uniform_distribution See also: unifcdf, unifinv, unifpdf, unifrnd, unifit # name: # type: sq_string # elements: 1 # length: 69 Compute statistics of the continuous uniform cumulative distribution. # name: # type: sq_string # elements: 1 # length: 7 wblstat # name: # type: sq_string # elements: 1 # length: 719 statistics: [ m , v ] = wblstat ( lambda , k ) Compute statistics of the Weibull distribution. [ m , v ] = wblstat ( lambda , k ) returns the mean and variance of the Weibull distribution with scale parameter lambda and shape parameter k . The size of m (mean) and v (variance) is the common size of the input arguments. A scalar input functions as a constant matrix of the same size as the other inputs. Further information about the Weibull distribution can be found at https://en.wikipedia.org/wiki/Weibull_distribution The prob.WeibullDistribution class names these same two parameters A and B , after MATLAB. lambda is its A and k is its B . See also: wblcdf, wblinv, wblpdf, wblrnd, wblfit, wbllike, wblplot # name: # type: sq_string # elements: 1 # length: 47 Compute statistics of the Weibull distribution. statistics-release-1.9.2/inst/Distribution_Statistics/evstat.m000066400000000000000000000074071524624707500247110ustar00rootroot00000000000000## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} evstat (@var{mu}, @var{sigma}) ## ## Compute statistics of the extreme value distribution. ## ## @code{[@var{m}, @var{v}] = evstat (@var{mu}, @var{sigma})} returns the mean ## and variance of the extreme value distribution (also known as the Gumbel ## or the type I generalized extreme value distribution) with location parameter ## @var{mu} and scale parameter @var{sigma}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## The type 1 extreme value distribution is also known as the Gumbel ## distribution. This version is suitable for modeling minima. The mirror image ## of this distribution can be used to model maxima by negating @var{x}. If ## @var{y} has a Weibull distribution, then @code{@var{x} = log (@var{y})} has ## the type 1 extreme value distribution. ## ## Further information about the Gumbel distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gumbel_distribution} ## ## @seealso{evcdf, evinv, evpdf, evrnd, evfit, evlike} ## @end deftypefn function [m, v] = evstat (mu, sigma) ## Check for valid number of input arguments if (nargin < 2) error ("evstat: function called with too few input arguments."); endif ## Check for MU and SIGMA being numeric if (! (isnumeric (mu) && isnumeric (sigma))) error ("evstat: MU and SIGMA must be numeric."); endif ## Check for MU and SIGMA being real if (iscomplex (mu) || iscomplex (sigma)) error ("evstat: MU and SIGMA must not be complex."); endif ## Check for common size of MU and SIGMA if (! isscalar (mu) || ! isscalar (sigma)) [retval, mu, sigma] = common_size (mu, sigma); if (retval > 0) error ("evstat: MU and SIGMA must be of common size or scalars."); endif endif ## Return NaNs for out of range values of SIGMA sigma(sigma <= 0) = NaN; ## Calculate mean and variance m = mu + psi (1) .* sigma; v = (pi .* sigma) .^ 2 ./ 6; endfunction ## Input validation tests %!error evstat () %!error evstat (1) %!error evstat ({}, 2) %!error evstat (1, '') %!error evstat (i, 2) %!error evstat (1, i) %!error ... %! evstat (ones (3), ones (2)) %!error ... %! evstat (ones (2), ones (3)) ## Output validation tests %!shared x, y0, y1 %! x = [-5, 0, 1, 2, 3]; %! y0 = [NaN, NaN, 0.4228, 0.8456, 1.2684]; %! y1 = [-5.5772, -3.4633, -3.0405, -2.6177, -2.1949]; %!assert_equal (evstat (x, x), y0, 1e-4) %!assert_equal (evstat (x, x+6), y1, 1e-4) %!assert_equal (evstat (x, x-6), NaN (1,5)) statistics-release-1.9.2/inst/Distribution_Statistics/expstat.m000066400000000000000000000053531524624707500250710ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} expstat (@var{mu}) ## ## Compute statistics of the exponential distribution. ## ## @code{[@var{m}, @var{v}] = expstat (@var{mu})} returns the mean and ## variance of the exponential distribution with mean parameter @var{mu}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the same size of the ## input argument. ## ## A common alternative parameterization of the exponential distribution is to ## use the parameter @math{λ} defined as the mean number of events in an ## interval as opposed to the parameter @math{μ}, which is the mean wait time ## for an event to occur. @math{λ} and @math{μ} are reciprocals, ## i.e. @math{μ = 1 / λ}. ## ## Further information about the exponential distribution can be found at ## @url{https://en.wikipedia.org/wiki/Exponential_distribution} ## ## @seealso{expcdf, expinv, exppdf, exprnd, expfit, explike} ## @end deftypefn function [m, v] = expstat (mu) ## Check for valid number of input arguments if (nargin < 1) error ("expstat: function called with too few input arguments."); endif ## Check for MU being numeric if (! isnumeric (mu)) error ("expstat: MU must be numeric."); endif ## Check for MU being real if (iscomplex (mu)) error ("expstat: MU must not be complex."); endif ## Calculate moments m = mu; v = m .^ 2; ## Continue argument check k = find (! (mu > 0) | ! (mu < Inf)); if (any (k)) m(k) = NaN; v(k) = NaN; endif endfunction ## Input validation tests %!error expstat () %!error expstat ({}) %!error expstat ('') %!error expstat (i) ## Output validation tests %!test %! mu = 1:6; %! [m, v] = expstat (mu); %! assert_equal (m, [1, 2, 3, 4, 5, 6], 0.001); %! assert_equal (v, [1, 4, 9, 16, 25, 36], 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/fstat.m000066400000000000000000000073531524624707500245240ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} fstat (@var{df1}, @var{df2}) ## ## Compute statistics of the @math{F}-distribution. ## ## @code{[@var{m}, @var{v}] = fstat (@var{df1}, @var{df2})} returns the mean and ## variance of the @math{F}-distribution with @var{df1} and @var{df2} degrees ## of freedom. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the @math{F}-distribution can be found at ## @url{https://en.wikipedia.org/wiki/F-distribution} ## ## @seealso{fcdf, finv, fpdf, frnd} ## @end deftypefn function [m, v] = fstat (df1, df2) ## Check for valid number of input arguments if (nargin < 2) error ("fstat: function called with too few input arguments."); endif ## Check for DF1 and DF2 being numeric if (! (isnumeric (df1) && isnumeric (df2))) error ("fstat: DF1 and DF2 must be numeric."); endif ## Check for DF1 and DF2 being real if (iscomplex (df1) || iscomplex (df2)) error ("fstat: DF1 and DF2 must not be complex."); endif ## Check for common size of DF1 and DF2 if (! isscalar (df1) || ! isscalar (df2)) [retval, df1, df2] = common_size (df1, df2); if (retval > 0) error ("fstat: DF1 and DF2 must be of common size or scalars."); endif endif ## Calculate moments m = df2 ./ (df2 - 2); v = (2 .* (df2 .^ 2) .* (df1 + df2 - 2)) ./ ... (df1 .* ((df2 - 2) .^ 2) .* (df2 - 4)); ## Continue argument check k = find (! (df1 > 0) | ! (df1 < Inf) | ! (df2 > 2) | ! (df2 < Inf)); if (any (k)) m(k) = NaN; v(k) = NaN; endif k = find (! (df2 > 4)); if (any (k)) v(k) = NaN; endif endfunction ## Input validation tests %!error fstat () %!error fstat (1) %!error fstat ({}, 2) %!error fstat (1, '') %!error fstat (i, 2) %!error fstat (1, i) %!error ... %! fstat (ones (3), ones (2)) %!error ... %! fstat (ones (2), ones (3)) ## Output validation tests %!test %! df1 = 1:6; %! df2 = 5:10; %! [m, v] = fstat (df1, df2); %! expected_mn = [1.6667, 1.5000, 1.4000, 1.3333, 1.2857, 1.2500]; %! expected_v = [22.2222, 6.7500, 3.4844, 2.2222, 1.5869, 1.2153]; %! assert_equal (m, expected_mn, 0.001); %! assert_equal (v, expected_v, 0.001); %!test %! df1 = 1:6; %! [m, v] = fstat (df1, 5); %! expected_mn = [1.6667, 1.6667, 1.6667, 1.6667, 1.6667, 1.6667]; %! expected_v = [22.2222, 13.8889, 11.1111, 9.7222, 8.8889, 8.3333]; %! assert_equal (m, expected_mn, 0.001); %! assert_equal (v, expected_v, 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/gamstat.m000066400000000000000000000077601524624707500250450ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} gamstat (@var{a}, @var{b}) ## ## Compute statistics of the Gamma distribution. ## ## @code{[@var{m}, @var{v}] = gamstat (@var{a}, @var{b})} returns the mean ## and variance of the Gamma distribution with shape parameter @var{a} and ## scale parameter @var{b}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## OCTAVE/MATLAB use the alternative parameterization given by the pair ## @math{α, β}, i.e. shape @var{a} and scale @var{b}. In Wikipedia, the two ## common parameterizations use the pairs @math{k, θ}, as shape and scale, and ## @math{α, β}, as shape and rate, respectively. The parameter names @var{a} ## and @var{b} used here (for MATLAB compatibility) correspond to the parameter ## notation @math{k, θ} instead of the @math{α, β} as reported in Wikipedia. ## ## Further information about the Gamma distribution can be found at ## @url{https://en.wikipedia.org/wiki/Gamma_distribution} ## ## @seealso{gamcdf, gaminv, gampdf, gamrnd, gamfit, gamlike} ## @end deftypefn function [m, v] = gamstat (a, b) ## Check for valid number of input arguments if (nargin < 2) error ("gamstat: function called with too few input arguments."); endif ## Check for A and B being numeric if (! (isnumeric (a) && isnumeric (b))) error ("gamstat: A and B must be numeric."); endif ## Check for A and B being real if (iscomplex (a) || iscomplex (b)) error ("gamstat: A and B must not be complex."); endif ## Check for common size of A and B if (! isscalar (a) || ! isscalar (b)) [retval, a, b] = common_size (a, b); if (retval > 0) error ("gamstat: A and B must be of common size or scalars."); endif endif ## Calculate moments m = a .* b; v = a .* (b .^ 2); ## Continue argument check a = find (! (a > 0) | ! (a < Inf) | ! (b > 0) | ! (b < Inf)); if (any (a)) m(a) = NaN; v(a) = NaN; endif endfunction ## Input validation tests %!error gamstat () %!error gamstat (1) %!error gamstat ({}, 2) %!error gamstat (1, '') %!error gamstat (i, 2) %!error gamstat (1, i) %!error ... %! gamstat (ones (3), ones (2)) %!error ... %! gamstat (ones (2), ones (3)) ## Output validation tests %!test %! a = 1:6; %! b = 1:0.2:2; %! [m, v] = gamstat (a, b); %! expected_m = [1.00, 2.40, 4.20, 6.40, 9.00, 12.00]; %! expected_v = [1.00, 2.88, 5.88, 10.24, 16.20, 24.00]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); %!test %! a = 1:6; %! [m, v] = gamstat (a, 1.5); %! expected_m = [1.50, 3.00, 4.50, 6.00, 7.50, 9.00]; %! expected_v = [2.25, 4.50, 6.75, 9.00, 11.25, 13.50]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/geostat.m000066400000000000000000000047071524624707500250510ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} geostat (@var{ps}) ## ## Compute statistics of the geometric distribution. ## ## @code{[@var{m}, @var{v}] = geostat (@var{ps})} returns the mean and ## variance of the geometric distribution with probability of success parameter ## @var{ps}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the same size of the ## input argument. ## ## Further information about the geometric distribution can be found at ## @url{https://en.wikipedia.org/wiki/Geometric_distribution} ## ## @seealso{geocdf, geoinv, geopdf, geornd, geofit} ## @end deftypefn function [m, v] = geostat (ps) ## Check for valid number of input arguments if (nargin < 1) error ("geostat: function called with too few input arguments."); endif ## Check for PS being numeric if (! isnumeric (ps)) error ("geostat: PS must be numeric."); endif ## Check for PS being real if (iscomplex (ps)) error ("geostat: PS must not be complex."); endif ## Calculate moments q = 1 - ps; m = q ./ ps; v = q ./ (ps .^ 2); ## Continue argument check k = find (! (ps >= 0) | ! (ps <= 1)); if (any (k)) m(k) = NaN; v(k) = NaN; endif endfunction ## Input validation tests %!error geostat () %!error geostat ({}) %!error geostat ('') %!error geostat (i) ## Output validation tests %!test %! ps = 1 ./ (1:6); %! [m, v] = geostat (ps); %! assert_equal (m, [0, 1, 2, 3, 4, 5], 0.001); %! assert_equal (v, [0, 2, 6, 12, 20, 30], 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/gevstat.m000066400000000000000000000113301524624707500250460ustar00rootroot00000000000000## Copyright (C) 2012 Nir Krakauer ## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} gevstat (@var{k}, @var{sigma}, @var{mu}) ## ## Compute statistics of the generalized extreme value distribution. ## ## @code{[@var{m}, @var{v}] = gevstat (@var{k}, @var{sigma}, @var{mu})} returns ## the mean and variance of the generalized extreme value distribution with ## shape parameter @var{k}, scale parameter @var{sigma}, and location parameter ## @var{mu}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## The mean of the GEV distribution is not finite when @qcode{@var{k} >= 1}, and ## the variance is not finite when @qcode{@var{k} >= 1/2}. The GEV distribution ## has positive density only for values of @var{x} such that ## @qcode{@var{k} * (@var{x} - @var{mu}) / @var{sigma} > -1}. ## ## Further information about the generalized extreme value distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution} ## ## @seealso{gevcdf, gevinv, gevpdf, gevrnd, gevfit, gevlike} ## @end deftypefn function [m, v] = gevstat (k, sigma, mu) ## Check for valid number of input arguments if (nargin < 3) error ("gevstat: function called with too few input arguments."); endif ## Check for K, SIGMA, and MU being numeric if (! (isnumeric (k) && isnumeric (sigma) && isnumeric (mu))) error ("gevstat: K, SIGMA, and MU must be numeric."); endif ## Check for K, SIGMA, and MU being real if (iscomplex (k) || iscomplex (sigma) || iscomplex (mu)) error ("gevstat: K, SIGMA, and MU must not be complex."); endif ## Check for common size of K, SIGMA, and MU if (! isscalar (k) || ! isscalar (sigma) || ! isscalar (mu)) [retval, k, sigma, mu] = common_size (k, sigma, mu); if (retval > 0) error ("gevstat: K, SIGMA, and MU must be of common size or scalars."); endif endif ## Euler-Mascheroni constant eg = 0.57721566490153286; m = v = k; ## Find the mean m(k >= 1) = Inf; m(k == 0) = mu(k == 0) + eg*sigma(k == 0); m(k < 1 & k != 0) = mu(k < 1 & k != 0) + sigma(k < 1 & k != 0) .* ... (gamma (1-k(k < 1 & k != 0)) - 1) ./ k(k < 1 & k != 0); ## Find the variance v(k >= 0.5) = Inf; v(k == 0) = (pi^2 / 6) * sigma(k == 0) .^ 2; v(k < 0.5 & k != 0) = (gamma (1-2*k(k < 0.5 & k != 0)) - ... gamma (1-k(k < 0.5 & k != 0)).^2) .* ... (sigma(k < 0.5 & k != 0) ./ k(k < 0.5 & k != 0)) .^ 2; endfunction ## Input validation tests %!error gevstat () %!error gevstat (1) %!error gevstat (1, 2) %!error gevstat ({}, 2, 3) %!error gevstat (1, '', 3) %!error gevstat (1, 2, '') %!error gevstat (i, 2, 3) %!error gevstat (1, i, 3) %!error gevstat (1, 2, i) %!error ... %! gevstat (ones (3), ones (2), 3) %!error ... %! gevstat (ones (2), 2, ones (3)) %!error ... %! gevstat (1, ones (2), ones (3)) ## Output validation tests %!test %! k = [-1, -0.5, 0, 0.2, 0.4, 0.5, 1]; %! sigma = 2; %! mu = 1; %! [m, v] = gevstat (k, sigma, mu); %! expected_m = [1, 1.4551, 2.1544, 2.6423, 3.4460, 4.0898, Inf]; %! expected_v = [4, 3.4336, 6.5797, 13.3761, 59.3288, Inf, Inf]; %! assert_equal (m, expected_m, -0.001); %! assert_equal (v, expected_v, -0.001); statistics-release-1.9.2/inst/Distribution_Statistics/gpstat.m000066400000000000000000000131351524624707500247000ustar00rootroot00000000000000## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} gpstat (@var{k}, @var{sigma}, @var{theta}) ## ## Compute statistics of the generalized Pareto distribution. ## ## @code{[@var{m}, @var{v}] = gpstat (@var{k}, @var{sigma}, @var{theta})} ## returns the mean and variance of the generalized Pareto distribution with ## shape parameter @var{k}, scale parameter @var{sigma}, and location parameter ## @var{theta}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## When @var{k} = 0 and @var{theta} = 0, the generalized Pareto distribution is ## equivalent to the exponential distribution. When @code{@var{k} > 0} and ## @code{@var{theta} = @var{sigma} / @var{k}}, the generalized Pareto ## distribution is equivalent to the Pareto distribution. The mean of the ## generalized Pareto distribution is not finite when @code{@var{k} >= 1}, and ## the variance is not finite when @code{@var{k} >= 1/2}. When ## @code{@var{k} >= 0}, the generalized Pareto distribution has positive density ## for @code{@var{x} > @var{theta}}, or, when @code{@var{k} < 0}, for ## @code{0 <= (@var{x} - @var{theta}) / @var{sigma} <= -1 / @var{k}}. ## ## Further information about the generalized Pareto distribution can be found at ## @url{https://en.wikipedia.org/wiki/Generalized_Pareto_distribution} ## ## @seealso{gpcdf, gpinv, gppdf, gprnd, gpfit, gplike} ## @end deftypefn function [m, v] = gpstat (k, sigma, theta) ## Check for valid number of input arguments if (nargin < 3) error ("gpstat: function called with too few input arguments."); endif ## Check for K, SIGMA, and MU being numeric if (! (isnumeric (k) && isnumeric (sigma) && isnumeric (theta))) error ("gpstat: K, SIGMA, and MU must be numeric."); endif ## Check for K, SIGMA, and MU being real if (iscomplex (k) || iscomplex (sigma) || iscomplex (theta)) error ("gpstat: K, SIGMA, and MU must not be complex."); endif ## Check for common size of K, SIGMA, and MU if (! isscalar (k) || ! isscalar (sigma) || ! isscalar (theta)) [retval, k, sigma, theta] = common_size (k, sigma, theta); if (retval > 0) error ("gpstat: K, SIGMA, and MU must be of common size or scalars."); endif endif ## Return NaNs for out of range SCALE parameters. sigma(sigma <= 0) = NaN; ## Check for appropriate class if (isa (k, 'single') || isa (sigma, 'single') || isa (theta, 'single')); is_class = 'single'; else is_class = 'double'; endif ## Prepare output m = NaN (size (k), is_class); v = NaN (size (k), is_class); ## Compute cases for SHAPE == 0 knot0 = (abs (k) < eps (is_class)); m(knot0) = 1; v(knot0) = 1; ## Compute cases for SHAPE != 0 knot0 = ! knot0; ## SHAPE < 1 kless = knot0 & (k < 1); m(kless) = 1 ./ (1 - k(kless)); ## SHAPE > 1 m(k >= 1) = Inf; ## SHAPE < 1/2 ## Find the k~=0 cases and fill in the variance. kless = knot0 & (k < 1/2); v(kless) = 1 ./ ((1-k(kless)).^2 .* (1-2.*k(kless))); ## SHAPE > 1/2 v(k >= 1/2) = Inf; ## Compute mean and variance m = theta + sigma .* m; v = sigma .^ 2 .* v; endfunction ## Input validation tests %!error gpstat () %!error gpstat (1) %!error gpstat (1, 2) %!error gpstat ({}, 2, 3) %!error gpstat (1, '', 3) %!error gpstat (1, 2, '') %!error gpstat (i, 2, 3) %!error gpstat (1, i, 3) %!error gpstat (1, 2, i) %!error ... %! gpstat (ones (3), ones (2), 3) %!error ... %! gpstat (ones (2), 2, ones (3)) %!error ... %! gpstat (1, ones (2), ones (3)) ## Output validation tests %!shared x, y %! x = [-Inf, -1, 0, 1/2, 1, Inf]; %! y = [0, 0.5, 1, 2, Inf, Inf]; %!assert_equal (gpstat (x, ones (1,6), zeros (1,6)), y, eps) ## Test class of input preserved %!assert_equal (gpstat (single (x), 1, 0), single (y), eps ('single')) %!assert_equal (gpstat (x, single (1), 0), single (y), eps ('single')) %!assert_equal (gpstat (x, 1, single (0)), single (y), eps ('single')) %!assert_equal (gpstat (single ([x, NaN]), 1, 0), single ([y, NaN]), eps ('single')) %!assert_equal (gpstat ([x, NaN], single (1), 0), single ([y, NaN]), eps ('single')) %!assert_equal (gpstat ([x, NaN], 1, single (0)), single ([y, NaN]), eps ('single')) statistics-release-1.9.2/inst/Distribution_Statistics/hnstat.m000066400000000000000000000073671524624707500247110ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} hnstat (@var{mu}, @var{sigma}) ## ## Compute statistics of the half-normal distribution. ## ## @code{[@var{m}, @var{v}] = hnstat (@var{mu}, @var{sigma})} returns the mean ## and variance of the half-normal distribution with non-centrality (distance) ## parameter @var{mu} and scale parameter @var{sigma}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the half-normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Half-normal_distribution} ## ## @seealso{hncdf, hninv, hnpdf, hnrnd, hnfit, hnlike} ## @end deftypefn function [m, v] = hnstat (mu, sigma) ## Check for valid number of input arguments if (nargin < 2) error ("hnstat: function called with too few input arguments."); endif ## Check for MU and SIGMA being numeric if (! (isnumeric (mu) && isnumeric (sigma))) error ("hnstat: MU and SIGMA must be numeric."); endif ## Check for MU and SIGMA being real if (iscomplex (mu) || iscomplex (sigma)) error ("hnstat: MU and SIGMA must not be complex."); endif ## Check for common size of MU and SIGMA if (! isscalar (mu) || ! isscalar (sigma)) [retval, mu, sigma] = common_size (mu, sigma); if (retval > 0) error ("hnstat: MU and SIGMA must be of common size or scalars."); endif endif ## Calculate moments m = mu + (sigma .* sqrt (2)) ./ sqrt (pi); v = sigma .^ 2 .* (1 - 2 / pi); ## Continue argument check k = find (! (sigma > 0) | ! (sigma < Inf)); if (any (k)) m(k) = NaN; v(k) = NaN; endif endfunction ## Input validation tests %!error hnstat () %!error hnstat (1) %!error hnstat ({}, 2) %!error hnstat (1, '') %!error hnstat (i, 2) %!error hnstat (1, i) %!error ... %! hnstat (ones (3), ones (2)) %!error ... %! hnstat (ones (2), ones (3)) ## Output validation tests %!test %! [m, v] = hnstat (0, 1); %! assert_equal (m, 0.7979, 1e-4); %! assert_equal (v, 0.3634, 1e-4); %!test %! [m, v] = hnstat (2, 1); %! assert_equal (m, 2.7979, 1e-4); %! assert_equal (v, 0.3634, 1e-4); %!test %! [m, v] = hnstat (2, 2); %! assert_equal (m, 3.5958, 1e-4); %! assert_equal (v, 1.4535, 1e-4); %!test %! [m, v] = hnstat (2, 2.5); %! assert_equal (m, 3.9947, 1e-4); %! assert_equal (v, 2.2711, 1e-4); %!test %! [m, v] = hnstat (1.5, 0.5); %! assert_equal (m, 1.8989, 1e-4); %! assert_equal (v, 0.0908, 1e-4); %!test %! [m, v] = hnstat (-1.5, 0.5); %! assert_equal (m, -1.1011, 1e-4); %! assert_equal (v, 0.0908, 1e-4); statistics-release-1.9.2/inst/Distribution_Statistics/hygestat.m000066400000000000000000000113561524624707500252310ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{mn}, @var{v}] =} hygestat (@var{m}, @var{k}, @var{n}) ## ## Compute statistics of the hypergeometric distribution. ## ## @code{[@var{mn}, @var{v}] = hygestat (@var{m}, @var{k}, @var{n})} returns the ## mean and variance of the hypergeometric distribution parameters @var{m}, ## @var{k}, and @var{n}. ## ## @itemize ## @item ## @var{m} is the total size of the population of the hypergeometric ## distribution. The elements of @var{m} must be positive natural numbers. ## ## @item ## @var{k} is the number of marked items of the hypergeometric distribution. ## The elements of @var{k} must be natural numbers. ## ## @item ## @var{n} is the size of the drawn sample of the hypergeometric ## distribution. The elements of @var{n} must be positive natural numbers. ## @end itemize ## ## The size of @var{mn} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the hypergeometric distribution can be found at ## @url{https://en.wikipedia.org/wiki/Hypergeometric_distribution} ## ## @seealso{hygecdf, hygeinv, hygepdf, hygernd} ## @end deftypefn function [mn, v] = hygestat (m, k, n) ## Check for valid number of input arguments if (nargin < 3) error ("hygestat: function called with too few input arguments."); endif ## Check for M, K, and N being numeric if (! (isnumeric (m) && isnumeric (k) && isnumeric (n))) error ("hygestat: M, K, and N must be numeric."); endif ## Check for M, K, and N being real if (iscomplex (m) || iscomplex (k) || iscomplex (n)) error ("hygestat: M, K, and N must not be complex."); endif ## Check for common size of M, K, and N if (! isscalar (m) || ! isscalar (k) || ! isscalar (n)) [retval, m, k, n] = common_size (m, k, n); if (retval > 0) error ("hygestat: M, K, and N must be of common size or scalars."); endif endif ## Calculate moments mn = (n .* k) ./ m; v = (n .* (k ./ m) .* (1 - k ./ m) .* (m - n)) ./ (m - 1); ## Continue argument check is_nan = find (! (m >= 0) | ! (k >= 0) | ! (n > 0) | ! (m == round (m)) | ... ! (k == round (k)) | ! (n == round (n)) | ! (k <= m) | ... ! (n <= m)); if (any (is_nan)) mn(is_nan) = NaN; v(is_nan) = NaN; endif endfunction ## Input validation tests %!error hygestat () %!error hygestat (1) %!error hygestat (1, 2) %!error hygestat ({}, 2, 3) %!error hygestat (1, '', 3) %!error hygestat (1, 2, '') %!error hygestat (i, 2, 3) %!error hygestat (1, i, 3) %!error hygestat (1, 2, i) %!error ... %! hygestat (ones (3), ones (2), 3) %!error ... %! hygestat (ones (2), 2, ones (3)) %!error ... %! hygestat (1, ones (2), ones (3)) ## Output validation tests %!test %! m = 4:9; %! k = 0:5; %! n = 1:6; %! [mn, v] = hygestat (m, k, n); %! expected_mn = [0.0000, 0.4000, 1.0000, 1.7143, 2.5000, 3.3333]; %! expected_v = [0.0000, 0.2400, 0.4000, 0.4898, 0.5357, 0.5556]; %! assert_equal (mn, expected_mn, 0.001); %! assert_equal (v, expected_v, 0.001); %!test %! m = 4:9; %! k = 0:5; %! [mn, v] = hygestat (m, k, 2); %! expected_mn = [0.0000, 0.4000, 0.6667, 0.8571, 1.0000, 1.1111]; %! expected_v = [0.0000, 0.2400, 0.3556, 0.4082, 0.4286, 0.4321]; %! assert_equal (mn, expected_mn, 0.001); %! assert_equal (v, expected_v, 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/invgstat.m000066400000000000000000000070571524624707500252430ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} invgstat (@var{mu}, @var{lambda}) ## ## Compute statistics of the inverse Gaussian distribution. ## ## @code{[@var{m}, @var{v}] = invgstat (@var{mu}, @var{lambda})} returns the ## mean and variance of the inverse Gaussian distribution with mean parameter ## @var{mu} and shape parameter @var{lambda}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the inverse Gaussian distribution can be found at ## @url{https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution} ## ## @seealso{invgcdf, invginv, invgpdf, invgrnd, invgfit, invglike} ## @end deftypefn function [m, v] = invgstat (mu, lambda) ## Check for valid number of input arguments if (nargin < 2) error ("invgstat: function called with too few input arguments."); endif ## Check for MU and LAMBDA being numeric if (! (isnumeric (mu) && isnumeric (lambda))) error ("invgstat: MU and LAMBDA must be numeric."); endif ## Check for MU and LAMBDA being real if (iscomplex (mu) || iscomplex (lambda)) error ("invgstat: MU and LAMBDA must not be complex."); endif ## Check for common size of MU and LAMBDA if (! isscalar (mu) || ! isscalar (lambda)) [retval, mu, lambda] = common_size (mu, lambda); if (retval > 0) error ("invgstat: MU and LAMBDA must be of common size or scalars."); endif endif ## Calculate moments m = mu; v = (mu .^ 3) ./ lambda; ## Continue argument check m(lambda <= 0 | mu <= 0) = NaN; v(lambda <= 0 | mu <= 0) = NaN; endfunction ## Input validation tests %!error invgstat () %!error invgstat (1) %!error invgstat ({}, 2) %!error invgstat (1, '') %!error invgstat (i, 2) %!error invgstat (1, i) %!error ... %! invgstat (ones (3), ones (2)) %!error ... %! invgstat (ones (2), ones (3)) ## Output validation tests %!test %! [m, v] = invgstat (1, 1); %! assert_equal (m, 1); %! assert_equal (v, 1); %!test %! [m, v] = invgstat (2, 1); %! assert_equal (m, 2); %! assert_equal (v, 8); %!test %! [m, v] = invgstat (2, 2); %! assert_equal (m, 2); %! assert_equal (v, 4); %!test %! [m, v] = invgstat (2, 2.5); %! assert_equal (m, 2); %! assert_equal (v, 3.2); %!test %! [m, v] = invgstat (1.5, 0.5); %! assert_equal (m, 1.5); %! assert_equal (v, 6.75); statistics-release-1.9.2/inst/Distribution_Statistics/logistat.m000066400000000000000000000070461524624707500252300ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} logistat (@var{mu}, @var{sigma}) ## ## Compute statistics of the logistic distribution. ## ## @code{[@var{m}, @var{v}] = logistat (@var{mu}, @var{sigma})} returns the mean ## and variance of the logistic distribution with mean parameter @var{mu} and ## scale parameter @var{sigma}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the logistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Logistic_distribution} ## ## @seealso{logicdf, logiinv, logipdf, logirnd, logifit, logilike} ## @end deftypefn function [m, v] = logistat (mu, sigma) ## Check for valid number of input arguments if (nargin < 2) error ("logistat: function called with too few input arguments."); endif ## Check for MU and SIGMA being numeric if (! (isnumeric (mu) && isnumeric (sigma))) error ("logistat: MU and SIGMA must be numeric."); endif ## Check for MU and SIGMA being real if (iscomplex (mu) || iscomplex (sigma)) error ("logistat: MU and SIGMA must not be complex."); endif ## Check for common size of MU and SIGMA if (! isscalar (mu) || ! isscalar (sigma)) [retval, mu, sigma] = common_size (mu, sigma); if (retval > 0) error ("logistat: MU and SIGMA must be of common size or scalars."); endif endif ## Calculate moments m = mu; v = (sigma .^ 2 .* pi .^ 2) ./ 3; ## Continue argument check m(sigma <= 0) = NaN; v(sigma <= 0) = NaN; endfunction ## Input validation tests %!error logistat () %!error logistat (1) %!error logistat ({}, 2) %!error logistat (1, '') %!error logistat (i, 2) %!error logistat (1, i) %!error ... %! logistat (ones (3), ones (2)) %!error ... %! logistat (ones (2), ones (3)) ## Output validation tests %!test %! [m, v] = logistat (0, 1); %! assert_equal (m, 0); %! assert_equal (v, 3.2899, 0.001); %!test %! [m, v] = logistat (0, 0.8); %! assert_equal (m, 0); %! assert_equal (v, 2.1055, 0.001); %!test %! [m, v] = logistat (1, 0.6); %! assert_equal (m, 1); %! assert_equal (v, 1.1844, 0.001); %!test %! [m, v] = logistat (0, 0.4); %! assert_equal (m, 0); %! assert_equal (v, 0.5264, 0.001); %!test %! [m, v] = logistat (-1, 0.2); %! assert_equal (m, -1); %! assert_equal (v, 0.1316, 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/loglstat.m000066400000000000000000000101661524624707500252300ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} loglstat (@var{mu}, @var{sigma}) ## ## Compute statistics of the loglogistic distribution. ## ## @code{[@var{m}, @var{v}] = loglstat (@var{mu}, @var{sigma})} returns the mean ## and variance of the loglogistic distribution with mean parameter @var{mu} and ## scale parameter @var{sigma}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the loglogistic distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-logistic_distribution} ## ## OCTAVE/MATLAB use an alternative parameterization given by the pair ## @math{μ, σ}, i.e. @var{mu} and @var{sigma}, in analogy with the logistic ## distribution. Their relation to the @math{α} and @math{b} parameters used ## in Wikipedia are given below: ## ## @itemize ## @item @qcode{@var{mu} = log (@var{a})} ## @item @qcode{@var{sigma} = 1 / @var{a}} ## @end itemize ## ## @seealso{logncdf, logninv, lognpdf, lognrnd, lognfit, lognlike} ## @end deftypefn function [m, v] = loglstat (mu, sigma) ## Check for valid number of input arguments if (nargin < 2) error ("loglstat: function called with too few input arguments."); endif ## Check for MU and SIGMA being numeric if (! (isnumeric (mu) && isnumeric (sigma))) error ("loglstat: MU and SIGMA must be numeric."); endif ## Check for MU and SIGMA being real if (iscomplex (mu) || iscomplex (sigma)) error ("loglstat: MU and SIGMA must not be complex."); endif ## Check for common size of MU and SIGMA if (! isscalar (mu) || ! isscalar (sigma)) [retval, mu, sigma] = common_size (mu, sigma); if (retval > 0) error ("loglstat: MU and SIGMA must be of common size or scalars."); endif endif ## Calculate moments pib = pi .* sigma; m = (exp (mu) .* pib) ./ sin (pib); pib2 = 2 * pib; v = exp (mu) .^ 2 .* (pib2 ./ sin (pib2) - pib .^ 2 ./ sin (pib) .^ 2); ## Continue argument check m(sigma >= 1) = Inf; v(sigma >= 0.5) = Inf; m(sigma <= 0) = NaN; v(sigma <= 0) = NaN; endfunction ## Input validation tests %!error loglstat () %!error loglstat (1) %!error loglstat ({}, 2) %!error loglstat (1, '') %!error loglstat (i, 2) %!error loglstat (1, i) %!error ... %! loglstat (ones (3), ones (2)) %!error ... %! loglstat (ones (2), ones (3)) ## Output validation tests %!test %! [m, v] = loglstat (0, 1); %! assert_equal (m, Inf, 0.001); %! assert_equal (v, Inf, 0.001); %!test %! [m, v] = loglstat (0, 0.8); %! assert_equal (m, 4.2758, 0.001); %! assert_equal (v, Inf, 0.001); %!test %! [m, v] = loglstat (0, 0.6); %! assert_equal (m, 1.9820, 0.001); %! assert_equal (v, Inf, 0.001); %!test %! [m, v] = loglstat (0, 0.4); %! assert_equal (m, 1.3213, 0.001); %! assert_equal (v, 2.5300, 0.001); %!test %! [m, v] = loglstat (0, 0.2); %! assert_equal (m, 1.0690, 0.001); %! assert_equal (v, 0.1786, 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/lognstat.m000066400000000000000000000075621524624707500252400ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} lognstat (@var{mu}, @var{sigma}) ## ## Compute statistics of the lognormal distribution. ## ## @code{[@var{m}, @var{v}] = lognstat (@var{mu}, @var{sigma})} returns the mean ## and variance of the lognormal distribution with mean parameter @var{mu} and ## standard deviation parameter @var{sigma}, each corresponding to the ## associated normal distribution. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the lognormal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Log-normal_distribution} ## ## @seealso{logncdf, logninv, lognpdf, lognrnd, lognfit, lognlike} ## @end deftypefn function [m, v] = lognstat (mu, sigma) ## Check for valid number of input arguments if (nargin < 2) error ("lognstat: function called with too few input arguments."); endif ## Check for MU and SIGMA being numeric if (! (isnumeric (mu) && isnumeric (sigma))) error ("lognstat: MU and SIGMA must be numeric."); endif ## Check for MU and SIGMA being real if (iscomplex (mu) || iscomplex (sigma)) error ("lognstat: MU and SIGMA must not be complex."); endif ## Check for common size of MU and SIGMA if (! isscalar (mu) || ! isscalar (sigma)) [retval, mu, sigma] = common_size (mu, sigma); if (retval > 0) error ("lognstat: MU and SIGMA must be of common size or scalars."); endif endif ## Calculate moments m = exp (mu + (sigma .^ 2) ./ 2); v = (exp (sigma .^ 2) - 1) .* exp (2 .* mu + sigma .^ 2); ## Continue argument check k = find (! (sigma >= 0) | ! (sigma < Inf)); if (any (k)) m(k) = NaN; v(k) = NaN; endif endfunction ## Input validation tests %!error lognstat () %!error lognstat (1) %!error lognstat ({}, 2) %!error lognstat (1, '') %!error lognstat (i, 2) %!error lognstat (1, i) %!error ... %! lognstat (ones (3), ones (2)) %!error ... %! lognstat (ones (2), ones (3)) ## Output validation tests %!test %! mu = 0:0.2:1; %! sigma = 0.2:0.2:1.2; %! [m, v] = lognstat (mu, sigma); %! expected_m = [1.0202, 1.3231, 1.7860, 2.5093, 3.6693, 5.5845]; %! expected_v = [0.0425, 0.3038, 1.3823, 5.6447, 23.1345, 100.4437]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); %!test %! sigma = 0.2:0.2:1.2; %! [m, v] = lognstat (0, sigma); %! expected_m = [1.0202, 1.0833, 1.1972, 1.3771, 1.6487, 2.0544]; %! expected_v = [0.0425, 0.2036, 0.6211, 1.7002, 4.6708, 13.5936]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/nakastat.m000066400000000000000000000070011524624707500251770ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} nakastat (@var{mu}, @var{omega}) ## ## Compute statistics of the Nakagami distribution. ## ## @code{[@var{m}, @var{v}] = nakastat (@var{mu}, @var{omega})} returns the mean ## and variance of the Nakagami distribution with shape parameter @var{mu} and ## spread parameter @var{omega}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the Nakagami distribution can be found at ## @url{https://en.wikipedia.org/wiki/Normal_distribution} ## ## @seealso{nakacdf, nakainv, nakapdf, nakarnd, nakafit, nakalike} ## @end deftypefn function [m, v] = nakastat (mu, omega) ## Check for valid number of input arguments if (nargin < 2) error ("nakastat: function called with too few input arguments."); endif ## Check for MU and OMEGA being numeric if (! (isnumeric (mu) && isnumeric (omega))) error ("nakastat: MU and OMEGA must be numeric."); endif ## Check for MU and OMEGA being real if (iscomplex (mu) || iscomplex (omega)) error ("nakastat: MU and OMEGA must not be complex."); endif ## Check for common size of MU and OMEGA if (! isscalar (mu) || ! isscalar (omega)) [retval, mu, omega] = common_size (mu, omega); if (retval > 0) error ("nakastat: MU and OMEGA must be of common size or scalars."); endif endif ## Calculate moments g = gamma (mu + 0.5) ./ gamma (mu); m = g .* sqrt (omega ./ mu); v = omega .* (1 - ((1 ./ mu) .* (g .^ 2))); ## Continue argument check knan = mu < 0.5 | omega <= 0; m(knan) = NaN; v(knan) = NaN; endfunction ## Input validation tests %!error nakastat () %!error nakastat (1) %!error nakastat ({}, 2) %!error nakastat (1, '') %!error nakastat (i, 2) %!error nakastat (1, i) %!error ... %! nakastat (ones (3), ones (2)) %!error ... %! nakastat (ones (2), ones (3)) ## Output validation tests %!test %! [m, v] = nakastat (1, 1); %! assert_equal (m, 0.8862269254, 1e-10); %! assert_equal (v, 0.2146018366, 1e-10); %!test %! [m, v] = nakastat (1, 2); %! assert_equal (m, 1.25331413731, 1e-10); %! assert_equal (v, 0.42920367321, 1e-10); %!test %! [m, v] = nakastat (2, 1); %! assert_equal (m, 0.93998560299, 1e-10); %! assert_equal (v, 0.11642706618, 1e-10); statistics-release-1.9.2/inst/Distribution_Statistics/nbinstat.m000066400000000000000000000073741524624707500252300ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} nbinstat (@var{r}, @var{ps}) ## ## Compute statistics of the negative binomial distribution. ## ## @code{[@var{m}, @var{v}] = nbinstat (@var{r}, @var{ps})} returns the mean ## and variance of the negative binomial distribution with parameters @var{r} ## and @var{ps}, where @var{r} is the number of successes until the experiment ## is stopped and @var{ps} is the probability of success in each experiment, ## given the number of failures in @var{x}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the negative binomial distribution can be found at ## @url{https://en.wikipedia.org/wiki/Negative_binomial_distribution} ## ## @seealso{nbincdf, nbininv, nbinpdf, nbinrnd, nbinfit, nbinlike} ## @end deftypefn function [m, v] = nbinstat (r, ps) ## Check for valid number of input arguments if (nargin < 2) error ("nbinstat: function called with too few input arguments."); endif ## Check for R and PS being numeric if (! (isnumeric (r) && isnumeric (ps))) error ("nbinstat: R and PS must be numeric."); endif ## Check for R and PS being real if (iscomplex (r) || iscomplex (ps)) error ("nbinstat: R and PS must not be complex."); endif ## Check for common size of R and PS if (! isscalar (r) || ! isscalar (ps)) [retval, r, ps] = common_size (r, ps); if (retval > 0) error ("nbinstat: R and PS must be of common size or scalars."); endif endif ## Calculate moments q = 1 - ps; m = r .* q ./ ps; v = r .* q ./ (ps .^ 2); ## Continue argument check k = find (! (r > 0) | ! (r < Inf) | ! (ps > 0) | ! (ps < 1)); if (any (k)) m(k) = NaN; v(k) = NaN; endif endfunction ## Input validation tests %!error nbinstat () %!error nbinstat (1) %!error nbinstat ({}, 2) %!error nbinstat (1, '') %!error nbinstat (i, 2) %!error nbinstat (1, i) %!error ... %! nbinstat (ones (3), ones (2)) %!error ... %! nbinstat (ones (2), ones (3)) ## Output validation tests %!test %! r = 1:4; %! ps = 0.2:0.2:0.8; %! [m, v] = nbinstat (r, ps); %! expected_m = [ 4.0000, 3.0000, 2.0000, 1.0000]; %! expected_v = [20.0000, 7.5000, 3.3333, 1.2500]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); %!test %! r = 1:4; %! [m, v] = nbinstat (r, 0.5); %! expected_m = [1, 2, 3, 4]; %! expected_v = [2, 4, 6, 8]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/ncfstat.m000066400000000000000000000120151524624707500250340ustar00rootroot00000000000000## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} ncfstat (@var{df1}, @var{df1}, @var{lambda}) ## ## Compute statistics for the noncentral @math{F}-distribution. ## ## @code{[@var{m}, @var{v}] = ncfstat (@var{df1}, @var{df1}, @var{lambda})} ## returns the mean and variance of the noncentral @math{F}-distribution with ## @var{df1} and @var{df2} degrees of freedom and noncentrality parameter ## @var{lambda}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the same ## size as the other inputs. ## ## Further information about the noncentral @math{F}-distribution can be found ## at @url{https://en.wikipedia.org/wiki/Noncentral_F-distribution} ## ## @seealso{ncfcdf, ncfinv, ncfpdf, ncfrnd, fstat} ## @end deftypefn function [m, v] = ncfstat (df1, df2, lambda) ## Check for valid number of input arguments if (nargin < 3) error ("ncfstat: function called with too few input arguments."); endif ## Check for DF1, DF2, and LAMBDA being numeric if (! (isnumeric (df1) && isnumeric (df2) && isnumeric (lambda))) error ("ncfstat: DF1, DF2, and LAMBDA must be numeric."); endif ## Check for DF1, DF2, and LAMBDA being reals if (iscomplex (df1) || iscomplex (df2) || iscomplex (lambda)) error ("ncfstat: DF1, DF2, and LAMBDA must not be complex."); endif ## Check for common size of DF1, DF2, and LAMBDA if (! isscalar (df1) || ! isscalar (df2) || ! isscalar (lambda)) [retval, df1, df2, lambda] = common_size (df1, df2, lambda); if (retval > 0) error ("ncfstat: DF1, DF2, and LAMBDA must be of common size or scalars."); endif endif ## Initialize mean and variance if (isa (df1, 'single') || isa (df2, 'single') || isa (lambda, 'single')) m = zeros (size (df1), 'single'); v = m; else m = zeros (size (df1)); v = m; endif ## Return NaNs for invalid df2 parameters m(df2 <= 2) = NaN; v(df2 <= 4) = NaN; ## Compute mean and variance for valid parameter values. k = (df2 > 2); if (any (k(:))) m(k) = df2(k) .* (df1(k) + lambda(k)) ./ (df1(k) .* (df2(k) - 2)); endif k = (df2 > 4); if (any (k(:))) df1_idx = df1(k) + lambda(k); df2_idx = df2(k) - 2; df1_df2 = (df2(k) ./ df1(k)) .^ 2; v(k) = 2 * df1_df2 .* (df1_idx .^ 2 + (df1_idx + lambda(k)) .* ... df2_idx) ./ ((df2(k) - 4) .* df2_idx .^ 2); endif endfunction ## Input validation tests %!error ncfstat () %!error ncfstat (1) %!error ncfstat (1, 2) %!error ncfstat ({}, 2, 3) %!error ncfstat (1, '', 3) %!error ncfstat (1, 2, '') %!error ncfstat (i, 2, 3) %!error ncfstat (1, i, 3) %!error ncfstat (1, 2, i) %!error ... %! ncfstat (ones (3), ones (2), 3) %!error ... %! ncfstat (ones (2), 2, ones (3)) %!error ... %! ncfstat (1, ones (2), ones (3)) ## Output validation tests %!shared df1, df2, lambda %! df1 = [2, 0, -1, 1, 4, 5]; %! df2 = [2, 4, -1, 5, 6, 7]; %! lambda = [1, NaN, 3, 0, 2, -1]; %!assert_equal (ncfstat (df1, df2, lambda), [NaN, NaN, NaN, 1.6667, 2.25, 1.12], 1e-4); %!assert_equal (ncfstat (df1(4:6), df2(4:6), 1), [3.3333, 1.8750, 1.6800], 1e-4); %!assert_equal (ncfstat (df1(4:6), df2(4:6), 2), [5.0000, 2.2500, 1.9600], 1e-4); %!assert_equal (ncfstat (df1(4:6), df2(4:6), 3), [6.6667, 2.6250, 2.2400], 1e-4); %!assert_equal (ncfstat (2, [df2(1), df2(4:6)], 5), [NaN,5.8333,5.2500,4.9000], 1e-4); %!assert_equal (ncfstat (0, [df2(1), df2(4:6)], 5), [NaN, Inf, Inf, Inf]); %!assert_equal (ncfstat (1, [df2(1), df2(4:6)], 5), [NaN, 10, 9, 8.4], 1e-14); %!assert_equal (ncfstat (4, [df2(1), df2(4:6)], 5), [NaN, 3.75, 3.375, 3.15], 1e-14); statistics-release-1.9.2/inst/Distribution_Statistics/nctstat.m000066400000000000000000000102531524624707500250540ustar00rootroot00000000000000## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} nctstat (@var{df}, @var{mu}) ## ## Compute statistics for the noncentral @math{t}-distribution. ## ## @code{[@var{m}, @var{v}] = nctstat (@var{df}, @var{mu})} returns the mean ## and variance of the noncentral @math{t}-distribution with @var{df} degrees ## of freedom and noncentrality parameter @var{mu}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the same ## size as the other inputs. ## ## Further information about the noncentral @math{t}-distribution can be found ## at @url{https://en.wikipedia.org/wiki/Noncentral_t-distribution} ## ## @seealso{nctcdf, nctinv, nctpdf, nctrnd, tstat} ## @end deftypefn function [m, v] = nctstat (df, mu) ## Check for valid number of input arguments if (nargin < 2) error ("nctstat: function called with too few input arguments."); endif ## Check for DF and MU being numeric if (! (isnumeric (df) && isnumeric (mu))) error ("nctstat: DF and MU must be numeric."); endif ## Check for DF and MU being real if (iscomplex (df) || iscomplex (mu)) error ("nctstat: DF and MU must not be complex."); endif ## Check for common size of DF and MU if (! isscalar (df) || ! isscalar (mu)) [retval, df, mu] = common_size (df, mu); if (retval > 0) error ("nctstat: DF and MU must be of common size or scalars."); endif endif ## Initialize mean and variance if (isa (df, 'single') || isa (mu, 'single')) m = NaN (size (df), 'single'); v = m; else m = NaN (size (df)); v = m; endif ## Compute mean and variance for valid parameter values. mk = df > 1; if (any (mk(:))) m(mk) = mu(mk) .* sqrt ((df(mk) / 2)) .* ... gamma ((df(mk) - 1) / 2) ./ gamma (df(mk) / 2); endif vk = df > 2; if (any (vk(:))) v(vk) = (df(vk) ./ (df(vk) - 2)) .* ... (1 + mu(vk) .^2) - 0.5 * (df(vk) .* mu(vk) .^ 2) .* ... exp (2 * (gammaln ((df(vk) - 1) / 2) - gammaln (df(vk) / 2))); endif endfunction ## Input validation tests %!error nctstat () %!error nctstat (1) %!error nctstat ({}, 2) %!error nctstat (1, '') %!error nctstat (i, 2) %!error nctstat (1, i) %!error ... %! nctstat (ones (3), ones (2)) %!error ... %! nctstat (ones (2), ones (3)) ## Output validation tests %!shared df, mu %! df = [2, 0, -1, 1, 4]; %! mu = [1, NaN, 3, -1, 2]; %!assert_equal (nctstat (df, mu), [1.7725, NaN, NaN, NaN, 2.5066], 1e-4); %!assert_equal (nctstat ([df(1:2), df(4:5)], 1), [1.7725, NaN, NaN, 1.2533], 1e-4); %!assert_equal (nctstat ([df(1:2), df(4:5)], 3), [5.3174, NaN, NaN, 3.7599], 1e-4); %!assert_equal (nctstat ([df(1:2), df(4:5)], 2), [3.5449, NaN, NaN, 2.5066], 1e-4); %!assert_equal (nctstat (2, [mu(1), mu(3:5)]), [1.7725,5.3174,-1.7725,3.5449], 1e-4); %!assert_equal (nctstat (0, [mu(1), mu(3:5)]), [NaN, NaN, NaN, NaN]); %!assert_equal (nctstat (1, [mu(1), mu(3:5)]), [NaN, NaN, NaN, NaN]); %!assert_equal (nctstat (4, [mu(1), mu(3:5)]), [1.2533,3.7599,-1.2533,2.5066], 1e-4); statistics-release-1.9.2/inst/Distribution_Statistics/ncx2stat.m000066400000000000000000000077011524624707500251460ustar00rootroot00000000000000## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} ncx2stat (@var{df}, @var{lambda}) ## ## Compute statistics for the noncentral chi-squared distribution. ## ## @code{[@var{m}, @var{v}] = ncx2stat (@var{df}, @var{lambda})} returns the ## mean and variance of the noncentral chi-squared distribution with @var{df} ## degrees of freedom and noncentrality parameter @var{lambda}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the noncentral chi-squared distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Noncentral_chi-squared_distribution} ## ## @seealso{ncx2cdf, ncx2inv, ncx2pdf, ncx2rnd} ## @end deftypefn function [m, v] = ncx2stat (df, lambda) ## Check for valid number of input arguments if (nargin < 2) error ("ncx2stat: function called with too few input arguments."); endif ## Check for DF and LAMBDA being numeric if (! (isnumeric (df) && isnumeric (lambda))) error ("ncx2stat: DF and LAMBDA must be numeric."); endif ## Check for DF and LAMBDA being real if (iscomplex (df) || iscomplex (lambda)) error ("ncx2stat: DF and LAMBDA must not be complex."); endif ## Check for common size of DF and LAMBDA if (! isscalar (df) || ! isscalar (lambda)) [retval, df, lambda] = common_size (df, lambda); if (retval > 0) error ("ncx2stat: DF and LAMBDA must be of common size or scalars."); endif endif ## Initialize mean and variance if (isa (df, 'single') || isa (lambda, 'single')) m = NaN (size (df), 'single'); v = m; else m = NaN (size (df)); v = m; endif ## Compute mean and variance for valid parameter values. k = (df > 0 & lambda >= 0); if (any (k(:))) m(k) = lambda(k) + df(k); v(k) = 2 * (df(k) + 2 * (lambda(k))); endif endfunction ## Input validation tests %!error ncx2stat () %!error ncx2stat (1) %!error ncx2stat ({}, 2) %!error ncx2stat (1, '') %!error ncx2stat (i, 2) %!error ncx2stat (1, i) %!error ... %! ncx2stat (ones (3), ones (2)) %!error ... %! ncx2stat (ones (2), ones (3)) ## Output validation tests %!shared df, d1 %! df = [2, 0, -1, 1, 4]; %! d1 = [1, NaN, 3, -1, 2]; %!assert_equal (ncx2stat (df, d1), [3, NaN, NaN, NaN, 6]); %!assert_equal (ncx2stat ([df(1:2), df(4:5)], 1), [3, NaN, 2, 5]); %!assert_equal (ncx2stat ([df(1:2), df(4:5)], 3), [5, NaN, 4, 7]); %!assert_equal (ncx2stat ([df(1:2), df(4:5)], 2), [4, NaN, 3, 6]); %!assert_equal (ncx2stat (2, [d1(1), d1(3:5)]), [3, 5, NaN, 4]); %!assert_equal (ncx2stat (0, [d1(1), d1(3:5)]), [NaN, NaN, NaN, NaN]); %!assert_equal (ncx2stat (1, [d1(1), d1(3:5)]), [2, 4, NaN, 3]); %!assert_equal (ncx2stat (4, [d1(1), d1(3:5)]), [5, 7, NaN, 6]); statistics-release-1.9.2/inst/Distribution_Statistics/normstat.m000066400000000000000000000071661524624707500252540ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} normstat (@var{mu}, @var{sigma}) ## ## Compute statistics of the normal distribution. ## ## @code{[@var{m}, @var{v}] = normstat (@var{mu}, @var{sigma})} returns the mean ## and variance of the normal distribution with non-centrality (distance) ## parameter @var{mu} and scale parameter @var{sigma}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the normal distribution can be found at ## @url{https://en.wikipedia.org/wiki/Normal_distribution} ## ## @seealso{normcdf, norminv, normpdf, normrnd, normfit, normlike} ## @end deftypefn function [m, v] = normstat (mu, sigma) ## Check for valid number of input arguments if (nargin < 2) error ("normstat: function called with too few input arguments."); endif ## Check for MU and SIGMA being numeric if (! (isnumeric (mu) && isnumeric (sigma))) error ("normstat: MU and SIGMA must be numeric."); endif ## Check for MU and SIGMA being real if (iscomplex (mu) || iscomplex (sigma)) error ("normstat: MU and SIGMA must not be complex."); endif ## Check for common size of MU and SIGMA if (! isscalar (mu) || ! isscalar (sigma)) [retval, mu, sigma] = common_size (mu, sigma); if (retval > 0) error ("normstat: MU and SIGMA must be of common size or scalars."); endif endif ## Calculate moments m = mu; v = sigma .* sigma; ## Continue argument check k = find (! (sigma > 0) | ! (sigma < Inf)); if (any (k)) m(k) = NaN; v(k) = NaN; endif endfunction ## Input validation tests %!error normstat () %!error normstat (1) %!error normstat ({}, 2) %!error normstat (1, '') %!error normstat (i, 2) %!error normstat (1, i) %!error ... %! normstat (ones (3), ones (2)) %!error ... %! normstat (ones (2), ones (3)) ## Output validation tests %!test %! mu = 1:6; %! sigma = 0.2:0.2:1.2; %! [m, v] = normstat (mu, sigma); %! expected_v = [0.0400, 0.1600, 0.3600, 0.6400, 1.0000, 1.4400]; %! assert_equal (m, mu); %! assert_equal (v, expected_v, 0.001); %!test %! sigma = 0.2:0.2:1.2; %! [m, v] = normstat (0, sigma); %! expected_mn = [0, 0, 0, 0, 0, 0]; %! expected_v = [0.0400, 0.1600, 0.3600, 0.6400, 1.0000, 1.4400]; %! assert_equal (m, expected_mn, 0.001); %! assert_equal (v, expected_v, 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/plstat.m000066400000000000000000000066021524624707500247060ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} plstat (@var{x}, @var{Fx}) ## ## Compute statistics of the piecewise linear distribution. ## ## @code{[@var{m}, @var{v}] = plstat (@var{x}, @var{Fx})} returns the mean, ## @var{m}, and variance, @var{v}, of the piecewise linear distribution with a ## vector of @var{x} values at which the CDF changes slope and a vector of CDF ## values @var{Fx} that correspond to each value in @var{x}. Both @var{x} and ## @var{Fx} must be vectors of the same size and at least 2-elements long. ## ## Further information about the piecewise linear distribution can be found at ## @url{https://en.wikipedia.org/wiki/Piecewise_linear_function} ## ## @seealso{plcdf, plinv, plpdf, plrnd} ## @end deftypefn function [m, v] = plstat (x, Fx) ## Check for valid number of input arguments if (nargin < 2) error ("plstat: function called with too few input arguments."); endif ## Check for common size of X and FX if (! isvector (x) || ! isvector (Fx) || ! isequal (size (x), size (Fx))) error ("plstat: X and FX must be vectors of equal size."); endif ## Check for X and FX being at least 2-elements long if (length (x) < 2 || length (Fx) < 2) error ("plstat: X and FX must be at least two-elements long."); endif ## Check for Fx being bounded in [0, 1] if (any (Fx < 0) || any (Fx > 1)) error ("plstat: FX must be bounded in the range [0, 1]."); endif ## Check for X and FX being reals if (iscomplex (x) || iscomplex (Fx)) error ("plstat: X and FX must not be complex."); endif ## Compute the mean and variance x_m = (x(1:end-1) + x(2:end)) / 2; dFx = diff (Fx); m = dot (dFx, x_m); x_v = diff (x) .^ 2 / 12; v = dot (dFx, x_v + (x_m - m) .^ 2); endfunction ## Test output %!shared x, Fx %! x = [0, 1, 3, 4, 7, 10]; %! Fx = [0, 0.2, 0.5, 0.6, 0.7, 1]; %!assert_equal (plstat (x, Fx), 4.15) %!test %! [m, v] = plstat (x, Fx); %! assert_equal (v, 10.3775, 1e-14) ## Test input validation %!error plstat () %!error plstat (1) %!error ... %! plstat ([0, 1, 2], [0, 1]) %!error ... %! plstat ([0], [1]) %!error ... %! plstat ([0, 1, 2], [0, 1, 1.5]) %!error ... %! plstat ([0, 1, 2], [0, i, 1]) %!error ... %! plstat ([0, i, 2], [0, 0.5, 1]) %!error ... %! plstat ([0, i, 2], [0, 0.5i, 1]) statistics-release-1.9.2/inst/Distribution_Statistics/poisstat.m000066400000000000000000000047141524624707500252470ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} poisstat (@var{lambda}) ## ## Compute statistics of the Poisson distribution. ## ## @code{[@var{m}, @var{v}] = poisstat (@var{lambda})} returns the mean and ## variance of the Poisson distribution with rate parameter @var{lambda}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the same size of the ## input argument. ## ## Further information about the Poisson distribution can be found at ## @url{https://en.wikipedia.org/wiki/Poisson_distribution} ## ## @seealso{poisscdf, poissinv, poisspdf, poissrnd, poissfit, poisslike} ## @end deftypefn function [m, v] = poisstat (lambda) ## Check for valid number of input arguments if (nargin < 1) error ("poisstat: function called with too few input arguments."); endif ## Check for LAMBDA being numeric if (! isnumeric (lambda)) error ("poisstat: LAMBDA must be numeric."); endif ## Check for LAMBDA being real if (iscomplex (lambda)) error ("poisstat: LAMBDA must not be complex."); endif ## Set moments m = lambda; v = lambda; ## Continue argument check k = find (! (lambda > 0) | ! (lambda < Inf)); if (any (k)) m(k) = NaN; v(k) = NaN; endif endfunction ## Input validation tests %!error poisstat () %!error poisstat ({}) %!error poisstat ('') %!error poisstat (i) ## Output validation tests %!test %! lambda = 1 ./ (1:6); %! [m, v] = poisstat (lambda); %! assert_equal (m, lambda); %! assert_equal (v, lambda); statistics-release-1.9.2/inst/Distribution_Statistics/raylstat.m000066400000000000000000000053151524624707500252420ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} raylstat (@var{sigma}) ## ## Compute statistics of the Rayleigh distribution. ## ## @code{[@var{m}, @var{v}] = raylstat (@var{sigma})} returns the mean and ## variance of the Rayleigh distribution with scale parameter @var{sigma}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the same size of the ## input argument. ## ## Further information about the Rayleigh distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rayleigh_distribution} ## ## The @code{prob.RayleighDistribution} class names this same parameter ## @qcode{B}, after MATLAB. ## @seealso{raylcdf, raylinv, raylpdf, raylrnd, raylfit, rayllike} ## @end deftypefn function [m, v] = raylstat (sigma) ## Check for valid number of input arguments if (nargin < 1) error ("raylstat: function called with too few input arguments."); endif ## Check for SIGMA being numeric if (! isnumeric (sigma)) error ("raylstat: SIGMA must be numeric."); endif ## Check for SIGMA being real if (iscomplex (sigma)) error ("raylstat: SIGMA must not be complex."); endif ## Calculate moments m = sigma .* sqrt (pi ./ 2); v = (2 - pi ./ 2) .* sigma .^ 2; ## Continue argument check k = find (! (sigma > 0)); if (any (k)) m(k) = NaN; v(k) = NaN; endif endfunction ## Input validation tests %!error raylstat () %!error raylstat ({}) %!error raylstat ('') %!error raylstat (i) ## Output validation tests %!test %! sigma = 1:6; %! [m, v] = raylstat (sigma); %! expected_m = [1.2533, 2.5066, 3.7599, 5.0133, 6.2666, 7.5199]; %! expected_v = [0.4292, 1.7168, 3.8628, 6.8673, 10.7301, 15.4513]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/ricestat.m000066400000000000000000000104731524624707500252160ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} ricestat (@var{s}, @var{sigma}) ## ## Compute statistics of the Rician distribution. ## ## @code{[@var{m}, @var{v}] = ricestat (@var{s}, @var{sigma})} returns the mean ## and variance of the Rician distribution with non-centrality (distance) ## parameter @var{s} and scale parameter @var{sigma}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the Rician distribution can be found at ## @url{https://en.wikipedia.org/wiki/Rice_distribution} ## ## @seealso{ricecdf, riceinv, ricepdf, ricernd, ricefit, ricelike} ## @end deftypefn function [m, v] = ricestat (s, sigma) ## Check for valid number of input arguments if (nargin < 2) error ("ricestat: function called with too few input arguments."); endif ## Check for S and SIGMA being numeric if (! (isnumeric (s) && isnumeric (sigma))) error ("ricestat: S and SIGMA must be numeric."); endif ## Check for S and SIGMA being real if (iscomplex (s) || iscomplex (sigma)) error ("ricestat: S and SIGMA must not be complex."); endif ## Check for common size of S and SIGMA if (! isscalar (s) || ! isscalar (sigma)) [retval, s, sigma] = common_size (s, sigma); if (retval > 0) error ("ricestat: S and SIGMA must be of common size or scalars."); endif endif ## Initialize mean and variance if (isa (s, 'single') || isa (sigma, 'single')) m = NaN (size (s), 'single'); v = m; else m = NaN (size (s)); v = m; endif ## Compute mean and variance for valid parameter values. k = (s >= 0 & sigma > 0); if (any (k(:))) thetasq = (s(k) .^ 2) ./ (sigma(k) .^ 2); L = Laguerre_half (-0.5 .* thetasq); m(k) = sigma(k) .* sqrt (pi / 2) .* L; v(k) = 2 * (sigma(k) .^ 2) + s(k) .^ 2 - ... (0.5 .* pi .* sigma(k) .^ 2) .* L .^ 2; endif endfunction function L = Laguerre_half (x) L = exp (x ./ 2) .* ((1 - x) .* besseli (0, -x./2) - x .* besseli (1, -x./2)); endfunction ## Input validation tests %!error ricestat () %!error ricestat (1) %!error ricestat ({}, 2) %!error ricestat (1, '') %!error ricestat (i, 2) %!error ricestat (1, i) %!error ... %! ricestat (ones (3), ones (2)) %!error ... %! ricestat (ones (2), ones (3)) ## Output validation tests %!shared s, sigma %! s = [2, 0, -1, 1, 4]; %! sigma = [1, NaN, 3, -1, 2]; %!assert_equal (ricestat (s, sigma), [2.2724, NaN, NaN, NaN, 4.5448], 1e-4); %!assert_equal (ricestat ([s(1:2), s(4:5)], 1), [2.2724, 1.2533, 1.5486, 4.1272], 1e-4); %!assert_equal (ricestat ([s(1:2), s(4:5)], 3), [4.1665, 3.7599, 3.8637, 5.2695], 1e-4); %!assert_equal (ricestat ([s(1:2), s(4:5)], 2), [3.0971, 2.5066, 2.6609, 4.5448], 1e-4); %!assert_equal (ricestat (2, [sigma(1), sigma(3:5)]), [2.2724, 4.1665, NaN, 3.0971], 1e-4); %!assert_equal (ricestat (0, [sigma(1), sigma(3:5)]), [1.2533, 3.7599, NaN, 2.5066], 1e-4); %!assert_equal (ricestat (1, [sigma(1), sigma(3:5)]), [1.5486, 3.8637, NaN, 2.6609], 1e-4); %!assert_equal (ricestat (4, [sigma(1), sigma(3:5)]), [4.1272, 5.2695, NaN, 4.5448], 1e-4); statistics-release-1.9.2/inst/Distribution_Statistics/tlsstat.m000066400000000000000000000122541524624707500250750ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} tlsstat (@var{mu}, @var{sigma}, @var{nu}) ## ## Compute statistics of the location-scale Student's T distribution. ## ## @code{[@var{m}, @var{v}] = tlsstat (@var{mu}, @var{sigma}, @var{nu})} returns ## the mean and variance of the location-scale Student's T distribution with ## location parameter @var{mu}, scale parameter @var{sigma}, and @var{nu} ## degrees of freedom. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the location-scale Student's T distribution can be ## found at ## @url{https://en.wikipedia.org/wiki/Student%27s_t-distribution#Location-scale_t_distribution} ## ## @seealso{tlscdf, tlsinv, tlspdf, tlsrnd, tlsfit, tlslike} ## @end deftypefn function [m, v] = tlsstat (mu, sigma, nu) ## Check for valid number of input arguments if (nargin < 3) error ("tlsstat: function called with too few input arguments."); endif ## Check for MU, SIGMA, and NU being numeric if (! (isnumeric (mu) && isnumeric (sigma) && isnumeric (nu))) error ("tlsstat: MU, SIGMA, and NU must be numeric."); endif ## Check for MU, SIGMA, and NU being real if (iscomplex (mu) || iscomplex (sigma) || iscomplex (nu)) error ("tlsstat: MU, SIGMA, and NU must not be complex."); endif ## Check for common size of MU, SIGMA, and NU if (! isscalar (mu) || ! isscalar (sigma) || ! isscalar (nu)) [retval, mu, sigma, nu] = common_size (mu, sigma, nu); if (retval > 0) error ("tlsstat: MU, SIGMA, and NU must be of common size or scalars."); endif endif ## Calculate moments m = zeros (size (nu)) + mu; v = sigma .* (nu ./ (nu - 2)); ## Continue argument check k = find (! (nu > 1) | ! (nu < Inf)); if (any (k)) m(k) = NaN; v(k) = NaN; endif k = find (! (nu > 2) & (nu < Inf)); if (any (k)) v(k) = NaN; endif endfunction ## Input validation tests %!error tlsstat () %!error tlsstat (1) %!error tlsstat (1, 2) %!error tlsstat ({}, 2, 3) %!error tlsstat (1, '', 3) %!error tlsstat (1, 2, ['d']) %!error tlsstat (i, 2, 3) %!error tlsstat (1, i, 3) %!error tlsstat (1, 2, i) %!error ... %! tlsstat (ones (3), ones (2), 1) %!error ... %! tlsstat (ones (2), 1, ones (3)) %!error ... %! tlsstat (1, ones (2), ones (3)) ## Output validation tests %!test %! [m, v] = tlsstat (0, 1, 0); %! assert_equal (m, NaN); %! assert_equal (v, NaN); %!test %! [m, v] = tlsstat (0, 1, 1); %! assert_equal (m, NaN); %! assert_equal (v, NaN); %!test %! [m, v] = tlsstat (2, 1, 1); %! assert_equal (m, NaN); %! assert_equal (v, NaN); %!test %! [m, v] = tlsstat (-2, 1, 1); %! assert_equal (m, NaN); %! assert_equal (v, NaN); %!test %! [m, v] = tlsstat (0, 1, 2); %! assert_equal (m, 0); %! assert_equal (v, NaN); %!test %! [m, v] = tlsstat (2, 1, 2); %! assert_equal (m, 2); %! assert_equal (v, NaN); %!test %! [m, v] = tlsstat (-2, 1, 2); %! assert_equal (m, -2); %! assert_equal (v, NaN); %!test %! [m, v] = tlsstat (0, 2, 2); %! assert_equal (m, 0); %! assert_equal (v, NaN); %!test %! [m, v] = tlsstat (2, 2, 2); %! assert_equal (m, 2); %! assert_equal (v, NaN); %!test %! [m, v] = tlsstat (-2, 2, 2); %! assert_equal (m, -2); %! assert_equal (v, NaN); %!test %! [m, v] = tlsstat (0, 1, 3); %! assert_equal (m, 0); %! assert_equal (v, 3); %!test %! [m, v] = tlsstat (0, 2, 3); %! assert_equal (m, 0); %! assert_equal (v, 6); %!test %! [m, v] = tlsstat (2, 1, 3); %! assert_equal (m, 2); %! assert_equal (v, 3); %!test %! [m, v] = tlsstat (2, 2, 3); %! assert_equal (m, 2); %! assert_equal (v, 6); %!test %! [m, v] = tlsstat (-2, 1, 3); %! assert_equal (m, -2); %! assert_equal (v, 3); %!test %! [m, v] = tlsstat (-2, 2, 3); %! assert_equal (m, -2); %! assert_equal (v, 6); statistics-release-1.9.2/inst/Distribution_Statistics/tristat.m000066400000000000000000000071571524624707500250770ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} tristat (@var{a}, @var{b}, @var{c}) ## ## Compute statistics of the Triangular distribution. ## ## @code{[@var{m}, @var{v}] = tristat (@var{a}, @var{b}, @var{c})} returns the ## mean and variance of the Triangular distribution with lower limit parameter ## @var{a}, peak location (mode) parameter @var{b}, and upper limit parameter ## @var{c}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Note that the order of the parameter input arguments has been changed after ## statistics version 1.6.3 in order to be MATLAB compatible with the parameters ## used in the TriangularDistribution probability distribution object. More ## specifically, the positions of the parameters @var{b} and @var{c} have been ## swapped. As a result, the naming conventions no longer coincide with those ## used in Wikipedia, in which @math{b} denotes the upper limit and @math{c} ## denotes the mode or peak parameter. ## ## Further information about the triangular distribution can be found at ## @url{https://en.wikipedia.org/wiki/Triangular_distribution} ## ## @seealso{tcdf, tinv, tpdf, trnd} ## @end deftypefn function [m, v] = tristat (a, b, c) ## Check for valid number of input arguments if (nargin < 3) error ("tristat: function called with too few input arguments."); endif ## Check for A, B, and C being numeric if (! (isnumeric (a) && isnumeric (b) && isnumeric (c))) error ("tristat: A, B, and C must be numeric."); endif ## Check for A, B, and C being real if (! (isreal (a) && isreal (b) && isreal (c))) error ("tristat: A, B, and C must be real."); endif ## Calculate moments m = (a + b + c) ./ 3; v = (a .^ 2 + b .^ 2 + c .^ 2 - a .* b - a .* c - b .* c) ./ 18; ## Continue argument check k = find (! (a < c) | ! (a <= b & b <= c)); if (any (k)) m(k) = NaN; v(k) = NaN; endif endfunction ## Input validation tests %!error tristat () %!error tristat (1) %!error tristat (1, 2) %!error tristat ('i', 2, 1) %!error tristat (0, 'd', 1) %!error tristat (0, 3, {}) %!error tristat (i, 2, 1) %!error tristat (0, i, 1) %!error tristat (0, 3, i) ## Output validation tests %!test %! a = 1:5; %! b = 3:7; %! c = 5:9; %! [m, v] = tristat (a, b, c); %! expected_m = [3, 4, 5, 6, 7]; %! assert_equal (m, expected_m); %! assert_equal (v, ones (1, 5) * (2/3)); statistics-release-1.9.2/inst/Distribution_Statistics/tstat.m000066400000000000000000000050401524624707500245310ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} tstat (@var{df}) ## ## Compute statistics of the Student's T distribution. ## ## @code{[@var{m}, @var{v}] = tstat (@var{df})} returns the mean and variance of ## the Student's T distribution with @var{df} degrees of freedom. ## ## The size of @var{m} (mean) and @var{v} (variance) is the same size of the ## input argument. ## ## Further information about the Student's T distribution can be found at ## @url{https://en.wikipedia.org/wiki/Student%27s_t-distribution} ## ## @seealso{tcdf, tinv, tpdf, trnd} ## @end deftypefn function [m, v] = tstat (df) ## Check for valid number of input arguments if (nargin < 1) error ("tstat: function called with too few input arguments."); endif ## Check for DF being numeric if (! isnumeric (df)) error ("tstat: DF must be numeric."); endif ## Check for DF being real if (iscomplex (df)) error ("tstat: DF must not be complex."); endif ## Calculate moments m = zeros (size (df)); v = df ./ (df - 2); ## Continue argument check k = find (! (df > 1) | ! (df < Inf)); if (any (k)) m(k) = NaN; v(k) = NaN; endif k = find (! (df > 2) & (df < Inf)); if (any (k)) v(k) = Inf; endif endfunction ## Input validation tests %!error tstat () %!error tstat ({}) %!error tstat ('') %!error tstat (i) ## Output validation tests %!test %! df = 3:8; %! [m, v] = tstat (df); %! expected_m = [0, 0, 0, 0, 0, 0]; %! expected_v = [3.0000, 2.0000, 1.6667, 1.5000, 1.4000, 1.3333]; %! assert_equal (m, expected_m); %! assert_equal (v, expected_v, 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/unidstat.m000066400000000000000000000052771524624707500252410ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} unidstat (@var{df}) ## ## Compute statistics of the discrete uniform cumulative distribution. ## ## @code{[@var{m}, @var{v}] = unidstat (@var{df})} returns the mean and variance ## of the discrete uniform cumulative distribution with parameter @var{N}, which ## corresponds to the maximum observable value and must be a positive natural ## number. ## ## The size of @var{m} (mean) and @var{v} (variance) is the same size of the ## input argument. ## ## Further information about the discrete uniform distribution can be found at ## @url{https://en.wikipedia.org/wiki/Discrete_uniform_distribution} ## ## @seealso{unidcdf, unidinv, unidpdf, unidrnd, unidfit} ## @end deftypefn function [m, v] = unidstat (N) ## Check for valid number of input arguments if (nargin < 1) error ("unidstat: function called with too few input arguments."); endif ## Check for N being numeric if (! isnumeric (N)) error ("unidstat: N must be numeric."); endif ## Check for N being real if (iscomplex (N)) error ("unidstat: N must not be complex."); endif ## Calculate moments m = (N + 1) ./ 2; v = ((N .^ 2) - 1) ./ 12; ## Continue argument check k = find (! (N > 0) | ! (N < Inf) | ! (N == round (N))); if (any (k)) m(k) = NaN; v(k) = NaN; endif endfunction ## Input validation tests %!error unidstat () %!error unidstat ({}) %!error unidstat ('') %!error unidstat (i) ## Output validation tests %!test %! N = 1:6; %! [m, v] = unidstat (N); %! expected_m = [1.0000, 1.5000, 2.0000, 2.5000, 3.0000, 3.5000]; %! expected_v = [0.0000, 0.2500, 0.6667, 1.2500, 2.0000, 2.9167]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/unifstat.m000066400000000000000000000073141524624707500252350ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} unifstat (@var{df}) ## ## Compute statistics of the continuous uniform cumulative distribution. ## ## @code{[@var{m}, @var{v}] = unifstat (@var{df})} returns the mean and variance ## of the continuous uniform cumulative distribution with parameters @var{a} and ## @var{b}, which define the lower and upper bounds of the interval ## @qcode{[@var{a}, @var{b}]}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the continuous uniform distribution can be found at ## @url{https://en.wikipedia.org/wiki/Continuous_uniform_distribution} ## ## @seealso{unifcdf, unifinv, unifpdf, unifrnd, unifit} ## @end deftypefn function [m, v] = unifstat (a, b) ## Check for valid number of input arguments if (nargin < 2) error ("unifstat: function called with too few input arguments."); endif ## Check for A and B being numeric if (! (isnumeric (a) && isnumeric (b))) error ("unifstat: A and B must be numeric."); endif ## Check for A and B being real if (iscomplex (a) || iscomplex (b)) error ("unifstat: A and B must not be complex."); endif ## Check for common size of A and B if (! isscalar (a) || ! isscalar (b)) [retval, a, b] = common_size (a, b); if (retval > 0) error ("unifstat: A and B must be of common size or scalars."); endif endif ## Calculate moments m = (a + b) ./ 2; v = ((b - a) .^ 2) ./ 12; ## Continue argument check k = find (! (-Inf < a) | ! (a < b) | ! (b < Inf)); if (any (k)) m(k) = NaN; v(k) = NaN; endif endfunction ## Input validation tests %!error unifstat () %!error unifstat (1) %!error unifstat ({}, 2) %!error unifstat (1, '') %!error unifstat (i, 2) %!error unifstat (1, i) %!error ... %! unifstat (ones (3), ones (2)) %!error ... %! unifstat (ones (2), ones (3)) ## Output validation tests %!test %! a = 1:6; %! b = 2:2:12; %! [m, v] = unifstat (a, b); %! expected_m = [1.5000, 3.0000, 4.5000, 6.0000, 7.5000, 9.0000]; %! expected_v = [0.0833, 0.3333, 0.7500, 1.3333, 2.0833, 3.0000]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); %!test %! a = 1:6; %! [m, v] = unifstat (a, 10); %! expected_m = [5.5000, 6.0000, 6.5000, 7.0000, 7.5000, 8.0000]; %! expected_v = [6.7500, 5.3333, 4.0833, 3.0000, 2.0833, 1.3333]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); statistics-release-1.9.2/inst/Distribution_Statistics/wblstat.m000066400000000000000000000077061524624707500250650ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{m}, @var{v}] =} wblstat (@var{lambda}, @var{k}) ## ## Compute statistics of the Weibull distribution. ## ## @code{[@var{m}, @var{v}] = wblstat (@var{lambda}, @var{k})} returns the mean ## and variance of the Weibull distribution with scale parameter @var{lambda} ## and shape parameter @var{k}. ## ## The size of @var{m} (mean) and @var{v} (variance) is the common size of the ## input arguments. A scalar input functions as a constant matrix of the ## same size as the other inputs. ## ## Further information about the Weibull distribution can be found at ## @url{https://en.wikipedia.org/wiki/Weibull_distribution} ## ## The @code{prob.WeibullDistribution} class names these same two parameters ## @qcode{A} and @qcode{B}, after MATLAB. @var{lambda} is its @qcode{A} and ## @var{k} is its @qcode{B}. ## @seealso{wblcdf, wblinv, wblpdf, wblrnd, wblfit, wbllike, wblplot} ## @end deftypefn function [m, v] = wblstat (lambda, k) ## Check for valid number of input arguments if (nargin < 2) error ("wblstat: function called with too few input arguments."); endif ## Check for LAMBDA and K being numeric if (! (isnumeric (lambda) && isnumeric (k))) error ("wblstat: LAMBDA and K must be numeric."); endif ## Check for LAMBDA and K being real if (iscomplex (lambda) || iscomplex (k)) error ("wblstat: LAMBDA and K must not be complex."); endif ## Check for common size of LAMBDA and K if (! isscalar (lambda) || ! isscalar (k)) [retval, lambda, k] = common_size (lambda, k); if (retval > 0) error ("wblstat: LAMBDA and K must be of common size or scalars."); endif endif ## Calculate moments m = lambda .* gamma (1 + 1 ./ k); v = (lambda .^ 2) .* gamma (1 + 2 ./ k) - m .^ 2; ## Continue argument check is_nan = find (! (lambda > 0) | ! (lambda < Inf) | ! (k > 0) | ! (k < Inf)); if (any (is_nan)) m(is_nan) = NaN; v(is_nan) = NaN; endif endfunction ## Input validation tests %!error wblstat () %!error wblstat (1) %!error wblstat ({}, 2) %!error wblstat (1, '') %!error wblstat (i, 2) %!error wblstat (1, i) %!error ... %! wblstat (ones (3), ones (2)) %!error ... %! wblstat (ones (2), ones (3)) ## Output validation tests %!test %! lambda = 3:8; %! k = 1:6; %! [m, v] = wblstat (lambda, k); %! expected_m = [3.0000, 3.5449, 4.4649, 5.4384, 6.4272, 7.4218]; %! expected_v = [9.0000, 3.4336, 2.6333, 2.3278, 2.1673, 2.0682]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); %!test %! k = 1:6; %! [m, v] = wblstat (6, k); %! expected_m = [ 6.0000, 5.3174, 5.3579, 5.4384, 5.5090, 5.5663]; %! expected_v = [36.0000, 7.7257, 3.7920, 2.3278, 1.5923, 1.1634]; %! assert_equal (m, expected_m, 0.001); %! assert_equal (v, expected_v, 0.001); statistics-release-1.9.2/inst/Distribution_Wrappers/000077500000000000000000000000001524624707500226665ustar00rootroot00000000000000statistics-release-1.9.2/inst/Distribution_Wrappers/cdf.m000066400000000000000000000423611524624707500236060ustar00rootroot00000000000000## Copyright (C) 2013 Pantxo Diribarne ## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} cdf (@var{name}, @var{x}, @var{A}) ## @deftypefnx {statistics} {@var{p} =} cdf (@var{name}, @var{x}, @var{A}, @var{B}) ## @deftypefnx {statistics} {@var{p} =} cdf (@var{name}, @var{x}, @var{A}, @var{B}, @var{C}) ## @deftypefnx {statistics} {@var{p} =} cdf (@dots{}, @qcode{'upper'}) ## ## Return the CDF of a univariate distribution evaluated at @var{x}. ## ## @code{cdf} is a wrapper for the univariate cumulative distribution functions ## available in the statistics package. See the corresponding functions' help ## to learn the signification of the parameters after @var{x}. ## ## @code{@var{p} = cdf (@var{name}, @var{x}, @var{A})} returns the CDF for the ## one-parameter distribution family specified by @var{name} and the ## distribution parameter @var{A}, evaluated at the values in @var{x}. ## ## @code{@var{p} = cdf (@var{name}, @var{x}, @var{A}, @var{B})} returns the CDF ## for the two-parameter distribution family specified by @var{name} and the ## distribution parameters @var{A} and @var{B}, evaluated at the values in ## @var{x}. ## ## @code{@var{p} = cdf (@var{name}, @var{x}, @var{A}, @var{B}, @var{C})} returns ## the CDF for the three-parameter distribution family specified by @var{name} ## and the distribution parameters @var{A}, @var{B}, and @var{C}, evaluated at ## the values in @var{x}. ## ## @code{@var{p} = cdf (@dots{}, @qcode{'upper'})} returns the complement of the ## CDF using an algorithm that more accurately computes the extreme upper-tail ## probabilities. @qcode{'upper'} can follow any of the input arguments in the ## previous syntaxes. ## ## @var{name} must be a char string of the name or the abbreviation of the ## desired cumulative distribution function as listed in the following table. ## The last column shows the number of required parameters that should be parsed ## after @var{x} to the desired CDF. The optional input argument ## @qcode{'upper'} does not count in the required number of parameters. ## ## @multitable @columnfractions 0.4 0.2 0.3 ## @headitem Distribution Name @tab Abbreviation @tab Input Parameters ## @item @qcode{'Beta'} @tab @qcode{'beta'} @tab 2 ## @item @qcode{'Binomial'} @tab @qcode{'bino'} @tab 2 ## @item @qcode{'Birnbaum-Saunders'} @tab @qcode{'bisa'} @tab 2 ## @item @qcode{'Burr'} @tab @qcode{'burr'} @tab 3 ## @item @qcode{'Cauchy'} @tab @qcode{'cauchy'} @tab 2 ## @item @qcode{'Chi-squared'} @tab @qcode{'chi2'} @tab 1 ## @item @qcode{'Extreme Value'} @tab @qcode{'ev'} @tab 2 ## @item @qcode{'Exponential'} @tab @qcode{'exp'} @tab 1 ## @item @qcode{'F-Distribution'} @tab @qcode{'f'} @tab 2 ## @item @qcode{'Gamma'} @tab @qcode{'gam'} @tab 2 ## @item @qcode{'Geometric'} @tab @qcode{'geo'} @tab 1 ## @item @qcode{'Generalized Extreme Value'} @tab @qcode{'gev'} @tab 3 ## @item @qcode{'Generalized Pareto'} @tab @qcode{'gp'} @tab 3 ## @item @qcode{'Gumbel'} @tab @qcode{'gumbel'} @tab 2 ## @item @qcode{'Half-normal'} @tab @qcode{'hn'} @tab 2 ## @item @qcode{'Hypergeometric'} @tab @qcode{'hyge'} @tab 3 ## @item @qcode{'Inverse Gaussian'} @tab @qcode{'invg'} @tab 2 ## @item @qcode{'Laplace'} @tab @qcode{'laplace'} @tab 2 ## @item @qcode{'Logistic'} @tab @qcode{'logi'} @tab 2 ## @item @qcode{'Log-Logistic'} @tab @qcode{'logl'} @tab 2 ## @item @qcode{'Lognormal'} @tab @qcode{'logn'} @tab 2 ## @item @qcode{'Nakagami'} @tab @qcode{'naka'} @tab 2 ## @item @qcode{'Negative Binomial'} @tab @qcode{'nbin'} @tab 2 ## @item @qcode{'Noncentral F-Distribution'} @tab @qcode{'ncf'} @tab 3 ## @item @qcode{'Noncentral Student T'} @tab @qcode{'nct'} @tab 2 ## @item @qcode{'Noncentral Chi-Squared'} @tab @qcode{'ncx2'} @tab 2 ## @item @qcode{'Normal'} @tab @qcode{'norm'} @tab 2 ## @item @qcode{'Poisson'} @tab @qcode{'poiss'} @tab 1 ## @item @qcode{'Rayleigh'} @tab @qcode{'rayl'} @tab 1 ## @item @qcode{'Rician'} @tab @qcode{'rice'} @tab 2 ## @item @qcode{'Student T'} @tab @qcode{'t'} @tab 1 ## @item @qcode{'location-scale T'} @tab @qcode{'tls'} @tab 3 ## @item @qcode{'Triangular'} @tab @qcode{'tri'} @tab 3 ## @item @qcode{'Discrete Uniform'} @tab @qcode{'unid'} @tab 1 ## @item @qcode{'Uniform'} @tab @qcode{'unif'} @tab 2 ## @item @qcode{'Von Mises'} @tab @qcode{'vm'} @tab 2 ## @item @qcode{'Weibull'} @tab @qcode{'wbl'} @tab 2 ## @end multitable ## ## Distribution names are matched ignoring case, spaces and hyphens, so that ## @qcode{'Extreme Value'}, @qcode{'ExtremeValue'} and @qcode{'extreme-value'} ## all select the same distribution, and the same set of names is accepted by ## @code{cdf}, @code{pdf}, @code{icdf}, @code{random}, @code{makedist}, ## @code{fitdist} and @code{mle}. ## ## This accepts more names than MATLAB. MATLAB takes the spaced and the ## squashed spelling but refuses the hyphenated one, so ## @qcode{'Birnbaum-Saunders'} and @qcode{'Log-Logistic'} are errors there; ## Octave has always accepted them and continues to. MATLAB also accepts ## @qcode{'tLocationScale'} in @code{makedist} while refusing it in ## @code{cdf} for the same distribution; Octave accepts it, and ## @qcode{'location-scale T'}, everywhere. Code written against MATLAB's ## names therefore runs unchanged, but code relying on these names will not ## port back. ## ## @seealso{icdf, pdf, cdf, betacdf, binocdf, bisacdf, burrcdf, cauchycdf, ## chi2cdf, evcdf, expcdf, fcdf, gamcdf, geocdf, gevcdf, gpcdf, gumbelcdf, ## hncdf, hygecdf, invgcdf, laplacecdf, logicdf, loglcdf, logncdf, nakacdf, ## nbincdf, ncfcdf, nctcdf, ncx2cdf, normcdf, poisscdf, raylcdf, ricecdf, tcdf, ## tlscdf, tricdf, unidcdf, unifcdf, vmcdf, wblcdf} ## @end deftypefn function p = cdf (name, x, varargin) ## implemented functions persistent allDF = { ... {'beta' , 'Beta'}, @betacdf, 2, ... {'bino' , 'Binomial'}, @binocdf, 2, ... {'bisa' , 'Birnbaum-Saunders'}, @bisacdf, 2, ... {'burr' , 'Burr'}, @burrcdf, 3, ... {'cauchy' , 'Cauchy'}, @cauchycdf, 2, ... {'chi2' , 'Chi-squared'}, @chi2cdf, 1, ... {'ev' , 'Extreme Value'}, @evcdf, 2, ... {'exp' , 'Exponential'}, @expcdf, 1, ... {'f' , 'F-Distribution'}, @fcdf, 2, ... {'gam' , 'Gamma'}, @gamcdf, 2, ... {'geo' , 'Geometric'}, @geocdf, 1, ... {'gev' , 'Generalized Extreme Value'}, @gevcdf, 3, ... {'gp' , 'Generalized Pareto'}, @gpcdf, 3, ... {'gumbel' , 'Gumbel'}, @gumbelcdf, 2, ... {'hn' , 'Half-normal'}, @hncdf, 2, ... {'hyge' , 'Hypergeometric'}, @hygecdf, 3, ... {'invg' , 'Inverse Gaussian'}, @invgcdf, 2, ... {'laplace' , 'Laplace'}, @laplacecdf, 2, ... {'logi' , 'Logistic'}, @logicdf, 2, ... {'logl' , 'Log-Logistic'}, @loglcdf, 2, ... {'logn' , 'Lognormal'}, @logncdf, 2, ... {'naka' , 'Nakagami'}, @nakacdf, 2, ... {'nbin' , 'Negative Binomial'}, @nbincdf, 2, ... {'ncf' , 'Noncentral F-Distribution'}, @ncfcdf, 3, ... {'nct' , 'Noncentral Student T'}, @nctcdf, 2, ... {'ncx2' , 'Noncentral Chi-squared'}, @ncx2cdf, 2, ... {'norm' , 'Normal'}, @normcdf, 2, ... {'poiss' , 'Poisson'}, @poisscdf, 1, ... {'rayl' , 'Rayleigh'}, @raylcdf, 1, ... {'rice' , 'Rician'}, @ricecdf, 2, ... {'t' , 'Student T'}, @tcdf, 1, ... {'tls', 'location-scale T', 'tLocationScale'}, @tlscdf, 3, ... {'tri' , 'Triangular'}, @tricdf, 3, ... {'unid' , 'Discrete Uniform'}, @unidcdf, 1, ... {'unif' , 'Uniform'}, @unifcdf, 2, ... {'vm' , 'Von Mises'}, @vmcdf, 2, ... {'wbl' , 'Weibull'}, @wblcdf, 2}; ## Check NAME being a char string if (! ischar (name)) error ("cdf: distribution NAME must be a char string."); endif ## Check X being numeric and real if (! isnumeric (x)) error ("cdf: X must be numeric."); elseif (! isreal (x)) error ("cdf: values in X must be real."); endif ## Get number of arguments nargs = numel (varargin); ## Get available functions cdfnames = allDF(1:3:end); cdfhandl = allDF(2:3:end); cdf_args = allDF(3:3:end); ## Search for CDF function ## Match on the folded key so that every spelling of a name resolves key = __distname_key__ (name); idx = cellfun (@(x) any (strcmp (key, cellfun (@__distname_key__, x, ... 'UniformOutput', false))), cdfnames); if (any (idx)) if (nargs == cdf_args{idx} + 1) ## Check for "upper" option if (! strcmpi (varargin{nargs}, 'upper')) error ("cdf: invalid argument for upper tail."); else ## Check that all remaining distribution parameters are numeric if (! all (cellfun (@(x)isnumeric (x), (varargin([1:nargs-1]))))) error ("cdf: distribution parameters must be numeric."); endif ## Call appropriate CDF with "upper" flag p = feval (cdfhandl{idx}, x, varargin{:}); endif elseif (nargs == cdf_args{idx}) ## Check that all distribution parameters are numeric if (! all (cellfun (@(x)isnumeric (x), (varargin)))) error ("cdf: distribution parameters must be numeric."); endif ## Call appropriate CDF without "upper" flag p = feval (cdfhandl{idx}, x, varargin{:}); else if (cdf_args{idx} == 1) error ("cdf: %s distribution requires 1 parameter.", name); else error ("cdf: %s distribution requires %d parameters.", ... name, cdf_args{idx}); endif endif else error ("cdf: %s distribution is not implemented in Statistics.", name); endif endfunction ## Test results %!shared x %! x = [1:5]; %!assert_equal (cdf ('Beta', x, 5, 2), betacdf (x, 5, 2)) %!assert_equal (cdf ('beta', x, 5, 2, 'upper'), betacdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Binomial', x, 5, 2), binocdf (x, 5, 2)) %!assert_equal (cdf ('bino', x, 5, 2, 'upper'), binocdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Birnbaum-Saunders', x, 5, 2), bisacdf (x, 5, 2)) %!assert_equal (cdf ('bisa', x, 5, 2, 'upper'), bisacdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Burr', x, 5, 2, 2), burrcdf (x, 5, 2, 2)) %!assert_equal (cdf ('burr', x, 5, 2, 2, 'upper'), burrcdf (x, 5, 2, 2, 'upper')) %!assert_equal (cdf ('Cauchy', x, 5, 2), cauchycdf (x, 5, 2)) %!assert_equal (cdf ('cauchy', x, 5, 2, 'upper'), cauchycdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Chi-squared', x, 5), chi2cdf (x, 5)) %!assert_equal (cdf ('chi2', x, 5, 'upper'), chi2cdf (x, 5, 'upper')) %!assert_equal (cdf ('Extreme Value', x, 5, 2), evcdf (x, 5, 2)) %!assert_equal (cdf ('ev', x, 5, 2, 'upper'), evcdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Exponential', x, 5), expcdf (x, 5)) %!assert_equal (cdf ('exp', x, 5, 'upper'), expcdf (x, 5, 'upper')) %!assert_equal (cdf ('F-Distribution', x, 5, 2), fcdf (x, 5, 2)) %!assert_equal (cdf ('f', x, 5, 2, 'upper'), fcdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Gamma', x, 5, 2), gamcdf (x, 5, 2)) %!assert_equal (cdf ('gam', x, 5, 2, 'upper'), gamcdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Geometric', x, 5), geocdf (x, 5)) %!assert_equal (cdf ('geo', x, 5, 'upper'), geocdf (x, 5, 'upper')) %!assert_equal (cdf ('Generalized Extreme Value', x, 5, 2, 2), gevcdf (x, 5, 2, 2)) %!assert_equal (cdf ('gev', x, 5, 2, 2, 'upper'), gevcdf (x, 5, 2, 2, 'upper')) %!assert_equal (cdf ('Generalized Pareto', x, 5, 2, 2), gpcdf (x, 5, 2, 2)) %!assert_equal (cdf ('gp', x, 5, 2, 2, 'upper'), gpcdf (x, 5, 2, 2, 'upper')) %!assert_equal (cdf ('Gumbel', x, 5, 2), gumbelcdf (x, 5, 2)) %!assert_equal (cdf ('gumbel', x, 5, 2, 'upper'), gumbelcdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Half-normal', x, 5, 2), hncdf (x, 5, 2)) %!assert_equal (cdf ('hn', x, 5, 2, 'upper'), hncdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Hypergeometric', x, 5, 2, 2), hygecdf (x, 5, 2, 2)) %!assert_equal (cdf ('hyge', x, 5, 2, 2, 'upper'), hygecdf (x, 5, 2, 2, 'upper')) %!assert_equal (cdf ('Inverse Gaussian', x, 5, 2), invgcdf (x, 5, 2)) %!assert_equal (cdf ('invg', x, 5, 2, 'upper'), invgcdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Laplace', x, 5, 2), laplacecdf (x, 5, 2)) %!assert_equal (cdf ('laplace', x, 5, 2, 'upper'), laplacecdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Logistic', x, 5, 2), logicdf (x, 5, 2)) %!assert_equal (cdf ('logi', x, 5, 2, 'upper'), logicdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Log-Logistic', x, 5, 2), loglcdf (x, 5, 2)) %!assert_equal (cdf ('logl', x, 5, 2, 'upper'), loglcdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Lognormal', x, 5, 2), logncdf (x, 5, 2)) %!assert_equal (cdf ('logn', x, 5, 2, 'upper'), logncdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Nakagami', x, 5, 2), nakacdf (x, 5, 2)) %!assert_equal (cdf ('naka', x, 5, 2, 'upper'), nakacdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Negative Binomial', x, 5, 2), nbincdf (x, 5, 2)) %!assert_equal (cdf ('nbin', x, 5, 2, 'upper'), nbincdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Noncentral F-Distribution', x, 5, 2, 2), ncfcdf (x, 5, 2, 2)) %!assert_equal (cdf ('ncf', x, 5, 2, 2, 'upper'), ncfcdf (x, 5, 2, 2, 'upper')) %!assert_equal (cdf ('Noncentral Student T', x, 5, 2), nctcdf (x, 5, 2)) %!assert_equal (cdf ('nct', x, 5, 2, 'upper'), nctcdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Noncentral Chi-Squared', x, 5, 2), ncx2cdf (x, 5, 2)) %!assert_equal (cdf ('ncx2', x, 5, 2, 'upper'), ncx2cdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Normal', x, 5, 2), normcdf (x, 5, 2)) %!assert_equal (cdf ('norm', x, 5, 2, 'upper'), normcdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Poisson', x, 5), poisscdf (x, 5)) %!assert_equal (cdf ('poiss', x, 5, 'upper'), poisscdf (x, 5, 'upper')) %!assert_equal (cdf ('Rayleigh', x, 5), raylcdf (x, 5)) %!assert_equal (cdf ('rayl', x, 5, 'upper'), raylcdf (x, 5, 'upper')) %!assert_equal (cdf ('Rician', x, 5, 1), ricecdf (x, 5, 1)) %!assert_equal (cdf ('rice', x, 5, 1, 'upper'), ricecdf (x, 5, 1, 'upper')) %!assert_equal (cdf ('Student T', x, 5), tcdf (x, 5)) %!assert_equal (cdf ('t', x, 5, 'upper'), tcdf (x, 5, 'upper')) %!assert_equal (cdf ('location-scale T', x, 5, 1, 2), tlscdf (x, 5, 1, 2)) %!assert_equal (cdf ('tls', x, 5, 1, 2, 'upper'), tlscdf (x, 5, 1, 2, 'upper')) %!assert_equal (cdf ('Triangular', x, 5, 2, 2), tricdf (x, 5, 2, 2)) %!assert_equal (cdf ('tri', x, 5, 2, 2, 'upper'), tricdf (x, 5, 2, 2, 'upper')) %!assert_equal (cdf ('Discrete Uniform', x, 5), unidcdf (x, 5)) %!assert_equal (cdf ('unid', x, 5, 'upper'), unidcdf (x, 5, 'upper')) %!assert_equal (cdf ('Uniform', x, 5, 2), unifcdf (x, 5, 2)) %!assert_equal (cdf ('unif', x, 5, 2, 'upper'), unifcdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Von Mises', x, 5, 2), vmcdf (x, 5, 2)) %!assert_equal (cdf ('vm', x, 5, 2, 'upper'), vmcdf (x, 5, 2, 'upper')) %!assert_equal (cdf ('Weibull', x, 5, 2), wblcdf (x, 5, 2)) %!assert_equal (cdf ('wbl', x, 5, 2, 'upper'), wblcdf (x, 5, 2, 'upper')) ## Test input validation %!test %! ## Every spelling of a name reaches the same distribution: case, spaces, %! ## hyphens and underscores are all ignored. %! for n = {'Extreme Value', 'ExtremeValue', 'extreme-value', 'EXTREME VALUE'} %! assert_equal (cdf (n{1}, 1, 2, 3), cdf ('ev', 1, 2, 3)); %! endfor %!test %! ## The name that makedist uses is accepted here too, and the reverse %! assert_equal (cdf ('tLocationScale', 1, 2, 3, 4), cdf ('tls', 1, 2, 3, 4)); %!error cdf (1) %!error cdf ({'beta'}) %!error cdf ('beta', {[1 2 3 4 5]}) %!error cdf ('beta', 'text') %!error cdf ('beta', 1+i) %!error ... %! cdf ('Beta', x, 'a', 2) %!error ... %! cdf ('Beta', x, 5, '') %!error ... %! cdf ('Beta', x, 5, {2}) %!error cdf ('chi2', x) %!error cdf ('Beta', x, 5) %!error cdf ('Burr', x, 5) %!error cdf ('Burr', x, 5, 2) statistics-release-1.9.2/inst/Distribution_Wrappers/doc-cache000066400000000000000000000671331524624707500244310ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 8 # name: # type: sq_string # elements: 1 # length: 3 cdf # name: # type: sq_string # elements: 1 # length: 3540 statistics: p = cdf ( name , x , A ) statistics: p = cdf ( name , x , A , B ) statistics: p = cdf ( name , x , A , B , C ) statistics: p = cdf (…, 'upper' ) Return the CDF of a univariate distribution evaluated at x . cdf is a wrapper for the univariate cumulative distribution functions available in the statistics package. See the corresponding functions’ help to learn the signification of the parameters after x . p = cdf ( name , x , A ) returns the CDF for the one-parameter distribution family specified by name and the distribution parameter A , evaluated at the values in x . p = cdf ( name , x , A , B ) returns the CDF for the two-parameter distribution family specified by name and the distribution parameters A and B , evaluated at the values in x . p = cdf ( name , x , A , B , C ) returns the CDF for the three-parameter distribution family specified by name and the distribution parameters A , B , and C , evaluated at the values in x . p = cdf (…, 'upper' ) returns the complement of the CDF using an algorithm that more accurately computes the extreme upper-tail probabilities. 'upper' can follow any of the input arguments in the previous syntaxes. name must be a char string of the name or the abbreviation of the desired cumulative distribution function as listed in the following table. The last column shows the number of required parameters that should be parsed after x to the desired CDF. The optional input argument 'upper' does not count in the required number of parameters. Distribution Name Abbreviation Input Parameters 'Beta' 'beta' 2 'Binomial' 'bino' 2 'Birnbaum-Saunders' 'bisa' 2 'Burr' 'burr' 3 'Cauchy' 'cauchy' 2 'Chi-squared' 'chi2' 1 'Extreme Value' 'ev' 2 'Exponential' 'exp' 1 'F-Distribution' 'f' 2 'Gamma' 'gam' 2 'Geometric' 'geo' 1 'Generalized Extreme Value' 'gev' 3 'Generalized Pareto' 'gp' 3 'Gumbel' 'gumbel' 2 'Half-normal' 'hn' 2 'Hypergeometric' 'hyge' 3 'Inverse Gaussian' 'invg' 2 'Laplace' 'laplace' 2 'Logistic' 'logi' 2 'Log-Logistic' 'logl' 2 'Lognormal' 'logn' 2 'Nakagami' 'naka' 2 'Negative Binomial' 'nbin' 2 'Noncentral F-Distribution' 'ncf' 3 'Noncentral Student T' 'nct' 2 'Noncentral Chi-Squared' 'ncx2' 2 'Normal' 'norm' 2 'Poisson' 'poiss' 1 'Rayleigh' 'rayl' 1 'Rician' 'rice' 2 'Student T' 't' 1 'location-scale T' 'tls' 3 'Triangular' 'tri' 3 'Discrete Uniform' 'unid' 1 'Uniform' 'unif' 2 'Von Mises' 'vm' 2 'Weibull' 'wbl' 2 Distribution names are matched ignoring case, spaces and hyphens, so that 'Extreme Value' , 'ExtremeValue' and 'extreme-value' all select the same distribution, and the same set of names is accepted by cdf , pdf , icdf , random , makedist , fitdist and mle . This accepts more names than MATLAB. MATLAB takes the spaced and the squashed spelling but refuses the hyphenated one, so 'Birnbaum-Saunders' and 'Log-Logistic' are errors there; Octave has always accepted them and continues to. MATLAB also accepts 'tLocationScale' in makedist while refusing it in cdf for the same distribution; Octave accepts it, and 'location-scale T' , everywhere. Code written against MATLAB’s names therefore runs unchanged, but code relying on these names will not port back. See also: icdf, pdf, cdf, betacdf, binocdf, bisacdf, burrcdf, cauchycdf, chi2cdf, evcdf, expcdf, fcdf, gamcdf, geocdf, gevcdf, gpcdf, gumbelcdf, hncdf, hygecdf, invgcdf, laplacecdf, logicdf, loglcdf, logncdf, nakacdf, nbincdf, ncfcdf, nctcdf, ncx2cdf, normcdf, poisscdf, raylcdf, ricecdf, tcdf, tlscdf, tricdf, unidcdf, unifcdf, vmcdf, wblcdf # name: # type: sq_string # elements: 1 # length: 59 Return the CDF of a univariate distribution evaluated at x. # name: # type: sq_string # elements: 1 # length: 7 fitdist # name: # type: sq_string # elements: 1 # length: 3180 statistics: pd = fitdist ( x , distname ) statistics: pd = fitdist ( x , distname , Name , Value ) statistics: [ pdca , gn , gl ] = fitdist ( x , distname , 'By' , groupvar ) statistics: [ pdca , gn , gl ] = fitdist ( x , distname , 'By' , groupvar , Name , Value ) Create probability distribution object. pd = fitdist ( x , distname ) creates a probability distribution object by fitting the distribution specified by distname to the data in vector x . pd = fitdist ( x , distname , Name , Value ) creates the probability distribution object with additional options specified by one or more Name-Value pair arguments listed below. Name Value 'distribution' A character vector specifying the distribution type for which to estimate parameters. 'Ntrials' A scalar specifying the number of trials for the corresponding element of x for the binomial distribution. 'theta' A scalar specifying the location parameter for the generalized Pareto distribution. It defaults to 0, as MATLAB assumes it, and is not estimated. 'mu' A scalar specifying the location parameter for the half-normal distribution. 'censoring' A vector of the same size as x indicating censored data in x . By default it is censor = zeros (size ( x )) . 'frequency' A vector of nonnegative integer counts of the same size as x used as frequency observations. By default it is freq = ones (size ( x )) . 'alpha' A scalar in the range (0,1) , as the significance level for the confidence interval pci . By default it is 0.05 corresponding to 95% confidence intervals. 'options' A structure specifying the control parameters for the iterative algorithm used to compute ML estimates with the fminsearch function. [ pdca , gn , gl ] = fitdist ( x , distname , 'By' , groupvar ) creates probability distribution objects by fitting the distribution specified by distname to the data in x based on the grouping variable groupvar . It returns a cell array of fitted probability distribution object, pdca , a cell array of group labels, gn , and a cell array of grouping variable levels, gl . [ pdca , gn , gl ] = fitdist ( x , distname , 'By' , groupvar , Name , Value ) returns the same output arguments using additional options specified by one or more Name-Value pair arguments mentioned above. Note: calling fitdist without any input arguments will return a cell array of character vectors listing all supported distributions. Distribution names are matched ignoring case, spaces and hyphens, so that 'Extreme Value' , 'ExtremeValue' and 'extreme-value' all select the same distribution, and the same set of names is accepted by cdf , pdf , icdf , random , makedist , fitdist and mle . This accepts more names than MATLAB. MATLAB takes the spaced and the squashed spelling but refuses the hyphenated one, so 'Birnbaum-Saunders' and 'Log-Logistic' are errors there; Octave has always accepted them and continues to. MATLAB also accepts 'tLocationScale' in makedist while refusing it in cdf for the same distribution; Octave accepts it, and 'location-scale T' , everywhere. Code written against MATLAB’s names therefore runs unchanged, but code relying on these names will not port back. See also: makedist # name: # type: sq_string # elements: 1 # length: 39 Create probability distribution object. # name: # type: sq_string # elements: 1 # length: 4 icdf # name: # type: sq_string # elements: 1 # length: 3212 statistics: x = icdf ( name , p , A ) statistics: x = icdf ( name , p , A , B ) statistics: x = icdf ( name , p , A , B , C ) Return the inverse CDF of a univariate distribution evaluated at p . icdf is a wrapper for the univariate quantile distribution functions (iCDF) available in the statistics package. See the corresponding functions’ help to learn the signification of the parameters after p . x = icdf ( name , p , A ) returns the iCDF for the one-parameter distribution family specified by name and the distribution parameter A , evaluated at the values in p . x = icdf ( name , p , A , B ) returns the iCDF for the two-parameter distribution family specified by name and the distribution parameters A and B , evaluated at the values in p . x = icdf ( name , p , A , B , C ) returns the iCDF for the three-parameter distribution family specified by name and the distribution parameters A , B , and C , evaluated at the values in p . name must be a char string of the name or the abbreviation of the desired quantile distribution function as listed in the following table. The last column shows the number of required parameters that should be parsed after x to the desired iCDF. Distribution Name Abbreviation Input Parameters 'Beta' 'beta' 2 'Binomial' 'bino' 2 'Birnbaum-Saunders' 'bisa' 2 'Burr' 'burr' 3 'Cauchy' 'cauchy' 2 'Chi-squared' 'chi2' 1 'Extreme Value' 'ev' 2 'Exponential' 'exp' 1 'F-Distribution' 'f' 2 'Gamma' 'gam' 2 'Geometric' 'geo' 1 'Generalized Extreme Value' 'gev' 3 'Generalized Pareto' 'gp' 3 'Gumbel' 'gumbel' 2 'Half-normal' 'hn' 2 'Hypergeometric' 'hyge' 3 'Inverse Gaussian' 'invg' 2 'Laplace' 'laplace' 2 'Logistic' 'logi' 2 'Log-Logistic' 'logl' 2 'Lognormal' 'logn' 2 'Nakagami' 'naka' 2 'Negative Binomial' 'nbin' 2 'Noncentral F-Distribution' 'ncf' 3 'Noncentral Student T' 'nct' 2 'Noncentral Chi-Squared' 'ncx2' 2 'Normal' 'norm' 2 'Poisson' 'poiss' 1 'Rayleigh' 'rayl' 1 'Rician' 'rice' 2 'Student T' 't' 1 'location-scale T' 'tls' 3 'Triangular' 'tri' 3 'Discrete Uniform' 'unid' 1 'Uniform' 'unif' 2 'Von Mises' 'vm' 2 'Weibull' 'wbl' 2 Distribution names are matched ignoring case, spaces and hyphens, so that 'Extreme Value' , 'ExtremeValue' and 'extreme-value' all select the same distribution, and the same set of names is accepted by cdf , pdf , icdf , random , makedist , fitdist and mle . This accepts more names than MATLAB. MATLAB takes the spaced and the squashed spelling but refuses the hyphenated one, so 'Birnbaum-Saunders' and 'Log-Logistic' are errors there; Octave has always accepted them and continues to. MATLAB also accepts 'tLocationScale' in makedist while refusing it in cdf for the same distribution; Octave accepts it, and 'location-scale T' , everywhere. Code written against MATLAB’s names therefore runs unchanged, but code relying on these names will not port back. See also: icdf, pdf, random, betainv, binoinv, bisainv, burrinv, cauchyinv, chi2inv, evinv, expinv, finv, gaminv, geoinv, gevinv, gpinv, gumbelinv, hninv, hygeinv, invginv, laplaceinv, logiinv, loglinv, logninv, nakainv, nbininv, ncfinv, nctinv, ncx2inv, norminv, poissinv, raylinv, riceinv, tinv, tlsinv, triinv, unidinv, unifinv, vminv, wblinv # name: # type: sq_string # elements: 1 # length: 67 Return the inverse CDF of a univariate distribution evaluated at p. # name: # type: sq_string # elements: 1 # length: 8 makedist # name: # type: sq_string # elements: 1 # length: 1398 statistics: pd = makedist ( distname ) statistics: pd = makedist ( distname , Name , Value ) statistics: list = makedist Create probability distribution object. pd = makedist ( distname ) creates a probability distribution object for the distribution specified in distname , using the default parameter values. pd = makedist ( distname , Name , Value ) also creates a probability distribution object with one or more distribution parameter values specified by Name-Value pair arguments. list = makedist returns a cell array, list , containing a list of the probability distributions that makedist can create. Distribution names are matched ignoring case, spaces and hyphens, so that 'Extreme Value' , 'ExtremeValue' and 'extreme-value' all select the same distribution, and the same set of names is accepted by cdf , pdf , icdf , random , makedist , fitdist and mle . This accepts more names than MATLAB. MATLAB takes the spaced and the squashed spelling but refuses the hyphenated one, so 'Birnbaum-Saunders' and 'Log-Logistic' are errors there; Octave has always accepted them and continues to. MATLAB also accepts 'tLocationScale' in makedist while refusing it in cdf for the same distribution; Octave accepts it, and 'location-scale T' , everywhere. Code written against MATLAB’s names therefore runs unchanged, but code relying on these names will not port back. See also: fitdist # name: # type: sq_string # elements: 1 # length: 39 Create probability distribution object. # name: # type: sq_string # elements: 1 # length: 3 mle # name: # type: sq_string # elements: 1 # length: 4370 statistics: phat = mle ( x ) statistics: phat = mle ( x , Name , Value ) statistics: [ phat , pci ] = mle (…) Compute maximum likelihood estimates. phat = mle ( x ) returns the maximum likelihood estimates (MLEs) for the parameters of a normal distribution using the sample data in x , which must be a numeric vector of real values. phat = mle ( x , Name , Value ) returns the MLEs with additional options specified by Name-Value pair arguments listed below. Name Value 'distribution' A character vector specifying the distribution type for which to estimate parameters. 'Ntrials' A scalar specifying the number of trials for the corresponding element of x for the binomial distribution. 'theta' A scalar specifying the location parameter for the generalized Pareto distribution. It defaults to 0 and is not estimated: the data is shifted by it and only k and sigma are returned. 'mu' A scalar specifying the location parameter for the half-normal distribution. 'censoring' A vector of the same size as x indicating censored data in x . By default it is censor = zeros (size ( x )) . 'frequency' A vector of nonnegative integer counts of the same size as x used as frequency observations. By default it is freq = ones (size ( x )) . 'alpha' A scalar in the range (0,1) , as the significance level for the confidence interval pci . By default it is 0.05 corresponding to 95% confidence intervals. 'options' A structure specifying the control parameters for the iterative algorithm used to compute ML estimates with the fminsearch function. 'pdf' A function handle @( data , p1 , p2 , …) to the probability density of a custom distribution, whose parameters are then estimated by maximum likelihood. Requires 'start' . It is mutually exclusive with 'distribution' and with 'logpdf' / 'nloglf' . 'cdf' A function handle to the cumulative distribution function of the custom distribution, with the same calling convention as 'pdf' . Required together with 'pdf' for censored or truncated data. 'logpdf' A function handle to the log probability density of a custom distribution, with the same calling convention as 'pdf' . Requires 'start' . 'logsf' A function handle to the log survivor function log (1 - cdf) of the custom distribution, with the same calling convention as 'pdf' . Required together with 'logpdf' for censored data. 'nloglf' A function handle @( params , data , cens , freq ) returning the scalar negative log-likelihood of a custom distribution. Requires 'start' . 'start' A vector of initial parameter values for a custom-distribution fit. Required with 'pdf' , 'logpdf' , or 'nloglf' . 'lowerbound' A scalar or vector of lower bounds for the custom-distribution parameters. By default they are unbounded below. 'upperbound' A scalar or vector of upper bounds for the custom-distribution parameters. By default they are unbounded above. 'truncationbounds' A two-element vector [L U] giving the truncation interval of a custom distribution. Requires a 'cdf' function. 'optimfun' The optimizer for a custom-distribution fit. Only 'fminsearch' is supported; bounded fits are handled by internal reparameterization of the constrained parameters. When a custom distribution is specified through 'pdf' , 'logpdf' , or 'nloglf' , the parameters are estimated by maximizing the likelihood with fminsearch , and the second output pci gives asymptotic normal (Wald) confidence intervals computed from the observed Fisher information at phat (see mlecov ). Bounded parameters are estimated on an internally reparameterized unconstrained scale. Distribution names are matched ignoring case, spaces and hyphens, so that 'Extreme Value' , 'ExtremeValue' and 'extreme-value' all select the same distribution, and the same set of names is accepted by cdf , pdf , icdf , random , makedist , fitdist and mle . This accepts more names than MATLAB. MATLAB takes the spaced and the squashed spelling but refuses the hyphenated one, so 'Birnbaum-Saunders' and 'Log-Logistic' are errors there; Octave has always accepted them and continues to. MATLAB also accepts 'tLocationScale' in makedist while refusing it in cdf for the same distribution; Octave accepts it, and 'location-scale T' , everywhere. Code written against MATLAB’s names therefore runs unchanged, but code relying on these names will not port back. See also: mlecov, fitdist, makedist # name: # type: sq_string # elements: 1 # length: 37 Compute maximum likelihood estimates. # name: # type: sq_string # elements: 1 # length: 6 mlecov # name: # type: sq_string # elements: 1 # length: 3720 statistics: acov = mlecov ( params , data , Name , Value ) Asymptotic covariance matrix of maximum likelihood estimators. acov = mlecov ( params , data , …) returns an approximation to the asymptotic covariance matrix of the maximum likelihood estimators of the parameters of a distribution, evaluated at the parameter values in params for the sample data in data . params is a numeric vector of parameter values (typically the estimates returned by mle or fitdist ) and data is a numeric vector of the sample observations. acov is a p×p matrix, where p = numel ( params ) . The distribution is not identified by name; instead it is supplied through Name-Value paired arguments that give function handles to its density, its log density, or its negative log-likelihood. Exactly one of the following three arguments must be specified: Name Value 'pdf' A function handle, f ( data , p1 , p2 , …) , that accepts the sample data as its first argument and the distribution parameters as subsequent scalar arguments, and returns a vector of probability density values, one per observation. 'logpdf' A function handle, f ( data , p1 , p2 , …) , with the same calling convention as 'pdf' but returning the logarithm of the density. 'nloglf' A function handle, nll ( params , data , cens , freq ) , that returns the scalar negative log-likelihood of the whole sample. It receives the current parameter vector, the data, the censoring vector, and the frequency vector, and is responsible for incorporating censoring and frequency itself. 'cdf' A function handle to the cumulative distribution function, with the same calling convention as 'pdf' . It is required together with 'pdf' when the data are censored, so that censored observations can contribute their survival probability. 'logsf' A function handle to the logarithm of the survivor function log (1 - cdf) , with the same calling convention as 'pdf' . It is required together with 'logpdf' when the data are censored. 'Censoring' A vector of the same size as data indicating censored observations (nonzero for right-censored). By default no observation is censored. 'Frequency' A vector of nonnegative integer counts of the same size as data , giving the number of times each observation was observed. By default it is ones (size ( data )) . 'Options' A structure that may contain a 'DerivStep' field specifying the relative finite-difference step used to approximate the Hessian (a positive scalar or a vector the same size as params ). The default step is eps ^ (1/4) . Computation and numerical behavior. mlecov approximates the covariance matrix as the inverse of the observed Fisher information, that is, the inverse of the Hessian of the aggregate negative log-likelihood of the sample, evaluated by central finite differences at params . The covariance is computed at the supplied params ; mlecov does not refit the parameters, so params should be the maximum likelihood estimates for the result to be meaningful. Whichever of 'pdf' , 'logpdf' , or 'nloglf' is supplied, the Hessian is always formed by differencing the same aggregate negative log-likelihood rather than by differentiating the density itself. This makes the three input forms consistent with one another and is numerically far more stable than differentiating a density; as a consequence acov may differ from other implementations (including MATLAB) in ill-conditioned cases where those differentiate the density directly and return unreliable values or NaN . If the computed Hessian is not positive definite (for example when params is not at a likelihood maximum), a warning is issued and acov is returned as an all- NaN matrix. See also: mle, fitdist, makedist # name: # type: sq_string # elements: 1 # length: 62 Asymptotic covariance matrix of maximum likelihood estimators. # name: # type: sq_string # elements: 1 # length: 3 pdf # name: # type: sq_string # elements: 1 # length: 3190 statistics: y = pdf ( name , x , A ) statistics: y = pdf ( name , x , A , B ) statistics: y = pdf ( name , x , A , B , C ) Return the PDF of a univariate distribution evaluated at x . pdf is a wrapper for the univariate cumulative distribution functions available in the statistics package. See the corresponding functions’ help to learn the signification of the parameters after x . y = pdf ( name , x , A ) returns the CDF for the one-parameter distribution family specified by name and the distribution parameter A , evaluated at the values in x . y = pdf ( name , x , A , B ) returns the CDF for the two-parameter distribution family specified by name and the distribution parameters A and B , evaluated at the values in x . y = pdf ( name , x , A , B , C ) returns the CDF for the three-parameter distribution family specified by name and the distribution parameters A , B , and C , evaluated at the values in x . name must be a char string of the name or the abbreviation of the desired cumulative distribution function as listed in the following table. The last column shows the number of required parameters that should be parsed after x to the desired PDF. Distribution Name Abbreviation Input Parameters 'Beta' 'beta' 2 'Binomial' 'bino' 2 'Birnbaum-Saunders' 'bisa' 2 'Burr' 'burr' 3 'Cauchy' 'cauchy' 2 'Chi-squared' 'chi2' 1 'Extreme Value' 'ev' 2 'Exponential' 'exp' 1 'F-Distribution' 'f' 2 'Gamma' 'gam' 2 'Geometric' 'geo' 1 'Generalized Extreme Value' 'gev' 3 'Generalized Pareto' 'gp' 3 'Gumbel' 'gumbel' 2 'Half-normal' 'hn' 2 'Hypergeometric' 'hyge' 3 'Inverse Gaussian' 'invg' 2 'Laplace' 'laplace' 2 'Logistic' 'logi' 2 'Log-Logistic' 'logl' 2 'Lognormal' 'logn' 2 'Nakagami' 'naka' 2 'Negative Binomial' 'nbin' 2 'Noncentral F-Distribution' 'ncf' 3 'Noncentral Student T' 'nct' 2 'Noncentral Chi-Squared' 'ncx2' 2 'Normal' 'norm' 2 'Poisson' 'poiss' 1 'Rayleigh' 'rayl' 1 'Rician' 'rice' 2 'Student T' 't' 1 'location-scale T' 'tls' 3 'Triangular' 'tri' 3 'Discrete Uniform' 'unid' 1 'Uniform' 'unif' 2 'Von Mises' 'vm' 2 'Weibull' 'wbl' 2 Distribution names are matched ignoring case, spaces and hyphens, so that 'Extreme Value' , 'ExtremeValue' and 'extreme-value' all select the same distribution, and the same set of names is accepted by cdf , pdf , icdf , random , makedist , fitdist and mle . This accepts more names than MATLAB. MATLAB takes the spaced and the squashed spelling but refuses the hyphenated one, so 'Birnbaum-Saunders' and 'Log-Logistic' are errors there; Octave has always accepted them and continues to. MATLAB also accepts 'tLocationScale' in makedist while refusing it in cdf for the same distribution; Octave accepts it, and 'location-scale T' , everywhere. Code written against MATLAB’s names therefore runs unchanged, but code relying on these names will not port back. See also: cdf, icdf, random, betapdf, binopdf, bisapdf, burrpdf, cauchypdf, chi2pdf, evpdf, exppdf, fpdf, gampdf, geopdf, gevpdf, gppdf, gumbelpdf, hnpdf, hygepdf, invgpdf, laplacepdf, logipdf, loglpdf, lognpdf, nakapdf, nbinpdf, ncfpdf, nctpdf, ncx2pdf, normpdf, poisspdf, raylpdf, ricepdf, tpdf, tlspdf, tripdf, unidpdf, unifpdf, vmpdf, wblpdf # name: # type: sq_string # elements: 1 # length: 59 Return the PDF of a univariate distribution evaluated at x. # name: # type: sq_string # elements: 1 # length: 6 random # name: # type: sq_string # elements: 1 # length: 3359 statistics: r = random ( name , A ) statistics: r = random ( name , A , B ) statistics: r = random ( name , A , B , C ) statistics: r = random ( name , …, rows , cols ) statistics: r = random ( name , …, rows , cols , …) statistics: r = random ( name , …, [ sz ]) Random arrays from a given one-, two-, or three-parameter distribution. The variable name must be a string with the name of the distribution to sample from. If this distribution is a one-parameter distribution, A must be supplied, if it is a two-parameter distribution, B must also be supplied, and if it is a three-parameter distribution, C must also be supplied. Any arguments following the distribution parameters will determine the size of the result. When called with a single size argument, return a square matrix with the dimension specified. When called with more than one scalar argument the first two arguments are taken as the number of rows and columns and any further arguments specify additional matrix dimensions. The size may also be specified with a vector of dimensions sz . name must be a char string of the name or the abbreviation of the desired probability distribution function as listed in the following table. The last column shows the required number of parameters that must be passed to the desired *rnd distribution function. Distribution Name Abbreviation Input Parameters 'Beta' 'beta' 2 'Binomial' 'bino' 2 'Birnbaum-Saunders' 'bisa' 2 'Burr' 'burr' 3 'Cauchy' 'cauchy' 2 'Chi-squared' 'chi2' 1 'Extreme Value' 'ev' 2 'Exponential' 'exp' 1 'F-Distribution' 'f' 2 'Gamma' 'gam' 2 'Geometric' 'geo' 1 'Generalized Extreme Value' 'gev' 3 'Generalized Pareto' 'gp' 3 'Gumbel' 'gumbel' 2 'Half-normal' 'hn' 2 'Hypergeometric' 'hyge' 3 'Inverse Gaussian' 'invg' 2 'Laplace' 'laplace' 2 'Logistic' 'logi' 2 'Log-Logistic' 'logl' 2 'Lognormal' 'logn' 2 'Nakagami' 'naka' 2 'Negative Binomial' 'nbin' 2 'Noncentral F-Distribution' 'ncf' 3 'Noncentral Student T' 'nct' 2 'Noncentral Chi-Squared' 'ncx2' 2 'Normal' 'norm' 2 'Poisson' 'poiss' 1 'Rayleigh' 'rayl' 1 'Rician' 'rice' 2 'Student T' 't' 1 'location-scale T' 'tls' 3 'Triangular' 'tri' 3 'Discrete Uniform' 'unid' 1 'Uniform' 'unif' 2 'Von Mises' 'vm' 2 'Weibull' 'wbl' 2 Distribution names are matched ignoring case, spaces and hyphens, so that 'Extreme Value' , 'ExtremeValue' and 'extreme-value' all select the same distribution, and the same set of names is accepted by cdf , pdf , icdf , random , makedist , fitdist and mle . This accepts more names than MATLAB. MATLAB takes the spaced and the squashed spelling but refuses the hyphenated one, so 'Birnbaum-Saunders' and 'Log-Logistic' are errors there; Octave has always accepted them and continues to. MATLAB also accepts 'tLocationScale' in makedist while refusing it in cdf for the same distribution; Octave accepts it, and 'location-scale T' , everywhere. Code written against MATLAB’s names therefore runs unchanged, but code relying on these names will not port back. See also: cdf, icdf, pdf, betarnd, binornd, bisarnd, burrrnd, cauchyrnd, chi2rnd, evrnd, exprnd, frnd, gamrnd, geornd, gevrnd, gprnd, gumbelrnd, hnrnd, hygernd, invgrnd, laplacernd, logirnd, loglrnd, lognrnd, nakarnd, nbinrnd, ncfrnd, nctrnd, ncx2rnd, normrnd, poissrnd, raylrnd, ricernd, trnd, tlsrnd, trirnd, unidrnd, unifrnd, vmrnd, wblrnd # name: # type: sq_string # elements: 1 # length: 71 Random arrays from a given one-, two-, or three-parameter distribution. statistics-release-1.9.2/inst/Distribution_Wrappers/fitdist.m000066400000000000000000001415711524624707500245230ustar00rootroot00000000000000## Copyright (C) 2024-2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{pd} =} fitdist (@var{x}, @var{distname}) ## @deftypefnx {statistics} {@var{pd} =} fitdist (@var{x}, @var{distname}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{pdca}, @var{gn}, @var{gl}] =} fitdist (@var{x}, @var{distname}, @qcode{'By'}, @var{groupvar}) ## @deftypefnx {statistics} {[@var{pdca}, @var{gn}, @var{gl}] =} fitdist (@var{x}, @var{distname}, @qcode{'By'}, @var{groupvar}, @var{Name}, @var{Value}) ## ## Create probability distribution object. ## ## @code{@var{pd} = fitdist (@var{x}, @var{distname})} creates a probability ## distribution object by fitting the distribution specified by ## @var{distname} to the data in vector @var{x}. ## ## @code{@var{pd} = fitdist (@var{x}, @var{distname}, @var{Name}, @var{Value})} ## creates the probability distribution object with additional options specified ## by one or more @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'distribution'} @tab A character vector specifying the ## distribution type for which to estimate parameters. ## ## @item @qcode{'Ntrials'} @tab A scalar specifying the number of trials ## for the corresponding element of @var{x} for the binomial distribution. ## ## @item @qcode{'theta'} @tab A scalar specifying the location parameter ## for the generalized Pareto distribution. It defaults to 0, as MATLAB ## assumes it, and is not estimated. ## ## @item @qcode{'mu'} @tab A scalar specifying the location parameter ## for the half-normal distribution. ## ## @item @qcode{'censoring'} @tab A vector of the same size as @var{x} ## indicating censored data in @var{x}. By default it is ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @item @qcode{'frequency'} @tab A vector of nonnegative integer counts of ## the same size as @var{x} used as frequency observations. By default it is ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @item @qcode{'alpha'} @tab A scalar in the range @math{(0,1)}, as the ## significance level for the confidence interval @var{pci}. By default it is ## 0.05 corresponding to 95% confidence intervals. ## ## @item @qcode{'options'} @tab A structure specifying the control ## parameters for the iterative algorithm used to compute ML estimates with the ## @code{fminsearch} function. ## @end multitable ## ## @code{[@var{pdca}, @var{gn}, @var{gl}] = fitdist (@var{x}, @var{distname}, ## @qcode{'By'}, @var{groupvar})} creates probability distribution objects by ## fitting the distribution specified by @var{distname} to the data in @var{x} ## based on the grouping variable @var{groupvar}. It returns a cell array of ## fitted probability distribution object, @var{pdca}, a cell array of group ## labels, @var{gn}, and a cell array of grouping variable levels, @var{gl}. ## ## @code{[@var{pdca}, @var{gn}, @var{gl}] = fitdist (@var{x}, @var{distname}, ## @qcode{'By'}, @var{groupvar}, @var{Name}, @var{Value})} returns the same ## output arguments using additional options specified by one or more ## @qcode{Name-Value} pair arguments mentioned above. ## ## Note: calling @code{fitdist} without any input arguments will return a cell ## array of character vectors listing all supported distributions. ## ## Distribution names are matched ignoring case, spaces and hyphens, so that ## @qcode{'Extreme Value'}, @qcode{'ExtremeValue'} and @qcode{'extreme-value'} ## all select the same distribution, and the same set of names is accepted by ## @code{cdf}, @code{pdf}, @code{icdf}, @code{random}, @code{makedist}, ## @code{fitdist} and @code{mle}. ## ## This accepts more names than MATLAB. MATLAB takes the spaced and the ## squashed spelling but refuses the hyphenated one, so ## @qcode{'Birnbaum-Saunders'} and @qcode{'Log-Logistic'} are errors there; ## Octave has always accepted them and continues to. MATLAB also accepts ## @qcode{'tLocationScale'} in @code{makedist} while refusing it in ## @code{cdf} for the same distribution; Octave accepts it, and ## @qcode{'location-scale T'}, everywhere. Code written against MATLAB's ## names therefore runs unchanged, but code relying on these names will not ## port back. ## ## @seealso{makedist} ## @end deftypefn function [varargout] = fitdist (varargin) ## Add list of supported probability distribution objects PDO = {'Beta'; 'Binomial'; 'BirnbaumSaunders'; 'Burr'; 'Exponential'; ... 'ExtremeValue'; 'Gamma'; 'GeneralizedExtremeValue'; ... 'GeneralizedPareto'; 'HalfNormal'; 'InverseGaussian'; ... 'Kernel'; 'Logistic'; 'Loglogistic'; 'Lognormal'; 'Nakagami'; ... 'NegativeBinomial'; 'Normal'; 'Poisson'; 'Rayleigh'; 'Rician'; ... 'Stable'; 'tLocationScale'; 'Weibull'}; ABBR = {'bisa', 'ev', 'gev', 'gp', 'hn', 'invg', 'nbin', 'tls'}; ## Check for input arguments if (nargin == 0) varargout{1} = PDO; return elseif (nargin == 1) error ("fitdist: DISTNAME is required."); else x = varargin{1}; distname = varargin{2}; varargin([1:2]) = []; endif ## Check distribution name if (! (ischar (distname) && size (distname, 1) == 1)) error ("fitdist: DISTNAME must be a character vector."); elseif (! (any (strcmpi (distname, PDO)) || any (strcmpi (distname, ABBR)))) error ("fitdist: unrecognized distribution name."); endif ## Check data in X being a real vector if (! (isvector (x) && isnumeric (x) && isreal (x))) error ("fitdist: X must be a numeric vector of real values."); endif ## Add defaults groupvar = []; censor = zeros (size (x)); freq = ones (size (x)); alpha = 0.05; ntrials = 1; mu = 0; theta = 0; kernel = 'normal'; ksupport = 'unbounded'; kwidth = []; options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; ## Parse extra arguments if (mod (numel (varargin), 2) != 0) error ("fitdist: optional arguments must be in NAME-VALUE pairs."); endif while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'by' groupvar = varargin{2}; if (! isequal (size (x), size (groupvar)) && ! isempty (groupvar)) error (strcat ("fitdist: GROUPVAR argument must have the same", ... " size as the input data in X.")); endif case 'censoring' censor = varargin{2}; if (! isequal (size (x), size (censor))) error (strcat ("fitdist: 'censoring' argument must have the", ... " same size as the input data in X.")); endif case 'frequency' freq = varargin{2}; if (! isequal (size (x), size (freq))) error (strcat ("fitdist: 'frequency' argument must have the", ... " same size as the input data in X.")); endif if (any (freq != round (freq)) || any (freq < 0)) error (strcat ("fitdist: 'frequency' argument must contain", ... " non-negative integer values.")); endif case 'alpha' alpha = varargin{2}; if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("fitdist: invalid value for 'alpha' argument."); endif case 'ntrials' ntrials = varargin{2}; if (! (isscalar (ntrials) && isreal (ntrials) && ntrials > 0 && fix (ntrials) == ntrials)) error (strcat ("fitdist: 'ntrials' argument must be a positive", ... " integer scalar value.")); endif case {'mu'} mu = varargin{2}; case {'theta'} theta = varargin{2}; case 'options' options = varargin{2}; if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("fitdist: 'options' argument must be a", ... " structure compatible for 'fminsearch'.")); endif case 'kernel' kernel = varargin{2}; case 'support' ksupport = varargin{2}; case 'width' kwidth = varargin{2}; otherwise error ("fitdist: unknown parameter name."); endswitch varargin([1:2]) = []; endwhile ## Handle missing values is_nan = isnan (x) | isnan (censor) | isnan (freq); if (any (is_nan)) x(is_nan) = []; censor(is_nan) = []; freq(is_nan) = []; endif if (isempty (x)) error ("fitdist: no data in X to fit a '%s' distribution.", distname); endif ## Handle group variable if (isempty (groupvar) && nargout > 1) error ("fitdist: must define GROUPVAR for more than one output arguments."); endif if (! isempty (groupvar)) [g, gn, gl] = grp2idx (groupvar); groups = numel (gn); if (any (is_nan)) groupvar(is_nan) = []; g = grp2idx (groupvar); endif endif ## Warning message for no group data msg = 'fitdist: no data in group ''%s'' to fit a ''%s'' distribution.'; ## Switch to selected distribution switch (__distname_key__ (distname)) case 'beta' if (isempty (groupvar)) varargout{1} = prob.BetaDistribution.fit (x, alpha, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.BetaDistribution.fit (x_i, alpha, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'binomial' if (any (x > ntrials)) error ("fitdist: invalid NTRIALS value for Binomial distribution.") endif if (isempty (groupvar)) varargout{1} = prob.BinomialDistribution.fit (x, ntrials, alpha, freq); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.BinomialDistribution.fit (x_i, ntrials, alpha, f_i); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case {'birnbaumsaunders', 'bisa'} if (isempty (groupvar)) varargout{1} = prob.BirnbaumSaundersDistribution.fit ... (x, alpha, censor, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.BirnbaumSaundersDistribution.fit ... (x_i, alpha, c_i, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'burr' if (isempty (groupvar)) varargout{1} = prob.BurrDistribution.fit (x, alpha, censor, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.BurrDistribution.fit (x_i, alpha, c_i, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'exponential' if (isempty (groupvar)) varargout{1} = prob.ExponentialDistribution.fit (x, alpha, censor, freq); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.ExponentialDistribution.fit (x_i, alpha, c_i, f_i); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case {'extremevalue', 'ev'} if (isempty (groupvar)) varargout{1} = prob.ExtremeValueDistribution.fit ... (x, alpha, censor, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.ExtremeValueDistribution.fit (x_i, alpha, c_i, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'gamma' if (isempty (groupvar)) varargout{1} = prob.GammaDistribution.fit (x, alpha, censor, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.GammaDistribution.fit (x_i, alpha, c_i, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case {'generalizedextremevalue', 'gev'} if (isempty (groupvar)) varargout{1} = prob.GeneralizedExtremeValueDistribution.fit ... (x, alpha, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.GeneralizedExtremeValueDistribution.fit ... (x_i, alpha, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case {'generalizedpareto', 'gp'} if (any (x - theta < 0)) error (strcat ("fitdist: invalid THETA value for generalized", ... " Pareto distribution.")); endif if (isempty (groupvar)) varargout{1} = prob.GeneralizedParetoDistribution.fit ... (x, theta, alpha, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.GeneralizedParetoDistribution.fit ... (x_i, theta, alpha, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case {'halfnormal', 'hn'} if (any (x - mu < 0)) error ("fitdist: invalid MU value for half-normal distribution."); endif if (isempty (groupvar)) varargout{1} = prob.HalfNormalDistribution.fit (x, mu, alpha, freq); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.HalfNormalDistribution.fit (x_i, mu, alpha, f_i); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case {'inversegaussian', 'invg'} if (isempty (groupvar)) varargout{1} = prob.InverseGaussianDistribution.fit ... (x, alpha, censor, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.InverseGaussianDistribution.fit ... (x_i, alpha, c_i, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'kernel' if (any (censor != 0)) error ("fitdist: censoring is not supported for a 'Kernel' distribution."); endif if (isempty (groupvar)) varargout{1} = prob.KernelDistribution.fit ... (x, kernel, ksupport, kwidth, freq); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.KernelDistribution.fit ... (x_i, kernel, ksupport, kwidth, f_i); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'logistic' if (isempty (groupvar)) varargout{1} = prob.LogisticDistribution.fit ... (x, alpha, censor, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.LogisticDistribution.fit (x_i, alpha, c_i, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'loglogistic' if (isempty (groupvar)) varargout{1} = prob.LoglogisticDistribution.fit ... (x, alpha, censor, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.LoglogisticDistribution.fit (x_i, alpha, c_i, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'lognormal' if (isempty (groupvar)) varargout{1} = prob.LognormalDistribution.fit ... (x, alpha, censor, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.LognormalDistribution.fit (x_i, alpha, c_i, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'nakagami' if (isempty (groupvar)) varargout{1} = prob.NakagamiDistribution.fit ... (x, alpha, censor, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.NakagamiDistribution.fit (x_i, alpha, c_i, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case {'negativebinomial', 'nbin'} if (isempty (groupvar)) varargout{1} = prob.NegativeBinomialDistribution.fit ... (x, alpha, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.NegativeBinomialDistribution.fit (x_i, alpha, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'normal' if (isempty (groupvar)) varargout{1} = prob.NormalDistribution.fit (x, alpha, censor, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.NormalDistribution.fit (x_i, alpha, c_i, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'poisson' if (isempty (groupvar)) varargout{1} = prob.PoissonDistribution.fit (x, alpha, freq); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.PoissonDistribution.fit (x_i, alpha, f_i); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'rayleigh' if (isempty (groupvar)) varargout{1} = prob.RayleighDistribution.fit (x, alpha, censor, freq); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.RayleighDistribution.fit (x_i, alpha, c_i, f_i); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'rician' if (isempty (groupvar)) varargout{1} = prob.RicianDistribution.fit (x, alpha, censor, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.RicianDistribution.fit (x_i, alpha, c_i, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'stable' if (any (censor != 0)) error ("fitdist: censoring is not supported for a 'Stable' distribution."); endif if (isempty (groupvar)) varargout{1} = prob.StableDistribution.fit (x, alpha, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.StableDistribution.fit (x_i, alpha, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case {'tlocationscale', 'tls'} if (isempty (groupvar)) varargout{1} = prob.tLocationScaleDistribution.fit ... (x, alpha, censor, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.tLocationScaleDistribution.fit ... (x_i, alpha, c_i, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif case 'weibull' if (isempty (groupvar)) varargout{1} = prob.WeibullDistribution.fit (x, alpha, censor, freq, options); else pd = cell (1, groups); for i = 1:groups x_i = x(g == i); c_i = censor(g == i); f_i = freq(g == i); if (isempty (x_i)) pd{i} = []; warning (msg, gn{i}, distname); else pd{i} = prob.WeibullDistribution.fit (x_i, alpha, c_i, f_i, options); endif endfor varargout{1} = pd; varargout{2} = gn; varargout{3} = gl; endif endswitch endfunction ## Test output %!test ## fitdist returns a fitted prob.KernelDistribution object %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]'; %! pd = fitdist (x, 'Kernel'); %! assert_equal (class (pd), 'prob.KernelDistribution'); %! assert_equal (pd.Kernel, 'normal'); %! assert_equal (pd.Bandwidth, 0.639566, 1e-4); %! assert_equal (pd.InputData.data, x); %!test ## grouped kernel fit returns a cell of prob.KernelDistribution objects %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]'; %! [pd, gn] = fitdist (x, 'Kernel', 'By', [ones(8, 1); 2*ones(7, 1)]); %! assert_equal (numel (pd), 2); %! assert_equal (class (pd{1}), 'prob.KernelDistribution'); %! assert_equal (class (pd{2}), 'prob.KernelDistribution'); %!test %! x = betarnd (1, 1, 100, 1); %! pd = fitdist (x, 'Beta'); %! [phat, pci] = betafit (x); %! assert_equal ([pd.a, pd.b], phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = betarnd (1, 1, 100, 1); %! x2 = betarnd (5, 2, 100, 1); %! pd = fitdist ([x1; x2], 'Beta', 'By', [ones(100, 1); 2*ones(100, 1)]); %! [phat, pci] = betafit (x1); %! assert_equal ([pd{1}.a, pd{1}.b], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = betafit (x2); %! assert_equal ([pd{2}.a, pd{2}.b], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([betarnd(1, 1, 100, 1); nan(100, 1)], 'Beta', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! N = 1; %! x = binornd (N, 0.5, 100, 1); %! pd = fitdist (x, 'binomial'); %! [phat, pci] = binofit (sum (x), numel (x)); %! assert_equal ([pd.N, pd.p], [N, phat]); %! assert_equal (paramci (pd), [N, pci(1); N, pci(2)]); %!test %! N = 3; %! x = binornd (N, 0.4, 100, 1); %! pd = fitdist (x, 'binomial', 'ntrials', N); %! [phat, pci] = binofit (sum (x), numel (x) * N); %! assert_equal ([pd.N, pd.p], [N, phat]); %! assert_equal (paramci (pd), [N, pci(1); N, pci(2)]); %!test %! N = 1; %! x1 = binornd (N, 0.5, 100, 1); %! x2 = binornd (N, 0.7, 100, 1); %! pd = fitdist ([x1; x2], 'binomial', 'By', [ones(100, 1); 2*ones(100, 1)]); %! [phat, pci] = binofit (sum (x1), numel (x1)); %! assert_equal ([pd{1}.N, pd{1}.p], [N, phat]); %! assert_equal (paramci (pd{1}), [N, pci(1); N, pci(2)]); %! [phat, pci] = binofit (sum (x2), numel (x2)); %! assert_equal ([pd{2}.N, pd{2}.p], [N, phat]); %! assert_equal (paramci (pd{2}), [N, pci(1); N, pci(2)]); %!warning ... %! fitdist ([binornd(1, 0.5, 100, 1); nan(100, 1)], 'binomial', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! N = 5; %! x1 = binornd (N, 0.5, 100, 1); %! x2 = binornd (N, 0.8, 100, 1); %! pd = fitdist ([x1; x2], 'binomial', 'ntrials', N, ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %! [phat, pci] = binofit (sum (x1), numel (x1) * N); %! assert_equal ([pd{1}.N, pd{1}.p], [N, phat]); %! assert_equal (paramci (pd{1}), [N, pci(1); N, pci(2)]); %! [phat, pci] = binofit (sum (x2), numel (x2) * N); %! assert_equal ([pd{2}.N, pd{2}.p], [N, phat]); %! assert_equal (paramci (pd{2}), [N, pci(1); N, pci(2)]); %!warning ... %! fitdist ([binornd(5, 0.5, 100, 1); nan(100, 1)], 'binomial', 'ntrials', 5, ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = bisarnd (1, 1, 100, 1); %! pd = fitdist (x, 'BirnbaumSaunders'); %! [phat, pci] = bisafit (x); %! assert_equal ([pd.beta, pd.gamma], phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = bisarnd (1, 1, 100, 1); %! x2 = bisarnd (5, 2, 100, 1); %! pd = fitdist ([x1; x2], 'bisa', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = bisafit (x1); %! assert_equal ([pd{1}.beta, pd{1}.gamma], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = bisafit (x2); %! assert_equal ([pd{2}.beta, pd{2}.gamma], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([bisarnd(1, 1, 100, 1); nan(100, 1)], 'bisa', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = burrrnd (1, 2, 1, 100, 1); %! pd = fitdist (x, 'Burr'); %! [phat, pci] = burrfit (x); %! assert_equal ([pd.alpha, pd.c, pd.k], phat); %! assert_equal (paramci (pd), pci); %!test %! rand ('seed', 4); # for reproducibility %! x1 = burrrnd (1, 2, 1, 100, 1); %! rand ('seed', 3); # for reproducibility %! x2 = burrrnd (1, 0.5, 2, 100, 1); %! pd = fitdist ([x1; x2], 'burr', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = burrfit (x1); %! assert_equal ([pd{1}.alpha, pd{1}.c, pd{1}.k], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = burrfit (x2); %! assert_equal ([pd{2}.alpha, pd{2}.c, pd{2}.k], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([burrrnd(1, 2, 1, 100, 1); nan(100, 1)], 'burr', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = exprnd (1, 100, 1); %! pd = fitdist (x, 'exponential'); %! [muhat, muci] = expfit (x); %! assert_equal ([pd.mu], muhat); %! assert_equal (paramci (pd), muci); %!test %! x1 = exprnd (1, 100, 1); %! x2 = exprnd (5, 100, 1); %! pd = fitdist ([x1; x2], 'exponential', 'By', [ones(100,1); 2*ones(100,1)]); %! [muhat, muci] = expfit (x1); %! assert_equal ([pd{1}.mu], muhat); %! assert_equal (paramci (pd{1}), muci); %! [muhat, muci] = expfit (x2); %! assert_equal ([pd{2}.mu], muhat); %! assert_equal (paramci (pd{2}), muci); %!warning ... %! fitdist ([exprnd(1, 100, 1); nan(100, 1)], 'exponential', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = evrnd (1, 1, 100, 1); %! pd = fitdist (x, 'ev'); %! [phat, pci] = evfit (x); %! assert_equal ([pd.mu, pd.sigma], phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = evrnd (1, 1, 100, 1); %! x2 = evrnd (5, 2, 100, 1); %! pd = fitdist ([x1; x2], 'extremevalue', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = evfit (x1); %! assert_equal ([pd{1}.mu, pd{1}.sigma], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = evfit (x2); %! assert_equal ([pd{2}.mu, pd{2}.sigma], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([evrnd(1, 1, 100, 1); nan(100, 1)], 'extremevalue', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = gamrnd (1, 1, 100, 1); %! pd = fitdist (x, 'Gamma'); %! [phat, pci] = gamfit (x); %! assert_equal ([pd.a, pd.b], phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = gamrnd (1, 1, 100, 1); %! x2 = gamrnd (5, 2, 100, 1); %! pd = fitdist ([x1; x2], 'Gamma', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = gamfit (x1); %! assert_equal ([pd{1}.a, pd{1}.b], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = gamfit (x2); %! assert_equal ([pd{2}.a, pd{2}.b], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([gamrnd(1, 1, 100, 1); nan(100, 1)], 'Gamma', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! rand ('seed', 4); # for reproducibility %! x = gevrnd (-0.5, 1, 2, 1000, 1); %! pd = fitdist (x, 'generalizedextremevalue'); %! [phat, pci] = gevfit (x); %! assert_equal ([pd.k, pd.sigma, pd.mu], phat); %! assert_equal (paramci (pd), pci); %!test %! rand ('seed', 5); # for reproducibility %! x1 = gevrnd (-0.5, 1, 2, 1000, 1); %! rand ('seed', 9); # for reproducibility %! x2 = gevrnd (0, 1, -4, 1000, 1); %! pd = fitdist ([x1; x2], 'gev', 'By', [ones(1000,1); 2*ones(1000,1)]); %! [phat, pci] = gevfit (x1); %! assert_equal ([pd{1}.k, pd{1}.sigma, pd{1}.mu], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = gevfit (x2); %! assert_equal ([pd{2}.k, pd{2}.sigma, pd{2}.mu], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([gevrnd(-0.5, 1, 2, 1000, 1); nan(1000, 1)], 'gev', ... %! 'By', [ones(1000, 1); 2*ones(1000, 1)]); %!test %! x = gprnd (1, 1, 1, 100, 1); %! pd = fitdist (x, 'GeneralizedPareto', 'theta', 1); %! [phat, pci] = gpfit (x - 1); %! assert_equal ([pd.k, pd.sigma, pd.theta], [phat, 1]); %! assert_equal (paramci (pd), [pci, [1; 1]]); %!test %! x = gprnd (1, 1, 2, 100, 1); %! pd = fitdist (x, 'GeneralizedPareto', 'theta', 2); %! [phat, pci] = gpfit (x - 2); %! assert_equal ([pd.k, pd.sigma, pd.theta], [phat, 2]); %! assert_equal (paramci (pd), [pci, [2; 2]]); %!test %! x1 = gprnd (1, 1, 1, 100, 1); %! x2 = gprnd (0, 2, 1, 100, 1); %! pd = fitdist ([x1; x2], 'gp', 'theta', 1, ... %! 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = gpfit (x1 - 1); %! assert_equal ([pd{1}.k, pd{1}.sigma, pd{1}.theta], [phat, 1]); %! assert_equal (paramci (pd{1}), [pci, [1; 1]]); %! [phat, pci] = gpfit (x2 - 1); %! assert_equal ([pd{2}.k, pd{2}.sigma, pd{2}.theta], [phat, 1]); %! assert_equal (paramci (pd{2}), [pci, [1; 1]]); %!warning ... %! fitdist ([gprnd(1, 1, 1, 100, 1); nan(100, 1)], 'gp', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x1 = gprnd (3, 2, 2, 100, 1); %! x2 = gprnd (2, 3, 2, 100, 1); %! pd = fitdist ([x1; x2], 'GeneralizedPareto', 'theta', 2, ... %! 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = gpfit (x1 - 2); %! assert_equal ([pd{1}.k, pd{1}.sigma, pd{1}.theta], [phat, 2]); %! assert_equal (paramci (pd{1}), [pci, [2; 2]]); %! [phat, pci] = gpfit (x2 - 2); %! assert_equal ([pd{2}.k, pd{2}.sigma, pd{2}.theta], [phat, 2]); %! assert_equal (paramci (pd{2}), [pci, [2; 2]]); %!warning ... %! fitdist ([gprnd(3, 2, 2, 100, 1); nan(100, 1)], 'gp', 'theta', 2, ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = hnrnd (0, 1, 100, 1); %! pd = fitdist (x, 'HalfNormal'); %! [phat, pci] = hnfit (x, 0); %! assert_equal ([pd.mu, pd.sigma], phat); %! assert_equal (paramci (pd), pci); %!test %! x = hnrnd (1, 1, 100, 1); %! pd = fitdist (x, 'HalfNormal', 'mu', 1); %! [phat, pci] = hnfit (x, 1); %! assert_equal ([pd.mu, pd.sigma], phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = hnrnd (0, 1, 100, 1); %! x2 = hnrnd (0, 2, 100, 1); %! pd = fitdist ([x1; x2], 'HalfNormal', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = hnfit (x1, 0); %! assert_equal ([pd{1}.mu, pd{1}.sigma], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = hnfit (x2, 0); %! assert_equal ([pd{2}.mu, pd{2}.sigma], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([hnrnd(0, 1, 100, 1); nan(100, 1)], 'HalfNormal', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x1 = hnrnd (2, 1, 100, 1); %! x2 = hnrnd (2, 2, 100, 1); %! pd = fitdist ([x1; x2], 'HalfNormal', 'mu', 2, ... %! 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = hnfit (x1, 2); %! assert_equal ([pd{1}.mu, pd{1}.sigma], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = hnfit (x2, 2); %! assert_equal ([pd{2}.mu, pd{2}.sigma], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([hnrnd(2, 1, 100, 1); nan(100, 1)], 'HalfNormal', 'mu', 2, ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = invgrnd (1, 1, 100, 1); %! pd = fitdist (x, 'InverseGaussian'); %! [phat, pci] = invgfit (x); %! assert_equal ([pd.mu, pd.lambda], phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = invgrnd (1, 1, 100, 1); %! x2 = invgrnd (5, 2, 100, 1); %! pd = fitdist ([x1; x2], 'InverseGaussian', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = invgfit (x1); %! assert_equal ([pd{1}.mu, pd{1}.lambda], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = invgfit (x2); %! assert_equal ([pd{2}.mu, pd{2}.lambda], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([invgrnd(1, 1, 100, 1); nan(100, 1)], 'InverseGaussian', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = logirnd (1, 1, 100, 1); %! pd = fitdist (x, 'logistic'); %! [phat, pci] = logifit (x); %! assert_equal ([pd.mu, pd.sigma], phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = logirnd (1, 1, 100, 1); %! x2 = logirnd (5, 2, 100, 1); %! pd = fitdist ([x1; x2], 'logistic', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = logifit (x1); %! assert_equal ([pd{1}.mu, pd{1}.sigma], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = logifit (x2); %! assert_equal ([pd{2}.mu, pd{2}.sigma], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([logirnd(1, 1, 100, 1); nan(100, 1)], 'logistic', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = loglrnd (1, 1, 100, 1); %! pd = fitdist (x, 'loglogistic'); %! [phat, pci] = loglfit (x); %! assert_equal ([pd.mu, pd.sigma], phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = loglrnd (1, 1, 100, 1); %! x2 = loglrnd (5, 2, 100, 1); %! pd = fitdist ([x1; x2], 'loglogistic', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = loglfit (x1); %! assert_equal ([pd{1}.mu, pd{1}.sigma], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = loglfit (x2); %! assert_equal ([pd{2}.mu, pd{2}.sigma], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([loglrnd(1, 1, 100, 1); nan(100, 1)], 'loglogistic', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = lognrnd (1, 1, 100, 1); %! pd = fitdist (x, 'lognormal'); %! [phat, pci] = lognfit (x); %! assert_equal ([pd.mu, pd.sigma], phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = lognrnd (1, 1, 100, 1); %! x2 = lognrnd (5, 2, 100, 1); %! pd = fitdist ([x1; x2], 'lognormal', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = lognfit (x1); %! assert_equal ([pd{1}.mu, pd{1}.sigma], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = lognfit (x2); %! assert_equal ([pd{2}.mu, pd{2}.sigma], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([lognrnd(1, 1, 100, 1); nan(100, 1)], 'lognormal', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = nakarnd (2, 0.5, 100, 1); %! pd = fitdist (x, 'Nakagami'); %! [phat, pci] = nakafit (x); %! assert_equal ([pd.mu, pd.omega], phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = nakarnd (2, 0.5, 100, 1); %! x2 = nakarnd (5, 0.8, 100, 1); %! pd = fitdist ([x1; x2], 'Nakagami', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = nakafit (x1); %! assert_equal ([pd{1}.mu, pd{1}.omega], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = nakafit (x2); %! assert_equal ([pd{2}.mu, pd{2}.omega], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([nakarnd(2, 0.5, 100, 1); nan(100, 1)], 'Nakagami', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! randp ('seed', 123); %! randg ('seed', 321); %! x = nbinrnd (2, 0.5, 100, 1); %! pd = fitdist (x, 'negativebinomial'); %! [phat, pci] = nbinfit (x); %! assert_equal ([pd.R, pd.P], phat); %! assert_equal (paramci (pd), pci); %!test %! randp ('seed', 345); %! randg ('seed', 543); %! x1 = nbinrnd (2, 0.5, 100, 1); %! randp ('seed', 432); %! randg ('seed', 234); %! x2 = nbinrnd (5, 0.8, 100, 1); %! pd = fitdist ([x1; x2], 'nbin', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = nbinfit (x1); %! assert_equal ([pd{1}.R, pd{1}.P], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = nbinfit (x2); %! assert_equal ([pd{2}.R, pd{2}.P], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([nbinrnd(2, 0.5, 100, 1); nan(100, 1)], 'nbin', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = normrnd (1, 1, 100, 1); %! pd = fitdist (x, 'normal'); %! [muhat, sigmahat, muci, sigmaci] = normfit (x); %! assert_equal ([pd.mu, pd.sigma], [muhat, sigmahat]); %! assert_equal (paramci (pd), [muci, sigmaci]); %!test %! x1 = normrnd (1, 1, 100, 1); %! x2 = normrnd (5, 2, 100, 1); %! pd = fitdist ([x1; x2], 'normal', 'By', [ones(100,1); 2*ones(100,1)]); %! [muhat, sigmahat, muci, sigmaci] = normfit (x1); %! assert_equal ([pd{1}.mu, pd{1}.sigma], [muhat, sigmahat]); %! assert_equal (paramci (pd{1}), [muci, sigmaci]); %! [muhat, sigmahat, muci, sigmaci] = normfit (x2); %! assert_equal ([pd{2}.mu, pd{2}.sigma], [muhat, sigmahat]); %! assert_equal (paramci (pd{2}), [muci, sigmaci]); %!warning ... %! fitdist ([normrnd(1, 1, 100, 1); nan(100, 1)], 'normal', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = poissrnd (1, 100, 1); %! pd = fitdist (x, 'poisson'); %! [phat, pci] = poissfit (x); %! assert_equal (pd.lambda, phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = poissrnd (1, 100, 1); %! x2 = poissrnd (5, 100, 1); %! pd = fitdist ([x1; x2], 'poisson', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = poissfit (x1); %! assert_equal (pd{1}.lambda, phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = poissfit (x2); %! assert_equal (pd{2}.lambda, phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([poissrnd(1, 100, 1); nan(100, 1)], 'poisson', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = raylrnd (1, 100, 1); %! pd = fitdist (x, 'rayleigh'); %! [phat, pci] = raylfit (x); %! assert_equal (pd.B, phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = raylrnd (1, 100, 1); %! x2 = raylrnd (5, 100, 1); %! pd = fitdist ([x1; x2], 'rayleigh', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = raylfit (x1); %! assert_equal (pd{1}.B, phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = raylfit (x2); %! assert_equal (pd{2}.B, phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([raylrnd(1, 100, 1); nan(100, 1)], 'rayleigh', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = ricernd (1, 1, 100, 1); %! pd = fitdist (x, 'rician'); %! [phat, pci] = ricefit (x); %! assert_equal ([pd.s, pd.sigma], phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = ricernd (1, 1, 100, 1); %! x2 = ricernd (5, 2, 100, 1); %! pd = fitdist ([x1; x2], 'rician', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = ricefit (x1); %! assert_equal ([pd{1}.s, pd{1}.sigma], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = ricefit (x2); %! assert_equal ([pd{2}.s, pd{2}.sigma], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([ricernd(1, 1, 100, 1); nan(100, 1)], 'rician', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test ## fitdist returns a fitted prob.StableDistribution object %! rand ("seed", 2718); %! randn ("seed", 2718); %! x = stblrnd (1.5, 0.5, 2, 1, 150, 1); %! pd = fitdist (x, 'Stable'); %! assert_equal (class (pd), 'prob.StableDistribution'); %! assert_equal (! any (pd.ParameterIsFixed, 'all'), true); %! assert_equal (pd.alpha, 1.5, 0.4); %! assert_equal (pd.gam, 2, 0.5); %!test %! x = tlsrnd (0, 1, 1, 100, 1); %! pd = fitdist (x, 'tlocationscale'); %! [phat, pci] = tlsfit (x); %! assert_equal ([pd.mu, pd.sigma, pd.nu], phat); %! assert_equal (paramci (pd), pci); %!test %! x1 = tlsrnd (0, 1, 1, 100, 1); %! x2 = tlsrnd (5, 2, 1, 100, 1); %! pd = fitdist ([x1; x2], 'tlocationscale', 'By', [ones(100,1); 2*ones(100,1)]); %! [phat, pci] = tlsfit (x1); %! assert_equal ([pd{1}.mu, pd{1}.sigma, pd{1}.nu], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = tlsfit (x2); %! assert_equal ([pd{2}.mu, pd{2}.sigma, pd{2}.nu], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([tlsrnd(0, 1, 1, 100, 1); nan(100, 1)], 'tlocationscale', ... %! 'By', [ones(100, 1); 2*ones(100, 1)]); %!test %! x = [1 2 3 4 5]; %! pd = fitdist (x, 'weibull'); %! [phat, pci] = wblfit (x); %! assert_equal ([pd.A, pd.B], phat); %! assert_equal (paramci (pd), pci); %!test %! x = [1 2 3 4 5 6 7 8 9 10]; %! pd = fitdist (x, 'weibull', 'By', [1 1 1 1 1 2 2 2 2 2]); %! [phat, pci] = wblfit (x(1:5)); %! assert_equal ([pd{1}.A, pd{1}.B], phat); %! assert_equal (paramci (pd{1}), pci); %! [phat, pci] = wblfit (x(6:10)); %! assert_equal ([pd{2}.A, pd{2}.B], phat); %! assert_equal (paramci (pd{2}), pci); %!warning ... %! fitdist ([1 2 3 4 5 NaN NaN NaN NaN NaN], 'weibull', 'By', [1 1 1 1 1 2 2 2 2 2]); ## Test input validation %!error fitdist (1) %!error fitdist (1, ['as';'sd']) %!error fitdist (1, 'some') %!error ... %! fitdist (ones (2), 'normal') %!error ... %! fitdist ([i, 2, 3], 'normal') %!error ... %! fitdist (['a', 's', 'd'], 'normal') %!error ... %! fitdist ([1, 2, 3], 'normal', 'By') %!error ... %! fitdist ([1, 2, 3], 'normal', 'By', [1, 2]) %!error ... %! fitdist ([1, 2, 3], 'normal', 'Censoring', [1, 2]) %!error ... %! fitdist ([1, 2, 3], 'normal', 'frequency', [1, 2]) %!error ... %! fitdist ([1, 2, 3], 'negativebinomial', 'frequency', [1, -2, 3]) %!error ... %! fitdist ([1, 2, 3], 'normal', 'alpha', [1, 2]) %!error ... %! fitdist ([1, 2, 3], 'normal', 'alpha', i) %!error ... %! fitdist ([1, 2, 3], 'normal', 'alpha', -0.5) %!error ... %! fitdist ([1, 2, 3], 'normal', 'alpha', 1.5) %!error ... %! fitdist ([1, 2, 3], 'normal', 'ntrials', [1, 2]) %!error ... %! fitdist ([1, 2, 3], 'normal', 'ntrials', 0) %!error ... %! fitdist ([1, 2, 3], 'normal', 'options', 0) %!error ... %! fitdist ([1, 2, 3], 'normal', 'options', struct ('options', 1)) %!error ... %! fitdist ([1, 2, 3]', 'kernel', 'Censoring', [1, 0, 0]'); %!error ... %! fitdist ([1, 2, 3]', 'Stable', 'Censoring', [1, 0, 0]'); %!error ... %! fitdist ([1, 2, 3], 'normal', 'param', struct ('options', 1)) %!error ... %! fitdist (nan (100,1), 'normal'); %!error ... %! [pdca, gn, gl] = fitdist ([1, 2, 3], 'normal'); %!error ... %! fitdist ([1, 2, 3], 'generalizedpareto', 'theta', 2); %!error ... %! fitdist ([1, 2, 3], 'halfnormal', 'mu', 2); statistics-release-1.9.2/inst/Distribution_Wrappers/icdf.m000066400000000000000000000373411524624707500237610ustar00rootroot00000000000000# Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} icdf (@var{name}, @var{p}, @var{A}) ## @deftypefnx {statistics} {@var{x} =} icdf (@var{name}, @var{p}, @var{A}, @var{B}) ## @deftypefnx {statistics} {@var{x} =} icdf (@var{name}, @var{p}, @var{A}, @var{B}, @var{C}) ## ## Return the inverse CDF of a univariate distribution evaluated at @var{p}. ## ## @code{icdf} is a wrapper for the univariate quantile distribution functions ## (iCDF) available in the statistics package. See the corresponding functions' ## help to learn the signification of the parameters after @var{p}. ## ## @code{@var{x} = icdf (@var{name}, @var{p}, @var{A})} returns the iCDF for the ## one-parameter distribution family specified by @var{name} and the ## distribution parameter @var{A}, evaluated at the values in @var{p}. ## ## @code{@var{x} = icdf (@var{name}, @var{p}, @var{A}, @var{B})} returns the ## iCDF for the two-parameter distribution family specified by @var{name} and ## the distribution parameters @var{A} and @var{B}, evaluated at the values in ## @var{p}. ## ## @code{@var{x} = icdf (@var{name}, @var{p}, @var{A}, @var{B}, @var{C})} ## returns the iCDF for the three-parameter distribution family specified by ## @var{name} and the distribution parameters @var{A}, @var{B}, and @var{C}, ## evaluated at the values in @var{p}. ## ## @var{name} must be a char string of the name or the abbreviation of the ## desired quantile distribution function as listed in the following table. ## The last column shows the number of required parameters that should be parsed ## after @var{x} to the desired iCDF. ## ## @multitable @columnfractions 0.4 0.2 0.3 ## @headitem Distribution Name @tab Abbreviation @tab Input Parameters ## @item @qcode{'Beta'} @tab @qcode{'beta'} @tab 2 ## @item @qcode{'Binomial'} @tab @qcode{'bino'} @tab 2 ## @item @qcode{'Birnbaum-Saunders'} @tab @qcode{'bisa'} @tab 2 ## @item @qcode{'Burr'} @tab @qcode{'burr'} @tab 3 ## @item @qcode{'Cauchy'} @tab @qcode{'cauchy'} @tab 2 ## @item @qcode{'Chi-squared'} @tab @qcode{'chi2'} @tab 1 ## @item @qcode{'Extreme Value'} @tab @qcode{'ev'} @tab 2 ## @item @qcode{'Exponential'} @tab @qcode{'exp'} @tab 1 ## @item @qcode{'F-Distribution'} @tab @qcode{'f'} @tab 2 ## @item @qcode{'Gamma'} @tab @qcode{'gam'} @tab 2 ## @item @qcode{'Geometric'} @tab @qcode{'geo'} @tab 1 ## @item @qcode{'Generalized Extreme Value'} @tab @qcode{'gev'} @tab 3 ## @item @qcode{'Generalized Pareto'} @tab @qcode{'gp'} @tab 3 ## @item @qcode{'Gumbel'} @tab @qcode{'gumbel'} @tab 2 ## @item @qcode{'Half-normal'} @tab @qcode{'hn'} @tab 2 ## @item @qcode{'Hypergeometric'} @tab @qcode{'hyge'} @tab 3 ## @item @qcode{'Inverse Gaussian'} @tab @qcode{'invg'} @tab 2 ## @item @qcode{'Laplace'} @tab @qcode{'laplace'} @tab 2 ## @item @qcode{'Logistic'} @tab @qcode{'logi'} @tab 2 ## @item @qcode{'Log-Logistic'} @tab @qcode{'logl'} @tab 2 ## @item @qcode{'Lognormal'} @tab @qcode{'logn'} @tab 2 ## @item @qcode{'Nakagami'} @tab @qcode{'naka'} @tab 2 ## @item @qcode{'Negative Binomial'} @tab @qcode{'nbin'} @tab 2 ## @item @qcode{'Noncentral F-Distribution'} @tab @qcode{'ncf'} @tab 3 ## @item @qcode{'Noncentral Student T'} @tab @qcode{'nct'} @tab 2 ## @item @qcode{'Noncentral Chi-Squared'} @tab @qcode{'ncx2'} @tab 2 ## @item @qcode{'Normal'} @tab @qcode{'norm'} @tab 2 ## @item @qcode{'Poisson'} @tab @qcode{'poiss'} @tab 1 ## @item @qcode{'Rayleigh'} @tab @qcode{'rayl'} @tab 1 ## @item @qcode{'Rician'} @tab @qcode{'rice'} @tab 2 ## @item @qcode{'Student T'} @tab @qcode{'t'} @tab 1 ## @item @qcode{'location-scale T'} @tab @qcode{'tls'} @tab 3 ## @item @qcode{'Triangular'} @tab @qcode{'tri'} @tab 3 ## @item @qcode{'Discrete Uniform'} @tab @qcode{'unid'} @tab 1 ## @item @qcode{'Uniform'} @tab @qcode{'unif'} @tab 2 ## @item @qcode{'Von Mises'} @tab @qcode{'vm'} @tab 2 ## @item @qcode{'Weibull'} @tab @qcode{'wbl'} @tab 2 ## @end multitable ## ## Distribution names are matched ignoring case, spaces and hyphens, so that ## @qcode{'Extreme Value'}, @qcode{'ExtremeValue'} and @qcode{'extreme-value'} ## all select the same distribution, and the same set of names is accepted by ## @code{cdf}, @code{pdf}, @code{icdf}, @code{random}, @code{makedist}, ## @code{fitdist} and @code{mle}. ## ## This accepts more names than MATLAB. MATLAB takes the spaced and the ## squashed spelling but refuses the hyphenated one, so ## @qcode{'Birnbaum-Saunders'} and @qcode{'Log-Logistic'} are errors there; ## Octave has always accepted them and continues to. MATLAB also accepts ## @qcode{'tLocationScale'} in @code{makedist} while refusing it in ## @code{cdf} for the same distribution; Octave accepts it, and ## @qcode{'location-scale T'}, everywhere. Code written against MATLAB's ## names therefore runs unchanged, but code relying on these names will not ## port back. ## ## @seealso{icdf, pdf, random, betainv, binoinv, bisainv, burrinv, cauchyinv, ## chi2inv, evinv, expinv, finv, gaminv, geoinv, gevinv, gpinv, gumbelinv, ## hninv, hygeinv, invginv, laplaceinv, logiinv, loglinv, logninv, nakainv, ## nbininv, ncfinv, nctinv, ncx2inv, norminv, poissinv, raylinv, riceinv, tinv, ## tlsinv, triinv, unidinv, unifinv, vminv, wblinv} ## @end deftypefn function x = icdf (name, p, varargin) ## implemented functions persistent allDF = { ... {'beta' , 'Beta'}, @betainv, 2, ... {'bino' , 'Binomial'}, @binoinv, 2, ... {'bisa' , 'Birnbaum-Saunders'}, @bisainv, 2, ... {'burr' , 'Burr'}, @burrinv, 3, ... {'cauchy' , 'Cauchy'}, @cauchyinv, 2, ... {'chi2' , 'Chi-squared'}, @chi2inv, 1, ... {'ev' , 'Extreme Value'}, @evinv, 2, ... {'exp' , 'Exponential'}, @expinv, 1, ... {'f' , 'F-Distribution'}, @finv, 2, ... {'gam' , 'Gamma'}, @gaminv, 2, ... {'geo' , 'Geometric'}, @geoinv, 1, ... {'gev' , 'Generalized Extreme Value'}, @gevinv, 3, ... {'gp' , 'Generalized Pareto'}, @gpinv, 3, ... {'gumbel' , 'Gumbel'}, @gumbelinv, 2, ... {'hn' , 'Half-normal'}, @hninv, 2, ... {'hyge' , 'Hypergeometric'}, @hygeinv, 3, ... {'invg' , 'Inverse Gaussian'}, @invginv, 2, ... {'laplace' , 'Laplace'}, @laplaceinv, 2, ... {'logi' , 'Logistic'}, @logiinv, 2, ... {'logl' , 'Log-Logistic'}, @loglinv, 2, ... {'logn' , 'Lognormal'}, @logninv, 2, ... {'naka' , 'Nakagami'}, @nakainv, 2, ... {'nbin' , 'Negative Binomial'}, @nbininv, 2, ... {'ncf' , 'Noncentral F-Distribution'}, @ncfinv, 3, ... {'nct' , 'Noncentral Student T'}, @nctinv, 2, ... {'ncx2' , 'Noncentral Chi-squared'}, @ncx2inv, 2, ... {'norm' , 'Normal'}, @norminv, 2, ... {'poiss' , 'Poisson'}, @poissinv, 1, ... {'rayl' , 'Rayleigh'}, @raylinv, 1, ... {'rice' , 'Rician'}, @riceinv, 2, ... {'t' , 'Student T'}, @tinv, 1, ... {'tls', 'location-scale T', 'tLocationScale'}, @tlsinv, 3, ... {'tri' , 'Triangular'}, @triinv, 3, ... {'unid' , 'Discrete Uniform'}, @unidinv, 1, ... {'unif' , 'Uniform'}, @unifinv, 2, ... {'vm' , 'Von Mises'}, @vminv, 2, ... {'wbl' , 'Weibull'}, @wblinv, 2}; if (! ischar (name)) error ("icdf: distribution NAME must be a char string."); endif ## Check P being numeric and real if (! isnumeric (p)) error ("icdf: P must be numeric."); elseif (! isreal (p)) error ("icdf: values in P must be real."); endif ## Get number of arguments nargs = numel (varargin); ## Get available functions icdfnames = allDF(1:3:end); icdfhandl = allDF(2:3:end); icdf_args = allDF(3:3:end); ## Search for iCDF function ## Match on the folded key so that every spelling of a name resolves key = __distname_key__ (name); idx = cellfun (@(x) any (strcmp (key, cellfun (@__distname_key__, x, ... 'UniformOutput', false))), icdfnames); if (any (idx)) if (nargs == icdf_args{idx}) ## Check that all distribution parameters are numeric if (! all (cellfun (@(x)isnumeric (x), (varargin)))) error ("icdf: distribution parameters must be numeric."); endif ## Call appropriate iCDF x = feval (icdfhandl{idx}, p, varargin{:}); else if (icdf_args{idx} == 1) error ("icdf: %s distribution requires 1 parameter.", name); else error ("icdf: %s distribution requires %d parameters.", ... name, icdf_args{idx}); endif endif else error ("icdf: %s distribution is not implemented in Statistics.", name); endif endfunction ## Test results %!shared p %! p = [0.05:0.05:0.5]; %!assert_equal (icdf ('Beta', p, 5, 2), betainv (p, 5, 2)) %!assert_equal (icdf ('beta', p, 5, 2), betainv (p, 5, 2)) %!assert_equal (icdf ('Binomial', p, 5, 2), binoinv (p, 5, 2)) %!assert_equal (icdf ('bino', p, 5, 2), binoinv (p, 5, 2)) %!assert_equal (icdf ('Birnbaum-Saunders', p, 5, 2), bisainv (p, 5, 2)) %!assert_equal (icdf ('bisa', p, 5, 2), bisainv (p, 5, 2)) %!assert_equal (icdf ('Burr', p, 5, 2, 2), burrinv (p, 5, 2, 2)) %!assert_equal (icdf ('burr', p, 5, 2, 2), burrinv (p, 5, 2, 2)) %!assert_equal (icdf ('Cauchy', p, 5, 2), cauchyinv (p, 5, 2)) %!assert_equal (icdf ('cauchy', p, 5, 2), cauchyinv (p, 5, 2)) %!assert_equal (icdf ('Chi-squared', p, 5), chi2inv (p, 5)) %!assert_equal (icdf ('chi2', p, 5), chi2inv (p, 5)) %!assert_equal (icdf ('Extreme Value', p, 5, 2), evinv (p, 5, 2)) %!assert_equal (icdf ('ev', p, 5, 2), evinv (p, 5, 2)) %!assert_equal (icdf ('Exponential', p, 5), expinv (p, 5)) %!assert_equal (icdf ('exp', p, 5), expinv (p, 5)) %!assert_equal (icdf ('F-Distribution', p, 5, 2), finv (p, 5, 2)) %!assert_equal (icdf ('f', p, 5, 2), finv (p, 5, 2)) %!assert_equal (icdf ('Gamma', p, 5, 2), gaminv (p, 5, 2)) %!assert_equal (icdf ('gam', p, 5, 2), gaminv (p, 5, 2)) %!assert_equal (icdf ('Geometric', p, 5), geoinv (p, 5)) %!assert_equal (icdf ('geo', p, 5), geoinv (p, 5)) %!assert_equal (icdf ('Generalized Extreme Value', p, 5, 2, 2), gevinv (p, 5, 2, 2)) %!assert_equal (icdf ('gev', p, 5, 2, 2), gevinv (p, 5, 2, 2)) %!assert_equal (icdf ('Generalized Pareto', p, 5, 2, 2), gpinv (p, 5, 2, 2)) %!assert_equal (icdf ('gp', p, 5, 2, 2), gpinv (p, 5, 2, 2)) %!assert_equal (icdf ('Gumbel', p, 5, 2), gumbelinv (p, 5, 2)) %!assert_equal (icdf ('gumbel', p, 5, 2), gumbelinv (p, 5, 2)) %!assert_equal (icdf ('Half-normal', p, 5, 2), hninv (p, 5, 2)) %!assert_equal (icdf ('hn', p, 5, 2), hninv (p, 5, 2)) %!assert_equal (icdf ('Hypergeometric', p, 5, 2, 2), hygeinv (p, 5, 2, 2)) %!assert_equal (icdf ('hyge', p, 5, 2, 2), hygeinv (p, 5, 2, 2)) %!assert_equal (icdf ('Inverse Gaussian', p, 5, 2), invginv (p, 5, 2)) %!assert_equal (icdf ('invg', p, 5, 2), invginv (p, 5, 2)) %!assert_equal (icdf ('Laplace', p, 5, 2), laplaceinv (p, 5, 2)) %!assert_equal (icdf ('laplace', p, 5, 2), laplaceinv (p, 5, 2)) %!assert_equal (icdf ('Logistic', p, 5, 2), logiinv (p, 5, 2)) %!assert_equal (icdf ('logi', p, 5, 2), logiinv (p, 5, 2)) %!assert_equal (icdf ('Log-Logistic', p, 5, 2), loglinv (p, 5, 2)) %!assert_equal (icdf ('logl', p, 5, 2), loglinv (p, 5, 2)) %!assert_equal (icdf ('Lognormal', p, 5, 2), logninv (p, 5, 2)) %!assert_equal (icdf ('logn', p, 5, 2), logninv (p, 5, 2)) %!assert_equal (icdf ('Nakagami', p, 5, 2), nakainv (p, 5, 2)) %!assert_equal (icdf ('naka', p, 5, 2), nakainv (p, 5, 2)) %!assert_equal (icdf ('Negative Binomial', p, 5, 2), nbininv (p, 5, 2)) %!assert_equal (icdf ('nbin', p, 5, 2), nbininv (p, 5, 2)) %!assert_equal (icdf ('Noncentral F-Distribution', p, 5, 2, 2), ncfinv (p, 5, 2, 2)) %!assert_equal (icdf ('ncf', p, 5, 2, 2), ncfinv (p, 5, 2, 2)) %!assert_equal (icdf ('Noncentral Student T', p, 5, 2), nctinv (p, 5, 2)) %!assert_equal (icdf ('nct', p, 5, 2), nctinv (p, 5, 2)) %!assert_equal (icdf ('Noncentral Chi-Squared', p, 5, 2), ncx2inv (p, 5, 2)) %!assert_equal (icdf ('ncx2', p, 5, 2), ncx2inv (p, 5, 2)) %!assert_equal (icdf ('Normal', p, 5, 2), norminv (p, 5, 2)) %!assert_equal (icdf ('norm', p, 5, 2), norminv (p, 5, 2)) %!assert_equal (icdf ('Poisson', p, 5), poissinv (p, 5)) %!assert_equal (icdf ('poiss', p, 5), poissinv (p, 5)) %!assert_equal (icdf ('Rayleigh', p, 5), raylinv (p, 5)) %!assert_equal (icdf ('rayl', p, 5), raylinv (p, 5)) %!assert_equal (icdf ('Rician', p, 5, 1), riceinv (p, 5, 1)) %!assert_equal (icdf ('rice', p, 5, 1), riceinv (p, 5, 1)) %!assert_equal (icdf ('Student T', p, 5), tinv (p, 5)) %!assert_equal (icdf ('t', p, 5), tinv (p, 5)) %!assert_equal (icdf ('location-scale T', p, 5, 1, 2), tlsinv (p, 5, 1, 2)) %!assert_equal (icdf ('tls', p, 5, 1, 2), tlsinv (p, 5, 1, 2)) %!assert_equal (icdf ('Triangular', p, 5, 2, 2), triinv (p, 5, 2, 2)) %!assert_equal (icdf ('tri', p, 5, 2, 2), triinv (p, 5, 2, 2)) %!assert_equal (icdf ('Discrete Uniform', p, 5), unidinv (p, 5)) %!assert_equal (icdf ('unid', p, 5), unidinv (p, 5)) %!assert_equal (icdf ('Uniform', p, 5, 2), unifinv (p, 5, 2)) %!assert_equal (icdf ('unif', p, 5, 2), unifinv (p, 5, 2)) %!assert_equal (icdf ('Von Mises', p, 5, 2), vminv (p, 5, 2)) %!assert_equal (icdf ('vm', p, 5, 2), vminv (p, 5, 2)) %!assert_equal (icdf ('Weibull', p, 5, 2), wblinv (p, 5, 2)) %!assert_equal (icdf ('wbl', p, 5, 2), wblinv (p, 5, 2)) ## Test input validation %!test %! ## Every spelling of a name reaches the same distribution: case, spaces, %! ## hyphens and underscores are all ignored. %! for n = {'Extreme Value', 'ExtremeValue', 'extreme-value', 'EXTREME VALUE'} %! assert_equal (icdf (n{1}, 0.5, 2, 3), icdf ('ev', 0.5, 2, 3)); %! endfor %!test %! ## The name that makedist uses is accepted here too, and the reverse %! assert_equal (icdf ('tLocationScale', 0.5, 2, 3, 4), ... %! icdf ('tls', 0.5, 2, 3, 4)); %!error icdf (1) %!error icdf ({'beta'}) %!error icdf ('beta', {[1 2 3 4 5]}) %!error icdf ('beta', 'text') %!error icdf ('beta', 1+i) %!error ... %! icdf ('Beta', p, 'a', 2) %!error ... %! icdf ('Beta', p, 5, '') %!error ... %! icdf ('Beta', p, 5, {2}) %!error icdf ('chi2', p) %!error icdf ('Beta', p, 5) %!error icdf ('Burr', p, 5) %!error icdf ('Burr', p, 5, 2) statistics-release-1.9.2/inst/Distribution_Wrappers/makedist.m000066400000000000000000001064601524624707500246540ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{pd} =} makedist (@var{distname}) ## @deftypefnx {statistics} {@var{pd} =} makedist (@var{distname}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {@var{list} =} makedist ## ## Create probability distribution object. ## ## @code{@var{pd} = makedist (@var{distname})} creates a probability ## distribution object for the distribution specified in @var{distname}, using ## the default parameter values. ## ## @code{@var{pd} = makedist (@var{distname}, @var{Name}, @var{Value})} also ## creates a probability distribution object with one or more distribution ## parameter values specified by @qcode{Name-Value} pair arguments. ## ## @code{@var{list} = makedist} returns a cell array, @var{list}, containing a ## list of the probability distributions that makedist can create. ## ## Distribution names are matched ignoring case, spaces and hyphens, so that ## @qcode{'Extreme Value'}, @qcode{'ExtremeValue'} and @qcode{'extreme-value'} ## all select the same distribution, and the same set of names is accepted by ## @code{cdf}, @code{pdf}, @code{icdf}, @code{random}, @code{makedist}, ## @code{fitdist} and @code{mle}. ## ## This accepts more names than MATLAB. MATLAB takes the spaced and the ## squashed spelling but refuses the hyphenated one, so ## @qcode{'Birnbaum-Saunders'} and @qcode{'Log-Logistic'} are errors there; ## Octave has always accepted them and continues to. MATLAB also accepts ## @qcode{'tLocationScale'} in @code{makedist} while refusing it in ## @code{cdf} for the same distribution; Octave accepts it, and ## @qcode{'location-scale T'}, everywhere. Code written against MATLAB's ## names therefore runs unchanged, but code relying on these names will not ## port back. ## ## @seealso{fitdist} ## @end deftypefn function pd = makedist (varargin) ## Add list of supported probability distribution objects PDO = {'Beta'; 'Binomial'; 'BirnbaumSaunders'; 'Burr'; 'Exponential'; ... 'ExtremeValue'; 'Gamma'; 'GeneralizedExtremeValue'; ... 'GeneralizedPareto'; 'HalfNormal'; 'InverseGaussian'; 'Kernel'; ... 'Logistic'; 'Loglogistic'; 'Lognormal'; 'Loguniform'; ... 'Multinomial'; 'Nakagami'; 'NegativeBinomial'; 'Normal'; ... 'PiecewiseLinear'; 'Poisson'; 'Rayleigh'; 'Rician'; ... 'Stable'; 'tLocationScale'; 'Triangular'; 'Uniform'; 'Weibull'}; ABBR = {'bisa', 'ev', 'gev', 'gp', 'hn', 'invg', 'nbin', 'tls', ... 'location-scale T'}; ## Check for input arguments if (nargin == 0) pd = PDO; return else distname = varargin{1}; varargin(1) = []; endif ## Check distribution name if (! (ischar (distname) && size (distname, 1) == 1)) error ("makedist: DISTNAME must be a character vector."); elseif (! (any (strcmp (__distname_key__ (distname), ... cellfun (@__distname_key__, PDO, ... "UniformOutput", false))) || any (strcmp (__distname_key__ (distname), ... cellfun (@__distname_key__, ABBR, ... "UniformOutput", false))))) error ("makedist: unrecognized distribution name."); endif ## Check for additional arguments being in pairs if (mod (numel (varargin), 2) != 0) error ("makedist: optional arguments must be in NAME-VALUE pairs."); endif ## Switch to selected distribution switch (__distname_key__ (distname)) case 'beta' ## Add default parameters a = 1; b = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'a' a = varargin{2}; case 'b' b = varargin{2}; otherwise error ("makedist: unknown parameter for 'Beta' distribution."); endswitch varargin([1:2]) = []; endwhile pd = prob.BetaDistribution (a, b); case 'binomial' N = 1; p = 0.5; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'n' N = varargin{2}; case 'p' p = varargin{2}; otherwise error ("makedist: unknown parameter for 'Binomial' distribution."); endswitch varargin([1:2]) = []; endwhile pd = prob.BinomialDistribution (N, p); case {'birnbaumsaunders', 'bisa'} beta = 1; gamma = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'beta' beta = varargin{2}; case 'gamma' gamma = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'BirnbaumSaunders' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.BirnbaumSaundersDistribution (beta, gamma); case 'burr' alpha = 1; c = 1; k = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case {'lambda', 'alpha'} alpha = varargin{2}; case 'c' c = varargin{2}; case 'k' k = varargin{2}; otherwise error ("makedist: unknown parameter for 'Burr' distribution."); endswitch varargin([1:2]) = []; endwhile pd = prob.BurrDistribution (alpha, c, k); case 'exponential' mu = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'mu' mu = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'Exponential' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.ExponentialDistribution (mu); case {'extremevalue', 'ev'} mu = 0; sigma = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'mu' mu = varargin{2}; case 'sigma' sigma = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'ExtremeValue' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.ExtremeValueDistribution (mu, sigma); case 'gamma' a = 1; b = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'a' a = varargin{2}; case 'b' b = varargin{2}; otherwise error ("makedist: unknown parameter for 'Gamma' distribution."); endswitch varargin([1:2]) = []; endwhile pd = prob.GammaDistribution (a, b); case {'generalizedextremevalue', 'gev'} k = 0; sigma = 1; mu = 0; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'k' k = varargin{2}; case 'sigma' sigma = varargin{2}; case 'mu' mu = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'GeneralizedExtremeValue' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.GeneralizedExtremeValueDistribution (k, sigma, mu); case {'generalizedpareto', 'gp'} k = 1; sigma = 1; theta = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'k' k = varargin{2}; case 'sigma' sigma = varargin{2}; case 'theta' theta = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'GeneralizedPareto' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.GeneralizedParetoDistribution (k, sigma, theta); case {'halfnormal', 'hn'} mu = 0; sigma = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'mu' mu = varargin{2}; case 'sigma' sigma = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'HalfNormal' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.HalfNormalDistribution (mu, sigma); case {'inversegaussian', 'invg'} mu = 1; lambda = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'mu' mu = varargin{2}; case 'lambda' lambda = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'InverseGaussian' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.InverseGaussianDistribution (mu, lambda); case 'kernel' error (strcat ("makedist: the Kernel distribution is not parametric", ... " and cannot be created with makedist; use fitdist.")); case 'logistic' mu = 0; sigma = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'mu' mu = varargin{2}; case 'sigma' sigma = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'Logistic' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.LogisticDistribution (mu, sigma); case 'loglogistic' mu = 0; sigma = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'mu' mu = varargin{2}; case 'sigma' sigma = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'Loglogistic' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.LoglogisticDistribution (mu, sigma); case 'lognormal' mu = 0; sigma = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'mu' mu = varargin{2}; case 'sigma' sigma = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'Lognormal' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.LognormalDistribution (mu, sigma); case 'loguniform' lower = 1; upper = 4; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'lower' lower = varargin{2}; case 'upper' upper = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'Loguniform' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.LoguniformDistribution (lower, upper); case 'multinomial' probs = [0.5, 0.5]; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'probabilities' probs = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'Multinomial' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.MultinomialDistribution (probs); case 'nakagami' mu = 1; omega = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'mu' mu = varargin{2}; case 'omega' omega = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'Nakagami' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.NakagamiDistribution (mu, omega); case {'negativebinomial', 'nbin'} R = 1; P = 0.5; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'r' R = varargin{2}; case {'ps', 'p'} P = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'NegativeBinomial' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.NegativeBinomialDistribution (R, P); case 'normal' mu = 0; sigma = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'mu' mu = varargin{2}; case 'sigma' sigma = varargin{2}; otherwise error ("makedist: unknown parameter for 'Normal' distribution."); endswitch varargin([1:2]) = []; endwhile pd = prob.NormalDistribution (mu, sigma); case 'piecewiselinear' x = [0, 1]; Fx = [0, 1]; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'x' x = varargin{2}; case 'fx' Fx = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'PiecewiseLinear' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.PiecewiseLinearDistribution (x, Fx); case 'poisson' lambda = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'lambda' lambda = varargin{2}; otherwise error ("makedist: unknown parameter for 'Poisson' distribution."); endswitch varargin([1:2]) = []; endwhile pd = prob.PoissonDistribution (lambda); case 'rayleigh' sigma = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case {'sigma', 'b'} sigma = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'Rayleigh' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.RayleighDistribution (sigma); case 'rician' s = 1; sigma = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 's' s = varargin{2}; case 'sigma' sigma = varargin{2}; otherwise error ("makedist: unknown parameter for 'Rician' distribution."); endswitch varargin([1:2]) = []; endwhile pd = prob.RicianDistribution (s, sigma); case 'stable' alpha = 2; beta = 0; gam = 1; delta = 0; while (numel (varargin) > 0) switch (tolower (varargin{1})) case {'alpha', 's'} alpha = varargin{2}; case 'beta' beta = varargin{2}; case 'gam' gam = varargin{2}; case 'delta' delta = varargin{2}; otherwise error ("makedist: unknown parameter for 'Stable' distribution."); endswitch varargin([1:2]) = []; endwhile pd = prob.StableDistribution (alpha, beta, gam, delta); case {'tlocationscale', 'tls', 'locationscalet'} mu = 0; sigma = 1; df = 5; while (numel (varargin) > 0) switch (tolower (varargin{1})) case {'mu', 's'} mu = varargin{2}; case 'sigma' sigma = varargin{2}; case {'df', 'nu'} df = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'tLocationScale' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.tLocationScaleDistribution (mu, sigma, df); case 'triangular' A = 0; B = 0.5; C = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'a' A = varargin{2}; case 'b' B = varargin{2}; case 'c' C = varargin{2}; otherwise error (strcat ("makedist: unknown parameter for", ... " 'Triangular' distribution.")); endswitch varargin([1:2]) = []; endwhile pd = prob.TriangularDistribution (A, B, C); case 'uniform' Lower = 0; Upper = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'lower' Lower = varargin{2}; case 'upper' Upper = varargin{2}; otherwise error ("makedist: unknown parameter for 'Uniform' distribution."); endswitch varargin([1:2]) = []; endwhile pd = prob.UniformDistribution (Lower, Upper); case 'weibull' lambda = 1; k = 1; while (numel (varargin) > 0) switch (tolower (varargin{1})) case {'lambda', 'a'} lambda = varargin{2}; case {'k', 'b'} k = varargin{2}; otherwise error ("makedist: unknown parameter for 'Weibull' distribution."); endswitch varargin([1:2]) = []; endwhile pd = prob.WeibullDistribution (lambda, k); endswitch endfunction ## Test output %!test %! pd = makedist ('beta'); %! assert_equal (class (pd), "prob.BetaDistribution"); %! assert_equal (pd.a, 1); %! assert_equal (pd.b, 1); %!test %! pd = makedist ('beta', 'a', 5); %! assert_equal (pd.a, 5); %! assert_equal (pd.b, 1); %!test %! pd = makedist ('beta', 'b', 5); %! assert_equal (pd.a, 1); %! assert_equal (pd.b, 5); %!test %! pd = makedist ('beta', 'a', 3, 'b', 5); %! assert_equal (pd.a, 3); %! assert_equal (pd.b, 5); %!test %! pd = makedist ('binomial'); %! assert_equal (class (pd), "prob.BinomialDistribution"); %! assert_equal (pd.N, 1); %! assert_equal (pd.p, 0.5); %!test %! pd = makedist ('binomial', 'N', 5); %! assert_equal (pd.N, 5); %! assert_equal (pd.p, 0.5); %!test %! pd = makedist ('binomial', 'p', 0.2); %! assert_equal (pd.N, 1); %! assert_equal (pd.p, 0.2); %!test %! pd = makedist ('binomial', 'N', 3, 'p', 0.3); %! assert_equal (pd.N, 3); %! assert_equal (pd.p, 0.3); %!test %! pd = makedist ('birnbaumsaunders'); %! assert_equal (class (pd), "prob.BirnbaumSaundersDistribution"); %! assert_equal (pd.beta, 1); %! assert_equal (pd.gamma, 1); %!test %! pd = makedist ('birnbaumsaunders', 'beta', 5); %! assert_equal (pd.beta, 5); %! assert_equal (pd.gamma, 1); %!test %! pd = makedist ('birnbaumsaunders', 'gamma', 5); %! assert_equal (pd.beta, 1); %! assert_equal (pd.gamma, 5); %!test %! pd = makedist ('birnbaumsaunders', 'beta', 3, 'gamma', 5); %! assert_equal (pd.beta, 3); %! assert_equal (pd.gamma, 5); %!test %! pd = makedist ('burr'); %! assert_equal (class (pd), "prob.BurrDistribution"); %! assert_equal (pd.alpha, 1); %! assert_equal (pd.c, 1); %! assert_equal (pd.k, 1); %!test %! pd = makedist ('burr', 'k', 5); %! assert_equal (pd.alpha, 1); %! assert_equal (pd.c, 1); %! assert_equal (pd.k, 5); %!test %! pd = makedist ('burr', 'c', 5); %! assert_equal (pd.alpha, 1); %! assert_equal (pd.c, 5); %! assert_equal (pd.k, 1); %!test %! pd = makedist ('burr', 'alpha', 3, 'c', 5); %! assert_equal (pd.alpha, 3); %! assert_equal (pd.c, 5); %! assert_equal (pd.k, 1); %!test %! pd = makedist ('burr', 'k', 3, 'c', 5); %! assert_equal (pd.alpha, 1); %! assert_equal (pd.c, 5); %! assert_equal (pd.k, 3); %!test %! pd = makedist ('exponential'); %! assert_equal (class (pd), "prob.ExponentialDistribution"); %! assert_equal (pd.mu, 1); %!test %! pd = makedist ('exponential', 'mu', 5); %! assert_equal (pd.mu, 5); %!test %! pd = makedist ('extremevalue'); %! assert_equal (class (pd), "prob.ExtremeValueDistribution"); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('extremevalue', 'mu', 5); %! assert_equal (class (pd), "prob.ExtremeValueDistribution"); %! assert_equal (pd.mu, 5); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('ev', 'sigma', 5); %! assert_equal (class (pd), "prob.ExtremeValueDistribution"); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 5); %!test %! pd = makedist ('ev', 'mu', -3, 'sigma', 5); %! assert_equal (class (pd), "prob.ExtremeValueDistribution"); %! assert_equal (pd.mu, -3); %! assert_equal (pd.sigma, 5); %!test %! pd = makedist ('gamma'); %! assert_equal (class (pd), "prob.GammaDistribution"); %! assert_equal (pd.a, 1); %! assert_equal (pd.b, 1); %!test %! pd = makedist ('gamma', 'a', 5); %! assert_equal (pd.a, 5); %! assert_equal (pd.b, 1); %!test %! pd = makedist ('gamma', 'b', 5); %! assert_equal (pd.a, 1); %! assert_equal (pd.b, 5); %!test %! pd = makedist ('gamma', 'a', 3, 'b', 5); %! assert_equal (pd.a, 3); %! assert_equal (pd.b, 5); %!test %! pd = makedist ('GeneralizedExtremeValue'); %! assert_equal (class (pd), "prob.GeneralizedExtremeValueDistribution"); %! assert_equal (pd.k, 0); %! assert_equal (pd.sigma, 1); %! assert_equal (pd.mu, 0); %!test %! pd = makedist ('GeneralizedExtremeValue', 'k', 5); %! assert_equal (pd.k, 5); %! assert_equal (pd.sigma, 1); %! assert_equal (pd.mu, 0); %!test %! pd = makedist ('GeneralizedExtremeValue', 'sigma', 5); %! assert_equal (pd.k, 0); %! assert_equal (pd.sigma, 5); %! assert_equal (pd.mu, 0); %!test %! pd = makedist ('GeneralizedExtremeValue', 'k', 3, 'sigma', 5); %! assert_equal (pd.k, 3); %! assert_equal (pd.sigma, 5); %! assert_equal (pd.mu, 0); %!test %! pd = makedist ('GeneralizedExtremeValue', 'mu', 3, 'sigma', 5); %! assert_equal (pd.k, 0); %! assert_equal (pd.sigma, 5); %! assert_equal (pd.mu, 3); %!test %! pd = makedist ('GeneralizedPareto'); %! assert_equal (class (pd), "prob.GeneralizedParetoDistribution"); %! assert_equal (pd.k, 1); %! assert_equal (pd.sigma, 1); %! assert_equal (pd.theta, 1); %!test %! pd = makedist ('GeneralizedPareto', 'k', 5); %! assert_equal (pd.k, 5); %! assert_equal (pd.sigma, 1); %! assert_equal (pd.theta, 1); %!test %! pd = makedist ('GeneralizedPareto', 'sigma', 5); %! assert_equal (pd.k, 1); %! assert_equal (pd.sigma, 5); %! assert_equal (pd.theta, 1); %!test %! pd = makedist ('GeneralizedPareto', 'k', 3, 'sigma', 5); %! assert_equal (pd.k, 3); %! assert_equal (pd.sigma, 5); %! assert_equal (pd.theta, 1); %!test %! pd = makedist ('GeneralizedPareto', 'theta', 3, 'sigma', 5); %! assert_equal (pd.k, 1); %! assert_equal (pd.sigma, 5); %! assert_equal (pd.theta, 3); %!test %! pd = makedist ('HalfNormal'); %! assert_equal (class (pd), "prob.HalfNormalDistribution"); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('HalfNormal', 'mu', 5); %! assert_equal (pd.mu, 5); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('HalfNormal', 'sigma', 5); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 5); %!test %! pd = makedist ('HalfNormal', 'mu', 3, 'sigma', 5); %! assert_equal (pd.mu, 3); %! assert_equal (pd.sigma, 5); %!test %! pd = makedist ('InverseGaussian'); %! assert_equal (class (pd), "prob.InverseGaussianDistribution"); %! assert_equal (pd.mu, 1); %! assert_equal (pd.lambda, 1); %!test %! pd = makedist ('InverseGaussian', 'mu', 5); %! assert_equal (pd.mu, 5); %! assert_equal (pd.lambda, 1); %!test %! pd = makedist ('InverseGaussian', 'lambda', 5); %! assert_equal (pd.mu, 1); %! assert_equal (pd.lambda, 5); %!test %! pd = makedist ('InverseGaussian', 'mu', 3, 'lambda', 5); %! assert_equal (pd.mu, 3); %! assert_equal (pd.lambda, 5); %!test %! pd = makedist ('logistic'); %! assert_equal (class (pd), "prob.LogisticDistribution"); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('logistic', 'mu', 5); %! assert_equal (pd.mu, 5); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('logistic', 'sigma', 5); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 5); %!test %! pd = makedist ('logistic', 'mu', 3, 'sigma', 5); %! assert_equal (pd.mu, 3); %! assert_equal (pd.sigma, 5); %!test %! pd = makedist ('loglogistic'); %! assert_equal (class (pd), "prob.LoglogisticDistribution"); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('loglogistic', 'mu', 5); %! assert_equal (pd.mu, 5); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('loglogistic', 'sigma', 5); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 5); %!test %! pd = makedist ('loglogistic', 'mu', 3, 'sigma', 5); %! assert_equal (pd.mu, 3); %! assert_equal (pd.sigma, 5); %!test %! pd = makedist ('Lognormal'); %! assert_equal (class (pd), "prob.LognormalDistribution"); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('Lognormal', 'mu', 5); %! assert_equal (pd.mu, 5); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('Lognormal', 'sigma', 5); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 5); %!test %! pd = makedist ('Lognormal', 'mu', -3, 'sigma', 5); %! assert_equal (pd.mu, -3); %! assert_equal (pd.sigma, 5); %!test %! pd = makedist ('Loguniform'); %! assert_equal (class (pd), "prob.LoguniformDistribution"); %! assert_equal (pd.Lower, 1); %! assert_equal (pd.Upper, 4); %!test %! pd = makedist ('Loguniform', 'Lower', 2); %! assert_equal (pd.Lower, 2); %! assert_equal (pd.Upper, 4); %!test %! pd = makedist ('Loguniform', 'Lower', 1, 'Upper', 3); %! assert_equal (pd.Lower, 1); %! assert_equal (pd.Upper, 3); %!test %! pd = makedist ('Multinomial'); %! assert_equal (class (pd), "prob.MultinomialDistribution"); %! assert_equal (pd.Probabilities, [0.5, 0.5]); %!test %! pd = makedist ('Multinomial', 'Probabilities', [0.2, 0.3, 0.1, 0.4]); %! assert_equal (class (pd), "prob.MultinomialDistribution"); %! assert_equal (pd.Probabilities, [0.2, 0.3, 0.1, 0.4]); %!test %! pd = makedist ('Nakagami'); %! assert_equal (class (pd), "prob.NakagamiDistribution"); %! assert_equal (pd.mu, 1); %! assert_equal (pd.omega, 1); %!test %! pd = makedist ('Nakagami', 'mu', 5); %! assert_equal (class (pd), "prob.NakagamiDistribution"); %! assert_equal (pd.mu, 5); %! assert_equal (pd.omega, 1); %!test %! pd = makedist ('Nakagami', 'omega', 0.3); %! assert_equal (class (pd), "prob.NakagamiDistribution"); %! assert_equal (pd.mu, 1); %! assert_equal (pd.omega, 0.3); %!test %! pd = makedist ('NegativeBinomial'); %! assert_equal (class (pd), "prob.NegativeBinomialDistribution"); %! assert_equal (pd.R, 1); %! assert_equal (pd.P, 0.5); %!test %! pd = makedist ('NegativeBinomial', 'R', 5); %! assert_equal (class (pd), "prob.NegativeBinomialDistribution"); %! assert_equal (pd.R, 5); %! assert_equal (pd.P, 0.5); %!test %! pd = makedist ('NegativeBinomial', 'p', 0.3); %! assert_equal (class (pd), "prob.NegativeBinomialDistribution"); %! assert_equal (pd.R, 1); %! assert_equal (pd.P, 0.3); %!test %! pd = makedist ('Normal'); %! assert_equal (class (pd), "prob.NormalDistribution"); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('Normal', 'mu', 5); %! assert_equal (class (pd), "prob.NormalDistribution"); %! assert_equal (pd.mu, 5); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('Normal', 'sigma', 5); %! assert_equal (class (pd), "prob.NormalDistribution"); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 5); %!test %! pd = makedist ('Normal', 'mu', -3, 'sigma', 5); %! assert_equal (class (pd), "prob.NormalDistribution"); %! assert_equal (pd.mu, -3); %! assert_equal (pd.sigma, 5); %!test %! pd = makedist ('PiecewiseLinear'); %! assert_equal (class (pd), "prob.PiecewiseLinearDistribution"); %! assert_equal (pd.x, [0, 1]); %! assert_equal (pd.Fx, [0, 1]); %!test %! pd = makedist ('PiecewiseLinear', 'x', [0, 1, 2], 'Fx', [0, 0.5, 1]); %! assert_equal (pd.x, [0, 1, 2]); %! assert_equal (pd.Fx, [0, 0.5, 1]); %!test %! ## a column is stored the same way, as MATLAB stores it %! pd = makedist ('PiecewiseLinear', 'x', [0; 1; 2], 'Fx', [0; 0.5; 1]); %! assert_equal (pd.x, [0, 1, 2]); %! assert_equal (pd.Fx, [0, 0.5, 1]); %!test %! ## Multinomial already agreed with MATLAB and is unchanged %! pd = makedist ('Multinomial', 'Probabilities', [0.2, 0.3, 0.5]); %! assert_equal (pd.Probabilities, [0.2, 0.3, 0.5]); %! assert_equal (size (pd.Probabilities), [1, 3]); %!test %! pd = makedist ('Poisson'); %! assert_equal (class (pd), "prob.PoissonDistribution"); %! assert_equal (pd.lambda, 1); %!test %! pd = makedist ('Poisson', 'lambda', 5); %! assert_equal (pd.lambda, 5); %!test %! pd = makedist ('Rayleigh'); %! assert_equal (class (pd), "prob.RayleighDistribution"); %! assert_equal (pd.B, 1); %!test %! pd = makedist ('Rayleigh', 'sigma', 5); %! assert_equal (pd.B, 5); %!test %! pd = makedist ('Rician'); %! assert_equal (class (pd), "prob.RicianDistribution"); %! assert_equal (pd.s, 1); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('Rician', 's', 3); %! assert_equal (pd.s, 3); %! assert_equal (pd.sigma, 1); %!test %! pd = makedist ('Rician', 'sigma', 3); %! assert_equal (pd.s, 1); %! assert_equal (pd.sigma, 3); %!test %! pd = makedist ('Rician', 's', 2, 'sigma', 3); %! assert_equal (pd.s, 2); %! assert_equal (pd.sigma, 3); %!test %! pd = makedist ('stable'); %! assert_equal (class (pd), "prob.StableDistribution"); %! assert_equal (pd.alpha, 2); %! assert_equal (pd.beta, 0); %!test %! pd = makedist ('tlocationscale'); %! assert_equal (class (pd), "prob.tLocationScaleDistribution"); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 1); %! assert_equal (pd.nu, 5); %!test %! pd = makedist ('tlocationscale', 'mu', 5); %! assert_equal (pd.mu, 5); %! assert_equal (pd.sigma, 1); %! assert_equal (pd.nu, 5); %!test %! pd = makedist ('tlocationscale', 'sigma', 2); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 2); %! assert_equal (pd.nu, 5); %!test %! pd = makedist ('tlocationscale', 'mu', 5, 'sigma', 2); %! assert_equal (pd.mu, 5); %! assert_equal (pd.sigma, 2); %! assert_equal (pd.nu, 5); %!test %! pd = makedist ('tlocationscale', 'nu', 1, 'sigma', 2); %! assert_equal (pd.mu, 0); %! assert_equal (pd.sigma, 2); %! assert_equal (pd.nu, 1); %!test %! pd = makedist ('tlocationscale', 'mu', -2, 'sigma', 3, 'nu', 1); %! assert_equal (pd.mu, -2); %! assert_equal (pd.sigma, 3); %! assert_equal (pd.nu, 1); %!test %! pd = makedist ('Triangular'); %! assert_equal (class (pd), "prob.TriangularDistribution"); %! assert_equal (pd.A, 0); %! assert_equal (pd.B, 0.5); %! assert_equal (pd.C, 1); %!test %! pd = makedist ('Triangular', 'A', -2); %! assert_equal (pd.A, -2); %! assert_equal (pd.B, 0.5); %! assert_equal (pd.C, 1); %!test %! pd = makedist ('Triangular', 'A', 0.5, 'B', 0.9); %! assert_equal (pd.A, 0.5); %! assert_equal (pd.B, 0.9); %! assert_equal (pd.C, 1); %!test %! pd = makedist ('Triangular', 'A', 1, 'B', 2, 'C', 5); %! assert_equal (pd.A, 1); %! assert_equal (pd.B, 2); %! assert_equal (pd.C, 5); %!test %! pd = makedist ('Uniform'); %! assert_equal (class (pd), "prob.UniformDistribution"); %! assert_equal (pd.Lower, 0); %! assert_equal (pd.Upper, 1); %!test %! pd = makedist ('Uniform', 'Lower', -2); %! assert_equal (pd.Lower, -2); %! assert_equal (pd.Upper, 1); %!test %! pd = makedist ('Uniform', 'Lower', 1, 'Upper', 3); %! assert_equal (pd.Lower, 1); %! assert_equal (pd.Upper, 3); %!test %! pd = makedist ('Weibull'); %! assert_equal (class (pd), "prob.WeibullDistribution"); %! assert_equal (pd.A, 1); %! assert_equal (pd.B, 1); %!test %! pd = makedist ('Weibull', 'lambda', 3); %! assert_equal (pd.A, 3); %! assert_equal (pd.B, 1); %!test %! pd = makedist ('Weibull', 'lambda', 3, 'k', 2); %! assert_equal (pd.A, 3); %! assert_equal (pd.B, 2); ## Test input validation %!error makedist (1) %!error makedist (['as';'sd']) %!error makedist ('some') %!error ... %! makedist ('Beta', 'a') %!error ... %! makedist ('Beta', 'a', 1, 'Q', 23) %!error ... %! makedist ('Binomial', 'N', 1, 'Q', 23) %!error ... %! makedist ('BirnbaumSaunders', 'N', 1) %!error ... %! makedist ('Burr', 'lambda', 1, 'sdfs', 34) %!error ... %! makedist ('extremevalue', 'mu', 1, 'sdfs', 34) %!error ... %! makedist ('exponential', 'mu', 1, 'sdfs', 34) %!error ... %! makedist ('Gamma', 'k', 1, 'sdfs', 34) %!error ... %! makedist ('GeneralizedExtremeValue', 'k', 1, 'sdfs', 34) %!error ... %! makedist ('GeneralizedPareto', 'k', 1, 'sdfs', 34) %!error ... %! makedist ('HalfNormal', 'k', 1, 'sdfs', 34) %!error ... %! makedist ('InverseGaussian', 'k', 1, 'sdfs', 34) %!error ... %! makedist ('Logistic', 'k', 1, 'sdfs', 34) %!error ... %! makedist ('Loglogistic', 'k', 1, 'sdfs', 34) %!error ... %! makedist ('Lognormal', 'k', 1, 'sdfs', 34) %!error ... %! makedist ('Loguniform', 'k', 1, 'sdfs', 34) %!error ... %! makedist ('Multinomial', 'k', 1, 'sdfs', 34) %!error ... %! makedist ('Nakagami', 'mu', 1, 'sdfs', 34) %!error ... %! makedist ('NegativeBinomial', 'mu', 1, 'sdfs', 34) %!error ... %! makedist ('Normal', 'mu', 1, 'sdfs', 34) %!error ... %! makedist ('PiecewiseLinear', 'mu', 1, 'sdfs', 34) %!error ... %! makedist ('Poisson', 'mu', 1, 'sdfs', 34) %!error ... %! makedist ('Rayleigh', 'mu', 1, 'sdfs', 34) %!error ... %! makedist ('Rician', 'mu', 1, 'sdfs', 34) %!error ... %! makedist ('Stable', 'mu', 1, 'sdfs', 34) %!error ... %! makedist ('tLocationScale', 'mu', 1, 'sdfs', 34) %!error ... %! makedist ('Triangular', 'mu', 1, 'sdfs', 34) %!error ... %! makedist ('Uniform', 'mu', 1, 'sdfs', 34) %!error ... %! makedist ('Weibull', 'mu', 1, 'sdfs', 34) statistics-release-1.9.2/inst/Distribution_Wrappers/mle.m000066400000000000000000001230701524624707500236240ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{phat} =} mle (@var{x}) ## @deftypefnx {statistics} {@var{phat} =} mle (@var{x}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{phat}, @var{pci}] =} mle (@dots{}) ## ## Compute maximum likelihood estimates. ## ## @code{@var{phat} = mle (@var{x})} returns the maximum likelihood estimates ## (MLEs) for the parameters of a normal distribution using the sample data in ## @var{x}, which must be a numeric vector of real values. ## ## @code{@var{phat} = mle (@var{x}, @var{Name}, @var{Value})} returns the MLEs ## with additional options specified by @qcode{Name-Value} pair arguments listed ## below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'distribution'} @tab A character vector specifying the ## distribution type for which to estimate parameters. ## ## @item @qcode{'Ntrials'} @tab A scalar specifying the number of trials ## for the corresponding element of @var{x} for the binomial distribution. ## ## @item @qcode{'theta'} @tab A scalar specifying the location parameter ## for the generalized Pareto distribution. It defaults to 0 and is not ## estimated: the data is shifted by it and only @var{k} and @var{sigma} are ## returned. ## ## @item @qcode{'mu'} @tab A scalar specifying the location parameter ## for the half-normal distribution. ## ## @item @qcode{'censoring'} @tab A vector of the same size as @var{x} ## indicating censored data in @var{x}. By default it is ## @qcode{@var{censor} = zeros (size (@var{x}))}. ## ## @item @qcode{'frequency'} @tab A vector of nonnegative integer counts of ## the same size as @var{x} used as frequency observations. By default it is ## @qcode{@var{freq} = ones (size (@var{x}))}. ## ## @item @qcode{'alpha'} @tab A scalar in the range @math{(0,1)}, as the ## significance level for the confidence interval @var{pci}. By default it is ## 0.05 corresponding to 95% confidence intervals. ## ## @item @qcode{'options'} @tab A structure specifying the control ## parameters for the iterative algorithm used to compute ML estimates with the ## @code{fminsearch} function. ## ## @item @qcode{'pdf'} @tab A function handle ## @code{@@(@var{data}, @var{p1}, @var{p2}, @dots{})} to the probability density ## of a @strong{custom} distribution, whose parameters are then estimated by ## maximum likelihood. Requires @qcode{'start'}. It is mutually exclusive with ## @qcode{'distribution'} and with @qcode{'logpdf'}/@qcode{'nloglf'}. ## ## @item @qcode{'cdf'} @tab A function handle to the cumulative distribution ## function of the custom distribution, with the same calling convention as ## @qcode{'pdf'}. Required together with @qcode{'pdf'} for censored or ## truncated data. ## ## @item @qcode{'logpdf'} @tab A function handle to the log probability density ## of a custom distribution, with the same calling convention as @qcode{'pdf'}. ## Requires @qcode{'start'}. ## ## @item @qcode{'logsf'} @tab A function handle to the log survivor function ## @math{log (1 - cdf)} of the custom distribution, with the same calling ## convention as @qcode{'pdf'}. Required together with @qcode{'logpdf'} for ## censored data. ## ## @item @qcode{'nloglf'} @tab A function handle ## @code{@@(@var{params}, @var{data}, @var{cens}, @var{freq})} returning the ## scalar negative log-likelihood of a custom distribution. Requires ## @qcode{'start'}. ## ## @item @qcode{'start'} @tab A vector of initial parameter values for a ## custom-distribution fit. Required with @qcode{'pdf'}, @qcode{'logpdf'}, or ## @qcode{'nloglf'}. ## ## @item @qcode{'lowerbound'} @tab A scalar or vector of lower bounds for the ## custom-distribution parameters. By default they are unbounded below. ## ## @item @qcode{'upperbound'} @tab A scalar or vector of upper bounds for the ## custom-distribution parameters. By default they are unbounded above. ## ## @item @qcode{'truncationbounds'} @tab A two-element vector @qcode{[L U]} ## giving the truncation interval of a custom distribution. Requires a ## @qcode{'cdf'} function. ## ## @item @qcode{'optimfun'} @tab The optimizer for a custom-distribution fit. ## Only @qcode{'fminsearch'} is supported; bounded fits are handled by internal ## reparameterization of the constrained parameters. ## @end multitable ## ## When a custom distribution is specified through @qcode{'pdf'}, ## @qcode{'logpdf'}, or @qcode{'nloglf'}, the parameters are estimated by ## maximizing the likelihood with @code{fminsearch}, and the second output ## @var{pci} gives asymptotic normal (Wald) confidence intervals computed from ## the observed Fisher information at @var{phat} (see @code{mlecov}). Bounded ## parameters are estimated on an internally reparameterized unconstrained ## scale. ## ## Distribution names are matched ignoring case, spaces and hyphens, so that ## @qcode{'Extreme Value'}, @qcode{'ExtremeValue'} and @qcode{'extreme-value'} ## all select the same distribution, and the same set of names is accepted by ## @code{cdf}, @code{pdf}, @code{icdf}, @code{random}, @code{makedist}, ## @code{fitdist} and @code{mle}. ## ## This accepts more names than MATLAB. MATLAB takes the spaced and the ## squashed spelling but refuses the hyphenated one, so ## @qcode{'Birnbaum-Saunders'} and @qcode{'Log-Logistic'} are errors there; ## Octave has always accepted them and continues to. MATLAB also accepts ## @qcode{'tLocationScale'} in @code{makedist} while refusing it in ## @code{cdf} for the same distribution; Octave accepts it, and ## @qcode{'location-scale T'}, everywhere. Code written against MATLAB's ## names therefore runs unchanged, but code relying on these names will not ## port back. ## ## @seealso{mlecov, fitdist, makedist} ## @end deftypefn function [phat, pci] = mle (x, varargin) ## Check data if (! (isvector (x) && isnumeric (x) && isreal (x))) error ("mle: X must be a numeric vector of real values."); endif ## Add defaults censor = []; freq = ones (size (x)); alpha = 0.05; ntrials = []; mu = 0; theta = 0; options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolX = 1e-6; distname = 'normal'; userdist = false; custpdf = []; custlogpdf = []; custnloglf = []; custcdf = []; custlogsf = []; start = []; lowbnd = []; uppbnd = []; truncbnd = []; optimfun = 'fminsearch'; ## Parse extra arguments if (mod (numel (varargin), 2) != 0) error ("mle: optional arguments must be in NAME-VALUE pairs."); endif while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'distribution' distname = varargin{2}; userdist = true; case 'censoring' censor = varargin{2}; if (! isequal (size (x), size (censor)) && ! isempty (censor)) error (strcat ("mle: 'censoring' argument must have the same", ... " size as the input data in X.")); endif case 'frequency' freq = varargin{2}; if (isempty (freq)) freq = ones (size (x)); elseif (! isequal (size (x), size (freq))) error (strcat ("mle: 'frequency' argument must have the same", ... " size as the input data in X.")); endif if (any (freq != round (freq)) || any (freq < 0)) error (strcat ("mle: 'frequency' argument must contain", ... " non-negative integer values.")); endif case 'alpha' alpha = varargin{2}; if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("mle: invalid value for 'alpha' argument."); endif case 'ntrials' ntrials = varargin{2}; if (! (isscalar (ntrials) && isreal (ntrials) && ntrials > 0 && fix (ntrials) == ntrials)) error (strcat ("mle: 'ntrials' argument must be a positive", ... " integer scalar value.")); endif case {'mu'} mu = varargin{2}; case {'theta'} theta = varargin{2}; case 'options' options = varargin{2}; if (! isstruct (options) || ! isfield (options, 'Display') || ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter') || ! isfield (options, 'TolX')) error (strcat ("mle: 'options' argument must be a structure", ... " compatible for 'fminsearch'.")); endif case 'pdf' custpdf = varargin{2}; case 'logpdf' custlogpdf = varargin{2}; case 'nloglf' custnloglf = varargin{2}; case 'cdf' custcdf = varargin{2}; case 'logsf' custlogsf = varargin{2}; case 'start' start = varargin{2}; case 'lowerbound' lowbnd = varargin{2}; case 'upperbound' uppbnd = varargin{2}; case 'truncationbounds' truncbnd = varargin{2}; case 'optimfun' optimfun = varargin{2}; otherwise error ("mle: unknown parameter name."); endswitch varargin([1:2]) = []; endwhile ## Custom-distribution fit through user-supplied likelihood functions. When a ## 'pdf', 'logpdf', or 'nloglf' handle is given, the parameters are estimated ## by maximum likelihood and the named-distribution path below is bypassed. ncust = (! isempty (custpdf)) + (! isempty (custlogpdf)) ... + (! isempty (custnloglf)); if (ncust > 0) if (userdist) error (strcat ("mle: the 'distribution' argument cannot be combined", ... " with a custom 'pdf', 'logpdf', or 'nloglf' function.")); endif if (ncust > 1) error (strcat ("mle: only one of the 'pdf', 'logpdf', or 'nloglf'", ... " arguments can be specified.")); endif [phat, pci] = mle_custom (x, custpdf, custlogpdf, custnloglf, custcdf, ... custlogsf, start, lowbnd, uppbnd, truncbnd, ... censor, freq, alpha, options, optimfun, nargout); return; endif ## Switch to known distributions switch (__distname_key__ (distname)) case 'bernoulli' if (! isempty (censor)) error (strcat ("mle: censoring is not supported for", ... " the Bernoulli distribution.")); elseif (any (x != 0 & x != 1)) error ("mle: invalid data for the Bernoulli distribution."); endif if (! isempty (freq)) x = expandFreq (x, freq); endif if (nargout < 2) phat = binofit (sum (x), numel (x)); else [phat, pci] = binofit (sum (x), numel (x), alpha); endif case 'beta' if (! isempty (censor)) error ("mle: censoring is not supported for the Beta distribution."); endif if (nargout < 2) phat = betafit (x, alpha, freq, options); else [phat, pci] = betafit (x, alpha, freq, options); endif case {'binomial', 'bino'} if (! isempty (censor)) error (strcat ("mle: censoring is not supported for", ... " the Binomial distribution.")); elseif (isempty (ntrials)) error (strcat ("mle: 'Ntrials' parameter is required", ... " for the Binomial distribution.")); endif if (nargout < 2) phat = binofit (sum (x .* freq), sum (freq) .* ntrials); else [phat, pci] = binofit (sum (x .* freq), sum (freq) .* ntrials, alpha); ## binofit reports the interval as a row; mle reports one column per ## parameter, as it does for every other distribution pci = pci(:); endif case {'bisa', 'birnbaumsaunders'} if (nargout < 2) phat = bisafit (x, alpha, censor, freq, options); else [phat, pci] = bisafit (x, alpha, censor, freq, options); endif case 'burr' if (nargout < 2) phat = burrfit (x, alpha, censor, freq, options); else [phat, pci] = burrfit (x, alpha, censor, freq, options); endif case {'ev', 'extremevalue'} if (nargout < 2) phat = evfit (x, alpha, censor, freq, options); else [phat, pci] = evfit (x, alpha, censor, freq, options); endif case {'exp', 'exponential'} if (nargout < 2) phat = expfit (x, alpha, censor, freq); else [phat, pci] = expfit (x, alpha, censor, freq); endif case {'gam', 'gamma'} if (nargout < 2) phat = gamfit (x, alpha, censor, freq, options); else [phat, pci] = gamfit (x, alpha, censor, freq, options); endif case {'geo', 'geometric'} if (! isempty (censor)) error (strcat ("mle: censoring is not supported for the", ... " Geometric distribution.")); endif if (nargout < 2) phat = geofit (x, alpha, freq); else [phat, pci] = geofit (x, alpha, freq); endif case {'gev', 'generalizedextremevalue'} if (! isempty (censor)) error (strcat ("mle: censoring is not supported for the", ... " Generalized Extreme Value distribution.")); endif if (nargout < 2) phat = gevfit (x, alpha, freq, options); else [phat, pci] = gevfit (x, alpha, freq, options); endif case {'gp', 'generalizedpareto'} if (! isempty (censor)) error (strcat ("mle: censoring is not supported for", ... " the Generalized Pareto distribution.")); endif if (any (x < theta)) error (strcat ("mle: invalid 'theta' location parameter", ... " for the Generalized Pareto distribution.")); endif ## GPFIT assumes a zero location, so shift the data by the known THETA, ## which leaves the estimates of K and SIGMA unchanged. if (nargout < 2) phat = gpfit (x - theta, alpha, options, freq); else [phat, pci] = gpfit (x - theta, alpha, options, freq); endif case 'gumbel' if (nargout < 2) phat = gumbelfit (x, alpha, censor, freq, options); else [phat, pci] = gumbelfit (x, alpha, censor, freq, options); endif case {'hn', 'halfnormal'} if (! isempty (censor)) error (strcat ("mle: censoring is not supported for", ... " the Half Normal distribution.")); endif if (any (x < mu)) error (strcat ("mle: invalid 'mu' location parameter", ... " for the Half Normal distribution.")); endif if (nargout < 2) phat = hnfit (x, mu, alpha, freq); else [phat, pci] = hnfit (x, mu, alpha, freq); endif case {'invg', 'inversegaussian'} if (nargout < 2) phat = invgfit (x, alpha, censor, freq, options); else [phat, pci] = invgfit (x, alpha, censor, freq, options); endif case {'logi', 'logistic'} if (nargout < 2) phat = logifit (x, alpha, censor, freq, options); else [phat, pci] = logifit (x, alpha, censor, freq, options); endif case {'logl', 'loglogistic'} if (nargout < 2) phat = loglfit (x, alpha, censor, freq, options); else [phat, pci] = loglfit (x, alpha, censor, freq, options); endif case {'logn', 'lognormal'} if (nargout < 2) phat = lognfit (x, alpha, censor, freq, options); else [phat, pci] = lognfit (x, alpha, censor, freq, options); endif phat(2) = mle_sigma (phat(2), censor, freq); case {'naka', 'nakagami'} if (nargout < 2) phat = nakafit (x, alpha, censor, freq, options); else [phat, pci] = nakafit (x, alpha, censor, freq, options); endif case {'nbin', 'negativebinomial'} if (! isempty (censor)) error (strcat ("mle: censoring is not supported for", ... " the Negative Binomial distribution.")); endif if (nargout < 2) phat = nbinfit (x, alpha, freq, options); else [phat, pci] = nbinfit (x, alpha, freq, options); endif case {'norm', 'normal'} if (nargout < 2) [muhat, sigmahat] = normfit (x, alpha, censor, freq, options); phat = [muhat, mle_sigma(sigmahat, censor, freq)]; else [muhat, sigmahat, muci, sigmaci] = normfit (x, alpha, censor, ... freq, options); phat = [muhat, mle_sigma(sigmahat, censor, freq)]; pci = [muci, sigmaci]; endif case {'poiss', 'poisson'} if (! isempty (censor)) error (strcat ("mle: censoring is not supported for", ... " the Poisson distribution.")); endif if (nargout < 2) phat = poissfit (x, alpha, freq); else [phat, pci] = poissfit (x, alpha, freq); endif case {'rayl', 'rayleigh'} if (nargout < 2) phat = raylfit (x, alpha, censor, freq); else [phat, pci] = raylfit (x, alpha, censor, freq); endif case {'rice', 'rician'} if (nargout < 2) phat = ricefit (x, alpha, censor, freq, options); else [phat, pci] = ricefit (x, alpha, censor, freq, options); endif case {'stbl', 'stable'} if (! isempty (censor)) error (strcat ("mle: censoring is not supported for", ... " the Stable distribution.")); endif if (nargout < 2) phat = stblfit (x, alpha, freq, options); else [phat, pci] = stblfit (x, alpha, freq, options); endif case {'tls', 'tlocationscale', 'locationscalet'} if (nargout < 2) phat = tlsfit (x, alpha, censor, freq, options); else [phat, pci] = tlsfit (x, alpha, censor, freq, options); endif case {'unid', 'uniformdiscrete', 'discreteuniform', 'discrete'} if (! isempty (censor)) error (strcat ("mle: censoring is not supported for", ... " the Discrete Uniform distribution.")); endif if (nargout < 2) phat = unidfit (x, alpha, freq); else [phat, pci] = unidfit (x, alpha, freq); endif case {'unif', 'uniform', 'continuousuniform'} if (! isempty (censor)) error (strcat ("mle: censoring is not supported for", ... " the Continuous Uniform distribution.")); endif ## UNIFIT returns one output per endpoint, as MATLAB's does, which MLE ## packs into its own parameter vector and interval matrix. if (nargout < 2) [ahat, bhat] = unifit (x, alpha, freq); phat = [ahat, bhat]; else [ahat, bhat, aci, bci] = unifit (x, alpha, freq); phat = [ahat, bhat]; pci = [aci, bci]; endif case {'wbl', 'weibull'} if (nargout < 2) phat = wblfit (x, alpha, censor, freq, options); else [phat, pci] = wblfit (x, alpha, censor, freq, options); endif otherwise error ("mle: unrecognized distribution name."); endswitch endfunction ## Helper function for expanding data according to frequency vector function [x, freq] = expandFreq (x, freq) ## Drop observations whose frequency is NaN remove = isnan (freq); x(remove) = []; freq(remove) = []; ## Repeat each observation according to its frequency. Building the ## expanded vector only when some frequency differed from 1 left it ## unassigned in exactly the default case, so every call without a frequency ## vector died on an undefined variable. repelem covers all of it, drops ## zero-frequency observations, and keeps the orientation of X. x = repelem (x, freq); freq = ones (size (x)); endfunction ## Maximum likelihood fit of a user-supplied custom distribution function [phat, pci] = mle_custom (x, custpdf, custlogpdf, custnloglf, ... custcdf, custlogsf, start, lowbnd, uppbnd, truncbnd, ... censor, freq, alpha, options, optimfun, nout) ## Only fminsearch is available in core Octave; bounded fits are handled by ## reparameterization, so a separate constrained optimizer is not required. if (! (ischar (optimfun) && strcmpi (optimfun, 'fminsearch'))) error (strcat ("mle: 'optimfun' only supports 'fminsearch'; bounded", ... " fits are handled by internal reparameterization.")); endif ## 'start' is required and sets the number of parameters if (isempty (start)) error (strcat ("mle: a 'start' vector of initial parameter values is", ... " required for a custom distribution fit.")); endif if (! (isvector (start) && isnumeric (start) && isreal (start))) error ("mle: 'start' must be a numeric vector of real values."); endif start = start(:).'; k = numel (start); ## Identify the input form and validate the supplied handles if (! isempty (custpdf)) form = 'pdf'; if (! is_function_handle (custpdf)) error ("mle: 'pdf' argument must be a function handle."); endif elseif (! isempty (custlogpdf)) form = 'logpdf'; if (! is_function_handle (custlogpdf)) error ("mle: 'logpdf' argument must be a function handle."); endif else form = 'nloglf'; if (! is_function_handle (custnloglf)) error ("mle: 'nloglf' argument must be a function handle."); endif endif if (! isempty (custcdf) && ! is_function_handle (custcdf)) error ("mle: 'cdf' argument must be a function handle."); endif if (! isempty (custlogsf) && ! is_function_handle (custlogsf)) error ("mle: 'logsf' argument must be a function handle."); endif ## Data, censoring, and frequency as columns x = x(:); n = numel (x); if (isempty (censor)) cens = false (n, 1); else cens = logical (censor(:)); endif freq = freq(:); docens = any (cens); ## Truncation bounds dotrunc = ! isempty (truncbnd); if (dotrunc && ! (isnumeric (truncbnd) && isreal (truncbnd) && numel (truncbnd) == 2 && truncbnd(1) < truncbnd(2))) error (strcat ("mle: 'truncationbounds' must be a two-element vector", ... " [L U] with L < U.")); endif ## Censoring and truncation need the complementary functions if (strcmp (form, 'pdf') && (docens || dotrunc) && isempty (custcdf)) error (strcat ("mle: a 'cdf' function handle is required for censored", ... " or truncated data when using the 'pdf' argument.")); endif if (strcmp (form, 'logpdf') && docens && isempty (custlogsf)) error (strcat ("mle: a 'logsf' function handle is required for", ... " censored data when using the 'logpdf' argument.")); endif if (strcmp (form, 'logpdf') && dotrunc && isempty (custcdf)) error (strcat ("mle: a 'cdf' function handle is required for truncated", ... " data when using the 'logpdf' argument.")); endif ## Parameter bounds, expanded to per-parameter row vectors if (isempty (lowbnd)) lb = -Inf (1, k); elseif (isscalar (lowbnd)) lb = lowbnd * ones (1, k); elseif (numel (lowbnd) == k) lb = lowbnd(:).'; else error ("mle: 'lowerbound' must be a scalar or match the size of 'start'."); endif if (isempty (uppbnd)) ub = Inf (1, k); elseif (isscalar (uppbnd)) ub = uppbnd * ones (1, k); elseif (numel (uppbnd) == k) ub = uppbnd(:).'; else error ("mle: 'upperbound' must be a scalar or match the size of 'start'."); endif if (any (lb >= ub)) error (strcat ("mle: each 'lowerbound' must be strictly less than its", ... " corresponding 'upperbound'.")); endif if (any (start <= lb) || any (start >= ub)) error ("mle: 'start' values must lie strictly within the given bounds."); endif ## Aggregate negative log-likelihood of the sample at a parameter row vector nllfun = @(th) custom_nll (th, form, custpdf, custlogpdf, custnloglf, ... custcdf, custlogsf, x, cens, freq, docens, ... dotrunc, truncbnd); ## Maximize the likelihood. Bounded parameters are optimized on an ## unconstrained transformed scale and mapped back. if (any (isfinite (lb)) || any (isfinite (ub))) obj = @(u) nllfun (to_con (u, lb, ub)); uopt = fminsearch (obj, to_uncon (start, lb, ub), options); phat = to_con (uopt, lb, ub); else phat = fminsearch (nllfun, start, options); endif ## Asymptotic (Wald) confidence intervals from the observed information if (nout > 1) acov = mlecov (phat, x, 'nloglf', @(pp, dd, cc, ff) nllfun (pp)); se = sqrt (diag (acov)).'; z = norminv (1 - alpha / 2); pci = [phat - z .* se; phat + z .* se]; else pci = []; endif endfunction ## Aggregate negative log-likelihood for a custom distribution function nll = custom_nll (th, form, cpdf, clogpdf, cnloglf, ccdf, clogsf, ... x, cens, freq, docens, dotrunc, tb) if (strcmp (form, 'nloglf')) nll = cnloglf (th, x, double (cens), freq); return; endif pc = num2cell (th); unc = ! cens; terms = zeros (size (x)); if (strcmp (form, 'pdf')) terms(unc) = log (cpdf (x(unc), pc{:})); if (docens) terms(cens) = log (1 - ccdf (x(cens), pc{:})); endif else terms(unc) = clogpdf (x(unc), pc{:}); if (docens) terms(cens) = clogsf (x(cens), pc{:}); endif endif if (dotrunc) logZ = log (ccdf (tb(2), pc{:}) - ccdf (tb(1), pc{:})); ## Right-censored survival is renormalized to the truncation interval if (docens) terms(cens) = log (ccdf (tb(2), pc{:}) - ccdf (x(cens), pc{:})); endif terms = terms - logZ; endif nll = -sum (freq .* terms); endfunction ## Map constrained parameters to an unconstrained scale for optimization function u = to_uncon (th, lb, ub) u = th; for i = 1:numel (th) if (isfinite (lb(i)) && isfinite (ub(i))) u(i) = log ((th(i) - lb(i)) / (ub(i) - th(i))); elseif (isfinite (lb(i))) u(i) = log (th(i) - lb(i)); elseif (isfinite (ub(i))) u(i) = log (ub(i) - th(i)); endif endfor endfunction ## Map unconstrained parameters back to the constrained scale function th = to_con (u, lb, ub) th = u; for i = 1:numel (u) if (isfinite (lb(i)) && isfinite (ub(i))) th(i) = lb(i) + (ub(i) - lb(i)) / (1 + exp (-u(i))); elseif (isfinite (lb(i))) th(i) = lb(i) + exp (u(i)); elseif (isfinite (ub(i))) th(i) = ub(i) - exp (u(i)); endif endfor endfunction ## Turn an unbiased standard deviation into the maximum likelihood estimate. ## normfit and lognfit deliberately return the unbiased estimator when the data ## are uncensored, and each documents the correction needed to recover the MLE; ## mle promises the MLE, so it applies that correction here. With censoring ## both fits maximise the likelihood directly and no correction is wanted. function s = mle_sigma (s, censor, freq) if (isempty (censor) || ! any (censor)) n = sum (freq); s = s .* sqrt ((n - 1) ./ n); endif endfunction %!demo %! ## Fit a custom (normal) distribution by maximum likelihood and return the %! ## asymptotic 95% confidence intervals of the estimates. %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! pdf = @(x, mu, sigma) normpdf (x, mu, sigma); %! [phat, pci] = mle (x, 'pdf', pdf, 'start', [mean(x), std(x)]) ## Two distribution families were dead. 'bernoulli' expanded its data through ## a helper that only assigned the expanded vector when some frequency ## differed from 1, so the default case died on an undefined variable, and ## 'unif' called a uniffit that does not exist -- the function is unifit. %!test %! x = [1 0 1 0 1 1 0 1]; %! assert_equal (mle (x, 'distribution', 'bernoulli'), mean (x), 1e-12); %!test %! x = [1 0 1 0 1 1 0 1]; %! [phat, pci] = mle (x, 'distribution', 'bernoulli'); %! [bp, bci] = binofit (sum (x), numel (x), 0.05); %! assert_equal (phat, bp); %! assert_equal (pci, bci); %!test %! ## a frequency vector expands the sample, and a zero frequency drops it %! assert_equal (mle ([1, 0], 'distribution', 'bernoulli', ... %! 'frequency', [3, 5]), 3/8, 1e-12); %! assert_equal (mle ([1, 0], 'distribution', 'bernoulli', ... %! 'frequency', [2, 0]), 1, 1e-12); %!test %! ## a column vector must work as well as a row %! assert_equal (mle ([1;0;1;1], 'distribution', 'bernoulli'), 0.75, 1e-12); %!test %! ## the uniform MLE is the sample range %! u = [0.2, 0.5, 0.7, 0.9, 0.35]; %! assert_equal (mle (u, 'distribution', 'unif'), [min(u), max(u)]); %! assert_equal (mle (u, 'distribution', 'uniform'), [min(u), max(u)]); %! assert_equal (mle (u, 'distribution', 'continuous uniform'), ... %! [min(u), max(u)]); %!test %! u = [0.2, 0.5, 0.7, 0.9, 0.35]; %! [phat, pci] = mle (u, 'distribution', 'uniform'); %! [ahat, bhat, aci, bci] = unifit (u, 0.05); %! assert_equal (phat, [ahat, bhat]); %! assert_equal (pci, [aci, bci]); ## mle returns the maximum likelihood estimate, so the scale parameter is ## std (x, 1) and not normfit's unbiased std (x, 0). It used to return the ## unbiased value, contradicting both its own name and its custom-pdf path. %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]'; %! phat = mle (x, 'distribution', 'normal'); %! assert_equal (phat(1), mean (x), 1e-12); %! assert_equal (phat(2), std (x, 1), 1e-12); %! assert_equal (isequal (abs (phat(2) - std (x, 0)) < 1e-12, true), false); %!test %! ## the named path and the equivalent custom pdf must agree %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]'; %! a = mle (x, 'distribution', 'normal'); %! b = mle (x, 'pdf', @(v, m, s) normpdf (v, m, s), 'start', [3, 1]); %! assert_equal (a, b, 1e-4); %!test %! ## lognfit is a normal fit on the logs, so it carried the same bias %! x = [1.2, 2.4, 0.9, 3.2, 1.1, 2.8, 1.3, 4.7, 2.2, 0.6, 3.0, 1.5]'; %! phat = mle (x, 'distribution', 'lognormal'); %! assert_equal (phat(2), std (log (x), 1), 1e-12); %!test %! ## a frequency vector scales the effective sample size %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8]'; %! f = [1, 2, 1, 3, 1, 2]'; %! phat = mle (x, 'distribution', 'normal', 'frequency', f); %! xx = repelem (x, f); %! assert_equal (phat(2), std (xx, 1), 1e-10); %!test %! ## with censoring normfit already maximises the likelihood: no correction %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]'; %! c = [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0]'; %! ref = normfit (x, 0.05, c); %! [~, s] = normfit (x, 0.05, c); %! phat = mle (x, 'distribution', 'normal', 'censoring', c); %! assert_equal (phat(2), s, 1e-12); %!test %! ## the confidence interval is normfit's and must not shift %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]'; %! [~, pci] = mle (x, 'distribution', 'normal'); %! [~, ~, muci, sci] = normfit (x); %! assert_equal (pci, [muci, sci], 1e-12); ## Test custom-distribution fitting (values verified against MATLAB) %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! [phat, pci] = mle (x, 'pdf', @(x, mu, s) normpdf (x, mu, s), ... %! 'start', [mean(x), std(x)]); %! assert_equal (phat, [3.42499970800201, 1.03208912390818], 1e-4); %! assert_equal (pci, [2.8410510441517, 0.619175174501268; ... %! 4.00894837185232, 1.44500307331509], 1e-4); %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! nll = @(p, d, c, f) -sum (log (normpdf (d, p(1), p(2)))); %! phat = mle (x, 'nloglf', nll, 'start', [3, 1]); %! assert_equal (phat, [3.42499959294639, 1.03208933560634], 1e-4); %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! phat = mle (x, 'logpdf', @(x, mu, s) log (normpdf (x, mu, s)), ... %! 'start', [3, 1]); %! assert_equal (phat, [3.42499959294639, 1.03208933560634], 1e-4); %!test %! ## Alpha propagates into the Wald interval %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! [~, pci] = mle (x, 'pdf', @(x, mu, s) normpdf (x, mu, s), ... %! 'start', [mean(x), std(x)], 'alpha', 0.10); %! assert_equal (pci, [2.93493454085403, 0.685560813868748; ... %! 3.91506487514999, 1.37861743394761], 1e-4); %!test %! ## Frequency-weighted fit %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! f = [1, 2, 1, 1, 3, 1, 2, 1, 1, 1, 2, 1]; %! phat = mle (x, 'pdf', @(x, mu, s) normpdf (x, mu, s), ... %! 'start', [mean(x), std(x)], 'frequency', f); %! assert_equal (phat, [3.47058837030174, 0.902783454993327], 1e-4); %!test %! ## Right-censored fit (needs a cdf) %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! c = [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0]; %! phat = mle (x, 'pdf', @(x, mu, s) normpdf (x, mu, s), ... %! 'cdf', @(x, mu, s) normcdf (x, mu, s), ... %! 'start', [mean(x), std(x)], 'censoring', c); %! assert_equal (phat, [3.62195934926527, 1.03307264832117], 1e-4); %!test %! ## Bounded fit via reparameterization (lower bound inactive at the optimum) %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! phat = mle (x, 'pdf', @(x, mu) exppdf (x, mu), 'start', 3, 'lowerbound', 0); %! assert_equal (phat, 3.42499980926514, 1e-4); %!test %! ## Truncated fit on [1, 6] %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! phat = mle (x, 'pdf', @(x, mu, s) normpdf (x, mu, s), ... %! 'cdf', @(x, mu, s) normcdf (x, mu, s), ... %! 'start', [mean(x), std(x)], 'truncationbounds', [1, 6]); %! assert_equal (phat, [3.41148048015442, 1.12176348358835], 1e-4); %!test %! ## The BirnbaumSaunders distribution is reachable by its full name, not only %! ## by 'bisa'; the name is matched after tolower, so a mixed-case label was %! ## never matched by anything. %! x = [1.2; 0.4; 3.1; 0.7; 2.5; 1.8; 0.3; 4.2; 1.1; 0.9; ... %! 2.2; 0.6; 1.5; 3.7; 0.8; 2.9; 1.3; 0.5; 2.0; 1.6]; %! assert_equal (mle (x, 'distribution', 'BirnbaumSaunders'), ... %! mle (x, 'distribution', 'bisa')); %! assert_equal (mle (x, 'distribution', 'birnbaumsaunders'), ... %! mle (x, 'distribution', 'bisa')); ## Values below are R2024a's, measured 2026-08-17. %!test %! ## the Generalized Pareto location defaults to zero and is not returned %! x = [2.2196, 11.9301, 4.3673, 1.0949, 6.5626, ... %! 1.2109, 1.8576, 1.0039, 12.7917, 2.2590]; %! assert_equal (mle (x, 'distribution', 'gp'), ... %! [-0.163107819293798, 5.305483917184919], 1e-4); %!test %! ## a known location shifts the data, leaving two parameters estimated %! x = [2.2196, 11.9301, 4.3673, 1.0949, 6.5626, ... %! 1.2109, 1.8576, 1.0039, 12.7917, 2.2590]; %! assert_equal (mle (x, 'distribution', 'gp', 'theta', 1), ... %! [0.893710299404345, 1.322962458731574], 1e-6); %!test %! ## the Continuous Uniform interval holds one endpoint per column %! u = [0.2, 0.5, 0.7, 0.9, 0.35]; %! [phat, pci] = mle (u, 'distribution', 'unif'); %! assert_equal (phat, [0.2, 0.9], 1e-12); %! assert_equal (pci, [-0.374394942118256, 0.9; ... %! 0.2, 1.474394942118256], 1e-12); ## Test input validation %!error mle (ones (2)) %!error mle ('text') %!error mle ([1, 2, 3, i, 5]) %!error ... %! mle ([1:50], 'distribution') %!error ... %! mle ([1:50], 'censoring', logical ([1,0,1,0])) %!error ... %! mle ([1:50], 'frequency', [1,0,1,0]) %!error ... %! mle ([1 0 1 0], 'frequency', [-1 1 0 0]) %!error ... %! mle ([1 0 1 0], 'distribution', 'nbin', 'frequency', [-1 1 0 0]) %!error mle ([1:50], 'alpha', [0.05, 0.01]) %!error mle ([1:50], 'alpha', 1) %!error mle ([1:50], 'alpha', -1) %!error mle ([1:50], 'alpha', i) %!error ... %! mle ([1:50], 'ntrials', -1) %!error ... %! mle ([1:50], 'ntrials', [20, 50]) %!error ... %! mle ([1:50], 'ntrials', [20.3]) %!error ... %! mle ([1:50], 'ntrials', 3i) %!error ... %! mle ([1:50], 'options', 4) %!error ... %! mle ([1:50], 'options', struct ('x', 3)) %!error mle ([1:50], 'NAME', 'value') %!error ... %! mle ([1 0 1 0], 'distribution', 'bernoulli', 'censoring', [1 1 0 0]) %!error ... %! mle ([1 2 1 0], 'distribution', 'bernoulli') %!error ... %! mle ([1 0 1 0], 'distribution', 'beta', 'censoring', [1 1 0 0]) %!error ... %! mle ([1 0 1 0], 'distribution', 'bino', 'censoring', [1 1 0 0]) %!error ... %! mle ([1 0 1 0], 'distribution', 'bino') %!error ... %! mle ([1 0 1 0], 'distribution', 'geo', 'censoring', [1 1 0 0]) %!error ... %! mle ([1 0 1 0], 'distribution', 'gev', 'censoring', [1 1 0 0]) %!error ... %! mle ([1 0 1 0], 'distribution', 'gp', 'censoring', [1 1 0 0]) %!error ... %! mle ([1 0 -1 0], 'distribution', 'gp') %!error ... %! mle ([1 0 1 0], 'distribution', 'hn', 'censoring', [1 1 0 0]) %!error ... %! mle ([1 0 -1 0], 'distribution', 'hn') %!error ... %! mle ([1 0 1 0], 'distribution', 'nbin', 'censoring', [1 1 0 0]) %!error ... %! mle ([1 0 1 0], 'distribution', 'poisson', 'censoring', [1 1 0 0]) %!error ... %! mle ([1 0 1 0], 'distribution', 'unid', 'censoring', [1 1 0 0]) %!error ... %! mle ([1 0 1 0], 'distribution', 'unif', 'censoring', [1 1 0 0]) %!error mle ([1:50], 'distribution', 'value') %!error ... %! mle ([1 0 1 0], 'distribution', 'unif', 'censoring', [1 1 0 0]) %!error ... %! mle ([1:50], 'distribution', 'normal', 'pdf', @(x, a, b) normpdf (x, a, b)) %!error ... %! mle ([1:50], 'pdf', @sin, 'nloglf', @cos) %!error ... %! mle ([1:50], 'pdf', @(x, a, b) normpdf (x, a, b)) %!error ... %! mle ([1:50], 'pdf', @(x, a, b) normpdf (x, a, b), 'start', 'text') %!error ... %! mle ([1:50], 'pdf', 5, 'start', [0, 1]) %!error ... %! mle ([1:50], 'nloglf', 5, 'start', [0, 1]) %!error ... %! mle ([1:50], 'pdf', @(x, a, b) normpdf (x, a, b), 'cdf', 5, 'start', [0, 1]) %!error ... %! mle ([1:50], 'pdf', @(x, a, b) normpdf (x, a, b), 'start', [0, 1], ... %! 'censoring', [1, zeros(1, 49)]) %!error ... %! mle ([1:50], 'pdf', @(x, a, b) normpdf (x, a, b), ... %! 'cdf', @(x, a, b) normcdf (x, a, b), 'start', [0, 1], ... %! 'truncationbounds', [6, 1]) %!error ... %! mle ([1:50], 'pdf', @(x, a, b) normpdf (x, a, b), 'start', [0, 1], ... %! 'lowerbound', [0, 2], 'upperbound', [0, 5]) %!error ... %! mle ([1:50], 'pdf', @(x, a, b) normpdf (x, a, b), 'start', [0, 1], ... %! 'lowerbound', [1, 0]) %!error ... %! mle ([1:50], 'pdf', @(x, a, b) normpdf (x, a, b), 'start', [0, 1], ... %! 'optimfun', 'fmincon') statistics-release-1.9.2/inst/Distribution_Wrappers/mlecov.m000066400000000000000000000471401524624707500243370ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{acov} =} mlecov (@var{params}, @var{data}, @var{Name}, @var{Value}) ## ## Asymptotic covariance matrix of maximum likelihood estimators. ## ## @code{@var{acov} = mlecov (@var{params}, @var{data}, @dots{})} ## returns an approximation to the asymptotic covariance matrix of the maximum ## likelihood estimators of the parameters of a distribution, evaluated at the ## parameter values in @var{params} for the sample data in @var{data}. ## @var{params} is a numeric vector of parameter values (typically the estimates ## returned by @code{mle} or @code{fitdist}) and @var{data} is a numeric vector ## of the sample observations. @var{acov} is a @math{p*p} matrix, where ## @math{p = numel (@var{params})}. ## ## The distribution is not identified by name; instead it is supplied through ## @qcode{Name-Value} paired arguments that give function handles to its ## density, its log density, or its negative log-likelihood. Exactly ## @strong{one} of the following three arguments must be specified: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'pdf'} @tab A function handle, ## @code{@var{f}(@var{data}, @var{p1}, @var{p2}, @dots{})}, that accepts the ## sample data as its first argument and the distribution parameters as ## subsequent scalar arguments, and returns a vector of probability density ## values, one per observation. ## ## @item @qcode{'logpdf'} @tab A function handle, ## @code{@var{f}(@var{data}, @var{p1}, @var{p2}, @dots{})}, with the same ## calling convention as @qcode{'pdf'} but returning the @emph{logarithm} of ## the density. ## ## @item @qcode{'nloglf'} @tab A function handle, ## @code{@var{nll}(@var{params}, @var{data}, @var{cens}, @var{freq})}, that ## returns the scalar negative log-likelihood of the whole sample. It receives ## the current parameter vector, the data, the censoring vector, and the ## frequency vector, and is responsible for incorporating censoring and ## frequency itself. ## ## @item @qcode{'cdf'} @tab A function handle to the cumulative distribution ## function, with the same calling convention as @qcode{'pdf'}. It is ## @strong{required} together with @qcode{'pdf'} when the data are censored, so ## that censored observations can contribute their survival probability. ## ## @item @qcode{'logsf'} @tab A function handle to the logarithm of the survivor ## function @math{log (1 - cdf)}, with the same calling convention as ## @qcode{'pdf'}. It is @strong{required} together with @qcode{'logpdf'} when ## the data are censored. ## ## @item @qcode{'Censoring'} @tab A vector of the same size as @var{data} ## indicating censored observations (nonzero for right-censored). By default no ## observation is censored. ## ## @item @qcode{'Frequency'} @tab A vector of nonnegative integer counts of the ## same size as @var{data}, giving the number of times each observation was ## observed. By default it is @qcode{ones (size (@var{data}))}. ## ## @item @qcode{'Options'} @tab A structure that may contain a ## @qcode{'DerivStep'} field specifying the relative finite-difference step ## used to approximate the Hessian (a positive scalar or a vector the same size ## as @var{params}). The default step is @qcode{eps ^ (1/4)}. ## @end multitable ## ## @strong{Computation and numerical behavior.} @code{mlecov} approximates the ## covariance matrix as the inverse of the observed Fisher information, that is, ## the inverse of the Hessian of the @emph{aggregate} negative log-likelihood of ## the sample, evaluated by central finite differences at @var{params}. The ## covariance is computed @emph{at} the supplied @var{params}; @code{mlecov} ## does not refit the parameters, so @var{params} should be the maximum ## likelihood estimates for the result to be meaningful. ## ## Whichever of @qcode{'pdf'}, @qcode{'logpdf'}, or @qcode{'nloglf'} is ## supplied, the Hessian is always formed by differencing the same aggregate ## negative log-likelihood rather than by differentiating the density itself. ## This makes the three input forms consistent with one another and is ## numerically far more stable than differentiating a density; as a consequence ## @var{acov} may differ from other implementations (including MATLAB) in ## ill-conditioned cases where those differentiate the density directly and ## return unreliable values or @code{NaN}. If the computed Hessian is not ## positive definite (for example when @var{params} is not at a likelihood ## maximum), a warning is issued and @var{acov} is returned as an ## all-@code{NaN} matrix. ## ## @seealso{mle, fitdist, makedist} ## @end deftypefn function acov = mlecov (params, data, varargin) ## Check number of input arguments if (nargin < 3) print_usage (); endif ## Check PARAMS and DATA if (! (isvector (params) && isnumeric (params) && isreal (params) && ! isempty (params))) error ("mlecov: PARAMS must be a nonempty numeric vector of real values."); endif if (! (isvector (data) && isnumeric (data) && isreal (data) && ! isempty (data))) error ("mlecov: DATA must be a nonempty numeric vector of real values."); endif ## Add defaults pdf = []; logpdf = []; nloglf = []; cdf = []; logsf = []; censor = []; freq = []; derivstep = eps ^ (1/4); ## Parse optional arguments as NAME-VALUE pairs if (mod (numel (varargin), 2) != 0) error ("mlecov: optional arguments must be in NAME-VALUE pairs."); endif while (numel (varargin) > 0) name = varargin{1}; value = varargin{2}; if (! (ischar (name) && isrow (name))) error ("mlecov: NAME arguments must be character vectors."); endif switch (tolower (name)) case 'pdf' pdf = value; case 'logpdf' logpdf = value; case 'nloglf' nloglf = value; case 'cdf' cdf = value; case 'logsf' logsf = value; case 'censoring' censor = value; case 'frequency' freq = value; case 'options' if (! isstruct (value)) error ("mlecov: 'Options' argument must be a structure."); endif if (isfield (value, 'DerivStep') && ! isempty (value.DerivStep)) derivstep = value.DerivStep; endif otherwise error ("mlecov: unknown parameter name '%s'.", name); endswitch varargin([1:2]) = []; endwhile ## Exactly one distribution function must be specified nfun = (! isempty (pdf)) + (! isempty (logpdf)) + (! isempty (nloglf)); if (nfun == 0) error (strcat ("mlecov: a distribution must be specified with one of", ... " the 'pdf', 'logpdf', or 'nloglf' arguments.")); elseif (nfun > 1) error (strcat ("mlecov: only one of the 'pdf', 'logpdf', or 'nloglf'", ... " arguments can be specified.")); endif ## Check that supplied handles are function handles if (! isempty (pdf) && ! is_function_handle (pdf)) error ("mlecov: 'pdf' argument must be a function handle."); endif if (! isempty (logpdf) && ! is_function_handle (logpdf)) error ("mlecov: 'logpdf' argument must be a function handle."); endif if (! isempty (nloglf) && ! is_function_handle (nloglf)) error ("mlecov: 'nloglf' argument must be a function handle."); endif if (! isempty (cdf) && ! is_function_handle (cdf)) error ("mlecov: 'cdf' argument must be a function handle."); endif if (! isempty (logsf) && ! is_function_handle (logsf)) error ("mlecov: 'logsf' argument must be a function handle."); endif ## Add defaults and validate FREQUENCY and CENSORING if (isempty (freq)) freq = ones (size (data)); elseif (! isequal (size (data), size (freq))) error (strcat ("mlecov: 'Frequency' argument must have the same size", ... " as the input data in DATA.")); elseif (any (freq(:) < 0) || any (freq(:) != round (freq(:)))) error (strcat ("mlecov: 'Frequency' argument must contain non-negative", ... " integer values.")); endif if (isempty (censor)) censor = zeros (size (data)); elseif (! isequal (size (data), size (censor))) error (strcat ("mlecov: 'Censoring' argument must have the same size", ... " as the input data in DATA.")); endif docens = any (censor(:) != 0); ## Censored data need a survivor function for the 'pdf'/'logpdf' forms if (docens && ! isempty (pdf) && isempty (cdf)) error (strcat ("mlecov: a 'cdf' function handle is required for", ... " censored data when using the 'pdf' argument.")); endif if (docens && ! isempty (logpdf) && isempty (logsf)) error (strcat ("mlecov: a 'logsf' function handle is required for", ... " censored data when using the 'logpdf' argument.")); endif ## Validate DerivStep and build the per-parameter finite-difference step theta = params(:).'; p = numel (theta); if (! (isnumeric (derivstep) && isreal (derivstep) && all (derivstep(:) > 0) && (isscalar (derivstep) || numel (derivstep) == p))) error (strcat ("mlecov: 'DerivStep' must be a positive real scalar or", ... " a vector the same size as PARAMS.")); endif hstep = derivstep(:).' .* max (abs (theta), 1); ## Assemble the aggregate negative log-likelihood as a function of the ## parameter vector only, capturing data, censoring, and frequency if (! isempty (pdf)) form = 'pdf'; elseif (! isempty (logpdf)) form = 'logpdf'; else form = 'nloglf'; endif nllfun = @(t) aggregate_nll (t, form, pdf, cdf, logpdf, logsf, nloglf, ... data, censor, freq, docens); ## Central finite-difference Hessian of the negative log-likelihood H = num_hessian (nllfun, theta, hstep); H = (H + H') / 2; ## Invert only if the Hessian is positive definite; otherwise warn and NaN [~, notpd] = chol (H); if (notpd != 0) warning (strcat ("mlecov: unable to compute a covariance matrix", ... " because the computed Hessian matrix is not positive", ... " definite.")); acov = NaN (p); else acov = inv (H); acov = (acov + acov') / 2; endif endfunction ## Aggregate negative log-likelihood at parameter vector T function nll = aggregate_nll (t, form, pdf, cdf, logpdf, logsf, nloglf, ... data, censor, freq, docens) switch (form) case 'pdf' pc = num2cell (t); dens = pdf (data, pc{:}); if (docens) surv = 1 - cdf (data, pc{:}); terms = (censor == 0) .* log (dens) + (censor != 0) .* log (surv); else terms = log (dens); endif nll = -sum (freq(:) .* terms(:)); case 'logpdf' pc = num2cell (t); lpd = logpdf (data, pc{:}); if (docens) lsf = logsf (data, pc{:}); terms = (censor == 0) .* lpd + (censor != 0) .* lsf; else terms = lpd; endif nll = -sum (freq(:) .* terms(:)); case 'nloglf' nll = nloglf (t, data, censor, freq); endswitch endfunction ## Central finite-difference approximation of the Hessian of NLLFUN at THETA function H = num_hessian (nllfun, theta, hstep) p = numel (theta); H = zeros (p); f0 = nllfun (theta); for i = 1:p ei = zeros (1, p); ei(i) = hstep(i); fpi = nllfun (theta + ei); fmi = nllfun (theta - ei); H(i,i) = (fpi - 2 * f0 + fmi) / (hstep(i) ^ 2); for j = (i + 1):p ej = zeros (1, p); ej(j) = hstep(j); fpp = nllfun (theta + ei + ej); fpm = nllfun (theta + ei - ej); fmp = nllfun (theta - ei + ej); fmm = nllfun (theta - ei - ej); H(i,j) = (fpp - fpm - fmp + fmm) / (4 * hstep(i) * hstep(j)); H(j,i) = H(i,j); endfor endfor endfunction %!demo %! ## Asymptotic covariance matrix of the ML estimates for a normal fit. %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! phat = mle (x); %! acov = mlecov (phat, x, 'pdf', @(x, mu, sigma) normpdf (x, mu, sigma)) ## Reference values below were computed with MATLAB's mlecov using the reliable ## 'nloglf' input form (its 'pdf'/'logpdf' paths differentiate the density ## directly and return NaN or unreliable values on these inputs). %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! phat = [mean(x), std(x, 1)]; %! nll = @(p, data, cens, freq) -sum (log (normpdf (data, p(1), p(2)))); %! acov = mlecov (phat, x, 'nloglf', nll); %! assert_equal (acov, [0.0887673606073251, 0; 0, 0.0443836789388774], 1e-6); %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! phat = [3.5, 1.0]; %! nll = @(p, data, cens, freq) -sum (log (normpdf (data, p(1), p(2)))); %! acov = mlecov (phat, x, 'nloglf', nll); %! ref = [0.0841894975321553, 0.00570776282157731; ... %! 0.00570776282157731, 0.0380517511049636]; %! assert_equal (acov, ref, 1e-6); %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! phat = gamfit (x); %! nll = @(p, data, cens, freq) -sum (log (gampdf (data, p(1), p(2)))); %! acov = mlecov (phat, x, 'nloglf', nll); %! ref = [17.6711714212941, -0.553235139352906; ... %! -0.553235139352906, 0.0181745610037496]; %! assert_equal (acov, ref, 5e-4); %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! f = [1, 2, 1, 1, 3, 1, 2, 1, 1, 1, 2, 1]; %! phat = [mean(x), std(x, 1)]; %! nll = @(p, data, cens, freq) -sum (freq .* log (normpdf (data, p(1), p(2)))); %! acov = mlecov (phat, x, 'nloglf', nll, 'Frequency', f); %! ref = [0.0630373869314514, -0.00427967204059965; ... %! -0.00427967204059965, 0.0484445550668851]; %! assert_equal (acov, ref, 1e-6); %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! c = [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0]; %! phat = [mean(x), std(x, 1)]; %! nll = @(p, data, cens, freq) -sum ((1 - cens) .* ... %! log (normpdf (data, p(1), p(2))) + ... %! cens .* log (1 - normcdf (data, p(1), p(2)))); %! acov = mlecov (phat, x, 'nloglf', nll, 'Censoring', c); %! ref = [0.100361972376949, -0.0148199117431384; ... %! -0.0148199117431384, 0.054254671056408]; %! assert_equal (acov, ref, 1e-6); ## The 'pdf', 'logpdf', and 'nloglf' forms differentiate the same aggregate ## negative log-likelihood, so they agree (unlike MATLAB, whose density-based ## 'pdf' path returns NaN for the normal and 51.84 for the exponential here). %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! phat = [mean(x), std(x, 1)]; %! a_pdf = mlecov (phat, x, 'pdf', @(x, mu, s) normpdf (x, mu, s)); %! a_logpdf = mlecov (phat, x, 'logpdf', @(x, mu, s) log (normpdf (x, mu, s))); %! a_nloglf = mlecov (phat, x, 'nloglf', ... %! @(p, d, c, f) -sum (log (normpdf (d, p(1), p(2))))); %! assert_equal (a_pdf, a_nloglf, 1e-10); %! assert_equal (a_logpdf, a_nloglf, 1e-10); %! assert_equal (a_pdf, [0.0887673606073251, 0; 0, 0.0443836789388774], 1e-6); %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! acov = mlecov (mean (x), x, 'pdf', @(x, mu) exppdf (x, mu)); %! assert_equal (acov, 0.977552156166645, 1e-6); ## Censored 'pdf' form (with a 'cdf' handle) matches the hand-built censored ## 'nloglf' likelihood. %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! c = [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0]; %! phat = [mean(x), std(x, 1)]; %! acov = mlecov (phat, x, 'pdf', @(x, mu, s) normpdf (x, mu, s), ... %! 'cdf', @(x, mu, s) normcdf (x, mu, s), 'Censoring', c); %! ref = [0.100361972376949, -0.0148199117431384; ... %! -0.0148199117431384, 0.054254671056408]; %! assert_equal (acov, ref, 1e-6); ## A user-supplied 'DerivStep' still yields the correct covariance. %!test %! x = [2.1, 3.4, 1.9, 5.2, 4.1, 2.8, 3.3, 4.7, 2.2, 3.9, 3.0, 4.5]; %! phat = [mean(x), std(x, 1)]; %! nll = @(p, data, cens, freq) -sum (log (normpdf (data, p(1), p(2)))); %! acov = mlecov (phat, x, 'nloglf', nll, ... %! 'Options', struct ('DerivStep', 1e-4)); %! assert_equal (acov, [0.0887673606073251, 0; 0, 0.0443836789388774], 1e-5); ## A non-positive-definite Hessian yields a warning and an all-NaN matrix. %!warning ... %! mlecov (1, [1, 2, 3, 4, 5], 'nloglf', @(p, d, c, f) -sum (p(1) .* d)); %!test %! warning ("off", "all", "local"); %! acov = mlecov (1, [1, 2, 3, 4, 5], 'nloglf', @(p, d, c, f) -sum (p(1) .* d)); %! assert_equal (all (isnan (acov), 'all'), true); ## Test input validation %!error mlecov (1, [1, 2, 3]) %!error ... %! mlecov ([1, 2; 3, 4], [1, 2, 3], 'pdf', @(x, a) x) %!error ... %! mlecov ([1, 2i], [1, 2, 3], 'pdf', @(x, a, b) x) %!error ... %! mlecov ([1, 2], ones (2, 2), 'pdf', @(x, a, b) x) %!error ... %! mlecov ([1, 2], [1, 2, 3], 'pdf') %!error ... %! mlecov ([1, 2], [1, 2, 3], 5, @sin) %!error ... %! mlecov ([1, 2], [1, 2, 3], 'Frequency', [1, 1, 1]) %!error ... %! mlecov ([1, 2], [1, 2, 3], 'pdf', @sin, 'nloglf', @cos) %!error ... %! mlecov ([1, 2], [1, 2, 3], 'pdf', 5) %!error ... %! mlecov ([1, 2], [1, 2, 3], 'nloglf', 'text') %!error ... %! mlecov ([1, 2], [1, 2, 3], 'pdf', @sin, 'bogus', 1) %!error ... %! mlecov ([1, 2], [1, 2, 3], 'nloglf', @(varargin) 1, 'Frequency', [1, 1]) %!error ... %! mlecov ([1, 2], [1, 2, 3], 'nloglf', @(varargin) 1, 'Frequency', [1, 0.5, 1]) %!error ... %! mlecov ([1, 2], [1, 2, 3], 'nloglf', @(varargin) 1, 'Censoring', [1, 0]) %!error ... %! mlecov ([1, 2], [1, 2, 3], 'pdf', @(x, a, b) x, 'Censoring', [1, 0, 0]) %!error ... %! mlecov ([1, 2], [1, 2, 3], 'logpdf', @(x, a, b) x, 'Censoring', [1, 0, 0]) %!error ... %! mlecov ([1, 2], [1, 2, 3], 'nloglf', @(varargin) 1, 'Options', 5) %!error ... %! mlecov ([1, 2], [1, 2, 3], 'nloglf', @(varargin) 1, ... %! 'Options', struct ('DerivStep', -1)) statistics-release-1.9.2/inst/Distribution_Wrappers/pdf.m000066400000000000000000000371171524624707500236260ustar00rootroot00000000000000## Copyright (C) 2016 Andreas Stahel ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{y} =} pdf (@var{name}, @var{x}, @var{A}) ## @deftypefnx {statistics} {@var{y} =} pdf (@var{name}, @var{x}, @var{A}, @var{B}) ## @deftypefnx {statistics} {@var{y} =} pdf (@var{name}, @var{x}, @var{A}, @var{B}, @var{C}) ## ## Return the PDF of a univariate distribution evaluated at @var{x}. ## ## @code{pdf} is a wrapper for the univariate cumulative distribution functions ## available in the statistics package. See the corresponding functions' help ## to learn the signification of the parameters after @var{x}. ## ## @code{@var{y} = pdf (@var{name}, @var{x}, @var{A})} returns the CDF for the ## one-parameter distribution family specified by @var{name} and the ## distribution parameter @var{A}, evaluated at the values in @var{x}. ## ## @code{@var{y} = pdf (@var{name}, @var{x}, @var{A}, @var{B})} returns the CDF ## for the two-parameter distribution family specified by @var{name} and the ## distribution parameters @var{A} and @var{B}, evaluated at the values in ## @var{x}. ## ## @code{@var{y} = pdf (@var{name}, @var{x}, @var{A}, @var{B}, @var{C})} returns ## the CDF for the three-parameter distribution family specified by @var{name} ## and the distribution parameters @var{A}, @var{B}, and @var{C}, evaluated at ## the values in @var{x}. ## ## @var{name} must be a char string of the name or the abbreviation of the ## desired cumulative distribution function as listed in the following table. ## The last column shows the number of required parameters that should be parsed ## after @var{x} to the desired PDF. ## ## @multitable @columnfractions 0.4 0.2 0.3 ## @headitem Distribution Name @tab Abbreviation @tab Input Parameters ## @item @qcode{'Beta'} @tab @qcode{'beta'} @tab 2 ## @item @qcode{'Binomial'} @tab @qcode{'bino'} @tab 2 ## @item @qcode{'Birnbaum-Saunders'} @tab @qcode{'bisa'} @tab 2 ## @item @qcode{'Burr'} @tab @qcode{'burr'} @tab 3 ## @item @qcode{'Cauchy'} @tab @qcode{'cauchy'} @tab 2 ## @item @qcode{'Chi-squared'} @tab @qcode{'chi2'} @tab 1 ## @item @qcode{'Extreme Value'} @tab @qcode{'ev'} @tab 2 ## @item @qcode{'Exponential'} @tab @qcode{'exp'} @tab 1 ## @item @qcode{'F-Distribution'} @tab @qcode{'f'} @tab 2 ## @item @qcode{'Gamma'} @tab @qcode{'gam'} @tab 2 ## @item @qcode{'Geometric'} @tab @qcode{'geo'} @tab 1 ## @item @qcode{'Generalized Extreme Value'} @tab @qcode{'gev'} @tab 3 ## @item @qcode{'Generalized Pareto'} @tab @qcode{'gp'} @tab 3 ## @item @qcode{'Gumbel'} @tab @qcode{'gumbel'} @tab 2 ## @item @qcode{'Half-normal'} @tab @qcode{'hn'} @tab 2 ## @item @qcode{'Hypergeometric'} @tab @qcode{'hyge'} @tab 3 ## @item @qcode{'Inverse Gaussian'} @tab @qcode{'invg'} @tab 2 ## @item @qcode{'Laplace'} @tab @qcode{'laplace'} @tab 2 ## @item @qcode{'Logistic'} @tab @qcode{'logi'} @tab 2 ## @item @qcode{'Log-Logistic'} @tab @qcode{'logl'} @tab 2 ## @item @qcode{'Lognormal'} @tab @qcode{'logn'} @tab 2 ## @item @qcode{'Nakagami'} @tab @qcode{'naka'} @tab 2 ## @item @qcode{'Negative Binomial'} @tab @qcode{'nbin'} @tab 2 ## @item @qcode{'Noncentral F-Distribution'} @tab @qcode{'ncf'} @tab 3 ## @item @qcode{'Noncentral Student T'} @tab @qcode{'nct'} @tab 2 ## @item @qcode{'Noncentral Chi-Squared'} @tab @qcode{'ncx2'} @tab 2 ## @item @qcode{'Normal'} @tab @qcode{'norm'} @tab 2 ## @item @qcode{'Poisson'} @tab @qcode{'poiss'} @tab 1 ## @item @qcode{'Rayleigh'} @tab @qcode{'rayl'} @tab 1 ## @item @qcode{'Rician'} @tab @qcode{'rice'} @tab 2 ## @item @qcode{'Student T'} @tab @qcode{'t'} @tab 1 ## @item @qcode{'location-scale T'} @tab @qcode{'tls'} @tab 3 ## @item @qcode{'Triangular'} @tab @qcode{'tri'} @tab 3 ## @item @qcode{'Discrete Uniform'} @tab @qcode{'unid'} @tab 1 ## @item @qcode{'Uniform'} @tab @qcode{'unif'} @tab 2 ## @item @qcode{'Von Mises'} @tab @qcode{'vm'} @tab 2 ## @item @qcode{'Weibull'} @tab @qcode{'wbl'} @tab 2 ## @end multitable ## ## Distribution names are matched ignoring case, spaces and hyphens, so that ## @qcode{'Extreme Value'}, @qcode{'ExtremeValue'} and @qcode{'extreme-value'} ## all select the same distribution, and the same set of names is accepted by ## @code{cdf}, @code{pdf}, @code{icdf}, @code{random}, @code{makedist}, ## @code{fitdist} and @code{mle}. ## ## This accepts more names than MATLAB. MATLAB takes the spaced and the ## squashed spelling but refuses the hyphenated one, so ## @qcode{'Birnbaum-Saunders'} and @qcode{'Log-Logistic'} are errors there; ## Octave has always accepted them and continues to. MATLAB also accepts ## @qcode{'tLocationScale'} in @code{makedist} while refusing it in ## @code{cdf} for the same distribution; Octave accepts it, and ## @qcode{'location-scale T'}, everywhere. Code written against MATLAB's ## names therefore runs unchanged, but code relying on these names will not ## port back. ## ## @seealso{cdf, icdf, random, betapdf, binopdf, bisapdf, burrpdf, cauchypdf, ## chi2pdf, evpdf, exppdf, fpdf, gampdf, geopdf, gevpdf, gppdf, gumbelpdf, ## hnpdf, hygepdf, invgpdf, laplacepdf, logipdf, loglpdf, lognpdf, nakapdf, ## nbinpdf, ncfpdf, nctpdf, ncx2pdf, normpdf, poisspdf, raylpdf, ricepdf, tpdf, ## tlspdf, tripdf, unidpdf, unifpdf, vmpdf, wblpdf} ## @end deftypefn function y = pdf (name, x, varargin) ## implemented functions persistent allDF = { ... {'beta' , 'Beta'}, @betapdf, 2, ... {'bino' , 'Binomial'}, @binopdf, 2, ... {'bisa' , 'Birnbaum-Saunders'}, @bisapdf, 2, ... {'burr' , 'Burr'}, @burrpdf, 3, ... {'cauchy' , 'Cauchy'}, @cauchypdf, 2, ... {'chi2' , 'Chi-squared'}, @chi2pdf, 1, ... {'ev' , 'Extreme Value'}, @evpdf, 2, ... {'exp' , 'Exponential'}, @exppdf, 1, ... {'f' , 'F-Distribution'}, @fpdf, 2, ... {'gam' , 'Gamma'}, @gampdf, 2, ... {'geo' , 'Geometric'}, @geopdf, 1, ... {'gev' , 'Generalized Extreme Value'}, @gevpdf, 3, ... {'gp' , 'Generalized Pareto'}, @gppdf, 3, ... {'gumbel' , 'Gumbel'}, @gumbelpdf, 2, ... {'hn' , 'Half-normal'}, @hnpdf, 2, ... {'hyge' , 'Hypergeometric'}, @hygepdf, 3, ... {'invg' , 'Inverse Gaussian'}, @invgpdf, 2, ... {'laplace' , 'Laplace'}, @laplacepdf, 2, ... {'logi' , 'Logistic'}, @logipdf, 2, ... {'logl' , 'Log-Logistic'}, @loglpdf, 2, ... {'logn' , 'Lognormal'}, @lognpdf, 2, ... {'naka' , 'Nakagami'}, @nakapdf, 2, ... {'nbin' , 'Negative Binomial'}, @nbinpdf, 2, ... {'ncf' , 'Noncentral F-Distribution'}, @ncfpdf, 3, ... {'nct' , 'Noncentral Student T'}, @nctpdf, 2, ... {'ncx2' , 'Noncentral Chi-squared'}, @ncx2pdf, 2, ... {'norm' , 'Normal'}, @normpdf, 2, ... {'poiss' , 'Poisson'}, @poisspdf, 1, ... {'rayl' , 'Rayleigh'}, @raylpdf, 1, ... {'rice' , 'Rician'}, @ricepdf, 2, ... {'t' , 'Student T'}, @tpdf, 1, ... {'tls', 'location-scale T', 'tLocationScale'}, @tlspdf, 3, ... {'tri' , 'Triangular'}, @tripdf, 3, ... {'unid' , 'Discrete Uniform'}, @unidpdf, 1, ... {'unif' , 'Uniform'}, @unifpdf, 2, ... {'vm' , 'Von Mises'}, @vmpdf, 2, ... {'wbl' , 'Weibull'}, @wblpdf, 2}; if (! ischar (name)) error ("pdf: distribution NAME must a char string."); endif ## Check X being numeric and real if (! isnumeric (x)) error ("pdf: X must be numeric."); elseif (! isreal (x)) error ("pdf: values in X must be real."); endif ## Get number of arguments nargs = numel (varargin); ## Get available functions pdfnames = allDF(1:3:end); pdfhandl = allDF(2:3:end); pdf_args = allDF(3:3:end); ## Search for PDF function ## Match on the folded key so that every spelling of a name resolves key = __distname_key__ (name); idx = cellfun (@(x) any (strcmp (key, cellfun (@__distname_key__, x, ... 'UniformOutput', false))), pdfnames); if (any (idx)) if (nargs == pdf_args{idx}) ## Check that all distribution parameters are numeric if (! all (cellfun (@(x)isnumeric (x), (varargin)))) error ("pdf: distribution parameters must be numeric."); endif ## Call appropriate iCDF y = feval (pdfhandl{idx}, x, varargin{:}); else if (pdf_args{idx} == 1) error ("pdf: %s distribution requires 1 parameter.", name); else error ("pdf: %s distribution requires %d parameters.", ... name, pdf_args{idx}); endif endif else error ("pdf: %s distribution is not implemented in Statistics.", name); endif endfunction ## Test results %!shared x %! x = [1:5]; %!assert_equal (pdf ('Beta', x, 5, 2), betapdf (x, 5, 2)) %!assert_equal (pdf ('beta', x, 5, 2), betapdf (x, 5, 2)) %!assert_equal (pdf ('Binomial', x, 5, 2), binopdf (x, 5, 2)) %!assert_equal (pdf ('bino', x, 5, 2), binopdf (x, 5, 2)) %!assert_equal (pdf ('Birnbaum-Saunders', x, 5, 2), bisapdf (x, 5, 2)) %!assert_equal (pdf ('bisa', x, 5, 2), bisapdf (x, 5, 2)) %!assert_equal (pdf ('Burr', x, 5, 2, 2), burrpdf (x, 5, 2, 2)) %!assert_equal (pdf ('burr', x, 5, 2, 2), burrpdf (x, 5, 2, 2)) %!assert_equal (pdf ('Cauchy', x, 5, 2), cauchypdf (x, 5, 2)) %!assert_equal (pdf ('cauchy', x, 5, 2), cauchypdf (x, 5, 2)) %!assert_equal (pdf ('Chi-squared', x, 5), chi2pdf (x, 5)) %!assert_equal (pdf ('chi2', x, 5), chi2pdf (x, 5)) %!assert_equal (pdf ('Extreme Value', x, 5, 2), evpdf (x, 5, 2)) %!assert_equal (pdf ('ev', x, 5, 2), evpdf (x, 5, 2)) %!assert_equal (pdf ('Exponential', x, 5), exppdf (x, 5)) %!assert_equal (pdf ('exp', x, 5), exppdf (x, 5)) %!assert_equal (pdf ('F-Distribution', x, 5, 2), fpdf (x, 5, 2)) %!assert_equal (pdf ('f', x, 5, 2), fpdf (x, 5, 2)) %!assert_equal (pdf ('Gamma', x, 5, 2), gampdf (x, 5, 2)) %!assert_equal (pdf ('gam', x, 5, 2), gampdf (x, 5, 2)) %!assert_equal (pdf ('Geometric', x, 5), geopdf (x, 5)) %!assert_equal (pdf ('geo', x, 5), geopdf (x, 5)) %!assert_equal (pdf ('Generalized Extreme Value', x, 5, 2, 2), gevpdf (x, 5, 2, 2)) %!assert_equal (pdf ('gev', x, 5, 2, 2), gevpdf (x, 5, 2, 2)) %!assert_equal (pdf ('Generalized Pareto', x, 5, 2, 2), gppdf (x, 5, 2, 2)) %!assert_equal (pdf ('gp', x, 5, 2, 2), gppdf (x, 5, 2, 2)) %!assert_equal (pdf ('Gumbel', x, 5, 2), gumbelpdf (x, 5, 2)) %!assert_equal (pdf ('gumbel', x, 5, 2), gumbelpdf (x, 5, 2)) %!assert_equal (pdf ('Half-normal', x, 5, 2), hnpdf (x, 5, 2)) %!assert_equal (pdf ('hn', x, 5, 2), hnpdf (x, 5, 2)) %!assert_equal (pdf ('Hypergeometric', x, 5, 2, 2), hygepdf (x, 5, 2, 2)) %!assert_equal (pdf ('hyge', x, 5, 2, 2), hygepdf (x, 5, 2, 2)) %!assert_equal (pdf ('Inverse Gaussian', x, 5, 2), invgpdf (x, 5, 2)) %!assert_equal (pdf ('invg', x, 5, 2), invgpdf (x, 5, 2)) %!assert_equal (pdf ('Laplace', x, 5, 2), laplacepdf (x, 5, 2)) %!assert_equal (pdf ('laplace', x, 5, 2), laplacepdf (x, 5, 2)) %!assert_equal (pdf ('Logistic', x, 5, 2), logipdf (x, 5, 2)) %!assert_equal (pdf ('logi', x, 5, 2), logipdf (x, 5, 2)) %!assert_equal (pdf ('Log-Logistic', x, 5, 2), loglpdf (x, 5, 2)) %!assert_equal (pdf ('logl', x, 5, 2), loglpdf (x, 5, 2)) %!assert_equal (pdf ('Lognormal', x, 5, 2), lognpdf (x, 5, 2)) %!assert_equal (pdf ('logn', x, 5, 2), lognpdf (x, 5, 2)) %!assert_equal (pdf ('Nakagami', x, 5, 2), nakapdf (x, 5, 2)) %!assert_equal (pdf ('naka', x, 5, 2), nakapdf (x, 5, 2)) %!assert_equal (pdf ('Negative Binomial', x, 5, 2), nbinpdf (x, 5, 2)) %!assert_equal (pdf ('nbin', x, 5, 2), nbinpdf (x, 5, 2)) %!assert_equal (pdf ('Noncentral F-Distribution', x, 5, 2, 2), ncfpdf (x, 5, 2, 2)) %!assert_equal (pdf ('ncf', x, 5, 2, 2), ncfpdf (x, 5, 2, 2)) %!assert_equal (pdf ('Noncentral Student T', x, 5, 2), nctpdf (x, 5, 2)) %!assert_equal (pdf ('nct', x, 5, 2), nctpdf (x, 5, 2)) %!assert_equal (pdf ('Noncentral Chi-Squared', x, 5, 2), ncx2pdf (x, 5, 2)) %!assert_equal (pdf ('ncx2', x, 5, 2), ncx2pdf (x, 5, 2)) %!assert_equal (pdf ('Normal', x, 5, 2), normpdf (x, 5, 2)) %!assert_equal (pdf ('norm', x, 5, 2), normpdf (x, 5, 2)) %!assert_equal (pdf ('Poisson', x, 5), poisspdf (x, 5)) %!assert_equal (pdf ('poiss', x, 5), poisspdf (x, 5)) %!assert_equal (pdf ('Rayleigh', x, 5), raylpdf (x, 5)) %!assert_equal (pdf ('rayl', x, 5), raylpdf (x, 5)) %!assert_equal (pdf ('Rician', x, 5, 1), ricepdf (x, 5, 1)) %!assert_equal (pdf ('rice', x, 5, 1), ricepdf (x, 5, 1)) %!assert_equal (pdf ('Student T', x, 5), tpdf (x, 5)) %!assert_equal (pdf ('t', x, 5), tpdf (x, 5)) %!assert_equal (pdf ('location-scale T', x, 5, 1, 2), tlspdf (x, 5, 1, 2)) %!assert_equal (pdf ('tls', x, 5, 1, 2), tlspdf (x, 5, 1, 2)) %!assert_equal (pdf ('Triangular', x, 5, 2, 2), tripdf (x, 5, 2, 2)) %!assert_equal (pdf ('tri', x, 5, 2, 2), tripdf (x, 5, 2, 2)) %!assert_equal (pdf ('Discrete Uniform', x, 5), unidpdf (x, 5)) %!assert_equal (pdf ('unid', x, 5), unidpdf (x, 5)) %!assert_equal (pdf ('Uniform', x, 5, 2), unifpdf (x, 5, 2)) %!assert_equal (pdf ('unif', x, 5, 2), unifpdf (x, 5, 2)) %!assert_equal (pdf ('Von Mises', x, 5, 2), vmpdf (x, 5, 2)) %!assert_equal (pdf ('vm', x, 5, 2), vmpdf (x, 5, 2)) %!assert_equal (pdf ('Weibull', x, 5, 2), wblpdf (x, 5, 2)) %!assert_equal (pdf ('wbl', x, 5, 2), wblpdf (x, 5, 2)) ## Test input validation %!test %! ## Every spelling of a name reaches the same distribution: case, spaces, %! ## hyphens and underscores are all ignored. %! for n = {'Extreme Value', 'ExtremeValue', 'extreme-value', 'EXTREME VALUE'} %! assert_equal (pdf (n{1}, 1, 2, 3), pdf ('ev', 1, 2, 3)); %! endfor %!test %! ## The name that makedist uses is accepted here too, and the reverse %! assert_equal (pdf ('tLocationScale', 1, 2, 3, 4), pdf ('tls', 1, 2, 3, 4)); %!error pdf (1) %!error pdf ({'beta'}) %!error pdf ('beta', {[1 2 3 4 5]}) %!error pdf ('beta', 'text') %!error pdf ('beta', 1+i) %!error ... %! pdf ('Beta', x, 'a', 2) %!error ... %! pdf ('Beta', x, 5, '') %!error ... %! pdf ('Beta', x, 5, {2}) %!error pdf ('chi2', x) %!error pdf ('Beta', x, 5) %!error pdf ('Burr', x, 5) %!error pdf ('Burr', x, 5, 2) statistics-release-1.9.2/inst/Distribution_Wrappers/private/000077500000000000000000000000001524624707500243405ustar00rootroot00000000000000statistics-release-1.9.2/inst/Distribution_Wrappers/private/__distname_key__.m000066400000000000000000000045671524624707500300020ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{key} =} __distname_key__ (@var{name}) ## ## Fold a distribution name to the key its wrapper looks up. ## ## The key is @var{name} in lower case with spaces and hyphens removed, so ## that every spelling of a distribution reaches the same distribution in ## every wrapper. @qcode{'Extreme Value'}, @qcode{'ExtremeValue'} and ## @qcode{'extreme-value'} all give @qcode{'extremevalue'}. Both separators ## occur in names the package already ships -- @qcode{'Birnbaum-Saunders'}, ## @qcode{'location-scale T'} -- which is why they are folded. ## ## The accepted set is deliberately a superset of MATLAB's, in two ways. ## ## MATLAB takes the spaced and the squashed spelling of a name but refuses ## the hyphenated one, so @qcode{'Birnbaum-Saunders'} and ## @qcode{'Log-Logistic'} are errors there. Octave has accepted those since ## they are the display names its own tables carry, and withdrawing them to ## match MATLAB would break working code for no gain. This is an extension, ## not a correction: MATLAB is not wrong to have a narrower set. ## ## MATLAB is, however, inconsistent with itself over ## @qcode{'tLocationScale'}: @code{makedist} accepts that name while ## @code{cdf} refuses it for the same distribution, which @code{cdf} calls ## @qcode{'location-scale T'}. Measured against R2024a on 2026-08-07. Every ## Octave wrapper accepts both names, so a name that builds a distribution ## also evaluates it. ## ## @end deftypefn function key = __distname_key__ (name) key = tolower (name); key(key == " " | key == "-") = []; endfunction statistics-release-1.9.2/inst/Distribution_Wrappers/random.m000066400000000000000000000434441524624707500243350ustar00rootroot00000000000000## Copyright (C) 2007 Soren Hauberg ## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} random (@var{name}, @var{A}) ## @deftypefnx {statistics} {@var{r} =} random (@var{name}, @var{A}, @var{B}) ## @deftypefnx {statistics} {@var{r} =} random (@var{name}, @var{A}, @var{B}, @var{C}) ## @deftypefnx {statistics} {@var{r} =} random (@var{name}, @dots{}, @var{rows}, @var{cols}) ## @deftypefnx {statistics} {@var{r} =} random (@var{name}, @dots{}, @var{rows}, @var{cols}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} random (@var{name}, @dots{}, [@var{sz}]) ## ## Random arrays from a given one-, two-, or three-parameter distribution. ## ## The variable @var{name} must be a string with the name of the distribution to ## sample from. If this distribution is a one-parameter distribution, @var{A} ## must be supplied, if it is a two-parameter distribution, @var{B} must also be ## supplied, and if it is a three-parameter distribution, @var{C} must also be ## supplied. Any arguments following the distribution parameters will determine ## the size of the result. ## ## When called with a single size argument, return a square matrix with the ## dimension specified. When called with more than one scalar argument the ## first two arguments are taken as the number of rows and columns and any ## further arguments specify additional matrix dimensions. The size may also ## be specified with a vector of dimensions @var{sz}. ## ## @var{name} must be a char string of the name or the abbreviation of the ## desired probability distribution function as listed in the following table. ## The last column shows the required number of parameters that must be passed ## to the desired @qcode{*rnd} distribution function. ## ## @multitable @columnfractions 0.4 0.2 0.3 ## @headitem Distribution Name @tab Abbreviation @tab Input Parameters ## @item @qcode{'Beta'} @tab @qcode{'beta'} @tab 2 ## @item @qcode{'Binomial'} @tab @qcode{'bino'} @tab 2 ## @item @qcode{'Birnbaum-Saunders'} @tab @qcode{'bisa'} @tab 2 ## @item @qcode{'Burr'} @tab @qcode{'burr'} @tab 3 ## @item @qcode{'Cauchy'} @tab @qcode{'cauchy'} @tab 2 ## @item @qcode{'Chi-squared'} @tab @qcode{'chi2'} @tab 1 ## @item @qcode{'Extreme Value'} @tab @qcode{'ev'} @tab 2 ## @item @qcode{'Exponential'} @tab @qcode{'exp'} @tab 1 ## @item @qcode{'F-Distribution'} @tab @qcode{'f'} @tab 2 ## @item @qcode{'Gamma'} @tab @qcode{'gam'} @tab 2 ## @item @qcode{'Geometric'} @tab @qcode{'geo'} @tab 1 ## @item @qcode{'Generalized Extreme Value'} @tab @qcode{'gev'} @tab 3 ## @item @qcode{'Generalized Pareto'} @tab @qcode{'gp'} @tab 3 ## @item @qcode{'Gumbel'} @tab @qcode{'gumbel'} @tab 2 ## @item @qcode{'Half-normal'} @tab @qcode{'hn'} @tab 2 ## @item @qcode{'Hypergeometric'} @tab @qcode{'hyge'} @tab 3 ## @item @qcode{'Inverse Gaussian'} @tab @qcode{'invg'} @tab 2 ## @item @qcode{'Laplace'} @tab @qcode{'laplace'} @tab 2 ## @item @qcode{'Logistic'} @tab @qcode{'logi'} @tab 2 ## @item @qcode{'Log-Logistic'} @tab @qcode{'logl'} @tab 2 ## @item @qcode{'Lognormal'} @tab @qcode{'logn'} @tab 2 ## @item @qcode{'Nakagami'} @tab @qcode{'naka'} @tab 2 ## @item @qcode{'Negative Binomial'} @tab @qcode{'nbin'} @tab 2 ## @item @qcode{'Noncentral F-Distribution'} @tab @qcode{'ncf'} @tab 3 ## @item @qcode{'Noncentral Student T'} @tab @qcode{'nct'} @tab 2 ## @item @qcode{'Noncentral Chi-Squared'} @tab @qcode{'ncx2'} @tab 2 ## @item @qcode{'Normal'} @tab @qcode{'norm'} @tab 2 ## @item @qcode{'Poisson'} @tab @qcode{'poiss'} @tab 1 ## @item @qcode{'Rayleigh'} @tab @qcode{'rayl'} @tab 1 ## @item @qcode{'Rician'} @tab @qcode{'rice'} @tab 2 ## @item @qcode{'Student T'} @tab @qcode{'t'} @tab 1 ## @item @qcode{'location-scale T'} @tab @qcode{'tls'} @tab 3 ## @item @qcode{'Triangular'} @tab @qcode{'tri'} @tab 3 ## @item @qcode{'Discrete Uniform'} @tab @qcode{'unid'} @tab 1 ## @item @qcode{'Uniform'} @tab @qcode{'unif'} @tab 2 ## @item @qcode{'Von Mises'} @tab @qcode{'vm'} @tab 2 ## @item @qcode{'Weibull'} @tab @qcode{'wbl'} @tab 2 ## @end multitable ## ## Distribution names are matched ignoring case, spaces and hyphens, so that ## @qcode{'Extreme Value'}, @qcode{'ExtremeValue'} and @qcode{'extreme-value'} ## all select the same distribution, and the same set of names is accepted by ## @code{cdf}, @code{pdf}, @code{icdf}, @code{random}, @code{makedist}, ## @code{fitdist} and @code{mle}. ## ## This accepts more names than MATLAB. MATLAB takes the spaced and the ## squashed spelling but refuses the hyphenated one, so ## @qcode{'Birnbaum-Saunders'} and @qcode{'Log-Logistic'} are errors there; ## Octave has always accepted them and continues to. MATLAB also accepts ## @qcode{'tLocationScale'} in @code{makedist} while refusing it in ## @code{cdf} for the same distribution; Octave accepts it, and ## @qcode{'location-scale T'}, everywhere. Code written against MATLAB's ## names therefore runs unchanged, but code relying on these names will not ## port back. ## ## @seealso{cdf, icdf, pdf, betarnd, binornd, bisarnd, burrrnd, cauchyrnd, ## chi2rnd, evrnd, exprnd, frnd, gamrnd, geornd, gevrnd, gprnd, gumbelrnd, ## hnrnd, hygernd, invgrnd, laplacernd, logirnd, loglrnd, lognrnd, nakarnd, ## nbinrnd, ncfrnd, nctrnd, ncx2rnd, normrnd, poissrnd, raylrnd, ricernd, trnd, ## tlsrnd, trirnd, unidrnd, unifrnd, vmrnd, wblrnd} ## @end deftypefn function r = random (name, varargin) ## implemented functions persistent allDF = { ... {'beta' , 'Beta'}, @betarnd, 2, ... {'bino' , 'Binomial'}, @binornd, 2, ... {'bisa' , 'Birnbaum-Saunders'}, @bisarnd, 2, ... {'burr' , 'Burr'}, @burrrnd, 3, ... {'cauchy' , 'Cauchy'}, @cauchyrnd, 2, ... {'chi2' , 'Chi-squared'}, @chi2rnd, 1, ... {'ev' , 'Extreme Value'}, @evrnd, 2, ... {'exp' , 'Exponential'}, @exprnd, 1, ... {'f' , 'F-Distribution'}, @frnd, 2, ... {'gam' , 'Gamma'}, @gamrnd, 2, ... {'geo' , 'Geometric'}, @geornd, 1, ... {'gev' , 'Generalized Extreme Value'}, @gevrnd, 3, ... {'gp' , 'Generalized Pareto'}, @gprnd, 3, ... {'gumbel' , 'Gumbel'}, @gumbelrnd, 2, ... {'hn' , 'Half-normal'}, @hnrnd, 2, ... {'hyge' , 'Hypergeometric'}, @hygernd, 3, ... {'invg' , 'Inverse Gaussian'}, @invgrnd, 2, ... {'laplace' , 'Laplace'}, @laplacernd, 2, ... {'logi' , 'Logistic'}, @logirnd, 2, ... {'logl' , 'Log-Logistic'}, @loglrnd, 2, ... {'logn' , 'Lognormal'}, @lognrnd, 2, ... {'naka' , 'Nakagami'}, @nakarnd, 2, ... {'nbin' , 'Negative Binomial'}, @nbinrnd, 2, ... {'ncf' , 'Noncentral F-Distribution'}, @ncfrnd, 3, ... {'nct' , 'Noncentral Student T'}, @nctrnd, 2, ... {'ncx2' , 'Noncentral Chi-squared'}, @ncx2rnd, 2, ... {'norm' , 'Normal'}, @normrnd, 2, ... {'poiss' , 'Poisson'}, @poissrnd, 1, ... {'rayl' , 'Rayleigh'}, @raylrnd, 1, ... {'rice' , 'Rician'}, @ricernd, 2, ... {'t' , 'Student T'}, @trnd, 1, ... {'tls', 'location-scale T', 'tLocationScale'}, @tlsrnd, 3, ... {'tri' , 'Triangular'}, @trirnd, 3, ... {'unid' , 'Discrete Uniform'}, @unidrnd, 1, ... {'unif' , 'Uniform'}, @unifrnd, 2, ... {'vm' , 'Von Mises'}, @vmrnd, 2, ... {'wbl' , 'Weibull'}, @wblrnd, 2}; if (! ischar (name)) error ("random: distribution NAME must be a char string."); endif ## Get number of arguments nargs = numel (varargin); ## Get available functions rndnames = allDF(1:3:end); rndhandl = allDF(2:3:end); rnd_args = allDF(3:3:end); ## Search for RND function ## Match on the folded key so that every spelling of a name resolves key = __distname_key__ (name); idx = cellfun (@(x) any (strcmp (key, cellfun (@__distname_key__, x, ... 'UniformOutput', false))), rndnames); if (any (idx)) if (nargs == rnd_args{idx}) ## Check that all distribution parameters are numeric if (! all (cellfun (@(x)isnumeric (x), (varargin)))) error ("random: distribution parameters must be numeric."); endif ## Call appropriate RND r = feval (rndhandl{idx}, varargin{:}); elseif (nargs > rnd_args{idx}) ## Check that all distribution parameters are numeric if (! all (cellfun (@(x)isnumeric (x), (varargin(1:rnd_args{idx}))))) error ("random: distribution parameters must be numeric."); endif ## Call appropriate RND. SIZE arguments are checked by the RND function. r = feval (rndhandl{idx}, varargin{:}); else if (rnd_args{idx} == 1) error ("random: %s distribution requires 1 parameter.", name); else error ("random: %s distribution requires %d parameters.", ... name, rnd_args{idx}); endif endif else error ("random: %s distribution is not implemented in Statistics.", name); endif endfunction ## Test results %!assert_equal (size (random ('Beta', 5, 2, 2, 10)), size (betarnd (5, 2, 2, 10))) %!assert_equal (size (random ('beta', 5, 2, 2, 10)), size (betarnd (5, 2, 2, 10))) %!assert_equal (size (random ('Binomial', 5, 2, [10, 20])), size (binornd (5, 2, 10, 20))) %!assert_equal (size (random ('bino', 5, 2, [10, 20])), size (binornd (5, 2, 10, 20))) %!assert_equal (size (random ('Birnbaum-Saunders', 5, 2, [10, 20])), size (bisarnd (5, 2, 10, 20))) %!assert_equal (size (random ('bisa', 5, 2, [10, 20])), size (bisarnd (5, 2, 10, 20))) %!assert_equal (size (random ('Burr', 5, 2, 2, [10, 20])), size (burrrnd (5, 2, 2, 10, 20))) %!assert_equal (size (random ('burr', 5, 2, 2, [10, 20])), size (burrrnd (5, 2, 2, 10, 20))) %!assert_equal (size (random ('Cauchy', 5, 2, [10, 20])), size (cauchyrnd (5, 2, 10, 20))) %!assert_equal (size (random ('cauchy', 5, 2, [10, 20])), size (cauchyrnd (5, 2, 10, 20))) %!assert_equal (size (random ('Chi-squared', 5, [10, 20])), size (chi2rnd (5, 10, 20))) %!assert_equal (size (random ('chi2', 5, [10, 20])), size (chi2rnd (5, 10, 20))) %!assert_equal (size (random ('Extreme Value', 5, 2, [10, 20])), size (evrnd (5, 2, 10, 20))) %!assert_equal (size (random ('ev', 5, 2, [10, 20])), size (evrnd (5, 2, 10, 20))) %!assert_equal (size (random ('Exponential', 5, [10, 20])), size (exprnd (5, 10, 20))) %!assert_equal (size (random ('exp', 5, [10, 20])), size (exprnd (5, 10, 20))) %!assert_equal (size (random ('F-Distribution', 5, 2, [10, 20])), size (frnd (5, 2, 10, 20))) %!assert_equal (size (random ('f', 5, 2, [10, 20])), size (frnd (5, 2, 10, 20))) %!assert_equal (size (random ('Gamma', 5, 2, [10, 20])), size (gamrnd (5, 2, 10, 20))) %!assert_equal (size (random ('gam', 5, 2, [10, 20])), size (gamrnd (5, 2, 10, 20))) %!assert_equal (size (random ('Geometric', 5, [10, 20])), size (geornd (5, 10, 20))) %!assert_equal (size (random ('geo', 5, [10, 20])), size (geornd (5, 10, 20))) %!assert_equal (size (random ('Generalized Extreme Value', 5, 2, 2, [10, 20])), size (gevrnd (5, 2, 2, 10, 20))) %!assert_equal (size (random ('gev', 5, 2, 2, [10, 20])), size (gevrnd (5, 2, 2, 10, 20))) %!assert_equal (size (random ('Generalized Pareto', 5, 2, 2, [10, 20])), size (gprnd (5, 2, 2, 10, 20))) %!assert_equal (size (random ('gp', 5, 2, 2, [10, 20])), size (gprnd (5, 2, 2, 10, 20))) %!assert_equal (size (random ('Gumbel', 5, 2, [10, 20])), size (gumbelrnd (5, 2, 10, 20))) %!assert_equal (size (random ('gumbel', 5, 2, [10, 20])), size (gumbelrnd (5, 2, 10, 20))) %!assert_equal (size (random ('Half-normal', 5, 2, [10, 20])), size (hnrnd (5, 2, 10, 20))) %!assert_equal (size (random ('hn', 5, 2, [10, 20])), size (hnrnd (5, 2, 10, 20))) %!assert_equal (size (random ('Hypergeometric', 5, 2, 2, [10, 20])), size (hygernd (5, 2, 2, 10, 20))) %!assert_equal (size (random ('hyge', 5, 2, 2, [10, 20])), size (hygernd (5, 2, 2, 10, 20))) %!assert_equal (size (random ('Inverse Gaussian', 5, 2, [10, 20])), size (invgrnd (5, 2, 10, 20))) %!assert_equal (size (random ('invg', 5, 2, [10, 20])), size (invgrnd (5, 2, 10, 20))) %!assert_equal (size (random ('Laplace', 5, 2, [10, 20])), size (laplacernd (5, 2, 10, 20))) %!assert_equal (size (random ('laplace', 5, 2, [10, 20])), size (laplacernd (5, 2, 10, 20))) %!assert_equal (size (random ('Logistic', 5, 2, [10, 20])), size (logirnd (5, 2, 10, 20))) %!assert_equal (size (random ('logi', 5, 2, [10, 20])), size (logirnd (5, 2, 10, 20))) %!assert_equal (size (random ('Log-Logistic', 5, 2, [10, 20])), size (loglrnd (5, 2, 10, 20))) %!assert_equal (size (random ('logl', 5, 2, [10, 20])), size (loglrnd (5, 2, 10, 20))) %!assert_equal (size (random ('Lognormal', 5, 2, [10, 20])), size (lognrnd (5, 2, 10, 20))) %!assert_equal (size (random ('logn', 5, 2, [10, 20])), size (lognrnd (5, 2, 10, 20))) %!assert_equal (size (random ('Nakagami', 5, 2, [10, 20])), size (nakarnd (5, 2, 10, 20))) %!assert_equal (size (random ('naka', 5, 2, [10, 20])), size (nakarnd (5, 2, 10, 20))) %!assert_equal (size (random ('Negative Binomial', 5, 2, [10, 20])), size (nbinrnd (5, 2, 10, 20))) %!assert_equal (size (random ('nbin', 5, 2, [10, 20])), size (nbinrnd (5, 2, 10, 20))) %!assert_equal (size (random ('Noncentral F-Distribution', 5, 2, 2, [10, 20])), size (ncfrnd (5, 2, 2, 10, 20))) %!assert_equal (size (random ('ncf', 5, 2, 2, [10, 20])), size (ncfrnd (5, 2, 2, 10, 20))) %!assert_equal (size (random ('Noncentral Student T', 5, 2, [10, 20])), size (nctrnd (5, 2, 10, 20))) %!assert_equal (size (random ('nct', 5, 2, [10, 20])), size (nctrnd (5, 2, 10, 20))) %!assert_equal (size (random ('Noncentral Chi-Squared', 5, 2, [10, 20])), size (ncx2rnd (5, 2, 10, 20))) %!assert_equal (size (random ('ncx2', 5, 2, [10, 20])), size (ncx2rnd (5, 2, 10, 20))) %!assert_equal (size (random ('Normal', 5, 2, [10, 20])), size (normrnd (5, 2, 10, 20))) %!assert_equal (size (random ('norm', 5, 2, [10, 20])), size (normrnd (5, 2, 10, 20))) %!assert_equal (size (random ('Poisson', 5, [10, 20])), size (poissrnd (5, 10, 20))) %!assert_equal (size (random ('poiss', 5, [10, 20])), size (poissrnd (5, 10, 20))) %!assert_equal (size (random ('Rayleigh', 5, [10, 20])), size (raylrnd (5, 10, 20))) %!assert_equal (size (random ('rayl', 5, [10, 20])), size (raylrnd (5, 10, 20))) %!assert_equal (size (random ('Rician', 5, 1, [10, 20])), size (ricernd (5, 1, 10, 20))) %!assert_equal (size (random ('rice', 5, 1, [10, 20])), size (ricernd (5, 1, 10, 20))) %!assert_equal (size (random ('Student T', 5, [10, 20])), size (trnd (5, 10, 20))) %!assert_equal (size (random ('t', 5, [10, 20])), size (trnd (5, 10, 20))) %!assert_equal (size (random ('location-scale T', 5, 1, 2, [10, 20])), size (tlsrnd (5, 1, 2, 10, 20))) %!assert_equal (size (random ('tls', 5, 1, 2, [10, 20])), size (tlsrnd (5, 1, 2, 10, 20))) %!assert_equal (size (random ('Triangular', 5, 2, 2, [10, 20])), size (trirnd (5, 2, 2, 10, 20))) %!assert_equal (size (random ('tri', 5, 2, 2, [10, 20])), size (trirnd (5, 2, 2, 10, 20))) %!assert_equal (size (random ('Discrete Uniform', 5, [10, 20])), size (unidrnd (5, 10, 20))) %!assert_equal (size (random ('unid', 5, [10, 20])), size (unidrnd (5, 10, 20))) %!assert_equal (size (random ('Uniform', 5, 2, [10, 20])), size (unifrnd (5, 2, 10, 20))) %!assert_equal (size (random ('unif', 5, 2, [10, 20])), size (unifrnd (5, 2, 10, 20))) %!assert_equal (size (random ('Von Mises', 5, 2, [10, 20])), size (vmrnd (5, 2, 10, 20))) %!assert_equal (size (random ('vm', 5, 2, [10, 20])), size (vmrnd (5, 2, 10, 20))) %!assert_equal (size (random ('Weibull', 5, 2, [10, 20])), size (wblrnd (5, 2, 10, 20))) %!assert_equal (size (random ('wbl', 5, 2, [10, 20])), size (wblrnd (5, 2, 10, 20))) ## Test input validation %!error random (1) %!error random ({'beta'}) %!error ... %! random ('Beta', 'a', 2) %!error ... %! random ('Beta', 5, '') %!error ... %! random ('Beta', 5, {2}) %!error ... %! random ('Beta', 'a', 2, 2, 10) %!error ... %! random ('Beta', 5, '', 2, 10) %!error ... %! random ('Beta', 5, {2}, 2, 10) %!error ... %! random ('Beta', 5, '', 2, 10) %!error random ('chi2') %!error random ('Beta', 5) %!error random ('Burr', 5) %!error random ('Burr', 5, 2) statistics-release-1.9.2/inst/Experimental_Design/000077500000000000000000000000001524624707500222525ustar00rootroot00000000000000statistics-release-1.9.2/inst/Experimental_Design/doc-cache000066400000000000000000000271561524624707500240160ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 5 # name: # type: sq_string # elements: 1 # length: 4 ff2n # name: # type: sq_string # elements: 1 # length: 415 statistics: dFF2 = ff2n ( n ) Two-level full factorial design. dFF2 = ff2n ( n ) gives factor settings dFF2 for a two-level full factorial design with n factors. dFF2 is m-by-n, where m is the number of treatments in the full-factorial design. Each row of dFF2 corresponds to a single treatment. Each column contains the settings for a single factor, with values of 0 and 1 for the two levels. See also: fullfact # name: # type: sq_string # elements: 1 # length: 32 Two-level full factorial design. # name: # type: sq_string # elements: 1 # length: 8 fullfact # name: # type: sq_string # elements: 1 # length: 464 statistics: A = fullfact ( levels ) Full factorial design. A = fullfact ( levels ) returns a numeric matrix A with the treatments of a full factorial design specified by levels , which must be a numeric vector of real positive integer values with each value specifying the number of levels of each individual factor. Each row of A corresponds to a single treatment and each column to a single factor. For binary full factorial design, use ff2n . See also: ff2n # name: # type: sq_string # elements: 1 # length: 22 Full factorial design. # name: # type: sq_string # elements: 1 # length: 21 parseWilkinsonFormula # name: # type: sq_string # elements: 1 # length: 6099 statistics: terms = parseWilkinsonFormula ( formula ) statistics: result = parseWilkinsonFormula ( formula , mode ) statistics: [ X , y , names ] = parseWilkinsonFormula ( formula , "model_matrix", data ) statistics: S = parseWilkinsonFormula ( formula , "mixed") Parse and expand statistical model formulae using the Wilkinson notation. This function implements the recursive-descent parser and expansion logic described by Wilkinson & Rogers (1973) for factorial models. It allows the symbolic specification of analysis of variance and regression models, converting strings into computational schemas or design matrices. It also supports multi-variable response specification on the Left-Hand Side (LHS) using lists or ranges. parseWilkinsonFormula accepts as its first input argument a Wilkinson notation string specified by formula either as a character vector or a string scalar with the following list of valid symbols: Right-Hand Side (Model) Operators The RHS specifies the independent variables (predictors) and the structural relationships between them, such as interactions and nesting. The parser expands these expressions into fundamental model terms following the standard statistical rules of marginality. Additionally, explicit nesting notation (e.g., B(A) ) is supported to denote that factor B is nested within A. Operator Description Expansion Example + Addition (Union) A + B expands to A, B * Crossing A * B expands to A, B, A:B - Deletion A*B - A:B expands to A, B / Nesting A / B expands to A, A:B : Interaction A : B expands to A:B ^ Power (Limit) (A+B)^2 expands to A, B, A:B 1 Intercept y ~ A - 1 removes intercept Left-Hand Side (Response) Operators The LHS, separated by the ~ operator, defines the dependent variables. It natively supports multi-response syntaxes. Operator Description Usage Example ~ Formula separator y ~ x , List separator y1, y2 ~ x - Range operator T1 - T3 ~ x Processing Modes parseWilkinsonFormula ( formula , mode ) evaluates the formula string based on the selected mode : 'expand' (default) - Returns a structure containing response and model fields. Each field contains cell arrays of the expanded, fundamental terms. 'equation' - Generates a string representing the mathematical equation of the fitted model. Coefficients are represented generically as c1, c2, ... . If multiple responses are specified, it returns a string array of equations. Formula String Equation Output y ~ x "y = c1 + c2*x" y ~ A * B "y = c1 + c2*A + c3*B + c4*A*B" y ~ School / Class "y = c1 + c2*School + c3*Class*School" y ~ x^2 "y = c1 + c2*x + c3*x^2" y1 - y2 ~ Trt ["y1 = c1 + c2*Trt", "y2 = ..."] 'matrix' - Returns a schema structure containing a binary matrix defining term membership, useful for internal algorithmic processing. 'model_matrix' - Constructs the numeric Design Matrix ( X ) and Response Matrix ( y ) directly from a provided data table. 'parse' - Returns the raw Abstract Syntax Tree (AST) structure. 'tokenize' - Returns the array of tokens generated by the lexer. 'mixed' - Decomposes a mixed-effects formula containing random-effects terms of the form ( expr | group ) into its fixed and random parts. See Mixed-Effects Formulae below. Data Handling (’model_matrix’ mode) When using the 'model_matrix' mode, a data argument must be provided as an Octave table . Categorical Variables: Cell arrays of strings in the table are automatically detected as categorical factors and undergo corner-point (reference) dummy coding. Numeric Variables: Standard numeric vectors are treated as continuous predictors or responses. Missing Data: Rows containing NaN values in any of the active variables are automatically omitted from the final matrices. Mixed-Effects Formulae (’mixed’ mode) A random-effects term is written ( expr | group ) , where expr is a Wilkinson design expression for the random intercept and slopes and group is the grouping factor (or an interaction of factors such as g1:g2 ). As with fixed effects an intercept is implicit; suppress it with 0 or -1 (for example (x - 1 | g) or (-1 + x | g) for a random slope with no random intercept). The 'mixed' mode returns a structure with the following fields: Response The response (LHS) as a character vector. FixedTerms A cell array of the expanded fixed-effects terms, excluding the intercept. FixedIntercept A logical flag, true when the fixed model includes an intercept. Random A struct array with one element per random-effects term, each with fields Expr (the raw expression), Terms (its expanded predictor terms), Intercept (logical), Group (the raw grouping spec), and GroupVars (its grouping variables as a cell array). HasRandom A logical flag, true when the formula contains any random-effects term. A formula with no random-effects term is still valid in 'mixed' mode: it returns HasRandom false and an empty Random array. Outputs terms / result The processed model structure, string array, or cell array depending on the selected mode . X The generated numeric design matrix (Observations x Parameters). Includes a column of ones for the intercept unless - 1 is in the formula. y The numeric response matrix (Observations x K responses). names A cell array of character vectors containing the column names corresponding to the generated design matrix X . References Wilkinson, G. N. and Rogers, C. E. (1973). Symbolic Description of Factorial Models for Analysis of Variance. Applied Statistics, 22, 392-399. In "model_matrix" mode a categorical variable expands to indicator columns, one per level bar the reference level, which the intercept carries. The levels of a character or string column are taken in the order the data presents them, so the reference level is the one seen first; a categorical column uses its own category order. When the formula has no intercept, the first categorical variable is given an indicator for every one of its levels and any further categorical variable stays reference coded. MATLAB omits the reference level whether or not an intercept is present, and so cannot fit the reference group. # name: # type: sq_string # elements: 1 # length: 73 Parse and expand statistical model formulae using the Wilkinson notation. # name: # type: sq_string # elements: 1 # length: 9 sigma_pts # name: # type: sq_string # elements: 1 # length: 1024 statistics: pts = sigma_pts ( n ) statistics: pts = sigma_pts ( n , m ) statistics: pts = sigma_pts ( n , m , K ) statistics: pts = sigma_pts ( n , m , K , l ) Calculates 2* n +1 sigma points in n dimensions. Sigma points are used in the unscented transform to estimate the result of applying a given nonlinear transformation to a probability distribution that is characterized only in terms of a finite set of statistics. If only the dimension n is given the resulting points have zero mean and identity covariance matrix. If the mean m or the covariance matrix K are given, then the resulting points will have those statistics. The factor l scales the points away from the mean. It is useful to tune the accuracy of the unscented transform. There is no unique way of computing sigma points, this function implements the algorithm described in section 2.6 "The New Filter" pages 40-41 of Uhlmann, Jeffrey (1995). "Dynamic Map Building and Localization: New Theoretical Foundations". Ph.D. thesis. University of Oxford. # name: # type: sq_string # elements: 1 # length: 46 Calculates 2*n+1 sigma points in n dimensions. # name: # type: sq_string # elements: 1 # length: 4 x2fx # name: # type: sq_string # elements: 1 # length: 2466 statistics: [ d , model , termstart , termend ] = x2fx ( x ) statistics: [ d , model , termstart , termend ] = x2fx ( x , model ) statistics: [ d , model , termstart , termend ] = x2fx ( x , model , categ ) statistics: [ d , model , termstart , termend ] = x2fx ( x , model , categ , catlevels ) Convert predictors to design matrix. d = x2fx ( x , model ) converts a matrix of predictors x to a design matrix d for regression analysis. Distinct predictor variables should appear in different columns of x . The optional input model controls the regression model. By default, x2fx returns the design matrix for a linear additive model with a constant term. model can be any one of the following strings: "linear" Constant and linear terms (the default) "interaction" Constant, linear, and interaction terms "quadratic" Constant, linear, interaction, and squared terms "purequadratic" Constant, linear, and squared terms If x has n columns, the order of the columns of d for a full quadratic model is: The constant term. The linear terms (the columns of X, in order 1,2,...,n). The interaction terms (pairwise products of columns of x , in order (1,2), (1,3), ..., (1,n), (2,3), ..., (n-1,n). The squared terms (in the order 1,2,...,n). Other models use a subset of these terms, in the same order. Alternatively, MODEL can be a matrix specifying polynomial terms of arbitrary order. In this case, MODEL should have one column for each column in X and one r for each term in the model. The entries in any r of MODEL are powers for the corresponding columns of x . For example, if x has columns X1, X2, and X3, then a row [0 1 2] in model would specify the term (X1.^0).*(X2.^1).*(X3.^2). A row of all zeros in model specifies a constant term, which you can omit. d = x2fx ( x , model , categ ) treats columns with numbers listed in the vector categ as categorical variables. Terms involving categorical variables produce dummy variable columns in d . Dummy variables are computed under the assumption that possible categorical levels are completely enumerated by the unique values that appear in the corresponding column of x . d = x2fx ( x , model , categ , catlevels ) accepts a vector catlevels the same length as categ , specifying the number of levels in each categorical variable. In this case, values in the corresponding column of x must be integers in the range from 1 to the specified number of levels. Not all of the levels need to appear in x . # name: # type: sq_string # elements: 1 # length: 36 Convert predictors to design matrix. statistics-release-1.9.2/inst/Experimental_Design/ff2n.m000066400000000000000000000040651524624707500232700ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## based on public domain work by Paul Kienzle ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{dFF2} =} ff2n (@var{n}) ## ## Two-level full factorial design. ## ## @code{@var{dFF2} = ff2n (@var{n})} gives factor settings dFF2 for a two-level ## full factorial design with n factors. @var{dFF2} is m-by-n, where m is the ## number of treatments in the full-factorial design. Each row of @var{dFF2} ## corresponds to a single treatment. Each column contains the settings for a ## single factor, with values of 0 and 1 for the two levels. ## ## @seealso{fullfact} ## @end deftypefn function A = ff2n (n) if (nargin != 1) error ("ff2n: wrong number of input arguments."); endif if (floor (n) != n || numel (n) != 1 || n < 1 ... || ! isfinite (n) || ! isreal (n)) error ("ff2n: @var{N} must be a positive integer scalar."); endif A = flip (fullfact (2 * ones (1, n)), 2) - 1; endfunction %!error ff2n (); %!error ff2n (2, 5); %!error ff2n (2.5); %!error ff2n (0); %!error ff2n (-3); %!error ff2n (3+2i); %!error ff2n (Inf); %!error ff2n (NaN); %!test %! A = ff2n (3); %! assert_equal (A, [0, 0, 0; 0, 0, 1; 0, 1, 0; 0, 1, 1; ... %! 1, 0, 0; 1, 0, 1; 1, 1, 0; 1, 1, 1]); %!test %! A = ff2n (2); %! assert_equal (A, [0, 0; 0, 1; 1, 0; 1, 1]); statistics-release-1.9.2/inst/Experimental_Design/fullfact.m000066400000000000000000000105111524624707500242260ustar00rootroot00000000000000## Copyright (C) 2022-2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{A} =} fullfact (@var{levels}) ## ## Full factorial design. ## ## @code{@var{A} = fullfact (@var{levels})} returns a numeric matrix @var{A} ## with the treatments of a full factorial design specified by @var{levels}, ## which must be a numeric vector of real positive integer values with each ## value specifying the number of levels of each individual factor. ## ## Each row of @var{A} corresponds to a single treatment and each column to a ## single factor. For binary full factorial design, use @code{ff2n}. ## ## @seealso{ff2n} ## @end deftypefn function A = fullfact (levels) if (nargin != 1) error ("fullfact: one input argument is required."); endif if (! (isvector (levels) && isnumeric (levels) && isfinite (levels))) error ("fullfact: input argument must be a finite real numeric vector."); endif if (any (fix (levels) != levels) || any (levels < 1) || ! all (isreal (levels))) error ("fullfact: factor levels must be real positive integers."); endif rows = prod (levels); cols = numel (levels); A = zeros (rows, cols); n_seqs = rows; for i = 1:cols factor = [1:levels(i)]'; n_reps = rows / n_seqs; factor = repelem (factor, n_reps, 1); n_seqs = n_seqs / levels(i); factor = repmat (factor, n_seqs, 1); A(:,i) = factor; endfor endfunction %!demo %! ## Full factorial design with 3 ordinal variables %! fullfact ([2, 3, 4]) %!error fullfact (); %!error ... %! fullfact (Inf); %!error ... %! fullfact (NaN); %!error ... %! fullfact (ones (2)); %!error ... %! fullfact ([1, 2, NaN]); %!error ... %! fullfact ([1, 2, Inf]); %!error fullfact (2.5); %!error fullfact (0); %!error fullfact (-3); %!error fullfact (3+2i); %!error fullfact ([1, 2, -3]); %!error fullfact ([0, 1, 2]); %!test %! A = fullfact (1); %! assert_equal (A, 1); %!test %! A = fullfact (2); %! assert_equal (A, [1; 2]); %!test %!test %! A = fullfact (3); %! assert_equal (A, [1; 2; 3]); %!test %! A = fullfact ([1, 2, 4]); %! A_out = [1, 1, 1; 1, 2, 1; 1, 1, 2; 1, 2, 2; ... %! 1, 1, 3; 1, 2, 3; 1, 1, 4; 1, 2, 4]; %! assert_equal (A, A_out); %!test %! A = fullfact ([2, 2]); %! assert_equal (A, [1, 1; 2, 1; 1, 2; 2, 2]); %!test %! A = fullfact ([2, 2, 4]); %! A_out = [1, 1, 1; 2, 1, 1; 1, 2, 1; 2, 2, 1; ... %! 1, 1, 2; 2, 1, 2; 1, 2, 2; 2, 2, 2; ... %! 1, 1, 3; 2, 1, 3; 1, 2, 3; 2, 2, 3; ... %! 1, 1, 4; 2, 1, 4; 1, 2, 4; 2, 2, 4]; %! assert_equal (A, A_out); %!test %! A = fullfact ([3, 2, 4]); %! A_out = [1, 1, 1; 2, 1, 1; 3, 1, 1; 1, 2, 1; 2, 2, 1; 3, 2, 1; ... %! 1, 1, 2; 2, 1, 2; 3, 1, 2; 1, 2, 2; 2, 2, 2; 3, 2, 2; ... %! 1, 1, 3; 2, 1, 3; 3, 1, 3; 1, 2, 3; 2, 2, 3; 3, 2, 3; ... %! 1, 1, 4; 2, 1, 4; 3, 1, 4; 1, 2, 4; 2, 2, 4; 3, 2, 4]; %! assert_equal (A, A_out); %!test %! A = fullfact ([4, 2]); %! assert_equal (A, [1, 1; 2, 1; 3, 1; 4, 1; 1, 2; 2, 2; 3, 2; 4, 2]); statistics-release-1.9.2/inst/Experimental_Design/parseWilkinsonFormula.m000066400000000000000000002267371524624707500270070ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{terms} =} parseWilkinsonFormula (@var{formula}) ## @deftypefnx {statistics} {@var{result} =} parseWilkinsonFormula (@var{formula}, @var{mode}) ## @deftypefnx {statistics} {[@var{X}, @var{y}, @var{names}] =} parseWilkinsonFormula (@var{formula}, "model_matrix", @var{data}) ## @deftypefnx {statistics} {@var{S} =} parseWilkinsonFormula (@var{formula}, "mixed") ## ## Parse and expand statistical model formulae using the Wilkinson notation. ## ## This function implements the recursive-descent parser and expansion logic ## described by Wilkinson & Rogers (1973) for factorial models. It allows the ## symbolic specification of analysis of variance and regression models, ## converting strings into computational schemas or design matrices. It also ## supports multi-variable response specification on the Left-Hand Side (LHS) ## using lists or ranges. ## ## @code{parseWilkinsonFormula} accepts as its first input argument a Wilkinson ## notation string specified by @var{formula} either as a character vector or a ## string scalar with the following list of valid symbols: ## ## @strong{Right-Hand Side (Model) Operators} ## The RHS specifies the independent variables (predictors) and the structural ## relationships between them, such as interactions and nesting. The parser ## expands these expressions into fundamental model terms following the standard ## statistical rules of marginality. Additionally, explicit nesting notation ## (e.g., @code{B(A)}) is supported to denote that factor B is nested within A. ## ## @multitable @columnfractions 0.15 0.35 0.50 ## @headitem Operator @tab Description @tab Expansion Example ## @item @code{+} @tab Addition (Union) @tab @code{A + B} expands to A, B ## @item @code{*} @tab Crossing @tab @code{A * B} expands to A, B, A:B ## @item @code{-} @tab Deletion @tab @code{A*B - A:B} expands to A, B ## @item @code{/} @tab Nesting @tab @code{A / B} expands to A, A:B ## @item @code{:} @tab Interaction @tab @code{A : B} expands to A:B ## @item @code{^} @tab Power (Limit) @tab @code{(A+B)^2} expands to A, B, A:B ## @item @code{1} @tab Intercept @tab @code{y ~ A - 1} removes intercept ## @end multitable ## ## ## @strong{Left-Hand Side (Response) Operators} ## The LHS, separated by the @code{~} operator, defines the dependent variables. ## It natively supports multi-response syntaxes. ## ## @multitable @columnfractions 0.15 0.35 0.50 ## @headitem Operator @tab Description @tab Usage Example ## @item @code{~} @tab Formula separator @tab @code{y ~ x} ## @item @code{,} @tab List separator @tab @code{y1, y2 ~ x} ## @item @code{-} @tab Range operator @tab @code{T1 - T3 ~ x} ## @end multitable ## ## @strong{Processing Modes} ## @code{parseWilkinsonFormula (@var{formula}, @var{mode})} evaluates the ## formula string based on the selected @var{mode}: ## ## @itemize ## @item @code{'expand'} (default) - Returns a structure containing ## @code{response} and @code{model} fields. Each field contains cell arrays ## of the expanded, fundamental terms. ## ## @item @code{'equation'} - Generates a string representing the mathematical ## equation of the fitted model. Coefficients are represented generically as ## @code{c1, c2, ...}. If multiple responses are specified, it returns a ## string array of equations. ## ## @multitable @columnfractions 0.40 0.60 ## @headitem Formula String @tab Equation Output ## @item @code{y ~ x} @tab @code{"y = c1 + c2*x"} ## @item @code{y ~ A * B} @tab @code{"y = c1 + c2*A + c3*B + c4*A*B"} ## @item @code{y ~ School / Class} @tab ## @code{"y = c1 + c2*School + ## c3*Class*School"} ## @item @code{y ~ x^2} @tab @code{"y = c1 + c2*x + c3*x^2"} ## @item @code{y1 - y2 ~ Trt} @tab @code{["y1 = c1 + c2*Trt", "y2 = ..."]} ## @end multitable ## ## @item @code{'matrix'} - Returns a schema structure containing a binary ## matrix defining term membership, useful for internal algorithmic processing. ## ## @item @code{'model_matrix'} - Constructs the numeric Design Matrix (@var{X}) ## and Response Matrix (@var{y}) directly from a provided data table. ## ## @item @code{'parse'} - Returns the raw Abstract Syntax Tree (AST) structure. ## ## @item @code{'tokenize'} - Returns the array of tokens generated by the lexer. ## ## @item @code{'mixed'} - Decomposes a mixed-effects formula containing ## random-effects terms of the form @code{(@var{expr} | @var{group})} into its ## fixed and random parts. See @strong{Mixed-Effects Formulae} below. ## @end itemize ## ## @strong{Data Handling ('model_matrix' mode)} ## When using the @code{'model_matrix'} mode, a @var{data} argument must be ## provided as an Octave @code{table}. ## @itemize ## @item @strong{Categorical Variables:} Cell arrays of strings in the table ## are automatically detected as categorical factors and undergo corner-point ## (reference) dummy coding. ## @item @strong{Numeric Variables:} Standard numeric vectors are treated as ## continuous predictors or responses. ## @item @strong{Missing Data:} Rows containing @code{NaN} values in any of the ## active variables are automatically omitted from the final matrices. ## @end itemize ## ## @strong{Mixed-Effects Formulae ('mixed' mode)} ## A random-effects term is written @code{(@var{expr} | @var{group})}, where ## @var{expr} is a Wilkinson design expression for the random intercept and ## slopes and @var{group} is the grouping factor (or an interaction of factors ## such as @code{g1:g2}). As with fixed effects an intercept is implicit; ## suppress it with @code{0} or @code{-1} (for example @code{(x - 1 | g)} or ## @code{(-1 + x | g)} for a random slope with no random intercept). The ## @code{'mixed'} mode returns a structure with the following fields: ## ## @table @code ## @item Response ## The response (LHS) as a character vector. ## @item FixedTerms ## A cell array of the expanded fixed-effects terms, excluding the intercept. ## @item FixedIntercept ## A logical flag, true when the fixed model includes an intercept. ## @item Random ## A struct array with one element per random-effects term, each with fields ## @code{Expr} (the raw expression), @code{Terms} (its expanded predictor ## terms), @code{Intercept} (logical), @code{Group} (the raw grouping spec), ## and @code{GroupVars} (its grouping variables as a cell array). ## @item HasRandom ## A logical flag, true when the formula contains any random-effects term. ## @end table ## ## A formula with no random-effects term is still valid in @code{'mixed'} mode: ## it returns @code{HasRandom} false and an empty @code{Random} array. ## ## @strong{Outputs} ## @table @var ## @item terms / result ## The processed model structure, string array, or cell array depending on the ## selected @var{mode}. ## @item X ## The generated numeric design matrix (Observations x Parameters). Includes ## a column of ones for the intercept unless @code{- 1} is in the formula. ## @item y ## The numeric response matrix (Observations x K responses). ## @item names ## A cell array of character vectors containing the column names corresponding ## to the generated design matrix @var{X}. ## @end table ## ## @strong{References} ## ## Wilkinson, G. N. and Rogers, C. E. (1973). Symbolic Description of Factorial ## Models for Analysis of Variance. Applied Statistics, 22, 392-399. ## ## ## In @qcode{"model_matrix"} mode a categorical variable expands to indicator ## columns, one per level bar the reference level, which the intercept carries. ## The levels of a character or string column are taken in the order the data ## presents them, so the reference level is the one seen first; a ## @code{categorical} column uses its own category order. When the formula has ## no intercept, the first categorical variable is given an indicator for every ## one of its levels and any further categorical variable stays reference ## coded. MATLAB omits the reference level whether or not an intercept is ## present, and so cannot fit the reference group. ## ## @end deftypefn function varargout = parseWilkinsonFormula (varargin) if (nargin < 1) error ("parseWilkinsonFormula: Input formula string is required."); elseif (nargin > 4) error ("parseWilkinsonFormula: Too many input arguments."); endif formula_str = varargin{1}; if (nargin > 1) mode = varargin{2}; else mode = 'expand'; endif mode = lower (mode); ## Mixed-effects decomposition: split off the random-effects '(expr|group)' ## terms at the string level and return a structured decomposition. Kept ## fully separate from the fixed-effects pipeline so the other modes are ## unaffected by formulas without random effects. if (strcmp (mode, 'mixed')) varargout{1} = parse_mixed_formula (formula_str); return; endif ## A '|' only ever appears inside a random-effects group, which the ## fixed-effects modes cannot represent. Fail with a clear pointer to 'mixed' ## rather than the lexer's generic "unexpected character". if (! isempty (strfind (char (formula_str), '|'))) error (strcat ("parseWilkinsonFormula: random-effects formulae (with", ... " '(...|...)' terms) require the 'mixed' mode.")); endif if (strcmp (mode, 'tokenize')) varargout{1} = run_lexer (formula_str); return; endif if (! strcmp (mode, 'model_matrix') && ! strcmp (mode, 'equation')) tokens = run_lexer (formula_str); [tree, curr] = run_parser (tokens); ## Check for Nested Tildes. if (isstruct (tree) && strcmp (tree.type, 'OPERATOR') && strcmp (tree.value, '~')) if ((! isempty (tree.left) && isstruct (tree.left) && strcmp (tree.left.type, 'OPERATOR') && strcmp (tree.left.value, '~')) || (! isempty (tree.right) && isstruct (tree.right) && strcmp (tree.right.type, 'OPERATOR') && strcmp (tree.right.value, '~'))) error ("parseWilkinsonFormula: Unexpected token"); endif endif ## Check for trailing. if (curr <= length (tokens) && ! strcmp (tokens(curr).type, 'EOF')) error ("parseWilkinsonFormula: Unexpected token"); endif else tree = []; endif ## Mode specific Processing switch (mode) case 'parse' varargout{1} = tree; case 'expand' varargout{1} = run_expander (tree, mode); case 'equation' has_data = nargin > 2 && isa (varargin{3}, 'table'); data_table = []; if (has_data), data_table = varargin{3}; endif [lhs_str, rhs_terms] = split_and_expand_rhs (formula_str, mode); ## resolve LHS. if (has_data) lhs_vars = resolve_lhs_vars (lhs_str, data_table); else lhs_vars = resolve_lhs_symbolic (lhs_str); endif ## build the required output. varargout{1} = run_equation_builder (lhs_vars, rhs_terms); case 'matrix' expanded = run_expander (tree, mode); varargout{1} = run_schema_builder (expanded); case 'model_matrix' if (nargin < 3) error (strcat ("parseWilkinsonFormula: 'model_matrix'", ... " mode requires a Data Table.")); endif data_table = varargin{3}; if (! isa (data_table, 'table')) error ("parseWilkinsonFormula: Input data must be a 'table' class."); endif [lhs_str, rhs_terms] = split_and_expand_rhs (formula_str, mode); ## build schema. schema = run_schema_builder (rhs_terms); ## resolve LHS. if (! isempty (lhs_str)) schema.ResponseVars = resolve_lhs_vars (lhs_str, data_table); else schema.ResponseVars = {}; endif ## build the required matrix. ## Variables the caller declares categorical, over and above those whose ## own type says so. catvars = {}; if (nargin > 3) catvars = varargin{4}; if (! iscellstr (catvars)) error (strcat ("parseWilkinsonFormula: CATVARS must be a cell", ... " array of character vectors.")); endif endif [X, y, names] = run_model_matrix_builder (schema, data_table, catvars); varargout{1} = X; if (nargout > 1), varargout{2} = y; endif if (nargout > 2), varargout{3} = names; endif otherwise error ("parseWilkinsonFormula: Unknown mode: %s", mode); endswitch endfunction ## lexer function tokens = run_lexer (formula_str) if (isempty (formula_str)) tokens = struct ('type', {}, 'value', {}, 'pos', {}); return; endif str = char (formula_str); n = length (str); tokens(n) = struct ('type', '', 'value', '', 'pos', 0); tok_idx = 0; i = 1; while (i <= n) c = str(i); start_pos = i; if (isspace (c)) i = i + 1; continue; endif if (c == ',') tok_idx = tok_idx + 1; tokens(tok_idx) = create_token ('COMMA', ',', start_pos); i++; continue; endif ## Identifiers (Factors) if (isletter (c)) val = c; i = i + 1; while (i <= n) next_c = str(i); if (isletter (next_c) || (next_c >= '0' && next_c <= '9') ... || next_c == '_') val = [val, next_c]; i = i + 1; else break; endif endwhile tok_idx = tok_idx + 1; tokens(tok_idx) = create_token ('IDENTIFIER', val, start_pos); continue; endif ## Numbers if (c >= '0' && c <= '9') val = c; i = i + 1; while (i <= n) next_c = str(i); if (next_c >= '0' && next_c <= '9') val = [val, next_c]; i = i + 1; elseif (next_c == '.') ## Simple float support if (i < n && str(i+1) >= '0' && str(i+1) <= '9') val = [val, next_c]; i = i + 1; else break; endif else break; endif endwhile tok_idx = tok_idx + 1; tokens(tok_idx) = create_token ('NUMBER', val, start_pos); continue; endif ## Operators type = ''; val = c; skip = 0; switch (c) case '-' if (i < n) if (i < n && str(i+1) == '/') type = 'OP_MINUS_MARGIN'; val = '-/'; skip = 1; elseif (i < n && str(i+1) == '*') type = 'OP_MINUS_CLEAN'; val = '-*'; skip = 1; else type = 'OP_MINUS'; endif else type = 'OP_MINUS'; endif case '*' type = 'OP_CROSS'; case '^' type = 'OP_POWER'; case '/' type = 'OP_NEST'; case '+' type = 'OP_PLUS'; case {'.', ':'} type = 'OP_DOT'; case '~' type = 'SEPARATOR'; case '(' type = 'LPAREN'; case ')' type = 'RPAREN'; otherwise error (strcat ("parseWilkinsonFormula: Unexpected", ... " character '%s' at position %d", c, i)); endswitch tok_idx = tok_idx + 1; tokens(tok_idx) = create_token (type, val, start_pos); i = i + 1 + skip; endwhile tokens = tokens(1:tok_idx); tokens(end+1) = create_token ('EOF', 'EOF', i); endfunction function t = create_token (type, val, pos) t.type = type; t.value = val; t.pos = pos; endfunction function [tree, curr] = run_parser (tokens, curr, prec_limit) if (nargin < 2), curr = 1; endif if (nargin < 3), prec_limit = 0; endif n = length (tokens); if (curr > n) error ("parseWilkinsonFormula: Unexpected End Of Formula."); endif t = tokens(curr); curr = curr + 1; ## Basic Units if (strcmp (t.type, 'IDENTIFIER')) ## Check for Function Call: IDENTIFIER followed by LPAREN if (curr <= n && strcmp (tokens(curr).type, 'LPAREN')) func_name = t.value; curr = curr + 1; args = {}; ## Parse arguments if (curr <= n && ! strcmp (tokens(curr).type, 'RPAREN')) while (true) [arg_node, curr] = run_parser (tokens, curr, 0); args{end+1} = arg_node; if (curr <= n && strcmp (tokens(curr).type, 'COMMA')) curr = curr + 1; else break; endif endwhile endif if (curr <= n && strcmp (tokens(curr).type, 'RPAREN')) curr = curr + 1; else error (strcat ("parseWilkinsonFormula: Missing ')'", ... " for function call '%s'."), func_name); endif tree.type = 'FUNCTION'; tree.name = func_name; tree.args = args; tree.left = []; tree.right = []; else ## Standard Variable tree.type = t.type; tree.value = t.value; tree.left = []; tree.right = []; endif elseif (strcmp (t.type, 'NUMBER')) tree.type = t.type; tree.value = t.value; tree.left = []; tree.right = []; elseif (strcmp (t.type, 'SEPARATOR')) ## Handle unary '~' tree.type = 'OPERATOR'; tree.value = t.value; tree.left = []; ## Recursively parse the RHS with precedence 5. [tree.right, curr] = run_parser (tokens, curr, 5); elseif (strcmp (t.type, 'LPAREN')) [tree, curr] = run_parser (tokens, curr, 0); if (curr <= n && strcmp (tokens(curr).type, 'RPAREN')) curr = curr + 1; else error ("parseWilkinsonFormula: Mismatched Parentheses. Missing ')'."); endif elseif (strcmp (t.type, 'EOF')) error ("parseWilkinsonFormula: Unexpected End Of Formula."); else error ("parseWilkinsonFormula: Syntax Error. Unexpected token: '%s'", t.value); endif ## Operator Handling while (curr <= n) op_type = tokens(curr).type; op_prec = 0; ## precedences if (strcmp (op_type, 'OP_POWER')) op_prec = 60; elseif (strcmp (op_type, 'OP_DOT')) op_prec = 50; elseif (strcmp (op_type, 'OP_NEST')) op_prec = 40; elseif (strcmp (op_type, 'OP_CROSS')) op_prec = 30; elseif (strcmp (op_type, 'OP_PLUS')) op_prec = 20; elseif (strncmp (op_type, 'OP_MINUS', 8)) op_prec = 10; elseif (strcmp (op_type, 'SEPARATOR')) op_prec = 5; else break; endif if (op_prec <= prec_limit) break; endif op_val = tokens(curr).value; curr = curr + 1; [right, curr] = run_parser (tokens, curr, op_prec); new_node.type = 'OPERATOR'; new_node.value = op_val; new_node.left = tree; new_node.right = right; tree = new_node; endwhile endfunction ## expander function result = run_expander (node, mode) if (nargin < 2), mode = 'expand'; endif if (isempty (node)) result = {}; return; endif ## Terminals if (strcmp (node.type, 'IDENTIFIER')) result = {{node.value}}; return; elseif (strcmp (node.type, 'FUNCTION')) if (strcmp (mode, 'equation')) ## preserve nesting syntax. args_str_parts = {}; for k = 1:length (node.args) arg_res = run_expander (node.args{k}, mode); if (! isempty (arg_res) && ! isempty (arg_res{1})) args_str_parts{end+1} = arg_res{1}{1}; else args_str_parts{end+1} = ''; endif endfor full_term = sprintf ("%s(%s)", node.name, strjoin (args_str_parts, ',')); result = {{full_term}}; else ## mathematical expansion in matrix mode. result = {{node.name}}; for k = 1:length (node.args) arg_expanded = run_expander (node.args{k}, mode); result = list_product (result, arg_expanded); endfor endif return; elseif (strcmp (node.type, 'NUMBER')) if (strcmp (node.value, '1')) result = {{}}; else result = {{node.value}}; endif return; endif if (strcmp (node.type, 'OPERATOR')) if (strcmp (node.value, '~')) result.response = run_expander (node.left, mode); add_intercept = true; if (! isempty (node.right) && strcmp (node.right.type, 'OPERATOR') ... && (strcmp (node.right.value, '-') ... || strcmp (node.right.value, '-/') ... || strcmp (node.right.value, '-*'))) if (! isempty (node.right.right) ... && strcmp (node.right.right.type, 'NUMBER') ... && strcmp (node.right.right.value, '1')) add_intercept = false; endif endif model_raw = run_expander (node.right, mode); if (add_intercept) result.model = list_union ({{}}, model_raw); else result.model = model_raw; endif return; endif lhs = run_expander (node.left, mode); rhs = run_expander (node.right, mode); switch (node.value) case '+' result = list_union (lhs, rhs); case {'.', ':'} result = list_product (lhs, rhs); case '*' interaction = list_product (lhs, rhs); step1 = list_union (lhs, rhs); result = list_union (step1, interaction); case '^' if (strcmp (node.left.type, 'IDENTIFIER') && strcmp (node.right.type, 'NUMBER')) base_name = node.left.value; power_val = round (str2double (node.right.value)); result = {}; for k = 1:power_val if (k == 1) result{end+1} = {base_name}; else result{end+1} = {sprintf("%s^%d", base_name, k)}; endif endfor return; endif base_terms = run_expander (node.left, mode); if (! strcmp (node.right.type, 'NUMBER')) error ("parseWilkinsonFormula: Exponent must be a number."); endif power_val = str2double (node.right.value); result = base_terms; ## Repeatedly apply Crossing. for k = 2:power_val interaction = list_product (result, base_terms); step1 = list_union (result, base_terms); result = list_union (step1, interaction); endfor case '/' max_L = get_maximal_terms (lhs); interaction = list_product (max_L, rhs); result = list_union (lhs, interaction); case '-' ## Simple Deletion result = list_difference (lhs, rhs, 'exact'); case '-*' ## Delete term + Higher Order Interactions result = list_difference (lhs, rhs, 'clean'); case '-/' ## Delete terms where T is marginal result = list_difference (lhs, rhs, 'margin'); otherwise error ("parseWilkinsonFormula: Unknown operator '%s'", node.value); endswitch return; endif error ("parseWilkinsonFormula: Corrupt Tree."); endfunction ## set operations. function C = list_union (A, B) raw_list = [A, B]; C = simplify_term_list (raw_list); endfunction function C = list_product (A, B) C = {}; idx = 1; for i = 1:length (A) for j = 1:length (B) ## Dot product merges factor sets: new_term = union (A{i}, B{j}); C{idx} = new_term; idx = idx + 1; endfor endfor C = simplify_term_list (C); endfunction function C = list_difference (S, T, mode) if (isempty (S)), C = {}; return; endif if (isempty (T)), C = S; return; endif C = {}; strS = terms_to_strings (S); strT = terms_to_strings (T); keep_mask = true (size (S)); for i = 1:length (S) term_s = S{i}; s_str = strS{i}; for j = 1:length (T) term_t = T{j}; t_str = strT{j}; match = false; switch (mode) case 'exact' if (strcmp (s_str, t_str)) match = true; endif case 'clean' ## Delete T and any S where T is a subset of S if (strcmp (s_str, t_str) || is_subset (term_t, term_s)) match = true; endif case 'margin' ## Delete S where T is subset of S (but not T itself) if (! strcmp (s_str, t_str) && is_subset (term_t, term_s)) match = true; endif endswitch if (match) keep_mask(i) = false; break; endif endfor endfor C = S(keep_mask); endfunction function fac = get_fac (term_list) all_factors = {}; for i = 1:length (term_list) all_factors = union (all_factors, term_list{i}); endfor fac = {all_factors}; endfunction function is_sub = is_subset (small_set, large_set) is_sub = all (ismember (small_set, large_set)); endfunction function clean_list = simplify_term_list (raw_list) if (isempty (raw_list)) clean_list = {}; return; endif str_sigs = terms_to_strings (raw_list); [~, unique_idx] = unique (str_sigs); clean_list = raw_list(sort (unique_idx)); endfunction function strs = terms_to_strings (term_list) strs = cell (size (term_list)); for i = 1:length (term_list) if (isempty (term_list{i})) strs{i} = '1'; else sorted_factors = sort (term_list{i}); strs{i} = strjoin (sorted_factors, ':'); endif endfor endfunction ## schema builder function schema = run_schema_builder (expanded) ## Handle struct vs cell if (isstruct (expanded)) if (isfield (expanded, 'model')) rhs_terms = expanded.model; else rhs_terms = expanded.rhs; endif if (isfield (expanded, 'response')) lhs_term = expanded.response; else lhs_term = expanded.lhs; endif else rhs_terms = expanded; lhs_term = {}; endif function out = flatten_recursive (in_val) out = {}; if (ischar (in_val) || isstring (in_val)) out = {char(in_val)}; elseif (iscell (in_val)) for k = 1:numel (in_val) out = [out, flatten_recursive(in_val{k})]; endfor endif endfunction ## extract variables all_vars = {}; if (! isempty (lhs_term)) all_vars = [all_vars, flatten_recursive(lhs_term)]; endif cleaned_rhs = cell (length (rhs_terms), 1); for i = 1:length (rhs_terms) term_vars = flatten_recursive (rhs_terms{i}); final_term_vars = {}; for j = 1:length (term_vars) parts = strsplit (term_vars{j}, ':'); final_term_vars = [final_term_vars, parts]; endfor cleaned_rhs{i} = final_term_vars; all_vars = [all_vars, final_term_vars]; endfor all_vars = unique (all_vars); ## Remove intercept marker from var list all_vars(strcmp (all_vars, '1')) = []; schema.VariableNames = all_vars; ## Identify Response schema.ResponseIdx = []; if (! isempty (lhs_term)) flat_lhs = flatten_recursive (lhs_term); if (! isempty (flat_lhs)) [found, idx] = ismember (flat_lhs{1}, all_vars); if (found), schema.ResponseIdx = idx; endif endif endif ## Build terms matrix n_vars = length (all_vars); n_terms = length (cleaned_rhs); terms_mat = zeros (n_terms, n_vars); for i = 1:n_terms vars_in_this_term = cleaned_rhs{i}; ## Check for intercept term. if (isempty (vars_in_this_term) || (length (vars_in_this_term) == 1 && strcmp (vars_in_this_term{1}, '1'))) continue; endif [found, idx] = ismember (vars_in_this_term, all_vars); if (any (! found)) error ("parseWilkinsonFormula: Unknown variable in term definition."); endif terms_mat(i, idx) = 1; endfor ## sorting : order by true polynomial degree, not raw presence count. var_degree = ones (1, n_vars); for v = 1:n_vars tok = regexp (all_vars{v}, '\^(\d+)$', 'tokens'); if (! isempty (tok)) var_degree(v) = str2double (tok{1}{1}); endif endfor term_orders = terms_mat * var_degree'; M = [term_orders, terms_mat]; [~, unique_idx] = unique (M, 'rows'); terms_mat = terms_mat(unique_idx, :); [~, sort_idx] = sortrows ([terms_mat * var_degree', -terms_mat]); schema.Terms = terms_mat(sort_idx, :); endfunction ## model matrix builder. function [X, y, col_names] = run_model_matrix_builder (schema, data, catvars) if (nargin < 3) catvars = {}; endif req_vars = schema.VariableNames; table_vars = data.Properties.VariableNames; ## A model's terms are ordered by the variable order of the data, not by the ## order the formula happens to name them: over a table whose columns are ## (u, g), both 'resp ~ 1 + u + g' and 'resp ~ 1 + g + u' give the same model, ## while the same formula over a (g, u) table puts g first. The schema is ## built without the data in hand, so it arrives in the parser's own order; ## sort it into the table's here, then order the terms over it. [req_vars, schema.Terms, perm] = order_by_table (req_vars, schema.Terms, ... table_vars); if (! isempty (schema.ResponseIdx)) schema.ResponseIdx = find (perm == schema.ResponseIdx, 1); endif schema.Terms = order_terms (schema.Terms, req_vars, table_vars); ## Data validation & masking if (isempty (req_vars)) n_total = height (data); valid_mask = true (n_total, 1); else base0 = regexprep (req_vars{1}, '\^.*$', ''); if (! ismember (base0, table_vars)) error ("parseWilkinsonFormula: Unknown variable '%s' in Data Table.", ... req_vars{1}); endif n_total = length (data.(base0)); valid_mask = true (n_total, 1); for i = 1:length (req_vars) base_i = regexprep (req_vars{i}, '\^.*$', ''); col = data.(base_i); if (isnumeric (col)) valid_mask = valid_mask & ! isnan (col); endif endfor endif if (! isempty (schema.ResponseIdx)) y_name = req_vars{schema.ResponseIdx}; if (! ismember (y_name, table_vars)) error ("parseWilkinsonFormula: Unknown variable '%s' in Data Table.", ... y_name); endif y_col = data.(y_name); if (isnumeric (y_col)) valid_mask = valid_mask & ! isnan (y_col); endif endif if (isfield (schema, 'ResponseVars') && ! isempty (schema.ResponseVars)) for k = 1:length (schema.ResponseVars) y_name = schema.ResponseVars{k}; if (ismember (y_name, table_vars)) col = data.(y_name); if (isnumeric (col)) valid_mask = valid_mask & ! isnan (col); endif endif endfor endif n_rows = sum (valid_mask); ## Process predictors var_info = struct (); for i = 1:length (req_vars) vname = req_vars{i}; hat_pos = strfind (vname, '^'); if (! isempty (hat_pos)) base_name = vname(1:hat_pos(1)-1); exp_val = str2double (vname(hat_pos(1)+1:end)); else base_name = vname; exp_val = 1; endif raw = data.(base_name); if (iscell (raw)), raw = raw(valid_mask); else, raw = raw(valid_mask, :); endif ## A logical column, or a numeric one the caller declared categorical, is ## coded by its distinct values in ascending order, so the omitted ## reference level is the smallest. Left to itself a logical column would ## fall through to the character branch below, where 'cellstr' turns false ## and true into the characters of code 0 and 1. is_declared = any (strcmp (catvars, base_name)); if (islogical (raw) || (is_declared && isnumeric (raw))) if (exp_val != 1) error (strcat ("parseWilkinsonFormula: Power operator '^' is", ... " only valid on numeric variables.")); endif vals = double (raw(:)); vals = sort (unique (vals(! isnan (vals)))); var_info.(vname).type = 'categorical'; var_info.(vname).levels = arrayfun (@(x) strtrim (num2str (x)), ... vals, 'UniformOutput', false); [~, var_info.(vname).indices] = ismember (double (raw), vals); elseif (isnumeric (raw)) var_info.(vname).type = 'numeric'; var_info.(vname).data = raw .^ exp_val; elseif (exp_val != 1) error (strcat ("parseWilkinsonFormula: Power operator '^' is", ... " only valid on numeric variables.")); elseif (isa (raw, 'categorical')) var_info.(vname).type = 'categorical'; var_info.(vname).levels = categories (raw); [~, var_info.(vname).indices] = ismember (raw, var_info.(vname).levels); else if (! iscellstr (raw) && ! isstring (raw)) raw = cellstr (raw); endif ## The levels of a character or string grouping column are taken in the ## order the data presents them, not in sorted order, so the omitted ## reference level is the one seen first. A 'categorical' column carries ## its own category order and is handled above. [u, ~, idx] = unique (raw, 'stable'); var_info.(vname).type = 'categorical'; var_info.(vname).levels = u; var_info.(vname).indices = idx; endif endfor ## Build Design Matrix X X = []; col_names = {}; ## Check for intercept term. intercept_row_idx = find (sum (schema.Terms, 2) == 0); has_intercept = ! isempty (intercept_row_idx); ## Without an intercept the first categorical variable takes its place and is ## given all of its levels; any further categorical stays reference coded, or ## the design would be rank deficient with each full set of indicators ## summing to the intercept column. full_coded_var = ''; if (! has_intercept) for v = 1:numel (req_vars) if (strcmp (var_info.(req_vars{v}).type, 'categorical')) full_coded_var = req_vars{v}; break; endif endfor endif n_terms = size (schema.Terms, 1); for i = 1:n_terms term_row = schema.Terms(i, :); vars_idx = find (term_row); ## Intercept Term if (isempty (vars_idx)) X = [X, ones(n_rows, 1)]; col_names = [col_names; '(Intercept)']; continue; endif current_block = ones (n_rows, 1); current_names = {''}; for v = vars_idx vname = req_vars{v}; info = var_info.(vname); if (strcmp (info.type, 'numeric')) current_block = current_block .* info.data; for k = 1:length (current_names) if (isempty (current_names{k})) current_names{k} = vname; else current_names{k} = [current_names{k}, ':', vname]; endif endfor else ## Categorical n_lev = length (info.levels); ## Drop the reference level only when something can carry it: an ## intercept, or the first categorical standing in for one. This is ## the cell-means parameterisation that dropping the intercept asks ## for. MATLAB drops the reference level either way and so cannot fit ## the reference group at all: 'resp ~ h2 - 1' over a three-level h2 ## gives it two coefficients, fitted values of exactly 0 for that group ## and a negative R^2 (measured against R2024a, 2026-08-05). R and ## patsy both code every level here. Do not "fix" this to match. if (has_intercept || ! strcmp (vname, full_coded_var)) start_lev = 2; n_cols = n_lev - 1; else start_lev = 1; n_cols = n_lev; endif dummies = zeros (n_rows, n_cols); dum_names = {}; for L = start_lev:n_lev col_idx = L - start_lev + 1; dummies(:, col_idx) = (info.indices == L); dum_names = [dum_names; ... sprintf("%s_%s", vname, char (info.levels{L}))]; endfor ## Cartesian product of current block and new dummies next_block = []; next_names = {}; for c1 = 1:size (current_block, 2) for c2 = 1:size (dummies, 2) next_block = [next_block, current_block(:, c1) .* dummies(:, c2)]; n1 = current_names{c1}; n2 = dum_names{c2}; if (isempty (n1)) next_names = [next_names; n2]; else next_names = [next_names; [n1, ':', n2]]; endif endfor endfor current_block = next_block; current_names = next_names; endif endfor X = [X, current_block]; col_names = [col_names; current_names]; endfor ## Extract Response y = []; if (isfield (schema, 'ResponseVars') && ! isempty (schema.ResponseVars)) y_vars = schema.ResponseVars; y = zeros (n_rows, length (y_vars)); for k = 1:length (y_vars) y_name = y_vars{k}; raw_y = data.(y_name); if (iscell (raw_y)) col_data = raw_y(valid_mask); try col_data = cell2mat (col_data); catch error (strcat ("parseWilkinsonFormula: Response", ... " variable '%s' must be numeric."), y_name); end_try_catch else col_data = raw_y(valid_mask, :); endif if (! isnumeric (col_data)) error (strcat ("parseWilkinsonFormula: Response", ... " variable '%s' must be numeric"), y_name); endif if (size (col_data, 1) != n_rows) error (strcat ("parseWilkinsonFormula: Mismatch in number", ... " of rows for response variable '%s'"), y_name); endif y(:, k) = col_data; endfor ## fallback to previous. elseif (! isempty (schema.ResponseIdx)) y_name = req_vars{schema.ResponseIdx}; raw_y = data.(y_name); if (iscell (raw_y)), y = raw_y(valid_mask); else, y = raw_y(valid_mask, :); endif endif endfunction ## Sort a schema's variables into the order the data table lists them and ## permute the columns of its terms matrix to match. A power such as 'u^2' ## sorts with its base variable, immediately after it. Names the table does not ## carry keep their relative order at the end, so that an unresolvable variable ## still reaches the error raised for it further down. function [vars, terms, perm] = order_by_table (vars, terms, table_vars) n = numel (vars); key = zeros (n, 2); for i = 1:n base = regexprep (vars{i}, '\^.*$', ''); j = find (strcmp (table_vars, base), 1); if (isempty (j)) j = numel (table_vars) + i; endif key(i,:) = [j, var_power(vars{i})]; endfor [~, perm] = sortrows (key); vars = vars(perm); terms = terms(:, perm); endfunction ## Order the rows of a terms matrix the way a model's terms are ordered: by the ## total degree of the term, then -- within one degree -- interactions before ## powers, and last by the variables the term involves. With the columns ## already in the data's variable order this yields '1 + u + v + u:v + u^2'. function terms = order_terms (terms, vars, table_vars) n_terms = rows (terms); n_vars = columns (terms); if (n_terms < 2) return; endif base_idx = zeros (1, n_vars); degree = ones (1, n_vars); for j = 1:n_vars base = regexprep (vars{j}, '\^.*$', ''); k = find (strcmp (table_vars, base), 1); if (isempty (k)) k = numel (table_vars) + j; endif base_idx(j) = k; degree(j) = var_power (vars{j}); endfor ## The intercept row is all zeros, so it keys as degree 0 and sorts first. key = zeros (n_terms, n_vars + 2); for t = 1:n_terms bases = unique (base_idx(terms(t, :) != 0)); key(t, 1) = terms(t, :) * degree(:); key(t, 2) = -numel (bases); key(t, 3:2+numel (bases)) = bases; endfor [~, ord] = sortrows (key); terms = terms(ord, :); endfunction ## The exponent carried by a variable name, 1 when it carries none. function p = var_power (name) p = 1; tok = regexp (name, '\^(\d+)$', 'tokens'); if (! isempty (tok)) p = str2double (tok{1}{1}); endif endfunction function max_terms = get_maximal_terms (term_list) n = length (term_list); if (n == 0), max_terms = {}; return; endif is_max = true (1, n); for i = 1:n for j = 1:n if (i == j), continue; endif ## If term 'i' is a subset of 'j', it is NOT maximal if (is_subset (term_list{i}, term_list{j})) is_max(i) = false; break; endif endfor endfor max_terms = term_list(is_max); endfunction function vars = resolve_lhs_vars (lhs_str, data) all_names = data.Properties.VariableNames; vars = {}; if (isempty (lhs_str)), return; endif parts = strsplit (lhs_str, ','); for i = 1:length (parts) p = strtrim (parts{i}); if (isempty (p)), continue; endif ## check for the range. range_parts = strsplit (p, '-'); if (length (range_parts) == 2) start_var = strtrim (range_parts{1}); end_var = strtrim (range_parts{2}); [found_s, idx_s] = ismember (start_var, all_names); [found_e, idx_e] = ismember (end_var, all_names); if (! found_s) error ("parseWilkinsonFormula: Unknown variable '%s' in range", ... start_var); endif if (! found_e) error ("parseWilkinsonFormula: Unknown variable '%s' in range", ... end_var); endif ## Slice names. if (idx_s <= idx_e) range_vars = all_names(idx_s:idx_e); else range_vars = all_names(idx_e:idx_s); endif vars = [vars, range_vars]; elseif (length (range_parts) == 1) ## Single Variable if (! any (strcmp (all_names, p))) error (strcat ("parseWilkinsonFormula: Response", ... " variable '%s' not found in Data."), p); endif vars = [vars, {p}]; else error ("parseWilkinsonFormula: Invalid syntax in response term '%s'", p); endif endfor vars = unique (vars, 'stable'); endfunction function [lhs_str, rhs_terms] = split_and_expand_rhs (formula_str, mode) if (nargin < 2), mode = 'expand'; endif tilde_idx = strfind (formula_str, '~'); if (! isempty (tilde_idx)) lhs_str = strtrim (formula_str(1:tilde_idx(1)-1)); rhs_str = strtrim (formula_str(tilde_idx(1)+1:end)); else lhs_str = ''; rhs_str = formula_str; endif ## process RHS rhs_tokens = run_lexer (rhs_str); [rhs_tree, ~] = run_parser (rhs_tokens); wrapper.type = 'OPERATOR'; wrapper.value = '~'; wrapper.left = []; wrapper.right = rhs_tree; expanded = run_expander (wrapper, mode); ## extract the terms. if (isstruct (expanded) && isfield (expanded, 'model')) rhs_terms = expanded.model; else rhs_terms = expanded; endif endfunction function vars = resolve_lhs_symbolic (lhs_str) vars = {}; if (isempty (lhs_str)), return; endif parts = strsplit (lhs_str, ','); for i = 1:length (parts) p = strtrim (parts{i}); if (isempty (p)), continue; endif range_parts = strsplit (p, '-'); if (length (range_parts) == 2) s_str = strtrim (range_parts{1}); e_str = strtrim (range_parts{2}); [s_tok] = regexp (s_str, '^([a-zA-Z_]\w*)(\d+)$', 'tokens'); [e_tok] = regexp (e_str, '^([a-zA-Z_]\w*)(\d+)$', 'tokens'); if (! isempty (s_tok) && ! isempty (e_tok)) prefix = s_tok{1}{1}; s_num = str2double (s_tok{1}{2}); e_prefix = e_tok{1}{1}; e_num = str2double (e_tok{1}{2}); if (strcmp (prefix, e_prefix) && s_num <= e_num) for n = s_num:e_num vars{end+1} = sprintf ("%s%d", prefix, n); endfor continue; endif endif error ("parseWilkinsonFormula: Invalid symbolic range '%s'.", p); elseif (length (range_parts) == 1) vars{end+1} = p; else error ("parseWilkinsonFormula: Invalid syntax '%s'.", p); endif endfor vars = unique (vars, 'stable'); endfunction function eq_list = run_equation_builder (lhs_vars, rhs_terms) term_strs = {}; for i = 1:length (rhs_terms) t = rhs_terms{i}; if (isempty (t)) term_strs{end+1} = ''; else if (length (t) == 1 && any (strfind (t{1}, '('))) term_strs{end+1} = t{1}; else term_strs{end+1} = strjoin (sort (t), '*'); endif endif endfor lines = {}; c_idx = 1; for k = 1:length (lhs_vars) rhs_parts = {}; for t = 1:length (term_strs) t_str = term_strs{t}; coeff = sprintf ("c%d", c_idx++); if (isempty (t_str)) rhs_parts{end+1} = coeff; elseif (strcmp (t_str, '1')) rhs_parts{end+1} = coeff; else rhs_parts{end+1} = sprintf ("%s*%s", coeff, t_str); endif endfor full_rhs = strjoin (rhs_parts, ' + '); if (isempty (full_rhs)), full_rhs = '0'; endif lines{end+1} = sprintf ("%s = %s", lhs_vars{k}, full_rhs); endfor eq_list = string (lines'); endfunction ## --------------------------------------------------------------------------- ## Mixed-effects formula support: y ~ + ( | ) + ... ## --------------------------------------------------------------------------- ## Decompose a mixed-model formula into its fixed and random parts. Returns a ## struct: Response (char LHS), FixedTerms (cell of predictor term-cells, no ## intercept marker), FixedIntercept (logical), Random (struct array with fields ## Expr/Terms/Intercept/Group/GroupVars), HasRandom (logical). function S = parse_mixed_formula (formula_str) formula_str = char (formula_str); tilde = strfind (formula_str, '~'); if (isempty (tilde)) lhs_str = ''; rhs_str = strtrim (formula_str); else lhs_str = strtrim (formula_str(1:tilde(1)-1)); rhs_str = strtrim (formula_str(tilde(1)+1:end)); endif [fixed_str, specs] = extract_random_effects (rhs_str); ## Fixed part -- full existing Wilkinson grammar, unchanged. if (isempty (strtrim (fixed_str))) fixed_terms = {}; fixed_int = true; ## MATLAB includes a fixed intercept else [~, ft] = split_and_expand_rhs (["__resp__ ~ ", fixed_str], "expand"); fixed_int = any (cellfun (@isempty, ft)); fixed_terms = ft(! cellfun (@isempty, ft)); endif ## Random parts. R = struct ("Expr", {}, "Terms", {}, "Intercept", {}, ... "Group", {}, "GroupVars", {}); for i = 1:numel (specs) [rt, hi] = parse_random_expr (specs(i).Expr); gv = parse_group_spec (specs(i).Group); R(i) = struct ("Expr", specs(i).Expr, "Terms", {rt}, "Intercept", hi, ... "Group", specs(i).Group, "GroupVars", {gv}); endfor S.Response = lhs_str; S.FixedTerms = fixed_terms; S.FixedIntercept = fixed_int; S.Random = R; S.HasRandom = ! isempty (specs); ## Reconstructed fixed-only formula, so callers can build the fixed design ## through the ordinary 'model_matrix' path. if (isempty (strtrim (fixed_str))) fixed_rhs = "1"; else fixed_rhs = fixed_str; endif if (isempty (lhs_str)) S.FixedFormula = ["~ ", fixed_rhs]; else S.FixedFormula = [lhs_str, " ~ ", fixed_rhs]; endif endfunction ## Split a formula RHS into its fixed-effects string and the list of ## random-effects '(expr | group)' specs. Parentheses that do NOT contain a ## top-level '|' (e.g. nesting 'B(A)' or precedence grouping) are left in the ## fixed string for the ordinary parser. function [fixed_str, specs] = extract_random_effects (rhs_str) specs = struct ("Expr", {}, "Group", {}); out = ""; i = 1; n = numel (rhs_str); while (i <= n) c = rhs_str(i); if (c == "(") depth = 1; j = i + 1; pipe = 0; while (j <= n && depth > 0) cj = rhs_str(j); if (cj == "(") depth += 1; elseif (cj == ")") depth -= 1; if (depth == 0) break; endif elseif (cj == "|" && depth == 1 && pipe == 0) pipe = j; endif j += 1; endwhile if (depth != 0) error ("parseWilkinsonFormula: unbalanced parentheses in formula."); endif if (pipe > 0) expr = strtrim (rhs_str(i+1:pipe-1)); grp = strtrim (rhs_str(pipe+1:j-1)); if (isempty (expr) || isempty (grp)) error ("parseWilkinsonFormula: malformed random-effects term '(%s)'.", ... rhs_str(i+1:j-1)); endif specs(end+1) = struct ("Expr", expr, "Group", grp); else out = [out, rhs_str(i:j)]; ## ordinary group -> keep for fixed endif i = j + 1; else out = [out, c]; i += 1; endif endwhile fixed_str = clean_fixed_str (out); endfunction ## Tidy the fixed-effects string after random groups are removed: collapse the ## '+ +' and dangling leading/trailing '+' they leave behind. function s = clean_fixed_str (s) s = strtrim (s); s = regexprep (s, '\+\s*\+', '+'); s = regexprep (s, '^\s*\+', ''); s = regexprep (s, '\+\s*$', ''); s = strtrim (s); endfunction ## Expand a random-effects design expression into predictor terms + an ## intercept flag. Handles the intercept-suppression forms MATLAB accepts but ## the base parser does not: '-1 + x', 'x - 1', '0 + x', 'x + 0'. Random ## expressions use only '+', ':' and '*', so splitting on '+' is safe here. function [terms, has_int] = parse_random_expr (expr) expr = strtrim (regexprep (char (expr), '\s+', ' ')); has_int = true; ## leading '-1 +' / '-0 +' suppresses the intercept e2 = regexprep (expr, '^-\s*[01]\s*\+\s*', ''); if (! strcmp (e2, expr)), has_int = false; endif expr = e2; ## trailing '- 1', '- 0' or '+ 0' suppresses; trailing '+ 1' keeps it e3 = regexprep (expr, '\s*[-+]\s*[01]\s*$', ''); if (! strcmp (e3, expr)) tail = expr(numel (e3)+1:end); if (! isempty (strfind (tail, "-")) || ! isempty (strfind (tail, "0"))) has_int = false; endif endif expr = e3; atoms = strsplit (expr, "+"); preds = {}; for k = 1:numel (atoms) a = strtrim (atoms{k}); if (isempty (a) || strcmp (a, "1")) continue; ## empty or explicit intercept marker elseif (strcmp (a, "0")) has_int = false; else preds{end+1} = a; endif endfor if (isempty (preds)) terms = {}; return; endif [~, ex] = split_and_expand_rhs (["__d__ ~ ", strjoin(preds, " + ")], "expand"); terms = ex(! cellfun (@isempty, ex)); endfunction ## Parse a grouping spec into its variable list ('g' -> {g}; 'g:g2' -> {g,g2}). function gv = parse_group_spec (g) gv = {}; parts = strsplit (strtrim (g), ":"); for i = 1:numel (parts) p = strtrim (parts{i}); if (! isempty (p)) gv{end+1} = p; endif endfor endfunction %!demo %! %! ## Simple Linear Regression : %! ## This example models a continuous response (Height) as a linear function %! ## of a single continuous predictor (Age). The 'equation' mode returns the %! ## symbolic representation, while 'model_matrix' generates the design matrix. %! Age = [10; 12; 14; 16; 18]; %! Height = [140; 148; 155; 162; 170]; %! t = table (Height, Age); %! %! formula = 'Height ~ Age'; %! disp (['Formula: ', formula]); %! equation = parseWilkinsonFormula (formula, 'equation') %! [X, y, names] = parseWilkinsonFormula (formula, 'model_matrix', t) %!demo %! %! ## Multiple Regression : %! ## Here we model House Price based on two independent predictors: Area and %! ## number of Rooms. The '+' operator adds terms to the model without assuming %! ## any interaction between them. %! Price = [300; 350; 400; 450]; %! Area = [1500; 1800; 2200; 2500]; %! Rooms = [3; 3; 4; 5]; %! t = table (Price, Area, Rooms); %! %! formula = 'Price ~ Area + Rooms'; %! disp (['Formula: ', formula]); %! equation = parseWilkinsonFormula (formula, 'equation') %! [X, y, names] = parseWilkinsonFormula (formula, 'model_matrix', t) %!demo %! %! ## Interaction Effects : %! ## We analyze Relief Score based on Drug Type and Dosage Level. %! ## The '*' operator expands to the main effects PLUS the interaction term. %! ## Categorical variables are automatically created. %! Relief = [5; 7; 6; 8]; %! Drug = {'Placebo'; 'Placebo'; 'Active'; 'Active'}; %! Dose = {'Low'; 'High'; 'Low'; 'High'}; %! t = table (Relief, Drug, Dose); %! %! formula = 'Relief ~ Drug * Dose'; %! disp (['Formula: ', formula]); %! equation = parseWilkinsonFormula (formula, 'equation') %! [X, y, names] = parseWilkinsonFormula (formula, 'model_matrix', t) %!demo %! %! ## Polynomial Regression : %! ## Uses the power operator (^) to model non-linear relationships. %! Distance = [20; 45; 80; 125]; %! Speed = [30; 50; 70; 90]; %! Speed_2 = Speed .^ 2; %! t = table (Distance, Speed, Speed_2, 'VariableNames', {'Distance', 'Speed', 'Speed^2'}); %! %! formula = 'Distance ~ Speed^2'; %! disp (['Formula: ', formula]); %! equation = parseWilkinsonFormula (formula, 'equation') %! [X, y, names] = parseWilkinsonFormula (formula, 'model_matrix', t) %!demo %! %! ## Hierarchical Design. %! ## Common in psychometrics. Here, 'Class' is nested within 'School'. %! ## The '/' operator implies School + School:Class. %! Score = [88; 92; 75; 80]; %! School = {'North'; 'North'; 'South'; 'South'}; %! Class = {'Rm101'; 'Rm102'; 'Rm201'; 'Rm202'}; %! t = table (Score, School, Class); %! %! formula = 'Score ~ School / Class'; %! disp (['Formula: ', formula]); %! equation = parseWilkinsonFormula (formula, 'equation') %! terms = parseWilkinsonFormula (formula, 'expand') %!demo %! %! ## Explicit Nesting : %! ## The parser also supports the explicit 'B(A)' syntax, which means %! ## 'B is nested within A'. This is equivalent to the interaction 'A:B' %! ## but often used to denote random effects or specific hierarchy. %! formula = 'y ~ Class(School)'; %! disp (['Formula: ', formula]); %! equation = parseWilkinsonFormula (formula, 'equation') %! terms = parseWilkinsonFormula (formula, 'expand') %!demo %! %! ## Excluding Terms : %! ## Demonstrates building a complex model and then simplifying it. %! ## We define a full 3-way interaction (A*B*C) but explicitly remove the %! ## three-way term (A:B:C) using the minus operator. %! formula = 'y ~ (A + B + C)^3 - A:B:C'; %! disp (['Formula: ', formula]); %! equation = parseWilkinsonFormula (formula, 'equation') %! terms = parseWilkinsonFormula (formula, 'expand') %!demo %! %! ## Repeated Measures : %! ## This allows predicting multiple outcomes simultaneously. %! ## The range operator '-' selects all variables between 'T1' and 'T3' %! ## as the response matrix Y. %! T1 = [10; 11]; %! T2 = [12; 13]; %! T3 = [14; 15]; %! Treatment = {'Control'; 'Treated'}; %! t = table (T1, T2, T3, Treatment); %! %! formula = 'T1 - T3 ~ Treatment'; %! disp (['Formula: ', formula]); %! equations = parseWilkinsonFormula (formula, 'equation') %! [X, Y, names] = parseWilkinsonFormula (formula, 'model_matrix', t) %!test %! ## Test : Identifiers with numbers and underscores %! tokens = parseWilkinsonFormula ('Yield ~ Var_1 + A2_B', 'tokenize'); %! vals = {tokens.value}; %! assert_equal (vals, {'Yield', '~', 'Var_1', '+', 'A2_B', 'EOF'}); %!test %! ## Test : Floating point numbers %! tokens = parseWilkinsonFormula ('y ~ 0.5 * A', 'tokenize'); %! vals = {tokens.value}; %! assert_equal (vals, {'y', '~', '0.5', '*', 'A', 'EOF'}); %!test %! ## Test : Whitespace insensitivity %! t1 = parseWilkinsonFormula ('A*B', 'tokenize'); %! t2 = parseWilkinsonFormula ('A * B', 'tokenize'); %! assert_equal ({t1.value}, {t2.value}); %!test %! ## Test : Precedence %! t = parseWilkinsonFormula ('A + B * C . D', 'expand'); %! terms = cellfun (@(x) strjoin (sort (x), ':'), t, 'UniformOutput', false); %! assert_equal (sort (terms), sort ({'A', 'B', 'C:D', 'B:C:D'})); %!test %! ## Test : Parentheses Override %! t = parseWilkinsonFormula ('(A + B) . C', 'expand'); %! terms = cellfun (@(x) strjoin (sort (x), ':'), t, 'UniformOutput', false); %! assert_equal (sort (terms), sort ({'A:C', 'B:C'})); %!test %! ## Test : Crossing Operator (*) %! t = parseWilkinsonFormula ('A * B', 'expand'); %! assert_equal (length (t), 3); %! t3 = parseWilkinsonFormula ('A * B * C', 'expand'); %! assert_equal (length (t3), 7); %!test %! ## Test : Nesting Operator (/) %! t = parseWilkinsonFormula ('Field / Plot', 'expand'); %! terms = cellfun (@(x) strjoin (sort (x), ':'), t, 'UniformOutput', false); %! assert_equal (sort (terms), sort ({'Field', 'Field:Plot'})); %!test %! ## Test : Multi-level Nesting %! t = parseWilkinsonFormula ('Block / Plot / Subplot', 'expand'); %! terms = cellfun (@(x) strjoin (sort (x), ':'), t, 'UniformOutput', false); %! assert_equal (sort (terms), sort ({'Block', 'Block:Plot', 'Block:Plot:Subplot'})); %!test %! ## Test : Interaction Operator (.) %! t = parseWilkinsonFormula ('A . B', 'expand'); %! assert_equal (length (t), 1); %! assert_equal (t{1}, {'A', 'B'}); %!test %! ## Test : Power operator on cube. %! t = parseWilkinsonFormula ('(A + B + C)^3', 'expand'); %! terms = cellfun (@(x) strjoin (sort (x), ':'), t, 'UniformOutput', false); %! expected = sort ({'A', 'B', 'C', 'A:B', 'A:C', 'B:C', 'A:B:C'}); %! assert_equal (sort (terms), expected); %!test %! ## Test : Power Operator. %! t = parseWilkinsonFormula ('(A + B + C)^2', 'expand'); %! terms = cellfun (@(x) strjoin (sort (x), ':'), t, 'UniformOutput', false); %! assert_equal (! ismember ('A:B:C', terms), true); %! assert_equal (ismember ('A:B', terms), true); %!test %! ## Test : Redundancy Check %! t1 = parseWilkinsonFormula ('A + A', 'expand'); %! assert_equal (length (t1), 1); %! t2 = parseWilkinsonFormula ('A * A', 'expand'); %! assert_equal (length (t2), 1); %!test %! ## Test : Deletion - Exact (-) %! t = parseWilkinsonFormula ('A * B - A', 'expand'); %! terms = cellfun (@(x) strjoin (sort (x), ':'), t, 'UniformOutput', false); %! assert_equal (sort (terms), sort ({'B', 'A:B'})); %!test %! ## Test : Deletion - Clean (-*) %! t = parseWilkinsonFormula ('A * B -* A', 'expand'); %! terms = cellfun (@(x) strjoin (sort (x), ':'), t, 'UniformOutput', false); %! assert_equal (sort (terms), {'B'}); %!test %! ## Test : Deletion - Marginal (-/) %! t = parseWilkinsonFormula ('A * B -/ A', 'expand'); %! terms = cellfun (@(x) strjoin (sort (x), ':'), t, 'UniformOutput', false); %! assert_equal (sort (terms), sort ({'A', 'B'})); %!test %! ## Test : Deletion - Complex Sequence %! t = parseWilkinsonFormula ('A*B*C - A:B:C', 'expand'); %! assert_equal (length (t), 6); %! terms = cellfun (@(x) strjoin (sort (x), ':'), t, 'UniformOutput', false); %! assert_equal (! ismember ('A:B:C', terms), true); %! assert_equal (ismember ('A:B', terms), true); %!test %! ## Test : LHS and RHS Identification %! s = parseWilkinsonFormula ('logY ~ A + B', 'matrix'); %! assert_equal (s.VariableNames{s.ResponseIdx}, 'logY'); %! assert_equal (any (strcmp ('A', s.VariableNames)), true); %!test %! ## Test : No Response Variable %! s = parseWilkinsonFormula ('~ A + B', 'matrix'); %! assert_equal (isempty (s.ResponseIdx), true); %!test %! ## Test : Intercept Handling %! s1 = parseWilkinsonFormula ('~ A', 'matrix'); %! assert_equal (any (all (s1.Terms == 0, 2)), true); %! s2 = parseWilkinsonFormula ('~ A - 1', 'matrix'); %! assert_equal (! any (all (s2.Terms == 0, 2)), true); %!test %! ## Test : Numeric Interaction %! y = [1;2;3;4;5]; %! X1 = [1;2;1;2;1]; %! X2 = [10;10;20;20;10]; %! d = table (y, X1, X2); %! [M, ~, ~] = parseWilkinsonFormula ('y ~ X1:X2', 'model_matrix', d); %! assert_equal (size (M), [5, 2]); %! assert_equal (M(:, 2), d.X1 .* d.X2); %!test %! ## Test : Categorical Expansion %! y = [1;1;1]; %! G = {'A'; 'B'; 'C'}; %! d = table (y, G); %! [M, ~, names] = parseWilkinsonFormula ('~ G', 'model_matrix', d); %! assert_equal (size (M, 2), 3); %! assert_equal (names, {'(Intercept)'; 'G_B'; 'G_C'}); %!test %! ## Test : Categorical * Categorical Rank %! y = [1;2;3;4]; %! F1 = {'a';'b';'a';'b'}; %! F2 = {'x';'x';'y';'y'}; %! d = table (y, F1, F2); %! [M, ~, ~] = parseWilkinsonFormula ('~ F1 * F2', 'model_matrix', d); %! assert_equal (size (M, 2), 4); %! assert_equal (rank (M), 4); %!test %! ## Test : Numeric * Categorical Naming %! ## The terms follow the data's variable order, and the omitted reference %! ## level of a character grouping column is the one the data shows first. %! y = [1;2]; %! N = [10; 20]; %! C = {'lo'; 'hi'}; %! d = table (y, N, C); %! [M, ~, names] = parseWilkinsonFormula ('~ N * C', 'model_matrix', d); %! assert_equal (names(:)', {'(Intercept)', 'N', 'C_hi', 'N:C_hi'}); %!test %! ## Test : the variable order of the data drives the term order %! y = [1;2]; %! N = [10; 20]; %! C = {'lo'; 'hi'}; %! d = table (y, C, N); %! [M, ~, names] = parseWilkinsonFormula ('~ N * C', 'model_matrix', d); %! assert_equal (names(:)', {'(Intercept)', 'C_hi', 'N', 'C_hi:N'}); %!test %! ## Test : Intercept Only Model %! y = [1; 2; 3]; %! d = table (y); %! [X, ~, names] = parseWilkinsonFormula ('y ~ 1', 'model_matrix', d); %! assert_equal (size (X, 2), 1); %! assert_equal (names, {'(Intercept)'}); %! assert_equal (all (X == 1), true); %!test %! ## Test : NaNs and Missing Data %! y = [1; 2; 3; 4]; %! A = [1; 1; NaN; 1]; %! B = [10; 20; 30; NaN]; %! d = table (y, A, B); %! [X, y_out, ~] = parseWilkinsonFormula ('y ~ A', 'model_matrix', d); %! assert_equal (length (y_out), 3); %! assert_equal (y_out(3), 4); %! assert_equal (size (X, 1), 3); %!test %! ## Test : Nesting with Groups %! t = parseWilkinsonFormula ('A / (B + C)', 'expand'); %! terms = cellfun (@(x) strjoin (sort (x), ':'), t, 'UniformOutput', false); %! expected = sort ({'A', 'A:B', 'A:C'}); %! assert_equal (sort (terms), expected); %! ## Test : Variable Name Collision %! Var = [1; 1]; %! Var_1 = [2; 2]; %! d = table (Var, Var_1); %! [~, ~, names] = parseWilkinsonFormula ('~ Var + Var_1', 'model_matrix', d); %! assert_equal (any (strcmp (names, 'Var')), true); %! assert_equal (any (strcmp (names, 'Var_1')), true); %!test %! ## Test : One-argument call %! result = parseWilkinsonFormula ('A * B'); %! expected = sort ({'A', 'B', 'A:B'}); %! actual = cellfun (@(x) strjoin (sort (x), ':'), result, 'UniformOutput', false); %! assert_equal (sort (actual), expected); %!test %! ## Test : Compatibility with Table Data %! Age = [25; 30; 35; 40; 45]; %! Weight = [70; 75; 80; 85; 90]; %! BP = [120; 122; 128; 130; 135]; %! T = table (Age, Weight, BP); %! formula = 'BP ~ Age * Weight'; %! [X, y, names] = parseWilkinsonFormula (formula, 'model_matrix', T); %! assert_equal (size (X), [5, 4]); %! assert_equal (y, BP); %! assert_equal (any (strcmp ('Age', names)), true); %! assert_equal (any (strcmp ('Weight', names)), true); %! assert_equal (names{1}, '(Intercept)'); %!test %! ## Test : Multi-variable List %! y1 = [1; 2; 3]; y2 = [4; 5; 6]; x = [1; 0; 1]; %! d = table (y1, y2, x); %! [X, y, ~] = parseWilkinsonFormula ('y1, y2 ~ x', 'model_matrix', d); %! assert_equal (size (y), [3, 2]); %! assert_equal (y(:,1), d.y1); %! assert_equal (y(:,2), d.y2); %!test %!test %! ## Test : multivariable range. %! A = [10;20]; B = [30;40]; C = [50;60]; x = [1;2]; %! d = table (A, B, C, x); %! [X, y, ~] = parseWilkinsonFormula ('A - C ~ x', 'model_matrix', d); %! assert_equal (size (y), [2, 3]); %! assert_equal (y(:,1), d.A); %! assert_equal (y(:,2), d.B); %! assert_equal (y(:,3), d.C); %!test %! ## Test : multivariable list + range. %! y1 = [1]; y2 = [2]; y3 = [3]; y4 = [4]; y5 = [5]; %! x1 = [10]; x2 = [2]; %! d = table (y1, y2, y3, y4, y5, x1, x2); %! [X, y, names] = parseWilkinsonFormula ('y1, y3 - y5 ~ x1:x2', 'model_matrix', d); %! expected_y = [d.y1, d.y3, d.y4, d.y5]; %! assert_equal (isequal (y, expected_y), true); %! assert_equal (size (X, 2), 2); %! assert_equal (any (strcmp (names, 'x1:x2')), true); %!test %! ## Test : reverse range. %! A = [1]; B = [2]; C = [3]; x = [10]; %! d = table (A, B, C, x); %! [X, y, names] = parseWilkinsonFormula ('C - A ~ x - 1', 'model_matrix', d); %! assert_equal (size (y), [1, 3]); %! assert_equal (y(:,1), d.A); %! assert_equal (y(:,3), d.C); %! assert_equal (size (X, 2), 1); %! assert_equal (! any (strcmp (names, '(Intercept)')), true); %!test %! ## Test : nans in multi-y. %! yA = {1; 2; 3; 4}; %! yB = [10; 20; NaN; 40]; %! x = [1; 1; 1; 1]; %! d = table (yA, yB, x); %! [X, y, ~] = parseWilkinsonFormula ('yA, yB ~ x', 'model_matrix', d); %! assert_equal (size (y), [3, 2]); %! assert_equal (y(3, 1), 4); %! assert_equal (y(3, 2), 40); %! assert_equal (size (X, 1), 3); %!test %! ## Test : basic. %! eq = parseWilkinsonFormula ('y ~ x1 + x2 - 9', 'equation'); %! expected = string ('y = c1 + c2*x1 + c3*x2'); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : explicit intercept. %! eq = parseWilkinsonFormula ('y ~ x1 + x2', 'equation'); %! expected = string ('y = c1 + c2*x1 + c3*x2'); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : interaction. %! eq = parseWilkinsonFormula ('y ~ x1:x2:x3:x4', 'equation'); %! expected = string ('y = c1 + c2*x1*x2*x3*x4'); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : crossing/factorial. %! eq = parseWilkinsonFormula ('y ~ A * B', 'equation'); %! expected = string ('y = c1 + c2*A + c3*B + c4*A*B'); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : polynomials. %! eq = parseWilkinsonFormula ('y ~ x^4 - x^2', 'equation'); %! expected = string ('y = c1 + c2*x^3 + c3*x^4'); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : repeated measures %! eq = parseWilkinsonFormula ('y1-y3 ~ x', 'equation'); %! expected = string (['y1 = c1 + c2*x'; ... %! 'y2 = c3 + c4*x'; ... %! 'y3 = c5 + c6*x']); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : nesting syntax. %! eq = parseWilkinsonFormula ('y ~ x2(x1)', 'equation'); %! expected = string ('y = c1 + c2*x2(x1)'); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : nesting with interaction. %! eq = parseWilkinsonFormula ('y ~ x3:x2(x1)', 'equation'); %! expected = string ('y = c1 + c2*x2(x1)*x3'); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : multiple nesting. %! eq = parseWilkinsonFormula ('y ~ Var(A, B)', 'equation'); %! expected = string ('y = c1 + c2*Var(A,B)'); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : nested factors %! eq = parseWilkinsonFormula ('y ~ x2(x1) + x3(x4)', 'equation'); %! expected = string ('y = c1 + c2*x2(x1) + c3*x3(x4)'); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : polynomial and nesting. %! eq = parseWilkinsonFormula ('y ~ x^2 + Effect(Group)', 'equation'); %! expected = string ('y = c1 + c2*x + c3*x^2 + c4*Effect(Group)'); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : symbolic resolution of LHS list %! eq = parseWilkinsonFormula ('A, B ~ x', 'equation'); %! expected = string (['A = c1 + c2*x'; 'B = c3 + c4*x']); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : intercept only. %! eq = parseWilkinsonFormula ('y ~ 1', 'equation'); %! expected = string ('y = c1'); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : empty model. %! eq = parseWilkinsonFormula ('y ~ A - A', 'equation'); %! expected = string ('y = c1'); %! assert_equal (isequal (eq, expected), true); %!test %! ## Test : term row sorting. %! eq = parseWilkinsonFormula ('Y ~ x1 * x2 * x3', 'matrix'); %! expected_terms = [0, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1; 0, 1, 1, 0; 0, 1, 0, 1; 0, 0, 1, 1; 0, 1, 1, 1]; %! assert_equal (eq.VariableNames, {'Y', 'x1', 'x2', 'x3'}); %! assert_equal (eq.Terms, expected_terms); %!test %! ## Test : polynomial term Weight^2 resolves from base column not as table variable name %! Weight = [2000; 2500; 3000; 3500; 4000]; %! MPG = [30; 28; 25; 22; 18]; %! d = table (Weight, MPG); %! [X, yout, names] = parseWilkinsonFormula ('MPG ~ Weight^2', 'model_matrix', d); %! assert_equal (size (X), [5, 3]); %! assert_equal (names{1}, '(Intercept)'); %! assert_equal (any (strcmp (names, 'Weight')), true); %! assert_equal (any (strcmp (names, 'Weight^2')), true); %! w2i = find (strcmp (names, 'Weight^2')); %! assert_equal (X(:, w2i), Weight .^ 2, 1e-10); %! assert_equal (yout, MPG); %!test %! ## Test : squared term sorts after a categorical term %! Weight = [2000;2500;3000;3500;4000;4500;2200;2700;3200;3700;4200;4700]; %! MPG = [30;28;25;22;18;16;29;26;23;20;17;15]; %! Year = {'70';'70';'70';'70';'76';'76';'76';'76';'82';'82';'82';'82'}; %! d = table (MPG, Weight, Year); %! [~, ~, n1] = parseWilkinsonFormula ('MPG ~ Year + Weight^2', 'model_matrix', d); %! [~, ~, n2] = parseWilkinsonFormula ('MPG ~ Weight^2 + Year', 'model_matrix', d); %! assert_equal (n1, {'(Intercept)'; 'Weight'; 'Year_76'; 'Year_82'; 'Weight^2'}); %! assert_equal (n2, n1); %!error parseWilkinsonFormula () %!error parseWilkinsonFormula ('y ~ x', 'invalid_mode') %!error parseWilkinsonFormula ('', 'parse') %!error parseWilkinsonFormula ('A +', 'parse') %!error parseWilkinsonFormula ('A *', 'parse') %!error parseWilkinsonFormula ('A .', 'parse') %!error parseWilkinsonFormula ('A /', 'parse') %!error parseWilkinsonFormula ('(A+B)^C', 'expand') %!error parseWilkinsonFormula ('(A + B', 'parse') %!error parseWilkinsonFormula ('A + B)', 'parse') %!error parseWilkinsonFormula ('( )', 'parse') %!error parseWilkinsonFormula ('A + * B', 'parse') %!error parseWilkinsonFormula ('y ~ x ~ z', 'parse') %!error <'model_matrix' mode requires a Data Table> parseWilkinsonFormula ('~ A', 'model_matrix') %!error d=table ([1], 'VariableNames', {'x'}); parseWilkinsonFormula ('~ Z', 'model_matrix', d) %!error d=table ([1], [1], 'VariableNames', {'x', 'y'}); parseWilkinsonFormula ('Z ~ x', 'model_matrix', d) %!error d=table ([1], [1], 'VariableNames', {'x', 'y'}); parseWilkinsonFormula ('A - y ~ x', 'model_matrix', d) %!error d=table ([1], [1], 'VariableNames', {'x', 'y'}); parseWilkinsonFormula ('y - B ~ x', 'model_matrix', d) %!error d=table ([1], 'VariableNames', {'y'}); parseWilkinsonFormula ('y - y - y ~ x', 'model_matrix', d) %!error S={'a';'b'}; x=[1;2]; d=table (S, x); parseWilkinsonFormula ('S ~ x', 'model_matrix', d) %!error parseWilkinsonFormula ('y ~ x', 'model_matrix', [1,2,3]) %!error parseWilkinsonFormula ('y1-yA ~ x', 'equation') %!error parseWilkinsonFormula ('yA-y1 ~ x', 'equation') %!error parseWilkinsonFormula ('A-B ~ x', 'equation') %!error parseWilkinsonFormula ('y1- ~ x', 'equation') %!error parseWilkinsonFormula () ## Mixed-effects 'mixed' mode %!test # random intercept: basic decomposition %! S = parseWilkinsonFormula ('y ~ x + z + (1|g)', 'mixed'); %! assert_equal (S.HasRandom, true); %! assert_equal (S.FixedIntercept, true); %! assert_equal (numel (S.FixedTerms), 2); %! assert_equal (numel (S.Random), 1); %! assert_equal (S.Random(1).Intercept, true); %! assert_equal (isempty (S.Random(1).Terms), true); %! assert_equal (S.Random(1).GroupVars, {'g'}); %! assert_equal (S.Random(1).Group, 'g'); %! assert_equal (S.Response, 'y'); %!test # (x|g) carries an implicit intercept, like the fixed-effects rule %! S = parseWilkinsonFormula ('y ~ x + (x|g)', 'mixed'); %! assert_equal (S.Random(1).Intercept, true); %! assert_equal (numel (S.Random(1).Terms), 1); %! assert_equal (S.Random(1).Terms{1}, {'x'}); %!test # explicit (1 + x|g) matches the implicit form %! S = parseWilkinsonFormula ('y ~ x + (1 + x|g)', 'mixed'); %! assert_equal (S.Random(1).Intercept, true); %! assert_equal (S.Random(1).Terms, {{'x'}}); %!test # all four intercept-suppression spellings agree: slope only, no int %! for f = {'(x-1|g)', '(-1 + x|g)', '(0 + x|g)', '(x + 0|g)'} %! S = parseWilkinsonFormula (['y ~ x + ' f{1}], 'mixed'); %! assert_equal (! S.Random(1).Intercept, true); %! assert_equal (S.Random(1).Terms, {{'x'}}); %! endfor %!test # two random blocks on the same grouping factor %! S = parseWilkinsonFormula ('y ~ x + (1|g) + (x-1|g)', 'mixed'); %! assert_equal (numel (S.Random), 2); %! assert_equal (S.Random(1).Intercept && isempty (S.Random(1).Terms), true); %! assert_equal (! S.Random(2).Intercept, true); %! assert_equal (S.Random(2).Terms, {{'x'}}); %! assert_equal (S.Random(1).GroupVars, {'g'}); %! assert_equal (S.Random(2).GroupVars, {'g'}); %!test # crossed grouping factors %! S = parseWilkinsonFormula ('y ~ x + (1|g) + (1|g2)', 'mixed'); %! assert_equal (numel (S.Random), 2); %! assert_equal (S.Random(1).GroupVars, {'g'}); %! assert_equal (S.Random(2).GroupVars, {'g2'}); %!test # interaction (nested) grouping g:g2 %! S = parseWilkinsonFormula ('y ~ x + (1|g:g2)', 'mixed'); %! assert_equal (S.Random(1).GroupVars, {'g', 'g2'}); %! assert_equal (S.Random(1).Group, 'g:g2'); %!test # triple interaction grouping %! S = parseWilkinsonFormula ('y ~ (1|a:b:c)', 'mixed'); %! assert_equal (S.Random(1).GroupVars, {'a', 'b', 'c'}); %!test # multi-term random slopes (1 + x + z|g) %! S = parseWilkinsonFormula ('y ~ x + (1 + x + z|g)', 'mixed'); %! assert_equal (S.Random(1).Intercept, true); %! assert_equal (numel (S.Random(1).Terms), 2); %!test # interaction in the random design (1 + x:z|g) %! S = parseWilkinsonFormula ('y ~ x + (1 + x:z|g)', 'mixed'); %! assert_equal (numel (S.Random(1).Terms), 1); %! assert_equal (sort (S.Random(1).Terms{1}), {'x', 'z'}); %!test # crossing '*' expands in the random design to x + z + x:z %! S = parseWilkinsonFormula ('y ~ x + (x*z|g)', 'mixed'); %! assert_equal (S.Random(1).Intercept, true); %! assert_equal (numel (S.Random(1).Terms), 3); %!test # intercept-only fixed part: y ~ (1|g) %! S = parseWilkinsonFormula ('y ~ (1|g)', 'mixed'); %! assert_equal (S.FixedIntercept, true); %! assert_equal (isempty (S.FixedTerms), true); %! assert_equal (numel (S.Random), 1); %!test # suppressed fixed intercept coexists with a random term %! S = parseWilkinsonFormula ('y ~ x - 1 + (1|g)', 'mixed'); %! assert_equal (! S.FixedIntercept, true); %! assert_equal (numel (S.FixedTerms), 1); %!test # fixed interaction via '*' expands independently of the random part %! S = parseWilkinsonFormula ('y ~ x*z + (1|g)', 'mixed'); %! assert_equal (numel (S.FixedTerms), 3); %! assert_equal (numel (S.Random), 1); %!test # precedence parentheses in the fixed part are NOT random terms %! S = parseWilkinsonFormula ('y ~ (x + z) + (1|g)', 'mixed'); %! assert_equal (numel (S.FixedTerms), 2); %! assert_equal (numel (S.Random), 1); %!test # nesting in the fixed part coexists with a random term %! S = parseWilkinsonFormula ('y ~ a/b + (1|g)', 'mixed'); %! assert_equal (S.FixedIntercept, true); %! assert_equal (numel (S.Random), 1); %!test # 'mixed' mode degrades gracefully on a fixed-only formula %! S = parseWilkinsonFormula ('y ~ x + z', 'mixed'); %! assert_equal (! S.HasRandom, true); %! assert_equal (isempty (S.Random), true); %! assert_equal (numel (S.FixedTerms), 2); %!test # FixedFormula reconstructs the fixed-only formula string %! S = parseWilkinsonFormula ('y ~ x + x2 + (1 + x|g)', 'mixed'); %! assert_equal (strtrim (S.FixedFormula), 'y ~ x + x2'); %! S2 = parseWilkinsonFormula ('y ~ (1|g)', 'mixed'); %! assert_equal (strtrim (S2.FixedFormula), 'y ~ 1'); %!test # one-sided (no response) formula %! S = parseWilkinsonFormula ('~ x + (1|g)', 'mixed'); %! assert_equal (S.Response, ''); %! assert_equal (numel (S.Random), 1); %!test # random design widths q reproduce the MATLAB-verified reference forms %! chk = { 'y ~ x + x2 + (1|g)', [1]; ... %! 'y ~ x + x2 + (1 + x|g)', [2]; ... %! 'y ~ x + x2 + (1|g) + (x-1|g)', [1 1]; ... %! 'y ~ x + x2 + (x-1|g)', [1]; ... %! 'y ~ x + x2 + (-1 + x|g)', [1]; ... %! 'y ~ x + x2 + (1|g) + (1|g2)', [1 1]; ... %! 'y ~ x + x2 + (1|g:g2)', [1]; ... %! 'y ~ x + x2 + (1 + x + x2|g)', [3] }; %! for k = 1:rows (chk) %! S = parseWilkinsonFormula (chk{k,1}, 'mixed'); %! q = arrayfun (@(r) r.Intercept + numel (r.Terms), S.Random); %! assert_equal (q, chk{k,2}); %! endfor ## Mixed-effects error handling %!error parseWilkinsonFormula ('y ~ x + (1|g)', 'expand') %!error parseWilkinsonFormula ('y ~ (1|g)') %!error parseWilkinsonFormula ('y ~ x + (1|g)', 'model_matrix', table ()) %!error parseWilkinsonFormula ('y ~ x + (1|g', 'mixed') %!error parseWilkinsonFormula ('y ~ (1|)', 'mixed') %!error parseWilkinsonFormula ('y ~ (|g)', 'mixed') statistics-release-1.9.2/inst/Experimental_Design/sigma_pts.m000066400000000000000000000100271524624707500244160ustar00rootroot00000000000000## Copyright (C) 2017 - Juan Pablo Carbajal ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program. If not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{pts} =} sigma_pts (@var{n}) ## @deftypefnx {statistics} {@var{pts} =} sigma_pts (@var{n}, @var{m}) ## @deftypefnx {statistics} {@var{pts} =} sigma_pts (@var{n}, @var{m}, @var{K}) ## @deftypefnx {statistics} {@var{pts} =} sigma_pts (@var{n}, @var{m}, @var{K}, @var{l}) ## ## Calculates 2*@var{n}+1 sigma points in @var{n} dimensions. ## ## Sigma points are used in the unscented transform to estimate the result of ## applying a given nonlinear transformation to a probability distribution that ## is characterized only in terms of a finite set of statistics. ## ## If only the dimension @var{n} is given the resulting points have zero mean ## and identity covariance matrix. If the mean @var{m} or the covariance matrix ## @var{K} are given, then the resulting points will have those statistics. The ## factor @var{l} scales the points away from the mean. It is useful to tune the ## accuracy of the unscented transform. ## ## There is no unique way of computing sigma points, this function implements ## the algorithm described in section 2.6 "The New Filter" pages 40-41 of ## ## Uhlmann, Jeffrey (1995). "Dynamic Map Building and Localization: New ## Theoretical Foundations". Ph.D. thesis. University of Oxford. ## ## @end deftypefn function pts = sigma_pts (n, m = [], K = [], l = 0) if isempty (K) K = eye (n); endif if isempty (m) m = zeros (1, n); endif if (n != length (m)) error ("Dimension and size of mean vector don't match.") endif if any (n != size (K)) error ("Dimension and size of covariance matrix don't match.") endif if isdefinite (K) <= 0 error ("Covariance matrix should be positive definite.") endif pts = zeros (2 * n + 1, n); pts(1,:) = m; K = sqrtm ((n + l) * K); pts(2:n+1,:) = bsxfun (@plus, m , K); pts(n+2:end,:) = bsxfun (@minus, m , K); endfunction %!demo %! K = [1 0.5; 0.5 1]; # covariance matrix %! # calculate and build associated ellipse %! [R,S,~] = svd (K); %! theta = atan2 (R(2,1), R(1,1)); %! v = sqrt (diag (S)); %! v = v .* [cos(theta) sin(theta); -sin(theta) cos(theta)]; %! t = linspace (0, 2*pi, 100).'; %! xe = v(1,1) * cos (t) + v(2,1) * sin (t); %! ye = v(1,2) * cos (t) + v(2,2) * sin (t); %! %! figure (1); clf; hold on %! # Plot ellipse and axes %! line ([0 0; v(:,1).'],[0 0; v(:,2).']) %! plot (xe,ye,'-r'); %! %! col = 'rgb'; %! l = [-1.8 -1 1.5]; %! for li = 1:3 %! p = sigma_pts (2, [], K, l(li)); %! tmp = plot (p(2:end,1), p(2:end,2), ['x' col(li)], ... %! p(1,1), p(1,2), ['o' col(li)]); %! h(li) = tmp(1); %! endfor %! hold off %! axis image %! legend (h, arrayfun (@(x) sprintf ("l:%.2g", x), l, 'unif', 0)); %!test %! p = sigma_pts (5); %! assert_equal (mean (p), zeros (1,5), sqrt (eps)); %! assert_equal (cov (p), eye (5), sqrt (eps)); %!test %! m = randn (1, 5); %! p = sigma_pts (5, m); %! assert_equal (mean (p), m, sqrt (eps)); %! assert_equal (cov (p), eye (5), sqrt (eps)); %!test %! x = linspace (0,1,5); %! K = exp (- (x.' - x).^2/ 0.5); %! p = sigma_pts (5, [], K); %! assert_equal (mean (p), zeros (1,5), sqrt (eps)); %! assert_equal (cov (p), K, sqrt (eps)); %!error sigma_pts (2,1); %!error sigma_pts (2,[],1); %!error sigma_pts (2,1,1); %!error sigma_pts (2,[0.5 0.5],[-1 0; 0 0]); statistics-release-1.9.2/inst/Experimental_Design/x2fx.m000066400000000000000000000245041524624707500233240ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{d}, @var{model}, @var{termstart}, @var{termend}] =} x2fx (@var{x}) ## @deftypefnx {statistics} {[@var{d}, @var{model}, @var{termstart}, @var{termend}] =} x2fx (@var{x}, @var{model}) ## @deftypefnx {statistics} {[@var{d}, @var{model}, @var{termstart}, @var{termend}] =} x2fx (@var{x}, @var{model}, @var{categ}) ## @deftypefnx {statistics} {[@var{d}, @var{model}, @var{termstart}, @var{termend}] =} x2fx (@var{x}, @var{model}, @var{categ}, @var{catlevels}) ## ## Convert predictors to design matrix. ## ## @code{@var{d} = x2fx (@var{x}, @var{model})} converts a matrix of predictors ## @var{x} to a design matrix @var{d} for regression analysis. Distinct ## predictor variables should appear in different columns of @var{x}. ## ## The optional input @var{model} controls the regression model. By default, ## @code{x2fx} returns the design matrix for a linear additive model with a ## constant term. @var{model} can be any one of the following strings: ## ## @multitable @columnfractions 0.2 0.75 ## @item "linear" @tab Constant and linear terms (the default) ## @item "interaction" @tab Constant, linear, and interaction terms ## @item "quadratic" @tab Constant, linear, interaction, and squared terms ## @item "purequadratic" @tab Constant, linear, and squared terms ## @end multitable ## ## If @var{x} has n columns, the order of the columns of @var{d} for a full ## quadratic model is: ## ## @itemize ## @item ## The constant term. ## @item ## The linear terms (the columns of X, in order 1,2,...,n). ## @item ## The interaction terms (pairwise products of columns of @var{x}, in order ## (1,2), (1,3), ..., (1,n), (2,3), ..., (n-1,n). ## @item ## The squared terms (in the order 1,2,...,n). ## @end itemize ## ## Other models use a subset of these terms, in the same order. ## ## Alternatively, MODEL can be a matrix specifying polynomial terms of arbitrary ## order. In this case, MODEL should have one column for each column in X and ## one r for each term in the model. The entries in any r of MODEL are powers ## for the corresponding columns of @var{x}. For example, if @var{x} has ## columns X1, X2, and X3, then a row [0 1 2] in @var{model} would specify the ## term (X1.^0).*(X2.^1).*(X3.^2). A row of all zeros in @var{model} specifies ## a constant term, which you can omit. ## ## @code{@var{d} = x2fx (@var{x}, @var{model}, @var{categ})} treats columns with ## numbers listed in the vector @var{categ} as categorical variables. Terms ## involving categorical variables produce dummy variable columns in @var{d}. ## Dummy variables are computed under the assumption that possible categorical ## levels are completely enumerated by the unique values that appear in the ## corresponding column of @var{x}. ## ## @code{@var{d} = x2fx (@var{x}, @var{model}, @var{categ}, @var{catlevels})} ## accepts a vector @var{catlevels} the same length as @var{categ}, specifying ## the number of levels in each categorical variable. In this case, values in ## the corresponding column of @var{x} must be integers in the range from 1 to ## the specified number of levels. Not all of the levels need to appear in ## @var{x}. ## ## @end deftypefn function [D, model, termstart, termend] = x2fx (x, model, categ, catlevels) ## Get matrix size [m, n] = size (x); ## Get data class if (isa (x, 'single')) data_class = 'single'; else data_class = 'double'; endif ## Check for input arguments if (nargin < 2 || isempty (model)) model = 'linear'; endif if (nargin < 3) categ = []; else if (! all (ismember (categ, 1:n))) error ("x2fx: category index exceeds number of columns in X."); endif endif if (nargin < 4) catlevels = []; endif ## Convert models parsed as strings to numerical matrix if (ischar (model)) if (strcmpi (model, 'linear') || strcmpi (model, 'additive')) interactions = false; quadratic = false; elseif (strcmpi (model, 'interaction')) interactions = true; quadratic = false; elseif (strcmpi (model, 'quadratic')) interactions = true; quadratic = true; elseif (strcmpi (model, 'purequadratic')) interactions = false; quadratic = true; else try D = feval (model, x); catch error ("x2fx: unrecognized function '%s'.", model); end_try_catch termstart = []; termend = []; return endif I = eye (n); ## Construct interactions part if (interactions && n > 1) [r, c] = find (tril (ones (n) ,-1)); nt = length (r); intpart = zeros (nt,n); intpart(sub2ind (size (intpart),(1:nt)', r)) = 1; intpart(sub2ind (size (intpart),(1:nt)', c)) = 1; else intpart = zeros (0,n); endif ## Construct quadratic part if (quadratic) quadpart = 2 * I; quadpart(categ,:) = []; else quadpart = zeros (0,n); endif model = [zeros(1,n); I]; model = [model; intpart; quadpart]; endif ## Process each categorical variable catmember = ismember (1:n, categ); var_DF = ones (1,n); if (isempty (catlevels)) ## Get values of each categorical variable and replace them with integers for idx=1:length (categ) categ_idx = categ(idx); [Y, I, J] = unique (x(:,categ_idx)); var_DF(categ_idx) = length (Y) - 1; x(:,categ_idx) = J; endfor else ## Ensure all categorical variables take valid values var_DF(categ) = catlevels - 1; for idx = 1:length (categ) categ_idx = categ(idx); if (any (! ismember (x(:,categ_idx), 1:catlevels(idx)))) error ("x2fx: wrong value %f in category %d.", ... catlevels(idx), categ_idx); endif endfor endif ## Get size of model matrix [r, c] = size (model); ## Check for equal number of columns between x and model if (c != n) error ("x2fx: wrong number of columns between X and MODEL."); endif ## Allocate space for the dummy variables for all terms termdf = prod (max (1, (model > 0) .* repmat (var_DF, r, 1)), 2); termend = cumsum (termdf); termstart = termend - termdf + 1; D = zeros (m, termend(end), data_class); allrows = (1:m)'; for idx = 1:r cols = termstart(idx):termend(idx); pwrs = model(idx,:); t = pwrs > 0; C = 1; if (any (t)) if (any (pwrs(! catmember))) pwrs_cat = pwrs .* (! catmember); C = ones (size (x, 1), 1); collist = find (pwrs_cat > 0); for j = 1:length (collist) categ_idx = collist(j); C = C .* x(:,categ_idx) .^ pwrs_cat(categ_idx); endfor endif if (any (pwrs(catmember) > 0)) Z = zeros (m, termdf(idx)); collist = find (pwrs > 0 & catmember); xcol = x(:,collist(1)); keep = (xcol <= var_DF(collist(1))); colnum = xcol; cumdf = 1; for j = 2:length (collist) cumdf = cumdf * var_DF(collist(j-1)); xcol = x(:,collist(j)); keep = keep & (xcol <= var_DF(collist(j))); colnum = colnum + cumdf * (xcol - 1); endfor if (length (C) > 1) C = C(keep); endif Z(sub2ind (size (Z),allrows(keep),colnum(keep))) = C; C = Z; endif endif D(:,cols) = C; endfor endfunction %!test %! X = [1, 10; 2, 20; 3, 10; 4, 20; 5, 15; 6, 15]; %! D = x2fx (X,'quadratic'); %! assert_equal (D(1,:), [1, 1, 10, 10, 1, 100]); %! assert_equal (D(2,:), [1, 2, 20, 40, 4, 400]); %!test %! X = [1, 10; 2, 20; 3, 10; 4, 20; 5, 15; 6, 15]; %! model = [0, 0; 1, 0; 0, 1; 1, 1; 2, 0]; %! D = x2fx (X,model); %! assert_equal (D(1,:), [1, 1, 10, 10, 1]); %! assert_equal (D(2,:), [1, 2, 20, 40, 4]); %! assert_equal (D(4,:), [1, 4, 20, 80, 16]); %!test %! x = [1, 2, 3; 2, 3, 4; 3, 4, 5]; %! D = x2fx (x, 'linear'); %! assert_equal (D, [1, 1, 2, 3; 1, 2, 3, 4;, 1, 3, 4, 5]); %! D = x2fx (x, 'interaction'); %! assert_equal (D(1,:), [1, 1, 2, 3, 2, 3, 6]); %! assert_equal (D(2,:), [1, 2, 3, 4, 6, 8, 12]); %! assert_equal (D(3,:), [1, 3, 4, 5, 12, 15, 20]); %! D = x2fx (x, 'quadratic'); %! assert_equal (D(1,:), [1, 1, 2, 3, 2, 3, 6, 1, 4, 9]); %! assert_equal (D(2,:), [1, 2, 3, 4, 6, 8, 12, 4, 9, 16]); %! assert_equal (D(3,:), [1, 3, 4, 5, 12, 15, 20, 9, 16, 25]); %! D = x2fx (x, 'purequadratic'); %! assert_equal (D(1,:), [1, 1, 2, 3, 1, 4, 9]); %! assert_equal (D(2,:), [1, 2, 3, 4, 4, 9, 16]); %! assert_equal (D(3,:), [1, 3, 4, 5, 9, 16, 25]); %!test %! x = [1, 2, 3; 2, 3, 4; 3, 4, 5]; %! D = x2fx (x, [0, 0, 1; 1, 0, 2]); %! assert_equal (D, [3, 9; 4, 32; 5, 75]); %!test %! x = [1, 2, 3; 2, 3, 4; 3, 4, 5]; %! D = x2fx (x, 'linear', [1, 3]); %! assert_equal (D, [1, 1, 0, 2, 1, 0; 1, 0, 1, 3, 0, 1; 1, 0, 0, 4, 0, 0]); %!test %! x = [1, 2, 3; 2, 3, 4; 3, 4, 5]; %! D = x2fx (x, 'quadratic', [1, 3]); %! assert_equal (D(1,:), [1, 1, 0, 2, 1, 0, 2, 0, 1, 0, 0, 0, 2, 0, 4]); %! assert_equal (D(2,:), [1, 0, 1, 3, 0, 1, 0, 3, 0, 0, 0, 1, 0, 3, 9]); %! assert_equal (D(3,:), [1, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16]); %!test %! x = [1, 2, 3; 2, 3, 4; 3, 4, 5]; %! D = x2fx (x, 'cos'); %! assert_equal (D(1,:), [0.5403, -0.4161, -0.9900], 1e-4); %! assert_equal (D(2,:), [-0.4161, -0.9900, -0.6536], 1e-4); %! assert_equal (D(3,:), [-0.9900, -0.6536, 0.2837], 1e-4); %!error ... %! x2fx ([1, 2, 3; 2, 3, 4], 'quadratic', [1, 4]) %!error ... %! D = x2fx ([1, 2, 3; 2, 3, 4; 3, 4, 5], 'cosine') %!error ... %! x2fx ([1, 10; 2, 20; 3, 10], [0; 1]); %!error ... %! x2fx ([1, 10, 15; 2, 20, 40; 3, 10, 25], [0, 0; 1, 0; 0, 1; 1, 1; 2, 0]); statistics-release-1.9.2/inst/Hypothesis_Testing/000077500000000000000000000000001524624707500221605ustar00rootroot00000000000000statistics-release-1.9.2/inst/Hypothesis_Testing/adtest.m000066400000000000000000001010601524624707500236200ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} adtest (@var{x}) ## @deftypefnx {statistics} {@var{h} =} adtest (@var{x}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}] =} adtest (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{adstat}, @var{cv}] =} adtest (@dots{}) ## ## Anderson-Darling goodness-of-fit hypothesis test. ## ## @code{@var{h} = adtest (@var{x})} returns a test decision for the null ## hypothesis that the data in vector @var{x} is from a population with a normal ## distribution, using the Anderson-Darling test. The alternative hypothesis is ## that x is not from a population with a normal distribution. The result ## @var{h} is 1 if the test rejects the null hypothesis at the 5% significance ## level, or 0 otherwise. ## ## @code{@var{h} = adtest (@var{x}, @var{Name}, @var{Value})} returns a test ## decision for the Anderson-Darling test with additional options specified by ## one or more Name-Value pair arguments. For example, you can specify a null ## distribution other than normal, or select an alternative method for ## calculating the p-value, such as a Monte Carlo simulation. ## ## The following parameters can be parsed as Name-Value pair arguments. ## ## @multitable @columnfractions 0.25 0.75 ## @headitem Name @tab Description ## @item "Distribution" @tab The distribution being tested for. It tests ## whether @var{x} could have come from the specified distribution. There are ## two choices available for parsing distribution parameters: ## @end multitable ## ## @itemize ## @item ## One of the following char strings: "norm", "exp", "ev", "logn", "weibull", ## for defining either the 'normal', 'exponential', 'extreme value', lognormal, ## or 'Weibull' distribution family, respectively. In this case, @var{x} is ## tested against a composite hypothesis for the specified distribution family ## and the required distribution parameters are estimated from the data in ## @var{x}. The default is "norm". ## ## @item ## A cell array defining a distribution in which the first cell contains a char ## string with the distribution name, as mentioned above, and the consecutive ## cells containing all specified parameters of the null distribution. In this ## case, @var{x} is tested against a simple hypothesis. ## @end itemize ## ## @multitable @columnfractions 0.25 0.75 ## @headitem @var{Name} @tab @var{Value} ## @item "Alpha" @tab Significance level alpha for the test. Any scalar ## numeric value between 0 and 1. The default is 0.05 corresponding to the 5% ## significance level. ## ## @item "MCTol" @tab Monte-Carlo standard error for the p-value, ## @var{pval}, value. which must be a positive scalar value. In this case, an ## approximation for the p-value is computed directly, using Monte-Carlo ## simulations. ## ## @item "Asymptotic" @tab Method for calculating the p-value of the ## Anderson-Darling test, which can be either true or false logical value. If ## you specify 'true', adtest estimates the p-value using the limiting ## distribution of the Anderson-Darling test statistic. If you specify 'false', ## adtest calculates the p-value based on an analytical formula. For sample ## sizes greater than 120, the limiting distribution estimate is likely to be ## more accurate than the small sample size approximation method. ## @end multitable ## ## @itemize ## @item ## If you specify a distribution family with unknown parameters for the ## distribution Name-Value pair (i.e. composite distribution hypothesis test), ## the "Asymptotic" option must be false. ## @item ## ## If you use MCTol to calculate the p-value using a Monte Carlo simulation, ## the "Asymptotic" option must be false. ## @end itemize ## ## @code{[@var{h}, @var{pval}] = adtest (@dots{})} also returns the p-value, ## @var{pval}, of the Anderson-Darling test, using any of the input arguments ## from the previous syntaxes. ## ## @code{[@var{h}, @var{pval}, @var{adstat}, @var{cv}] = adtest (@dots{})} also ## returns the test statistic, @var{adstat}, and the critical value, @var{cv}, ## for the Anderson-Darling test. ## ## The Anderson-Darling test statistic belongs to the family of Quadratic ## Empirical Distribution Function statistics, which are based on the weighted ## sum of the difference @math{[Fn(x)-F(x)]^2} over the ordered sample values ## @math{X1 < X2 < ... < Xn}, where @math{F} is the hypothesized continuous ## distribution and @math{Fn} is the empirical CDF based on the data sample with ## @math{n} sample points. ## ## @seealso{kstest} ## @end deftypefn function [H, pVal, ADStat, CV] = adtest (x, varargin) ## Check for valid input data if (nargin < 1) print_usage; endif ## Ensure the sample data is a real vector. if (! isvector (x) || ! isreal (x)) error ("adtest: X must be a vector of real numbers."); endif ## Add defaults distribution = 'norm'; isCompositeTest = true; alpha = 0.05; MCTol = []; asymptotic = false; ## Parse arguments and check if parameter/value pairs are valid i = 1; while (i <= length (varargin)) switch lower (varargin{i}) case 'distribution' i = i + 1; distribution = varargin{i}; ## Check for char string or cell array ## If distribution is a char string, then X is tested against a ## composite hypothesis for the specified distribution family and ## distribution parameters are estimated from the sample data. valid_dists = {'norm', 'exp', 'ev', 'logn', 'weibull'}; if (ischar (distribution)) if (! any (strcmpi (distribution, valid_dists))) error ("adtest: invalid distribution family in char string."); endif ## If distribution is a cell array, then X is tested against a ## simple hypothesis for the specified distribution and ## distribution parameters given in the cell array. elseif (iscell (distribution)) if (! any (strcmpi (distribution(1), valid_dists))) error ("adtest: invalid distribution family in cell array."); endif ## Check for valid distribution parameter in cell array err_msg = 'adtest: invalid distribution parameters in cell array.'; if (strcmpi (distribution(1), 'norm')) if (numel (distribution) != 3) error (err_msg); endif z = normcdf (x, distribution{2}, distribution{3}); elseif (strcmpi (distribution(1), 'exp')) if (numel (distribution) != 2) error (err_msg); endif z = expcdf (x, distribution{2}); elseif (strcmpi (distribution(1), 'ev')) if (numel (distribution) != 3) error (err_msg); endif z = evcdf (x, distribution{2}, distribution{3}); elseif (strcmpi (distribution(1), 'logn')) if (numel (distribution) != 3) error (err_msg); endif z = logncdf (x, distribution{2}, distribution{3}); elseif (strcmpi (distribution(1), 'weibull')) if (numel (distribution) != 3) error (err_msg); endif z = wblcdf (x, distribution{2}, distribution{3}); endif isCompositeTest = false; else error ("adtest: invalid distribution option."); endif case 'alpha' i = i + 1; alpha = varargin{i}; ## Check for valid alpha if (! isscalar (alpha) || ! isnumeric (alpha) || ... alpha <= 0 || alpha >= 1) error ("adtest: invalid value for alpha."); endif case 'mctol' i = i + 1; MCTol = varargin{i}; ## Check Monte Carlo tolerance is a numeric scalar greater than 0 if (! isempty (MCTol) && (! isscalar (MCTol) || MCTol <= 0)) error ("adtest: invalid Monte Carlo Tolerance."); endif case 'asymptotic' i = i + 1; asymptotic = varargin{i}; ## Check that it is either true or false if (! isbool (asymptotic)) error ("adtest: asymptotic option must be boolean."); endif otherwise error ("adtest: invalid Name argument."); endswitch i = i + 1; endwhile ## Check conflicts with asymptotic option if (asymptotic && isCompositeTest) error (strcat ("adtest: asymptotic option is not valid", ... " for the composite distribution test.")); elseif (asymptotic && ! isempty (MCTol)) error (strcat ("adtest: asymptotic option is not valid", ... " for the Monte Carlo simulation test.")); endif ## Remove missing values. x = x(! isnan (x)); ## Compute sample size n. n = length (x); ## For composite tests if (isCompositeTest) ## Abort for sample size less than 4 if (n < 4) error ("adtest: not enough data for composite testing."); endif ## If data follow a lognormal distribution, log(x) is normally distributed if (strcmpi (distribution, 'logn')) x = log (x); distribution = 'norm'; ## If data follow a Weibull distribution, log(x) has a type I extreme-value ## distribution elseif (strcmpi (distribution, 'weibull')) x = log (x); distribution = 'ev'; endif ## Compute ADStat switch distribution case 'norm' ## Check for complex numbers due to log (x) if (any (! isreal (x))) ## Data is not compatible with logn distribution test warning ("adtest: bad data for lognormal distribution."); ADStat = NaN; else z = normcdf (x, mean (x), std (x)); ADStat = ComputeADStat (z, n); endif case 'exp' z = expcdf (x, mean (x)); ADStat = ComputeADStat (z, n); case 'ev' ## Check for complex numbers due to log (x) if (any (! isreal (x))) ## Data is not compatible with Weibull distribution test warning ("adtest: bad data for Weibull distribution."); ADStat = NaN; else params = evfit (x); z = evcdf (x, params(1), params(2)); ADStat = ComputeADStat (z, n); endif endswitch ## Compute p-value and critical values without Monte Carlo simulation if (isempty (MCTol)) alphas = [0.0005, 0.0010, 0.0015, 0.0020, 0.0050, 0.0100, 0.0250, ... 0.0500, 0.1000, 0.1500, 0.2000, 0.2500, 0.3000, 0.3500, ... 0.4000, 0.4500, 0.5000, 0.5500, 0.6000, 0.6500, 0.7000, ... 0.7500, 0.8000, 0.8500, 0.9000, 0.9500, 0.9900]; switch distribution case 'norm' CVs = computeCriticalValues_norm (n); case 'exp' CVs = computeCriticalValues_exp (n); case 'ev' CVs = computeCriticalValues_ev (n); endswitch ## 1-D interpolation into the tabulated results pp = pchip (log (alphas), CVs); CV = ppval (pp, log (alpha)); ## If alpha is not within the lookup table, throw a warning ## Hypothesis result is computed by comparing the p-value with ## alpha, rather than CV with ADStat if alpha < alphas(1) CV = CVs(1); warning ("adtest: alpha not within the lookup table."); elseif alpha > alphas(end) CV = CVs(end); warning ("adtest: alpha not within the lookup table."); endif if (ADStat > CVs(1)) ## P value is smaller than smallest tabulated value warning (strcat ("adtest: out of range min p-value:", ... sprintf (" %g", alphas(1)))); pVal = alphas(1); elseif (ADStat < CVs(end)) ## P value is larger than largest tabulated value warning (strcat ("adtest: out of range max p-value:", ... sprintf (" %g", alphas(end)))); pVal = alphas(end); elseif (isnan (ADStat)) ## Handle certain cases of negative data (ADStat == NaN) pVal = 0; else ## Find p-value by inverse interpolation i = find (ADStat > CVs, 1, 'first'); logPVal = fzero (@(x)ppval (pp,x) - ADStat, log (alphas([i-1,i]))); pVal = exp (logPVal); endif ## Compute p-value and critical values without Monte Carlo simulation else [CV, pVal] = adtestMC (ADStat, n, alpha, distribution, mctol); endif ## Calculate H if (isnan (ADStat)) H = true; else if (isempty (MCTol)) if (alpha < alphas(1) || alpha > alphas(end)) H = (pVal < alpha); else H = (ADStat > CV); endif else H = (ADStat > CV); endif endif ## For simple tests else ## Compute the Anderson-Darling statistic ADStat = ComputeADStat (z, n); ## Compute p-value and critical values without Monte Carlo simulation if (isempty (MCTol)) alphas = [0.0005, 0.0010, 0.0015, 0.0020, 0.0050, 0.0100, 0.0250, ... 0.0500, 0.1000, 0.1500, 0.2000, 0.2500, 0.3000, 0.3500, ... 0.4000, 0.4500, 0.5000, 0.5500, 0.6000, 0.6500, 0.7000, ... 0.7500, 0.8000, 0.8500, 0.9000, 0.9500, 0.9900]; if (asymptotic) if (n <= 120) warning ("adtest: asymptotic distribution with small sample size."); endif pVal = 1 - ADInf (ADStat); ## For extra output arguments if (nargout > 3) ## Make sure alpha is within the lookup table validateAlpha (alpha, alphas); ## Find critical values critVals = findAsymptoticDistributionCriticalValues; i = find (alphas > alpha, 1, 'first'); startVal = critVals(i-1); CV = fzero (@(ad)1-ADInf (ad)-alpha, startVal); endif else if (n == 1) pVal = 1 - sqrt (1 - 4 * exp (-1 - ADStat)); else if (n < 4) warning ("adtest: small sample size."); endif pVal = 1 - ADn (n, ADStat); endif ## For extra output arguments if (nargout > 3) ## Make sure alpha is within the lookup table validateAlpha (alpha, alphas); ## Find critical values [CVs, sampleSizes] = findCriticalValues; [OneOverSampleSizes, LogAlphas] = meshgrid (1 ./ sampleSizes, ... log (alphas)); CV = interp2 (OneOverSampleSizes, LogAlphas, CVs', 1./n, log (alpha)); endif endif ## Compute p-value and critical values with Monte Carlo simulation else [CV, pVal] = adtestMC (ADStat, n, alpha, 'unif', mctol); endif ## Calculate H H = (pVal < alpha); endif endfunction ## Compute Anderson-Darling Statistic function ADStat = ComputeADStat (z, n) ## Sort the data and compute the statistic z = reshape (z, n, 1); z = sort (z); w = 2 * (1:n) - 1; ADStat = - w * (log (z)+ log (1-z(end:-1:1))) / n - n; endfunction ## Anderson-Darling distribution Pr(An= 2); x(ad >= 2) = exp (-exp (1.0776 - (2.30695 - (0.43424 - (0.082433 - ... (0.008056 - 0.0003146 .* adh) .* adh) .* adh) .* adh) .* adh)); ## Compute error function defined between 0 and 1 if (any (x < 0 | x > 1)) error ("adtest: invalid values for error function."); endif e = zeros (size (x)); c = 0.01265 + 0.1757/n; ## Define function by intervals using 3 fixed functions g1, g2, g3. xc1 = x(x < c) / c; g1 = sqrt (xc1) .* (1 - xc1) .* (49 * xc1 - 102); e(x < c) = (0.0037 / n ^ 3 + 0.00078 / n ^ 2 + 0.00006 / n) * g1; xc2 = (x(x >= c & x < 0.8) - c) ./ (0.8 - c); g2 = -0.00022633 + (6.54034 - (14.6538 - (14.458 - (8.259 -... 1.91864 .* xc2) .* xc2) .* xc2) .* xc2) .* xc2; e(x >= c & x < 0.8) = (0.04213 / n + 0.01365 / n ^ 2) * g2; xc3 = x(x >= 0.8); e(x >= 0.8) = 1 / n * (-130.2137 + (745.2337 - (1705.091 - (1950.646 -... (1116.360 - 255.7844 .* ... xc3) .* xc3) .* xc3) .* xc3) .* xc3) .* xc3; p = x + e; endfunction ## Evaluate the Anderson-Darling limit distribution function ad = ADInf (z) ## Distribution is invalid for negative values if (z < 0) error ("adtest: invalid X for asymptotic distribution."); endif ## Due to floating point precision: ## Return 0 below a certain threshold if (z < 0.02) ad = 0; return; ## Return 1 above the following threshold elseif (z >= 32.4) ad = 1; return; endif n = 1:500; K = 1/z*[1, ((4*n + 1).*cumprod((1/2 - n)./n))]; ADTerms = arrayfun (@(j)ADf (z,j),0:500); ad = ADTerms*K'; endfunction ## Series expansion for f(z,j) called by ADInf function f = ADf (z,j) ## Compute t=tj=(4j+1)^2*pi^2/(8z) t = (4 * j + 1) ^ 2 * 1.233700550136170 / z; ## First 2 terms in recursive series ## c0=pi*exp(-t)/(sqrt(2t)) ## c1=pi*sqrt(pi/2)*erfc(sqrt(t)) c0 = 2.221441469079183 * exp (-t) / sqrt (t); c1 = 3.937402486430604 * erfc (sqrt (t)); r = z / 8; f = c0 + c1 * r; ## Evaluate the recursion for n = 2:500 c = 1 / (n - 1) * ((n - 3 / 2 - t) * c1 + t * c0); r = r * (z / 8) * (1 / n); fn = f + c * r; c0 = c1; c1 = c; if (f == fn) return; endif f = fn; endfor endfunction ## An improved version of the Petitt method for the composite normal case. function CVs = computeCriticalValues_norm (n) CVs = [1.5649, 1.4407, 1.3699, 1.3187, 1.1556, 1.0339, 0.8733, ... 0.7519, 0.6308, 0.5598, 0.5092, 0.4694, 0.4366, 0.4084, ... 0.3835, 0.3611, 0.3405, 0.3212, 0.3029, 0.2852, 0.2679, ... 0.2506, 0.2330, 0.2144, 0.1935, 0.1673, 0.1296] + ... [-0.9362, -0.9029, -0.8906, -0.8865, -0.8375, -0.7835, -0.6746, ... -0.5835, -0.4775, -0.4094, -0.3679, -0.3327, -0.3099, -0.2969, ... -0.2795, -0.2623, -0.2464, -0.2325, -0.2164, -0.1994, -0.1784, ... -0.1569, -0.1377, -0.1201, -0.0989, -0.0800, -0.0598] ./ n + ... [-8.3249, -6.6022, -5.6461, -4.9685, -3.2208, -2.1647, -1.2460, ... -0.7803, -0.4627, -0.3672, -0.2833, -0.2349, -0.1442, -0.0229, ... 0.0377, 0.0817, 0.1150, 0.1583, 0.1801, 0.1887, 0.1695, ... 0.1513, 0.1533, 0.1724, 0.2027, 0.3158, 0.6431] ./ n ^ 2; endfunction ## An improved version of the Petitt method for the composite exponential case. function CVs = computeCriticalValues_exp (n) CVs = [3.2371, 2.9303, 2.7541, 2.6307, 2.2454, 1.9621, 1.5928, ... 1.3223, 1.0621, 0.9153, 0.8134, 0.7355, 0.6725, 0.6194, ... 0.5734, 0.5326, 0.4957, 0.4617, 0.4301, 0.4001, 0.3712, ... 0.3428, 0.3144, 0.2849, 0.2527, 0.2131, 0.1581] + ... [1.6146, 0.8716, 0.4715, 0.2066, -0.4682, -0.7691, -0.7388, ... -0.5758, -0.4036, -0.3142, -0.2564, -0.2152, -0.1845, -0.1607, ... -0.1409, -0.1239, -0.1084, -0.0942, -0.0807, -0.0674, -0.0537, ... -0.0401, -0.0261, -0.0116, 0.0047, 0.0275, 0.0780] ./ n; endfunction ## An improved version of the Petitt method for the composite extreme value case function CVs = computeCriticalValues_ev (n) CVs = [1.6473, 1.5095, 1.4301, 1.3742, 1.1974, 1.0667, 0.8961, ... 0.7683, 0.6416, 0.5680, 0.5156, 0.4744, 0.4405, 0.4115, ... 0.3858, 0.3626, 0.3415, 0.3217, 0.3029, 0.2848, 0.2672, ... 0.2496, 0.2315, 0.2124, 0.1909, 0.1633, 0.1223] + ... [-0.7097, -0.5934, -0.5328, -0.4930, -0.3708, -0.2973, -0.2075, ... -0.1449, -0.0892, -0.0619, -0.0442, -0.0302, -0.0196, -0.0112, ... -0.0039, 0.0024, 0.0074, 0.0122, 0.0167, 0.0207, 0.0245, ... 0.0282, 0.0323, 0.0371, 0.0436, 0.0549, 0.0813] ./ n .^ (1 / 2); endfunction ## Find rows of critical values at relevant significance levels function [CVs, sampleSizes] = findCriticalValues CVs = [7.2943, 6.6014, 6.1962, 5.9088, 4.9940, 4.3033, 3.3946, 2.7142, ... 2.0470, 1.6682, 1.4079, 1.2130, 1.0596, 0.9353, 0.8326, 0.7465, ... 0.6740, 0.6126, 0.5606, 0.5170, 0.4806, 0.4508, 0.4271, 0.4091, ... NaN, NaN, NaN; ... %n=1 7.6624, 6.4955, 5.9916, 5.6682, 4.7338, 4.0740, 3.2247, 2.5920, ... 1.9774, 1.6368, 1.4078, 1.2329, 1.0974, 0.9873, 0.8947, 0.8150, ... 0.7448, 0.6820, 0.6251, 0.5727, 0.5240, 0.4779, 0.4337, 0.3903, ... 0.3462, 0.3030, 0.2558; ... %n=2 7.2278, 6.3094, 5.8569, 5.5557, 4.6578, 4.0111, 3.1763, 2.5581, ... 1.9620, 1.6314, 1.4079, 1.2390, 1.1065, 0.9979, 0.9060, 0.8264, ... 0.7560, 0.6928, 0.6350, 0.5816, 0.5314, 0.4835, 0.4371, 0.3907, ... 0.3424, 0.2885, 0.2255; ... %n=3 7.0518, 6.2208, 5.7904, 5.4993, 4.6187, 3.9788, 3.1518, 2.5414, ... 1.9545, 1.6288, 1.4080, 1.2416, 1.1104, 1.0025, 0.9110, 0.8315, ... 0.7611, 0.6977, 0.6397, 0.5859, 0.5352, 0.4868, 0.4395, 0.3920, ... 0.3421, 0.2845, 0.2146; ... %n=4 6.9550, 6.1688, 5.7507, 5.4653, 4.5949, 3.9591, 3.1370, 2.5314, ... 1.9501, 1.6272, 1.4080, 1.2430, 1.1126, 1.0051, 0.9138, 0.8344, ... 0.7640, 0.7005, 0.6424, 0.5884, 0.5375, 0.4888, 0.4411, 0.3930, ... 0.3424, 0.2833, 0.2097; ... %n=5 6.8935, 6.1345, 5.7242, 5.4426, 4.5789, 3.9459, 3.1271, 2.5248, ... 1.9472, 1.6262, 1.4081, 1.2439, 1.1140, 1.0067, 0.9156, 0.8362, ... 0.7658, 0.7023, 0.6441, 0.5901, 0.5391, 0.4901, 0.4422, 0.3938, ... 0.3427, 0.2828, 0.2071; ... %n=6 6.8509, 6.1102, 5.7053, 5.4264, 4.5674, 3.9364, 3.1201, 2.5201, ... 1.9451, 1.6255, 1.4081, 1.2445, 1.1149, 1.0079, 0.9168, 0.8375, ... 0.7671, 0.7036, 0.6454, 0.5912, 0.5401, 0.4911, 0.4430, 0.3944, ... 0.3430, 0.2826, 0.2056; ... %n=7 6.8196, 6.0920, 5.6912, 5.4142, 4.5588, 3.9293, 3.1148, 2.5166, ... 1.9436, 1.6249, 1.4081, 1.2450, 1.1156, 1.0087, 0.9177, 0.8384, ... 0.7681, 0.7045, 0.6463, 0.5921, 0.5409, 0.4918, 0.4436, 0.3949, ... 0.3433, 0.2825, 0.2046; ... %n=8 6.7486, 6.0500, 5.6582, 5.3856, 4.5384, 3.9124, 3.1024, 2.5084, ... 1.9400, 1.6237, 1.4081, 1.2460, 1.1171, 1.0106, 0.9197, 0.8406, ... 0.7702, 0.7066, 0.6483, 0.5941, 0.5428, 0.4935, 0.4451, 0.3961, ... 0.3441, 0.2826, 0.2029; ... %n=12 6.7140, 6.0292, 5.6417, 5.3713, 4.5281, 3.9040, 3.0962, 2.5044, ... 1.9382, 1.6230, 1.4081, 1.2465, 1.1179, 1.0115, 0.9207, 0.8416, ... 0.7712, 0.7077, 0.6493, 0.5950, 0.5437, 0.4944, 0.4459, 0.3968, ... 0.3445, 0.2827, 0.2023; ... %n=16 6.6801, 6.0084, 5.6252, 5.3569, 4.5178, 3.8955, 3.0900, 2.5003, ... 1.9365, 1.6224, 1.4081, 1.2470, 1.1186, 1.0123, 0.9217, 0.8426, ... 0.7723, 0.7087, 0.6503, 0.5960, 0.5446, 0.4952, 0.4466, 0.3974, ... 0.3450, 0.2829, 0.2019; ... %n=24 6.6468, 5.9877, 5.6087, 5.3425, 4.5075, 3.8869, 3.0837, 2.4963, ... 1.9347, 1.6218, 1.4082, 1.2474, 1.1193, 1.0132, 0.9226, 0.8436, ... 0.7732, 0.7097, 0.6513, 0.5969, 0.5455, 0.4960, 0.4474, 0.3980, ... 0.3455, 0.2832, 0.2016; ... %n=48 6.6634, 5.9980, 5.6169, 5.3497, 4.5127, 3.8912, 3.0868, 2.4983, ... 1.9356, 1.6221, 1.4081, 1.2472, 1.1190, 1.0128, 0.9222, 0.8431, ... 0.7728, 0.7092, 0.6508, 0.5965, 0.5451, 0.4956, 0.4470, 0.3977, ... 0.3453, 0.2830, 0.2017; ... %n=32 6.6385, 5.9825, 5.6046, 5.3389, 4.5049, 3.8848, 3.0822, 2.4953, ... 1.9343, 1.6217, 1.4082, 1.2475, 1.1195, 1.0134, 0.9228, 0.8438, ... 0.7735, 0.7099, 0.6516, 0.5972, 0.5458, 0.4962, 0.4476, 0.3982, ... 0.3456, 0.2833, 0.2016; ... %n=64 6.6318, 5.9783, 5.6012, 5.3360, 4.5028, 3.8830, 3.0809, 2.4944, ... 1.9339, 1.6215, 1.4082, 1.2476, 1.1197, 1.0136, 0.9230, 0.8440, ... 0.7737, 0.7101, 0.6517, 0.5974, 0.5459, 0.4964, 0.4477, 0.3984, ... 0.3457, 0.2833, 0.2015; ... %n=88 6.6297, 5.9770, 5.6001, 5.3350, 4.5021, 3.8825, 3.0805, 2.4942, ... 1.9338, 1.6215, 1.4082, 1.2476, 1.1197, 1.0136, 0.9231, 0.8441, ... 0.7738, 0.7102, 0.6518, 0.5974, 0.5460, 0.4965, 0.4478, 0.3984, ... 0.3458, 0.2834, 0.2015; ... %n=100 6.6262, 5.9748, 5.5984, 5.3335, 4.5010, 3.8816, 3.0798, 2.4937, ... 1.9336, 1.6214, 1.4082, 1.2477, 1.1198, 1.0137, 0.9232, 0.8442, ... 0.7739, 0.7103, 0.6519, 0.5975, 0.5461, 0.4966, 0.4479, 0.3985, ... 0.3458, 0.2834, 0.2015; ... %n=128 6.6201, 5.9709, 5.5953, 5.3308, 4.4990, 3.8800, 3.0787, 2.4930, ... 1.9333, 1.6213, 1.4082, 1.2478, 1.1199, 1.0139, 0.9234, 0.8443, ... 0.7740, 0.7105, 0.6521, 0.5977, 0.5463, 0.4967, 0.4480, 0.3986, ... 0.3459, 0.2834, 0.2015; ... %n=256 6.6127, 5.9694, 5.5955, 5.3314, 4.4982, 3.8781, 3.0775, 2.4924, ... 1.9330, 1.6212, 1.4082, 1.2479, 1.1201, 1.0140, 0.9235, 0.8445, ... 0.7742, 0.7106, 0.6523, 0.5979, 0.5464, 0.4969, 0.4481, 0.3987, ... 0.3460, 0.2835, 0.2015]; %n=Inf sampleSizes = [1 2 3 4 5 6 7 8 12 16 24 32 48 64 88 100 128 256 Inf]; endfunction % ------------------------------------------ function critVals = findAsymptoticDistributionCriticalValues critVals = [6.6127034546551, 5.9694013422151, 5.5954643397078, ... 5.3313658857909, 4.4981996466091, 3.8781250216054, ... 3.0774641787107, 2.4923671600494, 1.9329578327416, ... 1.6212385363175, 1.4081977005506, 1.2478596347253, ... 1.1200136586965, 1.0140004020016, 0.9235137094902, ... 0.8445069178452, 0.7742142410993, 0.7106405935247, ... 0.6522701010084, 0.5978828157471, 0.5464229310982, ... 0.4968804113119, 0.4481425895777, 0.3987228486242, ... 0.3460480234939, 0.2835161264344, 0.2014922164166]; %n=Inf endfunction ## Make sure alpha is within the lookup table function validateAlpha (alpha, alphas) if (alpha < alphas(1) || alpha > alphas(end)) error (strcat ("adtest: out of range invalid alpha -", ... sprintf (" lower limit: %g", alphas(1)),... sprintf (" upper limit: %g", alphas(end)))); endif endfunction ## Simulated critical values and p-values for Anderson-Darling test function [crit, p] = adtestMC (ADStat, n, alpha, distribution, MCTol) ## Initial values vartol = mctol^2; crit = 0; p = 0; mcRepsTot = 0; mcRepsMin = 1000; ## Monte Carlo loop while true mcRepsOld = mcRepsTot; mcReps = ceil (mcRepsMin - mcRepsOld); ADstatMC = zeros (mcReps,1); ## Switch to selected distribution switch distribution case 'norm' mu0 = 0; sigma0 = 1; for rep = 1:length (ADstatMC) x = normrnd (mu0, sigma0, n, 1); xCDF = sort (x); nullCDF = normcdf (xCDF, mean (x), std (x)); w = 2 * (1:n) - 1 ; ADstatMC(rep) = - w * (log (nullCDF) + ... log (1 - nullCDF(end:-1:1))) / n - n; endfor case 'exp' beta0 = 1; for rep = 1:length (ADstatMC) x = exprnd (beta0, n, 1); xCDF = sort (x); nullCDF = expcdf (xCDF, mean (x)); w = 2 * (1:n) - 1 ; ADstatMC(rep) = - w * (log (nullCDF) + ... log (1 - nullCDF(end:-1:1))) / n - n; endfor case 'ev' mu0 = 0; sigma0 = 1; for rep = 1:length (ADstatMC) x = evrnd (mu0, sigma0, n, 1); pHat = evfit (x); xCDF = sort (x); nullCDF = evcdf (xCDF, pHat(1), pHat(2)); w = 2 * (1:n) - 1 ; ADstatMC(rep) = - w * (log (nullCDF) + ... log (1 - nullCDF(end:-1:1))) / n - n; endfor case 'unif' for rep = 1:length (ADstatMC) z = sort (rand (n, 1)); w = 2 * (1:n) - 1 ; ADstatMC(rep) = - w * (log (z) + ... log (1 - z(end:-1:1))) / n - n; endfor endswitch critMC = prctile (ADstatMC, 100 * (1 - alpha)); pMC = sum (ADstatMC > ADStat) ./ mcReps; mcRepsTot = mcRepsOld + mcReps; crit = (mcRepsOld * crit + mcReps * critMC) / mcRepsTot; p = (mcRepsOld * p + mcReps * pMC) / mcRepsTot; ## Compute a std err for p, with lower bound (1/N)*(1-1/N)/N when p==0. sepsq = max (p * (1 - p) / mcRepsTot, 1 / mcRepsTot ^ 2); if (sepsq < MCTol ^ 2) break endif ## Based on the current estimate, find the number of trials needed to ## make the MC std err less than the specified tolerance. mcRepsMin = 1.2 * (mcRepsTot * sepsq) / (MCTol ^ 2); endwhile endfunction ## Test input validation %!error adtest (); %!error adtest (ones (20,2)); %!error adtest ([1+i,0-3i]); %!error ... %! adtest (ones (20,1), 'Distribution', 'normal'); %!error ... %! adtest (rand (20,1), 'Distribution', {'normal', 5, 3}); %!error ... %! adtest (rand (20,1), 'Distribution', {'norm', 5}); %!error ... %! adtest (rand (20,1), 'Distribution', {'exp', 5, 4}); %!error ... %! adtest (rand (20,1), 'Distribution', {'ev', 5}); %!error ... %! adtest (rand (20,1), 'Distribution', {'logn', 5, 3, 2}); %!error ... %! adtest (rand (20,1), 'Distribution', {'Weibull', 5}); %!error ... %! adtest (rand (20,1), 'Distribution', 35); %!error ... %! adtest (rand (20,1), 'Name', 'norm'); %!error ... %! adtest (rand (20,1), 'Name', {'norm', 75, 10}); %!error ... %! adtest (rand (20,1), 'Distribution', 'norm', 'Asymptotic', true); %!error ... %! adtest (rand (20,1), 'MCTol', 0.001, 'Asymptotic', true); %!error ... %! adtest (rand (20,1), 'Distribution', {'norm', 5, 3}, 'MCTol', 0.001, ... %! 'Asymptotic', true); %!error ... %! [h, pval, ADstat, CV] = adtest (ones (20,1), 'Distribution', {'norm',5,3},... %! 'Alpha', 0.000000001); %!error ... %! [h, pval, ADstat, CV] = adtest (ones (20,1), 'Distribution', {'norm',5,3},... %! 'Alpha', 0.999999999); %!error ... %! adtest (10); ## Test warnings %!warning ... %! randn ('seed', 34); %! adtest (ones (20,1), 'Alpha', 0.000001); %!warning ... %! randn ('seed', 34); %! adtest (normrnd (0,1,100,1), 'Alpha', 0.99999); %!warning ... %! randn ('seed', 34); %! adtest (normrnd (0,1,100,1), 'Alpha', 0.00001); ## Test results %!test %! load examgrades %! x = grades(:,1); %! [h, pval, adstat, cv] = adtest (x); %! assert_equal (h, false); %! assert_equal (pval, 0.1854, 1e-4); %! assert_equal (adstat, 0.5194, 1e-4); %! assert_equal (cv, 0.7470, 1e-4); %!test %! load examgrades %! x = grades(:,1); %! [h, pval, adstat, cv] = adtest (x, 'Distribution', 'ev'); %! assert_equal (h, false); %! assert_equal (pval, 0.071363, 1e-6); %!test %! load examgrades %! x = grades(:,1); %! [h, pval, adstat, cv] = adtest (x, 'Distribution', {'norm', 75, 10}); %! assert_equal (h, false); %! assert_equal (pval, 0.4687, 1e-4); statistics-release-1.9.2/inst/Hypothesis_Testing/anova.m000066400000000000000000005043531524624707500234540ustar00rootroot00000000000000## Copyright (C) 2026 Aman Behera ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef anova ## -*- texinfo -*- ## @deftp {statistics} anova ## ## Object-oriented interface for analysis of variance. ## ## The @code{anova} class provides a MATLAB-compatible object interface for ## analysis of variance. It stores factors, response data, model ## specification, and fitted results in one object. The class chooses the ## narrowest compatible backend, delegates the numeric computation to the ## existing ANOVA functions, and exposes common follow-up operations such as ## @code{stats}, @code{groupmeans}, @code{boxchart}, ## @code{plotComparisons}, @code{varianceComponent}, and ## @code{multcompare}. ## ## Models are fitted lazily. Methods that need fitted results call ## @code{fit} internally when necessary, so users may construct an object and ## immediately call inspection or post-hoc methods. ## ## @seealso{anova1, anova2, anovan, multcompare} ## @end deftp properties (GetAccess = public, SetAccess = private) ## Data ## -*- texinfo -*- ## @deftp {anova} {property} Y ## ## Response data ## ## Numeric response vector (or matrix, for the one-way column form) used to ## fit the ANOVA model. This property is read-only. ## ## @end deftp Y ## -*- texinfo -*- ## @deftp {anova} {property} Factors ## ## Factor data ## ## Table containing one variable for each factor used to fit the ANOVA ## model. This property is read-only. ## ## @end deftp Factors ## -*- texinfo -*- ## @deftp {anova} {property} Formula ## ## Model formula ## ## Read-only structural formula value with response, predictor, term, ## nesting, and linear-predictor fields matching MATLAB's formula object. ## ## @end deftp Formula = struct (); ## -*- texinfo -*- ## @deftp {anova} {property} FactorNames ## ## Factor names ## ## String row vector containing the factor names used by the fitted ANOVA ## model. This property is read-only. ## ## @end deftp FactorNames = []; ## -*- texinfo -*- ## @deftp {anova} {property} ExpandedFactorNames ## ## Coefficient names ## ## Cell array of character vectors naming the model coefficients when the ## selected backend exposes them, otherwise an empty cell array. This ## property is read-only. ## ## @end deftp ExpandedFactorNames = {}; ## -*- texinfo -*- ## @deftp {anova} {property} SumOfSquaresType ## ## Sum-of-squares type ## ## String scalar selecting @qcode{"one"}, @qcode{"two"}, ## @qcode{'three'}, or @qcode{'hierarchical'} sums of squares. This ## property is read-only. ## ## @end deftp SumOfSquaresType = 'three'; ## -*- texinfo -*- ## @deftp {anova} {property} RandomFactors ## ## Random factors ## ## Positive integer indices of random factors. This property is ## read-only. ## ## @end deftp RandomFactors = []; ## -*- texinfo -*- ## @deftp {anova} {property} CategoricalFactors ## ## Categorical factors ## ## Positive integer indices of factors treated as categorical. This ## property is read-only. ## ## @end deftp CategoricalFactors = 'all'; ## -*- texinfo -*- ## @deftp {anova} {property} ResponseName ## ## Response variable name ## ## Character vector used as the response name in formula display. ## This property is read-only. ## ## @end deftp ResponseName = 'Y'; ## -*- texinfo -*- ## @deftp {anova} {property} NumObservations ## ## Number of observations ## ## Scalar number of response observations used by the model. This ## property is read-only. ## ## @end deftp NumObservations = 0; ## Results (populated after fit; empty until then) ## -*- texinfo -*- ## @deftp {anova} {property} Coefficients ## ## Model coefficient estimates ## ## Numeric vector of fitted coefficient estimates. This property is ## read-only. ## ## @end deftp Coefficients = []; ## -*- texinfo -*- ## @deftp {anova} {property} Residuals ## ## Model residuals ## ## Table with variables @code{Raw} (observed minus fitted values) and ## @code{Pearson} (raw residuals scaled by the root mean squared error) ## when the selected backend exposes residuals, otherwise empty. This ## property is read-only. ## ## @end deftp Residuals = []; ## -*- texinfo -*- ## @deftp {anova} {property} Metrics ## ## Model fit metrics ## ## Table with variables @code{MSE}, @code{RMSE}, @code{SSE}, @code{SSR}, ## @code{SST}, @code{RSquared}, and @code{AdjustedRSquared} summarising the ## fitted model. This property is read-only. ## ## @end deftp Metrics = []; endproperties properties (GetAccess = public, SetAccess = private, Hidden) ## Backend aliases and Octave-specific extensions retained for internal ## use and power users; kept off the documented MATLAB property surface. GROUP ## raw factor data (fit workhorse) Response = []; ModelType = 'linear'; ModelSpecification = 'linear'; SSType = 3; VarNames = {}; Continuous = []; Random = []; NumFactors = 0; AnovaTable = {}; FittedValues = []; DFE = []; MSE = []; DesignMatrix = []; Stats = struct (); endproperties properties (Access = private) Alpha = 0.05; Contrasts = {}; ## Accepted and validated for compatibility with the anova1 / anova2 / ## anovan calling convention, but never forwarded to a backend: results are ## presented through summary, disp, and plotDiagnostics instead. Display = 'off'; Weights = []; endproperties properties (Access = private) fitted_ = false; dirty_ = true; nFactors_ = 0; backend_ = ''; ## 'anova1' | 'anova2' | 'anovan' reps_ = []; ## replicate count for anova2 backend continuousSpecified_ = false; coefficientStats_ = []; rawResiduals_ = []; nesting_ = []; formulaText_ = ''; formulaTerms_ = []; endproperties methods (Hidden) ## Custom display of the model summary: backend, fit state, number of ## factors, sum-of-squares type and significance level. function disp (obj) obj.ensureFit_ (); constrained = 'constrained'; fprintf ("\n %d-way anova, %s (Type %s) sums of squares.\n\n", ... obj.NumFactors, constrained, obj.sstypeLabel_ ()); if (! isempty (obj.formulaText_)) fprintf (" %s\n\n", obj.formulaText_); endif obj.printAtab_ (obj.AnovaTable); fprintf ("\n Properties, Methods\n\n"); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {anova} {@var{obj} =} anova (@var{Y}) ## @deftypefnx {anova} {@var{obj} =} anova (@var{factors}, @var{Y}) ## @deftypefnx {anova} {@var{obj} =} anova (@var{tbl}, @var{Y}) ## @deftypefnx {anova} {@var{obj} =} anova (@var{tbl}, @var{responseVarName}) ## @deftypefnx {anova} {@var{obj} =} anova (@var{tbl}, @var{formula}) ## @deftypefnx {anova} {@var{obj} =} anova (@dots{}, @var{name}, @var{value}) ## ## Create an object-oriented analysis of variance model. ## ## @var{Y} is a non-empty numeric response vector or matrix. ## @var{factors} contains grouping variables for vector responses and may ## be a grouping vector, a matrix of grouping variables, or a cell array of ## grouping vectors. If @var{factors} is omitted and @var{Y} is a matrix, ## columns of @var{Y} are treated as groups following @code{anova1} matrix ## syntax. ## ## @var{tbl} is a table whose variables contain factors. The response may ## be supplied separately, selected by variable name, or specified with a ## Wilkinson formula. @qcode{'FactorNames'} can select a subset of table ## variables when a formula is not supplied. ## ## Supported name-value arguments include @qcode{'ModelSpecification'}, ## @qcode{'SumOfSquaresType'}, @qcode{'FactorNames'}, ## @qcode{'CategoricalFactors'}, @qcode{'RandomFactors'}, ## @qcode{'ResponseName'}, @qcode{'Alpha'}, and @qcode{'Display'}. ## Passing @qcode{'Reps'} selects the balanced two-way @code{anova2} ## backend when @var{Y} is a non-vector matrix. ## ## @end deftypefn function obj = anova (factors, Y, varargin) if (nargin < 1) error ("anova: too few input arguments."); endif if (nargin < 2) Y = []; endif table_factors = []; formula_input = ""; response_locked = false; if (isa (factors, "table")) if (nargin < 2) error (strcat ("anova: table input requires response data or", ... " a response name.")); endif if (mod (numel (varargin), 2) != 0) error ("anova: name-value pairs must come in pairs."); endif response_locked = obj.isName_ (Y); [factors, Y, table_factors, table_names, response_name, ... formula_input, formula_model, formula_nesting] = ... obj.parseTableInput_ (factors, Y, varargin{:}); obj.VarNames = table_names; obj.FactorNames = string (table_names); obj.ResponseName = response_name; if (! isempty (formula_input)) obj.formulaText_ = formula_input; obj.formulaTerms_ = formula_model; obj.ModelSpecification = formula_input; obj.ModelType = formula_model; obj.nesting_ = formula_nesting; endif elseif (nargin < 2) Y = factors; factors = []; elseif (isempty (Y) && isnumeric (factors)) Y = factors; factors = []; elseif (obj.isName_ (Y)) varargin = [{Y}, varargin]; Y = factors; factors = []; endif if (! isnumeric (Y) || isempty (Y)) error ("anova: Y must be a non-empty numeric array."); endif if (mod (numel (varargin), 2) != 0) error ("anova: name-value pairs must come in pairs."); endif obj.Response = Y; obj.Y = Y(:); obj.GROUP = factors; ## Parse name-value pairs (mirrors anovan.m's loop style) for idx = 1:2:numel (varargin) name = varargin{idx}; value = varargin{idx + 1}; if (! ischar (name)) error ("anova: parameter name must be a character vector."); endif switch (lower (name)) case {'model', 'modelspecification'} if (isempty (formula_input)) obj = obj.setModelSpecification_ (value); endif case {'sstype', 'sumofsquarestype'} obj = obj.setSumOfSquaresType_ (value); case {'varnames', 'factornames'} if (isempty (formula_input)) obj = obj.setFactorNames_ (value); endif case 'contrasts' obj.Contrasts = value; case 'alpha' obj.Alpha = value; case {'categoricalfactors'} obj.CategoricalFactors = value; case {'continuous'} obj.Continuous = value; obj.continuousSpecified_ = true; case {'random', 'randomfactors'} obj.RandomFactors = value; obj.Random = value; case 'weights' obj.Weights = value; case {'display', 'displayopt'} obj.Display = value; case 'responsename' if (! response_locked) obj.ResponseName = value; endif case 'reps' obj.reps_ = value; otherwise error ("anova: parameter '%s' is not supported.", name); endswitch endfor [obj.Response, obj.GROUP, obj.Weights, table_factors] = ... obj.removeMissing_ (obj.Response, obj.GROUP, obj.Weights, ... table_factors); obj.GROUP = obj.normalizeGroups_ (obj.GROUP); obj.Y = obj.Response(:); obj.nFactors_ = obj.countFactors_ (); obj.NumFactors = obj.nFactors_; obj.NumObservations = numel (obj.Response); if (isnumeric (obj.ModelType) && isscalar (obj.ModelType)) obj.ModelType = min (obj.ModelType, obj.nFactors_); endif if (isempty (formula_input)) obj = obj.setFormula_ (); endif obj.FactorNames = string (obj.VarNames); obj.SumOfSquaresType = string (obj.SumOfSquaresType); obj = obj.updateFormula_ (); obj = obj.syncFactorSelectors_ (); obj.validateSpec_ (); obj.validateData_ (); if (isempty (table_factors)) obj.Factors = obj.factorTable_ (); else obj.Factors = table_factors; endif obj = obj.selectBackend_ (); obj.dirty_ = true; ## Fit eagerly at construction so result properties are populated on the ## returned value object (value-class semantics; matches MATLAB's anova). obj = obj.ensureFit_ (); endfunction endmethods methods (Hidden) ## -*- texinfo -*- ## @deftypefn {anova} {} fit (@var{obj}) ## ## Fit the ANOVA model if it has not already been fitted. ## ## This method is optional for most workflows because methods such as ## @code{summary}, @code{multcompare}, @code{plotDiagnostics}, ## @code{predict}, and @code{getEffectSizes} call @code{fit} lazily when ## fitted results are needed. ## ## @end deftypefn function fit (obj) obj.ensureFit_ (); endfunction ## -*- texinfo -*- ## @deftypefn {anova} {} summary (@var{obj}) ## ## Display the fitted ANOVA table and basic fit statistics. ## ## The model is fitted first if needed. The printed table uses the ## backend table returned by @code{anova1}, @code{anova2}, or ## @code{anovan}, followed by the mean squared error, error degrees of ## freedom, and significance level. ## ## @end deftypefn function summary (obj) obj.ensureFit_ (); atab = obj.AnovaTable; if (isempty (atab)) fprintf (" anova: no results to display.\n"); return; endif sstype_char = obj.sstypeLabel_ (); fprintf ("\nANOVA TABLE (Type %s sums-of-squares, backend = %s):\n\n", ... sstype_char, obj.backend_); obj.printAtab_ (atab); if (! isempty (obj.MSE)) fprintf ("\nMSE: %g DFE: %g Alpha: %g\n", ... obj.MSE, obj.DFE, obj.Alpha); endif fprintf ("\n"); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {anova} {@var{s} =} stats (@var{obj}) ## @deftypefnx {anova} {@var{s} =} stats (@var{obj}, @var{type}) ## @deftypefnx {anova} {@var{s} =} stats (@var{obj}, @qcode{"Component"}, @var{sstype}) ## @deftypefnx {anova} {[@var{s}, @var{ems}] =} stats (@dots{}) ## ## Return component or summary ANOVA statistics as a table. ## ## With no @var{type}, or with @qcode{"component"}, return statistics for ## each model term, error, and total. With @qcode{"summary"}, group terms ## into linear, nonlinear, and regression rows. Replicated continuous ## designs also report lack-of-fit and pure-error statistics. ## ## The @qcode{"Component"} form computes the component table using ## @var{sstype}, which must be @qcode{"one"}, @qcode{"two"}, ## @qcode{"three"}, or @qcode{"hierarchical"}. This request does not ## change the object's read-only @code{SumOfSquaresType} property. ## The second output @var{ems} contains expected mean-square information ## for each model term and the error term. Its variables are ## @code{Type}, @code{ExpectedMeanSquares}, ## @code{MeanSquaresDenominator}, @code{DFDenominator}, and ## @code{FDenominator}. ## ## @end deftypefn function [s, ems] = stats (obj, varargin) type = "component"; sstype = []; if (numel (varargin) == 1) type = varargin{1}; elseif (numel (varargin) == 2 && obj.isName_ (varargin{1}) ... && strcmpi (varargin{1}, "component")) sstype = obj.parseSSType_ (varargin{2}); elseif (! isempty (varargin)) error ("anova.stats: invalid input arguments."); endif if (! obj.isName_ (type) ... || ! any (strcmpi (type, {"component", "summary"}))) error ("anova.stats: type must be 'component' or 'summary'."); endif if (strcmpi (type, "summary")) atab = obj.summaryStats_ (); elseif (! isempty (sstype)) atab = obj.componentStats_ (sstype); else obj.ensureFit_ (); atab = obj.AnovaTable; endif s = obj.statsTable_ (atab); if (nargout > 1) if (isempty (sstype)) sstype = obj.SSType; endif ems = obj.expectedMeanSquares_ (sstype); endif endfunction ## -*- texinfo -*- ## @deftypefn {anova} {@var{means} =} groupmeans (@var{obj}) ## @deftypefnx {anova} {@var{means} =} groupmeans (@var{obj}, @var{factors}) ## ## Return mean response estimates by factor level. ## ## The returned value is a @code{table} with one row per factor-level ## combination and columns for the level, mean, standard error, and ## confidence bounds. ## ## @end deftypefn function means = groupmeans (obj, factors, varargin) if (nargin < 2 || isempty (factors)) if (obj.NumFactors != 1) error (strcat ("anova.groupmeans: factors must be specified for", ... " a multi-factor ANOVA.")); endif factors = obj.VarNames; endif alpha = obj.parseAlpha_ (varargin{:}); obj.ensureFit_ (); idx = obj.factorIndices_ (factors); names = obj.VarNames(idx); y = obj.Y(:); if (isempty (idx)) error ("anova.groupmeans: factors are required for group means."); endif [gid, levels] = findgroups (obj.Factors(:, names)); valid = isfinite (y) & isfinite (gid) & gid > 0; used = unique (gid(valid), "stable"); [~, compact] = ismember (gid(valid), used); levels = levels(used, :); n = accumarray (compact, 1); mu = accumarray (compact, y(valid), [], @mean); se = sqrt (obj.MSE ./ n); crit = tinv (1 - alpha / 2, max (obj.DFE, 1)); lower = mu - crit * se; upper = mu + crit * se; intervals = table (mu, se, lower, upper, "VariableNames", ... {"Mean", "SE", "MeanLower", "MeanUpper"}); means = [levels, intervals]; endfunction ## -*- texinfo -*- ## @deftypefn {anova} {} boxchart (@var{obj}) ## @deftypefnx {anova} {@var{h} =} boxchart (@var{obj}, @dots{}) ## ## Plot response values grouped by up to two categorical factors. ## ## This method uses @code{boxplot} as the graphics backend in Octave and ## returns the native box graphics handles. A target axes may be supplied ## as the first optional argument. ## ## @end deftypefn function h = boxchart (obj, varargin) obj.ensureFit_ (); [target, varargin] = obj.extractAxes_ (varargin); factors = {}; if (! isempty (varargin)) candidate = varargin{1}; if (isstring (candidate)) candidate = cellstr (candidate(:))'; elseif (ischar (candidate)) candidate = {candidate}; endif if (iscellstr (candidate) ... && all (ismember (candidate, obj.VarNames))) factors = candidate; varargin(1) = []; endif endif if (isempty (factors)) if (obj.NumFactors != 1) error (strcat ("anova.boxchart: factors must be specified for", ... " a multi-factor ANOVA.")); endif factors = obj.VarNames; endif idx = obj.factorIndices_ (factors); if (numel (idx) > 2) error ("anova.boxchart: at most two factors can be plotted."); endif if (! all (ismember (idx, obj.CategoricalFactors))) error ("anova.boxchart: factors must be categorical."); endif grouping = cell (1, numel (idx)); for k = 1:numel (idx) grouping{k} = obj.Factors{:, obj.VarNames{idx(k)}}; endfor if (numel (grouping) == 1) grouping = grouping{1}; endif if (! isempty (target)) axes (target); endif [~, handles] = boxplot (obj.Y(:), grouping, "notch", "on", ... varargin{:}); h = handles.box; endfunction ## -*- texinfo -*- ## @deftypefn {anova} {} plotComparisons (@var{obj}) ## @deftypefnx {anova} {@var{h} =} plotComparisons (@var{obj}, @dots{}) ## ## Plot multiple-comparison intervals for model-adjusted group means. ## ## Clicking a group highlights it and distinguishes groups whose adjusted ## comparison is significant at the requested alpha level. A target axes ## may be supplied as the first optional argument. ## ## @end deftypefn function h = plotComparisons (obj, varargin) obj.ensureFit_ (); [target, varargin] = obj.extractAxes_ (varargin); [stats_, args, sidak] = obj.comparisonArguments_ (varargin{:}); args = obj.replaceOption_ (args, "display", "off"); alpha = obj.optionValue_ (args, "alpha", obj.Alpha); [comparisons, means, ~, names] = multcompare (stats_, args{:}); if (! isempty (sidak) && ! isempty (comparisons)) per_comparison = 1 - (1 - sidak) ^ (1 / rows (comparisons)); critical = tinv (1 - per_comparison / 2, comparisons(1, 8)); half_width = means(:, 2) * critical / sqrt (2); means(:, 3:4) = [means(:, 1) - half_width, ... means(:, 1) + half_width]; comparisons(:, 6) = 1 - (1 - comparisons(:, 6)) ... .^ rows (comparisons); endif if (isempty (target)) target = gca (); endif h = ancestor (target, "figure"); cla (target); hold (target, "on"); count = rows (means); intervals = zeros (count, 1); markers = zeros (count, 1); for group = 1:count intervals(group) = line (target, means(group, 3:4), ... [group, group], "color", [0.35, 0.35, 0.35], ... "linewidth", 1.5); markers(group) = line (target, means(group, 1), group, ... "linestyle", "none", "marker", "o", ... "markerfacecolor", [0.00, 0.45, 0.74], ... "markeredgecolor", [0.00, 0.45, 0.74]); endfor hold (target, "off"); set (target, "ydir", "reverse", "ytick", 1:count, ... "yticklabel", names); ylim (target, [0.5, count + 0.5]); xlabel (target, sprintf ("Group mean with %g%% comparison interval", ... 100 * (1 - alpha))); title (target, "Multiple comparisons"); for group = 1:count set (markers(group), "buttondownfcn", ... @(~, ~) obj.highlightComparison_ (group, markers, intervals, ... comparisons, alpha)); endfor if (count > 0) obj.highlightComparison_ (1, markers, intervals, comparisons, alpha); endif endfunction ## -*- texinfo -*- ## @deftypefn {anova} {@var{v} =} varianceComponent (@var{obj}) ## @deftypefnx {anova} {@var{v} =} varianceComponent (@var{obj}, @dots{}) ## ## Return variance component estimates for random model terms and error. ## ## @end deftypefn function v = varianceComponent (obj, varargin) alpha = obj.parseAlpha_ (varargin{:}); obj.ensureFit_ (); [coefficients, mean_squares, dfs, names] = ... obj.varianceSystem_ (obj.Stats, obj.AnovaTable, obj.SSType); estimates = coefficients \ mean_squares; lower_ms = dfs .* mean_squares ./ chi2inv (1 - alpha / 2, dfs); upper_ms = dfs .* mean_squares ./ chi2inv (alpha / 2, dfs); inverse = pinv (coefficients); lower = sum (max (inverse, 0) .* lower_ms' ... + min (inverse, 0) .* upper_ms', 2); upper = sum (max (inverse, 0) .* upper_ms' ... + min (inverse, 0) .* lower_ms', 2); ## A variance cannot be negative, so the propagated lower bound is ## truncated at zero. A negative estimate has no interval at all. lower = max (lower, 0); undefined = (estimates < 0); lower(undefined) = NaN; upper(undefined) = NaN; v = table (estimates, lower, upper, "VariableNames", ... {"VarianceComponent", "VarianceComponentLower", ... "VarianceComponentUpper"}, "RowNames", names); endfunction ## -*- texinfo -*- ## @deftypefn {anova} {@var{m} =} multcompare (@var{obj}) ## @deftypefnx {anova} {@var{m} =} multcompare (@var{obj}, @var{factors}) ## @deftypefnx {anova} {@var{m} =} multcompare (@dots{}, @var{name}, @var{value}) ## ## Perform post-hoc multiple comparisons for a fitted ANOVA object. ## ## The returned table contains the compared groups, estimated mean ## difference, confidence limits, and p-value. The default critical value ## type is @qcode{"tukey-kramer"}. ## ## @end deftypefn function m = multcompare (obj, varargin) obj.ensureFit_ (); if (isempty (fieldnames (obj.Stats))) error ("anova.multcompare: model has no stats to compare."); endif [stats_, args, sidak, dims] = obj.comparisonArguments_ (varargin{:}); gid = findgroups (obj.Factors(:, obj.VarNames(dims))); if (numel (unique (gid(isfinite (gid) & gid > 0))) < 2) m = obj.comparisonTable_ (zeros (0, 6), cell (0, 1), dims); return; endif [C, ~, ~, group_names] = multcompare (stats_, args{:}); if (! isempty (sidak)) per_comparison = 1 - (1 - sidak) ^ (1 / rows (C)); old_critical = tinv (1 - sidak / 2, C(:, 8)); standard_error = (C(:, 5) - C(:, 3)) ./ (2 * old_critical); critical = tinv (1 - per_comparison / 2, C(:, 8)); C(:, 3) = C(:, 4) - critical .* standard_error; C(:, 5) = C(:, 4) + critical .* standard_error; C(:, 6) = 1 - (1 - C(:, 6)) .^ rows (C); endif if (strcmp (stats_.source, "anovan")) group_names = obj.comparisonGroups_ (stats_, dims); endif m = obj.comparisonTable_ (C, group_names, dims); endfunction endmethods methods (Hidden) ## -*- texinfo -*- ## @deftypefn {anova} {} plotDiagnostics (@var{obj}) ## @deftypefnx {anova} {@var{h} =} plotDiagnostics (@var{obj}) ## @deftypefnx {anova} {@var{h} =} plotDiagnostics (@var{obj}, @var{name}, @var{value}) ## ## Plot residual diagnostics for an ANOVA object. ## ## The method creates a four-panel figure containing a Normal Q-Q plot, a ## Spread-Location plot, a Residual-Leverage plot, and a Cook's distance ## plot. Diagnostic plots require an @code{anovan}-backed fit because the ## fast @code{anova1} and @code{anova2} backends do not expose residuals ## and design-matrix diagnostics. ## ## Supported name-value arguments are @qcode{'FigureName'} and ## @qcode{'Visible'}. ## ## @end deftypefn function h = plotDiagnostics (obj, varargin) obj.ensureFit_ (); if (isempty (obj.rawResiduals_) || isempty (obj.FittedValues) ... || isempty (obj.DesignMatrix)) error (strcat ("anova.plotDiagnostics: diagnostic plots require", ... " an anovan-backed fit.")); endif leverage = obj.leverage_ (); if (isfield (obj.Stats, 'CooksD')) cooksd = obj.Stats.CooksD; else cooksd = obj.cooksDistance_ (leverage); endif h = obj.plotDiagnostics_ (obj.rawResiduals_, obj.FittedValues, ... leverage, cooksd, obj.DFE, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {anova} {@var{ypred} =} predict (@var{obj}) ## @deftypefnx {anova} {@var{ypred} =} predict (@var{obj}, @var{Xnew}) ## ## Predict fitted responses from an ANOVA object. ## ## With no @var{Xnew}, return fitted values for the training data. For an ## @code{anovan}-backed object, @var{Xnew} must be a numeric design matrix ## with one column per coefficient. ## ## @end deftypefn function ypred = predict (obj, Xnew, varargin) obj.ensureFit_ (); if (nargin < 2 || isempty (Xnew)) ypred = obj.FittedValues; return; endif if (isempty (obj.Coefficients)) error ("anova.predict: coefficients are unavailable for this backend."); endif if (! isempty (obj.coefficientStats_)) beta = obj.coefficientStats_(:, 1); else beta = obj.Coefficients; endif if (! isnumeric (Xnew) || columns (Xnew) != numel (beta)) error (strcat ("anova.predict: Xnew must be a numeric design", ... " matrix with %d columns."), numel (beta)); endif ypred = Xnew * beta; endfunction ## -*- texinfo -*- ## @deftypefn {anova} {@var{ES} =} getEffectSizes (@var{obj}) ## ## Return ANOVA effect-size estimates. ## ## The returned structure contains fields @code{Source}, ## @code{EtaSquared}, @code{PartialEtaSquared}, and @code{OmegaSquared}. ## Values are derived from the fitted ANOVA table. ## ## @end deftypefn function es = getEffectSizes (obj) obj.ensureFit_ (); es = obj.effectSizesFromAtab_ (); endfunction endmethods methods (Access = private) function tf = isName_ (obj, value) tf = ischar (value) || (isstring (value) && isscalar (value)); endfunction function [target, args] = extractAxes_ (obj, args) target = []; if (! isempty (args) && isscalar (args{1}) ... && ishghandle (args{1}) ... && strcmp (get (args{1}, "type"), "axes")) target = args{1}; args(1) = []; endif endfunction function highlightComparison_ (obj, selected, markers, intervals, ... comparisons, alpha) neutral = [0.45, 0.45, 0.45]; different = [0.85, 0.20, 0.16]; chosen = [0.00, 0.45, 0.74]; for group = 1:numel (markers) color = neutral; if (group == selected) color = chosen; else row = find ((comparisons(:, 1) == selected ... & comparisons(:, 2) == group) ... | (comparisons(:, 2) == selected ... & comparisons(:, 1) == group), 1); if (! isempty (row) && comparisons(row, 6) < alpha) color = different; endif endif set (markers(group), "markerfacecolor", color, ... "markeredgecolor", color); set (intervals(group), "color", color); endfor endfunction function [groups, response, factor_table, factor_names, response_name, ... formula, model, nesting] = parseTableInput_ (obj, tbl, ... response_arg, ... varargin) variable_names = tbl.Properties.VariableNames; formula = ""; model = []; nesting = []; if (isnumeric (response_arg)) response = response_arg; response_name = "Y"; factor_names = variable_names; elseif (obj.isName_ (response_arg)) value = char (response_arg); if (! isempty (strfind (value, "~"))) formula = value; schema = parseWilkinsonFormula (formula, "matrix"); if (! isscalar (schema.ResponseIdx)) error ("anova: formula must specify one response variable."); endif response_name = schema.VariableNames{schema.ResponseIdx}; [factor_names, model] = obj.schemaModel_ (schema); ordered = variable_names(ismember (variable_names, factor_names)); [~, order] = ismember (ordered, factor_names); factor_names = ordered; model = model(:, order); [model, nesting] = obj.nestedSpecification_ (formula, ... factor_names, model); else response_name = value; factor_names = variable_names(! strcmp (variable_names, value)); endif if (! any (strcmp (variable_names, response_name))) error ("anova: response variable '%s' is not in the table.", ... response_name); endif response = tbl{:, response_name}; else error (strcat ("anova: table response must be numeric, a response", ... " variable name, or a formula.")); endif if (! isempty (formula)) missing = ! ismember (factor_names, variable_names); if (any (missing)) error ("anova: formula variable '%s' is not in the table.", ... factor_names{find (missing, 1)}); endif else selected = obj.optionValue_ (varargin, "factornames", []); if (! isempty (selected)) if (isstring (selected)) selected = cellstr (selected(:))'; elseif (ischar (selected)) selected = {selected}; endif if (! iscellstr (selected) ... || ! all (ismember (selected, factor_names))) error (strcat ("anova: FactorNames must identify factor", ... " variables in the table.")); endif factor_names = selected; endif endif if (! isnumeric (response) || isempty (response) || ! isvector (response)) error (strcat ("anova: table response data must be a non-empty", ... " numeric vector.")); endif if (numel (response) != height (tbl)) error (strcat ("anova: table and response must contain the same", ... " number of rows.")); endif factor_table = tbl(:, factor_names); groups = cell (1, numel (factor_names)); for k = 1:numel (factor_names) groups{k} = tbl{:, factor_names{k}}; if (ischar (groups{k}) && rows (groups{k}) > 1) groups{k} = cellstr (groups{k}); endif endfor if (numel (groups) == 1) groups = groups{1}; endif endfunction function [factor_names, model] = schemaModel_ (obj, schema) predictor_idx = setdiff (1:numel (schema.VariableNames), ... schema.ResponseIdx, "stable"); used = any (schema.Terms(:, predictor_idx) != 0, 1); predictor_idx = predictor_idx(used); factor_names = {}; exponents = zeros (1, numel (predictor_idx)); factor_idx = zeros (1, numel (predictor_idx)); for k = 1:numel (predictor_idx) name = schema.VariableNames{predictor_idx(k)}; token = regexp (name, '^(.*)\^(\d+)$', 'tokens', 'once'); if (isempty (token)) base = name; exponents(k) = 1; else base = token{1}; exponents(k) = str2double (token{2}); endif idx = find (strcmp (factor_names, base), 1); if (isempty (idx)) factor_names{end + 1} = base; idx = numel (factor_names); endif factor_idx(k) = idx; endfor model = zeros (rows (schema.Terms), numel (factor_names)); for k = 1:numel (predictor_idx) rows_ = schema.Terms(:, predictor_idx(k)) != 0; model(rows_, factor_idx(k)) = exponents(k); endfor model(! any (model, 2), :) = []; model = unique (model, "rows", "stable"); endfunction function [model, nesting] = nestedSpecification_ (obj, formula, ... factor_names, model) nesting = false (numel (factor_names)); definitions = regexp (formula, ... '([A-Za-z]\w*)\s*\(([^()]*)\)', ... 'tokens'); for k = 1:numel (definitions) child_name = definitions{k}{1}; parent_names = strtrim (strsplit (definitions{k}{2}, ',')); child = find (strcmp (factor_names, child_name), 1); [found, parents] = ismember (parent_names, factor_names); if (isempty (child) || ! all (found)) error ("anova: nested formula contains an unknown factor."); endif nesting(child, parents) = true; members = [child, parents]; rows_ = all (model(:, members) > 0, 2) ... & sum (model > 0, 2) == numel (members); replacement = zeros (1, columns (model)); replacement(child) = 1; model(rows_, :) = repmat (replacement, sum (rows_), 1); endfor model = unique (model, "rows", "stable"); endfunction function value = optionValue_ (obj, args, option, default) value = default; for k = 1:2:numel (args) if (obj.isName_ (args{k}) && strcmpi (char (args{k}), option)) value = args{k + 1}; endif endfor endfunction function factors = factorTable_ (obj) if (obj.nFactors_ == 0) factors = table (); return; endif if (isempty (obj.GROUP) && ! isvector (obj.Response)) if (isempty (obj.reps_)) [n, groups] = size (obj.Response); group_values = reshape (repmat (1:groups, n, 1), [], 1); columns_ = {group_values}; else [~, columns_] = obj.anova2Data_ (); endif elseif (iscell (obj.GROUP) ... && size (obj.GROUP, 1) == 1 ... && numel (obj.GROUP) == obj.nFactors_) columns_ = cellfun (@(x) x(:), obj.GROUP, "UniformOutput", false); elseif (obj.nFactors_ == 1) columns_ = {obj.GROUP(:)}; else columns_ = cell (1, obj.nFactors_); for k = 1:obj.nFactors_ columns_{k} = obj.GROUP(:, k); endfor endif factors = table (columns_{:}, "VariableNames", obj.VarNames); endfunction function [response, groups, weights, factor_table] = removeMissing_ (... obj, response, groups, weights, ... factor_table) if (! isvector (response)) if (! isempty (obj.reps_)) return; elseif (isempty (groups) && any (! isfinite (response(:)))) [n, levels] = size (response); groups = reshape (repmat (1:levels, n, 1), [], 1); response = response(:); else return; endif endif valid = isfinite (response(:)); columns_ = obj.groupColumns_ (groups); for k = 1:numel (columns_) group_id = grp2idx (columns_{k}); if (numel (group_id) != numel (valid)) return; endif valid &= isfinite (group_id); endfor if (all (valid)) return; endif if (! any (valid)) error (strcat ("anova: no complete observations remain after", ... " removing missing data.")); endif response = response(valid); if (iscell (groups) && size (groups, 1) == 1 ... && numel (groups) == numel (columns_)) for k = 1:numel (groups) value = groups{k}; if (ischar (value) && rows (value) > 1) groups{k} = value(valid, :); else groups{k} = value(valid); endif endfor elseif (! isempty (groups)) groups = groups(valid, :); endif if (! isempty (weights) && numel (weights) != numel (valid)) return; elseif (! isempty (weights)) weights = weights(valid); endif if (isa (factor_table, "table") && width (factor_table) > 0) factor_table = factor_table(valid, :); endif endfunction function columns_ = groupColumns_ (obj, groups) if (isempty (groups)) columns_ = {}; elseif (iscell (groups) && size (groups, 1) == 1 ... && all (cellfun (@(x) isvector (x) || ischar (x), groups))) columns_ = groups; elseif (isvector (groups) || ischar (groups)) columns_ = {groups}; else columns_ = cell (1, columns (groups)); for k = 1:columns (groups) columns_{k} = groups(:, k); endfor endif endfunction function groups = normalizeGroups_ (obj, groups) if (ischar (groups) && rows (groups) > 1) groups = cellstr (groups); elseif (iscell (groups) && size (groups, 1) == 1) for k = 1:numel (groups) if (ischar (groups{k}) && rows (groups{k}) > 1) groups{k} = cellstr (groups{k}); endif endfor endif endfunction function obj = setModelSpecification_ (obj, value) if (isstring (value) && isscalar (value)) value = char (value); endif obj.ModelSpecification = value; if (ischar (value) && strcmpi (value, 'interactions')) obj.ModelType = 'interaction'; else obj.ModelType = value; endif endfunction function obj = setSumOfSquaresType_ (obj, value) if (isnumeric (value) && isscalar (value)) names = {'one', 'two', 'three'}; if (! any (value == [1, 2, 3])) error (strcat ("anova: SumOfSquaresType must be 'one',", ... " 'two', 'three', or 'hierarchical'.")); endif obj.SumOfSquaresType = string (names{value}); obj.SSType = value; return; endif if (isstring (value) && isscalar (value)) value = char (value); endif if (! ischar (value)) error ("anova: SumOfSquaresType must be a character vector."); endif switch (lower (value)) case {"one", "typei", "i"} obj.SumOfSquaresType = string ("one"); obj.SSType = 1; case {"two", "typeii", "ii"} obj.SumOfSquaresType = string ("two"); obj.SSType = 2; case "hierarchical" obj.SumOfSquaresType = string ("hierarchical"); obj.SSType = "h"; case {"three", "typeiii", "iii"} obj.SumOfSquaresType = string ("three"); obj.SSType = 3; otherwise error (strcat ("anova: SumOfSquaresType must be 'one',", ... " 'two', 'three', or 'hierarchical'.")); endswitch endfunction function value = parseSSType_ (obj, value) if (isstring (value) && isscalar (value)) value = char (value); endif if (! ischar (value)) error ("anova.stats: component type must be a character vector."); endif switch (lower (value)) case "one" value = 1; case "two" value = 2; case "three" value = 3; case "hierarchical" value = "h"; otherwise error (strcat ("anova.stats: component type must be 'one',", ... " 'two', 'three', or 'hierarchical'.")); endswitch endfunction function obj = setFactorNames_ (obj, value) if (isstring (value)) value = cellstr (value(:))'; elseif (ischar (value)) value = {value}; endif obj.VarNames = value; obj.FactorNames = string (value); endfunction function obj = syncFactorSelectors_ (obj) if (obj.continuousSpecified_) continuous = obj.normalizeFactorSelector_ (obj.Continuous, ... "CategoricalFactors", ... false); obj.Continuous = continuous; obj.CategoricalFactors = setdiff (1:obj.nFactors_, continuous); else obj.CategoricalFactors = obj.normalizeFactorSelector_ (... obj.CategoricalFactors, ... "CategoricalFactors", true); obj.Continuous = setdiff (1:obj.nFactors_, ... obj.CategoricalFactors); endif obj.RandomFactors = obj.normalizeFactorSelector_ (obj.RandomFactors, ... "RandomFactors", ... true); obj.Random = obj.RandomFactors; endfunction function idx = normalizeFactorSelector_ (obj, value, option, allow_all) if (isempty (value)) idx = []; return; endif if (obj.isName_ (value) && strcmpi (char (value), "all")) if (! allow_all) error ("anova: %s must identify valid factors.", option); endif idx = 1:obj.nFactors_; return; endif if (islogical (value)) if (! isvector (value) || numel (value) != obj.nFactors_) error ("anova: %s logical input must have one value per factor.", ... option); endif idx = find (value(:))'; return; endif if (isstring (value)) value = cellstr (value(:))'; elseif (ischar (value)) value = {value}; endif if (iscellstr (value)) [found, idx] = ismember (value, obj.VarNames); if (! all (found)) error ("anova: %s contains an unknown factor name.", option); endif idx = idx(:)'; return; endif if (! isnumeric (value) || ! isvector (value) ... || any (value != fix (value)) || any (value < 1) ... || any (value > obj.nFactors_)) error ("anova: %s must contain valid factor indices.", option); endif idx = unique (value(:)', "stable"); endfunction function obj = setFormula_ (obj) if (isempty (obj.VarNames)) obj.VarNames = arrayfun (@(k) sprintf ("Factor%d", k), ... 1:obj.nFactors_, "UniformOutput", false); obj.FactorNames = string (obj.VarNames); elseif (numel (obj.VarNames) != obj.nFactors_) error ("anova: FactorNames must contain one name per factor."); endif if (ischar (obj.ModelType) ... && ! isempty (strfind (obj.ModelType, "~"))) schema = parseWilkinsonFormula (obj.ModelType, "matrix"); if (! isscalar (schema.ResponseIdx)) error ("anova: ModelSpecification must contain one response."); endif response = schema.VariableNames{schema.ResponseIdx}; if (! strcmp (response, obj.ResponseName)) error (strcat ("anova: formula response must match", ... " ResponseName.")); endif [schema_names, schema_model] = obj.schemaModel_ (schema); [found, columns_] = ismember (obj.VarNames, schema_names); if (! all (found)) error ("anova: formula must use names in FactorNames."); endif model = schema_model(:, columns_); [model, obj.nesting_] = obj.nestedSpecification_ (... obj.ModelType, obj.VarNames, model); obj.ModelType = model; obj.formulaTerms_ = model; obj.formulaText_ = obj.ModelSpecification; return; endif if (obj.nFactors_ == 0) obj.formulaTerms_ = zeros (0, 0); obj.formulaText_ = sprintf ("%s ~ 1", obj.ResponseName); else if (ischar (obj.ModelType) ... && obj.isPolynomialModel_ (obj.ModelType)) model = obj.polynomialTerms_ (obj.ModelType); obj.ModelType = model; else model = obj.modelTerms_ (obj.ModelType); endif obj.formulaTerms_ = model; terms = obj.formulaTermNames_ (model); rhs = strjoin (terms, " + "); obj.formulaText_ = sprintf ("%s ~ 1 + %s", obj.ResponseName, rhs); endif endfunction function model = modelTerms_ (obj, specification) if (isnumeric (specification) && ! isscalar (specification)) model = specification; return; endif if (isnumeric (specification)) max_order = min (specification, obj.nFactors_); elseif (strcmpi (specification, "linear")) max_order = 1; elseif (strcmpi (specification, "interaction")) max_order = min (2, obj.nFactors_); else max_order = obj.nFactors_; endif model = zeros (0, obj.nFactors_); for order = 1:max_order combinations = nchoosek (1:obj.nFactors_, order); block = zeros (rows (combinations), obj.nFactors_); for row = 1:rows (combinations) block(row, combinations(row, :)) = 1; endfor model = [model; block]; endfor endfunction function names = formulaTermNames_ (obj, model) names = obj.termNames_ (model); if (isempty (obj.nesting_)) return; endif for factor = find (any (obj.nesting_, 2))' parents = find (obj.nesting_(factor, :)); nested = sprintf ("%s(%s)", obj.VarNames{factor}, ... strjoin (obj.VarNames(parents), ",")); for term = find (model(:, factor) > 0)' names{term} = strrep (names{term}, obj.VarNames{factor}, nested); endfor endfor endfunction function obj = updateFormula_ (obj) separator = strfind (obj.formulaText_, "~"); if (isempty (separator)) linear_predictor = obj.formulaText_; else linear_predictor = strtrim (obj.formulaText_(separator(1) + 1:end)); endif term_names = obj.formulaTermNames_ (obj.formulaTerms_); nesting = obj.nesting_; if (isempty (nesting)) nesting = false (obj.nFactors_); endif obj.Formula = struct (... "ResponseName", string (obj.ResponseName), ... "PredictorNames", string (obj.VarNames), ... "Terms", obj.formulaTerms_, ... "TermNames", string (term_names(:)), ... "HasIntercept", true, ... "Nesting", logical (nesting), ... "LinearPredictor", string (linear_predictor), ... "Text", string (obj.formulaText_)); endfunction function tf = isPolynomialModel_ (obj, model) tf = any (strcmpi (model, {"purequadratic", "quadratic"})) ... || ! isempty (regexp (lower (model), '^poly[0-9]+$', 'once')); endfunction function model = polynomialTerms_ (obj, specification) n = obj.nFactors_; if (strcmpi (specification, "purequadratic")) model = [eye(n); 2 * eye(n)]; return; elseif (strcmpi (specification, "quadratic")) interactions = zeros (0, n); if (n > 1) pairs = nchoosek (1:n, 2); interactions = zeros (rows (pairs), n); for i = 1:rows (pairs) interactions(i, pairs(i, :)) = 1; endfor endif model = [eye(n); interactions; 2 * eye(n)]; return; endif degrees = specification(5:end) - '0'; if (numel (degrees) != n) error (strcat ("anova: polyIJK must specify one degree per", ... " factor.")); endif model = []; for factor = 1:n for exponent = 1:degrees(factor) row = zeros (1, n); row(factor) = exponent; model(end + 1, :) = row; endfor endfor grids = cell (1, n); ranges = arrayfun (@(d) 0:d, degrees, "UniformOutput", false); [grids{:}] = ndgrid (ranges{:}); combinations = cell2mat (cellfun (@(g) g(:), grids, ... "UniformOutput", false)); interactions = combinations(sum (combinations > 0, 2) > 1 ... & sum (combinations, 2) <= max (degrees), :); model = [model; interactions]; endfunction function names = termNames_ (obj, model) names = cell (rows (model), 1); for row = 1:rows (model) pieces = {}; for column = find (model(row, :) != 0) exponent = model(row, column); if (exponent == 1) pieces{end + 1} = obj.VarNames{column}; else pieces{end + 1} = sprintf ("%s^%d", ... obj.VarNames{column}, exponent); endif endfor names{row} = strjoin (pieces, ":"); endfor endfunction function alpha = parseAlpha_ (obj, varargin) alpha = obj.Alpha; if (isempty (varargin)) return; endif if (mod (numel (varargin), 2) != 0) error ("anova: name-value pairs must come in pairs."); endif for k = 1:2:numel (varargin) if (! obj.isName_ (varargin{k})) error ("anova: parameter name must be a character vector."); endif switch (lower (char (varargin{k}))) case 'alpha' alpha = varargin{k + 1}; otherwise error ("anova: parameter '%s' is not supported.", varargin{k}); endswitch endfor if (! (isnumeric (alpha) && isscalar (alpha) ... && alpha > 0 && alpha < 1)) error ("anova: Alpha must be a numeric scalar in (0, 1)."); endif endfunction function [stats_, args, sidak_alpha, dims] = comparisonArguments_ (... obj, varargin) factors = {}; option_names = {"alpha", "criticalvaluetype", "approximate", ... "controlgroup", "display", "displayopt", ... "estimate"}; if (! isempty (varargin)) first = varargin{1}; is_option = obj.isName_ (first) ... && any (strcmpi (char (first), option_names)); if ((iscellstr (first) || isstring (first) || ischar (first)) ... && ! is_option) factors = first; varargin(1) = []; endif endif if (isempty (factors)) if (obj.NumFactors != 1) error (strcat ("anova.multcompare: factors must be specified for", ... " a multi-factor ANOVA.")); endif dims = 1; else dims = obj.factorIndices_ (factors); endif if (mod (numel (varargin), 2) != 0) error ("anova.multcompare: name-value pairs must come in pairs."); endif alpha = 0.05; ctype = "tukey-kramer"; has_approximate = false; has_control = false; args = {"display", "off"}; for k = 1:2:numel (varargin) if (! obj.isName_ (varargin{k})) error ("anova.multcompare: parameter name must be text."); endif name = lower (char (varargin{k})); value = varargin{k + 1}; switch (name) case "alpha" alpha = value; case "criticalvaluetype" if (! obj.isName_ (value)) error ("anova.multcompare: CriticalValueType must be text."); endif ctype = lower (char (value)); case "approximate" if (! ((islogical (value) || isnumeric (value)) ... && isscalar (value) && any (value == [0, 1]))) error (strcat ("anova.multcompare: Approximate must be a", ... " logical scalar.")); endif has_approximate = true; case "controlgroup" has_control = true; args = [args, {"controlgroup", value}]; case {"display", "displayopt", "estimate"} args = [args, {name, value}]; otherwise error ("anova.multcompare: parameter '%s' is not supported.", ... varargin{k}); endswitch endfor if (! (isnumeric (alpha) && isscalar (alpha) ... && alpha > 0 && alpha < 1)) error ("anova.multcompare: Alpha must be a numeric scalar in (0, 1)."); endif supported = {"tukey-kramer", "hsd", "dunn-sidak", ... "bonferroni", "scheffe", "dunnett", "lsd"}; if (! any (strcmp (ctype, supported))) error ("anova.multcompare: unsupported CriticalValueType '%s'.", ... ctype); endif if ((has_approximate || has_control) && ! strcmp (ctype, "dunnett")) error (strcat ("anova.multcompare: Approximate and ControlGroup", ... " require CriticalValueType 'dunnett'.")); endif if (strcmp (ctype, "dunnett") && numel (dims) != 1) error ("anova.multcompare: Dunnett's test requires one factor."); endif sidak_alpha = []; if (strcmp (ctype, "dunn-sidak")) sidak_alpha = alpha; ctype = "lsd"; endif args = [args, {"alpha", alpha, "criticalvaluetype", ctype}]; stats_ = obj.Stats; if (strcmp (obj.backend_, "anova2")) if (numel (dims) == 1) estimate = "column"; if (dims == 2) estimate = "row"; endif args = obj.replaceOption_ (args, "estimate", estimate); else [y, groups] = obj.anova2Data_ (); [~, ~, stats_] = anovan (y, groups, obj.buildAnovanArgs_ (){:}); args = [args, {"dim", dims}]; endif elseif (strcmp (obj.backend_, "anovan")) args = [args, {"dim", dims}]; endif endfunction function args = replaceOption_ (obj, args, option, value) names = args(1:2:end); idx = find (cellfun (@(x) obj.isName_ (x) ... && strcmpi (char (x), option), names), 1, "last"); if (isempty (idx)) args = [args, {option, value}]; else args{2 * idx} = value; endif endfunction function idx = factorIndices_ (obj, factors) if (isstring (factors)) factors = cellstr (factors(:))'; elseif (ischar (factors)) factors = {factors}; endif if (! iscellstr (factors)) error ("anova.multcompare: factors must be factor names."); endif [found, idx] = ismember (factors, obj.VarNames); if (! all (found) || isempty (idx)) error ("anova.multcompare: factors contains an unknown factor name."); endif idx = idx(:)'; endfunction function [y, groups] = anova2Data_ (obj) [nr, nc] = size (obj.Response); nlevels = nr / obj.reps_; y = obj.Response(:); column_factor = kron ((1:nc)', ones (nr, 1)); row_factor = repmat (kron ((1:nlevels)', ones (obj.reps_, 1)), ... nc, 1); groups = {column_factor, row_factor}; endfunction function out = comparisonTable_ (obj, C, group_names, dims) if (istable (group_names)) if (numel (dims) == 1) group1 = group_names{C(:, 1), 1}; group2 = group_names{C(:, 2), 1}; if (ischar (group1) && rows (group1) > 1) group1 = cellstr (group1); group2 = cellstr (group2); endif else group1 = group_names(C(:, 1), :); group2 = group_names(C(:, 2), :); endif else group1 = obj.groupIdentifiers_ (group_names(C(:, 1)), dims); group2 = obj.groupIdentifiers_ (group_names(C(:, 2)), dims); endif out = table (group1, group2, C(:, 4), C(:, 3), C(:, 5), C(:, 6), ... "VariableNames", {"Group1", "Group2", ... "MeanDifference", "MeanDifferenceLower", ... "MeanDifferenceUpper", "pValue"}); endfunction function groups = comparisonGroups_ (obj, stats_, dims) df = stats_.df; offsets = 1 + cumsum (df); terms = find (sum (stats_.terms(:,dims) > 0, 2) ... == sum (stats_.terms > 0, 2)); design = zeros (rows (stats_.X), columns (stats_.X)); design(:,1) = 1; for term = terms' columns_ = offsets(term) - df(term) + 1:offsets(term); design(:,columns_) = stats_.X(:,columns_); endfor unique_design = unique (design, "rows", "stable"); rows_ = zeros (rows (unique_design), 1); for i = 1:rows (unique_design) rows_(i) = find (all (design == unique_design(i,:), 2), 1); endfor selected = obj.Factors(rows_, obj.VarNames(dims)); groups = selected; endfunction function groups = groupIdentifiers_ (obj, labels, dims) labels = cellstr (labels); if (isempty (labels)) groups = obj.convertGroupValues_ (cell (0, 1), dims(1)); return; endif parts = cellfun (@(x) strsplit (x, ", "), labels, ... "UniformOutput", false); widths = cellfun (@numel, parts); has_names = ! any (widths != widths(1)) ... && all (cellfun (@(x) ! isempty (strfind (x, "=")), ... [parts{:}])); if (! has_names) parts = cellfun (@(x) {x}, labels, "UniformOutput", false); widths(:) = 1; endif values = cell (numel (labels), widths(1)); for i = 1:numel (labels) for j = 1:widths(1) if (has_names) separator = strfind (parts{i}{j}, "="); value = parts{i}{j}(separator(1) + 1:end); else value = parts{i}{j}; endif values{i, j} = strtrim (value); endfor endfor if (widths(1) == 1) groups = obj.convertGroupValues_ (values(:, 1), dims(1)); else columns_ = cell (1, widths(1)); for j = 1:widths(1) columns_{j} = obj.convertGroupValues_ (values(:, j), dims(j)); endfor groups = table (columns_{:}, ... "VariableNames", obj.VarNames(dims)); endif endfunction function values = convertGroupValues_ (obj, text_values, dim) original = obj.Factors{:, obj.VarNames{dim}}; if (islogical (original)) values = strcmpi (text_values, "true") ... | strcmp (text_values, "1"); elseif (isnumeric (original)) values = cellfun (@str2double, text_values); elseif (iscategorical (original)) values = categorical (text_values, categories (original)); elseif (isstring (original)) values = string (text_values); else values = text_values; endif endfunction function [G, names] = selectedFactorMatrix_ (obj, factors) if (ischar (factors)) idx = find (strcmp (obj.VarNames, factors)); elseif (iscellstr (factors)) idx = cellfun (@(s) find (strcmp (obj.VarNames, s), 1), factors); else idx = factors; endif if (isempty (idx)) G = []; names = {}; return; endif if (any (idx < 1) || any (idx > obj.NumFactors)) error ("anova: factor index exceeds the number of factors."); endif if (isempty (obj.GROUP) && ! isvector (obj.Response)) [n, m] = size (obj.Response); group_arg = reshape (repmat ((1:m), n, 1), [], 1); G = group_arg(:, idx); elseif (iscell (obj.GROUP) ... && all (cellfun (@(c) isvector (c) || ischar (c), ... obj.GROUP(:))) ... && size (obj.GROUP, 1) == 1) G = cell2mat (cellfun (@(c) c(:), obj.GROUP(idx), ... 'UniformOutput', false)); else G = obj.GROUP(:, idx); endif names = obj.VarNames(idx); endfunction function name = factorName_ (obj, idx) if (idx >= 1 && idx <= numel (obj.VarNames)) name = obj.VarNames{idx}; else name = sprintf ("Factor%d", idx); endif endfunction ## Build the public Factors table (one named column per factor) from the ## raw GROUP data, leaving the internal GROUP alias untouched. function tbl = buildFactorsTable_ (obj) if (istable (obj.GROUP)) tbl = obj.GROUP; return; endif if (isempty (obj.GROUP)) if (isvector (obj.Y)) tbl = table (); ## intercept-only: no factors return; endif [nr, nc] = size (obj.Y); if (isempty (obj.reps_)) ## one-way column form: single synthetic factor = column index cols = {reshape(repmat ((1:nc), nr, 1), [], 1)}; else ## balanced two-way (anova2) form: row-block and column factors rowfac = repmat (ceil ((1:nr)' / obj.reps_), nc, 1); colfac = reshape (repmat ((1:nc), nr, 1), [], 1); cols = {rowfac, colfac}; endif elseif (iscell (obj.GROUP) ... && all (cellfun (@(c) isvector (c) || ischar (c), ... obj.GROUP(:))) ... && size (obj.GROUP, 1) == 1) cols = cellfun (@(c) c(:), obj.GROUP, 'UniformOutput', false); elseif (isvector (obj.GROUP)) cols = {obj.GROUP(:)}; else cols = num2cell (obj.GROUP, 1); endif tbl = table (cols{:}, 'VariableNames', obj.FactorNames); endfunction ## Build the public Residuals table (Raw and Pearson) from a raw residual ## vector. Pearson residuals scale the raw residuals by the RMSE. function tbl = residualsTable_ (obj, raw) raw = raw(:); pearson = raw ./ sqrt (max (obj.MSE, eps)); tbl = table (raw, pearson, 'VariableNames', {'Raw', 'Pearson'}); endfunction ## Build the public Metrics table from the fitted ANOVA table and the ## error variance, deriving SSE/SSR/SST and the R-squared measures. function tbl = metricsTable_ (obj) mse = obj.MSE; if (isempty (mse)) mse = NaN; endif dfe = obj.DFE; if (isempty (dfe)) dfe = NaN; endif sse = NaN; sst = NaN; atab = obj.AnovaTable; if (! isempty (atab)) source_col = obj.findAtabColumn_ (atab, {'Source'}); ss_col = obj.findAtabColumn_ (atab, {'SS', 'Sum Sq.', 'Sum Sq'}); for r = 2:rows (atab) name = atab{r, source_col}; if (! ischar (name)) continue; endif if (strcmpi (name, 'Error')) sse = atab{r, ss_col}; elseif (strcmpi (name, 'Total')) sst = atab{r, ss_col}; endif endfor endif ssr = sst - sse; rsq = ssr / max (sst, eps); adj = 1 - (1 - rsq) * (obj.NumObservations - 1) / max (dfe, 1); tbl = table (mse, sqrt (max (mse, 0)), sse, ssr, sst, rsq, adj, ... 'VariableNames', {'MSE', 'RMSE', 'SSE', 'SSR', 'SST', ... 'RSquared', 'AdjustedRSquared'}); endfunction function out = statsTable_ (obj, atab) if (isempty (atab)) out = table ([], [], [], [], [], "VariableNames", ... {"SumOfSquares", "DF", "MeanSquares", "F", "pValue"}); return; endif source_col = obj.findAtabColumn_ (atab, {"Source"}); columns = {{"SumOfSquares", "Sum Sq.", "Sum Sq", "SS"}, ... {"DF", "d.f.", "df"}, ... {"MeanSquares", "Mean Sq.", "Mean Sq", "MS"}, ... {"F"}, {"pValue", "Prob>F"}}; names = {"SumOfSquares", "DF", "MeanSquares", "F", "pValue"}; n = rows (atab) - 1; values = cell (1, numel (columns)); for j = 1:numel (columns) col = obj.findAtabColumn_ (atab, columns{j}); values{j} = NaN (n, 1); for i = 1:n value = atab{i + 1, col}; if (isnumeric (value) && isscalar (value) && ! isempty (value)) values{j}(i) = value; endif endfor endfor row_names = obj.publicSourceNames_ (atab(2:end, source_col)); out = table (values{:}, "VariableNames", names, "RowNames", row_names); endfunction function ems = expectedMeanSquares_ (obj, sstype) obj.ensureFit_ (); [atab, stats_] = obj.componentFit_ (sstype); sources = obj.publicSourceNames_ (atab(2:end-1, 1)); nterms = numel (sources) - 1; term_names = sources(1:nterms); is_random = false (nterms, 1); q_coeff = ones (nterms, 1); variance_coeff = zeros (nterms, nterms); if (strcmp (obj.backend_, "anovan") && isfield (stats_, "terms")) terms = stats_.terms; if (! isempty (obj.RandomFactors)) is_random = obj.randomTermMask_ (terms, stats_); endif [q_coeff, variance_coeff] = obj.emsCoefficients_ (stats_, terms, ... sstype, is_random); elseif (strcmp (obj.backend_, "anova1")) [q_coeff(1), variance_coeff(1, 1)] = obj.oneWayEmsCoefficient_ (); is_random(1) = any (obj.RandomFactors == 1); endif formulas = cell (nterms + 1, 1); types = repmat ({"fixed"}, nterms + 1, 1); for i = 1:nterms pieces = {}; if (is_random(i)) types{i} = "random"; else term = obj.emsTerm_ (q_coeff(i), "Q", term_names{i}); if (! isempty (term)) pieces{end + 1} = term; endif ## A fixed term that contains this one contributes a Q component of ## its own, so the expected mean square names it too. for j = find (! is_random(:))' if (j != i && abs (variance_coeff(i, j)) > 1e-10) pieces{end + 1} = obj.emsTerm_ (variance_coeff(i, j), "Q", ... term_names{j}); endif endfor endif for j = find (is_random(:))' if (abs (variance_coeff(i, j)) > 1e-10) pieces{end + 1} = obj.emsTerm_ (variance_coeff(i, j), ... "V", term_names{j}); endif endfor pieces{end + 1} = "V(Error)"; formulas{i} = strjoin (pieces, "+"); endfor types{end} = "random"; formulas{end} = "V(Error)"; [term_ms, term_df, error_ms, error_df] = obj.meanSquareData_ (atab); error_ms = obj.MSE; error_df = obj.DFE; [denominator_ms, denominator_df, denominator_formula] = ... obj.denominatorData_ (variance_coeff, is_random, term_ms, ... term_df, error_ms, error_df, term_names); denominator_ms(end + 1, 1) = NaN; denominator_df(end + 1, 1) = NaN; denominator_formula{end + 1, 1} = ""; row_names = [term_names; {"Error"}]; ems = table (string (types), string (formulas), denominator_ms, ... denominator_df, string (denominator_formula), ... "VariableNames", {"Type", "ExpectedMeanSquares", ... "MeanSquaresDenominator", "DFDenominator", ... "FDenominator"}, "RowNames", row_names); endfunction function names = publicSourceNames_ (obj, names) if (strcmp (obj.backend_, "anova1") && ! isempty (names)) idx = find (strcmpi (names, "Groups"), 1); if (! isempty (idx)) names{idx} = obj.VarNames{1}; endif elseif (strcmp (obj.backend_, "anova2")) idx = find (strcmpi (names, "Columns"), 1); if (! isempty (idx)) names{idx} = obj.VarNames{1}; endif idx = find (strcmpi (names, "Rows"), 1); if (! isempty (idx)) names{idx} = obj.VarNames{2}; endif idx = find (strcmpi (names, "Interaction"), 1); if (! isempty (idx)) names{idx} = sprintf ("%s:%s", obj.VarNames{:}); endif endif endfunction function [q_coeff, variance_coeff] = emsCoefficients_ (obj, stats_, ... terms, sstype, is_random) X = full (stats_.X); dfs = stats_.df(:); nterms = rows (terms); blocks = cell (nterms, 1); first = 2; for i = 1:nterms blocks{i} = first:first + dfs(i) - 1; first += dfs(i); endfor bases = cell (nterms, 1); for i = 1:nterms factors = find (terms(i, :)); if (isfield (stats_, "vnested") && ! isempty (stats_.vnested)) nested = factors(any (stats_.vnested(factors,:), 2)); if (! isempty (nested)) factors = union (factors, find (any (stats_.vnested(nested,:), 1))); endif endif if (all (! ismember (factors, obj.Continuous))) [~, ~, ids] = unique (stats_.grps(:, factors), "rows"); bases{i} = sparse (1:numel (ids), ids, 1); else bases{i} = X(:, blocks{i}); endif endfor q_coeff = zeros (nterms, 1); variance_coeff = zeros (nterms, nterms); for i = 1:nterms [included, excluded] = obj.emsModels_ (X, blocks, terms, i, sstype); q_coeff(i) = obj.projectionDifference_ (included, excluded, ... bases{i}) / dfs(i); ## Every term that contains this one contributes to its expected mean ## square, whether that term is fixed or random, so the coefficient is ## taken for all of them; which letter names it is decided later. for j = 1:nterms variance_coeff(i, j) = obj.projectionDifference_ (... included, excluded, bases{j}) / dfs(i); endfor endfor endfunction function [term_ms, term_df, error_ms, error_df] = ... meanSquareData_ (obj, atab) source_col = obj.findAtabColumn_ (atab, {"Source"}); ms_col = obj.findAtabColumn_ (atab, ... {"MeanSquares", "Mean Sq.", ... "Mean Sq", "MS"}); df_col = obj.findAtabColumn_ (atab, {"DF", "d.f.", "df"}); sources = atab(2:end, source_col); error_row = find (strcmpi (sources, "Error"), 1); total_row = find (strcmpi (sources, "Total"), 1); term_rows = setdiff (1:numel (sources), [error_row, total_row], ... "stable"); term_ms = cell2mat (atab(term_rows + 1, ms_col)); term_df = cell2mat (atab(term_rows + 1, df_col)); error_ms = atab{error_row + 1, ms_col}; error_df = atab{error_row + 1, df_col}; endfunction function [denominator_ms, denominator_df, formulas] = ... denominatorData_ (obj, variance_coeff, is_random, term_ms, ... term_df, error_ms, error_df, term_names) nterms = numel (term_ms); random_terms = find (is_random(:)); if (isempty (random_terms)) denominator_ms = repmat (error_ms, nterms, 1); denominator_df = repmat (error_df, nterms, 1); formulas = repmat ({"MS(Error)"}, nterms, 1); return; endif random_ms = [term_ms(random_terms); error_ms]; random_df = [term_df(random_terms); error_df]; coefficients = [variance_coeff(random_terms, random_terms), ... ones(numel (random_terms), 1); zeros(1, numel (random_terms)), 1]; names = [term_names(random_terms); {"Error"}]; denominator_ms = zeros (nterms, 1); denominator_df = zeros (nterms, 1); formulas = cell (nterms, 1); for i = 1:nterms target = [variance_coeff(i, random_terms), 1]; own = find (random_terms == i, 1); if (! isempty (own)) target(own) = 0; endif weights = pinv (coefficients') * target'; ## pinv leaves rounding dust on the mean squares a denominator does ## not use. Drop it, so a denominator that is one mean square is ## exactly that mean square, and so the formula and the number agree. weights(abs (weights) <= 1e-10) = 0; denominator_ms(i) = weights' * random_ms; ## Satterthwaite's approximation, over the mean squares the ## denominator actually uses. A mean square carrying no degrees of ## freedom has no sampling distribution to combine, so a denominator ## resting on one has no degrees of freedom either. used = (weights != 0); if (any (used & (random_df(:) <= 0))) denominator_df(i) = 0; else parts = (weights(used) .* random_ms(used)) .^ 2 ./ random_df(used); denominator_df(i) = denominator_ms(i) ^ 2 / sum (parts); endif formulas{i} = obj.meanSquareFormula_ (weights, names); endfor endfunction function formula = meanSquareFormula_ (obj, weights, names) pieces = {}; for i = 1:numel (weights) coefficient = weights(i); if (abs (coefficient) <= 1e-10) continue; elseif (abs (coefficient - 1) <= 1e-10) term = sprintf ("MS(%s)", names{i}); elseif (abs (coefficient + 1) <= 1e-10) term = sprintf ("-MS(%s)", names{i}); else term = sprintf ("%.6g*MS(%s)", coefficient, names{i}); endif if (! isempty (pieces) && coefficient > 0) term = ["+", term]; endif pieces{end + 1} = term; endfor formula = strjoin (pieces, ""); endfunction function [coefficients, mean_squares, dfs, names] = ... varianceSystem_ (obj, stats_, atab, sstype) [term_ms, term_df, error_ms, error_df] = obj.meanSquareData_ (atab); sources = obj.publicSourceNames_ (atab(2:end-1, 1)); term_names = sources(1:numel (term_ms)); is_random = false (numel (term_ms), 1); variance_coeff = zeros (numel (term_ms)); if (! isempty (obj.RandomFactors)) if (strcmp (obj.backend_, "anovan") && isfield (stats_, "terms")) terms = stats_.terms; is_random = obj.randomTermMask_ (terms, stats_); [~, variance_coeff] = obj.emsCoefficients_ (stats_, terms, ... sstype, is_random); elseif (strcmp (obj.backend_, "anova1")) is_random(1) = true; [~, variance_coeff(1, 1)] = obj.oneWayEmsCoefficient_ (); endif endif random_terms = find (is_random); coefficients = [variance_coeff(random_terms, random_terms), ... ones(numel (random_terms), 1); zeros(1, numel (random_terms)), 1]; mean_squares = [term_ms(random_terms); error_ms]; dfs = [term_df(random_terms); error_df]; names = [term_names(random_terms); {"Error"}]; endfunction function atab = applyRandomInference_ (obj, atab, stats_, sstype) if (isempty (obj.RandomFactors) || ! isfield (stats_, "terms")) return; endif terms = stats_.terms; is_random = obj.randomTermMask_ (terms, stats_); [~, variance_coeff] = obj.emsCoefficients_ (stats_, terms, ... sstype, is_random); [term_ms, term_df, error_ms, error_df] = obj.meanSquareData_ (atab); names = obj.publicSourceNames_ (atab(2:rows (terms) + 1, 1)); [denominator_ms, denominator_df] = obj.denominatorData_ (... variance_coeff, is_random, term_ms, term_df, error_ms, ... error_df, names); f_col = obj.findAtabColumn_ (atab, {"F"}); p_col = obj.findAtabColumn_ (atab, {"pValue", "Prob>F"}); f_stat = term_ms ./ denominator_ms; p_value = fcdf (f_stat, term_df, denominator_df, "upper"); atab(2:numel (term_ms) + 1, f_col) = num2cell (f_stat); atab(2:numel (term_ms) + 1, p_col) = num2cell (p_value); endfunction function is_random = randomTermMask_ (obj, terms, stats_) membership = terms > 0; if (isfield (stats_, "vnested") && ! isempty (stats_.vnested)) for factor = 1:columns (membership) parents = find (stats_.vnested(factor,:)); if (! isempty (parents)) membership(:, parents) |= membership(:, factor); endif endfor endif is_random = any (membership(:, obj.RandomFactors), 2); endfunction function [included, excluded] = emsModels_ (obj, X, blocks, terms, ... term, sstype) nterms = numel (blocks); switch (sstype) case 1 included_terms = 1:term; excluded_terms = 1:term-1; case 2 factors = find (terms(term, :)); excluded_terms = find (any (terms(:, factors) ... != terms(term, factors), 2))'; included_terms = [term, excluded_terms]; case "h" factors = find (terms(term, :)); excluded_terms = find (any (terms(:, factors) ... < terms(term, factors), 2))'; included_terms = [term, excluded_terms]; otherwise included_terms = 1:nterms; excluded_terms = setdiff (included_terms, term, "stable"); endswitch included = X(:, [1, blocks{included_terms}]); excluded = X(:, [1, blocks{excluded_terms}]); endfunction function value = projectionDifference_ (obj, included, excluded, basis) value = obj.projectionEnergy_ (included, basis) ... - obj.projectionEnergy_ (excluded, basis); value = max (value, 0); endfunction function value = projectionEnergy_ (obj, design, basis) Q = orth (full (design)); value = sum (sumsq (Q' * basis)); endfunction function [q_coeff, variance_coeff] = oneWayEmsCoefficient_ (obj) if (isvector (obj.Response)) group = obj.GROUP(:); else [n, groups] = size (obj.Response); group = reshape (repmat (1:groups, n, 1), [], 1); endif [~, ~, ids] = unique (group, "stable"); Z = sparse (1:numel (ids), ids, 1); intercept = ones (rows (Z), 1); df = columns (Z) - 1; if (df == 0) q_coeff = 0; else q_coeff = obj.projectionDifference_ (Z, intercept, Z) / df; endif variance_coeff = q_coeff; endfunction function value = emsTerm_ (obj, coefficient, symbol, name) if (! isfinite (coefficient) || abs (coefficient) <= 1e-10) value = ""; elseif (abs (coefficient - 1) <= 1e-10) value = sprintf ("%s(%s)", symbol, name); else value = sprintf ("%.6g*%s(%s)", coefficient, symbol, name); endif endfunction function validateSpec_ (obj) if (! ((isnumeric (obj.SSType) && isscalar (obj.SSType) ... && any (obj.SSType == [1, 2, 3])) ... || (ischar (obj.SSType) && strcmp (obj.SSType, "h")))) error (strcat ("anova: SumOfSquaresType must be 'one',", ... " 'two', 'three', or 'hierarchical'.")); endif if (! (isnumeric (obj.Alpha) && isscalar (obj.Alpha) ... && obj.Alpha > 0 && obj.Alpha < 1)) error ("anova: Alpha must be a numeric scalar in (0, 1)."); endif if (! ischar (obj.Display) ... || ! any (strcmpi (obj.Display, {'on', 'off'}))) error ("anova: Display must be 'on' or 'off'."); endif if (ischar (obj.ModelType)) if (! any (strcmpi (obj.ModelType, ... {'linear', 'interaction', 'full'}))) error (strcat ("anova: ModelSpecification must be 'linear',", ... " 'interactions', 'purequadratic', 'quadratic',", ... " 'polyIJK', 'full', or a terms matrix.")); endif elseif (! isnumeric (obj.ModelType)) error (strcat ("anova: ModelSpecification must be a string", ... " or a numeric terms matrix.")); endif if (isnumeric (obj.ModelType) && isscalar (obj.ModelType) ... && (obj.ModelType != fix (obj.ModelType) || obj.ModelType < 1)) error (strcat ("anova: integer ModelSpecification must be a", ... " positive integer.")); endif if (isnumeric (obj.ModelType) && ! isscalar (obj.ModelType) ... && (any (! isfinite (obj.ModelType(:))) ... || any (obj.ModelType(:) < 0) ... || any (obj.ModelType(:) != fix (obj.ModelType(:))))) error (strcat ("anova: terms matrix entries must be nonnegative", ... " integers.")); endif if (! iscellstr (obj.VarNames)) error ("anova: FactorNames must be a character vector or cellstr."); endif if (! obj.isName_ (obj.ResponseName)) error ("anova: ResponseName must be a character vector."); endif if (! isempty (obj.Continuous) && ! isnumeric (obj.Continuous)) error (strcat ("anova: CategoricalFactors must be 'all' or a", ... " numeric index vector.")); endif if (! isempty (obj.Continuous) ... && (! isvector (obj.Continuous) ... || any (obj.Continuous != fix (obj.Continuous)) ... || any (obj.Continuous < 1))) error ("anova: CategoricalFactors must contain valid factor indices."); endif if (! isempty (obj.Weights) && ! isnumeric (obj.Weights)) error ("anova: Weights must be numeric."); endif if (! isempty (obj.reps_) ... && ! (isnumeric (obj.reps_) && isscalar (obj.reps_) ... && obj.reps_ > 0 && obj.reps_ == fix (obj.reps_))) error ("anova: Reps must be a positive integer scalar."); endif endfunction function validateData_ (obj) nobs = numel (obj.Y); if (! isempty (obj.GROUP)) obj.validateGroupLength_ (obj.GROUP, nobs); endif if (! isempty (obj.Weights) && numel (obj.Weights) != nobs) error ("anova: Weights must have one value per observation."); endif if (! isempty (obj.Continuous) && any (obj.Continuous > obj.nFactors_)) error ("anova: CategoricalFactors must contain valid factor indices."); endif if (! isempty (obj.RandomFactors) ... && any (obj.RandomFactors > obj.nFactors_)) error ("anova: RandomFactors indices exceed the number of factors."); endif if (isnumeric (obj.ModelType) && ! isempty (obj.ModelType) ... && ! isscalar (obj.ModelType) ... && columns (obj.ModelType) != obj.nFactors_) error ("anova: terms matrix must have one column per factor."); endif if (isnumeric (obj.ModelType) && ! isscalar (obj.ModelType)) powered = find (any (obj.ModelType > 1, 1)); if (any (ismember (powered, obj.CategoricalFactors))) error (strcat ("anova: polynomial powers require continuous", ... " factors.")); endif endif endfunction function validateGroupLength_ (obj, group, nobs) if (iscell (group) ... && all (cellfun (@(c) isvector (c) || ischar (c), group(:))) ... && size (group, 1) == 1) for k = 1:numel (group) if (ischar (group{k}) && rows (group{k}) > 1) count = rows (group{k}); else count = numel (group{k}); endif if (count != nobs) error ("anova: GROUP variables must match the number of observations."); endif endfor elseif (isvector (group)) if (numel (group) != nobs) error ("anova: GROUP must match the number of observations."); endif elseif (rows (group) != nobs) error ("anova: GROUP must have one row per observation."); endif endfunction ## Infer the number of factors implied by GROUP / Y. ## - Empty GROUP + matrix Y -> 1 (anova1 matrix form) ## - Empty GROUP + vector Y -> 0 (intercept-only, falls to anovan) ## - GROUP is a cell of vectors -> numel (GROUP) ## - GROUP is a 2-D numeric / cell matrix -> size (GROUP, 2) function nf = countFactors_ (obj) if (isempty (obj.GROUP)) if (ismatrix (obj.Response) && ! isvector (obj.Response)) if (isempty (obj.reps_)) nf = 1; else nf = 2; endif else nf = 0; endif return; endif if (iscell (obj.GROUP) ... && all (cellfun (@(c) isvector (c) || ischar (c), obj.GROUP(:))) ... && size (obj.GROUP, 1) == 1) nf = numel (obj.GROUP); else nf = size (obj.GROUP, 2); endif endfunction ## Backend heuristic. ## anova2 : user passed 'reps' AND Y is a non-vector matrix ## (Y carries the factor structure; reps is required) ## anova1 : 1 factor, no continuous, no weights, SSType == 3 ## anovan : everything else (full generality) function obj = selectBackend_ (obj) anova2_model = ischar (obj.ModelType) ... && any (strcmpi (obj.ModelType, ... {"linear", "interaction", "full"})); ## anova2 requires a balanced design and rejects missing observations, ## so data holding any NaN is fitted through anovan, which omits them. if (! isempty (obj.reps_) && ismatrix (obj.Response) ... && ! isvector (obj.Response) ... && ! any (isnan (obj.Response(:))) ... && isempty (obj.Continuous) && isempty (obj.Weights) ... && isempty (obj.RandomFactors) ... && (isempty (obj.nesting_) || ! any (obj.nesting_(:))) ... && anova2_model) obj.backend_ = 'anova2'; elseif (obj.nFactors_ == 1 && isempty (obj.Continuous) ... && isempty (obj.Weights) && obj.SSType == 3) obj.backend_ = 'anova1'; else obj.backend_ = 'anovan'; endif endfunction ## Lazy refit guard: only fits when never-fit or spec changed. function obj = ensureFit_ (obj) if (! obj.fitted_ || obj.dirty_) obj = obj.selectBackend_ (); obj = obj.fit_ (); endif endfunction ## Dispatch to the selected backend; populate result properties. function obj = fit_ (obj) switch (obj.backend_) case 'anova1' obj = obj.fitAnova1_ (); case 'anova2' obj = obj.fitAnova2_ (); case 'anovan' obj = obj.fitAnovan_ (); endswitch obj = obj.updatePublicResults_ (); obj.fitted_ = true; obj.dirty_ = false; endfunction function obj = fitAnova1_ (obj) ## Run the backend silently; this class owns display and plotting. if (isvector (obj.Response)) [~, atab, stats] = anova1 (obj.Response, obj.GROUP, 'off'); else [~, atab, stats] = anova1 (obj.Response, [], 'off'); endif obj.AnovaTable = atab; obj.Stats = stats; obj.DFE = stats.df; obj.MSE = stats.s ^ 2; ## anova1 reports sqrt(MSE) as s ## Coefficients / Residuals / DesignMatrix / FittedValues are not ## exposed by anova1's stats; they remain at their empty defaults. endfunction function obj = fitAnova2_ (obj) modelarg = 'interaction'; if (ischar (obj.ModelType)) modelarg = obj.ModelType; endif [~, atab, stats] = anova2 (obj.Response, obj.reps_, 'off', ... modelarg); obj.AnovaTable = atab; obj.Stats = stats; obj.DFE = stats.df; obj.MSE = stats.sigmasq; ## Balanced anova2 does not expose coefficients or residuals. endfunction function obj = fitAnovan_ (obj) [y_vec, group_arg] = obj.anovanData_ (); [~, atab, stats] = anovan (y_vec, group_arg, ... obj.buildAnovanArgs_(){:}); obj.AnovaTable = obj.applyRandomInference_ (atab, stats, obj.SSType); obj.Stats = stats; if (isfield (stats, 'coeffs')) obj.coefficientStats_ = stats.coeffs; obj.Coefficients = stats.coeffs(:, 1); endif if (isfield (stats, 'resid')) obj.rawResiduals_ = stats.resid; endif if (isfield (stats, 'X')) obj.DesignMatrix = stats.X; endif if (isfield (stats, 'dfe')) obj.DFE = stats.dfe; endif if (isfield (stats, 'mse')) obj.MSE = stats.mse; endif ## anovan omits any row holding a missing value, so adopt the ## observations it actually fitted whenever it dropped some. if (isfield (stats, 'Y') && numel (stats.Y) != numel (obj.Y)) obj.Y = stats.Y(:); obj.Response = obj.Y; obj.NumObservations = numel (obj.Y); if (isfield (stats, 'grps') ... && columns (stats.grps) == numel (obj.VarNames) ... && rows (stats.grps) == obj.NumObservations) obj.GROUP = num2cell (stats.grps, 1); obj.Factors = table (obj.GROUP{:}, 'VariableNames', obj.VarNames); endif endif if (! isempty (obj.DesignMatrix) && ! isempty (obj.Coefficients)) obj.FittedValues = full (obj.DesignMatrix) * obj.Coefficients; obj.rawResiduals_ = obj.Y - obj.FittedValues; endif if (! isempty (obj.rawResiduals_)) obj.Residuals = obj.residualTable_ (obj.rawResiduals_, obj.MSE); endif endfunction function atab = componentStats_ (obj, sstype) [atab] = obj.componentFit_ (sstype); endfunction function [atab, stats_] = componentFit_ (obj, sstype) if (strcmp (obj.backend_, "anova2")) obj.ensureFit_ (); atab = obj.AnovaTable; stats_ = obj.Stats; return; endif [y_vec, group_arg] = obj.anovanData_ (); args = obj.buildAnovanArgs_ (sstype); [~, atab, stats_] = anovan (y_vec, group_arg, args{:}); atab = obj.applyRandomInference_ (atab, stats_, sstype); endfunction function atab = summaryStats_ (obj) obj.ensureFit_ (); component = obj.componentStats_ (1); sources = component(2:end, 1); error_idx = find (strcmpi (sources, "Error"), 1); total_idx = find (strcmpi (sources, "Total"), 1); term_idx = setdiff (1:numel (sources), [error_idx, total_idx], "stable"); term_ss = cell2mat (component(term_idx + 1, 2)); term_df = cell2mat (component(term_idx + 1, 3)); if (strcmp (obj.backend_, "anova2")) nonlinear = strcmpi (sources(term_idx), "Interaction"); elseif (isfield (obj.Stats, "terms") ... && rows (obj.Stats.terms) == numel (term_idx)) nonlinear = (sum (obj.Stats.terms, 2) > 1); else nonlinear = ! cellfun (@isempty, regexp (sources(term_idx), "[:*]")); endif linear = ! nonlinear; labels = {}; sum_sq = []; df = []; if (any (linear)) labels{end + 1, 1} = "Linear"; sum_sq(end + 1, 1) = sum (term_ss(linear)); df(end + 1, 1) = sum (term_df(linear)); endif if (any (nonlinear)) labels{end + 1, 1} = "NonLinear"; sum_sq(end + 1, 1) = sum (term_ss(nonlinear)); df(end + 1, 1) = sum (term_df(nonlinear)); endif labels{end + 1, 1} = "Regression"; sum_sq(end + 1, 1) = sum (term_ss); df(end + 1, 1) = sum (term_df); tested_rows = numel (labels); error_ss = component{error_idx + 1, 2}; error_df = component{error_idx + 1, 3}; labels{end + 1, 1} = "Error"; sum_sq(end + 1, 1) = error_ss; df(end + 1, 1) = error_df; error_row = numel (labels); lack_row = []; if (strcmp (obj.backend_, "anovan") && isempty (obj.Weights) ... && isfield (obj.Stats, "grps") && isfield (obj.Stats, "Y")) [~, ~, group_id] = unique (obj.Stats.grps, "rows"); group_mean = accumarray (group_id, obj.Stats.Y, [], @mean); pure_ss = sum ((obj.Stats.Y - group_mean(group_id)) .^ 2); pure_df = numel (obj.Stats.Y) - max (group_id); lack_df = error_df - pure_df; if (pure_df > 0 && lack_df > 0) labels(end + 1:end + 2, 1) = {"LackOfFit"; "PureError"}; sum_sq(end + 1:end + 2, 1) = [max(error_ss - pure_ss, 0); pure_ss]; df(end + 1:end + 2, 1) = [lack_df; pure_df]; lack_row = numel (labels) - 1; endif endif labels{end + 1, 1} = "Total"; sum_sq(end + 1, 1) = component{total_idx + 1, 2}; df(end + 1, 1) = component{total_idx + 1, 3}; mean_sq = sum_sq ./ df; mse = mean_sq(error_row); f_stat = mean_sq(1:tested_rows) ./ mse; p_value = fcdf (f_stat, df(1:tested_rows), error_df, "upper"); atab = cell (numel (labels) + 1, 6); atab(1, :) = {"Source", "Sum Sq.", "d.f.", "Mean Sq.", "F", "Prob>F"}; atab(2:end, 1) = labels; atab(2:end, 2:4) = num2cell ([sum_sq, df, mean_sq]); atab(2:tested_rows + 1, 5:6) = num2cell ([f_stat, p_value]); if (! isempty (lack_row)) lack_f = mean_sq(lack_row) / mean_sq(lack_row + 1); lack_p = fcdf (lack_f, df(lack_row), df(lack_row + 1), "upper"); atab(lack_row + 1, 5:6) = {lack_f, lack_p}; endif endfunction function [y_vec, group_arg] = anovanData_ (obj) if (! isempty (obj.reps_) && isempty (obj.GROUP) ... && ! isvector (obj.Response)) [y_vec, group_arg] = obj.anova2Data_ (); elseif (isempty (obj.GROUP) && ! isvector (obj.Response)) [n, m] = size (obj.Response); y_vec = obj.Response(:); group = reshape (repmat (1:m, n, 1), [], 1); group_arg = {group}; else y_vec = obj.Response(:); group_arg = obj.GROUP; if (isempty (group_arg)) group_arg = {}; endif endif endfunction function s = sstypeLabel_ (obj) switch (obj.SSType) case 1; s = "I"; case 2; s = "II"; case 3; s = "III"; otherwise; s = "hierarchical"; endswitch endfunction function printAtab_ (obj, atab) [nrows, ncols] = size (atab); col_w = max (12, ceil (80 / max (ncols, 1))); for j = 1:ncols fprintf ("%-*s", col_w, char (atab{1, j})); endfor fprintf ("\n%s\n", repmat ("-", 1, col_w * ncols)); for i = 2:nrows for j = 1:ncols v = atab{i, j}; if (ischar (v)) fprintf ("%-*s", col_w, v); elseif (isnumeric (v) && ! isempty (v) && isscalar (v)) if (isnan (v)) fprintf ("%-*s", col_w, "NaN"); elseif (v == fix (v) && abs (v) < 1e6) fprintf ("%-*d", col_w, v); else fprintf ("%-*.*g", col_w, 5, v); endif else fprintf ("%-*s", col_w, ""); endif endfor fprintf ("\n"); endfor endfunction function nv = buildAnovanArgs_ (obj, sstype) if (nargin < 2) sstype = obj.SSType; endif ## Run the backend silently; this class owns display and plotting. nv = {'display', 'off', 'sstype', sstype, 'alpha', obj.Alpha}; if (ischar (obj.ModelType) || isnumeric (obj.ModelType)) if (! (isnumeric (obj.ModelType) && isempty (obj.ModelType))) nv = [nv, {'model', obj.ModelType}]; endif endif if (! isempty (obj.VarNames)) nv = [nv, {'varnames', obj.VarNames}]; endif if (! isempty (obj.Continuous)) nv = [nv, {'continuous', obj.Continuous}]; endif if (! isempty (obj.nesting_) && any (obj.nesting_(:))) nv = [nv, {'nested', obj.nesting_}]; endif ## Random terms are retained so their expected mean squares can provide ## the correct F denominators and variance component estimates. if (! isempty (obj.Weights)) nv = [nv, {'weights', obj.Weights}]; endif if (! isempty (obj.Contrasts)) nv = [nv, {'contrasts', obj.Contrasts}]; endif endfunction function h = leverage_ (obj) X = full (obj.DesignMatrix); Q = qr (X, 0); h = sum (Q .^ 2, 2); endfunction function D = cooksDistance_ (obj, leverage) p = max (columns (obj.DesignMatrix), 1); D = (obj.rawResiduals_ .^ 2 ./ max (p * obj.MSE, eps)) ... .* leverage ./ max ((1 - leverage) .^ 2, eps); endfunction function residuals = residualTable_ (obj, raw, mse) pearson = raw ./ sqrt (mse); residuals = table (raw(:), pearson(:), "VariableNames", ... {"Raw", "Pearson"}); endfunction function obj = updatePublicResults_ (obj) if (strcmp (obj.backend_, "anovan") ... && ! isempty (obj.coefficientStats_)) [obj.Coefficients, names] = obj.expandedCoefficients_ (); obj.ExpandedFactorNames = string (names); elseif (strcmp (obj.backend_, "anova1")) obj = obj.populateOneWayResults_ (); elseif (isfield (obj.Stats, "coeffnames") ... && numel (obj.Stats.coeffnames) == numel (obj.Coefficients)) obj.ExpandedFactorNames = string (obj.Stats.coeffnames(:)); elseif (! isempty (obj.Coefficients)) names = arrayfun (@(k) sprintf ("Coefficient%d", k), ... (1:numel (obj.Coefficients))', ... "UniformOutput", false); obj.ExpandedFactorNames = string (names); endif if (isempty (obj.AnovaTable) || isempty (obj.MSE)) return; endif source = obj.findAtabColumn_ (obj.AnovaTable, {"Source"}); ss = obj.findAtabColumn_ (obj.AnovaTable, ... {"SumOfSquares", "Sum Sq.", "Sum Sq", ... "SS"}); df = obj.findAtabColumn_ (obj.AnovaTable, {"DF", "d.f.", "df"}); names = obj.AnovaTable(2:end, source); error_row = find (strcmpi (names, "Error"), 1) + 1; total_row = find (strcmpi (names, "Total"), 1) + 1; if (isempty (error_row) || isempty (total_row)) return; endif sse = obj.AnovaTable{error_row, ss}; sst = obj.AnovaTable{total_row, ss}; total_df = obj.AnovaTable{total_row, df}; if (isnumeric (total_df) && isscalar (total_df) && isfinite (total_df)) obj.NumObservations = total_df + 1; endif ssr = sst - sse; if (sst == 0) rsquared = NaN; adjusted = NaN; else rsquared = ssr / sst; adjusted = 1 - (obj.NumObservations - 1) * sse ... / (obj.DFE * sst); endif obj.Metrics = table (obj.MSE, sqrt (obj.MSE), sse, ssr, sst, ... rsquared, adjusted, "VariableNames", ... {"MSE", "RMSE", "SSE", "SSR", "SST", ... "RSquared", "AdjustedRSquared"}); endfunction function [coefficients, names] = expandedCoefficients_ (obj) coefficients = obj.coefficientStats_(1, 1); names = {"(Intercept)"}; first = 2; for term = 1:rows (obj.Stats.terms) factors = find (obj.Stats.terms(term, :)); mapping = 1; expanded_names = {""}; for factor = factors if (any (factor == obj.Continuous)) contrast = 1; factor_names = {obj.VarNames{factor}}; elseif (isfield (obj.Stats, "vnested") ... && any (obj.Stats.vnested(factor,:))) [contrast, factor_names] = ... obj.nestedCoefficientMap_ (factor); else contrast = obj.Stats.contrasts{factor}; levels = obj.Stats.grpnames{factor}; factor_names = cellfun (@(x) sprintf ("(%s==%s)", ... obj.VarNames{factor}, ... obj.levelText_ (x)), levels, ... "UniformOutput", false); endif mapping = kron (mapping, contrast); combined = cell (numel (expanded_names) * numel (factor_names), 1); cursor = 1; for left = 1:numel (expanded_names) for right = 1:numel (factor_names) if (isempty (expanded_names{left})) combined{cursor} = factor_names{right}; else combined{cursor} = sprintf ("%s:%s", ... expanded_names{left}, ... factor_names{right}); endif cursor += 1; endfor endfor expanded_names = combined; endfor width = obj.Stats.df(term); compact = obj.coefficientStats_(first:first + width - 1, 1); coefficients = [coefficients; mapping * compact]; names = [names; expanded_names]; first += width; endfor endfunction function [mapping, names] = nestedCoefficientMap_ (obj, factor) parents = find (obj.Stats.vnested(factor,:)); [parent_codes, ~, parent_id] = unique (... obj.Stats.grps(:, parents), "rows", "stable"); mapping = zeros (0, 0); names = {}; for parent = 1:rows (parent_codes) rows_ = parent_id == parent; child_codes = unique (obj.Stats.grps(rows_, factor), "stable"); nlevels = numel (child_codes); contrast = [zeros(1, nlevels - 1); eye(nlevels - 1)] ... - 1 / nlevels; mapping = blkdiag (mapping, contrast); for child = child_codes(:)' pieces = cell (1, numel (parents) + 1); pieces{1} = sprintf ("(%s==%s)", obj.VarNames{factor}, ... obj.levelText_ (... obj.Stats.grpnames{factor}{child})); for k = 1:numel (parents) code = parent_codes(parent, k); pieces{k + 1} = sprintf ("(%s==%s)", ... obj.VarNames{parents(k)}, ... obj.levelText_ (... obj.Stats.grpnames{parents(k)}{code})); endfor names{end + 1, 1} = strjoin (pieces, ":"); endfor endfor endfunction function obj = populateOneWayResults_ (obj) means = obj.Stats.means(:); intercept = mean (means); obj.Coefficients = [intercept; means - intercept]; level_names = cellfun (@(x) sprintf ("(%s==%s)", ... obj.VarNames{1}, obj.levelText_ (x)), ... obj.Stats.gnames(:), "UniformOutput", false); obj.ExpandedFactorNames = string ([{"(Intercept)"}; level_names]); if (isvector (obj.Response)) group_id = grp2idx (obj.GROUP); fitted = NaN (size (group_id)); grouped = isfinite (group_id) & group_id > 0; fitted(grouped) = means(group_id(grouped)); else fitted = repmat (means', rows (obj.Response), 1); fitted = fitted(:); endif valid = isfinite (obj.Y) & isfinite (fitted); obj.FittedValues = NaN (size (obj.Y)); obj.FittedValues(valid) = fitted(valid); obj.rawResiduals_ = obj.Y - obj.FittedValues; obj.Residuals = obj.residualTable_ (obj.rawResiduals_, obj.MSE); endfunction function text = levelText_ (obj, value) if (isnumeric (value) || islogical (value)) text = num2str (value); elseif (isstring (value)) text = char (value); else text = value; endif endfunction function h = plotDiagnostics_ (obj, residuals, fitted, leverage, ... cooksd, dfe, varargin) if (isempty (residuals) || isempty (fitted) || isempty (leverage) ... || isempty (cooksd)) error ("anova.plotDiagnostics: diagnostic inputs must be non-empty."); endif residuals = residuals(:); fitted = fitted(:); leverage = leverage(:); cooksd = cooksd(:); n = numel (residuals); if (numel (fitted) != n || numel (leverage) != n ... || numel (cooksd) != n) error ("anova.plotDiagnostics: diagnostic inputs must match."); endif fig_name = 'Diagnostic Plots: Model Residuals'; visible = 'on'; if (mod (numel (varargin), 2) != 0) error ("anova.plotDiagnostics: name-value pairs must come in pairs."); endif for k = 1:2:numel (varargin) switch (lower (varargin{k})) case 'figurename' fig_name = varargin{k + 1}; case 'visible' visible = varargin{k + 1}; otherwise error ("anova.plotDiagnostics: unknown option '%s'.", varargin{k}); endswitch endfor mse = sum (residuals .^ 2) / max (dfe, 1); t = residuals ./ sqrt (mse * max (1 - leverage, eps)); [~, DI] = sort (cooksd, 'descend'); nk = min (4, n); h = figure ('Name', fig_name, 'Visible', visible); subplot (2, 2, 1); x = ((1:n)' - 0.5) / n; [ts, I] = sort (t); q = norminv (x); plot (q, ts, 'ok', 'markersize', 3); box off; grid on; xlabel ('Theoretical quantiles'); ylabel ('Studentized residuals'); title ('Normal Q-Q Plot'); arrayfun (@(i) text (q(I == DI(i)), t(DI(i)), ... sprintf (" %u", DI(i))), 1:nk); iqr = [0.25; 0.75]; yl = quantile (t, iqr, 1, 6); xl = norminv (iqr); slope = diff (yl) / diff (xl); int = yl(1) - slope * xl(1); ax1_xlim = get (gca, 'XLim'); hold on; plot (ax1_xlim, slope * ax1_xlim + int, 'k-'); hold off; set (gca, 'Xlim', ax1_xlim); subplot (2, 2, 2); plot (fitted, sqrt (abs (t)), 'ko', 'markersize', 3); box off; xlabel ('Fitted values'); ylabel ('sqrt ( | Studentized residuals | )'); title ('Spread-Location Plot'); ax2_xlim = get (gca, 'XLim'); hold on; plot (ax2_xlim, ones (1, 2) * sqrt (2), 'k:'); plot (ax2_xlim, ones (1, 2) * sqrt (3), 'k-.'); plot (ax2_xlim, ones (1, 2) * sqrt (4), 'k--'); hold off; arrayfun (@(i) text (fitted(DI(i)), sqrt (abs (t(DI(i)))), ... sprintf (" %u", DI(i))), 1:nk); xlim (ax2_xlim); subplot (2, 2, 3); plot (leverage, t, 'ko', 'markersize', 3); box off; xlabel ('Leverage'); ylabel ('Studentized residuals'); title ('Residual-Leverage Plot'); ax3_xlim = get (gca, 'XLim'); ax3_ylim = get (gca, 'YLim'); hold on; plot (ax3_xlim, zeros (1, 2), 'k-'); hold off; arrayfun (@(i) text (leverage(DI(i)), t(DI(i)), ... sprintf (" %u", DI(i))), 1:nk); set (gca, 'ygrid', 'on'); xlim (ax3_xlim); ylim (ax3_ylim); subplot (2, 2, 4); stem (cooksd, 'ko', 'markersize', 3); box off; xlabel ('Obs. number'); ylabel ('Cook''s distance'); title ('Cook''s Distance Stem Plot'); xlim ([0, n]); ax4_xlim = get (gca, 'XLim'); ax4_ylim = get (gca, 'YLim'); hold on; plot (ax4_xlim, ones (1, 2) * 4 / max (dfe, eps), 'k:'); plot (ax4_xlim, ones (1, 2) * 0.5, 'k-.'); plot (ax4_xlim, ones (1, 2), 'k--'); hold off; arrayfun (@(i) text (DI(i), cooksd(DI(i)), ... sprintf (" %u", DI(i))), 1:nk); xlim (ax4_xlim); ylim (ax4_ylim); set (findall (gcf, '-property', 'FontSize'), 'FontSize', 7); endfunction function es = effectSizesFromAtab_ (obj) atab = obj.AnovaTable; if (isempty (atab)) error ("anova.getEffectSizes: model has no ANOVA table."); endif source_col = obj.findAtabColumn_ (atab, {'Source'}); ss_col = obj.findAtabColumn_ (atab, {'SS', 'Sum Sq.', 'Sum Sq'}); df_col = obj.findAtabColumn_ (atab, {'df', 'd.f.'}); sources = {}; ss = []; df = []; sse = []; sst = []; for r = 2:rows (atab) name = atab{r, source_col}; if (! ischar (name)) continue; endif val_ss = atab{r, ss_col}; val_df = atab{r, df_col}; if (strcmpi (name, 'Error')) sse = val_ss; elseif (strcmpi (name, 'Total')) sst = val_ss; elseif (isnumeric (val_ss) && isscalar (val_ss)) sources{end + 1} = name; ss(end + 1, 1) = val_ss; df(end + 1, 1) = val_df; endif endfor if (isempty (sst)) sst = sum (ss) + ifelse (isempty (sse), 0, sse); endif if (isempty (sse)) sse = max (sst - sum (ss), 0); endif eta = ss ./ max (sst, eps); partial_eta = ss ./ max (ss + sse, eps); omega = (ss - df .* obj.MSE) ./ max (sst + obj.MSE, eps); es = struct (); es.Source = sources; es.EtaSquared = eta; es.PartialEtaSquared = partial_eta; es.OmegaSquared = omega; endfunction function idx = findAtabColumn_ (obj, atab, names) idx = []; for k = 1:numel (names) hit = find (strcmpi (atab(1, :), names{k}), 1); if (! isempty (hit)) idx = hit; return; endif endfor error ("anova: ANOVA table is missing the '%s' column.", names{1}); endfunction endmethods endclassdef %!demo %! ## Fit an ANOVA object and inspect component and summary statistics %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! aov = anova (g, y, 'FactorNames', {'Treatment'}); %! component = stats (aov) %! summary_table = stats (aov, 'summary') %!demo %! ## Estimate group means and perform post-hoc comparisons %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! aov = anova (g, y, 'SumOfSquaresType', 'two'); %! means = groupmeans (aov) %! comparisons = multcompare (aov, 'display', 'off') %!demo %! ## Plot multiple-comparison intervals %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! aov = anova (g, y, 'SumOfSquaresType', 'two'); %! plotComparisons (aov); ## --- BISTs --------------------------------------------------------------- ## Basic construction: vector Y + single grouping vector %!test %! y = [1; 2; 3; 4; 5; 6]; %! g = [1; 1; 2; 2; 3; 3]; %! a = anova (g, y); %! assert_equal (class (a), 'anova'); %! assert_equal (a.Y, y); %! assert_equal (a.GROUP, g); ## Basic construction: matrix Y, no GROUP %!test %! y = magic (4); %! a = anova (y); %! assert_equal (class (a), "anova"); %! assert_equal (a.Y, y(:)); %! assert_equal (a.GROUP, []); ## Basic construction: cell of two grouping vectors %!test %! y = (1:12)'; %! g1 = repmat ([1;2;3], 4, 1); %! g2 = repmat ([1;1;2;2], 3, 1); %! a = anova ({g1, g2}, y); %! assert_equal (class (a), 'anova'); %! assert_equal (a.NumFactors, 2); ## Property defaults %!test %! a = anova ([1;1;2;2], [1;2;3;4]); %! assert_equal (a.ModelSpecification, 'linear'); %! assert_equal (char (a.SumOfSquaresType), 'three'); %! assert_equal (a.ResponseName, 'Y'); %! assert_equal (cellstr (a.FactorNames), {'Factor1'}); %! assert_equal (a.RandomFactors, []); %! assert_equal (a.CategoricalFactors, 1); %! assert_equal (a.NumFactors, 1); %! assert_equal (a.NumObservations, 4); ## Eager one-way fits populate the derived public result properties. %!test %! a = anova ([1;1;2;2], [1;2;3;4]); %! assert_equal (a.Coefficients, [2.5; -1; 1], 1e-12); %! assert_equal (cellstr (a.ExpandedFactorNames), ... %! {'(Intercept)'; '(Factor1==1)'; '(Factor1==2)'}); %! assert_equal (a.FittedValues, [1.5; 1.5; 3.5; 3.5], 1e-12); %! assert_equal (a.Residuals.Raw, [-0.5; 0.5; -0.5; 0.5], 1e-12); %! assert_equal (a.Residuals.Pearson, ... %! [-1; 1; -1; 1] / sqrt (2), 1e-12); %! assert_equal (a.DesignMatrix, []); ## Name-value parsing: MATLAB-compatible names %!test %! y = (1:12)'; %! g1 = repmat ([1;2;3], 4, 1); %! g2 = repmat ([1;1;2;2], 3, 1); %! a = anova ({g1, g2}, y, 'SumOfSquaresType', 'two', ... %! 'ModelSpecification', 'full', 'FactorNames', {'A', 'B'}); %! assert_equal (char (a.SumOfSquaresType), 'two'); %! assert_equal (a.ModelSpecification, 'full'); %! assert_equal (cellstr (a.FactorNames), {'A', 'B'}); ## Name-value parsing: case-insensitive names, displayopt alias %!test %! a = anova ([1;1;2;2], [1;2;3;4], 'SumOfSquaresType', 'one', 'displayopt', 'on'); %! assert_equal (char (a.SumOfSquaresType), 'one'); ## Display 'on' never reaches a backend: no fit prints a table or opens a ## figure. The three constructions select the anova1, anova2, and anovan ## backends respectively. %!test %! y = [1; 2; 3; 4]; %! g = [1; 1; 2; 2]; %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! figs = get (0, 'children'); %! cmd = strcat ("a1 = anova (g, y, 'Display', 'on');", ... %! "a2 = anova (popcorn, [], 'reps', 3, 'Display', 'on');", ... %! "a3 = anova (g, y, 'SumOfSquaresType', 'one',", ... %! " 'Display', 'on');"); %! str = evalc (cmd); %! assert_equal (isempty (str), true); %! assert_equal (isempty (setdiff (get (0, 'children'), figs)), true); ## Backend selection: one-way default -> anova1 %!test %! a = anova ([1;1;2;2;3;3], [1;2;3;4;5;6]); %! assert_equal (isempty (strfind (evalc ('disp (a)'), '1-way anova')), false); ## Backend selection: one-way matrix-Y form -> anova1 %!test %! a = anova (magic (4)); %! str = evalc ('disp (a)'); %! assert_equal (isempty (strfind (str, '1-way anova')), false); ## Backend selection: matrix Y + explicit 'reps' -> anova2 %!test %! y = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! a = anova (y, [], 'reps', 3); %! assert_equal (isempty (strfind (evalc ('disp (a)'), '2-way anova')), false); ## Backend selection: two-factor cell groups without reps -> anovan %!test %! y = (1:12)'; %! g1 = repmat ([1;2;3], 4, 1); %! g2 = repmat ([1;1;2;2], 3, 1); %! a = anova ({g1, g2}, y); %! str = evalc ('disp (a)'); %! assert_equal (isempty (strfind (str, '2-way anova')), false); ## Backend selection: three factors -> anovan %!test %! y = (1:24)'; %! g1 = repmat ([1;2], 12, 1); %! g2 = repmat ([1;1;2;2], 6, 1); %! g3 = repmat ([1;1;1;1;2;2;2;2], 3, 1); %! a = anova ({g1, g2, g3}, y); %! str = evalc ('disp (a)'); %! assert_equal (isempty (strfind (str, '3-way anova')), false); ## Backend selection: SSType != 3 with 1 factor falls through to anovan %!test %! a = anova ([1;1;2;2;3;3], [1;2;3;4;5;6], 'SumOfSquaresType', 'two'); %! assert_equal (isempty (strfind (evalc ('disp (a)'), 'Type II')), false); ## Backend selection: continuous predictors force anovan %!test %! y = (1:12)'; %! g1 = repmat ([1;2;3], 4, 1); %! g2 = (1:12)'; ## continuous %! a = anova ({g1, g2}, y, 'CategoricalFactors', 1); %! assert_equal (a.CategoricalFactors, 1); ## Backend selection: NaN in a vector response falls through to anovan %!test %! y = [1; 2; 3; NaN; 5; 6; 7; 8]; %! g1 = [1; 2; 1; 2; 1; 2; 1; 2]; %! g2 = [1; 1; 2; 2; 1; 1; 2; 2]; %! a = anova ({g1, g2}, y); %! assert_equal (a.NumFactors, 2); %! assert_equal (a.NumObservations, 7); %! assert_equal (a.Stats.source, 'anovan'); ## Backend selection: weights force anovan %!test %! a = anova ([1;1;2;2;3;3], [1;2;3;4;5;6], 'Weights', ones (6, 1)); %! assert_equal (a.Stats.source, 'anovan'); %! assert_equal (stats (a).Properties.RowNames, ... %! {'Factor1'; 'Error'; 'Total'}); ## --- Week 2: fit delegation smoke tests -------------------------------- ## fit_(): one-way fixture populates the unified result surface %!test %! y = [1; 2; 3; 4; 5; 6]; %! g = [1; 1; 2; 2; 3; 3]; %! a = anova (g, y); %! a.fit (); %! assert_equal (size (a.AnovaTable), [4, 6]); %! assert_equal (a.Stats.source, 'anova1'); %! assert_equal (a.DFE, 3); %! assert_equal (a.MSE, 0.5, 1e-12); ## fit_(): two-way balanced fixture (anova2 backend, popcorn data) %!test %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! a = anova (popcorn, [], 'reps', 3); %! assert_equal (! isempty (strfind (evalc ('disp (a)'), '2-way anova')), true); %! a.fit (); %! assert_equal (size (a.AnovaTable), [5, 6]); %! assert_equal (a.MSE, 0.125, 1e-12); %! assert_equal (a.Stats.sigmasq, 0.125, 1e-12); ## fit_(): N-way fixture (anovan backend, three factors) %!test %! y = (1:24)'; %! g1 = repmat ([1;2], 12, 1); %! g2 = repmat ([1;1;2;2], 6, 1); %! g3 = repmat ([1;1;1;1;2;2;2;2], 3, 1); %! a = anova ({g1, g2, g3}, y); %! assert_equal (a.NumFactors, 3); %! a.fit (); %! assert_equal (a.Stats.source, 'anovan'); %! assert_equal (size (a.AnovaTable), [6, 9]); %! assert_equal (size (a.Coefficients), [7, 1]); %! assert_equal (size (a.Residuals), [24, 2]); %! assert_equal (size (a.DesignMatrix), [24, 4]); ## fit_(): FittedValues = DesignMatrix * Coefficients(:,1) for anovan %!test %! y = [10; 12; 11; 14; 16; 15; 9; 8; 10]; %! g = [1;1;1;2;2;2;3;3;3]; %! a = anova (g, y, 'SumOfSquaresType', 'two'); %! a.fit (); %! assert_equal (numel (a.FittedValues), numel (y)); %! assert_equal (a.FittedValues + a.Residuals.Raw, y, 1e-9); ## ensureFit_(): fit() is idempotent (second call does nothing) %!test %! a = anova ([1;1;1;2;2;2;3;3;3], (1:9)'); %! a.fit (); %! first_table = a.AnovaTable; %! a.fit (); %! assert_equal (a.AnovaTable, first_table); ## buildAnovanArgs_(): SSType and Alpha are forwarded to anovan %!test %! y = (1:12)'; %! g = repmat ([1;2;3], 4, 1); %! a = anova ({g}, y, 'SumOfSquaresType', 'two', 'Alpha', 0.10); %! a.fit (); %! assert_equal (char (a.SumOfSquaresType), 'two'); %! assert_equal (a.Stats.alpha, 0.10, 1e-12); ## --- Numeric references ------------------------------------------------- ## One-way ANOVA: reference values match R's aov(y ~ factor(g)). %!test %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! a = anova (g, y, 'SumOfSquaresType', 'two'); %! a.fit (); %! T = a.AnovaTable; %! assert_equal (T{2, 2}, 126, 1e-12); %! assert_equal (T{3, 2}, 6, 1e-12); %! assert_equal (T{4, 2}, 132, 1e-12); %! assert_equal (T{2, 3}, 2); %! assert_equal (T{3, 3}, 6); %! assert_equal (T{2, 5}, 63, 1e-12); %! assert_equal (T{3, 5}, 1, 1e-12); %! assert_equal (T{2, 6}, 63, 1e-12); %! assert_equal (T{2, 7}, 9.3914e-05, 1e-9); %! assert_equal (a.MSE, 1, 1e-12); %! assert_equal (a.DFE, 6); ## Fitted values and residuals: reference values from one-way cell means. %!test %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! a = anova (g, y, 'SumOfSquaresType', 'two'); %! assert_equal (predict (a), [2; 2; 2; 5; 5; 5; 11; 11; 11], 1e-12); %! assert_equal (a.Residuals.Raw, ... %! [-1; 0; 1; -1; 0; 1; -1; 0; 1], 1e-12); ## Effect sizes: reference eta2 = SS/SST, omega2 = (SS-df*MSE)/(SST+MSE). %!test %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! es = getEffectSizes (anova (g, y, 'SumOfSquaresType', 'two')); %! assert_equal (es.Source, {'Factor1'}); %! assert_equal (es.EtaSquared, 126 / 132, 1e-12); %! assert_equal (es.PartialEtaSquared, 126 / (126 + 6), 1e-12); %! assert_equal (es.OmegaSquared, 124 / 133, 1e-12); ## Two-way balanced ANOVA: popcorn values match the anova2 doc example. %!test %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! a = anova (popcorn, [], 'reps', 3); %! a.fit (); %! T = a.AnovaTable; %! assert_equal (T{2, 2}, 15.75, 1e-12); %! assert_equal (T{3, 2}, 4.5, 1e-12); %! assert_equal (T{4, 2}, 1.75, 1e-12); %! assert_equal (T{5, 2}, 22, 1e-12); %! assert_equal (T{2, 5}, 63, 1e-12); %! assert_equal (T{3, 5}, 36, 1e-12); %! assert_equal (a.MSE, 0.125, 1e-12); ## Post-hoc comparisons: reference values checked against R's TukeyHSD output. %!test %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! a = anova (g, y, 'SumOfSquaresType', 'two'); %! C = multcompare (a, 'CriticalValueType', 'bonferroni'); %! assert_equal (istable (C), true); %! assert_equal (C.Group1, [1; 1; 2]); %! assert_equal (C.Group2, [2; 3; 3]); %! assert_equal (C.MeanDifference, [-3; -9; -6], 1e-12); %! assert_equal (all (C.pValue >= 0 & C.pValue <= 1), true); ## --- Week 3: summary / disp --------------------------------------------- ## summary(): runs ensureFit_ and prints a table %!test %! a = anova ([1;1;1;2;2;2;3;3;3], (1:9)', 'SumOfSquaresType', 'two'); %! str = evalc ('summary (a)'); %! assert_equal (! isempty (strfind (str, 'ANOVA TABLE')), true); %! assert_equal (! isempty (strfind (str, 'backend = anovan')), true); ## summary(): includes MSE / DFE / Alpha line %!test %! a = anova ([1;1;1;2;2;2;3;3;3], (1:9)', 'SumOfSquaresType', 'two', ... %! 'Alpha', 0.10); %! str = evalc ('summary (a)'); %! assert_equal (! isempty (strfind (str, 'Alpha: 0.1')), true); ## disp(): one-line overview of key fields %!test %! a = anova ([1;1;1;2;2;2;3;3;3], (1:9)', 'SumOfSquaresType', 'two'); %! str = evalc ('disp (a)'); %! assert_equal (! isempty (strfind (str, '1-way anova')), true); %! assert_equal (! isempty (strfind (str, 'Type II')), true); %! assert_equal (! isempty (strfind (str, 'Properties, Methods')), true); ## summary(): SSType label appears in the header (Type I / II / III) %!test %! a1 = anova ([1;1;1;2;2;2;3;3;3], (1:9)', 'SumOfSquaresType', 'one'); %! a2 = anova ([1;1;1;2;2;2;3;3;3], (1:9)', 'SumOfSquaresType', 'two'); %! assert_equal (! isempty (strfind (evalc ('summary (a1)'), ... %! 'Type I sums')), true); %! assert_equal (! isempty (strfind (evalc ('summary (a2)'), ... %! 'Type II sums')), true); ## --- Week 4: multcompare pass-through ---------------------------------- ## multcompare(): anovan backend returns MATLAB's comparison table %!test %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! a = anova (g, y, 'SumOfSquaresType', 'two'); %! C = multcompare (a); %! assert_equal (size (C), [3, 6]); %! assert_equal (C.Properties.VariableNames, ... %! {'Group1', 'Group2', 'MeanDifference', ... %! 'MeanDifferenceLower', 'MeanDifferenceUpper', 'pValue'}); ## multcompare(): two-way balanced via anova2 backend %!test %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! a = anova (popcorn, [], 'reps', 3); %! C = multcompare (a, 'Factor1'); %! assert_equal (size (C), [3, 6]); ## multcompare(): runs after a fresh construction (triggers ensureFit_) %!test %! a = anova ([1;1;2;2;3;3], (1:6)', 'SumOfSquaresType', 'two'); %! C = multcompare (a); %! assert_equal (size (C), [3, 6]); ## MATLAB-compatible public methods and properties %!test %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! a = anova (g, y, "FactorNames", {"Brand"}, ... %! "ResponseName", "Yield", "SumOfSquaresType", "two"); %! assert_equal (char (a.Formula.Text), "Yield ~ 1 + Brand"); %! assert_equal (cellstr (a.Formula.PredictorNames), {"Brand"}); %! assert_equal (a.Formula.Terms, 1); %! assert_equal (istable (a.Factors), true); %! assert_equal (a.Factors.Brand, g); %! assert_equal (a.Response, y); %! assert_equal (isstring (a.FactorNames), true); %! assert_equal (cellstr (a.FactorNames), {"Brand"}); %! assert_equal (isstring (a.SumOfSquaresType), true); %! assert_equal (char (a.SumOfSquaresType), "two"); %! assert_equal (isstruct (a.Formula), true); %! assert_equal (! any (strcmp (methods ("anova"), "predict")), true); %! T = stats (a); %! M = groupmeans (a); %! V = varianceComponent (a); %! assert_equal (T.SumOfSquares(1), 126, 1e-12); %! assert_equal (M.Mean, [2; 5; 11], 1e-12); %! assert_equal (V.VarianceComponent, 1, 1e-12); ## Public property surface matches MATLAB's anova object (13 read-only names) %!test %! a = anova ([1;1;1;2;2;2;3;3;3], (1:9)'); %! assert_equal (sort (properties (a)), sort ({'Y'; 'Factors'; 'Formula'; ... %! 'FactorNames'; 'ExpandedFactorNames'; 'SumOfSquaresType'; ... %! 'RandomFactors'; 'CategoricalFactors'; 'ResponseName'; ... %! 'NumObservations'; 'Coefficients'; 'Residuals'; 'Metrics'})); ## Factors is a table with one named column per factor %!test %! y = (1:12)'; %! g1 = repmat ([1;2;3], 4, 1); %! g2 = repmat ([1;1;2;2], 3, 1); %! a = anova ({g1, g2}, y, 'FactorNames', {'A', 'B'}); %! assert_equal (istable (a.Factors), true); %! assert_equal (a.Factors.A, g1); %! assert_equal (a.Factors.B, g2); ## Residuals is a Raw/Pearson table (Pearson = Raw ./ sqrt (MSE)) %!test %! y = [10; 12; 11; 14; 16; 15; 9; 8; 10]; %! g = [1;1;1;2;2;2;3;3;3]; %! a = anova (g, y, 'SumOfSquaresType', 'two'); %! a.fit (); %! assert_equal (istable (a.Residuals), true); %! assert_equal (a.Residuals.Properties.VariableNames, {'Raw', 'Pearson'}); %! assert_equal (a.Residuals.Pearson, a.Residuals.Raw ./ sqrt (a.MSE), 1e-12); ## Metrics table exposes the fit summary with a correct R-squared %!test %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! a = anova (g, y, 'SumOfSquaresType', 'two'); %! a.fit (); %! M = a.Metrics; %! assert_equal (M.Properties.VariableNames, {'MSE', 'RMSE', 'SSE', 'SSR', ... %! 'SST', 'RSquared', 'AdjustedRSquared'}); %! assert_equal (M.SSE, 6, 1e-12); %! assert_equal (M.SST, 132, 1e-12); %! assert_equal (M.RSquared, 126 / 132, 1e-12); ## ExpandedFactorNames is populated for the anovan backend %!test %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! a = anova (g, y, 'SumOfSquaresType', 'two'); %! a.fit (); %! assert_equal (isstring (a.ExpandedFactorNames), true); %! assert_equal (cellstr (a.ExpandedFactorNames), ... %! {'(Intercept)'; '(Factor1==1)'; '(Factor1==2)'; ... %! '(Factor1==3)'}); ## groupmeans(): confidence bounds bracket the group means %!test %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! M = groupmeans (anova (g, y, 'SumOfSquaresType', 'two')); %! assert_equal (all (M.MeanLower <= M.Mean, 'all'), true); %! assert_equal (all (M.Mean <= M.MeanUpper, 'all'), true); ## boxchart(): returns a non-empty graphics result %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! h = boxchart (anova (g, y)); %! assert_equal (all (ishghandle (h)), true); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Weighted anovan fit exposes raw residuals (FittedValues + Residuals == Y) %!test %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! w = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! a = anova (g, y, 'Weights', w); %! a.fit (); %! assert_equal (a.FittedValues + a.Residuals.Raw, y, 1e-9); ## boxchart(): returns graphics handles in the requested axes %!test %! hfig = figure ("visible", "off"); %! unwind_protect %! ax = axes ("parent", hfig); %! a = anova ([1;1;1;2;2;2], [1;2;3;4;5;6], ... %! "SumOfSquaresType", "two"); %! h = boxchart (a, ax); %! assert_equal (all (ishghandle (h)), true); %! assert_equal (all (arrayfun (@(x) ancestor (x, "axes") == ax, h)), true); %! unwind_protect_cleanup %! close (hfig); %! end_unwind_protect ## plotComparisons(): plots adjusted means in the requested figure %!test %! hfig = figure ("visible", "off"); %! unwind_protect %! ax = axes ("parent", hfig); %! a = anova ([1;1;1;2;2;2;3;3;3], (1:9)', ... %! "SumOfSquaresType", "two"); %! h = plotComparisons (a, ax); %! assert_equal (h, hfig); %! assert_equal (numel (findall (ax, "type", "line")) >= 6, true); %! unwind_protect_cleanup %! close (hfig); %! end_unwind_protect ## plotComparisons(): Dunn-Sidak intervals are wider than unadjusted LSD %!test %! hfig = figure ("visible", "off"); %! unwind_protect %! ax1 = subplot (1, 2, 1, "parent", hfig); %! ax2 = subplot (1, 2, 2, "parent", hfig); %! a = anova (kron ((1:4)', ones (3, 1)), (1:12)', ... %! "SumOfSquaresType", "two"); %! plotComparisons (a, ax1, "CriticalValueType", "lsd"); %! plotComparisons (a, ax2, "CriticalValueType", "dunn-sidak"); %! lsd = findobj (ax1, "type", "line", "linestyle", "-"); %! sidak = findobj (ax2, "type", "line", "linestyle", "-"); %! lsd_width = diff (get (lsd(1), "xdata")); %! sidak_width = diff (get (sidak(1), "xdata")); %! assert_equal (sidak_width > lsd_width, true); %! unwind_protect_cleanup %! close (hfig); %! end_unwind_protect ## --- Week 5: diagnostic plots ------------------------------------------ ## plotDiagnostics(): anovan-backed fit creates the four-panel figure %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! y = [10; 12; 11; 14; 16; 15; 9; 8; 10]; %! g = [1;1;1;2;2;2;3;3;3]; %! a = anova (g, y, 'SumOfSquaresType', 'two'); %! h = plotDiagnostics (a, 'Visible', 'off'); %! assert_equal (all (ishghandle (h), 'all'), true); %! assert_equal (numel (findall (h, 'type', 'axes')), 4); %! unwind_protect_cleanup %! close (h); %! close (hf); %! end_unwind_protect ## --- Week 6: predict / effect sizes ------------------------------------- ## predict(): no Xnew returns fitted values %!test %! y = [10; 12; 11; 14; 16; 15; 9; 8; 10]; %! g = [1;1;1;2;2;2;3;3;3]; %! a = anova (g, y, 'SumOfSquaresType', 'two'); %! assert_equal (predict (a), a.FittedValues, 1e-9); ## predict(): accepts an explicit design matrix for anovan-backed fits %!test %! y = [10; 12; 11; 14; 16; 15; 9; 8; 10]; %! g = [1;1;1;2;2;2;3;3;3]; %! a = anova (g, y, 'SumOfSquaresType', 'two'); %! a.fit (); %! assert_equal (predict (a, full (a.DesignMatrix)), a.FittedValues, 1e-9); ## getEffectSizes(): anovan-backed fits report effect-size vectors %!test %! y = [10; 12; 11; 14; 16; 15; 9; 8; 10]; %! g = [1;1;1;2;2;2;3;3;3]; %! a = anova (g, y, 'SumOfSquaresType', 'two'); %! es = getEffectSizes (a); %! assert_equal (iscell (es.Source), true); %! assert_equal (numel (es.EtaSquared), numel (es.Source)); %! assert_equal (all (isfinite (es.PartialEtaSquared), 'all'), true); ## --- Input validation --------------------------------------------------- %!error anova () %!error anova ('abc') %!error anova ([]) %!error ... %! anova ([1;1;2;2], [1;2;3;4], 'SumOfSquaresType') %!error ... %! anova ([1;1;2;2], [1;2;3;4], 'bogus', 1) %!error ... %! anova ([1;1;2;2], [1;2;3;4], 'SumOfSquaresType', 5) %!error ... %! anova ([1;1;2;2], [1;2;3;4], 'Alpha', 2) %!error ... %! anova ([1;1;2;2], [1;2;3;4], 'Display', 'maybe') %!error ... %! anova ([1;1;2;2], [1;2;3;4], 1, 2) %!error ... %! anova (magic (4), [], 'reps', -2) %!error ... %! anova (magic (4), [], 'reps', 1.5) %!error ... %! anova ([1;1;2;2], [1;2;3;4], 'ModelSpecification', 'cubic') %!error ... %! anova ([1;1;2;2], [1;2;3;4], 'Model', [1 0]) %!error ... %! anova ([1;1;2], [1;2;3;4]) %!error ... %! anova ({[1;1;2], [1;2;1;2]}, [1;2;3;4]) %!error ... %! anova ([1;1;2;2], [1;2;3;4], 'Weights', [1;1;1]) %!error ... %! anova ([1;1;2;2], [1;2;3;4], 'CategoricalFactors', 1.5) %!error ... %! anova ([1;1;2;2], [1;2;3;4], 'CategoricalFactors', 2) ## --- Week 9: edge cases ------------------------------------------------ %!test %! a = anova (ones (5, 1), (1:5)'); %! [T, ems] = stats (a); %! assert_equal (T.DF(1), 0); %! assert_equal (T.pValue(1), NaN); %! assert_equal (cellstr (ems.ExpectedMeanSquares), ... %! {'V(Error)'; 'V(Error)'}); %! a = anova (ones (5, 1), (1:5)', 'SumOfSquaresType', 'two'); %! T = stats (a); %! assert_equal (T.DF(1), 0); %! assert_equal (T.pValue(1), NaN); %!test %! a = anova (1, 7); %! T = stats (a); %! assert_equal (a.NumObservations, 1); %! assert_equal (T.pValue(1), NaN); %!test %! a = anova ([1; 1; 2; 2], ones (4, 1)); %! T = stats (a); %! assert_equal (T.SumOfSquares(1), 0); %! assert_equal (T.DF, [1; 2; 3]); %! assert_equal (T.F(1), NaN); %! assert_equal (T.pValue(1), NaN); %!test %! g = categorical ([3; 3; 1; 1], [3, 2, 1]); %! a = anova (g, (1:4)'); %! T = stats (a); %! assert_equal (isfinite (T.pValue(1)), true); %! assert_equal (a.Stats.n, [2, 2]); %! assert_equal (a.Stats.gnames, {'3'; '1'}); %! assert_equal (a.Stats.means, [1.5, 3.5]); %! a = anova (g, (1:4)', 'SumOfSquaresType', 'two'); %! T = stats (a); %! assert_equal (T.F(1), 8, 1e-12); %! assert_equal (a.Stats.grpnames{1}, {'3'; '1'}); %!test %! g = kron ((1:120)', ones (2, 1)); %! a = anova (g, (1:240)'); %! T = stats (a); %! assert_equal (T.DF(1), 119); %! assert_equal (T.DF(2), 120); %!test %! n = 128; %! group = cell (1, 6); %! for k = 1:6 %! group{k} = mod (floor ((0:n-1)' / 2^(k-1)), 2) + 1; %! endfor %! a = anova (group, (1:n)', 'ModelSpecification', 'full'); %! stats (a); %! assert_equal (rows (a.Stats.terms), 63); %! assert_equal (columns (a.DesignMatrix), 64); ## --- Week 10: configuration integration ------------------------------- %!test %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! a = anova (g, y, 'FactorNames', {'Treatment'}); %! [component, ems] = stats (a); %! assert_equal (istable (component), true); %! assert_equal (component.Properties.VariableNames, ... %! {'SumOfSquares', 'DF', 'MeanSquares', 'F', 'pValue'}); %! assert_equal (component.Properties.RowNames, ... %! {'Treatment'; 'Error'; 'Total'}); %! assert_equal (component.SumOfSquares, [126; 6; 132], 1e-12); %! assert_equal (istable (ems), true); %! assert_equal (ems.Properties.VariableNames, ... %! {'Type', 'ExpectedMeanSquares', 'MeanSquaresDenominator', ... %! 'DFDenominator', 'FDenominator'}); %! assert_equal (ems.Properties.RowNames, {'Treatment'; 'Error'}); %! assert_equal (cellstr (ems.Type), {'fixed'; 'random'}); %! assert_equal (cellstr (ems.ExpectedMeanSquares), ... %! {'3*Q(Treatment)+V(Error)'; 'V(Error)'}); %! assert_equal (ems.MeanSquaresDenominator, [1; NaN]); %! assert_equal (ems.DFDenominator, [6; NaN]); %! assert_equal (cellstr (ems.FDenominator), {'MS(Error)'; ''}); %!test %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! a = anova (g, y, 'FactorNames', {'Treatment'}, 'RandomFactors', 1); %! [~, ems] = stats (a); %! assert_equal (cellstr (ems.Type), {'random'; 'random'}); %! assert_equal (cellstr (ems.ExpectedMeanSquares), ... %! {'3*V(Treatment)+V(Error)'; 'V(Error)'}); %!test %! y = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! a = anova (y, [], 'Reps', 3, 'ModelSpecification', 'interactions', ... %! 'FactorNames', {'Brand', 'PopperType'}); %! component = stats (a); %! assert_equal (component.Properties.RowNames, ... %! {'Brand'; 'PopperType'; 'Brand:PopperType'; 'Error'; 'Total'}); %!function values = __anova_values__ (tbl) %! if (istable (tbl)) %! values = [tbl.SumOfSquares, tbl.DF, tbl.MeanSquares, tbl.F, tbl.pValue]; %! return; %! endif %! if (columns (tbl) == 6) %! columns_ = [2, 3, 4, 5, 6]; ## anova1 and anova2 layout %! else %! columns_ = [2, 3, 5, 6, 7]; ## anovan layout, Mean Sq. after Singular? %! endif %! values = NaN (rows (tbl) - 1, numel (columns_)); %! for i = 2:rows (tbl) %! for j = 1:numel (columns_) %! value = tbl{i, columns_(j)}; %! if (isnumeric (value) && isscalar (value)) %! values(i - 1, j) = value; %! endif %! endfor %! endfor %!endfunction %!test %! y = [24; 26; 25; 24; 15; 17; 20; 16; 25; 29; 27; 19; 18; 21; 20]; %! gender = [1; 1; 1; 1; 1; 1; 1; 1; 2; 2; 2; 2; 2; 2; 2]; %! degree = [1; 1; 1; 1; 0; 0; 0; 0; 1; 1; 1; 0; 0; 0; 0]; %! a = anova ({gender, degree}, y, 'ModelSpecification', 'full'); %! component = stats (a, 'component', 'one'); %! [~, expected] = anovan (y, {gender, degree}, 'model', 'full', ... %! 'sstype', 1, 'display', 'off'); %! assert_equal (component.Properties.RowNames, ... %! {'Factor1'; 'Factor2'; 'Factor1:Factor2'; 'Error'; 'Total'}); %! assert_equal (__anova_values__ (component), ... %! __anova_values__ (expected), 1e-10); %! assert_equal (char (a.SumOfSquaresType), 'three'); %! assert_equal (istable (stats (a)), true); %!test %! y = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! a = anova (y, [], 'Reps', 3, 'ModelSpecification', 'interactions'); %! component = stats (a, 'Component', 'one'); %! assert_equal (__anova_values__ (component), ... %! __anova_values__ (stats (a)), 1e-10); %! summary_table = stats (a, 'summary'); %! assert_equal (summary_table.Properties.RowNames, ... %! {'Linear'; 'NonLinear'; 'Regression'; 'Error'; 'Total'}); %! assert_equal (summary_table.SumOfSquares(1:3), ... %! [20.25; 1 / 12; 61 / 3], 1e-10); %! assert_equal (summary_table.DF(1:3), [3; 2; 5]); %! assert_equal (summary_table.F(1:3), [48.6; 0.3; 29.28], 1e-10); %!test %! x = kron ((0:3)', ones (2, 1)); %! y = [0; 0.2; 1; 1.2; 4; 4.2; 9; 9.2]; %! summary_table = stats (anova (x, y, 'CategoricalFactors', []), 'summary'); %! assert_equal (summary_table.Properties.RowNames, ... %! {'Linear'; 'Regression'; 'Error'; 'LackOfFit'; ... %! 'PureError'; 'Total'}); %! assert_equal (summary_table.SumOfSquares(3:5), [8.08; 8; 0.08], 1e-12); %! assert_equal (summary_table.DF(3:5), [6; 2; 4]); %! assert_equal (summary_table.F(4), 200, 1e-10); %! assert_equal (summary_table.pValue(4), 9.802960494567e-05, 1e-12); %! g = [ones(10, 1); 2 * ones(10, 1)]; %! y = [(0:9)'; (1000:1009)']; %! summary_table = stats (anova (g, y), 'summary'); %! assert_equal (summary_table.pValue(1) > 0, true); %!test %! [demo_code, demo_idx] = test ('anovan', 'grabdemo'); %! assert_equal (numel (demo_idx) - 1, 13); %! for k = 1:numel (demo_idx) - 1 %! code = demo_code(demo_idx(k):demo_idx(k + 1) - 1); %! code = strrep (code, "'display', 'on'", "'display', 'off'"); %! code = strrep (code, "anovan (y, g, 'weights', v.^-1)", ... %! "anovan (y, g, 'weights', v.^-1, 'display', 'off')"); %! code = regexprep (code, '^ *(figure|plot|xlabel) [^\n]*$', '', 'lineanchors'); %! eval (code); %! switch (k) %! case 1 %! a = anova (gender, score, 'FactorNames', {'gender'}); %! case 2 %! a = anova ({treatment(:), subject(:)}, score(:), ... %! 'ModelSpecification', 'full', 'RandomFactors', 2, ... %! 'SumOfSquaresType', 'two', ... %! 'FactorNames', {'treatment', 'subject'}); %! case 3 %! a = anova (alloy, strength, 'FactorNames', {'alloy'}); %! case 4 %! a = anova ({seconds(:), subject(:)}, words(:), ... %! 'ModelSpecification', 'full', 'RandomFactors', 2, ... %! 'SumOfSquaresType', 'two', ... %! 'FactorNames', {'seconds', 'subject'}); %! case 5 %! a = anova ({brands(:), popper(:)}, popcorn(:), ... %! 'ModelSpecification', 'full', ... %! 'FactorNames', {'brands', 'popper'}); %! case 6 %! a = anova ({gender, degree}, salary, 'ModelSpecification', 'full', ... %! 'FactorNames', {'gender', 'degree'}); %! case 7 %! a = anova ({sugar, milk}, babble, 'ModelSpecification', 'full', ... %! 'FactorNames', {'sugar', 'milk'}); %! case 8 %! a = anova ({drug(:), feedback(:), diet(:)}, BP(:), ... %! 'ModelSpecification', 'full', ... %! 'FactorNames', {'drug', 'feedback', 'diet'}); %! case 9 %! a = anova ({strain, treatment, block}, measurement / 10, ... %! 'ModelSpecification', 'full', 'RandomFactors', 3, ... %! 'SumOfSquaresType', 'two', ... %! 'FactorNames', {'strain', 'treatment', 'block'}); %! case 10 %! a = anova ({species, temp}, pulse, 'CategoricalFactors', 1, ... %! 'SumOfSquaresType', 'hierarchical', ... %! 'FactorNames', {'species', 'temp'}); %! case 11 %! model = [1 0 0; 0 1 0; 0 0 1; 1 1 0]; %! a = anova ({treatment, exercise, age}, score, ... %! 'ModelSpecification', model, 'CategoricalFactors', [1, 2], ... %! 'SumOfSquaresType', 'hierarchical', ... %! 'FactorNames', {'treatment', 'exercise', 'age'}); %! case 12 %! a = anova (g, dv, 'FactorNames', {'score'}); %! case 13 %! a = anova (g, y, 'Weights', v .^ -1); %! endswitch %! switch (k) %! case 2 %! [~, ATAB, STATS] = anovan (score(:), ... %! {treatment(:), subject(:)}, 'model', 'full', 'sstype', 2, ... %! 'varnames', {'treatment', 'subject'}, 'display', 'off'); %! case 4 %! [~, ATAB, STATS] = anovan (words(:), ... %! {seconds(:), subject(:)}, 'model', 'full', 'sstype', 2, ... %! 'varnames', {'seconds', 'subject'}, 'display', 'off'); %! case 9 %! [~, ATAB, STATS] = anovan (measurement / 10, ... %! {strain, treatment, block}, 'model', 'full', 'sstype', 2, ... %! 'varnames', {'strain', 'treatment', 'block'}, ... %! 'display', 'off'); %! endswitch %! actual = stats (a); %! actual_sources = actual.Properties.RowNames; %! expected_sources = ATAB(2:end, 1); %! expected_sources = regexprep (expected_sources, "X([0-9]+)", "Factor$1"); %! if (strcmp (a.Stats.source, 'anova1')) %! actual_sources{1} = expected_sources{1}; %! endif %! assert_equal (actual_sources, expected_sources); %! actual_values = __anova_values__ (actual); %! expected_values = __anova_values__ (ATAB); %! if (any (k == [2, 4, 9])) %! assert_equal (actual_values(:, 1:3), expected_values(:, 1:3), 1e-8); %! else %! assert_equal (actual_values, expected_values, 1e-8); %! endif %! if (! any (k == [1, 3, 12])) %! if (k == 13) %! expected_residuals = STATS.Y - full (STATS.X) * STATS.coeffs(:, 1); %! else %! expected_residuals = STATS.resid; %! endif %! assert_equal (a.Residuals.Raw, expected_residuals, 1e-8); %! assert_equal (size (a.DesignMatrix), size (STATS.X)); %! endif %! endfor ## MATLAB-compatible table constructors and public result schemas. %!test %! dose = [1; 1; 2; 2; 1; 2]; %! site = {'A'; 'B'; 'A'; 'B'; 'A'; 'B'}; %! yield = [1; 2; 4; 5; 2; 6]; %! tbl = table (dose, site, yield, ... %! 'VariableNames', {'Dose', 'Site', 'Yield'}); %! a = anova (tbl, 'Yield'); %! assert_equal (cellstr (a.FactorNames), {'Dose', 'Site'}); %! assert_equal (a.ResponseName, 'Yield'); %! assert_equal (istable (a.Factors), true); %! assert_equal (a.Factors.Properties.VariableNames, {'Dose', 'Site'}); %! assert_equal (a.Y, yield); %! T = stats (a); %! direct = stats (anova ({dose, site}, yield, ... %! 'FactorNames', {'Dose', 'Site'})); %! assert_equal (__anova_values__ (T), __anova_values__ (direct), 1e-12); %!test %! dose = [1; 1; 2; 2; 1; 2]; %! site = {'A'; 'B'; 'A'; 'B'; 'A'; 'B'}; %! yield = [1; 2; 4; 5; 2; 6]; %! tbl = table (dose, site, yield, ... %! 'VariableNames', {'Dose', 'Site', 'Yield'}); %! a = anova (tbl, 'Yield ~ Dose + Site + Dose:Site'); %! T = stats (a); %! direct = stats (anova ({dose, site}, yield, ... %! 'FactorNames', {'Dose', 'Site'}, ... %! 'ModelSpecification', 'full')); %! assert_equal (char (a.Formula.Text), ... %! 'Yield ~ Dose + Site + Dose:Site'); %! assert_equal (a.ModelSpecification, char (a.Formula.Text)); %! assert_equal (__anova_values__ (T), __anova_values__ (direct), 1e-12); %!test %! dose = [1; 1; 2; 2]; %! site = [1; 2; 1; 2]; %! y = [1; 2; 4; 5]; %! tbl = table (dose, site, 'VariableNames', {'Dose', 'Site'}); %! a = anova (tbl, y, 'FactorNames', {'Site'}); %! assert_equal (cellstr (a.FactorNames), {'Site'}); %! assert_equal (a.Factors.Site, site); %! assert_equal (stats (a).DF, [1; 2; 3]); %!test %! g1 = [1; 1; 1; 1; 2; 2; 2; 2]; %! g2 = [1; 1; 2; 2; 1; 1; 2; 2]; %! y = (1:8)'; %! a = anova ({g1, g2}, y, 'FactorNames', {'A', 'B'}, ... %! 'CategoricalFactors', [true, false], 'RandomFactors', 'A'); %! assert_equal (a.CategoricalFactors, 1); %! assert_equal (a.RandomFactors, 1); %! b = anova ({g1, g2}, y, 'FactorNames', {'A', 'B'}, ... %! 'CategoricalFactors', {'A', 'B'}, 'RandomFactors', 'all'); %! assert_equal (b.CategoricalFactors, [1, 2]); %! assert_equal (b.RandomFactors, [1, 2]); %!test %! y = [1; 2; 3; 4; 5; 6]; %! g = [1; 1; 1; 2; 2; 2]; %! a = anova (g, y, 'SumOfSquaresType', 'two'); %! stats (a); %! assert_equal (isvector (a.Coefficients), true); %! assert_equal (isstring (a.ExpandedFactorNames), true); %! assert_equal (istable (a.Residuals), true); %! assert_equal (a.Residuals.Properties.VariableNames, {'Raw', 'Pearson'}); %! assert_equal (a.Residuals.Pearson, ... %! a.Residuals.Raw / sqrt (a.Metrics.MSE), 1e-12); %! assert_equal (a.Metrics.Properties.VariableNames, ... %! {'MSE', 'RMSE', 'SSE', 'SSR', 'SST', ... %! 'RSquared', 'AdjustedRSquared'}); %! assert_equal (a.Metrics.SST, a.Metrics.SSE + a.Metrics.SSR, 1e-12); %! assert_equal (a.Coefficients, [3.5; -1.5; 1.5], 1e-12); %! assert_equal (cellstr (a.ExpandedFactorNames), ... %! {'(Intercept)'; '(Factor1==1)'; '(Factor1==2)'}); %!test %! y = [1; 2; 3; 4; 5; 6; 10; 11; 12]; %! g = [1; 1; 1; 2; 2; 2; 3; 3; 3]; %! a = anova (g, y); %! m = multcompare (a); %! assert_equal (istable (m), true); %! assert_equal (m.Properties.VariableNames, ... %! {'Group1', 'Group2', 'MeanDifference', ... %! 'MeanDifferenceLower', 'MeanDifferenceUpper', 'pValue'}); %! assert_equal (m.MeanDifference, [-3; -9; -6], 1e-12); %! v = varianceComponent (a); %! assert_equal (v.Properties.VariableNames, ... %! {'VarianceComponent', 'VarianceComponentLower', ... %! 'VarianceComponentUpper'}); %! assert_equal (v.Properties.RowNames, {'Error'}); %! one_group = multcompare (anova (ones (4, 1), (1:4)')); %! assert_equal (istable (one_group), true); %! assert_equal (size (one_group), [0, 6]); %!test %! g1 = [1; 1; 1; 1; 2; 2; 2; 2]; %! g2 = {'A'; 'A'; 'B'; 'B'; 'A'; 'A'; 'B'; 'B'}; %! y = [1; 2; 3; 4; 5; 6; 8; 9]; %! a = anova ({g1, g2}, y, 'FactorNames', {'Dose', 'Site'}, ... %! 'ModelSpecification', 'full'); %! m = multcompare (a, {'Dose', 'Site'}, ... %! 'CriticalValueType', 'bonferroni'); %! assert_equal (istable (m.Group1), true); %! assert_equal (istable (m.Group2), true); %! assert_equal (m.Group1.Properties.VariableNames, {'Dose', 'Site'}); %! assert_equal (rows (m), 6); %!test %! g1 = {"x, y"; "x, y"; "x, y"; "x, y"; "z=1"; "z=1"; "z=1"; "z=1"}; %! g2 = {"a=1"; "a=1"; "b,2"; "b,2"; "a=1"; "a=1"; "b,2"; "b,2"}; %! y = (1:8)'; %! a = anova ({g1, g2}, y, "FactorNames", {"Dose", "Site"}, ... %! "ModelSpecification", "full"); %! m = multcompare (a, {"Dose", "Site"}); %! values = [m.Group1.Dose; m.Group2.Dose; m.Group1.Site; m.Group2.Site]; %! assert_equal (any (strcmp (values, "x, y")), true); %! assert_equal (any (strcmp (values, "z=1")), true); %! assert_equal (any (strcmp (values, "a=1")), true); %! assert_equal (any (strcmp (values, "b,2")), true); %!test %! g1 = [1; 1; 1; 1; 2; 2; 2; 2]; %! g2 = [1; 1; 2; 2; 1; 1; 2; 2]; %! y = (1:8)'; %! a = anova ({g1, g2}, y, 'FactorNames', {'A', 'B'}, ... %! 'ResponseName', 'R', ... %! 'ModelSpecification', 'R ~ A + B + A:B'); %! assert_equal (char (a.Formula.Text), 'R ~ A + B + A:B'); %! assert_equal (stats (a).Properties.RowNames, ... %! {'A'; 'B'; 'A:B'; 'Error'; 'Total'}); %! b = anova ({g1, g2}, y, 'FactorNames', {'A', 'B'}, ... %! 'ModelSpecification', 3); %! assert_equal (b.ModelSpecification, 3); %! assert_equal (char (b.Formula.Text), 'Y ~ 1 + A + B + A:B'); %! assert_equal (__anova_values__ (stats (a)), ... %! __anova_values__ (stats (b)), 1e-12); %!test %! tbl = table ([1; 1; 2; 2], [1; 2; 3; 4], ... %! 'VariableNames', {'Group', 'Yield'}); %! a = anova (tbl, 'Yield ~ Group', 'FactorNames', {'Yield'}, ... %! 'ResponseName', 'Ignored', 'ModelSpecification', 'full'); %! assert_equal (cellstr (a.FactorNames), {'Group'}); %! assert_equal (a.ResponseName, 'Yield'); %! assert_equal (a.ModelSpecification, 'Yield ~ Group'); %!test %! a = anova ([1; 1; 2; 2], [1; NaN; 3; 4]); %! stats (a); %! assert_equal (a.NumObservations, 3); %! assert_equal (a.Y, [1; 3; 4]); %! assert_equal (height (a.Factors), 3); %! assert_equal (height (a.Residuals), 3); ## Random-factor variance components match MATLAB's documented carsmall example. %!test %! load carsmall %! a = anova ({Origin, Model_Year}, MPG, "RandomFactors", [1, 2], ... %! "FactorNames", {"Origin", "Year"}); %! v = varianceComponent (a); %! assert_equal (v.Properties.RowNames, {"Origin"; "Year"; "Error"}); %! assert_equal (v.VarianceComponent, [21.337; 44.031; 20.198], 5e-3); %! assert_equal (v.VarianceComponentLower, [6.1257; 11.176; 15.298], 5e-3); %! assert_equal (v.VarianceComponentUpper, [139.94; 1765.7; 27.909], 5e-1); ## Interactions containing a random factor remain in the model and provide ## the denominator for fixed-effect tests. %!test %! A = kron ([1; 2], ones (12, 1)); %! B = repmat (kron ([1; 2; 3], ones (4, 1)), 2, 1); %! y = 10 + 2 * (A == 2) + ... %! [0;1;-1;0; 1;0;-1;0; -1;0;1;0; 0;1;0;-1; 2;1;0;1; -1;0;1;0]; %! a = anova ({A, B}, y, "FactorNames", {"A", "B"}, ... %! "ModelSpecification", "full", "RandomFactors", 2); %! [s, ems] = stats (a); %! assert_equal (s.Properties.RowNames, {"A"; "B"; "A:B"; "Error"; "Total"}); %! assert_equal (s.F(1:3), [49; 1; 1], 1e-12); %! assert_equal (cellstr (ems.Type), {"fixed"; "random"; "random"; "random"}); %! assert_equal (cellstr (ems.FDenominator), ... %! {"MS(A:B)"; "MS(A:B)"; "MS(Error)"; ""}); %! v = varianceComponent (a); %! assert_equal (v.Properties.RowNames, {"B"; "A:B"; "Error"}); ## A negative variance-component estimate is reported without an interval. %!test %! A = kron ([1; 2], ones (12, 1)); %! B = repmat (kron ([1; 2; 3; 4], ones (3, 1)), 2, 1); %! y = [5;6;4; 12;13;11; 20;19;21; 8;7;9; ... %! 7;8;6; 15;14;16; 23;22;24; 10;11;9]; %! a = anova ({A, B}, y, 'FactorNames', {'A', 'B'}, ... %! 'ModelSpecification', 'full', 'RandomFactors', 2); %! v = varianceComponent (a); %! assert_equal (v.Properties.RowNames, {'B'; 'A:B'; 'Error'}); %! assert_equal (v.VarianceComponent, ... %! [45.4166666666666; -0.166666666666666; 1], 1e-9); %! assert_equal (v.VarianceComponentLower, ... %! [13.4429180926305; NaN; 0.554682109897800], 1e-9); %! assert_equal (v.VarianceComponentUpper, ... %! [632.517205323886; NaN; 2.31626772541430], 1e-8); ## A non-negative estimate has its lower bound truncated at zero. %!test %! A = kron ([1; 2], ones (12, 1)); %! B = repmat (kron ([1; 2; 3; 4], ones (3, 1)), 2, 1); %! y = [5;6;4; 12;13;11; 20;19;21; 8;7;9; ... %! 7;8;6; 15;14;16; 23;22;24; 10;11;9]; %! y(1:12) += [0;0;0; 1;1;1; -1;-1;-1; 0.4;0.4;0.4]; %! a = anova ({A, B}, y, 'FactorNames', {'A', 'B'}, ... %! 'ModelSpecification', 'full', 'RandomFactors', 2); %! v = varianceComponent (a); %! assert_equal (v.VarianceComponent(2), 0.253333333333400, 1e-9); %! assert_equal (v.VarianceComponentLower(2), 0); %! assert_equal (v.VarianceComponentUpper(2), 7.97098397237600, 1e-9); ## Named polynomial models preserve MATLAB's term definitions. %!test %! x = (-2:2)'; %! y = 2 + 3 * x + 4 * x .^ 2; %! a = anova (x, y, "CategoricalFactors", [], ... %! "ModelSpecification", "purequadratic", ... %! "FactorNames", {"x"}); %! s = stats (a); %! assert_equal (a.Stats.terms, [1; 2]); %! assert_equal (s.Properties.RowNames, {"x"; "x^2"; "Error"; "Total"}); %! assert_equal (a.FittedValues, y, 1e-12); ## Hierarchical sums preserve polynomial power containment. %!test %! x = [0; 1; 2; 4; 7; 8; 10; 15]; %! y = [1; 2; 3; 6; 12; 15; 21; 40]; %! a = anova (x, y, "CategoricalFactors", [], ... %! "ModelSpecification", [1; 2], ... %! "SumOfSquaresType", "hierarchical"); %! s = stats (a); %! assert_equal (char (a.SumOfSquaresType), "hierarchical"); %! assert_equal (a.SSType, "h"); %! assert_equal (s.SumOfSquares(1:2), ... %! [1149.540669856459; 60.32250042332054], 1e-10); ## Accepted aliases are canonicalized in the public property. %!test %! a = anova ([1; 1; 2; 2], (1:4)', "SumOfSquaresType", "typeii"); %! assert_equal (char (a.SumOfSquaresType), "two"); %!test %! [x1, x2] = ndgrid ((-2:2)', (-1:1)'); %! x1 = x1(:); %! x2 = x2(:); %! y = 1 + 2*x1 + 3*x2 + 4*x1.*x2 + 5*x1.^2 + 6*x2.^2; %! a = anova ({x1, x2}, y, "CategoricalFactors", [], ... %! "ModelSpecification", "quadratic"); %! stats (a); %! assert_equal (a.Stats.terms, [1 0; 0 1; 1 1; 2 0; 0 2]); %! assert_equal (a.FittedValues, y, 1e-10); %!test %! [x1, x2] = ndgrid ([-1; 1], (-2:2)'); %! x1 = x1(:); %! x2 = x2(:); %! y = x1 + x2.^2 + x2.^3 + x1.*x2 + x1.*x2.^2; %! a = anova ({x1, x2}, y, "CategoricalFactors", [], ... %! "ModelSpecification", "poly13"); %! stats (a); %! assert_equal (a.Stats.terms, [1 0; 0 1; 0 2; 0 3; 1 1; 1 2]); %!test %! x = (-2:2)'; %! tbl = table (x, 2 + 3*x + 4*x.^2, "VariableNames", {"x", "y"}); %! a = anova (tbl, "y ~ x^2", "CategoricalFactors", []); %! stats (a); %! assert_equal (a.Stats.terms, [1; 2]); %! assert_equal (a.FittedValues, tbl.y, 1e-12); ## Nested factors use separate within-parent contrasts. %!test %! A = kron ([1; 2], ones (6, 1)); %! B = repmat (kron ([1; 2], ones (3, 1)), 2, 1); %! y = 10*A + 2*B + repmat ([-1; 0; 1], 4, 1); %! tbl = table (A, B, y, "VariableNames", {"A", "B", "Y"}); %! a = anova (tbl, "Y ~ A + B(A)"); %! s = stats (a); %! assert_equal (a.Stats.terms, [1 0; 0 1]); %! assert_equal (a.Stats.vnested, logical ([0 0; 1 0])); %! assert_equal (s.Properties.RowNames, {"A"; "B(A)"; "Error"; "Total"}); %! assert_equal (s.DF, [1; 2; 8; 11]); %!test %! [A, C, B, R] = ndgrid ([1; 2], [1; 2], [1; 2], [1; 2]); %! A = A(:); C = C(:); B = B(:); R = R(:); %! y = A + 2*C + 3*B + 0.1*R; %! tbl = table (A, B, C, y, "VariableNames", {"A", "B", "C", "Y"}); %! a = anova (tbl, "Y ~ A*C + B(A,C)"); %! s = stats (a); %! assert_equal (a.Stats.vnested, logical ([0 0 0; 1 0 1; 0 0 0])); %! assert_equal (s.Properties.RowNames, ... %! {"A"; "C"; "A:C"; "B(A,C)"; "Error"; "Total"}); %! assert_equal (s.DF(4), 4); ## Unbalanced NaN cleanup preserves the anovan fallback results. %!test %! x = [1, 5; 2, 6; 3, 7; NaN, 8]; %! warning ("off", "all", "local"); %! a = anova (x, [], "Reps", 2, "ModelSpecification", "interactions"); %! stats (a); %! assert_equal (a.Stats.source, "anovan"); %! assert_equal (a.NumObservations, 7); %! assert_equal (height (a.Residuals), 7); ## Numeric replicated models bypass anova2 when it cannot represent the terms. %!test %! x = [1, 5; 2, 6; 3, 7; 4, 8]; %! a = anova (x, [], "Reps", 2, ... %! "ModelSpecification", [1 0; 0 1], ... %! "FactorNames", {"A", "B"}); %! s = stats (a); %! assert_equal (s.Properties.RowNames, {"A"; "B"; "Error"; "Total"}); ## Raw residuals are unweighted observation-minus-fit differences. %!test %! g = [1; 1; 2; 2; 3; 3]; %! y = [1; 3; 4; 8; 9; 15]; %! a = anova (g, y, "Weights", [1; 2; 1; 3; 1; 4]); %! stats (a); %! assert_equal (a.Residuals.Raw, a.Y - a.FittedValues, 1e-12); %!error ... %! anova ([1; 1; 2; 2], (1:4)', "ModelSpecification", "purequadratic") %!error ... %! anova ([1;1;2;2], [1;2;3;4], "RandomFactors", 0) %!error ... %! anova ([1;1;2;2], [1;2;3;4], "RandomFactors", 2) %!error %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! plotDiagnostics (anova (popcorn, [], 'reps', 3)); %!error %! y = [10; 12; 11; 14; 16; 15; 9; 8; 10]; %! g = [1;1;1;2;2;2;3;3;3]; %! plotDiagnostics (anova (g, y, 'SumOfSquaresType', 'two'), 'Visible'); %!error %! y = [10; 12; 11; 14; 16; 15; 9; 8; 10]; %! g = [1;1;1;2;2;2;3;3;3]; %! plotDiagnostics (anova (g, y, 'SumOfSquaresType', 'two'), 'BadOption', true); %!error %! y = [10; 12; 11; 14; 16; 15; 9; 8; 10]; %! g = [1;1;1;2;2;2;3;3;3]; %! predict (anova (g, y, 'SumOfSquaresType', 'two'), ones (2, 2)); %!error %! a = anova ([1; 1; 2; 2], (1:4)'); %! stats (a, 'Component', 1); %!error %! a = anova ([1; 1; 2; 2], (1:4)'); %! stats (a, 'Component', 'typei'); %!error %! a = anova ([1; 1; 2; 2], (1:4)'); %! stats (a, 'details'); %!error %! a = anova ([1; 1; 2; 2], (1:4)'); %! stats (a, 'summary', 'one'); ## A saturated mixed model still yields every F ratio and p-value. %!test %! y = [444 614 423 625 408 856 447 719 ... %! 764 831 586 782 609 1002 606 766]' / 10; %! X1 = {'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola', ... %! 'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola'}'; %! X2 = {'C','T','C','T','C','T','C','T','C','T','C','T','C','T','C','T'}'; %! X3 = [1;1;1;1;1;1;1;1;2;2;2;2;2;2;2;2]; %! a = anova ({X1, X2, X3}, y, 'ModelSpecification', 'full', ... %! 'RandomFactors', 3, 'FactorNames', {'X1', 'X2', 'X3'}); %! s = stats (a); %! assert_equal (s.DF', [3, 1, 1, 3, 3, 1, 3, 0, 15]); %! assert_equal (s.SumOfSquares(8), 0); %! assert_equal (s.pValue(1:6), ... %! [0.288811428913179; 0.091455278902114; 0.042134889025806; ... %! 0.010944863181481; 0.061814763198376; 0.066584161056625], ... %! 1e-12); %! assert_equal (s.pValue(7), NaN); ## The denominator of each F ratio comes from the expected mean squares. %!test %! y = [444 614 423 625 408 856 447 719 ... %! 764 831 586 782 609 1002 606 766]' / 10; %! X1 = {'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola', ... %! 'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola'}'; %! X2 = {'C','T','C','T','C','T','C','T','C','T','C','T','C','T','C','T'}'; %! X3 = [1;1;1;1;1;1;1;1;2;2;2;2;2;2;2;2]; %! a = anova ({X1, X2, X3}, y, 'ModelSpecification', 'full', ... %! 'RandomFactors', 3, 'FactorNames', {'X1', 'X2', 'X3'}); %! [~, ems] = stats (a); %! assert_equal (ems.MeanSquaresDenominator(1:7), ... %! [47.1575; 47.61; 88.7925; 5.975; 5.975; 5.975; 0], 1e-9); %! assert_equal (ems.DFDenominator(1:7), ... %! [3; 1; 2.610727841363640; 3; 3; 3; 0], 1e-12); ## A term contained in another contributes to that term's expected mean square, ## whether the containing term is fixed or random. %!test %! y = [444 614 423 625 408 856 447 719 ... %! 764 831 586 782 609 1002 606 766]' / 10; %! X1 = {'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola', ... %! 'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola'}'; %! X2 = {'C','T','C','T','C','T','C','T','C','T','C','T','C','T','C','T'}'; %! X3 = [1;1;1;1;1;1;1;1;2;2;2;2;2;2;2;2]; %! a = anova ({X1, X2, X3}, y, 'ModelSpecification', 'full', ... %! 'RandomFactors', 3, 'FactorNames', {'X1', 'X2', 'X3'}); %! [~, ems] = stats (a); %! assert_equal (cellstr (ems.ExpectedMeanSquares), ... %! {'4*Q(X1)+2*Q(X1:X2)+2*V(X1:X3)+V(X1:X2:X3)+V(Error)'; ... %! '8*Q(X2)+2*Q(X1:X2)+4*V(X2:X3)+V(X1:X2:X3)+V(Error)'; ... %! '8*V(X3)+2*V(X1:X3)+4*V(X2:X3)+V(X1:X2:X3)+V(Error)'; ... %! '2*Q(X1:X2)+V(X1:X2:X3)+V(Error)'; ... %! '2*V(X1:X3)+V(X1:X2:X3)+V(Error)'; ... %! '4*V(X2:X3)+V(X1:X2:X3)+V(Error)'; ... %! 'V(X1:X2:X3)+V(Error)'; 'V(Error)'}); ## A saturated model reports no residual rather than an infinite one. %!test %! y = [3; 4; 7; 8]; %! [~, tbl] = anovan (y, {[1;1;2;2], [1;2;1;2]}, 'model', 'full', ... %! 'display', 'off'); %! assert_equal (tbl{end-1, 2}, 0); %! assert_equal (tbl{end-1, 3}, 0); %! assert_equal (tbl{end-1, 5}, 0); statistics-release-1.9.2/inst/Hypothesis_Testing/anova1.m000066400000000000000000000463031524624707500235310ustar00rootroot00000000000000## Copyright (C) 2021-2022 Andreas Bertsatos ## Copyright (C) 2022 Andrew Penn ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} anova1 (@var{x}) ## @deftypefnx {statistics} {@var{p} =} anova1 (@var{x}, @var{group}) ## @deftypefnx {statistics} {@var{p} =} anova1 (@var{x}, @var{group}, @var{displayopt}) ## @deftypefnx {statistics} {@var{p} =} anova1 (@var{x}, @var{group}, @var{displayopt}, @var{vartype}) ## @deftypefnx {statistics} {[@var{p}, @var{atab}] =} anova1 (@var{x}, @dots{}) ## @deftypefnx {statistics} {[@var{p}, @var{atab}, @var{stats}] =} anova1 (@var{x}, @dots{}) ## ## Perform a one-way analysis of variance (ANOVA) for comparing the means of two ## or more groups of data under the null hypothesis that the groups are drawn ## from distributions with the same mean. For planned contrasts and/or ## diagnostic plots, use @qcode{anovan} instead. ## ## anova1 can take up to three input arguments: ## ## @itemize ## @item ## @var{x} contains the data and it can either be a vector or matrix. ## If @var{x} is a matrix, then each column is treated as a separate group. ## If @var{x} is a vector, then the @var{group} argument is mandatory. ## ## @item ## @var{group} contains the names for each group. If @var{x} is a matrix, then ## @var{group} can either be a cell array of strings of a character array, with ## one row per column of @var{x}. If you want to omit this argument, enter an ## empty array ([]). If @var{x} is a vector, then @var{group} must be a vector ## of the same length, or a string array or cell array of strings with one row ## for each element of @var{x}. @var{x} values corresponding to the same value ## of @var{group} are placed in the same group. ## ## @item ## @var{displayopt} is an optional parameter for displaying the groups contained ## in the data in a boxplot. If omitted, it is 'on' by default. If group names ## are defined in @var{group}, these are used to identify the groups in the ## boxplot. Use 'off' to omit displaying this figure. ## ## @item ## @var{vartype} is an optional parameter to used to indicate whether the ## groups can be assumed to come from populations with equal variance. When ## @qcode{vartype} is @qcode{'equal'} the variances are assumed to be equal ## (this is the default). When @qcode{vartype} is @qcode{'unequal'} the ## population variances are not assumed to be equal and Welch's ANOVA test is ## used instead. ## ## @var{vartype} is an Octave extension: MATLAB's @code{anova1} takes no ## fourth argument. It does not error on one either, it accepts it and ## ignores it, returning the same @var{p} and @var{F} for @qcode{'unequal'} ## as for @qcode{'equal'}. Code written against this function and then run ## in MATLAB therefore gets the classic ANOVA silently, with no diagnostic ## of any kind. Note that @code{anova2}'s analogous fourth argument does ## make MATLAB error, so the silence here is particular to @code{anova1}. ## @end itemize ## ## anova1 can return up to three output arguments: ## ## @itemize ## @item ## @var{p} is the p-value of the null hypothesis that all group means are equal. ## ## @item ## @var{atab} is a cell array containing the results in a standard ANOVA table. ## ## @item ## @var{stats} is a structure containing statistics useful for performing ## a multiple comparison of means with the MULTCOMPARE function. ## @end itemize ## ## A categorical @var{group} may declare levels that no observation uses. ## Such a level takes no part in the analysis and is dropped from every field ## of @var{stats}, so @code{n}, @code{means} and @code{gnames} always describe ## the same groups, in the same order, and can be indexed together. ## ## This is a deliberate deviation from MATLAB, which drops an unused level ## from @code{gnames} but keeps it in @code{n} and @code{means} as a count of ## zero and a mean of @code{NaN}. Those fields then disagree in length and ## the group indices run past @code{gnames}, so MATLAB's own @code{multcompare} ## reports comparisons against a group holding no observations and labels them ## with indices that its @code{gnames} cannot resolve. ## ## If anova1 is called without any output arguments, then it prints the results ## in a one-way ANOVA table to the standard output. It is also printed when ## @var{displayopt} is 'on'. ## ## ## Examples: ## ## @example ## x = meshgrid (1:6); ## x = x + normrnd (0, 1, 6, 6); ## anova1 (x, [], 'off'); ## [p, atab] = anova1(x); ## @end example ## ## ## @example ## x = ones (50, 4) .* [-2, 0, 1, 5]; ## x = x + normrnd (0, 2, 50, 4); ## groups = @{"A", "B", "C", "D"@}; ## anova1 (x, groups); ## @end example ## ## @seealso{anova2, anovan, multcompare} ## @end deftypefn function [p, anovatab, stats] = anova1 (x, group, displayopt, vartype) ## Check for valid number of input arguments if (nargin < 1 || nargin > 4) error ("anova1: invalid number of input arguments."); endif ## Add defaults if (nargin < 2) group = []; endif if (nargin < 3) displayopt = 'on'; endif if (nargin < 4) vartype = 'equal'; endif plotdata = ! (strcmp (displayopt, 'off')); ## Convert group to cell array from character array, make it a column if (! isempty (group) && ischar (group)) group = cellstr (group); endif if (size (group, 1) == 1) group = group'; endif ## If x is a matrix, convert it to column vector and create a ## corresponding column vector for groups if (length (x) < prod (size (x))) [n, m] = size (x); x = x(:); gi = reshape (repmat ((1:m), n, 1), n*m, 1); if (length (group) == 0) ## no group names are provided group = gi; elseif (size (group, 1) == m) ## group names exist and match columns group = group(gi,:); else error ("anova1: columns in X and GROUP length do not match."); endif endif ## Check that x and group are the same size if (numel (x) != prod (size (group))) error ("anova1: GROUP must be a vector with the same number of rows as x."); endif ## Convert group to indices and separate names first if (isa (group, "categorical")) group_id = double (group(:)); group_names = categories (group); else [group_id, group_names] = grp2idx (group); group_id = group_id(:); endif x = x(:); ## identify NaN values in x or missing/empty categories in the group and remove them. valid_data = ! isnan (x) & ! isnan (group_id); x = x(valid_data); group_id = group_id(valid_data); ## A declared but unused categorical level, or one emptied by NaN removal, ## is dropped from the design and from every field of STATS, so the group ## counts, means and names stay the same length. MATLAB drops it from the ## names alone; see the deviation noted in the help text above. groups = size (group_names, 1); xs = accumarray (group_id, 1, [groups, 1], @sum, 0); observed_groups = (xs > 0); if (any (! observed_groups)) group_map = cumsum (observed_groups); group_id = group_map(group_id); group_names = group_names(observed_groups, :); xs = xs(observed_groups); endif named = 1; ## Center data to improve accuracy and keep uncentered data for plotting xorig = x; mu = mean (x); x = x - mu; xr = x; ## Get group size and mean for each group groups = size (group_names, 1); xsum = accumarray (group_id, xr, [groups, 1], @sum, 0); xm = xsum ./ xs; xdev = xr - xm(group_id); xss = accumarray (group_id, xdev .^ 2, [groups, 1], @sum, 0); xv = xss ./ max (xs - 1, 1); xv(xs == 0) = NaN; xs = xs'; xm = xm'; xv = xv'; ## Calculate statistics lx = length (xr); ## Number of samples in groups gm = mean (xr); ## Grand mean of groups dfm = length (xm) - 1; ## degrees of freedom for model dfe = lx - dfm - 1; ## degrees of freedom for error SSM = xs .* (xm - gm) * (xm - gm)'; ## Sum of Squares for Model SST = (xr(:) - gm)' * (xr(:) - gm); ## Sum of Squares Total SSE = SST - SSM; ## Sum of Squares Error if (dfm > 0) MSM = SSM / dfm; ## Mean Square for Model else MSM = NaN; endif if (dfe > 0) MSE = SSE / dfe; ## Mean Square for Error else MSE = NaN; endif ## Calculate F statistic if (dfm <= 0) ## No between-group degrees of freedom. F = NaN; p = NaN; elseif (SSE != 0) ## Regular Matrix case. switch (lower (vartype)) case 'equal' ## Assume equal variances (Fisher's One-way ANOVA) F = (SSM / dfm) / MSE; case 'unequal' ## Accommodate for unequal variances (Welch's One-way ANOVA) ## Calculate the sampling variance for each group (i.e. the square of the SEM) sv = xv ./ xs; ## Calculate weights as the reciprocal of the sampling variance w = 1 ./ sv; ## Calculate the origin ori = sum (w .* xm) ./ sum (w); ## Calculate Welch's F statistic F = (groups - 1)^-1 * sum (w .* (xm - ori).^2) /... (1 + ((2 * (groups - 2)/(groups^2 - 1)) * ... sum ((1 - w / sum (w)).^2 .* (xs - 1).^-1))); ## Welch's test does not use a pooled error term MSE = NaN; ## Correct the error degrees of freedom dfe = (3 /(groups^2 - 1) * sum ((1 - w / sum (w)).^2 .* (xs-1).^-1))^-1; otherwise error ("anova1: invalid fourth (vartype) argument to anova1."); endswitch p = 1 - fcdf (F, dfm, dfe); ## Probability of F given equal means. elseif (SSM == 0) ## Constant Matrix case. ## Both sums-of-squares vanish, so F is 0/0. There is no within-group ## scale to test against, and the limit depends on the direction of ## approach, so the statistic is undefined rather than zero. F = NaN; p = NaN; else ## Perfect fit case. F = Inf; p = 0; endif ## Create results table (if requested) if (nargout > 1) switch (lower (vartype)) case 'equal' anovatab = {'Source', 'SS', 'df', 'MS', 'F', 'Prob>F'; ... 'Groups', SSM, dfm, MSM, F, p; ... 'Error', SSE, dfe, MSE, '', ''; ... 'Total', SST, dfm + dfe, '', '', ''}; case 'unequal' anovatab = {'Source', 'F', 'df', 'dfe', 'F', 'Prob>F'; ... 'Groups', SSM, dfm, dfe, F, p}; endswitch endif ## Create stats structure (if requested) for MULTCOMPARE if (nargout > 2) if (length (group_names) > 0) stats.gnames = group_names; else stats.gnames = strjust (num2str ((1:length (xm))'), 'left'); endif stats.n = xs; stats.source = 'anova1'; stats.vartype = vartype; stats.means = xm + mu; stats.vars = xv; stats.df = dfe; stats.s = sqrt (MSE); endif ## Print results table on screen if no output argument was requested if (nargout == 0 || plotdata) switch (lower (vartype)) case 'equal' printf ("\n ANOVA Table\n\n"); printf ("Source SS df MS F Prob>F\n"); printf ("------------------------------------------------------\n"); printf ("Groups %10.4f %5.0f %10.4f %8.2f %9.4f\n", SSM, dfm, MSM, F, p); printf ("Error %10.4f %5.0f %10.4f\n", SSE, dfe, MSE); printf ("Total %10.4f %5.0f\n\n", SST, dfm + dfe); case 'unequal' printf ("\n Welch's ANOVA Table\n\n"); printf ("Source F df dfe Prob>F\n"); printf ("-----------------------------------------\n"); printf ("Groups %8.2f %5.0f %7.2f %10.4f\n\n", F, dfm, dfe, p); endswitch endif ## Plot data using BOXPLOT (unless opted out) if (plotdata) ## Create a new figure and ensure it becomes active f = figure (); set (0, 'currentfigure', f); ## Create a new axes inside this figure and make it current ax = axes ('parent', f); set (f, 'currentaxes', ax); ## Now boxplot will ALWAYS draw here boxplot (x, group_id, 'Notch', 'on', 'Labels', group_names); endif endfunction %!demo %! rng (42); %! x = meshgrid (1:6); %! x = x + normrnd (0, 1, 6, 6); %! anova1 (x, [], 'off'); %!demo %! rng (42); %! x = meshgrid (1:6); %! x = x + normrnd (0, 1, 6, 6); %! [p, atab] = anova1 (x); %!demo %! rng (42); %! x = ones (50, 4) .* [-2, 0, 1, 5]; %! x = x + normrnd (0, 2, 50, 4); %! groups = {'A', 'B', 'C', 'D'}; %! anova1 (x, groups); %!demo %! y = [54 87 45; 23 98 39; 45 64 51; 54 77 49; 45 89 50; 47 NaN 55]; %! g = [1 2 3 ; 1 2 3 ; 1 2 3 ; 1 2 3 ; 1 2 3 ; 1 2 3 ]; %! anova1 (y(:), g(:), 'on', 'unequal'); ## testing against GEAR.DAT data file and results for one-factor ANOVA from ## https://www.itl.nist.gov/div898/handbook/eda/section3/eda354.htm %!test %! data = [1.006, 0.996, 0.998, 1.000, 0.992, 0.993, 1.002, 0.999, 0.994, 1.000, ... %! 0.998, 1.006, 1.000, 1.002, 0.997, 0.998, 0.996, 1.000, 1.006, 0.988, ... %! 0.991, 0.987, 0.997, 0.999, 0.995, 0.994, 1.000, 0.999, 0.996, 0.996, ... %! 1.005, 1.002, 0.994, 1.000, 0.995, 0.994, 0.998, 0.996, 1.002, 0.996, ... %! 0.998, 0.998, 0.982, 0.990, 1.002, 0.984, 0.996, 0.993, 0.980, 0.996, ... %! 1.009, 1.013, 1.009, 0.997, 0.988, 1.002, 0.995, 0.998, 0.981, 0.996, ... %! 0.990, 1.004, 0.996, 1.001, 0.998, 1.000, 1.018, 1.010, 0.996, 1.002, ... %! 0.998, 1.000, 1.006, 1.000, 1.002, 0.996, 0.998, 0.996, 1.002, 1.006, ... %! 1.002, 0.998, 0.996, 0.995, 0.996, 1.004, 1.004, 0.998, 0.999, 0.991, ... %! 0.991, 0.995, 0.984, 0.994, 0.997, 0.997, 0.991, 0.998, 1.004, 0.997]; %! group = [1:10] .* ones (10,10); %! group = group(:); %! [p, tbl] = anova1 (data, group, 'off'); %! assert_equal (p, 0.022661, 1e-6); %! assert_equal (tbl{2,5}, 2.2969, 1e-4); %! assert_equal (tbl{2,3}, 9, 0); %! assert_equal (tbl{4,2}, 0.003903, 1e-6); %! data = reshape (data, 10, 10); %! [p, tbl, stats] = anova1 (data, [], 'off'); %! assert_equal (p, 0.022661, 1e-6); %! assert_equal (tbl{2,5}, 2.2969, 1e-4); %! assert_equal (tbl{2,3}, 9, 0); %! assert_equal (tbl{4,2}, 0.003903, 1e-6); %! means = [0.998, 0.9991, 0.9954, 0.9982, 0.9919, 0.9988, 1.0015, 1.0004, 0.9983, 0.9948]; %! N = 10 * ones (1, 10); %! assert_equal (stats.means, means, 1e-6); %! assert_equal (length (stats.gnames), 10, 0); %! assert_equal (stats.n, N, 0); ## testing against one-way ANOVA example dataset from GraphPad Prism 8 %!test %! y = [54 87 45; 23 98 39; 45 64 51; 54 77 49; 45 89 50; 47 NaN 55]; %! g = [1 2 3 ; 1 2 3 ; 1 2 3 ; 1 2 3 ; 1 2 3 ; 1 2 3 ]; %! [p, tbl] = anova1 (y(:), g(:), 'off', 'equal'); %! assert_equal (p, 0.00004163, 1e-6); %! assert_equal (tbl{2,5}, 22.573418, 1e-6); %! assert_equal (tbl{2,3}, 2, 0); %! assert_equal (tbl{3,3}, 14, 0); %! [p, tbl] = anova1 (y(:), g(:), 'off', 'unequal'); %! assert_equal (p, 0.00208877, 1e-8); %! assert_equal (tbl{2,5}, 15.523192, 1e-6); %! assert_equal (tbl{2,3}, 2, 0); %! assert_equal (tbl{2,4}, 7.5786897, 1e-6); ## testing against one-way ANOVA example dataset from GraphPad Prism 8 ## using categorical array as a grouping variable %!test %! y = [54, 87, 45; 23, 98, 39; 45, 64, 51; ... %! 54, 77, 49; 45, 89, 50; 47, NaN, 55]; %! g = categorical ([1, 2, 3; 1, 2, 3; 1, 2, 3; 1, 2, 3; 1, 2, 3; 1, 2, 3]); %! [p, tbl] = anova1 (y(:), g(:), 'off', 'equal'); %! assert_equal (p, 0.00004163, 1e-6); %! assert_equal (tbl{2,5}, 22.573418, 1e-6); %! assert_equal (tbl{2,3}, 2, 0); %! assert_equal (tbl{3,3}, 14, 0); %! [p, tbl] = anova1 (y(:), g(:), 'off', 'unequal'); %! assert_equal (p, 0.00208877, 1e-8); %! assert_equal (tbl{2,5}, 15.523192, 1e-6); %! assert_equal (tbl{2,3}, 2, 0); %! assert_equal (tbl{2,4}, 7.5786897, 1e-6); ## testing handling of missing values in both data and grouping variables %!test %! y = [10; 20; 9999; NaN; 40; 50]; %! g = [1; 1; NaN; 1; 2; 2]; %! [p, tbl, stats] = anova1 (y, g, 'off'); %! assert_equal (p, 0.051317, 1e-6); %! assert_equal (tbl{2,5}, 18, 1e-6); %! assert_equal (tbl{2,3}, 1, 0); %! assert_equal (tbl{3,3}, 2, 0); %! assert_equal (tbl{4,3}, 3, 0); %! assert_equal (stats.n, [2, 2], 0); ## Grouped variance remains stable when group means have large offsets. %!test %! y = [1e12 + (1:10), -1e12 + (1:10)](:); %! g = [ones(10, 1); 2 * ones(10, 1)]; %! [~, ~, stats] = anova1 (y, g, 'off', 'unequal'); %! assert_equal (stats.vars, [55 / 6, 55 / 6], 1e-10); ## A single group leaves no between-group degrees of freedom. %!test %! [p, tbl] = anova1 ((1:5)', ones (5, 1), 'off'); %! assert_equal (p, NaN); %! assert_equal (tbl{2, 3}, 0); %! assert_equal (tbl{3, 2}, 10); %! [p, tbl] = anova1 (7, 1, 'off'); %! assert_equal (p, NaN); %! assert_equal (tbl{3, 3}, 0); ## One observation per group leaves no residual, so the fit is exact. %!test %! [p, tbl] = anova1 ([1; 2], [1; 2], 'off'); %! assert_equal (p, 0); %! assert_equal (tbl{2, 2}, 0.5); %! assert_equal (tbl{2, 3}, 1); %! assert_equal (tbl{3, 2}, 0); %! assert_equal (tbl{3, 3}, 0); ## Identical observations leave no scale to test against, so F is 0/0. %!test %! [p, tbl] = anova1 (ones (6, 1), [1; 1; 1; 2; 2; 2], 'off'); %! assert_equal (p, NaN); %! assert_equal (tbl{2, 5}, NaN); %! assert_equal (tbl{2, 2}, 0); %! assert_equal (tbl{2, 3}, 1); %! assert_equal (tbl{3, 2}, 0); %! assert_equal (tbl{3, 3}, 4); ## An unused categorical level leaves every field of stats, not just ## gnames as in MATLAB, so the three stay indexable together. %!test %! y = (1:6)'; %! g = categorical ([1; 1; 3; 3; 3; 1], [1, 2, 3]); %! [p, tbl, stats] = anova1 (y, g, 'off'); %! [p_ref, tbl_ref] = anova1 (y, [1; 1; 2; 2; 2; 1], 'off'); %! assert_equal (p, p_ref, 1e-12); %! assert_equal (tbl, tbl_ref); %! assert_equal (stats.n, [3, 3]); %! assert_equal (stats.gnames, {'1'; '3'}); %! assert_equal (stats.means, [3, 4]); %! g = categorical ([3; 3; 1; 1], [3, 2, 1]); %! [~, ~, stats] = anova1 ((1:4)', g, 'off'); %! assert_equal (stats.gnames, {'3'; '1'}); %! assert_equal (stats.means, [1.5, 3.5]); ## Many factor levels retain the expected degrees of freedom. %!test %! g = kron ((1:120)', ones (2, 1)); %! [p, tbl, stats] = anova1 ((1:240)', g, 'off'); %! assert_equal (isfinite (p), true); %! assert_equal (tbl{2, 3}, 119); %! assert_equal (tbl{3, 3}, 120); %! assert_equal (stats.n, 2 * ones (1, 120)); ## testing handling of missing values in both data and grouping variables ## using categorical array as a grouping variable %!test %! y = [10; 20; 9999; NaN; 40; 50]; %! g = categorical ([1; 1; NaN; 1; 2; 2]); %! [p, tbl, stats] = anova1 (y, g, 'off'); %! assert_equal (p, 0.051317, 1e-6); %! assert_equal (tbl{2,5}, 18, 1e-6); %! assert_equal (tbl{2,3}, 1, 0); %! assert_equal (tbl{3,3}, 2, 0); %! assert_equal (tbl{4,3}, 3, 0); %! assert_equal (stats.n, [2, 2], 0); statistics-release-1.9.2/inst/Hypothesis_Testing/anova2.m000066400000000000000000000376431524624707500235410ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## Copyright (C) 2022 Andrew Penn ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} anova2 (@var{x}, @var{reps}) ## @deftypefnx {statistics} {@var{p} =} anova2 (@var{x}, @var{reps}, @var{displayopt}) ## @deftypefnx {statistics} {@var{p} =} anova2 (@var{x}, @var{reps}, @var{displayopt}, @var{model}) ## @deftypefnx {statistics} {[@var{p}, @var{atab}] =} anova2 (@dots{}) ## @deftypefnx {statistics} {[@var{p}, @var{atab}, @var{stats}] =} anova2 (@dots{}) ## ## Performs two-way factorial (crossed) or a nested analysis of variance ## (ANOVA) for balanced designs. For unbalanced factorial designs, diagnostic ## plots and/or planned contrasts, use @qcode{anovan} instead. ## ## @qcode{anova2} requires two input arguments with an optional third and ## fourth: ## ## @itemize ## @item ## @var{x} contains the data and it must be a matrix of at least two columns and ## two rows. @code{NaN} values are not accepted, since @code{anova2} requires a ## balanced design; use @code{anovan} for data with missing observations. ## ## @item ## @var{reps} is the number of replicates for each combination of factor groups. ## ## @item ## @var{displayopt} is an optional parameter for displaying the ANOVA table, ## when it is 'on' (default) and suppressing the display when it is 'off'. ## ## @item ## @var{model} is an optional parameter to specify the model type as either: ## ## @itemize ## @item ## "interaction" or "full" (default): compute both main effects and their ## interaction ## ## @item ## "linear": compute both main effects without an interaction. When @var{reps} ## > 1 the test is suitable for a balanced randomized block design. When ## @var{reps} == 1, the test becomes a One-way Repeated Measures (RM)-ANOVA ## with Greenhouse-Geisser correction to the column factor degrees of freedom ## to make the test robust to violations of sphericity ## ## @item ## "nested": treat the row factor as nested within columns. Note that the row ## factor is considered a random factor in the calculation of the statistics. ## ## @end itemize ## @end itemize ## ## @qcode{anova2} returns up to three output arguments: ## ## @itemize ## @item ## @var{p} is the p-value of the null hypothesis that all group means are equal. ## ## @item ## @var{atab} is a cell array containing the results in a standard ANOVA table. ## ## @item ## @var{stats} is a structure containing statistics useful for performing ## a multiple comparison of means with the MULTCOMPARE function. ## @end itemize ## ## If anova2 is called without any output arguments, then it prints the results ## in a one-way ANOVA table to the standard output as if @var{displayopt} is ## 'on'. ## ## Examples: ## ## @example ## load popcorn; ## anova2 (popcorn, 3); ## @end example ## ## ## @example ## [p, anovatab, stats] = anova2 (popcorn, 3, "off"); ## disp (p); ## @end example ## ## @seealso{anova1, anovan, multcompare} ## @end deftypefn function [p, anovatab, stats] = anova2 (x, reps, displayopt, model) ## Check for valid number of input arguments if (nargin < 1 || nargin >4) error ("anova2: invalid number of input arguments."); endif ## Check for NaN values in X if (any (isnan ( x(:)))) error ("anova2: NaN values in input are not allowed. Use anovan instead."); endif ## Add defaults if (nargin == 1) reps = 1; endif if (nargin < 3) displayopt = 'on'; endif if (nargin < 4) model = 'interaction'; endif epsilonhat = []; plotdata = ! (strcmp (displayopt, 'off')); ## Calculate group numbers FFGn = size (x, 1) / reps; ## Number of groups in Row Factor SFGn = size (x, 2); ## Number of groups in Column Factor ## Check for valid repetitions if (! (int16 (FFGn) == FFGn)) error ("anova2: the number of rows in X must be a multiple of REPS."); endif idx_s = 1; idx_e = reps; for i = 1:FFGn RIdx(i,:) = [idx_s:idx_e]; idx_s += reps; idx_e += reps; endfor ## Calculate group sample sizes GTsz = length (x(:)); ## Number of total samples FFGs = prod (size (x(RIdx(1,:),:))); ## Number of group samples of Row Factor SFGs = size (x, 1); ## Number of group samples of Column Factor ## Calculate group means GTmu = sum (x(:)) / GTsz; ## Grand mean of groups for i = 1:FFGn ## Group means of Row Factor FFGm(i) = mean (x(RIdx(i,:),:), 'all'); endfor for i = 1:SFGn ## Group means of Column Factor SFGm(i) = mean (x(:,i)); endfor ## Calculate Sum of Squares for Row and Column Factors SSR = sum (FFGs * ((FFGm - GTmu) .^ 2)); ## Rows Sum of Squares SSC = sum (SFGs * ((SFGm - GTmu) .^ 2)); ## Columns Sum of Squares ## Calculate Total Sum of Squares SST = (x(:) - GTmu)' * (x(:) - GTmu); ## Calculate Sum of Squares Error (Within) if (reps > 1) xcells = reshape (x, reps, FFGn, SFGn); xdev = bsxfun (@minus, xcells, mean (xcells, 1)); cell_sse = squeeze (sumsq (xdev, 1)); SSE = sum (cell_sse(:)); else SSE = SST - SSC - SSR; endif ## Calculate degrees of freedom and Sum of Squares Interaction (if applicable) df_SSR = FFGn - 1; ## Row Factor df_SSC = SFGn - 1; ## Column Factor if (reps > 1) df_SSE = GTsz - (FFGn * SFGn); ## Error with replication df_SSI = df_SSR * df_SSC; ## Interaction: Degrees of Freedom SSI = SST - SSR - SSC - SSE; ## Interaction: Sum of Squares else df_SSE = df_SSR * df_SSC; ## No replication, assuming additive model df_SSI = 0; SSI = 0; endif df_tot = GTsz - 1; ## Total ## Model-specific calculations of sums-of-squares, mean squares and degrees of ## freedom. The calculations are based on equalities for the partitioning of ## variance in fully balanced designs. switch (lower (model)) case {'interaction', 'full'} ## TWO-WAY ANOVA WITH INTERACTION (full factorial model) ## Sums--of-squares are already partitioned into main effects and ## interaction. Just calculate mean-squares and degrees of freedom model = 'interaction'; MSE = SSE / df_SSE; ## Mean Square for Error (Within) MSR = SSR / df_SSR; ## Mean Square for Row Factor MS_DENOM = MSE; df_DENOM = df_SSE; case 'linear' ## TWO-WAY ANOVA WITHOUT INTERACTION (additive, linear model) ## Pool Error and Interaction term model = 'linear'; SSE += SSI; df_SSE += df_SSI; SSI = 0; df_SSI = 0; if (reps == 1) ## Assume one-way repeated measures ANOVA. Perform calculations for a ## correction factor (epsilonhat) to make tests of the Column factor ## robust to violations of sphericity vcov = cov (x); N = SFGn^2 * (mean (diag (vcov)) - mean (mean (vcov)))^2; D = (SFGn - 1) * ... (sum (sumsq (vcov)) - 2 * SFGn * sum ((mean (vcov, 2).^2)) + ... SFGn^2 * mean (mean (vcov))^2); epsilonhat = N / D; dfN_GG = epsilonhat * (SFGn - 1); dfD_GG = epsilonhat * (FFGn - 1) * (SFGn - 1); endif reps = 1; ## Set reps to 1 to avoid printing interaction MSE = SSE / df_SSE; ## Mean Square for Error (Within) MSR = SSR / df_SSR; ## Mean Square for Row Factor MS_DENOM = MSE; df_DENOM = df_SSE; case 'nested' ## NESTED ANOVA ## Row Factor is nested within Column Factor. Treat Row factor as random. ## Pool Row Factor and Interaction term model = 'nested'; SSR += SSI; df_SSR += df_SSI; SSI = 0; df_SSI = 0; reps = 1; ## Set reps to 1 to avoid printing interaction MSE = SSE / df_SSE; ## Mean Square for Error (Within) MSR = SSR / df_SSR; ## Mean Square for Row Factor MS_DENOM = MSR; ## Row factor is random so MSR is denominator df_DENOM = df_SSR; ## Row factor is random so df_SSR is denominator otherwise error ("anova2: model type not recognised"); endswitch ## Calculate F statistics and p values F_MSR = MSR / MSE; ## F statistic for Row Factor p_MSR = 1 - fcdf (F_MSR, df_SSR, df_SSE); MSC = SSC / df_SSC; ## Mean Square for Column Factor F_MSC = MSC / MS_DENOM; ## F statistic for Column Factor if (isempty (epsilonhat)) p_MSC = 1 - fcdf (F_MSC, df_SSC, df_DENOM); else ## Apply correction for sphericity to the p-value of the column factor p_MSC = 1 - fcdf (F_MSC, dfN_GG, dfD_GG); endif ## With replication if (reps > 1) MSI = SSI / df_SSI; ## Mean Square for Interaction F_MSI = MSI / MSE; ## F statistic for Interaction p_MSI = 1 - fcdf (F_MSI, df_SSI, df_SSE); else MSI = 0; F_MSI = 0; p_MSI = NaN; endif ## Create p output (if requested) if (nargout > 0) if (reps > 1) p = [p_MSC, p_MSR, p_MSI]; else p = [p_MSC, p_MSR]; endif endif ## Create results table (if requested) if (nargout > 1 && reps > 1) anovatab = {'Source', 'SS', 'df', 'MS', 'F', 'Prob>F'; ... 'Columns', SSC, df_SSC, MSC, F_MSC, p_MSC; ... 'Rows', SSR, df_SSR, MSR, F_MSR, p_MSR; ... 'Interaction', SSI, df_SSI, MSI, F_MSI, p_MSI; ... 'Error', SSE, df_SSE, MSE, '', ''; ... 'Total', SST, df_tot, '', '', ''}; elseif (nargout > 1 && reps == 1) anovatab = {'Source', 'SS', 'df', 'MS', 'F', 'Prob>F'; ... 'Columns', SSC, df_SSC, MSC, F_MSC, p_MSC; ... 'Rows', SSR, df_SSR, MSR, F_MSR, p_MSR; ... 'Error', SSE, df_SSE, MSE, '', ''; ... 'Total', SST, df_tot, '', '', ''}; endif ## Create stats structure (if requested) for MULTCOMPARE if (nargout > 2) stats.source = 'anova2'; stats.sigmasq = MS_DENOM; ## MS used to calculate F relating to stats.pval stats.colmeans = SFGm(:)'; stats.coln = SFGs; stats.rowmeans = FFGm(:)'; stats.rown = FFGs; stats.inter = (reps > 1); if stats.inter stats.pval = p_MSI; ## Interaction p-value if stats.inter is true else stats.pval = p_MSC; ## Column Factor p-value if stats.inter is false endif stats.df = df_DENOM; ## Degrees of freedom used to calculate stats.pval stats.model = model; endif ## Print results table on screen if no output argument was requested if (nargout == 0 || plotdata) printf ("\n ANOVA Table\n\n"); printf ("Source SS df MS F Prob>F\n"); printf ("-----------------------------------------------------------\n"); printf ("Columns %10.4f %5.0f %10.4f %8.2f %9.4f\n", ... SSC, df_SSC, MSC, F_MSC, p_MSC); printf ("Rows %10.4f %5.0f %10.4f %8.2f %9.4f\n", ... SSR, df_SSR, MSR, F_MSR, p_MSR); if (reps > 1) printf ("Interaction %10.4f %5.0f %10.4f %8.2f %9.4f\n", ... SSI, df_SSI, MSI, F_MSI, p_MSI); endif printf ("Error %10.4f %5.0f %10.4f\n", SSE, df_SSE, MSE); printf ("Total %10.4f %5.0f\n\n", SST, df_tot); if (! isempty (epsilonhat)) printf (strcat ("Note: Greenhouse-Geisser's correction was applied to the\n", ... "degrees of freedom for the Column factor: F(%.2f,%.2f)\n\n"), ... dfN_GG, dfD_GG); endif if (strcmpi (model, 'nested')) printf (strcat ("Note: Rows are a random factor nested within the columns.\n", ... "The Column F statistic uses the Row MS instead of the MSE.\n\n")); endif endif endfunction %!demo %! %! # Factorial (Crossed) Two-way ANOVA with Interaction %! %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! %! [p, atab, stats] = anova2 (popcorn, 3, 'on'); %!demo %! %! # One-way Repeated Measures ANOVA (Rows are a crossed random factor) %! %! data = [54, 43, 78, 111; %! 23, 34, 37, 41; %! 45, 65, 99, 78; %! 31, 33, 36, 35; %! 15, 25, 30, 26]; %! %! [p, atab, stats] = anova2 (data, 1, 'on', 'linear'); %!demo %! %! # Balanced Nested One-way ANOVA (Rows are a nested random factor) %! %! data = [4.5924 7.3809 21.322; -0.5488 9.2085 25.0426; ... %! 6.1605 13.1147 22.66; 2.3374 15.2654 24.1283; ... %! 5.1873 12.4188 16.5927; 3.3579 14.3951 10.2129; ... %! 6.3092 8.5986 9.8934; 3.2831 3.4945 10.0203]; %! %! [p, atab, stats] = anova2 (data, 4, 'on', 'nested'); ## testing against popcorn data and results from Matlab %!test %! ## Test for anova2 ("interaction") %! ## comparison with results from Matlab for column effect %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! [p, atab, stats] = anova2 (popcorn, 3, 'off'); %! assert_equal (p(1), 7.678957383294716e-07, 1e-14); %! assert_equal (p(2), 0.0001003738963050171, 1e-14); %! assert_equal (p(3), 0.7462153966366274, 1e-14); %! assert_equal (atab{2,5}, 56.700, 1e-14); %! assert_equal (atab{2,3}, 2, 0); %! assert_equal (atab{4,2}, 0.08333333333333348, 1e-14); %! assert_equal (atab{5,4}, 0.1388888888888889, 1e-14); %! assert_equal (atab{5,2}, 1.666666666666667, 1e-14); %! assert_equal (atab{6,2}, 22); %! assert_equal (stats.source, "anova2"); %! assert_equal (stats.colmeans, [6.25, 4.75, 4]); %! assert_equal (stats.inter, true); %! assert_equal (stats.pval, 0.7462153966366274, 1e-14); %! assert_equal (stats.df, 12); %!test %! ## Test for anova2 ("linear") - comparison with results from GraphPad Prism 8 %! data = [54, 43, 78, 111; %! 23, 34, 37, 41; %! 45, 65, 99, 78; %! 31, 33, 36, 35; %! 15, 25, 30, 26]; %! [p, atab, stats] = anova2 (data, 1, 'off', 'linear'); %! assert_equal (atab{2,2}, 2174.95, 1e-10); %! assert_equal (atab{3,2}, 8371.7, 1e-10); %! assert_equal (atab{4,2}, 2404.3, 1e-10); %! assert_equal (atab{5,2}, 12950.95, 1e-10); %! assert_equal (atab{2,4}, 724.983333333333, 1e-10); %! assert_equal (atab{3,4}, 2092.925, 1e-10); %! assert_equal (atab{4,4}, 200.358333333333, 1e-10); %! assert_equal (atab{2,5}, 3.61843363972882, 1e-10); %! assert_equal (atab{3,5}, 10.445909412303, 1e-10); %! assert_equal (atab{2,6}, 0.087266112738617, 1e-10); %! assert_equal (atab{3,6}, 0.000698397753556, 1e-10); %!test %! ## Test for anova2 ("nested") - comparison with results from GraphPad Prism 8 %! data = [4.5924 7.3809 21.322; -0.5488 9.2085 25.0426; ... %! 6.1605 13.1147 22.66; 2.3374 15.2654 24.1283; ... %! 5.1873 12.4188 16.5927; 3.3579 14.3951 10.2129; ... %! 6.3092 8.5986 9.8934; 3.2831 3.4945 10.0203]; %! [p, atab, stats] = anova2 (data, 4, 'off', 'nested'); %! assert_equal (atab{2,2}, 745.360306290833, 1e-10); %! assert_equal (atab{3,2}, 278.01854140125, 1e-10); %! assert_equal (atab{4,2}, 180.180377467501, 1e-10); %! assert_equal (atab{5,2}, 1203.55922515958, 1e-10); %! assert_equal (atab{2,4}, 372.680153145417, 1e-10); %! assert_equal (atab{3,4}, 92.67284713375, 1e-10); %! assert_equal (atab{4,4}, 10.0100209704167, 1e-10); %! assert_equal (atab{2,5}, 4.02146005730833, 1e-10); %! assert_equal (atab{3,5}, 9.25800729165627, 1e-10); %! assert_equal (atab{2,6}, 0.141597630656771, 1e-10); %! assert_equal (atab{3,6}, 0.000636643812875719, 1e-10); statistics-release-1.9.2/inst/Hypothesis_Testing/anovan.m000066400000000000000000003215031524624707500236240ustar00rootroot00000000000000## Copyright (C) 2003-2005 Andy Adler ## Copyright (C) 2021 Christian Scholz ## Copyright (C) 2022 Andreas Bertsatos ## Copyright (C) 2022 Andrew Penn ## Copyright (C) 2024 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} anovan (@var{Y}, @var{GROUP}) ## @deftypefnx {statistics} {@var{p} =} anovan (@var{Y}, @var{GROUP}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{p}, @var{atab}] =} anovan (@dots{}) ## @deftypefnx {statistics} {[@var{p}, @var{atab}, @var{stats}] =} anovan (@dots{}) ## @deftypefnx {statistics} {[@var{p}, @var{atab}, @var{stats}, @var{terms}] =} anovan (@dots{}) ## ## Perform a multi (N)-way analysis of (co)variance (ANOVA or ANCOVA) to ## evaluate the effect of one or more categorical or continuous predictors (i.e. ## independent variables) on a continuous outcome (i.e. dependent variable). The ## algorithms used make @code{anovan} suitable for balanced or unbalanced ## factorial (crossed) designs. By default, @code{anovan} treats all factors ## as fixed. Examples of function usage can be found by entering the command ## @code{demo anovan}. A bootstrap resampling variant of this function, ## @code{bootlm}, is available in the statistics-resampling package and has ## similar usage. ## ## Data is a single vector @var{Y} with groups specified by a corresponding ## matrix or cell array of group labels @var{GROUP}, where each column of ## @var{GROUP} has the same number of rows as @var{Y}. For example, if ## @code{@var{Y} = [23; 27; 31; 29; 30; 32]; @var{GROUP} = [1, 2; 1, 3; 1, 2; 2, ## 3; 2, 3; 3, ## 2];} ## then observation 23 was measured under conditions 1,2; observation 27 was ## measured under conditions 1,3; and so on. If the @var{GROUP} provided is ## empty, then the linear model is fit with just the intercept (no predictors). ## ## @code{anovan} can take a number of optional parameters as name-value pairs. ## ## @code{[@dots{}] = anovan (@var{Y}, @var{GROUP}, "continuous", ## @var{continuous})} ## ## @itemize ## @item ## @var{continuous} is a vector of indices indicating which of the columns (i.e. ## factors) in @var{GROUP} should be treated as continuous predictors rather ## than as categorical predictors. The relationship between continuous ## predictors and the outcome should be linear. ## @end itemize ## ## @code{[@dots{}] = anovan (@var{Y}, @var{GROUP}, "random", @var{random})} ## ## @itemize ## @item ## @var{random} is a vector of indices indicating which of the columns (i.e. ## factors) in @var{GROUP} should be treated as random effects rather than ## fixed effects. ## ## In the table @code{anovan} prints, the name of a random factor is followed ## by a @qcode{'} so that it can be told apart at a glance. That mark is a ## convention of the printed output only: the names returned in @var{atab}, ## in @var{stats}.varnames, and inside the expected-mean-square and ## denominator expressions carry no mark, so that they read as MATLAB's do. ## Which terms are random is reported in the @qcode{"Type"} column of ## @var{atab} instead. ## ## Every interaction involving a random factor stays in the model, and each ## F ratio is taken against the denominator its expected mean square calls ## for rather than against the mean squared error. The expected mean square ## of a term names the variance each component contributes to it, written ## @code{Q(@dots{})} for a fixed term and @code{V(@dots{})} for a random one; ## the denominator is the combination of mean squares whose expectation ## matches the term's with the term itself removed. Often that is a single ## mean square, in which case the F ratio carries its degrees of freedom. ## When no single mean square will do, one is synthesised from several and ## carries Satterthwaite degrees of freedom, which are generally not whole ## numbers. ## ## The variance component of every random term is estimated from the same ## system, and reported with confidence bounds. A component estimated as ## negative has no interval, as it lies outside the parameter space. ## @end itemize ## ## @code{[@dots{}] = anovan (@var{Y}, @var{GROUP}, "model", @var{modeltype})} ## ## @itemize ## @item ## @var{modeltype} can specified as one of the following: ## ## @itemize ## @item ## "linear" (default) : compute @math{N} main effects with no interactions. ## ## @item ## "interaction" : compute @math{N} effects and @math{N*(N-1)} two-factor ## interactions ## ## @item ## "full" : compute the @math{N} main effects and interactions at all levels ## ## @item ## a scalar integer : representing the maximum interaction order ## ## @item ## a matrix of term definitions : each row is a term and each column is a ## factor. Entries are nonnegative integer exponents. Exponents greater than ## one are valid only for factors selected by @qcode{"continuous"}. ## @end itemize ## ## @example ## -- Example: ## A two-way ANOVA with interaction would be: [1 0; 0 1; 1 1] ## @end example ## ## @end itemize ## ## @code{[@dots{}] = anovan (@var{Y}, @var{GROUP}, "nested", @var{nested})} ## ## @itemize ## @item ## @var{nested} is an N-by-N logical matrix, where N is the number of factors. ## A true entry @code{@var{nested}(i,j)} specifies that factor i is nested in ## factor j. A factor may be nested in more than one parent. Nested factors ## must be categorical and use the default contrasts. ## @end itemize ## ## @code{[@dots{}] = anovan (@var{Y}, @var{GROUP}, "sstype", @var{sstype})} ## ## @itemize ## @item ## @var{sstype} can specified as one of the following: ## ## @itemize ## @item ## 1 : Type I sequential sums-of-squares. ## ## @item ## 2 : Type II partially sequential sums-of-squares. Each term is adjusted ## for every other term that does not contain it. ## ## @item ## "h" : hierarchical sums-of-squares. Each term is adjusted only for the ## terms below it in the hierarchy, so for a model whose terms are all of ## first order it agrees with Type II, while a polynomial term is adjusted ## for its lower powers but not for its higher ones. ## ## @item ## 3 (default) : Type III partial, constrained or marginal sums-of-squares ## ## @end itemize ## @end itemize ## ## @code{[@dots{}] = anovan (@var{Y}, @var{GROUP}, "varnames", @var{varnames})} ## ## @itemize ## @item ## @var{varnames} must be a cell array of strings with each element containing a ## factor name for each column of @var{GROUP}. By default (if not parsed as ## optional argument), @var{varnames} are "X1","X2","X3", etc. ## @end itemize ## ## @code{[@dots{}] = anovan (@var{Y}, @var{GROUP}, "alpha", @var{alpha})} ## ## @itemize ## @item ## @var{alpha} must be a scalar value between 0 and 1 requesting ## @math{100*(1-@var{alpha})%} confidence bounds for the regression coefficients ## returned in @var{stats}.coeffs (default 0.05 for 95% confidence). ## @end itemize ## ## @code{[@dots{}] = anovan (@var{Y}, @var{GROUP}, "display", @var{dispopt})} ## ## @itemize ## @item ## @var{dispopt} can be either "on" (default) or "off" and controls the display ## of the model formula, table of model parameters, the ANOVA table and the ## diagnostic plots. The F-statistic and p-values are formatted in APA-style. ## To avoid p-hacking, the table of model parameters is only displayed if we set ## planned contrasts (see below). ## @end itemize ## ## @code{[@dots{}] = anovan (@var{Y}, @var{GROUP}, "contrasts", ## @var{contrasts})} ## ## @itemize ## @item ## @var{contrasts} can be specified as one of the following: ## ## @itemize ## @item ## A string corresponding to one of the built-in contrasts listed below: ## ## @itemize ## @item ## "simple" or "anova" (default): Simple (ANOVA) contrast coding. (The first ## level appearing in the @var{GROUP} column is the reference level) ## ## @item ## "poly": Polynomial contrast coding for trend analysis. ## ## @item ## "helmert": Helmert contrast coding: the difference between each level with ## the mean of the subsequent levels. ## ## @item ## "effect": Deviation effect coding. (The first level appearing in the ## @var{GROUP} column is omitted). ## ## @item ## "sdif" or "sdiff": Successive differences contrast coding: the difference ## between each level with the previous level. ## ## @item ## "treatment": Treatment contrast (or dummy) coding. (The first level appearing ## in the @var{GROUP} column is the reference level). These contrasts are not ## compatible with @var{sstype} = 3. ## ## @end itemize ## ## @item ## A matrix containing a custom contrast coding scheme (i.e. the generalized ## inverse of contrast weights). Rows in the contrast matrices correspond to ## factor levels in the order that they first appear in the @var{GROUP} column. ## The matrix must contain the same number of columns as there are the number of ## factor levels minus one. ## @end itemize ## ## If the anovan model contains more than one factor and a built-in contrast ## coding scheme was specified, then those contrasts are applied to all factors. ## To specify different contrasts for different factors in the model, ## @var{contrasts} should be a cell array with the same number of cells as there ## are columns in @var{GROUP}. Each cell should define contrasts for the ## respective column in @var{GROUP} by one of the methods described above. If ## cells are left empty, then the default contrasts are applied. Contrasts for ## cells corresponding to continuous factors are ignored. ## @end itemize ## ## @code{[@dots{}] = anovan (@var{Y}, @var{GROUP}, "weights", @var{weights})} ## ## @itemize ## @item ## @var{weights} is an optional vector of weights to be used when fitting the ## linear model. Weighted least squares (WLS) is used with weights (that is, ## minimizing @code{sum (@var{weights} * @var{residuals} .^ 2))}; otherwise ## ordinary least squares (OLS) is used (default is empty for OLS). ## @end itemize ## ## @code{anovan} can return up to four output arguments: ## ## @code{@var{p} = anovan (@dots{})} returns a vector of p-values, one for each ## term. ## ## @code{[@var{p}, @var{atab}] = anovan (@dots{})} returns a cell array ## containing the ANOVA table. Its first row holds the column names, and the ## columns are, in order, the term name, its sum-of-squares, its degrees of ## freedom, a singularity flag, its mean square, the @math{F} statistic, the ## p-value, and then two effect sizes: eta squared and partial eta squared. ## The first seven follow MATLAB's layout, so a caller reading them by ## position gets the same quantity in either language; the two effect sizes ## are an Octave extension and are appended after them. ## ## A model naming any factor as random reports eight further columns after ## the p-value, as MATLAB does, with the two effect sizes still last: the ## term's type, its expected mean square, the mean square and degrees of ## freedom of the denominator its F ratio was taken against, the definition of ## that denominator, and the variance component of a random term with its ## confidence bounds. A denominator that no single mean square provides is ## synthesised from several and carries Satterthwaite degrees of freedom, ## which are generally not whole numbers. ## ## The singularity flag is @math{1} when a term is aliased with the rest of ## the model, which happens when the design is not of full rank, most often ## because a combination of factor levels holds no observations. The degrees ## of freedom reported for such a term are the ones that can be estimated, ## which may be fewer than the term's design block has columns and may be ## zero. Whether a term is aliased depends on the model it is adjusted for, ## and therefore on @qcode{"sstype"}: a term that is estimable in a sequential ## fit may not be estimable in a marginal one. A flagged term's ## sum-of-squares is not uniquely attributable to it, so the corresponding ## @math{F} and p-value should not be read as a test of that term. ## ## @code{[@var{p}, @var{atab}, @var{stats}] = anovan (@dots{})} returns a ## structure containing additional statistics, including degrees of freedom and ## effect sizes for each term in the linear model, the design matrix, the ## variance-covariance matrix, (weighted) model residuals, and the mean squared ## error. The columns of @var{stats}.coeffs (from left-to-right) report the ## model coefficients, standard errors, lower and upper @math{100*(1-alpha)%} ## confidence interval bounds, t-statistics, and p-values relating to the ## contrasts. The number appended to each term name in @var{stats}.coeffnames ## corresponds to the column number in the relevant contrast matrix for that ## factor. The @var{stats} structure can be used as input for ## @code{multcompare}. ## ## @code{[@var{p}, @var{atab}, @var{stats}, @var{terms}] = anovan (@dots{})} ## returns the model term definitions. ## ## @seealso{anova1, anova2, multcompare, fitlm} ## @end deftypefn function [P, T, STATS, TERMS] = anovan (Y, GROUP, varargin) if (nargin < 2) print_usage; endif ## Check supplied parameters if ((numel (varargin) / 2) != fix (numel (varargin) / 2)) error ("anovan: wrong number of arguments.") endif MODELTYPE = 'linear'; DISPLAY = 'on'; SSTYPE = 3; VARNAMES = []; CONTINUOUS = []; RANDOM = []; NESTED = []; CONTRASTS = {}; ALPHA = 0.05; WEIGHTS = []; for idx = 3:2:nargin name = varargin{idx-2}; value = varargin{idx-1}; switch (lower (name)) case 'model' MODELTYPE = value; case 'continuous' CONTINUOUS = value; case 'random' RANDOM = value; case 'nested' NESTED = value; case 'sstype' SSTYPE = value; case 'varnames' VARNAMES = value; case {'display','displayopt'} DISPLAY = value; case 'contrasts' CONTRASTS = value; case 'alpha' ALPHA = value; case 'weights' WEIGHTS = value; otherwise error ("anovan: parameter '%s' is not supported.", name); endswitch endfor ## Evaluate continuous input argument if (isnumeric (CONTINUOUS)) if (any (CONTINUOUS != abs (fix (CONTINUOUS)))) error (strcat ("anovan: the value provided for the CONTINUOUS", ... " parameter must be a positive integer.")); endif else error (strcat ("anovan: the value provided for the CONTINUOUS", ... " parameter must be numeric.")); endif ## Accommodate for different formats for GROUP ## GROUP can be a matrix of numeric identifiers of a cell arrays ## of strings or numeric identifiers N = size (GROUP, 2); # number of anova "ways" n = numel (Y); # total number of observations categorical_levels = cell (1, N); categorical_missing = false (n, 1); if (prod (size (Y)) != n) error ("anovan: for ""anovan (Y, GROUP)"", Y must be a vector."); endif if (numel (unique (CONTINUOUS)) > N) error (strcat ("anovan: the number of factors assigned as continuous", ... " cannot exceed the number of factors in GROUP.")); endif if (any ((CONTINUOUS > N) || any (CONTINUOUS <= 0))) error (strcat ("anovan: one or more indices provided in the value", ... " for the continuous parameter are out of range.")); endif cont_vec = false (1, N); cont_vec(CONTINUOUS) = true; if (isempty (NESTED)) NESTED = false (N); elseif (! (isnumeric (NESTED) || islogical (NESTED)) ... || ! isequal (size (NESTED), [N, N]) ... || any (! ismember (NESTED(:), [0, 1])) ... || any (diag (NESTED))) error (strcat ("anovan: NESTED must be an N-by-N logical matrix", ... " with a zero diagonal.")); else NESTED = logical (NESTED); endif closure = NESTED; for factor = 1:N closure |= closure(:, factor) * closure(factor, :); endfor if (any (diag (closure))) error ("anovan: NESTED factor relationships must be acyclic."); endif nested_factors = any (NESTED, 2)' | any (NESTED, 1); if (any (cont_vec & nested_factors)) error ("anovan: nested factors must be categorical."); endif if (isa (GROUP, "categorical")) for j = 1:N if (ismember (j, CONTINUOUS)) error ("anovan: continuous factors must be a numeric datatype."); endif categorical_levels{j} = categories (GROUP(:,j)); categorical_missing |= isundefined (GROUP(:,j)); endfor GROUP = double (GROUP); elseif (iscell (GROUP)) if (size (GROUP, 1) == 1) tmp = cell (n, N); for j = 1:N if (isa (GROUP{j}, "categorical")) if (ismember (j, CONTINUOUS)) error ("anovan: continuous factors must be a numeric datatype."); endif categorical_levels{j} = categories (GROUP{j}); categorical_missing |= isundefined (GROUP{j}(:)); tmp(:,j) = num2cell (double (GROUP{j}(:))); elseif (isnumeric (GROUP{j})) if (ismember (j, CONTINUOUS)) tmp(:,j) = num2cell (GROUP{j}); else tmp(:,j) = cellstr (num2str (GROUP{j})); endif else if (ismember (j, CONTINUOUS)) error ("anovan: continuous factors must be a numeric datatype."); endif tmp(:,j) = GROUP{j}; endif endfor GROUP = tmp; endif endif if (! isempty (GROUP)) if (size (GROUP,1) != n) error (strcat ("anovan: GROUP must be a matrix", ... " with the same number of rows as Y.")); endif endif if (! isempty (VARNAMES)) if (iscell (VARNAMES)) if (all (cellfun (@ischar, VARNAMES))) nvarnames = numel (VARNAMES); else error (strcat ("anovan: all variable names must", ... " be character or character arrays.")); endif elseif (ischar (VARNAMES)) nvarnames = 1; VARNAMES = {VARNAMES}; elseif (isstring (VARNAMES)) nvarnames = 1; VARNAMES = {char(VARNAMES)}; else error (strcat ("anovan: varnames is not of a valid type.", ... " Must be a cell array of character arrays,", ... " character array or string.")); endif else nvarnames = N; VARNAMES = arrayfun (@(x) ['X',num2str(x)], 1:N, 'UniformOutput', 0); endif if (nvarnames != N) error (strcat ("anovan: number of variable names is not equal", ... " to the number of grouping variables.")); endif ## Evaluate random argument (if applicable) if (! isempty (RANDOM)) if (isnumeric (RANDOM)) if (any (RANDOM != abs (fix (RANDOM)))) error (strcat ("anovan: the value provided for the RANDOM", ... " parameter must be a positive integer.")); endif else error (strcat ("anovan: the value provided for the RANDOM", ... " parameter must be numeric.")); endif if (numel (RANDOM) > N) error (strcat ("anovan: the number of elements in RANDOM cannot", ... " exceed the number of columns in GROUP.")); endif if (max (RANDOM) > N) error (strcat ("anovan: the indices listed in RANDOM cannot", ... " exceed the number of columns in GROUP.")); endif endif ## Evaluate contrasts (if applicable) if isempty (CONTRASTS) CONTRASTS = cell (1, N); planned = false; else if (ischar (CONTRASTS)) contr_str = CONTRASTS; CONTRASTS = cell (1, N); CONTRASTS(:) = {contr_str}; endif if (! iscell (CONTRASTS)) CONTRASTS = {CONTRASTS}; endif for i = 1:N if (! isempty (CONTRASTS{i})) msg = strcat ("columns in CONTRASTS must sum to", ... " 0 for SSTYPE 3. Switching to SSTYPE 2 instead."); if (isnumeric (CONTRASTS{i})) ## Check whether all the columns sum to 0 if (any (abs (sum (CONTRASTS{i})) > eps ('single'))) warning (strcat ("Note that the CONTRASTS for predictor", ... " %u do not sum to zero."), i); endif ## Check whether contrasts are orthogonal if (any (abs (reshape (corr (CONTRASTS{i}) - ... eye (size (CONTRASTS{i}, 2)), [], 1))... > eps ('single'))) warning (strcat ("Note that the CONTRASTS for predictor", ... " %u are not orthogonal."), i); endif else if (! ismember (lower (CONTRASTS{i}), ... {'simple','anova','poly','helmert','effect',... 'sdif','sdiff','treatment'})) error (strcat ("anovan: valid built-in contrasts are:", ... " 'simple', 'poly', 'helmert',",... "'effect', 'sdif' or 'treatment'.")); endif if (strcmpi (CONTRASTS{i}, 'treatment') && (SSTYPE==3)) warning (msg); SSTYPE = 2; endif endif endif endfor planned = true; endif ## Evaluate alpha input argument if (! isa (ALPHA,'numeric') || numel (ALPHA) != 1) error ("anovan: alpha must be a numeric scalar value."); endif if ((ALPHA <= 0) || (ALPHA >= 1)) error ("anovan: alpha must be a value between 0 and 1."); endif ## Remove NaN or non-finite observations if (isempty (GROUP)) excl = any ([isnan(Y), isinf(Y)], 2); else XC = GROUP(:,CONTINUOUS); if iscell (XC) XC = cell2mat (XC); endif excl = any ([isnan(Y), isinf(Y), any(isnan(XC),2), any(isinf(XC),2)], 2) ... | categorical_missing; GROUP(excl,:) = []; endif Y(excl) = []; if (size (Y, 1) == 1) Y = Y.'; # if Y is a row vector, make it a column vector endif n = numel (Y); # recalculate total number of observations ## Evaluate weights input argument if (! isempty (WEIGHTS)) if (! isnumeric (WEIGHTS)) error ("anovan: WEIGHTS must be a numeric datatype."); endif if (any (size (WEIGHTS) != [n,1])) error (strcat ("anovan: WEIGHTS must be a vector", ... " with the same dimensions as Y.")); endif if (any (! (WEIGHTS > 0)) || any (isinf (WEIGHTS))) error ("anovan: WEIGHTS must be a vector of positive finite values."); endif # Create diagonal matrix of normalized weights W = diag (WEIGHTS / mean (WEIGHTS)); else # Create identity matrix W = eye (n);; endif ## Evaluate model type input argument and create terms matrix if not provided msg = strcat ("anovan: the number of columns in the term definitions", ... " cannot exceed the number of columns of GROUP."); if (ischar (MODELTYPE)) switch (lower (MODELTYPE)) case 'linear' MODELTYPE = 1; case {'interaction','interactions'} MODELTYPE = 2; case 'full' MODELTYPE = N; otherwise error ("anovan: model type not recognised"); endswitch endif if (isscalar (MODELTYPE)) TERMS = cell (MODELTYPE,1); v = false (1, N); switch (lower (MODELTYPE)) case 1 ## Create term definitions for an additive linear model TERMS = eye (N); case 2 ## Create term definitions for a model with two factor interactions if (N > 1) Nx = nchoosek (N, 2); else Nx = 0; endif TERMS = zeros (N + Nx, N); TERMS(1:N,:) = eye (N); cnt = N + 1; for j = 1:N for i = j:N-1 TERMS(cnt,j) = 1; TERMS(cnt,i+1) = 1; cnt++; endfor endfor otherwise if (MODELTYPE > N) error (msg); endif ## Create term definitions for a full model Nx = zeros (1, N-1); Nx = 0; for k = 1:N Nx = Nx + nchoosek (N,k); endfor for j = 1:MODELTYPE v(1:j) = 1; TERMS(j) = flipud (unique (perms (v), 'rows')); endfor TERMS = cell2mat (TERMS); endswitch TERMS = double (TERMS); else ## Assume that the user provided a suitable matrix of term definitions if (size (MODELTYPE, 2) != N) error (msg); endif if (! isnumeric (MODELTYPE) || any (! isfinite (MODELTYPE(:))) ... || any (MODELTYPE(:) < 0) ... || any (MODELTYPE(:) != fix (MODELTYPE(:)))) error (strcat ("anovan: elements of the model terms", ... " matrix must be nonnegative integers.")); endif TERMS = double (MODELTYPE); endif ## Evaluate terms matrix TERMS(! any (TERMS > 0, 2), :) = []; Ng = sum (TERMS > 0, 2); ## Evaluate terms Nt = rows (TERMS); ## Calculate total sum-of-squares ct = sum (Y)^2 / n; % correction term sst = sum (Y.^2) - ct; dft = n - 1; ## Create design matrix mDesignMatrix (); ## Fit linear models, and calculate sums-of-squares for ANOVA sstype_id = lower (SSTYPE); switch (sstype_id) case 1 ## Type I sequential sums-of-squares (SSTYPE = 1) R = sst; ss = zeros (Nt,1); df_est = zeros (Nt,1); [jnk, jnk, jnk, jnk, jnk, rk_prev] = lmfit (cell2mat (X(1)), Y, W); for j = 1:Nt XS = cell2mat (X(1:j+1)); [b, sse, jnk, jnk, jnk, rk_now] = lmfit (XS, Y, W); ss(j) = R - sse; df_est(j) = rk_now - rk_prev; R = sse; rk_prev = rk_now; endfor [b, sse, resid, ucov, hat] = lmfit (XS, Y, W); sstype_char = 'I'; case {2,'h'} ## Type II (partially sequential, or hierarchical) sums-of-squares ss = zeros (Nt,1); df_est = zeros (Nt,1); for j = 1:Nt i = find (TERMS(j,:) > 0); if (isequal (sstype_id, 'h')) excludes_term = any (TERMS(:,i) < TERMS(j,i), 2); else excludes_term = any (TERMS(:,i) != TERMS(j,i), 2); endif k = cat (1, 1, 1 + find (excludes_term)); XS = cell2mat (X(k)); [jnk, R1, jnk, jnk, jnk, rk1] = lmfit (XS, Y, W); k = cat (1, j+1, k); XS = cell2mat (X(k)); [jnk, R2, jnk, jnk, jnk, rk2] = lmfit (XS, Y, W); ss(j) = R1 - R2; df_est(j) = rk2 - rk1; endfor [b, sse, resid, ucov, hat] = lmfit (cell2mat (X), Y, W); sstype_char = 'II'; case 3 ## Type III (partial, constrained or marginal) sums-of-squares ss = zeros (Nt, 1); df_est = zeros (Nt, 1); [b, sse, resid, ucov, hat, rk_full] = lmfit (cell2mat (X), Y, W); for j = 1:Nt XS = cell2mat (X(1:Nt+1 != j+1)); [jnk, R, jnk, jnk, jnk, rk_red] = lmfit (XS, Y, W); ss(j) = R - sse; df_est(j) = rk_full - rk_red; endfor sstype_char = 'III'; otherwise error ("anovan: sstype value not supported."); endswitch ss = max (0, ss); # Truncate negative SS at 0 ## A term that is aliased with the rest of the model contributes fewer ## degrees of freedom than its design block has columns. Report what is ## estimable and flag the shortfall, so a sum-of-squares of zero can be ## told apart from a factor that genuinely has no effect. singular = (df_est < df); df_coef = df; ## nominal width of each term's block, for the coefficients df = df_est; ## estimable degrees of freedom, as reported in the table dfe = n - rank (cell2mat (X)); ms = ss ./ df; if (dfe > 0) mse = sse / dfe; else ## A saturated model leaves no residual degrees of freedom. The ## residual sum-of-squares is zero up to rounding, so report both as ## zero rather than letting 0/0 become Inf and poison every mean square ## that is tested against the error term. sse = 0; mse = 0; endif eta_sq = ss ./ sst; partial_eta_sq = ss ./ (ss + sse); ## With every factor fixed, each mean square is tested against the error. ## A random factor changes that: the expected mean squares decide which ## mean square, or which combination of them, estimates the same variance ## as the term under test, and that becomes the denominator. is_random = false (Nt, 1); ems_coef = []; txtems = {}; denom = []; msdenom = []; dfdenom = []; txtdenom = {}; varest = []; varci = []; if (isempty (RANDOM)) msdenom = repmat (mse, Nt, 1); dfdenom = repmat (dfe, Nt, 1); else [is_random, ems_coef, txtems, denom, msdenom, dfdenom, ... txtdenom, varest, varci] = mRandomEffects (TERMS, RANDOM, ... NESTED, X, gid, CONTINUOUS, ... sstype_id, df, ms, mse, dfe, ... ALPHA, VARNAMES, Nt); endif F = ms ./ msdenom; P = 1 - fcdf (F, df, dfdenom); ## The denominator columns describe a random-effects fit, so they stay ## empty when every factor is fixed and the error term is the denominator. if (isempty (RANDOM)) msdenom_out = []; dfdenom_out = []; rtnames = {}; else msdenom_out = msdenom; dfdenom_out = dfdenom; rtnames = [cellfun(@(t) mTermName (t, VARNAMES, NESTED), ... num2cell (TERMS(is_random,:), 2), ... 'UniformOutput', false); {'Error'}]; endif ## Prepare model formula and cell array containing the ANOVA table ## Columns 1 to 7 follow MATLAB's layout. The two effect sizes are an ## Octave extension and are appended, so a caller indexing the shared ## columns by position reads the same quantity in either language. if (isempty (RANDOM)) T = cell (Nt + 3, 9); T(1,:) = {'Source', 'Sum Sq.', 'd.f.', 'Singular?', 'Mean Sq.', 'F', ... 'Prob>F', 'Eta Sq.', 'Part. Eta Sq.'}; T(2:Nt+1,2:9) = num2cell ([ss, df, singular, ms, F, P, eta_sq, ... partial_eta_sq]); else ## A random factor adds the eight columns MATLAB adds, describing what ## each mean square estimates and what it was tested against. The two ## effect sizes stay last, as they are in the fixed-effects layout. T = cell (Nt + 3, 17); T(1,:) = {'Source', 'Sum Sq.', 'd.f.', 'Singular?', 'Mean Sq.', 'F', ... 'Prob>F', 'Type', 'Expected MS', 'MS denom', 'd.f. denom', ... 'Denom. defn.', 'Var. est.', 'Var. lower bnd', ... 'Var. upper bnd', 'Eta Sq.', 'Part. Eta Sq.'}; T(2:Nt+1,2:7) = num2cell ([ss, df, singular, ms, F, P]); types = repmat ({'fixed'}, Nt, 1); types(is_random) = {'random'}; T(2:Nt+1,8) = types; T(2:Nt+1,9) = txtems(1:Nt); T(2:Nt+1,10) = num2cell (msdenom); T(2:Nt+1,11) = num2cell (dfdenom); T(2:Nt+1,12) = txtdenom; rterms = find (is_random); for k = 1:numel (rterms) T(1+rterms(k),13:15) = {varest(k), varci(k,1), varci(k,2)}; endfor T(2:Nt+1,16:17) = num2cell ([eta_sq, partial_eta_sq]); T(end-1,8:9) = {'random', txtems{end}}; T(end-1,13:15) = {varest(end), varci(end,1), varci(end,2)}; endif T(end-1,1:5) = {'Error', sse, dfe, 0, mse}; T(end,1:4) = {'Total', sst, dft, 0}; formula = sprintf ("Y ~ 1"); # Initialize model formula for i = 1:Nt str = mTermName (TERMS(i,:), VARNAMES, NESTED); T(i+1,1) = str; ## Append model term to formula if (any (ismember (find (TERMS(i,:) > 0), RANDOM))) ## Random intercept term formula = sprintf ("%s + (1|%s)", formula, str); ## Remove statistics for random factors from the ANOVA table #T(RANDOM+1,4:7) = cell(1,4); #P(RANDOM) = NaN; else ## Fixed effect term formula = sprintf ("%s + %s", formula, str); endif endfor ## Calculate a standard error, t-statistic and p-value for each ## of the regression coefficients (fixed effects only) t_crit = tinv (1 - ALPHA / 2, dfe); se = sqrt (diag (ucov) * mse); t = b ./ se; p = 2 * (1 - (tcdf (abs (t), dfe))); coeff_stats = zeros (1 + sum (df_coef), 6); coeff_stats(:,1) = b; # coefficients coeff_stats(:,2) = se; # standard errors coeff_stats(:,3) = b - se * t_crit; # Lower CI bound coeff_stats(:,4) = b + se * t_crit; # Upper CI bound coeff_stats(:,5) = t; # t-statistics coeff_stats(:,6) = p; # p-values ## Assign NaN to p-value to avoid printing statistics relating to ## coefficients for 'random' effects hi = 1 + cumsum (df_coef); random_terms = find (any (TERMS(:,RANDOM) > 0, 2)); for ignore = random_terms' coeff_stats(hi(ignore)-df_coef(ignore)+1:hi(ignore), 6) = NaN; endfor ## Compute leverage values and Cook's distance h = diag (hat); % Leverage values D = resid.^2 / ((1 + sum (df_coef)) * mse) ... .* h ./ (1 - h).^2; % Cook's distance ## Create STATS structure for MULTCOMPARE STATS = struct ('source','anovan', ... 'resid', resid, ... # These are weighted (not raw) residuals 'coeffs', coeff_stats, ... 'Rtr', [], ... # Not used by Octave 'rowbasis', [], ... # Not used by Octave 'dfe', dfe, ... 'mse', mse, ... 'nullproject', [], ... # Not used by Octave 'terms', TERMS, ... 'nlevels', nlevels, ... 'continuous', cont_vec, ... 'vmeans', vmeans, ... 'termcols', termcols, ... 'coeffnames', {cellstr(char(coeffnames{:}))}, ... 'vars', [], ... # Not used by Octave 'varnames', {VARNAMES}, ... 'grpnames', {levels}, ... 'vnested', NESTED, ... ## Empty unless the model holds a random factor, in ## which case they carry its expected mean squares, the ## denominator each F ratio was taken against, and the ## variance component of every random term. 'ems', ems_coef, ... 'denom', denom, ... 'dfdenom', dfdenom_out, ... 'msdenom', msdenom_out, ... 'varest', varest, ... 'varci', varci, ... 'txtdenom', {txtdenom}, ... 'txtems', {txtems}, ... 'rtnames', {rtnames}, ... ## Additional STATS fields used exclusively by Octave 'center_continuous', center_continuous, ... 'random', RANDOM, ... 'formula', formula, ... 'alpha', ALPHA, ... 'df', df, ... 'contrasts', {CONTRASTS}, ... 'X', sparse (cell2mat (X)), ... 'Y', Y, ... 'W', sparse (W), ... 'lmfit', @lmfit, ... 'vcov', sparse (ucov * mse), ... 'CooksD', D, ... 'grps', gid, ... 'eta_squared', eta_sq, ... 'partial_eta_squared', partial_eta_sq); ## Print ANOVA table switch (lower (DISPLAY)) case {'on', true} ## Print model formula fprintf ("\nMODEL FORMULA (based on Wilkinson's notation):\n\n%s\n", formula); ## If applicable, print parameter estimates (a.k.a contrasts) for fixed effects if (planned && ! isempty (GROUP)) ## Parameter estimates correspond to the contrasts we set. To avoid ## p-hacking, don't print contrasts if we don't specify them to start with fprintf ("\nMODEL PARAMETERS (contrasts for the fixed effects)\n\n"); fprintf ("Parameter Estimate SE Lower.CI Upper.CI t Prob>|t|\n"); fprintf ("--------------------------------------------------------------------------------\n"); for j = 1:size (coeff_stats, 1) if (p(j) < 0.001) fprintf ("%-20s %10.3g %9.3g %9.3g %9.3g %8.2f <.001 \n", ... STATS.coeffnames{j}, STATS.coeffs(j,1:end-1)); elseif (p(j) < 0.9995) fprintf ("%-20s %10.3g %9.3g %9.3g %9.3g %8.2f .%03u \n", ... STATS.coeffnames{j}, STATS.coeffs(j,1:end-1), round (p(j) * 1e+03)); elseif (isnan (p(j))) ## Don't display coefficients for 'random' effects since they were ## treated as fixed effects else fprintf ("%-20s %10.3g %9.3g %9.3g %9.3g %8.2f 1.000 \n", ... STATS.coeffnames{j}, STATS.coeffs(j,1:end-1)); endif endfor endif ## Print ANOVA table [nrows, ncols] = size (T); ## The ' marking a random factor is a printing convention of this ## implementation, not part of the names it returns, which carry no ## marker so that they match what MATLAB reports. VARNAMES_DISP = VARNAMES; for v = RANDOM VARNAMES_DISP{v} = strcat (VARNAMES{v}, "'"); endfor fprintf ("\nANOVA TABLE (Type %s sums-of-squares):\n\n", sstype_char); fprintf ("Source Sum Sq. d.f. Mean Sq. Part.Eta F Prob>F\n"); fprintf ("--------------------------------------------------------------------------------\n"); for i = 1:Nt str = mTermName (TERMS(i,:), VARNAMES_DISP, NESTED); l = numel (str); # Needed to truncate source term name at 18 characters ## Format and print the statistics for each model term ## Format F statistics and p-values in APA style ## Mean Sq., F and Prob>F sit at 5, 6 and 7 in both layouts, and ## partial eta squared is the last column of either. row = {T{i+1,2}, T{i+1,3}, T{i+1,5}, T{i+1,end}, T{i+1,6}}; if (P(i) < 0.001) fprintf ("%-20s %10.5g %6d %10.5g %4.3f %11.2f <.001 \n", ... str(1:min (18,l)), row{:}); elseif (P(i) < 0.9995) fprintf ("%-20s %10.5g %6d %10.5g %4.3f %11.2f .%03u \n", ... str(1:min (18,l)), row{:}, round (P(i) * 1e+03)); elseif (isnan (P(i))) fprintf ("%-20s %10.5g %6d \n", str(1:min (18,l)), T{i+1,2:3}); else fprintf ("%-20s %10.5g %6d %10.5g %4.3f %11.2f 1.000 \n", ... str(1:min (18,l)), row{:}); endif endfor fprintf ("Error %10.5g %6d %10.5g\n", ... T{end-1,2}, T{end-1,3}, T{end-1,5}); fprintf ("Total %10.5g %6d \n", T{end,2:3}); if (any (singular)) fprintf (strcat ("Singular terms are aliased with the rest of the", ... " model and are not\nuniquely estimable: %s\n"), ... strjoin (T(1 + find (singular), 1)', ", ")); endif if (! isempty (RANDOM)) fprintf ("\nEach F ratio is taken against the denominator its\n"); fprintf ("expected mean square calls for:\n"); for i = 1:Nt fprintf ("%-20s %s on %.4g d.f.\n", ... T{i+1,1}(1:min (18, numel (T{i+1,1}))), ... txtdenom{i}, dfdenom(i)); endfor endif fprintf ("\n"); ## Make figure of diagnostic plots figure ('Name', 'Diagnostic Plots: Model Residuals'); t = STATS.resid ./ (sqrt (mse * (1 - h))); % Studentized residuals fit = STATS.X * STATS.coeffs(:,1); % Fitted values [jnk, DI] = sort (D, 'descend'); % Indices of sorted D nk = 4; % Top nk residuals with largest D ## Normal quantile-quantile plot subplot (2, 2, 1); x = ((1 : n)' - .5) / n; [ts, I] = sort (t); q = norminv (x); plot (q, ts, 'ok', 'markersize', 3); box off; grid on; xlabel ('Theoretical quantiles'); ylabel ('Studentized Residuals'); title ('Normal Q-Q Plot'); arrayfun (@(i) text (q(I == DI(i)), t(DI(i)), ... sprintf (" %u", DI(i))), [1:min(nk,n)]) iqr = [0.25; 0.75]; yl = quantile (t, iqr, 1, 6); xl = norminv (iqr); slope = diff (yl) / diff (xl); int = yl(1) - slope * xl(1); ax1_xlim = get (gca, 'XLim'); hold on; plot (ax1_xlim, slope * ax1_xlim + int, 'k-'); hold off; set (gca, 'Xlim', ax1_xlim); ## Spread-Location Plot subplot (2, 2, 2); plot (fit, sqrt (abs (t)), 'ko', 'markersize', 3); box off; xlabel ('Fitted values'); ylabel ('sqrt ( | Studentized Residuals | )'); title ('Spread-Location Plot') ax2_xlim = get (gca, 'XLim'); hold on; plot (ax2_xlim, ones (1, 2) * sqrt (2), 'k:'); plot (ax2_xlim, ones (1, 2) * sqrt (3), 'k-.'); plot (ax2_xlim, ones (1, 2) * sqrt (4), 'k--'); hold off; arrayfun (@(i) text (fit(DI(i)), sqrt (abs (t(DI(i)))), ... sprintf (" %u", DI(i))), [1:min(nk,n)]); xlim (ax2_xlim); ## Residual-Leverage plot subplot (2, 2, 3); plot (h, t, 'ko', 'markersize', 3); box off; xlabel ('Leverage') ylabel ('Studentized Residuals'); title ('Residual-Leverage Plot') ax3_xlim = get (gca, 'XLim'); ax3_ylim = get (gca, 'YLim'); hold on; plot (ax3_xlim, zeros (1, 2), 'k-'); hold off; arrayfun (@(i) text (h(DI(i)), t(DI(i)), ... sprintf (" %u", DI(i))), [1:min(nk,n)]); set (gca, 'ygrid', 'on'); xlim (ax3_xlim); ylim (ax3_ylim); ## Cook's distance stem plot subplot (2, 2, 4); stem (D, 'ko', 'markersize', 3); box off; xlabel ('Obs. number') ylabel ('Cook''s distance') title ('Cook''s Distance Stem Plot') xlim ([0, n]); ax4_xlim = get (gca, 'XLim'); ax4_ylim = get (gca, 'YLim'); hold on; plot (ax4_xlim, ones (1, 2) * 4 / dfe, 'k:'); plot (ax4_xlim, ones (1, 2) * 0.5, 'k-.'); plot (ax4_xlim, ones (1, 2), 'k--'); hold off; arrayfun (@(i) text (DI(i), D(DI(i)), ... sprintf (" %u", DI(i))), [1:min(nk,n)]); xlim (ax4_xlim); ylim (ax4_ylim); set (findall ( gcf, '-property', 'FontSize'), 'FontSize', 7) case {'off', false} ## do nothing otherwise error ("anovan: wrong value for 'display' parameter."); endswitch function mDesignMatrix () ## Nested function that returns a cell array of the design matrix for ## each term in the model ## Input variables it uses: ## GROUP, TERMS, CONTINUOUS, CONTRASTS, VARNAMES, n ## Variables it creates or modifies: ## X, grpnames, nlevels, df, termcols, coeffnames, vmeans, gid, CONTRASTS ## EVALUATE FACTOR LEVELS levels = cell (N, 1); gid = zeros (n, N); nlevels = zeros (N, 1); for j = 1:N if (cont_vec(j)) nlevels(j) = 1; if (iscell (GROUP(:,j))) gid(:,j) = cell2mat ([GROUP(:,j)]); else gid(:,j) = GROUP(:,j); endif elseif (isempty (categorical_levels{j})) levels{j} = unique (GROUP(:,j), 'stable'); if (isnumeric (levels{j})) levels{j} = num2cell (levels{j}); endif nlevels(j) = numel (levels{j}); for k = 1:nlevels(j) gid(ismember (GROUP(:,j), levels{j}{k}),j) = k; endfor else if (iscell (GROUP)) codes = cell2mat (GROUP(:,j)); else codes = GROUP(:,j); endif level_values = find (accumarray (codes, 1) > 0); levels{j} = categorical_levels{j}(level_values); nlevels(j) = numel (level_values); for k = 1:nlevels(j) gid(codes == level_values(k),j) = k; endfor endif endfor ## ENCODE EACH FACTOR ONCE base = cell (N, 1); vmeans = zeros (N, 1); center_continuous = cont_vec; for j = 1:N if (cont_vec(j)) ## Keep the predictor uncentred here. A term raises it to its ## exponent first and centres the resulting column, so that x^2 ## means the square of x rather than the square of x - mean (x). base{j} = gid(:,j); if (ischar (CONTRASTS{j}) ... && strcmpi (CONTRASTS{j}, 'treatment')) center_continuous(j) = false; CONTRASTS{j} = []; else vmeans(j) = mean (base{j}); endif elseif (any (NESTED(j,:))) if (! isempty (CONTRASTS{j})) error (strcat ("anovan: custom contrasts are not supported", ... " for nested factors.")); endif parents = find (NESTED(j,:)); [~, ~, parent_id] = unique (gid(:,parents), 'rows'); blocks = cell (max (parent_id), 1); for parent = 1:max (parent_id) rows_ = find (parent_id == parent); child_levels = unique (gid(rows_,j), 'stable'); [~, local_id] = ismember (gid(rows_,j), child_levels); C = contr_simple (numel (child_levels)); block = zeros (n, columns (C)); block(rows_,:) = C(local_id,:); blocks{parent} = block; endfor base{j} = cell2mat (blocks'); CONTRASTS{j} = []; else CONTRASTS{j} = mContrasts (CONTRASTS{j}, nlevels(j)); base{j} = CONTRASTS{j}(gid(:,j), :); endif endfor ## BUILD ONE DESIGN BLOCK FOR EACH REQUESTED TERM X = cell (1, Nt + 1); X{1} = ones (n, 1); coeffnames = cell (1, Nt + 1); coeffnames{1} = '(Intercept)'; df = zeros (Nt, 1); termcols = ones (Nt + 1, 1); for i = 1:Nt factors = find (TERMS(i, :) > 0); tmp = ones (n, 1); for j = factors exponent = TERMS(i, j); if (! cont_vec(j) && exponent != 1) error (strcat ("anovan: categorical factors cannot have", ... " exponent values greater than 1.")); endif block = base{j}; if (cont_vec(j)) if (exponent != 1) block = block .^ exponent; endif if (center_continuous(j)) block -= mean (block); endif endif tmp = reshape (bsxfun (@times, ... reshape (tmp, n, 1, columns (tmp)), ... reshape (block, n, columns (block), 1)), n, []); endfor X{i + 1} = tmp; df(i) = columns (tmp); ## termcols counts the columns a dummy coding of the term would take, ## which is one per level rather than the contrast width in df. termcols(i + 1) = prod (nlevels(factors)); term_name = mTermName (TERMS(i, :), VARNAMES, NESTED); if (df(i) == 1) coeffnames{i + 1} = term_name; else coeffnames{i + 1} = arrayfun (... @(v) sprintf ("%s_%u", term_name, v), (1:df(i))', ... 'UniformOutput', false); endif endfor endfunction endfunction function C = mContrasts (specification, nlevels) if (nlevels == 1) C = zeros (1, 0); elseif (isempty (specification)) C = contr_simple (nlevels); elseif (ischar (specification)) switch (lower (specification)) case {'simple', 'anova'} C = contr_simple (nlevels); case 'poly' C = contr_poly (nlevels); case 'helmert' C = contr_helmert (nlevels); case 'effect' C = contr_sum (nlevels); case {'sdif', 'sdiff'} C = contr_sdif (nlevels); case 'treatment' C = contr_treatment (nlevels); otherwise error ("anovan: unknown contrast specification."); endswitch else C = specification; if (! isequal (size (C), [nlevels, nlevels - 1])) error (strcat ("anovan: each contrast matrix must have one row per", ... " factor level and one fewer column.")); elseif (! all (any (C))) error ("anovan: a contrast must be coded in every column."); endif endif endfunction function [is_random, ems_coef, txtems, denom, msdenom, dfdenom, txtdenom, ... varest, varci] = mRandomEffects (TERMS, RANDOM, NESTED, X, gid, ... CONTINUOUS, sstype_id, df, ms, mse, dfe, ... ALPHA, VARNAMES, Nt) ## Derive the expected mean squares of a model holding one or more random ## factors, and from them the denominator each F ratio is taken against. ## A term is random when any factor in it is random, counting the ## parents of a nested factor as part of the term. membership = TERMS > 0; for factor = 1:columns (membership) parents = find (NESTED(factor,:)); if (! isempty (parents)) membership(:,parents) |= membership(:,factor); endif endfor is_random = any (membership(:,RANDOM), 2); term_names = cell (Nt, 1); for i = 1:Nt term_names{i} = mTermName (TERMS(i,:), VARNAMES, NESTED); endfor ## A term's basis is the set of cells its factors define, so that a ## coefficient counts observations per cell rather than design columns. bases = cell (Nt, 1); for i = 1:Nt factors = find (TERMS(i,:) > 0); nested_here = factors(any (NESTED(factors,:), 2)); if (! isempty (nested_here)) factors = union (factors, find (any (NESTED(nested_here,:), 1))); endif if (all (! ismember (factors, CONTINUOUS))) [jnk, jnk, ids] = unique (gid(:,factors), 'rows'); bases{i} = sparse (1:numel (ids), ids, 1); else bases{i} = X{i+1}; endif endfor ## The coefficient of a component in an expected mean square is the ## energy its basis gains when the term enters the model that the term's ## sum-of-squares is measured against, per degree of freedom. ems_coef = zeros (Nt, Nt); for i = 1:Nt [included, excluded] = mEmsModels (TERMS, X, sstype_id, Nt, i); Qi = orth (full (included)); Qe = orth (full (excluded)); for j = 1:Nt gain = sum (sumsq (Qi' * bases{j})) - sum (sumsq (Qe' * bases{j})); ems_coef(i,j) = max (gain, 0) / max (df(i), 1); endfor endfor ## Name each component: Q for a fixed term, V for a random one. txtems = cell (Nt + 1, 1); for i = 1:Nt pieces = {}; if (! is_random(i)) piece = mEmsComponent (ems_coef(i,i), 'Q', term_names{i}); if (! isempty (piece)) pieces{end+1} = piece; endif for j = find (! is_random(:))' if (j != i && abs (ems_coef(i,j)) > 1e-10) pieces{end+1} = mEmsComponent (ems_coef(i,j), 'Q', ... term_names{j}); endif endfor endif for j = find (is_random(:))' if (abs (ems_coef(i,j)) > 1e-10) pieces{end+1} = mEmsComponent (ems_coef(i,j), 'V', term_names{j}); endif endfor pieces{end+1} = 'V(Error)'; txtems{i} = strjoin (pieces, '+'); endfor txtems{Nt+1} = 'V(Error)'; ## The denominator of a term's F ratio is the combination of mean ## squares whose expectation matches the term's, minus the term itself. rt = find (is_random(:)); random_ms = [ms(rt); mse]; random_df = [df(rt); dfe]; coefficients = [ems_coef(rt,rt), ones(numel (rt), 1); ... zeros(1, numel (rt)), 1]; names = [term_names(rt); {'Error'}]; denom = zeros (Nt, numel (rt) + 1); msdenom = zeros (Nt, 1); dfdenom = zeros (Nt, 1); txtdenom = cell (Nt, 1); for i = 1:Nt target = [ems_coef(i,rt), 1]; own = find (rt == i, 1); if (! isempty (own)) target(own) = 0; endif weights = pinv (coefficients') * target'; ## pinv leaves rounding dust on the mean squares a denominator does ## not use. Drop it, so the formula and the number agree. weights(abs (weights) <= 1e-10) = 0; denom(i,:) = weights'; msdenom(i) = weights' * random_ms; ## Satterthwaite's approximation over the mean squares in use. One ## carrying no degrees of freedom has no sampling distribution to ## combine, so the denominator has none either. used = (weights != 0); if (any (used & (random_df(:) <= 0))) dfdenom(i) = 0; else parts = (weights(used) .* random_ms(used)) .^ 2 ./ random_df(used); dfdenom(i) = msdenom(i) ^ 2 / sum (parts); endif txtdenom{i} = mMeanSquareFormula (weights, names); endfor ## Variance components, and a confidence interval for each from the ## chi-squared bounds of the mean squares it is built from. varest = coefficients \ random_ms; lower_ms = random_df .* random_ms ./ chi2inv (1 - ALPHA / 2, random_df); upper_ms = random_df .* random_ms ./ chi2inv (ALPHA / 2, random_df); inverse = pinv (coefficients); lo = sum (max (inverse, 0) .* lower_ms' ... + min (inverse, 0) .* upper_ms', 2); hi = sum (max (inverse, 0) .* upper_ms' ... + min (inverse, 0) .* lower_ms', 2); lo = max (lo, 0); undefined = (varest < 0) | (! isfinite (lo)) | (! isfinite (hi)); lo(undefined) = NaN; hi(undefined) = NaN; varci = [lo, hi]; ## Report the coefficients with a row and column for the error term, as ## MATLAB does, so the matrix describes every component of the model. ems_coef = [ems_coef, ones(Nt, 1); zeros(1, Nt), 1]; endfunction function [included, excluded] = mEmsModels (TERMS, X, sstype_id, Nt, term) ## Return the models a term's sum-of-squares is measured between, which is ## what its expected mean square describes. switch (sstype_id) case 1 inc = 1:term; exc = 1:term-1; case {2, 'h'} fac = find (TERMS(term,:) > 0); if (isequal (sstype_id, 'h')) exc = find (any (TERMS(:,fac) < TERMS(term,fac), 2))'; else exc = find (any (TERMS(:,fac) != TERMS(term,fac), 2))'; endif inc = [term, exc]; otherwise inc = 1:Nt; exc = setdiff (inc, term, 'stable'); endswitch included = cell2mat (X([1, inc + 1])); excluded = cell2mat (X([1, exc + 1])); endfunction function value = mEmsComponent (coefficient, symbol, name) if (! isfinite (coefficient) || abs (coefficient) <= 1e-10) value = ""; elseif (abs (coefficient - 1) <= 1e-10) value = sprintf ("%s(%s)", symbol, name); else value = sprintf ("%.6g*%s(%s)", coefficient, symbol, name); endif endfunction function formula = mMeanSquareFormula (weights, names) pieces = {}; for i = 1:numel (weights) coefficient = weights(i); if (abs (coefficient) <= 1e-10) continue; elseif (abs (coefficient - 1) <= 1e-10) term = sprintf ("MS(%s)", names{i}); elseif (abs (coefficient + 1) <= 1e-10) term = sprintf ("-MS(%s)", names{i}); else term = sprintf ("%.6g*MS(%s)", coefficient, names{i}); endif if (! isempty (pieces) && coefficient > 0) term = ["+", term]; endif pieces{end+1} = term; endfor formula = strjoin (pieces, ""); endfunction function name = mTermName (term, varnames, nested) pieces = cell (1, nnz (term)); count = 0; for j = find (term > 0) count += 1; factor = varnames{j}; if (nargin > 2 && any (nested(j,:))) parents = varnames(nested(j,:)); factor = sprintf ("%s(%s)", factor, strjoin (parents, ",")); endif if (term(j) == 1) pieces{count} = factor; else pieces{count} = sprintf ("%s^%d", factor, term(j)); endif endfor ## Wilkinson notation: ':' is the interaction term alone, which is what a ## row of the table holds, where '*' would name the crossed set of the ## factors and every interaction among them. name = strjoin (pieces, ":"); endfunction ## BUILT IN CONTRAST CODING FUNCTIONS function C = contr_simple (N) ## Create contrast matrix (of doubles) using simple (ANOVA) contrast coding ## These contrasts are centered (i.e. sum to 0) ## Ideal for unordered factors, with comparison to a reference level ## The first factor level is the reference level C = cat (1, zeros (1,N-1), eye (N-1)) - 1/N; endfunction function C = contr_poly (N) ## Create contrast matrix (of doubles) using polynomial contrast coding ## for trend analysis of ordered categorical factor levels ## These contrasts are orthogonal and centered (i.e. sum to 0) ## Ideal for ordered factors [C, jnk] = qr (bsxfun (@power, [1:N]' - mean ([1:N]'), [0:N-1])); C(:,1) = []; s = ones (1, N-1); s(1:2:N-1) *= -1; f = (sign (C(1,:)) != s); C(:,f) *= -1; endfunction function C = contr_helmert (N) ## Create contrast matrix (of doubles) using Helmert coding contrasts ## These contrasts are orthogonal and centered (i.e. sum to 0) C = cat (1, tril (-ones (N-1), -1) + diag (N-1:-1:1), ... -ones (1, N-1)) ./ (N:-1:2); endfunction function C = contr_sum (N) ## Create contrast matrix (of doubles) using deviation effect coding ## These contrasts are centered (i.e. sum to 0) C = cat (1, - (ones (1,N-1)), eye (N-1)); endfunction function C = contr_sdif (N) ## Create contrast matrix (of doubles) using successive differences coding ## These contrasts are centered (i.e. sum to 0) C = tril (ones (N, N - 1), -1) - ones (N, 1) / N * [N - 1 : -1 : 1]; endfunction function C = contr_treatment (N) ## Create contrast matrix (of doubles) using treatment contrast coding ## Not compatible with SSTYPE 3 since contrasts are not centered ## Ideal for unordered factors, with comparison to a reference level ## The first factor level is the reference level C = cat (1, zeros (1,N-1), eye (N-1)); endfunction ## FUNCTION TO FIT THE LINEAR MODEL function [b, sse, resid, ucov, hat, rk] = lmfit (X, Y, W) ## Get model coefficients by solving the linear equation by QR decomposition ## The number of free parameters (i.e. intercept + coefficients) is equal ## to n - dfe. If optional argument W is provided, it should be a diagonal ## matrix of weights or a positive definite covariance matrix if (nargin < 3) ## If no weights are provided, create an identity matrix n = numel (Y); W = eye (n); endif C = chol (W); XW = C*X; YW = C*Y; [Q, R] = qr (XW, 0); ## A design that is not of full column rank has no unique solution, and the ## triangular solve gives an arbitrarily large one: the residuals follow it, ## so the error sum-of-squares can exceed the total. Detect the deficiency ## and take the minimum-norm least-squares solution instead, which leaves ## the fitted values, the residuals and SSE correct whatever the rank. npar = columns (XW); if (npar == 0) deficient = false; elseif (rows (R) != columns (R)) deficient = true; else deficient = (rcond (R) < eps); endif if (deficient) b = pinv (XW) * YW; rk = rank (XW); else b = R \ (Q' * YW); rk = npar; endif ## Get fitted values fit = XW * b; ## Get residuals from the fit resid = YW - fit; ## Calculate the residual sums-of-squares sse = sum (resid.^2); ## Calculate the unscaled covariance matrix (i.e. inv (X'*X )) if (nargout > 3) if (deficient) ucov = pinv (XW' * XW); else ucov = R \ Q' / XW'; endif endif ## Calculate the Hat matrix if (nargout > 4) w = diag (W); rw = sqrt (w); if (deficient) P = XW * pinv (XW); hat = diag (1 ./ rw) * P * diag (rw); else Q1 = diag (1 ./ rw) * Q; Q2 = diag (rw) * Q; hat = Q1 * Q2'; endif endif endfunction %!demo %! %! # Two-sample unpaired test on independent samples (equivalent to Student's %! # t-test). Note that the absolute value of t-statistic can be obtained by %! # taking the square root of the reported F statistic. In this example, %! # t = sqrt (1.44) = 1.20. %! %! score = [54 23 45 54 45 43 34 65 77 46 65]'; %! gender = {'male' 'male' 'male' 'male' 'male' 'female' 'female' 'female' ... %! 'female' 'female' 'female'}'; %! %! [P, ATAB, STATS] = anovan (score, gender, 'display', 'on', 'varnames', 'gender'); %!demo %! %! # Two-sample paired test on dependent or matched samples equivalent to a %! # paired t-test. As for the first example, the t-statistic can be obtained by %! # taking the square root of the reported F statistic. Naming subject as a %! # random factor (') keeps the treatment x subject interaction in the model %! # and makes its mean square the denominator of the test on treatment. %! %! score = [4.5 5.6; 3.7 6.4; 5.3 6.4; 5.4 6.0; 3.9 5.7]'; %! treatment = {'before' 'after'; 'before' 'after'; 'before' 'after'; %! 'before' 'after'; 'before' 'after'}'; %! subject = {'GS' 'GS'; 'JM' 'JM'; 'HM' 'HM'; 'JW' 'JW'; 'PS' 'PS'}'; %! %! [P, ATAB, STATS] = anovan (score(:), {treatment(:), subject(:)}, ... %! 'model', 'full', 'random', 2, 'sstype', 2, ... %! 'varnames', {'treatment', 'subject'}, ... %! 'display', 'on'); %!demo %! %! # One-way ANOVA on the data from a study on the strength of structural beams, %! # in Hogg and Ledolter (1987) Engineering Statistics. New York: MacMillan %! %! strength = [82 86 79 83 84 85 86 87 74 82 ... %! 78 75 76 77 79 79 77 78 82 79]'; %! alloy = {'st','st','st','st','st','st','st','st', ... %! 'al1','al1','al1','al1','al1','al1', ... %! 'al2','al2','al2','al2','al2','al2'}'; %! %! [P, ATAB, STATS] = anovan (strength, alloy, 'display', 'on', ... %! 'varnames', 'alloy'); %!demo %! %! # One-way repeated measures ANOVA on the data from a study on the number of %! # words recalled by 10 subjects for three time conditions, in Loftus & Masson %! # (1994) Psychon Bull Rev. 1(4):476-490, Table 2. Naming subject as a random %! # factor (') keeps the seconds x subject interaction in the model and makes %! # its mean square the denominator of the test on seconds. %! %! words = [10 13 13; 6 8 8; 11 14 14; 22 23 25; 16 18 20; ... %! 15 17 17; 1 1 4; 12 15 17; 9 12 12; 8 9 12]; %! seconds = [1 2 5; 1 2 5; 1 2 5; 1 2 5; 1 2 5; ... %! 1 2 5; 1 2 5; 1 2 5; 1 2 5; 1 2 5;]; %! subject = [ 1 1 1; 2 2 2; 3 3 3; 4 4 4; 5 5 5; ... %! 6 6 6; 7 7 7; 8 8 8; 9 9 9; 10 10 10]; %! %! [P, ATAB, STATS] = anovan (words(:), {seconds(:), subject(:)}, ... %! 'model', 'full', 'random', 2, 'sstype', 2, ... %! 'display', 'on', 'varnames', {'seconds', 'subject'}); %!demo %! %! # Balanced two-way ANOVA with interaction on the data from a study of popcorn %! # brands and popper types, in Hogg and Ledolter (1987) Engineering Statistics. %! # New York: MacMillan %! %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! brands = {'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'}; %! popper = {'oil', 'oil', 'oil'; 'oil', 'oil', 'oil'; 'oil', 'oil', 'oil'; ... %! 'air', 'air', 'air'; 'air', 'air', 'air'; 'air', 'air', 'air'}; %! %! [P, ATAB, STATS] = anovan (popcorn(:), {brands(:), popper(:)}, ... %! 'display', 'on', 'model', 'full', ... %! 'varnames', {'brands', 'popper'}); %!demo %! %! # Unbalanced two-way ANOVA (2x2) on the data from a study on the effects of %! # gender and having a college degree on salaries of company employees, %! # in Maxwell, Delaney and Kelly (2018): Chapter 7, Table 15 %! %! salary = [24 26 25 24 27 24 27 23 15 17 20 16, ... %! 25 29 27 19 18 21 20 21 22 19]'; %! gender = {'f' 'f' 'f' 'f' 'f' 'f' 'f' 'f' 'f' 'f' 'f' 'f'... %! 'm' 'm' 'm' 'm' 'm' 'm' 'm' 'm' 'm' 'm'}'; %! degree = [1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 0 0 0 0 0 0 0]'; %! %! [P, ATAB, STATS] = anovan (salary, {gender, degree}, 'model', 'full', ... %! 'sstype', 3, 'display', 'on', 'varnames', ... %! {'gender', 'degree'}); %!demo %! %! # Unbalanced two-way ANOVA (3x2) on the data from a study of the effect of %! # adding sugar and/or milk on the tendency of coffee to make people babble, %! # in from Navarro (2019): 16.10 %! %! sugar = {'real' 'fake' 'fake' 'real' 'real' 'real' 'none' 'none' 'none' ... %! 'fake' 'fake' 'fake' 'real' 'real' 'real' 'none' 'none' 'fake'}'; %! milk = {'yes' 'no' 'no' 'yes' 'yes' 'no' 'yes' 'yes' 'yes' ... %! 'no' 'no' 'yes' 'no' 'no' 'no' 'no' 'no' 'yes'}'; %! babble = [4.6 4.4 3.9 5.6 5.1 5.5 3.9 3.5 3.7... %! 5.6 4.7 5.9 6.0 5.4 6.6 5.8 5.3 5.7]'; %! %! [P, ATAB, STATS] = anovan (babble, {sugar, milk}, 'model', 'full', ... %! 'sstype', 3, 'display', 'on', ... %! 'varnames', {'sugar', 'milk'}); %!demo %! %! # Unbalanced three-way ANOVA (3x2x2) on the data from a study of the effects %! # of three different drugs, biofeedback and diet on patient blood pressure, %! # adapted* from Maxwell, Delaney and Kelly (2018): Chapter 8, Table 12 %! # * Missing values introduced to make the sample sizes unequal to test the %! # calculation of different types of sums-of-squares %! %! drug = {'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' ... %! 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X'; %! 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' ... %! 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y'; %! 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' ... %! 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z'}; %! feedback = [1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0; %! 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0; %! 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0]; %! diet = [0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1; %! 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1; %! 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1]; %! BP = [170 175 165 180 160 158 161 173 157 152 181 190 ... %! 173 194 197 190 176 198 164 190 169 164 176 175; %! 186 194 201 215 219 209 164 166 159 182 187 174 ... %! 189 194 217 206 199 195 171 173 196 199 180 NaN; %! 180 187 199 170 204 194 162 184 183 156 180 173 ... %! 202 228 190 206 224 204 205 199 170 160 NaN NaN]; %! %! [P, ATAB, STATS] = anovan (BP(:), {drug(:), feedback(:), diet(:)}, ... %! 'model', 'full', 'sstype', 3, ... %! 'display', 'on', ... %! 'varnames', {'drug', 'feedback', 'diet'}); %!demo %! %! # Balanced three-way ANOVA (2x2x2) with one of the factors being a blocking %! # factor. The data is from a randomized block design study on the effects %! # of antioxidant treatment on glutathione-S-transferase (GST) levels in %! # different mouse strains, from Festing (2014), ILAR Journal, 55(3):427-476. %! # Naming block as a random factor (') keeps every interaction with block in %! # the model; each F ratio then takes the denominator its expected mean %! # square calls for, which for block itself is a combination of three mean %! # squares carried on fractional degrees of freedom. %! %! measurement = [444 614 423 625 408 856 447 719 ... %! 764 831 586 782 609 1002 606 766]'; %! strain= {'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola', ... %! 'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola'}'; %! treatment={'C' 'T' 'C' 'T' 'C' 'T' 'C' 'T' 'C' 'T' 'C' 'T' 'C' 'T' 'C' 'T'}'; %! block = [1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2]'; %! %! [P, ATAB, STATS] = anovan (measurement/10, {strain, treatment, block}, ... %! 'sstype', 2, 'model', 'full', 'random', 3, ... %! 'display', 'on', ... %! 'varnames', {'strain', 'treatment', 'block'}); %!demo %! %! # One-way ANCOVA on data from a study of the additive effects of species %! # and temperature on chirpy pulses of crickets, from Stitch, The Worst Stats %! # Text eveR %! %! pulse = [67.9 65.1 77.3 78.7 79.4 80.4 85.8 86.6 87.5 89.1 ... %! 98.6 100.8 99.3 101.7 44.3 47.2 47.6 49.6 50.3 51.8 ... %! 60 58.5 58.9 60.7 69.8 70.9 76.2 76.1 77 77.7 84.7]'; %! temp = [20.8 20.8 24 24 24 24 26.2 26.2 26.2 26.2 28.4 ... %! 29 30.4 30.4 17.2 18.3 18.3 18.3 18.9 18.9 20.4 ... %! 21 21 22.1 23.5 24.2 25.9 26.5 26.5 26.5 28.6]'; %! species = {'ex' 'ex' 'ex' 'ex' 'ex' 'ex' 'ex' 'ex' 'ex' 'ex' 'ex' ... %! 'ex' 'ex' 'ex' 'niv' 'niv' 'niv' 'niv' 'niv' 'niv' 'niv' ... %! 'niv' 'niv' 'niv' 'niv' 'niv' 'niv' 'niv' 'niv' 'niv' 'niv'}; %! %! [P, ATAB, STATS] = anovan (pulse, {species, temp}, 'model', 'linear', ... %! 'continuous', 2, 'sstype', 'h', 'display', 'on', ... %! 'varnames', {'species', 'temp'}); %!demo %! %! # Factorial ANCOVA on data from a study of the effects of treatment and %! # exercise on stress reduction score after adjusting for age. Data from R %! # datarium package). %! %! score = [95.6 82.2 97.2 96.4 81.4 83.6 89.4 83.8 83.3 85.7 ... %! 97.2 78.2 78.9 91.8 86.9 84.1 88.6 89.8 87.3 85.4 ... %! 81.8 65.8 68.1 70.0 69.9 75.1 72.3 70.9 71.5 72.5 ... %! 84.9 96.1 94.6 82.5 90.7 87.0 86.8 93.3 87.6 92.4 ... %! 100. 80.5 92.9 84.0 88.4 91.1 85.7 91.3 92.3 87.9 ... %! 91.7 88.6 75.8 75.7 75.3 82.4 80.1 86.0 81.8 82.5]'; %! treatment = {'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' ... %! 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' ... %! 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' ... %! 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' ... %! 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' ... %! 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no'}'; %! exercise = {'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' ... %! 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' ... %! 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' ... %! 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' ... %! 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' ... %! 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi'}'; %! age = [59 65 70 66 61 65 57 61 58 55 62 61 60 59 55 57 60 63 62 57 ... %! 58 56 57 59 59 60 55 53 55 58 68 62 61 54 59 63 60 67 60 67 ... %! 75 54 57 62 65 60 58 61 65 57 56 58 58 58 52 53 60 62 61 61]'; %! %! [P, ATAB, STATS] = anovan (score, {treatment, exercise, age}, ... %! 'model', [1 0 0; 0 1 0; 0 0 1; 1 1 0], ... %! 'continuous', 3, 'sstype', 'h', 'display', 'on', ... %! 'varnames', {'treatment', 'exercise', 'age'}); %!demo %! %! # Unbalanced one-way ANOVA with custom, orthogonal contrasts. The statistics %! # relating to the contrasts are shown in the table of model parameters, and %! # can be retrieved from the STATS.coeffs output. %! %! dv = [ 8.706 10.362 11.552 6.941 10.983 10.092 6.421 14.943 15.931 ... %! 22.968 18.590 16.567 15.944 21.637 14.492 17.965 18.851 22.891 ... %! 22.028 16.884 17.252 18.325 25.435 19.141 21.238 22.196 18.038 ... %! 22.628 31.163 26.053 24.419 32.145 28.966 30.207 29.142 33.212 ... %! 25.694 ]'; %! g = [1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 3 ... %! 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5]'; %! C = [ 0.4001601 0.3333333 0.5 0.0 %! 0.4001601 0.3333333 -0.5 0.0 %! 0.4001601 -0.6666667 0.0 0.0 %! -0.6002401 0.0000000 0.0 0.5 %! -0.6002401 0.0000000 0.0 -0.5]; %! %! [P,ATAB, STATS] = anovan (dv, g, 'contrasts', C, 'varnames', 'score', ... %! 'alpha', 0.05, 'display', 'on'); %!demo %! %! # One-way ANOVA with the linear model fit by weighted least squares to %! # account for heteroskedasticity. In this example, the variance appears %! # proportional to the outcome, so weights have been estimated by initially %! # fitting the model without weights and regressing the absolute residuals on %! # the fitted values. Although this data could have been analysed by Welch's %! # ANOVA test, the approach here can generalize to ANOVA models with more than %! # one factor. %! %! g = [1, 1, 1, 1, 1, 1, 1, 1, ... %! 2, 2, 2, 2, 2, 2, 2, 2, ... %! 3, 3, 3, 3, 3, 3, 3, 3]'; %! y = [13, 16, 16, 7, 11, 5, 1, 9, ... %! 10, 25, 66, 43, 47, 56, 6, 39, ... %! 11, 39, 26, 35, 25, 14, 24, 17]'; %! %! [P,ATAB,STATS] = anovan (y, g, 'display', 'off'); %! fitted = STATS.X * STATS.coeffs(:,1); # fitted values %! b = polyfit (fitted, abs (STATS.resid), 1); %! v = polyval (b, fitted); # Variance as a function of the fitted values %! figure ('Name', 'Regression of the absolute residuals on the fitted values'); %! plot (fitted, abs (STATS.resid),'ob');hold on; plot (fitted,v,'-r'); hold off; %! xlabel ('Fitted values'); ylabel ('Absolute residuals'); %! %! [P,ATAB,STATS] = anovan (y, g, 'weights', v.^-1); ## Test 1 for anovan example 1 ## Test compares anovan to results from MATLAB's anovan and ttest2 functions %!test %! score = [54 23 45 54 45 43 34 65 77 46 65]'; %! gender = {'male' 'male' 'male' 'male' 'male' 'female' 'female' 'female' ... %! 'female' 'female' 'female'}'; %! %! [P, T, STATS] = anovan (score,gender,'display','off'); %! assert_equal (P(1), 0.2612876773271042, 1e-09); # compared to p calculated by MATLAB anovan %! assert_equal (sqrt (T{2,6}), abs (1.198608733288208), 1e-09); # compared to abs(t) calculated from sqrt(F) by MATLAB anovan %! assert_equal (P(1), 0.2612876773271047, 1e-09); # compared to p calculated by MATLAB ttest2 %! assert_equal (sqrt (T{2,6}), abs (-1.198608733288208), 1e-09); # compared to abs(t) calculated by MATLAB ttest2 ## Test 2 for anovan example 2 ## Test compares anovan to results from MATLAB's anovan and ttest functions %!test %! score = [4.5 5.6; 3.7 6.4; 5.3 6.4; 5.4 6.0; 3.9 5.7]'; %! treatment = {'before' 'after'; 'before' 'after'; 'before' 'after'; %! 'before' 'after'; 'before' 'after'}'; %! subject = {'GS' 'GS'; 'JM' 'JM'; 'HM' 'HM'; 'JW' 'JW'; 'PS' 'PS'}'; %! %! [P, ATAB, STATS] = anovan (score(:),{treatment(:),subject(:)},'display','off','sstype',2); %! assert_equal (P(1), 0.016004356735364, 1e-09); # compared to p calculated by MATLAB anovan %! assert_equal (sqrt (ATAB{2,6}), abs (4.00941576558195), 1e-09); # compared to abs(t) calculated from sqrt(F) by MATLAB anovan %! assert_equal (P(1), 0.016004356735364, 1e-09); # compared to p calculated by MATLAB ttest2 %! assert_equal (sqrt (ATAB{2,6}), abs (-4.00941576558195), 1e-09); # compared to abs(t) calculated by MATLAB ttest2 ## Test 3 for anovan example 3 ## Test compares anovan to results from MATLAB's anovan and anova1 functions %!test %! strength = [82 86 79 83 84 85 86 87 74 82 ... %! 78 75 76 77 79 79 77 78 82 79]'; %! alloy = {'st','st','st','st','st','st','st','st', ... %! 'al1','al1','al1','al1','al1','al1', ... %! 'al2','al2','al2','al2','al2','al2'}'; %! %! [P, ATAB, STATS] = anovan (strength,{alloy},'display','off'); %! assert_equal (P(1), 0.000152643638830491, 1e-09); %! assert_equal (ATAB{2,6}, 15.4, 1e-09); ## Test 4 for anovan example 4 ## Test compares anovan to results from MATLAB's anovan function %!test %! words = [10 13 13; 6 8 8; 11 14 14; 22 23 25; 16 18 20; ... %! 15 17 17; 1 1 4; 12 15 17; 9 12 12; 8 9 12]; %! subject = [ 1 1 1; 2 2 2; 3 3 3; 4 4 4; 5 5 5; ... %! 6 6 6; 7 7 7; 8 8 8; 9 9 9; 10 10 10]; %! seconds = [1 2 5; 1 2 5; 1 2 5; 1 2 5; 1 2 5; ... %! 1 2 5; 1 2 5; 1 2 5; 1 2 5; 1 2 5;]; %! %! [P, ATAB, STATS] = anovan (words(:),{seconds(:),subject(:)},'model','full','random',2,'sstype',2,'display','off'); %! assert_equal (P(1), 1.51865926758752e-07, 1e-09); %! assert_equal (ATAB{2,2}, 52.2666666666667, 1e-09); %! assert_equal (ATAB{3,2}, 942.533333333333, 1e-09); %! assert_equal (ATAB{4,2}, 11.0666666666667, 1e-09); ## Test 5 for anovan example 5 ## Test compares anovan to results from MATLAB's anovan function %!test %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! brands = {'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'}; %! popper = {'oil', 'oil', 'oil'; 'oil', 'oil', 'oil'; 'oil', 'oil', 'oil'; ... %! 'air', 'air', 'air'; 'air', 'air', 'air'; 'air', 'air', 'air'}; %! %! [P, ATAB, STATS] = anovan (popcorn(:),{brands(:),popper(:)},'display','off','model','full'); %! assert_equal (P(1), 7.67895738278171e-07, 1e-09); %! assert_equal (P(2), 0.000100373896304998, 1e-09); %! assert_equal (P(3), 0.746215396636649, 1e-09); %! assert_equal (ATAB{2,6}, 56.7, 1e-09); %! assert_equal (ATAB{3,6}, 32.4, 1e-09); %! assert_equal (ATAB{4,6}, 0.29999999999997, 1e-09); ## Test 6 for anovan example 6 ## Test compares anovan to results from MATLAB's anovan function %!test %! salary = [24 26 25 24 27 24 27 23 15 17 20 16, ... %! 25 29 27 19 18 21 20 21 22 19]'; %! gender = {'f' 'f' 'f' 'f' 'f' 'f' 'f' 'f' 'f' 'f' 'f' 'f'... %! 'm' 'm' 'm' 'm' 'm' 'm' 'm' 'm' 'm' 'm'}'; %! degree = [1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 0 0 0 0 0 0 0]'; %! %! [P, ATAB, STATS] = anovan (salary,{gender,degree},'model','full','sstype',1,'display','off'); %! assert_equal (P(1), 0.747462549227232, 1e-09); %! assert_equal (P(2), 1.03809316857694e-08, 1e-09); %! assert_equal (P(3), 0.523689833702691, 1e-09); %! assert_equal (ATAB{2,2}, 0.296969696969699, 1e-09); %! assert_equal (ATAB{3,2}, 272.391841491841, 1e-09); %! assert_equal (ATAB{4,2}, 1.17482517482512, 1e-09); %! assert_equal (ATAB{5,2}, 50.0000000000001, 1e-09); %! [P, ATAB, STATS] = anovan (salary,{degree,gender},'model','full','sstype',1,'display','off'); %! assert_equal (P(1), 2.53445097305047e-08, 1e-09); %! assert_equal (P(2), 0.00388133678528749, 1e-09); %! assert_equal (P(3), 0.523689833702671, 1e-09); %! assert_equal (ATAB{2,2}, 242.227272727273, 1e-09); %! assert_equal (ATAB{3,2}, 30.4615384615384, 1e-09); %! assert_equal (ATAB{4,2}, 1.17482517482523, 1e-09); %! assert_equal (ATAB{5,2}, 50.0000000000001, 1e-09); %! [P, ATAB, STATS] = anovan (salary,{gender,degree},'model','full','sstype',2,'display','off'); %! assert_equal (P(1), 0.00388133678528743, 1e-09); %! assert_equal (P(2), 1.03809316857694e-08, 1e-09); %! assert_equal (P(3), 0.523689833702691, 1e-09); %! assert_equal (ATAB{2,2}, 30.4615384615385, 1e-09); %! assert_equal (ATAB{3,2}, 272.391841491841, 1e-09); %! assert_equal (ATAB{4,2}, 1.17482517482512, 1e-09); %! assert_equal (ATAB{5,2}, 50.0000000000001, 1e-09); %! [P, ATAB, STATS] = anovan (salary,{gender,degree},'model','full','sstype',3,'display','off'); %! assert_equal (P(1), 0.00442898146583742, 1e-09); %! assert_equal (P(2), 1.30634252053587e-08, 1e-09); %! assert_equal (P(3), 0.523689833702691, 1e-09); %! assert_equal (ATAB{2,2}, 29.3706293706294, 1e-09); %! assert_equal (ATAB{3,2}, 264.335664335664, 1e-09); %! assert_equal (ATAB{4,2}, 1.17482517482512, 1e-09); %! assert_equal (ATAB{5,2}, 50.0000000000001, 1e-09); ## Test 7 for anovan example 7 ## Test compares anovan to results from MATLAB's anovan function %!test %! sugar = {'real' 'fake' 'fake' 'real' 'real' 'real' 'none' 'none' 'none' ... %! 'fake' 'fake' 'fake' 'real' 'real' 'real' 'none' 'none' 'fake'}'; %! milk = {'yes' 'no' 'no' 'yes' 'yes' 'no' 'yes' 'yes' 'yes' ... %! 'no' 'no' 'yes' 'no' 'no' 'no' 'no' 'no' 'yes'}'; %! babble = [4.6 4.4 3.9 5.6 5.1 5.5 3.9 3.5 3.7... %! 5.6 4.7 5.9 6.0 5.4 6.6 5.8 5.3 5.7]'; %! %! [P, ATAB, STATS] = anovan (babble,{sugar,milk},'model','full','sstype',1,'display','off'); %! assert_equal (P(1), 0.0108632139833963, 1e-09); %! assert_equal (P(2), 0.0810606976703546, 1e-09); %! assert_equal (P(3), 0.00175433329935627, 1e-09); %! assert_equal (ATAB{2,2}, 3.55752380952381, 1e-09); %! assert_equal (ATAB{3,2}, 0.956108477471702, 1e-09); %! assert_equal (ATAB{4,2}, 5.94386771300448, 1e-09); %! assert_equal (ATAB{5,2}, 3.1625, 1e-09); %! [P, ATAB, STATS] = anovan (babble,{milk,sugar},'model','full','sstype',1,'display','off'); %! assert_equal (P(1), 0.0373333189297505, 1e-09); %! assert_equal (P(2), 0.017075098787169, 1e-09); %! assert_equal (P(3), 0.00175433329935627, 1e-09); %! assert_equal (ATAB{2,2}, 1.444, 1e-09); %! assert_equal (ATAB{3,2}, 3.06963228699552, 1e-09); %! assert_equal (ATAB{4,2}, 5.94386771300448, 1e-09); %! assert_equal (ATAB{5,2}, 3.1625, 1e-09); %! [P, ATAB, STATS] = anovan (babble,{sugar,milk},'model','full','sstype',2,'display','off'); %! assert_equal (P(1), 0.017075098787169, 1e-09); %! assert_equal (P(2), 0.0810606976703546, 1e-09); %! assert_equal (P(3), 0.00175433329935627, 1e-09); %! assert_equal (ATAB{2,2}, 3.06963228699552, 1e-09); %! assert_equal (ATAB{3,2}, 0.956108477471702, 1e-09); %! assert_equal (ATAB{4,2}, 5.94386771300448, 1e-09); %! assert_equal (ATAB{5,2}, 3.1625, 1e-09); %! [P, ATAB, STATS] = anovan (babble,{sugar,milk},'model','full','sstype',3,'display','off'); %! assert_equal (P(1), 0.0454263063473954, 1e-09); %! assert_equal (P(2), 0.0746719907091438, 1e-09); %! assert_equal (P(3), 0.00175433329935627, 1e-09); %! assert_equal (ATAB{2,2}, 2.13184977578476, 1e-09); %! assert_equal (ATAB{3,2}, 1.00413461538462, 1e-09); %! assert_equal (ATAB{4,2}, 5.94386771300448, 1e-09); %! assert_equal (ATAB{5,2}, 3.1625, 1e-09); ## Test 8 for anovan example 8 ## Test compares anovan to results from MATLAB's anovan function %!test %! drug = {'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' ... %! 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X'; %! 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' ... %! 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y' 'Y'; %! 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' ... %! 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z' 'Z'}; %! feedback = [1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0; %! 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0; %! 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0]; %! diet = [0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1; %! 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1; %! 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1]; %! BP = [170 175 165 180 160 158 161 173 157 152 181 190 ... %! 173 194 197 190 176 198 164 190 169 164 176 175; %! 186 194 201 215 219 209 164 166 159 182 187 174 ... %! 189 194 217 206 199 195 171 173 196 199 180 NaN; %! 180 187 199 170 204 194 162 184 183 156 180 173 ... %! 202 228 190 206 224 204 205 199 170 160 NaN NaN]; %! %! [P, ATAB, STATS] = anovan (BP(:),{drug(:),feedback(:),diet(:)},'model','full','sstype', 1,'display','off'); %! assert_equal (P(1), 7.02561843825325e-05, 1e-09); %! assert_equal (P(2), 0.000425806013389362, 1e-09); %! assert_equal (P(3), 6.16780773446401e-07, 1e-09); %! assert_equal (P(4), 0.261347622678438, 1e-09); %! assert_equal (P(5), 0.0542278432357043, 1e-09); %! assert_equal (P(6), 0.590353225626655, 1e-09); %! assert_equal (P(7), 0.0861628249564267, 1e-09); %! assert_equal (ATAB{2,2}, 3614.70355731226, 1e-09); %! assert_equal (ATAB{3,2}, 2227.46639771024, 1e-09); %! assert_equal (ATAB{4,2}, 5008.25614451819, 1e-09); %! assert_equal (ATAB{5,2}, 437.066007908781, 1e-09); %! assert_equal (ATAB{6,2}, 976.180770397332, 1e-09); %! assert_equal (ATAB{7,2}, 46.616653365254, 1e-09); %! assert_equal (ATAB{8,2}, 814.345251396648, 1e-09); %! assert_equal (ATAB{9,2}, 9065.8, 1e-09); %! [P, ATAB, STATS] = anovan (BP(:),{drug(:),feedback(:),diet(:)},'model','full','sstype',2,'display','off'); %! assert_equal (P(1), 9.4879638470754e-05, 1e-09); %! assert_equal (P(2), 0.00124177666315809, 1e-09); %! assert_equal (P(3), 6.86162012732911e-07, 1e-09); %! assert_equal (P(4), 0.260856132341256, 1e-09); %! assert_equal (P(5), 0.0523758623892078, 1e-09); %! assert_equal (P(6), 0.590353225626655, 1e-09); %! assert_equal (P(7), 0.0861628249564267, 1e-09); %! assert_equal (ATAB{2,2}, 3481.72176560122, 1e-09); %! assert_equal (ATAB{3,2}, 1837.08812970469, 1e-09); %! assert_equal (ATAB{4,2}, 4957.20277938622, 1e-09); %! assert_equal (ATAB{5,2}, 437.693674777847, 1e-09); %! assert_equal (ATAB{6,2}, 988.431929811402, 1e-09); %! assert_equal (ATAB{7,2}, 46.616653365254, 1e-09); %! assert_equal (ATAB{8,2}, 814.345251396648, 1e-09); %! assert_equal (ATAB{9,2}, 9065.8, 1e-09); %! [P, ATAB, STATS] = anovan (BP(:),{drug(:),feedback(:),diet(:)},'model','full','sstype', 3,'display','off'); %! assert_equal (P(1), 0.000106518678028207, 1e-09); %! assert_equal (P(2), 0.00125371366571508, 1e-09); %! assert_equal (P(3), 5.30813260778464e-07, 1e-09); %! assert_equal (P(4), 0.308353667232981, 1e-09); %! assert_equal (P(5), 0.0562901327343161, 1e-09); %! assert_equal (P(6), 0.599091042141092, 1e-09); %! assert_equal (P(7), 0.0861628249564267, 1e-09); %! assert_equal (ATAB{2,2}, 3430.88156424581, 1e-09); %! assert_equal (ATAB{3,2}, 1833.68031496063, 1e-09); %! assert_equal (ATAB{4,2}, 5080.48346456693, 1e-09); %! assert_equal (ATAB{5,2}, 382.07709497207, 1e-09); %! assert_equal (ATAB{6,2}, 963.037988826813, 1e-09); %! assert_equal (ATAB{7,2}, 44.4519685039322, 1e-09); %! assert_equal (ATAB{8,2}, 814.345251396648, 1e-09); %! assert_equal (ATAB{9,2}, 9065.8, 1e-09); ## Test 9 for anovan example 9 ## Test compares anovan to results from MATLAB's anovan function %!test %! measurement = [444 614 423 625 408 856 447 719 ... %! 764 831 586 782 609 1002 606 766]'; %! strain= {'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola', ... %! 'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola'}'; %! treatment={'C' 'T' 'C' 'T' 'C' 'T' 'C' 'T' 'C' 'T' 'C' 'T' 'C' 'T' 'C' 'T'}'; %! block = [1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2]'; %! %! [P, ATAB, STATS] = anovan (measurement/10,{strain,treatment,block},'model','full','random',3,'display','off'); %! assert_equal (P(1:6), [0.288811428913179; 0.091455278902114; ... %! 0.042134889025806; 0.010944863181481; ... %! 0.061814763198376; 0.066584161056625], 1e-12); %! assert_equal (P(7), NaN); %! assert_equal (cell2mat (ATAB(2:8,2)), ... %! [286.132499999999; 2275.28999999999; 1242.5625; ... %! 495.905000000000; 141.472499999998; 47.6099999999987; ... %! 17.9249999999992], 1e-9); %! assert_equal (ATAB{9,2}, 0); %! assert_equal (ATAB{10,2}, 4506.8975, 1e-9); %! assert_equal (STATS.msdenom, ... %! [47.1575; 47.61; 88.7925; 5.975; 5.975; 5.975; 0], 1e-9); %! assert_equal (STATS.dfdenom, ... %! [3; 1; 2.610727841363640; 3; 3; 3; 0], 1e-12); %! assert_equal (STATS.varest, ... %! [144.22125; 20.59125; 10.40875; 5.975; 0], 1e-9); ## Test 10 for anovan example 10 ## Test compares anovan to results from MATLAB's anovan function %!test %! pulse = [67.9 65.1 77.3 78.7 79.4 80.4 85.8 86.6 87.5 89.1 ... %! 98.6 100.8 99.3 101.7 44.3 47.2 47.6 49.6 50.3 51.8 ... %! 60 58.5 58.9 60.7 69.8 70.9 76.2 76.1 77 77.7 84.7]'; %! temp = [20.8 20.8 24 24 24 24 26.2 26.2 26.2 26.2 28.4 ... %! 29 30.4 30.4 17.2 18.3 18.3 18.3 18.9 18.9 20.4 ... %! 21 21 22.1 23.5 24.2 25.9 26.5 26.5 26.5 28.6]'; %! species = {'ex' 'ex' 'ex' 'ex' 'ex' 'ex' 'ex' 'ex' 'ex' 'ex' 'ex' ... %! 'ex' 'ex' 'ex' 'niv' 'niv' 'niv' 'niv' 'niv' 'niv' 'niv' ... %! 'niv' 'niv' 'niv' 'niv' 'niv' 'niv' 'niv' 'niv' 'niv' 'niv'}; %! %! [P, ATAB, STATS] = anovan (pulse,{species,temp},'model','linear','continuous',2,'sstype','h','display','off'); %! assert_equal (P(1), 6.27153318786007e-14, 1e-09); %! assert_equal (P(2), 2.48773241196644e-25, 1e-09); %! assert_equal (ATAB{2,2}, 598.003953318404, 1e-09); %! assert_equal (ATAB{3,2}, 4376.08256843712, 1e-09); %! assert_equal (ATAB{4,2}, 89.3498685376726, 1e-09); %! assert_equal (ATAB{2,6}, 187.399388123951, 1e-09); %! assert_equal (ATAB{3,6}, 1371.35413763454, 1e-09); ## Test 11 for anovan example 11 ## Test compares anovan to results from MATLAB's anovan function %!test %! score = [95.6 82.2 97.2 96.4 81.4 83.6 89.4 83.8 83.3 85.7 ... %! 97.2 78.2 78.9 91.8 86.9 84.1 88.6 89.8 87.3 85.4 ... %! 81.8 65.8 68.1 70.0 69.9 75.1 72.3 70.9 71.5 72.5 ... %! 84.9 96.1 94.6 82.5 90.7 87.0 86.8 93.3 87.6 92.4 ... %! 100. 80.5 92.9 84.0 88.4 91.1 85.7 91.3 92.3 87.9 ... %! 91.7 88.6 75.8 75.7 75.3 82.4 80.1 86.0 81.8 82.5]'; %! treatment = {'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' ... %! 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' ... %! 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' ... %! 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' ... %! 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' ... %! 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no'}'; %! exercise = {'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' ... %! 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' ... %! 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' ... %! 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' ... %! 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' ... %! 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi'}'; %! age = [59 65 70 66 61 65 57 61 58 55 62 61 60 59 55 57 60 63 62 57 ... %! 58 56 57 59 59 60 55 53 55 58 68 62 61 54 59 63 60 67 60 67 ... %! 75 54 57 62 65 60 58 61 65 57 56 58 58 58 52 53 60 62 61 61]'; %! %! [P, ATAB, STATS] = anovan (score,{treatment,exercise,age},'model','full','continuous',3,'sstype','h','display','off'); %! assert_equal (P(5), 0.9245630968248468, 1e-09); %! assert_equal (P(6), 0.791115159521822, 1e-09); %! assert_equal (P(7), 0.9296668751457956, 1e-09); %! [P, ATAB, STATS] = anovan (score,{treatment,exercise,age},'model',[1 0 0; 0 1 0; 0 0 1; 1 1 0],'continuous',3,'sstype','h','display','off'); %! assert_equal (P(1), 0.00158132928938933, 1e-09); %! assert_equal (P(2), 2.12537505039986e-07, 1e-09); %! assert_equal (P(3), 0.00390292555160047, 1e-09); %! assert_equal (P(4), 0.0164086580775543, 1e-09); %! assert_equal (ATAB{2,6}, 11.0956027650549, 1e-09); %! assert_equal (ATAB{3,6}, 20.8195665467178, 1e-09); %! assert_equal (ATAB{4,6}, 9.10966630720186, 1e-09); %! assert_equal (ATAB{5,6}, 4.4457923698584, 1e-09); ## Test 12 for anovan example 12 ## Test compares anovan regression coefficients to R: ## https://www.uvm.edu/~statdhtx/StatPages/Unequal-ns/Unequal_n%27s_contrasts.html %!test %! dv = [ 8.706 10.362 11.552 6.941 10.983 10.092 6.421 14.943 15.931 ... %! 22.968 18.590 16.567 15.944 21.637 14.492 17.965 18.851 22.891 ... %! 22.028 16.884 17.252 18.325 25.435 19.141 21.238 22.196 18.038 ... %! 22.628 31.163 26.053 24.419 32.145 28.966 30.207 29.142 33.212 ... %! 25.694 ]'; %! g = [1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5]'; %! C = [ 0.4001601 0.3333333 0.5 0.0 %! 0.4001601 0.3333333 -0.5 0.0 %! 0.4001601 -0.6666667 0.0 0.0 %! -0.6002401 0.0000000 0.0 0.5 %! -0.6002401 0.0000000 0.0 -0.5]; %! %! [P,ATAB,STATS] = anovan (dv,g,'contrasts',{C},'display','off'); %! assert_equal (STATS.coeffs(1,1), 19.4001, 1e-04); %! assert_equal (STATS.coeffs(2,1), -9.3297, 1e-04); %! assert_equal (STATS.coeffs(3,1), -5.0000, 1e-04); %! assert_equal (STATS.coeffs(4,1), -8.0000, 1e-04); %! assert_equal (STATS.coeffs(5,1), -8.0000, 1e-04); %! assert_equal (STATS.coeffs(1,2), 0.4831, 1e-04); %! assert_equal (STATS.coeffs(2,2), 0.9694, 1e-04); %! assert_equal (STATS.coeffs(3,2), 1.3073, 1e-04); %! assert_equal (STATS.coeffs(4,2), 1.6411, 1e-04); %! assert_equal (STATS.coeffs(5,2), 1.4507, 1e-04); %! assert_equal (STATS.coeffs(1,5), 40.161, 1e-03); %! assert_equal (STATS.coeffs(2,5), -9.624, 1e-03); %! assert_equal (STATS.coeffs(3,5), -3.825, 1e-03); %! assert_equal (STATS.coeffs(4,5), -4.875, 1e-03); %! assert_equal (STATS.coeffs(5,5), -5.515, 1e-03); %! assert_equal (STATS.coeffs(2,6), 5.74e-11, 1e-12); %! assert_equal (STATS.coeffs(3,6), 0.000572, 1e-06); %! assert_equal (STATS.coeffs(4,6), 2.86e-05, 1e-07); %! assert_equal (STATS.coeffs(5,6), 4.44e-06, 1e-08); ## Interaction columns preserve factor-column ordering. %!test %! y = (1:12)'; %! g1 = repmat ([1; 2; 3], 4, 1); %! g2 = kron ([1; 2], ones (6, 1)); %! [~, ~, stats] = anovan (y, {g1, g2}, 'model', 'full', ... %! 'display', 'off'); %! assert_equal (stats.X(:,5), stats.X(:,2) .* stats.X(:,4)); %! assert_equal (stats.X(:,6), stats.X(:,3) .* stats.X(:,4)); ## Full six-factor interactions preserve every model term. %!test %! n = 128; %! group = cell (1, 6); %! for k = 1:6 %! group{k} = mod (floor ((0:n-1)' / 2^(k-1)), 2) + 1; %! endfor %! [p, ~, stats, terms] = anovan ((1:n)', group, 'model', 'full', ... %! 'display', 'off'); %! assert_equal (rows (terms), 63); %! assert_equal (columns (stats.X), 64); %! assert_equal (numel (p), 63); ## A one-level factor has no estimable main effect. %!test %! group = ones (3, 1); %! [p, tbl, stats] = anovan ((1:3)', group, ... %! 'sstype', 2, 'display', 'off'); %! assert_equal (p, NaN); %! assert_equal (tbl{2, 2}, 0); %! assert_equal (tbl{2, 3}, 0); %! assert_equal (tbl{2, 6}, NaN); %! assert_equal (size (stats.X), [3, 1]); ## Categorical factors retain their declared order and omit missing observations. %!test %! group = categorical ([3; 3; NaN; 1; 1], [3, 2, 1]); %! [p, tbl, stats] = anovan ((1:5)', group, 'sstype', 2, 'display', 'off'); %! [p_ref, tbl_ref] = anovan ([1; 2; 4; 5], [1; 1; 2; 2], ... %! 'sstype', 2, 'display', 'off'); %! assert_equal (p, p_ref, 1e-12); %! assert_equal (tbl, tbl_ref); %! assert_equal (stats.grpnames{1}, {'3'; '1'}); %! assert_equal (stats.Y, [1; 2; 4; 5]); ## Terms matrices select factors by column rather than row position. %!test %! A = [1; 1; 2; 2]; %! B = [1; 2; 1; 2]; %! y = 100 * A + B; %! [~, tbl, stats, terms] = anovan (y, {A, B}, 'model', [0, 1], ... %! 'display', 'off'); %! assert_equal (terms, [0, 1]); %! assert_equal (tbl{2, 1}, 'X2'); %! assert_equal (tbl{2, 2}, 1, 1e-12); %! assert_equal (full (stats.X(:, 2)), [-0.5; 0.5; -0.5; 0.5]); ## Reordering terms changes Type I order without relabeling their columns. %!test %! A = [1; 1; 2; 2]; %! B = [1; 2; 1; 2]; %! y = 100 * A + B; %! [~, tbl] = anovan (y, {A, B}, 'model', [0, 1; 1, 0], ... %! 'sstype', 1, 'display', 'off'); %! assert_equal (tbl(2:3, 1), {'X2'; 'X1'}); %! assert_equal (cell2mat (tbl(2:3, 2)), [1; 10000], 1e-10); ## A continuous factor raised to a power is the power of the raw predictor. %!test %! x = (1:5)'; %! y = 2 + 3 * x + 4 * x .^ 2 + [0.3; -0.2; 0.1; 0.4; -0.6]; %! [p, tbl, ~, terms] = anovan (y, x, 'model', [1; 2], 'continuous', 1, ... %! 'sstype', 2, 'display', 'off'); %! assert_equal (terms, [1; 2]); %! assert_equal (tbl(2:3, 1), {'X1'; 'X1^2'}); %! assert_equal (cell2mat (tbl(2:3, 2)), ... %! [4.09767456073500; 216.071428571430], 1e-9); %! assert_equal (cell2mat (tbl(2:3, 3)), [1; 1]); %! assert_equal (cell2mat (tbl(4, 2:3)), [0.444571428570271, 2], 1e-9); %! assert_equal (p, [0.0501972848960940; 0.00102717552592300], 1e-12); ## Hierarchical sums-of-squares adjust a power only for its lower powers. %!test %! x = (1:5)'; %! y = 2 + 3 * x + 4 * x .^ 2 + [0.3; -0.2; 0.1; 0.4; -0.6]; %! [p, tbl] = anovan (y, x, 'model', [1; 2], 'continuous', 1, ... %! 'sstype', 'h', 'display', 'off'); %! assert_equal (cell2mat (tbl(2:3, 2)), ... %! [7225.34400000000; 216.071428571430], 1e-8); %! assert_equal (p, [3.07633044150000e-05; 0.00102717552592300], 1e-12); ## The hierarchical sums-of-squares selector is not case sensitive. %!test %! x = (1:5)'; %! y = 2 + 3 * x + 4 * x .^ 2 + [0.3; -0.2; 0.1; 0.4; -0.6]; %! [p_lower, tbl_lower] = anovan (y, x, 'model', [1; 2], 'continuous', 1, ... %! 'sstype', 'h', 'display', 'off'); %! [p_upper, tbl_upper] = anovan (y, x, 'model', [1; 2], 'continuous', 1, ... %! 'sstype', 'H', 'display', 'off'); %! assert_equal (p_upper, p_lower); %! assert_equal (tbl_upper, tbl_lower); %!error ... %! anovan ((1:4)', {[1; 1; 2; 2], [1; 2; 1; 2]}, ... %! 'model', [2, 0], 'display', 'off') %!error ... %! anovan ((1:4)', {[1; 1; 2; 2], [1; 2; 1; 2]}, ... %! 'nested', [0, 1], 'display', 'off') %!error ... %! anovan ((1:4)', {[1; 1; 2; 2], [1; 2; 1; 2]}, ... %! 'nested', [0, 2; 0, 0], 'display', 'off') %!error ... %! anovan ((1:4)', {[1; 1; 2; 2], [1; 2; 1; 2]}, ... %! 'nested', eye (2), 'display', 'off') %!error ... %! anovan ((1:4)', {[1; 1; 2; 2], [1; 2; 1; 2]}, ... %! 'nested', [0, 1; 1, 0], 'display', 'off') %!error ... %! anovan ((1:4)', {[1; 1; 2; 2], [1; 2; 1; 2]}, ... %! 'nested', [0, 0; 1, 0], 'continuous', 1, 'display', 'off') %!error ... %! anovan ((1:8)', [kron([1; 2], ones(4, 1)), ... %! repmat([1; 1; 2; 2], 2, 1)], 'nested', [0, 0; 1, 0], ... %! 'contrasts', {[]; [-0.5; 0.5]}, 'display', 'off') ## A design that is not of full rank keeps a finite error sum-of-squares. %!test %! A = [1; 1; 1; 1; 2; 2]; %! B = [1; 1; 2; 2; 1; 1]; %! y = [3; 4; 7; 8; 5; 6]; %! [~, tbl] = anovan (y, {A, B}, 'model', 'full', 'sstype', 3, ... %! 'display', 'off'); %! assert_equal (tbl{end-1, 2}, 1.5, 1e-12); %! assert_equal (tbl{end-1, 3}, 3); %! assert_equal (tbl{end-1, 5}, 0.5, 1e-12); %! assert_equal (tbl{end, 2}, 17.5, 1e-12); ## Which terms are aliased depends on the model each is adjusted for. %!test %! A = [1; 1; 1; 1; 2; 2]; %! B = [1; 1; 2; 2; 1; 1]; %! y = [3; 4; 7; 8; 5; 6]; %! [~, t3] = anovan (y, {A, B}, 'model', 'full', 'sstype', 3, 'display', 'off'); %! assert_equal (cell2mat (t3(2:4, 3)), [0; 0; 0]); %! assert_equal (cell2mat (t3(2:4, 4)), [1; 1; 1]); %! [~, t2] = anovan (y, {A, B}, 'model', 'full', 'sstype', 2, 'display', 'off'); %! assert_equal (cell2mat (t2(2:4, 3)), [1; 1; 0]); %! assert_equal (cell2mat (t2(2:4, 4)), [0; 0; 1]); %! assert_equal (cell2mat (t2(2:4, 2)), [4; 16; 0], 1e-12); ## A partially aliased term reports the degrees of freedom it can estimate. %!test %! A = [1; 1; 2; 2; 3; 3]; %! B = [1; 1; 1; 1; 2; 2]; %! y = [3; 4; 7; 8; 5; 6]; %! [~, tbl] = anovan (y, {A, B}, 'model', 'linear', 'sstype', 3, ... %! 'display', 'off'); %! assert_equal (cell2mat (tbl(2:3, 3)), [1; 0]); %! assert_equal (cell2mat (tbl(2:3, 4)), [1; 1]); %! assert_equal (tbl{2, 2}, 16, 1e-12); ## A full rank design flags nothing. %!test %! A = [1; 1; 2; 2; 1; 1; 2; 2]; %! B = [1; 2; 1; 2; 1; 2; 1; 2]; %! y = [3; 4; 7; 8; 5; 6; 9; 11]; %! [~, tbl] = anovan (y, {A, B}, 'model', 'full', 'display', 'off'); %! assert_equal (cell2mat (tbl(2:4, 4)), [0; 0; 0]); %! assert_equal (cell2mat (tbl(2:4, 2)), [36.125; 3.125; 0.125], 1e-12); ## A factor with a single level has no degrees of freedom but is not aliased. %!test %! [~, tbl] = anovan ((1:3)', ones (3, 1), 'sstype', 2, 'display', 'off'); %! assert_equal (tbl{2, 3}, 0); %! assert_equal (tbl{2, 4}, 0); %! assert_equal (tbl{end-1, 2}, 2, 1e-12); ## Both effect sizes are reported, each under its own name. %!test %! A = [1; 1; 2; 2; 1; 1; 2; 2]; %! B = [1; 2; 1; 2; 1; 2; 1; 2]; %! y = [3; 4; 7; 8; 5; 6; 9; 11]; %! [~, tbl] = anovan (y, {A, B}, 'model', 'full', 'display', 'off'); %! assert_equal (tbl(1, :), {'Source', 'Sum Sq.', 'd.f.', 'Singular?', ... %! 'Mean Sq.', 'F', 'Prob>F', 'Eta Sq.', ... %! 'Part. Eta Sq.'}); %! ss = cell2mat (tbl(2:4, 2)); %! sse = tbl{end-1, 2}; %! sst = tbl{end, 2}; %! assert_equal (cell2mat (tbl(2:4, 8)), ss ./ sst, 1e-12); %! assert_equal (cell2mat (tbl(2:4, 9)), ss ./ (ss + sse), 1e-12); ## A model with a random factor reports what each mean square estimates. %!test %! y = [444 614 423 625 408 856 447 719 ... %! 764 831 586 782 609 1002 606 766]' / 10; %! g1 = {'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola', ... %! 'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola'}'; %! g2 = {'C','T','C','T','C','T','C','T','C','T','C','T','C','T','C','T'}'; %! g3 = [1;1;1;1;1;1;1;1;2;2;2;2;2;2;2;2]; %! [~, tbl] = anovan (y, {g1, g2, g3}, 'model', 'full', 'random', 3, ... %! 'display', 'off'); %! assert_equal (size (tbl), [10, 17]); %! assert_equal (tbl(1, 1:15), {'Source', 'Sum Sq.', 'd.f.', 'Singular?', ... %! 'Mean Sq.', 'F', 'Prob>F', 'Type', 'Expected MS', ... %! 'MS denom', 'd.f. denom', 'Denom. defn.', 'Var. est.', ... %! 'Var. lower bnd', 'Var. upper bnd'}); %! assert_equal (tbl(2:9, 8), {'fixed'; 'fixed'; 'random'; 'fixed'; ... %! 'random'; 'random'; 'random'; 'random'}); %! assert_equal (cell2mat (tbl(2:8, 11)), ... %! [3; 1; 2.610727841363640; 3; 3; 3; 0], 1e-12); ## With every factor fixed the table keeps the fixed-effects layout. %!test %! [~, tbl] = anovan ((1:12)', {repmat([1;2;3],4,1), kron([1;2],ones(6,1))}, ... %! 'model', 'full', 'display', 'off'); %! assert_equal (size (tbl), [6, 9]); %! assert_equal (tbl{1, 9}, 'Part. Eta Sq.'); ## A random factor keeps its interactions in the model. %!test %! y = [444 614 423 625 408 856 447 719 ... %! 764 831 586 782 609 1002 606 766]' / 10; %! g1 = {'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola', ... %! 'NIH','NIH','BALB/C','BALB/C','A/J','A/J','129/Ola','129/Ola'}'; %! g2 = {'C','T','C','T','C','T','C','T','C','T','C','T','C','T','C','T'}'; %! g3 = [1;1;1;1;1;1;1;1;2;2;2;2;2;2;2;2]; %! [p, tbl, s, terms] = anovan (y, {g1, g2, g3}, 'model', 'full', ... %! 'random', 3, 'display', 'off'); %! assert_equal (rows (terms), 7); %! assert_equal (numel (p), 7); %! assert_equal (size (s.denom), [7, 5]); %! assert_equal (size (s.ems), [8, 8]); %! assert_equal (numel (s.txtems), 8); ## An interaction term is named with Wilkinson's interaction operator. %!test %! [~, tbl] = anovan ((1:12)', {repmat([1;2;3],4,1), kron([1;2],ones(6,1))}, ... %! 'model', 'full', 'display', 'off'); %! assert_equal (tbl(2:end, 1), ... %! {'X1'; 'X2'; 'X1:X2'; 'Error'; 'Total'}); ## A three-factor interaction names every factor in it. %!test %! g = cell (1, 3); %! for k = 1:3 %! g{k} = mod (floor ((0:15)' / 2^(k-1)), 2) + 1; %! endfor %! [~, tbl] = anovan ((1:16)', g, 'model', 'full', 'display', 'off'); %! assert_equal (tbl{8, 1}, 'X1:X2:X3'); ## The random-factor marker is a printing convention, not part of the names. %!test %! y = [3; 4; 7; 8; 5; 6; 9; 11]; %! A = [1; 1; 2; 2; 1; 1; 2; 2]; %! B = [1; 2; 1; 2; 1; 2; 1; 2]; %! [~, tbl, s] = anovan (y, {A, B}, 'model', 'full', 'random', 2, ... %! 'display', 'off'); %! assert_equal (tbl(2:4, 1), {'X1'; 'X2'; 'X1:X2'}); %! assert_equal (s.varnames, {'X1', 'X2'}); %! assert_equal (any (cellfun (@(t) any (t == "'"), s.txtems)), false); %! assert_equal (any (cellfun (@(t) any (t == "'"), s.txtdenom)), false); ## The printed table marks a random factor with a trailing quote. %!test %! y = [3; 4; 7; 8; 5; 6; 9; 11]; %! A = [1; 1; 2; 2; 1; 1; 2; 2]; %! B = [1; 2; 1; 2; 1; 2; 1; 2]; %! visible = get (0, 'defaultfigurevisible'); %! unwind_protect %! set (0, 'defaultfigurevisible', 'off'); %! txt = evalc (["anovan (y, {A, B}, 'model', 'full', 'random', 2, ", ... %! "'display', 'on');"]); %! unwind_protect_cleanup %! set (0, 'defaultfigurevisible', visible); %! close all; %! end_unwind_protect %! assert_equal (! isempty (strfind (txt, "X2'")), true); %! assert_equal (! isempty (strfind (txt, "X1:X2'")), true); statistics-release-1.9.2/inst/Hypothesis_Testing/ansaribradley.m000066400000000000000000000300641524624707500251610ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} ansaribradley (@var{x}, @var{y}) ## @deftypefnx {statistics} {@var{h} =} ansaribradley (@var{x}, @var{y}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{h}, @var{p}] =} ansaribradley (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{p}, @var{stats}] =} ansaribradley (@dots{}) ## ## Ansari-Bradley two-sample test for equal dispersions. ## ## @code{@var{h} = ansaribradley (@var{x}, @var{y})} performs an Ansari-Bradley ## test of the hypothesis that the two independent samples in the vectors ## @var{x} and @var{y} come from distributions with the same dispersion ## parameter, against the alternative that they come from distributions with ## different dispersions. The result is @var{h} = 0 if the null hypothesis of ## equal dispersions cannot be rejected at the 5% significance level, or ## @var{h} = 1 if it can. ## ## The Ansari-Bradley test is a nonparametric alternative to the two-sample ## @math{F} test (@code{vartest2}) that does not assume normality. It assumes ## that the two samples are independent and that they come from distributions ## with the same median and shape, differing (under the alternative) only in ## dispersion. If the medians differ, the data should be recentred (e.g.@: by ## subtracting the sample medians) before applying the test. ## ## @code{ansaribradley} treats NaNs in @var{x} or @var{y} as missing values and ## ignores them. ## ## @code{[@var{h}, @var{p}] = ansaribradley (@dots{})} returns the p-value of ## the test, that is the probability, under the null hypothesis, of observing a ## value of the test statistic as or more extreme than the one observed. ## ## @code{[@var{h}, @var{p}, @var{stats}] = ansaribradley (@dots{})} returns a ## structure with the following fields: ## ## @multitable @columnfractions 0.2 0.75 ## @item @qcode{W} @tab the value of the Ansari-Bradley test statistic, the sum ## of the Ansari-Bradley scores of the sample @var{x} ## @item @qcode{Wstar} @tab the value of the approximate normal (z) statistic ## @end multitable ## ## @code{[@dots{}] = ansaribradley (@dots{}, @var{name}, @var{value})} specifies ## one or more of the following name/value pairs: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'alpha'} @tab the significance level. Default is 0.05. ## ## @item @qcode{'tail'} @tab a string specifying the alternative hypothesis ## ## @item @qcode{'method'} @tab a string selecting the p-value computation, ## either @qcode{'exact'} to use the exact permutation distribution of the ## statistic, or @qcode{'approximate'} to use the normal approximation. The ## default is @qcode{'exact'} when the total sample size is 25 or less, and ## @qcode{'approximate'} otherwise. ## @end multitable ## ## The @qcode{'tail'} option can take one of the following values: ## ## @multitable @columnfractions 0.15 0.75 ## @item @qcode{'both'} @tab dispersions are not equal (two-tailed, default) ## @item @qcode{'right'} @tab dispersion of @var{x} is greater than dispersion ## of @var{y} (right-tailed) ## @item @qcode{'left'} @tab dispersion of @var{x} is less than dispersion of ## @var{y} (left-tailed) ## @end multitable ## ## @seealso{vartest2, vartestn, kstest2, ranksum} ## @end deftypefn function [h, p, stats] = ansaribradley (x, y, varargin) ## Validate input arguments if (nargin < 2) error ("ansaribradley: too few input arguments."); endif if (! isvector (x) || ! isvector (y)) error ("ansaribradley: X and Y must be vectors."); endif ## Remove missing data and make column vectors x = x(! isnan (x))(:); y = y(! isnan (y))(:); if (isempty (x)) error ("ansaribradley: not enough data in X."); endif if (isempty (y)) error ("ansaribradley: not enough data in Y."); endif ## Add defaults and parse optional name/value pairs alpha = 0.05; tail = 'both'; method = []; if (mod (numel (varargin), 2) != 0) error ("ansaribradley: optional arguments must be in name/value pairs."); endif for idx = 1:2:numel (varargin) name = varargin{idx}; value = varargin{idx + 1}; switch (lower (name)) case 'alpha' alpha = value; if (! isscalar (alpha) || ! isnumeric (alpha) || ! isreal (alpha) ... || alpha <= 0 || alpha >= 1) error ("ansaribradley: invalid value for alpha."); endif case 'tail' tail = value; if (! (ischar (tail) && isrow (tail)) ... || ! any (strcmpi (tail, {'both', 'left', 'right'}))) error ("ansaribradley: invalid value for tail."); endif case 'method' method = value; if (! (ischar (method) && isrow (method)) ... || ! any (strcmpi (method, {'exact', 'approximate'}))) error ("ansaribradley: invalid value for method."); endif otherwise error ("ansaribradley: invalid name for optional arguments."); endswitch endfor nx = numel (x); ny = numel (y); N = nx + ny; ## Select the default computation method if (isempty (method)) if (N <= 25) method = 'exact'; else method = 'approximate'; endif endif ## Compute the Ansari-Bradley scores of the pooled sample. The raw score of ## the observation ranked I among the N pooled values is min (I, N + 1 - I), ## so that the extremes get the smallest scores and the centre the largest. ## Tied observations receive the average of their raw scores (mid-ranks). z = [x; y]; [zs, ord] = sort (z); pos = (1:N)'; rawscore = min (pos, N + 1 - pos); [~, ~, grp] = unique (zs); meanscore = accumarray (grp, rawscore, [], @mean); sortedscore = meanscore(grp); abscore = zeros (N, 1); abscore(ord) = sortedscore; ## Ansari-Bradley statistic: sum of the scores belonging to sample X W = sum (abscore(1:nx)); ## Normal approximation statistic (tie-corrected), always reported in STATS abar = mean (abscore); EW = nx * abar; VarW = (nx * ny) / (N * (N - 1)) * sum ((abscore - abar) .^ 2); Wstar = (W - EW) / sqrt (VarW); ## Compute the p-value. Larger dispersion in X pushes its observations to the ## extremes, lowering its scores and hence W; thus the 'right' alternative ## (dispersion of X greater) corresponds to the lower tail of W. if (strcmpi (method, 'exact')) ## Exact permutation distribution of W by dynamic programming over the ## scores doubled to integers (mid-ranks are integers or half-integers). s2 = round (2 * abscore); S = sum (s2); ## counts(k+1,t+1) = number of size-k subsets of scores summing to t counts = zeros (nx + 1, S + 1); counts(1, 1) = 1; for i = 1:N si = s2(i); for k = min (i, nx):-1:1 counts(k + 1, (si + 1):(S + 1)) += counts(k, 1:(S + 1 - si)); endfor endfor dist = counts(nx + 1, :); total = sum (dist); w2 = round (2 * W); p_le = sum (dist(1:(w2 + 1))) / total; p_ge = sum (dist((w2 + 1):end)) / total; switch (lower (tail)) case 'both' p = min (1, 2 * min (p_le, p_ge)); case 'right' p = p_le; case 'left' p = p_ge; endswitch else switch (lower (tail)) case 'both' p = 2 * normcdf (- abs (Wstar)); case 'right' p = normcdf (Wstar); case 'left' p = normcdf (- Wstar); endswitch endif ## Determine the test outcome and assemble the STATS structure h = double (p <= alpha); if (nargout > 2) stats = struct ('W', W, 'Wstar', Wstar); endif endfunction %!demo %! ## Test whether two samples have the same dispersion. The second sample is %! ## drawn with twice the standard deviation, so the null hypothesis of equal %! ## dispersions should be rejected. %! x = [42, 44, 38, 52, 48, 46, 40, 50]; %! y = [30, 62, 25, 70, 33, 58, 20, 65]; %! [h, p, stats] = ansaribradley (x, y) ## Test input validation %!error ansaribradley (1); %!error ... %! ansaribradley (ones (3, 2), ones (3, 1)); %!error ... %! ansaribradley (ones (3, 1), ones (2, 2)); %!error ansaribradley ([NaN, NaN], [1, 2]); %!error ansaribradley ([1, 2], [NaN, NaN]); %!error ... %! ansaribradley ([1, 2, 3], [4, 5, 6], 'alpha'); %!error ... %! ansaribradley ([1, 2, 3], [4, 5, 6], 'alpha', 0); %!error ... %! ansaribradley ([1, 2, 3], [4, 5, 6], 'alpha', 1); %!error ... %! ansaribradley ([1, 2, 3], [4, 5, 6], 'alpha', -0.2); %!error ... %! ansaribradley ([1, 2, 3], [4, 5, 6], 'alpha', [0.01, 0.05]); %!error ... %! ansaribradley ([1, 2, 3], [4, 5, 6], 'alpha', 'x'); %!error ... %! ansaribradley ([1, 2, 3], [4, 5, 6], 'tail', 'other'); %!error ... %! ansaribradley ([1, 2, 3], [4, 5, 6], 'tail', 5); %!error ... %! ansaribradley ([1, 2, 3], [4, 5, 6], 'method', 'other'); %!error ... %! ansaribradley ([1, 2, 3], [4, 5, 6], 'method', 5); %!error ... %! ansaribradley ([1, 2, 3], [4, 5, 6], 'name', 'value'); ## Test results %!test %! ## A concentrated sample versus a dispersed one (default exact, N = 16). %! x = [42, 44, 38, 52, 48, 46, 40, 50]; %! y = [30, 62, 25, 70, 33, 58, 20, 65]; %! [h, p, stats] = ansaribradley (x, y); %! assert_equal (h, 1); %! assert_equal (p, 0.000155400155400155, 1e-15); %! assert_equal (stats.W, 52); %! assert_equal (stats.Wstar, 3.38061701891407, 1e-13); %!test %! ## Same data, left-tailed exact test (dispersion of X less than Y). %! x = [42, 44, 38, 52, 48, 46, 40, 50]; %! y = [30, 62, 25, 70, 33, 58, 20, 65]; %! [h, p] = ansaribradley (x, y, 'tail', 'left'); %! assert_equal (h, 1); %! assert_equal (p, 7.77000777000777e-05, 1e-16); %!test %! ## Same data, normal approximation. %! x = [42, 44, 38, 52, 48, 46, 40, 50]; %! y = [30, 62, 25, 70, 33, 58, 20, 65]; %! [h, p, stats] = ansaribradley (x, y, 'method', 'approximate'); %! assert_equal (h, 1); %! assert_equal (p, 0.000723232716430194, 1e-15); %! assert_equal (stats.Wstar, 3.38061701891407, 1e-13); %!test %! ## Total sample size 30 (> 25) defaults to the approximate method. %! u = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; %! v = [3, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 11, 12, 12, 13]; %! [h, p, stats] = ansaribradley (u, v); %! assert_equal (h, 0); %! assert_equal (p, 0.184251738662491, 1e-13); %! assert_equal (stats.Wstar, -1.32777714715389, 1e-13); %!test %! ## Tied observations receive mid-rank scores (exact method). %! x = [1, 1, 2, 3, 3]; %! y = [0, 2, 2, 2, 4, 4]; %! [h, p, stats] = ansaribradley (x, y); %! assert_equal (h, 0); %! assert_equal (p, 0.904761904761905, 1e-14); %! assert_equal (stats.W, 17); %! assert_equal (stats.Wstar, 0.245274554572897, 1e-14); %!test %! ## NaNs are ignored as missing values. %! x = [1, 2, 3, NaN, 5]; %! y = [2, NaN, 4, 6]; %! [h, p, stats] = ansaribradley (x, y); %! assert_equal (h, 0); %! assert_equal (p, 0.971428571428571, 1e-14); %! assert_equal (stats.W, 9.5); %! assert_equal (stats.Wstar, 0.253836541283405, 1e-14); statistics-release-1.9.2/inst/Hypothesis_Testing/bartlett_test.m000066400000000000000000000221461524624707500252230ustar00rootroot00000000000000## Copyright (C) 1995-2017 Kurt Hornik ## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} bartlett_test (@var{x}) ## @deftypefnx {statistics} {@var{h} =} bartlett_test (@var{x}, @var{group}) ## @deftypefnx {statistics} {@var{h} =} bartlett_test (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {@var{h} =} bartlett_test (@var{x}, @var{group}, @var{alpha}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}] =} bartlett_test (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{chisq}] =} bartlett_test (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{chisq}, @var{df}] =} bartlett_test (@dots{}) ## ## Perform a Bartlett test for the homogeneity of variances. ## ## Under the null hypothesis of equal variances, the test statistic @var{chisq} ## approximately follows a chi-square distribution with @var{df} degrees of ## freedom. ## ## The p-value (1 minus the CDF of this distribution at @var{chisq}) is ## returned in @var{pval}. @var{h} = 1 if the null hypothesis is rejected at ## the significance level of @var{alpha}. Otherwise @var{h} = 0. ## ## Input Arguments: ## ## @itemize ## @item ## @var{x} contains the data and it can either be a vector or matrix. ## If @var{x} is a matrix, then each column is treated as a separate group. ## If @var{x} is a vector, then the @var{group} argument is mandatory. ## NaN values are omitted. ## ## @item ## @var{group} contains the names for each group. If @var{x} is a vector, then ## @var{group} must be a vector of the same length, or a string array or cell ## array of strings with one row for each element of @var{x}. @var{x} values ## corresponding to the same value of @var{group} are placed in the same group. ## If @var{x} is a matrix, then @var{group} can either be a cell array of ## strings of a character array, with one row per column of @var{x} in the same ## way it is used in @code{anova1} function. If @var{x} is a matrix, then ## @var{group} can be omitted either by entering an empty array ([]) or by ## parsing only @var{alpha} as a second argument (if required to change its ## default value). ## ## @item ## @var{alpha} is the statistical significance value at which the null ## hypothesis is rejected. Its default value is 0.05 and it can be parsed ## either as a second argument (when @var{group} is omitted) or as a third ## argument. ## @end itemize ## ## @seealso{levene_test, vartest2, vartestn} ## @end deftypefn function [h, pval, chisq, df] = bartlett_test (x, varargin) ## Check for valid number of input arguments if (nargin < 1 || nargin > 3) error ("bartlett_test: invalid number of input arguments."); endif ## Add defaults group = []; alpha = 0.05; ## Check for 2nd argument being ALPHA or GROUP if (nargin > 1) if (isscalar (varargin{1}) && isnumeric (varargin{1}) ... && numel (varargin{1}) == 1) alpha = varargin{1}; ## Check for valid alpha value if (alpha <= 0 || alpha >= 1) error ("bartlett_test: wrong value for alpha."); endif elseif (isvector (varargin{1}) && numel (varargin{1} > 1)) if ((size (x, 2) == 1 && size (x, 1) == numel (varargin{1})) || ... (size (x, 2) > 1 && size (x, 2) == numel (varargin{1}))) group = varargin{1}; else error ("bartlett_test: GROUP and X mismatch."); endif elseif (isempty (varargin{1})) ## Do nothing else error ("bartlett_test: invalid second input argument."); endif endif ## Check for 3rd argument if (nargin > 2) alpha = varargin{2}; ## Check for valid alpha value if (! isscalar (alpha) || ! isnumeric (alpha) || alpha <= 0 || alpha >= 1) error ("bartlett_test: wrong value for alpha."); endif endif ## Convert group to cell array from character array, make it a column if (! isempty (group) && ischar (group)) group = cellstr (group); endif if (size (group, 1) == 1) group = group'; endif ## If x is a matrix, convert it to column vector and create a ## corresponding column vector for groups if (length (x) < prod (size (x))) [n, m] = size (x); x = x(:); gi = reshape (repmat ((1:m), n, 1), n*m, 1); if (length (group) == 0) ## no group names are provided group = gi; elseif (size (group, 1) == m) ## group names exist and match columns group = group(gi,:); else error ("bartlett_test: columns in X and GROUP length do not match."); endif endif ## Check that x and group are the same size if (! all (numel (x) == numel (group))) error (strcat ("bartlett_test: GROUP must be a vector with the same", ... " number of rows as x.")); endif ## Identify NaN values (if any) and remove them from X along with ## their corresponding values from group vector nonan = ! isnan (x); x = x(nonan); group = group(nonan, :); ## Convert group to indices and separate names [group_id, group_names] = grp2idx (group); group_id = group_id(:); ## Get sample size (n_i) and var (s^2_i) for each group with n_i > 1 groups = size (group_names, 1); rgroup = []; n_i = zeros (1, groups); s_i = n_i; for k = 1:groups group_size = find (group_id == k); if (length (group_size) > 1) n_i(k) = length (group_size); s_i(k) = var (x(group_size)); else warning (strcat (sprintf ("bartlett_test: GROUP %s has a single", ... group_names{k}), [" sample and is not included in the test.\n"])); rgroup = [rgroup, k]; n_i(k) = 1; s_i(k) = NaN; endif endfor ## Remove groups with a single sample if (! isempty (rgroup)) n_i(rgroup) = []; s_i(rgroup) = []; k = k - numel (rgroup); endif ## Compute total sample size (N) and pooled variance (S) N = sum (n_i); S = (1 / (N - k)) * sum ((n_i - 1) .* s_i); ## Calculate B statistic. That is, B ~ X^2(k-1) B_nom = (N - k) * log (S) - sum ((n_i - 1) .* log (s_i)); B_den = 1 + (1 / (3 * (k - 1))) * (sum (1 ./ (n_i - 1)) - (1 / (N - k))); chisq = B_nom / B_den; ## Calculate p-value from the chi-square distribution df = k - 1; pval = 1 - chi2cdf (chisq, df); ## Determine the test outcome h = double (pval < alpha); endfunction ## Test input validation %!error bartlett_test () %!error ... %! bartlett_test (1, 2, 3, 4); %!error bartlett_test (randn (50, 2), 0); %!error ... %! bartlett_test (randn (50, 2), [1, 2, 3]); %!error ... %! bartlett_test (randn (50, 1), ones (55, 1)); %!error ... %! bartlett_test (randn (50, 1), ones (50, 2)); %!error ... %! bartlett_test (randn (50, 2), [], 1.2); %!error ... %! bartlett_test (randn (50, 2), [], 'alpha'); %!error ... %! bartlett_test (randn (50, 1), [ones(25, 1); 2*ones(25, 1)], 1.2); %!error ... %! bartlett_test (randn (50, 1), [ones(25, 1); 2*ones(25, 1)], 'err'); %!warning ... %! bartlett_test (randn (50, 1), [ones(24, 1); 2*ones(25, 1); 3]); ## Test results %!test %! load examgrades %! [h, pval, chisq, df] = bartlett_test (grades); %! assert_equal (h, 1); %! assert_equal (pval, 7.908647337018238e-08, 1e-14); %! assert_equal (chisq, 38.73324, 1e-5); %! assert_equal (df, 4); %!test %! load examgrades %! [h, pval, chisq, df] = bartlett_test (grades(:,[2:4])); %! assert_equal (h, 1); %! assert_equal (pval, 0.01172, 1e-5); %! assert_equal (chisq, 8.89274, 1e-5); %! assert_equal (df, 2); %!test %! load examgrades %! [h, pval, chisq, df] = bartlett_test (grades(:,[1,4])); %! assert_equal (h, 0); %! assert_equal (pval, 0.88118, 1e-5); %! assert_equal (chisq, 0.02234, 1e-5); %! assert_equal (df, 1); %!test %! load examgrades %! grades = [grades; nan(10, 5)]; %! [h, pval, chisq, df] = bartlett_test (grades(:,[1,4])); %! assert_equal (h, 0); %! assert_equal (pval, 0.88118, 1e-5); %! assert_equal (chisq, 0.02234, 1e-5); %! assert_equal (df, 1); %!test %! load examgrades %! [h, pval, chisq, df] = bartlett_test (grades(:,[2,5]), 0.01); %! assert_equal (h, 0); %! assert_equal (pval, 0.01791, 1e-5); %! assert_equal (chisq, 5.60486, 1e-5); %! assert_equal (df, 1); statistics-release-1.9.2/inst/Hypothesis_Testing/barttest.m000066400000000000000000000142541524624707500241740ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{ndim} =} barttest (@var{x}) ## @deftypefnx {statistics} {@var{ndim} =} barttest (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@var{ndim}, @var{pval}] =} barttest (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@var{ndim}, @var{pval}, @var{chisq}] =} barttest (@var{x}, @var{alpha}) ## ## Bartlett's test of sphericity for correlation. ## ## It compares an observed correlation matrix to the identity matrix in order to ## check if there is a certain redundancy between the variables that we can ## summarize with a few number of factors. A statistically significant test ## shows that the variables (columns) in @var{x} are correlated, thus it makes ## sense to perform some dimensionality reduction of the data in @var{x}. ## ## @code{@var{ndim} = barttest (@var{x}, @var{alpha})} returns the number of ## dimensions necessary to explain the nonrandom variation in the data matrix ## @var{x} at the @var{alpha} significance level. @var{alpha} is an optional ## input argument and, when not provided, it is 0.05 by default. ## ## @code{[@var{ndim}, @var{pval}, @var{chisq}] = barttest (@dots{})} also ## returns the significance values @var{pval} for the hypothesis test for each ## dimension as well as the associated chi^2 values in @var{chisq} ## ## @end deftypefn function [ndim, pval, chisq] = barttest (x, alpha); ## Check for valid number of input arguments if (nargin < 1 || nargin >2) error ("barttest: invalid number of input arguments."); endif ## Check for NaN values in X if (any (isnan ( x(:)))) error ("barttest: NaN values in input are not allowed."); endif ## Add default value for alpha if not supplied if (nargin == 1) alpha = 0.05; endif ## Check for valid value of alpha if (! isscalar (alpha) || ! isnumeric (alpha) || alpha <= 0 || alpha >= 1) error ("barttest: wrong value for alpha."); endif ## Check size of data [row, col] = size (x); if (col <= 1 || row <= 1) error ("barttest: not enough data in X."); endif ## Compute the eigenvalues of X in a more efficient way latent = sort ((svd (x - repmat (mean (x, 1), row, 1)) .^ 2) / (row - 1)); ## The degrees of freedom should be N-1, where N is the sample size row -= 1; k = (0:col - 2)'; pk = col - k; loglatent = flipud (cumsum (log (latent))); ## Compute the chi-square statistic logsum = log (flipud ((latent(1) + cumsum (latent(2:col))) ./ flipud (pk))); chisq = (pk .* logsum - loglatent(1:col - 1)) * row; ## Calculate the degrees of freedom df = (pk - 1) .* (pk + 2) / 2; ## Find the corresponding p-values pval = 1 - chi2cdf (chisq, df); ## Get ndim dim = min (find (pval > alpha)); if (isempty (dim)) ndim = col; return; endif if (dim == 1) ndim = NaN; warning ("barttest: heuristics are violated."); else ndim = dim - 1; endif endfunction ## Test input validation %!error barttest () %!error barttest ([2,NaN;3,4]) %!error barttest (ones (30, 4), 'alpha') %!error barttest (ones (30, 4), 0) %!error barttest (ones (30, 4), 1.2) %!error barttest (ones (30, 4), [0.2, 0.05]) %!error barttest (ones (30, 1)) %!error barttest (ones (30, 1), 0.05) ## Test results %!test %! x = [2, 3, 4, 5, 6, 7, 8, 9; 1, 2, 3, 4, 5, 6, 7, 8]'; %! [ndim, pval, chisq] = barttest (x); %! assert_equal (ndim, 2); %! assert_equal (pval, 0); %! ## assert_equal (chisq, 512.0558, 1e-4); Result differs between octave 6 and 7 ? %!test %! x = [0.53767, 0.62702, -0.10224, -0.25485, 1.4193, 1.5237 ; ... %! 1.8339, 1.6452, -0.24145, -0.23444, 0.29158, 0.1634 ; ... %! -2.2588, -2.1351, 0.31286, 0.39396, 0.19781, 0.20995 ; ... %! 0.86217, 1.0835, 0.31286, 0.46499, 1.5877, 1.495 ; ... %! 0.31877, 0.38454, -0.86488, -0.63839, -0.80447, -0.7536 ; ... %! -1.3077, -1.1487, -0.030051, -0.017629, 0.69662, 0.60497 ; ... %! -0.43359, -0.32672, -0.16488, -0.37364, 0.83509, 0.89586 ; ... %! 0.34262, 0.29639, 0.62771, 0.51672, -0.24372, -0.13698 ; ... %! 3.5784, 3.5841, 1.0933, 0.93258, 0.21567, 0.455 ; ... %! 2.7694, 2.6307, 1.1093, 1.4298, -1.1658, -1.1816 ; ... %! -1.3499, -1.2111, -0.86365, -0.94186, -1.148, -1.4381 ; ... %! 3.0349, 2.8428, 0.077359, 0.18211, 0.10487, -0.014613; ... %! 0.7254, 0.56737, -1.2141, -1.2291, 0.72225, 0.90612 ; ... %! -0.063055,-0.17662, -1.1135, -0.97701, 2.5855, 2.4084 ; ... %! 0.71474, 0.29225, -0.0068493, -0.11468, -0.66689, -0.52466 ; ... %! -0.20497, -7.8874e-06, 1.5326, 1.3195, 0.18733, 0.20296 ; ... %! -0.12414, -0.077029, -0.76967, -0.96262, -0.082494, 0.121 ; ... %! 1.4897, 1.3683, 0.37138, 0.43653, -1.933, -2.1903 ; ... %! 1.409, 1.5882, -0.22558, -0.24835, -0.43897, -0.46247 ; ... %! 1.4172, 1.1616, 1.1174, 1.0785, -1.7947, -1.9471 ]; %! [ndim, pval, chisq] = barttest (x); %! assert_equal (ndim, 3); %! assert_equal (pval, [0; 0; 0; 0.52063; 0.34314], 1e-5); %! chisq_out = [251.6802; 210.2670; 153.1773; 4.2026; 2.1392]; %! assert_equal (chisq, chisq_out, 1e-4); statistics-release-1.9.2/inst/Hypothesis_Testing/binotest.m000066400000000000000000000123521524624707500241700ustar00rootroot00000000000000## Copyright (C) 2016 Andreas Stahel ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{h}, @var{pval}, @var{ci}] =} binotest (@var{pos}, @var{N}, @var{p0}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}] =} binotest (@var{pos}, @var{N}, @var{p0}, @var{Name}, @var{Value}) ## ## Test for probability @var{p} of a binomial sample ## ## Perform a test of the null hypothesis @var{p} == @var{p0} for a sample ## of size @var{N} with @var{pos} positive results. ## ## ## Name-Value pair arguments can be used to set various options. ## @qcode{'alpha'} can be used to specify the significance level ## of the test (the default value is 0.05). The option @qcode{'tail'}, ## can be used to select the desired alternative hypotheses. If the ## value is @qcode{'both'} (default) the null is tested against the two-sided ## alternative @code{@var{p} != @var{p0}}. The value of @var{pval} is ## determined by adding the probabilities of all event less or equally ## likely than the observed number @var{pos} of positive events. ## If the value of @qcode{'tail'} is @qcode{'right'} ## the one-sided alternative @code{@var{p} > @var{p0}} is considered. ## Similarly for @qcode{'left'}, the one-sided alternative ## @code{@var{p} < @var{p0}} is considered. ## ## If @var{h} is 0 the null hypothesis is accepted, if it is 1 the null ## hypothesis is rejected. The p-value of the test is returned in @var{pval}. ## A 100(1-alpha)% confidence interval is returned in @var{ci}. ## ## @end deftypefn function [h, p, ci] = binotest (pos, n, p0, varargin) ## Set default arguments alpha = 0.05; tail = 'both'; i = 1; while (i <= length (varargin)) switch (lower (varargin{i})) case 'alpha' i = i + 1; alpha = varargin{i}; case 'tail' i = i + 1; tail = varargin{i}; otherwise error ("binotest: Invalid Name argument."); endswitch i = i + 1; endwhile if (! isa (tail, 'char')) error ("binotest: tail argument to vartest must be a string."); endif if (n <= 0) error ("binotest: required n > 0."); endif if (p0 < 0) || (p0 > 1) error ("binotest: required 0 <= p0 <= 1."); endif if (pos < 0) || (pos > n) error ("binotest: required 0 <= pos <= n."); endif ## Based on the "tail" argument determine the P-value, the critical values, ## and the confidence interval. switch lower (tail) case 'both' A_low = binoinv (alpha / 2, n, p0) / n; A_high = binoinv (1 - alpha / 2, n, p0) / n; p_pos = binopdf (pos, n, p0); p_all = binopdf ([0:n], n, p0); ind = find (p_all <= p_pos); ## p = min(1,sum(p_all(ind))); p = sum (p_all(ind)); if (pos == 0) p_low = 0; else p_low = fzero (@(pl) 1 - binocdf (pos - 1, n, pl) - alpha / 2, [0, 1]); endif if (pos == n) p_high = 1; else p_high = fzero (@(ph) binocdf (pos, n, ph) - alpha / 2, [0, 1]); endif ci = [p_low, p_high]; case 'left' p = 1 - binocdf (pos - 1, n, p0); if (pos == n) p_high = 1; else p_high = fzero (@(ph) binocdf (pos, n, ph) - alpha, [0, 1]); endif ci = [0, p_high]; case 'right' p = binocdf (pos, n, p0); if (pos == 0) p_low = 0; else p_low = fzero (@(pl) 1 - binocdf (pos - 1, n, pl) - alpha, [0, 1]); endif ci = [p_low 1]; otherwise error ("binotest: invalid fifth (tail) argument to binotest."); endswitch % Determine the test outcome % MATLAB returns this a double instead of a logical array h = double (p < alpha); endfunction %!demo %! % flip a coin 1000 times, showing 475 heads %! % Hypothesis: coin is fair, i.e. p=1/2 %! [h,p_val,ci] = binotest (475,1000,0.5) %! % Result: h = 0 : null hypothesis not rejected, coin could be fair %! % P value 0.12, i.e. hypothesis not rejected for alpha up to 12% %! % 0.444 <= p <= 0.506 with 95% confidence %!demo %! % flip a coin 100 times, showing 65 heads %! % Hypothesis: coin shows less than 50% heads, i.e. p<=1/2 %! [h,p_val,ci] = binotest (65,100,0.5,'tail','left','alpha',0.01) %! % Result: h = 1 : null hypothesis is rejected, i.e. coin shows more heads than tails %! % P value 0.0018, i.e. hypothesis not rejected for alpha up to 0.18% %! % 0 <= p <= 0.76 with 99% confidence %!test #example from https://en.wikipedia.org/wiki/Binomial_test %! [h,p_val,ci] = binotest (51,235,1/6); %! assert_equal (p_val, 0.0437, 0.00005) %! [h,p_val,ci] = binotest (51,235,1/6,'tail','left'); %! assert_equal (p_val, 0.027, 0.0005) statistics-release-1.9.2/inst/Hypothesis_Testing/chi2gof.m000066400000000000000000000413161524624707500236640ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} chi2gof (@var{x}) ## @deftypefnx {statistics} {[@var{h}, @var{p}] =} chi2gof (@var{x}) ## @deftypefnx {statistics} {[@var{p}, @var{h}, @var{stats}] =} chi2gof (@var{x}) ## @deftypefnx {statistics} {[@dots{}] =} chi2gof (@var{x}, @var{Name}, @var{Value}, @dots{}) ## ## Chi-square goodness-of-fit test. ## ## @code{chi2gof} performs a chi-square goodness-of-fit test for discrete or ## continuous distributions. The test is performed by grouping the data into ## bins, calculating the observed and expected counts for those bins, and ## computing the chi-square test statistic ## @tex ## $$ \chi ^ 2 = \sum_{i=1}^N \left (O_i - E_i \right) ^ 2 / E_i $$ ## @end tex ## @ifnottex ## SUM((O-E).^2./E), ## @end ifnottex ## where O is the observed counts and E is the expected counts. This test ## statistic has an approximate chi-square distribution when the counts are ## sufficiently large. ## ## Bins in either tail with an expected count less than 5 are pooled with ## neighboring bins until the count in each extreme bin is at least 5. If ## bins remain in the interior with counts less than 5, @code{chi2gof} displays ## a warning. In that case, you should use fewer bins, or provide bin centers ## or binedges, to increase the expected counts in all bins. ## ## @code{@var{h} = chi2gof (@var{x})} performs a chi-square goodness-of-fit test ## that the data in the vector X are a random sample from a normal distribution ## with mean and variance estimated from @var{x}. The result is @var{h} = 0 if ## the null hypothesis (that @var{x} is a random sample from a normal ## distribution) cannot be rejected at the 5% significance level, or @var{h} = 1 ## if the null hypothesis can be rejected at the 5% level. @code{chi2gof} uses ## by default 10 bins (@qcode{'nbins'}), and compares the test statistic to a ## chi-square distribution with @qcode{@var{nbins} - 3} degrees of freedom, to ## take into account that two parameters were estimated. ## ## @code{[@var{h}, @var{p}] = chi2gof (@var{x})} also returns the p-value ## @var{p}, ## which is the probability of observing the given result, or one more extreme, ## by chance if the null hypothesis is true. If there are not enough degrees of ## freedom to carry out the test, @var{p} is NaN. ## ## @code{[@var{h}, @var{p}, @var{stats}] = chi2gof (@var{x})} also returns a ## @var{stats} structure with the following fields: ## ## @multitable @columnfractions 0.3 0.65 ## @item "chi2stat" @tab Chi-square statistic ## @item "df" @tab Degrees of freedom ## @item "binedges" @tab Vector of bin binedges after pooling ## @item "O" @tab Observed count in each bin ## @item "E" @tab Expected count in each bin ## @end multitable ## ## @code{[@dots{}] = chi2gof (@var{x}, @var{Name}, @var{Value}, @dots{})} ## specifies optional Name/Value pair arguments chosen from the following list. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'nbins'} @tab The number of bins to use. Default is 10. ## @item @qcode{'binctrs'} @tab A vector of bin centers. ## @item @qcode{'binedges'} @tab A vector of bin binedges. ## @item @qcode{'cdf'} @tab A fully specified cumulative distribution ## function or a function handle provided in a cell array whose first element is ## a function handle, and all later elements are its parameter values. The ## function must take @var{x} values as its first argument, and other parameters ## as later arguments. ## @item @qcode{'expected'} @tab A vector with one element per bin ## specifying the expected counts for each bin. ## @item @qcode{'nparams'} @tab The number of estimated parameters; used to ## adjust the degrees of freedom to be @qcode{@var{nbins} - 1 - @var{nparams}}, ## where @var{nbins} is the number of bins. ## @item @qcode{'emin'} @tab The minimum allowed expected value for a bin; ## any bin in either tail having an expected value less than this amount is ## pooled with a neighboring bin. Use the value 0 to prevent pooling. Default ## is 5. ## @item @qcode{'frequency'} @tab A vector of the same length as @var{x} ## containing the frequency of the corresponding @var{x} values. ## @item @qcode{'alpha'} @tab An @var{alpha} value such that the hypothesis ## is rejected if @qcode{@var{p} < @var{alpha}}. Default is ## @qcode{@var{alpha} = 0.05}. ## @end multitable ## ## You should specify either @qcode{'cdf'} or @qcode{'expected'} parameters, but ## not both. If your @qcode{'cdf'} input contains extra parameters, these are ## accounted for automatically and there is no need to specify ## @qcode{'nparams'}. If your @qcode{'expected'} input depends on estimated ## parameters, you should use the @qcode{'nparams'} parameter to ensure that the ## degrees of freedom for the test is correct. ## ## @end deftypefn function [h, p, stats] = chi2gof (x, varargin) ## Check input arguments if (nargin < 1) error ("chi2gof: At least one input argument is required."); endif if (! isvector (x) || ! isreal (x)) error ("chi2gof: X must be a vector of real numbers."); endif ## Add initial parameters nbins = []; binctrs = []; binedges = []; cdf_spec = []; expected = []; nparams = []; emin = 5; frequency = []; alpha = 0.05; ## Parse additional arguments numarg = nargin - 1; argpos = 1; while (numarg) argname = varargin{argpos}; switch (lower (argname)) case 'nbins' nbins = varargin{argpos + 1}; case 'ctrs' binctrs = varargin{argpos + 1}; case 'edges' binedges = varargin{argpos + 1}; case 'cdf' cdf_spec = varargin{argpos + 1}; case 'expected' expected = varargin{argpos + 1}; case 'nparams' nparams = varargin{argpos + 1}; case 'emin' emin = varargin{argpos + 1}; case 'frequency' frequency = varargin{argpos + 1}; case 'alpha' alpha = varargin{argpos + 1}; endswitch numarg -= 2; argpos += 2; endwhile ## Check additional arguments for errors if ((! isempty (nbins) + ! isempty (binctrs) + ! isempty (binedges)) > 1) error ("chi2gof: Inconsistent Arguments."); endif if ((! isempty (cdf_spec) + ! isempty (expected)) > 1) error ("chi2gof: Conflicted Arguments."); endif if (! isempty (frequency)) if (! isvector (frequency) || numel (frequency) != numel (x)) error ("chi2gof: X and Frequency vectors mismatch."); endif if (any (frequency < 0)) error ("chi2gof: Frequency vector contains negative numbers."); endif endif if (! isscalar (emin) || emin < 0 || emin != round (emin) || ! isreal (emin)) error ("chi2gof: 'emin' must be a positive integer."); endif if (! isempty (nparams)) if (! isscalar (nparams) || nparams < 0 || nparams != round (nparams) ... || ! isreal (nparams)) error ("chi2gof: Wrong number of parameters."); endif endif if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1) error ("chi2gof: Wrong value of alpha."); endif ## Make X a column vector x = x(:); ## Parse or create a frequency vector if (isempty (frequency)) frequency = ones (size (x)); else frequency = frequency(:); endif ## Remove NaNs if any remove_NaNs = isnan (frequency) | isnan (x); if (any (remove_NaNs)) x(remove_NaNs) = []; frequency(remove_NaNs) = []; endif ## Check for bin numbers, centers, or edges and calculate bins accordingly if (! isempty (binctrs)) [Observed, binedges] = calculatebins (x, frequency, 'ctrs', binctrs); elseif (! isempty (binedges)) [Observed, binedges] = calculatebins (x, frequency, 'edges', binedges); else if (isempty (nbins)) if (isempty (expected)) nbins = 10; ## default number of bins else nbins = length (expected); ## determined by Expected vector endif endif [Observed, binedges] = calculatebins (x, frequency, 'nbins', nbins); endif Observed = Observed(:); nbins = length (Observed); ## Calculate expected vector cdfargs = {}; if (! isempty (expected)) ## Provided as input argument if (! isvector (expected) || numel (expected) != nbins) error ("chi2gof: Expected counts vector is the wrong size."); endif if (any (expected < 0)) error ("chi2gof: Expected counts vector has negative values."); endif Expected = expected(:); else ## Calculate from the cdf if (isempty (cdf_spec)) ## Use estimated normal as default cdffunc = @normcdf; sumfreq = sum (frequency); mu = sum (x.*frequency)/sumfreq; sigma = sqrt (sum ((x.*frequency - mu) .^ 2) / (sumfreq-1)); cdfargs = {mu, sigma}; if (isempty (nparams)) nparams = 2; endif elseif (isa (cdf_spec, 'function_handle')) ## Split function handle to get function name and optional parameters cstr = ostrsplit (func2str (cdf_spec), ','); ## Simple function handle, no parameters: e.g. @normcdf if (isempty (strfind (cstr, '@')) && numel (cstr) == 1) cdffunc = str2func (char (strcat ("@", cstr))); if (isempty (nparams)) nparams = numel (cdfargs); endif ## Complex function handle, no parameters: e.g. @(x) normcdf(x) elseif (! isempty (strfind (cstr, '@')) && numel (cstr) == 1) ## Remove white spaces cstr = char (cstr); cstr(strfind (cstr, ' ')) = []; ## Remove input argument in parentheses while (length (strfind (cstr,'('))) cstr(index (cstr, '('):index (cstr, ')')) = []; endwhile cdffunc = str2func (cstr); if (isempty (nparams)) nparams = numel (cdfargs); endif elseif (! isempty (strfind (cstr, '@')) && numel (cstr) > 1) ## Evaluate function name in first cell cstr_f = char (cstr(1)); cstr_f(strfind (cstr_f, ' ')) = []; cstr_f(index (cstr_f, '('):index (cstr_f, ')')) = []; cstr_f(index (cstr_f, '('):end) = []; cdffunc = str2func (cstr_f); ## Evaluate optional parameters in remaining cells cstr_idx = 2; while (cstr_idx <= numel (cstr)) cstr_p = char (cstr(cstr_idx)); cstr_p(strfind (cstr_p, ' ')) = []; ## Check for numerical value if (isscalar (str2num (cstr_p))) cdfargs{cstr_idx - 1} = cstr_p; else ## Get function handle: e.g. mean cstr_p(index (cstr_p, '('):end) = []; cdfargs{cstr_idx - 1} = feval (str2func (cstr_p), x .* frequency); cstr_idx += 1; endif endwhile if (isempty (nparams)) nparams = numel (cdfargs); endif endif elseif (iscell (cdf_spec)) % Get function and args from cell array cdffunc = cdf_spec{1}; cdfargs = cdf_spec(2:end); if (isempty (nparams)) nparams = numel (cdfargs); endif endif if (! is_function_handle (cdffunc)) error ("chi2gof: Poorly specified cumulative distribution function."); else cdfname = func2str (cdffunc); endif ## Calculate only inner bins, since tail probabilities included in the ## calculation of expected counts for the first and last bins interioredges = binedges(2:end-1); ## Compute the cumulative probabilities Fcdf = feval (cdffunc, interioredges, cdfargs{:}); if (! isvector (Fcdf) || numel (Fcdf) != (nbins - 1)) msg = sprintf ("chi2gof: Wrong number of outputs from: %s\n", cdfname); error (msg); endif % Compute the expected values Expected = sum (Observed) * diff ([0;Fcdf(:);1]); endif ## Avoid too small expected values if (any (Expected < emin)) [Expected, Observed, binedges] = poolbins (Expected, Observed, binedges, emin); nbins = length (Expected); endif ## Compute test statistic cstat = sum (((Observed - Expected) .^ 2) ./ Expected); ## Calculate degrees of freedom if (isempty (nparams)) nparams = 0; endif df = nbins - 1 - nparams; if (df > 0) p = 1 - chi2cdf (cstat, df); else df = 0; p = NaN; endif h = cast (p <= alpha, 'double'); ## Create 3rd output argument if necessary if (nargout > 2) stats.chi2stat = cstat; stats.df = df; stats.edges = binedges; stats.O = Observed'; stats.E = Expected'; endif endfunction function [Expected, Observed, binedges] = poolbins (Expected, ... Observed, binedges, emin) i = 1; j = length (Expected); while (i < j - 1 && (Expected(i) < emin || Expected(i + 1) < emin || ... Expected(j) < emin || Expected(j - 1) < emin)) if (Expected(i) < Expected(j)) Expected(i+1) = Expected(i+1) + Expected(i); Observed(i+1) = Observed(i+1) + Observed(i); i = i + 1; else Expected(j-1) = Expected(j-1) + Expected(j); Observed(j-1) = Observed(j-1) + Observed(j); j = j - 1; endif endwhile ## Keep only pooled bins Expected = Expected(i:j); Observed = Observed(i:j); binedges(j+1:end-1) = []; binedges(2:i) = []; endfunction function [Observed, binedges] = calculatebins (x, frequency, binspec, specval) lo = double (min (x(:))); hi = double (max (x(:))); ## Check binspec for bin count, bin centers, or bin edges. switch (binspec) case 'nbins' nbins = specval; if (isempty (x)) lo = 0; hi = 1; endif if (lo == hi) lo = lo - floor (nbins / 2) - 0.5; hi = hi + ceil (nbins / 2) - 0.5; endif binwidth = (hi - lo) ./ nbins; binedges = lo + binwidth * (0:nbins); binedges(length (binedges)) = hi; case 'ctrs' binctrs = specval(:)'; binwidth = diff (binctrs); binwidth = [binwidth binwidth(end)]; binedges = [binctrs(1)-binwidth(1)/2 binctrs+binwidth/2]; case 'edges' binedges = specval(:)'; endswitch ## Update bins nbins = length (binedges) - 1; ## Calculate bin numbers if (isempty (x)) binnum = x; elseif (! isequal (binspec, 'edges')) binedges = binedges + eps (binedges); [ignore, binnum] = histc (x, [-Inf binedges(2:end-1) Inf]); else [ignore, binnum] = histc (x, binedges); binnum(binnum == nbins + 1) = nbins; endif ## Remove empty bins if (any (binnum == 0)) frequency(binnum == 0) = []; binnum(binnum == 0) = []; endif ## Compute Observed vector binnum = binnum(:); Observed = accumarray ([ones(size(binnum)), binnum], frequency, [1, nbins]); endfunction %!demo %! rng (42); %! x = normrnd (50, 5, 100, 1); %! [h, p, stats] = chi2gof (x) %! [h, p, stats] = chi2gof (x, 'cdf', @(x)normcdf (x, mean (x), std (x))) %! [h, p, stats] = chi2gof (x, 'cdf', {@normcdf, mean(x), std(x)}) %!demo %! rng (42); %! x = rand (100,1 ); %! n = length (x); %! binedges = linspace (0, 1, 11); %! expectedCounts = n * diff (binedges); %! [h, p, stats] = chi2gof (x, 'binedges', binedges, 'expected', expectedCounts) %!demo %! bins = 0:5; %! obsCounts = [6 16 10 12 4 2]; %! n = sum (obsCounts); %! lambdaHat = sum (bins.*obsCounts) / n; %! expCounts = n * poisspdf (bins,lambdaHat); %! [h, p, stats] = chi2gof (bins, 'binctrs', bins, 'frequency', obsCounts, ... %! 'expected', expCounts, 'nparams',1) ## Test input validation %!error chi2gof () %!error chi2gof ([2,3;3,4]) %!error chi2gof ([1,2,3,4], 'nbins', 3, 'ctrs', [2,3,4]) %!error chi2gof ([1,2,3,4], 'frequency', [2,3,2]) %!error chi2gof ([1,2,3,4], 'frequency', [2,3,2,-2]) %!error chi2gof ([1,2,3,4], 'frequency', [2,3,2,2], 'nparams', i) %!error chi2gof ([1,2,3,4], 'frequency', [2,3,2,2], 'alpha', 1.3) %!error chi2gof ([1,2,3,4], 'expected', [-3,2,2]) %!error chi2gof ([1,2,3,4], 'expected', [3,2,2], 'nbins', 5) %!error chi2gof ([1,2,3,4], 'cdf', @normcdff) %!test %! x = [1 2 1 3 2 4 3 2 4 3 2 2]; %! [h, p, stats] = chi2gof (x); %! assert_equal (h, 0); %! assert_equal (p, NaN); %! assert_equal (stats.chi2stat, 0.1205375022748029, 1e-14); %! assert_equal (stats.df, 0); %! assert_equal (stats.edges, [1, 2.5, 4], 1e-14); %! assert_equal (stats.O, [7, 5], 1e-14); %! assert_equal (stats.E, [6.399995519909668, 5.600004480090332], 1e-14); statistics-release-1.9.2/inst/Hypothesis_Testing/chi2test.m000066400000000000000000000422121524624707500240640ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{pval} =} chi2test (@var{x}) ## @deftypefnx {statistics} {[@var{pval}, @var{chisq}] =} chi2test (@var{x}) ## @deftypefnx {statistics} {[@var{pval}, @var{chisq}, @var{dF}] =} chi2test (@var{x}) ## @deftypefnx {statistics} {[@var{pval}, @var{chisq}, @var{dF}, @var{E}] =} chi2test (@var{x}) ## @deftypefnx {statistics} {[@dots{}] =} chi2test (@var{x}, @var{name}, @var{value}) ## ## Perform a chi-squared test (for independence or homogeneity). ## ## For 2-way contingency tables, @code{chi2test} performs and a chi-squared test ## for independence or homogeneity, according to the sampling scheme and related ## question. Independence means that the two variables forming the 2-way ## table are not associated, hence you cannot predict from one another. ## Homogeneity refers to the concept of similarity, hence they all come from the ## same distribution. ## ## Both tests are computationally identical and will produce the same result. ## Nevertheless, they answer to different questions. Consider two variables, ## one for gender and another for smoking. To test independence (whether gender ## and smoking is associated), we would randomly sample from the general ## population and break them down into categories in the table. To test ## homogeneity (whether men and women share the same smoking habits), we would ## sample individuals from within each gender, and then measure their smoking ## habits (e.g. smokers vs non-smokers). ## ## When @code{chi2test} is called without any output arguments, it will print ## the result in the terminal including p-value, chi^2 statistic, and degrees of ## freedom. Otherwise it can return the following output arguments: ## ## @multitable @columnfractions 0.1 0.85 ## @item @var{pval} @tab the p-value of the relevant test. ## @item @var{chisq} @tab the chi^2 statistic of the relevant test. ## @item @var{dF} @tab the degrees of freedom of the relevant test. ## @item @var{E} @tab the EXPECTED values of the original contingency ## table. ## @end multitable ## ## Unlike MATLAB, in GNU Octave @code{chi2test} also supports 3-way tables, ## which involve three categorical variables (each in a different dimension of ## @var{x}. In its simplest form, @code{[@dots{}] = chi2test (@var{x})} will ## will test for mutual independence among the three variables. Alternatively, ## when called in the form @code{[@dots{}] = chi2test (@var{x}, @var{name}, ## @var{value})}, it can perform the following tests: ## ## @multitable @columnfractions 0.2 0.1 0.7 ## @headitem @var{name} @tab @var{value} @tab Description ## @item "mutual" @tab [] @tab Mutual independence. All variables are ## independent from each other, (A, B, C). Value must be an empty matrix. ## @item "joint" @tab scalar @tab Joint independence. Two variables are jointly ## independent of the third, (AB, C). The scalar value corresponds to the ## dimension of the independent variable (i.e. 3 for C). ## @item "marginal" @tab scalar @tab Marginal independence. Two variables are ## independent if you ignore the third, (A, C). The scalar value corresponds ## to the dimension of the variable to be ignored (i.e. 2 for B). ## @item "conditional" @tab scalar @tab Conditional independence. Two variables ## are independent given the third, (AC, BC). The scalar value corresponds to ## the dimension of the variable that forms the conditional dependence ## (i.e. 3 for C). ## @item "homogeneous" @tab [] @tab Homogeneous associations. Conditional ## (partial) odds-ratios are not related on the value of the third, ## (AB, AC, BC). Value must be an empty matrix. ## @end multitable ## ## When testing for homogeneous associations in 3-way tables, the iterative ## proportional fitting procedure is used. For small samples it is better to ## use the Cochran-Mantel-Haenszel Test. K-way tables for k > 3 are supported ## only for testing mutual independence. Similar to 2-way tables, no optional ## parameters are required for k > 3 multi-way tables. ## ## @code{chi2test} produces a warning if any cell of a 2x2 table has an expected ## frequency less than 5 or if more than 20% of the cells in larger 2-way tables ## have expected frequencies less than 5 or any cell with expected frequency ## less than 1. In such cases, use @code{fishertest}. ## ## @seealso{crosstab, fishertest, mcnemar_test} ## @end deftypefn function [pval, chisq, df, E] = chi2test (x, varargin) ## Check input arguments if (nargin < 1) print_usage (); endif if (isvector (x)) error ("chi2test: X must be a matrix."); endif if (! isreal (x)) error ("chi2test: values in X must be real numbers."); endif if (any (isnan (x(:)))) error ("chi2test: X must not have missing values (NaN)."); endif ## Get size and dimensions of contingency table sz = size (x); dim = length (sz); ## Check optional arguments if (dim == 2 && nargin > 1) error ("chi2test: optional arguments are not supported for 2-way tables."); endif if (dim == 3 && mod (numel (varargin(:)), 2) != 0) error ("chi2test: optional arguments must be in pairs."); endif if (dim == 3 && nargin > 1 && ! isnumeric (varargin{2})) error (strcat ("chi2test: value must be numeric in optional argument", ... " name/value pair, for 3-way tables.")); endif if (dim == 3 && nargin > 1 && numel (varargin{2}) > 1) error (strcat ("chi2test: value must be empty or scalar in optional", ... " argument name/value pair, for 3-way tables.")); endif if (dim >= 4 && nargin > 1) error ("chi2test: optional arguments are not supported for k>3."); endif ## Calculate total sample size n = sum (x(:)); ## For 2-way contingency table if (length (sz) == 2) ## Calculate degrees of freedom df = prod (sz - 1); ## Calculate expected values E = sum (x')' * sum (x) / n; ## For 3-way contingency table elseif (length (sz) == 3) ## Check optional arguments if (nargin == 1 || strcmpi (varargin{1}, 'mutual')) ## Calculate degrees of freedom df = prod (sz) - sum (sz) + 2; ## Calculate marginal table sums q1 = sum (sum (x, 2), 3); q2 = sum (sum (x, 1), 3); q3 = sum (sum (x, 1), 2); ns = sum (x(:)) ^ 2; for d1 = 1:size (x, 1) for d2 = 1:size (x, 2) for d3 = 1:size (x, 3) E(d1,d2,d3) = q1(d1,:,:) * q2(:,d2,:) * q3(:,:,d3) / ns; endfor endfor endfor elseif (strcmpi (varargin{1}, 'joint')) ## Get dimension of independent variable (dim) c_dim = varargin{2}; ## Calculate degrees of freedom c_sz = sz; c_sz(c_dim) = []; df = (sz(c_dim) - 1) * (prod (c_sz) - 1); ## Rearrange dimensions so that independent variable goes in dim 1 dm = [1, 2, 3]; dm(c_dim) = []; x = permute (x, [c_dim, dm]); ## Calculate partial table sums q1 = sum (sum (x, 1), 1); q2 = sum (sum (x, 2), 3); n = sum (x(:)); for d1 = 1:size (x, 1) for d2 = 1:size (x, 2) for d3 = 1:size (x, 3) E(d1,d2,d3) = q1(:,d2,d3) * q2(d1) / n; endfor endfor endfor ## Rearrange OBSERVED and EXPECTED matrices in original dimensions x = permute (x, [c_dim, dm]); x = permute (x, [c_dim, dm]); E = permute (E, [c_dim, dm]); E = permute (E, [c_dim, dm]); elseif (strcmpi (varargin{1}, 'marginal')) ## Get dimension of marginal variable (dim) c_dim = varargin{2}; ## Calculate degrees of freedom c_sz = sz; c_sz(c_dim) = []; df = prod (sz) - sum (c_sz) + 1; ## Rearrange dimensions so that marginal variable goes in dim 1 dm = [1, 2, 3]; dm(c_dim) = []; x = permute (x, [c_dim, dm]); ## Calculate partial table sums q1 = sum (sum (x, 1), 3); q2 = sum (sum (x, 1), 2); n2 = sz(c_dim) * sum (x(:)); ## Calculate expected values for d1 = 1:size (x, 1) for d2 = 1:size (x, 2) for d3 = 1:size (x, 3) E(d1,d2,d3) = q1(:,d2) * q2(:,:,d3) / n2; endfor endfor endfor ## Rearrange OBSERVED and EXPECTED matrices in original dimensions x = permute (x, [c_dim, dm]); E = permute (E, [c_dim, dm]); elseif (strcmpi (varargin{1}, 'conditional')) ## Get dimension of conditional variable (dim) c_dim = varargin{2}; ## Calculate degrees of freedom c_sz = sz; c_sz(c_dim) = []; df = prod (c_sz - 1) * sz(c_dim); ## Rearrange dimensions so that conditional variable goes in dim 1 dm = [1, 2, 3]; dm(c_dim) = []; x = permute (x, [c_dim, dm]); ## Calculate partial table sums q1 = sum (sum (x, 3), 3); q2 = sum (sum (x, 2), 2); q3 = sum (sum (x, 2), 3); ## Calculate expected values for d1 = 1:size (x, 1) for d2 = 1:size (x, 2) for d3 = 1:size (x, 3) E(d1,d2,d3) = q1(d1,d2) * q2(d1,:,d3) / q3(d1); endfor endfor endfor ## Rearrange OBSERVED and EXPECTED matrices in original dimensions x = permute (x, [c_dim, dm]); x = permute (x, [c_dim, dm]); E = permute (E, [c_dim, dm]); E = permute (E, [c_dim, dm]); elseif (strcmpi (varargin{1}, 'homogeneous')) ## Calculate degrees of freedom df = prod (sz - 1); ## Compute observed marginal totals for any two dimensions omt12 = sum (sum (x, 3), 3); omt13 = sum (sum (x, 2), 2); omt23 = sum (sum (x, 1), 1); ## Produce initial seed 3-way table S = ones (sz); ## Calculate initial expected marginal totals emt12 = sum (sum (S, 3), 3); emt13 = sum (sum (S, 2), 2); emt23 = sum (sum (S, 1), 1); ## Compute difference to converge within certain tolerance or iterations OEdiff = sum (omt12(:) - emt12(:)) + sum (omt13(:) - emt13(:)) + ... sum (omt23(:) - emt23(:)); iter = 1; tol = 1e-6; ## Start Iterative Proportional Fitting Procedure while (OEdiff > tol || iter > 50) ## Rows x Columns for d1 = 1:size (x, 1) for d2 = 1:size (x, 2) for d3 = 1:size (x, 3) E(d1,d2,d3) = S(d1,d2,d3) * omt12(d1,d2) / emt12(d1,d2); endfor endfor endfor ## Update seed and recalculate Rows x Layers expected marginal totals S = E; emt13 = sum (sum (S, 2), 2); ## Rows x Layers for d1 = 1:size (x, 1) for d2 = 1:size (x, 2) for d3 = 1:size (x, 3) E(d1,d2,d3) = S(d1,d2,d3) * omt13(d1,:,d3) / emt13(d1,:,d3); endfor endfor endfor ## Update seed and recalculate Columns x Layers expected marginal totals S = E; emt23 = sum (sum (S, 1), 1); ## Columns x Layers for d1 = 1:size (x, 1) for d2 = 1:size (x, 2) for d3 = 1:size (x, 3) E(d1,d2,d3) = S(d1,d2,d3) * omt23(:,d2,d3) / emt23(:,d2,d3); endfor endfor endfor ## Update seed and recalculate Rows x Layers expected marginal totals S = E; emt12 = sum (sum (S, 3), 3); ## Update difference between OBSERVED and EXPECTED tables OEdiff = sum (omt12(:) - emt12(:)) + sum (omt13(:) - emt13(:)) + ... sum (omt23(:) - emt23(:)); iter += 1; endwhile else error ("chi2test: invalid model name for testing a 3-way table."); endif ## For k-way contingency table, where k > 3 else ## Calculate degrees of freedom df = prod (sz) - sum (sz) + 2; ## Calculate squared sample size ns = sum (x(:)) ^ (dim - 1); ## Calculate marginal table sums for each available dimension for i = 1:dim qi(i) = {x}; remdim = [1:dim]; remdim(remdim == i) = []; for j = 1:length (remdim) qi(i) = sum (qi{i}, remdim(j)); endfor qi(i) = squeeze (qi{i}); endfor ## Iterate through all cells cn = numel (x); for i = 1:cn E(i) = 1; cid = i; ## Keep track of indexing for d = dim - 1:-1:1 idx(d+1) = ceil (cid / prod (sz(1:d))); if (idx(d+1) > 1) cid -= (idx(d+1) - 1) * prod (sz(1:d)); endif endfor idx(1) = cid; ## Calculate the expected value for j = 1:dim E(i) = E(i) * qi{j}(idx(j)); endfor E(i) = E(i) / ns; endfor ## Reshape to original dimensions E = reshape (E, sz); endif ## Check expected values and display warnings if ((dim == 2 && isequal (sz, [2, 2]) && any (E(:) < 5)) || ... (dim == 2 && any (sz > 2) && sum (E(:) < 5) > 0.2 * numel (E)) || ... (dim > 2 && sum (E(:) < 5) > 0.2 * numel (E))) warning ("chi2test: Expected values less than 5."); endif if (any (E(:) < 1)) warning ("chi2test: Expected values less than 1."); endif ## Calculate chi-squared and p-value cells = ((x - E) .^2) ./ E; chisq = sum (cells(:)); pval = 1 - chi2cdf (chisq, df); ## Print results if no output requested if (nargout == 0) printf ("p-val = %f with chi^2 statistic = %f and d.f. = %d.\n", ... pval, chisq, df); endif endfunction ## Input validation tests %!error chi2test (); %!error chi2test ([1, 2, 3, 4, 5]); %!error chi2test ([1, 2; 2, 1+3i]); %!error chi2test ([NaN, 6; 34, 12]); %!error ... %! p = chi2test (ones (3, 3), 'mutual', []); %!error ... %! p = chi2test (ones (3, 3, 3), 'testtype', 2); %!error ... %! p = chi2test (ones (3, 3, 3), 'mutual'); %!error ... %! p = chi2test (ones (3, 3, 3), 'joint', ['a']); %!error ... %! p = chi2test (ones (3, 3, 3), 'joint', [2, 3]); %!error ... %! p = chi2test (ones (3, 3, 3, 4), 'mutual', []) ## Check warning %!warning p = chi2test (ones (2)); %!warning p = chi2test (ones (3, 2)); %!warning p = chi2test (0.4 * ones (3)); ## Output validation tests %!test %! x = [11, 3, 8; 2, 9, 14; 12, 13, 28]; %! p = chi2test (x); %! assert_equal (p, 0.017787, 1e-6); %!test %! x = [11, 3, 8; 2, 9, 14; 12, 13, 28]; %! [p, chisq] = chi2test (x); %! assert_equal (chisq, 11.9421, 1e-4); %!test %! x = [11, 3, 8; 2, 9, 14; 12, 13, 28]; %! [p, chisq, df] = chi2test (x); %! assert_equal (df, 4); %!test %!shared x %! x(:,:,1) = [59, 32; 9,16]; %! x(:,:,2) = [55, 24;12,33]; %! x(:,:,3) = [107,80;17,56];%! %!assert_equal (chi2test (x), 2.282063427117009e-11, 1e-14); %!assert_equal (chi2test (x, 'mutual', []), 2.282063427117009e-11, 1e-14); %!assert_equal (chi2test (x, 'joint', 1), 1.164834895206468e-11, 1e-14); %!assert_equal (chi2test (x, 'joint', 2), 7.771350230001417e-11, 1e-14); %!assert_equal (chi2test (x, 'joint', 3), 0.07151361728026107, 1e-14); %!assert_equal (chi2test (x, 'marginal', 1), 0, 1e-14); %!assert_equal (chi2test (x, 'marginal', 2), 6.347555814301131e-11, 1e-14); %!assert_equal (chi2test (x, 'marginal', 3), 0, 1e-14); %!assert_equal (chi2test (x, 'conditional', 1), 0.2303114201312508, 1e-14); %!assert_equal (chi2test (x, 'conditional', 2), 0.0958810684407079, 1e-14); %!assert_equal (chi2test (x, 'conditional', 3), 2.648037344954446e-11, 1e-14); %!assert_equal (chi2test (x, 'homogeneous', []), 0.4485579470993741, 1e-14); %!test %! [pval, chisq, df, E] = chi2test (x); %! assert_equal (chisq, 64.0982, 1e-4); %! assert_equal (df, 7); %! assert_equal (E(:,:,1), [42.903, 39.921; 17.185, 15.991], ones (2, 2) * 1e-3); %!test %! [pval, chisq, df, E] = chi2test (x, 'joint', 2); %! assert_equal (chisq, 56.0943, 1e-4); %! assert_equal (df, 5); %! assert_equal (E(:,:,2), [40.922, 23.310; 38.078, 21.690], ones (2, 2) * 1e-3); %!test %! [pval, chisq, df, E] = chi2test (x, 'marginal', 3); %! assert_equal (chisq, 146.6058, 1e-4); %! assert_equal (df, 9); %! assert_equal (E(:,1,1), [61.642; 57.358], ones (2, 1) * 1e-3); %!test %! [pval, chisq, df, E] = chi2test (x, 'conditional', 3); %! assert_equal (chisq, 52.2509, 1e-4); %! assert_equal (df, 3); %! assert_equal (E(:,:,1), [53.345, 37.655; 14.655, 10.345], ones (2, 2) * 1e-3); %!test %! [pval, chisq, df, E] = chi2test (x, 'homogeneous', []); %! assert_equal (chisq, 1.6034, 1e-4); %! assert_equal (df, 2); %! assert_equal (E(:,:,1), [60.827, 31.382; 7.173, 16.618], ones (2, 2) * 1e-3); statistics-release-1.9.2/inst/Hypothesis_Testing/correlation_test.m000066400000000000000000000212211524624707500257140ustar00rootroot00000000000000## Copyright (C) 1995-2017 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} correlation_test (@var{x}, @var{y}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}] =} correlation_test (@var{y}, @var{x}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{stats}] =} correlation_test (@var{y}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} correlation_test (@var{y}, @var{x}, @var{Name}, @var{Value}) ## ## Perform a correlation coefficient test to determine whether two samples ## @var{x} and @var{y} come from uncorrelated populations. ## ## @code{@var{h} = correlation_test (@var{y}, @var{x})} tests the null ## hypothesis that the two samples @var{x} and @var{y} come from uncorrelated ## populations. The result is @var{h} = 0 if the null hypothesis cannot be ### rejected at the 5% significance level, or @var{h} = 1 if the null hypothesis ## can be rejected at the 5% level. @var{y} and @var{x} must be vectors of ## equal length with finite real numbers. ## ## The p-value of the test is returned in @var{pval}. @var{stats} is a ## structure with the following fields: ## @multitable @columnfractions 0.2 0.70 ## @headitem Field @tab Value ## @item @qcode{method} @tab the type of correlation coefficient used ## for the test ## @item @qcode{df} @tab the degrees of freedom (where applicable) ## @item @qcode{corrcoef} @tab the correlation coefficient ## @item @qcode{stat} @tab the test's statistic ## @item @qcode{dist} @tab the respective distribution for the test ## @item @qcode{alt} @tab the alternative hypothesis for the test ## @end multitable ## ## ## @code{[@dots{}] = correlation_test (@dots{}, @var{name}, @var{value})} ## specifies one or more of the following name/value pairs: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'alpha'} @tab the significance level. Default is 0.05. ## ## @item @qcode{'tail'} @tab a string specifying the alternative hypothesis ## @end multitable ## @multitable @columnfractions 0.25 0.65 ## @item @qcode{'both'} @tab @math{corrcoef} is not 0 (two-tailed, default) ## @item @qcode{'left'} @tab @math{corrcoef} is less than 0 (left-tailed) ## @item @qcode{'right'} @tab @math{corrcoef} is greater than 0 ## (right-tailed) ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @item @qcode{'method'} @tab a string specifying the correlation ## coefficient used for the test ## @end multitable ## @multitable @columnfractions 0.25 0.65 ## @item @qcode{'pearson'} @tab Pearson's product moment correlation ## (Default) ## @item @qcode{'kendall'} @tab Kendall's rank correlation tau ## @item @qcode{'spearman'} @tab Spearman's rank correlation rho ## @end multitable ## ## @seealso{regression_ftest, regression_ttest} ## @end deftypefn function [h, pval, stats] = correlation_test (x, y, varargin) if (nargin < 2) print_usage (); endif if (! isvector (x) || ! isvector (y) || length (x) != length (y)) error ("correlation_test: X and Y must be vectors of equal length."); endif ## Force to column vectors x = x(:); y = y(:); ## Check for finite real numbers in X and Y if (! all (isfinite (x)) || ! isreal (x)) error ("correlation_test: X must contain finite real numbers."); endif if (! all (isfinite (y(:))) || ! isreal (y)) error ("correlation_test: Y must contain finite real numbers."); endif ## Set default arguments alpha = 0.05; tail = 'both'; method = 'pearson'; ## Check additional options i = 1; while (i <= length (varargin)) switch lower (varargin{i}) case 'alpha' i = i + 1; alpha = varargin{i}; ## Check for valid alpha if (! isscalar (alpha) || ! isnumeric (alpha) || ... alpha <= 0 || alpha >= 1) error ("correlation_test: invalid value for alpha."); endif case 'tail' i = i + 1; tail = varargin{i}; if (! any (strcmpi (tail, {'both', 'left', 'right'}))) error ("correlation_test: invalid value for tail."); endif case 'method' i = i + 1; method = varargin{i}; if (! any (strcmpi (method, {'pearson', 'kendall', 'spearman'}))) error ("correlation_test: invalid value for method."); endif otherwise error ("correlation_test: invalid Name argument."); endswitch i = i + 1; endwhile n = length (x); if (strcmpi (method, 'pearson')) r = corr (x, y); stats.method = 'Pearson''s product moment correlation'; stats.df = n - 2; stats.corrcoef = r; stats.stat = sqrt (stats.df) .* r / sqrt (1 - r.^2); stats.dist = 'Student''s t'; cdf = tcdf (stats.stat, stats.df); elseif (strcmpi (method, 'kendall')) tau = kendall (x, y); stats.method = 'Kendall''s rank correlation tau'; stats.df = []; stats.corrcoef = tau; stats.stat = tau / sqrt ((2 * (2*n+5)) / (9*n*(n-1))); stats.dist = 'standard normal'; cdf = stdnormal_cdf (stats.stat); else # spearman rho = spearman (x, y); stats.method = 'Spearman''s rank correlation rho'; stats.df = []; stats.corrcoef = rho; stats.stat = sqrt (n-1) * (rho - 6/(n^3-n)); stats.dist = 'standard normal'; cdf = stdnormal_cdf (stats.stat); endif ## Based on the "tail" argument determine the P-value switch lower (tail) case 'both' pval = 2 * min (cdf, 1 - cdf); case 'right' pval = 1 - cdf; case 'left' pval = cdf; endswitch stats.alt = tail; ## Determine the test outcome h = double (pval < alpha); endfunction ## Test input validation %!error correlation_test (); %!error correlation_test (1); %!error ... %! correlation_test ([1 2 NaN]', [2 3 4]'); %!error ... %! correlation_test ([1 2 Inf]', [2 3 4]'); %!error ... %! correlation_test ([1 2 3+i]', [2 3 4]'); %!error ... %! correlation_test ([1 2 3]', [2 3 NaN]'); %!error ... %! correlation_test ([1 2 3]', [2 3 Inf]'); %!error ... %! correlation_test ([1 2 3]', [3 4 3+i]'); %!error ... %! correlation_test ([1 2 3]', [3 4 4 5]'); %!error ... %! correlation_test ([1 2 3]', [2 3 4]', 'alpha', 0); %!error ... %! correlation_test ([1 2 3]', [2 3 4]', 'alpha', 1.2); %!error ... %! correlation_test ([1 2 3]', [2 3 4]', 'alpha', [.02 .1]); %!error ... %! correlation_test ([1 2 3]', [2 3 4]', 'alpha', 'a'); %!error ... %! correlation_test ([1 2 3]', [2 3 4]', 'some', 0.05); %!error ... %! correlation_test ([1 2 3]', [2 3 4]', 'tail', 'val'); %!error ... %! correlation_test ([1 2 3]', [2 3 4]', 'alpha', 0.01, 'tail', 'val'); %!error ... %! correlation_test ([1 2 3]', [2 3 4]', 'method', 0.01); %!error ... %! correlation_test ([1 2 3]', [2 3 4]', 'method', 'some'); %!test %! x = [6 7 7 9 10 12 13 14 15 17]; %! y = [19 22 27 25 30 28 30 29 25 32]; %! [h, pval, stats] = correlation_test (x, y); %! assert_equal (stats.corrcoef, corr (x', y'), 1e-14); %! assert_equal (pval, 0.0223, 1e-4); %!test %! x = [6 7 7 9 10 12 13 14 15 17]'; %! y = [19 22 27 25 30 28 30 29 25 32]'; %! [h, pval, stats] = correlation_test (x, y); %! assert_equal (stats.corrcoef, corr (x, y), 1e-14); %! assert_equal (pval, 0.0223, 1e-4); statistics-release-1.9.2/inst/Hypothesis_Testing/doc-cache000066400000000000000000003707071524624707500237270ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 60 # name: # type: sq_string # elements: 1 # length: 6 adtest # name: # type: sq_string # elements: 1 # length: 3784 statistics: h = adtest ( x ) statistics: h = adtest ( x , Name , Value ) statistics: [ h , pval ] = adtest (…) statistics: [ h , pval , adstat , cv ] = adtest (…) Anderson-Darling goodness-of-fit hypothesis test. h = adtest ( x ) returns a test decision for the null hypothesis that the data in vector x is from a population with a normal distribution, using the Anderson-Darling test. The alternative hypothesis is that x is not from a population with a normal distribution. The result h is 1 if the test rejects the null hypothesis at the 5% significance level, or 0 otherwise. h = adtest ( x , Name , Value ) returns a test decision for the Anderson-Darling test with additional options specified by one or more Name-Value pair arguments. For example, you can specify a null distribution other than normal, or select an alternative method for calculating the p-value, such as a Monte Carlo simulation. The following parameters can be parsed as Name-Value pair arguments. Name Description "Distribution" The distribution being tested for. It tests whether x could have come from the specified distribution. There are two choices available for parsing distribution parameters: One of the following char strings: "norm", "exp", "ev", "logn", "weibull", for defining either the ’normal’, ’exponential’, ’extreme value’, lognormal, or ’Weibull’ distribution family, respectively. In this case, x is tested against a composite hypothesis for the specified distribution family and the required distribution parameters are estimated from the data in x . The default is "norm". A cell array defining a distribution in which the first cell contains a char string with the distribution name, as mentioned above, and the consecutive cells containing all specified parameters of the null distribution. In this case, x is tested against a simple hypothesis. Name Value "Alpha" Significance level alpha for the test. Any scalar numeric value between 0 and 1. The default is 0.05 corresponding to the 5% significance level. "MCTol" Monte-Carlo standard error for the p-value, pval , value. which must be a positive scalar value. In this case, an approximation for the p-value is computed directly, using Monte-Carlo simulations. "Asymptotic" Method for calculating the p-value of the Anderson-Darling test, which can be either true or false logical value. If you specify ’true’, adtest estimates the p-value using the limiting distribution of the Anderson-Darling test statistic. If you specify ’false’, adtest calculates the p-value based on an analytical formula. For sample sizes greater than 120, the limiting distribution estimate is likely to be more accurate than the small sample size approximation method. If you specify a distribution family with unknown parameters for the distribution Name-Value pair (i.e. composite distribution hypothesis test), the "Asymptotic" option must be false. If you use MCTol to calculate the p-value using a Monte Carlo simulation, the "Asymptotic" option must be false. [ h , pval ] = adtest (…) also returns the p-value, pval , of the Anderson-Darling test, using any of the input arguments from the previous syntaxes. [ h , pval , adstat , cv ] = adtest (…) also returns the test statistic, adstat , and the critical value, cv , for the Anderson-Darling test. The Anderson-Darling test statistic belongs to the family of Quadratic Empirical Distribution Function statistics, which are based on the weighted sum of the difference [Fn(x)-F(x)]^2 over the ordered sample values X1 < X2 < ... < Xn , where F is the hypothesized continuous distribution and Fn is the empirical CDF based on the data sample with n sample points. See also: kstest # name: # type: sq_string # elements: 1 # length: 49 Anderson-Darling goodness-of-fit hypothesis test. # name: # type: sq_string # elements: 1 # length: 5 anova # name: # type: sq_string # elements: 1 # length: 732 statistics: anova Object-oriented interface for analysis of variance. The anova class provides a MATLAB-compatible object interface for analysis of variance. It stores factors, response data, model specification, and fitted results in one object. The class chooses the narrowest compatible backend, delegates the numeric computation to the existing ANOVA functions, and exposes common follow-up operations such as stats , groupmeans , boxchart , plotComparisons , varianceComponent , and multcompare . Models are fitted lazily. Methods that need fitted results call fit internally when necessary, so users may construct an object and immediately call inspection or post-hoc methods. See also: anova1, anova2, anovan, multcompare # name: # type: sq_string # elements: 1 # length: 51 Object-oriented interface for analysis of variance. # name: # type: sq_string # elements: 1 # length: 24 anova.CategoricalFactors # name: # type: sq_string # elements: 1 # length: 144 anova: property CategoricalFactors Categorical factors Positive integer indices of factors treated as categorical. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Categorical factors # name: # type: sq_string # elements: 1 # length: 18 anova.Coefficients # name: # type: sq_string # elements: 1 # length: 134 anova: property Coefficients Model coefficient estimates Numeric vector of fitted coefficient estimates. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Model coefficient estimates # name: # type: sq_string # elements: 1 # length: 25 anova.ExpandedFactorNames # name: # type: sq_string # elements: 1 # length: 216 anova: property ExpandedFactorNames Coefficient names Cell array of character vectors naming the model coefficients when the selected backend exposes them, otherwise an empty cell array. This property is read-only. # name: # type: sq_string # elements: 1 # length: 17 Coefficient names # name: # type: sq_string # elements: 1 # length: 17 anova.FactorNames # name: # type: sq_string # elements: 1 # length: 148 anova: property FactorNames Factor names String row vector containing the factor names used by the fitted ANOVA model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 12 Factor names # name: # type: sq_string # elements: 1 # length: 13 anova.Factors # name: # type: sq_string # elements: 1 # length: 140 anova: property Factors Factor data Table containing one variable for each factor used to fit the ANOVA model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 11 Factor data # name: # type: sq_string # elements: 1 # length: 13 anova.Formula # name: # type: sq_string # elements: 1 # length: 183 anova: property Formula Model formula Read-only structural formula value with response, predictor, term, nesting, and linear-predictor fields matching MATLAB’s formula object. # name: # type: sq_string # elements: 1 # length: 13 Model formula # name: # type: sq_string # elements: 1 # length: 13 anova.Metrics # name: # type: sq_string # elements: 1 # length: 185 anova: property Metrics Model fit metrics Table with variables MSE , RMSE , SSE , SSR , SST , RSquared , and AdjustedRSquared summarising the fitted model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 17 Model fit metrics # name: # type: sq_string # elements: 1 # length: 21 anova.NumObservations # name: # type: sq_string # elements: 1 # length: 142 anova: property NumObservations Number of observations Scalar number of response observations used by the model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 19 anova.RandomFactors # name: # type: sq_string # elements: 1 # length: 118 anova: property RandomFactors Random factors Positive integer indices of random factors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Random factors # name: # type: sq_string # elements: 1 # length: 15 anova.Residuals # name: # type: sq_string # elements: 1 # length: 255 anova: property Residuals Model residuals Table with variables Raw (observed minus fitted values) and Pearson (raw residuals scaled by the root mean squared error) when the selected backend exposes residuals, otherwise empty. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Model residuals # name: # type: sq_string # elements: 1 # length: 18 anova.ResponseName # name: # type: sq_string # elements: 1 # length: 144 anova: property ResponseName Response variable name Character vector used as the response name in formula display. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 22 anova.SumOfSquaresType # name: # type: sq_string # elements: 1 # length: 167 anova: property SumOfSquaresType Sum-of-squares type String scalar selecting "one" , "two" , 'three' , or 'hierarchical' sums of squares. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Sum-of-squares type # name: # type: sq_string # elements: 1 # length: 7 anova.Y # name: # type: sq_string # elements: 1 # length: 155 anova: property Y Response data Numeric response vector (or matrix, for the one-way column form) used to fit the ANOVA model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 13 Response data # name: # type: sq_string # elements: 1 # length: 11 anova.anova # name: # type: sq_string # elements: 1 # length: 1104 anova: obj = anova ( Y ) anova: obj = anova ( factors , Y ) anova: obj = anova ( tbl , Y ) anova: obj = anova ( tbl , responseVarName ) anova: obj = anova ( tbl , formula ) anova: obj = anova (…, name , value ) Create an object-oriented analysis of variance model. Y is a non-empty numeric response vector or matrix. factors contains grouping variables for vector responses and may be a grouping vector, a matrix of grouping variables, or a cell array of grouping vectors. If factors is omitted and Y is a matrix, columns of Y are treated as groups following anova1 matrix syntax. tbl is a table whose variables contain factors. The response may be supplied separately, selected by variable name, or specified with a Wilkinson formula. 'FactorNames' can select a subset of table variables when a formula is not supplied. Supported name-value arguments include 'ModelSpecification' , 'SumOfSquaresType' , 'FactorNames' , 'CategoricalFactors' , 'RandomFactors' , 'ResponseName' , 'Alpha' , and 'Display' . Passing 'Reps' selects the balanced two-way anova2 backend when Y is a non-vector matrix. # name: # type: sq_string # elements: 1 # length: 53 Create an object-oriented analysis of variance model. # name: # type: sq_string # elements: 1 # length: 14 anova.boxchart # name: # type: sq_string # elements: 1 # length: 292 anova: boxchart ( obj ) anova: h = boxchart ( obj , …) Plot response values grouped by up to two categorical factors. This method uses boxplot as the graphics backend in Octave and returns the native box graphics handles. A target axes may be supplied as the first optional argument. # name: # type: sq_string # elements: 1 # length: 62 Plot response values grouped by up to two categorical factors. # name: # type: sq_string # elements: 1 # length: 16 anova.groupmeans # name: # type: sq_string # elements: 1 # length: 271 anova: means = groupmeans ( obj ) anova: means = groupmeans ( obj , factors ) Return mean response estimates by factor level. The returned value is a table with one row per factor-level combination and columns for the level, mean, standard error, and confidence bounds. # name: # type: sq_string # elements: 1 # length: 47 Return mean response estimates by factor level. # name: # type: sq_string # elements: 1 # length: 17 anova.multcompare # name: # type: sq_string # elements: 1 # length: 347 anova: m = multcompare ( obj ) anova: m = multcompare ( obj , factors ) anova: m = multcompare (…, name , value ) Perform post-hoc multiple comparisons for a fitted ANOVA object. The returned table contains the compared groups, estimated mean difference, confidence limits, and p-value. The default critical value type is "tukey-kramer" . # name: # type: sq_string # elements: 1 # length: 64 Perform post-hoc multiple comparisons for a fitted ANOVA object. # name: # type: sq_string # elements: 1 # length: 21 anova.plotComparisons # name: # type: sq_string # elements: 1 # length: 333 anova: plotComparisons ( obj ) anova: h = plotComparisons ( obj , …) Plot multiple-comparison intervals for model-adjusted group means. Clicking a group highlights it and distinguishes groups whose adjusted comparison is significant at the requested alpha level. A target axes may be supplied as the first optional argument. # name: # type: sq_string # elements: 1 # length: 66 Plot multiple-comparison intervals for model-adjusted group means. # name: # type: sq_string # elements: 1 # length: 11 anova.stats # name: # type: sq_string # elements: 1 # length: 872 anova: s = stats ( obj ) anova: s = stats ( obj , type ) anova: s = stats ( obj , "Component" , sstype ) anova: [ s , ems ] = stats (…) Return component or summary ANOVA statistics as a table. With no type , or with "component" , return statistics for each model term, error, and total. With "summary" , group terms into linear, nonlinear, and regression rows. Replicated continuous designs also report lack-of-fit and pure-error statistics. The "Component" form computes the component table using sstype , which must be "one" , "two" , "three" , or "hierarchical" . This request does not change the object’s read-only SumOfSquaresType property. The second output ems contains expected mean-square information for each model term and the error term. Its variables are Type , ExpectedMeanSquares , MeanSquaresDenominator , DFDenominator , and FDenominator . # name: # type: sq_string # elements: 1 # length: 56 Return component or summary ANOVA statistics as a table. # name: # type: sq_string # elements: 1 # length: 23 anova.varianceComponent # name: # type: sq_string # elements: 1 # length: 154 anova: v = varianceComponent ( obj ) anova: v = varianceComponent ( obj , …) Return variance component estimates for random model terms and error. # name: # type: sq_string # elements: 1 # length: 69 Return variance component estimates for random model terms and error. # name: # type: sq_string # elements: 1 # length: 6 anova1 # name: # type: sq_string # elements: 1 # length: 3727 statistics: p = anova1 ( x ) statistics: p = anova1 ( x , group ) statistics: p = anova1 ( x , group , displayopt ) statistics: p = anova1 ( x , group , displayopt , vartype ) statistics: [ p , atab ] = anova1 ( x , …) statistics: [ p , atab , stats ] = anova1 ( x , …) Perform a one-way analysis of variance (ANOVA) for comparing the means of two or more groups of data under the null hypothesis that the groups are drawn from distributions with the same mean. For planned contrasts and/or diagnostic plots, use anovan instead. anova1 can take up to three input arguments: x contains the data and it can either be a vector or matrix. If x is a matrix, then each column is treated as a separate group. If x is a vector, then the group argument is mandatory. group contains the names for each group. If x is a matrix, then group can either be a cell array of strings of a character array, with one row per column of x . If you want to omit this argument, enter an empty array ([]). If x is a vector, then group must be a vector of the same length, or a string array or cell array of strings with one row for each element of x . x values corresponding to the same value of group are placed in the same group. displayopt is an optional parameter for displaying the groups contained in the data in a boxplot. If omitted, it is ’on’ by default. If group names are defined in group , these are used to identify the groups in the boxplot. Use ’off’ to omit displaying this figure. vartype is an optional parameter to used to indicate whether the groups can be assumed to come from populations with equal variance. When vartype is 'equal' the variances are assumed to be equal (this is the default). When vartype is 'unequal' the population variances are not assumed to be equal and Welch’s ANOVA test is used instead. vartype is an Octave extension: MATLAB’s anova1 takes no fourth argument. It does not error on one either, it accepts it and ignores it, returning the same p and F for 'unequal' as for 'equal' . Code written against this function and then run in MATLAB therefore gets the classic ANOVA silently, with no diagnostic of any kind. Note that anova2 ’s analogous fourth argument does make MATLAB error, so the silence here is particular to anova1 . anova1 can return up to three output arguments: p is the p-value of the null hypothesis that all group means are equal. atab is a cell array containing the results in a standard ANOVA table. stats is a structure containing statistics useful for performing a multiple comparison of means with the MULTCOMPARE function. A categorical group may declare levels that no observation uses. Such a level takes no part in the analysis and is dropped from every field of stats , so n , means and gnames always describe the same groups, in the same order, and can be indexed together. This is a deliberate deviation from MATLAB, which drops an unused level from gnames but keeps it in n and means as a count of zero and a mean of NaN . Those fields then disagree in length and the group indices run past gnames , so MATLAB’s own multcompare reports comparisons against a group holding no observations and labels them with indices that its gnames cannot resolve. If anova1 is called without any output arguments, then it prints the results in a one-way ANOVA table to the standard output. It is also printed when displayopt is ’on’. Examples: x = meshgrid (1:6); x = x + normrnd (0, 1, 6, 6); anova1 (x, [], 'off'); [p, atab] = anova1(x); x = ones (50, 4) .* [-2, 0, 1, 5]; x = x + normrnd (0, 2, 50, 4); groups = {"A", "B", "C", "D"}; anova1 (x, groups); See also: anova2, anovan, multcompare # name: # type: sq_string # elements: 1 # length: 191 Perform a one-way analysis of variance (ANOVA) for comparing the means of two or more groups of data under the null hypothesis that the groups are drawn from distributions with the same mean. # name: # type: sq_string # elements: 1 # length: 6 anova2 # name: # type: sq_string # elements: 1 # length: 2217 statistics: p = anova2 ( x , reps ) statistics: p = anova2 ( x , reps , displayopt ) statistics: p = anova2 ( x , reps , displayopt , model ) statistics: [ p , atab ] = anova2 (…) statistics: [ p , atab , stats ] = anova2 (…) Performs two-way factorial (crossed) or a nested analysis of variance (ANOVA) for balanced designs. For unbalanced factorial designs, diagnostic plots and/or planned contrasts, use anovan instead. anova2 requires two input arguments with an optional third and fourth: x contains the data and it must be a matrix of at least two columns and two rows. NaN values are not accepted, since anova2 requires a balanced design; use anovan for data with missing observations. reps is the number of replicates for each combination of factor groups. displayopt is an optional parameter for displaying the ANOVA table, when it is ’on’ (default) and suppressing the display when it is ’off’. model is an optional parameter to specify the model type as either: "interaction" or "full" (default): compute both main effects and their interaction "linear": compute both main effects without an interaction. When reps > 1 the test is suitable for a balanced randomized block design. When reps == 1, the test becomes a One-way Repeated Measures (RM)-ANOVA with Greenhouse-Geisser correction to the column factor degrees of freedom to make the test robust to violations of sphericity "nested": treat the row factor as nested within columns. Note that the row factor is considered a random factor in the calculation of the statistics. anova2 returns up to three output arguments: p is the p-value of the null hypothesis that all group means are equal. atab is a cell array containing the results in a standard ANOVA table. stats is a structure containing statistics useful for performing a multiple comparison of means with the MULTCOMPARE function. If anova2 is called without any output arguments, then it prints the results in a one-way ANOVA table to the standard output as if displayopt is ’on’. Examples: load popcorn; anova2 (popcorn, 3); [p, anovatab, stats] = anova2 (popcorn, 3, "off"); disp (p); See also: anova1, anovan, multcompare # name: # type: sq_string # elements: 1 # length: 99 Performs two-way factorial (crossed) or a nested analysis of variance (ANOVA) for balanced designs. # name: # type: sq_string # elements: 1 # length: 6 anovan # name: # type: sq_string # elements: 1 # length: 10515 statistics: p = anovan ( Y , GROUP ) statistics: p = anovan ( Y , GROUP , name , value ) statistics: [ p , atab ] = anovan (…) statistics: [ p , atab , stats ] = anovan (…) statistics: [ p , atab , stats , terms ] = anovan (…) Perform a multi (N)-way analysis of (co)variance (ANOVA or ANCOVA) to evaluate the effect of one or more categorical or continuous predictors (i.e. independent variables) on a continuous outcome (i.e. dependent variable). The algorithms used make anovan suitable for balanced or unbalanced factorial (crossed) designs. By default, anovan treats all factors as fixed. Examples of function usage can be found by entering the command demo anovan . A bootstrap resampling variant of this function, bootlm , is available in the statistics-resampling package and has similar usage. Data is a single vector Y with groups specified by a corresponding matrix or cell array of group labels GROUP , where each column of GROUP has the same number of rows as Y . For example, if Y = [23; 27; 31; 29; 30; 32]; GROUP = [1, 2; 1, 3; 1, 2; 2, 3; 2, 3; 3, 2]; then observation 23 was measured under conditions 1,2; observation 27 was measured under conditions 1,3; and so on. If the GROUP provided is empty, then the linear model is fit with just the intercept (no predictors). anovan can take a number of optional parameters as name-value pairs. […] = anovan ( Y , GROUP , "continuous", continuous ) continuous is a vector of indices indicating which of the columns (i.e. factors) in GROUP should be treated as continuous predictors rather than as categorical predictors. The relationship between continuous predictors and the outcome should be linear. […] = anovan ( Y , GROUP , "random", random ) random is a vector of indices indicating which of the columns (i.e. factors) in GROUP should be treated as random effects rather than fixed effects. In the table anovan prints, the name of a random factor is followed by a ' so that it can be told apart at a glance. That mark is a convention of the printed output only: the names returned in atab , in stats .varnames, and inside the expected-mean-square and denominator expressions carry no mark, so that they read as MATLAB’s do. Which terms are random is reported in the "Type" column of atab instead. Every interaction involving a random factor stays in the model, and each F ratio is taken against the denominator its expected mean square calls for rather than against the mean squared error. The expected mean square of a term names the variance each component contributes to it, written Q(…) for a fixed term and V(…) for a random one; the denominator is the combination of mean squares whose expectation matches the term’s with the term itself removed. Often that is a single mean square, in which case the F ratio carries its degrees of freedom. When no single mean square will do, one is synthesised from several and carries Satterthwaite degrees of freedom, which are generally not whole numbers. The variance component of every random term is estimated from the same system, and reported with confidence bounds. A component estimated as negative has no interval, as it lies outside the parameter space. […] = anovan ( Y , GROUP , "model", modeltype ) modeltype can specified as one of the following: "linear" (default) : compute N main effects with no interactions. "interaction" : compute N effects and N×(N-1) two-factor interactions "full" : compute the N main effects and interactions at all levels a scalar integer : representing the maximum interaction order a matrix of term definitions : each row is a term and each column is a factor. Entries are nonnegative integer exponents. Exponents greater than one are valid only for factors selected by "continuous" . -- Example: A two-way ANOVA with interaction would be: [1 0; 0 1; 1 1] […] = anovan ( Y , GROUP , "nested", nested ) nested is an N-by-N logical matrix, where N is the number of factors. A true entry nested (i,j) specifies that factor i is nested in factor j. A factor may be nested in more than one parent. Nested factors must be categorical and use the default contrasts. […] = anovan ( Y , GROUP , "sstype", sstype ) sstype can specified as one of the following: 1 : Type I sequential sums-of-squares. 2 : Type II partially sequential sums-of-squares. Each term is adjusted for every other term that does not contain it. "h" : hierarchical sums-of-squares. Each term is adjusted only for the terms below it in the hierarchy, so for a model whose terms are all of first order it agrees with Type II, while a polynomial term is adjusted for its lower powers but not for its higher ones. 3 (default) : Type III partial, constrained or marginal sums-of-squares […] = anovan ( Y , GROUP , "varnames", varnames ) varnames must be a cell array of strings with each element containing a factor name for each column of GROUP . By default (if not parsed as optional argument), varnames are "X1","X2","X3", etc. […] = anovan ( Y , GROUP , "alpha", alpha ) alpha must be a scalar value between 0 and 1 requesting 100×(1- alpha )% confidence bounds for the regression coefficients returned in stats .coeffs (default 0.05 for 95% confidence). […] = anovan ( Y , GROUP , "display", dispopt ) dispopt can be either "on" (default) or "off" and controls the display of the model formula, table of model parameters, the ANOVA table and the diagnostic plots. The F-statistic and p-values are formatted in APA-style. To avoid p-hacking, the table of model parameters is only displayed if we set planned contrasts (see below). […] = anovan ( Y , GROUP , "contrasts", contrasts ) contrasts can be specified as one of the following: A string corresponding to one of the built-in contrasts listed below: "simple" or "anova" (default): Simple (ANOVA) contrast coding. (The first level appearing in the GROUP column is the reference level) "poly": Polynomial contrast coding for trend analysis. "helmert": Helmert contrast coding: the difference between each level with the mean of the subsequent levels. "effect": Deviation effect coding. (The first level appearing in the GROUP column is omitted). "sdif" or "sdiff": Successive differences contrast coding: the difference between each level with the previous level. "treatment": Treatment contrast (or dummy) coding. (The first level appearing in the GROUP column is the reference level). These contrasts are not compatible with sstype = 3. A matrix containing a custom contrast coding scheme (i.e. the generalized inverse of contrast weights). Rows in the contrast matrices correspond to factor levels in the order that they first appear in the GROUP column. The matrix must contain the same number of columns as there are the number of factor levels minus one. If the anovan model contains more than one factor and a built-in contrast coding scheme was specified, then those contrasts are applied to all factors. To specify different contrasts for different factors in the model, contrasts should be a cell array with the same number of cells as there are columns in GROUP . Each cell should define contrasts for the respective column in GROUP by one of the methods described above. If cells are left empty, then the default contrasts are applied. Contrasts for cells corresponding to continuous factors are ignored. […] = anovan ( Y , GROUP , "weights", weights ) weights is an optional vector of weights to be used when fitting the linear model. Weighted least squares (WLS) is used with weights (that is, minimizing sum ( weights * residuals .^ 2)) ; otherwise ordinary least squares (OLS) is used (default is empty for OLS). anovan can return up to four output arguments: p = anovan (…) returns a vector of p-values, one for each term. [ p , atab ] = anovan (…) returns a cell array containing the ANOVA table. Its first row holds the column names, and the columns are, in order, the term name, its sum-of-squares, its degrees of freedom, a singularity flag, its mean square, the F statistic, the p-value, and then two effect sizes: eta squared and partial eta squared. The first seven follow MATLAB’s layout, so a caller reading them by position gets the same quantity in either language; the two effect sizes are an Octave extension and are appended after them. A model naming any factor as random reports eight further columns after the p-value, as MATLAB does, with the two effect sizes still last: the term’s type, its expected mean square, the mean square and degrees of freedom of the denominator its F ratio was taken against, the definition of that denominator, and the variance component of a random term with its confidence bounds. A denominator that no single mean square provides is synthesised from several and carries Satterthwaite degrees of freedom, which are generally not whole numbers. The singularity flag is 1 when a term is aliased with the rest of the model, which happens when the design is not of full rank, most often because a combination of factor levels holds no observations. The degrees of freedom reported for such a term are the ones that can be estimated, which may be fewer than the term’s design block has columns and may be zero. Whether a term is aliased depends on the model it is adjusted for, and therefore on "sstype" : a term that is estimable in a sequential fit may not be estimable in a marginal one. A flagged term’s sum-of-squares is not uniquely attributable to it, so the corresponding F and p-value should not be read as a test of that term. [ p , atab , stats ] = anovan (…) returns a structure containing additional statistics, including degrees of freedom and effect sizes for each term in the linear model, the design matrix, the variance-covariance matrix, (weighted) model residuals, and the mean squared error. The columns of stats .coeffs (from left-to-right) report the model coefficients, standard errors, lower and upper 100×(1-alpha)% confidence interval bounds, t-statistics, and p-values relating to the contrasts. The number appended to each term name in stats .coeffnames corresponds to the column number in the relevant contrast matrix for that factor. The stats structure can be used as input for multcompare . [ p , atab , stats , terms ] = anovan (…) returns the model term definitions. See also: anova1, anova2, multcompare, fitlm # name: # type: sq_string # elements: 1 # length: 147 Perform a multi (N)-way analysis of (co)variance (ANOVA or ANCOVA) to evaluate the effect of one or more categorical or continuous predictors (i.e. # name: # type: sq_string # elements: 1 # length: 13 ansaribradley # name: # type: sq_string # elements: 1 # length: 2416 statistics: h = ansaribradley ( x , y ) statistics: h = ansaribradley ( x , y , name , value ) statistics: [ h , p ] = ansaribradley (…) statistics: [ h , p , stats ] = ansaribradley (…) Ansari-Bradley two-sample test for equal dispersions. h = ansaribradley ( x , y ) performs an Ansari-Bradley test of the hypothesis that the two independent samples in the vectors x and y come from distributions with the same dispersion parameter, against the alternative that they come from distributions with different dispersions. The result is h = 0 if the null hypothesis of equal dispersions cannot be rejected at the 5% significance level, or h = 1 if it can. The Ansari-Bradley test is a nonparametric alternative to the two-sample F test ( vartest2 ) that does not assume normality. It assumes that the two samples are independent and that they come from distributions with the same median and shape, differing (under the alternative) only in dispersion. If the medians differ, the data should be recentred (e.g. by subtracting the sample medians) before applying the test. ansaribradley treats NaNs in x or y as missing values and ignores them. [ h , p ] = ansaribradley (…) returns the p-value of the test, that is the probability, under the null hypothesis, of observing a value of the test statistic as or more extreme than the one observed. [ h , p , stats ] = ansaribradley (…) returns a structure with the following fields: W the value of the Ansari-Bradley test statistic, the sum of the Ansari-Bradley scores of the sample x Wstar the value of the approximate normal (z) statistic […] = ansaribradley (…, name , value ) specifies one or more of the following name/value pairs: Name Value 'alpha' the significance level. Default is 0.05. 'tail' a string specifying the alternative hypothesis 'method' a string selecting the p-value computation, either 'exact' to use the exact permutation distribution of the statistic, or 'approximate' to use the normal approximation. The default is 'exact' when the total sample size is 25 or less, and 'approximate' otherwise. The 'tail' option can take one of the following values: 'both' dispersions are not equal (two-tailed, default) 'right' dispersion of x is greater than dispersion of y (right-tailed) 'left' dispersion of x is less than dispersion of y (left-tailed) See also: vartest2, vartestn, kstest2, ranksum # name: # type: sq_string # elements: 1 # length: 53 Ansari-Bradley two-sample test for equal dispersions. # name: # type: sq_string # elements: 1 # length: 13 bartlett_test # name: # type: sq_string # elements: 1 # length: 1823 statistics: h = bartlett_test ( x ) statistics: h = bartlett_test ( x , group ) statistics: h = bartlett_test ( x , alpha ) statistics: h = bartlett_test ( x , group , alpha ) statistics: [ h , pval ] = bartlett_test (…) statistics: [ h , pval , chisq ] = bartlett_test (…) statistics: [ h , pval , chisq , df ] = bartlett_test (…) Perform a Bartlett test for the homogeneity of variances. Under the null hypothesis of equal variances, the test statistic chisq approximately follows a chi-square distribution with df degrees of freedom. The p-value (1 minus the CDF of this distribution at chisq ) is returned in pval . h = 1 if the null hypothesis is rejected at the significance level of alpha . Otherwise h = 0. Input Arguments: x contains the data and it can either be a vector or matrix. If x is a matrix, then each column is treated as a separate group. If x is a vector, then the group argument is mandatory. NaN values are omitted. group contains the names for each group. If x is a vector, then group must be a vector of the same length, or a string array or cell array of strings with one row for each element of x . x values corresponding to the same value of group are placed in the same group. If x is a matrix, then group can either be a cell array of strings of a character array, with one row per column of x in the same way it is used in anova1 function. If x is a matrix, then group can be omitted either by entering an empty array ([]) or by parsing only alpha as a second argument (if required to change its default value). alpha is the statistical significance value at which the null hypothesis is rejected. Its default value is 0.05 and it can be parsed either as a second argument (when group is omitted) or as a third argument. See also: levene_test, vartest2, vartestn # name: # type: sq_string # elements: 1 # length: 57 Perform a Bartlett test for the homogeneity of variances. # name: # type: sq_string # elements: 1 # length: 8 barttest # name: # type: sq_string # elements: 1 # length: 1030 statistics: ndim = barttest ( x ) statistics: ndim = barttest ( x , alpha ) statistics: [ ndim , pval ] = barttest ( x , alpha ) statistics: [ ndim , pval , chisq ] = barttest ( x , alpha ) Bartlett’s test of sphericity for correlation. It compares an observed correlation matrix to the identity matrix in order to check if there is a certain redundancy between the variables that we can summarize with a few number of factors. A statistically significant test shows that the variables (columns) in x are correlated, thus it makes sense to perform some dimensionality reduction of the data in x . ndim = barttest ( x , alpha ) returns the number of dimensions necessary to explain the nonrandom variation in the data matrix x at the alpha significance level. alpha is an optional input argument and, when not provided, it is 0.05 by default. [ ndim , pval , chisq ] = barttest (…) also returns the significance values pval for the hypothesis test for each dimension as well as the associated chi^2 values in chisq # name: # type: sq_string # elements: 1 # length: 46 Bartlett's test of sphericity for correlation. # name: # type: sq_string # elements: 1 # length: 8 binotest # name: # type: sq_string # elements: 1 # length: 1092 statistics: [ h , pval , ci ] = binotest ( pos , N , p0 ) statistics: [ h , pval , ci ] = binotest ( pos , N , p0 , Name , Value ) Test for probability p of a binomial sample Perform a test of the null hypothesis p == p0 for a sample of size N with pos positive results. Name-Value pair arguments can be used to set various options. 'alpha' can be used to specify the significance level of the test (the default value is 0.05). The option 'tail' , can be used to select the desired alternative hypotheses. If the value is 'both' (default) the null is tested against the two-sided alternative p != p0 . The value of pval is determined by adding the probabilities of all event less or equally likely than the observed number pos of positive events. If the value of 'tail' is 'right' the one-sided alternative p > p0 is considered. Similarly for 'left' , the one-sided alternative p < p0 is considered. If h is 0 the null hypothesis is accepted, if it is 1 the null hypothesis is rejected. The p-value of the test is returned in pval . A 100(1-alpha)% confidence interval is returned in ci . # name: # type: sq_string # elements: 1 # length: 43 Test for probability p of a binomial sample # name: # type: sq_string # elements: 1 # length: 7 chi2gof # name: # type: sq_string # elements: 1 # length: 3703 statistics: h = chi2gof ( x ) statistics: [ h , p ] = chi2gof ( x ) statistics: [ p , h , stats ] = chi2gof ( x ) statistics: […] = chi2gof ( x , Name , Value , …) Chi-square goodness-of-fit test. chi2gof performs a chi-square goodness-of-fit test for discrete or continuous distributions. The test is performed by grouping the data into bins, calculating the observed and expected counts for those bins, and computing the chi-square test statistic $$ \chi ^ 2 = \sum_{i=1}^N \left (O_i - E_i \right) ^ 2 / E_i $$ where O is the observed counts and E is the expected counts. This test statistic has an approximate chi-square distribution when the counts are sufficiently large. Bins in either tail with an expected count less than 5 are pooled with neighboring bins until the count in each extreme bin is at least 5. If bins remain in the interior with counts less than 5, chi2gof displays a warning. In that case, you should use fewer bins, or provide bin centers or binedges, to increase the expected counts in all bins. h = chi2gof ( x ) performs a chi-square goodness-of-fit test that the data in the vector X are a random sample from a normal distribution with mean and variance estimated from x . The result is h = 0 if the null hypothesis (that x is a random sample from a normal distribution) cannot be rejected at the 5% significance level, or h = 1 if the null hypothesis can be rejected at the 5% level. chi2gof uses by default 10 bins ( 'nbins' ), and compares the test statistic to a chi-square distribution with nbins - 3 degrees of freedom, to take into account that two parameters were estimated. [ h , p ] = chi2gof ( x ) also returns the p-value p , which is the probability of observing the given result, or one more extreme, by chance if the null hypothesis is true. If there are not enough degrees of freedom to carry out the test, p is NaN. [ h , p , stats ] = chi2gof ( x ) also returns a stats structure with the following fields: "chi2stat" Chi-square statistic "df" Degrees of freedom "binedges" Vector of bin binedges after pooling "O" Observed count in each bin "E" Expected count in each bin […] = chi2gof ( x , Name , Value , …) specifies optional Name/Value pair arguments chosen from the following list. Name Value 'nbins' The number of bins to use. Default is 10. 'binctrs' A vector of bin centers. 'binedges' A vector of bin binedges. 'cdf' A fully specified cumulative distribution function or a function handle provided in a cell array whose first element is a function handle, and all later elements are its parameter values. The function must take x values as its first argument, and other parameters as later arguments. 'expected' A vector with one element per bin specifying the expected counts for each bin. 'nparams' The number of estimated parameters; used to adjust the degrees of freedom to be nbins - 1 - nparams , where nbins is the number of bins. 'emin' The minimum allowed expected value for a bin; any bin in either tail having an expected value less than this amount is pooled with a neighboring bin. Use the value 0 to prevent pooling. Default is 5. 'frequency' A vector of the same length as x containing the frequency of the corresponding x values. 'alpha' An alpha value such that the hypothesis is rejected if p < alpha . Default is alpha = 0.05 . You should specify either 'cdf' or 'expected' parameters, but not both. If your 'cdf' input contains extra parameters, these are accounted for automatically and there is no need to specify 'nparams' . If your 'expected' input depends on estimated parameters, you should use the 'nparams' parameter to ensure that the degrees of freedom for the test is correct. # name: # type: sq_string # elements: 1 # length: 32 Chi-square goodness-of-fit test. # name: # type: sq_string # elements: 1 # length: 8 chi2test # name: # type: sq_string # elements: 1 # length: 3610 statistics: pval = chi2test ( x ) statistics: [ pval , chisq ] = chi2test ( x ) statistics: [ pval , chisq , dF ] = chi2test ( x ) statistics: [ pval , chisq , dF , E ] = chi2test ( x ) statistics: […] = chi2test ( x , name , value ) Perform a chi-squared test (for independence or homogeneity). For 2-way contingency tables, chi2test performs and a chi-squared test for independence or homogeneity, according to the sampling scheme and related question. Independence means that the two variables forming the 2-way table are not associated, hence you cannot predict from one another. Homogeneity refers to the concept of similarity, hence they all come from the same distribution. Both tests are computationally identical and will produce the same result. Nevertheless, they answer to different questions. Consider two variables, one for gender and another for smoking. To test independence (whether gender and smoking is associated), we would randomly sample from the general population and break them down into categories in the table. To test homogeneity (whether men and women share the same smoking habits), we would sample individuals from within each gender, and then measure their smoking habits (e.g. smokers vs non-smokers). When chi2test is called without any output arguments, it will print the result in the terminal including p-value, chi^2 statistic, and degrees of freedom. Otherwise it can return the following output arguments: pval the p-value of the relevant test. chisq the chi^2 statistic of the relevant test. dF the degrees of freedom of the relevant test. E the EXPECTED values of the original contingency table. Unlike MATLAB, in GNU Octave chi2test also supports 3-way tables, which involve three categorical variables (each in a different dimension of x . In its simplest form, […] = chi2test ( x ) will will test for mutual independence among the three variables. Alternatively, when called in the form […] = chi2test ( x , name , value ) , it can perform the following tests: name value Description "mutual" [] Mutual independence. All variables are independent from each other, (A, B, C). Value must be an empty matrix. "joint" scalar Joint independence. Two variables are jointly independent of the third, (AB, C). The scalar value corresponds to the dimension of the independent variable (i.e. 3 for C). "marginal" scalar Marginal independence. Two variables are independent if you ignore the third, (A, C). The scalar value corresponds to the dimension of the variable to be ignored (i.e. 2 for B). "conditional" scalar Conditional independence. Two variables are independent given the third, (AC, BC). The scalar value corresponds to the dimension of the variable that forms the conditional dependence (i.e. 3 for C). "homogeneous" [] Homogeneous associations. Conditional (partial) odds-ratios are not related on the value of the third, (AB, AC, BC). Value must be an empty matrix. When testing for homogeneous associations in 3-way tables, the iterative proportional fitting procedure is used. For small samples it is better to use the Cochran-Mantel-Haenszel Test. K-way tables for k > 3 are supported only for testing mutual independence. Similar to 2-way tables, no optional parameters are required for k > 3 multi-way tables. chi2test produces a warning if any cell of a 2x2 table has an expected frequency less than 5 or if more than 20% of the cells in larger 2-way tables have expected frequencies less than 5 or any cell with expected frequency less than 1. In such cases, use fishertest . See also: crosstab, fishertest, mcnemar_test # name: # type: sq_string # elements: 1 # length: 61 Perform a chi-squared test (for independence or homogeneity). # name: # type: sq_string # elements: 1 # length: 16 correlation_test # name: # type: sq_string # elements: 1 # length: 1715 statistics: h = correlation_test ( x , y ) statistics: [ h , pval ] = correlation_test ( y , x ) statistics: [ h , pval , stats ] = correlation_test ( y , x ) statistics: […] = correlation_test ( y , x , Name , Value ) Perform a correlation coefficient test to determine whether two samples x and y come from uncorrelated populations. h = correlation_test ( y , x ) tests the null hypothesis that the two samples x and y come from uncorrelated populations. The result is h = 0 if the null hypothesis cannot be rejected at the 5% significance level, or h = 1 if the null hypothesis can be rejected at the 5% level. y and x must be vectors of equal length with finite real numbers. The p-value of the test is returned in pval . stats is a structure with the following fields: Field Value method the type of correlation coefficient used for the test df the degrees of freedom (where applicable) corrcoef the correlation coefficient stat the test’s statistic dist the respective distribution for the test alt the alternative hypothesis for the test […] = correlation_test (…, name , value ) specifies one or more of the following name/value pairs: Name Value 'alpha' the significance level. Default is 0.05. 'tail' a string specifying the alternative hypothesis 'both' corrcoef is not 0 (two-tailed, default) 'left' corrcoef is less than 0 (left-tailed) 'right' corrcoef is greater than 0 (right-tailed) 'method' a string specifying the correlation coefficient used for the test 'pearson' Pearson’s product moment correlation (Default) 'kendall' Kendall’s rank correlation tau 'spearman' Spearman’s rank correlation rho See also: regression_ftest, regression_ttest # name: # type: sq_string # elements: 1 # length: 115 Perform a correlation coefficient test to determine whether two samples x and y come from uncorrelated populations. # name: # type: sq_string # elements: 1 # length: 6 dwtest # name: # type: sq_string # elements: 1 # length: 1523 statistics: p = dwtest ( r , x ) statistics: p = dwtest ( r , x , name , value ) statistics: [ p , d ] = dwtest (…) Durbin-Watson test for autocorrelation in linear regression residuals. p = dwtest ( r , x ) performs the Durbin-Watson test on the residuals r of a linear regression with design matrix x (which should include a column of ones if the model has a constant term). The null hypothesis is that the residuals are uncorrelated, against the alternative that they are autocorrelated. r is an N×1 vector and x is an N×P matrix. p is the p-value of the test. The Durbin-Watson statistic is $$ d = \sum_{i=1}^{n-1} (r_{i+1} - r_i)^2 / \sum_{i=1}^{n} r_i^2. $$ Values near 2 indicate no autocorrelation, values towards 0 positive autocorrelation, and values towards 4 negative autocorrelation. p = dwtest ( r , x , name , value ) specifies additional options using Name-Value pair arguments: Name Value 'Method' 'exact' to compute the exact p-value from the null distribution of the statistic (a ratio of quadratic forms, evaluated with Imhof’s method), or 'approximate' to use a normal approximation based on the mean and variance of the statistic. The default is 'exact' for n < 400 and 'approximate' otherwise. 'Tail' The alternative hypothesis: 'both' (default) for a nonzero autocorrelation, 'right' for a positive autocorrelation, or 'left' for a negative autocorrelation. [ p , d ] = dwtest (…) also returns the Durbin-Watson statistic d . See also: regress, fitlm, runstest # name: # type: sq_string # elements: 1 # length: 70 Durbin-Watson test for autocorrelation in linear regression residuals. # name: # type: sq_string # elements: 1 # length: 10 fishertest # name: # type: sq_string # elements: 1 # length: 2096 statistics: h = fishertest ( x ) statistics: h = fishertest ( x , param1 , value1 , …) statistics: [ h , pval ] = fishertest (…) statistics: [ h , pval , stats ] = fishertest (…) Fisher’s exact test. h = fishertest ( x ) performs Fisher’s exact test on a 2×2 contingency table given in matrix x . This is a test of the hypothesis that there are no non-random associations between the two 2-level categorical variables in x . fishertest returns the result of the tested hypothesis in h . h = 0 indicates that the null hypothesis (of no association) cannot be rejected at the 5% significance level. h = 1 indicates that the null hypothesis can be rejected at the 5% level. x must contain only non-negative integers. Use the crosstab function to generate the contingency table from samples of two categorical variables. Fisher’s exact test is not suitable when all integers in x are very large. User can use the Chi-square test in this case. [ h , pval ] = fishertest ( x ) returns the p-value in pval . That is the probability of observing the given result, or one more extreme, by chance if the null hypothesis is true. Small values of pval cast doubt on the validity of the null hypothesis. [ p , pval , stats ] = fishertest (…) returns the structure stats with the following fields: OddsRatio – the odds ratio ConfidenceInterval – the asymptotic confidence interval for the odds ratio. If any of the four entries in the contingency table x is zero, the confidence interval will not be computed, and [-Inf Inf] will be displayed. […] = fishertest (…, name , value , …) specifies one or more of the following name/value pairs: Name Value 'alpha' the significance level. Default is 0.05. 'tail' a string specifying the alternative hypothesis 'both' odds ratio not equal to 1, indicating association between two variables (two-tailed test, default) 'left' odds ratio greater than 1 (right-tailed test) 'right' odds ratio is less than 1 (left-tailed test) See also: crosstab, chi2test, mcnemar_test, ztest2 # name: # type: sq_string # elements: 1 # length: 20 Fisher's exact test. # name: # type: sq_string # elements: 1 # length: 8 friedman # name: # type: sq_string # elements: 1 # length: 1880 statistics: p = friedman ( x ) statistics: p = friedman ( x , reps ) statistics: p = friedman ( x , reps , displayopt ) statistics: [ p , tbl ] = friedman (…) statistics: [ p , tbl , stats ] = friedman (…) Performs the nonparametric Friedman’s test to compare column effects in a two-way layout. friedman tests the null hypothesis that the column effects are all the same against the alternative that they are not all the same. friedman requires one up to three input arguments: x contains the data and it must be a matrix of at least two columns and two rows. reps is the number of replicates for each combination of factor groups. If not provided, no replicates are assumed. displayopt is an optional parameter for displaying the Friedman’s ANOVA table, when it is ’on’ (default) and suppressing the display when it is ’off’. MATLAB renders the table in a figure window; this package prints it to the standard output, as anova2 does. friedman returns up to three output arguments: p is the p-value of the null hypothesis that all group means are equal. tbl is a cell array containing the results of the Friedman’s test in ANOVA table format. Its first row holds the column labels Source, SS, df, MS, Chi-sq and Prob>Chi-sq, followed by a row per source: Columns, [Interaction], Error and Total. An entry that does not apply to a row, such as the chi-square statistic of the Error row, is empty. stats is a structure containing statistics useful for performing a multiple comparison of medians with the MULTCOMPARE function. If friedman is called without any output arguments, then it prints the results in a Friedman’s ANOVA table to the standard output. Examples: load popcorn; friedman (popcorn, 3); [p, anovatab, stats] = friedman (popcorn, 3); disp (p); See also: anova2, kruskalwallis, multcompare # name: # type: sq_string # elements: 1 # length: 89 Performs the nonparametric Friedman's test to compare column effects in a two-way layout. # name: # type: sq_string # elements: 1 # length: 16 hotelling_t2test # name: # type: sq_string # elements: 1 # length: 1747 statistics: [ h , pval , stats ] = hotelling_t2test ( x ) statistics: […] = hotelling_t2test ( x , m ) statistics: […] = hotelling_t2test ( x , y ) statistics: […] = hotelling_t2test ( x , m , Name , Value ) statistics: […] = hotelling_t2test ( x , y , Name , Value ) Compute Hotelling’s T^2 ("T-squared") test for a single sample or two dependent samples (paired-samples). For a sample x from a multivariate normal distribution with unknown mean and covariance matrix, test the null hypothesis that mean ( x ) == m . For two dependent samples x and y from a multivariate normal distributions with unknown means and covariance matrices, test the null hypothesis that mean ( x - y ) == 0 . hotelling_t2test treats NaNs as missing values, and ignores the corresponding rows. Name-Value pair arguments can be used to set statistical significance. 'alpha' can be used to specify the significance level of the test (the default value is 0.05). If h is 1 the null hypothesis is rejected, meaning that the tested sample does not come from a multivariate distribution with mean m , or in case of two dependent samples that they do not come from the same multivariate distribution. If h is 0, then the null hypothesis cannot be rejected and it can be assumed that it holds true. The p-value of the test is returned in pval . stats is a structure containing the value of the Hotelling’s T^2 test statistic in the field "Tsq", and the degrees of freedom of the F distribution in the fields "df1" and "df2". Under the null hypothesis, (n-p) T^2 / (p(n-1)) has an F distribution with p and n-p degrees of freedom, where n and p are the numbers of samples and variables, respectively. See also: hotelling_t2test2 # name: # type: sq_string # elements: 1 # length: 105 Compute Hotelling's T^2 ("T-squared") test for a single sample or two dependent samples (paired-samples). # name: # type: sq_string # elements: 1 # length: 17 hotelling_t2test2 # name: # type: sq_string # elements: 1 # length: 1442 statistics: [ h , pval , stats ] = hotelling_t2test2 ( x , y ) statistics: […] = hotelling_t2test2 ( x , y , Name , Value ) Compute Hotelling’s T^2 ("T-squared") test for two independent samples. For two samples x from multivariate normal distributions with the same number of variables (columns), unknown means and unknown equal covariance matrices, test the null hypothesis mean ( x ) == mean ( y ) . hotelling_t2test2 treats NaNs as missing values, and ignores the corresponding rows for each sample independently. Name-Value pair arguments can be used to set statistical significance. 'alpha' can be used to specify the significance level of the test (the default value is 0.05). If h is 1 the null hypothesis is rejected, meaning that the tested samples do not come from the same multivariate distribution. If h is 0, then the null hypothesis cannot be rejected and it can be assumed that both samples come from the same multivariate distribution. The p-value of the test is returned in pval . stats is a structure containing the value of the Hotelling’s T^2 test statistic in the field "Tsq", and the degrees of freedom of the F distribution in the fields "df1" and "df2". Under the null hypothesis, $$ {(n_x+n_y-p-1) T^2 \over p(n_x+n_y-2)} $$ has an F distribution with p and n_x+n_y-p-1 degrees of freedom, where n_x and n_y are the sample sizes and p is the number of variables. See also: hotelling_t2test # name: # type: sq_string # elements: 1 # length: 71 Compute Hotelling's T^2 ("T-squared") test for two independent samples. # name: # type: sq_string # elements: 1 # length: 6 jbtest # name: # type: sq_string # elements: 1 # length: 2989 statistics: h = jbtest ( x ) statistics: h = jbtest ( x , alpha ) statistics: h = jbtest ( x , alpha , mctol ) statistics: [ h , p ] = jbtest (…) statistics: [ h , p , jbstat , critval ] = jbtest (…) Jarque-Bera hypothesis test of composite normality. h = jbtest ( x ) performs the Jarque-Bera test of the null hypothesis that the sample in the vector x comes from a normal distribution with unknown mean and variance, against the alternative that it does not come from a normal distribution. The result h is 1 if the test rejects the null hypothesis at the 5% significance level, and 0 otherwise. x must be a vector of real values; NaN values are treated as missing and removed. The Jarque-Bera test statistic is $$ JB = \frac{n}{6} \left( s^2 + \frac{(k-3)^2}{4} \right), $$ where n is the sample size, s is the sample skewness, and k is the sample kurtosis. Under the null hypothesis it is asymptotically chi-square distributed with two degrees of freedom. h = jbtest ( x , alpha ) performs the test at the significance level alpha , a scalar in the range (0,1) . The default is 0.05 . h = jbtest ( x , alpha , mctol ) computes a Monte-Carlo approximation of the p-value instead of interpolating the embedded table. mctol is the maximum Monte-Carlo standard error allowed for the p-value; the number of simulated samples is chosen accordingly. Use this for small samples, where the chi-square approximation is inaccurate, or for significance levels outside [0.001, 0.5] . [ h , p ] = jbtest (…) also returns the p-value p of the test. p is clamped to the tabulated range [0.001, 0.5] , as MATLAB clamps it, and a warning is issued when the value lies outside that range. The warning is an addition here: MATLAB clamps silently, so a p-value reported as 0.001 or 0.5 there may be a bound rather than an estimate, with nothing to say so. [ h , p , jbstat , critval ] = jbtest (…) also returns the test statistic jbstat and the critical value critval at significance level alpha . The null hypothesis is rejected when jbstat > critval . Note: for n \le 2000 the p-value and critical value are obtained by interpolating an embedded critical-value table (the same approach MATLAB uses); for larger samples the large-sample chi-square approximation with two degrees of freedom is used instead. The embedded table was generated here by Monte-Carlo simulation, so it is itself an estimate of the true null quantiles. MATLAB’s table is likewise a Monte-Carlo estimate but from a different simulation, so the two tables agree only to about two decimal places. As a result the reported p-value and critical value, and (in a narrow band of statistic values around the critical value) the test decision h , can differ slightly from MATLAB in edge cases. These differences are an unavoidable consequence of the Monte-Carlo origin of both tables, not a difference in method. Supply mctol for a direct Monte-Carlo p-value. See also: kstest, adtest, lillietest # name: # type: sq_string # elements: 1 # length: 51 Jarque-Bera hypothesis test of composite normality. # name: # type: sq_string # elements: 1 # length: 13 kruskalwallis # name: # type: sq_string # elements: 1 # length: 2239 statistics: p = kruskalwallis ( x ) statistics: p = kruskalwallis ( x , group ) statistics: p = kruskalwallis ( x , group , displayopt ) statistics: [ p , tbl ] = kruskalwallis ( x , …) statistics: [ p , tbl , stats ] = kruskalwallis ( x , …) Perform a Kruskal-Wallis test, the non-parametric alternative of a one-way analysis of variance (ANOVA), for comparing the means of two or more groups of data under the null hypothesis that the groups are drawn from the same population, i.e. the group means are equal. kruskalwallis can take up to three input arguments: x contains the data and it can either be a vector or matrix. If x is a matrix, then each column is treated as a separate group. If x is a vector, then the group argument is mandatory. group contains the names for each group. If x is a matrix, then group can either be a cell array of strings of a character array, with one row per column of x . If you want to omit this argument, enter an empty array ([]). If x is a vector, then group must be a vector of the same length, or a string array or cell array of strings with one row for each element of x . x values corresponding to the same value of group are placed in the same group. displayopt is an optional parameter for displaying the groups contained in the data in a boxplot. If omitted, it is ’on’ by default. If group names are defined in group , these are used to identify the groups in the boxplot. Use ’off’ to omit displaying this figure. kruskalwallis can return up to three output arguments: p is the p-value of the null hypothesis that all group means are equal. tbl is a cell array containing the results in a standard ANOVA table. stats is a structure containing statistics useful for performing a multiple comparison of means with the MULTCOMPARE function. If kruskalwallis is called without any output arguments, then it prints the results in a one-way ANOVA table to the standard output. It is also printed when displayopt is ’on’. Examples: x = meshgrid (1:6); x = x + normrnd (0, 1, 6, 6); [p, atab] = kruskalwallis(x); x = ones (50, 4) .* [-2, 0, 1, 5]; x = x + normrnd (0, 2, 50, 4); group = {"A", "B", "C", "D"}; kruskalwallis (x, group); # name: # type: sq_string # elements: 1 # length: 241 Perform a Kruskal-Wallis test, the non-parametric alternative of a one-way analysis of variance (ANOVA), for comparing the means of two or more groups of data under the null hypothesis that the groups are drawn from the same population, i.e. # name: # type: sq_string # elements: 1 # length: 6 kstest # name: # type: sq_string # elements: 1 # length: 3518 statistics: h = kstest ( x ) statistics: h = kstest ( x , name , value ) statistics: [ h , p ] = kstest (…) statistics: [ h , p , ksstat , cv ] = kstest (…) Single sample Kolmogorov-Smirnov (K-S) goodness-of-fit hypothesis test. h = kstest ( x ) performs a Kolmogorov-Smirnov (K-S) test to determine if a random sample x could have come from a standard normal distribution. h indicates the results of the null hypothesis test. h = 0 => Do not reject the null hypothesis at the 5% significance h = 1 => Reject the null hypothesis at the 5% significance x is a vector representing a random sample from some unknown distribution with a cumulative distribution function F(X). Missing values declared as NaNs in x are ignored. h = kstest ( x , name , value ) returns a test decision for a single-sample K-S test with additional options specified by one or more Name - Value pair arguments as shown below. Name Value 'alpha' A numeric scalar between 0 and 1 specifying th the significance level. Default is 0.05 for 5% significance. 'CDF' The hypothesized CDF under the null hypothesis. It can be specified as a function handle of an existing cdf function, a character vector defining a probability distribution with default parameters, a probability distribution object, or a two-column matrix. If not provided, the default is the standard normal, N(0,1) . The one-sample Kolmogorov-Smirnov test is only valid for continuous cumulative distribution functions, and requires the CDF to be predetermined. The result is not accurate if CDF is estimated from the data. 'tail' A string indicating the type of test: 'unequal' "F(X) not equal to CDF(X)" (two-sided) (Default) 'larger' "F(X) > CDF(X)" (one-sided) 'smaller' "F(X) < CDF(X)" (one-sided) Let S(X) be the empirical c.d.f. estimated from the sample vector x , F(X) be the corresponding true (but unknown) population c.d.f., and CDF be the known input c.d.f. specified under the null hypothesis. For tail = "unequal", "larger", and "smaller", the test statistics are max|S(X) - CDF(X)|, max[S(X) - CDF(X)], and max[CDF(X) - S(X)], respectively. [ h , p ] = kstest (…) also returns the asymptotic p-value p . [ h , p , ksstat ] = kstest (…) returns the K-S test statistic ksstat defined above for the test type indicated by the "tail" option In the matrix version of CDF, column 1 contains the x-axis data and column 2 the corresponding y-axis c.d.f data. Since the K-S test statistic will occur at one of the observations in x , the calculation is most efficient when CDF is only specified at the observations in x . When column 1 of CDF represents x-axis points independent of x , CDF is linearly interpolated at the observations found in the vector x . In this case, the interval along the x-axis (the column 1 spread of CDF) must span the observations in x for successful interpolation. The decision to reject the null hypothesis is based on comparing the p-value p with the "alpha" value, not by comparing the statistic ksstat with the critical value cv . cv is computed separately using an approximate formula or by interpolation using Miller’s approximation table. The formula and table cover the range 0.01 <= "alpha" <= 0.2 for two-sided tests and 0.005 <= "alpha" <= 0.1 for one-sided tests. CV is returned as NaN if "alpha" is outside this range. Since CV is approximate, a comparison of ksstat with cv may occasionally lead to a different conclusion than a comparison of p with "alpha". See also: kstest2, cdfplot # name: # type: sq_string # elements: 1 # length: 71 Single sample Kolmogorov-Smirnov (K-S) goodness-of-fit hypothesis test. # name: # type: sq_string # elements: 1 # length: 7 kstest2 # name: # type: sq_string # elements: 1 # length: 1960 statistics: h = kstest2 ( x1 , x2 ) statistics: h = kstest2 ( x1 , x2 , name , value ) statistics: [ h , p ] = kstest2 (…) statistics: [ h , p , ks2stat ] = kstest2 (…) Two-sample Kolmogorov-Smirnov goodness-of-fit hypothesis test. h = kstest2 ( x1 , x2 ) returns a test decision for the null hypothesis that the data in vectors x1 and x2 are from the same continuous distribution, using the two-sample Kolmogorov-Smirnov test. The alternative hypothesis is that x1 and x2 are from different continuous distributions. The result h is 1 if the test rejects the null hypothesis at the 5% significance level, and 0 otherwise. h = kstest2 ( x1 , x2 , name , value ) returns a test decision for a two-sample Kolmogorov-Smirnov test with additional options specified by one or more name-value pair arguments as shown below. Name Value "alpha" A value alpha between 0 and 1 specifying the significance level. Default is 0.05 for 5% significance. "tail" A string indicating the type of test: "unequal" "F(X1) not equal to F(X2)" (two-sided) [Default] "larger" "F(X1) > F(X2)" (one-sided) "smaller" "F(X1) < F(X2)" (one-sided) The two-sided test uses the maximum absolute difference between the cdfs of the distributions of the two data vectors. The test statistic is D* = max(|F1(x) - F2(x)|) , where F1(x) is the proportion of x1 values less or equal to x and F2(x) is the proportion of x2 values less than or equal to x. The one-sided test uses the actual value of the difference between the cdfs of the distributions of the two data vectors rather than the absolute value. The test statistic is D* = max(F1(x) - F2(x)) or D* = max(F2(x) - F1(x)) for tail = "larger" or "smaller", respectively. [ h , p ] = kstest2 (…) also returns the asymptotic p-value p . [ h , p , ks2stat ] = kstest2 (…) also returns the Kolmogorov-Smirnov test statistic ks2stat defined above for the test type indicated by tail . See also: kstest, cdfplot # name: # type: sq_string # elements: 1 # length: 62 Two-sample Kolmogorov-Smirnov goodness-of-fit hypothesis test. # name: # type: sq_string # elements: 1 # length: 11 levene_test # name: # type: sq_string # elements: 1 # length: 2415 statistics: h = levene_test ( x ) statistics: h = levene_test ( x , group ) statistics: h = levene_test ( x , alpha ) statistics: h = levene_test ( x , testtype ) statistics: h = levene_test ( x , group , alpha ) statistics: h = levene_test ( x , group , testtype ) statistics: h = levene_test ( x , group , alpha , testtype ) statistics: [ h , pval ] = levene_test (…) statistics: [ h , pval , W ] = levene_test (…) statistics: [ h , pval , W , df ] = levene_test (…) Perform a Levene’s test for the homogeneity of variances. Under the null hypothesis of equal variances, the test statistic W approximately follows an F distribution with df degrees of freedom being a vector ([k-1, N-k]). The p-value (1 minus the CDF of this distribution at W ) is returned in pval . h = 1 if the null hypothesis is rejected at the significance level of alpha . Otherwise h = 0. Input Arguments: x contains the data and it can either be a vector or matrix. If x is a matrix, then each column is treated as a separate group. If x is a vector, then the group argument is mandatory. NaN values are omitted. group contains the names for each group. If x is a vector, then group must be a vector of the same length, or a string array or cell array of strings with one row for each element of x . x values corresponding to the same value of group are placed in the same group. If x is a matrix, then group can either be a cell array of strings of a character array, with one row per column of x in the same way it is used in anova1 function. If x is a matrix, then group can be omitted either by entering an empty array ([]) or by parsing only alpha as a second argument (if required to change its default value). alpha is the statistical significance value at which the null hypothesis is rejected. Its default value is 0.05 and it can be parsed either as a second argument (when group is omitted) or as a third argument. testtype is a string determining the type of Levene’s test. By default it is set to "absolute", but the user can also parse "quadratic" in order to perform Levene’s Quadratic test for equal variances or "median" in order to to perform the Brown-Forsythe’s test. These options determine how the Z_ij values are computed. If an invalid name is parsed for testtype , then the Levene’s Absolute test is performed. See also: bartlett_test, vartest2, vartestn # name: # type: sq_string # elements: 1 # length: 57 Perform a Levene's test for the homogeneity of variances. # name: # type: sq_string # elements: 1 # length: 10 lillietest # name: # type: sq_string # elements: 1 # length: 2086 statistics: h = lillietest ( x ) statistics: h = lillietest ( x , name , value ) statistics: [ h , p ] = lillietest (…) statistics: [ h , p , kstat , critval ] = lillietest (…) Lilliefors goodness-of-fit hypothesis test. h = lillietest ( x ) tests the null hypothesis that the sample in the vector x comes from a normal distribution with unknown mean and variance, against the alternative that it does not, using the Lilliefors test. h is 1 if the test rejects the null at the 5% significance level and 0 otherwise. The Lilliefors statistic is the Kolmogorov-Smirnov statistic — the maximum absolute difference between the empirical cumulative distribution function of x and the cumulative distribution function of the hypothesized family with parameters estimated from x . Because the parameters are estimated, the null distribution of the statistic differs from that of the ordinary Kolmogorov-Smirnov test. The following Name-Value pairs are supported: Name Value 'Distribution' The hypothesized family: 'normal' (default), 'exponential' , or 'extreme value' . The parameters are estimated from x : mean and standard deviation for the normal, mean for the exponential, and location and scale for the extreme value distribution. 'Alpha' The significance level, a scalar. Without 'MCTol' it must lie in [0.001, 0.5] (the tabulated range); with 'MCTol' it may be any value in (0, 1) . The default is 0.05 . 'MCTol' Maximum Monte-Carlo standard error for the p-value. When supplied, the p-value and critical value are computed by Monte-Carlo simulation instead of by interpolating the embedded table. [ h , p , kstat , critval ] = lillietest (…) also returns the p-value p , the test statistic kstat , and the critical value critval . Without 'MCTol' the p-value is clamped to the tabulated range [0.001, 0.5] and a warning is issued when it lies outside. The warning is an addition here: MATLAB clamps silently, so a p-value reported as 0.001 or 0.5 there may be a bound rather than an estimate, with nothing to say so. See also: kstest, adtest, jbtest # name: # type: sq_string # elements: 1 # length: 43 Lilliefors goodness-of-fit hypothesis test. # name: # type: sq_string # elements: 1 # length: 7 manova1 # name: # type: sq_string # elements: 1 # length: 2569 statistics: d = manova1 ( x , group ) statistics: d = manova1 ( x , group , alpha ) statistics: [ d , p ] = manova1 (…) statistics: [ d , p , stats ] = manova1 (…) One-way multivariate analysis of variance (MANOVA). d = manova1 ( x , group , alpha ) performs a one-way MANOVA for comparing the mean vectors of two or more groups of multivariate data. x is a matrix with each row representing a multivariate observation, and each column representing a variable. group is a numeric vector, string array, or cell array of strings with the same number of rows as x . x values are in the same group if they correspond to the same value of GROUP. alpha is the scalar significance level and is 0.05 by default. d is an estimate of the dimension of the group means. It is the smallest dimension such that a test of the hypothesis that the means lie on a space of that dimension is not rejected. If d = 0 for example, we cannot reject the hypothesis that the means are the same. If d = 1, we reject the hypothesis that the means are the same but we cannot reject the hypothesis that they lie on a line. [ d , p ] = manova1 (…) returns P, a vector of p-values for testing the null hypothesis that the mean vectors of the groups lie on various dimensions. P(1) is the p-value for a test of dimension 0, P(2) for dimension 1, etc. [ d , p , stats ] = manova1 (…) returns a STATS structure with the following fields: "W" within-group sum of squares and products matrix "B" between-group sum of squares and products matrix "T" total sum of squares and products matrix "dfW" degrees of freedom for WSSP matrix "dfB" degrees of freedom for BSSP matrix "dfT" degrees of freedom for TSSP matrix "lambda" value of Wilk’s lambda (the test statistic) "chisq" transformation of lambda to a chi-square distribution "chisqdf" degrees of freedom for chisq "eigenval" eigenvalues of (WSSP^-1) * BSSP "eigenvec" eigenvectors of (WSSP^-1) * BSSP; these are the coefficients for canonical variables, and they are scaled so the within-group variance of C is 1 "canon" canonical variables, equal to XC*eigenvec, where XC is X with columns centered by subtracting their means "mdist" Mahalanobis distance from each point to its group mean "gmdist" Mahalanobis distances between each pair of group means "gnames" Group names The canonical variables C have the property that C(:,1) is the linear combination of the x columns that has the maximum separation between groups, C(:,2) has the maximum separation subject to it being orthogonal to C(:,1), and so on. # name: # type: sq_string # elements: 1 # length: 51 One-way multivariate analysis of variance (MANOVA). # name: # type: sq_string # elements: 1 # length: 12 mcnemar_test # name: # type: sq_string # elements: 1 # length: 1690 statistics: [ h , pval , chisq ] = mcnemar_test ( x ) statistics: [ h , pval , chisq ] = mcnemar_test ( x , alpha ) statistics: [ h , pval , chisq ] = mcnemar_test ( x , testtype ) statistics: [ h , pval , chisq ] = mcnemar_test ( x , alpha , testtype ) Perform a McNemar’s test on paired nominal data. McNemar’s test is applied to a 2×2 contingency table x with a dichotomous trait, with matched pairs of subjects, of data cross-classified on the row and column variables to testing the null hypothesis of symmetry of the classification probabilities. More formally, the null hypothesis of marginal homogeneity states that the two marginal probabilities for each outcome are the same. Under the null, with a sufficiently large number of discordants ( x (1,2) + x (2,1) >= 25 ), the test statistic, chisq , follows a chi-squared distribution with 1 degree of freedom. When the number of discordants is less than 25, then the mid-P exact McNemar test is used. testtype will force mcnemar_test to apply a particular method for testing the null hypothesis independently of the number of discordants. Valid options for testtype : 'asymptotic' Original McNemar test statistic 'corrected' Edwards’ version with continuity correction 'exact' An exact binomial test 'mid-p' The mid-P McNemar test (mid-p binomial test) The test decision is returned in h , which is 1 when the null hypothesis is rejected ( pval < alpha ) or 0 otherwise. alpha defines the critical value of statistical significance for the test. Further information about the McNemar’s test can be found at https://en.wikipedia.org/wiki/McNemar%27s_test See also: crosstab, chi2test, fishertest # name: # type: sq_string # elements: 1 # length: 48 Perform a McNemar's test on paired nominal data. # name: # type: sq_string # elements: 1 # length: 11 multcompare # name: # type: sq_string # elements: 1 # length: 6815 statistics: C = multcompare ( STATS ) statistics: C = multcompare ( STATS , "name", value ) statistics: [ C , M ] = multcompare (...) statistics: [ C , M , H ] = multcompare (...) statistics: [ C , M , H , GNAMES ] = multcompare (...) statistics: padj = multcompare ( p ) statistics: padj = multcompare ( p , "ctype", CTYPE ) Perform posthoc multiple comparison tests or p-value adjustments to control the family-wise error rate (FWER) or false discovery rate (FDR). C = multcompare ( STATS ) performs a multiple comparison using a STATS structure that is obtained as output from any of the following functions: anova1, anova2, anovan, kruskalwallis, and friedman. The return value C is a matrix with one row per comparison and six columns. Columns 1-2 are the indices of the two samples being compared. Columns 3-5 are a lower bound, estimate, and upper bound for their difference, where the bounds are for 95% confidence intervals. Column 6-8 are the multiplicity adjusted p-values for each individual comparison, the test statistic and the degrees of freedom. All tests by multcompare are two-tailed. multcompare can take a number of optional parameters as name-value pairs. […] = multcompare ( STATS , "alpha", ALPHA ) ALPHA sets the significance level of null hypothesis significance tests to ALPHA, and the central coverage of two-sided confidence intervals to 100*(1- ALPHA )%. (Default ALPHA is 0.05). […] = multcompare ( STATS , "ControlGroup", REF ) REF is the index of the control group to limit comparisons to. The index must be a positive integer scalar value. For each dimension (d) listed in DIM , multcompare uses STATS.grpnames{d}(idx) as the control group. (Default is empty, i.e. [], for full pairwise comparisons) […] = multcompare ( STATS , "ctype", CTYPE ) CTYPE is the type of comparison test to use. In order of increasing power, the choices are: "bonferroni", "scheffe", "mvt", "holm" (default), "hochberg", "fdr", or "lsd". The first five methods control the family-wise error rate. The "fdr" method controls false discovery rate (by the original Benjamini-Hochberg step-up procedure). The final method, "lsd" (or "none"), makes no attempt to control the Type 1 error rate of multiple comparisons. The coverage of confidence intervals are only corrected for multiple comparisons in the cases where CTYPE is "bonferroni", "scheffe" or "mvt", which control the Type 1 error rate for simultaneous inference. The "mvt" method uses the multivariate t distribution to assess the probability or critical value of the maximum statistic across the tests, thereby accounting for correlations among comparisons in the control of the family-wise error rate with simultaneous inference. In the case of pairwise comparisons, it simulates Tukey’s (or the Games-Howell) test, in the case of comparisons with a single control group, it simulates Dunnett’s test. CTYPE values "tukey-kramer" and "hsd" are recognised but set the value of CTYPE and REF to "mvt" and empty respectively. A CTYPE value "dunnett" is recognised but sets the value of CTYPE to "mvt", and if REF is empty, sets REF to 1. Since the algorithm uses a Monte Carlo method (of 1e+06 random samples), you can expect the results to fluctuate slightly with each call to multcompare and the calculations may be slow to complete for a large number of comparisons. If the parallel package is installed and loaded, multcompare will automatically accelerate computations by parallel processing. Note that p-values calculated by the "mvt" are truncated at 1e-06. […] = multcompare ( STATS , "df", DF ) DF is an optional scalar value to set the number of degrees of freedom in the calculation of p-values for the multiple comparison tests. By default, this value is extracted from the STATS structure of the ANOVA test, but setting DF maybe necessary to approximate Satterthwaite correction if anovan was performed using weights. […] = multcompare ( STATS , "dim", DIM ) DIM is a vector specifying the dimension or dimensions over which the estimated marginal means are to be calculated. Used only if STATS comes from anovan. The value [1 3], for example, computes the estimated marginal mean for each combination of the first and third predictor values. The default is to compute over the first dimension (i.e. 1). If the specified dimension is, or includes, a continuous factor then multcompare will return an error. […] = multcompare ( STATS , "estimate", ESTIMATE ) ESTIMATE is a string specifying the estimates to be compared when computing multiple comparisons after anova2; this argument is ignored by anovan and anova1. Accepted values for ESTIMATE are either "column" (default) to compare column means, or "row" to compare row means. If the model type in anova2 was "linear" or "nested" then only "column" is accepted for ESTIMATE since the row factor is assumed to be a random effect. […] = multcompare ( STATS , "display", DISPLAY ) DISPLAY is either "on" (the default): to display a table and graph of the comparisons (e.g. difference between means), their 100*(1- ALPHA )% intervals and multiplicity adjusted p-values in APA style; or "off": to omit the table and graph. On the graph, markers and error bars colored red have multiplicity adjusted p-values < ALPHA, otherwise the markers and error bars are blue. […] = multcompare ( STATS , "seed", SEED ) SEED is a scalar value used to initialize the random number generator so that CTYPE "mvt" produces reproducible results. [ C , M , H , GNAMES ] = multcompare (…) returns additional outputs. M is a matrix where columns 1-2 are the estimated marginal means and their standard errors, and columns 3-4 are lower and upper bounds of the confidence intervals for the means; the critical value of the test statistic is scaled by a factor of 2^(-0.5) before multiplying by the standard errors of the group means so that the intervals overlap when the difference in means becomes significant at approximately the level ALPHA . When ALPHA is 0.05, this corresponds to confidence intervals with 83.4% central coverage. H is a handle to the figure containing the graph. GNAMES is a cell array with one row for each group, containing the names of the groups. padj = multcompare ( p ) calculates and returns adjusted p-values ( padj ) using the Holm-step down Bonferroni procedure to control the family-wise error rate. padj = multcompare ( p , "ctype", CTYPE ) calculates and returns adjusted p-values ( padj ) computed using the method CTYPE . In order of increasing power, CTYPE for p-value adjustment can be either "bonferroni", "holm" (default), "hochberg", or "fdr". See above for further information about the CTYPE methods. See also: anova1, anova2, anovan, kruskalwallis, friedman, fitlm # name: # type: sq_string # elements: 1 # length: 140 Perform posthoc multiple comparison tests or p-value adjustments to control the family-wise error rate (FWER) or false discovery rate (FDR). # name: # type: sq_string # elements: 1 # length: 7 ranksum # name: # type: sq_string # elements: 1 # length: 2814 statistics: p = ranksum ( x , y ) statistics: p = ranksum ( x , y , alpha ) statistics: p = ranksum ( x , y , alpha , Name , Value ) statistics: p = ranksum ( x , y , Name , Value ) statistics: [ p , h ] = ranksum ( x , y , …) statistics: [ p , h , stats ] = ranksum ( x , y , …) Wilcoxon rank sum test for equal medians. This test is equivalent to a Mann-Whitney U-test. p = ranksum ( x , y ) returns the p-value of a two-sided Wilcoxon rank sum test. It tests the null hypothesis that two independent samples, in the vectors X and Y, come from continuous distributions with equal medians, against the alternative hypothesis that they are not. x and y can have different lengths and the test assumes that they are independent. ranksum treats NaN in x , y as missing values. The two-sided p-value is computed by doubling the most significant one-sided value. [ p , h ] = ranksum ( x , y ) also returns the result of the hypothesis test with h = 1 indicating a rejection of the null hypothesis at the default alpha = 0.05 significance level, and h = 0 indicating a failure to reject the null hypothesis at the same significance level. [ p , h , stats ] = ranksum ( x , y ) also returns the structure stats with information about the test statistic. It contains the field ranksum with the value of the rank sum test statistic and if computed with the "approximate" method it also contains the value of the z-statistic in the field zval . […] = ranksum ( x , y , alpha ) or alternatively […] = ranksum ( x , y , "alpha", alpha ) returns the result of the hypothesis test performed at the significance level ALPHA. […] = ranksum ( x , y , "method", M ) defines the computation method of the p-value specified in M , which can be "exact", "approximate", or "oldexact". M must be a single string. When "method" is unspecified, the default is: "exact" when min (length ( x ), length ( y )) < 10 and length ( x ) + length ( y ) < 10 , otherwise the "approximate" method is used. "exact" method uses full enumeration for small total sample size (< 10), otherwise the network algorithm is used for larger samples. "approximate" uses normal approximation method for computing the p-value. "oldexact" uses full enumeration for any sample size. Note, that this option can lead to out of memory error for large samples. Use with caution! […] = ranksum ( x , y , "tail", tail ) defines the type of test, which can be "both", "right", or "left". tail must be a single string. "both" – "medians are not equal" (two-tailed test, default) "right" – "median of X is greater than median of Y" (right-tailed test) "left" – "median of X is less than median of Y" (left-tailed test) Note: the rank sum statistic is based on the smaller sample of vectors x and y . # name: # type: sq_string # elements: 1 # length: 41 Wilcoxon rank sum test for equal medians. # name: # type: sq_string # elements: 1 # length: 16 regression_ftest # name: # type: sq_string # elements: 1 # length: 2324 statistics: [ h , pval , stats ] = regression_ftest ( y , x , fm ) statistics: […] = regression_ftest ( y , x , fm , rm ) statistics: […] = regression_ftest ( y , x , fm , rm , Name , Value ) statistics: […] = regression_ftest ( y , x , fm , [], Name , Value ) F-test for General Linear Regression Analysis Perform a general linear regression F test for the null hypothesis that the full model of the form y = b_0 + b_1 * x_1 + b_2 * x_2 + … + b_n * x_n + e , where n is the number of variables in x , does not perform better than a reduced model, such as y = b'_0 + b'_1 * x_1 + b'_2 * x_2 + … + b'_k * x_k + e , where k < n and it corresponds to the first k variables in x . Explanatory (dependent) variable y and response (independent) variables x must not contain any missing values (NaNs). The full model, fm , must be a vector of length equal to the columns of x , in which case the constant term b_0 is assumed 0, or equal to the columns of x plus one, in which case the first element is the constant b_0. The reduced model, rm , must include the constant term and a subset of the variables (columns) in x . If rm is not given, then a constant term b’_0 is assumed equal to the constant term, b_0, of the full model or 0, if the full model, fm , does not have a constant term. rm must be a vector or a scalar if only a constant term is passed into the function. Name-Value pair arguments can be used to set statistical significance. 'alpha' can be used to specify the significance level of the test (the default value is 0.05). If you want to pass optional Name-Value pair without a reduced model, make sure that the latter is passed as an empty variable. If h is 1 the null hypothesis is rejected, meaning that the full model explains the variance better than the restricted model. If h is 0, it can be assumed that the full model does NOT explain the variance any better than the restricted model. The p-value (1 minus the CDF of this distribution at f ) is returned in pval . Under the null, the test statistic f follows an F distribution with ’df1’ and ’df2’ degrees of freedom, which are returned as fields in the stats structure along with the test’s F-statistic, ’fstat’ See also: regression_ttest, regress, regress_gp # name: # type: sq_string # elements: 1 # length: 45 F-test for General Linear Regression Analysis # name: # type: sq_string # elements: 1 # length: 16 regression_ttest # name: # type: sq_string # elements: 1 # length: 1472 statistics: h = regression_ttest ( y , x ) statistics: [ h , pval ] = regression_ttest ( y , x ) statistics: [ h , pval , ci ] = regression_ttest ( y , x ) statistics: [ h , pval , ci , stats ] = regression_ttest ( y , x ) statistics: […] = regression_ttest ( y , x , Name , Value ) Perform a linear regression t-test. h = regression_ttest ( y , x ) tests the null hypothesis that the slope beta1 of a simple linear regression equals 0. The result is h = 0 if the null hypothesis cannot be rejected at the 5% significance level, or h = 1 if the null hypothesis can be rejected at the 5% level. y and x must be vectors of equal length with finite real numbers. The p-value of the test is returned in pval . A 100(1-alpha)% confidence interval for beta1 is returned in ci . stats is a structure containing the value of the test statistic ( tstat ), the degrees of freedom ( df ), the slope coefficient ( beta1 ), and the intercept ( beta0 ). Under the null, the test statistic stats . tstat follows a T -distribution with stats . df degrees of freedom. […] = regression_ttest (…, name , value ) specifies one or more of the following name/value pairs: Name Value 'alpha' the significance level. Default is 0.05. 'tail' a string specifying the alternative hypothesis 'both' beta1 is not 0 (two-tailed, default) 'left' beta1 is less than 0 (left-tailed) 'right' beta1 is greater than 0 (right-tailed) See also: regression_ftest, regress, regress_gp # name: # type: sq_string # elements: 1 # length: 35 Perform a linear regression t-test. # name: # type: sq_string # elements: 1 # length: 8 runstest # name: # type: sq_string # elements: 1 # length: 1797 statistics: h = runstest ( x ) statistics: h = runstest ( x , v ) statistics: h = runstest ( x , 'ud' ) statistics: h = runstest (…, Name , Value ) statistics: [ h , pval , stats ] = runstest (…) Run test for randomness in the vector x . h = runstest ( x ) calculates the number of runs of consecutive values above or below the mean of x and tests the null hypothesis that the values in the data vector x come in random order. h is 1 if the test rejects the null hypothesis at the 5% significance level, or 0 otherwise. h = runstest ( x , v ) tests the null hypothesis based on the number of runs of consecutive values above or below the specified reference value v . Values exactly equal to v are omitted. h = runstest ( x , 'ud' ) calculates the number of runs up or down and tests the null hypothesis that the values in the data vector x follow a trend. Too few runs indicate a trend, while too many runs indicate an oscillation. Values exactly equal to the preceding value are omitted. h = runstest (…, Name , Value ) specifies additional options to the above tests by one or more Name - Value pair arguments. Name Value 'alpha' the significance level. Default is 0.05. 'method' a string specifying the method used to compute the p-value of the test. It can be either 'exact' to use an exact algorithm, or 'approximate' to use a normal approximation. The default is 'exact' for runs above/below, and for runs up/down when the length of x is less than or equal to 50. When testing for runs up/down and the length of x is greater than 50, then the default is 'approximate' , and the 'exact' method is not available. 'tail' a string specifying the alternative hypothesis 'both' two-tailed (default) 'left' left-tailed 'right' right-tailed See also: signrank, signtest # name: # type: sq_string # elements: 1 # length: 40 Run test for randomness in the vector x. # name: # type: sq_string # elements: 1 # length: 11 sampsizepwr # name: # type: sq_string # elements: 1 # length: 5259 statistics: n = sampsizepwr ( testtype , params , p1 ) statistics: n = sampsizepwr ( testtype , params , p1 , power ) statistics: power = sampsizepwr ( testtype , params , p1 , [], n ) statistics: p1 = sampsizepwr ( testtype , params , [], power , n ) statistics: [ n1 , n2 ] = sampsizepwr ( 't2' , params , p1 , power ) statistics: […] = sampsizepwr ( testtype , params , p1 , power , n , name , value ) Sample size and power calculation for hypothesis test. sampsizepwr computes the sample size, power, or alternative parameter value for a hypothesis test, given the other two values. For example, you can compute the sample size required to obtain a particular power for a hypothesis test, given the parameter value of the alternative hypothesis. n = sampsizepwr ( testtype , params , p1 ) returns the sample size N required for a two-sided test of the specified type to have a power (probability of rejecting the null hypothesis when the alternative is true) of 0.90 when the significance level (probability of rejecting the null hypothesis when the null hypothesis is true) is 0.05. params specifies the parameter values under the null hypothesis. P1 specifies the value of the single parameter being tested under the alternative hypothesis. For the two-sample t-test, N is the value of the equal sample size for both samples, params specifies the parameter values of the first sample under the null and alternative hypotheses, and P1 specifies the value of the single parameter from the other sample under the alternative hypothesis. The following TESTTYPE values are available: "z" one-sample z-test for normally distributed data with known standard deviation. params is a two-element vector [MU0 SIGMA0] of the mean and standard deviation, respectively, under the null hypothesis. P1 is the value of the mean under the alternative hypothesis. "t" one-sample t-test or paired t-test for normally distributed data with unknown standard deviation. params is a two-element vector [MU0 SIGMA0] of the mean and standard deviation, respectively, under the null hypothesis. P1 is the value of the mean under the alternative hypothesis. "t2" two-sample pooled t-test (test for equal means) for normally distributed data with equal unknown standard deviations. params is a two-element vector [MU0 SIGMA0] of the mean and standard deviation of the first sample under the null and alternative hypotheses. P1 is the the mean of the second sample under the alternative hypothesis. "var" chi-square test of variance for normally distributed data. params is the variance under the null hypothesis. P1 is the variance under the alternative hypothesis. "p" test of the P parameter (success probability) for a binomial distribution. params is the value of P under the null hypothesis. P1 is the value of P under the alternative hypothesis. "r" test of the correlation coefficient parameter for significance. params is the value of r under the null hypothesis. P1 is the value of r under the alternative hypothesis. The "p" test for the binomial distribution is a discrete test for which increasing the sample size does not always increase the power. For N values larger than 200, there may be values smaller than the returned N value that also produce the desired power. n = sampsizepwr ( testtype , params , p1 , power ) returns the sample size N such that the power is power for the parameter value P1. For the two-sample t-test, N is the equal sample size of both samples. [ n1 , n2 ] = sampsizepwr ("t2", params , p1 , power ) returns the sample sizes n1 and n2 for the two samples. These values are the same unless the "ratio" parameter, ratio = n2 / n2 , is set to a value other than the default (See the name/value pair definition of ratio below). power = sampsizepwr ( testtype , params , p1 , [], n ) returns the power achieved for a sample size of n when the true parameter value is p1 . For the two-sample t-test, n is the smaller one of the two sample sizes. p1 = sampsizepwr ( testtype , params , [], power , n ) returns the parameter value detectable with the specified sample size n and power power . For the two-sample t-test, n is the smaller one of the two sample sizes. When computing p1 for the "p" test, if no alternative can be rejected for a given params , n and power value, the function displays a warning message and returns NaN. […] = sampsizepwr (…, n , name , value ) specifies one or more of the following name / value pairs: Name Value "alpha" significance level of the test (default is 0.05) "tail" the type of test which can be: "both" two-sided test for an alternative p1 not equal to params "right" one-sided test for an alternative p1 larger than params "left" one-sided test for an alternative p1 smaller than params "ratio" desired ratio n2 / n2 of the larger sample size n2 to the smaller sample size n1 . Used only for the two-sample t-test. The value of ratio is greater than or equal to 1 (default is 1). sampsizepwr computes the sample size, power, or alternative hypothesis value given values for the other two. Specify one of these as [] to compute it. The remaining parameters (and ALPHA, RATIO) can be scalars or arrays of the same size. See also: vartest, ttest, ttest2, ztest, binocdf # name: # type: sq_string # elements: 1 # length: 54 Sample size and power calculation for hypothesis test. # name: # type: sq_string # elements: 1 # length: 8 signrank # name: # type: sq_string # elements: 1 # length: 2865 statistics: pval = signrank ( x ) statistics: pval = signrank ( x , my ) statistics: pval = signrank ( x , my , Name , Value ) statistics: [ pval , h ] = signrank (…) statistics: [ pval , h , stats ] = signrank (…) Wilcoxon signed rank test for median. pval = signrank ( x ) returns the p -value of a two-sided Wilcoxon signed rank test. It tests the null hypothesis that data in x come from a distribution with zero median at the 5% significance level under the assumption that the distribution is symmetric about its median. x must be a vector. If the second argument my is a scalar, the null hypothesis is that x has median my , whereas if my is a vector, the null hypothesis is that the distribution of x - my has zero median. pval = signrank (…, Name , Value ) performs the Wilcoxon signed rank test with additional options specified by one or more of the following Name , Value pair arguments: Name Value 'alpha' A scalar value for the significance level of the test. Default is 0.05. 'tail' A character vector specifying the alternative hypothesis. It can take one of the following values: Value Description 'both' For one-sample test ( my is empty or a scalar), the data in x come from a continuous distribution with median different than zero or my . For two-sample test ( my is a vector), the data in x - my come from a continuous distribution with median different than zero. 'left' For one-sample test ( my is empty or a scalar), the data in x come from a continuous distribution with median less than zero or my . For two-sample test ( my is a vector), the data in x - my come from a continuous distribution with median less than zero. 'right' For one-sample test ( my is empty or a scalar), the data in x come from a continuous distribution with median greater than zero or my . For two-sample test ( my is a vector), the data in x - my come from a continuous distribution with median greater than zero. Name Value 'method' A character vector specifying the method for computing the p -value. It can take one of the following values: Value Description 'exact' Exact computation of the p -value. It is the default value for 15 of fewer observations when 'method' is not specified. 'approximate' Using normal approximation for computing the p -value. It is the default value for more than 15 observations when 'method' is not specified. [ pval , h ] = signrank (…) also returns a logical value indicating the test decision. If h is 0, the null hypothesis is accepted, whereas if h is 1, the null hypothesis is rejected. [ pval , h , stats ] = signrank (…) also returns the structure stats containing the following fields: Field Value signedrank Value of the sign rank test statistic. zval Value of the z -statistic (only computed when the 'method' is 'approximate' ). See also: tiedrank, signtest, runstest # name: # type: sq_string # elements: 1 # length: 37 Wilcoxon signed rank test for median. # name: # type: sq_string # elements: 1 # length: 8 signtest # name: # type: sq_string # elements: 1 # length: 2753 statistics: pval = signtest ( x ) statistics: pval = signtest ( x , my ) statistics: pval = signtest ( x , my , Name , Value ) statistics: [ pval , h ] = signtest (…) statistics: [ pval , h , stats ] = signtest (…) Signed test for median. pval = signtest ( x ) returns the p -value of a two-sided sign test. It tests the null hypothesis that data in x come from a distribution with zero median at the 5% significance level. x must be a vector. If the second argument my is a scalar, the null hypothesis is that x has median my , whereas if my is a vector, the null hypothesis is that the distribution of x - my has zero median. pval = signtest (…, Name , Value ) performs the Wilcoxon signed rank test with additional options specified by one or more of the following Name , Value pair arguments: Name Value 'alpha' A scalar value for the significance level of the test. Default is 0.05. 'tail' A character vector specifying the alternative hypothesis. It can take one of the following values: Value Description 'both' For one-sample test ( my is empty or a scalar), the data in x come from a continuous distribution with median different than zero or my . For two-sample test ( my is a vector), the data in x - my come from a continuous distribution with median different than zero. 'left' For one-sample test ( my is empty or a scalar), the data in x come from a continuous distribution with median less than zero or my . For two-sample test ( my is a vector), the data in x - my come from a continuous distribution with median less than zero. 'right' For one-sample test ( my is empty or a scalar), the data in x come from a continuous distribution with median greater than zero or my . For two-sample test ( my is a vector), the data in x - my come from a continuous distribution with median greater than zero. Name Value 'method' A character vector specifying the method for computing the p -value. It can take one of the following values: Value Description 'exact' Exact computation of the p -value. It is the default value for fewer than 100 observations when 'method' is not specified. 'approximate' Using normal approximation for computing the p -value. It is the default value for 100 or more observations when 'method' is not specified. [ pval , h ] = signtest (…) also returns a logical value indicating the test decision. If h is 0, the null hypothesis is accepted, whereas if h is 1, the null hypothesis is rejected. [ pval , h , stats ] = signtest (…) also returns the structure stats containing the following fields: Field Value sign Value of the sign test statistic. zval Value of the z -statistic (only computed when the 'method' is 'approximate' ). See also: signrank, tiedrank, runstest # name: # type: sq_string # elements: 1 # length: 23 Signed test for median. # name: # type: sq_string # elements: 1 # length: 5 ttest # name: # type: sq_string # elements: 1 # length: 2360 statistics: [ h , pval , ci , stats ] = ttest ( x ) statistics: [ h , pval , ci , stats ] = ttest ( x , m ) statistics: [ h , pval , ci , stats ] = ttest ( x , y ) statistics: [ h , pval , ci , stats ] = ttest ( x , m , Name , Value ) statistics: [ h , pval , ci , stats ] = ttest ( x , y , Name , Value ) Test for mean of a normal sample with unknown variance. Perform a t-test of the null hypothesis mean ( x ) == m for a sample x from a normal distribution with unknown mean and unknown standard deviation. Under the null, the test statistic t has a Student’s t distribution. The default value of m is 0. If the second argument y is a vector, a paired-t test of the hypothesis mean ( x ) = mean ( y ) is performed. If x and y are vectors, they must have the same size and dimensions. x (and y ) can also be matrices. For matrices, ttest performs separate t-tests along each column, and returns a vector of results. x and y must have the same number of columns. The Type I error rate of the resulting vector of pval can be controlled by entering pval as input to the function multcompare . ttest treats NaNs as missing values, and ignores them. Name-Value pair arguments can be used to set various options. 'alpha' can be used to specify the significance level of the test (the default value is 0.05). 'tail' , can be used to select the desired alternative hypotheses. If the value is 'both' (default) the null is tested against the two-sided alternative mean ( x ) != m . If it is 'right' the one-sided alternative mean ( x ) > m is considered. Similarly for 'left' , the one-sided alternative mean ( x ) < m is considered. When argument x is a matrix, 'dim' can be used to select the dimension over which to perform the test. (The default is the first non-singleton dimension). If h is 1 the null hypothesis is rejected, meaning that the tested sample does not come from a Student’s t distribution. If h is 0, then the null hypothesis cannot be rejected and it can be assumed that x follows a Student’s t distribution. The p-value of the test is returned in pval . A 100(1-alpha)% confidence interval is returned in ci . stats is a structure containing the value of the test statistic ( tstat ), the degrees of freedom ( df ) and the sample’s standard deviation ( sd ). See also: hotelling_t2test, ttest2, hotelling_t2test2 # name: # type: sq_string # elements: 1 # length: 55 Test for mean of a normal sample with unknown variance. # name: # type: sq_string # elements: 1 # length: 6 ttest2 # name: # type: sq_string # elements: 1 # length: 1862 statistics: [ h , pval , ci , stats ] = ttest2 ( x , y ) statistics: [ h , pval , ci , stats ] = ttest2 ( x , y , Name , Value ) Perform a t-test to compare the means of two groups of data under the null hypothesis that the groups are drawn from distributions with the same mean. x and y can be vectors or matrices. For matrices, ttest2 performs separate t-tests along each column, and returns a vector of results. x and y must have the same number of columns. The Type I error rate of the resulting vector of pval can be controlled by entering pval as input to the function multcompare . ttest2 treats NaNs as missing values, and ignores them. For a nested t-test, use anova2 . The argument 'alpha' can be used to specify the significance level of the test (the default value is 0.05). The string argument 'tail' , can be used to select the desired alternative hypotheses. If 'tail' is 'both' (default) the null is tested against the two-sided alternative mean ( x ) != m . If 'tail' is 'right' the one-sided alternative mean ( x ) > m is considered. Similarly for 'left' , the one-sided alternative mean ( x ) < m is considered. When 'vartype' is 'equal' the variances are assumed to be equal (this is the default). When 'vartype' is 'unequal' the variances are not assumed equal. When argument x and y are matrices the 'dim' argument can be used to select the dimension over which to perform the test. (The default is the first non-singleton dimension.) If h is 0 the null hypothesis is accepted, if it is 1 the null hypothesis is rejected. The p-value of the test is returned in pval . A 100(1-alpha)% confidence interval is returned in ci . stats is a structure containing the value of the test statistic ( tstat ), the degrees of freedom ( df ) and the sample standard deviation ( sd ). See also: hotelling_t2test, anova1, hotelling_t2test2, ttest # name: # type: sq_string # elements: 1 # length: 150 Perform a t-test to compare the means of two groups of data under the null hypothesis that the groups are drawn from distributions with the same mean. # name: # type: sq_string # elements: 1 # length: 7 vartest # name: # type: sq_string # elements: 1 # length: 1903 statistics: h = vartest ( x , v ) statistics: h = vartest ( x , v , name , value ) statistics: [ h , pval ] = vartest (…) statistics: [ h , pval , ci ] = vartest (…) statistics: [ h , pval , ci , stats ] = vartest (…) One-sample test of variance. h = vartest ( x , v ) performs a chi-square test of the hypothesis that the data in the vector x come from a normal distribution with variance v , against the alternative that x comes from a normal distribution with a different variance. The result is h = 0 if the null hypothesis ("variance is V") cannot be rejected at the 5% significance level, or h = 1 if the null hypothesis can be rejected at the 5% level. x may also be a matrix or an N-D array. For matrices, vartest performs separate tests along each column of x , and returns a vector of results. For N-D arrays, vartest works along the first non-singleton dimension of x . v must be a scalar. vartest treats NaNs as missing values, and ignores them. [ h , pval ] = vartest (…) returns the p-value. That is the probability of observing the given result, or one more extreme, by chance if the null hypothesis true. [ h , pval , ci ] = vartest (…) returns a 100 * (1 - alpha )% confidence interval for the true variance. [ h , pval , ci , stats ] = vartest (…) returns a structure with the following fields: chisqstat the value of the test statistic df the degrees of freedom of the test […] = vartest (…, name , value ), … specifies one or more of the following name/value pairs: Name Value 'alpha' the significance level. Default is 0.05. 'dim' dimension to work along a matrix or an N-D array. 'tail' a string specifying the alternative hypothesis 'both' variance is not v (two-tailed, default) 'left' variance is less than v (left-tailed) 'right' variance is greater than v (right-tailed) See also: ttest, ztest, kstest # name: # type: sq_string # elements: 1 # length: 28 One-sample test of variance. # name: # type: sq_string # elements: 1 # length: 8 vartest2 # name: # type: sq_string # elements: 1 # length: 2080 statistics: h = vartest2 ( x , y ) statistics: h = vartest2 ( x , y , name , value ) statistics: [ h , pval ] = vartest2 (…) statistics: [ h , pval , ci ] = vartest2 (…) statistics: [ h , pval , ci , stats ] = vartest2 (…) Two-sample F test for equal variances. h = vartest2 ( x , y ) performs an F test of the hypothesis that the independent data in vectors x and y come from normal distributions with equal variance, against the alternative that they come from normal distributions with different variances. The result is h = 0 if the null hypothesis ("variance are equal") cannot be rejected at the 5% significance level, or h = 1 if the null hypothesis can be rejected at the 5% level. x and y may also be matrices or N-D arrays. For matrices, vartest2 performs separate tests along each column and returns a vector of results. For N-D arrays, vartest2 works along the first non-singleton dimension and x and y must have the same size along all the remaining dimensions. vartest2 treats NaNs as missing values, and ignores them. [ h , pval ] = vartest2 (…) returns the p-value. That is the probability of observing the given result, or one more extreme, by chance if the null hypothesis true. [ h , pval , ci ] = vartest2 (…) returns a 100 × (1 - alpha )% confidence interval for the true ratio var(X)/var(Y). [ h , pval , ci , stats ] = vartest2 (…) returns a structure with the following fields: fstat the value of the test statistic df1 the numerator degrees of freedom of the test df2 the denominator degrees of freedom of the test […] = vartest2 (…, name , value ), … specifies one or more of the following name/value pairs: Name Value 'alpha' the significance level. Default is 0.05. 'dim' dimension to work along a matrix or an N-D array. 'tail' a string specifying the alternative hypothesis 'both' variance is not v (two-tailed, default) 'left' variance is less than v (left-tailed) 'right' variance is greater than v (right-tailed) See also: ttest2, kstest2, bartlett_test, levene_test # name: # type: sq_string # elements: 1 # length: 38 Two-sample F test for equal variances. # name: # type: sq_string # elements: 1 # length: 8 vartestn # name: # type: sq_string # elements: 1 # length: 2744 statistics: vartestn ( x ) statistics: vartestn ( x , group ) statistics: vartestn (…, name , value ) statistics: p = vartestn (…) statistics: [ p , stats ] = vartestn (…) statistics: [ p , stats ] = vartestn (…, name , value ) Test for equal variances across multiple groups. h = vartestn ( x ) performs Bartlett’s test for equal variances for the columns of the matrix x . This is a test of the null hypothesis that the columns of x come from normal distributions with the same variance, against the alternative that they come from normal distributions with different variances. The result is displayed in a summary table of statistics as well as a box plot of the groups. vartestn ( x , group ) requires a vector x , and a group argument that is a categorical variable, vector, string array, or cell array of strings with one row for each element of x . Values of x corresponding to the same value of group are placed in the same group. vartestn treats NaNs as missing values, and ignores them. p = vartestn (…) returns the probability of observing the given result, or one more extreme, by chance under the null hypothesis that all groups have equal variances. Small values of p cast doubt on the validity of the null hypothesis. [ p , stats ] = vartestn (…) returns a structure with the following fields: chistat – the value of the test statistic df – the degrees of freedom of the test [ p , stats ] = vartestn (…, name , value ) specifies one or more of the following name / value pairs: Name Value 'display' 'on' to display a boxplot and table, or 'off' to omit these displays. Default 'on' . 'testtype' One of the following strings to control the type of test to perform 'Bartlett' Bartlett’s test (default). 'LeveneQuadratic' Levene’s test computed by performing anova on the squared deviations of the data values from their group means. 'LeveneAbsolute' Levene’s test computed by performing anova on the absolute deviations of the data values from their group means. 'BrownForsythe' Brown-Forsythe test computed by performing anova on the absolute deviations of the data values from the group medians. 'OBrien' O’Brien’s modification of Levene’s test with W=0.5 . The classical Bartlett’s test is sensitive to the assumption that the distribution in each group is normal. The other test types are more robust to non-normal distributions, especially ones prone to outliers. For these tests, the STATS output structure has a field named fstat containing the test statistic, and df1 and df2 containing its numerator and denominator degrees of freedom. See also: vartest, vartest2, anova1, bartlett_test, levene_test # name: # type: sq_string # elements: 1 # length: 48 Test for equal variances across multiple groups. # name: # type: sq_string # elements: 1 # length: 5 ztest # name: # type: sq_string # elements: 1 # length: 1797 statistics: h = ztest ( x , m , sigma ) statistics: h = ztest ( x , m , sigma , Name , Value ) statistics: [ h , pval ] = ztest (…) statistics: [ h , pval , ci ] = ztest (…) statistics: [ h , pval , ci , zvalue ] = ztest (…) One-sample Z-test. h = ztest ( x , v ) performs a Z-test of the hypothesis that the data in the vector x come from a normal distribution with mean m , against the alternative that x comes from a normal distribution with a different mean m . The result is h = 0 if the null hypothesis ("mean is M") cannot be rejected at the 5% significance level, or h = 1 if the null hypothesis can be rejected at the 5% level. x may also be a matrix or an N-D array. For matrices, ztest performs separate tests along each column of x , and returns a vector of results. For N-D arrays, ztest works along the first non-singleton dimension of x . m and sigma must be scalars. ztest treats NaNs as missing values, and ignores them. [ h , pval ] = ztest (…) returns the p-value. That is the probability of observing the given result, or one more extreme, by chance if the null hypothesis true. [ h , pval , ci ] = ztest (…) returns a 100 * (1 - alpha )% confidence interval for the true mean. [ h , pval , ci , zvalue ] = ztest (…) returns the value of the test statistic. […] = ztest (…, Name , Value , …) specifies one or more of the following Name / Value pairs: Name Value "alpha" the significance level. Default is 0.05. "dim" dimension to work along a matrix or an N-D array. "tail" a string specifying the alternative hypothesis: "both" "mean is not m " (two-tailed, default) "left" "mean is less than m " (left-tailed) "right" "mean is greater than m " (right-tailed) See also: ttest, vartest, signtest, kstest # name: # type: sq_string # elements: 1 # length: 18 One-sample Z-test. # name: # type: sq_string # elements: 1 # length: 6 ztest2 # name: # type: sq_string # elements: 1 # length: 1518 statistics: h = ztest2 ( x1 , n1 , x2 , n2 ) statistics: h = ztest2 ( x1 , n1 , x2 , n2 , Name , Value ) statistics: [ h , pval ] = ztest2 (…) statistics: [ h , pval , zvalue ] = ztest2 (…) Two proportions Z-test. If x1 and n1 are the counts of successes and trials in one sample, and x2 and n2 those in a second one, test the null hypothesis that the success probabilities p1 and p2 are the same. The result is h = 0 if the null hypothesis cannot be rejected at the 5% significance level, or h = 1 if the null hypothesis can be rejected at the 5% level. Under the null, the test statistic zvalue approximately follows a standard normal distribution. The size of h , pval , and zvalue is the common size of x1 , n1 , x2 , and n2 , which must be scalars or of common size. A scalar input functions as a constant matrix of the same size as the other inputs. [ h , pval ] = ztest2 (…) returns the p-value. That is the probability of observing the given result, or one more extreme, by chance if the null hypothesis true. [ h , pval , zvalue ] = ztest2 (…) returns the value of the test statistic. […] = ztest2 (…, Name , Value , …) specifies one or more of the following Name / Value pairs: Name Value 'alpha' the significance level. Default is 0.05. 'tail' a string specifying the alternative hypothesis 'both' p1 is not p2 (two-tailed, default) 'left' p1 is less than p2 (left-tailed) 'right' p1 is greater than p2 (right-tailed) See also: chi2test, fishertest # name: # type: sq_string # elements: 1 # length: 23 Two proportions Z-test. statistics-release-1.9.2/inst/Hypothesis_Testing/dwtest.m000066400000000000000000000211501524624707500236470ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} dwtest (@var{r}, @var{x}) ## @deftypefnx {statistics} {@var{p} =} dwtest (@var{r}, @var{x}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{p}, @var{d}] =} dwtest (@dots{}) ## ## Durbin-Watson test for autocorrelation in linear regression residuals. ## ## @code{@var{p} = dwtest (@var{r}, @var{x})} performs the Durbin-Watson test on ## the residuals @var{r} of a linear regression with design matrix @var{x} ## (which should include a column of ones if the model has a constant term). ## The null hypothesis is that the residuals are uncorrelated, against the ## alternative that they are autocorrelated. @var{r} is an @math{N*1} vector and ## @var{x} is an @math{N*P} matrix. @var{p} is the p-value of the test. ## ## The Durbin-Watson statistic is ## @tex ## $$ d = \sum_{i=1}^{n-1} (r_{i+1} - r_i)^2 / \sum_{i=1}^{n} r_i^2. $$ ## ## @end tex ## @ifnottex ## @code{@var{d} = sum ((diff (@var{r})) .^ 2) / sum (@var{r} .^ 2)}. ## @end ifnottex ## Values near 2 indicate no autocorrelation, values towards 0 positive ## autocorrelation, and values towards 4 negative autocorrelation. ## ## @code{@var{p} = dwtest (@var{r}, @var{x}, @var{name}, @var{value})} specifies ## additional options using @qcode{Name-Value} pair arguments: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Method'} @tab @qcode{'exact'} to compute the exact p-value ## from the null distribution of the statistic (a ratio of quadratic forms, ## evaluated with Imhof's method), or @qcode{'approximate'} to use a normal ## approximation based on the mean and variance of the statistic. The default is ## @qcode{'exact'} for @math{n < 400} and @qcode{'approximate'} otherwise. ## ## @item @qcode{'Tail'} @tab The alternative hypothesis: @qcode{'both'} ## (default) for a nonzero autocorrelation, @qcode{'right'} for a positive ## autocorrelation, or @qcode{'left'} for a negative autocorrelation. ## @end multitable ## ## @code{[@var{p}, @var{d}] = dwtest (@dots{})} also returns the Durbin-Watson ## statistic @var{d}. ## ## @seealso{regress, fitlm, runstest} ## @end deftypefn function [pval, d] = dwtest (r, x, varargin) if (nargin < 2) print_usage (); endif if (! (isnumeric (r) && isreal (r) && isvector (r))) error ("dwtest: R must be a real vector of residuals."); endif r = r(:); n = numel (r); if (! (isnumeric (x) && isreal (x) && ismatrix (x) && rows (x) == n)) error ("dwtest: X must be a real matrix with one row per residual."); endif ## Parse Name-Value pairs method = ""; tail = "both"; if (mod (numel (varargin), 2) != 0) error ("dwtest: optional arguments must be given as Name-Value pairs."); endif for k = 1:2:numel (varargin) if (! ischar (varargin{k})) error ("dwtest: parameter names must be character vectors."); endif switch (lower (varargin{k})) case "method" method = lower (varargin{k+1}); case "tail" tail = lower (varargin{k+1}); otherwise error ("dwtest: unknown parameter name '%s'.", varargin{k}); endswitch endfor if (isempty (method)) method = ifelse (n < 400, "exact", "approximate"); endif if (! any (strcmp (method, {"exact", "approximate"}))) error ("dwtest: 'Method' must be 'exact' or 'approximate'."); endif if (! any (strcmp (tail, {"both", "right", "left"}))) error ("dwtest: 'Tail' must be 'both', 'right', or 'left'."); endif ## Durbin-Watson statistic d = sum (diff (r) .^ 2) / sum (r .^ 2); ## Orthonormal basis of the column space of X (residual-maker M = I - Q*Q') [Q, R] = qr (x, 0); keep = abs (diag (R)) > max (size (x)) * eps (max (abs (diag (R)))); Q = Q(:, keep); p = columns (Q); ## Durbin-Watson difference operator A (so that r'*A*r = sum (diff (r) .^ 2)) A = 2 * eye (n); A(1,1) = 1; A(n,n) = 1; A -= diag (ones (n-1, 1), 1) + diag (ones (n-1, 1), -1); ## Lower-tail probability P(D <= d) under the null hypothesis if (strcmp (method, "exact")) M = eye (n) - Q * Q'; MAM = M * A * M; nu = sort (eig ((MAM + MAM') / 2), "descend"); nu = nu(1:n - p); ## n - p nonzero eigenvalues of M*A*M plow = dwtest_imhof_ (nu, d); else ## Mean and variance of D from traces, avoiding the eigendecomposition AQ = A * Q; QtAQ = Q' * A * Q; sumnu = (2 * n - 2) - trace (QtAQ); ## tr (M*A) sumnu2 = (6 * n - 8) - 2 * sum (AQ(:) .^ 2) + sum (QtAQ(:) .^ 2); ## tr((M*A)^2) m1 = sumnu / (n - p); s2 = 2 * (sumnu2 - sumnu ^ 2 / (n - p)) / ((n - p) * (n - p + 2)); plow = normcdf ((d - m1) / sqrt (s2)); endif plow = min (max (plow, 0), 1); ## Alternative hypothesis: 'right' -> positive autocorrelation (small D), ## 'left' -> negative autocorrelation (large D) switch (tail) case "right" pval = plow; case "left" pval = 1 - plow; case "both" pval = 2 * min (plow, 1 - plow); endswitch pval = min (max (pval, 0), 1); endfunction ## Imhof's method for P(sum ((nu_i - d) * chi2_1) <= 0), which equals the ## lower-tail probability P(D <= d) of the Durbin-Watson statistic. function plow = dwtest_imhof_ (nu, d) lam = nu - d; I = quadgk (@(u) dwtest_imhof_integrand_ (u, lam), 0, Inf, ... "AbsTol", 1e-10, "RelTol", 1e-9); plow = 0.5 - I / pi; endfunction function y = dwtest_imhof_integrand_ (u, lam) sz = size (u); uv = u(:).'; ## row vector of quadrature nodes lam = lam(:); ## column vector of coefficients theta = 0.5 * sum (atan (lam * uv), 1); rho = prod ((1 + (lam .^ 2) * (uv .^ 2)) .^ 0.25, 1); y = sin (theta) ./ (uv .* rho); y(uv == 0) = 0.5 * sum (lam); ## limit of the integrand as u -> 0 y = reshape (y, sz); ## match the shape quadgk expects endfunction function out = ifelse (cond, a, b) if (cond) out = a; else out = b; endif endfunction %!demo %! ## Test regression residuals for autocorrelation %! x = [ones(20, 1), (1:20)']; %! y = x * [1; 0.5] + sin ((1:20)' / 2); # add an autocorrelated component %! b = x \ y; %! r = y - x * b; %! [p, d] = dwtest (r, x) ## Test the statistic value %!test %! x = [ones(6, 1), (1:6)']; %! r = [1; -1; 1; -1; 1; -1]; # strong negative autocorrelation %! [p, d] = dwtest (r, x); %! assert_equal (d, sum (diff (r) .^ 2) / sum (r .^ 2), 1e-12); %! assert_equal (d, 20 / 6, 1e-12); %! assert_equal (p >= 0 && p <= 1, true); %!test # exact and approximate methods give similar p-values %! x = [ones(30, 1), (1:30)', ((1:30)') .^ 2]; %! r = sin ((1:30)' / 3); %! pe = dwtest (r, x, "Method", "exact"); %! pa = dwtest (r, x, "Method", "approximate"); %! assert_equal (pe, pa, 0.05); %!test # tail selection is consistent %! x = [ones(15, 1), (1:15)']; %! r = (-1) .^ (1:15)'; # alternating -> D near 4 %! pr = dwtest (r, x, "Tail", "right"); %! pl = dwtest (r, x, "Tail", "left"); %! pb = dwtest (r, x, "Tail", "both"); %! assert_equal (pr + pl, 1, 1e-10); %! assert_equal (pb, 2 * min (pr, pl), 1e-10); %!test # left tail rejects strong negative autocorrelation (D near 4) %! x = [ones(20, 1), (1:20)']; %! r = (-1) .^ (1:20)'; %! assert_equal (dwtest (r, x, "Tail", "left") < 0.05, true); ## Test input validation %!error dwtest (1) %!error dwtest (ones (3, 3), ones (3, 2)) %!error ... %! dwtest ([1;2;3], ones (2, 2)) %!error ... %! dwtest ([1;2;3], ones (3, 1), "Tail") %!error ... %! dwtest ([1;2;3], ones (3, 1), "foo", "bar") %!error ... %! dwtest ([1;2;3], ones (3, 1), "Method", "fast") %!error ... %! dwtest ([1;2;3], ones (3, 1), "Tail", "up") statistics-release-1.9.2/inst/Hypothesis_Testing/fishertest.m000066400000000000000000000215461524624707500245260ustar00rootroot00000000000000## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/OR ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, OR (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} fishertest (@var{x}) ## @deftypefnx {statistics} {@var{h} =} fishertest (@var{x}, @var{param1}, @var{value1}, @dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}] =} fishertest (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{stats}] =} fishertest (@dots{}) ## ## Fisher's exact test. ## ## @code{@var{h} = fishertest (@var{x})} performs Fisher's exact test on a ## @math{2*2} contingency table given in matrix @var{x}. This is a test of the ## hypothesis that there are no non-random associations between the two 2-level ## categorical variables in @var{x}. @code{fishertest} returns the result of ## the tested hypothesis in @var{h}. @var{h} = 0 indicates that the null ## hypothesis (of no association) cannot be rejected at the 5% significance ## level. @var{h} = 1 indicates that the null hypothesis can be rejected at the ## 5% level. @var{x} must contain only non-negative integers. Use the ## @code{crosstab} function to generate the contingency table from samples of ## two ## categorical variables. Fisher's exact test is not suitable when all integers ## in @var{x} are very large. User can use the Chi-square test in this case. ## ## @code{[@var{h}, @var{pval}] = fishertest (@var{x})} returns the p-value in ## @var{pval}. That is the probability of observing the given result, or one ## more extreme, by chance if the null hypothesis is true. Small values of ## @var{pval} cast doubt on the validity of the null hypothesis. ## ## @code{[@var{p}, @var{pval}, @var{stats}] = fishertest (@dots{})} returns the ## structure @var{stats} with the following fields: ## ## @multitable @columnfractions 0.3 0.65 ## @item @qcode{OddsRatio} @tab -- the odds ratio ## @item @qcode{ConfidenceInterval} @tab -- the asymptotic confidence ## interval for the odds ratio. If any of the four entries in the contingency ## table @var{x} is zero, the confidence interval will not be computed, and ## @qcode{[-Inf Inf]} will be displayed. ## @end multitable ## ## @code{[@dots{}] = fishertest (@dots{}, @var{name}, @var{value}, @dots{})} ## specifies one or more of the following name/value pairs: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'alpha'} @tab the significance level. Default is 0.05. ## ## @item @qcode{'tail'} @tab a string specifying the alternative hypothesis ## @end multitable ## @multitable @columnfractions 0.25 0.65 ## @item @qcode{'both'} @tab odds ratio not equal to 1, indicating ## association between two variables (two-tailed test, default) ## @item @qcode{'left'} @tab odds ratio greater than 1 (right-tailed test) ## @item @qcode{'right'} @tab odds ratio is less than 1 (left-tailed test) ## @end multitable ## ## @seealso{crosstab, chi2test, mcnemar_test, ztest2} ## @end deftypefn function [h, p, stats] = fishertest (x, varargin) if (nargin < 1) error ("fishertest: contingency table is missing."); endif if (nargin > 5) error ("fishertest: too many input parameters."); endif ## Check contingency table if (! ismatrix (x) || ndims (x) != 2) error ("fishertest: X must be a 2-dimensional matrix."); endif if (any (x(:) < 0) || any (isnan (x(:))) || any (isinf (x(:))) || ... iscomplex (x) || any (fix (x(:)) != x(:))) error ("fishertest: X must contain only non-negative real integers."); endif if (all (x(:) >= 1e7)) error ("fishertest: cannot handle large entries (>=1e7)."); endif ## Add defaults and parse optional arguments alpha = 0.05; tail = 'both'; if (nargin > 1) params = numel (varargin); if ((params / 2) != fix (params / 2)) error ("fishertest: optional arguments must be in Name-Value pairs.") endif for idx = 1:2:params name = varargin{idx}; value = varargin{idx+1}; switch (lower (name)) case 'alpha' alpha = value; if (! isscalar (alpha) || ! isnumeric (alpha) || ... alpha <= 0 || alpha >= 1) error ("fishertest: invalid value for alpha."); endif case 'tail' tail = value; if (! any (strcmpi (tail, {'both', 'left', 'right'}))) error ("fishertest: invalid value for tail."); endif otherwise error ("fishertest: invalid name for optional arguments."); endswitch endfor endif ## For 2x2 contingency table apply Fisher's exact test ## For larger tables apply the Fisher-Freeman-Halton variance if (all (size (x) == 2)) ## Get margin sums r1 = sum (x(1,:)); r2 = sum (x(2,:)); c1 = sum (x(:,1)); c2 = sum (x(:,2)); sz = sum (x(:)); ## Use try_catch block to avoid memory overflow for large numbers try if (strcmp (tail, 'left')) p = hygecdf (x(1,1), sz, r1, c1); else if (min (r1, c1) <= min (r2, c2)) x11 = (0 : min (r1, c1))'; else x22 = (0 : min (r2, c2))'; x12 = c2 - x22; x11 = r1 - x12; endif switch tail case 'both' p1 = hygepdf (x11, sz, r1, c1); p2 = hygepdf (x(1,1), sz, r1, c1); p = sum (p1(p1 < p2 + 10 * eps (p2))); case 'right' xr = x11(x11 >= x(1,1)); p = sum (hygepdf (xr,sz,r1,c1)); endswitch endif catch error ("fishertest: cannot handle large entries."); end_try_catch ## Return test decision h = (p <= alpha); ## Calculate extra output arguments (if necessary) if (nargout > 2) OR = x(1,1) * x(2,2) / x(1,2) / x(2,1); if (any (x(:) == 0)) CI = [-Inf, Inf]; else SE = sqrt (1 / x(1,1) + 1 / x(1,2) + 1 / x(2,1) + 1 / x(2,2)); LB = OR * exp (-norminv (1 - alpha / 2) * SE); UB = OR * exp (norminv (1 - alpha / 2) * SE); CI = [LB, UB]; endif stats = struct ('OddsRatio', OR, 'ConfidenceInterval', CI); endif else error ("fishertest: the Fisher-Freeman-Halton test is not implemented yet."); endif endfunction %!demo %! ## A Fisher's exact test example %! %! x = [3, 1; 1, 3] %! [h, p, stats] = fishertest (x) ## Test output against MATLAB R2018 %!assert_equal (fishertest ([3, 4; 5, 7]), false); %!assert_equal (isa (fishertest ([3, 4; 5, 7]), 'logical'), true); %!test %! [h, pval, stats] = fishertest ([3, 4; 5, 7]); %! assert_equal (pval, 1, 1e-14); %! assert_equal (stats.OddsRatio, 1.05); %! CI = [0.159222057151289, 6.92429189601808]; %! assert_equal (stats.ConfidenceInterval, CI, 1e-14) %!test %! [h, pval, stats] = fishertest ([3, 4; 5, 0]); %! assert_equal (pval, 0.08080808080808080, 1e-14); %! assert_equal (stats.OddsRatio, 0); %! assert_equal (stats.ConfidenceInterval, [-Inf, Inf]) ## Test input validation %!error fishertest (); %!error fishertest (1, 2, 3, 4, 5, 6); %!error ... %! fishertest (ones (2, 2, 2)); %!error ... %! fishertest ([1, 2; -3, 4]); %!error ... %! fishertest ([1, 2; 3, 4+i]); %!error ... %! fishertest ([1, 2; 3, 4.2]); %!error ... %! fishertest ([NaN, 2; 3, 4]); %!error ... %! fishertest ([1, Inf; 3, 4]); %!error ... %! fishertest (ones (2) * 1e8); %!error ... %! fishertest ([1, 2; 3, 4], 'alpha', 0); %!error ... %! fishertest ([1, 2; 3, 4], 'alpha', 1.2); %!error ... %! fishertest ([1, 2; 3, 4], 'alpha', 'val'); %!error ... %! fishertest ([1, 2; 3, 4], 'tail', 'val'); %!error ... %! fishertest ([1, 2; 3, 4], 'alpha', 0.01, 'tail', 'val'); %!error ... %! fishertest ([1, 2; 3, 4], 'alpha', 0.01, 'badoption', 3); statistics-release-1.9.2/inst/Hypothesis_Testing/friedman.m000066400000000000000000000270111524624707500241240ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} friedman (@var{x}) ## @deftypefnx {statistics} {@var{p} =} friedman (@var{x}, @var{reps}) ## @deftypefnx {statistics} {@var{p} =} friedman (@var{x}, @var{reps}, @var{displayopt}) ## @deftypefnx {statistics} {[@var{p}, @var{tbl}] =} friedman (@dots{}) ## @deftypefnx {statistics} {[@var{p}, @var{tbl}, @var{stats}] =} friedman (@dots{}) ## ## Performs the nonparametric Friedman's test to compare column effects in a ## two-way layout. @qcode{friedman} tests the null hypothesis that the column ## effects are all the same against the alternative that they are not all the ## same. ## ## @qcode{friedman} requires one up to three input arguments: ## ## @itemize ## @item ## @var{x} contains the data and it must be a matrix of at least two columns and ## two rows. ## @item ## @var{reps} is the number of replicates for each combination of factor groups. ## If not provided, no replicates are assumed. ## @item ## @var{displayopt} is an optional parameter for displaying the Friedman's ANOVA ## table, when it is 'on' (default) and suppressing the display when it is ## 'off'. MATLAB renders the table in a figure window; this package prints it ## to the standard output, as @code{anova2} does. ## @end itemize ## ## @qcode{friedman} returns up to three output arguments: ## ## @itemize ## @item ## @var{p} is the p-value of the null hypothesis that all group means are equal. ## @item ## @var{tbl} is a cell array containing the results of the Friedman's test in ## ANOVA table format. Its first row holds the column labels Source, SS, df, ## MS, Chi-sq and Prob>Chi-sq, followed by a row per source: Columns, ## [Interaction], Error and Total. An entry that does not apply to a row, such ## as the chi-square statistic of the Error row, is empty. ## @item ## @var{stats} is a structure containing statistics useful for performing a ## multiple comparison of medians with the MULTCOMPARE function. ## @end itemize ## ## If friedman is called without any output arguments, then it prints the ## results in a Friedman's ANOVA table to the standard output. ## ## Examples: ## ## @example ## load popcorn; ## friedman (popcorn, 3); ## @end example ## ## ## @example ## [p, anovatab, stats] = friedman (popcorn, 3); ## disp (p); ## @end example ## ## @seealso{anova2, kruskalwallis, multcompare} ## @end deftypefn function [p, tbl, stats] = friedman (x, reps, displayopt) ## Check for valid number of input arguments narginchk (1, 3); ## Check for NaN values in X if (any (isnan (x(:)))) error ("friedman: NaN values in input are not allowed."); endif ## Add defaults if (nargin == 1) reps = 1; endif ## Check for correct size of input matrix [r, c] = size (x); if (r <= 1 || c <= 1) error ("friedman: bad size of input matrix."); endif if (reps > 1) r = r / reps; if (floor (r) != r) error ("friedman: repetitions and observations do not match."); endif endif ## Check for displayopt. It is 'on' by default, as it is in MATLAB and in ## ANOVA1 and ANOVA2. disp_table = true; if (nargin == 3) if (! any (strcmp (displayopt, {'on', 'off'}))) error ("friedman: displayopt must be either 'on' or 'off'."); endif disp_table = strcmp (displayopt, 'on'); endif ## Prepare a matrix of ranks. Replicates are ranked together. m = x; sum_R = 0; for j = 1:r jrows = reps * (j - 1) + (1:reps); v = x(jrows,:); [R, tieadj] = tiedrank (v(:)); m(jrows,:) = reshape (R, reps, c); sum_R = sum_R + 2 * tieadj; endfor ## Perform 2-way anova silently [p0, anova_table] = anova2 (m, reps, 'off'); ## Compute Friedman test statistic and p-value chi_r = anova_table{2,2}; sigmasq = c * reps * (reps * c + 1) / 12; if (sum_R > 0) sigmasq = sigmasq - sum_R / (12 * r * (reps * c - 1)); endif if (chi_r > 0) chi_r = chi_r / sigmasq; endif p = 1 - chi2cdf (chi_r, c - 1); ## Create ANOVA table data for output if (reps > 1) ## When there are replicates, include interaction row ## ANOVA2 reports Columns, Rows, Interaction, Error and Total; the ## Friedman table carries the interaction, row 4, and not the block ## effect in row 3. source_list = {'Columns'; 'Interaction'; 'Error'; 'Total'}; ss_list = [anova_table{2,2}; anova_table{4,2}; ... anova_table{end - 1,2}; anova_table{end,2}]; df_list = [anova_table{2,3}; anova_table{4,3}; ... anova_table{end - 1,3}; anova_table{end,3}]; ms_list = {anova_table{2,4}; anova_table{4,4}; ... anova_table{end - 1,4}; []}; chi_sq_list = {chi_r; []; []; []}; prob_list = {p; []; []; []}; else ## When there are no replicates (reps = 1), exclude interaction row source_list = {'Columns'; 'Error'; 'Total'}; ss_list = [anova_table{2,2}; anova_table{end - 1,2}; anova_table{end,2}]; df_list = [anova_table{2,3}; anova_table{end - 1,3}; anova_table{end,3}]; ms_list = {anova_table{2,4}; anova_table{end - 1,4}; []}; chi_sq_list = {chi_r; []; []}; prob_list = {p; []; []}; endif ## Create the output table as a cell array with a header row, as MATLAB ## does. Entries that do not apply to a row are empty, not zero, and the ## column labels carry the characters a table variable name cannot hold. tbl = [{'Source', 'SS', 'df', 'MS', 'Chi-sq', 'Prob>Chi-sq'}; ... [source_list, num2cell(ss_list), num2cell(df_list), ... ms_list, chi_sq_list, prob_list]]; ## Create stats structure (if requested) for MULTCOMPARE if (nargout > 2) stats.source = 'friedman'; stats.n = r; stats.meanranks = mean (m); stats.sigma = sqrt (sigmasq); endif ## Display ANOVA table if opted or no output argument is requested. MATLAB ## renders it in a figure window; this package prints it, as ANOVA2 does. if (nargout == 0 || disp_table) print_friedman_table (tbl); endif endfunction ## Print the ANOVA table the way ANOVA2 prints its own: one header line, then ## a row per source, with an empty field where the statistic does not apply. function print_friedman_table (tbl) printf ("\n"); printf ("%-14s %10s %6s %10s %10s %12s\n", tbl{1,:}); for i = 2:rows (tbl) printf ("%-14s", tbl{i,1}); for j = 2:columns (tbl) v = tbl{i,j}; if (isempty (v)) printf (" %10s", ""); elseif (j == 3) printf (" %6d", v); elseif (j == 6) printf (" %12.4f", v); else printf (" %10.4f", v); endif endfor printf ("\n"); endfor printf ("\n"); endfunction %!demo %! load popcorn; %! friedman (popcorn, 3); %!demo %! load popcorn; %! [p, atab] = friedman (popcorn, 3, 'off'); %! disp (p); ## testing against popcorn data and results from Matlab %!test %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! [p, atab] = friedman (popcorn, 3, 'off'); %! assert_equal (p, 0.001028853354594794, 1e-14); %! assert_equal (atab(1,:), {'Source', 'SS', 'df', 'MS', 'Chi-sq', 'Prob>Chi-sq'}); %! assert_equal (atab{2,1}, 'Columns'); %! assert_equal (atab{2,2}, 99.75, 1e-14); %! assert_equal (atab{2,3}, 2, 0); %! assert_equal (atab{2,4}, 49.875, 1e-14); %! assert_equal (atab{2,5}, 13.75862068965517, 1e-14); %! assert_equal (atab{2,6}, 0.001028853354594794, 1e-14); %!test %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! [p, atab, stats] = friedman (popcorn, 3, 'off'); %! assert_equal (atab{end,1}, 'Total'); %! assert_equal (atab{end,2}, 116, 0); %! assert_equal (atab{end,3}, 17, 0); %! assert_equal (stats.source, 'friedman'); %! assert_equal (stats.n, 2); %! assert_equal (stats.meanranks, [8, 4.75, 2.25], 0); %! assert_equal (stats.sigma, 2.692582403567252, 1e-14); %!test %! ## every row of the table, not only Columns and Total %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! [p, atab] = friedman (popcorn, 3, 'off'); %! assert_equal (atab{3,1}, 'Interaction'); %! assert_equal (atab{3,2}, 0.083333333333258, 1e-12); %! assert_equal (atab{3,3}, 2, 0); %! assert_equal (atab{3,4}, 0.041666666666629, 1e-12); %! assert_equal (atab{4,1}, 'Error'); %! assert_equal (atab{4,2}, 16.166666666666742, 1e-12); %! assert_equal (atab{4,3}, 12, 0); %! assert_equal (atab{4,4}, 1.347222222222229, 1e-12); %!test %! ## the interaction carries (c-1)(r-1) degrees of freedom %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! [p, atab] = friedman (popcorn, 3, 'off'); %! c = 3; r = 2; %! assert_equal (atab{3,3}, (c - 1) * (r - 1), 0); %! assert_equal (atab{2,3} + atab{3,3} + atab{4,3} < atab{5,3}, true); %!test %! ## without replicates the table has no interaction row %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! [p, atab] = friedman (popcorn, 1, 'off'); %! assert_equal (atab(:,1), {'Source'; 'Columns'; 'Error'; 'Total'}); %! assert_equal (atab{2,2}, 12, 1e-12); %! assert_equal (atab{3,2}, 0, 1e-12); %! assert_equal (atab{3,3}, 10, 0); %! assert_equal (atab{4,3}, 17, 0); %!test %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! s = evalc ('[p, atab] = friedman (popcorn, 3, "off");'); %! assert_equal (isempty (strtrim (s)), true); %!test %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! s = evalc ('[p, atab] = friedman (popcorn, 3, "on");'); %! assert_equal (! isempty (strtrim (s)), true); %!test %! ## the table is displayed by default, as in MATLAB and in anova1 and anova2 %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! s = evalc ('[p, atab] = friedman (popcorn, 3);'); %! assert_equal (! isempty (strtrim (s)), true); %!test %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! [p, atab] = friedman (popcorn, 3, 'off'); %! assert_equal (size (atab), [5, 6], 0); %! assert_equal (iscell (atab), true); %! assert_equal (isempty (atab{end,4}), true); %!test %! x = [1, 2, 3; 2, 1, 3; 3, 2, 1]; %! [p, atab] = friedman (x, 1, 'off'); %! assert_equal (size (atab), [4, 6], 0); %! assert_equal (atab{3,1}, 'Error'); %! assert_equal (isempty (atab{2,5}), false); %!error ... %! friedman ([5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; 6.5, 5.0, 4.0; ... %! 7.0, 5.5, 5.0; 7.0, 5.0, 4.5], 3, 'invalid_displayopt'); %!error ... %! friedman ([1, 2; NaN, 4]); %!error ... %! friedman ([1,2; 3,4; 5,6], 2); statistics-release-1.9.2/inst/Hypothesis_Testing/hotelling_t2test.m000066400000000000000000000155031524624707500256340ustar00rootroot00000000000000## Copyright (C) 1996-2017 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{h}, @var{pval}, @var{stats}] =} hotelling_t2test (@var{x}) ## @deftypefnx {statistics} {[@dots{}] =} hotelling_t2test (@var{x}, @var{m}) ## @deftypefnx {statistics} {[@dots{}] =} hotelling_t2test (@var{x}, @var{y}) ## @deftypefnx {statistics} {[@dots{}] =} hotelling_t2test (@var{x}, @var{m}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@dots{}] =} hotelling_t2test (@var{x}, @var{y}, @var{Name}, @var{Value}) ## ## Compute Hotelling's T^2 ("T-squared") test for a single sample or two ## dependent samples (paired-samples). ## ## For a sample @var{x} from a multivariate normal distribution with unknown ## mean and covariance matrix, test the null hypothesis that ## @code{mean (@var{x}) == @var{m}}. ## ## For two dependent samples @var{x} and @var{y} from a multivariate normal ## distributions with unknown means and covariance matrices, test the null ## hypothesis that @code{mean (@var{x} - @var{y}) == 0}. ## ## @qcode{hotelling_t2test} treats NaNs as missing values, and ignores the ## corresponding rows. ## ## Name-Value pair arguments can be used to set statistical significance. ## @qcode{'alpha'} can be used to specify the significance level of the test ## (the default value is 0.05). ## ## If @var{h} is 1 the null hypothesis is rejected, meaning that the tested ## sample does not come from a multivariate distribution with mean @var{m}, or ## in case of two dependent samples that they do not come from the same ## multivariate distribution. If @var{h} is 0, then the null hypothesis cannot ## be rejected and it can be assumed that it holds true. ## ## The p-value of the test is returned in @var{pval}. ## ## @var{stats} is a structure containing the value of the Hotelling's @math{T^2} ## test statistic in the field "Tsq", and the degrees of freedom of the F ## distribution in the fields "df1" and "df2". Under the null hypothesis, ## @math{(n-p) T^2 / (p(n-1))} has an F distribution with @math{p} and ## @math{n-p} degrees of freedom, where @math{n} and @math{p} are the ## numbers of samples and variables, respectively. ## ## @seealso{hotelling_t2test2} ## @end deftypefn function [h, pval, stats] = hotelling_t2test (x, my, varargin) ## Check for minimum number of input arguments if (nargin < 1) print_usage (); endif ## Check X being a valid data set if (isscalar (x) || ndims (x) > 2) error ("hotelling_t2test: X must be a vector or a 2D matrix."); endif ## Set default arguments alpha = 0.05; ## Fix MY when X is a single input argument if (nargin == 1) if (isvector (x)) my = 0; elseif (ismatrix (x)) [n, p] = size (x); my = zeros (1, p); endif endif ## When X and MY are of equal size, then assume paired-sample if (isequal (size (x), size (my))) x = x - my; if (isvector (x)) my = 0; elseif (ismatrix (x)) [n, p] = size (x); my = zeros (1, p); endif endif ## Remove rows containing any NaNs x = rmmissing (x); ## Check additional options i = 1; while (i <= length (varargin)) switch lower (varargin{i}) case 'alpha' i = i + 1; alpha = varargin{i}; ## Check for valid alpha if (! isscalar (alpha) || ! isnumeric (alpha) || ... alpha <= 0 || alpha >= 1) error ("hotelling_t2test: invalid value for alpha."); endif otherwise error ("hotelling_t2test: invalid Name argument."); endswitch i = i + 1; endwhile ## Conditional error checking for X being a vector or matrix if (isvector (x)) if (! isscalar (my)) error ("hotelling_t2test: if X is a vector, M must be a scalar."); endif n = length (x); p = 1; elseif (ismatrix (x)) [n, p] = size (x); if (n <= p) error ("hotelling_t2test: X must have more rows than columns."); endif if (isvector (my) && length (my) == p) my = reshape (my, 1, p); else error (strcat ("hotelling_t2test: if X is a matrix, M must be a", ... " vector of length equal to the columns of X.")); endif endif ## Calculate the necessary statistics d = mean (x) - my; stats.Tsq = n * d * (cov (x) \ d'); stats.df1 = p; stats.df2 = n - p; pval = 1 - fcdf ((n-p) * stats.Tsq / (p * (n-1)), stats.df1, stats.df2); ## Determine the test outcome ## MATLAB returns this a double instead of a logical array h = double (pval < alpha); endfunction ## Test input validation %!error hotelling_t2test (); %!error ... %! hotelling_t2test (1); %!error ... %! hotelling_t2test (ones (2,2,2)); %!error ... %! hotelling_t2test (ones (20,2), [0, 0], 'alpha', 1); %!error ... %! hotelling_t2test (ones (20,2), [0, 0], 'alpha', -0.2); %!error ... %! hotelling_t2test (ones (20,2), [0, 0], 'alpha', 'a'); %!error ... %! hotelling_t2test (ones (20,2), [0, 0], 'alpha', [0.01, 0.05]); %!error ... %! hotelling_t2test (ones (20,2), [0, 0], 'name', 0.01); %!error ... %! hotelling_t2test (ones (20,1), [0, 0]); %!error ... %! hotelling_t2test (ones (4,5), [0, 0, 0, 0, 0]); %!error ... %! hotelling_t2test (ones (20,5), [0, 0, 0, 0]); ## Test results %!test %! randn ('seed', 1); %! x = randn (50000, 5); %! [h, pval, stats] = hotelling_t2test (x); %! assert_equal (h, 0); %! assert_equal (stats.df1, 5); %! assert_equal (stats.df2, 49995); %!test %! randn ('seed', 1); %! x = randn (50000, 5); %! [h, pval, stats] = hotelling_t2test (x, ones (1, 5) * 10); %! assert_equal (h, 1); %! assert_equal (stats.df1, 5); %! assert_equal (stats.df2, 49995); statistics-release-1.9.2/inst/Hypothesis_Testing/hotelling_t2test2.m000066400000000000000000000147051524624707500257210ustar00rootroot00000000000000## Copyright (C) 1996-2017 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{h}, @var{pval}, @var{stats}] =} hotelling_t2test2 (@var{x}, @var{y}) ## @deftypefnx {statistics} {[@dots{}] =} hotelling_t2test2 (@var{x}, @var{y}, @var{Name}, @var{Value}) ## ## Compute Hotelling's T^2 ("T-squared") test for two independent samples. ## ## For two samples @var{x} from multivariate normal distributions with ## the same number of variables (columns), unknown means and unknown ## equal covariance matrices, test the null hypothesis ## @code{mean (@var{x}) == mean (@var{y})}. ## ## @qcode{hotelling_t2test2} treats NaNs as missing values, and ignores the ## corresponding rows for each sample independently. ## ## Name-Value pair arguments can be used to set statistical significance. ## @qcode{'alpha'} can be used to specify the significance level of the test ## (the default value is 0.05). ## ## If @var{h} is 1 the null hypothesis is rejected, meaning that the tested ## samples do not come from the same multivariate distribution. If @var{h} is ## 0, then the null hypothesis cannot be rejected and it can be assumed that ## both samples come from the same multivariate distribution. ## ## The p-value of the test is returned in @var{pval}. ## ## @var{stats} is a structure containing the value of the Hotelling's @math{T^2} ## test statistic in the field "Tsq", and the degrees of freedom of the F ## distribution in the fields "df1" and "df2". Under the null hypothesis, ## @tex ## $$ ## {(n_x+n_y-p-1) T^2 \over p(n_x+n_y-2)} ## $$ ## @end tex ## @ifnottex ## ## @example ## (n_x+n_y-p-1) T^2 / (p(n_x+n_y-2)) ## @end example ## ## @end ifnottex ## @noindent ## has an F distribution with @math{p} and @math{n_x+n_y-p-1} degrees of ## freedom, where @math{n_x} and @math{n_y} are the sample sizes and ## @math{p} is the number of variables. ## ## @seealso{hotelling_t2test} ## @end deftypefn function [h, pval, stats] = hotelling_t2test2 (x, y, varargin) ## Check for minimum number of input arguments if (nargin < 2) print_usage (); endif ## Check X being a valid data set if (isscalar (x) || ndims (x) > 2) error ("hotelling_t2test2: X must be a vector or a 2D matrix."); endif ## Check Y being a valid data set if (isscalar (y) || ndims (y) > 2) error ("hotelling_t2test2: Y must be a vector or a 2D matrix."); endif ## Set default arguments alpha = 0.05; ## Remove rows containing any NaNs x = rmmissing (x); y = rmmissing (y); ## Check additional options i = 1; while (i <= length (varargin)) switch lower (varargin{i}) case 'alpha' i = i + 1; alpha = varargin{i}; ## Check for valid alpha if (! isscalar (alpha) || ! isnumeric (alpha) || ... alpha <= 0 || alpha >= 1) error ("hotelling_t2test2: invalid value for alpha."); endif otherwise error ("hotelling_t2test2: invalid Name argument."); endswitch i = i + 1; endwhile ## Conditional error checking for X being a vector or matrix if (isvector (x)) n_x = length (x); if (! isvector (y)) error ("hotelling_t2test2: if X is a vector, Y must also be a vector."); else n_y = length (y); p = 1; endif elseif (ismatrix (x)) [n_x, p] = size (x); [n_y, q] = size (y); if (p != q) error (strcat ("hotelling_t2test2: X and Y must have the same", ... " number of columns.")); endif endif ## Calculate the necessary statistics d = mean (x) - mean (y); S = ((n_x - 1) * cov (x) + (n_y - 1) * cov (y)) / (n_x + n_y - 2); stats.Tsq = (n_x * n_y / (n_x + n_y)) * d * (S \ d'); stats.df1 = p; stats.df2 = n_x + n_y - p - 1; pval = 1 - fcdf ((n_x + n_y - p - 1) * stats.Tsq / (p * (n_x + n_y - 2)), ... stats.df1, stats.df2); ## Determine the test outcome ## MATLAB returns this a double instead of a logical array h = double (pval < alpha); endfunction ## Test input validation %!error hotelling_t2test2 (); %!error ... %! hotelling_t2test2 ([2, 3, 4, 5, 6]); %!error ... %! hotelling_t2test2 (1, [2, 3, 4, 5, 6]); %!error ... %! hotelling_t2test2 (ones (2,2,2), [2, 3, 4, 5, 6]); %!error ... %! hotelling_t2test2 ([2, 3, 4, 5, 6], 2); %!error ... %! hotelling_t2test2 ([2, 3, 4, 5, 6], ones (2,2,2)); %!error ... %! hotelling_t2test2 (ones (20,2), ones (20,2), 'alpha', 1); %!error ... %! hotelling_t2test2 (ones (20,2), ones (20,2), 'alpha', -0.2); %!error ... %! hotelling_t2test2 (ones (20,2), ones (20,2), 'alpha', 'a'); %!error ... %! hotelling_t2test2 (ones (20,2), ones (20,2), 'alpha', [0.01, 0.05]); %!error ... %! hotelling_t2test2 (ones (20,2), ones (20,2), 'name', 0.01); %!error ... %! hotelling_t2test2 (ones (20,1), ones (20,2)); %!error ... %! hotelling_t2test2 (ones (20,2), ones (25,3)); ## Test results %!test %! randn ('seed', 1); %! x1 = randn (60000, 5); %! randn ('seed', 5); %! x2 = randn (30000, 5); %! [h, pval, stats] = hotelling_t2test2 (x1, x2); %! assert_equal (h, 0); %! assert_equal (stats.df1, 5); %! assert_equal (stats.df2, 89994); statistics-release-1.9.2/inst/Hypothesis_Testing/jbtest.m000066400000000000000000000354201524624707500236350ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} jbtest (@var{x}) ## @deftypefnx {statistics} {@var{h} =} jbtest (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {@var{h} =} jbtest (@var{x}, @var{alpha}, @var{mctol}) ## @deftypefnx {statistics} {[@var{h}, @var{p}] =} jbtest (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{p}, @var{jbstat}, @var{critval}] =} jbtest (@dots{}) ## ## Jarque-Bera hypothesis test of composite normality. ## ## @code{@var{h} = jbtest (@var{x})} performs the Jarque-Bera test of the null ## hypothesis that the sample in the vector @var{x} comes from a normal ## distribution with unknown mean and variance, against the alternative that it ## does not come from a normal distribution. The result @var{h} is 1 if the ## test rejects the null hypothesis at the 5% significance level, and 0 ## otherwise. @var{x} must be a vector of real values; @qcode{NaN} values are ## treated as missing and removed. ## ## The Jarque-Bera test statistic is ## @tex ## $$ JB = \frac{n}{6} \left( s^2 + \frac{(k-3)^2}{4} \right), $$ ## ## @end tex ## @ifnottex ## @code{JB = (n / 6) * (s^2 + (k - 3)^2 / 4)}, ## @end ifnottex ## where @math{n} is the sample size, @math{s} is the sample skewness, and ## @math{k} is the sample kurtosis. Under the null hypothesis it is ## asymptotically chi-square distributed with two degrees of freedom. ## ## @code{@var{h} = jbtest (@var{x}, @var{alpha})} performs the test at the ## significance level @var{alpha}, a scalar in the range @math{(0,1)}. The ## default is @math{0.05}. ## ## @code{@var{h} = jbtest (@var{x}, @var{alpha}, @var{mctol})} computes a ## Monte-Carlo approximation of the p-value instead of interpolating the ## embedded table. @var{mctol} is the maximum Monte-Carlo standard ## error allowed for the p-value; the number of simulated samples is chosen ## accordingly. Use this for small samples, where the chi-square approximation ## is inaccurate, or for significance levels outside @math{[0.001, 0.5]}. ## ## @code{[@var{h}, @var{p}] = jbtest (@dots{})} also returns the p-value ## @var{p} of the test. @var{p} is clamped to the tabulated range ## @math{[0.001, 0.5]}, as MATLAB clamps it, and a warning is issued when the ## value lies outside that range. The warning is an addition here: MATLAB ## clamps silently, so a p-value reported as @math{0.001} or @math{0.5} there ## may be a bound rather than an estimate, with nothing to say so. ## ## @code{[@var{h}, @var{p}, @var{jbstat}, @var{critval}] = jbtest (@dots{})} also ## returns the test statistic @var{jbstat} and the critical value @var{critval} ## at significance level @var{alpha}. The null hypothesis is rejected when ## @code{@var{jbstat} > @var{critval}}. ## ## Note: for @math{n \le 2000} the p-value and critical value are obtained by ## interpolating an embedded critical-value table (the same approach MATLAB ## uses); for larger samples the large-sample chi-square approximation with two ## degrees of freedom is used instead. The embedded table was generated here by ## Monte-Carlo simulation, so it is itself an estimate of the true null ## quantiles. MATLAB's table is likewise a Monte-Carlo estimate but from a ## different simulation, so the two tables agree only to about two decimal ## places. As a result the reported p-value and critical value, and (in a ## narrow band of statistic values around the critical value) the test decision ## @var{h}, can differ slightly from MATLAB in edge cases. These differences are ## an unavoidable consequence of the Monte-Carlo origin of both tables, not a ## difference in method. Supply @var{mctol} for a direct Monte-Carlo p-value. ## ## @seealso{kstest, adtest, lillietest} ## @end deftypefn function [h, p, jbstat, critval] = jbtest (x, alpha, mctol) ## Check input arguments if (nargin < 1) print_usage (); endif if (! (isnumeric (x) && isreal (x) && isvector (x))) error ("jbtest: X must be a vector of real values."); endif ## Remove missing values and check sample size x = x(! isnan (x)); x = x(:); n = numel (x); if (n < 2) error ("jbtest: X must contain at least two non-missing values."); endif if (nargin < 2 || isempty (alpha)) alpha = 0.05; endif if (! (isnumeric (alpha) && isscalar (alpha) && isreal (alpha) ... && alpha > 0 && alpha < 1)) error ("jbtest: ALPHA must be a scalar in the range (0,1)."); endif domc = (nargin > 2 && ! isempty (mctol)); if (domc && ! (isnumeric (mctol) && isscalar (mctol) && isreal (mctol) ... && mctol > 0)) error ("jbtest: MCTOL must be a positive scalar."); endif ## Jarque-Bera test statistic (biased sample skewness and kurtosis) s = skewness (x); k = kurtosis (x); jbstat = (n / 6) * (s ^ 2 + (k - 3) ^ 2 / 4); if (! domc) [tsizes, talphas, tcv] = jbtest_table_ (); if (n > tsizes(end)) ## Beyond the tabulated sample sizes: large-sample chi-square with two ## degrees of freedom p = 1 - chi2cdf (jbstat, 2); critval = chi2inv (1 - alpha, 2); if (p < 0.001) warning ("jbtest:pTooSmall", ... "jbtest: P is less than the smallest tabulated value; returning 0.001."); p = 0.001; elseif (p > 0.5) warning ("jbtest:pTooBig", ... "jbtest: P is greater than the largest tabulated value; returning 0.5."); p = 0.5; endif else ## Interpolate the embedded Monte-Carlo critical-value table. Critical ## values as a function of the significance level at this sample size: nn = max (n, tsizes(1)); cvn = interp1 (fliplr (1 ./ tsizes), flipud (tcv), 1 / nn, "linear"); ## Critical value at the requested significance level (interpolated over ## the log of the tabulated levels, as in adtest) pp = pchip (log (talphas), cvn); aclamp = min (max (alpha, talphas(1)), talphas(end)); critval = ppval (pp, log (aclamp)); if (alpha < talphas(1) || alpha > talphas(end)) warning ("jbtest:alphaRange", ... "jbtest: ALPHA is outside the tabulated range [0.001, 0.5]."); endif ## p-value by inverse interpolation into the table if (jbstat > cvn(1)) warning ("jbtest:pTooSmall", ... "jbtest: P is less than the smallest tabulated value; returning 0.001."); p = talphas(1); elseif (jbstat < cvn(end)) warning ("jbtest:pTooBig", ... "jbtest: P is greater than the largest tabulated value; returning 0.5."); p = talphas(end); else i = find (jbstat > cvn, 1, "first"); logp = fzero (@(x) ppval (pp, x) - jbstat, log (talphas([i-1, i]))); p = exp (logp); endif endif else ## Monte-Carlo approximation of the null distribution of the statistic reps = max (1000, ceil (0.25 / mctol ^ 2)); jbsim = jbtest_simulate_ (n, reps); p = (1 + sum (jbsim >= jbstat)) / (reps + 1); critval = quantile (jbsim, 1 - alpha); endif h = double (jbstat > critval); endfunction ## Simulate REPS values of the Jarque-Bera statistic for standard normal samples ## of size N, evaluated in chunks to bound memory use. function jbsim = jbtest_simulate_ (n, reps) jbsim = zeros (reps, 1); chunk = max (1, floor (1e6 / n)); done = 0; while (done < reps) m = min (chunk, reps - done); z = randn (n, m); s = skewness (z, 1, 1); ## column-wise, biased k = kurtosis (z, 1, 1); jbsim(done+1:done+m) = (n / 6) .* (s(:) .^ 2 + (k(:) - 3) .^ 2 / 4); done += m; endwhile endfunction ## Embedded Jarque-Bera critical-value table, generated by Monte-Carlo ## simulation (1e6 standard-normal samples per sample size). Row i, column j is ## the upper @code{1 - ALPHAS(j)} quantile of the statistic for a sample of size ## @code{SIZES(i)}. Values agree with MATLAB's table to about two decimals; ## both are Monte-Carlo estimates of the same quantiles. function [sizes, alphas, cv] = jbtest_table_ () sizes = [4 5 6 7 8 9 10 11 12 13 14 15 16 18 20 22 25 28 32 37 43 50 ... 60 75 90 110 140 180 250 350 500 750 1000 2000]; alphas = [0.001 0.0025 0.005 0.01 0.025 0.05 0.075 0.1 0.15 0.2 0.25 ... 0.3 0.4 0.5]; cv = [ ... 0.9605 0.9570 0.9509 0.9393 0.9056 0.8522 0.8015 0.7554 0.6729 0.6307 0.5950 0.5630 0.5102 0.4739; ... 1.8291 1.7788 1.7186 1.6283 1.4350 1.2179 1.0626 0.9425 0.7939 0.7298 0.6877 0.6512 0.5896 0.5289; ... 3.1838 2.9786 2.7665 2.4815 2.0002 1.5549 1.2841 1.1007 0.9195 0.8301 0.7709 0.7229 0.6423 0.5735; ... 4.9350 4.4225 3.9482 3.3862 2.5284 1.8493 1.4757 1.2531 1.0229 0.9146 0.8430 0.7859 0.6924 0.6091; ... 6.9241 5.9457 5.1255 4.2348 2.9843 2.0890 1.6446 1.3906 1.1179 0.9911 0.9074 0.8423 0.7352 0.6418; ... 8.8975 7.4734 6.2462 4.9863 3.3891 2.3208 1.8140 1.5169 1.2052 1.0616 0.9675 0.8934 0.7733 0.6699; ... 10.9383 8.9031 7.3105 5.6927 3.7457 2.5276 1.9602 1.6254 1.2823 1.1240 1.0198 0.9389 0.8076 0.6943; ... 12.9458 10.2945 8.2830 6.3396 4.0810 2.7105 2.0797 1.7233 1.3544 1.1818 1.0692 0.9812 0.8393 0.7181; ... 14.7456 11.5915 9.1227 6.8706 4.3397 2.8724 2.2000 1.8123 1.4194 1.2347 1.1137 1.0193 0.8678 0.7392; ... 16.6856 12.6696 9.8759 7.3578 4.6172 3.0267 2.3061 1.8960 1.4816 1.2858 1.1563 1.0558 0.8942 0.7574; ... 18.1583 13.7946 10.6398 7.8586 4.8579 3.1663 2.4152 1.9802 1.5424 1.3333 1.1966 1.0897 0.9196 0.7751; ... 19.3397 14.5252 11.1616 8.1792 5.0433 3.2907 2.5011 2.0505 1.5970 1.3771 1.2321 1.1202 0.9407 0.7903; ... 21.1047 15.4426 11.8050 8.5872 5.2432 3.3999 2.5948 2.1230 1.6496 1.4209 1.2700 1.1521 0.9647 0.8075; ... 22.8485 16.7542 12.7010 9.1686 5.6093 3.6350 2.7427 2.2420 1.7393 1.4936 1.3306 1.2031 1.0012 0.8325; ... 25.0858 18.2343 13.6105 9.7801 5.9031 3.7947 2.8745 2.3471 1.8213 1.5622 1.3877 1.2519 1.0360 0.8567; ... 26.4913 18.9880 14.0979 10.2355 6.1599 3.9611 3.0039 2.4524 1.8990 1.6255 1.4404 1.2959 1.0674 0.8788; ... 28.4147 20.0407 14.9295 10.7391 6.4411 4.1481 3.1440 2.5749 1.9973 1.7047 1.5056 1.3506 1.1061 0.9045; ... 29.8195 21.0006 15.4959 11.0930 6.6884 4.2933 3.2540 2.6754 2.0865 1.7778 1.5680 1.4044 1.1450 0.9309; ... 31.2255 21.8267 16.1070 11.5171 6.9558 4.4934 3.4104 2.8046 2.1884 1.8618 1.6391 1.4628 1.1851 0.9590; ... 32.0166 22.4555 16.5364 11.9241 7.1674 4.6567 3.5449 2.9289 2.2966 1.9503 1.7104 1.5225 1.2280 0.9883; ... 33.2009 23.1281 17.0640 12.1954 7.3963 4.8309 3.6920 3.0593 2.4063 2.0435 1.7879 1.5880 1.2719 1.0165; ... 33.4237 23.2982 17.1731 12.3563 7.5793 4.9742 3.8217 3.1810 2.5093 2.1293 1.8594 1.6467 1.3141 1.0447; ... 33.5896 23.4273 17.3344 12.5402 7.7329 5.1294 3.9730 3.3281 2.6337 2.2310 1.9437 1.7175 1.3627 1.0782; ... 33.1832 23.4014 17.3724 12.6873 7.8909 5.2810 4.1324 3.4803 2.7681 2.3450 2.0372 1.7951 1.4141 1.1114; ... 32.1951 22.8349 17.1964 12.5965 7.9188 5.3719 4.2382 3.5961 2.8783 2.4343 2.1145 1.8597 1.4594 1.1399; ... 30.7519 22.1102 16.7325 12.3753 7.9505 5.4573 4.3608 3.7217 2.9824 2.5237 2.1883 1.9204 1.5003 1.1689; ... 30.1986 21.4958 16.4958 12.2626 7.9444 5.5527 4.4764 3.8553 3.1041 2.6213 2.2712 1.9881 1.5454 1.1974; ... 27.6248 20.4179 15.6934 11.9179 7.9354 5.6402 4.6136 3.9895 3.2225 2.7224 2.3508 2.0582 1.5936 1.2290; ... 26.3970 19.3670 15.1261 11.5818 7.8954 5.7581 4.7527 4.1311 3.3428 2.8256 2.4425 2.1320 1.6437 1.2631; ... 23.9042 17.9448 14.3083 11.1508 7.7814 5.7994 4.8474 4.2331 3.4422 2.9143 2.5134 2.1895 1.6831 1.2874; ... 21.3995 16.6461 13.4998 10.7451 7.6847 5.8609 4.9439 4.3331 3.5378 2.9931 2.5803 2.2471 1.7199 1.3113; ... 19.8917 15.5804 12.8247 10.3682 7.6270 5.9036 5.0108 4.4173 3.6127 3.0576 2.6380 2.2946 1.7544 1.3325; ... 18.8596 15.0900 12.4719 10.1555 7.5849 5.9373 5.0527 4.4619 3.6487 3.0892 2.6623 2.3156 1.7676 1.3407; ... 16.3822 13.5505 11.5058 9.6830 7.4839 5.9564 5.1088 4.5241 3.7189 3.1529 2.7147 2.3586 1.7953 1.3615]; endfunction %!demo %! ## Test whether a sample departs from normality %! x = [1 2 3 4 5 6 7 8 9 100]; # last value is an outlier %! [h, p, jbstat] = jbtest (x) ## Test output against known values (chi-square approximation) %!test %! warning ("off", "jbtest:pTooBig", "local"); %! x = [1 2 3 4 5 6 7 8 9 10]; %! [h, p, jbstat, cv] = jbtest (x); %! assert_equal (h, 0); %! assert_equal (jbstat, 0.624487, 1e-5); # skewness 0, kurtosis 1.7758 %! assert_equal (cv, 2.5276, 1e-4); # tabulated critical value, n=10, a=0.05 %! assert_equal (p, 0.5, 1e-12); # clamped to the tabulated maximum ## A very normal-looking (platykurtic) sample warns that p exceeds 0.5 %!warning ... %! jbtest (1:10); %!test # a strongly non-normal sample is rejected %! x = [zeros(1, 20), 100]; %! h = jbtest (x); %! assert_equal (h, 1); %!test # NaNs are removed %! assert_equal (jbtest ([1 2 3 4 5 6 7 8 9 10, NaN]), jbtest (1:10)); %!test # alpha controls the critical value %! x = randn (1, 50); %! [~, ~, ~, cv1] = jbtest (x, 0.05); %! [~, ~, ~, cv2] = jbtest (x, 0.01); %! assert_equal (cv2 > cv1, true); %!test # Monte-Carlo p-value runs and lies in (0,1] %! x = [1 2 3 4 5 6 7 8 9 10]; %! [h, p] = jbtest (x, 0.05, 0.05); %! assert_equal (p > 0 && p <= 1, true); ## Test input validation %!error jbtest () %!error jbtest (ones (3, 3)) %!error jbtest ({1, 2, 3}) %!error jbtest ([1 2 3i]) %!error jbtest (5) %!error jbtest (1:10, 0) %!error jbtest (1:10, 1) %!error jbtest (1:10, [0.1 0.2]) %!error jbtest (1:10, 0.05, -1) statistics-release-1.9.2/inst/Hypothesis_Testing/kruskalwallis.m000066400000000000000000000246661524624707500252440ustar00rootroot00000000000000## Copyright (C) 2021 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} kruskalwallis (@var{x}) ## @deftypefnx {statistics} {@var{p} =} kruskalwallis (@var{x}, @var{group}) ## @deftypefnx {statistics} {@var{p} =} kruskalwallis (@var{x}, @var{group}, @var{displayopt}) ## @deftypefnx {statistics} {[@var{p}, @var{tbl}] =} kruskalwallis (@var{x}, @dots{}) ## @deftypefnx {statistics} {[@var{p}, @var{tbl}, @var{stats}] =} kruskalwallis (@var{x}, @dots{}) ## ## Perform a Kruskal-Wallis test, the non-parametric alternative of a one-way ## analysis of variance (ANOVA), for comparing the means of two or more groups ## of data under the null hypothesis that the groups are drawn from the same ## population, i.e. the group means are equal. ## ## kruskalwallis can take up to three input arguments: ## ## @itemize ## @item ## @var{x} contains the data and it can either be a vector or matrix. ## If @var{x} is a matrix, then each column is treated as a separate group. ## If @var{x} is a vector, then the @var{group} argument is mandatory. ## @item ## @var{group} contains the names for each group. If @var{x} is a matrix, then ## @var{group} can either be a cell array of strings of a character array, with ## one row per column of @var{x}. If you want to omit this argument, enter an ## empty array ([]). If @var{x} is a vector, then @var{group} must be a vector ## of the same length, or a string array or cell array of strings with one row ## for each element of @var{x}. @var{x} values corresponding to the same value ## of @var{group} are placed in the same group. ## @item ## @var{displayopt} is an optional parameter for displaying the groups contained ## in the data in a boxplot. If omitted, it is 'on' by default. If group names ## are defined in @var{group}, these are used to identify the groups in the ## boxplot. Use 'off' to omit displaying this figure. ## @end itemize ## ## kruskalwallis can return up to three output arguments: ## ## @itemize ## @item ## @var{p} is the p-value of the null hypothesis that all group means are equal. ## @item ## @var{tbl} is a cell array containing the results in a standard ANOVA table. ## @item ## @var{stats} is a structure containing statistics useful for performing ## a multiple comparison of means with the MULTCOMPARE function. ## @end itemize ## ## If kruskalwallis is called without any output arguments, then it prints the ## results in a one-way ANOVA table to the standard output. It is also printed ## when @var{displayopt} is 'on'. ## ## Examples: ## ## @example ## x = meshgrid (1:6); ## x = x + normrnd (0, 1, 6, 6); ## [p, atab] = kruskalwallis(x); ## @end example ## ## ## @example ## x = ones (50, 4) .* [-2, 0, 1, 5]; ## x = x + normrnd (0, 2, 50, 4); ## group = @{"A", "B", "C", "D"@}; ## kruskalwallis (x, group); ## @end example ## ## @end deftypefn function [p, tbl, stats] = kruskalwallis (x, group, displayopt) ## check for valid number of input arguments narginchk (1, 3); ## add defaults if (nargin < 2) group = []; endif if (nargin < 3) displayopt = 'on'; endif plotdata = ! (strcmp (displayopt, 'off')); ## Convert group to cell array from character array, make it a column if (! isempty (group) && ischar (group)) group = cellstr (group); endif if (size (group, 1) == 1) group = group'; endif ## If X is a matrix, convert it to column vector and create a ## corresponding column vector for groups if (length (x) < prod (size (x))) [n, m] = size (x); x = x(:); gi = reshape (repmat ((1:m), n, 1), n*m, 1); if (length (group) == 0) ## no group names are provided group = gi; elseif (size (group, 1) == m) ## group names exist and match columns group = group(gi,:); else error ("X columns and GROUP length do not match."); endif endif ## Identify NaN values (if any) and remove them from X along with ## their corresponding values from group vector nonan = ! isnan (x); x = x(nonan); group = group(nonan, :); ## Convert group to indices and separate names [group_id, group_names] = grp2idx (group); group_id = group_id(:); named = 1; ## Rank data for non-parametric analysis [xr, tieadj] = tieranks (x); ## Get group size and mean for each group groups = size (group_names, 1); xs = zeros (1, groups); xm = xs; for j = 1:groups group_size = find (group_id == j); xs(j) = length (group_size); xm(j) = mean (xr(group_size)); endfor ## Calculate statistics lx = length (xr); ## Number of samples in groups gm = mean (xr); ## Grand mean of groups dfm = length (xm) - 1; ## degrees of freedom for model dfe = lx - dfm - 1; ## degrees of freedom for error SSM = xs .* (xm - gm) * (xm - gm)'; ## Sum of Squares for Model SST = (xr(:) - gm)' * (xr(:) - gm); ## Sum of Squares Total SSE = SST - SSM; ## Sum of Squares Error if (dfm > 0) MSM = SSM / dfm; ## Mean Square for Model else MSM = NaN; endif if (dfe > 0) MSE = SSE / dfe; ## Mean Squared Error else MSE = NaN; endif ## Calculate Chi-sq statistic ChiSq = (12 * SSM) / (lx * (lx + 1)); if (tieadj > 0) ChiSq = ChiSq / (1 - 2 * tieadj / (lx ^ 3 - lx)); endif p = 1 - chi2cdf (ChiSq, dfm); ## Create results table (if requested) if (nargout > 1) tbl = {'Source', 'SS', 'df', 'MS', 'Chi-sq', 'Prob>Chi-sq'; ... 'Groups', SSM, dfm, MSM, ChiSq, p; ... 'Error', SSE, dfe, MSE, '', ''; ... 'Total', SST, dfm + dfe, '', '', ''}; endif ## Create stats structure (if requested) for MULTCOMPARE if (nargout > 2) if (length (group_names) > 0) stats.gnames = group_names; else stats.gnames = strjust (num2str ((1:length (xm))'), 'left'); endif stats.n = xs; stats.source = 'kruskalwallis'; stats.meanranks = xm; stats.sumt = 2 * tieadj; endif ## Print results table on screen if no output argument was requested if (nargout == 0 || plotdata) printf (" Kruskal-Wallis ANOVA Table\n"); printf ("Source SS df MS Chi-sq Prob>Chi-sq\n"); printf ("---------------------------------------------------------\n"); printf ("Columns %10.2f %5.0f %10.2f %8.2f %11.5e\n", ... SSM, dfm, MSM, ChiSq, p); printf ("Error %10.2f %5.0f %10.2f\n", SSE, dfe, MSE); printf ("Total %10.2f %5.0f\n", SST, dfm + dfe); endif ## Plot data using BOXPLOT (unless opted out) if (plotdata) boxplot (x, group_id, 'Notch', 'on', 'Labels', group_names); endif endfunction ## local function for computing tied ranks on column vectors function [r, tieadj] = tieranks (x) ## Sort data [value, x_idx] = sort (x); epsx = zeros (size (x)); epsx = epsx(x_idx); x_l = numel (x); ## Count ranks from start (min value) ranks = [1:x_l]'; ## Initialize tie adjustments tieadj = 0; ## Adjust for ties. ties = value(1:x_l-1) + epsx(1:x_l-1) >= value(2:x_l) - epsx(2:x_l); t_idx = find (ties); t_idx(end+1) = 0; maxTies = numel (t_idx); ## Calculate tie adjustments tiecount = 1; while (tiecount < maxTies) tiestart = t_idx(tiecount); ntied = 2; while (t_idx(tiecount+1) == t_idx(tiecount) + 1) tiecount = tiecount + 1; ntied = ntied + 1; endwhile ## Check for tieflag tieadj = tieadj + ntied * (ntied - 1) * (ntied + 1) / 2; ## Average tied ranks ranks(tiestart:tiestart + ntied - 1) = ... sum (ranks(tiestart:tiestart + ntied - 1)) / ntied; tiecount = tiecount + 1; endwhile ## Remap data to original dimensions r(x_idx) = ranks; endfunction %!demo %! rng (42); %! x = meshgrid (1:6); %! x = x + normrnd (0, 1, 6, 6); %! kruskalwallis (x, [], 'off'); %!demo %! rng (42); %! x = meshgrid (1:6); %! x = x + normrnd (0, 1, 6, 6); %! [p, atab] = kruskalwallis (x); %!demo %! rng (42); %! x = ones (30, 4) .* [-2, 0, 1, 5]; %! x = x + normrnd (0, 2, 30, 4); %! group = {'A', 'B', 'C', 'D'}; %! kruskalwallis (x, group); ## testing results against SPSS and R on the GEAR.DAT data file available from ## https://www.itl.nist.gov/div898/handbook/eda/section3/eda354.htm %!test %! data = [1.006, 0.996, 0.998, 1.000, 0.992, 0.993, 1.002, 0.999, 0.994, 1.000, ... %! 0.998, 1.006, 1.000, 1.002, 0.997, 0.998, 0.996, 1.000, 1.006, 0.988, ... %! 0.991, 0.987, 0.997, 0.999, 0.995, 0.994, 1.000, 0.999, 0.996, 0.996, ... %! 1.005, 1.002, 0.994, 1.000, 0.995, 0.994, 0.998, 0.996, 1.002, 0.996, ... %! 0.998, 0.998, 0.982, 0.990, 1.002, 0.984, 0.996, 0.993, 0.980, 0.996, ... %! 1.009, 1.013, 1.009, 0.997, 0.988, 1.002, 0.995, 0.998, 0.981, 0.996, ... %! 0.990, 1.004, 0.996, 1.001, 0.998, 1.000, 1.018, 1.010, 0.996, 1.002, ... %! 0.998, 1.000, 1.006, 1.000, 1.002, 0.996, 0.998, 0.996, 1.002, 1.006, ... %! 1.002, 0.998, 0.996, 0.995, 0.996, 1.004, 1.004, 0.998, 0.999, 0.991, ... %! 0.991, 0.995, 0.984, 0.994, 0.997, 0.997, 0.991, 0.998, 1.004, 0.997]; %! group = [1:10] .* ones (10,10); %! group = group(:); %! [p, tbl] = kruskalwallis (data, group, 'off'); %! assert_equal (p, 0.048229, 1e-6); %! assert_equal (tbl{2,5}, 17.03124, 1e-5); %! assert_equal (tbl{2,3}, 9, 0); %! assert_equal (tbl{4,2}, 82655.5, 1e-16); %! data = reshape (data, 10, 10); %! [p, tbl, stats] = kruskalwallis (data, [], 'off'); %! assert_equal (p, 0.048229, 1e-6); %! assert_equal (tbl{2,5}, 17.03124, 1e-5); %! assert_equal (tbl{2,3}, 9, 0); %! assert_equal (tbl{4,2}, 82655.5, 1e-16); %! means = [51.85, 60.45, 37.6, 51.1, 29.5, 54.25, 64.55, 66.7, 53.65, 35.35]; %! N = 10 * ones (1, 10); %! assert_equal (stats.meanranks, means, 1e-6); %! assert_equal (length (stats.gnames), 10, 0); %! assert_equal (stats.n, N, 0); statistics-release-1.9.2/inst/Hypothesis_Testing/kstest.m000066400000000000000000000422441524624707500236610ustar00rootroot00000000000000## Copyright (C) 2022-2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} kstest (@var{x}) ## @deftypefnx {statistics} {@var{h} =} kstest (@var{x}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{h}, @var{p}] =} kstest (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{p}, @var{ksstat}, @var{cv}] =} kstest (@dots{}) ## ## Single sample Kolmogorov-Smirnov (K-S) goodness-of-fit hypothesis test. ## ## @code{@var{h} = kstest (@var{x})} performs a Kolmogorov-Smirnov (K-S) test to ## determine if a random sample @var{x} could have come from a standard normal ## distribution. @var{h} indicates the results of the null hypothesis test. ## ## @itemize ## @item @var{h} = 0 => Do not reject the null hypothesis at the 5% significance ## @item @var{h} = 1 => Reject the null hypothesis at the 5% significance ## @end itemize ## ## @var{x} is a vector representing a random sample from some unknown ## distribution with a cumulative distribution function F(X). Missing values ## declared as NaNs in @var{x} are ignored. ## ## @code{@var{h} = kstest (@var{x}, @var{name}, @var{value})} returns ## a test decision for a single-sample K-S test with additional options ## specified by one or more @var{Name}-@var{Value} pair arguments as shown ## below. ## ## @multitable @columnfractions 0.15 0.8 ## @headitem Name @tab Value ## @item @qcode{'alpha'} @tab A numeric scalar between 0 and 1 specifying ## th ## the significance level. Default is 0.05 for 5% significance. ## ## @item @qcode{'CDF'} @tab The hypothesized CDF under the null hypothesis. ## It can be specified as a function handle of an existing cdf function, a ## character vector defining a probability distribution with default parameters, ## a probability distribution object, or a two-column matrix. If not provided, ## the default is the standard normal, @math{N(0,1)}. The one-sample ## Kolmogorov-Smirnov test is only valid for continuous cumulative distribution ## functions, and requires the CDF to be predetermined. The result is not ## accurate if CDF is estimated from the data. ## ## @item @qcode{'tail'} @tab A string indicating the type of test: ## @end multitable ## @multitable @columnfractions 0.15 0.5 ## @item @qcode{'unequal'} @tab "F(X) not equal to CDF(X)" (two-sided) ## (Default) ## ## @item @qcode{'larger'} @tab "F(X) > CDF(X)" (one-sided) ## ## @item @qcode{'smaller'} @tab "F(X) < CDF(X)" (one-sided) ## @end multitable ## ## Let S(X) be the empirical c.d.f. estimated from the sample vector @var{x}, ## F(X) be the corresponding true (but unknown) population c.d.f., and CDF be ## the known input c.d.f. specified under the null hypothesis. ## For @code{tail} = "unequal", "larger", and "smaller", the test statistics are ## max|S(X) - CDF(X)|, max[S(X) - CDF(X)], and max[CDF(X) - S(X)], respectively. ## ## @code{[@var{h}, @var{p}] = kstest (@dots{})} also returns the asymptotic ## p-value @var{p}. ## ## @code{[@var{h}, @var{p}, @var{ksstat}] = kstest (@dots{})} returns the K-S ## test statistic @var{ksstat} defined above for the test type indicated by the ## "tail" option ## ## In the matrix version of CDF, column 1 contains the x-axis data and column 2 ## the corresponding y-axis c.d.f data. Since the K-S test statistic will ## occur at one of the observations in @var{x}, the calculation is most ## efficient when CDF is only specified at the observations in @var{x}. When ## column 1 of CDF represents x-axis points independent of @var{x}, CDF is ## linearly interpolated at the observations found in the vector @var{x}. In ## this case, the interval along the x-axis (the column 1 spread of CDF) must ## span the observations in @var{x} for successful interpolation. ## ## The decision to reject the null hypothesis is based on comparing the p-value ## @var{p} with the "alpha" value, not by comparing the statistic @var{ksstat} ## with the critical value @var{cv}. @var{cv} is computed separately using an ## approximate formula or by interpolation using Miller's approximation table. ## The formula and table cover the range 0.01 <= "alpha" <= 0.2 for two-sided ## tests and 0.005 <= "alpha" <= 0.1 for one-sided tests. CV is returned as NaN ## if "alpha" is outside this range. Since CV is approximate, a comparison of ## @var{ksstat} with @var{cv} may occasionally lead to a different conclusion ## than a comparison of @var{p} with "alpha". ## ## @seealso{kstest2, cdfplot} ## @end deftypefn function [H, pValue, ksstat, cV] = kstest (x, varargin) ## Check input parameters if (nargin < 1) error ("kstest: too few inputs."); endif if (! isvector (x) || ! isreal (x)) error ("kstest: X must be a vector of real numbers."); endif ## Add defaults alpha = 0.05; tail = 'unequal'; CDF = []; ## Parse extra parameters if (length (varargin) > 0 && mod (numel (varargin), 2) == 0) [~, prop] = parseparams (varargin); while (! isempty (prop)) switch (lower (prop{1})) case 'alpha' alpha = prop{2}; case 'tail' tail = prop{2}; case 'cdf' CDF = prop{2}; otherwise error ("kstest: unknown option '%s'.", prop{1}); endswitch prop = prop(3:end); endwhile elseif (mod (numel (varargin), 2) != 0) error ("kstest: optional parameters must be in name/value pairs."); endif ## Check for valid alpha and tail parameters if (! isnumeric (alpha) || isnan (alpha) || ! isscalar (alpha) ... || alpha <= 0 || alpha >= 1) error ("kstest: alpha must be a numeric scalar in the range (0,1)."); endif if (! isa (tail, 'char')) error ("kstest: tail argument must be a string."); elseif (sum (strcmpi (tail, {'unequal', 'larger', 'smaller'})) < 1) error ("kstest: tail value must be either 'both', right' or 'left'."); endif ## Remove NaNs, get sample size and compute empirical cdf x(isnan (x)) = []; n = length (x); [sampleCDF, x] = ecdf (x); ## Remove 1st element x = x(2:end); ## Check the hypothesized CDF specified under the null hypothesis. ## No CDF was provided (use default) if (isempty (CDF)) xCDF = x; yCDF = normcdf (x, 0, 1); ## If CDF is a function handle elseif (isa (CDF, 'function_handle')) xCDF = x; yCDF = feval (CDF, x); if (! isequal (size (xCDF), size (yCDF))) error ("kstest: invalid function handle."); endif ## If CDF is character vector elseif (isa (CDF, 'char') && isvector (CDF)) ## Check for supported distributions PDO = makedist (); if (! any (strcmpi (PDO, CDF))) error ("kstest: '%s' is not a supported distribution.", CDF); endif pd = makedist (CDF); xCDF = x; yCDF = pd.cdf (x); ## If CDF is a probability distribution object elseif (isobject (CDF)) PDO = makedist (); PDO = cellfun (@(x) sprintf ("prob.%sDistribution", x), PDO, ... 'UniformOutput', false); if (! any (isa (CDF, PDO))) error ("kstest: 'CDF' must be a probability distribution object."); endif xCDF = x; yCDF = CDF.cdf (x); ## If CDF is numerical elseif (! isempty (CDF) && isnumeric (CDF)) if (size (CDF, 2) != 2) error ("kstest: numerical CDF should have only 2 columns."); endif CDF(isnan (sum (CDF, 2)),:) = []; if (size (CDF, 1) == 0) error ("kstest: numerical CDF should have at least one row."); endif ## Sort numerical CDF [xCDF, i] = sort (CDF(:,1)); yCDF = CDF(i,2); ## Check that numerical CDF is incrementally sorted ydiff = diff (yCDF); if (any (ydiff < 0)) error ("kstest: non-incrementing numerical CDF."); endif ## Remove duplicates. Check for consistency rd = find (diff (xCDF) == 0); if (! isempty (rd)) if (! all (ydiff(rd) == 0)) error ("kstest: wrong duplicates in numerical CDF."); endif xCDF(rd) = []; yCDF(rd) = []; endif ## Invalid value parsed as CDF optional argument else error ("kstest: invalid value parsed as CDF optional argument."); endif ## Check if CDF is specified at the observations in X and assign 2nd column ## of numerical CDF to null CDF if (isequal (x, xCDF)) nCDF = yCDF; ## Otherwise interpolate the numerical CDF to assign values to the null CDF else ## Check that 1st column range bounds the observations in X if (x(1) < xCDF(1) || x(end) > xCDF(end)) error ("kstest: wrong span in CDF."); endif nCDF = interp1 (xCDF, yCDF, x); endif ## Calculate the suitable KS statistic according to tail switch (tail) case 'unequal' # 2-sided test: T = max|S(x) - CDF(x)|. delta1 = sampleCDF(1:end - 1) - nCDF; delta2 = sampleCDF(2:end) - nCDF; deltaCDF = abs ([delta1; delta2]); case 'smaller' # 1-sided test: T = max[CDF(x) - S(x)]. delta1 = nCDF - sampleCDF(1:end - 1); delta2 = nCDF - sampleCDF(2:end); deltaCDF = [delta1; delta2]; case 'larger' # 1-sided test: T = max[S(x) - CDF(x)]. delta1 = sampleCDF(1:end - 1) - nCDF; delta2 = sampleCDF(2:end) - nCDF; deltaCDF = [delta1; delta2]; endswitch ksstat = max (deltaCDF); ## Compute the asymptotic P-value approximation if (strcmpi (tail, 'unequal')) # 2-sided test s = n * ksstat ^ 2; ## For d values that are in the far tail of the distribution (i.e. ## p-values > .999), the following lines will speed up the computation ## significantly, and provide accuracy up to 7 digits. if ((s > 7.24) || ((s > 3.76) && (n > 99))) pValue = 2 * exp (-(2.000071 + 0.331 / sqrt (n) + 1.409 / n) * s); else ## Express d as d = (k-h)/n, where k is a +ve integer and 0 < h < 1. k = ceil (ksstat * n); h = k - ksstat * n; m = 2 * k - 1; ## Create the H matrix, according to Marsaglia et al. if (m > 1) c = 1 ./ gamma ((1:m)' + 1); r = zeros (1,m); r(1) = 1; r(2) = 1; T = toeplitz (c, r); T(:,1) = T(:,1) - (h .^ (1:m)') ./ gamma ((1:m)' + 1); T(m,:) = fliplr (T(:,1)'); T(m,1) = (1 - 2 * h ^ m + max (0, 2 * h - 1) ^m) / gamma (m+1); else T = (1 - 2 * h ^ m + max (0, 2 * h - 1) ^ m) / gamma (m+1); endif ## Scaling before raising the matrix to a power if (! isscalar (T)) lmax = max (eig (T)); T = (T ./ lmax) ^ n; else lmax = 1; endif pValue = 1 - exp (gammaln (n+1) + n * log (lmax) - n * log (n)) * T(k,k); endif else # 1-sided test t = n * ksstat; k = ceil (t):n; pValue = sum (exp (log (t) - n * log (n) + gammaln (n + 1) ... - gammaln (k + 1) - gammaln (n - k + 1) + k .* log (k - t) ... + (n - k - 1) .* log (t + n - k))); endif ## Return hypothesis test H = (pValue < alpha); ## Calculate critical Value (cV) if requested if (nargout > 3) ## The critical value table used below is expressed in reference to a ## 1-sided significance level. Hence alpha is halved for a two-sided test. if (strcmpi (tail, 'unequal')) # 2-sided test alpha1 = alpha / 2; else # 1-sided test alpha1 = alpha; endif if ((alpha1 >= 0.005) && (alpha1 <= 0.10)) ## If the sample size 'n' is greater than 20, use Miller's approximation ## Otherwise interpolate into his 'exact' table. if (n <= 20) # Small sample exact values. % Exact K-S test critical values based on Miller's approximation. a1 = [0.00500, 0.01000, 0.02500, 0.05000, 0.10000]'; exact = [0.99500, 0.99000, 0.97500, 0.95000, 0.90000; ... 0.92929, 0.90000, 0.84189, 0.77639, 0.68377; ... 0.82900, 0.78456, 0.70760, 0.63604, 0.56481; ... 0.73424, 0.68887, 0.62394, 0.56522, 0.49265; ... 0.66853, 0.62718, 0.56328, 0.50945, 0.44698; ... 0.61661, 0.57741, 0.51926, 0.46799, 0.41037; ... 0.57581, 0.53844, 0.48342, 0.43607, 0.38148; ... 0.54179, 0.50654, 0.45427, 0.40962, 0.35831; ... 0.51332, 0.47960, 0.43001, 0.38746, 0.33910; ... 0.48893, 0.45662, 0.40925, 0.36866, 0.32260; ... 0.46770, 0.43670, 0.39122, 0.35242, 0.30829; ... 0.44905, 0.41918, 0.37543, 0.33815, 0.29577; ... 0.43247, 0.40362, 0.36143, 0.32549, 0.28470; ... 0.41762, 0.38970, 0.34890, 0.31417, 0.27481; ... 0.40420, 0.37713, 0.33760, 0.30397, 0.26588; ... 0.39201, 0.36571, 0.32733, 0.29472, 0.25778; ... 0.38086, 0.35528, 0.31796, 0.28627, 0.25039; ... 0.37062, 0.34569, 0.30936, 0.27851, 0.24360; ... 0.36117, 0.33685, 0.30143, 0.27136, 0.23735; ... 0.35241, 0.32866, 0.29408, 0.26473, 0.23156]; cV = spline (a1 , exact(n,:)' , alpha1); else # Large sample approximate values. A = 0.09037 * (-log10 (alpha1)) .^ 1.5 + 0.01515 * ... log10 (alpha1) .^ 2 - 0.08467 * alpha1 - 0.11143; asymptoticStat = sqrt (-0.5 * log (alpha1) ./ n); cV = asymptoticStat - 0.16693 ./ n - A ./ n .^ 1.5; cV = min (cV, 1 - alpha1); endif else cV = NaN; endif endif endfunction %!demo %! ## Use the stock return data set to test the null hypothesis that the data %! ## come from a standard normal distribution against the alternative %! ## hypothesis that the population CDF of the data is larger that the %! ## standard normal CDF. %! %! load stockreturns; %! x = stocks(:,2); %! [h, p, k, c] = kstest (x, 'Tail', 'larger') %! %! ## Compute the empirical CDF and plot against the standard normal CDF %! [f, x_values] = ecdf (x); %! h1 = plot (x_values, f); %! hold on; %! h2 = plot (x_values, normcdf (x_values), 'r--'); %! set (h1, 'LineWidth', 2); %! set (h2, 'LineWidth', 2); %! legend ([h1, h2], 'Empirical CDF', 'Standard Normal CDF', ... %! 'Location', 'southeast'); %! title ('Empirical CDF of stock return data against standard normal CDF') ## Test input %!error kstest () %!error kstest (ones (2, 4)) %!error kstest ([2, 3, 5, 3+3i]) %!error kstest ([2, 3, 4, 5, 6], 'opt', 0.51) %!error ... %! kstest ([2, 3, 4, 5, 6], 'tail') %!error ... %! kstest ([2,3,4,5,6],'alpha', [0.05, 0.05]) %!error ... %! kstest ([2, 3, 4, 5, 6], 'alpha', NaN) %!error ... %! kstest ([2, 3, 4, 5, 6], 'tail', 0) %!error ... %! kstest ([2,3,4,5,6], 'tail', 'whatever') %!error ... %! kstest ([1, 2, 3, 4, 5], 'CDF', @(x) repmat (x, 2, 3)) %!error ... %! kstest ([1, 2, 3, 4, 5], 'CDF', 'somedist') %!error ... %! kstest ([1, 2, 3, 4, 5], 'CDF', cvpartition (5, 'resubstitution')) %!error ... %! kstest ([2, 3, 4, 5, 6], 'alpha', 0.05, 'CDF', [2, 3, 4; 1, 3, 4; 1, 2, 1]) %!error ... %! kstest ([2, 3, 4, 5, 6], 'alpha', 0.05, 'CDF', nan (5, 2)) %!error ... %! kstest ([2, 3, 4, 5, 6], 'CDF', [2, 3; 1, 4; 3, 2]) %!error ... %! kstest ([2, 3, 4, 5, 6], 'CDF', [2, 3; 2, 4; 3, 5]) %!error ... %! kstest ([2, 3, 4, 5, 6], 'CDF', {1, 2, 3, 4, 5}) ## Test results %!test %! load examgrades %! [h, p] = kstest (grades(:,1)); %! assert_equal (h, true); %! assert_equal (p, 7.58603305206105e-107, 1e-14); %!test %! load examgrades %! [h, p] = kstest (grades(:,1), 'CDF', @(x) normcdf (x, 75, 10)); %! assert_equal (h, false); %! assert_equal (p, 0.5612, 1e-4); %!test %! load examgrades %! x = grades(:,1); %! test_cdf = makedist ('tlocationscale', 'mu', 75, 'sigma', 10, 'nu', 1); %! [h, p] = kstest (x, 'alpha', 0.01, 'CDF', test_cdf); %! assert_equal (h, true); %! assert_equal (p, 0.0021, 1e-4); %!test %! load stockreturns %! x = stocks(:,3); %! [h,p,k,c] = kstest (x, 'Tail', 'larger'); %! assert_equal (h, true); %! assert_equal (p, 5.085438806199252e-05, 1e-14); %! assert_equal (k, 0.2197, 1e-4); %! assert_equal (c, 0.1207, 1e-4); statistics-release-1.9.2/inst/Hypothesis_Testing/kstest2.m000066400000000000000000000170651524624707500237460ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} kstest2 (@var{x1}, @var{x2}) ## @deftypefnx {statistics} {@var{h} =} kstest2 (@var{x1}, @var{x2}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{h}, @var{p}] =} kstest2 (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{p}, @var{ks2stat}] =} kstest2 (@dots{}) ## ## Two-sample Kolmogorov-Smirnov goodness-of-fit hypothesis test. ## ## @code{@var{h} = kstest2 (@var{x1}, @var{x2})} returns a test decision for the ## null hypothesis that the data in vectors @var{x1} and @var{x2} are from the ## same continuous distribution, using the two-sample Kolmogorov-Smirnov test. ## The alternative hypothesis is that @var{x1} and @var{x2} are from different ## continuous distributions. The result @var{h} is 1 if the test rejects the ## null hypothesis at the 5% significance level, and 0 otherwise. ## ## @code{@var{h} = kstest2 (@var{x1}, @var{x2}, @var{name}, @var{value})} ## returns a test decision for a two-sample Kolmogorov-Smirnov test with ## additional options specified by one or more name-value pair arguments as ## shown below. ## ## @multitable @columnfractions 0.20 0.8 ## @headitem @var{Name} @tab @var{Value} ## @item "alpha" @tab A value @var{alpha} between 0 and 1 specifying the ## significance level. Default is 0.05 for 5% significance. ## ## @item "tail" @tab A string indicating the type of test: ## @end multitable ## ## @multitable @columnfractions 0.2 0.77 ## @item "unequal" @tab "F(X1) not equal to F(X2)" (two-sided) [Default] ## ## @item "larger" @tab "F(X1) > F(X2)" (one-sided) ## ## @item "smaller" @tab "F(X1) < F(X2)" (one-sided) ## @end multitable ## ## The two-sided test uses the maximum absolute difference between the cdfs of ## the distributions of the two data vectors. The test statistic is ## @code{D* = max(|F1(x) - F2(x)|)}, where F1(x) is the proportion of @var{x1} ## values less or equal to x and F2(x) is the proportion of @var{x2} values less ## than or equal to x. The one-sided test uses the actual value of the ## difference between the cdfs of the distributions of the two data vectors ## rather than the absolute value. The test statistic is ## @code{D* = max(F1(x) - F2(x))} or @code{D* = max(F2(x) - F1(x))} for ## @code{tail} = "larger" or "smaller", respectively. ## ## @code{[@var{h}, @var{p}] = kstest2 (@dots{})} also returns the ## asymptotic p-value @var{p}. ## ## @code{[@var{h}, @var{p}, @var{ks2stat}] = kstest2 (@dots{})} also returns ## the Kolmogorov-Smirnov test statistic @var{ks2stat} defined above for the ## test type indicated by @code{tail}. ## ## @seealso{kstest, cdfplot} ## @end deftypefn function [H, pValue, ks2stat] = kstest2 (x1, x2, varargin) ## Check input parameters if nargin < 2 error ("kstest2: Too few inputs."); endif if ! isvector (x1) || ! isreal (x1) || ! isvector (x2) || ! isreal (x2) error ("kstest2: X1 and X2 must be vectors of real numbers."); endif ## Add defaults alpha = 0.05; tail = 'unequal'; ## Parse extra parameters if nargin > 2 && mod (numel (varargin), 2) == 0 [~, prop] = parseparams (varargin); while (! isempty (prop)) switch (lower (prop{1})) case 'alpha' alpha = prop{2}; case 'tail' tail = prop{2}; otherwise error ("kstest2: Unknown option %s", prop{1}); endswitch prop = prop(3:end); endwhile elseif nargin > 2 error ("kstest2: optional parameters must be in name/value pairs."); endif ## Check for valid alpha and tail parameters if (! isnumeric (alpha) || isnan (alpha) || ! isscalar (alpha) ... || alpha <= 0 || alpha >= 1) error ("kstest2: alpha must be a numeric scalar in the range (0,1)."); endif if ! isa (tail, 'char') error ("kstest2: tail argument must be a string"); elseif sum (strcmpi (tail, {'unequal', 'larger', 'smaller'})) < 1 error (strcat ("kstest2: tail value must be either", ... " 'unequal', 'larger' or 'smaller'.")); endif ## Make x1 and x2 column vectors x1 = x1(:); x2 = x2(:); ## Remove missing values (NaN) x1(isnan (x1)) = []; x2(isnan (x2)) = []; ## Check for remaining data in both vectors if isempty (x1) error ("kstest2: Not enough data in X1"); elseif isempty (x2) error ("kstest2: Not enough data in X2"); endif ## Calculate F1(x) and F2(x) binEdges = [-inf; sort([x1;x2]); inf]; binCounts1 = histc (x1 , binEdges, 1); binCounts2 = histc (x2 , binEdges, 1); sumCounts1 = cumsum (binCounts1) ./ sum (binCounts1); sumCounts2 = cumsum (binCounts2) ./ sum (binCounts2); sampleCDF1 = sumCounts1(1:end - 1); sampleCDF2 = sumCounts2(1:end - 1); ## Calculate the suitable KS statistic according to tail switch tail case 'unequal' # 2-sided test: T = max|F1(x) - F2(x)|. deltaCDF = abs (sampleCDF1 - sampleCDF2); case 'smaller' # 1-sided test: T = max[F2(x) - F1(x)]. deltaCDF = sampleCDF2 - sampleCDF1; case 'larger' # 1-sided test: T = max[F1(x) - F2(x)]. deltaCDF = sampleCDF1 - sampleCDF2; endswitch ks2stat = max (deltaCDF); ## Compute the asymptotic P-value approximation n_x1 = length (x1); n_x2 = length (x2); n = n_x1 * n_x2 /(n_x1 + n_x2); lambda = max ((sqrt (n) + 0.12 + 0.11 / sqrt (n)) * ks2stat, 0); if strcmpi (tail, 'unequal') # 2-sided test v = [1:101]; pValue = 2 * sum ((-1) .^ (v-1) .* exp (-2 * lambda * lambda * v .^ 2)); pValue = min (max (pValue, 0), 1); else # 1-sided test pValue = exp (-2 * lambda * lambda); endif ## Return hypothesis test H = (alpha >= pValue); endfunction ## Test input %!error kstest2 ([1,2,3,4,5,5]) %!error kstest2 (ones (2,4), [1,2,3,4,5,5]) %!error kstest2 ([2,3,5,7,3+3i], [1,2,3,4,5,5]) %!error kstest2 ([2,3,4,5,6],[3;5;7;8;7;6;5],'tail') %!error kstest2 ([2,3,4,5,6],[3;5;7;8;7;6;5],'tail', 'whatever') %!error kstest2 ([2,3,4,5,6],[3;5;7;8;7;6;5],'badoption', 0.51) %!error kstest2 ([2,3,4,5,6],[3;5;7;8;7;6;5],'tail', 0) %!error kstest2 ([2,3,4,5,6],[3;5;7;8;7;6;5],'alpha', 0) %!error kstest2 ([2,3,4,5,6],[3;5;7;8;7;6;5],'alpha', NaN) %!error kstest2 ([NaN,NaN,NaN,NaN,NaN],[3;5;7;8;7;6;5],'tail', 'unequal') ## Test results %!test %! load examgrades %! [h, p] = kstest2 (grades(:,1), grades(:,2)); %! assert_equal (h, false); %! assert_equal (p, 0.1222791870137312, 1e-14); %!test %! load examgrades %! [h, p] = kstest2 (grades(:,1), grades(:,2), 'tail', 'larger'); %! assert_equal (h, false); %! assert_equal (p, 0.1844421391011258, 1e-14); %!test %! load examgrades %! [h, p] = kstest2 (grades(:,1), grades(:,2), 'tail', 'smaller'); %! assert_equal (h, false); %! assert_equal (p, 0.06115357930171663, 1e-14); %!test %! load examgrades %! [h, p] = kstest2 (grades(:,1), grades(:,2), 'tail', 'smaller', 'alpha', 0.1); %! assert_equal (h, true); %! assert_equal (p, 0.06115357930171663, 1e-14); statistics-release-1.9.2/inst/Hypothesis_Testing/levene_test.m000066400000000000000000000274371524624707500246700ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} levene_test (@var{x}) ## @deftypefnx {statistics} {@var{h} =} levene_test (@var{x}, @var{group}) ## @deftypefnx {statistics} {@var{h} =} levene_test (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {@var{h} =} levene_test (@var{x}, @var{testtype}) ## @deftypefnx {statistics} {@var{h} =} levene_test (@var{x}, @var{group}, @var{alpha}) ## @deftypefnx {statistics} {@var{h} =} levene_test (@var{x}, @var{group}, @var{testtype}) ## @deftypefnx {statistics} {@var{h} =} levene_test (@var{x}, @var{group}, @var{alpha}, @var{testtype}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}] =} levene_test (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{W}] =} levene_test (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{W}, @var{df}] =} levene_test (@dots{}) ## ## Perform a Levene's test for the homogeneity of variances. ## ## Under the null hypothesis of equal variances, the test statistic @var{W} ## approximately follows an F distribution with @var{df} degrees of ## freedom being a vector ([k-1, N-k]). ## ## The p-value (1 minus the CDF of this distribution at @var{W}) is returned in ## @var{pval}. @var{h} = 1 if the null hypothesis is rejected at the ## significance level of @var{alpha}. Otherwise @var{h} = 0. ## ## Input Arguments: ## ## @itemize ## @item ## @var{x} contains the data and it can either be a vector or matrix. ## If @var{x} is a matrix, then each column is treated as a separate group. ## If @var{x} is a vector, then the @var{group} argument is mandatory. ## NaN values are omitted. ## ## @item ## @var{group} contains the names for each group. If @var{x} is a vector, then ## @var{group} must be a vector of the same length, or a string array or cell ## array of strings with one row for each element of @var{x}. @var{x} values ## corresponding to the same value of @var{group} are placed in the same group. ## If @var{x} is a matrix, then @var{group} can either be a cell array of ## strings of a character array, with one row per column of @var{x} in the same ## way it is used in @code{anova1} function. If @var{x} is a matrix, then ## @var{group} can be omitted either by entering an empty array ([]) or by ## parsing only @var{alpha} as a second argument (if required to change its ## default value). ## ## @item ## @var{alpha} is the statistical significance value at which the null ## hypothesis is rejected. Its default value is 0.05 and it can be parsed ## either as a second argument (when @var{group} is omitted) or as a third ## argument. ## ## @item ## @var{testtype} is a string determining the type of Levene's test. By default ## it is set to "absolute", but the user can also parse "quadratic" in order to ## perform Levene's Quadratic test for equal variances or "median" in order to ## to perform the Brown-Forsythe's test. These options determine how the Z_ij ## values are computed. If an invalid name is parsed for @var{testtype}, then ## the Levene's Absolute test is performed. ## @end itemize ## ## @seealso{bartlett_test, vartest2, vartestn} ## @end deftypefn function [h, pval, W, df] = levene_test (x, varargin) ## Check for valid number of input arguments if (nargin < 1 || nargin > 4) error ("levene_test: invalid number of input arguments."); endif ## Add defaults group = []; alpha = 0.05; ttype = 'absolute'; ## Check for 2nd argument being ALPHA, GROUP, or TESTTYPE if (nargin > 1) if (isscalar (varargin{1}) && isnumeric (varargin{1}) ... && numel (varargin{1}) == 1) alpha = varargin{1}; ## Check for valid alpha value if (alpha <= 0 || alpha >= 1) error ("levene_test: wrong value for alpha."); endif elseif (any (strcmpi (varargin{1}, {'absolute', 'quadratic', 'median'}))) ttype = varargin{1}; elseif (isvector (varargin{1}) && numel (varargin{1} > 1)) if ((size (x, 2) == 1 && size (x, 1) == numel (varargin{1})) || ... (size (x, 2) > 1 && size (x, 2) == numel (varargin{1}))) group = varargin{1}; else error ("levene_test: GROUP and X mismatch."); endif elseif (isempty (varargin{1})) ## Do nothing else error ("levene_test: invalid second input argument."); endif endif ## Check for 3rd argument if (nargin > 2) if (isscalar (varargin{2}) && isnumeric (varargin{2}) ... && numel (varargin{2} == 1)) alpha = varargin{2}; ## Check for valid alpha value if (alpha <= 0 || alpha >= 1) error ("levene_test: wrong value for alpha."); endif elseif (any (strcmpi (varargin{2}, {'absolute', 'quadratic', 'median'}))) ttype = varargin{2}; else error ("levene_test: invalid third input argument."); endif endif ## Check for 3rd argument if (nargin > 3) if (any (strcmpi (varargin{3}, {'absolute', 'quadratic', 'median'}))) ttype = varargin{3}; else error ("levene_test: invalid option for TESTTYPE as 4th argument."); endif endif ## Convert group to cell array from character array, make it a column if (! isempty (group) && ischar (group)) group = cellstr (group); endif if (size (group, 1) == 1) group = group'; endif ## If x is a matrix, convert it to column vector and create a ## corresponding column vector for groups if (length (x) < prod (size (x))) [n, m] = size (x); x = x(:); gi = reshape (repmat ((1:m), n, 1), n*m, 1); if (length (group) == 0) ## no group names are provided group = gi; elseif (size (group, 1) == m) ## group names exist and match columns group = group(gi,:); else error ("levene_test: columns in X and GROUP length do not match."); endif endif ## Check that x and group are the same size if (! all (numel (x) == numel (group))) error (strcat ("levene_test: GROUP must be a vector with the same", ... " number of rows as x.")); endif ## Identify NaN values (if any) and remove them from X along with ## their corresponding values from group vector nonan = ! isnan (x); x = x(nonan); group = group(nonan, :); ## Convert group to indices and separate names [group_id, group_names] = grp2idx (group); group_id = group_id(:); ## Get sample size (N_i), mean (Y_i), median (Y_I) and sample values (Y_ij) ## for groups with more than one sample groups = size (group_names, 1); rgroup = []; N_i = zeros (1, groups); Y_i = N_i; Y_I = N_i; for k = 1:groups group_size = find (group_id == k); if (length (group_size) > 1) N_i(k) = length (group_size); Y_i(k) = mean (x(group_size)); Y_I(k) = median (x(group_size)); Y_ij{k} = x(group_size); else warning (strcat (sprintf ("levene_test: GROUP %s has a single", ... group_names{k}), [" sample and is not included in the test.\n"])); rgroup = [rgroup, k]; N_i(k) = 1; Y_i(k) = x(group_size); Y_I(k) = x(group_size); Y_ij{k} = x(group_size); endif endfor ## Remove groups with a single sample if (! isempty (rgroup)) N_i(rgroup) = []; Y_i(rgroup) = []; Y_I(rgroup) = []; Y_ij(rgroup) = []; k = k - numel (rgroup); endif ## Compute Z_ij for "absolute" or "quadratic" Levene's test switch (lower (ttype)) case 'absolute' for i = 1:k Z_ij{i} = abs (Y_ij{i} - Y_i(i)); endfor case 'quadratic' for i = 1:k Z_ij{i} = sqrt ((Y_ij{i} - Y_i(i)) .^ 2); endfor case 'median' for i = 1:k Z_ij{i} = abs (Y_ij{i} - Y_I(i)); endfor endswitch ## Compute Z_i and Z_ Z_ = []; for i = 1:k Z_i(i) = mean (Z_ij{i}); Z_ = [Z_; Z_ij{i}(:)]; endfor Z_ = mean (Z_); ## Compute total sample size (N) N = sum (N_i); ## Calculate W statistic. termA = (N - k) / (k - 1); termB = sum (N_i .* ((Z_i - Z_) .^ 2)); termC = 0; for i = 1:k termC += sum ((Z_ij{i} - Z_i(i)) .^ 2); endfor W = termA * (termB / termC); ## Calculate p-value from the chi-square distribution pval = 1 - fcdf (W, k - 1, N - k); ## Save dfs df = [k-1, N-k]; ## Determine the test outcome h = double (pval < alpha); endfunction ## Test input validation %!error levene_test () %!error ... %! levene_test (1, 2, 3, 4, 5); %!error levene_test (randn (50, 2), 0); %!error ... %! levene_test (randn (50, 2), [1, 2, 3]); %!error ... %! levene_test (randn (50, 1), ones (55, 1)); %!error ... %! levene_test (randn (50, 1), ones (50, 2)); %!error ... %! levene_test (randn (50, 2), [], 1.2); %!error ... %! levene_test (randn (50, 2), 'some_string'); %!error ... %! levene_test (randn (50, 2), [], 'alpha'); %!error ... %! levene_test (randn (50, 1), [ones(25, 1); 2*ones(25, 1)], 1.2); %!error ... %! levene_test (randn (50, 1), [ones(25, 1); 2*ones(25, 1)], 'err'); %!error ... %! levene_test (randn (50, 1), [ones(25, 1); 2*ones(25, 1)], 0.05, 'type'); %!warning ... %! levene_test (randn (50, 1), [ones(24, 1); 2*ones(25, 1); 3]); ## Test results %!test %! load examgrades %! [h, pval, W, df] = levene_test (grades); %! assert_equal (h, 1); %! assert_equal (pval, 9.523239714592791e-07, 1e-14); %! assert_equal (W, 8.59529, 1e-5); %! assert_equal (df, [4, 595]); %!test %! load examgrades %! [h, pval, W, df] = levene_test (grades, [], 'quadratic'); %! assert_equal (h, 1); %! assert_equal (pval, 9.523239714592791e-07, 1e-14); %! assert_equal (W, 8.59529, 1e-5); %! assert_equal (df, [4, 595]); %!test %! load examgrades %! [h, pval, W, df] = levene_test (grades, [], 'median'); %! assert_equal (h, 1); %! assert_equal (pval, 1.312093241723211e-06, 1e-14); %! assert_equal (W, 8.415969, 1e-6); %! assert_equal (df, [4, 595]); %!test %! load examgrades %! [h, pval, W, df] = levene_test (grades(:,[1:3])); %! assert_equal (h, 1); %! assert_equal (pval, 0.004349390980463497, 1e-14); %! assert_equal (W, 5.52139, 1e-5); %! assert_equal (df, [2, 357]); %!test %! load examgrades %! [h, pval, W, df] = levene_test (grades(:,[1:3]), 'median'); %! assert_equal (h, 1); %! assert_equal (pval, 0.004355216763951453, 1e-14); %! assert_equal (W, 5.52001, 1e-5); %! assert_equal (df, [2, 357]); %!test %! load examgrades %! [h, pval, W, df] = levene_test (grades(:,[3,4]), 'quadratic'); %! assert_equal (h, 0); %! assert_equal (pval, 0.1807494957440653, 2e-14); %! assert_equal (W, 1.80200, 1e-5); %! assert_equal (df, [1, 238]); %!test %! load examgrades %! [h, pval, W, df] = levene_test (grades(:,[3,4]), 'median'); %! assert_equal (h, 0); %! assert_equal (pval, 0.1978225622063785, 2e-14); %! assert_equal (W, 1.66768, 1e-5); %! assert_equal (df, [1, 238]); statistics-release-1.9.2/inst/Hypothesis_Testing/lillietest.m000066400000000000000000000716531524624707500245240ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} lillietest (@var{x}) ## @deftypefnx {statistics} {@var{h} =} lillietest (@var{x}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{h}, @var{p}] =} lillietest (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{p}, @var{kstat}, @var{critval}] =} lillietest (@dots{}) ## ## Lilliefors goodness-of-fit hypothesis test. ## ## @code{@var{h} = lillietest (@var{x})} tests the null hypothesis that the ## sample in the vector @var{x} comes from a normal distribution with unknown ## mean and variance, against the alternative that it does not, using the ## Lilliefors test. @var{h} is 1 if the test rejects the null at the 5% ## significance level and 0 otherwise. ## ## The Lilliefors statistic is the Kolmogorov-Smirnov statistic --- the maximum ## absolute difference between the empirical cumulative distribution function of ## @var{x} and the cumulative distribution function of the hypothesized family ## with parameters estimated from @var{x}. Because the parameters are estimated, ## the null distribution of the statistic differs from that of the ordinary ## Kolmogorov-Smirnov test. ## ## The following @qcode{Name-Value} pairs are supported: ## ## @multitable @columnfractions 0.2 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Distribution'} @tab The hypothesized family: ## @qcode{'normal'} (default), @qcode{'exponential'}, or ## @qcode{'extreme value'}. The parameters are estimated from @var{x}: mean and ## standard deviation for the normal, mean for the exponential, and location and ## scale for the extreme value distribution. ## ## @item @qcode{'Alpha'} @tab The significance level, a scalar. Without ## @qcode{'MCTol'} it must lie in @math{[0.001, 0.5]} (the tabulated range); with ## @qcode{'MCTol'} it may be any value in @math{(0, 1)}. The default is ## @math{0.05}. ## ## @item @qcode{'MCTol'} @tab Maximum Monte-Carlo standard error for the ## p-value. When supplied, the p-value and critical value are computed by ## Monte-Carlo simulation instead of by interpolating the embedded table. ## @end multitable ## ## @code{[@var{h}, @var{p}, @var{kstat}, @var{critval}] = lillietest (@dots{})} ## also returns the p-value @var{p}, the test statistic @var{kstat}, and the ## critical value @var{critval}. Without @qcode{'MCTol'} the p-value is clamped ## to the tabulated range @math{[0.001, 0.5]} and a warning is issued when it ## lies outside. The warning is an addition here: MATLAB clamps silently, so a ## p-value reported as @math{0.001} or @math{0.5} there may be a bound rather ## than an estimate, with nothing to say so. ## ## @seealso{kstest, adtest, jbtest} ## @end deftypefn function [h, p, kstat, critval] = lillietest (x, varargin) if (nargin < 1) print_usage (); endif if (! (isnumeric (x) && isreal (x) && isvector (x))) error ("lillietest: X must be a vector of real values."); endif x = x(! isnan (x)); x = x(:); n = numel (x); if (n < 4) error ("lillietest: X must contain at least four non-missing values."); endif ## Defaults and Name-Value parsing distribution = "normal"; alpha = 0.05; mctol = []; if (mod (numel (varargin), 2) != 0) error ("lillietest: optional arguments must be Name-Value pairs."); endif for k = 1:2:numel (varargin) if (! ischar (varargin{k})) error ("lillietest: parameter names must be character vectors."); endif switch (lower (varargin{k})) case "distribution" distribution = lower (varargin{k+1}); case "alpha" alpha = varargin{k+1}; case "mctol" mctol = varargin{k+1}; otherwise error ("lillietest: unknown parameter name '%s'.", varargin{k}); endswitch endfor if (strcmp (distribution, "ev")) distribution = "extreme value"; endif if (! any (strcmp (distribution, {"normal", "exponential", "extreme value"}))) error ("lillietest: unrecognised 'Distribution' value."); endif domc = ! isempty (mctol); if (! (isnumeric (alpha) && isscalar (alpha) && isreal (alpha) ... && alpha > 0 && alpha < 1)) error ("lillietest: 'Alpha' must be a scalar in the range (0,1)."); endif if (! domc && (alpha < 0.001 || alpha > 0.5)) error ("lillietest: 'Alpha' must be in [0.001, 0.5] unless 'MCTol' is set."); endif if (domc && ! (isnumeric (mctol) && isscalar (mctol) && isreal (mctol) ... && mctol > 0)) error ("lillietest: 'MCTol' must be a positive scalar."); endif ## Lilliefors (Kolmogorov-Smirnov) statistic with estimated parameters kstat = lillietest_stat_ (x, distribution); if (domc) ## Monte-Carlo p-value and critical value. REPS is chosen so the worst-case ## standard error of the estimated p-value (at p = 0.5) is below MCTOL. reps = max (1000, ceil (0.25 / mctol ^ 2)); sim = lillietest_simulate_ (n, distribution, reps); p = (1 + sum (sim >= kstat)) / (reps + 1); critval = quantile (sim, 1 - alpha); else ## Interpolate the p-value and critical value from the embedded tables. [p, critval] = lillietest_interp_ (kstat, n, alpha, distribution); endif h = double (kstat > critval); endfunction ## Kolmogorov-Smirnov statistic of X against DISTRIBUTION fitted to X. function ks = lillietest_stat_ (x, distribution) x = sort (x(:)); n = numel (x); switch (distribution) case "normal" cdf = normcdf ((x - mean (x)) / std (x)); case "exponential" cdf = expcdf (x, mean (x)); case "extreme value" [mu, sigma] = lillietest_evfit_ (x); cdf = evcdf (x, mu, sigma); endswitch ks = max (max ((1:n)' / n - cdf), max (cdf - (0:n-1)' / n)); endfunction ## Interpolate the p-value P and critical value CRITVAL for an observed ## Lilliefors statistic KSTAT at sample size N and level ALPHA from the embedded ## tables for DISTRIBUTION. Critical values scale roughly as 1/sqrt(N), so the ## size interpolation is carried out on sqrt(N)*CV, which is nearly flat in N. function [p, critval] = lillietest_interp_ (kstat, n, alpha, distribution) [sizes, alphas, CV] = lillietest_table_ (distribution); ## Critical values at the ALPHAS grid for this N (scaled size interpolation). S = sqrt (sizes(:)) .* CV; nn = min (max (n, sizes(1)), sizes(end)); cvrow = interp1 (sizes, S, nn, "linear") / sqrt (n); ## Critical value at the requested ALPHA. critval = interp1 (alphas, cvrow, alpha, "linear"); ## P-value: invert the (decreasing) CVROW-versus-ALPHA relation, interpolating ## on a logarithmic ALPHA axis. Outside the tabulated range clamp and warn. xa = fliplr (cvrow); ya = fliplr (log (alphas)); if (kstat >= xa(end)) warning ("lillietest:pTooSmall", ... "lillietest: P is less than the smallest tabulated value; returning 0.001."); p = 0.001; elseif (kstat <= xa(1)) warning ("lillietest:pTooBig", ... "lillietest: P is greater than the largest tabulated value; returning 0.5."); p = 0.5; else p = exp (interp1 (xa, ya, kstat, "linear")); endif endfunction ## Embedded Lilliefors critical-value tables. CV(i,j) is the upper ## ALPHAS(j) quantile of the null Kolmogorov-Smirnov statistic for sample ## size SIZES(i), estimated by Monte-Carlo simulation (1e6 replications for ## the normal and exponential families, 5e5 for the extreme value). function [sizes, alphas, CV] = lillietest_table_ (distribution) sizes = [ ... 4 5 6 7 8 9 10 11 12 13 14 15 16 18 20 22 25 28 32 37 43 50 60 75 90 ... 110 140 180 250 350 500 750 1000]; alphas = [ ... 0.001 0.0025 0.005 0.01 0.025 0.05 0.075 0.1 0.15 0.2 0.25 0.3 0.4 0.5]; switch (distribution) case "normal" CV = [ ... 0.432607 0.427314 0.421544 0.412974 0.395412 0.375069 0.358773 ... 0.344895 0.321124 0.302680 0.293294 0.285351 0.271140 0.258179 ; ... 0.438947 0.425875 0.413107 0.396753 0.366969 0.342917 0.329250 ... 0.318907 0.302820 0.289536 0.277772 0.266904 0.247692 0.233374 ; ... 0.424197 0.405483 0.388133 0.370576 0.345884 0.323510 0.308511 ... 0.297386 0.281157 0.268855 0.258699 0.249711 0.233599 0.218785 ; ... 0.401078 0.382781 0.367417 0.350468 0.325084 0.303960 0.290422 ... 0.279960 0.264135 0.252140 0.242311 0.233862 0.219170 0.206117 ; ... 0.382388 0.364242 0.348914 0.332627 0.308427 0.287768 0.274805 ... 0.265040 0.250196 0.238797 0.229315 0.221085 0.207026 0.194764 ; ... 0.364615 0.347571 0.333138 0.317227 0.293796 0.274126 0.261612 ... 0.252164 0.238102 0.227230 0.218245 0.210363 0.196932 0.185128 ; ... 0.350372 0.333142 0.318717 0.303202 0.280639 0.261755 0.249869 ... 0.240907 0.227381 0.217025 0.208460 0.200969 0.188100 0.176802 ; ... 0.337136 0.320659 0.306638 0.291628 0.269841 0.251607 0.240013 ... 0.231260 0.218243 0.208146 0.199874 0.192696 0.180365 0.169564 ; ... 0.326036 0.309246 0.295603 0.280640 0.259588 0.241865 0.230680 ... 0.222431 0.209891 0.200234 0.192271 0.185389 0.173559 0.163133 ; ... 0.314469 0.298673 0.285316 0.271092 0.250789 0.233523 0.222730 ... 0.214592 0.202490 0.193221 0.185511 0.178844 0.167435 0.157421 ; ... 0.305048 0.288845 0.276165 0.262540 0.242548 0.225793 0.215372 ... 0.207618 0.195917 0.186890 0.179512 0.173080 0.161981 0.152232 ; ... 0.295728 0.280416 0.267977 0.254389 0.235139 0.219113 0.208875 ... 0.201248 0.189880 0.181118 0.173979 0.167721 0.156942 0.147554 ; ... 0.287612 0.272464 0.260058 0.246934 0.228260 0.212575 0.202683 ... 0.195333 0.184269 0.175844 0.168839 0.162843 0.152429 0.143337 ; ... 0.273032 0.258786 0.246958 0.234047 0.216321 0.201506 0.192084 ... 0.185039 0.174572 0.166536 0.159949 0.154179 0.144349 0.135730 ; ... 0.260964 0.246747 0.235353 0.223405 0.206317 0.192106 0.183158 ... 0.176435 0.166437 0.158796 0.152459 0.147012 0.137595 0.129406 ; ... 0.249352 0.235477 0.224833 0.213555 0.197044 0.183554 0.175025 ... 0.168686 0.159089 0.151819 0.145781 0.140554 0.131558 0.123713 ; ... 0.235149 0.222673 0.212144 0.201195 0.185787 0.172986 0.164863 ... 0.158889 0.149828 0.142935 0.137273 0.132374 0.123917 0.116555 ; ... 0.222915 0.210979 0.201180 0.190964 0.176099 0.164012 0.156384 ... 0.150657 0.142092 0.135606 0.130265 0.125587 0.117581 0.110586 ; ... 0.210179 0.198110 0.188792 0.179350 0.165482 0.153912 0.146722 ... 0.141333 0.133320 0.127227 0.122176 0.117831 0.110342 0.103795 ; ... 0.196029 0.185358 0.176625 0.167508 0.154654 0.143799 0.136996 ... 0.132002 0.124556 0.118849 0.114129 0.110075 0.103108 0.096989 ; ... 0.181729 0.171874 0.163883 0.155465 0.143695 0.133754 0.127462 ... 0.122841 0.115885 0.110575 0.106155 0.102365 0.095866 0.090223 ; ... 0.169503 0.160647 0.153079 0.145094 0.133664 0.124409 0.118640 ... 0.114311 0.107826 0.102841 0.098792 0.095267 0.089218 0.083942 ; ... 0.155301 0.146647 0.139809 0.132754 0.122473 0.113971 0.108667 ... 0.104718 0.098765 0.094271 0.090541 0.087316 0.081766 0.076932 ; ... 0.139819 0.132195 0.126097 0.119306 0.110058 0.102447 0.097644 ... 0.094080 0.088737 0.084677 0.081312 0.078438 0.073444 0.069100 ; ... 0.127896 0.120937 0.115169 0.109184 0.100722 0.093671 0.089265 ... 0.086023 0.081133 0.077428 0.074397 0.071747 0.067197 0.063233 ; ... 0.116099 0.109584 0.104236 0.098821 0.091194 0.084911 0.080996 ... 0.078010 0.073587 0.070262 0.067502 0.065073 0.060960 0.057379 ; ... 0.102927 0.097209 0.092748 0.087942 0.081104 0.075501 0.071965 ... 0.069305 0.065415 0.062456 0.059984 0.057847 0.054186 0.051006 ; ... 0.091297 0.086224 0.082012 0.077744 0.071708 0.066744 0.063638 ... 0.061317 0.057851 0.055223 0.053058 0.051177 0.047953 0.045135 ; ... 0.077577 0.073285 0.069823 0.066221 0.061083 0.056793 0.054122 ... 0.052157 0.049222 0.046991 0.045141 0.043549 0.040812 0.038427 ; ... 0.065480 0.061877 0.059004 0.055924 0.051649 0.048089 0.045856 ... 0.044200 0.041694 0.039800 0.038244 0.036895 0.034586 0.032573 ; ... 0.055045 0.052013 0.049536 0.046920 0.043271 0.040290 0.038414 ... 0.037032 0.034951 0.033367 0.032059 0.030937 0.029003 0.027311 ; ... 0.045036 0.042528 0.040526 0.038397 0.035409 0.032980 0.031446 ... 0.030319 0.028606 0.027306 0.026244 0.025322 0.023740 0.022364 ; ... 0.039021 0.036838 0.035071 0.033266 0.030704 0.028572 0.027242 ... 0.026260 0.024790 0.023673 0.022750 0.021947 0.020573 0.019381]; case "exponential" CV = [ ... 0.621231 0.596647 0.578555 0.557012 0.521002 0.484365 0.460917 ... 0.444253 0.419905 0.400745 0.383966 0.368475 0.340166 0.316284 ; ... 0.581399 0.556666 0.536434 0.512287 0.474415 0.441820 0.421006 ... 0.404518 0.379200 0.360240 0.344923 0.331829 0.308759 0.287711 ; ... 0.549130 0.522356 0.499491 0.474102 0.439000 0.408508 0.388211 ... 0.373014 0.349883 0.331731 0.316949 0.304399 0.283105 0.264562 ; ... 0.517209 0.491806 0.469835 0.445867 0.410891 0.381419 0.362594 ... 0.348257 0.326600 0.309868 0.295926 0.283728 0.263358 0.245867 ; ... 0.490769 0.464785 0.443930 0.420827 0.387555 0.359052 0.340897 ... 0.327455 0.306965 0.291390 0.278345 0.266966 0.247499 0.230833 ; ... 0.466815 0.442620 0.421629 0.399813 0.367723 0.340727 0.323370 ... 0.310404 0.290787 0.275846 0.263617 0.252940 0.234618 0.218591 ; ... 0.446077 0.422144 0.402122 0.381150 0.350113 0.324100 0.307705 ... 0.295307 0.276634 0.262563 0.250843 0.240710 0.223322 0.208134 ; ... 0.429002 0.404610 0.385513 0.364916 0.335123 0.310642 0.294705 ... 0.282768 0.264905 0.251266 0.240049 0.230302 0.213761 0.199317 ; ... 0.413509 0.390479 0.371529 0.351122 0.322359 0.297998 0.282657 ... 0.271245 0.254188 0.240956 0.230112 0.220761 0.204873 0.191056 ; ... 0.397798 0.374918 0.357659 0.337904 0.310339 0.286979 0.272289 ... 0.261265 0.244793 0.232165 0.221657 0.212660 0.197342 0.184012 ; ... 0.385420 0.362808 0.345258 0.327040 0.300080 0.277503 0.263174 ... 0.252428 0.236426 0.224253 0.214177 0.205439 0.190565 0.177775 ; ... 0.373875 0.351966 0.334784 0.316911 0.290465 0.268630 0.254774 ... 0.244329 0.228761 0.217002 0.207292 0.198913 0.184511 0.172025 ; ... 0.364072 0.342485 0.325739 0.307869 0.282036 0.260677 0.247270 ... 0.237103 0.222032 0.210486 0.201023 0.192872 0.178954 0.166851 ; ... 0.343857 0.323300 0.307894 0.290967 0.266588 0.246316 0.233568 ... 0.224021 0.209856 0.198916 0.189997 0.182308 0.169162 0.157810 ; ... 0.328276 0.309911 0.293642 0.277377 0.254224 0.234822 0.222551 ... 0.213464 0.199829 0.189511 0.180993 0.173601 0.161008 0.150168 ; ... 0.313426 0.295155 0.280713 0.264858 0.242481 0.224028 0.212452 ... 0.203722 0.190762 0.180827 0.172700 0.165711 0.153753 0.143437 ; ... 0.295571 0.277971 0.264150 0.249484 0.228211 0.210880 0.199954 ... 0.191659 0.179378 0.170122 0.162511 0.155881 0.144681 0.134978 ; ... 0.280510 0.263116 0.250524 0.236649 0.216378 0.199786 0.189279 ... 0.181516 0.169932 0.161096 0.153832 0.147583 0.136933 0.127748 ; ... 0.262082 0.246758 0.234743 0.221759 0.202973 0.187268 0.177590 ... 0.170250 0.159334 0.151077 0.144293 0.138403 0.128418 0.119827 ; ... 0.245218 0.231082 0.219361 0.206905 0.189182 0.174581 0.165441 ... 0.158687 0.148603 0.140814 0.134523 0.129092 0.119791 0.111731 ; ... 0.227667 0.214598 0.203750 0.192400 0.175912 0.162384 0.153909 ... 0.147586 0.138135 0.130949 0.125084 0.120027 0.111416 0.103943 ; ... 0.211652 0.199281 0.189172 0.178429 0.163277 0.150705 0.142917 ... 0.137061 0.128254 0.121664 0.116249 0.111518 0.103532 0.096619 ; ... 0.194074 0.182575 0.173232 0.163522 0.149612 0.138049 0.130848 ... 0.125503 0.117451 0.111405 0.106425 0.102130 0.094779 0.088454 ; ... 0.175158 0.164330 0.155762 0.146784 0.134325 0.123906 0.117382 ... 0.112549 0.105375 0.099953 0.095476 0.091623 0.085068 0.079387 ; ... 0.159882 0.149858 0.142202 0.134226 0.122746 0.113283 0.107427 ... 0.103009 0.096380 0.091399 0.087288 0.083769 0.077784 0.072603 ; ... 0.144230 0.135727 0.128659 0.121333 0.111164 0.102668 0.097303 ... 0.093303 0.087350 0.082860 0.079165 0.075970 0.070563 0.065865 ; ... 0.128495 0.120499 0.114330 0.107995 0.098821 0.091225 0.086449 ... 0.082928 0.077630 0.073637 0.070350 0.067503 0.062679 0.058544 ; ... 0.113413 0.106294 0.101052 0.095438 0.087302 0.080475 0.076278 ... 0.073160 0.068548 0.065003 0.062119 0.059610 0.055385 0.051715 ; ... 0.096379 0.090613 0.085925 0.080983 0.074111 0.068417 0.064880 ... 0.062242 0.058272 0.055281 0.052822 0.050704 0.047106 0.044004 ; ... 0.081386 0.076633 0.072761 0.068611 0.062700 0.057906 0.054887 ... 0.052647 0.049306 0.046758 0.044696 0.042923 0.039890 0.037265 ; ... 0.068356 0.064361 0.061005 0.057527 0.052616 0.048557 0.046022 ... 0.044147 0.041356 0.039237 0.037494 0.035999 0.033443 0.031237 ; ... 0.055801 0.052452 0.049770 0.046970 0.042947 0.039645 0.037590 ... 0.036061 0.033791 0.032080 0.030666 0.029444 0.027361 0.025564 ; ... 0.048320 0.045422 0.043178 0.040721 0.037250 0.034406 0.032637 ... 0.031303 0.029314 0.027817 0.026586 0.025531 0.023717 0.022158]; case "extreme value" CV = [ ... 0.467021 0.456860 0.444418 0.431023 0.408276 0.384264 0.365779 ... 0.349871 0.332962 0.323214 0.314608 0.306471 0.291057 0.275911 ; ... 0.448392 0.431006 0.415271 0.395238 0.370420 0.351146 0.337934 ... 0.327333 0.309799 0.295607 0.283706 0.273894 0.258973 0.245929 ; ... 0.421319 0.402657 0.387883 0.371815 0.346615 0.324328 0.311050 ... 0.301127 0.286302 0.274484 0.264125 0.254856 0.238705 0.224770 ; ... 0.398601 0.380627 0.364490 0.348233 0.324999 0.304811 0.291665 ... 0.281572 0.266898 0.255673 0.246363 0.238052 0.223434 0.210266 ; ... 0.377539 0.360570 0.346195 0.330320 0.306864 0.287484 0.275298 ... 0.265885 0.251562 0.240710 0.231741 0.223912 0.210332 0.198229 ; ... 0.361815 0.344239 0.329077 0.313848 0.291618 0.272873 0.261196 ... 0.252438 0.238785 0.228321 0.219688 0.212082 0.199061 0.187662 ; ... 0.346846 0.328814 0.314941 0.300596 0.278715 0.260554 0.249057 ... 0.240517 0.227625 0.217625 0.209319 0.202140 0.189702 0.178789 ; ... 0.331757 0.315718 0.302157 0.288110 0.267152 0.249601 0.238490 ... 0.230278 0.217836 0.208272 0.200438 0.193528 0.181568 0.171059 ; ... 0.320460 0.303888 0.291007 0.276929 0.256887 0.239937 0.229379 ... 0.221394 0.209334 0.200147 0.192548 0.185928 0.174399 0.164215 ; ... 0.309684 0.294064 0.281405 0.267452 0.247970 0.231470 0.221160 ... 0.213316 0.201755 0.192862 0.185483 0.179047 0.167980 0.158141 ; ... 0.299198 0.283407 0.271902 0.258334 0.239607 0.223786 0.213675 ... 0.206326 0.195050 0.186394 0.179276 0.173026 0.162254 0.152888 ; ... 0.290486 0.276319 0.263743 0.250518 0.231948 0.216624 0.206943 ... 0.199721 0.188844 0.180445 0.173514 0.167509 0.157080 0.147923 ; ... 0.281127 0.267349 0.255627 0.243245 0.225333 0.210345 0.200878 ... 0.193781 0.183123 0.174925 0.168261 0.162419 0.152372 0.143462 ; ... 0.267045 0.253562 0.242229 0.230420 0.213477 0.199239 0.190140 ... 0.183476 0.173396 0.165658 0.159319 0.153757 0.144232 0.135816 ; ... 0.254628 0.242018 0.231149 0.219530 0.203026 0.189534 0.181088 ... 0.174655 0.165068 0.157735 0.151650 0.146340 0.137210 0.129251 ; ... 0.243894 0.231515 0.221212 0.210322 0.194282 0.181207 0.173041 ... 0.166941 0.157845 0.150884 0.145026 0.139955 0.131286 0.123601 ; ... 0.230118 0.218163 0.208315 0.197830 0.183121 0.170905 0.163143 ... 0.157357 0.148619 0.142000 0.136513 0.131743 0.123490 0.116309 ; ... 0.218954 0.207136 0.197973 0.187592 0.173576 0.161748 0.154470 ... 0.148986 0.140779 0.134457 0.129275 0.124779 0.117027 0.110225 ; ... 0.206085 0.195001 0.185956 0.176596 0.163038 0.151859 0.145003 ... 0.139799 0.132086 0.126162 0.121259 0.116983 0.109696 0.103309 ; ... 0.191852 0.181144 0.172924 0.164409 0.152019 0.141856 0.135444 ... 0.130619 0.123329 0.117806 0.113257 0.109287 0.102451 0.096493 ; ... 0.178237 0.168926 0.161218 0.153063 0.141587 0.131935 0.125944 ... 0.121462 0.114757 0.109633 0.105382 0.101699 0.095344 0.089798 ; ... 0.165755 0.157078 0.149594 0.142057 0.131446 0.122525 0.117103 ... 0.112954 0.106706 0.101940 0.097987 0.094570 0.088671 0.083521 ; ... 0.151898 0.143496 0.137105 0.130317 0.120396 0.112257 0.107215 ... 0.103351 0.097641 0.093274 0.089688 0.086568 0.081173 0.076472 ; ... 0.137316 0.129609 0.123408 0.117120 0.108162 0.100760 0.096212 ... 0.092774 0.087626 0.083716 0.080489 0.077691 0.072876 0.068630 ; ... 0.124853 0.118346 0.112935 0.107155 0.099075 0.092326 0.088203 ... 0.085039 0.080345 0.076713 0.073747 0.071177 0.066762 0.062898 ; ... 0.113566 0.107243 0.102355 0.097262 0.089748 0.083647 0.079847 ... 0.076958 0.072682 0.069463 0.066772 0.064463 0.060478 0.057010 ; ... 0.100706 0.095348 0.090914 0.086360 0.079839 0.074371 0.070940 ... 0.068405 0.064605 0.061760 0.059375 0.057313 0.053730 0.050627 ; ... 0.089322 0.084347 0.080547 0.076367 0.070562 0.065778 0.062768 ... 0.060509 0.057157 0.054613 0.052499 0.050687 0.047544 0.044801 ; ... 0.076039 0.071818 0.068625 0.065032 0.060008 0.055943 0.053394 ... 0.051499 0.048670 0.046506 0.044702 0.043162 0.040497 0.038171 ; ... 0.064185 0.060831 0.057921 0.054922 0.050793 0.047340 0.045195 ... 0.043590 0.041199 0.039371 0.037852 0.036551 0.034314 0.032327 ; ... 0.053752 0.050888 0.048556 0.046068 0.042607 0.039712 0.037883 ... 0.036532 0.034519 0.032993 0.031719 0.030631 0.028739 0.027076 ; ... 0.043902 0.041496 0.039619 0.037640 0.034817 0.032426 0.030982 ... 0.029891 0.028241 0.026987 0.025957 0.025064 0.023517 0.022179 ; ... 0.038071 0.035964 0.034353 0.032583 0.030154 0.028121 0.026853 ... 0.025915 0.024482 0.023403 0.022504 0.021735 0.020398 0.019227]; endswitch endfunction ## Simulate REPS Lilliefors statistics for samples of size N from the (pivotal) ## standardized DISTRIBUTION, evaluated in chunks to bound memory use. function sim = lillietest_simulate_ (n, distribution, reps) sim = zeros (reps, 1); chunk = max (1, floor (2e5 / n)); done = 0; ii = (1:n)' / n; im = (0:n-1)' / n; while (done < reps) m = min (chunk, reps - done); switch (distribution) case "normal" z = randn (n, m); z = sort (z); c = normcdf ((z - mean (z)) ./ std (z)); case "exponential" z = -log (rand (n, m)); z = sort (z); c = 1 - exp (-z ./ mean (z)); case "extreme value" z = log (-log (rand (n, m))); ## standard EV (minima) draws z = sort (z); [mu, sigma] = lillietest_evfit_ (z); ## column-wise fit c = 1 - exp (-exp ((z - mu) ./ sigma)); endswitch sim(done+1:done+m) = max (max (ii - c), max (c - im)); done += m; endwhile endfunction ## Maximum-likelihood fit of the extreme value (minima Gumbel) distribution, ## vectorised over the columns of X. The scale SIGMA solves the monotone score ## equation g(sigma) = E_w[x] - xbar - sigma = 0, where the weights w_i are ## proportional to exp (x_i / sigma). It is found by bisection: g is positive ## as sigma -> 0 (E_w[x] -> max (x)) and negative for large sigma, so the root ## is unique and bracketed. A fixed-point iteration on the same equation is not ## contractive for all samples and diverges on a small fraction of them, so ## bisection is used here. Returns row vectors MU and SIGMA. function [mu, sigma] = lillietest_evfit_ (x) xbar = mean (x); lo = 1e-8 * ones (1, columns (x)); hi = (max (x) - xbar) + 10; for iter = 1:80 sigma = 0.5 * (lo + hi); mx = max (x); w = exp ((x - mx) ./ sigma); g = sum (x .* w) ./ sum (w) - xbar - sigma; pos = g > 0; lo(pos) = sigma(pos); hi(! pos) = sigma(! pos); endfor sigma = 0.5 * (lo + hi); mx = max (x); w = exp ((x - mx) ./ sigma); mu = mx + sigma .* log (sum (w) / rows (x)); endfunction %!demo %! ## Test whether a sample is normally distributed %! x = [1 2 3 4 5 6 7 8 9 50]; # last value is an outlier %! [h, p, kstat] = lillietest (x) %!test # statistic matches a direct Kolmogorov-Smirnov computation and MATLAB %! warning ("off", "lillietest:pTooBig", "local"); %! warning ("off", "lillietest:pTooSmall", "local"); %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]'; %! [~, ~, ks] = lillietest (x); %! xs = sort (x); n = numel (x); %! cdf = normcdf ((xs - mean (x)) / std (x)); %! d = max (max ((1:n)'/n - cdf), max (cdf - (0:n-1)'/n)); %! assert_equal (ks, d, 1e-12); %! assert_equal (ks, 0.1026, 5e-4); # MATLAB lillietest reference %!test # exponential and extreme value statistics match MATLAB references %! warning ("off", "lillietest:pTooBig", "local"); %! warning ("off", "lillietest:pTooSmall", "local"); %! xe = [0.5 1.2 0.3 2.1 0.8 1.5 0.2 3.0 0.7 1.1 0.4 2.5]'; %! [~, ~, kse] = lillietest (xe, "Distribution", "exponential"); %! assert_equal (kse, 0.1545, 5e-4); %! xn = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]'; %! [~, ~, ksv] = lillietest (xn, "Distribution", "extreme value"); %! assert_equal (ksv, 0.1512, 5e-4); %!test # a clearly non-normal sample is rejected; h, p, critval consistent %! warning ("off", "lillietest:pTooSmall", "local"); %! x = [zeros(1, 15), 100]; %! [h, p, ks, cv] = lillietest (x); %! assert_equal (h, 1); %! assert_equal (h, double (ks > cv)); %! assert_equal (p, 0.001); # clamped to the tabulated minimum %!test # distribution families run and return a decision %! warning ("off", "lillietest:pTooBig", "local"); %! warning ("off", "lillietest:pTooSmall", "local"); %! x = -log (rand (30, 1)); %! assert_equal (ismember (lillietest (x, "Distribution", "exponential"), [0 1]), true); %! assert_equal (ismember (lillietest (x, "Distribution", "extreme value"), [0 1]), true); %!test # p-value stays in the tabulated range; critval grows as alpha falls %! warning ("off", "lillietest:pTooBig", "local"); %! warning ("off", "lillietest:pTooSmall", "local"); %! x = [3 1 4 1 5 9 2 6 5 3 5 8 9 7]'; %! [~, p, ~, cv05] = lillietest (x); %! [~, ~, ~, cv01] = lillietest (x, "Alpha", 0.01); %! assert_equal (p >= 0.001 && p <= 0.5, true); %! assert_equal (cv01 > cv05, true); %!test # Monte-Carlo path runs and returns a valid p-value %! warning ("off", "lillietest:pTooBig", "local"); %! warning ("off", "lillietest:pTooSmall", "local"); %! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]'; %! [h, p] = lillietest (x, "MCTol", 0.05); %! assert_equal (ismember (h, [0 1]), true); %! assert_equal (p > 0 && p <= 1, true); %!test # interpolation works for a sample size off the table grid (n = 17) %! warning ("off", "lillietest:pTooBig", "local"); %! warning ("off", "lillietest:pTooSmall", "local"); %! x = [1 3 2 5 4 7 6 9 8 11 10 13 12 15 14 17 16]'; %! [~, ~, ~, cv] = lillietest (x); %! assert_equal (isfinite (cv) && cv > 0, true); %!warning ... %! lillietest (norminv ((1:20)' / 21)); %!warning ... %! lillietest ([zeros(1, 15), 100]); ## Test input validation %!error lillietest () %!error lillietest (ones (3, 3)) %!error ... %! lillietest ([1 2 3]) %!error ... %! lillietest (1:10, "Distribution", "poisson") %!error ... %! lillietest (1:10, "Alpha", 0) %!error ... %! lillietest (1:10, "Alpha", 0.75) %!error ... %! lillietest (1:10, "MCTol", -1) statistics-release-1.9.2/inst/Hypothesis_Testing/manova1.m000066400000000000000000000215151524624707500237040ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{d} =} manova1 (@var{x}, @var{group}) ## @deftypefnx {statistics} {@var{d} =} manova1 (@var{x}, @var{group}, @var{alpha}) ## @deftypefnx {statistics} {[@var{d}, @var{p}] =} manova1 (@dots{}) ## @deftypefnx {statistics} {[@var{d}, @var{p}, @var{stats}] =} manova1 (@dots{}) ## ## One-way multivariate analysis of variance (MANOVA). ## ## @code{@var{d} = manova1 (@var{x}, @var{group}, @var{alpha})} performs a ## one-way MANOVA for comparing the mean vectors of two or more groups of ## multivariate data. ## ## @var{x} is a matrix with each row representing a multivariate observation, ## and each column representing a variable. ## ## @var{group} is a numeric vector, string array, or cell array of strings with ## the same number of rows as @var{x}. @var{x} values are in the same group if ## they correspond to the same value of GROUP. ## ## @var{alpha} is the scalar significance level and is 0.05 by default. ## ## @var{d} is an estimate of the dimension of the group means. It is the ## smallest dimension such that a test of the hypothesis that the means lie on ## a space of that dimension is not rejected. If @var{d} = 0 for example, we ## cannot reject the hypothesis that the means are the same. If @var{d} = 1, we ## reject the hypothesis that the means are the same but we cannot reject the ## hypothesis that they lie on a line. ## ## @code{[@var{d}, @var{p}] = manova1 (@dots{})} returns P, a vector of p-values ## for testing the null hypothesis that the mean vectors of the groups lie on ## various dimensions. P(1) is the p-value for a test of dimension 0, P(2) for ## dimension 1, etc. ## ## @code{[@var{d}, @var{p}, @var{stats}] = manova1 (@dots{})} returns a STATS ## structure with the following fields: ## ## @multitable @columnfractions 0.2 0.75 ## @item "W" @tab within-group sum of squares and products matrix ## @item "B" @tab between-group sum of squares and products matrix ## @item "T" @tab total sum of squares and products matrix ## @item "dfW" @tab degrees of freedom for WSSP matrix ## @item "dfB" @tab degrees of freedom for BSSP matrix ## @item "dfT" @tab degrees of freedom for TSSP matrix ## @item "lambda" @tab value of Wilk's lambda (the test statistic) ## @item "chisq" @tab transformation of lambda to a chi-square distribution ## @item "chisqdf" @tab degrees of freedom for chisq ## @item "eigenval" @tab eigenvalues of (WSSP^-1) * BSSP ## @item "eigenvec" @tab eigenvectors of (WSSP^-1) * BSSP; these are the ## coefficients for canonical variables, and they are scaled so the within-group ## variance of C is 1 ## @item "canon" @tab canonical variables, equal to XC*eigenvec, where XC ## is X with columns centered by subtracting their means ## @item "mdist" @tab Mahalanobis distance from each point to its group ## mean ## @item "gmdist" @tab Mahalanobis distances between each pair of group ## means ## @item "gnames" @tab Group names ## @end multitable ## ## The canonical variables C have the property that C(:,1) is the linear ## combination of the @var{x} columns that has the maximum separation between ## groups, C(:,2) has the maximum separation subject to it being orthogonal to ## C(:,1), and so on. ## ## @end deftypefn function [d, p, stats] = manova1 (x, group, alpha) ## Check input arguments narginchk (2,3) nargoutchk (1,3) ## Validate alpha value if parsed or add default if (nargin > 2) if (length (alpha) > 1 || ! isreal (alpha)) error ("manova1: Alpha must be a real scalar."); elseif (alpha <= 0 || alpha >= 1) error ("manova1: Alpha must be in the range (0,1)."); endif else alpha = 0.05; endif ## Convert group to cell array from character array if (ischar (group)) group = cellstr (group); endif ## Make group a column if (size (group, 1) == 1) group = group'; endif ## Check for equal size in samples between groups and data if (size (group, 1) != size (x, 1)) error ("manova1: Samples in X and groups mismatch."); endif ## Remove samples (rows) in X and GROUP if there are missing values in X no_nan = (sum (isnan (x), 2) == 0); x = x(no_nan, :); group = group(no_nan, :); is_nan = ! no_nan; ## Get group names and indices [group_idx, group_names] = grp2idx (group); ngroups = length (group_names); ## Remove NaN values from updated GROUP no_nan = ! isnan (group_idx); if (! all (no_nan)) group_idx = group_idx(no_nan); x = x(no_nan,: ); is_nan(! is_nan) = ! no_nan; endif ## Get number of samples and variables [nsample, nvar] = size (x); realgroups = ismember (1:ngroups, group_idx); nrgroups = sum (realgroups); ## Calculate Total Sum of Squares and Products matrix xm = mean (x); x = x - xm; TSSP = x' * x; ## Calculate Within-samples Sum of Squares and Products matrix WSSP = zeros (size (TSSP)); for j = 1:ngroups row = find (group_idx == j); ## Only meaningful for groups with more than one samples if (length (row) > 1) group_x = x(row, :); group_x = group_x - mean (group_x); WSSP = WSSP + group_x' * group_x; endif endfor ## Calculate Between-samples Sum of Squares and Products matrix BSSP = TSSP - WSSP; ## Instead of simply computing `eig (BSSP / WSSP)` we use Matlab's technique ## with chol to insure v' * WSSP * v = I is met [R, p] = chol (WSSP); if (p > 0) error ("manova1: Cannot factorize WSSP."); endif S = R' \ BSSP / R; ## Remove asymmetry caused by roundoff S = (S + S') / 2; [vv, ed] = eig (S); v = R \ vv; ## Sort in descending order [e,ei] = sort (diag (ed)); ## Check for valid eigenvalues if (min (e) <= -1) error ("manova1: wrong value in eigenvector: singular sum of squares."); endif ## Compute Bartlett's statistic for each dimension dims = 0:(min (nrgroups - 1, nvar) - 1); lambda = flipud (1 ./ cumprod (e + 1)); lambda = lambda(1 + dims); chistat = -(nsample - 1 - (nrgroups + nvar) / 2) .* log (lambda); chisqdf = ((nvar - dims) .* (nrgroups - 1 - dims))'; pp = 1 - chi2cdf (chistat, chisqdf); ## Get dimension where we can reject the null hypothesis d = dims(pp>alpha); if (length (d) > 0) d = d(1); else d = max (dims) + 1; endif ## Create extra outputs as necessary if (nargout > 1) p = pp; endif if (nargout > 2) stats.W = WSSP; stats.B = BSSP; stats.T = TSSP; stats.dfW = nsample - nrgroups; stats.dfB = nrgroups - 1; stats.dfT = nsample - 1; stats.lambda = lambda; stats.chisq = chistat; stats.chisqdf = chisqdf; ## Reorder to increasing stats.eigenval = flipud (e); ## Flip so that it is in order of increasing eigenvalues v = v(:, flipud (ei)); ## Re-scale eigenvectors so the within-group variance is 1 vs = diag ((v' * WSSP * v))' ./ (nsample - nrgroups); vs(vs<=0) = 1; v = v ./ repmat (sqrt (vs), size (v,1), 1); ## Flip sign so that the average element is positive j = (sum (v) < 0); v(:,j) = -v(:,j); stats.eigenvec = v; canon = x*v; if (any (is_nan)) tmp(! is_nan,:) = canon; tmp(is_nan,:) = NaN; stats.canon = tmp; else stats.canon = canon; endif ## Compute Mahalanobis distances from points to group means gmean = nan (ngroups, size (canon, 2)); gmean(realgroups,:) = grpstats (canon, group_idx); mdist = sum ((canon - gmean(group_idx,:)) .^ 2, 2); if (any (is_nan)) stats.mdist(! is_nan) = mdist; stats.mdist(is_nan) = NaN; else stats.mdist = mdist; endif ## Compute Mahalanobis distances between group means stats.gmdist = squareform (pdist (gmean)) .^ 2; stats.gnames = group_names; endif endfunction %!demo %! load carbig %! [d,p] = manova1 ([MPG, Acceleration, Weight, Displacement], Origin) %!test %! load carbig %! [d,p] = manova1 ([MPG, Acceleration, Weight, Displacement], Origin); %! assert_equal (d, 3); %! assert_equal (p, [0, 3.140583347827075e-07, 0.007510999577743149, ... %! 0.1934100745898493]', [1e-12, 1e-12, 1e-12, 1e-12]'); %!test %! load carbig %! [d,p] = manova1 ([MPG, Acceleration, Weight], Origin); %! assert_equal (d, 2); %! assert_equal (p, [0, 0.00516082975137544, 0.1206528056514453]', ... %! [1e-12, 1e-12, 1e-12]'); statistics-release-1.9.2/inst/Hypothesis_Testing/mcnemar_test.m000066400000000000000000000154761524624707500250340ustar00rootroot00000000000000## Copyright (C) 1996-2017 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{h}, @var{pval}, @var{chisq}] =} mcnemar_test (@var{x}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{chisq}] =} mcnemar_test (@var{x}, @var{alpha}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{chisq}] =} mcnemar_test (@var{x}, @var{testtype}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{chisq}] =} mcnemar_test (@var{x}, @var{alpha}, @var{testtype}) ## ## Perform a McNemar's test on paired nominal data. ## ## @nospell{McNemar's} test is applied to a @math{2*2} contingency table @var{x} ## with a dichotomous trait, with matched pairs of subjects, of data ## cross-classified on the row and column variables to testing the null ## hypothesis of symmetry of the classification probabilities. More formally, ## the null hypothesis of marginal homogeneity states that the two marginal ## probabilities for each outcome are the same. ## ## Under the null, with a sufficiently large number of discordants ## (@qcode{@var{x}(1,2) + @var{x}(2,1) >= 25}), the test statistic, @var{chisq}, ## follows a chi-squared distribution with 1 degree of freedom. When the number ## of discordants is less than 25, then the mid-P exact McNemar test is used. ## ## @var{testtype} will force @code{mcnemar_test} to apply a particular method ## for testing the null hypothesis independently of the number of discordants. ## Valid options for @var{testtype}: ## @itemize ## @item @qcode{'asymptotic'} Original McNemar test statistic ## @item @qcode{'corrected'} Edwards' version with continuity correction ## @item @qcode{'exact'} An exact binomial test ## @item @qcode{'mid-p'} The mid-P McNemar test (mid-p binomial test) ## @end itemize ## ## The test decision is returned in @var{h}, which is 1 when the null hypothesis ## is rejected (@qcode{@var{pval} < @var{alpha}}) or 0 otherwise. @var{alpha} ## defines the critical value of statistical significance for the test. ## ## Further information about the McNemar's test can be found at ## @url{https://en.wikipedia.org/wiki/McNemar%27s_test} ## ## @seealso{crosstab, chi2test, fishertest} ## @end deftypefn function [h, pval, chisq] = mcnemar_test (x, varargin) ## Check for valid number of input arguments if (nargin > 3) error ("mcnemar_test: too many input arguments."); endif ## Check contingency table if (! isequal (size (x), [2, 2])) error ("mcnemar_test: X must be a 2x2 matrix."); elseif (! (all ((x(:) >= 0)) && all (x(:) == fix (x(:))))) error ("mcnemar_test: all entries of X must be non-negative integers."); endif ## Add defaults alpha = 0.05; b = x(1,2); c = x(2,1); if (b + c < 25) testtype = 'mid-p'; else testtype = 'asymptotic'; endif ## Parse optional arguments if (nargin == 2) if (isnumeric (varargin{1})) alpha = varargin{1}; elseif (ischar (varargin{1})) testtype = varargin{1}; else error ("mcnemar_test: invalid 2nd input argument."); endif elseif (nargin == 3) alpha = varargin{1}; testtype = varargin{2}; endif ## Check optional arguments if (! isscalar (alpha) || alpha <= 0 || alpha >= 1) error ("mcnemar_test: invalid value for ALPHA."); endif types = {'exact', 'asymptotic', 'mid-p', 'corrected'}; if (! any (strcmpi (testtype, types))) error ("mcnemar_test: invalid value for TESTTYPE."); endif ## Calculate test switch (lower (testtype)) case 'asymptotic' chisq = (b - c) .^2 / (b + c); pval = 1 - chi2cdf (chisq, 1); case 'corrected' chisq = (abs (b - c) - 1) .^2 / (b + c); pval = 1 - chi2cdf (chisq, 1); case 'exact' chisq = []; pval = 2 * (binocdf (b, b + c, 0.5)); case 'mid-p' chisq = []; pval = 2 * (binocdf (b, b + c, 0.5)) - binopdf (b, b + c, 0.5); endswitch ## Get null hypothesis test result if (pval < alpha) h = 1; else h = 0; endif endfunction %!test %! [h, pval, chisq] = mcnemar_test ([101,121;59,33]); %! assert_equal (h, 1); %! assert_equal (pval, 3.8151e-06, 1e-10); %! assert_equal (chisq, 21.356, 1e-3); %!test %! [h, pval, chisq] = mcnemar_test ([59,6;16,80]); %! assert_equal (h, 1); %! assert_equal (pval, 0.034690, 1e-6); %! assert_equal (isempty (chisq), true); %!test %! [h, pval, chisq] = mcnemar_test ([59,6;16,80], 0.01); %! assert_equal (h, 0); %! assert_equal (pval, 0.034690, 1e-6); %! assert_equal (isempty (chisq), true); %!test %! [h, pval, chisq] = mcnemar_test ([59,6;16,80], 'mid-p'); %! assert_equal (h, 1); %! assert_equal (pval, 0.034690, 1e-6); %! assert_equal (isempty (chisq), true); %!test %! [h, pval, chisq] = mcnemar_test ([59,6;16,80], 'asymptotic'); %! assert_equal (h, 1); %! assert_equal (pval, 0.033006, 1e-6); %! assert_equal (chisq, 4.5455, 1e-4); %!test %! [h, pval, chisq] = mcnemar_test ([59,6;16,80], 'exact'); %! assert_equal (h, 0); %! assert_equal (pval, 0.052479, 1e-6); %! assert_equal (isempty (chisq), true); %!test %! [h, pval, chisq] = mcnemar_test ([59,6;16,80], 'corrected'); %! assert_equal (h, 0); %! assert_equal (pval, 0.055009, 1e-6); %! assert_equal (chisq, 3.6818, 1e-4); %!test %! [h, pval, chisq] = mcnemar_test ([59,6;16,80], 0.1, 'corrected'); %! assert_equal (h, 1); %! assert_equal (pval, 0.055009, 1e-6); %! assert_equal (chisq, 3.6818, 1e-4); %!error mcnemar_test (59, 6, 16, 80) %!error mcnemar_test (ones (3, 3)) %!error ... %! mcnemar_test ([59,6;16,-80]) %!error ... %! mcnemar_test ([59,6;16,4.5]) %!error ... %! mcnemar_test ([59,6;16,80], {''}) %!error ... %! mcnemar_test ([59,6;16,80], -0.2) %!error ... %! mcnemar_test ([59,6;16,80], [0.05, 0.1]) %!error ... %! mcnemar_test ([59,6;16,80], 1) %!error ... %! mcnemar_test ([59,6;16,80], '') statistics-release-1.9.2/inst/Hypothesis_Testing/multcompare.m000066400000000000000000001510101524624707500246640ustar00rootroot00000000000000## Copyright (C) 2022 Andrew Penn ## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{C} =} multcompare (@var{STATS}) ## @deftypefnx {statistics} {@var{C} =} multcompare (@var{STATS}, "name", @var{value}) ## @deftypefnx {statistics} {[@var{C}, @var{M}] =} multcompare (...) ## @deftypefnx {statistics} {[@var{C}, @var{M}, @var{H}] =} multcompare (...) ## @deftypefnx {statistics} {[@var{C}, @var{M}, @var{H}, @var{GNAMES}] =} multcompare (...) ## @deftypefnx {statistics} {@var{padj} =} multcompare (@var{p}) ## @deftypefnx {statistics} {@var{padj} =} multcompare (@var{p}, "ctype", @var{CTYPE}) ## ## Perform posthoc multiple comparison tests or p-value adjustments to control ## the family-wise error rate (FWER) or false discovery rate (FDR). ## ## @code{@var{C} = multcompare (@var{STATS})} performs a multiple comparison ## using a @var{STATS} structure that is obtained as output from any of ## the following functions: anova1, anova2, anovan, kruskalwallis, and friedman. ## The return value @var{C} is a matrix with one row per comparison and six ## columns. Columns 1-2 are the indices of the two samples being compared. ## Columns 3-5 are a lower bound, estimate, and upper bound for their ## difference, where the bounds are for 95% confidence intervals. Column 6-8 are ## the multiplicity adjusted p-values for each individual comparison, the test ## statistic and the degrees of freedom. ## All tests by multcompare are two-tailed. ## ## @qcode{multcompare} can take a number of optional parameters as name-value ## pairs. ## ## @code{[@dots{}] = multcompare (@var{STATS}, "alpha", @var{ALPHA})} ## ## @itemize ## @item ## @var{ALPHA} sets the significance level of null hypothesis significance ## tests to ALPHA, and the central coverage of two-sided confidence intervals to ## 100*(1-@var{ALPHA})%. (Default ALPHA is 0.05). ## @end itemize ## ## @code{[@dots{}] = multcompare (@var{STATS}, "ControlGroup", @var{REF})} ## ## @itemize ## @item ## @var{REF} is the index of the control group to limit comparisons to. The ## index must be a positive integer scalar value. For each dimension (d) listed ## in @var{DIM}, multcompare uses STATS.grpnames@{d@}(idx) as the control group. ## (Default is empty, i.e. [], for full pairwise comparisons) ## @end itemize ## ## @code{[@dots{}] = multcompare (@var{STATS}, "ctype", @var{CTYPE})} ## ## @itemize ## @item ## @var{CTYPE} is the type of comparison test to use. In order of increasing ## power, the choices are: "bonferroni", "scheffe", "mvt", "holm" (default), ## "hochberg", "fdr", or "lsd". The first five methods control the family-wise ## error rate. The "fdr" method controls false discovery rate (by the original ## Benjamini-Hochberg step-up procedure). The final method, "lsd" (or "none"), ## makes no attempt to control the Type 1 error rate of multiple comparisons. ## The coverage of confidence intervals are only corrected for multiple ## comparisons in the cases where @var{CTYPE} is "bonferroni", "scheffe" or ## "mvt", which control the Type 1 error rate for simultaneous inference. ## ## The "mvt" method uses the multivariate t distribution to assess the ## probability or critical value of the maximum statistic across the tests, ## thereby accounting for correlations among comparisons in the control of the ## family-wise error rate with simultaneous inference. In the case of pairwise ## comparisons, it simulates Tukey's (or the Games-Howell) test, in the case of ## comparisons with a single control group, it simulates Dunnett's test. ## @var{CTYPE} values "tukey-kramer" and "hsd" are recognised but set the value ## of @var{CTYPE} and @var{REF} to "mvt" and empty respectively. A @var{CTYPE} ## value "dunnett" is recognised but sets the value of @var{CTYPE} to "mvt", and ## if @var{REF} is empty, sets @var{REF} to 1. Since the algorithm uses a Monte ## Carlo method (of 1e+06 random samples), you can expect the results to ## fluctuate slightly with each call to multcompare and the calculations may be ## slow to complete for a large number of comparisons. If the parallel package ## is installed and loaded, @qcode{multcompare} will automatically accelerate ## computations by parallel processing. Note that p-values calculated by the ## "mvt" are truncated at 1e-06. ## @end itemize ## ## @code{[@dots{}] = multcompare (@var{STATS}, "df", @var{DF})} ## ## @itemize ## @item ## @var{DF} is an optional scalar value to set the number of degrees of freedom ## in the calculation of p-values for the multiple comparison tests. By default, ## this value is extracted from the @var{STATS} structure of the ANOVA test, but ## setting @var{DF} maybe necessary to approximate Satterthwaite correction if ## @qcode{anovan} was performed using weights. ## @end itemize ## ## @code{[@dots{}] = multcompare (@var{STATS}, "dim", @var{DIM})} ## ## @itemize ## @item ## @var{DIM} is a vector specifying the dimension or dimensions over which the ## estimated marginal means are to be calculated. Used only if STATS comes from ## anovan. The value [1 3], for example, computes the estimated marginal mean ## for each combination of the first and third predictor values. The default is ## to compute over the first dimension (i.e. 1). If the specified dimension is, ## or includes, a continuous factor then @qcode{multcompare} will return an ## error. ## @end itemize ## ## @code{[@dots{}] = multcompare (@var{STATS}, "estimate", @var{ESTIMATE})} ## ## @itemize ## @item ## @var{ESTIMATE} is a string specifying the estimates to be compared when ## computing multiple comparisons after anova2; this argument is ignored by ## anovan and anova1. Accepted values for @var{ESTIMATE} are either "column" ## (default) to compare column means, or "row" to compare row means. If the ## model type in anova2 was "linear" or "nested" then only "column" is accepted ## for @var{ESTIMATE} since the row factor is assumed to be a random effect. ## @end itemize ## ## @code{[@dots{}] = multcompare (@var{STATS}, "display", @var{DISPLAY})} ## ## @itemize ## @item ## @var{DISPLAY} is either "on" (the default): to display a table and graph of ## the comparisons (e.g. difference between means), their 100*(1-@var{ALPHA})% ## intervals and multiplicity adjusted p-values in APA style; or "off": to omit ## the table and graph. On the graph, markers and error bars colored red have ## multiplicity adjusted p-values < ALPHA, otherwise the markers and error bars ## are blue. ## @end itemize ## ## @code{[@dots{}] = multcompare (@var{STATS}, "seed", @var{SEED})} ## ## @itemize ## @item ## @var{SEED} is a scalar value used to initialize the random number generator ## so that @var{CTYPE} "mvt" produces reproducible results. ## @end itemize ## ## @code{[@var{C}, @var{M}, @var{H}, @var{GNAMES}] = multcompare (@dots{})} ## returns additional outputs. @var{M} is a matrix where columns 1-2 are the ## estimated marginal means and their standard errors, and columns 3-4 are lower ## and upper bounds of the confidence intervals for the means; the critical ## value of the test statistic is scaled by a factor of 2^(-0.5) before ## multiplying by the standard errors of the group means so that the intervals ## overlap when the difference in means becomes significant at approximately ## the level @var{ALPHA}. When @var{ALPHA} is 0.05, this corresponds to ## confidence intervals with 83.4% central coverage. @var{H} is a handle to the ## figure containing the graph. @var{GNAMES} is a cell array with one row for ## each group, containing the names of the groups. ## ## @code{@var{padj} = multcompare (@var{p})} calculates and returns adjusted ## p-values (@var{padj}) using the Holm-step down Bonferroni procedure to ## control the family-wise error rate. ## ## @code{@var{padj} = multcompare (@var{p}, "ctype", @var{CTYPE})} calculates ## and returns adjusted p-values (@var{padj}) computed using the method ## @var{CTYPE}. In order of increasing power, @var{CTYPE} for p-value adjustment ## can be either "bonferroni", "holm" (default), "hochberg", or "fdr". See ## above for further information about the @var{CTYPE} methods. ## ## @seealso{anova1, anova2, anovan, kruskalwallis, friedman, fitlm} ## @end deftypefn function [C, M, H, GNAMES] = multcompare (STATS, varargin) if (nargin < 1) print_usage; endif ## Check supplied parameters if ((numel (varargin) / 2) != fix (numel (varargin) / 2)) error ("multcompare: wrong number of arguments.") endif ALPHA = 0.05; REF = []; CTYPE = 'holm'; DISPLAY = 'on'; DIM = 1; ESTIMATE = 'column'; DFE = []; for idx = 3:2:nargin name = varargin{idx-2}; value = varargin{idx-1}; switch (lower (name)) case 'alpha' ALPHA = value; case {'controlgroup','ref'} REF = value; case {'ctype','criticalvaluetype'} CTYPE = lower (value); case {'display','displayopt'} DISPLAY = lower (value); case {'dim','dimension'} DIM = value; case 'estimate' ESTIMATE = lower (value); case {'df','dfe'} DFE = value; case {'seed'} SEED = value; ## Set random seed for mvtrnd and mvnrnd randn ('seed', SEED); randg ('seed', SEED); otherwise error ("multcompare: parameter %s is not supported.", name); endswitch endfor ## Evaluate ALPHA input argument if (! isa (ALPHA,'numeric') || numel (ALPHA) != 1) error ("multcompare:alpha must be a numeric scalar value."); endif if ((ALPHA <= 0) || (ALPHA >= 1)) error ("multcompare: alpha must be a value between 0 and 1."); endif ## Evaluate CTYPE input argument if (ismember (CTYPE, {'tukey-kramer', 'hsd'})) CTYPE = 'mvt'; REF = []; elseif (strcmp (CTYPE, 'dunnett')) CTYPE = 'mvt'; if (isempty (REF)) REF = 1; endif elseif (strcmp (CTYPE, 'none')) CTYPE = 'lsd'; endif if (! ismember (CTYPE, ... {'bonferroni','scheffe','mvt','holm','hochberg','fdr','lsd'})) error ("multcompare: '%s' is not a supported value for CTYPE.", CTYPE) endif ## Evaluate DFE input argument if (! isempty (DFE)) if (! isscalar (DFE)) error ("multcompare: df must be a scalar value."); endif if (! (DFE > 0) || isinf (DFE)) error ("multcompare: df must be a positive finite value."); endif endif ## If STATS is numeric, assume it is a vector of p-values if (isnumeric (STATS)) if (nargout > 1) error (strcat ("multcompare: invalid number of output", ... " arguments if only used to adjust p-values.")) endif if (! isempty (varargin)) if (! any (strcmpi (varargin{1}, {'ctype','criticalvaluetype'})) ... || (nargin > 3) ) error (strcat ("multcompare: invalid input arguments", ... " if only used to adjust p-values.")); endif endif if (! ismember (CTYPE, {'bonferroni','holm','hochberg','fdr'})) error ("multcompare: '%s' is not a supported p-adjustment method.", ... CTYPE) endif p = STATS; if (all (size (p) > 1)) error ("multcompare: p-values must be a vector.") endif padj = feval (CTYPE, p); if (size (p, 1) > 1) C = padj; else C = padj'; endif return endif ## Perform test specific calculations switch (STATS.source) case 'anova1' ## Make matrix of requested comparisons (pairs) ## Also return the corresponding hypothesis matrix (L) n = STATS.n(:); Ng = numel (n); if (isempty (REF)) ## Pairwise comparisons [pairs, L] = pairwise (Ng); else ## Treatment vs. Control comparisons [pairs, L] = trt_vs_ctrl (Ng, REF); endif Np = size (pairs, 1); switch (STATS.vartype) case 'equal' ## Calculate estimated marginal means and their standard errors gmeans = STATS.means(:); gvar = (STATS.s^2) ./ n; # Sampling variance gcov = diag (gvar); Ng = numel (gmeans); M = zeros (Ng, 4); M(:,1:2) = cat (2, gmeans, sqrt (gvar)); ## Get the error degrees of freedom from anova1 output if (isempty (DFE)) DFE = STATS.df; endif case 'unequal' ## Error checking if (strcmp (CTYPE, 'scheffe')) error (strcat ("multcompare: the CTYPE value 'scheffe'", ... " does not support tests with varying", ... " degrees of freedom ")); endif ## Calculate estimated marginal means and their standard errors gmeans = STATS.means(:); gvar = STATS.vars(:) ./ n; # Sampling variance gcov = diag (gvar); Ng = numel (gmeans); M = zeros (Ng, 4); M(:,1:2) = cat (2, gmeans, sqrt (gvar)); ## Calculate Welch's corrected degrees of freedom if (isempty (DFE)) DFE = sum (gvar(pairs), 2).^2 ./ ... sum ((gvar(pairs).^2 ./ (n(pairs) - 1)), 2); endif endswitch ## Calculate t statistics corresponding to the comparisons defined in L [mean_diff, sed, t] = tValue (gmeans, gcov, L); ## Calculate correlation matrix vcov = L * gcov * L'; R = cov2corr (vcov); ## Create cell array of group names corresponding to each row of m GNAMES = STATS.gnames; case 'anova2' ## Fetch estimate specific information from the STATS structure switch (ESTIMATE) case {'column','columns','col','cols'} gmeans = STATS.colmeans(:); Ng = numel (gmeans); n = STATS.coln; case {'row','rows'} if (ismember (STATS.model, {'linear','nested'})) error (strcat ("multcompare: no support for the row factor",... " (random effect) in a 'nested' or 'linear'",... " anova2 model.")); endif gmeans = STATS.rowmeans(:); Ng = numel (gmeans); n = STATS.rown; endswitch ## Make matrix of requested comparisons (pairs) ## Also return the corresponding hypothesis matrix (L) if (isempty (REF)) ## Pairwise comparisons [pairs, L, R] = pairwise (Ng); else ## Treatment vs. Control comparisons [pairs, L, R] = trt_vs_ctrl (Ng, REF); endif Np = size (pairs, 1); ## Calculate estimated marginal means and their standard errors gvar = ((STATS.sigmasq) / n) * ones (Ng, 1); # Sampling variance gcov = diag (gvar); M = zeros (Ng, 4); M(:,1:2) = cat (2, gmeans, sqrt (gvar)); ## Get the error degrees of freedom from anova2 output if (isempty (DFE)) DFE = STATS.df; endif ## Calculate t statistics corresponding to the comparisons defined in L [mean_diff, sed, t] = tValue (gmeans, gcov, L); ## Create character array of group names corresponding to each row of m GNAMES = cellstr (num2str ([1:Ng]')); case 'anovan' ## Our calculations treat all effects as fixed if (ismember (STATS.random, DIM)) warning (strcat ("multcompare: ignoring random effects", ... " (all effects treated as fixed).")); endif ## Check what type of factor is requested in DIM if (any (STATS.nlevels(DIM) < 2)) error (strcat ("multcompare: DIM must specify only categorical", ... " factors with 2 or more degrees of freedom.")); endif ## Check that all continuous variables were centered msg = strcat ("multcompare: use a STATS structure from a model", ... " refit with a sum-to-zero contrast coding."); if (any (STATS.continuous - STATS.center_continuous)) error (msg) endif ## Check that the columns sum to 0 N = numel (STATS.contrasts); for j = 1:N if (isnumeric (STATS.contrasts{j})) if (any (abs (sum (STATS.contrasts{j})) > eps ('single'))) error (msg); endif endif endfor ## Calculate estimated marginal means and their standard errors Nd = numel (DIM); n = numel (STATS.resid); df = STATS.df; if (isempty (DFE)) DFE = STATS.dfe; endif i = 1 + cumsum (df); k = find (sum (STATS.terms(:,DIM), 2) == sum (STATS.terms, 2)); Nb = 1 + sum (df(k)); Nt = numel (k); L = zeros (n, sum (df) + 1); for j = 1:Nt L(:, i(k(j)) - df(k(j)) + 1 : i(k(j))) = STATS.X(:,i(k(j)) - ... df(k(j)) + 1 : i(k(j))); endfor L(:,1) = 1; U = unique (L, 'rows', 'stable'); Ng = size (U, 1); idx = zeros (Ng, 1); for k = 1:Ng idx(k) = find (all (L == U(k, :), 2),1); endfor gmeans = U * STATS.coeffs(:,1); # Estimated marginal means gcov = U * STATS.vcov * U'; gvar = diag (gcov); # Sampling variance M = zeros (Ng, 4); M(:,1:2) = cat (2, gmeans, sqrt (gvar)); ## Create cell array of group names corresponding to each row of m GNAMES = cell (Ng, 1); for i = 1:Ng str = ''; for j = 1:Nd str = sprintf ("%s%s=%s, ", str, ... num2str (STATS.varnames{DIM(j)}), ... num2str (STATS.grpnames{DIM(j)}{STATS.grps(idx(i),DIM(j))})); endfor GNAMES{i} = str(1:end-2); str = ''; endfor ## Make matrix of requested comparisons (pairs) ## Also return the corresponding hypothesis matrix (L) if (isempty (REF)) ## Pairwise comparisons [pairs, L] = pairwise (Ng); else ## Treatment vs. Control comparisons [pairs, L] = trt_vs_ctrl (Ng, REF); endif Np = size (pairs, 1); ## Calculate t statistics corresponding to the comparisons defined in L [mean_diff, sed, t] = tValue (gmeans, gcov, L); ## Calculate correlation matrix. vcov = L * gcov * L'; R = cov2corr (vcov); case 'friedman' ## Get stats from structure gmeans = STATS.meanranks(:); Ng = length (gmeans); sigma = STATS.sigma; ## Make group names GNAMES = strjust (num2str ((1:Ng)'), 'left'); ## Make matrix of requested comparisons (pairs) ## Also return the corresponding hypothesis matrix (L) if (isempty (REF)) ## Pairwise comparisons [pairs, L, R] = pairwise (Ng); else ## Treatment vs. Control comparisons [pairs, L, R] = trt_vs_ctrl (Ng, REF); endif Np = size (pairs, 1); ## Calculate covariance matrix gcov = ((sigma ^ 2) / STATS.n) * eye (Ng); ## Create matrix with group means and standard errors M = cat (2, gmeans, sqrt (diag (gcov))); ## Calculate t statistics corresponding to the comparisons defined in L [mean_diff, sed, t] = tValue (gmeans, gcov, L); # z-statistic (not t) ## Calculate degrees of freedom from number of groups if (isempty (DFE)) DFE = inf; # this is a z-statistic so infinite degrees of freedom endif case 'kruskalwallis' ## Get stats from structure gmeans = STATS.meanranks(:); sumt = STATS.sumt; Ng = length (gmeans); n = STATS.n(:); N = sum (n); ## Make group names GNAMES = STATS.gnames; ## Make matrix of requested comparisons (pairs) ## Also return the corresponding hypothesis matrix (L) if (isempty (REF)) ## Pairwise comparisons [pairs, L] = pairwise (Ng); else ## Treatment vs. Control comparisons [pairs, L] = trt_vs_ctrl (Ng, REF); endif Np = size (pairs, 1); ## Calculate covariance matrix gcov = diag (((N * (N + 1) / 12) - (sumt / (12 * (N - 1)))) ./ n); ## Create matrix with group means and standard errors M = cat (2, gmeans, sqrt (diag (gcov))); ## Calculate t statistics corresponding to the comparisons defined in L [mean_diff, sed, t] = tValue (gmeans, gcov, L); # z-statistic (not t) ## Calculate correlation matrix vcov = L * gcov * L'; R = cov2corr (vcov); ## Calculate degrees of freedom from number of groups if (isempty (DFE)) DFE = inf; # this is a z-statistic so infinite degrees of freedom endif otherwise error (strcat ("multcompare: the STATS structure from %s", ... " is not currently supported"), STATS.source); endswitch ## The test specific code above needs to create the following variables in ## order to proceed with the remainder of the function tasks ## - Ng: number of groups involved in comparisons ## - M: Ng-by-2 matrix of group means (col 1) and standard errors (col 2) ## - Np: number of comparisons (pairs of groups being compared) ## - pairs: Np-by-2 matrix of numeric group IDs - each row is a comparison ## - R: correlation matrix for the requested comparisons ## - sed: vector containing SE of the difference for each comparisons ## - t: vector containing t for the difference relating to each comparisons ## - DFE: residual/error degrees of freedom ## - GNAMES: a cell array containing the names of the groups being compared ## Create matrix of comparisons and calculate confidence intervals and ## multiplicity adjusted p-values for the comparisons. C = zeros (Np, 8); C(:,1:2) = pairs; C(:,4) = (M(pairs(:, 1),1) - M(pairs(:, 2),1)); C(:,7) = t; # Unlike Matlab, we include the t statistic C(:,8) = DFE; # Unlike Matlab, we include the degrees of freedom if (any (isinf (DFE))) p = 2 * (1 - normcdf (abs (t))); else p = 2 * (1 - tcdf (abs (t), DFE)); endif [C(:,6), critval, C(:,8)] = feval (CTYPE, p, t, Ng, DFE, R, ALPHA); C(:,3) = C(:,4) - sed .* critval; C(:,5) = C(:,4) + sed .* critval; ## Calculate confidence intervals of the estimated marginal means with ## central coverage such that the intervals start to overlap where the ## difference reaches a two-tailed p-value of ALPHA. When ALPHA is 0.05, ## central coverage is approximately 83.4% if (! isscalar (DFE)) # Upper bound critval (corresponding to lower bound DFE) critval = max (critval); endif M(:,3) = M(:,1) - M(:,2) .* critval / sqrt (2); M(:,4) = M(:,1) + M(:,2) .* critval / sqrt (2); ## If requested, plot graph of the difference means for each comparison ## with central coverage of confidence intervals at 100*(1-alpha)% switch (lower (DISPLAY)) case {'on',true} H = figure; plot ([0; 0], [0; Np + 1]','k:'); # Plot vertical dashed line at 0 effect set (gca, 'Ydir', 'reverse') # Flip y-axis direction ylim ([0.5, Np + 0.5]); # Set y-axis limits hold on # Plot on the same axis for j = 1:Np if (C(j,6) < ALPHA) ## Plot marker for the difference in means plot (C(j,4), j,'or','MarkerFaceColor', 'r'); ## Plot line for each confidence interval plot ([C(j,3), C(j,5)], j * ones (2,1), 'r-'); else ## Plot marker for the difference in means plot (C(j,4), j,'ob','MarkerFaceColor', 'b'); ## Plot line for each confidence interval plot ([C(j,3), C(j,5)], j * ones (2,1), 'b-'); endif endfor hold off xlabel (sprintf ("%g%% confidence interval for the difference",... 100 * (1 - ALPHA))); ylabel ('Row number in matrix of comparisons (C)'); case {'off',false} H = []; endswitch ## Print multcompare table on screen if no output argument was requested if (nargout == 0 || strcmp (DISPLAY, 'on')) printf ("\n %s Multiple Comparison (Post Hoc) Test for %s\n\n", ... upper (CTYPE), upper (STATS.source)); header = strcat ("Group ID Group ID LBoundDiff EstimatedDiff",... " UBoundDiff p-value\n", ... "-------------------------------------------------",... "---------------------\n"); printf ("%s", header); for j = 1:Np if (C(j,6) < 0.001) printf ("%5i %5i %10.3f %10.3f %10.3f <.001\n",... C(j,1), C(j,2), C(j,3), C(j,4), C(j,5)); elseif (C(j,6) < 0.9995) printf ("%5i %5i %10.3f %10.3f %10.3f .%03u\n",... C(j,1), C(j,2), C(j,3), C(j,4), C(j,5), round (C(j,6) * 1e+03)); else printf ("%5i %5i %10.3f %10.3f %10.3f 1.000\n",... C(j,1), C(j,2), C(j,3), C(j,4), C(j,5)); endif endfor printf ("\n"); endif endfunction ## Posthoc comparisons function [pairs, L, R] = pairwise (Ng) ## Create pairs matrix for pairwise comparisons gid = [1:Ng]'; # Create numeric group ID A = ones (Ng, 1) * gid'; B = tril (gid * ones (1, Ng),-1); pairs = [A(:), B(:)]; ridx = (pairs(:, 2) == 0); pairs(ridx, :) = []; ## Calculate correlation matrix (required for CTYPE "mvt") Np = size (pairs, 1); L = zeros (Np, Ng); for j = 1:Np L(j, pairs(j,:)) = [1,-1]; # Hypothesis matrix endfor R = corr (L'); # Correlation matrix endfunction function [pairs, L, R] = trt_vs_ctrl (Ng, REF) ## Create pairs matrix for comparisons with control (REF) gid = [1:Ng]'; # Create numeric group ID pairs = zeros (Ng - 1, 2); pairs(:, 1) = REF; pairs(:, 2) = gid(gid != REF); ## Calculate correlation matrix (required for CTYPE "mvt") Np = size (pairs, 1); L = zeros (Np, Ng); for j = 1:Np L(j, pairs(j,:)) = [1,-1]; # Hypothesis matrix endfor R = corr (L'); # Correlation matrix endfunction function [mn, se, t] = tValue (gmeans, gcov, L) ## Calculate means, standard errors and t (or z) statistics ## corresponding to the comparisons defined in L. mn = sum (L * diag (gmeans), 2); se = sqrt (diag (L * gcov * L')); t = mn ./ se; endfunction function R = cov2corr (vcov) ## Convert covariance matrix to correlation matrix sed = sqrt (diag (vcov)); R = vcov ./ (sed * sed'); R = (R + R') / 2; # This step ensures that the matrix is positive definite endfunction ## Methods to control family-wise error rate in multiple comparisons function [padj, critval, dfe] = scheffe (p, t, Ng, dfe, R, ALPHA) ## Calculate the p-value if (isinf (dfe)) padj = 1 - chi2cdf (t.^2, Ng - 1); else padj = 1 - fcdf ((t.^2) / (Ng - 1), Ng - 1, dfe); endif ## Calculate critical value at Scheffe-adjusted ALPHA level if (isinf (dfe)) tmp = chi2inv (1 - ALPHA, Ng - 1) / (Ng - 1); else tmp = finv (1 - ALPHA, Ng - 1, dfe); endif critval = sqrt ((Ng - 1) * tmp); endfunction function [padj, critval, dfe] = bonferroni (p, t, Ng, dfe, R, ALPHA) ## Bonferroni procedure Np = numel (p); padj = min (p * Np, 1.0); ## If requested, calculate critical value at Bonferroni-adjusted ALPHA level if (nargout > 1) critval = tinv (1 - ALPHA / Np * 0.5, dfe); endif endfunction function [padj, critval, dfe] = mvt (p, t, Ng, dfe, R, ALPHA) ## Monte Carlo simulation of the maximum test statistic in random samples ## generated from a multivariate t distribution. This method accounts for ## correlations among comparisons. This method simulates Tukey's test in the ## case of pairwise comparisons or Dunnett's tests in the case of trt_vs_ctrl. ## The "mvt" method is equivalent to methods used in the following R packages: ## - emmeans: the "mvt" adjust method in functions within emmeans ## - glht: the "single-step" adjustment in the multcomp.function ## Lower bound for error degrees of freedom to ensure type 1 error rate isn't ## exceeded for any test if (! isscalar (dfe)) dfe = max (1, round (min (dfe))); fprintf ("Note: df set to %u (lower bound)\n", dfe); endif ## Check if we can use parallel processing to accelerate computations pat = '^parallel'; software = pkg ('list'); names = cellfun (@(S) S.name, software, 'UniformOutput', false); status = cellfun (@(S) S.loaded, software, 'UniformOutput', false); index = find (! cellfun (@isempty, regexpi (names, pat))); if (! isempty (index)) if (logical (status{index})) PARALLEL = true; else PARALLEL = false; endif else PARALLEL = false; endif ## Generate the distribution of (correlated) t statistics under the null, and ## calculate the maximum test statistic for each random sample. Computations ## are performed in chunks to prevent memory issues when the number of ## comparisons is large. chunkSize = 1000; numChunks = 1000; nsim = chunkSize * numChunks; if (isinf (dfe)) # Multivariate z-statistics func = @(jnk) max (abs (mvnrnd (0, R, chunkSize)'), [], 1); else # Multivariate t-statistics func = @(jnk) max (abs (mvtrnd (R, dfe, chunkSize)'), [], 1); endif if (PARALLEL) maxT = cell2mat (parcellfun (nproc, func, ... cell (1, numChunks), 'UniformOutput', false)); else maxT = cell2mat (cellfun (func, cell (1, numChunks), 'UniformOutput', false)); endif ## Calculate multiplicity adjusted p-values (two-tailed) padj = max (sum (bsxfun (@ge, maxT, abs (t)), 2) / nsim, nsim^-1); ## Calculate critical value adjusted by the maxT procedure critval = quantile (maxT, 1 - ALPHA); endfunction function [padj, critval, dfe] = holm (p, t, Ng, dfe, R, ALPHA) ## Holm's step-down Bonferroni procedure ## Order raw p-values [ps, idx] = sort (p, 'ascend'); Np = numel (ps); ## Implement Holm's step-down Bonferroni procedure padj = nan (Np,1); padj(1) = Np * ps(1); for i = 2:Np padj(i) = max (padj(i - 1), (Np - i + 1) * ps(i)); endfor ## Reorder the adjusted p-values to match the order of the original p-values [~, original_order] = sort (idx, 'ascend'); padj = padj(original_order); ## Truncate adjusted p-values to 1.0 padj(padj>1) = 1; ## If requested, calculate critical value at ALPHA ## No adjustment to confidence interval coverage if (nargout > 1) critval = tinv (1 - ALPHA / 2, dfe); endif endfunction function [padj, critval, dfe] = hochberg (p, t, Ng, dfe, R, ALPHA) ## Hochberg's step-up Bonferroni procedure ## Order raw p-values [ps, idx] = sort (p, 'ascend'); Np = numel (ps); ## Implement Hochberg's step-down Bonferroni procedure padj = nan (Np,1); padj(Np) = ps(Np); for j = 1:Np-1 i = Np - j; padj(i) = min (padj(i + 1), (Np -i + 1) * ps(i)); endfor ## Reorder the adjusted p-values to match the order of the original p-values [~, original_order] = sort (idx, 'ascend'); padj = padj(original_order); ## Truncate adjusted p-values to 1.0 padj(padj>1) = 1; ## If requested, calculate critical value at ALPHA ## No adjustment to confidence interval coverage if (nargout > 1) critval = tinv (1 - ALPHA / 2, dfe); endif endfunction function [padj, critval, dfe] = fdr (p, t, Ng, dfe, R, ALPHA) ## Benjamini-Hochberg procedure to control the false discovery rate (FDR) ## This procedure does not control the family-wise error rate ## Order raw p-values [ps, idx] = sort (p, 'ascend'); Np = numel (ps); ## Initialize padj = nan (Np,1); alpha = nan (Np,1); ## Benjamini-Hochberg step-up procedure to control the false discovery rate padj = nan (Np,1); padj(Np) = ps(Np); for j = 1:Np-1 i = Np - j; padj(i) = min (padj(i + 1), Np / i * ps(i)); endfor ## Reorder the adjusted p-values to match the order of the original p-values [~, original_order] = sort (idx, 'ascend'); padj = padj(original_order); ## Truncate adjusted p-values to 1.0 padj(padj>1) = 1; ## If requested, calculate critical value at ALPHA ## No adjustment to confidence interval coverage if (nargout > 1) critval = tinv (1 - ALPHA / 2, dfe); endif endfunction function [padj, critval, dfe] = lsd (p, t, Ng, dfe, R, ALPHA) ## Fisher's Least Significant Difference ## No control of the type I error rate across multiple comparisons padj = p; ## Calculate critical value at ALPHA ## No adjustment to confidence interval coverage critval = tinv (1 - ALPHA / 2, dfe); endfunction %!demo %! %! ## Demonstration using balanced one-way ANOVA from anova1 %! %! rng (42); %! randg ('state', 42); %! x = ones (50, 4) .* [-2, 0, 1, 5]; %! x = x + normrnd (0, 2, 50, 4); %! groups = {'A', 'B', 'C', 'D'}; %! [p, tbl, stats] = anova1 (x, groups, 'off'); %! multcompare (stats); %!demo %! %! ## Demonstration using unbalanced one-way ANOVA example from anovan %! %! dv = [ 8.706 10.362 11.552 6.941 10.983 10.092 6.421 14.943 15.931 ... %! 22.968 18.590 16.567 15.944 21.637 14.492 17.965 18.851 22.891 ... %! 22.028 16.884 17.252 18.325 25.435 19.141 21.238 22.196 18.038 ... %! 22.628 31.163 26.053 24.419 32.145 28.966 30.207 29.142 33.212 ... %! 25.694 ]'; %! g = [1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 3 ... %! 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5]'; %! %! [P,ATAB, STATS] = anovan (dv, g, 'varnames', 'score', 'display', 'off'); %! %! [C, M, H, GNAMES] = multcompare (STATS, 'dim', 1, 'ctype', 'holm', ... %! 'ControlGroup', 1, 'display', 'on') %! %!demo %! %! ## Demonstration using factorial ANCOVA example from anovan %! %! score = [95.6 82.2 97.2 96.4 81.4 83.6 89.4 83.8 83.3 85.7 ... %! 97.2 78.2 78.9 91.8 86.9 84.1 88.6 89.8 87.3 85.4 ... %! 81.8 65.8 68.1 70.0 69.9 75.1 72.3 70.9 71.5 72.5 ... %! 84.9 96.1 94.6 82.5 90.7 87.0 86.8 93.3 87.6 92.4 ... %! 100. 80.5 92.9 84.0 88.4 91.1 85.7 91.3 92.3 87.9 ... %! 91.7 88.6 75.8 75.7 75.3 82.4 80.1 86.0 81.8 82.5]'; %! treatment = {'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' ... %! 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' ... %! 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' 'yes' ... %! 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' ... %! 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' ... %! 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no' 'no'}'; %! exercise = {'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' ... %! 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' ... %! 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' ... %! 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' 'lo' ... %! 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' 'mid' ... %! 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi' 'hi'}'; %! age = [59 65 70 66 61 65 57 61 58 55 62 61 60 59 55 57 60 63 62 57 ... %! 58 56 57 59 59 60 55 53 55 58 68 62 61 54 59 63 60 67 60 67 ... %! 75 54 57 62 65 60 58 61 65 57 56 58 58 58 52 53 60 62 61 61]'; %! %! [P, ATAB, STATS] = anovan (score, {treatment, exercise, age}, 'model', ... %! [1 0 0; 0 1 0; 0 0 1; 1 1 0], 'continuous', 3, ... %! 'sstype', 'h', 'display', 'off', 'contrasts', ... %! {'simple','poly',''}); %! %! [C, M, H, GNAMES] = multcompare (STATS, 'dim', [1 2], 'ctype', 'holm', ... %! 'display', 'on') %! %!demo %! %! ## Demonstration using one-way ANOVA from anovan, with fit by weighted least %! ## squares to account for heteroskedasticity. %! %! g = [1, 1, 1, 1, 1, 1, 1, 1, ... %! 2, 2, 2, 2, 2, 2, 2, 2, ... %! 3, 3, 3, 3, 3, 3, 3, 3]'; %! %! y = [13, 16, 16, 7, 11, 5, 1, 9, ... %! 10, 25, 66, 43, 47, 56, 6, 39, ... %! 11, 39, 26, 35, 25, 14, 24, 17]'; %! %! [P,ATAB,STATS] = anovan (y, g, 'display', 'off'); %! fitted = STATS.X * STATS.coeffs(:,1); # fitted values %! b = polyfit (fitted, abs (STATS.resid), 1); %! v = polyval (b, fitted); # Variance as a function of the fitted values %! [P,ATAB,STATS] = anovan (y, g, 'weights', v.^-1, 'display', 'off'); %! [C, M] = multcompare (STATS, 'display', 'on', 'ctype', 'mvt') %!demo %! %! ## Demonstration of p-value adjustments to control the false discovery rate %! ## Data from Westfall (1997) JASA. 92(437):299-306 %! %! p = [.005708; .023544; .024193; .044895; ... %! .048805; .221227; .395867; .693051; .775755]; %! %! padj = multcompare (p,'ctype','fdr') %!test %! %! ## Tests using unbalanced one-way ANOVA example from anovan and anova1 %! %! ## Test for anovan - compare pairwise comparisons with matlab for CTYPE "lsd" %! %! dv = [ 8.706 10.362 11.552 6.941 10.983 10.092 6.421 14.943 15.931 ... %! 22.968 18.590 16.567 15.944 21.637 14.492 17.965 18.851 22.891 ... %! 22.028 16.884 17.252 18.325 25.435 19.141 21.238 22.196 18.038 ... %! 22.628 31.163 26.053 24.419 32.145 28.966 30.207 29.142 33.212 ... %! 25.694 ]'; %! g = [1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 3 ... %! 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5]'; %! %! [P, ATAB, STATS] = anovan (dv, g, 'varnames', 'score', 'display', 'off'); %! [C, M, H, GNAMES] = multcompare (STATS, 'dim', 1, 'ctype', 'lsd', ... %! 'display', 'off'); %! assert_equal (C(1,6), 2.85812420217898e-05, 1e-09); %! assert_equal (C(2,6), 5.22936741204085e-07, 1e-09); %! assert_equal (C(3,6), 2.12794763209146e-08, 1e-09); %! assert_equal (C(4,6), 7.82091664406946e-15, 1e-09); %! assert_equal (C(5,6), 0.546591417210693, 1e-09); %! assert_equal (C(6,6), 0.0845897945254446, 1e-09); %! assert_equal (C(7,6), 9.47436557975328e-08, 1e-09); %! assert_equal (C(8,6), 0.188873478781067, 1e-09); %! assert_equal (C(9,6), 4.08974010364197e-08, 1e-09); %! assert_equal (C(10,6), 4.44427348175241e-06, 1e-09); %! assert_equal (M(1,1), 10, 1e-09); %! assert_equal (M(2,1), 18, 1e-09); %! assert_equal (M(3,1), 19, 1e-09); %! assert_equal (M(4,1), 21.0001428571429, 1e-09); %! assert_equal (M(5,1), 29.0001111111111, 1e-09); %! assert_equal (M(1,2), 1.0177537954095, 1e-09); %! assert_equal (M(2,2), 1.28736803631001, 1e-09); %! assert_equal (M(3,2), 1.0177537954095, 1e-09); %! assert_equal (M(4,2), 1.0880245732889, 1e-09); %! assert_equal (M(5,2), 0.959547480416536, 1e-09); %! %! ## Compare "fdr" adjusted p-values to those obtained using p.adjust in R %! %! [C, M, H, GNAMES] = multcompare (STATS, 'dim', 1, 'ctype', 'fdr', ... %! 'display', 'off'); %! assert_equal (C(1,6), 4.08303457454140e-05, 1e-09); %! assert_equal (C(2,6), 1.04587348240817e-06, 1e-09); %! assert_equal (C(3,6), 1.06397381604573e-07, 1e-09); %! assert_equal (C(4,6), 7.82091664406946e-14, 1e-09); %! assert_equal (C(5,6), 5.46591417210693e-01, 1e-09); %! assert_equal (C(6,6), 1.05737243156806e-01, 1e-09); %! assert_equal (C(7,6), 2.36859139493832e-07, 1e-09); %! assert_equal (C(8,6), 2.09859420867852e-01, 1e-09); %! assert_equal (C(9,6), 1.36324670121399e-07, 1e-09); %! assert_equal (C(10,6), 7.40712246958735e-06, 1e-09); %! %! ## Compare "hochberg" adjusted p-values to those obtained using p.adjust in R %! %! [C, M, H, GNAMES] = multcompare (STATS, 'dim', 1, 'ctype', 'hochberg', ... %! 'display', 'off'); %! assert_equal (C(1,6), 1.14324968087159e-04, 1e-09); %! assert_equal (C(2,6), 3.13762044722451e-06, 1e-09); %! assert_equal (C(3,6), 1.91515286888231e-07, 1e-09); %! assert_equal (C(4,6), 7.82091664406946e-14, 1e-09); %! assert_equal (C(5,6), 5.46591417210693e-01, 1e-09); %! assert_equal (C(6,6), 2.53769383576334e-01, 1e-09); %! assert_equal (C(7,6), 6.63205590582730e-07, 1e-09); %! assert_equal (C(8,6), 3.77746957562134e-01, 1e-09); %! assert_equal (C(9,6), 3.27179208291358e-07, 1e-09); %! assert_equal (C(10,6), 2.22213674087620e-05, 1e-09); %! %! ## Compare "holm" adjusted p-values to those obtained using p.adjust in R %! %! [C, M, H, GNAMES] = multcompare (STATS, 'dim', 1, 'ctype', 'holm', ... %! 'display', 'off'); %! assert_equal (C(1,6), 1.14324968087159e-04, 1e-09); %! assert_equal (C(2,6), 3.13762044722451e-06, 1e-09); %! assert_equal (C(3,6), 1.91515286888231e-07, 1e-09); %! assert_equal (C(4,6), 7.82091664406946e-14, 1e-09); %! assert_equal (C(5,6), 5.46591417210693e-01, 1e-09); %! assert_equal (C(6,6), 2.53769383576334e-01, 1e-09); %! assert_equal (C(7,6), 6.63205590582730e-07, 1e-09); %! assert_equal (C(8,6), 3.77746957562134e-01, 1e-09); %! assert_equal (C(9,6), 3.27179208291358e-07, 1e-09); %! assert_equal (C(10,6), 2.22213674087620e-05, 1e-09); %! %! ## Compare "scheffe" adjusted p-values to those obtained using 'scheffe' in Matlab %! %! [C, M, H, GNAMES] = multcompare (STATS, 'dim', 1, 'ctype', 'scheffe', ... %! 'display', 'off'); %! assert_equal (C(1,6), 0.00108105386141085, 1e-09); %! assert_equal (C(2,6), 2.7779386789517e-05, 1e-09); %! assert_equal (C(3,6), 1.3599854038198e-06, 1e-09); %! assert_equal (C(4,6), 7.58830197867751e-13, 1e-09); %! assert_equal (C(5,6), 0.984039948220281, 1e-09); %! assert_equal (C(6,6), 0.539077018557706, 1e-09); %! assert_equal (C(7,6), 5.59475764460574e-06, 1e-09); %! assert_equal (C(8,6), 0.771173490574105, 1e-09); %! assert_equal (C(9,6), 2.52838425729905e-06, 1e-09); %! assert_equal (C(10,6), 0.000200719143889168, 1e-09); %! %! ## Compare "bonferroni" adjusted p-values to those obtained using p.adjust in R %! %! [C, M, H, GNAMES] = multcompare (STATS, 'dim', 1, 'ctype', 'bonferroni', ... %! 'display', 'off'); %! assert_equal (C(1,6), 2.85812420217898e-04, 1e-09); %! assert_equal (C(2,6), 5.22936741204085e-06, 1e-09); %! assert_equal (C(3,6), 2.12794763209146e-07, 1e-09); %! assert_equal (C(4,6), 7.82091664406946e-14, 1e-09); %! assert_equal (C(5,6), 1.00000000000000e+00, 1e-09); %! assert_equal (C(6,6), 8.45897945254446e-01, 1e-09); %! assert_equal (C(7,6), 9.47436557975328e-07, 1e-09); %! assert_equal (C(8,6), 1.00000000000000e+00, 1e-09); %! assert_equal (C(9,6), 4.08974010364197e-07, 1e-09); %! assert_equal (C(10,6), 4.44427348175241e-05, 1e-09); %! %! ## Test for anova1 ("equal")- comparison of results from Matlab %! %! [P, ATAB, STATS] = anova1 (dv, g, 'off', 'equal'); %! [C, M, H, GNAMES] = multcompare (STATS, 'ctype', 'lsd', 'display', 'off'); %! assert_equal (C(1,6), 2.85812420217898e-05, 1e-09); %! assert_equal (C(2,6), 5.22936741204085e-07, 1e-09); %! assert_equal (C(3,6), 2.12794763209146e-08, 1e-09); %! assert_equal (C(4,6), 7.82091664406946e-15, 1e-09); %! assert_equal (C(5,6), 0.546591417210693, 1e-09); %! assert_equal (C(6,6), 0.0845897945254446, 1e-09); %! assert_equal (C(7,6), 9.47436557975328e-08, 1e-09); %! assert_equal (C(8,6), 0.188873478781067, 1e-09); %! assert_equal (C(9,6), 4.08974010364197e-08, 1e-09); %! assert_equal (C(10,6), 4.44427348175241e-06, 1e-09); %! assert_equal (M(1,1), 10, 1e-09); %! assert_equal (M(2,1), 18, 1e-09); %! assert_equal (M(3,1), 19, 1e-09); %! assert_equal (M(4,1), 21.0001428571429, 1e-09); %! assert_equal (M(5,1), 29.0001111111111, 1e-09); %! assert_equal (M(1,2), 1.0177537954095, 1e-09); %! assert_equal (M(2,2), 1.28736803631001, 1e-09); %! assert_equal (M(3,2), 1.0177537954095, 1e-09); %! assert_equal (M(4,2), 1.0880245732889, 1e-09); %! assert_equal (M(5,2), 0.959547480416536, 1e-09); %! %! ## Test for anova1 ("unequal") - comparison with results from GraphPad Prism 8 %! [P, ATAB, STATS] = anova1 (dv, g, 'off', 'unequal'); %! [C, M, H, GNAMES] = multcompare (STATS, 'ctype', 'lsd', 'display', 'off'); %! assert_equal (C(1,6), 0.001247025266382, 1e-09); %! assert_equal (C(2,6), 0.000018037115146, 1e-09); %! assert_equal (C(3,6), 0.000002974595187, 1e-09); %! assert_equal (C(4,6), 0.000000000786046, 1e-09); %! assert_equal (C(5,6), 0.5693192886650109, 1e-09); %! assert_equal (C(6,6), 0.110501699029776, 1e-09); %! assert_equal (C(7,6), 0.000131226488700, 1e-09); %! assert_equal (C(8,6), 0.1912101409715992, 1e-09); %! assert_equal (C(9,6), 0.000005385256394, 1e-09); %! assert_equal (C(10,6), 0.000074089106171, 1e-09); %!test %! %! ## Test for anova2 ("interaction") - comparison with results from Matlab for column effect %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! [P, ATAB, STATS] = anova2 (popcorn, 3, 'off'); %! [C, M, H, GNAMES] = multcompare (STATS, 'estimate', 'column',... %! 'ctype', 'lsd', 'display', 'off'); %! assert_equal (C(1,6), 1.49311100811177e-05, 1e-09); %! assert_equal (C(2,6), 2.20506904243535e-07, 1e-09); %! assert_equal (C(3,6), 0.00449897860490058, 1e-09); %! assert_equal (M(1,1), 6.25, 1e-09); %! assert_equal (M(2,1), 4.75, 1e-09); %! assert_equal (M(3,1), 4, 1e-09); %! assert_equal (M(1,2), 0.152145154862547, 1e-09); %! assert_equal (M(2,2), 0.152145154862547, 1e-09); %! assert_equal (M(3,2), 0.152145154862547, 1e-09); %!test %! %! ## Test for anova2 ("linear") - comparison with results from GraphPad Prism 8 %! words = [10 13 13; 6 8 8; 11 14 14; 22 23 25; 16 18 20; ... %! 15 17 17; 1 1 4; 12 15 17; 9 12 12; 8 9 12]; %! [P, ATAB, STATS] = anova2 (words, 1, 'off', 'linear'); %! [C, M, H, GNAMES] = multcompare (STATS, 'estimate', 'column',... %! 'ctype', 'lsd', 'display', 'off'); %! assert_equal (C(1,6), 0.000020799832702, 1e-09); %! assert_equal (C(2,6), 0.000000035812410, 1e-09); %! assert_equal (C(3,6), 0.003038942449215, 1e-09); %!test %! %! ## Test for anova2 ("nested") - comparison with results from GraphPad Prism 8 %! data = [4.5924 7.3809 21.322; -0.5488 9.2085 25.0426; ... %! 6.1605 13.1147 22.66; 2.3374 15.2654 24.1283; ... %! 5.1873 12.4188 16.5927; 3.3579 14.3951 10.2129; ... %! 6.3092 8.5986 9.8934; 3.2831 3.4945 10.0203]; %! [P, ATAB, STATS] = anova2 (data, 4, 'off', 'nested'); %! [C, M, H, GNAMES] = multcompare (STATS, 'estimate', 'column',... %! 'ctype', 'lsd', 'display', 'off'); %! assert_equal (C(1,6), 0.261031111511073, 1e-09); %! assert_equal (C(2,6), 0.065879755907745, 1e-09); %! assert_equal (C(3,6), 0.241874613529270, 1e-09); %!shared visibility_setting %! visibility_setting = get (0, 'DefaultFigureVisible'); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! %! ## Test for kruskalwallis - comparison with results from MATLAB %! data = [3,2,4; 5,4,4; 4,2,4; 4,2,4; 4,1,5; ... %! 4,2,3; 4,3,5; 4,2,4; 5,2,4; 5,3,3]; %! group = [1:3] .* ones (10,3); %! [P, ATAB, STATS] = kruskalwallis (data(:), group(:), 'off'); %! C = multcompare (STATS, 'ctype', 'lsd', 'display', 'off'); %! assert_equal (C(1,6), 0.000163089828959986, 1e-09); %! assert_equal (C(2,6), 0.630298044801257, 1e-09); %! assert_equal (C(3,6), 0.00100567660695682, 1e-09); %! C = multcompare (STATS, 'ctype', 'bonferroni', 'display', 'off'); %! assert_equal (C(1,6), 0.000489269486879958, 1e-09); %! assert_equal (C(2,6), 1, 1e-09); %! assert_equal (C(3,6), 0.00301702982087047, 1e-09); %! C = multcompare (STATS, 'ctype', 'scheffe', 'display', 'off'); %! assert_equal (C(1,6), 0.000819054880289573, 1e-09); %! assert_equal (C(2,6), 0.890628039849261, 1e-09); %! assert_equal (C(3,6), 0.00447816059021654, 1e-09); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! ## Test for friedman - comparison with results from MATLAB %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! [P, ATAB, STATS] = friedman (popcorn, 3, 'off'); %! C = multcompare (STATS, 'ctype', 'lsd', 'display', 'off'); %! assert_equal (C(1,6), 0.227424558028569, 1e-09); %! assert_equal (C(2,6), 0.0327204848315735, 1e-09); %! assert_equal (C(3,6), 0.353160353315988, 1e-09); %! C = multcompare (STATS, 'ctype', 'bonferroni', 'display', 'off'); %! assert_equal (C(1,6), 0.682273674085708, 1e-09); %! assert_equal (C(2,6), 0.0981614544947206, 1e-09); %! assert_equal (C(3,6), 1, 1e-09); %! C = multcompare (STATS, 'ctype', 'scheffe', 'display', 'off'); %! assert_equal (C(1,6), 0.482657360384373, 1e-09); %! assert_equal (C(2,6), 0.102266573027672, 1e-09); %! assert_equal (C(3,6), 0.649836502233148, 1e-09); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! ## Test for anovan with 'simple' contrasts - same comparisons as for first anovan example %! y = [ 8.706 10.362 11.552 6.941 10.983 10.092 6.421 14.943 15.931 ... %! 22.968 18.590 16.567 15.944 21.637 14.492 17.965 18.851 22.891 ... %! 22.028 16.884 17.252 18.325 25.435 19.141 21.238 22.196 18.038 ... %! 22.628 31.163 26.053 24.419 32.145 28.966 30.207 29.142 33.212 ... %! 25.694 ]'; %! X = [1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5]'; %! [P, ATAB, STATS] = anovan (y, {X}, 'contrasts', 'simple', 'display', 'off'); %! [C, M] = multcompare (STATS, 'ctype', 'lsd', 'display', 'off'); %! assert_equal (C(1,6), 2.85812420217898e-05, 1e-09); %! assert_equal (C(2,6), 5.22936741204085e-07, 1e-09); %! assert_equal (C(3,6), 2.12794763209146e-08, 1e-09); %! assert_equal (C(4,6), 7.82091664406946e-15, 1e-09); %! assert_equal (C(5,6), 0.546591417210693, 1e-09); %! assert_equal (C(6,6), 0.0845897945254446, 1e-09); %! assert_equal (C(7,6), 9.47436557975328e-08, 1e-09); %! assert_equal (C(8,6), 0.188873478781067, 1e-09); %! assert_equal (C(9,6), 4.08974010364197e-08, 1e-09); %! assert_equal (C(10,6), 4.44427348175241e-06, 1e-09); %! assert_equal (M(1,1), 10, 1e-09); %! assert_equal (M(2,1), 18, 1e-09); %! assert_equal (M(3,1), 19, 1e-09); %! assert_equal (M(4,1), 21.0001428571429, 1e-09); %! assert_equal (M(5,1), 29.0001111111111, 1e-09); %! assert_equal (M(1,2), 1.0177537954095, 1e-09); %! assert_equal (M(2,2), 1.28736803631001, 1e-09); %! assert_equal (M(3,2), 1.0177537954095, 1e-09); %! assert_equal (M(4,2), 1.0880245732889, 1e-09); %! assert_equal (M(5,2), 0.959547480416536, 1e-09); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! ## Test p-value adjustments compared to R stats package function p.adjust %! ## Data from Westfall (1997) JASA. 92(437):299-306 %! p = [.005708; .023544; .024193; .044895; ... %! .048805; .221227; .395867; .693051; .775755]; %! padj = multcompare (p); %! assert_equal (padj(1), 0.051372, 1e-06); %! assert_equal (padj(2), 0.188352, 1e-06); %! assert_equal (padj(3), 0.188352, 1e-06); %! assert_equal (padj(4), 0.269370, 1e-06); %! assert_equal (padj(5), 0.269370, 1e-06); %! assert_equal (padj(6), 0.884908, 1e-06); %! assert_equal (padj(7), 1.000000, 1e-06); %! assert_equal (padj(8), 1.000000, 1e-06); %! assert_equal (padj(9), 1.000000, 1e-06); %! padj = multcompare (p,'ctype','holm'); %! assert_equal (padj(1), 0.051372, 1e-06); %! assert_equal (padj(2), 0.188352, 1e-06); %! assert_equal (padj(3), 0.188352, 1e-06); %! assert_equal (padj(4), 0.269370, 1e-06); %! assert_equal (padj(5), 0.269370, 1e-06); %! assert_equal (padj(6), 0.884908, 1e-06); %! assert_equal (padj(7), 1.000000, 1e-06); %! assert_equal (padj(8), 1.000000, 1e-06); %! assert_equal (padj(9), 1.000000, 1e-06); %! padj = multcompare (p,'ctype','hochberg'); %! assert_equal (padj(1), 0.051372, 1e-06); %! assert_equal (padj(2), 0.169351, 1e-06); %! assert_equal (padj(3), 0.169351, 1e-06); %! assert_equal (padj(4), 0.244025, 1e-06); %! assert_equal (padj(5), 0.244025, 1e-06); %! assert_equal (padj(6), 0.775755, 1e-06); %! assert_equal (padj(7), 0.775755, 1e-06); %! assert_equal (padj(8), 0.775755, 1e-06); %! assert_equal (padj(9), 0.775755, 1e-06); %! padj = multcompare (p,'ctype','fdr'); %! assert_equal (padj(1), 0.0513720, 1e-07); %! assert_equal (padj(2), 0.0725790, 1e-07); %! assert_equal (padj(3), 0.0725790, 1e-07); %! assert_equal (padj(4), 0.0878490, 1e-07); %! assert_equal (padj(5), 0.0878490, 1e-07); %! assert_equal (padj(6), 0.3318405, 1e-07); %! assert_equal (padj(7), 0.5089719, 1e-07); %! assert_equal (padj(8), 0.7757550, 1e-07); %! assert_equal (padj(9), 0.7757550, 1e-07); statistics-release-1.9.2/inst/Hypothesis_Testing/private/000077500000000000000000000000001524624707500236325ustar00rootroot00000000000000statistics-release-1.9.2/inst/Hypothesis_Testing/private/exact2xkCT.m000066400000000000000000000170241524624707500257740ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{p_net}, @var{p_val}] =} exact2xkCT (@var{ct}, @var{weights}, @var{rsstat}) ## ## Compute the exact p-value for a 2-by-K contingency table based on the ## network algorithm. ## ## Reference: Cyrus R. Mehta & Nitin R. Patel (1980) A network algorithm for ## the exact treatment of the 2×k contingency table, Communications in ## Statistics - Simulation and Computation, 9:6, 649-664, ## DOI: 10.1080/03610918008812182 ## ## @end deftypefn function [p_net, p_val] = exact2xkCT (ct, weights, rsstat) ## Calculate nodes and arcs [nodes, arcs] = build_nodes (ct,weights); ## Apply backward induction to nodes nodes = backward_induce (nodes,arcs); ## Forward scan the network to get p-values p_val = forward_scan (nodes, arcs, rsstat); ## Calculate p-values TP = nodes{4,1}; p_val = p_val / TP; p_net = p_val(2) + min (p_val(1), p_val(3)); endfunction ## Calculate structures describing nodes and arcs function [nodes, arcs] = build_nodes (ct, weights) column = size (ct, 2); ## number of columns in contingency table rowsum = sum (ct, 2); ## sum of rows colsum = sum (ct, 1); ## sum of columns oldnodes = zeros (1,2); ## nodes added during last pass oldlo = 0; ## min possible sum so far oldhi = 0; ## max possible sum so far oldnn = 1; ## node numbers (row numbers) from last pass ctsum = rowsum(1); ## sum of entries in first row nodecount = 1; ## current node count ## Initialize cell structures for nodes and arcs nodes = cell (4, column+1); ## to hold nodes nodes{1,1} = zeros (1,2); ## n-by-2 array, n = # of nodes, row = [j,mj] nodes{2,column+1} = 0; ## n-vector of longest path to end from here nodes{3,column+1} = 0; ## n-vector of shortest path to end from here nodes{4,column+1} = 1; ## n-vector of total probability to end from here arcs = cell (3, column); ## to hold arcs ## row 1: n-by-2 array, n = # of connections, row = pair connected ## row 2: n-vector of arc lengths ## row 3: n-vector of arc probabilities for j = 1:column ## Find nodes possible at the next step nj = colsum(j); lo = max (oldlo, ctsum - sum (colsum(j+1:end))); hi = min (ctsum, oldhi + nj); newnodes = zeros (hi - lo + 1,2); newnodes(:,1) = j; newnodes(:,2) = (lo:hi)'; newnn = 1:size (newnodes,1); nodecount = nodecount + size (newnodes, 1); nodes{1,j+1} = newnodes; ## Find arcs possible to the next step [a0, a1] = meshgrid (oldnn, newnn); a0 = a0(:); a1 = a1(:); oldsum = oldnodes(a0,2); newsum = newnodes(a1,2); xj = newsum - oldsum; ok = (xj >= 0) & (xj <= nj); arcs{1,j} = [a0(ok) a1(ok)]; ## arc connections xj = xj(ok); arcs{2,j} = weights(j) * xj; pj = exp (gammaln (nj + 1) - gammaln (xj + 1) - gammaln (nj - xj + 1)); arcs{3,j} = pj; ## arc probabilities ## Update data structures oldlo = lo; oldhi = hi; oldnodes = newnodes; oldnn = newnn; endfor endfunction ## Calculate backward induction by adding information to NODES array function nodes = backward_induce (nodes, arcs) ## initialize for final node column = size (nodes,2) - 1; startSP = zeros (1); startLP = startSP; startTP = ones (1); for j = column:-1:1 ## destination nodes are previous start nodes endSP = startSP; endLP = startLP; endTP = startTP; ## get new start nodes and information about them a = arcs{1,j}; startmax = max (a(:,1)); startSP = zeros (startmax,1); startLP = startSP; startTP = startSP; arclen = arcs{2,j}; arcprob = arcs{3,j}; for nodenum = 1:startmax % for each start node, compute SP, LP, TP k1 = find (a(:,1) == nodenum); k2 = a(k1,2); startLP(nodenum) = max (arclen(k1) + endLP(k2)); startSP(nodenum) = min (arclen(k1) + endSP(k2)); startTP(nodenum) = sum (arcprob(k1) .* endTP(k2)); endfor ## store information about nodes at this level nodes{2,j} = startLP; nodes{3,j} = startSP; nodes{4,j} = startTP; endfor endfunction ## Get p-values by forward scanning the network function p_val = forward_scan (nodes, arcs, rsstat) NROWS = 50; p_val = zeros (3,1); ## [ProbT] stack = zeros (NROWS, 4); stack(:,1) = Inf; stack(1,1) = 1; ## level of current node stack(1,2) = 1; ## number at this level of current node stack(1,3) = 0; ## length so far to this node stack(1,4) = 1; ## probability so far of reaching this node N = size (stack, 1); i1 = 0; i2 = 0; i3 = 0; while (1) ## Get next lowest level node to process minlevel = min (stack((stack(1:N)>0))); if (isinf (minlevel)) break; endif sp = find (stack(1:N) == minlevel); sp = sp(1); L = stack(sp,1); J = stack(sp,2); pastL = stack(sp,3); pastP = stack(sp,4); stack(sp,1) = Inf; ## Get info for arcs at level L and their target nodes LP = nodes{2,L+1}; SP = nodes{3,L+1}; TP = nodes{4,L+1}; aj = arcs{1,L}; arclen = arcs{2,L}; arcprob = arcs{3,L}; ## Look only at arcs from node J seps = sqrt (eps); arows = find (aj(:,1) == J)'; for k = arows tonode = aj(k,2); thisL = arclen(k); thisP = pastP * arcprob(k); len = pastL + thisL; ## No paths from node J are significant if (len + LP(tonode) < rsstat - seps) p_val(1) = p_val(1) + thisP * TP(tonode); ## All paths from node J are significant elseif (len + SP(tonode) > rsstat + seps) p_val(3) = p_val(3) + thisP * TP(tonode); ## Single match from node J elseif (SP(tonode) == LP(tonode)) p_val(2) = p_val(2) + thisP * TP(tonode); ## Match node J with another already stored node else ## Find a stored node that matches this one r = find (stack(:,1) == L+1); if (any (r)) r = r(stack(r,2) == tonode); if (any (r)) r = r(abs (stack(r,3) - len) < seps); endif endif ## If any one is found, merge node J with it if (any (r)) sp = r(1); stack(sp,4) = stack(sp,4) + thisP; i1 = i1 + 1; ## Otherwise add a new node else z = find (isinf (stack(:,1))); if (isempty (z)) i2 = i2 +1; block = zeros (NROWS, 4); block(:,1) = Inf; stack = [stack; block]; sp = N + 1; N = N + NROWS; else i3 = i3 + 1; sp = z(1); endif stack(sp,1) = L + 1; stack(sp,2) = tonode; stack(sp,3) = len; stack(sp,4) = thisP; endif endif endfor endwhile endfunction statistics-release-1.9.2/inst/Hypothesis_Testing/ranksum.m000066400000000000000000000256521524624707500240300ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{p} =} ranksum (@var{x}, @var{y}) ## @deftypefnx {statistics} {@var{p} =} ranksum (@var{x}, @var{y}, @var{alpha}) ## @deftypefnx {statistics} {@var{p} =} ranksum (@var{x}, @var{y}, @var{alpha}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {@var{p} =} ranksum (@var{x}, @var{y}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{p}, @var{h}] =} ranksum (@var{x}, @var{y}, @dots{}) ## @deftypefnx {statistics} {[@var{p}, @var{h}, @var{stats}] =} ranksum (@var{x}, @var{y}, @dots{}) ## ## Wilcoxon rank sum test for equal medians. This test is equivalent to a ## Mann-Whitney U-test. ## ## @code{@var{p} = ranksum (@var{x}, @var{y})} returns the p-value of a ## two-sided Wilcoxon rank sum test. It tests the null hypothesis that two ## independent samples, in the vectors X and Y, come from continuous ## distributions with equal medians, against the alternative hypothesis that ## they are not. @var{x} and @var{y} can have different lengths and the test ## assumes that they are independent. ## ## @code{ranksum} treats NaN in @var{x}, @var{y} as missing values. ## The two-sided p-value is computed by doubling the most significant one-sided ## value. ## ## @code{[@var{p}, @var{h}] = ranksum (@var{x}, @var{y})} also returns the ## result of the hypothesis test with @code{@var{h} = 1} indicating a rejection ## of the null hypothesis at the default alpha = 0.05 significance level, and ## @code{@var{h} = 0} indicating a failure to reject the null hypothesis at the ## same significance level. ## ## @code{[@var{p}, @var{h}, @var{stats}] = ranksum (@var{x}, @var{y})} also ## returns the structure @var{stats} with information about the test statistic. ## It contains the field @code{ranksum} with the value of the rank sum test ## statistic and if computed with the "approximate" method it also contains the ## value of the z-statistic in the field @code{zval}. ## ## @code{[@dots{}] = ranksum (@var{x}, @var{y}, @var{alpha})} or alternatively ## @code{[@dots{}] = ranksum (@var{x}, @var{y}, "alpha", @var{alpha})} returns ## the result of the hypothesis test performed at the significance level ALPHA. ## ## @code{[@dots{}] = ranksum (@var{x}, @var{y}, "method", @var{M})} defines the ## computation method of the p-value specified in @var{M}, which can be "exact", ## "approximate", or "oldexact". @var{M} must be a single string. When "method" ## is unspecified, the default is: "exact" when ## @code{min (length (@var{x}), length (@var{y})) < 10} and ## @code{length (@var{x}) + length (@var{y}) < 10}, otherwise the "approximate" ## method is used. ## ## @itemize ## @item ## "exact" method uses full enumeration for small total sample size (< 10), ## otherwise the network algorithm is used for larger samples. ## @item ## "approximate" uses normal approximation method for computing the p-value. ## @item ## "oldexact" uses full enumeration for any sample size. Note, that this option ## can lead to out of memory error for large samples. Use with caution! ## @end itemize ## ## @code{[@dots{}] = ranksum (@var{x}, @var{y}, "tail", @var{tail})} defines the ## type of test, which can be "both", "right", or "left". @var{tail} must be a ## single string. ## ## @itemize ## @item ## "both" -- "medians are not equal" (two-tailed test, default) ## @item ## "right" -- "median of X is greater than median of Y" (right-tailed test) ## @item ## "left" -- "median of X is less than median of Y" (left-tailed test) ## @end itemize ## ## Note: the rank sum statistic is based on the smaller sample of vectors ## @var{x} and @var{y}. ## ## @end deftypefn function [p, h, stats] = ranksum(x, y, varargin) ## Check that x and y are vectors if ! isvector (x) || ! isvector (y) error ("X and Y must be vectors"); endif ## Remove missing data and make column vectors x = x(! isnan (x))(:); y = y(! isnan (y))(:); if isempty (x) error ("Not enough data in X"); endif if isempty (y) error ("Not enough data in Y"); endif ## Check for extra input arguments alpha = 0.05; method = []; tail = 'both'; ## Old syntax: ranksum (x, y, alpha) if nargin > 2 && isnumeric (varargin{1}) && isscalar (varargin{1}) alpha = varargin{1}; varargin(1) = []; if isnan (alpha) || alpha <= 0 || alpha >= 1 error ("Alpha does not have a valid value"); endif endif ## Check for Name:Value pairs arg_pairs = length (varargin); if ! (int16 (arg_pairs / 2) == arg_pairs / 2) error ("Extra arguments are not in Name:Value pairs"); endif num_pair = 1; while (arg_pairs) name = varargin{num_pair}; value = varargin{num_pair + 1}; switch (lower (name)) case 'alpha' alpha = value; if (isnan (alpha) || alpha <= 0 || alpha >= 1 || ! isnumeric (alpha) ... || ! isscalar (alpha)) error ("Alpha does not have a valid value"); endif case 'method' method = value; if ! any (strcmpi (method, {'exact', 'approximate', 'oldexact'})) error ("Wrong value for method option"); endif case 'tail' tail = value; if ! any (strcmpi (tail, {'both', 'right', 'left'})) error ("Wrong value for tail option"); endif endswitch arg_pairs -= 2; num_pair += 2; endwhile ## Determine method nx = length (x); ny = length (y); ns = min (nx, ny); if isempty (method) if (ns < 10) && ((nx + ny) < 20) method = 'exact'; else method = 'approximate'; endif endif % Determine computational technique switch method case 'approximate' technique = 'approximation'; case 'oldexact' technique = 'exact'; case 'exact' if (nx + ny) < 10 technique = 'exact'; else technique = 'network_algorithm'; endif endswitch % Compute the rank sum statistic based on the smaller sample if nx <= ny [ranks, tieadj] = tiedrank ([x; y]); x_y = true; else [ranks, tieadj] = tiedrank ([y; x]); x_y = false; endif srank = ranks(1:ns); ranksumstat = sum (srank); ## Calculate p-value according to selected technique switch technique case 'exact' allpos = nchoosek (ranks, ns); sumranks = sum (allpos, 2); np = size (sumranks, 1); switch tail case 'both' p_low = sum (sumranks <= ranksumstat) / np; p_high = sum (sumranks >= ranksumstat) / np; p = 2 * min (p_low, p_high); if p > 1 p = 1; endif case 'right' if x_y p = sum (sumranks >= ranksumstat) / np; else p = sum (sumranks <= ranksumstat) / np; endif case 'left' if x_y p = sum (sumranks <= ranksumstat) / np; else p = sum (sumranks >= ranksumstat) / np; endif endswitch case 'network_algorithm' ## Calculate contingency table u = unique ([x; y]); ct = zeros (2, length (u)); if x_y ct(1,:) = histc (x,u)'; ct(2,:) = histc (y,u)'; else ct(1,:) = histc (y,u)'; ct(2,:) = histc (x,u)'; endif ## Calculate weights for wmw test colsum = sum (ct,1); tmp = cumsum (colsum); weights = [0 tmp(1:end - 1)] + .5 * (1 + diff ([0 tmp])); ## Compute p-value using network algorithm for contingency tables [p_net, p_val] = exact2xkCT (ct, weights, ranksumstat); ## Check if p = NaN if any (isnan (p_net)) || any (isnan (p_val)) p = NaN; else switch tail case 'both' p = 2 * p_net; if p > 1 p = 1; endif case 'right' if x_y p = p_val(2) + p_val(3); else p = p_val(2) + p_val(1); endif case 'left' if x_y p = p_val(2) + p_val(1); else p = p_val(2) + p_val(3); endif endswitch endif case 'approximation' wmean = ns * (nx + ny + 1) / 2; tiescores = 2 * tieadj / ((nx + ny) * (nx + ny - 1)); wvar = nx * ny * ((nx + ny + 1) - tiescores) / 12; wc = ranksumstat - wmean; ## compute z-value, including continuity correction switch tail case 'both' z = (wc - 0.5 * sign (wc)) / sqrt (wvar); if ! x_y z = -z; endif p = 2 * normcdf (-abs (z)); case 'right' if x_y z = (wc - 0.5) / sqrt (wvar); else z = -(wc + 0.5) / sqrt (wvar); endif p = normcdf (-z); case 'left' if x_y z = (wc + 0.5) / sqrt (wvar); else z = -(wc - 0.5) / sqrt (wvar); endif p = normcdf (z); endswitch ## For additional output argument if (nargout > 2) stats.zval = z; endif endswitch ## For additional output arguments if nargout > 1, h = (p <= alpha); if (nargout > 2) if x_y stats.ranksum = ranksumstat; else stats.ranksum = sum (ranks(ns+1:end)); endif endif endif endfunction ## testing against mileage data and results from Matlab %!test %! mileage = [33.3, 34.5, 37.4; 33.4, 34.8, 36.8; ... %! 32.9, 33.8, 37.6; 32.6, 33.4, 36.6; ... %! 32.5, 33.7, 37.0; 33.0, 33.9, 36.7]; %! [p,h,stats] = ranksum (mileage(:,1),mileage(:,2)); %! assert_equal (p, 0.004329004329004329, 1e-14); %! assert_equal (h, true); %! assert_equal (stats.ranksum, 21.5); %!test %! year1 = [51 52 62 62 52 52 51 53 59 63 59 56 63 74 68 86 82 70 69 75 73 ... %! 49 47 50 60 59 60 62 61 71]'; %! year2 = [54 53 64 66 57 53 54 54 62 66 59 59 67 76 75 86 82 67 74 80 75 ... %! 54 50 53 62 62 62 72 60 67]'; %! [p,h,stats] = ranksum (year1, year2, 'alpha', 0.01, 'tail', 'left'); %! assert_equal (p, 0.1270832752950605, 1e-14); %! assert_equal (h, false); %! assert_equal (stats.ranksum, 837.5); %! assert_equal (stats.zval, -1.140287483634606, 1e-14); %! [p,h,stats] = ranksum (year1, year2, 'alpha', 0.01, 'tail', 'left', ... %! 'method', 'exact'); %! assert_equal (p, 0.127343916432862, 1e-14); %! assert_equal (h, false); %! assert_equal (stats.ranksum, 837.5); statistics-release-1.9.2/inst/Hypothesis_Testing/regression_ftest.m000066400000000000000000000305411524624707500257260ustar00rootroot00000000000000## Copyright (C) 1995-2017 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{h}, @var{pval}, @var{stats}] =} regression_ftest (@var{y}, @var{x}, @var{fm}) ## @deftypefnx {statistics} {[@dots{}] =} regression_ftest (@var{y}, @var{x}, @var{fm}, @var{rm}) ## @deftypefnx {statistics} {[@dots{}] =} regression_ftest (@var{y}, @var{x}, @var{fm}, @var{rm}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@dots{}] =} regression_ftest (@var{y}, @var{x}, @var{fm}, [], @var{Name}, @var{Value}) ## ## F-test for General Linear Regression Analysis ## ## Perform a general linear regression F test for the null hypothesis that the ## full model of the form @qcode{y = b_0 + b_1 * x_1 + b_2 * x_2 + @dots{} + ## b_n * x_n + e}, where n is the number of variables in @var{x}, does not ## perform better than a reduced model, such as @qcode{y = b'_0 + b'_1 * x_1 + ## b'_2 * x_2 + @dots{} + b'_k * x_k + e}, where k < n and it corresponds to the ## first k variables in @var{x}. Explanatory (dependent) variable @var{y} and ## response (independent) variables @var{x} must not contain any missing values ## (NaNs). ## ## The full model, @var{fm}, must be a vector of length equal to the columns of ## @var{x}, in which case the constant term b_0 is assumed 0, or equal to ## the columns of @var{x} plus one, in which case the first element is the ## constant b_0. ## ## The reduced model, @var{rm}, must include the constant term and a subset of ## the variables (columns) in @var{x}. If @var{rm} is not given, then a constant ## term b'_0 is assumed equal to the constant term, b_0, of the full model or 0, ## if the full model, @var{fm}, does not have a constant term. @var{rm} must be ## a vector or a scalar if only a constant term is passed into the function. ## ## Name-Value pair arguments can be used to set statistical significance. ## @qcode{'alpha'} can be used to specify the significance level of the test ## (the default value is 0.05). If you want to pass optional Name-Value pair ## without a reduced model, make sure that the latter is passed as an empty ## variable. ## ## If @var{h} is 1 the null hypothesis is rejected, meaning that the full model ## explains the variance better than the restricted model. If @var{h} is 0, it ## can be assumed that the full model does NOT explain the variance any better ## than the restricted model. ## ## The p-value (1 minus the CDF of this distribution at @var{f}) is returned ## in @var{pval}. ## ## Under the null, the test statistic @var{f} follows an F distribution with ## 'df1' and 'df2' degrees of freedom, which are returned as fields in the ## @var{stats} structure along with the test's F-statistic, 'fstat' ## ## @seealso{regression_ttest, regress, regress_gp} ## @end deftypefn function [h, pval, stats] = regression_ftest (y, x, fm, rm, varargin) ## Check for valid input if (nargin < 3) print_usage (); endif ## Check for finite real numbers in Y, X if (! all (isfinite (y)) || ! isreal (y)) error ("regression_ftest: Y must contain finite real numbers."); endif if (! all (isfinite (x(:))) || ! isreal (x)) error ("regression_ftest: X must contain finite real numbers."); endif ## Set default arguments alpha = 0.05; ## Check additional options i = 1; while (i <= length (varargin)) switch lower (varargin{i}) case 'alpha' i = i + 1; alpha = varargin{i}; ## Check for valid alpha if (! isscalar (alpha) || ! isnumeric (alpha) || ... alpha <= 0 || alpha >= 1) error ("regression_ftest: invalid value for alpha."); endif otherwise error ("regression_ftest: invalid Name argument."); endswitch i = i + 1; endwhile ## Get size of response (independent) variables [s, v] = size (x); ## Add a constant term of 1s in X x = [ones(s, 1), x]; ## Check the size of explanatory (dependent) variable if (! (isvector (y) && (length (y) == s))) error ("regression_ftest: Y must be a vector of length 'rows (X)'."); endif y = reshape (y, s, 1); ## Check the full model if (! (isvector (fm) && (length (fm) == v || length (fm) == v + 1))) error (strcat ("regression_ftest: full model, FM, must be a vector", ... " of length equal to 'columns (X)' or 'columns (X) + 1'.")); endif ## Make it row vector and add a constant = 0 if necessary fm_len = length (fm); fm = reshape (fm, 1, fm_len); if (fm_len == v) fm = [0, fm]; fm_len += 1; endif ## Check the reduced model if (nargin - length (varargin) == 4) if (isempty (rm)) rm = [fm(1), zeros(1, fm_len - 1)]; rm_len = 1; else if (! isvector (rm) || ! isnumeric (rm)) error (strcat ("regression_ftest: reduced model, RM, must be a", ... " numeric vector or a scalar.")); endif rm_len = length (rm); ## A reduced model need only be shorter than the full one. Requiring ## rm_len < fm_len - 1 rejected the commonest test of all, dropping a ## single predictor, which the message itself does not ask for. if (rm_len >= fm_len) error (strcat ("regression_ftest: reduced model, RM, must have", ... " smaller length than the full model, FM.")); endif rm = reshape (rm, 1, rm_len); rm = [rm, zeros(1, fm_len - rm_len)]; endif else rm = [fm(1), zeros(1, fm_len - 1)]; rm_len = 1; endif ## Calculate the fitted response for full and reduced models y_fm = sum (x .* fm, 2); y_rm = sum (x .* rm, 2); ## Calculate Sum of Squares Error for full and reduced models SSE_fm = sumsq (y - y_fm); SSE_rm = sumsq (y - y_rm); ## Calculate the necessary statistics stats.df1 = fm_len - rm_len; stats.df2 = s - v; stats.fstat = ((SSE_rm - SSE_fm) / stats.df1) / (SSE_fm / stats.df2); pval = 1 - fcdf (stats.fstat, stats.df1, stats.df2); ## Determine the test outcome ## MATLAB returns this a double instead of a logical array h = double (pval < alpha); endfunction ## The function shipped with error tests only, and nothing that called it ## successfully. Check the returned statistics against a recomputation from ## the construction the documentation describes. %!test %! X = [1 1; 2 1; 3 2; 4 2; 5 3; 6 3; 7 4; 8 4; 9 5; 10 5; 11 6; 12 6]; %! y = 2 + 3 * X(:,1) - 1.5 * X(:,2) + ... %! [0.2 -0.3 0.1 0.4 -0.2 0.3 -0.1 0.2 -0.4 0.1 0.3 -0.2]'; %! n = rows (X); %! Xa = [ones(n,1), X]; %! fm = (Xa \ y)'; %! [h, pval, stats] = regression_ftest (y, X, fm); %! ## with no reduced model the constant is kept and the slopes are zeroed %! rm = [fm(1), 0, 0]; %! SSE_fm = sumsq (y - Xa * fm'); %! SSE_rm = sumsq (y - Xa * rm'); %! assert_equal (stats.df1, 2); %! assert_equal (stats.fstat, ... %! ((SSE_rm - SSE_fm) / stats.df1) / (SSE_fm / stats.df2), 1e-9); %! assert_equal (pval, 1 - fcdf (stats.fstat, stats.df1, stats.df2), 1e-12); %! assert_equal (h, 1); ## A reduced model may drop a single predictor. The guard read ## rm_len >= fm_len - 1, which rejected exactly that, though the message only ## asks for a reduced model shorter than the full one. %!test %! X = [1 1 2; 2 1 1; 3 2 4; 4 2 3; 5 3 6; 6 3 5; ... %! 7 4 8; 8 4 7; 9 5 10; 10 5 9; 11 6 12; 12 6 11]; %! y = 2 + 3 * X(:,1) - 1.5 * X(:,2) + 0.4 * X(:,3) + ... %! [0.2 -0.3 0.1 0.4 -0.2 0.3 -0.1 0.2 -0.4 0.1 0.3 -0.2]'; %! fm = ([ones(rows (X),1), X] \ y)'; %! [~, ~, s3] = regression_ftest (y, X, fm, fm(1:3)); %! assert_equal (s3.df1, 1); %! [~, ~, s2] = regression_ftest (y, X, fm, fm(1:2)); %! assert_equal (s2.df1, 2); %! [~, ~, s1] = regression_ftest (y, X, fm, fm(1)); %! assert_equal (s1.df1, 3); ## A full model that adds nothing to the reduced one gives no evidence. %!test %! X = [1 1; 2 1; 3 2; 4 2; 5 3; 6 3; 7 4; 8 4; 9 5; 10 5; 11 6; 12 6]; %! y = 2 + 3 * X(:,1) - 1.5 * X(:,2) + ... %! [0.2 -0.3 0.1 0.4 -0.2 0.3 -0.1 0.2 -0.4 0.1 0.3 -0.2]'; %! [h, pval, stats] = regression_ftest (y, X, [mean(y), 0, 0]); %! assert_equal (stats.fstat, 0, 1e-12); %! assert_equal (pval, 1, 1e-12); %! assert_equal (h, 0); ## A full model given without a constant term is padded with b_0 = 0. %!test %! X = [1 1; 2 1; 3 2; 4 2; 5 3; 6 3; 7 4; 8 4; 9 5; 10 5; 11 6; 12 6]; %! y = 2 + 3 * X(:,1) - 1.5 * X(:,2) + ... %! [0.2 -0.3 0.1 0.4 -0.2 0.3 -0.1 0.2 -0.4 0.1 0.3 -0.2]'; %! [~, ~, sa] = regression_ftest (y, X, [3, -1.5]); %! [~, ~, sb] = regression_ftest (y, X, [0, 3, -1.5]); %! assert_equal (sa.fstat, sb.fstat, 1e-12); %! assert_equal (sa.df1, sb.df1); ## 'alpha' moves the decision but never the p-value. %!test %! X = [1 1; 2 1; 3 2; 4 2; 5 3; 6 3; 7 4; 8 4; 9 5; 10 5; 11 6; 12 6]; %! y = X(:,1) + [0.9 -1.1 0.8 -0.7 1.2 -0.9 0.6 -1.3 1.1 -0.8 0.7 -1.0]'; %! [~, p1] = regression_ftest (y, X, [mean(y), 0.02, 0], [], 'alpha', 0.01); %! [~, p2] = regression_ftest (y, X, [mean(y), 0.02, 0], [], 'alpha', 0.10); %! assert_equal (p1, p2, 0); ## A row vector for Y must give the same answer as a column. %!test %! X = [1 1; 2 1; 3 2; 4 2; 5 3; 6 3; 7 4; 8 4; 9 5; 10 5; 11 6; 12 6]; %! y = 2 + 3 * X(:,1) - 1.5 * X(:,2) + ... %! [0.2 -0.3 0.1 0.4 -0.2 0.3 -0.1 0.2 -0.4 0.1 0.3 -0.2]'; %! fm = ([ones(rows (X),1), X] \ y)'; %! [~, pc] = regression_ftest (y, X, fm); %! [~, pr] = regression_ftest (y', X, fm); %! assert_equal (pr, pc, 0); ## Test input validation %!error regression_ftest (); %!error ... %! regression_ftest ([1 2 3]', [2 3 4; 3 4 5]'); %!error ... %! regression_ftest ([1 2 NaN]', [2 3 4; 3 4 5]', [1 0.5]); %!error ... %! regression_ftest ([1 2 Inf]', [2 3 4; 3 4 5]', [1 0.5]); %!error ... %! regression_ftest ([1 2 3+i]', [2 3 4; 3 4 5]', [1 0.5]); %!error ... %! regression_ftest ([1 2 3]', [2 3 NaN; 3 4 5]', [1 0.5]); %!error ... %! regression_ftest ([1 2 3]', [2 3 Inf; 3 4 5]', [1 0.5]); %!error ... %! regression_ftest ([1 2 3]', [2 3 4; 3 4 3+i]', [1 0.5]); %!error ... %! regression_ftest ([1 2 3]', [2 3 4; 3 4 5]', [1 0.5], [], 'alpha', 0); %!error ... %! regression_ftest ([1 2 3]', [2 3 4; 3 4 5]', [1 0.5], [], 'alpha', 1.2); %!error ... %! regression_ftest ([1 2 3]', [2 3 4; 3 4 5]', [1 0.5], [], 'alpha', [.02 .1]); %!error ... %! regression_ftest ([1 2 3]', [2 3 4; 3 4 5]', [1 0.5], [], 'alpha', 'a'); %!error ... %! regression_ftest ([1 2 3]', [2 3 4; 3 4 5]', [1 0.5], [], 'some', 0.05); %!error ... %! regression_ftest ([1 2 3]', [2 3; 3 4]', [1 0.5]); %!error ... %! regression_ftest ([1 2; 3 4]', [2 3; 3 4]', [1 0.5]); %!error ... %! regression_ftest ([1 2 3]', [2 3 4; 3 4 5]', [1 0.5], ones (2)); %!error ... %! regression_ftest ([1 2 3]', [2 3 4; 3 4 5]', [1 0.5], 'alpha'); ## RM of length 2 against a full model of length 3 is a legal reduction now, ## so the invalid case is one that is not shorter than the full model. %!error ... %! regression_ftest ([1 2 3]', [2 3 4; 3 4 5]', [1 0.5], [1 2 3]); ## Test results statistics-release-1.9.2/inst/Hypothesis_Testing/regression_ttest.m000066400000000000000000000246571524624707500257570ustar00rootroot00000000000000## Copyright (C) 1995-2017 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} regression_ttest (@var{y}, @var{x}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}] =} regression_ttest (@var{y}, @var{x}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}] =} regression_ttest (@var{y}, @var{x}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}, @var{stats}] =} regression_ttest (@var{y}, @var{x}) ## @deftypefnx {statistics} {[@dots{}] =} regression_ttest (@var{y}, @var{x}, @var{Name}, @var{Value}) ## ## Perform a linear regression t-test. ## ## @code{@var{h} = regression_ttest (@var{y}, @var{x})} tests the null ## hypothesis that the slope @math{beta1} of a simple linear regression equals ## 0. The result is @var{h} = 0 if the null hypothesis cannot be rejected at ## the 5% significance level, or @var{h} = 1 if the null hypothesis can be ## rejected at the 5% level. @var{y} and @var{x} must be vectors of equal ## length with finite real numbers. ## ## The p-value of the test is returned in @var{pval}. A @math{100(1-alpha)%} ## confidence interval for @math{beta1} is returned in @var{ci}. @var{stats} is ## a structure containing the value of the test statistic (@qcode{tstat}), ## the degrees of freedom (@qcode{df}), the slope coefficient (@qcode{beta1}), ## and the intercept (@qcode{beta0}). Under the null, the test statistic ## @var{stats}.@qcode{tstat} follows a @math{T}-distribution with ## @var{stats}.@qcode{df} degrees of freedom. ## ## @code{[@dots{}] = regression_ttest (@dots{}, @var{name}, @var{value})} ## specifies one or more of the following name/value pairs: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'alpha'} @tab the significance level. Default is 0.05. ## ## @item @qcode{'tail'} @tab a string specifying the alternative hypothesis ## @end multitable ## @multitable @columnfractions 0.25 0.65 ## @item @qcode{'both'} @tab @math{beta1} is not 0 (two-tailed, default) ## @item @qcode{'left'} @tab @math{beta1} is less than 0 (left-tailed) ## @item @qcode{'right'} @tab @math{beta1} is greater than 0 (right-tailed) ## @end multitable ## ## @seealso{regression_ftest, regress, regress_gp} ## @end deftypefn function [h, pval, ci, stats] = regression_ttest (y, x, varargin) ## Check for valid input if (nargin < 2) print_usage (); endif ## Check for finite real numbers in Y, X if (! all (isfinite (y)) || ! isreal (y)) error ("regression_ttest: Y must contain finite real numbers."); endif if (! all (isfinite (x(:))) || ! isreal (x)) error ("regression_ttest: X must contain finite real numbers."); endif # Get number of observations n = length (y); ## Check Y and X have the same number of observations if (! isvector (y) || ! isvector (x) || length (x) != n) error ("regression_ttest: Y and X must be vectors of equal length."); endif ## Set default arguments alpha = 0.05; tail = 'both'; ## Check additional options i = 1; while (i <= length (varargin)) switch lower (varargin{i}) case 'alpha' i = i + 1; alpha = varargin{i}; ## Check for valid alpha if (! isscalar (alpha) || ! isnumeric (alpha) || ... alpha <= 0 || alpha >= 1) error ("regression_ttest: invalid value for alpha."); endif case 'tail' i = i + 1; tail = varargin{i}; if (! any (strcmpi (tail, {'both', 'left', 'right'}))) error ("regression_ttest: invalid value for tail."); endif otherwise error ("regression_ttest: invalid Name argument."); endswitch i = i + 1; endwhile y_bar = mean (y); x_bar = mean (x); ## Ordinary least squares slope, taken from the centred data. Going through ## cov (x, y) tied this to whether cov returns the scalar covariance or the ## 2*2 matrix; it returns the matrix, which left every call form of this ## function dying on a nonconformant operand. x_ctr = x - x_bar; Sxx = sum (x_ctr .^ 2); stats.beta1 = sum (x_ctr .* (y - y_bar)) / Sxx; stats.beta0 = y_bar - stats.beta1 * x_bar; ## Fitted values, and from them the residual sum of squares. Evaluating the ## fit at x_bar rather than at x returned y_bar for every observation, since ## the least squares line passes through the means, so SSE was really the ## total sum of squares and the test statistic came out far too small. y_hat = stats.beta0 + stats.beta1 * x; SSE = sum ((y - y_hat) .^ 2); stats.df = n - 2; SE = sqrt (SSE / stats.df); term = SE / sqrt (Sxx); stats.tstat = stats.beta1 / term; ## Based on the "tail" argument determine the P-value, the critical values, ## and the confidence interval. switch lower (tail) case 'both' pval = 2 * (1 - tcdf (abs (stats.tstat), stats.df)); tcrit = - tinv (alpha / 2, stats.df); ci = [stats.beta1 - tcrit * term; stats.beta1 + tcrit * term]; case 'left' pval = tcdf (stats.tstat, stats.df); tcrit = - tinv (alpha, stats.df); ci = [-inf; stats.beta1 + tcrit * term]; case 'right' pval = 1 - tcdf (stats.tstat, stats.df); tcrit = - tinv (alpha, stats.df); ci = [stats.beta1 - tcrit * term; inf]; endswitch ## Determine the test outcome h = double (pval < alpha); h(isnan (pval)) = NaN; endfunction ## The function had no test that called it successfully, which is how every ## documented call form came to die on a nonconformant operand. Check the ## returned quantities against an ordinary least squares fit done here. %!test %! x = [1 2 3 4 5 6 7 8 9 10]'; %! y = 2 + 3 * x + [0.3 -0.2 0.5 -0.4 0.1 0.2 -0.3 0.4 -0.1 0.2]'; %! [h, pval, ci, stats] = regression_ttest (y, x); %! n = numel (x); %! b = [ones(n,1), x] \ y; %! resid = y - [ones(n,1), x] * b; %! se = sqrt (sum (resid .^ 2) / (n - 2) / sum ((x - mean (x)) .^ 2)); %! assert_equal (stats.beta1, b(2), 1e-12); %! assert_equal (stats.beta0, b(1), 1e-12); %! assert_equal (stats.df, n - 2); %! assert_equal (stats.tstat, b(2) / se, 1e-9); %! assert_equal (pval, 2 * (1 - tcdf (abs (b(2) / se), n - 2)), 1e-12); %! assert_equal (ci(:)', [b(2) - tinv(0.975, n-2) * se, ... %! b(2) + tinv(0.975, n-2) * se], 1e-9); %! assert_equal (h, 1); ## The fitted values must come from x, not from its mean. Evaluating the fit ## at x_bar returns y_bar for every observation, because the least squares ## line passes through the means, which made SSE the total sum of squares. %!test %! x = [1 2 3 4 5 6 7 8 9 10]'; %! y = 2 + 3 * x + [0.3 -0.2 0.5 -0.4 0.1 0.2 -0.3 0.4 -0.1 0.2]'; %! [~, ~, ~, stats] = regression_ttest (y, x); %! SSE = sum ((y - (stats.beta0 + stats.beta1 * x)) .^ 2); %! SST = sum ((y - mean (y)) .^ 2); %! assert_equal (stats.tstat, ... %! stats.beta1 / sqrt (SSE / stats.df / sum ((x - mean (x)) .^ 2)), ... %! 1e-9); %! assert_equal (SSE < SST / 100, true); ## A slope of zero must not be rejected. %!test %! x = (1:40)'; %! y = [0.5 -1.2 0.3 0.8 -0.4 1.1 -0.7 0.2 -0.9 0.6 ... %! 1.3 -0.1 0.4 -1.1 0.7 0.9 -0.5 0.1 -0.8 1.0 ... %! -0.3 0.2 1.2 -0.6 0.5 -1.0 0.8 0.3 -0.2 0.9 ... %! -1.3 0.6 0.1 -0.7 1.1 -0.4 0.7 0.2 -0.9 0.4]'; %! [h, pval] = regression_ttest (y, x); %! assert_equal (h, 0); %! assert_equal (pval > 0.05, true); ## The one-sided tails must bracket the two-sided p-value. %!test %! x = [1 2 3 4 5 6 7 8 9 10]'; %! y = 2 + 3 * x + [0.3 -0.2 0.5 -0.4 0.1 0.2 -0.3 0.4 -0.1 0.2]'; %! [~, pb] = regression_ttest (y, x, 'tail', 'both'); %! [~, pr] = regression_ttest (y, x, 'tail', 'right'); %! [~, pl] = regression_ttest (y, x, 'tail', 'left'); %! assert_equal (pr, pb / 2, 1e-12); %! assert_equal (pl, 1 - pr, 1e-12); ## A row-vector input must give the same answer as a column. %!test %! x = [1 2 3 4 5 6 7 8 9 10]'; %! y = 2 + 3 * x + [0.3 -0.2 0.5 -0.4 0.1 0.2 -0.3 0.4 -0.1 0.2]'; %! [~, ~, ~, sc] = regression_ttest (y, x); %! [~, ~, ~, sr] = regression_ttest (y', x'); %! assert_equal (sr.beta1, sc.beta1, 1e-12); %! assert_equal (sr.tstat, sc.tstat, 1e-9); ## Test input validation %!error regression_ttest (); %!error regression_ttest (1); %!error ... %! regression_ttest ([1 2 NaN]', [2 3 4]'); %!error ... %! regression_ttest ([1 2 Inf]', [2 3 4]'); %!error ... %! regression_ttest ([1 2 3+i]', [2 3 4]'); %!error ... %! regression_ttest ([1 2 3]', [2 3 NaN]'); %!error ... %! regression_ttest ([1 2 3]', [2 3 Inf]'); %!error ... %! regression_ttest ([1 2 3]', [3 4 3+i]'); %!error ... %! regression_ttest ([1 2 3]', [3 4 4 5]'); %!error ... %! regression_ttest ([1 2 3]', [2 3 4]', 'alpha', 0); %!error ... %! regression_ttest ([1 2 3]', [2 3 4]', 'alpha', 1.2); %!error ... %! regression_ttest ([1 2 3]', [2 3 4]', 'alpha', [.02 .1]); %!error ... %! regression_ttest ([1 2 3]', [2 3 4]', 'alpha', 'a'); %!error ... %! regression_ttest ([1 2 3]', [2 3 4]', 'some', 0.05); %!error ... %! regression_ttest ([1 2 3]', [2 3 4]', 'tail', 'val'); %!error ... %! regression_ttest ([1 2 3]', [2 3 4]', 'alpha', 0.01, 'tail', 'val'); statistics-release-1.9.2/inst/Hypothesis_Testing/runstest.m000066400000000000000000000301231524624707500242240ustar00rootroot00000000000000## Copyright (C) 2013 Nir Krakauer ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} runstest (@var{x}) ## @deftypefnx {statistics} {@var{h} =} runstest (@var{x}, @var{v}) ## @deftypefnx {statistics} {@var{h} =} runstest (@var{x}, @qcode{'ud'}) ## @deftypefnx {statistics} {@var{h} =} runstest (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{stats}] =} runstest (@dots{}) ## ## Run test for randomness in the vector @var{x}. ## ## @code{@var{h} = runstest (@var{x})} calculates the number of runs of ## consecutive values above or below the mean of @var{x} and tests the null ## hypothesis that the values in the data vector @var{x} come in random order. ## @var{h} is 1 if the test rejects the null hypothesis at the 5% significance ## level, or 0 otherwise. ## ## @code{@var{h} = runstest (@var{x}, @var{v})} tests the null hypothesis based ## on the number of runs of consecutive values above or below the specified ## reference value @var{v}. Values exactly equal to @var{v} are omitted. ## ## @code{@var{h} = runstest (@var{x}, @qcode{'ud'})} calculates the number of ## runs up or down and tests the null hypothesis that the values in the data ## vector @var{x} follow a trend. Too few runs indicate a trend, while too ## many runs indicate an oscillation. Values exactly equal to the preceding ## value are omitted. ## ## @code{@var{h} = runstest (@dots{}, @var{Name}, @var{Value})} specifies ## additional options to the above tests by one or more @var{Name}-@var{Value} ## pair arguments. ## ## @multitable @columnfractions 0.15 0.8 ## @headitem Name @tab Value ## @item @qcode{'alpha'} @tab the significance level. Default is 0.05. ## ## @item @qcode{'method'} @tab a string specifying the method used to ## compute the p-value of the test. It can be either @qcode{'exact'} to use an ## exact algorithm, or @qcode{'approximate'} to use a normal approximation. The ## default is @qcode{'exact'} for runs above/below, and for runs up/down when ## the length of x is less than or equal to 50. When testing for runs up/down ## and the length of @var{x} is greater than 50, then the default is ## @qcode{'approximate'}, and the @qcode{'exact'} method is not available. ## ## @item @qcode{'tail'} @tab a string specifying the alternative hypothesis ## @end multitable ## @multitable @columnfractions 0.15 0.5 ## @item @qcode{'both'} @tab two-tailed (default) ## @item @qcode{'left'} @tab left-tailed ## @item @qcode{'right'} @tab right-tailed ## @end multitable ## ## @seealso{signrank, signtest} ## @end deftypefn function [h, pval, stats] = runstest (x, v, varargin) ## Check arguments if (nargin < 1) print_usage; endif ## Check X being a vector of scalar values if (! isvector (x) || ! isnumeric (x)) error ("runstest: X must be a vector a scalar values."); else ## Remove missing values (NaNs) x(isnan (x)) = []; endif ## Check second argument being either a scalar reference number or "ud" string if (nargin > 1) if (isempty (v)) v = mean (x); endif if (isnumeric (v) && isscalar (v)) x = sign (x - v); rm = x == 0; if (sum (rm) > 0) warning ("runstest: %d elements equal to V were omitted.", sum (rm)); endif x(rm) = []; N = numel (x); UD = false; elseif (strcmpi (v, 'ud')) x = diff (x); rm = x == 0; if (sum (rm) > 0) warning ("runstest: %d repeated elements were omitted.", sum (rm)); endif x(rm) = []; N = numel (x) + 1; UD = true; else error ("runstest: V must be either a scalar number or 'ud' char string."); endif v = v; else v = mean (x); x = sign (x - v); rm = x == 0; if (sum (rm) > 0) warning ("runstest: %d elements equal to 'mean(X)' were omitted.", ... sum (rm)); endif x(rm) = []; N = numel (x); UD = false; endif ## Get number of runs n_up = sum (x==1); n_dn = numel (x) - n_up; ## Add defaults alpha = 0.05; if (N < 50 || ! UD) method = 'exact'; else method = 'approximate'; endif tail = 'both'; ## Parse optional arguments and validate parameters while (numel (varargin) > 1) switch (lower (varargin{1})) case 'alpha' alpha = varargin{2}; if (! isscalar (alpha) || ! isnumeric (alpha) || alpha <= 0 || alpha >= 1) error ("runstest: invalid value for alpha."); endif case 'method' method = varargin{2}; if (! any (strcmpi (method, {'exact', 'approximate'}))) error ("runstest: invalid value for method."); endif if (strcmpi (method, 'exact') && N > 50) warning ("runstest: exact method is not available for N > 50."); method = 'approximate'; endif case 'tail' tail = varargin{2}; if (! any (strcmpi (tail, {'both', 'left', 'right'}))) error ("runstest: invalid value for tail."); endif otherwise error ("runstest: invalid optional argument."); endswitch varargin([1:2]) = []; endwhile ## Do the calculations here if (N > 0) R_num = sum (x([1:end-1]) != x([2:end])) + 1; ##R_num = sum ((x(1:(end-1)) .* x(2:end)) < 0) + 1; #number of runs ## Special case if (N == 1) z = NaN; ## Compute with z statistic else ## Handle up/down or above/below if (UD) R_bar = (2 * N - 1) / 3; R_std = sqrt ((16 * N - 29) / 90); else R_bar = 1 + 2 * n_up * n_dn / N; R_std = sqrt (2 * n_up * n_dn * (2 * n_up * n_dn - N) / ... (N ^ 2 * (N - 1))); endif ## Handle tail if (strcmpi (tail, 'both')) tc = -0.5 * sign (R_num - R_bar); elseif (strcmpi (tail, 'left')) tc = 0.5; else tc = -0.5; endif ## Compute z value if (R_std > 0) z = (R_num + tc - R_bar) / R_std; else z = Inf * sign (R_num + tc - R_bar); endif endif ## Exact method if (strcmpi (method, 'exact')) if (UD) R_max = N - 1; ## Get precalculated results from rundist.mat file temp = load ('rundist.mat'); runD = temp.rundist; M = runD{N}; p = M / sum (M); p = p([1:R_max]); else R_max = 2 * min ([n_up, n_dn]) + 1; if (n_up == 0 || n_dn == 0) p = 1; else R_vec = [1:R_max]; p = zeros (size (R_vec)); t = mod (R_vec, 2) == 0; ## Compute even if (any (t)) k = R_vec(t) / 2; p(t) = 2 * exp (logBinoCoeff (n_up - 1, k - 1) + ... logBinoCoeff (n_dn - 1, k - 1) - ... logBinoCoeff (N, n_dn)); endif ## Compute odd if (any (! t)) k = floor (R_vec(! t) / 2); logdenom = logBinoCoeff (N, n_dn); p(! t) = exp (logBinoCoeff (n_up - 1, k - 1) + ... logBinoCoeff (n_dn - 1, k) - logdenom) + ... exp (logBinoCoeff (n_up - 1, k) + ... logBinoCoeff (n_dn - 1, k - 1) - logdenom); endif endif endif if (isempty (p)) p_ex = 1; else p_ex = p(R_num); endif p_lo = sum (p([1:R_num-1])); p_hi = sum (p([R_num+1:end])); else ## Compute with z statistic p_ex = 0; p_lo = normcdf (z); p_hi = normcdf (-z); endif ## Assume a constant vector in data else R_num = NaN; p_ex = 1; p_lo = 0; p_hi = 0; z = NaN; endif ## Compute tail probability if (strcmpi (tail, 'both')) pval = min ([1, 2*(p_ex + min ([p_lo, p_hi]))]); elseif (strcmpi (tail, 'left')) pval = p_ex + p_lo; else pval = p_ex + p_hi; endif ## Return decision of test h = double (pval <= alpha); if (nargout > 2) stats.nruns = R_num; stats.n1 = n_up; stats.n0 = n_dn; stats.z = z; endif endfunction ## Compute the log of the binomial coefficient function logBC = logBinoCoeff (N,n) logBC = gammaln (N + 1) - gammaln (n + 1) - gammaln (N - n + 1); endfunction %!test %! ## NIST beam deflection data %! ## http://www.itl.nist.gov/div898/handbook/eda/section4/eda425.htm %! data = [-213, -564, -35, -15, 141, 115, -420, -360, 203, -338, -431, ... %! 194, -220, -513, 154, -125, -559, 92, -21, -579, -52, 99, -543, ... %! -175, 162, -457, -346, 204, -300, -474, 164, -107, -572, -8, 83, ... %! -541, -224, 180, -420, -374, 201, -236, -531, 83, 27, -564, -112, ... %! 131, -507, -254, 199, -311, -495, 143, -46, -579, -90, 136, ... %! -472, -338, 202, -287, -477, 169, -124, -568, 17, 48, -568, -135, ... %! 162, -430, -422, 172, -74, -577, -13, 92, -534, -243, 194, -355, ... %! -465, 156, -81, -578, -64, 139, -449, -384, 193, -198, -538, 110, ... %! -44, -577, -6, 66, -552, -164, 161, -460, -344, 205, -281, -504, ... %! 134, -28, -576, -118, 156, -437, -381, 200, -220, -540, 83, 11, ... %! -568, -160, 172, -414, -408, 188, -125, -572, -32, 139, -492, ... %! -321, 205, -262, -504, 142, -83, -574, 0, 48, -571, -106, 137, ... %! -501, -266, 190, -391, -406, 194, -186, -553, 83, -13, -577, -49, ... %! 103, -515, -280, 201, 300, -506, 131, -45, -578, -80, 138, -462, ... %! -361, 201, -211, -554, 32, 74, -533, -235, 187, -372, -442, 182, ... %! -147, -566, 25, 68, -535, -244, 194, -351, -463, 174, -125, -570, ... %! 15, 72, -550, -190, 172, -424, -385, 198, -218, -536, 96]; %! [h, p, stats] = runstest (data, median (data)); %! expected_h = 1; %! expected_p = 0.008562; %! expected_z = 2.6229; %! assert_equal (h, expected_h); %! assert_equal (p, expected_p, 1E-6); %! assert_equal (stats.z, expected_z, 1E-4); %!shared x %! x = [45, -60, 1.225, 55.4, -9 27]; %!test %! [h, p, stats] = runstest (x); %! assert_equal (h, 0); %! assert_equal (p, 0.6, 1e-14); %! assert_equal (stats.nruns, 5); %! assert_equal (stats.n1, 3); %! assert_equal (stats.n0, 3); %! assert_equal (stats.z, 0.456435464587638, 1e-14); %!test %! [h, p, stats] = runstest (x, [], 'method', 'approximate'); %! assert_equal (h, 0); %! assert_equal (p, 0.6481, 1e-4); %! assert_equal (stats.z, 0.456435464587638, 1e-14); %!test %! [h, p, stats] = runstest (x, [], 'tail', 'left'); %! assert_equal (h, 0); %! assert_equal (p, 0.9, 1e-14); %! assert_equal (stats.z, 1.369306393762915, 1e-14); %!error runstest (ones (2,20)) %!error runstest (['asdasda']) %!error ... %! runstest ([2 3 4 3 2 3 4], 'updown') %!error ... %! runstest ([2 3 4 3 2 3 4], [], 'alpha', 0) %!error ... %! runstest ([2 3 4 3 2 3 4], [], 'alpha', [0.02 0.2]) %!error ... %! runstest ([2 3 4 3 2 3 4], [], 'alpha', 1.2) %!error ... %! runstest ([2 3 4 3 2 3 4], [], 'alpha', -0.05) %!error ... %! runstest ([2 3 4 3 2 3 4], [], 'method', 'some') %!error ... %! runstest ([2 3 4 3 2 3 4], [], 'tail', 'some') %!error ... %! runstest ([2 3 4 3 2 3 4], [], 'option', 'some') statistics-release-1.9.2/inst/Hypothesis_Testing/sampsizepwr.m000066400000000000000000001265311524624707500247320ustar00rootroot00000000000000## Copyright (C) 2022 Andrew Penn ## Copyright (C) 2022-2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{n} =} sampsizepwr (@var{testtype}, @var{params}, @var{p1}) ## @deftypefnx {statistics} {@var{n} =} sampsizepwr (@var{testtype}, @var{params}, @var{p1}, @var{power}) ## @deftypefnx {statistics} {@var{power} =} sampsizepwr (@var{testtype}, @var{params}, @var{p1}, [], @var{n}) ## @deftypefnx {statistics} {@var{p1} =} sampsizepwr (@var{testtype}, @var{params}, [], @var{power}, @var{n}) ## @deftypefnx {statistics} {[@var{n1}, @var{n2}] =} sampsizepwr (@qcode{'t2'}, @var{params}, @var{p1}, @var{power}) ## @deftypefnx {statistics} {[@dots{}] =} sampsizepwr (@var{testtype}, @var{params}, @var{p1}, @var{power}, @var{n}, @var{name}, @var{value}) ## ## Sample size and power calculation for hypothesis test. ## ## @code{sampsizepwr} computes the sample size, power, or alternative parameter ## value for a hypothesis test, given the other two values. For example, you can ## compute the sample size required to obtain a particular power for a ## hypothesis test, given the parameter value of the alternative hypothesis. ## ## @code{@var{n} = sampsizepwr (@var{testtype}, @var{params}, @var{p1})} returns ## the sample size N required for a two-sided test of the specified type to have ## a power (probability of rejecting the null hypothesis when the alternative is ## true) of 0.90 when the significance level (probability of rejecting the null ## hypothesis when the null hypothesis is true) is 0.05. @var{params} specifies ## the parameter values under the null hypothesis. P1 specifies the value of ## the single parameter being tested under the alternative hypothesis. For the ## two-sample t-test, N is the value of the equal sample size for both samples, ## @var{params} specifies the parameter values of the first sample under the ## null and alternative hypotheses, and P1 specifies the value of the single ## parameter from the other sample under the alternative hypothesis. ## ## The following TESTTYPE values are available: ## ## @multitable @columnfractions 0.1 0.85 ## @item "z" @tab one-sample z-test for normally distributed data with ## known standard deviation. @var{params} is a two-element vector [MU0 SIGMA0] ## of the mean and standard deviation, respectively, under the null hypothesis. ## P1 is the value of the mean under the alternative hypothesis. ## @item "t" @tab one-sample t-test or paired t-test for normally ## distributed data with unknown standard deviation. @var{params} is a ## two-element vector [MU0 SIGMA0] of the mean and standard deviation, ## respectively, under the null hypothesis. P1 is the value of the mean under ## the alternative hypothesis. ## @item "t2" @tab two-sample pooled t-test (test for equal means) for ## normally distributed data with equal unknown standard deviations. ## @var{params} is a two-element vector [MU0 SIGMA0] of the mean and standard ## deviation of the first sample under the null and alternative hypotheses. P1 ## is the the mean of the second sample under the alternative hypothesis. ## @item "var" @tab chi-square test of variance for normally distributed ## data. @var{params} is the variance under the null hypothesis. P1 is the ## variance under the alternative hypothesis. ## @item "p" @tab test of the P parameter (success probability) for a ## binomial distribution. @var{params} is the value of P under the null ## hypothesis. P1 is the value of P under the alternative hypothesis. ## @item "r" @tab test of the correlation coefficient parameter for ## significance. @var{params} is the value of r under the null hypothesis. ## P1 is the value of r under the alternative hypothesis. ## @end multitable ## ## The "p" test for the binomial distribution is a discrete test for which ## increasing the sample size does not always increase the power. For N values ## larger than 200, there may be values smaller than the returned N value that ## also produce the desired power. ## ## @code{@var{n} = sampsizepwr (@var{testtype}, @var{params}, @var{p1}, ## @var{power})} returns the sample size N such that the power is @var{power} ## for the parameter value P1. For the two-sample t-test, N is the equal sample ## size of both samples. ## ## @code{[@var{n1}, @var{n2}] = sampsizepwr ("t2", @var{params}, @var{p1}, ## @var{power})} returns the sample sizes @var{n1} and @var{n2} for the two ## samples. These values are the same unless the "ratio" parameter, ## @code{@var{ratio} = @var{n2} / @var{n2}}, is set to a value other than ## the default (See the name/value pair definition of ratio below). ## ## @code{@var{power} = sampsizepwr (@var{testtype}, @var{params}, @var{p1}, [], ## @var{n})} returns the power achieved for a sample size of @var{n} when the ## true parameter value is @var{p1}. For the two-sample t-test, @var{n} is the ## smaller one of the two sample sizes. ## ## @code{@var{p1} = sampsizepwr (@var{testtype}, @var{params}, [], @var{power}, ## @var{n})} returns the parameter value detectable with the specified sample ## size @var{n} and power @var{power}. For the two-sample t-test, @var{n} is ## the smaller one of the two sample sizes. When computing @var{p1} for the "p" ## test, if no alternative can be rejected for a given @var{params}, @var{n} and ## @var{power} value, the function displays a warning message and returns NaN. ## ## @code{[@dots{}] = sampsizepwr (@dots{}, @var{n}, @var{name}, @var{value})} ## specifies one or more of the following @var{name} / @var{value} pairs: ## ## @multitable @columnfractions 0.15 0.8 ## @headitem @var{Name} @tab @var{Value} ## @item "alpha" @tab significance level of the test (default is 0.05) ## @item "tail" @tab the type of test which can be: ## @end multitable ## ## @multitable @columnfractions 0.20 0.7 ## @item "both" @tab two-sided test for an alternative @var{p1} not equal ## to @var{params} ## ## @item "right" @tab one-sided test for an alternative @var{p1} larger ## than @var{params} ## ## @item "left" @tab one-sided test for an alternative @var{p1} smaller ## than @var{params} ## @end multitable ## ## @multitable @columnfractions 0.15 0.8 ## @item "ratio" @tab desired ratio @var{n2} / @var{n2} of the larger ## sample size @var{n2} to the smaller sample size @var{n1}. Used only for the ## two-sample t-test. The value of @code{@var{ratio}} is greater than or equal ## to 1 (default is 1). ## @end multitable ## ## @code{sampsizepwr} computes the sample size, power, or alternative hypothesis ## value given values for the other two. Specify one of these as [] to compute ## it. The remaining parameters (and ALPHA, RATIO) can be scalars or arrays of ## the same size. ## ## @seealso{vartest, ttest, ttest2, ztest, binocdf} ## @end deftypefn function [out, N2] = sampsizepwr (TestType, params, p1, power, n, varargin) ## Check for valid number of input arguments narginchk (3, Inf); ## Add defaults for 3 or 4 input arguments if (nargin == 3) power = 0.90; n = []; elseif (nargin == 4) n = []; endif ## Force TestType to lower case TestType = lower (TestType); ## Check for valid test type and corresponding size of parameters t_types = {'z', 't', 't2', 'var', 'p', 'r'}; nparams = [2, 2, 2, 1, 1, 1]; if (isempty (TestType) || ! ischar (TestType) || size (TestType, 1) != 1) error ("sampsizepwr: test type must be a non-empty character vector."); endif if (sum (strcmp (TestType, t_types)) != 1) error ("sampsizepwr: invalid test type."); endif if (! isnumeric (params)) error ("sampsizepwr: parameters must be numeric."); endif if (length (params) != nparams(strcmp (TestType, t_types))) error ("sampsizepwr: invalid size of parameters for this test type."); endif ## Check for correct number of output arguments if ((nargout > 1) && ! strcmp (TestType, 't2')) error ("sampsizepwr: wrong number of output arguments for this test type."); endif Lbound = [-Inf, -Inf, -Inf, 0, 0, -1]; Ubound = [ Inf, Inf, Inf, Inf, 1, 1]; Lbound = Lbound(strcmp (TestType, t_types)); Ubound = Ubound(strcmp (TestType, t_types)); ## Check for invalid parameters specific to each test type switch (TestType) case 'z' if (params(2) <= 0) error ("sampsizepwr: negative or zero variance."); endif PowerFunction = @PowerFunction_N; case 't' if (params(2) <= 0) error ("sampsizepwr: negative or zero variance."); endif PowerFunction = @PowerFunction_T; case 't2' if (params(2) <= 0) error ("sampsizepwr: negative or zero variance."); endif PowerFunction = @PowerFunction_T2; case 'var' if (params(1) <= 0) error ("sampsizepwr: negative or zero variance."); endif PowerFunction = @PowerFunction_V; case 'p' if (params(1) <= 0 || params(1) >= 1) error ("sampsizepwr: out of range probability."); endif PowerFunction = @PowerFunction_P; case 'r' if (params(1) <= -1 || params(1) >= 1) error ("sampsizepwr: out of range regression coefficient."); endif PowerFunction = @PowerFunction_R; endswitch ## Parse optional Name-Value paired arguments optNames = {'alpha', 'tail', 'ratio'}; dfValues = {0.05, 'both', 1}; [alpha, tail, ratio, args] = parsePairedArguments (optNames, dfValues, ... varargin(:)); ## Validate optional parameters if (! isempty (args)) error ("sampsizepwr: unrecognized optional Name-Value parameter name."); endif if (numel (varargin) > 0) if (! isnumeric (alpha) || any (alpha(:) <= 0) || any (alpha(:) >= 1)) error ("sampsizepwr: invalid value for 'alpha' parameter."); endif tail = lower (tail); if (! ischar (tail) || (size (tail, 1) != 1)) error (strcat ("sampsizepwr: 'tail' parameter must", ... " be a non-empty character vector.")); endif if (! ismember (tail, {'left', 'both', 'right'})) error ("sampsizepwr: invalid value for 'tail' parameter."); endif if (! isnumeric (ratio) || any (ratio(:) < 1)) error ("sampsizepwr: invalid value for 'ratio' parameter."); endif endif ## Check that only one of either p1, power, or n are missing if (isempty (p1) + isempty (power) + isempty (n) != 1) error ("sampsizepwr: only one of either p1, power, or n must be missing."); endif ## Check for valid P1 if (! isempty (p1)) if (! isnumeric (p1)) error ("sampsizepwr: alternative hypothesis parameter must be numeric."); elseif ((! strcmp (tail, 'right') && any (p1(:) <= Lbound)) || (! strcmp (tail, 'left') && any (p1(:) >= Ubound))) error ("sampsizepwr: alternative hypothesis parameter out of range."); endif endif ## Check for valid POWER if (! isempty (power) && (! isnumeric (power) || any (power(:) <= 0) || any (power(:) >= 1))) error ("sampsizepwr: invalid value for POWER."); endif if (! isempty (power) && any (power(:) <= alpha(:))) error ("sampsizepwr: Cannot compute N or P1 unless POWER > 'alpha'."); endif ## Expand non-empty P1/POWER/N so they are all the same size if (isempty (p1)) [err, power, n, alpha, ratio] = common_size (power, n, alpha, ratio); outclass = getclass (power, n, alpha, ratio); elseif (isempty (power)) [err, p1, n, alpha, ratio] = common_size (p1, n, alpha, ratio); outclass = getclass (p1, n, alpha, ratio); else # n is empty [err, p1, power, alpha, ratio] = common_size (p1, power, alpha, ratio); outclass = getclass (power, p1, alpha, ratio); endif if (err > 0) error ("sampsizepwr: input arguments size mismatch."); endif ## Check for valid options when computing N if (isempty (n)) if (any (p1(:) == params(1))) error ("sampsizepwr: Same value for null and alternative hypothesis."); elseif (strcmp (tail, 'left') && any (p1(:) >= params(1))) error ("sampsizepwr: Invalid P1 for testing left tail."); elseif (strcmp (tail, 'right') && any (p1(:) <= params(1))) error ("sampsizepwr: Invalid P1 for testing right tail."); endif endif ## Allocate output of proper size and class out = zeros (size (alpha), outclass); ## Compute whichever one of P1/POWER/N that is now empty if (isempty (p1)) ## Compute effect size given power and sample size switch (TestType) case 'z' ## z (normal) test out(:) = findP1z (params(1), params(2), power, n, alpha, tail); case 't' ## t-test out(:) = findP1t (params(1), params(2), power, n, alpha, tail); case 't2' ## two-sample t-test out(:) = findP1t2 (params(1), params(2), power, n, alpha, tail, ratio); case 'var' ## chi-square (variance) test out(:) = findP1v (params(1), power, n, alpha, tail); case 'p' ## binomial (p) test out(:) = findP1p (params(1), power, n, alpha, tail); case 'r' ## regression coefficient (r) test out(:) = findP1r (params(1), power, n, alpha, tail); endswitch elseif (isempty (power)) ## Compute power given effect size and sample size switch (TestType) case {'z', 't'} out(:) = PowerFunction(params(1), p1, params(2), alpha, tail, n); case 't2' out(:) = PowerFunction(params(1), p1, params(2), alpha, tail, n, ratio); case {'var', 'p', 'r'} out(:) = PowerFunction(params(1), p1, alpha, tail, n); endswitch else ## Compute sample size given power and effect size switch (TestType) case {'z', 't'} ## Calculate one-sided Z value directly out(:) = z1testN (params(1), p1, params(2), power, alpha, tail); ## Iterate upward from there for the other cases if (strcmp (TestType, 't') || strcmp (tail, 'both')) if (strcmp (TestType, 't')) out = max (out, 2); endif ## Count upward until we get the value we need elem = 1:numel (alpha); while (! isempty (elem)) #MK: THE BUG WAS _T ON THE NEXT LINE! actualpower = PowerFunction(params(1), p1(elem), params(2), ... alpha(elem), tail, out(elem)); elem = elem(actualpower < power(elem)); out(elem) = out(elem) + 1; endwhile endif case 't2' ## Initialize second output argument N2 = zeros (size (alpha), outclass); ## Caculate one-sided two-sample t-test iteratively [out(:), N2(:)] = t1testN (params(1), p1, params(2), power, ... alpha, tail, ratio); case 'var' ## Use a binary search method out(:) = searchbinaryN (PowerFunction, [1, 100], params(1), ... p1, power, alpha, tail); case 'p' ## Use a binary search method out(:) = searchbinaryN (PowerFunction, [0, 100], params(1), ... p1, power, alpha, tail); ## Adjust for discrete distribution t = out <= 200; if (any (t(:))) ## Try values from 1 up to N (out) and pick the smallest value out(t) = adjdiscreteN (out(t), PowerFunction, params(1), ... p1(t), alpha(t), tail, power(t)); endif if (any (! t(:))) warning ("sampsizepwr: approximate N."); endif case 'r' ## Calculate sample size from Fisher's z transformation out(:) = rtestN (params(1), p1, power, alpha, tail); endswitch endif endfunction ## Define class for output function out = getclass (varargin) if (any (cellfun (@(x) isa (x, 'single'), varargin))) out = 'single'; else out = 'double'; endif endfunction ## Sample size calculation for the one-sided Z test function N = z1testN (mu0, mu1, sig, desiredpower, alpha, tail) ## Compute the one-sided normal value directly if (strcmp (tail, 'both')) alpha = alpha ./ 2; endif z1 = -norminv (alpha); z2 = norminv (1 - desiredpower); mudiff = abs (mu0 - mu1) / sig; N = ceil (((z1 - z2) ./ mudiff) .^ 2); endfunction ## Sample size calculation for R test function N = rtestN (r0, r1, desiredpower, alpha, tail) ## Compute only for 2-tailed test if (strcmp (tail, 'both')) alpha = alpha ./ 2; else error ("sampsizepwr: only 2-tailed testing for regression coefficient."); endif ## Get quantiles of the standard normal deviates for alpha and power Za = norminv (alpha); Zb = norminv (1 - desiredpower); ## Fisher's z transformation of each correlation. Transforming their ## difference instead is only right when the null correlation is zero, which ## happened to be the one value this test type refused to accept. C = abs (atanh (r1) - atanh (r0)); ## Compute sample size N = ceil (((Za + Zb) ./ C) .^ 2 + 3); endfunction ## Find alternative hypothesis parameter value P1 for Z test function mu1 = findP1z (mu0, sig, desiredpower, N, alpha, tail) if (strcmp (tail, 'both')) alpha = alpha ./ 2; endif sig = sig ./ sqrt (N); ## Get quantiles of the normal or t distribution if (strcmp (tail, 'left')) z1 = norminv (alpha); z2 = norminv (desiredpower); else # upper or two-tailed test z1 = norminv (1 - alpha); z2 = norminv (1 - desiredpower); endif mu1 = mu0 + sig .* (z1 - z2); ## For 2-sided test, refine by taking the other tail into account if (strcmp (tail, 'both')) elem = 1:numel (alpha); desiredbeta = 1 - desiredpower; betahi = desiredbeta; betalo = zeros (size (desiredbeta)); while (true) ## Compute probability of being below the lower critical value under H1 betalo(elem) = normcdf (-z1(elem) + (mu0 - mu1(elem)) ./ sig(elem)); ## See if the upper and lower probabilities are close enough elem = elem(abs ((betahi(elem) - betalo(elem)) - desiredbeta(elem)) > ... 1e-6 * desiredbeta(elem)); if (isempty (elem)) break endif ## Find a new mu1 by adjusting beta to take lower tail into account betahi(elem) = desiredbeta(elem) + betalo(elem); mu1(elem) = mu0 + sig(elem) .* (z1(elem) - norminv (betahi(elem))); endwhile endif endfunction ## Find alternative hypothesis parameter value P1 for t-test function mu1 = findP1t (mu0, sig, desiredpower, N, alpha, tail) if (strcmp (tail, 'both')) a2 = alpha ./ 2; else a2 = alpha; endif ## Get quantiles of the normal or t distribution if (strcmp (tail, 'left')) z1 = norminv (alpha); z2 = norminv (desiredpower); else # upper or two-tailed test z1 = norminv (1-a2); z2 = norminv (1-desiredpower); endif mu1 = mu0 + sig .* (z1-z2) ./ sqrt (N); ## Refine using fzero for j=1:numel (mu1) if (mu1(j) > mu0) F0 = @(mu1arg) PowerFunction_T (mu0, max (mu0, mu1arg), sig, alpha(j), ... tail, N(j)) - desiredpower(j); else F0 = @(mu1arg) desiredpower(j) - PowerFunction_T (mu0, min (mu0, ... mu1arg), sig, alpha(j), tail, N(j)); endif mu1(j) = fzero (F0, mu1(j)); endfor endfunction ## Sample size calculation for the one-sided two-sample t-test function [N1, N2] = t1testN (mu0, mu1, sig, desiredpower, alpha, tail, ratio) if (strcmp (tail, 'both')) alpha = alpha ./ 2; endif ## Compute the initial value of N, approximated by normal distribution z1 = -norminv (alpha); z2 = norminv (1 - desiredpower); n_0 = ceil ((z1 - z2) .^2 .* (sig ./ abs ((mu0 - mu1))) .^ 2 * 2); ## n need to be > 1, otherwise the degree of freedom of t < 0 n_0(n_0 <= 1) = 2; N = ones (size (n_0)); ## iteratively update the sample size if (strcmp (tail, 'both')) for j = 1:numel (n_0) F = @(n) nctcdf (tinv (alpha(j), n + ratio(j) .* n - 2), ... n + ratio(j) .* n - 2, abs (mu1(j) - mu0) ./ ... (sig .* sqrt (1 ./ n + 1 ./ (ratio(j) .* n)))) + ... (1 - nctcdf (- tinv (alpha(j), n + ratio(j) .* n - 2), ... n + ratio(j) .* n - 2, abs (mu1(j) - mu0) ./ ... (sig .* sqrt (1 ./ n + 1 ./ (ratio(j) .* n)))))- ... desiredpower(j); N(j) = localfzero (F, n_0(j), ratio); endfor else for j = 1:numel (n_0) F = @(n) (1 - nctcdf (- tinv (alpha(j), n + ratio(j) .* n - 2), ... n + ratio(j) .* n - 2, abs (mu1(j) - mu0) ./ (sig .* ... sqrt (1 ./ n + 1 ./ (ratio(j) .* n))))) - desiredpower(j); N(j) = localfzero (F, n_0(j), ratio); endfor endif N1 = ceil (N); N2 = ceil (ratio .* N); endfunction ## Find alternative hypothesis parameter value P1 for two-sample t-test function mu1 = findP1t2 (mu0, sig, desiredpower, N, alpha, tail, ratio) if (strcmp (tail, 'both')) a2 = alpha ./ 2; else a2 = alpha; endif ## Get quantiles of the normal or t distribution if (strcmp (tail, 'left')) t1 = tinv (alpha, N + ratio .* N - 2); t2 = tinv (desiredpower, N + ratio .* N - 2); else # upper or two-tailed test t1 = tinv (1 - a2, N + ratio .* N - 2); # upper tail under H0 t2 = tinv (1 - desiredpower, N + ratio .* N - 2); # lower tail under H1 endif mu1 = mu0 + sig .* (t1 - t2) .* sqrt (1 ./ N + 1 ./ (ratio .* N)); ## Refine using fzero for j = 1:numel (mu1) if (mu1(j) > mu0) F0 = @(mu1arg) PowerFunction_T2 (mu0, max (mu0, mu1arg), sig, ... alpha(j), tail, N(j), ratio(j)) - desiredpower(j); else F0 = @(mu1arg) desiredpower(j) - PowerFunction_T2 (mu0, min (mu0, ... mu1arg), sig, alpha(j), tail, N(j), ratio(j)); endif mu1(j) = fzero (F0, mu1(j)); endfor endfunction ## Find alternative hypothesis parameter value P1 for variance test function p1 = findP1v (p0, desiredpower, N, alpha, tail) ## F and Finv are the cdf and inverse cdf F = @(x,n,p1) chi2cdf (x .* (n - 1) ./ p1, n - 1); # cdf for s^2 Finv = @(p,n,p1) p1 .* chi2inv (p, n - 1) ./ (n - 1); # inverse if (strcmp (tail, 'both')) alpha = alpha ./ 2; endif desiredbeta = 1 - desiredpower; ## Calculate critical values and p1 for one-sided test if (! strcmp (tail, 'left')) critU = Finv(1 - alpha, N, p0); p1 = 1 ./ Finv(desiredbeta, N, 1 ./ critU); endif if (! strcmp (tail, 'right')) critL = Finv(alpha, N, p0); endif if (strcmp (tail, 'left')) p1 = 1 ./ Finv(desiredpower, N, 1 ./ critL); endif if (strcmp (tail, 'both')) ## For 2-sided test, we have the upper tail probability under H1. ## Refine by taking the other tail into account. elem = 1:numel (alpha); betahi = desiredbeta; betalo = zeros (size (desiredbeta)); while (true) ## Compute probability of being in the lower tail under H1 betalo(elem) = F(critL(elem), N(elem), p1(elem)); ## See if the upper and lower probabilities are close enough obsbeta = betahi(elem) - betalo(elem); elem = elem(abs (obsbeta - desiredbeta(elem)) > 1e-6 * desiredbeta(elem)); if (isempty (elem)) break endif ## Find a new mu1 by adjusting beta to take lower tail into account betahi(elem) = desiredbeta(elem) + betalo(elem); p1(elem) = 1 ./ Finv(betahi(elem), N(elem), 1 ./ critU(elem)); endwhile endif endfunction ## Find alternative hypothesis parameter value P1 for p test function p1 = findP1p (p0, desiredpower, N, alpha, tail) ## Get critical values [critL, critU] = getcritP (p0, N, alpha, tail); ## Use a normal approximation to find P1 values sigma = sqrt (p0 .* (1 - p0) ./ N); p1 = findP1z (p0, sigma, desiredpower, N, alpha, tail); ## Problem if we have no critical region left if (strcmp (tail, 'both')) t = (critL == 0 & critU == N); elseif (strcmp (tail, 'right')) t = (critU == N); else t = (critL == 0); endif if (any (t)) warning ("sampsizepwr: No Valid Parameter"); p1(t) = NaN; endif ## Force in bounds t = p1 <= 0; if (any (t(:))) p1(t) = p0 / 2; endif t = p1 >= 1; if (any (t(:))) p1(t) = 1 - p0 / 2; endif ## Refine using fzero for j=1:numel (p1) if (! isnan (p1(j))); if (p1(j) > p0) F0 = @(p1arg) PowerFunction_P (p0, max (p0, min (1, p1arg)), ... alpha(j), tail, N(j), critL(j), critU(j)) - desiredpower(j); else F0 = @(p1arg) desiredpower(j) - PowerFunction_P (p0, max (0, ... min (p0, p1arg)), alpha(j), tail, N(j), critL(j), critU(j)); endif p1(j) = fzero (F0, p1(j)); endif endfor endfunction ## Find alternative hypothesis parameter value P1 for r test function p1 = findP1r (p0, desiredpower, N, alpha, tail) ## Compute only for 2-tailed test if (! strcmp (tail, 'both')) error ("sampsizepwr: only 2-tailed testing for regression coefficient."); endif ## Set initial search boundaries for p1 p1_lo = eps; p1_hi = 1 - eps; ## Compute initial sample size N0 according to P0, POWER and ALPHA N0 = rtestN (p0, 0, desiredpower, alpha, tail); ## Find P0 for N0 == N while (N != N0) if (N0 < N) p1_hi = p0; p1 = (p0 + p1_lo) / 2; p0 = p1; N0 = rtestN (p1, 0, desiredpower, alpha, tail); else p1_lo = p0; p1 = (p0 + p1_hi) / 2; p0 = p1; N0 = rtestN (p1, 0, desiredpower, alpha, tail); endif endwhile endfunction ## Get upper and lower critical values for binomial (p) test. function [critL, critU] = getcritP (p0, N, alpha, tail) ## For two-sided tests, this function tries to compute critical values ## favorable for p0<.5. It does this by allocating alpha/2 to the lower ## tail where the probabilities come in larger chunks, then using any ## left-over alpha, probably more than alpha/2, for the upper tail. ## Get part of alpha available for lower tail if (strcmp (tail, 'both')) Alo = alpha ./ 2; elseif (strcmp (tail, 'left')) Alo = alpha; else Alo = 0; endif ## Calculate critical values critU = N; critL = zeros (size (N)); if (! strcmp (tail, 'right')) critL = binoinv (Alo, N, p0); Alo = binocdf (critL, N, p0); t = (critL < N) & (Alo <= alpha / 2); critL(t) = critL(t) + 1; Alo(! t) = Alo(! t) - binopdf (critL(! t), N(! t), p0); endif if (! strcmp (tail, 'left')) Aup = max (0, alpha - Alo); critU = binoinv (1 - Aup, N, p0); endif endfunction ## Sample size calculation via binary search function N = searchbinaryN (F, lohi, p0, p1, desiredpower, alpha, tail) ## Find uper and lower bounds nlo = repmat (lohi(1),size (alpha)); nhi = repmat (lohi(2),size (alpha)); obspower = F(p0,p1,alpha,tail,nhi); ## Iterate on n until we achieve the desired power elem = 1:numel (alpha); while (! isempty (elem)) elem = elem(obspower(elem) < desiredpower(elem)); nhi(elem) = nhi(elem) * 2; obspower(elem) = F(p0, p1(elem), alpha(elem), tail, nhi(elem)); endwhile ## Binary search between these bounds for required sample size elem = find (nhi > nlo+1); while (! isempty (elem)) n = floor ((nhi(elem) + nlo(elem)) / 2); obspower = F(p0, p1(elem), alpha(elem), tail, n); toohigh = (obspower > desiredpower(elem)); nhi(elem(toohigh)) = n(toohigh); nlo(elem(! toohigh)) = n(! toohigh); elem = elem(nhi(elem) > nlo(elem) + 1); endwhile N = nhi; endfunction ## Adjust sample size to take discreteness into account function N = adjdiscreteN (N, PowerFunction, p0, p1, alpha, tail, power) for j=1:numel (N) allN = 1:N(j); obspower = PowerFunction(p0, p1(j), alpha(j), tail, allN); N(j) = allN(find (obspower >= power(j), 1, 'first')); endfor endfunction ## Normal power calculation function power = PowerFunction_N (mu0, mu1, sig, alpha, tail, n) S = sig ./ sqrt (n); if (strcmp (tail, 'both')) critL = norminv (alpha / 2, mu0, S); critU = mu0 + (mu0 - critL); power = normcdf (critL, mu1, S) + normcdf (-critU, -mu1, S); elseif (strcmp (tail, 'right')) crit = mu0 + (mu0 - norminv (alpha, mu0, S)); power = normcdf (-crit, -mu1, S); else crit = norminv (alpha, mu0, S); power = normcdf (crit, mu1, S); endif endfunction ## T power calculation function power = PowerFunction_T (mu0, mu1, sig, alpha, tail, n) S = sig ./ sqrt (n); ncp = (mu1 - mu0) ./ S; if (strcmp (tail, 'both')) critL = tinv (alpha / 2, n - 1); critU = -critL; power = nctcdf (critL, n - 1, ncp) + nctcdf (-critU, n - 1, -ncp); elseif (strcmp (tail, 'right')) crit = tinv (1 - alpha, n - 1); power = nctcdf (-crit, n - 1, -ncp); else crit = tinv (alpha, n - 1); power = nctcdf (crit, n - 1, ncp); endif endfunction ## Two-sample T power calculation function power = PowerFunction_T2 (mu0, mu1, sig, alpha, tail, n, ratio) ncp = (mu1 - mu0) ./ (sig .* sqrt (1 ./ n + 1 ./ (ratio .* n))); if (strcmp (tail, 'both')) critL = tinv (alpha / 2, n + ratio .* n - 2); critU = -critL; power = nctcdf (critL, n + ratio .* n - 2, ncp) + ... nctcdf (-critU, n + ratio .* n - 2, -ncp); elseif (strcmp (tail, 'right')) crit = tinv (1 - alpha, n + ratio .* n - 2); power = nctcdf (-crit, n + ratio .* n - 2, -ncp); else crit = tinv (alpha, n + ratio .* n - 2); power = nctcdf (crit, n + ratio .* n - 2, ncp); endif endfunction ## Chi-square power calculation #MK: THESE CONFUSED POWER WITH 1-POWER! function power = PowerFunction_V (v0, v1, alpha, tail, n) if (strcmp (tail, 'both')) critU = v0 .* chi2inv (1 - alpha / 2, n - 1); critL = v0 .* chi2inv (alpha / 2, n - 1); power = chi2cdf (critL ./ v1, n - 1) + 1-chi2cdf (critU ./ v1, n - 1); elseif (strcmp (tail, 'right')) crit = v0 .* chi2inv (1 - alpha, n - 1); power = 1-chi2cdf (crit ./ v1, n - 1); else crit = v0 .* chi2inv (alpha, n - 1); power = chi2cdf (crit ./ v1, n - 1); endif endfunction ## Binomial power calculation function [power, critL, critU] = PowerFunction_P (p0, p1, alpha, ... tail, n, critL, critU) if (nargin < 6) [critL, critU] = getcritP (p0, n, alpha, tail); endif if (strcmp (tail, 'both')) power = binocdf (critL - 1, n, p1) + 1 - binocdf (critU, n, p1); elseif (strcmp (tail, 'right')) power = 1 - binocdf (critU , n, p1); else power = binocdf (critL - 1, n, p1); endif endfunction ## Regression power calculation function power = PowerFunction_R (r0, r1, alpha, tail, n) ## Compute only for 2-tailed test if (! strcmp (tail, 'both')) error ("sampsizepwr: only 2-tailed testing for regression coefficient."); endif ## Under Fisher's z transformation the statistic is asymptotically normal ## with unit variance and mean (atanh (r1) - atanh (r0)) * sqrt (n - 3), so ## the power follows in closed form. This was a bisection on the sample ## size that searched for exact equality between two doubles, using a ## variable 'N' that does not exist here -- the parameter is 'n' -- and a ## misspelt 'pd_hi' for 'dp_hi' in the branch it never reached. delta = (atanh (r1) - atanh (r0)) .* sqrt (n - 3); zcrit = - norminv (alpha ./ 2); power = normcdf (- zcrit + delta) + normcdf (- zcrit - delta); endfunction ## Local zero function for "t2" test function N = localfzero (F, N0, ratio) ## Set minN according to ratio if (ratio >= 2) minN = 1; else minN = 2; endif ## Return minN if function gives a value above zero if (F(minN) > 0) N = minN; return; endif ## Make sure that fzero does not try values below minN if (N0 == minN) N0 = N0 + 1; endif ## Find solution if (F(N0) > 0) N = fzero (F, [minN, N0], optimset ('TolX', 1e-6)); # N0 is an upper bound else N = fzero (F, N0, optimset ('TolX', 1e-6)); # N0 is a starting value endif endfunction ## Demos %!demo %! ## Compute the mean closest to 100 that can be determined to be %! ## significantly different from 100 using a t-test with a sample size %! ## of 60 and a power of 0.8. %! mu1 = sampsizepwr ('t', [100, 10], [], 0.8, 60); %! disp (mu1); %!demo %! ## Compute the sample sizes required to distinguish mu0 = 100 from %! ## mu1 = 110 by a two-sample t-test with a ratio of the larger and the %! ## smaller sample sizes of 1.5 and a power of 0.6. %! [N1,N2] = sampsizepwr ('t2', [100, 10], 110, 0.6, [], 'ratio', 1.5) %!demo %! ## Compute the sample size N required to distinguish p=.26 from p=.2 %! ## with a binomial test. The result is approximate, so make a plot to %! ## see if any smaller N values also have the required power of 0.6. %! Napprox = sampsizepwr ('p', 0.2, 0.26, 0.6); %! nn = 1:250; %! pwr = sampsizepwr ('p', 0.2, 0.26, [], nn); %! Nexact = min (nn(pwr >= 0.6)); %! plot (nn,pwr,'b-', [Napprox Nexact],pwr([Napprox Nexact]),'ro'); %! grid on %!demo %! ## The company must test 52 bottles to detect the difference between a mean %! ## volume of 100 mL and 102 mL with a power of 0.80. Generate a power curve %! ## to visualize how the sample size affects the power of the test. %! %! nout = sampsizepwr ('t',[100 5],102,0.80); %! nn = 1:100; %! pwrout = sampsizepwr ('t',[100 5],102,[],nn); %! %! figure; %! plot (nn, pwrout, 'b-', nout, 0.8, 'ro') %! title ('Power versus Sample Size') %! xlabel ('Sample Size') %! ylabel ('Power') ## Input validation %!error ... %! out = sampsizepwr ([], [100, 10], [], 0.8, 60); %!error ... %! out = sampsizepwr (3, [100, 10], [], 0.8, 60); %!error ... %! out = sampsizepwr ({'t', 't2'}, [100, 10], [], 0.8, 60); %!error ... %! out = sampsizepwr ('reg', [100, 10], [], 0.8, 60); %!error ... %! out = sampsizepwr ('t', ['a', 'e'], [], 0.8, 60); %!error ... %! out = sampsizepwr ('z', 100, [], 0.8, 60); %!error ... %! out = sampsizepwr ('t', 100, [], 0.8, 60); %!error ... %! out = sampsizepwr ('t2', 60, [], 0.8, 60); %!error ... %! out = sampsizepwr ('var', [100, 10], [], 0.8, 60); %!error ... %! out = sampsizepwr ('p', [100, 10], [], 0.8, 60); %!error ... %! out = sampsizepwr ('r', [100, 10], [], 0.8, 60); %!error ... %! [out, N1] = sampsizepwr ('z', [100, 10], [], 0.8, 60); %!error ... %! [out, N1] = sampsizepwr ('t', [100, 10], [], 0.8, 60); %!error ... %! [out, N1] = sampsizepwr ('var', 2, [], 0.8, 60); %!error ... %! [out, N1] = sampsizepwr ('p', 0.1, [], 0.8, 60); %!error ... %! [out, N1] = sampsizepwr ('r', 0.5, [], 0.8, 60); %!error ... %! out = sampsizepwr ('z', [100, 0], [], 0.8, 60); %!error ... %! out = sampsizepwr ('z', [100, -5], [], 0.8, 60); %!error ... %! out = sampsizepwr ('t', [100, 0], [], 0.8, 60); %!error ... %! out = sampsizepwr ('t', [100, -5], [], 0.8, 60); %!error ... %! [out, N1] = sampsizepwr ('t2', [100, 0], [], 0.8, 60); %!error ... %! [out, N1] = sampsizepwr ('t2', [100, -5], [], 0.8, 60); %!error ... %! out = sampsizepwr ('var', 0, [], 0.8, 60); %!error ... %! out = sampsizepwr ('var', -5, [], 0.8, 60); %!error ... %! out = sampsizepwr ('p', 0, [], 0.8, 60); %!error ... %! out = sampsizepwr ('p', 1.2, [], 0.8, 60); %!error ... %! out = sampsizepwr ('r', -1.5, [], 0.8, 60); %!error ... %! out = sampsizepwr ('r', -1, [], 0.8, 60); %!error ... %! out = sampsizepwr ('r', 1.2, [], 0.8, 60); %!error ... %! out = sampsizepwr ('r', 0.2, [], 0.8, 60, 'alpha', -0.2); %!error ... %! out = sampsizepwr ('r', 0.2, [], 0.8, 60, 'alpha', 0); %!error ... %! out = sampsizepwr ('r', 0.2, [], 0.8, 60, 'alpha', 1.5); %!error ... %! out = sampsizepwr ('r', 0.2, [], 0.8, 60, 'alpha', 'zero'); %!error ... %! out = sampsizepwr ('r', 0.2, [], 0.8, 60, 'tail', 1.5); %!error ... %! out = sampsizepwr ('r', 0.2, [], 0.8, 60, 'tail', {'both', 'left'}); %!error ... %! out = sampsizepwr ('r', 0.2, [], 0.8, 60, 'tail', 'other'); %!error ... %! out = sampsizepwr ('r', 0.2, [], 0.8, 60, 'ratio', 'some'); %!error ... %! out = sampsizepwr ('r', 0.2, [], 0.8, 60, 'ratio', 0.5); %!error ... %! out = sampsizepwr ('r', 0.2, [], 0.8, 60, 'ratio', [2, 1.3, 0.3]); %!error ... %! out = sampsizepwr ('z', [100, 5], [], [], 60); %!error ... %! out = sampsizepwr ('z', [100, 5], 110, [], []); %!error ... %! out = sampsizepwr ('z', [100, 5], [], 0.8, []); %!error ... %! out = sampsizepwr ('z', [100, 5], 110, 0.8, 60); %!error ... %! out = sampsizepwr ('z', [100, 5], 'mu', [], 60); %!error ... %! out = sampsizepwr ('var', 5, -1, [], 60); %!error ... %! out = sampsizepwr ('p', 0.8, 1.2, [], 60, 'tail', 'right'); %!error ... %! out = sampsizepwr ('r', 0.8, 1.2, [], 60); %!error ... %! out = sampsizepwr ('r', 0.8, -1.2, [], 60); %!error ... %! out = sampsizepwr ('z', [100, 5], 110, 1.2); %!error ... %! out = sampsizepwr ('z', [100, 5], 110, 0); %!error ... %! out = sampsizepwr ('z', [100, 5], 110, 0.05, [], 'alpha', 0.1); %!error ... %! out = sampsizepwr ('z', [100, 5], [], [0.8, 0.7], [60, 80, 100]); %!error ... %! out = sampsizepwr ('t', [100, 5], 100, 0.8, []); %!error ... %! out = sampsizepwr ('t', [100, 5], 110, 0.8, [], 'tail', 'left'); %!error ... %! out = sampsizepwr ('t', [100, 5], 90, 0.8, [], 'tail', 'right'); ## Warning test %!warning ... %! Napprox = sampsizepwr ('p', 0.2, 0.26, 0.6); %!warning ... %! Napprox = sampsizepwr ('p', 0.30, 0.36, 0.8); ## Results validation ## The 'r' test type was unreachable: the sample size branch called a ## misspelt 'r1testN', the power branch read an 'N' that does not exist there, ## and a null correlation of zero, the standard null, was refused outright. %!test %! ## sample size, against the Fisher z formula worked out here %! n = sampsizepwr ('r', 0.1, 0.5); %! C = abs (atanh (0.5) - atanh (0.1)); %! assert_equal (n, ceil (((norminv (0.025) + norminv (0.10)) / C) ^ 2 + 3)); %!test %! ## power at a given sample size, likewise %! pwr = sampsizepwr ('r', 0.1, 0.5, [], 60); %! d = abs (atanh (0.5) - atanh (0.1)) * sqrt (60 - 3); %! zc = - norminv (0.025); %! assert_equal (pwr, normcdf (-zc + d) + normcdf (-zc - d), 1e-12); %!test %! ## a null correlation of zero is the usual null and must be accepted %! assert_equal (sampsizepwr ('r', 0, 0.4, 0.90), 62); %! assert_equal (sampsizepwr ('r', 0, 0.3, 0.80), 85); %!test %! ## asking for a power must yield a sample size that delivers it %! for pw = [0.7, 0.8, 0.9] %! n = sampsizepwr ('r', 0, 0.4, pw); %! assert_equal (sampsizepwr ('r', 0, 0.4, [], n) >= pw, true); %! endfor %!test %! ## power must rise with the sample size and with the effect size %! p20 = sampsizepwr ('r', 0, 0.4, [], 20); %! p40 = sampsizepwr ('r', 0, 0.4, [], 40); %! p80 = sampsizepwr ('r', 0, 0.4, [], 80); %! assert_equal (p20 < p40 && p40 < p80, true); %! assert_equal (sampsizepwr ('r', 0, 0.2, [], 40) < p40, true); %! assert_equal (sampsizepwr ('r', 0, 0.6, [], 40) > p40, true); %!test %! ## a correlation and its negative need the same sample size %! assert_equal (sampsizepwr ('r', 0, -0.4, 0.90), ... %! sampsizepwr ('r', 0, 0.4, 0.90)); %!test %! ## the alternative correlation can be recovered from N and the power, %! ## to the precision of the search findP1r runs %! r1 = sampsizepwr ('r', 0, [], 0.80, 60); %! assert_equal (sampsizepwr ('r', 0, r1, [], 60), 0.80, 5e-3); %!test %! mu1 = sampsizepwr ('t', [100, 10], [], 0.8, 60); %! assert_equal (mu1, 103.67704316, 1e-8); %!test %! [N1,N2] = sampsizepwr ('t2', [100, 10], 110, 0.6, [], 'ratio', 1.5); %! assert_equal (N1, 9); %! assert_equal (N2, 14); %!test %! nn = 1:250; %! pwr = sampsizepwr ('p', 0.2, 0.26, [], nn); %! pwr_out = [0, 0.0676, 0.0176, 0.0566, 0.0181, 0.0431, 0.0802, 0.0322]; %! assert_equal (pwr([1:8]), pwr_out, 1e-4 * ones (1,8)); %! pwr_out = [0.59275, 0.6073, 0.62166, 0.6358, 0.6497, 0.6087, 0.6229, 0.6369]; %! assert_equal (pwr([243:end]), pwr_out, 1e-4 * ones (1,8)); %!test %! nout = sampsizepwr ('t', [100, 5], 102, 0.80); %! assert_equal (nout, 52); %!test %! power = sampsizepwr ('t', [20, 5], 25, [], 5, 'Tail', 'right'); %! assert_equal (power, 0.5797373588621888, 1e-14); %!test %! nout = sampsizepwr ('t', [20, 5], 25, 0.99, [], 'Tail', 'right'); %! assert_equal (nout, 18); %!test %! p1out = sampsizepwr ('t', [20, 5], [], 0.95, 10, 'Tail', 'right'); %! assert_equal (p1out, 25.65317979360237, 5e-14); %!test %! pwr = sampsizepwr ('t2', [1.4, 0.2], 1.7, [], 5, 'Ratio', 2); %! assert_equal (pwr, 0.716504004686586, 1e-14); %!test %! n = sampsizepwr ('t2', [1.4, 0.2], 1.7, 0.9, []); %! assert_equal (n, 11); %!test %! [n1, n2] = sampsizepwr ('t2', [1.4, 0.2], 1.7, 0.9, [], 'Ratio', 2); %! assert_equal ([n1, n2], [8, 16]); statistics-release-1.9.2/inst/Hypothesis_Testing/signrank.m000066400000000000000000000373701524624707500241640ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{pval} =} signrank (@var{x}) ## @deftypefnx {statistics} {@var{pval} =} signrank (@var{x}, @var{my}) ## @deftypefnx {statistics} {@var{pval} =} signrank (@var{x}, @var{my}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{pval}, @var{h}] =} signrank (@dots{}) ## @deftypefnx {statistics} {[@var{pval}, @var{h}, @var{stats}] =} signrank (@dots{}) ## ## Wilcoxon signed rank test for median. ## ## @code{@var{pval} = signrank (@var{x})} returns the @math{p}-value of a ## two-sided Wilcoxon signed rank test. It tests the null hypothesis that data ## in @var{x} come from a distribution with zero median at the 5% significance ## level under the assumption that the distribution is symmetric about its ## median. @var{x} must be a vector. ## ## If the second argument @var{my} is a scalar, the null hypothesis is that ## @var{x} has median @var{my}, whereas if @var{my} is a vector, the null ## hypothesis is that the distribution of @code{@var{x} - @var{my}} has zero ## median. ## ## @code{@var{pval} = signrank (@dots{}, @var{Name}, @var{Value})} performs the ## Wilcoxon signed rank test with additional options specified by one or more of ## the following @var{Name}, @var{Value} pair arguments: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'alpha'} @tab A scalar value for the significance level of ## the test. Default is 0.05. ## ## @item @qcode{'tail'} @tab A character vector specifying the alternative ## hypothesis. It can take one of the following values: ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## ## @item @qcode{'both'} @tab For one-sample test (@var{my} is empty or a ## scalar), the data in @var{x} come from a continuous distribution with median ## different than zero or @var{my}. For two-sample test (@var{my} is a vector), ## the data in @qcode{@var{x} - @var{my}} come from a continuous distribution ## with median different than zero. ## ## @item @qcode{'left'} @tab For one-sample test (@var{my} is empty or a ## scalar), the data in @var{x} come from a continuous distribution with median ## less than zero or @var{my}. For two-sample test (@var{my} is a vector), the ## data in @qcode{@var{x} - @var{my}} come from a continuous distribution with ## median less than zero. ## ## @item @qcode{'right'} @tab For one-sample test (@var{my} is empty or a ## scalar), the data in @var{x} come from a continuous distribution with median ## greater than zero or @var{my}. For two-sample test (@var{my} is a vector), ## the data in @qcode{@var{x} - @var{my}} come from a continuous distribution ## with median greater than zero. ## @end multitable ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'method'} @tab A character vector specifying the method for ## computing the @math{p}-value. It can take one of the following values: ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## ## @item @qcode{'exact'} @tab Exact computation of the @math{p}-value. It ## is the default value for 15 of fewer observations when @qcode{'method'} is ## not specified. ## ## @item @qcode{'approximate'} @tab Using normal approximation for ## computing the @math{p}-value. It is the default value for more than 15 ## observations when @qcode{'method'} is not specified. ## @end multitable ## ## @code{[@var{pval}, @var{h}] = signrank (@dots{})} also returns a logical ## value indicating the test decision. If @var{h} is 0, the null hypothesis is ## accepted, whereas if @var{h} is 1, the null hypothesis is rejected. ## ## @code{[@var{pval}, @var{h}, @var{stats}] = signrank (@dots{})} also returns ## the structure @var{stats} containing the following fields: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Field} @tab @var{Value} ## @item @qcode{signedrank} @tab Value of the sign rank test statistic. ## ## @item @qcode{zval} @tab Value of the @math{z}-statistic (only computed ## when the @qcode{'method'} is @qcode{'approximate'}). ## @end multitable ## ## @seealso{tiedrank, signtest, runstest} ## @end deftypefn function [p, h, stats] = signrank (x, my, varargin) ## Check X being a vector if (! isvector (x)) error ("signrank: X must be a vector."); endif ## Add defaults alpha = 0.05; tail = 'both'; if (numel (x) <= 15) method = 'exact'; else method = 'approximate'; endif method_present = false; ## When called with a single input argument of second argument is empty if (nargin == 1 || isempty (my)) my = zeros (size (x)); endif ## If second argument is a scalar convert to vector or check for Y being a ## vector and that X and Y have equal lengths if (isscalar (my)) my = repmat (my, size (x)); elseif (! isvector (my)) error ("signrank: Y must be either a scalar of a vector."); elseif (numel (x) != numel (my)) error ("signrank: X and Y vectors have different lengths."); endif ## Get optional input arguments if (mod (numel (varargin), 2) != 0) error ("signrank: optional arguments must be in pairs."); endif while (numel (varargin) > 0) switch (lower (varargin{1})) case 'alpha' alpha = varargin{2}; case 'tail' tail = varargin{2}; case 'method' method = varargin{2}; method_present = true; otherwise error ("signrank: invalid Name argument."); endswitch varargin([1:2]) = []; endwhile ## Check values for optional input arguments if (! isnumeric (alpha) || isnan (alpha) || ! isscalar (alpha) ... || alpha <= 0 || alpha >= 1) error ("signrank: 'alpha' must be a numeric scalar in the range 0 to 1."); endif if (! ischar (tail)) error ("signrank: 'tail' argument must be a character vector."); elseif (sum (strcmpi (tail, {'both', 'right', 'left'})) != 1) error ("signrank: 'tail' value must be either 'both', right' or 'left'."); endif if (! ischar (method)) error ("signrank: 'method' argument must be a character vector."); elseif (sum (strcmpi (method, {'exact', 'approximate'})) != 1) error ("signrank: 'method' value must be either 'exact' or 'approximate'."); endif ## Calculate differences between X and Y vectors: remove equal values of NaNs. ## A difference smaller than the combined resolution of the two values it came ## from is not a real difference, so it counts as equal, and the same ## tolerance decides which differences rank as tied. MATLAB defines it as ## EPS (X) + EPS (Y) per pair. XY_diff = x(:) - my(:); if (isfloat (x) && isfloat (my)) epsdiff = eps (x(:)) + eps (my(:)); else epsdiff = zeros (size (XY_diff)); endif drop = abs (XY_diff) < epsdiff | XY_diff == 0 | isnan (XY_diff); XY_diff(drop) = []; epsdiff(drop) = []; ## Recalculate remaining length of X vector (after equal or NaNs removal) n = length (XY_diff); ## Check for identical X and Y input arguments if (n == 0) p = 1; h = 0; stats.signedrank = 0; stats.zval = []; return; endif ## Re-evaluate method selection if (! method_present) if (n <= 15) method = 'exact'; else method = 'approximate'; endif endif ## Compute signed rank statistic [tie_rank, tieadj] = tiedrank (abs (XY_diff), 0, 0, epsdiff); w = sum (tie_rank(XY_diff > 0)); stats.signedrank = w; ## Calculate stats according to selected method and tail switch (lower (method)) case 'exact' w_max = n * (n + 1) / 2; ## Always compute lower tail switch_tail = false; if (w > w_max / 2) w = w_max - w; switch_tail = true; endif ## Avoid integers in tied ranks double_ties = any (tie_rank != fix (tie_rank)); if (double_ties) tie_rank = round (2 * tie_rank); w = round (2 * w); endif ## Loop through all combinations of ranks C = zeros (w + 1,1); C(1) = 1; curr = 1; tie_rank = sort (tie_rank); w_tr = tie_rank(tie_rank <= w); for tr = 1:numel (w_tr) next = min (curr + w_tr(tr), w + 1); C_hi = min (w_tr(tr), w + 1) + 1:next; C_lo = 1:length (C_hi); C(C_hi) = C(C_hi) + C(C_lo); curr = next; endfor ## Fix rank statistic if (double_ties) w = w / 2; endif ## Compute tail probability C = C / (2 ^ n); p = sum (C); switch (lower (tail)) case 'both' p = min (1, 2 * p); # two-sided case 'right' if (! switch_tail) # right tail is larger p = 1 - p + C(end); endif case 'left' if (switch_tail) # left tail is larger p = 1 - p + C(end); endif endswitch ## No Z-statistic exists for the exact test, so the field is created ## empty rather than holding a value. R2024a omits it altogether, but ## R2026a and the documentation both give an empty one. stats.zval = []; case 'approximate' ## Compute z-value z_nom = w - n * (n + 1) / 4; z_den = sqrt ((n * (n + 1) * (2 * n + 1) - tieadj) / 24); switch (lower (tail)) case 'both' z = z_nom / z_den; p = 2 * normcdf (-abs (z)); case 'right' z = (z_nom - 0.5) / z_den; p = normcdf (-z); case 'left' z = (z_nom + 0.5) / z_den; p = normcdf (z); endswitch stats.zval = z; endswitch h = p <= alpha; endfunction ## Test output ## Field layouts below are R2024a's, measured 2026-08-17. %!test %! ## the exact test has no z-statistic, so no ZVAL field is created at all %! x = [1.83 0.50 1.62 2.48 1.68 1.88 1.55 3.06 1.30]; %! y = [0.878 0.647 0.598 2.05 1.06 1.29 1.06 3.14 1.29]; %! [p, h, stats] = signrank (x, y, 'method', 'exact'); %! assert_equal (fieldnames (stats), {'signedrank'; 'zval'}); %! assert_equal (stats.signedrank, 40); %! assert_equal (isempty (stats.zval), true); %! assert_equal (p, 0.039062500000000, 1e-14); %!test %! ## the default method for a small sample is the exact one %! x = [1.83 0.50 1.62 2.48 1.68 1.88 1.55 3.06 1.30]; %! [~, ~, stats] = signrank (x, 1); %! assert_equal (fieldnames (stats), {'signedrank'; 'zval'}); %! assert_equal (stats.signedrank, 43); %! assert_equal (isempty (stats.zval), true); %!test %! ## identical inputs give a zero statistic and no z-value %! [p, h, stats] = signrank ([1 2 3], [1 2 3]); %! assert_equal (p, 1); %! assert_equal (fieldnames (stats), {'signedrank'; 'zval'}); %! assert_equal (stats.signedrank, 0); %! assert_equal (isempty (stats.zval), true); %!test %! load gradespaired.mat %! [p, h, stats] = signrank (gradespaired(:,1), ... %! gradespaired(:,2), 'tail', 'left'); %! assert_equal (p, 0.0047, 1e-4); %! assert_equal (h, true); %! assert_equal (stats.zval, -2.5982, 1e-4); %! assert_equal (stats.signedrank, 2017.5); %!test %! load ('gradespaired.mat'); %! [p, h, stats] = signrank (gradespaired(:,1), gradespaired(:,2), ... %! 'tail', 'left', 'method', 'exact'); %! assert_equal (p, 0.0045, 1e-4); %! assert_equal (h, true); %! assert_equal (isempty (stats.zval), true); %! assert_equal (stats.signedrank, 2017.5); %!test %! load mileage %! [p, h, stats] = signrank (mileage(:,2), 33); %! assert_equal (p, 0.0312, 1e-4); %! assert_equal (h, true); %! assert_equal (isempty (stats.zval), true); %! assert_equal (stats.signedrank, 21); %!test %! load mileage %! [p, h, stats] = signrank (mileage(:,2), 33, 'tail', 'right'); %! assert_equal (p, 0.0156, 1e-4); %! assert_equal (h, true); %! assert_equal (isempty (stats.zval), true); %! assert_equal (stats.signedrank, 21); %!test %! load mileage %! [p, h, stats] = signrank (mileage(:,2), 33, 'tail', 'right', ... %! 'alpha', 0.01, 'method', 'approximate'); %! assert_equal (p, 0.0180, 1e-4); %! assert_equal (h, false); %! assert_equal (stats.zval, 2.0966, 1e-4); %! assert_equal (stats.signedrank, 21); %!test %! x = [1, 2, 3, NaN, 4, 5]; %! p_clean = signrank ([1, 2, 3, 4, 5]); %! p_nan = signrank (x); %! assert_equal (p_nan, p_clean); %!test %! ## Differences equal to within the precision of the values they came from %! ## rank as tied, as MATLAB ranks them. Two of these sit 2 ulps apart. %! big = [2.1 3.4 1.2 5.6 4.3 2.2 6.7 3.3 4.4 5.5 1.1 2.9 3.8 4.9 5.1 6.2 ... %! 7.3 2.4]; %! [p, h, stats] = signrank (big, 3, 'method', 'approximate'); %! assert_equal (stats.signedrank, 132); %! assert_equal (stats.zval, 2.025571814690999, 1e-12); %!test %! ## A difference below that precision counts as no difference at all, and is %! ## dropped exactly as an exact zero is. Both forms measured against R2024a. %! x = [5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20]; %! y = x + 1; %! y(16) = x(16) + eps (x(16)); %! [p, h, stats] = signrank (x, y, 'method', 'approximate'); %! assert_equal (stats.signedrank, 0); %! assert_equal (stats.zval, -3.872983346207417, 1e-12); %! y(16) = x(16); %! [p2, h2, stats2] = signrank (x, y, 'method', 'approximate'); %! assert_equal (p2, p, 1e-15); %! assert_equal (stats2.zval, stats.zval, 1e-15); ## Test input validation %!error signrank (ones (2)) %!error ... %! signrank ([1, 2, 3, 4], ones (2)) %!error ... %! signrank ([1, 2, 3, 4], [1, 2, 3]) %!error ... %! signrank ([1, 2, 3, 4], [], 'tail') %!error ... %! signrank ([1, 2, 3, 4], [], 'alpha', 1.2) %!error ... %! signrank ([1, 2, 3, 4], [], 'alpha', 0) %!error ... %! signrank ([1, 2, 3, 4], [], 'alpha', -0.05) %!error ... %! signrank ([1, 2, 3, 4], [], 'alpha', 'a') %!error ... %! signrank ([1, 2, 3, 4], [], 'alpha', [0.01, 0.05]) %!error ... %! signrank ([1, 2, 3, 4], [], 'tail', 0.01) %!error ... %! signrank ([1, 2, 3, 4], [], 'tail', {'both'}) %!error ... %! signrank ([1, 2, 3, 4], [], 'tail', 'some') %!error ... %! signrank ([1, 2, 3, 4], [], 'method', 'exact', 'tail', 'some') %!error ... %! signrank ([1, 2, 3, 4], [], 'method', 0.01) %!error ... %! signrank ([1, 2, 3, 4], [], 'method', {'exact'}) %!error ... %! signrank ([1, 2, 3, 4], [], 'method', 'some') %!error ... %! signrank ([1, 2, 3, 4], [], 'tail', 'both', 'method', 'some') statistics-release-1.9.2/inst/Hypothesis_Testing/signtest.m000066400000000000000000000277231524624707500242110ustar00rootroot00000000000000## Copyright (C) 2014 Tony Richardson ## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{pval} =} signtest (@var{x}) ## @deftypefnx {statistics} {@var{pval} =} signtest (@var{x}, @var{my}) ## @deftypefnx {statistics} {@var{pval} =} signtest (@var{x}, @var{my}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{pval}, @var{h}] =} signtest (@dots{}) ## @deftypefnx {statistics} {[@var{pval}, @var{h}, @var{stats}] =} signtest (@dots{}) ## ## Signed test for median. ## ## @code{@var{pval} = signtest (@var{x})} returns the @math{p}-value of a ## two-sided sign test. It tests the null hypothesis that data in @var{x} come ## from a distribution with zero median at the 5% significance level. @var{x} ## must be a vector. ## ## If the second argument @var{my} is a scalar, the null hypothesis is that ## @var{x} has median @var{my}, whereas if @var{my} is a vector, the null ## hypothesis is that the distribution of @code{@var{x} - @var{my}} has zero ## median. ## ## @code{@var{pval} = signtest (@dots{}, @var{Name}, @var{Value})} performs the ## Wilcoxon signed rank test with additional options specified by one or more of ## the following @var{Name}, @var{Value} pair arguments: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'alpha'} @tab A scalar value for the significance level of ## the test. Default is 0.05. ## ## @item @qcode{'tail'} @tab A character vector specifying the alternative ## hypothesis. It can take one of the following values: ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## ## @item @qcode{'both'} @tab For one-sample test (@var{my} is empty or a ## scalar), the data in @var{x} come from a continuous distribution with median ## different than zero or @var{my}. For two-sample test (@var{my} is a vector), ## the data in @qcode{@var{x} - @var{my}} come from a continuous distribution ## with median different than zero. ## ## @item @qcode{'left'} @tab For one-sample test (@var{my} is empty or a ## scalar), the data in @var{x} come from a continuous distribution with median ## less than zero or @var{my}. For two-sample test (@var{my} is a vector), the ## data in @qcode{@var{x} - @var{my}} come from a continuous distribution with ## median less than zero. ## ## @item @qcode{'right'} @tab For one-sample test (@var{my} is empty or a ## scalar), the data in @var{x} come from a continuous distribution with median ## greater than zero or @var{my}. For two-sample test (@var{my} is a vector), ## the data in @qcode{@var{x} - @var{my}} come from a continuous distribution ## with median greater than zero. ## @end multitable ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'method'} @tab A character vector specifying the method for ## computing the @math{p}-value. It can take one of the following values: ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## ## @item @qcode{'exact'} @tab Exact computation of the @math{p}-value. It ## is the default value for fewer than 100 observations when @qcode{'method'} is ## not specified. ## ## @item @qcode{'approximate'} @tab Using normal approximation for ## computing the @math{p}-value. It is the default value for 100 or more ## observations when @qcode{'method'} is not specified. ## @end multitable ## ## @code{[@var{pval}, @var{h}] = signtest (@dots{})} also returns a logical ## value indicating the test decision. If @var{h} is 0, the null hypothesis is ## accepted, whereas if @var{h} is 1, the null hypothesis is rejected. ## ## @code{[@var{pval}, @var{h}, @var{stats}] = signtest (@dots{})} also returns ## the structure @var{stats} containing the following fields: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Field} @tab @var{Value} ## @item @qcode{sign} @tab Value of the sign test statistic. ## ## @item @qcode{zval} @tab Value of the @math{z}-statistic (only computed ## when the @qcode{'method'} is @qcode{'approximate'}). ## @end multitable ## ## @seealso{signrank, tiedrank, runstest} ## @end deftypefn function [p, h, stats] = signtest (x, my, varargin) ## Check X being a vector if (! isvector (x)) error ("signtest: X must be a vector."); endif ## Add defaults alpha = 0.05; tail = 'both'; if (numel (x) < 100) method = 'exact'; else method = 'approximate'; endif method_present = false; ## When called with a single input argument of second argument is empty if (nargin == 1 || isempty (my)) my = zeros (size (x)); endif ## If second argument is a scalar convert to vector or check for Y being a ## vector and that X and Y have equal lengths if (isscalar (my)) my = repmat (my, size (x)); elseif (! isvector (my)) error ("signtest: Y must be either a scalar of a vector."); elseif (numel (x) != numel (my)) error ("signtest: X and Y vectors have different lengths."); endif ## Get optional input arguments if (mod (numel (varargin), 2) != 0) error ("signtest: optional arguments must be in pairs."); endif while (numel (varargin) > 0) switch (lower (varargin{1})) case 'alpha' alpha = varargin{2}; case 'tail' tail = varargin{2}; case 'method' method = varargin{2}; method_present = true; otherwise error ("signtest: invalid Name argument."); endswitch varargin([1:2]) = []; endwhile ## Check values for optional input arguments if (! isnumeric (alpha) || isnan (alpha) || ! isscalar (alpha) ... || alpha <= 0 || alpha >= 1) error ("signtest: 'alpha' must be a numeric scalar in the range 0 to 1."); endif if (! ischar (tail)) error ("signtest: 'tail' argument must be a character vector."); elseif (sum (strcmpi (tail, {'both', 'right', 'left'})) != 1) error ("signtest: 'tail' value must be either 'both', right' or 'left'."); endif if (! ischar (method)) error ("signtest: 'method' argument must be a character vector."); elseif (sum (strcmpi (method, {'exact', 'approximate'})) != 1) error ("signtest: 'method' value must be either 'exact' or 'approximate'."); endif ## Calculate differences between X and Y vectors: remove equal values of NaNs XY_diff = x(:) - my(:); XY_diff(XY_diff == 0 | isnan (XY_diff)) = []; ## Recalculate remaining length of X vector (after equal or NaNs removal) n = length (XY_diff); ## Check for identical X and Y input arguments if (n == 0) p = 1; h = 0; stats.sign = 0; stats.zval = NaN; return; endif ## Re-evaluate method selection if (! method_present) if (n < 100) method = 'exact'; else method = 'approximate'; endif endif ## Get the number of positive and negative elements from X-Y differences pos_n = sum (XY_diff > 0); neg_n = n - pos_n; ## Set before the Z-statistic so the fields come in MATLAB's order stats.sign = pos_n; ## Calculate stats according to selected method and tail switch (lower (method)) case 'exact' switch (lower (tail)) case 'both' p = 2 * binocdf (min (neg_n, pos_n), n, 0.5); p = min (1, p); case 'left' p = binocdf (pos_n, n, 0.5); case 'right' p = binocdf (neg_n, n, 0.5); endswitch stats.zval = NaN; case 'approximate' switch (lower (tail)) case 'both' z_value = (pos_n - neg_n - sign (pos_n - neg_n)) / sqrt (n); p = 2 * normcdf (- abs (z_value)); case 'left' z_value = (pos_n - neg_n + 1) / sqrt (n); p = normcdf (z_value); case 'right' z_value = (pos_n - neg_n - 1) / sqrt (n); p = normcdf (- z_value); endswitch stats.zval = z_value; endswitch h = p <= alpha; endfunction ## Test output %!test %! [pval, h, stats] = signtest ([-ones(1, 1000) 1], 0, 'tail', 'left'); %! assert_equal (pval, 1.091701889420221e-218, 1e-14); %! assert_equal (h, true); %! assert_equal (stats.zval, -31.5437631079266, 1e-14); %!test %! [pval, h, stats] = signtest ([-2 -1 0 2 1 3 1], 0); %! assert_equal (pval, 0.6875000000000006, 1e-14); %! assert_equal (h, false); %! assert_equal (stats.zval, NaN); %! assert_equal (stats.sign, 4); %!test %! [pval, h, stats] = signtest ([-2 -1 0 2 1 3 1], 0, 'method', 'approximate'); %! assert_equal (pval, 0.6830913983096086, 1e-14); %! assert_equal (h, false); %! assert_equal (stats.zval, 0.4082482904638631, 1e-14); %! assert_equal (stats.sign, 4); %!test %! x = [1, 2, 3, 4, NaN, NaN, NaN]; %! [pval, h] = signtest (x); %! assert_equal (pval, 0.1250, 1e-4); %! assert_equal (h, false); %!test %! x = [1, 2, 3, 4, 5]; %! y = [1, 1, NaN, 5, 4]; %! [pval, h] = signtest (x, y); %! assert_equal (pval, 1.0, 1e-4); %! assert_equal (h, false); %!test %! x = [1, 2, 3, 4, 5, -1]; %! [p_val, ~] = signtest (x); %! [p, h, stats] = signtest (x, 0, 'alpha', p_val); %! assert_equal (h, true); %!test %! ## the decision is logical, as it is in MATLAB and in signrank and ranksum %! [~, h] = signtest ([1.2 2.3 0.5 3.1 2.2 1.8 0.9 2.7 1.1 3.3], 2); %! assert_equal (class (h), 'logical'); %! assert_equal (h, false); %!test %! [~, h] = signtest ([1.2 2.3 0.5 3.1 2.2 1.8 0.9 2.7 1.1 3.3], 100); %! assert_equal (class (h), 'logical'); %! assert_equal (h, true); ## Test input validation %!error signtest (ones (2)) %!error ... %! signtest ([1, 2, 3, 4], ones (2)) %!error ... %! signtest ([1, 2, 3, 4], [1, 2, 3]) %!error ... %! signtest ([1, 2, 3, 4], [], 'tail') %!error ... %! signtest ([1, 2, 3, 4], [], 'alpha', 1.2) %!error ... %! signtest ([1, 2, 3, 4], [], 'alpha', 0) %!error ... %! signtest ([1, 2, 3, 4], [], 'alpha', -0.05) %!error ... %! signtest ([1, 2, 3, 4], [], 'alpha', 'a') %!error ... %! signtest ([1, 2, 3, 4], [], 'alpha', [0.01, 0.05]) %!error ... %! signtest ([1, 2, 3, 4], [], 'tail', 0.01) %!error ... %! signtest ([1, 2, 3, 4], [], 'tail', {'both'}) %!error ... %! signtest ([1, 2, 3, 4], [], 'tail', 'some') %!error ... %! signtest ([1, 2, 3, 4], [], 'method', 'exact', 'tail', 'some') %!error ... %! signtest ([1, 2, 3, 4], [], 'method', 0.01) %!error ... %! signtest ([1, 2, 3, 4], [], 'method', {'exact'}) %!error ... %! signtest ([1, 2, 3, 4], [], 'method', 'some') %!error ... %! signtest ([1, 2, 3, 4], [], 'tail', 'both', 'method', 'some') statistics-release-1.9.2/inst/Hypothesis_Testing/ttest.m000066400000000000000000000234751524624707500235140ustar00rootroot00000000000000## Copyright (C) 2014 Tony Richardson ## Copyright (C) 2022 Andrew Penn ## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{h}, @var{pval}, @var{ci}, @var{stats}] =} ttest (@var{x}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}, @var{stats}] =} ttest (@var{x}, @var{m}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}, @var{stats}] =} ttest (@var{x}, @var{y}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}, @var{stats}] =} ttest (@var{x}, @var{m}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}, @var{stats}] =} ttest (@var{x}, @var{y}, @var{Name}, @var{Value}) ## ## Test for mean of a normal sample with unknown variance. ## ## Perform a t-test of the null hypothesis @code{mean (@var{x}) == ## @var{m}} for a sample @var{x} from a normal distribution with unknown ## mean and unknown standard deviation. Under the null, the test statistic ## @var{t} has a Student's t distribution. The default value of ## @var{m} is 0. ## ## If the second argument @var{y} is a vector, a paired-t test of the ## hypothesis @code{mean (@var{x}) = mean (@var{y})} is performed. If @var{x} ## and @var{y} are vectors, they must have the same size and dimensions. ## ## @var{x} (and @var{y}) can also be matrices. For matrices, @qcode{ttest} ## performs separate t-tests along each column, and returns a vector of results. ## @var{x} and @var{y} must have the same number of columns. The Type I error ## rate of the resulting vector of @var{pval} can be controlled by entering ## @var{pval} as input to the function @qcode{multcompare}. ## ## @qcode{ttest} treats NaNs as missing values, and ignores them. ## ## Name-Value pair arguments can be used to set various options. ## @qcode{'alpha'} can be used to specify the significance level ## of the test (the default value is 0.05). @qcode{'tail'}, can be used ## to select the desired alternative hypotheses. If the value is ## @qcode{'both'} (default) the null is tested against the two-sided ## alternative @code{mean (@var{x}) != @var{m}}. ## If it is @qcode{'right'} the one-sided alternative @code{mean (@var{x}) ## > @var{m}} is considered. Similarly for @qcode{'left'}, the one-sided ## alternative @code{mean (@var{x}) < @var{m}} is considered. ## When argument @var{x} is a matrix, @qcode{'dim'} can be used to select ## the dimension over which to perform the test. (The default is the ## first non-singleton dimension). ## ## If @var{h} is 1 the null hypothesis is rejected, meaning that the tested ## sample does not come from a Student's t distribution. If @var{h} is 0, then ## the null hypothesis cannot be rejected and it can be assumed that @var{x} ## follows a Student's t distribution. The p-value of the test is returned in ## @var{pval}. A 100(1-alpha)% confidence interval is returned in @var{ci}. ## ## @var{stats} is a structure containing the value of the test statistic ## (@var{tstat}), the degrees of freedom (@var{df}) and the sample's standard ## deviation (@var{sd}). ## ## @seealso{hotelling_t2test, ttest2, hotelling_t2test2} ## @end deftypefn function [h, p, ci, stats] = ttest (x, my, varargin) ## Set default arguments my_default = 0; alpha = 0.05; tail = 'both'; ## Find the first non-singleton dimension of x dim = min (find (size (x) != 1)); if (isempty (dim)) dim = 1; endif if (nargin == 1) my = my_default; endif i = 1; while (i <= length (varargin)) switch lower (varargin{i}) case 'alpha' i = i + 1; alpha = varargin{i}; if (! (isscalar (alpha) && isnumeric (alpha) && isreal (alpha) && alpha > 0 && alpha < 1)) error ("ttest: ALPHA must be a scalar between 0 and 1."); endif case 'tail' i = i + 1; tail = varargin{i}; case 'dim' i = i + 1; dim = varargin{i}; otherwise error ("ttest: Invalid Name argument."); endswitch i = i + 1; endwhile if (! isa (tail, 'char')) error ("ttest: tail argument must be a string."); endif if (any (and (! isscalar (my), size (x) != size (my)))) error ("ttest: Arrays in paired test must be the same size."); endif ## Set default values if arguments are present but empty if (isempty (my)) my = my_default; endif ## This adjustment allows everything else to remain the ## same for both the one-sample t test and paired tests. x = x - my; if (! isscalar (my)) my = 0; endif ## Calculate the test statistic value (tval) n = sum (! isnan (x), dim); x_bar = mean (x, dim, 'omitnan'); stats.tstat = []; stats.df = n - 1; stats.sd = std (x, 0, dim, 'omitnan'); x_bar_std = stats.sd ./ sqrt (n); tval = (x_bar) ./ x_bar_std; stats.tstat = tval; ## Based on the "tail" argument determine the P-value, the critical values, ## and the confidence interval. switch lower (tail) case 'both' p = 2 * (1 - tcdf (abs (tval), n - 1)); tcrit = - tinv (alpha / 2, n - 1); ci = [x_bar-tcrit.*x_bar_std; x_bar+tcrit.*x_bar_std] + my; case 'left' p = tcdf (tval, n - 1); tcrit = - tinv (alpha, n - 1); ci = [-inf*ones(size(x_bar)); my+x_bar+tcrit.*x_bar_std]; case 'right' p = 1 - tcdf (tval, n - 1); tcrit = - tinv (alpha, n - 1); ci = [my+x_bar-tcrit.*x_bar_std; inf*ones(size(x_bar))]; otherwise error ("ttest: Invalid value for tail argument."); endswitch ## Reshape the ci array to match MATLAB shaping if (isscalar (x_bar) && dim == 2) ci = ci(:)'; elseif (size (x_bar, 2) < size (x_bar, 1)) ci = reshape (ci(:), length (x_bar), 2); endif ## Determine the test outcome ## MATLAB returns this a double instead of a logical array h = double (p < alpha); endfunction %!test %! x = 8:0.1:12; %! [h, pval, ci] = ttest (x, 10); %! assert_equal (h, 0) %! assert_equal (pval, 1, 10*eps) %! assert_equal (ci, [9.6219 10.3781], 1E-5) %! [h, pval, ci0] = ttest (x, 0); %! assert_equal (h, 1) %! assert_equal (pval, 0) %! assert_equal (ci0, ci, 2e-15) %! [h, pval, ci] = ttest (x, 10, 'tail', 'right', 'dim', 2, 'alpha', 0.05); %! assert_equal (h, 0) %! assert_equal (pval, 0.5, 10*eps) %! assert_equal (ci, [9.68498 Inf], 1E-5) %!error ttest ([8:0.1:12], 10, 'tail', 'invalid'); %!error ttest ([8:0.1:12], 10, 'tail', 25); ## Reference values from MATLAB R2024a (probe run 2026-08-02). Only the ## two-tailed default and 'tail','right' at the default alpha were covered ## before, so the left tail, a non-default alpha, the stats output and the ## 'dim' option went unchecked. %!shared x, xm %! x = [10.2 9.7 11.1 10.5 9.9 10.8 10.1 9.6 10.4 10.7]; %! xm = [10.2 11.4; 9.7 10.9; 11.1 12.6; 10.5 11.1; 9.9 13.2]; %!test %! [h, p, ci] = ttest (x, 10, 'Tail', 'left'); %! assert_equal (h, 0); %! assert_equal (p, 0.957607393606414, 1e-14); %! assert_equal (ci, [-Inf, 10.583984634395803], 1e-13); %!test %! [h, p, ci] = ttest (x, 10, 'Tail', 'right'); %! assert_equal (h, 1); %! assert_equal (p, 0.042392606393586, 1e-14); %! assert_equal (ci, [10.016015365604199, Inf], 1e-13); %!test %! ## a non-default alpha must actually widen the interval %! [~, p1, ci1] = ttest (x, 10); %! [~, p2, ci2] = ttest (x, 10, 'Alpha', 0.01); %! assert_equal (ci1, [9.949548119279150, 10.650451880720851], 1e-13); %! assert_equal (ci2, [9.796537642780031, 10.803462357219971], 1e-13); %! assert_equal (diff (ci2) > diff (ci1), true); %! assert_equal (p1, p2, 0); %!test %! ## the fourth output was never exercised %! [~, ~, ~, stats] = ttest (x, 10); %! assert_equal (stats.tstat, 1.936491673103713, 1e-13); %! assert_equal (stats.df, 9); %! assert_equal (stats.sd, 0.489897948556636, 1e-14); %!test %! [h, p] = ttest (xm, 10, 'Dim', 1); %! assert_equal (h, [0, 1]); %! assert_equal (p, [0.318172286240873, 0.015001169519765], 1e-13); %!test %! [h, p] = ttest (xm, 10, 'Dim', 2); %! assert_equal (h(:)', [0, 0, 0, 0, 0]); %! assert_equal (p(:)', [0.409665529398267, 0.704832764699133, ... %! 0.245198884026780, 0.228400502439816, ... %! 0.519887895647178], 1e-13); ## ALPHA was not validated at all, so a negative one silently produced a ## [NaN NaN] confidence interval and "do not reject", and a vector one ## quietly vectorised into an undocumented signature. MATLAB refuses each of ## these with "ALPHA must be a scalar between 0 and 1." %!error ... %! ttest ([8:0.1:12], 10, 'Alpha', -0.05); %!error ... %! ttest ([8:0.1:12], 10, 'Alpha', 0); %!error ... %! ttest ([8:0.1:12], 10, 'Alpha', 1); %!error ... %! ttest ([8:0.1:12], 10, 'Alpha', 1.5); %!error ... %! ttest ([8:0.1:12], 10, 'Alpha', [0.01, 0.05]); %!error ... %! ttest ([8:0.1:12], 10, 'Alpha', 'a'); %!error ... %! ttest ([8:0.1:12], 10, 'Alpha', NaN); %!error ... %! ttest ([8:0.1:12], 10, 'Alpha', 2 + 1i); statistics-release-1.9.2/inst/Hypothesis_Testing/ttest2.m000066400000000000000000000247721524624707500235770ustar00rootroot00000000000000## Copyright (C) 2014 Tony Richardson ## Copyright (C) 2022 Andrew Penn ## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{h}, @var{pval}, @var{ci}, @var{stats}] =} ttest2 (@var{x}, @var{y}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}, @var{stats}] =} ttest2 (@var{x}, @var{y}, @var{Name}, @var{Value}) ## ## Perform a t-test to compare the means of two groups of data under the null ## hypothesis that the groups are drawn from distributions with the same mean. ## ## @var{x} and @var{y} can be vectors or matrices. For matrices, @qcode{ttest2} ## performs separate t-tests along each column, and returns a vector of results. ## @var{x} and @var{y} must have the same number of columns. The Type I error ## rate of the resulting vector of @var{pval} can be controlled by entering ## @var{pval} as input to the function @qcode{multcompare}. ## ## @qcode{ttest2} treats NaNs as missing values, and ignores them. ## ## For a nested t-test, use @qcode{anova2}. ## ## The argument @qcode{'alpha'} can be used to specify the significance level ## of the test (the default value is 0.05). The string argument @qcode{'tail'}, ## can be used to select the desired alternative hypotheses. If @qcode{'tail'} ## is @qcode{'both'} (default) the null is tested against the two-sided ## alternative @code{mean (@var{x}) != @var{m}}. If @qcode{'tail'} is ## @qcode{'right'} the one-sided alternative @code{mean (@var{x}) > @var{m}} is ## considered. Similarly for @qcode{'left'}, the one-sided alternative ## @code{mean (@var{x}) < @var{m}} is considered. ## ## When @qcode{'vartype'} is @qcode{'equal'} the variances are assumed to be ## equal (this is the default). When @qcode{'vartype'} is @qcode{'unequal'} the ## variances are not assumed equal. ## ## When argument @var{x} and @var{y} are matrices the @qcode{'dim'} argument can ## be used to select the dimension over which to perform the test. ## (The default is the first non-singleton dimension.) ## ## If @var{h} is 0 the null hypothesis is accepted, if it is 1 the null ## hypothesis is rejected. The p-value of the test is returned in @var{pval}. ## A 100(1-alpha)% confidence interval is returned in @var{ci}. @var{stats} ## is a structure containing the value of the test statistic (@var{tstat}), ## the degrees of freedom (@var{df}) and the sample standard deviation ## (@var{sd}). ## ## @seealso{hotelling_t2test, anova1, hotelling_t2test2, ttest} ## @end deftypefn function [h, p, ci, stats] = ttest2 (x, y, varargin) ## Set defaults alpha = 0.05; tail = 'both'; vartype = 'equal'; ## Find the first non-singleton dimension of x dim = min (find (size (x) != 1)); if (isempty (dim)) dim = 1; endif ## Evaluate optional input arguments i = 1; while ( i <= length (varargin) ) switch lower (varargin{i}) case 'alpha' i = i + 1; alpha = varargin{i}; if (! (isscalar (alpha) && isnumeric (alpha) && isreal (alpha) && alpha > 0 && alpha < 1)) error ("ttest2: ALPHA must be a scalar between 0 and 1."); endif case 'tail' i = i + 1; tail = varargin{i}; case 'vartype' i = i + 1; vartype = varargin{i}; case 'dim' i = i + 1; dim = varargin{i}; otherwise error ("ttest2: Invalid Name argument."); endswitch i = i + 1; endwhile ## Error checking if (! isa (tail, 'char')) error ("ttest2: tail argument must be a string."); endif if (size (x, abs (dim - 3)) != size (y, abs (dim - 3))) error ("ttest2: The data in a 2-sample t-test must be commensurate") endif ## Calculate mean, variance and size of each sample m = sum (! isnan (x), dim); n = sum (! isnan (y), dim); x_bar = mean (x, dim, 'omitnan') - mean (y, dim, 'omitnan'); s1_var = var (x, 0, dim, 'omitnan'); s2_var = var (y, 0, dim, 'omitnan'); ## Perform test-specific calculations switch lower (vartype) case 'equal' stats.tstat = []; stats.df = (m + n - 2); sp_var = ((m - 1) .* s1_var + (n - 1) .* s2_var) ./ stats.df; stats.sd = sqrt (sp_var); x_bar_std = sqrt (sp_var .* (1 ./ m + 1 ./ n)); n_sd = 1; case 'unequal' stats.tstat = []; se1 = sqrt (s1_var ./ m); se2 = sqrt (s2_var ./ n); sp_var = s1_var ./ m + s2_var ./ n; stats.df = ((se1 .^ 2 + se2 .^ 2) .^ 2 ./ ... (se1 .^ 4 ./ (m - 1) + se2 .^ 4 ./ (n - 1))); stats.sd = [sqrt(s1_var); sqrt(s2_var)]; x_bar_std = sqrt (sp_var); n_sd = 2; otherwise error ("ttest2: Invalid value for vartype argument."); endswitch stats.tstat = x_bar ./ x_bar_std; ## Based on the "tail" argument determine the P-value, the critical values, ## and the confidence interval. switch lower (tail) case 'both' p = 2 * (1 - tcdf (abs (stats.tstat), stats.df)); tcrit = - tinv (alpha / 2, stats.df); ci = [x_bar-tcrit.*x_bar_std; x_bar+tcrit.*x_bar_std]; case 'left' p = tcdf (stats.tstat, stats.df); tcrit = - tinv (alpha, stats.df); ci = [-inf*ones(size(x_bar)); x_bar+tcrit.*x_bar_std]; case 'right' p = 1 - tcdf (stats.tstat, stats.df); tcrit = - tinv (alpha, stats.df); ci = [x_bar-tcrit.*x_bar_std; inf*ones(size(x_bar))]; otherwise error ("ttest2: Invalid value for tail argument."); endswitch ## Reshape the ci array to match MATLAB shaping if (isscalar (x_bar) && dim == 2) ci = ci(:)'; stats.sd = stats.sd(:)'; elseif (size (x_bar, 2) < size (x_bar, 1)) ci = reshape (ci(:), length (x_bar), 2); stats.sd = reshape (stats.sd(:), length (x_bar), n_sd); endif ## Determine the test outcome ## MATLAB returns this a double instead of a logical array h = double (p < alpha); endfunction %!test %! a = 1:5; %! b = 6:10; %! b(5) = NaN; %! [h,p,ci,stats] = ttest2 (a,b); %! assert_equal (h, 1); %! assert_equal (p, 0.002535996080258229, 1e-14); %! assert_equal (ci, [-6.822014919225481, -2.17798508077452], 1e-14); %! assert_equal (stats.tstat, -4.582575694955839, 1e-14); %! assert_equal (stats.df, 7); %! assert_equal (stats.sd, 1.4638501094228, 1e-13); %!error ttest2 ([8:0.1:12], [8:0.1:12], 'tail', 'invalid'); %!error ttest2 ([8:0.1:12], [8:0.1:12], 'tail', 25); ## Reference values from MATLAB R2024a (probe run 2026-08-02). The single ## existing block covered only the two-sample default, leaving both 'Vartype' ## values, both one-sided tails, 'alpha' and 'dim' unchecked. 'Vartype' ## matters most: 'unequal' is Welch's test, with its own denominator and a ## non-integer degrees of freedom. %!shared x, y, xm %! x = [10.2 9.7 11.1 10.5 9.9 10.8 10.1 9.6 10.4 10.7]; %! y = [11.4 10.9 12.6 11.1 13.2 10.4 12.8 11.7]; %! xm = [10.2 11.4; 9.7 10.9; 11.1 12.6; 10.5 11.1; 9.9 13.2]; %!test %! [h, p, ci, stats] = ttest2 (x, y, 'Vartype', 'equal'); %! assert_equal (h, 1); %! assert_equal (p, 8.895270853487265e-04, 1e-15); %! assert_equal (ci, [-2.224122032184765, -0.700877967815235], 1e-13); %! assert_equal (stats.tstat, -4.070735048482627, 1e-13); %! assert_equal (stats.df, 16); %! assert_equal (stats.sd, 0.757411298436985, 1e-14); %!test %! ## Welch: a pooled denominator would give the 'equal' answer instead %! [h, p, ci, stats] = ttest2 (x, y, 'Vartype', 'unequal'); %! assert_equal (h, 1); %! assert_equal (p, 0.003801117252046, 1e-14); %! assert_equal (ci, [-2.327640087870322, -0.597359912129679], 1e-13); %! assert_equal (stats.tstat, -3.784559445642580, 1e-13); %! assert_equal (stats.df, 9.661941319801253, 1e-13); %! assert_equal (stats.sd(:)', [0.489897948556636, 1.001338390070295], 1e-14); %!test %! ## the default is the equal-variance test %! [~, pd] = ttest2 (x, y); %! [~, pe] = ttest2 (x, y, 'Vartype', 'equal'); %! [~, pu] = ttest2 (x, y, 'Vartype', 'unequal'); %! assert_equal (pd, pe, 0); %! assert_equal (isequal (pd, pu), false); %!test %! [h, p, ci] = ttest2 (x, y, 'Tail', 'left'); %! assert_equal (h, 1); %! assert_equal (p, 4.447635426743633e-04, 1e-15); %! assert_equal (ci, [-Inf, -0.835253361212791], 1e-13); %!test %! [h, p, ci] = ttest2 (x, y, 'Tail', 'right'); %! assert_equal (h, 0); %! assert_equal (p, 0.999555236457326, 1e-14); %! assert_equal (ci, [-2.089746638787210, Inf], 1e-13); %!test %! ## a non-default alpha must widen the interval and leave the p-value alone %! [~, p1, ci1] = ttest2 (x, y); %! [~, p2, ci2] = ttest2 (x, y, 'Alpha', 0.01); %! assert_equal (ci2, [-2.511854249766015, -0.413145750233985], 1e-13); %! assert_equal (diff (ci2) > diff (ci1), true); %! assert_equal (p1, p2, 0); %!test %! [h, p] = ttest2 (xm, xm + 1, 'Dim', 1); %! assert_equal (h, [1, 0]); %! assert_equal (p, [0.020600636177229, 0.154833732538475], 1e-13); ## ALPHA was not validated at all, so a negative one silently produced a ## [NaN NaN] confidence interval and "do not reject", and a vector one ## quietly vectorised into an undocumented signature. MATLAB refuses each of ## these with "ALPHA must be a scalar between 0 and 1." %!error ... %! ttest2 ([8:0.1:12], [9:0.1:13], 'Alpha', -0.05); %!error ... %! ttest2 ([8:0.1:12], [9:0.1:13], 'Alpha', 0); %!error ... %! ttest2 ([8:0.1:12], [9:0.1:13], 'Alpha', 1); %!error ... %! ttest2 ([8:0.1:12], [9:0.1:13], 'Alpha', 1.5); %!error ... %! ttest2 ([8:0.1:12], [9:0.1:13], 'Alpha', [0.01, 0.05]); %!error ... %! ttest2 ([8:0.1:12], [9:0.1:13], 'Alpha', 'a'); %!error ... %! ttest2 ([8:0.1:12], [9:0.1:13], 'Alpha', NaN); %!error ... %! ttest2 ([8:0.1:12], [9:0.1:13], 'Alpha', 2 + 1i); statistics-release-1.9.2/inst/Hypothesis_Testing/vartest.m000066400000000000000000000207341524624707500240340ustar00rootroot00000000000000## Copyright (C) 2014 Tony Richardson ## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} vartest (@var{x}, @var{v}) ## @deftypefnx {statistics} {@var{h} =} vartest (@var{x}, @var{v}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}] =} vartest (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}] =} vartest (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}, @var{stats}] =} vartest (@dots{}) ## ## One-sample test of variance. ## ## @code{@var{h} = vartest (@var{x}, @var{v})} performs a chi-square test of the ## hypothesis that the data in the vector @var{x} come from a normal ## distribution with variance @var{v}, against the alternative that @var{x} ## comes from a normal distribution with a different variance. The result is ## @var{h} = 0 if the null hypothesis ("variance is V") cannot be rejected at ## the 5% significance level, or @var{h} = 1 if the null hypothesis can be ## rejected at the 5% level. ## ## @var{x} may also be a matrix or an N-D array. For matrices, @code{vartest} ## performs separate tests along each column of @var{x}, and returns a vector of ## results. For N-D arrays, @code{vartest} works along the first non-singleton ## dimension of @var{x}. @var{v} must be a scalar. ## ## @code{vartest} treats NaNs as missing values, and ignores them. ## ## @code{[@var{h}, @var{pval}] = vartest (@dots{})} returns the p-value. That ## is the probability of observing the given result, or one more extreme, by ## chance if the null hypothesis true. ## ## @code{[@var{h}, @var{pval}, @var{ci}] = vartest (@dots{})} returns a ## 100 * (1 - @var{alpha})% confidence interval for the true variance. ## ## @code{[@var{h}, @var{pval}, @var{ci}, @var{stats}] = vartest (@dots{})} ## returns a structure with the following fields: ## ## @multitable @columnfractions 0.2 0.75 ## @item @qcode{chisqstat} @tab the value of the test statistic ## @item @qcode{df} @tab the degrees of freedom of the test ## @end multitable ## ## @code{[@dots{}] = vartest (@dots{}, @var{name}, @var{value}), @dots{}} ## specifies one or more of the following name/value pairs: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'alpha'} @tab the significance level. Default is 0.05. ## ## @item @qcode{'dim'} @tab dimension to work along a matrix or an N-D ## array. ## ## @item @qcode{'tail'} @tab a string specifying the alternative hypothesis ## @end multitable ## @multitable @columnfractions 0.15 0.75 ## @item @qcode{'both'} @tab variance is not @var{v} (two-tailed, default) ## @item @qcode{'left'} @tab variance is less than @var{v} (left-tailed) ## @item @qcode{'right'} @tab variance is greater than @var{v} ## (right-tailed) ## @end multitable ## ## @seealso{ttest, ztest, kstest} ## @end deftypefn function [h, pval, ci, stats] = vartest (x, v, varargin) ## Validate input arguments if (nargin < 2) error ("vartest: too few input arguments."); endif if (! isscalar (v) || ! isnumeric (v) || ! isreal (v) || v < 0) error ("vartest: invalid value for variance."); endif ## Add defaults alpha = 0.05; tail = 'both'; dim = []; if (nargin > 2 && mod (numel (varargin(:)), 2) == 0) for idx = 3:2:nargin name = varargin{idx-2}; value = varargin{idx-1}; switch (lower (name)) case 'alpha' alpha = value; if (! isscalar (alpha) || ! isnumeric (alpha) || ... alpha <= 0 || alpha >= 1) error ("vartest: invalid value for alpha."); endif case 'tail' tail = value; if (! any (strcmpi (tail, {'both', 'left', 'right'}))) error ("vartest: invalid value for tail."); endif case 'dim' dim = value; if (! isscalar (dim) || ! ismember (dim, 1:ndims (x))) error ("vartest: invalid value for operating dimension."); endif otherwise error ("vartest: invalid name for optional arguments."); endswitch endfor elseif (nargin > 2 && mod (numel (varargin(:)), 2) != 0) error ("vartest: optional arguments must be in name/value pairs."); endif ## Figure out which dimension mean will work along if (isempty (dim)) dim = find (size (x) != 1, 1); endif ## Replace all NaNs with zeros is_nan = isnan (x); x_dims = ndims (x); x(is_nan) = 0; ## Find sample size for each group (if more than one) if (any (is_nan(:))) sz = sum (! is_nan, dim); else sz = size (x, dim); endif ## Find degrees of freedom for each group (if more than one) df = max (sz - 1, 0); ## Calculate mean for each group (if more than one) x_mean = sum (x, dim) ./ max (1, sz); ## Center data if (isscalar (x_mean)) x_centered = x - x_mean; else rep = ones (1, x_dims); rep(dim) = size (x, dim); x_centered = x - repmat (x_mean, rep); endif ## Replace all NaNs with zeros x_centered(is_nan) = 0; ## Calculate chi-square statistic sumsq = sum (abs (x_centered) .^ 2, dim); if (v > 0) chisqstat = sumsq ./ v; else chisqstat = Inf (size (sumsq)); chisqstat(sumsq == 0) = NaN; endif ## Calculate p-value for the test and confidence intervals (if requested) if (strcmpi (tail, 'both')) pval = chi2cdf (chisqstat, df); pval = 2 * min (pval, 1 - pval); if (nargout > 2) ci = cat (dim, sumsq ./ chi2inv (1 - alpha / 2, df), ... sumsq ./ chi2inv (alpha / 2, df)); endif elseif (strcmpi (tail, 'right')) pval = gammainc (chisqstat / 2, df / 2, 'upper'); if (nargout > 2) ci = cat (dim, sumsq ./ chi2inv (1 - alpha, df), Inf (size (pval))); endif elseif (strcmpi (tail, 'left')) pval = chi2cdf (chisqstat, df); if (nargout > 2) ci = cat (dim, zeros (size (pval)), sumsq ./ chi2inv (alpha, df)); endif endif ## Determine the test outcome h = double (pval < alpha); h(isnan (pval)) = NaN; ## Create stats output structure (if requested) if (nargout > 3) stats = struct ('chisqstat', chisqstat, 'df', df); endif endfunction ## Test input validation %!error vartest (); %!error vartest ([1, 2, 3, 4], -0.5); %!error ... %! vartest ([1, 2, 3, 4], 1, 'alpha', 0); %!error ... %! vartest ([1, 2, 3, 4], 1, 'alpha', 1.2); %!error ... %! vartest ([1, 2, 3, 4], 1, 'alpha', 'val'); %!error ... %! vartest ([1, 2, 3, 4], 1, 'tail', 'val'); %!error ... %! vartest ([1, 2, 3, 4], 1, 'alpha', 0.01, 'tail', 'val'); %!error ... %! vartest ([1, 2, 3, 4], 1, 'dim', 3); %!error ... %! vartest ([1, 2, 3, 4], 1, 'alpha', 0.01, 'tail', 'both', 'dim', 3); %!error ... %! vartest ([1, 2, 3, 4], 1, 'alpha', 0.01, 'tail', 'both', 'badoption', 3); %!error ... %! vartest ([1, 2, 3, 4], 1, 'alpha', 0.01, 'tail'); ## Test results %!test %! load carsmall %! [h, pval, ci] = vartest (MPG, 7^2); %! assert_equal (h, 1); %! assert_equal (pval, 0.04335086742174443, 1e-14); %! assert_equal (ci, [49.397; 88.039], 1e-3); %!test %! load carsmall %! [h, pval, ci] = vartest (MPG, 7^2, 'tail', 'left'); %! assert_equal (h, 0); %! assert_equal (pval, 0.978324566289128, 1e-14); %! assert_equal (ci, [0; 83.685], 1e-3); %!test %! load carsmall %! [h, pval, ci] = vartest (MPG, 7^2, 'tail', 'right'); %! assert_equal (h, 1); %! assert_equal (pval, 0.021675433710872, 1e-14); %! assert_equal (ci, [51.543; Inf], 1e-3); statistics-release-1.9.2/inst/Hypothesis_Testing/vartest2.m000066400000000000000000000237531524624707500241220ustar00rootroot00000000000000## Copyright (C) 2014 Tony Richardson ## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} vartest2 (@var{x}, @var{y}) ## @deftypefnx {statistics} {@var{h} =} vartest2 (@var{x}, @var{y}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}] =} vartest2 (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}] =} vartest2 (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}, @var{stats}] =} vartest2 (@dots{}) ## ## Two-sample F test for equal variances. ## ## @code{@var{h} = vartest2 (@var{x}, @var{y})} performs an F test of the ## hypothesis that the independent data in vectors @var{x} and @var{y} come from ## normal distributions with equal variance, against the alternative that they ## come from normal distributions with different variances. The result is ## @var{h} = 0 if the null hypothesis ("variance are equal") cannot be rejected ## at the 5% significance level, or @var{h} = 1 if the null hypothesis can be ## rejected at the 5% level. ## ## @var{x} and @var{y} may also be matrices or N-D arrays. For matrices, ## @code{vartest2} performs separate tests along each column and returns a ## vector of results. For N-D arrays, @code{vartest2} works along the first ## non-singleton dimension and @var{x} and @var{y} must have the same size along ## all the remaining dimensions. ## ## @code{vartest2} treats NaNs as missing values, and ignores them. ## ## @code{[@var{h}, @var{pval}] = vartest2 (@dots{})} returns the p-value. That ## is the probability of observing the given result, or one more extreme, by ## chance if the null hypothesis true. ## ## @code{[@var{h}, @var{pval}, @var{ci}] = vartest2 (@dots{})} returns a ## @math{100 * (1 - @var{alpha})%} confidence interval for the true ratio ## var(X)/var(Y). ## ## @code{[@var{h}, @var{pval}, @var{ci}, @var{stats}] = vartest2 (@dots{})} ## returns a structure with the following fields: ## ## @multitable @columnfractions 0.2 0.75 ## @item @qcode{fstat} @tab the value of the test statistic ## @item @qcode{df1} @tab the numerator degrees of freedom of the test ## @item @qcode{df2} @tab the denominator degrees of freedom of the test ## @end multitable ## ## @code{[@dots{}] = vartest2 (@dots{}, @var{name}, @var{value}), @dots{}} ## specifies one or more of the following name/value pairs: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'alpha'} @tab the significance level. Default is 0.05. ## ## @item @qcode{'dim'} @tab dimension to work along a matrix or an N-D ## array. ## ## @item @qcode{'tail'} @tab a string specifying the alternative hypothesis ## @end multitable ## @multitable @columnfractions 0.15 0.75 ## @item @qcode{'both'} @tab variance is not @var{v} (two-tailed, default) ## @item @qcode{'left'} @tab variance is less than @var{v} (left-tailed) ## @item @qcode{'right'} @tab variance is greater than @var{v} ## (right-tailed) ## @end multitable ## ## @seealso{ttest2, kstest2, bartlett_test, levene_test} ## @end deftypefn function [h, pval, ci, stats] = vartest2 (x, y, varargin) ## Validate input arguments if (nargin < 2) error ("vartest2: too few input arguments."); endif if (isscalar (x) || isscalar (y)) error ("vartest2: X and Y must be vectors or matrices or N-D arrays."); endif ## If X and Y are vectors make them the same orientation if (isvector (x) && isvector (y)) if (size (x, 1) == 1) y = y(:)'; else y = y(:); endif endif ## Add defaults alpha = 0.05; tail = 'both'; dim = []; if (nargin > 2 && mod (numel (varargin(:)), 2) == 0) for idx = 3:2:nargin name = varargin{idx-2}; value = varargin{idx-1}; switch (lower (name)) case 'alpha' alpha = value; if (! isscalar (alpha) || ! isnumeric (alpha) || ... alpha <= 0 || alpha >= 1) error ("vartest2: invalid value for alpha."); endif case 'tail' tail = value; if (! any (strcmpi (tail, {'both', 'left', 'right'}))) error ("vartest2: invalid value for tail."); endif case 'dim' dim = value; if (! isscalar (dim) || ! ismember (dim, 1:ndims (x))) error ("vartest2: invalid value for operating dimension."); endif otherwise error ("vartest2: invalid name for optional arguments."); endswitch endfor elseif (nargin > 2 && mod (numel (varargin(:)), 2) != 0) error ("vartest2: optional arguments must be in name/value pairs."); endif ## Figure out which dimension mean will work along if (isempty (dim)) dim = find (size (x) != 1, 1); endif ## Check that all non-working dimensions of X and Y are of equal size x_size = size (x); y_size = size (y); x_size(dim) = 1; y_size(dim) = 1; if (! isequal (x_size, y_size)) error ("vartest2: input size mismatch."); endif ## Compute statistics for each sample [df1, x_var] = getstats (x,dim); [df2, y_var] = getstats (y,dim); ## Compute F statistic F = NaN (size (x_var)); t1 = (y_var > 0); F(t1) = x_var(t1) ./ y_var(t1); t2 = (x_var > 0) & ! t1; F(t2) = Inf; ## Calculate p-value for the test and confidence intervals (if requested) if (strcmpi (tail, 'both')) pval = 2 * min (fcdf (F, df1, df2), 1 - fcdf (F, df1, df2)); if (nargout > 2) ci = cat (dim, F .* finv (alpha / 2, df2, df1), ... F ./ finv (alpha / 2, df1, df2)); endif elseif (strcmpi (tail, 'right')) Ftmp = F; Ftmp(Ftmp < 0) = 0; pval = fcdf (1 ./ Ftmp, df2, df1); if (nargout > 2) ci = cat (dim, F .* finv (alpha, df2, df1), Inf (size (F))); endif elseif (strcmpi (tail, 'left')) pval = fcdf (F, df1, df2); if (nargout > 2) ci = cat (dim, zeros (size (F)), F ./ finv (alpha, df1, df2)); endif endif ## Determine the test outcome h = double (pval < alpha); h(isnan (pval)) = NaN; ## Create stats output structure (if requested) if (nargout > 3) stats = struct ('fstat', F, 'df1', df1, 'df2', df2); endif endfunction ## Compute statistics for one sample function [df, data_var] = getstats (data, dim) ## Calculate sample size and df by ignoring NaNs is_nan = isnan (data); n_data = sum (! is_nan, dim); df = max (n_data - 1, 0); ## Calculate mean data(is_nan) = 0; m_data = sum (data, dim) ./ max (1, n_data); ## Calculate variance if (isscalar (m_data)) c_data = data - m_data; else rep = ones (1, ndims (data)); rep(dim) = size (data, dim); c_data = data - repmat (m_data, rep); endif c_data(is_nan) = 0; data_var = sum (abs (c_data) .^ 2,dim); t = (df > 0); data_var(t) = data_var(t) ./ df(t); data_var(! t) = NaN; ## Make df a scalar if possible if (numel (df) > 1 && all (df(:) == df(1))) df = df(1); endif endfunction ## Test input validation %!error vartest2 (); %!error vartest2 (ones (20,1)); %!error ... %! vartest2 (rand (20,1), 5); %!error ... %! vartest2 (rand (20,1), rand (25,1)*2, 'alpha', 0); %!error ... %! vartest2 (rand (20,1), rand (25,1)*2, 'alpha', 1.2); %!error ... %! vartest2 (rand (20,1), rand (25,1)*2, 'alpha', 'some'); %!error ... %! vartest2 (rand (20,1), rand (25,1)*2, 'alpha', [0.05, 0.001]); %!error ... %! vartest2 (rand (20,1), rand (25,1)*2, 'tail', [0.05, 0.001]); %!error ... %! vartest2 (rand (20,1), rand (25,1)*2, 'tail', 'some'); %!error ... %! vartest2 (rand (20,1), rand (25,1)*2, 'dim', 3); %!error ... %! vartest2 (rand (20,1), rand (25,1)*2, 'alpha', 0.001, 'dim', 3); %!error ... %! vartest2 (rand (20,1), rand (25,1)*2, 'some', 3); %!error ... %! vartest2 (rand (20,1), rand (25,1)*2, 'some'); ## Test results %!test %! load carsmall %! [h, pval, ci, stat] = vartest2 (MPG(Model_Year==82), MPG(Model_Year==76)); %! assert_equal (h, 0); %! assert_equal (pval, 0.6288022362718455, 1e-13); %! assert_equal (ci, [0.4139; 1.7193], 1e-4); %! assert_equal (stat.fstat, 0.8384, 1e-4); %! assert_equal (stat.df1, 30); %! assert_equal (stat.df2, 33); %!test %! load carsmall %! [h, pval, ci, stat] = vartest2 (MPG(Model_Year==82), MPG(Model_Year==76), ... %! 'tail', 'left'); %! assert_equal (h, 0); %! assert_equal (pval, 0.314401118135922, 1e-13); %! assert_equal (ci, [0; 1.5287], 1e-4); %! assert_equal (stat.fstat, 0.8384, 1e-4); %! assert_equal (stat.df1, 30); %! assert_equal (stat.df2, 33); %!test %! load carsmall %! [h, pval, ci, stat] = vartest2 (MPG(Model_Year==82), MPG(Model_Year==76), ... %! 'tail', 'right'); %! assert_equal (h, 0); %! assert_equal (pval, 0.685598881864077, 1e-13); %! assert_equal (ci, [0.4643; Inf], 1e-4); %! assert_equal (stat.fstat, 0.8384, 1e-4); %! assert_equal (stat.df1, 30); %! assert_equal (stat.df2, 33); statistics-release-1.9.2/inst/Hypothesis_Testing/vartestn.m000066400000000000000000000406171524624707500242140ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {} vartestn (@var{x}) ## @deftypefnx {statistics} {} vartestn (@var{x}, @var{group}) ## @deftypefnx {statistics} {} vartestn (@dots{}, @var{name}, @var{value}) ## @deftypefnx {statistics} {@var{p} =} vartestn (@dots{}) ## @deftypefnx {statistics} {[@var{p}, @var{stats}] =} vartestn (@dots{}) ## @deftypefnx {statistics} {[@var{p}, @var{stats}] =} vartestn (@dots{}, @var{name}, @var{value}) ## ## Test for equal variances across multiple groups. ## ## @code{@var{h} = vartestn (@var{x})} performs Bartlett's test for equal ## variances for the columns of the matrix @var{x}. This is a test of the null ## hypothesis that the columns of @var{x} come from normal distributions with ## the same variance, against the alternative that they come from normal ## distributions with different variances. The result is displayed in a summary ## table of statistics as well as a box plot of the groups. ## ## @code{vartestn (@var{x}, @var{group})} requires a vector @var{x}, and a ## @var{group} argument that is a categorical variable, vector, string array, or ## cell array of strings with one row for each element of @var{x}. Values of ## @var{x} corresponding to the same value of @var{group} are placed in the same ## group. ## ## @code{vartestn} treats NaNs as missing values, and ignores them. ## ## @code{@var{p} = vartestn (@dots{})} returns the probability of observing the ## given result, or one more extreme, by chance under the null hypothesis that ## all groups have equal variances. Small values of @var{p} cast doubt on the ## validity of the null hypothesis. ## ## @code{[@var{p}, @var{stats}] = vartestn (@dots{})} returns a structure with ## the following fields: ## ## @multitable @columnfractions 0.2 0.75 ## @item @qcode{chistat} @tab -- the value of the test statistic ## @item @qcode{df} @tab -- the degrees of freedom of the test ## @end multitable ## ## ## @code{[@var{p}, @var{stats}] = vartestn (@dots{}, @var{name}, @var{value})} ## specifies one or more of the following @var{name}/@var{value} pairs: ## ## @multitable @columnfractions 0.20 0.8 ## @headitem @var{Name} @tab @var{Value} ## @item @qcode{'display'} @tab @qcode{'on'} to display a boxplot and table, or ## @qcode{'off'} to omit these displays. Default @qcode{'on'}. ## ## @item @qcode{'testtype'} @tab One of the following strings to control the ## type of test to perform ## @end multitable ## ## @multitable @columnfractions 0.25 0.72 ## @item @qcode{'Bartlett'} @tab Bartlett's test (default). ## ## @item @qcode{'LeveneQuadratic'} @tab Levene's test computed by ## performing anova on the squared deviations of the data values from their ## group means. ## ## @item @qcode{'LeveneAbsolute'} @tab Levene's test computed by performing ## anova on the absolute deviations of the data values from their group means. ## ## @item @qcode{'BrownForsythe'} @tab Brown-Forsythe test computed by ## performing anova on the absolute deviations of the data values from the group ## medians. ## ## @item @qcode{'OBrien'} @tab O'Brien's modification of Levene's test with ## @math{W=0.5}. ## @end multitable ## ## The classical Bartlett's test is sensitive to the assumption that the ## distribution in each group is normal. The other test types are more robust ## to non-normal distributions, especially ones prone to outliers. For these ## tests, the STATS output structure has a field named @qcode{fstat} containing ## the test statistic, and @qcode{df1} and @qcode{df2} containing its numerator ## and denominator degrees of freedom. ## ## @seealso{vartest, vartest2, anova1, bartlett_test, levene_test} ## @end deftypefn function [p, stats] = vartestn (x, group, varargin) ## Validate input arguments if (nargin < 1) error ("vartestn: too few input arguments."); endif if (isscalar (x)) error ("vartestn: X must be a vector or a matrix."); endif if (nargin < 2) group = []; endif if (nargin > 1 && any (strcmpi (group, {'display', 'testtype'}))) varargin = [{group} varargin]; group = []; endif if (isvector (x) && (nargin < 2 || isempty (group ))) error ("vartestn: if X is a vector then a group vector is required."); endif ## Add defaults plotdata = true; testtype = 'Bartlett'; if (numel (varargin(:)) > 0 && mod (numel (varargin(:)), 2) == 0) for idx = 1:2:numel (varargin(:)) name = varargin{idx}; value = varargin{idx+1}; switch (lower (name)) case 'display' plotdata = value; if (! any (strcmpi (plotdata, {'on', 'off'}))) error ("vartestn: invalid value for display."); endif if (strcmpi (plotdata, 'on')) plotdata = true; else plotdata = false; endif case 'testtype' testtype = value; if (! any (strcmpi (testtype, {'Bartlett', 'LeveneAbsolute', ... 'LeveneQuadratic', 'BrownForsythe', 'OBrien'}))) error ("vartestn: invalid value for testtype."); endif otherwise error ("vartestn: invalid name for optional arguments."); endswitch endfor elseif (numel (varargin(:)) > 0 && mod (numel (varargin(:)), 2) != 0) error ("vartestn: optional arguments must be in name/value pairs."); endif ## Convert group to cell array from character array, make it a column if (! isempty (group) && ischar (group)) group = cellstr (group); endif if (size (group, 1) == 1) group = group'; endif ## If x is a matrix, convert it to column vector and create a ## corresponding column vector for groups if (length (x) < prod (size (x))) [n, m] = size (x); x = x(:); gi = reshape (repmat ((1:m), n, 1), n*m, 1); if (length (group) == 0) ## no group names are provided group = gi; elseif (size (group, 1) == m) ## group names exist and match columns group = group(gi,:); else error ("vartestn: columns in X and GROUP length do not match."); endif endif ## Check that x and group are the same size if (! all (numel (x) == numel (group))) error ("vartestn: GROUP must be a vector with the same number of rows as x."); endif ## Identify NaN values (if any) and remove them from X along with ## their corresponding values from group vector nonan = ! isnan (x); x = x(nonan); group = group(nonan, :); ## Convert group to indices and separate names [group_id, group_names] = grp2idx (group); group_id = group_id(:); ## Compute group summary statistics [group_mean, group_ster, group_size] = grpstats (x, group_id, ... {'mean', 'sem', 'numel'}); ## Compute group degrees of freedom and variances group_DF = group_size - 1; groupVAR = group_size .* group_ster .^ 2; sum_DF = sum (group_DF); ## Calculate pooled variance if (sum_DF > 0) pooledVAR = sum (group_DF .* groupVAR) / sum_DF; else pooledVAR = NaN; endif ## Get number of groups k = length (group_DF); ## Test for equal variance according to specified testtype switch (lower (testtype)) case 'bartlett' ## Calculate degrees of freedom Bdf = max (0, sum (group_DF > 0) - 1); ## Get valid groups msgroups = group_DF > 0; ## For valid groups if (Bdf > 0 && sum_DF > 0) B = log (pooledVAR) * sum (group_DF) - ... sum (group_DF(msgroups) .* log (groupVAR(msgroups))); C = 1 + (sum (1 ./ group_DF(msgroups)) - 1 / sum (group_DF)) / (3 * Bdf); F = B / C; else F = NaN; endif ## Compute p-value p = 1 - chi2cdf (F, Bdf); testname = 'Bartlett''s statistic '; if (nargout > 1) stats = struct ('chisqstat', F, 'df', Bdf); endif case {'leveneabsolute', 'levenequadratic'} ## Remove single-sample groups ssgroups = find (group_size < 2); msgroups = ! ismember (group_id, ssgroups); ## Center each group with mean x_center = x(msgroups) - group_mean(group_id(msgroups)); ## Get number of valid groups (group size > 1) n_groups = length (group_size) - length (ssgroups); ## Perform one-way anova and extract results from the anova table if (n_groups > 1) if (strcmpi (testtype, 'LeveneAbsolute')) [p, atab] = anova1 (abs (x_center), group_id(msgroups), 'off'); testname = 'Levene''s statistic (absolute) '; else [p, atab] = anova1 (x_center .^ 2, group_id(msgroups), 'off'); testname = 'Levene''s statistic (quadratic) '; endif ## Get F statistic and both degrees of freedom F = atab{2,5}; Bdf = [atab{2,3}, atab{3,3}]; else p = NaN; F = NaN; Bdf = [0, (length (x_center) - n_groups)]; endif if (nargout > 1) stats = struct ('fstat', F, 'df', Bdf); endif case 'brownforsythe' ## Remove single-sample groups ssgroups = find (group_size < 2); msgroups = ! ismember (group_id, ssgroups); ## Calculate group medians group_md = grpstats (x, group_id, 'median'); ## Center each group with median xcbf = x(msgroups) - group_md(group_id(msgroups)); ## Get number of valid groups (group size > 1) n_groups = length (group_size) - length (ssgroups); ## Perform one-way anova and extract results from the anova table if (n_groups > 1) [p, atab] = anova1 (abs (xcbf), group_id(msgroups), 'off'); ## Get F statistic and both degrees of freedom F = atab{2,5}; Bdf = [atab{2,3}, atab{3,3}]; else p = NaN; F = NaN; Bdf = [0, (length (xcbf) - n_groups)]; endif testname = 'Brown-Forsythe statistic '; if (nargout > 1) stats = struct ('fstat', F, 'df', Bdf); endif case 'obrien' ## Remove single-sample groups ssgroups = find (group_size < 2); msgroups = ! ismember (group_id, ssgroups); ## Center each group with mean x_center = x(msgroups) - group_mean(group_id(msgroups)); ## Calculate OBrien Z_ij xcs = x_center.^2; W = 0.5; xcw = ((W + group_size(group_id(msgroups)) - 2) .* ... group_size(group_id(msgroups)) .* xcs - W .* ... (group_size(group_id(msgroups)) - 1) .* ... groupVAR(group_id(msgroups))) ./ ... ((group_size(group_id(msgroups)) - 1) .* ... (group_size(group_id(msgroups)) - 2)); ## Get number of valid groups (group size > 1) n_groups = length (group_size) - length (ssgroups); ## Perform one-way anova and extract results from the anova table if (n_groups > 1) [p, atab] = anova1 (xcw, group_id(msgroups), 'off'); ## Get F statistic and both degrees of freedom F = atab{2,5}; Bdf = [atab{2,3}, atab{3,3}]; else p = NaN; F = NaN; Bdf = [0, length(xcw)-n_groups]; endif testname = 'OBrien statistic '; if (nargout > 1) stats = struct ('fstat', F, 'df', Bdf); endif endswitch ## Print Group Summary Table (unless opted out) if (nargout == 0 || plotdata) groupSTD = sqrt (groupVAR); printf ("\n Group Summary Table\n\n"); printf ("Group Count Mean Std Dev\n"); printf ("------------------------------------------------------------\n"); for i = 1:k printf ("%-20s %10i %9.4f %1.6f\n", ... group_names{i}, group_size(i), group_mean(i), groupSTD(i)); endfor printf ("Pooled Groups %10i %9.4f %1.6f\n", ... sum (group_size), mean (group_mean), mean (groupSTD)); printf ("Pooled valid Groups %10i %9.4f %1.6f\n\n", ... sum (group_size(group_id(msgroups))), ... mean (group_mean(group_id(msgroups))), ... mean (groupSTD(group_id(msgroups)))); printf ("%s %7.5f\n", testname, F); if (numel (Bdf) == 1) printf ("Degrees of Freedom %10i\n", Bdf); else printf ("Degrees of Freedom %10i, %3i\n", Bdf(1), Bdf(2)); endif printf ("p-value %1.6f\n\n", p); endif ## Plot data using BOXPLOT (unless opted out) if (plotdata) boxplot (x, group_id, 'Notch', 'on', 'Labels', group_names); endif endfunction %!demo %! ## Test the null hypothesis that the variances are equal across the five %! ## columns of data in the students’ exam grades matrix, grades. %! %! load examgrades %! vartestn (grades) %!demo %! ## Test the null hypothesis that the variances in miles per gallon (MPG) are %! ## equal across different model years. %! %! load carsmall %! vartestn (MPG, Model_Year) %!demo %! ## Use Levene’s test to test the null hypothesis that the variances in miles %! ## per gallon (MPG) are equal across different model years. %! %! load carsmall %! p = vartestn (MPG, Model_Year, 'TestType', 'LeveneAbsolute') %!demo %! ## Test the null hypothesis that the variances are equal across the five %! ## columns of data in the students’ exam grades matrix, grades, using the %! ## Brown-Forsythe test. Suppress the display of the summary table of %! ## statistics and the box plot. %! %! load examgrades %! [p, stats] = vartestn (grades, 'TestType', 'BrownForsythe', 'Display', 'off') ## Test input validation %!error vartestn (); %!error vartestn (1); %!error ... %! vartestn ([1, 2, 3, 4, 5, 6, 7]); %!error ... %! vartestn ([1, 2, 3, 4, 5, 6, 7], []); %!error ... %! vartestn ([1, 2, 3, 4, 5, 6, 7], 'TestType', 'LeveneAbsolute'); %!error ... %! vartestn ([1, 2, 3, 4, 5, 6, 7], [], 'TestType', 'LeveneAbsolute'); %!error ... %! vartestn ([1, 2, 3, 4, 5, 6, 7], [1, 1, 1, 2, 2, 2, 2], 'Display', 'some'); %!error ... %! vartestn (ones (50,3), 'Display', 'some'); %!error ... %! vartestn (ones (50,3), 'Display', 'off', 'testtype', 'some'); %!error ... %! vartestn (ones (50,3), [], 'som'); %!error ... %! vartestn (ones (50,3), [], 'some', 'some'); %!error ... %! vartestn (ones (50,3), [1, 2], 'Display', 'off'); ## Test results %!test %! load examgrades %! [p, stat] = vartestn (grades, 'Display', 'off'); %! assert_equal (p, 7.908647337018238e-08, 1e-14); %! assert_equal (stat.chisqstat, 38.7332, 1e-4); %! assert_equal (stat.df, 4); %!test %! load examgrades %! [p, stat] = vartestn (grades, 'Display', 'off', 'TestType', 'LeveneAbsolute'); %! assert_equal (p, 9.523239714592791e-07, 1e-14); %! assert_equal (stat.fstat, 8.5953, 1e-4); %! assert_equal (stat.df, [4, 595]); %!test %! load examgrades %! [p, stat] = vartestn (grades, 'Display', 'off', 'TestType', 'LeveneQuadratic'); %! assert_equal (p, 7.219514351897161e-07, 1e-14); %! assert_equal (stat.fstat, 8.7503, 1e-4); %! assert_equal (stat.df, [4, 595]); %!test %! load examgrades %! [p, stat] = vartestn (grades, 'Display', 'off', 'TestType', 'BrownForsythe'); %! assert_equal (p, 1.312093241723211e-06, 1e-14); %! assert_equal (stat.fstat, 8.4160, 1e-4); %! assert_equal (stat.df, [4, 595]); %!test %! load examgrades %! [p, stat] = vartestn (grades, 'Display', 'off', 'TestType', 'OBrien'); %! assert_equal (p, 8.235660885480556e-07, 1e-14); %! assert_equal (stat.fstat, 8.6766, 1e-4); %! assert_equal (stat.df, [4, 595]); statistics-release-1.9.2/inst/Hypothesis_Testing/ztest.m000066400000000000000000000204231524624707500235100ustar00rootroot00000000000000## Copyright (C) 2014 Tony Richardson ## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} ztest (@var{x}, @var{m}, @var{sigma}) ## @deftypefnx {statistics} {@var{h} =} ztest (@var{x}, @var{m}, @var{sigma}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}] =} ztest (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}] =} ztest (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{ci}, @var{zvalue}] =} ztest (@dots{}) ## ## One-sample Z-test. ## ## @code{@var{h} = ztest (@var{x}, @var{v})} performs a Z-test of the hypothesis ## that the data in the vector @var{x} come from a normal distribution with mean ## @var{m}, against the alternative that @var{x} comes from a normal ## distribution with a different mean @var{m}. The result is @var{h} = 0 if the ## null hypothesis ("mean is M") cannot be rejected at the 5% significance ## level, or @var{h} = 1 if the null hypothesis can be rejected at the 5% level. ## ## @var{x} may also be a matrix or an N-D array. For matrices, @code{ztest} ## performs separate tests along each column of @var{x}, and returns a vector of ## results. For N-D arrays, @code{ztest} works along the first non-singleton ## dimension of @var{x}. @var{m} and @var{sigma} must be scalars. ## ## @code{ztest} treats NaNs as missing values, and ignores them. ## ## @code{[@var{h}, @var{pval}] = ztest (@dots{})} returns the p-value. That ## is the probability of observing the given result, or one more extreme, by ## chance if the null hypothesis true. ## ## @code{[@var{h}, @var{pval}, @var{ci}] = ztest (@dots{})} returns a ## 100 * (1 - @var{alpha})% confidence interval for the true mean. ## ## @code{[@var{h}, @var{pval}, @var{ci}, @var{zvalue}] = ztest (@dots{})} ## returns the value of the test statistic. ## ## @code{[@dots{}] = ztest (@dots{}, @var{Name}, @var{Value}, @dots{})} ## specifies one or more of the following @var{Name}/@var{Value} pairs: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Name} @tab @var{Value} ## @item "alpha" @tab the significance level. Default is 0.05. ## ## @item "dim" @tab dimension to work along a matrix or an N-D array. ## ## @item "tail" @tab a string specifying the alternative hypothesis: ## @end multitable ## @multitable @columnfractions 0.15 0.75 ## @item "both" @tab "mean is not @var{m}" (two-tailed, default) ## @item "left" @tab "mean is less than @var{m}" (left-tailed) ## @item "right" @tab "mean is greater than @var{m}" (right-tailed) ## @end multitable ## ## @seealso{ttest, vartest, signtest, kstest} ## @end deftypefn function [h, pval, ci, zvalue] = ztest (x, m, sigma, varargin) ## Validate input arguments if (nargin < 3) error ("ztest: too few input arguments."); endif if (! isscalar (m) || ! isnumeric (m) || ! isreal (m)) error ("ztest: invalid value for mean."); endif if (! isscalar (sigma) || ! isnumeric (sigma) || ! isreal (sigma) || sigma < 0) error ("ztest: invalid value for standard deviation."); endif ## Add defaults alpha = 0.05; tail = 'both'; dim = []; if (nargin > 3) for idx = 4:2:nargin name = varargin{idx-3}; value = varargin{idx-2}; switch (lower (name)) case 'alpha' alpha = value; if (! isscalar (alpha) || ! isnumeric (alpha) || ... alpha <= 0 || alpha >= 1) error ("ztest: invalid VALUE for alpha."); endif case 'tail' tail = value; if (! any (strcmpi (tail, {'both', 'left', 'right'}))) error ("ztest: invalid VALUE for tail."); endif case 'dim' dim = value; if (! isscalar (dim) || ! ismember (dim, 1:ndims (x))) error ("ztest: invalid VALUE for operating dimension."); endif otherwise error ("ztest: invalid NAME for optional arguments."); endswitch endfor endif ## Figure out which dimension mean will work along if (isempty (dim)) dim = find (size (x) != 1, 1); endif ## Replace all NaNs with zeros is_nan = isnan (x); ## Find sample size for each group (if more than one) if (any (is_nan(:))) sz = sum (! is_nan, dim); else sz = size (x, dim); endif ## Calculate mean, standard error and z-value for each group x_mean = sum (x(! is_nan), dim) ./ max (1, sz); stderr = sigma ./ sqrt (sz); zvalue = (x_mean - m) ./ stderr; ## Calculate p-value for the test and confidence intervals (if requested) if (strcmpi (tail, 'both')) pval = 2 * normcdf (- abs (zvalue), 0, 1); if (nargout > 2) crit = norminv (1 - alpha / 2, 0, 1) .* stderr; ci = cat (dim, x_mean - crit, x_mean + crit); endif elseif (strcmpi (tail, 'right')) pval = normcdf (- zvalue, 0, 1); if (nargout > 2) crit = norminv (1 - alpha, 0, 1) .* stderr; ci = cat (dim, x_mean - crit, Inf (size (pval))); endif elseif (strcmpi (tail, 'left')) pval = normcdf (zvalue, 0, 1); if (nargout > 2) crit = norminv (1 - alpha, 0, 1) .* stderr; ci = cat (dim, - Inf (size (pval)), x_mean + crit); endif endif ## Determine the test outcome h = double (pval < alpha); h(isnan (pval)) = NaN; endfunction ## Test input validation %!error ztest (); %!error ... %! ztest ([1, 2, 3, 4], 2, -0.5); %!error ... %! ztest ([1, 2, 3, 4], 1, 2, 'alpha', 0); %!error ... %! ztest ([1, 2, 3, 4], 1, 2, 'alpha', 1.2); %!error ... %! ztest ([1, 2, 3, 4], 1, 2, 'alpha', 'val'); %!error ... %! ztest ([1, 2, 3, 4], 1, 2, 'tail', 'val'); %!error ... %! ztest ([1, 2, 3, 4], 1, 2, 'alpha', 0.01, 'tail', 'val'); %!error ... %! ztest ([1, 2, 3, 4], 1, 2, 'dim', 3); %!error ... %! ztest ([1, 2, 3, 4], 1, 2, 'alpha', 0.01, 'tail', 'both', 'dim', 3); %!error ... %! ztest ([1, 2, 3, 4], 1, 2, 'alpha', 0.01, 'tail', 'both', 'badoption', 3); ## Test results %!test %! load carsmall %! [h, pval, ci] = ztest (MPG, mean (MPG, 'omitnan'), std (MPG, 'omitnan')); %! assert_equal (h, 0); %! assert_equal (pval, 1, 1e-14); %! assert_equal (ci, [22.094; 25.343], 1e-3); %!test %! load carsmall %! [h, pval, ci] = ztest (MPG, 26, 8); %! assert_equal (h, 1); %! assert_equal (pval, 0.00568359158544743, 1e-14); %! assert_equal (ci, [22.101; 25.335], 1e-3); %!test %! load carsmall %! [h, pval, ci] = ztest (MPG, 26, 4); %! assert_equal (h, 1); %! assert_equal (pval, 3.184168011941316e-08, 1e-14); %! assert_equal (ci, [22.909; 24.527], 1e-3); %!test %! x = normrnd (10, 2, 100, 1); %! [h, pval, ci] = ztest (x, 10, 2, 'tail', 'right'); %! assert_equal (isnan (pval), false); %! assert_equal (pval >= 0 && pval <= 1, true); %!test %! x = normrnd (10, 2, 100, 1); %! [h, pval, ci] = ztest (x, 10, 2, 'tail', 'left'); %! assert_equal (isnan (pval), false); %! assert_equal (pval >= 0 && pval <= 1, true); %!test %! load fisheriris; %! x = meas(:,1); %! m = 5.8; %! sigma = 0.8; %! [h, pval, ci] = ztest (x, m, sigma, 'tail', 'right'); %! assert_equal (h, 0) %! assert_equal (pval, 0.2535, 1e-4) %! assert_equal (ci, [5.7359; Inf], 1e-5) %!test %! load fisheriris; %! x = meas(:,1); %! m = 5.8; %! sigma = 0.8; %! [h, pval, ci] = ztest (x, m, sigma, 'tail', 'left'); %! assert_equal (h, 0) %! assert_equal (pval, 0.7465, 1e-4) %! assert_equal (ci, [-Inf; 5.9508], 1e-4) statistics-release-1.9.2/inst/Hypothesis_Testing/ztest2.m000066400000000000000000000134011524624707500235700ustar00rootroot00000000000000## Copyright (C) 1996-2017 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{h} =} ztest2 (@var{x1}, @var{n1}, @var{x2}, @var{n2}) ## @deftypefnx {statistics} {@var{h} =} ztest2 (@var{x1}, @var{n1}, @var{x2}, @var{n2}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}] =} ztest2 (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{pval}, @var{zvalue}] =} ztest2 (@dots{}) ## ## Two proportions Z-test. ## ## If @var{x1} and @var{n1} are the counts of successes and trials in one ## sample, and @var{x2} and @var{n2} those in a second one, test the null ## hypothesis that the success probabilities @math{p1} and @math{p2} are the ## same. The result is @var{h} = 0 if the null hypothesis cannot be rejected at ## the 5% significance level, or @var{h} = 1 if the null hypothesis can be ## rejected at the 5% level. ## ## Under the null, the test statistic @var{zvalue} approximately follows a ## standard normal distribution. ## ## The size of @var{h}, @var{pval}, and @var{zvalue} is the common size of ## @var{x1}, @var{n1}, @var{x2}, and @var{n2}, which must be scalars or of ## common ## size. A scalar input functions as a constant matrix of the same size as the ## other inputs. ## ## @code{[@var{h}, @var{pval}] = ztest2 (@dots{})} returns the p-value. That ## is the probability of observing the given result, or one more extreme, by ## chance if the null hypothesis true. ## ## @code{[@var{h}, @var{pval}, @var{zvalue}] = ztest2 (@dots{})} returns the ## value of the test statistic. ## ## @code{[@dots{}] = ztest2 (@dots{}, @var{Name}, @var{Value}, @dots{})} ## specifies one or more of the following @var{Name}/@var{Value} pairs: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Name} @tab @var{Value} ## @item @qcode{'alpha'} @tab the significance level. Default is 0.05. ## ## @item @qcode{'tail'} @tab a string specifying the alternative hypothesis ## @end multitable ## @multitable @columnfractions 0.25 0.65 ## @item @qcode{'both'} @tab @math{p1} is not @math{p2} ## (two-tailed, default) ## @item @qcode{'left'} @tab @math{p1} is less than @math{p2} ## (left-tailed) ## @item @qcode{'right'} @tab @math{p1} is greater than @math{p2} ## (right-tailed) ## @end multitable ## ## @seealso{chi2test, fishertest} ## @end deftypefn function [h, pval, zvalue] = ztest2 (x1, n1, x2, n2, varargin) if (nargin < 4) print_usage (); endif if (! isscalar (x1) || ! isscalar (n1) || ! isscalar (x2) || ! isscalar (n2)) [retval, x1, n1, x2, n2] = common_size (x1, n1, x2, n2); if (retval > 0) error ("ztest2: X1, N1, X2, and N2 must be of common size or scalars."); endif endif if (iscomplex (x1) || iscomplex (n1) || iscomplex (x2) || iscomplex (n2)) error ("ztest2: X1, N1, X2, and N2 must not be complex."); endif if (any (x1(:) > n1(:)) || any (x2(:) > n2(:))) error ("ztest2: X1 must be <= N1 and X2 must be <= N2."); endif ## Add defaults and parse optional arguments alpha = 0.05; tail = 'both'; if (nargin > 4) params = numel (varargin); if ((params / 2) != fix (params / 2)) error ("ztest2: optional arguments must be in NAME-VALUE pairs.") endif for idx = 1:2:params name = varargin{idx}; value = varargin{idx+1}; switch (lower (name)) case 'alpha' alpha = value; if (! isscalar (alpha) || ! isnumeric (alpha) || ... alpha <= 0 || alpha >= 1) error ("ztest2: invalid VALUE for alpha."); endif case 'tail' tail = value; if (! any (strcmpi (tail, {'both', 'left', 'right'}))) error ("ztest2: invalid VALUE for tail."); endif otherwise error ("ztest2: invalid NAME for optional arguments."); endswitch endfor endif p1 = x1 ./ n1; p2 = x2 ./ n2; pc = (x1 + x2) ./ (n1 + n2); zvalue = (p1 - p2) ./ sqrt (pc .* (1 - pc) .* (1 ./ n1 + 1 ./ n2)); cdf = normcdf (zvalue); if (strcmpi (tail, 'both')) pval = 2 * min (cdf, 1 - cdf); elseif (strcmpi (tail, 'right')) pval = 1 - cdf; elseif (strcmpi (tail, 'left')) pval = cdf; endif ## Determine the test outcome h = double (pval < alpha); h(isnan (pval)) = NaN; endfunction ## Test input validation %!error ztest2 (); %!error ztest2 (1); %!error ztest2 (1, 2); %!error ztest2 (1, 2, 3); %!error ztest2 (1, 2, 3, 2); %!error ... %! ztest2 (1, 2, 3, 4, 'alpha') %!error ... %! ztest2 (1, 2, 3, 4, 'alpha', 0); %!error ... %! ztest2 (1, 2, 3, 4, 'alpha', 1.2); %!error ... %! ztest2 (1, 2, 3, 4, 'alpha', 'val'); %!error ... %! ztest2 (1, 2, 3, 4, 'tail', 'val'); %!error ... %! ztest2 (1, 2, 3, 4, 'alpha', 0.01, 'tail', 'val'); %!error ... %! ztest2 (1, 2, 3, 4, 'alpha', 0.01, 'tail', 'both', 'badoption', 3); statistics-release-1.9.2/inst/Markov_Models/000077500000000000000000000000001524624707500210665ustar00rootroot00000000000000statistics-release-1.9.2/inst/Markov_Models/doc-cache000066400000000000000000000377051524624707500226330ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 5 # name: # type: sq_string # elements: 1 # length: 9 hmmdecode # name: # type: sq_string # elements: 1 # length: 2479 statistics: pstates = hmmdecode ( sequence , transprob , outprob ) statistics: [ pstates , logpseq ] = hmmdecode (…) statistics: [ pstates , logpseq , fs , bs , s ] = hmmdecode (…) statistics: […] = hmmdecode (…, "symbols" , symbols ) Posterior state probabilities of a hidden Markov model. Calculate the posterior state probabilities of the sequence sequence from a hidden Markov model. The posterior state probabilities are the conditional probabilities of being in each state given the whole observed sequence. The model assumes that the generation starts in state 1 at step 0 but does not include step 0 in the sequence. Arguments sequence is a vector of length len of given outputs. The outputs must be integers ranging from 1 to columns (outprob) . transprob is the matrix of transition probabilities of the states. transprob(i, j) is the probability of a transition to state j given state i . outprob is the matrix of output probabilities. outprob(i, j) is the probability of generating output j given state i . Return values pstates is the matrix of posterior state probabilities. It has one row for each state and one column for each element of sequence . pstates(i, j) is the conditional probability that the model is in state i when it generates the j -th output of sequence , given that sequence is emitted. logpseq is the logarithm of the probability of the sequence sequence . fs and bs are the scaled forward and backward probabilities, respectively, and s is the vector of scale factors used to keep the computation numerically stable. If "symbols" is specified, then sequence is expected to be a sequence of the elements of symbols instead of integers ranging from 1 to columns (outprob) . symbols can be a cell array. Examples transprob = [0.8, 0.2; 0.4, 0.6]; outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; [sequence, states] = hmmgenerate (25, transprob, outprob); pstates = hmmdecode (sequence, transprob, outprob); symbols = {"A", "B", "C"}; [sequence, states] = hmmgenerate (25, transprob, outprob, ... "symbols", symbols); pstates = hmmdecode (sequence, transprob, outprob, "symbols", symbols); References Wendy L. Martinez and Angel R. Martinez. Computational Statistics Handbook with MATLAB . Appendix E, pages 547-557, Chapman & Hall/CRC, 2001. Lawrence R. Rabiner. A Tutorial on Hidden Markov Models and Selected Applications in Speech Recognition. Proceedings of the IEEE , 77(2), pages 257-286, February 1989. # name: # type: sq_string # elements: 1 # length: 55 Posterior state probabilities of a hidden Markov model. # name: # type: sq_string # elements: 1 # length: 11 hmmestimate # name: # type: sq_string # elements: 1 # length: 3722 statistics: [ transprobest , outprobest ] = hmmestimate ( sequence , states ) statistics: […] = hmmestimate (…, "statenames" , statenames ) statistics: […] = hmmestimate (…, "symbols" , symbols ) statistics: […] = hmmestimate (…, "pseudotransitions" , pseudotransitions ) statistics: […] = hmmestimate (…, "pseudoemissions" , pseudoemissions ) Estimation of a hidden Markov model for a given sequence. Estimate the matrix of transition probabilities and the matrix of output probabilities of a given sequence of outputs and states generated by a hidden Markov model. The transition probabilities are estimated by counting the transitions that actually occur between consecutive states in states ; the output probabilities are estimated from the outputs emitted by each state. Arguments sequence is a vector of a sequence of given outputs. The outputs must be integers ranging from 1 to the number of outputs of the hidden Markov model. states is a vector of the same length as sequence of given states. The states must be integers ranging from 1 to the number of states of the hidden Markov model. Return values transprobest is the matrix of the estimated transition probabilities of the states. transprobest(i, j) is the estimated probability of a transition to state j given state i . outprobest is the matrix of the estimated output probabilities. outprobest(i, j) is the estimated probability of generating output j given state i . If 'symbols' is specified, then sequence is expected to be a sequence of the elements of symbols instead of integers. symbols can be a cell array. If 'statenames' is specified, then states is expected to be a sequence of the elements of statenames instead of integers. statenames can be a cell array. If 'pseudotransitions' is specified then the integer matrix pseudotransitions is used as an initial number of counted transitions. pseudotransitions(i, j) is the initial number of counted transitions from state i to state j . transprobest will have the same size as pseudotransitions . Use this if you have transitions that are very unlikely to occur. If 'pseudoemissions' is specified then the integer matrix pseudoemissions is used as an initial number of counted outputs. pseudoemissions(i, j) is the initial number of counted outputs j given state i . If 'pseudoemissions' is also specified then the number of rows of pseudoemissions must be the same as the number of rows of pseudotransitions . outprobest will have the same size as pseudoemissions . Use this if you have outputs or states that are very unlikely to occur. Examples transprob = [0.8, 0.2; 0.4, 0.6]; outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; [sequence, states] = hmmgenerate (25, transprob, outprob); [transprobest, outprobest] = hmmestimate (sequence, states) symbols = {"A", "B", "C"}; statenames = {"One", "Two"}; [sequence, states] = hmmgenerate (25, transprob, outprob, ... "symbols", symbols, ... "statenames", statenames); [transprobest, outprobest] = hmmestimate (sequence, states, ... "symbols', symbols, ... "statenames', statenames) pseudotransitions = [8, 2; 4, 6]; pseudoemissions = [2, 4, 4; 7, 2, 1]; [sequence, states] = hmmgenerate (25, transprob, outprob); [transprobest, outprobest] = hmmestimate (sequence, states, ... "pseudotransitions", pseudotransitions, ... "pseudoemissions", pseudoemissions) References Wendy L. Martinez and Angel R. Martinez. Computational Statistics Handbook with MATLAB . Appendix E, pages 547-557, Chapman & Hall/CRC, 2001. Lawrence R. Rabiner. A Tutorial on Hidden Markov Models and Selected Applications in Speech Recognition. Proceedings of the IEEE , 77(2), pages 257-286, February 1989. # name: # type: sq_string # elements: 1 # length: 57 Estimation of a hidden Markov model for a given sequence. # name: # type: sq_string # elements: 1 # length: 11 hmmgenerate # name: # type: sq_string # elements: 1 # length: 2091 statistics: [ sequence , states ] = hmmgenerate ( len , transprob , outprob ) statistics: […] = hmmgenerate (…, "symbols" , symbols ) statistics: […] = hmmgenerate (…, "statenames" , statenames ) Output sequence and hidden states of a hidden Markov model. Generate an output sequence and hidden states of a hidden Markov model. The model starts in state 1 at step 0 but will not include step 0 in the generated states and sequence. Arguments len is the number of steps to generate. sequence and states will have len entries each. transprob is the matrix of transition probabilities of the states. transprob(i, j) is the probability of a transition to state j given state i . outprob is the matrix of output probabilities. outprob(i, j) is the probability of generating output j given state i . Return values sequence is a vector of length len of the generated outputs. The outputs are integers ranging from 1 to columns (outprob) . states is a vector of length len of the generated hidden states. The states are integers ranging from 1 to columns (transprob) . If "symbols" is specified, then the elements of symbols are used for the output sequence instead of integers ranging from 1 to columns (outprob) . symbols can be a cell array. If "statenames" is specified, then the elements of statenames are used for the states instead of integers ranging from 1 to columns (transprob) . statenames can be a cell array. Examples transprob = [0.8, 0.2; 0.4, 0.6]; outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; [sequence, states] = hmmgenerate (25, transprob, outprob) symbols = {"A", "B", "C"}; statenames = {"One", "Two"}; [sequence, states] = hmmgenerate (25, transprob, outprob, ... "symbols", symbols, ... "statenames", statenames) References Wendy L. Martinez and Angel R. Martinez. Computational Statistics Handbook with MATLAB . Appendix E, pages 547-557, Chapman & Hall/CRC, 2001. Lawrence R. Rabiner. A Tutorial on Hidden Markov Models and Selected Applications in Speech Recognition. Proceedings of the IEEE , 77(2), pages 257-286, February 1989. # name: # type: sq_string # elements: 1 # length: 59 Output sequence and hidden states of a hidden Markov model. # name: # type: sq_string # elements: 1 # length: 8 hmmtrain # name: # type: sq_string # elements: 1 # length: 4335 statistics: [ esttr , estout ] = hmmtrain ( sequence , transguess , outguess ) statistics: […] = hmmtrain (…, "algorithm" , algorithm ) statistics: […] = hmmtrain (…, "symbols" , symbols ) statistics: […] = hmmtrain (…, "tolerance" , tol ) statistics: […] = hmmtrain (…, "maxiterations" , maxiter ) statistics: […] = hmmtrain (…, "pseudotransitions" , pseudotransitions ) statistics: […] = hmmtrain (…, "pseudoemissions" , pseudoemissions ) statistics: […] = hmmtrain (…, "verbose" , vflag ) Estimate the parameters of a hidden Markov model from emitted sequences. Given one or more observed output sequences and initial guesses for the transition and output probability matrices, hmmtrain finds maximum likelihood estimates of the two matrices using the Baum-Welch algorithm (the default) or Viterbi training. The model assumes that the generation starts in state 1 at step 0 but does not include step 0 in the sequence. Arguments sequence is a vector of a sequence of given outputs, or, for training from several sequences, a cell array of such vectors or a matrix whose rows are individual sequences. The outputs must be integers ranging from 1 to columns (outguess) . transguess is the initial guess for the matrix of transition probabilities. transguess(i, j) is the probability of a transition to state j given state i . outguess is the initial guess for the matrix of output probabilities. outguess(i, j) is the probability of generating output j given state i . Return values esttr is the estimated matrix of transition probabilities. estout is the estimated matrix of output probabilities. Name-Value pair arguments "algorithm" selects the training algorithm, either "BaumWelch" (default) or "Viterbi" . "BaumWelch" performs the standard forward-backward re-estimation and is recommended for most uses. "Viterbi" performs segmental (hard) re-estimation from the most likely state path of each sequence; it is faster but only approximates the maximum-likelihood estimate. "symbols" specifies the possible outputs. If given, sequence is expected to hold the elements of symbols instead of integers. symbols can be a cell array. "tolerance" is the convergence tolerance (default 1e-6 ). The algorithm terminates when the change in the log-likelihood and in both estimated matrices falls below tol . "maxiterations" is the maximum number of iterations (default 500 ). A warning is issued if the algorithm has not converged within this many iterations. "pseudotransitions" and "pseudoemissions" supply pseudo-count matrices for Viterbi training, used to keep transitions or outputs that are very unlikely to occur from collapsing to zero probability. "verbose" , when true, prints the log-likelihood and the change in the estimates at each iteration. Examples transprob = [0.8, 0.2; 0.4, 0.6]; outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; sequence = hmmgenerate (100, transprob, outprob); transguess = [0.6, 0.4; 0.5, 0.5]; outguess = [0.3, 0.3, 0.4; 0.5, 0.3, 0.2]; [esttr, estout] = hmmtrain (sequence, transguess, outguess); Two results of Viterbi training differ from MATLAB’s, deliberately. Given several sequences, the counts of every sequence are pooled and normalized once, so a sequence contributes in proportion to its length. MATLAB normalizes each sequence separately and averages the results, which weights an eight-symbol sequence as heavily as a twenty-four-symbol one and is not the maximum likelihood estimate. Its own Baum-Welch pools expected counts, as both algorithms do here. Given "pseudotransitions" or "pseudoemissions" , the pseudo-counts are added to the counted transitions and outputs, once per iteration, before the row is normalized. MATLAB does the same on its first iteration; from its second it adds them to an estimate that has already been normalized, so its iterate mixes counts with probabilities and is no longer a count matrix of any state path. References Wendy L. Martinez and Angel R. Martinez. Computational Statistics Handbook with MATLAB . Appendix E, pages 547-557, Chapman & Hall/CRC, 2001. Lawrence R. Rabiner. A Tutorial on Hidden Markov Models and Selected Applications in Speech Recognition. Proceedings of the IEEE , 77(2), pages 257-286, February 1989. # name: # type: sq_string # elements: 1 # length: 72 Estimate the parameters of a hidden Markov model from emitted sequences. # name: # type: sq_string # elements: 1 # length: 10 hmmviterbi # name: # type: sq_string # elements: 1 # length: 2201 statistics: vpath = hmmviterbi ( sequence , transprob , outprob ) statistics: vpath = hmmviterbi (…, "symbols" , symbols ) statistics: vpath = hmmviterbi (…, "statenames" , statenames ) Viterbi path of a hidden Markov model. Use the Viterbi algorithm to find the Viterbi path of a hidden Markov model given a sequence of outputs. The model assumes that the generation starts in state 1 at step 0 but does not include step 0 in the generated states and sequence. Arguments sequence is the vector of length len of given outputs. The outputs must be integers ranging from 1 to columns (outprob) . transprob is the matrix of transition probabilities of the states. transprob(i, j) is the probability of a transition to state j given state i . outprob is the matrix of output probabilities. outprob(i, j) is the probability of generating output j given state i . Return values vpath is the vector of the same length as sequence of the estimated hidden states. The states are integers ranging from 1 to columns (transprob) . If "symbols" is specified, then sequence is expected to be a sequence of the elements of symbols instead of integers ranging from 1 to columns (outprob) . symbols can be a cell array. If "statenames" is specified, then the elements of statenames are used for the states in vpath instead of integers ranging from 1 to columns (transprob) . statenames can be a cell array. Examples transprob = [0.8, 0.2; 0.4, 0.6]; outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; [sequence, states] = hmmgenerate (25, transprob, outprob); vpath = hmmviterbi (sequence, transprob, outprob); symbols = {"A", "B", "C"}; statenames = {"One", "Two"}; [sequence, states] = hmmgenerate (25, transprob, outprob, ... "symbols", symbols, "statenames", statenames); vpath = hmmviterbi (sequence, transprob, outprob, ... "symbols", symbols, "statenames", statenames); References Wendy L. Martinez and Angel R. Martinez. Computational Statistics Handbook with MATLAB . Appendix E, pages 547-557, Chapman & Hall/CRC, 2001. Lawrence R. Rabiner. A Tutorial on Hidden Markov Models and Selected Applications in Speech Recognition. Proceedings of the IEEE , 77(2), pages 257-286, February 1989. # name: # type: sq_string # elements: 1 # length: 38 Viterbi path of a hidden Markov model. statistics-release-1.9.2/inst/Markov_Models/hmmdecode.m000066400000000000000000000265071524624707500232030ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{pstates} =} hmmdecode (@var{sequence}, @var{transprob}, @var{outprob}) ## @deftypefnx {statistics} {[@var{pstates}, @var{logpseq}] =} hmmdecode (@dots{}) ## @deftypefnx {statistics} {[@var{pstates}, @var{logpseq}, @var{fs}, @var{bs}, @var{s}] =} hmmdecode (@dots{}) ## @deftypefnx {statistics} {[@dots{}] =} hmmdecode (@dots{}, @code{"symbols"}, @var{symbols}) ## ## Posterior state probabilities of a hidden Markov model. ## ## Calculate the posterior state probabilities of the sequence @var{sequence} ## from a hidden Markov model. The posterior state probabilities are the ## conditional probabilities of being in each state given the whole observed ## sequence. The model assumes that the generation starts in state @code{1} ## at step @code{0} but does not include step @code{0} in the sequence. ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{sequence} is a vector of length @var{len} of given outputs. The ## outputs must be integers ranging from @code{1} to @code{columns (outprob)}. ## ## @item ## @var{transprob} is the matrix of transition probabilities of the states. ## @code{transprob(i, j)} is the probability of a transition to state ## @code{j} given state @code{i}. ## ## @item ## @var{outprob} is the matrix of output probabilities. ## @code{outprob(i, j)} is the probability of generating output @code{j} ## given state @code{i}. ## @end itemize ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{pstates} is the matrix of posterior state probabilities. It has one ## row for each state and one column for each element of @var{sequence}. ## @code{pstates(i, j)} is the conditional probability that the model is in ## state @code{i} when it generates the @code{j}-th output of @var{sequence}, ## given that @var{sequence} is emitted. ## ## @item ## @var{logpseq} is the logarithm of the probability of the sequence ## @var{sequence}. ## ## @item ## @var{fs} and @var{bs} are the scaled forward and backward probabilities, ## respectively, and @var{s} is the vector of scale factors used to keep the ## computation numerically stable. ## @end itemize ## ## If @code{"symbols"} is specified, then @var{sequence} is expected to be a ## sequence of the elements of @var{symbols} instead of integers ranging from ## @code{1} to @code{columns (outprob)}. @var{symbols} can be a cell array. ## ## @subheading Examples ## ## @example ## @group ## transprob = [0.8, 0.2; 0.4, 0.6]; ## outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; ## [sequence, states] = hmmgenerate (25, transprob, outprob); ## pstates = hmmdecode (sequence, transprob, outprob); ## @end group ## ## @group ## symbols = @{"A", "B", "C"@}; ## [sequence, states] = hmmgenerate (25, transprob, outprob, ... ## "symbols", symbols); ## pstates = hmmdecode (sequence, transprob, outprob, "symbols", symbols); ## @end group ## @end example ## ## @subheading References ## ## @enumerate ## @item ## Wendy L. Martinez and Angel R. Martinez. @cite{Computational Statistics ## Handbook with MATLAB}. Appendix E, pages 547-557, Chapman & Hall/CRC, ## 2001. ## ## @item ## Lawrence R. Rabiner. A Tutorial on Hidden Markov Models and Selected ## Applications in Speech Recognition. @cite{Proceedings of the IEEE}, ## 77(2), pages 257-286, February 1989. ## @end enumerate ## @end deftypefn function [pstates, logpseq, fs, bs, s] = hmmdecode (sequence, transprob, ... outprob, varargin) # Check arguments if (nargin < 3 || mod (numel (varargin), 2) != 0) print_usage (); endif if (! ismatrix (transprob)) error ("hmmdecode: transprob must be a non-empty numeric matrix."); endif if (! ismatrix (outprob)) error ("hmmdecode: outprob must be a non-empty numeric matrix."); endif # nstate is the number of states of the hidden Markov model nstate = rows (transprob); # noutput is the number of different outputs that the hidden Markov model # can generate noutput = columns (outprob); # Check whether transprob and outprob are feasible for a hidden Markov model if (columns (transprob) != nstate) error ("hmmdecode: transprob must be a square matrix."); endif if (rows (outprob) != nstate) error (strcat ("hmmdecode: outprob must have the same number of", ... " rows as transprob.")); endif # Flag for symbols usesym = false; # Process varargin for i = 1:2:numel (varargin) # There must be an identifier: 'symbols' if (! ischar (varargin{i})) print_usage (); endif # Upper case is also fine lowerarg = lower (varargin{i}); if (strcmp (lowerarg, 'symbols')) if (numel (varargin{i + 1}) != noutput) error (strcat ("hmmdecode: number of symbols does not match", ... " number of possible outputs.")); endif usesym = true; # Use the following argument as symbols symbols = varargin{i + 1}; else error (strcat ("hmmdecode: expected 'symbols'", ... sprintf (" but found '%s'.", varargin{i}))); endif endfor len = numel (sequence); # Transform sequence from symbols to integers if necessary if (usesym) # sequenceint is used to build the transformed sequence sequenceint = zeros (1, len); for i = 1:noutput # Search for symbols(i) in the sequence; isequal will have 1 at # corresponding indices; i is the right integer for that symbol isequal = ismember (sequence, symbols(i)); # We do not want to change sequenceint if the symbol appears a second # time in symbols if (any ((sequenceint == 0) & (isequal == 1))) isequal *= i; sequenceint += isequal; endif endfor if (! all (sequenceint) && len > 0) index = max ((sequenceint == 0) .* (1:len)); error (strcat ("hmmdecode: sequence(", int2str (index), ... ") not in symbols.")); endif sequence = sequenceint; else if (! isvector (sequence) && ! isempty (sequence)) error ("hmmdecode: sequence must be a vector."); endif if (! all (ismember (sequence, 1:noutput))) index = max ((ismember (sequence, 1:noutput) == 0) .* (1:len)); error (strcat ("hmmdecode: sequence(", int2str (index), ... ") out of range.")); endif endif # Each row in transprob and outprob should contain probabilities # => scale so that the sum is 1. A zero row remains zero. # - for transprob ts = sum (transprob, 2); ts(ts == 0) = 1; transprob = transprob ./ ts; # - for outprob os = sum (outprob, 2); os(os == 0) = 1; outprob = outprob ./ os; # Prepend a dummy output so that the forward and backward recursions have a # clean starting column representing the initial state 1 at step 0. The # dummy column is stripped from PSTATES before returning. seq = [noutput + 1, sequence(:)']; L = len + 1; # Scaled forward probabilities. Column 1 holds the initial distribution: # the model starts in state 1 with probability 1. fs = zeros (nstate, L); fs(1, 1) = 1; s = ones (1, L); for count = 2:L fs(:, count) = outprob(:, seq(count)) .* (transprob' * fs(:, count - 1)); # The scale factor normalizes each forward column to sum to 1 s(count) = sum (fs(:, count)); fs(:, count) = fs(:, count) ./ s(count); endfor # Scaled backward probabilities using the same scale factors bs = ones (nstate, L); for count = L - 1:-1:1 bs(:, count) = (transprob * (bs(:, count + 1) .* ... outprob(:, seq(count + 1)))) ./ s(count + 1); endfor # The log probability of the sequence is the sum of the log scale factors logpseq = sum (log (s)); # Posterior state probabilities; strip the dummy starting column pstates = fs .* bs; pstates(:, 1) = []; endfunction %!demo %! ## Posterior probability of each state at every step of an observed sequence. %! %! transprob = [0.95, 0.05; 0.10, 0.90]; %! outprob = [1/6, 1/6, 1/6, 1/6, 1/6, 1/6; 1/10, 1/10, 1/10, 1/10, 1/10, 1/2]; %! sequence = hmmgenerate (10, transprob, outprob); %! [pstates, logpseq] = hmmdecode (sequence, transprob, outprob) %!test %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! sequence = [1, 2, 1, 1, 1, 2, 2, 1, 2, 3]; %! pstates = hmmdecode (sequence, transprob, outprob); %! assert_equal (size (pstates), [2, 10]); %! assert_equal (all (abs (sum (pstates, 1) - 1) < 1e-10), true); %! assert_equal (all (pstates(:) >= 0 & pstates(:) <= 1), true); %!test %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! sequence = [1, 2, 1, 1, 1, 2, 2, 1, 2, 3]; %! [pstates, logpseq] = hmmdecode (sequence, transprob, outprob); %! ## Independent brute-force forward algorithm for the log probability %! nstate = 2; %! alpha = transprob(1, :) .* outprob(:, sequence(1))'; %! for t = 2:numel (sequence) %! alpha = (alpha * transprob) .* outprob(:, sequence(t))'; %! endfor %! assert_equal (logpseq, log (sum (alpha)), 1e-10); %!test %! ## Symbols form must match the integer form %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! sequence = [1, 2, 1, 1, 1, 2, 2, 1, 2, 3]; %! symbseq = {'A', 'B', 'A', 'A', 'A', 'B', 'B', 'A', 'B', 'C'}; %! p1 = hmmdecode (sequence, transprob, outprob); %! p2 = hmmdecode (symbseq, transprob, outprob, 'symbols', {'A', 'B', 'C'}); %! assert_equal (p1, p2, 1e-12); %!test %! ## Scaled forward/backward reproduce the posterior gamma %! transprob = [0.9, 0.1; 0.3, 0.7]; %! outprob = [0.5, 0.5; 0.1, 0.9]; %! sequence = [1, 2, 2, 1, 2]; %! [pstates, logpseq, fs, bs, s] = hmmdecode (sequence, transprob, outprob); %! recovered = fs .* bs; %! recovered(:, 1) = []; %! assert_equal (recovered, pstates, 1e-12); %! assert_equal (logpseq, sum (log (s)), 1e-12); %!test %! ## Empty sequence: no columns in the posterior, unit probability %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! [pstates, logpseq] = hmmdecode ([], transprob, outprob); %! assert_equal (size (pstates), [2, 0]); %! assert_equal (logpseq, 0, 1e-12); %!error hmmdecode ([1, 2]) %!error ... %! hmmdecode ([1, 2], [0.8, 0.2; 0.4, 0.6; 0.1, 0.9], [0.5, 0.5; 0.1, 0.9]) %!error ... %! hmmdecode ([1, 2], [0.8, 0.2; 0.4, 0.6], [0.5, 0.5]) %!error ... %! hmmdecode ([1, 5], [0.8, 0.2; 0.4, 0.6], [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]) %!error ... %! hmmdecode ([1, 2], [0.8, 0.2; 0.4, 0.6], [0.2, 0.4, 0.4; 0.7, 0.2, 0.1], ... %! 'symbols', {'A', 'B'}) statistics-release-1.9.2/inst/Markov_Models/hmmestimate.m000066400000000000000000000355321524624707500235710ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{transprobest}, @var{outprobest}] =} hmmestimate (@var{sequence}, @var{states}) ## @deftypefnx {statistics} {[@dots{}] =} hmmestimate (@dots{}, @code{"statenames"}, @var{statenames}) ## @deftypefnx {statistics} {[@dots{}] =} hmmestimate (@dots{}, @code{"symbols"}, @var{symbols}) ## @deftypefnx {statistics} {[@dots{}] =} hmmestimate (@dots{}, @code{"pseudotransitions"}, @var{pseudotransitions}) ## @deftypefnx {statistics} {[@dots{}] =} hmmestimate (@dots{}, @code{"pseudoemissions"}, @var{pseudoemissions}) ## ## Estimation of a hidden Markov model for a given sequence. ## ## Estimate the matrix of transition probabilities and the matrix of output ## probabilities of a given sequence of outputs and states generated by a ## hidden Markov model. The transition probabilities are estimated by counting ## the transitions that actually occur between consecutive states in ## @var{states}; the output probabilities are estimated from the outputs ## emitted by each state. ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{sequence} is a vector of a sequence of given outputs. The outputs ## must be integers ranging from @code{1} to the number of outputs of the ## hidden Markov model. ## ## @item ## @var{states} is a vector of the same length as @var{sequence} of given ## states. The states must be integers ranging from @code{1} to the number ## of states of the hidden Markov model. ## @end itemize ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{transprobest} is the matrix of the estimated transition ## probabilities of the states. @code{transprobest(i, j)} is the estimated ## probability of a transition to state @code{j} given state @code{i}. ## ## @item ## @var{outprobest} is the matrix of the estimated output probabilities. ## @code{outprobest(i, j)} is the estimated probability of generating ## output @code{j} given state @code{i}. ## @end itemize ## ## If @code{'symbols'} is specified, then @var{sequence} is expected to be a ## sequence of the elements of @var{symbols} instead of integers. ## @var{symbols} can be a cell array. ## ## If @code{'statenames'} is specified, then @var{states} is expected to be ## a sequence of the elements of @var{statenames} instead of integers. ## @var{statenames} can be a cell array. ## ## If @code{'pseudotransitions'} is specified then the integer matrix ## @var{pseudotransitions} is used as an initial number of counted ## transitions. @code{pseudotransitions(i, j)} is the initial number of ## counted transitions from state @code{i} to state @code{j}. ## @var{transprobest} will have the same size as @var{pseudotransitions}. ## Use this if you have transitions that are very unlikely to occur. ## ## If @code{'pseudoemissions'} is specified then the integer matrix ## @var{pseudoemissions} is used as an initial number of counted outputs. ## @code{pseudoemissions(i, j)} is the initial number of counted outputs ## @code{j} given state @code{i}. If @code{'pseudoemissions'} is also ## specified then the number of rows of @var{pseudoemissions} must be the ## same as the number of rows of @var{pseudotransitions}. @var{outprobest} ## will have the same size as @var{pseudoemissions}. Use this if you have ## outputs or states that are very unlikely to occur. ## ## @subheading Examples ## ## @example ## @group ## transprob = [0.8, 0.2; 0.4, 0.6]; ## outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; ## [sequence, states] = hmmgenerate (25, transprob, outprob); ## [transprobest, outprobest] = hmmestimate (sequence, states) ## @end group ## ## @group ## symbols = @{"A", "B", "C"@}; ## statenames = @{"One", "Two"@}; ## [sequence, states] = hmmgenerate (25, transprob, outprob, ... ## "symbols", symbols, ... ## "statenames", statenames); ## [transprobest, outprobest] = hmmestimate (sequence, states, ... ## "symbols', symbols, ... ## "statenames', statenames) ## @end group ## ## @group ## pseudotransitions = [8, 2; 4, 6]; ## pseudoemissions = [2, 4, 4; 7, 2, 1]; ## [sequence, states] = hmmgenerate (25, transprob, outprob); ## [transprobest, outprobest] = hmmestimate (sequence, states, ... ## "pseudotransitions", pseudotransitions, ... ## "pseudoemissions", pseudoemissions) ## @end group ## @end example ## ## @subheading References ## ## @enumerate ## @item ## Wendy L. Martinez and Angel R. Martinez. @cite{Computational Statistics ## Handbook with MATLAB}. Appendix E, pages 547-557, Chapman & Hall/CRC, ## 2001. ## ## @item ## Lawrence R. Rabiner. A Tutorial on Hidden Markov Models and Selected ## Applications in Speech Recognition. @cite{Proceedings of the IEEE}, ## 77(2), pages 257-286, February 1989. ## @end enumerate ## @end deftypefn function [transprobest, outprobest] = hmmestimate (sequence, states, varargin) # Check arguments if (nargin < 2 || mod (length (varargin), 2) != 0) print_usage (); endif len = length (sequence); if (length (states) != len) error ("hmmestimate: sequence and states must have equal length"); endif # Flag for symbols usesym = false; # Flag for statenames usesn = false; # Variables for return values transprobest = []; outprobest = []; # Process varargin for i = 1:2:length (varargin) # There must be an identifier: 'symbols', 'statenames', # 'pseudotransitions' or 'pseudoemissions' if (! ischar (varargin{i})) print_usage (); endif # Upper case is also fine lowerarg = lower (varargin{i}); if (strcmp (lowerarg, 'symbols')) usesym = true; # Use the following argument as symbols symbols = varargin{i + 1}; # The same for statenames elseif (strcmp (lowerarg, 'statenames')) usesn = true; # Use the following argument as statenames statenames = varargin{i + 1}; elseif (strcmp (lowerarg, 'pseudotransitions')) # Use the following argument as an initial count for transitions transprobest = varargin{i + 1}; if (! ismatrix (transprobest)) error (strcat ("hmmestimate: pseudotransitions must be a", ... " non-empty numeric matrix")); endif if (rows (transprobest) != columns (transprobest)) error ("hmmestimate: pseudotransitions must be a square matrix"); endif elseif (strcmp (lowerarg, 'pseudoemissions')) # Use the following argument as an initial count for outputs outprobest = varargin{i + 1}; if (! ismatrix (outprobest)) error (strcat ("hmmestimate: pseudoemissions must be a non-empty", ... " numeric matrix")); endif else error (strcat ("hmmestimate: expected 'symbols', 'statenames',", ... " 'pseudotransitions' or 'pseudoemissions' but", ... sprintf (" found '%s'", varargin{i}))); endif endfor # Transform sequence from symbols to integers if necessary if (usesym) # sequenceint is used to build the transformed sequence sequenceint = zeros (1, len); for i = 1:length (symbols) # Search for symbols(i) in the sequence, isequal will have 1 at # corresponding indices; i is the right integer for that symbol isequal = ismember (sequence, symbols(i)); # We do not want to change sequenceint if the symbol appears a second # time in symbols if (any ((sequenceint == 0) & (isequal == 1))) isequal *= i; sequenceint += isequal; endif endfor if (! all (sequenceint)) index = max ((sequenceint == 0) .* (1:len)); error (strcat ("hmmestimate: sequence(", int2str (index), ... ") not in symbols")); endif sequence = sequenceint; else if (! isvector (sequence)) error ("hmmestimate: sequence must be a non-empty vector"); endif if (! all (ismember (sequence, 1:max (sequence)))) index = max ((ismember (sequence, 1:max (sequence)) == 0) .* (1:len)); error (strcat ("hmmestimate: sequence(", int2str (index), ... ") not feasible")); endif endif # Transform states from statenames to integers if necessary if (usesn) # statesint is used to build the transformed states statesint = zeros (1, len); for i = 1:length (statenames) # Search for statenames(i) in states, isequal will have 1 at # corresponding indices; i is the right integer for that statename isequal = ismember (states, statenames(i)); # We do not want to change statesint if the statename appears a second # time in statenames if (any ((statesint == 0) & (isequal == 1))) isequal *= i; statesint += isequal; endif endfor if (! all (statesint)) index = max ((statesint == 0) .* (1:len)); error (strcat ("hmmestimate: states(", int2str (index), ... ") not in statenames")); endif states = statesint; else if (! isvector (states)) error ("hmmestimate: states must be a non-empty vector"); endif if (! all (ismember (states, 1:max (states)))) index = max ((ismember (states, 1:max (states)) == 0) .* (1:len)); error (strcat ("hmmestimate: states(", int2str (index), ... ") not feasible")); endif endif # Estimate the number of different states as the max of states nstate = max (states); # Estimate the number of different outputs as the max of sequence noutput = max (sequence); # transprobest is empty if pseudotransitions is not specified if (isempty (transprobest)) # outprobest is not empty if pseudoemissions is specified if (! isempty (outprobest)) if (nstate > rows (outprobest)) error ("hmmestimate: not enough rows in pseudoemissions"); endif # The number of states is specified by pseudoemissions nstate = rows (outprobest); endif transprobest = zeros (nstate, nstate); else if (nstate > rows (transprobest)) error ("hmmestimate: not enough rows in pseudotransitions"); endif # The number of states is given by pseudotransitions nstate = rows (transprobest); endif # outprobest is empty if pseudoemissions is not specified if (isempty (outprobest)) outprobest = zeros (nstate, noutput); else if (noutput > columns (outprobest)) error ("hmmestimate: not enough columns in pseudoemissions"); endif # Number of outputs is specified by pseudoemissions noutput = columns (outprobest); if (rows (outprobest) != nstate) error (strcat ("hmmestimate: pseudoemissions must have the same", ... " number of rows as pseudotransitions")); endif endif # Count the observed transitions between consecutive states and the outputs # emitted from each state. Only transitions that actually occur within the # given state sequence are counted; no transition out of an assumed initial # state is added (matching MATLAB). for i = 1:len # Count the number of outputs for each state output pair outprobest(states(i), sequence(i)) ++; # Count the number of transitions for each consecutive state pair if (i < len) transprobest(states(i), states(i + 1)) ++; endif endfor # transprobest and outprobest contain counted numbers # Each row in transprobest and outprobest should contain estimated # probabilities # => scale so that the sum is 1 # A zero row remains zero # - for transprobest s = sum (transprobest, 2); s(s == 0) = 1; transprobest = transprobest ./ (s * ones (1, nstate)); # - for outprobest s = sum (outprobest, 2); s(s == 0) = 1; outprobest = outprobest ./ (s * ones (1, noutput)); endfunction %!demo %! ## Recover the transition and output matrices of a model from a long %! ## sequence together with its known sequence of hidden states. %! %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! [sequence, states] = hmmgenerate (1000, transprob, outprob); %! [transest, outest] = hmmestimate (sequence, states) %!test %! sequence = [1, 2, 1, 1, 1, 2, 2, 1, 2, 3, 3, ... %! 3, 3, 2, 3, 1, 1, 1, 1, 3, 3, 2, 3, 1, 3]; %! states = [1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, ... %! 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1]; %! [transprobest, outprobest] = hmmestimate (sequence, states); %! expectedtransprob = [0.88235, 0.11765; 0.28571, 0.71429]; %! expectedoutprob = [0.16667, 0.33333, 0.50000; 1.00000, 0.00000, 0.00000]; %! assert_equal (transprobest, expectedtransprob, 0.001); %! assert_equal (outprobest, expectedoutprob, 0.001); %!test %! sequence = {'A', 'B', 'A', 'A', 'A', 'B', 'B', 'A', 'B', 'C', 'C', 'C', ... %! 'C', 'B', 'C', 'A', 'A', 'A', 'A', 'C', 'C', 'B', 'C', 'A', 'C'}; %! states = {'One', 'One', 'Two', 'Two', 'Two', 'One', 'One', 'One', 'One', ... %! 'One', 'One', 'One', 'One', 'One', 'One', 'Two', 'Two', 'Two', ... %! 'Two', 'One', 'One', 'One', 'One', 'One', 'One'}; %! symbols = {'A', 'B', 'C'}; %! statenames = {'One', 'Two'}; %! [transprobest, outprobest] = hmmestimate (sequence, states, 'symbols', ... %! symbols, 'statenames', statenames); %! expectedtransprob = [0.88235, 0.11765; 0.28571, 0.71429]; %! expectedoutprob = [0.16667, 0.33333, 0.50000; 1.00000, 0.00000, 0.00000]; %! assert_equal (transprobest, expectedtransprob, 0.001); %! assert_equal (outprobest, expectedoutprob, 0.001); %!test %! sequence = [1, 2, 1, 1, 1, 2, 2, 1, 2, 3, 3, 3, ... %! 3, 2, 3, 1, 1, 1, 1, 3, 3, 2, 3, 1, 3]; %! states = [1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, ... %! 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1]; %! pseudotransitions = [8, 2; 4, 6]; %! pseudoemissions = [2, 4, 4; 7, 2, 1]; %! [transprobest, outprobest] = hmmestimate (sequence, states, ... %! 'pseudotransitions', pseudotransitions, 'pseudoemissions', pseudoemissions); %! expectedtransprob = [0.851852, 0.148148; 0.352941, 0.647059]; %! expectedoutprob = [0.178571, 0.357143, 0.464286; ... %! 0.823529, 0.117647, 0.058824]; %! assert_equal (transprobest, expectedtransprob, 0.001); %! assert_equal (outprobest, expectedoutprob, 0.001); statistics-release-1.9.2/inst/Markov_Models/hmmgenerate.m000066400000000000000000000227121524624707500235440ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{sequence}, @var{states}] =} hmmgenerate (@var{len}, @var{transprob}, @var{outprob}) ## @deftypefnx {statistics} {[@dots{}] =} hmmgenerate (@dots{}, @code{"symbols"}, @var{symbols}) ## @deftypefnx {statistics} {[@dots{}] =} hmmgenerate (@dots{}, @code{"statenames"}, @var{statenames}) ## ## Output sequence and hidden states of a hidden Markov model. ## ## Generate an output sequence and hidden states of a hidden Markov model. ## The model starts in state @code{1} at step @code{0} but will not include ## step @code{0} in the generated states and sequence. ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{len} is the number of steps to generate. @var{sequence} and ## @var{states} will have @var{len} entries each. ## ## @item ## @var{transprob} is the matrix of transition probabilities of the states. ## @code{transprob(i, j)} is the probability of a transition to state ## @code{j} given state @code{i}. ## ## @item ## @var{outprob} is the matrix of output probabilities. ## @code{outprob(i, j)} is the probability of generating output @code{j} ## given state @code{i}. ## @end itemize ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{sequence} is a vector of length @var{len} of the generated ## outputs. The outputs are integers ranging from @code{1} to ## @code{columns (outprob)}. ## ## @item ## @var{states} is a vector of length @var{len} of the generated hidden ## states. The states are integers ranging from @code{1} to ## @code{columns (transprob)}. ## @end itemize ## ## If @code{"symbols"} is specified, then the elements of @var{symbols} are ## used for the output sequence instead of integers ranging from @code{1} to ## @code{columns (outprob)}. @var{symbols} can be a cell array. ## ## If @code{"statenames"} is specified, then the elements of ## @var{statenames} are used for the states instead of integers ranging from ## @code{1} to @code{columns (transprob)}. @var{statenames} can be a cell ## array. ## ## @subheading Examples ## ## @example ## @group ## transprob = [0.8, 0.2; 0.4, 0.6]; ## outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; ## [sequence, states] = hmmgenerate (25, transprob, outprob) ## @end group ## ## @group ## symbols = @{"A", "B", "C"@}; ## statenames = @{"One", "Two"@}; ## [sequence, states] = hmmgenerate (25, transprob, outprob, ... ## "symbols", symbols, ... ## "statenames", statenames) ## @end group ## @end example ## ## @subheading References ## ## @enumerate ## @item ## Wendy L. Martinez and Angel R. Martinez. @cite{Computational Statistics ## Handbook with MATLAB}. Appendix E, pages 547-557, Chapman & Hall/CRC, ## 2001. ## ## @item ## Lawrence R. Rabiner. A Tutorial on Hidden Markov Models and Selected ## Applications in Speech Recognition. @cite{Proceedings of the IEEE}, ## 77(2), pages 257-286, February 1989. ## @end enumerate ## @end deftypefn function [sequence, states] = hmmgenerate (len, transprob, outprob, varargin) # Check arguments if (nargin < 3 || mod (length (varargin), 2) != 0) print_usage (); endif if (! isscalar (len) || len < 0 || round (len) != len) error ("hmmgenerate: len must be a non-negative scalar integer.") endif if (! ismatrix (transprob)) error ("hmmgenerate: transprob must be a non-empty numeric matrix."); endif if (! ismatrix (outprob)) error ("hmmgenerate: outprob must be a non-empty numeric matrix."); endif # nstate is the number of states of the hidden Markov model nstate = rows (transprob); # noutput is the number of different outputs that the hidden Markov model # can generate noutput = columns (outprob); # Check whether transprob and outprob are feasible for a hidden Markov # model if (columns (transprob) != nstate) error ("hmmgenerate: transprob must be a square matrix."); endif if (rows (outprob) != nstate) error (strcat ("hmmgenerate: outprob must have the same number", ... " of rows as transprob.")); endif # Flag for symbols usesym = false; # Flag for statenames usesn = false; # Process varargin for i = 1:2:length (varargin) # There must be an identifier: 'symbols' or 'statenames' if (! ischar (varargin{i})) print_usage (); endif # Upper case is also fine lowerarg = lower (varargin{i}); if (strcmp (lowerarg, 'symbols')) if (length (varargin{i + 1}) != noutput) error (strcat ("hmmgenerate: number of symbols does not match", ... " number of possible outputs.")); endif usesym = true; # Use the following argument as symbols symbols = varargin{i + 1}; # The same for statenames elseif (strcmp (lowerarg, 'statenames')) if (length (varargin{i + 1}) != nstate) error (strcat ("hmmgenerate: number of statenames does not", ... " match number of states.")); endif usesn = true; # Use the following argument as statenames statenames = varargin{i + 1}; else error (strcat ("hmmgenerate: expected 'symbols' or 'statenames'", ... sprintf (" but found '%s'.", varargin{i}))); endif endfor # Each row in transprob and outprob should contain probabilities # => scale so that the sum is 1 # A zero row remains zero # - for transprob s = sum (transprob, 2); s(s == 0) = 1; transprob = transprob ./ repmat (s, 1, nstate); # - for outprob s = sum (outprob, 2); s(s == 0) = 1; outprob = outprob ./ repmat (s, 1, noutput); # Generate sequences of uniformly distributed random numbers between 0 and 1 # - for the state transitions transdraw = rand (1, len); # - for the outputs outdraw = rand (1, len); # Generate the return vectors # They remain unchanged if the according probability row of transprob # and outprob contain, respectively, only zeros sequence = ones (1, len); states = ones (1, len); if (len > 0) # Calculate cumulated probabilities backwards for easy comparison with # the generated random numbers # Cumulated probability in first column must always be 1 # We might have a zero row # - for transprob transprob(:, end:-1:1) = cumsum (transprob(:, end:-1:1), 2); transprob(:, 1) = 1; # - for outprob outprob(:, end:-1:1) = cumsum (outprob(:, end:-1:1), 2); outprob(:, 1) = 1; # cstate is the current state # Start in state 1 but do not include it in the states vector cstate = 1; for i = 1:len # Compare the random number i of transdraw to the cumulated # probability of the state transition and set the transition # accordingly states(i) = sum (transdraw(i) <= transprob(cstate, :)); cstate = states(i); endfor # Compare the random numbers of outdraw to the cumulated probabilities # of the outputs and set the sequence vector accordingly sequence = sum (repmat (outdraw, noutput, 1) <= outprob(states, :)', 1); # Transform default matrices into symbols/statenames if requested if (usesym) sequence = reshape (symbols(sequence), 1, len); endif if (usesn) states = reshape (statenames(states), 1, len); endif endif endfunction %!demo %! ## Generate an output sequence and its hidden states from a two-state model %! ## (a fair and a loaded die). %! %! transprob = [0.95, 0.05; 0.10, 0.90]; %! outprob = [1/6, 1/6, 1/6, 1/6, 1/6, 1/6; 1/10, 1/10, 1/10, 1/10, 1/10, 1/2]; %! [sequence, states] = hmmgenerate (15, transprob, outprob, ... %! "statenames", {"fair", "loaded"}) %!test %! len = 25; %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! [sequence, states] = hmmgenerate (len, transprob, outprob); %! assert_equal (length (sequence), len); %! assert_equal (length (states), len); %! assert_equal (all ((min (sequence) >= 1)(:)), true); %! assert_equal (all ((max (sequence) <= columns (outprob))(:)), true); %! assert_equal (all ((min (states) >= 1)(:)), true); %! assert_equal (all ((max (states) <= rows (transprob))(:)), true); %!test %! len = 25; %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! symbols = {'A', 'B', 'C'}; %! statenames = {'One', 'Two'}; %! [sequence, states] = hmmgenerate (len, transprob, outprob, ... %! 'symbols', symbols, 'statenames', statenames); %! assert_equal (length (sequence), len); %! assert_equal (length (states), len); %! assert_equal (all ((strcmp (sequence, 'A') + strcmp (sequence, 'B') + ... %! strcmp (sequence, 'C') == ones (1, len))(:)), true); %! assert_equal (all ((strcmp (states, 'One') + strcmp (states, 'Two') == ones (1, len))(:)), true); statistics-release-1.9.2/inst/Markov_Models/hmmtrain.m000066400000000000000000000535551524624707500231000ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{esttr}, @var{estout}] =} hmmtrain (@var{sequence}, @var{transguess}, @var{outguess}) ## @deftypefnx {statistics} {[@dots{}] =} hmmtrain (@dots{}, @code{"algorithm"}, @var{algorithm}) ## @deftypefnx {statistics} {[@dots{}] =} hmmtrain (@dots{}, @code{"symbols"}, @var{symbols}) ## @deftypefnx {statistics} {[@dots{}] =} hmmtrain (@dots{}, @code{"tolerance"}, @var{tol}) ## @deftypefnx {statistics} {[@dots{}] =} hmmtrain (@dots{}, @code{"maxiterations"}, @var{maxiter}) ## @deftypefnx {statistics} {[@dots{}] =} hmmtrain (@dots{}, @code{"pseudotransitions"}, @var{pseudotransitions}) ## @deftypefnx {statistics} {[@dots{}] =} hmmtrain (@dots{}, @code{"pseudoemissions"}, @var{pseudoemissions}) ## @deftypefnx {statistics} {[@dots{}] =} hmmtrain (@dots{}, @code{"verbose"}, @var{vflag}) ## ## Estimate the parameters of a hidden Markov model from emitted sequences. ## ## Given one or more observed output sequences and initial guesses for the ## transition and output probability matrices, @code{hmmtrain} finds maximum ## likelihood estimates of the two matrices using the Baum-Welch algorithm ## (the default) or Viterbi training. The model assumes that the generation ## starts in state @code{1} at step @code{0} but does not include step ## @code{0} in the sequence. ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{sequence} is a vector of a sequence of given outputs, or, for training ## from several sequences, a cell array of such vectors or a matrix whose rows ## are individual sequences. The outputs must be integers ranging from ## @code{1} to @code{columns (outguess)}. ## ## @item ## @var{transguess} is the initial guess for the matrix of transition ## probabilities. @code{transguess(i, j)} is the probability of a transition ## to state @code{j} given state @code{i}. ## ## @item ## @var{outguess} is the initial guess for the matrix of output probabilities. ## @code{outguess(i, j)} is the probability of generating output @code{j} ## given state @code{i}. ## @end itemize ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{esttr} is the estimated matrix of transition probabilities. ## ## @item ## @var{estout} is the estimated matrix of output probabilities. ## @end itemize ## ## @subheading Name-Value pair arguments ## ## @itemize @bullet ## @item ## @code{"algorithm"} selects the training algorithm, either ## @code{"BaumWelch"} (default) or @code{"Viterbi"}. @code{"BaumWelch"} ## performs the standard forward-backward re-estimation and is recommended for ## most uses. @code{"Viterbi"} performs segmental (hard) re-estimation from ## the most likely state path of each sequence; it is faster but only ## approximates the maximum-likelihood estimate. ## ## @item ## @code{"symbols"} specifies the possible outputs. If given, @var{sequence} ## is expected to hold the elements of @var{symbols} instead of integers. ## @var{symbols} can be a cell array. ## ## @item ## @code{"tolerance"} is the convergence tolerance (default @code{1e-6}). The ## algorithm terminates when the change in the log-likelihood and in both ## estimated matrices falls below @var{tol}. ## ## @item ## @code{"maxiterations"} is the maximum number of iterations (default ## @code{500}). A warning is issued if the algorithm has not converged within ## this many iterations. ## ## @item ## @code{"pseudotransitions"} and @code{"pseudoemissions"} supply pseudo-count ## matrices for Viterbi training, used to keep transitions or outputs that are ## very unlikely to occur from collapsing to zero probability. ## ## @item ## @code{"verbose"}, when true, prints the log-likelihood and the change in the ## estimates at each iteration. ## @end itemize ## ## @subheading Examples ## ## @example ## @group ## transprob = [0.8, 0.2; 0.4, 0.6]; ## outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; ## sequence = hmmgenerate (100, transprob, outprob); ## transguess = [0.6, 0.4; 0.5, 0.5]; ## outguess = [0.3, 0.3, 0.4; 0.5, 0.3, 0.2]; ## [esttr, estout] = hmmtrain (sequence, transguess, outguess); ## @end group ## @end example ## ## Two results of Viterbi training differ from MATLAB's, deliberately. ## ## Given several sequences, the counts of every sequence are pooled and ## normalized once, so a sequence contributes in proportion to its length. ## MATLAB normalizes each sequence separately and averages the results, which ## weights an eight-symbol sequence as heavily as a twenty-four-symbol one and ## is not the maximum likelihood estimate. Its own Baum-Welch pools expected ## counts, as both algorithms do here. ## ## Given @code{"pseudotransitions"} or @code{"pseudoemissions"}, the ## pseudo-counts are added to the counted transitions and outputs, once per ## iteration, before the row is normalized. MATLAB does the same on its first ## iteration; from its second it adds them to an estimate that has already been ## normalized, so its iterate mixes counts with probabilities and is no longer ## a count matrix of any state path. ## ## @subheading References ## ## @enumerate ## @item ## Wendy L. Martinez and Angel R. Martinez. @cite{Computational Statistics ## Handbook with MATLAB}. Appendix E, pages 547-557, Chapman & Hall/CRC, ## 2001. ## ## @item ## Lawrence R. Rabiner. A Tutorial on Hidden Markov Models and Selected ## Applications in Speech Recognition. @cite{Proceedings of the IEEE}, ## 77(2), pages 257-286, February 1989. ## @end enumerate ## @end deftypefn function [esttr, estout] = hmmtrain (sequence, transguess, outguess, varargin) # Check arguments if (nargin < 3 || mod (numel (varargin), 2) != 0) print_usage (); endif if (! ismatrix (transguess)) error ("hmmtrain: transguess must be a non-empty numeric matrix."); endif if (! ismatrix (outguess)) error ("hmmtrain: outguess must be a non-empty numeric matrix."); endif # nstate is the number of states of the hidden Markov model nstate = rows (transguess); # noutput is the number of different outputs that the hidden Markov model # can generate noutput = columns (outguess); # Check whether transguess and outguess are feasible for a hidden Markov model if (columns (transguess) != nstate) error ("hmmtrain: transguess must be a square matrix."); endif if (rows (outguess) != nstate) error (strcat ("hmmtrain: outguess must have the same number of", ... " rows as transguess.")); endif # Defaults algorithm = 'baumwelch'; usesym = false; tol = 1e-6; maxiter = 500; verbose = false; pseudotr = []; pseudoout = []; # Process varargin for i = 1:2:numel (varargin) if (! ischar (varargin{i})) print_usage (); endif lowerarg = lower (varargin{i}); if (strcmp (lowerarg, 'algorithm')) if (! ischar (varargin{i + 1})) error ("hmmtrain: algorithm must be a string."); endif algorithm = lower (varargin{i + 1}); if (! any (strcmp (algorithm, {'baumwelch', 'viterbi'}))) error (strcat ("hmmtrain: expected 'BaumWelch' or 'Viterbi'", ... sprintf (" but found '%s'.", varargin{i + 1}))); endif elseif (strcmp (lowerarg, 'symbols')) if (numel (varargin{i + 1}) != noutput) error (strcat ("hmmtrain: number of symbols does not match", ... " number of possible outputs.")); endif usesym = true; symbols = varargin{i + 1}; elseif (strcmp (lowerarg, 'tolerance')) tol = varargin{i + 1}; if (! (isscalar (tol) && isnumeric (tol) && tol > 0)) error ("hmmtrain: tolerance must be a positive scalar."); endif elseif (strcmp (lowerarg, 'maxiterations')) maxiter = varargin{i + 1}; if (! (isscalar (maxiter) && maxiter >= 1 && round (maxiter) == maxiter)) error (strcat ("hmmtrain: maxiterations must be a positive", ... " scalar integer.")); endif elseif (strcmp (lowerarg, 'pseudotransitions')) pseudotr = varargin{i + 1}; if (! ismatrix (pseudotr) || rows (pseudotr) != columns (pseudotr)) error ("hmmtrain: pseudotransitions must be a square matrix."); endif if (rows (pseudotr) != nstate) error (strcat ("hmmtrain: pseudotransitions must have the same", ... " size as transguess.")); endif elseif (strcmp (lowerarg, 'pseudoemissions')) pseudoout = varargin{i + 1}; if (! ismatrix (pseudoout)) error ("hmmtrain: pseudoemissions must be a numeric matrix."); endif if (rows (pseudoout) != nstate || columns (pseudoout) != noutput) error (strcat ("hmmtrain: pseudoemissions must have the same", ... " size as outguess.")); endif elseif (strcmp (lowerarg, 'verbose')) verbose = logical (varargin{i + 1}); else error (strcat ("hmmtrain: unknown parameter name", ... sprintf (" '%s'.", varargin{i}))); endif endfor # Pseudo-count matrices default to zero (no smoothing) if (isempty (pseudotr)) pseudotr = zeros (nstate, nstate); endif if (isempty (pseudoout)) pseudoout = zeros (nstate, noutput); endif if (! usesym) symbols = {}; endif # Collect the sequences into a cell array of integer row vectors. A cell # array holds one sequence per element (symbol sequences must use this or a # single vector form); a numeric matrix with several rows is one sequence per # row; anything else is treated as a single sequence. if (iscell (sequence) && ! usesym) seqs = sequence; elseif (iscell (sequence) && usesym && ! isempty (sequence) ... && iscell (sequence{1})) seqs = sequence; elseif (isnumeric (sequence) && rows (sequence) > 1) seqs = num2cell (sequence, 2); else seqs = {sequence}; endif nseq = numel (seqs); for j = 1:nseq seqs{j} = checkseq (seqs{j}, usesym, symbols, noutput); endfor # Normalize the initial guesses so each row sums to 1 (a zero row stays zero) guesstr = normalizerows (transguess); guessout = normalizerows (outguess); usebaum = strcmp (algorithm, 'baumwelch'); converged = false; loglik = 1; for iter = 1:maxiter oldloglik = loglik; loglik = 0; oldguesstr = guesstr; oldguessout = guessout; if (usebaum) # Baum-Welch: accumulate expected transition and output counts TR = zeros (nstate, nstate); OUT = zeros (nstate, noutput); for j = 1:nseq seqj = seqs{j}; len = numel (seqj); if (len == 0) continue; endif [fs, bs, sc] = fwdback (seqj, guesstr, guessout, noutput); loglik += sum (log (sc)); # Expected transition counts (includes the forced transition out of # the initial state 1 via the padding column fs(:,1)) xi = zeros (nstate, nstate); for i = 1:len xi += (fs(:, i) * (bs(:, i + 1) .* guessout(:, seqj(i)))') ... / sc(i + 1); endfor TR += guesstr .* xi; # Expected output counts: sum of posteriors at positions per symbol for l = 1:noutput pos = find (seqj == l); if (! isempty (pos)) OUT(:, l) += sum (fs(:, pos + 1) .* bs(:, pos + 1), 2); endif endfor endfor else ## Viterbi training: count the transitions and outputs along the best ## path of each sequence. Only observed consecutive-state transitions are ## counted -- no transition out of the initial state -- matching ## hmmestimate and MATLAB's first Viterbi iteration. ## ## Counts are pooled across sequences and normalized once, the maximum ## likelihood estimate over the pooled data. MATLAB normalizes each ## sequence and averages, weighting every sequence equally whatever its ## length; its own Baum-Welch pools, as this does. Deliberate deviation. ## ## Pseudo-counts seed the count matrix, so they are counts throughout. ## From its second iteration MATLAB adds them to the normalized estimate ## instead, mixing counts with probabilities. Deliberate deviation. TR = pseudotr; OUT = pseudoout; for j = 1:nseq seqj = seqs{j}; len = numel (seqj); if (len == 0) continue; endif vpath = hmmviterbi (seqj, guesstr, guessout); # Path log-likelihood (used only for the convergence test); this does # include the forced initial transition out of state 1. lp = log (guesstr(1, vpath(1))) + log (guessout(vpath(1), seqj(1))); OUT(vpath(1), seqj(1)) += 1; for i = 2:len TR(vpath(i - 1), vpath(i)) += 1; OUT(vpath(i), seqj(i)) += 1; lp += log (guesstr(vpath(i - 1), vpath(i))) ... + log (guessout(vpath(i), seqj(i))); endfor loglik += lp; endfor endif # Normalize the accumulated counts into probability matrices guesstr = normalizerows (TR); guessout = normalizerows (OUT); # Relative changes used for convergence dll = abs (loglik - oldloglik) / (1 + abs (oldloglik)); dtr = norm (guesstr - oldguesstr, Inf) / nstate; dout = norm (guessout - oldguessout, Inf) / noutput; if (verbose) printf (strcat ("hmmtrain: iteration %d, log-likelihood = %g,", ... " rel. change = %g\n"), iter, loglik, dll); endif if (dll < tol && dtr < tol && dout < tol) converged = true; break; endif endfor if (! converged) warning (strcat ("hmmtrain: algorithm did not converge to within", ... sprintf (" tolerance %g in %d iterations.", ... tol, maxiter))); endif esttr = guesstr; estout = guessout; endfunction ## Scale each row of M to sum to 1; a row summing to zero is left unchanged. function M = normalizerows (M) s = sum (M, 2); s(s == 0) = 1; M = M ./ s; endfunction ## Padded, scaled forward-backward recursion (see hmmdecode). Returns FS and ## BS of size nstate-by-(len+1) with a leading column for the initial state 1, ## and the vector SC of scale factors (SC(1) = 1). function [fs, bs, sc] = fwdback (seqj, transprob, outprob, noutput) nstate = rows (transprob); len = numel (seqj); seq = [noutput + 1, seqj(:)']; L = len + 1; fs = zeros (nstate, L); fs(1, 1) = 1; sc = ones (1, L); for count = 2:L fs(:, count) = outprob(:, seq(count)) .* (transprob' * fs(:, count - 1)); sc(count) = sum (fs(:, count)); fs(:, count) = fs(:, count) ./ sc(count); endfor bs = ones (nstate, L); for count = L - 1:-1:1 bs(:, count) = (transprob * (bs(:, count + 1) .* ... outprob(:, seq(count + 1)))) ./ sc(count + 1); endfor endfunction ## Validate one sequence and, when USESYM, map its symbols to integers. function seqint = checkseq (seqj, usesym, symbols, noutput) len = numel (seqj); if (usesym) seqint = zeros (1, len); for i = 1:noutput isequal = ismember (seqj, symbols(i)); if (any ((seqint == 0) & (isequal == 1))) isequal *= i; seqint += isequal; endif endfor if (! all (seqint) && len > 0) index = max ((seqint == 0) .* (1:len)); error (strcat ("hmmtrain: sequence(", int2str (index), ... ") not in symbols.")); endif else if (! isvector (seqj) && ! isempty (seqj)) error ("hmmtrain: each sequence must be a vector."); endif if (! all (ismember (seqj, 1:noutput))) index = max ((ismember (seqj, 1:noutput) == 0) .* (1:len)); error (strcat ("hmmtrain: sequence(", int2str (index), ... ") out of range.")); endif seqint = seqj(:)'; endif endfunction %!demo %! ## Re-estimate a model with Baum-Welch, starting from rough initial guesses. %! %! transprob = [0.95, 0.05; 0.10, 0.90]; %! outprob = [1/6, 1/6, 1/6, 1/6, 1/6, 1/6; 1/10, 1/10, 1/10, 1/10, 1/10, 1/2]; %! sequence = hmmgenerate (1000, transprob, outprob); %! transguess = [0.8, 0.2; 0.2, 0.8]; %! outguess = [1/6, 1/6, 1/6, 1/6, 1/6, 1/6; 1/8, 1/8, 1/8, 1/8, 1/8, 3/8]; %! [esttr, estout] = hmmtrain (sequence, transguess, outguess) %!test %! ## Baum-Welch recovers matrices close to the generating model %! transprob = [0.9, 0.1; 0.1, 0.9]; %! outprob = [0.9, 0.1; 0.1, 0.9]; %! rand ("seed", 42); %! sequence = hmmgenerate (500, transprob, outprob); %! transguess = [0.8, 0.2; 0.2, 0.8]; %! outguess = [0.7, 0.3; 0.3, 0.7]; %! [esttr, estout] = hmmtrain (sequence, transguess, outguess); %! assert_equal (size (esttr), [2, 2]); %! assert_equal (size (estout), [2, 2]); %! assert_equal (all (abs (sum (esttr, 2) - 1) < 1e-10), true); %! assert_equal (all (abs (sum (estout, 2) - 1) < 1e-10), true); %!test %! ## Baum-Welch never decreases the data log-likelihood %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! rand ("seed", 7); %! sequence = hmmgenerate (200, transprob, outprob); %! transguess = [0.5, 0.5; 0.5, 0.5]; %! outguess = [0.4, 0.3, 0.3; 0.2, 0.4, 0.4]; %! [~, ll0] = hmmdecode (sequence, transguess, outguess); %! [esttr, estout] = hmmtrain (sequence, transguess, outguess); %! [~, ll1] = hmmdecode (sequence, esttr, estout); %! assert_equal (ll1 >= ll0 - 1e-8, true); %!test %! ## Multiple sequences supplied as a cell array %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! rand ("seed", 11); %! s1 = hmmgenerate (60, transprob, outprob); %! s2 = hmmgenerate (80, transprob, outprob); %! transguess = [0.6, 0.4; 0.5, 0.5]; %! outguess = [0.3, 0.3, 0.4; 0.5, 0.3, 0.2]; %! [esttr, estout] = hmmtrain ({s1, s2}, transguess, outguess); %! assert_equal (all (abs (sum (esttr, 2) - 1) < 1e-10), true); %! assert_equal (all (abs (sum (estout, 2) - 1) < 1e-10), true); %!test %! ## Viterbi training runs and returns stochastic matrices. Distinguishable %! ## states keep both visited; pseudo-counts guard against empty rows. %! transprob = [0.9, 0.1; 0.2, 0.8]; %! outprob = [0.8, 0.2; 0.2, 0.8]; %! rand ("seed", 3); %! sequence = hmmgenerate (300, transprob, outprob); %! transguess = [0.8, 0.2; 0.3, 0.7]; %! outguess = [0.7, 0.3; 0.3, 0.7]; %! [esttr, estout] = hmmtrain (sequence, transguess, outguess, ... %! 'algorithm', 'Viterbi', ... %! 'pseudotransitions', [1, 1; 1, 1], ... %! 'pseudoemissions', [1, 1; 1, 1]); %! assert_equal (all (abs (sum (esttr, 2) - 1) < 1e-10), true); %! assert_equal (all (abs (sum (estout, 2) - 1) < 1e-10), true); %!test %! ## Symbols form matches the integer form %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! intseq = [1, 2, 1, 1, 3, 2, 2, 1, 3, 3, 1, 2, 1, 1, 2]; %! symbseq = {'A', 'B', 'A', 'A', 'C', 'B', 'B', 'A', 'C', 'C', ... %! 'A', 'B', 'A', 'A', 'B'}; %! transguess = [0.6, 0.4; 0.5, 0.5]; %! outguess = [0.3, 0.3, 0.4; 0.5, 0.3, 0.2]; %! [t1, o1] = hmmtrain (intseq, transguess, outguess, 'maxiterations', 5); %! [t2, o2] = hmmtrain (symbseq, transguess, outguess, 'maxiterations', 5, ... %! 'symbols', {'A', 'B', 'C'}); %! assert_equal (t1, t2, 1e-12); %! assert_equal (o1, o2, 1e-12); %!test %! ## Several sequences pool their counts, so the estimate follows sequence %! ## length. MATLAB averages the per-sequence estimates (deviation). %! s1 = [1, 2, 2, 3, 3, 3, 3, 3]; %! s2 = [2, 2, 2, 3, 2, 2, 1, 1, 1, 2, 1, 1, 3, 1, 1, 1, 2, 2, 3, 3, 3, 2, 3, 2]; %! transguess = [0.7, 0.3; 0.4, 0.6]; %! outguess = [0.5, 0.3, 0.2; 0.2, 0.3, 0.5]; %! [esttr, estout] = hmmtrain ({s1, s2}, transguess, outguess, ... %! 'algorithm', 'Viterbi', 'maxiterations', 1, ... %! 'tolerance', 1e6); %! assert_equal (esttr, [19/21, 2/21; 0, 1], 1e-14); %! assert_equal (estout, [9/21, 10/21, 2/21; 0, 2/11, 9/11], 1e-14); %!test %! ## Pseudo-counts stay counts on every iteration. MATLAB adds them to the %! ## normalized estimate from its second iteration on (deviation). %! seq = [2, 2, 2, 2, 3, 2, 3, 3, 1, 2, 3, 3, 3, 2, 3, 1, 2, 2, 1, 2, ... %! 3, 3, 1, 2, 3, 1, 3, 3, 2, 1]; %! transguess = [0.7, 0.3; 0.4, 0.6]; %! outguess = [0.5, 0.3, 0.2; 0.2, 0.3, 0.5]; %! [esttr, estout] = hmmtrain (seq, transguess, outguess, ... %! 'algorithm', 'Viterbi', ... %! 'pseudotransitions', [2, 1; 1, 3], ... %! 'pseudoemissions', [1, 2, 1; 3, 1, 2]); %! assert_equal (esttr, [5/7, 2/7; 1/29, 28/29], 1e-14); %! assert_equal (estout, [1/8, 6/8, 1/8; 9/32, 9/32, 14/32], 1e-14); %!warning ... %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! rand ("seed", 1); %! sequence = hmmgenerate (50, transprob, outprob); %! hmmtrain (sequence, [0.5, 0.5; 0.5, 0.5], [0.4, 0.3, 0.3; 0.2, 0.4, 0.4], ... %! 'maxiterations', 1); %!error hmmtrain ([1, 2], [0.8, 0.2; 0.4, 0.6]) %!error ... %! hmmtrain ([1, 2], [0.8, 0.2; 0.4, 0.6; 0.1, 0.9], [0.5, 0.5; 0.1, 0.9]) %!error ... %! hmmtrain ([1, 2], [0.8, 0.2; 0.4, 0.6], [0.5, 0.5]) %!error ... %! hmmtrain ([1, 2], [0.8, 0.2; 0.4, 0.6], [0.5, 0.5; 0.1, 0.9], ... %! 'algorithm', 'nope') %!error ... %! hmmtrain ([1, 5], [0.8, 0.2; 0.4, 0.6], [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]) statistics-release-1.9.2/inst/Markov_Models/hmmviterbi.m000066400000000000000000000256571524624707500234310ustar00rootroot00000000000000## Copyright (C) 2006, 2007 Arno Onken ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{vpath} =} hmmviterbi (@var{sequence}, @var{transprob}, @var{outprob}) ## @deftypefnx {statistics} {@var{vpath} =} hmmviterbi (@dots{}, @code{"symbols"}, @var{symbols}) ## @deftypefnx {statistics} {@var{vpath} =} hmmviterbi (@dots{}, @code{"statenames"}, @var{statenames}) ## ## Viterbi path of a hidden Markov model. ## ## Use the Viterbi algorithm to find the Viterbi path of a hidden Markov ## model given a sequence of outputs. The model assumes that the generation ## starts in state @code{1} at step @code{0} but does not include step ## @code{0} in the generated states and sequence. ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{sequence} is the vector of length @var{len} of given outputs. The ## outputs must be integers ranging from @code{1} to ## @code{columns (outprob)}. ## ## @item ## @var{transprob} is the matrix of transition probabilities of the states. ## @code{transprob(i, j)} is the probability of a transition to state ## @code{j} given state @code{i}. ## ## @item ## @var{outprob} is the matrix of output probabilities. ## @code{outprob(i, j)} is the probability of generating output @code{j} ## given state @code{i}. ## @end itemize ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{vpath} is the vector of the same length as @var{sequence} of the ## estimated hidden states. The states are integers ranging from @code{1} to ## @code{columns (transprob)}. ## @end itemize ## ## If @code{"symbols"} is specified, then @var{sequence} is expected to be a ## sequence of the elements of @var{symbols} instead of integers ranging ## from @code{1} to @code{columns (outprob)}. @var{symbols} can be a cell array. ## ## If @code{"statenames"} is specified, then the elements of ## @var{statenames} are used for the states in @var{vpath} instead of ## integers ranging from @code{1} to @code{columns (transprob)}. ## @var{statenames} can be a cell array. ## ## @subheading Examples ## ## @example ## @group ## transprob = [0.8, 0.2; 0.4, 0.6]; ## outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; ## [sequence, states] = hmmgenerate (25, transprob, outprob); ## vpath = hmmviterbi (sequence, transprob, outprob); ## @end group ## ## @group ## symbols = @{"A", "B", "C"@}; ## statenames = @{"One", "Two"@}; ## [sequence, states] = hmmgenerate (25, transprob, outprob, ... ## "symbols", symbols, "statenames", statenames); ## vpath = hmmviterbi (sequence, transprob, outprob, ... ## "symbols", symbols, "statenames", statenames); ## @end group ## @end example ## ## @subheading References ## ## @enumerate ## @item ## Wendy L. Martinez and Angel R. Martinez. @cite{Computational Statistics ## Handbook with MATLAB}. Appendix E, pages 547-557, Chapman & Hall/CRC, ## 2001. ## ## @item ## Lawrence R. Rabiner. A Tutorial on Hidden Markov Models and Selected ## Applications in Speech Recognition. @cite{Proceedings of the IEEE}, ## 77(2), pages 257-286, February 1989. ## @end enumerate ## @end deftypefn function vpath = hmmviterbi (sequence, transprob, outprob, varargin) # Check arguments if (nargin < 3 || mod (length (varargin), 2) != 0) print_usage (); endif if (! ismatrix (transprob)) error ("hmmviterbi: transprob must be a non-empty numeric matrix"); endif if (! ismatrix (outprob)) error ("hmmviterbi: outprob must be a non-empty numeric matrix"); endif len = length (sequence); # nstate is the number of states of the hidden Markov model nstate = rows (transprob); # noutput is the number of different outputs that the hidden Markov model # can generate noutput = columns (outprob); # Check whether transprob and outprob are feasible for a hidden Markov model if (columns (transprob) != nstate) error ("hmmviterbi: transprob must be a square matrix"); endif if (rows (outprob) != nstate) error (strcat ("hmmviterbi: outprob must have the same number of", ... " rows as transprob")); endif # Flag for symbols usesym = false; # Flag for statenames usesn = false; # Process varargin for i = 1:2:length (varargin) # There must be an identifier: 'symbols' or 'statenames' if (! ischar (varargin{i})) print_usage (); endif # Upper case is also fine lowerarg = lower (varargin{i}); if (strcmp (lowerarg, 'symbols')) if (length (varargin{i + 1}) != noutput) error (strcat ("hmmviterbi: number of symbols does not match", ... " number of possible outputs")); endif usesym = true; # Use the following argument as symbols symbols = varargin{i + 1}; # The same for statenames elseif (strcmp (lowerarg, 'statenames')) if (length (varargin{i + 1}) != nstate) error (strcat ("hmmviterbi: number of statenames does not match", ... " number of states")); endif usesn = true; # Use the following argument as statenames statenames = varargin{i + 1}; else error (strcat ("hmmviterbi: expected 'symbols' or 'statenames'", ... sprintf (" but found '%s'", varargin{i}))); endif endfor # Transform sequence from symbols to integers if necessary if (usesym) # sequenceint is used to build the transformed sequence sequenceint = zeros (1, len); for i = 1:noutput # Search for symbols(i) in the sequence, isequal will have 1 at # corresponding indices; i is the right integer for that symbol isequal = ismember (sequence, symbols(i)); # We do not want to change sequenceint if the symbol appears a second # time in symbols if (any ((sequenceint == 0) & (isequal == 1))) isequal *= i; sequenceint += isequal; endif endfor if (! all (sequenceint)) index = max ((sequenceint == 0) .* (1:len)); error (strcat ("hmmviterbi: sequence(", int2str (index), ... ") not in symbols")); endif sequence = sequenceint; else if (! isvector (sequence) && ! isempty (sequence)) error ("hmmviterbi: sequence must be a vector"); endif if (! all (ismember (sequence, 1:noutput))) index = max ((ismember (sequence, 1:noutput) == 0) .* (1:len)); error (strcat ("hmmviterbi: sequence(", int2str (index), ... ") out of range")); endif endif # Each row in transprob and outprob should contain log probabilities # => scale so that the sum is 1 and convert to log space # - for transprob s = sum (transprob, 2); s(s == 0) = 1; transprob = log (transprob ./ s); # - for outprob s = sum (outprob, 2); s(s == 0) = 1; outprob = log (outprob ./ s); # Viterbi recursion. The model starts in state 1 at step 0, so the first # observation is emitted after one transition from state 1. delta(k) holds # the log probability of the most likely path that ends in state k at the # current step; back(:, i) stores, for each state, the predecessor state on # that best path, used for the traceback. if (len == 0) vpath = zeros (1, 0); else delta = transprob(1, :)' + outprob(:, sequence(1)); back = zeros (nstate, len); for i = 2:len # best predecessor for each current state, then add the emission [delta, back(:, i)] = max (delta + transprob, [], 1); delta = delta' + outprob(:, sequence(i)); endfor # Trace back from the most likely final state vpath = zeros (1, len); [~, vpath(len)] = max (delta); for i = len - 1:-1:1 vpath(i) = back(vpath(i + 1), i + 1); endfor endif # Transform vpath into statenames if requested if (usesn) vpath = reshape (statenames(vpath), 1, len); endif endfunction %!demo %! ## Most likely (Viterbi) state path for a two-state, three-symbol model. %! %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! sequence = [1, 2, 1, 1, 3, 2, 3, 1, 2, 3]; %! vpath = hmmviterbi (sequence, transprob, outprob) %!demo %! ## The state path can also be reported using custom state names. %! %! transprob = [0.95, 0.05; 0.10, 0.90]; %! outprob = [1/6, 1/6, 1/6, 1/6, 1/6, 1/6; 1/10, 1/10, 1/10, 1/10, 1/10, 1/2]; %! [sequence, states] = hmmgenerate (12, transprob, outprob, ... %! "statenames", {"fair", "loaded"}); %! vpath = hmmviterbi (sequence, transprob, outprob, ... %! "statenames", {"fair", "loaded"}) %!test %! sequence = [1, 2, 1, 1, 1, 2, 2, 1, 2, 3, 3, 3, ... %! 3, 2, 3, 1, 1, 1, 1, 3, 3, 2, 3, 1, 3]; %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! vpath = hmmviterbi (sequence, transprob, outprob); %! expected = [1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, ... %! 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1]; %! assert_equal (vpath, expected); %!test %! sequence = {'A', 'B', 'A', 'A', 'A', 'B', 'B', 'A', 'B', 'C', 'C', 'C', ... %! 'C', 'B', 'C', 'A', 'A', 'A', 'A', 'C', 'C', 'B', 'C', 'A', 'C'}; %! transprob = [0.8, 0.2; 0.4, 0.6]; %! outprob = [0.2, 0.4, 0.4; 0.7, 0.2, 0.1]; %! symbols = {'A', 'B', 'C'}; %! statenames = {'One', 'Two'}; %! vpath = hmmviterbi (sequence, transprob, outprob, 'symbols', symbols, ... %! 'statenames', statenames); %! expected = {'One', 'One', 'Two', 'Two', 'Two', 'One', 'One', 'One', ... %! 'One', 'One', 'One', 'One', 'One', 'One', 'One', 'Two', ... %! 'Two', 'Two', 'Two', 'One', 'One', 'One', 'One', 'One', 'One'}; %! assert_equal (vpath, expected); %!test %! ## The returned path is the true maximum-probability path. A former bug %! ## scored a spurious transition out of the last state, biasing the final %! ## states of the path; these cases guard against a regression. %! transprob = [0.1854, 0.8146; 0.5948, 0.4052]; %! outprob = [0.5536, 0.4464; 0.2712, 0.7288]; %! assert_equal (hmmviterbi ([1, 2], transprob, outprob), [2, 2]); %!test %! transprob = [0.6056, 0.3944; 0.2036, 0.7964]; %! outprob = [0.6525, 0.3475; 0.2878, 0.7122]; %! assert_equal (hmmviterbi ([1, 1, 2, 1], transprob, outprob), [1, 1, 1, 1]); %!test %! ## An empty sequence yields an empty path %! vpath = hmmviterbi ([], [0.8, 0.2; 0.4, 0.6], [0.5, 0.5; 0.3, 0.7]); %! assert_equal (vpath, zeros (1, 0)); statistics-release-1.9.2/inst/Model_Evaluation/000077500000000000000000000000001524624707500215535ustar00rootroot00000000000000statistics-release-1.9.2/inst/Model_Evaluation/ConfusionMatrixChart.m000066400000000000000000001160641524624707500260530ustar00rootroot00000000000000## Copyright (C) 2020-2021 Stefano Guidoni ## Copyright (C) 2023-2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef ConfusionMatrixChart < handle ## -*- texinfo -*- ## @deftp {statistics} ConfusionMatrixChart ## ## Confusion matrix chart for classification results ## ## The @code{ConfusionMatrixChart} class implements a confusion matrix chart ## object, which displays the classification performance of a classifier by ## showing the counts of true positive, true negative, false positive, and ## false negative predictions. ## ## A confusion matrix chart is a visual representation of the performance of ## a classification algorithm. The rows represent the true classes and the ## columns represent the predicted classes. The diagonal elements represent ## the correctly classified observations, while the off-diagonal elements ## represent the misclassified observations. ## ## Create a @code{ConfusionMatrixChart} object by using the ## @code{confusionchart} function. ## ## @seealso{confusionchart} ## @end deftp properties(Access = public) ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} XLabel ## ## X-axis label ## ## A character vector specifying the label for the x-axis. Default is ## "Predicted Class". ## ## @end deftp XLabel = 'Predicted Class'; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} YLabel ## ## Y-axis label ## ## A character vector specifying the label for the y-axis. Default is ## "True Class". ## ## @end deftp YLabel = 'True Class'; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} Title ## ## Chart title ## ## A character vector specifying the title of the confusion matrix chart. ## Default is empty string. ## ## @end deftp Title = ''; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} FontName ## ## Font name for text elements ## ## A character vector specifying the font name used for all text elements ## in the chart. Default is empty string, which uses the axes font name. ## ## @end deftp FontName = ''; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} FontSize ## ## Font size for text elements ## ## A numeric scalar specifying the font size used for all text elements ## in the chart. Default is 0, which uses the axes font size. ## ## @end deftp FontSize = 0; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} DiagonalColor ## ## Color for diagonal elements ## ## A 1x3 RGB vector specifying the color for the diagonal elements of the ## confusion matrix, which represent correct classifications. Default is ## [0.0, 0.4471, 0.7412]. ## ## @end deftp DiagonalColor = [0 0.4471 0.7412]; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} OffDiagonalColor ## ## Color for off-diagonal elements ## ## A 1x3 RGB vector specifying the color for the off-diagonal elements of ## the confusion matrix, which represent misclassifications. Default is ## [0.8510, 0.3255, 0.0980]. ## ## @end deftp OffDiagonalColor = [0.8510 0.3255 0.0980]; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} Normalization ## ## Normalization method for confusion matrix values ## ## A character vector specifying how to normalize the confusion matrix ## values. Supported values are: ## ## @itemize ## @item @qcode{'absolute'} - Display absolute counts (default) ## @item @qcode{'column-normalized'} - Normalize by column totals ## @item @qcode{'row-normalized'} - Normalize by row totals ## @item @qcode{'total-normalized'} - Normalize by total number of ## observations ## @end itemize ## ## @end deftp Normalization = 'absolute'; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} ColumnSummary ## ## Column summary display ## ## A character vector specifying whether and how to display column ## summaries. Supported values are: ## ## @itemize ## @item @qcode{'off'} - Do not display column summary (default) ## @item @qcode{'absolute'} - Display absolute counts ## @item @qcode{'column-normalized'} - Display normalized by column ## @item @qcode{'total-normalized'} - Display normalized by total ## @end itemize ## ## @end deftp ColumnSummary = 'off'; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} RowSummary ## ## Row summary display ## ## A character vector specifying whether and how to display row summaries. ## Supported values are: ## ## @itemize ## @item @qcode{'off'} - Do not display row summary (default) ## @item @qcode{'absolute'} - Display absolute counts ## @item @qcode{'row-normalized'} - Display normalized by row ## @item @qcode{'total-normalized'} - Display normalized by total ## @end itemize ## ## @end deftp RowSummary = 'off'; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} GridVisible ## ## Grid visibility ## ## A character vector specifying whether to display grid lines in the ## confusion matrix. Supported values are: ## ## @itemize ## @item @qcode{'on'} - Display grid lines (default) ## @item @qcode{'off'} - Hide grid lines ## @end itemize ## ## @end deftp GridVisible = 'on'; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} HandleVisibility ## ## Handle visibility ## ## A character vector specifying the visibility of the object's handle. ## Supported values are @qcode{'on'}, @qcode{'off'}, and @qcode{'callback'}. ## ## @end deftp HandleVisibility = ''; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} OuterPosition ## ## Outer position of the chart ## ## A 1x4 numeric vector specifying the outer position of the chart in the ## format [left, bottom, width, height]. ## ## @end deftp OuterPosition = []; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} Position ## ## Position of the chart ## ## A 1x4 numeric vector specifying the position of the chart in the ## format [left, bottom, width, height]. ## ## @end deftp Position = []; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} Units ## ## Position units ## ## A character vector specifying the units for the position properties. ## Supported values are @qcode{'centimeters'}, @qcode{'characters'}, ## @qcode{'inches'}, @qcode{'normalized'}, @qcode{'pixels'}, and ## @qcode{'points'}. ## ## @end deftp Units = ''; endproperties properties(GetAccess = public, SetAccess = private) ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} ClassLabels ## ## Class labels ## ## A cell array of character vectors containing the class labels used in ## the confusion matrix. This property is read-only. ## ## @end deftp ClassLabels = {}; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} NormalizedValues ## ## Normalized confusion matrix values ## ## A numeric matrix containing the normalized confusion matrix values ## according to the current normalization setting. This property is ## read-only. ## ## @end deftp NormalizedValues = []; ## -*- texinfo -*- ## @deftp {ConfusionMatrixChart} {property} Parent ## ## Parent object ## ## A handle to the parent figure or container object. This property is ## read-only. ## ## @end deftp Parent = 0; endproperties properties(Access = protected, Hidden) ## Axes handle hax = 0.0; ## Number of classes ClassN = 0; ## Absolute confusion matrix values AbsoluteValues = []; ## Column summary absolute values ColumnSummaryAbsoluteValues = []; ## Row summary absolute values RowSummaryAbsoluteValues = []; endproperties methods (Hidden) ## Custom display of the normalized values and the class labels. function disp (this) nv_sizes = size (this.NormalizedValues); cl_sizes = size (this.ClassLabels); printf ("%s with properties:\n\n", class (this)); printf ("\tNormalizedValues: [ %dx%d %s ]\n", nv_sizes(1), nv_sizes(2),... class (this.NormalizedValues)); printf ("\tClassLabels: { %dx%d %s }\n\n", cl_sizes(1), cl_sizes(2),... class (this.ClassLabels)); endfunction ## Set functions function set.XLabel (this, string) if (! ischar (string)) close (this.Parent); error ("confusionchart: XLabel must be a string."); endif this.XLabel = updateAxesProperties (this, 'xlabel', string); endfunction function set.YLabel (this, string) if (! ischar (string)) close (this.Parent); error ("confusionchart: YLabel must be a string."); endif this.YLabel = updateAxesProperties (this, 'ylabel', string); endfunction function set.Title (this, string) if (! ischar (string)) close (this.Parent); error ("confusionchart: Title must be a string."); endif this.Title = updateAxesProperties (this, 'title', string); endfunction function set.FontName (this, string) if (! ischar (string)) close (this.Parent); error ("confusionchart: FontName must be a string."); endif this.FontName = updateTextProperties (this, 'fontname', string); endfunction function set.FontSize (this, value) if (! isnumeric (value)) close (this.Parent); error ("confusionchart: FontSize must be numeric."); endif this.FontSize = updateTextProperties (this, 'fontsize', value); endfunction function set.DiagonalColor (this, color) if (ischar (color)) color = this.convertNamedColor (color); endif if (! (isvector (color) && length (color) == 3 )) close (this.Parent); error ("confusionchart: DiagonalColor must be a color."); endif this.DiagonalColor = color; updateColorMap (this); endfunction function set.OffDiagonalColor (this, color) if (ischar (color)) color = this.convertNamedColor (color); endif if (! (isvector (color) && length (color) == 3)) close (this.Parent); error ("confusionchart: OffDiagonalColor must be a color."); endif this.OffDiagonalColor = color; updateColorMap (this); endfunction function set.Normalization (this, string) if (! any (strcmp (string, {'absolute', 'column-normalized',... 'row-normalized', 'total-normalized'}))) close (this.Parent); error ("confusionchart: invalid value for Normalization."); endif this.Normalization = string; updateChart (this); endfunction function set.ColumnSummary (this, string) if (! any (strcmp (string, {'off', 'absolute', 'column-normalized',... 'total-normalized'}))) close (this.Parent); error ("confusionchart: invalid value for ColumnSummary."); endif this.ColumnSummary = string; updateChart (this); endfunction function set.RowSummary (this, string) if (! any (strcmp (string, {'off', 'absolute', 'row-normalized',... 'total-normalized'}))) close (this.Parent); error ("confusionchart: invalid value for RowSummary."); endif this.RowSummary = string; updateChart (this); endfunction function set.GridVisible (this, string) if (! any (strcmp (string, {'off', 'on'}))) close (this.Parent); error ("confusionchart: invalid value for GridVisible."); endif this.GridVisible = string; setGridVisibility (this); endfunction function set.HandleVisibility (this, string) if (! any (strcmp (string, {'off', 'on', 'callback'}))) close (this.Parent); error ("confusionchart: invalid value for HandleVisibility"); endif set (this.hax, 'handlevisibility', string); endfunction function set.OuterPosition (this, vector) if (! isvector (vector) || ! isnumeric (vector) || length (vector) != 4) close (this.Parent); error ("confusionchart: invalid value for OuterPosition"); endif set (this.hax, 'outerposition', vector); endfunction function set.Position (this, vector) if (! isvector (vector) || ! isnumeric (vector) || length (vector) != 4) close (this.Parent); error ("confusionchart: invalid value for Position"); endif set (this.hax, 'position', vector); endfunction function set.Units (this, string) if (! any (strcmp (string, {'centimeters', 'characters', 'inches', ... 'normalized', 'pixels', 'points'}))) close (this.Parent); error ("confusionchart: invalid value for Units"); endif set (this.hax, 'units', string); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {statistics} {@var{cmc} =} ConfusionMatrixChart (@var{hax}, @var{cm}, @var{cl}) ## @deftypefnx {statistics} {@var{cmc} =} ConfusionMatrixChart (@dots{}, @var{name}, @var{value}) ## ## Create a @qcode{ConfusionMatrixChart} object for visualizing ## classification performance. ## ## @code{@var{cmc} = ConfusionMatrixChart (@var{hax}, @var{cm}, @var{cl})} ## returns a ConfusionMatrixChart object with parent axes @var{hax}, ## confusion matrix @var{cm}, and class labels @var{cl}. ## ## @itemize ## @item ## @code{hax} must be a valid axes handle where the chart will be displayed. ## @item ## @code{cm} must be a square numeric matrix containing the confusion matrix ## values, where rows represent true classes and columns represent predicted ## classes. ## @item ## @code{cl} must be a cell array of character vectors containing the class ## labels. The number of labels must match the size of the confusion matrix. ## @end itemize ## ## @code{@var{cmc} = ConfusionMatrixChart (@dots{}, @var{name}, ## @var{value})} ## returns a ConfusionMatrixChart object with additional parameters ## specified by @qcode{@var{name}, @var{value}} paired arguments: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'XLabel'} @tab A character vector specifying the ## x-axis label. Default is "Predicted Class". ## ## @item @qcode{'YLabel'} @tab A character vector specifying the ## y-axis label. Default is "True Class". ## ## @item @qcode{'Title'} @tab A character vector specifying the chart ## title. Default is empty string. ## ## @item @qcode{'FontName'} @tab A character vector specifying the ## font name for text elements. Default is the axes font name. ## ## @item @qcode{'FontSize'} @tab A numeric scalar specifying the ## font size for text elements. Default is the axes font size. ## ## @item @qcode{'DiagonalColor'} @tab A 1x3 RGB vector specifying ## the color for diagonal elements (correct classifications). Default is ## [0.0, 0.4471, 0.7412]. ## ## @item @qcode{'OffDiagonalColor'} @tab A 1x3 RGB vector specifying ## the color for off-diagonal elements (misclassifications). Default is ## [0.8510, 0.3255, 0.0980]. ## ## @item @qcode{'Normalization'} @tab A character vector specifying ## the normalization method. Supported values are @qcode{'absolute'}, ## @qcode{'column-normalized'}, @qcode{'row-normalized'}, and ## @qcode{'total-normalized'}. Default is @qcode{'absolute'}. ## ## @item @qcode{'ColumnSummary'} @tab A character vector specifying ## whether and how to display column summaries. Supported values are ## @qcode{'off'}, @qcode{'absolute'}, @qcode{'column-normalized'}, and ## @qcode{'total-normalized'}. Default is @qcode{'off'}. ## ## @item @qcode{'RowSummary'} @tab A character vector specifying ## whether and how to display row summaries. Supported values are ## @qcode{'off'}, @qcode{'absolute'}, @qcode{'row-normalized'}, and ## @qcode{'total-normalized'}. Default is @qcode{'off'}. ## ## @item @qcode{'GridVisible'} @tab A character vector specifying ## whether to display grid lines. Supported values are @qcode{'on'} and ## @qcode{'off'}. Default is @qcode{'on'}. ## ## @item @qcode{'HandleVisibility'} @tab A character vector specifying ## the handle visibility. Supported values are @qcode{'on'}, @qcode{'off'}, ## and @qcode{'callback'}. ## ## @item @qcode{'OuterPosition'} @tab A 1x4 numeric vector specifying ## the outer position of the chart. ## ## @item @qcode{'Position'} @tab A 1x4 numeric vector specifying ## the position of the chart. ## ## @item @qcode{'Units'} @tab A character vector specifying the ## position units. Supported values are @qcode{'centimeters'}, ## @qcode{'characters'}, @qcode{'inches'}, @qcode{'normalized'}, ## @qcode{'pixels'}, and @qcode{'points'}. ## @end multitable ## ## @seealso{confusionchart} ## @end deftypefn function this = ConfusionMatrixChart (hax, cm, cl, args) ## class initialization this.hax = hax; this.Parent = get (this.hax, 'parent'); this.ClassLabels = cl; this.NormalizedValues = cm; this.AbsoluteValues = cm; this.ClassN = rows (cm); this.FontName = get (this.hax, 'fontname'); this.FontSize = get (this.hax, 'fontsize'); set (this.hax, 'xlabel', this.XLabel); set (this.hax, 'ylabel', this.YLabel); ## draw the chart draw (this); ## apply paired properties if (! isempty (args)) pair_idx = 1; while (pair_idx < length (args)) switch (args{pair_idx}) case 'XLabel' this.XLabel = args{pair_idx + 1}; case 'YLabel' this.YLabel = args{pair_idx + 1}; case 'Title' this.Title = args{pair_idx + 1}; case 'FontName' this.FontName = args{pair_idx + 1}; case 'FontSize' this.FontSize = args{pair_idx + 1}; case 'DiagonalColor' this.DiagonalColor = args{pair_idx + 1}; case 'OffDiagonalColor' this.OffDiagonalColor = args{pair_idx + 1}; case 'Normalization' this.Normalization = args{pair_idx + 1}; case 'ColumnSummary' this.ColumnSummary = args{pair_idx + 1}; case 'RowSummary' this.RowSummary = args{pair_idx + 1}; case 'GridVisible' this.GridVisible = args{pair_idx + 1}; case 'HandleVisibility' this.HandleVisibility = args{pair_idx + 1}; case 'OuterPosition' this.OuterPosition = args{pair_idx + 1}; case 'Position' this.Position = args{pair_idx + 1}; case 'Units' this.Units = args{pair_idx + 1}; otherwise close (this.Parent); error ("confusionchart: invalid property %s", args{pair_idx}); endswitch pair_idx += 2; endwhile endif ## init the color map updateColorMap (this); endfunction ## -*- texinfo -*- ## @deftypefn {ConfusionMatrixChart} {} sortClasses (@var{cmc}, @var{order}) ## ## Sort the classes in the confusion matrix chart. ## ## @code{sortClasses (@var{cmc}, @var{order})} sorts the classes in the ## confusion matrix chart @var{cmc} according to the specified @var{order}. ## ## @var{order} can be: ## @itemize ## @item A cell array of class labels in the desired order ## @item @qcode{'auto'} - Sort class labels alphabetically ## @item @qcode{'ascending-diagonal'} - Sort by ascending diagonal values ## @item @qcode{'descending-diagonal'} - Sort by descending diagonal values ## @item @qcode{'cluster'} - Sort using hierarchical clustering ## @end itemize ## ## When using @qcode{'cluster'}, the classes are grouped based on similarity ## using hierarchical clustering, which can help identify groups of ## frequently confused classes. ## ## @seealso{confusionchart, linkage, pdist} ## @end deftypefn function sortClasses (this, order) ## check the input parameters if (nargin != 2) print_usage (); endif cl = this.ClassLabels; cm_size = this.ClassN; nv = this.NormalizedValues; av = this.AbsoluteValues; cv = this.ColumnSummaryAbsoluteValues; rv = this.RowSummaryAbsoluteValues; scl = {}; Idx = []; if (strcmp (order, 'auto')) [scl, Idx] = sort (cl); elseif (strcmp (order, 'ascending-diagonal')) [s, Idx] = sort (diag (nv)); scl = cl(Idx); elseif (strcmp (order, 'descending-diagonal')) [s, Idx] = sort (diag (nv)); Idx = flip (Idx); scl = cl(Idx); elseif (strcmp (order, 'cluster')) ## the classes are all grouped together ## this way one can visually evaluate which are the most similar classes ## according to the learning algorithm D = zeros (1, ((cm_size - 1) * cm_size / 2)); # a pdist like vector maxD = 2 * max (max (av)); k = 1; # better than computing the index at every cycle for i = 1 : (cm_size - 1) for j = (i + 1) : cm_size D(k++) = maxD - (av(i, j) + av(j, i)); # distance endfor endfor tree = linkage (D, 'average'); # clustering ## we could have optimal leaf ordering with Idx = optimalleaforder (tree, D); # optimal clustering ##[~, Idx] = sort (cluster (tree)); nodes_to_visit = 2 * cm_size - 1; nodecount = 0; while (! isempty (nodes_to_visit)) current_node = nodes_to_visit(1); nodes_to_visit(1) = []; if (current_node > cm_size) node = current_node - cm_size; nodes_to_visit = [tree(node,[2 1]) nodes_to_visit]; endif if (current_node <= cm_size) nodecount++; Idx(nodecount) = current_node; endif endwhile ## scl = cl(Idx); else ## must be an array or cell array of labels if (! iscellstr (order)) if (! ischar (order)) if (isrow (order)) order = vec (order); endif order = num2str (order); endif scl = cellstr (order); endif if (length (scl) != length (cl)) error ("sortClasses: wrong size for order.") endif Idx = zeros (length (scl), 1); for i = 1 : length (scl) Idx(i) = find (strcmp (cl, scl{i})); endfor endif ## rearrange the normalized values... nv = nv(Idx, :); nv = nv(:, Idx); this.NormalizedValues = nv; ## ...and the absolute values... av = av(Idx, :); av = av(:, Idx); this.AbsoluteValues = av; cv = cv([Idx ( Idx + cm_size )]); this.ColumnSummaryAbsoluteValues = cv; rv = rv([Idx ( Idx + cm_size )]); this.RowSummaryAbsoluteValues = rv; ## ...and the class labels this.ClassLabels = scl; ## update the axes set (this.hax, 'xtick', (0.5 : 1 : (cm_size - 0.5)), 'xticklabel', scl,... 'ytick', (0.5 : 1 : (cm_size - 0.5)), 'yticklabel', scl); ## get text and patch handles kids = get (this.hax, 'children'); t_kid = kids(find (isprop (kids, 'fontname'))); # hack to find texts m_kid = kids(find (strcmp (get (kids, 'userdata'), 'MainChart'))); c_kid = kids(find (strcmp (get (kids, 'userdata'), 'ColumnSummary'))); r_kid = kids(find (strcmp (get (kids, 'userdata'), 'RowSummary'))); ## re-assign colors to the main chart cdata_v = get (m_kid, 'cdata'); cdata_m = reshape (cdata_v, cm_size, cm_size); cdata_m = cdata_m(Idx, :); cdata_m = cdata_m(:, Idx); cdata_v = reshape (cdata_m, size (cdata_v)); set (m_kid, 'cdata', cdata_v); ## re-assign colors to the column summary cdata_v = get (c_kid, 'cdata'); cdata_m = reshape (transpose (cdata_v), cm_size, 2); cdata_m = cdata_m(Idx, :); cdata_v = reshape (cdata_m, size (cdata_v)); set (c_kid, 'cdata', cdata_v); ## re-assign colors to the row summary cdata_v = get (r_kid, 'cdata'); cdata_m = reshape (cdata_v, cm_size, 2); cdata_m = cdata_m(Idx, :); cdata_v = reshape (cdata_m, size (cdata_v)); set (r_kid, 'cdata', cdata_v); ## move the text labels for i = 1:length (t_kid) t_pos = get (t_kid(i), 'userdata'); if (t_pos(2) > cm_size) ## row summary t_pos(1) = find (Idx == (t_pos(1) + 1)) - 1; set (t_kid(i), 'userdata', t_pos); t_pos = t_pos([2 1]) + 0.5; set (t_kid(i), 'position', t_pos); elseif (t_pos(1) > cm_size) ## column summary t_pos(2) = find (Idx == (t_pos(2) + 1)) - 1; set (t_kid(i), 'userdata', t_pos); t_pos = t_pos([2 1]) + 0.5; set (t_kid(i), 'position', t_pos); else ## main chart t_pos(1) = find (Idx == (t_pos(1) + 1)) - 1; t_pos(2) = find (Idx == (t_pos(2) + 1)) - 1; set (t_kid(i), 'userdata', t_pos); t_pos = t_pos([2 1]) + 0.5; set (t_kid(i), 'position', t_pos); endif endfor updateChart (this); endfunction endmethods methods(Access = private) ## convertNamedColor ## convert a named colour to a colour triplet function ret = convertNamedColor (this, color) vColorNames = ['ymcrgbwk']'; vColorTriplets = [1 1 0; 1 0 1; 0 1 1; 1 0 0; 0 1 0; 0 0 1; 1 1 1; 0 0 0]; if (strcmp (color, 'black')) color = 'k'; endif index = find (vColorNames == color(1)); if (! isempty (index)) ret = vColorTriplets(index, :); else ret = []; # trigger an error message endif endfunction ## updateAxesProperties ## update the properties of the axes function ret = updateAxesProperties (this, prop, value) set (this.hax, prop, value); ret = value; endfunction ## updateTextProperties ## set the properties of the texts function ret = updateTextProperties (this, prop, value) hax_kids = get (this.hax, 'children'); text_kids = hax_kids(isprop (hax_kids , 'fontname')); # hack to find texts text_kids(end + 1) = get (this.hax, 'xlabel'); text_kids(end + 1) = get (this.hax, 'ylabel'); text_kids(end + 1) = get (this.hax, 'title'); updateAxesProperties (this, prop, value); set (text_kids, prop, value); ret = value; endfunction ## setGridVisibility ## toggle the visibility of the grid function setGridVisibility (this) kids = get (this.hax, 'children'); kids = kids(find (isprop (kids, 'linestyle'))); if (strcmp (this.GridVisible, 'on')) set (kids, 'linestyle', '-'); else set (kids, 'linestyle', 'none'); endif endfunction ## updateColorMap ## change the colormap and, accordingly, the text colors function updateColorMap (this) cm_size = this.ClassN; d_color = this.DiagonalColor; o_color = this.OffDiagonalColor; ## quick hack d_color(find (d_color == 1.0)) = 0.999; o_color(find (o_color == 1.0)) = 0.999; ## 64 shades for each color cm_colormap(1:64,:) = [1.0 : (-(1.0 - o_color(1)) / 63) : o_color(1);... 1.0 : (-(1.0 - o_color(2)) / 63) : o_color(2);... 1.0 : (-(1.0 - o_color(3)) / 63) : o_color(3)]'; cm_colormap(65:128,:) = [1.0 : (-(1.0 - d_color(1)) / 63) : d_color(1);... 1.0 : (-(1.0 - d_color(2)) / 63) : d_color(2);... 1.0 : (-(1.0 - d_color(3)) / 63) : d_color(3)]'; colormap (this.hax, cm_colormap); ## update text colors kids = get (this.hax, 'children'); t_kids = kids(find (isprop (kids, 'fontname'))); # hack to find texts m_patch = kids(find (strcmp (get (kids, 'userdata'), 'MainChart'))); c_patch = kids(find (strcmp (get (kids, 'userdata'), 'ColumnSummary'))); r_patch = kids(find (strcmp (get (kids, 'userdata'), 'RowSummary'))); m_colors = get (m_patch, 'cdata'); c_colors = get (c_patch, 'cdata'); r_colors = get (r_patch, 'cdata'); ## when a patch is dark, let's use a pale color for the text for i = 1 : length (t_kids) t_pos = get (t_kids(i), 'userdata'); color_idx = 1; if (t_pos(2) > cm_size) ## row summary idx = (t_pos(2) - cm_size - 1) * cm_size + t_pos(1) + 1; color_idx = r_colors(idx) + 1; elseif (t_pos(1) > cm_size) ## column summary idx = (t_pos(1) - cm_size - 1) * cm_size + t_pos(2) + 1; color_idx = c_colors(idx) + 1; else ## main chart idx = t_pos(2) * cm_size + t_pos(1) + 1; color_idx = m_colors(idx) + 1; endif if (sum (cm_colormap(color_idx, :)) < 1.8) set (t_kids(i), 'color', [.97 .97 1.0]); else set (t_kids(i), 'color', [.15 .15 .15]); endif endfor endfunction ## updateChart ## update the text labels and the NormalizedValues property function updateChart (this) cm_size = this.ClassN; cm = this.AbsoluteValues; l_cs = this.ColumnSummaryAbsoluteValues; l_rs = this.RowSummaryAbsoluteValues; kids = get (this.hax, 'children'); t_kids = kids(find (isprop (kids, 'fontname'))); # hack to find texts normalization = this.Normalization; column_summary = this.ColumnSummary; row_summary = this.RowSummary; ## normalization for labelling row_totals = sum (cm, 2); col_totals = sum (cm, 1); mat_total = sum (col_totals); cm_labels = cm; add_percent = true; if (strcmp (normalization, 'column-normalized')) for i = 1 : cm_size cm_labels(:,i) = cm_labels(:,i) ./ col_totals(i); endfor elseif (strcmp (normalization, 'row-normalized')) for i = 1 : cm_size cm_labels(i,:) = cm_labels(i,:) ./ row_totals(i); endfor elseif (strcmp (normalization, 'total-normalized')) cm_labels = cm_labels ./ mat_total; else add_percent = false; endif ## update NormalizedValues this.NormalizedValues = cm_labels; ## update axes last_row = cm_size; last_col = cm_size; userdata = cell2mat (get (t_kids, 'userdata')); cs_kids = t_kids(find (userdata(:,1) > cm_size)); cs_kids(end + 1) = kids(find (strcmp (get (kids, 'userdata'),... 'ColumnSummary'))); if (! strcmp ('off', column_summary)) set (cs_kids, 'visible', 'on'); last_row += 3; else set (cs_kids, 'visible', 'off'); endif rs_kids = t_kids(find (userdata(:,2) > cm_size)); rs_kids(end + 1) = kids(find (strcmp (get (kids, 'userdata'),... 'RowSummary'))); if (! strcmp ('off', row_summary)) set (rs_kids, 'visible', 'on'); last_col += 3; else set (rs_kids, 'visible', 'off'); endif axis (this.hax, [0 last_col 0 last_row]); ## update column summary data cs_add_percent = true; if (! strcmp (column_summary, 'off')) if (strcmp (column_summary, 'column-normalized')) for i = 1 : cm_size if (col_totals(i) == 0) ## avoid division by zero l_cs([i (cm_size + i)]) = 0; else l_cs([i, cm_size + i]) = l_cs([i, cm_size + i]) ./ col_totals(i); endif endfor elseif strcmp (column_summary, 'total-normalized') l_cs = l_cs ./ mat_total; else cs_add_percent = false; endif endif ## update row summary data rs_add_percent = true; if (! strcmp (row_summary, 'off')) if (strcmp (row_summary, 'row-normalized')) for i = 1 : cm_size if (row_totals(i) == 0) ## avoid division by zero l_rs([i (cm_size + i)]) = 0; else l_rs([i, cm_size + i]) = l_rs([i, cm_size + i]) ./ row_totals(i); endif endfor elseif (strcmp (row_summary, 'total-normalized')) l_rs = l_rs ./ mat_total; else rs_add_percent = false; endif endif ## update text label_list = vec (cm_labels); for i = 1 : length (t_kids) t_pos = get (t_kids(i), 'userdata'); new_string = ''; if (t_pos(2) > cm_size) ## this is the row summary idx = (t_pos(2) - cm_size - 1) * cm_size + t_pos(1) + 1; if (rs_add_percent) new_string = num2str (100.0 * l_rs(idx), '%3.1f'); new_string = [new_string '%']; else new_string = num2str (l_rs(idx)); endif elseif (t_pos(1) > cm_size) ## this is the column summary idx = (t_pos(1) - cm_size - 1) * cm_size + t_pos(2) + 1; if (cs_add_percent) new_string = num2str (100.0 * l_cs(idx), '%3.1f'); new_string = [new_string '%']; else new_string = num2str (l_cs(idx)); endif else ## this is the main chart idx = t_pos(2) * cm_size + t_pos(1) + 1; if (add_percent) new_string = num2str (100.0 * label_list(idx), '%3.1f'); new_string = [new_string '%']; else new_string = num2str (label_list(idx)); endif endif set (t_kids(i), 'string', new_string); endfor endfunction ## draw ## draw the chart function draw (this) cm = this.AbsoluteValues; cl = this.ClassLabels; cm_size = this.ClassN; ## set up the axes set (this.hax, 'xtick', (0.5 : 1 : (cm_size - 0.5)), 'xticklabel', cl,... 'ytick', (0.5 : 1 : (cm_size - 0.5)), 'yticklabel', cl ); axis ('ij'); axis (this.hax, [0 cm_size 0 cm_size]); ## prepare the patches indices_b = 0 : (cm_size -1); indices_v = repmat (indices_b, cm_size, 1); indices_vx = transpose (vec (indices_v)); indices_vy = vec (indices_v', 2); indices_ex = vec ((cm_size + 1) * [1; 2] .* ones (2, cm_size), 2); ## normalization for colorization ## it is used a colormap of 128 shades of two colors, 64 shades for each ## color normal = max (max (cm)); cm_norm = round (63 * cm ./ normal); cm_norm = cm_norm + 64 * eye (cm_size); ## default normalization: absolute cm_labels = vec (cm); ## the patches of the main chart x_patch = [indices_vx; ( indices_vx + 1 ); ( indices_vx + 1 ); indices_vx]; y_patch = [indices_vy; indices_vy; ( indices_vy + 1 ); ( indices_vy + 1 )]; c_patch = vec (cm_norm(1 : cm_size, 1 : cm_size)); ## display the patches ph = patch (this.hax, x_patch, y_patch, c_patch); set (ph, 'userdata', 'MainChart'); ## display the labels userdata = [indices_vy; indices_vx]'; nonzero_idx = find (cm_labels != 0); th = text ((x_patch(1, nonzero_idx) + 0.5), (y_patch(1, nonzero_idx) +... 0.5), num2str (cm_labels(nonzero_idx)), 'parent', this.hax ); set (th, 'horizontalalignment', 'center'); for i = 1 : length (nonzero_idx) set (th(i), 'userdata', userdata(nonzero_idx(i), :)); endfor ## patches for the summaries main_values = diag (cm); ct_values = sum (cm)'; rt_values = sum (cm, 2); cd_values = ct_values - main_values; rd_values = rt_values - main_values; ## column summary x_cs = [[indices_b indices_b]; ( [indices_b indices_b] + 1 ); ( [indices_b indices_b] + 1 ); [indices_b indices_b]]; y_cs = [(repmat ([1 1 2 2]', 1, cm_size)) (repmat ([2 2 3 3]', 1, cm_size))] +... cm_size; c_cs = [(round (63 * (main_values ./ ct_values)) + 64); (round (63 * (cd_values ./ ct_values)))]; c_cs(isnan (c_cs)) = 0; l_cs = [main_values; cd_values]; ph = patch (this.hax, x_cs, y_cs, c_cs); set (ph, 'userdata', 'ColumnSummary'); set (ph, 'visible', 'off' ); userdata = [y_cs(1,:); x_cs(1,:)]'; nonzero_idx = find (l_cs != 0); th = text ((x_cs(1,nonzero_idx) + 0.5), (y_cs(1,nonzero_idx) + 0.5),... num2str (l_cs(nonzero_idx)), 'parent', this.hax); set (th, 'horizontalalignment', 'center'); for i = 1 : length (nonzero_idx) set (th(i), 'userdata', userdata(nonzero_idx(i), :)); endfor set (th, 'visible', 'off'); ## row summary x_rs = y_cs; y_rs = x_cs; c_rs = [(round (63 * (main_values ./ rt_values)) + 64); (round (63 * (rd_values ./ rt_values)))]; c_rs(isnan (c_rs)) = 0; l_rs = [main_values; rd_values]; ph = patch (this.hax, x_rs, y_rs, c_rs); set (ph, 'userdata', 'RowSummary'); set (ph, 'visible', 'off'); userdata = [y_rs(1,:); x_rs(1,:)]'; nonzero_idx = find (l_rs != 0); th = text ((x_rs(1,nonzero_idx) + 0.5), (y_rs(1,nonzero_idx) + 0.5),... num2str (l_rs(nonzero_idx)), 'parent', this.hax); set (th, 'horizontalalignment', 'center'); for i = 1 : length (nonzero_idx) set (th(i), 'userdata', userdata(nonzero_idx(i), :)); endfor set (th, 'visible', 'off'); this.ColumnSummaryAbsoluteValues = l_cs; this.RowSummaryAbsoluteValues = l_rs; endfunction endmethods endclassdef %!demo %! ## Create a simple ConfusionMatrixChart Object %! %! cm = ConfusionMatrixChart (gca, [1 2; 1 2], {'A','B'}, {'XLabel','LABEL A'}) %! NormalizedValues = cm.NormalizedValues %! ClassLabels = cm.ClassLabels ## Test plotting %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! cm = ConfusionMatrixChart (gca, [1 2; 1 2], {'A','B'}, {'XLabel','LABEL A'}); %! assert_equal (isa (cm, 'ConfusionMatrixChart'), true); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect statistics-release-1.9.2/inst/Model_Evaluation/confusionchart.m000066400000000000000000000252161524624707500247640ustar00rootroot00000000000000## Copyright (C) 2020-2021 Stefano Guidoni ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} confusionchart (@var{trueLabels}, @var{predictedLabels}) ## @deftypefnx {statistics} {} confusionchart (@var{m}) ## @deftypefnx {statistics} {} confusionchart (@var{m}, @var{classLabels}) ## @deftypefnx {statistics} {} confusionchart (@var{parent}, @dots{}) ## @deftypefnx {statistics} {} confusionchart (@dots{}, @var{prop}, @var{val}, @dots{}) ## @deftypefnx {statistics} {@var{cm} =} confusionchart (@dots{}) ## ## Display a chart of a confusion matrix. ## ## The two vectors of values @var{trueLabels} and @var{predictedLabels}, which ## are used to compute the confusion matrix, must be defined with the same ## format as the inputs of @code{confusionmat}. ## Otherwise a confusion matrix @var{m} as computed by @code{confusionmat} can ## be given. ## ## @var{classLabels} is an array of labels, i.e. the list of the class names. ## ## If the first argument is a handle to a @code{figure} or to a @code{uipanel}, ## then the confusion matrix chart is displayed inside that object. ## ## Optional property/value pairs are passed directly to the underlying objects, ## e.g. @qcode{'xlabel'}, @qcode{'ylabel'}, @qcode{'title'}, @qcode{'fontname'}, ## @qcode{'fontsize'} etc. ## ## The optional return value @var{cm} is a @code{ConfusionMatrixChart} object. ## Specific properties of a @code{ConfusionMatrixChart} object are: ## @itemize @bullet ## @item @qcode{'DiagonalColor'} ## The color of the patches on the diagonal, default is [0.0, 0.4471, 0.7412]. ## ## @item @qcode{'OffDiagonalColor'} ## The color of the patches off the diagonal, default is [0.851, 0.3255, 0.098]. ## ## @item @qcode{'GridVisible'} ## Available values: @qcode{on} (default), @qcode{off}. ## ## @item @qcode{'Normalization'} ## Available values: @qcode{absolute} (default), @qcode{column-normalized}, ## @qcode{row-normalized}, @qcode{total-normalized}. ## ## @item @qcode{'ColumnSummary'} ## Available values: @qcode{off} (default), @qcode{absolute}, ## @qcode{column-normalized},@qcode{total-normalized}. ## ## @item @qcode{'RowSummary'} ## Available values: @qcode{off} (default), @qcode{absolute}, ## @qcode{row-normalized}, @qcode{total-normalized}. ## @end itemize ## ## Run @code{demo confusionchart} to see some examples. ## ## @seealso{confusionmat, sortClasses} ## @end deftypefn function cm = confusionchart (varargin) ## check the input parameters if (nargin < 1) print_usage (); endif p_i = 1; if (ishghandle (varargin{p_i})) ## parameter is a parent figure handle_type = get (varargin{p_i}, 'type'); if (strcmp (handle_type, 'figure')) h = figure (varargin{p_i}); hax = axes ('parent', h); elseif (strcmp (handle_type, 'uipanel')) h = varargin{p_i}; hax = axes ('parent', varargin{p_i}); else ## MATLAB compatibility: on MATLAB are also available Tab objects, ## TiledChartLayout objects, GridLayout objects error ("confusionchart: invalid handle to parent object"); endif p_i++; else h = figure (); hax = axes ('parent', h); endif if (ismatrix (varargin{p_i}) && rows (varargin{p_i}) == ... columns (varargin{p_i})) ## parameter is a confusion matrix conmat = varargin{p_i}; p_i++; if (p_i <= nargin && ((isvector (varargin{p_i}) && ... length (varargin{p_i}) == rows (conmat)) || ... (ischar ( varargin{p_i}) && rows (varargin{p_i}) == rows (conmat)) ... || iscellstr (varargin{p_i}))) ## parameter is an array of labels labarr = varargin{p_i}; if (isrow (labarr)) labarr = vec (labarr); endif p_i++; else labarr = [1 : (rows (conmat))]'; endif elseif (isvector (varargin{p_i})) ## parameter must be a group for confusionmat [conmat, labarr] = confusionmat (varargin{p_i}, varargin{p_i + 1}); p_i = p_i + 2; else close (h); error ("confusionchart: invalid argument"); endif ## remaining parameters are stored i = p_i; args = {}; while (i <= nargin) args{end + 1} = varargin{i++}; endwhile ## prepare the labels if (! iscellstr (labarr)) if (! ischar (labarr)) labarr = num2str (labarr); endif labarr = cellstr (labarr); endif ## MATLAB compatibility: labels are sorted [labarr, I] = sort (labarr); conmat = conmat(I, :); conmat = conmat(:, I); cm = ConfusionMatrixChart (hax, conmat, labarr, args); endfunction ## Demonstration using the confusion matrix example from ## R.Bonnin, "Machine Learning for Developers", pp. 55-56 %!demo %! close all %! ## Setting the chart properties %! Yt = [8 5 6 8 5 3 1 6 4 2 5 3 1 4]'; %! Yp = [8 5 6 8 5 2 3 4 4 5 5 7 2 6]'; %! confusionchart (Yt, Yp, 'Title', ... %! 'Demonstration with summaries','Normalization',... %! 'absolute','ColumnSummary', 'column-normalized','RowSummary',... %! 'row-normalized') ## example: confusion matrix and class labels %!demo %! close all %! ## Cellstr as inputs %! Yt = {'Positive', 'Positive', 'Positive', 'Negative', 'Negative'}; %! Yp = {'Positive', 'Positive', 'Negative', 'Negative', 'Negative'}; %! m = confusionmat (Yt, Yp); %! confusionchart (m, {'Positive', 'Negative'}); %! hold off ## example: editing the properties of an existing ConfusionMatrixChart object %!demo %! close all %! ## Editing the object properties %! Yt = {'Positive', 'Positive', 'Positive', 'Negative', 'Negative'}; %! Yp = {'Positive', 'Positive', 'Negative', 'Negative', 'Negative'}; %! cm = confusionchart (Yt, Yp); %! cm.Title = 'This is an example with a green diagonal'; %! cm.DiagonalColor = [0.4660, 0.6740, 0.1880]; %! hold off ## example: drawing the chart inside a uipanel %!demo %! close all %! ## Confusion chart in a uipanel %! h = uipanel (); %! Yt = {'Positive', 'Positive', 'Positive', 'Negative', 'Negative'}; %! Yp = {'Positive', 'Positive', 'Negative', 'Negative', 'Negative'}; %! cm = confusionchart (h, Yt, Yp); %! hold off ## example: sortClasses %!demo %! close all %! ## Sorting classes %! Yt = [8 5 6 8 5 3 1 6 4 2 5 3 1 4]'; %! Yp = [8 5 6 8 5 2 3 4 4 5 5 7 2 6]'; %! cm = confusionchart (Yt, Yp, 'Title', ... %! 'Classes are sorted in ascending order'); %! cm = confusionchart (Yt, Yp, 'Title', ... %! 'Classes are sorted according to clusters'); %! sortClasses (cm, 'cluster'); ## Test input validation ## Get current figure visibility so it can be restored after tests %!shared visibility_setting %! visibility_setting = get (0, 'DefaultFigureVisible'); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ()', 'Invalid call'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 1; 2 2; 3 3])', 'invalid argument'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''xxx'', 1)', 'invalid property'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''XLabel'', 1)', 'XLabel .* string'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''YLabel'', [1 0])', ... %! '.* YLabel .* string'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''Title'', .5)', '.* Title .* string'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''FontName'', [])', ... %! '.* FontName .* string'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''FontSize'', ''b'')', ... %! '.* FontSize .* numeric'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''DiagonalColor'', ''h'')', ... %! '.* DiagonalColor .* color'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''OffDiagonalColor'', [])', ... %! '.* OffDiagonalColor .* color'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''Normalization'', '''')', ... %! '.* invalid .* Normalization'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''ColumnSummary'', [])', ... %! '.* invalid .* ColumnSummary'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''RowSummary'', 1)', ... %! '.* invalid .* RowSummary'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''GridVisible'', .1)', ... %! '.* invalid .* GridVisible'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''HandleVisibility'', .1)', ... %! '.* invalid .* HandleVisibility'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''OuterPosition'', .1)', ... %! '.* invalid .* OuterPosition'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''Position'', .1)', ... %! '.* invalid .* Position'); %! set (0, 'DefaultFigureVisible', visibility_setting); %!test %! set (0, 'DefaultFigureVisible', 'off'); %! fail ('confusionchart ([1 2], [0 1], ''Units'', .1)', '.* invalid .* Units'); %! set (0, 'DefaultFigureVisible', visibility_setting); statistics-release-1.9.2/inst/Model_Evaluation/confusionmat.m000066400000000000000000000347671524624707500244570ustar00rootroot00000000000000## Copyright (C) 2020 Stefano Guidoni ## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{C} =} confusionmat (@var{group}, @var{grouphat}) ## @deftypefnx {statistics} {@var{C} =} confusionmat (@var{group}, @var{grouphat}, "Order", @var{grouporder}) ## @deftypefnx {statistics} {[@var{C}, @var{order}] =} confusionmat (@var{group}, @var{grouphat}) ## ## Compute a confusion matrix for classification problems ## ## @code{confusionmat} returns the confusion matrix @var{C} for the group of ## actual values @var{group} and the group of predicted values @var{grouphat}. ## The row indices of the confusion matrix represent actual values, while the ## column indices represent predicted values. The indices are the same for both ## actual and predicted values, so the confusion matrix is a square matrix. ## Each element of the matrix represents the number of matches between a given ## actual value (row index) and a given predicted value (column index), hence ## correct matches lie on the main diagonal of the matrix. ## The order of the rows and columns is returned in @var{order}. ## ## @var{group} and @var{grouphat} must have the same number of observations ## and the same data type. ## Valid data types are numeric vectors, logical vectors, character arrays, ## string arrays, cell arrays of strings, and categorical arrays. ## ## The order of the rows and columns can be specified by setting the ## @var{grouporder} variable. The data type of @var{grouporder} must be the ## same of @var{group} and @var{grouphat}. ## ## @seealso{crosstab} ## @end deftypefn function [C, order] = confusionmat (group, grouphat, opt = 'Order', grouporder) ## check the input parameters if ((nargin < 2) || (nargin > 4)) print_usage (); endif y_true = group; y_pred = grouphat; if (! strcmp (class (y_true), class (y_pred))) error ("confusionmat: group and grouphat must be of the same data type."); endif obs_true = length (y_true); if (ischar (y_true)) obs_true = rows (y_true); endif obs_pred = length (y_pred); if (ischar (y_pred)) obs_pred = rows (y_pred); endif if (obs_true != obs_pred) error ("confusionmat: group and grouphat must be of the same length."); endif if ((nargin > 3) && strcmp (opt, 'Order')) unique_tokens = grouporder; if (! strcmp (class (y_true), class (unique_tokens))) error ("confusionmat: group and grouporder must be of the same data type."); endif endif convert_order_to_char = false; ## Handle y_true if (ischar (y_true)) y_true = cellstr (y_true); convert_order_to_char = true; elseif (isvector (y_true)) y_true = y_true(:); else error ("confusionmat: group must be a vector or character array."); endif ## Handle y_pred if (ischar (y_pred)) y_pred = cellstr (y_pred); elseif (isvector (y_pred)) y_pred = y_pred(:); else error ("confusionmat: grouphat must be a vector or character array."); endif if (exist ( 'unique_tokens', 'var')) if (ischar (unique_tokens)) unique_tokens = cellstr (unique_tokens); elseif (isvector (unique_tokens)) unique_tokens = unique_tokens(:); else error ("confusionmat: grouporder must be a vector or character array."); endif endif ## compute the confusion matrix if (isa (y_true, 'numeric') || isa (y_true, 'logical')) ## Remove observations where EITHER group or grouphat is NaN nan_indices = isnan (y_true) | isnan (y_pred); y_true(nan_indices) = []; y_pred(nan_indices) = []; ## numeric and boolean values are sorted in ascending order if (! exist ('unique_tokens', 'var')) unique_tokens = unique ([y_true; y_pred]); endif C_size = length (unique_tokens); C = zeros (C_size); for i = 1:length (y_true) row_index = find (unique_tokens == y_true(i)); col_index = find (unique_tokens == y_pred(i)); ## Check valid indices if (! isempty (row_index) && ! isempty (col_index)) C(row_index, col_index)++; endif endfor elseif (iscellstr (y_true)) ## string cells ## remove observations where EITHER input is empty empty_indices = cellfun ('isempty', y_true) | cellfun ('isempty', y_pred); y_true(empty_indices) = []; y_pred(empty_indices) = []; ## string values are sorted according to their ## first appearance in group and grouphat if (! exist ('unique_tokens', 'var')) all_tokens = [y_true; y_pred]; if (isempty (all_tokens)) unique_tokens = {}; else [~, idx] = unique (all_tokens, 'first'); unique_tokens = all_tokens(sort (idx)); endif endif C_size = length (unique_tokens); C = zeros (C_size); for i = 1:length (y_true) row_index = find (strcmp (y_true{i}, unique_tokens)); col_index = find (strcmp (y_pred{i}, unique_tokens)); if (! isempty (row_index) && ! isempty (col_index)) C(row_index, col_index)++; endif endfor elseif (ischar (y_true)) ## character values are sorted according to their ## first appearance in group and grouphat if (! exist ('unique_tokens', 'var')) all_tokens = vertcat (y_true, y_pred); unique_tokens = [all_tokens(1)]; for i = 2:length (all_tokens) if (! any (find (unique_tokens == all_tokens(i)))) unique_tokens = [unique_tokens; all_tokens(i)]; endif endfor endif C_size = length ( unique_tokens ); C = zeros ( C_size ); for i = 1:length ( y_true) row_index = find (unique_tokens == y_true(i)); col_index = find (unique_tokens == y_pred(i)); C(row_index, col_index)++; endfor elseif (isa (y_true, 'string')) ## 1. Filter Missing Values bad_indices = ismissing (y_true) | ismissing (y_pred); y_true(bad_indices) = []; y_pred(bad_indices) = []; ## 2. Determine Order ## String arrays are sorted ALPHABETICALLY by unique(). if (! exist ('unique_tokens', 'var')) all_tokens = [y_true; y_pred]; if (isempty (all_tokens)) unique_tokens = strings (0, 1); else unique_tokens = unique (all_tokens); endif endif C_size = length (unique_tokens); C = zeros (C_size); [~, row_indices] = ismember (y_true, unique_tokens); [~, col_indices] = ismember (y_pred, unique_tokens); valid_mask = (row_indices > 0) & (col_indices > 0); row_indices = row_indices(valid_mask); col_indices = col_indices(valid_mask); for i = 1:length (row_indices) C(row_indices(i), col_indices(i))++; endfor elseif (isa (y_true, 'categorical')) ## 1. Filter Undefined Values bad_indices = isundefined (y_true) | isundefined (y_pred); y_true(bad_indices) = []; y_pred(bad_indices) = []; ## 2. Determine Order if (! exist ('unique_tokens', 'var')) ## This ensures the matrix includes all defined categories cats_true = categories (y_true); cats_pred = categories (y_pred); ## Union of defined categories all_cats = union (cats_true, cats_pred, 'stable'); ## Create the reference order vector unique_tokens = categorical (all_cats, all_cats); endif C_size = length (unique_tokens); C = zeros (C_size); [~, row_indices] = ismember (y_true, unique_tokens); [~, col_indices] = ismember (y_pred, unique_tokens); valid_mask = (row_indices > 0) & (col_indices > 0); row_indices = row_indices(valid_mask); col_indices = col_indices(valid_mask); for i = 1:length (row_indices) C(row_indices(i), col_indices(i))++; endfor else error ("confusionmat: invalid data type."); endif order = unique_tokens; if (convert_order_to_char && iscellstr (order)) order = char (order); endif endfunction ## Test the confusion matrix example from ## R.Bonnin, "Machine Learning for Developers", pp. 55-56 %!test %! Yt = [8 5 6 8 5 3 1 6 4 2 5 3 1 4]'; %! Yp = [8 5 6 8 5 2 3 4 4 5 5 7 2 6]'; %! C = [0 1 1 0 0 0 0 0; 0 0 0 0 1 0 0 0; 0 1 0 0 0 0 1 0; 0 0 0 1 0 1 0 0; ... %! 0 0 0 0 3 0 0 0; 0 0 0 1 0 1 0 0; 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 2]; %! assert_equal (confusionmat (Yt, Yp), C) ## Test 2: Basic Integers %!test %! g = [1; 2; 3; 1]; %! gh = [1; 2; 2; 1]; %! [C, order] = confusionmat (g, gh); %! assert_equal (C, [2 0 0; 0 1 0; 0 1 0]); %! assert_equal (order, [1; 2; 3]); ## Test 3: Logical Vectors %!test %! g = [true; false; true; false]; %! gh = [true; true; false; false]; %! [C, order] = confusionmat (g, gh); %! assert_equal (C, [1 1; 1 1]); %! assert_equal (order, [false; true]); ## Test 4: Floating Point Numbers %!test %! g = [1.1; 2.2; 1.1]; %! gh = [1.1; 2.2; 2.2]; %! [C, order] = confusionmat (g, gh); %! assert_equal (C, [1 1; 0 1]); %! assert_equal (order, [1.1; 2.2]); ## Test 5: Numeric with NaNs %!test %! g = [1; 2; NaN; 3]; %! gh = [1; 1; 2; 3]; %! [C, order] = confusionmat (g, gh); %! assert_equal (C, [1 0 0; 1 0 0; 0 0 1]); %! assert_equal (order, [1; 2; 3]); ## Test 6: Empty Inputs %!error %! confusionmat ([], []) ## Test 7: Scalar Inputs %!test %! [C, order] = confusionmat (1, 1); %! assert_equal (C, 1); %! assert_equal (order, 1); ## Test 8: Cell Array with Empty Strings %!test %! g = {'A'; ''; 'B'}; %! gh = {'A'; 'B'; 'B'}; %! [C, order] = confusionmat (g, gh); %! assert_equal (C, [1 0; 0 1]); %! assert_equal (order, {'A'; 'B'}); ## Test 9: Character Arrays %!test %! g = ['AA'; 'BB'; 'AA'; 'CC']; %! gh = ['AA'; 'BB'; 'BB'; 'CC']; %! [C, order] = confusionmat (g, gh); %! assert_equal (C, [1 1 0; 0 1 0; 0 0 1]); %! assert_equal (order, ['AA'; 'BB'; 'CC']); ## Test 10: Character Arrays (Whitespace Handling) %!test %! g = char ('A', 'B', 'A'); %! gh = char ('A', 'A', 'B'); %! [C, order] = confusionmat (g, gh); %! assert_equal (C, [1 1; 1 0]); %! assert_equal (order, char ('A', 'B')); ## Test 11: Cell Array of Strings %!test %! g = {'Cat'; 'Dog'; 'Cat'; 'Bird'}; %! gh = {'Cat'; 'Cat'; 'Bird'; 'Bird'}; %! [C, order] = confusionmat (g, gh); %! assert_equal (C, [1 0 1; 1 0 0; 0 0 1]); %! assert_equal (order, {'Cat'; 'Dog'; 'Bird'}); ## Test 12: String Arrays %!test %! g = ['Apple'; 'Banana'; 'Apple']; %! gh = ['Apple'; 'Apple'; 'Cherry']; %! [C, order] = confusionmat (g, gh); %! assert_equal (C, [1 0 1; 1 0 0; 0 0 0]); %! assert_equal (order, ['Apple'; 'Banana'; 'Cherry']); ## Test 13: String Arrays (Missing Values) %!test %! g = string ({'A'; 'B'; 'B'}); %! g(2) = missing; %! gh = string (['A'; 'B'; 'B']); %! [C, order] = confusionmat (g, gh); %! assert_equal (C, [1 0; 0 1]); %! assert_equal (isequal (order, string (['A'; 'B'])), true); ## Test 14: Categorical Arrays %!test %! g = categorical ({'Small', 'Medium', 'Large'}); %! gh = categorical ({'Small', 'Large', 'Large'}); %! [C, order] = confusionmat (g, gh); %! assert_equal (C, [1 0 0; 1 0 0; 0 0 1]); %! assert_equal (cellstr (char (order)), {'Large'; 'Medium'; 'Small'}); ## Test 15: Categorical (Undefined Values / NaN) %!test %! g = categorical ({'Red', 'Blue', 'Red'}); %! g(2) = missing; %! gh = categorical ({'Red', 'Blue', 'Red'}); %! [C, order] = confusionmat (g, gh); %! assert_equal (C, [0 0; 0 2]); %! assert_equal (cellstr (char (order)), {'Blue'; 'Red'}); ## Test 16: Categorical (Unused Categories) %!test %! vals = {'A', 'B', 'A'}; %! cats = {'A', 'B', 'C'}; %! g = categorical (vals, cats); %! gh = categorical (vals, cats); %! [C, order] = confusionmat (g, gh); %! assert_equal (size (C), [3 3]); %! assert_equal (C(3,3), 0); %! assert_equal (cellstr (char (order)), {'A'; 'B'; 'C'}); ## Test 17: Categorical (Union of Categories) %!test %! g = categorical ({'A'}, {'A', 'B'}); %! gh = categorical ({'A'}, {'A', 'C'}); %! [C, order] = confusionmat (g, gh); %! assert_equal (size (C), [3 3]); %! assert_equal (cellstr (char (order)), {'A'; 'B'; 'C'}); ## Test 18: Row vs Column Vector %!test %! g = [1, 2, 3]; %! gh = [1; 2; 3]; %! [C, order] = confusionmat (g, gh); %! assert_equal (C, eye (3)); %! assert_equal (order, [1; 2; 3]); ## Test 19: Custom Order %!test %! g = [1; 2; 3]; %! gh = [1; 2; 3]; %! myOrder = [3; 2; 1]; %! [C, order] = confusionmat (g, gh, 'Order', myOrder); %! assert_equal (C, [1 0 0; 0 1 0; 0 0 1]); %! assert_equal (order, [3; 2; 1]); ## Test 20: Custom Order (Reordering Strings) %!test %! g = {'A'; 'B'}; %! gh = {'A'; 'B'}; %! [C, order] = confusionmat (g, gh, 'Order', {'B'; 'A'}); %! assert_equal (C, [1 0; 0 1]); %! assert_equal (order, {'B'; 'A'}); ## Test 21: Custom Order (Subset / Filtering) %!test %! g = [1; 2; 3]; %! gh = [1; 2; 3]; %! [C, order] = confusionmat (g, gh, 'Order', [1; 2]); %! assert_equal (C, eye (2)); %! assert_equal (order, [1; 2]); ## Test 22: Custom Order (Superset / Adding empty rows) %!test %! g = [1; 2]; %! gh = [1; 2]; %! [C, order] = confusionmat (g, gh, 'Order', [1; 2; 4]); %! assert_equal (C, [1 0 0; 0 1 0; 0 0 0]); %! assert_equal (order, [1; 2; 4]); ## Test 23: All Mismatch %!test %! g = [1; 1; 1]; %! gh = [2; 2; 2]; %! [C, order] = confusionmat (g, gh); %! assert_equal (C, [0 3; 0 0]); %! assert_equal (order, [1; 2]); ## Test 24: Single Class Present %!test %! g = [1; 1; 1]; %! gh = [1; 1; 1]; %! [C, order] = confusionmat (g, gh); %! assert_equal (C, 3); %! assert_equal (order, 1); ## Teset input validation %!error %! confusionmat ([1; 2], {'A'; 'B'}) %!error %! confusionmat ('A', [1]) %!error %! confusionmat ([1; 2; 3], [1; 2]) %!error %! confusionmat ([1; 2], [1; 2], 'Order', {'A'; 'B'}) %!error %! confusionmat ({'A'}, {'A'}, 'Order', [1]) %!error %! confusionmat (eye (2), eye (2)) %!error confusionmat ({1; 2}, {1; 2}) statistics-release-1.9.2/inst/Model_Evaluation/crossval.m000066400000000000000000000322121524624707500235650ustar00rootroot00000000000000## Copyright (C) 2014 Nir Krakauer ## Copyright (C) 2025 Yassin Achengli ## Copyright (C) 2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; If not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{results} =} crossval (@var{f}, @var{X}, @var{y}) ## @deftypefnx {statistics} {@var{results} =} crossval (@var{f}, @var{X}, @var{y}, @var{name}, @var{value}) ## ## Perform cross validation on given data. ## ## @var{f} should be a function that takes 4 inputs @var{xtrain}, @var{ytrain}, ## @var{xtest}, @var{ytest}, fits a model based on @var{xtrain}, @var{ytrain}, ## applies the fitted model to @var{xtest}, and returns a goodness of fit ## measure based on comparing the predicted and actual @var{ytest}. ## @code{crossval} returns an array containing the values returned by @var{f} ## for every cross-validation fold or resampling applied to the given data. ## ## @var{X} should be an @var{n} by @var{m} matrix of predictor values ## ## @var{y} should be an @var{n} by @var{1} vector of predicand values ## ## Optional arguments may include name-value pairs as follows: ## ## @table @asis ## @item @qcode{'KFold'} ## Divide set into @var{k} equal-size subsets, using each one successively ## for validation. ## ## @item @qcode{'HoldOut'} ## Divide set into two subsets, training and validation. If the value ## @var{k} is a fraction, that is the fraction of values put in the ## validation subset (by default @var{k}=0.1); if it is a positive integer, ## that is the number of values in the validation subset. ## ## @item @qcode{'LeaveOut'} ## Leave-one-out partition (each element is placed in its own subset). ## The value is ignored, but it is required. ## ## @item @qcode{'Partition'} ## The value should be a @var{cvpartition} object. ## ## @item @qcode{'Given'} ## The value should be an @var{n} by @var{1} vector specifying in which ## partition to put each element. ## ## @item @qcode{'stratify'} ## The value should be an @var{n} by @var{1} vector containing class ## designations for the elements, in which case the @qcode{'KFold'} and ## @qcode{'HoldOut'} partitionings attempt to ensure each partition ## represents the classes proportionately. ## ## @item @qcode{'mcreps'} ## The value should be a positive integer specifying the number of times ## to resample based on different partitionings. Currently only works with ## the partition type @qcode{'HoldOut'}. ## ## @end table ## ## Only one of @qcode{'KFold'}, @qcode{'HoldOut'}, @qcode{'LeaveOut'}, ## @qcode{'Given'}, @qcode{'Partition'} should be specified. If none is ## specified, the default is @qcode{'KFold'} with @var{k} = 10. ## ## @seealso{cvpartition} ## @end deftypefn function results = crossval (f, varargin) ## Parse optional Name-Value paired arguments optNames = {'Holdout', 'KFold', 'Leaveout', 'MCReps', ... 'Partition', 'Stratify', 'Predfun'}; dfValues = {[], [], [], 1, [], [], []}; [Holdout, KFold, Leaveout, MCReps, Partition, Stratify, Predfun, args] = ... parsePairedArguments (optNames, dfValues, varargin(:)); ## Check first input argument if (ischar (f)) ## Check for valid criterion if (! ismember (f, {'mse',',mcr'})) error ("crossval: criterion must be 'mse' or 'mcr'."); endif ## Check for user supplied prediction function handle if (! is_function_handle (Predfun)) error (strcat ("crossval: prediction function handle", ... " is required for error evaluation.")); endif ## At least two additional input arguments (X and y) are required nargs = numel (args); if (nargs < 2) error ("crossval: X and Y are required for error evaluation."); endif y = args{end}; if (nargs == 2) X = args{1}; # numeric matrix with single data variable n = size (X, 1); is_cellarray = false; else X = args(1:end-1); # cell array with multiple data variables n = size (args{1}, 1); is_cellarray = true; endif ## Check for valid prediction function handle on user data try if (is_cellarray) yFit = Predfun(X{:}, y, X{:}); else yFit = Predfun(X, y, X); endif catch error ("crossval: bad prediction function handle for error evaluation."); end_try_catch if (! iscolumn (yFit) || numel (yFit) != numel (y)) error (strcat ("crossval: prediction function handle must return", ... " a column vector with the same rows as XTest.")); endif elseif (is_function_handle (f)) ## At least one additional input argument (X) is required nargs = numel (args); if (nargs < 1) error ("crossval: X is required for values evaluation."); endif if (nargs == 1) X = args{1}; # numeric matrix with single data variable n = size (X, 1); is_cellarray = false; else X = args; # cell array with multiple data variables n = size (args{1}, 1); is_cellarray = true; endif ## Check for valid function handle on user data. Every data variable is ## passed for the training set and again for the test set, so a handle over ## X and Y is called as f (Xtrain, Ytrain, Xtest, Ytest). try if (is_cellarray) value = f(X{:}, X{:}); else value = f(X, X); endif catch error ("crossval: bad function handle to cross-validate."); end_try_catch if (isscalar (value)) is_scalar = true; elseif (isrow (value)) is_scalar = false; else error ("crossval: function handle must return a scalar or a row vector."); endif else error ("crossval: invalid first input argument."); endif ## Input validation for valid values are handled by the cvpartition class. ## Check for single paired argument for CV partition. vcpa = sum ([isempty(Holdout), isempty(KFold), ... isempty(Leaveout), isempty(Partition)]); if (vcpa < 3) # at least 3 must be empty error (strcat ("crossval: you can only set one", ... " cvpartition type in paired arguments.")); endif ## Check for Partition and Stratify paired arguments if (! isempty (Partition) && ! isempty (Stratify)) error ("crossval: you cannot specify both 'Partition' and 'Stratify'."); endif ## Construct the CV partition if (! isempty (Partition)) P = Partition; nSets = P.NumTestSets; if (P.IsCustom || ismember (P.Type, {'resubstitution', 'leaveout'})) MCReps = 1; endif elseif (! isempty (Leaveout)) P = cvpartition (n, 'LeaveOut'); nSets = P.NumTestSets; MCReps = 1; elseif (! isempty (Holdout)) if (isempty (Stratify)) P = cvpartition (n, 'HoldOut', Holdout); else P = cvpartition (Stratify, 'HoldOut', Holdout); endif nSets = P.NumTestSets; elseif (! isempty (KFold)) if (isempty (Stratify)) P = cvpartition (n, 'KFold', KFold); else P = cvpartition (Stratify, 'KFold', KFold); endif nSets = P.NumTestSets; else # KFold by default if (isempty (Stratify)) P = cvpartition (n, 'KFold'); else P = cvpartition (Stratify, 'KFold'); endif nSets = P.NumTestSets; endif ## Apply cross-validation scheme if (ischar (f)) # error evaluation results = nan (MCReps, nSets); for rep = 1:MCReps if (rep > 1) P = repartition (P); endif for idx = 1:nSets idx_train = training (P, idx); idx_test = test (P, idx); if (is_cellarray) Xtrain = cellfun (@(x) x(idx_train, :), X, 'UniformOutput', false); Xtest = cellfun (@(x) x(idx_test, :), X, 'UniformOutput', false); y_fit = Predfun(Xtrain{:}, y(idx_train), Xtest{:}); else y_fit = Predfun(X(idx_train, :), y(idx_train), X(idx_test, :)); endif if (strcmp (f, 'mse')) err = sum ((y_fit - y(idx_test)).^2) / numel (y_fit); results(rep, idx) = err; else # MCR err = sum (y_fit == y(idx_test)) / numel (y_fit); results(rep, idx) = err; endif endfor endfor results = mean (mean (results)); else # model execution if (is_scalar) results = nan (MCReps, nSets); for rep = 1:MCReps if (rep > 1) P = repartition (P); endif for idx = 1:nSets idx_train = training (P, idx); idx_test = test (P, idx); if (is_cellarray) Xtrain = cellfun (@(x) x(idx_train, :), X, 'UniformOutput', false); Xtest = cellfun (@(x) x(idx_test, :), X, 'UniformOutput', false); result = f(Xtrain{:}, Xtest{:}); else result = f(X(idx_train, :), X(idx_test, :)); endif results(rep, idx) = result; endfor endfor else # concatenate Monte Carlo repetitions along first dimension results = []; for rep = 1:MCReps if (rep > 1) P = repartition (P); endif tmpresults = []; for idx = 1:nSets idx_train = training (P, idx); idx_test = test (P, idx); if (is_cellarray) Xtrain = cellfun (@(x) x(idx_train, :), X, 'UniformOutput', false); Xtest = cellfun (@(x) x(idx_test, :), X, 'UniformOutput', false); result = f(Xtrain{:}, Xtest{:}); else result = f(X(idx_train, :), X(idx_test, :)); endif tmpresults = [tmpresults; result]; endfor results = [results; tmpresults]; endfor endif endif endfunction %!demo %! ## Determine the optimal number of clusters using cross-validation %! %! ## Declare a function to compute the sum of squared distances %! ## between data points and a varying number of clusters. %! function D = dist2clusters (X, Y, k) %! [Z, Zmu, Zstd] = zscore (X); %! [~, C] = kmeans (Z, k); %! ZY = (Y - Zmu) ./ Zstd; %! d = pdist2 (C, ZY, 'euclidean', 'Smallest', 1); %! D = sum (d .^ 2); %! endfunction %! %! load fisheriris %! for k = 1:8 %! fcn = @(X, Y) dist2clusters (X, Y, k); %! distances = crossval (fcn, meas); %! cvdist(k) = sum (distances); %! endfor %! %! plot (cvdist) %! xlabel ('Number of Clusters') %! ylabel ('CV Sum of Squared Distances') %! xlim ([1,8]); ## Test output %!test %! function yfit = regf (Xtrain, ytrain, Xtest) %! b = regress (ytrain, Xtrain); %! yfit = Xtest * b; %! endfunction %! %! load carsmall %! data = [Acceleration Horsepower Weight MPG]; %! data(any (isnan (data),2),:) = []; %! %! y = data(:,4); %! X = [ones(length(y),1) data(:,1:3)]; %! rand ('seed', 3); %! cvMSE = crossval ('mse',X,y,'Predfun',@regf); %! assert_equal (cvMSE, 18.720, 1e-3); ## Test input validation %!test %! ## With a response variable the handle is called over both, as %! ## f (Xtrain, Ytrain, Xtest, Ytest); Y used to be dropped and the handle %! ## called with two arguments, so any supervised handle was refused. %! X = [1, 2; 2, 3; 3, 4; 4, 5; 5, 6; 6, 7; 7, 8; 8, 9; 9, 10; 10, 11]; %! Y = (1:10)'; %! f = @(xtr, ytr, xte, yte) size (xtr, 1) + size (xte, 1) + numel (ytr) + numel (yte); %! r = crossval (f, X, Y, 'KFold', 5); %! assert_equal (size (r), [1, 5]); %! assert_equal (unique (r), 20); %!error ... %! crossval ('fe', rand (10, 1), rand (10, 1), 1); %!error ... %! crossval ('mse', rand (10, 1), rand (10, 1), 1); %!error ... %! crossval ('mse', rand (10, 1), 'Predfun', @(x,y) x + y); %!error ... %! crossval ('mse', rand (10, 3), rand (10, 1), 'Predfun', @(x,y) sum (x + y)); %!error ... %! crossval ('mse', rand (10, 3), rand (10, 1), 'Predfun', @(x,y,z) sum (x + y)); %!error crossval (@(x) x); %!error ... %! crossval (@(x) x, rand (10, 3), rand (10, 1)); %!error ... %! crossval (@(xtr, ytr, xte, yte) [xte, yte], rand (10, 3), rand (10, 1)); %!error crossval ({1}, 1, 1); %!error ... %! crossval (@(x,y) sum ([x; y]), rand (10, 3), 'Holdout', 0.1, 'Leaveout', true) %!error ... %! crossval (@(x,y) sum ([x; y]), rand (10, 3), 'Partition', cvpartition (10, 'Leaveout'), 'Stratify', true) statistics-release-1.9.2/inst/Model_Evaluation/cvpartition.m000066400000000000000000003474171524624707500243130ustar00rootroot00000000000000## Copyright (C) 2025 Andreas Bertsatos ## Copyright (C) 2025 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef cvpartition ## -*- texinfo -*- ## @deftp {statistics} cvpartition ## ## Partition data for cross-validation ## ## The @code{cvpartition} class generates a partitioning scheme on a dataset ## to facilitate cross-validation of statistical models utilizing training and ## testing subsets of the dataset. ## ## @seealso{crossval} ## @end deftp properties(GetAccess = public, SetAccess = private) ## -*- texinfo -*- ## @deftp {cvpartition} {property} NumObservations ## ## Number of observations ## ## A positive integer scalar specifying the number of observations in the ## dataset (including any missing data, where applicable). This property ## is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {cvpartition} {property} NumTestSets ## ## Number of test sets ## ## A positive integer scalar specifying the number of folds for partition ## types @qcode{'kfold'} and @qcode{'leaveout'}. When partition type is ## @qcode{'holdout'} and @qcode{'resubstitution'}, then @qcode{NumTestSets} ## is 1. This property is read-only. ## ## @end deftp NumTestSets = []; ## -*- texinfo -*- ## @deftp {cvpartition} {property} TrainSize ## ## Size of each train set ## ## A positive integer scalar specifying the size of the train set for ## partition types @qcode{'holdout'} and @qcode{'resubstitution'} or a ## vector of positive integers specifying the size of each training set for ## partition types @qcode{'kfold'} and @qcode{'leaveout'}. This property ## is read-only. ## ## @end deftp TrainSize = []; ## -*- texinfo -*- ## @deftp {cvpartition} {property} TestSize ## ## Size of each test set ## ## A positive integer scalar specifying the size of the test set for ## partition types @qcode{'holdout'} and @qcode{'resubstitution'} or a ## vector of positive integers specifying the size of each testing set for ## partition types @qcode{'kfold'} and @qcode{'leaveout'}. This property ## is read-only. ## ## @end deftp TestSize = []; ## -*- texinfo -*- ## @deftp {cvpartition} {property} Type ## ## Type of validation partition ## ## A character vector specifying the type of the @qcode{cvpartition} object. ## It can be @qcode{kfold}, @qcode{holdout}, @qcode{leaveout}, or ## @qcode{resubstitution}. This property is read-only. ## ## @end deftp Type = ''; ## -*- texinfo -*- ## @deftp {cvpartition} {property} IsCustom ## ## Flag for custom partition ## ## A logical scalar specifying whether the @qcode{cvpartition} object ## was created using custom partition partitioning (@qcode{true}) or ## not (@qcode{false}). This property is read-only. ## ## @end deftp IsCustom = []; ## -*- texinfo -*- ## @deftp {cvpartition} {property} IsGrouped ## ## Flag for grouped partition ## ## A logical scalar specifying whether the @qcode{cvpartition} object was ## created using grouping variables (@qcode{true}) or not (@qcode{false}). ## This property is read-only. ## ## @end deftp IsGrouped = []; ## -*- texinfo -*- ## @deftp {cvpartition} {property} IsStratified ## ## Flag for stratified partition ## ## A logical scalar specifying whether the @qcode{cvpartition} object was ## created with a @qcode{'stratify'} value of @qcode{true}. ## This property is read-only. ## ## @end deftp IsStratified = []; endproperties properties(Access = private, Hidden) missidx = []; indices = []; cvptype = ''; classes = []; classID = []; grpvars = []; endproperties methods(Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n%s\n", this.cvptype); ## Print selected properties fprintf ("%+25s: %d\n", 'NumObservations', this.NumObservations); fprintf ("%+25s: %d\n", 'NumTestSets', this.NumTestSets); vlen = numel (this.TrainSize); if (vlen <= 10) str = repmat ({'%d'}, 1, vlen); str = strcat ('[', strjoin (str, ' '), ']'); str1 = sprintf (str, this.TrainSize); str2 = sprintf (str, this.TestSize); else str = repmat ({'%d'}, 1, 10); str = strcat ('[', strjoin (str, ' '), ' ... ]'); str1 = sprintf (str, this.TrainSize(1:10)); str2 = sprintf (str, this.TestSize(1:10)); endif fprintf ("%+25s: %s\n", 'TrainSize', str1); fprintf ("%+25s: %s\n", 'TestSize', str2); fprintf ("%+25s: %d\n", 'IsCustom', this.IsCustom); fprintf ("%+25s: %d\n", 'IsGrouped', this.IsGrouped); fprintf ("%+25s: %d\n\n", 'IsStratified', this.IsStratified); endfunction ## Class specific subscripted reference function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); t = 'Invalid %s indexing for referencing values in a cvpartition object.'; switch (s.type) case '()' error (t, '()'); case '{}' error (t, '{}'); case '.' if (! ischar (s.subs)) error (strcat ("cvpartition.subsref: '.' indexing", ... " argument must be a character vector.")); endif try out = this.(s.subs); catch error ("cvpartition.subref: unrecognized property: '%s'", s.subs); end_try_catch endswitch ## Chained references if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction ## Class specific subscripted assignment function this = subsasgn (this, s, val) if (numel (s) > 1) error (strcat ("cvpartition.subsasgn:", ... " chained subscripts not allowed.")); endif t = 'Invalid %s indexing for assigning values to a cvpartition object.'; switch s.type case '()' error (t, '()'); case '{}' error (t, '{}'); case '.' if (! ischar (s.subs)) error (strcat ("cvpartition.subsasgn: '.' indexing", ... " argument must be a character vector.")); endif error (strcat ("cvpartition.subsasgn: unrecognized", ... " or read-only property: '%s'"), s.subs); endswitch endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {cvpartition} {@var{C} =} cvpartition (@var{n}, @qcode{'KFold'}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{n}, @qcode{'KFold'}, @var{k}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{n}, @qcode{'KFold'}, @var{k}, @qcode{'GroupingVariables'}, @var{grpvars}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{n}, @qcode{'Holdout'}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{n}, @qcode{'Holdout'}, @var{p}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{n}, @qcode{'Holdout'}, @var{p}, @qcode{'GroupingVariables'}, @var{grpvars}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{n}, @qcode{'Leaveout'}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{n}, @qcode{'Leaveout'}, @qcode{'GroupingVariables'}, @var{grpvars}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{n}, @qcode{'Resubstitution'}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{X}, @qcode{'KFold'}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{X}, @qcode{'KFold'}, @var{k}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{X}, @qcode{'KFold'}, @var{k}, @qcode{'Stratify'}, @var{opt}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{X}, @qcode{'Holdout'}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{X}, @qcode{'Holdout'}, @var{p}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@var{X}, @qcode{'Holdout'}, @var{p}, @qcode{'Stratify'}, @var{opt}) ## @deftypefnx {cvpartition} {@var{C} =} cvpartition (@qcode{'CustomPartition'}, @var{testSets}) ## ## Repartition data for cross-validation. ## ## @code{@var{C} = cvpartition (@var{n}, @qcode{'KFold'})} creates a ## @qcode{cvpartition} object @var{C}, which defines a random nonstratified ## partition for k-fold cross-validation on @var{n} observations with each ## fold (subsample) having approximately the same number of observations. ## The default number of folds is 10 for @code{@var{n} >= 10} or equal to ## @var{n} otherwise. ## ## @code{@var{C} = cvpartition (@var{n}, @qcode{'KFold'}, @var{k})} also ## creates a nonstratified random partition for k-fold cross-validation with ## the number of folds defined by @var{k}, which must be a positive integer ## scalar smaller than the number of observations @var{n}. ## ## @code{@var{C} = cvpartition (@var{n}, @qcode{'KFold'}, @var{k}, ## @qcode{'GroupingVariables'}, @var{grpvars})} creates a ## @qcode{cvpartition} ## object @var{C} that defines a random partition for k-fold ## cross-validation in which every observation sharing a group label, as ## defined by @var{grpvars}, is assigned to the same fold. No group is ## split between the training and test sets, so a fold holds out whole ## groups; this is the partition scikit-learn calls @code{GroupKFold}, and ## it is what you want when observations within a group are not ## independent, such as repeated measurements of one subject. It is not ## stratification: a fold does not contain a proportional mix of the group ## labels, and @qcode{'Stratify'} is the option for that. The grouping ## variables specified in @var{grpvars} can be one of the following: ## ## @itemize ## @item A numeric vector, logical vector, categorical vector, character ## array, string array, or cell array of character vectors containing one ## grouping variable. ## @item A numeric matrix or cell array containing two or more grouping ## variables. Each column in the matrix or array must correspond to one ## grouping variable. ## @end itemize ## ## @qcode{'GroupingVariables'} is an Octave extension: MATLAB's ## @code{cvpartition} has no such option. It follows the group-aware ## splitters of scikit-learn, and each partition type takes the analogue of ## its ungrouped self: ## ## @multitable @columnfractions 0.28 0.34 0.38 ## @headitem Partition type @tab scikit-learn analogue @tab Effect ## @item @qcode{'KFold'} @tab @code{GroupKFold} @tab whole groups fill each ## fold ## @item @qcode{'Holdout'} @tab @code{GroupShuffleSplit} @tab whole groups ## are held out ## @item @qcode{'Leaveout'} @tab @code{LeaveOneGroupOut} @tab one whole ## group is held out at a time ## @item @qcode{'Resubstitution'} @tab none @tab an error: nothing can be ## held out of a partition that holds everything ## @end multitable ## ## Given a stratification variable, @qcode{'GroupingVariables'} may ## follow @qcode{'Stratify'}, as in @code{cvpartition (@var{y}, ## @qcode{'KFold'}, @var{k}, @qcode{'Stratify'}, true, ## @qcode{'GroupingVariables'}, @var{grpvars})}. Each group is then ## kept whole while the classes of @var{y} are spread as evenly over ## the folds as the groups allow, which is scikit-learn's ## @code{StratifiedGroupKFold}. The two demands conflict, since a ## group carries whatever class mix it has, so the balance is ## approximate: groups are placed largest first, each into the fold ## whose class counts it disturbs least. ## ## In every case an observation never appears in both the training and the ## test set as one of its group fellows, which is what makes grouping worth ## asking for when observations within a group are not independent. ## ## @code{@var{C} = cvpartition (@var{n}, @qcode{'Holdout'})} creates a ## @qcode{cvpartition} object @var{C}, which defines a random nonstratified ## partition for holdout validation on @var{n} observations. 90% of the ## observations are assigned to the training set and the remaining 10% to ## the test set. ## ## @code{@var{C} = cvpartition (@var{n}, @qcode{'Holdout'}, @var{p})} also ## creates a nonstratified random partition for holdout validation with the ## percentage of training and test sets defined by @var{p}, which can be a ## scalar value in the range @math{(0,1)} or a positive integer scalar in ## the range @math{[1,@var{n})}. ## ## @code{@var{C} = cvpartition (@var{n}, @qcode{'Leaveout'})} creates a ## @qcode{cvpartition} object @var{C}, which defines a random partition for ## leave-one-out cross-validation on @var{n} observations. This is a ## special case of k-fold cross-validation with the number of folds equal to ## the number of observations. ## ## @code{@var{C} = cvpartition (@var{n}, @qcode{'Resubstitution'})} creates ## a @qcode{cvpartition} object @var{C} without partitioning the data and ## both training and test sets containing all observations @var{n}. ## ## @code{@var{C} = cvpartition (@var{X}, @qcode{'KFold'})} creates a ## @qcode{cvpartition} object @var{C}, which defines a stratified random ## partition for k-fold cross-validation according to the class proportions ## in @var{Χ}. @var{X} can be a numeric, logical, categorical, or string ## vector, or a character array or a cell array of character vectors. ## Missing values in @var{X} are discarded. The default number of folds is ## 10 for @code{numel (@var{X}) >= 10} or equal to @code{numel (@var{X})} ## otherwise. ## ## @code{@var{C} = cvpartition (@var{X}, @qcode{'KFold'}, @var{k})} also ## creates a stratified random partition for k-fold cross-validation with ## the number of folds defined by @var{k}, which must be a positive integer ## scalar smaller than the number of observations in @var{X}. ## ## @code{@var{C} = cvpartition (@var{X}, @qcode{'KFold'}, @var{k}, ## @qcode{'Stratify'}, @var{opt})} creates a random partition for k-fold ## cross-validation, which is stratified if @var{opt} is @qcode{true}, or ## nonstratified if @var{opt} is @qcode{false}. ## ## @code{@var{C} = cvpartition (@var{X}, @qcode{'Holdout'})} creates a ## @qcode{cvpartition} object @var{C}, which defines a stratified random ## partition for holdout validation while maintaining the class proportions ## in @var{Χ}. 90% of the observations are assigned to the training set and ## the remaining 10% to the test set. ## ## @code{@var{C} = cvpartition (@var{X}, @qcode{'Holdout'}, @var{p})} also ## creates a stratified random partition for holdout validation with the ## percentage of training and test sets defined by @var{p}, which can be a ## scalar value in the range @math{(0,1)} or a positive integer scalar in ## the range @math{[1,@var{n})}. ## ## @code{@var{C} = cvpartition (@var{X}, @qcode{'Holdout'}, @var{p}, ## @qcode{'Stratify'}, @var{opt})} creates a random partition for holdout ## validation, which is stratified if @var{opt} is @qcode{true}, or ## nonstratified if @var{opt} is @qcode{false}. ## ## @code{@var{C} = cvpartition (@qcode{'CustomPartition'}, @var{testSets})} ## creates a custom partition according to @var{testSets}, which can be a ## positive integer vector, a logical vector, or a logical matrix according ## to the following options: ## @itemize ## @item A positive integer vector of length @math{N} with values in the ## range @math{[1,K]}, where @math{K < N}, will specify a K-fold ## cross-validation partition, in which each value indicates the test set ## of each observation. Alternatively, the same vector with values in the ## range @math{[1,N]} will specify a leave-one-out cross-validation. ## @item A logical vector will specify a holdout validation, in which the ## @qcode{true} elements correspond to the test set and the @qcode{false} ## elements correspond to the training set. ## @item A logical matrix with @math{K} columns will specify a K-fold ## cross-validation partition, in which each column corresponds to a fold ## and each row to an observation. Alternatively, an @math{N*N} logical ## matrix will specify a leave-one-out cross-validation, where @math{N} is ## the number of observations. @qcode{true} elements correspond to the ## test set and the @qcode{false} elements correspond to the training set. ## @end itemize ## ## @seealso{cvpartition, summary, test, training} ## @end deftypefn function this = cvpartition (X, varargin) ## Check for appropriate number of input arguments if (nargin < 2) error ("cvpartition: too few input arguments."); endif if (nargin > 7) error ("cvpartition: too many input arguments."); endif ## Check for custom partition if (strcmpi (X, 'CustomPartition')) testSets = varargin{1}; ## Check for valid test set if (! (isnumeric (testSets) || islogical (testSets))) error ("cvpartition: TESTSETS must be numeric of logical."); endif if (isnumeric (testSets)) if (! isvector (testSets)) error ("cvpartition: TESTSETS must be a numeric vector."); endif [~, idx, inds] = unique (testSets); this.NumObservations = numel (testSets); this.NumTestSets = numel (idx); nvec = this.NumObservations * ones (1, this.NumTestSets); if (this.NumTestSets < this.NumObservations) this.indices = inds; for i = 1:this.NumTestSets this.TestSize(i) = sum (inds == i); endfor this.TrainSize = nvec - this.TestSize; this.Type = 'kfold'; this.cvptype = 'K-fold cross validation partition'; else this.TrainSize = nvec - 1; this.TestSize = nvec - this.TrainSize; this.Type = 'leaveout'; this.cvptype = 'Leave-one-out cross validation partition'; endif else # logical vector of matrix if (! ismatrix (testSets)) error ("cvpartition: TESTSETS must be a logical vector or matrix."); elseif (isvector (testSets)) this.NumObservations = numel (testSets); this.NumTestSets = 1; this.indices = testSets; this.TrainSize = sum (! testSets); this.TestSize = sum (testSets); this.Type = 'holdout'; this.cvptype = 'Hold-out cross validation partition'; else # logical matrix ## Each observation must be present in exactly one test set if (any (sum (testSets, 2) > 1)) error (strcat ("cvpartition: each observation in TESTSETS", ... " must be exactly one in each row.")); endif [this.NumObservations, this.NumTestSets] = size (testSets); nvec = this.NumObservations * ones (1, this.NumTestSets); if (this.NumTestSets < this.NumObservations) this.indices = zeros (this.NumObservations, 1); for i = 1:this.NumTestSets this.TestSize(i) = sum (testSets(:,i)); this.indices(testSets(:,i)) = i; endfor this.TrainSize = nvec - this.TestSize; this.Type = 'kfold'; this.cvptype = 'K-fold cross validation partition'; elseif (this.NumTestSets == this.NumObservations) this.TrainSize = nvec - 1; this.TestSize = nvec - this.TrainSize; this.Type = 'leaveout'; this.cvptype = 'Leave-one-out cross validation partition'; else error (strcat ("cvpartition: a logical matrix in TESTSETS", ... " must not have more columns that rows.")); endif endif endif this.IsCustom = true; this.IsGrouped = false; this.IsStratified = false; ## Check first input being a scalar value elseif (isscalar (X)) if (! (isnumeric (X) && X > 0 && fix (X) == X)) error ("cvpartition: X must be a scalar positive integer value."); endif ## Get number of observations and partition type this.NumObservations = X; type = varargin{1}; this.IsCustom = false; this.IsStratified = false; ## "Resubstitution" if (strcmpi (type, 'resubstitution')) if (nargin > 2 && ischar (varargin{2}) && strcmpi (varargin{2}, 'groupingvariables')) error (strcat ("cvpartition: 'GroupingVariables' does not", ... " apply to 'resubstitution': the training and", ... " test sets are both the whole sample, so no", ... " group can be held out of either.")); endif this.NumTestSets = 1; this.TrainSize = X; this.TestSize = X; this.Type = 'resubstitution'; this.cvptype = 'Resubstitution (no partition of data)'; this.IsGrouped = false; ## "Leaveout" elseif (strcmpi (type, 'leaveout')) this.Type = 'leaveout'; ## A non-character third argument has never meant anything here and ## is still ignored; a character one must name 'GroupingVariables'. if (nargin > 2 && ischar (varargin{2})) if (! strcmpi (varargin{2}, 'groupingvariables')) error (strcat ("cvpartition: invalid optional paired", ... " argument for 'GroupingVariables'.")); endif if (nargin < 4) error (strcat ("cvpartition: missing value for optional", ... " paired argument 'GroupingVariables'.")); endif [inds, NumGroups, GroupSize, this.missidx, grpvars] = ... __resolve_groups__ (varargin{3}); nobs = X - sum (this.missidx); if (nobs != numel (inds)) error (strcat ("cvpartition: grouping variable does", ... " not match the number of observations.")); endif this.grpvars = grpvars; ## Each group is held out in turn: leave-one-group-out this.indices = inds; this.NumTestSets = NumGroups; this.TestSize = GroupSize; this.TrainSize = X * ones (1, NumGroups) - this.TestSize; this.cvptype = 'Leave-one-group-out cross validation partition'; this.IsGrouped = true; else this.NumTestSets = X; this.TrainSize = (X - 1) * ones (1, X); this.TestSize = ones (1, X); this.cvptype = 'Leave-one-out cross validation partition'; this.IsGrouped = false; endif ## "Holdout" elseif (strcmpi (type, 'holdout')) if (nargin > 2) p = varargin{2}; if (! isnumeric (p) || ! isscalar (p)) error (strcat ("cvpartition: P value for 'holdout'", ... " must be a numeric scalar.")); endif if (! ((p > 0 && p < 1) || (p == fix (p) && p > 0 && p < X))) error (strcat ("cvpartition: P value for 'holdout' must be", ... " a scalar in the range (0,1) or an integer", ... " scalar in the range [1, N).")); endif else p = 0.1; endif this.NumTestSets = 1; if (p < 1) # target fraction to sample p = round (p * X); # number of samples endif this.Type = 'holdout'; ## 'GroupingVariables' holds out whole groups instead of individual ## observations, drawing groups at random until the test set is at ## least the requested size grouped = (nargin > 3 && ischar (varargin{3}) && strcmpi (varargin{3}, 'groupingvariables')); if (nargin > 3 && ! grouped) error (strcat ("cvpartition: invalid optional paired", ... " argument for 'GroupingVariables'.")); endif if (grouped) if (nargin < 5) error (strcat ("cvpartition: missing value for optional", ... " paired argument 'GroupingVariables'.")); endif [gidx, NumGroups, GroupSize, this.missidx, grpvars] = ... __resolve_groups__ (varargin{4}); nobs = X - sum (this.missidx); if (nobs != numel (gidx)) error (strcat ("cvpartition: grouping variable does", ... " not match the number of observations.")); endif this.grpvars = grpvars; order = randsample (NumGroups, NumGroups); held = false (NumGroups, 1); ntest = 0; for i = 1:NumGroups if (ntest >= p) break; endif held(order(i)) = true; ntest += GroupSize(order(i)); endfor if (all (held)) error (strcat ("cvpartition: holding out %d observations", ... " needs every group; leave a group for", ... " training."), p); endif inds = false (X, 1); inds(! this.missidx) = held(gidx); this.cvptype = 'Group hold-out cross validation partition'; this.IsGrouped = true; else inds = false (X, 1); inds(randsample (X, p)) = true; # indices for test set this.cvptype = 'Hold-out cross validation partition'; this.IsGrouped = false; endif this.indices = inds; this.TrainSize = sum (! inds); this.TestSize = sum (inds); ## "KFold" elseif (strcmpi (type, 'kfold')) this.Type = 'kfold'; if (nargin > 2) k = varargin{2}; if (! isnumeric (k) || ! isscalar (k)) error (strcat ("cvpartition: K value for 'kfold'", ... " must be a numeric scalar.")); endif else if (X < 10) k = X; else k = 10; endif endif ## No grouping variables if (nargin < 4) if (! (k == fix (k) && k > 0 && k <= X)) error (strcat ("cvpartition: K value for 'kfold' must be", ... " an integer scalar in the range [1, N].")); endif this.NumTestSets = k; indices = floor ((0:(X - 1))' * (k / X)) + 1; indices = randsample (indices, X); nvec = X * ones (1, k); for i = 1:k this.TestSize(i) = sum (indices == i); endfor this.indices = indices; this.TrainSize = nvec - this.TestSize; this.cvptype = 'K-fold cross validation partition'; this.IsGrouped = false; else # with grouping variables if (! strcmpi (varargin{3}, 'groupingvariables')) error (strcat ("cvpartition: invalid optional paired", ... " argument for 'GroupingVariables'.")); endif if (nargin < 5) error (strcat ("cvpartition: missing value for optional", ... " paired argument 'GroupingVariables'.")); endif grpvars = varargin{4}; [inds, NumGroups, GroupSize, this.missidx, grpvars] = ... __resolve_groups__ (grpvars); X -= sum (this.missidx); if (X != numel (inds)) error (strcat ("cvpartition: grouping variable does", ... " not match the number of observations.")); endif this.grpvars = grpvars; ## Compare k-fold to number of groups and reduce K accordingly if (k > NumGroups) warning (strcat ("cvpartition: number of folds K is greater", ... " than the groups in 'GroupingVariables'.", ... " K is set to the number of groups.")); k = NumGroups; endif ## If k == NumGroups, then each group becomes a test in a fold. ## If k < NumGroups, then cluster NumGroups to k folds. indices = zeros (X, 1); if (k == NumGroups) for i = 1:k indices(inds == i) = i; endfor else [GroupIdx, ~, GroupSz] = multiway (GroupSize, k, 'completeKK'); for i = 1:k idxGV = find (GroupIdx == i); vecGV = arrayfun (@(x) x == inds, idxGV, 'UniformOutput', false); index = vecGV{1}; if (numel (vecGV) > 1) for j = 2:numel (vecGV) index = index | vecGV{j}; endfor endif indices(index) = i; endfor endif ## Randomize the order of folds random_idx = randsample ([1:k], k); randomized = zeros (size (inds)); for i = 1:k randomized(indices == i) = random_idx(i); endfor ## Save values to properties this.indices = randomized; this.NumTestSets = k; nvec = X * ones (1, k); for i = 1:k this.TestSize(i) = sum (this.indices == i); endfor this.TrainSize = nvec - this.TestSize; this.cvptype = 'Group K-fold cross validation partition'; this.IsGrouped = true; endif ## Invalid paired argument else error ("cvpartition: invalid optional paired argument."); endif ## A grouping variable may name its groups in the rows of a character ## matrix, which is one label per row and not one per element. Such a ## matrix is not a vector, so it is turned into the cell array of names ## it stands for and taken down the vector branch below. A character ## vector is left alone: there each element is already an observation. elseif (ischar (X) && ! isvector (X)) this = cvpartition (cellstr (X), varargin{:}); return; ## Check first input being a vector for stratification elseif (isvector (X)) ## Get number of observations (including missing values) this.NumObservations = numel (X); ## Remove missing values from partitioning. ## Keep missing index to include them in the test indices. this.missidx = ismissing (X); X(this.missidx) = []; ## Get stratify option if (nargin < 4) this.IsStratified = true; else if (! strcmpi (varargin{3}, 'stratify')) error (strcat ("cvpartition: invalid optional paired", ... " argument for stratification.")); endif if (nargin < 5) error (strcat ("cvpartition: missing value for optional", ... " paired argument 'stratify'.")); endif if (! isscalar (varargin{4}) || ! islogical (varargin{4})) error (strcat ("cvpartition: invalid value for optional", ... " paired argument 'stratify'.")); endif this.IsStratified = varargin{4}; endif ## 'GroupingVariables' may follow 'Stratify', giving a partition that ## keeps each group whole and the classes balanced across folds grpvars = []; if (nargin > 5) if (! (ischar (varargin{5}) && strcmpi (varargin{5}, 'groupingvariables'))) error (strcat ("cvpartition: invalid optional paired", ... " argument for 'GroupingVariables'.")); endif if (nargin < 7) error (strcat ("cvpartition: missing value for optional", ... " paired argument 'GroupingVariables'.")); endif grpvars = varargin{6}; if (isempty (grpvars)) error (strcat ("cvpartition: invalid value for optional", ... " paired argument 'GroupingVariables'.")); endif endif ## Handle stratification if (this.IsStratified) [classID, idx, classes] = unique (X); NumClasses = numel (idx); for i = 1:NumClasses ClassSize(i) = sum (classes == i); endfor this.classes = classes; this.classID = classID; endif X = numel (X); ## Get partition type type = varargin{1}; this.IsCustom = false; this.IsGrouped = false; ## "Holdout" if (strcmpi (type, 'holdout')) this.Type = 'holdout'; if (nargin > 2) p = varargin{2}; if (! isnumeric (p) || ! isscalar (p)) error (strcat ("cvpartition: P value for 'holdout'", ... " must be a numeric scalar.")); endif if (! ((p > 0 && p < 1) || (p == fix (p) && p > 0 && p < X))) error (strcat ("cvpartition: P value for 'holdout' must be", ... " a scalar in the range (0,1) or an integer", ... " scalar in the range [1, N), where N is the", ... " number of nonmissing observations in X.")); endif else p = 0.1; endif this.NumTestSets = 1; if (this.IsStratified) if (p < 1) f = p; # target fraction to sample p = round (p * X); # number of test samples else f = p / X; endif inds = zeros (X, 1, 'logical'); k_check = 0; for i = 1:NumClasses ki = round (f * ClassSize(i)); inds(find (classes == i)(randsample (ClassSize(i), ki))) = true; k_check += ki; endfor if (k_check < p) # add random elements to test set to make it p inds(find (! inds)(randsample (X - k_check, p - k_check))) = true; elseif (k_check > p) # remove random elements from test set inds(find (inds)(randsample (k_check, k_check - p))) = false; endif this.cvptype = 'Stratified hold-out cross validation partition'; else if (p < 1) # target fraction to sample p = round (p * X); # number of samples endif inds = false (X, 1); inds(randsample (X, p)) = true; # indices for test set this.cvptype = 'Hold-out cross validation partition'; endif this.indices = inds; this.TrainSize = sum (! inds); this.TestSize = sum (inds); ## "KFold" elseif (strcmpi (type, 'kfold')) this.Type = 'kfold'; if (nargin > 2) k = varargin{2}; if (! isnumeric (k) || ! isscalar (k)) error (strcat ("cvpartition: K value for 'kfold'", ... " must be a numeric scalar.")); endif if (! (k == fix (k) && k > 0 && k <= X)) error (strcat ("cvpartition: K value for 'kfold' must be", ... " an integer scalar in the range [1, N],", ... " where N is the number of nonmissing", ... " observations in X.")); endif else if (X < 10) k = X; else k = 10; endif endif this.NumTestSets = k; if (this.IsStratified && ! isempty (grpvars)) ## Both: whole groups, classes balanced across folds [gidx, NumGroups, GroupSize] = __resolve_groups__ (grpvars); if (numel (gidx) != X) error (strcat ("cvpartition: grouping variable does", ... " not match the number of observations.")); endif if (k > NumGroups) warning (strcat ("cvpartition: number of folds K is greater", ... " than the groups in 'GroupingVariables'.", ... " K is set to the number of groups.")); k = NumGroups; this.NumTestSets = k; endif this.grpvars = grpvars; inds = __stratified_group_folds__ (this.classes, gidx, k); this.indices = inds; for i = 1:k this.TestSize(i) = sum (inds == i); endfor this.TrainSize = X * ones (1, k) - this.TestSize; this.cvptype = ... 'Stratified group K-fold cross validation partition'; this.IsGrouped = true; elseif (this.IsStratified) inds = nan (X, 1); pooled_idx = false (X, 1); do_warn = true; do_ceil = false; for i = 1:NumClasses cls_size = ClassSize(i); cls_k_eq = fix (cls_size / k) == (cls_size / k); ## Check that the elements in each class exceed the number of ## requested folds, otherwise emit a warning and add the class ## elements into a pooled class if (cls_size < k) if (do_warn) warning (strcat ("One or more of the unique class values", ... " in the stratification variable is not", ... " present in one or more folds.")); do_warn = false; endif pooled_idx = pooled_idx | classes == i; elseif (fix (X / k) == X / k) ## Make sure that when X / k = integer, all ## test/training sizes must be equal across all folds if (do_ceil && ! cls_k_eq) idx = ceil ((0:(cls_size - 1))' * (k / cls_size)); idx(idx == 0) = max (idx); do_ceil = false; else idx = floor ((0:(cls_size - 1))' * (k / cls_size)) + 1; tmp = arrayfun (@(x) numel (find (x == idx)), [1:k]); if (any (diff (tmp))) do_ceil = true; endif endif inds(classes == i) = randsample (idx, cls_size); else ## Alternate ordering over classes so that ## the subsets are more nearly the same size if (! do_ceil || cls_k_eq) idx = floor ((0:(cls_size - 1))' * (k / cls_size)) + 1; if (! cls_k_eq) do_ceil = true; endif else idx = floor (((cls_size - 1):-1:0)' * (k / cls_size)) + 1; do_ceil = false; endif inds(classes == i) = randsample (idx, cls_size); endif endfor ## Stratify pooled classes (if any). They must be distributed ## in a way to make the test/training sizes as equal as possible ## across folds. pooled_inds = find (pooled_idx); while (numel (pooled_inds) > 0) tmp = arrayfun (@(x) numel (find (x == inds)), [1:k]); [min_cls, min_idx] = min (tmp); [max_cls, max_idx] = max (tmp); if (min_cls != max_cls) inds(pooled_inds(1)) = min_idx; else inds(pooled_inds(1)) = randsample (k, 1); endif pooled_inds(1) = []; endwhile this.cvptype = 'Stratified K-fold cross validation partition'; elseif (! isempty (grpvars)) ## Groups without stratification: whole groups, and the ## stratification variable is not used, as 'Stratify' asked [gidx, NumGroups] = __resolve_groups__ (grpvars); if (numel (gidx) != X) error (strcat ("cvpartition: grouping variable does", ... " not match the number of observations.")); endif if (k > NumGroups) warning (strcat ("cvpartition: number of folds K is greater", ... " than the groups in 'GroupingVariables'.", ... " K is set to the number of groups.")); k = NumGroups; this.NumTestSets = k; endif this.grpvars = grpvars; gfold = randsample (NumGroups, NumGroups); gfold = mod (gfold - 1, k) + 1; inds = gfold(gidx); this.cvptype = 'Group K-fold cross validation partition'; this.IsGrouped = true; else inds = floor ((0:(X - 1))' * (k / X)) + 1; inds = randsample (inds, X); this.cvptype = 'K-fold cross validation partition'; endif this.indices = inds; nvec = X * ones (1, k); for i = 1:k this.TestSize(i) = sum (inds == i); endfor this.TrainSize = nvec - this.TestSize; ## Invalid paired argument else error ("cvpartition: invalid optional paired argument."); endif ## Otherwise first input is invalid else error ("cvpartition: invalid first input argument."); endif endfunction ## -*- texinfo -*- ## @deftypefn {cvpartition} {@var{Cnew} =} repartition (@var{C}) ## @deftypefnx {cvpartition} {@var{Cnew} =} repartition (@var{C}, @var{sval}) ## @deftypefnx {cvpartition} {@var{Cnew} =} repartition (@var{C}, @qcode{'legacy'}) ## ## Repartition data for cross-validation. ## ## @code{@var{Cnew} = repartition (@var{C})} creates a @qcode{cvpartition} ## object @var{Cnew} that defines a new random partition of the same type as ## the @qcode{cvpartition} @var{C}. ## ## @code{@var{Cnew} = repartition (@var{C}, @var{sval})} also uses the value ## of @var{sval} to set the state of the random generator used in ## repartitioning @var{C}. If @var{sval} is a vector, then the random ## generator is set using the @qcode{'state'} keyword as in ## @code{rand ("state", @var{sval})}. If @var{sval} is a scalar, then the ## @qcode{'seed'} keyword is used as in @code{rand ("seed", @var{sval})} to ## specify that old generators should be used. ## ## Seeding is confined to this call: the state of the random generator is ## saved beforehand and restored before @code{repartition} returns, so ## @var{sval} does not carry over into the random numbers the caller draws ## afterwards. @var{sval} is an Octave extension; MATLAB expects a ## @code{RandStream} object in this position, which Octave does not have. ## ## @code{@var{Cnew} = repartition (@var{C}, @qcode{'legacy'})} only applies ## to @qcode{cvpartition} objects @var{C} that use k-fold partitioning and ## it will repartition @var{C} in the same non-random manner that was ## previously used by the old-style @qcode{cvpartition} class of the ## statistics package. The @qcode{'legacy'} option does not apply to ## stratified or grouped partitions. ## ## @seealso{cvpartition, summary, test, training} ## @end deftypefn function this = repartition (this, sval = []) ## Emit error for custom partitions if (this.IsCustom) error ("cvpartition.repartition: cannot repartition a custom partition."); endif ## Handle legacy code with no randomization of kfold option if (strcmpi (sval, 'legacy')) if (strcmpi (this.Type, 'kfold')) X = this.NumObservations; k = this.NumTestSets; if (! (this.IsGrouped || this.IsStratified)) inds = floor ((0:(X - 1))' * (k / X)) + 1; this.indices = inds; nvec = X * ones (1, k); for i = 1:k this.TestSize(i) = sum (inds == i); endfor this.TrainSize = nvec - this.TestSize; else # legacy option does not apply for grouped or stratified error (strcat ("cvpartition.repartition: 'legacy' flag does", ... " not apply to stratified or grouped 'kfold'", ... " partitioned objects.")); endif return; else error (strcat ("cvpartition.repartition: 'legacy' flag is only", ... " valid for 'kfold' partitioned objects.")); endif endif ## Check sval if (! isempty (sval)) if (! (isvector (sval) && isnumeric (sval) && isreal (sval))) error (strcat ("cvpartition.repartition: SVAL must be", ... " a real scalar or vector.")); endif ## SVAL seeds this repartitioning only. The caller's generator is put ## back before returning -- on the error path too, hence onCleanup -- ## so that seeding a partition cannot silently make the rest of the ## session reproducible as well. Octave has no RandStream to confine ## the draw to, which is why the state has to be saved and restored. saved_rand_state = rand ('state'); rand_state_guard = onCleanup (@() rand ('state', saved_rand_state)); if (isscalar (sval)) rand ('seed', sval); else rand ('state', sval); endif endif ## Handle repartitioning of randomized holdout and kfold options if (strcmpi (this.Type, 'holdout')) p = this.TestSize; if (this.IsStratified) X = sum (! this.missidx); inds = false (X, 1); NumClasses = numel (this.classID); classes = this.classes; for i = 1:NumClasses ClassSize(i) = sum (classes == i); endfor f = p / X; k_check = 0; for i = 1:NumClasses ki = round (f * ClassSize(i)); inds(find (classes == i)(randsample (ClassSize(i), ki))) = true; k_check += ki; endfor if (k_check < p) # add random elements to test set to make it p inds(find (! inds)(randsample (X - k_check, p - k_check))) = true; elseif (k_check > p) # remove random elements from test set inds(find (inds)(randsample (k_check, k_check - p))) = false; endif else X = this.NumObservations; inds = false (X, 1); inds(randsample (X, p)) = true; # indices for test set endif this.indices = inds; elseif (strcmpi (this.Type, 'kfold')) k = this.NumTestSets; if (! (this.IsGrouped || this.IsStratified)) X = this.NumObservations; inds = floor ((0:(X - 1))' * (k / X)) + 1; inds = randsample (inds, X); this.indices = inds; nvec = X * ones (1, k); for i = 1:k this.TestSize(i) = sum (inds == i); endfor this.TrainSize = nvec - this.TestSize; elseif (this.IsGrouped) ## We only need resample the order of folds in this case ## Randomize the order of folds random_idx = randsample ([1:k], k); randomized = zeros (size (this.indices)); for i = 1:k randomized(this.indices == i) = random_idx(i); endfor ## Save values to properties this.indices = randomized; this.NumTestSets = k; nvec = sum (! this.missidx) * ones (1, k); for i = 1:k this.TestSize(i) = sum (this.indices == i); endfor this.TrainSize = nvec - this.TestSize; else # is stratified X = sum (! this.missidx); NumClasses = numel (this.classID); classes = this.classes; for i = 1:NumClasses ClassSize(i) = sum (classes == i); endfor inds = nan (X, 1); pooled_idx = false (X, 1); do_warn = true; do_ceil = false; for i = 1:NumClasses cls_size = ClassSize(i); cls_k_eq = fix (cls_size / k) == (cls_size / k); ## Check that the elements in each class exceed the number of ## requested folds, otherwise emit a warning and add the class ## elements into a pooled class if (cls_size < k) if (do_warn) warning (strcat ("One or more of the unique class values", ... " in the stratification variable is not", ... " present in one or more folds.")); do_warn = false; endif pooled_idx = pooled_idx | classes == i; elseif (fix (X / k) == X / k) ## Make sure that when X / k = integer, all ## test/training sizes must be equal across all folds if (do_ceil && ! cls_k_eq) idx = ceil ((0:(cls_size - 1))' * (k / cls_size)); idx(idx == 0) = max (idx); do_ceil = false; else idx = floor ((0:(cls_size - 1))' * (k / cls_size)) + 1; tmp = arrayfun (@(x) numel (find (x == idx)), [1:k]); if (any (diff (tmp))) do_ceil = true; endif endif inds(classes == i) = randsample (idx, cls_size); else ## Alternate ordering over classes so that ## the subsets are more nearly the same size if (! do_ceil || cls_k_eq) idx = floor ((0:(cls_size - 1))' * (k / cls_size)) + 1; if (! cls_k_eq) do_ceil = true; endif else idx = floor (((cls_size - 1):-1:0)' * (k / cls_size)) + 1; do_ceil = false; endif inds(classes == i) = randsample (idx, cls_size); endif endfor ## Stratify pooled classes (if any). They must be distributed ## in a way to make the test/training sizes as equal as possible ## across folds. pooled_inds = find (pooled_idx); while (numel (pooled_inds) > 0) tmp = arrayfun (@(x) numel (find (x == inds)), [1:k]); [min_cls, min_idx] = min (tmp); [max_cls, max_idx] = max (tmp); if (min_cls != max_cls) inds(pooled_inds(1)) = min_idx; else inds(pooled_inds(1)) = randsample (k, 1); endif pooled_inds(1) = []; endwhile this.indices = inds; nvec = X * ones (1, k); for i = 1:k this.TestSize(i) = sum (inds == i); endfor this.TrainSize = nvec - this.TestSize; endif endif endfunction ## -*- texinfo -*- ## @deftypefn {cvpartition} {@var{tbl} =} summary (@var{c}) ## ## Summarize stratified or grouped cross-validation partitions. ## ## @code{@var{tbl} = summary (@var{c})} returns a summary table @var{tbl} of ## the validation partition contained in the @code{cvpartition} object ## @var{c}. ## ## This method calculates the distribution of classes (if stratified) or ## groups (if grouped) across the entire dataset, as well as within every ## training and test set generated by the partition. ## ## @subheading Inputs ## @itemize ## @item @var{c} ## A @code{cvpartition} object. The object must satisfy two conditions: ## @enumerate ## @item The partition type (@code{c.Type}) must be @qcode{'kfold'} or ## @qcode{'holdout'}. ## @item The partition must be created with a stratification or grouping ## variable (i.e., @code{c.IsStratified} or @code{c.IsGrouped} must be ## @code{true}). ## @end enumerate ## @end itemize ## ## @subheading Outputs ## @itemize ## @item @var{tbl} ## A @code{table} object containing the summary statistics. The table ## contains one row for every unique label/group in every set (all, train, ## test). The columns are: ## @table @code ## @item Set ## The specific subset being described. Values include @qcode{'all'} (the ## full dataset), @qcode{'train1'}, @qcode{'test1'}, etc. ## @item SetSize ## The total number of observations in that specific set. ## @item Label ## The class or group identifier. If @code{c.IsStratified} is true, this ## column is named @code{StratificationLabel}. If @code{c.IsGrouped} is ## true, it is named @code{GroupLabel}. ## @item Count ## The number of observations of that label within the set. If stratified, ## this column is named @code{StratificationCount}; otherwise, ## @code{GroupCount}. ## @item PercentInSet ## The percentage of the set composed of that specific label. ## @end table ## @end itemize ## ## @seealso{cvpartition, repartition, test, training} ## @end deftypefn function tbl = summary (this) ## Validation Checks if (! (this.IsStratified || this.IsGrouped)) error ("cvpartition.summary: partition must be stratified or grouped."); endif if (! (strcmpi (this.Type, 'kfold') || strcmpi (this.Type, 'holdout'))) error ("cvpartition.summary: partition type must be 'kfold' or 'holdout'."); endif ## Prepare Labels and Data Map if (this.IsStratified) LabelVarName = 'StratificationLabel'; CountVarName = 'StratificationCount'; UniqueLabels = this.classID; DataMap = this.classes; else ## Grouped LabelVarName = 'GroupLabel'; CountVarName = 'GroupCount'; ## Use __unique__ internal helper to ensure stable rows if (isa (this.grpvars, 'categorical')) [UniqueLabels, ~, DataMap] = unique (this.grpvars, 'rows', 'stable'); else [UniqueLabels, ~, DataMap] = __unique__ (this.grpvars, 'rows', 'stable'); endif endif ## Calculate dimensions for preallocation NumLabels = size (UniqueLabels, 1); NumSets = 1 + (2 * this.NumTestSets); ## 1 ("all") + 2 * K (Train/Test) TotalRows = NumLabels * NumSets; ## Preallocate Columns col_Set = cell (TotalRows, 1); col_SetSize = zeros (TotalRows, 1); col_Count = zeros (TotalRows, 1); col_Percent = zeros (TotalRows, 1); ## Determine if Label column is text or numeric if (iscell (UniqueLabels) || isstring (UniqueLabels) || ischar (UniqueLabels)) col_Label = cell (TotalRows, 1); is_text_label = true; else col_Label = zeros (TotalRows, 1); is_text_label = false; endif ## Helper for populating data curr_idx = 1; ## Inline helper function to calculate stats function [c_set, c_size, c_lbl, c_cnt, c_pct, idx_next] = ... fill_rows (name, mask, map, u_lbl, n_lbl, ... c_set, c_size, c_lbl, c_cnt, c_pct, idx_start, is_txt) subset_map = map(mask); subset_size = numel (subset_map); for u = 1:n_lbl count = sum (subset_map == u); c_set{idx_start} = name; c_size(idx_start) = subset_size; if (is_txt) if (iscell (u_lbl)) c_lbl{idx_start} = u_lbl{u}; elseif (isstring (u_lbl)) ## Convert string object to char for cell storage c_lbl{idx_start} = char (u_lbl(u)); else c_lbl{idx_start} = u_lbl(u, :); endif else c_lbl(idx_start) = u_lbl(u); endif c_cnt(idx_start) = count; c_pct(idx_start) = (count / subset_size) * 100; idx_start = idx_start + 1; endfor idx_next = idx_start; endfunction ## Calculate Statistics ## --- Set: "all" --- all_mask = true (size (DataMap)); [col_Set, col_SetSize, col_Label, col_Count, col_Percent, curr_idx] = ... fill_rows ('all', all_mask, DataMap, UniqueLabels, NumLabels, ... col_Set, col_SetSize, col_Label, col_Count, col_Percent, ... curr_idx, is_text_label); ## --- Set: Folds --- for k = 1:this.NumTestSets if (strcmpi (this.Type, 'holdout')) test_mask = this.indices; else test_mask = (this.indices == k); endif train_name = sprintf ('train%d', k); [col_Set, col_SetSize, col_Label, col_Count, col_Percent, curr_idx] = ... fill_rows (train_name, ! test_mask, DataMap, UniqueLabels, NumLabels, ... col_Set, col_SetSize, col_Label, col_Count, col_Percent, ... curr_idx, is_text_label); test_name = sprintf ('test%d', k); [col_Set, col_SetSize, col_Label, col_Count, col_Percent, curr_idx] = ... fill_rows (test_name, test_mask, DataMap, UniqueLabels, NumLabels, ... col_Set, col_SetSize, col_Label, col_Count, col_Percent, ... curr_idx, is_text_label); endfor ## Construct Table if (exist ('string', 'class')) col_Set = string (col_Set); if (is_text_label) col_Label = string (col_Label); endif endif tbl = table (col_Set, col_SetSize, col_Label, col_Count, col_Percent, ... 'VariableNames', {'Set', 'SetSize', LabelVarName, ... CountVarName, 'PercentInSet'}); endfunction ## -*- texinfo -*- ## @deftypefn {cvpartition} {@var{idx} =} test (@var{C}) ## @deftypefnx {cvpartition} {@var{idx} =} test (@var{C}, @var{i}) ## @deftypefnx {cvpartition} {@var{idx} =} test (@var{C}, @qcode{'all'}) ## ## Test indices for cross-validation. ## ## @code{@var{idx} = test (@var{C})} returns a logical vector @var{idx} with ## @qcode{true} values indicating the elements corresponding to the test ## set defined in the @qcode{cvpartition} object @var{C}. For K-fold and ## leave-one-out partitions, the indices corresponding to the first test set ## are returned. ## ## @code{@var{idx} = test (@var{C}, @var{i})} returns a logical vector or ## matrix with the indices of the test set indicated by @var{i}. If @var{i} ## is a scalar, then @var{idx} is a logical vector with the indices of the ## @math{i-th} set. If @var{i} is a vector, then @var{idx} is a logical ## matrix in which @code{@var{idx}(:,j)} specified the observations in the ## test set @code{@var{i}(j)}. The value(s) in @var{i} must not exceed the ## number of tests in the @qcode{cvpartition} object @var{C}. ## ## @code{@var{idx} = test (@var{C}, @qcode{'all'})} returns a logical vector ## or matrix for all test sets defined in the @qcode{cvpartition} object ## @var{C}. For holdout and resubstitution partition types, a vector is ## returned. For K-fold and leave-one-out, a matrix is returned. ## ## @seealso{cvpartition, repartition, summary, training} ## @end deftypefn function idx = test (this, varargin) ## Check for sufficient input arguments if (nargin > 2) error ("cvpartition.test: too many input arguments."); elseif (nargin == 2) i = varargin{1}; if (strcmpi (i, 'all')) idx = logical ([]); switch (this.Type) case 'kfold' for i = 1:this.NumTestSets if (this.IsStratified || this.IsGrouped) cid = false (this.NumObservations, 1); cid(! this.missidx) = this.indices == i; else cid = this.indices == i; endif idx = [idx, cid]; endfor case 'leaveout' for i = 1:this.NumTestSets cid = false (this.NumObservations, 1); if (this.IsGrouped) cid(! this.missidx) = this.indices == i; else cid(i) = true; endif idx = [idx, cid]; endfor case 'holdout' if (this.IsStratified) idx = false (this.NumObservations, 1); idx(! this.missidx) = this.indices; else idx = this.indices; endif idx = this.indices; case 'resubstitution' # no stratification idx = true (this.NumObservations, 1); endswitch return elseif (isempty (i)) i = 1; endif else i = 1; endif if (! (isvector (i) && isnumeric (i) && all (fix (i) == i) && all (i > 0))) error ("cvpartition.test: set index must be a positive integer vector."); elseif (any (i > this.NumTestSets)) error ("cvpartition.test: set index exceeds 'NumTestSets'."); endif switch (this.Type) case 'kfold' if (isscalar (i)) if (this.IsStratified || this.IsGrouped) idx = false (this.NumObservations, 1); idx(! this.missidx) = this.indices == i; else idx = this.indices == i; endif else idx = logical ([]); for j = i if (this.IsStratified || this.IsGrouped) cid = false (this.NumObservations, 1); cid(! this.missidx) = this.indices == j; else cid = this.indices == j; endif idx = [idx, cid]; endfor endif case 'leaveout' if (isscalar (i)) idx = false (this.NumObservations, 1); if (this.IsGrouped) idx(! this.missidx) = this.indices == i; else idx(i) = true; endif else idx = logical ([]); for j = i new = false (this.NumObservations, 1); if (this.IsGrouped) new(! this.missidx) = this.indices == j; else new(j) = true; endif idx = [idx, new]; endfor endif case 'holdout' if (this.IsStratified) idx = false (this.NumObservations, 1); idx(! this.missidx) = this.indices; else idx = this.indices; endif case 'resubstitution' # no stratification idx = true (this.NumObservations, 1); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {cvpartition} {@var{idx} =} training (@var{C}) ## @deftypefnx {cvpartition} {@var{idx} =} training (@var{C}, @var{i}) ## @deftypefnx {cvpartition} {@var{idx} =} training (@var{C}, @qcode{'all'}) ## ## Training indices for cross-validation. ## ## @code{@var{idx} = training (@var{C})} returns a logical vector @var{idx} ## with @qcode{true} values indicating the elements corresponding to the ## training set defined in the @qcode{cvpartition} object @var{C}. For ## K-fold and leave-one-out partitions, the indices corresponding to the ## first training set are returned. ## ## @code{@var{idx} = training (@var{C}, @var{i})} returns a logical vector ## or matrix with the indices of the training set indicated by @var{i}. If ## @var{i} is a scalar, then @var{idx} is a logical vector with the indices ## of the @math{i-th} set. If @var{i} is a vector, then @var{idx} is a ## logical matrix in which @code{@var{idx}(:,j)} specified the observations ## in the training set @code{@var{i}(j)}. The value(s) in @var{i} must not ## exceed the number of tests in the @qcode{cvpartition} object @var{C}. ## ## @code{@var{idx} = training (@var{C}, @qcode{'all'})} returns a logical ## vector or matrix for all training sets defined in the @qcode{cvpartition} ## object @var{C}. For holdout and resubstitution partition types, a vector ## is returned. For K-fold and leave-one-out, a matrix is returned. ## ## @seealso{cvpartition, repartition, summary, test} ## @end deftypefn function idx = training (this, varargin) ## Check for sufficient input arguments if (nargin > 2) error ("cvpartition.training: too many input arguments."); elseif (nargin == 2) i = varargin{1}; if (strcmpi (i, 'all')) idx = logical ([]); switch (this.Type) case 'kfold' for i = 1:this.NumTestSets if (this.IsStratified || this.IsGrouped) cid = false (this.NumObservations, 1); cid(! this.missidx) = this.indices != i; else cid = this.indices != i; endif idx = [idx, cid]; endfor case 'leaveout' for i = 1:this.NumTestSets cid = true (this.NumObservations, 1); if (this.IsGrouped) cid(! this.missidx) = this.indices != i; else cid(i) = false; endif idx = [idx, cid]; endfor case 'holdout' if (this.IsStratified) idx = false (this.NumObservations, 1); idx(! this.missidx) = ! this.indices; else idx = ! this.indices; endif case 'resubstitution' # no stratification idx = true (this.NumObservations, 1); endswitch return elseif (isempty (i)) i = 1; endif else i = 1; endif if (! (isvector (i) && isnumeric (i) && all (fix (i) == i) && all (i > 0))) error (strcat ("cvpartition.training: set index must", ... " be a positive integer vector.")); elseif (any (i > this.NumTestSets)) error ("cvpartition.training: set index exceeds 'NumTestSets'."); endif switch (this.Type) case 'kfold' if (isscalar (i)) if (this.IsStratified || this.IsGrouped) idx = false (this.NumObservations, 1); idx(! this.missidx) = this.indices != i; else idx = this.indices != i; endif else idx = logical ([]); for j = i if (this.IsStratified || this.IsGrouped) cid = false (this.NumObservations, 1); cid(! this.missidx) = this.indices != j; else cid = this.indices != j; endif idx = [idx, cid]; endfor endif case 'leaveout' if (isscalar (i)) idx = true (this.NumObservations, 1); if (this.IsGrouped) idx(! this.missidx) = this.indices != i; else idx(i) = false; endif else idx = logical ([]); for j = i new = true (this.NumObservations, 1); if (this.IsGrouped) new(! this.missidx) = this.indices != j; else new(j) = false; endif idx = [idx, new]; endfor endif case 'holdout' if (this.IsStratified) idx = false (this.NumObservations, 1); idx(! this.missidx) = ! this.indices; else idx = ! this.indices; endif case 'resubstitution' # no stratification idx = true (this.NumObservations, 1); endswitch endfunction endmethods endclassdef ## Test output results for custom partition %!test %! custom = [1, 1, 1, 2, 2, 2, 1, 2, 3, 2, 3, 3, 2, 1, 3]'; %! cv = cvpartition ('CustomPartition', custom); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 15); %! assert_equal (cv.NumTestSets, 3); %! assert_equal (cv.TrainSize, [10, 9, 11]); %! assert_equal (cv.TestSize, [5, 6, 4]); %! assert_equal (cv.IsCustom, true); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, false); %! idx = training (cv, 1); %! assert_equal (idx, custom != 1); %! idx = test (cv, 1); %! assert_equal (idx, custom == 1); %! idx = training (cv, 2); %! assert_equal (idx, custom != 2); %! idx = test (cv, 2); %! assert_equal (idx, custom == 2); %! idx = training (cv, 3); %! assert_equal (idx, custom != 3); %! idx = test (cv, 3); %! assert_equal (idx, custom == 3); %! idx1 = training (cv, 'all'); %! idx2 = test (cv, 'all'); %! assert_equal (idx1, ! idx2); %!test %! custom = logical ([1, 1, 1, 0, 0, 0, 1, 0, 1, 1])'; %! cv = cvpartition ('CustomPartition', custom); %! assert_equal (cv.Type, 'holdout'); %! assert_equal (cv.NumObservations, 10); %! assert_equal (cv.NumTestSets, 1); %! assert_equal (cv.TrainSize, 4); %! assert_equal (cv.TestSize, 6); %! assert_equal (cv.IsCustom, true); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, false); %! idx = training (cv, 1); %! assert_equal (idx, custom != 1); %! assert_equal (idx, training (cv, 'all')); %! idx = test (cv, 1); %! assert_equal (idx, custom == 1); %! assert_equal (idx, test (cv, 'all')); %!test %! custom = logical ([1, 0, 0; 0, 1, 0; 1, 0, 0; 0, 0, 1]); %! cv = cvpartition ('CustomPartition', custom); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 4); %! assert_equal (cv.NumTestSets, 3); %! assert_equal (cv.TrainSize, [2, 3, 3]); %! assert_equal (cv.TestSize, [2, 1, 1]); %! assert_equal (cv.IsCustom, true); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, false); %! idx = training (cv, 1); %! assert_equal (idx, custom(:,1) == false); %! idx = test (cv, 1); %! assert_equal (idx, custom(:,1) == true); %! idx = training (cv, 2); %! assert_equal (idx, custom(:,2) == false); %! idx = test (cv, 2); %! assert_equal (idx, custom(:,2) == true); %! assert_equal (! custom, training (cv, 'all')); %! assert_equal (custom, test (cv, 'all')); %!test %! cv = cvpartition ('CustomPartition', [1:8]); %! assert_equal (cv.Type, 'leaveout'); %! assert_equal (cv.NumObservations, 8); %! assert_equal (cv.NumTestSets, 8); %! assert_equal (cv.TrainSize, [7, 7, 7, 7, 7, 7, 7, 7]); %! assert_equal (cv.TestSize, [1, 1, 1, 1, 1, 1, 1, 1]); %! assert_equal (cv.IsCustom, true); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, false); %! assert_equal (class (training (cv, 1)), 'logical'); %! assert_equal (sum (training (cv, 1)), 7); %! assert_equal (sum (training (cv, 'all')), cv.TrainSize); %! assert_equal (class (test (cv, 1)), 'logical'); %! assert_equal (sum (test (cv, 1)), 1); %! assert_equal (sum (test (cv, 'all')), cv.TestSize); %! assert_equal (! training (cv, 'all'), test (cv, 'all')); %!test %! cv = cvpartition ('CustomPartition', logical (eye (8))); %! assert_equal (cv.Type, 'leaveout'); %! assert_equal (cv.NumObservations, 8); %! assert_equal (cv.NumTestSets, 8); %! assert_equal (cv.TrainSize, [7, 7, 7, 7, 7, 7, 7, 7]); %! assert_equal (cv.TestSize, [1, 1, 1, 1, 1, 1, 1, 1]); %! assert_equal (cv.IsCustom, true); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, false); %! assert_equal (class (training (cv, 1)), 'logical'); %! assert_equal (sum (training (cv, 1)), 7); %! assert_equal (sum (training (cv, 'all')), cv.TrainSize); %! assert_equal (class (test (cv, 1)), 'logical'); %! assert_equal (sum (test (cv, 1)), 1); %! assert_equal (sum (test (cv, 'all')), cv.TestSize); %! assert_equal (! training (cv, 'all'), test (cv, 'all')); ## Test output results for scalar input N %!test %! cv = cvpartition (10, 'resubstitution'); %! assert_equal (cv.Type, 'resubstitution'); %! assert_equal (cv.NumObservations, 10); %! assert_equal (cv.NumTestSets, 1); %! assert_equal (cv.TrainSize, 10); %! assert_equal (cv.TestSize, 10); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, false); %! assert_equal (class (training (cv, 1)), 'logical'); %! assert_equal (sum (training (cv, 1)), 10); %! assert_equal (training (cv, 'all'), logical (ones (10, 1))); %! assert_equal (class (test (cv, 1)), 'logical'); %! assert_equal (sum (test (cv, 1)), 10); %! assert_equal (test (cv, 'all'), logical (ones (10, 1))); %! assert_equal (test (cv), training (cv)); %!test %! cv = cvpartition (10, 'leaveout'); %! assert_equal (cv.Type, 'leaveout'); %! assert_equal (cv.NumObservations, 10); %! assert_equal (cv.NumTestSets, 10); %! assert_equal (cv.TrainSize, ones (1, 10) * 9); %! assert_equal (cv.TestSize, ones (1, 10)); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, false); %! assert_equal (class (training (cv, 1)), 'logical'); %! assert_equal (sum (training (cv, 1)), 9); %! assert_equal (training (cv, 'all'), ! logical (eye (10))); %! assert_equal (class (test (cv, 1)), 'logical'); %! assert_equal (sum (test (cv, 1)), 1); %! assert_equal (test (cv, 'all'), logical (eye (10))); %! assert_equal (test (cv), ! training (cv)); %! assert_equal (test (cv, 'all'), ! training (cv, 'all')); %!test %! rand ('seed', 5); # for reproducibility %! cv = cvpartition (10, 'holdout', 0.3); %! assert_equal (cv.Type, 'holdout'); %! assert_equal (cv.NumObservations, 10); %! assert_equal (cv.NumTestSets, 1); %! assert_equal (cv.TrainSize, 7); %! assert_equal (cv.TestSize, 3); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, false); %! assert_equal (class (training (cv, 1)), 'logical'); %! assert_equal (sum (training (cv, 1)), 7); %! assert_equal (training (cv, 'all'), logical ([1, 0, 1, 1, 0, 1, 1, 1, 0, 1])'); %! assert_equal (class (test (cv, 1)), 'logical'); %! assert_equal (sum (test (cv, 1)), 3); %! assert_equal (test (cv, 'all'), logical ([0, 1, 0, 0, 1, 0, 0, 0, 1, 0])'); %! assert_equal (test (cv), ! training (cv)); %! assert_equal (test (cv, 'all'), ! training (cv, 'all')); %!test %! cv = cvpartition (10, 'holdout', 4); %! assert_equal (cv.Type, 'holdout'); %! assert_equal (cv.NumObservations, 10); %! assert_equal (cv.NumTestSets, 1); %! assert_equal (cv.TrainSize, 6); %! assert_equal (cv.TestSize, 4); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, false); %! assert_equal (class (training (cv, 1)), 'logical'); %! assert_equal (sum (training (cv, 1)), 6); %! assert_equal (class (test (cv, 1)), 'logical'); %! assert_equal (sum (test (cv, 1)), 4); %! assert_equal (test (cv), ! training (cv)); %! assert_equal (test (cv, 'all'), ! training (cv, 'all')); %!test %! cv = cvpartition (5, 'holdout', 4); %! assert_equal (cv.Type, 'holdout'); %! assert_equal (cv.NumObservations, 5); %! assert_equal (cv.NumTestSets, 1); %! assert_equal (cv.TrainSize, 1); %! assert_equal (cv.TestSize, 4); %! assert_equal (sum (test (cv, 1)), 4); %!test %! cv = cvpartition (5, 'holdout', 1); %! assert_equal (cv.Type, 'holdout'); %! assert_equal (cv.NumObservations, 5); %! assert_equal (cv.NumTestSets, 1); %! assert_equal (cv.TrainSize, 4); %! assert_equal (cv.TestSize, 1); %! assert_equal (sum (test (cv, 1)), 1); %!test %! cv = cvpartition (5, 'kfold'); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 5); %! assert_equal (cv.NumTestSets, 5); %!test %! cv = cvpartition (20, 'kfold'); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 20); %! assert_equal (cv.NumTestSets, 10); %!test %! cv = cvpartition (10, 'kfold', 5); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 10); %! assert_equal (cv.NumTestSets, 5); %! assert_equal (cv.TrainSize, [8, 8, 8, 8, 8]); %! assert_equal (cv.TestSize, [2, 2, 2, 2, 2]); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, false); %! assert_equal (test (cv, 1), ! training (cv, 1)); %! assert_equal (test (cv, 'all'), ! training (cv, 'all')); %! assert_equal (size (test (cv, 'all')), [10, 5]); %!test %! grpvar = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 5, 5]; %! rand ('seed', 5); %! cv = cvpartition (12, 'kfold', 5, 'GroupingVariables', grpvar); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 12); %! assert_equal (cv.NumTestSets, 5); %! assert_equal (cv.TrainSize, [10, 10, 10, 8, 10]); %! assert_equal (cv.TestSize, [2, 2, 2, 4, 2]); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, true); %! assert_equal (cv.IsStratified, false); %! assert_equal (test (cv, 1), ! training (cv, 1)); %! assert_equal (test (cv, 'all'), ! training (cv, 'all')); %! assert_equal (size (test (cv, 'all')), [12, 5]); %! assert_equal (sum (test (cv, 'all')), [2, 2, 2, 4, 2]); %! assert_equal (sum (training (cv, 'all')), [10, 10, 10, 8, 10]); %!test %! grpvar = [1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3]; %! rand ('seed', 5); %! cv = cvpartition (12, 'kfold', 3, 'GroupingVariables', grpvar); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 12); %! assert_equal (cv.NumTestSets, 3); %! assert_equal (cv.TrainSize, [9, 10, 5]); %! assert_equal (cv.TestSize, [3, 2, 7]); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, true); %! assert_equal (cv.IsStratified, false); %! assert_equal (test (cv, 1), ! training (cv, 1)); %! assert_equal (test (cv, 'all'), ! training (cv, 'all')); %! assert_equal (size (test (cv, 'all')), [12, 3]); %! assert_equal (sum (test (cv, 'all')), [3, 2, 7]); %! assert_equal (sum (training (cv, 'all')), [9, 10, 5]); %!test %! grpvar = [1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3]; %! rand ('seed', 5); %! cv = cvpartition (12, 'kfold', 2, 'GroupingVariables', grpvar); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 12); %! assert_equal (cv.NumTestSets, 2); %! assert_equal (cv.TrainSize, [6, 6]); %! assert_equal (cv.TestSize, [6, 6]); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, true); %! assert_equal (cv.IsStratified, false); %! assert_equal (test (cv, 1), ! training (cv, 1)); %! assert_equal (test (cv, 'all'), ! training (cv, 'all')); %! assert_equal (size (test (cv, 'all')), [12, 2]); %! assert_equal (sum (test (cv, 'all')), [6, 6]); %! assert_equal (sum (training (cv, 'all')), [6, 6]); %!test %! grpvar = [1, 1, 1, 2, 2, 2, 2, NaN, 2, 3, 3, 3]; %! rand ('seed', 5); %! cv = cvpartition (12, 'kfold', 2, 'GroupingVariables', grpvar); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 12); %! assert_equal (cv.NumTestSets, 2); %! assert_equal (cv.TrainSize, [6, 5]); %! assert_equal (cv.TestSize, [5, 6]); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, true); %! assert_equal (cv.IsStratified, false); %! idx = ! isnan (grpvar); %! assert_equal (test (cv, 1)(idx), ! training (cv, 1)(idx)); %! assert_equal (test (cv, 'all')(idx, :), ! training (cv, 'all')(idx, :)); %! assert_equal (size (test (cv, 'all')), [12, 2]); %! assert_equal (sum (test (cv, 'all')), [5, 6]); %! assert_equal (sum (training (cv, 'all')), [6, 5]); %!test %! grpvar = [1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3]; %! rand ('seed', 5); %! cv = cvpartition (12, 'kfold', 2, 'GroupingVariables', grpvar); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 12); %! assert_equal (cv.NumTestSets, 2); %! assert_equal (cv.TrainSize, [5, 7]); %! assert_equal (cv.TestSize, [7, 5]); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, true); %! assert_equal (cv.IsStratified, false); %! assert_equal (test (cv, 1), ! training (cv, 1)); %! assert_equal (test (cv, 'all'), ! training (cv, 'all')); %! assert_equal (size (test (cv, 'all')), [12, 2]); %! assert_equal (sum (test (cv, 'all')), [7, 5]); %! assert_equal (sum (training (cv, 'all')), [5, 7]); %! assert_equal (test (cv, 1)', grpvar == 2); %! assert_equal (test (cv, 2)', grpvar != 2); %!test %! grpvar = [1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3]; %! rand ('seed', 5); %! cv = cvpartition (12, 'kfold', 2, 'GroupingVariables', grpvar); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 12); %! assert_equal (cv.NumTestSets, 2); %! assert_equal (cv.TrainSize, [7, 5]); %! assert_equal (cv.TestSize, [5, 7]); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, true); %! assert_equal (cv.IsStratified, false); %! assert_equal (test (cv, 1), ! training (cv, 1)); %! assert_equal (test (cv, 'all'), ! training (cv, 'all')); %! assert_equal (size (test (cv, 'all')), [12, 2]); %! assert_equal (sum (test (cv, 'all')), [5, 7]); %! assert_equal (sum (training (cv, 'all')), [7, 5]); %! assert_equal (test (cv, 1)', grpvar == 2); %! assert_equal (test (cv, 2)', grpvar != 2); %!test %! grpvar = [1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3]; %! rand ('seed', 5); %! cv = cvpartition (12, 'kfold', 2, 'GroupingVariables', grpvar); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 12); %! assert_equal (cv.NumTestSets, 2); %! assert_equal (cv.TrainSize, [7, 5]); %! assert_equal (cv.TestSize, [5, 7]); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, true); %! assert_equal (cv.IsStratified, false); %! assert_equal (test (cv, 1), ! training (cv, 1)); %! assert_equal (test (cv, 'all'), ! training (cv, 'all')); %! assert_equal (size (test (cv, 'all')), [12, 2]); %! assert_equal (sum (test (cv, 'all')), [5, 7]); %! assert_equal (sum (training (cv, 'all')), [7, 5]); %! assert_equal (test (cv, 1)', grpvar == 3); %! assert_equal (test (cv, 2)', grpvar != 3); %!test %! status = warning; %! warning ('off'); %! cv = cvpartition (5, 'kfold', 5, 'GroupingVariables', {'a';'a';'b';'b';''}); %! warning (status); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 5); %! assert_equal (cv.NumTestSets, 2); %! assert_equal (cv.TrainSize, [2, 2]); %! assert_equal (cv.TestSize, [2, 2]); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, true); %! assert_equal (cv.IsStratified, false); %! idx = ! ismissing ({'a';'a';'b';'b';''}); %! assert_equal (test (cv, 1)(idx), ! training (cv, 1)(idx)); %! assert_equal (test (cv, 'all')(idx,:), ! training (cv, 'all')(idx,:)); %! assert_equal (size (test (cv, 'all')), [5, 2]); %! assert_equal (sum (test (cv, 'all')), [2, 2]); %! assert_equal (sum (test (cv, 'all'), 2), [1; 1; 1; 1; 0]); ## Test output results for vector input X %!test %! rand ('seed', 5); %! cv = cvpartition ([1, 1, 1, 1, 1, 2, 2, 2, 2, 2], 'holdout', 3); %! assert_equal (cv.Type, 'holdout'); %! assert_equal (cv.NumObservations, 10); %! assert_equal (cv.NumTestSets, 1); %! assert_equal (cv.TrainSize, 7); %! assert_equal (cv.TestSize, 3); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, true); %! assert_equal (test (cv, 1), ! training (cv, 1)); %! assert_equal (test (cv), logical ([0, 0, 0, 0, 1, 0, 1, 0, 0, 1])'); %!test %! cv = cvpartition ([1, 1, 1, 1, 1, 2, 2, 2, 2, 2], 'holdout', 4); %! assert_equal (cv.Type, 'holdout'); %! assert_equal (cv.NumObservations, 10); %! assert_equal (cv.NumTestSets, 1); %! assert_equal (cv.TrainSize, 6); %! assert_equal (cv.TestSize, 4); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, true); %! assert_equal (test (cv, 1), ! training (cv, 1)); %! assert_equal (sum (test (cv)(1:5)), 2); %! assert_equal (sum (test (cv)(6:10)), 2); %!test %! grpvar = [1, 1, 1, 1, 1, 2, 2, 2, 2, 2]; %! rand ('seed', 5); %! cv = cvpartition (grpvar, 'holdout', 4, 'Stratify', false); %! assert_equal (cv.Type, 'holdout'); %! assert_equal (cv.NumObservations, 10); %! assert_equal (cv.NumTestSets, 1); %! assert_equal (cv.TrainSize, 6); %! assert_equal (cv.TestSize, 4); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, false); %! assert_equal (test (cv, 1), ! training (cv, 1)); %! assert_equal (sum (test (cv)(1:5)), 3); %! assert_equal (sum (test (cv)(6:10)), 1); %!test %! cv = cvpartition ([1 1 1 1 1 2 2 2 2 1], 'kfold', 2); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 10); %! assert_equal (cv.NumTestSets, 2); %! assert_equal (cv.TrainSize, [5, 5]); %! assert_equal (cv.TestSize, [5, 5]); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, true); %! assert_equal (test (cv, 1), ! training (cv, 1)); %! assert_equal (test (cv, 'all'), ! training (cv, 'all')); %! assert_equal (sum (test (cv, 1)(1:5)), 3); %! assert_equal (sum (test (cv, 2)(1:5)), 2); %! assert_equal (sum (test (cv, 1)(6:10)), 2); %! assert_equal (sum (test (cv, 2)(6:10)), 3); %!test %! grpvar = [1 1 1 1 1 2 2 2 2 1]; %! rand ('seed', 5); %! cv = cvpartition (grpvar, 'kfold', 2, 'Stratify', false); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 10); %! assert_equal (cv.NumTestSets, 2); %! assert_equal (cv.TrainSize, [5, 5]); %! assert_equal (cv.TestSize, [5, 5]); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, false); %! assert_equal (test (cv, 1), ! training (cv, 1)); %! assert_equal (test (cv, 'all'), ! training (cv, 'all')); %! assert_equal (sum (test (cv, 1)(1:5)), 4); %! assert_equal (sum (test (cv, 2)(1:5)), 1); %! assert_equal (sum (test (cv, 1)(6:10)), 1); %! assert_equal (sum (test (cv, 2)(6:10)), 4); %!test %! status = warning; %! warning ('off'); %! cv = cvpartition ({'a','a','b','b',''}, 'kfold'); %! warning (status); %! assert_equal (cv.Type, 'kfold'); %! assert_equal (cv.NumObservations, 5); %! assert_equal (cv.NumTestSets, 4); %! assert_equal (cv.TrainSize, [3, 3, 3, 3]); %! assert_equal (cv.TestSize, [1, 1, 1, 1]); %! assert_equal (cv.IsCustom, false); %! assert_equal (cv.IsGrouped, false); %! assert_equal (cv.IsStratified, true); %! idx = ! ismissing ({'a','a','b','b',''}); %! assert_equal (test (cv, 1)(idx), ! training (cv, 1)(idx)); %! assert_equal (test (cv, 'all')(idx,:), ! training (cv, 'all')(idx,:)); %! assert_equal (sum (test (cv, 'all'), 2), [1; 1; 1; 1; 0]); ## Test input validation %!test %! ## A vector of set indices returns one column per set, not every set. %! ## The k-fold loop used the whole index vector instead of the loop %! ## variable, so the comparison broadcast and each pass added a column %! ## per requested set. %! c = cvpartition (60, 'KFold', 4); %! t = test (c, [1, 2]); %! assert_equal (size (t), [60, 2]); %! assert_equal (t(:,1), test (c, 1)); %! assert_equal (t(:,2), test (c, 2)); %! assert_equal (any (t(:,1) & t(:,2)), false); %! assert_equal (size (test (c, [1, 3, 4])), [60, 3]); %!test %! ## training indexes the same way, and stays the complement of test %! c = cvpartition (60, 'KFold', 4); %! r = training (c, [1, 2]); %! assert_equal (size (r), [60, 2]); %! assert_equal (r(:,1), training (c, 1)); %! assert_equal (r, ! test (c, [1, 2])); %!test %! ## 'GroupingVariables' with 'HoldOut' holds out whole groups, as %! ## scikit-learn's GroupShuffleSplit does: no group is split between the %! ## training and test sets. %! g = [1 1 1 1 2 2 2 2 3 3 3 3]'; %! c = cvpartition (12, 'HoldOut', 0.25, 'GroupingVariables', g); %! assert_equal (c.IsGrouped, true); %! te = test (c); tr = training (c); %! assert_equal (any (ismember (unique (g(te)), unique (g(tr)))), false); %! assert_equal (tr, ! te); %!test %! ## 'GroupingVariables' with 'LeaveOut' leaves one whole group out at a %! ## time, as scikit-learn's LeaveOneGroupOut does. %! g = [1 1 1 1 2 2 2 2 3 3 3 3]'; %! c = cvpartition (12, 'LeaveOut', 'GroupingVariables', g); %! assert_equal (c.IsGrouped, true); %! assert_equal (c.NumTestSets, 3); %! assert_equal (c.TestSize, [4, 4, 4]); %! for s = 1:3 %! te = test (c, s); %! assert_equal (numel (unique (g(te))), 1); %! assert_equal (training (c, s), ! te); %! endfor %! assert_equal (size (test (c, 'all')), [12, 3]); %!test %! ## the ungrouped forms are untouched %! c = cvpartition (12, 'LeaveOut'); %! assert_equal (c.NumTestSets, 12); %! assert_equal (c.IsGrouped, false); %! c = cvpartition (12, 'HoldOut', 0.25); %! assert_equal (c.IsGrouped, false); %!test %! ## 'Stratify' and 'GroupingVariables' together keep each group whole %! ## while spreading the classes across the folds, as scikit-learn's %! ## StratifiedGroupKFold does. Six single-class groups, two classes. %! g = [1 1 2 2 3 3 4 4 5 5 6 6]'; %! y = [1 1 1 1 1 1 2 2 2 2 2 2]'; %! c = cvpartition (y, 'KFold', 3, 'Stratify', true, ... %! 'GroupingVariables', g); %! assert_equal (c.IsStratified, true); %! assert_equal (c.IsGrouped, true); %! assert_equal (c.NumTestSets, 3); %! assert_equal (c.TestSize, [4, 4, 4]); %! for s = 1:3 %! te = test (c, s); %! ## no group is split between training and test %! assert_equal (any (ismember (unique (g(te)), ... %! unique (g(training (c, s))))), false); %! ## and the fold still sees both classes %! assert_equal (numel (unique (y(te))), 2); %! endfor %!test %! ## 'GroupingVariables' with 'Stratify', false groups without stratifying: %! ## the stratification variable is simply not used, as asked. %! g = [1 1 2 2 3 3 4 4]'; %! y = [1 1 1 1 2 2 2 2]'; %! c = cvpartition (y, 'KFold', 2, 'Stratify', false, 'GroupingVariables', g); %! assert_equal (c.IsGrouped, true); %! assert_equal (c.IsStratified, false); %! for s = 1:c.NumTestSets %! te = test (c, s); %! assert_equal (any (ismember (unique (g(te)), ... %! unique (g(training (c, s))))), false); %! endfor ## A grouping variable may name its groups in the rows of a character ## matrix, which MATLAB accepts and reports the same partition for as the ## cell array of the same names. %!test %! load fisheriris %! c1 = cvpartition (char (species), "KFold", 3); %! c2 = cvpartition (species, "KFold", 3); %! assert_equal (c1.TestSize, c2.TestSize); %! assert_equal (c1.NumObservations, 150); ## A character vector is left alone: there each element is an observation ## already, and turning it into names would change what it partitions. %!test %! c = cvpartition ("aabbcc", "KFold", 3); %! assert_equal (c.NumObservations, 6); %!error ... %! cvpartition (12, 'Resubstitution', 'GroupingVariables', ... %! [1 1 2 2 3 3 1 1 2 2 3 3]) %!error cvpartition (2) %!error ... %! cvpartition (1, 2, 3, 4, 5, 6, 7, 8) %!error ... %! cvpartition ('CustomPartition', 'a') %!error ... %! cvpartition ('CustomPartition', [2, 3; 2, 3]) %!error ... %! cvpartition ('CustomPartition', false (3, 3, 3)) %!error ... %! cvpartition ('CustomPartition', [false, true; true, true; true, false]) %!error ... %! cvpartition ('CustomPartition', false (3, 5)) %!error ... %! cvpartition (-20, 'LeaveOut') %!error ... %! cvpartition (20.5, 'LeaveOut') %!error ... %! cvpartition (20, 'HoldOut', [0.2, 0.3]) %!error ... %! cvpartition (20, 'HoldOut', 'a') %!error ... %! cvpartition (20, 'HoldOut', 0) %!error ... %! cvpartition (20, 'HoldOut', -0.1) %!error ... %! cvpartition (20, 'HoldOut', 21) %!error ... %! cvpartition (20, 'kfold', [2, 3]) %!error ... %! cvpartition (20, 'kfold', 'a') %!error ... %! cvpartition (20, 'kfold', 2.5) %!error ... %! cvpartition (20, 'kfold', 21) %!error ... %! cvpartition (10, 'kfold', 3, 'Group') %!error ... %! cvpartition (10, 'kfold', 3, 'GroupingVariables') %!error ... %! cvpartition (10, 'kfold', 3, 'GroupingVariables', ones (3, 3, 3)) %!error ... %! cvpartition (10, 'kfold', 3, 'GroupingVariables', {'a', 'a', 'a', 'b', 'b'}) %!warning ... %! cvpartition (5, 'kfold', 3, 'GroupingVariables', {'a', 'a', 'a', 'b', 'b'}); %!error ... %! cvpartition (20, 'some') %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', 2, 'strat') %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', 2, 'stratify') %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', 2, 'stratify', [true, true]) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', 2, 'stratify', 'no') %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'holdout', 'a') %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'holdout', 'a', 'stratify', true) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'holdout', [0.2, 0.3]) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'holdout', [0.2, 0.3], 'stratify', true) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'holdout', 0) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'holdout', 0, 'stratify', true) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'holdout', -0.1) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'holdout', -0.1, 'stratify', true) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'holdout', 1.2) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'holdout', 1.2, 'stratify', false) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'holdout', 6) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'holdout', 6, 'stratify', false) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', 'a') %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', 'a', 'stratify', true) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', [2, 3]) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', [2, 3], 'stratify', false) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', 0) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', 0, 'stratify', true) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', 1.5) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', 1.5, 'stratify', true) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', 6) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'kfold', 6, 'stratify', true) %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'leaveout') %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'resubstitution') %!error ... %! cvpartition ([1, 1, 1, 2, 2], 'some') %!error ... %! cvpartition ({1, 1; 2, 2}, 'kfold') ## A scalar SVAL seeds the repartitioning. The keyword was misspelt 'sval' ## rather than 'seed', so every scalar-seeded call used to error out. %!test %! c = cvpartition (60, 'KFold', 5); %! assert_equal (test (repartition (c, 42), 1), test (repartition (c, 42), 1)); %! assert_equal (isequal (test (repartition (c, 42), 1), ... %! test (repartition (c, 43), 1)), false); %!test %! c = cvpartition (60, 'KFold', 5); %! assert_equal (test (repartition (c, [1 2 3]), 1), ... %! test (repartition (c, [1 2 3]), 1)); ## Seeding is scoped to the call: the caller's generator is restored, so the ## seed does not carry into the random numbers drawn afterwards. %!test %! c = cvpartition (60, 'KFold', 5); %! rand ('twister', 5); %! expect = rand (1, 4); %! rand ('twister', 5); %! repartition (c, 42); %! assert_equal (rand (1, 4), expect); %! rand ('twister', 5); %! repartition (c, [1 2 3]); %! assert_equal (rand (1, 4), expect); ## The seeded partition must not depend on the caller's state either. %!test %! c = cvpartition (60, 'KFold', 5); %! rand ('twister', 5); %! a = test (repartition (c, 42), 1); %! rand ('twister', 777); %! assert_equal (test (repartition (c, 42), 1), a); ## Without SVAL the call still consumes randomness, as it must. %!test %! c = cvpartition (60, 'KFold', 5); %! rand ('twister', 5); %! untouched = rand (1, 4); %! rand ('twister', 5); %! repartition (c); %! assert_equal (isequal (rand (1, 4), untouched), false); %!error ... %! repartition (cvpartition ('CustomPartition', [1,1,2,2,3,3])) %!error ... %! repartition (cvpartition ([1 1 1 1 1 2 2 2 2 1], 'kfold', 2, 'Stratify', true), 'legacy') %!error ... %! repartition (cvpartition (20, 'Leaveout', 0.2), 'legacy') %!error ... %! repartition (cvpartition (20, 'Leaveout', 0.2), 'asd') %!error ... %! repartition (cvpartition (20, 'Leaveout', 0.2), 2+i) %!error ... %! repartition (cvpartition (20, 'KFold', 5), [34, 56; 2, 3]) %!error ... %! test (cvpartition (20, 'kfold'), 2, 3) %!error ... %! test (cvpartition (20, 'kfold'), 0) %!error ... %! test (cvpartition (20, 'kfold'), 1.5) %!error ... %! test (cvpartition (20, 'kfold'), [1, 1.5]) %!error ... %! test (cvpartition (20, 'kfold'), [2, 3; 2, 3]) %!error ... %! test (cvpartition (20, 'kfold'), 21) %!error ... %! test (cvpartition (20, 'kfold'), [18, 21]) %!error ... %! training (cvpartition (20, 'kfold'), 2, 3) %!error ... %! training (cvpartition (20, 'kfold'), 0) %!error ... %! training (cvpartition (20, 'kfold'), 1.5) %!error ... %! training (cvpartition (20, 'kfold'), [1, 1.5]) %!error ... %! training (cvpartition (20, 'kfold'), [2, 3; 2, 3]) %!error ... %! training (cvpartition (20, 'kfold'), 21) %!error ... %! training (cvpartition (20, 'kfold'), [18, 21]) ## Test 'summary' method %!test %! ## 1. Stratified K-Fold: Basic Text Labels %! species = [repmat({'Setosa'}, 10, 1); repmat({'Versicolor'}, 10, 1)]; %! rand ('state', 42); %! c = cvpartition (species, 'KFold', 2); %! T = summary (c); %! assert_equal (height (T), 10); %! assert_equal (all (ismember ({'Set', 'SetSize', 'StratificationLabel', ... %! 'StratificationCount', 'PercentInSet'}, ... %! T.Properties.VariableNames)), true); %! %! ## Check Output Type (String Array) and Counts %! if (exist ('string', 'class')) %! assert_equal (isa (T.Set, 'string'), true); %! assert_equal (isa (T.StratificationLabel, 'string'), true); %! mask = (T.Set == 'all') & (T.StratificationLabel == 'Setosa'); %! else %! ## Fallback for older environments %! mask = strcmp (T.Set, 'all') & strcmp (T.StratificationLabel, 'Setosa'); %! endif %! assert_equal (T.StratificationCount(mask), 10); %!test %! ## 2. Grouped K-Fold: Basic Numeric Labels %! groups = [1; 1; 1; 2; 2; 3; 3; 3; 3; 3]; %! rand ('state', 100); %! c = cvpartition (numel (groups), 'KFold', 2, 'GroupingVariables', groups); %! T = summary (c); %! assert_equal (any (strcmp ('GroupLabel', T.Properties.VariableNames)), true); %! %! ## Verify Group Integrity %! if (iscell (T.GroupLabel)) %! vals = cell2mat (T.GroupLabel); %! else %! vals = T.GroupLabel; %! endif %! mask_g3 = (vals == 3); %! %! if (exist ('string', 'class')) %! mask_t1 = (T.Set == 'test1'); %! else %! mask_t1 = strcmp (T.Set, 'test1'); %! endif %! %! count_g3 = T.GroupCount(mask_g3 & mask_t1); %! assert_equal (count_g3 == 5 || count_g3 == 0, true); %!test %! ## 3. Grouped K-Fold: Matrix Grouping %! g1 = [1; 1; 1; 2; 2; 2]; %! g2 = [1; 1; 2; 1; 2; 2]; %! groups = [g1, g2]; %! c = cvpartition (6, 'KFold', 2, 'GroupingVariables', groups); %! T = summary (c); %! ## 4 unique groups * 5 sets (all + 2 train + 2 test) %! assert_equal (height (T), 20); %!test %! ## 4. Stratified Holdout: Basic %! species = [repmat({'A'}, 10, 1); repmat({'B'}, 10, 1)]; %! c = cvpartition (species, 'Holdout', 0.5); %! T = summary (c); %! sets = unique (T.Set); %! assert_equal (numel (sets), 3); ## all, train1, test1 %!test %! ## 5. Mathematical Consistency: Percentages %! classes = [1; 1; 2; 2; 3; 3]; %! c = cvpartition (classes, 'KFold', 2); %! T = summary (c); %! %! if (exist ('string', 'class')) %! mask_all = (T.Set == 'all'); %! mask_tr1 = (T.Set == 'train1'); %! else %! mask_all = strcmp (T.Set, 'all'); %! mask_tr1 = strcmp (T.Set, 'train1'); %! endif %! %! assert_equal (sum (T.PercentInSet(mask_all)), 100, 1e-10); %! assert_equal (sum (T.PercentInSet(mask_tr1)), 100, 1e-10); %!test %! ## 6. Mathematical Consistency: Set Sizes %! N = 20; %! c = cvpartition (ones (N, 1), 'KFold', 4); %! T = summary (c); %! %! if (exist ('string', 'class')) %! mask_tr1 = (T.Set == 'train1'); %! mask_ts1 = (T.Set == 'test1'); %! else %! mask_tr1 = strcmp (T.Set, 'train1'); %! mask_ts1 = strcmp (T.Set, 'test1'); %! endif %! %! size_tr1 = T.SetSize(find (mask_tr1, 1)); %! size_ts1 = T.SetSize(find (mask_ts1, 1)); %! assert_equal (size_tr1 + size_ts1, N); %!test %! ## 7. Logical Grouping Variables %! groups = [true; true; true; false; false]; %! c = cvpartition (5, 'KFold', 2, 'GroupingVariables', groups); %! T = summary (c); %! assert_equal (height (T), 2 * 5); %! if (iscell (T.GroupLabel)) %! u_labels = unique (cell2mat (T.GroupLabel)); %! else %! u_labels = unique (T.GroupLabel); %! endif %! assert_equal (numel (u_labels), 2); %!test %! ## 8. Char Array Grouping Variables %! groups = ['A'; 'A'; 'B'; 'B'; 'C']; %! c = cvpartition (5, 'KFold', 2, 'GroupingVariables', groups); %! T = summary (c); %! assert_equal (height (T), 3 * 5); %! assert_equal (any (strcmp ('GroupLabel', T.Properties.VariableNames)), true); %!test %! ## 9. Floating Point Grouping Variables %! groups = [1.1; 1.1; 2.2; 2.2]; %! c = cvpartition (4, 'KFold', 2, 'GroupingVariables', groups); %! T = summary (c); %! if (iscell (T.GroupLabel)) %! vals = cell2mat (T.GroupLabel); %! else %! vals = T.GroupLabel; %! endif %! assert_equal (any (abs (vals - 1.1) < 1e-10), true); %! assert_equal (any (abs (vals - 2.2) < 1e-10), true); %!test %! ## 10. Negative Numeric Grouping %! groups = [-5; -5; -10; -10]; %! c = cvpartition (4, 'KFold', 2, 'GroupingVariables', groups); %! T = summary (c); %! assert_equal (height (T), 2 * 5); %!test %! ## 11. Missing Values in Stratification (NaN) %! classes = [1; 1; 2; 2; NaN; NaN]; %! c = cvpartition (classes, 'KFold', 2); %! T = summary (c); %! if (exist ('string', 'class')) %! mask_all = (T.Set == 'all'); %! else %! mask_all = strcmp (T.Set, 'all'); %! endif %! total_obs = T.SetSize(find (mask_all, 1)); %! assert_equal (total_obs, 4); %!test %! ## 12. Missing Values in Grouping (NaN) %! groups = [1; 1; 2; 2; NaN]; %! c = cvpartition (5, 'KFold', 2, 'GroupingVariables', groups); %! T = summary (c); %! if (exist ('string', 'class')) %! mask_all = (T.Set == 'all'); %! else %! mask_all = strcmp (T.Set, 'all'); %! endif %! assert_equal (T.SetSize(find (mask_all, 1)), 4); %!test %! ## 13. Unbalanced Stratification %! species = [repmat({'C1'}, 90, 1); repmat({'C2'}, 10, 1)]; %! c = cvpartition (species, 'KFold', 2); %! T = summary (c); %! if (exist ('string', 'class')) %! mask_ts1 = (T.Set == 'test1'); %! subT = T(mask_ts1, :); %! c1_count = subT.StratificationCount(subT.StratificationLabel == 'C1'); %! c2_count = subT.StratificationCount(subT.StratificationLabel == 'C2'); %! else %! mask_ts1 = strcmp (T.Set, 'test1'); %! subT = T(mask_ts1, :); %! c1_count = subT.StratificationCount(strcmp (subT.StratificationLabel, 'C1')); %! c2_count = subT.StratificationCount(strcmp (subT.StratificationLabel, 'C2')); %! endif %! assert_equal (c1_count == 45, true); %! assert_equal (c2_count == 5, true); %!test %! ## 14. Single Observation per Group (Edge Case) %! groups = [1; 2; 3; 4]; %! c = cvpartition (4, 'KFold', 2, 'GroupingVariables', groups); %! T = summary (c); %! if (exist ('string', 'class')) %! mask_ts1 = (T.Set == 'test1'); %! else %! mask_ts1 = strcmp (T.Set, 'test1'); %! endif %! counts = T.GroupCount(mask_ts1); %! assert_equal (sum (counts == 1), 2); %! assert_equal (sum (counts == 0), 2); %!test %! ## 15. Set Name Generation Verification %! species = [1; 1; 2; 2]; %! c = cvpartition (species, 'KFold', 2); %! T = summary (c); %! set_names = unique (T.Set); %! expected = {'all'; 'train1'; 'test1'; 'train2'; 'test2'}; %! if (exist ('string', 'class')) %! ## Convert string array to cell for sort comparison %! assert_equal (sort (cellstr (set_names)), sort (expected)); %! else %! assert_equal (sort (set_names), sort (expected)); %! endif %!test %! ## 16. Label Column Consistency %! groups = ['A'; 'B']; %! c = cvpartition (2, 'KFold', 2, 'GroupingVariables', groups); %! T = summary (c); %! if (exist ('string', 'class')) %! assert_equal (isa (T.GroupLabel, 'string'), true); %! else %! assert_equal (iscellstr (T.GroupLabel), true); %! endif %!test %! ## 17. Valid "Blank" Labels (Space) - FIX APPLIED %! species = {'A'; 'A'; ' '; ' '}; %! c = cvpartition (species, 'KFold', 2); %! T = summary (c); %! if (exist ('string', 'class')) %! labels = cellstr (T.StratificationLabel); %! sets = cellstr (T.Set); %! assert_equal (any (strcmp (labels, ' ')), true); %! mask_space = strcmp (labels, ' '); %! mask_all = strcmp (sets, 'all'); %! else %! assert_equal (any (strcmp (T.StratificationLabel, ' ')), true); %! mask_space = strcmp (T.StratificationLabel, ' '); %! mask_all = strcmp (T.Set, 'all'); %! endif %! assert_equal (sum (T.StratificationCount(mask_space & mask_all)), 2); %!test %! ## 18. Large K (Leave-One-Out Simulation) - FIX APPLIED %! species = [1; 1; 2; 2]; %! warn_state = warning ("off", 'all'); %! c = cvpartition (species, 'KFold', 4); %! warning (warn_state); %! T = summary (c); %! assert_equal (height (T), 18); %! if (exist ('string', 'class')) %! mask_test = startsWith (cellstr(T.Set), 'test'); %! else %! mask_test = strncmp (T.Set, 'test', 4); %! endif %! assert_equal (all (T.SetSize(mask_test) == 1), true); %!test %! ## 19. Repeated Holdout Integrity %! species = [1; 1; 2; 2]; %! rand ('state', 42); %! c = cvpartition (species, 'Holdout', 0.5); %! T = summary (c); %! if (exist ('string', 'class')) %! mask_ts1 = (T.Set == 'test1'); %! else %! mask_ts1 = strcmp (T.Set, 'test1'); %! endif %! size_ts1 = T.SetSize(find (mask_ts1, 1)); %! assert_equal (size_ts1, 2); %!test %! ## 20. Empty String Handling (Missing Data) %! species = {'A'; 'A'; ''; ''}; %! c = cvpartition (species, 'KFold', 2); %! T = summary (c); %! if (exist ('string', 'class')) %! assert_equal (! any (T.StratificationLabel == ''), true); %! mask_all = (T.Set == 'all'); %! else %! assert_equal (! any (strcmp (T.StratificationLabel, '')), true); %! mask_all = strcmp (T.Set, 'all'); %! endif %! total_rows = T.SetSize(find (mask_all, 1)); %! assert_equal (total_rows, 2); %!test %! ## 21. Basic Unstacking (Stratified K-Fold) %! species = [repmat({'Alpha'}, 10, 1); repmat({'Beta'}, 10, 1)]; %! c = cvpartition (species, 'KFold', 2); %! T = summary (c); %! T_wide = unstack (T(:, 1:4), 'StratificationCount', 'StratificationLabel'); %! ## Check dimensions: 3 sets (all, train1, test1, etc) x (Set+SetSize + 2 Labels) %! assert_equal (height (T_wide), 5); %! assert_equal (width (T_wide), 4); %! assert_equal (all (ismember ({'Alpha', 'Beta'}, T_wide.Properties.VariableNames)), true); %!test %! ## 22. Data Integrity Check (Row Sums) %! species = [repmat({'Control'}, 20, 1); repmat({'Treatment'}, 80, 1)]; %! c = cvpartition (species, 'Holdout', 0.25); %! T = summary (c); %! T_wide = unstack (T(:, 1:4), 'StratificationCount', 'StratificationLabel'); %! row_sums = T_wide.Control + T_wide.Treatment; %! assert_equal (all (row_sums == T_wide.SetSize), true); %!test %! ## 23. Unstacking Grouped Data (Numeric Labels) %! groups = [1; 1; 2; 2; 2]; %! c = cvpartition (5, 'KFold', 2, 'GroupingVariables', groups); %! T = summary (c); %! T_wide = unstack (T(:, 1:4), 'GroupCount', 'GroupLabel'); %! ## Check if numeric columns were created successfully %! col_names = T_wide.Properties.VariableNames; %! assert_equal (any (cellfun (@(x) ! isempty (strfind (x, '1')), col_names)), true); %! assert_equal (any (cellfun (@(x) ! isempty (strfind (x, '2')), col_names)), true); %!test %! ## 24. Unstacking with Missing/NaN Groups %! groups = [1; 1; 2; 2; NaN]; %! c = cvpartition (5, 'KFold', 2, 'GroupingVariables', groups); %! T = summary (c); %! T_wide = unstack (T(:, 1:4), 'GroupCount', 'GroupLabel'); %! ## Should only have columns for 1 and 2, not NaN or 'undefined' %! assert_equal (width (T_wide), 4); ## Set, SetSize, x1, x2 %!test %! ## 25. Unstacking String Array Inputs %! species = {'Red'; 'Blue'; 'Red'; 'Blue'}; %! c = cvpartition (species, 'KFold', 2); %! T = summary (c); %! ## Verify input is actually string before unstacking checks %! if (exist ('string', 'class')) %! assert_equal (isa (T.Set, 'string'), true); %! endif %! T_wide = unstack (T(:, 1:4), 'StratificationCount', 'StratificationLabel'); %! ## Check the 'all' row count for Red %! assert_equal (T_wide.Red(strcmp(cellstr(T_wide.Set), 'all')) == 2, true); %!test %! ## 26. Large K Unstacking (Many Rows) %! species = [repmat({'High'}, 10, 1); repmat({'Low'}, 10, 1)]; %! c = cvpartition (species, 'KFold', 10); %! T = summary (c); %! T_wide = unstack (T(:, 1:4), 'StratificationCount', 'StratificationLabel'); %! ## 10 folds * 2 (train/test) + 1 (all) = 21 rows %! assert_equal (height (T_wide), 21); %!test %! ## 27. Unstacking with Special Characters in Labels %! species = {'Type A'; 'Type A'; 'Type-B'; 'Type-B'}; %! c = cvpartition (species, 'KFold', 2); %! T = summary (c); %! T_wide = unstack (T(:, 1:4), 'StratificationCount', 'StratificationLabel'); %! vnames = T_wide.Properties.VariableNames; %! ## Check if spaces/dashes were handled/preserved in some valid form %! assert_equal (numel (vnames), 4); %!test %! ## 28. Verification of 'all' row logic after Unstacking %! species = [repmat({'Yes'}, 50, 1); repmat({'No'}, 50, 1)]; %! c = cvpartition (species, 'Holdout', 0.2); %! T = summary (c); %! T_wide = unstack (T(:, 1:4), 'StratificationCount', 'StratificationLabel'); %! mask = strcmp (cellstr (T_wide.Set), 'all'); %! assert_equal (T_wide.Yes(mask) == 50, true); %! assert_equal (T_wide.No(mask) == 50, true); %!test %! ## 29. Robustness against re-ordering %! species = {'Left'; 'Left'; 'Right'; 'Right'}; %! c = cvpartition (species, 'Holdout', 0.5); %! T = summary (c); %! T_shuffled = T([3, 1, 2], :); %! T_wide = unstack (T_shuffled(:, 1:4), 'StratificationCount', 'StratificationLabel'); %! mask = strcmp (cellstr (T_wide.Set), 'all'); %! assert_equal (T_wide.Left(mask) == 2, true); %!error %! c = cvpartition (20, 'KFold', 5); %! summary (c); %!error %! c = cvpartition (10, 'LeaveOut'); %! summary (c); statistics-release-1.9.2/inst/Model_Evaluation/doc-cache000066400000000000000000001414301524624707500233070ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 52 # name: # type: sq_string # elements: 1 # length: 20 ConfusionMatrixChart # name: # type: sq_string # elements: 1 # length: 759 statistics: ConfusionMatrixChart Confusion matrix chart for classification results The ConfusionMatrixChart class implements a confusion matrix chart object, which displays the classification performance of a classifier by showing the counts of true positive, true negative, false positive, and false negative predictions. A confusion matrix chart is a visual representation of the performance of a classification algorithm. The rows represent the true classes and the columns represent the predicted classes. The diagonal elements represent the correctly classified observations, while the off-diagonal elements represent the misclassified observations. Create a ConfusionMatrixChart object by using the confusionchart function. See also: confusionchart # name: # type: sq_string # elements: 1 # length: 49 Confusion matrix chart for classification results # name: # type: sq_string # elements: 1 # length: 32 ConfusionMatrixChart.ClassLabels # name: # type: sq_string # elements: 1 # length: 177 ConfusionMatrixChart: property ClassLabels Class labels A cell array of character vectors containing the class labels used in the confusion matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 12 Class labels # name: # type: sq_string # elements: 1 # length: 34 ConfusionMatrixChart.ColumnSummary # name: # type: sq_string # elements: 1 # length: 352 ConfusionMatrixChart: property ColumnSummary Column summary display A character vector specifying whether and how to display column summaries. Supported values are: 'off' - Do not display column summary (default) 'absolute' - Display absolute counts 'column-normalized' - Display normalized by column 'total-normalized' - Display normalized by total # name: # type: sq_string # elements: 1 # length: 22 Column summary display # name: # type: sq_string # elements: 1 # length: 41 ConfusionMatrixChart.ConfusionMatrixChart # name: # type: sq_string # elements: 1 # length: 2805 statistics: cmc = ConfusionMatrixChart ( hax , cm , cl ) statistics: cmc = ConfusionMatrixChart (…, name , value ) Create a ConfusionMatrixChart object for visualizing classification performance. cmc = ConfusionMatrixChart ( hax , cm , cl ) returns a ConfusionMatrixChart object with parent axes hax , confusion matrix cm , and class labels cl . hax must be a valid axes handle where the chart will be displayed. cm must be a square numeric matrix containing the confusion matrix values, where rows represent true classes and columns represent predicted classes. cl must be a cell array of character vectors containing the class labels. The number of labels must match the size of the confusion matrix. cmc = ConfusionMatrixChart (…, name , value ) returns a ConfusionMatrixChart object with additional parameters specified by name , value paired arguments: Name Value 'XLabel' A character vector specifying the x-axis label. Default is "Predicted Class". 'YLabel' A character vector specifying the y-axis label. Default is "True Class". 'Title' A character vector specifying the chart title. Default is empty string. 'FontName' A character vector specifying the font name for text elements. Default is the axes font name. 'FontSize' A numeric scalar specifying the font size for text elements. Default is the axes font size. 'DiagonalColor' A 1x3 RGB vector specifying the color for diagonal elements (correct classifications). Default is [0.0, 0.4471, 0.7412]. 'OffDiagonalColor' A 1x3 RGB vector specifying the color for off-diagonal elements (misclassifications). Default is [0.8510, 0.3255, 0.0980]. 'Normalization' A character vector specifying the normalization method. Supported values are 'absolute' , 'column-normalized' , 'row-normalized' , and 'total-normalized' . Default is 'absolute' . 'ColumnSummary' A character vector specifying whether and how to display column summaries. Supported values are 'off' , 'absolute' , 'column-normalized' , and 'total-normalized' . Default is 'off' . 'RowSummary' A character vector specifying whether and how to display row summaries. Supported values are 'off' , 'absolute' , 'row-normalized' , and 'total-normalized' . Default is 'off' . 'GridVisible' A character vector specifying whether to display grid lines. Supported values are 'on' and 'off' . Default is 'on' . 'HandleVisibility' A character vector specifying the handle visibility. Supported values are 'on' , 'off' , and 'callback' . 'OuterPosition' A 1x4 numeric vector specifying the outer position of the chart. 'Position' A 1x4 numeric vector specifying the position of the chart. 'Units' A character vector specifying the position units. Supported values are 'centimeters' , 'characters' , 'inches' , 'normalized' , 'pixels' , and 'points' . See also: confusionchart # name: # type: sq_string # elements: 1 # length: 80 Create a ConfusionMatrixChart object for visualizing classification performance. # name: # type: sq_string # elements: 1 # length: 34 ConfusionMatrixChart.DiagonalColor # name: # type: sq_string # elements: 1 # length: 238 ConfusionMatrixChart: property DiagonalColor Color for diagonal elements A 1x3 RGB vector specifying the color for the diagonal elements of the confusion matrix, which represent correct classifications. Default is [0.0, 0.4471, 0.7412]. # name: # type: sq_string # elements: 1 # length: 27 Color for diagonal elements # name: # type: sq_string # elements: 1 # length: 29 ConfusionMatrixChart.FontName # name: # type: sq_string # elements: 1 # length: 210 ConfusionMatrixChart: property FontName Font name for text elements A character vector specifying the font name used for all text elements in the chart. Default is empty string, which uses the axes font name. # name: # type: sq_string # elements: 1 # length: 27 Font name for text elements # name: # type: sq_string # elements: 1 # length: 29 ConfusionMatrixChart.FontSize # name: # type: sq_string # elements: 1 # length: 197 ConfusionMatrixChart: property FontSize Font size for text elements A numeric scalar specifying the font size used for all text elements in the chart. Default is 0, which uses the axes font size. # name: # type: sq_string # elements: 1 # length: 27 Font size for text elements # name: # type: sq_string # elements: 1 # length: 32 ConfusionMatrixChart.GridVisible # name: # type: sq_string # elements: 1 # length: 228 ConfusionMatrixChart: property GridVisible Grid visibility A character vector specifying whether to display grid lines in the confusion matrix. Supported values are: 'on' - Display grid lines (default) 'off' - Hide grid lines # name: # type: sq_string # elements: 1 # length: 15 Grid visibility # name: # type: sq_string # elements: 1 # length: 37 ConfusionMatrixChart.HandleVisibility # name: # type: sq_string # elements: 1 # length: 195 ConfusionMatrixChart: property HandleVisibility Handle visibility A character vector specifying the visibility of the object’s handle. Supported values are 'on' , 'off' , and 'callback' . # name: # type: sq_string # elements: 1 # length: 17 Handle visibility # name: # type: sq_string # elements: 1 # length: 34 ConfusionMatrixChart.Normalization # name: # type: sq_string # elements: 1 # length: 396 ConfusionMatrixChart: property Normalization Normalization method for confusion matrix values A character vector specifying how to normalize the confusion matrix values. Supported values are: 'absolute' - Display absolute counts (default) 'column-normalized' - Normalize by column totals 'row-normalized' - Normalize by row totals 'total-normalized' - Normalize by total number of observations # name: # type: sq_string # elements: 1 # length: 48 Normalization method for confusion matrix values # name: # type: sq_string # elements: 1 # length: 37 ConfusionMatrixChart.NormalizedValues # name: # type: sq_string # elements: 1 # length: 227 ConfusionMatrixChart: property NormalizedValues Normalized confusion matrix values A numeric matrix containing the normalized confusion matrix values according to the current normalization setting. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Normalized confusion matrix values # name: # type: sq_string # elements: 1 # length: 37 ConfusionMatrixChart.OffDiagonalColor # name: # type: sq_string # elements: 1 # length: 247 ConfusionMatrixChart: property OffDiagonalColor Color for off-diagonal elements A 1x3 RGB vector specifying the color for the off-diagonal elements of the confusion matrix, which represent misclassifications. Default is [0.8510, 0.3255, 0.0980]. # name: # type: sq_string # elements: 1 # length: 31 Color for off-diagonal elements # name: # type: sq_string # elements: 1 # length: 34 ConfusionMatrixChart.OuterPosition # name: # type: sq_string # elements: 1 # length: 183 ConfusionMatrixChart: property OuterPosition Outer position of the chart A 1x4 numeric vector specifying the outer position of the chart in the format [left, bottom, width, height]. # name: # type: sq_string # elements: 1 # length: 27 Outer position of the chart # name: # type: sq_string # elements: 1 # length: 27 ConfusionMatrixChart.Parent # name: # type: sq_string # elements: 1 # length: 132 ConfusionMatrixChart: property Parent Parent object A handle to the parent figure or container object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 13 Parent object # name: # type: sq_string # elements: 1 # length: 29 ConfusionMatrixChart.Position # name: # type: sq_string # elements: 1 # length: 166 ConfusionMatrixChart: property Position Position of the chart A 1x4 numeric vector specifying the position of the chart in the format [left, bottom, width, height]. # name: # type: sq_string # elements: 1 # length: 21 Position of the chart # name: # type: sq_string # elements: 1 # length: 31 ConfusionMatrixChart.RowSummary # name: # type: sq_string # elements: 1 # length: 334 ConfusionMatrixChart: property RowSummary Row summary display A character vector specifying whether and how to display row summaries. Supported values are: 'off' - Do not display row summary (default) 'absolute' - Display absolute counts 'row-normalized' - Display normalized by row 'total-normalized' - Display normalized by total # name: # type: sq_string # elements: 1 # length: 19 Row summary display # name: # type: sq_string # elements: 1 # length: 26 ConfusionMatrixChart.Title # name: # type: sq_string # elements: 1 # length: 146 ConfusionMatrixChart: property Title Chart title A character vector specifying the title of the confusion matrix chart. Default is empty string. # name: # type: sq_string # elements: 1 # length: 11 Chart title # name: # type: sq_string # elements: 1 # length: 26 ConfusionMatrixChart.Units # name: # type: sq_string # elements: 1 # length: 226 ConfusionMatrixChart: property Units Position units A character vector specifying the units for the position properties. Supported values are 'centimeters' , 'characters' , 'inches' , 'normalized' , 'pixels' , and 'points' . # name: # type: sq_string # elements: 1 # length: 14 Position units # name: # type: sq_string # elements: 1 # length: 27 ConfusionMatrixChart.XLabel # name: # type: sq_string # elements: 1 # length: 138 ConfusionMatrixChart: property XLabel X-axis label A character vector specifying the label for the x-axis. Default is "Predicted Class". # name: # type: sq_string # elements: 1 # length: 12 X-axis label # name: # type: sq_string # elements: 1 # length: 27 ConfusionMatrixChart.YLabel # name: # type: sq_string # elements: 1 # length: 133 ConfusionMatrixChart: property YLabel Y-axis label A character vector specifying the label for the y-axis. Default is "True Class". # name: # type: sq_string # elements: 1 # length: 12 Y-axis label # name: # type: sq_string # elements: 1 # length: 32 ConfusionMatrixChart.sortClasses # name: # type: sq_string # elements: 1 # length: 689 ConfusionMatrixChart: sortClasses ( cmc , order ) Sort the classes in the confusion matrix chart. sortClasses ( cmc , order ) sorts the classes in the confusion matrix chart cmc according to the specified order . order can be: A cell array of class labels in the desired order 'auto' - Sort class labels alphabetically 'ascending-diagonal' - Sort by ascending diagonal values 'descending-diagonal' - Sort by descending diagonal values 'cluster' - Sort using hierarchical clustering When using 'cluster' , the classes are grouped based on similarity using hierarchical clustering, which can help identify groups of frequently confused classes. See also: confusionchart, linkage, pdist # name: # type: sq_string # elements: 1 # length: 47 Sort the classes in the confusion matrix chart. # name: # type: sq_string # elements: 1 # length: 14 confusionchart # name: # type: sq_string # elements: 1 # length: 1675 statistics: confusionchart ( trueLabels , predictedLabels ) statistics: confusionchart ( m ) statistics: confusionchart ( m , classLabels ) statistics: confusionchart ( parent , …) statistics: confusionchart (…, prop , val , …) statistics: cm = confusionchart (…) Display a chart of a confusion matrix. The two vectors of values trueLabels and predictedLabels , which are used to compute the confusion matrix, must be defined with the same format as the inputs of confusionmat . Otherwise a confusion matrix m as computed by confusionmat can be given. classLabels is an array of labels, i.e. the list of the class names. If the first argument is a handle to a figure or to a uipanel , then the confusion matrix chart is displayed inside that object. Optional property/value pairs are passed directly to the underlying objects, e.g. 'xlabel' , 'ylabel' , 'title' , 'fontname' , 'fontsize' etc. The optional return value cm is a ConfusionMatrixChart object. Specific properties of a ConfusionMatrixChart object are: 'DiagonalColor' The color of the patches on the diagonal, default is [0.0, 0.4471, 0.7412]. 'OffDiagonalColor' The color of the patches off the diagonal, default is [0.851, 0.3255, 0.098]. 'GridVisible' Available values: on (default), off . 'Normalization' Available values: absolute (default), column-normalized , row-normalized , total-normalized . 'ColumnSummary' Available values: off (default), absolute , column-normalized , total-normalized . 'RowSummary' Available values: off (default), absolute , row-normalized , total-normalized . Run demo confusionchart to see some examples. See also: confusionmat, sortClasses # name: # type: sq_string # elements: 1 # length: 38 Display a chart of a confusion matrix. # name: # type: sq_string # elements: 1 # length: 12 confusionmat # name: # type: sq_string # elements: 1 # length: 1254 statistics: C = confusionmat ( group , grouphat ) statistics: C = confusionmat ( group , grouphat , "Order", grouporder ) statistics: [ C , order ] = confusionmat ( group , grouphat ) Compute a confusion matrix for classification problems confusionmat returns the confusion matrix C for the group of actual values group and the group of predicted values grouphat . The row indices of the confusion matrix represent actual values, while the column indices represent predicted values. The indices are the same for both actual and predicted values, so the confusion matrix is a square matrix. Each element of the matrix represents the number of matches between a given actual value (row index) and a given predicted value (column index), hence correct matches lie on the main diagonal of the matrix. The order of the rows and columns is returned in order . group and grouphat must have the same number of observations and the same data type. Valid data types are numeric vectors, logical vectors, character arrays, string arrays, cell arrays of strings, and categorical arrays. The order of the rows and columns can be specified by setting the grouporder variable. The data type of grouporder must be the same of group and grouphat . See also: crosstab # name: # type: sq_string # elements: 1 # length: 54 Compute a confusion matrix for classification problems # name: # type: sq_string # elements: 1 # length: 8 crossval # name: # type: sq_string # elements: 1 # length: 1883 statistics: results = crossval ( f , X , y ) statistics: results = crossval ( f , X , y , name , value ) Perform cross validation on given data. f should be a function that takes 4 inputs xtrain , ytrain , xtest , ytest , fits a model based on xtrain , ytrain , applies the fitted model to xtest , and returns a goodness of fit measure based on comparing the predicted and actual ytest . crossval returns an array containing the values returned by f for every cross-validation fold or resampling applied to the given data. X should be an n by m matrix of predictor values y should be an n by 1 vector of predicand values Optional arguments may include name-value pairs as follows: 'KFold' Divide set into k equal-size subsets, using each one successively for validation. 'HoldOut' Divide set into two subsets, training and validation. If the value k is a fraction, that is the fraction of values put in the validation subset (by default k =0.1); if it is a positive integer, that is the number of values in the validation subset. 'LeaveOut' Leave-one-out partition (each element is placed in its own subset). The value is ignored, but it is required. 'Partition' The value should be a cvpartition object. 'Given' The value should be an n by 1 vector specifying in which partition to put each element. 'stratify' The value should be an n by 1 vector containing class designations for the elements, in which case the 'KFold' and 'HoldOut' partitionings attempt to ensure each partition represents the classes proportionately. 'mcreps' The value should be a positive integer specifying the number of times to resample based on different partitionings. Currently only works with the partition type 'HoldOut' . Only one of 'KFold' , 'HoldOut' , 'LeaveOut' , 'Given' , 'Partition' should be specified. If none is specified, the default is 'KFold' with k = 10. See also: cvpartition # name: # type: sq_string # elements: 1 # length: 39 Perform cross validation on given data. # name: # type: sq_string # elements: 1 # length: 11 cvpartition # name: # type: sq_string # elements: 1 # length: 256 statistics: cvpartition Partition data for cross-validation The cvpartition class generates a partitioning scheme on a dataset to facilitate cross-validation of statistical models utilizing training and testing subsets of the dataset. See also: crossval # name: # type: sq_string # elements: 1 # length: 35 Partition data for cross-validation # name: # type: sq_string # elements: 1 # length: 20 cvpartition.IsCustom # name: # type: sq_string # elements: 1 # length: 220 cvpartition: property IsCustom Flag for custom partition A logical scalar specifying whether the cvpartition object was created using custom partition partitioning ( true ) or not ( false ). This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Flag for custom partition # name: # type: sq_string # elements: 1 # length: 21 cvpartition.IsGrouped # name: # type: sq_string # elements: 1 # length: 211 cvpartition: property IsGrouped Flag for grouped partition A logical scalar specifying whether the cvpartition object was created using grouping variables ( true ) or not ( false ). This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Flag for grouped partition # name: # type: sq_string # elements: 1 # length: 24 cvpartition.IsStratified # name: # type: sq_string # elements: 1 # length: 199 cvpartition: property IsStratified Flag for stratified partition A logical scalar specifying whether the cvpartition object was created with a 'stratify' value of true . This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Flag for stratified partition # name: # type: sq_string # elements: 1 # length: 27 cvpartition.NumObservations # name: # type: sq_string # elements: 1 # length: 217 cvpartition: property NumObservations Number of observations A positive integer scalar specifying the number of observations in the dataset (including any missing data, where applicable). This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 23 cvpartition.NumTestSets # name: # type: sq_string # elements: 1 # length: 264 cvpartition: property NumTestSets Number of test sets A positive integer scalar specifying the number of folds for partition types 'kfold' and 'leaveout' . When partition type is 'holdout' and 'resubstitution' , then NumTestSets is 1. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Number of test sets # name: # type: sq_string # elements: 1 # length: 20 cvpartition.TestSize # name: # type: sq_string # elements: 1 # length: 313 cvpartition: property TestSize Size of each test set A positive integer scalar specifying the size of the test set for partition types 'holdout' and 'resubstitution' or a vector of positive integers specifying the size of each testing set for partition types 'kfold' and 'leaveout' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Size of each test set # name: # type: sq_string # elements: 1 # length: 21 cvpartition.TrainSize # name: # type: sq_string # elements: 1 # length: 317 cvpartition: property TrainSize Size of each train set A positive integer scalar specifying the size of the train set for partition types 'holdout' and 'resubstitution' or a vector of positive integers specifying the size of each training set for partition types 'kfold' and 'leaveout' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Size of each train set # name: # type: sq_string # elements: 1 # length: 16 cvpartition.Type # name: # type: sq_string # elements: 1 # length: 210 cvpartition: property Type Type of validation partition A character vector specifying the type of the cvpartition object. It can be kfold , holdout , leaveout , or resubstitution . This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Type of validation partition # name: # type: sq_string # elements: 1 # length: 23 cvpartition.cvpartition # name: # type: sq_string # elements: 1 # length: 7450 cvpartition: C = cvpartition ( n , 'KFold' ) cvpartition: C = cvpartition ( n , 'KFold' , k ) cvpartition: C = cvpartition ( n , 'KFold' , k , 'GroupingVariables' , grpvars ) cvpartition: C = cvpartition ( n , 'Holdout' ) cvpartition: C = cvpartition ( n , 'Holdout' , p ) cvpartition: C = cvpartition ( n , 'Holdout' , p , 'GroupingVariables' , grpvars ) cvpartition: C = cvpartition ( n , 'Leaveout' ) cvpartition: C = cvpartition ( n , 'Leaveout' , 'GroupingVariables' , grpvars ) cvpartition: C = cvpartition ( n , 'Resubstitution' ) cvpartition: C = cvpartition ( X , 'KFold' ) cvpartition: C = cvpartition ( X , 'KFold' , k ) cvpartition: C = cvpartition ( X , 'KFold' , k , 'Stratify' , opt ) cvpartition: C = cvpartition ( X , 'Holdout' ) cvpartition: C = cvpartition ( X , 'Holdout' , p ) cvpartition: C = cvpartition ( X , 'Holdout' , p , 'Stratify' , opt ) cvpartition: C = cvpartition ( 'CustomPartition' , testSets ) Repartition data for cross-validation. C = cvpartition ( n , 'KFold' ) creates a cvpartition object C , which defines a random nonstratified partition for k-fold cross-validation on n observations with each fold (subsample) having approximately the same number of observations. The default number of folds is 10 for n >= 10 or equal to n otherwise. C = cvpartition ( n , 'KFold' , k ) also creates a nonstratified random partition for k-fold cross-validation with the number of folds defined by k , which must be a positive integer scalar smaller than the number of observations n . C = cvpartition ( n , 'KFold' , k , 'GroupingVariables' , grpvars ) creates a cvpartition object C that defines a random partition for k-fold cross-validation in which every observation sharing a group label, as defined by grpvars , is assigned to the same fold. No group is split between the training and test sets, so a fold holds out whole groups; this is the partition scikit-learn calls GroupKFold , and it is what you want when observations within a group are not independent, such as repeated measurements of one subject. It is not stratification: a fold does not contain a proportional mix of the group labels, and 'Stratify' is the option for that. The grouping variables specified in grpvars can be one of the following: A numeric vector, logical vector, categorical vector, character array, string array, or cell array of character vectors containing one grouping variable. A numeric matrix or cell array containing two or more grouping variables. Each column in the matrix or array must correspond to one grouping variable. 'GroupingVariables' is an Octave extension: MATLAB’s cvpartition has no such option. It follows the group-aware splitters of scikit-learn, and each partition type takes the analogue of its ungrouped self: Partition type scikit-learn analogue Effect 'KFold' GroupKFold whole groups fill each fold 'Holdout' GroupShuffleSplit whole groups are held out 'Leaveout' LeaveOneGroupOut one whole group is held out at a time 'Resubstitution' none an error: nothing can be held out of a partition that holds everything Given a stratification variable, 'GroupingVariables' may follow 'Stratify' , as in cvpartition ( y , 'KFold' , k , 'Stratify' , true, 'GroupingVariables' , grpvars ) . Each group is then kept whole while the classes of y are spread as evenly over the folds as the groups allow, which is scikit-learn’s StratifiedGroupKFold . The two demands conflict, since a group carries whatever class mix it has, so the balance is approximate: groups are placed largest first, each into the fold whose class counts it disturbs least. In every case an observation never appears in both the training and the test set as one of its group fellows, which is what makes grouping worth asking for when observations within a group are not independent. C = cvpartition ( n , 'Holdout' ) creates a cvpartition object C , which defines a random nonstratified partition for holdout validation on n observations. 90% of the observations are assigned to the training set and the remaining 10% to the test set. C = cvpartition ( n , 'Holdout' , p ) also creates a nonstratified random partition for holdout validation with the percentage of training and test sets defined by p , which can be a scalar value in the range (0,1) or a positive integer scalar in the range [1, n ) . C = cvpartition ( n , 'Leaveout' ) creates a cvpartition object C , which defines a random partition for leave-one-out cross-validation on n observations. This is a special case of k-fold cross-validation with the number of folds equal to the number of observations. C = cvpartition ( n , 'Resubstitution' ) creates a cvpartition object C without partitioning the data and both training and test sets containing all observations n . C = cvpartition ( X , 'KFold' ) creates a cvpartition object C , which defines a stratified random partition for k-fold cross-validation according to the class proportions in Χ . X can be a numeric, logical, categorical, or string vector, or a character array or a cell array of character vectors. Missing values in X are discarded. The default number of folds is 10 for numel ( X ) >= 10 or equal to numel ( X ) otherwise. C = cvpartition ( X , 'KFold' , k ) also creates a stratified random partition for k-fold cross-validation with the number of folds defined by k , which must be a positive integer scalar smaller than the number of observations in X . C = cvpartition ( X , 'KFold' , k , 'Stratify' , opt ) creates a random partition for k-fold cross-validation, which is stratified if opt is true , or nonstratified if opt is false . C = cvpartition ( X , 'Holdout' ) creates a cvpartition object C , which defines a stratified random partition for holdout validation while maintaining the class proportions in Χ . 90% of the observations are assigned to the training set and the remaining 10% to the test set. C = cvpartition ( X , 'Holdout' , p ) also creates a stratified random partition for holdout validation with the percentage of training and test sets defined by p , which can be a scalar value in the range (0,1) or a positive integer scalar in the range [1, n ) . C = cvpartition ( X , 'Holdout' , p , 'Stratify' , opt ) creates a random partition for holdout validation, which is stratified if opt is true , or nonstratified if opt is false . C = cvpartition ( 'CustomPartition' , testSets ) creates a custom partition according to testSets , which can be a positive integer vector, a logical vector, or a logical matrix according to the following options: A positive integer vector of length N with values in the range [1,K] , where K < N , will specify a K-fold cross-validation partition, in which each value indicates the test set of each observation. Alternatively, the same vector with values in the range [1,N] will specify a leave-one-out cross-validation. A logical vector will specify a holdout validation, in which the true elements correspond to the test set and the false elements correspond to the training set. A logical matrix with K columns will specify a K-fold cross-validation partition, in which each column corresponds to a fold and each row to an observation. Alternatively, an N×N logical matrix will specify a leave-one-out cross-validation, where N is the number of observations. true elements correspond to the test set and the false elements correspond to the training set. See also: cvpartition, summary, test, training # name: # type: sq_string # elements: 1 # length: 38 Repartition data for cross-validation. # name: # type: sq_string # elements: 1 # length: 23 cvpartition.repartition # name: # type: sq_string # elements: 1 # length: 1361 cvpartition: Cnew = repartition ( C ) cvpartition: Cnew = repartition ( C , sval ) cvpartition: Cnew = repartition ( C , 'legacy' ) Repartition data for cross-validation. Cnew = repartition ( C ) creates a cvpartition object Cnew that defines a new random partition of the same type as the cvpartition C . Cnew = repartition ( C , sval ) also uses the value of sval to set the state of the random generator used in repartitioning C . If sval is a vector, then the random generator is set using the 'state' keyword as in rand ("state", sval ) . If sval is a scalar, then the 'seed' keyword is used as in rand ("seed", sval ) to specify that old generators should be used. Seeding is confined to this call: the state of the random generator is saved beforehand and restored before repartition returns, so sval does not carry over into the random numbers the caller draws afterwards. sval is an Octave extension; MATLAB expects a RandStream object in this position, which Octave does not have. Cnew = repartition ( C , 'legacy' ) only applies to cvpartition objects C that use k-fold partitioning and it will repartition C in the same non-random manner that was previously used by the old-style cvpartition class of the statistics package. The 'legacy' option does not apply to stratified or grouped partitions. See also: cvpartition, summary, test, training # name: # type: sq_string # elements: 1 # length: 38 Repartition data for cross-validation. # name: # type: sq_string # elements: 1 # length: 19 cvpartition.summary # name: # type: sq_string # elements: 1 # length: 1431 cvpartition: tbl = summary ( c ) Summarize stratified or grouped cross-validation partitions. tbl = summary ( c ) returns a summary table tbl of the validation partition contained in the cvpartition object c . This method calculates the distribution of classes (if stratified) or groups (if grouped) across the entire dataset, as well as within every training and test set generated by the partition. Inputs c A cvpartition object. The object must satisfy two conditions: The partition type ( c.Type ) must be 'kfold' or 'holdout' . The partition must be created with a stratification or grouping variable (i.e., c.IsStratified or c.IsGrouped must be true ). Outputs tbl A table object containing the summary statistics. The table contains one row for every unique label/group in every set (all, train, test). The columns are: Set The specific subset being described. Values include 'all' (the full dataset), 'train1' , 'test1' , etc. SetSize The total number of observations in that specific set. Label The class or group identifier. If c.IsStratified is true, this column is named StratificationLabel . If c.IsGrouped is true, it is named GroupLabel . Count The number of observations of that label within the set. If stratified, this column is named StratificationCount ; otherwise, GroupCount . PercentInSet The percentage of the set composed of that specific label. See also: cvpartition, repartition, test, training # name: # type: sq_string # elements: 1 # length: 60 Summarize stratified or grouped cross-validation partitions. # name: # type: sq_string # elements: 1 # length: 16 cvpartition.test # name: # type: sq_string # elements: 1 # length: 1079 cvpartition: idx = test ( C ) cvpartition: idx = test ( C , i ) cvpartition: idx = test ( C , 'all' ) Test indices for cross-validation. idx = test ( C ) returns a logical vector idx with true values indicating the elements corresponding to the test set defined in the cvpartition object C . For K-fold and leave-one-out partitions, the indices corresponding to the first test set are returned. idx = test ( C , i ) returns a logical vector or matrix with the indices of the test set indicated by i . If i is a scalar, then idx is a logical vector with the indices of the i-th set. If i is a vector, then idx is a logical matrix in which idx (:,j) specified the observations in the test set i (j) . The value(s) in i must not exceed the number of tests in the cvpartition object C . idx = test ( C , 'all' ) returns a logical vector or matrix for all test sets defined in the cvpartition object C . For holdout and resubstitution partition types, a vector is returned. For K-fold and leave-one-out, a matrix is returned. See also: cvpartition, repartition, summary, training # name: # type: sq_string # elements: 1 # length: 34 Test indices for cross-validation. # name: # type: sq_string # elements: 1 # length: 20 cvpartition.training # name: # type: sq_string # elements: 1 # length: 1123 cvpartition: idx = training ( C ) cvpartition: idx = training ( C , i ) cvpartition: idx = training ( C , 'all' ) Training indices for cross-validation. idx = training ( C ) returns a logical vector idx with true values indicating the elements corresponding to the training set defined in the cvpartition object C . For K-fold and leave-one-out partitions, the indices corresponding to the first training set are returned. idx = training ( C , i ) returns a logical vector or matrix with the indices of the training set indicated by i . If i is a scalar, then idx is a logical vector with the indices of the i-th set. If i is a vector, then idx is a logical matrix in which idx (:,j) specified the observations in the training set i (j) . The value(s) in i must not exceed the number of tests in the cvpartition object C . idx = training ( C , 'all' ) returns a logical vector or matrix for all training sets defined in the cvpartition object C . For holdout and resubstitution partition types, a vector is returned. For K-fold and leave-one-out, a matrix is returned. See also: cvpartition, repartition, summary, test # name: # type: sq_string # elements: 1 # length: 38 Training indices for cross-validation. # name: # type: sq_string # elements: 1 # length: 9 perfcurve # name: # type: sq_string # elements: 1 # length: 2519 statistics: [ X , Y ] = perfcurve ( labels , scores , posclass ) statistics: [ X , Y , T , AUC , OPTROCPT ] = perfcurve (…) statistics: […] = perfcurve (…, Name , Value ) Receiver operating characteristic (ROC) and other classifier performance curves. [ X , Y ] = perfcurve ( labels , scores , posclass ) returns the ROC curve for the classifier scores in scores given the true class labels and the positive class posclass . labels is a numeric vector or a cell array of character vectors; scores is a numeric vector of the same length, where larger values indicate stronger evidence for the positive class. By default X is the false positive rate and Y the true positive rate. [ X , Y , T , AUC , OPTROCPT ] = perfcurve (…) also returns the thresholds T on the scores, the area AUC under the ( X , Y ) curve, and the optimal operating point OPTROCPT = [FPR, TPR] of the ROC curve. The following Name-Value pairs are supported: Name Value 'XCrit' The criterion for X . The default is 'FPR' . 'YCrit' The criterion for Y . The default is 'TPR' . Supported criteria are 'TPR' ( 'sens' , 'reca' ), 'FNR' , 'FPR' ( 'fall' ), 'TNR' ( 'spec' ), 'PPV' ( 'prec' ), 'NPV' , 'accu' , the counts 'TP' , 'FN' , 'FP' , 'TN' , and the rates 'RPP' , 'RNP' . 'NegClass' The negative class(es). The default, 'all' , treats every label other than posclass as negative. 'Weights' A vector of non-negative observation weights. 'Cost' A 2×2 misclassification-cost matrix [C(P|P) C(N|P); C(P|N) C(N|N)] used for OPTROCPT . The default is [0 1; 1 0] . 'XVals' Values of the X criterion at which to return the curve. 'TVals' does the same for the thresholds. 'ProcessNaN' How to treat NaN scores: 'ignore' (default) or 'addtofalse' . 'NBoot' Number of bootstrap replicates for confidence bounds on Y and AUC . The default 0 computes no bounds. 'BootType' The bootstrap interval: 'bca' (default, bias-corrected and accelerated), 'percentile' , or 'normal' . 'Alpha' The significance level for the bounds, so the confidence level is 1 - Alpha . The default is 0.05 . With 'NBoot' greater than zero, Y is returned as an m×3 array [ Y , Ylow , Yhigh ] and AUC as [ AUC , AUClow , AUChigh ] . The bootstrap uses an independent random stream, so the bounds do not match MATLAB numerically. […, SUBY , SUBYNAMES ] = perfcurve (…) returns the Y values for each negative subclass and their names. When called with no output arguments the curve is plotted. See also: fitcsvm, fitcknn, glmfit # name: # type: sq_string # elements: 1 # length: 80 Receiver operating characteristic (ROC) and other classifier performance curves. # name: # type: sq_string # elements: 1 # length: 10 rocmetrics # name: # type: sq_string # elements: 1 # length: 793 statistics: rocmetrics Receiver operating characteristic (ROC) metrics for classifier output. The rocmetrics class evaluates a classifier’s performance by computing, for each class, a one-versus-all ROC curve together with a set of threshold-dependent performance metrics. It stores the results in the Metrics table and the per-class area under the curve in AUC , and provides the addMetrics , average , and plot methods for follow-up analysis. For a problem with K classes and scores supplied as an N -by- K matrix, the discriminant score used for class k is the one-versus-all margin Scores(:,k) - max (Scores(:,j)) over j != k , matching MATLAB’s rocmetrics . Every metric is evaluated at each distinct value of that margin. See also: perfcurve, confusionmat, confusionchart # name: # type: sq_string # elements: 1 # length: 70 Receiver operating characteristic (ROC) metrics for classifier output. # name: # type: sq_string # elements: 1 # length: 14 rocmetrics.AUC # name: # type: sq_string # elements: 1 # length: 179 rocmetrics: property AUC Area under the ROC curve Row vector holding the area under the one-versus-all ROC curve for each class, in ClassNames order. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Area under the ROC curve # name: # type: sq_string # elements: 1 # length: 21 rocmetrics.ClassNames # name: # type: sq_string # elements: 1 # length: 157 rocmetrics: property ClassNames Class names Class names for which the ROC metrics are computed, in the column order of Scores . This property is read-only. # name: # type: sq_string # elements: 1 # length: 11 Class names # name: # type: sq_string # elements: 1 # length: 15 rocmetrics.Cost # name: # type: sq_string # elements: 1 # length: 223 rocmetrics: property Cost Misclassification cost matrix Square misclassification-cost matrix, with zero diagonal and unit off-diagonal entries by default. It is used by the ExpectedCost metric. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Misclassification cost matrix # name: # type: sq_string # elements: 1 # length: 17 rocmetrics.Labels # name: # type: sq_string # elements: 1 # length: 141 rocmetrics: property Labels Observation labels True class labels supplied at construction, one per observation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 18 Observation labels # name: # type: sq_string # elements: 1 # length: 18 rocmetrics.Metrics # name: # type: sq_string # elements: 1 # length: 389 rocmetrics: property Metrics Performance metrics table Table of performance metrics, vertically concatenated across the classes in ClassNames order with one row per distinct threshold. The standard variables are ClassName , Threshold , FalsePositiveRate , and TruePositiveRate , followed by one variable for each metric requested through 'AdditionalMetrics' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Performance metrics table # name: # type: sq_string # elements: 1 # length: 16 rocmetrics.Prior # name: # type: sq_string # elements: 1 # length: 160 rocmetrics: property Prior Prior class probabilities Row vector of prior class probabilities, in ClassNames order, summing to one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Prior class probabilities # name: # type: sq_string # elements: 1 # length: 17 rocmetrics.Scores # name: # type: sq_string # elements: 1 # length: 150 rocmetrics: property Scores Classification scores Classification scores supplied at construction, as an N -by- K matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Classification scores # name: # type: sq_string # elements: 1 # length: 18 rocmetrics.Weights # name: # type: sq_string # elements: 1 # length: 163 rocmetrics: property Weights Observation weights Non-negative observation weights, one per observation. Defaults to a vector of ones. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 21 rocmetrics.addMetrics # name: # type: sq_string # elements: 1 # length: 372 rocmetrics: obj = addMetrics ( obj , metrics ) Append additional performance metrics to an existing rocmetrics object. metrics is a metric name or a cell array of metric names, chosen from the list supported by the 'AdditionalMetrics' constructor argument. The named metrics are appended as new variables of the Metrics table; metrics already present are left unchanged. # name: # type: sq_string # elements: 1 # length: 71 Append additional performance metrics to an existing rocmetrics object. # name: # type: sq_string # elements: 1 # length: 18 rocmetrics.average # name: # type: sq_string # elements: 1 # length: 735 rocmetrics: [ FPR , TPR , Thresholds , AUC ] = average ( obj , type ) Compute an averaged ROC curve across the classes of a rocmetrics object. type selects the averaging method: 'macro' (unweighted mean of the per-class curves), 'micro' (a single curve pooling every one-versus-all instance), or 'weighted' (mean of the per-class curves weighted by Prior ). The function returns the averaged false and true positive rates FPR and TPR , the corresponding Thresholds , and the area AUC under the averaged curve. The averaged curve is evaluated on the union of the per-class thresholds. MATLAB inserts additional staircase points when building the averaged curve, so the exact rows and the averaged AUC may differ slightly from MATLAB. # name: # type: sq_string # elements: 1 # length: 72 Compute an averaged ROC curve across the classes of a rocmetrics object. # name: # type: sq_string # elements: 1 # length: 15 rocmetrics.plot # name: # type: sq_string # elements: 1 # length: 265 rocmetrics: plot ( obj ) rocmetrics: h = plot ( obj ) Plot the per-class ROC curves of a rocmetrics object. Each class in ClassNames contributes one true-positive-rate versus false-positive-rate curve. A handle to the line objects is returned in h when requested. # name: # type: sq_string # elements: 1 # length: 53 Plot the per-class ROC curves of a rocmetrics object. # name: # type: sq_string # elements: 1 # length: 21 rocmetrics.rocmetrics # name: # type: sq_string # elements: 1 # length: 1861 rocmetrics: obj = rocmetrics ( labels , scores , classnames ) rocmetrics: obj = rocmetrics (…, Name , Value ) Create a rocmetrics object from labels and classification scores. labels is a vector of true class labels with one element per observation; it may be numeric, logical, a character matrix, or a cell array of character vectors. scores is an N -by- K numeric matrix of classification scores, where scores(i,k) is the score of observation i for the class classnames(k) . classnames lists the K classes in the column order of scores . The following Name-Value pairs are supported: Name Value 'AdditionalMetrics' A character vector or cell array of metric names to append to Metrics . Supported names are 'TruePositives' , 'FalseNegatives' , 'FalsePositives' , 'TrueNegatives' , 'SumOfTrueAndFalsePositives' , 'RateOfPositivePredictions' , 'RateOfNegativePredictions' , 'Accuracy' , 'FalseNegativeRate' , 'TrueNegativeRate' , 'PositivePredictiveValue' , 'NegativePredictiveValue' , 'ExpectedCost' , and 'f1score' . 'Prior' Prior class probabilities, given as 'empirical' (default), 'uniform' , or a numeric vector with one value per class. 'Cost' A K -by- K misclassification-cost matrix used by the ExpectedCost metric. The default has zero diagonal and unit off-diagonal entries. 'Weights' A vector of non-negative observation weights. The default is a vector of ones. 'NaNFlag' How to treat NaN scores: 'omitnan' (default) drops the affected observations, while 'includenan' treats them as always classified negative. 'FixedMetricValues' 'all' (default) to use every distinct threshold, or a numeric vector of threshold values at which to report the curve (nearest actual thresholds are returned). Construction from a trained model object, the 'FixedMetric' grids other than thresholds, and bootstrap confidence intervals are not implemented. # name: # type: sq_string # elements: 1 # length: 65 Create a rocmetrics object from labels and classification scores. statistics-release-1.9.2/inst/Model_Evaluation/perfcurve.m000066400000000000000000000575751524624707500237550ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{X}, @var{Y}] =} perfcurve (@var{labels}, @var{scores}, @var{posclass}) ## @deftypefnx {statistics} {[@var{X}, @var{Y}, @var{T}, @var{AUC}, @var{OPTROCPT}] =} perfcurve (@dots{}) ## @deftypefnx {statistics} {[@dots{}] =} perfcurve (@dots{}, @var{Name}, @var{Value}) ## ## Receiver operating characteristic (ROC) and other classifier performance ## curves. ## ## @code{[@var{X}, @var{Y}] = perfcurve (@var{labels}, @var{scores}, ## @var{posclass})} returns the ROC curve for the classifier scores in ## @var{scores} given the true class @var{labels} and the positive class ## @var{posclass}. @var{labels} is a numeric vector or a cell array of ## character vectors; @var{scores} is a numeric vector of the same length, where ## larger values indicate stronger evidence for the positive class. By default ## @var{X} is the false positive rate and @var{Y} the true positive rate. ## ## @code{[@var{X}, @var{Y}, @var{T}, @var{AUC}, @var{OPTROCPT}] = perfcurve ## (@dots{})} also returns the thresholds @var{T} on the scores, the area ## @var{AUC} under the (@var{X}, @var{Y}) curve, and the optimal operating point ## @var{OPTROCPT} @code{= [FPR, TPR]} of the ROC curve. ## ## The following @qcode{Name-Value} pairs are supported: ## ## @multitable @columnfractions 0.18 0.82 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'XCrit'} @tab The criterion for @var{X}. The default is ## @qcode{'FPR'}. ## ## @item @qcode{'YCrit'} @tab The criterion for @var{Y}. The default is ## @qcode{'TPR'}. Supported criteria are @qcode{'TPR'} (@qcode{'sens'}, ## @qcode{'reca'}), @qcode{'FNR'}, @qcode{'FPR'} (@qcode{'fall'}), @qcode{'TNR'} ## (@qcode{'spec'}), @qcode{'PPV'} (@qcode{'prec'}), @qcode{'NPV'}, ## @qcode{'accu'}, the counts @qcode{'TP'}, @qcode{'FN'}, @qcode{'FP'}, ## @qcode{'TN'}, and the rates @qcode{'RPP'}, @qcode{'RNP'}. ## ## @item @qcode{'NegClass'} @tab The negative class(es). The default, ## @qcode{'all'}, treats every label other than @var{posclass} as negative. ## ## @item @qcode{'Weights'} @tab A vector of non-negative observation weights. ## ## @item @qcode{'Cost'} @tab A @math{2*2} misclassification-cost matrix ## @code{[C(P|P) C(N|P); C(P|N) C(N|N)]} used for @var{OPTROCPT}. The default ## is @code{[0 1; 1 0]}. ## ## @item @qcode{'XVals'} @tab Values of the @var{X} criterion at which to return ## the curve. @qcode{'TVals'} does the same for the thresholds. ## ## @item @qcode{'ProcessNaN'} @tab How to treat @code{NaN} scores: ## @qcode{'ignore'} (default) or @qcode{'addtofalse'}. ## ## @item @qcode{'NBoot'} @tab Number of bootstrap replicates for confidence ## bounds on @var{Y} and @var{AUC}. The default @math{0} computes no bounds. ## ## @item @qcode{'BootType'} @tab The bootstrap interval: @qcode{'bca'} (default, ## bias-corrected and accelerated), @qcode{'percentile'}, or @qcode{'normal'}. ## ## @item @qcode{'Alpha'} @tab The significance level for the bounds, so the ## confidence level is @math{1 - @var{Alpha}}. The default is @math{0.05}. ## @end multitable ## ## With @qcode{'NBoot'} greater than zero, @var{Y} is returned as an ## @math{m*3} array @code{[@var{Y}, @var{Ylow}, @var{Yhigh}]} and @var{AUC} as ## @code{[@var{AUC}, @var{AUClow}, @var{AUChigh}]}. The bootstrap uses an ## independent random stream, so the bounds do not match MATLAB numerically. ## @code{[@dots{}, @var{SUBY}, @var{SUBYNAMES}] = perfcurve (@dots{})} returns ## the @var{Y} values for each negative subclass and their names. ## ## When called with no output arguments the curve is plotted. ## ## @seealso{fitcsvm, fitcknn, glmfit} ## @end deftypefn function [X, Y, T, AUC, OPTROCPT, SUBY, SUBYNAMES] = perfcurve (labels, ... scores, posclass, varargin) if (nargin < 3) print_usage (); endif if (! (isnumeric (scores) && isreal (scores) && isvector (scores))) error ("perfcurve: SCORES must be a vector of real values."); endif scores = scores(:); n = numel (scores); ## Check the class and the length separately, so that a type failure is not ## reported as a length failure if (! (isnumeric (labels) || islogical (labels) || iscellstr (labels) || ischar (labels))) error (strcat ("perfcurve: LABELS must be numeric, logical, a character", ... " matrix, or a cell array of character vectors.")); endif if (numel_labels (labels) != n) error ("perfcurve: LABELS must have one element per score."); endif ## Defaults and Name-Value parsing. xcrit = "FPR"; ycrit = "TPR"; negclass = "all"; weights = []; cost = [0 1; 1 0]; xvals = []; tvals = []; usenearest = true; prior = "empirical"; processnan = "ignore"; nboot = 0; boottype = "bca"; alpha = 0.05; if (mod (numel (varargin), 2) != 0) error ("perfcurve: optional arguments must be Name-Value pairs."); endif for k = 1:2:numel (varargin) if (! ischar (varargin{k})) error ("perfcurve: parameter names must be character vectors."); endif switch (lower (varargin{k})) case "xcrit" xcrit = varargin{k+1}; case "ycrit" ycrit = varargin{k+1}; case "negclass" negclass = varargin{k+1}; case "weights" weights = varargin{k+1}; case "cost" cost = varargin{k+1}; case "xvals" xvals = varargin{k+1}; case "tvals" tvals = varargin{k+1}; case "usenearest" uv = varargin{k+1}; usenearest = strcmpi (uv, "on") || isequal (uv, true); case "prior" prior = varargin{k+1}; case "processnan" processnan = lower (varargin{k+1}); case "nboot" nboot = varargin{k+1}; case "boottype" boottype = lower (varargin{k+1}); case "alpha" alpha = varargin{k+1}; otherwise error ("perfcurve: unknown parameter name '%s'.", varargin{k}); endswitch endfor if (! isempty (xvals) && ! isempty (tvals)) error ("perfcurve: 'XVals' and 'TVals' cannot be used together."); endif if (! (isnumeric (cost) && isequal (size (cost), [2, 2]))) error ("perfcurve: 'Cost' must be a 2-by-2 matrix."); endif if (! (isnumeric (nboot) && isscalar (nboot) && nboot >= 0 && nboot == fix (nboot))) error ("perfcurve: 'NBoot' must be a non-negative integer."); endif if (! (isnumeric (alpha) && isscalar (alpha) && alpha > 0 && alpha < 1)) error ("perfcurve: 'Alpha' must be a scalar in the range (0,1)."); endif ## Positive / negative membership (one-vs-all by default). ispos = label_match (labels, posclass); if (ischar (negclass) && strcmpi (negclass, "all")) isneg = ! ispos; else isneg = label_match (labels, negclass); endif SUBYNAMES = negclass_names (labels, isneg, negclass); ## Weights. if (isempty (weights)) w = ones (n, 1); else if (! (isnumeric (weights) && isreal (weights) && isvector (weights) && numel (weights) == n && all (weights >= 0))) error ("perfcurve: 'Weights' must be a non-negative vector with one element per score."); endif w = weights(:); endif ## NaN score handling. isnanscore = isnan (scores); if (any (isnanscore)) switch (processnan) case "ignore" keep = ! isnanscore; scores = scores(keep); ispos = ispos(keep); isneg = isneg(keep); w = w(keep); case "addtofalse" scores(isnanscore) = -Inf; ## always classified negative otherwise error ("perfcurve: unrecognised 'ProcessNaN' value."); endswitch endif ## Restrict to labelled (positive or negative) observations. use = ispos | isneg; scores = scores(use); ispos = ispos(use); isneg = isneg(use); w = w(use); if (! any (ispos) || ! any (isneg)) error ("perfcurve: LABELS must contain both positive and negative classes."); endif ## Optionally reweight the observations to a requested prior. [~, ~, w] = apply_prior (prior, sum (w(ispos)), sum (w(isneg)), w, ... ispos, isneg); ## Full-sample curve for the requested criteria plus the underlying ROC. [X, Y, T, tpr, fpr, P, N] = build_curve (scores, ispos, isneg, w, ... xcrit, ycrit); ## Area under the (X, Y) curve and the cost-optimal ROC operating point. AUC = trapz (X, Y); ecost = P * (1 - tpr) * cost(1,2) + N * fpr * cost(2,1); best = find (abs (ecost - min (ecost)) < 1e-12); [~, bk] = min (fpr(best)); OPTROCPT = [fpr(best(bk)), tpr(best(bk))]; ## Restrict the returned curve to requested X or threshold values. if (! isempty (xvals)) idx = select_xvals (X, Y, xvals(:), usenearest); elseif (! isempty (tvals)) idx = select_tvals (T, tvals(:)); else idx = (1:numel (X))'; endif X = X(idx); Y = Y(idx); T = T(idx); SUBY = Y; ## Bootstrap confidence bounds on Y and AUC (self-contained resampler). if (nboot > 0) [Ylo, Yhi, alo, ahi] = perfcurve_boot_ (scores, ispos, isneg, w, xcrit, ... ycrit, X, Y, AUC, nboot, boottype, alpha); Y = [Y, Ylo, Yhi]; SUBY = Y; AUC = [AUC, alo, ahi]; endif ## Plot when no output is requested. if (nargout == 0) plot (X, Y(:,1)); xlabel (xcrit); ylabel (ycrit); clear X Y T AUC OPTROCPT SUBY SUBYNAMES endif endfunction ## Build the full performance curve for a data subset: returns the X and Y ## criteria, thresholds T, the underlying ROC rates, and the class totals. function [X, Y, T, tpr, fpr, P, N] = build_curve (scores, ispos, isneg, w, ... xcrit, ycrit) P = sum (w(ispos)); N = sum (w(isneg)); [ss, ord] = sort (scores, "descend"); pw = cumsum (w(ord) .* ispos(ord)); nw = cumsum (w(ord) .* isneg(ord)); chg = find (diff (ss) != 0); last = [chg; numel(ss)]; ## last index of each score group tp = [0; pw(last)]; fp = [0; nw(last)]; T = [ss(1); ss(last)]; X = criterion (xcrit, tp, fp, P, N); Y = criterion (ycrit, tp, fp, P, N); tpr = tp / P; fpr = fp / N; endfunction ## Number of labels in a numeric vector or cell array of character vectors. function m = numel_labels (labels) if (ischar (labels)) m = rows (labels); else m = numel (labels); endif endfunction ## Logical membership of LABELS in CLS (numeric equality or string match). function tf = label_match (labels, cls) if (isnumeric (labels)) tf = ismember (labels(:), cls(:)); else if (ischar (labels)) labels = cellstr (labels); endif if (ischar (cls)) cls = {cls}; endif tf = ismember (labels(:), cls(:)); endif endfunction ## Rescale weights so the class totals follow a requested prior. function [P, N, w] = apply_prior (prior, P, N, w, ispos, isneg) if (ischar (prior)) switch (lower (prior)) case "empirical" return; case "uniform" pn = [0.5, 0.5]; otherwise error ("perfcurve: unrecognised 'Prior' value."); endswitch elseif (isnumeric (prior) && numel (prior) == 2 && all (prior > 0)) pn = prior(:)' / sum (prior); else error ("perfcurve: 'Prior' must be 'empirical', 'uniform', or [P N]."); endif w(ispos) = w(ispos) * (pn(1) / P); w(isneg) = w(isneg) * (pn(2) / N); P = pn(1); N = pn(2); endfunction ## Evaluate a named performance criterion from the count arrays. function v = criterion (name, tp, fp, P, N) tn = N - fp; fn = P - tp; total = P + N; pp = tp + fp; switch (lower (name)) case {"tpr", "sens", "reca", "recall"} v = tp / P; case {"fnr", "miss"} v = fn / P; case {"fpr", "fall"} v = fp / N; case {"tnr", "spec"} v = tn / N; case {"ppv", "prec", "precision"} v = tp ./ pp; case "npv" v = tn ./ (tn + fn); case {"accu", "acc"} v = (tp + tn) / total; case "tp" v = tp; case "fn" v = fn; case "fp" v = fp; case "tn" v = tn; case "rpp" v = pp / total; case "rnp" v = (tn + fn) / total; otherwise error ("perfcurve: unsupported criterion '%s'.", name); endswitch endfunction ## Indices of the curve returned for requested X values: the (0,0) start plus, ## for each value, the upper-envelope point at the largest actual X not above. function idx = select_xvals (X, Y, xvals, usenearest) idx = 1; for v = xvals' if (usenearest) cand = find (X <= v + 1e-12); if (isempty (cand)) cand = 1; endif xstar = max (X(cand)); here = find (abs (X - xstar) < 1e-12); idx(end+1) = here(end); ## upper envelope (largest Y at this X) else idx(end+1) = interp_index (X, v); endif endfor idx = idx(:); endfunction ## Indices for requested threshold values: nearest actual threshold (Inf -> the ## first, "reject all" point). function idx = select_tvals (T, tvals) idx = zeros (numel (tvals), 1); for k = 1:numel (tvals) if (tvals(k) >= T(1)) idx(k) = 1; else [~, idx(k)] = min (abs (T - tvals(k))); endif endfor endfunction ## Nearest index (used for UseNearest 'off' fallback). function i = interp_index (X, v) [~, i] = min (abs (X - v)); endfunction ## Names of the negative class(es), for SUBYNAMES. function names = negclass_names (labels, isneg, negclass) if (ischar (negclass) && strcmpi (negclass, "all")) if (isnumeric (labels)) names = arrayfun (@(v) num2str (v), unique (labels(isneg)), ... "UniformOutput", false); else if (ischar (labels)) labels = cellstr (labels); endif names = unique (labels(isneg)); endif elseif (isnumeric (negclass)) names = arrayfun (@(v) num2str (v), negclass(:), "UniformOutput", false); elseif (ischar (negclass)) names = {negclass}; else names = negclass(:); endif endfunction ## Nonparametric bootstrap confidence bounds on the Y criterion (at the returned ## reference X values REFX, point estimates REFY) and on the AUC. Observations ## are resampled with replacement; percentile or bias-corrected-and-accelerated ## (BCa) bounds are formed. Bounds do not match MATLAB numerically because the ## bootstrap uses an independent random stream. function [Ylo, Yhi, alo, ahi] = perfcurve_boot_ (scores, ispos, isneg, w, ... xcrit, ycrit, refX, refY, aucHat, ... nboot, boottype, alpha) n = numel (scores); m = numel (refX); Yb = zeros (nboot, m); aucB = zeros (nboot, 1); for b = 1:nboot ii = resample_both (n, ispos, isneg); [Xr, Yr] = build_curve (scores(ii), ispos(ii), isneg(ii), w(ii), ... xcrit, ycrit); Yb(b,:) = interp_curve (Xr, Yr, refX); aucB(b) = trapz (Xr, Yr); endfor switch (boottype) case {"percentile", "per"} [Ylo, Yhi] = ci_percentile (Yb, alpha); [alo, ahi] = ci_percentile (aucB, alpha); case "bca" Yj = zeros (n, m); aucJ = zeros (n, 1); valid = true (n, 1); for i = 1:n keep = true (n, 1); keep(i) = false; if (! any (ispos(keep)) || ! any (isneg(keep))) valid(i) = false; continue; endif [Xj, Yjc] = build_curve (scores(keep), ispos(keep), isneg(keep), ... w(keep), xcrit, ycrit); Yj(i,:) = interp_curve (Xj, Yjc, refX); aucJ(i) = trapz (Xj, Yjc); endfor [Ylo, Yhi] = ci_bca (Yb, Yj(valid,:), refY(:)', alpha); [alo, ahi] = ci_bca (aucB, aucJ(valid), aucHat, alpha); case {"normal", "norm"} z = norminv (1 - alpha / 2); Ylo = (mean (Yb) - z * std (Yb))'; Yhi = (mean (Yb) + z * std (Yb))'; alo = mean (aucB) - z * std (aucB); ahi = mean (aucB) + z * std (aucB); otherwise error ("perfcurve: unrecognised 'BootType' value."); endswitch Ylo = Ylo(:); Yhi = Yhi(:); endfunction ## Resample N observation indices with replacement, ensuring both classes are ## present in the draw. function ii = resample_both (n, ispos, isneg) ii = randi (n, n, 1); tries = 0; while ((! any (ispos(ii)) || ! any (isneg(ii))) && tries < 100) ii = randi (n, n, 1); tries++; endwhile endfunction ## Evaluate a curve (X, Y) at query abscissae XQ using its upper envelope (max Y ## at each distinct X) and linear interpolation, clamped to the endpoints. function yq = interp_curve (X, Y, xq) [xu, ~, ic] = unique (X(:)); yu = accumarray (ic, Y(:), [], @max); yq = interp1 (xu, yu, xq(:), "linear"); yq(xq(:) <= xu(1)) = yu(1); yq(xq(:) >= xu(end)) = yu(end); yq = yq'; endfunction ## Percentile confidence bounds (over rows) of the bootstrap replicates B. function [lo, hi] = ci_percentile (B, alpha) lo = quantile (B, alpha / 2, 1)'; hi = quantile (B, 1 - alpha / 2, 1)'; endfunction ## Bias-corrected and accelerated (BCa) confidence bounds. B is nboot-by-m, ## J the jackknife replicates (n-by-m), THETAHAT the point estimate (1-by-m). function [lo, hi] = ci_bca (B, J, thetahat, alpha) nb = rows (B); m = columns (B); frac = sum (B < thetahat, 1) / nb; frac = min (max (frac, 1 / (nb + 1)), nb / (nb + 1)); z0 = norminv (frac); d = mean (J, 1) - J; a = sum (d .^ 3, 1) ./ (6 * (sum (d .^ 2, 1)) .^ 1.5); a(! isfinite (a)) = 0; za = norminv (alpha / 2); zb = norminv (1 - alpha / 2); a1 = normcdf (z0 + (z0 + za) ./ (1 - a .* (z0 + za))); a2 = normcdf (z0 + (z0 + zb) ./ (1 - a .* (z0 + zb))); a1(! isfinite (a1)) = alpha / 2; a2(! isfinite (a2)) = 1 - alpha / 2; lo = zeros (m, 1); hi = zeros (m, 1); for j = 1:m lo(j) = quantile (B(:,j), a1(j)); hi(j) = quantile (B(:,j), a2(j)); endfor endfunction %!demo %! ## ROC curve for scores with a known positive class %! scores = [0.9 0.8 0.7 0.6 0.55 0.5 0.4 0.3 0.2 0.1]; %! labels = [1 1 0 1 0 1 0 0 1 0]; %! [X, Y, T, AUC] = perfcurve (labels, scores, 1); %! plot (X, Y, "b-o"); xlabel ("FPR"); ylabel ("TPR"); %! title (sprintf ("ROC curve (AUC = %.2f)", AUC)); %!shared labels, scores %! labels = [1 1 0 1 0 1 0 0 1 0]; %! scores = [0.95 0.9 0.85 0.7 0.7 0.6 0.55 0.4 0.35 0.2]; %!test # MATLAB parity: default ROC curve, thresholds, AUC and OPTROCPT %! [X, Y, T, AUC, OPT] = perfcurve (labels, scores, 1); %! assert_equal (X, [0 0 0 0.2 0.4 0.4 0.6 0.8 0.8 1]', 1e-12); %! assert_equal (Y, [0 0.2 0.4 0.4 0.6 0.8 0.8 0.8 1 1]', 1e-12); %! assert_equal (T, [0.95 0.95 0.9 0.85 0.7 0.6 0.55 0.4 0.35 0.2]', 1e-12); %! assert_equal (AUC, 0.7, 1e-12); %! assert_equal (OPT, [0 0.4], 1e-12); %!test # MATLAB parity: precision-recall and its NaN at the origin %! [Xr, Yr] = perfcurve (labels, scores, 1, "XCrit", "reca", "YCrit", "prec"); %! assert_equal (Xr, [0 0.2 0.4 0.4 0.6 0.8 0.8 0.8 1 1]', 1e-12); %! assert_equal (Yr(2:end), [1 1 2/3 0.6 2/3 4/7 0.5 5/9 0.5]', 1e-12); %! assert_equal (isnan (Yr(1)), true); %!test # MATLAB parity: accuracy and specificity/sensitivity criteria %! [~, Ya] = perfcurve (labels, scores, 1, "YCrit", "accu"); %! assert_equal (Ya, [0.5 0.6 0.7 0.6 0.6 0.7 0.6 0.5 0.6 0.5]', 1e-12); %! [Xs, Ys] = perfcurve (labels, scores, 1, "XCrit", "spec", "YCrit", "sens"); %! assert_equal (Xs, [1 1 1 0.8 0.6 0.6 0.4 0.2 0.2 0]', 1e-12); %! assert_equal (Ys, [0 0.2 0.4 0.4 0.6 0.8 0.8 0.8 1 1]', 1e-12); %!test # MATLAB parity: weighted curve, AUC and OPTROCPT %! w = [2 1 1 1 1 1 1 1 1 2]; %! [Xw, Yw, Tw, AUCw, OPTw] = perfcurve (labels, scores, 1, "Weights", w); %! assert_equal (Xw, [0 0 0 1 2 2 3 4 4 6]'/6, 1e-12); %! assert_equal (Yw, [0 2 3 3 4 5 5 5 6 6]'/6, 1e-12); %! assert_equal (AUCw, 0.791666666666667, 1e-12); %! assert_equal (OPTw, [0 0.5], 1e-12); %!test # MATLAB parity: OPTROCPT shifts with a non-default cost matrix %! [~, ~, ~, ~, OPTc] = perfcurve (labels, scores, 1, "Cost", [0 1; 2 0]); %! assert_equal (OPTc, [0 0.4], 1e-12); %!test # MATLAB parity: TVals selects nearest thresholds %! [Xt, Yt, Tt] = perfcurve (labels, scores, 1, "TVals", [Inf 0.8 0.6 0.4 0.2]); %! assert_equal (Xt, [0 0.2 0.4 0.8 1]', 1e-12); %! assert_equal (Yt, [0 0.4 0.8 0.8 1]', 1e-12); %! assert_equal (Tt, [0.95 0.85 0.6 0.4 0.2]', 1e-12); %!test # MATLAB parity: XVals selects floor points plus the origin %! [Xv, Yv, Tv] = perfcurve (labels, scores, 1, "XVals", [0 0.25 0.5 0.75 1]); %! assert_equal (Xv, [0 0 0.2 0.4 0.6 1]', 1e-12); %! assert_equal (Yv, [0 0.4 0.4 0.8 0.8 1]', 1e-12); %! assert_equal (Tv, [0.95 0.9 0.85 0.6 0.55 0.2]', 1e-12); %!test # MATLAB parity: cell-array string labels %! labels2 = {"g","g","b","g","b","g","b","b","g","b"}; %! [X, Y, T, AUC] = perfcurve (labels2, scores, "g"); %! assert_equal (AUC, 0.7, 1e-12); %! assert_equal (X, [0 0 0 0.2 0.4 0.4 0.6 0.8 0.8 1]', 1e-12); %!test # AUC of a perfectly separable problem is 1 %! sc = [0.9 0.8 0.7 0.6 0.3 0.2 0.1 0.05]; %! lb = [1 1 1 1 0 0 0 0]; %! [~, ~, ~, auc] = perfcurve (lb, sc, 1); %! assert_equal (auc, 1, 1e-12); %!test # bootstrap: shapes, point estimate preserved, bounds bracket it (BCa) %! rand ("seed", 42); randn ("seed", 42); %! [X, Y, T, AUC, OPT, SUBY, SUBYN] = ... %! perfcurve (labels, scores, 1, "NBoot", 200); %! assert_equal (columns (Y), 3); %! assert_equal (size (AUC), [1, 3]); %! assert_equal (Y(:,1), [0 0.2 0.4 0.4 0.6 0.8 0.8 0.8 1 1]', 1e-12); %! assert_equal (all (Y(:,2) <= Y(:,1) + 1e-9), true); # lower <= est %! assert_equal (all (Y(:,3) >= Y(:,1) - 1e-9), true); # upper >= est %! assert_equal (AUC(2) <= AUC(1) && AUC(1) <= AUC(3), true); %! assert_equal (isequal (SUBY, Y), true); %!test # bootstrap: percentile bounds also run and bracket the AUC estimate %! rand ("seed", 7); randn ("seed", 7); %! [~, Y, ~, AUC] = perfcurve (labels, scores, 1, "NBoot", 200, ... %! "BootType", "percentile"); %! assert_equal (columns (Y), 3); %! assert_equal (AUC(2) <= AUC(1) && AUC(1) <= AUC(3), true); ## Test input validation %!error perfcurve (1, 2) %!test %! ## Logical labels are accepted, as MATLAB does, and give the same curve as %! ## the equivalent numeric labels. %! s = [0.9; 0.8; 0.7; 0.6; 0.5; 0.4; 0.3; 0.2]; %! l = [1; 1; 1; 0; 1; 0; 0; 0]; %! [xn, yn, tn, an] = perfcurve (l, s, 1); %! [xg, yg, tg, ag] = perfcurve (logical (l), s, true); %! assert_equal (xg, xn); %! assert_equal (yg, yn); %! assert_equal (tg, tn); %! assert_equal (ag, an); %! ## and the curve is MATLAB's, not merely self-consistent %! assert_equal (xg(:)', [0, 0, 0, 0, 0.25, 0.25, 0.5, 0.75, 1], 1e-12); %!error ... %! perfcurve ([1 0], ones (2, 2), 1) %!error ... %! perfcurve ([1 0 1], [0.5 0.4], 1) %!error ... %! perfcurve (struct ('a', 1), [0.5 0.4], 1) %!error ... %! perfcurve ({1, 2}, [0.5 0.4], 1) %!error ... %! perfcurve ([1 1 1], [0.5 0.4 0.3], 1) %!error ... %! perfcurve ([1 0], [0.5 0.4], 1, "YCrit", "foo") %!error ... %! perfcurve ([1 0], [0.5 0.4], 1, "Cost", [0 1 0]) %!error ... %! perfcurve ([1 0], [0.5 0.4], 1, "NBoot", -5) %!error ... %! perfcurve ([1 0], [0.5 0.4], 1, "Alpha", 1.5) statistics-release-1.9.2/inst/Model_Evaluation/private/000077500000000000000000000000001524624707500232255ustar00rootroot00000000000000statistics-release-1.9.2/inst/Model_Evaluation/private/__resolve_groups__.m000066400000000000000000000046671524624707500272720ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{inds}, @var{ngroups}, @var{gsize}, @var{missidx}, @var{grpvars}] =} __resolve_groups__ (@var{grpvars}) ## ## Reduce grouping variables to a group index per observation. ## ## @var{inds} numbers each observation by the group it belongs to, @var{ngroups} ## counts the groups and @var{gsize} their sizes. @var{missidx} flags the ## observations dropped for holding a missing value, and @var{grpvars} is ## returned with those observations removed. ## ## One grouping variable is given as a vector, several as a matrix or cell ## array whose columns are the variables; a row then belongs to a group only if ## it matches on every one of them. ## ## @end deftypefn function [inds, ngroups, gsize, missidx, grpvars] = __resolve_groups__ (grpvars) if (isvector (grpvars)) missidx = ismissing (grpvars); if (any (missidx)) grpvars(missidx) = []; endif if (isa (grpvars, 'categorical')) [~, idx, inds] = unique (grpvars, 'stable'); else [~, idx, inds] = __unique__ (grpvars, 'stable'); endif elseif (ismatrix (grpvars)) missidx = any (ismissing (grpvars), 2); if (any (missidx)) grpvars(missidx, :) = []; endif if (isa (grpvars, 'categorical')) [~, idx, inds] = unique (grpvars, 'rows', 'stable'); else [~, idx, inds] = __unique__ (grpvars, 'rows', 'stable'); endif else error (strcat ("cvpartition: invalid value for optional", ... " paired argument 'GroupingVariables'.")); endif ngroups = numel (idx); gsize = zeros (1, ngroups); for i = 1:ngroups gsize(i) = sum (inds == i); endfor endfunction statistics-release-1.9.2/inst/Model_Evaluation/private/__stratified_group_folds__.m000066400000000000000000000054721524624707500307500ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{folds} =} __stratified_group_folds__ (@var{classes}, @var{gidx}, @var{k}) ## ## Assign whole groups to @var{k} folds while keeping the class balance even. ## ## @var{classes} numbers each observation by its class and @var{gidx} by its ## group; @var{folds} returns the fold each observation is assigned to. Every ## observation of a group lands in the same fold, so no group is split, and the ## folds are chosen to keep each class spread as evenly as the groups allow. ## ## The two demands conflict: once a group is atomic it carries whatever class ## mix it has, so the class balance can only be approximate. Groups are placed ## largest first, each into the fold whose class counts it disturbs least, ## which is the greedy assignment scikit-learn's @code{StratifiedGroupKFold} ## uses. ## ## @end deftypefn function folds = __stratified_group_folds__ (classes, gidx, k) nclasses = max (classes); ngroups = max (gidx); ## Class counts of every group gcount = zeros (ngroups, nclasses); for g = 1:ngroups for c = 1:nclasses gcount(g,c) = sum (gidx == g & classes == c); endfor endfor ## Largest groups first: they constrain the balance most, so they are placed ## while every fold is still empty enough to take them [~, order] = sort (sum (gcount, 2), 'descend'); fcount = zeros (k, nclasses); # class counts accumulated per fold gfold = zeros (ngroups, 1); for i = 1:ngroups g = order(i); best = 1; best_cost = Inf; for f = 1:k trial = fcount; trial(f,:) += gcount(g,:); ## How unevenly each class is spread over the folds, averaged cost = mean (std (trial, 0, 1)); ## Break ties toward the emptiest fold, so the folds stay similar in size if (cost < best_cost || (cost == best_cost && sum (trial(f,:)) < sum (fcount(best,:)))) best = f; best_cost = cost; endif endfor fcount(best,:) += gcount(g,:); gfold(g) = best; endfor folds = gfold(gidx); endfunction statistics-release-1.9.2/inst/Model_Evaluation/private/__unique__.m000066400000000000000000000376521524624707500255220ustar00rootroot00000000000000######################################################################## ## ## Copyright (C) 2000-2024 The Octave Project Developers ## ## See the file COPYRIGHT.md in the top-level directory of this ## distribution or . ## ## This file is part of Octave. ## ## Octave is free software: you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation, either version 3 of the License, or ## (at your option) any later version. ## ## Octave is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Octave; see the file COPYING. If not, see ## . ## ######################################################################## ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{y} =} unique (@var{x}) ## @deftypefnx {Private Function} {@var{y} =} unique (@var{x}, "rows") ## @deftypefnx {Private Function} {@var{y} =} unique (@dots{}, "sorted") ## @deftypefnx {Private Function} {@var{y} =} unique (@dots{}, "stable") ## @deftypefnx {Private Function} {[@var{y}, @var{i}, @var{j}] =} unique (@dots{}) ## @deftypefnx {Private Function} {[@var{y}, @var{i}, @var{j}] =} unique (@dots{}, "first") ## @deftypefnx {Private Function} {[@var{y}, @var{i}, @var{j}] =} unique (@dots{}, "last") ## @deftypefnx {Private Function} {[@var{y}, @var{i}, @var{j}] =} unique (@dots{}, "legacy") ## Return the unique elements of @var{x}. ## ## If the input @var{x} is a column vector then return a column vector; ## Otherwise, return a row vector. @var{x} may also be a cell array of ## strings. ## ## If the optional argument @qcode{'rows'} is given then return the unique ## rows of @var{x}. ## ## The optional argument @qcode{'sorted'}/@qcode{'stable'} controls the order ## in which unique values appear in the output. The default is ## @qcode{'sorted'} and values in the output are placed in ascending order. ## The alternative @qcode{'stable'} preserves the order found in the input ## @var{x}. ## ## If requested, return column index vectors @var{i} and @var{j} such that ## @code{@var{y} = @var{x}(@var{i})} and @code{@var{x} = @var{y}(@var{j})}. ## ## Additionally, if @var{i} is a requested output then one of the flags ## @qcode{'first'} or @qcode{'last'} may be given. If @qcode{'last'} is ## specified, return the highest possible indices in @var{i}, otherwise, if ## @qcode{'first'} is specified, return the lowest. The default is ## @qcode{'first'}. ## ## Example 1 : sort order ## ## @example ## @group ## unique ([3, 1, 1, 2]) ## @result{} [1, 2, 3] ## unique ([3, 1, 1, 2], "stable") ## @result{} [3, 1, 2] ## @end group ## @end example ## ## Example 2 : index selection ## ## @example ## @group ## [~, @var{i}] = unique ([3, 1, 1, 2], "first") ## @result{} @var{i} = [2; 4; 1] ## [~, @var{i}] = unique ([3, 1, 1, 2], "last") ## @result{} @var{i} = [3; 4; 1] ## @end group ## @end example ## ## Programming Notes: The input flag @qcode{'legacy'} changes the algorithm ## to be compatible with @sc{matlab} releases prior to R2012b. Specifically, ## The index ordering flag is changed to @qcode{'last'}, and the shape of the ## outputs @var{i}, @var{j} will follow the shape of the input @var{x} rather ## than always being column vectors. ## ## @seealso{uniquetol, union, intersect, setdiff, setxor, ismember} ## @end deftypefn function [y, i, j] = __unique__ (x, varargin) if (nargin < 1) print_usage (); elseif (! (isnumeric (x) || islogical (x) || ischar (x) || iscellstr (x))) error ("unique: X must be an array or cell array of strings"); endif if (nargin > 1) ## parse options if (! iscellstr (varargin)) error ("unique: options must be strings"); endif optrows = any (strcmp ('rows', varargin)); optfirst = any (strcmp ('first', varargin)); optlast = any (strcmp ('last', varargin)); optsorted = any (strcmp ('sorted', varargin)); optstable = any (strcmp ('stable', varargin)); optlegacy = any (strcmp ('legacy', varargin)); if (optfirst && optlast) error ('unique: cannot specify both "first" and "last"'); elseif (optsorted && optstable) error ('unique: cannot specify both "sorted" and "stable"'); elseif ((optfirst || optlast) && (optsorted || optstable)) error ('unique: cannot specify "first"/"last" with "sorted"/"stable"'); elseif (optlegacy && (optsorted || optstable)) error ('unique: cannot specify "sorted" or "stable" with "legacy"'); elseif (optrows + optfirst + optlast + optsorted + optstable + optlegacy != nargin-1) error ("unique: invalid option"); endif ## Set defaults if not set earlier. if (! optfirst && ! optlast) optfirst = true; endif if (! optsorted && ! optstable) optsorted = true; endif else optrows = false; optfirst = true; optsorted = true; optlegacy = false; endif ## FIXME: The operations ## ## match = (y(1:n-1) == y(2:n)); ## y(idx) = []; ## ## are very slow on sparse matrices. Until they are fixed to be as ## fast as for full matrices, operate on the nonzero elements of the ## sparse array as long as we are not operating on rows. if (issparse (x) && ! optrows && nargout <= 1) if (nnz (x) < numel (x)) y = unique ([0; nonzeros(x)], varargin{:}); else ## Corner case where sparse matrix is actually full y = unique (full (x), varargin{:}); endif return; endif if (optrows) n = rows (x); isrowvec = false; else n = numel (x); isrowvec = isrow (x); endif ## Special cases 0 and 1 if (n == 0) y = x; if (! optrows && any (size (x))) if (iscellstr (x)) y = cell (0, 1); else y = zeros (0, 1, class (x)); endif endif i = j = []; return; elseif (n == 1) y = x; i = j = 1; return; endif ## Calculate y output if (optrows) if (nargout > 1 || ! optsorted) [y, j] = sortrows (x); j = j(:); else y = sortrows (x); endif if (iscellstr (x)) match = all (cellfun (@isequal, y(1:n-1,:), y(2:n,:)), 2); else match = all (y(1:n-1,:) == y(2:n,:), 2); endif if (optsorted) y(match,:) = []; else y = x; y(j([false; match]), :) = []; endif else if (isvector (x)) y = x; else y = x(:); endif if (nargout > 1 || ! optsorted) [y, j] = sort (y); j = j(:); else y = sort (y); endif if (iscellstr (y)) match = strcmp (y(1:n-1), y(2:n)); else match = (y(1:n-1) == y(2:n)); endif if (optsorted) y(match) = []; else if (isvector (x)) y = x; else y = x(:); endif y(j([false; match(:)])) = []; endif endif ## Calculate i and j outputs (2nd and 3rd outputs) if (nargout > 1) if (optsorted) idx = find (match); if (! optlegacy && optfirst) idx += 1; # in-place is faster than other forms of increment endif i = j; i(idx) = []; if (nargout > 2) j(j) = cumsum (! [false; match(:)]); endif else ## Get inverse of sort index j so that sort(x)(k) = x(j)(k) = x. k = j; # cheap way to copy dimensions k(j) = 1:n; ## Generate logical index of sorted unique value locations. uniquex = ! [false; match(:)]; ## Remap unique locations to unsorted x, such that y = x(i). i = find (uniquex(k)); if (nargout > 2) ## Example of index mappings to obtain i and j ('stable'). ## x = [40,20,40,20,20,30,10]' # input data, n = 7, m = 4 ## x(j) = [10,20,20,20,30,40,40]' # sorted x ## j = [7,2,4,5,6,1,3]' # sort index, x(j) = sort(x) ## k = [6,2,7,3,4,5,1]' # inverse idx of j, sort(x)(k) = x ## y = [40,20,30,10]' # unique x preserving ordering ## uniquex = [1,1,0,0,1,1,0]' # logical sorted idx of unique x vals ## i = [1,2,6,7]' # unique output index, y = x(i) ## u = [1,2,5,6]' # linear idx of unique x(j) elems. ## l = [1,2,2,2,5,6,6]' # unique elem. in full sort(x) ## l(k) = [6,2,6,2,2,5,1]' # l mapped back to unsorted x ## j(l(k)) = [1,2,1,2,2,6,7]' # unique elem. mapped to x idx ## p(i) = [1,2,#,#,#,3,4]' # map between i and j(l(k)) ni = numel (i); u = find (uniquex); # Linear index of unique elements of sort(x) l = u(cumsum (uniquex)); # Expand u for all elements in sort(x) p = j; # cheap way to copy dimensions p(i) = 1:ni; # set p to contain the vector positions of i. j = p(j(l(k))); # Replace j with 3rd output mapping y->x. endif endif if (optlegacy && isrowvec) i = i.'; if (nargout > 2) j = j.'; endif endif endif endfunction %!assert_equal (unique ([1 1 2; 1 2 1; 1 1 2]), [1;2]) %!assert_equal (unique ([1 1 2; 1 0 1; 1 1 2],'rows'), [1 0 1; 1 1 2]) %!assert_equal (unique ([]), []) %!assert_equal (unique ([1]), [1]) %!assert_equal (unique ([1 2]), [1 2]) %!assert_equal (unique ([1;2]), [1;2]) %!assert_equal (unique ([1,NaN,Inf,NaN,Inf]), [1,Inf,NaN,NaN]) %!assert_equal (unique ([1,NaN,Inf,NaN,Inf], 'stable'), [1,NaN,Inf,NaN]) %!assert_equal (unique ({'Foo','Bar','Foo'}), {'Bar','Foo'}) %!assert_equal (unique ({'Foo','Bar','Foo'}, 'stable'), {'Foo', 'Bar'}) %!assert_equal (unique ({'Foo','Bar','FooBar'}'), {'Bar','Foo','FooBar'}') %!assert_equal (unique (zeros (1,0)), zeros (0,1)) %!assert_equal (unique (zeros (1,0), 'rows'), zeros (1,0)) %!assert_equal (unique (cell (1,0)), cell (0,1)) %!assert_equal (unique ({}), {}) %!assert_equal (unique ([1,2,2,3,2,4], 'rows'), [1,2,2,3,2,4]) %!assert_equal (unique ([1,2,2,3,2,4]), [1,2,3,4]) %!assert_equal (unique ([1,2,2,3,2,4]', 'rows'), [1;2;3;4]) %!assert_equal (unique (sparse ([2,0;2,0])), [0;2]) %!assert_equal (unique (sparse ([1,2;2,3])), [1;2;3]) %!assert_equal (unique ([1,2,2,3,2,4]', 'rows'), [1;2;3;4]) %!assert_equal (unique (single ([1,2,2,3,2,4]), 'rows'), single ([1,2,2,3,2,4])) %!assert_equal (unique (single ([1,2,2,3,2,4])), single ([1,2,3,4])) %!assert_equal (unique (single ([1,2,2,3,2,4]'), 'rows'), single ([1;2;3;4])) %!assert_equal (unique (uint8 ([1,2,2,3,2,4]), 'rows'), uint8 ([1,2,2,3,2,4])) %!assert_equal (unique (uint8 ([1,2,2,3,2,4])), uint8 ([1,2,3,4])) %!assert_equal (unique (uint8 ([1,2,2,3,2,4]'), 'rows'), uint8 ([1;2;3;4])) ## Test options with numeric inputs %!test %! [y,i,j] = unique ([1,1,2,3,3,3,4], 'sorted'); %! assert_equal (y, [1,2,3,4]); %! assert_equal (i, [1;3;4;7]); %! assert_equal (j, [1;1;2;3;3;3;4]); %!test %! [y,i,j] = unique ([4,4,2,2,2,3,1], 'stable'); %! assert_equal (y, [4,2,3,1]); %! assert_equal (i, [1;3;6;7]); %! assert_equal (j, [1;1;2;2;2;3;4]); %!test %! [y,i,j] = unique ([1,1,2,3,3,3,4]', 'last'); %! assert_equal (y, [1,2,3,4]'); %! assert_equal (i, [2;3;6;7]); %! assert_equal (j, [1;1;2;3;3;3;4]); ## Test options with cellstr inputs %!test %! [y,i,j] = unique ({'z'; 'z'; 'z'}); %! assert_equal (y, {'z'}); %! assert_equal (i, [1]); %! assert_equal (j, [1;1;1]); %!test %! [y,i,~] = unique ({'B'; 'A'; 'B'}, 'stable'); %! assert_equal (y, {'B'; 'A'}); %! assert_equal (i, [1; 2]); %!test %! A = [1,2,3; 1,2,3]; %! [y,i,j] = unique (A, 'rows'); %! assert_equal (y, [1,2,3]); %! assert_equal (A(i,:), y); %! assert_equal (y(j,:), A); %!test %! A = [4,5,6; 1,2,3; 4,5,6]; %! [y,i,j] = unique (A, 'rows', 'stable'); %! assert_equal (y, [4,5,6; 1,2,3]); %! assert_equal (A(i,:), y); %! assert_equal (y(j,:), A); %!test %! A = {'1','2','3'; '1','2','3'}; %! [y,i,j] = unique (A, 'rows'); %! assert_equal (y, {'1','2','3'}); %! assert_equal (A(i,:), y); %! assert_equal (y(j,:), A); %!test %! A = {'4','5','6'; '1','2','3'; '4','5','6'}; %! [y,i,j] = unique (A, 'rows', 'stable'); %! assert_equal (y, {'4','5','6'; '1','2','3'}); %! assert_equal (A(i,:), y); %! assert_equal (y(j,:), A); ## Test "legacy" option %!test %! [y,i,j] = unique ([1,1,2,3,3,3,4], 'legacy'); %! assert_equal (y, [1,2,3,4]); %! assert_equal (i, [2,3,6,7]); %! assert_equal (j, [1,1,2,3,3,3,4]); %!test %! A = [7 9 7; 0 0 0; 7 9 7; 5 5 5; 1 4 5]; %! [y,i,j] = unique (A, 'rows', 'legacy'); %! assert_equal (y, [0 0 0; 1 4 5; 5 5 5; 7 9 7]); %! assert_equal (i, [2; 5; 4; 3]); %! assert_equal (j, [4; 1; 4; 3; 2]); %!test <*65176> %! a = [3 2 1 2; 1 2 2 1]; %! [o1, o2, o3] = unique (a); %! assert_equal ({o1, o2, o3}, {[1;2;3], [2;3;1], [3;1;2;2;1;2;2;1]}); %! [o1, o2, o3] = unique (a, 'stable'); %! assert_equal ({o1, o2, o3}, {[3;1;2], [1;2;3], [1;2;3;3;2;3;3;2]}) %!test <*65176> %! a = [4,2,4,2,2,3,1]; %! [o1, o2, o3] = unique (a); %! assert_equal ({o1, o2, o3}, {[1,2,3,4], [7;2;6;1], [4;2;4;2;2;3;1]}); %! [o1, o2, o3] = unique (a, 'stable'); %! assert_equal ({o1, o2, o3}, {[4,2,3,1], [1;2;6;7], [1;2;1;2;2;3;4]}) %!test <*65176> %! a = [3 2 1 2; 2 1 2 1]; %! [o1, o2, o3] = unique (a(1,:), 'rows'); %! assert_equal ({o1, o2, o3}, {a(1,:), 1, 1}); %! [o1, o2, o3] = unique (a(1,:), 'rows', 'stable'); %! assert_equal ({o1, o2, o3}, {a(1,:), 1, 1}); %! [o1, o2, o3] = unique (a, 'rows'); %! assert_equal ({o1, o2, o3}, {[a(2,:); a(1,:)], [2;1], [2;1]}); %! [o1, o2, o3] = unique (a, 'rows', 'stable'); %! assert_equal ({o1, o2, o3}, {a, [1;2], [1;2]}); %! [o1, o2, o3] = unique ([a;a], 'rows'); %! assert_equal ({o1, o2, o3}, {[a(2,:); a(1,:)], [2;1], [2;1;2;1]}); %! [o1, o2, o3] = unique ([a;a], 'rows', 'stable'); %! assert_equal ({o1, o2, o3}, {a, [1;2], [1;2;1;2]}); %!test <*65176> %! a = gallery ('integerdata', [-100, 100], 6, 6); %! a = [a(2,:); a(1:5,:); a(2:6,:)]; %! [o1, o2, o3] = unique (a); %! assert_equal ({o1, o1(o3), o2, o3}, {a(:)(o2), a(:), ... %! [26;22;34;45;57; 6;11;17;33;28;35;15;56; 2;59; 4;66; ... %! 16;50;49;27;24;37;44;48;39;38;13;23; 5;12;46;55; 1], ... %! [34;14;34;16;30; 6;34;16;30; 6; 7;31;28;31;12;18; 8;31;12;18; 8; 2;29; ... %! 22;29; 1;21;10;29; 1;21;10; 9; 3;11; 3;23;27;26; 3;23;27;26;24; 4;32; ... %! 4; 25;20;19; 4;25;20;19;33;13; 5;13;15; 2;24;13;15; 2;24;17]}); %! [o1, o2, o3] = unique (a, 'stable'); %! assert_equal ({o1, o1(o3), o2, o3}, {a(:)(o2), a(:), ... %! [ 1; 2; 4; 5; 6;11;12;13;15;16;17;22;23;24;26;27;28; ... %! 33;34;35;37;38;39;44;45;46;48;49;50;55;56;57;59;66], ... %! [ 1; 2; 1; 3; 4; 5; 1; 3; 4; 5; 6; 7; 8; 7; 9;10;11; 7; 9;10;11;12;13; ... %! 14;13;15;16;17;13;15;16;17;18;19;20;19;21;22;23;19;21;22;23;24;25;26;... %! 25;27;28;29;25;27;28;29;30;31;32;31;33;12;24;31;33;12;24;34]}); %! [o1, o2, o3] = unique (a, 'rows'); %! assert_equal ({o1, o1(o3,:), o2, o3}, {a(o2,:), a, ... %! [6;11;2;4;5;1], [6;3;6;4;5;1;6;4;5;1;2]}); %! [o1, o2, o3] = unique (a, 'rows', 'stable'); %! assert_equal ({o1, o1(o3,:), o2, o3}, {a(o2,:), a, ... %! [1;2;4;5;6;11], [1;2;1;3;4;5;1;3;4;5;6]}); ## Test input validation %!error unique () %!error unique ({1}) %!error unique (1, 2) %!error unique (1, 'first', 'last') %!error %! unique (1, 'sorted', 'stable'); %!error %! unique (1, 'first', 'sorted'); %!error %! unique (1, 'last', 'stable'); %!error %! unique (1, 'sorted', 'legacy'); %!error %! unique (1, 'stable', 'legacy'); %!error unique (1, 'middle') %!error unique ({'a', 'b', 'c'}, 'UnknownOption') %!error unique ({'a', 'b', 'c'}, 'UnknownOption1', 'UnknownOption2') %!error unique ({'a', 'b', 'c'}, 'rows', 'UnknownOption2') %!error unique ({'a', 'b', 'c'}, 'UnknownOption1', 'last') statistics-release-1.9.2/inst/Model_Evaluation/rocmetrics.m000066400000000000000000001033541524624707500241110ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef rocmetrics ## -*- texinfo -*- ## @deftp {statistics} rocmetrics ## ## Receiver operating characteristic (ROC) metrics for classifier output. ## ## The @code{rocmetrics} class evaluates a classifier's performance by ## computing, for each class, a one-versus-all ROC curve together with a set ## of threshold-dependent performance metrics. It stores the results in the ## @code{Metrics} table and the per-class area under the curve in @code{AUC}, ## and provides the @code{addMetrics}, @code{average}, and @code{plot} ## methods for follow-up analysis. ## ## For a problem with @math{K} classes and scores supplied as an ## @math{N}-by-@math{K} matrix, the discriminant score used for class ## @var{k} is the one-versus-all margin ## @code{Scores(:,k) - max (Scores(:,j))} over @code{j != k}, matching ## MATLAB's @code{rocmetrics}. Every metric is evaluated at each distinct ## value of that margin. ## ## @seealso{perfcurve, confusionmat, confusionchart} ## @end deftp properties (GetAccess = public, SetAccess = private) ## -*- texinfo -*- ## @deftp {rocmetrics} {property} Metrics ## ## Performance metrics table ## ## Table of performance metrics, vertically concatenated across the classes ## in @code{ClassNames} order with one row per distinct threshold. The ## standard variables are @code{ClassName}, @code{Threshold}, ## @code{FalsePositiveRate}, and @code{TruePositiveRate}, followed by one ## variable for each metric requested through @qcode{'AdditionalMetrics'}. ## This property is read-only. ## ## @end deftp Metrics = []; ## -*- texinfo -*- ## @deftp {rocmetrics} {property} AUC ## ## Area under the ROC curve ## ## Row vector holding the area under the one-versus-all ROC curve for each ## class, in @code{ClassNames} order. This property is read-only. ## ## @end deftp AUC = []; ## -*- texinfo -*- ## @deftp {rocmetrics} {property} ClassNames ## ## Class names ## ## Class names for which the ROC metrics are computed, in the column order ## of @code{Scores}. This property is read-only. ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {rocmetrics} {property} Cost ## ## Misclassification cost matrix ## ## Square misclassification-cost matrix, with zero diagonal and unit ## off-diagonal entries by default. It is used by the @code{ExpectedCost} ## metric. This property is read-only. ## ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {rocmetrics} {property} Prior ## ## Prior class probabilities ## ## Row vector of prior class probabilities, in @code{ClassNames} order, ## summing to one. This property is read-only. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {rocmetrics} {property} Labels ## ## Observation labels ## ## True class labels supplied at construction, one per observation. This ## property is read-only. ## ## @end deftp Labels = []; ## -*- texinfo -*- ## @deftp {rocmetrics} {property} Scores ## ## Classification scores ## ## Classification scores supplied at construction, as an ## @math{N}-by-@math{K} matrix. This property is read-only. ## ## @end deftp Scores = []; ## -*- texinfo -*- ## @deftp {rocmetrics} {property} Weights ## ## Observation weights ## ## Non-negative observation weights, one per observation. Defaults to a ## vector of ones. This property is read-only. ## ## @end deftp Weights = []; endproperties properties (GetAccess = public, SetAccess = private, Hidden) ## Per-class raw curve data retained so that addMetrics can append columns ## without recomputing, and so average/plot can reuse the curves. Each ## element has fields Threshold, tp, fp, tn, fn, P, N, priorPos, cNP, cPN. ClassData_ = struct ([]); ## Names of the additional metrics currently materialised in Metrics. AddMetrics_ = {}; endproperties methods (Hidden) ## Custom display of the object summary. function disp (obj) printf (" rocmetrics with properties:\n\n"); printf (" ClassNames: %d class(es)\n", numel (obj.ClassData_)); printf (" AUC: [%s]\n", ... strtrim (sprintf ("%.4f ", obj.AUC))); printf (" Metrics: [%dx%d table]\n\n", size (obj.Metrics, 1), ... size (obj.Metrics, 2)); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {rocmetrics} {@var{obj} =} rocmetrics (@var{labels}, @var{scores}, @var{classnames}) ## @deftypefnx {rocmetrics} {@var{obj} =} rocmetrics (@dots{}, @var{Name}, @var{Value}) ## ## Create a @code{rocmetrics} object from labels and classification scores. ## ## @var{labels} is a vector of true class labels with one element per ## observation; it may be numeric, logical, a character matrix, or a cell ## array of character vectors. @var{scores} is an @math{N}-by-@math{K} ## numeric matrix of classification scores, where @code{scores(i,k)} is the ## score of observation @var{i} for the class @code{classnames(k)}. ## @var{classnames} lists the @math{K} classes in the column order of ## @var{scores}. ## ## The following @qcode{Name-Value} pairs are supported: ## ## @multitable @columnfractions 0.25 0.75 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'AdditionalMetrics'} @tab A character vector or cell array ## of metric names to append to @code{Metrics}. Supported names are ## @qcode{'TruePositives'}, @qcode{'FalseNegatives'}, ## @qcode{'FalsePositives'}, @qcode{'TrueNegatives'}, ## @qcode{'SumOfTrueAndFalsePositives'}, ## @qcode{'RateOfPositivePredictions'}, ## @qcode{'RateOfNegativePredictions'}, @qcode{'Accuracy'}, ## @qcode{'FalseNegativeRate'}, @qcode{'TrueNegativeRate'}, ## @qcode{'PositivePredictiveValue'}, @qcode{'NegativePredictiveValue'}, ## @qcode{'ExpectedCost'}, and @qcode{'f1score'}. ## ## @item @qcode{'Prior'} @tab Prior class probabilities, given as ## @qcode{'empirical'} (default), @qcode{'uniform'}, or a numeric vector ## with one value per class. ## ## @item @qcode{'Cost'} @tab A @math{K}-by-@math{K} misclassification-cost ## matrix used by the @code{ExpectedCost} metric. The default has zero ## diagonal and unit off-diagonal entries. ## ## @item @qcode{'Weights'} @tab A vector of non-negative observation ## weights. The default is a vector of ones. ## ## @item @qcode{'NaNFlag'} @tab How to treat @code{NaN} scores: ## @qcode{'omitnan'} (default) drops the affected observations, while ## @qcode{'includenan'} treats them as always classified negative. ## ## @item @qcode{'FixedMetricValues'} @tab @qcode{'all'} (default) to use ## every distinct threshold, or a numeric vector of threshold values at ## which to report the curve (nearest actual thresholds are returned). ## @end multitable ## ## Construction from a trained model object, the @qcode{'FixedMetric'} ## grids other than thresholds, and bootstrap confidence intervals are not ## implemented. ## ## @end deftypefn function obj = rocmetrics (labels, scores, classnames, varargin) if (nargin < 3) error ("rocmetrics: too few input arguments."); endif if (isa (labels, 'ClassificationSVM') || isobject (labels)) error (strcat (["rocmetrics: construction from a trained model"], ... [" object is not supported; supply labels, scores,"], ... [" and class names."])); endif ## Validate scores. if (! (isnumeric (scores) && isreal (scores) && ! isempty (scores))) error ("rocmetrics: SCORES must be a real numeric matrix."); endif if (isvector (scores)) scores = scores(:); endif [n, K] = size (scores); ## Validate labels. if (rocmetrics.numelLabels_ (labels) != n) error ("rocmetrics: LABELS must have one element per observation."); endif ## Validate class names against the score columns. cn = rocmetrics.tocol_ (classnames); if (numel (cn) != K) error (strcat (["rocmetrics: CLASSNAMES must have one element per"], ... [" column of SCORES."])); endif ## Defaults and Name-Value parsing. addmetrics = {}; prior = 'empirical'; cost = []; weights = []; nanflag = 'omitnan'; fixedvals = 'all'; if (mod (numel (varargin), 2) != 0) error ("rocmetrics: optional arguments must be Name-Value pairs."); endif for k = 1:2:numel (varargin) name = varargin{k}; if (! (ischar (name) && isrow (name))) error ("rocmetrics: parameter names must be character vectors."); endif value = varargin{k+1}; switch (lower (name)) case 'additionalmetrics' addmetrics = value; case 'prior' prior = value; case 'cost' cost = value; case 'weights' weights = value; case 'nanflag' nanflag = lower (value); case 'fixedmetricvalues' fixedvals = value; case 'fixedmetric' if (! (ischar (value) && strcmpi (value, 'Thresholds'))) error (strcat (["rocmetrics: only the 'Thresholds' value of"], ... [" 'FixedMetric' is supported."])); endif case {'numbootstraps', 'bootstraptype', 'bootstrapoptions'} error (strcat (["rocmetrics: bootstrap confidence intervals are"], ... [" not supported."])); otherwise error ("rocmetrics: unknown parameter name '%s'.", name); endswitch endfor addmetrics = rocmetrics.resolveMetricNames_ (addmetrics); if (! any (strcmpi (nanflag, {'omitnan', 'includenan'}))) error ("rocmetrics: 'NaNFlag' must be 'omitnan' or 'includenan'."); endif ## Weights. if (isempty (weights)) w = ones (n, 1); else if (! (isnumeric (weights) && isreal (weights) && isvector (weights) && numel (weights) == n && all (weights >= 0))) error (strcat (["rocmetrics: 'Weights' must be a non-negative"], ... [" vector with one element per observation."])); endif w = double (weights(:)); endif ## Prior probabilities (in ClassNames order). priorvec = rocmetrics.computePrior_ (prior, labels, cn, w, K); ## Misclassification-cost matrix. cost = rocmetrics.validateCost_ (cost, K); ## Build the per-class curves and assemble the Metrics table. obj.Labels = labels; obj.Scores = scores; obj.Weights = w; obj.ClassNames = classnames; obj.Prior = priorvec; obj.Cost = cost; obj.AddMetrics_ = addmetrics; obj = obj.buildCurves_ (cn, nanflag, fixedvals); obj = obj.assembleTable_ (); endfunction ## -*- texinfo -*- ## @deftypefn {rocmetrics} {@var{obj} =} addMetrics (@var{obj}, @var{metrics}) ## ## Append additional performance metrics to an existing @code{rocmetrics} ## object. ## ## @var{metrics} is a metric name or a cell array of metric names, chosen ## from the list supported by the @qcode{'AdditionalMetrics'} constructor ## argument. The named metrics are appended as new variables of the ## @code{Metrics} table; metrics already present are left unchanged. ## ## @end deftypefn function obj = addMetrics (obj, metrics) if (nargin != 2) error ("rocmetrics.addMetrics: METRICS input is required."); endif newmetrics = rocmetrics.resolveMetricNames_ (metrics); for k = 1:numel (newmetrics) if (! any (strcmp (newmetrics{k}, obj.AddMetrics_))) obj.AddMetrics_{end+1} = newmetrics{k}; endif endfor obj = obj.assembleTable_ (); endfunction ## -*- texinfo -*- ## @deftypefn {rocmetrics} {[@var{FPR}, @var{TPR}, @var{Thresholds}, @var{AUC}] =} average (@var{obj}, @var{type}) ## ## Compute an averaged ROC curve across the classes of a @code{rocmetrics} ## object. ## ## @var{type} selects the averaging method: @qcode{'macro'} (unweighted ## mean of the per-class curves), @qcode{'micro'} (a single curve pooling ## every one-versus-all instance), or @qcode{'weighted'} (mean of the ## per-class curves weighted by @code{Prior}). The function returns the ## averaged false and true positive rates @var{FPR} and @var{TPR}, the ## corresponding @var{Thresholds}, and the area @var{AUC} under the ## averaged curve. ## ## The averaged curve is evaluated on the union of the per-class ## thresholds. MATLAB inserts additional staircase points when building ## the averaged curve, so the exact rows and the averaged @var{AUC} may ## differ slightly from MATLAB. ## ## @end deftypefn function [FPR, TPR, Thresholds, AUC] = average (obj, type) if (nargin < 2) type = 'macro'; endif [FPR, TPR, Thresholds, AUC] = obj.averageCurve_ (lower (type)); endfunction ## -*- texinfo -*- ## @deftypefn {rocmetrics} {} plot (@var{obj}) ## @deftypefnx {rocmetrics} {@var{h} =} plot (@var{obj}) ## ## Plot the per-class ROC curves of a @code{rocmetrics} object. ## ## Each class in @code{ClassNames} contributes one true-positive-rate ## versus false-positive-rate curve. A handle to the line objects is ## returned in @var{h} when requested. ## ## @end deftypefn function varargout = plot (obj) cn = rocmetrics.tocol_ (obj.ClassNames); h = []; newplot (); hold_state = ishold (); hold ("on"); leg = cell (1, numel (obj.ClassData_)); for k = 1:numel (obj.ClassData_) cd = obj.ClassData_(k); h(end+1) = plot (cd.fp / cd.N, cd.tp / cd.P, '-o'); leg{k} = sprintf ("%s (AUC = %.4f)", ... rocmetrics.nameStr_ (cn, k), obj.AUC(k)); endfor plot ([0, 1], [0, 1], 'k:'); if (! hold_state) hold ("off"); endif xlabel ("False Positive Rate"); ylabel ("True Positive Rate"); legend (leg, "location", "southeast"); if (nargout > 0) varargout{1} = h; endif endfunction endmethods methods (Access = private) ## Build the one-versus-all curve data for every class. function obj = buildCurves_ (obj, cn, nanflag, fixedvals) scores = obj.Scores; [n, K] = size (scores); w = obj.Weights; cd = struct ("Threshold", {}, "tp", {}, "fp", {}, "tn", {}, ... "fn", {}, "P", {}, "N", {}, "priorPos", {}, ... "cNP", {}, "cPN", {}); auc = zeros (1, K); for k = 1:K ## One-versus-all margin score. if (K == 1) margin = scores(:,1); else other = max (scores(:,[1:k-1, k+1:K]), [], 2); margin = scores(:,k) - other; endif ## Collapse floating-point noise introduced by the margin so that ## thresholds that are equal in exact arithmetic group together. margin = rocmetrics.roundSig_ (margin, 14); ispos = rocmetrics.labelEq_ (obj.Labels, cn, k); isneg = ! ispos; wk = w; ## NaN score handling. isnanm = isnan (margin); if (any (isnanm)) if (strcmp (nanflag, "omitnan")) keep = ! isnanm; margin = margin(keep); ispos = ispos(keep); isneg = isneg(keep); wk = wk(keep); else margin(isnanm) = -Inf; endif endif if (! any (ispos) || ! any (isneg)) error (strcat (["rocmetrics: class '%s' must have both positive"], ... [" and negative observations."]), ... rocmetrics.nameStr_ (cn, k)); endif ## Prior-adjusted effective weights (verified MATLAB normalisation). pp = obj.Prior(k); pn = 1 - pp; Wpos = sum (wk(ispos)); Wneg = sum (wk(isneg)); S = 1 / (pp / Wpos + pn / Wneg); we = zeros (size (wk)); we(ispos) = wk(ispos) * (pp / Wpos) * S; we(isneg) = wk(isneg) * (pn / Wneg) * S; ## Cumulative counts at each distinct threshold, descending. [ss, ord] = sort (margin, "descend"); pw = cumsum (we(ord) .* ispos(ord)); nw = cumsum (we(ord) .* isneg(ord)); chg = find (diff (ss) != 0); last = [chg; numel(ss)]; tp = [0; pw(last)]; fp = [0; nw(last)]; T = [ss(1); ss(last)]; P = pp * S; N = pn * S; ## Optionally restrict to requested threshold values. if (! (ischar (fixedvals) && strcmpi (fixedvals, "all"))) idx = rocmetrics.selectThresholds_ (T, fixedvals(:)); tp = tp(idx); fp = fp(idx); T = T(idx); endif [cNP, cPN] = rocmetrics.ovaCost_ (obj.Cost, K, k); cd(k).Threshold = T; cd(k).tp = tp; cd(k).fp = fp; cd(k).tn = N - fp; cd(k).fn = P - tp; cd(k).P = P; cd(k).N = N; cd(k).priorPos = pp; cd(k).cNP = cNP; cd(k).cPN = cPN; auc(k) = trapz (fp / N, tp / P); endfor obj.ClassData_ = cd; obj.AUC = auc; endfunction ## Assemble the Metrics table from the per-class curve data. function obj = assembleTable_ (obj) cn = rocmetrics.tocol_ (obj.ClassNames); names = {'ClassName', 'Threshold', 'FalsePositiveRate', ... 'TruePositiveRate', obj.AddMetrics_{:}}; ncol = numel (names); cols = cell (1, ncol); for c = 1:ncol cols{c} = []; endfor classcol = {}; classnum = []; numericnames = isnumeric (cn) || islogical (cn); for k = 1:numel (obj.ClassData_) cd = obj.ClassData_(k); m = numel (cd.Threshold); if (numericnames) classnum = [classnum; repmat(double (cn(k)), m, 1)]; else classcol = [classcol; repmat({rocmetrics.nameStr_(cn, k)}, m, 1)]; endif cols{2} = [cols{2}; cd.Threshold]; cols{3} = [cols{3}; cd.fp / cd.N]; cols{4} = [cols{4}; cd.tp / cd.P]; for c = 5:ncol cols{c} = [cols{c}; rocmetrics.metricValue_(names{c}, cd)]; endfor endfor if (numericnames) cols{1} = classnum; else cols{1} = classcol; endif obj.Metrics = table (cols{:}, 'VariableNames', names); endfunction ## Averaged ROC curve for a given averaging type. function [FPR, TPR, Thresholds, AUC] = averageCurve_ (obj, type) cd = obj.ClassData_; K = numel (cd); if (strcmp (type, "micro")) ## Pool every one-versus-all instance into one binary problem. allthr = []; alltp = []; allfp = []; P = 0; N = 0; for k = 1:K allthr = [allthr; cd(k).Threshold]; P = P + cd(k).P; N = N + cd(k).N; endfor Thresholds = unique (allthr, "sorted"); Thresholds = flipud (Thresholds(:)); tp = zeros (size (Thresholds)); fp = zeros (size (Thresholds)); for k = 1:K tp = tp + rocmetrics.stepAt_ (cd(k).Threshold, cd(k).tp, Thresholds); fp = fp + rocmetrics.stepAt_ (cd(k).Threshold, cd(k).fp, Thresholds); endfor FPR = fp / N; TPR = tp / P; else ## Macro / weighted: mean of the per-class rates on the union grid. if (strcmp (type, "weighted")) wt = obj.Prior(:)'; elseif (strcmp (type, "macro")) wt = ones (1, K) / K; else error ("rocmetrics.average: unknown averaging type '%s'.", type); endif wt = wt / sum (wt); allthr = []; for k = 1:K allthr = [allthr; cd(k).Threshold]; endfor Thresholds = unique (allthr, "sorted"); Thresholds = flipud (Thresholds(:)); FPR = zeros (size (Thresholds)); TPR = zeros (size (Thresholds)); for k = 1:K fk = rocmetrics.stepAt_ (cd(k).Threshold, cd(k).fp / cd(k).N, ... Thresholds); tk = rocmetrics.stepAt_ (cd(k).Threshold, cd(k).tp / cd(k).P, ... Thresholds); FPR = FPR + wt(k) * fk; TPR = TPR + wt(k) * tk; endfor endif AUC = trapz (FPR, TPR); endfunction endmethods methods (Static, Access = private) ## Number of labels in a vector or character matrix. function m = numelLabels_ (labels) if (ischar (labels)) m = rows (labels); else m = numel (labels); endif endfunction ## Column form of a class-name list. function c = tocol_ (classnames) if (ischar (classnames)) c = cellstr (classnames); elseif (iscell (classnames)) c = classnames(:); else c = classnames(:); endif endfunction ## Logical membership of the labels in the k-th class. function tf = labelEq_ (labels, cn, k) if (isnumeric (cn) || islogical (cn)) tf = (labels(:) == cn(k)); else if (ischar (labels)) labels = cellstr (labels); endif tf = strcmp (labels(:), cn{k}); endif endfunction ## Printable name of the k-th class. function s = nameStr_ (cn, k) if (iscell (cn)) s = cn{k}; elseif (ischar (cn)) s = cn(k,:); else s = num2str (cn(k)); endif endfunction ## Prior probabilities in ClassNames order, summing to one. function p = computePrior_ (prior, labels, cn, w, K) if (ischar (prior)) switch (lower (prior)) case 'empirical' p = zeros (1, K); for k = 1:K p(k) = sum (w(rocmetrics.labelEq_ (labels, cn, k))); endfor if (sum (p) == 0) error ("rocmetrics: empirical prior is undefined (no labels)."); endif p = p / sum (p); case 'uniform' p = ones (1, K) / K; otherwise error ("rocmetrics: 'Prior' must be 'empirical', 'uniform', or a vector."); endswitch elseif (isnumeric (prior) && isvector (prior) && numel (prior) == K && all (prior >= 0) && sum (prior) > 0) p = prior(:)' / sum (prior); else error ("rocmetrics: 'Prior' must be 'empirical', 'uniform', or a vector."); endif endfunction ## Validate or default the misclassification-cost matrix. function cost = validateCost_ (cost, K) if (isempty (cost)) cost = ones (K) - eye (K); elseif (! (isnumeric (cost) && isequal (size (cost), [K, K]))) error ("rocmetrics: 'Cost' must be a %d-by-%d matrix.", K, K); endif endfunction ## One-versus-all costs C(N|P) and C(P|N) for the k-th class. function [cNP, cPN] = ovaCost_ (cost, K, k) if (K == 2) other = 3 - k; cNP = cost(k, other); cPN = cost(other, k); else ## For K != 2 only the default identity cost is validated; the ## off-diagonal cost of the k-th class is used symmetrically. off = cost(k, [1:k-1, k+1:K]); cNP = off(1); cPN = off(1); endif endfunction ## Value of a named metric for a class-data record. function v = metricValue_ (name, cd) tp = cd.tp; fp = cd.fp; tn = cd.tn; fn = cd.fn; P = cd.P; N = cd.N; total = P + N; pp = cd.priorPos; pn = 1 - pp; switch (name) case 'TruePositives' v = tp; case 'FalseNegatives' v = fn; case 'FalsePositives' v = fp; case 'TrueNegatives' v = tn; case 'SumOfTrueAndFalsePositives' v = tp + fp; case 'RateOfPositivePredictions' v = (tp + fp) / total; case 'RateOfNegativePredictions' v = (tn + fn) / total; case 'Accuracy' v = (tp + tn) / total; case 'FalseNegativeRate' v = fn / P; case 'TrueNegativeRate' v = tn / N; case 'PositivePredictiveValue' v = tp ./ (tp + fp); case 'NegativePredictiveValue' v = tn ./ (tn + fn); case 'ExpectedCost' v = pp * pn * (pp * (fn / P) * cd.cNP + pn * (fp / N) * cd.cPN); case 'f1score' v = 2 * tp ./ (2 * tp + fp + fn); otherwise error ("rocmetrics: unsupported metric '%s'.", name); endswitch endfunction ## Canonicalise a metric name or list of metric names. function names = resolveMetricNames_ (metrics) if (isempty (metrics)) names = {}; return; endif if (ischar (metrics)) metrics = {metrics}; elseif (! iscell (metrics)) error ("rocmetrics: metric names must be a string or cell array."); endif canon = {'TruePositives', 'FalseNegatives', 'FalsePositives', ... 'TrueNegatives', 'SumOfTrueAndFalsePositives', ... 'RateOfPositivePredictions', 'RateOfNegativePredictions', ... 'Accuracy', 'FalseNegativeRate', 'TrueNegativeRate', ... 'PositivePredictiveValue', 'NegativePredictiveValue', ... 'ExpectedCost', 'f1score'}; names = cell (1, numel (metrics)); for k = 1:numel (metrics) idx = find (strcmpi (metrics{k}, canon)); if (isempty (idx)) error ("rocmetrics: unrecognised metric '%s'.", metrics{k}); endif names{k} = canon{idx}; endfor endfunction ## Indices of the thresholds nearest the requested values. function idx = selectThresholds_ (T, vals) idx = zeros (numel (vals), 1); for k = 1:numel (vals) if (vals(k) >= T(1)) idx(k) = 1; else [~, idx(k)] = min (abs (T - vals(k))); endif endfor endfunction ## Round to SIG significant digits, collapsing floating-point noise while ## preserving genuinely distinct values. function y = roundSig_ (x, sig) y = x; nz = (x != 0) & isfinite (x); e = floor (log10 (abs (x(nz)))); f = 10 .^ (sig - 1 - e); y(nz) = round (x(nz) .* f) ./ f; endfunction ## Step-function value of a curve (thresholds T descending, values Y) at ## the query thresholds TQ, using the operating point for score >= TQ. function yq = stepAt_ (T, Y, TQ) yq = zeros (size (TQ)); for i = 1:numel (TQ) j = find (T >= TQ(i), 1, "last"); if (isempty (j)) yq(i) = Y(1); else yq(i) = Y(j); endif endfor endfunction endmethods endclassdef %!demo %! ## One-versus-all ROC curves for a three-class problem %! labels = [1 1 2 2 3 3]'; %! scores = [0.9 0.05 0.05; 0.6 0.3 0.1; 0.2 0.7 0.1; ... %! 0.1 0.6 0.3; 0.2 0.2 0.6; 0.1 0.3 0.6]; %! rocObj = rocmetrics (labels, scores, [1 2 3]); %! disp (rocObj.AUC) %! plot (rocObj); %!demo %! ## Binary ROC metrics with additional performance metrics %! labels = [1 1 1 1 0 0]'; %! p = [0.9 0.7 0.4 0.3 0.8 0.2]'; %! scores = [1-p p]; %! rocObj = rocmetrics (labels, scores, [0 1], ... %! "AdditionalMetrics", {"Accuracy", "ExpectedCost"}); %! head = rocObj.Metrics(1:4,:); %! disp (head); %!shared labels, scores, cn %! labels = [1 1 2 2 3 3]'; %! scores = [0.9 0.05 0.05; 0.6 0.3 0.1; 0.2 0.7 0.1; ... %! 0.1 0.6 0.3; 0.2 0.2 0.6; 0.1 0.3 0.6]; %! cn = [1 2 3]; %!test # MATLAB parity: default Metrics columns, thresholds, FPR/TPR, AUC %! r = rocmetrics (labels, scores, cn); %! assert_equal (r.Metrics.Properties.VariableNames, ... %! {'ClassName', 'Threshold', 'FalsePositiveRate', 'TruePositiveRate'}); %! assert_equal (double (r.Metrics.ClassName)', ... %! [1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 3], 0); %! assert_equal (r.Metrics.Threshold, ... %! [0.85;0.85;0.3;-0.4;-0.5;0.5;0.5;0.3;-0.3;-0.4;-0.85; ... %! 0.4;0.4;0.3;-0.3;-0.5;-0.6;-0.85], 1e-12); %! assert_equal (r.Metrics.TruePositiveRate, ... %! [0;0.5;1;1;1;0;0.5;1;1;1;1;0;0.5;1;1;1;1;1], 1e-12); %! assert_equal (r.AUC, [1 1 1], 1e-12); %! assert_equal (r.Prior, [1 1 1]/3, 1e-12); %!test # MATLAB parity: one-versus-all margin score sets the thresholds %! r = rocmetrics (labels, scores, cn); %! m1 = r.Metrics(double (r.Metrics.ClassName) == 1, :); %! assert_equal (m1.Threshold, [0.85;0.85;0.3;-0.4;-0.5], 1e-12); %! assert_equal (m1.FalsePositiveRate, [0;0;0;0.25;1], 1e-12); %!test # MATLAB parity: additional count and rate metrics (imbalanced binary) %! y = [1 1 1 1 0 0]'; %! p = [0.9 0.7 0.4 0.3 0.8 0.2]'; %! s = [1-p p]; %! mets = {"TruePositives","FalsePositives","TrueNegatives","FalseNegatives"}; %! r = rocmetrics (y, s, [0 1], "AdditionalMetrics", mets); %! m0 = r.Metrics(double (r.Metrics.ClassName) == 0, :); %! assert_equal (m0.TruePositives, [0;0.5;0.5;0.5;0.5;1;1], 1e-12); %! assert_equal (m0.FalsePositives, [0;0;0.5;1;1.5;1.5;2], 1e-12); %! assert_equal (m0.TrueNegatives, [2;2;1.5;1;0.5;0.5;0], 1e-12); %! assert_equal (m0.FalseNegatives, [1;0.5;0.5;0.5;0.5;0;0], 1e-12); %! assert_equal (r.AUC, [0.625 0.625], 1e-12); %!test # MATLAB parity: ExpectedCost with default and custom cost %! y = [1 1 1 1 0 0]'; %! p = [0.9 0.7 0.4 0.3 0.8 0.2]'; %! s = [1-p p]; %! r = rocmetrics (y, s, [0 1], "AdditionalMetrics", "ExpectedCost"); %! ec0 = r.Metrics.ExpectedCost(double (r.Metrics.ClassName) == 0); %! assert_equal (ec0(1), 2/27, 1e-12); %! assert_equal (ec0(5), 4/27, 1e-12); %! rc = rocmetrics (y, s, [0 1], "Cost", [0 2; 1 0], ... %! "AdditionalMetrics", "ExpectedCost"); %! ecc = rc.Metrics.ExpectedCost(double (rc.Metrics.ClassName) == 0); %! assert_equal (ecc(1), 4/27, 1e-12); %! assert_equal (ecc(5), 5/27, 1e-12); %!test # MATLAB parity: uniform prior reweights the counts %! y = [1 1 1 1 0 0]'; %! p = [0.9 0.7 0.4 0.3 0.8 0.2]'; %! s = [1-p p]; %! r = rocmetrics (y, s, [0 1], "Prior", "uniform", ... %! "AdditionalMetrics", "TruePositives"); %! assert_equal (r.Prior, [0.5 0.5], 1e-12); %! tp0 = r.Metrics.TruePositives(double (r.Metrics.ClassName) == 0); %! assert_equal (max (tp0), 4/3, 1e-12); %!test # explicit weights renormalise the effective counts %! y = [1 1 1 1 0 0]'; %! p = [0.9 0.7 0.4 0.3 0.8 0.2]'; %! s = [1-p p]; %! w = [1 1 1 1 2 2]'; %! r = rocmetrics (y, s, [0 1], "Weights", w, ... %! "AdditionalMetrics", "TruePositives"); %! assert_equal (r.Prior, [0.5 0.5], 1e-12); %! tp0 = r.Metrics.TruePositives(double (r.Metrics.ClassName) == 0); %! assert_equal (max (tp0), 2, 1e-12); %!test # addMetrics appends without recomputing the curve %! r = rocmetrics (labels, scores, cn); %! r = addMetrics (r, "Accuracy"); %! assert_equal (any (strcmp ("Accuracy", ... %! r.Metrics.Properties.VariableNames)), true); %! acc1 = r.Metrics.Accuracy(1); %! assert_equal (acc1, 2/3, 1e-12); %!test # PPV is NaN at the origin, NPV is NaN at the all-positive end %! r = rocmetrics (labels, scores, cn, "AdditionalMetrics", ... %! {"PositivePredictiveValue","NegativePredictiveValue"}); %! m1 = r.Metrics(double (r.Metrics.ClassName) == 1, :); %! assert_equal (isnan (m1.PositivePredictiveValue(1)), true); %! assert_equal (isnan (m1.NegativePredictiveValue(end)), true); %!test # cell-array string labels and names %! y = {"a","a","b","b","c","c"}; %! r = rocmetrics (y, scores, {"a","b","c"}); %! assert_equal (r.AUC, [1 1 1], 1e-12); %! assert_equal (iscellstr (r.Metrics.ClassName), true); %!test # FixedMetricValues selects nearest thresholds %! r = rocmetrics (labels, scores, cn, "FixedMetricValues", [Inf 0.4 -0.5]); %! m1 = r.Metrics(double (r.Metrics.ClassName) == 1, :); %! assert_equal (numel (m1.Threshold), 3); %! assert_equal (m1.Threshold(1), 0.85, 1e-12); %!test # average returns a valid curve for each averaging type %! r = rocmetrics (labels, scores, cn); %! [fpr, tpr, thr, auc] = average (r, "macro"); %! assert_equal (fpr(1), 0, 1e-12); %! assert_equal (tpr(end), 1, 1e-12); %! assert_equal (auc >= 0 && auc <= 1, true); %! [~, ~, ~, aucmi] = average (r, "micro"); %! assert_equal (aucmi >= 0 && aucmi <= 1, true); ## Test input validation %!error rocmetrics ([1 0], [0.4 0.6]) %!error ... %! rocmetrics ([1 0], {1, 2}, [0 1]) %!error ... %! rocmetrics ([1 0 1], [0.4 0.6; 0.5 0.5], [0 1]) %!error ... %! rocmetrics ([1 0], [0.4 0.6; 0.5 0.5], [0 1 2]) %!error ... %! rocmetrics ([1 0], [0.4 0.6; 0.5 0.5], [0 1], "AdditionalMetrics", "foo") %!error ... %! rocmetrics ([1 0], [0.4 0.6; 0.5 0.5], [0 1], "Prior", "bogus") %!error ... %! rocmetrics ([1 0], [0.4 0.6; 0.5 0.5], [0 1], "NumBootstraps", 100) %!error ... %! rocmetrics ([1 0], [0.4 0.6; 0.5 0.5], [0 1], "Zzz", 1) statistics-release-1.9.2/inst/Nearest_Neighbors/000077500000000000000000000000001524624707500217255ustar00rootroot00000000000000statistics-release-1.9.2/inst/Nearest_Neighbors/ExhaustiveSearcher.m000066400000000000000000001432301524624707500257100ustar00rootroot00000000000000## Copyright (C) 2025 Swayam Shah ## Copyright (C) 2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef ExhaustiveSearcher ## -*- texinfo -*- ## @deftp {statistics} ExhaustiveSearcher ## ## Exhaustive nearest neighbor searcher ## ## The @code{ExhaustiveSearcher} class implements an exhaustive search ## algorithm for nearest neighbor queries. It stores training data and ## supports various distance metrics along with their parameter values for ## performing an exhaustive search. The exhaustive search algorithm computes ## the distance from each query point to all the points in the training data ## and facilitates a nearest neighbor search using @code{knnsearch} or a ## radius search using @code{rangesearch}. ## ## You can either use the @code{ExhaustiveSearcher} class constructor or the ## @code{createns} function to create an @qcode{ExhaustiveSearcher} object. ## ## @seealso{createns, KDTreeSearcher, hnswSearcher, knnsearch, rangesearch} ## @end deftp properties(SetAccess = private) ## -*- texinfo -*- ## @deftp {ExhaustiveSearcher} {property} X ## ## Point data ## ## Point data, specified as an @math{N*P} numeric matrix where each row ## is an observation and each column is a feature. This property is private ## and cannot be modified after object creation. ## ## Data of class @qcode{single} is stored and searched in single ## precision, any other numeric class is converted to @qcode{double}. ## ## @end deftp X = [] endproperties properties ## -*- texinfo -*- ## @deftp {ExhaustiveSearcher} {property} Distance ## ## Distance metric ## ## Distance metric used for searches, specified as a character vector (e.g., ## @qcode{'euclidean'}, @qcode{'minkowski'}) or a function handle to a ## custom distance function. Default is @qcode{'euclidean'}. Supported ## metrics align with those in @code{pdist2}. ## ## @end deftp Distance = 'euclidean' ## -*- texinfo -*- ## @deftp {ExhaustiveSearcher} {property} DistParameter ## ## Distance parameter ## ## The type and value of the distance parameter depends on the selected ## @qcode{Distance} metric and can be any of the following: ## ## @itemize ## @item For @qcode{'minkowski'}, a positive scalar exponent (default 2). ## @item For @qcode{'seuclidean'}, a nonnegative vector of scaling factors ## matching the number of columns in @qcode{X} (default is standard ## deviation of @qcode{X}). ## @item For @qcode{'mahalanobis'}, a positive definite covariance matrix ## matching the dimensions of @qcode{X} (default is @code{cov (@var{X})}). ## @item Empty for other metrics or custom functions. ## @end itemize ## ## @end deftp DistParameter = [] endproperties methods (Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) if (isscalar (this)) fprintf ("\n ExhaustiveSearcher with properties:\n\n"); fprintf ("%+25s: '%s'\n", 'Distance', this.Distance); if (! isempty (this.DistParameter)) if (isscalar (this.DistParameter)) fprintf ("%+25s: %g\n", 'DistParameter', this.DistParameter); elseif (isvector (this.DistParameter)) fprintf ("%+25s: %s\n", 'DistParameter', ... mat2str (this.DistParameter)); else fprintf ("%+25s: [%dx%d %s]\n", 'DistParameter', ... size (this.DistParameter), class (this.DistParameter)); endif else fprintf ("%+25s: []\n", 'DistParameter'); endif fprintf ("%+25s: [%dx%d %s]\n", 'X', size (this.X), class (this.X)); endif endfunction ## Class specific subscripted reference function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' error ("ExhaustiveSearcher.subsref: () indexing not supported."); case '{}' error ("ExhaustiveSearcher.subsref: {} indexing not supported."); case '.' if (! ischar (s.subs)) error (strcat ("ExhaustiveSearcher.subsref: property", ... " name must be a character vector.")); endif try out = this.(s.subs); catch error (strcat ("ExhaustiveSearcher.subsref: unrecognized", ... " property: '%s'"), s.subs); end_try_catch endswitch ## Chained references if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction ## Class specific subscripted assignment function this = subsasgn (this, s, val) if (numel (s) > 1) error ("ExhaustiveSearcher.subsasgn: chained subscripts not allowed."); endif switch s.type case '()' error ("ExhaustiveSearcher.subsasgn: () indexing not supported."); case '{}' error ("ExhaustiveSearcher.subsasgn: {} indexing not supported."); case '.' if (! ischar (s.subs)) error (strcat ("ExhaustiveSearcher.subsasgn: property", ... " name must be a character vector.")); endif switch (s.subs) case 'X' error (strcat ("ExhaustiveSearcher.subsasgn: X is", ... " read-only and cannot be modified.")); case 'Distance' vm = {'euclidean', 'minkowski', 'seuclidean', 'mahalanobis', ... 'cityblock', 'manhattan', 'chebychev', 'cosine', ... 'correlation', 'spearman', 'hamming', 'jaccard'}; if (ischar (val)) if (! any (strcmpi (vm, val))) error (strcat ("ExhaustiveSearcher.subsasgn:", ... " unsupported distance metric '%s'."), val); endif this.Distance = val; elseif (isa (val, 'function_handle')) try D = val(this.X(1,:), this.X); catch error (strcat ("ExhaustiveSearcher.subsasgn:", ... " invalid distance function handle.")); end_try_catch if (! isvector (D) || length (D) != rows (this.X)) error (strcat ("ExhaustiveSearcher.subsasgn: custom", ... " distance function output invalid.")); endif this.Distance = val; else error (strcat ("ExhaustiveSearcher.subsasgn: Distance", ... " must be a string or function handle.")); endif case 'DistParameter' if (strcmpi (this.Distance, 'minkowski')) if (! (isscalar (val) && isnumeric (val) && val > 0 && isfinite (val))) error (strcat ("ExhaustiveSearcher.subsasgn:", ... " DistParameter must be a positive", ... " finite scalar for minkowski.")); endif elseif (strcmpi (this.Distance, 'seuclidean')) if (! (isvector (val) && isnumeric (val) && all (val >= 0) && all (isfinite (val)) && length (val) == columns (this.X))) error (strcat ("ExhaustiveSearcher.subsasgn:", ... " DistParameter must be a nonnegative", ... " vector matching X columns.")); endif elseif (strcmpi (this.Distance, 'mahalanobis')) if (! (ismatrix (val) && isnumeric (val) && all (isfinite (val(:))) && rows (val) == columns (val) && rows (val) == columns (this.X))) error (strcat ("ExhaustiveSearcher.subsasgn:", ... " DistParameter must be a square", ... " matrix matching X columns.")); endif [~, p] = chol (val); if (p != 0) error (strcat ("ExhaustiveSearcher.subsasgn:", ... " DistParameter must be positive", ... " definite for mahalanobis.")); endif else if (! isempty (val)) error (strcat ("ExhaustiveSearcher.subsasgn:", ... " DistParameter must be empty for this", ... " distance metric.")); endif endif this.DistParameter = val; otherwise error (strcat ("ExhaustiveSearcher.subsasgn:", ... " unrecognized property: '%s'"), s.subs); endswitch endswitch endfunction endmethods methods ## -*- texinfo -*- ## @deftypefn {ExhaustiveSearcher} {@var{obj} =} ExhaustiveSearcher (@var{X}) ## @deftypefnx {ExhaustiveSearcher} {@var{obj} =} ExhaustiveSearcher (@var{X}, @var{name}, @var{value}) ## ## Create an @qcode{ExhaustiveSearcher} object for nearest neighbor ## searches. ## ## @code{@var{obj} = ExhaustiveSearcher (@var{X})} constructs an ## @qcode{ExhaustiveSearcher} object with training data @var{X} using the ## default @qcode{'euclidean'} distance metric. @var{X} must be an ## @math{N*P} numeric matrix, where rows represent observations and columns ## represent features. ## ## @code{@var{obj} = ExhaustiveSearcher (@var{X}, @var{name}, @var{value})} ## allows customization through name-value pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Distance'} @tab Distance metric, specified as a ## character vector (e.g., @qcode{'euclidean'}, @qcode{'minkowski'}) or a ## function handle. Default is @qcode{'euclidean'}. See @code{pdist2} for ## supported metrics. ## ## @item @qcode{'P'} @tab a positive scalar specifying the exponent for ## the Minkowski distance. Valid only when @qcode{'Distance'} is ## @qcode{'minkowski'}. Default is 2. ## ## @item @qcode{'Scale'} @tab a nonnegative vector with the same number ## of elements as the columns in @var{X} specifying the scale parameter for ## the standardized Euclidean distance. Valid only when ## @qcode{'Distance'} is @qcode{'seuclidean'}. Default is @code{std (X)}. ## ## @item @qcode{'Cov'} @tab a positive definite matrix matching the ## number of columns in @var{X} specifying the covariance matrix for the ## Mahalanobis distance. Valid only when @qcode{'Distance'} is ## @qcode{'mahalanobis'}. Default is @code{cov (X)}. ## @end multitable ## ## @seealso{ExhaustiveSearcher, knnsearch, rangesearch, pdist2} ## ## @qcode{'Distance'}, @qcode{'P'}, @qcode{'Cov'} and @qcode{'Scale'} override ## the searcher's own metric for that call only; the @qcode{Distance} and ## @qcode{DistParameter} properties keep their values, as they do in MATLAB. ## @end deftypefn function obj = ExhaustiveSearcher (X, varargin) if (nargin < 1) error ("ExhaustiveSearcher: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error ("ExhaustiveSearcher: Name-Value arguments must be in pairs."); endif if (! (isnumeric (X) && ismatrix (X) && all (isfinite (X)(:)))) error ("ExhaustiveSearcher: X must be a finite numeric matrix."); endif ## Single precision is carried through, but every other class is ## converted up to double, as MATLAB does. Integer data would ## otherwise round each coordinate difference and corrupt distances. if (! isa (X, "single")) X = double (X); endif obj.X = X; ## Default values for optional parameters Distance = 'euclidean'; P = []; S = []; C = []; ## Parse optional parameters while (numel (varargin) > 0) switch (lower (varargin{1})) case 'distance' Distance = varargin{2}; case 'p' P = varargin{2}; case 'scale' S = varargin{2}; case 'cov' C = varargin{2}; otherwise error (strcat ("ExhaustiveSearcher: invalid parameter", ... " name: '%s'."), varargin{1}); endswitch varargin(1:2) = []; endwhile ## Validate and set distance metric valid_metrics = {'euclidean', 'minkowski', 'seuclidean', ... 'mahalanobis', 'cityblock', 'manhattan', ... 'chebychev', 'cosine', 'correlation', ... 'spearman', 'hamming', 'jaccard'}; if (ischar (Distance)) if (! any (strcmpi (valid_metrics, Distance))) error ("ExhaustiveSearcher: unsupported distance metric '%s'.", ... Distance); endif obj.Distance = Distance; elseif (isa (Distance, 'function_handle')) try D = Distance(X(1,:), X); catch error ("ExhaustiveSearcher: invalid distance function handle."); end_try_catch if (! isvector (D) || length (D) != rows (X)) error (strcat ("ExhaustiveSearcher: custom distance", ... " function output invalid.")); endif obj.Distance = Distance; else error (strcat ("ExhaustiveSearcher: Distance must", ... " be a string or function handle.")); endif ## Set DistParameter based on Distance if (strcmpi (obj.Distance, 'minkowski')) if (isempty (P)) obj.DistParameter = 2; else if (! (isscalar (P) && isnumeric (P) && P > 0 && isfinite (P))) error ("ExhaustiveSearcher: P must be a positive finite scalar."); endif obj.DistParameter = P; endif elseif (strcmpi (obj.Distance, 'seuclidean')) if (isempty (S)) obj.DistParameter = std (X, [], 1); else if (! (isvector (S) && isnumeric (S) && all (S >= 0) && ... all (isfinite (S)) && length (S) == columns (X))) error (strcat ("ExhaustiveSearcher: Scale must be a", ... " nonnegative vector matching X columns.")); endif obj.DistParameter = S; endif elseif (strcmpi (obj.Distance, 'mahalanobis')) if (isempty (C)) obj.DistParameter = cov (X); else if (! (ismatrix (C) && isnumeric (C) && all (isfinite (C)(:)) && ... rows (C) == columns (C) && rows (C) == columns (X))) error (strcat ("ExhaustiveSearcher: Cov must be a square", ... " matrix matching X columns.")); endif [~, p] = chol (C); if (p != 0) error ("ExhaustiveSearcher: Cov must be positive definite."); endif obj.DistParameter = C; endif else obj.DistParameter = []; endif endfunction ## -*- texinfo -*- ## @deftypefn {ExhaustiveSearcher} {[@var{idx}, @var{D}] =} knnsearch (@var{obj}, @var{Y}) ## @deftypefnx {ExhaustiveSearcher} {[@var{idx}, @var{D}] =} knnsearch (@var{obj}, @var{Y}, @var{name}, @var{value}) ## ## Find the @math{K} nearest neighbors in the training data to query points. ## ## @code{[@var{idx}, @var{D}] = knnsearch (@var{obj}, @var{Y})} returns the ## indices @var{idx} and distances @var{D} of the nearest neighbor in ## @var{obj.X} to each point in @var{Y}, using the distance metric specified ## in @var{obj.Distance}. ## ## @itemize ## @item @var{obj} is an @qcode{ExhaustiveSearcher} object. ## @item @var{Y} is an @math{M*P} numeric matrix of query points, where ## @math{P} must match the number of columns in @var{obj.X}. ## @end itemize ## ## @var{idx} is always of class @qcode{double}. @var{D} is of class ## @qcode{single} when either @var{obj.X} or @var{Y} is @qcode{single}, ## in which case the distances are computed in single precision, and of ## class @qcode{double} otherwise. ## ## @code{[@var{idx}, @var{D}] = knnsearch (@var{obj}, @var{Y}, @var{name}, ## @var{value})} ## allows additional options via name-value pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'K'} @tab A positive integer specifying the number of ## nearest neighbors to find. Default is 1. A value larger than the ## number of observations in the training data is answered with all of ## them, since there are no more neighbors to return. ## ## @item @qcode{'IncludeTies'} @tab Logical flag indicating whether to ## include all neighbors tied with the @math{K}th smallest distance. Default ## is @qcode{false}. If @qcode{true}, @var{idx} and @var{D} are cell arrays. ## @end multitable ## ## @var{idx} contains the indices of the nearest neighbors in @var{obj.X}. ## @var{D} contains the corresponding distances. ## ## @seealso{ExhaustiveSearcher, rangesearch, pdist2} ## @end deftypefn function [idx, D] = knnsearch (obj, Y, varargin) if (nargin < 2) error ("ExhaustiveSearcher.knnsearch: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ExhaustiveSearcher.knnsearch:", ... " Name-Value arguments must be in pairs.")); endif if (! (isnumeric (Y) && ismatrix (Y) && all (isfinite (Y)(:)))) error (strcat ("ExhaustiveSearcher.knnsearch: Y", ... " must be a finite numeric matrix.")); endif if (size (obj.X, 2) != size (Y, 2)) error (strcat ("ExhaustiveSearcher.knnsearch: number", ... " of columns in X and Y must match.")); endif K = 1; IncludeTies = false; Dist = []; Pval = []; Cval = []; Sval = []; ## Parse options while (numel (varargin) > 0) switch (lower (varargin{1})) case 'k' K = varargin{2}; if (! (isscalar (K) && isnumeric (K) && K >= 1 && K == fix (K) && isfinite (K))) error (strcat ("ExhaustiveSearcher.knnsearch: K", ... " must be a positive integer.")); endif case 'includeties' IncludeTies = varargin{2}; if (! (islogical (IncludeTies) && isscalar (IncludeTies))) error (strcat ("ExhaustiveSearcher.knnsearch:", ... " IncludeTies must be a logical scalar.")); endif case 'distance' Dist = varargin{2}; case 'p' Pval = varargin{2}; case 'cov' Cval = varargin{2}; case 'scale' Sval = varargin{2}; otherwise error (strcat ("ExhaustiveSearcher.knnsearch:", ... " invalid parameter name: '%s'."), varargin{1}); endswitch varargin(1:2) = []; endwhile ## A metric given here applies to this call only: the searcher's ## own Distance and DistParameter are left untouched, as MATLAB ## leaves them. Distance = obj.Distance; DistParameter = obj.DistParameter; if (! (isempty (Dist) && isempty (Pval) && isempty (Cval) && isempty (Sval))) if (isempty (Dist)) Dist = Distance; endif [Distance, DistParameter] = __resolve_metric__ ( ... "ExhaustiveSearcher.knnsearch", obj.X, Dist, ... Pval, Cval, Sval, {"euclidean", "seuclidean", "mahalanobis", "minkowski", ... "cityblock", "manhattan", "chebychev", "cosine", ... "correlation", "spearman", "hamming", "jaccard"}); endif ## Determine block size based on memory target (128 MB) N = rows (obj.X); ## There are only N points to return, so a larger K is answered with all ## of them. Indexing the sorted list past its end raised an internal ## out-of-bound error instead. K = min (K, N); M = rows (Y); targetBytes = 128 * 1024^2; BlockSize = max (1, floor (targetBytes / (N * 8))); ## Distances are computed in single precision when either the training ## data or the query is single, and in double otherwise. The indices ## are always double. cls = "double"; if (isa (obj.X, "single") || isa (Y, "single")) cls = "single"; endif X = cast (obj.X, cls); Y = cast (Y, cls); ## Initialize outputs if (IncludeTies) idx = cell (M, 1); D = cell (M, 1); elseif (K == 1) idx = zeros (M, 1); D = zeros (M, 1, cls); else idx = zeros (M, K); D = zeros (M, K, cls); endif ## Process Y in blocks ## Where the metric is one the compiled search knows and no ties are ## wanted, the whole search runs there and no block of the distance ## matrix is formed at all. if (! IncludeTies && ischar (Distance) && any (strcmpi (Distance, {'euclidean', 'cityblock', ... 'chebychev', 'minkowski'}))) [idx, D] = __knnbrute__ (obj.X, Y, K, lower (Distance), DistParameter); return; endif for blk_start = 1:BlockSize:M blk_end = min (blk_start + BlockSize - 1, M); Y_blk = Y(blk_start:blk_end, :); blk_rows = blk_end - blk_start + 1; ## Compute distance matrix for this block if (ischar (Distance)) D_blk = pdist2 (Y_blk, X, Distance, DistParameter); else D_blk = pdist2 (X, Y_blk, Distance, DistParameter); D_blk = reshape (D_blk', blk_rows, N); endif ## Process block results if (K == 1 && ! IncludeTies) [D(blk_start:blk_end), idx(blk_start:blk_end)] = min (D_blk, [], 2); else if (IncludeTies) [sorted_D, sorted_idx] = sort (D_blk, 2); for i = 1:blk_rows if (K > columns (sorted_D)) kth_dist = sorted_D(i, end); else kth_dist = sorted_D(i, K); endif tie_idx = find (D_blk(i, :) <= kth_dist); [D{blk_start + i - 1}, order] = sort (D_blk(i, tie_idx)); idx{blk_start + i - 1} = tie_idx(order); endfor else ## Partial selection, not a full sort of every row. [bi, bd] = __knnselect__ (D_blk, K); idx(blk_start:blk_end, :) = bi; D(blk_start:blk_end, :) = bd; endif endif endfor endfunction ## -*- texinfo -*- ## @deftypefn {ExhaustiveSearcher} {[@var{idx}, @var{D}] =} rangesearch (@var{obj}, @var{Y}, @var{r}) ## @deftypefnx {ExhaustiveSearcher} {[@var{idx}, @var{D}] =} rangesearch (@var{obj}, @var{Y}, @var{r}, @var{name}, @var{value}) ## ## Find all neighbors within a specified radius of query points. ## ## @code{[@var{idx}, @var{D}] = rangesearch (@var{obj}, @var{Y}, @var{r})} ## returns the indices @var{idx} and distances @var{D} of all points in ## @var{obj.X} within radius @var{r} of each point in @var{Y}, using the ## distance metric specified in @var{obj.Distance}. ## ## @itemize ## @item @var{obj} is an @qcode{ExhaustiveSearcher} object. ## @item @var{Y} is an @math{M*P} numeric matrix of query points, where ## @math{P} must match the number of columns in @var{obj.X}. ## @item @var{r} is a nonnegative scalar specifying the search radius. ## @end itemize ## ## @var{idx} is always of class @qcode{double}. @var{D} is of class ## @qcode{single} when either @var{obj.X} or @var{Y} is @qcode{single}, ## in which case the distances are computed in single precision, and of ## class @qcode{double} otherwise. ## ## @code{[@var{idx}, @var{D}] = rangesearch (@dots{}, @var{name}, ## @var{value})} allows additional options via name-value pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'SortIndices'} @tab Logical flag indicating whether to ## sort the indices by distance. Default is @qcode{true}. ## @end multitable ## ## @var{idx} and @var{D} are cell arrays where each cell contains the ## indices and distances for one query point in @var{Y}. ## ## @seealso{ExhaustiveSearcher, knnsearch, pdist2} ## @end deftypefn function [idx, D] = rangesearch (obj, Y, r, varargin) if (nargin < 3) error ("ExhaustiveSearcher.rangesearch: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ExhaustiveSearcher.rangesearch:", ... " Name-Value arguments must be in pairs.")); endif if (! (isnumeric (Y) && ismatrix (Y) && all (isfinite (Y)(:)))) error (strcat ("ExhaustiveSearcher.rangesearch: Y", ... " must be a finite numeric matrix.")); endif if (size (obj.X, 2) != size (Y, 2)) error (strcat ("ExhaustiveSearcher.rangesearch: number", ... " of columns in X and Y must match.")); endif if (! (isscalar (r) && isnumeric (r) && r >= 0 && isfinite (r))) error (strcat ("ExhaustiveSearcher.rangesearch: R", ... " must be a nonnegative finite scalar.")); endif ## Parse options SortIndices = true; Dist = []; Pval = []; Cval = []; Sval = []; while (numel (varargin) > 0) switch (lower (varargin{1})) case 'sortindices' SortIndices = varargin{2}; if (! (islogical (SortIndices) && isscalar (SortIndices))) error (strcat ("ExhaustiveSearcher.rangesearch:", ... " SortIndices must be a logical scalar.")); endif case 'distance' Dist = varargin{2}; case 'p' Pval = varargin{2}; case 'cov' Cval = varargin{2}; case 'scale' Sval = varargin{2}; otherwise error (strcat ("ExhaustiveSearcher.rangesearch: invalid", ... " parameter name: '%s'."), varargin{1}); endswitch varargin(1:2) = []; endwhile ## A metric given here applies to this call only: the searcher's ## own Distance and DistParameter are left untouched, as MATLAB ## leaves them. Distance = obj.Distance; DistParameter = obj.DistParameter; if (! (isempty (Dist) && isempty (Pval) && isempty (Cval) && isempty (Sval))) if (isempty (Dist)) Dist = Distance; endif [Distance, DistParameter] = __resolve_metric__ ( ... "ExhaustiveSearcher.rangesearch", obj.X, Dist, ... Pval, Cval, Sval, {"euclidean", "seuclidean", "mahalanobis", "minkowski", ... "cityblock", "manhattan", "chebychev", "cosine", ... "correlation", "spearman", "hamming", "jaccard"}); endif ## Determine block size based on memory target (128 MB) N = rows (obj.X); M = rows (Y); targetBytes = 128 * 1024^2; BlockSize = max (1, floor (targetBytes / (N * 8))); ## Distances are computed in single precision when either the training ## data or the query is single, and in double otherwise. The indices ## are always double. cls = "double"; if (isa (obj.X, "single") || isa (Y, "single")) cls = "single"; endif X = cast (obj.X, cls); Y = cast (Y, cls); ## Initialize outputs idx = cell (M, 1); D = cell (M, 1); ## Process Y in blocks for blk_start = 1:BlockSize:M blk_end = min (blk_start + BlockSize - 1, M); Y_blk = Y(blk_start:blk_end, :); blk_rows = blk_end - blk_start + 1; ## Compute distance matrix for this block if (ischar (Distance)) D_blk = pdist2 (Y_blk, X, Distance, DistParameter); else D_blk = pdist2 (X, Y_blk, Distance, DistParameter); D_blk = reshape (D_blk', blk_rows, N); endif ## Process block results for i = 1:blk_rows within_r = find (D_blk(i, :) <= r); if (SortIndices) [sorted_D, sort_idx] = sort (D_blk(i, within_r)); idx{blk_start + i - 1} = within_r(sort_idx); D{blk_start + i - 1} = sorted_D; else idx{blk_start + i - 1} = within_r; D{blk_start + i - 1} = D_blk(i, within_r); endif endfor endfor endfunction endmethods endclassdef ## Demo Examples %!demo %! ## Demo to verify implementation using fisheriris dataset %! load fisheriris %! numSamples = size (meas, 1); %! queryIndices = [20, 95, 123, 136, 138]; %! dataPoints = meas(! ismember (1:numSamples, queryIndices), :); %! queryPoints = meas(queryIndices, :); %! searchModel = ExhaustiveSearcher (dataPoints, 'Distance', 'mahalanobis') %! mahalanobisParam = searchModel.DistParameter %! searchRadius = 3; %! nearestNeighbors = knnsearch (searchModel, queryPoints, 'K', 2) %! neighborsInRange = rangesearch (searchModel, queryPoints, searchRadius) %!demo %! ## Create an ExhaustiveSearcher with Euclidean distance %! X = [1, 2; 3, 4; 5, 6]; %! obj = ExhaustiveSearcher (X); %! ## Find the nearest neighbor to [2, 3] %! Y = [2, 3]; %! [idx, D] = knnsearch (obj, Y); %! disp ('Nearest neighbor index:'); disp (idx); %! disp ('Distance:'); disp (D); %! ## Find all points within radius 2 %! [idx, D] = rangesearch (obj, Y, 2); %! disp ('Indices within radius:'); disp (idx); %! disp ('Distances:'); disp (D); %!demo %! ## Create an ExhaustiveSearcher with Minkowski distance (P=1) %! X = [0, 0; 1, 0; 0, 1]; %! obj = ExhaustiveSearcher (X, 'Distance', 'minkowski', 'P', 1); %! ## Find the 2 nearest neighbors to [0.5, 0.5] %! Y = [0.5, 0.5]; %! [idx, D] = knnsearch (obj, Y, 'K', 2); %! disp ('Nearest neighbor indices:'); disp (idx); %! disp ('Distances:'); disp (D); %!demo %! rng (42); %! disp ('Demonstrating ExhaustiveSearcher'); %! %! n = 100; %! mu1 = [0.3, 0.3]; %! mu2 = [0.7, 0.7]; %! sigma = 0.1; %! X1 = mu1 + sigma * randn (n/2, 2); %! X2 = mu2 + sigma * randn (n/2, 2); %! X = [X1; X2]; %! %! obj = ExhaustiveSearcher (X); %! %! Y = [0.3, 0.3; 0.7, 0.7; 0.5, 0.5]; %! %! K = 5; %! [idx, D] = knnsearch (obj, Y, 'K', K); %! %! disp ('For the first query point:'); %! disp (['Query point: ', num2str(Y(1,:))]); %! disp ('Indices of nearest neighbors:'); %! disp (idx(1,:)); %! disp ('Distances:'); %! disp (D(1,:)); %! %! figure; %! scatter (X(:,1), X(:,2), 36, 'b', 'filled'); % Training points %! hold on; %! scatter (Y(:,1), Y(:,2), 36, 'r', 'filled'); % Query points %! for i = 1:size (Y,1) %! query = Y(i,:); %! neighbors = X(idx(i,:), :); %! for j = 1:K %! plot ([query(1), neighbors(j,1)], [query(2), neighbors(j,2)], 'k-'); %! endfor %! endfor %! hold off; %! title ('K Nearest Neighbors with ExhaustiveSearcher'); %! xlabel ('X1'); %! ylabel ('X2'); %! %! r = 0.15; %! [idx, D] = rangesearch (obj, Y, r); %! %! disp ('For the first query point in rangesearch:'); %! disp (['Query point: ', num2str(Y(1,:))]); %! disp ('Indices of points within radius:'); %! disp (idx{1}); %! disp ('Distances:'); %! disp (D{1}); %! %! figure; %! scatter (X(:,1), X(:,2), 36, 'b', 'filled'); %! hold on; %! scatter (Y(:,1), Y(:,2), 36, 'r', 'filled'); %! theta = linspace (0, 2*pi, 100); %! for i = 1:size (Y,1) %! center = Y(i,:); %! x_circle = center(1) + r * cos (theta); %! y_circle = center(2) + r * sin (theta); %! plot (x_circle, y_circle, 'g-'); %! % Highlight points within radius %! if ! isempty (idx{i}) %! in_radius = X(idx{i}, :); %! scatter (in_radius(:,1), in_radius(:,2), 36, 'g', 'filled'); %! endif %! endfor %! hold off; %! title ('Points within Radius with ExhaustiveSearcher'); %! xlabel ('X1'); %! ylabel ('X2'); ## Test Cases %!test %! ## Basic constructor with default Euclidean %! X = [1, 2; 3, 4; 5, 6]; %! obj = ExhaustiveSearcher (X); %! assert_equal (obj.X, X) %! assert_equal (obj.Distance, "euclidean") %! assert_equal (isempty (obj.DistParameter), true) %!test %! ## Minkowski distance with custom P %! X = [1, 2; 3, 4]; %! obj = ExhaustiveSearcher (X, 'Distance', 'minkowski', 'P', 3); %! assert_equal (obj.Distance, "minkowski") %! assert_equal (obj.DistParameter, 3) %!test %! ## Seuclidean distance with custom Scale %! X = [1, 2; 3, 4; 5, 6]; %! S = [1, 2]; %! obj = ExhaustiveSearcher (X, 'Distance', 'seuclidean', 'Scale', S); %! assert_equal (obj.Distance, "seuclidean") %! assert_equal (obj.DistParameter, S) %!test %! ## Mahalanobis distance with custom Cov %! X = [1, 2; 3, 4; 5, 6]; %! C = [1, 0; 0, 1]; %! obj = ExhaustiveSearcher (X, 'Distance', 'mahalanobis', 'Cov', C); %! assert_equal (obj.Distance, "mahalanobis") %! assert_equal (obj.DistParameter, C) %!test %! ## knnsearch with Euclidean distance %! X = [1, 2; 3, 4; 5, 6]; %! obj = ExhaustiveSearcher (X); %! Y = [2, 3]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (idx, 1) %! assert_equal (D, sqrt (2), 1e-10) %!test %! ## knnsearch with Cityblock distance %! X = [0, 0; 1, 1; 2, 2]; %! obj = ExhaustiveSearcher (X, 'Distance', 'cityblock'); %! Y = [1, 0]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (idx, 1) %! assert_equal (D, 1, 1e-10) %!test %! ## knnsearch with Chebychev distance %! X = [1, 1; 2, 3; 4, 2]; %! obj = ExhaustiveSearcher (X, 'Distance', 'chebychev'); %! Y = [2, 2]; %! [idx, D] = knnsearch (obj, Y); %! assert_equal (idx, 1) %! assert_equal (D, 1, 1e-10) %!test %! ## knnsearch with Cosine distance %! X = [1, 0; 0, 1; 1, 1]; %! obj = ExhaustiveSearcher (X, 'Distance', 'cosine'); %! Y = [1, 0.5]; %! [idx, D] = knnsearch (obj, Y); %! assert_equal (idx, 3) %! assert_equal (D < 0.1, true) %!test %! ## knnsearch with Minkowski P=1 (Manhattan) %! X = [0, 0; 1, 0; 0, 1]; %! obj = ExhaustiveSearcher (X, 'Distance', 'minkowski', 'P', 1); %! Y = [0.5, 0.5]; %! [idx, D] = knnsearch (obj, Y, 'K', 2, 'IncludeTies', true); %! assert_equal (iscell (idx), true) %! assert_equal (idx{1}, [1, 2, 3]) %! assert_equal (D{1}, [1, 1, 1], 1e-10) %!test %! ## rangesearch with Seuclidean %! X = [1, 1; 2, 2; 3, 3]; %! S = [1, 1]; %! obj = ExhaustiveSearcher (X, 'Distance', 'seuclidean', 'Scale', S); %! Y = [0, 0]; %! [idx, D] = rangesearch (obj, Y, 2); %! assert_equal (idx{1}, [1]) %! assert_equal (D{1}, [sqrt(2)], 1e-10) %!test %! ## rangesearch with Mahalanobis %! X = [1, 1; 2, 2; 3, 3]; %! C = [1, 0; 0, 1]; %! obj = ExhaustiveSearcher (X, 'Distance', 'mahalanobis', 'Cov', C); %! Y = [0, 0]; %! [idx, D] = rangesearch (obj, Y, 3, 'SortIndices', false); %! assert_equal (idx{1}, [1, 2]) %! assert_equal (D{1}, [sqrt(2), sqrt(8)], 1e-10) %!test %! ## rangesearch with Hamming distance %! X = [0, 1; 1, 0; 1, 1]; %! obj = ExhaustiveSearcher (X, 'Distance', 'hamming'); %! Y = [0, 0]; %! [idx, D] = rangesearch (obj, Y, 0.5); %! assert_equal (idx{1}, [1, 2]) %! assert_equal (D{1}, [0.5, 0.5], 1e-10) %!test %! ## Custom distance function %! X = [1, 2; 3, 4]; %! custom_dist = @(x, y) sum (abs (x - y)); %! obj = ExhaustiveSearcher (X, 'Distance', custom_dist); %! Y = [2, 3]; %! [idx, D] = knnsearch (obj, Y); %! assert_equal (idx, 1) %! assert_equal (D, 2, 1e-10) %!test %! ## IncludeTies returns all tied neighbors %! X = [0; 1; 2]; %! obj = ExhaustiveSearcher (X); %! Y = 1; %! [idx, D] = knnsearch (obj, Y, 'K', 2, 'IncludeTies', true); %! assert_equal (idx{1}, [2, 1, 3]) %! assert_equal (D{1}, [0, 1, 1]) %!test %! ## Custom distance function with vectorized output %! X = [1, 2; 3, 4]; %! f = @(x, y) sum (abs (x - y), 2); %! obj = ExhaustiveSearcher (X, 'Distance', f); %! Y = [2, 3]; %! [idx, D] = knnsearch (obj, Y); %! assert_equal (idx, 1) %! assert_equal (D, 2) %!test %! ## Euclidean with high-dimensional data %! X = [1, 2, 3; 4, 5, 6; 7, 8, 9; 10, 11, 12]; %! obj = ExhaustiveSearcher (X); %! Y = [5, 6, 7]; %! [idx, D] = knnsearch (obj, Y); %! assert_equal (idx, 2) %! assert_equal (D, sqrt (3), 1e-10) %!test %! ## Minkowski P=3 with scaled data %! X = [0, 1; 2, 3; 4, 5] * 10; %! obj = ExhaustiveSearcher (X, 'Distance', 'minkowski', 'P', 3); %! Y = [20, 30]; %! [idx, D] = knnsearch (obj, Y); %! assert_equal (idx, 2) %! assert_equal (D, 0, 1e-10) %!test %! ## Seuclidean with custom scales on diverse data %! X = [1, 10; 2, 20; 3, 30]; %! S = [1, 5]; %! obj = ExhaustiveSearcher (X, 'Distance', 'seuclidean', 'Scale', S); %! Y = [1.5, 15]; %! [idx, D] = knnsearch (obj, Y); %! assert_equal (idx, 1) %! assert_equal (D, sqrt ((0.5/1)^2 + (5/5)^2), 1e-10) %!test %! ## Mahalanobis with correlated data %! X = [1, 1; 2, 1.5; 3, 2]; %! C = [1, 0.5; 0.5, 1]; %! obj = ExhaustiveSearcher (X, 'Distance', 'mahalanobis', 'Cov', C); %! Y = [2, 1.5]; %! [idx, D] = knnsearch (obj, Y); %! assert_equal (idx, 2) %! assert_equal (D, 0, 1e-10) %!test %! ## Cityblock with sparse data %! X = [0, 0, 1; 1, 0, 0; 0, 1, 0]; %! obj = ExhaustiveSearcher (X, 'Distance', 'cityblock'); %! Y = [0, 0, 0]; %! [idx, D] = rangesearch (obj, Y, 1); %! assert_equal (idx{1}, [1, 2, 3]) %! assert_equal (D{1}, [1, 1, 1], 1e-10) %!test %! ## Chebychev with extreme values %! X = [0, 100; 50, 50; 100, 0]; %! obj = ExhaustiveSearcher (X, 'Distance', 'chebychev'); %! Y = [60, 60]; %! [idx, D] = knnsearch (obj, Y); %! assert_equal (idx, 2) %! assert_equal (D, 10, 1e-10) %!test %! ## Cosine with normalized data %! X = [1, 0; 0, 1; 1/sqrt(2), 1/sqrt(2)]; %! obj = ExhaustiveSearcher (X, 'Distance', 'cosine'); %! Y = [1, 1]; %! [idx, D] = knnsearch (obj, Y); %! assert_equal (idx, 3) %! assert_equal (D < 0.1, true) %!test %! ## Correlation with time-series-like data %! X = [1, 2, 3; 2, 4, 6; 1, 1, 1]; %! obj = ExhaustiveSearcher (X, 'Distance', 'correlation'); %! Y = [1.5, 3, 4.5]; %! [idx, D] = knnsearch (obj, Y); %! assert_equal (idx, 1) %! assert_equal (D < 0.1, true) %!test %! ## Spearman with ranked data %! X = [1, 2, 3; 3, 2, 1; 2, 1, 3]; %! obj = ExhaustiveSearcher (X, 'Distance', 'spearman'); %! Y = [1, 2, 3]; %! [idx, D] = knnsearch (obj, Y); %! assert_equal (idx, 1) %! assert_equal (D, 0, 1e-10) %!test %! ## Jaccard with binary sparse data %! X = [1, 0, 0; 0, 1, 0; 1, 1, 0]; %! obj = ExhaustiveSearcher (X, 'Distance', 'jaccard'); %! Y = [1, 0, 0]; %! [idx, D] = knnsearch (obj, Y); %! assert_equal (idx, 1) %! assert_equal (D, 0, 1e-10) %!test %! obj = ExhaustiveSearcher (ones (3,2)); %! assert_equal (obj.X, ones (3,2)) %! assert_equal (obj.Distance, "euclidean") %! assert_equal (isempty (obj.DistParameter), true) %!test %! obj = ExhaustiveSearcher (ones (3,2)); %! obj.Distance = 'minkowski'; %! assert_equal (obj.Distance, "minkowski") %!test %! obj = ExhaustiveSearcher (ones (3,2), 'Distance', 'minkowski'); %! obj.DistParameter = 3; %! assert_equal (obj.DistParameter, 3) %!test %! obj = ExhaustiveSearcher (ones (3,2), 'Distance', 'seuclidean'); %! obj.DistParameter = [1, 2]; %! assert_equal (obj.DistParameter, [1, 2]) %!test %! obj = ExhaustiveSearcher (ones (3,2), 'Distance', 'mahalanobis'); %! obj.DistParameter = eye (2); %! assert_equal (obj.DistParameter, eye (2)) ## Test Input Validation %!test %! ## A metric given per call overrides the searcher's own for that call, %! ## matching a searcher built with it, and leaves the object unchanged. %! X = [1, 1; 2, 2; 3, 3; 4, 4; 5, 5; 1, 5; 5, 1]; Y = [2, 2; 4, 4]; %! o = ExhaustiveSearcher (X); %! assert_equal (knnsearch (o, Y, 'K', 3, 'Distance', 'cityblock'), ... %! knnsearch (ExhaustiveSearcher (X, 'Distance', 'cityblock'), ... %! Y, 'K', 3)); %! assert_equal (rangesearch (o, Y, 3, 'Distance', 'cityblock'), ... %! rangesearch (ExhaustiveSearcher (X, 'Distance', ... %! 'cityblock'), Y, 3)); %! assert_equal (knnsearch (o, Y, 'K', 2, 'Distance', 'minkowski', 'P', 3), ... %! knnsearch (ExhaustiveSearcher (X, 'Distance', 'minkowski', ... %! 'P', 3), Y, 'K', 2)); %! ## the searcher keeps its own metric %! assert_equal (o.Distance, 'euclidean'); %! assert_equal (isempty (o.DistParameter), true); %!error ... %! knnsearch (ExhaustiveSearcher ([1, 1; 2, 2]), [1, 1], 'K', 1, ... %! 'Distance', 'cityblock', 'P', 3) %!error ... %! knnsearch (ExhaustiveSearcher ([1, 1; 2, 2]), [1, 1], 'K', 1, ... %! 'Distance', 'bogus') %!error ... %! ExhaustiveSearcher () %!error ... %! ExhaustiveSearcher (ones (3,2), 'Distance') %!error ... %! ExhaustiveSearcher ('abc') %!error ... %! ExhaustiveSearcher ([1; Inf; 3]) %!error ... %! ExhaustiveSearcher (ones (3,2), 'foo', 'bar') %!error ... %! ExhaustiveSearcher (ones (3,2), 'Distance', 'invalid') %!error ... %! ExhaustiveSearcher (ones (3,2), 'Distance', @(x) x) %!error ... %! ExhaustiveSearcher (ones (3,2), 'Distance', 1) %!error ... %! ExhaustiveSearcher (ones (3,2), 'Distance', 'minkowski', 'P', -1) %!error ... %! ExhaustiveSearcher (ones (3,2), 'Distance', 'seuclidean', 'Scale', [-1, 1]) %!error ... %! ExhaustiveSearcher (ones (3,2), 'Distance', 'mahalanobis', 'Cov', ones (3,3)) %!error ... %! ExhaustiveSearcher (ones (3,2), 'Distance', 'mahalanobis', 'Cov', -eye (2)) %!error ... %! knnsearch (ExhaustiveSearcher (ones (3,2))) %!error ... %! knnsearch (ExhaustiveSearcher (ones (3,2)), ones (3,2), 'IncludeTies') %!error ... %! knnsearch (ExhaustiveSearcher (ones (3,2)), 'abc') %!error ... %! knnsearch (ExhaustiveSearcher (ones (3,2)), ones (3,3)) %!error ... %! knnsearch (ExhaustiveSearcher (ones (3,2)), ones (3,2), 'K', 0) %!error ... %! knnsearch (ExhaustiveSearcher (ones (3,2)), ones (3,2), 'foo', 'bar') %!error ... %! knnsearch (ExhaustiveSearcher (ones (3,2)), ones (3,2), 'IncludeTies', 1) %!error ... %! rangesearch (ExhaustiveSearcher (ones (3,2))) %!error ... %! rangesearch (ExhaustiveSearcher (ones (3,2)), ones (3,2), 1, 'SortIndices') %!error ... %! rangesearch (ExhaustiveSearcher (ones (3,2)), 'abc', 1) %!error ... %! rangesearch (ExhaustiveSearcher (ones (3,2)), ones (3,3), 1) %!error ... %! rangesearch (ExhaustiveSearcher (ones (3,2)), ones (3,2), -1) %!error ... %! rangesearch (ExhaustiveSearcher (ones (3,2)), ones (3,2), 1, 'foo', 'bar') %!error ... %! rangesearch (ExhaustiveSearcher (ones (3,2)), ones (3,2), 1, 'SortIndices', 1) %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj(1) %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj{1} %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj.(1) %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj.invalid %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj(1) = 1 %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj{1} = 1 %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj.X.Y = 1 %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj.(1) = 1 %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj.X = 1 %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj.Distance = 'invalid' %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj.Distance = @(x) x %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj.Distance = @(x, y) [1; 1] %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj.Distance = 1 %!error ... %! obj = ExhaustiveSearcher (ones (3,2), 'Distance', 'minkowski'); obj.DistParameter = -1 %!error ... %! obj = ExhaustiveSearcher (ones (3,2), 'Distance', 'seuclidean'); obj.DistParameter = [-1, 1] %!error ... %! obj = ExhaustiveSearcher (ones (3,2), 'Distance', 'mahalanobis'); obj.DistParameter = ones (3,3) %!error ... %! obj = ExhaustiveSearcher (ones (3,2), 'Distance', 'mahalanobis'); obj.DistParameter = -eye (2) %!error ... %! obj = ExhaustiveSearcher (ones (3,2), 'Distance', 'euclidean'); obj.DistParameter = 1 %!error ... %! obj = ExhaustiveSearcher (ones (3,2)); obj.invalid = 1 ## More neighbours than there are points is answered with all of them, where ## indexing the sorted list past its end used to raise an out-of-bound error. %!test %! randn ("seed", 4); %! X = randn (20, 2); Y = randn (5, 2); %! ex = ExhaustiveSearcher (X); %! for K = [20, 21, 100] %! [idx, D] = knnsearch (ex, Y, "K", K); %! assert_equal (size (idx), [5, 20]); %! assert_equal (size (D), [5, 20]); %! assert_equal (any (isnan (idx(:))), false); %! assert_equal (all (diff (D, 1, 2)(:) >= -1e-12), true); %! endfor ## Single precision is carried through to the distances, every other class is ## converted up to double, and the indices are always double. %!test %! X = [1, 2; 3, 4; 5, 6; 7, 8]; Y = [2, 3; 6, 7]; %! obj = ExhaustiveSearcher (single (X)); %! assert_equal (class (obj.X), 'single'); %! [idx, D] = knnsearch (obj, single (Y), "K", 2); %! assert_equal (class (idx), 'double'); %! assert_equal (class (D), 'single'); %! ## a double query against single data is still computed in single %! [~, Dm] = knnsearch (obj, Y, "K", 2); %! assert_equal (class (Dm), 'single'); %! assert_equal (Dm, D); %! ## and so is a single query against double data %! [~, Ds] = knnsearch (ExhaustiveSearcher (X), single (Y), "K", 2); %! assert_equal (class (Ds), 'single'); %! ## double throughout stays double %! [~, Dd] = knnsearch (ExhaustiveSearcher (X), Y, "K", 2); %! assert_equal (class (Dd), 'double'); ## Integer data is converted to double on the way in. Held as int32 the ## coordinate differences were rounded and the distances came out wrong. %!test %! X = int32 ([10, 20; 33, 41; 55, 62]); Y = [21, 33]; %! obj = ExhaustiveSearcher (X); %! assert_equal (class (obj.X), 'double'); %! [~, D] = knnsearch (obj, Y, "K", 1); %! assert_equal (D, min (sqrt (sum ((double (X) - Y) .^ 2, 2))), 1e-12); ## rangesearch follows the same rule: single distances, double indices. %!test %! X = [1, 2; 3, 4; 5, 6; 7, 8]; Y = [2, 3]; %! [idx, D] = rangesearch (ExhaustiveSearcher (single (X)), single (Y), 4); %! assert_equal (class (idx{1}), 'double'); %! assert_equal (class (D{1}), 'single'); statistics-release-1.9.2/inst/Nearest_Neighbors/KDTreeSearcher.m000066400000000000000000001545121524624707500247060ustar00rootroot00000000000000## Copyright (C) 2025 Swayam Shah ## Copyright (C) 2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef KDTreeSearcher ## -*- texinfo -*- ## @deftp {statistics} KDTreeSearcher ## ## KD-tree nearest neighbor searcher ## ## The @code{KDTreeSearcher} class implements a KD-tree search algorithm for ## nearest neighbor queries. It stores training data and supports various ## distance metrics along with their parameter values for performing a KD-tree ## search. The KD-tree algorithm partitions the training data into a ## hierarchical tree structure and performs search operations by traversing ## the tree to reduce the number of distance computations. It facilitates ## nearest neighbor queries using @code{knnsearch} and radius queries using ## @code{rangesearch}. ## ## You can either use the @code{KDTreeSearcher} class constructor or the ## @code{createns} function to create an @qcode{KDTreeSearcher} object. ## ## @seealso{createns, ExhaustiveSearcher, hnswSearcher, knnsearch, ## rangesearch} ## @end deftp properties(SetAccess = private, Hidden) KDTree # KD-tree structure endproperties properties(SetAccess = private) ## -*- texinfo -*- ## @deftp {KDTreeSearcher} {property} X ## ## Point data ## ## Point data, specified as an @math{N*P} numeric matrix where each row is ## an observation and each column is a feature. This property is private ## and cannot be modified after object creation. ## ## Data of class @qcode{single} is stored and searched in single ## precision, any other numeric class is converted to @qcode{double}. ## ## @end deftp X = [] ## -*- texinfo -*- ## @deftp {KDTreeSearcher} {property} BucketSize ## ## Maximum number of data points in each leaf node ## ## The maximum number of data points in the leaf node of the KD-tree. ## Default value is 50. This property is private and cannot be modified ## after object creation. ## ## @end deftp BucketSize = 50 endproperties properties ## -*- texinfo -*- ## @deftp {KDTreeSearcher} {property} Distance ## ## Distance metric ## ## Distance metric used for searches, specified as a character vector. ## Supported metrics are @qcode{'euclidean'}, @qcode{'cityblock'}, ## @qcode{'minkowski'}, and @qcode{'chebychev'}. Default value is ## @qcode{'euclidean'}. ## ## @end deftp Distance = 'euclidean' ## -*- texinfo -*- ## @deftp {KDTreeSearcher} {property} DistParameter ## ## Distance parameter ## ## The type and value of the distance parameter depends on the selected ## @qcode{Distance} metric and can be any of the following: ## ## @itemize ## @item For @qcode{'minkowski'}, a positive scalar exponent (default 2). ## @item Empty for other metrics (@qcode{'euclidean'}, @qcode{'cityblock'}, ## @qcode{'chebychev'}). Attempting to set a non-empty value for these ## metrics will result in an error. ## @end itemize ## ## @end deftp DistParameter = [] endproperties methods (Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) if (isscalar (this)) fprintf ("\n KDTreeSearcher with properties:\n\n"); fprintf ("%+25s: %d\n", 'BucketSize', this.BucketSize); fprintf ("%+25s: '%s'\n", 'Distance', this.Distance); if (! isempty (this.DistParameter)) if (isscalar (this.DistParameter)) fprintf ("%+25s: %g\n", 'DistParameter', this.DistParameter); elseif (isvector (this.DistParameter)) fprintf ("%+25s: %s\n", 'DistParameter', ... mat2str (this.DistParameter)); else fprintf ("%+25s: [%dx%d %s]\n", 'DistParameter', ... size (this.DistParameter), class (this.DistParameter)); endif else fprintf ("%+25s: []\n", 'DistParameter'); endif fprintf ("%+25s: [%dx%d %s]\n", 'X', size (this.X), class (this.X)); endif endfunction ## Class specific subscripted reference function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' error ("KDTreeSearcher.subsref: () indexing not supported."); case '{}' error ("KDTreeSearcher.subsref: {} indexing not supported."); case '.' if (! ischar (s.subs)) error (strcat ("KDTreeSearcher.subsref: property", ... " name must be a character vector.")); endif try out = this.(s.subs); catch error (strcat ("KDTreeSearcher.subsref: unrecognized", ... " property: '%s'."), s.subs); end_try_catch endswitch ## Chained references if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction ## Class specific subscripted assignment function this = subsasgn (this, s, val) if (numel (s) > 1) error ("KDTreeSearcher.subsasgn: chained subscripts not allowed."); endif switch s.type case '()' error ("KDTreeSearcher.subsasgn: () indexing not supported."); case '{}' error ("KDTreeSearcher.subsasgn: {} indexing not supported."); case '.' if (! ischar (s.subs)) error (strcat ("KDTreeSearcher.subsasgn: property", ... " name must be a character vector.")); endif switch (s.subs) case 'X' error (strcat ("KDTreeSearcher.subsasgn: 'X' is", ... " read-only and cannot be modified.")); case 'KDTree' error (strcat ("KDTreeSearcher.subsasgn: 'KDTree' is", ... " read-only and cannot be modified.")); case 'BucketSize' error (strcat ("KDTreeSearcher.subsasgn: 'BucketSize'", ... " is read-only and cannot be modified.")); case 'Distance' allowed_distances = {'euclidean', 'cityblock', 'minkowski', ... 'chebychev'}; if (ischar (val)) if (! any (strcmpi (allowed_distances, val))) error (strcat ("KDTreeSearcher.subsasgn:", ... " unsupported distance metric '%s'."), val); endif this.Distance = val; else error (strcat ("KDTreeSearcher.subsasgn: 'Distance'", ... " must be a string.")); endif case 'DistParameter' if (strcmpi (this.Distance, 'minkowski')) if (! (isscalar (val) && isnumeric (val) && val > 0 && isfinite (val))) error (strcat ("KDTreeSearcher.subsasgn: 'DistParameter'", ... " must be a positive finite scalar for", ... " Minkowski distance.")); endif this.DistParameter = val; else if (! isempty (val)) error (strcat ("KDTreeSearcher.subsasgn: 'DistParameter'", ... " must be empty for this distance metric.")); endif this.DistParameter = val; endif otherwise error ("KDTreeSearcher.subsasgn: unrecognized property: '%s'.",... s.subs); endswitch endswitch endfunction endmethods methods ## -*- texinfo -*- ## @deftypefn {KDTreeSearcher} {@var{obj} =} KDTreeSearcher (@var{X}) ## @deftypefnx {KDTreeSearcher} {@var{obj} =} KDTreeSearcher (@var{X}, @var{name}, @var{value}) ## ## Create a @qcode{KDTreeSearcher} object for nearest neighbor searches. ## ## @code{@var{obj} = KDTreeSearcher (@var{X})} constructs a ## @qcode{KDTreeSearcher} object with training data @var{X} using the ## default @qcode{'euclidean'} distance metric. @var{X} must be an ## @math{N*P} numeric matrix, where rows represent observations and columns ## represent features. ## ## @code{@var{obj} = KDTreeSearcher (@var{X}, @var{name}, @var{value})} ## allows customization through name-value pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Distance'} @tab Distance metric, specified as a ## character vector (@qcode{'euclidean'}, @qcode{'cityblock'}, ## @qcode{'minkowski'}, @qcode{'chebychev'}). Default is ## @qcode{'euclidean'}. ## ## @item @qcode{'P'} @tab Minkowski distance exponent, a positive ## scalar. Valid only when @qcode{'Distance'} is @qcode{'minkowski'}. ## Default is 2. ## ## @item @qcode{'BucketSize'} @tab Maximum number of data points in the ## leaf node of the KD-tree, a positive integer. Default is 50. ## @end multitable ## ## You can also create a @qcode{KDTreeSearcher} object using the ## @code{createns} function. ## ## @seealso{KDTreeSearcher, knnsearch, rangesearch, createns} ## ## @qcode{'Distance'} and @qcode{'P'} override the searcher's own metric for ## that call only; the @qcode{Distance} and @qcode{DistParameter} ## properties keep their values. The tree is built from the data alone, so ## changing the metric does not rebuild it. @qcode{'Cov'} and ## @qcode{'Scale'} are not accepted, since they belong to metrics a kd-tree ## cannot search. ## @end deftypefn function obj = KDTreeSearcher (X, varargin) if (nargin < 1) error ("KDTreeSearcher: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error ("KDTreeSearcher: Name-Value arguments must be in pairs."); endif if (! (isnumeric (X) && ismatrix (X) && all (isfinite (X)(:)))) error ("KDTreeSearcher: X must be a finite numeric matrix."); endif ## Single precision is carried through, but every other class is ## converted up to double, as MATLAB does. Integer data would ## otherwise round each coordinate difference and corrupt distances. if (! isa (X, "single")) X = double (X); endif obj.X = X; ## Default values Distance = 'euclidean'; P = 2; BucketSize = 50; ## Parse optional parameters while (numel (varargin) > 0) switch (lower (varargin{1})) case 'distance' Distance = varargin{2}; case 'p' P = varargin{2}; case 'bucketsize' BucketSize = varargin{2}; otherwise error (strcat ("KDTreeSearcher: invalid parameter", ... " name: '%s'."), varargin{1}); endswitch varargin(1:2) = []; endwhile ## Validate Distance allowed_distances = {'euclidean', 'cityblock', 'minkowski', 'chebychev'}; if (ischar (Distance)) if (! any (strcmpi (allowed_distances, Distance))) error ("KDTreeSearcher: unsupported distance metric '%s'.", Distance); endif obj.Distance = Distance; else error ("KDTreeSearcher: Distance must be a string."); endif ## Set DistParameter if (strcmpi (obj.Distance, 'minkowski')) if (! (isscalar (P) && isnumeric (P) && P > 0 && isfinite (P))) error ("KDTreeSearcher: P must be a positive finite scalar."); endif obj.DistParameter = P; else obj.DistParameter = []; endif ## Set BucketSize if (! (isscalar (BucketSize) && isnumeric (BucketSize) && BucketSize > 0 && BucketSize == fix (BucketSize))) error ("KDTreeSearcher: BucketSize must be a positive integer."); endif obj.BucketSize = BucketSize; ## Build KDTree obj.KDTree = __build_kdtree__ (1:size (X,1), 0, X, BucketSize); endfunction ## -*- texinfo -*- ## @deftypefn {KDTreeSearcher} {[@var{idx}, @var{D}] =} knnsearch (@var{obj}, @var{Y}) ## @deftypefnx {KDTreeSearcher} {[@var{idx}, @var{D}] =} knnsearch (@var{obj}, @var{Y}, @var{name}, @var{value}) ## ## Find the @math{K} nearest neighbors in the training data to query points. ## ## @code{[@var{idx}, @var{D}] = knnsearch (@var{obj}, @var{Y}, @var{K})} ## returns the indices @var{idx} and distances @var{D} of the @math{K} ## nearest neighbors in @var{obj.X} to each point in @var{Y}, using the ## distance metric specified in @var{obj.Distance}. ## ## @itemize ## @item @var{obj} is a @qcode{KDTreeSearcher} object. ## @item @var{Y} is an @math{M*P} numeric matrix of query points, where ## @math{P} must match the number of columns in @var{obj.X}. ## @item @var{idx} contains the indices of the nearest neighbors in ## @var{obj.X}. ## @item @var{D} contains the corresponding distances. ## @end itemize ## ## @var{idx} is always of class @qcode{double}. @var{D} is of class ## @qcode{single} when either @var{obj.X} or @var{Y} is @qcode{single}, ## in which case the distances are computed in single precision, and of ## class @qcode{double} otherwise. ## ## @code{[@var{idx}, @var{D}] = knnsearch (@var{obj}, @var{Y}, @var{name}, ## @var{value})} allows additional options via name-value pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'K'} @tab A positive integer specifying the number of ## nearest neighbors to find. Default is 1. A value larger than the ## number of observations in the training data is answered with all of ## them, since there are no more neighbors to return. ## ## @item @qcode{'IncludeTies'} @tab Logical flag indicating whether to ## include all neighbors tied with the @math{K}th smallest distance. Default ## is @qcode{false}. If @qcode{true}, @var{idx} and @var{D} are cell arrays. ## ## @item @qcode{'SortIndices'} @tab Logical flag indicating whether to ## sort the indices by distance. Default is @qcode{true}. ## @end multitable ## ## @seealso{KDTreeSearcher, rangesearch} ## @end deftypefn function [idx, D] = knnsearch (obj, Y, varargin) ## Initial input validation if (nargin < 2) error ("KDTreeSearcher.knnsearch: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("KDTreeSearcher.knnsearch:", ... " Name-Value arguments must be in pairs.")); endif ## Get training data size [N, C] = size (obj.X); ## Validate Y if (isempty (Y)) error ("KDTreeSearcher.knnsearch: Y cannot be empty."); endif if (! (isnumeric (Y) && ismatrix (Y) && all (isfinite (Y)(:)))) error ("KDTreeSearcher.knnsearch: Y must be a finite numeric matrix."); endif if (C != size (Y, 2)) error (strcat ("KDTreeSearcher.knnsearch: Y must have the same", ... " number of columns as the training data in OBJ.X.")); endif ## Default values K = 1; ## Parse options IncludeTies = false; SortIndices = true; Dist = []; Pval = []; while (numel (varargin) > 0) switch (lower (varargin{1})) case 'k' K = varargin{2}; if (! (isscalar (K) && isnumeric (K) && K >= 1 && K == fix (K) && isfinite (K))) error (strcat ("KDTreeSearcher.knnsearch: 'K' must", ... " be a positive integer.")); endif case 'includeties' IncludeTies = varargin{2}; if (! (islogical (IncludeTies) && isscalar (IncludeTies))) error (strcat ("KDTreeSearcher.knnsearch:", ... " IncludeTies must be a logical scalar.")); endif case 'sortindices' SortIndices = varargin{2}; if (! (islogical (SortIndices) && isscalar (SortIndices))) error (strcat ("KDTreeSearcher.knnsearch:", ... " SortIndices must be a logical scalar.")); endif case 'distance' Dist = varargin{2}; case 'p' Pval = varargin{2}; otherwise error (strcat ("KDTreeSearcher.knnsearch: invalid", ... " parameter name: '%s'."), varargin{1}); endswitch varargin(1:2) = []; endwhile ## A metric given here applies to this call only. The tree is ## built from the data alone, so changing the metric does not ## invalidate it; only the pruning bound changes. 'Cov' and ## 'Scale' are not accepted: they belong to metrics a kd-tree ## cannot search. Distance = obj.Distance; DistParameter = obj.DistParameter; if (! (isempty (Dist) && isempty (Pval))) if (isempty (Dist)) Dist = Distance; endif [Distance, DistParameter] = __resolve_metric__ ( ... "KDTreeSearcher.knnsearch", obj.X, Dist, ... Pval, [], [], {"euclidean", "cityblock", "chebychev", "minkowski"}); endif ## There are only as many points to return as the training data holds, ## so a larger K is answered with all of them. Building the result at ## the requested width raised an internal nonconformance instead. K = min (K, rows (obj.X)); ## Distances are computed in single precision when either the training ## data or the query is single, and in double otherwise. The indices ## are always double. cls = "double"; if (isa (obj.X, "single") || isa (Y, "single")) cls = "single"; endif Y = cast (Y, cls); if (IncludeTies) idx = cell (rows (Y), 1); D = cell (rows (Y), 1); for i = 1:rows (Y) [temp_idx, temp_D] = __search_kdtree__ (obj.KDTree, Y(i,:), K, obj.X, ... Distance, DistParameter, ... false); r = temp_D(end) + 1e-10; # Add small epsilon to capture ties [idx{i}, D{i}] = __search_kdtree__ (obj.KDTree, Y(i,:), Inf, obj.X, ... Distance, DistParameter, ... true, r); if (SortIndices) iv = idx{i}(:); [sorted_D, sort_idx] = sortrows ([D{i}(:), iv]); D{i} = sorted_D(:, 1); idx{i} = iv(sort_idx); endif ## One row per query point, as MATLAB and ExhaustiveSearcher return idx{i} = idx{i}(:)'; D{i} = D{i}(:)'; endfor else idx = zeros (rows (Y), K); D = zeros (rows (Y), K, cls); for i = 1:rows (Y) [temp_idx, temp_D] = __search_kdtree__ (obj.KDTree, Y(i,:), K, obj.X, ... Distance, DistParameter, ... false); if (SortIndices) [sorted_D, sort_idx] = sortrows ([temp_D, temp_idx]); idx(i,:) = temp_idx(sort_idx); D(i,:) = sorted_D(:,1)'; else idx(i,:) = temp_idx; D(i,:) = temp_D; endif endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {KDTreeSearcher} {[@var{idx}, @var{D}] =} rangesearch (@var{obj}, @var{Y}, @var{r}) ## @deftypefnx {KDTreeSearcher} {[@var{idx}, @var{D}] =} rangesearch (@var{obj}, @var{Y}, @var{r}, @var{name}, @var{value}) ## ## Find all neighbors within a specified radius of query points. ## ## @code{[@var{idx}, @var{D}] = rangesearch (@var{obj}, @var{Y}, @var{r})} ## returns the indices @var{idx} and distances @var{D} of all points in ## @var{obj.X} within radius @var{r} of each point in @var{Y}, using the ## distance metric specified in @var{obj.Distance}. ## ## @itemize ## @item @var{obj} is a @qcode{KDTreeSearcher} object. ## @item @var{Y} is an @math{M*P} numeric matrix of query points, where ## @math{P} must match the number of columns in @var{obj.X}. ## @item @var{r} is a nonnegative scalar specifying the search radius. ## @end itemize ## ## @var{idx} is always of class @qcode{double}. @var{D} is of class ## @qcode{single} when either @var{obj.X} or @var{Y} is @qcode{single}, ## in which case the distances are computed in single precision, and of ## class @qcode{double} otherwise. ## ## @code{[@var{idx}, @var{D}] = rangesearch (@var{obj}, @var{Y}, @var{r}, ## @var{name}, ## @var{value})} ## allows additional options via name-value pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'SortIndices'} @tab Logical flag indicating whether to ## sort the indices by distance. Default is @qcode{true}. ## @end multitable ## ## @var{idx} and @var{D} are cell arrays where each cell contains the ## indices and distances for one query point in @var{Y}. ## ## @seealso{KDTreeSearcher, knnsearch} ## @end deftypefn function [idx, D] = rangesearch (obj, Y, r, varargin) if (nargin < 3) error ("KDTreeSearcher.rangesearch: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("KDTreeSearcher.rangesearch:", ... " Name-Value arguments must be in pairs.")); endif Dist = []; Pval = []; if (! (isnumeric (Y) && ismatrix (Y) && all (isfinite (Y)(:)))) error (strcat ("KDTreeSearcher.rangesearch:", ... " Y must be a finite numeric matrix.")); endif if (size (obj.X, 2) != size (Y, 2)) error (strcat ("KDTreeSearcher.rangesearch:", ... " number of columns in X and Y must match.")); endif if (! (isscalar (r) && isnumeric (r) && r >= 0 && isfinite (r))) error (strcat ("KDTreeSearcher.rangesearch:", ... " R must be a nonnegative finite scalar.")); endif ## Parse options SortIndices = true; while (numel (varargin) > 0) switch (lower (varargin{1})) case 'sortindices' SortIndices = varargin{2}; if (! (islogical (SortIndices) && isscalar (SortIndices))) error (strcat ("KDTreeSearcher.rangesearch:", ... " SortIndices must be a logical scalar.")); endif case 'distance' Dist = varargin{2}; case 'p' Pval = varargin{2}; otherwise error (strcat ("KDTreeSearcher.rangesearch:", ... " invalid parameter name: '%s'."), varargin{1}); endswitch varargin(1:2) = []; endwhile ## A metric given here applies to this call only. The tree is ## built from the data alone, so changing the metric does not ## invalidate it; only the pruning bound changes. 'Cov' and ## 'Scale' are not accepted: they belong to metrics a kd-tree ## cannot search. Distance = obj.Distance; DistParameter = obj.DistParameter; if (! (isempty (Dist) && isempty (Pval))) if (isempty (Dist)) Dist = Distance; endif [Distance, DistParameter] = __resolve_metric__ ( ... "KDTreeSearcher.rangesearch", obj.X, Dist, ... Pval, [], [], {"euclidean", "cityblock", "chebychev", "minkowski"}); endif ## Distances are computed in single precision when either the training ## data or the query is single, and in double otherwise. The indices ## are always double. cls = "double"; if (isa (obj.X, "single") || isa (Y, "single")) cls = "single"; endif Y = cast (Y, cls); idx = cell (rows (Y), 1); D = cell (rows (Y), 1); for i = 1:rows (Y) [idx{i}, D{i}] = __search_kdtree__ (obj.KDTree, Y(i,:), Inf, obj.X, ... Distance, DistParameter, ... true, r); if (SortIndices) iv = idx{i}(:); [sorted_D, sort_idx] = sortrows ([D{i}(:), iv]); D{i} = sorted_D(:, 1); idx{i} = iv(sort_idx); endif ## One row per query point, as MATLAB and ExhaustiveSearcher return idx{i} = idx{i}(:)'; D{i} = D{i}(:)'; endfor endfunction endmethods endclassdef ## Private functions: ## Demo Examples %!demo %! ## Demo to verify implementation using fisheriris dataset %! load fisheriris %! numSamples = size (meas, 1); %! queryIndices = [1, 23, 46, 63, 109]; %! dataIndices = ! ismember (1:numSamples, queryIndices); %! queryPoints = meas(queryIndices, :); %! dataPoints = meas(dataIndices, :); %! searchRadius = 0.3; %! kdTree = KDTreeSearcher (dataPoints, 'Distance', 'minkowski') %! nearestNeighbors = knnsearch (kdTree, queryPoints, 'K', 2) %! neighborsInRange = rangesearch (kdTree, queryPoints, searchRadius) %!demo %! ## Create a KDTreeSearcher with Euclidean distance %! X = [1, 2; 3, 4; 5, 6]; %! obj = KDTreeSearcher (X); %! ## Find the nearest neighbor to [2, 3] %! Y = [2, 3]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! disp ('Nearest neighbor index:'); %! disp (idx); %! disp ('Distance:'); %! disp (D); %! ## Find all points within radius 2 %! [idx, D] = rangesearch (obj, Y, 2); %! disp ('Indices within radius:'); %! disp (idx); %! disp ('Distances:'); %! disp (D); %!demo %! ## Create a KDTreeSearcher with Minkowski distance (P=3) %! X = [0, 0; 1, 0; 2, 0]; %! obj = KDTreeSearcher (X, 'Distance', 'minkowski', 'P', 3); %! ## Find the nearest neighbor to [1, 0] %! Y = [1, 0]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! disp ('Nearest neighbor index:'); %! disp (idx); %! disp ('Distance:'); %! disp (D); %!demo %! rng (42); %! disp ('Demonstrating KDTreeSearcher'); %! %! n = 100; %! mu1 = [0.3, 0.3]; %! mu2 = [0.7, 0.7]; %! sigma = 0.1; %! X1 = mu1 + sigma * randn (n / 2, 2); %! X2 = mu2 + sigma * randn (n / 2, 2); %! X = [X1; X2]; %! %! obj = KDTreeSearcher (X); %! %! Y = [0.3, 0.3; 0.7, 0.7; 0.5, 0.5]; %! %! K = 5; %! [idx, D] = knnsearch (obj, Y, 'K', K); %! %! disp ('For the first query point:'); %! disp (['Query point: ', num2str(Y(1,:))]); %! disp ('Indices of nearest neighbors:'); %! disp (idx(1,:)); %! disp ('Distances:'); %! disp (D(1,:)); %! %! figure; %! scatter (X(:,1), X(:,2), 36, 'b', 'filled'); # Training points %! hold on; %! scatter (Y(:,1), Y(:,2), 36, 'r', 'filled'); # Query points %! for i = 1:size (Y, 1) %! query = Y(i,:); %! neighbors = X(idx(i,:), :); %! for j = 1:K %! plot ([query(1), neighbors(j,1)], [query(2), neighbors(j,2)], 'k-'); %! endfor %! endfor %! hold off; %! title ('K Nearest Neighbors with KDTreeSearcher'); %! xlabel ('X1'); %! ylabel ('X2'); %! %! r = 0.15; %! [idx, D] = rangesearch (obj, Y, r); %! %! disp ('For the first query point in rangesearch:'); %! disp (['Query point: ', num2str(Y(1,:))]); %! disp ('Indices of points within radius:'); %! disp (idx{1}); %! disp ('Distances:'); %! disp (D{1}); %! %! figure; %! scatter (X(:,1), X(:,2), 36, 'b', 'filled'); %! hold on; %! scatter (Y(:,1), Y(:,2), 36, 'r', 'filled'); %! theta = linspace (0, 2 * pi, 100); %! for i = 1:size (Y, 1) %! center = Y(i,:); %! x_circle = center(1) + r * cos (theta); %! y_circle = center(2) + r * sin (theta); %! plot (x_circle, y_circle, 'g-'); %! ## Highlight points within radius %! if (! isempty (idx{i})) %! in_radius = X(idx{i}, :); %! scatter (in_radius(:,1), in_radius(:,2), 36, 'g', 'filled'); %! endif %! endfor %! hold off %! title ('Points within Radius with KDTreeSearcher'); %! xlabel ('X1'); %! ylabel ('X2'); ## Test Cases %!test %! load fisheriris %! X = meas; %! obj = KDTreeSearcher (X); %! Y = X(1:5,:); %! [idx, D] = knnsearch (obj, Y, 'K', 3); %! assert_equal (idx, [[1, 18, 5]; [2, 35, 46]; [3, 48, 4]; [4, 48, 30]; [5, 38, 1]]) %! assert_equal (D, [[0, 0.1000, 0.1414]; [0, 0.1414, 0.1414]; [0, 0.1414, 0.2449]; %! [0, 0.1414, 0.1732]; [0, 0.1414, 0.1414]], 5e-5) %!test %! load fisheriris %! X = meas; %! obj = KDTreeSearcher (X, 'Distance', 'minkowski', 'P', 3); %! Y = X(10:15,:); %! [idx, D] = knnsearch (obj, Y, 'K', 2); %! assert_equal (idx, [[10, 35]; [11, 49]; [12, 30]; [13, 2]; [14, 39]; [15, 34]]) %! assert_equal (D, [[0, 0.1000]; [0, 0.1000]; [0, 0.2080]; [0, 0.1260]; [0, 0.2154]; %! [0, 0.3503]], 5e-5) %!test %! load fisheriris %! X = meas; %! obj = KDTreeSearcher (X, 'Distance', 'cityblock'); %! Y = X(20:25,:); %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (idx, [20; 21; 22; 23; 24; 25]) %! assert_equal (D, [0; 0; 0; 0; 0; 0]) %!test %! load fisheriris %! X = meas; %! obj = KDTreeSearcher (X, 'Distance', 'chebychev'); %! Y = X(30:35,:); %! [idx, D] = knnsearch (obj, Y, 'K', 4); %! assert_equal (idx, [[30, 31, 4, 12]; [31, 30, 10, 35]; [32, 21, 37, 28]; %! [33, 20, 34, 47]; [34, 16, 15, 33]; [35, 10, 2, 26]]) %! assert_equal (D, [[0, 0.1000, 0.1000, 0.2000]; [0, 0.1000, 0.1000, 0.1000]; %! [0, 0.2000, 0.2000, 0.2000]; [0, 0.3000, 0.3000, 0.3000]; %! [0, 0.2000, 0.3000, 0.3000]; [0, 0.1000, 0.1000, 0.1000]], 5e-15) %!test %! load fisheriris %! X = meas; %! obj = KDTreeSearcher (X, 'BucketSize', 20); %! Y = X(40:45,:); %! [idx, D] = knnsearch (obj, Y, 'K', 2); %! assert_equal (idx, [[40, 8]; [41, 18]; [42, 9]; [43, 39]; [44, 27]; [45, 47]]) %! assert_equal (D, [[0, 0.1000]; [0, 0.1414]; [0, 0.6245]; [0, 0.2000]; [0, 0.2236]; %! [0, 0.3606]], 4.7e-5) %!test %! load fisheriris %! X = meas; %! obj = KDTreeSearcher (X); %! Y = X(50:55,:); %! [idx, D] = knnsearch (obj, Y, 'K', 3, 'IncludeTies', true); %! assert_equal (idx, {[50, 8, 40]; [51, 53, 87]; [52, 57, 76]; ... %! [53, 51, 87]; [54, 90, 81]; [55, 59, 76]}) %! assert_equal (D, {[0, 0.1414, 0.1732]; [0, 0.2646, 0.3317]; ... %! [0, 0.2646, 0.3162]; [0, 0.2646, 0.2828]; ... %! [0, 0.2000, 0.3000]; [0, 0.2449, 0.3162]}, 5e-5) %!test %! load fisheriris %! X = meas; %! obj = KDTreeSearcher (X); %! Y = X(60:65,:); %! [idx, D] = rangesearch (obj, Y, 0.4); %! assert_equal (idx, {[60, 90]; [61, 94]; ... %! [62, 97, 79, 96, 100, 89, 98, 72]; [63]; ... %! [64, 92, 74, 79]; [65]}) %! assert_equal (D, {[0, 0.3873]; [0, 0.3606]; ... %! [0, 0.3000, 0.3317, 0.3606, 0.3606, 0.3742, 0.3873, ... %! 0.4000]; [0]; [0, 0.1414, 0.2236, 0.2449]; [0]}, 5e-5) %!test %! load fisheriris %! X = meas; %! obj = KDTreeSearcher (X, 'Distance', 'cityblock'); %! Y = X(70:72,:); %! [idx, D] = rangesearch (obj, Y, 1.0); %! assert_equal (idx, {[70, 81, 90, 82, 83, 93, 54, 68, 95, 80, 91, 100, ... %! 60, 65, 89, 63]; ... %! [71, 139, 128, 150, 127, 57, 86, 64, 79, 92, 124]; ... %! [72, 100, 98, 83, 93, 97, 75, 68, 62, 89, 95, 74, ... %! 56, 90, 79, 92, 96, 64, 63, 65]}) %! assert_equal (D, {[0, 0.3000, 0.4000, 0.5000, 0.5000, 0.5000, 0.6000, ... %! 0.7000, 0.7000, 0.7000, 0.8000, 0.8000, 0.9000, ... %! 0.9000, 0.9000, 0.9000]; ... %! [0, 0.3000, 0.5000, 0.5000, 0.7000, 0.8000, 0.8000, ... %! 1.0000, 1.0000, 1.0000, 1]; ... %! [0, 0.5000, 0.5000, 0.6000, 0.6000, 0.7000, 0.7000, ... %! 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.9000, ... %! 0.9000, 0.9000, 0.9000, 0.9000, 0.9000, 1.0000, 1]}, 5e-5) %!test %! load fisheriris %! X = meas; %! obj = KDTreeSearcher (X, 'Distance', 'minkowski', 'P', 3); %! Y = X(80:85,:); %! [idx, D] = rangesearch (obj, Y, 0.8); %! assert_equal (idx, {[80, 82, 81, 65, 70, 83, 93, 90, 54, 63, 68, 72, ... %! 100, 60, 89, 99, 95, 94, 97, 96]; [81, 82, 70, 54, ... %! 90, 93, 80, 83, 60, 68, 95, 100, 65, 63, 97, 61, 91, ... %! 94, 89, 96, 72, 58, 62, 56]; [82, 81, 70, 80, 54, ... %! 90, 93, 83, 68, 60, 65, 63, 100, 95, 94, 61, 58, 97, ... %! 89, 72, 96, 91, 99]; [83, 93, 100, 68, 70, 72, 95, ... %! 90, 97, 65, 89, 96, 81, 82, 80, 62, 54, 98, 63, 91, ... %! 56, 60, 79, 67, 75, 88, 85, 92, 69]; [84, 134, 102, ... %! 143, 150, 124, 128, 73, 127, 139, 147, 64, 112, 114, ... %! 120, 74, 135, 122, 71, 92, 104, 138, 148, 117, 79, ... %! 55, 56, 57, 67, 111, 129, 69, 78, 59, 52, 133, 85, ... %! 88, 87]; [85, 67, 56, 97, 95, 89, 96, 91, 100, 62, ... %! 71, 122, 79, 60, 107, 90, 139, 93, 68, 86, 83, 92, ... %! 64, 150, 102, 143, 74, 114, 70, 128, 84, 54, 72]}) %! assert_equal (D, {[0, 0.2884, 0.3530, 0.3826, 0.4062, 0.4198, 0.5117, ... %! 0.5440, 0.5718, 0.6000, 0.6018, 0.6073, 0.6308, ... %! 0.6333, 0.6753, 0.7000, 0.7192, 0.7230, 0.7350, ... %! 0.7459]; [0, 0.1260, 0.1442, 0.2571, 0.2571, 0.3530, ... %! 0.3530, 0.3826, 0.4344, 0.4344, 0.4642, 0.4747, ... %! 0.5217, 0.5217, 0.5896, 0.6009, 0.6082, 0.6316, ... %! 0.6316, 0.6611, 0.6664, 0.6993, 0.7417, 0.7507]; [0, ... %! 0.1260, 0.2224, 0.2884, 0.3803, 0.3803, 0.4121, ... %! 0.4121, 0.4905, 0.5013, 0.5360, 0.5429, 0.5463, ... %! 0.5646, 0.5749, 0.5819, 0.6542, 0.6581, 0.6753, ... %! 0.6938, 0.7094, 0.7107, 0.7423]; [0, 0.1260, 0.2224, ... %! 0.2520, 0.2571, 0.3107, 0.3302, 0.3332, 0.3332, ... %! 0.3530, 0.3530, 0.3803, 0.3826, 0.4121, 0.4198, ... %! 0.4344, 0.4531, 0.5155, 0.5217, 0.5348, 0.6028, ... %! 0.6073, 0.6374, 0.6527, 0.6611, 0.6804, 0.6938, ... %! 0.7399, 0.7560]; [0, 0.3072, 0.3271, 0.3271, 0.3302, ... %! 0.3503, 0.3530, 0.3530, 0.3530, 0.3958, 0.3979, ... %! 0.4327, 0.4626, 0.4642, 0.5027, 0.5066, 0.5130, ... %! 0.5155, 0.5440, 0.5440, 0.5518, 0.5848, 0.6009, ... %! 0.6073, 0.6082, 0.6316, 0.6471, 0.6746, 0.6753, ... %! 0.6797, 0.6804, 0.7047, 0.7192, 0.7218, 0.7405, ... %! 0.7405, 0.7719, 0.7725, 0.7786]; [0, 0.2000, 0.3503, ... %! 0.3979, 0.4121, 0.4309, 0.4327, 0.4531, 0.4747, ... %! 0.5337, 0.5718, 0.5896, 0.6009, 0.6316, 0.6366, ... %! 0.6374, 0.6463, 0.6542, 0.6542, 0.6550, 0.6938, ... %! 0.7014, 0.7067, 0.7166, 0.7186, 0.7186, 0.7281, ... %! 0.7380, 0.7447, 0.7571, 0.7719, 0.7813, 0.7851]}, 5e-5) %!test %! load fisheriris %! X = meas; %! obj = KDTreeSearcher (X, 'Distance', 'chebychev'); %! Y = X(90,:); %! [idx, D] = rangesearch (obj, Y, 0.7); %! assert_equal (idx, {[90, 70, 54, 81, 95, 60, 83, 93, 100, 68, 82, 65, ... %! 97, 91, 56, 61, 62, 63, 67, 79, 80, 85, 89, 96, 72, ... %! 92, 107]}) %! assert_equal (D, {[0, 0.2000, 0.2000, 0.2000, 0.2000, 0.3000, 0.3000, ... %! 0.3000, 0.3000, 0.3000, 0.3000, 0.4000, 0.4000, ... %! 0.4000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.6000, ... %! 0.6000, 0.6000]}, 5e-5) %!test %! ## Constructor with single-point dataset %! X = [0, 0]; %! obj = KDTreeSearcher (X); %! assert_equal (obj.X, X); %! assert_equal (obj.Distance, "euclidean"); %! assert_equal (isempty (obj.DistParameter), true); %! assert_equal (obj.BucketSize, 50); %!test %! ## Constructor with duplicate points %! X = [0, 0; 0, 0; 1, 0]; %! obj = KDTreeSearcher (X, 'Distance', 'cityblock'); %! assert_equal (obj.X, X); %! assert_equal (obj.Distance, "cityblock"); %!test %! ## Constructor with 3D data %! X = [0, 0, 0; 1, 0, 0; 0, 1, 0]; %! obj = KDTreeSearcher (X, 'Distance', 'minkowski', 'P', 3); %! assert_equal (obj.X, X); %! assert_equal (obj.DistParameter, 3); %!test %! ## knnsearch with grid, K = 1 %! X = [0, 0; 0, 1; 1, 0; 1, 1]; %! obj = KDTreeSearcher (X, 'Distance', 'euclidean'); %! Y = [0.5, 0.5]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! D_true = pdist2 (X, Y, 'euclidean'); %! assert_equal (D, min (D_true), 1e-10); %! assert_equal (any (idx == find (D_true == min (D_true))), true); %!test %! ## knnsearch with IncludeTies, all points equidistant %! X = [0, 0; 0, 1; 1, 0; 1, 1]; %! obj = KDTreeSearcher (X); %! Y = [0.5, 0.5]; %! [idx, D] = knnsearch (obj, Y, 'K', 1, 'IncludeTies', true); %! D_true = pdist2 (X, Y, 'euclidean'); %! expected_idx = find (D_true == min (D_true)); %! assert_equal (sort (idx{1}(:)), sort (expected_idx)); %! assert_equal (D{1}(:)', repmat (min (D_true), 1, 4), 1e-10); %!test %! ## rangesearch with line dataset %! X = [0, 0; 1, 0; 2, 0; 3, 0]; %! obj = KDTreeSearcher (X); %! Y = [1.5, 0]; %! r = 1; %! [idx, D] = rangesearch (obj, Y, r); %! D_true = pdist2 (X, Y, 'euclidean'); %! expected_idx = find (D_true <= r); %! assert_equal (sort (idx{1}(:)), sort (expected_idx)); %! assert_equal (D{1}, sort (D_true(expected_idx))', 1e-10); %!test %! ## knnsearch with duplicates %! X = [0, 0; 0, 0; 1, 0]; %! obj = KDTreeSearcher (X, 'Distance', 'cityblock'); %! Y = [0, 0]; %! [idx, D] = knnsearch (obj, Y, 'K', 1, 'IncludeTies', true); %! assert_equal (sort (idx{1}(:))', [1, 2]); %! assert_equal (D{1}, [0, 0], 1e-10); %!test %! ## rangesearch with 3D data %! X = [0, 0, 0; 1, 0, 0; 0, 1, 0]; %! obj = KDTreeSearcher (X, 'Distance', 'cityblock'); %! Y = [0, 0, 0]; %! r = 1; %! [idx, D] = rangesearch (obj, Y, r); %! assert_equal (sort (idx{1}(:))', [1, 2, 3]); %! assert_equal (D{1}, [0, 1, 1], 1e-10); %!test %! ## knnsearch with P = 2 (Euclidean equivalent) %! X = [0, 0; 1, 1]; %! obj = KDTreeSearcher (X, 'Distance', 'minkowski', 'P', 2); %! Y = [0, 1]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (idx, 1); %! assert_equal (D, 1, 1e-10); %!test %! ## rangesearch with P = 3 %! X = [0, 0; 1, 0; 0, 1]; %! obj = KDTreeSearcher (X, 'Distance', 'minkowski', 'P', 3); %! Y = [0.5, 0.5]; %! r = 0.8; %! [idx, D] = rangesearch (obj, Y, r); %! D_true = pdist2 (X, Y, 'minkowski', 3); %! expected_idx = find (D_true <= r); %! assert_equal (sort (idx{1}(:)), sort (expected_idx)); %! assert_equal (D{1}, sort (D_true(expected_idx))', 1e-10); %!test %! ## knnsearch with P = 4, random data %! X = rand (5, 2); %! obj = KDTreeSearcher (X, 'Distance', 'minkowski', 'P', 4); %! Y = rand (1, 2); %! [idx, D] = knnsearch (obj, Y, 'K', 3); %! D_true = pdist2 (X, Y, 'minkowski', 4); %! [sorted_D, sort_idx] = sort (D_true); %! assert_equal (idx', sort_idx(1:3)); %! assert_equal (D', sorted_D(1:3), 1e-10); %!test %! ## knnsearch with all same points %! X = [1, 1; 1, 1; 1, 1]; %! obj = KDTreeSearcher (X, 'Distance', 'chebychev'); %! Y = [1, 1]; %! [idx, D] = knnsearch (obj, Y, 'K', 1, 'IncludeTies', true); %! assert_equal (sort (idx{1}(:))', [1, 2, 3]); %! assert_equal (D{1}, [0, 0, 0], 1e-10); %!test %! ## rangesearch with grid %! X = [0, 0; 0, 1; 1, 0; 1, 1]; %! obj = KDTreeSearcher (X, 'Distance', 'chebychev'); %! Y = [0.5, 0.5]; %! r = 0.5; %! [idx, D] = rangesearch (obj, Y, r); %! D_true = pdist2 (X, Y, 'chebychev'); %! expected_idx = find (D_true <= r); %! assert_equal (sort (idx{1}(:)), sort (expected_idx)); %! assert_equal (D{1}, D_true(expected_idx)', 1e-10); %!test %! ## Changing Distance and verifying search %! X = [0,0; 1,0]; %! obj = KDTreeSearcher (X, 'Distance', 'euclidean'); %! Y = [0,1]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (D, 1, 1e-10); %! obj.Distance = 'chebychev'; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (D, 1, 1e-10); %!test %! ## Changing DistParameter for minkowski %! X = [0,0; 1,0]; %! obj = KDTreeSearcher (X, 'Distance', 'minkowski', 'P', 1); %! Y = [0,1]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (D, 1, 1e-10); %! obj.DistParameter = 3; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (D, 1, 1e-10); %!test %! ## Different BucketSize values %! X = rand (20,2); %! obj1 = KDTreeSearcher (X, 'BucketSize', 5); %! obj2 = KDTreeSearcher (X, 'BucketSize', 15); %! Y = rand (1,2); %! [idx1, D1] = knnsearch (obj1, Y, 'K', 3); %! [idx2, D2] = knnsearch (obj2, Y, 'K', 3); %! assert_equal (idx1, idx2); %! assert_equal (D1, D2, 1e-10); %!test %! ## Basic constructor with default Euclidean %! X = [1, 2; 3, 4; 5, 6]; %! obj = KDTreeSearcher (X); %! assert_equal (obj.X, X); %! assert_equal (obj.Distance, "euclidean"); %! assert_equal (isempty (obj.DistParameter), true); %! assert_equal (obj.BucketSize, 50); %!test %! ## Minkowski distance with custom P %! X = [0, 0; 1, 1; 2, 2]; %! obj = KDTreeSearcher (X, 'Distance', 'minkowski', 'P', 3); %! assert_equal (obj.Distance, "minkowski"); %! assert_equal (obj.DistParameter, 3); %!test %! ## Cityblock distance %! X = [0, 0; 1, 0; 0, 1]; %! obj = KDTreeSearcher (X, 'Distance', 'cityblock'); %! assert_equal (obj.Distance, "cityblock"); %! assert_equal (isempty (obj.DistParameter), true); %!test %! ## Chebychev distance %! X = [1, 1; 2, 3; 4, 2]; %! obj = KDTreeSearcher (X, 'Distance', 'chebychev'); %! assert_equal (obj.Distance, "chebychev"); %! assert_equal (isempty (obj.DistParameter), true); %!test %! ## knnsearch with Euclidean distance %! X = [1, 2; 3, 4; 5, 6]; %! obj = KDTreeSearcher (X); %! Y = [2, 3]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (idx, 1); %! assert_equal (D, sqrt (2), 1e-10); %!test %! ## knnsearch with Cityblock distance %! X = [0, 0; 1, 1; 2, 2]; %! obj = KDTreeSearcher (X, 'Distance', 'cityblock'); %! Y = [1, 0]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (ismember (idx, [1, 2]), true); %! assert_equal (D, 1, 1e-10); %!test %! ## knnsearch with Chebychev distance %! X = [1, 1; 2, 3; 4, 2]; %! obj = KDTreeSearcher (X, 'Distance', 'chebychev'); %! Y = [2, 2]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (ismember (idx, [1, 2]), true); %! assert_equal (D, 1, 1e-10); %!test %! ## knnsearch with Minkowski P=3 %! X = [0, 0; 1, 0; 2, 0]; %! obj = KDTreeSearcher (X, 'Distance', 'minkowski', 'P', 3); %! Y = [1, 0]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (idx, 2); %! assert_equal (D, 0, 1e-10); %!test %! ## knnsearch with IncludeTies %! X = [0, 0; 1, 0; 0, 1]; %! obj = KDTreeSearcher (X); %! Y = [0.5, 0]; %! [idx, D] = knnsearch (obj, Y, 'K', 1, 'IncludeTies', true); %! assert_equal (iscell (idx), true); %! assert_equal (sort (idx{1}(:))', [1, 2]); %! assert_equal (sort (D{1}(:)), [0.5; 0.5], 1e-10); %!test %! ## rangesearch with Euclidean %! X = [1, 1; 2, 2; 3, 3]; %! obj = KDTreeSearcher (X); %! Y = [0, 0]; %! [idx, D] = rangesearch (obj, Y, 2); %! assert_equal (idx{1}, [1]); %! assert_equal (D{1}, [sqrt(2)], 1e-10); %!test %! ## rangesearch with Cityblock %! X = [0, 0; 1, 1; 2, 2]; %! obj = KDTreeSearcher (X, 'Distance', 'cityblock'); %! Y = [0, 0]; %! [idx, D] = rangesearch (obj, Y, 1); %! assert_equal (idx{1}, [1]); %! assert_equal (D{1}, [0], 1e-10); %!test %! ## rangesearch with Chebychev %! X = [1, 1; 2, 3; 4, 2]; %! obj = KDTreeSearcher (X, 'Distance', 'chebychev'); %! Y = [2, 2]; %! [idx, D] = rangesearch (obj, Y, 1); %! assert_equal (sort (idx{1}(:))', [1, 2]); %! assert_equal (sort (D{1}(:))', [1, 1], 1e-10); %!test %! ## rangesearch with Minkowski P=3 %! X = [0, 0; 1, 0; 2, 0]; %! obj = KDTreeSearcher (X, 'Distance', 'minkowski', 'P', 3); %! Y = [1, 0]; %! [idx, D] = rangesearch (obj, Y, 1); %! assert_equal (sort (idx{1}(:))', [1, 2, 3]); %! assert_equal (sort (D{1}(:))', [0, 1, 1], 1e-10); %!test %! ## Diverse dataset with Euclidean %! X = [0, 10; 5, 5; 10, 0]; %! obj = KDTreeSearcher (X); %! Y = [5, 5]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (idx, 2); %! assert_equal (D, 0, 1e-10); %!test %! ## High-dimensional data with Cityblock %! X = [1, 2, 3; 4, 5, 6; 7, 8, 9]; %! obj = KDTreeSearcher (X, 'Distance', 'cityblock'); %! Y = [4, 5, 6]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (idx, 2); %! assert_equal (D, 0, 1e-10); ## Test Input Validation %!test %! ## Each cell holds one row per query point, as MATLAB returns and as %! ## ExhaustiveSearcher does; this searcher used to return columns, so a %! ## caller could not swap one searcher for the other. %! X = [1, 1; 2, 2; 3, 3; 4, 4; 5, 5; 1, 5; 5, 1]; %! Y = [2, 2; 4, 4]; %! [idx, D] = rangesearch (KDTreeSearcher (X), Y, 2); %! assert_equal (size (idx), [2, 1]); %! assert_equal (rows (idx{1}), 1); %! assert_equal (rows (D{1}), 1); %! [ie, de] = rangesearch (ExhaustiveSearcher (X), Y, 2); %! assert_equal (idx, ie); %! assert_equal (D, de, 1e-12); %!test %! ## knnsearch with ties returns cells in the same orientation %! X = [1, 1; 2, 2; 3, 3; 4, 4; 5, 5; 1, 5; 5, 1]; %! Y = [2, 2; 4, 4]; %! [idx, D] = knnsearch (KDTreeSearcher (X), Y, 'K', 2, 'IncludeTies', true); %! assert_equal (rows (idx{1}), 1); %! assert_equal (rows (D{1}), 1); %!test %! ## Distance and P may be overridden per call: the tree is built from the %! ## data alone, so only the pruning bound changes. The object is unchanged. %! X = [1, 1; 2, 2; 3, 3; 4, 4; 5, 5; 1, 5; 5, 1]; Y = [2, 2; 4, 4]; %! o = KDTreeSearcher (X); %! assert_equal (knnsearch (o, Y, 'K', 3, 'Distance', 'cityblock'), ... %! knnsearch (KDTreeSearcher (X, 'Distance', 'cityblock'), ... %! Y, 'K', 3)); %! assert_equal (rangesearch (o, Y, 3, 'Distance', 'cityblock'), ... %! rangesearch (KDTreeSearcher (X, 'Distance', 'cityblock'), ... %! Y, 3)); %! assert_equal (o.Distance, 'euclidean'); %!error ... %! knnsearch (KDTreeSearcher ([1, 1; 2, 2]), [1, 1], 'K', 1, 'Cov', eye (2)) %!error ... %! knnsearch (KDTreeSearcher ([1, 1; 2, 2]), [1, 1], 'K', 1, 'Scale', [1, 1]) %!error ... %! KDTreeSearcher () %!error ... %! KDTreeSearcher (ones (3,2), 'Distance') %!error ... %! KDTreeSearcher ('abc') %!error ... %! KDTreeSearcher ([1; Inf; 3]) %!error ... %! KDTreeSearcher (ones (3,2), 'foo', 'bar') %!error ... %! KDTreeSearcher (ones (3,2), 'Distance', 'invalid') %!error ... %! KDTreeSearcher (ones (3,2), 'Distance', 1) %!error ... %! KDTreeSearcher (ones (3,2), 'Distance', 'minkowski', 'P', -1) %!error ... %! KDTreeSearcher (ones (3,2), 'BucketSize', 0) %!error ... %! KDTreeSearcher (ones (3,2), 'BucketSize', -1) %!error ... %! knnsearch (KDTreeSearcher (ones (3,2))) %!error ... %! knnsearch (KDTreeSearcher (ones (3,2)), ones (3,2), 'K', 1, 'IncludeTies') %!error ... %! knnsearch (KDTreeSearcher (ones (3,2)), 'abc', 'K', 1) %!error ... %! knnsearch (KDTreeSearcher (ones (3,2)), ones (3,3), 'K', 1) %!error ... %! knnsearch (KDTreeSearcher (ones (3,2)), ones (3,2), 'K', 0) %!error ... %! obj = KDTreeSearcher (ones (3,2)); knnsearch (obj, ones (1,2), 'K', Inf) %!error ... %! knnsearch (KDTreeSearcher (ones (3,2)), ones (3,2), 'K', 1, 'foo', 'bar') %!error ... %! knnsearch (KDTreeSearcher (ones (3,2)), ones (3,2), 'K', 1, 'IncludeTies', 1) %!error ... %! knnsearch (KDTreeSearcher (ones (3,2)), ones (3,2), 'K', 1, 'SortIndices', 1) %!error ... %! rangesearch (KDTreeSearcher (ones (3,2))) %!error ... %! rangesearch (KDTreeSearcher (ones (3,2)), ones (3,2), 1, 'SortIndices') %!error ... %! rangesearch (KDTreeSearcher (ones (3,2)), 'abc', 1) %!error ... %! rangesearch (KDTreeSearcher (ones (3,2)), ones (3,3), 1) %!error ... %! rangesearch (KDTreeSearcher (ones (3,2)), ones (3,2), -1) %!error ... %! obj = KDTreeSearcher (ones (3,2)); rangesearch (obj, ones (1,2), Inf) %!error ... %! rangesearch (KDTreeSearcher (ones (3,2)), ones (3,2), 1, 'foo', 'bar') %!error ... %! rangesearch (KDTreeSearcher (ones (3,2)), ones (3,2), 1, 'SortIndices', 1) %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj(1) %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj{1} %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj.invalid %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj(1) = 1 %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj{1} = 1 %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj.X.Y = 1 %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj.X = 1 %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj.KDTree = 1 %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj.Distance = 'invalid' %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj.Distance = 1 %!error ... %! obj = KDTreeSearcher (ones (3,2), 'Distance', 'minkowski'); obj.DistParameter = -1 %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj.DistParameter = 1 %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj.BucketSize = 0 %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj.BucketSize = -1 %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj.BucketSize = 1.5 %!error ... %! obj = KDTreeSearcher (ones (3,2)); obj.invalid = 1 ## More neighbours than there are points is answered with all of them, where ## it used to raise an internal nonconformance. MATLAB caps at the sample. %!test %! randn ("seed", 4); %! X = randn (20, 2); Y = randn (5, 2); %! kd = KDTreeSearcher (X); %! for K = [20, 21, 100] %! [idx, D] = knnsearch (kd, Y, "K", K); %! assert_equal (size (idx), [5, 20]); %! assert_equal (size (D), [5, 20]); %! assert_equal (any (isnan (idx(:))), false); %! endfor %! ## and it agrees with the exhaustive answer %! [~, Dk] = knnsearch (kd, Y, "K", 100); %! [~, De] = knnsearch (ExhaustiveSearcher (X), Y, "K", 100); %! assert_equal (Dk, De, 1e-12); ## Single precision is carried through to the distances, every other class is ## converted up to double, and the indices are always double. %!test %! X = [1, 2; 3, 4; 5, 6; 7, 8]; Y = [2, 3; 6, 7]; %! obj = KDTreeSearcher (single (X)); %! assert_equal (class (obj.X), 'single'); %! [idx, D] = knnsearch (obj, single (Y), "K", 2); %! assert_equal (class (idx), 'double'); %! assert_equal (class (D), 'single'); %! ## a double query against single data is still computed in single %! [~, Dm] = knnsearch (obj, Y, "K", 2); %! assert_equal (class (Dm), 'single'); %! assert_equal (Dm, D); %! ## and so is a single query against double data %! [~, Ds] = knnsearch (KDTreeSearcher (X), single (Y), "K", 2); %! assert_equal (class (Ds), 'single'); %! ## double throughout stays double %! [~, Dd] = knnsearch (KDTreeSearcher (X), Y, "K", 2); %! assert_equal (class (Dd), 'double'); ## Integer data is converted to double on the way in. Held as int32 the ## coordinate differences were rounded and the distances came out wrong. %!test %! X = int32 ([10, 20; 33, 41; 55, 62]); Y = [21, 33]; %! obj = KDTreeSearcher (X); %! assert_equal (class (obj.X), 'double'); %! [~, D] = knnsearch (obj, Y, "K", 1); %! assert_equal (D, min (sqrt (sum ((double (X) - Y) .^ 2, 2))), 1e-12); ## rangesearch follows the same rule: single distances, double indices. %!test %! X = [1, 2; 3, 4; 5, 6; 7, 8]; Y = [2, 3]; %! [idx, D] = rangesearch (KDTreeSearcher (single (X)), single (Y), 4); %! assert_equal (class (idx{1}), 'double'); %! assert_equal (class (D{1}), 'single'); statistics-release-1.9.2/inst/Nearest_Neighbors/createns.m000066400000000000000000000151521524624707500237130ustar00rootroot00000000000000## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} createns (@var{X}) ## @deftypefnx {statistics} {@var{obj} =} createns (@var{X}, @var{name}, @var{value}, @dots{}) ## ## Create a nearest neighbor searcher object. ## ## @code{@var{obj} = createns (@var{X})} creates a nearest neighbor searcher ## object using the training data @var{X}. By default, it constructs an ## @code{ExhaustiveSearcher} object with the Euclidean distance metric. ## ## @code{@var{obj} = createns (@var{X}, @var{name}, @var{value}, @dots{})} ## allows customization of the searcher type and its properties through ## name-value pairs. The following name-value pair is supported to specify ## the searcher type: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'NSMethod'} @tab Specifies the nearest neighbor search ## method. Possible values are: ## @itemize @bullet ## @item @qcode{'exhaustive'}: Creates an @code{ExhaustiveSearcher} object. ## @item @qcode{'kdtree'}: Creates a @code{KDTreeSearcher} object. ## @item @qcode{'hnsw'}: Creates an @code{hnswSearcher} object. ## @end itemize ## Default is @qcode{'exhaustive'}. ## ## @end multitable ## ## Additional name-value pairs depend on the selected @qcode{'NSMethod'} and ## are passed directly to the constructor of the corresponding class: ## ## @itemize @bullet ## @item For @qcode{'exhaustive'}, see @code{ExhaustiveSearcher} documentation ## for parameters like @qcode{'Distance'}, @qcode{'P'}, @qcode{'Scale'}, and ## @qcode{'Cov'}. ## @item For @qcode{'kdtree'}, see @code{KDTreeSearcher} documentation for ## parameters like @qcode{'Distance'}, @qcode{'P'}, and @qcode{'BucketSize'}. ## @item For @qcode{'hnsw'}, see @code{hnswSearcher} documentation for ## parameters ## like @qcode{'Distance'}, @qcode{'P'}, @qcode{'Scale'}, @qcode{'Cov'}, ## @qcode{'MaxNumLinksPerNode'}, and @qcode{'TrainSetSize'}. ## @end itemize ## ## @strong{Input Arguments:} ## @itemize @bullet ## @item @var{X} - Training data, specified as an @math{N*P} numeric matrix ## where rows represent observations and columns represent features. Must be ## finite and numeric. ## @end itemize ## ## @strong{Output:} ## @itemize @bullet ## @item @var{obj} - A nearest neighbor searcher object of type ## @code{ExhaustiveSearcher}, @code{KDTreeSearcher}, or @code{hnswSearcher}, ## depending on the specified @qcode{'NSMethod'}. ## @end itemize ## ## @strong{Examples:} ## ## @example ## ## Create an ExhaustiveSearcher with default parameters ## X = [1, 2; 3, 4; 5, 6]; ## obj = createns (X); ## ## ## Create a KDTreeSearcher with Euclidean distance ## obj = createns (X, "NSMethod", "kdtree", "Distance", "euclidean"); ## ## ## Create an hnswSearcher with Minkowski distance and custom parameters ## obj = createns (X, "NSMethod", "hnsw", "Distance", "minkowski", "P", 3, ... ## "MaxNumLinksPerNode", 2); ## @end example ## ## @seealso{ExhaustiveSearcher, KDTreeSearcher, hnswSearcher, knnsearch, ## rangesearch} ## @end deftypefn function obj = createns (X, varargin) ## Input validation if (nargin < 1) error ("createns: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error ("createns: Name-Value arguments must be in pairs."); endif if (! (isnumeric (X) && ismatrix (X) && all (isfinite (X)(:)))) error ("createns: X must be a finite numeric matrix."); endif ## Set default NSMethod NSMethod = 'exhaustive'; ## Extract 'NSMethod' from varargin and remove it names = varargin(1:2:end); idx = find (strcmpi (names, 'nsmethod')); if (! isempty (idx)) if (length (idx) > 1) warning ("createns: multiple 'NSMethod' specified, using the last one."); endif NSMethod = varargin{2*idx(end)}; if (! ischar (NSMethod)) error ("createns: 'NSMethod' must be a string."); endif NSMethod = lower (NSMethod); remove_idx = [2*idx-1, 2*idx]; varargin(remove_idx) = []; endif ## Validate NSMethod value allowed_methods = {'exhaustive', 'kdtree', 'hnsw'}; if (! any (strcmp (NSMethod, allowed_methods))) error ("createns: invalid 'NSMethod' value: '%s'.", NSMethod); endif ## Instantiate the appropriate searcher object switch (NSMethod) case 'exhaustive' obj = ExhaustiveSearcher (X, varargin{:}); case 'kdtree' obj = KDTreeSearcher (X, varargin{:}); case 'hnsw' obj = hnswSearcher (X, varargin{:}); endswitch endfunction ## Test Cases %!test %! ## Default ExhaustiveSearcher %! X = [1, 2; 3, 4; 5, 6]; %! obj = createns (X); %! assert_equal (isa (obj, 'ExhaustiveSearcher'), true); %! assert_equal (obj.X, X); %! assert_equal (obj.Distance, "euclidean"); %!test %! ## KDTreeSearcher with default parameters %! X = [1, 2; 3, 4; 5, 6]; %! obj = createns (X, 'NSMethod', 'kdtree'); %! assert_equal (isa (obj, 'KDTreeSearcher'), true); %! assert_equal (obj.X, X); %! assert_equal (obj.Distance, "euclidean"); %!test %! ## hnswSearcher with custom parameters %! X = [1, 2; 3, 4; 5, 6]; %! obj = createns (X, 'NSMethod', 'hnsw', 'MaxNumLinksPerNode', 2, 'TrainSetSize', 3); %! assert_equal (isa (obj, 'hnswSearcher'), true); %! assert_equal (obj.X, X); %! assert_equal (obj.MaxNumLinksPerNode, 2); %! assert_equal (obj.TrainSetSize, 3); %!test %! ## ExhaustiveSearcher with custom distance %! X = [1, 2; 3, 4]; %! obj = createns (X, 'NSMethod', 'exhaustive', 'Distance', 'cityblock'); %! assert_equal (isa (obj, 'ExhaustiveSearcher'), true); %! assert_equal (obj.Distance, "cityblock"); %!error %! createns () %!error %! X = [1, 2; 3, 4]; createns (X, 'NSMethod') %!error %! createns ([1; Inf; 3]) %!error %! X = [1, 2; 3, 4]; createns (X, 'NSMethod', 1) %!error %! X = [1, 2; 3, 4]; createns (X, 'NSMethod', 'invalid') statistics-release-1.9.2/inst/Nearest_Neighbors/doc-cache000066400000000000000000001321551524624707500234650ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 30 # name: # type: sq_string # elements: 1 # length: 18 ExhaustiveSearcher # name: # type: sq_string # elements: 1 # length: 715 statistics: ExhaustiveSearcher Exhaustive nearest neighbor searcher The ExhaustiveSearcher class implements an exhaustive search algorithm for nearest neighbor queries. It stores training data and supports various distance metrics along with their parameter values for performing an exhaustive search. The exhaustive search algorithm computes the distance from each query point to all the points in the training data and facilitates a nearest neighbor search using knnsearch or a radius search using rangesearch . You can either use the ExhaustiveSearcher class constructor or the createns function to create an ExhaustiveSearcher object. See also: createns, KDTreeSearcher, hnswSearcher, knnsearch, rangesearch # name: # type: sq_string # elements: 1 # length: 36 Exhaustive nearest neighbor searcher # name: # type: sq_string # elements: 1 # length: 32 ExhaustiveSearcher.DistParameter # name: # type: sq_string # elements: 1 # length: 531 ExhaustiveSearcher: property DistParameter Distance parameter The type and value of the distance parameter depends on the selected Distance metric and can be any of the following: For 'minkowski' , a positive scalar exponent (default 2). For 'seuclidean' , a nonnegative vector of scaling factors matching the number of columns in X (default is standard deviation of X ). For 'mahalanobis' , a positive definite covariance matrix matching the dimensions of X (default is cov ( X ) ). Empty for other metrics or custom functions. # name: # type: sq_string # elements: 1 # length: 18 Distance parameter # name: # type: sq_string # elements: 1 # length: 27 ExhaustiveSearcher.Distance # name: # type: sq_string # elements: 1 # length: 281 ExhaustiveSearcher: property Distance Distance metric Distance metric used for searches, specified as a character vector (e.g., 'euclidean' , 'minkowski' ) or a function handle to a custom distance function. Default is 'euclidean' . Supported metrics align with those in pdist2 . # name: # type: sq_string # elements: 1 # length: 15 Distance metric # name: # type: sq_string # elements: 1 # length: 37 ExhaustiveSearcher.ExhaustiveSearcher # name: # type: sq_string # elements: 1 # length: 1506 ExhaustiveSearcher: obj = ExhaustiveSearcher ( X ) ExhaustiveSearcher: obj = ExhaustiveSearcher ( X , name , value ) Create an ExhaustiveSearcher object for nearest neighbor searches. obj = ExhaustiveSearcher ( X ) constructs an ExhaustiveSearcher object with training data X using the default 'euclidean' distance metric. X must be an N×P numeric matrix, where rows represent observations and columns represent features. obj = ExhaustiveSearcher ( X , name , value ) allows customization through name-value pairs: Name Value 'Distance' Distance metric, specified as a character vector (e.g., 'euclidean' , 'minkowski' ) or a function handle. Default is 'euclidean' . See pdist2 for supported metrics. 'P' a positive scalar specifying the exponent for the Minkowski distance. Valid only when 'Distance' is 'minkowski' . Default is 2. 'Scale' a nonnegative vector with the same number of elements as the columns in X specifying the scale parameter for the standardized Euclidean distance. Valid only when 'Distance' is 'seuclidean' . Default is std (X) . 'Cov' a positive definite matrix matching the number of columns in X specifying the covariance matrix for the Mahalanobis distance. Valid only when 'Distance' is 'mahalanobis' . Default is cov (X) . 'Distance' , 'P' , 'Cov' and 'Scale' override the searcher’s own metric for that call only; the Distance and DistParameter properties keep their values, as they do in MATLAB. See also: ExhaustiveSearcher, knnsearch, rangesearch, pdist2 # name: # type: sq_string # elements: 1 # length: 66 Create an ExhaustiveSearcher object for nearest neighbor searches. # name: # type: sq_string # elements: 1 # length: 20 ExhaustiveSearcher.X # name: # type: sq_string # elements: 1 # length: 345 ExhaustiveSearcher: property X Point data Point data, specified as an N×P numeric matrix where each row is an observation and each column is a feature. This property is private and cannot be modified after object creation. Data of class single is stored and searched in single precision, any other numeric class is converted to double . # name: # type: sq_string # elements: 1 # length: 10 Point data # name: # type: sq_string # elements: 1 # length: 28 ExhaustiveSearcher.knnsearch # name: # type: sq_string # elements: 1 # length: 1363 ExhaustiveSearcher: [ idx , D ] = knnsearch ( obj , Y ) ExhaustiveSearcher: [ idx , D ] = knnsearch ( obj , Y , name , value ) Find the K nearest neighbors in the training data to query points. [ idx , D ] = knnsearch ( obj , Y ) returns the indices idx and distances D of the nearest neighbor in obj.X to each point in Y , using the distance metric specified in obj.Distance . obj is an ExhaustiveSearcher object. Y is an M×P numeric matrix of query points, where P must match the number of columns in obj.X . idx is always of class double . D is of class single when either obj.X or Y is single , in which case the distances are computed in single precision, and of class double otherwise. [ idx , D ] = knnsearch ( obj , Y , name , value ) allows additional options via name-value pairs: Name Value 'K' A positive integer specifying the number of nearest neighbors to find. Default is 1. A value larger than the number of observations in the training data is answered with all of them, since there are no more neighbors to return. 'IncludeTies' Logical flag indicating whether to include all neighbors tied with the K th smallest distance. Default is false . If true , idx and D are cell arrays. idx contains the indices of the nearest neighbors in obj.X . D contains the corresponding distances. See also: ExhaustiveSearcher, rangesearch, pdist2 # name: # type: sq_string # elements: 1 # length: 66 Find the K nearest neighbors in the training data to query points. # name: # type: sq_string # elements: 1 # length: 30 ExhaustiveSearcher.rangesearch # name: # type: sq_string # elements: 1 # length: 1140 ExhaustiveSearcher: [ idx , D ] = rangesearch ( obj , Y , r ) ExhaustiveSearcher: [ idx , D ] = rangesearch ( obj , Y , r , name , value ) Find all neighbors within a specified radius of query points. [ idx , D ] = rangesearch ( obj , Y , r ) returns the indices idx and distances D of all points in obj.X within radius r of each point in Y , using the distance metric specified in obj.Distance . obj is an ExhaustiveSearcher object. Y is an M×P numeric matrix of query points, where P must match the number of columns in obj.X . r is a nonnegative scalar specifying the search radius. idx is always of class double . D is of class single when either obj.X or Y is single , in which case the distances are computed in single precision, and of class double otherwise. [ idx , D ] = rangesearch (…, name , value ) allows additional options via name-value pairs: Name Value 'SortIndices' Logical flag indicating whether to sort the indices by distance. Default is true . idx and D are cell arrays where each cell contains the indices and distances for one query point in Y . See also: ExhaustiveSearcher, knnsearch, pdist2 # name: # type: sq_string # elements: 1 # length: 61 Find all neighbors within a specified radius of query points. # name: # type: sq_string # elements: 1 # length: 14 KDTreeSearcher # name: # type: sq_string # elements: 1 # length: 762 statistics: KDTreeSearcher KD-tree nearest neighbor searcher The KDTreeSearcher class implements a KD-tree search algorithm for nearest neighbor queries. It stores training data and supports various distance metrics along with their parameter values for performing a KD-tree search. The KD-tree algorithm partitions the training data into a hierarchical tree structure and performs search operations by traversing the tree to reduce the number of distance computations. It facilitates nearest neighbor queries using knnsearch and radius queries using rangesearch . You can either use the KDTreeSearcher class constructor or the createns function to create an KDTreeSearcher object. See also: createns, ExhaustiveSearcher, hnswSearcher, knnsearch, rangesearch # name: # type: sq_string # elements: 1 # length: 33 KD-tree nearest neighbor searcher # name: # type: sq_string # elements: 1 # length: 25 KDTreeSearcher.BucketSize # name: # type: sq_string # elements: 1 # length: 244 KDTreeSearcher: property BucketSize Maximum number of data points in each leaf node The maximum number of data points in the leaf node of the KD-tree. Default value is 50. This property is private and cannot be modified after object creation. # name: # type: sq_string # elements: 1 # length: 47 Maximum number of data points in each leaf node # name: # type: sq_string # elements: 1 # length: 28 KDTreeSearcher.DistParameter # name: # type: sq_string # elements: 1 # length: 384 KDTreeSearcher: property DistParameter Distance parameter The type and value of the distance parameter depends on the selected Distance metric and can be any of the following: For 'minkowski' , a positive scalar exponent (default 2). Empty for other metrics ( 'euclidean' , 'cityblock' , 'chebychev' ). Attempting to set a non-empty value for these metrics will result in an error. # name: # type: sq_string # elements: 1 # length: 18 Distance parameter # name: # type: sq_string # elements: 1 # length: 23 KDTreeSearcher.Distance # name: # type: sq_string # elements: 1 # length: 232 KDTreeSearcher: property Distance Distance metric Distance metric used for searches, specified as a character vector. Supported metrics are 'euclidean' , 'cityblock' , 'minkowski' , and 'chebychev' . Default value is 'euclidean' . # name: # type: sq_string # elements: 1 # length: 15 Distance metric # name: # type: sq_string # elements: 1 # length: 29 KDTreeSearcher.KDTreeSearcher # name: # type: sq_string # elements: 1 # length: 1315 KDTreeSearcher: obj = KDTreeSearcher ( X ) KDTreeSearcher: obj = KDTreeSearcher ( X , name , value ) Create a KDTreeSearcher object for nearest neighbor searches. obj = KDTreeSearcher ( X ) constructs a KDTreeSearcher object with training data X using the default 'euclidean' distance metric. X must be an N×P numeric matrix, where rows represent observations and columns represent features. obj = KDTreeSearcher ( X , name , value ) allows customization through name-value pairs: Name Value 'Distance' Distance metric, specified as a character vector ( 'euclidean' , 'cityblock' , 'minkowski' , 'chebychev' ). Default is 'euclidean' . 'P' Minkowski distance exponent, a positive scalar. Valid only when 'Distance' is 'minkowski' . Default is 2. 'BucketSize' Maximum number of data points in the leaf node of the KD-tree, a positive integer. Default is 50. You can also create a KDTreeSearcher object using the createns function. 'Distance' and 'P' override the searcher’s own metric for that call only; the Distance and DistParameter properties keep their values. The tree is built from the data alone, so changing the metric does not rebuild it. 'Cov' and 'Scale' are not accepted, since they belong to metrics a kd-tree cannot search. See also: KDTreeSearcher, knnsearch, rangesearch, createns # name: # type: sq_string # elements: 1 # length: 61 Create a KDTreeSearcher object for nearest neighbor searches. # name: # type: sq_string # elements: 1 # length: 16 KDTreeSearcher.X # name: # type: sq_string # elements: 1 # length: 341 KDTreeSearcher: property X Point data Point data, specified as an N×P numeric matrix where each row is an observation and each column is a feature. This property is private and cannot be modified after object creation. Data of class single is stored and searched in single precision, any other numeric class is converted to double . # name: # type: sq_string # elements: 1 # length: 10 Point data # name: # type: sq_string # elements: 1 # length: 24 KDTreeSearcher.knnsearch # name: # type: sq_string # elements: 1 # length: 1441 KDTreeSearcher: [ idx , D ] = knnsearch ( obj , Y ) KDTreeSearcher: [ idx , D ] = knnsearch ( obj , Y , name , value ) Find the K nearest neighbors in the training data to query points. [ idx , D ] = knnsearch ( obj , Y , K ) returns the indices idx and distances D of the K nearest neighbors in obj.X to each point in Y , using the distance metric specified in obj.Distance . obj is a KDTreeSearcher object. Y is an M×P numeric matrix of query points, where P must match the number of columns in obj.X . idx contains the indices of the nearest neighbors in obj.X . D contains the corresponding distances. idx is always of class double . D is of class single when either obj.X or Y is single , in which case the distances are computed in single precision, and of class double otherwise. [ idx , D ] = knnsearch ( obj , Y , name , value ) allows additional options via name-value pairs: Name Value 'K' A positive integer specifying the number of nearest neighbors to find. Default is 1. A value larger than the number of observations in the training data is answered with all of them, since there are no more neighbors to return. 'IncludeTies' Logical flag indicating whether to include all neighbors tied with the K th smallest distance. Default is false . If true , idx and D are cell arrays. 'SortIndices' Logical flag indicating whether to sort the indices by distance. Default is true . See also: KDTreeSearcher, rangesearch # name: # type: sq_string # elements: 1 # length: 66 Find the K nearest neighbors in the training data to query points. # name: # type: sq_string # elements: 1 # length: 26 KDTreeSearcher.rangesearch # name: # type: sq_string # elements: 1 # length: 1120 KDTreeSearcher: [ idx , D ] = rangesearch ( obj , Y , r ) KDTreeSearcher: [ idx , D ] = rangesearch ( obj , Y , r , name , value ) Find all neighbors within a specified radius of query points. [ idx , D ] = rangesearch ( obj , Y , r ) returns the indices idx and distances D of all points in obj.X within radius r of each point in Y , using the distance metric specified in obj.Distance . obj is a KDTreeSearcher object. Y is an M×P numeric matrix of query points, where P must match the number of columns in obj.X . r is a nonnegative scalar specifying the search radius. idx is always of class double . D is of class single when either obj.X or Y is single , in which case the distances are computed in single precision, and of class double otherwise. [ idx , D ] = rangesearch ( obj , Y , r , name , value ) allows additional options via name-value pairs: Name Value 'SortIndices' Logical flag indicating whether to sort the indices by distance. Default is true . idx and D are cell arrays where each cell contains the indices and distances for one query point in Y . See also: KDTreeSearcher, knnsearch # name: # type: sq_string # elements: 1 # length: 61 Find all neighbors within a specified radius of query points. # name: # type: sq_string # elements: 1 # length: 8 createns # name: # type: sq_string # elements: 1 # length: 2102 statistics: obj = createns ( X ) statistics: obj = createns ( X , name , value , …) Create a nearest neighbor searcher object. obj = createns ( X ) creates a nearest neighbor searcher object using the training data X . By default, it constructs an ExhaustiveSearcher object with the Euclidean distance metric. obj = createns ( X , name , value , …) allows customization of the searcher type and its properties through name-value pairs. The following name-value pair is supported to specify the searcher type: Name Value 'NSMethod' Specifies the nearest neighbor search method. Possible values are: 'exhaustive' : Creates an ExhaustiveSearcher object. 'kdtree' : Creates a KDTreeSearcher object. 'hnsw' : Creates an hnswSearcher object. Default is 'exhaustive' . Additional name-value pairs depend on the selected 'NSMethod' and are passed directly to the constructor of the corresponding class: For 'exhaustive' , see ExhaustiveSearcher documentation for parameters like 'Distance' , 'P' , 'Scale' , and 'Cov' . For 'kdtree' , see KDTreeSearcher documentation for parameters like 'Distance' , 'P' , and 'BucketSize' . For 'hnsw' , see hnswSearcher documentation for parameters like 'Distance' , 'P' , 'Scale' , 'Cov' , 'MaxNumLinksPerNode' , and 'TrainSetSize' . Input Arguments: X - Training data, specified as an N×P numeric matrix where rows represent observations and columns represent features. Must be finite and numeric. Output: obj - A nearest neighbor searcher object of type ExhaustiveSearcher , KDTreeSearcher , or hnswSearcher , depending on the specified 'NSMethod' . Examples: ## Create an ExhaustiveSearcher with default parameters X = [1, 2; 3, 4; 5, 6]; obj = createns (X); ## Create a KDTreeSearcher with Euclidean distance obj = createns (X, "NSMethod", "kdtree", "Distance", "euclidean"); ## Create an hnswSearcher with Minkowski distance and custom parameters obj = createns (X, "NSMethod", "hnsw", "Distance", "minkowski", "P", 3, ... "MaxNumLinksPerNode", 2); See also: ExhaustiveSearcher, KDTreeSearcher, hnswSearcher, knnsearch, rangesearch # name: # type: sq_string # elements: 1 # length: 42 Create a nearest neighbor searcher object. # name: # type: sq_string # elements: 1 # length: 12 hnswSearcher # name: # type: sq_string # elements: 1 # length: 671 statistics: hnswSearcher Hierarchical Navigable Small World (HNSW) nearest neighbor searcher class. The hnswSearcher class implements the HNSW algorithm for efficient nearest neighbor queries. It stores training data and supports various distance metrics for performing searches. The HNSW algorithm builds a multilayer graph structure that enables fast approximate nearest neighbor searches by navigating through the graph. It facilitates nearest neighbor queries search using knnsearch . You can either use the hnswSearcher class constructor or the createns function to create an hnswSearcher object. See also: createns, ExhaustiveSearcher, KDTreeSearcher, knnsearch # name: # type: sq_string # elements: 1 # length: 74 Hierarchical Navigable Small World (HNSW) nearest neighbor searcher class. # name: # type: sq_string # elements: 1 # length: 26 hnswSearcher.DistParameter # name: # type: sq_string # elements: 1 # length: 577 hnswSearcher: property DistParameter Distance parameter The type and value of the distance parameter depends on the selected Distance metric and can be any of the following: For 'minkowski' , a positive scalar exponent (default 2). For 'seuclidean' , a nonnegative vector of scaling factors matching the number of columns in X (default is standard deviation of X ). For 'mahalanobis' , a positive definite covariance matrix matching the dimensions of X (default is cov ( X ) ). Empty for other metrics. This property is private and cannot be modified after object creation. # name: # type: sq_string # elements: 1 # length: 18 Distance parameter # name: # type: sq_string # elements: 1 # length: 21 hnswSearcher.Distance # name: # type: sq_string # elements: 1 # length: 309 hnswSearcher: property Distance Distance metric Distance metric used for searches, specified as a character vector (e.g., 'euclidean' , 'minkowski' , 'cityblock' ). Default is 'euclidean' . Supported metrics align with those in pdist2 . This property is private and cannot be modified after object creation. # name: # type: sq_string # elements: 1 # length: 15 Distance metric # name: # type: sq_string # elements: 1 # length: 31 hnswSearcher.MaxNumLinksPerNode # name: # type: sq_string # elements: 1 # length: 283 hnswSearcher: property MaxNumLinksPerNode Number of connections created for each node Maximum number of neighbors per node in the HNSW graph. Affects graph connectivity and search accuracy. Default value is 16. This property is private and cannot be modified after object creation. # name: # type: sq_string # elements: 1 # length: 43 Number of connections created for each node # name: # type: sq_string # elements: 1 # length: 25 hnswSearcher.TrainSetSize # name: # type: sq_string # elements: 1 # length: 295 hnswSearcher: property TrainSetSize Number of potential nearest neighbors Size of the dynamic candidate list during graph construction. Higher values improve accuracy at the cost of construction time. Default value is 200. This property is private and cannot be modified after object creation. # name: # type: sq_string # elements: 1 # length: 37 Number of potential nearest neighbors # name: # type: sq_string # elements: 1 # length: 14 hnswSearcher.X # name: # type: sq_string # elements: 1 # length: 339 hnswSearcher: property X Point data Point data, specified as an N×P numeric matrix where each row is an observation and each column is a feature. This property is private and cannot be modified after object creation. Data of class single is stored and searched in single precision, any other numeric class is converted to double . # name: # type: sq_string # elements: 1 # length: 10 Point data # name: # type: sq_string # elements: 1 # length: 25 hnswSearcher.hnswSearcher # name: # type: sq_string # elements: 1 # length: 1353 hnswSearcher: obj = hnswSearcher ( X ) hnswSearcher: obj = hnswSearcher ( X , name , value ) Create an hnswSearcher object for approximate nearest neighbor searches. obj = hnswSearcher ( X ) constructs an hnswSearcher object with training data X using the default 'euclidean' distance metric. X must be an N×P numeric matrix, where rows represent observations and columns represent features. obj = hnswSearcher ( X , name , value ) allows customization through name-value pairs: Name Value 'Distance' Distance metric, specified as a character vector (e.g., 'euclidean' , 'minkowski' , 'cityblock' ). Default is 'euclidean' . See pdist2 for supported metrics. 'P' Minkowski distance exponent, a positive scalar. Valid only when 'Distance' is 'minkowski' . Default is 2. 'Scale' Nonnegative vector of scaling factors matching the number of columns in X . Valid only when 'Distance' is 'seuclidean' . Default is std (X) . 'Cov' Positive definite covariance matrix matching the number of columns in X . Valid only when 'Distance' is 'mahalanobis' . Default is cov (X) . 'MaxNumLinksPerNode' Maximum number of neighbors per node in the HNSW graph, a positive integer. Default is 16. 'TrainSetSize' Size of the dynamic candidate list during graph construction, a positive integer. Default is 200. See also: hnswSearcher, knnsearch, createns, pdist2 # name: # type: sq_string # elements: 1 # length: 72 Create an hnswSearcher object for approximate nearest neighbor searches. # name: # type: sq_string # elements: 1 # length: 22 hnswSearcher.knnsearch # name: # type: sq_string # elements: 1 # length: 1474 hnswSearcher: [ idx , D ] = knnsearch ( obj , Y ) hnswSearcher: [ idx , D ] = knnsearch ( obj , Y , name , value ) Find the nearest neighbors in the training data to query points. [ idx , D ] = knnsearch ( obj , Y ) returns the indices idx and distances D of the nearest neighbor in obj.X to each point in Y , using the distance metric specified in obj.Distance . obj is an hnswSearcher object. Y is an M×P numeric matrix of query points, where P must match the number of columns in obj.X . idx contains the indices of the nearest neighbors in obj.X . D contains the corresponding distances. idx is always of class double . D is of class single when either obj.X or Y is single , in which case the distances are computed in single precision, and of class double otherwise. [ idx , D ] = knnsearch ( obj , Y , name , value ) allows additional options via name-value pairs: Name Value 'K' A positive integer specifying the number of nearest neighbors to find. Default is 1. A value larger than the number of observations in the training data is answered with all of them, since there are no more neighbors to return. 'SearchSetSize' A positive integer specifying the size of the candidate list of nearest neighbors for a single query point during the search process. Default is max (10, C ) , where C is the number of columns in obj.X . 'SearchSetSize' must be at least C and no more than the number of rows in training data obj.X . See also: hnswSearcher, pdist2 # name: # type: sq_string # elements: 1 # length: 64 Find the nearest neighbors in the training data to query points. # name: # type: sq_string # elements: 1 # length: 9 knnsearch # name: # type: sq_string # elements: 1 # length: 5130 statistics: idx = knnsearch ( X , Y ) statistics: [ idx , D ] = knnsearch ( X , Y ) statistics: […] = knnsearch (…, name , value ) Find k-nearest neighbors from input data. idx = knnsearch ( X , Y ) finds K nearest neighbors in X for Y . It returns idx which contains indices of K nearest neighbors of each row of Y , If not specified, K = 1 . X must be an N×P numeric matrix of input data, where rows correspond to observations and columns correspond to features or variables. Y is an M×P numeric matrix with query points, which must have the same numbers of column as X . [ idx , D ] = knnsearch ( X , Y ) also returns the the distances, D , which correspond to the K nearest neighbour in X for each Y Additional parameters can be specified by Name-Value pair arguments. Name Value 'K' is the number of nearest neighbors to be found in the kNN search. It must be a positive integer value and by default it is 1. 'P' is the Minkowski distance exponent and it must be a positive scalar. This argument is only valid when the selected distance metric is 'minkowski' . By default it is 2. 'Scale' is the scale parameter for the standardized Euclidean distance and it must be a nonnegative numeric vector of equal length to the number of columns in X . This argument is only valid when the selected distance metric is 'seuclidean' , in which case each coordinate of X is scaled by the corresponding element of 'scale' , as is each query point in Y . By default, the scale parameter is the standard deviation of each coordinate in X . 'Cov' is the covariance matrix for computing the mahalanobis distance and it must be a positive definite matrix matching the the number of columns in X . This argument is only valid when the selected distance metric is 'mahalanobis' . 'BucketSize' is the maximum number of data points in the leaf node of the Kd-tree and it must be a positive integer. This argument is only valid when the selected search method is 'kdtree' . 'SortIndices' is a boolean flag to sort the returned indices in ascending order by distance and it is true by default. When the selected search method is 'exhaustive' or the 'IncludeTies' flag is true, knnsearch always sorts the returned indices. 'Distance' is the distance metric used by knnsearch as specified below: 'euclidean' Euclidean distance. 'seuclidean' standardized Euclidean distance. Each coordinate difference between the rows in X and the query matrix Y is scaled by dividing by the corresponding element of the standard deviation computed from X . To specify a different scaling, use the 'Scale' name-value argument. 'cityblock' City block distance. 'chebychev' Chebychev distance (maximum coordinate difference). 'minkowski' Minkowski distance. The default exponent is 2. To specify a different exponent, use the 'P' name-value argument. 'mahalanobis' Mahalanobis distance, computed using a positive definite covariance matrix. To change the value of the covariance matrix, use the 'Cov' name-value argument. 'cosine' Cosine distance. 'correlation' One minus the sample linear correlation between observations (treated as sequences of values). 'spearman' One minus the sample Spearman’s rank correlation between observations (treated as sequences of values). 'hamming' Hamming distance, which is the percentage of coordinates that differ. 'jaccard' One minus the Jaccard coefficient, which is the percentage of nonzero coordinates that differ. @distfun Custom distance function handle. A distance function of the form function D2 = distfun ( XI , YI ) , where XI is a 1×P vector containing a single observation in P -dimensional space, YI is an N×P matrix containing an arbitrary number of observations in the same P -dimensional space, and D2 is an N×P vector of distances, where ( D2 k) is the distance between observations XI and ( YI k,:) . 'NSMethod' is the nearest neighbor search method used by knnsearch as specified below. 'kdtree' Creates and uses a Kd-tree to find nearest neighbors. 'kdtree' is the default value when the number of columns in X is less than or equal to 10, X is not sparse, and the distance metric is 'euclidean' , 'cityblock' , 'manhattan' , 'chebychev' , or 'minkowski' . Otherwise, the default value is 'exhaustive' . This argument is only valid when the distance metric is one of the four aforementioned metrics. 'exhaustive' Uses the exhaustive search algorithm by computing the distance values from all the points in X to each point in Y . 'IncludeTies' is a boolean flag to indicate if the returned values should contain the indices that have same distance as the K^th neighbor. When false , knnsearch chooses the observation with the smallest index among the observations that have the same distance from a query point. When true , knnsearch includes all nearest neighbors whose distances are equal to the K^th smallest distance in the output arguments. To specify K , use the 'K' name-value pair argument. In that case idx and D are M -by- 1 cell arrays and each cell holds a row vector, whichever search method is used. See also: rangesearch, pdist2, fitcknn # name: # type: sq_string # elements: 1 # length: 41 Find k-nearest neighbors from input data. # name: # type: sq_string # elements: 1 # length: 5 mahal # name: # type: sq_string # elements: 1 # length: 468 statistics: d = mahal ( y , x ) Mahalanobis’ D-square distance. Return the Mahalanobis’ D-square distance of the points in y from the distribution implied by points x . Specifically, it uses a Cholesky decomposition to set answer(i) = ( y (i,:) - mean ( x )) * inv (A) * ( y (i,:)-mean ( x ))' where A is the covariance of x . The data x and y must have the same number of components (columns), but may have a different number of observations (rows). # name: # type: sq_string # elements: 1 # length: 31 Mahalanobis' D-square distance. # name: # type: sq_string # elements: 1 # length: 5 pdist # name: # type: sq_string # elements: 1 # length: 3050 statistics: D = pdist ( X ) statistics: D = pdist ( X , Distance ) statistics: D = pdist ( X , Distance , DistParameter ) Return the distance between any two rows in X . D = pdist ( X calculates the euclidean distance between pairs of observations in X . X must be an M×P numeric matrix representing M points in P -dimensional space. This function computes the pairwise distances returned in D as an M×(M-1)/P row vector. Use Z = squareform ( D ) to convert the row vector D into a an M×M symmetric matrix Z , where Z (i,j) corresponds to the pairwise distance between points i and j . D = pdist ( X , Y , Distance ) returns the distance between pairs of observations in X using the metric specified by Distance , which can be any of the following options. 'euclidean' Euclidean distance. 'fasteuclidean' Euclidean distance computed with an alternative algorithm which may be faster but might reduce accuracy. 'squaredeuclidean' Squared Euclidean distance. 'fastsquaredeuclidean' Euclidean distance computed with an alternative algorithm which may be faster but might reduce accuracy. 'seuclidean' standardized Euclidean distance. Each coordinate difference between the rows in X and the query matrix Y is scaled by dividing by the corresponding element of the standard deviation computed from X . A different scaling vector can be specified with the subsequent DistParameter input argument. 'mahalanobis' Mahalanobis distance, computed using a positive definite covariance matrix. A different covariance matrix can be specified with the subsequent DistParameter input argument. 'cityblock' City block distance. 'minkowski' Minkowski distance. The default exponent is 2. A different exponent can be specified with the subsequent DistParameter input argument. 'chebychev' Chebychev distance (maximum coordinate difference). 'cosine' One minus the cosine of the included angle between points (treated as vectors). 'correlation' One minus the sample linear correlation between observations (treated as sequences of values). 'hamming' Hamming distance, which is the percentage of coordinates that differ. 'jaccard' One minus the Jaccard coefficient, which is the percentage of nonzero coordinates that differ. 'spearman' One minus the sample Spearman’s rank correlation between observations (treated as sequences of values). @distfun Custom distance function handle. A distance function of the form function D2 = distfun ( XI , YI ) , where XI is a 1×P vector containing a single observation in P -dimensional space, YI is an N×P matrix containing an arbitrary number of observations in the same P -dimensional space, and D2 is an N×P vector of distances, where ( D2 k) is the distance between observations XI and ( YI k,:) . D = pdist ( X , Y , Distance , DistParameter ) returns the distance using the metric specified by Distance and DistParameter . The latter one can only be specified when the selected Distance is 'seuclidean' , 'minkowski' , and 'mahalanobis' . See also: pdist2, squareform, linkage # name: # type: sq_string # elements: 1 # length: 46 Return the distance between any two rows in X. # name: # type: sq_string # elements: 1 # length: 6 pdist2 # name: # type: sq_string # elements: 1 # length: 3984 statistics: D = pdist2 ( X , Y ) statistics: D = pdist2 ( X , Y , Distance ) statistics: D = pdist2 ( X , Y , Distance , DistParameter ) statistics: D = pdist2 (…, Name , Value ) statistics: [ D , I ] = pdist2 (…, Name , Value ) Compute pairwise distance between two sets of vectors. D = pdist2 ( X , Y ) calculates the euclidean distance between each pair of observations in X and Y . Let X be an M×P matrix representing M points in P -dimensional space and Y be an N×P matrix representing another set of points in the same space. This function computes the M×N distance matrix D , where D (i,j) is the distance between X (i,:) and Y (j,:) . D = pdist2 ( X , Y , Distance ) returns the distance between each pair of observations in X and Y using the metric specified by Distance , which can be any of the following options. 'euclidean' Euclidean distance. 'fasteuclidean' Euclidean distance computed with an alternative algorithm which may be faster but might reduce accuracy. 'squaredeuclidean' Squared Euclidean distance. 'fastsquaredeuclidean' Euclidean distance computed with an alternative algorithm which may be faster but might reduce accuracy. 'seuclidean' standardized Euclidean distance. Each coordinate difference between the rows in X and the query matrix Y is scaled by dividing by the corresponding element of the standard deviation computed from X . A different scaling vector can be specified with the subsequent DistParameter input argument. 'mahalanobis' Mahalanobis distance, computed using a positive definite covariance matrix. A different covariance matrix can be specified with the subsequent DistParameter input argument. 'cityblock' City block distance. 'minkowski' Minkowski distance. The default exponent is 2. A different exponent can be specified with the subsequent DistParameter input argument. 'chebychev' Chebychev distance (maximum coordinate difference). 'cosine' One minus the cosine of the included angle between points (treated as vectors). 'correlation' One minus the sample linear correlation between observations (treated as sequences of values). 'hamming' Hamming distance, which is the percentage of coordinates that differ. 'jaccard' One minus the Jaccard coefficient, which is the percentage of nonzero coordinates that differ. 'spearman' One minus the sample Spearman’s rank correlation between observations (treated as sequences of values). @distfun Custom distance function handle. A distance function of the form function D2 = distfun ( XI , YI ) , where XI is a 1×P vector containing a single observation in P -dimensional space, YI is an N×P matrix containing an arbitrary number of observations in the same P -dimensional space, and D2 is an N×P vector of distances, where ( D2 k) is the distance between observations XI and ( YI k,:) . D = pdist2 ( X , Y , Distance , DistParameter ) returns the distance using the metric specified by Distance and DistParameter . The latter one can only be specified when the selected Distance is 'seuclidean' , 'minkowski' , and 'mahalanobis' . D = pdist2 (…, Name , Value ) for any previous arguments, modifies the computation using Name - Value parameters. D = pdist2 ( X , Y , Distance , 'Smallest' , K ) computes the distance using the metric specified by Distance and returns the K smallest pairwise distances to observations in X for each observation in Y in ascending order. D = pdist2 ( X , Y , Distance , DistParameter , 'Largest' , K ) computes the distance using the metric specified by Distance and DistParameter and returns the K largest pairwise distances in descending order. [ D , I ] = pdist2 (…, Name , Value ) also returns the matrix I , which contains the indices of the observations in X corresponding to the distances in D . You must specify either 'Smallest' or 'Largest' as an optional Name - Value pair argument to compute the second output argument. See also: pdist, knnsearch, rangesearch # name: # type: sq_string # elements: 1 # length: 54 Compute pairwise distance between two sets of vectors. # name: # type: sq_string # elements: 1 # length: 11 rangesearch # name: # type: sq_string # elements: 1 # length: 4841 statistics: idx = rangesearch ( X , Y , r ) statistics: [ idx , D ] = rangesearch ( X , Y , r ) statistics: […] = rangesearch (…, name , value ) Find all neighbors within specified distance from input data. idx = rangesearch ( X , Y , r ) returns all the points in X that are within distance r from the points in Y . X must be an N×P numeric matrix of input data, where rows correspond to observations and columns correspond to features or variables. Y is an M×P numeric matrix with query points, which must have the same numbers of column as X . r must be a nonnegative scalar value. idx is an M×1 cell array, where M is the number of observations in Y . The vector Idx {j} contains the indices of observations (rows) in X whose distances to Y (j,:) are not greater than r . [ idx , D ] = rangesearch ( X , Y , r ) also returns the distances, D , which correspond to the points in X that are within distance r from the points in Y . D is an M×1 cell array, where M is the number of observations in Y . The vector D {j} contains the distances of observations (rows) in X whose distances to Y (j,:) are not greater than r . Additional parameters can be specified by Name-Value pair arguments. Name Value 'P' is the Minkowski distance exponent and it must be a positive scalar. This argument is only valid when the selected distance metric is 'minkowski' . By default it is 2. 'Scale' is the scale parameter for the standardized Euclidean distance and it must be a nonnegative numeric vector of equal length to the number of columns in X . This argument is only valid when the selected distance metric is 'seuclidean' , in which case each coordinate of X is scaled by the corresponding element of 'scale' , as is each query point in Y . By default, the scale parameter is the standard deviation of each coordinate in X . 'Cov' is the covariance matrix for computing the mahalanobis distance and it must be a positive definite matrix matching the the number of columns in X . This argument is only valid when the selected distance metric is 'mahalanobis' . 'BucketSize' is the maximum number of data points in the leaf node of the Kd-tree and it must be a positive integer. This argument is only valid when the selected search method is 'kdtree' . 'SortIndices' is a boolean flag to sort the returned indices in ascending order by distance and it is true by default. When the selected search method is 'exhaustive' or the 'IncludeTies' flag is true, rangesearch always sorts the returned indices. 'Distance' is the distance metric used by rangesearch as specified below: 'euclidean' Euclidean distance. 'seuclidean' standardized Euclidean distance. Each coordinate difference between the rows in X and the query matrix Y is scaled by dividing by the corresponding element of the standard deviation computed from X . To specify a different scaling, use the 'Scale' name-value argument. 'cityblock' City block distance. 'chebychev' Chebychev distance (maximum coordinate difference). 'minkowski' Minkowski distance. The default exponent is 2. To specify a different exponent, use the 'P' name-value argument. 'mahalanobis' Mahalanobis distance, computed using a positive definite covariance matrix. To change the value of the covariance matrix, use the 'Cov' name-value argument. 'cosine' Cosine distance. 'correlation' One minus the sample linear correlation between observations (treated as sequences of values). 'spearman' One minus the sample Spearman’s rank correlation between observations (treated as sequences of values). 'hamming' Hamming distance, which is the percentage of coordinates that differ. 'jaccard' One minus the Jaccard coefficient, which is the percentage of nonzero coordinates that differ. @distfun Custom distance function handle. A distance function of the form function D2 = distfun ( XI , YI ) , where XI is a 1×P vector containing a single observation in P -dimensional space, YI is an N×P matrix containing an arbitrary number of observations in the same P -dimensional space, and D2 is an N×P vector of distances, where ( D2 k) is the distance between observations XI and ( YI k,:) . 'NSMethod' is the nearest neighbor search method used by rangesearch as specified below. 'kdtree' Creates and uses a Kd-tree to find nearest neighbors. 'kdtree' is the default value when the number of columns in X is less than or equal to 10, X is not sparse, and the distance metric is 'euclidean' , 'cityblock' , 'manhattan' , 'chebychev' , or 'minkowski' . Otherwise, the default value is 'exhaustive' . This argument is only valid when the distance metric is one of the four aforementioned metrics. 'exhaustive' Uses the exhaustive search algorithm by computing the distance values from all the points in X to each point in Y . See also: knnsearch, pdist2 # name: # type: sq_string # elements: 1 # length: 61 Find all neighbors within specified distance from input data. # name: # type: sq_string # elements: 1 # length: 10 squareform # name: # type: sq_string # elements: 1 # length: 1182 statistics: zOut = squareform ( yIn ) statistics: yOut = squareform ( zIn ) statistics: zOut = squareform ( yIn , 'tovector' ) statistics: yOut = squareform ( zIn , 'tomatrix' ) Interchange between distance matrix and distance vector formats. Converts between a hollow (diagonal filled with zeros), square, and symmetric matrix and a vector of the lower triangular part. Its target application is the conversion of the vector returned by pdist into a distance matrix. It performs the opposite operation if input is a matrix. If x is a numeric or logical vector, its number of elements must fit into the triangular part of a matrix (main diagonal excluded). In other words, numel ( x ) = n * ( n - 1) / 2 for some integer n . The resulting matrix will be n by n . If x is a numeric or logical distance matrix, it must be square and the diagonal entries of x must all be zeros. If x is not symmetric, only the lower triangular part is used. The second argument is used to specify the output type in case the distance input is a scalar. Accepted values are 'tomatrix' (or 'tom' ) and 'tovector' (or 'tov' ). If not specified, it defaults to 'tomatrix' otherwise. See also: pdist # name: # type: sq_string # elements: 1 # length: 64 Interchange between distance matrix and distance vector formats. statistics-release-1.9.2/inst/Nearest_Neighbors/hnswSearcher.m000066400000000000000000001245501524624707500245460ustar00rootroot00000000000000## Copyright (C) 2025 Swayam Shah ## Copyright (C) 2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef hnswSearcher ## -*- texinfo -*- ## @deftp {statistics} hnswSearcher ## ## Hierarchical Navigable Small World (HNSW) nearest neighbor searcher class. ## ## The @code{hnswSearcher} class implements the HNSW algorithm for efficient ## nearest neighbor queries. It stores training data and supports various ## distance metrics for performing searches. The HNSW algorithm builds a ## multilayer graph structure that enables fast approximate nearest neighbor ## searches by navigating through the graph. It facilitates nearest neighbor ## queries search using @code{knnsearch}. ## ## You can either use the @code{hnswSearcher} class constructor or the ## @code{createns} function to create an @qcode{hnswSearcher} object. ## ## @seealso{createns, ExhaustiveSearcher, KDTreeSearcher, knnsearch} ## @end deftp properties(SetAccess = private, Hidden) HNSWGraph # HNSW graph structure endproperties properties(SetAccess = private) ## -*- texinfo -*- ## @deftp {hnswSearcher} {property} Distance ## ## Distance metric ## ## Distance metric used for searches, specified as a character vector (e.g., ## @qcode{'euclidean'}, @qcode{'minkowski'}, @qcode{'cityblock'}). Default ## is @qcode{'euclidean'}. Supported metrics align with those in ## @code{pdist2}. This property is private and cannot be modified after ## object creation. ## ## @end deftp Distance = 'euclidean' ## -*- texinfo -*- ## @deftp {hnswSearcher} {property} DistParameter ## ## Distance parameter ## ## The type and value of the distance parameter depends on the selected ## @qcode{Distance} metric and can be any of the following: ## ## @itemize ## @item For @qcode{'minkowski'}, a positive scalar exponent (default 2). ## @item For @qcode{'seuclidean'}, a nonnegative vector of scaling factors ## matching the number of columns in @qcode{X} (default is standard ## deviation of @qcode{X}). ## @item For @qcode{'mahalanobis'}, a positive definite covariance matrix ## matching the dimensions of @qcode{X} (default is @code{cov (@var{X})}). ## @item Empty for other metrics. ## @end itemize ## ## This property is private and cannot be modified after object creation. ## ## @end deftp DistParameter = [] ## -*- texinfo -*- ## @deftp {hnswSearcher} {property} MaxNumLinksPerNode ## ## Number of connections created for each node ## ## Maximum number of neighbors per node in the HNSW graph. Affects graph ## connectivity and search accuracy. Default value is 16. This property is ## private and cannot be modified after object creation. ## ## @end deftp MaxNumLinksPerNode = 16 ## -*- texinfo -*- ## @deftp {hnswSearcher} {property} TrainSetSize ## ## Number of potential nearest neighbors ## ## Size of the dynamic candidate list during graph construction. Higher ## values improve accuracy at the cost of construction time. Default value ## is 200. This property is private and cannot be modified after object ## creation. ## ## @end deftp TrainSetSize = 200 ## -*- texinfo -*- ## @deftp {hnswSearcher} {property} X ## ## Point data ## ## Point data, specified as an @math{N*P} numeric matrix where each row is ## an observation and each column is a feature. This property is private ## and cannot be modified after object creation. ## ## Data of class @qcode{single} is stored and searched in single ## precision, any other numeric class is converted to @qcode{double}. ## ## @end deftp X = [] endproperties methods (Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) if (isscalar (this)) fprintf ("\n hnswSearcher with properties:\n\n"); fprintf ("%+25s: %d\n", 'MaxNumLinksPerNode', this.MaxNumLinksPerNode); fprintf ("%+25s: %d\n", 'TrainSetSize', this.TrainSetSize); fprintf ("%+25s: '%s'\n", 'Distance', this.Distance); if (! isempty (this.DistParameter)) if (isscalar (this.DistParameter)) fprintf ("%+25s: %g\n", 'DistParameter', this.DistParameter); elseif (isvector (this.DistParameter)) fprintf ("%+25s: %s\n", 'DistParameter', ... mat2str (this.DistParameter)); else fprintf ("%+25s: [%dx%d %s]\n", 'DistParameter', ... size (this.DistParameter), class (this.DistParameter)); endif else fprintf ("%+25s: []\n", 'DistParameter'); endif fprintf ("%+25s: [%dx%d %s]\n", 'X', size (this.X), class (this.X)); endif endfunction ## Class specific subscripted reference function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' error ("hnswSearcher.subsref: () indexing not supported."); case '{}' error ("hnswSearcher.subsref: {} indexing not supported."); case '.' if (! ischar (s.subs)) error (strcat ("hnswSearcher.subsref: property", ... " name must be a character vector.")); endif try out = this.(s.subs); catch error (strcat ("hnswSearcher.subsref: unrecognized", ... " property: '%s'."), s.subs); end_try_catch endswitch ## Chained references if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction ## Class specific subscripted assignment function this = subsasgn (this, s, val) switch s.type case '()' error ("hnswSearcher.subsasgn: () indexing not supported."); case '{}' error ("hnswSearcher.subsasgn: {} indexing not supported."); case '.' if (! ischar (s.subs)) error (strcat ("hnswSearcher.subsasgn: property", ... " name must be a character vector.")); endif switch (s.subs) case 'X' error (strcat ("hnswSearcher.subsasgn: 'X' is", ... " read-only and cannot be modified.")); case 'HNSWGraph' error (strcat ("hnswSearcher.subsasgn: 'HNSWGraph' is", ... " read-only and cannot be modified.")); case 'Distance' error (strcat ("hnswSearcher.subsasgn: 'Distance' is", ... " read-only and cannot be modified.")); case 'DistParameter' error (strcat ("hnswSearcher.subsasgn: 'DistParameter'", ... " is read-only and cannot be modified.")); case 'MaxNumLinksPerNode' error (strcat ("hnswSearcher.subsasgn: 'MaxNumLinksPerNode'", ... " is read-only and cannot be modified.")); case 'TrainSetSize' error (strcat ("hnswSearcher.subsasgn: 'TrainSetSize'", ... " is read-only and cannot be modified.")); otherwise error (strcat ("hnswSearcher.subsasgn:", ... " unrecognized property: '%s'."), s.subs); endswitch endswitch endfunction endmethods methods ## -*- texinfo -*- ## @deftypefn {hnswSearcher} {@var{obj} =} hnswSearcher (@var{X}) ## @deftypefnx {hnswSearcher} {@var{obj} =} hnswSearcher (@var{X}, @var{name}, @var{value}) ## ## Create an @qcode{hnswSearcher} object for approximate nearest neighbor ## searches. ## ## @code{@var{obj} = hnswSearcher (@var{X})} constructs an ## @qcode{hnswSearcher} object with training data @var{X} using the ## default @qcode{'euclidean'} distance metric. @var{X} must be an ## @math{N*P} numeric matrix, where rows represent observations and columns ## represent features. ## ## @code{@var{obj} = hnswSearcher (@var{X}, @var{name}, @var{value})} ## allows customization through name-value pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Distance'} @tab Distance metric, specified as a ## character vector (e.g., @qcode{'euclidean'}, @qcode{'minkowski'}, ## @qcode{'cityblock'}). Default is @qcode{'euclidean'}. See @code{pdist2} ## for supported metrics. ## ## @item @qcode{'P'} @tab Minkowski distance exponent, a positive ## scalar. Valid only when @qcode{'Distance'} is @qcode{'minkowski'}. ## Default is 2. ## ## @item @qcode{'Scale'} @tab Nonnegative vector of scaling factors ## matching the number of columns in @var{X}. Valid only when ## @qcode{'Distance'} is @qcode{'seuclidean'}. Default is @code{std (X)}. ## ## @item @qcode{'Cov'} @tab Positive definite covariance matrix ## matching the number of columns in @var{X}. Valid only when ## @qcode{'Distance'} is @qcode{'mahalanobis'}. Default is @code{cov (X)}. ## ## @item @qcode{'MaxNumLinksPerNode'} @tab Maximum number of neighbors ## per node in the HNSW graph, a positive integer. Default is 16. ## ## @item @qcode{'TrainSetSize'} @tab Size of the dynamic candidate ## list during graph construction, a positive integer. Default is 200. ## @end multitable ## ## @seealso{hnswSearcher, knnsearch, createns, pdist2} ## @end deftypefn function obj = hnswSearcher (X, varargin) ## Initial input validation if (nargin < 1) error ("hnswSearcher: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error ("hnswSearcher: Name-Value arguments must be in pairs."); endif ## Validate X if (isempty (X)) error ("hnswSearcher: X cannot be empty."); endif if (! (isnumeric (X) && ismatrix (X) && all (isfinite (X(:))))) error ("hnswSearcher: X must be a finite numeric matrix."); endif ## Single precision is carried through, but every other class is ## converted up to double, as MATLAB does. Integer data would ## otherwise round each coordinate difference and corrupt distances. if (! isa (X, "single")) X = double (X); endif obj.X = X; N = size (X, 1); ## Default values Distance = 'euclidean'; P = []; S = []; C = []; MaxNumLinksPerNode = min (16, N); TrainSetSize = min (200, N); TrainSetSize = max (TrainSetSize, MaxNumLinksPerNode); ## Parse optional parameters while (numel (varargin) > 0) switch (lower (varargin{1})) case 'distance' Distance = varargin{2}; case 'p' P = varargin{2}; case 'scale' S = varargin{2}; case 'cov' C = varargin{2}; case 'maxnumlinkspernode' MaxNumLinksPerNode = varargin{2}; case 'trainsetsize' TrainSetSize = varargin{2}; otherwise error ("hnswSearcher: invalid parameter name: '%s'.", varargin{1}); endswitch varargin(1:2) = []; endwhile ## Validate Distance valid_metrics = {'euclidean', 'minkowski', 'seuclidean', ... 'mahalanobis', 'cityblock', 'manhattan', ... 'chebychev', 'cosine', 'correlation', ... 'spearman', 'hamming', 'jaccard'}; if (ischar (Distance)) if (! any (strcmpi (valid_metrics, Distance))) error ("hnswSearcher: unsupported distance metric '%s'.", Distance); endif obj.Distance = Distance; else error ("hnswSearcher: 'Distance' must be a string."); endif ## Set DistParameter if (strcmpi (obj.Distance, 'minkowski')) if (isempty (P)) obj.DistParameter = 2; else if (! (isscalar (P) && isnumeric (P) && P > 0 && isfinite (P))) error ("hnswSearcher: 'P' must be a positive finite scalar."); endif obj.DistParameter = P; endif elseif (strcmpi (obj.Distance, 'seuclidean')) if (isempty (S)) obj.DistParameter = std (X, [], 1); else if (! (isvector (S) && isnumeric (S) && all (S >= 0) && all (isfinite (S)) && length (S) == columns (X))) error (strcat ("hnswSearcher: 'Scale' must be a", ... " nonnegative vector matching X columns.")); endif obj.DistParameter = S; endif elseif (strcmpi (obj.Distance, 'mahalanobis')) if (isempty (C)) obj.DistParameter = cov (X); else if (! (ismatrix (C) && isnumeric (C) && all (isfinite (C)(:)) && rows (C) == columns (C) && rows (C) == columns (X))) error (strcat ("hnswSearcher: 'Cov' must be a square", ... " matrix matching X columns.")); endif if (! issymmetric (C)) error (strcat ("hnswSearcher: 'Cov' must be symmetric", ... " for mahalanobis.")); endif [~, p] = chol (C); if (p != 0) error (strcat ("hnswSearcher: 'Cov' must be positive", ... " definite for mahalanobis.")); endif obj.DistParameter = C; endif else obj.DistParameter = []; endif ## Validate MaxNumLinksPerNode and TrainSetSize if (! (isscalar (MaxNumLinksPerNode) && isnumeric (MaxNumLinksPerNode) && MaxNumLinksPerNode > 0 && MaxNumLinksPerNode == fix (MaxNumLinksPerNode))) error (strcat ("hnswSearcher: 'MaxNumLinksPerNode'", ... " must be a positive integer.")); endif if (! (isscalar (TrainSetSize) && isnumeric (TrainSetSize) && TrainSetSize > 0 && TrainSetSize == fix (TrainSetSize))) error ("hnswSearcher: 'TrainSetSize' must be a positive integer."); endif if (TrainSetSize > N) error (strcat ("hnswSearcher: 'TrainSetSize' cannot", ... " exceed the number of rows in X.")); endif if (MaxNumLinksPerNode > TrainSetSize) error (strcat ("hnswSearcher: 'MaxNumLinksPerNode'", ... " cannot exceed 'TrainSetSize'.")); endif obj.MaxNumLinksPerNode = MaxNumLinksPerNode; obj.TrainSetSize = TrainSetSize; ## Build HNSW graph obj.HNSWGraph = build_hnsw (X, obj.Distance, obj.DistParameter, ... MaxNumLinksPerNode, TrainSetSize); endfunction ## -*- texinfo -*- ## @deftypefn {hnswSearcher} {[@var{idx}, @var{D}] =} knnsearch (@var{obj}, @var{Y}) ## @deftypefnx {hnswSearcher} {[@var{idx}, @var{D}] =} knnsearch (@var{obj}, @var{Y}, @var{name}, @var{value}) ## ## Find the nearest neighbors in the training data to query points. ## ## @code{[@var{idx}, @var{D}] = knnsearch (@var{obj}, @var{Y})} returns the ## indices @var{idx} and distances @var{D} of the nearest neighbor in ## @var{obj.X} to each point in @var{Y}, using the distance metric specified ## in @var{obj.Distance}. ## ## @itemize ## @item @var{obj} is an @qcode{hnswSearcher} object. ## @item @var{Y} is an @math{M*P} numeric matrix of query points, where ## @math{P} must match the number of columns in @var{obj.X}. ## @item @var{idx} contains the indices of the nearest neighbors in ## @var{obj.X}. ## @item @var{D} contains the corresponding distances. ## @end itemize ## ## @var{idx} is always of class @qcode{double}. @var{D} is of class ## @qcode{single} when either @var{obj.X} or @var{Y} is @qcode{single}, ## in which case the distances are computed in single precision, and of ## class @qcode{double} otherwise. ## ## @code{[@var{idx}, @var{D}] = knnsearch (@var{obj}, @var{Y}, @var{name}, ## @var{value})} ## allows additional options via name-value pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'K'} @tab A positive integer specifying the number of ## nearest neighbors to find. Default is 1. A value larger than the ## number of observations in the training data is answered with all of ## them, since there are no more neighbors to return. ## ## @item @qcode{'SearchSetSize'} @tab A positive integer specifying the ## size of the candidate list of nearest neighbors for a single query point ## during the search process. Default is @qcode{max (10, @var{C})}, where ## @var{C} is the number of columns in @var{obj.X}. @qcode{'SearchSetSize'} ## must be at least @var{C} and no more than the number of rows in training ## data @var{obj.X}. ## @end multitable ## ## @seealso{hnswSearcher, pdist2} ## @end deftypefn function [idx, D] = knnsearch (obj, Y, varargin) ## Initial input validation if (nargin < 2) error ("hnswSearcher.knnsearch: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("hnswSearcher.knnsearch:", ... " Name-Value arguments must be in pairs.")); endif ## Get training data size [N, C] = size (obj.X); ## Validate Y. A non-finite query is answered rather than refused: its ## distances come back Inf or NaN, which is the honest result and what ## MATLAB returns. An empty query is answered with an empty result. if (! (isnumeric (Y) && isreal (Y) && ismatrix (Y))) error ("hnswSearcher.knnsearch: Y must be a real numeric matrix."); endif if (C != size (Y, 2)) error (strcat ("hnswSearcher.knnsearch: Y must have the same", ... " number of columns as the training data in OBJ.X.")); endif ## Default values K = 1; SearchSetSize = max (10, C); ## Parse options while (numel (varargin) > 0) switch (lower (varargin{1})) case 'k' K = varargin{2}; if (! (isscalar (K) && isnumeric (K) && K >= 1 && K == fix (K) && isfinite (K))) error ("hnswSearcher.knnsearch: 'K' must be a positive integer."); endif case 'searchsetsize' SearchSetSize = varargin{2}; if (! (isscalar (SearchSetSize) && isnumeric (SearchSetSize) && SearchSetSize >= 1 && SearchSetSize == fix (SearchSetSize) && isfinite (SearchSetSize))) error (strcat ("hnswSearcher.knnsearch: 'SearchSetSize' must", ... " be a positive integer.")); endif if (SearchSetSize < C || SearchSetSize > N) error (strcat ("hnswSearcher.knnsearch: 'SearchSetSize' must", ... " be at least the number of features but no", ... " more than the sample size of the training data.")); endif otherwise error (strcat ("hnswSearcher.knnsearch: invalid", ... " parameter name: '%s'."), varargin{1}); endswitch varargin(1:2) = []; endwhile ## More neighbours cannot be returned than there are points to return, ## so asking for more is answered with all of them rather than with a ## matrix wider than the training data padded out with NaN. K = min (K, N); ## The search only ever inspects SearchSetSize candidates, so it cannot ## produce K neighbours while that is the smaller of the two: asking for ## every point used to come back mostly NaN. SearchSetSize = min (max (SearchSetSize, K), N); ## Distances are computed in single precision when either the training ## data or the query is single, and in double otherwise. The indices ## are always double. cls = "double"; if (isa (obj.X, "single") || isa (Y, "single")) cls = "single"; endif Y = cast (Y, cls); if (isempty (Y)) idx = zeros (rows (Y), K); D = zeros (rows (Y), K, cls); return; endif ## Search HNSW graph idx = cell (rows (Y), 1); D = cell (rows (Y), 1); for i = 1:rows (Y) [temp_idx, temp_D] = search_hnsw (obj.HNSWGraph, Y(i,:), obj.X, ... obj.Distance, obj.DistParameter, ... K, SearchSetSize); [sorted_D, sort_idx] = sort (temp_D); idx{i} = temp_idx(sort_idx); D{i} = sorted_D; endfor idx_mat = NaN (rows (Y), K); D_mat = NaN (rows (Y), K, cls); for i = 1:rows (Y) len = min (K, length (idx{i})); idx_mat(i, 1:len) = idx{i}(1:len); D_mat(i, 1:len) = D{i}(1:len); endfor idx = idx_mat; D = D_mat; endfunction endmethods endclassdef ## Private Function to Build HNSW graph function graph = build_hnsw (X, dist, param, MaxNumLinksPerNode, TrainSetSize) N = size (X, 1); if (N < 1) error ("build_hnsw: X must have at least one point."); endif max_layers = floor (log2 (max (N, 2))) + 1; graph.layers = cell (max_layers, 1); graph.entry_point = 1; mL = 1 / log (MaxNumLinksPerNode); ## Initialize graph with empty adjacency lists for L = 1:max_layers graph.layers{L} = cell (N, 1); endfor ## Handle single-point case if (N == 1) graph.layers{1}{1} = []; return; endif ## Add points to graph for i = 1:N layer = min (max_layers - 1, floor (-log (rand ()) * mL)); for L = 0:layer if (i == 1) graph.layers{L+1}{i} = []; continue; endif ## Find nearest neighbors in current layer [neighbors, dists] = search_hnsw_layer (graph, X(i,:), X, dist, param, ... MaxNumLinksPerNode, ... TrainSetSize, L, ... graph.entry_point); graph.layers{L+1}{i} = neighbors; ## Update neighbors' connections for j = neighbors if (length (graph.layers{L+1}{j}) < MaxNumLinksPerNode) graph.layers{L+1}{j} = [graph.layers{L+1}{j}, i]; else ## Select MaxNumLinksPerNode closest neighbors all_neighbors = [graph.layers{L+1}{j}, i]; dists_j = pdist2 (X(all_neighbors,:), X(j,:), dist, param); [~, sort_idx] = sort (dists_j); graph.layers{L+1}{j} = all_neighbors(sort_idx(1:MaxNumLinksPerNode)); endif endfor if (L == layer && i > 1) graph.entry_point = i; endif endfor endfor endfunction ## Private Function to Search HNSW graph for k nearest neighbors function [indices, distances] = search_hnsw (graph, Y, X, dist, param, ... K, SearchSetSize) max_layers = length (graph.layers); current_entry = graph.entry_point; ## Navigate to the lowest layer (from the top layer down to layer 1) for L = max_layers:-1:2 [new_candidates, new_dists] = search_hnsw_layer (graph, Y, X, ... dist, param, ... 1, SearchSetSize, L-1, ... current_entry); current_entry = new_candidates(1); endfor ## Search in the base layer [indices, distances] = search_hnsw_layer (graph, Y, X, dist, param, ... K, SearchSetSize, 0, current_entry); endfunction ## Private Function Search a single HNSW layer (optimized) function [indices, distances] = search_hnsw_layer (graph, Y, X, dist, param, ... Points, SetSize, L, ... entry_points) N = size (X, 1); visited = false (N, 1); ## Pre-allocate fixed-size arrays with generous capacity capacity = N; ## Candidate list (nodes to explore) cand_nodes = zeros (1, capacity); cand_dists = zeros (1, capacity); cand_count = 0; ## Best results list (top SetSize closest nodes, kept sorted) best_nodes = zeros (1, capacity); best_dists_arr = zeros (1, capacity); best_count = 0; ## Initialize with entry points entry_points = entry_points(:)'; num_entries = length (entry_points); init_dists = pdist2 (X(entry_points,:), Y, dist, param); for i = 1:num_entries ep = entry_points(i); visited(ep) = true; cand_count = cand_count + 1; cand_nodes(cand_count) = ep; cand_dists(cand_count) = init_dists(i); best_count = best_count + 1; best_nodes(best_count) = ep; best_dists_arr(best_count) = init_dists(i); endfor ## Cache layer adjacency list reference layer_adj = graph.layers{L+1}; ## Main search loop while (cand_count > 0) ## Find minimum distance candidate [~, idx] = min (cand_dists(1:cand_count)); closest = cand_nodes(idx); ## Swap-and-pop removal (O(1) instead of O(n) array shift) cand_nodes(idx) = cand_nodes(cand_count); cand_dists(idx) = cand_dists(cand_count); cand_count = cand_count - 1; ## Early termination check if (best_count > 0 && cand_count > 0) if (max (best_dists_arr(1:best_count)) < min (cand_dists(1:cand_count))) break; endif endif ## Get neighbors of closest node neighbors = layer_adj{closest}; if (isempty (neighbors)) continue; endif ## Filter to unvisited neighbors only unvisited_mask = ! visited(neighbors); new_neighbors = neighbors(unvisited_mask); if (isempty (new_neighbors)) continue; endif ## Mark all new neighbors as visited visited(new_neighbors) = true; ## Batch distance computation (single pdist2 call) new_dists = pdist2 (X(new_neighbors,:), Y, dist, param); ## Add each new neighbor to candidates and best lists num_new = length (new_neighbors); for i = 1:num_new n = new_neighbors(i); d = new_dists(i); ## Add to candidates cand_count = cand_count + 1; cand_nodes(cand_count) = n; cand_dists(cand_count) = d; ## Add to best list best_count = best_count + 1; best_nodes(best_count) = n; best_dists_arr(best_count) = d; endfor ## Trim best list once per batch instead of per element if (best_count > SetSize) [best_dists_arr(1:best_count), sort_idx] = sort (best_dists_arr(1:best_count)); best_nodes(1:best_count) = best_nodes(sort_idx); best_count = SetSize; endif endwhile ## Return top Points results if (best_count > Points) [best_dists_arr(1:best_count), sort_idx] = sort (best_dists_arr(1:best_count)); best_nodes(1:best_count) = best_nodes(sort_idx); indices = best_nodes(1:Points); distances = best_dists_arr(1:Points); else indices = best_nodes(1:best_count); distances = best_dists_arr(1:best_count); endif endfunction %!demo %! ## Create an hnswSearcher with Euclidean distance %! X = [1, 2; 3, 4; 5, 6]; %! obj = hnswSearcher (X); %! ## Find the nearest neighbor to [2, 3] %! Y = [2, 3]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! disp ('Nearest neighbor index:'); %! disp (idx); %! disp ('Distance:'); %! disp (D); %!demo %! ## Create an hnswSearcher with Minkowski distance (P=3) %! X = [0, 0; 1, 0; 2, 0]; %! obj = hnswSearcher (X, 'Distance', 'minkowski', 'P', 3); %! ## Find the nearest neighbor to [1, 0] %! Y = [1, 0]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! disp ('Nearest neighbor index:'); %! disp (idx); %! disp ('Distance:'); %! disp (D); ## Test Cases %!test %! ## the graph is drawn from rand, so seed it: the search is approximate %! rand ("seed", 42); %! load fisheriris %! X = meas; %! obj = hnswSearcher (X, 'Distance', 'chebychev'); %! Y = X(30:35,:); %! [idx, D] = knnsearch (obj, Y, 'K', 4); %! ## Under chebychev these queries have several equidistant neighbours, so %! ## which of them is listed first is not defined. Assert the distances, %! ## which are, and that each returned index really sits at the distance %! ## reported for it. %! assert_equal (D, [[0 0.1000 0.1000 0.2000]; [0 0.1000 0.1000 0.1000]; [0 0.2000 ... %! 0.2000 0.2000]; [0 0.3000 0.3000 0.3000]; [0 0.2000 0.3000 ... %! 0.3000]; [0 0.1000 0.1000 0.1000]], 5e-15) %! for i = 1:rows (Y) %! assert_equal (numel (unique (idx(i,:))), 4); %! for j = 1:4 %! assert_equal (max (abs (X(idx(i,j),:) - Y(i,:))), D(i,j), 5e-15); %! endfor %! endfor %!test %! ## the graph is drawn from rand, so seed it: the search is approximate %! rand ("seed", 42); %! load fisheriris %! X = meas; %! C = cov (X); %! obj = hnswSearcher (X, 'Distance', 'mahalanobis', 'Cov', C); %! Y = X(120:125,:); %! [idx, D] = knnsearch (obj, Y, 'K', 2); %! assert_equal (idx(1, :), [120 82]) %! assert_equal (idx(4, :), [123 106]) %! assert_equal (idx(5, :), [124 127]) %! assert_equal (idx(6, :), [125 57]) %! assert_equal (D(1, :), [0 0.7734], 1e-4) %! assert_equal (D(4, :), [0 0.8452], 1e-4) %! assert_equal (D(5, :), [0 0.4152], 1e-4) %! assert_equal (D(6, :), [0 0.7322], 1e-4) %!test %! ## Basic constructor with default Euclidean %! X = [1, 2; 3, 4; 5, 6]; %! obj = hnswSearcher (X); %! assert_equal (obj.X, X); %! assert_equal (obj.Distance, "euclidean"); %! assert_equal (isempty (obj.DistParameter), true); %!test %! ## Minkowski distance with custom P %! X = [0, 0; 1, 1; 2, 2]; %! obj = hnswSearcher (X, 'Distance', 'minkowski', 'P', 3); %! assert_equal (obj.Distance, "minkowski"); %! assert_equal (obj.DistParameter, 3); %!test %! ## Seuclidean distance with custom Scale %! X = [1, 2; 3, 4; 5, 6]; %! S = [1, 2]; %! obj = hnswSearcher (X, 'Distance', 'seuclidean', 'Scale', S); %! assert_equal (obj.Distance, "seuclidean"); %! assert_equal (obj.DistParameter, S); %!test %! ## Mahalanobis distance with custom Cov %! X = [1, 2; 3, 4; 5, 6]; %! C = [1, 0; 0, 1]; %! obj = hnswSearcher (X, 'Distance', 'mahalanobis', 'Cov', C); %! assert_equal (obj.Distance, "mahalanobis"); %! assert_equal (obj.DistParameter, C); %!test %! ## knnsearch with Euclidean distance %! X = [1, 2; 3, 4; 5, 6]; %! obj = hnswSearcher (X); %! Y = [2, 3]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (ismember (idx, [2]), true); %! assert_equal (abs (D - sqrt (2)) < 1e-2, true); %!test %! ## knnsearch with Cityblock distance %! X = [0, 0; 1, 1; 2, 2]; %! obj = hnswSearcher (X, 'Distance', 'cityblock'); %! Y = [1, 0]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (ismember (idx, [1, 2]), true); %! assert_equal (abs (D - 1) < 1e-2, true); %!test %! ## knnsearch with Chebychev distance %! X = [1, 1; 2, 3; 4, 2]; %! obj = hnswSearcher (X, 'Distance', 'chebychev'); %! Y = [2, 2]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (ismember (idx, [1, 2]), true); %! assert_equal (abs (D - 1) < 1e-2, true); %!test %! ## knnsearch with Minkowski P=3 %! X = [0, 0; 1, 0; 2, 0]; %! obj = hnswSearcher (X, 'Distance', 'minkowski', 'P', 3); %! Y = [1, 0]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (ismember (idx, [2]), true); %! assert_equal (abs (D - 0) < 1e-2, true); %!test %! ## Diverse dataset with Euclidean %! X = [0, 10; 5, 5; 10, 0]; %! obj = hnswSearcher (X); %! Y = [5, 5]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (ismember (idx, [2]), true); %! assert_equal (abs (D - 0) < 1e-2, true); %!test %! ## High-dimensional data with Cityblock %! X = [1, 2, 3; 4, 5, 6; 7, 8, 9]; %! obj = hnswSearcher (X, 'Distance', 'cityblock'); %! Y = [4, 5, 6]; %! [idx, D] = knnsearch (obj, Y, 'K', 1); %! assert_equal (ismember (idx, [2]), true); %! assert_equal (abs (D - 0) < 1e-2, true); ## Raising 'SearchSetSize' must not make the search worse. The early ## termination check read the last element of the best list on the assumption ## that the list was sorted, but the list is only sorted when it is trimmed, ## and it is only trimmed once it grows past 'SearchSetSize'. For a large ## enough 'SearchSetSize' the trim never ran, the check compared whatever ## distance happened to have been appended last, and the walk stopped at the ## entry point -- returning the same neighbour for every query. %!test %! ## both the data and the graph are drawn, so seed both: the search is %! ## approximate and an unlucky graph misses a true neighbour %! rand ("seed", 42); %! randn ("seed", 4); %! X = [randn(20,2); randn(20,2) + 3; randn(20,2) + [0, 6]]; %! Y = randn (8, 2); %! hn = hnswSearcher (X); %! es = ExhaustiveSearcher (X); %! truth = knnsearch (es, Y, 'K', 3); %! for ess = [5, 16, 31, 32, 33, 60] %! assert_equal (knnsearch (hn, Y, 'K', 3, 'SearchSetSize', ess), truth); %! endfor %!test %! ## the boundary that used to break was 2 * MaxNumLinksPerNode %! rand ("seed", 42); %! randn ("seed", 4); %! X = [randn(20,2); randn(20,2) + 3; randn(20,2) + [0, 6]]; %! Y = randn (8, 2); %! hn = hnswSearcher (X); %! assert_equal (knnsearch (hn, Y, 'K', 3, 'SearchSetSize', 31), ... %! knnsearch (hn, Y, 'K', 3, 'SearchSetSize', 32)); %!test %! ## distinct queries must not collapse onto one index %! rand ("seed", 42); %! randn ("seed", 4); %! X = [randn(20,2); randn(20,2) + 3; randn(20,2) + [0, 6]]; %! Y = randn (8, 2); %! idx = knnsearch (hnswSearcher (X), Y, 'K', 3, 'SearchSetSize', 60); %! assert_equal (numel (unique (idx(:,1))) > 1, true); ## Test Input Validation %!error ... %! hnswSearcher () %!error ... %! hnswSearcher (ones (3,2), 'Distance') %!error ... %! hnswSearcher ([]) %!error ... %! hnswSearcher ('abc') %!error ... %! hnswSearcher ([1; Inf; 3]) %!error ... %! hnswSearcher (ones (3,2), 'foo', 'bar') %!error ... %! hnswSearcher (ones (3,2), 'Distance', 'invalid') %!error ... %! hnswSearcher (ones (3,2), 'Distance', 1) %!error ... %! hnswSearcher (ones (3,2), 'Distance', 'minkowski', 'P', -1) %!error ... %! hnswSearcher (ones (3,2), 'Distance', 'seuclidean', 'Scale', [-1, 1]) %!error ... %! hnswSearcher (ones (3,2), 'Distance', 'mahalanobis', 'Cov', ones (3,3)) %!error ... %! hnswSearcher (ones (3,2), 'Distance', 'mahalanobis', 'Cov', [1, 2; 3, 4]) %!error ... %! hnswSearcher (ones (3,2), 'Distance', 'mahalanobis', 'Cov', -eye (2)) %!error ... %! hnswSearcher (ones (3,2), 'MaxNumLinksPerNode', 0) %!error ... %! hnswSearcher (ones (3,2), 'TrainSetSize', -1) %!error ... %! hnswSearcher (ones (3,2), 'TrainSetSize', 4) %!error ... %! hnswSearcher (ones (3,2), 'MaxNumLinksPerNode', 200, 'TrainSetSize', 100) %!error ... %! knnsearch (hnswSearcher (ones (3,2))) %!error ... %! knnsearch (hnswSearcher (ones (3,2)), ones (3,2), 'K') %!error ... %! knnsearch (hnswSearcher (ones (3,2)), {1, 2}) %!error ... %! knnsearch (hnswSearcher (ones (3,2)), ones (3,2), 'K', 0) %!error ... %! knnsearch (hnswSearcher (ones (3,2)), ones (3,2), 'foo', 'bar') %!error ... %! obj = hnswSearcher (ones (3,2)); obj(1) %!error ... %! obj = hnswSearcher (ones (3,2)); obj{1} %!error ... %! obj = hnswSearcher (ones (3,2)); obj.invalid %!error ... %! obj = hnswSearcher (ones (3,2)); obj(1) = 1 %!error ... %! obj = hnswSearcher (ones (3,2)); obj{1} = 1 %!error ... %! obj = hnswSearcher (ones (3,2)); obj.X = 1 %!error ... %! obj = hnswSearcher (ones (3,2)); obj.HNSWGraph = 1 %!error ... %! obj = hnswSearcher (ones (3,2)); obj.Distance = 'invalid' %!error ... %! obj = hnswSearcher (ones (3,2)); obj.Distance = 1 %!error ... %! obj = hnswSearcher (ones (3,2), 'Distance', 'minkowski'); obj.DistParameter = -1 %!error ... %! obj = hnswSearcher (ones (3,2), 'Distance', 'seuclidean'); obj.DistParameter = [-1, 1] %!error ... %! obj = hnswSearcher (ones (3,2), 'Distance', 'mahalanobis'); obj.DistParameter = ones (3,3) %!error ... %! obj = hnswSearcher (ones (3,2), 'Distance', 'mahalanobis'); obj.DistParameter = -eye (2) %!error ... %! obj = hnswSearcher (ones (3,2)); obj.DistParameter = 1 %!error ... %! obj = hnswSearcher (ones (3,2)); obj.MaxNumLinksPerNode = 0 %!error ... %! obj = hnswSearcher (ones (3,2)); obj.TrainSetSize = -1 %!error ... %! obj = hnswSearcher (ones (3,2)); obj.efSearch = 1.5 %!error ... %! obj = hnswSearcher (ones (3,2)); obj.invalid = 1 ## More neighbours than there are points, and queries that are empty or not ## finite, are answered rather than refused. Verified against MATLAB R2024a. %!shared Xs, Ys, hs %! ## the graph is built from rand, so seed it: the search is approximate and %! ## an unlucky graph misses a true neighbour %! rand ("seed", 42); %! randn ("seed", 4); %! Xs = [randn(20,2); randn(20,2) + 3; randn(20,2) + [0, 6]]; %! Ys = randn (8, 2); %! hs = hnswSearcher (Xs); %!test # asking for every point returns every point, not a padding of NaN %! [idx, D] = knnsearch (hs, Ys, "K", 60); %! assert_equal (size (idx), [8, 60]); %! assert_equal (any (isnan (idx(:))), false); %! for r = 1:8 %! assert_equal (sort (idx(r,:)), 1:60); %! endfor %!test # and the neighbours are the right ones, not merely present %! for K = [1, 5, 30, 60] %! [~, Dh] = knnsearch (hs, Ys, "K", K); %! [~, De] = knnsearch (ExhaustiveSearcher (Xs), Ys, "K", K); %! assert_equal (Dh, De, 1e-12); %! endfor %!test # more neighbours than points gives all of them, not a wider matrix %! for K = [61, 100, 1000] %! [idx, D] = knnsearch (hs, Ys, "K", K); %! assert_equal (size (idx), [8, 60]); %! assert_equal (size (D), [8, 60]); %! assert_equal (any (isnan (idx(:))), false); %! endfor %!test # an empty query is answered with an empty result %! [idx, D] = knnsearch (hs, zeros (0, 2), "K", 3); %! assert_equal (size (idx), [0, 3]); %! assert_equal (size (D), [0, 3]); %!test # a query that is not finite is answered, its distances saying so %! [idx, D] = knnsearch (hs, [Inf, 0], "K", 3); %! assert_equal (size (idx), [1, 3]); %! assert_equal (all (isinf (D)), true); %! [idx, D] = knnsearch (hs, [NaN, NaN], "K", 3); %! assert_equal (size (idx), [1, 3]); %! assert_equal (all (isnan (D)), true); ## Single precision is carried through to the distances, every other class is ## converted up to double, and the indices are always double. %!test %! rand ("seed", 42); %! X = [1, 2; 3, 4; 5, 6; 7, 8]; Y = [2, 3; 6, 7]; %! obj = hnswSearcher (single (X)); %! assert_equal (class (obj.X), 'single'); %! [idx, D] = knnsearch (obj, single (Y), "K", 2); %! assert_equal (class (idx), 'double'); %! assert_equal (class (D), 'single'); %! ## a double query against single data is still computed in single %! [~, Dm] = knnsearch (obj, Y, "K", 2); %! assert_equal (class (Dm), 'single'); %! assert_equal (Dm, D); %! ## and so is a single query against double data %! [~, Ds] = knnsearch (hnswSearcher (X), single (Y), "K", 2); %! assert_equal (class (Ds), 'single'); %! ## double throughout stays double %! [~, Dd] = knnsearch (hnswSearcher (X), Y, "K", 2); %! assert_equal (class (Dd), 'double'); ## Integer data is converted to double on the way in. Held as int32 the ## coordinate differences were rounded and the distances came out wrong. %!test %! rand ("seed", 42); %! X = int32 ([10, 20; 33, 41; 55, 62]); Y = [21, 33]; %! obj = hnswSearcher (X); %! assert_equal (class (obj.X), 'double'); %! [~, D] = knnsearch (obj, Y, "K", 1); %! assert_equal (D, min (sqrt (sum ((double (X) - Y) .^ 2, 2))), 1e-12); statistics-release-1.9.2/inst/Nearest_Neighbors/knnsearch.m000066400000000000000000000715361524624707500240730ustar00rootroot00000000000000## Copyright (C) 2023-2024 Andreas Bertsatos ## Copyright (C) 2023 Mohammed Azmat Khan ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{idx} =} knnsearch (@var{X}, @var{Y}) ## @deftypefnx {statistics} {[@var{idx}, @var{D}] =} knnsearch (@var{X}, @var{Y}) ## @deftypefnx {statistics} {[@dots{}] =} knnsearch (@dots{}, @var{name}, @var{value}) ## ## Find k-nearest neighbors from input data. ## ## @code{@var{idx} = knnsearch (@var{X}, @var{Y})} finds @math{K} nearest ## neighbors in @var{X} for @var{Y}. It returns @var{idx} which contains indices ## of @math{K} nearest neighbors of each row of @var{Y}, If not specified, ## @qcode{@var{K} = 1}. @var{X} must be an @math{N*P} numeric matrix of input ## data, where rows correspond to observations and columns correspond to ## features or variables. @var{Y} is an @math{M*P} numeric matrix with query ## points, which must have the same numbers of column as @var{X}. ## ## @code{[@var{idx}, @var{D}] = knnsearch (@var{X}, @var{Y})} also returns the ## the distances, @var{D}, which correspond to the @math{K} nearest neighbour in ## @var{X} for each @var{Y} ## ## Additional parameters can be specified by @qcode{Name-Value} pair arguments. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'K'} @tab is the number of nearest neighbors to be found ## in the kNN search. It must be a positive integer value and by default it is ## 1. ## ## @item @qcode{'P'} @tab is the Minkowski distance exponent and it must be ## a positive scalar. This argument is only valid when the selected distance ## metric is @qcode{'minkowski'}. By default it is 2. ## ## @item @qcode{'Scale'} @tab is the scale parameter for the standardized ## Euclidean distance and it must be a nonnegative numeric vector of equal ## length to the number of columns in @var{X}. This argument is only valid when ## the selected distance metric is @qcode{'seuclidean'}, in which case each ## coordinate of @var{X} is scaled by the corresponding element of ## @qcode{'scale'}, as is each query point in @var{Y}. By default, the scale ## parameter is the standard deviation of each coordinate in @var{X}. ## ## @item @qcode{'Cov'} @tab is the covariance matrix for computing the ## mahalanobis distance and it must be a positive definite matrix matching the ## the number of columns in @var{X}. This argument is only valid when the ## selected distance metric is @qcode{'mahalanobis'}. ## ## @item @qcode{'BucketSize'} @tab is the maximum number of data points in ## the leaf node of the Kd-tree and it must be a positive integer. This ## argument is only valid when the selected search method is @qcode{'kdtree'}. ## ## @item @qcode{'SortIndices'} @tab is a boolean flag to sort the returned ## indices in ascending order by distance and it is @qcode{true} by default. ## When the selected search method is @qcode{'exhaustive'} or the ## @qcode{'IncludeTies'} flag is true, @code{knnsearch} always sorts the ## returned indices. ## ## @item @qcode{'Distance'} @tab is the distance metric used by ## @code{knnsearch} as specified below: ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @item @qcode{'euclidean'} @tab Euclidean distance. ## @item @qcode{'seuclidean'} @tab standardized Euclidean distance. Each ## coordinate difference between the rows in @var{X} and the query matrix ## @var{Y} is scaled by dividing by the corresponding element of the standard ## deviation computed from @var{X}. To specify a different scaling, use the ## @qcode{'Scale'} name-value argument. ## @item @qcode{'cityblock'} @tab City block distance. ## @item @qcode{'chebychev'} @tab Chebychev distance (maximum coordinate ## difference). ## @item @qcode{'minkowski'} @tab Minkowski distance. The default exponent ## is 2. To specify a different exponent, use the @qcode{'P'} name-value ## argument. ## @item @qcode{'mahalanobis'} @tab Mahalanobis distance, computed using a ## positive definite covariance matrix. To change the value of the covariance ## matrix, use the @qcode{'Cov'} name-value argument. ## @item @qcode{'cosine'} @tab Cosine distance. ## @item @qcode{'correlation'} @tab One minus the sample linear correlation ## between observations (treated as sequences of values). ## @item @qcode{'spearman'} @tab One minus the sample Spearman's rank ## correlation between observations (treated as sequences of values). ## @item @qcode{'hamming'} @tab Hamming distance, which is the percentage ## of coordinates that differ. ## @item @qcode{'jaccard'} @tab One minus the Jaccard coefficient, which is ## the percentage of nonzero coordinates that differ. ## @item @var{@@distfun} @tab Custom distance function handle. A distance ## function of the form @code{function @var{D2} = distfun (@var{XI}, @var{YI})}, ## where @var{XI} is a @math{1*P} vector containing a single observation in ## @math{P}-dimensional space, @var{YI} is an @math{N*P} matrix containing an ## arbitrary number of observations in the same @math{P}-dimensional space, and ## @var{D2} is an @math{N*P} vector of distances, where @qcode{(@var{D2}k)} is ## the distance between observations @var{XI} and @qcode{(@var{YI}k,:)}. ## @end multitable ## ## @multitable @columnfractions 0.18 0.8 ## @item @qcode{'NSMethod'} @tab is the nearest neighbor search method used ## by @code{knnsearch} as specified below. ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @item @qcode{'kdtree'} @tab Creates and uses a Kd-tree to find nearest ## neighbors. @qcode{'kdtree'} is the default value when the number of columns ## in @var{X} is less than or equal to 10, @var{X} is not sparse, and the ## distance metric is @qcode{'euclidean'}, @qcode{'cityblock'}, ## @qcode{'manhattan'}, @qcode{'chebychev'}, or @qcode{'minkowski'}. Otherwise, ## the default value is @qcode{'exhaustive'}. This argument is only valid when ## the distance metric is one of the four aforementioned metrics. ## @item @qcode{'exhaustive'} @tab Uses the exhaustive search algorithm by ## computing the distance values from all the points in @var{X} to each point in ## @var{Y}. ## @end multitable ## ## @multitable @columnfractions 0.18 0.8 ## @item @qcode{'IncludeTies'} @tab is a boolean flag to indicate if the ## returned values should contain the indices that have same distance as the ## @math{K^th} neighbor. When @qcode{false}, @code{knnsearch} chooses the ## observation with the smallest index among the observations that have the same ## distance from a query point. When @qcode{true}, @code{knnsearch} includes ## all nearest neighbors whose distances are equal to the @math{K^th} smallest ## distance in the output arguments. To specify @math{K}, use the @qcode{'K'} ## name-value pair argument. In that case @var{idx} and @var{D} are ## @math{M}-by-@math{1} cell arrays and each cell holds a @emph{row} vector, ## whichever search method is used. ## @end multitable ## ## @seealso{rangesearch, pdist2, fitcknn} ## @end deftypefn function [idx, dist] = knnsearch (X, Y, varargin) ## Check input data if (nargin < 2) error ("knnsearch: too few input arguments."); endif if (size (X, 2) != size (Y, 2)) error ("knnsearch: number of columns in X and Y must match."); endif ## Add default values K = 1; # Number of nearest neighbors P = 2; # Exponent for Minkowski distance S = []; # Scale for the standardized Euclidean distance C = []; # Covariance matrix for Mahalanobis distance BS = 50; # Maximum number of points per leaf node for Kd-tree SI = true; # Sort returned indices according to distance Distance = 'euclidean'; # Distance metric to be used NSMethod = []; # Nearest neighbor search method InclTies = false; # Include ties for distance with kth neighbor DistParameter = []; # Distance parameter for pdist2 ## Parse additional parameters in Name/Value pairs PSC = 0; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'k' K = varargin{2}; case 'p' P = varargin{2}; PSC += 1; case 'scale' S = varargin{2}; PSC += 1; case 'cov' C = varargin{2}; PSC += 1; case 'bucketsize' BS = varargin{2}; case 'sortindices' SI = varargin{2}; case 'distance' Distance = varargin{2}; ## 'manhattan' is this package's documented alias of 'cityblock' and ## is resolved here, so every search path takes it. MATLAB has no ## such name and refuses it. if (ischar (Distance) && strcmpi (Distance, 'manhattan')) Distance = 'cityblock'; endif case 'nsmethod' NSMethod = varargin{2}; case 'includeties' InclTies = varargin{2}; otherwise error ("knnsearch: invalid NAME in optional pairs of arguments."); endswitch varargin(1:2) = []; endwhile ## Check input parameters if (PSC > 1) error ("knnsearch: only a single distance parameter can be defined."); endif if (! isscalar (K) || ! isnumeric (K) || K < 1 || K != round (K)) error ("knnsearch: invalid value of K."); endif if (! isscalar (P) || ! isnumeric (P) || P <= 0) error ("knnsearch: invalid value of Minkowski Exponent."); endif if (! isempty (S)) if (any (S) < 0 || numel (S) != columns (X) || ! strcmpi (Distance, 'seuclidean')) error ("knnsearch: invalid value in Scale or the size of Scale."); endif endif if (! isempty (C)) if (! strcmp (Distance, 'mahalanobis') || ! ismatrix (C) || ! isnumeric (C)) error (strcat ("knnsearch: invalid value in Cov, Cov can only", ... " be given for mahalanobis distance.")); endif endif if (! isscalar (BS) || ! isnumeric (BS) || BS < 0 || fix (BS) != BS) error ("knnsearch: invalid value of bucketsize."); endif ## Select the appropriate distance parameter if (strcmpi (Distance, 'minkowski')) DistParameter = P; elseif (strcmpi (Distance, 'seuclidean')) DistParameter = S; elseif (strcmpi (Distance, 'mahalanobis')) DistParameter = C; endif ## Check NSMethod and set kdtree as default if the conditions match if (isempty (NSMethod)) ## Set default method 'kdtree' if conditions are satisfied; if (! issparse (X) && (columns (X) <= 10) && (strcmpi (Distance, 'euclidean') || strcmpi (Distance, 'cityblock') || strcmpi (Distance, 'manhattan') || strcmpi (Distance, 'minkowski') || strcmpi (Distance, 'chebychev'))) NSMethod = 'kdtree'; else NSMethod = 'exhaustive'; endif else ## Disallow kdtree with custom distance functions if (strcmpi (NSMethod, 'kdtree') && isa (Distance, 'function_handle')) error (strcat ("knnsearch: 'kdtree' cannot be used", ... " with custom distance functions.")); endif ## Check if kdtree can be used if (strcmpi (NSMethod, 'kdtree') && ! (strcmpi (Distance, 'euclidean') || strcmpi (Distance, 'cityblock') || strcmpi (Distance, 'minkowski') || strcmpi (Distance, 'chebychev'))) error (strcat ("knnsearch: 'kdtree' cannot be used", ... " with the given distance metric.")); endif endif ## Distances are computed in single precision when either the training data ## or the query is single, and in double otherwise. The indices are always ## double. cls = "double"; if (isa (X, "single") || isa (Y, "single")) cls = "single"; endif X = cast (X, cls); Y = cast (Y, cls); ## Check for NSMethod if (strcmpi (NSMethod, 'kdtree')) ## Build kdtree and search the query point kdtree = __build_kdtree__ (1:size (X,1), 0, X, BS); ## Check for ties and sortindices if (! InclTies) ## Only return k neighbors dist = zeros (rows (Y), K, cls); idx = zeros (rows (Y), K); for i = 1:rows (Y) [temp_idx, temp_D] = __search_kdtree__ (kdtree, Y(i,:), K, X, ... Distance, DistParameter, ... false); if (SI) [sorted_D, sort_idx] = sort (temp_D); idx(i,:) = temp_idx(sort_idx); dist(i,:) = sorted_D; else idx(i,:) = temp_idx; dist(i,:) = temp_D; endif endfor else ## Return all neighbors as cell dist = cell (rows (Y), 1); idx = cell (rows (Y), 1); for i = 1:rows (Y) [temp_idx, temp_D] = __search_kdtree__ (kdtree, Y(i,:), K, ... X, Distance, DistParameter, ... false); r = temp_D(end) + 1e-10; # Add small epsilon to capture ties [tied_idx, tied_D] = __search_kdtree__ (kdtree, Y(i,:), Inf, X, ... Distance, DistParameter, ... true, r); ## Each cell holds a row vector, as MATLAB returns and as both the ## exhaustive path below and RANGESEARCH already did. The kd-tree ## search returns columns, so they are laid down explicitly here. if (SI) [sorted_D, sort_idx] = sort (tied_D); idx{i} = tied_idx(sort_idx)(:).'; dist{i} = sorted_D(:).'; else idx{i} = tied_idx(:).'; dist{i} = tied_D(:).'; endif endfor endif else ## Where the metric is one the compiled search knows, the whole search ## runs there: each distance is compared against a running list of the K ## best and discarded, so the M-by-N matrix is never formed. That matrix ## is what puts a ceiling on N, being 128 MB at four thousand points ## against four thousand, and forming it cost about half of this branch. ## Every other metric keeps the older route, pdist2 followed by a partial ## selection. if (! InclTies && ischar (Distance) && any (strcmpi (Distance, {'euclidean', 'cityblock', ... 'chebychev', 'minkowski'}))) [idx, dist] = __knnbrute__ (X, Y, K, lower (Distance), DistParameter); ## Calculate all distances. The single-neighbour shortcut applies only ## when ties are not wanted: taking the minimum returns one neighbour ## whatever else sits at the same distance, so asking for K = 1 with ## 'IncludeTies' used to get the flag silently ignored, and a double where ## a cell was promised. elseif (K == 1 && ! InclTies) D = pdist2 (X, Y, Distance, DistParameter); D = reshape (D', size (Y, 1), size (X, 1)); [dist, idx] = min (D, [], 2); else # always sort indices in this case if (InclTies) dist = cell (rows (Y), 1); idx = cell (rows (Y), 1); for i = 1:rows (Y) D = pdist2 (X, Y(i,:), Distance, DistParameter); [dt, id] = sort (D); kth_dist = dt(K); tied_idx = (dt <= kth_dist); dist {i} = dt(tied_idx, :)'; idx {i} = id(tied_idx, :)'; endfor else ## No ties included. The K smallest come from a partial selection ## rather than from sorting every row and discarding the tail, which ## is where this branch used to spend most of its time. D = pdist2 (X, Y, Distance, DistParameter); D = reshape (D', size (Y, 1), size (X, 1)); [idx, dist] = __knnselect__ (D, K); endif endif endif endfunction %!demo %! ## find 10 nearest neighbour of a point using different distance metrics %! ## and compare the results by plotting %! load fisheriris %! X = meas(:,3:4); %! Y = species; %! point = [5, 1.45]; %! %! ## calculate 10 nearest-neighbours by minkowski distance %! [id, d] = knnsearch (X, point, 'K', 10); %! %! ## calculate 10 nearest-neighbours by minkowski distance %! [idm, dm] = knnsearch (X, point, 'K', 10, 'distance', 'minkowski', 'p', 5); %! %! ## calculate 10 nearest-neighbours by chebychev distance %! [idc, dc] = knnsearch (X, point, 'K', 10, 'distance', 'chebychev'); %! %! ## plotting the results %! gscatter (X(:,1), X(:,2), species, [.75 .75 0; 0 .75 .75; .75 0 .75], '.', 20); %! title ('Fisher''s Iris Data - Nearest Neighbors with different types of distance metrics'); %! xlabel ('Petal length (cm)'); %! ylabel ('Petal width (cm)'); %! %! line (point(1), point(2), 'marker', 'X', 'color', 'k', ... %! 'linewidth', 2, 'displayname', 'query point') %! line (X(id,1), X(id,2), 'color', [0.5 0.5 0.5], 'marker', 'o', ... %! 'linestyle', 'none', 'markersize', 10, 'displayname', 'euclidean') %! line (X(idm,1), X(idm,2), 'color', [0.5 0.5 0.5], 'marker', 'd', ... %! 'linestyle', 'none', 'markersize', 10, 'displayname', 'Minkowski') %! line (X(idc,1), X(idc,2), 'color', [0.5 0.5 0.5], 'marker', 'p', ... %! 'linestyle', 'none', 'markersize', 10, 'displayname', 'chebychev') %! xlim ([4.5 5.5]); %! ylim ([1 2]); %! axis square; %!demo %! ## knnsearch on iris dataset using kdtree method %! load fisheriris %! X = meas(:,3:4); %! gscatter (X(:,1), X(:,2), species, [.75 .75 0; 0 .75 .75; .75 0 .75], '.', 20); %! title ('Fisher''s iris dataset : Nearest Neighbors with kdtree search'); %! %! ## new point to be predicted %! point = [5 1.45]; %! %! line (point(1), point(2), 'marker', 'X', 'color', 'k', ... %! 'linewidth', 2, 'displayname', 'query point') %! %! ## knnsearch using kdtree method %! [idx, d] = knnsearch (X, point, 'K', 10, 'NSMethod', 'kdtree'); %! %! ## plotting predicted neighbours %! line (X(idx,1), X(idx,2), 'color', [0.5 0.5 0.5], 'marker', 'o', ... %! 'linestyle', 'none', 'markersize', 10, ... %! 'displayname', 'nearest neighbour') %! xlim ([4 6]) %! ylim ([1 3]) %! axis square %! ## details of predicted labels %! tabulate (species(idx)) %! %! ctr = point - d(end); %! diameter = 2 * d(end); %! ## Draw a circle around the 10 nearest neighbors. %! h = rectangle ('position', [ctr, diameter, diameter], 'curvature', [1 1]); %! %! ## here only 8 neighbours are plotted instead of 10 since the dataset %! ## contains duplicate values ## Test output %!shared X, Y %! X = [1, 2, 3, 4; 2, 3, 4, 5; 3, 4, 5, 6]; %! Y = [1, 2, 2, 3; 2, 3, 3, 4]; %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'euclidean'); %! assert_equal (idx, [1; 1]); %! assert_equal (D, ones (2, 1) * sqrt (2)); %!test %! eucldist = @(v,m) sqrt (sumsq (repmat (v,rows (m),1)-m,2)); %! [idx, D] = knnsearch (X, Y, 'Distance', eucldist); %! assert_equal (idx, [1; 1]); %! assert_equal (D, ones (2, 1) * sqrt (2)); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'euclidean', 'includeties', true); %! assert_equal (iscell (idx), true); %! assert_equal (iscell (D), true) %! assert_equal (idx {1}, [1]); %! assert_equal (idx {2}, [1, 2]); %! assert_equal (D{1}, ones (1, 1) * sqrt (2)); %! assert_equal (D{2}, ones (1, 2) * sqrt (2)); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'euclidean', 'k', 2); %! assert_equal (idx, [1, 2; 1, 2]); %! assert_equal (D, [sqrt(2), 3.162277660168380; sqrt(2), sqrt(2)], 1e-14); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'seuclidean'); %! assert_equal (idx, [1; 1]); %! assert_equal (D, ones (2, 1) * sqrt (2)); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'seuclidean', 'k', 2); %! assert_equal (idx, [1, 2; 1, 2]); %! assert_equal (D, [sqrt(2), 3.162277660168380; sqrt(2), sqrt(2)], 1e-14); %!test %! xx = [1, 2; 1, 3; 2, 4; 3, 6]; %! yy = [2, 4; 2, 6]; %! [idx, D] = knnsearch (xx, yy, 'Distance', 'mahalanobis'); %! assert_equal (idx, [3; 2]); %! assert_equal (D, [0; 3.162277660168377], 1e-14); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'minkowski'); %! assert_equal (idx, [1; 1]); %! assert_equal (D, ones (2, 1) * sqrt (2)); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'minkowski', 'p', 3); %! assert_equal (idx, [1; 1]); %! assert_equal (D, ones (2, 1) * 1.259921049894873, 1e-14); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'cityblock'); %! assert_equal (idx, [1; 1]); %! assert_equal (D, [2; 2]); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'chebychev'); %! assert_equal (idx, [1; 1]); %! assert_equal (D, [1; 1]); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'cosine'); %! assert_equal (idx, [2; 3]); %! assert_equal (D, [0.005674536395645; 0.002911214328620], 1e-14); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'correlation'); %! assert_equal (idx, [1; 1]); %! assert_equal (D, ones (2, 1) * 0.051316701949486, 1e-14); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'spearman'); %! assert_equal (idx, [1; 1]); %! assert_equal (D, ones (2, 1) * 0.051316701949486, 1e-14); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'hamming'); %! assert_equal (idx, [1; 1]); %! assert_equal (D, [0.5; 0.5]); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'jaccard'); %! assert_equal (idx, [1; 1]); %! assert_equal (D, [0.5; 0.5]); %!test %! [idx, D] = knnsearch (X, Y, 'Distance', 'jaccard', 'k', 2); %! assert_equal (idx, [1, 2; 1, 2]); %! assert_equal (D, [0.5, 1; 0.5, 0.5]); %!test %! a = [1, 5; 1, 2; 2, 2; 1.5, 1.5; 5, 1; 2 -1.34; 1, -3; 4, -4; -3, 1; 8, 9]; %! b = [1, 1]; %! [idx, D] = knnsearch (a, b, 'K', 5, 'NSMethod', 'kdtree', 'includeties', true); %! assert_equal (iscell (idx), true); %! assert_equal (iscell (D), true) %! assert_equal (cell2mat (idx), [4, 2, 3, 6, 1, 5, 7, 9]); %! assert_equal (cell2mat (D), [0.7071, 1.0000, 1.4142, 2.5447, 4.0000, 4.0000, 4.0000, 4.0000], 1e-4); %!test %! a = [1, 5; 1, 2; 2, 2; 1.5, 1.5; 5, 1; 2 -1.34; 1, -3; 4, -4; -3, 1; 8, 9]; %! b = [1, 1]; %! [idx, D] = knnsearch (a, b, 'K', 5, 'NSMethod', 'exhaustive', 'includeties', true); %! assert_equal (iscell (idx), true); %! assert_equal (iscell (D), true) %! assert_equal (cell2mat (idx), [4, 2, 3, 6, 1, 5, 7, 9]); %! assert_equal (cell2mat (D), [0.7071, 1.0000, 1.4142, 2.5447, 4.0000, 4.0000, 4.0000, 4.0000], 1e-4); %!test %! a = [1, 5; 1, 2; 2, 2; 1.5, 1.5; 5, 1; 2 -1.34; 1, -3; 4, -4; -3, 1; 8, 9]; %! b = [1, 1]; %! [idx, D] = knnsearch (a, b, 'K', 5, 'NSMethod', 'kdtree', 'includeties', false); %! assert_equal (iscell (idx), false); %! assert_equal (iscell (D), false) %! assert_equal (idx, [4, 2, 3, 6, 1]); %! assert_equal (D, [0.7071, 1.0000, 1.4142, 2.5447, 4.0000], 1e-4); %!test %! a = [1, 5; 1, 2; 2, 2; 1.5, 1.5; 5, 1; 2 -1.34; 1, -3; 4, -4; -3, 1; 8, 9]; %! b = [1, 1]; %! [idx, D] = knnsearch (a, b, 'K', 5, 'NSMethod', 'exhaustive', 'includeties', false); %! assert_equal (iscell (idx), false); %! assert_equal (iscell (D), false) %! assert_equal (idx, [4, 2, 3, 6, 1]); %! assert_equal (D, [0.7071, 1.0000, 1.4142, 2.5447, 4.0000], 1e-4); %!test %! load fisheriris %! a = meas; %! b = min (meas); %! [idx, D] = knnsearch (a, b, 'K', 5, 'NSMethod', 'kdtree'); %! assert_equal (idx, [42, 9, 14, 39, 13]); %! assert_equal (D, [0.5099, 0.9950, 1.0050, 1.0536, 1.1874], 1e-4); %!test %! load fisheriris %! a = meas; %! b = mean (meas); %! [idx, D] = knnsearch (a, b, 'K', 5, 'NSMethod', 'kdtree'); %! assert_equal (idx, [65, 83, 89, 72, 100]); %! assert_equal (D, [0.3451, 0.3869, 0.4354, 0.4481, 0.4625], 1e-4); %!test %! load fisheriris %! a = meas; %! b = max (meas); %! [idx, D] = knnsearch (a, b, 'K', 5, 'NSMethod', 'kdtree'); %! assert_equal (idx, [118, 132, 110, 106, 136]); %! assert_equal (D, [0.7280, 0.9274, 1.3304, 1.5166, 1.6371], 1e-4); %! %!test %! load fisheriris %! a = meas; %! b = max (meas); %! [idx, D] = knnsearch (a, b, 'K', 5, 'includeties', true); %! assert_equal (iscell (idx), true); %! assert_equal (iscell (D), true); %! assert_equal (cell2mat (idx), [118, 132, 110, 106, 136]); %! assert_equal (cell2mat (D), [0.7280, 0.9274, 1.3304, 1.5166, 1.6371], 1e-4); %!test # IncludeTies gives a row per cell, whichever method is used %! a = [1, 5; 1, 2; 2, 2; 1.5, 1.5; 5, 1; 2 -1.34; 1, -3; 4, -4; -3, 1; 8, 9]; %! b = [1, 1]; %! [ik, dk] = knnsearch (a, b, 'K', 5, 'NSMethod', 'kdtree', 'includeties', true); %! [ie, de] = knnsearch (a, b, 'K', 5, 'NSMethod', 'exhaustive', 'includeties', true); %! assert_equal (size (ik{1}), [1, 8]); %! assert_equal (size (dk{1}), [1, 8]); %! assert_equal (ik{1}, ie{1}); %! assert_equal (dk{1}, de{1}, 1e-12); %!test # the cell array itself is one column, as MATLAB returns %! a = [1, 5; 1, 2; 2, 2; 1.5, 1.5; 5, 1; 2 -1.34; 1, -3; 4, -4; -3, 1; 8, 9]; %! [idx, D] = knnsearch (a, [1, 1; 2, 2], 'K', 3, 'includeties', true); %! assert_equal (size (idx), [2, 1]); %! assert_equal (size (D), [2, 1]); %! assert_equal (rows (idx{1}), 1); %! assert_equal (rows (idx{2}), 1); ## Test input validation %!error knnsearch (1) %!error ... %! knnsearch (ones (4, 5), ones (4)) %!error ... %! knnsearch (ones (4, 2), ones (3, 2), 'Distance', 'euclidean', 'some', 'some') %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'scale', ones (1, 5), 'P', 3) %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'K', 0) %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'P', -2) %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'scale', ones (4,5), 'distance', 'euclidean') %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'cov', ['some' 'some']) %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'cov', ones (4,5), 'distance', 'euclidean') %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'bucketsize', -1) %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'bucketsize', 2.5) %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'NSmethod', 'kdtree', 'distance', 'cosine') %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'NSmethod', 'kdtree', 'distance', 'mahalanobis') %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'NSmethod', 'kdtree', 'distance', 'correlation') %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'NSmethod', 'kdtree', 'distance', 'seuclidean') %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'NSmethod', 'kdtree', 'distance', 'spearman') %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'NSmethod', 'kdtree', 'distance', 'hamming') %!error ... %! knnsearch (ones (4, 5), ones (1, 5), 'NSmethod', 'kdtree', 'distance', 'jaccard') ## Single precision is carried through to the distances, and the indices are ## always double, on both search methods. %!test %! X = [1, 2; 3, 4; 5, 6; 7, 8]; Y = [2, 3; 6, 7]; %! for m = {"kdtree", "exhaustive"} %! [idx, D] = knnsearch (single (X), single (Y), "K", 2, "NSMethod", m{1}); %! assert_equal (class (idx), 'double'); %! assert_equal (class (D), 'single'); %! [~, Dm] = knnsearch (single (X), Y, "K", 2, "NSMethod", m{1}); %! assert_equal (class (Dm), 'single'); %! assert_equal (Dm, D); %! [~, Dd] = knnsearch (X, Y, "K", 2, "NSMethod", m{1}); %! assert_equal (class (Dd), 'double'); %! endfor ## A single neighbour with 'IncludeTies' used to take a shortcut that ignored ## the flag, returning one index as a double where a cell of every tied ## neighbour was promised. The kd-tree path was unaffected. %!test %! X = [0, 0; 0, 0; 1, 1; 1, 1; 2, 2; 2, 2]; %! idx = knnsearch (X, [0.5, 0.5], 'k', 1, 'NSMethod', 'exhaustive', ... %! 'IncludeTies', true); %! assert_equal (class (idx), 'cell'); %! assert_equal (sort (idx{1}), [1, 2, 3, 4]); %!test %! X = [0, 0; 0, 0; 1, 1; 1, 1; 2, 2; 2, 2]; %! [i1, d1] = knnsearch (X, [0.5, 0.5], 'k', 1, 'NSMethod', 'exhaustive', ... %! 'IncludeTies', true); %! [i2, d2] = knnsearch (X, [0.5, 0.5], 'k', 1, 'NSMethod', 'kdtree', ... %! 'IncludeTies', true); %! assert_equal (sort (i1{1}), sort (i2{1})); %! assert_equal (sort (d1{1}), sort (d2{1}), 1e-12); statistics-release-1.9.2/inst/Nearest_Neighbors/mahal.m000066400000000000000000000052321524624707500231670ustar00rootroot00000000000000## Copyright (C) 2015 Lachlan Andrew ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or (at ## your option) any later version. ## ## This program, is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{d} =} mahal (@var{y}, @var{x}) ## ## Mahalanobis' D-square distance. ## ## Return the Mahalanobis' D-square distance of the points in ## @var{y} from the distribution implied by points @var{x}. ## ## Specifically, it uses a Cholesky decomposition to set ## ## @example ## answer(i) = (@var{y}(i,:) - mean (@var{x})) * inv (A) * ## (@var{y}(i,:)-mean (@var{x}))' ## @end example ## ## where A is the covariance of @var{x}. ## ## The data @var{x} and @var{y} must have the same number of components ## (columns), but may have a different number of observations (rows). ## ## @end deftypefn function retval = mahal (y, x) if (nargin != 2) print_usage (); endif if (! (isnumeric (x) || islogical (x)) || ! (isnumeric (y) || islogical (y))) error ("mahal: X and Y must be numeric matrices or vectors"); endif if (! ismatrix (x) || ! ismatrix (y)) error ("mahal: X and Y must be 2-D matrices or vectors"); endif [xr, xc] = size (x); [yr, yc] = size (y); if (xc != yc) error ("mahal: X and Y must have the same number of columns"); endif if (isinteger (x)) x = double (x); endif xm = mean (x, 1); ## Center data by subtracting mean of x x = bsxfun (@minus, x, xm); y = bsxfun (@minus, y, xm); w = (x' * x) / (xr - 1); retval = sumsq (y / chol (w), 2); endfunction ## Test input validation %!error mahal () %!error mahal (1, 2, 3) %!error mahal ('A', 'B') %!error mahal ([1, 2], ['A', 'B']) %!error mahal (ones (2, 2, 2)) %!error mahal (ones (2, 2), ones (2, 2, 2)) %!error mahal (ones (2, 2), ones (2, 3)) %!test %! X = [1 0; 0 1; 1 1; 0 0]; %! assert_equal (mahal (X, X), [1.5; 1.5; 1.5; 1.5], 10*eps) %! assert_equal (mahal (X, X+1), [7.5; 7.5; 1.5; 13.5], 10*eps) %!assert_equal (mahal ([true; true], [false; true]), [0.5; 0.5], eps) statistics-release-1.9.2/inst/Nearest_Neighbors/pdist.m000066400000000000000000000510101524624707500232230ustar00rootroot00000000000000## Copyright (C) 2008 Francesco Potortì ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{D} =} pdist (@var{X}) ## @deftypefnx {statistics} {@var{D} =} pdist (@var{X}, @var{Distance}) ## @deftypefnx {statistics} {@var{D} =} pdist (@var{X}, @var{Distance}, @var{DistParameter}) ## ## Return the distance between any two rows in @var{X}. ## ## @code{@var{D} = pdist (@var{X}} calculates the euclidean distance between ## pairs of observations in @var{X}. @var{X} must be an @math{M*P} numeric ## matrix representing @math{M} points in @math{P}-dimensional space. This ## function computes the pairwise distances returned in @var{D} as an ## @math{M*(M-1)/P} row vector. Use @code{@var{Z} = squareform (@var{D})} to ## convert the row vector @var{D} into a an @math{M*M} symmetric matrix @var{Z}, ## where @qcode{@var{Z}(i,j)} corresponds to the pairwise distance between ## points @qcode{i} and @qcode{j}. ## ## @code{@var{D} = pdist (@var{X}, @var{Y}, @var{Distance})} returns the ## distance between pairs of observations in @var{X} using the metric specified ## by @var{Distance}, which can be any of the following options. ## ## @multitable @columnfractions 0.23 0.65 ## @item @qcode{'euclidean'} @tab Euclidean distance. ## @item @qcode{'fasteuclidean'} @tab Euclidean distance computed with an ## alternative algorithm which may be faster but might reduce accuracy. ## @item @qcode{'squaredeuclidean'} @tab Squared Euclidean distance. ## @item @qcode{'fastsquaredeuclidean'} @tab Euclidean distance computed ## with an alternative algorithm which may be faster but might reduce accuracy. ## @item @qcode{'seuclidean'} @tab standardized Euclidean distance. Each ## coordinate difference between the rows in @var{X} and the query matrix ## @var{Y} is scaled by dividing by the corresponding element of the standard ## deviation computed from @var{X}. A different scaling vector can be specified ## with the subsequent @var{DistParameter} input argument. ## @item @qcode{'mahalanobis'} @tab Mahalanobis distance, computed using a ## positive definite covariance matrix. A different covariance matrix can be ## specified with the subsequent @var{DistParameter} input argument. ## @item @qcode{'cityblock'} @tab City block distance. ## @item @qcode{'minkowski'} @tab Minkowski distance. The default exponent ## is 2. A different exponent can be specified with the subsequent ## @var{DistParameter} input argument. ## @item @qcode{'chebychev'} @tab Chebychev distance (maximum coordinate ## difference). ## @item @qcode{'cosine'} @tab One minus the cosine of the included angle ## between points (treated as vectors). ## @item @qcode{'correlation'} @tab One minus the sample linear correlation ## between observations (treated as sequences of values). ## @item @qcode{'hamming'} @tab Hamming distance, which is the percentage ## of coordinates that differ. ## @item @qcode{'jaccard'} @tab One minus the Jaccard coefficient, which is ## the percentage of nonzero coordinates that differ. ## @item @qcode{'spearman'} @tab One minus the sample Spearman's rank ## correlation between observations (treated as sequences of values). ## @item @var{@@distfun} @tab Custom distance function handle. A distance ## function of the form @code{function @var{D2} = distfun (@var{XI}, @var{YI})}, ## where @var{XI} is a @math{1*P} vector containing a single observation in ## @math{P}-dimensional space, @var{YI} is an @math{N*P} matrix containing an ## arbitrary number of observations in the same @math{P}-dimensional space, and ## @var{D2} is an @math{N*P} vector of distances, where @qcode{(@var{D2}k)} is ## the distance between observations @var{XI} and @qcode{(@var{YI}k,:)}. ## @end multitable ## ## @code{@var{D} = pdist (@var{X}, @var{Y}, @var{Distance}, ## @var{DistParameter})} ## returns the distance using the metric specified by @var{Distance} and ## @var{DistParameter}. The latter one can only be specified when the selected ## @var{Distance} is @qcode{'seuclidean'}, @qcode{'minkowski'}, and ## @qcode{'mahalanobis'}. ## ## @seealso{pdist2, squareform, linkage} ## @end deftypefn function D = pdist (X, varargin) ## Check input data if (nargin < 1) error ("pdist: too few input arguments."); endif if (! isnumeric (X) || isempty (X)) error ("pdist: X must be a nonempty numeric matrix."); endif if (ndims (X) != 2) error ("pdist: X must be a two-dimensional matrix."); endif if (rows (X) < 2) D = cast (zeros (1, 0), class (X)); return; endif ## Add default values Distance = 'euclidean'; # Distance metric DistParameter = []; # Distance parameter ## Parse additional Distance metric and Distance parameter (if available) DMs = {'euclidean', 'squaredeuclidean', 'seuclidean', ... 'fasteuclidean', 'fastsquaredeuclidean', ... 'chebychev', 'cityblock', 'cosine', 'correlation', ... 'mahalanobis', 'minkowski', 'hamming', 'jaccard', 'spearman'}; if (numel (varargin) > 0) if (any (strcmpi (DMs, varargin{1}))) Distance = tolower (varargin{1}); elseif (is_function_handle (varargin{1})) Distance = varargin{1}; else error ("pdist: invalid value for Distance input argument."); endif endif if (numel (varargin) > 1) if (isnumeric (varargin{2})) DistParameter = varargin{2}; else error ("pdist: invalid value for DistParameter input argument."); endif endif ## FAST PATH: Optimization for fast Euclidean algorithm fast_algs = {'fasteuclidean', 'fastsquaredeuclidean'}; if (ischar (Distance) && ismember (Distance, fast_algs)) ## sumsq avoids the temporary memory of X.^2 ## D = ||x||^2 + ||x||^2 - 2x.x D = sumsq (X, 2) + sumsq (X, 2).' - 2 * (X * X'); ## Remove possible numerical negative noise D = max (D, 0); if (strcmp (Distance, 'fasteuclidean')) D = sqrt (D); endif ## Handle SortOrder (Smallest/Largest) output if requested if (nargout > 1 || parcount) ## This finds K nearest X's for each Y. [D, I] = sort (D, 1, SortOrder); K = min (size (D, 1), K); D = D(1:K, :); I = I(1:K, :); endif return; endif ## Calculate selected distance N = rows (X); ## Handle a function handle (always row-by-row) if (is_function_handle (Distance)) D2 = []; try D2 = Distance(X(1,:), X([2:end],:)); catch ME error ("pdist: invalid function handle for distance metric."); end_try_catch Xrows = N - 1; if (! isequal (size (D2), [Xrows, 1])) error ("pdist: custom distance function produces wrong output size."); endif num_pairs = N * (N - 1) / 2; D = zeros (1, num_pairs); id_beg = 1; for r = 1:Xrows id_end = id_beg + (N - r) - 1; D(id_beg:id_end) = feval (Distance, X(r,:), X([r+1:end],:)); id_beg = id_end + 1; endfor return; endif ## Threshold for switching between vectorized and blocked computation N_threshold = 1000; ## For small N: use original vectorized implementation (fast, O(N^2) memory) if (N < N_threshold) order = nchoosek (1:N, 2); ix = order(:,1); iy = order(:,2); switch (Distance) case 'euclidean' D = sqrt (sum ((X(ix,:) - X(iy,:)) .^ 2, 2))'; case 'squaredeuclidean' D = sum ((X(ix,:) - X(iy,:)) .^ 2, 2)'; case 'seuclidean' if (isempty (DistParameter)) DistParameter = std (X, [], 1); else if (numel (DistParameter) != columns (X)) error (strcat ("pdist: DistParameter for standardized", ... " euclidean must be a vector of equal length", ... " to the number of columns in X.")); endif if (any (DistParameter < 0)) error (strcat ("pdist: DistParameter for standardized", ... " euclidean must be a nonnegative vector.")); endif endif DistParameter(DistParameter == 0) = 1; D = sqrt (sum (((X(ix,:) - X(iy,:)) ./ DistParameter) .^ 2, 2))'; case 'mahalanobis' if (isempty (DistParameter)) DistParameter = cov (X(! any (isnan (X), 2),:)); else if (columns (DistParameter) != columns (X)) error (strcat ("pdist: DistParameter for mahalanobis", ... " distance must be a covariance matrix with", ... " the same number of columns as X.")); endif [~, p] = chol (DistParameter); if (p != 0) error (strcat ("pdist: covariance matrix for mahalanobis", ... " distance must be symmetric and positive", ... " definite.")); endif endif dxx = X(ix,:) - X(iy,:); [DP_inv, rc] = inv (DistParameter); if (rc < eps) warning (sprintf (strcat ("pdist: matrix is close to singular", ... " or badly scaled.\n RCOND = %e. Results may be inaccurate."), rc)); endif D = sqrt (sum ((dxx * DP_inv) .* dxx, 2))'; case 'cityblock' D = sum (abs (X(ix,:) - X(iy,:)), 2)'; case 'minkowski' if (isempty (DistParameter)) DistParameter = 2; else if (! (isnumeric (DistParameter) && isscalar (DistParameter) && DistParameter > 0)) error (strcat ("pdist: DistParameter for minkowski distance", ... " must be a positive scalar.")); endif endif D = (sum (abs (X(ix,:) - X(iy,:)) .^ DistParameter, 2) .^ ... (1 / DistParameter))'; case 'chebychev' D = max (abs (X(ix,:) - X(iy,:)), [], 2)'; case 'cosine' sx = sum (X .^ 2, 2) .^ (-1 / 2); D = (1 - sum (X(ix,:) .* X(iy,:), 2) .* sx(ix) .* sx(iy))'; ## A similarity a rounding step above one would give a negative ## distance, which MATLAB never returns. Only the sign is corrected: ## parallel rows still land a rounding step above zero in both ## engines. A NaN compares false and is left alone. D(D < 0) = 0; case 'correlation' mX = mean (X(ix,:), 2); mY = mean (X(iy,:), 2); xy = sum ((X(ix,:) - mX) .* (X(iy,:) - mY), 2); xx = sqrt (sum ((X(ix,:) - mX) .^ 2, 2)); yy = sqrt (sum ((X(iy,:) - mY) .^ 2, 2)); D = (1 - xy ./ (xx .* yy))'; D(D < 0) = 0; case 'hamming' D = mean (X(ix,:) != X(iy,:), 2)'; case 'jaccard' nz = (X(ix,:) != 0 | X(iy,:) != 0); D = (sum ((X(ix,:) != X(iy,:)) & nz, 2) ./ sum (nz, 2))'; case 'spearman' rX = zeros (size (X)); for i = 1:N rX(i,:) = tiedrank (X(i,:)); endfor rM = (columns (X) + 1) / 2; xy = sum ((rX(ix,:) - rM) .* (rX(iy,:) - rM), 2); xx = sqrt (sum ((rX(ix,:) - rM) .^ 2, 2)); yy = sqrt (sum ((rX(iy,:) - rM) .^ 2, 2)); D = (1 - xy ./ (xx .* yy))'; D(D < 0) = 0; endswitch ## For large N: use blocked row-by-row computation (O(N) memory) else num_pairs = N * (N - 1) / 2; D = zeros (1, num_pairs); switch (Distance) case 'euclidean' idx = 0; for i = 1:(N-1) Xi = X(i,:); for j = (i+1):N idx += 1; d = Xi - X(j,:); D(idx) = sqrt (sum (d .^ 2)); endfor endfor case 'squaredeuclidean' idx = 0; for i = 1:(N-1) Xi = X(i,:); for j = (i+1):N idx += 1; d = Xi - X(j,:); D(idx) = sum (d .^ 2); endfor endfor case 'seuclidean' if (isempty (DistParameter)) DistParameter = std (X, [], 1); else if (numel (DistParameter) != columns (X)) error (strcat ("pdist: DistParameter for standardized", ... " euclidean must be a vector of equal length", ... " to the number of columns in X.")); endif if (any (DistParameter < 0)) error (strcat ("pdist: DistParameter for standardized", ... " euclidean must be a nonnegative vector.")); endif endif DistParameter(DistParameter == 0) = 1; idx = 0; for i = 1:(N-1) Xi = X(i,:); for j = (i+1):N idx += 1; d = (Xi - X(j,:)) ./ DistParameter; D(idx) = sqrt (sum (d .^ 2)); endfor endfor case 'mahalanobis' if (isempty (DistParameter)) DistParameter = cov (X(! any (isnan (X), 2),:)); else if (columns (DistParameter) != columns (X)) error (strcat ("pdist: DistParameter for mahalanobis", ... " distance must be a covariance matrix with", ... " the same number of columns as X.")); endif [~, p] = chol (DistParameter); if (p != 0) error (strcat ("pdist: covariance matrix for mahalanobis", ... " distance must be symmetric and positive", ... " definite.")); endif endif [DP_inv, rc] = inv (DistParameter); if (rc < eps) warning (sprintf (strcat ("pdist: matrix is close to singular", ... " or badly scaled.\n RCOND = %e. Results may be inaccurate."), rc)); endif idx = 0; for i = 1:(N-1) Xi = X(i,:); for j = (i+1):N idx += 1; d = Xi - X(j,:); D(idx) = sqrt (sum ((d * DP_inv) .* d)); endfor endfor case 'cityblock' idx = 0; for i = 1:(N-1) Xi = X(i,:); for j = (i+1):N idx += 1; D(idx) = sum (abs (Xi - X(j,:))); endfor endfor case 'minkowski' if (isempty (DistParameter)) DistParameter = 2; else if (! (isnumeric (DistParameter) && isscalar (DistParameter) && DistParameter > 0)) error (strcat ("pdist: DistParameter for minkowski distance", ... " must be a positive scalar.")); endif endif p_exp = DistParameter; p_inv = 1 / DistParameter; idx = 0; for i = 1:(N-1) Xi = X(i,:); for j = (i+1):N idx += 1; D(idx) = sum (abs (Xi - X(j,:)) .^ p_exp) .^ p_inv; endfor endfor case 'chebychev' idx = 0; for i = 1:(N-1) Xi = X(i,:); for j = (i+1):N idx += 1; D(idx) = max (abs (Xi - X(j,:))); endfor endfor case 'cosine' sx = sum (X .^ 2, 2) .^ (-1 / 2); idx = 0; for i = 1:(N-1) Xi = X(i,:); sx_i = sx(i); for j = (i+1):N idx += 1; D(idx) = 1 - sum (Xi .* X(j,:)) * sx_i * sx(j); if (D(idx) < 0) D(idx) = 0; endif endfor endfor case 'correlation' idx = 0; for i = 1:(N-1) Xi = X(i,:); mXi = mean (Xi); Xi_c = Xi - mXi; for j = (i+1):N idx += 1; Xj = X(j,:); mXj = mean (Xj); Xj_c = Xj - mXj; xy = sum (Xi_c .* Xj_c); xx = sqrt (sum (Xi_c .^ 2)); yy = sqrt (sum (Xj_c .^ 2)); D(idx) = 1 - xy / (xx * yy); if (D(idx) < 0) D(idx) = 0; endif endfor endfor case 'hamming' idx = 0; for i = 1:(N-1) Xi = X(i,:); for j = (i+1):N idx += 1; D(idx) = mean (Xi != X(j,:)); endfor endfor case 'jaccard' idx = 0; for i = 1:(N-1) Xi = X(i,:); for j = (i+1):N idx += 1; Xj = X(j,:); nz = (Xi != 0 | Xj != 0); D(idx) = sum ((Xi != Xj) & nz) / sum (nz); endfor endfor case 'spearman' rX = zeros (size (X)); for i = 1:N rX(i,:) = tiedrank (X(i,:)); endfor rM = (columns (X) + 1) / 2; idx = 0; for i = 1:(N-1) rXi = rX(i,:) - rM; for j = (i+1):N idx += 1; rXj = rX(j,:) - rM; xy = sum (rXi .* rXj); xx = sqrt (sum (rXi .^ 2)); yy = sqrt (sum (rXj .^ 2)); D(idx) = 1 - xy / (xx * yy); if (D(idx) < 0) D(idx) = 0; endif endfor endfor endswitch endif endfunction ## Test output %!shared xy, t, eucl, x %! xy = [0 1; 0 2; 7 6; 5 6]; %! t = 1e-3; %! eucl = @(v,m) sqrt (sumsq (repmat (v,rows (m),1)-m,2)); %! x = [1 2 3; 4 5 6; 7 8 9; 3 2 1]; %!assert_equal (pdist (xy), [1.000 8.602 7.071 8.062 6.403 2.000], t); %!assert_equal (pdist (xy, eucl), [1.000 8.602 7.071 8.062 6.403 2.000], t); %!assert_equal (pdist (xy, 'euclidean'), [1.000 8.602 7.071 8.062 6.403 2.000], t); %!assert_equal (pdist (xy, 'seuclidean'), [0.380 2.735 2.363 2.486 2.070 0.561], t); %!assert_equal (pdist (xy, 'mahalanobis'), [1.384 1.967 2.446 2.384 1.535 2.045], t); %!assert_equal (pdist (xy, 'cityblock'), [1.000 12.00 10.00 11.00 9.000 2.000], t); %!assert_equal (pdist (xy, 'minkowski'), [1.000 8.602 7.071 8.062 6.403 2.000], t); %!assert_equal (pdist (xy, 'minkowski', 3), [1.000 7.763 6.299 7.410 5.738 2.000], t); %!assert_equal (pdist (xy, 'cosine'), [0.000 0.349 0.231 0.349 0.231 0.013], t); %!assert_equal (pdist (xy, 'correlation'), [0.000 2.000 0.000 2.000 0.000 2.000], t); %!assert_equal (pdist (xy, 'spearman'), [0.000 2.000 0.000 2.000 0.000 2.000], t); %!assert_equal (pdist (xy, 'hamming'), [0.500 1.000 1.000 1.000 1.000 0.500], t); %!assert_equal (pdist (xy, 'jaccard'), [1.000 1.000 1.000 1.000 1.000 0.500], t); %!assert_equal (pdist (xy, 'chebychev'), [1.000 7.000 5.000 7.000 5.000 2.000], t); %!assert_equal (pdist (x), [5.1962, 10.3923, 2.8284, 5.1962, 5.9161, 10.7703], 1e-4); %!assert_equal (pdist (x, 'euclidean'), ... %! [5.1962, 10.3923, 2.8284, 5.1962, 5.9161, 10.7703], 1e-4); %!assert_equal (pdist (x, eucl), ... %! [5.1962, 10.3923, 2.8284, 5.1962, 5.9161, 10.7703], 1e-4); %!assert_equal (pdist (x, 'squaredeuclidean'), [27, 108, 8, 27, 35, 116]); %!assert_equal (pdist (x, 'seuclidean'), ... %! [1.8071, 3.6142, 0.9831, 1.8071, 1.8143, 3.4854], 1e-4); %!warning ... %! pdist (x, 'mahalanobis'); %!assert_equal (pdist (x, 'cityblock'), [9, 18, 4, 9, 9, 18]); %!assert_equal (pdist (x, 'minkowski'), ... %! [5.1962, 10.3923, 2.8284, 5.1962, 5.9161, 10.7703], 1e-4); %!assert_equal (pdist (x, 'minkowski', 3), ... %! [4.3267, 8.6535, 2.5198, 4.3267, 5.3485, 9.2521], 1e-4); %!assert_equal (pdist (x, 'cosine'), ... %! [0.0254, 0.0406, 0.2857, 0.0018, 0.1472, 0.1173], 1e-4); %!assert_equal (pdist (x, 'correlation'), [0, 0, 2, 0, 2, 2], 1e-14); %!assert_equal (pdist (x, 'spearman'), [0, 0, 2, 0, 2, 2], 1e-14); %!assert_equal (pdist (x, 'hamming'), [1, 1, 2/3, 1, 1, 1]); %!assert_equal (pdist (x, 'jaccard'), [1, 1, 2/3, 1, 1, 1]); %!assert_equal (pdist (x, 'chebychev'), [3, 6, 2, 3, 5, 8]); %!test %! ## A row is never at a negative distance from itself, the similarity of %! ## identical rows being able to round a step above one. %! X = [-3, -3; -3, -3]; %! assert_equal (pdist (X, "cosine") >= 0, true); %!test %! ## Neither do the other two metrics built as one minus a similarity, on %! ## either the vectorised path or the blocked one taken for large N. %! Y = [1, 2, 4; 2, 4, 8; -1, -2, -4; 3, 6, 12]; %! for m = {"cosine", "correlation", "spearman"} %! assert_equal (any (pdist (Y, m{1}) < 0), false); %! assert_equal (any (pdist (repmat (Y, 300, 1), m{1}) < 0), false); %! endfor statistics-release-1.9.2/inst/Nearest_Neighbors/pdist2.m000066400000000000000000000647111524624707500233210ustar00rootroot00000000000000## Copyright (C) 2014-2019 Piotr Dollar ## Copyright (C) 2024-2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{D} =} pdist2 (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{D} =} pdist2 (@var{X}, @var{Y}, @var{Distance}) ## @deftypefnx {statistics} {@var{D} =} pdist2 (@var{X}, @var{Y}, @var{Distance}, @var{DistParameter}) ## @deftypefnx {statistics} {@var{D} =} pdist2 (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{D}, @var{I}] =} pdist2 (@dots{}, @var{Name}, @var{Value}) ## ## Compute pairwise distance between two sets of vectors. ## ## @code{@var{D} = pdist2 (@var{X}, @var{Y})} calculates the euclidean distance ## between each pair of observations in @var{X} and @var{Y}. Let @var{X} be an ## @math{M*P} matrix representing @math{M} points in @math{P}-dimensional space ## and @var{Y} be an @math{N*P} matrix representing another set of points in the ## same space. This function computes the @math{M*N} distance matrix @var{D}, ## where @qcode{@var{D}(i,j)} is the distance between @qcode{@var{X}(i,:)} and ## @qcode{@var{Y}(j,:)}. ## ## @code{@var{D} = pdist2 (@var{X}, @var{Y}, @var{Distance})} returns the ## distance between each pair of observations in @var{X} and @var{Y} using the ## metric specified by @var{Distance}, which can be any of the following ## options. ## ## @multitable @columnfractions 0.23 0.65 ## @item @qcode{'euclidean'} @tab Euclidean distance. ## @item @qcode{'fasteuclidean'} @tab Euclidean distance computed with an ## alternative algorithm which may be faster but might reduce accuracy. ## @item @qcode{'squaredeuclidean'} @tab Squared Euclidean distance. ## @item @qcode{'fastsquaredeuclidean'} @tab Euclidean distance computed ## with an alternative algorithm which may be faster but might reduce accuracy. ## @item @qcode{'seuclidean'} @tab standardized Euclidean distance. Each ## coordinate difference between the rows in @var{X} and the query matrix ## @var{Y} is scaled by dividing by the corresponding element of the standard ## deviation computed from @var{X}. A different scaling vector can be specified ## with the subsequent @var{DistParameter} input argument. ## @item @qcode{'mahalanobis'} @tab Mahalanobis distance, computed using a ## positive definite covariance matrix. A different covariance matrix can be ## specified with the subsequent @var{DistParameter} input argument. ## @item @qcode{'cityblock'} @tab City block distance. ## @item @qcode{'minkowski'} @tab Minkowski distance. The default exponent ## is 2. A different exponent can be specified with the subsequent ## @var{DistParameter} input argument. ## @item @qcode{'chebychev'} @tab Chebychev distance (maximum coordinate ## difference). ## @item @qcode{'cosine'} @tab One minus the cosine of the included angle ## between points (treated as vectors). ## @item @qcode{'correlation'} @tab One minus the sample linear correlation ## between observations (treated as sequences of values). ## @item @qcode{'hamming'} @tab Hamming distance, which is the percentage ## of coordinates that differ. ## @item @qcode{'jaccard'} @tab One minus the Jaccard coefficient, which is ## the percentage of nonzero coordinates that differ. ## @item @qcode{'spearman'} @tab One minus the sample Spearman's rank ## correlation between observations (treated as sequences of values). ## @item @var{@@distfun} @tab Custom distance function handle. A distance ## function of the form @code{function @var{D2} = distfun (@var{XI}, @var{YI})}, ## where @var{XI} is a @math{1*P} vector containing a single observation in ## @math{P}-dimensional space, @var{YI} is an @math{N*P} matrix containing an ## arbitrary number of observations in the same @math{P}-dimensional space, and ## @var{D2} is an @math{N*P} vector of distances, where @qcode{(@var{D2}k)} is ## the distance between observations @var{XI} and @qcode{(@var{YI}k,:)}. ## @end multitable ## ## @code{@var{D} = pdist2 (@var{X}, @var{Y}, @var{Distance}, ## @var{DistParameter})} ## returns the distance using the metric specified by @var{Distance} and ## @var{DistParameter}. The latter one can only be specified when the selected ## @var{Distance} is @qcode{'seuclidean'}, @qcode{'minkowski'}, and ## @qcode{'mahalanobis'}. ## ## @code{@var{D} = pdist2 (@dots{}, @var{Name}, @var{Value})} for any previous ## arguments, modifies the computation using @var{Name}-@var{Value} parameters. ## @itemize ## @item ## @code{@var{D} = pdist2 (@var{X}, @var{Y}, @var{Distance}, @qcode{'Smallest'}, ## @var{K})} computes the distance using the metric specified by ## @var{Distance} and returns the @var{K} smallest pairwise distances to ## observations in @var{X} for each observation in @var{Y} in ascending order. ## @item ## @code{@var{D} = pdist2 (@var{X}, @var{Y}, @var{Distance}, ## @var{DistParameter}, ## @qcode{'Largest'}, @var{K})} computes the distance using the metric specified ## by @var{Distance} and @var{DistParameter} and returns the @var{K} largest ## pairwise distances in descending order. ## @end itemize ## ## @code{[@var{D}, @var{I}] = pdist2 (@dots{}, @var{Name}, @var{Value})} also ## returns the matrix @var{I}, which contains the indices of the observations in ## @var{X} corresponding to the distances in @var{D}. You must specify either ## @qcode{'Smallest'} or @qcode{'Largest'} as an optional @var{Name}-@var{Value} ## pair argument to compute the second output argument. ## ## @seealso{pdist, knnsearch, rangesearch} ## @end deftypefn function [D, I] = pdist2 (X, Y, varargin) ## Check input data if (nargin < 2) error ("pdist2: too few input arguments."); endif if (size (X, 2) != size (Y, 2)) error ("pdist2: X and Y must have equal number of columns."); endif if (ndims (X) != 2 || ndims (Y) != 2) error ("pdist2: X and Y must be 2 dimensional matrices."); endif ## Add default values Distance = 'euclidean'; # Distance metric DistParameter = []; # Distance parameter SortOrder = []; # Flag for sorting distances to find ## Parse additional Distance metric and Distance parameter (if available) DMs = {'euclidean', 'squaredeuclidean', 'seuclidean', ... 'fasteuclidean', 'fastsquaredeuclidean', ... 'chebychev', 'cityblock', 'cosine', 'correlation', ... 'mahalanobis', 'minkowski', 'hamming', 'jaccard', 'spearman'}; if (numel (varargin) > 0) if (any (strcmpi (DMs, varargin{1}))) Distance = tolower (varargin{1}); varargin(1) = []; if (numel (varargin) > 0) if (isnumeric (varargin{1})) DistParameter = varargin{1}; varargin(1) = []; endif endif elseif (is_function_handle (varargin{1})) Distance = varargin{1}; varargin(1) = []; if (numel (varargin) > 0) if (isnumeric (varargin{1})) DistParameter = varargin{1}; varargin(1) = []; endif endif endif endif ## Parse additional parameters in Name/Value pairs parcount = 0; while (numel (varargin) > 0) if (numel (varargin) < 2) error ("pdist2: missing value in optional name/value paired arguments."); endif switch (tolower (varargin{1})) case 'smallest' SortOrder = 'ascend'; K = varargin{2}; parcount += 1; case 'largest' SortOrder = 'descend'; K = varargin{2}; parcount += 1; otherwise error ("pdist2: invalid NAME in optional pairs of arguments."); endswitch varargin(1:2) = []; endwhile ## Check additional arguments if (parcount > 0) if (fix (K) != K || K < 1) error ("pdist2: K must be a positive integer value."); endif endif if (parcount > 1) error ("pdist2: you can only specify either 'Smallest' or 'Largest'."); endif if (isempty (SortOrder) && nargout > 1) error (strcat ("pdist2: 'Smallest' or 'Largest' must be", ... " specified to compute second output.")); endif ## FAST PATH: Optimization for fast Euclidean algorithm fast_algs = {'fasteuclidean', 'fastsquaredeuclidean'}; if (ischar (Distance) && ismember (Distance, fast_algs)) ## sumsq avoids the temporary memory of X.^2 ## D = ||x||^2 + ||y||^2 - 2x.y D = sumsq (X, 2) + sumsq (Y, 2).' - 2 * (X * Y'); ## Remove possible numerical negative noise D = max (D, 0); if (strcmp (Distance, 'fasteuclidean')) D = sqrt (D); endif ## Handle SortOrder (Smallest/Largest) output if requested if (nargout > 1 || parcount) ## This finds K nearest X's for each Y. [D, I] = sort (D, 1, SortOrder); K = min (size (D, 1), K); D = D(1:K, :); I = I(1:K, :); endif return; endif ## Threshold for switching between vectorized and blocked computation M = size (X, 1); Nrows = size (Y, 1); N_threshold = 500; ## Handle a function handle (always row-by-row) if (is_function_handle (Distance)) ## Check the input output sizes of the user function D2 = []; try D2 = Distance(X(1,:), Y); catch ME error ("pdist2: invalid function handle for distance metric."); end_try_catch if (! isequal (size (D2), [Nrows, 1])) error ("pdist2: custom distance function produces wrong output size."); endif ## Evaluate user defined distance metric function D = zeros (M, Nrows); for r = 1:M D(r,:) = feval (Distance, X(r,:), Y)'; endfor if (nargout > 1 || parcount) [D, I] = sort (D', 2, SortOrder); K = min (size (D, 2), K); D = D(:,1:K)'; I = I(:,1:K)'; endif return; endif ## For small M and N: use original vectorized implementation (fast, O(M*N*P) memory) ## For large M or N: use blocked row-by-row computation (slower, O(N*P) memory) if (max (M, Nrows) < N_threshold) ## Original vectorized implementation if (strcmp (Distance, 'cosine')) X_inv_norm = 1 ./ sqrt (sumsq (X, 2)); Y_inv_norm = 1 ./ sqrt (sumsq (Y, 2)); ## Normalize the matrices in-place (broadcasting) X = X .* X_inv_norm; Y = Y .* Y_inv_norm; ## Cosine distance = 1 - dot_product (normalized vectors) D = 1 - (X * Y'); ## A similarity a rounding step above one would give a negative ## distance, which MATLAB never returns. Only the sign is corrected: ## parallel rows still land a rounding step above zero in both ## engines. A NaN compares false and is left alone. D(D < 0) = 0; else ## Create indexing grid [ix, iy] = meshgrid (1:M, 1:Nrows); switch (Distance) case 'euclidean' D = sqrt (sum ((X(ix(:),:) - Y(iy(:),:)) .^ 2, 2)); case 'squaredeuclidean' D = sum ((X(ix(:),:) - Y(iy(:),:)) .^ 2, 2); case 'seuclidean' if (isempty (DistParameter)) DistParameter = std (X, [], 1); else if (numel (DistParameter) != columns (X)) error (strcat ("pdist2: DistParameter for standardized", ... " euclidean must be a vector of equal length", ... " to the number of columns in X.")); endif if (any (DistParameter < 0)) error (strcat ("pdist2: DistParameter for standardized", ... " euclidean must be a nonnegative vector.")); endif endif DistParameter(DistParameter == 0) = 1; # fix constant variable D = sqrt (sum (((X(ix(:),:) - Y(iy(:),:)) ./ DistParameter) .^ 2, 2)); case 'mahalanobis' if (isempty (DistParameter)) DistParameter = cov (X(! any (isnan (X), 2),:)); else if (columns (DistParameter) != columns (X)) error (strcat ("pdist2: DistParameter for mahalanobis", ... " distance must be a covariance matrix with", ... " the same number of columns as X.")); endif [~, p] = chol (DistParameter); if (p != 0) error (strcat ("pdist2: covariance matrix for mahalanobis", ... " distance must be symmetric and positive", ... " definite.")); endif endif ## Catch warning if matrix is close to singular or badly scaled. [DP_inv, rc] = inv (DistParameter); if (rc < eps) msg = sprintf (strcat ("pdist2: matrix is close to", ... " singular or badly scaled.\n RCOND = ", ... " %e. Results may be inaccurate."), rc); warning (msg); endif dxy = X(ix(:),:) - Y(iy(:),:); D = sqrt (sum ((dxy * DP_inv) .* dxy, 2)); case 'cityblock' D = sum (abs (X(ix(:),:) - Y(iy(:),:)), 2); case 'minkowski' if (isempty (DistParameter)) DistParameter = 2; else if (! (isnumeric (DistParameter) && isscalar (DistParameter) && DistParameter > 0)) error (strcat ("pdist2: DistParameter for minkowski distance", ... " must be a positive scalar.")); endif endif D = sum (abs (X(ix(:),:) - Y(iy(:),:)) .^ DistParameter, 2) .^ ... (1 / DistParameter); case 'chebychev' D = max (abs (X(ix(:),:) - Y(iy(:),:)), [], 2); case 'correlation' mX = mean (X(ix(:),:), 2); mY = mean (Y(iy(:),:), 2); xy = sum ((X(ix(:),:) - mX) .* (Y(iy(:),:) - mY), 2); xx = sqrt (sum ((X(ix(:),:) - mX) .* (X(ix(:),:) - mX), 2)); yy = sqrt (sum ((Y(iy(:),:) - mY) .* (Y(iy(:),:) - mY), 2)); D = 1 - (xy ./ (xx .* yy)); D(D < 0) = 0; case 'hamming' D = mean (abs (X(ix(:),:) != Y(iy(:),:)), 2); case 'jaccard' xy0 = (X(ix(:),:) != 0 | Y(iy(:),:) != 0); D = sum ((X(ix(:),:) != Y(iy(:),:)) & xy0, 2) ./ sum (xy0, 2); case 'spearman' for i = 1:M rX(i,:) = tiedrank (X(i,:)); endfor for i = 1:Nrows rY(i,:) = tiedrank (Y(i,:)); endfor rM = (size (X, 2) + 1) / 2; xy = sum ((rX(ix(:),:) - rM) .* (rY(iy(:),:) - rM), 2); xx = sqrt (sum ((rX(ix(:),:) - rM) .* (rX(ix(:),:) - rM), 2)); yy = sqrt (sum ((rY(iy(:),:) - rM) .* (rY(iy(:),:) - rM), 2)); D = 1 - (xy ./ (xx .* yy)); D(D < 0) = 0; endswitch ## From vector to matrix D = reshape (D, Nrows, M)'; endif else ## Blocked row-by-row computation for large M or N (avoids O(M*N*P) memory) D = zeros (M, Nrows); ## Precompute metric-specific data switch (Distance) case 'seuclidean' if (isempty (DistParameter)) DistParameter = std (X, [], 1); else if (numel (DistParameter) != columns (X)) error (strcat ("pdist2: DistParameter for standardized", ... " euclidean must be a vector of equal length", ... " to the number of columns in X.")); endif if (any (DistParameter < 0)) error (strcat ("pdist2: DistParameter for standardized", ... " euclidean must be a nonnegative vector.")); endif endif DistParameter(DistParameter == 0) = 1; case 'mahalanobis' if (isempty (DistParameter)) DistParameter = cov (X(! any (isnan (X), 2),:)); else if (columns (DistParameter) != columns (X)) error (strcat ("pdist2: DistParameter for mahalanobis", ... " distance must be a covariance matrix with", ... " the same number of columns as X.")); endif [~, p] = chol (DistParameter); if (p != 0) error (strcat ("pdist2: covariance matrix for mahalanobis", ... " distance must be symmetric and positive", ... " definite.")); endif endif [DP_inv, rc] = inv (DistParameter); if (rc < eps) msg = sprintf (strcat ("pdist2: matrix is close to", ... " singular or badly scaled.\n RCOND = ", ... " %e. Results may be inaccurate."), rc); warning (msg); endif case 'minkowski' if (isempty (DistParameter)) DistParameter = 2; else if (! (isnumeric (DistParameter) && isscalar (DistParameter) && DistParameter > 0)) error (strcat ("pdist2: DistParameter for minkowski distance", ... " must be a positive scalar.")); endif endif case 'cosine' sx = sum (X .^ 2, 2) .^ (-1 / 2); sy = sum (Y .^ 2, 2) .^ (-1 / 2); case 'spearman' rX = zeros (size (X)); rY = zeros (size (Y)); for i = 1:M rX(i,:) = tiedrank (X(i,:)); endfor for i = 1:Nrows rY(i,:) = tiedrank (Y(i,:)); endfor rM = (size (X, 2) + 1) / 2; endswitch ## Row-by-row computation with switch outside loop switch (Distance) case 'euclidean' for i = 1:M D(i,:) = sqrt (sum ((X(i,:) - Y) .^ 2, 2))'; endfor case 'squaredeuclidean' for i = 1:M D(i,:) = sum ((X(i,:) - Y) .^ 2, 2)'; endfor case 'seuclidean' for i = 1:M D(i,:) = sqrt (sum (((X(i,:) - Y) ./ DistParameter) .^ 2, 2))'; endfor case 'mahalanobis' for i = 1:M dxy = X(i,:) - Y; D(i,:) = sqrt (sum ((dxy * DP_inv) .* dxy, 2))'; endfor case 'cityblock' for i = 1:M D(i,:) = sum (abs (X(i,:) - Y), 2)'; endfor case 'minkowski' for i = 1:M D(i,:) = (sum (abs (X(i,:) - Y) .^ DistParameter, 2) .^ (1 / DistParameter))'; endfor case 'chebychev' for i = 1:M D(i,:) = max (abs (X(i,:) - Y), [], 2)'; endfor case 'cosine' for i = 1:M d = (1 - sum (X(i,:) .* Y, 2) .* sx(i) .* sy)'; d(d < 0) = 0; D(i,:) = d; endfor case 'correlation' for i = 1:M mXi = mean (X(i,:)); mY = mean (Y, 2); xy = sum ((X(i,:) - mXi) .* (Y - mY), 2); xx = sqrt (sum ((X(i,:) - mXi) .^ 2)); yy = sqrt (sum ((Y - mY) .^ 2, 2)); d = (1 - (xy ./ (xx .* yy)))'; d(d < 0) = 0; D(i,:) = d; endfor case 'hamming' for i = 1:M D(i,:) = mean (abs (X(i,:) != Y), 2)'; endfor case 'jaccard' for i = 1:M xy0 = (X(i,:) != 0 | Y != 0); D(i,:) = (sum ((X(i,:) != Y) & xy0, 2) ./ sum (xy0, 2))'; endfor case 'spearman' for i = 1:M xy = sum ((rX(i,:) - rM) .* (rY - rM), 2); xx = sqrt (sum ((rX(i,:) - rM) .^ 2)); yy = sqrt (sum ((rY - rM) .^ 2, 2)); d = (1 - (xy ./ (xx .* yy)))'; d(d < 0) = 0; D(i,:) = d; endfor endswitch endif if (nargout > 1 || parcount) [D, I] = sort (D', 2, SortOrder); K = min (size (D, 2), K); # fix max K to avoid out of bound error D = D(:,1:K)'; I = I(:,1:K)'; endif endfunction ## Test output %!shared x, y, xx %! x = [1, 1, 1; 2, 2, 2; 3, 3, 3]; %! y = [0, 0, 0; 1, 2, 3; 0, 2, 4; 4, 7, 1]; %! xx = [1 2 3; 4 5 6; 7 8 9; 3 2 1]; %!test %! d = sqrt ([3, 5, 11, 45; 12, 2, 8, 30; 27, 5, 11, 21]); %! assert_equal (pdist2 (x, y), d); %!test %! d = [5.1962, 2.2361, 3.3166, 6.7082; ... %! 3.4641, 2.2361, 3.3166, 5.4772]; %! i = [3, 1, 1, 1; 2, 3, 3, 2]; %! [D, I] = pdist2 (x, y, 'euclidean', 'largest', 2); %! assert_equal ({D, I}, {d, i}, 1e-4); %!test %! d = [1.7321, 1.4142, 2.8284, 4.5826; ... %! 3.4641, 2.2361, 3.3166, 5.4772]; %! i = [1, 2, 2, 3;2, 1, 1, 2]; %! [D, I] = pdist2 (x, y, 'euclidean', 'smallest', 2); %! assert_equal ({D, I}, {d, i}, 1e-4); %!test %! yy = [1 2 3;5 6 7;9 5 1]; %! d = [0, 6.1644, 5.3852; 1.4142, 6.9282, 8.7750; ... %! 3.7417, 7.0711, 9.9499; 6.1644, 10.4881, 10.3441]; %! i = [2, 4, 4; 3, 2, 2; 1, 3, 3; 4, 1, 1]; %! [D, I] = pdist2 (y, yy, 'euclidean', 'smallest', 4); %! assert_equal ({D, I}, {d, i}, 1e-4); %!test %! yy = [1 2 3;5 6 7;9 5 1]; %! d = [0, 38, 29; 2, 48, 77; 14, 50, 99; 38, 110, 107]; %! i = [2, 4, 4; 3, 2, 2; 1, 3, 3; 4, 1, 1]; %! [D, I] = pdist2 (y, yy, 'squaredeuclidean', 'smallest', 4); %! assert_equal ({D, I}, {d, i}, 1e-4); %!test %! yy = [1 2 3;5 6 7;9 5 1]; %! d = [0, 3.3256, 2.7249; 0.7610, 3.3453, 4.4799; ... %! 1.8514, 3.3869, 5.0703; 2.5525, 5.0709, 5.1297]; %! i = [2, 2, 4; 3, 4, 2; 1, 3, 1; 4, 1, 3]; %! [D, I] = pdist2 (y, yy, 'seuclidean', 'smallest', 4); %! assert_equal ({D, I}, {d, i}, 1e-4); %!test %! d = [2.1213, 4.2426, 6.3640; 1.2247, 2.4495, 4.4159; ... %! 3.2404, 4.8990, 6.8191; 2.7386, 4.2426, 6.1237]; %! assert_equal (pdist2 (y, x, 'mahalanobis'), d, 1e-4); %!test %! xx = [1, 3, 4; 3, 5, 4; 8, 7, 6]; %! d = [1.3053, 1.8257, 15.0499; 1.3053, 3.3665, 16.5680]; %! i = [2, 2, 2; 3, 4, 4]; %! [D, I] = pdist2 (y, xx, 'mahalanobis', 'smallest', 2); %! assert_equal ({D, I}, {d, i}, 1e-4); %!test %! d = [2.5240, 4.1633, 17.3638; 2.0905, 3.9158, 17.0147]; %! i = [1, 1, 3; 4, 3, 1]; %! [D, I] = pdist2 (y, xx, 'mahalanobis', 'largest', 2); %! assert_equal ({D, I}, {d, i}, 1e-4); %!test %! d = [3, 3, 5, 9; 6, 2, 4, 8; 9, 3, 5, 7]; %! assert_equal (pdist2 (x, y, 'cityblock'), d); %!test %! d = [1, 2, 3, 6; 2, 1, 2, 5; 3, 2, 3, 4]; %! assert_equal (pdist2 (x, y, 'chebychev'), d); %!test %! d = repmat ([NaN, 0.0742, 0.2254, 0.1472], [3, 1]); %! assert_equal (pdist2 (x, y, 'cosine'), d, 1e-4); %!test %! yy = [1 2 3;5 6 7;9 5 1]; %! d = [0, 0, 0.5; 0, 0, 2; 1.5, 1.5, 2; NaN, NaN, NaN]; %! i = [2, 2, 4; 3, 3, 2; 4, 4, 3; 1, 1, 1]; %! [D, I] = pdist2 (y, yy, 'correlation', 'smallest', 4); %! assert_equal ({D, I}, {d, i}, eps); %! [D, I] = pdist2 (y, yy, 'spearman', 'smallest', 4); %! assert_equal ({D, I}, {d, i}, eps); %!test %! d = [1, 2/3, 1, 1; 1, 2/3, 1, 1; 1, 2/3, 2/3, 2/3]; %! i = [1, 1, 1, 2; 2, 2, 3, 3; 3, 3, 2, 1]; %! [D, I] = pdist2 (x, y, 'hamming', 'largest', 4); %! assert_equal ({D, I}, {d, i}, eps); %! [D, I] = pdist2 (x, y, 'jaccard', 'largest', 4); %! assert_equal ({D, I}, {d, i}, eps); %!test %! xx = [1, 2, 3, 4; 2, 3, 4, 5; 3, 4, 5, 6]; %! yy = [1, 2, 2, 3; 2, 3, 3, 4]; %! [D, I] = pdist2 (x, y, 'euclidean', 'Smallest', 4); %! eucldist = @(v,m) sqrt (sumsq (repmat (v,rows (m),1)-m,2)); %! [d, i] = pdist2 (x, y, eucldist, 'Smallest', 4); %! assert_equal ({D, I}, {d, i}); %!warning ... %! pdist2 (xx, xx, 'mahalanobis'); ## Test input validation %!test %! ## A row is never at a negative distance from itself. For identical rows %! ## the similarity can round a step above one, and one minus it is then %! ## below zero, which no distance may be. A distance a rounding step above %! ## zero is kept, MATLAB returning one there too. %! X = [-3, -3; -3, -3]; %! assert_equal (pdist2 (X, X, "cosine"), zeros (2)); %!test %! ## Neither do the other two metrics built as one minus a similarity. %! Y = [1, 2, 4; 2, 4, 8; -1, -2, -4]; %! for m = {"cosine", "correlation", "spearman"} %! D = pdist2 (Y, Y, m{1}); %! assert_equal (any (D(:) < 0), false); %! endfor %!test %! ## The clamp corrects the sign and leaves a NaN alone: a zero row has no %! ## direction, so its cosine distance is undefined rather than zero, and %! ## stays undefined. %! Z = [0, 0; 1, 1]; %! D = pdist2 (Z, Z, "cosine"); %! assert_equal (isnan (D), [true, true; true, false]); %! assert_equal (D(2,2) >= 0, true); %!error pdist2 (1) %!error ... %! pdist2 (ones (4, 5), ones (4)) %!error ... %! pdist2 (ones (4, 2, 3), ones (3, 2)) %!error ... %! pdist2 (ones (3), ones (3), 'euclidean', 'Largest') %!error ... %! pdist2 (ones (3), ones (3), 'minkowski', 3, 'Largest') %!error ... %! pdist2 (ones (3), ones (3), 'minkowski', 3, 'large', 4) %!error ... %! pdist2 (ones (3), ones (3), 'minkowski', 3, 'largest', 4.5) %!error ... %! pdist2 (ones (3), ones (3), 'minkowski', 3, 'Largest', 4, 'smallest', 5) %!error ... %! [d, i] = pdist2 (ones (3), ones (3), 'minkowski', 3) %!error ... %! pdist2 (ones (3), ones (3), 'seuclidean', 3) %!error ... %! pdist2 (ones (3), ones (3), 'seuclidean', [1, -1, 3]) %!error ... %! pdist2 (ones (3), eye (3), 'mahalanobis', eye (2)) %!error ... %! pdist2 (ones (3), eye (3), 'mahalanobis', ones (3)) %!error ... %! pdist2 (ones (3), eye (3), 'minkowski', 0) %!error ... %! pdist2 (ones (3), eye (3), 'minkowski', -5) %!error ... %! pdist2 (ones (3), eye (3), 'minkowski', [1, 2]) %!error ... %! pdist2 (ones (3), ones (3), @(v,m) sqrt (repmat (v,rows (m),1)-m,2)) %!error ... %! pdist2 (ones (3), ones (3), @(v,m) sqrt (sum (sumsq (repmat (v,rows (m),1)-m,2)))) statistics-release-1.9.2/inst/Nearest_Neighbors/private/000077500000000000000000000000001524624707500233775ustar00rootroot00000000000000statistics-release-1.9.2/inst/Nearest_Neighbors/private/__build_kdtree__.m000066400000000000000000000046031524624707500270110ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by the Free ## Software Foundation; either version 3 of the License, or (at your option) ## any later version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ## more details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{node} =} __build_kdtree__ (@var{indices}, @var{depth}, @var{X}, @var{bucket_size}) ## ## Build a Kd-tree over the rows of @var{X} named by @var{indices}. Shared by ## @code{knnsearch}, @code{rangesearch} and @code{KDTreeSearcher}. ## ## A node is a leaf, carrying an @qcode{indices} field, or a split, carrying ## @qcode{axis}, @qcode{split_value}, @qcode{left} and @qcode{right}. The ## split axis cycles with the depth. ## ## @end deftypefn function node = __build_kdtree__ (indices, depth, X, bucket_size) if (length (indices) <= bucket_size) node = struct ('indices', indices); else k = size (X, 2); axis = mod (depth, k) + 1; values = X(indices, axis); sorted_values = sort (values); median_idx = floor ((length (indices) + 1) / 2); split_value = sorted_values(median_idx); left_indices = indices(values <= split_value); right_indices = indices(values > split_value); ## Points sharing the split value all go left, so a set that is constant ## on this axis cannot be divided here. Without this the recursion hands ## itself the same set forever, which more than BUCKET_SIZE identical rows ## used to do. if (isempty (left_indices) || isempty (right_indices)) node = struct ('indices', indices); return; endif left_node = __build_kdtree__ (left_indices, depth + 1, X, bucket_size); right_node = __build_kdtree__ (right_indices, depth + 1, X, bucket_size); node = struct ('axis', axis, 'split_value', split_value, ... 'left', left_node, 'right', right_node); endif endfunction statistics-release-1.9.2/inst/Nearest_Neighbors/private/__resolve_metric__.m000066400000000000000000000073021524624707500273750ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{dist}, @var{param}] =} __resolve_metric__ (@var{fname}, @var{X}, @var{dist}, @var{P}, @var{C}, @var{S}, @var{metrics}) ## ## Validate a distance metric and its parameter for a per-call override. ## ## @var{fname} names the caller for the error messages, @var{X} is the training ## data a default parameter is derived from, and @var{dist} the metric to use. ## @var{P}, @var{C} and @var{S} carry the @qcode{'P'}, @qcode{'Cov'} and ## @qcode{'Scale'} values, each empty when not supplied, and @var{metrics} lists ## the metrics the caller accepts. ## ## The returned @var{param} is the distance parameter belonging to @var{dist}: ## the supplied value where there is one, otherwise the same default the ## constructor would derive from @var{X}. Nothing is written back to the ## searcher, so an override applies only to the call that asked for it. ## ## @end deftypefn function [dist, param] = __resolve_metric__ (fname, X, dist, P, C, S, metrics) if (! (ischar (dist) && any (strcmpi (dist, metrics)))) error ("%s: unsupported distance metric '%s'.", fname, dist); endif dist = lower (dist); if (strcmpi (dist, 'minkowski')) if (isempty (P)) param = 2; else if (! (isscalar (P) && isnumeric (P) && isfinite (P) && P > 0)) error ("%s: 'P' must be a positive finite scalar.", fname); endif param = P; endif elseif (strcmpi (dist, 'seuclidean')) if (isempty (S)) param = std (X, [], 1); else if (! (isvector (S) && isnumeric (S) && all (S >= 0) && numel (S) == columns (X))) error (strcat ("%s: 'Scale' must be a nonnegative vector", ... " matching the columns of X."), fname); endif param = S(:)'; endif elseif (strcmpi (dist, 'mahalanobis')) if (isempty (C)) param = cov (X); else if (! (isnumeric (C) && ismatrix (C) && rows (C) == columns (C) && rows (C) == columns (X))) error (strcat ("%s: 'Cov' must be a square matrix matching the", ... " columns of X."), fname); endif if (! issymmetric (C)) error ("%s: 'Cov' must be symmetric for mahalanobis.", fname); endif [~, p] = chol (C); if (p != 0) error ("%s: 'Cov' must be positive definite for mahalanobis.", fname); endif param = C; endif else param = []; endif ## A parameter that belongs to a different metric is a mistake, not a value ## to be quietly dropped if (! isempty (P) && ! strcmpi (dist, 'minkowski')) error ("%s: 'P' applies only to the minkowski metric.", fname); endif if (! isempty (S) && ! strcmpi (dist, 'seuclidean')) error ("%s: 'Scale' applies only to the seuclidean metric.", fname); endif if (! isempty (C) && ! strcmpi (dist, 'mahalanobis')) error ("%s: 'Cov' applies only to the mahalanobis metric.", fname); endif endfunction statistics-release-1.9.2/inst/Nearest_Neighbors/private/__search_kdtree__.m000066400000000000000000000120551524624707500271570ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by the Free ## Software Foundation; either version 3 of the License, or (at your option) ## any later version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ## more details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{indices}, @var{distances}] =} __search_kdtree__ (@var{node}, @var{query}, @var{k}, @var{X}, @var{dist}, @var{distparam}, @var{is_range}) ## @deftypefnx {Private Function} {[@var{indices}, @var{distances}] =} __search_kdtree__ (@dots{}, @var{r}) ## ## Search a Kd-tree built by @code{__build_kdtree__} for one query point. ## Shared by @code{knnsearch}, @code{rangesearch} and @code{KDTreeSearcher}. ## ## With @var{is_range} false the @var{k} nearest neighbours are returned; with ## it true, every neighbour within the radius @var{r}. @var{dist} names the ## metric and @var{distparam} is the exponent for @qcode{'minkowski'} and empty ## otherwise. ## ## The metric is resolved to a function of a leaf's rows once per call rather ## than dispatched per leaf, which is what makes the walk affordable: a leaf ## holds a few dozen points and a general distance call costs far more than the ## arithmetic it performs on them. ## ## @end deftypefn function [indices, distances] = __search_kdtree__ (node, query, k, X, dist, ... distparam, is_range, r) if (nargin < 8) r = Inf; endif if (strcmpi (dist, 'minkowski')) if (! (isscalar (distparam) && isnumeric (distparam) && distparam > 0 && isfinite (distparam))) error (strcat ("__search_kdtree__: DISTPARAM must be a positive", ... " finite scalar for minkowski.")); endif elseif (! isempty (distparam)) error (strcat ("__search_kdtree__: DISTPARAM must be empty for", ... " non-minkowski metrics.")); endif ## The metric, resolved once. 'manhattan' is the documented alias of ## 'cityblock' and the search accepts it wherever the callers let it through. switch (lower (dist)) case 'euclidean' compute_dists = @(leaf_X) sqrt (sum ((leaf_X - query) .^ 2, 2)); case {'cityblock', 'manhattan'} compute_dists = @(leaf_X) sum (abs (leaf_X - query), 2); case 'chebychev' compute_dists = @(leaf_X) max (abs (leaf_X - query), [], 2); case 'minkowski' p = distparam; compute_dists = @(leaf_X) sum (abs (leaf_X - query) .^ p, 2) .^ (1 / p); otherwise error ("__search_kdtree__: unsupported distance metric '%s'.", dist); endswitch indices = []; distances = []; search (node, 0); function search (node, depth) if (isempty (node)) return; endif if (isfield (node, 'indices')) leaf_indices = node.indices; dists = compute_dists (X(leaf_indices,:)); if (is_range) mask = dists <= r; indices = [indices; leaf_indices(mask)']; distances = [distances; dists(mask)]; elseif (length (distances) >= k) ## The list is already full, so only a candidate beating its worst ## member is worth merging, and most leaves supply none. mask = dists < distances(end); if (any (mask)) indices = [indices; leaf_indices(mask)']; distances = [distances; dists(mask)]; [distances, sort_idx] = sort (distances); indices = indices(sort_idx); distances = distances(1:k); indices = indices(1:k); endif else indices = [indices; leaf_indices']; distances = [distances; dists]; if (length (distances) >= k) [distances, sort_idx] = sort (distances); indices = indices(sort_idx); if (length (distances) > k) distances = distances(1:k); indices = indices(1:k); endif endif endif else axis = node.axis; split_value = node.split_value; if (query(axis) <= split_value) nearer = node.left; further = node.right; else nearer = node.right; further = node.left; endif search (nearer, depth + 1); ## The far side can only hold something better if the splitting plane is ## itself closer than the worst neighbour held so far. plane_dist = abs (query(axis) - split_value); if (is_range) if (plane_dist <= r) search (further, depth + 1); endif elseif (length (distances) < k || plane_dist < distances(end)) search (further, depth + 1); endif endif endfunction endfunction statistics-release-1.9.2/inst/Nearest_Neighbors/rangesearch.m000066400000000000000000000516651524624707500244020ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{idx} =} rangesearch (@var{X}, @var{Y}, @var{r}) ## @deftypefnx {statistics} {[@var{idx}, @var{D}] =} rangesearch (@var{X}, @var{Y}, @var{r}) ## @deftypefnx {statistics} {[@dots{}] =} rangesearch (@dots{}, @var{name}, @var{value}) ## ## Find all neighbors within specified distance from input data. ## ## @code{@var{idx} = rangesearch (@var{X}, @var{Y}, @var{r})} returns all the ## points in @var{X} that are within distance @var{r} from the points in ## @var{Y}. ## @var{X} must be an @math{N*P} numeric matrix of input data, where rows ## correspond to observations and columns correspond to features or variables. ## @var{Y} is an @math{M*P} numeric matrix with query points, which must have ## the same numbers of column as @var{X}. @var{r} must be a nonnegative scalar ## value. @var{idx} is an @math{M*1} cell array, where @math{M} is the number ## of observations in @var{Y}. The vector @qcode{@var{Idx}@{j@}} contains the ## indices of observations (rows) in @var{X} whose distances to ## @qcode{@var{Y}(j,:)} are not greater than @var{r}. ## ## @code{[@var{idx}, @var{D}] = rangesearch (@var{X}, @var{Y}, @var{r})} also ## returns the distances, @var{D}, which correspond to the points in @var{X} ## that are within distance @var{r} from the points in @var{Y}. @var{D} is an ## @math{M*1} cell array, where @math{M} is the number of observations in ## @var{Y}. The vector @qcode{@var{D}@{j@}} contains the distances of ## observations (rows) in @var{X} whose distances to @qcode{@var{Y}(j,:)} are ## not greater than @var{r}. ## ## Additional parameters can be specified by @qcode{Name-Value} pair arguments. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'P'} @tab is the Minkowski distance exponent and it must be ## a positive scalar. This argument is only valid when the selected distance ## metric is @qcode{'minkowski'}. By default it is 2. ## ## @item @qcode{'Scale'} @tab is the scale parameter for the standardized ## Euclidean distance and it must be a nonnegative numeric vector of equal ## length to the number of columns in @var{X}. This argument is only valid when ## the selected distance metric is @qcode{'seuclidean'}, in which case each ## coordinate of @var{X} is scaled by the corresponding element of ## @qcode{'scale'}, as is each query point in @var{Y}. By default, the scale ## parameter is the standard deviation of each coordinate in @var{X}. ## ## @item @qcode{'Cov'} @tab is the covariance matrix for computing the ## mahalanobis distance and it must be a positive definite matrix matching the ## the number of columns in @var{X}. This argument is only valid when the ## selected distance metric is @qcode{'mahalanobis'}. ## ## @item @qcode{'BucketSize'} @tab is the maximum number of data points in ## the leaf node of the Kd-tree and it must be a positive integer. This ## argument is only valid when the selected search method is @qcode{'kdtree'}. ## ## @item @qcode{'SortIndices'} @tab is a boolean flag to sort the returned ## indices in ascending order by distance and it is @qcode{true} by default. ## When the selected search method is @qcode{'exhaustive'} or the ## @qcode{'IncludeTies'} flag is true, @code{rangesearch} always sorts the ## returned indices. ## ## @item @qcode{'Distance'} @tab is the distance metric used by ## @code{rangesearch} as specified below: ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @item @qcode{'euclidean'} @tab Euclidean distance. ## @item @qcode{'seuclidean'} @tab standardized Euclidean distance. Each ## coordinate difference between the rows in @var{X} and the query matrix ## @var{Y} is scaled by dividing by the corresponding element of the standard ## deviation computed from @var{X}. To specify a different scaling, use the ## @qcode{'Scale'} name-value argument. ## @item @qcode{'cityblock'} @tab City block distance. ## @item @qcode{'chebychev'} @tab Chebychev distance (maximum coordinate ## difference). ## @item @qcode{'minkowski'} @tab Minkowski distance. The default exponent ## is 2. To specify a different exponent, use the @qcode{'P'} name-value ## argument. ## @item @qcode{'mahalanobis'} @tab Mahalanobis distance, computed using a ## positive definite covariance matrix. To change the value of the covariance ## matrix, use the @qcode{'Cov'} name-value argument. ## @item @qcode{'cosine'} @tab Cosine distance. ## @item @qcode{'correlation'} @tab One minus the sample linear correlation ## between observations (treated as sequences of values). ## @item @qcode{'spearman'} @tab One minus the sample Spearman's rank ## correlation between observations (treated as sequences of values). ## @item @qcode{'hamming'} @tab Hamming distance, which is the percentage ## of coordinates that differ. ## @item @qcode{'jaccard'} @tab One minus the Jaccard coefficient, which is ## the percentage of nonzero coordinates that differ. ## @item @var{@@distfun} @tab Custom distance function handle. A distance ## function of the form @code{function @var{D2} = distfun (@var{XI}, @var{YI})}, ## where @var{XI} is a @math{1*P} vector containing a single observation in ## @math{P}-dimensional space, @var{YI} is an @math{N*P} matrix containing an ## arbitrary number of observations in the same @math{P}-dimensional space, and ## @var{D2} is an @math{N*P} vector of distances, where @qcode{(@var{D2}k)} is ## the distance between observations @var{XI} and @qcode{(@var{YI}k,:)}. ## @end multitable ## ## @multitable @columnfractions 0.18 0.8 ## @item @qcode{'NSMethod'} @tab is the nearest neighbor search method used ## by @code{rangesearch} as specified below. ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @item @qcode{'kdtree'} @tab Creates and uses a Kd-tree to find nearest ## neighbors. @qcode{'kdtree'} is the default value when the number of columns ## in @var{X} is less than or equal to 10, @var{X} is not sparse, and the ## distance metric is @qcode{'euclidean'}, @qcode{'cityblock'}, ## @qcode{'manhattan'}, @qcode{'chebychev'}, or @qcode{'minkowski'}. Otherwise, ## the default value is @qcode{'exhaustive'}. This argument is only valid when ## the distance metric is one of the four aforementioned metrics. ## @item @qcode{'exhaustive'} @tab Uses the exhaustive search algorithm by ## computing the distance values from all the points in @var{X} to each point in ## @var{Y}. ## @end multitable ## ## @seealso{knnsearch, pdist2} ## @end deftypefn function [idx, dist] = rangesearch (X, Y, r, varargin) ## Check input data if (nargin < 3) error ("rangesearch: too few input arguments."); endif if (size (X, 2) != size (Y, 2)) error ("rangesearch: number of columns in X and Y must match."); endif if (! isscalar (r) || ! isnumeric (r) || r < 0) error ("rangesearch: radius r must be a nonnegative scalar."); endif ## Add default values P = 2; # Exponent for Minkowski distance S = []; # Scale for the standardized Euclidean distance C = []; # Covariance matrix for Mahalanobis distance BS = 50; # Maximum number of points per leaf node for Kd-tree SI = true; # Sort returned indices according to distance Distance = 'euclidean'; # Distance metric to be used NSMethod = []; # Nearest neighbor search method DistParameter = []; # Distance parameter for pdist2 ## Parse additional parameters in Name/Value pairs PSC = 0; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'p' P = varargin{2}; PSC += 1; case 'scale' S = varargin{2}; PSC += 1; case 'cov' C = varargin{2}; PSC += 1; case 'bucketsize' BS = varargin{2}; case 'sortindices' SI = varargin{2}; case 'distance' Distance = varargin{2}; ## 'manhattan' is this package's documented alias of 'cityblock' and ## is resolved here, so every search path takes it. MATLAB has no ## such name and refuses it. if (ischar (Distance) && strcmpi (Distance, 'manhattan')) Distance = 'cityblock'; endif case 'nsmethod' NSMethod = varargin{2}; otherwise error ("rangesearch: invalid NAME in optional pairs of arguments."); endswitch varargin(1:2) = []; endwhile ## Check input parameters if (PSC > 1) error ("rangesearch: only a single distance parameter can be defined."); endif if (! isscalar (P) || ! isnumeric (P) || P <= 0) error ("rangesearch: invalid value of Minkowski Exponent."); endif if (! isempty (S)) if (any (S) < 0 || numel (S) != columns (X) || ! strcmpi (Distance, 'seuclidean')) error ("rangesearch: invalid value in Scale or the size of Scale."); endif endif if (! isempty (C)) if (! strcmp (Distance, 'mahalanobis') || ! ismatrix (C) || ! isnumeric (C)) error (strcat ("rangesearch: invalid value in Cov, Cov can only", ... " be given for mahalanobis distance.")); endif endif if (! isscalar (BS) || ! isnumeric (BS) || BS <= 0 || fix (BS) != BS) error ("rangesearch: invalid value of bucketsize."); endif ## Select the appropriate distance parameter if (strcmpi (Distance, 'minkowski')) DistParameter = P; elseif (strcmpi (Distance, 'seuclidean')) DistParameter = S; elseif (strcmpi (Distance, 'mahalanobis')) DistParameter = C; endif ## Check NSMethod and set kdtree as default if the conditions match if (isempty (NSMethod)) ## Set default method 'kdtree' if conditions are satisfied; if (! issparse (X) && (columns (X) <= 10) && (strcmpi (Distance, 'euclidean') || strcmpi (Distance, 'cityblock') || strcmpi (Distance, 'minkowski') || strcmpi (Distance, 'chebychev'))) NSMethod = 'kdtree'; else NSMethod = 'exhaustive'; endif else ## Disallow kdtree with custom distance functions if (strcmpi (NSMethod, 'kdtree') && isa (Distance, 'function_handle')) error (strcat ("rangesearch: 'kdtree' cannot be used", ... " with custom distance functions.")); endif ## Check if kdtree can be used if (strcmpi (NSMethod, 'kdtree') && ! (strcmpi (Distance, 'euclidean') || strcmpi (Distance, 'cityblock') || strcmpi (Distance, 'minkowski') || strcmpi (Distance, 'chebychev'))) error (strcat ("rangesearch: 'kdtree' cannot be used", ... " with the given distance metric.")); endif endif ## Check for NSMethod if (strcmpi (NSMethod, 'kdtree')) ## Build kdtree and search the query point kdtree = __build_kdtree__ (1:size (X,1), 0, X, BS); ## Return all neighbors as cell dist = cell (rows (Y), 1); idx = cell (rows (Y), 1); for i = 1:rows (Y) [temp_idx, temp_D] = __search_kdtree__ (kdtree, Y(i,:), Inf, X, ... Distance, DistParameter, true, r); if (SI) [sorted_D, sort_idx] = sort (temp_D); idx{i} = temp_idx(sort_idx)(:).'; dist{i} = sorted_D(:).'; else idx{i} = temp_idx(:).'; dist{i} = temp_D(:).'; endif endfor else ## Calculate all distances dist = cell (rows (Y), 1); idx = cell (rows (Y), 1); for i = 1:rows (Y) D = pdist2 (X, Y(i,:), Distance, DistParameter); Didx_row = find (D <= r)'; Dist_row = D(Didx_row)'; if (SI) [S, I] = sort (Dist_row); Dist_row = Dist_row(I); Didx_row = Didx_row(I); endif dist{i} = Dist_row; idx{i} = Didx_row; endfor endif endfunction %!demo %! ## Generate 100 random 2D points from each of five distinct multivariate %! ## normal distributions that form five separate classes %! rng (42); %! N = 100; %! d = 10; %! X1 = mvnrnd (d * [0, 0], eye (2), N); %! X2 = mvnrnd (d * [1, 1], eye (2), N); %! X3 = mvnrnd (d * [-1, -1], eye (2), N); %! X4 = mvnrnd (d * [1, -1], eye (2), N); %! X5 = mvnrnd (d * [-1, 1], eye (2), N); %! X = [X1; X2; X3; X4; X5]; %! %! ## For each point in X, find the points in X that are within a radius d %! ## away from the points in X. %! Idx = rangesearch (X, X, d, 'NSMethod', 'exhaustive'); %! %! ## Select the first point in X (corresponding to the first class) and find %! ## its nearest neighbors within the radius d. Display these points in %! ## one color and the remaining points in a different color. %! x = X(1,:); %! nearestPoints = X(Idx{1},:); %! nonNearestIdx = true (size (X, 1), 1); %! nonNearestIdx(Idx{1}) = false; %! %! scatter (X(nonNearestIdx,1), X(nonNearestIdx,2)) %! hold on %! scatter (nearestPoints(:,1),nearestPoints(:,2)) %! scatter (x(1), x(2), 'black', 'filled') %! hold off %! %! ## Select the last point in X (corresponding to the fifth class) and find %! ## its nearest neighbors within the radius d. Display these points in %! ## one color and the remaining points in a different color. %! x = X(end,:); %! nearestPoints = X(Idx{end},:); %! nonNearestIdx = true (size (X, 1), 1); %! nonNearestIdx(Idx{end}) = false; %! %! figure %! scatter (X(nonNearestIdx,1), X(nonNearestIdx,2)) %! hold on %! scatter (nearestPoints(:,1),nearestPoints(:,2)) %! scatter (x(1), x(2), 'black', 'filled') %! hold off ## Test output %!shared x, y, X, Y %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = [2, 3, 4; 1, 4, 3]; %! X = [1, 2, 3, 4; 2, 3, 4, 5; 3, 4, 5, 6]; %! Y = [1, 2, 2, 3; 2, 3, 3, 4]; %!test %! [idx, D] = rangesearch (x, y, 4); %! assert_equal (idx, {[1, 4, 2]; [1, 4]}); %! assert_equal (D, {[1.7321, 3.3166, 3.4641]; [2, 3.4641]}, 1e-4); %!test %! [idx, D] = rangesearch (x, y, 4, 'NSMethod', 'exhaustive'); %! assert_equal (idx, {[1, 4, 2]; [1, 4]}); %! assert_equal (D, {[1.7321, 3.3166, 3.4641]; [2, 3.4641]}, 1e-4); %!test %! [idx, D] = rangesearch (x, y, 4, 'NSMethod', 'kdtree'); %! assert_equal (idx, {[1, 4, 2]; [1, 4]}); %! assert_equal (D, {[1.7321, 3.3166, 3.4641]; [2, 3.4641]}, 1e-4); %!test %! [idx, D] = rangesearch (x, y, 4, 'SortIndices', true); %! assert_equal (idx, {[1, 4, 2]; [1, 4]}); %! assert_equal (D, {[1.7321, 3.3166, 3.4641]; [2, 3.4641]}, 1e-4); %!test %! [idx, D] = rangesearch (x, y, 4, 'SortIndices', false); %! assert_equal (idx, {[1, 2, 4]; [1, 4]}); %! assert_equal (D, {[1.7321, 3.4641, 3.3166]; [2, 3.4641]}, 1e-4); %!test %! [idx, D] = rangesearch (x, y, 4, 'NSMethod', 'exhaustive', ... %! 'SortIndices', false); %! assert_equal (idx, {[1, 2, 4]; [1, 4]}); %! assert_equal (D, {[1.7321, 3.4641, 3.3166]; [2, 3.4641]}, 1e-4); %!test %! eucldist = @(v,m) sqrt (sumsq (repmat (v,rows (m),1)-m,2)); %! [idx, D] = rangesearch (x, y, 4, 'Distance', eucldist); %! assert_equal (idx, {[1, 4, 2]; [1, 4]}); %! assert_equal (D, {[1.7321, 3.3166, 3.4641]; [2, 3.4641]}, 1e-4); %!test %! eucldist = @(v,m) sqrt (sumsq (repmat (v,rows (m),1)-m,2)); %! [idx, D] = rangesearch (x, y, 4, 'Distance', eucldist, ... %! 'NSMethod', 'exhaustive'); %! assert_equal (idx, {[1, 4, 2]; [1, 4]}); %! assert_equal (D, {[1.7321, 3.3166, 3.4641]; [2, 3.4641]}, 1e-4); %!test %! [idx, D] = rangesearch (x, y, 1.5, 'Distance', 'seuclidean', ... %! 'NSMethod', 'exhaustive'); %! assert_equal (idx, {[1, 4, 2]; [1, 4]}); %! assert_equal (D, {[0.6024, 1.0079, 1.2047]; [0.6963, 1.2047]}, 1e-4); %!test %! [idx, D] = rangesearch (x, y, 1.5, 'Distance', 'seuclidean', ... %! 'NSMethod', 'exhaustive', 'SortIndices', false); %! assert_equal (idx, {[1, 2, 4]; [1, 4]}); %! assert_equal (D, {[0.6024, 1.2047, 1.0079]; [0.6963, 1.2047]}, 1e-4); %!test %! [idx, D] = rangesearch (X, Y, 4); %! assert_equal (idx, {[1, 2]; [1, 2, 3]}); %! assert_equal (D, {[1.4142, 3.1623]; [1.4142, 1.4142, 3.1623]}, 1e-4); %!test %! [idx, D] = rangesearch (X, Y, 2); %! assert_equal (idx, {[1]; [1, 2]}); %! assert_equal (D, {[1.4142]; [1.4142, 1.4142]}, 1e-4); %!test %! eucldist = @(v,m) sqrt (sumsq (repmat (v,rows (m),1)-m,2)); %! [idx, D] = rangesearch (X, Y, 4, 'Distance', eucldist); %! assert_equal (idx, {[1, 2]; [1, 2, 3]}); %! assert_equal (D, {[1.4142, 3.1623]; [1.4142, 1.4142, 3.1623]}, 1e-4); %!test %! [idx, D] = rangesearch (X, Y, 4, 'SortIndices', false); %! assert_equal (idx, {[1, 2]; [1, 2, 3]}); %! assert_equal (D, {[1.4142, 3.1623]; [1.4142, 1.4142, 3.1623]}, 1e-4); %!test %! [idx, D] = rangesearch (X, Y, 4, 'Distance', 'seuclidean', ... %! 'NSMethod', 'exhaustive'); %! assert_equal (idx, {[1, 2]; [1, 2, 3]}); %! assert_equal (D, {[1.4142, 3.1623]; [1.4142, 1.4142, 3.1623]}, 1e-4); %!test %! X = ones (10, 2); %! [idx, D] = rangesearch (X, X, 0.1, 'NSMethod', 'kdtree'); %! assert_equal (numel (idx), 10); %!test %! X = ones (3, 2); %! [idx, D] = rangesearch (X, X, 0.1, 'NSMethod', 'kdtree', 'BucketSize', 1); %! assert_equal (numel (idx), 3); %! assert_equal (cellfun (@numel, idx) == 3, [true; true; true]); %! assert_equal (idx{1}, [1, 2, 3]); %! assert_equal (idx{2}, [1, 2, 3]); %! assert_equal (idx{3}, [1, 2, 3]); %! assert_equal (D{1}, [0, 0, 0]); %! assert_equal (D{2}, [0, 0, 0]); %! assert_equal (D{3}, [0, 0, 0]); %!test %! [idx, D] = rangesearch (x, y, 4, 'NSMethod', 'kdtree', 'SortIndices', true); %! assert_equal (idx, {[1, 4, 2]; [1, 4]}); %! assert_equal (D, {[1.7321, 3.3166, 3.4641]; [2, 3.4641]}, 1e-4); %!test %! [idx, D] = rangesearch (x, y, 4, 'NSMethod', 'kdtree', 'SortIndices', false); %! assert_equal (idx, {[1, 2, 4]; [1, 4]}); %! assert_equal (D, {[1.7321, 3.4641, 3.3166]; [2, 3.4641]}, 1e-4); ## Test input validation %!error rangesearch (1) %!error rangesearch (ones (4, 5)) %!error ... %! rangesearch (ones (4, 5), ones (4)) %!error ... %! rangesearch (ones (4, 5), ones (4), 1) %!error ... %! rangesearch (ones (4, 2), ones (3, 2), 1, 'Distance', 'euclidean', 'some', 'some') %!error ... %! rangesearch (ones (4, 5), ones (1, 5), 1, 'scale', ones (1, 5), 'P', 3) %!error ... %! rangesearch (ones (4, 5), ones (1, 5), 1, 'P', -2) %!error ... %! rangesearch (ones (4, 5), ones (1, 5), 1, 'scale', ones (4,5), 'distance', 'euclidean') %!error ... %! rangesearch (ones (4, 5), ones (1, 5), 1, 'cov', ['some' 'some']) %!error ... %! rangesearch (ones (4, 5), ones (1, 5), 1, 'cov', ones (4,5), 'distance', 'euclidean') %!error ... %! rangesearch (ones (4, 5), ones (1, 5), 1, 'bucketsize', -1) %!error ... %! rangesearch (ones (4,2), ones (1,2), 1, 'BucketSize', 2.5) %!error ... %! rangesearch (ones (4, 5), ones (1, 5), 1, 'NSmethod', 'kdtree', 'distance', 'cosine') %!error ... %! rangesearch (ones (4, 5), ones (1, 5), 1, 'NSmethod', 'kdtree', 'distance', 'mahalanobis') %!error ... %! rangesearch (ones (4, 5), ones (1, 5), 1, 'NSmethod', 'kdtree', 'distance', 'correlation') %!error ... %! rangesearch (ones (4, 5), ones (1, 5), 1, 'NSmethod', 'kdtree', 'distance', 'seuclidean') %!error ... %! rangesearch (ones (4, 5), ones (1, 5), 1, 'NSmethod', 'kdtree', 'distance', 'spearman') %!error ... %! rangesearch (ones (4, 5), ones (1, 5), 1, 'NSmethod', 'kdtree', 'distance', 'hamming') %!error ... %! rangesearch (ones (4, 5), ones (1, 5), 1, 'NSmethod', 'kdtree', 'distance', 'jaccard') %!error ... %! rangesearch (ones (4,2), ones (1,2), 1, 'Distance', @(x,y) sqrt (sum ((x-y).^2)), 'NSMethod', 'kdtree') statistics-release-1.9.2/inst/Nearest_Neighbors/squareform.m000066400000000000000000000147361524624707500243020ustar00rootroot00000000000000## Copyright (C) 2015 Carnë Draug ## Copyright (C) 2026 Andreas Bertsatos ## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{zOut} =} squareform (@var{yIn}) ## @deftypefnx {statistics} {@var{yOut} =} squareform (@var{zIn}) ## @deftypefnx {statistics} {@var{zOut} =} squareform (@var{yIn}, @qcode{'tovector'}) ## @deftypefnx {statistics} {@var{yOut} =} squareform (@var{zIn}, @qcode{'tomatrix'}) ## ## Interchange between distance matrix and distance vector formats. ## ## Converts between a hollow (diagonal filled with zeros), square, and ## symmetric matrix and a vector of the lower triangular part. ## ## Its target application is the conversion of the vector returned by ## @code{pdist} into a distance matrix. It performs the opposite operation ## if input is a matrix. ## ## If @var{x} is a numeric or logical vector, its number of elements must fit ## into the triangular part of a matrix (main diagonal excluded). In other ## words, @code{numel (@var{x}) = @var{n} * (@var{n} - 1) / 2} for some integer ## @var{n}. The resulting matrix will be @var{n} by @var{n}. ## ## If @var{x} is a numeric or logical distance matrix, it must be square and the ## diagonal entries of @var{x} must all be zeros. If @var{x} is not symmetric, ## only the lower triangular part is used. ## ## The second argument is used to specify the output type in case the distance ## input is a scalar. Accepted values are @qcode{'tomatrix'} (or @qcode{'tom'}) ## and @qcode{'tovector'} (or @qcode{'tov'}). If not specified, it defaults to ## @qcode{'tomatrix'} otherwise. ## ## @seealso{pdist} ## @end deftypefn function y = squareform (x, method) if (nargin < 1 || nargin > 2) print_usage (); elseif (! isnumeric (x) && ! islogical (x)) error ("squareform: distance input must be either numeric or logical."); elseif (! ismatrix (x)) error ("squareform: distance input must be either a vector or a matrix."); endif if (nargin == 1) ## This is ambiguous when numel (x) == 1, but that's the whole reason ## why the "method" option exists. if (isvector (x)) method = 'tomatrix'; else method = 'tovector'; endif elseif (isstring (method)) if (! isscalar (method)) error (strcat ("squareform: second argument must be either", ... " a character vector or a string scalar.")); endif method = char (method); elseif (! ischar (method)) error (strcat ("squareform: second argument must be either", ... " a character vector or a string scalar.")); endif switch (tolower (method)) case {'tovector', 'tov'} if (! issquare (x)) error ("squareform: distance input is not a square matrix."); elseif (any (diag (x) != 0)) error ("squareform: distance input is not a hollow matrix."); endif y = vec (tril (x, -1, 'pack'), 2); case {'tomatrix', 'tom'} ## the dimensions of y are the solution to the quadratic formula for: ## length (x) = (sy - 1) * (sy / 2) sy = (1 + sqrt (1 + 8 * numel (x))) / 2; if (fix (sy) != sy) error ("squareform: distance input vector cannot form a square matrix."); endif y = zeros (sy, class (x)); y(tril (true (sy), -1)) = x; # fill lower triangular part y += y.'; # and then the upper triangular part otherwise error ("squareform: invalid METHOD '%s'.", method); endswitch ## Force to logical (if applicable) if (isa (x, 'logical')) y = logical (y); endif endfunction %!shared v, m %! v = 1:6; %! m = [0, 1, 2, 3; 1, 0, 4, 5; 2, 4, 0, 6; 3, 5, 6, 0]; ## make sure that it can go both directions automatically %!test %!assert_equal (squareform (v), m) %!assert_equal (squareform (squareform (v)), v) %!assert_equal (squareform (m), v) ## treat row and column vectors equally %!test %!assert_equal (squareform (v'), m) ## handle 1 element input properly %!test %!assert_equal (squareform (1), [0 1;1 0]) %!assert_equal (squareform (1, 'tomatrix'), [0 1; 1 0]) %!assert_equal (squareform (0, 'tovector'), zeros (1, 0)) ## confirm that it respects input class %!test %! for c = {@single, @double, @uint8, @uint16, @uint32, @uint64, @logical} %! f = c{1}; %! assert_equal (squareform (f(v)), f(m)) %! assert_equal (squareform (f(m)), f(v)) %! endfor ## test logical inputs. %!test %! v_log = [true, false, true]; %! m_log = [false, true, false; true, false, true; false, true, false]; %! assert_equal (squareform (v_log), m_log); %! assert_equal (squareform (m_log), v_log); ## test partial string matching and case insensitivity %!assert_equal (squareform (v, 'tom'), m); %!assert_equal (squareform (m, 'tov'), v); %!assert_equal (squareform (v, 'TOMATRIX'), m); %!assert_equal (squareform (v, string ('tomatrix')), m); %!assert_equal (squareform (m, string ('tovector')), v); ## input validations %!error ... %! squareform ('string') %!error ... %! squareform ({1, 2, 3}) %!error ... %! squareform ([1, 2, 3; 4, 5, 6], 'tovector') %!error ... %! squareform (eye (3), 'tovector') %!error ... %! squareform ([1, 2, 3; 4, 5, 6], string ({'tomatrix', 'tomatrix'})) %!error ... %! squareform ([1, 2, 3; 4, 5, 6], true) %!error ... %! squareform ([1, 2, 3, 4], 'tomatrix') %!error squareform ([1, 2, 3], 'invalid') statistics-release-1.9.2/inst/PKG_ADD000066400000000000000000000017711524624707500173460ustar00rootroot00000000000000a1_e324kporit985_itogj3_dirlist = ... {'Anomaly_Detection', 'Clustering', 'Data_Manipulation', 'datasets', ... 'demos', 'Descriptive_Statistics', 'Dimensionality_Reduction', ... 'Distribution_Classes', 'Distribution_Fitting', ... 'Distribution_Functions', 'Distribution_Statistics', ... 'Distribution_Wrappers', 'Experimental_Design', ... 'Hypothesis_Testing', 'Markov_Models', 'Model_Evaluation', ... 'Nearest_Neighbors', 'Plotting', 'Random_Sampling', ... 'Regression', 'Supervised_Learning'}; d_2seRTE546_oyi_795jg09_dirname = fileparts (canonicalize_file_name ... (mfilename ("fullpath"))); for iiII123DRT_idx = 1:length (a1_e324kporit985_itogj3_dirlist) addpath (fullfile (d_2seRTE546_oyi_795jg09_dirname, ... a1_e324kporit985_itogj3_dirlist{iiII123DRT_idx})); endfor clear a1_e324kporit985_itogj3_dirlist clear d_2seRTE546_oyi_795jg09_dirname iiII123DRT_idx warning ("off", "Octave:data-file-in-path") statistics-release-1.9.2/inst/PKG_DEL000066400000000000000000000020141524624707500173510ustar00rootroot00000000000000clear -f editDistance libsvmread libsvmwrite svmpredict svmtrain a1_e324kporit985_itogj3_dirlist = ... {'Anomaly_Detection', 'Clustering', 'Data_Manipulation', 'datasets', ... 'demos', 'Descriptive_Statistics', 'Dimensionality_Reduction', ... 'Distribution_Classes', 'Distribution_Fitting', ... 'Distribution_Functions', 'Distribution_Statistics', ... 'Distribution_Wrappers', 'Experimental_Design', ... 'Hypothesis_Testing', 'Markov_Models', 'Model_Evaluation', ... 'Nearest_Neighbors', 'Plotting', 'Random_Sampling', ... 'Regression', 'Supervised_Learning'}; d_2seRTE546_oyi_795jg09_dirname = fileparts (canonicalize_file_name ... (mfilename ("fullpath"))); for iiII123DRT_idx = 1:length (a1_e324kporit985_itogj3_dirlist) rmpath (fullfile (d_2seRTE546_oyi_795jg09_dirname, ... a1_e324kporit985_itogj3_dirlist{iiII123DRT_idx})); endfor clear a1_e324kporit985_itogj3_dirlist clear d_2seRTE546_oyi_795jg09_dirname iiII123DRT_idx statistics-release-1.9.2/inst/Plotting/000077500000000000000000000000001524624707500201245ustar00rootroot00000000000000statistics-release-1.9.2/inst/Plotting/andrewsplot.m000066400000000000000000000221531524624707500226470ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} andrewsplot (@var{x}) ## @deftypefnx {statistics} {} andrewsplot (@var{x}, @var{name}, @var{value}, @dots{}) ## @deftypefnx {statistics} {} andrewsplot (@var{ax}, @dots{}) ## @deftypefnx {statistics} {@var{h} =} andrewsplot (@dots{}) ## ## Create an Andrews plot of the multivariate data in @var{x}. ## ## @code{andrewsplot (@var{x})} plots each observation (row) of the ## @code{n}-by-@code{p} matrix @var{x} as a smooth curve defined by the finite ## Fourier series ## ## @example ## f_i(t) = x_i1/sqrt(2) + x_i2 sin(2*pi*t) + x_i3 cos(2*pi*t) ## + x_i4 sin(4*pi*t) + x_i5 cos(4*pi*t) + @dots{} ## @end example ## ## @noindent ## evaluated over @math{t} in the interval @math{[0,1]}, where @var{x_ij} is the ## @math{j}-th variable of the @math{i}-th observation. ## ## The following name/value pairs are accepted: ## ## @table @asis ## @item @qcode{"Group"} ## A grouping variable (numeric, logical, character, string, or cell array of ## strings) with one entry per row of @var{x}. Curves are colored by group. ## ## @item @qcode{"Standardize"} ## Controls how the columns of @var{x} are transformed before the curves are ## computed: @qcode{"off"} (default) uses the raw data, @qcode{"on"} centers and ## scales each column to zero mean and unit standard deviation, @qcode{"PCA"} ## uses the principal component scores, and @qcode{"PCAStd"} uses the principal ## component scores of the standardized data. ## ## @item @qcode{"Quantile"} ## A scalar @var{alpha} in the interval @math{(0,1)}. Instead of one curve per ## observation, only three curves per group are drawn: the pointwise median and ## the @var{alpha} and @math{1-}@var{alpha} quantiles of the group's curves. ## @end table ## ## @code{andrewsplot (@var{ax}, @dots{})} plots into the axes @var{ax}. ## ## The optional output @var{h} is a vector of handles to the plotted lines: one ## per observation, or three per group when @qcode{"Quantile"} is used. ## ## @seealso{parallelcoords, glyphplot, pca} ## @end deftypefn function h = andrewsplot (varargin) ## Optional leading axes handle hax = []; if (numel (varargin) > 0 && isaxes (varargin{1})) hax = varargin{1}; varargin(1) = []; endif if (numel (varargin) < 1) print_usage (); endif X = varargin{1}; varargin(1) = []; if (! isnumeric (X) || ! isreal (X) || ndims (X) > 2) error ("andrewsplot: X must be a real numeric matrix."); endif n = rows (X); ## Parse name/value options group = []; standardize = "off"; alpha = []; if (mod (numel (varargin), 2) != 0) error ("andrewsplot: name/value arguments must come in pairs."); endif for i = 1:2:numel (varargin) name = varargin{i}; value = varargin{i+1}; if (! ischar (name)) error ("andrewsplot: property names must be strings."); endif switch (lower (name)) case "group" group = value; case "standardize" standardize = value; case "quantile" alpha = value; otherwise error ("andrewsplot: unknown property '%s'.", name); endswitch endfor ## Validate before plotting, so a bad option never leaves a stray figure if (! isempty (alpha)) if (! (isscalar (alpha) && isreal (alpha) && alpha > 0 && alpha < 1)) error ("andrewsplot: Quantile ALPHA must be a scalar in (0,1)."); endif endif ## Standardize the data switch (lower (standardize)) case "off" Z = X; case "on" Z = zscore (X); case "pca" [~, Z] = pca (X); case "pcastd" [~, Z] = pca (zscore (X)); otherwise error ("andrewsplot: invalid Standardize option '%s'.", standardize); endswitch ## Grouping if (isempty (group)) gidx = ones (n, 1); gnames = {"1"}; else [gidx, gnames] = grp2idx (group); if (numel (gidx) != n) error ("andrewsplot: GROUP must have one entry per row of X."); endif endif k = numel (gnames); gcol = lines (k); ## Evaluate the Andrews curves f_i(t) over t in [0,1] t = linspace (0, 1, 1001); p = columns (Z); F = (Z(:,1) / sqrt (2)) * ones (1, numel (t)); for j = 2:p kf = floor (j / 2); if (mod (j, 2) == 0) F += Z(:,j) * sin (2 * pi * kf * t); else F += Z(:,j) * cos (2 * pi * kf * t); endif endfor if (isempty (hax)) hax = newplot (); else newplot (hax); endif old_hold = ishold (hax); hold (hax, "on"); h = []; if (isempty (alpha)) ## One curve per observation, colored by group for i = 1:n h(end+1) = line (hax, t, F(i,:), "color", gcol(gidx(i),:)); endfor else ## Median and alpha / 1-alpha quantile curves per group for g = 1:k Fg = F(gidx == g, :); med = median (Fg, 1); lo = quantile (Fg, alpha, 1); hi = quantile (Fg, 1 - alpha, 1); h(end+1) = line (hax, t, med, "color", gcol(g,:), "linewidth", 2); h(end+1) = line (hax, t, lo, "color", gcol(g,:), "linestyle", "--"); h(end+1) = line (hax, t, hi, "color", gcol(g,:), "linestyle", "--"); endfor endif xlabel (hax, "t"); ylabel (hax, "f(t)"); if (k > 1 && ! isempty (group)) warning ("off", "Octave:legend:unimplemented-location", "local"); ## One representative line handle per group for the legend if (isempty (alpha)) [~, first] = unique (gidx, "first"); legend (hax, h(sort (first)), gnames, "location", "best"); else legend (hax, h(1:3:end), gnames, "location", "best"); endif endif if (! old_hold) hold (hax, "off"); endif if (nargout == 0) clear h; endif endfunction %!demo %! ## Andrews plot of Fisher's iris data, grouped by species. %! %! load fisheriris; %! andrewsplot (meas, "Group", species); %!demo %! ## The same data with median and quartile curves per species. %! %! load fisheriris; %! andrewsplot (meas, "Group", species, "Quantile", 0.25); ## Test output %!test %! hf = figure ("visible", "off"); %! unwind_protect %! X = [1 2 3 4; 5 6 7 8; 2 3 1 4]; %! h = andrewsplot (X); %! assert_equal (numel (h), 3); %! t = get (h(1), "xdata"); %! assert_equal (numel (t), 1001); %! assert_equal (t([1, 501, 1001]), [0, 0.5, 1], 1e-12); %! y1 = get (h(1), "ydata"); %! assert_equal (y1([1, 501, 1001]), [3.70711, -2.29289, 3.70711], 1e-4); %! y3 = get (h(3), "ydata"); %! assert_equal (y3([1, 501, 1001]), [2.41421, 0.41421, 2.41421], 1e-4); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # Standardize "on" (z-score each column) %! hf = figure ("visible", "off"); %! unwind_protect %! X = [1 2 3 4; 5 6 7 8; 2 3 1 4]; %! h = andrewsplot (X, "Standardize", "on"); %! y1 = get (h(1), "ydata"); %! assert_equal (y1([1, 501, 1001]), [-0.78436, -0.34792, -0.78436], 1e-4); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # Standardize "pca" %! hf = figure ("visible", "off"); %! unwind_protect %! X = [1 2 3 4; 5 6 7 8; 2 3 1 4]; %! h = andrewsplot (X, "Standardize", "pca"); %! y1 = get (h(1), "ydata"); %! assert_equal (y1([1, 501, 1001]), [-1.76701, -1.76701, -1.76701], 1e-4); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # grouping and quantile mode give three curves per group %! hf = figure ("visible", "off"); %! unwind_protect %! X = [1 2 3 4; 5 6 7 8; 2 3 1 4; 3 1 4 1; 5 9 2 6; 4 2 1 3]; %! g = [1 1 1 2 2 2]'; %! h = andrewsplot (X, "Group", g, "Quantile", 0.25); %! assert_equal (numel (h), 6); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error andrewsplot () %!error andrewsplot ({1}) %!error ... %! andrewsplot (ones (3, 2), "Group") %!error ... %! andrewsplot (ones (3, 2), "bogus", 1) %!error ... %! andrewsplot (ones (3, 2), "Standardize", "xxx") %!error ... %! andrewsplot (ones (3, 2), "Group", [1 2]) %!error ... %! andrewsplot (ones (3, 2), "Quantile", 1.5) ## A bad Quantile ALPHA must error before any figure is created (no stray figure) %!test %! nfig = numel (get (0, "children")); %! fail ('andrewsplot (ones (3, 2), "Quantile", 1.5)', ... %! 'andrewsplot: Quantile ALPHA must be a scalar in .0,1..'); %! assert_equal (numel (get (0, "children")), nfig); statistics-release-1.9.2/inst/Plotting/bar3.m000066400000000000000000000432421524624707500211360ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} bar3 (@var{z}) ## @deftypefnx {statistics} {} bar3 (@var{y}, @var{z}) ## @deftypefnx {statistics} {} bar3 (@dots{}, @var{width}) ## @deftypefnx {statistics} {} bar3 (@dots{}, @var{style}) ## @deftypefnx {statistics} {} bar3 (@dots{}, @var{color}) ## @deftypefnx {statistics} {} bar3 (@dots{}, @var{name}, @var{value}) ## @deftypefnx {statistics} {} bar3 (@var{ax}, @dots{}) ## @deftypefnx {statistics} {@var{p} =} bar3 (@dots{}) ## ## Plot a 3D bar graph. ## ## @code{bar3 (@var{z})} plots 3D bar graph for the elements of @var{z}. Each ## bar corresponds to an element in @var{z}, which can be a scalar, vector, or ## 2D matrix. By default, each column in @var{z} is considered as a series and ## it is handled as a distinct series of bars. When @var{z} is a vector, unlike ## MATLAB, which plots it as a single series of bars, Octave discriminates ## between a row and column vector of @var{z}. Hence, when @var{z} is column ## vector, it is plotted as a single series of bars (same color), whereas when ## @var{z} is row vector, each bar is plotted as a different group (different ## colors). For an @math{M*N} matrix, the function plots the bars corresponding ## to each row on the @qcode{y-axis} ranging from @math{1} to @math{M} and each ## column on the @qcode{x-axis} ranging from @math{1} to @math{N}. ## ## @code{bar3 (@var{y}, @var{z})} plots a 3D bar graph of the elements in ## @var{z} at the @qcode{y-values} specified in @var{y}. It should be noted ## that @var{y} only affects the tick names along the @qcode{y-axis} rather the ## actual values. If you want to specify non-numerical values for @var{y}, you ## can specify it with the paired @var{name}/@var{value} syntax shown below. ## ## @code{bar3 (@dots{}, @var{width})} sets the width of the bars along the ## @qcode{x-} and @qcode{y-axes} and controls the separation of bars among each ## other. @var{width} can take any value in the range @math{(0,1]}. By default, ## @var{width} is 0.8 and the bars have a small separation. If width is 1, the ## bars touch one another. Alternatively, you can define @var{width} as a two- ## element vector using the paired @var{name}/@var{value} syntax shown below, in ## which case you can control the bar separation along each axis independently. ## ## @code{bar3 (@dots{}, @var{style})} specifies the style of the bars, where ## @var{style} can be @qcode{'detached'}, @qcode{'grouped'}, or ## @qcode{'stacked'}. The default style is @qcode{'detached'}. ## ## @code{bar3 (@dots{}, @var{color})} displays all bars using the color ## specified ## by color. For example, use @qcode{'red'} or @qcode{'r'} to specify all red ## bars. When you want to specify colors for several groups, @var{color} can be ## a cellstr vector with each element specifying the color of each group. ## @var{color} can also be specified as a numerical @math{M*3} matrix, where ## each row corresponds to a RGB value with its elements in the range ## @math{[0,1]}. If only one color is specified, then it applies to all bars. ## If the number of colors equals the number of groups, then each color is ## applied to each group. If the number of colors equals the number of elements ## in @var{z}, then each individual bar is assigned the particular color. You ## can also define @var{color} using the paired @var{name}/@var{value} syntax ## shown below. ## ## @code{bar3 (@dots{}, @var{name}, @var{value})} specifies one or more of the ## following name/value pairs: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'width'} @tab A two-element vector specifying the width ## of the bars along the @qcode{x-} and @qcode{y-axes}, respectively. Each ## element must be in the range @math{(0,1]}. ## ## @item @qcode{'color'} @tab A character or a cellstr vector, or a ## numerical @math{M*3} matrix following the same conventions as the @var{color} ## input argument. ## ## @item @qcode{'xlabel'} @tab A cellstr vector specifying the group names ## along the @qcode{x-axis}. ## ## @item @qcode{'ylabel'} @tab A cellstr vector specifying the names of the ## bars in the same series along the @qcode{y-axis}. ## @end multitable ## ## @code{bar3 (@var{ax}, @dots{})} can also take an axes handle @var{ax} as a ## first argument in which case it plots into the axes specified by @var{ax} ## instead of into the current axes specified by @code{gca ()}. The optional ## argument @var{ax} can precede any of the input argument combinations in the ## previous syntaxes. ## ## @code{@var{p} = bar3 (@dots{})} returns a patch handle @var{p}, which can be ## used to set properties of the bars after displaying the 3D bar graph. ## ## @seealso{boxplot, hist3} ## @end deftypefn function [varargout] = bar3 (varargin) if (nargin < 1) print_usage (); endif ## Check if first input is an axes handle if (isaxes (varargin{1})) ax = varargin{1}; varargin(1) = []; new_axes = false; else new_axes = true; endif ## Parse input argument Z if (numel (varargin) < 1) print_usage (); endif z = varargin{1}; varargin(1) = []; if (! isnumeric (z)) error ("bar3: Z must be numeric."); endif ## Add defaults y = []; width = 0.8; depth = 0.8; style = 'detached'; color = []; xlabel = []; ylabel = []; ## Valid Colors for input validation vc = {'red', 'r', 'green', 'g', 'blue', 'b', 'cyan', 'c', ... 'magenta', 'm', 'yellow', 'y', 'black', 'k', 'white', 'w'}; ## Parse extra input arguments while (numel (varargin) > 0) tmp = varargin{1}; if (isnumeric (tmp) && isempty (y)) if (isvector (tmp) && isvector (z) && numel (tmp) == numel (z)) y = z; z = tmp; elseif (all (size (tmp) > 1) && size (tmp, 1) == numel (z) && isvector (z)) y = z; z = tmp; elseif (isscalar (tmp)) y = NaN; if (tmp > 0 && tmp <= 1) width = tmp; depth = tmp; else error ("bar3: WIDTH must be a scalar in the range (0,1]."); endif elseif (size (tmp, 2) == 3 && all (tmp(:) >= 0) && all (tmp(:) <= 1)) y = NaN; color = tmp; else error ("bar3: inconsistent size in Y and Z input arguments."); endif varargin(1) = []; elseif (isnumeric (tmp) && isscalar (tmp)) if (tmp > 0 && tmp <= 1) width = tmp; depth = tmp; else error ("bar3: WIDTH must be a scalar in the range (0,1]."); endif varargin(1) = []; elseif (isnumeric (tmp)) if (size (tmp, 2) == 3 && all (tmp(:) >= 0) && all (tmp(:) <= 1)) color = tmp; else error (strcat ("bar3: numeric COLOR must be a 1x3 vector of an Nx3 matrix", " where each value is between 0 and 1 inclusive.")); endif color = tmp; varargin(1) = []; elseif (ischar (tmp)) if (any (strcmpi (tmp, {'detached', 'grouped', 'stacked'}))) style = tmp; varargin(1) = []; elseif (any (strcmpi (tmp, vc))) color = tmp; varargin(1) = []; elseif (strcmpi (tmp, 'width')) if (numel (varargin) < 2) error ("bar3: missing value for optional argument 'width'."); endif w = varargin{2}; if (isscalar (w) && isnumeric (w) && isfinite (w) && w > 0 && w <= 1) width = w; depth = w; elseif (numel (w) == 2 && isnumeric (w) && isfinite (w) && all (w > 0) && all (w <= 1)) width = w(1); depth = w(2); else error ("bar3: invalid value for optional argument 'width'."); endif varargin([1:2]) = []; elseif (strcmpi (tmp, 'color')) if (numel (varargin) < 2) error ("bar3: missing value for optional argument 'color'."); endif c = varargin{2}; if (iscellstr (c)) is_vc = all (cell2mat (cellfun (@(x) any (strcmpi (vc, x)), ... c, 'UniformOutput', false))); if (is_vc) color = c; else error ("bar3: invalid value for optional argument 'color'."); endif elseif (ischar (c) && isvector (c)) if (any (strcmpi (c, vc))) color = c; else error ("bar3: invalid value for optional argument 'color'."); endif elseif (isnumeric (c)) if (size (c, 2) == 3 && all (c(:) >= 0) && all (c(:) <= 1)) color = c; else error (strcat ("bar3: numeric COLOR must be a 1x3 vector of an Nx3", " matrix where each value is between 0 and 1 inclusive.")); endif else error ("bar3: invalid value for optional argument 'color'."); endif varargin([1:2]) = []; elseif (strcmpi (tmp, 'xlabel')) if (numel (varargin) < 2) error ("bar3: missing value for optional argument 'xlabel'."); endif xlabel = varargin{2}; if (! iscellstr (xlabel)) error ("bar3: invalid value for optional argument 'xlabel'."); endif varargin([1:2]) = []; elseif (strcmpi (tmp, 'ylabel')) if (numel (varargin) < 2) error ("bar3: missing value for optional argument 'ylabel'."); endif ylabel = varargin{2}; if (! iscellstr (ylabel)) error ("bar3: invalid value for optional argument 'ylabel'."); endif varargin([1:2]) = []; else error ("bar3: invalid optional argument."); endif elseif (iscellstr (tmp)) is_vc = all (cell2mat (cellfun (@(x) any (strcmpi (vc, x)), ... tmp, 'UniformOutput', false))); if (is_vc) color = tmp; else error ("bar3: invalid value for optional COLOR argument."); endif varargin(1) = []; else error ("bar3: invalid optional argument."); endif endwhile ## Get number of column bars from z input [ny, nx] = size (z); ## Check xlabel and ylabel if (! isempty (xlabel) && numel (xlabel) != nx) error ("bar3: the elements in 'xlabel' must equal the columns in Z."); endif if (! isempty (ylabel) && numel (ylabel) != ny) error ("bar3: the elements in 'ylabel' must equal the rows in Z."); endif ## Check COLOR for valid dimensions if (isempty (color)) if (nx == 1) cargs = {'FaceColor', 'b'}; elseif (nx == 0) defc = [0,0,1;1,0,1;0,1,0;1,1,0]; fvcd = kron (defc([1:nx],:), ones (6 * ny, 1)); cargs = {'FaceVertexCData', fvcd, 'FaceColor', 'flat'}; else fvcd = kron ((1:nx)', ones (6 * ny, 1)); cargs = {'FaceVertexCData', fvcd, 'FaceColor', 'flat', ... 'CDataMapping', 'scaled'}; endif elseif (isnumeric (color)) if (size (color, 1) == numel (z)) fvcd = kron (color, ones (6, 1)); cargs = {'FaceVertexCData', fvcd, 'FaceColor', 'flat'}; elseif (size (color, 1) == nx) fvcd = kron (color, ones (6 * ny, 1)); cargs = {'FaceVertexCData', fvcd, 'FaceColor', 'flat'}; elseif (size (color, 1) == ny) fvcd = repmat (kron (color, ones (6, 1)), nx, 1); cargs = {'FaceVertexCData', fvcd, 'FaceColor', 'flat'}; elseif (size (color, 1) == 1) cargs = {'FaceVertexCData', color, 'FaceColor', 'flat'}; endif elseif (ischar (color)) cargs = {'FaceColor', color}; elseif (iscellstr (color)) endif ## Construct a "template" column-bar (8 vertices and 6 faces) centered at ## origin, with height = 1, and width along x-axis and depth along y-axis hw = width / 2; hd = depth / 2; ## Scale the bar's base when grouping together if (strcmpi (style, 'grouped')) sc = nx + 1; hw = hw / sc; hd = hd / sc; endif [X, Y, Z] = ndgrid ([-hw, hw], [-hd, hd], [0, 1]); V = [X(:), Y(:), Z(:)]; F = [1, 2, 4, 3; 5, 6, 8, 7; 1, 2, 6, 5; 3, 4, 8, 7; 1, 5, 7, 3; 2, 6, 8, 4]; ## Replicate faces to the required number of bars increments = 0:8:8 * (nx * ny - 1); F = bsxfun (@plus, F, permute (increments, [1, 3, 2])); F = reshape (permute (F, [2, 1, 3]), 4, []).'; ## Replicate vertices to the required number of bars [offsetX, offsetY] = meshgrid (1:nx, 1:ny); offset = [offsetX(:), offsetY(:)]; offset(:,3) = 0; V = bsxfun (@plus, V, permute (offset, [3, 2, 1])); V = reshape (permute (V, [2, 1, 3]), 3, []).'; if (strcmpi (style, 'detached')) ## Adjust bar heights according to values in z input V(:,3) = V(:,3) .* kron (z(:), ones (8,1)); elseif (strcmpi (style, 'grouped')) ## Adjust bar heights according to values in z input V(:,3) = V(:,3) .* kron (z(:), ones (8,1)); ## Move groups along x axis V(:,1) = V(:,1) - kron (kron ([0:nx-1], ones (ny, 1))(:), ones (8,1)); ## Move groups along y axis offset = [-nx+1:2:nx-1] * (hd / width); V(:,2) = V(:,2) + kron (kron (ones (1, ny), offset)(:), ones (8,1)); nx = 1; elseif (strcmpi (style, 'stacked')) ## Move groups along x axis V(:,1) = V(:,1) - kron (kron ([0:nx-1], ones (ny, 1))(:), ones (8,1)); ## Adjust bar heights according to values in z input ZC = cumsum (z,2); Q1 = kron (ZC(:), ones (8,1)); ZC(:,end) = []; ZC = [zeros(ny,1), ZC]; Q2 = kron (ZC(:), ones (8,1)); V(:,3) = V(:,3) .* Q1; idx = V(:,3) == 0; V(idx,3) = Q2(idx); ## Collapse x axis nx = 1; endif ## Draw column bars as patches specified by faces/vertices if (new_axes) ax = gca (); endif p = patch ('Faces', F, 'Vertices', V, 'EdgeColor', 'k', 'Parent', ax, cargs{:}); ## Set view port and axes view (ax, 3); grid (ax, 'on'); axis tight; xlim ([0.5, nx+0.5]); ylim ([0.5, ny+0.5]); set (ax, 'XTick', 1:nx, 'YTick', 1:ny, 'Box', 'off', 'YDir', 'reverse'); ## Fix aspect ratio so that bars appear square when rotating the bar plot if (nx > ny) set (ax, 'PlotBoxAspectRatio', [1, ny/nx, (sqrt(5)-1)/2]); elseif (nx < ny) set (ax, 'PlotBoxAspectRatio', [nx/ny, 1, (sqrt(5)-1)/2]); else set (ax, 'PlotBoxAspectRatio', [1, 1, (sqrt(5)-1)/2]); endif ## Add tick labels in axes (if requested) if (! isempty (xlabel)) set (ax, 'XTickLabel', xlabel); endif if (! isempty (ylabel)) set (ax, 'YTickLabel', ylabel); elseif (! isempty (y) && ! isnan (y)) ylabel = arrayfun (@(x) sprintf ('%d', x), y, 'UniformOutput', false); set (ax, 'YTickLabel', ylabel); endif ## Return handle to patch object if requested if nargout > 0 varargout{1} = p; endif endfunction %!demo %! ## Plotting 5 bars in the same series. %! %! z = [50; 40; 30; 20; 10]; %! bar3 (z); %!demo %! ## Plotting 5 bars in different groups. %! %! z = [50, 40, 30, 20, 10]; %! bar3 (z); %!demo %! ## A 3D bar graph with each series corresponding to a column in z. %! %! z = [1, 4, 7; 2, 5, 8; 3, 6, 9; 4, 7, 10]; %! bar3 (z); %!demo %! ## Specify y-axis locations as tick names. y must be a column vector! %! %! y = [1950, 1960, 1970, 1980, 1990]'; %! z = [16, 8, 4, 2, 1]'; %! bar3 (y, z); %!demo %! ## Plot 3 series as a grouped plot without any space between the grouped bars %! %! z = [70 50 33 10; 75 55 35 15; 80 60 40 20]; %! bar3 (z, 1, 'grouped'); %!demo %! ## Plot a stacked style 3D bar graph %! %! z = [19, 30, 21, 30; 40, 16, 32, 12]; %! b = bar3 (z, 0.5, 'stacked'); ## Test input validation %!error bar3 ('A') %!error bar3 ({2,3,4,5}) %!error ... %! bar3 ([1,2,3]', ones (2)) %!error ... %! bar3 ([1:5], 1.2) %!error ... %! bar3 ([1:5]', ones (5), 1.2) %!error ... %! bar3 ([1:5]', ones (5), [0.8, 0.7]) %!error ... %! bar3 (ones (5), 'width') %!error ... %! bar3 (ones (5), 'width', 1.2) %!error ... %! bar3 (ones (5), 'width', [0.8, 0.8, 0.8]) %!error ... %! bar3 (ones (5), 'color') %!error ... %! bar3 (ones (5), 'color', [0.8, 0.8]) %!error ... %! bar3 (ones (5), 'color', 'brown') %!error ... %! bar3 (ones (5), 'color', {'r', 'k', 'c', 'm', 'brown'}) %!error ... %! bar3 (ones (5), 'xlabel') %!error ... %! bar3 (ones (5), 'xlabel', 4) %!error ... %! bar3 (ones (5), 'ylabel') %!error ... %! bar3 (ones (5), 'ylabel', 4) %!error bar3 (ones (5), 'this', 4) %!error ... %! bar3 (ones (5), 'xlabel', {'A', 'B', 'C'}) %!error ... %! bar3 (ones (5), 'ylabel', {'A', 'B', 'C'}) statistics-release-1.9.2/inst/Plotting/bar3h.m000066400000000000000000000434421524624707500213100ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} bar3h (@var{y}) ## @deftypefnx {statistics} {} bar3h (@var{z}, @var{y}) ## @deftypefnx {statistics} {} bar3h (@dots{}, @var{width}) ## @deftypefnx {statistics} {} bar3h (@dots{}, @var{style}) ## @deftypefnx {statistics} {} bar3h (@dots{}, @var{color}) ## @deftypefnx {statistics} {} bar3h (@dots{}, @var{name}, @var{value}) ## @deftypefnx {statistics} {} bar3h (@var{ax}, @dots{}) ## @deftypefnx {statistics} {@var{p} =} bar3h (@dots{}) ## ## Plot a horizontal 3D bar graph. ## ## @code{bar3h (@var{y})} plots 3D bar graph for the elements of @var{y}. Each ## bar corresponds to an element in @var{y}, which can be a scalar, vector, or ## 2D matrix. By default, each column in @var{y} is considered as a series and ## it is handled as a distinct series of bars. When @var{y} is a vector, unlike ## MATLAB, which plots it as a single series of bars, Octave distinguishes ## between a row and column vector of @var{y}. Hence, when @var{y} is column ## vector, it is plotted as a single series of bars (same color), whereas when ## @var{y} is row vector, each bar is plotted as a different group (different ## colors). For an @math{M*N} matrix, the function plots the bars corresponding ## to each row on the @qcode{z-axis} ranging from @math{1} to @math{M} and each ## column on the @qcode{x-axis} ranging from @math{1} to @math{N}. ## ## @code{bar3h (@var{z}, @var{y})} plots a 3D bar graph of the elements in ## @var{y} at the @qcode{z-values} specified in @var{z}. It should be noted ## that @var{z} only affects the tick names along the @qcode{z-axis} rather the ## actual values. If you want to specify non-numerical values for @var{z}, you ## can specify it with the paired @var{name}/@var{value} syntax shown below. ## ## @code{bar3h (@dots{}, @var{width})} sets the width of the bars along the ## @qcode{x-} and @qcode{z-axes} and controls the separation of bars among each ## other. @var{width} can take any value in the range @math{(0,1]}. By default, ## @var{width} is 0.8 and the bars have a small separation. If width is 1, the ## bars touch one another. Alternatively, you can define @var{width} as a two- ## element vector using the paired @var{name}/@var{value} syntax shown below, in ## which case you can control the bar separation along each axis independently. ## ## @code{bar3h (@dots{}, @var{style})} specifies the style of the bars, where ## @var{style} can be @qcode{'detached'}, @qcode{'grouped'}, or ## @qcode{'stacked'}. The default style is @qcode{'detached'}. ## ## @code{bar3h (@dots{}, @var{color})} displays all bars using the color ## specified by color. For example, use @qcode{'red'} or @qcode{'r'} to ## specify all red bars. When you want to specify colors for several groups, ## @var{color} can be a cellstr vector with each element specifying the color of ## each group. @var{color} can also be specified as a numerical @math{M*3} ## matrix, where each row corresponds to a RGB value with its elements in the ## range @math{[0,1]}. If only one color is specified, then it applies to all ## bars. If the number of colors equals the number of groups, then each color ## is applied to each group. If the number of colors equals the number of ## elements in @var{y}, then each individual bar is assigned the particular ## color. You can also define @var{color} using the paired ## @var{name}/@var{value} syntax shown below. ## ## @code{bar3h (@dots{}, @var{name}, @var{value})} specifies one or more of the ## following name/value pairs: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'width'} @tab A two-element vector specifying the width ## of the bars along the @qcode{x-} and @qcode{z-axes}, respectively. Each ## element must be in the range @math{(0,1]}. ## ## @item @qcode{'color'} @tab A character or a cellstr vector, or a ## numerical @math{M*3} matrix following the same conventions as the @var{color} ## input argument. ## ## @item @qcode{'xlabel'} @tab A cellstr vector specifying the group names ## along the @qcode{x-axis}. ## ## @item @qcode{'zlabel'} @tab A cellstr vector specifying the names of the ## bars in the same series along the @qcode{z-axis}. ## @end multitable ## ## @code{bar3h (@var{ax}, @dots{})} can also take an axes handle @var{ax} as a ## first argument in which case it plots into the axes specified by @var{ax} ## instead of into the current axes specified by @code{gca ()}. The optional ## argument @var{ax} can precede any of the input argument combinations in the ## previous syntaxes. ## ## @code{@var{p} = bar3h (@dots{})} returns a patch handle @var{p}, which can be ## used to set properties of the bars after displaying the 3D bar graph. ## ## @seealso{boxplot, hist3} ## @end deftypefn function [varargout] = bar3h (varargin) if (nargin < 1) print_usage (); endif ## Check if first input is an axes handle if (isaxes (varargin{1})) ax = varargin{1}; varargin(1) = []; new_axes = false; else new_axes = true; endif ## Parse input argument Z if (numel (varargin) < 1) print_usage (); endif y = varargin{1}; varargin(1) = []; if (! isnumeric (y)) error ("bar3h: Z must be numeric."); endif ## Add defaults z = []; width = 0.8; depth = 0.8; style = 'detached'; color = []; xlabel = []; zlabel = []; ## Valid Colors for input validation vc = {'red', 'r', 'green', 'g', 'blue', 'b', 'cyan', 'c', ... 'magenta', 'm', 'yellow', 'y', 'black', 'k', 'white', 'w'}; ## Parse extra input arguments while (numel (varargin) > 0) tmp = varargin{1}; if (isnumeric (tmp) && isempty (z)) if (isvector (tmp) && isvector (y) && numel (tmp) == numel (y)) z = y; y = tmp; elseif (all (size (tmp) > 1) && size (tmp, 1) == numel (y) && isvector (y)) z = y; y = tmp; elseif (isscalar (tmp)) z = NaN; if (tmp > 0 && tmp <= 1) width = tmp; depth = tmp; else error ("bar3h: WIDTH must be a scalar in the range (0,1]."); endif elseif (size (tmp, 2) == 3 && all (tmp(:) >= 0) && all (tmp(:) <= 1)) z = NaN; color = tmp; else error ("bar3h: inconsistent size in Y and Z input arguments."); endif varargin(1) = []; elseif (isnumeric (tmp) && isscalar (tmp)) if (tmp > 0 && tmp <= 1) width = tmp; depth = tmp; else error ("bar3h: WIDTH must be a scalar in the range (0,1]."); endif varargin(1) = []; elseif (isnumeric (tmp)) if (size (tmp, 2) == 3 && all (tmp(:) >= 0) && all (tmp(:) <= 1)) color = tmp; else error (strcat ("bar3h: numeric COLOR must be a 1x3 vector of an Nx3 matrix", " where each value is between 0 and 1 inclusive.")); endif color = tmp; varargin(1) = []; elseif (ischar (tmp)) if (any (strcmpi (tmp, {'detached', 'grouped', 'stacked'}))) style = tmp; varargin(1) = []; elseif (any (strcmpi (tmp, vc))) color = tmp; varargin(1) = []; elseif (strcmpi (tmp, 'width')) if (numel (varargin) < 2) error ("bar3h: missing value for optional argument 'width'."); endif w = varargin{2}; if (isscalar (w) && isnumeric (w) && isfinite (w) && w > 0 && w <= 1) width = w; depth = w; elseif (numel (w) == 2 && isnumeric (w) && isfinite (w) && all (w > 0) && all (w <= 1)) width = w(1); depth = w(2); else error ("bar3h: invalid value for optional argument 'width'."); endif varargin([1:2]) = []; elseif (strcmpi (tmp, 'color')) if (numel (varargin) < 2) error ("bar3h: missing value for optional argument 'color'."); endif c = varargin{2}; if (iscellstr (c)) is_vc = all (cell2mat (cellfun (@(x) any (strcmpi (vc, x)), ... c, 'UniformOutput', false))); if (is_vc) color = c; else error ("bar3h: invalid value for optional argument 'color'."); endif elseif (ischar (c) && isvector (c)) if (any (strcmpi (c, vc))) color = c; else error ("bar3h: invalid value for optional argument 'color'."); endif elseif (isnumeric (c)) if (size (c, 2) == 3 && all (c(:) >= 0) && all (c(:) <= 1)) color = c; else error (strcat ("bar3h: numeric COLOR must be a 1x3 vector of an Nx3", " matrix where each value is between 0 and 1 inclusive.")); endif else error ("bar3h: invalid value for optional argument 'color'."); endif varargin([1:2]) = []; elseif (strcmpi (tmp, 'xlabel')) if (numel (varargin) < 2) error ("bar3h: missing value for optional argument 'xlabel'."); endif xlabel = varargin{2}; if (! iscellstr (xlabel)) error ("bar3h: invalid value for optional argument 'xlabel'."); endif varargin([1:2]) = []; elseif (strcmpi (tmp, 'zlabel')) if (numel (varargin) < 2) error ("bar3h: missing value for optional argument 'zlabel'."); endif zlabel = varargin{2}; if (! iscellstr (zlabel)) error ("bar3h: invalid value for optional argument 'zlabel'."); endif varargin([1:2]) = []; else error ("bar3h: invalid optional argument."); endif elseif (iscellstr (tmp)) is_vc = all (cell2mat (cellfun (@(x) any (strcmpi (vc, x)), ... tmp, 'UniformOutput', false))); if (is_vc) color = tmp; else error ("bar3h: invalid value for optional COLOR argument."); endif varargin(1) = []; else error ("bar3h: invalid optional argument."); endif endwhile ## Get number of column bars from y input [nz, nx] = size (y); ## Check xlabel and zlabel if (! isempty (xlabel) && numel (xlabel) != nx) error ("bar3h: the elements in 'xlabel' must equal the columns in Z."); endif if (! isempty (zlabel) && numel (zlabel) != nz) error ("bar3h: the elements in 'zlabel' must equal the rows in Z."); endif ## Check COLOR for valid dimensions if (isempty (color)) if (nx == 1) cargs = {'FaceColor', 'b'}; elseif (nx == 0) defc = [0,0,1;1,0,1;0,1,0;1,1,0]; fvcd = kron (defc([1:nx],:), ones (6 * nz, 1)); cargs = {'FaceVertexCData', fvcd, 'FaceColor', 'flat'}; else fvcd = kron ((1:nx)', ones (6 * nz, 1)); cargs = {'FaceVertexCData', fvcd, 'FaceColor', 'flat', ... 'CDataMapping', 'scaled'}; endif elseif (isnumeric (color)) if (size (color, 1) == numel (y)) fvcd = kron (color, ones (6, 1)); cargs = {'FaceVertexCData', fvcd, 'FaceColor', 'flat'}; elseif (size (color, 1) == nx) fvcd = kron (color, ones (6 * nz, 1)); cargs = {'FaceVertexCData', fvcd, 'FaceColor', 'flat'}; elseif (size (color, 1) == nz) fvcd = repmat (kron (color, ones (6, 1)), nx, 1); cargs = {'FaceVertexCData', fvcd, 'FaceColor', 'flat'}; elseif (size (color, 1) == 1) cargs = {'FaceVertexCData', color, 'FaceColor', 'flat'}; endif elseif (ischar (color)) cargs = {'FaceColor', color}; elseif (iscellstr (color)) endif ## Construct a "template" column-bar (8 vertices and 6 faces) centered at ## origin, with height = 1, and width along x-axis and depth along z-axis hw = width / 2; hd = depth / 2; ## Scale the bar's base when grouping together if (strcmpi (style, 'grouped')) sc = nx + 1; hw = hw / sc; hd = hd / sc; endif [X, Y, Z] = ndgrid ([-hw, hw], [0, 1], [-hd, hd]); V = [X(:), Y(:), Z(:)]; F = [1, 2, 4, 3; 5, 6, 8, 7; 1, 2, 6, 5; 3, 4, 8, 7; 1, 5, 7, 3; 2, 6, 8, 4]; ## Replicate faces to the required number of bars increments = 0:8:8 * (nx * nz - 1); F = bsxfun (@plus, F, permute (increments, [1, 3, 2])); F = reshape (permute (F, [2, 1, 3]), 4, []).'; ## Replicate vertices to the required number of bars [offsetX, offsetZ] = meshgrid (1:nx, 1:nz); offset = [offsetX(:), zeros(numel (y), 1), offsetZ(:)]; #offset(:,3) = 0; V = bsxfun (@plus, V, permute (offset, [3, 2, 1])); V = reshape (permute (V, [2, 1, 3]), 3, []).'; A = NaN; if (strcmpi (style, 'detached')) ## Adjust bar heights according to values in y input V(:,2) = V(:,2) .* kron (y(:), ones (8,1)); elseif (strcmpi (style, 'grouped')) ## Adjust bar heights according to values in y input V(:,2) = V(:,2) .* kron (y(:), ones (8,1)); ## Move groups along x axis V(:,1) = V(:,1) - kron (kron ([0:nx-1], ones (nz, 1))(:), ones (8,1)); ## Move groups along z axis offset = [-nx+1:2:nx-1] * (hd / width); V(:,3) = V(:,3) + kron (kron (ones (1, nz), offset)(:), ones (8,1)); nx = 1; elseif (strcmpi (style, 'stacked')) ## Move groups along x axis V(:,1) = V(:,1) - kron (kron ([0:nx-1], ones (nz, 1))(:), ones (8,1)); ## Adjust bar heights according to values in y input ZC = cumsum (y,2); Q1 = kron (ZC(:), ones (8,1)); ZC(:,end) = []; ZC = [zeros(nz,1), ZC]; Q2 = kron (ZC(:), ones (8,1)); V(:,2) = V(:,2) .* Q1; idx = V(:,2) == 0; V(idx,2) = Q2(idx); ## Collapse x axis nx = 1; endif ## Draw column bars as patches specified by faces/vertices if (new_axes) ax = gca (); endif p = patch ('Faces', F, 'Vertices', V, 'EdgeColor', 'k', 'Parent', ax, cargs{:}); ## Set view port and axes view (ax, 3); grid (ax, 'on'); axis tight; xlim ([0.5, nx+0.5]); zlim ([0.5, nz+0.5]); set (ax, 'XTick', 1:nx, 'ZTick', 1:nz, 'Box', 'off', 'YDir', 'reverse'); ## Fix aspect ratio so that bars appear square when rotating the bar plot if (nx > nz) set (ax, 'PlotBoxAspectRatio', [1, (sqrt(5)-1)/2, nz/nx]); elseif (nx < nz) set (ax, 'PlotBoxAspectRatio', [nx/nz, (sqrt(5)-1)/2, 1]); else set (ax, 'PlotBoxAspectRatio', [1, (sqrt(5)-1)/2, 1]); endif ## Add tick labels in axes (if requested) if (! isempty (xlabel)) set (ax, 'XTickLabel', xlabel); endif if (! isempty (zlabel)) set (ax, 'ZTickLabel', zlabel); elseif (! isempty (z) && ! isnan (z)) zlabel = arrayfun (@(x) sprintf ("%d", x), z, 'UniformOutput', false); set (ax, 'ZTickLabel', zlabel); endif ## Return handle to patch object if requested if (nargout > 0) varargout{1} = p; endif endfunction %!demo %! ## Plotting 5 bars in the same series. %! %! y = [50; 40; 30; 20; 10]; %! bar3h (y); %!demo %! ## Plotting 5 bars in different groups. %! %! y = [50, 40, 30, 20, 10]; %! bar3h (y); %!demo %! ## A 3D bar graph with each series corresponding to a column in y. %! %! y = [1, 4, 7; 2, 5, 8; 3, 6, 9; 4, 7, 10]; %! bar3h (y); %!demo %! ## Specify z-axis locations as tick names. z must be a column vector! %! %! z = [1950, 1960, 1970, 1980, 1990]'; %! y = [16, 8, 4, 2, 1]'; %! bar3h (z, y); %!demo %! ## Plot 3 series as a grouped plot without any space between the grouped bars %! %! y = [70 50 33 10; 75 55 35 15; 80 60 40 20]; %! bar3h (y, 1, 'grouped'); %!demo %! ## Plot a stacked style 3D bar graph %! %! y = [19, 30, 21, 30; 40, 16, 32, 12]; %! b = bar3h (y, 0.5, 'stacked'); ## Test input validation %!error bar3h ('A') %!error bar3h ({2,3,4,5}) %!error ... %! bar3h ([1,2,3]', ones (2)) %!error ... %! bar3h ([1:5], 1.2) %!error ... %! bar3h ([1:5]', ones (5), 1.2) %!error ... %! bar3h ([1:5]', ones (5), [0.8, 0.7]) %!error ... %! bar3h (ones (5), 'width') %!error ... %! bar3h (ones (5), 'width', 1.2) %!error ... %! bar3h (ones (5), 'width', [0.8, 0.8, 0.8]) %!error ... %! bar3h (ones (5), 'color') %!error ... %! bar3h (ones (5), 'color', [0.8, 0.8]) %!error ... %! bar3h (ones (5), 'color', 'brown') %!error ... %! bar3h (ones (5), 'color', {'r', 'k', 'c', 'm', 'brown'}) %!error ... %! bar3h (ones (5), 'xlabel') %!error ... %! bar3h (ones (5), 'xlabel', 4) %!error ... %! bar3h (ones (5), 'zlabel') %!error ... %! bar3h (ones (5), 'zlabel', 4) %!error bar3h (ones (5), 'this', 4) %!error ... %! bar3h (ones (5), 'xlabel', {'A', 'B', 'C'}) %!error ... %! bar3h (ones (5), 'zlabel', {'A', 'B', 'C'}) statistics-release-1.9.2/inst/Plotting/biplot.m000066400000000000000000000304641524624707500216020ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} biplot (@var{coefs}) ## @deftypefnx {statistics} {} biplot (@var{coefs}, @var{name}, @var{value}, @dots{}) ## @deftypefnx {statistics} {} biplot (@var{ax}, @dots{}) ## @deftypefnx {statistics} {@var{h} =} biplot (@dots{}) ## ## Create a biplot of the coefficients in @var{coefs}. ## ## @code{biplot (@var{coefs})} plots the rows of @var{coefs}, typically the ## principal component coefficients returned by @code{pca} or @code{pcacov} or ## the factor loadings returned by @code{factoran}, as vectors from the origin. ## @var{coefs} has one row per observed variable and either two columns (for a ## 2-D biplot) or three columns (for a 3-D biplot). ## ## The following name/value pairs are accepted: ## ## @table @asis ## @item @qcode{"Scores"} ## A matrix of scores with the same number of columns as @var{coefs} (one row ## per observation). The scores are plotted as points, scaled to fit within the ## unit circle relative to the maximum coefficient length: each score is divided ## by the maximum absolute score value and multiplied by the length of the ## longest coefficient vector. ## ## @item @qcode{"VarLabels"} ## A character array or cell array of strings labeling each variable vector. ## ## @item @qcode{"ObsLabels"} ## A character array or cell array of strings labeling each observation. ## ## @item @qcode{"Positive"} ## If @qcode{true}, the reference axes are drawn only over the positive quadrant ## (2-D) or octant (3-D). The default is @qcode{false}. ## @end table ## ## Any additional name/value pairs are treated as line properties and applied to ## the variable vectors. ## ## For readability the sign of each column of @var{coefs} is chosen so that its ## largest-magnitude element is positive; the same sign change is applied to the ## corresponding column of the scores. ## ## @code{biplot (@var{ax}, @dots{})} plots into the axes @var{ax}. ## ## The optional output @var{h} is a column vector of handles to the plotted ## graphics objects, ordered as the variable vector lines, the variable markers, ## the variable text labels (if any), the observation markers, the observation ## text labels (if any), and finally the reference axis lines. ## ## @seealso{pca, pcacov, factoran, rotatefactors} ## @end deftypefn function h = biplot (varargin) ## Optional leading axes handle hax = []; if (numel (varargin) > 0 && isaxes (varargin{1})) hax = varargin{1}; varargin(1) = []; endif if (numel (varargin) < 1) print_usage (); endif coefs = varargin{1}; varargin(1) = []; if (! isnumeric (coefs) || ! isreal (coefs) || ndims (coefs) > 2 ... || (columns (coefs) != 2 && columns (coefs) != 3)) error ("biplot: COEFS must be a real matrix with 2 or 3 columns."); endif p = rows (coefs); d = columns (coefs); ## Parse name/value options scores = []; varlabels = {}; obslabels = {}; positive = false; lineprops = {}; if (mod (numel (varargin), 2) != 0) error ("biplot: name/value arguments must come in pairs."); endif for i = 1:2:numel (varargin) name = varargin{i}; value = varargin{i+1}; if (! ischar (name)) error ("biplot: property names must be strings."); endif switch (lower (name)) case "scores" scores = value; case "varlabels" varlabels = value; case "obslabels" obslabels = value; case "positive" positive = value; otherwise lineprops(end+1:end+2) = {name, value}; endswitch endfor have_scores = ! isempty (scores); if (have_scores) if (! isnumeric (scores) || ! isreal (scores) || columns (scores) != d) error ("biplot: SCORES must be a real matrix with the same number of columns as COEFS."); endif endif if (ischar (varlabels)) varlabels = cellstr (varlabels); endif if (ischar (obslabels)) obslabels = cellstr (obslabels); endif ## Sign convention: force the largest-magnitude element of each column positive for j = 1:d [~, idx] = max (abs (coefs(:,j))); if (coefs(idx,j) < 0) coefs(:,j) = -coefs(:,j); if (have_scores) scores(:,j) = -scores(:,j); endif endif endfor ## Scale the scores to the coefficient space if (have_scores) maxlen = max (sqrt (sum (coefs .^ 2, 2))); maxscore = max (abs (scores(:))); if (maxscore > 0) scores = scores * (maxlen / maxscore); endif endif ## Reference axis extent axlim = 1.1 * max (abs (coefs(:))); if (axlim == 0 || isnan (axlim)) axlim = 1; endif if (isempty (hax)) hax = newplot (); else newplot (hax); endif old_hold = ishold (hax); hold (hax, "on"); vcol = [0 0 1]; # variable vectors in blue ocol = [1 0 0]; # observations in red acol = [0.5 0.5 0.5]; # reference axes in gray varlines = zeros (p, 1); varmarks = zeros (p, 1); vartext = zeros (numel (varlabels), 1); obsmarks = zeros (0, 1); obstext = zeros (numel (obslabels), 1); ## Variable vector lines (origin to each coefficient) for i = 1:p if (d == 2) varlines(i) = line (hax, [0, coefs(i,1)], [0, coefs(i,2)], ... "color", vcol, "marker", "none", lineprops{:}); else varlines(i) = line (hax, [0, coefs(i,1)], [0, coefs(i,2)], ... [0, coefs(i,3)], "color", vcol, "marker", "none", ... lineprops{:}); endif endfor ## Variable markers at the vector tips. Each marker's data carries the ## trailing NaN MATLAB puts there; it draws nothing, but code reading XData ## off the returned handle sees the same array in both. for i = 1:p if (d == 2) varmarks(i) = line (hax, [coefs(i,1), NaN], [coefs(i,2), NaN], ... "linestyle", "none", "marker", "o", "color", vcol); else varmarks(i) = line (hax, [coefs(i,1), NaN], [coefs(i,2), NaN], ... [coefs(i,3), NaN], "linestyle", "none", ... "marker", "o", "color", vcol); endif endfor ## Variable text labels for i = 1:numel (varlabels) if (d == 2) vartext(i) = text (hax, coefs(i,1), coefs(i,2), varlabels{i}, ... "color", vcol); else vartext(i) = text (hax, coefs(i,1), coefs(i,2), coefs(i,3), ... varlabels{i}, "color", vcol); endif endfor ## Observation markers if (have_scores) m = rows (scores); obsmarks = zeros (m, 1); for j = 1:m if (d == 2) obsmarks(j) = line (hax, [scores(j,1), NaN], [scores(j,2), NaN], ... "linestyle", "none", "marker", ".", "color", ocol); else obsmarks(j) = line (hax, [scores(j,1), NaN], [scores(j,2), NaN], ... [scores(j,3), NaN], "linestyle", "none", ... "marker", ".", "color", ocol); endif endfor ## Observation text labels for j = 1:numel (obslabels) if (d == 2) obstext(j) = text (hax, scores(j,1), scores(j,2), obslabels{j}, ... "color", ocol); else obstext(j) = text (hax, scores(j,1), scores(j,2), scores(j,3), ... obslabels{j}, "color", ocol); endif endfor endif ## Reference axis lines through the origin lo = ifelse (positive, 0, -axlim); if (d == 2) axline = line (hax, [lo, axlim, NaN, 0, 0], [0, 0, NaN, lo, axlim], ... "color", acol); else axline = line (hax, [lo, axlim, NaN, 0, 0, NaN, 0, 0], ... [0, 0, NaN, lo, axlim, NaN, 0, 0], ... [0, 0, NaN, 0, 0, NaN, lo, axlim], "color", acol); endif if (d == 3) view (hax, 3); zlabel (hax, "Component 3"); endif xlabel (hax, "Component 1"); ylabel (hax, "Component 2"); if (! old_hold) hold (hax, "off"); endif if (nargout > 0) h = [varlines; varmarks; vartext; obsmarks; obstext; axline]; endif endfunction ## Return a if cond is true, else b. function r = ifelse (cond, a, b) if (cond) r = a; else r = b; endif endfunction %!demo %! ## Biplot of the first two principal components of Fisher's iris data. %! %! load fisheriris; %! [coefs, score] = pca (zscore (meas)); %! biplot (coefs(:,1:2), "Scores", score(:,1:2), ... %! "VarLabels", {"SL", "SW", "PL", "PW"}); %!demo %! ## Three-component biplot of the same data. %! %! load fisheriris; %! coefs = pca (zscore (meas)); %! biplot (coefs(:,1:3), "VarLabels", {"SL", "SW", "PL", "PW"}); ## Test output %!test %! hf = figure ("visible", "off"); %! unwind_protect %! coefs = [0.6 -0.3; -0.2 0.7; 0.5 0.5]; %! score = [1 2; -3 1; 0.5 -2; 4 0]; %! h = biplot (coefs, "Scores", score); %! assert_equal (numel (h), 11); %! ## variable vector lines (origin to coefficient) %! assert_equal (get (h(1), "xdata"), [0 0.6], 1e-12); %! assert_equal (get (h(1), "ydata"), [0 -0.3], 1e-12); %! assert_equal (get (h(2), "xdata"), [0 -0.2], 1e-12); %! ## variable tip markers %! ## the trailing NaN is MATLAB's own, and draws nothing %! assert_equal (get (h(4), "xdata"), [0.6 NaN], 1e-12); %! assert_equal (get (h(4), "ydata"), [-0.3 NaN], 1e-12); %! ## observation markers (scaled scores) %! assert_equal (get (h(7), "xdata"), [0.182 NaN], 1e-4); %! assert_equal (get (h(7), "ydata"), [0.364 NaN], 1e-4); %! assert_equal (get (h(10), "xdata"), [0.728 NaN], 1e-4); %! ## reference axis extent %! assert_equal (get (h(11), "xdata"), [-0.77 0.77 NaN 0 0], 1e-12); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # 3-D biplot without scores %! hf = figure ("visible", "off"); %! unwind_protect %! coefs = [0.6 -0.3 0.2; -0.2 0.7 0.1; 0.5 0.5 -0.6]; %! h = biplot (coefs); %! assert_equal (numel (h), 7); %! ## column 3 has its largest-magnitude element (-0.6) forced positive, %! ## so the whole column is negated: [0.2 0.1 -0.6] -> [-0.2 -0.1 0.6] %! assert_equal (get (h(1), "zdata"), [0 -0.2], 1e-12); %! assert_equal (get (h(6), "zdata"), [0.6 NaN], 1e-12); %! assert_equal (get (h(7), "zdata"), [0 0 NaN 0 0 NaN -0.77 0.77], 1e-12); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # sign convention flips a column with a negative largest element %! hf = figure ("visible", "off"); %! unwind_protect %! coefs = [-0.8 0.1; 0.2 0.9; -0.5 0.5]; %! h = biplot (coefs); %! assert_equal (get (h(1), "xdata"), [0 0.8], 1e-12); %! assert_equal (get (h(2), "xdata"), [0 -0.2], 1e-12); %! assert_equal (get (h(3), "xdata"), [0 0.5], 1e-12); %! assert_equal (get (h(1), "ydata"), [0 0.1], 1e-12); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # labels add text handles in the documented order %! hf = figure ("visible", "off"); %! unwind_protect %! coefs = [0.6 -0.3; -0.2 0.7]; %! score = [1 2; -3 1]; %! h = biplot (coefs, "Scores", score, "VarLabels", {"a", "b"}, ... %! "ObsLabels", {"x", "y"}); %! ## 2 varlines + 2 varmarks + 2 vartext + 2 obsmarks + 2 obstext + 1 axis %! assert_equal (numel (h), 11); %! assert_equal (strcmp (get (h(5), "string"), "a"), true); %! assert_equal (strcmp (get (h(9), "string"), "x"), true); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error biplot () %!error ... %! biplot (ones (3, 4)) %!error biplot ({1}) %!error ... %! biplot ([0.6 0.3; 0.2 0.7], "Scores", [1 2 3]) %!error ... %! biplot ([0.6 0.3; 0.2 0.7], "Scores") statistics-release-1.9.2/inst/Plotting/boxplot.m000066400000000000000000001344151524624707500220010ustar00rootroot00000000000000## Copyright (C) 2002 Alberto Terruzzi ## Copyright (C) 2006 Alberto Pose ## Copyright (C) 2011 Pascal Dupuis ## Copyright (C) 2012 Juan Pablo Carbajal ## Copyright (C) 2016 Pascal Dupuis ## Copyright (C) 2020 Andreas Bertsatos ## Copyright (C) 2020 Philip Nienhuis (prnienhuis@users.sf.net) ## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{s} =} boxplot (@var{data}) ## @deftypefnx {statistics} {@var{s} =} boxplot (@var{data}, @var{group}) ## @deftypefnx {statistics} {@var{s} =} boxplot (@var{data}, @var{notched}, @var{symbol}, @var{orientation}, @var{whisker}, @dots{}) ## @deftypefnx {statistics} {@var{s} =} boxplot (@var{data}, @var{group}, @var{notched}, @var{symbol}, @var{orientation}, @var{whisker}, @dots{}) ## @deftypefnx {statistics} {@var{s} =} boxplot (@var{data}, @var{options}) ## @deftypefnx {statistics} {@var{s} =} boxplot (@var{data}, @var{group}, @var{options}, @dots{}) ## @deftypefnx {statistics} {[@dots{}, @var{h}] =} boxplot (@var{data}, @dots{}) ## ## Produce a box plot. ## ## A box plot is a graphical display that simultaneously describes several ## important features of a data set, such as center, spread, departure from ## symmetry, and identification of observations that lie unusually far from ## the bulk of the data. ## ## Input arguments (case-insensitive) recognized by boxplot are: ## ## @itemize ## @item ## @var{data} is a matrix with one column for each data set, or a cell vector ## with one cell for each data set. Each cell must contain a numerical row or ## column vector (NaN and NA are ignored) and not a nested vector of cells. ## ## @item ## @var{notched} = 1 produces a notched-box plot. Notches represent a robust ## estimate of the uncertainty about the median. ## ## @var{notched} = 0 (default) produces a rectangular box plot. ## ## @var{notched} within the interval (0,1) produces a notch of the specified ## depth. Notched values outside (0,1) are amusing if not exactly impractical. ## ## @item ## @var{symbol} sets the symbol for the outlier values. The default symbol ## for points that lie outside 3 times the interquartile range is 'o'; ## the default symbol for points between 1.5 and 3 times the interquartile ## range is '+'. @* ## Alternative @var{symbol} settings: ## ## @var{symbol} = '.': points between 1.5 and 3 times the IQR are marked with ## '.' and points outside 3 times IQR with 'o'. ## ## @var{symbol} = ['x','*']: points between 1.5 and 3 times the IQR are marked ## with 'x' and points outside 3 times IQR with '*'. ## ## @item ## @var{orientation} = 0 makes the boxes horizontally. @* ## @var{orientation} = 1 plots the boxes vertically (default). Alternatively, ## orientation can be passed as a string, e.g., 'vertical' or 'horizontal'. ## ## @item ## @var{whisker} defines the length of the whiskers as a function of the IQR ## (default = 1.5). If @var{whisker} = 0 then @code{boxplot} displays all data ## values outside the box using the plotting symbol for points that lie ## outside 3 times the IQR. ## ## @item ## @var{group} may be passed as an optional argument only in the second ## position after @var{data}. @var{group} can be a numeric, character, ## string, or categorical vector defining separate categories. To group by ## multiple variables simultaneously, pass a cell array of grouping vectors ## (e.g., @code{@{group1, group2@}}). A separate box is plotted for each ## unique combination of group values. All grouping variables must have the ## same length as @var{data}. ## ## @item ## @var{options} are additional paired arguments passed with the formalism ## (Name, Value) that provide extra functionality as listed below. ## @var{options} can be passed at any order after the initial arguments and ## are case-insensitive. ## ## @multitable {Name} {Value} {description} @columnfractions .2 .2 .6 ## @item 'Notch' @tab 'on' @tab Notched by 0.25 of the boxes width. ## @item @tab 'off' @tab Produces a straight box. ## @item @tab scalar @tab Proportional width of the notch. ## ## @item 'Symbol' @tab '.' @tab Defines only outliers between 1.5 and 3 IQR. ## @item @tab ['x','*'] @tab 2nd character defines outliers > 3 IQR ## ## @item 'Orientation' @tab 'vertical' @tab Default value, can also be defined ## with numerical 1. ## @item @tab 'horizontal' @tab Can also be defined with numerical 0. ## ## @item 'Whisker' @tab scalar @tab Multiplier of IQR (default is 1.5). ## ## @item 'OutlierTags' @tab 'on' or 1 @tab Plot the vector index of the outlier ## value next to its point. ## @item @tab 'off' or 0 @tab No tags are plotted (default value). ## ## @item 'Sample_IDs' @tab 'cell' @tab A cell vector with one cell for each ## data set containing a nested cell vector with each sample's ID (should be ## a string). If this option is passed, then all outliers are tagged with ## their respective sample's ID string instead of their vector's index. ## ## @item 'BoxWidth' @tab 'proportional' @tab Create boxes with their width ## proportional to the number of samples in their respective dataset (default ## value). ## @item @tab 'fixed' @tab Make all boxes with equal width. ## ## @item 'Widths' @tab scalar @tab Scaling factor for box widths (default ## value is 0.4). ## ## @item 'CapWidths' @tab scalar @tab Scaling factor for whisker cap widths ## (default value is 1, which results to 'Widths'/8 halflength) ## ## @item 'BoxStyle' @tab 'outline' @tab Draw boxes as outlines (default value). ## @item @tab 'filled' @tab Fill boxes with a color (outlines are still ## plotted). ## ## @item 'Positions' @tab vector @tab Numerical vector that defines the ## position of each data set. It must have the same length as the number of ## groups in a desired manner. This vector merely defines the points along ## the group axis, which by default is [1:number of groups]. ## ## @item 'Labels' @tab cell @tab A cell vector of strings containing the names ## of each group. By default each group is labeled numerically. If multiple ## grouping variables are provided, default labels are automatically generated ## by joining the category names and stacked hierarchically. ## ## @item 'Colors' @tab character string or Nx3 numerical matrix @tab If just ## one character or 1x3 vector of RGB values, specify the fill color of all ## boxes when BoxStyle = 'filled'. If a character string or Nx3 matrix is ## entered, box #1's fill color corresponds to the first character or first ## matrix row, and the next boxes' fill colors corresponds to the next ## characters or rows. If the char string or Nx3 array is exhausted the color ## selection wraps around. ## @end multitable ## @end itemize ## ## Supplemental arguments not described above (@dots{}) are concatenated and ## passed to the plot() function. ## ## The returned matrix @var{s} has one column for each data set as follows: ## ## @multitable @columnfractions .1 .8 ## @item 1 @tab Minimum ## @item 2 @tab 1st quartile ## @item 3 @tab 2nd quartile (median) ## @item 4 @tab 3rd quartile ## @item 5 @tab Maximum ## @item 6 @tab Lower confidence limit for median ## @item 7 @tab Upper confidence limit for median ## @end multitable ## ## The quartiles are those of @code{quantile} at its default method, which is ## also what @code{prctile} returns, so the box edges of a data set always ## agree with @code{prctile (@var{data}, [25, 75])}. They set the ## inter-quartile range, and so the whisker fences and which observations are ## reported as outliers. ## ## The returned structure @var{h} contains handles to the plot elements, ## allowing customization of the visualization using set/get functions. ## ## Example ## ## @example ## title ("Grade 3 heights"); ## axis ([0,3]); ## set(gca (), "xtick", [1 2], "xticklabel", @{"girls", "boys"@}); ## boxplot (@{randn(10,1)*5+140, randn(13,1)*8+135@}); ## @end example ## ## @end deftypefn function [s_o, hs_o] = boxplot (data, varargin) ## Assign parameter defaults if (nargin < 1) print_usage; endif ## Check data if (! (isnumeric (data) || iscell (data))) error ("boxplot: numerical array or cell array containing data expected."); elseif (iscell (data)) ## Check if cell contain numerical data if (! all (cellfun ('isnumeric', data))) error ("boxplot: data cells must contain numerical data."); endif endif ## Integer observations are perfectly good data, and MATLAB accepts them, but ## the quartiles are taken through statistics, whose call to var insists on ## floating point. Convert rather than refuse. Unlike MATLAB the quartiles ## are then computed in double, so they are not rounded back to the integer ## grid: the quartiles of int32 (1:7) are 2.25 and 5.75, not 3 and 6. if (isinteger (data)) data = double (data); elseif (iscell (data) && any (cellfun (@isinteger, data))) data = cellfun (@double, data, 'UniformOutput', false); endif ## Default values maxwhisker = 1.5; orientation = 1; symbol = ['+', 'o']; notched = 0; plot_opts = {}; groups = []; sample_IDs = {}; outlier_tags = 0; box_width = 'proportional'; widths = 0.4; capwid = 1; box_style = 0; positions = []; labels = {}; nug = 0; bcolor = 'y'; ## Optional arguments analysis numarg = nargin - 1; indopt = 1; group_exists = 0; while (numarg) dummy = varargin{indopt++}; if ((! ischar (dummy) || iscellstr (dummy)) && indopt < 6) ## MATLAB allows passing the second argument as a grouping vector if (length (dummy) > 1) if (2 != indopt) error ("boxplot: grouping vector may only be passed as second arg."); endif if (isnumeric (dummy) || ischar (dummy) || iscell (dummy) || ... iscategorical (dummy) || isa (dummy, 'string')) groups = dummy; group_exists = 1; else error ("boxplot: grouping variable must be numeric, character, string, categorical, or cell array."); endif elseif (length (dummy) == 1) ## Old way: positional argument switch indopt - group_exists case 2 notched = dummy; case 4 orientation = dummy; case 5 maxwhisker = dummy; otherwise error ("boxplot: no positional argument allowed at position %d", ... --indopt); endswitch endif numarg--; continue; else if (3 == (indopt - group_exists) && length (dummy) <= 2) symbol = dummy; numarg--; continue; else ## Check for additional paired arguments switch lower (dummy) case 'notch' notched = varargin{indopt}; ## Check for string input: "on" or "off" if (ischar (notched)) if (strcmpi (notched, 'on')) notched = 1; elseif (strcmpi (notched, 'off')) notched = 0; else error (strcat ("boxplot: 'Notch' input argument accepts", ... " only 'on', 'off' or a numeric scalar value.")); endif elseif (! (isnumeric (notched) && isreal (notched))) error ("boxplot: invalid 'Notch' value."); endif case 'symbol' symbol = varargin{indopt}; if (! ischar (symbol)) error ("boxplot; Symbol(s) must be character(s)"); endif case 'orientation' orientation = varargin{indopt}; if (ischar (orientation)) ## Check for string input: "vertical" or "horizontal" if (strcmpi (orientation, 'vertical')) orientation = 1; elseif (strcmpi (orientation, 'horizontal')) orientation = 0; else error (strcat ("boxplot: 'Orientation' input argument", ... " accepts only 'vertical' (or 1) or", ... " 'horizontal' (or 0) as value.")); endif elseif (! (isnumeric (orientation) && isreal (orientation))) error ("boxplot: invalid 'Orientation' value."); endif case 'whisker' maxwhisker = varargin{indopt}; if (! isscalar (maxwhisker) || ! (isnumeric (maxwhisker) && isreal (maxwhisker))) error (strcat ("boxplot: 'Whisker' input argument accepts", ... " only a real scalar value as input parameter.")); endif case 'outliertags' outlier_tags = varargin{indopt}; ## Check for string input: "on" or "off" if (ischar (outlier_tags)) if (strcmpi (outlier_tags, 'on')) outlier_tags = 1; elseif (strcmpi (outlier_tags, 'off')) outlier_tags = 0; else error (strcat ("boxplot: 'OutlierTags' input argument accepts", ... " only 'on' (or 1) or 'off' (or 0) as value.")); endif elseif (! (isnumeric (outlier_tags) && isreal (outlier_tags))) error ("boxplot: invalid 'OutlierTags' value."); endif case 'sample_ids' sample_IDs = varargin{indopt}; if (! iscell (sample_IDs)) error (strcat ("boxplot: 'Sample_IDs' input argument", ... " accepts only a cell array as value.")); endif outlier_tags = 1; case 'boxwidth' box_width = varargin{indopt}; ## Check for string input: "fixed" or "proportional" if (! ischar (box_width) || ! ismember (lower (box_width), {'fixed', 'proportional'})) error (strcat ("boxplot: 'BoxWidth' input argument accepts", ... " only 'fixed' or 'proportional' as value.")); endif box_width = lower (box_width); case 'widths' widths = varargin{indopt}; if (! isscalar (widths) || ! (isnumeric (widths) && isreal (widths))) error (strcat ("boxplot: 'Widths' input argument accepts", ... " only a real scalar value as value.")); endif case 'capwidths' capwid = varargin{indopt}; if (! isscalar (capwid) || ! (isnumeric (capwid) && isreal (capwid))) error (strcat ("boxplot: 'CapWidths' input argument accepts", ... " only a real scalar value as value.")); endif case 'boxstyle' box_style = varargin{indopt}; ## Check for string input: "outline" or "filled" if (! ischar (box_style) || ! ismember (lower (box_style), {'outline', 'filled'})) error (strcat ("boxplot: 'BoxStyle' input argument accepts", ... " only 'outline' or 'filled' as value.")); endif box_style = lower (box_style); case 'positions' positions = varargin{indopt}; if (! isvector (positions) || ! isnumeric (positions)) error (strcat ("boxplot: 'Positions' input argument accepts", ... " only a numeric vector as value.")); endif case 'labels' labels = varargin{indopt}; if (! iscellstr (labels)) error (strcat ("boxplot: 'Labels' input argument accepts", ... " only a cellstr array as value.")); endif case 'colors' bcolor = varargin{indopt}; if (! (ischar (bcolor) || (isnumeric (bcolor) && size (bcolor, 2) == 3))) error (strcat ("boxplot: 'Colors' input argument accepts", ... " only a character vector or Nx3 numeric", ... " array as value.")); endif otherwise ## Take two args and append them to plot_opts plot_opts(1, end+1:end+2) = {dummy, varargin{indopt}}; endswitch endif numarg -= 2; indopt++; endif endwhile if (1 == length (symbol)) symbol(2) = symbol(1); endif if (1 == notched) notched = 0.25; endif a = 1-notched; ## Figure out how many data sets we have if (isempty (groups)) if (iscell (data)) nc = nug = length (data); for ind_c = (1:nc) lc(ind_c) = length (data{ind_c}); endfor else if (isvector (data)) data = data(:); endif nc = nug = columns (data); lc = ones (1, nc) * rows (data); endif groups = (1:nc); ## In case sample_IDs exists. check that it has same size as data if (! isempty (sample_IDs) && length (sample_IDs) == 1) for ind_c = (1:nc) if (lc(ind_c) != length (sample_IDs)) error ("boxplot: Sample_IDs must match the data."); endif endfor elseif (! isempty (sample_IDs) && length (sample_IDs) == nc) for ind_c = (1:nc) if (lc(ind_c) != length (sample_IDs{ind_c})) error ("boxplot: Sample_IDs must match the data."); endif endfor elseif (! isempty (sample_IDs) && length (sample_IDs) != nc) error ("boxplot: Sample_IDs must match the data."); endif ## Create labels according to number of datasets as ordered in data ## in case they are not provided by the user as optional argument if (isempty (labels)) for i = 1:nc column_label = num2str (groups(i)); labels(i) = {column_label}; endfor endif else if (! isvector (data)) error ("boxplot: with the formalism (data, group), both must be vectors."); endif ## Normalize groups into a cell array of grouping variables if (! iscell (groups) || iscellstr (groups)) groups_cell = {groups}; else groups_cell = groups; endif ## Validate dimensions against data n_obs = numel (data); for i = 1:numel (groups_cell) curr_group = groups_cell{i}; if (size (curr_group, 1) != n_obs && size (curr_group, 2) != n_obs) error ("boxplot: all grouping variables must have the same length as the data."); endif ## Ensure column vector format for consistent processing later if (isrow (curr_group) && ! ischar (curr_group)) groups_cell{i} = curr_group(:); endif endfor ## If sample IDs given, check that their size matches the data if (! isempty (sample_IDs)) if (length (sample_IDs) != 1 || length (sample_IDs{1}) != length (data)) error ("boxplot: Sample_IDs must match the data"); endif endif ## map native groups to integers n_groups = numel (groups_cell); idx_matrix = zeros (n_obs, n_groups); raw_uniques = cell (1, n_groups); for i = 1:n_groups [unq_vals, ~, col_idx] = unique (groups_cell{i}); idx_matrix(:, i) = col_idx; raw_uniques{i} = unq_vals; endfor ## find valid group combinations. [valid_combinations, ~, final_numeric_groups] = unique (idx_matrix, 'rows'); nc = size (valid_combinations, 1); nug = 1:nc; dummy_data = cell (1, nc); dummy_sIDs = cell (1, nc); ## apply mask and populate dummy arrays for i = 1:nc mask = (final_numeric_groups == i); dummy_data{i} = data(mask); if (! isempty (sample_IDs)) dummy_sIDs{i} = sample_IDs{1}(mask); endif endfor ## generate labels based on native datatypes if (isempty (labels)) labels = cell (1, nc); for i = 1:nc comb = valid_combinations(i, :); lbl_parts = cell (1, n_groups); for j = 1:n_groups if (iscell (raw_uniques{j})) val = raw_uniques{j}{comb(j)}; else val = raw_uniques{j}(comb(j)); endif if (isnumeric (val) || islogical (val)) lbl_parts{j} = num2str (val); elseif (ischar (val)) lbl_parts{j} = val; elseif (isa (val, 'string') || iscategorical (val)) lbl_parts{j} = char (val); else lbl_parts{j} = ''; endif endfor ## Join multiple group labels with a newline for hierarchical stacking labels{i} = strjoin (lbl_parts, char (10)); endfor endif data = dummy_data; groups = nug(:).'; if (! isempty (sample_IDs)) sample_IDs = dummy_sIDs; endif endif ## Compute statistics. ## s will contain ## 1,5 min and max ## 2,3,4 1st, 2nd and 3rd quartile ## 6,7 lower and upper confidence intervals for median s = zeros (7, nc); box = zeros (1, nc); ## Arrange the boxes into desired positions (if requested, otherwise leave ## default 1:nc) if (! isempty (positions)) groups = positions; endif ## Initialize whisker matrices to correct size and all necessary outlier ## variables whisker_x = ones (2, 1) * [groups, groups]; whisker_y = zeros (2, 2 * nc); outliers_x = []; outliers_y = []; outliers_idx = []; outliers_IDs = {}; outliers2_x = []; outliers2_y = []; outliers2_idx = []; outliers2_IDs = {}; for indi = (1:nc) ## Get the next data set from the array or cell array if (iscell (data)) col = data{indi}(:); if (! isempty (sample_IDs)) sIDs = sample_IDs{indi}; else sIDs = num2cell ([1:length(col)]); endif else col = data(:, indi); sIDs = num2cell ([1:length(col)]); endif ## Skip missing data (NaN, NA) and remove respective sample IDs. ## Do this only on nonempty data if (length (col) > 0) remove_samples = find (isnan (col) | isna (col)); if (length (remove_samples) > 0) col(remove_samples) = []; sIDs(remove_samples) = []; endif endif ## Remember data length nd = length (col); box(indi) = nd; if (nd > 1) ## Min, max and quartiles. These come from quantile at its own default ## method, which is the one prctile and MATLAB both use. Taking them ## from the core statistics function instead put the quartiles on a ## different definition (it asks quantile for method 7), so a box drawn ## here disagreed with prctile called on the same data, and the shifted ## inter-quartile range moved the whisker fences with it. s(1:5, indi) = quantile (col, [0, 0.25, 0.5, 0.75, 1])(:); ## Confidence interval for the median est = 1.57 * (s(4, indi) - s(2, indi)) / sqrt (nd); s(6, indi) = max ([s(3, indi) - est, s(2, indi)]); s(7, indi) = min ([s(3, indi) + est, s(4, indi)]); ## Whiskers out to the last point within the desired inter-quartile range IQR = maxwhisker * (s(4, indi) - s(2, indi)); lo_adj = min (col(col >= s(2, indi) - IQR)); hi_adj = max (col(col <= s(4, indi) + IQR)); ## A whisker never reaches back across its own quartile. When no ## observation lies between a quartile and its fence, the whisker ## collapses onto the quartile rather than being drawn into the box. lo_adj = min (lo_adj, s(2, indi)); hi_adj = max (hi_adj, s(4, indi)); whisker_y(:, indi) = [lo_adj; s(2, indi)]; whisker_y(:, nc+indi) = [hi_adj; s(4, indi)]; ## Outliers beyond 1 and 2 inter-quartile ranges outliers = col((col < s(2, indi) - IQR & col >= s(2, indi) - 2 * IQR) | ... (col > s(4, indi) + IQR & col <= s(4, indi) + 2 * IQR)); outliers2 = col(col < s(2, indi) - 2 * IQR | col > s(4, indi) + 2 * IQR); ## Get outliers indices from this dataset if (length (outliers) > 0) for out_i = 1:length (outliers) outliers_idx = [outliers_idx; (find (col == outliers(out_i)))]; outliers_IDs = {outliers_IDs{:}, sIDs{(find (col == outliers(out_i)))}}; endfor endif if (length (outliers2) > 0) for out_i = 1:length (outliers2) outliers2_idx = [outliers2_idx; find(col == outliers2(out_i))]; outliers2_IDs = {outliers2_IDs{:}, sIDs{find(col == outliers2(out_i))}}; endfor endif outliers_x = [outliers_x; (groups(indi) * ones (size (outliers)))]; outliers_y = [outliers_y; outliers]; outliers2_x = [outliers2_x; (groups(indi) * ones (size (outliers2)))]; outliers2_y = [outliers2_y; outliers2]; elseif (1 == nd) ## All statistics collapse to the value of the point s(:, indi) = col; ## Single point data sets are plotted as outliers. outliers_x = [outliers_x; groups(indi)]; outliers_y = [outliers_y; col]; ## Append the single point's index to keep the outliers' vector aligned outliers_idx = [outliers_idx; 1]; outliers_IDs = {outliers_IDs{:}, sIDs{:}}; else ## No statistics if no points s(:, indi) = NaN; endif endfor ## Note which boxes don't have enough stats chop = find (box <= 1); ## Replicate widths (if scalar or shorter vector) to match the number of boxes widths = widths(repmat (1:length (widths), 1, nc)); ## Truncate just in case :) widths([nc+1:end]) = []; ## Draw a box around the quartiles, with box width being fixed or proportional ## to the number of items in the box. if (strcmpi (box_width, 'proportional')) box = box .* (widths ./ max (box)); else box = box .* (widths ./ box); endif ## Draw notches if desired. quartile_x = ones (11, 1) * groups + ... [-a; -1; -1; 1 ; 1; a; 1; 1; -1; -1; -a] * box; quartile_y = s([3, 7, 4, 4, 7, 3, 6, 2, 2, 6, 3], :); ## Draw a line through the median median_x = ones (2, 1) * groups + [-a; +a] * box; median_y = s([3, 3], :); ## Chop all boxes which don't have enough stats quartile_x(:, chop) = []; quartile_y(:, chop) = []; whisker_x(:, [chop, chop + nc]) = []; whisker_y(:, [chop, chop + nc]) = []; median_x(:, chop) = []; median_y(:, chop) = []; box(chop) = []; ## The cap widths below are built from BOX and WIDTHS together, so WIDTHS has ## to lose the same entries. Left at its full length it made every grouped ## plot with a one-point group fail on a nonconformant subtraction. widths(chop) = []; ## Add caps to the remaining whiskers cap_x = whisker_x; if (strcmpi (box_width, 'proportional')) cap_x(1, :) -= repmat (((capwid * box .* (widths ./ max (box))) / 8), 1, 2); cap_x(2, :) += repmat (((capwid * box .* (widths ./ max (box))) / 8), 1, 2); else cap_x(1, :) -= repmat ((capwid * widths / 8), 1, 2); cap_x(2, :) += repmat ((capwid * widths / 8), 1, 2); endif cap_y = whisker_y([1, 1], :); ## Calculate coordinates for outlier tags outliers_tags_x = outliers_x + 0.08; outliers_tags_y = outliers_y; outliers2_tags_x = outliers2_x + 0.08; outliers2_tags_y = outliers2_y; ## Do the plot hold_status = ishold (); if (orientation) ## Define outlier_tags' vertical alignment outlier_tags_alignment = {'horizontalalignment', 'left'}; if (box_style) f = fillbox (quartile_x, quartile_y, bcolor); endif h = plot (quartile_x, quartile_y, 'b;;', whisker_x, whisker_y, 'b;;', cap_x, cap_y, 'b;;', median_x, median_y, 'r;;', outliers_x, outliers_y, [symbol(1), 'r;;'], outliers2_x, outliers2_y, [symbol(2), 'r;;'], plot_opts{:}); ## Print outlier tags if (outlier_tags == 1 && outliers_x > 0) t1 = plot_tags (outliers_tags_x, outliers_tags_y, outliers_idx, outliers_IDs, sample_IDs, outlier_tags_alignment); endif if (outlier_tags == 1 && outliers2_x > 0) t2 = plot_tags (outliers2_tags_x, outliers2_tags_y, outliers2_idx, outliers2_IDs, sample_IDs, outlier_tags_alignment); endif else ## Define outlier_tags' horizontal alignment outlier_tags_alignment = {'horizontalalignment', 'left', 'rotation', 90}; if (box_style) f = fillbox (quartile_y, quartile_x, bcolor); endif h = plot (quartile_y, quartile_x, 'b;;', whisker_y, whisker_x, 'b;;', cap_y, cap_x, 'b;;', median_y, median_x, 'r;;', outliers_y, outliers_x, [symbol(1), 'r;;'], outliers2_y, outliers2_x, [symbol(2), 'r;;'], plot_opts{:}); ## Print outlier tags if (outlier_tags == 1 && outliers_x > 0) t1 = plot_tags (outliers_tags_y, outliers_tags_x, outliers_idx, outliers_IDs, sample_IDs, outlier_tags_alignment); endif if (outlier_tags == 1 && outliers2_x > 0) t2 = plot_tags (outliers2_tags_y, outliers2_tags_x, outliers2_idx, outliers2_IDs, sample_IDs, outlier_tags_alignment); endif endif ## Distribute the handles that plot returned. They come back in the order ## the segments were passed and a segment with no columns contributes none, ## so walk them with a running offset. Chaining each block off the last index ## of the one before it broke as soon as a block was empty: a single-point, ## an all-NaN, or an empty data set leaves no box at all, and the chain then ## indexed the previous block at zero. n_box = columns (quartile_x); n_whis = 2 * columns (whisker_x); # the caps repeat the whisker columns n_med = columns (median_x); ## The outliers of every group are plotted as one series, so they account for ## a single handle when there are any. Counting their columns would say one ## for the 0x1 empty they collapse to when there are none. n_out = double (! isempty (outliers_y)); n_out2 = double (! isempty (outliers2_y)); used = 0; ## Box outlines and box fill (if any). The fill follows the boxes that were ## actually drawn, which is not the number of groups once any were chopped. hs.box = h(used + [1 : n_box]); used += n_box; if (box_style) hs.box_fill = f(1 : n_box); else hs.box_fill = []; endif ## Whiskers (including caps) and median lines hs.whisker = h(used + [1 : n_whis]); used += n_whis; hs.median = h(used + [1 : n_med]); used += n_med; ## Outliers (if any) and their respective tags (if applicable) if (n_out > 0) hs.outliers = h(used + [1 : n_out]); used += n_out; if (outlier_tags == 1) hs.out_tags = t1(1 : length (outliers_tags_y)); else hs.out_tags = []; endif else hs.outliers = []; hs.out_tags = []; endif ## Extreme outliers (if any) and their respective tags (if applicable) if (n_out2 > 0) hs.outliers2 = h(used + [1 : n_out2]); used += n_out2; if (outlier_tags == 1) hs.out_tags2 = t2(1 : length (outliers2_tags_y)); else hs.out_tags2 = []; endif else hs.outliers2 = []; hs.out_tags2 = []; endif ## Redraw the median lines to avoid colour overlapping in case of 'filled' ## BoxStyle if (box_style) set (hs.median, 'color', 'r'); endif ## Print labels according to orientation and return handle if (orientation) set (gca (), 'xtick', groups, 'xticklabel', labels); hs.labels = get (gcf, 'currentaxes'); else set (gca (), 'ytick', groups, 'yticklabel', labels); hs.labels = get (gcf, 'currentaxes'); endif ## retain original ishold status. if (! hold_status) hold off; endif ## return output arguments if desired. if (nargout >= 1) s_o = s; endif if (nargout == 2) hs_o = hs; endif endfunction function htags = plot_tags (out_tags_x, out_tags_y, out_idx, out_IDs, ... sample_IDs, opt) for i=1 : length (out_tags_x) if (! isempty (sample_IDs)) htags(i) = text (out_tags_x(i), out_tags_y(i), out_IDs{i}, opt{:}); else htags(i) = text (out_tags_x(i), out_tags_y(i), num2str (out_idx(i)), ... opt{:}); endif endfor endfunction function f = fillbox (quartile_y, quartile_x, bcolor) f = []; for icol = 1 : columns (quartile_x) if (ischar (bcolor)) f = [ f; fill(quartile_y(:, icol), quartile_x(:, icol), ... bcolor(mod (icol - 1, numel (bcolor)) + 1)) ]; else f = [ f; fill(quartile_y(:, icol), quartile_x(:, icol), ... bcolor(mod (icol - 1, size (bcolor, 1)) + 1, :)) ]; endif hold on; endfor endfunction %!demo %! rng (42); %! axis ([0, 3]); %! girls = randn (10, 1) * 5 + 140; %! boys = randn (13, 1) * 8 + 135; %! boxplot ({girls, boys}); %! set (gca (), 'xtick', [1 2], 'xticklabel', {'girls', 'boys'}) %! title ('Grade 3 heights'); %!demo %! rng (42); %! A = randn (10, 1) * 5 + 140; %! B = randn (25, 1) * 8 + 135; %! C = randn (20, 1) * 6 + 165; %! data = [A; B; C]; %! groups = [(ones (10, 1)); (ones (25, 1) * 2); (ones (20, 1) * 3)]; %! labels = {'Team A', 'Team B', 'Team C'}; %! pos = [2, 1, 3]; %! boxplot (data, groups, 'Notch', 'on', 'Labels', labels, 'Positions', pos, ... %! 'OutlierTags', 'on', 'BoxStyle', 'filled'); %! title ('Example of Group splitting with paired vectors'); %!demo %! rng (42); %! data = randn (100, 9); %! boxplot (data, 'notch', 'on', 'boxstyle', 'filled', ... %! 'colors', 'ygcwkmb', 'whisker', 1.2); %! title ('Example of different colors specified with characters'); %!demo %! rng (42); %! data = randn (100, 13); %! colors = [0.7 0.7 0.7; ... %! 0.0 0.4 0.9; ... %! 0.7 0.4 0.3; ... %! 0.7 0.1 0.7; ... %! 0.8 0.7 0.4; ... %! 0.1 0.8 0.5; ... %! 0.9 0.9 0.2]; %! boxplot (data, 'notch', 'on', 'boxstyle', 'filled', ... %! 'colors', colors, 'whisker', 1.3, 'boxwidth', 'proportional'); %! title ('Example of different colors specified as RGB values'); %!demo %! rng (42); %! data = randn (30, 1); %! ## Using modern string arrays %! str_groups = string (repmat (['Control'; 'TreatmentA'; 'TreatmentB'], 10, 1)); %! boxplot (data, str_groups, 'colors', 'rgb'); %! title ('Example using modern string arrays for grouping'); %!demo %! rng (42); %! data = randn (40, 1) * 5 + 50; %! ## Create two different grouping variables %! group1 = repmat ({'Alpha'; 'Beta'}, 20, 1); %! group2 = repmat ([2022; 2022; 2023; 2023], 10, 1); %! ## Pass them together as a cell array %! boxplot (data, {group1, group2}); %! title ('Example of Multiple Grouping Variables (Model & Year)'); ## Input data validation %!error boxplot ('a') %!error boxplot ({[1 2 3], 'a'}) %!error boxplot ([1 2 3], 1, {2, 3}) %!error boxplot ([1 2 3], {'a', 'b'}) %!error <'Notch' input argument accepts> boxplot ([1:10], 'notch', 'any') %!error boxplot ([1:10], 'notch', i) %!error boxplot ([1:10], 'notch', {}) %!error boxplot (1, 'symbol', 1) %!error <'Orientation' input argument accepts only> boxplot (1, 'orientation', 'diagonal') %!error boxplot (1, 'orientation', {}) %!error <'Whisker' input argument accepts only> boxplot (1, 'whisker', 'a') %!error <'Whisker' input argument accepts only> boxplot (1, 'whisker', [1 3]) %!error <'OutlierTags' input argument accepts only> boxplot (3, 'OutlierTags', 'maybe') %!error boxplot (3, 'OutlierTags', {}) %!error <'Sample_IDs' input argument accepts only> boxplot (1, 'sample_IDs', 1) %!error <'BoxWidth' input argument accepts only> boxplot (1, 'boxwidth', 2) %!error <'BoxWidth' input argument accepts only> boxplot (1, 'boxwidth', 'anything') %!error <'Widths' input argument accepts only> boxplot (5, 'widths', 'a') %!error <'Widths' input argument accepts only> boxplot (5, 'widths', [1:4]) %!error <'Widths' input argument accepts only> boxplot (5, 'widths', []) %!error <'CapWidths' input argument accepts only> boxplot (5, 'capwidths', 'a') %!error <'CapWidths' input argument accepts only> boxplot (5, 'capwidths', [1:4]) %!error <'CapWidths' input argument accepts only> boxplot (5, 'capwidths', []) %!error <'BoxStyle' input argument accepts only> boxplot (1, 'Boxstyle', 1) %!error <'BoxStyle' input argument accepts only> boxplot (1, 'Boxstyle', 'garbage') %!error <'Positions' input argument accepts only> boxplot (1, 'positions', 'aa') %!error <'Labels' input argument accepts only> boxplot (3, 'labels', [1 5]) %!error <'Colors' input argument accepts only> boxplot (1, 'colors', {}) %!error <'Colors' input argument accepts only> boxplot (2, 'colors', [1 2 3 4]) %!error boxplot (randn (10, 3), 'Sample_IDs', {'a', 'b'}) %!error boxplot (rand (3, 3), [1 2]) ## Test plotting %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! [a, b] = boxplot (rand (10, 3)); %! assert_equal (size (a), [7, 3]); %! assert_equal (numel (b.box), 3); %! assert_equal (numel (b.whisker), 12); %! assert_equal (numel (b.median), 3); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! [~, b] = boxplot (rand (10, 3), 'BoxStyle', 'filled', 'colors', 'ybc'); %! assert_equal (numel (b.box_fill), 3); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! hold on %! [a, b] = boxplot (rand (10, 3)); %! assert_equal (ishold, true); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## Test multi-variable grouping. %! hf = figure ('visible', 'off'); %! unwind_protect %! data = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12]; %! g1 = [1; 1; 2; 2; 3; 3; 1; 1; 2; 2; 3; 3]; %! g2 = string ({'A'; 'B'; 'A'; 'B'; 'A'; 'B'; 'A'; 'B'; 'A'; 'B'; 'A'; 'B'}); %! g3 = categorical ({'X'; 'X'; 'Y'; 'Y'; 'Z'; 'Z'; 'X'; 'X'; 'Y'; 'Y'; 'Z'; 'Z'}); %! [a, b] = boxplot (data, {g1, g2, g3}); %! assert_equal (size (a, 2), 6); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## Test multi-variable grouping with empty intersections dropping correctly. %! hf = figure ('visible', 'off'); %! unwind_protect %! data = [1; 2; 3; 4]; %! g1 = [1; 1; 2; 2]; %! g2 = string ({'A'; 'A'; 'B'; 'B'}); %! [a, b] = boxplot (data, {g1, g2}); %! assert_equal (size (a, 2), 2); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Inputs that used to die inside boxplot rather than be plotted or refused. %!test %! ## A single observation is plotted, not an internal subscript error. Every %! ## statistic collapses to the value, as it does in MATLAB. %! hf = figure ('visible', 'off'); %! unwind_protect %! [s, hs] = boxplot (5); %! assert_equal (s, 5 * ones (7, 1)); %! assert_equal (isempty (hs.box), true); %! assert_equal (numel (hs.outliers), 1); %! assert_equal (get (hs.outliers, 'YData'), 5); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## An empty input draws nothing and returns no statistics, as in MATLAB, %! ## rather than indexing an empty handle list at zero. %! hf = figure ('visible', 'off'); %! unwind_protect %! [s, hs] = boxplot ([]); %! assert_equal (size (s, 2), 0); %! assert_equal (isempty (hs.box), true); %! assert_equal (isempty (hs.whisker), true); %! assert_equal (isempty (hs.median), true); %! assert_equal (isempty (hs.outliers), true); %! clf; %! [s, hs] = boxplot ({}); %! assert_equal (size (s, 2), 0); %! assert_equal (isempty (hs.box), true); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## A variable that is entirely missing leaves no box, which used to take the %! ## handle bookkeeping with it. %! hf = figure ('visible', 'off'); %! unwind_protect %! [s, hs] = boxplot ([NaN; NaN; NaN]); %! assert_equal (all (isnan (s)), true); %! assert_equal (isempty (hs.box), true); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## Integer observations are accepted and agree with the same numbers in %! ## double; the internal call to var used to refuse them outright. %! hf = figure ('visible', 'off'); %! unwind_protect %! sd = boxplot ([1; 2; 3; 4; 5; 6; 7]); %! clf; %! si = boxplot (int32 ([1; 2; 3; 4; 5; 6; 7])); %! clf; %! su = boxplot (uint8 ([1; 2; 3; 4; 5; 6; 7])); %! assert_equal (si, sd); %! assert_equal (su, sd); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## The same holds for an integer variable inside a cell. %! hf = figure ('visible', 'off'); %! unwind_protect %! sd = boxplot ({[1; 2; 3; 4; 5], [2; 3; 4; 5; 6]}); %! clf; %! si = boxplot ({int32([1; 2; 3; 4; 5]), int32([2; 3; 4; 5; 6])}); %! assert_equal (si, sd); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## A group holding a single observation leaves fewer boxes than groups. The %! ## cap widths were built from the full-length widths vector against the %! ## chopped box vector, so the subtraction did not conform. %! hf = figure ('visible', 'off'); %! unwind_protect %! data = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 42]; %! grp = [1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 2]; %! [s, hs] = boxplot (data, grp); %! assert_equal (size (s, 2), 2); %! assert_equal (s(:, 2), 42 * ones (7, 1)); %! assert_equal (numel (hs.box), 1); %! assert_equal (numel (hs.median), 1); %! assert_equal (numel (hs.whisker), 4); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## The box fill follows the boxes actually drawn, not the number of groups. %! hf = figure ('visible', 'off'); %! unwind_protect %! data = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 42]; %! grp = [1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 2]; %! [s, hs] = boxplot (data, grp, 'BoxStyle', 'filled'); %! assert_equal (numel (hs.box_fill), numel (hs.box)); %! assert_equal (get (hs.box_fill(1), 'Type'), 'patch'); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## Both kinds of outlier keep their own handles, and the ones after them %! ## stay in step. %! hf = figure ('visible', 'off'); %! unwind_protect %! data = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 42]; %! grp = [1; 1; 1; 1; 1; 1; 1; 1; 1; 1; 2]; %! [s, hs] = boxplot ([data; 60], [grp; 1]); %! assert_equal (numel (hs.outliers), 1); %! assert_equal (numel (hs.outliers2), 1); %! assert_equal (get (hs.median, 'Type'), 'line'); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Logical and character data stay refused, as they are in MATLAB. %!error ... %! boxplot ([true; false; true; true]) %!error ... %! boxplot ('abcde') ## Quartiles follow quantile's own default method, which is prctile's and ## MATLAB's. Values below are MATLAB R2024a's, read off the box, whisker and ## outlier objects it draws. %!test %! ## The box edges are the 25th and 75th percentiles, for odd and even counts %! ## alike, and agree with prctile called on the same data. %! hf = figure ('visible', 'off'); %! unwind_protect %! s = boxplot ((1:7)'); %! assert_equal (s(1:5)', [1, 2.25, 4, 5.75, 7]); %! clf; %! s = boxplot ((1:8)'); %! assert_equal (s(1:5)', [1, 2.5, 4.5, 6.5, 8]); %! clf; %! x = [2; 4; 4; 4; 5; 5; 7; 9]; %! s = boxplot (x); %! assert_equal (s(1:5)', [2, 4, 4.5, 6, 9]); %! assert_equal (s(2:4)', [prctile(x, 25), median(x), prctile(x, 75)]); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## Small samples, where the two quantile definitions differ most. %! hf = figure ('visible', 'off'); %! unwind_protect %! s = boxplot ([1; 2]); %! assert_equal (s(1:5)', [1, 1, 1.5, 2, 2]); %! clf; %! s = boxplot ([1; 2; 3]); %! assert_equal (s(1:5)', [1, 1.25, 2, 2.75, 3]); %! clf; %! s = boxplot ([1; 2; 3; 4]); %! assert_equal (s(1:5)', [1, 1.5, 2.5, 3.5, 4]); %! clf; %! s = boxplot ([1; 2; 3; 4; 5]); %! assert_equal (s(1:5)', [1, 1.75, 3, 4.25, 5]); %! clf; %! s = boxplot ([1; 2; 3; 4; 5; 6]); %! assert_equal (s(1:5)', [1, 2, 3.5, 5, 6]); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## Missing values are dropped before the quartiles are taken. %! hf = figure ('visible', 'off'); %! unwind_protect %! s = boxplot ([1; 2; NaN; 4; 5; 6; 7]); %! assert_equal (s(1:5)', [1, 2, 4.5, 6, 7]); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## The inter-quartile range sets the fences, so the quartile definition %! ## decides which points are outliers. 33 lies beyond the fence and 31.5 %! ## does not; the old quartiles put the fence between them and called both %! ## outliers. %! hf = figure ('visible', 'off'); %! unwind_protect %! [s, hs] = boxplot ([(1:20)'; 31.5]); %! assert_equal (s(1:5)', [1, 5.75, 11, 16.25, 31.5]); %! assert_equal (isempty (hs.outliers), true); %! assert_equal (isempty (hs.outliers2), true); %! clf; %! [s, hs] = boxplot ([(1:20)'; 33]); %! assert_equal (s(1:5)', [1, 5.75, 11, 16.25, 33]); %! assert_equal (isempty (hs.outliers) && isempty (hs.outliers2), false); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## When no observation lies between a quartile and its fence the whisker %! ## collapses onto the quartile instead of being drawn back into the box. %! hf = figure ('visible', 'off'); %! unwind_protect %! [s, hs] = boxplot ([1; 1; 2; 2; 3; 3; 4; 40; 50]); %! assert_equal (s(1:5)', [1, 1.75, 3, 13, 50]); %! yd = get (hs.whisker, 'YData'); %! assert_equal (max ([yd{3}, yd{4}]), 13); %! clf; %! [s, hs] = boxplot ([-50; -40; -4; -3; -3; -2; -2; -1; -1]); %! assert_equal (s(1:5)', [-50, -13, -3, -1.75, -1]); %! yd = get (hs.whisker, 'YData'); %! assert_equal (min ([yd{1}, yd{2}]), -13); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! ## A whisker that does reach real data is not clamped. %! hf = figure ('visible', 'off'); %! unwind_protect %! [s, hs] = boxplot ([-50; -40; -4; -3; -3; -2; -2; 40; 50]); %! assert_equal (s(1:5)', [-50, -13, -3, 8.5, 50]); %! yd = get (hs.whisker, 'YData'); %! assert_equal (min ([yd{1}, yd{2}]), -40); %! assert_equal (max ([yd{3}, yd{4}]), 40); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect statistics-release-1.9.2/inst/Plotting/cdfplot.m000066400000000000000000000074211524624707500217410ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{hCDF} =} cdfplot (@var{x}) ## @deftypefnx {statistics} {[@var{hCDF}, @var{stats}] =} cdfplot (@var{x}) ## ## Display an empirical cumulative distribution function. ## ## @code{@var{hCDF} = cdfplot (@var{x})} plots an empirical cumulative ## distribution function (CDF) of the observations in the data sample vector ## @var{x}. @var{x} may be a row or column vector, and represents a random ## sample of observations from some underlying distribution. ## ## @code{cdfplot} plots F(x), the empirical (or sample) CDF versus the ## observations in @var{x}. The empirical CDF, F(x), is defined as follows: ## ## F(x) = (Number of observations <= x) / (Total number of observations) ## ## for all values in the sample vector @var{x}. NaNs are ignored. @var{hCDF} ## is the handle of the empirical CDF curve (a handle graphics 'line' object). ## ## @code{[@var{hCDF}, @var{stats}] = cdfplot (@var{x})} also returns a structure ## with the following fields as a statistical summary. ## ## @multitable @columnfractions 0.3 0.65 ## @item STATS.min @tab minimum value of @var{x} ## @item STATS.max @tab maximum value of @var{x} ## @item STATS.mean @tab sample mean of @var{x} ## @item STATS.median @tab sample median (50th percentile) of @var{x} ## @item STATS.std @tab sample standard deviation of @var{x} ## @end multitable ## ## @seealso{qqplot, cdfcalc} ## @end deftypefn function [hCDF, stats] = cdfplot (x) ## Check number of input arguments narginchk (1,1); ## Calculate sample cdf [yy, xx, ~, ~, eid] = cdfcalc (x); ## Check for errors returned from cdfcalc if (strcmpi (eid, 'VectorRequired')) error ("cdfplot: vector required as input."); elseif (strcmpi (eid, 'NotEnoughData')) error ("cdfplot: not enough data."); endif ## Create vectors for plotting k = length (xx); n = reshape (repmat (1:k, 2, 1), 2*k, 1); xCDF = [-Inf; xx(n); Inf]; yCDF = [0; 0; yy(1+n)]; ## Plot cdf h = plot (xCDF, yCDF); grid ('on') xlabel ('x') ylabel ('F(x)') title ('CDF plot of x'); ## Return requested output arguments if (nargout > 0) hCDF = h; endif if (nargout > 1) stats.min = nanmin (x); stats.max = nanmax (x); stats.mean = mean (x, 'omitnan'); stats.median = median (x, 'omitnan'); stats.std = std (x, 'omitnan'); endif endfunction %!demo %! rng (42); %! x = randn (100,1); %! cdfplot (x); ## Test results %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! x = [2, 4, 3, 2, 4, 3, 2, 5, 6, 4]; %! [hCDF, stats] = cdfplot (x); %! assert_equal (stats.min, 2); %! assert_equal (stats.max, 6); %! assert_equal (stats.median, 3.5); %! assert_equal (stats.std, 1.35400640077266, 1e-14); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! x = randn (100,1); %! cdfplot (x); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error cdfplot (); %!error cdfplot ([x',x']); %!error cdfplot ([NaN, NaN, NaN, NaN]); statistics-release-1.9.2/inst/Plotting/dendrogram.m000066400000000000000000000337751524624707500224430ustar00rootroot00000000000000## Copyright (c) 2012 Juan Pablo Carbajal ## Copyright (C) 2021 Stefano Guidoni ## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} dendrogram (@var{tree}) ## @deftypefnx {statistics} {} dendrogram (@var{tree}, @var{p}) ## @deftypefnx {statistics} {} dendrogram (@var{tree}, @var{prop}, @var{val}) ## @deftypefnx {statistics} {} dendrogram (@var{tree}, @var{p}, @var{prop}, @var{val} ) ## @deftypefnx {statistics} {@var{h} =} dendrogram (@dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{t}, @var{perm}] =} dendrogram (@dots{}) ## ## Plot a dendrogram of a hierarchical binary cluster tree. ## ## Given @var{tree}, a hierarchical binary cluster tree as the output of ## @code{linkage}, plot a dendrogram of the tree. The number of leaves shown by ## the dendrogram plot is limited to @var{p}. The default value for @var{p} is ## 30. Set @var{p} to 0 to plot all leaves. ## ## The optional outputs are @var{h}, @var{t} and @var{perm}: ## @itemize @bullet ## @item @var{h} is a handle to the lines of the plot. ## ## @item @var{t} is the vector with the numbers assigned to each leaf. ## Each element of @var{t} is a leaf of @var{tree} and its value is the number ## shown in the plot. ## When the dendrogram plot is collapsed, that is when the number of shown ## leaves @var{p} is inferior to the total number of leaves, a single leaf of ## the plot can represent more than one leaf of @var{tree}: in that case ## multiple elements of @var{t} share the same value, that is the same leaf of ## the plot. ## When the dendrogram plot is not collapsed, each leaf of the plot is the leaf ## of @var{tree} with the same number. ## ## @item @var{perm} is the vector list of the leaves as ordered as in the plot. ## @end itemize ## ## Additional input properties can be specified by pairs of properties and ## values. Known properties are: ## @itemize @bullet ## @item @qcode{'Reorder'} ## Reorder the leaves of the dendrogram plot using a numerical vector of size n, ## the number of leaves. When @var{p} is smaller than @var{n}, the reordering ## cannot break the @var{p} groups of leaves. ## ## @item @qcode{'Orientation'} ## Change the orientation of the plot. Available values: @qcode{top} (default), ## @qcode{bottom}, @qcode{left}, @qcode{right}. ## ## @item @qcode{'CheckCrossing'} ## Check if the lines of a reordered dendrogram cross each other. Available ## values: @qcode{true} (default), @qcode{false}. ## ## @item @qcode{'ColorThreshold'} ## Not implemented. ## ## @item @qcode{'Labels'} ## Use a char, string or cellstr array of size @var{n} to set the label for each ## leaf; the label is displayed only for nodes with just one leaf. ## @end itemize ## ## @seealso{cluster, clusterdata, cophenet, inconsistent, linkage, pdist} ## @end deftypefn function [H, T, perm] = dendrogram (tree, varargin) [m, d] = size (tree); if ((d != 3) || (! isnumeric (tree)) || (! (max (tree(end, 1:2)) == m * 2))) error (strcat ("dendrogram: tree must be a matrix as generated", ... " by the linkage function.")); endif pair_index = 1; ## Node count n = m + 1; ## Add default values P = 30; vReorder = []; csLabels = {}; checkCrossing = 1; orientation = 'top'; if (nargin > 1) if (isnumeric (varargin{1}) && isscalar (varargin{1})) ## dendrogram (tree, P) P = varargin{1}; pair_index++; endif ## dendrogram (..., Name, Value) while (pair_index < (nargin - 1)) switch (lower (varargin{pair_index})) case 'reorder' if (isvector (varargin{pair_index + 1}) && isnumeric (varargin{pair_index + 1}) && length (varargin{pair_index + 1}) == n ) vReorder = varargin{pair_index + 1}; else error (strcat ("dendrogram: 'reorder' must be a numeric", ... " vector of size n, the number of leaves.")); endif case 'checkcrossing' if (ischar (varargin{pair_index + 1})) switch (lower (varargin{pair_index + 1})) case 'true' checkCrossing = 1; case 'false' checkCrossing = 0; otherwise error ("dendrogram: unknown value '%s' for CheckCrossing.", ... varargin{pair_index + 1}); endswitch else error (strcat ("dendrogram: 'CheckCrossing' must be", ... " either 'true' or 'false'.")); endif case 'colorthreshold' warning ("dendrogram: property '%s' not implemented.",... varargin{pair_index}); case 'orientation' orientation = varargin{pair_index + 1}; # validity check below case 'labels' if (ischar (varargin{pair_index + 1}) && (isvector (varargin{pair_index + 1}) && length (varargin{pair_index + 1}) == n) || (ismatrix (varargin{pair_index + 1}) && rows (varargin{pair_index + 1}) == n)) csLabels = cellstr (varargin{pair_index + 1}); elseif (iscellstr (varargin{pair_index + 1}) && length (varargin{pair_index + 1}) == n) csLabels = varargin{pair_index + 1}; else error (strcat ("dendrogram: labels must be a char or", ... " string or cellstr array of size n.")); endif otherwise error ("dendrogram: unknown property '%s'.", varargin{pair_index}); endswitch pair_index += 2; endwhile endif ## MATLAB compatibility: ## P <= 0 to plot all leaves if (P < 1) P = n; endif if (n > P) level_0 = tree((n - P), 3); else P = n; level_0 = 0; endif vLeafPosition = zeros ((n + m), 1); T = (1:n)'; nodecnt = 1; ## main dendrogram_recursive (m, 0); ## T reordering ## MATLAB compatibility: when n > P, each node group is renamed with a number ## between 1 and P, according to the smallest node index of each group; ## the group with the node 1 is always group 1, while group 2 is the group ## with the smallest node index outside of group 1, and group 3 is the group ## with the smallest node index outside of groups 1 and 2... newT = 1 : (length (T)); if (n > P) uniqueT = unique (T); minT = zeros (size (uniqueT)); counter = 1; for i = 1:length (uniqueT) # it should be exactly equal to P idcs = find (T == uniqueT(i)); minT(i) = min (idcs); endfor minT = minT(find (minT > 0)); # to prevent a strange bug [minT, minTidcs] = sort (minT); uniqueT = uniqueT(minTidcs); for i = 1:length (uniqueT) idcs = find (T == uniqueT(i)); newT(idcs) = counter++; endfor endif ## leaf reordering if (! isempty (vReorder)) if (P < n) checkT = newT(vReorder(:)); for i = 1 : P idcs = find (checkT == i); if (length (idcs) > 1) if (max (idcs) - min (idcs) >= length (idcs)) error (strcat ("dendrogram: invalid reordering that", ... " redefines the 'P' groups of leaves")); endif endif endfor checkT = unique (checkT, 'stable'); vNewLeafPosition = zeros (n, 1); uT = unique (T, 'stable'); for i = 1:P vNewLeafPosition(uT(checkT(i))) = i; endfor vLeafPosition = vNewLeafPosition; else for i = 1:length (vReorder) vLeafPosition(vReorder(i)) = i; endfor endif endif ## figure x = []; ## ticks and tricks xticks = 1:P; perm = zeros (P, 1); for i = 1 : length (vLeafPosition) if (vLeafPosition(i) != 0) idcs = find (T == i); perm(vLeafPosition(i)) = newT(idcs(1)); endif endfor T = newT; # this should be unnecessary for n <= P ## lines for i = (n - P + 1):m vLeafPosition(n + i) = mean (vLeafPosition(tree(i, 1:2), 1)); x(end + 1,1:4) = [vLeafPosition(tree(i, 1:2))' tree(i, [3 3])]; for j = 1 : 2 x0 = 0; if (tree(i,j) > (2 * n - P)) x0 = tree(tree(i, j) - n, 3); endif x(end + 1, 1:4) = [vLeafPosition(tree(i, [j j]))' x0 tree(i, 3)]; endfor endfor ## plot stuff if (strcmp (orientation, 'top')) H = line (x(:, 1:2)', x(:, 3:4)', 'color', 'blue'); set (gca, 'xticklabel', perm, 'xtick', xticks); elseif (strcmp (orientation, 'bottom')) H = line (x(:, 1:2)', x(:, 3:4)', 'color', 'blue'); set (gca, 'xticklabel', perm, 'xtick', xticks, 'xaxislocation', 'top'); axis ('ij'); elseif (strcmp (orientation, 'left')) H = line (x(:, 3:4)', x(:, 1:2)', 'color', 'blue'); set (gca, 'yticklabel', perm, 'ytick', xticks, 'xdir', 'reverse',... 'yaxislocation', 'right'); elseif (strcmp (orientation, 'right')) H = line (x(:, 3:4)', x(:, 1:2)', 'color', 'blue'); set (gca, 'yticklabel', perm, 'ytick', xticks); else close (H); error ("dendrogram: invalid orientation '%s'", orientation); endif ## labels if (! isempty (csLabels)) csCurrent = cellstr (num2str (perm)); for i = 1:n ## when there is just one leaf, use the named label for that leaf if (1 == length (find (T == i))) csCurrent(find (perm == i)) = csLabels(find (T == i)); endif endfor switch (orientation) case {'top', 'bottom'} xticklabels (csCurrent); case {'left', 'right'} yticklabels (csCurrent); endswitch endif ## check crossings if (checkCrossing && ! isempty (vReorder)) for j = 1:rows (x) if (x(j, 3) == x(j, 4)) # an horizontal line for i = 1:rows (x) if (x(i, 1) == x(i, 2) && ... # orthogonal lines (x(i, 1) > x(j, 1) && x(i, 1) < x(j, 2)) && ... (x(j, 3) > x(i, 3) && x(j, 3) < x(i, 4))) warning ("dendrogram: line intersection detected"); endif endfor endif endfor endif ## dendrogram_recursive function dendrogram_recursive (k, cn) if (tree(k, 3) > level_0) for j = 1:2 if (tree(k, j) > n) dendrogram_recursive (tree(k, j) - n, 0) else vLeafPosition(tree(k, j)) = nodecnt++; T(tree(k, j)) = tree(k, j); endif endfor else for j = 1:2 if (cn == 0) cn = n + k; vLeafPosition(cn) = nodecnt++; endif if (tree(k, j) > n) dendrogram_recursive (tree(k, j) - n, cn) else T(tree(k, j)) = cn; endif endfor endif endfunction endfunction %!demo %! ## simple dendrogram %! y = [4, 5; 2, 6; 3, 7; 8, 9; 1, 10]; %! y(:,3) = 1:5; %! dendrogram (y); %! title ('simple dendrogram'); %!demo %! ## another simple dendrogram %! rng (42); %! v = 2 * rand (30, 1) - 1; %! d = abs (bsxfun (@minus, v(:, 1), v(:, 1)')); %! y = linkage (squareform (d, 'tovector')); %! dendrogram (y); %! title ('another simple dendrogram'); %!demo %! ## collapsed tree, find all the leaves of node 5 %! rng (42); %! X = randn (60, 2); %! D = pdist (X); %! y = linkage (D, 'average'); %! subplot (2, 1, 1); %! title ('original tree'); %! dendrogram (y, 0); %! subplot (2, 1, 2); %! title ('collapsed tree'); %! [~, t] = dendrogram (y, 20); %! find (t == 5) %!demo %! ## optimal leaf order %! rng (42); %! X = randn (30, 2); %! D = pdist (X); %! y = linkage (D, 'average'); %! order = optimalleaforder (y, D); %! subplot (2, 1, 1); %! title ('original leaf order'); %! dendrogram (y); %! subplot (2, 1, 2); %! title ('optimal leaf order'); %! dendrogram (y, 'Reorder', order); %!demo %! ## horizontal orientation and labels %! rng (42); %! X = randn (8, 2); %! D = pdist (X); %! L = ['Snow White'; 'Doc'; 'Grumpy'; 'Happy'; 'Sleepy'; 'Bashful'; ... %! 'Sneezy'; 'Dopey']; %! y = linkage (D, 'average'); %! dendrogram (y, 'Orientation', 'left', 'Labels', L); %! title ('horizontal orientation and labels'); ## Test plotting %!shared visibility_setting %! visibility_setting = get (0, 'DefaultFigureVisible'); %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! y = [4, 5; 2, 6; 3, 7; 8, 9; 1, 10]; %! y(:,3) = 1:5; %! dendrogram (y); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! y = [4, 5; 2, 6; 3, 7; 8, 9; 1, 10]; %! y(:,3) = 1:5; %! dendrogram (y); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! v = 2 * rand (30, 1) - 1; %! d = abs (bsxfun (@minus, v(:, 1), v(:, 1)')); %! y = linkage (squareform (d, 'tovector')); %! dendrogram (y); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! X = randn (30, 2); %! D = pdist (X); %! y = linkage (D, 'average'); %! order = optimalleaforder (y, D); %! subplot (2, 1, 1); %! title ('original leaf order'); %! dendrogram (y); %! subplot (2, 1, 2); %! title ('optimal leaf order'); %! dendrogram (y, 'Reorder', order); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error dendrogram (); %!error dendrogram (ones (2, 2), 1); %!error dendrogram ([1 2 1], 1, 'xxx', 'xxx'); %!error dendrogram ([1 2 1], 'Reorder', 'xxx'); %!error dendrogram ([1 2 1], 'Reorder', [1 2 3 4]); %! fail ('dendrogram ([1 2 1], "Orientation", "north")', 'invalid orientation .*') statistics-release-1.9.2/inst/Plotting/doc-cache000066400000000000000000001527671524624707500216770ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 24 # name: # type: sq_string # elements: 1 # length: 11 andrewsplot # name: # type: sq_string # elements: 1 # length: 1524 statistics: andrewsplot ( x ) statistics: andrewsplot ( x , name , value , …) statistics: andrewsplot ( ax , …) statistics: h = andrewsplot (…) Create an Andrews plot of the multivariate data in x . andrewsplot ( x ) plots each observation (row) of the n -by- p matrix x as a smooth curve defined by the finite Fourier series f_i(t) = x_i1/sqrt(2) + x_i2 sin(2*pi*t) + x_i3 cos(2*pi*t) + x_i4 sin(4*pi*t) + x_i5 cos(4*pi*t) + … evaluated over t in the interval [0,1] , where x_ij is the j -th variable of the i -th observation. The following name/value pairs are accepted: "Group" A grouping variable (numeric, logical, character, string, or cell array of strings) with one entry per row of x . Curves are colored by group. "Standardize" Controls how the columns of x are transformed before the curves are computed: "off" (default) uses the raw data, "on" centers and scales each column to zero mean and unit standard deviation, "PCA" uses the principal component scores, and "PCAStd" uses the principal component scores of the standardized data. "Quantile" A scalar alpha in the interval (0,1) . Instead of one curve per observation, only three curves per group are drawn: the pointwise median and the alpha and 1- alpha quantiles of the group’s curves. andrewsplot ( ax , …) plots into the axes ax . The optional output h is a vector of handles to the plotted lines: one per observation, or three per group when "Quantile" is used. See also: parallelcoords, glyphplot, pca # name: # type: sq_string # elements: 1 # length: 53 Create an Andrews plot of the multivariate data in x. # name: # type: sq_string # elements: 1 # length: 4 bar3 # name: # type: sq_string # elements: 1 # length: 3699 statistics: bar3 ( z ) statistics: bar3 ( y , z ) statistics: bar3 (…, width ) statistics: bar3 (…, style ) statistics: bar3 (…, color ) statistics: bar3 (…, name , value ) statistics: bar3 ( ax , …) statistics: p = bar3 (…) Plot a 3D bar graph. bar3 ( z ) plots 3D bar graph for the elements of z . Each bar corresponds to an element in z , which can be a scalar, vector, or 2D matrix. By default, each column in z is considered as a series and it is handled as a distinct series of bars. When z is a vector, unlike MATLAB, which plots it as a single series of bars, Octave discriminates between a row and column vector of z . Hence, when z is column vector, it is plotted as a single series of bars (same color), whereas when z is row vector, each bar is plotted as a different group (different colors). For an M×N matrix, the function plots the bars corresponding to each row on the y-axis ranging from 1 to M and each column on the x-axis ranging from 1 to N . bar3 ( y , z ) plots a 3D bar graph of the elements in z at the y-values specified in y . It should be noted that y only affects the tick names along the y-axis rather the actual values. If you want to specify non-numerical values for y , you can specify it with the paired name / value syntax shown below. bar3 (…, width ) sets the width of the bars along the x- and y-axes and controls the separation of bars among each other. width can take any value in the range (0,1] . By default, width is 0.8 and the bars have a small separation. If width is 1, the bars touch one another. Alternatively, you can define width as a two- element vector using the paired name / value syntax shown below, in which case you can control the bar separation along each axis independently. bar3 (…, style ) specifies the style of the bars, where style can be 'detached' , 'grouped' , or 'stacked' . The default style is 'detached' . bar3 (…, color ) displays all bars using the color specified by color. For example, use 'red' or 'r' to specify all red bars. When you want to specify colors for several groups, color can be a cellstr vector with each element specifying the color of each group. color can also be specified as a numerical M×3 matrix, where each row corresponds to a RGB value with its elements in the range [0,1] . If only one color is specified, then it applies to all bars. If the number of colors equals the number of groups, then each color is applied to each group. If the number of colors equals the number of elements in z , then each individual bar is assigned the particular color. You can also define color using the paired name / value syntax shown below. bar3 (…, name , value ) specifies one or more of the following name/value pairs: Name Value 'width' A two-element vector specifying the width of the bars along the x- and y-axes , respectively. Each element must be in the range (0,1] . 'color' A character or a cellstr vector, or a numerical M×3 matrix following the same conventions as the color input argument. 'xlabel' A cellstr vector specifying the group names along the x-axis . 'ylabel' A cellstr vector specifying the names of the bars in the same series along the y-axis . bar3 ( ax , …) can also take an axes handle ax as a first argument in which case it plots into the axes specified by ax instead of into the current axes specified by gca () . The optional argument ax can precede any of the input argument combinations in the previous syntaxes. p = bar3 (…) returns a patch handle p , which can be used to set properties of the bars after displaying the 3D bar graph. See also: boxplot, hist3 # name: # type: sq_string # elements: 1 # length: 20 Plot a 3D bar graph. # name: # type: sq_string # elements: 1 # length: 5 bar3h # name: # type: sq_string # elements: 1 # length: 3726 statistics: bar3h ( y ) statistics: bar3h ( z , y ) statistics: bar3h (…, width ) statistics: bar3h (…, style ) statistics: bar3h (…, color ) statistics: bar3h (…, name , value ) statistics: bar3h ( ax , …) statistics: p = bar3h (…) Plot a horizontal 3D bar graph. bar3h ( y ) plots 3D bar graph for the elements of y . Each bar corresponds to an element in y , which can be a scalar, vector, or 2D matrix. By default, each column in y is considered as a series and it is handled as a distinct series of bars. When y is a vector, unlike MATLAB, which plots it as a single series of bars, Octave distinguishes between a row and column vector of y . Hence, when y is column vector, it is plotted as a single series of bars (same color), whereas when y is row vector, each bar is plotted as a different group (different colors). For an M×N matrix, the function plots the bars corresponding to each row on the z-axis ranging from 1 to M and each column on the x-axis ranging from 1 to N . bar3h ( z , y ) plots a 3D bar graph of the elements in y at the z-values specified in z . It should be noted that z only affects the tick names along the z-axis rather the actual values. If you want to specify non-numerical values for z , you can specify it with the paired name / value syntax shown below. bar3h (…, width ) sets the width of the bars along the x- and z-axes and controls the separation of bars among each other. width can take any value in the range (0,1] . By default, width is 0.8 and the bars have a small separation. If width is 1, the bars touch one another. Alternatively, you can define width as a two- element vector using the paired name / value syntax shown below, in which case you can control the bar separation along each axis independently. bar3h (…, style ) specifies the style of the bars, where style can be 'detached' , 'grouped' , or 'stacked' . The default style is 'detached' . bar3h (…, color ) displays all bars using the color specified by color. For example, use 'red' or 'r' to specify all red bars. When you want to specify colors for several groups, color can be a cellstr vector with each element specifying the color of each group. color can also be specified as a numerical M×3 matrix, where each row corresponds to a RGB value with its elements in the range [0,1] . If only one color is specified, then it applies to all bars. If the number of colors equals the number of groups, then each color is applied to each group. If the number of colors equals the number of elements in y , then each individual bar is assigned the particular color. You can also define color using the paired name / value syntax shown below. bar3h (…, name , value ) specifies one or more of the following name/value pairs: Name Value 'width' A two-element vector specifying the width of the bars along the x- and z-axes , respectively. Each element must be in the range (0,1] . 'color' A character or a cellstr vector, or a numerical M×3 matrix following the same conventions as the color input argument. 'xlabel' A cellstr vector specifying the group names along the x-axis . 'zlabel' A cellstr vector specifying the names of the bars in the same series along the z-axis . bar3h ( ax , …) can also take an axes handle ax as a first argument in which case it plots into the axes specified by ax instead of into the current axes specified by gca () . The optional argument ax can precede any of the input argument combinations in the previous syntaxes. p = bar3h (…) returns a patch handle p , which can be used to set properties of the bars after displaying the 3D bar graph. See also: boxplot, hist3 # name: # type: sq_string # elements: 1 # length: 31 Plot a horizontal 3D bar graph. # name: # type: sq_string # elements: 1 # length: 6 biplot # name: # type: sq_string # elements: 1 # length: 1853 statistics: biplot ( coefs ) statistics: biplot ( coefs , name , value , …) statistics: biplot ( ax , …) statistics: h = biplot (…) Create a biplot of the coefficients in coefs . biplot ( coefs ) plots the rows of coefs , typically the principal component coefficients returned by pca or pcacov or the factor loadings returned by factoran , as vectors from the origin. coefs has one row per observed variable and either two columns (for a 2-D biplot) or three columns (for a 3-D biplot). The following name/value pairs are accepted: "Scores" A matrix of scores with the same number of columns as coefs (one row per observation). The scores are plotted as points, scaled to fit within the unit circle relative to the maximum coefficient length: each score is divided by the maximum absolute score value and multiplied by the length of the longest coefficient vector. "VarLabels" A character array or cell array of strings labeling each variable vector. "ObsLabels" A character array or cell array of strings labeling each observation. "Positive" If true , the reference axes are drawn only over the positive quadrant (2-D) or octant (3-D). The default is false . Any additional name/value pairs are treated as line properties and applied to the variable vectors. For readability the sign of each column of coefs is chosen so that its largest-magnitude element is positive; the same sign change is applied to the corresponding column of the scores. biplot ( ax , …) plots into the axes ax . The optional output h is a column vector of handles to the plotted graphics objects, ordered as the variable vector lines, the variable markers, the variable text labels (if any), the observation markers, the observation text labels (if any), and finally the reference axis lines. See also: pca, pcacov, factoran, rotatefactors # name: # type: sq_string # elements: 1 # length: 45 Create a biplot of the coefficients in coefs. # name: # type: sq_string # elements: 1 # length: 7 boxplot # name: # type: sq_string # elements: 1 # length: 6499 statistics: s = boxplot ( data ) statistics: s = boxplot ( data , group ) statistics: s = boxplot ( data , notched , symbol , orientation , whisker , …) statistics: s = boxplot ( data , group , notched , symbol , orientation , whisker , …) statistics: s = boxplot ( data , options ) statistics: s = boxplot ( data , group , options , …) statistics: […, h ] = boxplot ( data , …) Produce a box plot. A box plot is a graphical display that simultaneously describes several important features of a data set, such as center, spread, departure from symmetry, and identification of observations that lie unusually far from the bulk of the data. Input arguments (case-insensitive) recognized by boxplot are: data is a matrix with one column for each data set, or a cell vector with one cell for each data set. Each cell must contain a numerical row or column vector (NaN and NA are ignored) and not a nested vector of cells. notched = 1 produces a notched-box plot. Notches represent a robust estimate of the uncertainty about the median. notched = 0 (default) produces a rectangular box plot. notched within the interval (0,1) produces a notch of the specified depth. Notched values outside (0,1) are amusing if not exactly impractical. symbol sets the symbol for the outlier values. The default symbol for points that lie outside 3 times the interquartile range is ’o’; the default symbol for points between 1.5 and 3 times the interquartile range is ’+’. Alternative symbol settings: symbol = ’.’: points between 1.5 and 3 times the IQR are marked with ’.’ and points outside 3 times IQR with ’o’. symbol = [’x’,’*’]: points between 1.5 and 3 times the IQR are marked with ’x’ and points outside 3 times IQR with ’*’. orientation = 0 makes the boxes horizontally. orientation = 1 plots the boxes vertically (default). Alternatively, orientation can be passed as a string, e.g., ’vertical’ or ’horizontal’. whisker defines the length of the whiskers as a function of the IQR (default = 1.5). If whisker = 0 then boxplot displays all data values outside the box using the plotting symbol for points that lie outside 3 times the IQR. group may be passed as an optional argument only in the second position after data . group can be a numeric, character, string, or categorical vector defining separate categories. To group by multiple variables simultaneously, pass a cell array of grouping vectors (e.g., {group1, group2} ). A separate box is plotted for each unique combination of group values. All grouping variables must have the same length as data . options are additional paired arguments passed with the formalism (Name, Value) that provide extra functionality as listed below. options can be passed at any order after the initial arguments and are case-insensitive. ’Notch’ ’on’ Notched by 0.25 of the boxes width. ’off’ Produces a straight box. scalar Proportional width of the notch. ’Symbol’ ’.’ Defines only outliers between 1.5 and 3 IQR. [’x’,’*’] 2nd character defines outliers > 3 IQR ’Orientation’ ’vertical’ Default value, can also be defined with numerical 1. ’horizontal’ Can also be defined with numerical 0. ’Whisker’ scalar Multiplier of IQR (default is 1.5). ’OutlierTags’ ’on’ or 1 Plot the vector index of the outlier value next to its point. ’off’ or 0 No tags are plotted (default value). ’Sample_IDs’ ’cell’ A cell vector with one cell for each data set containing a nested cell vector with each sample’s ID (should be a string). If this option is passed, then all outliers are tagged with their respective sample’s ID string instead of their vector’s index. ’BoxWidth’ ’proportional’ Create boxes with their width proportional to the number of samples in their respective dataset (default value). ’fixed’ Make all boxes with equal width. ’Widths’ scalar Scaling factor for box widths (default value is 0.4). ’CapWidths’ scalar Scaling factor for whisker cap widths (default value is 1, which results to ’Widths’/8 halflength) ’BoxStyle’ ’outline’ Draw boxes as outlines (default value). ’filled’ Fill boxes with a color (outlines are still plotted). ’Positions’ vector Numerical vector that defines the position of each data set. It must have the same length as the number of groups in a desired manner. This vector merely defines the points along the group axis, which by default is [1:number of groups]. ’Labels’ cell A cell vector of strings containing the names of each group. By default each group is labeled numerically. If multiple grouping variables are provided, default labels are automatically generated by joining the category names and stacked hierarchically. ’Colors’ character string or Nx3 numerical matrix If just one character or 1x3 vector of RGB values, specify the fill color of all boxes when BoxStyle = ’filled’. If a character string or Nx3 matrix is entered, box #1’s fill color corresponds to the first character or first matrix row, and the next boxes’ fill colors corresponds to the next characters or rows. If the char string or Nx3 array is exhausted the color selection wraps around. Supplemental arguments not described above (…) are concatenated and passed to the plot() function. The returned matrix s has one column for each data set as follows: 1 Minimum 2 1st quartile 3 2nd quartile (median) 4 3rd quartile 5 Maximum 6 Lower confidence limit for median 7 Upper confidence limit for median The quartiles are those of quantile at its default method, which is also what prctile returns, so the box edges of a data set always agree with prctile ( data , [25, 75]) . They set the inter-quartile range, and so the whisker fences and which observations are reported as outliers. The returned structure h contains handles to the plot elements, allowing customization of the visualization using set/get functions. Example title ("Grade 3 heights"); axis ([0,3]); set(gca (), "xtick", [1 2], "xticklabel", {"girls", "boys"}); boxplot ({randn(10,1)*5+140, randn(13,1)*8+135}); # name: # type: sq_string # elements: 1 # length: 19 Produce a box plot. # name: # type: sq_string # elements: 1 # length: 7 cdfplot # name: # type: sq_string # elements: 1 # length: 1047 statistics: hCDF = cdfplot ( x ) statistics: [ hCDF , stats ] = cdfplot ( x ) Display an empirical cumulative distribution function. hCDF = cdfplot ( x ) plots an empirical cumulative distribution function (CDF) of the observations in the data sample vector x . x may be a row or column vector, and represents a random sample of observations from some underlying distribution. cdfplot plots F(x), the empirical (or sample) CDF versus the observations in x . The empirical CDF, F(x), is defined as follows: F(x) = (Number of observations <= x) / (Total number of observations) for all values in the sample vector x . NaNs are ignored. hCDF is the handle of the empirical CDF curve (a handle graphics ’line’ object). [ hCDF , stats ] = cdfplot ( x ) also returns a structure with the following fields as a statistical summary. STATS.min minimum value of x STATS.max maximum value of x STATS.mean sample mean of x STATS.median sample median (50th percentile) of x STATS.std sample standard deviation of x See also: qqplot, cdfcalc # name: # type: sq_string # elements: 1 # length: 54 Display an empirical cumulative distribution function. # name: # type: sq_string # elements: 1 # length: 10 dendrogram # name: # type: sq_string # elements: 1 # length: 2000 statistics: dendrogram ( tree ) statistics: dendrogram ( tree , p ) statistics: dendrogram ( tree , prop , val ) statistics: dendrogram ( tree , p , prop , val ) statistics: h = dendrogram (…) statistics: [ h , t , perm ] = dendrogram (…) Plot a dendrogram of a hierarchical binary cluster tree. Given tree , a hierarchical binary cluster tree as the output of linkage , plot a dendrogram of the tree. The number of leaves shown by the dendrogram plot is limited to p . The default value for p is 30. Set p to 0 to plot all leaves. The optional outputs are h , t and perm : h is a handle to the lines of the plot. t is the vector with the numbers assigned to each leaf. Each element of t is a leaf of tree and its value is the number shown in the plot. When the dendrogram plot is collapsed, that is when the number of shown leaves p is inferior to the total number of leaves, a single leaf of the plot can represent more than one leaf of tree : in that case multiple elements of t share the same value, that is the same leaf of the plot. When the dendrogram plot is not collapsed, each leaf of the plot is the leaf of tree with the same number. perm is the vector list of the leaves as ordered as in the plot. Additional input properties can be specified by pairs of properties and values. Known properties are: 'Reorder' Reorder the leaves of the dendrogram plot using a numerical vector of size n, the number of leaves. When p is smaller than n , the reordering cannot break the p groups of leaves. 'Orientation' Change the orientation of the plot. Available values: top (default), bottom , left , right . 'CheckCrossing' Check if the lines of a reordered dendrogram cross each other. Available values: true (default), false . 'ColorThreshold' Not implemented. 'Labels' Use a char, string or cellstr array of size n to set the label for each leaf; the label is displayed only for nodes with just one leaf. See also: cluster, clusterdata, cophenet, inconsistent, linkage, pdist # name: # type: sq_string # elements: 1 # length: 56 Plot a dendrogram of a hierarchical binary cluster tree. # name: # type: sq_string # elements: 1 # length: 8 ecdfhist # name: # type: sq_string # elements: 1 # length: 1316 statistics: [ n , c ] = ecdfhist ( f , x ) statistics: [ n , c ] = ecdfhist ( f , x , m ) statistics: [ n , c ] = ecdfhist ( f , x , centers ) statistics: ecdfhist (…) statistics: ecdfhist ( ax , …) Create a histogram from the output of ecdf . [ n , c ] = ecdfhist ( f , x ) takes the empirical cumulative distribution function f evaluated at the points x , as computed by ecdf , and returns the heights n of histogram bars for 10 equally spaced bins together with their centers c . Unlike a count histogram, the bar heights are normalized so that the area of the histogram is equal to 1, giving an estimate of the probability density function. [ n , c ] = ecdfhist ( f , x , m ) uses m equally spaced bins. [ n , c ] = ecdfhist ( f , x , centers ) uses bins with the specified centers, given as a vector of monotonically increasing values. ecdfhist (…) without output arguments plots the histogram. ecdfhist ( ax , …) plots into the axes ax instead of the current axes. The probability mass assigned to each bin is the sum of the increments of the empirical cdf, diff ( f ) , over the points x that fall closest to the corresponding bin center; ties are assigned to the lower center. Each bar height is that mass divided by the bin width. See also: ecdf, cdfplot, hist, histogram # name: # type: sq_string # elements: 1 # length: 43 Create a histogram from the output of ecdf. # name: # type: sq_string # elements: 1 # length: 8 einstein # name: # type: sq_string # elements: 1 # length: 1261 statistics: einstein () statistics: tiles = einstein ( a , b ) statistics: [ tiles , rhat ] = einstein ( a , b ) statistics: [ tiles , rhat , that ] = einstein ( a , b ) statistics: [ tiles , rhat , that , shat ] = einstein ( a , b ) statistics: [ tiles , rhat , that , shat , phat ] = einstein ( a , b ) statistics: [ tiles , rhat , that , shat , phat , fhat ] = einstein ( a , b ) Plots the tiling of the basic clusters of einstein tiles. Scalars a and b define the shape of the einstein tile. See Smith et al (2023) for details: https://arxiv.org/abs/2303.10798 tiles is a structure containing the coordinates of the einstein tiles that are tiled on the plot. Each field contains the tile coordinates of the corresponding clusters. tiles .rhat contains the reflected einstein tiles tiles .that contains the three-hat shells tiles .shat contains the single-hat clusters tiles .phat contains the paired-hat clusters tiles .fhat contains the fylfot clusters rhat contains the coordinates of the first reflected tile that contains the coordinates of the first three-hat shell shat contains the coordinates of the first single-hat cluster phat contains the coordinates of the first paired-hat cluster fhat contains the coordinates of the first fylfot cluster # name: # type: sq_string # elements: 1 # length: 57 Plots the tiling of the basic clusters of einstein tiles. # name: # type: sq_string # elements: 1 # length: 9 glyphplot # name: # type: sq_string # elements: 1 # length: 1562 statistics: glyphplot ( x ) statistics: glyphplot ( x , name , value , …) statistics: g = glyphplot (…) Create a star (glyph) plot of the multivariate data in x . glyphplot ( x ) draws each observation (row) of the n -by- p matrix x as a star glyph, arranged on a grid. The p spokes of each star radiate from its center at equally spaced angles, with lengths proportional to the values of the p variables; the tips of the spokes are joined to form the star perimeter. The following name/value pairs are accepted: "Glyph" "star" (default) draws star glyphs. "face" (Chernoff faces) is not currently supported. "Standardize" How the columns of x are scaled to spoke lengths: "column" (default) scales each column to the range [0,1] , "matrix" scales the whole matrix to [0,1] , "PCA" uses principal component scores scaled to [0,1] , and "off" uses the values as given. A spoke of relative length 0 is still drawn at 10% of the maximum radius so that it remains visible. "Grid" A two-element vector [rows cols] specifying the layout of the glyphs. The default is chosen automatically. "Centers" An n -by-2 matrix giving the center coordinates of the glyphs explicitly, overriding "Grid" . "Radius" The maximum glyph radius (default 0.4). "ObsLabels" A character array or cell array of strings labeling the observations. The default is the observation numbers. The optional output g is an n -by-3 matrix of handles whose columns hold, respectively, the star perimeters, the star spokes, and the text labels. See also: andrewsplot, parallelcoords # name: # type: sq_string # elements: 1 # length: 57 Create a star (glyph) plot of the multivariate data in x. # name: # type: sq_string # elements: 1 # length: 11 gplotmatrix # name: # type: sq_string # elements: 1 # length: 2644 statistics: gplotmatrix ( x , y , group ) statistics: gplotmatrix ( x , [], group ) statistics: gplotmatrix ( x , y , group , clr , sym , siz ) statistics: gplotmatrix (…, doleg , dispopt ) statistics: gplotmatrix (…, doleg , dispopt , xnam , ynam ) statistics: gplotmatrix ( parent , …) statistics: [ h , ax , bigax ] = gplotmatrix (…) Create a matrix of scatter plots grouped by a categorical variable. gplotmatrix ( x , y , group ) creates a matrix of scatter plots. Each subplot in the resulting figure is a scatter plot of a column of x against a column of y . If x is n -by- p and y is n -by- q , the resulting figure holds a q -by- p grid of subplots; the subplot in row i and column j plots x (:,j) on the horizontal axis against y (:,i) on the vertical axis. Points are grouped and colored according to group , which is a grouping variable (numeric, logical, character, string, or cell array of strings) with one entry per row of x . gplotmatrix ( x , [], group ) is equivalent to gplotmatrix ( x , x , group ) except that the diagonal of the p -by- p grid is replaced by grouped histograms of the columns of x . The appearance of the plot is controlled by further positional arguments: clr Marker colors, given as a character vector of color specifiers (e.g. "rgb" ) or as a matrix of RGB triplets, one row per group. Colors cycle if fewer are supplied than there are groups. sym Marker symbols, given as a character vector (e.g. "o+x" ); defaults to "." . Symbols cycle if fewer are supplied than there are groups. siz Marker sizes, given as a numeric vector. Sizes cycle if fewer are supplied than there are groups. doleg Either "on" (default) to display a legend of the groups or "off" to suppress it. dispopt Controls the diagonal of the grid when y is empty: "stairs" (default) for grouped stairstep histograms, "hist" or "grpbars" for grouped bar histograms, "none" to leave the diagonal empty, or "variable" to write the variable names on the diagonal. xnam , ynam Character vectors or cell arrays of strings giving the names of the columns of x and y , used to label the outer axes. An optional leading parent argument (a figure or uipanel handle) selects the container for the plot. The optional outputs are h , an array of handles to the plotted objects with size ny -by- p -by- k (where ny is the number of rows of the grid and k the number of groups); ax , the matrix of handles to the subplot axes (with an extra row of hidden axes for the diagonal histograms); and bigax , the handle to the invisible enclosing axes used for titles and labels. See also: gscatter, plotmatrix, grpstats # name: # type: sq_string # elements: 1 # length: 67 Create a matrix of scatter plots grouped by a categorical variable. # name: # type: sq_string # elements: 1 # length: 8 gscatter # name: # type: sq_string # elements: 1 # length: 1566 statistics: gscatter ( x , y , g ) statistics: gscatter ( x , y , g , clr , sym , siz ) statistics: gscatter (…, doleg , xnam , ynam ) statistics: h = gscatter (…) Draw a scatter plot with grouped data. gscatter is a utility function to draw a scatter plot of x and y , according to the groups defined by g . Input x and y are numeric vectors of the same size, while g is either a vector of the same size as x or a character matrix with the same number of rows as the size of x . As a vector g can be numeric, logical, a character array, a string array (not implemented), a cell string or cell array. A number of optional inputs change the appearance of the plot: "clr" defines the color for each group; if not enough colors are defined by "clr" , gscatter cycles through the specified colors. Colors can be defined as named colors, as rgb triplets or as indices for the current colormap . The default value is a different color for each group, according to the current colormap . "sym" is a char array of symbols for each group; if not enough symbols are defined by "sym" , gscatter cycles through the specified symbols. "siz" is a numeric array of sizes for each group; if not enough sizes are defined by "siz" , gscatter cycles through the specified sizes. "doleg" is a boolean value to show the legend; it can be either on (default) or off . "xnam" is a character array, the name for the x axis. "ynam" is a character array, the name for the y axis. Output h is an array of graphics handles to the line object of each group. See also: scatter # name: # type: sq_string # elements: 1 # length: 38 Draw a scatter plot with grouped data. # name: # type: sq_string # elements: 1 # length: 5 hist3 # name: # type: sq_string # elements: 1 # length: 1879 statistics: hist3 ( X ) statistics: hist3 ( X , nbins ) statistics: hist3 ( X , "Nbins" , nbins ) statistics: hist3 ( X , centers ) statistics: hist3 ( X , "Ctrs" , centers ) statistics: hist3 ( X , "Edges" , edges ) statistics: [ N , C ] = hist3 (…) statistics: hist3 (…, prop , val , …) statistics: hist3 ( hax , …) Produce bivariate (2D) histogram counts or plots. The elements to produce the histogram are taken from the Nx2 matrix X . Any row with NaN values are ignored. The actual bins can be configured in 3 different: number, centers, or edges of the bins: Number of bins (default) Produces equally spaced bins between the minimum and maximum values of X . Defined as a 2 element vector, nbins , one for each dimension. Defaults to [10 10] . Center of bins Defined as a cell array of 2 monotonically increasing vectors, centers . The width of each bin is determined from the adjacent values in the vector with the initial and final bin, extending to Infinity. Edge of bins Defined as a cell array of 2 monotonically increasing vectors, edges . N (i,j) contains the number of elements in X for which: edges {1}(i) <= X (:,1) < edges {1}(i+1) edges {2}(j) <= X (:,2) < edges {2}(j+1) The consequence of this definition is that values outside the initial and final edge values are ignored, and that the final bin only contains the number of elements exactly equal to the final edge. The return values, N and C , are the bin counts and centers respectively. These are specially useful to produce intensity maps: [counts, centers] = hist3 (data); imagesc (centers{1}, centers{2}, counts) If there is no output argument, or if the axes graphics handle hax is defined, the function will plot a 3 dimensional bar graph. Any extra property/value pairs are passed directly to the underlying surface object. See also: hist, histc, lookup, mesh # name: # type: sq_string # elements: 1 # length: 49 Produce bivariate (2D) histogram counts or plots. # name: # type: sq_string # elements: 1 # length: 7 histfit # name: # type: sq_string # elements: 1 # length: 1331 statistics: histfit ( x ) statistics: histfit ( x , nbins ) statistics: histfit ( x , nbins , distname ) statistics: histfit ( ax , …) statistics: h = histfit (…) Plot histogram with superimposed distribution fit. histfit ( x ) plots a histogram of the values in the vector x using the number of bins equal to the square root of the number of non-missing elements in x and superimposes a fitted normal density function. histfit ( x , nbins ) plots a histogram of the values in the vector x using nbins number of bins in the histogram and superimposes a fitted normal density function. histfit ( x , nbins , distname ) plots a histogram of the values in the vector x using nbins number of bins in the histogram and superimposes a fitted density function from the distribution specified by distname . histfit ( ax , …) uses the axes handle ax to plot the histogram and the fitted density function onto followed by any of the input argument combinations specified in the previous syntaxes. h = histfit (…) returns a vector of handles h , where h (1) is the handle to the histogram and h (2) is the handle to the density curve. Note: calling histfit without any input arguments will return a cell array of character vectors listing all supported distributions. See also: bar, hist, normplot, fitdist # name: # type: sq_string # elements: 1 # length: 50 Plot histogram with superimposed distribution fit. # name: # type: sq_string # elements: 1 # length: 13 manovacluster # name: # type: sq_string # elements: 1 # length: 848 statistics: manovacluster ( stats ) statistics: manovacluster ( stats , method ) statistics: h = manovacluster ( stats ) statistics: h = manovacluster ( stats , method ) Cluster group means using manova1 output. manovacluster ( stats ) draws a dendrogram showing the clustering of group means, calculated using the output STATS structure from manova1 and applying the single linkage algorithm. See the dendrogram function for more information about the figure. manovacluster ( stats , method ) uses the method algorithm in place of single linkage. The available methods are: "single" — nearest distance "complete" — furthest distance "average" — average distance "centroid" — center of mass distance "ward" — inner squared distance h = manovacluster (…) returns a vector of line handles. See also: manova1 # name: # type: sq_string # elements: 1 # length: 41 Cluster group means using manova1 output. # name: # type: sq_string # elements: 1 # length: 8 normplot # name: # type: sq_string # elements: 1 # length: 690 statistics: normplot ( x ) statistics: normplot ( ax , x ) statistics: h = normplot (…) Produce normal probability plot of the data in x . If x is a matrix, normplot plots the data for each column. NaN values are ignored. h = normplot ( ax , x ) takes a handle ax in addition to the data in x and it uses that axes for plotting. You may get this handle of an existing plot with gca . The line joining the 1st and 3rd quantile is drawn solid whereas its extensions to both ends are dotted. If the underlying distribution is normal, the points will cluster around the solid part of the line. Other distribution types will introduce curvature in the plot. See also: cdfplot, wblplot # name: # type: sq_string # elements: 1 # length: 49 Produce normal probability plot of the data in x. # name: # type: sq_string # elements: 1 # length: 14 parallelcoords # name: # type: sq_string # elements: 1 # length: 1492 statistics: parallelcoords ( x ) statistics: parallelcoords ( x , name , value , …) statistics: parallelcoords ( ax , …) statistics: h = parallelcoords (…) Create a parallel coordinates plot of the multivariate data in x . parallelcoords ( x ) plots each observation (row) of the n -by- p matrix x as a line connecting the values of its p coordinates, which are placed at the equally spaced horizontal positions 1, 2, …, p . The following name/value pairs are accepted: "Group" A grouping variable (numeric, logical, character, string, or cell array of strings) with one entry per row of x . Lines are colored by group. "Standardize" Controls how the columns of x are transformed before plotting: "off" (default) uses the raw data, "on" centers and scales each column to zero mean and unit standard deviation, "PCA" uses the principal component scores, and "PCAStd" uses the principal component scores of the standardized data. "Quantile" A scalar alpha in the interval (0,1) . Instead of one line per observation, only three lines per group are drawn: the coordinate-wise median and the alpha and 1- alpha quantiles of the group. "Labels" A character array or cell array of strings giving the tick labels for the coordinate axis. parallelcoords ( ax , …) plots into the axes ax . The optional output h is a vector of handles to the plotted lines: one per observation, or three per group when "Quantile" is used. See also: andrewsplot, glyphplot, pca # name: # type: sq_string # elements: 1 # length: 65 Create a parallel coordinates plot of the multivariate data in x. # name: # type: sq_string # elements: 1 # length: 6 ppplot # name: # type: sq_string # elements: 1 # length: 890 statistics: ppplot ( x , dist ) statistics: ppplot ( x , dist , params ) statistics: [ p , y ] = ppplot ( x , dist , params ) Perform a PP-plot (probability plot). If F is the CDF of the distribution dist with parameters params and x a sample vector of length n , the PP-plot graphs ordinate y ( i ) = F ( i -th largest element of x ) versus abscissa p ( i ) = ( i - 0.5)/ n . If the sample comes from F, the pairs will approximately follow a straight line. The default for dist is the standard normal distribution. The optional argument params contains a list of parameters of dist . For example, for a probability plot of the uniform distribution on [2,4] and x , use ppplot (x, "unif", 2, 4) dist can be any string for which a function distcdf that calculates the CDF of distribution dist exists. If no output is requested then the data are plotted immediately. See also: qqplot # name: # type: sq_string # elements: 1 # length: 37 Perform a PP-plot (probability plot). # name: # type: sq_string # elements: 1 # length: 8 probplot # name: # type: sq_string # elements: 1 # length: 1998 statistics: probplot ( y ) statistics: probplot ( dist , y ) statistics: probplot ( dist , y , cens ) statistics: probplot ( dist , y , cens , freq ) statistics: probplot ( ax , …) statistics: probplot (…, "noref" ) statistics: h = probplot (…) Produce a probability plot of the data in y against the distribution dist . On a probability plot the ordered data are drawn against a nonlinear probability axis chosen so that a sample from the reference distribution dist falls approximately along a straight line. Systematic departures from the reference line indicate departures from the distribution. dist is one of "normal" (the default when dist is omitted), "lognormal" , "exponential" , "extreme value" , "weibull" , "rayleigh" , "logistic" , or "loglogistic" . For "lognormal" , "weibull" , and "loglogistic" the data axis is logarithmic. y is a numeric vector, or a matrix in which case each column is plotted as a separate sample. NaN values are ignored. cens is a logical vector the same size as y that is true for right-censored observations; censored points are not plotted and the plotting positions of the remaining points follow the Kaplan-Meier estimate. freq is a vector of nonnegative integer frequencies (counts) the same size as y . Pass [] to omit either one. probplot ( ax , …) plots into the axes ax instead of the current axes. The trailing option "noref" suppresses the reference line. h = probplot (…) returns a column vector of handles to the plotted line objects (the data markers, followed by the reference line unless "noref" was given). The reference line is a robust fit through the first and third quartiles of the data on the transformed scale. For censored data the quartiles are taken from the Kaplan-Meier plotting positions; when heavy censoring prevents the data from reaching a quartile the position is linearly extrapolated, which may deviate slightly from MATLAB . See also: normplot, wblplot, qqplot, cdfplot, ecdf # name: # type: sq_string # elements: 1 # length: 74 Produce a probability plot of the data in y against the distribution dist. # name: # type: sq_string # elements: 1 # length: 6 qqplot # name: # type: sq_string # elements: 1 # length: 1127 statistics: [ q , s ] = qqplot ( x ) statistics: [ q , s ] = qqplot ( x , y ) statistics: [ q , s ] = qqplot ( x , dist ) statistics: [ q , s ] = qqplot ( x , y , params ) statistics: qqplot (…) Perform a QQ-plot (quantile plot). If F is the CDF of the distribution dist with parameters params and G its inverse, and x a sample vector of length n , the QQ-plot graphs ordinate s ( i ) = i -th largest element of x versus abscissa q ( i f) = G(( i - 0.5)/ n ). If the sample comes from F, except for a transformation of location and scale, the pairs will approximately follow a straight line. If the second argument is a vector y the empirical CDF of y is used as dist . The default for dist is the standard normal distribution. The optional argument params contains a list of parameters of dist . For example, for a quantile plot of the uniform distribution on [2,4] and x , use qqplot (x, "unif", 2, 4) dist can be any string for which a function distinv or dist_inv exists that calculates the inverse CDF of distribution dist . If no output arguments are given, the data are plotted directly. See also: ppplot # name: # type: sq_string # elements: 1 # length: 34 Perform a QQ-plot (quantile plot). # name: # type: sq_string # elements: 1 # length: 11 scatterhist # name: # type: sq_string # elements: 1 # length: 3438 statistics: scatterhist ( x , y ) statistics: scatterhist ( x , y , name , value , …) statistics: h = scatterhist (…) Create a scatter plot of x and y with marginal histograms. scatterhist ( x , y ) draws a scatter plot of the vectors x and y in a central set of axes, with a histogram of x above it and a histogram of y to its right. x and y must be vectors of the same length; NaN values are removed pairwise from the scatter plot and individually from each marginal histogram. The following name/value pairs are accepted: "Group" A grouping variable (numeric, logical, character, string, or cell array of strings) with one entry per point. The scatter points and the marginal histograms are separated and colored by group. "NBins" The number of bins for the marginal histograms, either a scalar applied to both or a two-element vector [nx ny] . The default is chosen by Scott’s rule. "Kernel" "off" (default) draws histograms for the marginals; "on" or "overlay" draws kernel density estimates instead. "Location" Corner occupied by the marginal plots, the scatter taking the opposite one: "SouthWest" (default), "SouthEast" , "NorthEast" , or "NorthWest" . "Legend" "on" or "off" to show or hide the group legend. The default is "on" when a grouping variable is supplied. "Marker" , "MarkerSize" The marker symbol(s) and size(s) for the scatter points, cycled over the groups. The optional output h is a three-element vector of axes handles: the central scatter axes, the axes of the x (horizontal) histogram, and the axes of the y (vertical) histogram. The y histogram’s axes is built differently from MATLAB’s and its properties read accordingly. We plot that marginal transposed, so its "XLim" is the density and "XDir" carries the bar direction. MATLAB plots it like the x one – data along "XLim" , density along "YLim" – and rotates the whole axes by setting "View" to [270, 90] , so there the direction is carried by "YDir" and "XDir" is always "normal" . The picture is the same; code reading those properties off h (3) is not portable between the two. 'Parent' draws into a supplied figure or uipanel instead of the current figure. The container is used as it stands and is never cleared, so a scatterhist can be placed alongside other axes. 'Location' names the corner the marginal histograms occupy, 'SouthWest' by default, or 'SouthEast' , 'NorthEast' or 'NorthWest' ; the scatter takes the opposite corner. With the default the histograms are drawn below and to the left of the scatter. 'Direction' points the marginal bars toward the scatter plot, 'in' by default, or away from it with 'out' , whichever side 'Location' has placed them on. 'PlotGroup' draws one marginal per group with 'on' , or a single pooled marginal with 'off' . It defaults to 'on' when 'Group' is given and to 'off' otherwise. 'Style' outlines the marginals as 'bar' or 'stairs' , defaulting to stairs when the data are grouped and bars when they are not. 'Color' sets the group colours, either as a character vector of colour names or as an N -by-3 matrix of RGB values, cycled over the groups. 'LineStyle' and 'LineWidth' style the marginal outlines, not the scatter markers, and are cycled over the groups. 'Bandwidth' sets the kernel bandwidth used when 'Kernel' is on: a scalar for all marginals, a pair for x and y , or one row per group. See also: gscatter, scatter, hist, ksdensity # name: # type: sq_string # elements: 1 # length: 58 Create a scatter plot of x and y with marginal histograms. # name: # type: sq_string # elements: 1 # length: 10 silhouette # name: # type: sq_string # elements: 1 # length: 1782 statistics: silhouette ( X , clust ) statistics: [ si , h ] = silhouette ( X , clust ) statistics: [ si , h ] = silhouette (…, Metric , MetricArg ) Compute the silhouette values of clustered data and show them on a plot. X is a n-by-p matrix of n data points in a p-dimensional space. Each datapoint is assigned to a cluster using clust , a vector of n elements, one cluster assignment for each data point. Each silhouette value of si , a vector of size n, is a measure of the likelihood that a data point is accurately classified to the right cluster. Defining "a" as the mean distance between a point and the other points from its cluster, and "b" as the mean distance between that point and the points from other clusters, the silhouette value of the i-th point is: $$ S_i = \frac{b_i - a_i}{max(a_1,b_i)} $$ Each element of si ranges from -1, minimum likelihood of a correct classification, to 1, maximum likelihood. Optional input value Metric is the metric used to compute the distances between data points. Since silhouette uses pdist to compute these distances, Metric is similar to the Distance input argument of pdist and it can be: A known distance metric defined as a string: euclidean , squaredeuclidean (default), seuclidean , mahalanobis , cityblock , minkowski , chebychev , cosine , correlation , hamming , jaccard , or spearman . A vector as those created by pdist . In this case X does nothing. A function handle that is passed to pdist with MetricArg as optional inputs. Optional return value h is a handle to the silhouette plot. Reference Peter J. Rousseeuw, Silhouettes: a Graphical Aid to the Interpretation and Validation of Cluster Analysis. 1987. doi:10.1016/0377-0427(87)90125-7 See also: dendrogram, evalclusters, kmeans, linkage, pdist # name: # type: sq_string # elements: 1 # length: 72 Compute the silhouette values of clustered data and show them on a plot. # name: # type: sq_string # elements: 1 # length: 6 violin # name: # type: sq_string # elements: 1 # length: 2324 statistics: violin ( x ) statistics: h = violin ( x ) statistics: h = violin (…, property , value , …) statistics: h = violin ( hax , …) statistics: h = violin (…, "horizontal" ) Produce a Violin plot of the data x . The input data x can be a N-by-m array containing N observations of m variables. It can also be a cell with m elements, for the case in which the variables are not uniformly sampled. The following property can be set using property / value pairs (default values in parenthesis). The value of the property can be a scalar indicating that it applies to all the variables in the data. It can also be a cell/array, indicating the property for each variable. In this case it should have m columns (as many as variables). Color ("y") Indicates the filling color of the violins. Nbins (50) Internally, the function calls hist to compute the histogram of the data. This property indicates how many bins to use. See help hist for more details. SmoothFactor (4) The function performs simple kernel density estimation and automatically finds the bandwidth of the kernel function that best approximates the histogram using optimization ( sqp ). The result is in general very noisy. To smooth the result the bandwidth is multiplied by the value of this property. The higher the value the smoother the violins, but values too high might remove features from the data distribution. Bandwidth (NA) If this property is given a value other than NA, it sets the bandwidth of the kernel function. No optimization is performed and the property SmoothFactor is ignored. Width (0.5) Sets the maximum width of the violins. Violins are centered at integer axis values. The distance between two violin middle axis is 1. Setting a value higher than 1 in this property will cause the violins to overlap. If the string "Horizontal" is among the input arguments, the violin plot is rendered along the x axis with the variables in the y axis. The returned structure h has handles to the plot elements, allowing customization of the visualization using set/get functions. Example: title ("Grade 3 heights"); axis ([0,3]); set (gca, "xtick", 1:2, "xticklabel", {"girls"; "boys"}); h = violin ({randn(100,1)*5+140, randn(130,1)*8+135}, "Nbins", 10); set (h.violin, "linewidth", 2) See also: boxplot, hist # name: # type: sq_string # elements: 1 # length: 36 Produce a Violin plot of the data x. # name: # type: sq_string # elements: 1 # length: 7 wblplot # name: # type: sq_string # elements: 1 # length: 1647 statistics: wblplot ( data , …) statistics: handle = wblplot ( data , …) statistics: [ handle , param ] = wblplot ( data ) statistics: [ handle , param ] = wblplot ( data , censor ) statistics: [ handle , param ] = wblplot ( data , censor , freq ) statistics: [ handle , param ] = wblplot ( data , censor , freq , confint ) statistics: [ handle , param ] = wblplot ( data , censor , freq , confint , fancygrid ) statistics: [ handle , param ] = wblplot ( data , censor , freq , confint , fancygrid , showlegend ) Plot a column vector data on a Weibull probability plot using rank regression. censor : optional parameter is a column vector of same size as data with 1 for right censored data and 0 for exact observation. Pass [] when no censor data are available. freq : optional vector same size as data with the number of occurrences for corresponding data. Pass [] when no frequency data are available. confint : optional confidence limits for plotting upper and lower confidence bands using beta binomial confidence bounds. If a single value is given this will be used such as LOW = a and HIGH = 1 - a. Pass [] if confidence bounds is not requested. fancygrid : optional parameter which if set to anything but 1 will turn off the fancy gridlines. showlegend : optional parameter that when set to zero(0) turns off the legend. If one output argument is given, a handle for the data marker and plotlines is returned, which can be used for further modification of line and marker style. If a second output argument is specified, a param vector with scale, shape and correlation factor is returned. See also: normplot, wblpdf # name: # type: sq_string # elements: 1 # length: 78 Plot a column vector data on a Weibull probability plot using rank regression. statistics-release-1.9.2/inst/Plotting/ecdfhist.m000066400000000000000000000157151524624707500221040ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{n}, @var{c}] =} ecdfhist (@var{f}, @var{x}) ## @deftypefnx {statistics} {[@var{n}, @var{c}] =} ecdfhist (@var{f}, @var{x}, @var{m}) ## @deftypefnx {statistics} {[@var{n}, @var{c}] =} ecdfhist (@var{f}, @var{x}, @var{centers}) ## @deftypefnx {statistics} {} ecdfhist (@dots{}) ## @deftypefnx {statistics} {} ecdfhist (@var{ax}, @dots{}) ## ## Create a histogram from the output of @code{ecdf}. ## ## @code{[@var{n}, @var{c}] = ecdfhist (@var{f}, @var{x})} takes the empirical ## cumulative distribution function @var{f} evaluated at the points @var{x}, as ## computed by @code{ecdf}, and returns the heights @var{n} of histogram bars ## for 10 equally spaced bins together with their centers @var{c}. Unlike a ## count histogram, the bar heights are normalized so that the area of the ## histogram is equal to 1, giving an estimate of the probability density ## function. ## ## @code{[@var{n}, @var{c}] = ecdfhist (@var{f}, @var{x}, @var{m})} uses @var{m} ## equally spaced bins. ## ## @code{[@var{n}, @var{c}] = ecdfhist (@var{f}, @var{x}, @var{centers})} uses ## bins with the specified centers, given as a vector of monotonically ## increasing values. ## ## @code{ecdfhist (@dots{})} without output arguments plots the histogram. ## ## @code{ecdfhist (@var{ax}, @dots{})} plots into the axes @var{ax} instead of ## the current axes. ## ## The probability mass assigned to each bin is the sum of the increments of the ## empirical cdf, @code{diff (@var{f})}, over the points @var{x} that fall ## closest to the corresponding bin center; ties are assigned to the lower ## center. Each bar height is that mass divided by the bin width. ## ## @seealso{ecdf, cdfplot, hist, histogram} ## @end deftypefn function [nout, cout] = ecdfhist (varargin) ## Detect a leading axes handle ax = []; if (numel (varargin) > 0 && isaxes (varargin{1})) ax = varargin{1}; varargin(1) = []; endif if (numel (varargin) < 2 || numel (varargin) > 3) print_usage (); endif f = varargin{1}; x = varargin{2}; if (! isnumeric (f) || ! isreal (f) || ! isnumeric (x) || ! isreal (x)) error ("ecdfhist: F and X must be real numeric vectors."); endif f = f(:); x = x(:); if (numel (f) != numel (x)) error ("ecdfhist: F and X must have the same length."); endif if (numel (f) < 2) error ("ecdfhist: F and X must have at least two elements."); endif ## Probability masses are the cdf increments, located at x(2:end) xm = x(2:end); masses = diff (f); ## Determine the bin centers and widths if (numel (varargin) < 3 || isempty (varargin{3})) m = 10; centers = []; elseif (isscalar (varargin{3})) m = varargin{3}; if (! isnumeric (m) || ! isreal (m) || m < 1 || fix (m) != m) error ("ecdfhist: M must be a positive integer."); endif centers = []; else centers = varargin{3}; if (! isnumeric (centers) || ! isreal (centers) || ! isvector (centers)) error ("ecdfhist: CENTERS must be a real numeric vector."); endif centers = centers(:).'; endif if (isempty (centers)) xlo = min (xm); xhi = max (xm); if (xlo == xhi) xlo -= 0.5; xhi += 0.5; endif edges = linspace (xlo, xhi, m + 1); centers = edges(1:end-1) + diff (edges) / 2; binwidth = diff (edges); elseif (numel (centers) == 1) binwidth = 1; else ec = (centers(1:end-1) + centers(2:end)) / 2; binwidth = diff ([2 * centers(1) - ec(1), ec, 2 * centers(end) - ec(end)]); endif ncnt = numel (centers); ## Assign each mass to the nearest center (ties go to the lower center) [~, bin] = min (abs (xm - centers), [], 2); binmass = accumarray (bin, masses, [ncnt, 1]); heights = binmass.' ./ binwidth; if (nargout == 0) if (isempty (ax)) ax = newplot (); endif bar (ax, centers, heights, 'hist'); else nout = heights; cout = centers; endif endfunction %!demo %! ## Histogram (density estimate) from the empirical cdf of a random sample. %! %! rng (42); %! x = randn (100, 1); %! [f, xx] = ecdf (x); %! ecdfhist (f, xx); %! title ("ecdfhist of a standard normal sample"); %!demo %! ## Compare the empirical density with a finer set of bins. %! %! rande ('state', 42); %! x = exprnd (2, 200, 1); %! [f, xx] = ecdf (x); %! ecdfhist (f, xx, 20); %! title ("ecdfhist of an exponential sample (20 bins)"); ## Test output %!test %! f = [0 0.1 0.3 0.6 0.8 0.9 1.0]'; %! x = [1 1 2 3 4 5 8]'; %! [n, c] = ecdfhist (f, x); %! assert_equal (c, 1.35:0.7:7.65, 1e-12); %! assert_equal (n, [0.1 0.2 0.3 0 0.2 0.1 0 0 0 0.1] / 0.7, 1e-12); %! assert_equal (sum (n .* 0.7), 1, 1e-12); %!test %! f = [0 0.1 0.3 0.6 0.8 0.9 1.0]'; %! x = [1 1 2 3 4 5 8]'; %! [n, c] = ecdfhist (f, x, 5); %! assert_equal (c, [1.7 3.1 4.5 5.9 7.3], 1e-12); %! assert_equal (n, [0.3 0.3 0.3 0 0.1] / 1.4, 1e-12); %!test %! f = [0 0.1 0.3 0.6 0.8 0.9 1.0]'; %! x = [1 1 2 3 4 5 8]'; %! [n, c] = ecdfhist (f, x, [1.5 2.5 3.5 4.5 5.5 6.5 7.5]); %! assert_equal (c, [1.5 2.5 3.5 4.5 5.5 6.5 7.5], 1e-12); %! assert_equal (n, [0.3 0.3 0.2 0.1 0 0 0.1], 1e-12); %!test # returns only the heights when a single output is requested %! f = [0 0.1 0.3 0.6 0.8 0.9 1.0]'; %! x = [1 1 2 3 4 5 8]'; %! n = ecdfhist (f, x, 5); %! assert_equal (n, [0.3 0.3 0.3 0 0.1] / 1.4, 1e-12); ## Test plotting %!test %! hf = figure ("visible", "off"); %! unwind_protect %! f = [0 0.1 0.3 0.6 0.8 0.9 1.0]'; %! x = [1 1 2 3 4 5 8]'; %! ecdfhist (f, x); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ("visible", "off"); %! unwind_protect %! ax = axes ("parent", hf); %! f = [0 0.1 0.3 0.6 0.8 0.9 1.0]'; %! x = [1 1 2 3 4 5 8]'; %! ecdfhist (ax, f, x); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error ecdfhist () %!error ecdfhist ([0 1]) %!error ecdfhist ([0 1], {1 2}) %!error ecdfhist ([0 1], [1 2 3]) %!error ecdfhist (1, 1) %!error ecdfhist ([0 1], [1 2], 0) %!error ecdfhist ([0 1], [1 2], 2.5) statistics-release-1.9.2/inst/Plotting/einstein.m000066400000000000000000000225631524624707500221300ustar00rootroot00000000000000## Copyright (C) 2023 Arun Giridhar ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} einstein () ## @deftypefnx {statistics} {@var{tiles} =} einstein (@var{a}, @var{b}) ## @deftypefnx {statistics} {[@var{tiles}, @var{rhat}] =} einstein (@var{a}, @var{b}) ## @deftypefnx {statistics} {[@var{tiles}, @var{rhat}, @var{that}] =} einstein (@var{a}, @var{b}) ## @deftypefnx {statistics} {[@var{tiles}, @var{rhat}, @var{that}, @var{shat}] =} einstein (@var{a}, @var{b}) ## @deftypefnx {statistics} {[@var{tiles}, @var{rhat}, @var{that}, @var{shat}, @var{phat}] =} einstein (@var{a}, @var{b}) ## @deftypefnx {statistics} {[@var{tiles}, @var{rhat}, @var{that}, @var{shat}, @var{phat}, @var{fhat}] =} einstein (@var{a}, @var{b}) ## ## Plots the tiling of the basic clusters of einstein tiles. ## ## Scalars @var{a} and @var{b} define the shape of the einstein tile. ## See Smith et al (2023) for details: @url{https://arxiv.org/abs/2303.10798} ## ## @itemize ## @item @var{tiles} is a structure containing the coordinates of the einstein ## tiles that are tiled on the plot. Each field contains the tile coordinates ## of the corresponding clusters. ## @itemize ## @item @var{tiles}@qcode{.rhat} contains the reflected einstein tiles ## @item @var{tiles}@qcode{.that} contains the three-hat shells ## @item @var{tiles}@qcode{.shat} contains the single-hat clusters ## @item @var{tiles}@qcode{.phat} contains the paired-hat clusters ## @item @var{tiles}@qcode{.fhat} contains the fylfot clusters ## @end itemize ## ## @item @var{rhat} contains the coordinates of the first reflected tile ## @item @var{that} contains the coordinates of the first three-hat shell ## @item @var{shat} contains the coordinates of the first single-hat cluster ## @item @var{phat} contains the coordinates of the first paired-hat cluster ## @item @var{fhat} contains the coordinates of the first fylfot cluster ## @end itemize ## ## @end deftypefn function [varargout] = einstein (a, b, varargin) ## Check for valid number of input arguments if (nargin < 2) print_usage; endif ## Check A and B for valid type and range if (! (isscalar (a) && isscalar (b) && isnumeric (a) && isnumeric (b)... && isreal (a) && isreal (b))) error ("einstein: A and B must real scalars."); endif if (a <= 0 || a >= 1 || b <= 0 || b >= 1) error ("einstein: A and B must be within the open interval (0,1)."); endif ## Get initial hat points single_hat = getpoly (a, b) * rotz (-150)([1,2],[1,2]); ## Make a reflected-hat reflecthat = [-1, 1] .* single_hat; ## Make a three-hat shell cluster three_hat1 = rotatehat (single_hat, -60); three_hat1 = three_hat1 + (reflecthat(1,:) - three_hat1(9,:)); three_hat2 = three_hat1 + (reflecthat(6,:) - three_hat1(11,:)); three_hat3 = rotatehat (three_hat1, 120); three_hat3 = three_hat3 + (reflecthat(5,:) - three_hat1(9,:)); three_hat = [three_hat1, three_hat2, three_hat3]; ## Translate another four-tile cluster translate = three_hat(5,[3,4]) - three_hat(12,[1,2]); all.rhat = translatehat (reflecthat, translate); all.that = translatehat (three_hat, translate); all.rhat = [reflecthat, all.rhat]; all.that = [three_hat, all.that]; ## Rotate and translate another four-tile cluster tmp_3hat = rotatehat (three_hat, 120); tmp_rhat = rotatehat (reflecthat, 120); translate = three_hat(12,[5,6]) - tmp_3hat(5,[3,4]); tmp_3hat = translatehat (tmp_3hat, translate); tmp_rhat = translatehat (tmp_rhat, translate); all.that = [all.that, tmp_3hat]; all.rhat = [all.rhat, tmp_rhat]; ## Plot four-tile clusters patch (all.that(:,[1:2:end]), all.that(:,[2:2:end]), 'LineWidth', 2, ... 'FaceColor', 'c', 'EdgeColor','k'); patch (all.rhat(:,[1:2:end]), all.rhat(:,[2:2:end]), 'LineWidth', 2, ... 'FaceColor', 'b', 'EdgeColor','k'); title (sprintf ("a = %4.2f b = %4.2f", a, b), 'FontSize',30) ## Make a single-hat cluster translate = three_hat(10,[3,4]) - single_hat(2,:); singlehat = translatehat (single_hat, translate); all.shat = singlehat; ## Plot single-tile cluster patch (all.shat(:,1), all.shat(:,2), 'LineWidth', 2, ... 'FaceColor', [0.95, 0.95, 0.95], 'EdgeColor','k'); axis ('equal') ## Make a paired-hat cluster paired_hat1 = all.shat + (all.rhat(9,[5,6]) - all.shat(5,:)); paired_hat2 = three_hat(:,[3,4]) + (three_hat(1,[5,6]) - three_hat(3,[3,4])); paired_hat = [paired_hat1, paired_hat2]; ## Rotate and translate another two paired-hat clusters tmp_phat = rotatehat (paired_hat, -120); translate = three_hat(4,[3,4]) - tmp_phat(4,[3,4]); tmp_phat = translatehat (tmp_phat, translate); all.phat = [paired_hat, tmp_phat]; tmp_phat = rotatehat (paired_hat, -60); translate = all.that(10,[11,12]) - tmp_phat(11,[3,4]); tmp_phat = translatehat (tmp_phat, translate); all.phat = [all.phat, tmp_phat]; ## Plot paired-tiles clusters patch (all.phat(:,[1:2:end]), all.phat(:,[2:2:end]), 'LineWidth', 2, ... 'FaceColor', [0.9, 0.9, 0.9], 'EdgeColor','k'); ## Make a fylfot cluster fylfot_hat = paired_hat; tmp_fhat = rotatehat (paired_hat, -120); translate = fylfot_hat(1,[3,4]) - tmp_fhat(3,[3,4]); tmp_fhat = translatehat (tmp_fhat, translate); fylfot_hat = [fylfot_hat, tmp_fhat]; tmp_fhat = rotatehat (paired_hat, 120); translate = fylfot_hat(3,[3,4]) - tmp_fhat(1,[3,4]); tmp_fhat = translatehat (tmp_fhat, translate); fylfot_hat = [fylfot_hat, tmp_fhat]; translate = all.that(13,[13,14]) - fylfot_hat(13,[7,8]); fylfot_hat = translatehat (fylfot_hat, translate); ## Translate another two fylfot clusters translate = all.that(4,[9,10]) - fylfot_hat(13,[3,4]); tmp_fhat = translatehat (fylfot_hat, translate); all.fhat = [fylfot_hat, tmp_fhat]; translate = all.that(13,[1,2]) - fylfot_hat(4,[3,4]); tmp_fhat = translatehat (fylfot_hat, translate); all.fhat = [all.fhat, tmp_fhat]; ## Plot fylfot clusters patch (all.fhat(:,[1:2:end]), all.fhat(:,[2:2:end]), 'LineWidth', 2, ... 'FaceColor', 'r', 'EdgeColor','k'); if (nargout > 0) varargout{1} = all; endif if (nargout > 1) varargout{2} = reflecthat; endif if (nargout > 2) varargout{3} = three_hat; endif if (nargout > 3) varargout{4} = singlehat; endif if (nargout > 4) varargout{5} = paired_hat; endif if (nargout > 5) varargout{6} = fylfot_hat; endif endfunction ## Rotates a cluster of hats. function newhat = rotatehat (hat, degrees) rotM = rotz (degrees)([1,2],[1,2]); newhat = zeros (size (hat)); for i=1:2:columns (hat) newhat(:,[i,i+1]) = hat(:,[i,i+1]) * rotM; endfor endfunction ## Translates a cluster of hats. function newhat = translatehat (hat, dist) nhats = columns (hat) / 2; newhat = hat + repmat (dist, 1, nhats); endfunction ## Returns a unit vector given a direction angle in degrees function ret = u (t) persistent angles = (0:30:330); persistent tbl = [cosd(angles); sind(angles)]'; ret = tbl(angles == mod (t, 360), :); endfunction ## Returns the hat polygon function single_hat = getpoly (a, b) single_hat = zeros (14, 2); pos = 0; single_hat(++pos, :) = [0 0]; t = 270; single_hat(pos+1, :) = single_hat(pos++, :) + a * u (t); t += 60; single_hat(pos+1, :) = single_hat(pos++, :) + a * u (t); t -= 90; single_hat(pos+1, :) = single_hat(pos++, :) + b * u (t); t += 60; single_hat(pos+1, :) = single_hat(pos++, :) + b * u (t); t += 90; single_hat(pos+1, :) = single_hat(pos++, :) + a * u (t); t -= 60; single_hat(pos+1, :) = single_hat(pos++, :) + a * u (t); t += 90; single_hat(pos+1, :) = single_hat(pos++, :) + b * u (t); t -= 60; single_hat(pos+1, :) = single_hat(pos++, :) + b * u (t); t += 90; single_hat(pos+1, :) = single_hat(pos++, :) + a * u (t); t += 60; single_hat(pos+1, :) = single_hat(pos++, :) + a * u (t) * 2; t += 60; single_hat(pos+1, :) = single_hat(pos++, :) + a * u (t); t -= 90; single_hat(pos+1, :) = single_hat(pos++, :) + b * u (t); t += 60; single_hat(pos+1, :) = single_hat(pos++, :) + b * u (t); ## t(14, :) == t(1, :) to within numerical roundoff endfunction %!demo %! einstein (0.4, 0.6) %!demo %! einstein (0.2, 0.5) %!demo %! einstein (0.6, 0.1) ## Test plotting %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! tiles = einstein (0.4, 0.6); %! assert_equal (isstruct (tiles), true); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error einstein %!error einstein (0.5) %!error einstein (0, 0.9) %!error einstein (0.4, 1) %!error einstein (-0.4, 1) statistics-release-1.9.2/inst/Plotting/glyphplot.m000066400000000000000000000210411524624707500223220ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} glyphplot (@var{x}) ## @deftypefnx {statistics} {} glyphplot (@var{x}, @var{name}, @var{value}, @dots{}) ## @deftypefnx {statistics} {@var{g} =} glyphplot (@dots{}) ## ## Create a star (glyph) plot of the multivariate data in @var{x}. ## ## @code{glyphplot (@var{x})} draws each observation (row) of the ## @code{n}-by-@code{p} matrix @var{x} as a star glyph, arranged on a grid. The ## @var{p} spokes of each star radiate from its center at equally spaced angles, ## with lengths proportional to the values of the @var{p} variables; the tips of ## the spokes are joined to form the star perimeter. ## ## The following name/value pairs are accepted: ## ## @table @asis ## @item @qcode{"Glyph"} ## @qcode{"star"} (default) draws star glyphs. @qcode{"face"} (Chernoff faces) ## is not currently supported. ## ## @item @qcode{"Standardize"} ## How the columns of @var{x} are scaled to spoke lengths: @qcode{"column"} ## (default) scales each column to the range @math{[0,1]}, @qcode{"matrix"} ## scales the whole matrix to @math{[0,1]}, @qcode{"PCA"} uses principal ## component scores scaled to @math{[0,1]}, and @qcode{"off"} uses the values as ## given. A spoke of relative length 0 is still drawn at 10% of the maximum ## radius so that it remains visible. ## ## @item @qcode{"Grid"} ## A two-element vector @code{[rows cols]} specifying the layout of the glyphs. ## The default is chosen automatically. ## ## @item @qcode{"Centers"} ## An @code{n}-by-2 matrix giving the center coordinates of the glyphs ## explicitly, overriding @qcode{"Grid"}. ## ## @item @qcode{"Radius"} ## The maximum glyph radius (default 0.4). ## ## @item @qcode{"ObsLabels"} ## A character array or cell array of strings labeling the observations. The ## default is the observation numbers. ## @end table ## ## The optional output @var{g} is an @code{n}-by-3 matrix of handles whose ## columns hold, respectively, the star perimeters, the star spokes, and the ## text labels. ## ## @seealso{andrewsplot, parallelcoords} ## @end deftypefn function g = glyphplot (varargin) ## Optional leading figure handle parent = []; if (numel (varargin) > 0 && isscalar (varargin{1}) && ishghandle (varargin{1}) && strcmp (get (varargin{1}, "type"), "figure")) parent = varargin{1}; varargin(1) = []; endif if (numel (varargin) < 1) print_usage (); endif X = varargin{1}; varargin(1) = []; if (! isnumeric (X) || ! isreal (X) || ndims (X) > 2) error ("glyphplot: X must be a real numeric matrix."); endif n = rows (X); p = columns (X); ## Parse name/value options glyph = "star"; standardize = "column"; grid = []; centers = []; maxr = 0.4; obslabels = {}; if (mod (numel (varargin), 2) != 0) error ("glyphplot: name/value arguments must come in pairs."); endif for i = 1:2:numel (varargin) name = varargin{i}; value = varargin{i+1}; if (! ischar (name)) error ("glyphplot: property names must be strings."); endif switch (lower (name)) case "glyph" glyph = value; case "standardize" standardize = value; case "grid" grid = value; case "centers" centers = value; case "radius" maxr = value; case "obslabels" obslabels = value; otherwise error ("glyphplot: unknown property '%s'.", name); endswitch endfor if (strcmpi (glyph, "face")) error ("glyphplot: 'face' (Chernoff) glyphs are not yet supported."); elseif (! strcmpi (glyph, "star")) error ("glyphplot: Glyph must be 'star' or 'face'."); endif ## Scale the columns to spoke lengths in [0,1] switch (lower (standardize)) case "column" S = normalize01 (X, 1); case "matrix" S = normalize01 (X(:), 1); S = reshape (S, size (X)); case "pca" [~, sc] = pca (X); S = normalize01 (sc, 1); p = columns (S); case "off" S = X; otherwise error ("glyphplot: invalid Standardize option '%s'.", standardize); endswitch if (ischar (obslabels)) obslabels = cellstr (obslabels); endif if (isempty (obslabels)) obslabels = arrayfun (@num2str, (1:n)', "UniformOutput", false); endif ## Glyph centers if (! isempty (centers)) if (! isnumeric (centers) || rows (centers) != n || columns (centers) != 2) error ("glyphplot: Centers must be an n-by-2 numeric matrix."); endif cxy = centers; nrows = max (centers(:,2)); else if (isempty (grid)) ncols = ceil (sqrt (n)); nrows = ceil (n / ncols); else nrows = grid(1); ncols = grid(2); endif cxy = zeros (n, 2); for i = 1:n cxy(i,1) = mod (i - 1, ncols) + 1; cxy(i,2) = nrows - floor ((i - 1) / ncols); endfor endif hax = newplot (); old_hold = ishold (hax); hold (hax, "on"); ang = 2 * pi * (0:p-1) / p; g = zeros (n, 3); for i = 1:n cx = cxy(i,1); cy = cxy(i,2); r = maxr * (0.1 + 0.9 * S(i,:)); tx = cx + r .* cos (ang); ty = cy + r .* sin (ang); ## Perimeter (closed) g(i,1) = line (hax, [tx, tx(1)], [ty, ty(1)], "color", [0 0 1]); set (g(i,1), "userdata", [cx, cy, 1]); ## Spokes from the center to each tip sx = reshape ([cx * ones(1, p); tx; nan(1, p)], 1, []); sy = reshape ([cy * ones(1, p); ty; nan(1, p)], 1, []); g(i,2) = line (hax, sx, sy, "color", [0 0 1]); ## Text label below the glyph g(i,3) = text (hax, cx, cy - maxr * 1.2, obslabels{i}, ... "horizontalalignment", "center", "verticalalignment", "top"); endfor axis (hax, "equal"); axis (hax, "off"); if (! old_hold) hold (hax, "off"); endif if (nargout == 0) clear g; endif endfunction ## Scale to [0,1] along dimension dim (columns). Constant columns map to 0. function S = normalize01 (X, dim) lo = min (X, [], dim); hi = max (X, [], dim); rng = hi - lo; rng(rng == 0) = 1; S = (X - lo) ./ rng; endfunction %!demo %! ## Star plot of the first few cars in the carsmall data set. %! %! load carsmall; %! X = [Acceleration, Cylinders, Displacement, Horsepower, Weight]; %! glyphplot (X(1:9,:), "ObsLabels", cellstr (num2str ((1:9)'))); ## Test output %!test %! hf = figure ("visible", "off"); %! unwind_protect %! X = [1 4 2; 3 2 5; 5 5 1; 2 1 4]; %! g = glyphplot (X); %! assert_equal (size (g), [4, 3]); %! ## star perimeter of observation 1 (column standardize, radius 0.4) %! assert_equal (get (g(1,1), "xdata"), [1.04 0.845 0.935 1.04], 1e-4); %! assert_equal (get (g(1,1), "ydata"), [2 2.2685 1.8874 2], 1e-4); %! assert_equal (get (g(1,1), "userdata"), [1 2 1], 1e-12); %! ## observation 2 %! assert_equal (get (g(2,1), "xdata"), [2.22 1.935 1.8 2.22], 1e-4); %! assert_equal (get (g(2,1), "ydata"), [2 2.1126 1.6536 2], 1e-4); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # Radius option scales the glyph %! hf = figure ("visible", "off"); %! unwind_protect %! X = [1 4 2; 3 2 5; 5 5 1; 2 1 4]; %! g = glyphplot (X, "Radius", 0.8); %! ## perimeter x of obs 1 spoke 1: cx + 0.8*(0.1+0.9*0) = 1 + 0.08 %! assert_equal (get (g(1,1), "xdata")(1), 1.08, 1e-12); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error glyphplot () %!error glyphplot ({1}) %!error ... %! glyphplot (ones (3, 3), "Glyph", "face") %!error ... %! glyphplot (ones (3, 3), "Radius") %!error ... %! glyphplot (ones (3, 3), "bogus", 1) %!error ... %! glyphplot (ones (3, 3), "Standardize", "xxx") %!error ... %! glyphplot (ones (3, 3), "Centers", [1 2]) statistics-release-1.9.2/inst/Plotting/gplotmatrix.m000066400000000000000000000332541524624707500226630ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} gplotmatrix (@var{x}, @var{y}, @var{group}) ## @deftypefnx {statistics} {} gplotmatrix (@var{x}, [], @var{group}) ## @deftypefnx {statistics} {} gplotmatrix (@var{x}, @var{y}, @var{group}, @var{clr}, @var{sym}, @var{siz}) ## @deftypefnx {statistics} {} gplotmatrix (@dots{}, @var{doleg}, @var{dispopt}) ## @deftypefnx {statistics} {} gplotmatrix (@dots{}, @var{doleg}, @var{dispopt}, @var{xnam}, @var{ynam}) ## @deftypefnx {statistics} {} gplotmatrix (@var{parent}, @dots{}) ## @deftypefnx {statistics} {[@var{h}, @var{ax}, @var{bigax}] =} gplotmatrix (@dots{}) ## ## Create a matrix of scatter plots grouped by a categorical variable. ## ## @code{gplotmatrix (@var{x}, @var{y}, @var{group})} creates a matrix of ## scatter plots. Each subplot in the resulting figure is a scatter plot of a ## column of @var{x} against a column of @var{y}. If @var{x} is @code{n}-by-@var{p} ## and @var{y} is @code{n}-by-@var{q}, the resulting figure holds a ## @var{q}-by-@var{p} grid of subplots; the subplot in row @var{i} and column ## @var{j} plots @code{@var{x}(:,j)} on the horizontal axis against ## @code{@var{y}(:,i)} on the vertical axis. Points are grouped and colored ## according to @var{group}, which is a grouping variable (numeric, logical, ## character, string, or cell array of strings) with one entry per row of ## @var{x}. ## ## @code{gplotmatrix (@var{x}, [], @var{group})} is equivalent to ## @code{gplotmatrix (@var{x}, @var{x}, @var{group})} except that the diagonal of ## the @var{p}-by-@var{p} grid is replaced by grouped histograms of the columns ## of @var{x}. ## ## The appearance of the plot is controlled by further positional arguments: ## ## @table @asis ## @item @var{clr} ## Marker colors, given as a character vector of color specifiers (e.g. ## @qcode{"rgb"}) or as a matrix of RGB triplets, one row per group. Colors ## cycle if fewer are supplied than there are groups. ## ## @item @var{sym} ## Marker symbols, given as a character vector (e.g. @qcode{"o+x"}); defaults to ## @qcode{"."}. Symbols cycle if fewer are supplied than there are groups. ## ## @item @var{siz} ## Marker sizes, given as a numeric vector. Sizes cycle if fewer are supplied ## than there are groups. ## ## @item @var{doleg} ## Either @qcode{"on"} (default) to display a legend of the groups or ## @qcode{"off"} to suppress it. ## ## @item @var{dispopt} ## Controls the diagonal of the grid when @var{y} is empty: @qcode{"stairs"} ## (default) for grouped stairstep histograms, @qcode{"hist"} or ## @qcode{"grpbars"} for grouped bar histograms, @qcode{"none"} to leave the ## diagonal empty, or @qcode{"variable"} to write the variable names on the ## diagonal. ## ## @item @var{xnam}, @var{ynam} ## Character vectors or cell arrays of strings giving the names of the columns of ## @var{x} and @var{y}, used to label the outer axes. ## @end table ## ## An optional leading @var{parent} argument (a figure or uipanel handle) selects ## the container for the plot. ## ## The optional outputs are @var{h}, an array of handles to the plotted objects ## with size @var{ny}-by-@var{p}-by-@var{k} (where @var{ny} is the number of rows ## of the grid and @var{k} the number of groups); @var{ax}, the matrix of handles ## to the subplot axes (with an extra row of hidden axes for the diagonal ## histograms); and @var{bigax}, the handle to the invisible enclosing axes used ## for titles and labels. ## ## @seealso{gscatter, plotmatrix, grpstats} ## @end deftypefn function [h, ax, bigax] = gplotmatrix (varargin) ## Optional leading parent (figure or uipanel) handle parent = []; if (numel (varargin) > 0 && isscalar (varargin{1}) && ishghandle (varargin{1}) && any (strcmp (get (varargin{1}, "type"), {"figure", "uipanel"}))) parent = varargin{1}; varargin(1) = []; endif if (numel (varargin) < 1) print_usage (); endif ## Positional arguments X = varargin{1}; Y = []; group = []; clr = []; sym = []; siz = []; doleg = "on"; dispopt = "stairs"; xnam = []; ynam = []; nv = numel (varargin); if (nv >= 2), Y = varargin{2}; endif if (nv >= 3), group = varargin{3}; endif if (nv >= 4), clr = varargin{4}; endif if (nv >= 5), sym = varargin{5}; endif if (nv >= 6), siz = varargin{6}; endif if (nv >= 7 && ! isempty (varargin{7})), doleg = varargin{7}; endif if (nv >= 8 && ! isempty (varargin{8})), dispopt = varargin{8}; endif if (nv >= 9), xnam = varargin{9}; endif if (nv >= 10), ynam = varargin{10}; endif if (nv > 10) error ("gplotmatrix: too many input arguments."); endif if (! isnumeric (X) || ! isreal (X) || ! ismatrix (X) || ndims (X) > 2) error ("gplotmatrix: X must be a real numeric matrix."); endif n = rows (X); p = columns (X); ## Y empty selects the self plot with histograms on the diagonal do_hist = isempty (Y); if (do_hist) Y = X; elseif (! isnumeric (Y) || ! isreal (Y) || ndims (Y) > 2) error ("gplotmatrix: Y must be a real numeric matrix."); elseif (rows (Y) != n) error ("gplotmatrix: X and Y must have the same number of rows."); endif ny = columns (Y); ## Grouping variable if (isempty (group)) gidx = ones (n, 1); gnames = {"1"}; else [gidx, gnames] = grp2idx (group); if (numel (gidx) != n) error ("gplotmatrix: GROUP must have one entry per row of X."); endif endif k = numel (gnames); ## Per-group color, symbol, and size gcol = expand_color (clr, k); gsym = expand_sym (sym, k); gsiz = expand_siz (siz, k); ## Names for the outer axes xnam = name_list (xnam, p); ynam = name_list (ynam, ny); ## Container to draw into if (isempty (parent)) parent = gcf (); endif clf (parent); ## Grid geometry (row 1 at the top, column 1 at the left) Lm = 0.10; Bm = 0.10; Wt = 0.86; Ht = 0.86; gap = 0.015; cw = Wt / p; chh = Ht / ny; ax = zeros (ny, p); histax = zeros (1, p); h = []; glines = []; legax = []; for r = 1:ny for c = 1:p x0 = Lm + (c - 1) * cw; y0 = Bm + (ny - r) * chh; pos = [x0 + gap/2, y0 + gap/2, cw - gap, chh - gap]; a = axes ("parent", parent, "position", pos, "box", "on", ... "nextplot", "add"); ax(r,c) = a; if (do_hist && r == c) ## Diagonal: grouped histogram drawn in an overlay axes ah = axes ("parent", parent, "position", pos, "color", "none", ... "nextplot", "add", "xtick", [], "ytick", []); histax(c) = ah; for l = 1:k h(r,c,l) = draw_diag (ah, X(gidx == l, c), dispopt, ... gcol(l,:), xnam{c}); endfor set (a, "xtick", [], "ytick", []); else cell_lines = zeros (1, k); for l = 1:k idx = (gidx == l); hl = line (a, X(idx, c), Y(idx, r), "linestyle", "none", ... "marker", gsym(l), "markersize", gsiz(l), ... "color", gcol(l,:)); h(r,c,l) = hl; cell_lines(l) = hl; endfor if (isempty (glines)) glines = cell_lines; legax = a; endif endif ## Only the outer edges carry tick labels if (r != ny) set (a, "xticklabel", []); endif if (c != 1) set (a, "yticklabel", []); endif if (r == ny && ! isempty (xnam{c})) xlabel (a, xnam{c}); endif if (c == 1 && ! isempty (ynam{r})) ylabel (a, ynam{r}); endif endfor endfor ## Enclosing invisible axes for titles and overall labels bigax = axes ("parent", parent, "position", [Lm, Bm, Wt, Ht], ... "visible", "off", "xtick", [], "ytick", []); ## Legend of the groups if (strcmpi (doleg, "on") && k > 1 && ! isempty (glines)) warning ("off", "Octave:legend:unimplemented-location", "local"); legend (legax, glines, gnames, "location", "best"); endif if (nargout == 0) clear h ax bigax; elseif (do_hist) ax = [ax; histax]; endif endfunction ## Expand a color specification to a k-by-3 matrix of RGB triplets. function gcol = expand_color (clr, k) if (isempty (clr)) base = lines (k); elseif (ischar (clr)) base = zeros (numel (clr), 3); for i = 1:numel (clr) base(i,:) = char2rgb (clr(i)); endfor elseif (isnumeric (clr) && columns (clr) == 3) base = clr; else error ("gplotmatrix: CLR must be a color string or an n-by-3 RGB matrix."); endif idx = mod (0:k-1, rows (base)) + 1; gcol = base(idx, :); endfunction function rgb = char2rgb (ch) switch (ch) case "r", rgb = [1 0 0]; case "g", rgb = [0 1 0]; case "b", rgb = [0 0 1]; case "c", rgb = [0 1 1]; case "m", rgb = [1 0 1]; case "y", rgb = [1 1 0]; case "k", rgb = [0 0 0]; case "w", rgb = [1 1 1]; otherwise error ("gplotmatrix: unknown color '%s'.", ch); endswitch endfunction ## Expand a marker specification to a k-element character vector. function gsym = expand_sym (sym, k) if (isempty (sym)) sym = "."; elseif (! ischar (sym)) error ("gplotmatrix: SYM must be a character vector of markers."); endif idx = mod (0:k-1, numel (sym)) + 1; gsym = sym(idx); endfunction ## Expand a size specification to a k-element numeric vector. function gsiz = expand_siz (siz, k) if (isempty (siz)) siz = 6; elseif (! isnumeric (siz) || ! isreal (siz)) error ("gplotmatrix: SIZ must be a numeric vector of marker sizes."); endif idx = mod (0:k-1, numel (siz)) + 1; gsiz = siz(idx); endfunction ## Normalize a name specification to a cell array of p strings. function nm = name_list (names, p) if (isempty (names)) nm = repmat ({""}, 1, p); elseif (ischar (names)) nm = cellstr (names).'; elseif (iscellstr (names)) nm = names(:).'; else error ("gplotmatrix: variable names must be strings."); endif if (numel (nm) < p) nm(end+1:p) = {""}; endif endfunction ## Draw one group's contribution to a diagonal histogram cell. function ho = draw_diag (ah, data, dispopt, col, vname) switch (lower (dispopt)) case "none" ho = line (ah, NaN, NaN, "linestyle", "none"); case "variable" ho = text (ah, 0.5, 0.5, vname, "parent", ah, ... "units", "normalized", "horizontalalignment", "center"); otherwise if (isempty (data) || all (isnan (data))) ho = line (ah, NaN, NaN, "linestyle", "none"); return; endif [nn, xx] = hist (data, 10); switch (lower (dispopt)) case {"hist", "grpbars"} ho = bar (ah, xx, nn, 1.0, "facecolor", col, "edgecolor", col); otherwise # "stairs" e = xx(1:end-1) + diff (xx) / 2; xe = [xx(1)-(xx(2)-xx(1))/2, e, xx(end)+(xx(end)-xx(end-1))/2]; ho = stairs (ah, xe, [nn, nn(end)], "color", col); endswitch endswitch endfunction %!demo %! ## Grouped scatter-plot matrix of Fisher's iris measurements. %! %! load fisheriris; %! gplotmatrix (meas, [], species); %!demo %! ## Two sets of variables plotted against each other by group. %! %! load fisheriris; %! gplotmatrix (meas(:,1:2), meas(:,3:4), species); ## Test output shapes and scatter orientation %!test %! hf = figure ("visible", "off"); %! unwind_protect %! X = [10 20; 11 25; 12 21; 13 28; 14 23; 15 29]; %! g = [1 1 1 2 2 2]'; %! [h, ax, bigax] = gplotmatrix (X, [], g); %! assert_equal (size (h), [2, 2, 2]); %! assert_equal (size (ax), [3, 2]); %! assert_equal (isscalar (bigax) && isaxes (bigax), true); %! assert_equal (get (h(1,2,1), "xdata"), [20 25 21]); %! assert_equal (get (h(1,2,1), "ydata"), [10 11 12]); %! assert_equal (get (h(2,1,1), "xdata"), [10 11 12]); %! assert_equal (get (h(2,1,1), "ydata"), [20 25 21]); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ("visible", "off"); %! unwind_protect %! X = [10 20; 11 25; 12 21; 13 28; 14 23; 15 29]; %! Y = [100 200 300; 110 250 280; 120 210 260; ... %! 130 280 240; 140 230 220; 150 290 210]; %! g = [1 1 1 2 2 2]'; %! [h, ax] = gplotmatrix (X, Y, g); %! assert_equal (size (h), [3, 2, 2]); %! assert_equal (size (ax), [3, 2]); %! assert_equal (get (h(1,2,1), "xdata"), [20 25 21]); %! assert_equal (get (h(1,2,1), "ydata"), [100 110 120]); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # runs without a grouping variable %! hf = figure ("visible", "off"); %! unwind_protect %! [h, ax] = gplotmatrix (randn (20, 3), [], []); %! assert_equal (size (h), [3, 3]); %! assert_equal (size (ax), [4, 3]); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error gplotmatrix () %!error gplotmatrix ({1}) %!error ... %! gplotmatrix (ones (5, 2), ones (4, 2), ones (5, 1)) %!error ... %! gplotmatrix (ones (5, 2), [], ones (4, 1)) %!error ... %! gplotmatrix (ones (5, 2), [], ones (5, 1), "r", ".", 6, "on", "hist", ... %! "a", "b", "c") statistics-release-1.9.2/inst/Plotting/gscatter.m000066400000000000000000000203021524624707500221130ustar00rootroot00000000000000## Copyright (C) 2021 Stefano Guidoni ## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} gscatter (@var{x}, @var{y}, @var{g}) ## @deftypefnx {statistics} {} gscatter (@var{x}, @var{y}, @var{g}, @var{clr}, @var{sym}, @var{siz}) ## @deftypefnx {statistics} {} gscatter (@dots{}, @var{doleg}, @var{xnam}, @var{ynam}) ## @deftypefnx {statistics} {@var{h} =} gscatter (@dots{}) ## ## Draw a scatter plot with grouped data. ## ## @code{gscatter} is a utility function to draw a scatter plot of @var{x} and ## @var{y}, according to the groups defined by @var{g}. Input @var{x} and ## @var{y} are numeric vectors of the same size, while @var{g} is either a ## vector of the same size as @var{x} or a character matrix with the same number ## of rows as the size of @var{x}. As a vector @var{g} can be numeric, logical, ## a character array, a string array (not implemented), a cell string or cell ## array. ## ## A number of optional inputs change the appearance of the plot: ## @itemize @bullet ## @item @var{"clr"} ## defines the color for each group; if not enough colors are defined by ## @var{"clr"}, @code{gscatter} cycles through the specified colors. Colors can ## be defined as named colors, as rgb triplets or as indices for the current ## @code{colormap}. The default value is a different color for each group, ## according to the current @code{colormap}. ## ## @item @var{"sym"} ## is a char array of symbols for each group; if not enough symbols are defined ## by @var{"sym"}, @code{gscatter} cycles through the specified symbols. ## ## @item @var{"siz"} ## is a numeric array of sizes for each group; if not enough sizes are defined ## by @var{"siz"}, @code{gscatter} cycles through the specified sizes. ## ## @item @var{"doleg"} ## is a boolean value to show the legend; it can be either @qcode{on} (default) ## or @qcode{off}. ## ## @item @var{"xnam"} ## is a character array, the name for the x axis. ## ## @item @var{"ynam"} ## is a character array, the name for the y axis. ## @end itemize ## ## Output @var{h} is an array of graphics handles to the @code{line} object of ## each group. ## ## @seealso{scatter} ## ## @end deftypefn function h = gscatter (varargin) ## optional axes handle if (isaxes (varargin{1})) ## parameter is an axes handle hax = varargin{1}; varargin = varargin(2:end); nargin--; endif ## check the input parameters if (nargin < 3) print_usage (); endif ## ## necessary parameters ## ## x coordinates if (isvector (varargin{1}) && isnumeric (varargin{1})) x = varargin{1}; n = numel (x); else error ("gscatter: x must be a numeric vector"); endif ## y coordinates if (isvector (varargin{2}) && isnumeric (varargin{2})) if (numel (varargin{2}) == n) y = varargin{2}; else error ("gscatter: x and y must have the same size"); endif else error ("gscatter: y must be a numeric vector"); endif ## groups if (isrow (varargin{3})) varargin{3} = transpose (varargin{3}); endif if (ismatrix (varargin{3}) && ischar (varargin{3})) varargin{3} = cellstr (varargin{3}); # char matrix to cellstr elseif (iscell (varargin{3}) && ! iscellstr (varargin{3})) varargin{3} = cell2mat (varargin{3}); # numeric cell to vector endif if (isvector (varargin{3})) # only numeric vectors or cellstr if (rows (varargin{3}) == n) gv = varargin{3}; if (iscellstr (gv)) g_names = unique (gv); # avoid warning else g_names = unique (gv, 'rows'); endif g_len = numel (g_names); if (iscellstr (g_names)) for i = 1 : g_len g(find (strcmp (gv, g_names{i}))) = i; endfor else for i = 1 : g_len g(find (gv == g_names(i))) = i; endfor endif else error ("gscatter: g must have the same size as x and y"); endif else error (strcat ("gscatter: g must be a numeric or logical or char vector,", " or a cell or cellstr array, or a char matrix")); endif ## ## optional parameters ## ## Note: this parameters are passed as they are to 'line', ## the validity check is delegated to 'line' g_col = lines (g_len); g_size = 6 * ones (g_len, 1); g_sym = repmat ('o', 1, g_len); ## optional parameters for legend and axes labels do_legend = 1; # legend shown by default ## MATLAB compatibility: by default MATLAB uses the variable name as ## label for either axis mygetname = @(x) inputname (1); # to retrieve the name of a variable x_nam = mygetname(varargin{1}); # this should retrieve the name of the var, y_nam = mygetname(varargin{2}); # but it does not work ## parameters are all in fixed positions for i = 4 : nargin switch (i) case 4 ## colours c_list = varargin{4}; if (isrow (c_list)) c_list = transpose (c_list); endif c_list_len = rows (c_list); g_col = repmat (c_list, ceil (g_len / c_list_len)); case {5, 6} ## size and symbols s_list = varargin{i}; s_list_len = length (s_list); g_tmp = repmat (s_list, ceil (g_len / s_list_len)); if (i == 6) g_size = g_tmp; else g_sym = g_tmp; endif case 7 ## legend switch (lower (varargin{7})) case 'on' do_legend = 1; case 'off' do_legend = 0; otherwise error ("gscatter: invalid dolegend parameter '%s'", varargin{7}); endswitch case {8, 9} ## x and y label if (! ischar (varargin{i}) && ! isvector (varargin{i})) error ("gscatter: xnam and ynam must be strings"); endif if (i == 8) x_nam = varargin{8}; else y_nam = varargin{9}; endif endswitch endfor ## scatter plot with grouping if (! exist ('hax', 'var')) hax = gca (); endif ## return value h = []; hold on; for i = 1 : g_len idcs = find (g == i); h(i) = line (hax, x(idcs), y(idcs), 'linestyle', 'none', ... 'markersize', g_size(i), 'color', g_col(i,:), 'marker', g_sym(i)); endfor if (do_legend) if (isnumeric (g_names)) g_names = num2str (g_names); endif warning ("off", 'Octave:legend:unimplemented-location', 'local'); legend (hax, g_names, 'location', 'best'); endif xlabel (hax, x_nam); ylabel (hax, y_nam); hold off; endfunction %!demo %! load fisheriris; %! X = meas(:,3:4); %! cidcs = kmeans (X, 3, 'Replicates', 5); %! gscatter (X(:,1), X(:,2), cidcs, [.75 .75 0; 0 .75 .75; .75 0 .75], 'os^'); %! title ('Fisher''s iris data'); ## Test plotting %!shared visibility_setting %! visibility_setting = get (0, 'DefaultFigureVisible'); %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! load fisheriris; %! X = meas(:,3:4); %! cidcs = kmeans (X, 3, 'Replicates', 5); %! gscatter (X(:,1), X(:,2), cidcs, [.75 .75 0; 0 .75 .75; .75 0 .75], 'os^'); %! title ('Fisher''s iris data'); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error gscatter (); %!error gscatter ([1]); %!error gscatter ([1], [2]); %!error gscatter ('abc', [1 2 3], [1]); %!error gscatter ([1 2 3], [1 2], [1]); %!error gscatter ([1 2 3], 'abc', [1]); %!error gscatter ([1 2], [1 2], [1]); %!error gscatter ([1 2], [1 2], [1 2], 'rb', 'so', 12, 'xxx'); statistics-release-1.9.2/inst/Plotting/hist3.m000066400000000000000000000314711524624707500213420ustar00rootroot00000000000000## Copyright (C) 2015 Carnë Draug ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {} hist3 (@var{X}) ## @deftypefnx {statistics} {} hist3 (@var{X}, @var{nbins}) ## @deftypefnx {statistics} {} hist3 (@var{X}, @code{"Nbins"}, @var{nbins}) ## @deftypefnx {statistics} {} hist3 (@var{X}, @var{centers}) ## @deftypefnx {statistics} {} hist3 (@var{X}, @code{"Ctrs"}, @var{centers}) ## @deftypefnx {statistics} {} hist3 (@var{X}, @code{"Edges"}, @var{edges}) ## @deftypefnx {statistics} {[@var{N}, @var{C}] =} hist3 (@dots{}) ## @deftypefnx {statistics} {} hist3 (@dots{}, @var{prop}, @var{val}, @dots{}) ## @deftypefnx {statistics} {} hist3 (@var{hax}, @dots{}) ## ## Produce bivariate (2D) histogram counts or plots. ## ## The elements to produce the histogram are taken from the Nx2 matrix ## @var{X}. Any row with NaN values are ignored. The actual bins can ## be configured in 3 different: number, centers, or edges of the bins: ## ## @table @asis ## @item Number of bins (default) ## Produces equally spaced bins between the minimum and maximum values ## of @var{X}. Defined as a 2 element vector, @var{nbins}, one for each ## dimension. Defaults to @code{[10 10]}. ## ## @item Center of bins ## Defined as a cell array of 2 monotonically increasing vectors, ## @var{centers}. The width of each bin is determined from the adjacent ## values in the vector with the initial and final bin, extending to Infinity. ## ## @item Edge of bins ## Defined as a cell array of 2 monotonically increasing vectors, ## @var{edges}. @code{@var{N}(i,j)} contains the number of elements ## in @var{X} for which: ## ## @itemize @w{} ## @item ## @var{edges}@{1@}(i) <= @var{X}(:,1) < @var{edges}@{1@}(i+1) ## @item ## @var{edges}@{2@}(j) <= @var{X}(:,2) < @var{edges}@{2@}(j+1) ## @end itemize ## ## The consequence of this definition is that values outside the initial ## and final edge values are ignored, and that the final bin only contains ## the number of elements exactly equal to the final edge. ## ## @end table ## ## The return values, @var{N} and @var{C}, are the bin counts and centers ## respectively. These are specially useful to produce intensity maps: ## ## @example ## [counts, centers] = hist3 (data); ## imagesc (centers@{1@}, centers@{2@}, counts) ## @end example ## ## If there is no output argument, or if the axes graphics handle ## @var{hax} is defined, the function will plot a 3 dimensional bar ## graph. Any extra property/value pairs are passed directly to the ## underlying surface object. ## ## @seealso{hist, histc, lookup, mesh} ## @end deftypefn function [N, C] = hist3 (X, varargin) if (nargin < 1) print_usage (); endif next_argin = 1; should_draw = true; if (isaxes (X)) hax = X; X = varargin{next_argin++}; elseif (nargout == 0) hax = gca (); else should_draw = false; endif if (! ismatrix (X) || columns (X) != 2) error ("hist3: X must be a 2 columns matrix"); endif method = 'nbins'; val = [10 10]; if (numel (varargin) >= next_argin) this_arg = varargin{next_argin++}; if (isnumeric (this_arg)) method = 'nbins'; val = this_arg; elseif (iscell (this_arg)) method = 'ctrs'; val = this_arg; elseif (numel (varargin) >= next_argin && any (strcmpi ({'nbins', 'ctrs', 'edges'}, this_arg))) method = tolower (this_arg); val = varargin{next_argin++}; else next_argin--; endif endif have_centers = false; switch (tolower (method)) case 'nbins' [r_edges, c_edges] = edges_from_nbins (X, val); case 'ctrs' have_centers = true; centers = val; [r_edges, c_edges] = edges_from_centers (val); case 'centers' ## This was supported until 1.2.4 when the Matlab compatible option ## 'Ctrs' was added. persistent warned = false; if (! warned) warning ("hist3: option `centers' is deprecated. Use `ctrs'"); endif have_centers = true; centers = val; [r_edges, c_edges] = edges_from_centers (val); case 'edges' if (! iscell (val) || numel (val) != 2 || ! all (cellfun (@isvector, val))) error ("hist3: EDGES must be a cell array with 2 vectors"); endif [r_edges] = vec (val{1}, 2); [c_edges] = vec (val{2}, 2); out_rows = any (X < [r_edges(1) c_edges(1)] | X > [r_edges(end) c_edges(end)], 2); X(out_rows,:) = []; otherwise ## we should never get here... error ("hist3: invalid binning method `%s'", method); endswitch ## We only remove the NaN now, after having computed the bin edges, ## because the extremes from each column that define the edges may ## be paired with a NaN. While such values do not appear on the ## histogram, they must still be used to compute the histogram ## edges. X(any (isnan (X), 2), :) = []; r_idx = lookup (r_edges, X(:,1), 'l'); c_idx = lookup (c_edges, X(:,2), 'l'); counts_size = [numel(r_edges) numel(c_edges)]; counts = accumarray ([r_idx, c_idx], 1, counts_size); if (should_draw) counts = counts.'; z = zeros ((size (counts) +1) *2); z(2:end-1,2:end-1) = kron (counts, ones (2, 2)); ## Setting the values for the end of the histogram bin like this ## seems straight wrong but that's how Matlab plots look. y = [kron(c_edges, ones (1, 2)) (c_edges(end)*2-c_edges(end-1))([1 1])]; x = [kron(r_edges, ones (1, 2)) (r_edges(end)*2-r_edges(end-1))([1 1])]; mesh (hax, x, y, z, 'facecolor', [.75 .85 .95], varargin{next_argin:end}); else N = counts; if (nargout > 1) if (! have_centers) C = {(r_edges + [diff(r_edges)([1:end end])]/ 2) ... (c_edges + [diff(c_edges)([1:end end])]/ 2)}; else C = centers(:)'; C{1} = vec (C{1}, 2); C{2} = vec (C{2}, 2); endif endif endif endfunction function [r_edges, c_edges] = edges_from_nbins (X, nbins) if (! isnumeric (nbins) || numel (nbins) != 2) error ("hist3: NBINS must be a 2 element vector"); endif inits = min (X, [], 1); ends = max (X, [], 1); ends -= (ends - inits) ./ vec (nbins, 2); ## If any histogram side has an empty range, then still make NBINS ## but then place that value at the centre of the centre bin so that ## they appear in the centre in the plot. single_bins = inits == ends; if (any (single_bins)) inits(single_bins) -= (floor (nbins(single_bins) ./2)) + 0.5; ends(single_bins) = inits(single_bins) + nbins(single_bins) -1; endif r_edges = linspace (inits(1), ends(1), nbins(1)); c_edges = linspace (inits(2), ends(2), nbins(2)); endfunction function [r_edges, c_edges] = edges_from_centers (ctrs) if (! iscell (ctrs) || numel (ctrs) != 2 || ! all (cellfun (@isvector, ctrs))) error ("hist3: CTRS must be a cell array with 2 vectors"); endif r_edges = vec (ctrs{1}, 2); c_edges = vec (ctrs{2}, 2); r_edges(2:end) -= diff (r_edges) / 2; c_edges(2:end) -= diff (c_edges) / 2; endfunction %!demo %! X = [ %! 1 1 %! 1 1 %! 1 10 %! 1 10 %! 5 5 %! 5 5 %! 5 5 %! 5 5 %! 5 5 %! 7 3 %! 7 3 %! 7 3 %! 10 10 %! 10 10]; %! hist3 (X) %!test %! N_exp = [ 0 0 0 5 20 %! 0 0 10 15 0 %! 0 15 10 0 0 %! 20 5 0 0 0]; %! %! n = 100; %! x = [1:n]'; %! y = [n:-1:1]'; %! D = [x y]; %! N = hist3 (D, [4 5]); %! assert_equal (N, N_exp); %!test %! N_exp = [0 0 0 0 1 %! 0 0 0 0 1 %! 0 0 0 0 1 %! 1 1 1 1 93]; %! %! n = 100; %! x = [1:n]'; %! y = [n:-1:1]'; %! D = [x y]; %! C{1} = [1 1.7 3 4]; %! C{2} = [1:5]; %! N = hist3 (D, C); %! assert_equal (N, N_exp); ## bug 44987 %!test %! D = [1 1; 3 1; 3 3; 3 1]; %! [c, nn] = hist3 (D, {0:4, 0:4}); %! exp_c = zeros (5); %! exp_c([7 9 19]) = [1 2 1]; %! assert_equal (c, exp_c); %! assert_equal (nn, {0:4, 0:4}); %!test %! for i = 10 %! assert_equal (size (hist3 (rand (9, 2), 'Edges', {[0:.2:1]; [0:.2:1]})), [6 6]) %! endfor %!test %! edge_1 = linspace (0, 10, 10); %! edge_2 = linspace (0, 50, 10); %! [c, nn] = hist3 ([1:10; 1:5:50]', 'Edges', {edge_1, edge_2}); %! exp_c = zeros (10, 10); %! exp_c([1 12 13 24 35 46 57 68 79 90]) = 1; %! assert_equal (c, exp_c); %! %! assert_equal (nn{1}, edge_1 + edge_1(2)/2, eps*10^4) %! assert_equal (nn{2}, edge_2 + edge_2(2)/2, eps*10^4) %!shared X %! X = [ %! 5 2 %! 5 3 %! 1 4 %! 5 3 %! 4 4 %! 1 2 %! 2 3 %! 3 3 %! 5 4 %! 5 3]; %!test %! N = zeros (10); %! N([1 10 53 56 60 91 98 100]) = [1 1 1 1 3 1 1 1]; %! C = {(1.2:0.4:4.8), (2.1:0.2:3.9)}; %! assert_equal (nthargout ([1 2], @hist3, X), {N C}, eps*10^3) %!test %! N = zeros (5, 7); %! N([1 5 17 18 20 31 34 35]) = [1 1 1 1 3 1 1 1]; %! C = {(1.4:0.8:4.6), ((2+(1/7)):(2/7):(4-(1/7)))}; %! assert_equal (nthargout ([1 2], @hist3, X, [5 7]), {N C}, eps*10^3) %! assert_equal (nthargout ([1 2], @hist3, X, 'Nbins', [5 7]), {N C}, eps*10^3) %!test %! N = [0 1 0; 0 1 0; 0 0 1; 0 0 0]; %! C = {(2:5), (2.5:1:4.5)}; %! assert_equal (nthargout ([1 2], @hist3, X, 'Edges', {(1.5:4.5), (2:4)}), {N C}) %!test %! N = [0 0 1 0 1 0; 0 0 0 1 0 0; 0 0 1 4 2 0]; %! C = {(1.2:3.2), (0:5)}; %! assert_equal (nthargout ([1 2], @hist3, X, 'Ctrs', C), {N C}) %! assert_equal (nthargout ([1 2], @hist3, X, C), {N C}) %!test %! [~, C] = hist3 (rand (10, 2), 'Edges', {[0 .05 .15 .35 .55 .95], %! [-1 .05 .07 .2 .3 .5 .89 1.2]}); %! C_exp = {[ 0.025 0.1 0.25 0.45 0.75 1.15], ... %! [-0.475 0.06 0.135 0.25 0.4 0.695 1.045 1.355]}; %! assert_equal (C, C_exp, eps*10^2) ## Test how handling of out of borders is different whether we are ## defining Centers or Edges. %!test %! Xv = repmat ([1:10]', [1 2]); %! %! ## Test Centers %! assert_equal (hist3 (Xv, 'Ctrs', {1:10, 1:10}), eye (10)) %! %! N_exp = eye (6); %! N_exp([1 end]) = 3; %! assert_equal (hist3 (Xv, 'Ctrs', {3:8, 3:8}), N_exp) %! %! N_exp = zeros (8, 6); %! N_exp([1 2 11 20 29 38 47 48]) = [2 1 1 1 1 1 1 2]; %! assert_equal (hist3 (Xv, 'Ctrs', {2:9, 3:8}), N_exp) %! %! ## Test Edges %! assert_equal (hist3 (Xv, 'Edges', {1:10, 1:10}), eye (10)) %! assert_equal (hist3 (Xv, 'Edges', {3:8, 3:8}), eye (6)) %! assert_equal (hist3 (Xv, 'Edges', {2:9, 3:8}), [zeros(1, 6); eye(6); zeros(1, 6)]) %! %! N_exp = zeros (14); %! N_exp(3:12, 3:12) = eye (10); %! assert_equal (hist3 (Xv, 'Edges', {-1:12, -1:12}), N_exp) %! %! ## Test for Nbins %! assert_equal (hist3 (Xv), eye (10)) %! assert_equal (hist3 (Xv, [10 10]), eye (10)) %! assert_equal (hist3 (Xv, 'nbins', [10 10]), eye (10)) %! assert_equal (hist3 (Xv, [5 5]), eye (5) * 2) %! %! N_exp = zeros (7, 5); %! N_exp([1 9 10 18 26 27 35]) = [2 1 1 2 1 1 2]; %! assert_equal (hist3 (Xv, [7 5]), N_exp) %!test # bug #51059 %! D = [1 1; NaN 2; 3 1; 3 3; 1 NaN; 3 1]; %! [c, nn] = hist3 (D, {0:4, 0:4}); %! exp_c = zeros (5); %! exp_c([7 9 19]) = [1 2 1]; %! assert_equal (c, exp_c) %! assert_equal (nn, {0:4, 0:4}) ## Single row of data or cases where all elements have the same value ## on one side of the histogram. %!test %! [c, nn] = hist3 ([1 8]); %! exp_c = zeros (10, 10); %! exp_c(6, 6) = 1; %! exp_nn = {-4:5, 3:12}; %! assert_equal (c, exp_c) %! assert_equal (nn, exp_nn, eps) %! %! [c, nn] = hist3 ([1 8], [10 11]); %! exp_c = zeros (10, 11); %! exp_c(6, 6) = 1; %! exp_nn = {-4:5, 3:13}; %! assert_equal (c, exp_c) %! assert_equal (nn, exp_nn, eps) ## NaNs paired with values defining the histogram edges. %!test %! [c, nn] = hist3 ([1 NaN; 2 3; 6 9; 8 NaN]); %! exp_c = zeros (10, 10); %! exp_c(2, 1) = 1; %! exp_c(8, 10) = 1; %! exp_nn = {linspace(1.35, 7.65, 10) linspace(3.3, 8.7, 10)}; %! assert_equal (c, exp_c) %! assert_equal (nn, exp_nn, eps*100) ## Columns full of NaNs (recent Matlab versions seem to throw an error ## but this did work like this on R2010b at least). %!test %! [c, nn] = hist3 ([1 NaN; 2 NaN; 6 NaN; 8 NaN]); %! exp_c = zeros (10, 10); %! exp_nn = {linspace(1.35, 7.65, 10) NaN(1, 10)}; %! assert_equal (c, exp_c) %! assert_equal (nn, exp_nn, eps*100) ## Behaviour of an empty X after removal of rows with NaN. %!test %! [c, nn] = hist3 ([1 NaN; NaN 3; NaN 9; 8 NaN]); %! exp_c = zeros (10, 10); %! exp_nn = {linspace(1.35, 7.65, 10) linspace(3.3, 8.7, 10)}; %! assert_equal (c, exp_c) %! assert_equal (nn, exp_nn, eps*100) statistics-release-1.9.2/inst/Plotting/histfit.m000066400000000000000000000207711524624707500217630ustar00rootroot00000000000000## Copyright (C) 2003 Alberto Terruzzi ## Copyright (C) 2022-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} histfit (@var{x}) ## @deftypefnx {statistics} {} histfit (@var{x}, @var{nbins}) ## @deftypefnx {statistics} {} histfit (@var{x}, @var{nbins}, @var{distname}) ## @deftypefnx {statistics} {} histfit (@var{ax}, @dots{}) ## @deftypefnx {statistics} {@var{h} =} histfit (@dots{}) ## ## Plot histogram with superimposed distribution fit. ## ## @code{histfit (@var{x})} plots a histogram of the values in the vector ## @var{x} using the number of bins equal to the square root of the number of ## non-missing elements in @var{x} and superimposes a fitted normal density ## function. ## ## @code{histfit (@var{x}, @var{nbins})} plots a histogram of the values in the ## vector @var{x} using @var{nbins} number of bins in the histogram and ## superimposes a fitted normal density function. ## ## @code{histfit (@var{x}, @var{nbins}, @var{distname})} plots a histogram of ## the values in the vector @var{x} using @var{nbins} number of bins in the ## histogram and superimposes a fitted density function from the distribution ## specified by @var{distname}. ## ## @code{histfit (@var{ax}, @dots{})} uses the axes handle @var{ax} to plot the ## histogram and the fitted density function onto followed by any of the input ## argument combinations specified in the previous syntaxes. ## ## @code{@var{h} = histfit (@dots{})} returns a vector of handles @var{h}, where ## @qcode{@var{h}(1)} is the handle to the histogram and @qcode{@var{h}(2)} is ## the handle to the density curve. ## ## Note: calling @code{histfit} without any input arguments will return a cell ## array of character vectors listing all supported distributions. ## ## @seealso{bar, hist, normplot, fitdist} ## @end deftypefn function [varargout] = histfit (varargin) ## Add list of supported probability distribution objects PDO = {'Beta'; 'BirnbaumSaunders'; 'Burr'; 'Exponential'; 'ExtremeValue'; ... 'Gamma'; 'GeneralizedExtremeValue'; 'GeneralizedPareto'; ... 'InverseGaussian'; 'Logistic'; 'Loglogistic'; 'Lognormal'; ... 'Nakagami'; 'NegativeBinomial'; 'Normal'; 'Poisson'; 'Rayleigh'; ... 'Rician'; 'tLocationScale'; 'Weibull'}; ABBR = {'bisa'; 'ev'; 'gev'; 'gp'; 'invg'; 'nbin'; 'tls'; 'wbl'}; ## Check for zero input arguments if (numel (varargin) < 1) varargout{1} = PDO; return endif ## Check for axes handle if (isaxes (varargin{1})) ax = varargin{1}; varargin(1) = []; get_current_axes = false; else get_current_axes = true; endif ## Get data if (numel (varargin) < 1) error ("histfit: too few input arguments."); else x = varargin{1}; if (! isnumeric (x) || ! isreal (x) || ! isvector (x) || isscalar (x)) error ("histfit: X must be a numeric vector of real numbers."); endif ## Remove missing values x(isnan (x)) = []; xsize = numel (x); ## Check for valid data if (xsize < 1) error ("histfit: no data in X."); endif endif ## Get nbins if (numel (varargin) > 1) nbins = varargin{2}; if (! (isreal (nbins) && isscalar (nbins) && fix (nbins) == nbins)) error ("histfit: NBINS must be a real scalar integer value."); endif else nbins = ceil (sqrt (xsize)); endif ## Get distribution if (numel (varargin) > 2) distname = varargin{3}; ## Check distribution name if (! (ischar (distname) && size (distname, 1) == 1)) error ("histfit: DISTNAME must be a character vector."); elseif (strcmpi (distname, 'kernel')) error ("histfit: 'Kernel' distribution is not supported yet."); elseif (! (any (strcmpi (distname, PDO)) || any (strcmpi (distname, ABBR)))) error ("histfit: unrecognized distribution name."); endif else distname = 'normal'; endif ## Create axes handle (if necessary) if (get_current_axes) ax = gca (); endif ## Plot the histogram if (any (strcmpi (distname, {'poisson', 'NegativeBinomial', 'nbin'}))) binwidth = 1; xmin = min (x) - 1; xmax = max (x) + 1; [binsize, bincenter] = hist (x, [xmin:xmax]); else [binsize, bincenter] = hist (x, nbins); binwidth = max (diff (bincenter)); xmin = min (x) - binwidth / 2; xmax = max (x) + binwidth / 2; endif h = bar (ax, bincenter, binsize, 1, 'facecolor', 'b'); ## Fit distribution to data pd = fitdist (x, distname); ## Compute density function if (any (strcmpi (distname, {'poisson', 'NegativeBinomial', 'nbin'}))) x = [min(x):max(x)]'; y = pdf (pd, x); else x = [xmin:(xmax-xmin)/100:xmax]'; y = pdf (pd, x); endif ## Normalize density line and overplot the histogram y = xsize * y * binwidth; hold on; if (any (strcmpi (distname, {'poisson', 'NegativeBinomial', 'nbin'}))) h(2) = plot (ax, x, y, ';;r-o'); else h(2) = plot (ax, x, y, ';;r-'); endif xlim ([xmin, xmax]); hold off; ## Return the plot's handle if requested if (nargout == 1) varargout{1} = h; endif endfunction %!demo %! rng (42); %! histfit (randn (100, 1)) %!demo %! rng (42); %! randp ('state', 42); %! histfit (poissrnd (2, 1000, 1), 10, 'Poisson') %!demo %! randg ('state', 42); %! histfit (betarnd (3, 10, 1000, 1), 10, 'beta') ## Test plotting %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! x = [2, 4, 3, 2, 4, 3, 2, 5, 6, 4, 7, 5, 9, 8, 10, 4, 11]; %! histfit (x); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! x = [2, 4, 3, 2, NaN, 3, 2, 5, 6, 4, 7, 5, 9, 8, 10, 4, 11]; %! histfit (x); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! x = [2, 4, 3, 2, NaN, 3, 2, 5, 6, 4, 7, 5, 9, 8, 10, 4, 11]; %! histfit (x, 3); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! histfit (randn (100, 1)); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! histfit (poissrnd (2, 1000, 1), 10, 'Poisson'); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! histfit (betarnd (3, 10, 1000, 1), 10, 'beta'); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! ax = gca (); %! histfit (ax, randn (100, 1)); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! ax = gca (); %! histfit (ax, poissrnd (2, 1000, 1), 10, 'Poisson'); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! ax = gca (); %! histfit (ax, betarnd (3, 10, 1000, 1), 10, 'beta'); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! ax = axes ('parent', hf); %! fail ('histfit (ax)', 'histfit: too few input arguments.'); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!error ... %! histfit ('wer') %!error histfit ([NaN, NaN, NaN]); %!error ... %! histfit (randn (100, 1), 5.6) %!error ... %! histfit (randn (100, 1), 8, 5) %!error ... %! histfit (randn (100, 1), 8, {'normal'}) %!error ... %! histfit (randn (100, 1), 8, 'Kernel') %!error ... %! histfit (randn (100, 1), 8, 'ASDASDASD') statistics-release-1.9.2/inst/Plotting/manovacluster.m000066400000000000000000000066471524624707500232020ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} manovacluster (@var{stats}) ## @deftypefnx {statistics} {} manovacluster (@var{stats}, @var{method}) ## @deftypefnx {statistics} {@var{h} =} manovacluster (@var{stats}) ## @deftypefnx {statistics} {@var{h} =} manovacluster (@var{stats}, @var{method}) ## ## Cluster group means using manova1 output. ## ## @code{manovacluster (@var{stats})} draws a dendrogram showing the clustering ## of group means, calculated using the output STATS structure from ## @code{manova1} and applying the single linkage algorithm. See the ## @code{dendrogram} function for more information about the figure. ## ## @code{manovacluster (@var{stats}, @var{method})} uses the @var{method} ## algorithm in place of single linkage. The available methods are: ## ## @multitable @columnfractions 0.2 0.75 ## @item "single" @tab --- nearest distance ## @item "complete" @tab --- furthest distance ## @item "average" @tab --- average distance ## @item "centroid" @tab --- center of mass distance ## @item "ward" @tab --- inner squared distance ## @end multitable ## ## @code{@var{h} = manovacluster (@dots{})} returns a vector of line handles. ## ## @seealso{manova1} ## @end deftypefn function h = manovacluster (stats, method) ## Check for valid input arguments narginchk (1, 2); if nargin > 1 valid_methods = {'single', 'complete', 'average', 'centroid', 'ward'}; if ! any (strcmpi (method, valid_methods)) error ("manovacluster: invalid method."); endif else method = 'single'; endif ## Get stats fields and create dendrogram dist = stats.gmdist; group_names = stats.gnames; [a, b] = meshgrid (1:length (dist)); hh = dendrogram (linkage (dist(a < b)', method), 0); ## Fix tick labels on x-axis oldlab = get (gca, 'XTickLabel'); maxlen = max (cellfun ('length', group_names)); newlab = repmat (' ', size (oldlab, 1), maxlen); ng = size (group_names, 1); for j = 1:size (oldlab, 1) k = str2num (oldlab(j,:)); if (! isempty (k) & k > 0 & k <= ng) x = group_names{k,:}; newlab(j,1:length (x)) = x; endif endfor set (gca, 'XtickLabel', newlab); ## Return plot handles if requested if nargout > 0 h = hh; endif endfunction %!demo %! load carbig %! X = [MPG Acceleration Weight Displacement]; %! [d, p, stats] = manova1 (X, Origin); %! manovacluster (stats) ## Test plotting %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! load carbig %! X = [MPG Acceleration Weight Displacement]; %! [d, p, stats] = manova1 (X, Origin); %! manovacluster (stats); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error manovacluster (stats, 'some'); statistics-release-1.9.2/inst/Plotting/normplot.m000066400000000000000000000140731524624707500221610ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## Based on previous work by Paul Kienzle originally ## granted to the public domain. ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} normplot (@var{x}) ## @deftypefnx {statistics} {} normplot (@var{ax}, @var{x}) ## @deftypefnx {statistics} {@var{h} =} normplot (@dots{}) ## ## Produce normal probability plot of the data in @var{x}. If @var{x} is a ## matrix, @code{normplot} plots the data for each column. NaN values are ## ignored. ## ## @code{@var{h} = normplot (@var{ax}, @var{x})} takes a handle @var{ax} in ## addition to the data in @var{x} and it uses that axes for plotting. You may ## get this handle of an existing plot with @code{gca}. ## ## The line joining the 1st and 3rd quantile is drawn solid whereas its ## extensions to both ends are dotted. If the underlying distribution is normal, ## the points will cluster around the solid part of the line. Other distribution ## types will introduce curvature in the plot. ## ## @seealso{cdfplot, wblplot} ## @end deftypefn function h = normplot (varargin) ## Check for valid input arguments narginchk (1, 2); ## Parse input arguments if (nargin == 1) ax = []; x = varargin{1}; else ax = varargin{1}; ## Check that ax is a valid axis handle try isstruct (get (ax)); catch error ("normplot: invalid handle %f.", ax); end_try_catch x = varargin{2}; endif ## Check that x is a vector or a 2-D matrix if (isscalar (x) || ndims (x) > 2) error ("normplot: x must be a vector or a 2-D matrix handle."); endif ## If x is a vector, make it a column vector if (rows (x) == 1) x = x(:); endif ## If ax is empty, create a new axes if (isempty (ax)) ax = newplot (); endif ## Get number of column vectors in x col = size (x, 2); ## Process each column and plot data and fit lines color = {'blue', 'black', 'cyan', 'green', 'magenta', 'red', 'white', 'yellow'}; hold on; for i = 1:col xc = x(:,i); ## Remove NaNs, get min, max, and range xc(isnan (xc)) = []; if (isempty (xc)) break; endif ## Transform data row_xc = rows (xc); yc = norminv (([1:row_xc]' - 0.5) / row_xc); xc = sort (xc); ## Find quartiles q1x = prctile (xc, 25); q3x = prctile (xc, 75); q1y = prctile (yc, 25); q3y = prctile (yc, 75); qx = [q1x; q3x]; qy = [q1y; q3y]; ## Calculate coordinates and limits for fitting lines dx = q3x - q1x; dy = q3y - q1y; slope = dy ./ dx; centerx = (q1x + q3x)/2; centery = (q1y + q3y)/2; maxx = max (xc); minx = min (xc); maxy = centery + slope.*(maxx - centerx); miny = centery - slope.*(centerx - minx); yinter = centery - slope.*(centerx); mx = [minx; maxx]; my = [miny; maxy]; ## Plot data and corresponding reference lines in the same color, ## following the default color order. Plot reference line first, ## followed by the data, so that data will be on top of reference line. h_end(i) = line (ax, mx, my, 'LineStyle', '-.', 'Marker', 'none', ... 'color', color{mod(i,8)}); h_mid(i) = line (ax, qx, qy, 'LineStyle', '-', 'Marker', 'none', ... 'color', color{mod(i,8)}); h_dat(i) = line (ax, xc, yc, 'LineStyle', 'none', 'Marker', '+', ... 'color', color{mod(i,8)}); endfor hold off; ## Change colors for single column vector if (i == 1) set (h_dat, 'Color', 'b'); set (h_mid, 'Color', 'r'); set (h_end, 'Color', 'r'); endif ## Bundle handles together if output requested if (nargout > 0) h = [h_dat, h_mid, h_end]'; endif ## Plot labels title 'Normal Probability Plot' ylabel 'Probability' xlabel 'Data' ## Plot grid p = [0.001, 0.003, 0.01, 0.02, 0.05, 0.10, 0.25, 0.5, ... 0.75, 0.90, 0.95, 0.98, 0.99, 0.997, 0.999]; label = {'0.001', '0.003', '0.01', '0.02', '0.05', '0.10', '0.25', '0.50', ... '0.75', '0.90', '0.95', '0.98', '0.99', '0.997', '0.999'}; tick = norminv (p, 0, 1); set (ax, 'ytick', tick, 'yticklabel', label); ## Set view range with a bit of space around data range = nanmax (x(:)) - nanmin (x(:)); if (range > 0) minxaxis = nanmin (x(:)) - 0.025 * range; maxxaxis = nanmax (x(:)) + 0.025 * range; else minxaxis = nanmin (x(:)) - 1; maxxaxis = nanmax (x(:)) + 1; endif minyaxis = norminv (0.25 ./ row_xc, 0, 1); maxyaxis = norminv ((row_xc - 0.25) ./ row_xc, 0, 1); set (ax, 'ylim', [minyaxis, maxyaxis], 'xlim', [minxaxis, maxxaxis]); grid (ax, 'on'); box (ax, 'off'); endfunction %!demo %! h = normplot ([1:20]); %!demo %! h = normplot ([1:20;5:2:44]'); %!demo %! ax = newplot (); %! h = normplot (ax, [1:20]); %! ax = gca; %! h = normplot (ax, [-10:10]); %! set (ax, 'xlim', [-11, 21]); ## Test input validation %!error normplot (); %!error normplot (23); %!error normplot (23, [1:20]); %!error normplot (ones (3,4,5)); ## Test plotting %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! ax = newplot (hf); %! h = normplot (ax, [1:20]); %! ax = gca; %! h = normplot (ax, [-10:10]); %! set (ax, 'xlim', [-11, 21]); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! h = normplot ([1:20;5:2:44]'); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect statistics-release-1.9.2/inst/Plotting/parallelcoords.m000066400000000000000000000226071524624707500233170ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} parallelcoords (@var{x}) ## @deftypefnx {statistics} {} parallelcoords (@var{x}, @var{name}, @var{value}, @dots{}) ## @deftypefnx {statistics} {} parallelcoords (@var{ax}, @dots{}) ## @deftypefnx {statistics} {@var{h} =} parallelcoords (@dots{}) ## ## Create a parallel coordinates plot of the multivariate data in @var{x}. ## ## @code{parallelcoords (@var{x})} plots each observation (row) of the ## @code{n}-by-@code{p} matrix @var{x} as a line connecting the values of its ## @var{p} coordinates, which are placed at the equally spaced horizontal ## positions @code{1, 2, @dots{}, p}. ## ## The following name/value pairs are accepted: ## ## @table @asis ## @item @qcode{"Group"} ## A grouping variable (numeric, logical, character, string, or cell array of ## strings) with one entry per row of @var{x}. Lines are colored by group. ## ## @item @qcode{"Standardize"} ## Controls how the columns of @var{x} are transformed before plotting: ## @qcode{"off"} (default) uses the raw data, @qcode{"on"} centers and scales ## each column to zero mean and unit standard deviation, @qcode{"PCA"} uses the ## principal component scores, and @qcode{"PCAStd"} uses the principal component ## scores of the standardized data. ## ## @item @qcode{"Quantile"} ## A scalar @var{alpha} in the interval @math{(0,1)}. Instead of one line per ## observation, only three lines per group are drawn: the coordinate-wise median ## and the @var{alpha} and @math{1-}@var{alpha} quantiles of the group. ## ## @item @qcode{"Labels"} ## A character array or cell array of strings giving the tick labels for the ## coordinate axis. ## @end table ## ## @code{parallelcoords (@var{ax}, @dots{})} plots into the axes @var{ax}. ## ## The optional output @var{h} is a vector of handles to the plotted lines: one ## per observation, or three per group when @qcode{"Quantile"} is used. ## ## @seealso{andrewsplot, glyphplot, pca} ## @end deftypefn function h = parallelcoords (varargin) ## Optional leading axes handle hax = []; if (numel (varargin) > 0 && isaxes (varargin{1})) hax = varargin{1}; varargin(1) = []; endif if (numel (varargin) < 1) print_usage (); endif X = varargin{1}; varargin(1) = []; if (! isnumeric (X) || ! isreal (X) || ndims (X) > 2) error ("parallelcoords: X must be a real numeric matrix."); endif n = rows (X); ## Parse name/value options group = []; standardize = "off"; alpha = []; labels = {}; if (mod (numel (varargin), 2) != 0) error ("parallelcoords: name/value arguments must come in pairs."); endif for i = 1:2:numel (varargin) name = varargin{i}; value = varargin{i+1}; if (! ischar (name)) error ("parallelcoords: property names must be strings."); endif switch (lower (name)) case "group" group = value; case "standardize" standardize = value; case "quantile" alpha = value; case "labels" labels = value; otherwise error ("parallelcoords: unknown property '%s'.", name); endswitch endfor ## Validate before plotting, so a bad option never leaves a stray figure if (! isempty (alpha)) if (! (isscalar (alpha) && isreal (alpha) && alpha > 0 && alpha < 1)) error ("parallelcoords: Quantile ALPHA must be a scalar in (0,1)."); endif endif ## Standardize the data switch (lower (standardize)) case "off" Z = X; case "on" Z = zscore (X); case "pca" [~, Z] = pca (X); case "pcastd" [~, Z] = pca (zscore (X)); otherwise error ("parallelcoords: invalid Standardize option '%s'.", standardize); endswitch p = columns (Z); cx = 1:p; ## Grouping if (isempty (group)) gidx = ones (n, 1); gnames = {"1"}; else [gidx, gnames] = grp2idx (group); if (numel (gidx) != n) error ("parallelcoords: GROUP must have one entry per row of X."); endif endif k = numel (gnames); gcol = lines (k); if (isempty (hax)) hax = newplot (); else newplot (hax); endif old_hold = ishold (hax); hold (hax, "on"); h = []; if (isempty (alpha)) ## One line per observation, colored by group for i = 1:n h(end+1) = line (hax, cx, Z(i,:), "color", gcol(gidx(i),:)); endfor else ## Coordinate-wise median and alpha / 1-alpha quantile lines per group for g = 1:k Zg = Z(gidx == g, :); med = median (Zg, 1); lo = quantile (Zg, alpha, 1); hi = quantile (Zg, 1 - alpha, 1); h(end+1) = line (hax, cx, med, "color", gcol(g,:), "linewidth", 2); h(end+1) = line (hax, cx, lo, "color", gcol(g,:), "linestyle", "--"); h(end+1) = line (hax, cx, hi, "color", gcol(g,:), "linestyle", "--"); endfor endif ## Coordinate axis ticks and labels set (hax, "xtick", cx); set (hax, "xlim", [1, max(p, 2)]); if (! isempty (labels)) if (ischar (labels)) labels = cellstr (labels); endif set (hax, "xticklabel", labels); endif xlabel (hax, "Coordinate"); ylabel (hax, "Coordinate Value"); if (k > 1 && ! isempty (group)) warning ("off", "Octave:legend:unimplemented-location", "local"); if (isempty (alpha)) [~, first] = unique (gidx, "first"); legend (hax, h(sort (first)), gnames, "location", "best"); else legend (hax, h(1:3:end), gnames, "location", "best"); endif endif if (! old_hold) hold (hax, "off"); endif if (nargout == 0) clear h; endif endfunction %!demo %! ## Parallel coordinates plot of Fisher's iris data, grouped by species. %! %! load fisheriris; %! parallelcoords (meas, "Group", species, "Labels", ... %! {"SL", "SW", "PL", "PW"}); %!demo %! ## The same data with median and quartile lines per species. %! %! load fisheriris; %! parallelcoords (meas, "Group", species, "Quantile", 0.25, ... %! "Standardize", "on"); ## Test output %!test %! hf = figure ("visible", "off"); %! unwind_protect %! X = [1 2 3; 4 5 6; 7 8 9; 2 1 5]; %! h = parallelcoords (X); %! assert_equal (numel (h), 4); %! assert_equal (get (h(1), "xdata"), [1 2 3]); %! assert_equal (get (h(1), "ydata"), [1 2 3]); %! assert_equal (get (h(2), "ydata"), [4 5 6]); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # Standardize "on" (z-score each column) %! hf = figure ("visible", "off"); %! unwind_protect %! X = [1 2 3; 4 5 6; 7 8 9; 2 1 5]; %! h = parallelcoords (X, "Standardize", "on"); %! assert_equal (get (h(1), "ydata"), [-0.94491, -0.63246, -1.1], 1e-4); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # Standardize "pca" %! hf = figure ("visible", "off"); %! unwind_protect %! X = [1 2 3; 4 5 6; 7 8 9; 2 1 5]; %! h = parallelcoords (X, "Standardize", "pca"); %! assert_equal (get (h(1), "ydata")(1:2), [-4.1082, 0.9670], 1e-4); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # Quantile mode: median, alpha, 1-alpha lines %! hf = figure ("visible", "off"); %! unwind_protect %! X = [1 2 3; 4 5 6; 7 8 9; 2 1 5]; %! h = parallelcoords (X, "Quantile", 0.25); %! assert_equal (numel (h), 3); %! assert_equal (get (h(1), "ydata"), [3, 3.5, 5.5], 1e-12); %! assert_equal (get (h(2), "ydata"), [1.5, 1.5, 4], 1e-12); %! assert_equal (get (h(3), "ydata"), [5.5, 6.5, 7.5], 1e-12); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # coordinate tick labels %! hf = figure ("visible", "off"); %! unwind_protect %! h = parallelcoords ([1 2 3; 4 5 6], "Labels", {"a", "b", "c"}); %! assert_equal (get (gca, "xtick"), [1 2 3]); %! assert_equal (get (gca, "xticklabel"), {"a"; "b"; "c"}); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error parallelcoords () %!error parallelcoords ({1}) %!error ... %! parallelcoords (ones (3, 2), "Group") %!error ... %! parallelcoords (ones (3, 2), "bogus", 1) %!error ... %! parallelcoords (ones (3, 2), "Standardize", "xxx") %!error ... %! parallelcoords (ones (3, 2), "Group", [1 2]) %!error ... %! parallelcoords (ones (3, 2), "Quantile", 0) ## A bad Quantile ALPHA must error before any figure is created (no stray figure) %!test %! nfig = numel (get (0, "children")); %! fail ('parallelcoords (ones (3, 2), "Quantile", 0)', ... %! 'parallelcoords: Quantile ALPHA must be a scalar in .0,1..'); %! assert_equal (numel (get (0, "children")), nfig); statistics-release-1.9.2/inst/Plotting/ppplot.m000066400000000000000000000062711524624707500216260ustar00rootroot00000000000000## Copyright (C) 1995-2017 Kurt Hornik ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {} ppplot (@var{x}, @var{dist}) ## @deftypefnx {statistics} {} ppplot (@var{x}, @var{dist}, @var{params}) ## @deftypefnx {statistics} {[@var{p}, @var{y}] =} ppplot (@var{x}, @var{dist}, @var{params}) ## ## Perform a PP-plot (probability plot). ## ## If F is the CDF of the distribution @var{dist} with parameters ## @var{params} and @var{x} a sample vector of length @var{n}, the PP-plot ## graphs ordinate @var{y}(@var{i}) = F (@var{i}-th largest element of ## @var{x}) versus abscissa @var{p}(@var{i}) = (@var{i} - 0.5)/@var{n}. If ## the sample comes from F, the pairs will approximately follow a straight ## line. ## ## The default for @var{dist} is the standard normal distribution. ## ## The optional argument @var{params} contains a list of parameters of ## @var{dist}. ## ## For example, for a probability plot of the uniform distribution on [2,4] ## and @var{x}, use ## ## @example ## ppplot (x, "unif", 2, 4) ## @end example ## ## @noindent ## @var{dist} can be any string for which a function @var{distcdf} that ## calculates the CDF of distribution @var{dist} exists. ## ## If no output is requested then the data are plotted immediately. ## @seealso{qqplot} ## @end deftypefn function [p, y] = ppplot (x, dist, varargin) if (nargin < 1) print_usage (); endif if (! isnumeric (x) || ! isreal (x) || ! isvector (x) || isscalar (x)) error ("ppplot: X must be a numeric vector of real numbers"); endif s = sort (x); n = length (x); p = ((1 : n)' - 0.5) / n; if (nargin == 1) F = @stdnormal_cdf; elseif (! ischar (dist)) error ("ppplot: DIST must be a string"); else F = str2func ([dist 'cdf']); endif if (nargin <= 2) y = feval (F, s); else y = feval (F, s, varargin{:}); endif if (nargout == 0) plot (p, y); axis ([0, 1, 0, 1]); endif endfunction function p = stdnormal_cdf (x) p = 0.5 * erfc (x ./ sqrt (2)); endfunction ## Test plotting %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! ppplot ([2 3 3 4 4 5 6 5 6 7 8 9 8 7 8 9 0 8 7 6 5 4 6 13 8 15 9 9]); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error ppplot () %!error ppplot (ones (2,2)) %!error ppplot (1, 2) %!error ppplot ([1 2 3 4], 2) statistics-release-1.9.2/inst/Plotting/probplot.m000066400000000000000000000333611524624707500221510ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} probplot (@var{y}) ## @deftypefnx {statistics} {} probplot (@var{dist}, @var{y}) ## @deftypefnx {statistics} {} probplot (@var{dist}, @var{y}, @var{cens}) ## @deftypefnx {statistics} {} probplot (@var{dist}, @var{y}, @var{cens}, @var{freq}) ## @deftypefnx {statistics} {} probplot (@var{ax}, @dots{}) ## @deftypefnx {statistics} {} probplot (@dots{}, @qcode{"noref"}) ## @deftypefnx {statistics} {@var{h} =} probplot (@dots{}) ## ## Produce a probability plot of the data in @var{y} against the distribution ## @var{dist}. ## ## On a probability plot the ordered data are drawn against a nonlinear ## probability axis chosen so that a sample from the reference distribution ## @var{dist} falls approximately along a straight line. Systematic departures ## from the reference line indicate departures from the distribution. ## ## @var{dist} is one of @qcode{"normal"} (the default when @var{dist} is ## omitted), @qcode{"lognormal"}, @qcode{"exponential"}, @qcode{"extreme value"}, ## @qcode{"weibull"}, @qcode{"rayleigh"}, @qcode{"logistic"}, or ## @qcode{"loglogistic"}. For @qcode{"lognormal"}, @qcode{"weibull"}, and ## @qcode{"loglogistic"} the data axis is logarithmic. ## ## @var{y} is a numeric vector, or a matrix in which case each column is plotted ## as a separate sample. @code{NaN} values are ignored. ## ## @var{cens} is a logical vector the same size as @var{y} that is true for ## right-censored observations; censored points are not plotted and the plotting ## positions of the remaining points follow the Kaplan-Meier estimate. @var{freq} ## is a vector of nonnegative integer frequencies (counts) the same size as ## @var{y}. Pass @code{[]} to omit either one. ## ## @code{probplot (@var{ax}, @dots{})} plots into the axes @var{ax} instead of ## the current axes. The trailing option @qcode{"noref"} suppresses the ## reference line. ## ## @code{@var{h} = probplot (@dots{})} returns a column vector of handles to the ## plotted line objects (the data markers, followed by the reference line unless ## @qcode{"noref"} was given). ## ## The reference line is a robust fit through the first and third quartiles of the ## data on the transformed scale. For censored data the quartiles are taken from ## the Kaplan-Meier plotting positions; when heavy censoring prevents the data ## from reaching a quartile the position is linearly extrapolated, which may ## deviate slightly from @sc{matlab}. ## ## @seealso{normplot, wblplot, qqplot, cdfplot, ecdf} ## @end deftypefn function h = probplot (varargin) if (nargin < 1) print_usage (); endif args = varargin; ## Optional leading axes handle. ax = []; if (isscalar (args{1}) && ishghandle (args{1}) ... && strcmp (get (args{1}, "type"), "axes")) ax = args{1}; args(1) = []; endif if (isempty (args)) print_usage (); endif ## Distribution name (default "normal") followed by the data. if (ischar (args{1})) dist = lower (args{1}); args(1) = []; else dist = "normal"; endif if (isempty (args)) error ("probplot: missing data vector Y."); endif y = args{1}; args(1) = []; ## Remaining arguments: numeric CENS / FREQ and the trailing "noref" flag. noref = false; numargs = {}; for k = 1:numel (args) a = args{k}; if (ischar (a)) if (strcmpi (a, "noref")) noref = true; else error ("probplot: unknown option '%s'.", a); endif else numargs{end+1} = a; endif endfor if (numel (numargs) > 2) error ("probplot: too many input arguments."); endif cens = []; freq = []; if (numel (numargs) >= 1) cens = numargs{1}; endif if (numel (numargs) >= 2) freq = numargs{2}; endif ## Look up the distribution transform and axis scale. [invfun, logx, prettyname] = dist_transform (dist); ## Validate the data. if (! (isnumeric (y) && isreal (y))) error ("probplot: Y must be real numeric."); endif if (isrow (y)) y = y(:); endif if (ndims (y) > 2) error ("probplot: Y must be a vector or a 2-D matrix."); endif ncol = columns (y); if (ncol > 1 && (! isempty (cens) || ! isempty (freq))) error ("probplot: CENS and FREQ are only supported for a vector Y."); endif if (isempty (ax)) ax = newplot (); endif ## Probability grid for the y-axis ticks (as in a normal probability plot). pgrid = [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.25, 0.5, ... 0.75, 0.9, 0.95, 0.99, 0.995, 0.999, 0.9995, 0.9999]; hmark = []; href = []; hold (ax, "on"); for j = 1:ncol yj = y(:,j); cj = cens; fj = freq; [xu, pp] = plot_positions (yj, cj, fj); if (isempty (xu)) continue; endif zz = invfun (pp); hmark(end+1) = line (ax, xu, zz, "linestyle", "none", "marker", "x", ... "color", "b"); if (! noref) href(end+1) = ref_line (ax, xu, pp, invfun, logx); endif endfor hold (ax, "off"); ## Data (x) axis scale. if (logx) set (ax, "xscale", "log"); else set (ax, "xscale", "linear"); endif ## Probability (y) axis: tick at invfun(pgrid), labelled with the probability. ok = isfinite (invfun (pgrid)); yt = invfun (pgrid(ok)); ylabels = arrayfun (@(p) num2str (p), pgrid(ok), "UniformOutput", false); set (ax, "ytick", yt, "yticklabel", ylabels); title (ax, sprintf ("Probability plot for %s distribution", prettyname)); xlabel (ax, "Data"); ylabel (ax, "Probability"); grid (ax, "on"); box (ax, "off"); if (nargout > 0) h = [hmark(:); href(:)]; endif endfunction ## Standardized inverse cdf (transform), log-x flag, and pretty name per dist. function [invfun, logx, prettyname] = dist_transform (dist) switch (dist) case "normal" invfun = @(p) norminv (p); logx = false; prettyname = "normal"; case "lognormal" invfun = @(p) norminv (p); logx = true; prettyname = "lognormal"; case "exponential" invfun = @(p) -log (1 - p); logx = false; prettyname = "exponential"; case {"extreme value", "ev"} invfun = @(p) log (-log (1 - p)); logx = false; prettyname = "extreme value"; case {"weibull", "wbl"} invfun = @(p) log (-log (1 - p)); logx = true; prettyname = "weibull"; case {"rayleigh", "rayl"} invfun = @(p) sqrt (-2 .* log (1 - p)); logx = false; prettyname = "rayleigh"; case "logistic" invfun = @(p) log (p ./ (1 - p)); logx = false; prettyname = "logistic"; case "loglogistic" invfun = @(p) log (p ./ (1 - p)); logx = true; prettyname = "loglogistic"; otherwise error ("probplot: unrecognized distribution '%s'.", dist); endswitch endfunction ## Sorted uncensored data and Kaplan-Meier survival-midpoint plotting positions. function [xu, pp] = plot_positions (y, cens, freq) keep = ! isnan (y); y = y(keep); if (isempty (cens)) cens = false (size (y)); else cens = logical (cens(:)); cens = cens(keep); endif if (isempty (freq)) freq = ones (size (y)); else freq = freq(:); freq = freq(keep); endif ## Expand by integer frequency. y = repelem (y(:), freq); cens = repelem (cens(:), freq); [ys, ord] = sort (y); cs = cens(ord); ## Kaplan-Meier survival, sampled to the midpoint of each uncensored jump. n = numel (ys); S = 1; nrisk = n; xu = []; pp = []; for i = 1:n if (! cs(i)) Safter = S * (nrisk - 1) / nrisk; xu(end+1) = ys(i); pp(end+1) = 1 - (S + Safter) / 2; S = Safter; endif nrisk -= 1; endfor xu = xu(:); pp = pp(:); endfunction ## Robust quartile reference line drawn across the data range. function hl = ref_line (ax, xu, pp, invfun, logx) ## Quartiles of the data from the plotting positions (transformed scale). if (logx) u = log (xu); else u = xu; endif ## interp1 needs strictly increasing, unique sample sites. [ppu, iu] = unique (pp); q = interp1 (ppu, u(iu), [0.25, 0.75], "linear", "extrap"); z = invfun ([0.25, 0.75]); slope = (z(2) - z(1)) / (q(2) - q(1)); intercept = z(1) - slope * q(1); ## Sample the straight line across the data range on the transformed scale. uu = linspace (min (u), max (u), 100)'; zz = intercept + slope * uu; if (logx) xx = exp (uu); else xx = uu; endif hl = line (ax, xx, zz, "linestyle", "--", "marker", "none", "color", "r"); endfunction %!demo %! probplot ([1:20]); %!demo %! probplot ("weibull", [1 2 3 4 5 6 7 8 9 10 15 20]); ## shared data %!shared y, c, f %! y = [2.1 3.4 1.8 5.2 2.9 4.1 3.3 2.7 6.0 3.8 4.5 2.2]; %! c = logical ([0 0 0 1 0 0 0 0 1 0 1 0]); %! f = [1 2 1 1 3 1 1 2 1 1 1 2]; ## normal: marker positions and reference line, verified against MATLAB %!test %! hf = figure ("visible", "off"); %! unwind_protect %! h = probplot (y); %! assert_equal (numel (h), 2); %! xd = get (h(1), "xdata"); %! yd = get (h(1), "ydata"); %! assert_equal (xd(:)', sort (y), 1e-12); %! ymatlab = [-1.7317 -1.1503 -0.8122 -0.5485 -0.3186 -0.1046 ... %! 0.1046 0.3186 0.5485 0.8122 1.1503 1.7317]; %! assert_equal (yd(:)', ymatlab, 1e-4); %! rl = get (h(2), "ydata"); %! rx = get (h(2), "xdata"); %! ## reference line slope matches MATLAB robust quartile fit %! slope = (rl(end) - rl(1)) / (rx(end) - rx(1)); %! assert_equal (slope, 0.72923, 1e-4); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## weibull uses a logarithmic data axis and the SEV transform %!test %! hf = figure ("visible", "off"); %! unwind_protect %! ax = axes (hf); %! h = probplot (ax, "weibull", y); %! assert_equal (get (ax, "xscale"), "log"); %! yd = get (h(1), "ydata"); %! ymatlab = [-3.1568 -2.0134 -1.4541 -1.0647 -0.7550 -0.4892 ... %! -0.2483 -0.0194 0.2088 0.4502 0.7321 1.1563]; %! assert_equal (yd(:)', ymatlab, 1e-4); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## remaining distributions: transform and data-axis scale vs MATLAB %!test %! hf = figure ("visible", "off"); %! unwind_protect %! dists = {"exponential", "lognormal", "rayleigh", "logistic", ... %! "loglogistic", "extreme value"}; %! gold = {[0.0426 0.1335 0.2336 0.3448 0.4700 0.6131 0.7802 0.9808 ... %! 1.2321 1.5686 2.0794 3.1781], ... %! [-1.7317 -1.1503 -0.8122 -0.5485 -0.3186 -0.1046 0.1046 ... %! 0.3186 0.5485 0.8122 1.1503 1.7317], ... %! [0.2918 0.5168 0.6835 0.8305 0.9695 1.1073 1.2491 1.4006 ... %! 1.5698 1.7712 2.0393 2.5211], ... %! [-3.1355 -1.9459 -1.3350 -0.8873 -0.5108 -0.1671 0.1671 ... %! 0.5108 0.8873 1.3350 1.9459 3.1355], ... %! [-3.1355 -1.9459 -1.3350 -0.8873 -0.5108 -0.1671 0.1671 ... %! 0.5108 0.8873 1.3350 1.9459 3.1355], ... %! [-3.1568 -2.0134 -1.4541 -1.0647 -0.7550 -0.4892 -0.2483 ... %! -0.0194 0.2088 0.4502 0.7321 1.1563]}; %! logx = {false, true, false, false, true, false}; %! for k = 1:numel (dists) %! ax = axes (hf); %! h = probplot (ax, dists{k}, y); %! assert_equal (get (h(1), "ydata")(:)', gold{k}, 1e-4); %! if (logx{k}) %! assert_equal (get (ax, "xscale"), "log"); %! else %! assert_equal (get (ax, "xscale"), "linear"); %! endif %! delete (ax); %! endfor %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## noref suppresses the reference line %!test %! hf = figure ("visible", "off"); %! unwind_protect %! h = probplot (y, "noref"); %! assert_equal (numel (h), 1); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## censoring: only uncensored points plotted, at Kaplan-Meier positions %!test %! hf = figure ("visible", "off"); %! unwind_protect %! h = probplot ("normal", y, c); %! xd = get (h(1), "xdata"); %! yd = get (h(1), "ydata"); %! assert_equal (xd(:)', [1.8 2.1 2.2 2.7 2.9 3.3 3.4 3.8 4.1], 1e-12); %! ymatlab = [-1.7317 -1.1503 -0.8122 -0.5485 -0.3186 -0.1046 ... %! 0.1046 0.3186 0.5485]; %! assert_equal (yd(:)', ymatlab, 1e-4); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## interspersed censoring: KM redistributes the plotting positions %!test %! hf = figure ("visible", "off"); %! unwind_protect %! cm = logical ([0 0 0 0 0 1 1 1 0 0 0 0]); %! h = probplot ("normal", y, cm); %! yd = get (h(1), "ydata"); %! ymatlab = [-1.7317 -1.1503 -0.8122 -0.5334 -0.2574 0.0196 ... %! 0.3462 0.7764 1.4544]; %! assert_equal (yd(:)', ymatlab, 1e-4); %! rl = get (h(2), "ydata"); %! rx = get (h(2), "xdata"); %! slope = (rl(end) - rl(1)) / (rx(end) - rx(1)); %! assert_equal (slope, 0.53519, 1e-4); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## frequency: each observation expanded into repeated markers %!test %! hf = figure ("visible", "off"); %! unwind_protect %! h = probplot ("normal", y, [], f); %! xd = get (h(1), "xdata"); %! assert_equal (numel (xd), sum (f)); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error probplot () %!error probplot ("foo", [1 2 3 4]) %!error probplot ([1 2 3 4], "bar") statistics-release-1.9.2/inst/Plotting/qqplot.m000066400000000000000000000103101524624707500216150ustar00rootroot00000000000000## Copyright (C) 1995-2017 Kurt Hornik ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{q}, @var{s}] =} qqplot (@var{x}) ## @deftypefnx {statistics} {[@var{q}, @var{s}] =} qqplot (@var{x}, @var{y}) ## @deftypefnx {statistics} {[@var{q}, @var{s}] =} qqplot (@var{x}, @var{dist}) ## @deftypefnx {statistics} {[@var{q}, @var{s}] =} qqplot (@var{x}, @var{y}, @var{params}) ## @deftypefnx {statistics} {} qqplot (@dots{}) ## ## Perform a QQ-plot (quantile plot). ## ## If F is the CDF of the distribution @var{dist} with parameters ## @var{params} and G its inverse, and @var{x} a sample vector of length ## @var{n}, the QQ-plot graphs ordinate @var{s}(@var{i}) = @var{i}-th ## largest element of x versus abscissa @var{q}(@var{i}f) = G((@var{i} - ## 0.5)/@var{n}). ## ## If the sample comes from F, except for a transformation of location ## and scale, the pairs will approximately follow a straight line. ## ## If the second argument is a vector @var{y} the empirical CDF of @var{y} ## is used as @var{dist}. ## ## The default for @var{dist} is the standard normal distribution. The ## optional argument @var{params} contains a list of parameters of ## @var{dist}. For example, for a quantile plot of the uniform ## distribution on [2,4] and @var{x}, use ## ## @example ## qqplot (x, "unif", 2, 4) ## @end example ## ## @noindent ## @var{dist} can be any string for which a function @var{distinv} or ## @var{dist_inv} exists that calculates the inverse CDF of distribution ## @var{dist}. ## ## If no output arguments are given, the data are plotted directly. ## @seealso{ppplot} ## @end deftypefn function [qout, sout] = qqplot (x, dist, varargin) if (nargin < 1) print_usage (); endif if (! isnumeric (x) || ! isreal (x) || ! isvector (x) || isscalar (x)) error ("qqplot: X must be a numeric vector of real numbers"); endif if (nargin == 1) f = @probit; else if (isnumeric (dist)) f = @(y) empirical_inv (y, dist); elseif (ischar (dist) && (exist(invname = [dist 'inv']) || exist(invname = [dist '_inv']))) f = str2func (invname); else error ("qqplot: no inverse CDF found for distribution DIST"); endif endif; s = sort (x); n = length (x); t = ((1 : n)' - .5) / n; if (nargin <= 2) q = f(t); q_label = func2str (f); else q = f(t, varargin{:}); if (nargin == 3) q_label = sprintf ("%s with parameter %g", func2str (f), varargin{1}); else q_label = sprintf ("%s with parameters %g", func2str (f), varargin{1}); param_str = sprintf (", %g", varargin{2:end}); q_label = [q_label param_str]; endif endif if (nargout == 0) plot (q, s, '-x'); q_label = strrep (q_label, '_inv', '\_inv'); if (q_label(1) == '@') q_label = q_label(6:end); # Strip "@(y) " from anon. function endif xlabel (q_label); ylabel ('sample points'); else qout = q; sout = s; endif endfunction ## Test plotting %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! qqplot ([2 3 3 4 4 5 6 5 6 7 8 9 8 7 8 9 0 8 7 6 5 4 6 13 8 15 9 9]); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error qqplot () %!error qqplot ({1}) %!error qqplot (ones (2,2)) %!error qqplot (1, 'foobar') %!error qqplot ([1 2 3], 'foobar') statistics-release-1.9.2/inst/Plotting/scatterhist.m000066400000000000000000000704431524624707500226470ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} scatterhist (@var{x}, @var{y}) ## @deftypefnx {statistics} {} scatterhist (@var{x}, @var{y}, @var{name}, @var{value}, @dots{}) ## @deftypefnx {statistics} {@var{h} =} scatterhist (@dots{}) ## ## Create a scatter plot of @var{x} and @var{y} with marginal histograms. ## ## @code{scatterhist (@var{x}, @var{y})} draws a scatter plot of the vectors ## @var{x} and @var{y} in a central set of axes, with a histogram of @var{x} ## above it and a histogram of @var{y} to its right. @var{x} and @var{y} must ## be vectors of the same length; @qcode{NaN} values are removed pairwise from ## the scatter plot and individually from each marginal histogram. ## ## The following name/value pairs are accepted: ## ## @table @asis ## @item @qcode{"Group"} ## A grouping variable (numeric, logical, character, string, or cell array of ## strings) with one entry per point. The scatter points and the marginal ## histograms are separated and colored by group. ## ## @item @qcode{"NBins"} ## The number of bins for the marginal histograms, either a scalar applied to ## both or a two-element vector @code{[nx ny]}. The default is chosen by Scott's ## rule. ## ## @item @qcode{"Kernel"} ## @qcode{"off"} (default) draws histograms for the marginals; @qcode{"on"} or ## @qcode{"overlay"} draws kernel density estimates instead. ## ## @item @qcode{"Location"} ## Corner occupied by the marginal plots, the scatter taking the opposite one: ## @qcode{"SouthWest"} (default), @qcode{"SouthEast"}, @qcode{"NorthEast"}, or ## @qcode{"NorthWest"}. ## ## @item @qcode{"Legend"} ## @qcode{"on"} or @qcode{"off"} to show or hide the group legend. The default ## is @qcode{"on"} when a grouping variable is supplied. ## ## @item @qcode{"Marker"}, @qcode{"MarkerSize"} ## The marker symbol(s) and size(s) for the scatter points, cycled over the ## groups. ## @end table ## ## The optional output @var{h} is a three-element vector of axes handles: the ## central scatter axes, the axes of the @var{x} (horizontal) histogram, and the ## axes of the @var{y} (vertical) histogram. ## ## The @var{y} histogram's axes is built differently from MATLAB's and its ## properties read accordingly. We plot that marginal transposed, so its ## @qcode{"XLim"} is the density and @qcode{"XDir"} carries the bar direction. ## MATLAB plots it like the @var{x} one -- data along @qcode{"XLim"}, density ## along @qcode{"YLim"} -- and rotates the whole axes by setting ## @qcode{"View"} to @qcode{[270, 90]}, so there the direction is carried by ## @qcode{"YDir"} and @qcode{"XDir"} is always @qcode{"normal"}. The picture ## is the same; code reading those properties off @code{@var{h}(3)} is not ## portable between the two. ## ## @seealso{gscatter, scatter, hist, ksdensity} ## ## @qcode{'Parent'} draws into a supplied figure or uipanel instead of the ## current figure. The container is used as it stands and is never ## cleared, so a scatterhist can be placed alongside other axes. ## ## @qcode{'Location'} names the corner the marginal histograms occupy, ## @qcode{'SouthWest'} by default, or @qcode{'SouthEast'}, @qcode{'NorthEast'} ## or @qcode{'NorthWest'}; the scatter takes the opposite corner. With the ## default the histograms are drawn below and to the left of the scatter. ## ## @qcode{'Direction'} points the marginal bars toward the scatter plot, ## @qcode{'in'} by default, or away from it with @qcode{'out'}, whichever side ## @qcode{'Location'} has placed them on. ## ## @qcode{'PlotGroup'} draws one marginal per group with @qcode{'on'}, or a ## single pooled marginal with @qcode{'off'}. It defaults to @qcode{'on'} when ## @qcode{'Group'} is given and to @qcode{'off'} otherwise. ## ## @qcode{'Style'} outlines the marginals as @qcode{'bar'} or @qcode{'stairs'}, ## defaulting to stairs when the data are grouped and bars when they are not. ## ## @qcode{'Color'} sets the group colours, either as a character vector of ## colour names or as an @math{N}-by-3 matrix of RGB values, cycled over the ## groups. ## ## @qcode{'LineStyle'} and @qcode{'LineWidth'} style the marginal outlines, not ## the scatter markers, and are cycled over the groups. ## ## @qcode{'Bandwidth'} sets the kernel bandwidth used when @qcode{'Kernel'} is ## on: a scalar for all marginals, a pair for @var{x} and @var{y}, or one row ## per group. ## ## @end deftypefn function h = scatterhist (varargin) if (numel (varargin) < 2) print_usage (); endif x = varargin{1}(:); y = varargin{2}(:); varargin(1:2) = []; if (! isnumeric (x) || ! isreal (x) || ! isnumeric (y) || ! isreal (y)) error ("scatterhist: X and Y must be real numeric vectors."); endif if (numel (x) != numel (y)) error ("scatterhist: X and Y must have the same length."); endif ## Parse name/value options through the datatypes parser. It consumes the ## names it knows and returns whatever is left, so anything remaining is an ## unrecognised property. optNames = {"Group", "NBins", "Kernel", "Location", "Legend", "Marker", ... "MarkerSize", "Direction", "PlotGroup", "Style", "Color", ... "Bandwidth", "LineStyle", "LineWidth", "Parent"}; dfValues = {[], [], "off", "southwest", "", "o", 6, "in", "", "", [], ... [], "-", 0.5, []}; if (mod (numel (varargin), 2) != 0) error ("scatterhist: name/value arguments must come in pairs."); endif for i = 1:2:numel (varargin) if (! ischar (varargin{i})) error ("scatterhist: property names must be strings."); endif endfor [group, nbins, kernel, location, legend_opt, marker, markersize, ... direction, plotgroup, style, colour, bandwidth, linestyle, linewidth, ... parent, rest] = parsePairedArguments (optNames, dfValues, varargin(:)); if (! isempty (rest)) error ("scatterhist: unknown property '%s'.", rest{1}); endif ## The parser only splits the pairs; every value is validated here. if (! isempty (nbins)) if (! (isnumeric (nbins) && all (nbins(:) > 0) && all (nbins(:) == fix (nbins(:))) && numel (nbins) <= 2)) error (strcat ("scatterhist: NBINS must be one or two positive", ... " integers.")); endif endif if (! (ischar (kernel) && any (strcmpi (kernel, {"off", "on", "overlay"})))) error ("scatterhist: KERNEL must be 'off', 'on' or 'overlay'."); endif if (! (ischar (location) && any (strcmpi (location, {"southwest", ... "southeast", "northeast", "northwest"})))) error (strcat ("scatterhist: LOCATION must be 'SouthWest', 'SouthEast',", ... " 'NorthEast' or 'NorthWest'.")); endif if (! (ischar (legend_opt) && any (strcmpi (legend_opt, {"", "on", "off"})))) error ("scatterhist: LEGEND must be 'on' or 'off'."); endif if (! ischar (marker)) error ("scatterhist: MARKER must be a character vector of markers."); endif if (! (isnumeric (markersize) && all (markersize(:) > 0))) error ("scatterhist: MARKERSIZE must be positive numeric values."); endif if (! (ischar (direction) && any (strcmpi (direction, {"in", "out"})))) error ("scatterhist: DIRECTION must be 'in' or 'out'."); endif if (! isempty (style) && ! (ischar (style) && any (strcmpi (style, {"bar", "stairs"})))) error ("scatterhist: STYLE must be 'bar' or 'stairs'."); endif if (! isempty (plotgroup) && ! (ischar (plotgroup) && any (strcmpi (plotgroup, {"on", "off"})))) error ("scatterhist: PLOTGROUP must be 'on' or 'off'."); endif if (! isempty (parent)) if (! (isscalar (parent) && ishghandle (parent) && any (strcmp (get (parent, "type"), {"figure", "uipanel"})))) error ("scatterhist: PARENT must be a figure or uipanel handle."); endif endif ## Marginal line style. 'Bandwidth' is a scalar, a pair for x and y, or one ## row per group; 'LineStyle' and 'LineWidth' cycle over the groups and apply ## to the marginals, not to the scatter markers. if (! isempty (bandwidth)) if (! (isnumeric (bandwidth) && all (bandwidth(:) > 0))) error ("scatterhist: BANDWIDTH must be positive numeric values."); endif if (isvector (bandwidth)) bandwidth = bandwidth(:)'; endif endif if (! (ischar (linestyle) || iscellstr (linestyle))) error ("scatterhist: LINESTYLE must be a string or a cell array of them."); endif if (ischar (linestyle)) linestyle = {linestyle}; endif if (! (isnumeric (linewidth) && all (linewidth(:) > 0))) error ("scatterhist: LINEWIDTH must be positive numeric values."); endif n = numel (x); if (isempty (group)) gidx = ones (n, 1); gnames = {"1"}; else [gidx, gnames] = grp2idx (group); if (numel (gidx) != n) error ("scatterhist: GROUP must have one entry per point."); endif endif k = numel (gnames); if (isempty (colour)) gcol = lines (k); else if (ischar (colour)) colour = colour(:); gcol = zeros (numel (colour), 3); for i = 1:numel (colour) gcol(i,:) = colour_rgb (colour(i)); endfor elseif (isnumeric (colour) && columns (colour) == 3) gcol = colour; else error (strcat ("scatterhist: COLOR must be a character vector of", ... " colour names or an N-by-3 matrix of RGB values.")); endif ## cycle the supplied colours over the groups gcol = gcol(mod ((0:k-1), rows (gcol)) + 1, :); endif do_kernel = any (strcmpi (kernel, {"on", "overlay"})); ## Number of bins for the marginals (Scott's rule by default) if (isempty (nbins)) nbx = scott_nbins (x); nby = scott_nbins (y); elseif (isscalar (nbins)) nbx = nby = nbins; else nbx = nbins(1); nby = nbins(2); endif ## Axes layout. 'Location' names the corner the marginals sit in, as MATLAB ## documents it, so the scatter takes the opposite one: with the default ## "southwest" the x marginal is below the scatter and the y marginal to its ## left, and the scatter itself is upper right. marg_west = any (strcmpi (location, {"southwest", "northwest"})); marg_south = any (strcmpi (location, {"southwest", "southeast"})); west = ! marg_west; south = ! marg_south; ## A container supplied through 'Parent' is drawn into as it stands: it ## belongs to the caller, so it is never cleared. Only a figure of our own ## choosing is. if (isempty (parent)) cf = gcf (); clf (cf); else cf = parent; endif ## The scatter is 0.55 square and offset by 0.35 on whichever sides carry a ## marginal, by 0.10 on the others -- R2024a's own geometry, measured. Each ## marginal then takes a 0.22 band in the strip its side frees. xs = ternary (marg_west, 0.35, 0.10); ys = ternary (marg_south, 0.35, 0.10); ys_marg = ternary (marg_west, 0.05, 0.73); xs_marg = ternary (marg_south, 0.05, 0.73); pos_s = [xs, ys, 0.55, 0.55]; pos_x = [xs, xs_marg, 0.55, 0.22]; pos_y = [ys_marg, ys, 0.22, 0.55]; ax_s = axes ("parent", cf, "position", pos_s, "box", "on", "nextplot", "add"); ax_x = axes ("parent", cf, "position", pos_x, "nextplot", "add"); ax_y = axes ("parent", cf, "position", pos_y, "nextplot", "add"); ## Central scatter plot, grouped scat = zeros (1, k); for g = 1:k idx = (gidx == g) & ! isnan (x) & ! isnan (y); scat(g) = line (ax_s, x(idx), y(idx), "linestyle", "none", ... "marker", marker(mod (g - 1, numel (marker)) + 1), ... "markersize", markersize(mod (g - 1, numel (markersize)) + 1), ... "color", gcol(g,:)); endfor xlabel (ax_s, inputname (1)); ylabel (ax_s, inputname (2)); ## Marginal for x along the top, for y along the right. 'Style' picks the ## outline, defaulting to stairs when grouped and bars when not, and ## 'PlotGroup' says whether the groups are drawn apart or pooled. if (isempty (style)) multi = (k > 1); else multi = strcmpi (style, "stairs"); endif if (isempty (plotgroup)) bygroup = ! isempty (group); else bygroup = strcmpi (plotgroup, "on"); endif if (bygroup) for g = 1:k xg = x(gidx == g & ! isnan (x)); yg = y(gidx == g & ! isnan (y)); marginal (ax_x, xg, nbx, gcol(g,:), do_kernel, multi, false, ... sty (bandwidth, linestyle, linewidth, g, 1)); marginal (ax_y, yg, nby, gcol(g,:), do_kernel, multi, true, ... sty (bandwidth, linestyle, linewidth, g, 2)); endfor else marginal (ax_x, x(! isnan (x)), nbx, gcol(1,:), do_kernel, multi, false, ... sty (bandwidth, linestyle, linewidth, 1, 1)); marginal (ax_y, y(! isnan (y)), nby, gcol(1,:), do_kernel, multi, true, ... sty (bandwidth, linestyle, linewidth, 1, 2)); endif ## 'Direction' points the marginal bars toward the scatter ("in", as MATLAB ## defaults) or away from it ("out"). Which way that is depends on where ## 'Location' put the marginals: bars grow along the positive axis, so the x ## marginal must be reversed only when it sits above the scatter, and the y ## marginal only when it sits to the scatter's right. toward = strcmpi (direction, "in"); set (ax_x, "ydir", ternary (xor (toward, ! south), "reverse", "normal")); set (ax_y, "xdir", ternary (xor (toward, ! west), "reverse", "normal")); ## Share the data axes with the scatter and tidy the marginal axes set (ax_x, "xlim", get (ax_s, "xlim"), "xtick", [], "ytick", []); set (ax_y, "ylim", get (ax_s, "ylim"), "xtick", [], "ytick", []); axis (ax_x, "off"); axis (ax_y, "off"); ## Legend show_legend = (! isempty (group)) && ! strcmpi (legend_opt, "off"); if (show_legend && k > 1) warning ("off", "Octave:legend:unimplemented-location", "local"); legend (ax_s, scat, gnames, "location", "best"); endif if (nargout > 0) h = [ax_s, ax_x, ax_y]; endif endfunction ## Choose between two values without an if block. function v = ternary (cond, a, b) if (cond) v = a; else v = b; endif endfunction ## Pick the bandwidth, line style and line width for group G and variable V ## (1 for x, 2 for y), cycling whatever the user supplied. function sy = sty (bandwidth, linestyle, linewidth, g, v) if (isempty (bandwidth)) sy.bw = []; elseif (isscalar (bandwidth)) sy.bw = bandwidth; elseif (isrow (bandwidth)) sy.bw = bandwidth(mod (v - 1, numel (bandwidth)) + 1); else row = bandwidth(mod (g - 1, rows (bandwidth)) + 1, :); sy.bw = row(mod (v - 1, numel (row)) + 1); endif sy.ls = linestyle{mod (g - 1, numel (linestyle)) + 1}; sy.lw = linewidth(mod (g - 1, numel (linewidth)) + 1); endfunction ## Map a colour name to its RGB triplet. function rgb = colour_rgb (c) switch (lower (c)) case "r", rgb = [1, 0, 0]; case "g", rgb = [0, 1, 0]; case "b", rgb = [0, 0, 1]; case "c", rgb = [0, 1, 1]; case "m", rgb = [1, 0, 1]; case "y", rgb = [1, 1, 0]; case "k", rgb = [0, 0, 0]; case "w", rgb = [1, 1, 1]; otherwise error ("scatterhist: unknown colour '%s'.", c); endswitch endfunction ## Scott's rule for the number of histogram bins. function nb = scott_nbins (v) v = v(! isnan (v)); n = numel (v); if (n < 2) nb = 1; return; endif bw = 3.5 * std (v) * n ^ (-1/3); if (bw <= 0) nb = 1; else nb = max (1, ceil ((max (v) - min (v)) / bw)); endif endfunction ## Draw one group's marginal, either as a histogram or a kernel density. ## When horiz is true the marginal runs along the vertical (y) axis. function marginal (ax, v, nb, col, do_kernel, multi, horiz, sy) if (isempty (v)) return; endif if (do_kernel) if (isempty (sy.bw)) [f, u] = ksdensity (v); else [f, u] = ksdensity (v, "Bandwidth", sy.bw); endif if (horiz) line (ax, f, u, "color", col, "linestyle", sy.ls, "linewidth", sy.lw); else line (ax, u, f, "color", col, "linestyle", sy.ls, "linewidth", sy.lw); endif else [nn, cc] = hist (v, nb); if (multi) ## stairstep outline e = cc(1:end-1) + diff (cc) / 2; ce = [cc(1)-(cc(2)-cc(1))/2, e, cc(end)+(cc(end)-cc(end-1))/2]; ne = [nn, nn(end)]; if (horiz) stairs (ax, ne, ce, "color", col, "linestyle", sy.ls, ... "linewidth", sy.lw); else stairs (ax, ce, ne, "color", col, "linestyle", sy.ls, ... "linewidth", sy.lw); endif else if (horiz) barh (ax, cc, nn, 1.0, "facecolor", col, "edgecolor", col, ... "linestyle", sy.ls, "linewidth", sy.lw); else bar (ax, cc, nn, 1.0, "facecolor", col, "edgecolor", col, ... "linestyle", sy.ls, "linewidth", sy.lw); endif endif endif endfunction %!demo %! ## Scatter plot of two iris measurements with marginal histograms by species. %! %! load fisheriris; %! scatterhist (meas(:,1), meas(:,2), "Group", species); %!demo %! ## Marginal kernel density estimates instead of histograms. %! %! load fisheriris; %! scatterhist (meas(:,3), meas(:,4), "Group", species, "Kernel", "on"); ## Test output %!test # LineWidth and LineStyle apply to the marginals and cycle over groups %! hf = figure ("visible", "off"); %! unwind_protect %! x = [1 2 3 4 5 6 7 8]'; y = [2 1 4 3 6 5 8 7]'; %! g = [1 1 1 1 2 2 2 2]'; %! h = scatterhist (x, y, "Group", g, "LineWidth", 3); %! ch = get (h(2), "children"); %! assert_equal (get (ch(1), "linewidth"), 3); %! assert_equal (get (ch(2), "linewidth"), 3); %! h = scatterhist (x, y, "Group", g, "LineStyle", {"--", ":"}); %! ch = get (h(2), "children"); %! assert_equal (get (ch(2), "linestyle"), "--"); %! assert_equal (get (ch(1), "linestyle"), ":"); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # Bandwidth is passed to the kernel density %! hf = figure ("visible", "off"); %! unwind_protect %! x = [1 2 3 4 5 6 7 8]'; y = [2 1 4 3 6 5 8 7]'; %! h = scatterhist (x, y, "Kernel", "on", "Bandwidth", 2); %! wide = get (get (h(2), "children")(1), "ydata"); %! h = scatterhist (x, y, "Kernel", "on"); %! auto = get (get (h(2), "children")(1), "ydata"); %! assert_equal (! isequal (wide, auto), true); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "LineWidth", -1) %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "LineStyle", 5) %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "Bandwidth", 0) %!test # Parent draws into a supplied container without clearing it %! hf = figure ("visible", "off"); %! unwind_protect %! x = [1 2 3 4 5 6 7 8]'; y = [2 1 4 3 6 5 8 7]'; %! pre = axes ("parent", hf, "position", [0.01, 0.01, 0.05, 0.05]); %! h = scatterhist (x, y, "Parent", hf); %! assert_equal (all (arrayfun (@(a) get (a, "parent"), h) == hf, 'all'), ... %! true); %! ## the caller's own axes are left alone %! assert_equal (ishghandle (pre), true); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # a uipanel is a container too %! hf = figure ("visible", "off"); %! unwind_protect %! x = [1 2 3 4 5 6 7 8]'; y = [2 1 4 3 6 5 8 7]'; %! hp = uipanel ("parent", hf, "position", [0, 0, 1, 1]); %! h = scatterhist (x, y, "Parent", hp); %! assert_equal (all (arrayfun (@(a) get (a, "parent"), h) == hp, 'all'), ... %! true); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # without Parent the current figure is still claimed and cleared %! hf = figure ("visible", "off"); %! unwind_protect %! x = [1 2 3 4 5 6 7 8]'; y = [2 1 4 3 6 5 8 7]'; %! old = axes ("parent", hf); %! h = scatterhist (x, y); %! assert_equal (ishghandle (old), false); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "Parent", 0) %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "Parent", -5) %!test # Location names the marginals' corner, so the scatter takes the other %! hf = figure ("visible", "off"); %! unwind_protect %! x = [1 2 3 4 5 6 7 8]'; y = [2 1 4 3 6 5 8 7]'; %! ## Offsets and size are R2024a's own, measured. %! h = scatterhist (x, y, "Location", "SouthWest"); %! ps = get (h(1), "position"); px = get (h(2), "position"); %! assert_equal (ps(1:2), [0.35, 0.35], 1e-12); %! assert_equal (ps(3:4), [0.55, 0.55], 1e-12); %! assert_equal (px(2) < ps(2), true); # x marginal below the scatter %! h = scatterhist (x, y, "Location", "NorthEast"); %! ps = get (h(1), "position"); px = get (h(2), "position"); %! assert_equal (ps(1:2), [0.10, 0.10], 1e-12); %! assert_equal (px(2) > ps(2), true); # and above it here %! ## The y marginal follows the named corner in the same way. %! h = scatterhist (x, y, "Location", "SouthEast"); %! ps = get (h(1), "position"); py = get (h(3), "position"); %! assert_equal (ps(1:2), [0.10, 0.35], 1e-12); %! assert_equal (py(1) > ps(1), true); # y marginal right of scatter %! h = scatterhist (x, y, "Location", "NorthWest"); %! ps = get (h(1), "position"); py = get (h(3), "position"); %! assert_equal (ps(1:2), [0.35, 0.10], 1e-12); %! assert_equal (py(1) < ps(1), true); # and left of it here %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # Direction is relative to where Location put each marginal %! hf = figure ("visible", "off"); %! unwind_protect %! x = [1 2 3 4 5 6 7 8]'; y = [2 1 4 3 6 5 8 7]'; %! ## "in" points the bars at the scatter from whichever side they sit on. %! ## The x marginal's ydir is R2024a's, measured in all four corners; the y %! ## marginal's xdir is ours, MATLAB rotating that axes instead (see the %! ## note in the file header). %! h = scatterhist (x, y, "Location", "SouthWest"); %! assert_equal (get (h(2), "ydir"), "normal"); %! assert_equal (get (h(3), "xdir"), "normal"); %! h = scatterhist (x, y, "Location", "NorthEast"); %! assert_equal (get (h(2), "ydir"), "reverse"); %! assert_equal (get (h(3), "xdir"), "reverse"); %! h = scatterhist (x, y, "Location", "SouthEast"); %! assert_equal (get (h(2), "ydir"), "normal"); %! assert_equal (get (h(3), "xdir"), "reverse"); %! h = scatterhist (x, y, "Location", "NorthWest"); %! assert_equal (get (h(2), "ydir"), "reverse"); %! assert_equal (get (h(3), "xdir"), "normal"); %! ## and "out" inverts each of them %! h = scatterhist (x, y, "Location", "NorthEast", "Direction", "out"); %! assert_equal (get (h(2), "ydir"), "normal"); %! assert_equal (get (h(3), "xdir"), "normal"); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # Direction points the marginals toward the scatter by default %! hf = figure ("visible", "off"); %! unwind_protect %! x = [1 2 3 4 5 6 7 8]'; y = [2 1 4 3 6 5 8 7]'; %! h = scatterhist (x, y); %! assert_equal (get (h(2), "ydir"), "normal"); %! assert_equal (get (h(3), "xdir"), "normal"); %! h = scatterhist (x, y, "Direction", "out"); %! assert_equal (get (h(2), "ydir"), "reverse"); %! assert_equal (get (h(3), "xdir"), "reverse"); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # Color sets the group colours, cycling if fewer than the groups %! hf = figure ("visible", "off"); %! unwind_protect %! x = [1 2 3 4 5 6 7 8]'; y = [2 1 4 3 6 5 8 7]'; %! g = [1 1 1 1 2 2 2 2]'; %! h = scatterhist (x, y, "Group", g, "Color", "rb"); %! c = get (get (h(1), "children"), "color"); %! assert_equal (c{2}, [1, 0, 0]); %! assert_equal (c{1}, [0, 0, 1]); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # PlotGroup pools the marginals when off %! hf = figure ("visible", "off"); %! unwind_protect %! x = [1 2 3 4 5 6 7 8]'; y = [2 1 4 3 6 5 8 7]'; %! g = [1 1 1 1 2 2 2 2]'; %! h = scatterhist (x, y, "Group", g, "PlotGroup", "off"); %! assert_equal (numel (get (h(2), "children")), 1); %! h = scatterhist (x, y, "Group", g, "PlotGroup", "on"); %! assert_equal (numel (get (h(2), "children")), 2); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "NBins", 0) %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "NBins", 2.5) %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "NBins", [2, 3, 4]) %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "Kernel", "sometimes") %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "Kernel", 1) %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "Location", "middle") %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "Legend", "maybe") %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "Marker", 7) %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "MarkerSize", 0) %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "Color", [1, 2]) %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "Color", "q") %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "Direction", "sideways") %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "Style", "curvy") %!error ... %! scatterhist ([1 2 3]', [1 2 3]', "PlotGroup", "maybe") %!test %! hf = figure ("visible", "off"); %! unwind_protect %! x = [2.1 3.4 1.9 5.6 4.2 3.3 2.8 6.1 4.9 3.7 2.2 5.1 4.4 3.9 2.6]'; %! y = [1.2 2.4 3.1 2.6 4.5 3.3 5.1 2.8 4.0 3.6 1.9 4.4 3.2 2.1 5.0]'; %! h = scatterhist (x, y); %! assert_equal (numel (h), 3); %! assert_equal (all (isaxes (h), 'all'), true); %! ## the scatter axes hold the data %! sc = get (h(1), "children"); %! assert_equal (get (sc(1), "xdata")(:), x, 1e-12); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # grouped scatterhist runs and returns three axes %! hf = figure ("visible", "off"); %! unwind_protect %! x = [1 2 3 4 5 6]'; %! y = [2 1 4 3 6 5]'; %! g = [1 1 1 2 2 2]'; %! h = scatterhist (x, y, "Group", g); %! assert_equal (numel (h), 3); %! assert_equal (all (isaxes (h), 'all'), true); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # kernel option runs %! hf = figure ("visible", "off"); %! unwind_protect %! x = randn (50, 1); %! y = randn (50, 1); %! h = scatterhist (x, y, "Kernel", "on"); %! assert_equal (numel (h), 3); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test # NBins accepts a two-element specification %! hf = figure ("visible", "off"); %! unwind_protect %! x = randn (40, 1); %! y = randn (40, 1); %! h = scatterhist (x, y, "NBins", [5 8]); %! assert_equal (numel (h), 3); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error scatterhist (1) %!error ... %! scatterhist ({1}, {2}) %!error ... %! scatterhist ([1 2 3], [1 2]) %!error ... %! scatterhist ([1 2 3], [1 2 3], "Group") %!error ... %! scatterhist ([1 2 3], [1 2 3], "bogus", 1) %!error ... %! scatterhist ([1 2 3], [1 2 3], "Group", [1 2]) statistics-release-1.9.2/inst/Plotting/silhouette.m000066400000000000000000000207441524624707500224760ustar00rootroot00000000000000## Copyright (C) 2016 Nan Zhou ## Copyright (C) 2021 Stefano Guidoni ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} silhouette (@var{X}, @var{clust}) ## @deftypefnx {statistics} {[@var{si}, @var{h}] =} silhouette (@var{X}, @var{clust}) ## @deftypefnx {statistics} {[@var{si}, @var{h}] =} silhouette (@dots{}, @var{Metric}, @var{MetricArg}) ## ## Compute the silhouette values of clustered data and show them on a plot. ## ## @var{X} is a n-by-p matrix of n data points in a p-dimensional space. Each ## datapoint is assigned to a cluster using @var{clust}, a vector of n elements, ## one cluster assignment for each data point. ## ## Each silhouette value of @var{si}, a vector of size n, is a measure of the ## likelihood that a data point is accurately classified to the right cluster. ## Defining "a" as the mean distance between a point and the other points from ## its cluster, and "b" as the mean distance between that point and the points ## from other clusters, the silhouette value of the i-th point is: ## ## @tex ## \def\frac#1#2{{\begingroup#1\endgroup\over#2}} ## $$ S_i = \frac{b_i - a_i}{max(a_1,b_i)} $$ ## @end tex ## @ifnottex ## @verbatim ## bi - ai ## Si = ------------ ## max(ai,bi) ## @end verbatim ## @end ifnottex ## ## Each element of @var{si} ranges from -1, minimum likelihood of a correct ## classification, to 1, maximum likelihood. ## ## Optional input value @var{Metric} is the metric used to compute the distances ## between data points. Since @code{silhouette} uses @code{pdist} to compute ## these distances, @var{Metric} is similar to the @var{Distance} input argument ## of @code{pdist} and it can be: ## @itemize @bullet ## @item A known distance metric defined as a string: @qcode{euclidean}, ## @qcode{squaredeuclidean} (default), @qcode{seuclidean}, @qcode{mahalanobis}, ## @qcode{cityblock}, @qcode{minkowski}, @qcode{chebychev}, @qcode{cosine}, ## @qcode{correlation}, @qcode{hamming}, @qcode{jaccard}, or @qcode{spearman}. ## ## @item A vector as those created by @code{pdist}. In this case @var{X} does ## nothing. ## ## @item A function handle that is passed to @code{pdist} with @var{MetricArg} ## as optional inputs. ## @end itemize ## ## Optional return value @var{h} is a handle to the silhouette plot. ## ## @strong{Reference} ## Peter J. Rousseeuw, Silhouettes: a Graphical Aid to the Interpretation and ## Validation of Cluster Analysis. 1987. doi:10.1016/0377-0427(87)90125-7 ## ## @seealso{dendrogram, evalclusters, kmeans, linkage, pdist} ## @end deftypefn function [si, h] = silhouette (X, clust, metric = 'squaredeuclidean', varargin) ## check the input parameters if (nargin < 2) print_usage (); endif ## Check for last argument being 'DoNotPlot' to prevent from opening a figure ## Undocumented feature to avoid issues with failing tests. It is only used by ## SilhouetteEvaluation class. DisplayPlot = true; if (numel (varargin) > 0) if (ischar (varargin{end})) if (strcmp (varargin{end}, 'DoNotPlot')) DisplayPlot = false; varargin{end} = []; endif endif endif n = size (clust, 1); ## check size if (! isempty (X)) if (size (X, 1) != n) error ("First dimension of X <%d> doesn't match that of clust <%d>",... size (X, 1), n); endif endif ## check metric if (ischar (metric)) metric = lower (metric); switch (metric) case 'sqeuclidean' metric = 'squaredeuclidean'; case {'euclidean', 'squaredeuclidean', 'seuclidean', 'mahalanobis', ... 'cityblock', 'minkowski', 'chebychev', 'cosine', 'correlation', ... 'hamming', 'jaccard', 'spearman'} ; otherwise error ("silhouette: invalid metric '%s'", metric); endswitch elseif (isnumeric (metric) && isvector (metric)) ## X can be omitted when using this distMatrix = squareform (metric); if (size (distMatrix, 1) != n) error ("First dimension of X <%d> doesn't match that of clust <%d>",... size (distMatrix, 1), n); endif endif ## main si = zeros (n, 1); clusterIDs = unique (clust); # eg [1; 2; 3; 4] m = length (clusterIDs); ## if only one cluster is defined, the silhouette value is not defined if (m == 1) si = NaN * ones (n, 1); return; endif ## Precompute cluster membership masks and counts clusterMask = false (n, m); clusterCount = zeros (m, 1); for jjj = 1:m clusterMask(:, jjj) = (clust == clusterIDs(jjj)); clusterCount(jjj) = sum (clusterMask(:, jjj)); endfor ## Map each point to its cluster index (1..m) pointClusterIdx = zeros (n, 1); for jjj = 1:m pointClusterIdx(clusterMask(:, jjj)) = jjj; endfor ## Use precomputed distance matrix if provided, otherwise compute on-the-fly if (exist ('distMatrix', 'var')) ## Use full precomputed distance matrix for iii = 1:n myCluster = pointClusterIdx(iii); myMask = clusterMask(:, myCluster); myCount = clusterCount(myCluster); ## Singleton cluster if (myCount == 1) si(iii) = 1; continue; endif ## a(i): mean distance to own cluster (excluding self) a_i = sum (distMatrix(iii, myMask)) / (myCount - 1); ## b(i): minimum mean distance to other clusters b_i = Inf; for jjj = 1:m if (jjj != myCluster) b_i = min (b_i, mean (distMatrix(iii, clusterMask(:, jjj)))); endif endfor ## s(i) si(iii) = (b_i - a_i) / max (a_i, b_i); endfor else ## Compute distances row-by-row to avoid O(n^2) memory for iii = 1:n ## Compute distances from point iii to all other points diffs = X - X(iii, :); switch (metric) case 'squaredeuclidean' dists = sum (diffs .^ 2, 2); case 'euclidean' dists = sqrt (sum (diffs .^ 2, 2)); case 'cityblock' dists = sum (abs (diffs), 2); case 'chebychev' dists = max (abs (diffs), [], 2); otherwise ## Fall back to pdist2-style computation for other metrics dists = pdist2 (X(iii, :), X, metric, varargin{:})'; endswitch myCluster = pointClusterIdx(iii); myMask = clusterMask(:, myCluster); myCount = clusterCount(myCluster); ## Singleton cluster if (myCount == 1) si(iii) = 1; continue; endif ## a(i): mean distance to own cluster (excluding self, dist=0) a_i = sum (dists(myMask)) / (myCount - 1); ## b(i): minimum mean distance to other clusters b_i = Inf; for jjj = 1:m if (jjj != myCluster) b_i = min (b_i, mean (dists(clusterMask(:, jjj)))); endif endfor ## s(i) si(iii) = (b_i - a_i) / max (a_i, b_i); endfor endif ## plot ## a poor man silhouette graph if (DisplayPlot) vBarsc = zeros (m, 1); vPadding = [0; 0; 0; 0]; Bars = vPadding; for i = 1 : m vBar = si(find (clust == clusterIDs(i))); vBarsc(i) = length (Bars) + (length (vBar) / 2); Bars = [Bars; (sort (vBar, 'descend')); vPadding]; endfor figure (); h = barh (Bars, 'hist', 'facecolor', [0 0.4471 0.7412]); xlabel ('Silhouette Value'); ylabel ('Cluster'); set (gca, 'ytick', vBarsc, 'yticklabel', clusterIDs); ylim ([0 (length (Bars))]); axis ('ij'); endif endfunction %!demo %! load fisheriris; %! X = meas(:,3:4); %! cidcs = kmeans (X, 3, 'Replicates', 5); %! silhouette (X, cidcs); %! y_labels(cidcs([1 51 101])) = unique (species); %! set (gca, 'yticklabel', y_labels); %! title ('Fisher''s iris data'); ## Test input validation %!error silhouette (); %!error silhouette ([1 2; 1 1]); %!error silhouette ([1 2; 1 1], [1 2 3]'); %!error silhouette ([1 2; 1 1], [1 2]', 'xxx'); statistics-release-1.9.2/inst/Plotting/violin.m000066400000000000000000000364601524624707500216130ustar00rootroot00000000000000## Copyright (C) 2016 Juan Pablo Carbajal ## Copyright (C) 2022-2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program. If not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} violin (@var{x}) ## @deftypefnx {statistics} {@var{h} =} violin (@var{x}) ## @deftypefnx {statistics} {@var{h} =} violin (@dots{}, @var{property}, @var{value}, @dots{}) ## @deftypefnx {statistics} {@var{h} =} violin (@var{hax}, @dots{}) ## @deftypefnx {statistics} {@var{h} =} violin (@dots{}, @code{"horizontal"}) ## ## Produce a Violin plot of the data @var{x}. ## ## The input data @var{x} can be a N-by-m array containing N observations of m ## variables. It can also be a cell with m elements, for the case in which the ## variables are not uniformly sampled. ## ## The following @var{property} can be set using @var{property}/@var{value} ## pairs (default values in parenthesis). The value of the property can be a ## scalar indicating that it applies to all the variables in the data. It can ## also be a cell/array, indicating the property for each variable. In this case ## it should have m columns (as many as variables). ## ## @table @asis ## ## @item Color ## (@asis{"y"}) Indicates the filling color of the violins. ## ## @item Nbins ## (50) Internally, the function calls @command{hist} to compute the histogram ## of the data. This property indicates how many bins to use. ## See @command{help hist} for more details. ## ## @item SmoothFactor ## (4) The function performs simple kernel density estimation and automatically ## finds the bandwidth of the kernel function that best approximates the ## histogram using optimization (@command{sqp}). ## The result is in general very noisy. To smooth the result the bandwidth is ## multiplied by the value of this property. The higher the value the smoother ## the violins, but values too high might remove features from the data ## distribution. ## ## @item Bandwidth ## (NA) If this property is given a value other than NA, it sets the bandwidth ## of the kernel function. No optimization is performed and the property ## @asis{SmoothFactor} is ignored. ## ## @item Width ## (0.5) Sets the maximum width of the violins. Violins are centered at integer ## axis values. The distance between two violin middle axis is 1. Setting a ## value higher than 1 in this property will cause the violins to overlap. ## @end table ## ## If the string @asis{"Horizontal"} is among the input arguments, the violin ## plot is rendered along the x axis with the variables in the y axis. ## ## The returned structure @var{h} has handles to the plot elements, allowing ## customization of the visualization using set/get functions. ## ## Example: ## ## @example ## title ("Grade 3 heights"); ## axis ([0,3]); ## set (gca, "xtick", 1:2, "xticklabel", @{"girls"; "boys"@}); ## h = violin (@{randn(100,1)*5+140, randn(130,1)*8+135@}, "Nbins", 10); ## set (h.violin, "linewidth", 2) ## @end example ## ## @seealso{boxplot, hist} ## @end deftypefn function h = violin (ax, varargin) if (nargin < 1) print_usage (); endif # First argument is not an axis if (! ishandle (ax) || ! isscalar (ax)) x = ax; ax_given = false; else if (isempty (varargin)) print_usage (); endif x = varargin{1}; varargin(1) = []; ax_given = true; endif ###################### ## Parse parameters ## parser = inputParser (); parser.CaseSensitive = false; parser.FunctionName = 'violin'; parser.addParamValue ('Nbins', 50); parser.addParamValue ('SmoothFactor', 4); parser.addParamValue ('Bandwidth', NA); parser.addParamValue ('Width', 0.5); parser.addParamValue ('Color', 'y'); parser.addSwitch ('Horizontal'); parser.parse (varargin{:}); res = parser.Results; c = res.Color; # Color of violins if (ischar (c)) c = c(:); endif nb = res.Nbins; # Number of bins in histogram sf = res.SmoothFactor; # Smoothing factor for kernel estimation r0 = res.Bandwidth; # User value for KDE bandwidth to prevent optimization is_horiz = res.Horizontal; # Whether the plot must be rotated width = res.Width; # Width of the violins clear parser res ###################### ## Make everything a cell for code simplicity if (! iscell (x)) ## A vector is one variable however it is oriented, as in boxplot. Taking ## a row vector as many variables of a single observation each made every ## row vector fail inside the kernel estimate. if (isvector (x)) x = x(:); endif [N, Nc] = size (x); if (N == 0 || Nc == 0) error ("violin: X must not be empty."); endif x = mat2cell (x, N, ones (1, Nc)); else Nc = numel (x); if (Nc == 0) error ("violin: X must not be empty."); endif x = cellfun (@(v) v(:), x, 'UniformOutput', false); endif ## Integer and logical observations are perfectly good data; the kernel ## estimate only needs them in floating point, which is what var insists on. ## And a variable needs a spread for there to be a density at all. Both ## used to surface as an internal subscript or nonconformance error. if (! all (cellfun (@(v) isnumeric (v) || islogical (v), x))) error ("violin: X must be numeric or logical."); endif if (any (cellfun (@numel, x) < 2)) error ("violin: each variable in X needs at least two observations."); endif x = cellfun (@double, x, 'UniformOutput', false); ## Only now claim an axis. gca creates a figure when there is none, so ## doing it before the checks above left a rejected call -- violin ([]), ## violin (5), violin ('abc') -- with an empty figure open behind the error. if (! ax_given) ax = gca (); endif try [nb, c, sf, r0, width] = to_cell (nb, c, sf, r0, width, Nc); catch err if strcmp (err.identifier, 'to_cell:element_idx') n = str2num (err.message); txt = {'Nbins', 'Color', 'SmoothFactor', 'Bandwidth', 'Width'}; error ("Octave:invalid-input-arg", ... ['options should be scalars or cell/array with as many values as' ... ' number of variables in the data (wrong size of %s).'], txt{n}); else rethrow (lasterror ()) endif end_try_catch ## Build violins [px py mx] = cellfun (@(y,n,s,r)build_polygon (y, n, s, r), ... x, nb, sf, r0, 'unif', 0); Nc = 1:numel (px); Ncc = mat2cell (Nc, 1, ones (1, Nc(end))); ## get hold state old_hold = ishold (); ## Draw plain violins tmp = cellfun (@(x,y,n,u, w)patch (ax, (w * x + n)(:), y(:) ,u'), ... px, py, Ncc, c, width); h.violin = tmp; hold on ## Overlay mean value tmp = cellfun (@(z,y)plot (ax, z, y,'.k', 'markersize', 6), Ncc, mx); h.mean = tmp; ## Overlay median Mx = cellfun (@median, x, 'unif', 0); tmp = cellfun (@(z,y)plot (ax, z, y, 'ok'), Ncc, Mx); h.median = tmp; ## Overlay 1st and 3rd quartiles LUBU = cellfun (@(x,y)abs (quantile (x,[0.25 0.75])-y), x, Mx, 'unif', 0); tmp = cellfun (@(x,y,z)errorbar (ax, x, y, z(1),z(2)), Ncc, Mx, LUBU)(:); ## Flatten errorbar output handles tmp2 = allchild (tmp); if (! iscell (tmp2)) tmp2 = mat2cell (tmp2, ones (length (tmp2), 1), 1); endif tmp = mat2cell (tmp, ones (length (tmp), 1), 1); tmp = cellfun (@vertcat, tmp, tmp2, 'unif', 0); h.quartile = cell2mat (tmp); hold off ## Rotate the plot if it is horizontal if (is_horiz) structfun (@swap_axes, h); set (ax, 'ytick', Nc); else set (ax, 'xtick', Nc); endif if (nargout < 1); clear h; endif ## restore hold state if (old_hold) hold on endif endfunction function y = stdnormal_pdf (x) y = (2 * pi)^(- 1/2) * exp (- x .^ 2 / 2); endfunction function k = kde (x,r) k = mean (stdnormal_pdf (x / r)) / r; k /= max (k); endfunction function [px py mx] = build_polygon (x, nb, sf, r) N = size (x, 1); mx = mean (x); sx = std (x); X = (x - mx ) / sx; [count bin] = hist (X, nb); count /= max (count); Y = X - bin; if isna (r) r0 = 1.06 * N^(1/5); r = sqp (r0, @(r)sumsq (kde (Y,r) - count), [], [], 1e-3, 1e2); else sf = 1; endif sig = sf * r; ## Create violin polygon ## smooth tails: extend to 1.83 sigmas, i.e. ~99% of data. xx = linspace (0, 1.83 * sig, 5); bin = [bin(1)-fliplr(xx) bin bin(end)+xx]; py = [bin; fliplr(bin)].' * sx + mx; v = kde (X-bin, sig).'; px = [v -flipud(v)]; endfunction function tf = swap_axes (h) tmp = mat2cell (h(:), ones (length (h),1), 1); tmpy = cellfun (@(x)get (x, 'ydata'), tmp, 'unif', 0); tmpx = cellfun (@(x)get (x, 'xdata'), tmp, 'unif', 0); cellfun (@(h,x,y)set (h, 'xdata', y, 'ydata', x), tmp, tmpx, tmpy); tf = true; endfunction function varargout = to_cell (varargin) m = varargin{end}; varargin(end) = []; for i = 1:numel (varargin) x = varargin{i}; if (isscalar (x)) x = repmat (x, m, 1); endif if (iscell (x)) if (numel (x) != m) # no dimension equals m error ("to_cell:element_idx", "%d\n",i); endif varargout{i} = x; continue endif sz = size (x); d = find (sz == m); if (isempty (d)) # no dimension equals m error ("to_cell:element_idx", "%d\n",i); elseif (length (d) == 2) ## both dims are m, choose 1st elseif (d == 1) # 2nd dimension is m --> transpose x = x.'; sz = fliplr (sz); endif varargout{i} = mat2cell (x, sz(1), ones (m,1)); endfor endfunction %!demo %! rng (42); %! clf %! x = zeros (9e2, 10); %! for i=1:10 %! x(:,i) = (0.1 * randn (3e2, 3) * (randn (3,1) + 1) + 2 * randn (1,3))(:); %! endfor %! h = violin (x, 'color', 'c'); %! axis tight %! set (h.violin, 'linewidth', 2); %! set (gca, 'xgrid', 'on'); %! xlabel ('Variables') %! ylabel ('Values') %!demo %! rng (42); %! clf %! data = {randn(100,1)*5+140, randn(130,1)*8+135}; %! subplot (1,2,1) %! title ('Grade 3 heights - vertical'); %! set (gca, 'xtick', 1:2, 'xticklabel', {'girls'; 'boys'}); %! violin (data, 'Nbins', 10); %! axis tight %! %! subplot (1,2,2) %! title ('Grade 3 heights - horizontal'); %! set (gca, 'ytick', 1:2, 'yticklabel', {'girls'; 'boys'}); %! violin (data, 'horizontal', 'Nbins', 10); %! axis tight %!demo %! rng (42); %! rande ('state', 42); %! clf %! data = exprnd (0.1, 500,4); %! violin (data, 'nbins', {5,10,50,100}); %! axis ([0 5 0 max(data(:))]) %!demo %! rng (42); %! rande ('state', 42); %! clf %! data = exprnd (0.1, 500,4); %! violin (data, 'color', jet (4)); %! axis ([0 5 0 max(data(:))]) %!demo %! rng (42); %! rande ('state', 42); %! clf %! data = repmat (exprnd (0.1, 500,1), 1, 4); %! violin (data, 'width', linspace (0.1,0.5,4)); %! axis ([0 5 0 max(data(:))]) %!demo %! rng (42); %! rande ('state', 42); %! clf %! data = repmat (exprnd (0.1, 500,1), 1, 4); %! violin (data, 'nbins', [5,10,50,100], 'smoothfactor', [4 4 8 10]); %! axis ([0 5 0 max(data(:))]) ## Test plotting %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! data = exprnd (0.1, 500,4); %! violin (data, 'color', jet (4)); %! axis ([0 5 0 max(data(:))]) %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! data = {randn(100,1)*5+140, randn(130,1)*8+135}; %! subplot (1,2,1) %! title ('Grade 3 heights - vertical'); %! set (gca, 'xtick', 1:2, 'xticklabel', {'girls'; 'boys'}); %! violin (data, 'Nbins', 10); %! axis tight %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! data = {randn(100,1)*5+140, randn(130,1)*8+135}; %! subplot (1,2,1) %! title ('Grade 3 heights - vertical'); %! set (gca, 'xtick', 1:2, 'xticklabel', {'girls'; 'boys'}); %! violin (data, 'Nbins', 10); %! axis tight %! subplot (1,2,2) %! title ('Grade 3 heights - horizontal'); %! set (gca, 'ytick', 1:2, 'yticklabel', {'girls'; 'boys'}); %! violin (data, 'horizontal', 'Nbins', 10); %! axis tight %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! data = repmat (exprnd (0.1, 500,1), 1, 4); %! violin (data, 'nbins', [5,10,50,100], 'smoothfactor', [4 4 8 10]); %! axis ([0 5 0 max(data(:))]) %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! data = repmat (exprnd (0.1, 500,1), 1, 4); %! violin (data, 'width', linspace (0.1,0.5,4)); %! axis ([0 5 0 max(data(:))]) %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## A vector is one variable however it is oriented, as in boxplot. A row ## vector used to be read as many variables of a single observation each, and ## died inside the kernel estimate. %!test %! hf = figure ("visible", "off"); %! unwind_protect %! r = violin (randn (1, 60)); %! nrow = numel (findobj (gca (), "Type", "patch")); %! clf; %! c = violin (randn (60, 1)); %! ncol = numel (findobj (gca (), "Type", "patch")); %! assert_equal (nrow, 1); %! assert_equal (ncol, 1); %! assert_equal (numel (r.violin), numel (c.violin)); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Columns of a matrix remain one variable each. %!test %! hf = figure ("visible", "off"); %! unwind_protect %! h = violin (randn (40, 3)); %! assert_equal (numel (findobj (gca (), "Type", "patch")), 3); %! assert_equal (numel (h.violin), 3); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## A cell of row vectors is as good as a cell of columns. %!test %! hf = figure ("visible", "off"); %! unwind_protect %! violin ({[1, 2, 3, 4], [3, 4, 5, 6]}); %! assert_equal (numel (findobj (gca (), "Type", "patch")), 2); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Integer and logical observations are data too; var wanted them in floating ## point, so they were refused outright. %!test %! hf = figure ("visible", "off"); %! unwind_protect %! v = [1 2 3 4 5 4 3 2 1 2 3]; %! violin (int32 (v)); %! ni = numel (findobj (gca (), "Type", "patch")); %! clf; %! violin (double (v)); %! nd = numel (findobj (gca (), "Type", "patch")); %! assert_equal (ni, nd); %! clf; %! violin (logical ([1 0 1 1 0 0 1 0 1 1])); %! assert_equal (numel (findobj (gca (), "Type", "patch")), 1); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error violin () %!error violin ([]) %!error violin ({}) %!error ... %! violin (5) %!error ... %! violin ({[1, 2, 3], 4}) %!error violin ('abcdef') statistics-release-1.9.2/inst/Plotting/wblplot.m000066400000000000000000000314061524624707500217710ustar00rootroot00000000000000## Copyright (C) 2014 Bj{\"o}rn Vennberg ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program. If not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} wblplot (@var{data}, @dots{}) ## @deftypefnx {statistics} {@var{handle} =} wblplot (@var{data}, @dots{}) ## @deftypefnx {statistics} {[@var{handle}, @var{param}] =} wblplot (@var{data}) ## @deftypefnx {statistics} {[@var{handle}, @var{param}] =} wblplot (@var{data}, @var{censor}) ## @deftypefnx {statistics} {[@var{handle}, @var{param}] =} wblplot (@var{data}, @var{censor}, @var{freq}) ## @deftypefnx {statistics} {[@var{handle}, @var{param}] =} wblplot (@var{data}, @var{censor}, @var{freq}, @var{confint}) ## @deftypefnx {statistics} {[@var{handle}, @var{param}] =} wblplot (@var{data}, @var{censor}, @var{freq}, @var{confint}, @var{fancygrid}) ## @deftypefnx {statistics} {[@var{handle}, @var{param}] =} wblplot (@var{data}, @var{censor}, @var{freq}, @var{confint}, @var{fancygrid}, @var{showlegend}) ## ## Plot a column vector @var{data} on a Weibull probability plot using rank ## regression. ## ## @var{censor}: optional parameter is a column vector of same size as ## @var{data} with 1 for right censored data and 0 for exact observation. ## Pass [] when no censor data are available. ## ## @var{freq}: optional vector same size as @var{data} with the number of ## occurrences for corresponding data. ## Pass [] when no frequency data are available. ## ## @var{confint}: optional confidence limits for plotting upper and lower ## confidence bands using beta binomial confidence bounds. If a single ## value is given this will be used such as LOW = a and HIGH = 1 - a. ## Pass [] if confidence bounds is not requested. ## ## @var{fancygrid}: optional parameter which if set to anything but 1 will turn ## off the fancy gridlines. ## ## @var{showlegend}: optional parameter that when set to zero(0) turns off the ## legend. ## ## If one output argument is given, a @var{handle} for the data marker and ## plotlines is returned, which can be used for further modification of line and ## marker style. ## ## If a second output argument is specified, a @var{param} vector with scale, ## shape and correlation factor is returned. ## ## @seealso{normplot, wblpdf} ## @end deftypefn function [handle, param] = wblplot (data, censor = [], freq = [], ... confint = [], fancygrid = 1, showlegend = 1) [mm, nn] = size (data); if (mm > 1 && nn > 1) error ("wblplot: can only handle a single data vector") elseif (mm == 1 && nn > 1) data = data(:); mm = nn; endif if (any (data <= 0)) error ("wblplot: data vector must be positive and non zero") endif ## A non-finite observation is not merely useless here, it is unrecoverable: ## Inf drives the view port to 10 ^ ceil (log10 (Inf)), and the grid loop ## below then runs over log10 (xmin) : Inf, never returning, while NaN makes ## the rank regression singular and plots a meaningless line. if (! all (isfinite (data))) error ("wblplot: data vector must contain finite values") endif if (isempty (freq)) freq = ones (mm, 1); N = mm; else [mmf nnf] = size (freq); if ((mmf == mm && nnf == 1) || (mmf == 1 && nnf == mm)) freq = freq(:); N = sum (freq); ## Total number of samples if (any (freq <= 0)) error ("wblplot: frequency vector must be positive non zero integers") endif else error ("wblplot: frequency must be vector of same length as data") endif endif if (isempty (censor)) censor = zeros (mm,1); else [mmc, nnc] = size (censor); if ((mmc == mm && nnc == 1) || (mmc == 1 && nnc == mm)) censor = censor(:); else error ("wblplot: censor must be a vector of same length as data") endif ## Make sure censored data is sorted correctly so that no censored samples ## are processed before failures if they have the same time. if (any (censor > 0)) ind = find (censor > 0); ind2 = find (data(1:end-1) == data(2:end)); if ((! isempty (ind)) && (! isempty (ind2))) if (any (ind == ind2)) tmp = censor(ind2); censor(ind2) = censor(ind2 + 1); censor(ind2+1) = tmp; tmp = freq(ind2); freq(ind2) = freq(ind2 + 1); freq(ind2 + 1) = tmp; endif endif endif endif ## Determine the order number wbdat = zeros (length (find (censor == 0)), 3); Op = 0; Oi = 0; c = N; nf = 0; for k = 1 : mm if (censor(k, 1) == 0) nf = nf + 1; wbdat(nf, 1) = data(k, 1); for s = 1 : freq(k, 1); Oi = Op + ((N + 1) - Op) / (1 + c); Op = Oi; c = c - 1; endfor wbdat(nf, 3) = Oi; else c = c - freq(k, 1); endif endfor ## Compute median rank a = wbdat(:, 3) ./ (N - wbdat(:, 3) + 1); f = finv (0.5, 2 * (N - wbdat(:, 3) + 1), 2 * wbdat(:, 3)); wbdat(:, 2) = a ./ (f+a); datx = log (wbdat(:,1)); daty = log (log (1 ./ (1 - wbdat(:,2)))); ## Rank regression poly = polyfit (datx, daty, 1); ## Shape factor beta_rry = poly(1); ## Scale factor eta_rry = exp (-(poly(2) / beta_rry)); ## Determine min-max values of view port aa = ceil (log10 (max (wbdat(:,1)))); bb = log10 (max (wbdat(:,1))); if ((aa - bb) < 0.2) aa = ceil (log10 (max (wbdat(:,1)))) + 1; endif xmax = 10 ^ aa; if ((log10 (min (wbdat(:,1))) - floor (log10 (min (wbdat(:,1))))) < 0.2) xmin = 10 ^ (floor (log10 (min (wbdat(:,1)))) - 1); else xmin = 10 ^ floor (log10 (min (wbdat(:,1)))); endif if (min (wbdat(:,2)) > 0.20) ymin = log (log (1 / (1 - 0.1))); elseif (min (wbdat(:,2)) > 0.02) ymin = log (log (1 / (1 - 0.01))); elseif (min (wbdat(:,2)) > 0.002) ymin = log (log (1 / (1 - 0.001))); else ymin = log (log (1 / (1 - 0.0001))); endif ymax= log (log (1 / (1 - 0.999))); x = [0;0]; y = [0;0]; label = char ('0.10', '1.00', '10.00', '99.00'); prob = [0.001 0.01 0.1 0.99]; tick = log (log (1 ./ (1 - prob))); xbf = [xmin; xmax]; ybf = polyval (poly, log (xbf)); newplot (); x(1, 1) = xmin; x(2, 1) = xmax; if (fancygrid == 1) for k = 1 : 4 ## Y major grids x(1, 1) = xmin; x(2, 1) = xmax*10; y(1, 1) = log (log (1 / (1 - 10 ^ (-k)))); y(2, 1) = y(1, 1); ymajorgrid(k) = line (x, y, 'LineStyle', '-', 'Marker', 'none', ... 'Color', [1 0.75 0.75], 'LineWidth', 0.1); endfor ## Y Minor grids 2 - 9 x(1, 1) = xmin; x(2, 1) = xmax * 10; for m = 1 : 4 for k = 1 : 8 y(1, 1) = log (log (1 / (1 - ((k + 1) / (10 ^ m))))); y(2, 1) = y(1, 1); yminorgrid(k) = line (x, y, 'LineStyle', '-', 'Marker', 'none', ... 'Color', [0.75 1 0.75], 'LineWidth', 0.1); endfor endfor ## X-axis grid y(1, 1) = ymin; y(2, 1) = ymax; for m = log10 (xmin) : log10 (xmax) x(1, 1) = 10 ^ m; x(2, 1) = x(1, 1); y(1, 1) = ymin; y(2, 1) = ymax; xmajorgrid(k) = line (x, y, 'LineStyle', '-', 'Marker', 'none', ... 'Color', [1 0.75 0.75]); for k = 1 : 8 ## X Minor grids - 2 - 9 x(1, 1) = (k + 1) * (10 ^ m); x(2, 1) = (k + 1) * (10 ^ m); xminorgrid(k) = line (x, y, 'LineStyle', '-', 'Marker', 'none', ... 'Color', [0.75 1 0.75], 'LineWidth', 0.1); endfor endfor endif set (gca, 'XScale', 'log'); set (gca, 'YTick', tick, 'YTickLabel', label); xlabel ('Data', 'FontSize', 12); ylabel ('Unreliability, F(t)=1-R(t)', 'FontSize', 12); title ('Weibull Probability Plot', 'FontSize', 12); set (gcf, 'Color', [0.9, 0.9, 0.9]); set (gcf, 'name', 'WblPlot'); hold on h = plot (wbdat(:,1), daty, 'o'); set (h, 'markerfacecolor', [0, 0, 1]); set (h, 'markersize', 8); h2 = line (xbf, ybf, 'LineStyle', '-', 'Marker', 'none', ... 'Color', [0.25 0.25 1], 'LineWidth', 1); ## If requested plot beta binomial confidence bounds if (! isempty (confint)) cb_high = []; cb_low = []; if (length (confint) == 1) if (confint > 0.5) cb_high = confint; cb_low = 1 - confint; else cb_high = 1 - confint; cb_low = confint; endif else cb_high = confint(2); cb_low = confint(1); endif conf = zeros (N + 4, 3); betainv = 1 / beta_rry; N2 = [1:N]'; N2 = [0.3; 0.7; N2; N2(end) + 0.5; N2(end) + 0.8]; ## Extend the ends a bit ypos = medianranks (0.5, N, N2); conf(:, 1) = eta_rry * log (1 ./ (1 - ypos)) .^ betainv; conf(:, 2) = medianranks (cb_low, N, N2); conf(:, 3) = medianranks (cb_high, N, N2); confy = log (log (1 ./ (1 - conf(:,2:3)))); confu = [conf(:,1) confy]; if (conf(1,1) > xmin) ## It looks better to extend the lines. p1 = polyfit (log (conf(1:2,1)), confy(1:2,1), 1); y1 = polyval (p1, log (xmin)); p2 = polyfit (log (conf(1:2,1)), confy(1:2,2), 1); y2 = polyval (p2, log (xmin)); confu = [xmin y1 y2; confu]; endif if (conf(end,1) < xmax) p3 = polyfit (log (conf(end-1:end,1)), confy(end-1:end,1), 1); y3 = polyval (p3, log (xmax)); p4 = polyfit (log (conf(end-1:end,1)), confy(end-1:end,2), 1); y4 = polyval (p4, log (xmax)); confu = [confu; xmax, y3, y4]; endif h3 = plot (confu(:,1), confu(:,2:3), 'LineStyle', '-' ,'Marker', 'none', ... 'Color', [1 0.25 0.25], 'LineWidth', 1); endif ## Correlation coefficient rsq = corr (datx, daty); if (showlegend == 1) s1 = sprintf (' RRY\n \\beta=%.3f \n \\eta=%.2f \n \\rho=%.4f', ... beta_rry, eta_rry, rsq); if (! isempty (confint)) s2 = sprintf ('CB_H=%.2f', cb_high); s3 = sprintf ('CB_L=%.2f', cb_low); legend ([h; h2; h3], 'Data', s1, s2, s3, 'location', 'northeastoutside'); else legend ([h; h2], 'Data', s1, 'location', 'northeastoutside'); endif legend ('boxoff'); endif axis ([xmin, xmax, ymin, (log (log (1 / (1 - 0.99))))]); hold off if (nargout >= 2) param = [eta_rry, beta_rry, rsq]; if (! isempty (confint)) handle = [h; h2; h3]; else handle = [h; h2]; endif endif if (nargout == 1) if (! isempty (confint)) handle = [h; h2; h3]; else handle = [h; h2]; endif endif endfunction function ret = medianranks (alpha, n, ii) a = ii ./ (n - ii + 1); f = finv (alpha, 2 * (n - ii + 1), 2 * ii); ret = a ./ (f + a); endfunction %!demo %! x = [16 34 53 75 93 120]; %! wblplot (x); %!demo %! x = [2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67]'; %! c = [0 1 0 1 0 1 1 1 0 0 1 0 1 0 1 1 0 1 1]'; %! [h, p] = wblplot (x, c); %! p %!demo %! x = [16, 34, 53, 75, 93, 120, 150, 191, 240 ,339]; %! [h, p] = wblplot (x, [], [], 0.05); %! p %! ## Benchmark Reliasoft eta = 146.2545 beta 1.1973 rho = 0.9999 %!demo %! x = [46 64 83 105 123 150 150]; %! c = [0 0 0 0 0 0 1]; %! f = [1 1 1 1 1 1 4]; %! wblplot (x, c, f, 0.05); %!demo %! x = [46 64 83 105 123 150 150]; %! c = [0 0 0 0 0 0 1]; %! f = [1 1 1 1 1 1 4]; %! ## Subtract 30.92 from x to simulate a 3 parameter wbl with gamma = 30.92 %! wblplot (x - 30.92, c, f, 0.05); ## Test plotting %!test %! hf = figure ('visible', 'off'); %! unwind_protect %! x = [16, 34, 53, 75, 93, 120, 150, 191, 240 ,339]; %! [h, p] = wblplot (x, [], [], 0.05); %! assert_equal (numel (h), 4) %! assert_equal (p(1), 146.2545, 1E-4) %! assert_equal (p(2), 1.1973, 1E-4) %! assert_equal (p(3), 0.9999, 5E-5) %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation ## Inf used to hang: the view port became 10 ^ ceil (log10 (Inf)) and the grid ## loop ran over log10 (xmin) : Inf. NaN returned, but only after polyfit went ## singular on it. %!error ... %! wblplot ([1, Inf, 2, 3]) %!error ... %! wblplot ([1, NaN, 2, 3]) %!error ... %! wblplot ([1, 0, 2, 3]) ## -Inf is negative, so the positivity check catches it before the finite one. %!error ... %! wblplot ([1, 2, 3, -Inf]) %!error ... %! wblplot (ones (3, 3)) statistics-release-1.9.2/inst/Random_Sampling/000077500000000000000000000000001524624707500213765ustar00rootroot00000000000000statistics-release-1.9.2/inst/Random_Sampling/doc-cache000066400000000000000000000231361524624707500231340ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 5 # name: # type: sq_string # elements: 1 # length: 8 johnsrnd # name: # type: sq_string # elements: 1 # length: 1687 statistics: r = johnsrnd ( quantiles ) statistics: r = johnsrnd ( quantiles , m ) statistics: r = johnsrnd ( quantiles , m , n , …) statistics: r = johnsrnd ( quantiles , [ m , n , …]) statistics: [ r , type , coefs ] = johnsrnd (…) Random arrays from the Johnson system of distributions. r = johnsrnd ( quantiles ) returns a random value drawn from the distribution in the Johnson system that matches the four values in quantiles . quantiles is a four-element vector of the desired quantiles at the standard normal quantiles [-1.5, -0.5, 0.5, 1.5] , and its elements must be strictly increasing. johnsrnd fits the Johnson curve passing through these four points using the quantile method of Slifker and Shapiro. quantiles may also be a 2-by-4 numeric matrix, whose first row holds four strictly increasing, evenly spaced standard normal quantiles and whose second row holds the corresponding strictly increasing data quantiles. johnsrnd ( quantiles , m , n , …) or johnsrnd ( quantiles , [ m , n , …]) returns an m -by- n -by-… array of random values, following the size conventions of randn . [ r , type , coefs ] = johnsrnd (…) also returns the selected member of the Johnson system in type , one of "SN" (the normal distribution), "SL" (lognormal), "SU" (unbounded), or "SB" (bounded), and the coefficients coefs = [ gamma , delta , xi , lambda ] of the transform. A value r is generated by transforming a standard normal deviate z as r = xi + lambda * g (( z - gamma ) / delta ) , where g is the identity, exp , sinh , or the logistic function for "SN" , "SL" , "SU" , and "SB" , respectively. See also: pearsrnd, random, randn # name: # type: sq_string # elements: 1 # length: 55 Random arrays from the Johnson system of distributions. # name: # type: sq_string # elements: 1 # length: 8 mhsample # name: # type: sq_string # elements: 1 # length: 2840 statistics: [ smpl , accept ] = mhsample ( start , nsamples , property , value , …) Draws nsamples samples from a target stationary distribution pdf using Metropolis-Hastings algorithm. Inputs: start is a nchain by dim matrix of starting points for each Markov chain. Each row is the starting point of a different chain and each column corresponds to a different dimension. nsamples is the number of samples, the length of each Markov chain. Some property-value pairs can or must be specified, they are: (Required) One of: "pdf" pdf : a function handle of the target stationary distribution to be sampled. The function should accept different locations in each row and each column corresponds to a different dimension. or "logpdf" logpdf : a function handle of the log of the target stationary distribution to be sampled. The function should accept different locations in each row and each column corresponds to a different dimension. In case optional argument symmetric is set to false (the default), one of: "proppdf" proppdf : a function handle of the proposal distribution that is sampled from with proprnd to give the next point in the chain. The function should accept two inputs, the random variable and the current location each input should accept different locations in each row and each column corresponds to a different dimension. or "logproppdf" logproppdf : the log of "proppdf". The following input property/pair values may be needed depending on the desired output: "proprnd" proprnd : (Required) a function handle which generates random numbers from proppdf . The function should accept different locations in each row and each column corresponds to a different dimension corresponding with the current location. "symmetric" symmetric : true or false based on whether proppdf is a symmetric distribution. If true, proppdf (or logproppdf ) need not be specified. The default is false. "burnin" burnin the number of points to discard at the beginning, the default is 0. "thin" thin : omits thin -1 of every thin points in the generated Markov chain. The default is 1. "nchain" nchain : the number of Markov chains to generate. The default is 1. Outputs: smpl : a nsamples x dim x nchain tensor of random values drawn from pdf , where the rows are different random values, the columns correspond to the dimensions of pdf , and the third dimension corresponds to different Markov chains. accept is a vector of the acceptance rate for each chain. Example : Sampling from a normal distribution start = 1; nsamples = 1e3; pdf = @(x) exp (-.5 * x .^ 2) / (pi ^ .5 * 2 ^ .5); proppdf = @(x,y) 1 / 6; proprnd = @(x) 6 * (rand (size (x)) - .5) + x; [smpl, accept] = mhsample (start, nsamples, "pdf", pdf, "proppdf", ... proppdf, "proprnd", proprnd, "thin", 4); histfit (smpl); See also: rand, slicesample # name: # type: sq_string # elements: 1 # length: 101 Draws nsamples samples from a target stationary distribution pdf using Metropolis-Hastings algorithm. # name: # type: sq_string # elements: 1 # length: 8 pearsrnd # name: # type: sq_string # elements: 1 # length: 1499 statistics: r = pearsrnd ( mu , sigma , skew , kurt ) statistics: r = pearsrnd ( mu , sigma , skew , kurt , m ) statistics: r = pearsrnd ( mu , sigma , skew , kurt , m , n , …) statistics: r = pearsrnd ( mu , sigma , skew , kurt , [ m , n , …]) statistics: [ r , type , coefs ] = pearsrnd (…) Random arrays from the Pearson system of distributions. r = pearsrnd ( mu , sigma , skew , kurt ) returns a random value drawn from the distribution in the Pearson system with mean mu , standard deviation sigma , skewness skew , and kurtosis kurt . kurt is the (non-excess) kurtosis, and the moments must satisfy kurt > skew ^2 + 1 . pearsrnd ( mu , sigma , skew , kurt , m , n , …) or pearsrnd (…, [ m , n , …]) returns an m -by- n -by-… array of random values, following the size conventions of randn . [ r , type , coefs ] = pearsrnd (…) also returns the type of the Pearson distribution (an integer 0 to 7 ) in type , and the three coefficients coefs = [ c0 , c1 , c2 ] of the denominator quadratic of the Pearson differential equation for the standardized distribution, so that f '( x ) / f ( x ) = -( x + c1 ) / ( c0 + c1 x + c2 x ^2) . The Pearson types are: 0 normal, 1 four-parameter beta, 2 symmetric four-parameter beta, 3 gamma, 4 (not a named distribution), 5 inverse gamma, 6 beta prime, and 7 Student’s t. Type 4 is generated by numerical inversion of its cumulative distribution function. See also: johnsrnd, random, randn # name: # type: sq_string # elements: 1 # length: 55 Random arrays from the Pearson system of distributions. # name: # type: sq_string # elements: 1 # length: 6 qrandn # name: # type: sq_string # elements: 1 # length: 463 statistics: z = qrandn ( q , r , c ) statistics: z = qrandn ( q , [ r , c ]) Returns random deviates drawn from a q-Gaussian distribution. Parameter q characterizes the q-Gaussian distribution. The result has the size indicated by s . Reference: W. Thistleton, J. A. Marsh, K. Nelson, C. Tsallis (2006) "Generalized Box-Muller method for generating q-Gaussian random deviates" arXiv:cond-mat/0605570 http://arxiv.org/abs/cond-mat/0605570 See also: rand, randn # name: # type: sq_string # elements: 1 # length: 61 Returns random deviates drawn from a q-Gaussian distribution. # name: # type: sq_string # elements: 1 # length: 11 slicesample # name: # type: sq_string # elements: 1 # length: 1756 statistics: [ smpl , neval ] = slicesample ( start , nsamples , property , value , …) Draws nsamples samples from a target stationary distribution pdf using slice sampling of Radford M. Neal. Input: start is a 1 by dim vector of the starting point of the Markov chain. Each column corresponds to a different dimension. nsamples is the number of samples, the length of the Markov chain. Next, several property-value pairs can or must be specified, they are: (Required properties) One of: "pdf" : the value is a function handle of the target stationary distribution to be sampled. The function should accept different locations in each row and each column corresponds to a different dimension. or logpdf : the value is a function handle of the log of the target stationary distribution to be sampled. The function should accept different locations in each row and each column corresponds to a different dimension. The following input property/pair values may be needed depending on the desired output: "burnin" burnin the number of points to discard at the beginning, the default is 0. "thin" thin omits m -1 of every m points in the generated Markov chain. The default is 1. "width" width the maximum Manhattan distance between two samples. The default is 10. Outputs: smpl is a nsamples by dim matrix of random values drawn from pdf where the rows are different random values, the columns correspond to the dimensions of pdf . neval is the number of function evaluations per sample. Example : Sampling from a normal distribution start = 1; nsamples = 1e3; pdf = @(x) exp (-.5 * x .^ 2) / (pi ^ .5 * 2 ^ .5); [smpl, accept] = slicesample (start, nsamples, "pdf", pdf, "thin", 4); histfit (smpl); See also: rand, mhsample, randsample # name: # type: sq_string # elements: 1 # length: 99 Draws nsamples samples from a target stationary distribution pdf using slice sampling of Radford M. statistics-release-1.9.2/inst/Random_Sampling/johnsrnd.m000066400000000000000000000245571524624707500234160ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} johnsrnd (@var{quantiles}) ## @deftypefnx {statistics} {@var{r} =} johnsrnd (@var{quantiles}, @var{m}) ## @deftypefnx {statistics} {@var{r} =} johnsrnd (@var{quantiles}, @var{m}, @var{n}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} johnsrnd (@var{quantiles}, [@var{m}, @var{n}, @dots{}]) ## @deftypefnx {statistics} {[@var{r}, @var{type}, @var{coefs}] =} johnsrnd (@dots{}) ## ## Random arrays from the Johnson system of distributions. ## ## @code{@var{r} = johnsrnd (@var{quantiles})} returns a random value drawn from ## the distribution in the Johnson system that matches the four values in ## @var{quantiles}. @var{quantiles} is a four-element vector of the desired ## quantiles at the standard normal quantiles @code{[-1.5, -0.5, 0.5, 1.5]}, and ## its elements must be strictly increasing. @code{johnsrnd} fits the Johnson ## curve passing through these four points using the quantile method of Slifker ## and Shapiro. ## ## @var{quantiles} may also be a 2-by-4 numeric matrix, whose first row holds ## four strictly increasing, evenly spaced standard normal quantiles and whose ## second row holds the corresponding strictly increasing data quantiles. ## ## @code{johnsrnd (@var{quantiles}, @var{m}, @var{n}, @dots{})} or ## @code{johnsrnd (@var{quantiles}, [@var{m}, @var{n}, @dots{}])} returns an ## @var{m}-by-@var{n}-by-@dots{} array of random values, following the size ## conventions of @code{randn}. ## ## @code{[@var{r}, @var{type}, @var{coefs}] = johnsrnd (@dots{})} also returns ## the selected member of the Johnson system in @var{type}, one of @qcode{"SN"} ## (the normal distribution), @qcode{"SL"} (lognormal), @qcode{"SU"} ## (unbounded), or @qcode{"SB"} (bounded), and the coefficients @var{coefs} = ## @code{[@var{gamma}, @var{delta}, @var{xi}, @var{lambda}]} of the transform. ## A value @var{r} is generated by transforming a standard normal deviate ## @var{z} as @code{@var{r} = @var{xi} + @var{lambda} * g ((@var{z} - ## @var{gamma}) / @var{delta})}, where @code{g} is the identity, @code{exp}, ## @code{sinh}, or the logistic function for @qcode{"SN"}, @qcode{"SL"}, ## @qcode{"SU"}, and @qcode{"SB"}, respectively. ## ## @seealso{pearsrnd, random, randn} ## @end deftypefn function [r, type, coefs] = johnsrnd (quantiles, varargin) if (nargin < 1) print_usage (); endif if (isnumeric (quantiles) && isreal (quantiles) && isvector (quantiles) ... && numel (quantiles) == 4) zrow = [-1.5, -0.5, 0.5, 1.5]; q = quantiles(:)'; elseif (isnumeric (quantiles) && isreal (quantiles) ... && isequal (size (quantiles), [2, 4])) zrow = quantiles(1, :); q = quantiles(2, :); zdiff = diff (zrow); if (any (zdiff <= 0) || any (abs (zdiff - zdiff(1)) > 1e-9 .* abs (zdiff(1)))) error (["johnsrnd: standard normal quantiles must be strictly " ... "increasing and evenly spaced."]); endif else error (["johnsrnd: QUANTILES must be a four-element numeric vector " ... "or a 2-by-4 numeric matrix."]); endif if (any (isnan (q)) || any (diff (q) <= 0)) error ("johnsrnd: QUANTILES must be strictly increasing."); endif ## Fit the Johnson curve in normalised z-space, then rescale gamma and delta ## to the standard normal quantiles actually supplied in ZROW [type, coefs] = johnson_fit (q); h = zrow(2) - zrow(1); center = (zrow(2) + zrow(3)) ./ 2; coefs(1) = center + h .* coefs(1); coefs(2) = h .* coefs(2); ## Draw standard normal deviates and apply the fitted transform. Negative ## dimensions are treated as zero, as in core Octave and MATLAB. szargs = cellfun (@(x) max (x, 0), varargin, 'UniformOutput', false); z = randn (szargs{:}); u = (z - coefs(1)) ./ coefs(2); xi = coefs(3); lambda = coefs(4); switch (type) case "SN" r = xi + lambda .* u; case "SL" r = xi + lambda .* exp (u); case "SU" r = xi + lambda .* sinh (u); case "SB" r = xi + lambda ./ (1 + exp (-u)); endswitch endfunction ## Quantile-method fit (Slifker & Shapiro 1980) at the fixed standard normal ## anchors z = [-1.5, -0.5, 0.5, 1.5], i.e. z0 = 0.5 and 3*z0 = 1.5. Returns ## the Johnson family and the transform coefficients [gamma, delta, xi, lambda]. function [type, coefs] = johnson_fit (q) x1 = q(1); x2 = q(2); x3 = q(3); x4 = q(4); m = x4 - x3; # top gap (x at 1.5 minus x at 0.5) n = x2 - x1; # bottom gap (x at -0.5 minus x at -1.5) p = x3 - x2; # middle gap (x at 0.5 minus x at -0.5) d = m .* n ./ p .^ 2; # Slifker-Shapiro discriminant t1 = m ./ p; t2 = n ./ p; if (abs (d - 1) < 1e-9) if (abs (m - n) < 1e-9) ## Symmetric and linear in z: the normal distribution type = "SN"; coefs = [0, 1 ./ p, (x2 + x3) ./ 2, 1]; else ## Lognormal type = "SL"; delta = 1 ./ log (t1); lambda = sign (delta); scale = p ./ (2 .* sinh (0.5 ./ delta)); gamma = -delta .* log (scale ./ lambda); xi = x2 - lambda .* exp ((-0.5 - gamma) ./ delta); coefs = [gamma, delta, xi, lambda]; endif elseif (d > 1) ## Unbounded type = "SU"; delta = 1 ./ acosh (0.5 .* (t1 + t2)); gamma = delta .* asinh ((t2 - t1) ./ (2 .* sqrt (d - 1))); lambda = 2 .* p .* sqrt (d - 1) ./ ((t1 + t2 - 2) .* sqrt (t1 + t2 + 2)); xi = 0.5 .* (x2 + x3) + p .* (t2 - t1) ./ (2 .* (t1 + t2 - 2)); coefs = [gamma, delta, xi, lambda]; else ## Bounded type = "SB"; a = (1 + p ./ m) .* (1 + p ./ n); r = p .^ 2 ./ (m .* n) - 1; delta = 0.5 ./ acosh (0.5 .* sqrt (a)); gamma = delta .* asinh ((p ./ n - p ./ m) .* sqrt (a - 4) ./ (2 .* r)); lambda = p .* sqrt ((a - 2) .^ 2 - 4) ./ r; xi = 0.5 .* (x2 + x3) - 0.5 .* lambda + p .* (p ./ n - p ./ m) ./ (2 .* r); coefs = [gamma, delta, xi, lambda]; endif endfunction %!demo %! ## Fit a Johnson distribution to four quantiles and identify its type %! rng (42); %! [r, type, coefs] = johnsrnd ([-1, -0.25, 0.75, 3]) %!demo %! ## Draw a sample and check its shape %! rng (42); %! r = johnsrnd ([-1, -0.25, 0.75, 3], 1, 1000); %! hist (r, 50); ## Type and coefficients against MATLAB %!test %! [~, type, coefs] = johnsrnd ([-1.5, -0.5, 0.5, 1.5]); %! assert_equal (type, "SN"); %! assert_equal (coefs, [0, 1, 0, 1], 1e-12); %!test %! [~, type, coefs] = johnsrnd ([-1, -0.25, 0.75, 3]); %! assert_equal (type, "SU"); %! assert_equal (coefs, [-0.843945656908448, 1.03904346061751, ... %! -0.5, 0.741619848709566], 1e-10); %!test %! [~, type, coefs] = johnsrnd ([0.2, 0.9, 1.4, 1.8]); %! assert_equal (type, "SU"); %! assert_equal (coefs, ... %! [1.76613094093528, 2.25444447413448, 1.9, 0.845154254728516], 1e-10); %!test %! [~, type, coefs] = johnsrnd ([1, 2, 4, 9]); %! assert_equal (type, "SU"); %! assert_equal (coefs, [-1.5, 1.03904346061751, 1, 0.894427190999916], 1e-10); %!test %! [~, type, coefs] = johnsrnd ([-3, -0.75, 0.25, 1]); %! assert_equal (type, "SU"); %! assert_equal (coefs, [0.843945656908448, 1.03904346061751, ... %! 0.5, 0.741619848709566], 1e-10); ## Bounded and lognormal fits reproduce their input quantiles (unique fit) %!test %! q = [0.1, 0.3, 0.8, 0.95]; %! [~, type, c] = johnsrnd (q); %! assert_equal (type, "SB"); %! z = [-1.5, -0.5, 0.5, 1.5]; %! xz = c(3) + c(4) ./ (1 + exp (-(z - c(1)) ./ c(2))); %! assert_equal (xz, q, 1e-12); %!test %! q = [0, 1, 3, 7]; %! [~, type, c] = johnsrnd (q); %! assert_equal (type, "SL"); %! z = [-1.5, -0.5, 0.5, 1.5]; %! xz = c(3) + c(4) .* exp ((z - c(1)) ./ c(2)); %! assert_equal (xz, q, 1e-10); ## Size handling follows randn; bounded draws stay within the support %!test %! r = johnsrnd ([-1, -0.25, 0.75, 3], 3, 4); %! assert_equal (size (r), [3, 4]); %! assert_equal (all (isfinite (r), 'all'), true); %! assert_equal (size (johnsrnd ([-1, -0.25, 0.75, 3], -1)), [0, 0]); %! assert_equal (size (johnsrnd ([-1, -0.25, 0.75, 3], 2, -1, 5)), [2, 0, 5]); %!test %! [r, ~, c] = johnsrnd ([0.1, 0.3, 0.8, 0.95], 1, 500); %! assert_equal (all (r > c(3) & r < c(3) + c(4), 'all'), true); %!test %! [~, type, coefs] = johnsrnd ([-7, -3, -1, 0]); %! assert_equal (type, "SL"); %! assert_equal (coefs, [1.5, -1.44269504088896, 1, -1], 1e-10); %! assert_equal (isreal (coefs), true); %!test %! qnorm = [.5, 1, 1.5, 2]; %! q = [16.7000, 18.2086, 19.5376, 21.7263]; %! [r, type, coefs] = johnsrnd ([qnorm; q], 0); %! assert_equal (r, []); %! assert_equal (type, "SU"); %! assert_equal (coefs, [1.0920, 0.5829, 18.4382, 1.4493], 1e-4); %!test %! [~, type, coefs] = johnsrnd ([-1.5, -0.5, 0.5, 1.5; -1, -0.25, 0.75, 3], 0); %! [~, type2, coefs2] = johnsrnd ([-1, -0.25, 0.75, 3]); %! assert_equal (type, type2); %! assert_equal (coefs, coefs2, 1e-12); ## Test input validation %!error johnsrnd () %!error ... %! johnsrnd ([1, 2, 3]) %!error ... %! johnsrnd ("abcd") %!error ... %! johnsrnd (ones (3, 4)) %!error ... %! johnsrnd ([1, 2, 2, 3]) %!error ... %! johnsrnd ([4, 3, 2, 1]) %!error ... %! johnsrnd ([1, NaN, 3, 4]) %!error ... %! johnsrnd ([1.5, 0.5, -0.5, -1.5; -1, -0.25, 0.75, 3]) %!error ... %! johnsrnd ([-1.5, -0.5, 0.5, 1; -1, -0.25, 0.75, 3]) statistics-release-1.9.2/inst/Random_Sampling/mhsample.m000066400000000000000000000266541524624707500233770ustar00rootroot00000000000000## Copyright (C) 1995-2022 The Octave Project Developers ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{smpl}, @var{accept}] =} mhsample (@var{start}, @var{nsamples}, @var{property}, @var{value}, @dots{}) ## ## Draws @var{nsamples} samples from a target stationary distribution @var{pdf} ## using Metropolis-Hastings algorithm. ## ## Inputs: ## ## @itemize ## @item ## @var{start} is a @var{nchain} by @var{dim} matrix of starting points for each ## Markov chain. Each row is the starting point of a different chain and each ## column corresponds to a different dimension. ## ## @item ## @var{nsamples} is the number of samples, the length of each Markov chain. ## @end itemize ## ## Some property-value pairs can or must be specified, they are: ## ## (Required) One of: ## ## @itemize ## @item ## "pdf" @var{pdf}: a function handle of the target stationary distribution to ## be sampled. The function should accept different locations in each row and ## each column corresponds to a different dimension. ## ## or ## ## @item ## "logpdf" @var{logpdf}: a function handle of the log of the target stationary ## distribution to be sampled. The function should accept different locations ## in each row and each column corresponds to a different dimension. ## @end itemize ## ## In case optional argument @var{symmetric} is set to false (the default), one ## of: ## ## @itemize ## @item ## "proppdf" @var{proppdf}: a function handle of the proposal distribution that ## is sampled from with @var{proprnd} to give the next point in the chain. The ## function should accept two inputs, the random variable and the current ## location each input should accept different locations in each row and each ## column corresponds to a different dimension. ## ## or ## ## @item ## "logproppdf" @var{logproppdf}: the log of "proppdf". ## @end itemize ## ## The following input property/pair values may be needed depending on the ## desired output: ## ## @itemize ## @item ## "proprnd" @var{proprnd}: (Required) a function handle which generates random ## numbers from @var{proppdf}. The function should accept different locations ## in each row and each column corresponds to a different dimension ## corresponding with the current location. ## ## @item ## "symmetric" @var{symmetric}: true or false based on whether @var{proppdf} is ## a symmetric distribution. If true, @var{proppdf} (or @var{logproppdf}) need ## not be specified. The default is false. ## ## @item ## "burnin" @var{burnin} the number of points to discard at the beginning, the ## default is 0. ## ## @item ## "thin" @var{thin}: omits @var{thin}-1 of every @var{thin} points in the ## generated Markov chain. The default is 1. ## ## @item ## "nchain" @var{nchain}: the number of Markov chains to generate. The default ## is 1. ## @end itemize ## ## Outputs: ## ## @itemize ## @item ## @var{smpl}: a @var{nsamples} x @var{dim} x @var{nchain} tensor of random ## values drawn from @var{pdf}, where the rows are different random values, the ## columns correspond to the dimensions of @var{pdf}, and the third dimension ## corresponds to different Markov chains. ## ## @item ## @var{accept} is a vector of the acceptance rate for each chain. ## @end itemize ## ## Example : Sampling from a normal distribution ## ## @example ## @group ## start = 1; ## nsamples = 1e3; ## pdf = @@(x) exp (-.5 * x .^ 2) / (pi ^ .5 * 2 ^ .5); ## proppdf = @@(x,y) 1 / 6; ## proprnd = @@(x) 6 * (rand (size (x)) - .5) + x; ## [smpl, accept] = mhsample (start, nsamples, "pdf", pdf, "proppdf", ... ## proppdf, "proprnd", proprnd, "thin", 4); ## histfit (smpl); ## @end group ## @end example ## ## @seealso{rand, slicesample} ## @end deftypefn function [smpl, accept] = mhsample (start, nsamples, varargin) if (nargin < 6) print_usage (); endif sizestart = size (start); pdf = []; proppdf = []; logpdf = []; logproppdf = []; proprnd = []; sym = false; K = 0; # burnin m = 1; # thin nchain = 1; for k = 1:2:length (varargin) if (ischar (varargin{k})) switch lower (varargin{k}) case 'pdf' if (isa (varargin{k+1}, 'function_handle')) pdf = varargin{k+1}; else error ("mhsample: pdf must be a function handle"); endif case 'proppdf' if (isa (varargin{k+1}, 'function_handle')) proppdf = varargin{k+1}; else error ("mhsample: proppdf must be a function handle"); endif case 'logpdf' if (isa (varargin{k+1}, 'function_handle')) pdf = varargin{k+1}; else error ("mhsample: logpdf must be a function handle"); endif case 'logproppdf' if (isa (varargin{k+1}, 'function_handle')) proppdf = varargin{k+1}; else error ("mhsample: logproppdf must be a function handle"); endif case 'proprnd' if (isa (varargin{k+1}, 'function_handle')) proprnd = varargin{k+1}; else error ("mhsample: proprnd must be a function handle"); endif case 'symmetric' if (isa (varargin{k+1}, 'logical')) sym = varargin{k+1}; else error ("mhsample: sym must be true or false"); endif case 'burnin' if (varargin{k+1}>=0) K = varargin{k+1}; else error ("mhsample: K must be greater than or equal to 0"); endif case 'thin' if (varargin{k+1} >= 1) m = varargin{k+1}; else error ("mhsample: m must be greater than or equal to 1"); endif case 'nchain' if (varargin{k+1} >= 1) nchain = varargin{k+1}; else error ("mhsample: nchain must be greater than or equal to 1"); endif otherwise warning ("mhsample: Ignoring unknown option %s", varargin{k}); endswitch else error ("mhsample: %s is not a valid property.", varargin{k}); endif endfor if (! isempty (pdf) && isempty (logpdf)) logpdf=@(x) rloge (pdf (x)); elseif (isempty (pdf) && isempty (logpdf)) error ("mhsample: pdf or logpdf must be input."); endif if (! isempty (proppdf) && isempty (logproppdf)) logproppdf = @(x, y) rloge (proppdf(x, y)); elseif (isempty (proppdf) && isempty (logproppdf) && ! sym) error ("mhsample: proppdf or logproppdf must be input unless 'symmetrical' is true."); endif if (! isa (proprnd, 'function_handle')) error ("mhsample: proprnd must be a function handle."); endif if (length (sizestart) == 2) sizestart = [sizestart 0]; endif smpl = zeros (nsamples, sizestart(2), nchain); if (all (sizestart([1 3]) == [1 nchain])) ## Could remove, not Matlab compatible but allows continuing chains smpl(1, :, :) = start; elseif (all (sizestart([1 3]) == [nchain 0])) smpl(1, :, :) = permute (start, [3, 2, 1]); elseif (all (sizestart([1 3]) == [1 0])) ## Could remove, not Matlab compatible but allows all chains to start ## at the same location smpl(1, :, :) = repmat (start,[1, 1, nchain]); else error ("mhsample: start must be a nchain by dim matrix."); endif cx = permute (smpl(1, :, :),[3, 2, 1]); accept = zeros (nchain, 1); i = 1; rnd = log (rand (nchain, nsamples*m+K)); for k = 1:nsamples*m+K canacc = rem (k-K, m) == 0; px = proprnd(cx); if (sym) A = logpdf(px) - logpdf(cx); else A = (logpdf(px) + logproppdf(cx, px)) - (logpdf(cx) + logproppdf(px, cx)); endif ac = rnd(:, k) < min (A, 0); cx(ac, :) = px(ac, :); accept(ac)++; if (canacc) smpl(i, :, :) = permute (cx, [3, 2, 1]); endif if (k > K && canacc) i++; endif endfor accept ./= (nsamples * m + K); endfunction function y = rloge (x) y = -inf (size (x)); xg0 = x > 0; y(xg0) = log (x(xg0)); endfunction %!demo %! ## Define function to sample %! rng (42); %! d = 2; %! mu = [-1; 2]; %! Sigma = rand (d); %! Sigma = (Sigma + Sigma'); %! Sigma += eye (d) * abs (eigs (Sigma, 1, 'sa')) * 1.1; %! pdf = @(x)(2*pi)^(-d/2)*det (Sigma)^-.5*exp (-.5*sum ((x.'-mu).*(Sigma\(x.'-mu)),1)); %! ## Inputs %! start = ones (1, 2); %! nsamples = 500; %! sym = true; %! K = 500; %! m = 10; %! proprnd = @(x) (rand (size (x)) - .5) * 3 + x; %! [smpl, accept] = mhsample (start, nsamples, 'pdf', pdf, 'proprnd', proprnd, ... %! 'symmetric', sym, 'burnin', K, 'thin', m); %! figure; %! hold on; %! plot (smpl(:, 1), smpl(:, 2), 'x'); %! [x, y] = meshgrid (linspace (-6, 4), linspace (-3, 7)); %! z = reshape (pdf ([x(:), y(:)]), size (x)); %! mesh (x, y, z, 'facecolor', 'None'); %! ## Using sample points to find the volume of half a sphere with radius of .5 %! f = @(x) ((.25-(x(:,1)+1).^2-(x(:,2)-2).^2).^.5.*(((x(:,1)+1).^2+(x(:,2)-2).^2)<.25)).'; %! int = mean (f(smpl) ./ pdf (smpl)); %! errest = std (f(smpl) ./ pdf (smpl)) / nsamples ^ .5; %! trueerr = abs (2 / 3 * pi * .25 ^ (3 / 2) - int); %! printf ("Monte Carlo integral estimate int f(x) dx = %f\n", int); %! printf ("Monte Carlo integral error estimate %f\n", errest); %! printf ("The actual error %f\n", trueerr); %! mesh (x, y, reshape (f([x(:), y(:)]), size (x)), 'facecolor', 'None'); %!demo %! ## Integrate truncated normal distribution to find normalization constant %! rng (42); %! pdf = @(x) exp (-.5*x.^2)/(pi^.5*2^.5); %! nsamples = 1e3; %! proprnd = @(x) (rand (size (x)) - .5) * 3 + x; %! [smpl, accept] = mhsample (1, nsamples, 'pdf', pdf, 'proprnd', proprnd, ... %! 'symmetric', true, 'thin', 4); %! f = @(x) exp (-.5 * x .^ 2) .* (x >= -2 & x <= 2); %! x = linspace (-3, 3, 1000); %! area (x, f(x)); %! xlabel ('x'); %! ylabel ('f(x)'); %! int = mean (f(smpl) ./ pdf (smpl)); %! errest = std (f(smpl) ./ pdf (smpl)) / nsamples^ .5; %! trueerr = abs (erf (2 ^ .5) * 2 ^ .5 * pi ^ .5 - int); %! printf ("Monte Carlo integral estimate int f(x) dx = %f\n", int); %! printf ("Monte Carlo integral error estimate %f\n", errest); %! printf ("The actual error %f\n", trueerr); ## Test output %!test %! nchain = 1e4; %! start = rand (nchain, 1); %! nsamples = 1e3; %! pdf = @(x) exp (-.5*(x-1).^2)/(2*pi)^.5; %! proppdf = @(x, y) 1/3; %! proprnd = @(x) 3 * (rand (size (x)) - .5) + x; %! [smpl, accept] = mhsample (start, nsamples, 'pdf', pdf, 'proppdf', proppdf, ... %! 'proprnd', proprnd, 'thin', 2, 'nchain', nchain, ... %! 'burnin', 0); %! assert_equal (mean (mean (smpl, 1), 3), 1, .01); %! assert_equal (mean (var (smpl, 1), 3), 1, .01) ## Test input validation %!error mhsample (); %!error mhsample (1); %!error mhsample (1, 1); %!error mhsample (1, 1, 'pdf', @(x)x); %!error mhsample (1, 1, 'pdf', @(x)x, 'proprnd', @(x)x+rand (size (x))); statistics-release-1.9.2/inst/Random_Sampling/pearsrnd.m000066400000000000000000000267041524624707500234030ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{r} =} pearsrnd (@var{mu}, @var{sigma}, @var{skew}, @var{kurt}) ## @deftypefnx {statistics} {@var{r} =} pearsrnd (@var{mu}, @var{sigma}, @var{skew}, @var{kurt}, @var{m}) ## @deftypefnx {statistics} {@var{r} =} pearsrnd (@var{mu}, @var{sigma}, @var{skew}, @var{kurt}, @var{m}, @var{n}, @dots{}) ## @deftypefnx {statistics} {@var{r} =} pearsrnd (@var{mu}, @var{sigma}, @var{skew}, @var{kurt}, [@var{m}, @var{n}, @dots{}]) ## @deftypefnx {statistics} {[@var{r}, @var{type}, @var{coefs}] =} pearsrnd (@dots{}) ## ## Random arrays from the Pearson system of distributions. ## ## @code{@var{r} = pearsrnd (@var{mu}, @var{sigma}, @var{skew}, @var{kurt})} ## returns a random value drawn from the distribution in the Pearson system with ## mean @var{mu}, standard deviation @var{sigma}, skewness @var{skew}, and ## kurtosis @var{kurt}. @var{kurt} is the (non-excess) kurtosis, and the ## moments must satisfy @code{@var{kurt} > @var{skew}^2 + 1}. ## ## @code{pearsrnd (@var{mu}, @var{sigma}, @var{skew}, @var{kurt}, @var{m}, ## @var{n}, @dots{})} or @code{pearsrnd (@dots{}, [@var{m}, @var{n}, @dots{}])} ## returns an @var{m}-by-@var{n}-by-@dots{} array of random values, following ## the size conventions of @code{randn}. ## ## @code{[@var{r}, @var{type}, @var{coefs}] = pearsrnd (@dots{})} also returns ## the type of the Pearson distribution (an integer @code{0} to @code{7}) in ## @var{type}, and the three coefficients @var{coefs} = ## @code{[@var{c0}, @var{c1}, @var{c2}]} of the denominator quadratic of the ## Pearson differential equation for the standardized distribution, so that ## @code{@var{f}'(@var{x}) / @var{f} (@var{x}) = -(@var{x} + @var{c1}) / ## (@var{c0} + @var{c1} @var{x} + @var{c2} @var{x}^2)}. ## ## The Pearson types are: @code{0} normal, @code{1} four-parameter beta, ## @code{2} symmetric four-parameter beta, @code{3} gamma, @code{4} (not a named ## distribution), @code{5} inverse gamma, @code{6} beta prime, and @code{7} ## Student's t. Type @code{4} is generated by numerical inversion of its ## cumulative distribution function. ## ## @seealso{johnsrnd, random, randn} ## @end deftypefn function [r, type, coefs] = pearsrnd (mu, sigma, skew, kurt, varargin) if (nargin < 4) print_usage (); endif if (! all (cellfun (@(v) isscalar (v) && isnumeric (v) && isreal (v), ... {mu, sigma, skew, kurt}))) error ("pearsrnd: MU, SIGMA, SKEW, and KURT must be real scalars."); endif if (sigma < 0) error ("pearsrnd: SIGMA must be non-negative."); endif if (kurt <= skew .^ 2 + 1) error ("pearsrnd: KURT must be greater than SKEW^2 + 1."); endif ## Output size, following randn's conventions sz = size_from_args (varargin); n = prod (sz); ## Standardized Pearson coefficients and the selected type [type, coefs] = pearson_type (skew, kurt); ## Generate standardized deviates (mean 0, variance 1), then scale and shift. ## Asymmetric shapes are generated for a non-negative skewness and reflected, ## so each generator only needs the positive-skew case. if (n == 0) z = zeros (0, 1); else sgn = sign (skew); if (sgn == 0) sgn = 1; endif z = sgn .* pearson_standard (type, abs (skew), kurt, n); endif r = reshape (mu + sigma .* z, sz); endfunction ## Standardized Pearson coefficients [c0, c1, c2] and the type (0-7) function [type, coefs] = pearson_type (skew, kurt) b1 = skew .^ 2; b2 = kurt; den = 10 .* b2 - 12 .* b1 - 18; c0 = (4 .* b2 - 3 .* b1) ./ den; c1 = skew .* (b2 + 3) ./ den; c2 = (2 .* b2 - 3 .* b1 - 6) ./ den; coefs = [c0, c1, c2]; if (b1 == 0) if (b2 == 3) type = 0; elseif (b2 < 3) type = 2; else type = 7; endif elseif (abs (c2) < 1e-12) type = 3; else kappa = c1 .^ 2 ./ (4 .* c0 .* c2); if (kappa < 0) type = 1; elseif (abs (kappa - 1) < 1e-9) type = 5; elseif (kappa < 1) type = 4; else type = 6; endif endif endfunction ## Draw n standardized (mean 0, variance 1) deviates for a non-negative skew function z = pearson_standard (type, skew, kurt, n) b1 = skew .^ 2; b2 = kurt; den = 10 .* b2 - 12 .* b1 - 18; c0 = (4 .* b2 - 3 .* b1) ./ den; c1 = skew .* (b2 + 3) ./ den; c2 = (2 .* b2 - 3 .* b1 - 6) ./ den; switch (type) case 0 z = randn (n, 1); case 7 nu = 4 + 6 ./ (b2 - 3); z = trnd (nu, n, 1) ./ sqrt (nu ./ (nu - 2)); case 2 a = (6 ./ (3 - b2) - 3) ./ 2; z = (betarnd (a, a, n, 1) - 0.5) .* 2 .* sqrt (2 .* a + 1); case 3 k = 4 ./ b1; z = (gamrnd (k, 1, n, 1) - k) ./ sqrt (k); case {1, 6} [r1, r2, p1, p2] = pearson_roots (c0, c1, c2); if (type == 1) ## Bounded: a beta distribution on [lo, hi] lo = min (r1, r2); hi = max (r1, r2); if (lo == r1) shapes = [p1, p2]; else shapes = [p2, p1]; endif z = lo + (hi - lo) .* betarnd (shapes(1) + 1, shapes(2) + 1, n, 1); else ## Beta prime, supported on (b, Inf) with b the root nearest the mass b = max (r1, r2); o = min (r1, r2); if (b == r1) pb = p1; po = p2; else pb = p2; po = p1; endif y = betarnd (pb + 1, -pb - po - 1, n, 1); y = y ./ (1 - y); z = b + (b - o) .* y; endif case 5 ## Inverse gamma: x = r0 + 1 / w, with w gamma-distributed r0 = -c1 ./ (2 .* c2); k = 1 ./ c2 - 1; rate = -(r0 + c1) ./ c2; z = r0 + 1 ./ gamrnd (k, 1 ./ rate, n, 1); case 4 ## Pearson IV: numerical inversion of the cdf z = pearson4_invcdf (c0, c1, c2, kurt, n); endswitch endfunction ## Real roots and partial-fraction exponents of the Pearson denominator, using ## the numerator (x + c1) (i.e. the mode-centring constant a = -c1). function [r1, r2, p1, p2] = pearson_roots (c0, c1, c2) sq = sqrt (c1 .^ 2 - 4 .* c0 .* c2); r1 = (-c1 - sq) ./ (2 .* c2); r2 = (-c1 + sq) ./ (2 .* c2); a = -c1; p1 = -(r1 - a) ./ (c2 .* (r1 - r2)); p2 = -(r2 - a) ./ (c2 .* (r2 - r1)); endfunction ## Pearson type IV standardized deviates by numerical inversion of the cdf built ## from the closed-form density (complex denominator roots). function z = pearson4_invcdf (c0, c1, c2, kurt, n) L = max (40, 6 .* kurt); x = linspace (-L, L, 400001)'; D = c2 .* x .^ 2 + c1 .* x + c0; s = sqrt (4 .* c0 .* c2 - c1 .^ 2); logf = -(1 ./ (2 .* c2)) .* log (D) ... - c1 .* (1 - 1 ./ (2 .* c2)) .* (2 ./ s) ... .* atan ((2 .* c2 .* x + c1) ./ s); f = exp (logf - max (logf)); F = cumtrapz (x, f); F = F ./ F(end); [F, ia] = unique (F); z = interp1 (F, x(ia), rand (n, 1), "linear"); endfunction ## Output size vector from the trailing size arguments, as randn accepts them function sz = size_from_args (args) if (numel (args) == 0) sz = [1, 1]; elseif (numel (args) == 1) a = args{1}; if (isscalar (a)) sz = [a, a]; else sz = a(:)'; endif else sz = [args{:}]; endif if (! all (sz == fix (sz))) error ("pearsrnd: dimensions must be integers."); endif ## Negative dimensions are treated as zero, as in core Octave and MATLAB sz = max (sz, 0); endfunction %!demo %! ## Identify the Pearson type matching a set of moments %! rng (42); %! randg ('state', 42); %! [r, type, coefs] = pearsrnd (0, 1, 0.75, 4) %!demo %! ## Draw a sample with a target mean, sd, skewness, and kurtosis %! rng (42); %! randg ('state', 42); %! r = pearsrnd (10, 2, 1, 5, 1, 1000); %! [mean(r), std(r)] ## Type and coefficients against MATLAB %!test %! [~, type, coefs] = pearsrnd (0, 1, 0, 3); %! assert_equal (type, 0); %! assert_equal (coefs, [1, 0, 0], 1e-12); %!test %! [~, type, coefs] = pearsrnd (0, 1, 0, 4.5); %! assert_equal (type, 7); %! assert_equal (coefs, [0.666666666666667, 0, 0.111111111111111], 1e-12); %!test %! [~, type, coefs] = pearsrnd (0, 1, 0, 2.5); %! assert_equal (type, 2); %! assert_equal (coefs, [1.42857142857143, 0, -0.142857142857143], 1e-12); %!test %! [~, type, coefs] = pearsrnd (0, 1, 0.5, 3.5); %! assert_equal (type, 4); %! assert_equal (coefs, ... %! [0.946428571428571, 0.232142857142857, 0.0178571428571429], 1e-12); %!test %! [~, type, coefs] = pearsrnd (0, 1, 0.75, 4); %! assert_equal (type, 6); %! assert_equal (coefs, ... %! [0.938524590163934, 0.344262295081967, 0.0204918032786885], 1e-12); %!test %! [~, type, coefs] = pearsrnd (2, 3, 1, 5); %! assert_equal (type, 4); %! assert_equal (coefs, [0.85, 0.4, 0.05], 1e-12); %!test %! [~, type, coefs] = pearsrnd (0, 1, 1, 4.5); %! assert_equal (type, 3); %! assert_equal (coefs, [1, 0.5, 0], 1e-12); %!test %! [~, type, coefs] = pearsrnd (0, 1, 2, 9); %! assert_equal (type, 3); %! assert_equal (coefs, [1, 1, 0], 1e-12); ## Generated samples reproduce the target moments (each type is exercised) %!test %! cases = [0 1 0 3; 0 1 0 4.5; 0 1 0 2.5; 0 1 0.5 3.5; 0 1 0.75 4; ... %! 2 3 1 5; 0 1 1 4.5; 0 1 2 9]; %! ## Seed per case, not once for the run: a single seed at the top leaves each %! ## case starting wherever the ones before it left the stream, so the sample %! ## moments below are only as reproducible as the number of draws every %! ## earlier case happens to take. randg needs seeding too: the Pearson %! ## types are drawn through betarnd, gamrnd and trnd, and seeding rand and %! ## randn alone leaves that stream running on from wherever it was. %! for i = 1:rows (cases) %! rand ("state", 42); randn ("state", 42); randg ("state", 42); %! m = cases(i,1); s = cases(i,2); sk = cases(i,3); ku = cases(i,4); %! r = pearsrnd (m, s, sk, ku, 200000, 1); %! mr = mean (r); sr = std (r); %! g1 = mean (((r - mr) ./ sr) .^ 3); g2 = mean (((r - mr) ./ sr) .^ 4); %! assert_equal (mr, m, 0.05 .* s + 0.02); %! assert_equal (sr, s, 0.05 .* s + 0.02); %! assert_equal (g1, sk, 0.1); %! assert_equal (g2, ku, 0.4); %! endfor ## Negative skewness reflects the positive-skew shape %!test %! rand ("state", 7); randn ("state", 7); %! r = pearsrnd (0, 1, -1, 5, 100000, 1); %! assert_equal (mean (((r - mean (r)) ./ std (r)) .^ 3), -1, 0.1); ## Size handling follows randn %!test %! assert_equal (size (pearsrnd (0, 1, 0, 3, 3, 4)), [3, 4]); %! assert_equal (size (pearsrnd (0, 1, 0, 3, [2, 5])), [2, 5]); %! assert_equal (isscalar (pearsrnd (0, 1, 0, 3)), true); %! assert_equal (size (pearsrnd (0, 1, 0, 3, -1)), [0, 0]); %! assert_equal (size (pearsrnd (0, 1, 0, 3, 2, -1, 5)), [2, 0, 5]); ## Test input validation %!error pearsrnd (0, 1, 0) %!error ... %! pearsrnd (0, 1, 0, [3, 4]) %!error pearsrnd (0, -1, 0, 3) %!error ... %! pearsrnd (0, 1, 1, 1.5) statistics-release-1.9.2/inst/Random_Sampling/qrandn.m000066400000000000000000000062701524624707500230440ustar00rootroot00000000000000## Copyright (C) 2014 - Juan Pablo Carbajal ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program. If not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{z} =} qrandn (@var{q}, @var{r}, @var{c}) ## @deftypefnx {statistics} {@var{z} =} qrandn (@var{q}, [@var{r}, @var{c}]) ## ## Returns random deviates drawn from a q-Gaussian distribution. ## ## Parameter @var{q} characterizes the q-Gaussian distribution. ## The result has the size indicated by @var{s}. ## ## Reference: ## W. Thistleton, J. A. Marsh, K. Nelson, C. Tsallis (2006) ## "Generalized Box-Muller method for generating q-Gaussian random deviates" ## arXiv:cond-mat/0605570 http://arxiv.org/abs/cond-mat/0605570 ## ## @seealso{rand, randn} ## @end deftypefn function z = qrandn (q, R, C=[]) if (nargin < 2) print_usage; endif if (! isscalar (q)) error ("qrandn: the parameter q must be a scalar."); endif ## Check that q < 3 if (q >= 3) error ("qrandn: the parameter q must be lower than 3."); endif if (numel (R) > 1) S = R; elseif (numel (R) == 1 && isempty (C)) S = [R, 1]; elseif (numel (R) == 1 && ! isempty (C)) S = [R, C]; endif ## Calculate the q to be used on the q-log qGen = (1 + q) / (3 - q); ## Initialize the output vector z = sqrt (-2 * log_q (rand (S), qGen)) .* sin (2 * pi * rand (S)); endfunction ## Returns the q-log of x, using q function a = log_q (x, q) dq = 1 - q; ## Check to see if q = 1 (to double precision) if (abs (dq) < 10 * eps) ## If q is 1, use the usual natural logarithm a = log (x); else ## If q differs from 1, use the definition of the q-log a = (x .^ dq - 1) ./ dq; endif endfunction %!demo %! z = qrandn (-5, 5e6); %! [c x] = hist (z,linspace (-1.5,1.5,200),1); %! figure (1) %! plot (x,c,'r.'); axis tight; axis ([-1.5,1.5]); %! %! z = qrandn (-0.14286, 5e6); %! [c x] = hist (z,linspace (-2,2,200),1); %! figure (2) %! plot (x,c,'r.'); axis tight; axis ([-2,2]); %! %! z = qrandn (2.75, 5e6); %! [c x] = hist (z,linspace (-1e3,1e3,1e3),1); %! figure (3) %! semilogy (x,c,'r.'); axis tight; axis ([-100,100]); %! %! # --------- %! # Figures from the reference paper. ## Tests for input validation %!error qrandn ([1 2], 1) %!error qrandn (4, 1) %!error qrandn (3, 1) %!error qrandn (2.5, 1, 2, 3) %!error qrandn (2.5) ## Tests for output validation %!test %! q = 1.5; %! s = [2, 3]; %! z = qrandn (q, s); %! assert_equal (isnumeric (z) && isequal (size (z), s), true); statistics-release-1.9.2/inst/Random_Sampling/slicesample.m000066400000000000000000000266661524624707500240750ustar00rootroot00000000000000## Copyright (C) 1995-2022 The Octave Project Developers ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{smpl}, @var{neval}] =} slicesample (@var{start}, @var{nsamples}, @var{property}, @var{value}, @dots{}) ## ## Draws @var{nsamples} samples from a target stationary distribution @var{pdf} ## using slice sampling of Radford M. Neal. ## ## Input: ## @itemize ## @item ## @var{start} is a 1 by @var{dim} vector of the starting point of the ## Markov chain. Each column corresponds to a different dimension. ## ## @item ## @var{nsamples} is the number of samples, the length of the Markov chain. ## @end itemize ## ## Next, several property-value pairs can or must be specified, they are: ## ## (Required properties) One of: ## ## @itemize ## @item ## @var{"pdf"}: the value is a function handle of the target stationary ## distribution to be sampled. The function should accept different locations ## in each row and each column corresponds to a different dimension. ## ## or ## ## @item ## @var{logpdf}: the value is a function handle of the log of the target ## stationary distribution to be sampled. The function should accept different ## locations in each row and each column corresponds to a different dimension. ## @end itemize ## ## The following input property/pair values may be needed depending on the ## desired output: ## ## @itemize ## @item ## "burnin" @var{burnin} the number of points to discard at the beginning, the ## default is 0. ## ## @item ## "thin" @var{thin} omits @var{m}-1 of every @var{m} points in the generated ## Markov chain. The default is 1. ## ## @item ## "width" @var{width} the maximum Manhattan distance between two samples. ## The default is 10. ## @end itemize ## ## Outputs: ## @itemize ## ## @item ## @var{smpl} is a @var{nsamples} by @var{dim} matrix of random ## values drawn from @var{pdf} where the rows are different random values, the ## columns correspond to the dimensions of @var{pdf}. ## ## @item ## @var{neval} is the number of function evaluations per sample. ## @end itemize ## Example : Sampling from a normal distribution ## ## @example ## @group ## start = 1; ## nsamples = 1e3; ## pdf = @@(x) exp (-.5 * x .^ 2) / (pi ^ .5 * 2 ^ .5); ## [smpl, accept] = slicesample (start, nsamples, "pdf", pdf, "thin", 4); ## histfit (smpl); ## @end group ## @end example ## ## @seealso{rand, mhsample, randsample} ## @end deftypefn function [smpl, neval] = slicesample (start, nsamples, varargin) if (nargin < 4) error ("slicesample: function called with too few input arguments."); endif sizestart = size (start); pdf = []; logpdf = []; width = 10; burnin = 0; thin = 1; for k = 1:2:length (varargin) if (ischar (varargin{k})) switch lower (varargin{k}) case 'pdf' if (isa (varargin{k+1}, 'function_handle')) pdf = varargin{k+1}; else error ("slicesample: pdf must be a function handle."); endif case 'logpdf' if (isa (varargin{k+1}, 'function_handle')) ## This assigned to PDF, so the supplied log density was then ## wrapped as log (pdf (x)) and taken the log of a second time. ## The chain sampled from something else entirely, quietly. logpdf = varargin{k+1}; else error ("slicesample: logpdf must be a function handle."); endif case 'width' if (numel (varargin{k+1}) == 1 || numel (varargin{k+1}) == sizestart(2)) width = varargin{k+1}(:).'; else error ("slicesample: width must be a scalar or 1 by dim vector."); endif if (! all (width > 0)) error (strcat ("slicesample: width must contain positive", ... " values that can be added to START.")); endif case 'burnin' if (varargin{k+1}>=0) burnin = varargin{k+1}; else error ("slicesample: burnin must be greater than or equal to 0."); endif case 'thin' if (varargin{k+1}>=1) thin = varargin{k+1}; else error ("slicesample: thin must be greater than or equal to 1."); endif otherwise error ("slicesample: invalid parameter name: %s.", varargin{k}); endswitch else error ("slicesample: %s is not a valid property.", varargin{k}); endif endfor if (! isempty (pdf) && isempty (logpdf)) logpdf = @(x) rloge (pdf (x)); elseif (isempty (pdf) && isempty (logpdf)) error ("slicesample: pdf or logpdf must be input."); endif dim = sizestart(2); ## Asking for no samples yields none; zeros () read a negative count as 1. if (nsamples < 1) nsamples = 0; endif smpl = zeros (nsamples, dim); if (! all (sizestart == [1 dim])) error ("slicesample: start must be a 1 by dim vector."); endif if (nsamples == 0) ## Nothing to draw. Assigning the start point below would grow SMPL back ## to one row and hand back a sample that was never taken. neval = 0; return; endif smpl(1, :) = start; maxit = 100; neval = 0; fgreaterthan = @(x, fxc) logpdf(x) >= fxc; ti = burnin + nsamples * thin; rndexp = rande (ti, 1); crand = rand (ti, dim); prand = rand (ti, dim); xc = smpl(1, :); for i = 1:ti neval++; sliceheight = logpdf(xc) - rndexp(i); c = width .* crand(i, :); lb = xc - c; ub = xc + width - c; #Only for single variable as bounds can not be found with point when dim > 1 if (dim == 1) for k=1:maxit neval++; if (! fgreaterthan(lb, sliceheight)) break endif lb -= width; endfor if (k == maxit) warning ("slicesample: Step out exceeded maximum iterations"); endif for k = 1:maxit neval++; if (! fgreaterthan(ub, sliceheight)) break endif ub += width; endfor if (k == maxit) warning ("slicesample: Step out exceeded maximum iterations"); endif endif xp = (ub - lb) .* prand(i, :) + lb; for k=1:maxit neval++; isgt = fgreaterthan(xp,sliceheight); if (all (isgt)) break endif lc = ! isgt & xp < xc; uc = ! isgt & xp > xc; lb(lc) = xp(lc); ub(uc) = xp(uc); xp = (ub - lb) .* rand (1, dim) + lb; endfor if (k == maxit) warning ("slicesample: Step in exceeded maximum iterations"); endif xc = xp; if (i > burnin) indx = (i - burnin) / thin; if rem (indx, 1) == 0 smpl(indx, :) = xc; endif endif endfor neval = neval / (nsamples * thin + burnin); endfunction function y = rloge (x) y = -inf (size (x)); xg0 = x > 0; y(xg0) = log (x(xg0)); endfunction %!demo %! ## Define function to sample %! rng (42); %! rande ('state', 42); %! d = 2; %! mu = [-1; 2]; %! Sigma = rand (d); %! Sigma = (Sigma + Sigma'); %! Sigma += eye (d)*abs (eigs (Sigma, 1, 'sa')) * 1.1; %! pdf = @(x)(2*pi)^(-d/2)*det (Sigma)^-.5*exp (-.5*sum ((x.'-mu).*(Sigma\(x.'-mu)),1)); %! %! ## Inputs %! start = ones (1,2); %! nsamples = 500; %! K = 500; %! m = 10; %! [smpl, accept] = slicesample (start, nsamples, 'pdf', pdf, 'burnin', K, 'thin', m, 'width', [20, 30]); %! figure; %! hold on; %! plot (smpl(:,1), smpl(:,2), 'x'); %! [x, y] = meshgrid (linspace (-6,4), linspace (-3,7)); %! z = reshape (pdf ([x(:), y(:)]), size (x)); %! mesh (x, y, z, 'facecolor', 'None'); %! %! ## Using sample points to find the volume of half a sphere with radius of .5 %! f = @(x) ((.25-(x(:,1)+1).^2-(x(:,2)-2).^2).^.5.*(((x(:,1)+1).^2+(x(:,2)-2).^2)<.25)).'; %! int = mean (f(smpl) ./ pdf (smpl)); %! errest = std (f(smpl) ./ pdf (smpl)) / nsamples^.5; %! trueerr = abs (2/3*pi*.25^(3/2)-int); %! fprintf ("Monte Carlo integral estimate int f(x) dx = %f\n", int); %! fprintf ("Monte Carlo integral error estimate %f\n", errest); %! fprintf ("The actual error %f\n", trueerr); %! mesh (x,y,reshape (f([x(:), y(:)]), size (x)), 'facecolor', 'None'); %!demo %! ## Integrate truncated normal distribution to find normalization constant %! rng (42); %! rande ('state', 42); %! pdf = @(x) exp (-.5*x.^2)/(pi^.5*2^.5); %! nsamples = 1e3; %! [smpl, accept] = slicesample (1, nsamples, 'pdf', pdf, 'thin', 4); %! f = @(x) exp (-.5 * x .^ 2) .* (x >= -2 & x <= 2); %! x = linspace (-3, 3, 1000); %! area (x, f(x)); %! xlabel ('x'); %! ylabel ('f(x)'); %! int = mean (f(smpl) ./ pdf (smpl)); %! errest = std (f(smpl) ./ pdf (smpl)) / nsamples ^ 0.5; %! trueerr = abs (erf (2 ^ 0.5) * 2 ^ 0.5 * pi ^ 0.5 - int); %! fprintf ("Monte Carlo integral estimate int f(x) dx = %f\n", int); %! fprintf ("Monte Carlo integral error estimate %f\n", errest); %! fprintf ("The actual error %f\n", trueerr); ## Test output %!test %! start = 0.5; %! nsamples = 1e3; %! pdf = @(x) exp (-.5*(x-1).^2)/(2*pi)^.5; %! [smpl, accept] = slicesample (start, nsamples, 'pdf', pdf, 'thin', 2, 'burnin', 0, 'width', 5); %! assert_equal (mean (smpl, 1), 1, .15); %! assert_equal (var (smpl, 1), 1, .25); ## 'logpdf' assigned its handle to PDF, which was then wrapped as ## log (pdf (x)). A supplied log density was therefore logged a second time ## and the chain sampled from something else entirely, without a word. %!test %! rand ('twister', 42); %! s1 = slicesample (0, 2000, 'logpdf', @(z) -z .^ 2 / 2, 'width', 5); %! assert_equal (mean (s1), 0, 0.15); %! assert_equal (std (s1), 1, 0.15); %!test %! ## the same target given as a density must agree to within sampling error %! rand ('twister', 42); %! sp = slicesample (0, 2000, 'pdf', @(z) exp (-z .^ 2 / 2), 'width', 5); %! rand ('twister', 42); %! sl = slicesample (0, 2000, 'logpdf', @(z) -z .^ 2 / 2, 'width', 5); %! assert_equal (mean (sp), mean (sl), 0.15); %! assert_equal (std (sp), std (sl), 0.15); %!test %! ## MATLAB accepts both, so this must produce a usable chain rather than %! ## the log of a log %! rand ('twister', 7); %! sb = slicesample (1, 500, 'pdf', @(z) exp (-z .^ 2 / 2), ... %! 'logpdf', @(z) -z .^ 2 / 2, 'width', 5); %! assert_equal (size (sb), [500, 1]); %! assert_equal (mean (sb), 0, 0.25); ## A sample count of zero or less draws nothing; zeros () read a negative ## count as 1 and the start point was then handed back as a sample. %!test %! assert_equal (size (slicesample (1, 0, 'pdf', @(z) exp (-z .^ 2 / 2))), [0, 1]); %! assert_equal (size (slicesample (1, -5, 'pdf', @(z) exp (-z .^ 2 / 2))), [0, 1]); ## Test input validation %!error ... %! slicesample (1, 50, 'pdf', @(z) exp (-z .^ 2 / 2), 'nosuch', 1) %!error ... %! slicesample (1, 50, 'pdf', @(z) exp (-z .^ 2 / 2), 'width', -1) %!error ... %! slicesample (1, 50, 'pdf', @(z) exp (-z .^ 2 / 2), 'width', 0) %!error slicesample (); %!error slicesample (1); %!error slicesample (1, 1); statistics-release-1.9.2/inst/Regression/000077500000000000000000000000001524624707500204445ustar00rootroot00000000000000statistics-release-1.9.2/inst/Regression/CompactLinearModel.m000066400000000000000000004055501524624707500243350ustar00rootroot00000000000000## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef CompactLinearModel ## -*- texinfo -*- ## @deftp {statistics} CompactLinearModel ## ## Compact linear regression model ## ## The @code{CompactLinearModel} class stores a fitted linear regression ## model without the training data. A @code{CompactLinearModel} object is ## returned by the @code{compact} method of a @code{LinearModel} object, and ## retains everything needed to inspect, predict from, and run inference on ## the fit, while discarding the observations and per-observation ## diagnostics that a @code{LinearModel} object carries. This makes a ## @code{CompactLinearModel} object smaller to store than the ## @code{LinearModel} it was compacted from. ## ## The properties of a @code{CompactLinearModel} object fall into four ## groups: ## ## @multitable @columnfractions 0.22 0.76 ## @headitem Group @tab Properties ## ## @item Coefficient estimates @tab @code{Coefficients} (a table of ## estimates, standard errors, t-statistics, and p-values for each term), ## @code{CoefficientCovariance}, @code{CoefficientNames}, and the ## coefficient counts @code{NumCoefficients} and ## @code{NumEstimatedCoefficients}. ## ## @item Summary statistics of the fit @tab @code{DFE}, @code{MSE}, ## @code{RMSE}, @code{Rsquared} (ordinary and adjusted), @code{SSE}, ## @code{SSR}, @code{SST}, @code{LogLikelihood}, and @code{ModelCriterion} ## (AIC, BIC, etc.). ## ## @item Fitting method information @tab @code{Robust}, which records ## the weighting function and tuning constant used when the model is fit by ## robust regression, and is empty for an ordinary least squares fit. ## ## @item Input data properties @tab @code{Formula}, @code{NumObservations}, ## @code{NumPredictors}, @code{NumVariables}, @code{PredictorNames}, ## @code{ResponseName}, @code{VariableInfo}, and @code{VariableNames}. ## @end multitable ## ## Because the training data is discarded, a @code{CompactLinearModel} ## object has no @code{Fitted}, @code{Residuals}, @code{Diagnostics}, or ## @code{ObservationInfo} properties, and none of its methods refit the ## model. Once created, the following methods are available on a ## @code{CompactLinearModel} object: ## ## @multitable @columnfractions 0.2 0.78 ## @headitem Method @tab Description ## ## @item @code{predict} @tab Predict responses at new predictor values ## given in a matrix or table. Can also return pointwise or simultaneous ## confidence intervals alongside the point predictions. ## ## @item @code{feval} @tab Predict responses given predictors as ## separate scalar or vector arguments (one per predictor variable) instead ## of a single matrix, so a @code{CompactLinearModel} object can be ## evaluated the same way as a plain function handle. Returns point ## predictions only. ## ## @item @code{random} @tab Simulate new response values at new ## predictor locations by adding independent Gaussian noise, drawn from the ## estimated error variance @code{MSE}, to the fitted response. ## ## @item @code{coefCI} @tab Return Wald confidence intervals for every ## fitted coefficient at a chosen significance level (default @math{0.05}). ## ## @item @code{coefTest} @tab Test a linear hypothesis on the fitted ## coefficients. With no arguments, tests the overall model F-test that ## all non-intercept coefficients are zero; a custom hypothesis can be ## given as a contrast matrix and, if needed, right-hand-side values. ## Returns the p-value, and optionally the F-statistic and its numerator ## degrees of freedom. ## ## @item @code{plotEffects} @tab Plot the estimated main effect and ## 95% confidence interval of each predictor, evaluated between its ## observed minimum and maximum with all other predictors held at their ## observed means. ## ## @item @code{plotInteraction} @tab Plot the main and conditional effects ## of two predictors, or the adjusted response as a function of one ## predictor for several fixed values of the other, to visualize whether ## the two predictors interact. ## ## @item @code{anova} @tab Analysis of variance for the fitted model. ## Type 3 raises an error on a model missing a lower-order relative of one ## of its terms, since a @code{CompactLinearModel} object has no data to ## refit with. ## @end multitable ## ## Create a @code{CompactLinearModel} object by using the @code{compact} ## method of a fitted @code{LinearModel} object. ## ## @seealso{LinearModel, compact} ## @end deftp properties(GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} CoefficientCovariance ## ## Covariance matrix of coefficient estimates ## ## A @math{p}-by-@math{p} numeric matrix of covariance values for the ## coefficient estimates, where @math{p} is the number of coefficients in ## the fitted model as given by @code{NumCoefficients}. This property is ## read-only. ## ## @end deftp CoefficientCovariance = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} CoefficientNames ## ## Coefficient names ## ## A cell array of character vectors, each containing the name of the ## corresponding model term (e.g., @qcode{'(Intercept)'}, @qcode{'x1'}, ## @qcode{'x1:x2'}). This property is read-only. ## ## @end deftp CoefficientNames = {}; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} Coefficients ## ## Coefficient values ## ## A table with one row for each coefficient and four columns: ## @itemize ## @item @code{Estimate} - estimated coefficient value ## @item @code{SE} - standard error of the estimate ## @item @code{tStat} - t-statistic for a two-sided test ## @item @code{pValue} - p-value for the t-statistic ## @end itemize ## Coefficients that are dropped due to rank deficiency have ## @code{Estimate = 0}, @code{SE = 0}, @code{tStat = NaN}, ## @code{pValue = NaN}. This property is read-only. ## ## @end deftp Coefficients = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} NumCoefficients ## ## Number of model coefficients ## ## A positive integer giving the total number of coefficients in the fitted ## model, including any coefficients set to zero because the model terms are ## rank deficient. This property is read-only. ## ## @end deftp NumCoefficients = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} NumEstimatedCoefficients ## ## Number of estimated coefficients ## ## A positive integer giving the number of coefficients actually estimated, ## i.e., not set to zero due to rank deficiency. ## @code{NumEstimatedCoefficients} equals the degrees of freedom for ## regression. This property is read-only. ## ## @end deftp NumEstimatedCoefficients = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} DFE ## ## Degrees of freedom for error ## ## A positive integer equal to the number of observations minus the number ## of estimated coefficients: @code{DFE = NumObservations - ## NumEstimatedCoefficients}. This property is read-only. ## ## @end deftp DFE = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} LogLikelihood ## ## Log-likelihood of the fitted model ## ## A scalar numeric value equal to the log-likelihood of the response ## values, assuming each response is normally distributed with mean equal ## to the fitted value and variance equal to @math{SSE/n} (the MLE ## variance estimate). This property is read-only. ## ## @end deftp LogLikelihood = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} ModelCriterion ## ## Model comparison criteria ## ## A structure with four fields: ## @itemize ## @item @code{AIC} - Akaike information criterion: ## @math{-2 * logL + 2 * m} ## @item @code{AICc} - AIC corrected for sample size: ## @math{AIC + (2*m*(m+1))/(n-m-1)} ## @item @code{BIC} - Bayesian information criterion: ## @math{-2 * logL + m * log(n)} ## @item @code{CAIC} - Consistent AIC: ## @math{-2 * logL + m * (log(n) + 1)} ## @end itemize ## Here @math{logL} is @code{LogLikelihood}, @math{m} is ## @code{NumEstimatedCoefficients}, and @math{n} is ## @code{NumObservations}. This property is read-only. ## ## @end deftp ModelCriterion = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} MSE ## ## Mean squared error ## ## A scalar numeric value equal to @math{SSE / DFE}, where @code{SSE} is ## the sum of squared errors and @code{DFE} is the degrees of freedom for ## error. This property is read-only. ## ## @end deftp MSE = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} RMSE ## ## Root mean squared error ## ## A scalar numeric value equal to @math{sqrt(MSE)}. This property is ## read-only. ## ## @end deftp RMSE = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} Rsquared ## ## R-squared goodness-of-fit statistics ## ## A structure with two fields: ## @itemize ## @item @code{Ordinary} - coefficient of determination: ## @math{R^2 = SSR / SST} ## @item @code{Adjusted} - adjusted @math{R^2} that accounts for the ## number of coefficients in the model ## @end itemize ## This property is read-only. ## ## @end deftp Rsquared = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} SSE ## ## Sum of squared errors ## ## A scalar numeric value equal to the sum of squared residuals. For a ## model with an intercept, @math{SST = SSE + SSR}. For weighted fits, ## this is the weighted sum of squares. This property is read-only. ## ## @end deftp SSE = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} SSR ## ## Regression sum of squares ## ## A scalar numeric value equal to the sum of squared deviations of the ## fitted values from the mean of the response. For a model with an ## intercept, @math{SST = SSE + SSR}. For weighted fits, this is the ## weighted sum of squares. This property is read-only. ## ## @end deftp SSR = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} SST ## ## Total sum of squares ## ## A scalar numeric value equal to the sum of squared deviations of the ## response from its mean. For a model with an intercept, ## @math{SST = SSE + SSR}. For a robust fit, @math{SST = SSE + SSR} ## rather than the deviation from the mean. For weighted fits, this is ## the weighted sum of squares. This property is read-only. ## ## @end deftp SST = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} Robust ## ## Robust fit information ## ## A structure with three fields: ## @itemize ## @item @code{WgtFun} - robust weighting function name, e.g. ## @qcode{'bisquare'} ## @item @code{Tune} - tuning constant; empty if @code{WgtFun} is ## @qcode{'ols'} or a function handle with the default tuning constant ## @item @code{Weights} - vector of final iteration weights; always ## empty for a @code{CompactLinearModel} object ## @end itemize ## This structure is empty unless the model was fit using robust ## regression. This property is read-only. ## ## @end deftp Robust = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} Formula ## ## Model formula information ## ## A structure representing the model formula with fields including ## @code{ResponseName}, @code{LinearPredictor}, @code{PredictorNames}, ## @code{TermNames}, @code{HasIntercept}, @code{Terms} (the terms ## matrix), and @code{InModel}. This property is read-only. ## ## @end deftp Formula = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} NumObservations ## ## Number of observations used in the fit ## ## A positive integer giving the number of observations actually used in ## fitting the original model. Rows with missing values and rows ## excluded via the @code{'Exclude'} name-value argument are not counted. ## This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} NumPredictors ## ## Number of predictor variables ## ## A positive integer giving the number of predictor variables used to ## fit the model. This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} NumVariables ## ## Number of variables in the input data ## ## A positive integer giving the total number of variables in the input ## data used to fit the original model, counting predictors, the ## response, and any unused columns. This property is read-only. ## ## @end deftp NumVariables = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} PredictorNames ## ## Names of predictor variables ## ## A cell array of character vectors containing the names of the ## predictor variables used to fit the model. This property is ## read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} ResponseName ## ## Response variable name ## ## A character vector containing the name of the response variable. ## This property is read-only. ## ## @end deftp ResponseName = ''; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} VariableInfo ## ## Information about input variables ## ## A table with one row per variable including any unused variables, and ## four columns: ## @itemize ## @item @code{Class} - variable class as a character vector, e.g. ## @qcode{'double'} or @qcode{'categorical'} ## @item @code{Range} - for continuous variables, a two-element vector ## @code{[min, max]}; for categorical variables, a vector of the ## distinct values ## @item @code{InModel} - logical; true if the variable is in the ## fitted model ## @item @code{IsCategorical} - logical; true if the variable is ## categorical ## @end itemize ## This property is read-only. ## ## @end deftp VariableInfo = []; ## -*- texinfo -*- ## @deftp {CompactLinearModel} {property} VariableNames ## ## Names of all variables in the input data ## ## A cell array of character vectors containing the names of all ## variables used to fit the original model, including predictors, the ## response, and unused variables. This property is read-only. ## ## @end deftp VariableNames = {}; endproperties properties(Access = private, Hidden) ## Terms matrix from modelspec or parse_modelspec TermsMatrix = []; ## Categorical level info for re-encoding in predict CatLevelInfo = []; ## Predictor names after categorical dummy expansion EncPredictorNames = {}; ## Cached per-predictor design contrasts used by plotEffects EffectContrasts = []; ## Cached per-predictor-pair design contrasts used by plotInteraction InteractionContrasts = []; ## Whether the model includes an intercept term HasIntercept = true; ## F-statistic of the fitted model vs. the intercept-only model ModelFitVsNullModel = []; endproperties methods(Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ("%s =\n", in_name); endif disp (this); endfunction ## Custom display function disp (this) if (isempty (this.Robust)) fprintf ("\n Compact linear regression model:\n"); else fprintf ("\n Compact linear regression model (robust fit):\n"); endif if (! isempty (this.Formula) && isa (this.Formula, 'LinearFormula')) fprintf (" %s\n", char (this.Formula)); endif if (! isempty (this.Coefficients)) fprintf ("\n Estimated Coefficients:\n\n"); disp (this.Coefficients); endif fprintf ("\n"); if (! isempty (this.NumObservations) && ! isempty (this.DFE)) fprintf ("Number of observations: %d, Error degrees of freedom: %d\n", ... this.NumObservations, this.DFE); endif if (! isempty (this.RMSE)) fprintf ("Root Mean Squared Error: %g\n", this.RMSE); endif if (! isempty (this.Rsquared) && isstruct (this.Rsquared)) fprintf ("R-squared: %g, Adjusted R-Squared: %g\n", ... this.Rsquared.Ordinary, this.Rsquared.Adjusted); endif if (! isempty (this.ModelFitVsNullModel) ... && isstruct (this.ModelFitVsNullModel) ... && isfield (this.ModelFitVsNullModel, 'Fstat')) fprintf ("F-statistic vs. constant model: %g, p-value = %g\n", ... this.ModelFitVsNullModel.Fstat, ... this.ModelFitVsNullModel.Pvalue); endif endfunction ## Class specific subscripted reference function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' error (strcat ("CompactLinearModel: () indexing is not", ... " supported. Use dot notation to access", ... " properties.")); case '{}' error (strcat ("CompactLinearModel: {} indexing is not", ... " supported. Use dot notation to access", ... " properties.")); case '.' if (! ischar (s.subs)) error ("CompactLinearModel.subsref: property name must be a character vector."); endif if (ismethod (this, s.subs)) [varargout{1:nargout}] = builtin ('subsref', this, [s, chain_s]); return; endif try out = this.(s.subs); catch error ("CompactLinearModel.subsref: unknown property '%s'.", s.subs); end_try_catch endswitch if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction ## -*- texinfo -*- ## @deftypefn {CompactLinearModel} {@var{cmdl} =} CompactLinearModel () ## @deftypefnx {CompactLinearModel} {@var{cmdl} =} CompactLinearModel (@var{mdl}) ## ## Create a compact linear regression model. ## ## @code{@var{cmdl} = CompactLinearModel ()} returns a ## @code{CompactLinearModel} object with all properties empty. ## ## @code{@var{cmdl} = CompactLinearModel (@var{mdl})} copies the ## coefficient estimates, fit statistics, and input data description ## from the fitted @code{LinearModel} object @var{mdl} into a new ## @code{CompactLinearModel} object @var{cmdl}, discarding the training ## data, per-observation diagnostics, and stepwise fitting history. If ## @var{mdl} was fit using robust regression, the @code{Weights} field of ## @code{@var{cmdl}.Robust} is emptied, although the rest of the ## @code{Robust} structure is retained. ## ## The usual way to obtain a @code{CompactLinearModel} object is to call ## the @code{compact} method on an already-fitted @code{LinearModel} ## object, rather than calling this constructor directly. ## ## @seealso{LinearModel, compact} ## @end deftypefn function this = CompactLinearModel (mdl = []) if (isempty (mdl)) return; elseif (! isa (mdl, 'LinearModel')) error ("CompactLinearModel: invalid model object."); endif this.CoefficientCovariance = mdl.CoefficientCovariance; this.CoefficientNames = mdl.CoefficientNames; this.Coefficients = mdl.Coefficients; this.NumCoefficients = mdl.NumCoefficients; this.NumEstimatedCoefficients = mdl.NumEstimatedCoefficients; this.DFE = mdl.DFE; this.LogLikelihood = mdl.LogLikelihood; this.ModelCriterion = mdl.ModelCriterion; this.MSE = mdl.MSE; this.RMSE = mdl.RMSE; this.Rsquared = mdl.Rsquared; this.SSE = mdl.SSE; this.SSR = mdl.SSR; this.SST = mdl.SST; this.Robust = mdl.Robust; if (isstruct (this.Robust) && isfield (this.Robust, 'Weights')) this.Robust.Weights = []; endif this.Formula = mdl.Formula; this.NumObservations = mdl.NumObservations; this.NumPredictors = mdl.NumPredictors; this.NumVariables = mdl.NumVariables; this.PredictorNames = mdl.PredictorNames; this.ResponseName = mdl.ResponseName; this.VariableInfo = mdl.VariableInfo; this.VariableNames = mdl.VariableNames; this.TermsMatrix = mdl.TermsMatrix; this.CatLevelInfo = mdl.CatLevelInfo; this.EncPredictorNames = mdl.EncPredictorNames; this.EffectContrasts = mdl.EffectContrasts; this.InteractionContrasts = mdl.InteractionContrasts; this.HasIntercept = mdl.HasIntercept; this.ModelFitVsNullModel = mdl.ModelFitVsNullModel; endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {CompactLinearModel} {@var{ci} =} coefCI (@var{mdl}) ## @deftypefnx {CompactLinearModel} {@var{ci} =} coefCI (@var{mdl}, @var{alpha}) ## ## Confidence intervals for the coefficient estimates of a fitted linear ## regression model. ## ## @code{@var{ci} = coefCI (@var{mdl})} returns 95% confidence intervals ## for every coefficient in @var{mdl} using a default significance level of ## @code{0.05}. ## ## @code{@var{ci} = coefCI (@var{mdl}, @var{alpha})} uses the significance ## level @var{alpha}, a scalar in @math{[0, 1]}. The resulting intervals ## have coverage @math{100(1-\alpha)\%}. Setting @var{alpha} to @code{0} ## produces intervals of infinite width; setting it to @code{1} collapses ## each interval to the corresponding point estimate. ## ## The output @var{ci} is a @math{k}-by-2 numeric matrix where ## @math{k = } @code{@var{mdl}.NumCoefficients}. Row @math{j} contains ## the interval for the @math{j}-th coefficient, whose name is stored in ## @code{@var{mdl}.CoefficientNames@{j@}}. Column 1 is the lower bound and ## column 2 is the upper bound. The midpoint of each interval equals the ## corresponding point estimate in @code{@var{mdl}.Coefficients.Estimate}. ## ## Intervals use the Wald method: ## @math{b_j \pm t_{(1-\alpha/2,\,\mathrm{DFE})}\,\mathrm{SE}(b_j)}, ## where @math{b_j} is the coefficient estimate, @math{\mathrm{SE}(b_j)} is ## its standard error from @code{@var{mdl}.Coefficients.SE}, and the ## critical value is the @math{1-\alpha/2} quantile of the ## @math{t}-distribution with @code{@var{mdl}.DFE} degrees of freedom. ## In rank-deficient models, aliased coefficients have ## @math{\mathrm{SE} = 0} and their row in @var{ci} is @code{[0, 0]}. ## ## @end deftypefn function ci = coefCI (mdl, alpha) if (nargin > 2) error ("coefCI: Too many input arguments."); endif if (nargin < 2) alpha = 0.05; endif if (! isscalar (alpha)) error (strcat ("coefCI: Invalid argument at position 2.", ... " Value must be a scalar.")); endif if (! (alpha >= 0)) error (strcat ("coefCI: Invalid argument at position 2.", ... " Value must be greater than or equal to 0.")); endif if (alpha > 1) error (strcat ("coefCI: Invalid argument at position 2.", ... " Value must be less than or equal to 1.")); endif t = tinv (1 - alpha / 2, mdl.DFE); b = mdl.Coefficients.Estimate; se = mdl.Coefficients.SE; ci = [b - t .* se, b + t .* se]; endfunction ## -*- texinfo -*- ## @deftypefn {CompactLinearModel} {@var{p} =} coefTest (@var{mdl}) ## @deftypefnx {CompactLinearModel} {@var{p} =} coefTest (@var{mdl}, @var{H}) ## @deftypefnx {CompactLinearModel} {@var{p} =} coefTest (@var{mdl}, @var{H}, @var{C}) ## @deftypefnx {CompactLinearModel} {[@var{p}, @var{F}] =} coefTest (@dots{}) ## @deftypefnx {CompactLinearModel} {[@var{p}, @var{F}, @var{r}] =} coefTest (@dots{}) ## ## Linear hypothesis test on the coefficients of a fitted linear regression ## model. ## ## @code{coefTest} tests whether one or more linear combinations of the ## fitted coefficients equal specified constants. Each linear combination ## is encoded as a row of the contrast matrix @var{H}, and the right-hand ## side is given by @var{C}. ## ## @code{@var{p} = coefTest (@var{mdl})} performs the overall model F-test: ## it tests the joint null hypothesis that every coefficient except the ## intercept is zero. The returned p-value matches the F-statistic line ## printed at the bottom of the model display. ## ## @code{@var{p} = coefTest (@var{mdl}, @var{H})} tests the null hypothesis ## @math{H \beta = 0}, where @math{\beta} is the full coefficient vector ## of length @math{k = } @code{@var{mdl}.NumCoefficients}. @var{H} must be ## a full-rank numeric matrix with @math{k} columns; each row specifies one ## linear constraint. To test a single coefficient, use a row vector with a ## @code{1} in that coefficient's position and zeros elsewhere; the ## resulting F-statistic equals the square of the corresponding t-statistic ## in @code{@var{mdl}.Coefficients}. To test a categorical predictor that ## expands to multiple indicator columns, include one row per indicator in ## @var{H}. ## ## @code{@var{p} = coefTest (@var{mdl}, @var{H}, @var{C})} tests ## @math{H \beta = C} instead of zero. @var{C} must be a numeric vector ## with the same number of elements as rows of @var{H}; both row and column ## vectors are accepted. ## ## The second output @var{F} is the value of the F-statistic: ## @math{F = (H\hat{\beta} - C)^\prime (H V H^\prime)^{-1} ## (H\hat{\beta} - C) / r}, where @math{V} is ## @code{@var{mdl}.CoefficientCovariance} and @math{r} is the number of ## rows of @var{H}. The third output @var{r} is that numerator degrees of ## freedom; the denominator degrees of freedom is @code{@var{mdl}.DFE}. ## Under the null hypothesis @math{F} follows an @math{F(r, \mathrm{DFE})} ## distribution and the p-value is the upper-tail probability. When ## @var{H} is rank-deficient but contains no @code{NaN}, both @var{p} and ## @var{F} are returned as @code{NaN} without an error. ## ## @end deftypefn function [p, F, r] = coefTest (mdl, varargin) if (nargout > 3) error ("coefTest: Too many output arguments."); endif if (numel (varargin) > 2) error ("coefTest: Too many input arguments."); endif k = mdl.NumCoefficients; if (numel (varargin) >= 1 && ! isempty (varargin{1})) H = varargin{1}; if (! isnumeric (H)) error ("coefTest: H must be a %d-by-%d numeric matrix.", size (H, 1), k); endif if (size (H, 2) != k) error ("coefTest: H must be a %d-by-%d numeric matrix.", size (H, 1), k); endif if (any (any (isnan (H)))) error (strcat ("coefTest: H is not full rank and hypotheses", ... " are not consistent.")); endif r = size (H, 1); if (numel (varargin) == 2) C = varargin{2}; if (! isnumeric (C)) error ("coefTest: C must be a numeric vector."); endif C = C(:); if (numel (C) != r) error ("coefTest: H must be a %d-by-%d numeric matrix.", numel (C), k); endif else C = zeros (r, 1); endif else if (mdl.HasIntercept && k > 1) H = [zeros(k-1, 1), eye(k-1)]; r = k - 1; else H = eye (k); r = k; endif C = zeros (r, 1); endif b = mdl.Coefficients.Estimate; V = mdl.CoefficientCovariance; HVH = H * V * H'; Hb_c = H * b - C; if (rcond (HVH) < eps (class (HVH))) F = NaN; p = NaN; else F = (Hb_c' * (HVH \ Hb_c)) / r; p = betainc (mdl.DFE / (mdl.DFE + r * F), mdl.DFE / 2, r / 2); endif endfunction ## -*- texinfo -*- ## @deftypefn {CompactLinearModel} {@var{ypred} =} predict (@var{mdl}, @var{Xnew}) ## @deftypefnx {CompactLinearModel} {[@var{ypred}, @var{yci}] =} predict (@var{mdl}, @var{Xnew}) ## @deftypefnx {CompactLinearModel} {[@var{ypred}, @var{yci}] =} predict (@var{mdl}, @var{Xnew}, @var{Name}, @var{Value}) ## ## Predict responses from a fitted linear regression model. ## ## @code{@var{ypred} = predict (@var{mdl}, @var{Xnew})} returns the fitted ## response values at the new predictor locations in @var{Xnew}. @var{Xnew} ## can be a numeric matrix with one column per predictor in the same order ## as the training data, or a table whose column names match ## @code{@var{mdl}.PredictorNames}. Rows containing @code{NaN} are returned ## as @code{NaN} without error. Unlike @code{LinearModel}, @var{Xnew} is ## required: a @code{CompactLinearModel} object does not store the ## training data, so there is no default to fall back on when it is ## omitted. ## ## @code{[@var{ypred}, @var{yci}] = predict (@dots{})} also returns ## @var{yci}, an @math{n}-by-2 matrix of confidence bounds where column 1 is ## the lower bound and column 2 is the upper bound. By default these are ## 95% pointwise confidence intervals on the mean response. ## ## Name-Value pair arguments: ## ## @multitable @columnfractions 0.2 0.78 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab Significance level for the confidence ## interval, specified as a scalar in @math{[0,1]}. The interval has ## coverage @math{100(1-\alpha)\%}. Default is @code{0.05}, giving a 95% ## interval. ## ## @item @qcode{'Prediction'} @tab Type of interval to compute. ## @code{"curve"} (default) gives a confidence interval on the mean response ## @math{f(x)}. @code{"observation"} gives a wider prediction interval for ## a single future observation @math{y = f(x) + \varepsilon}, which accounts ## for both estimation uncertainty and irreducible noise; it adds ## @code{@var{mdl}.MSE} to the variance before computing the half-width. ## ## @item @qcode{'Simultaneous'} @tab Logical flag controlling whether ## the bounds are simultaneous or pointwise. When @code{true}, ## Scheff@'{e}'s method is used so the entire predicted curve lies within ## the band with @math{100(1-\alpha)\%} confidence; these bands are always ## wider than pointwise ones. Default is @code{false}. ## @end multitable ## ## @end deftypefn function [ypred, yci] = predict (mdl, Xnew, varargin) if (nargin < 2) error ("predict: Not enough input arguments."); endif alpha = 0.05; pred_obs = false; simultan = false; i = 1; while (i <= numel (varargin)) if (strcmpi (varargin{i}, 'Alpha')) alpha = varargin{i+1}; if (! isscalar (alpha) || ! isnumeric (alpha) || alpha < 0 || alpha > 1) error ("predict: Alpha must be a scalar in [0,1]."); endif i += 2; elseif (strcmpi (varargin{i}, 'Prediction')) pred_str = lower (char (varargin{i+1})); if (! any (strcmp (pred_str, {'curve', 'observation'}))) error ("predict: Prediction must be 'curve' or 'observation'."); endif pred_obs = strcmp (pred_str, 'observation'); i += 2; elseif (strcmpi (varargin{i}, 'Simultaneous')) simultan = logical (varargin{i+1}); i += 2; else error ("predict: unknown option '%s'.", varargin{i}); endif endwhile pred_names = mdl.PredictorNames; p_raw = mdl.NumPredictors; if (istable (Xnew)) n_new = height (Xnew); X_raw = zeros (n_new, p_raw); for j = 1:p_raw if (! ismember (pred_names{j}, Xnew.Properties.VariableNames)) error ("predict: Xnew table is missing predictor '%s'.", pred_names{j}); endif col = Xnew.(pred_names{j}); if (iscell (col)) cat_idx = []; if (! isempty (mdl.CatLevelInfo.names)) cat_idx = find (strcmp (mdl.CatLevelInfo.names, pred_names{j})); endif if (! isempty (cat_idx)) levels_j = mdl.CatLevelInfo.levels{cat_idx}; codes = zeros (n_new, 1); for k = 1:numel (levels_j) codes(strcmp (col, levels_j{k})) = k; endfor X_raw(:, j) = codes; endif else X_raw(:, j) = double (col); endif endfor else X_raw = double (Xnew); if (columns (X_raw) != p_raw) error ("predict: Xnew must have %d columns.", p_raw); endif n_new = rows (X_raw); endif nan_rows = any (isnan (X_raw), 2); X_enc_new = reencode_predictors (X_raw, pred_names, mdl.CatLevelInfo, mdl.EncPredictorNames); X_design_new = build_design (mdl.TermsMatrix, X_enc_new); beta = mdl.Coefficients.Estimate; ypred = X_design_new * beta; ypred(nan_rows) = NaN; if (nargout > 1) CovB = mdl.CoefficientCovariance; var_cv = sum ((X_design_new * CovB) .* X_design_new, 2); if (pred_obs) var_ci = var_cv + mdl.MSE; else var_ci = var_cv; endif p_est = mdl.NumEstimatedCoefficients; if (simultan) mult = sqrt (p_est * finv (1 - alpha, p_est, mdl.DFE)); else mult = tinv (1 - alpha / 2, mdl.DFE); endif hw = mult * sqrt (max (var_ci, 0)); yci = [ypred - hw, ypred + hw]; yci(nan_rows,:) = NaN; endif endfunction ## -*- texinfo -*- ## @deftypefn {CompactLinearModel} {@var{ysim} =} random (@var{mdl}, @var{Xnew}) ## ## Simulate responses with random noise from a fitted linear regression ## model. ## ## @code{@var{ysim} = random (@var{mdl}, @var{Xnew})} computes the fitted ## response at each row of @var{Xnew} and then adds independent Gaussian ## noise to each value. The noise is drawn from @math{N(0, \sigma^2)} where ## @math{\sigma^2} is the estimated error variance @code{@var{mdl}.MSE} ## (mean squared error of the fit). The result is a column vector of the ## same length as the number of rows in @var{Xnew}. ## ## @var{Xnew} is required and must be non-empty. It can be a numeric ## matrix with one column per predictor in the same order as the training ## data, or a table whose column names match ## @code{@var{mdl}.PredictorNames}. ## ## Because the added noise is drawn freshly on every call, two calls with ## the same @var{Xnew} will generally produce different output. To get ## reproducible results, set the random seed with @code{rand ('state', s)} ## before calling @code{random}. ## ## For deterministic predictions without noise, use @code{predict} or ## @code{feval}. @code{predict} also provides confidence intervals on the ## mean response. ## ## @end deftypefn function ysim = random (mdl, Xnew, varargin) if (nargin < 2) error ("random: Not enough input arguments."); endif if (nargin > 2) error ("random: Too many input arguments."); endif if (isempty (Xnew)) error ("random: Xnew must have %d columns.", mdl.NumPredictors); endif ypred = predict (mdl, Xnew); ysim = ypred + sqrt (mdl.MSE) .* randn (numel (ypred), 1); endfunction ## -*- texinfo -*- ## @deftypefn {CompactLinearModel} {@var{ypred} =} feval (@var{mdl}, @var{X}) ## @deftypefnx {CompactLinearModel} {@var{ypred} =} feval (@var{mdl}, @var{x1}, @var{x2}, @dots{}, @var{xp}) ## ## Predict responses of a fitted linear regression model using separate ## predictor inputs. ## ## @code{@var{ypred} = feval (@var{mdl}, @var{X})} accepts a single ## numeric matrix @var{X} with one column per predictor in the same order ## as the training data, or a table whose column names match ## @code{@var{mdl}.PredictorNames}. The output is an @math{n}-by-1 column ## vector. Rows that contain @code{NaN} in any predictor column are ## returned as @code{NaN}. ## ## @code{@var{ypred} = feval (@var{mdl}, @var{x1}, @var{x2}, @dots{}, ## @var{xp})} accepts exactly @code{@var{mdl}.NumPredictors} separate ## arguments, one per predictor variable. All non-scalar arguments must ## have the same size; a scalar argument is broadcast to that size ## automatically. The output shape follows the shape of the non-scalar ## inputs: column vector inputs give a column vector output, row vector ## inputs give a row vector output, and all-scalar inputs give a scalar. ## This form is convenient when predictor data is already stored in separate ## vectors rather than a combined matrix. ## ## @code{feval} gives the same numerical predictions as @code{predict} but ## does not support confidence intervals. Use @code{predict} when you also ## need bounds on the response. Because a @code{CompactLinearModel} object ## behaves like a function through @code{feval}, it can be passed directly ## to routines that accept a function handle, such as @code{fminsearch} or ## @code{integral}. ## ## @end deftypefn function ypred = feval (mdl, varargin) p_raw = mdl.NumPredictors; n_extra = nargin - 1; if (n_extra < 1) error ("feval: Not enough input arguments."); endif if (n_extra == 1) Xnew = varargin{1}; if (istable (Xnew)) for j = 1:p_raw if (! ismember (mdl.PredictorNames{j}, Xnew.Properties.VariableNames)) error (strcat ("feval: X does not contain one or more", ... " predictor variables needed for this model.")); endif endfor else if (columns (double (Xnew)) != p_raw) error ("feval: Predictor data matrix must have %d columns.", p_raw); endif endif ypred = predict (mdl, Xnew); elseif (n_extra == p_raw) for i = 1:n_extra if (ischar (varargin{i}) || iscategorical (varargin{i})) if (iscategorical (varargin{i})) lvl_str = char (varargin{i}); else lvl_str = varargin{i}; endif ci = []; if (! isempty (mdl.CatLevelInfo.names)) ci = find (strcmp (mdl.CatLevelInfo.names, mdl.PredictorNames{i})); endif if (isempty (ci)) error ("feval: predictor '%s' is not categorical.", mdl.PredictorNames{i}); endif levels_i = mdl.CatLevelInfo.levels{ci}; code = find (strcmp (levels_i, lvl_str), 1); if (isempty (code)) code = NaN; endif varargin{i} = code; endif endfor ref_size = []; for i = 1:n_extra if (! isscalar (varargin{i})) s_i = size (varargin{i}); if (isempty (ref_size)) ref_size = s_i; elseif (! isequal (s_i, ref_size)) error ("feval: All input arguments must be the same size."); endif endif endfor if (isempty (ref_size)) ref_size = [1, 1]; endif n_pts = prod (ref_size); Xmat = zeros (n_pts, p_raw); for i = 1:n_extra ai = varargin{i}; if (isscalar (ai)) Xmat(:, i) = ai; else Xmat(:, i) = ai(:); endif endfor ypred = reshape (predict (mdl, Xmat), ref_size); else error (strcat ("feval: Incorrect number of input arguments. You", ... " must provide either %d separate predictor", ... " variable arguments, or one predictor matrix with", ... " %d columns."), p_raw, p_raw); endif endfunction ## -*- texinfo -*- ## @deftypefn {CompactLinearModel} {} plotEffects (@var{mdl}) ## @deftypefnx {CompactLinearModel} {} plotEffects (@var{ax}, @var{mdl}) ## @deftypefnx {CompactLinearModel} {@var{h} =} plotEffects (@dots{}) ## ## Plot the main effects of each predictor in a compact linear regression ## model. ## ## @code{plotEffects (@var{mdl})} creates a horizontal dot-and-line plot ## with one row per predictor. Each dot shows the estimated main effect on ## the response from changing that predictor from its minimum observed value ## to its maximum observed value, while holding all other predictors fixed ## at their observed means. A horizontal line through each dot shows the ## 95% confidence interval for that effect. ## ## The main effect for predictor @var{xs} is defined as ## @math{g(x_{s,\max}) - g(x_{s,\min})}, where the adjusted response ## function @math{g} evaluates the model at the specified value of ## @var{xs} with all other predictors set to their observed means. ## For numeric predictors the sign of the effect can be positive or ## negative depending on the direction of the relationship. Because a ## @code{CompactLinearModel} does not retain the training data, these ## values come from a summary computed once when the model was fitted, ## rather than recomputed from the original observations. ## ## @code{plotEffects (@var{ax}, @var{mdl})} creates the plot in the axes ## object @var{ax} instead of the current axes returned by @code{gca}. ## ## @code{@var{h} = plotEffects (@dots{})} returns a vector of ## @math{p+1} graphics handles where @math{p} is the number of predictors. ## @code{h(1)} is the line object containing the effect estimate markers ## (one circle per predictor, plotted as a single line object with ## @code{XData} of length @math{p} and @code{YData = 1:p}). ## @code{h(j+1)} is the confidence interval line for predictor @math{j}, ## with @code{XData = [ci_lo, ci_hi]} and @code{YData = [j, j]}. ## ## The y-axis tick labels follow the format ## @qcode{'varname: min to max'}, showing the predictor name and the ## minimum and maximum observed values used to compute the effect. ## ## @end deftypefn function h = plotEffects (this, varargin) [ax, mdl, args] = cm_plot_axes (this, varargin); if (! isempty (args)) error ("plotEffects: Wrong number of arguments."); endif p = mdl.NumPredictors; if (! any (any (mdl.TermsMatrix(:, 1:end-1) != 0))) error ("plotEffects: Model has no predictors."); endif if (isempty (ax)) ax = gca (); endif DEF_COLOR = [0.1490, 0.5490, 0.8660]; pred = mdl.PredictorNames; V = mdl.CoefficientCovariance; beta = mdl.Coefficients.Estimate; t_crit = tinv (0.975, mdl.DFE); cinfo = mdl.CatLevelInfo; C = mdl.EffectContrasts; effects = zeros (1, p); ci_lo = zeros (1, p); ci_hi = zeros (1, p); for j = 1:p effects(j) = C(j,:) * beta; SE = sqrt (max (0, C(j,:) * V * C(j,:)')); ci_lo(j) = effects(j) - t_crit * SE; ci_hi(j) = effects(j) + t_crit * SE; endfor hold (ax, 'on'); h(1) = plot (ax, effects, 1:p, ... 'LineStyle', 'none', ... 'Marker', 'o', ... 'MarkerSize', 6, ... 'Color', DEF_COLOR); for j = 1:p h(j+1) = line ([ci_lo(j), ci_hi(j)], [j, j], ... 'LineStyle', '-', ... 'Marker', 'none', ... 'Color', DEF_COLOR, ... 'Parent', ax); endfor hold (ax, 'off'); rn = mdl.VariableInfo.Properties.RowNames; ytl = cell (p, 1); for j = 1:p ci = []; if (! isempty (cinfo) && isfield (cinfo, 'names') && ! isempty (cinfo.names)) ci = find (strcmp (cinfo.names, pred{j})); endif vidx = find (strcmp (rn, pred{j})); rng = mdl.VariableInfo.Range{vidx}; if (! isempty (ci)) levels_j = cinfo.levels{ci}; lo_str = char (levels_j{1}); hi_str = char (levels_j{end}); else lo_str = num2str (rng(1), '%g'); hi_str = num2str (rng(2), '%g'); endif ytl{j} = [pred{j}, ': ', lo_str, ' to ', hi_str]; endfor set (ax, 'YTick', 1:p, 'YTickLabel', ytl, 'YDir', 'reverse'); ylim (ax, [0.5, p + 0.5]); xlabel (ax, 'Main Effect'); ylabel (ax, ''); title (ax, 'Main Effects Plot'); if (nargout == 0) clear h; endif endfunction ## -*- texinfo -*- ## @deftypefn {CompactLinearModel} {} plotInteraction (@var{mdl}, @var{var1}, @var{var2}) ## @deftypefnx {CompactLinearModel} {} plotInteraction (@var{mdl}, @var{var1}, @var{var2}, @var{ptype}) ## @deftypefnx {CompactLinearModel} {} plotInteraction (@var{ax}, @dots{}) ## @deftypefnx {CompactLinearModel} {@var{h} =} plotInteraction (@dots{}) ## ## Plot the interaction effects of two predictors in a compact linear ## regression model. ## ## @code{plotInteraction (@var{mdl}, @var{var1}, @var{var2})} creates a ## plot of the main effects of @var{var1} and @var{var2} together with ## their conditional effects, with horizontal lines through each effect ## value indicating its 95% confidence interval. @var{var1} and ## @var{var2} are each a character vector or string naming a variable in ## @code{mdl.VariableNames}, or a positive integer indexing into ## @code{mdl.VariableNames}; neither may name the response variable, and ## they must be different variables. ## ## The main effect of a predictor is the change in the adjusted response ## between the two predictor values that produce the minimum and maximum ## adjusted response, with the other predictor averaged over its own ## observed values row by row. For a numeric predictor these two values ## are its observed minimum and maximum; for a categorical predictor ## every level is evaluated and the levels producing the minimum and ## maximum adjusted response are used, so the effect is always ## nonnegative. ## ## The conditional effect of @var{var1} is its effect recomputed with ## @var{var2} additionally held fixed at each of a small set of ## conditioning values, and likewise the conditional effect of ## @var{var2} holds @var{var1} fixed. The conditioning values are the ## observed minimum, mean of the minimum and maximum, and maximum for a ## numeric predictor, or every level for a categorical predictor. When ## the main effect and conditional effect points for a predictor do not ## align vertically, the model exhibits an interaction between ## @var{var1} and @var{var2}. Because a @code{CompactLinearModel} does ## not retain the training data, these values come from a summary ## computed once when the model was fitted, rather than recomputed from ## the original observations. ## ## @code{plotInteraction (@var{mdl}, @var{var1}, @var{var2}, @var{ptype})} ## selects the plot type. @var{ptype} is @qcode{'effects'} (default), as ## described above, or @qcode{'predictions'}, which instead plots the ## adjusted response as a function of @var{var2} for each conditioning ## value of @var{var1} held fixed, evaluated over 101 equally spaced ## points spanning the observed range of @var{var2} when @var{var2} is ## numeric, or at each level of @var{var2} when it is categorical. ## ## @code{plotInteraction (@var{ax}, @dots{})} plots into the axes object ## @var{ax} instead of the current axes returned by @code{gca}. ## ## @code{@var{h} = plotInteraction (@dots{})} returns a vector of line ## handles. When @var{ptype} is @qcode{'effects'}, @code{h(1)} is the ## marker line through the two main effect points, @code{h(2)} and ## @code{h(3)} are the confidence interval lines for the main effects of ## @var{var1} and @var{var2}, and the remaining entries are the ## conditional effect points and their confidence intervals, tagged ## @qcode{'conditional1'} for @var{var1} and @qcode{'conditional2'} for ## @var{var2}. The main effect line objects are tagged @qcode{'main'}. ## When @var{ptype} is @qcode{'predictions'}, each entry in @var{h} ## corresponds to one adjusted response curve, one per conditioning ## value of @var{var1}. ## ## @end deftypefn function h = plotInteraction (this, varargin) [ax, mdl, args] = cm_plot_axes (this, varargin); if (numel (args) < 2) error ("plotInteraction: Not enough input arguments."); endif var1 = args{1}; var2 = args{2}; args = args(3:end); ptype = 'effects'; if (! isempty (args) && (ischar (args{1}) || isstring (args{1}))) ptype = lower (char (args{1})); args = args(2:end); if (! any (strcmp (ptype, {'effects', 'predictions'}))) error ("plotInteraction: PTYPE must be 'effects' or 'predictions'."); endif endif if (! isempty (args)) error ("plotInteraction: Too many input arguments."); endif vnames = mdl.VariableNames; if (ischar (var1) || isstring (var1)) v1name = char (var1); if (isempty (find (strcmp (vnames, v1name)))) error ("plotInteraction: '%s' is not a variable for this fit.", v1name); endif elseif (isnumeric (var1) && isscalar (var1)) if (var1 != fix (var1) || var1 < 1) error (strcat ("plotInteraction: Variable must be specified as a", ... " name or a positive integer.")); endif if (var1 > numel (vnames)) error ("plotInteraction: This model only contains %d variables.", numel (vnames)); endif v1name = vnames{var1}; else error (strcat ("plotInteraction: Variable must be specified as a", ... " name or a positive integer.")); endif if (strcmp (v1name, mdl.ResponseName)) error ("plotInteraction: The variable '%s' is the response in this model.", v1name); endif if (ischar (var2) || isstring (var2)) v2name = char (var2); if (isempty (find (strcmp (vnames, v2name)))) error ("plotInteraction: '%s' is not a variable for this fit.", v2name); endif elseif (isnumeric (var2) && isscalar (var2)) if (var2 != fix (var2) || var2 < 1) error (strcat ("plotInteraction: Variable must be specified as a", ... " name or a positive integer.")); endif if (var2 > numel (vnames)) error ("plotInteraction: This model only contains %d variables.", numel (vnames)); endif v2name = vnames{var2}; else error (strcat ("plotInteraction: Variable must be specified as a", ... " name or a positive integer.")); endif if (strcmp (v2name, mdl.ResponseName)) error ("plotInteraction: The variable '%s' is the response in this model.", v2name); endif if (strcmp (v1name, v2name)) error ("plotInteraction: VAR1 and VAR2 must be different variables."); endif pred = mdl.PredictorNames; cinfo = mdl.CatLevelInfo; beta = mdl.Coefficients.Estimate; V = mdl.CoefficientCovariance; t_crit = tinv (0.975, mdl.DFE); C = mdl.EffectContrasts; IC = mdl.InteractionContrasts; rn = mdl.VariableInfo.Properties.RowNames; j1 = find (strcmp (pred, v1name)); j2 = find (strcmp (pred, v2name)); is_cat1 = ! isempty (cinfo) && isfield (cinfo, 'names') ... && any (strcmp (cinfo.names, v1name)); is_cat2 = ! isempty (cinfo) && isfield (cinfo, 'names') ... && any (strcmp (cinfo.names, v2name)); if (is_cat1) ci1 = find (strcmp (cinfo.names, v1name)); levels_1 = cinfo.levels{ci1}; n_lv1 = numel (levels_1); g_lv1 = IC.OwnGridRows{j1} * beta; [~, i_lo1] = min (g_lv1); [~, i_hi1] = max (g_lv1); lbl1 = [v1name, ': ', char(levels_1{i_lo1}), ' to ', char(levels_1{i_hi1})]; grid1 = (1:n_lv1)'; grid1_lbls = cellfun (@(s) char (s), levels_1, 'UniformOutput', false); eff1 = g_lv1(i_hi1) - g_lv1(i_lo1); c_diff1 = IC.OwnGridRows{j1}(i_hi1,:) - IC.OwnGridRows{j1}(i_lo1,:); se1 = sqrt (max (0, c_diff1 * V * c_diff1')); hi1v = i_hi1; lo1v = i_lo1; else vidx1 = find (strcmp (rn, v1name)); rng1 = mdl.VariableInfo.Range{vidx1}; lo1 = rng1(1); hi1 = rng1(2); lbl1 = [v1name, ': ', num2str(lo1), ' to ', num2str(hi1)]; grid1 = [lo1; (lo1+hi1)/2; hi1]; grid1_lbls = arrayfun (@(v) num2str(v,'%g'), grid1, 'UniformOutput', false); eff1 = C(j1,:) * beta; se1 = sqrt (max (0, C(j1,:) * V * C(j1,:)')); hi1v = hi1; lo1v = lo1; endif if (is_cat2) ci2 = find (strcmp (cinfo.names, v2name)); levels_2 = cinfo.levels{ci2}; n_lv2 = numel (levels_2); g_lv2 = IC.OwnGridRows{j2} * beta; [~, i_lo2] = min (g_lv2); [~, i_hi2] = max (g_lv2); lbl2 = [v2name, ': ', char(levels_2{i_lo2}), ' to ', char(levels_2{i_hi2})]; grid2 = (1:n_lv2)'; grid2_lbls = cellfun (@(s) char (s), levels_2, 'UniformOutput', false); eff2 = g_lv2(i_hi2) - g_lv2(i_lo2); c_diff2 = IC.OwnGridRows{j2}(i_hi2,:) - IC.OwnGridRows{j2}(i_lo2,:); se2 = sqrt (max (0, c_diff2 * V * c_diff2')); hi2v = i_hi2; lo2v = i_lo2; else vidx2 = find (strcmp (rn, v2name)); rng2 = mdl.VariableInfo.Range{vidx2}; lo2 = rng2(1); hi2 = rng2(2); lbl2 = [v2name, ': ', num2str(lo2), ' to ', num2str(hi2)]; grid2 = [lo2; (lo2+hi2)/2; hi2]; grid2_lbls = arrayfun (@(v) num2str(v,'%g'), grid2, 'UniformOutput', false); eff2 = C(j2,:) * beta; se2 = sqrt (max (0, C(j2,:) * V * C(j2,:)')); hi2v = hi2; lo2v = lo2; endif P12 = IC.Pairs{j1,j2}; n2 = numel (grid2); if (isempty (P12)) eff_c1 = repmat (eff1, n2, 1); se_c1 = repmat (se1, n2, 1); else n2c = numel (P12.grid2); eff_c1 = zeros (n2, 1); se_c1 = zeros (n2, 1); for k = 1:n2 idx1_hi = find (P12.grid1 == hi1v, 1); idx1_lo = find (P12.grid1 == lo1v, 1); idx2_k = find (P12.grid2 == grid2(k), 1); c_hi = P12.rows((idx1_hi-1)*n2c + idx2_k, :); c_lo = P12.rows((idx1_lo-1)*n2c + idx2_k, :); eff_c1(k) = (c_hi - c_lo) * beta; se_c1(k) = sqrt (max (0, (c_hi - c_lo) * V * (c_hi - c_lo)')); endfor endif P21 = IC.Pairs{j2,j1}; n1 = numel (grid1); if (isempty (P21)) eff_c2 = repmat (eff2, n1, 1); se_c2 = repmat (se2, n1, 1); else n2c = numel (P21.grid2); eff_c2 = zeros (n1, 1); se_c2 = zeros (n1, 1); for k = 1:n1 idx1_hi = find (P21.grid1 == hi2v, 1); idx1_lo = find (P21.grid1 == lo2v, 1); idx2_k = find (P21.grid2 == grid1(k), 1); c_hi = P21.rows((idx1_hi-1)*n2c + idx2_k, :); c_lo = P21.rows((idx1_lo-1)*n2c + idx2_k, :); eff_c2(k) = (c_hi - c_lo) * beta; se_c2(k) = sqrt (max (0, (c_hi - c_lo) * V * (c_hi - c_lo)')); endfor endif if (isempty (ax)) ax = gca (); endif cla (ax); DEF_COLOR = [0.1490, 0.5490, 0.8660]; FIT_COLOR = [0.9600, 0.4660, 0.1600]; if (strcmp (ptype, 'effects')) y_main1 = 1; y_cond1 = (2:(1+n2))'; y_main2 = n2 + 4; y_cond2 = ((n2+5):(n2+4+n1))'; hold (ax, 'on'); line ([0, 0], [0.5, n2 + n1 + 4.5], 'LineStyle', ':', 'Marker', 'none', ... 'Color', [0, 0, 0], 'Parent', ax); h(1) = plot (ax, [eff1, eff2], [y_main1, y_main2], ... 'LineStyle', 'none', 'Marker', 'o', 'Color', DEF_COLOR, ... 'Tag', 'main'); h(2) = line ([eff1 - t_crit*se1, eff1 + t_crit*se1], [y_main1, y_main1], ... 'LineStyle', '-', 'Marker', 'none', 'Color', DEF_COLOR, ... 'Parent', ax, 'Tag', 'main'); h(3) = line ([eff2 - t_crit*se2, eff2 + t_crit*se2], [y_main2, y_main2], ... 'LineStyle', '-', 'Marker', 'none', 'Color', DEF_COLOR, ... 'Parent', ax, 'Tag', 'main'); h(4) = plot (ax, eff_c1, y_cond1, ... 'LineStyle', 'none', 'Marker', 'o', 'Color', FIT_COLOR, ... 'Tag', 'conditional1'); for k = 1:n2 h(4+k) = line ([eff_c1(k) - t_crit*se_c1(k), eff_c1(k) + t_crit*se_c1(k)], ... [y_cond1(k), y_cond1(k)], ... 'LineStyle', '-', 'Marker', 'none', 'Color', FIT_COLOR, ... 'Parent', ax, 'Tag', 'conditional1'); endfor h(5+n2) = plot (ax, eff_c2, y_cond2, ... 'LineStyle', 'none', 'Marker', 'o', 'Color', FIT_COLOR, ... 'Tag', 'conditional2'); for k = 1:n1 h(5+n2+k) = line ([eff_c2(k) - t_crit*se_c2(k), eff_c2(k) + t_crit*se_c2(k)], ... [y_cond2(k), y_cond2(k)], ... 'LineStyle', '-', 'Marker', 'none', 'Color', FIT_COLOR, ... 'Parent', ax, 'Tag', 'conditional2'); endfor hold (ax, 'off'); ytl = cell (2 + n1 + n2, 1); ytl{1} = lbl1; for k = 1:n2 ytl{1+k} = [v2name, '=', grid2_lbls{k}]; endfor ytl{2+n2} = lbl2; for k = 1:n1 ytl{2+n2+k} = [v1name, '=', grid1_lbls{k}]; endfor set (ax, 'YTick', [y_main1; y_cond1; y_main2; y_cond2], ... 'YTickLabel', ytl, 'YDir', 'reverse'); ylim (ax, [0.5, n2 + n1 + 4.5]); xlabel (ax, 'Effect'); ylabel (ax, ''); title (ax, ['Interaction of ', v1name, ' and ', v2name]); else ## 'predictions' if (is_cat2) x_grid2 = (1:n_lv2)'; else x_grid2 = linspace (lo2, hi2, 101)'; endif hold (ax, 'on'); line (NaN, NaN, 'Color', 'none', 'Parent', ax, 'DisplayName', v1name); colors = get (ax, 'ColorOrder'); n_colors = rows (colors); n2c = numel (P12.grid2); for k = 1:n1 idx1_k = find (P12.grid1 == grid1(k), 1); row_lo = (idx1_k - 1) * n2c + 1; row_hi = idx1_k * n2c; rows_k = P12.rows(row_lo:row_hi, :); if (is_cat2) y_curve = rows_k * beta; else deg = n2c - 1; Vm = (P12.grid2(:)) .^ (0:deg); coefs = Vm \ rows_k; Vq = (x_grid2(:)) .^ (0:deg); y_curve = Vq * coefs * beta; endif h(k) = line (x_grid2, y_curve, ... 'Color', colors(mod(k-1, n_colors)+1, :), ... 'LineStyle', '-', 'Marker', 'none', 'Parent', ax, ... 'DisplayName', grid1_lbls{k}); endfor hold (ax, 'off'); if (is_cat2) set (ax, 'XTick', 1:n_lv2, 'XTickLabel', grid2_lbls); endif xlabel (ax, v2name); ylabel (ax, ['Adjusted ', mdl.ResponseName]); title (ax, ['Interaction of ', v1name, ' and ', v2name]); legend (ax, 'show'); endif if (nargout == 0) clear h; endif endfunction ## -*- texinfo -*- ## @deftypefn {CompactLinearModel} {@var{tbl} =} anova (@var{mdl}) ## @deftypefnx {CompactLinearModel} {@var{tbl} =} anova (@var{mdl}, @var{anovatype}) ## @deftypefnx {CompactLinearModel} {@var{tbl} =} anova (@var{mdl}, @qcode{"components"}, @var{sstype}) ## ## Analysis of variance for a compact linear regression model. ## ## @code{anova (@var{mdl})} returns a table @var{tbl} with component ## ANOVA statistics for every term in @var{mdl} except the constant ## term, computed with hierarchical (@qcode{"h"}) sums of squares. Each ## row gives @code{SumSq}, @code{DF}, @code{MeanSq}, @code{F}, and ## @code{pValue} for the corresponding term; the trailing @qcode{Error} ## row gives @code{SumSq = @var{mdl}.SSE}, @code{DF = @var{mdl}.DFE}, ## @code{MeanSq = @var{mdl}.MSE}, and @code{NaN} for @code{F} and ## @code{pValue}. ## ## MATLAB reports @code{F = 1} and @code{pValue = 0.5} on that ## @qcode{Error} row instead. Those are not results: the row's @code{F} ## is its own @code{MeanSq} divided by itself, so it is @code{1} for every ## data set, and the @code{pValue} follows. MATLAB does not use them ## consistently either, reporting @code{NaN} for the same quantity on the ## @qcode{Residual} row of its summary table. This implementation reports ## @code{NaN} in both places. ## ## Every statistic is computed from ## @code{@var{mdl}.Coefficients} and ## @code{@var{mdl}.CoefficientCovariance} alone; a @code{CompactLinearModel} ## never refits, because it does not retain the training data. ## ## @code{anova (@var{mdl}, @var{anovatype})} selects ## @qcode{"components"} (default) or @qcode{"summary"}. For ## @qcode{"summary"}, @var{tbl} always contains rows @qcode{Total}, ## @qcode{Model}, and @qcode{Residual}, and additionally @qcode{. Linear} ## and @qcode{. Nonlinear} whenever @var{mdl} contains an interaction ## term or a continuous term of degree greater than 1. @qcode{Total} ## reports @code{@var{mdl}.SST} with @code{DF = NumObservations - 1}; ## @qcode{Model} reports @code{@var{mdl}.SSR} with @code{DF = ## NumCoefficients - HasIntercept}; @qcode{Residual} reports ## @code{@var{mdl}.SSE} with @code{DF = @var{mdl}.DFE}. Unlike ## @code{anova} on a @code{LinearModel}, @var{tbl} never contains ## @qcode{. Lack of fit} or @qcode{. Pure error} rows, since identifying ## observations with identical predictor values requires the training ## data that a @code{CompactLinearModel} does not retain. ## ## @code{anova (@var{mdl}, @qcode{"components"}, @var{sstype})} selects ## the sum of squares used for the component table: @code{1} (sequential, ## reduction from adding each term in formula order), @code{2} (reduction ## from adding the term to a model containing every term that does not ## contain it), @qcode{"h"} (default; as Type 2, but a higher-degree ## term in the same continuous variable, such as a squared term, is also ## treated as containing the lower-degree term), or @code{3} (reduction ## from adding the term to a model containing every other term, with ## categorical predictors recoded using sum-to-zero deviation contrasts ## instead of @var{mdl}'s reference-level coding). @var{sstype} is ## ignored when @var{anovatype} is @qcode{"summary"}. If @var{mdl} is ## missing a lower-order relative of one of its terms (e.g. an ## interaction without one of its main effects, or a categorical ## predictor fit without an intercept), type @code{3} raises an error, ## since a @code{CompactLinearModel} has no data to refit with. ## ## @seealso{LinearModel, coefTest} ## @end deftypefn function tbl = anova (mdl, varargin) if (numel (varargin) > 2) error ("anova: too many input arguments."); endif anovatype = 'components'; if (numel (varargin) >= 1) anovatype = varargin{1}; valid_types = {'summary', 'components', 'oldcomponents', 'newcomponents'}; if (! ischar (anovatype) || ! any (strcmpi (anovatype, valid_types))) error ("anova: ANOVATYPE must be 'summary' or 'components'."); endif endif sstype = 'h'; if (numel (varargin) == 2 && ! strcmpi (anovatype, 'summary')) sstype = varargin{2}; valid_n = isnumeric (sstype) && isscalar (sstype) && any (sstype == [1, 2, 3]); valid_h = ischar (sstype) && strcmpi (sstype, 'h'); if (! valid_n && ! valid_h) error ("anova: SSTYPE must be 1, 2, or 3."); endif endif [term_name, term_cols] = cm_anova_term_groups ( ... mdl.CoefficientNames, mdl.CatLevelInfo, mdl.HasIntercept); nterm = numel (term_name); if (mdl.HasIntercept) icol = find (strcmp (mdl.CoefficientNames, '(Intercept)')); else icol = []; endif b = mdl.Coefficients.Estimate; V = mdl.CoefficientCovariance; se = mdl.Coefficients.SE; MSE = mdl.MSE; DFE = mdl.DFE; all_cols = 1:mdl.NumCoefficients; if (strcmpi (anovatype, 'summary')) is_nonlinear = false (nterm, 1); for k = 1:nterm parts = strsplit (term_name{k}, ':'); is_nonlinear(k) = (numel (parts) > 1) || ! isempty (strfind (parts{1}, '^')); endfor SumSq = [mdl.SST; mdl.SSR]; DF = [mdl.NumObservations - 1; mdl.NumCoefficients - mdl.HasIntercept]; RowNm = {'Total', 'Model'}; if (any (is_nonlinear)) nl_cols = cell2mat (term_cols(is_nonlinear)); SS_nl = MSE * cm_anova_qform (nl_cols, b, V); DF_nl = numel (nl_cols); SumSq = [SumSq; SumSq(2) - SS_nl; SS_nl]; DF = [DF; DF(2) - DF_nl; DF_nl]; RowNm = [RowNm, {'. Linear', '. Nonlinear'}]; endif SumSq = [SumSq; mdl.SSE]; DF = [DF; DFE]; RowNm = [RowNm, {'Residual'}]; MeanSq = SumSq ./ DF; F = NaN (numel (SumSq), 1); pValue = NaN (numel (SumSq), 1); for r = 2:(numel (SumSq) - 1) F(r) = MeanSq(r) / MSE; pValue(r) = betainc (DFE / (DFE + DF(r) * F(r)), DFE / 2, DF(r) / 2); endfor else use_seq = isnumeric (sstype) && sstype == 1; use_type3 = isnumeric (sstype) && sstype == 3; extended = ischar (sstype) && strcmpi (sstype, 'h'); if (use_type3) ## Deviation coding rewrites a categorical predictor's indicator ## block and leaves everything else alone, so with no categorical ## predictor the two designs are identical and the change of basis ## is the identity. Saying so avoids a synthetic design that ## cannot represent them: it carries one row per term degree, which ## is fewer rows than coefficients for an ordinary additive model, ## and gives every numeric predictor the same values scaled by a ## constant, which makes its numeric columns collinear whatever its ## height. ci_all = mdl.CatLevelInfo; has_cat = ! isempty (ci_all) && isfield (ci_all, 'names') ... && any (ismember (ci_all.names, mdl.PredictorNames)); if (! has_cat) Mm = eye (numel (mdl.CoefficientNames)); is_hier = true; else [Xsyn, Dsyn] = cm_anova_synthetic_design (mdl); Mm = Xsyn \ Dsyn; is_hier = (max (max (abs (Dsyn - Xsyn * Mm))) < 1e-8 * max (max (abs (Dsyn)))) ... && (rcond (Mm) > eps); endif if (! is_hier) error (strcat ("Cannot perform anova with type 3 sums of", ... " squares for a compacted model with missing", ... " lower-order terms.")); endif Hinv = inv (Mm); SumSq = zeros (nterm, 1); DF = zeros (nterm, 1); F = zeros (nterm, 1); for k = 1:nterm Hk = Hinv(term_cols{k}, :); HVH = Hk * V * Hk'; if (rcond (HVH) < eps (class (HVH))) SumSq(k) = 0; DF(k) = 0; F(k) = NaN; continue; endif Hb = Hk * b; DF(k) = numel (term_cols{k}); F(k) = (Hb' * (HVH \ Hb)) / DF(k); SumSq(k) = F(k) * DF(k) * MSE; endfor MeanSq = SumSq ./ DF; ok = DF > 0; pValue = NaN (nterm, 1); pValue(ok) = betainc (DFE ./ (DFE + DF(ok) .* F(ok)), DFE / 2, DF(ok) / 2); else contain_mx = cm_anova_containment (term_name, extended); SumSq = zeros (nterm, 1); DF = zeros (nterm, 1); for k = 1:nterm if (any (se(term_cols{k}) == 0)) SumSq(k) = 0; DF(k) = 0; continue; endif if (use_seq) cmp_cols = union (icol, cell2mat (term_cols(1:k-1))); else keep = true (1, nterm); keep(k) = false; for j = 1:nterm if (j != k && contain_mx(k, j)) keep(j) = false; endif endfor cmp_cols = union (icol, cell2mat (term_cols(keep))); endif full_cols = union (cmp_cols, term_cols{k}); excl_cols = setdiff (all_cols, full_cols); SumSq(k) = cm_anova_ss (term_cols{k}, excl_cols, b, V, MSE); DF(k) = numel (term_cols{k}); endfor MeanSq = SumSq ./ DF; F = MeanSq / MSE; ok = DF > 0; pValue = NaN (nterm, 1); pValue(ok) = betainc (DFE ./ (DFE + DF(ok) .* F(ok)), DFE / 2, DF(ok) / 2); endif SumSq = [SumSq; mdl.SSE]; DF = [DF; DFE]; MeanSq = [MeanSq; MSE]; F = [F; NaN]; pValue = [pValue; NaN]; RowNm = [term_name, {'Error'}]; endif tbl = table (SumSq, DF, MeanSq, F, pValue, ... 'VariableNames', {'SumSq', 'DF', 'MeanSq', 'F', 'pValue'}, ... 'RowNames', RowNm(:)); endfunction endmethods endclassdef ## Duplicated from LinearModel, since we don't have superclass support ## for Octave yet; kept as a separate, self-contained copy here instead ## of being shared across both class files. function [ax, mdl, args] = cm_plot_axes (this, rest) if (isscalar (this) && isgraphics (this, 'axes')) ax = this; mdl = rest{1}; args = rest(2:end); else ax = []; mdl = this; args = rest; endif endfunction ## Duplicated from LinearModel, since we don't have superclass support ## for Octave yet; kept as a separate, self-contained copy here instead ## of being shared across both class files. function [term_name, term_cols] = cm_anova_term_groups (coef_names, cat_info, has_intercept) dummy_names = {}; dummy_bases = {}; for ci = 1:numel (cat_info.names) base_nm = cat_info.names{ci}; levels_c = cat_info.levels{ci}; for L = 1:numel (levels_c) dummy_names{end+1} = [base_nm, '_', char(levels_c{L})]; dummy_bases{end+1} = base_nm; endfor endfor if (has_intercept) orig_idx = find (! strcmp (coef_names, '(Intercept)')); else orig_idx = 1:numel (coef_names); endif non_int = coef_names(orig_idx); term_name = {}; term_cols = {}; for t = 1:numel (non_int) factors_t = strsplit (non_int{t}, ':'); for f = 1:numel (factors_t) idx = find (strcmp (dummy_names, factors_t{f}), 1); if (! isempty (idx)) factors_t{f} = dummy_bases{idx}; endif endfor nm = strjoin (factors_t, ':'); k = find (strcmp (term_name, nm), 1); if (isempty (k)) term_name{end+1} = nm; term_cols{end+1} = orig_idx(t); else term_cols{k}(end+1) = orig_idx(t); endif endfor endfunction ## Duplicated from LinearModel, since we don't have superclass support ## for Octave yet; kept as a separate, self-contained copy here instead ## of being shared across both class files. function contain_mx = cm_anova_containment (term_name, extended) nterm = numel (term_name); factor_list = cell (nterm, 1); for k = 1:nterm parts = strsplit (term_name{k}, ':'); fl = struct ('var', {}, 'exp', {}); for f = 1:numel (parts) p = strsplit (parts{f}, '^'); if (numel (p) == 2) fl(end+1) = struct ('var', p{1}, 'exp', str2double (p{2})); else fl(end+1) = struct ('var', p{1}, 'exp', 1); endif endfor factor_list{k} = fl; endfor contain_mx = false (nterm, nterm); for i = 1:nterm for j = 1:nterm if (i == j) continue; endif fi = factor_list{i}; fj = factor_list{j}; ok = true; for f = 1:numel (fi) match = false; for g = 1:numel (fj) if (strcmp (fi(f).var, fj(g).var)) if (extended) match = (fj(g).exp >= fi(f).exp); else match = (fj(g).exp == fi(f).exp); endif if (match) break; endif endif endfor if (! match) ok = false; break; endif endfor contain_mx(i, j) = ok; endfor endfor endfunction ## Duplicated from LinearModel, since we don't have superclass support ## for Octave yet; kept as a separate, self-contained copy here instead ## of being shared across both class files. function q = cm_anova_qform (cols, b, V) if (isempty (cols)) q = 0; else bc = b(cols); Vc = V(cols, cols); q = bc' * (Vc \ bc); endif endfunction ## Duplicated from LinearModel, since we don't have superclass support ## for Octave yet; kept as a separate, self-contained copy here instead ## of being shared across both class files. function SumSq = cm_anova_ss (term_cols_k, excl_cols, b, V, MSE) full_cols = [excl_cols, term_cols_k]; SumSq = MSE * (cm_anova_qform (full_cols, b, V) - cm_anova_qform (excl_cols, b, V)); endfunction ## Duplicated from LinearModel, since we don't have superclass support ## for Octave yet; kept as a separate, self-contained copy here instead ## of being shared across both class files. function X_dev = cm_deviation_encode (X_enc, pred_names, cat_info) X_dev = X_enc; enc_pos = 0; for j = 1:numel (pred_names) ci = find (strcmp (cat_info.names, pred_names{j})); if (isempty (ci)) enc_pos = enc_pos + 1; else n_lev = numel (cat_info.levels{ci}); block = enc_pos + (1:(n_lev - 1)); is_ref = 1 - sum (X_dev(:, block), 2); X_dev(:, block) = X_dev(:, block) - is_ref; enc_pos = enc_pos + n_lev - 1; endif endfor endfunction ## Duplicated from LinearModel, since we don't have superclass support ## for Octave yet; kept as a separate, self-contained copy here instead ## of being shared across both class files. function [Xsyn, Dsyn] = cm_anova_synthetic_design (mdl) pred_names = mdl.PredictorNames; cat_info = mdl.CatLevelInfo; p_raw = mdl.NumPredictors; cat_logical = false (1, p_raw); cat_levels = cell (1, p_raw); for j = 1:p_raw ci = find (strcmp (cat_info.names, pred_names{j})); if (! isempty (ci)) cat_logical(j) = true; cat_levels{j} = cat_info.levels{ci}; else cat_levels{j} = {}; endif endfor cat_idx = find (cat_logical); n_cat = numel (cat_idx); if (n_cat == 0) cat_rows = 1; cat_combo = zeros (1, 0); else lev_counts = zeros (1, n_cat); for c = 1:n_cat lev_counts(c) = numel (cat_levels{cat_idx(c)}); endfor cat_rows = prod (lev_counts); cat_combo = zeros (cat_rows, n_cat); rep_inner = 1; for c = 1:n_cat pattern = repelem ((1:lev_counts(c))', rep_inner); cat_combo(:, c) = repmat (pattern, cat_rows / numel (pattern), 1); rep_inner = rep_inner * lev_counts(c); endfor endif num_idx = find (! cat_logical); n_num = numel (num_idx); R = max (2, max (mdl.TermsMatrix(:)) + 1); ## Cross the numeric predictors as a full factorial of R levels each, R ## being one more than the highest degree any term raises them to. Giving ## them all the same values scaled by a constant instead would leave their ## columns exact multiples of one another, and the design rank deficient ## however many rows it had. if (n_num == 0) num_grid = zeros (1, 0); else num_grid = zeros (R ^ n_num, n_num); rep_inner = 1; for k = 1:n_num pattern = repelem ((1:R)', rep_inner); num_grid(:, k) = repmat (pattern, rows (num_grid) / numel (pattern), 1); rep_inner = rep_inner * R; endfor endif num_rows = rows (num_grid); n_syn = cat_rows * num_rows; X_num_syn = zeros (n_syn, p_raw); for a = 1:num_rows rows_a = (a - 1) * cat_rows + (1:cat_rows); X_num_syn(rows_a, cat_idx) = cat_combo; if (n_num > 0) X_num_syn(rows_a, num_idx) = repmat (num_grid(a,:), cat_rows, 1); endif endfor X_enc_syn = encode_categorical (X_num_syn, cat_logical, pred_names, cat_levels); X_dev_syn = cm_deviation_encode (X_enc_syn, pred_names, cat_info); Xsyn = build_design (mdl.TermsMatrix, X_enc_syn); Dsyn = build_design (mdl.TermsMatrix, X_dev_syn); endfunction %!shared mdl, cmdl, X, y, n %! n = 20; %! X = [(1:n); (1:n).^2]' / n; %! y = X * [3; -1] + 0.2 * sin ((1:n)'); %! mdl = fitlm (X, y); %! cmdl = compact (mdl); %!test %! assert_equal (cmdl.NumObservations, 20); %! assert_equal (cmdl.NumCoefficients, 3); %! assert_equal (cmdl.NumVariables, 3); %! assert_equal (cmdl.NumPredictors, 2); %! assert_equal (cmdl.NumEstimatedCoefficients, 3); %! assert_equal (cmdl.DFE, 17); %! assert_equal (cmdl.SSE, 0.386545331386823, 1e-9); %! assert_equal (cmdl.SSR, 583.523874670959, 1e-6); %! assert_equal (cmdl.SST, 583.910420002346, 1e-6); %! assert_equal (cmdl.MSE, 0.0227379606698351, 1e-10); %! assert_equal (cmdl.RMSE, 0.150791116017606, 1e-10); %! assert_equal (cmdl.Rsquared.Ordinary, 0.999338005765704, 1e-10); %! assert_equal (cmdl.Rsquared.Adjusted, 0.999260124091081, 1e-10); %! assert_equal (cmdl.LogLikelihood, 11.0836133807695, 1e-6); %! assert_equal (cmdl.ModelCriterion.AIC, -16.1672267615389, 1e-6); %! assert_equal (cmdl.ModelCriterion.AICc, -14.6672267615389, 1e-6); %! assert_equal (cmdl.ModelCriterion.BIC, -13.180029940877, 1e-6); %! assert_equal (cmdl.ModelCriterion.CAIC, -10.180029940877, 1e-6); %!test %! assert_equal (cmdl.Coefficients.Estimate, [0.1161886778; 2.508451491; -0.9788353298], 1e-7); %! assert_equal (cmdl.Coefficients.SE, [0.112185831; 0.4920818186; 0.02276108523], 1e-8); %! assert_equal (cmdl.Coefficients.tStat, [1.035680502; 5.097630913; -43.00477415], 1e-6); %! assert_equal (all (cmdl.Coefficients.pValue >= 0 & cmdl.Coefficients.pValue <= 1), true); %! assert_equal (isequal (cmdl.CoefficientNames, {'(Intercept)', 'x1', 'x2'}), true); %! assert_equal (isequal (cmdl.CoefficientNames, cmdl.Coefficients.Properties.RowNames(:)'), true); %! assert_equal (size (cmdl.CoefficientCovariance), [3, 3]); %! assert_equal (diag (cmdl.CoefficientCovariance), [0.0125857; 0.242145; 0.000518067], 1e-6); %! assert_equal (width (cmdl.Coefficients), 4); %! assert_equal (isequal (cmdl.Coefficients.Properties.VariableNames, ... %! {'Estimate','SE','tStat','pValue'}), true); %!test %! assert_equal (cmdl.Formula.LinearPredictor, '1 + x1 + x2'); %! assert_equal (cmdl.Formula.HasIntercept, true); %! assert_equal (cmdl.PredictorNames, {'x1'; 'x2'}); %! assert_equal (cmdl.ResponseName, 'y'); %! assert_equal (cmdl.VariableNames, {'x1'; 'x2'; 'y'}); %! assert_equal (cmdl.VariableInfo.Range{1}, [0.05, 1], 1e-10); %! assert_equal (cmdl.VariableInfo.Range{2}, [0.05, 20], 1e-10); %! assert_equal (cmdl.VariableInfo.InModel, [true; true; false]); %! assert_equal (cmdl.Robust, []); %!test %! ci = coefCI (cmdl); %! assert_equal (size (ci), [3, 2]); %! assert_equal (class (ci), 'double'); %! assert_equal (all (ci(:,1) < ci(:,2)), true); %! assert_equal (ci(1,1), -0.120502736154050, 1e-10); %! assert_equal (ci(1,2), 0.352880091734465, 1e-10); %! assert_equal (ci(2,1), 1.470249604061007, 1e-10); %! assert_equal (ci(2,2), 3.546653377080718, 1e-10); %! assert_equal (ci(3,1), -1.026857022014626, 1e-10); %! assert_equal (ci(3,2), -0.930813637635746, 1e-10); %!test %! ci = coefCI (cmdl); %! t = tinv (0.975, cmdl.DFE); %! assert_equal ((ci(:,1) + ci(:,2)) / 2, cmdl.Coefficients.Estimate, 1e-10); %! assert_equal (ci(:,2) - ci(:,1), 2 * t * cmdl.Coefficients.SE, 1e-10); %! assert_equal (coefCI (cmdl, 0.05), ci); %!test %! ci = coefCI (cmdl, 0.01); %! assert_equal (size (ci), [3, 2]); %! assert_equal (ci(1,1), -0.208951721610638, 1e-10); %! assert_equal (ci(1,2), 0.441329077191052, 1e-10); %! assert_equal (ci(2,1), 1.082284945644892, 1e-10); %! assert_equal (ci(2,2), 3.934618035496833, 1e-10); %! assert_equal (ci(3,1), -1.044802201703589, 1e-10); %! assert_equal (ci(3,2), -0.912868457946783, 1e-10); %!test %! ## a zero alpha gives an infinite interval and a full alpha collapses it to the estimate %! ci = coefCI (cmdl, 0); %! assert_equal (all (ci(:,1) == -Inf), true); %! assert_equal (all (ci(:,2) == +Inf), true); %! ci = coefCI (cmdl, 1); %! assert_equal (ci(:,1), cmdl.Coefficients.Estimate, 1e-10); %! assert_equal (ci(:,2), cmdl.Coefficients.Estimate, 1e-10); %!test %! m = fitlm (X, y, 'Intercept', false); %! cm = compact (m); %! ci = coefCI (cm); %! assert_equal (size (ci), [2, 2]); %! assert_equal (ci(1,1), 2.486679110991696, 1e-10); %! assert_equal (ci(1,2), 3.436164115360526, 1e-10); %! assert_equal (ci(2,1), -1.027166590567854, 1e-10); %! assert_equal (ci(2,2), -0.967330908318718, 1e-10); %!test %! m = fitlm (X, y, 'Weights', (1:n)' / sum (1:n)); %! cm = compact (m); %! ci = coefCI (cm); %! assert_equal (size (ci), [3, 2]); %! assert_equal (ci(1,1), -0.355978167660141, 1e-10); %! assert_equal (ci(1,2), 0.516619434992026, 1e-10); %! assert_equal (ci(2,1), 1.142016390035618, 1e-10); %! assert_equal (ci(2,2), 4.154555017558383, 1e-10); %! assert_equal (ci(3,1), -1.044530853341675, 1e-10); %! assert_equal (ci(3,2), -0.924508441530335, 1e-10); %!test %! m = fitlm ([ones(n,1), X, X(:,1)+X(:,2)], y); %! cm = compact (m); %! ci = coefCI (cm); %! drop = find (cm.Coefficients.SE == 0); %! keep = setdiff (1:5, drop'); %! assert_equal (size (ci), [5, 2]); %! assert_equal (numel (drop), 2); %! assert_equal (all (all (ci(drop, :) == 0)), true); %! assert_equal (all (all (isfinite (ci(keep, :)))), true); %! assert_equal ((ci(keep,1) + ci(keep,2)) / 2, cm.Coefficients.Estimate(keep), 1e-10); %! assert_equal (cm.DFE, 17); %! assert_equal (m.SSE, 0.386545331386824, 1e-10); %! assert_equal (m.Rsquared.Ordinary, 0.999338005765704, 1e-10); %! assert_equal (m.Rsquared.Adjusted, 0.999260124091081, 1e-10); %!test %! m = fitlm ([1;1;1;2;2;2;3;3;3], [2.1;2.3;1.9;4.1;3.9;4.2;6.3;5.8;6.1], ... %! 'linear', 'CategoricalVars', 1); %! cm = compact (m); %! ci = coefCI (cm); %! assert_equal (size (ci), [3, 2]); %! assert_equal (ci(1,1), 1.809712563216694, 1e-10); %! assert_equal (ci(1,2), 2.390287436783304, 1e-10); %! assert_equal (ci(2,1), 1.556138236581195, 1e-10); %! assert_equal (ci(2,2), 2.377195096752140, 1e-10); %! assert_equal (ci(3,1), 3.556138236581195, 1e-10); %! assert_equal (ci(3,2), 4.377195096752140, 1e-10); %!test %! m = fitlm (X, y, 'RobustOpts', 'bisquare'); %! cm = compact (m); %! ci = coefCI (cm); %! assert_equal (cm.DFE, 17); %! assert_equal (ci(1,1), -0.136385388374896, 1e-10); %! assert_equal (ci(1,2), 0.378422288262478, 1e-10); %! assert_equal (ci(2,1), 1.359092508160098, 1e-10); %! assert_equal (ci(2,2), 3.617198504352038, 1e-10); %! assert_equal (ci(3,1), -1.030210688187146, 1e-10); %! assert_equal (ci(3,2), -0.925762726293341, 1e-10); %! ci = coefCI (cm, 0.1); %! assert_equal (ci(1,1), -0.091218796364050, 1e-10); %! assert_equal (ci(1,2), 0.333255696251631, 1e-10); %! assert_equal (ci(2,1), 1.557207176755238, 1e-10); %! assert_equal (ci(2,2), 3.419083835756898, 1e-10); %! assert_equal (ci(3,1), -1.021046958321974, 1e-10); %! assert_equal (ci(3,2), -0.934926456158514, 1e-10); %!test %! m = fitlm (X, y, 'constant'); %! cm = compact (m); %! ci = coefCI (cm); %! t = tinv (0.975, cm.DFE); %! assert_equal (size (ci), [1, 2]); %! assert_equal (ci(1,1), -8.184528886493887, 1e-10); %! assert_equal (ci(1,2), -2.995506675817716, 1e-10); %! assert_equal ((ci(1,1) + ci(1,2)) / 2, cm.Coefficients.Estimate, 1e-10); %! assert_equal (ci(1,2) - ci(1,1), 2 * t * cm.Coefficients.SE, 1e-10); %!test %! [p, F, r] = coefTest (cmdl); %! assert_equal (size (p), [1, 1]); %! assert_equal (class (p), 'double'); %! assert_equal (p >= 0 && p <= 1, true); %! assert_equal (F >= 0, true); %! assert_equal (p, 9.489880832170599e-28, -1e-8); %! assert_equal (F, 1.283149098426142e+04, -1e-8); %! assert_equal (r, 2); %!test %! k = cmdl.NumCoefficients; %! H = [zeros(k-1, 1), eye(k-1)]; %! [p, F, r] = coefTest (cmdl, H); %! assert_equal (p, 9.489880832170599e-28, -1e-8); %! assert_equal (F, 1.283149098426142e+04, -1e-8); %! assert_equal (r, 2); %!test %! [p, F, r] = coefTest (cmdl, [1 0 0]); %! assert_equal (size (r), [1, 1]); %! assert_equal (p, 0.314859866747774, -1e-8); %! assert_equal (F, 1.072634101844537, -1e-8); %! assert_equal (r, 1); %! [p, F, r] = coefTest (cmdl, [0 1 0]); %! assert_equal (p, 8.937794169018252e-05, -1e-8); %! assert_equal (F, 25.985840929474932, -1e-8); %! assert_equal (r, 1); %! [p, F, r] = coefTest (cmdl, [0 0 1]); %! assert_equal (p, 8.656938305821102e-19, -1e-8); %! assert_equal (F, 1.849410599855684e+03, -1e-8); %! assert_equal (r, 1); %! [p, F, r] = coefTest (cmdl, [0 1 0; 0 0 1]); %! assert_equal (p, 9.489880832170599e-28, -1e-8); %! assert_equal (F, 1.283149098426142e+04, -1e-8); %! assert_equal (r, 2); %!test %! b = cmdl.Coefficients.Estimate; %! [p, F] = coefTest (cmdl, [0 1 0], b(2)); %! assert_equal (p, 1, 1e-10); %! assert_equal (F, 0, 1e-10); %! [p, F] = coefTest (cmdl, [0 1 0], 0); %! assert_equal (p, 8.937794169018252e-05, -1e-8); %! assert_equal (F, 25.985840929474932, -1e-8); %!test %! [p, F, r] = coefTest (cmdl, [0 1 0; 0 0 1], [1.5; -1.0]); %! assert_equal (p, 2.833788304242915e-09, -1e-8); %! assert_equal (F, 77.603887650386312, -1e-8); %! assert_equal (r, 2); %! [p, F] = coefTest (cmdl, [0 1 0], 1.5); %! assert_equal (p, 0.056184159363707, -1e-8); %! assert_equal (F, 4.199865537706047, -1e-8); %!test %! m = fitlm (X, y, 'Intercept', false); %! cm = compact (m); %! [p, F, r] = coefTest (cm); %! assert_equal (r, cm.NumCoefficients); %! assert_equal (p, 6.060655830723051e-32, -1e-8); %! assert_equal (F, 2.646694317541346e+04, -1e-8); %!test %! m = fitlm (X, y, 'interactions'); %! cm = compact (m); %! [p, F, r] = coefTest (cm); %! assert_equal (r, cm.NumCoefficients - 1); %! assert_equal (r != cm.NumPredictors, true); %! assert_equal (p, 1.164196605688161e-25, -1e-8); %! assert_equal (F, 8.107508574885546e+03, -1e-8); %!test %! m = fitlm (X, y, 'Weights', (1:n)' / sum (1:n)); %! cm = compact (m); %! [p, F, r] = coefTest (cm); %! assert_equal (p, 1.481920976389473e-27, -1e-8); %! assert_equal (F, 1.217557180481257e+04, -1e-8); %! assert_equal (r, 2); %!test %! m = fitlm ([1;1;1;2;2;2;3;3;3], [2.1;2.3;1.9;4.1;3.9;4.2;6.3;5.8;6.1], ... %! 'linear', 'CategoricalVars', 1); %! cm = compact (m); %! [p, F, r] = coefTest (cm); %! assert_equal (p, 1.197590680415813e-06, -1e-8); %! assert_equal (F, 2.795000000000035e+02, -1e-8); %! assert_equal (r, 2); %! [p, F] = coefTest (cm, [1 0 0]); %! assert_equal (p, 2.087464608380450e-06, -1e-8); %! assert_equal (F, 3.133421052631613e+02, -1e-8); %! [p, F] = coefTest (cm, [0 1 0]); %! assert_equal (p, 2.325514143662469e-05, -1e-8); %! assert_equal (F, 1.374078947368438e+02, -1e-8); %! [p, F] = coefTest (cm, [0 0 1]); %! assert_equal (p, 3.757733067786492e-07, -1e-8); %! assert_equal (F, 5.589868421052698e+02, -1e-8); %!test %! m = fitlm (X, y, 'constant'); %! cm = compact (m); %! [p, F, r] = coefTest (cm); %! assert_equal (p, 2.399364086950727e-04, -1e-8); %! assert_equal (F, 20.335916494750592, -1e-8); %! assert_equal (r, 1); %!test %! m = fitlm ([ones(n,1), X, X(:,1)+X(:,2)], y); %! cm = compact (m); %! [p, F] = coefTest (cm); %! assert_equal (size (p), [1, 1]); %! assert_equal (class (p), 'double'); %! assert_equal (isnan (p), true); %! assert_equal (isnan (F), true); %! drop = find (cm.Coefficients.SE == 0); %! keep = setdiff (2:cm.NumCoefficients, drop'); %! H = zeros (numel (keep), cm.NumCoefficients); %! for i = 1:numel (keep) %! H(i, keep(i)) = 1; %! endfor %! [p, F, r] = coefTest (cm, H); %! assert_equal (r, numel (keep)); %! assert_equal (p, 6.706570586430847e-30, -1e-8); %! assert_equal (F, 1.771618642634559e+04, -1e-8); %!test %! m = fitlm (X, y, 'RobustOpts', 'bisquare'); %! cm = compact (m); %! [p, F, r] = coefTest (cm); %! assert_equal (p, 3.941715170923545e-27, -1e-8); %! assert_equal (F, 1.085097669445008e+04, -1e-8); %! assert_equal (r, 2); %! [p, F, r] = coefTest (cm, [0 1 -1]); %! assert_equal (p, 9.729154060050210e-06, -1e-8); %! assert_equal (F, 38.417457909307693, -1e-8); %! assert_equal (r, 1); %!test %! yp = predict (cmdl, [0.5 0.25; 1.0 1.0; 0.2 0.04]); %! assert_equal (class (yp), 'double'); %! assert_equal (size (yp), [3, 1]); %! assert_equal (yp(1), 1.125705590619342, 1e-10); %! assert_equal (yp(2), 1.645804838535884, 1e-10); %! assert_equal (yp(3), 0.578725562711373, 1e-10); %!test %! [yp, yci] = predict (cmdl, [0.5 0.25; 1.0 1.0; 0.2 0.04]); %! assert_equal (size (yci), [3, 2]); %! assert_equal (all (yci(:,1) < yci(:,2)), true); %! assert_equal (yci(1,1), 0.810180780547058, 1e-9); %! assert_equal (yci(1,2), 1.441230400691626, 1e-9); %! assert_equal (yci(2,1), 0.858229321851332, 1e-9); %! assert_equal (yci(2,2), 2.433380355220436, 1e-9); %! assert_equal (yci(3,1), 0.470499753577336, 1e-9); %! assert_equal (yci(3,2), 0.686951371845409, 1e-9); %!test %! [~, yci] = predict (cmdl, [0.5 0.25; 1.0 1.0; 0.2 0.04], 'Alpha', 0.01); %! assert_equal (yci(1,1), 0.692272619569794, 1e-9); %! assert_equal (yci(1,2), 1.559138561668890, 1e-9); %! assert_equal (yci(2,1), 0.563920989071667, 1e-9); %! assert_equal (yci(2,2), 2.727688688000101, 1e-9); %! assert_equal (yci(3,1), 0.430056955680247, 1e-9); %! assert_equal (yci(3,2), 0.727394169742498, 1e-9); %!test %! [~, yci] = predict (cmdl, [0.5 0.25; 1.0 1.0; 0.2 0.04], 'Simultaneous', true); %! assert_equal (yci(1,1), 0.662572505689110, 1e-9); %! assert_equal (yci(1,2), 1.588838675549574, 1e-9); %! assert_equal (yci(2,1), 0.489787095987915, 1e-9); %! assert_equal (yci(2,2), 2.801822581083853, 1e-9); %! assert_equal (yci(3,1), 0.419869741383617, 1e-9); %! assert_equal (yci(3,2), 0.737581384039129, 1e-9); %!test %! [~, yci] = predict (cmdl, [0.5 0.25; 1.0 1.0; 0.2 0.04], 'Prediction', 'observation'); %! assert_equal (yci(1,1), 0.677632064105876, 1e-9); %! assert_equal (yci(1,2), 1.573779117132808, 1e-9); %! assert_equal (yci(2,1), 0.796399650258815, 1e-9); %! assert_equal (yci(2,2), 2.495210026812952, 1e-9); %! assert_equal (yci(3,1), 0.242679724835377, 1e-9); %! assert_equal (yci(3,2), 0.914771400587368, 1e-9); %!test %! [~, yci] = predict (cmdl, [0.5 0.25; 1.0 1.0; 0.2 0.04], ... %! 'Alpha', 0.1, 'Simultaneous', true, 'Prediction', 'observation'); %! assert_equal (yci(1,1), 0.551414812037842, 1e-9); %! assert_equal (yci(1,2), 1.699996369200842, 1e-9); %! assert_equal (yci(2,1), 0.557131801540151, 1e-9); %! assert_equal (yci(2,2), 2.734477875531617, 1e-9); %! assert_equal (yci(3,1), 0.148019407463707, 1e-9); %! assert_equal (yci(3,2), 1.009431717959039, 1e-9); %!test %! [yp, yci] = predict (cmdl, [0.5 0.25; NaN 1.0; 1.0 1.0]); %! assert_equal (isnan (yp(2)), true); %! assert_equal (all (isnan (yci(2,:))), true); %! assert_equal (yp(1), 1.125705590619342, 1e-10); %! assert_equal (yp(3), 1.645804838535884, 1e-10); %! assert_equal (yci(1,1), 0.810180780547058, 1e-9); %! assert_equal (yci(3,2), 2.433380355220436, 1e-9); %!test %! Xt = table (0.5, 0.25, 'VariableNames', {'x1', 'x2'}); %! yp = predict (cmdl, Xt); %! assert_equal (yp, 1.125705590619342, 1e-10); %!test %! m = fitlm ([1;1;1;2;2;2;3;3;3], [2.1;2.3;1.9;4.1;3.9;4.2;6.3;5.8;6.1], ... %! 'linear', 'CategoricalVars', 1); %! cm = compact (m); %! yp = predict (cm, table ([1;2;3], 'VariableNames', {'x1'})); %! assert_equal (yp(1), 2.099999999999999, 1e-10); %! assert_equal (yp(2), 4.066666666666666, 1e-10); %! assert_equal (yp(3), 6.066666666666666, 1e-10); %!test %! m = fitlm (X, y, 'Weights', (1:n)' / sum (1:n)); %! cm = compact (m); %! [yp, yci] = predict (cm, [0.5 0.25; 1.0 1.0; 0.2 0.04], 'Alpha', 0.05); %! assert_equal (yp(1), 1.158333573705442, 1e-10); %! assert_equal (yp(2), 1.744086690026939, 1e-10); %! assert_equal (yp(3), 0.570596988527903, 1e-10); %! assert_equal (yci(1,1), 0.802165170771357, 1e-9); %! assert_equal (yci(2,2), 2.788483587522537, 1e-9); %!test %! m = fitlm (X, y, 'RobustOpts', 'bisquare'); %! cm = compact (m); %! [yp, yci] = predict (cm, [0.5 0.25; 1.0 1.0; 0.2 0.04], 'Simultaneous', true, 'Alpha', 0.1); %! assert_equal (yp(1), 1.120594526261764, 1e-10); %! assert_equal (yp(2), 1.631177248959615, 1e-10); %! assert_equal (yp(3), 0.579528082905394, 1e-10); %! assert_equal (yci(1,1), 0.680801249227337, 1e-9); %! assert_equal (yci(2,2), 2.728936938016809, 1e-9); %!test %! m = fitlm (X, y, 'quadratic'); %! cm = compact (m); %! [yp, yci] = predict (cm, [0.5 0.25; 1.0 1.0; 0.2 0.04]); %! assert_equal (yp(1), -0.948865258803113, 1e-9); %! assert_equal (yp(2), -3.349087980348939, 1e-9); %! assert_equal (yp(3), -0.053659932832480, 1e-9); %! assert_equal (yci(1,1), -3.431959757763334, 1e-9); %! assert_equal (yci(1,2), 1.534229240157108, 1e-9); %!test %! ysim = random (cmdl, [0.5 0.25; 1.0 1.0]); %! assert_equal (size (ysim), [2, 1]); %! assert_equal (class (ysim), 'double'); %! assert_equal (iscolumn (ysim), true); %! ypred = predict (cmdl, [0.5 0.25; 1.0 1.0]); %! assert_equal (ypred(1), 1.125705590619342, 1e-10); %! assert_equal (ypred(2), 1.645804838535884, 1e-10); %! assert_equal (all (isfinite (ysim - ypred)), true); %!test %! assert_equal (size (random (cmdl, [0.5 0.25])), [1, 1]); %!test %! ysim = random (cmdl, [0.5 0.25; NaN 1.0; 1.0 1.0]); %! assert_equal (size (ysim), [3, 1]); %! assert_equal (isfinite (ysim(1)), true); %! assert_equal (isnan (ysim(2)), true); %! assert_equal (isfinite (ysim(3)), true); %!test %! ya = random (cmdl, [0.5 0.25]); %! yb = random (cmdl, [0.5 0.25]); %! assert_equal (isequal (ya, yb), false); %!test %! Xt = table (0.5, 0.25, 'VariableNames', {'x1', 'x2'}); %! assert_equal (size (random (cmdl, Xt)), [1, 1]); %! assert_equal (all (isfinite (random (cmdl, Xt))), true); %! ysim = random (cmdl, X); %! assert_equal (size (ysim), [20, 1]); %! assert_equal (sum (isnan (ysim)), 0); %!test %! mw = compact (fitlm (X, y, 'Weights', (1:n)' / sum (1:n))); %! mni = compact (fitlm (X, y, 'Intercept', false)); %! assert_equal (all (isfinite (random (mw, [0.5 0.25; 1.0 1.0]))), true); %! assert_equal (all (isfinite (random (mni, [0.5 0.25; 1.0 1.0]))), true); %!test %! yf = feval (cmdl, [0.5 0.25; 1.0 1.0; 0.2 0.04]); %! assert_equal (yf(1), 1.125705590619342, 1e-10); %! assert_equal (yf(2), 1.645804838535884, 1e-10); %! assert_equal (yf(3), 0.578725562711373, 1e-10); %! assert_equal (feval (cmdl, [0.5; 1.0; 0.2], [0.25; 1.0; 0.04]), yf, 1e-10); %!test %! yf3 = feval (cmdl, [0.5, 1.0, 0.2], [0.25, 1.0, 0.04]); %! assert_equal (size (yf3), [1, 3]); %! assert_equal (yf3(1), 1.125705590619342, 1e-10); %! assert_equal (yf3(2), 1.645804838535884, 1e-10); %! assert_equal (yf3(3), 0.578725562711373, 1e-10); %!test %! assert_equal (feval (cmdl, 0.5, 0.25), 1.125705590619342, 1e-10); %! yf5 = feval (cmdl, 0.5, [0.1; 0.2; 0.3]); %! assert_equal (yf5(1), 1.272530890093120, 1e-10); %! assert_equal (yf5(2), 1.174647357110602, 1e-10); %! assert_equal (yf5(3), 1.076763824128083, 1e-10); %! yf6 = feval (cmdl, [0.1; 0.5; 0.9], 0.25); %! assert_equal (yf6(1), 0.122324994390997, 1e-10); %! assert_equal (yf6(2), 1.125705590619342, 1e-10); %! assert_equal (yf6(3), 2.129086186847688, 1e-10); %!test %! ms = compact (fitlm (X(:,1), y)); %! assert_equal (size (feval (ms, 0.5)), [1, 1]); %! assert_equal (size (feval (ms, [0.3; 0.5; 0.9])), [3, 1]); %! assert_equal (feval (ms, 0.5), predict (ms, 0.5), 1e-10); %! assert_equal (feval (ms, [0.3; 0.5; 0.9]), predict (ms, [0.3; 0.5; 0.9]), 1e-10); %! yf14 = feval (ms, [1; 2; 3]); %! assert_equal (yf14(1), -14.162385738140875, 1e-9); %! assert_equal (yf14(2), -32.209476173898921, 1e-9); %! assert_equal (yf14(3), -50.256566609656964, 1e-9); %!test %! Xt = table (0.5, 0.25, 'VariableNames', {'x1', 'x2'}); %! assert_equal (feval (cmdl, Xt), 1.125705590619342, 1e-10); %!test %! yf9 = feval (cmdl, [0.5 0.25; NaN 1.0; 1.0 1.0]); %! assert_equal (isnan (yf9(2)), true); %! assert_equal (yf9(1), 1.125705590619342, 1e-10); %! assert_equal (yf9(3), 1.645804838535884, 1e-10); %! yf10 = feval (cmdl, [0.5; NaN; 1.0], [0.25; 1.0; 1.0]); %! assert_equal (isnan (yf10(2)), true); %! yf11 = feval (cmdl, [0.5; 1.0; 1.0], [0.25; NaN; 1.0]); %! assert_equal (isnan (yf11(2)), true); %!test %! yf12 = feval (cmdl, X); %! assert_equal (size (yf12), [20, 1]); %! assert_equal (sum (isnan (yf12)), 0); %!test %! mw = compact (fitlm (X, y, 'Weights', (1:n)' / sum (1:n))); %! yfw = feval (mw, [0.5 0.25; 1.0 1.0]); %! assert_equal (yfw(1), 1.158333573705442, 1e-10); %! assert_equal (yfw(2), 1.744086690026939, 1e-10); %! assert_equal (feval (mw, [0.5; 1.0], [0.25; 1.0]), yfw, 1e-10); %!test %! Weight = [2000;2100;2200;2300;2400;2500;2600;2700;2800;2900;3000; ... %! 3100;3200;3300;3400;3500;3600;3700;3800;3900]; %! Year = categorical ([70;70;70;70;70;76;76;76;76;76;76;76;82;82; ... %! 82;82;82;82;82;82]); %! MPG = [30;29;28;27;26;25;24;23;22;21;20;19;18;17;16;15;14;13;12;11]; %! m = fitlm (table (MPG, Weight, Year), 'MPG ~ Weight + Year'); %! cm = compact (m); %! yf = feval (cm, [2500;3000], '76'); %! assert_equal (yf(1), 25.000000000000000, 1e-9); %! assert_equal (yf(2), 20.000000000000004, 1e-9); %! assert_equal (yf, feval (m, [2500;3000], '76'), 1e-10); %! yf2 = feval (cm, [2500;3000], categorical (70)); %! assert_equal (yf2(1), 24.999999999999996, 1e-9); %! assert_equal (yf2(2), 20.000000000000000, 1e-9); %! assert_equal (feval (cm, 2800, '82'), 21.999999999999996, 1e-9); %! assert_equal (isnan (feval (cm, 2500, '99')), true); %!test %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotEffects (ax, cmdl); %! xd1 = get (h(1), 'XData'); %! yd1 = get (h(1), 'YData'); %! xd2 = get (h(2), 'XData'); %! yd2 = get (h(2), 'YData'); %! xd3 = get (h(3), 'XData'); %! yd3 = get (h(3), 'YData'); %! ytl = get (ax, 'YTickLabel'); %! assert_equal (numel (h), 3); %! assert_equal (xd1(1), 2.38302891604232, -1e-10); %! assert_equal (xd1(2), -19.5277648300125, -1e-10); %! assert_equal (yd1, [1 2]); %! assert_equal (xd2(1), 1.39673712385796, -1e-10); %! assert_equal (xd2(2), 3.36932070822668, -1e-10); %! assert_equal (yd2, [1 1]); %! assert_equal (xd3(1), -20.4857975891918, -1e-10); %! assert_equal (xd3(2), -18.5697320708331, -1e-10); %! assert_equal (yd3, [2 2]); %! assert_equal (get (h(1), 'Color'), [0.1490 0.5490 0.8660], 1e-4); %! assert_equal (get (h(2), 'Color'), [0.1490 0.5490 0.8660], 1e-4); %! assert_equal (get (h(3), 'Color'), [0.1490 0.5490 0.8660], 1e-4); %! assert_equal (get (h(1), 'Marker'), 'o'); %! assert_equal (get (h(1), 'LineStyle'), 'none'); %! assert_equal (get (h(2), 'LineStyle'), '-'); %! assert_equal (get (h(2), 'Marker'), 'none'); %! assert_equal (get (h(3), 'LineStyle'), '-'); %! assert_equal (get (h(3), 'Marker'), 'none'); %! assert_equal (mean (xd2), xd1(1), 1e-10); %! assert_equal (mean (xd3), xd1(2), 1e-10); %! assert_equal (get (get (ax, 'xlabel'), 'string'), 'Main Effect'); %! assert_equal (get (get (ax, 'ylabel'), 'string'), ''); %! assert_equal (get (get (ax, 'title'), 'string'), 'Main Effects Plot'); %! assert_equal (get (ax, 'YTick'), [1 2]); %! assert_equal (ytl{1}, 'x1: 0.05 to 1'); %! assert_equal (ytl{2}, 'x2: 0.05 to 20'); %! close (fig); %!test %! ## 3-predictor model %! X3 = [X, sin((1:n)' * pi / n)]; %! y3 = X3 * [3; -1; 2] + 0.1 * cos ((1:n)' * pi / 7); %! cm3 = compact (fitlm (X3, y3)); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotEffects (ax, cm3); %! xd1 = get (h(1), 'XData'); %! yd1 = get (h(1), 'YData'); %! xd2 = get (h(2), 'XData'); %! yd2 = get (h(2), 'YData'); %! xd3 = get (h(3), 'XData'); %! yd3 = get (h(3), 'YData'); %! xd4 = get (h(4), 'XData'); %! yd4 = get (h(4), 'YData'); %! ytl = get (ax, 'YTickLabel'); %! assert_equal (numel (h), 4); %! assert_equal (xd1(1), 8.10687671732127, -1e-10); %! assert_equal (xd1(2), -25.4487243632125, -1e-10); %! assert_equal (xd1(3), 0.661302203942261, -1e-10); %! assert_equal (yd1, [1 2 3]); %! assert_equal (xd2(1), 0.565266595687836, -1e-10); %! assert_equal (xd2(2), 15.6484868389547, -1e-10); %! assert_equal (yd2, [1 1]); %! assert_equal (xd3(1), -33.3368582824351, -1e-10); %! assert_equal (xd3(2), -17.5605904439899, -1e-10); %! assert_equal (yd3, [2 2]); %! assert_equal (xd4(1), -1.25582490831999, -1e-10); %! assert_equal (xd4(2), 2.57842931620451, -1e-10); %! assert_equal (yd4, [3 3]); %! assert_equal (get (ax, 'YTick'), [1 2 3]); %! assert_equal (ytl{1}, 'x1: 0.05 to 1'); %! assert_equal (ytl{2}, 'x2: 0.05 to 20'); %! assert_equal (ytl{3}, 'x3: 1.22465e-16 to 1'); %! assert_equal (mean (xd2), xd1(1), 1e-10); %! assert_equal (mean (xd3), xd1(2), 1e-10); %! assert_equal (mean (xd4), xd1(3), 1e-10); %! close (fig); %!test %! cme = compact (fitlm (X, y, 'Exclude', [2, 7])); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotEffects (ax, cme); %! xd1 = get (h(1), 'XData'); %! yd1 = get (h(1), 'YData'); %! xd2 = get (h(2), 'XData'); %! yd2 = get (h(2), 'YData'); %! xd3 = get (h(3), 'XData'); %! yd3 = get (h(3), 'YData'); %! ytl = get (ax, 'YTickLabel'); %! assert_equal (numel (h), 3); %! assert_equal (xd1(1), 2.50035744908398, -1e-10); %! assert_equal (xd1(2), -19.5912988214488, -1e-10); %! assert_equal (yd1, [1 2]); %! assert_equal (xd2(1), 1.40421088339552, -1e-10); %! assert_equal (xd2(2), 3.59650401477245, -1e-10); %! assert_equal (yd2, [1 1]); %! assert_equal (xd3(1), -20.6333076647782, -1e-10); %! assert_equal (xd3(2), -18.5492899781194, -1e-10); %! assert_equal (yd3, [2 2]); %! assert_equal (ytl{1}, 'x1: 0.05 to 1'); %! assert_equal (ytl{2}, 'x2: 0.05 to 20'); %! assert_equal (mean (xd2), xd1(1), 1e-10); %! assert_equal (mean (xd3), xd1(2), 1e-10); %! close (fig); %!test %! cmw = compact (fitlm (X, y, 'Weights', (1:n)' / sum (1:n))); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotEffects (ax, cmw); %! xd1 = get (h(1), 'XData'); %! yd1 = get (h(1), 'YData'); %! xd2 = get (h(2), 'XData'); %! yd2 = get (h(2), 'YData'); %! xd3 = get (h(3), 'XData'); %! yd3 = get (h(3), 'YData'); %! ytl = get (ax, 'YTickLabel'); %! assert_equal (numel (h), 3); %! assert_equal (xd1(1), 2.51587141860715, -1e-10); %! assert_equal (xd1(2), -19.6411669663483, -1e-10); %! assert_equal (yd1, [1 2]); %! assert_equal (xd2(1), 1.08491557053384, -1e-10); %! assert_equal (xd2(2), 3.94682726668046, -1e-10); %! assert_equal (yd2, [1 1]); %! assert_equal (xd3(1), -20.8383905241664, -1e-10); %! assert_equal (xd3(2), -18.4439434085302, -1e-10); %! assert_equal (yd3, [2 2]); %! assert_equal (ytl{1}, 'x1: 0.05 to 1'); %! assert_equal (ytl{2}, 'x2: 0.05 to 20'); %! assert_equal (mean (xd2), xd1(1), 1e-10); %! assert_equal (mean (xd3), xd1(2), 1e-10); %! close (fig); %!test %! cmni = compact (fitlm (X, y, 'Intercept', false)); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotEffects (ax, cmni); %! xd1 = get (h(1), 'XData'); %! yd1 = get (h(1), 'YData'); %! xd2 = get (h(2), 'XData'); %! yd2 = get (h(2), 'YData'); %! xd3 = get (h(3), 'XData'); %! yd3 = get (h(3), 'YData'); %! ytl = get (ax, 'YTickLabel'); %! assert_equal (numel (h), 3); %! assert_equal (xd1(1), 2.81335053251731, -1e-10); %! assert_equal (xd1(2), -19.8951125513936, -1e-10); %! assert_equal (yd1, [1 2]); %! assert_equal (xd2(1), 2.36234515544211, -1e-10); %! assert_equal (xd2(2), 3.26435590959250, -1e-10); %! assert_equal (yd2, [1 1]); %! assert_equal (xd3(1), -20.4919734818287, -1e-10); %! assert_equal (xd3(2), -19.2982516209584, -1e-10); %! assert_equal (yd3, [2 2]); %! assert_equal (ytl{1}, 'x1: 0.05 to 1'); %! assert_equal (ytl{2}, 'x2: 0.05 to 20'); %! assert_equal (mean (xd2), xd1(1), 1e-10); %! assert_equal (mean (xd3), xd1(2), 1e-10); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotEffects (ax, cmdl); %! assert_equal (isequal (get (h(1), 'Parent'), ax), true); %! assert_equal (get (h(1), 'XData'), [2.38302891604232, -19.5277648300125], -1e-10); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! h = plotEffects (cmdl); %! assert_equal (isequal (get (h(1), 'Parent'), gca ()), true); %! assert_equal (get (h(1), 'XData'), [2.38302891604232, -19.5277648300125], -1e-10); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! h1 = plotEffects (mdl); %! h2 = plotEffects (cmdl); %! assert_equal (get (h1(1), 'XData'), get (h2(1), 'XData'), 1e-10); %! assert_equal (get (h1(2), 'XData'), get (h2(2), 'XData'), 1e-10); %! assert_equal (get (h1(3), 'XData'), get (h2(3), 'XData'), 1e-10); %! close (fig); %!test %! ## continuous by continuous, effects mode %! cmi = compact (fitlm (X, y, 'y ~ x1*x2')); %! fig = figure ('visible', 'off'); %! h = plotInteraction (cmi, 'x1', 'x2'); %! assert_equal (numel (h), 11); %! assert_equal (get (h(1), 'XData'), [1.76843380852813, -18.8740348676059], 1e-9); %! assert_equal (get (h(1), 'YData'), [1, 7]); %! assert_equal (get (h(2), 'XData'), [-2.25617028020123, 5.79303789725749], 1e-9); %! assert_equal (get (h(3), 'XData'), [-23.1321080641968, -14.615961671015], 1e-9); %! assert_equal (get (h(4), 'XData'), ... %! [1.97967308209488, 1.68393809910143, 1.38820311610799], 1e-9); %! assert_equal (get (h(4), 'YData'), [2, 3, 4]); %! assert_equal (get (h(5), 'XData'), [-0.771057026434185, 4.73040319062394], 1e-9); %! assert_equal (get (h(6), 'XData'), [-2.86059582662229, 6.22847202482516], 1e-9); %! assert_equal (get (h(7), 'XData'), [-4.99614754441031, 7.77255377662629], 1e-9); %! assert_equal (get (h(8), 'XData'), ... %! [-18.5782998846125, -18.8740348676059, -19.1697698505994], 1e-9); %! assert_equal (get (h(8), 'YData'), [8, 9, 10]); %! assert_equal (get (h(9), 'XData'), [-24.674318784563, -12.4822809846619], 1e-9); %! assert_equal (get (h(10), 'XData'), [-23.1321080641968, -14.615961671015], 1e-9); %! assert_equal (get (h(11), 'XData'), [-21.6439971135684, -16.6955425876303], 1e-9); %! assert_equal (get (h(1), 'Tag'), 'main'); %! assert_equal (get (h(4), 'Tag'), 'conditional1'); %! assert_equal (get (h(8), 'Tag'), 'conditional2'); %! ax = gca (); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Interaction of x1 and x2'); %! assert_equal (get (get (ax, 'XLabel'), 'String'), 'Effect'); %! assert_equal (get (ax, 'YTick'), [1, 2, 3, 4, 7, 8, 9, 10]); %! assert_equal (get (ax, 'YTickLabel'), ... %! {'x1: 0.05 to 1'; 'x2=0.05'; 'x2=10.025'; 'x2=20'; ... %! 'x2: 0.05 to 20'; 'x1=0.05'; 'x1=0.525'; 'x1=1'}); %! assert_equal (get (ax, 'YLim'), [0.5, 10.5]); %! close (fig); %!test %! ## continuous by continuous, predictions mode %! cmi = compact (fitlm (X, y, 'y ~ x1*x2')); %! fig = figure ('visible', 'off'); %! h = plotInteraction (cmi, 'x1', 'x2', 'predictions'); %! assert_equal (numel (h), 3); %! xd = get (h(1), 'XData'); %! assert_equal (numel (xd), 101); %! assert_equal (xd(1:3), [0.05, 0.2495, 0.449], 1e-9); %! assert_equal (xd(end-2:end), [19.601, 19.8005, 20], 1e-9); %! yd1 = get (h(1), 'YData'); %! assert_equal (yd1(1:3), ... %! [0.215349913094656, 0.0295669142485309, -0.156216084597594], 1e-9); %! assert_equal (yd1(end-2:end), ... %! [-17.9913839738256, -18.1771669726717, -18.3629499715178], 1e-9); %! yd2 = get (h(2), 'YData'); %! assert_equal (yd2(1:3), [1.20518645414209, 1.01644610546604, 0.827705756789976], 1e-9); %! assert_equal (yd2(end-2:end), ... %! [-17.2913677161117, -17.4801080647878, -17.6688484134638], 1e-9); %! yd3 = get (h(3), 'YData'); %! assert_equal (yd3(1:3), [2.19502299518953, 2.00332529668354, 1.81162759817755], 1e-9); %! assert_equal (yd3(end-2:end), ... %! [-16.5913514583978, -16.7830491569038, -16.9747468554098], 1e-9); %! assert_equal (get (h(1), 'DisplayName'), '0.05'); %! assert_equal (get (h(2), 'DisplayName'), '0.525'); %! assert_equal (get (h(3), 'DisplayName'), '1'); %! ax = gca (); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Interaction of x1 and x2'); %! assert_equal (get (get (ax, 'XLabel'), 'String'), 'x2'); %! assert_equal (get (get (ax, 'YLabel'), 'String'), 'Adjusted y'); %! close (fig); %!test %! ## swapping var1/var2 order swaps roles and title %! cmi = compact (fitlm (X, y, 'y ~ x1*x2')); %! fig = figure ('visible', 'off'); %! h = plotInteraction (cmi, 'x2', 'x1'); %! assert_equal (numel (h), 11); %! assert_equal (get (h(1), 'XData'), [-18.8740348676059, 1.76843380852813], 1e-9); %! assert_equal (get (h(4), 'XData'), ... %! [-18.5782998846125, -18.8740348676059, -19.1697698505994], 1e-9); %! assert_equal (get (h(8), 'XData'), ... %! [1.97967308209488, 1.68393809910143, 1.38820311610799], 1e-9); %! ax = gca (); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Interaction of x2 and x1'); %! assert_equal (get (ax, 'YTickLabel'), ... %! {'x2: 0.05 to 20'; 'x1=0.05'; 'x1=0.525'; 'x1=1'; ... %! 'x1: 0.05 to 1'; 'x2=0.05'; 'x2=10.025'; 'x2=20'}); %! close (fig); %!test %! ## interaction effects: variables given as indices into VariableNames %! cmi = compact (fitlm (X, y, 'y ~ x1*x2')); %! fig = figure ('visible', 'off'); %! h = plotInteraction (cmi, 1, 2); %! assert_equal (numel (h), 11); %! assert_equal (get (h(1), 'XData'), [1.76843380852813, -18.8740348676059], 1e-9); %! assert_equal (get (h(1), 'YData'), [1, 7]); %! ax = gca (); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Interaction of x1 and x2'); %! close (fig); %!test %! ## interaction effects: explicit axes argument is honored %! cmi = compact (fitlm (X, y, 'y ~ x1*x2')); %! fig = figure ('visible', 'off'); %! axtarget = axes (fig); %! h = plotInteraction (axtarget, cmi, 'x1', 'x2'); %! assert_equal (numel (h), 11); %! assert_equal (isequal (get (h(1), 'Parent'), axtarget), true); %! assert_equal (isequal (gca (), axtarget), true); %! close (fig); %!test %! ## no interaction term: conditional effects collapse to the main effect %! cmn = compact (fitlm (X, y, 'y ~ x1 + x2')); %! fig = figure ('visible', 'off'); %! h = plotInteraction (cmn, 'x1', 'x2'); %! xd1 = get (h(1), 'XData'); %! eff1 = xd1(1); %! eff2 = xd1(2); %! assert_equal (eff1, 2.38302891604232, 1e-9); %! assert_equal (eff2, -19.5277648300125, 1e-9); %! assert_equal (get (h(4), 'XData'), [eff1, eff1, eff1], 1e-9); %! assert_equal (get (h(8), 'XData'), [eff2, eff2, eff2], 1e-9); %! close (fig); %!test %! ## categorical by continuous, effects mode %! xc = (1:30)' / 30; %! grp = categorical (repmat ({'A';'B';'C'}, 10, 1)); %! yv = 2*xc + 3*double (grp == 'B') - 1*double (grp == 'C') + ... %! 1.5*xc.*double (grp == 'B') + 0.3*sin ((1:30)'); %! tblc = table (yv, xc, grp, 'VariableNames', {'Response','Xc','Group'}); %! cmdlc = compact (fitlm (tblc, 'Response ~ Xc*Group')); %! fig = figure ('visible', 'off'); %! h = plotInteraction (cmdlc, 'Group', 'Xc'); %! assert_equal (numel (h), 11); %! assert_equal (get (h(1), 'XData'), [4.7896970899464, 2.32528157787528], 1e-9); %! assert_equal (get (h(2), 'XData'), [4.56862113373247, 5.01077304616034], 1e-9); %! assert_equal (get (h(3), 'XData'), [2.02254960835685, 2.62801354739371], 1e-9); %! assert_equal (get (h(4), 'XData'), ... %! [4.08328517685389, 4.7896970899464, 5.49610900303892], 1e-9); %! assert_equal (get (h(5), 'XData'), [3.64076372661607, 4.52580662709171], 1e-9); %! assert_equal (get (h(6), 'XData'), [4.56862113373247, 5.01077304616034], 1e-9); %! assert_equal (get (h(7), 'XData'), [5.07555715247022, 5.91666085360761], 1e-9); %! assert_equal (get (h(8), 'XData'), ... %! [1.88553612240401, 3.25156621870343, 1.8387423925184], 1e-9); %! assert_equal (get (h(9), 'XData'), [1.36118897012269, 2.40988327468532], 1e-9); %! assert_equal (get (h(10), 'XData'), [2.72721906642211, 3.77591337098474], 1e-9); %! assert_equal (get (h(11), 'XData'), [1.31439524023708, 2.36308954479972], 1e-9); %! ax = gca (); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Interaction of Group and Xc'); %! assert_equal (get (ax, 'YTick'), [1, 2, 3, 4, 7, 8, 9, 10]); %! assert_equal (get (ax, 'YTickLabel'), ... %! {'Group: C to B'; 'Xc=0.0333333'; 'Xc=0.516667'; 'Xc=1'; ... %! 'Xc: 0.033333 to 1'; 'Group=A'; 'Group=B'; 'Group=C'}); %! close (fig); %!test %! ## categorical by continuous, predictions mode %! xc = (1:30)' / 30; %! grp = categorical (repmat ({'A';'B';'C'}, 10, 1)); %! yv = 2*xc + 3*double (grp == 'B') - 1*double (grp == 'C') + ... %! 1.5*xc.*double (grp == 'B') + 0.3*sin ((1:30)'); %! tblc = table (yv, xc, grp, 'VariableNames', {'Response','Xc','Group'}); %! cmdlc = compact (fitlm (tblc, 'Response ~ Xc*Group')); %! fig = figure ('visible', 'off'); %! h = plotInteraction (cmdlc, 'Group', 'Xc', 'predictions'); %! assert_equal (numel (h), 3); %! xd = get (h(1), 'XData'); %! assert_equal (numel (xd), 101); %! assert_equal (xd(1:3), [0.0333333333333333, 0.043, 0.0526666666666667], 1e-9); %! assert_equal (xd(end-2:end), [0.980666666666667, 0.990333333333333, 1], 1e-9); %! yd1 = get (h(1), 'YData'); %! assert_equal (yd1(1:3), [0.107201421526318, 0.126056782750359, 0.144912143974399], 1e-9); %! assert_equal (yd1(end-2:end), [1.95502682148225, 1.97388218270629, 1.99273754393033], 1e-9); %! yd2 = get (h(2), 'YData'); %! assert_equal (yd2(1:3), [3.18658823804771, 3.21910390023474, 3.25161956242178], 1e-9); %! assert_equal (yd2(end-2:end), [6.37312313237707, 6.4056387945641, 6.43815445675114], 1e-9); %! yd3 = get (h(3), 'YData'); %! assert_equal (yd3(1:3), [-0.896696938806178, -0.878309514880994, -0.85992209095581], 1e-9); %! assert_equal (yd3(end-2:end), [0.905270605861853, 0.923658029787037, 0.942045453712221], 1e-9); %! assert_equal (get (h(1), 'DisplayName'), 'A'); %! close (fig); %!test %! mi = fitlm (X, y, 'y ~ x1*x2'); %! cmi = compact (mi); %! fig1 = figure ('visible', 'off'); %! ax1 = axes (fig1); %! h1 = plotInteraction (ax1, mi, 'x1', 'x2'); %! fig2 = figure ('visible', 'off'); %! ax2 = axes (fig2); %! h2 = plotInteraction (ax2, cmi, 'x1', 'x2'); %! for k = 1:numel (h1) %! assert_equal (get (h1(k), 'XData'), get (h2(k), 'XData'), 1e-10); %! endfor %! close (fig1); %! close (fig2); %!test %! t = anova (cmdl); %! assert_equal (t.Properties.RowNames, {'x1'; 'x2'; 'Error'}); %! assert_equal (t.SumSq(1), 0.590865029026421, -1e-9); %! assert_equal (t.DF(1), 1); %! assert_equal (t.F(1), 25.9858409294749, -1e-8); %! assert_equal (t.pValue(1), 8.93779416901828e-05, -1e-8); %! assert_equal (t.SumSq(2), 42.051825481854, -1e-8); %! assert_equal (t.F(2), 1849.41059985568, -1e-7); %! assert_equal (t.pValue(2), 8.6569383058211e-19, -1e-8); %! assert_equal (t.SumSq(3), 0.386545331386823, -1e-9); %! assert_equal (t.DF(3), 17); %! assert_equal (isnan (t.F(3)), true); %! assert_equal (isnan (t.pValue(3)), true); %!test %! ## type 3 on a continuous-only model: no categorical, so deviation coding %! ## is a no-op and no synthetic design is needed (values from R2024a) %! xa = [1;2;3;4;5;6;7;8;9;10;1;2;3;4;5;6;7;8;9;10]; %! xb = [2;1;4;3;6;5;8;7;10;9;2;1;4;3;6;5;8;7;10;9]; %! yv = 3 + 2*xa - 0.5*xb + 0.4*sin ((1:20)'); %! c = compact (fitlm (table (xa, xb, yv), 'yv ~ xa + xb')); %! t = anova (c, 'components', 3); %! assert_equal (t.Properties.RowNames, {'xa'; 'xb'; 'Error'}); %! assert_equal (t.SumSq', ... %! [78.1451599777569, 4.93546634617531, 1.6375633770915], -1e-9); %!test %! ## a model missing a lower-order relative is computed, not refused %! xa = [1;2;3;4;5;6;7;8;9;10;1;2;3;4;5;6;7;8;9;10]; %! xb = [2;1;4;3;6;5;8;7;10;9;2;1;4;3;6;5;8;7;10;9]; %! yv = 3 + 2*xa - 0.5*xb + 0.4*sin ((1:20)'); %! c = compact (fitlm (table (xa, xb, yv), 'yv ~ xa + xa:xb')); %! t = anova (c, 'components', 3); %! assert_equal (t.SumSq', ... %! [42.7962732581896, 1.58706426534189, 4.98596545792161], -1e-9); %!test %! ## a categorical beside two numeric predictors: the synthetic design must %! ## cross them, or its numeric columns come out collinear %! xa = [1;2;3;4;5;6;7;8;9;10;1;2;3;4;5;6;7;8;9;10]; %! xb = [2;1;4;3;6;5;8;7;10;9;2;1;4;3;6;5;8;7;10;9]; %! gc = categorical ([1;1;1;1;1;2;2;2;2;2;3;3;3;3;3;1;2;3;1;2]); %! yv = 3 + 2*xa - 0.5*xb + 0.4*sin ((1:20)'); %! c = compact (fitlm (table (xa, xb, gc, yv), 'yv ~ xa + xb + gc')); %! t = anova (c, 'components', 3); %! assert_equal (t.Properties.RowNames, {'xa'; 'xb'; 'gc'; 'Error'}); %! assert_equal (t.SumSq', [70.9172463138446, 4.93563224639584, ... %! 0.0327536552228964, 1.60480972186862], -1e-9); %!test %! ## a categorical interacting with a numeric predictor, beside another %! xa = [1;2;3;4;5;6;7;8;9;10;1;2;3;4;5;6;7;8;9;10]; %! xb = [2;1;4;3;6;5;8;7;10;9;2;1;4;3;6;5;8;7;10;9]; %! gc = categorical ([1;1;1;1;1;2;2;2;2;2;3;3;3;3;3;1;2;3;1;2]); %! yv = 3 + 2*xa - 0.5*xb + 0.4*sin ((1:20)'); %! c = compact (fitlm (table (xa, xb, gc, yv), 'yv ~ xa*gc + xb')); %! t = anova (c, 'components', 3); %! assert_equal (t.SumSq', [69.1671592639563, 4.32040128576299, ... %! 0.106963060253346, 0.141990871527191, 1.46281885034151], -1e-9); %!test %! ## the compact table matches the full model's, term for term %! xa = [1;2;3;4;5;6;7;8;9;10;1;2;3;4;5;6;7;8;9;10]; %! xb = [2;1;4;3;6;5;8;7;10;9;2;1;4;3;6;5;8;7;10;9]; %! gc = categorical ([1;1;1;1;1;2;2;2;2;2;3;3;3;3;3;1;2;3;1;2]); %! yv = 3 + 2*xa - 0.5*xb + 0.4*sin ((1:20)'); %! m = fitlm (table (xa, xb, gc, yv), 'yv ~ xa + xb + gc'); %! assert_equal (anova (compact (m), 'components', 3).SumSq, ... %! anova (m, 'components', 3).SumSq, -1e-9); %!test %! t = anova (cmdl, 'components', 2); %! assert_equal (t.SumSq(1), 0.590865029026421, -1e-9); %! assert_equal (t.SumSq(2), 42.051825481854, -1e-8); %!test %! t = anova (cmdl, 'components', 1); %! assert_equal (t.SumSq(1), 541.472049188541, -1e-6); %! assert_equal (t.F(1), 23813.5713686671, -1e-6); %! assert_equal (t.pValue(1), 3.41699381541991e-28, -1e-8); %! assert_equal (t.SumSq(2), 42.051825481854, -1e-8); %!test %! t = anova (cmdl, 'summary'); %! assert_equal (t.Properties.RowNames, {'Total'; 'Model'; 'Residual'}); %! assert_equal (t.SumSq(1), 583.910420002346, -1e-8); %! assert_equal (t.DF(1), 19); %! assert_equal (isnan (t.F(1)), true); %! assert_equal (t.SumSq(2), 583.523874670959, -1e-8); %! assert_equal (t.DF(2), 2); %! assert_equal (t.F(2), 12831.4909842738, -1e-6); %! assert_equal (t.pValue(2), 9.48988083209278e-28, -1e-8); %! assert_equal (t.SumSq(3), 0.386545331386823, -1e-9); %! assert_equal (t.DF(3), 17); %!test %! t1 = anova (cmdl, 'oldcomponents'); %! t2 = anova (cmdl, 'newcomponents'); %! assert_equal (t1.SumSq(1), 0.590865029026421, -1e-9); %! assert_equal (t2.SumSq(2), 42.051825481854, -1e-8); %!test %! t = anova (cmdl, 'summary', 2); %! assert_equal (t.Properties.RowNames, {'Total'; 'Model'; 'Residual'}); %! assert_equal (t.SumSq(2), 583.523874670959, -1e-8); %!test %! a = categorical ([1;1;2;1;2;1;1;2;1;1;2;1;2;2;1;1;2;1;1;2]); %! b = categorical ([1;2;1;1;2;2;1;1;2;1;1;2;2;1;2;1;1;2;2;1]); %! z = (1:20)' / 10; %! w = 2*(a=='2') + 1.5*(b=='2') + 0.7*z + 0.9*(a=='2').*(b=='2') + ... %! [0.1;-0.2;0.05;0.15;-0.1;0.2;-0.05;0.1;0.0;-0.15; ... %! 0.1;0.05;-0.2;0.15;0.0;-0.1;0.05;0.2;-0.05;0.1]; %! t2 = table (a, b, z, w); %! c = compact (fitlm (t2, 'w ~ a*b')); %! t = anova (c); %! assert_equal (t.Properties.RowNames, {'a'; 'b'; 'a:b'; 'Error'}); %! assert_equal (t.SumSq(1), 26.0224323327611, -1e-8); %! assert_equal (t.F(1), 138.504723473417, -1e-7); %! assert_equal (t.pValue(1), 2.72592668860933e-09, -1e-8); %! assert_equal (t.SumSq(2), 15.2365308176098, -1e-8); %! assert_equal (t.SumSq(3), 0.0142868014375561, -1e-6); %! assert_equal (t.SumSq(4), 3.00609904761905, -1e-8); %! assert_equal (t.DF(4), 16); %!test %! a = categorical ([1;1;2;1;2;1;1;2;1;1;2;1;2;2;1;1;2;1;1;2]); %! b = categorical ([1;2;1;1;2;2;1;1;2;1;1;2;2;1;2;1;1;2;2;1]); %! z = (1:20)' / 10; %! w = 2*(a=='2') + 1.5*(b=='2') + 0.7*z + 0.9*(a=='2').*(b=='2') + ... %! [0.1;-0.2;0.05;0.15;-0.1;0.2;-0.05;0.1;0.0;-0.15; ... %! 0.1;0.05;-0.2;0.15;0.0;-0.1;0.05;0.2;-0.05;0.1]; %! t2 = table (a, b, z, w); %! c = compact (fitlm (t2, 'w ~ a*b')); %! t = anova (c, 'components', 1); %! assert_equal (t.SumSq(1), 16.354083333333, -1e-7); %! assert_equal (t.F(1), 87.0448142886636, -1e-7); %! assert_equal (t.pValue(1), 7.14746180184192e-08, -1e-8); %!test %! a = categorical ([1;1;2;1;2;1;1;2;1;1;2;1;2;2;1;1;2;1;1;2]); %! b = categorical ([1;2;1;1;2;2;1;1;2;1;1;2;2;1;2;1;1;2;2;1]); %! z = (1:20)' / 10; %! w = 2*(a=='2') + 1.5*(b=='2') + 0.7*z + 0.9*(a=='2').*(b=='2') + ... %! [0.1;-0.2;0.05;0.15;-0.1;0.2;-0.05;0.1;0.0;-0.15; ... %! 0.1;0.05;-0.2;0.15;0.0;-0.1;0.05;0.2;-0.05;0.1]; %! t2 = table (a, b, z, w); %! c = compact (fitlm (t2, 'w ~ a + a:b')); %! t = anova (c); %! assert_equal (t.Properties.RowNames, {'a'; 'a:b'; 'Error'}); %! assert_equal (t.SumSq(1), 16.3540833333333, -1e-8); %! assert_equal (t.F(1), 22.011053580241, -1e-7); %! assert_equal (t.SumSq(2), 5.62601666666666, -1e-8); %! assert_equal (t.F(2), 7.57208776360618, -1e-7); %! assert_equal (t.SumSq(3), 12.6309, -1e-6); %! assert_equal (t.DF(3), 17); %!test %! a = categorical ([1;1;2;1;2;1;1;2;1;1;2;1;2;2;1;1;2;1;1;2]); %! b = categorical ([1;2;1;1;2;2;1;1;2;1;1;2;2;1;2;1;1;2;2;1]); %! z = (1:20)' / 10; %! w = 2*(a=='2') + 1.5*(b=='2') + 0.7*z + 0.9*(a=='2').*(b=='2') + ... %! [0.1;-0.2;0.05;0.15;-0.1;0.2;-0.05;0.1;0.0;-0.15; ... %! 0.1;0.05;-0.2;0.15;0.0;-0.1;0.05;0.2;-0.05;0.1]; %! t2 = table (a, b, z, w); %! c = compact (fitlm (t2, 'w ~ a + b - 1')); %! t = anova (c); %! assert_equal (t.SumSq(1), 63.3745141509434, -1e-8); %! assert_equal (t.F(1), 178.349190204051, -1e-7); %! assert_equal (t.SumSq(2), 15.2365308176101, -1e-8); %! assert_equal (t.SumSq(3), 3.02038584905660, -1e-8); %! assert_equal (t.DF(3), 17); %!test %! a = categorical ([1;1;2;1;2;1;1;2;1;1;2;1;2;2;1;1;2;1;1;2]); %! b = categorical ([1;2;1;1;2;2;1;1;2;1;1;2;2;1;2;1;1;2;2;1]); %! z = (1:20)' / 10; %! w = 2*(a=='2') + 1.5*(b=='2') + 0.7*z + 0.9*(a=='2').*(b=='2') + ... %! [0.1;-0.2;0.05;0.15;-0.1;0.2;-0.05;0.1;0.0;-0.15; ... %! 0.1;0.05;-0.2;0.15;0.0;-0.1;0.05;0.2;-0.05;0.1]; %! wt = [1.2;0.8;1.5;1.0;0.9;1.1;1.3;0.7;1.0;1.4; ... %! 0.8;1.2;1.0;1.1;0.9;1.3;0.7;1.5;1.0;1.2]; %! t2 = table (a, b, z, w); %! c = compact (fitlm (t2, 'w ~ a + b', 'Weights', wt)); %! t = anova (c); %! assert_equal (t.SumSq(1), 26.6364861423312, -1e-8); %! assert_equal (t.F(1), 134.770391630163, -1e-7); %! assert_equal (t.SumSq(2), 17.4880599694592, -1e-8); %! assert_equal (t.SumSq(3), 3.35993877395756, -1e-8); %! assert_equal (t.DF(3), 17); %!test %! g = categorical ([1;1;1;1;1;2;2;2;2;2;2;2;2;2;3;3;3;3;3;3]); %! w = [2.1;1.9;2.3;2.0;1.8;4.1;4.3;3.9;4.0;4.2; ... %! 4.4;3.8;4.1;4.0;6.1;5.9;6.3;6.0;5.8;6.2]; %! t2 = table (g, w); %! c = compact (fitlm (t2, 'w ~ g')); %! t = anova (c); %! assert_equal (t.Properties.RowNames, {'g'; 'Error'}); %! assert_equal (t.DF(1), 2); %! assert_equal (t.SumSq(1), 44.3761111111084, -1e-6); %! assert_equal (t.F(1), 616.446794988158, -1e-6); %!test %! g3 = categorical ([1;1;1;1;2;2;2;3;3;3;3;1;2;3]); %! g2 = categorical ([1;1;2;2;1;2;2;1;1;2;2;2;1;1]); %! w = [10;12;15;14;9;11;13;16;18;20;22;17;10;19]; %! t2 = table (g3, g2, w); %! c = compact (fitlm (t2, 'w ~ g3*g2')); %! t = anova (c, 'components', 3); %! assert_equal (t.Properties.RowNames, {'g3'; 'g2'; 'g3:g2'; 'Error'}); %! assert_equal (t.SumSq(1), 176.678431372552, -1e-6); %! assert_equal (t.F(1), 44.6345510835922, -1e-6); %! assert_equal (t.SumSq(2), 38.7604166666673, -1e-6); %! assert_equal (t.SumSq(3), 1.85490196078435, -1e-6); %! assert_equal (t.SumSq(4), 15.8333333333333, -1e-8); %! assert_equal (t.DF(4), 8); %!test %! g = categorical ([1;1;1;1;2;2;2;2;3;3;3;3]); %! z = (1:12)' / 2; %! w = 5 + 2*(g=='2') + 4*(g=='3') + 0.3*z; %! w(10) = w(10) + 20; %! t2 = table (g, z, w); %! c = compact (fitlm (t2, 'w ~ g + z', 'RobustOpts', 'on')); %! t = anova (c, 'components', 3); %! assert_equal (t.SumSq(1), 3.35664335664336, -1e-8); %! assert_equal (t.F(1), 0.0801017164653529, -1e-8); %! assert_equal (t.SumSq(2), 0.337499999999999, -1e-8); %! assert_equal (t.SumSq(3), 167.619047619048, -1e-6); %! assert_equal (t.DF(3), 8); %!test %! z = [25;31;42;29;55;38;46;33;27;50;41;36;48;30;44]; %! g = categorical ({'M';'F';'F';'M';'M';'F';'M';'F';'F';'M';'F';'M';'F';'M';'F'}); %! w = [118;122;135;120;150;128;140;124;119;145;130;126;138;121;136]; %! t2 = table (z, g, w); %! c = compact (fitlm (t2, 'w ~ g*z')); %! t = anova (c, 'components', 3); %! assert_equal (t.Properties.RowNames, {'z'; 'g'; 'z:g'; 'Error'}); %! assert_equal (t.SumSq(1), 1086.93049009449, -1e-6); %! assert_equal (t.F(1), 525.990068941166, -1e-6); %! assert_equal (t.SumSq(2), 3.73180668223453, -1e-8); %! assert_equal (t.SumSq(3), 7.05971182791548, -1e-8); %! assert_equal (t.SumSq(4), 22.7309147016933, -1e-8); %! assert_equal (t.DF(4), 11); %!test %! z = [25;31;42;29;55;38;46;33;27;50;41;36;48;30;44]; %! g = categorical ({'M';'F';'F';'M';'M';'F';'M';'F';'F';'M';'F';'M';'F';'M';'F'}); %! w = [118;122;135;120;150;128;140;124;119;145;130;126;138;121;136]; %! t2 = table (z, g, w); %! c = compact (fitlm (t2, 'w ~ g + z^2')); %! t = anova (c, 'summary'); %! assert_equal (t.Properties.RowNames, ... %! {'Total'; 'Model'; '. Linear'; '. Nonlinear'; 'Residual'}); %! assert_equal (t.SumSq(2), 1394.52505797828, -1e-6); %! assert_equal (t.F(2), 241.097329241452, -1e-7); %! assert_equal (t.SumSq(3), 1385.94270680373, -1e-6); %! assert_equal (t.SumSq(4), 8.58235117455061, -1e-8); %! assert_equal (t.SumSq(5), 21.2082753550521, -1e-8); %! assert_equal (t.DF(5), 11); %!error CompactLinearModel (123) %!error cmdl(1) %!error cmdl{1} %!error cmdl.NotAProperty %!error cmdl.Fitted %!error cmdl.ObservationInfo %!error cmdl.Steps %!error coefCI (cmdl, 0.05, 'extra') %!error coefCI (cmdl, 1.5) %!error coefCI (cmdl, -0.1) %!error coefCI (cmdl, NaN) %!error coefCI (cmdl, [0.01 0.05]) %!error coefCI (cmdl, 'abc') %!error coefTest (cmdl, [1 0]) %!error coefTest (cmdl, 'abc') %!error coefTest (cmdl, [0 1 0], 'abc') %!error coefTest (cmdl, [0 1 0; 0 0 1], [1]) %!error coefTest (cmdl, [0 NaN 0]) %!error coefTest (cmdl, [0 1 0], 0, 'extra') %!error [a, b, c, d] = coefTest (cmdl) %!error predict (cmdl) %!error predict (cmdl, [0.5 0.25], 'BadOption', 1) %!error predict (cmdl, [0.5 0.25], 'Prediction', 'bad') %!error predict (cmdl, ones (3, 5)) %!error predict (cmdl, ones (3, 1)) %!error predict (cmdl, table ([1;2], 'VariableNames', {'z'})) %!error random (cmdl) %!error random (cmdl, [0.5 0.25], 'extra') %!error random (cmdl, ones (3, 5)) %!error random (cmdl, []) %!error feval (cmdl) %!error feval (cmdl, [0.5;1.0], [0.25;1.0], [0.1;0.2]) %!error feval (cmdl, ones (3, 1)) %!error feval (cmdl, [0.5;1.0;0.2], [0.25;1.0]) %!error feval (cmdl, table ([1;2], 'VariableNames', {'z'})) %!error feval (cmdl, []) %!error feval (cmdl, '2500', 0.25) %!error plotEffects (cmdl, 'extra') %!error plotEffects (cmdl, 'a', 'b') %!error plotEffects (compact (fitlm (X(:,1), y, 'constant'))) %!error plotInteraction (cmdl) %!error plotInteraction (cmdl, 'x1') %!error plotInteraction (cmdl, 'x1', 'x2', 'badtype') %!error plotInteraction (cmdl, 'x1', 'x2', 'effects', 'extra') %!error plotInteraction (cmdl, 'z', 'x2') %!error plotInteraction (cmdl, 'x1', 'z') %!error plotInteraction (cmdl, 99, 'x2') %!error plotInteraction (cmdl, 1.5, 'x2') %!error plotInteraction (cmdl, 'y', 'x2') %!error plotInteraction (cmdl, 'x1', 'y') %!error plotInteraction (cmdl, 'x1', 'x1') %!error anova (cmdl, 'components', 'h', 'extra') %!error anova (cmdl, 'bogus') %!error anova (cmdl, 'components', 4) statistics-release-1.9.2/inst/Regression/CoxModel.m000066400000000000000000001651061524624707500223450ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftp {statistics} {} CoxModel ## ## Cox proportional hazards regression model class. ## ## A @code{CoxModel} object encapsulates a Cox proportional hazards model of a ## survival time on one or more predictors, fitted by maximizing the Cox partial ## likelihood. It is the object counterpart of @code{coxphfit} and is normally ## created with the @code{fitcox} function. ## ## The model states that an observation with predictor values @var{x} has hazard ## ## @tex ## $$ h(x, t) = h_0(t)\exp\left(\sum_{j=1}^{p} x_{j} b_j\right) $$ ## @end tex ## @ifnottex ## @math{h(x, t) = h_0(t) exp (x' b)} ## @end ifnottex ## ## where @math{h_0(t)} is an unspecified baseline hazard. The model carries no ## constant term: any constant is absorbed into that baseline. ## ## The most useful properties are @code{Coefficients} (a table of estimates, ## standard errors, @math{z}-statistics and p-values), @code{Hazard} (the ## estimated baseline cumulative hazard), @code{LogLikelihood}, ## @code{Residuals}, and the three p-values @code{LikelihoodRatioTestPValue}, ## @code{ProportionalHazardsPValue} and ## @code{ProportionalHazardsPValueGlobal}. Fitted models support the ## @code{survival}, @code{hazardratio}, @code{coefci}, @code{linhyptest}, ## @code{plotSurvival} and @code{discardResiduals} methods. ## ## A categorical predictor expands to indicator columns, one per level bar the ## first, which the baseline hazard carries; the indicator columns are named ## @qcode{@var{name}_@var{level}} and enter the default baseline as zero, while ## a numeric predictor enters it as its mean. ## ## @code{ProportionalHazardsPValue} is a Grambsch-Therneau test of each ## coefficient against the mid-ranks of the event times, and ## @code{ProportionalHazardsPValueGlobal} the same test taken over the whole ## model. A small p-value is evidence that the hazard ratio moves with time, ## which is what proportionality denies. ## ## @strong{Deviations from MATLAB, all in naming.} MATLAB derives the names ## reported by a fitted model from three different places and they need not ## agree with one another: with default predictor names its @code{Formula} ## reads @qcode{'y ~ x1 + x2'} in lower case while @code{PredictorNames} holds ## @qcode{'X1'} and @qcode{'X2'}, and supplying @qcode{'PredictorNames'} ## changes @code{ResponseName} from @qcode{'y'} to the name of the variable ## passed as the response. Here the names are consistent by construction: ## @code{ResponseName} is @qcode{'y'} unless the data came from a table, the ## @code{Formula} is built from @code{PredictorNames} and ## @code{ResponseName}, and neither depends on which optional arguments were ## given. Every fitted quantity agrees with MATLAB. ## ## @seealso{fitcox, coxphfit, GeneralizedLinearModel, LinearModel} ## @end deftp classdef CoxModel properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CoxModel} {property} Coefficients ## ## Coefficient estimates and their statistics ## ## A table with one row per encoded predictor column, its row names the ## encoded column names, and the variables @qcode{Beta}, @qcode{SE}, ## @qcode{zStat} and @qcode{pValue}. This property is read-only. ## ## @end deftp Coefficients = []; ## -*- texinfo -*- ## @deftp {CoxModel} {property} NumPredictors ## ## Number of predictors ## ## A positive integer counting the predictor variables of the model, ## before any categorical predictor is encoded. This property is ## read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {CoxModel} {property} LogLikelihood ## ## Log-likelihood of the fitted model ## ## A scalar, the maximised Cox partial log-likelihood. This property is ## read-only. ## ## @end deftp LogLikelihood = []; ## -*- texinfo -*- ## @deftp {CoxModel} {property} Hazard ## ## Estimated baseline cumulative hazard ## ## A numeric matrix of event times in its first column and the cumulative ## hazard at them in its second. A stratified model adds a third column ## holding the stratum each row belongs to. This property is read-only. ## ## @end deftp Hazard = []; ## -*- texinfo -*- ## @deftp {CoxModel} {property} PredictorNames ## ## Names of the predictor variables ## ## A cell array of character vectors with one name per predictor. This ## property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {CoxModel} {property} ResponseName ## ## Name of the response variable ## ## A character vector naming the response, which is the survival time. ## This property is read-only. ## ## @end deftp ResponseName = 'y'; ## -*- texinfo -*- ## @deftp {CoxModel} {property} Formula ## ## Model formula ## ## A @code{LinearFormula} object describing the terms of the model. This ## property is read-only. ## ## @end deftp Formula = []; ## -*- texinfo -*- ## @deftp {CoxModel} {property} Baseline ## ## Predictor values the baseline hazard is evaluated at ## ## The baseline the fit used, one row per stratum. It is reported as it ## was given when @code{fitcox} was given one, a scalar staying a scalar, ## and otherwise holds the rows the fit was centred on. This property is ## read-only. ## ## @end deftp Baseline = []; ## -*- texinfo -*- ## @deftp {CoxModel} {property} Stratification ## ## Stratification levels used in the fit ## ## The distinct levels of the stratification variable. It is empty when ## the model is not stratified. This property is read-only. ## ## @end deftp Stratification = []; ## -*- texinfo -*- ## @deftp {CoxModel} {property} CoefficientCovariance ## ## Estimated covariance of the coefficients ## ## A square numeric matrix, one row and column per encoded predictor ## column, holding the estimated covariance of the estimates in ## @qcode{Coefficients}. This property is read-only. ## ## @end deftp CoefficientCovariance = []; ## -*- texinfo -*- ## @deftp {CoxModel} {property} StandardError ## ## Standard errors of the coefficients ## ## A numeric column vector, the square roots of the diagonal of ## @qcode{CoefficientCovariance}, which is column @qcode{SE} of ## @qcode{Coefficients}. This property is read-only. ## ## @end deftp StandardError = []; ## -*- texinfo -*- ## @deftp {CoxModel} {property} Residuals ## ## Residuals of the fitted model ## ## A table with one row per observation and the variables ## @qcode{CoxSnell}, @qcode{Deviance}, @qcode{Martingale}, ## @qcode{Schoenfeld}, @qcode{ScaledSchoenfeld}, @qcode{Score} and ## @qcode{ScaledScore}. This property is read-only. ## ## @end deftp Residuals = []; ## -*- texinfo -*- ## @deftp {CoxModel} {property} ProportionalHazardsPValue ## ## Proportional hazards test, one predictor at a time ## ## A numeric vector with one @math{p} value per predictor, testing whether ## that predictor's effect is constant over time. A small value is ## evidence against the proportional hazards assumption. This property is ## read-only. ## ## @end deftp ProportionalHazardsPValue = []; ## -*- texinfo -*- ## @deftp {CoxModel} {property} ProportionalHazardsPValueGlobal ## ## Proportional hazards test over the whole model ## ## A scalar @math{p} value testing the proportional hazards assumption for ## all predictors at once. This property is read-only. ## ## @end deftp ProportionalHazardsPValueGlobal = []; ## -*- texinfo -*- ## @deftp {CoxModel} {property} LikelihoodRatioTestPValue ## ## Likelihood ratio test against the null model ## ## A scalar @math{p} value comparing the fitted model with the model that ## carries no predictors. This property is read-only. ## ## @end deftp LikelihoodRatioTestPValue = []; ## -*- texinfo -*- ## @deftp {CoxModel} {property} VariableInfo ## ## Information about the variables ## ## A table with one row per variable, its row names the variable names, ## and the variables @qcode{Class}, @qcode{Range}, @qcode{InModel} and ## @qcode{IsCategorical}. This property is read-only. ## ## @end deftp VariableInfo = []; endproperties properties (Access = private, Hidden) b_ = []; # coefficient vector aligned with the encoded columns X_ = []; # encoded predictor matrix used in the fit T_ = []; # response as passed to coxphfit cens_ = []; # censoring indicator freq_ = []; # frequency weights strata_ = []; # stratum label per observation ([] when unstratified) baserow_ = []; # baseline actually used, one row per stratum ties_ = 'breslow'; catinfo_ = []; # categorical encoding, for new data catcols_ = []; # logical mask of categorical predictors catbase_ = []; # logical mask of encoded columns that are indicators formulastr_ = ''; endproperties methods (Hidden) ## Custom display of the object name. function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ("%s =\n", in_name); endif disp (this); endfunction ## Custom display of the model summary. function disp (this) fprintf ("\n Cox proportional hazards regression model:\n"); if (! isempty (this.formulastr_)) fprintf (" %s\n", this.formulastr_); endif if (! isempty (this.Coefficients)) fprintf ("\n Coefficients:\n\n"); disp (this.Coefficients); endif fprintf ("\n"); if (! isempty (this.LogLikelihood)) fprintf ("Log-likelihood: %g\n", this.LogLikelihood); endif if (! isempty (this.LikelihoodRatioTestPValue)) fprintf ("Likelihood ratio test vs. constant model: p-value = %g\n", ... this.LikelihoodRatioTestPValue); endif endfunction ## Class specific subscripted reference. function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' error (strcat ("CoxModel: () indexing is not supported.", ... " Use dot notation for properties.")); case '{}' error (strcat ("CoxModel: {} indexing is not supported.", ... " Use dot notation for properties.")); case '.' if (! ischar (s.subs)) error (strcat ("CoxModel.subsref: property name must be a", ... " character vector.")); endif if (ismethod (this, s.subs)) [varargout{1:nargout}] = builtin ('subsref', this, [s, chain_s]); return; endif try out = this.(s.subs); catch error ("CoxModel.subsref: unknown property '%s'.", s.subs); end_try_catch endswitch if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {CoxModel} {@var{mdl} =} CoxModel (@var{X}, @var{T}) ## @deftypefnx {CoxModel} {@var{mdl} =} CoxModel (@var{tbl}, @var{respvar}) ## @deftypefnx {CoxModel} {@var{mdl} =} CoxModel (@dots{}, @var{Name}, @var{Value}) ## ## Fit a Cox proportional hazards regression model. ## ## @code{@var{mdl} = CoxModel (@var{X}, @var{T})} fits the model to the ## @math{n}-by-@math{p} numeric predictor matrix @var{X} and the ## @math{n}-by-1 vector of event times @var{T}. @var{T} may instead be an ## @math{n}-by-2 matrix giving a @math{(start, stop]} interval of exposure, ## the counting process form. ## ## @code{@var{mdl} = CoxModel (@var{tbl}, @var{respvar})} takes the data ## from the table @var{tbl}, using the variable named @var{respvar} as the ## response and every other variable as a predictor. A @code{categorical} ## variable is encoded as indicator columns. ## ## @var{X} must not contain a constant column: the model has no constant ## term, since any constant is absorbed into the baseline hazard. ## ## The following @var{Name}/@var{Value} pairs are accepted: ## ## @multitable @columnfractions 0.25 0.75 ## @headitem Name @tab Value ## @item @qcode{"Baseline"} @tab The @var{X} values at which the baseline ## hazard is computed, either a scalar or a 1-by-@math{p} vector. The ## default is the mean of each numeric predictor and zero for each indicator ## column of a categorical predictor, taken within each stratum. ## @item @qcode{"Beta"} @tab The starting value of the iteration, a vector ## of length @math{p}. The default is @code{0.01 ./ std (@var{X})}. ## @item @qcode{"CategoricalPredictors"} @tab The predictors to treat as ## categorical, given as column indices, a logical vector, or a cell array ## of predictor names. Table variables of class @code{categorical} are ## detected without this argument. ## @item @qcode{"Censoring"} @tab A logical or 0/1 vector of length ## @math{n}, where 1 marks an observation right-censored at its recorded ## time. The default is a vector of zeros. ## @item @qcode{"Frequency"} @tab A vector of length @math{n} of ## non-negative values giving the number of observations each row ## represents, or a weight. The default is a vector of ones. ## @item @qcode{"OptimizationOptions"} @tab A structure of iteration ## settings, as built by @code{statset ("fitcox")}. The fields used are ## @qcode{"MaxIter"}, @qcode{"TolX"} and @qcode{"Display"}. ## @item @qcode{"PredictorNames"} @tab A cell array of @math{p} predictor ## names. The default is @qcode{"X1"}, @qcode{"X2"}, and so on, or the ## table variable names. ## @item @qcode{"Stratification"} @tab A vector of length @math{n} of ## stratum labels. Each stratum carries its own baseline hazard and its own ## risk sets, while the coefficients are shared across all of them. ## @item @qcode{"TieBreakMethod"} @tab The method of handling tied event ## times, either @qcode{"breslow"} (default) or @qcode{"efron"}. ## @end multitable ## ## @end deftypefn function this = CoxModel (X, T, varargin) if (nargin < 2) error ("CoxModel: too few input arguments."); endif ## --- the data ---------------------------------------------------- istbl = isa (X, 'table'); if (istbl) if (! ((ischar (T) && isrow (T)) || (isa (T, 'string') && isscalar (T)))) error (strcat ("CoxModel: RESPVAR must be a character vector", ... " naming a variable of the data table.")); endif resp_name = char (T); all_names = X.Properties.VariableNames; r_idx = find (strcmp (all_names, resp_name), 1); if (isempty (r_idx)) error ("CoxModel: '%s' is not a variable of the data table.", ... resp_name); endif T_val = X.(resp_name); pred_names = all_names; pred_names(r_idx) = []; tbl = X; else if (! (isnumeric (X) && isreal (X) && ismatrix (X))) error ("CoxModel: X must be a real numeric matrix."); endif T_val = T; resp_name = 'y'; pred_names = arrayfun (@(k) sprintf ("X%d", k), 1:columns (X), ... 'UniformOutput', false); tbl = []; endif p_raw = numel (pred_names); if (p_raw < 1) error ("CoxModel: the model needs at least one predictor."); endif ## --- optional arguments ------------------------------------------ Baseline = []; Beta0 = []; CatPreds = []; Censoring = []; Frequency = []; Options = []; PredNames = {}; Strata = []; Ties = 'breslow'; if (mod (numel (varargin), 2) != 0) error ("CoxModel: optional arguments must be name-value pairs."); endif for i = 1:2:numel (varargin) name = varargin{i}; if (! (ischar (name) && isrow (name))) error ("CoxModel: parameter name must be a character vector."); endif switch (lower (name)) case 'baseline' Baseline = varargin{i+1}; case 'beta' Beta0 = varargin{i+1}; case 'categoricalpredictors' CatPreds = varargin{i+1}; case 'censoring' Censoring = varargin{i+1}; case 'frequency' Frequency = varargin{i+1}; case 'optimizationoptions' Options = varargin{i+1}; case 'predictornames' PredNames = varargin{i+1}; case 'stratification' Strata = varargin{i+1}; case 'tiebreakmethod' Ties = varargin{i+1}; otherwise error ("CoxModel: unknown parameter name '%s'.", name); endswitch endfor if (! isempty (PredNames)) if (! (iscellstr (PredNames) && numel (PredNames) == p_raw)) error (strcat ("CoxModel: 'PredictorNames' must be a cell array", ... " of one character vector per predictor.")); endif pred_names = PredNames(:)'; endif if (! (ischar (Ties) && any (strcmpi (Ties, {'breslow', 'efron'})))) error (strcat ("CoxModel: 'TieBreakMethod' must be either", ... " 'breslow' or 'efron'.")); endif Ties = lower (Ties); ## --- the predictor data, under the names the model will report ---- ## A renamed table variable is renamed once, here, so that every later ## lookup goes through PredictorNames alone. if (istbl) X_raw = []; tbl_p = tbl(:, setdiff (1:numel (all_names), r_idx)); tbl_p.Properties.VariableNames = pred_names; n_total = rows (tbl); data_in = tbl_p; else X_raw = X; tbl_p = []; n_total = rows (X); data_in = X; endif ## --- which predictors are categorical ---------------------------- cat_cols = false (1, p_raw); if (istbl) for j = 1:p_raw col = tbl_p.(pred_names{j}); cat_cols(j) = isa (col, 'categorical') || iscell (col); endfor endif if (! isempty (CatPreds)) cat_cols = cat_cols | categorical_mask (CatPreds, pred_names); endif ## --- numeric codes, then indicator columns ----------------------- [X_num, cat_levels] = raw_to_codes (data_in, X_raw, tbl_p, pred_names, ... cat_cols, n_total); ## The baseline hazard plays the part of an intercept -- it is what the ## reference level of a categorical predictor is folded into -- so the ## encoding is reference coded even though the model has no constant ## term, which is what MATLAB does here too. [X_enc, enc_names, cat_info] = encode_categorical (X_num, cat_cols, ... pred_names, ... cat_levels, true); p = columns (X_enc); ## Mark the encoded columns that came from a categorical predictor: they ## enter the default baseline as zero rather than as their mean. ## encode_categorical gives a categorical predictor one column per level ## bar the reference and a numeric one a single column, so the encoded ## columns are walked in the same order to find which came from which. cat_enc = false (1, p); k = 0; for j = 1:p_raw if (cat_cols(j)) nlev = numel (cat_levels{j}) - 1; cat_enc(k+1:k+nlev) = true; k += nlev; else k += 1; endif endfor ## --- the fit ------------------------------------------------------ args = {}; if (! isempty (Censoring)) args = [args, {'Censoring', Censoring}]; endif if (! isempty (Frequency)) args = [args, {'Frequency', Frequency}]; endif if (! isempty (Beta0)) args = [args, {'B0', Beta0}]; endif if (! isempty (Options)) args = [args, {'Options', Options}]; endif if (! isempty (Strata)) args = [args, {'Strata', Strata}]; endif args = [args, {'Ties', Ties}]; ## The baseline each stratum is centred on. A numeric predictor enters ## it as its frequency-weighted mean and a categorical one as zero, the ## reference level being what the baseline hazard already carries. w = ones (n_total, 1); if (! isempty (Frequency)) w = Frequency(:); endif base_given = ! isempty (Baseline); if (isempty (Strata)) slev = []; nS = 1; smask = {true(n_total, 1)}; else slev = unique (Strata(:)); nS = numel (slev); smask = arrayfun (@(v) Strata(:) == v, slev, 'UniformOutput', false); endif brow = zeros (nS, p); # baseline reported and predicted against bdef = zeros (nS, p); # baseline coxphfit centres on by default for s = 1:nS m = smask{s}; bdef(s,:) = sum (w(m) .* X_enc(m,:), 1) / sum (w(m)); if (base_given) bl = Baseline; if (isscalar (bl)) bl = repmat (bl, 1, p); endif brow(s,:) = bl(:)'; else brow(s,:) = bdef(s,:); brow(s,cat_enc) = 0; endif endfor ## coxphfit takes one baseline for every stratum, so a per-stratum ## baseline is reached by fitting on its own default and rescaling the ## hazard afterwards -- exact, the hazard being scaled by exp (B b) and ## nothing else. Only a stratified model with a categorical predictor ## needs it; anything else states its baseline outright. rescale = false; if (base_given) args = [args, {'Baseline', Baseline}]; elseif (nS == 1) args = [args, {'Baseline', brow(1,:)}]; elseif (any (cat_enc)) rescale = true; endif [b, logl, H, stats] = coxphfit (X_enc, T_val, args{:}); if (rescale) for s = 1:nS m = H(:,3) == slev(s); H(m,2) = H(m,2) * exp ((brow(s,:) - bdef(s,:)) * b); endfor endif ## --- properties --------------------------------------------------- this.b_ = b; this.X_ = X_enc; this.T_ = T_val; this.cens_ = Censoring; this.freq_ = Frequency; this.strata_ = Strata; this.ties_ = Ties; this.catinfo_ = cat_info; this.catcols_ = cat_cols; this.catbase_ = cat_enc; this.NumPredictors = p_raw; this.LogLikelihood = logl; this.Hazard = H; this.PredictorNames = pred_names; this.ResponseName = resp_name; this.CoefficientCovariance = stats.covb; this.StandardError = stats.se(:); this.LikelihoodRatioTestPValue = stats.LikelihoodRatioTestP; this.Coefficients = table (b(:), stats.se(:), stats.z(:), stats.p(:), ... 'VariableNames', {'Beta', 'SE', 'zStat', 'pValue'}, ... 'RowNames', enc_names(:)); this.Residuals = table (stats.csres, stats.devres, stats.martres, ... stats.schres, stats.sschres, stats.scores, stats.sscores, ... 'VariableNames', {'CoxSnell', 'Deviance', 'Martingale', ... 'Schoenfeld', 'ScaledSchoenfeld', 'Score', ... 'ScaledScore'}); ## The baseline actually used, one row per stratum, kept for prediction. ## Reported as given when it was given -- a scalar stays a scalar -- and ## otherwise as the rows the fit was centred on. this.Stratification = slev; this.baserow_ = brow; if (base_given) this.Baseline = Baseline; else this.Baseline = brow; endif ## --- formula and per-variable information ------------------------- var_names_all = [pred_names, {resp_name}]; terms = [eye(p_raw), zeros(p_raw, 1)]; this.Formula = LinearFormula (terms, var_names_all, ... 'ResponseName', resp_name); this.formulastr_ = char (this.Formula); vi_class = cell (p_raw + 1, 1); vi_range = cell (p_raw + 1, 1); vi_inmodel = [true(p_raw, 1); false]; vi_iscat = [cat_cols(:); false]; for j = 1:p_raw if (istbl) col = tbl_p.(pred_names{j}); else col = X_raw(:, j); endif [vi_class{j}, vi_range{j}] = variable_class_and_range (col); endfor [vi_class{end}, vi_range{end}] = variable_class_and_range (T_val(:,end)); this.VariableInfo = table (vi_class, vi_range, vi_inmodel, vi_iscat, ... 'VariableNames', {'Class', 'Range', 'InModel', 'IsCategorical'}, ... 'RowNames', var_names_all(:)); ## --- the proportional hazards assumption -------------------------- [phz, phg] = ph_assumption_test (stats, T_val); this.ProportionalHazardsPValue = phz; this.ProportionalHazardsPValueGlobal = phg; endfunction ## -*- texinfo -*- ## @deftypefn {CoxModel} {@var{ci} =} coefci (@var{obj}) ## @deftypefnx {CoxModel} {@var{ci} =} coefci (@var{obj}, @var{level}) ## ## Confidence intervals for the coefficients of a Cox model. ## ## @code{@var{ci} = coefci (@var{obj})} returns a two-column matrix with one ## row per coefficient, holding the 95% confidence interval of each. ## ## @code{@var{ci} = coefci (@var{obj}, @var{level})} uses a ## @code{100 (1 - @var{level})}% interval. @var{level} must be a positive ## scalar smaller than 1; it is a significance level, not a coverage. ## ## @end deftypefn function ci = coefci (this, level) if (nargin < 2) level = 0.05; endif if (! (isnumeric (level) && isreal (level) && isscalar (level) && level > 0 && level < 1)) error (strcat ("CoxModel.coefci: LEVEL must be a real scalar", ... " greater than 0 and smaller than 1.")); endif z = norminv (1 - level / 2); se = this.StandardError(:); b = this.b_(:); ci = [b - z * se, b + z * se]; endfunction ## -*- texinfo -*- ## @deftypefn {CoxModel} {@var{obj} =} discardResiduals (@var{obj}) ## ## Drop the stored residuals of a Cox model. ## ## @code{@var{obj} = discardResiduals (@var{obj})} returns the model with an ## empty @code{Residuals} property. The residual table holds one row per ## observation and is the largest thing a fitted model carries, so ## discarding it makes a model that is only going to be used for prediction ## considerably smaller. Nothing else about the model changes. ## ## @end deftypefn function this = discardResiduals (this) this.Residuals = table (); endfunction ## -*- texinfo -*- ## @deftypefn {CoxModel} {@var{hr} =} hazardratio (@var{obj}, @var{X}) ## @deftypefnx {CoxModel} {@var{hr} =} hazardratio (@var{obj}, @var{X}, @var{S}) ## @deftypefnx {CoxModel} {@var{hr} =} hazardratio (@dots{}, @qcode{"Baseline"}, @var{B}) ## ## Hazard of a Cox model relative to its baseline. ## ## @code{@var{hr} = hazardratio (@var{obj}, @var{X})} returns the hazard at ## the predictor values @var{X} relative to the baseline the model was ## fitted with, @code{exp ((@var{X} - @var{B}) * @var{b})}. @var{X} has one ## row per evaluation point and is a numeric matrix, or a table when the ## model was fitted from one. ## ## @code{@var{hr} = hazardratio (@var{obj}, @var{X}, @var{S})} gives the ## stratum of each row of @var{X}, and is required when the model is ## stratified, each stratum having its own baseline. ## ## @code{@var{hr} = hazardratio (@dots{}, "Baseline", @var{B})} evaluates ## the ratio against the baseline @var{B} instead, either a scalar or a row ## vector with one element per encoded predictor column. ## ## @end deftypefn function hr = hazardratio (this, varargin) if (numel (varargin) < 1) error ("CoxModel.hazardratio: X is required."); endif [Xq, Sq, opts] = split_prediction_args (this, 'hazardratio', varargin); base = prediction_baseline (this, Sq, opts.Baseline, 'hazardratio'); hr = exp (sum ((Xq - base) .* this.b_(:)', 2)); endfunction ## -*- texinfo -*- ## @deftypefn {CoxModel} {@var{tbl} =} linhyptest (@var{obj}) ## ## Sequential tests on the coefficients of a Cox model. ## ## @code{@var{tbl} = linhyptest (@var{obj})} returns a table with one row ## per predictor, whose @math{k}-th row tests the hypothesis that the ## coefficients of the @math{k}-th and every later predictor are jointly ## zero. The @code{Predictor} column names the predictors the hypothesis ## leaves in the model, so its first row reads @qcode{"Empty Model"} and ## tests every coefficient at once, and its last row tests the last ## coefficient alone, reproducing that coefficient's own p-value. ## ## Each test is a Wald test on the fitted model, not a refit. ## ## @end deftypefn function tbl = linhyptest (this) p = numel (this.b_); pvals = zeros (p, 1); names = cell (p, 1); for k = 1:p idx = k:p; bk = this.b_(idx); Vk = this.CoefficientCovariance(idx, idx); stat = bk(:)' * (Vk \ bk(:)); pvals(k) = 1 - chi2cdf (stat, numel (idx)); if (k == 1) names{k} = 'Empty Model'; else names{k} = strjoin (this.Coefficients.Properties.RowNames(1:k-1)', ... ', '); endif endfor tbl = table (names, pvals, ... 'VariableNames', {'Predictor', 'pValue'}); endfunction ## -*- texinfo -*- ## @deftypefn {CoxModel} {} plotSurvival (@var{obj}) ## @deftypefnx {CoxModel} {} plotSurvival (@var{obj}, @var{X}) ## @deftypefnx {CoxModel} {} plotSurvival (@var{obj}, @var{X}, @var{S}) ## @deftypefnx {CoxModel} {@var{h} =} plotSurvival (@dots{}) ## ## Plot the survival function of a Cox model. ## ## @code{plotSurvival (@var{obj})} draws the survival function at the ## model's baseline as a stairstep plot. @code{plotSurvival (@var{obj}, ## @var{X})} draws it at the predictor values @var{X}, one curve per row, ## and @var{S} gives the stratum of each row when the model is stratified. ## A stratified model with no @var{X} draws one curve per stratum. ## ## @code{@var{h} = plotSurvival (@dots{})} returns the handles of the ## stairstep lines. ## ## @end deftypefn function varargout = plotSurvival (this, varargin) [s, tout] = survival (this, varargin{:}); ## Unstratified curves come back as the columns of a matrix over one ## grid; stratified ones as a cell array, each on its own grid. if (iscell (s)) curves = s; times = tout; else curves = num2cell (s, 1); times = repmat ({tout}, 1, columns (s)); endif h = zeros (numel (curves), 1); hold_state = ishold (); for k = 1:numel (curves) h(k) = stairs (times{k}, curves{k}); if (k == 1) hold on; endif endfor if (! hold_state) hold off; endif xlabel (this.ResponseName); ylabel ("Survival probability"); title ("Cox proportional hazards model"); if (nargout > 0) varargout{1} = h; endif endfunction ## -*- texinfo -*- ## @deftypefn {CoxModel} {@var{s} =} survival (@var{obj}) ## @deftypefnx {CoxModel} {@var{s} =} survival (@var{obj}, @var{X}) ## @deftypefnx {CoxModel} {@var{s} =} survival (@var{obj}, @var{X}, @var{S}) ## @deftypefnx {CoxModel} {@var{s} =} survival (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {CoxModel} {[@var{s}, @var{T}] =} survival (@dots{}) ## ## Survival function of a Cox model. ## ## @code{@var{s} = survival (@var{obj})} returns the survival probability at ## the model's baseline, evaluated at each row of the @code{Hazard} ## property. @code{@var{s} = survival (@var{obj}, @var{X})} evaluates it at ## the predictor values @var{X}, and @var{S} gives the stratum of each row ## when the model is stratified. For a stratified model @var{s} is a cell ## array holding one column vector per curve. ## ## @code{[@var{s}, @var{T}] = survival (@dots{})} also returns the times the ## probabilities refer to. ## ## The following @var{Name}/@var{Value} pairs are accepted: ## ## @multitable @columnfractions 0.3 0.7 ## @headitem Name @tab Value ## @item @qcode{"Time"} @tab The times at which to evaluate the survival ## function. The default is the model's own event times. The baseline ## survival is interpolated linearly between them and raised to the hazard ## ratio of @var{X}. ## @item @qcode{"ExtrapolationMethod"} @tab How to evaluate a time outside ## the model's event times: @qcode{"nearest"} (default), @qcode{"linear"}, ## @qcode{"next"}, @qcode{"previous"}, or @qcode{"none"}. @qcode{"none"} ## returns @qcode{NaN} outside the range, as do @qcode{"next"} above it and ## @qcode{"previous"} below it. ## @end multitable ## ## @end deftypefn function [s, Tout] = survival (this, varargin) [Xq, Sq, opts] = split_prediction_args (this, 'survival', varargin); base = prediction_baseline (this, Sq, [], 'survival'); if (isempty (Xq)) hr = ones (max (1, numel (Sq)), 1); else hr = exp (sum ((Xq - base) .* this.b_(:)', 2)); endif strat = this.Stratification; if (isempty (strat)) grid_t = {this.Hazard(:,1)}; grid_H = {this.Hazard(:,2)}; which_grid = ones (numel (hr), 1); else nS = numel (strat); grid_t = cell (nS, 1); grid_H = cell (nS, 1); for k = 1:nS m = this.Hazard(:,3) == strat(k); grid_t{k} = this.Hazard(m,1); grid_H{k} = this.Hazard(m,2); endfor if (isempty (Sq)) which_grid = (1:nS)'; hr = ones (nS, 1); else [~, which_grid] = ismember (Sq(:), strat); endif endif n_out = numel (which_grid); s = cell (n_out, 1); Tout = cell (n_out, 1); for k = 1:n_out g = which_grid(k); tg = grid_t{g}; S0 = exp (-grid_H{g}); if (isempty (opts.Time)) s{k} = S0 .^ hr(k); Tout{k} = tg; else tq = opts.Time(:); s{k} = interp_survival (tg, S0, tq, opts.Extrap) .^ hr(k); Tout{k} = tq; endif endfor ## Unstratified curves all share one time grid, so they come back as the ## columns of a matrix, one per row of X. A stratified model gives each ## curve its own stratum's grid, so those stay a cell array. if (isempty (strat)) s = cell2mat (s(:)'); Tout = Tout{1}; else s = s(:)'; Tout = Tout(:)'; endif endfunction endmethods endclassdef ## Resolve a 'CategoricalPredictors' value to a logical mask over the ## predictors. function mask = categorical_mask (spec, pred_names) p = numel (pred_names); mask = false (1, p); if (islogical (spec)) if (numel (spec) != p) error (strcat ("CoxModel: a logical 'CategoricalPredictors' must", ... " have one element per predictor.")); endif mask = spec(:)'; elseif (isnumeric (spec)) if (any (spec != fix (spec)) || any (spec < 1) || any (spec > p)) error (strcat ("CoxModel: 'CategoricalPredictors' indices must be", ... " integers between 1 and the number of predictors.")); endif mask(spec) = true; elseif (iscellstr (spec) || ischar (spec)) if (ischar (spec)) spec = {spec}; endif for k = 1:numel (spec) j = find (strcmp (pred_names, spec{k}), 1); if (isempty (j)) error ("CoxModel: '%s' is not a predictor name.", spec{k}); endif mask(j) = true; endfor else error (strcat ("CoxModel: 'CategoricalPredictors' must be indices, a", ... " logical vector, or predictor names.")); endif endfunction ## Split the arguments of a prediction method into the predictor values, the ## stratum labels, and the name-value options. function [Xq, Sq, opts] = split_prediction_args (this, caller, args) opts = struct ('Time', [], 'Extrap', 'nearest', 'Baseline', []); ## Each method takes only its own options: 'Baseline' belongs to ## hazardratio, the times to survival. Accepting an option and ignoring it ## would claim a capability that is not there. if (strcmp (caller, 'hazardratio')) keys = {'baseline'}; else keys = {'time', 'extrapolationmethod'}; endif ## Every name is known to the scan that separates the positional arguments ## from the pairs, so that a name meant for the other method is reported as ## the wrong name rather than as a malformed argument list. scan_keys = {'time', 'extrapolationmethod', 'baseline'}; ## Positional arguments run until the first name that starts a pair. npos = 0; while (npos < numel (args) && npos < 2) a = args{npos+1}; if ((ischar (a) && isrow (a)) && any (strcmpi (a, scan_keys)) && numel (args) > npos + 1) break; endif npos++; endwhile Xq = []; Sq = []; if (npos >= 1) Xq = args{1}; endif if (npos >= 2) Sq = args{2}; endif rest = args(npos+1:end); if (mod (numel (rest), 2) != 0) error ("CoxModel.%s: optional arguments must be name-value pairs.", caller); endif for i = 1:2:numel (rest) name = rest{i}; if (! (ischar (name) && isrow (name))) error ("CoxModel.%s: parameter name must be a character vector.", caller); endif if (! any (strcmpi (name, keys))) error ("CoxModel.%s: unknown parameter name '%s'.", caller, name); endif switch (lower (name)) case 'time' t = rest{i+1}; if (! (isnumeric (t) && isreal (t) && isvector (t))) error ("CoxModel.%s: 'Time' must be a real vector.", caller); endif opts.Time = t; case 'extrapolationmethod' m = rest{i+1}; valid = {'nearest', 'linear', 'next', 'previous', 'none'}; if (! (ischar (m) && any (strcmpi (m, valid)))) error (strcat ("CoxModel.%s: 'ExtrapolationMethod' must be one", ... " of 'nearest', 'linear', 'next', 'previous', or", ... " 'none'."), caller); endif opts.Extrap = lower (m); case 'baseline' opts.Baseline = rest{i+1}; otherwise error ("CoxModel.%s: unknown parameter name '%s'.", caller, name); endswitch endfor ## Encode a table or raw predictor matrix the same way the fit did. if (! isempty (Xq)) Xq = encode_new_data (this, Xq, caller); endif if (! isempty (this.Stratification) && ! isempty (Xq) && isempty (Sq)) error (strcat ("CoxModel.%s: the model is stratified, so the stratum", ... " of each row of X is required."), caller); endif if (! isempty (Sq) && ! isempty (Xq) && numel (Sq) != rows (Xq)) error (strcat ("CoxModel.%s: S must have one element for each row", ... " of X."), caller); endif endfunction ## Encode new predictor values with the fitted model's own encoding. function Xq = encode_new_data (this, Xq, caller) if (isa (Xq, 'table')) names = Xq.Properties.VariableNames; X_num = zeros (rows (Xq), numel (this.PredictorNames)); for j = 1:numel (this.PredictorNames) k = find (strcmp (names, this.PredictorNames{j}), 1); if (isempty (k)) error ("CoxModel.%s: X has no variable named '%s'.", caller, ... this.PredictorNames{j}); endif col = Xq.(this.PredictorNames{j}); if (isa (col, 'categorical') || iscell (col)) levels = this.catinfo_.levels{j}; [tf, ic] = ismember (cellstr (col), levels); if (! all (tf)) error (strcat ("CoxModel.%s: X holds a level of '%s' that the", ... " model was not fitted with."), caller, ... this.PredictorNames{j}); endif X_num(:,j) = ic; else X_num(:,j) = double (col(:)); endif endfor Xq = encode_categorical (X_num, this.catcols_, this.PredictorNames, ... this.catinfo_.levels, true); else if (! (isnumeric (Xq) && isreal (Xq))) error ("CoxModel.%s: X must be a real numeric matrix.", caller); endif if (columns (Xq) == numel (this.PredictorNames) && any (this.catcols_)) Xq = encode_categorical (Xq, this.catcols_, this.PredictorNames, ... this.catinfo_.levels, true); endif endif if (columns (Xq) != numel (this.b_)) error (strcat ("CoxModel.%s: X must have one column for each", ... " predictor."), caller); endif endfunction ## The baseline each row of a prediction is measured against. function base = prediction_baseline (this, Sq, given, caller) p = numel (this.b_); if (! isempty (given)) if (! (isnumeric (given) && isreal (given) && (isscalar (given) || numel (given) == p))) error (strcat ("CoxModel.%s: 'Baseline' must be a scalar or a row", ... " vector with one element per predictor column."), caller); endif if (isscalar (given)) base = repmat (given, 1, p); else base = given(:)'; endif return; endif if (isempty (this.Stratification) || isempty (Sq)) base = this.baserow_(1,:); else [tf, idx] = ismember (Sq(:), this.Stratification); if (! all (tf)) error (strcat ("CoxModel.%s: S holds a stratum the model was not", ... " fitted with."), caller); endif base = this.baserow_(idx,:); endif endfunction ## Interpolate a baseline survival curve, honouring the extrapolation rule. function sq = interp_survival (tg, S0, tq, method) ## A repeated time carries two values -- the curve steps there -- and the ## lower one is what the interval to its right starts from. [tu, iu] = unique (tg(:), 'last'); su = S0(iu); below = tq < tu(1); above = tq > tu(end); if (numel (tu) < 2) ## One event time leaves nothing to interpolate between. sq = NaN (size (tq)); sq(tq == tu(1)) = su(1); else sq = interp1 (tu, su, tq, 'linear'); endif switch (method) case 'nearest' sq(below) = S0(1); sq(above) = su(end); case 'linear' sq(below) = S0(1); if (any (above) && numel (tu) > 1) slope = (su(end) - su(end-1)) / (tu(end) - tu(end-1)); sq(above) = min (1, max (0, su(end) + slope * (tq(above) - tu(end)))); endif case 'next' sq(below) = S0(1); sq(above) = NaN; case 'previous' sq(below) = NaN; sq(above) = su(end); case 'none' sq(below) = NaN; sq(above) = NaN; endswitch endfunction ## Grambsch-Therneau test of the proportional hazards assumption, on event ## times replaced by their ranks. The scaled Schoenfeld residual of a ## covariate is regressed on time; a slope that is not zero is a hazard ratio ## that moves with time, which is what proportionality denies. ## ## Tied event times take their mid-rank, the average of the ranks they span. ## Only ties tell the rankings apart -- dense ranks, ordinal ranks and ## mid-ranks agree on distinct times -- and the mid-rank is the one that ## reproduces MATLAB, measured under both tie-breaking methods. function [pz, pg] = ph_assumption_test (stats, T) ev = ! any (isnan (stats.schres), 2); if (! any (ev)) pz = NaN (1, columns (stats.schres)); pg = NaN; return; endif s = stats.schres(ev,:); t = T(ev,end); d = sum (ev); V = stats.covb; [ut, ~, ic] = unique (t); [~, ord] = sort (t); r = zeros (numel (t), 1); r(ord) = 1:numel (t); g = zeros (numel (t), 1); for j = 1:numel (ut) m = ic == j; g(m) = mean (r(m)); endfor w = g - mean (g); varx = sum (w .^ 2); if (varx == 0) pz = NaN (1, columns (s)); pg = NaN; return; endif ## The scaled Schoenfeld residual is beta + d V s; the constant drops out of ## the centred regression, so d V s is what is correlated with time. u = (w' * (s * V * d))'; z = (u .^ 2) ./ (varx * d * diag (V)); pz = (1 - chi2cdf (z, 1))'; pg = 1 - chi2cdf (u' * ((V * d * varx) \ u), columns (s)); endfunction %!demo %! ## Fit a Cox proportional hazards model and read its coefficients %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T) %!demo %! ## The survival function at the model's baseline %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! [s, t] = survival (mdl); %! [t, s] %!shared X, T, C, Tt, S, F, T2 %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! C = [0; 0; 1; 0; 0; 1; 0; 0; 1; 0]; %! Tt = [4; 4; 6; 6; 8; 8; 11; 11; 13; 13]; %! S = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! F = [1; 2; 1; 1; 3; 1; 1; 2; 1; 1]; %! T2 = [0 4; 0 6; 2 8; 0 11; 3 13; 0 16; 5 18; 0 21; 7 25; 0 30]; ## The fitted coefficients are those of coxphfit %!test %! mdl = CoxModel (X, T); %! assert_equal (mdl.Coefficients.Beta, ... %! [-1.3886093196382836; 4.3814437183613322], 1e-8); %! assert_equal (mdl.Coefficients.SE, ... %! [0.52737766743917369; 1.8537400210498443], 1e-8); %! assert_equal (mdl.Coefficients.zStat, ... %! [-2.6330453589001133; 2.3635696853973904], 1e-8); %! assert_equal (mdl.Coefficients.pValue, ... %! [0.0084623045168834322; 0.018099822319697333], 1e-10); %!test %! mdl = CoxModel (X, T); %! assert_equal (mdl.LogLikelihood, -8.8069639381632356, 1e-10); %! assert_equal (mdl.LikelihoodRatioTestPValue, 0.0018409958426933715, 1e-10); %! assert_equal (mdl.NumPredictors, 2); ## Coefficient table shape and names %!test %! mdl = CoxModel (X, T); %! assert_equal (mdl.Coefficients.Properties.VariableNames, ... %! {'Beta', 'SE', 'zStat', 'pValue'}); %! assert_equal (mdl.Coefficients.Properties.RowNames, {'X1'; 'X2'}); %! assert_equal (size (mdl.Coefficients), [2, 4]); ## The covariance, standard errors, and baseline %!test %! mdl = CoxModel (X, T); %! assert_equal (mdl.CoefficientCovariance, ... %! [0.27812720411358371, -0.88932589928247008; ... %! -0.88932589928247008, 3.4363520656418767], 1e-8); %! assert_equal (mdl.StandardError, ... %! [0.52737766743917369; 1.8537400210498443], 1e-8); %! assert_equal (mdl.Baseline, [5.9, 0.5], 1e-12); ## The baseline cumulative hazard, as coxphfit returns it %!test %! mdl = CoxModel (X, T); %! assert_equal (size (mdl.Hazard), [11, 2]); %! assert_equal (mdl.Hazard(1,:), [4, 0]); %! assert_equal (mdl.Hazard(end,2), 39.969396549779539, 1e-6); ## Names and formula are consistent with one another %!test %! mdl = CoxModel (X, T); %! assert_equal (mdl.PredictorNames, {'X1', 'X2'}); %! assert_equal (mdl.ResponseName, 'y'); %! assert_equal (char (mdl.Formula), 'y ~ X1 + X2'); %!test %! mdl = CoxModel (X, T, 'PredictorNames', {'age', 'trt'}); %! assert_equal (mdl.PredictorNames, {'age', 'trt'}); %! assert_equal (mdl.ResponseName, 'y'); %! assert_equal (char (mdl.Formula), 'y ~ age + trt'); %! assert_equal (mdl.Coefficients.Properties.RowNames, {'age'; 'trt'}); ## Per-variable information %!test %! mdl = CoxModel (X, T); %! assert_equal (mdl.VariableInfo.Properties.VariableNames, ... %! {'Class', 'Range', 'InModel', 'IsCategorical'}); %! assert_equal (mdl.VariableInfo.Properties.RowNames, {'X1'; 'X2'; 'y'}); %! assert_equal (mdl.VariableInfo.InModel, [true; true; false]); %! assert_equal (mdl.VariableInfo.IsCategorical, [false; false; false]); ## The residual table carries every type coxphfit computes %!test %! mdl = CoxModel (X, T); %! assert_equal (mdl.Residuals.Properties.VariableNames, ... %! {'CoxSnell', 'Deviance', 'Martingale', 'Schoenfeld', ... %! 'ScaledSchoenfeld', 'Score', 'ScaledScore'}); %! assert_equal (size (mdl.Residuals), [10, 7]); %! assert_equal (mdl.Residuals.Martingale(1), 0.6260391348376092, 1e-8); %!test %! mdl = CoxModel (X, T); %! mdl = discardResiduals (mdl); %! assert_equal (isempty (mdl.Residuals), true); %! assert_equal (mdl.LogLikelihood, -8.8069639381632356, 1e-10); ## The proportional hazards assumption test %!test %! mdl = CoxModel (X, T); %! assert_equal (mdl.ProportionalHazardsPValue, ... %! [0.43865495271858179, 0.71413497767000234], 1e-8); %! assert_equal (mdl.ProportionalHazardsPValueGlobal, ... %! 0.53179255135825187, 1e-8); %!test %! mdl = CoxModel (X, T, 'Censoring', C); %! assert_equal (mdl.ProportionalHazardsPValue, ... %! [0.38377818764906124, 0.62418297490071406], 1e-8); %! assert_equal (mdl.ProportionalHazardsPValueGlobal, ... %! 0.56302705208766479, 1e-8); ## Tied event times take their mid-rank; only a tie tells the rankings apart %!test %! mdl = CoxModel (X, Tt, 'Censoring', C); %! assert_equal (mdl.ProportionalHazardsPValue, ... %! [0.21978449883707185, 0.47185588628217012], 1e-8); %! assert_equal (mdl.ProportionalHazardsPValueGlobal, ... %! 0.33816199611424813, 1e-8); %!test %! mdl = CoxModel (X, Tt, 'Censoring', C, 'TieBreakMethod', 'efron'); %! assert_equal (mdl.Coefficients.SE, ... %! [0.40807722610877606; 1.7349017938498392], 1e-8); %! assert_equal (mdl.ProportionalHazardsPValue, ... %! [0.1946475346918406, 0.44447162515215921], 1e-8); %! assert_equal (mdl.ProportionalHazardsPValueGlobal, ... %! 0.29473648771539396, 1e-8); ## Confidence intervals %!test %! mdl = CoxModel (X, T); %! assert_equal (coefci (mdl), ... %! [-2.422250554069806, -0.35496808520676093; ... %! 0.74818004040311514, 8.0147073963195492], 1e-8); %! assert_equal (coefci (mdl, 0.01), ... %! [-2.747044169465374, -0.030174469811192983; ... %! -0.39347414902021249, 9.1563615857428768], 1e-8); ## Sequential Wald tests %!test %! mdl = CoxModel ([X, [1;3;2;5;4;6;8;7;9;10]], T); %! tbl = linhyptest (mdl); %! assert_equal (size (tbl), [3, 2]); %! assert_equal (tbl.Predictor, {'Empty Model'; 'X1'; 'X1, X2'}); %! assert_equal (tbl.pValue, [0.11426498364159436; 0.066700738069398954; ... %! 0.040978941270458896], 1e-8); ## The last sequential test is the last coefficient's own p-value %!test %! mdl = CoxModel ([X, [1;3;2;5;4;6;8;7;9;10]], T); %! tbl = linhyptest (mdl); %! assert_equal (tbl.pValue(end), mdl.Coefficients.pValue(end), 1e-10); ## Hazard ratios %!test %! mdl = CoxModel (X, T); %! assert_equal (hazardratio (mdl, X(1,:)), 25.149914260347884, 1e-6); %! assert_equal (hazardratio (mdl, X(1,:), 'Baseline', 0), ... %! 0.062211299031685895, 1e-8); %!test %! mdl = CoxModel (X, T); %! hr = hazardratio (mdl, X); %! assert_equal (numel (hr), 10); %! assert_equal (hr(end), 0.030119684486042686, 1e-8); ## The survival function %!test %! mdl = CoxModel (X, T); %! [s, t] = survival (mdl); %! assert_equal (numel (s), 11); %! assert_equal (s(1), 1); %! assert_equal (s(2), 0.98524073172292947, 1e-8); %! assert_equal (t, mdl.Hazard(:,1)); %!test %! mdl = CoxModel (X, T); %! s = survival (mdl, X(1,:)); %! assert_equal (s(2), 0.6880038362205394, 1e-8); %! assert_equal (s(3), 0.37858862123606984, 1e-8); ## Interpolation is linear on the survival scale, then raised to the ratio %!test %! mdl = CoxModel (X, T); %! s = survival (mdl, 'Time', [4; 5; 6; 7]); %! assert_equal (s, [0.98524073172292947; 0.97367819309820003; ... %! 0.96211565447347058; 0.91995051472240608], 1e-8); %!test %! mdl = CoxModel (X, T); %! s = survival (mdl, X(1,:), 'Time', [5; 12; 20]); %! assert_equal (s, [0.51126892454645156; 9.5014819976515837e-06; ... %! 1.7030500478184053e-37], 1e-8); ## Outside the event times, the extrapolation rule decides %!test %! mdl = CoxModel (X, T); %! tq = [1; 2; 3; 31; 40]; %! assert_equal (survival (mdl, 'Time', tq), ... %! [1; 1; 1; 4.3803784471438163e-18; ... %! 4.3803784471438163e-18], 1e-8); %! assert_equal (survival (mdl, 'Time', tq, 'ExtrapolationMethod', 'none'), ... %! [NaN; NaN; NaN; NaN; NaN]); %!test %! mdl = CoxModel (X, T); %! tq = [1; 2; 3; 31; 40]; %! s = survival (mdl, 'Time', tq, 'ExtrapolationMethod', 'previous'); %! assert_equal (isnan (s(1:3)), [true; true; true]); %! assert_equal (s(4), 4.3803784471438163e-18, 1e-8); %! s = survival (mdl, 'Time', tq, 'ExtrapolationMethod', 'next'); %! assert_equal (s(1:3), [1; 1; 1]); %! assert_equal (isnan (s(4:5)), [true; true]); ## Stratification %!test %! mdl = CoxModel (X, T, 'Censoring', C, 'Stratification', S); %! assert_equal (mdl.Stratification, [1; 2]); %! assert_equal (size (mdl.Hazard), [9, 3]); %! assert_equal (mdl.Coefficients.Beta, ... %! [-0.7705046389; 3.1181961544], 1e-6); %! assert_equal (mdl.ProportionalHazardsPValue, ... %! [0.59926200661233664, 0.7801291832899343], 1e-6); %!test %! mdl = CoxModel (X, T, 'Censoring', C, 'Stratification', S); %! s = survival (mdl); %! assert_equal (iscell (s), true); %! assert_equal (numel (s), 2); %! assert_equal (numel (s{1}), 5); %! assert_equal (numel (s{2}), 4); %!test %! mdl = CoxModel (X, T, 'Censoring', C, 'Stratification', S); %! assert_equal (hazardratio (mdl, X(1,:), 1), 1.825643762907144, 1e-6); ## Unstratified curves are the columns of a matrix, one per row of X %!test %! mdl = CoxModel (X, T); %! [s, t] = survival (mdl, X(1:3,:)); %! assert_equal (size (s), [11, 3]); %! assert_equal (size (t), [11, 1]); %! assert_equal (s(2,1), 0.6880038362205394, 1e-8); %! assert_equal (s(2,2), 0.62879787425614297, 1e-8); %! assert_equal (s(3,3), 0.78484832904143043, 1e-8); ## A stratified curve follows the stratum of its own row of X %!test %! mdl = CoxModel (X, T, 'Censoring', C, 'Stratification', S); %! s = survival (mdl, X(1:2,:), [2; 1]); %! assert_equal (iscell (s), true); %! assert_equal (numel (s{1}), 4); %! assert_equal (numel (s{2}), 5); ## A baseline given to the method overrides the fitted one %!test %! mdl = CoxModel (X, T); %! assert_equal (hazardratio (mdl, X(1,:), 'Baseline', [1 1]), ... %! 0.0031195920528550632, 1e-8); ## Discarding the residuals leaves prediction intact %!test %! mdl = discardResiduals (CoxModel (X, T)); %! s = survival (mdl, X(1,:)); %! assert_equal (s(2), 0.6880038362205394, 1e-8); ## Frequency weights may be given as a row %!test %! mdl = CoxModel (X, T, 'Frequency', F'); %! assert_equal (mdl.Coefficients.Beta, ... %! [-1.4447610177189139; 4.6300103378728581], 1e-6); ## plotSurvival draws one stairstep line per curve %!test %! f = figure ('visible', 'off'); %! unwind_protect %! mdl = CoxModel (X, T, 'Censoring', C, 'Stratification', S); %! h = plotSurvival (mdl); %! assert_equal (numel (h), 2); %! assert_equal (all (ishghandle (h)), true); %! unwind_protect_cleanup %! close (f); %! end_unwind_protect ## One line per curve, a curve per row of X when the model is not stratified %!test %! f = figure ('visible', 'off'); %! unwind_protect %! mdl = CoxModel (X, T); %! assert_equal (numel (plotSurvival (mdl)), 1); %! assert_equal (numel (plotSurvival (mdl, X(1:3,:))), 3); %! unwind_protect_cleanup %! close (f); %! end_unwind_protect ## The counting process form %!test %! mdl = CoxModel (X, T2, 'Censoring', C); %! assert_equal (mdl.Coefficients.Beta, [-1.0104; 3.2523], 1e-4); ## Frequency weights %!test %! mdl = CoxModel (X, T, 'Frequency', F); %! assert_equal (mdl.Coefficients.Beta, ... %! [-1.4447610177189139; 4.6300103378728581], 1e-6); ## A starting value changes nothing but the path taken to the answer %!test %! mdl = CoxModel (X, T, 'Beta', [0.1; -0.1]); %! assert_equal (mdl.Coefficients.Beta, ... %! [-1.3886093196382836; 4.3814437183613322], 1e-6); ## An options structure is accepted %!test %! mdl = CoxModel (X, T, 'OptimizationOptions', statset ('fitcox')); %! assert_equal (mdl.LogLikelihood, -8.8069639381632356, 1e-10); ## Errors %!error CoxModel (1) %!error CoxModel ({1}, [1; 2]) %!error ... %! CoxModel (X, T, 'Censoring') %!error ... %! CoxModel (X, T, 'Ties', 'efron') %!error ... %! CoxModel (X, T, 'TieBreakMethod', 'exact') %!error ... %! CoxModel (X, T, 'PredictorNames', {'only_one'}) %!error ... %! coefci (CoxModel (X, T), 1.5) %!error hazardratio (CoxModel (X, T)) %!error ... %! survival (CoxModel (X, T), X(1,:), 'Baseline', 0) %!error ... %! hazardratio (CoxModel (X, T), X(1,:), 'Time', 5) %!error ... %! survival (CoxModel (X, T), 'Time', 5, 'ExtrapolationMethod', 'cubic') %!error ... %! survival (CoxModel (X, T, 'Stratification', S), X(1,:)) %!error ... %! CoxModel (X, T)(1) %!error ... %! CoxModel (X, T).nosuch statistics-release-1.9.2/inst/Regression/GeneralizedLinearMixedModel.m000066400000000000000000000515731524624707500261710ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} GeneralizedLinearMixedModel ## ## Generalized linear mixed-effects model fitted to data. ## ## A @code{GeneralizedLinearMixedModel} object represents a fitted generalized ## linear mixed-effects model: a generalized linear model whose linear predictor ## @code{X*beta + Z*b} includes normally distributed random effects ## @code{b ~ N(0, Psi)}. Objects are created with @code{fitglme}. ## ## The model is fitted by penalized quasi-likelihood. The fixed-effects ## estimates and their statistics are available through the @code{Coefficients} ## table, the covariance parameters through @code{covarianceParameters}, and ## predictions, residuals, and hypothesis tests through the @code{predict}, ## @code{residuals}, @code{anova}, @code{coefTest}, and @code{coefCI} methods. ## ## @seealso{fitglme, fitlme, GeneralizedLinearModel} ## @end deftypefn classdef GeneralizedLinearMixedModel properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} Distribution ## ## Response distribution ## ## A character vector naming the distribution of the response, one of ## @qcode{'binomial'}, @qcode{'poisson'} and @qcode{'normal'}. This ## property is read-only. ## ## @end deftp Distribution = ""; ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} Link ## ## Link function ## ## A character vector naming the link, one of @qcode{'logit'}, ## @qcode{'log'} and @qcode{'identity'}. This property is read-only. ## ## @end deftp Link = ""; ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} FitMethod ## ## Estimation method ## ## A character vector naming the method that fitted the model, one of ## @qcode{'MPL'}, @qcode{'REMPL'}, @qcode{'Laplace'} and ## @qcode{'ApproximateLaplace'}. This property is read-only. ## ## @end deftp FitMethod = ""; ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} Dispersion ## ## Dispersion parameter ## ## A positive scalar. It is estimated for a normal response and fixed at ## 1 for a binomial or Poisson one. This property is read-only. ## ## @end deftp Dispersion = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} NumObservations ## ## Number of observations ## ## A positive integer counting the observations used for the fit. This ## property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} NumCoefficients ## ## Number of fixed-effects coefficients ## ## A positive integer counting the fixed-effects coefficients of the ## model. This property is read-only. ## ## @end deftp NumCoefficients = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} Coefficients ## ## Fixed-effects estimates and their statistics ## ## A table with one row per fixed-effects coefficient, its row names taken ## from @qcode{CoefficientNames}, and the variables @qcode{Estimate}, ## @qcode{SE}, @qcode{tStat}, @qcode{DF}, @qcode{pValue}, @qcode{Lower} ## and @qcode{Upper}. @qcode{Lower} and @qcode{Upper} bound a 95% ## confidence interval. This property is read-only. ## ## @end deftp Coefficients = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} CoefficientCovariance ## ## Covariance of the fixed-effects estimates ## ## A square numeric matrix, one row and column per fixed-effects ## coefficient, holding the estimated covariance of the estimates in ## @qcode{Coefficients}. This property is read-only. ## ## @end deftp CoefficientCovariance = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} CoefficientNames ## ## Names of the fixed-effects coefficients ## ## A cell array of character vectors with one name per fixed-effects ## coefficient. This property is read-only. ## ## @end deftp CoefficientNames = {}; ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} LogLikelihood ## ## Log-likelihood of the fitted model ## ## A scalar. It is a pseudo log-likelihood when @qcode{FitMethod} is ## @qcode{'MPL'} or @qcode{'REMPL'}, and a Laplace approximation to the ## log-likelihood otherwise. This property is read-only. ## ## @end deftp LogLikelihood = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} ModelCriterion ## ## Information criteria ## ## A scalar structure with the fields @qcode{AIC}, @qcode{BIC}, ## @qcode{LogLikelihood} and @qcode{Deviance}. The parameter count behind ## @qcode{AIC} and @qcode{BIC} holds the fixed-effects coefficients, the ## covariance parameters and the dispersion. This property is read-only. ## ## @end deftp ModelCriterion = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} DFE ## ## Residual degrees of freedom ## ## A nonnegative integer, @qcode{NumObservations} less ## @qcode{NumCoefficients}. This property is read-only. ## ## @end deftp DFE = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} Formula ## ## Model formula ## ## A character vector describing the model. It is empty for a fit built ## from design matrices, which carries no formula. This property is ## read-only. ## ## @end deftp Formula = ""; ## -*- texinfo -*- ## @deftp {GeneralizedLinearMixedModel} {property} ResponseName ## ## Name of the response variable ## ## A character vector naming the response. It is empty for a fit built ## from design matrices. This property is read-only. ## ## @end deftp ResponseName = ""; endproperties properties (Access = private, Hidden) X_ = []; y_ = []; beta_ = []; covbeta_ = []; Psi_ = {}; b_ = []; mu_ = []; resraw_ = []; respear_ = []; Zx_ = []; qk_ = []; nlev_ = []; levels_ = {}; gidx_ = {}; GroupNames_ = {}; REPred_ = {}; distr_ = ""; link_ = ""; endproperties methods (Hidden) function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ("%s =\n", in_name); endif disp (this); endfunction function disp (this) fprintf ("\n Generalized linear mixed-effects model fit by %s\n", ... this.FitMethod); fprintf (" Distribution: %s, Link: %s\n", ... this.Distribution, this.Link); if (! isempty (this.Formula)) fprintf ("\n Formula:\n %s\n", this.Formula); endif if (! isempty (this.Coefficients)) fprintf ("\n Fixed effects coefficients:\n\n"); disp (this.Coefficients); endif if (! isempty (this.Psi_)) fprintf ("\n Random effects covariance parameters:\n"); for k = 1:numel (this.Psi_) fprintf (" Group: %s (%d levels)\n", this.GroupNames_{k}, ... this.nlev_(k)); disp (this.Psi_{k}); endfor endif fprintf ("\n"); if (! isempty (this.NumObservations)) fprintf ("Number of observations: %d, Error DF: %d\n", ... this.NumObservations, this.DFE); endif if (! isempty (this.LogLikelihood)) fprintf ("Log-likelihood: %g\n", this.LogLikelihood); endif endfunction function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case "()" error (strcat ("GeneralizedLinearMixedModel: () indexing is not", ... " supported; use dot notation.")); case "{}" error (strcat ("GeneralizedLinearMixedModel: {} indexing is not", ... " supported; use dot notation.")); case "." if (! ischar (s.subs)) error ("GeneralizedLinearMixedModel.subsref: invalid index."); endif if (ismethod (this, s.subs)) [varargout{1:nargout}] = builtin ("subsref", this, [s, chain_s]); return; endif try out = this.(s.subs); catch error (strcat ("GeneralizedLinearMixedModel.subsref: ", ... "unknown property '%s'."), s.subs); end_try_catch endswitch if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearMixedModel} {@var{glme} =} GeneralizedLinearMixedModel (@var{info}) ## ## Construct from a fitted-model info struct. Used internally by ## @code{fitglme}; call that function rather than the constructor directly. ## ## @end deftypefn function this = GeneralizedLinearMixedModel (info) if (nargin == 0) return; endif n = info.n; p = info.p; this.X_ = info.X; this.y_ = info.y; this.beta_ = info.beta; this.covbeta_ = info.covbeta; this.Psi_ = info.Psi; this.b_ = info.b; this.mu_ = info.mu; this.resraw_ = info.resid_raw; this.respear_ = info.resid_pearson; this.Zx_ = info.Zx; this.qk_ = info.qk; this.nlev_ = info.nlev; this.levels_ = info.levels; this.gidx_ = info.gidx; this.GroupNames_ = info.GroupNames; this.REPred_ = info.REPred; this.distr_ = info.distr; this.link_ = info.link; this.Distribution = info.distr; this.Link = info.link; this.FitMethod = info.FitMethod; this.Dispersion = info.dispersion; this.NumObservations = n; this.NumCoefficients = p; this.CoefficientNames = info.CoefficientNames; this.CoefficientCovariance = info.covbeta; this.LogLikelihood = info.loglik; this.DFE = n - p; if (isfield (info, "Formula")), this.Formula = info.Formula; endif if (isfield (info, "ResponseName")) this.ResponseName = info.ResponseName; endif se = sqrt (diag (info.covbeta)); tstat = info.beta ./ se; dfe = n - p; pval = 2 * (1 - tcdf (abs (tstat), dfe)); tcrit = tinv (0.975, dfe); this.Coefficients = table (info.beta(:), se(:), tstat(:), ... repmat (dfe, p, 1), pval(:), info.beta(:) - tcrit*se(:), ... info.beta(:) + tcrit*se(:), ... "VariableNames", {"Estimate", "SE", "tStat", "DF", "pValue", ... "Lower", "Upper"}, ... "RowNames", info.CoefficientNames(:)); ncov = sum (arrayfun (@(q) q*(q+1)/2, info.qk)) + 1; kpar = p + ncov; dev = -2 * info.loglik; mc.AIC = dev + 2 * kpar; mc.BIC = dev + kpar * log (n); mc.LogLikelihood = info.loglik; mc.Deviance = dev; this.ModelCriterion = mc; endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearMixedModel} {[@var{beta}, @var{names}] =} fixedEffects (@var{glme}) ## Return the fixed-effects coefficients and, optionally, their names. ## @end deftypefn function [beta, names] = fixedEffects (this) beta = this.beta_; names = this.CoefficientNames; endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearMixedModel} {@var{b} =} randomEffects (@var{glme}) ## Return the estimated random-effects (the conditional modes). ## @end deftypefn function b = randomEffects (this) b = this.b_; endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearMixedModel} {[@var{psi}, @var{disp}] =} covarianceParameters (@var{glme}) ## Return the random-effects covariance matrices and the dispersion. ## @end deftypefn function [psi, dispn] = covarianceParameters (this) psi = this.Psi_; dispn = this.Dispersion; endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearMixedModel} {@var{yf} =} fitted (@var{glme}) ## Return the fitted mean response (conditional on the random effects). ## @end deftypefn function yf = fitted (this) yf = this.mu_; endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearMixedModel} {@var{r} =} residuals (@var{glme}) ## @deftypefnx {GeneralizedLinearMixedModel} {@var{r} =} residuals (@var{glme}, @qcode{"ResidualType"}, @var{type}) ## Return @qcode{"Raw"} (default) or @qcode{"Pearson"} residuals. ## @end deftypefn function r = residuals (this, varargin) type = "raw"; if (numel (varargin) >= 2 && strcmpi (varargin{1}, "ResidualType")) type = lower (varargin{2}); endif switch (type) case "raw" r = this.resraw_; case "pearson" r = this.respear_; otherwise error (strcat ("GeneralizedLinearMixedModel: unknown", ... " ResidualType '%s'."), type); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearMixedModel} {@var{ypred} =} predict (@var{glme}, @var{Xnew}, @var{Znew}, @var{Gnew}) ## Predict the mean response at new data. With @qcode{"Conditional"} true ## (default) the random effects of known grouping levels are added; ## unknown levels fall back to the marginal (fixed-effects) prediction. ## @end deftypefn function ypred = predict (this, Xnew, Znew, Gnew, varargin) if (nargin < 4), Znew = []; Gnew = []; endif cond = true; for i = 1:2:numel (varargin) if (strcmpi (varargin{i}, "Conditional")) cond = logical (varargin{i+1}); endif endfor eta = Xnew * this.beta_; if (cond && ! isempty (Znew) && ! isempty (Gnew)) Gnew = Gnew(:); q = this.qk_(1); lev = this.levels_{1}; for i = 1:rows (Xnew) li = find (lev == Gnew(i), 1); if (! isempty (li)) eta(i) += Znew(i, :) * this.b_((li-1)*q + (1:q)); endif endfor endif ypred = link_inv (eta, this.link_); endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearMixedModel} {@var{tbl} =} anova (@var{glme}) ## Analysis-of-deviance table of F-tests for the fixed-effects terms, using ## residual denominator degrees of freedom. ## @end deftypefn function tbl = anova (this) p = this.NumCoefficients; se = sqrt (diag (this.covbeta_)); Fstat = (this.beta_ ./ se) .^ 2; DF1 = ones (p, 1); DF2 = repmat (this.DFE, p, 1); pValue = 1 - fcdf (Fstat, DF1, DF2); tbl = table (Fstat(:), DF1(:), DF2(:), pValue(:), ... "VariableNames", {"FStat", "DF1", "DF2", "pValue"}, ... "RowNames", this.CoefficientNames(:)); endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearMixedModel} {[@var{p}, @var{F}, @var{df1}, @var{df2}] =} coefTest (@var{glme}, @var{H}) ## F-test of the linear hypothesis @code{H*beta = 0}. ## @end deftypefn function [pval, F, df1, df2] = coefTest (this, H) p = this.NumCoefficients; if (nargin < 2) H = [zeros(p-1, 1), eye(p-1)]; endif Hb = H * this.beta_; df1 = rank (H); df2 = this.DFE; F = (Hb' * ((H * this.covbeta_ * H') \ Hb)) / df1; pval = 1 - fcdf (F, df1, df2); endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearMixedModel} {@var{ci} =} coefCI (@var{glme}, @var{alpha}) ## Confidence intervals for the fixed-effects coefficients. ## @end deftypefn function ci = coefCI (this, alpha) if (nargin < 2), alpha = 0.05; endif se = sqrt (diag (this.covbeta_)); tcrit = tinv (1 - alpha/2, this.DFE); ci = [this.beta_ - tcrit * se, this.beta_ + tcrit * se]; endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearMixedModel} {@var{D} =} designMatrix (@var{glme}, @var{type}) ## Return the fixed (@qcode{"Fixed"}, default) or random (@qcode{"Random"}) ## design matrix. ## @end deftypefn function D = designMatrix (this, type) if (nargin < 2), type = "Fixed"; endif switch (lower (type)) case "fixed" D = this.X_; case "random" D = this.Zx_; otherwise error (strcat ("GeneralizedLinearMixedModel: type must be", ... " 'Fixed' or 'Random'.")); endswitch endfunction endmethods endclassdef ## Inverse link (canonical), shared with the fitting engine's convention. function mu = link_inv (eta, link) switch (lower (link)) case "logit" mu = 1 ./ (1 + exp (-eta)); case "log" mu = exp (eta); case "identity" mu = eta; otherwise error ("GeneralizedLinearMixedModel: unsupported link '%s'.", link); endswitch endfunction ## Shared MATLAB-verified fixture (fitglme R2026a): poisson random intercept. %!shared glme, tbl %! xL = [0.032760004 0.70410822 -0.8646718 -0.28869454 0.51276678 -1.4975462 ... %! -1.4527871 -0.80013541 -1.644209 1.5137701 0.72905543 0.20880758 1.0856145 ... %! 0.62862577 -0.87409978 1.9178276 0.09748204 0.50697633 1.0247569 ... %! -0.92789896 -0.88921018 -0.98322849 -0.031378913 0.86875961 -0.91481141 ... %! 0.034324163 -0.25025257 -1.0575644 -0.86131607 -0.35355444 0.82950729 ... %! -0.36874363 0.061580868 0.55803564 -0.1763803 1.0482413 1.0137831 ... %! -0.94876976 -0.010703972 -0.35149845 -1.6828735 -1.0493301]'; %! yPois = [3 3 1 1 1 1 1 2 1 5 2 0 5 0 1 5 0 2 0 0 1 0 0 5 3 0 1 0 0 1 1 1 2 2 ... %! 1 1 4 0 1 0 0 1]'; %! g = [1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 ... %! 6 1 2 3 4 5 6]'; %! tbl = table (yPois, xL, g); %! glme = fitglme (tbl, "yPois ~ xL + (1 | g)", "Distribution", "poisson", ... %! "FitMethod", "REMPL"); %!test # object type and basic properties %! assert_equal (isa (glme, "GeneralizedLinearMixedModel"), true); %! assert_equal (glme.NumObservations, 42); %! assert_equal (glme.NumCoefficients, 2); %! assert_equal (glme.DFE, 40); %! assert_equal (glme.Dispersion, 1); %!test # Coefficients table %! C = glme.Coefficients; %! assert_equal (C.Estimate, [0.23092; 0.67809], 1e-3); %! assert_equal (C.SE, [0.15395; 0.15220], 1e-3); %! assert_equal (C.DF, [40; 40]); %! assert_equal (C.tStat, C.Estimate ./ C.SE, 1e-10); %!test # effect extraction and covariance parameters %! [beta, names] = fixedEffects (glme); %! assert_equal (beta, glme.Coefficients.Estimate, 1e-12); %! assert_equal (names(:), {"(Intercept)"; "xL"}); %! b = randomEffects (glme); %! assert_equal (numel (b), 6); %! [psi, dispn] = covarianceParameters (glme); %! assert_equal (psi{1}, 0.015918, 1e-3); %! assert_equal (dispn, 1); %!test # anova F-tests: F = tStat^2 with residual DF %! a = anova (glme); %! assert_equal (a.FStat, (glme.Coefficients.tStat) .^ 2, 1e-8); %! assert_equal (a.DF2, [40; 40]); %!test # coefTest and coefCI %! [p, F, df1, df2] = coefTest (glme, [0 1]); %! assert_equal (F, glme.Coefficients.tStat(2) ^ 2, 1e-6); %! assert_equal (df1, 1); assert_equal (df2, 40); %! ci = coefCI (glme); %! assert_equal (ci(:,1), glme.Coefficients.Lower, 1e-12); %!test # fitted values are positive counts (log link) and residuals sum sensibly %! mu = fitted (glme); %! assert_equal (all (mu > 0, 'all'), true); %! assert_equal (residuals (glme), tbl.yPois - mu, 1e-12); %! assert_equal (residuals (glme, "ResidualType", "Pearson"), ... %! (tbl.yPois - mu) ./ sqrt (mu), 1e-10); %!test # predict: conditional (known group) and marginal (unseen group) %! ym = predict (glme, [1 0.5], [], [], "Conditional", false); %! yc = predict (glme, [1 0.5], 1, 1); %! yu = predict (glme, [1 0.5], 1, 99); # unseen group -> marginal %! assert_equal (ym, exp (glme.Coefficients.Estimate' * [1; 0.5]), 1e-10); %! assert_equal (yu, ym, 1e-12); %!test # designMatrix and ModelCriterion %! assert_equal (size (designMatrix (glme, "Fixed")), [42, 2]); %! assert_equal (size (designMatrix (glme, "Random")), [42, 6]); %! assert_equal (glme.ModelCriterion.Deviance, -2 * glme.LogLikelihood, 1e-10); ## Error handling %!error residuals (glme, "ResidualType", "xxx") %!error designMatrix (glme, "bogus") %!error glme(1) statistics-release-1.9.2/inst/Regression/GeneralizedLinearModel.m000066400000000000000000003172051524624707500251770ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftp {statistics} {} GeneralizedLinearModel ## ## Generalized linear regression model class. ## ## A @code{GeneralizedLinearModel} object encapsulates a generalized linear ## model (GLM) of a response on one or more predictors, fitted by iteratively ## reweighted least squares. It is the GLM counterpart of @code{LinearModel} ## and is normally created with the @code{fitglm} function. ## ## The response is modelled through a distribution from the exponential family ## (@qcode{'normal'}, @qcode{'binomial'}, @qcode{'poisson'}, @qcode{'gamma'}, or ## @qcode{'inverse gaussian'}) and a link function @math{g} relating the mean ## @math{mu} to the linear predictor @math{eta = g (mu)}. ## ## The most useful properties are @code{Coefficients} (a table of estimates, ## standard errors, @math{t}-statistics and p-values), @code{Deviance}, ## @code{Dispersion}, @code{Residuals}, @code{Fitted}, @code{Diagnostics}, ## @code{Distribution}, and @code{Link}. @code{ObservationInfo} records which ## rows were weighted, excluded, or missing, and @code{Variables} holds the data ## the model was built from. For a binomial response given as an ## @math{n}-by-@math{2} matrix of successes and trials, @code{Variables} holds ## the @strong{success count} alone, that being the response the model fits; ## MATLAB stores both columns there. Fitted models support the ## @code{predict} and @code{feval} methods for prediction. ## ## @code{Fitted}, @code{Residuals}, @code{Diagnostics}, and ## @code{ObservationInfo} have one row per @emph{input} observation, not per ## fitted observation. Rows that were excluded with the @qcode{'Exclude'} pair ## still carry a fitted value and a residual, since the model can be evaluated ## there; rows dropped because a variable was missing carry @code{NaN}. ## ## For a binomial response carrying a number of trials @math{N} -- given ## either by the @qcode{'BinomialSize'} pair or as the second column of a ## two-column response -- the response is the @emph{number of successes}, as ## @code{fitglm} documents. @code{Fitted.Response} is then the fitted count ## @math{N p} and @code{Residuals.Raw} is on that same count scale, while ## @code{Fitted.Probability} carries @math{p} itself. @code{predict} returns ## the probability, never a count: a trial count belongs to an observation, ## and new predictor values do not carry one. ## ## A categorical predictor expands to indicator columns, one per level bar the ## reference level, which the intercept carries. When the model has no ## intercept, the @emph{first} categorical predictor is given an indicator for ## every one of its levels instead, so that its coefficients are the group ## means; any further categorical predictor stays reference coded, which keeps ## the design full rank. This differs from MATLAB, which omits the reference ## level whether or not an intercept is present and so cannot fit the reference ## group at all -- for a three-level grouping variable @code{g}, MATLAB fits ## @code{y ~ g - 1} with two coefficients, predicts exactly 0 for every ## observation in the omitted group, and reports a negative @math{R^2}. This ## implementation returns three coefficients, one per group. ## ## @seealso{fitglm, LinearModel, glmfit, glmval} ## @end deftp classdef GeneralizedLinearModel properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} Coefficients ## ## Coefficient values ## ## A table with one row per coefficient and four columns: ## @itemize ## @item @code{Estimate} - estimated coefficient value ## @item @code{SE} - standard error of the estimate ## @item @code{tStat} - the estimate divided by its standard error ## @item @code{pValue} - p-value of that statistic ## @end itemize ## ## The statistic is referred to the normal distribution where the dispersion ## is fixed, as it is for the binomial and Poisson families, and to a ## @math{t}-distribution on @code{DFE} degrees of freedom where it is ## estimated. Coefficients dropped as rank deficient have ## @code{Estimate = 0}, @code{SE = 0}, and @code{NaN} for both statistics. ## Row names are the coefficient names. ## ## This property is read-only. ## ## @end deftp Coefficients = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} CoefficientNames ## ## Names of the coefficients ## ## A cell array of character vectors, one per coefficient, in the order the ## coefficients appear. The intercept is @qcode{'(Intercept)'}, an ## interaction joins its factors with a colon, and a categorical predictor ## contributes one name per indicator, spelled ## @qcode{@var{name}_@var{level}}, so these are not the term names. ## ## This property is read-only. ## ## @end deftp CoefficientNames = {}; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} CoefficientCovariance ## ## Covariance matrix of the coefficient estimates ## ## A square matrix with one row and column per coefficient, whose diagonal ## is the square of @code{Coefficients.SE}. It is scaled by ## @code{Dispersion}, so it is the covariance under the estimated dispersion ## wherever one was estimated. ## ## This property is read-only. ## ## @end deftp CoefficientCovariance = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} NumCoefficients ## ## Number of coefficients ## ## A positive integer counting every coefficient the model carries, those ## dropped as rank deficient included. A categorical predictor with ## @math{L} levels contributes @math{L - 1} of them. ## ## This property is read-only. ## ## @end deftp NumCoefficients = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} NumEstimatedCoefficients ## ## Number of coefficients actually estimated ## ## A positive integer counting the coefficients that carry a degree of ## freedom, which is @code{NumCoefficients} less however many were dropped ## as rank deficient. It is the number the degrees of freedom and the ## information criteria are computed from. ## ## This property is read-only. ## ## @end deftp NumEstimatedCoefficients = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} NumPredictors ## ## Number of predictor variables ## ## A nonnegative integer counting the predictors the model was given, ## whether or not each appears in a term. It counts variables, so a ## categorical predictor counts once however many indicators it expands to. ## ## This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} NumObservations ## ## Number of observations used in the fit ## ## A positive integer giving the number of observations the fit actually ## used. Rows holding a missing value and rows named by the ## @qcode{'Exclude'} name-value argument are not counted. ## ## This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} Deviance ## ## Deviance of the fitted model ## ## A nonnegative scalar, twice the difference between the log-likelihood of ## the saturated model and that of this one. It is the generalized linear ## model's counterpart of the residual sum of squares, and it is what a ## nested-model test compares. ## ## This property is read-only. ## ## @end deftp Deviance = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} DFE ## ## Error degrees of freedom ## ## A nonnegative integer, @code{NumObservations} less ## @code{NumEstimatedCoefficients}. ## ## This property is read-only. ## ## @end deftp DFE = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} Dispersion ## ## Dispersion parameter ## ## A positive scalar. It is estimated from the Pearson statistic for the ## normal, gamma, and inverse Gaussian families, and fixed at @math{1} for ## the binomial and Poisson families unless @qcode{'DispersionFlag'} asked ## otherwise. @code{CoefficientCovariance} and the standard errors are ## scaled by it. ## ## This property is read-only. ## ## @end deftp Dispersion = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} DispersionEstimated ## ## Whether the dispersion was estimated ## ## A logical scalar, true where @code{Dispersion} was estimated from the ## data and false where it was held at @math{1}. It decides whether a ## coefficient's statistic is referred to the normal or the @math{t} ## distribution. ## ## This property is read-only. ## ## @end deftp DispersionEstimated = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} Distribution ## ## The response distribution ## ## A structure with three fields: @code{Name}, the distribution's name; ## @code{DevianceFunction}, a function handle giving the deviance ## contribution of an observation from its response and mean; and ## @code{VarianceFunction}, a function handle giving the variance of an ## observation as a function of its mean. ## ## This property is read-only. ## ## @end deftp Distribution = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} Link ## ## The link function ## ## A structure with four fields: @code{Name}, the link's name; @code{Link}, ## a function handle mapping the mean to the linear predictor; ## @code{Derivative}, a handle giving that map's derivative; and ## @code{Inverse}, a handle mapping the linear predictor back to the mean. ## ## This property is read-only. ## ## @end deftp Link = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} Fitted ## ## Fitted values ## ## A table with one row per input observation and two columns, ## @code{Response} on the scale of the response and @code{LinearPredictor} ## on the scale of the link. A binomial fit gains a third, ## @code{Probability}, since its ## @code{Response} is a count of successes while the fit works in the ## proportion. Rows kept out of the fit by @qcode{'Exclude'} still carry a ## prediction; rows dropped as missing carry @code{NaN}. ## ## This property is read-only. ## ## @end deftp Fitted = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} Residuals ## ## Residuals for the fitted model ## ## A table with one row per input observation and five columns: ## @itemize ## @item @code{Raw} - observed minus fitted, on the response scale ## @item @code{LinearPredictor} - the working residual, on the link scale ## @item @code{Pearson} - raw residuals divided by the estimated standard ## deviation of the observation ## @item @code{Anscombe} - the transform that makes the residuals as nearly ## normal as the family allows ## @item @code{Deviance} - the signed square root of each observation's ## contribution to @code{Deviance} ## @end itemize ## ## Rows not used in the fit contain @code{NaN}. ## ## This property is read-only. ## ## @end deftp Residuals = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} Diagnostics ## ## Per-observation diagnostics ## ## A table with one row per input observation and three columns: ## @code{Leverage}, the diagonal of the weighted hat matrix; ## @code{CooksDistance}, the influence of the observation on every fitted ## value at once; and @code{HatMatrix}, that observation's row of the hat ## matrix. Rows not used in the fit contain @code{NaN}. ## ## This property is read-only. ## ## @end deftp Diagnostics = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} LogLikelihood ## ## Log-likelihood of the fitted model ## ## A scalar, the log-likelihood of the observations under the fitted ## coefficients and the family's own density. It is what the information ## criteria and the likelihood-ratio @math{R^2} are computed from. ## ## This property is read-only. ## ## @end deftp LogLikelihood = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} ModelCriterion ## ## Information criteria ## ## A structure with four fields, @code{AIC}, @code{AICc}, @code{BIC}, and ## @code{CAIC}, each penalising @code{LogLikelihood} by a different function ## of the coefficient count and the sample size. @code{AICc} is @code{Inf} ## where the correction's denominator is not positive. ## ## This property is read-only. ## ## @end deftp ModelCriterion = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} Rsquared ## ## Measures of fit ## ## A structure with five fields: @code{Ordinary} and @code{Adjusted}, ## computed from the sums of squares on the response scale; @code{Deviance}, ## one less the ratio of the model's deviance to the null model's; ## @code{LLR}, the same ratio taken over log-likelihoods; and ## @code{AdjGeneralized}, the Nagelkerke measure, which rescales the ## generalized @math{R^2} by its own attainable maximum so that it can reach ## one. ## ## This property is read-only. ## ## @end deftp Rsquared = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} SSE ## ## Error sum of squares ## ## A nonnegative scalar, the weighted sum of squared raw residuals on the ## response scale. For a generalized linear model this is a descriptive ## quantity rather than the fitted criterion, which is @code{Deviance}. ## ## This property is read-only. ## ## @end deftp SSE = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} SSR ## ## Regression sum of squares ## ## A nonnegative scalar, the weighted sum of squared differences between the ## fitted values and the weighted mean of the response, on the response ## scale. ## ## This property is read-only. ## ## @end deftp SSR = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} SST ## ## Total sum of squares ## ## A nonnegative scalar, the weighted sum of squared differences between the ## response and its weighted mean. Unlike a linear model, a generalized ## linear model does not in general satisfy @code{SST = SSE + SSR}. ## ## This property is read-only. ## ## @end deftp SST = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} Offset ## ## Offset added to the linear predictor ## ## A column vector with one element per input observation, added to the ## linear predictor with a coefficient fixed at one, so that it shifts the ## fit without being estimated. It is all zeros where no @qcode{'Offset'} ## was given. ## ## This property is read-only. ## ## @end deftp Offset = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} LikelihoodPenalty ## ## Penalty applied to the likelihood ## ## A character vector, always @qcode{'none'}: no penalized-likelihood ## fitting is offered, so the coefficients are always the plain ## maximum-likelihood ones. ## ## This property is read-only. ## ## @end deftp LikelihoodPenalty = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} ResponseName ## ## Name of the response variable ## ## A character vector. It is taken from the table column, the ## @qcode{'ResponseVar'} or @qcode{'VarNames'} argument, or the formula, and ## defaults to @qcode{'y'} for a predictor matrix. ## ## This property is read-only. ## ## @end deftp ResponseName = 'y'; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} PredictorNames ## ## Names of the predictor variables ## ## A cell array of character vectors naming the predictors in the order the ## data lists them. A predictor matrix gives them the names @qcode{'x1'}, ## @qcode{'x2'}, and so on. ## ## This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} VariableNames ## ## Names of all the variables ## ## A cell array of character vectors naming every variable the model was ## given, the response included, in the order the data lists them. ## ## This property is read-only. ## ## @end deftp VariableNames = {}; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} NumVariables ## ## Number of variables ## ## A positive integer, the number of elements of @code{VariableNames}: the ## predictors and the response together, whether or not each appears in a ## term. ## ## This property is read-only. ## ## @end deftp NumVariables = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} VariableInfo ## ## Per-variable information ## ## A table with one row per variable, named by it, and four columns: ## @code{Class}, the class of the data column; @code{Range}, its two-element ## range or, for a categorical, the list of its levels; @code{InModel}, true ## where the variable appears in a term; and @code{IsCategorical}, true ## where it was coded as indicators. ## ## This property is read-only. ## ## @end deftp VariableInfo = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} Variables ## ## The data the model was built from ## ## A table holding every variable, the response included, with one row per ## input observation. A model fitted from a predictor matrix gets a table ## assembled from it, so this property is a table either way. ## ## This property is read-only. ## ## @end deftp Variables = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} ObservationInfo ## ## Per-observation status ## ## A table with one row per input observation and four columns: ## @code{Weights}, the weight it was given; @code{Excluded}, true where ## @qcode{'Exclude'} named it; @code{Missing}, true where its data are ## incomplete; and @code{Subset}, true where it was used in the fit, which ## is neither excluded nor missing. ## ## This property is read-only. ## ## @end deftp ObservationInfo = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} ObservationNames ## ## Names of the observations ## ## A cell array of character vectors, one per input observation, and empty ## unless the data carried row names. ## ## This property is read-only. ## ## @end deftp ObservationNames = {}; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} Formula ## ## The model formula ## ## A @code{LinearFormula} object describing the fitted model, with ## properties including @code{ResponseName}, @code{LinearPredictor}, ## @code{PredictorNames}, ## @code{TermNames}, @code{Terms}, @code{HasIntercept}, and @code{Link}. ## Its terms are expressed over the model's variables, so a categorical ## predictor contributes one term however many indicators it expands to. ## ## This property is read-only. ## ## @end deftp Formula = []; ## -*- texinfo -*- ## @deftp {GeneralizedLinearModel} {property} Steps ## ## Stepwise fitting information ## ## A structure recording the term-selection trace, populated whenever the ## model was fit by @code{stepwiseglm} and @code{[]} otherwise. It has ## seven fields: ## ## @multitable @columnfractions 0.15 0.8 ## @headitem Field @tab Contents ## @item @code{Start} @tab a @code{LinearFormula} for the model the search ## started from. ## @item @code{Lower} @tab a @code{LinearFormula} for the smallest model ## considered; its terms are never removed. ## @item @code{Upper} @tab a @code{LinearFormula} for the largest model ## considered. ## @item @code{Criterion} @tab the selection criterion, such as ## @qcode{'deviance_chi2'}. ## @item @code{PEnter} @tab the threshold a term must beat to enter, empty ## unless one was given. ## @item @code{PRemove} @tab the threshold above which a term leaves, empty ## unless one was given. ## @item @code{History} @tab a table with one row per step. ## @end multitable ## ## @code{History} always carries @code{Action} (@qcode{'Start'}, ## @qcode{'Add'}, or @qcode{'Remove'}), @code{TermName}, @code{Terms} (the ## terms matrix after the step, over the model's variables), @code{DF} (the ## coefficient count after the step), and @code{delDF} (the change in it, ## negative for a removal). The remaining columns follow the criterion: ## @code{Deviance}, then ## @code{Chi2Stat} or @code{FStat}, then @code{PValue} under ## @qcode{'Deviance'}; ## @code{FStat} and @code{pValue} under @qcode{'sse'}; and a single column ## named ## @code{AIC} or @code{BIC} holding the criterion's value after the step ## otherwise. The first row is the starting model, named by its right-hand ## side. ## ## This property is read-only. ## ## @end deftp Steps = []; endproperties properties (Access = private, Hidden) b_ = []; # coefficient vector aligned with the design columns stats_ = []; # glmfit stats struct (for prediction CIs) distr_ = ''; # distribution name linkarg_ = []; # link specification for glmfit/glmval binomsize_ = []; # BinomialSize (trials) for the binomial family terms_ = []; # terms matrix (for rebuilding the design in predict) catinfo_ = []; # categorical level info (names + levels) encnames_ = {}; # encoded predictor column names prednames_ = {}; # raw predictor names nulldev_ = []; # deviance of the intercept-only model llnull_ = []; # log-likelihood of the intercept-only model design_ = []; # fitted design matrix (for diagnostics/plots) subset_ = []; # logical mask of the rows used in the fit formulastr_ = ''; # rendered formula, e.g. 'log(y) ~ 1 + x1 + x2' xmeans_ = []; # mean of each raw predictor (for slice/effect plots) endproperties methods (Hidden) ## Custom display of the object name. function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ("%s =\n", in_name); endif disp (this); endfunction ## Custom display of the model summary. function disp (this) fprintf ("\n Generalized linear regression model:\n"); if (! isempty (this.formulastr_)) fprintf (" %s\n", this.formulastr_); endif if (! isempty (this.Distribution) && isstruct (this.Distribution)) fprintf (" Distribution = %s, Link = %s\n", ... this.Distribution.Name, this.Link.Name); endif if (! isempty (this.Coefficients)) fprintf ("\n Coefficients:\n\n"); disp (this.Coefficients); endif fprintf ("\n"); if (! isempty (this.NumObservations) && ! isempty (this.DFE)) fprintf (strcat ("Number of observations: %d,", ... " Error degrees of freedom: %d\n"), ... this.NumObservations, this.DFE); endif if (! isempty (this.Dispersion)) fprintf ("Dispersion: %g\n", this.Dispersion); endif if (! isempty (this.Deviance)) fprintf ("Deviance: %g\n", this.Deviance); endif if (! isempty (this.NumCoefficients) && this.NumCoefficients > 1) df1 = this.NumCoefficients - 1; drop = this.nulldev_ - this.Deviance; if (this.DispersionEstimated) Fstat = (drop / df1) / this.Dispersion; pval = 1 - fcdf (Fstat, df1, this.DFE); fprintf ("F-statistic vs. constant model: %g, p-value = %g\n", ... Fstat, pval); else pval = 1 - chi2cdf (drop, df1); fprintf (strcat ("Chi^2-statistic vs. constant model: %g,", ... " p-value = %g\n"), drop, pval); endif endif endfunction ## Class specific subscripted reference. function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' error (strcat ("GeneralizedLinearModel: () indexing is not", ... " supported. Use dot notation for properties.")); case '{}' error (strcat ("GeneralizedLinearModel: {} indexing is not", ... " supported. Use dot notation for properties.")); case '.' if (! ischar (s.subs)) error (strcat ("GeneralizedLinearModel.subsref: property name", ... " must be a character vector.")); endif if (ismethod (this, s.subs)) [varargout{1:nargout}] = builtin ('subsref', this, [s, chain_s]); return; endif try out = this.(s.subs); catch error (strcat ("GeneralizedLinearModel.subsref: unknown", ... " property '%s'."), s.subs); end_try_catch endswitch if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction ## Attach a stepwise-selection history structure. Used by @code{stepwiseglm} ## to record the term-selection trace on the returned object; not intended ## for direct use. function this = setSteps (this, steps) this.Steps = steps; endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearModel} {@var{mdl} =} GeneralizedLinearModel (@var{data}, @var{resp}, @var{modelspec}) ## @deftypefnx {GeneralizedLinearModel} {@var{mdl} =} GeneralizedLinearModel (@dots{}, @var{Name}, @var{Value}) ## ## Fit a generalized linear model. Prefer the @code{fitglm} function, which ## documents the accepted inputs and @var{Name}/@var{Value} pairs. ## ## @end deftypefn function this = GeneralizedLinearModel (data, resp, modelspec, varargin) if (nargin == 0) return; # empty object endif if (nargin < 3) error ("GeneralizedLinearModel: DATA, RESP, and MODELSPEC are required."); endif opts = glm_parse_nv (varargin); is_formula = ischar (modelspec) && any (modelspec == '~'); tbl = []; # defined only for table input; passed through helpers ## ------------------------------------------------------------------ ## ## Intake: resolve predictors, response, names, and the response vector. ## ------------------------------------------------------------------ ## ## Trial counts taken from a two-column binomial response, empty for ## every other input form. Set here so the table branch below and the ## fitting code further down can both test it unconditionally. binom_2col = []; ## The response on the scale the caller gave it, which is the number of ## successes for a binomial fit carrying trial counts and the response ## itself everywhere else. Y_FULL is always the proportion the fit ## works in; this is what the Variables table reports. y_input = []; if (! istable (data)) if (! (isnumeric (data) && isreal (data) && ismatrix (data))) error ("GeneralizedLinearModel: X must be a real matrix."); endif ## A binomial response may be given as an N-by-2 matrix holding the ## successes and the trials, as MATLAB accepts. A row vector stays a ## response vector: [3, 10] is two observations, never one pair. is_2col = strcmp (opts.Distribution, 'binomial') && isnumeric (resp) ... && isreal (resp) && ismatrix (resp) && ! isvector (resp) ... && columns (resp) == 2; if (! (is_2col || (isnumeric (resp) && isreal (resp) ... && isvector (resp)))) error ("GeneralizedLinearModel: Y must be a real vector."); endif X_raw = double (data); n_total = rows (X_raw); p_raw = columns (X_raw); if (is_2col) binom_2col = double (resp(:,2)); emsg = check_binomial_counts (double (resp(:,1)), binom_2col); if (! isempty (emsg)) error ("GeneralizedLinearModel: %s", emsg); endif ## The fit works in proportions; the success counts are kept for ## the Variables table. Only the successes are kept, not the two ## columns as handed in: Y is the number of successes by both routes ## since cd9c81d0, so that is the response the model has. MATLAB ## stores the matrix exactly as given. y_input = double (resp(:,1)); y_full = y_input ./ binom_2col; else y_full = double (resp(:)); endif if (rows (X_raw) != numel (y_full)) error (strcat ("GeneralizedLinearModel: X and Y must have the", ... " same number of observations.")); endif if (! isempty (opts.VarNames)) if (numel (opts.VarNames) != p_raw + 1) error ("GeneralizedLinearModel: VarNames must have %d elements.", ... p_raw + 1); endif pred_names = opts.VarNames(1:p_raw)(:)'; resp_name = opts.VarNames{end}; else pred_names = arrayfun (@(k) sprintf ("x%d", k), 1:p_raw, ... 'UniformOutput', false); resp_name = 'y'; endif if (! isempty (opts.ResponseVar)) resp_name = opts.ResponseVar; endif var_names_all = [pred_names, {resp_name}]; else tbl = data; X_raw = []; # numeric matrix built below via raw_to_codes col_names = tbl.Properties.VariableNames; n_total = height (tbl); var_names_all = col_names; if (ischar (resp) && ! isempty (resp)) resp_name = resp; elseif (isnumeric (resp) && ! isempty (resp)) resp_name = 'y'; if (! isempty (opts.ResponseVar)) resp_name = opts.ResponseVar; endif y_ext = double (resp(:)); elseif (is_formula) tparts = strsplit (modelspec, '~'); resp_name = strtrim (tparts{1}); else resp_name = col_names{end}; endif if (! isempty (opts.PredictorVars)) pred_names = opts.PredictorVars; elseif (is_formula) ## Only the variables the formula names take part in the model; any ## other column of the table is carried but is not a predictor. used = formula_var_names (modelspec); in_formula = ismember (col_names, used) ... & ! strcmp (col_names, resp_name); pred_names = col_names(in_formula); else pred_names = col_names(! strcmp (col_names, resp_name)); endif p_raw = numel (pred_names); if (exist ('y_ext', 'var')) y_full = y_ext; else y_full = double (tbl.(resp_name)(:)); endif endif ## Categorical predictor flags. cat_logical = false (1, p_raw); if (! isempty (opts.CategoricalVars)) cv = opts.CategoricalVars; if (islogical (cv)) n_cv = min (numel (cv), p_raw); cat_logical(1:n_cv) = cv(1:n_cv); elseif (isnumeric (cv)) cat_logical(cv(cv > 0 & cv <= p_raw)) = true; elseif (iscell (cv)) for i = 1:numel (cv) cat_logical(strcmp (pred_names, cv{i})) = true; endfor endif endif if (istable (data)) for j = 1:p_raw col = tbl.(pred_names{j}); ## A logical or string column groups its observations just as a cell ## or categorical one does, and is coded the same way. if (iscell (col) || isa (col, 'categorical') ... || islogical (col) || isa (col, 'string')) cat_logical(j) = true; endif endfor endif ## ------------------------------------------------------------------ ## ## Binomial trial counts. Y is the number of successes in both accepted ## forms -- the two-column response and 'BinomialSize' -- while the fit ## works in the proportion. Both are resolved before the missing mask ## is taken, so that a missing count makes its proportion missing too. ## ------------------------------------------------------------------ ## N_full = []; if (strcmp (opts.Distribution, 'binomial')) if (! isempty (binom_2col)) ## The trials came with the response. A 'BinomialSize' passed as ## well is ignored rather than refused, which is what MATLAB does. N_full = binom_2col; elseif (! isempty (opts.BinomialSize)) Nb = double (opts.BinomialSize(:)); if (! isscalar (Nb) && numel (Nb) != n_total) error (strcat ("GeneralizedLinearModel: BinomialSize must be a", ... " scalar or have one element per observation.")); endif N_full = expand_to_rows (Nb, n_total); emsg = check_binomial_counts (y_full, N_full); if (! isempty (emsg)) error ("GeneralizedLinearModel: %s", emsg); endif ## Up to v1.8.4 this response was read as the proportion, so one ## lying wholly within [0, 1] against more than one trial is almost ## certainly written for the old meaning. Warn rather than raise: ## an all-0/1 count vector is legitimate rare-event data. obs = y_full(! isnan (y_full)); if (! isempty (obs) && all (obs <= 1) && any (N_full > 1)) warning (strcat ("GeneralizedLinearModel: with 'BinomialSize'", ... " the response is the number of successes,", ... " not the proportion, from statistics 1.9.0;", ... " multiply by the trials for the old meaning.")); endif y_input = y_full; y_full = y_full ./ N_full; endif endif if (isempty (y_input)) y_input = y_full; endif ## Missing/excluded masks and the fitting subset. Only the variables the ## model actually uses can make a row missing; an unused table column with ## a gap in it does not cost an observation. if (! istable (data)) missing_mask = any (isnan (X_raw), 2) | isnan (y_full); else used_cols = [pred_names(:)', {resp_name}]; used_cols = used_cols(ismember (used_cols, col_names)); missing_mask = any (ismissing (tbl(:, used_cols)), 2); endif missing_mask = missing_mask(:); excluded_mask = false (n_total, 1); if (! isempty (opts.Exclude)) ex = opts.Exclude(:); if (islogical (ex)) excluded_mask(1:numel (ex)) = ex; else excluded_mask(ex) = true; endif endif avail_mask = ! missing_mask; subset_mask = avail_mask & ! excluded_mask; n_obs = sum (subset_mask); if (n_obs < 1) error (strcat ("GeneralizedLinearModel: no observations remain after", ... " removing missing/excluded rows.")); endif ## Weights, offset, and binomial trial counts. Each is kept at full ## length so that the per-observation tables can span the input rows. has_weights = ! isempty (opts.Weights); w_full = ones (n_total, 1); if (has_weights) w_full = expand_to_rows (double (opts.Weights(:)), n_total); endif w_sub = w_full(subset_mask); has_offset = ! isempty (opts.Offset); off_full = zeros (n_total, 1); if (has_offset) off_full = expand_to_rows (double (opts.Offset(:)), n_total); endif off_sub = off_full(subset_mask); distr = opts.Distribution; N_sub = []; if (! isempty (N_full)) N_sub = N_full(subset_mask); endif y_sub = y_full(subset_mask); ## Numeric predictor matrix (categorical columns as 1-based codes), used ## for encoding and for the slice/effect plots. [X_num_full, cat_levels] = raw_to_codes (data, X_raw, tbl, ... pred_names, cat_logical, n_total); ## ------------------------------------------------------------------ ## ## Design matrix (intercept included as a column when present). It is ## built over every input row so that the fitted values, residuals, and ## diagnostics can be reported for rows kept out of the fit; the fit ## itself sees only the subset. ## ------------------------------------------------------------------ ## if (is_formula) if (! istable (data)) tbl_all = array2table ([X_raw, y_full], ... 'VariableNames', var_names_all); else tbl_all = tbl; endif ## Hand the parser only the rows it would keep anyway, so that the ## returned design lines up row for row with AVAIL_MASK. tbl_avail = tbl_all(avail_mask, :); [X_avail, ~, coef_names] = parseWilkinsonFormula ( ... modelspec, 'model_matrix', tbl_avail, pred_names(cat_logical)); coef_names = coef_names(:)'; has_intercept = any (strcmp (coef_names, '(Intercept)')); enc_names = coef_names(! strcmp (coef_names, '(Intercept)')); X_design_all = NaN (n_total, columns (X_avail)); X_design_all(avail_mask, :) = X_avail; [terms, cat_info, term_cols] = terms_from_coefnames (coef_names, ... pred_names, cat_logical, data, tbl_avail); else ## Whether a categorical is given all its indicator columns depends ## on the intercept, which has to be settled before encoding. [X_enc_all, enc_names, cat_info] = encode_categorical ( ... X_num_full, cat_logical, pred_names, cat_levels, ... modelspec_has_intercept (modelspec, opts.Intercept)); [terms, has_intercept, coef_names, emsg] = parse_modelspec ( ... modelspec, enc_names, columns (X_enc_all), opts.Intercept); if (! isempty (emsg)) error ("GeneralizedLinearModel: %s", emsg); endif X_design_all = build_design (terms, X_enc_all); term_cols = [enc_names, {''}]; ## A missing categorical code encodes as an all-zero dummy row rather ## than NaN, so mark the missing rows explicitly. X_design_all(missing_mask, :) = NaN; endif X_design = X_design_all(subset_mask, :); ## ------------------------------------------------------------------ ## ## Fit the design via glmfit (the intercept is a design column already). ## ------------------------------------------------------------------ ## if (! isempty (opts.Link)) linkspec = opts.Link; else linkspec = default_link_spec (distr); endif linkname = link_name (linkspec); gargs = {'link', linkspec, 'constant', 'off'}; if (has_weights) gargs = [gargs, {'weights', w_sub}]; endif if (has_offset) gargs = [gargs, {'offset', off_sub}]; endif if (! isempty (opts.DispersionFlag)) gargs = [gargs, {'estdisp', ternary(opts.DispersionFlag, 'on', 'off')}]; endif yfit = y_sub; if (strcmp (distr, 'binomial') && ! isempty (N_sub)) yfit = [y_sub .* N_sub, N_sub]; endif [b, dev, stats] = glmfit (X_design, yfit, distr, gargs{:}); ## ------------------------------------------------------------------ ## ## Assemble the object. ## ------------------------------------------------------------------ ## [flink, dlink, ilink] = getlinkfunctions (linkspec); this.Link = struct ('Name', linkname, 'Link', flink, ... 'Derivative', dlink, 'Inverse', ilink); [devfun, varfun] = glm_family_functions (distr); this.Distribution = struct ('Name', distribution_name (distr), ... 'DevianceFunction', devfun, ... 'VarianceFunction', varfun); this.Coefficients = table (b(:), stats.se(:), stats.t(:), stats.p(:), ... 'VariableNames', {'Estimate', 'SE', 'tStat', 'pValue'}, ... 'RowNames', coef_names(:)); ## Fitted values over every input row. Rows kept out of the fit by ## 'Exclude' still get a prediction; rows dropped as missing give NaN. eta_all = X_design_all * b + off_full; mu_prob_all = ilink (eta_all); # probability (binomial) or mean (others) if (isempty (N_full)) N_all = ones (n_total, 1); else N_all = N_full; endif if (strcmp (distr, 'binomial')) mu_resp_all = N_all .* mu_prob_all; this.Fitted = table (mu_resp_all, eta_all, mu_prob_all, ... 'VariableNames', {'Response', 'LinearPredictor', 'Probability'}); else mu_resp_all = mu_prob_all; this.Fitted = table (mu_resp_all, eta_all, ... 'VariableNames', {'Response', 'LinearPredictor'}); endif [r_raw, r_pear, r_ansc, r_dev] = glm_residuals (distr, y_full, ... mu_prob_all, N_full); r_lin = (y_full - mu_prob_all) .* dlink (mu_prob_all); this.Residuals = table (r_raw, r_lin, r_pear, r_ansc, r_dev, ... 'VariableNames', {'Raw', 'LinearPredictor', 'Pearson', 'Anscombe', ... 'Deviance'}); mu_prob = mu_prob_all(subset_mask); mu_resp = mu_resp_all(subset_mask); ## Log-likelihood, information criteria, and R-squared measures. These ## use the intercept-only ("null") model as the baseline. w_ll = w_sub; [bn, nulldev, sn] = glmfit (ones (n_obs, 1), yfit, distr, gargs{:}); eta0 = bn * ones (n_obs, 1); if (! isempty (off_sub)) eta0 = eta0 + off_sub; endif mu0 = ilink (eta0); LL = glm_loglik (distr, y_sub, mu_prob, N_sub, w_ll, stats.s); LL_null = glm_loglik (distr, y_sub, mu0, N_sub, w_ll, sn.s); this.LogLikelihood = LL; this.nulldev_ = nulldev; this.llnull_ = LL_null; k = numel (b); # MATLAB counts the coefficients only, not the dispersion aic = -2 * LL + 2 * k; if (n_obs - k - 1 > 0) aicc = aic + 2 * k * (k + 1) / (n_obs - k - 1); else aicc = Inf; endif this.ModelCriterion = struct ('AIC', aic, ... 'AICc', aicc, 'BIC', -2 * LL + k * log (n_obs), ... 'CAIC', -2 * LL + k * (log (n_obs) + 1)); ybar_w = sum (w_ll .* y_sub) / sum (w_ll); sse = sum (w_ll .* (y_sub - mu_resp) .^ 2); ssr = sum (w_ll .* (mu_resp - ybar_w) .^ 2); sst = sum (w_ll .* (y_sub - ybar_w) .^ 2); r2_ord = 1 - sse / max (sst, eps); dfe = stats.dfe; if (dfe > 0) r2_adj = 1 - (1 - r2_ord) * (n_obs - 1) / dfe; else r2_adj = NaN; endif r2_dev = 1 - dev / max (nulldev, eps); r2_llr = 1 - LL / LL_null; r2_gen = 1 - exp (2 * (LL_null - LL) / n_obs); r2_gen_max = 1 - exp (2 * LL_null / n_obs); this.Rsquared = struct ('Ordinary', r2_ord, 'Adjusted', r2_adj, ... 'Deviance', r2_dev, 'AdjGeneralized', r2_gen / r2_gen_max, ... 'LLR', r2_llr); ## Model formula. TERMS is expressed over the encoded design columns; the ## formula is expressed over the model's variables, so a categorical's ## indicator columns have to be folded back onto the variable they came ## from before the term names can be built. n_vars = numel (var_names_all); var_idx = zeros (1, p_raw); for j = 1:p_raw k = find (strcmp (var_names_all, pred_names{j}), 1); if (! isempty (k)) var_idx(j) = k; endif endfor ## Indexed by the columns of TERMS, which are not the coefficient ## names: a factor appearing only inside an interaction or a power has a ## column without ever being a coefficient. [enc2raw, col_pow] = encodednames_to_row (term_cols(1:end-1), pred_names, ... cat_info); terms_var = variable_level_terms (terms(:, 1:end-1), enc2raw, var_idx, ... n_vars, col_pow); this.Formula = LinearFormula (terms_var, var_names_all(:)', ... 'ResponseName', resp_name, ... 'Link', linkspec); this.formulastr_ = char (this.Formula); in_model = this.Formula.InModel; ## Per-variable information, over the fitting subset rather than the full ## data, as MATLAB reports it: VariableInfo sits on a fitted model, so a ## range describes the data the fit actually used. Measured against ## R2024a, which also drops a category appearing only in excluded rows. vi_class = cell (n_vars, 1); vi_range = cell (n_vars, 1); vi_inmodel = in_model(:); vi_iscat = false (n_vars, 1); vi_iscat(var_idx(var_idx > 0)) = cat_logical(var_idx > 0); for j = 1:n_vars if (istable (data)) col = tbl.(var_names_all{j}); elseif (j <= p_raw) col = X_raw(:, j); else col = y_input; endif col = col(subset_mask); if (isa (col, 'categorical')) col = removecats (col); endif [vi_class{j}, vi_range{j}] = variable_class_and_range (col); endfor this.VariableInfo = table (vi_class, vi_range, vi_inmodel, vi_iscat, ... 'VariableNames', {'Class', 'Range', 'InModel', 'IsCategorical'}, ... 'RowNames', var_names_all(:)); if (istable (data)) this.Variables = tbl; else this.Variables = array2table ([X_raw, y_input], ... 'VariableNames', var_names_all); endif this.ObservationInfo = table (w_full, excluded_mask, missing_mask, ... subset_mask, ... 'VariableNames', {'Weights', 'Excluded', 'Missing', 'Subset'}); ## Leverage, hat matrix, and Cook's distance. The hat matrix is the ## asymmetric IRLS form H = X inv(X'WX) X' W, whose diagonal is the ## leverage; rows outside the fit contribute nothing to it. dmu_deta = 1 ./ dlink (mu_prob); vwt = glm_varfun (distr, mu_prob, N_sub); w_irls = (dmu_deta .^ 2) ./ max (vwt, realmin) .* w_ll; XtW = (X_design .* w_irls)'; Hs = X_design * ((XtW * X_design) \ XtW); lev = diag (Hs); pear = r_pear(subset_mask); phi_c = ternary (logical (stats.estdisp), stats.s, 1); cooksd = w_ll .* pear .^ 2 .* lev ... ./ (max (1 - lev, eps) .^ 2 * numel (b) * phi_c); lev_all = zeros (n_total, 1); lev_all(subset_mask) = lev; cd_all = NaN (n_total, 1); cd_all(subset_mask) = cooksd; hat_all = zeros (n_total, n_total); hat_all(subset_mask, subset_mask) = Hs; this.Diagnostics = table (lev_all, cd_all, hat_all, ... 'VariableNames', {'Leverage', 'CooksDistance', 'HatMatrix'}); this.b_ = b; this.stats_ = stats; this.distr_ = distr; this.linkarg_ = linkspec; ## Trials from a two-column response stand in for 'BinomialSize', which ## is ignored when both are given. if (! isempty (binom_2col)) this.binomsize_ = binom_2col; else this.binomsize_ = opts.BinomialSize; endif this.terms_ = terms; this.catinfo_ = cat_info; this.encnames_ = enc_names; this.prednames_ = pred_names; this.design_ = X_design; this.subset_ = subset_mask; this.xmeans_ = mean (X_num_full(subset_mask,:), 1); this.CoefficientNames = coef_names; this.CoefficientCovariance = stats.covb; this.NumCoefficients = numel (b); this.NumEstimatedCoefficients = numel (b); this.NumPredictors = p_raw; this.NumObservations = n_obs; this.NumVariables = n_vars; this.Deviance = dev; this.DFE = stats.dfe; this.Dispersion = stats.s; this.DispersionEstimated = logical (stats.estdisp); this.SSE = sse; this.SSR = ssr; this.SST = sst; this.Offset = off_full; this.LikelihoodPenalty = string ("none"); this.ResponseName = resp_name; this.PredictorNames = pred_names(:); this.VariableNames = var_names_all(:); endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearModel} {@var{yhat} =} predict (@var{mdl}, @var{Xnew}) ## @deftypefnx {GeneralizedLinearModel} {[@var{yhat}, @var{yci}] =} predict (@var{mdl}, @var{Xnew}) ## @deftypefnx {GeneralizedLinearModel} {[@dots{}] =} predict (@dots{}, @var{Name}, @var{Value}) ## ## Predict the response of the model @var{mdl} at the new predictor data ## @var{Xnew} (a numeric matrix or a table). Predictions are on the mean ## (response) scale. With two outputs, @var{yci} is an @math{m}-by-2 matrix ## of confidence intervals. The @qcode{'Alpha'} pair sets the confidence ## level to @math{100 (1 - @var{Alpha})%} (default 0.05). ## ## @end deftypefn function [yhat, yci] = predict (mdl, Xnew, varargin) if (nargin < 2) error ("GeneralizedLinearModel.predict: Xnew is required."); endif alpha = 0.05; offnew = []; for k = 1:2:numel (varargin) switch (lower (varargin{k})) case 'alpha'; alpha = varargin{k+1}; case 'offset'; offnew = varargin{k+1}; otherwise error (strcat ("GeneralizedLinearModel.predict: unknown", ... " parameter '%s'."), varargin{k}); endswitch endfor ## Marshal Xnew into a raw numeric predictor matrix (encode categoricals). p_raw = mdl.NumPredictors; if (istable (Xnew)) n_new = height (Xnew); X_raw = zeros (n_new, p_raw); for j = 1:p_raw col = Xnew.(mdl.prednames_{j}); cidx = []; if (! isempty (mdl.catinfo_.names)) cidx = find (strcmp (mdl.catinfo_.names, mdl.prednames_{j})); endif if (! isempty (cidx) && iscell (col)) levels_j = mdl.catinfo_.levels{cidx}; codes = zeros (n_new, 1); for L = 1:numel (levels_j) codes(strcmp (col, levels_j{L})) = L; endfor X_raw(:, j) = codes; else X_raw(:, j) = double (col); endif endfor else X_raw = double (Xnew); if (columns (X_raw) != p_raw) error ("GeneralizedLinearModel.predict: Xnew must have %d columns.", ... p_raw); endif endif X_enc = reencode_predictors (X_raw, mdl.prednames_, mdl.catinfo_, ... mdl.encnames_); X_design = build_design (mdl.terms_, X_enc); valargs = {'constant', 'off', 'confidence', 1 - alpha}; if (! isempty (offnew)) valargs = [valargs, {'offset', offnew(:)}]; endif ## A binomial prediction is the probability of success, never a count: ## the number of trials belongs to an observation, and new predictor ## values do not carry one. Measured against R2024a, whose PREDICT ## returns the probability for the two-column response and for ## 'BinomialSize' alike. Passing the fitted trial counts through to ## GLMVAL as 'size' also could not work for new data of a different ## height, which is what exposed this. if (nargout > 1) [yhat, ylo, yhi] = glmval (mdl.b_, X_design, mdl.linkarg_, ... mdl.stats_, valargs{:}); yci = [yhat - ylo, yhat + yhi]; else yhat = glmval (mdl.b_, X_design, mdl.linkarg_, valargs{:}); endif endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearModel} {@var{yhat} =} feval (@var{mdl}, @var{x1}, @var{x2}, @dots{}) ## ## Predict the response by passing each predictor as a separate argument (a ## scalar or column vector), returning point predictions on the mean scale. ## Equivalent to @code{predict (@var{mdl}, [@var{x1}, @var{x2}, @dots{}])}. ## ## @end deftypefn function yhat = feval (mdl, varargin) if (numel (varargin) == 1 && size (varargin{1}, 2) == mdl.NumPredictors) Xnew = varargin{1}; else cols = cellfun (@(c) c(:), varargin, 'UniformOutput', false); Xnew = [cols{:}]; endif yhat = predict (mdl, Xnew); endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearModel} {@var{ci} =} coefCI (@var{mdl}) ## @deftypefnx {GeneralizedLinearModel} {@var{ci} =} coefCI (@var{mdl}, @var{alpha}) ## ## Confidence intervals for the coefficient estimates. @var{ci} is a ## @math{k}-by-2 matrix of @math{100 (1 - @var{alpha})%} intervals (default ## @var{alpha} = 0.05). The @math{t} distribution is used when the ## dispersion was estimated, the normal distribution otherwise. ## ## @end deftypefn function ci = coefCI (mdl, alpha) if (nargin < 2) alpha = 0.05; endif if (! (isscalar (alpha) && isnumeric (alpha) && alpha >= 0 && alpha <= 1)) error ("GeneralizedLinearModel.coefCI: ALPHA must be in [0, 1]."); endif b = mdl.Coefficients.Estimate; se = mdl.Coefficients.SE; crit = tinv (1 - alpha / 2, mdl.DFE); ci = [b - crit .* se, b + crit .* se]; endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearModel} {@var{p} =} coefTest (@var{mdl}) ## @deftypefnx {GeneralizedLinearModel} {[@var{p}, @var{stat}, @var{df}] =} coefTest (@var{mdl}, @var{H}) ## ## Wald test of the linear hypothesis @math{H b = 0} on the coefficients. ## @var{H} is an @math{m}-by-@math{k} contrast matrix; when omitted it tests ## that all coefficients except the intercept are zero (the model versus the ## constant model). Returns the p-value @var{p}, and optionally the test ## statistic @var{stat} and its numerator degrees of freedom @var{df}. ## ## @end deftypefn function [p, stat, df] = coefTest (mdl, H) k = mdl.NumCoefficients; if (nargin < 2) ipos = find (strcmp (mdl.CoefficientNames, '(Intercept)')); rows_h = setdiff (1:k, ipos); H = zeros (numel (rows_h), k); for i = 1:numel (rows_h) H(i, rows_h(i)) = 1; endfor endif b = mdl.Coefficients.Estimate; df = rows (H); Hb = H * b; HVH = H * mdl.CoefficientCovariance * H'; ## Wald F statistic (MATLAB uses the F distribution for coefTest). stat = (Hb' * (HVH \ Hb)) / df; p = 1 - fcdf (stat, df, mdl.DFE); endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearModel} {@var{tbl} =} devianceTest (@var{mdl}) ## ## Likelihood-ratio (deviance) test of the fitted model against the ## intercept-only model. Returns a table with the deviance, degrees of ## freedom, and p-value of each model, the last row giving the chi-square ## statistic (the drop in deviance) and its p-value. Each row is named by ## the formula of the model it describes. ## ## @end deftypefn function tbl = devianceTest (mdl) dev_full = mdl.Deviance; dev_null = mdl.nulldev_; df_diff = mdl.NumCoefficients - 1; chi2stat = dev_null - dev_full; pval = 1 - chi2cdf (chi2stat, df_diff); Deviance = [dev_null; dev_full]; DFE = [mdl.DFE + df_diff; mdl.DFE]; chi2Stat = [NaN; chi2stat]; pValue = [NaN; pval]; ## Both rows are named by their formula, as MATLAB renders them: the null ## model is the same linked response against an intercept alone. nullstr = sprintf ("%s ~ 1", strtrim (strtok (mdl.formulastr_, "~"))); tbl = table (Deviance, DFE, chi2Stat, pValue, ... 'VariableNames', {'Deviance', 'DFE', 'chi2Stat', 'pValue'}, ... 'RowNames', {nullstr, mdl.formulastr_}); endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearModel} {@var{ysim} =} random (@var{mdl}) ## @deftypefnx {GeneralizedLinearModel} {@var{ysim} =} random (@var{mdl}, @var{Xnew}) ## ## Simulate responses from the fitted model. With one argument the fitted ## values are used; otherwise the mean is predicted at the new predictor ## data @var{Xnew}. A random draw from the response distribution about that ## mean is returned. ## ## @end deftypefn function ysim = random (mdl, Xnew) if (nargin < 2) mu = mdl.Fitted.Response; else mu = predict (mdl, Xnew); endif switch (mdl.distr_) case 'normal' ysim = normrnd (mu, sqrt (mdl.Dispersion)); case 'poisson' ysim = poissrnd (mu); case 'binomial' if (isempty (mdl.binomsize_)) ysim = binornd (1, mu); else N = mdl.binomsize_(:); if (isscalar (N)) N = N * ones (size (mu)); endif ysim = binornd (N, mu ./ N); endif case 'gamma' ysim = gamrnd (1 / mdl.Dispersion, mu * mdl.Dispersion); case 'inverse gaussian' error (strcat ("GeneralizedLinearModel.random: simulation for the", ... " 'inverse gaussian' distribution is not supported.")); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearModel} {@var{h} =} plotResiduals (@var{mdl}) ## @deftypefnx {GeneralizedLinearModel} {@var{h} =} plotResiduals (@var{mdl}, @var{plottype}) ## @deftypefnx {GeneralizedLinearModel} {@var{h} =} plotResiduals (@dots{}, @qcode{'ResidualType'}, @var{rt}) ## ## Plot the model residuals. @var{plottype} is one of @qcode{'histogram'} ## (default), @qcode{'caseorder'}, @qcode{'fitted'}, @qcode{'lagged'}, or ## @qcode{'probability'}. @qcode{'ResidualType'} picks the residual column ## (@qcode{'Raw'} default, @qcode{'Pearson'}, @qcode{'Deviance'}, ## @qcode{'Anscombe'}). Returns the graphics handle. ## ## @end deftypefn function h = plotResiduals (mdl, varargin) ptype = 'histogram'; rtype = 'Raw'; k = 1; if (numel (varargin) >= 1 && ischar (varargin{1}) ... && ! strcmpi (varargin{1}, 'ResidualType')) ptype = varargin{1}; k = 2; endif for i = k:2:numel (varargin) if (strcmpi (varargin{i}, 'ResidualType')) rtype = varargin{i+1}; endif endfor r = mdl.Residuals.(rtype); switch (lower (ptype)) case 'histogram' h = hist (r); xlabel ("Residuals"); ylabel ("Frequency"); case 'caseorder' h = plot (1:numel (r), r, 'x'); xlabel ("Row number"); ylabel (sprintf ("%s residuals", rtype)); case 'fitted' h = plot (mdl.Fitted.Response, r, 'x'); xlabel ("Fitted values"); ylabel (sprintf ("%s residuals", rtype)); case 'lagged' h = plot (r(1:end-1), r(2:end), 'x'); xlabel ("Residual (t-1)"); ylabel ("Residual (t)"); case 'probability' [rs, idx] = sort (r); n = numel (rs); pp = ((1:n)' - 0.5) / n; h = plot (rs, norminv (pp), 'x'); xlabel (sprintf ("%s residuals", rtype)); ylabel ("Standard normal quantiles"); otherwise error ("GeneralizedLinearModel.plotResiduals: bad plot type '%s'.", ... ptype); endswitch title (sprintf ("Residuals: %s", ptype)); endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearModel} {@var{h} =} plotDiagnostics (@var{mdl}) ## @deftypefnx {GeneralizedLinearModel} {@var{h} =} plotDiagnostics (@var{mdl}, @var{plottype}) ## ## Plot observation diagnostics. @var{plottype} is @qcode{'leverage'} ## (default) or @qcode{'cookd'} (Cook's distance). A reference line marks ## the usual threshold. Returns the graphics handle. ## ## @end deftypefn function h = plotDiagnostics (mdl, plottype) if (nargin < 2) plottype = 'leverage'; endif n = mdl.NumObservations; idx = find (mdl.subset_); switch (lower (plottype)) case 'leverage' lev = mdl.Diagnostics.Leverage(idx); h = stem (idx, lev, 'Marker', 'x'); ref = 2 * mdl.NumCoefficients / n; ylabel ("Leverage"); case 'cookd' cd = mdl.Diagnostics.CooksDistance(idx); h = stem (idx, cd, 'Marker', 'x'); ref = 3 * mean (cd); ylabel ("Cook's distance"); otherwise error (strcat ("GeneralizedLinearModel.plotDiagnostics: bad plot", ... " type '%s'."), plottype); endswitch hold on; plot ([idx(1), idx(end)], [ref, ref], 'r--'); hold off; xlabel ("Row number"); title (sprintf ("Diagnostics: %s", plottype)); endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearModel} {@var{h} =} plotEffects (@var{mdl}) ## ## Main-effects plot: for each predictor, the change in the fitted mean ## response as that predictor sweeps its observed range while the others are ## held at their means. Returns the graphics handle. ## ## @end deftypefn function h = plotEffects (mdl) p = mdl.NumPredictors; base = mdl.xmeans_; eff = zeros (p, 1); for j = 1:p a = base; b = base; a(j) = base(j) - 1; b(j) = base(j) + 1; eff(j) = predict (mdl, b) - predict (mdl, a); endfor h = plot (eff, 1:p, 'o', 'MarkerFaceColor', 'b'); hold on; for j = 1:p plot ([0, eff(j)], [j, j], 'b-'); endfor plot ([0, 0], [0.5, p + 0.5], 'k:'); hold off; ylim ([0.5, p + 0.5]); set (gca, 'YTick', 1:p, 'YTickLabel', mdl.PredictorNames); xlabel ("Effect on fitted response (two-unit change)"); title ("Main effects"); endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearModel} {@var{h} =} plotAdjustedResponse (@var{mdl}, @var{var}) ## ## Adjusted-response plot for the predictor @var{var} (a name or index): the ## fitted mean response as @var{var} sweeps its observed range with other ## predictors held at their means, overlaid on the partial residuals. ## Returns the graphics handle. ## ## @end deftypefn function h = plotAdjustedResponse (mdl, var) j = resolve_predictor (mdl, var); xj = linspace (mdl.xmeans_(j) - 2, mdl.xmeans_(j) + 2, 50)'; Xg = repmat (mdl.xmeans_, numel (xj), 1); Xg(:,j) = xj; yg = predict (mdl, Xg); h = plot (xj, yg, 'b-', 'LineWidth', 1.5); xlabel (mdl.PredictorNames{j}); ylabel (sprintf ("Adjusted %s", mdl.ResponseName)); title (sprintf ("Adjusted response for %s", mdl.PredictorNames{j})); endfunction ## -*- texinfo -*- ## @deftypefn {GeneralizedLinearModel} {@var{h} =} plotAdded (@var{mdl}, @var{var}) ## ## Added-variable (partial-regression) plot for the predictor @var{var} (a ## name or index): the response residuals from the model without @var{var} ## against the residuals of @var{var} regressed on the remaining predictors. ## Returns the graphics handle. ## ## @end deftypefn function h = plotAdded (mdl, var) j = resolve_predictor (mdl, var); X = mdl.design_; ## Locate the design column of the requested predictor. cidx = find (strcmp (mdl.CoefficientNames, mdl.PredictorNames{j}), 1); if (isempty (cidx)) error (strcat ("GeneralizedLinearModel.plotAdded: predictor is not a", ... " single design column.")); endif others = setdiff (1:columns (X), cidx); Xo = X(:,others); ## The design holds the fitted rows only, so take the residuals of the ## same rows. ry = mdl.Residuals.Raw(mdl.subset_); xj = X(:,cidx); bx = Xo \ xj; rx = xj - Xo * bx; h = plot (rx, ry, 'x'); hold on; bb = rx \ ry; xr = [min(rx), max(rx)]; plot (xr, bb * xr, 'r-'); hold off; xlabel (sprintf ("%s (adjusted)", mdl.PredictorNames{j})); ylabel (sprintf ("%s (adjusted)", mdl.ResponseName)); title (sprintf ("Added variable plot for %s", mdl.PredictorNames{j})); endfunction endmethods endclassdef ## Parse GeneralizedLinearModel name/value options into a structure. function opts = glm_parse_nv (nv) opts = struct ('Distribution', 'normal', 'Link', [], 'Weights', [], ... 'Offset', [], 'BinomialSize', [], 'Intercept', true, ... 'DispersionFlag', [], 'VarNames', {{}}, 'ResponseVar', '', ... 'PredictorVars', {{}}, 'CategoricalVars', [], 'Exclude', []); if (mod (numel (nv), 2) != 0) error (strcat ("GeneralizedLinearModel: optional arguments must be", ... " Name-Value pairs.")); endif for k = 1:2:numel (nv) switch (lower (nv{k})) case 'distribution'; opts.Distribution = lower (nv{k+1}); case 'link'; opts.Link = nv{k+1}; case 'weights'; opts.Weights = nv{k+1}; case 'offset'; opts.Offset = nv{k+1}; case 'binomialsize'; opts.BinomialSize = nv{k+1}; case 'intercept'; opts.Intercept = logical (nv{k+1}); case 'dispersionflag'; opts.DispersionFlag = logical (nv{k+1}); case 'varnames'; opts.VarNames = nv{k+1}; case 'responsevar'; opts.ResponseVar = nv{k+1}; case 'predictorvars'; opts.PredictorVars = nv{k+1}; case 'categoricalvars'; opts.CategoricalVars = nv{k+1}; case 'exclude'; opts.Exclude = nv{k+1}; otherwise error (strcat ("GeneralizedLinearModel: unknown parameter", ... " name '%s'."), nv{k}); endswitch endfor if (! any (strcmp (opts.Distribution, {'normal', 'binomial', 'poisson', ... 'gamma', 'inverse gaussian'}))) error ("GeneralizedLinearModel: unknown distribution '%s'.", ... opts.Distribution); endif endfunction ## Broadcast a scalar to N rows; otherwise pass the vector through. function v = expand_to_rows (v, n) if (isscalar (v)) v = v * ones (n, 1); endif endfunction ## Check a binomial response given as counts S against its trial counts N. ## Returns a message body rather than raising, so that the caller names ## itself; empty when the pair is good. Shared by the two-column response and ## the 'BinomialSize' form, which mean the same thing. ## ## The rules are MATLAB's, measured against R2024a: the trials must be ## non-negative integers, while the successes need not be integral, only ## within [0, trials]. A NaN success count is left alone, so that the ## proportion becomes NaN and the row is dropped as missing exactly as a NaN ## in a plain response vector is. A non-finite trial count is refused here; ## MATLAB does not check it and leaks 'X must be in the interval [0,1]' out of ## the fitter instead. function emsg = check_binomial_counts (S, N) emsg = ''; ## Deliberately stricter than MATLAB on one point: a trial count of zero is ## refused rather than accepted. MATLAB rejects it for any row with a ## success (the [0,N] rule), leaving only the wholly empty observation, ## which carries no information and would divide zero by zero here. if (! all (isfinite (N)) || any (N < 1) || any (N != fix (N))) emsg = strcat ("the number of binomial trials must be finite", ... " positive integers."); return; endif if (! all (isnan (S) | (S >= 0 & S <= N))) emsg = strcat ("a binomial response holds the number of successes and", ... " must be between zero and the corresponding number of", ... " trials."); endif endfunction ## Base names of the variables a Wilkinson formula refers to (response ## included), with any '^k' power suffix stripped. function vnames = formula_var_names (modelspec) schema = parseWilkinsonFormula (modelspec, 'matrix'); vnames = regexprep (schema.VariableNames, '\^\d+$', ''); vnames = unique (vnames, 'stable'); endfunction ## Display name of a response distribution. function name = distribution_name (distr) switch (distr) case 'normal'; name = 'Normal'; case 'binomial'; name = 'Binomial'; case 'poisson'; name = 'Poisson'; case 'gamma'; name = 'Gamma'; case 'inverse gaussian'; name = 'Inverse Gaussian'; endswitch endfunction ## Deviance and variance functions of a response distribution, as the handles ## reported by the Distribution property. function [devfun, varfun] = glm_family_functions (distr) switch (distr) case 'normal' devfun = @(mu, y) (y - mu) .^ 2; varfun = @(mu) ones (size (mu)); case 'binomial' devfun = @(mu, y, N) 2 * N .* (y .* log ((y + (y == 0)) ./ mu) ... + (1 - y) .* log ((1 - y + (y == 1)) ./ (1 - mu))); varfun = @(mu, N) mu .* (1 - mu) ./ N; case 'poisson' devfun = @(mu, y) 2 * (y .* (log ((y + (y == 0)) ./ mu)) - (y - mu)); varfun = @(mu) mu; case 'gamma' devfun = @(mu, y) 2 * (-log (y ./ mu) + (y - mu) ./ mu); varfun = @(mu) mu .^ 2; case 'inverse gaussian' devfun = @(mu, y) (((y - mu) ./ mu) .^ 2) ./ y; varfun = @(mu) mu .^ 3; endswitch endfunction ## Raw, Pearson, Anscombe, and deviance residuals of a GLM fit, evaluated for ## every input row. Y is the response (a proportion for the binomial family), ## MU the fitted probability/mean, and N the binomial trial counts (empty ## otherwise). Matches the residuals @code{glmfit} returns for the fitted rows. function [raw, pear, ansc, devr] = glm_residuals (distr, y, mu, N) [devfun, varfun] = glm_family_functions (distr); if (strcmp (distr, 'binomial')) if (isempty (N)) N = ones (size (y)); endif raw = (y - mu) .* N; sd = sqrt (varfun (mu, N)); devn = devfun (mu, y, N); else raw = y - mu; sd = sqrt (varfun (mu)); devn = devfun (mu, y); endif pear = (y - mu) ./ (sd + (y == mu)); devr = sign (y - mu) .* sqrt (max (0, devn)); switch (distr) case 'normal' ansc = y - mu; case 'binomial' ab = 2 / 3; ansc = beta (ab, ab) * (betainc (y, ab, ab) - betainc (mu, ab, ab)) ... ./ ((mu .* (1 - mu)) .^ (1 / 6) ./ sqrt (N)); case 'poisson' ansc = 1.5 * ((y .^ (2 / 3) - mu .^ (2 / 3)) ./ mu .^ (1 / 6)); case 'gamma' pwr = 1 / 3; ansc = 3 * (y .^ pwr - mu .^ pwr) ./ mu .^ pwr; case 'inverse gaussian' ansc = (log (y) - log (mu)) ./ mu; endswitch endfunction ## Log-likelihood of a GLM fit. Y is the response (proportion for binomial), ## MU the fitted probability/mean, N the binomial trials (empty otherwise), W ## the prior weights, and PHI the dispersion parameter. function ll = glm_loglik (distr, y, mu, N, w, phi) rmin = realmin; switch (distr) case 'normal' ne = sum (w); s2 = sum (w .* (y - mu) .^ 2) / ne; ll = -0.5 * ne * (log (2 * pi * s2) + 1); case 'poisson' ll = sum (w .* (y .* log (max (mu, rmin)) - mu - gammaln (y + 1))); case 'binomial' if (isempty (N)) N = ones (size (y)); endif yc = y .* N; ll = sum (w .* (gammaln (N + 1) - gammaln (yc + 1) ... - gammaln (N - yc + 1) + yc .* log (max (mu, rmin)) ... + (N - yc) .* log (max (1 - mu, rmin)))); case 'gamma' a = 1 ./ phi; ll = sum (w .* (a .* log (a) - a .* log (mu) + (a - 1) .* log (y) ... - a .* y ./ mu - gammaln (a))); case 'inverse gaussian' ll = sum (w .* (-0.5 * (log (2 * pi * phi .* y .^ 3) ... + (y - mu) .^ 2 ./ (phi .* mu .^ 2 .* y)))); endswitch endfunction ## Resolve a predictor reference (name or index) to a column index. function j = resolve_predictor (mdl, var) if (ischar (var)) j = find (strcmp (mdl.PredictorNames, var), 1); if (isempty (j)) error ("GeneralizedLinearModel: unknown predictor '%s'.", var); endif else j = var; if (! (isscalar (j) && j >= 1 && j <= mdl.NumPredictors)) error ("GeneralizedLinearModel: predictor index out of range."); endif endif endfunction ## Variance function V(mu) of the response distribution (for the IRLS weights ## used in leverage/Cook's-distance diagnostics). function v = glm_varfun (distr, mu, N) [~, varfun] = glm_family_functions (distr); if (strcmp (distr, 'binomial')) if (isempty (N)) N = ones (size (mu)); endif v = varfun (mu, N); else v = varfun (mu); endif endfunction ## Response distribution's canonical link specification. function spec = default_link_spec (distr) switch (distr) case 'normal'; spec = 'identity'; case 'binomial'; spec = 'logit'; case 'poisson'; spec = 'log'; case 'gamma'; spec = 'reciprocal'; case 'inverse gaussian'; spec = -2; endswitch endfunction ## Human-readable name of a link specification. function name = link_name (linkarg) if (ischar (linkarg)) name = linkarg; elseif (isnumeric (linkarg) && isscalar (linkarg)) name = sprintf ('%g', linkarg); else name = 'custom'; endif endfunction ## Small inline conditional helper. function out = ternary (cond, a, b) if (cond) out = a; else out = b; endif endfunction %!demo %! ## Fit a Poisson regression and inspect the model object. %! X = [0.1, 1.2; 0.4, 0.7; 1.1, 0.2; 1.5, 1.9; 0.3, 0.5; 1.8, 1.1; 0.9, 0.3]; %! y = [1; 0; 2; 3; 1; 4; 2]; %! mdl = fitglm (X, y, 'Distribution', 'poisson'); %! disp (mdl.Coefficients) %! printf ("Deviance = %g, AIC = %g\n", mdl.Deviance, mdl.ModelCriterion.AIC); ## Test direct construction and input validation %!test %! X = [1 2; 2 1; 3 4; 4 3; 5 6; 6 5; 1 3; 4 2]; %! y = [1; 0; 2; 3; 2; 4; 1; 3]; %! mdl = GeneralizedLinearModel (X, y, "linear", "Distribution", "poisson"); %! assert_equal (class (mdl), "GeneralizedLinearModel"); %! assert_equal (mdl.Distribution.Name, "Poisson"); %! assert_equal (mdl.Link.Name, "log"); %!error GeneralizedLinearModel (1) %!error ... %! GeneralizedLinearModel ("a", [1;2], "linear") %!error ... %! GeneralizedLinearModel ([1 2; 3 4], "a", "linear") %!error ... %! GeneralizedLinearModel ([1 2; 3 4], [1;0], "linear", ... %! "Distribution", "wibble") %!error ... %! mdl = GeneralizedLinearModel ([1 2; 2 1; 3 4; 4 3; 5 6; 6 5], ... %! [1;0;2;3;2;4], "linear", ... %! "Distribution", "poisson"); mdl(1); ## Comprehensive property and method coverage %!shared X, yp, yb, yn %! X = [ 0.37, 0.06, 1.76; -0.76, -1.52, 0.84; 0.76, -0.19, -0.47; ... %! -0.80, -2.74, -0.90; 0.08, 0.39, 1.05; -0.41, -0.03, 0.74; ... %! 0.23, 1.21, 0.35; 0.66, 0.94, 0.13; 0.66, -0.12, -0.06; ... %! 2.09, 1.33, -0.71; 1.50, 0.08, -0.52; 0.59, 0.07, -1.13; ... %! -1.17, -0.35, -1.28; 0.68, 0.63, -0.80; -0.69, 0.08, 0.41; ... %! 2.04, 0.96, -0.56]; %! yp = [5 2 0 3 1 1 0 1 2 1 3 0 0 1 1 3]'; %! yb = [1 1 1 0 0 1 1 1 1 1 1 0 0 0 0 1]'; %! yn = [2.1 -0.3 1.2 -1.1 0.8 0.4 1.5 1.1 0.6 2.9 2.0 0.7 -1.3 0.9 -0.2 2.5]'; %!test # a normal-distribution GLM with identity link reproduces OLS exactly %! mdl = fitglm (X, yn, "Distribution", "normal"); %! b_ols = [ones(16, 1), X] \ yn; %! assert_equal (mdl.Coefficients.Estimate, b_ols, 1e-10); %! assert_equal (mdl.Fitted.Response, [ones(16, 1), X] * b_ols, 1e-10); %! assert_equal (mdl.Residuals.Raw, yn - [ones(16, 1), X] * b_ols, 1e-10); %! assert_equal (mdl.Deviance, sum ((yn - [ones(16, 1), X] * b_ols) .^ 2), 1e-10); %! assert_equal (mdl.Link.Name, "identity"); %!test # the normal-GLM standard errors match those from regress %! mdl = fitglm (X, yn, "Distribution", "normal"); %! [~, bint] = regress (yn, [ones(16, 1), X], 0.05); %! se_reg = (bint(:,1) - bint(:,2)) / 2 / tinv (0.025, 12); %! assert_equal (mdl.Coefficients.SE, se_reg, 1e-9); %!test # normal-GLM dispersion is estimated as SSE/DFE; log-likelihood closed form %! mdl = fitglm (X, yn, "Distribution", "normal"); %! rss = mdl.Deviance; %! assert_equal (mdl.DispersionEstimated, true); %! assert_equal (mdl.Dispersion, rss / mdl.DFE, 1e-12); %! assert_equal (mdl.LogLikelihood, -8 * (log (2 * pi * rss / 16) + 1), 1e-6); %!test # Poisson coefficients and fit statistics (verified against MATLAB) %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (mdl.Coefficients.Estimate, ... %! [-0.3420955; 1.2804868; -1.0743272; 0.8395779], 1e-6); %! assert_equal (mdl.Deviance, 7.403008, 1e-5); %! assert_equal (mdl.LogLikelihood, -18.543280, 1e-5); %! assert_equal (mdl.ModelCriterion.AIC, 45.086559, 1e-5); %! assert_equal (mdl.Rsquared.Deviance, 0.6627677, 1e-6); %!test # scalar count/size properties of the Poisson fit %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (mdl.NumCoefficients, 4); %! assert_equal (mdl.NumEstimatedCoefficients, 4); %! assert_equal (mdl.NumPredictors, 3); %! assert_equal (mdl.NumObservations, 16); %! assert_equal (mdl.DFE, 12); %! assert_equal (mdl.ResponseName, "y"); %! assert_equal (mdl.CoefficientNames, {'(Intercept)', 'x1', 'x2', 'x3'}); %!test # Poisson has a fixed unit dispersion (not estimated) %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (mdl.Dispersion, 1); %! assert_equal (mdl.DispersionEstimated, false); %! assert_equal (mdl.Distribution.Name, "Poisson"); %! assert_equal (mdl.Link.Name, "log"); %!test # the coefficient covariance is symmetric with SE^2 on its diagonal %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! C = mdl.CoefficientCovariance; %! assert_equal (size (C), [4, 4]); %! assert_equal (C, C', 1e-14); %! assert_equal (diag (C), mdl.Coefficients.SE .^ 2, 1e-12); %!test # predict at the training data reproduces the fitted response %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! [yhat, yci] = predict (mdl, X); %! assert_equal (yhat, mdl.Fitted.Response, 1e-10); %! assert_equal (size (yci), [16, 2]); %! assert_equal (all (yci(:,1) <= yhat & yhat <= yci(:,2)), true); %!test # feval evaluates the model and agrees with predict %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (feval (mdl, X(:,1), X(:,2), X(:,3)), predict (mdl, X), 1e-12); %!test # coefCI matches the t-interval and honours a custom alpha %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! b = mdl.Coefficients.Estimate; se = mdl.Coefficients.SE; %! t95 = tinv (0.975, mdl.DFE); %! assert_equal (coefCI (mdl), [b - t95 * se, b + t95 * se], 1e-12); %! t90 = tinv (0.95, mdl.DFE); %! assert_equal (coefCI (mdl, 0.10), [b - t90 * se, b + t90 * se], 1e-12); %!test # coefTest gives the Wald F statistic against the constant model %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! [p, F, df] = coefTest (mdl); %! assert_equal (F, 3.685312, 1e-5); %! assert_equal (p, 0.04331745, 1e-7); %! assert_equal (df, 3); %!test # devianceTest chi-square equals the drop from the null deviance %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! dt = devianceTest (mdl); %! assert_equal (class (dt), "table"); %! assert_equal (dt.chi2Stat(2), dt.Deviance(1) - dt.Deviance(2), 1e-10); %!test # information criteria satisfy their defining identities %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! k = mdl.NumEstimatedCoefficients; ll = mdl.LogLikelihood; %! assert_equal (mdl.ModelCriterion.AIC, -2 * ll + 2 * k, 1e-9); %! assert_equal (mdl.ModelCriterion.BIC, -2 * ll + k * log (16), 1e-9); %!test # raw residuals are response minus fit; random draws match the response size %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (mdl.Residuals.Raw, yp - mdl.Fitted.Response, 1e-12); %! ysim = random (mdl); %! assert_equal (size (ysim), [16, 1]); %! assert_equal (all (ysim == round (ysim) & ysim >= 0), true); %!test # binomial/logistic fit: coefficients agree with the glmfit engine %! mdl = fitglm (X, yb, "Distribution", "binomial"); %! assert_equal (mdl.Coefficients.Estimate, glmfit (X, yb, "binomial"), 1e-8); %! assert_equal (mdl.Link.Name, "logit"); %! assert_equal (all (mdl.Fitted.Response >= 0 & mdl.Fitted.Response <= 1), true); %! assert_equal (mdl.Deviance, 10.997099, 1e-5); %!test # an interaction model adds the cross term and one coefficient %! mdl = fitglm (X, yp, "interactions", "Distribution", "poisson"); %! assert_equal (any (strcmp (mdl.CoefficientNames, "x1:x2")), true); %! assert_equal (mdl.NumCoefficients, 7); %!test # an offset is stored and applied %! mdl = fitglm (X, yp, "Distribution", "poisson", "Offset", log (2 * ones (16, 1))); %! assert_equal (numel (mdl.Offset), 16); %!test # disp prints the model header and the coefficient table %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! s = evalc ("disp (mdl)"); %! assert_equal (isempty (strfind (s, "Generalized linear regression model")), false); %! assert_equal (isempty (strfind (s, "Estimate")), false); %!test # chained subsref reaches property -> table column -> element %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (numel (mdl.Coefficients.Estimate), 4); %! assert_equal (mdl.Coefficients.Estimate(1), -0.3420955, 1e-6); %! assert_equal (mdl.Coefficients.Estimate(2), mdl.Coefficients{2, "Estimate"}, 1e-12); %!test # the diagnostic and effect plots run without error %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! hf = figure ("visible", "off"); %! unwind_protect %! plotResiduals (mdl); %! plotResiduals (mdl, "fitted", "ResidualType", "Pearson"); %! plotDiagnostics (mdl); %! plotDiagnostics (mdl, "cookd"); %! plotEffects (mdl); %! plotAdjustedResponse (mdl, 1); %! plotAdded (mdl, "x2"); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## The property surface below is checked against MATLAB R2024a on the same data. %!test # each family reports its MATLAB display name and its own functions %! names = {'Normal', 'Binomial', 'Poisson', 'Gamma', 'Inverse Gaussian'}; %! dists = {'normal', 'binomial', 'poisson', 'gamma', 'inverse gaussian'}; %! resp = {yn, yb, yp, abs(yn) + 1, abs(yn) + 1}; %! for k = 1:numel (dists) %! mdl = fitglm (X, resp{k}, "Distribution", dists{k}); %! assert_equal (mdl.Distribution.Name, names{k}); %! dev = mdl.Distribution.DevianceFunction; %! var = mdl.Distribution.VarianceFunction; %! assert_equal (is_function_handle (dev), true); %! assert_equal (is_function_handle (var), true); %! endfor %!test # the variance function is the family's variance, not its scale %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! v = mdl.Distribution.VarianceFunction; %! assert_equal (v ([1; 4; 9]), [1; 4; 9]); %! mdl = fitglm (X, abs (yn) + 1, "Distribution", "gamma"); %! v = mdl.Distribution.VarianceFunction; %! assert_equal (v ([1; 2; 3]), [1; 4; 9]); %!test # the deviance function reproduces the reported deviance %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! d = mdl.Distribution.DevianceFunction; %! assert_equal (sum (d (mdl.Fitted.Response, yp)), mdl.Deviance, 1e-10); %!test # sums of squares match MATLAB and reproduce Rsquared.Ordinary %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (mdl.SSE, 5.878748587925331, 1e-12); %! assert_equal (mdl.SSR, 22.693546014008586, 1e-11); %! assert_equal (mdl.SST, 30, 1e-12); %! assert_equal (mdl.Rsquared.Ordinary, 1 - mdl.SSE / mdl.SST, 1e-14); %!test # SSE + SSR closes to SST only for the identity link %! mdl = fitglm (X, yn); %! assert_equal (mdl.SSE, 1.007522513007451, 1e-12); %! assert_equal (mdl.SSR, 20.549977486992557, 1e-11); %! assert_equal (mdl.SST, 21.557500000000005, 1e-12); %! assert_equal (mdl.SSE + mdl.SSR, mdl.SST, 1e-12); %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (mdl.SSE + mdl.SSR < mdl.SST, true); %!test # binomial and gamma sums of squares %! mdl = fitglm (X, yb, "Distribution", "binomial"); %! assert_equal ([mdl.SSE, mdl.SSR, mdl.SST], ... %! [1.529775270428043, 2.016466154627350, 3.75], 1e-11); %! mdl = fitglm (X, abs (yn) + 1, "Distribution", "gamma"); %! assert_equal ([mdl.SSE, mdl.SSR, mdl.SST], ... %! [3.537122914639216, 5.173700107311616, 9.45], 1e-9); %!test # counts, penalty, and observation names %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (mdl.NumVariables, 4); %! assert_equal (char (mdl.LikelihoodPenalty), "none"); %! assert_equal (mdl.ObservationNames, {}); %! assert_equal (size (mdl.Steps), [0, 0]); %!test # Variables holds the data the model was built from %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (class (mdl.Variables), "table"); %! assert_equal (size (mdl.Variables), [16, 4]); %! assert_equal (mdl.Variables.Properties.VariableNames, ... %! {'x1', 'x2', 'x3', 'y'}); %! assert_equal (mdl.Variables{:, 'x2'}, X(:,2)); %! assert_equal (mdl.Variables{:, 'y'}, yp); %!test # ObservationInfo spans the input rows and records why each was used %! mdl = fitglm (X, yn); %! assert_equal (size (mdl.ObservationInfo), [16, 4]); %! assert_equal (mdl.ObservationInfo.Properties.VariableNames, ... %! {'Weights', 'Excluded', 'Missing', 'Subset'}); %! assert_equal (mdl.ObservationInfo.Weights, ones (16, 1)); %! assert_equal (any (mdl.ObservationInfo.Excluded), false); %! assert_equal (all (mdl.ObservationInfo.Subset), true); %!test # excluded rows keep their weight and drop out of the fit %! mdl = fitglm (X, yn, "Exclude", [2 5], "Weights", (1:16)' / 16); %! assert_equal (mdl.NumObservations, 14); %! assert_equal (mdl.DFE, 10); %! assert_equal (mdl.ObservationInfo.Weights, (1:16)' / 16); %! assert_equal (find (mdl.ObservationInfo.Excluded), [2; 5]); %! assert_equal (any (mdl.ObservationInfo.Missing), false); %! assert_equal (find (! mdl.ObservationInfo.Subset), [2; 5]); %! assert_equal (mdl.SSE, 0.376329223083291, 1e-11); %! assert_equal (mdl.SST, 12.408120155038763, 1e-10); %! assert_equal (mdl.Dispersion, 0.037632922308329, 1e-12); %!test # an excluded row still gets a fitted value; a missing row does not %! mdl = fitglm (X, yn, "Exclude", [2 5], "Weights", (1:16)' / 16); %! assert_equal (size (mdl.Fitted), [16, 2]); %! assert_equal (mdl.Fitted.Response(1:3), ... %! [1.452778236704902; -0.409857651323262; 1.021905387197724], 1e-11); %! assert_equal (mdl.Residuals.Raw(2), 0.109857651323262, 1e-11); %! Xm = X; Xm(3,2) = NaN; %! mdl = fitglm (Xm, yn); %! assert_equal (mdl.NumObservations, 15); %! assert_equal (find (mdl.ObservationInfo.Missing), 3); %! assert_equal (isnan (mdl.Fitted.Response(3)), true); %! assert_equal (isnan (mdl.Residuals.Raw(3)), true); %! assert_equal (mdl.Fitted.Response(1), 1.675087141815260, 1e-11); %! assert_equal (mdl.SSE, 0.990693655817047, 1e-11); %!test # the binomial family adds a Probability column to Fitted %! mdl = fitglm (X, yb, "Distribution", "binomial"); %! assert_equal (mdl.Fitted.Properties.VariableNames, ... %! {'Response', 'LinearPredictor', 'Probability'}); %! assert_equal (mdl.Fitted.Probability(1), 0.998111791212415, 1e-9); %! assert_equal (mdl.Fitted.Response, mdl.Fitted.Probability, 1e-14); %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (mdl.Fitted.Properties.VariableNames, ... %! {'Response', 'LinearPredictor'}); %!test # with BinomialSize the response is a count and so is the fit %! N = 5 * ones (16, 1); %! y = [3 4 5 1 0 4 3 5 4 5 5 1 0 2 1 5]'; %! mdl = fitglm (X, y, "Distribution", "binomial", "BinomialSize", N); %! assert_equal (mdl.Fitted.Response, N .* mdl.Fitted.Probability, 1e-12); %! assert_equal (mdl.Residuals.Raw, y - mdl.Fitted.Response, 1e-12); %!test # a two-column response and BinomialSize describe the same model %! N = 5 * ones (16, 1); %! y = [3 4 5 1 0 4 3 5 4 5 5 1 0 2 1 5]'; %! m1 = fitglm (X, [y, N], "Distribution", "binomial"); %! m2 = fitglm (X, y, "Distribution", "binomial", "BinomialSize", N); %! assert_equal (m1.Coefficients.Estimate, m2.Coefficients.Estimate, 1e-12); %! assert_equal (m1.Deviance, m2.Deviance, 1e-12); %!test # the two-column fit matches MATLAB R2024a %! x = (1:10)'; %! S = [0 1 1 2 3 4 6 7 9 9]'; %! mdl = fitglm (x, [S, 10 * ones(10, 1)], "Distribution", "binomial"); %! assert_equal (mdl.Coefficients.Estimate, ... %! [-4.07619632416318; 0.639874139429377], 1e-10); %! assert_equal (mdl.Deviance, 1.37328920133713, 1e-10); %! assert_equal (mdl.LogLikelihood, -11.0853057442458, 1e-10); %! assert_equal (mdl.Fitted.Probability(1), 0.0311793894382727, 1e-10); %! assert_equal (mdl.Fitted.Response(1), 0.311793894382727, 1e-10); %! assert_equal (mdl.Residuals.Raw(1), -0.311793894382727, 1e-10); %!test # a two-column fit over the shared predictors matches MATLAB R2024a %! N = 5 * ones (16, 1); %! y = [3 4 5 1 0 4 3 5 4 5 5 1 0 2 1 5]'; %! mdl = fitglm (X, [y, N], "Distribution", "binomial"); %! assert_equal (mdl.Coefficients.Estimate, ... %! [-0.130914595525068; 2.08707009838652; ... %! -0.640296111095014; 0.746404482784674], 1e-10); %! assert_equal (mdl.Deviance, 30.7821361135612, 1e-10); %! assert_equal (mdl.LogLikelihood, -23.9339330144783, 1e-10); %! assert_equal (mdl.Fitted.Response(1), 4.35876915277425, 1e-10); %! assert_equal (mdl.Residuals.Raw(1), -1.35876915277425, 1e-10); %!test # the trials given with the response win over BinomialSize %! x = (1:10)'; %! S = [0 1 1 2 3 4 6 7 9 9]'; %! N = 10 * ones (10, 1); %! mdl = fitglm (x, [S, N], "Distribution", "binomial", "BinomialSize", (2:2:20)'); %! assert_equal (mdl.Coefficients.Estimate, ... %! [-4.07619632416318; 0.639874139429377], 1e-10); %!test # a binomial prediction is a probability, not a count %! x = (1:10)'; %! S = [0 1 1 2 3 4 6 7 9 9]'; %! mdl = fitglm (x, [S, 10 * ones(10, 1)], "Distribution", "binomial"); %! assert_equal (predict (mdl, [2; 5; 8]), ... %! [0.0575164189082781; 0.293836018581318; ... %! 0.739389288247498], 1e-10); %!test # Variables reports the successes, not the proportion fitted %! x = (1:10)'; %! S = [0 1 1 2 3 4 6 7 9 9]'; %! mdl = fitglm (x, [S, 10 * ones(10, 1)], "Distribution", "binomial"); %! assert_equal (mdl.Variables{:, 'y'}, S); %!test # a missing success count drops its observation %! x = (1:10)'; %! S = [0 1 NaN 2 3 4 6 7 9 9]'; %! mdl = fitglm (x, [S, 10 * ones(10, 1)], "Distribution", "binomial"); %! assert_equal (mdl.NumObservations, 9); %!test # the successes need not be whole numbers, as in MATLAB %! x = (1:10)'; %! S = [0 1 1 2 3 4 6 7 9 9]' + 0.5; %! mdl = fitglm (x, [S, 10 * ones(10, 1)], "Distribution", "binomial"); %! assert_equal (mdl.Coefficients.Estimate, ... %! [-3.55186660705826; 0.609271573628808], 1e-10); %!warning ... %! N = 5 * ones (16, 1); %! fitglm (X, [3 4 5 1 0 4 3 5 4 5 5 1 0 2 1 5]' ./ N, ... %! "Distribution", "binomial", "BinomialSize", N); %!error ... %! fitglm ((1:4)', [1 1 1 1]', "Distribution", "binomial", ... %! "BinomialSize", [2 2 2 2.5]') %!error ... %! fitglm ((1:4)', [1 1 1 1; 2 2 2 0]', "Distribution", "binomial") %!error ... %! fitglm ((1:4)', [1 1 1 5; 2 2 2 2]', "Distribution", "binomial") %!error ... %! fitglm ((1:4)', [1 1 1 1]', "Distribution", "binomial", ... %! "BinomialSize", [2 2]') %!error ... %! fitglm ((1:4)', [1 1 1 1; 2 2 2 2; 3 3 3 3]', "Distribution", "binomial") %!error ... %! fitglm ((1:4)', [1 1 1 1; 2 2 2 2]', "Distribution", "poisson") %!test # residuals gain the linear-predictor column, in MATLAB's order %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (mdl.Residuals.Properties.VariableNames, ... %! {'Raw', 'LinearPredictor', 'Pearson', 'Anscombe', 'Deviance'}); %! assert_equal (mdl.Residuals.LinearPredictor(1), 0.066685156329760, 1e-12); %! ## For the log link the working residual is the raw one over the mean. %! assert_equal (mdl.Residuals.LinearPredictor, ... %! mdl.Residuals.Raw ./ mdl.Fitted.Response, 1e-12); %!test # the identity link leaves the linear-predictor residual raw %! mdl = fitglm (X, yn); %! assert_equal (mdl.Residuals.LinearPredictor, mdl.Residuals.Raw, 1e-14); %! mdl = fitglm (X, yb, "Distribution", "binomial"); %! assert_equal (mdl.Residuals.LinearPredictor(1), 1.001891780864838, 1e-7); %!test # leverage, Cook's distance, and the hat matrix %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (mdl.Diagnostics.Properties.VariableNames, ... %! {'Leverage', 'CooksDistance', 'HatMatrix'}); %! assert_equal (mdl.Diagnostics.Leverage(1), 0.768034312650850, 1e-7); %! assert_equal (mdl.Diagnostics.CooksDistance(1), 0.074381550609974, 1e-7); %! assert_equal (size (mdl.Diagnostics.HatMatrix), [16, 16]); %! assert_equal (diag (mdl.Diagnostics.HatMatrix), mdl.Diagnostics.Leverage, ... %! 1e-14); %! assert_equal (sum (mdl.Diagnostics.Leverage), mdl.NumCoefficients, 1e-8); %!test # the GLM hat matrix is the asymmetric IRLS form, so H(i,j) != H(j,i) %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! H = mdl.Diagnostics.HatMatrix; %! assert_equal (H(1,2), 0.198729950284820, 1e-7); %! assert_equal (H(2,1), 0.334913252272241, 1e-7); %! ## The identity link with unit weights makes it symmetric again. %! H = fitglm (X, yn).Diagnostics.HatMatrix; %! assert_equal (H, H', 1e-12); %!test # normal-family diagnostics match MATLAB %! mdl = fitglm (X, yn); %! assert_equal (mdl.Diagnostics.Leverage(1:3), ... %! [0.392783227704423; 0.308688028422869; 0.103894702431155], 1e-12); %! assert_equal (mdl.Diagnostics.CooksDistance(1), 0.562165139840681, 1e-11); %!test # rows outside the fit have no leverage and no Cook's distance %! mdl = fitglm (X, yn, "Exclude", [2 5], "Weights", (1:16)' / 16); %! assert_equal (mdl.Diagnostics.Leverage([2, 5]), [0; 0]); %! assert_equal (isnan (mdl.Diagnostics.CooksDistance([2, 5])), [true; true]); %! assert_equal (mdl.Diagnostics.Leverage(1), 0.116937877466335, 1e-11); %! assert_equal (mdl.Diagnostics.CooksDistance(1), 0.026081406006346, 1e-10); %! assert_equal (mdl.Diagnostics.HatMatrix([2, 5], :), zeros (2, 16)); %!test # VariableInfo carries the class and range of every variable %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (mdl.VariableInfo.Properties.VariableNames, ... %! {'Class', 'Range', 'InModel', 'IsCategorical'}); %! assert_equal (size (mdl.VariableInfo), [4, 4]); %! assert_equal (mdl.VariableInfo.Range{1}, [-1.17, 2.09], 1e-14); %! assert_equal (mdl.VariableInfo.Range{2}, [-2.74, 1.33], 1e-14); %! assert_equal (mdl.VariableInfo.Range{4}, [0, 5]); %! assert_equal (mdl.VariableInfo.InModel, [true; true; true; false]); %! assert_equal (mdl.VariableInfo.IsCategorical, false (4, 1)); %! assert_equal (mdl.VariableInfo.Class, repmat ({'double'}, 4, 1)); %!test # a range spans the fitted rows, not the whole variable, as in MATLAB %! mdl = fitglm (X, yn, "Exclude", [1, 10]); %! assert_equal (mdl.VariableInfo.Range{1}, [-1.17, 2.04], 1e-14); %! assert_equal (mdl.VariableInfo.Range{4}, [-1.3, 2.5], 1e-14); %! ## the excluded rows carry the extremes that no longer appear %! assert_equal ([min(X(:,1)), max(X(:,1))], [-1.17, 2.09], 1e-14); %! assert_equal ([min(yn), max(yn)], [-1.3, 2.9], 1e-14); %!test # a category appearing only in excluded rows drops out of the range %! u = (1:20)'; %! g = categorical ([repmat({'a'}, 5, 1); repmat({'b'}, 5, 1); ... %! repmat({'c'}, 5, 1); repmat({'d'}, 5, 1)]); %! resp = u + 0.5; %! mdl = fitglm (table (u, g, resp), 'resp ~ u + g', 'Exclude', (16:20)'); %! assert_equal (cellstr (mdl.VariableInfo.Range{2}), {'a', 'b', 'c'}); %! assert_equal (mdl.VariableInfo.Range{1}, [1, 15], 1e-14); %!test # the offset spans the input rows and is zero when none was given %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (mdl.Offset, zeros (16, 1)); %! mdl = fitglm (X, yp, "Distribution", "poisson", ... %! "Offset", log (2 * ones (16, 1))); %! assert_equal (mdl.Offset, log (2) * ones (16, 1), 1e-14); %!test # the formula is a structure describing the model over its variables %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! f = mdl.Formula; %! assert_equal (f.ResponseName, "y"); %! assert_equal (f.LinearPredictor, "1 + x1 + x2 + x3"); %! assert_equal (f.PredictorNames, {'x1', 'x2', 'x3'}); %! assert_equal (f.VariableNames, {'x1', 'x2', 'x3', 'y'}); %! assert_equal (f.TermNames, {'(Intercept)'; 'x1'; 'x2'; 'x3'}); %! assert_equal (f.Terms, [0 0 0 0; 1 0 0 0; 0 1 0 0; 0 0 1 0]); %! assert_equal (f.Link, "log"); %! assert_equal (f.InModel, [true, true, true, false]); %! assert_equal (f.HasIntercept, true); %! assert_equal ([f.NTerms, f.NVars, f.NPredictors], [4, 4, 3]); %! assert_equal (f.FunctionCalls, cell (1, 0)); %! assert_equal (f.ModelFun ([1; 2], [1, 3]), 7); %!test # a term whose parts are all present is written as a product %! f = fitglm (X, yn, "interactions").Formula; %! assert_equal (f.LinearPredictor, "1 + x1*x2 + x1*x3 + x2*x3"); %! assert_equal (f.NTerms, 7); %! assert_equal (f.TermNames, ... %! {'(Intercept)'; 'x1'; 'x2'; 'x3'; 'x1:x2'; 'x1:x3'; 'x2:x3'}); %! f = fitglm (X(:,1:2), yn, "quadratic").Formula; %! assert_equal (f.LinearPredictor, "1 + x1*x2 + x1^2 + x2^2"); %! assert_equal (f.TermNames, ... %! {'(Intercept)'; 'x1'; 'x2'; 'x1:x2'; 'x1^2'; 'x2^2'}); %! assert_equal (f.Terms, [0 0 0; 1 0 0; 0 1 0; 1 1 0; 2 0 0; 0 2 0]); %!test # only pairs collapse into a product, never a three-way interaction %! f = fitglm (X, yn, "full").Formula; %! assert_equal (f.LinearPredictor, "1 + x1*x2 + x1*x3 + x2*x3 + x1:x2:x3"); %!test # dropping the intercept drops the leading 1 %! f = fitglm (X, yn, "Intercept", false).Formula; %! assert_equal (f.LinearPredictor, "x1 + x2 + x3"); %! assert_equal (f.HasIntercept, false); %! assert_equal (f.Terms, [1 0 0 0; 0 1 0 0; 0 0 1 0]); %!test # disp names the link alongside the response %! s = evalc ("disp (fitglm (X, yp, 'Distribution', 'poisson'))"); %! assert_equal (isempty (strfind (s, "log(y) ~ 1 + x1 + x2 + x3")), false); %! s = evalc ("disp (fitglm (X, yb, 'Distribution', 'binomial'))"); %! assert_equal (isempty (strfind (s, "logit(y) ~ 1 + x1 + x2 + x3")), false); %! s = evalc ("disp (fitglm (X, yn))"); %! assert_equal (isempty (strfind (s, "y ~ 1 + x1 + x2 + x3")), false); %!test # a numeric link is named by its exponent and shown as a power %! mdl = fitglm (X, abs (yn) + 1, "Distribution", "inverse gaussian"); %! assert_equal (mdl.Link.Name, "-2"); %! s = evalc ("disp (mdl)"); %! assert_equal (isempty (strfind (s, "power(y,-2) ~ 1 + x1")), false); %!test # a table model keeps every column but only models what it names %! tbl = array2table ([X, yn], "VariableNames", {'a', 'b', 'c', 'resp'}); %! mdl = fitglm (tbl, "resp ~ 1 + a + b"); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.NumVariables, 4); %! assert_equal (mdl.PredictorNames, {'a'; 'b'}); %! assert_equal (mdl.VariableNames, {'a'; 'b'; 'c'; 'resp'}); %! assert_equal (mdl.Formula.LinearPredictor, "1 + a + b"); %! assert_equal (mdl.Formula.InModel, [true, true, false, false]); %! assert_equal (mdl.VariableInfo.InModel, [true; true; false; false]); %! assert_equal (size (mdl.Variables), [16, 4]); %!test # a column the model does not use cannot cost an observation %! tbl = array2table ([X, yn], "VariableNames", {'a', 'b', 'c', 'resp'}); %! tbl.c(4) = NaN; %! mdl = fitglm (tbl, "resp ~ 1 + a + b"); %! assert_equal (mdl.NumObservations, 16); %! assert_equal (any (mdl.ObservationInfo.Missing), false); %!test # a grouping column is reported by its own class and its levels %! g = {'lo'; 'hi'; 'lo'; 'hi'; 'lo'; 'hi'; 'lo'; 'hi'; ... %! 'lo'; 'hi'; 'lo'; 'hi'; 'lo'; 'hi'; 'lo'; 'hi'}; %! tbl = table (X(:,1), g, yn, "VariableNames", {'u', 'grp', 'resp'}); %! mdl = fitglm (tbl, "resp ~ 1 + u + grp"); %! assert_equal (mdl.VariableInfo.Class, {'double'; 'cell'; 'double'}); %! assert_equal (mdl.VariableInfo.IsCategorical, [false; true; false]); %! ## The levels are listed as the design codes them: first seen first. %! assert_equal (mdl.VariableInfo.Range{2}, {'lo', 'hi'}); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.NumVariables, 3); %! ## The indicator columns fold back onto the variable they came from. %! assert_equal (mdl.Formula.NTerms, 3); %! assert_equal (sort (mdl.Formula.TermNames), {'(Intercept)'; 'grp'; 'u'}); %!test # devianceTest labels both rows with their rendered formula %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! dt = devianceTest (mdl); %! assert_equal (dt.Properties.RowNames, ... %! {'log(y) ~ 1'; 'log(y) ~ 1 + x1 + x2 + x3'}); %! mdl = fitglm (X, yn); %! dt = devianceTest (mdl); %! assert_equal (dt.Properties.RowNames, ... %! {'y ~ 1'; 'y ~ 1 + x1 + x2 + x3'}); %!test # a power keeps its exponent in the variable's own column %! u = [1;2;3;4;5;6;7;8;9;10;11;12]; %! v = [2;1;4;3;6;5;8;7;10;9;12;11]; %! cnt = [2;5;3;7;4;6;8;3;5;9;4;6]; %! tbl = table (u, v, cnt); %! f = fitglm (tbl, 'cnt ~ 1 + u*v + u^2', 'Distribution', 'poisson').Formula; %! assert_equal (char (f), 'log(cnt) ~ 1 + u*v + u^2'); %! assert_equal (f.Terms, [0 0 0; 1 0 0; 0 1 0; 1 1 0; 2 0 0]); %! assert_equal (f.TermNames, {'(Intercept)'; 'u'; 'v'; 'u:v'; 'u^2'}); %!test # an interaction survives when one factor is not a main effect %! u = [1;2;3;4;5;6;7;8;9;10;11;12]; %! v = [2;1;4;3;6;5;8;7;10;9;12;11]; %! cnt = [2;5;3;7;4;6;8;3;5;9;4;6]; %! tbl = table (u, v, cnt); %! f = fitglm (tbl, 'cnt ~ 1 + u + u:v', 'Distribution', 'poisson').Formula; %! assert_equal (char (f), 'log(cnt) ~ 1 + u + u:v'); %! assert_equal (f.Terms, [0 0 0; 1 0 0; 1 1 0]); %!test # dropping the intercept codes the first categorical in full, both paths %! u = [1;2;3;4;5;6;7;8;9;10;11;12]; %! h2 = {'b';'c';'a';'b';'c';'a';'b';'c';'a';'b';'c';'a'}; %! cnt = [2;5;3;7;4;6;8;3;5;9;4;6]; %! tbl = table (u, h2, cnt); %! m = fitglm (tbl, 'cnt ~ h2 - 1', 'Distribution', 'poisson'); %! assert_equal (m.CoefficientNames, {'h2_b', 'h2_c', 'h2_a'}); %! m = fitglm (tbl, 'linear', 'Distribution', 'poisson', 'Intercept', false); %! assert_equal (m.CoefficientNames, {'u', 'h2_b', 'h2_c', 'h2_a'}); statistics-release-1.9.2/inst/Regression/LinearFormula.m000066400000000000000000000557421524624707500233770ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftp {statistics} LinearFormula ## ## Model formula of a linear or generalized linear regression. ## ## A @qcode{LinearFormula} object describes the terms of a fitted model: which ## variables the model draws on, how they combine into terms, and how the whole ## thing reads back as a formula. It is the class of the @code{Formula} ## property of a @code{LinearModel} and of a @code{GeneralizedLinearModel}, and ## is normally obtained from a fitted model rather than built directly. ## ## The object is defined by its terms matrix and the names of the variables that ## matrix is written over; every other property is derived from those two. Each ## row of @code{Terms} is one term of the model and each column is one variable, ## the entry giving the power that variable carries in that term. An all-zero ## row is the intercept. The response variable occupies a column of its own, ## which is always zero. ## ## Converting the object with @code{char} renders the whole formula, response ## included, as @qcode{"y ~ 1 + x1 + x2"}; the @code{LinearPredictor} property ## holds the right-hand side on its own. For a generalized linear model the ## response carries its link function, as in @qcode{"logit(y) ~ 1 + x1"}. ## ## @end deftp classdef LinearFormula properties (SetAccess = private) ## -*- texinfo -*- ## @deftp {LinearFormula} {property} ResponseName ## ## Name of the response variable ## ## A character vector naming the variable on the left-hand side of the ## formula. This property is read-only. ## ## @end deftp ResponseName = ''; ## -*- texinfo -*- ## @deftp {LinearFormula} {property} VariableNames ## ## Names of all variables available to the model ## ## A cell array of character vectors naming every variable the model was ## given, whether or not it is used, in the order the data lists them. The ## response is included. This property is read-only. ## ## @end deftp VariableNames = {}; ## -*- texinfo -*- ## @deftp {LinearFormula} {property} PredictorNames ## ## Names of the variables the model actually uses ## ## A cell array of character vectors holding those elements of ## @code{VariableNames} that appear in at least one term. This property is ## read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {LinearFormula} {property} TermNames ## ## Name of each term of the model ## ## A column cell array of character vectors, one per row of @code{Terms}, ## naming the term over the model's @emph{variables}: the intercept is ## @qcode{"(Intercept)"}, an interaction joins its factors with a colon, and ## a power is written with a caret. A categorical variable contributes one ## term under its own name however many indicator columns it expands to, so ## these are not the coefficient names. This property is read-only. ## ## @end deftp TermNames = {}; ## -*- texinfo -*- ## @deftp {LinearFormula} {property} Terms ## ## Terms matrix of the model ## ## A numeric matrix with one row per term and one column per variable of ## @code{VariableNames}, each entry giving the power that variable carries ## in that term. An all-zero row is the intercept. This property is ## read-only. ## ## @end deftp Terms = []; ## -*- texinfo -*- ## @deftp {LinearFormula} {property} InModel ## ## Which variables take part in the model ## ## A logical row vector with one element per variable of ## @code{VariableNames}, true where that variable appears in at least one ## term. The response is false. This property is read-only. ## ## @end deftp InModel = logical ([]); ## -*- texinfo -*- ## @deftp {LinearFormula} {property} HasIntercept ## ## Whether the model carries an intercept ## ## A logical scalar, true when @code{Terms} holds an all-zero row. This ## property is read-only. ## ## @end deftp HasIntercept = false; ## -*- texinfo -*- ## @deftp {LinearFormula} {property} LinearPredictor ## ## Right-hand side of the formula ## ## A character vector rendering the model's terms, such as ## @qcode{"1 + x1 + x2"}. A pair of variables appearing both on their own ## and as an interaction is written as a product, so that ## @qcode{"x1 + x2 + x1:x2"} reads @qcode{"x1*x2"}. This property is ## read-only. ## ## @end deftp LinearPredictor = ''; ## -*- texinfo -*- ## @deftp {LinearFormula} {property} Link ## ## Link function applied to the response ## ## The link of a generalized linear model, given as its name, its numeric ## exponent, or a structure of function handles. It is @qcode{"identity"} ## for a linear model. This property is read-only. ## ## @end deftp Link = 'identity'; ## -*- texinfo -*- ## @deftp {LinearFormula} {property} ModelFun ## ## Function computing the linear predictor ## ## A function handle taking the coefficient vector and the design matrix. ## This property is read-only. ## ## @end deftp ModelFun = @(b, X) X * b; ## -*- texinfo -*- ## @deftp {LinearFormula} {property} FunctionCalls ## ## Functions called from within the formula ## ## A cell array of character vectors, empty unless the formula applies a ## function to a variable. This property is read-only. ## ## @end deftp FunctionCalls = cell (1, 0); ## -*- texinfo -*- ## @deftp {LinearFormula} {property} NTerms ## ## Number of terms in the model ## ## A non-negative integer, the number of rows of @code{Terms}. This ## property is read-only. ## ## @end deftp NTerms = 0; ## -*- texinfo -*- ## @deftp {LinearFormula} {property} NVars ## ## Number of variables available to the model ## ## A non-negative integer, the number of elements of ## @code{VariableNames}. This property is read-only. ## ## @end deftp NVars = 0; ## -*- texinfo -*- ## @deftp {LinearFormula} {property} NPredictors ## ## Number of variables the model uses ## ## A non-negative integer, the number of true elements of @code{InModel}. ## This property is read-only. ## ## @end deftp NPredictors = 0; endproperties methods (Hidden) function disp (this) fprintf ("%s\n", char (this)); endfunction function display (this) name = inputname (1); if (isempty (name)) name = 'ans'; endif fprintf ("%s = %s\n", name, char (this)); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {LinearFormula} {@var{obj} =} LinearFormula () ## @deftypefnx {LinearFormula} {@var{obj} =} LinearFormula (@var{terms}, @var{varnames}) ## @deftypefnx {LinearFormula} {@var{obj} =} LinearFormula (@var{terms}, @var{varnames}, @var{name}, @var{value}, @dots{}) ## ## Create a model formula from a terms matrix. ## ## @code{@var{obj} = LinearFormula ()} returns an empty formula. ## ## @code{@var{obj} = LinearFormula (@var{terms}, @var{varnames})} builds a ## formula whose terms matrix is @var{terms} and whose variables are named ## by the cell array of character vectors @var{varnames}. @var{terms} must ## have one column per element of @var{varnames}; each row is one term and ## each entry the power its variable carries in that term. An all-zero row ## is the intercept. ## ## The remaining properties are derived from these two arguments, except ## those given as @var{name}-@var{value} pairs: ## ## @multitable @columnfractions 0.25 0.75 ## @headitem @var{name} @tab @var{value} ## ## @item @qcode{"ResponseName"} @tab A character vector naming the response ## variable. It must be one of @var{varnames}. ## ## @item @qcode{"Link"} @tab The link function applied to the response, as a ## name, a numeric exponent, or a structure of function handles. It ## defaults to @qcode{"identity"}. ## ## @item @qcode{"ModelFun"} @tab A function handle computing the linear ## predictor from the coefficients and the design matrix. ## ## @item @qcode{"FunctionCalls"} @tab A cell array of character vectors ## naming functions the formula applies to its variables. ## @end multitable ## ## @end deftypefn function this = LinearFormula (varargin) if (nargin == 0) return; endif if (nargin < 2) error ("LinearFormula: TERMS and VARNAMES are both required."); endif terms = varargin{1}; varnames = varargin{2}; if (! isnumeric (terms) || ! isreal (terms) || ndims (terms) > 2) error ("LinearFormula: TERMS must be a real matrix."); endif if (! iscellstr (varnames)) error (strcat ("LinearFormula: VARNAMES must be a cell array of", ... " character vectors.")); endif if (! isempty (terms) && columns (terms) != numel (varnames)) error (strcat ("LinearFormula: TERMS must have one column per", ... " element of VARNAMES.")); endif if (mod (nargin - 2, 2) != 0) error ("LinearFormula: optional arguments must be name-value pairs."); endif respname = ''; for i = 3:2:nargin name = varargin{i}; if (! (ischar (name) && isrow (name))) error ("LinearFormula: option names must be character vectors."); endif switch (lower (name)) case 'responsename' respname = varargin{i+1}; if (! (ischar (respname) && (isrow (respname) ... || isempty (respname)))) error (strcat ("LinearFormula: 'ResponseName' must be a", ... " character vector.")); endif case 'link' this.Link = varargin{i+1}; case 'modelfun' this.ModelFun = varargin{i+1}; case 'functioncalls' this.FunctionCalls = varargin{i+1}; otherwise error ("LinearFormula: unknown option '%s'.", name); endswitch endfor varnames = varnames(:)'; if (! isempty (respname) && ! any (strcmp (varnames, respname))) error ("LinearFormula: '%s' is not one of VARNAMES.", respname); endif this.ResponseName = respname; this.VariableNames = varnames; this.Terms = terms; this.NVars = numel (varnames); this.NTerms = rows (terms); if (isempty (terms)) this.InModel = false (1, this.NVars); else this.InModel = any (terms != 0, 1); endif this.PredictorNames = varnames(this.InModel); this.NPredictors = sum (this.InModel); this.HasIntercept = ! isempty (terms) && any (all (terms == 0, 2)); this.TermNames = term_names (terms, varnames); this.LinearPredictor = predictor_string (terms, varnames, ... this.TermNames); endfunction ## -*- texinfo -*- ## @deftypefn {LinearFormula} {@var{str} =} char (@var{obj}) ## ## Render a model formula as a character vector. ## ## @code{@var{str} = char (@var{obj})} returns the whole formula, response ## included, as in @qcode{"y ~ 1 + x1 + x2"}. The response carries the ## link function of a generalized linear model, as in ## @qcode{"logit(y) ~ 1 + x1"}. ## ## @end deftypefn function str = char (this) if (isempty (this.LinearPredictor) && isempty (this.ResponseName)) str = ''; return; endif str = sprintf ("%s ~ %s", linked_response (this.ResponseName, ... this.Link), ... this.LinearPredictor); endfunction ## -*- texinfo -*- ## @deftypefn {LinearFormula} {@var{str} =} string (@var{obj}) ## ## Render a model formula as a string scalar. ## ## @code{@var{str} = string (@var{obj})} is the @code{string} counterpart of ## @code{char}. ## ## @end deftypefn function str = string (this) str = string (char (this)); endfunction endmethods endclassdef ## Name each row of a terms matrix over the model's variables. function names = term_names (terms, varnames) n_terms = rows (terms); names = cell (n_terms, 1); for t = 1:n_terms idx = find (terms(t, :) != 0); if (isempty (idx)) names{t} = '(Intercept)'; else parts = cell (1, numel (idx)); for k = 1:numel (idx) parts{k} = factor_string (varnames{idx(k)}, terms(t, idx(k))); endfor names{t} = strjoin (parts, ':'); endif endfor endfunction ## One factor of a term, carrying its power when that is not 1. function str = factor_string (name, power) if (power == 1) str = name; else str = sprintf ("%s^%d", name, power); endif endfunction ## Render the right-hand side of a formula. Two variables that appear both on ## their own and as their interaction are written as a product -- 'x1 + x2 + ## x1:x2' becomes 'x1*x2' -- and the main effects it absorbs are then left out. ## Only pairs collapse: a three-way interaction is written out as it stands even ## when every one of its sub-terms is present, and a power never collapses. function str = predictor_string (terms, varnames, names) n_terms = rows (terms); absorbed = false (n_terms, 1); rendered = names; for t = 1:n_terms idx = find (terms(t, :) != 0); if (numel (idx) != 2 || any (terms(t, idx) != 1)) continue; endif main = zeros (1, 2); for k = 1:2 row = zeros (1, columns (terms)); row(idx(k)) = 1; hit = find (all (terms == row, 2), 1); if (isempty (hit)) main = []; break; endif main(k) = hit; endfor if (isempty (main)) continue; endif rendered{t} = strjoin (varnames(idx), '*'); absorbed(main) = true; endfor parts = {}; for t = 1:n_terms if (absorbed(t)) continue; elseif (all (terms(t, :) == 0)) parts{end+1} = '1'; else parts{end+1} = rendered{t}; endif endfor if (isempty (parts)) str = ''; else str = strjoin (parts, ' + '); endif endfunction ## The response as the formula shows it, wrapped in its link function. function str = linked_response (respname, link) if (isempty (link) || (ischar (link) && strcmpi (link, 'identity'))) str = respname; elseif (ischar (link)) str = sprintf ("%s(%s)", link, respname); elseif (isnumeric (link) && isscalar (link)) ## The identity link written as an exponent leaves the response alone. if (link == 1) str = respname; else str = sprintf ("power(%s,%g)", respname, link); endif else str = sprintf ("link(%s)", respname); endif endfunction %!demo %! ## The Formula property of a fitted model is a LinearFormula object. %! x1 = [1 2 3 4 5 6 7 8]'; %! x2 = [2 1 4 3 6 5 8 7]'; %! y = [3.1 4.2 5.3 6.4 7.5 8.6 9.7 10.8]'; %! mdl = fitlm ([x1, x2], y, 'interactions'); %! f = mdl.Formula %! ## Rendering it as text gives back the whole formula, response included, %! ## while the LinearPredictor property holds the right-hand side alone. %! char (f) %! f.LinearPredictor %! ## A pair of variables present both on their own and as their interaction %! ## is written as a product. %! f.TermNames %! f.Terms %!demo %! ## A formula can also be built directly from a terms matrix. Each column is %! ## a variable and each entry the power it carries in that term; the all-zero %! ## row is the intercept and the response keeps a column of its own. %! terms = [0 0 0; 1 0 0; 0 1 0; 1 1 0]; %! f = LinearFormula (terms, {'dose', 'age', 'score'}, ... %! 'ResponseName', 'score') %! f.PredictorNames %! f.InModel ## Properties derived from the terms matrix %!test %! f = LinearFormula ([0 0 0; 1 0 0; 0 1 0], {'u', 'g', 'resp'}, ... %! 'ResponseName', 'resp'); %! assert_equal (f.ResponseName, 'resp'); %! assert_equal (f.VariableNames, {'u', 'g', 'resp'}); %! assert_equal (f.PredictorNames, {'u', 'g'}); %! assert_equal (f.TermNames, {'(Intercept)'; 'u'; 'g'}); %! assert_equal (f.Terms, [0 0 0; 1 0 0; 0 1 0]); %! assert_equal (f.InModel, [true, true, false]); %! assert_equal (f.HasIntercept, true); %! assert_equal (f.LinearPredictor, '1 + u + g'); %! assert_equal (f.NTerms, 3); %! assert_equal (f.NVars, 3); %! assert_equal (f.NPredictors, 2); %! assert_equal (f.Link, 'identity'); %! assert_equal (f.FunctionCalls, cell (1, 0)); %!test # the response takes no part in the model %! f = LinearFormula ([0 0 0 0; 1 0 0 0; 0 0 1 0], {'u', 'v', 'w', 'resp'}, ... %! 'ResponseName', 'resp'); %! assert_equal (f.InModel, [true, false, true, false]); %! assert_equal (f.PredictorNames, {'u', 'w'}); %! assert_equal (f.NPredictors, 2); %! assert_equal (f.NVars, 4); %!test # an empty formula is renderable %! f = LinearFormula (); %! assert_equal (char (f), ''); %! assert_equal (f.NTerms, 0); %! assert_equal (f.HasIntercept, false); ## Rendering %!test # char gives the whole formula, LinearPredictor the right-hand side %! f = LinearFormula ([0 0 0; 1 0 0; 0 1 0], {'u', 'g', 'resp'}, ... %! 'ResponseName', 'resp'); %! assert_equal (char (f), 'resp ~ 1 + u + g'); %! assert_equal (f.LinearPredictor, '1 + u + g'); %!test # a pair present on its own and as an interaction reads as a product %! f = LinearFormula ([0 0 0; 1 0 0; 0 1 0; 1 1 0], {'u', 'v', 'resp'}, ... %! 'ResponseName', 'resp'); %! assert_equal (char (f), 'resp ~ 1 + u*v'); %!test # a product needs both main effects; without one the term stands alone %! f = LinearFormula ([0 0 0; 1 0 0; 1 1 0], {'u', 'v', 'resp'}, ... %! 'ResponseName', 'resp'); %! assert_equal (char (f), 'resp ~ 1 + u + u:v'); %!test # only pairs collapse, never a three-way interaction %! terms = [0 0 0 0; 1 0 0 0; 0 1 0 0; 0 0 1 0; ... %! 1 1 0 0; 1 0 1 0; 0 1 1 0; 1 1 1 0]; %! f = LinearFormula (terms, {'u', 'v', 'w', 'resp'}, ... %! 'ResponseName', 'resp'); %! assert_equal (char (f), 'resp ~ 1 + u*v + u*w + v*w + u:v:w'); %!test # a power is never written as a product %! f = LinearFormula ([0 0 0; 1 0 0; 0 1 0; 1 1 0; 2 0 0; 0 2 0], ... %! {'u', 'v', 'resp'}, 'ResponseName', 'resp'); %! assert_equal (char (f), 'resp ~ 1 + u*v + u^2 + v^2'); %! assert_equal (f.TermNames, ... %! {'(Intercept)'; 'u'; 'v'; 'u:v'; 'u^2'; 'v^2'}); %!test # dropping the intercept drops the leading 1 %! f = LinearFormula ([1 0 0; 0 1 0], {'u', 'g', 'resp'}, ... %! 'ResponseName', 'resp'); %! assert_equal (char (f), 'resp ~ u + g'); %! assert_equal (f.HasIntercept, false); %!test # an intercept-only model %! f = LinearFormula ([0 0 0], {'u', 'g', 'resp'}, 'ResponseName', 'resp'); %! assert_equal (char (f), 'resp ~ 1'); %! assert_equal (f.TermNames, {'(Intercept)'}); %! assert_equal (f.NPredictors, 0); %! assert_equal (f.PredictorNames, cell (1, 0)); %!test # string renders the same text as char %! f = LinearFormula ([0 0; 1 0], {'u', 'resp'}, 'ResponseName', 'resp'); %! s = string (f); %! assert_equal (class (s), 'string'); %! assert_equal (char (s), char (f)); %!test # disp writes the rendered formula %! f = LinearFormula ([0 0; 1 0], {'u', 'resp'}, 'ResponseName', 'resp'); %! s = evalc ('disp (f)'); %! assert_equal (strtrim (s), 'resp ~ 1 + u'); ## The link wraps the response %!test # a named link %! f = LinearFormula ([0 0 0; 1 0 0; 0 1 0], {'u', 'g', 'yb'}, ... %! 'ResponseName', 'yb', 'Link', 'logit'); %! assert_equal (char (f), 'logit(yb) ~ 1 + u + g'); %! assert_equal (f.Link, 'logit'); %!test # the identity link leaves the response alone, named or as an exponent %! f = LinearFormula ([0 0; 1 0], {'u', 'y'}, 'ResponseName', 'y', ... %! 'Link', 'identity'); %! assert_equal (char (f), 'y ~ 1 + u'); %! f = LinearFormula ([0 0; 1 0], {'u', 'y'}, 'ResponseName', 'y', 'Link', 1); %! assert_equal (char (f), 'y ~ 1 + u'); %!test # a numeric link is shown as a power %! f = LinearFormula ([0 0; 1 0], {'u', 'y'}, 'ResponseName', 'y', 'Link', -2); %! assert_equal (char (f), 'power(y,-2) ~ 1 + u'); %!test # a link given as a structure of handles is named generically %! lk = struct ('Link', @(x) x, 'Derivative', @(x) 1, 'Inverse', @(x) x); %! f = LinearFormula ([0 0; 1 0], {'u', 'y'}, 'ResponseName', 'y', ... %! 'Link', lk); %! assert_equal (char (f), 'link(y) ~ 1 + u'); ## Remaining options %!test # ModelFun and FunctionCalls are carried through %! fun = @(b, X) X * b; %! f = LinearFormula ([0 0; 1 0], {'u', 'y'}, 'ResponseName', 'y', ... %! 'ModelFun', fun, 'FunctionCalls', {'log'}); %! assert_equal (func2str (f.ModelFun), func2str (fun)); %! assert_equal (f.FunctionCalls, {'log'}); %!test # option names are matched without regard to case %! f = LinearFormula ([0 0; 1 0], {'u', 'y'}, 'responsename', 'y', ... %! 'LINK', 'log'); %! assert_equal (char (f), 'log(y) ~ 1 + u'); ## Input validation %!error ... %! LinearFormula ([0 0]) %!error ... %! LinearFormula ('abc', {'u', 'y'}) %!error ... %! LinearFormula ([1+2i, 0], {'u', 'y'}) %!error ... %! LinearFormula ([0 0], 'uy') %!error ... %! LinearFormula ([0 0], {1, 2}) %!error ... %! LinearFormula ([0 0 0], {'u', 'y'}) %!error ... %! LinearFormula ([0 0], {'u', 'y'}, 'Link') %!error ... %! LinearFormula ([0 0], {'u', 'y'}, 5, 'log') %!error ... %! LinearFormula ([0 0], {'u', 'y'}, 'bogus', 1) %!error ... %! LinearFormula ([0 0], {'u', 'y'}, 'ResponseName', 5) %!error ... %! LinearFormula ([0 0], {'u', 'y'}, 'ResponseName', 'z') statistics-release-1.9.2/inst/Regression/LinearMixedModel.m000066400000000000000000000747341524624707500240230ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} LinearMixedModel ## ## Linear mixed-effects model fitted to data. ## ## A @code{LinearMixedModel} object represents a fitted linear mixed-effects ## model ## @tex ## $$ y = X\beta + Zb + \varepsilon, $$ ## ## @end tex ## @ifnottex ## @code{y = X*beta + Z*b + e}, ## @end ifnottex ## with fixed effects @var{beta}, random effects @var{b} distributed as ## @code{N(0, Psi)}, and independent errors @code{N(0, sigma2)}. Objects are ## created with @code{fitlmematrix} (from design matrices). ## ## The estimated fixed effects and their statistics are available through the ## @code{Coefficients} table; the covariance parameters through ## @code{covarianceParameters}; the random-effect BLUPs through ## @code{randomEffects}; and predictions, residuals, and hypothesis tests ## through the @code{predict}, @code{residuals}, @code{anova}, @code{coefTest}, ## and @code{coefCI} methods. ## ## @seealso{fitlmematrix, fitlm} ## @end deftypefn classdef LinearMixedModel properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} FitMethod ## ## Estimation method ## ## A character vector, either @qcode{'ML'} for maximum likelihood or ## @qcode{'REML'} for restricted maximum likelihood, naming the method ## that fitted the model. This property is read-only. ## ## @end deftp FitMethod = ""; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} NumObservations ## ## Number of observations ## ## A positive integer counting the observations used for the fit. This ## property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} NumCoefficients ## ## Number of fixed-effects coefficients ## ## A positive integer counting the fixed-effects coefficients of the ## model. This property is read-only. ## ## @end deftp NumCoefficients = []; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} NumEstimatedCoefficients ## ## Number of estimated fixed-effects coefficients ## ## A positive integer counting the fixed-effects coefficients estimated ## from the data. Every coefficient the model carries is estimated, so ## this equals @qcode{NumCoefficients}. This property is read-only. ## ## @end deftp NumEstimatedCoefficients = []; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} Coefficients ## ## Fixed-effects estimates and their statistics ## ## A table with one row per fixed-effects coefficient, its row names ## taken from @qcode{CoefficientNames}, and the variables ## @qcode{Estimate}, @qcode{SE}, @qcode{tStat}, @qcode{DF}, ## @qcode{pValue}, @qcode{Lower} and @qcode{Upper}. @qcode{Lower} and ## @qcode{Upper} bound a 95% confidence interval. This property is ## read-only. ## ## @end deftp Coefficients = []; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} CoefficientCovariance ## ## Covariance of the fixed-effects estimates ## ## A square numeric matrix, one row and column per fixed-effects ## coefficient, holding the estimated covariance of the estimates in ## @qcode{Coefficients}. This property is read-only. ## ## @end deftp CoefficientCovariance = []; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} CoefficientNames ## ## Names of the fixed-effects coefficients ## ## A cell array of character vectors with one name per fixed-effects ## coefficient. This property is read-only. ## ## @end deftp CoefficientNames = {}; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} LogLikelihood ## ## Log-likelihood of the fitted model ## ## A scalar, the maximised log-likelihood, or the maximised restricted ## log-likelihood when @qcode{FitMethod} is @qcode{'REML'}. This ## property is read-only. ## ## @end deftp LogLikelihood = []; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} ModelCriterion ## ## Information criteria ## ## A scalar structure with the fields @qcode{AIC}, @qcode{BIC}, ## @qcode{LogLikelihood} and @qcode{Deviance}. The parameter count ## behind @qcode{AIC} and @qcode{BIC} holds the fixed-effects ## coefficients, the covariance parameters and the residual variance. ## This property is read-only. ## ## @end deftp ModelCriterion = []; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} Rsquared ## ## Coefficient of determination ## ## A scalar structure with the fields @qcode{Ordinary} and ## @qcode{Adjusted}. @qcode{Ordinary} is one less the ratio of ## @qcode{SSE} to @qcode{SST}, and @qcode{Adjusted} corrects that ratio ## for the error degrees of freedom. This property is read-only. ## ## @end deftp Rsquared = []; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} SSE ## ## Error sum of squares ## ## A nonnegative scalar, the sum of the squared differences between the ## response and the conditional fit. This property is read-only. ## ## @end deftp SSE = []; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} SSR ## ## Regression sum of squares ## ## A nonnegative scalar, the sum of the squared deviations of the ## conditional fit about the mean of the response. This property is ## read-only. ## ## @end deftp SSR = []; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} SST ## ## Total sum of squares ## ## A nonnegative scalar, @qcode{SSE} plus @qcode{SSR}. This property is ## read-only. ## ## @end deftp SST = []; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} MSE ## ## Residual variance estimate ## ## A positive scalar, the estimate of the error variance. This property ## is read-only. ## ## @end deftp MSE = []; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} DFE ## ## Residual degrees of freedom ## ## A nonnegative integer, @qcode{NumObservations} less ## @qcode{NumCoefficients}. This property is read-only. ## ## @end deftp DFE = []; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} Formula ## ## Model formula ## ## A character vector describing the model. It is empty for a fit built ## from design matrices, which carries no formula. This property is ## read-only. ## ## @end deftp Formula = ""; ## -*- texinfo -*- ## @deftp {LinearMixedModel} {property} ResponseName ## ## Name of the response variable ## ## A character vector naming the response. It is empty for a fit built ## from design matrices. This property is read-only. ## ## @end deftp ResponseName = ""; endproperties properties (Access = private, Hidden) X_ = []; y_ = []; Zcell_ = {}; Gcell_ = {}; Zx_ = []; qk_ = []; nlev_ = []; levels_ = {}; gidx_ = {}; beta_ = []; covbeta_ = []; Psi_ = {}; sigma2_ = []; b_ = []; theta_ = []; GroupNames_ = {}; REPred_ = {}; fitted_ = []; fitted_marg_ = []; resid_ = []; endproperties methods (Hidden) function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ("%s =\n", in_name); endif disp (this); endfunction function disp (this) fprintf ("\n Linear mixed-effects model fit by %s\n", this.FitMethod); if (! isempty (this.Formula)) fprintf ("\n Formula:\n %s\n", this.Formula); endif if (! isempty (this.Coefficients)) fprintf ("\n Fixed effects coefficients:\n\n"); disp (this.Coefficients); endif if (! isempty (this.Psi_)) fprintf ("\n Random effects covariance parameters:\n"); for k = 1:numel (this.Psi_) fprintf (" Group: %s (%d levels)\n", this.GroupNames_{k}, ... this.nlev_(k)); disp (this.Psi_{k}); endfor fprintf (" Residual variance (sigma2): %g\n", this.sigma2_); endif fprintf ("\n"); if (! isempty (this.NumObservations)) fprintf ("Number of observations: %d, Error DF: %d\n", ... this.NumObservations, this.DFE); endif if (! isempty (this.LogLikelihood)) fprintf ("Log-likelihood: %g\n", this.LogLikelihood); endif if (! isempty (this.Rsquared) && isstruct (this.Rsquared)) fprintf ("R-squared: %g, Adjusted R-Squared: %g\n", ... this.Rsquared.Ordinary, this.Rsquared.Adjusted); endif endfunction function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case "()" error (strcat ("LinearMixedModel: () indexing is not supported.", ... " Use dot notation to access properties.")); case "{}" error (strcat ("LinearMixedModel: {} indexing is not supported.", ... " Use dot notation to access properties.")); case "." if (! ischar (s.subs)) error ("LinearMixedModel.subsref: property name must be a string."); endif ## Method calls (with or without arguments) go straight to builtin. if (ismethod (this, s.subs)) [varargout{1:nargout}] = builtin ("subsref", this, [s, chain_s]); return; endif try out = this.(s.subs); catch error ("LinearMixedModel.subsref: unknown property '%s'.", s.subs); end_try_catch endswitch ## Delegate any further indexing to the property's own subsref ## (e.g. lme.Coefficients.Estimate on the returned table). if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {LinearMixedModel} {@var{lme} =} LinearMixedModel (@var{info}) ## ## Construct a @code{LinearMixedModel} from a fitted-model info struct. ## This constructor is used internally by @code{fitlmematrix}; call that ## function rather than the constructor directly. ## ## @end deftypefn function this = LinearMixedModel (info) if (nargin == 0) return; endif n = info.n; p = numel (info.beta); this.X_ = info.X; this.y_ = info.y; this.Zcell_ = info.Zcell; this.Gcell_ = info.Gcell; this.Zx_ = info.Zx; this.qk_ = info.qk; this.nlev_ = info.nlev; this.levels_ = info.levels; this.gidx_ = info.gidx; this.beta_ = info.beta; this.covbeta_ = info.covbeta; this.Psi_ = info.Psi; this.sigma2_ = info.sigma2; this.b_ = info.b; this.theta_ = info.theta; this.GroupNames_ = info.GroupNames; this.REPred_ = info.REPred; this.fitted_ = info.fitted; this.fitted_marg_ = info.fitted_marg; this.resid_ = info.resid; if (isfield (info, "Formula")), this.Formula = info.Formula; endif if (isfield (info, "ResponseName")) this.ResponseName = info.ResponseName; endif this.FitMethod = info.method; this.NumObservations = n; this.NumCoefficients = p; this.NumEstimatedCoefficients = p; this.CoefficientNames = info.CoefficientNames; this.CoefficientCovariance = info.covbeta; this.LogLikelihood = info.loglik; this.MSE = info.sigma2; this.DFE = n - p; ## Fixed-effects coefficient statistics (Residual df = n - p). se = sqrt (diag (info.covbeta)); tstat = info.beta ./ se; dfe = n - p; pval = 2 * (1 - tcdf (abs (tstat), dfe)); tcrit = tinv (0.975, dfe); lower = info.beta - tcrit * se; upper = info.beta + tcrit * se; this.Coefficients = table (info.beta(:), se(:), tstat(:), ... repmat (dfe, p, 1), pval(:), lower(:), upper(:), ... "VariableNames", {"Estimate", "SE", "tStat", "DF", "pValue", ... "Lower", "Upper"}, ... "RowNames", info.CoefficientNames(:)); ## Sums of squares and R-squared (MATLAB's mixed-model convention: ## SST = SSE + SSR, both from the conditional fit). ybar = mean (info.y); this.SSE = sum ((info.y - info.fitted) .^ 2); this.SSR = sum ((info.fitted - ybar) .^ 2); this.SST = this.SSE + this.SSR; rsq.Ordinary = 1 - this.SSE / this.SST; rsq.Adjusted = 1 - (this.SSE / dfe) / (this.SST / (n - 1)); this.Rsquared = rsq; ## Information criteria (total parameter count = fixed + covariance). ncov = sum (arrayfun (@(q) q*(q+1)/2, info.qk)) + 1; kpar = p + ncov; dev = -2 * info.loglik; mc.AIC = dev + 2 * kpar; mc.BIC = dev + kpar * log (n); mc.LogLikelihood = info.loglik; mc.Deviance = dev; this.ModelCriterion = mc; endfunction ## -*- texinfo -*- ## @deftypefn {LinearMixedModel} {@var{beta} =} fixedEffects (@var{lme}) ## @deftypefnx {LinearMixedModel} {[@var{beta}, @var{names}] =} fixedEffects (@var{lme}) ## ## Return the estimated fixed-effects coefficients @var{beta} and, ## optionally, their names. ## ## @end deftypefn function [beta, names] = fixedEffects (this) beta = this.beta_; names = this.CoefficientNames; endfunction ## -*- texinfo -*- ## @deftypefn {LinearMixedModel} {@var{b} =} randomEffects (@var{lme}) ## @deftypefnx {LinearMixedModel} {[@var{b}, @var{names}] =} randomEffects (@var{lme}) ## ## Return the best linear unbiased predictors (BLUPs) of the random effects ## @var{b} and, optionally, a cell array of @code{group:level:predictor} ## labels. ## ## @end deftypefn function [b, names] = randomEffects (this) b = this.b_; if (nargout > 1) names = {}; for k = 1:numel (this.qk_) pn = this.REPred_{k}; for l = 1:this.nlev_(k) for j = 1:this.qk_(k) names{end+1, 1} = sprintf ("%s:%s:%s", this.GroupNames_{k}, ... num2str (this.levels_{k}(l)), pn{j}); endfor endfor endfor endif endfunction ## -*- texinfo -*- ## @deftypefn {LinearMixedModel} {[@var{psi}, @var{mse}] =} covarianceParameters (@var{lme}) ## ## Return the estimated random-effects covariance matrices @var{psi} (a cell ## array, one per grouping term) and the residual variance @var{mse}. ## ## @end deftypefn function [psi, mse] = covarianceParameters (this) psi = this.Psi_; mse = this.sigma2_; endfunction ## -*- texinfo -*- ## @deftypefn {LinearMixedModel} {@var{yf} =} fitted (@var{lme}) ## @deftypefnx {LinearMixedModel} {@var{yf} =} fitted (@var{lme}, @qcode{"Conditional"}, @var{tf}) ## ## Return the fitted values. With @qcode{"Conditional"} true (the default) ## the fit includes the random effects (@code{X*beta + Z*b}); with false it ## is the marginal fit (@code{X*beta}). ## ## @end deftypefn function yf = fitted (this, varargin) cond = true; if (numel (varargin) >= 2 && strcmpi (varargin{1}, "Conditional")) cond = logical (varargin{2}); endif if (cond) yf = this.fitted_; else yf = this.fitted_marg_; endif endfunction ## -*- texinfo -*- ## @deftypefn {LinearMixedModel} {@var{r} =} residuals (@var{lme}) ## @deftypefnx {LinearMixedModel} {@var{r} =} residuals (@var{lme}, @qcode{"ResidualType"}, @var{type}) ## ## Return the conditional residuals @code{y - (X*beta + Z*b)}. @var{type} ## is @qcode{"Raw"} (default), @qcode{"Pearson"} (raw divided by ## @code{sqrt (sigma2)}), or @qcode{"Standardized"} (raw divided by the ## square root of its estimated variance). ## ## @end deftypefn function r = residuals (this, varargin) type = "raw"; if (numel (varargin) >= 2 && strcmpi (varargin{1}, "ResidualType")) type = lower (varargin{2}); endif raw = this.resid_; switch (type) case "raw" r = raw; case "pearson" r = raw / sqrt (this.sigma2_); case "standardized" r = raw ./ sqrt (cond_resid_var (this)); otherwise error ("LinearMixedModel: unknown ResidualType '%s'.", type); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {LinearMixedModel} {@var{ypred} =} predict (@var{lme}, @var{Xnew}, @var{Znew}, @var{Gnew}) ## @deftypefnx {LinearMixedModel} {[@var{ypred}, @var{yci}] =} predict (@dots{}) ## @deftypefnx {LinearMixedModel} {[@dots{}] =} predict (@dots{}, @var{name}, @var{value}) ## ## Predict the response at new fixed-effects design @var{Xnew}, ## random-effects design @var{Znew}, and grouping @var{Gnew}. By default ## the prediction is conditional on the estimated random effects (levels of ## @var{Gnew} not seen in the fit fall back to the marginal prediction). ## With ## @qcode{"Conditional"} false the marginal prediction @code{Xnew*beta} is ## returned. The second output @var{yci} gives 95% (or @qcode{"Alpha"}) ## confidence intervals for the marginal mean. ## ## @end deftypefn function [ypred, yci] = predict (this, Xnew, Znew, Gnew, varargin) if (nargin < 2) error ("LinearMixedModel: predict requires Xnew."); endif if (nargin < 3), Znew = []; endif if (nargin < 4), Gnew = []; endif cond = true; alpha = 0.05; for i = 1:2:numel (varargin) switch (lower (varargin{i})) case "conditional" cond = logical (varargin{i+1}); case "alpha" alpha = varargin{i+1}; endswitch endfor ypred = Xnew * this.beta_; if (cond && ! isempty (Znew) && ! isempty (Gnew)) Gnew = Gnew(:); for k = 1:numel (this.qk_) q = this.qk_(k); lev = this.levels_{k}; ## offset of term k inside the stacked BLUP vector off = 0; for kk = 1:k-1 off += this.qk_(kk) * this.nlev_(kk); endfor for i = 1:rows (Xnew) li = find (lev == Gnew(i), 1); if (! isempty (li)) bk = this.b_(off + (li-1)*q + (1:q)); ypred(i) += Znew(i, :) * bk; endif endfor endfor endif if (nargout > 1) v = sum ((Xnew * this.covbeta_) .* Xnew, 2); ## marginal mean variance tcrit = tinv (1 - alpha/2, this.DFE); half = tcrit * sqrt (v); ymarg = Xnew * this.beta_; yci = [ymarg - half, ymarg + half]; endif endfunction ## -*- texinfo -*- ## @deftypefn {LinearMixedModel} {@var{tbl} =} anova (@var{lme}) ## @deftypefnx {LinearMixedModel} {@var{tbl} =} anova (@var{lme}, @qcode{"DFMethod"}, @var{method}) ## ## Analysis-of-variance table of F-tests for the fixed-effects terms. Each ## row tests one coefficient. @var{method} selects the denominator degrees ## of freedom: @qcode{"Residual"} (default, @code{n - p}) or ## @qcode{"Satterthwaite"}. ## ## @end deftypefn function tbl = anova (this, varargin) dfmethod = "residual"; if (numel (varargin) >= 2 && strcmpi (varargin{1}, "DFMethod")) dfmethod = lower (varargin{2}); endif p = this.NumCoefficients; beta = this.beta_; se = sqrt (diag (this.covbeta_)); Fstat = (beta ./ se) .^ 2; DF1 = ones (p, 1); if (strcmp (dfmethod, "satterthwaite")) DF2 = __lme_dfsatt__ (this.X_, this.y_, this.Zx_, this.qk_, ... this.nlev_, this.Psi_, this.sigma2_, this.FitMethod, eye (p)); elseif (strcmp (dfmethod, "residual")) DF2 = repmat (this.DFE, p, 1); else error ("LinearMixedModel: unknown DFMethod '%s'.", dfmethod); endif pValue = 1 - fcdf (Fstat, DF1, DF2); tbl = table (Fstat(:), DF1(:), DF2(:), pValue(:), ... "VariableNames", {"FStat", "DF1", "DF2", "pValue"}, ... "RowNames", this.CoefficientNames(:)); endfunction ## -*- texinfo -*- ## @deftypefn {LinearMixedModel} {@var{p} =} coefTest (@var{lme}) ## @deftypefnx {LinearMixedModel} {@var{p} =} coefTest (@var{lme}, @var{H}) ## @deftypefnx {LinearMixedModel} {[@var{p}, @var{F}, @var{df1}, @var{df2}] =} coefTest (@dots{}) ## ## Test the linear hypothesis @code{H*beta = 0} with an F-test. With no ## @var{H}, tests that all non-intercept coefficients are zero (intercept ## is taken to be the first coefficient). Returns the p-value and, ## optionally, the F-statistic and its numerator and denominator degrees of ## freedom (denominator = @code{n - p}). ## ## @end deftypefn function [pval, F, df1, df2] = coefTest (this, H) p = this.NumCoefficients; if (nargin < 2) H = [zeros(p-1, 1), eye(p-1)]; ## all but the (first) intercept endif if (columns (H) != p) error ("LinearMixedModel: H must have one column per coefficient."); endif Hb = H * this.beta_; df1 = rank (H); df2 = this.DFE; F = (Hb' * ((H * this.covbeta_ * H') \ Hb)) / df1; pval = 1 - fcdf (F, df1, df2); endfunction ## -*- texinfo -*- ## @deftypefn {LinearMixedModel} {@var{ci} =} coefCI (@var{lme}) ## @deftypefnx {LinearMixedModel} {@var{ci} =} coefCI (@var{lme}, @var{alpha}) ## ## Confidence intervals for the fixed-effects coefficients at level ## @code{1 - @var{alpha}} (default @var{alpha} = 0.05). Row @math{j} holds ## the lower and upper bounds for coefficient @math{j}. ## ## @end deftypefn function ci = coefCI (this, alpha) if (nargin < 2), alpha = 0.05; endif se = sqrt (diag (this.covbeta_)); tcrit = tinv (1 - alpha/2, this.DFE); ci = [this.beta_ - tcrit * se, this.beta_ + tcrit * se]; endfunction ## -*- texinfo -*- ## @deftypefn {LinearMixedModel} {@var{D} =} designMatrix (@var{lme}, @var{type}) ## ## Return the fixed-effects design matrix (@var{type} = @qcode{"Fixed"}, ## default) or the expanded random-effects design matrix ## (@var{type} = @qcode{"Random"}). ## ## @end deftypefn function D = designMatrix (this, type) if (nargin < 2), type = "Fixed"; endif switch (lower (type)) case "fixed" D = this.X_; case "random" D = this.Zx_; otherwise error ("LinearMixedModel: type must be 'Fixed' or 'Random'."); endswitch endfunction endmethods methods (Access = private) ## Diagonal of the covariance of the conditional residuals, for standardized ## residuals: r_c = Mc*y with Mc = (I - Hx) - Zx*Dabs*Zx'*P. function v = cond_resid_var (this) n = this.NumObservations; ## rebuild the marginal covariance V and the absolute RE covariance Dabs blocks = {}; for k = 1:numel (this.qk_) for l = 1:this.nlev_(k) blocks{end+1} = this.Psi_{k}; endfor endfor Dabs = blkdiag (blocks{:}); V = this.Zx_ * Dabs * this.Zx_' + this.sigma2_ * eye (n); Vi = inv (V); X = this.X_; XtViX = X' * Vi * X; Hx = X * (XtViX \ (X' * Vi)); P = Vi - Vi * X * (XtViX \ (X' * Vi)); Mc = (eye (n) - Hx) - this.Zx_ * Dabs * this.Zx_' * P; v = diag (Mc * V * Mc'); endfunction endmethods endclassdef ## Shared MATLAB-verified fixture (fitlmematrix R2026a): random-intercept REML ## model y ~ x + x2 + (1|g) on 42 observations, 6 groups. %!shared X, yL, grp, xL, x2, lme %! xL = [0.032760004 0.70410822 -0.8646718 -0.28869454 0.51276678 -1.4975462 ... %! -1.4527871 -0.80013541 -1.644209 1.5137701 0.72905543 0.20880758 1.0856145 ... %! 0.62862577 -0.87409978 1.9178276 0.09748204 0.50697633 1.0247569 ... %! -0.92789896 -0.88921018 -0.98322849 -0.031378913 0.86875961 -0.91481141 ... %! 0.034324163 -0.25025257 -1.0575644 -0.86131607 -0.35355444 0.82950729 ... %! -0.36874363 0.061580868 0.55803564 -0.1763803 1.0482413 1.0137831 ... %! -0.94876976 -0.010703972 -0.35149845 -1.6828735 -1.0493301]'; %! x2 = [0.68979276 0.0074354814 -0.45697437 -0.5636481 1.4567202 -0.97829955 ... %! -1.12922 -0.030542479 1.5847779 -0.87837755 0.24121762 0.68747601 ... %! -0.56728765 0.98895053 -0.39350661 0.85326015 0.36524343 0.15824977 ... %! -1.7665212 0.59808246 -0.55763708 -1.1982294 -2.1473319 0.22521416 ... %! 0.37034398 -1.880586 0.052941033 -0.70016994 0.2174853 -1.7797082 ... %! 0.51971317 -0.35551286 1.9845963 -1.3498848 -0.63514097 -0.78794714 ... %! 1.3681179 1.4423152 -0.51233905 0.30238864 2.0458136 0.17326323]'; %! yL = [3.5635971 -0.36763498 1.4192715 3.4471073 2.2760668 3.6420287 ... %! 4.4481522 1.5292777 5.437158 0.44016873 0.7259756 2.9771749 1.2941347 ... %! 0.26407508 1.9673466 0.77589984 1.2176078 0.8528384 -0.86522876 2.8651388 ... %! 2.0127931 3.1242841 -0.49164361 1.3192295 5.3782287 -0.40680701 2.3989882 ... %! 3.606108 1.9193197 1.6713765 2.4383124 1.9314844 3.5112706 0.91644553 ... %! 0.034923706 -0.15510033 2.1765206 2.5327412 3.1421219 3.3472574 5.343425 ... %! 3.8775899]'; %! grp = [1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 ... %! 5 6 1 2 3 4 5 6]'; %! X = [ones(42,1), xL, x2]; %! lme = fitlmematrix (X, yL, ones (42, 1), grp, "FitMethod", "REML", ... %! "FixedEffectPredictors", {"(Intercept)", "x", "x2"}); %!test # returns a LinearMixedModel and basic properties %! assert_equal (isa (lme, "LinearMixedModel"), true); %! assert_equal (lme.NumObservations, 42); %! assert_equal (lme.NumCoefficients, 3); %! assert_equal (lme.DFE, 39); %! assert_equal (lme.FitMethod, "REML"); %!test # Coefficients table -- SE / tStat / DF / pValue / CI vs MATLAB %! C = lme.Coefficients; %! assert_equal (C.Estimate, [1.96839; -1.37926; 0.811747], 1e-4); %! assert_equal (C.SE, [0.376509; 0.113264; 0.0966571], 1e-5); %! assert_equal (C.tStat, [5.22801; -12.1773; 8.39822], 1e-4); %! assert_equal (C.DF, [39; 39; 39]); %! assert_equal (C.pValue, [6.08512e-06; 7.30179e-15; 2.8079e-10], 1e-8); %! assert_equal (C.Lower, [1.20683; -1.60835; 0.61624], 1e-4); %! assert_equal (C.Upper, [2.72996; -1.15016; 1.00725], 1e-4); %!test # anova with Residual DF (default): F = t^2, DF2 = n - p %! a = anova (lme); %! assert_equal (a.FStat, [27.3321; 148.287; 70.5301], 1e-3); %! assert_equal (a.DF1, [1; 1; 1]); %! assert_equal (a.DF2, [39; 39; 39]); %!test # anova with Satterthwaite DF vs MATLAB %! a = anova (lme, "DFMethod", "Satterthwaite"); %! assert_equal (a.DF2, [4.95056; 34.5037; 34.3302], 1e-3); %! assert_equal (a.pValue, [0.00348739; 4.79374e-14; 7.71731e-10], 1e-6); %!test # coefTest: joint test of the non-intercept coefficients %! [p, F, df1, df2] = coefTest (lme); %! assert_equal (F, 106.847, 1e-2); %! assert_equal (df1, 2); assert_equal (df2, 39); %! assert_equal (p < 1e-12, true); # ~1e-16, below the precision of 1 - fcdf %!test # coefTest with an explicit single-row hypothesis (the x coefficient) %! [p, F, df1] = coefTest (lme, [0 1 0]); %! assert_equal (F, 148.287, 1e-2); %! assert_equal (df1, 1); %!test # coefCI matches the Coefficients table bounds %! ci = coefCI (lme); %! assert_equal (ci(:,1), lme.Coefficients.Lower, 1e-12); %! assert_equal (ci(:,2), lme.Coefficients.Upper, 1e-12); %!test # residuals: raw, Pearson (= raw/sqrt(mse)), Standardized vs MATLAB %! [~, mse] = covarianceParameters (lme); %! raw = residuals (lme); %! assert_equal (raw, yL - fitted (lme), 1e-12); %! assert_equal (residuals (lme, "ResidualType", "Pearson"), ... %! raw / sqrt (mse), 1e-12); %! rs = residuals (lme, "ResidualType", "Standardized"); %! assert_equal (rs(1:3), [0.1824776; -0.4452186; -2.057201], 1e-5); %!test # fitted: conditional includes random effects, marginal does not %! fc = fitted (lme); %! fm = fitted (lme, "Conditional", false); %! assert_equal (fm, X * fixedEffects (lme), 1e-12); %! assert_equal (any (abs (fc - fm) > 1e-3, 'all'), true); %!test # predict: conditional (unseen group falls back to marginal) + marginal %! Xn = [1 0.5 0; 1 -0.5 1; 1 1 -1]; %! yc = predict (lme, Xn, ones (3, 1), [1; 2; 7]); %! ym = predict (lme, Xn, ones (3, 1), [1; 2; 7], "Conditional", false); %! assert_equal (yc, [2.254803; 2.351467; -0.2226094], 1e-4); %! assert_equal (ym, [1.278766; 3.469768; -0.2226094], 1e-4); %! # group 7 unseen -> conditional == marginal %! assert_equal (yc(3), ym(3), 1e-10); %!test # predict confidence intervals bracket the marginal mean %! Xn = [1 0.5 0; 1 -0.5 1]; %! [yp, ci] = predict (lme, Xn, ones (2, 1), [1; 2], "Conditional", false); %! assert_equal (all (ci(:,1) < yp & yp < ci(:,2), 'all'), true); %!test # effect extraction and design matrices %! [beta, names] = fixedEffects (lme); %! assert_equal (beta, lme.Coefficients.Estimate, 1e-12); %! assert_equal (names, {"(Intercept)", "x", "x2"}); %! b = randomEffects (lme); %! assert_equal (numel (b), 6); # 6 intercept BLUPs %! assert_equal (b, [0.9760378; -1.118301; -0.1922078; 0.8754252; ... %! -0.7955462; 0.2545924], 1e-4); # BLUPs vs MATLAB %! assert_equal (size (designMatrix (lme, "Fixed")), [42, 3]); %! assert_equal (size (designMatrix (lme, "Random")), [42, 6]); %!test # R-squared and sums of squares (MATLAB SST = SSE + SSR convention) %! assert_equal (lme.Rsquared.Ordinary, 0.8758549, 1e-6); %! assert_equal (lme.Rsquared.Adjusted, 0.8694885, 1e-6); %! assert_equal (lme.SST, lme.SSE + lme.SSR, 1e-10); %! assert_equal (lme.ModelCriterion.Deviance, -2 * lme.LogLikelihood, 1e-10); ## Error handling %!error residuals (lme, "ResidualType", "xxx") %!error anova (lme, "DFMethod", "xxx") %!error designMatrix (lme, "bogus") %!error coefTest (lme, [1 0]) %!error lme(1) statistics-release-1.9.2/inst/Regression/LinearModel.m000066400000000000000000014315171524624707500230310ustar00rootroot00000000000000## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef LinearModel ## -*- texinfo -*- ## @deftp {statistics} LinearModel ## ## Linear regression model ## ## The @code{LinearModel} class represents a least-squares (or, optionally, ## robust) linear regression fit of a response variable to one or more ## predictor variables. A @code{LinearModel} object is returned by the ## @code{fitlm} function and holds everything about the fit in one place: ## the fitted coefficients, the data and specification used to produce ## them, and the diagnostics needed to assess the quality of the fit. ## ## The properties of a @code{LinearModel} object fall into four groups: ## ## @multitable @columnfractions 0.22 0.76 ## @headitem Group @tab Properties ## ## @item Coefficient estimates @tab @code{Coefficients} (a table of ## estimates, standard errors, t-statistics, and p-values for each term), ## @code{CoefficientCovariance}, @code{CoefficientNames}, and the ## coefficient counts @code{NumCoefficients} and ## @code{NumEstimatedCoefficients}. ## ## @item Summary statistics of the fit @tab @code{DFE}, ## @code{Fitted}, @code{Residuals} (raw, Pearson, Studentized, and ## standardized), @code{Diagnostics} (leverage, Cook's distance, and other ## per-observation influence measures), @code{MSE}, @code{RMSE}, ## @code{Rsquared} (ordinary and adjusted), @code{SSE}, @code{SSR}, ## @code{SST}, @code{LogLikelihood}, @code{ModelCriterion} (AIC, BIC, etc.), ## and @code{ModelFitVsNullModel} (the F-test of the fitted model against an ## intercept-only model). ## ## @item Fitting method information @tab @code{Robust}, which records ## the weighting function and tuning constant used when the model is fit by ## robust regression, and is empty for an ordinary least squares fit, and ## @code{Steps}, which records the stepwise fitting information whenever ## the model was fit using stepwise regression, and is currently always ## empty. ## ## @item Input data properties @tab @code{Formula}, ## @code{NumObservations}, @code{NumPredictors}, @code{NumVariables}, ## @code{ObservationInfo} (which observations were used, excluded, missing, ## or weighted), @code{ObservationNames}, @code{PredictorNames}, ## @code{ResponseName}, @code{VariableInfo}, @code{VariableNames}, and ## @code{Variables}. ## @end multitable ## ## A categorical predictor expands to indicator columns, one per level bar ## the reference level, which the intercept carries. When the model has no ## intercept, the @emph{first} categorical predictor is given an indicator ## for every one of its levels instead, so that its coefficients are the ## group means; any further categorical predictor stays reference coded, ## which keeps the design full rank. This differs from MATLAB, which omits ## the reference level whether or not an intercept is present and so cannot ## fit the reference group at all -- for a three-level grouping variable ## @code{g}, MATLAB fits @code{y ~ g - 1} with two coefficients, predicts ## exactly 0 for every observation in the omitted group, and reports a ## negative @math{R^2}. This implementation returns three coefficients, one ## per group. ## ## A @code{LinearModel} object supports categorical predictors, which are ## automatically encoded internally as indicator (dummy) variables, ## observation weights for a weighted least squares fit, excluding specific ## observations from the fit, and robust regression using iteratively ## reweighted least squares. Once fitted, the following methods are ## available on a @code{LinearModel} object: ## ## @multitable @columnfractions 0.2 0.78 ## @headitem Method @tab Description ## ## @item @code{predict} @tab Predict responses at new predictor values ## given in a matrix or table, or reproduce the training fitted values when ## called with no new data. Can also return pointwise or simultaneous ## confidence or prediction intervals alongside the point predictions. ## ## @item @code{feval} @tab Predict responses given predictors as ## separate scalar or vector arguments (one per predictor variable) instead ## of a single matrix, so a @code{LinearModel} object can be evaluated the ## same way as a plain function handle. Returns point predictions only. ## ## @item @code{random} @tab Simulate new response values at new ## predictor locations by adding independent Gaussian noise, drawn from the ## estimated error variance @code{MSE}, to the fitted response. ## ## @item @code{coefCI} @tab Return Wald confidence intervals for every ## fitted coefficient at a chosen significance level (default @math{0.05}). ## ## @item @code{coefTest} @tab Test a linear hypothesis on the fitted ## coefficients. With no arguments, tests the overall model F-test that ## all non-intercept coefficients are zero; a custom hypothesis can be ## given as a contrast matrix and, if needed, right-hand-side values. ## Returns the p-value, and optionally the F-statistic and its numerator ## degrees of freedom. ## ## @item @code{dwtest} @tab Durbin-Watson test for first-order ## autocorrelation among the model residuals, with a choice of exact or ## approximate p-value computation and a one- or two-sided alternative. ## ## @item @code{addTerms} @tab Return a new, refitted @code{LinearModel} ## with terms added to the current model specification, given as a ## Wilkinson formula fragment or a terms matrix. Weights, excluded rows, ## and categorical encodings carry over automatically; the original model ## object is left unmodified. ## ## @item @code{removeTerms} @tab Return a new, refitted ## @code{LinearModel} with terms removed from the current model ## specification, given as a Wilkinson formula fragment or a terms matrix. ## Weights, excluded rows, and categorical encodings carry over ## automatically; the original model object is left unmodified. ## ## @item @code{plotResiduals} @tab Plot the model residuals. Default ## is a probability density histogram; other supported plot types are ## @qcode{'fitted'}, @qcode{'caseorder'}, @qcode{'lagged'}, ## @qcode{'probability'}, and @qcode{'observed'}. ## ## @item @code{plotDiagnostics} @tab Plot per-observation influence ## diagnostics. Default is leverage by observation row number; other ## supported plot types are @qcode{'cookd'}, @qcode{'covratio'}, ## @qcode{'dfbetas'}, @qcode{'dffits'}, @qcode{'s2_i'}, and ## @qcode{'contour'} (standardized residuals against leverage with Cook's ## distance contours). ## ## @item @code{plotEffects} @tab Plot the estimated main effect and ## 95% confidence interval of each predictor, evaluated between its ## observed minimum and maximum with all other predictors held at their ## observed means. ## ## @item @code{plotAdjustedResponse} @tab Plot the fitted response ## against a single predictor, with the other predictors averaged out by ## averaging the fitted values over the observations used in the fit. ## ## @item @code{plotAdded} @tab Plot the incremental effect of one or ## more terms on the response, after removing the effects of all other ## terms, along with the fitted line and its 95% confidence bounds. ## ## @item @code{plot} @tab Plot a default view of the model. Creates an ## added variable plot for the whole model when more than one predictor ## is included, a scatter plot of the data with a fitted curve and 95% ## confidence bounds when exactly one predictor is included, or a ## histogram of the residuals when no predictors are included. ## ## @item @code{plotInteraction} @tab Plot the main and conditional effects ## of two predictors, or the adjusted response as a function of one ## predictor for several fixed values of the other, to visualize whether ## the two predictors interact. ## ## @item @code{compact} @tab Return a @code{CompactLinearModel} that ## discards the training data and per-observation diagnostics while ## retaining the coefficient estimates and fit statistics needed for ## prediction and inference. ## ## @item @code{anova} @tab Analysis of variance for the fitted model, ## reporting either the per-term breakdown of sums of squares or a ## summary table of the model against the total and residual variation. ## ## @item @code{step} @tab Improve the fitted model by one or more ## steps of stepwise term selection, returning a new, refitted ## @code{LinearModel} without modifying the original. ## @end multitable ## ## Create a @code{LinearModel} object by using the @code{fitlm} function or ## the class constructor directly. ## ## @seealso{fitlm} ## @end deftp properties(GetAccess = public, SetAccess = protected) ## Coefficient estimate properties ## -*- texinfo -*- ## @deftp {LinearModel} {property} CoefficientCovariance ## ## Covariance matrix of coefficient estimates ## ## A @math{p}-by-@math{p} numeric matrix of covariance values for the ## coefficient estimates, where @math{p} is the number of coefficients in ## the fitted model as given by @code{NumCoefficients}. This property is ## read-only. ## ## @end deftp CoefficientCovariance = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} CoefficientNames ## ## Coefficient names ## ## A cell array of character vectors, each containing the name of the ## corresponding model term (e.g., @qcode{'(Intercept)'}, @qcode{'x1'}, ## @qcode{'x1:x2'}). This property is read-only. ## ## @end deftp CoefficientNames = {}; ## -*- texinfo -*- ## @deftp {LinearModel} {property} Coefficients ## ## Coefficient values ## ## A table with one row for each coefficient and four columns: ## @itemize ## @item @code{Estimate} - estimated coefficient value ## @item @code{SE} - standard error of the estimate ## @item @code{tStat} - t-statistic for a two-sided test ## @item @code{pValue} - p-value for the t-statistic ## @end itemize ## Coefficients that are dropped due to rank deficiency have ## @code{Estimate = 0}, @code{SE = 0}, @code{tStat = NaN}, ## @code{pValue = NaN}. This property is read-only. ## ## @end deftp Coefficients = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} NumCoefficients ## ## Number of model coefficients ## ## A positive integer giving the total number of coefficients in the fitted ## model, including any coefficients set to zero because the model terms are ## rank deficient. This property is read-only. ## ## @end deftp NumCoefficients = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} NumEstimatedCoefficients ## ## Number of estimated coefficients ## ## A positive integer giving the number of coefficients actually estimated, ## i.e., not set to zero due to rank deficiency. ## @code{NumEstimatedCoefficients} equals the degrees of freedom for ## regression. This property is read-only. ## ## @end deftp NumEstimatedCoefficients = []; ## Summary statistic properties ## -*- texinfo -*- ## @deftp {LinearModel} {property} DFE ## ## Degrees of freedom for error ## ## A positive integer equal to the number of observations minus the number ## of estimated coefficients: @code{DFE = NumObservations - ## NumEstimatedCoefficients}. This property is read-only. ## ## @end deftp DFE = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} Diagnostics ## ## Observation diagnostics ## ## A table with one row per observation and seven columns: ## @itemize ## @item @code{Leverage} - diagonal of the hat matrix @math{H} ## @item @code{CooksDistance} - Cook's distance, a measure of scaled ## change in fitted values ## @item @code{Dffits} - delete-1 scaled differences in fitted values ## @item @code{S2_i} - delete-1 residual variance estimate ## @item @code{CovRatio} - ratio of the determinant of the coefficient ## covariance matrix with and without each observation ## @item @code{Dfbetas} - @math{n}-by-@math{p} matrix of scaled changes ## in coefficient estimates when each observation is deleted in turn ## @item @code{HatMatrix} - @math{n}-by-@math{n} projection matrix such ## that @code{Fitted = HatMatrix * y} ## @end itemize ## Rows not used in fitting have @code{NaN} in @code{CooksDistance}, ## @code{Dffits}, @code{S2_i}, and @code{CovRatio}, and zeros in ## @code{Leverage}, @code{Dfbetas}, and @code{HatMatrix}. This property ## is read-only. ## ## @end deftp Diagnostics = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} Fitted ## ## Fitted response values ## ## An @math{n}-by-1 numeric vector of predicted response values based on ## the training data, where @math{n} is the total number of observations, ## excluded and missing rows included. Every observation whose predictors ## are available carries a fitted value, whether or not it was used in the ## fit, so an excluded row and a row missing only its response are both ## fitted; only a row whose predictors are missing is @code{NaN}. The ## corresponding @code{Residuals} are @code{NaN} for any row not used in ## the fit, so @code{Fitted} and @code{Residuals.Raw} do not add back to ## the response there. Use @code{predict} to obtain predictions for new ## data or to compute confidence bounds. This property is read-only. ## ## @end deftp Fitted = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} LogLikelihood ## ## Log-likelihood of the fitted model ## ## A scalar numeric value equal to the log-likelihood of the response ## values, assuming each response is normally distributed with mean equal ## to the fitted value and variance equal to @math{SSE/n} (the MLE ## variance estimate). This property is read-only. ## ## For a weighted fit, observation @math{i} is taken to have variance ## @math{s^2/w_i}, so the log-likelihood carries the term ## @math{0.5 * sum (log (w))} and @math{n} counts only the observations ## with nonzero weight: ## ## @math{logL = -n/2 * (1 + log (2*pi*SSE/n)) + 0.5 * sum (log (w))} ## ## This makes the value invariant to the scale of the weights, as it must ## be: multiplying every weight by a constant rescales the estimated ## variance by the same constant and leaves the fit unchanged. ## ## MATLAB omits the @math{0.5 * sum (log (w))} term and counts every ## observation in @math{n}, so its @code{LogLikelihood} moves by ## @math{n/2 * log (c)} when the weights are multiplied by @math{c}, and ## the @code{ModelCriterion} values built on it move with it. This ## implementation follows R's @code{logLik.lm} instead. Unweighted fits ## are unaffected, and agree with MATLAB. ## ## A robust fit carries no weight term. Its @code{SSE} is a robust scale ## estimate rather than a weighted residual sum, so the two enter ## separately and the general form is used: ## ## @math{logL = -n/2 * log (2*pi*SSE/n) - sum (w .* r.^2) / (2*SSE/n)} ## ## which is what MATLAB computes, and which reduces to the expression ## above whenever @math{sum (w .* r.^2)} equals @code{SSE}, as it does for ## any least-squares fit. Robust fits therefore agree with MATLAB ## exactly, weighted or not. ## ## @end deftp LogLikelihood = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} ModelCriterion ## ## Model comparison criteria ## ## A structure with four fields: ## @itemize ## @item @code{AIC} - Akaike information criterion: ## @math{-2 * logL + 2 * m} ## @item @code{AICc} - AIC corrected for sample size: ## @math{AIC + (2*m*(m+1))/(n-m-1)} ## @item @code{BIC} - Bayesian information criterion: ## @math{-2 * logL + m * log(n)} ## @item @code{CAIC} - Consistent AIC: ## @math{-2 * logL + m * (log(n) + 1)} ## @end itemize ## Here @math{logL} is @code{LogLikelihood}, @math{m} is ## @code{NumEstimatedCoefficients}, and @math{n} is the number of ## observations with nonzero weight, which is @code{NumObservations} ## unless some weight is zero. This property is read-only. ## ## Because these are built on @code{LogLikelihood}, they inherit its ## treatment of weights; see that property for how it differs from ## MATLAB's. ## ## @end deftp ModelCriterion = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} ModelFitVsNullModel ## ## F-test of the fitted model versus the null model ## ## A structure with three fields: ## @itemize ## @item @code{Fstat} - F-statistic of the fitted model versus a null ## model containing only a constant term ## @item @code{Pvalue} - p-value for the F-statistic ## @item @code{NullModel} - character vector describing the null model ## @end itemize ## This property is read-only. ## ## @end deftp ModelFitVsNullModel = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} MSE ## ## Mean squared error ## ## A scalar numeric value equal to @math{SSE / DFE}, where @code{SSE} is ## the sum of squared errors and @code{DFE} is the degrees of freedom for ## error. This property is read-only. ## ## @end deftp MSE = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} Residuals ## ## Residuals for the fitted model ## ## A table with one row per observation and four columns: ## @itemize ## @item @code{Raw} - observed minus fitted values ## @item @code{Pearson} - raw residuals divided by @code{RMSE} ## @item @code{Standardized} - internally studentized residuals; raw ## residuals divided by their estimated standard deviation using the ## full-model @code{MSE} ## @item @code{Studentized} - externally studentized residuals; each raw ## residual divided by an estimate of the standard deviation based on ## all observations except that one, using the delete-1 @code{S2_i} ## @end itemize ## Rows not used in the fit contain @code{NaN}. This property is ## read-only. ## ## @end deftp Residuals = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} RMSE ## ## Root mean squared error ## ## A scalar numeric value equal to @math{sqrt(MSE)}. This property is ## read-only. ## ## @end deftp RMSE = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} Rsquared ## ## R-squared goodness-of-fit statistics ## ## A structure with two fields: ## @itemize ## @item @code{Ordinary} - coefficient of determination: ## @math{R^2 = SSR / SST} ## @item @code{Adjusted} - adjusted @math{R^2} that accounts for the ## number of coefficients in the model ## @end itemize ## This property is read-only. ## ## @end deftp Rsquared = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} SSE ## ## Sum of squared errors ## ## A scalar numeric value equal to the sum of squared residuals. For a ## model with an intercept, @math{SST = SSE + SSR}. For weighted fits, ## this is the weighted sum of squares. This property is read-only. ## ## @end deftp SSE = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} SSR ## ## Regression sum of squares ## ## A scalar numeric value equal to the sum of squared deviations of the ## fitted values from the mean of the response. For a model with an ## intercept, @math{SST = SSE + SSR}. For weighted fits, this is the ## weighted sum of squares. This property is read-only. ## ## @end deftp SSR = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} SST ## ## Total sum of squares ## ## A scalar numeric value equal to the sum of squared deviations of the ## response from its mean. For a model with an intercept, ## @math{SST = SSE + SSR}. For a robust fit, @math{SST = SSE + SSR} ## rather than the deviation from the mean. For weighted fits, this is ## the weighted sum of squares. This property is read-only. ## ## @end deftp SST = []; ## Fitting method properties ## -*- texinfo -*- ## @deftp {LinearModel} {property} Robust ## ## Robust fit information ## ## A structure with three fields: ## @itemize ## @item @code{WgtFun} - robust weighting function name, e.g. ## @qcode{'bisquare'} ## @item @code{Tune} - tuning constant; empty if @code{WgtFun} is ## @qcode{'ols'} or a function handle with the default tuning constant ## @item @code{Weights} - vector of final iteration weights; empty for ## a @code{CompactLinearModel} object ## @end itemize ## This structure is empty unless the model was fit using robust ## regression. This property is read-only. ## ## @end deftp Robust = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} Steps ## ## Stepwise fitting information ## ## A structure recording the term-selection trace, populated whenever the ## model was fit by @code{stepwiselm} or improved by @code{step}, and ## @code{[]} otherwise. It has seven fields: ## ## @multitable @columnfractions 0.15 0.8 ## @headitem Field @tab Contents ## @item @code{Start} @tab a @code{LinearFormula} for the model the search ## started from. ## @item @code{Lower} @tab a @code{LinearFormula} for the smallest model ## considered; its terms are never removed. ## @item @code{Upper} @tab a @code{LinearFormula} for the largest model ## considered. ## @item @code{Criterion} @tab the selection criterion, such as ## @qcode{'SSE'}. ## @item @code{PEnter} @tab the threshold a term must beat to enter. ## @item @code{PRemove} @tab the threshold above which a term leaves. ## @item @code{History} @tab a table with one row per step. ## @end multitable ## ## @code{History} carries the columns @code{Action} (@qcode{'Start'}, ## @qcode{'Add'}, or @qcode{'Remove'}), @code{TermName}, @code{Terms} (the ## terms matrix after the step, over the model's variables), @code{DF} (the ## coefficient count after the step), and @code{delDF} (the change in it, ## negative for a removal). The remaining columns follow the criterion: ## @code{FStat} and @code{pValue} under @qcode{'SSE'}, and otherwise a ## single column named for the criterion (@code{AIC}, @code{BIC}, ## @code{Rsquared}, or @code{AdjRsquared}) holding its value after the ## step. ## ## The first row is the starting model, named by its right-hand side, and ## @code{step} appends to the history it inherits rather than starting a ## new one. This property is read-only. ## ## @end deftp Steps = []; ## Input data properties ## -*- texinfo -*- ## @deftp {LinearModel} {property} Formula ## ## Model formula information ## ## A @code{LinearFormula} object representing the model formula, with ## properties including @code{ResponseName}, @code{LinearPredictor}, ## @code{PredictorNames}, @code{TermNames}, @code{HasIntercept}, ## @code{Terms} (the terms matrix), and @code{InModel}. Converting it with ## @code{char} renders the whole formula. This property is read-only. ## ## @end deftp Formula = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} NumObservations ## ## Number of observations used in the fit ## ## A positive integer giving the number of observations actually used in ## fitting. Rows with missing values and rows excluded via the ## @code{'Exclude'} name-value argument are not counted. This property ## is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} NumPredictors ## ## Number of predictor variables ## ## A positive integer giving the number of predictor variables used to ## fit the model. This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} NumVariables ## ## Number of variables in the input data ## ## A positive integer giving the total number of variables in the input ## data, counting predictors, the response, and any unused columns. ## This property is read-only. ## ## @end deftp NumVariables = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} ObservationInfo ## ## Per-observation metadata ## ## An @math{n}-by-4 table where @math{n} is the total number of rows in ## the input data. The four columns are: ## @itemize ## @item @code{Weights} - observation weight, default is 1 ## @item @code{Excluded} - logical; true if excluded via the ## @code{'Exclude'} argument ## @item @code{Missing} - logical; true if the row contains any ## @code{NaN} value ## @item @code{Subset} - logical; true if the observation was used in ## the fit, i.e. not excluded and not missing ## @end itemize ## This property is read-only. ## ## @end deftp ObservationInfo = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} ObservationNames ## ## Observation names ## ## A cell array of character vectors containing the names of the ## observations. If the fit was based on a table that has row names, ## this property holds those names. Otherwise it is an empty cell array. ## This property is read-only. ## ## @end deftp ObservationNames = {}; ## -*- texinfo -*- ## @deftp {LinearModel} {property} PredictorNames ## ## Names of predictor variables ## ## A cell array of character vectors containing the names of the ## predictor variables used to fit the model. This property is ## read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {LinearModel} {property} ResponseName ## ## Response variable name ## ## A character vector containing the name of the response variable. ## This property is read-only. ## ## @end deftp ResponseName = ''; ## -*- texinfo -*- ## @deftp {LinearModel} {property} VariableInfo ## ## Information about input variables ## ## A table with one row per variable including any unused variables, and ## four columns: ## @itemize ## @item @code{Class} - variable class as a character vector, e.g. ## @qcode{'double'} or @qcode{'categorical'} ## @item @code{Range} - for continuous variables, a two-element vector ## @code{[min, max]}; for categorical variables, a vector of the ## distinct values ## @item @code{InModel} - logical; true if the variable is in the ## fitted model ## @item @code{IsCategorical} - logical; true if the variable is ## categorical ## @end itemize ## This property is read-only. ## ## @end deftp VariableInfo = []; ## -*- texinfo -*- ## @deftp {LinearModel} {property} VariableNames ## ## Names of all variables in the input data ## ## A cell array of character vectors containing the names of all ## variables, including predictors, the response, and unused variables. ## For table input these are the table column names. For matrix input ## these are the values given by @code{'VarNames'}, defaulting to ## @qcode{@{'x1','x2',...,'xp','y'@}}. This property is read-only. ## ## @end deftp VariableNames = {}; ## -*- texinfo -*- ## @deftp {LinearModel} {property} Variables ## ## Input data as a table ## ## A table containing predictor and response values for all observations, ## including unused variables. For table input this is the full input ## table. For matrix input this is a table constructed from the ## predictor matrix and response vector. This property is read-only. ## ## @end deftp Variables = []; endproperties properties(Access = private, Hidden) ## Full design matrix, n by p_design, used for predictions DesignMatrix = []; ## Column indices of active coefficients in the design matrix ActiveCols = []; ## Whether the model includes an intercept term HasIntercept = true; ## Response vector, full n by 1 with NaN for non-subset rows ResponseVector = []; ## Full n by 1 observation weights WeightVector = []; ## n by 1 logical mask: true for rows used in the fit SubsetMask = []; ## Terms matrix from modelspec or parse_modelspec TermsMatrix = []; ## Categorical level info for re-encoding in predict CatLevelInfo = []; ## Predictor names after categorical dummy expansion EncPredictorNames = {}; ## Predictors available to the fit, before those the model does not use ## are dropped from the public PredictorNames. Stepwise selection needs ## the candidates, not the chosen ones. PredictorNamesRaw = {}; ## Cached per-predictor design contrasts used by plotEffects EffectContrasts = []; ## Cached per-predictor-pair design contrasts used by plotInteraction InteractionContrasts = []; ## Cached conceptual-term name and design-column grouping used by anova TermGroups = {}; ## Encoded predictor matrix (Path B only), cached for refit EncodedPredMatrix = []; ## Parsed NV options stored for refit operations OrigOpts = []; endproperties methods(Hidden) ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ("%s =\n", in_name); endif disp (this); endfunction ## Custom display function disp (this) if (isempty (this.Robust)) fprintf ("\n Linear regression model:\n"); else fprintf ("\n Linear regression model (robust fit):\n"); endif if (! isempty (this.Formula) && isa (this.Formula, 'LinearFormula')) fprintf (" %s\n", char (this.Formula)); endif if (! isempty (this.Coefficients)) fprintf ("\n Estimated Coefficients:\n\n"); disp (this.Coefficients); endif fprintf ("\n"); if (! isempty (this.NumObservations) && ! isempty (this.DFE)) fprintf ("Number of observations: %d, Error degrees of freedom: %d\n", ... this.NumObservations, this.DFE); endif if (! isempty (this.RMSE)) fprintf ("Root Mean Squared Error: %g\n", this.RMSE); endif if (! isempty (this.Rsquared) && isstruct (this.Rsquared)) fprintf ("R-squared: %g, Adjusted R-Squared: %g\n", ... this.Rsquared.Ordinary, this.Rsquared.Adjusted); endif if (! isempty (this.ModelFitVsNullModel) ... && isstruct (this.ModelFitVsNullModel) ... && isfield (this.ModelFitVsNullModel, 'Fstat')) fprintf ("F-statistic vs. constant model: %g, p-value = %g\n", ... this.ModelFitVsNullModel.Fstat, ... this.ModelFitVsNullModel.Pvalue); endif endfunction ## Class specific subscripted reference function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' error (strcat ("LinearModel: () indexing is not supported.", ... " Use dot notation to access properties.")); case '{}' error (strcat ("LinearModel: {} indexing is not supported.", ... " Use dot notation to access properties.")); case '.' if (! ischar (s.subs)) error ("LinearModel.subsref: property name must be a character vector."); endif ## Allow normal execution if the user is calling a class method if (ismethod (this, s.subs)) [varargout{1:nargout}] = builtin ('subsref', this, [s, chain_s]); return; endif try out = this.(s.subs); catch error ("LinearModel.subsref: unknown property '%s'.", s.subs); end_try_catch endswitch if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction ## Attach a stepwise-selection history structure. Used by ## @code{stepwiselm} and by the @code{step} method to record the ## term-selection trace on the returned object; not intended for direct use. function this = setSteps (this, steps) this.Steps = steps; endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {LinearModel} {@var{mdl} =} LinearModel (@var{X}, @var{y}) ## @deftypefnx {LinearModel} {@var{mdl} =} LinearModel (@var{tbl}, @var{resp_input}) ## @deftypefnx {LinearModel} {@var{mdl} =} LinearModel (@dots{}, @var{modelspec}) ## @deftypefnx {LinearModel} {@var{mdl} =} LinearModel (@dots{}, @var{Name}, @var{Value}, @dots{}) ## ## Create a @qcode{LinearModel} class object representing a linear ## regression model. ## ## @code{@var{mdl} = LinearModel (@var{X}, @var{y})} returns a ## @code{LinearModel} object fit to the response @var{y} and the ## predictor data @var{X}. Unless removed via the @qcode{'Intercept'} ## option, the fitted model contains a constant (intercept) term and one ## linear term for every column of @var{X}. ## ## @itemize ## @item ## @var{X} is an @math{N*P} numeric or logical matrix of predictor data, ## where rows correspond to observations and columns correspond to ## variables. By default, the predictors are named @qcode{'x1'}, ## @qcode{'x2'}, @dots{}, @qcode{'xP'}. ## @item ## @var{y} is an @math{N*1} numeric or logical vector of response values, ## and must have the same number of observations (rows) as @var{X}. By ## default, the response is named @qcode{'y'}. ## @end itemize ## ## @code{@var{mdl} = LinearModel (@var{tbl}, @var{resp_input})} fits a ## model using the variables in the table (or dataset) @var{tbl} as ## predictors. @var{resp_input} selects the response and can be a ## character vector naming a variable in @var{tbl}, or a numeric vector ## the same height as @var{tbl} to use as an external response. If ## @var{resp_input} is left empty, the last variable in @var{tbl} is used ## as the response. Variables that are @code{categorical} arrays, cell ## arrays of character vectors, or logical arrays are automatically ## treated as categorical predictors. ## ## @code{@var{mdl} = LinearModel (@dots{}, @var{modelspec})} additionally ## specifies the terms of the model to fit. @var{modelspec} can be any of ## the following. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Value} @tab @var{Description} ## ## @item @qcode{'constant'} @tab Model contains only an intercept ## term. ## ## @item @qcode{'linear'} @tab Model contains an intercept and one ## term for each predictor variable. This is the default when ## @var{modelspec} is not specified. ## ## @item @qcode{'interactions'} @tab Model contains an intercept, all ## linear terms, and all pairwise products of distinct predictor ## variables (no squared terms). ## ## @item @qcode{'purequadratic'} @tab Model contains an intercept, ## all linear terms, and all squared terms. ## ## @item @qcode{'quadratic'} @tab Model contains an intercept, all ## linear terms, all pairwise products of distinct predictor variables, ## and all squared terms. ## ## @item @qcode{'full'} @tab Model contains an intercept and all ## terms up to and including the full @math{P}-way interaction of the ## predictor variables. ## ## @item terms matrix @tab A @math{T*P} or @math{T*(P+1)} numeric ## matrix, where @math{T} is the number of terms and @math{P} is the ## number of predictor variables. Each row represents one term, and the ## value in column @math{j} is the exponent to which predictor @math{j} ## is raised in that term; a row of all zeros represents the intercept. ## If a @math{T*(P+1)} matrix is supplied, its last column (representing ## the response variable) must be all zeros. ## ## @item Wilkinson formula @tab A character vector of the form ## @qcode{'y ~ terms'} describing the response and predictor terms using ## Wilkinson notation. For table input, the variable to the left of ## @qcode{'~'} is used as the response, overriding @var{resp_input}. ## @end multitable ## ## @code{@var{mdl} = LinearModel (@dots{}, @var{Name}, @var{Value}, ## @dots{})} specifies additional options using one or more ## @qcode{Name-Value} pair arguments as described below. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Intercept'} @tab A logical scalar indicating ## whether to include a constant (intercept) term in the model. Default ## is @qcode{true}. Ignored when @var{modelspec} is a Wilkinson formula. ## ## @item @qcode{'Weights'} @tab A numeric vector of nonnegative ## observation weights, with one element per observation, used to fit a ## weighted least squares model. Default is a vector of ones. ## ## @item @qcode{'Exclude'} @tab A numeric or logical vector ## specifying observations to exclude from the fit, given as row indices ## or a logical mask. Excluded observations, together with any ## observation containing a missing value, are recorded in ## @code{ObservationInfo} but do not contribute to the fit. ## ## @item @qcode{'CategoricalVars'} @tab Specifies which predictor ## variables are treated as categorical, given as a vector of column ## indices, a logical vector, or a cell array of variable names. Each ## categorical predictor with @math{L} categories is expanded into ## @math{L-1} indicator (dummy) variables, using the first category as ## the reference level. ## ## @item @qcode{'VarNames'} @tab A cell array of character vectors ## naming the predictor and response variables, in order, with the ## response variable name last. Only applies to matrix input, since ## table variables already carry their own names. ## ## @item @qcode{'ResponseVar'} @tab A character vector naming the ## response variable, used to override the response variable name that ## would otherwise be used. ## ## @item @qcode{'PredictorVars'} @tab A cell array of character ## vectors naming which variables in @var{tbl} to use as predictors. By ## default, all variables other than the response variable are used. ## ## @item @qcode{'RobustOpts'} @tab Selects ordinary least squares or ## robust regression fitting. This value can be @qcode{'off'} (default, ## ordinary least squares), @qcode{'on'} (robust fitting using the ## @qcode{'bisquare'} weighting function), the name of one of the ## weighting functions below, a function handle for a custom weighting ## function, or a scalar structure with fields @qcode{RobustWgtFun} and ## @qcode{Tune} specifying the weighting function and its tuning ## constant. Robust fitting uses Iteratively Reweighted Least Squares ## (IRLS), refitting the model with updated observation weights until the ## coefficients converge. Supported weighting function names: ## @qcode{'andrews'}, @qcode{'bisquare'}, @qcode{'cauchy'}, ## @qcode{'fair'}, @qcode{'huber'}, @qcode{'logistic'}, @qcode{'ols'}, ## @qcode{'talwar'}, @qcode{'welsch'}, each with its own default tuning ## constant. ## @end multitable ## ## @var{mdl} is returned as a @code{LinearModel} object. If ## @qcode{'RobustOpts'} is anything other than @qcode{'off'}, the returned ## model is a robust fit rather than an ordinary least squares fit. ## ## @end deftypefn function this = LinearModel (varargin) ## LinearModel (X, y, modelspec, NV...) ## LinearModel (tbl, resp_input, modelspec, NV...) if (nargin == 0) return; endif data = varargin{1}; resp_input = varargin{2}; modelspec = varargin{3}; nv_args = varargin(4:end); opts = lm_parse_nv (nv_args); is_formula = ischar (modelspec) && any (modelspec == '~'); if (! istable (data)) X_raw = double (data); n_total = size (X_raw, 1); p_raw = size (X_raw, 2); y_full = double (resp_input(:)); if (! isempty (opts.VarNames)) if (numel (opts.VarNames) != p_raw + 1) error ("LinearModel: VarNames must have %d elements.", p_raw + 1); endif pred_names_raw = opts.VarNames(1:p_raw); resp_name = opts.VarNames{end}; else pred_names_raw = arrayfun (@(k) sprintf ("x%d", k), 1:p_raw, ... 'UniformOutput', false); resp_name = 'y'; endif if (! isempty (opts.ResponseVar)) resp_name = opts.ResponseVar; endif var_names_all = [pred_names_raw, {resp_name}]; n_vars = p_raw + 1; else ## 'table' tbl = data; col_names = tbl.Properties.VariableNames; n_total = height (tbl); n_vars = width (tbl); var_names_all = col_names; if (! isempty (opts.ResponseVar)) resp_name = opts.ResponseVar; if (isnumeric (resp_input) && ! isempty (resp_input)) y_ext = double (resp_input(:)); endif elseif (ischar (resp_input) && ! isempty (resp_input)) resp_name = resp_input; elseif (isstring (resp_input) && ! isempty (resp_input)) resp_name = char (resp_input); elseif (isnumeric (resp_input) && ! isempty (resp_input)) resp_name = 'y'; y_ext = double (resp_input(:)); elseif (is_formula) tparts = strsplit (modelspec, '~'); resp_name = strtrim (tparts{1}); else resp_name = col_names{end}; endif if (opts.PredictorVarsGiven) pv = opts.PredictorVars; if (isnumeric (pv) || islogical (pv)) pred_names_raw = col_names(pv); else pred_names_raw = pv; endif else pred_names_raw = col_names(! strcmp (col_names, resp_name)); endif p_raw = numel (pred_names_raw); if (exist ('y_ext', 'var')) y_full = y_ext; else y_full = double (tbl.(resp_name)(:)); endif endif ## categorical column flags cat_logical = false (1, p_raw); if (! isempty (opts.CategoricalVars)) cv = opts.CategoricalVars; if (islogical (cv)) n_cv = min (numel (cv), p_raw); cat_logical(1:n_cv) = cv(1:n_cv); elseif (isnumeric (cv)) valid_cv = cv(cv > 0 & cv <= p_raw); cat_logical(valid_cv) = true; elseif (iscell (cv)) for i = 1:numel (cv) cat_logical(strcmp (pred_names_raw, cv{i})) = true; endfor endif endif if (istable (data)) for j = 1:p_raw col = tbl.(pred_names_raw{j}); ## A logical or string column groups its observations just as a cell ## or categorical one does, and is coded the same way. if (iscell (col) || isa (col, 'categorical') ... || islogical (col) || isa (col, 'string')) cat_logical(j) = true; endif endfor endif ## missing and excluded masks if (! istable (data)) missing_mask = any (isnan (X_raw), 2) | isnan (y_full); else missing_mask = any (ismissing (tbl), 2); endif excluded_mask = false (n_total, 1); if (! isempty (opts.Exclude)) ex = opts.Exclude(:); if (islogical (ex)) excluded_mask(1:numel (ex)) = ex; else excluded_mask(ex) = true; endif endif subset_mask = ! missing_mask & ! excluded_mask; n_obs = sum (subset_mask); if (n_obs < 1) error ("LinearModel: No observations remain after removing missing/excluded rows."); endif ## weights if (isempty (opts.Weights)) w_full = ones (n_total, 1); else w_full = double (opts.Weights(:)); endif w_sub = w_full(subset_mask); if (is_formula) ## PATH A: Wilkinson formula string if (! istable (data)) tbl_temp = array2table ([X_raw, y_full], 'VariableNames', var_names_all); tbl_sub = tbl_temp(subset_mask, :); else tbl_sub = tbl(subset_mask, :); endif [X_design_sub, ~, coef_names_raw] = parseWilkinsonFormula ( ... modelspec, 'model_matrix', tbl_sub, pred_names_raw(cat_logical)); coef_names = coef_names_raw(:)'; y_sub = y_full(subset_mask); n_coef = size (X_design_sub, 2); has_intercept = any (strcmp (coef_names, '(Intercept)')); [terms, cat_info, term_cols] = terms_from_coefnames (coef_names, ... pred_names_raw, cat_logical, data, tbl_sub); enc_names = term_cols(1:end-1); else ## PATH B: Keyword / numeric terms matrix X_num_full = zeros (n_total, p_raw); cat_str_levels = cell (1, p_raw); for j = 1:p_raw if (istable (data)) col = tbl.(pred_names_raw{j}); if (iscell (col)) ## Appearance order, so that the omitted reference level is the ## one the data shows first; see parseWilkinsonFormula for the ## formula path. [cat_str_levels{j}, ~, ic] = unique (col, 'stable'); X_num_full(:, j) = ic; elseif (isa (col, 'categorical')) cat_str_levels{j} = categories (col); [~, ic] = ismember (cellstr (col), cat_str_levels{j}); X_num_full(:, j) = ic; else X_num_full(:, j) = double (col(:)); cat_str_levels{j} = {}; endif else if (cat_logical(j)) uvals = sort (unique (X_raw(isfinite (X_raw(:,j)), j))); cat_str_levels{j} = strtrim (cellstr (num2str (uvals(:)))); [~, ic] = ismember (X_raw(:,j), uvals); X_num_full(:, j) = ic; else X_num_full(:, j) = X_raw(:, j); cat_str_levels{j} = {}; endif endif endfor X_num_sub = X_num_full(subset_mask, :); y_sub = y_full(subset_mask); ## Whether a categorical is given all its indicator columns depends ## on the intercept, which has to be settled before encoding. [X_enc_sub, enc_names, cat_info] = encode_categorical ( ... X_num_sub, cat_logical, pred_names_raw, cat_str_levels, ... modelspec_has_intercept (modelspec, opts.Intercept)); p_enc = size (X_enc_sub, 2); [terms, has_intercept, coef_names, emsg] = parse_modelspec ( ... modelspec, enc_names, p_enc, opts.Intercept); if (! isempty (emsg)) error ("LinearModel: %s", emsg); endif n_coef = rows (terms); X_design_sub = build_design (terms, X_enc_sub); term_cols = [enc_names, {''}]; endif if (isempty (opts.RobustOpts)) fit = LinearModel.lm_fit (X_design_sub, y_sub, w_sub); RobustS = []; else fit = lm_robust_fit (X_design_sub, y_sub, w_sub, ... opts.RobustOpts.WgtFun, opts.RobustOpts.Tune); RobustS.RobustWgtFun = opts.RobustOpts.WgtFun; RobustS.Tune = opts.RobustOpts.Tune; RobustS.Weights = fit.RobustWeights; endif D = lm_diagnostics (X_design_sub, y_sub, fit, w_sub); p = fit.rank_X; SSE = fit.SSE; SSR = fit.SSR; SST = fit.SST; DFE = fit.DFE; MSE = fit.MSE; RMSE = fit.RMSE; crit = LinearModel.lm_criteria (fit, has_intercept); LogLikelihood = crit.LogLikelihood; AIC = crit.AIC; AICc = crit.AICc; BIC = crit.BIC; CAIC = crit.CAIC; R2_ord = crit.Rsquared; R2_adj = crit.AdjRsquared; Fstat = crit.Fstat; Fpval = crit.Fpval; h = fit.leverage; S2_i_sub = D.S2_i; Raw_full = NaN (n_total, 1); Raw_full(subset_mask) = fit.Raw; Pearson_sub = fit.Raw / sqrt (max (MSE, eps)); Std_sub = fit.Raw ./ (RMSE .* sqrt (max (1 - h, eps))); Stu_sub = fit.Raw ./ (sqrt (max (S2_i_sub, eps)) .* sqrt (max (1 - h, eps))); Pearson_full = NaN (n_total, 1); Std_full = NaN (n_total, 1); Stu_full = NaN (n_total, 1); Pearson_full(subset_mask) = Pearson_sub; Std_full(subset_mask) = Std_sub; Stu_full(subset_mask) = Stu_sub; beta_full = fit.beta; se_full = zeros (n_coef, 1); tstat_full = NaN (n_coef, 1); pval_full = NaN (n_coef, 1); active = fit.active_cols; cov_diag = diag (fit.CovBeta); se_full(active) = sqrt (cov_diag(active)); tstat_full(active) = beta_full(active) ./ se_full(active); pval_full(active) = 2 * tcdf (-abs (tstat_full(active)), DFE); CoeffTable = table (beta_full, se_full, tstat_full, pval_full, ... 'VariableNames', {'Estimate', 'SE', 'tStat', 'pValue'}, ... 'RowNames', coef_names(:)); ResidTable = table (Raw_full, Pearson_full, Stu_full, Std_full, ... 'VariableNames', {'Raw', 'Pearson', 'Studentized', 'Standardized'}); Lev_full = zeros (n_total, 1); CD_full = NaN (n_total, 1); Dff_full = NaN (n_total, 1); S2i_full = NaN (n_total, 1); CR_full = NaN (n_total, 1); Lev_full(subset_mask) = D.Leverage; CD_full(subset_mask) = D.CooksDistance; Dff_full(subset_mask) = D.Dffits; S2i_full(subset_mask) = D.S2_i; CR_full(subset_mask) = D.CovRatio; Dfb_full = NaN (n_total, n_coef); Dfb_full(subset_mask, :) = D.Dfbetas; HatMat_pad = zeros (n_total, n_total); HatMat_pad(subset_mask, subset_mask) = D.HatMatrix; DiagTable = table (Lev_full, CD_full, Dff_full, S2i_full, CR_full, ... Dfb_full, HatMat_pad, ... 'VariableNames', {'Leverage', 'CooksDistance', 'Dffits', 'S2_i', ... 'CovRatio', 'Dfbetas', 'HatMatrix'}); dummy_names = {}; dummy_bases = {}; for ci = 1:numel (cat_info.names) base_nm = cat_info.names{ci}; levels_c = cat_info.levels{ci}; for L = 1:numel (levels_c) dummy_names{end+1} = [base_nm, '_', char(levels_c{L})]; dummy_bases{end+1} = base_nm; endfor endfor if (has_intercept) orig_idx = find (! strcmp (coef_names, '(Intercept)')); else orig_idx = 1:numel (coef_names); endif non_int = coef_names(orig_idx); disp_terms = {}; grp_cols = {}; for t = 1:numel (non_int) factors_t = strsplit (non_int{t}, ':'); for f = 1:numel (factors_t) idx = find (strcmp (dummy_names, factors_t{f}), 1); if (! isempty (idx)) factors_t{f} = dummy_bases{idx}; endif endfor nm = strjoin (factors_t, ':'); k = find (strcmp (disp_terms, nm), 1); if (isempty (k)) disp_terms{end+1} = nm; grp_cols{end+1} = orig_idx(t); else grp_cols{k}(end+1) = orig_idx(t); endif endfor ## Model formula. TERMS is expressed over the encoded design columns; the ## formula is expressed over the model's variables, so a categorical's ## indicator columns have to be folded back onto the variable they came ## from before the term names can be built. var_idx = zeros (1, p_raw); for j = 1:p_raw k = find (strcmp (var_names_all, pred_names_raw{j}), 1); if (! isempty (k)) var_idx(j) = k; endif endfor ## Indexed by the columns of TERMS, which are not the coefficient ## names: a factor appearing only inside an interaction or a power has a ## column without ever being a coefficient. [enc2raw, col_pow] = encodednames_to_row (term_cols(1:end-1), ... pred_names_raw, cat_info); terms_var = variable_level_terms (terms(:, 1:end-1), enc2raw, var_idx, ... n_vars, col_pow); ## A response passed apart from the table is still a variable of the ## model, and the formula is written over every one of them, so it takes ## a column of the terms matrix like any other. form_vars = var_names_all(:)'; form_terms = terms_var; if (! any (strcmp (form_vars, resp_name))) form_vars{end+1} = resp_name; form_terms(:, end+1) = 0; endif FormulaObj = LinearFormula (form_terms, form_vars, ... 'ResponseName', resp_name); ObsInfo = table (w_full, excluded_mask, missing_mask, subset_mask, ... 'VariableNames', {'Weights', 'Excluded', 'Missing', 'Subset'}); if (! istable (data)) VarsTable = array2table ([X_raw, y_full], 'VariableNames', var_names_all); else VarsTable = tbl; endif ## A variable the model does not actually use is not a predictor of it: ## a table may carry columns the formula never mentions, and a terms ## matrix may zero a predictor out. MATLAB drops these from ## PredictorNames and NumPredictors, and marks VariableInfo.InModel ## false, keeping them in VariableNames. pred_in_model = false (1, p_raw); for j = 1:p_raw ci = []; if (! isempty (cat_info) && isfield (cat_info, 'names') ... && ! isempty (cat_info.names)) ci = find (strcmp (cat_info.names, pred_names_raw{j}), 1); endif if (isempty (ci)) ecols = find (strcmp (enc_names, pred_names_raw{j})); else levels_j = cat_info.levels{ci}; ecols = []; for L = 2:numel (levels_j) lvl_name = sprintf ("%s_%s", pred_names_raw{j}, char (levels_j{L})); ecols = [ecols, find(strcmp (enc_names, lvl_name))]; endfor endif pred_in_model(j) = ! isempty (ecols) ... && any (any (terms(:, ecols) != 0)); endfor pred_names_used = pred_names_raw(pred_in_model); nv_total = numel (var_names_all); vi_class = cell (nv_total, 1); vi_range = cell (nv_total, 1); vi_inmodel = false (nv_total, 1); vi_iscat = false (nv_total, 1); for j = 1:nv_total vname = var_names_all{j}; is_resp_var = strcmp (vname, resp_name); j_pred = find (strcmp (pred_names_raw, vname), 1); if (! istable (data)) if (! is_resp_var && ! isempty (j_pred)) col_d = X_raw(:, j_pred); vi_iscat(j) = cat_logical(j_pred); else col_d = y_full; endif vi_class{j} = 'double'; fv = col_d(subset_mask & isfinite (col_d)); vi_range{j} = ifelse (isempty (fv), [NaN, NaN], [min(fv), max(fv)]); else col_d = tbl.(vname); col_d = col_d(subset_mask); [vi_class{j}, vi_range{j}] = variable_class_and_range (col_d); if (! is_resp_var && ! isempty (j_pred)) vi_iscat(j) = cat_logical(j_pred); endif endif if (! is_resp_var && ! isempty (j_pred) && pred_in_model(j_pred)) vi_inmodel(j) = true; endif endfor VarInfo = table (vi_class, vi_range, vi_inmodel, vi_iscat, ... 'VariableNames', {'Class', 'Range', 'InModel', 'IsCategorical'}, ... 'RowNames', var_names_all(:)); this.Coefficients = CoeffTable; this.CoefficientCovariance = fit.CovBeta; this.CoefficientNames = coef_names; this.NumCoefficients = n_coef; this.NumEstimatedCoefficients = p; this.DFE = DFE; this.Diagnostics = DiagTable; this.LogLikelihood = LogLikelihood; this.ModelCriterion = struct ('AIC', AIC, 'AICc', AICc, ... 'BIC', BIC, 'CAIC', CAIC); if (isnan (Fstat)) NullModelName = NaN; else NullModelName = 'constant'; endif this.ModelFitVsNullModel = struct ('Fstat', Fstat, ... 'Pvalue', Fpval, ... 'NullModel', NullModelName); this.MSE = MSE; this.Residuals = ResidTable; this.RMSE = RMSE; this.Rsquared = struct ('Ordinary', R2_ord, 'Adjusted', R2_adj); this.SSE = SSE; this.SSR = SSR; this.SST = SST; this.Robust = RobustS; this.Steps = []; this.Formula = FormulaObj; this.NumObservations = n_obs; this.NumPredictors = numel (pred_names_used); this.NumVariables = n_vars; this.ObservationInfo = ObsInfo; this.ObservationNames = {}; ## MATLAB returns these two as columns, and CoefficientNames as a row. this.PredictorNames = pred_names_used(:); this.ResponseName = resp_name; this.VariableInfo = VarInfo; this.VariableNames = var_names_all(:); this.Variables = VarsTable; this.DesignMatrix = X_design_sub; this.ActiveCols = fit.active_cols; this.HasIntercept = has_intercept; this.ResponseVector = y_full; this.WeightVector = w_full; this.SubsetMask = subset_mask; this.TermsMatrix = terms; this.CatLevelInfo = cat_info; this.EncPredictorNames = enc_names; this.PredictorNamesRaw = pred_names_raw(:); this.EffectContrasts = lm_effects_contrasts (this); this.InteractionContrasts = lm_interaction_contrasts (this); this.TermGroups = struct ('Name', disp_terms, 'Cols', grp_cols); this.OrigOpts = opts; if (! is_formula) this.EncodedPredMatrix = X_enc_sub; endif ## The fitted value of an observation is the model's prediction at its ## predictors, whether or not the observation was used in the fit, so ## excluded rows and rows missing only the response carry a value. It ## is computed through PREDICT, which cannot then disagree with it, and ## which leaves NaN wherever a predictor itself is missing. this.Fitted = predict (this, this.Variables); endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {@var{ypred} =} predict (@var{mdl}, @var{Xnew}) ## @deftypefnx {LinearModel} {@var{ypred} =} predict (@var{mdl}) ## @deftypefnx {LinearModel} {[@var{ypred}, @var{yci}] =} predict (@var{mdl}, @var{Xnew}) ## @deftypefnx {LinearModel} {[@var{ypred}, @var{yci}] =} predict (@var{mdl}, @var{Xnew}, @var{Name}, @var{Value}) ## ## Predict responses from a fitted linear regression model. ## ## @code{@var{ypred} = predict (@var{mdl}, @var{Xnew})} returns the fitted ## response values at the new predictor locations in @var{Xnew}. @var{Xnew} ## can be a numeric matrix with one column per predictor in the same order ## as the training data, or a table whose column names match ## @code{@var{mdl}.PredictorNames}. Rows containing @code{NaN} are returned ## as @code{NaN} without error. ## ## @code{@var{ypred} = predict (@var{mdl})} omits @var{Xnew} and returns ## fitted values for the original training observations in their original ## row order. Rows that were excluded or contained missing values are ## returned as @code{NaN}. The result is identical to ## @code{@var{mdl}.Fitted}. ## ## @code{[@var{ypred}, @var{yci}] = predict (@dots{})} also returns ## @var{yci}, an @math{n}-by-2 matrix of confidence bounds where column 1 is ## the lower bound and column 2 is the upper bound. By default these are ## 95% pointwise confidence intervals on the mean response. ## ## Name-Value pair arguments: ## ## @multitable @columnfractions 0.2 0.78 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab Significance level for the confidence ## interval, specified as a scalar in @math{[0,1]}. The interval has ## coverage @math{100(1-\alpha)\%}. Default is @code{0.05}, giving a 95% ## interval. ## ## @item @qcode{'Prediction'} @tab Type of interval to compute. ## @code{"curve"} (default) gives a confidence interval on the mean response ## @math{f(x)}. @code{"observation"} gives a wider prediction interval for ## a single future observation @math{y = f(x) + \varepsilon}, which accounts ## for both estimation uncertainty and irreducible noise; it adds ## @code{@var{mdl}.MSE} to the variance before computing the half-width. ## ## @item @qcode{'Simultaneous'} @tab Logical flag controlling whether ## the bounds are simultaneous or pointwise. When @code{true}, ## Scheff@'{e}'s method is used so the entire predicted curve lies within ## the band with @math{100(1-\alpha)\%} confidence; these bands are always ## wider than pointwise ones. Default is @code{false}. ## @end multitable ## ## @end deftypefn function [ypred, yci] = predict (mdl, Xnew, varargin) alpha = 0.05; pred_obs = false; simultan = false; i = 1; while (i <= numel (varargin)) if (strcmpi (varargin{i}, 'Alpha')) alpha = varargin{i+1}; if (! isscalar (alpha) || ! isnumeric (alpha) || alpha < 0 || alpha > 1) error ("predict: Alpha must be a scalar in [0,1]."); endif i += 2; elseif (strcmpi (varargin{i}, 'Prediction')) pred_str = lower (char (varargin{i+1})); if (! any (strcmp (pred_str, {'curve', 'observation'}))) error ("predict: Prediction must be 'curve' or 'observation'."); endif pred_obs = strcmp (pred_str, 'observation'); i += 2; elseif (strcmpi (varargin{i}, 'Simultaneous')) simultan = logical (varargin{i+1}); i += 2; else error ("predict: unknown option '%s'.", varargin{i}); endif endwhile if (nargin < 2 || isempty (Xnew)) Xnew = mdl.Variables; endif pred_names = mdl.PredictorNames; p_raw = mdl.NumPredictors; if (istable (Xnew)) n_new = height (Xnew); X_raw = zeros (n_new, p_raw); for j = 1:p_raw if (! ismember (pred_names{j}, Xnew.Properties.VariableNames)) error ("predict: Xnew table is missing predictor '%s'.", pred_names{j}); endif col = Xnew.(pred_names{j}); if (iscell (col)) cat_idx = []; if (! isempty (mdl.CatLevelInfo.names)) cat_idx = find (strcmp (mdl.CatLevelInfo.names, pred_names{j})); endif if (! isempty (cat_idx)) levels_j = mdl.CatLevelInfo.levels{cat_idx}; codes = zeros (n_new, 1); for k = 1:numel (levels_j) codes(strcmp (col, levels_j{k})) = k; endfor X_raw(:, j) = codes; endif else X_raw(:, j) = double (col); endif endfor else X_raw = double (Xnew); if (columns (X_raw) != p_raw) error ("predict: Xnew must have %d columns.", p_raw); endif n_new = rows (X_raw); endif nan_rows = any (isnan (X_raw), 2); X_enc_new = reencode_predictors (X_raw, pred_names, mdl.CatLevelInfo, mdl.EncPredictorNames); X_design_new = build_design (mdl.TermsMatrix, X_enc_new); beta = mdl.Coefficients.Estimate; ypred = X_design_new * beta; ypred(nan_rows) = NaN; if (nargout > 1) CovB = mdl.CoefficientCovariance; var_cv = sum ((X_design_new * CovB) .* X_design_new, 2); if (pred_obs) var_ci = var_cv + mdl.MSE; else var_ci = var_cv; endif p_est = mdl.NumEstimatedCoefficients; if (simultan) mult = sqrt (p_est * finv (1 - alpha, p_est, mdl.DFE)); else mult = tinv (1 - alpha / 2, mdl.DFE); endif hw = mult * sqrt (max (var_ci, 0)); yci = [ypred - hw, ypred + hw]; yci(nan_rows,:) = NaN; endif endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {@var{ysim} =} random (@var{mdl}, @var{Xnew}) ## ## Simulate responses with random noise from a fitted linear regression ## model. ## ## @code{@var{ysim} = random (@var{mdl}, @var{Xnew})} computes the fitted ## response at each row of @var{Xnew} and then adds independent Gaussian ## noise to each value. The noise is drawn from @math{N(0, \sigma^2)} where ## @math{\sigma^2} is the estimated error variance @code{@var{mdl}.MSE} ## (mean squared error of the fit). The result is a column vector of the ## same length as the number of rows in @var{Xnew}. ## ## @var{Xnew} is required and must be non-empty. It can be a numeric ## matrix with one column per predictor in the same order as the training ## data, or a table whose column names match ## @code{@var{mdl}.PredictorNames}. Unlike @code{predict}, there is no ## no-argument form; the predictor locations must always be supplied ## explicitly. ## ## Because the added noise is drawn freshly on every call, two calls with ## the same @var{Xnew} will generally produce different output. To get ## reproducible results, set the random seed with @code{rand ('state', s)} ## before calling @code{random}. ## ## For deterministic predictions without noise, use @code{predict} or ## @code{feval}. @code{predict} also provides confidence intervals on the ## mean response. ## ## @end deftypefn function ysim = random (mdl, Xnew, varargin) if (nargin < 2) error ("random: Not enough input arguments."); endif if (nargin > 2) error ("random: Too many input arguments."); endif if (isempty (Xnew)) error ("random: Xnew must have %d columns.", mdl.NumPredictors); endif ypred = predict (mdl, Xnew); ysim = ypred + sqrt (mdl.MSE) .* randn (numel (ypred), 1); endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {@var{ypred} =} feval (@var{mdl}, @var{X}) ## @deftypefnx {LinearModel} {@var{ypred} =} feval (@var{mdl}, @var{x1}, @var{x2}, @dots{}, @var{xp}) ## ## Predict responses of a fitted linear regression model using separate ## predictor inputs. ## ## @code{@var{ypred} = feval (@var{mdl}, @var{X})} accepts a single ## numeric matrix @var{X} with one column per predictor in the same order ## as the training data, or a table whose column names match ## @code{@var{mdl}.PredictorNames}. The output is an @math{n}-by-1 column ## vector. Rows that contain @code{NaN} in any predictor column are ## returned as @code{NaN}. ## ## @code{@var{ypred} = feval (@var{mdl}, @var{x1}, @var{x2}, @dots{}, ## @var{xp})} accepts exactly @code{@var{mdl}.NumPredictors} separate ## arguments, one per predictor variable. All non-scalar arguments must ## have the same size; a scalar argument is broadcast to that size ## automatically. The output shape follows the shape of the non-scalar ## inputs: column vector inputs give a column vector output, row vector ## inputs give a row vector output, and all-scalar inputs give a scalar. ## This form is convenient when predictor data is already stored in separate ## vectors rather than a combined matrix. ## ## @code{feval} gives the same numerical predictions as @code{predict} but ## does not support confidence intervals. Use @code{predict} when you also ## need bounds on the response. Because a @code{LinearModel} object behaves ## like a function through @code{feval}, it can be passed directly to ## routines that accept a function handle, such as @code{fminsearch} or ## @code{integral}. ## ## @end deftypefn function ypred = feval (mdl, varargin) p_raw = mdl.NumPredictors; n_extra = nargin - 1; if (n_extra < 1) error ("feval: Not enough input arguments."); endif if (n_extra == 1) Xnew = varargin{1}; if (istable (Xnew)) for j = 1:p_raw if (! ismember (mdl.PredictorNames{j}, Xnew.Properties.VariableNames)) error (strcat ("feval: X does not contain one or more", ... " predictor variables needed for this model.")); endif endfor else if (columns (double (Xnew)) != p_raw) error ("feval: Predictor data matrix must have %d columns.", p_raw); endif endif ypred = predict (mdl, Xnew); elseif (n_extra == p_raw) for i = 1:n_extra if (ischar (varargin{i}) || iscategorical (varargin{i})) if (iscategorical (varargin{i})) lvl_str = char (varargin{i}); else lvl_str = varargin{i}; endif ci = []; if (! isempty (mdl.CatLevelInfo.names)) ci = find (strcmp (mdl.CatLevelInfo.names, mdl.PredictorNames{i})); endif if (isempty (ci)) error ("feval: predictor '%s' is not categorical.", mdl.PredictorNames{i}); endif levels_i = mdl.CatLevelInfo.levels{ci}; code = find (strcmp (levels_i, lvl_str), 1); if (isempty (code)) code = NaN; endif varargin{i} = code; endif endfor ref_size = []; for i = 1:n_extra if (! isscalar (varargin{i})) s_i = size (varargin{i}); if (isempty (ref_size)) ref_size = s_i; elseif (! isequal (s_i, ref_size)) error ("feval: All input arguments must be the same size."); endif endif endfor if (isempty (ref_size)) ref_size = [1, 1]; endif n_pts = prod (ref_size); Xmat = zeros (n_pts, p_raw); for i = 1:n_extra ai = varargin{i}; if (isscalar (ai)) Xmat(:, i) = ai; else Xmat(:, i) = ai(:); endif endfor ypred = reshape (predict (mdl, Xmat), ref_size); else error (strcat ("feval: Incorrect number of input arguments. You", ... " must provide either %d separate predictor", ... " variable arguments, or one predictor matrix with", ... " %d columns."), p_raw, p_raw); endif endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {@var{ci} =} coefCI (@var{mdl}) ## @deftypefnx {LinearModel} {@var{ci} =} coefCI (@var{mdl}, @var{alpha}) ## ## Confidence intervals for the coefficient estimates of a fitted linear ## regression model. ## ## @code{@var{ci} = coefCI (@var{mdl})} returns 95% confidence intervals ## for every coefficient in @var{mdl} using a default significance level of ## @code{0.05}. ## ## @code{@var{ci} = coefCI (@var{mdl}, @var{alpha})} uses the significance ## level @var{alpha}, a scalar in @math{[0, 1]}. The resulting intervals ## have coverage @math{100(1-\alpha)\%}. Setting @var{alpha} to @code{0} ## produces intervals of infinite width; setting it to @code{1} collapses ## each interval to the corresponding point estimate. ## ## The output @var{ci} is a @math{k}-by-2 numeric matrix where ## @math{k = } @code{@var{mdl}.NumCoefficients}. Row @math{j} contains ## the interval for the @math{j}-th coefficient, whose name is stored in ## @code{@var{mdl}.CoefficientNames@{j@}}. Column 1 is the lower bound and ## column 2 is the upper bound. The midpoint of each interval equals the ## corresponding point estimate in @code{@var{mdl}.Coefficients.Estimate}. ## ## Intervals use the Wald method: ## @math{b_j \pm t_{(1-\alpha/2,\,\mathrm{DFE})}\,\mathrm{SE}(b_j)}, ## where @math{b_j} is the coefficient estimate, @math{\mathrm{SE}(b_j)} is ## its standard error from @code{@var{mdl}.Coefficients.SE}, and the ## critical value is the @math{1-\alpha/2} quantile of the ## @math{t}-distribution with @code{@var{mdl}.DFE} degrees of freedom. ## In rank-deficient models, aliased coefficients have ## @math{\mathrm{SE} = 0} and their row in @var{ci} is @code{[0, 0]}. ## ## @end deftypefn function ci = coefCI (mdl, alpha) if (nargin > 2) error ("coefCI: Too many input arguments."); endif if (nargin < 2) alpha = 0.05; endif if (! isscalar (alpha)) error (strcat ("coefCI: Invalid argument at position 2.", ... " Value must be a scalar.")); endif if (! (alpha >= 0)) error (strcat ("coefCI: Invalid argument at position 2.", ... " Value must be greater than or equal to 0.")); endif if (alpha > 1) error (strcat ("coefCI: Invalid argument at position 2.", ... " Value must be less than or equal to 1.")); endif t = tinv (1 - alpha / 2, mdl.DFE); b = mdl.Coefficients.Estimate; se = mdl.Coefficients.SE; ci = [b - t .* se, b + t .* se]; endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {@var{p} =} coefTest (@var{mdl}) ## @deftypefnx {LinearModel} {@var{p} =} coefTest (@var{mdl}, @var{H}) ## @deftypefnx {LinearModel} {@var{p} =} coefTest (@var{mdl}, @var{H}, @var{C}) ## @deftypefnx {LinearModel} {[@var{p}, @var{F}] =} coefTest (@dots{}) ## @deftypefnx {LinearModel} {[@var{p}, @var{F}, @var{r}] =} coefTest (@dots{}) ## ## Linear hypothesis test on the coefficients of a fitted linear regression ## model. ## ## @code{coefTest} tests whether one or more linear combinations of the ## fitted coefficients equal specified constants. Each linear combination ## is encoded as a row of the contrast matrix @var{H}, and the right-hand ## side is given by @var{C}. ## ## @code{@var{p} = coefTest (@var{mdl})} performs the overall model F-test: ## it tests the joint null hypothesis that every coefficient except the ## intercept is zero. The returned p-value matches the F-statistic line ## printed at the bottom of the model display. ## ## @code{@var{p} = coefTest (@var{mdl}, @var{H})} tests the null hypothesis ## @math{H \beta = 0}, where @math{\beta} is the full coefficient vector ## of length @math{k = } @code{@var{mdl}.NumCoefficients}. @var{H} must be ## a full-rank numeric matrix with @math{k} columns; each row specifies one ## linear constraint. To test a single coefficient, use a row vector with a ## @code{1} in that coefficient's position and zeros elsewhere; the ## resulting F-statistic equals the square of the corresponding t-statistic ## in @code{@var{mdl}.Coefficients}. To test a categorical predictor that ## expands to multiple indicator columns, include one row per indicator in ## @var{H}. ## ## @code{@var{p} = coefTest (@var{mdl}, @var{H}, @var{C})} tests ## @math{H \beta = C} instead of zero. @var{C} must be a numeric vector ## with the same number of elements as rows of @var{H}; both row and column ## vectors are accepted. ## ## The second output @var{F} is the value of the F-statistic: ## @math{F = (H\hat{\beta} - C)^\prime (H V H^\prime)^{-1} ## (H\hat{\beta} - C) / r}, where @math{V} is ## @code{@var{mdl}.CoefficientCovariance} and @math{r} is the number of ## rows of @var{H}. The third output @var{r} is that numerator degrees of ## freedom; the denominator degrees of freedom is @code{@var{mdl}.DFE}. ## Under the null hypothesis @math{F} follows an @math{F(r, \mathrm{DFE})} ## distribution and the p-value is the upper-tail probability. When ## @var{H} is rank-deficient but contains no @code{NaN}, both @var{p} and ## @var{F} are returned as @code{NaN} without an error. ## ## @end deftypefn function [p, F, r] = coefTest (mdl, varargin) if (nargout > 3) error ("coefTest: Too many output arguments."); endif if (numel (varargin) > 2) error ("coefTest: Too many input arguments."); endif k = mdl.NumCoefficients; if (numel (varargin) >= 1 && ! isempty (varargin{1})) H = varargin{1}; if (! isnumeric (H)) error ("coefTest: H must be a %d-by-%d numeric matrix.", size (H, 1), k); endif if (size (H, 2) != k) error ("coefTest: H must be a %d-by-%d numeric matrix.", size (H, 1), k); endif if (any (any (isnan (H)))) error (strcat ("coefTest: H is not full rank and hypotheses", ... " are not consistent.")); endif r = size (H, 1); if (numel (varargin) == 2) C = varargin{2}; if (! isnumeric (C)) error ("coefTest: C must be a numeric vector."); endif C = C(:); if (numel (C) != r) error ("coefTest: H must be a %d-by-%d numeric matrix.", numel (C), k); endif else C = zeros (r, 1); endif else if (mdl.HasIntercept && k > 1) H = [zeros(k-1, 1), eye(k-1)]; r = k - 1; else H = eye (k); r = k; endif C = zeros (r, 1); endif b = mdl.Coefficients.Estimate; V = mdl.CoefficientCovariance; HVH = H * V * H'; Hb_c = H * b - C; if (rcond (HVH) < eps (class (HVH))) F = NaN; p = NaN; else F = (Hb_c' * (HVH \ Hb_c)) / r; p = betainc (mdl.DFE / (mdl.DFE + r * F), mdl.DFE / 2, r / 2); endif endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {@var{p} =} dwtest (@var{mdl}) ## @deftypefnx {LinearModel} {@var{p} =} dwtest (@var{mdl}, @var{method}) ## @deftypefnx {LinearModel} {@var{p} =} dwtest (@var{mdl}, @var{method}, @var{tail}) ## @deftypefnx {LinearModel} {[@var{p}, @var{DW}] =} dwtest (@dots{}) ## ## Durbin-Watson test for serial autocorrelation of linear regression ## residuals. ## ## @code{dwtest} checks whether the raw residuals of @var{mdl} are ## correlated with their immediate neighbours in observation order, which ## would violate the independence assumption of ordinary least squares. ## The null hypothesis is that there is no autocorrelation. A small ## p-value gives evidence against this and suggests that the residuals are ## not independent. This test is most meaningful when the observations ## have a natural ordering, such as a time series. ## ## The test is based on the Durbin-Watson statistic ## @math{DW = \sum_{i=1}^{n-1}(e_{i+1}-e_i)^2 / \sum_{i=1}^{n}e_i^2}, ## where @math{e_i} are the raw residuals of the active (non-excluded) ## observations. The statistic always lies in @math{[0, 4]}: values near ## @math{2} indicate no autocorrelation, values well below @math{2} ## indicate positive autocorrelation (adjacent residuals tend to have the ## same sign), and values well above @math{2} indicate negative ## autocorrelation (adjacent residuals tend to alternate in sign). ## ## @var{method} controls how the p-value is computed and defaults to ## @qcode{'exact'}. @qcode{'exact'} uses the eigenvalues of the ## projected differencing matrix together with Imhof's numerical ## integration to obtain a precise p-value; this is slower but accurate ## for any sample size. @qcode{'approximate'} uses a normal approximation ## based on the first two moments of the DW distribution under the null; ## this is faster and adequate for large samples but less reliable for ## small ones. The argument is case-insensitive. ## ## @var{tail} selects the alternative hypothesis and defaults to ## @qcode{'both'}. @qcode{'right'} tests for positive autocorrelation ## (@math{DW < 2}), @qcode{'left'} tests for negative autocorrelation ## (@math{DW > 2}), and @qcode{'both'} tests for autocorrelation in ## either direction. The one-sided p-values always satisfy ## @math{p_{\mathrm{right}} + p_{\mathrm{left}} = 1}, and the two-sided ## p-value equals @math{2\min(p_{\mathrm{right}}, p_{\mathrm{left}})}. ## ## The second output @var{DW} is the value of the Durbin-Watson statistic ## itself; it does not depend on @var{method} or @var{tail}. ## ## @end deftypefn function [p, DW] = dwtest (mdl, varargin) if (nargout > 2) error ("dwtest: Too many output arguments."); endif if (numel (varargin) > 2) error ("dwtest: Too many input arguments."); endif method = 'exact'; tail = 'both'; if (numel (varargin) >= 1) method = varargin{1}; endif if (numel (varargin) == 2) tail = varargin{2}; endif if (! ischar (method) || ! ismember (lower (method), {'exact', 'approximate'})) error ("dwtest: The METHOD argument must be 'approximate' or 'exact'."); endif method = lower (method); tail = lower (tail); subset = logical (mdl.ObservationInfo.Subset); r = mdl.Residuals.Raw(subset); r = r(:); [p, DW] = dwtest (r, mdl.DesignMatrix, 'Method', method, 'Tail', tail); endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {@var{NewMdl} =} addTerms (@var{mdl}, @var{terms}) ## ## Add terms to a fitted linear regression model. ## ## @code{addTerms} returns a new @code{LinearModel} refitted on the same ## data and settings as @var{mdl} with the specified @var{terms} appended ## to the model formula. The original model @var{mdl} is never modified; ## all settings including observation weights, excluded rows, and ## categorical variable encodings are carried over automatically. To ## update a model in place, reassign the result: ## @code{@var{mdl} = addTerms (@var{mdl}, @var{terms})}. ## ## @var{terms} may be a character vector in Wilkinson notation. Use ## @code{'x1'} for a main effect, @code{'x1:x2'} for a two-way ## interaction, @code{'x1*x2'} to add both main effects and their ## interaction in one step, @code{'x1 + x2^2'} to add several terms at ## once, or @code{'1'} to add an intercept to a no-intercept model. A ## bare power term @code{'x1^2'} adds @code{x1} together with ## @code{x1^2} (and any intermediate powers), matching the Wilkinson ## hierarchy convention; power notation used inside an interaction, e.g. ## @code{'x1:x2^2'}, adds only that exact interaction term. All ## variable names must match entries in @code{@var{mdl}.PredictorNames}. ## ## @var{terms} may also be a numeric matrix of size @var{t}-by-@var{v}, ## where @var{t} is the number of terms to add and @var{v} equals ## @code{@var{mdl}.NumVariables}. Entry @code{T(i,j)} is the exponent of ## variable @var{j} in term @var{i}. For example, in a model with ## variables @code{x1}, @code{x2}, @code{y}: @code{[0 0 0]} is the ## intercept, @code{[0 1 0]} is @code{x2}, @code{[1 1 0]} is ## @code{x1:x2}, and @code{[2 0 0]} is @code{x1^2}. The last column ## (response) is always zero. A matrix with @code{@var{mdl}.NumPredictors} ## columns is also accepted and is automatically padded with a trailing ## zero column for the response. ## ## Terms that are already present in @var{mdl} are silently skipped. If ## every specified term already exists, a warning is issued and @var{mdl} ## is returned unchanged. For a categorical predictor, @code{addTerms} ## adds the full group of indicator variables for that predictor in one ## step rather than adding individual indicator columns. ## ## @end deftypefn function NewMdl = addTerms (mdl, terms) if (nargin < 2) error ("addTerms: Not enough input arguments."); endif if (nargin > 2) error ("addTerms: Too many input arguments."); endif nv = mdl.NumVariables; ## The candidates, not the chosen: a term may name a predictor the ## current model does not use, and nv - 1 counts them all. pred = mdl.PredictorNamesRaw; if (isnumeric (terms) || islogical (terms)) T = double (terms); if (isempty (T)) error ("addTerms: Terms matrix must have %d columns.", nv); endif if (columns (T) == nv - 1) T = [T, zeros(rows (T), 1)]; endif if (columns (T) != nv) error ("addTerms: Terms matrix must have %d columns.", nv); endif elseif (ischar (terms) || isstring (terms)) terms_str = strtrim (char (terms)); T = zeros (0, nv); plus_tokens = strsplit (terms_str, '+'); for ti = 1:numel (plus_tokens) tok = strtrim (plus_tokens{ti}); if (isempty (tok)); continue; endif if (! isempty (strfind (tok, '*'))) star_parts = cellfun (@strtrim, strsplit (tok, '*'), ... 'UniformOutput', false); n_sp = numel (star_parts); colon_toks = {}; for mask = 1:(2^n_sp - 1) sub = {}; for bit = 1:n_sp if (bitand (mask, 2^(bit-1))) sub{end+1} = star_parts{bit}; endif endfor colon_toks{end+1} = strjoin (sub, ':'); endfor else colon_toks = {tok}; endif for ci = 1:numel (colon_toks) ctok = strtrim (colon_toks{ci}); if (strcmp (ctok, '1')) T = [T; zeros(1, nv)]; else parts = cellfun (@strtrim, strsplit (ctok, ':'), ... 'UniformOutput', false); if (numel (parts) == 1) part = parts{1}; hat = strfind (part, '^'); if (isempty (hat)) vname = part; exp = 1; else vname = strtrim (part(1:hat(1)-1)); exp = str2double (strtrim (part(hat(1)+1:end))); endif idx = find (strcmp (pred, vname)); if (isempty (idx)) error ("addTerms: Unrecognized variable: '%s'.", vname); endif for k = 1:exp row = zeros (1, nv); row(idx(1)) = k; T = [T; row]; endfor else row = zeros (1, nv); for pi = 1:numel (parts) part = parts{pi}; hat = strfind (part, '^'); if (isempty (hat)) vname = part; exp = 1; else vname = strtrim (part(1:hat(1)-1)); exp = str2double (strtrim (part(hat(1)+1:end))); endif idx = find (strcmp (pred, vname)); if (isempty (idx)) error ("addTerms: Unrecognized variable: '%s'.", vname); endif row(idx(1)) = row(idx(1)) + exp; endfor T = [T; row]; endif endif endfor endfor else error (strcat ("addTerms: Model update specification must be a", ... " model formula character vector or string scalar,", ... " or a terms matrix")); endif cat_info = mdl.CatLevelInfo; ename = mdl.EncPredictorNames; n_pred = nv - 1; ## Every candidate predictor's encoded column name(s), whether or not ## it is part of the model yet: a plain predictor occupies one column, ## a categorical one column per non-reference level, named exactly as ## reencode_predictors expects to find them. target_names = cell (n_pred, 1); for j = 1:n_pred ci = []; if (! isempty (cat_info) && isfield (cat_info, 'names') ... && ! isempty (cat_info.names)) ci = find (strcmp (cat_info.names, pred{j})); endif if (isempty (ci)) target_names{j} = pred(j); else levels_j = cat_info.levels{ci}; lvl_names = cell (1, numel (levels_j) - 1); for L = 2:numel (levels_j) lvl_names{L-1} = sprintf ("%s_%s", pred{j}, char (levels_j{L})); endfor target_names{j} = lvl_names; endif endfor target_enc = [target_names{:}]; nc_full = numel (target_enc) + 1; ## Re-slot the model's current encoded terms into that full space, so ## a predictor with no columns yet simply stays all zero. existing = zeros (rows (mdl.TermsMatrix), nc_full); existing(:, end) = mdl.TermsMatrix(:, end); for c = 1:numel (ename) col = find (strcmp (target_enc, ename{c}), 1); existing(:, col) = mdl.TermsMatrix(:, c); endfor ## Expand each requested raw-predictor row into that same full space. new_rows = zeros (0, nc_full); for i = 1:rows (T) orig_row = T(i, 1:n_pred); any_cat = false; cat_rows = zeros (0, nc_full); cont_row = zeros (1, nc_full); col_off = 0; for j = 1:n_pred n_cols = numel (target_names{j}); if (orig_row(j) != 0) if (n_cols > 1) any_cat = true; for k = 1:n_cols r = zeros (1, nc_full); r(col_off + k) = 1; cat_rows = [cat_rows; r]; endfor else cont_row(col_off + 1) = orig_row(j); endif endif col_off = col_off + n_cols; endfor if (any_cat) for k = 1:rows (cat_rows) new_rows = [new_rows; cat_rows(k,:) + cont_row]; endfor else new_rows = [new_rows; cont_row]; endif endfor is_new = false (rows (new_rows), 1); for i = 1:rows (new_rows) is_new(i) = ! any (all (existing == new_rows(i,:), 2)); endfor new_rows = new_rows(is_new, :); if (isempty (new_rows)) warning ("addTerms: There are no new terms among the terms you specified."); NewMdl = mdl; return; endif combined = [existing; new_rows]; int_mask = all (combined(:, 1:end-1) == 0, 2); body = combined(! int_mask, :); n_nonzero = sum (body(:, 1:end-1) != 0, 2); degree = sum (body(:, 1:end-1), 2); tier = zeros (rows (body), 1); tier(n_nonzero == 1 & degree == 1) = 1; tier(n_nonzero == 2) = 2; tier(n_nonzero == 1 & degree > 1) = 3; bitmask = zeros (rows (body), 1); for i = 1:rows (body) bitmask(i) = sum (2 .^ (find (body(i, 1:end-1)) - 1)); endfor [~, order] = sortrows ([tier, bitmask]); combined = [combined(int_mask, :); body(order, :)]; NewMdl = lm_refit (mdl, combined); endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {@var{NewMdl} =} removeTerms (@var{mdl}, @var{terms}) ## ## Remove terms from a fitted linear regression model. ## ## @code{removeTerms} returns a new @code{LinearModel} refitted on the same ## data and settings as @var{mdl}, but with the specified @var{terms} ## dropped from the model formula. The original model @var{mdl} is never ## modified; all settings including observation weights, excluded rows, and ## categorical variable encodings are carried over automatically. To ## update a model in place, reassign the result: ## @code{@var{mdl} = removeTerms (@var{mdl}, @var{terms})}. ## ## @var{terms} may be a character vector in Wilkinson notation. Use ## @code{'x2'} to remove a main effect, @code{'x1:x2'} to remove an ## interaction, @code{'1'} to remove the intercept, or @code{'x1 + x2^2'} ## to remove several terms at once. A bare power term @code{'x1^2'} ## removes @code{x1} together with @code{x1^2} (and any intermediate ## powers), matching the Wilkinson hierarchy convention; power notation ## used inside an interaction, e.g. @code{'x1:x2^2'}, removes only that ## exact interaction term. The star operator @code{'x1*x2'} removes the ## main effects @code{x1} and @code{x2} together with their interaction ## @code{x1:x2} in a single call, following the same expansion rule as ## @code{addTerms}. All variable names must match entries in ## @code{@var{mdl}.PredictorNames}. ## ## @var{terms} may also be a numeric matrix of size @var{t}-by-@var{v}, ## where @var{t} is the number of terms to remove and @var{v} equals ## @code{@var{mdl}.NumVariables}. Entry @code{T(i,j)} is the exponent of ## variable @var{j} in term @var{i}. For example, in a model with ## variables @code{x1}, @code{x2}, @code{y}: @code{[0 0 0]} is the ## intercept, @code{[0 1 0]} is @code{x2}, @code{[1 1 0]} is ## @code{x1:x2}, and @code{[2 0 0]} is @code{x1^2}. A matrix with ## @code{@var{mdl}.NumPredictors} columns is also accepted and is ## automatically padded with a trailing zero column for the response. ## ## Terms specified but absent from @var{mdl} are silently skipped. A ## warning is issued and @var{mdl} is returned unchanged only when every ## single specified term is absent from the model. For a categorical ## predictor, @code{removeTerms} removes the full group of indicator ## variables for that predictor in one step. ## ## @end deftypefn function NewMdl = removeTerms (mdl, terms) if (nargin < 2) error ("removeTerms: Not enough input arguments."); endif if (nargin > 2) error ("removeTerms: Too many input arguments."); endif nv = mdl.NumVariables; ## The candidates, not the chosen: a term may name a predictor the ## current model does not use, and nv - 1 counts them all. pred = mdl.PredictorNamesRaw; if (isnumeric (terms) || islogical (terms)) T = double (terms); if (isempty (T)) error ("removeTerms: Terms matrix must have %d columns.", nv); endif if (columns (T) == nv - 1) T = [T, zeros(rows (T), 1)]; endif if (columns (T) != nv) error ("removeTerms: Terms matrix must have %d columns.", nv); endif elseif (ischar (terms) || isstring (terms)) terms_str = strtrim (char (terms)); T = zeros (0, nv); plus_tokens = strsplit (terms_str, '+'); for ti = 1:numel (plus_tokens) tok = strtrim (plus_tokens{ti}); if (isempty (tok)); continue; endif if (! isempty (strfind (tok, '*'))) star_parts = cellfun (@strtrim, strsplit (tok, '*'), ... 'UniformOutput', false); n_sp = numel (star_parts); colon_toks = {}; for mask = 1:(2^n_sp - 1) sub = {}; for bit = 1:n_sp if (bitand (mask, 2^(bit-1))) sub{end+1} = star_parts{bit}; endif endfor colon_toks{end+1} = strjoin (sub, ':'); endfor else colon_toks = {tok}; endif for ci = 1:numel (colon_toks) ctok = strtrim (colon_toks{ci}); if (strcmp (ctok, '1')) T = [T; zeros(1, nv)]; else parts = cellfun (@strtrim, strsplit (ctok, ':'), ... 'UniformOutput', false); if (numel (parts) == 1) part = parts{1}; hat = strfind (part, '^'); if (isempty (hat)) vname = part; exp = 1; else vname = strtrim (part(1:hat(1)-1)); exp = str2double (strtrim (part(hat(1)+1:end))); endif idx = find (strcmp (pred, vname)); if (isempty (idx)) error ("removeTerms: Unrecognized variable: '%s'.", vname); endif for k = 1:exp row = zeros (1, nv); row(idx(1)) = k; T = [T; row]; endfor else row = zeros (1, nv); for pi = 1:numel (parts) part = parts{pi}; hat = strfind (part, '^'); if (isempty (hat)) vname = part; exp = 1; else vname = strtrim (part(1:hat(1)-1)); exp = str2double (strtrim (part(hat(1)+1:end))); endif idx = find (strcmp (pred, vname)); if (isempty (idx)) error ("removeTerms: Unrecognized variable: '%s'.", vname); endif row(idx(1)) = row(idx(1)) + exp; endfor T = [T; row]; endif endif endfor endfor else error (strcat ("removeTerms: Model update specification must be a", ... " model formula character vector or string scalar,", ... " or a terms matrix")); endif nc = columns (mdl.TermsMatrix); if (nc != nv) cat_info = mdl.CatLevelInfo; ename = mdl.EncPredictorNames; n_pred = nv - 1; orig_to_enc = cell (n_pred, 1); for j = 1:n_pred ci = []; if (! isempty (cat_info) && isfield (cat_info, 'names') ... && ! isempty (cat_info.names)) ci = find (strcmp (cat_info.names, pred{j})); endif if (isempty (ci)) orig_to_enc{j} = find (strcmp (ename, pred{j})); else levels_j = cat_info.levels{ci}; ecols = []; for L = 2:numel (levels_j) lvl_name = sprintf ("%s_%s", pred{j}, char (levels_j{L})); k = find (strcmp (ename, lvl_name)); if (! isempty (k)) ecols(end+1) = k; endif endfor orig_to_enc{j} = ecols; endif endfor T_enc = zeros (0, nc); for i = 1:rows (T) orig_row = T(i, 1:n_pred); any_cat = false; cat_rows = zeros (0, nc); cont_row = zeros (1, nc); for j = 1:n_pred if (orig_row(j) != 0) ecols = orig_to_enc{j}; if (numel (ecols) > 1) any_cat = true; for k = 1:numel (ecols) r = zeros (1, nc); r(ecols(k)) = 1; cat_rows = [cat_rows; r]; endfor else cont_row(ecols) = orig_row(j); endif endif endfor if (any_cat) for k = 1:rows (cat_rows) T_enc = [T_enc; cat_rows(k,:) + cont_row]; endfor else T_enc = [T_enc; cont_row]; endif endfor T = T_enc; endif existing = mdl.TermsMatrix; n_exist = rows (existing); n_req = rows (T); found = false (n_req, 1); for i = 1:n_req found(i) = any (all (existing == T(i,:), 2)); endfor if (! any (found)) warning ("removeTerms: No specified terms appear in the model."); NewMdl = mdl; return; endif keep = true (n_exist, 1); for i = 1:n_req if (found(i)) for j = 1:n_exist if (keep(j) && all (existing(j,:) == T(i,:))) keep(j) = false; break; endif endfor endif endfor remaining = existing(keep, :); int_mask = all (remaining(:, 1:end-1) == 0, 2); body = remaining(! int_mask, :); n_nonzero = sum (body(:, 1:end-1) != 0, 2); degree = sum (body(:, 1:end-1), 2); tier = zeros (rows (body), 1); tier(n_nonzero == 1 & degree == 1) = 1; tier(n_nonzero == 2) = 2; tier(n_nonzero == 1 & degree > 1) = 3; bitmask = zeros (rows (body), 1); for i = 1:rows (body) bitmask(i) = sum (2 .^ (find (body(i, 1:end-1)) - 1)); endfor [~, order] = sortrows ([tier, bitmask]); remaining = [remaining(int_mask, :); body(order, :)]; ## Back into the variable space the refit works in. The request was ## mapped down to the model's own columns to be compared with its terms, ## and those are only as wide as the predictors the formula uses, while ## lm_refit refits against the whole variable table. A model fitted from ## a matrix has no gap between the two, which is why this only ever ## showed on a table model whose formula names a subset. if (nc != nv) R = zeros (rows (remaining), nv); for j = 1:n_pred ecols = orig_to_enc{j}; for k = 1:numel (ecols) R(:,j) = max (R(:,j), remaining(:,ecols(k))); endfor endfor R(:,end) = remaining(:,end); remaining = R; endif NewMdl = lm_refit (mdl, remaining); endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {} plotResiduals (@var{mdl}) ## @deftypefnx {LinearModel} {} plotResiduals (@var{mdl}, @var{plottype}) ## @deftypefnx {LinearModel} {} plotResiduals (@var{mdl}, @var{plottype}, @var{Name}, @var{Value}) ## @deftypefnx {LinearModel} {} plotResiduals (@var{ax}, @dots{}) ## @deftypefnx {LinearModel} {@var{h} =} plotResiduals (@dots{}) ## ## Plot residuals of a fitted linear regression model. ## ## @code{plotResiduals (@var{mdl})} creates a probability density histogram ## of the raw residuals. Bin width follows Scott's rule ## @math{h = 3.5 \hat\sigma n^{-1/3}} and is rounded to a visually clean ## value. The bar areas sum to 1. ## ## @code{plotResiduals (@var{mdl}, @var{plottype})} creates the type of ## residual plot given by @var{plottype}. For all types except ## @code{"histogram"} and @code{"probability"}, the full observation vector ## including excluded rows is passed to the plot. Excluded or missing rows ## appear as @code{NaN} in the plotted data and produce visible gaps. ## @var{plottype} must be one of: ## ## @table @asis ## @item @qcode{'histogram'} (default) ## Probability density histogram. Only active observations are used. ## Returns one @code{patch} handle. Accepts @code{FaceColor}, ## @code{EdgeColor}, @code{FaceAlpha}, and @code{LineWidth} Name-Value ## arguments. ## ## @item @qcode{'fitted'} ## Residuals on the y-axis against fitted values on the x-axis. A dotted ## horizontal reference line marks @math{y = 0}. Returns two line handles: ## @code{h(1)} is the data scatter and @code{h(2)} is the reference line. ## ## @item @qcode{'caseorder'} ## Residuals on the y-axis against observation row number on the x-axis, ## covering all rows from 1 to @code{n_total}. A dotted horizontal ## reference line marks @math{y = 0}. Returns two line handles: ## @code{h(1)} is the data and @code{h(2)} is the reference line. ## ## @item @qcode{'lagged'} ## Each residual @math{r(t)} on the y-axis against the preceding residual ## @math{r(t-1)} on the x-axis. Two dotted reference lines mark ## @math{y = 0} and @math{x = 0}. Returns three line handles: @code{h(1)} ## is the scatter, @code{h(2)} is the horizontal reference, and @code{h(3)} ## is the vertical reference. ## ## @item @qcode{'probability'} ## Normal probability plot of the sorted active residuals produced by ## @code{normplot}. Returns two handles: @code{h(1)} is the data line and ## @code{h(2)} is the fitted reference line produced by @code{normplot}. ## Name-Value arguments are not applied for this plot type. ## ## @item @qcode{'observed'} ## Observed response values on the y-axis against fitted values on the ## x-axis. A dotted @math{y = x} reference line is drawn through the ## origin. Vertical segments connect each observed point down to the ## reference line. ## Returns three handles: @code{h(1)} is the scatter, @code{h(2)} is the ## @math{y = x} reference, and @code{h(3)} is the vertical segment line ## (stored as a single @code{NaN}-separated line object). ## ## @item @qcode{'symmetry'} ## Upper-tail distances from the median plotted against lower-tail distances ## from the median. Each point @code{(x, y)} satisfies ## @math{x = \mathrm{med} - r_{(i)}} and ## @math{y = r_{(n+1-i)} - \mathrm{med}}, using the ## @math{\lfloor n/2 \rfloor} most extreme observations on each side. A ## perfectly symmetric distribution falls on the dotted @math{y = x} ## reference line. Returns two handles: @code{h(1)} is the scatter and ## @code{h(2)} is the reference line. ## @end table ## ## @code{plotResiduals (@var{ax}, @dots{})} targets the axes object @var{ax} ## instead of the current axes returned by @code{gca}. ## ## @code{@var{h} = plotResiduals (@dots{})} returns a vector of graphics ## handles. The number of handles depends on @var{plottype} as described ## above. Name-Value arguments are applied to the data handle @code{h(1)} ## only. Reference lines are always drawn with the default style and are ## not affected by Name-Value arguments. ## ## The following Name-Value arguments are accepted. Arguments marked ## @emph{histogram only} are passed directly to the @code{patch} object and ## have no effect on other plot types. Arguments marked ## @emph{non-histogram} are applied to the scatter marker and have no ## effect on the histogram. ## ## @multitable @columnfractions 0.28 0.70 ## @headitem Name @tab Description and default ## ## @item @qcode{'ResidualType'} @tab ## Type of residual to plot. One of @qcode{'raw'} (default), ## @qcode{'pearson'}, @qcode{'standardized'}, or @qcode{'studentized'}. ## Case-insensitive. Selects the corresponding column of ## @code{mdl.Residuals}. ## ## @item @qcode{'Color'} @tab ## (@emph{non-histogram}) Marker color. ## Default: @code{[0.1490 0.5490 0.8660]}. ## ## @item @qcode{'Marker'} @tab ## (@emph{non-histogram}) Marker symbol. Any symbol accepted by ## @code{plot} is valid. Default: @qcode{'x'}. ## ## @item @qcode{'MarkerSize'} @tab ## (@emph{non-histogram}) Marker size in points. Default: @code{6}. ## ## @item @qcode{'MarkerEdgeColor'} @tab ## (@emph{non-histogram}) Marker edge color. Default: @qcode{'auto'}. ## ## @item @qcode{'MarkerFaceColor'} @tab ## (@emph{non-histogram}) Marker fill color. Default: @qcode{'none'}. ## ## @item @qcode{'LineWidth'} @tab ## (@emph{non-histogram}) Width of the marker edge in points. ## Default: @code{0.5}. ## ## @item @qcode{'FaceColor'} @tab ## (@emph{histogram only}) Fill color of the histogram bars. ## Default: @code{[0.1490 0.5490 0.8660]}. ## ## @item @qcode{'EdgeColor'} @tab ## (@emph{histogram only}) Edge color of the histogram bars. ## ## @item @qcode{'FaceAlpha'} @tab ## (@emph{histogram only}) Transparency of the histogram bars, ## specified as a scalar in @math{[0, 1]}. ## @end multitable ## ## @end deftypefn function h = plotResiduals (this, varargin) [ax, mdl, args] = lm_plot_axes (this, varargin); DEF_COLOR = [0.1490, 0.5490, 0.8660]; REF_COLOR = [0.8510, 0.8510, 0.8510]; valid_pt = {'histogram', 'fitted', 'lagged', 'caseorder', ... 'probability', 'observed', 'symmetry'}; known_nv = {'residualtype', 'color', 'marker', 'markersize', ... 'markeredgecolor', 'markerfacecolor', 'linewidth', ... 'facecolor', 'edgecolor', 'facealpha'}; if (! isempty (args) && ischar (args{1}) ... && ! any (strcmpi (args{1}, known_nv))) pt_str = args{1}; args = args(2:end); idx = find (strcmpi (pt_str, valid_pt)); if (isempty (idx)) error ("plotResiduals: Bad residuals plot type."); endif plottype = valid_pt{idx(1)}; else plottype = 'histogram'; endif residtype = 'raw'; nv_remaining = {}; i = 1; while (i <= numel (args)) if (ischar (args{i}) && strcmpi (args{i}, 'ResidualType')) if (i + 1 > numel (args)) error ("plotResiduals: ResidualType requires a value."); endif rt_val = lower (char (args{i+1})); valid_rt = {'raw', 'pearson', 'standardized', 'studentized'}; if (! any (strcmp (rt_val, valid_rt))) error (strcat ("plotResiduals: invalid ResidualType '%s'.", ... " Valid values are: 'Raw', 'Pearson',", ... " 'Standardized', 'Studentized'."), args{i+1}); endif residtype = rt_val; i = i + 2; else nv_remaining{end+1} = args{i}; i = i + 1; endif endwhile ## Parse the plot properties before taking an axes: gca creates a ## figure when none is current, and an unrecognised property would ## otherwise leave that figure behind when the error is raised. A ## histogram is the exception, forwarding its properties to PATCH, ## which accepts and validates its own. if (! strcmp (plottype, 'histogram')) props = lm_plot_props (nv_remaining); endif if (isempty (ax)) ax = gca (); endif switch (residtype) case 'raw'; rf = 'Raw'; case 'pearson'; rf = 'Pearson'; case 'standardized'; rf = 'Standardized'; case 'studentized'; rf = 'Studentized'; endswitch r = mdl.Residuals.(rf); switch (plottype) case 'histogram' r_act = r(! isnan (r)); n_act = numel (r_act); s = std (r_act); if (n_act <= 1 || s == 0) bw = 1; lo = floor (min (r_act)) - 0.5; hi = lo + 1; else bw_raw = 3.5 * s / (n_act ^ (1/3)); mag = 10 ^ floor (log10 (bw_raw)); frac = bw_raw / mag; if (frac < 1.5); nice = 1; elseif (frac < 2.5); nice = 2; elseif (frac < 4); nice = 3; elseif (frac < 7.5); nice = 5; else; nice = 10; endif bw = nice * mag; lo = floor (min (r_act) / bw) * bw; hi = ceil (max (r_act) / bw) * bw; endif n_bins = max (1, round ((hi - lo) / bw)); centers = lo + bw/2 : bw : lo + bw * (n_bins - 0.5); [counts, ~] = hist (r_act, centers); dens = counts / (n_act * bw); left = lo + (0:n_bins-1) * bw; right = left + bw; Xp = [left; left; right; right]; Yp = [zeros(1, n_bins); dens; dens; zeros(1, n_bins)]; h = patch (Xp, Yp, DEF_COLOR, 'FaceColor', DEF_COLOR, ... nv_remaining{:}, 'Parent', ax); xlabel (ax, 'Residuals'); ylabel (ax, 'Probability density'); title (ax, 'Histogram of residuals'); case 'fitted' fit = mdl.Fitted; fin = fit(! isnan (fit)); hold (ax, 'on'); h(1) = lm_plot_data (ax, fit, r, props); h(2) = line ([min(fin), max(fin)], [0, 0], ... 'LineStyle', ':', 'Color', REF_COLOR, 'Parent', ax); hold (ax, 'off'); xlabel (ax, 'Fitted values'); ylabel (ax, 'Residuals'); title (ax, 'Plot of residuals vs. fitted values'); case 'caseorder' n_tot = numel (r); hold (ax, 'on'); h(1) = lm_plot_data (ax, 1:n_tot, r, props); h(2) = line ([1, n_tot], [0, 0], ... 'LineStyle', ':', 'Color', REF_COLOR, 'Parent', ax); hold (ax, 'off'); xlabel (ax, 'Row number'); ylabel (ax, 'Residuals'); title (ax, 'Case order plot of residuals'); case 'lagged' r_x = r(1:end-1); r_y = r(2:end); rx_fin = r_x(! isnan (r_x)); ry_fin = r_y(! isnan (r_y)); hold (ax, 'on'); h(1) = lm_plot_data (ax, r_x, r_y, props); h(2) = line ([min(rx_fin), max(rx_fin)], [0, 0], ... 'LineStyle', ':', 'Color', REF_COLOR, 'Parent', ax); h(3) = line ([0, 0], [min(ry_fin), max(ry_fin)], ... 'LineStyle', ':', 'Color', REF_COLOR, 'Parent', ax); hold (ax, 'off'); xlabel (ax, 'Residual(t-1)'); ylabel (ax, 'Residual(t)'); title (ax, 'Plot of residuals vs. lagged residuals'); case 'probability' r_act = r(! isnan (r)); r_s = sort (r_act); h = normplot (ax, r_s); title (ax, 'Normal probability plot of residuals'); xlabel (ax, 'Residuals'); ylabel (ax, 'Probability'); case 'observed' fit = mdl.Fitted; obs = mdl.Variables{:, mdl.ResponseName}; fin = fit(! isnan (fit)); av = [fin(:); obs(! isnan (fit))]; xl = [min(av), max(av)]; n_tot = numel (fit); xv = reshape ([fit(:)'; fit(:)'; NaN(1, n_tot)], 1, []); yv = reshape ([fit(:)'; obs(:)'; NaN(1, n_tot)], 1, []); hold (ax, 'on'); h(1) = lm_plot_data (ax, fit, obs, props); h(2) = line (xl, xl, ... 'LineStyle', ':', 'Color', REF_COLOR, 'Parent', ax); h(3) = line (xv, yv, ... 'LineStyle', '-', 'Color', REF_COLOR, 'Parent', ax); hold (ax, 'off'); xlabel (ax, 'Fitted values'); ylabel (ax, 'Observed response values'); title (ax, 'Plot of observed vs. fitted values'); case 'symmetry' r_act = r(! isnan (r)); r_s = sort (r_act); med = median (r_s); m = floor (numel (r_s) / 2); x_sym = sort (med - r_s(1:m)); y_sym = sort (r_s(end-m+1:end) - med); mx = max ([x_sym(:); y_sym(:)]); hold (ax, 'on'); h(1) = lm_plot_data (ax, x_sym, y_sym, props); h(2) = line ([0, mx], [0, mx], ... 'LineStyle', ':', 'Color', REF_COLOR, 'Parent', ax); hold (ax, 'off'); xlabel (ax, 'Lower tail'); ylabel (ax, 'Upper tail'); title (ax, 'Symmetry plot of residuals around their median'); endswitch if (nargout == 0) clear h; endif endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {} plotDiagnostics (@var{mdl}) ## @deftypefnx {LinearModel} {} plotDiagnostics (@var{mdl}, @var{plottype}) ## @deftypefnx {LinearModel} {} plotDiagnostics (@var{mdl}, @var{plottype}, @var{Name}, @var{Value}) ## @deftypefnx {LinearModel} {} plotDiagnostics (@var{ax}, @dots{}) ## @deftypefnx {LinearModel} {@var{h} =} plotDiagnostics (@dots{}) ## ## Plot observation diagnostics of a fitted linear regression model. ## ## @code{plotDiagnostics (@var{mdl})} creates a case-order plot of the ## leverage of each observation. The x-axis is the observation row number ## running from 1 to the total number of rows including any excluded rows. ## A dotted horizontal reference line marks the recommended threshold ## @math{2p/n}, where @math{p} is @code{mdl.NumCoefficients} and @math{n} ## is @code{mdl.NumObservations}. ## ## @code{plotDiagnostics (@var{mdl}, @var{plottype})} creates the diagnostic ## plot specified by @var{plottype}. For all types except @code{"contour"}, ## the x-axis is the row number and covers all rows including excluded ones. ## Excluded rows produce @code{NaN} values in the diagnostic vectors, which ## appear as natural gaps in the plot with no special handling required. ## @var{plottype} must be one of: ## ## @table @asis ## @item @qcode{'leverage'} (default) ## Leverage of each observation (@code{mdl.Diagnostics.Leverage}). ## One dotted horizontal reference line at @math{2p/n}. ## Returns two handles: @code{h(1)} is the data scatter and @code{h(2)} ## is the reference line. ## ## @item @qcode{'cookd'} ## Cook's distance for each observation ## (@code{mdl.Diagnostics.CooksDistance}). One dotted reference line at ## @math{3 \times \mathrm{mean(CooksDistance)}}, where the mean ignores ## @code{NaN} values. Returns two handles: @code{h(1)} data, @code{h(2)} ## reference. ## ## @item @qcode{'covratio'} ## Delete-1 ratio of the determinant of the coefficient covariance matrix ## (@code{mdl.Diagnostics.CovRatio}). Two dotted reference lines at ## @math{1 - 3p/n} (lower bound) and @math{1 + 3p/n} (upper bound). ## Both bounds are stored as a single @code{NaN}-separated line object. ## Returns two handles: @code{h(1)} data, @code{h(2)} combined reference. ## ## @item @qcode{'dfbetas'} ## Delete-1 scaled change in each coefficient estimate ## (@code{mdl.Diagnostics.Dfbetas}, one column per coefficient). ## One line object is drawn per coefficient. Two dotted reference lines ## at @math{\pm 3/\sqrt{n}} are stored as a single @code{NaN}-separated ## line object. Returns @math{p+1} handles: @code{h(1)} through ## @code{h(p)} are the per-coefficient data lines and @code{h(p+1)} is ## the combined reference. Name-Value arguments are applied to all ## @math{p} data handles. ## ## @item @qcode{'dffits'} ## Delete-1 scaled change in the fitted value ## (@code{mdl.Diagnostics.Dffits}). Two dotted reference lines at ## @math{\pm 2\sqrt{p/n}} stored as a single @code{NaN}-separated line. ## Returns two handles: @code{h(1)} data, @code{h(2)} combined reference. ## ## @item @qcode{'s2_i'} ## Delete-1 variance estimate (@code{mdl.Diagnostics.S2_i}). One dotted ## reference line at @code{mdl.MSE}. Returns two handles: @code{h(1)} ## data, @code{h(2)} reference. ## ## @item @qcode{'contour'} ## Standardized residuals on the y-axis against leverage on the x-axis, ## with Cook's distance contours overlaid at levels ## @math{[0.05, 0.10, 0.15, 0.20, 0.25]}. The contour surface is ## computed on a 31-by-30 grid over the range of the active leverage and ## residual values. Returns two handles: @code{h(1)} is the data scatter ## (a @code{line} object) and @code{h(2)} is the contour object. ## @end table ## ## @code{plotDiagnostics (@var{ax}, @dots{})} targets the axes object ## @var{ax} instead of the current axes returned by @code{gca}. ## ## @code{@var{h} = plotDiagnostics (@dots{})} returns a vector of graphics ## handles. The number of handles depends on @var{plottype} as described ## above. Name-Value arguments are applied to the data handle @code{h(1)}, ## except for @code{"dfbetas"} where they are applied to all @math{p} ## coefficient handles. Reference line handles are never affected by ## Name-Value arguments. ## ## @multitable @columnfractions 0.28 0.70 ## @headitem Name @tab Description and default ## ## @item @qcode{'Color'} @tab ## Marker color for data points. For @code{"dfbetas"} this color is ## applied to all @math{p} coefficient line objects. ## Default: @code{[0.1490 0.5490 0.8660]}. ## ## @item @qcode{'Marker'} @tab ## Marker symbol. Any symbol accepted by @code{plot} is valid. ## Default: @qcode{'x'}. ## ## @item @qcode{'MarkerSize'} @tab ## Marker size in points. Default: @code{6}. ## ## @item @qcode{'MarkerEdgeColor'} @tab ## Marker edge color. Default: @qcode{'auto'}. ## ## @item @qcode{'MarkerFaceColor'} @tab ## Marker fill color. Default: @qcode{'none'}. ## ## @item @qcode{'LineWidth'} @tab ## Width of the marker edge in points. Default: @code{0.5}. ## @end multitable ## ## @end deftypefn function h = plotDiagnostics (this, varargin) [ax, mdl, args] = lm_plot_axes (this, varargin); REF_COLOR = [0.8510, 0.8510, 0.8510]; valid_pt = {'leverage', 'cookd', 'covratio', 'dfbetas', ... 'dffits', 's2_i', 'contour'}; if (! isempty (args) && ischar (args{1}) ... && ! any (strcmpi (args{1}, ... {'color','marker','markersize','markeredgecolor', ... 'markerfacecolor','linewidth'}))) pt_str = args{1}; args = args(2:end); idx = find (strcmpi (pt_str, valid_pt)); if (isempty (idx)) error ("plotDiagnostics: Bad diagnostics plot type."); endif plottype = valid_pt{idx(1)}; else plottype = 'leverage'; endif props = lm_plot_props (args); if (isempty (ax)) ax = gca (); endif diag_t = mdl.Diagnostics; p = mdl.NumCoefficients; n_obs = mdl.NumObservations; mse = mdl.MSE; n = numel (diag_t.Leverage); switch (plottype) case 'leverage' lev = diag_t.Leverage; ref = 2 * p / n_obs; hold (ax, 'on'); h(1) = lm_plot_data (ax, 1:n, lev, props); h(2) = line ([0, n], [ref, ref], ... 'LineStyle', ':', 'Color', REF_COLOR, 'Parent', ax); hold (ax, 'off'); xlabel (ax, 'Row number'); ylabel (ax, 'Leverage'); title (ax, 'Case order plot of leverage'); case 'cookd' cd_ = diag_t.CooksDistance; ref = 3 * mean (cd_, 'omitnan'); hold (ax, 'on'); h(1) = lm_plot_data (ax, 1:n, cd_, props); h(2) = line ([0, n], [ref, ref], ... 'LineStyle', ':', 'Color', REF_COLOR, 'Parent', ax); hold (ax, 'off'); xlabel (ax, 'Row number'); ylabel (ax, 'Cook''s distance'); title (ax, 'Case order plot of Cook''s distance'); case 'covratio' cv = diag_t.CovRatio; lo = 1 - 3*p/n_obs; hi = 1 + 3*p/n_obs; xv = [0, n, NaN, 0, n]; yv = [lo, lo, NaN, hi, hi]; hold (ax, 'on'); h(1) = lm_plot_data (ax, 1:n, cv, props); h(2) = line (xv, yv, ... 'LineStyle', ':', 'Color', REF_COLOR, 'Parent', ax); hold (ax, 'off'); xlabel (ax, 'Row number'); ylabel (ax, 'Covariance ratio'); title (ax, 'Case order plot of covariance ratio'); case 'dfbetas' db = diag_t.Dfbetas; thr = 3 / sqrt (n_obs); xv = [0, n, NaN, 0, n]; yv = [-thr, -thr, NaN, thr, thr]; hold (ax, 'on'); for k = 1:p h(k) = lm_plot_data (ax, (1:n)', db(:,k), props); endfor h(p+1) = line (xv, yv, ... 'LineStyle', ':', 'Color', REF_COLOR, 'Parent', ax); hold (ax, 'off'); xlabel (ax, 'Row number'); ylabel (ax, 'Scaled change in coefficients'); title (ax, 'Case order plot of scaled change in coefficients'); case 'dffits' df = diag_t.Dffits; thr = 2 * sqrt (p / n_obs); xv = [0, n, NaN, 0, n]; yv = [-thr, -thr, NaN, thr, thr]; hold (ax, 'on'); h(1) = lm_plot_data (ax, 1:n, df, props); h(2) = line (xv, yv, ... 'LineStyle', ':', 'Color', REF_COLOR, 'Parent', ax); hold (ax, 'off'); xlabel (ax, 'Row number'); ylabel (ax, 'Scaled change in fit'); title (ax, 'Case order plot of scaled change in fit'); case 's2_i' s2 = diag_t.S2_i; hold (ax, 'on'); h(1) = lm_plot_data (ax, 1:n, s2, props); h(2) = line ([0, n], [mse, mse], ... 'LineStyle', ':', 'Color', REF_COLOR, 'Parent', ax); hold (ax, 'off'); xlabel (ax, 'Row number'); ylabel (ax, 'Leave-one-out variance'); title (ax, 'Case order plot of leave-one-out variance'); case 'contour' lev = diag_t.Leverage; r_raw = mdl.Residuals.Raw; act = ! isnan (lev); lev_a = lev(act); r_a = r_raw(act); x_grid = linspace (min (lev_a), max (lev_a), 31); y_grid = linspace (min (r_a), max (r_a), 30); [Hg, Rg] = meshgrid (x_grid, y_grid); Hg = min (Hg, 1 - 1e-10); Zg = Rg.^2 .* Hg ./ (p .* mse .* (1 - Hg).^2); levels = [0.05, 0.10, 0.15, 0.20, 0.25]; hold (ax, 'on'); h(1) = lm_plot_data (ax, lev, r_raw, props); [~, h_ct] = contour (ax, x_grid, y_grid, Zg, levels); h(2) = h_ct; hold (ax, 'off'); xlabel (ax, 'Leverage'); ylabel (ax, 'Residual'); title (ax, 'Cook''s distance factorization'); endswitch if (nargout == 0) clear h; endif endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {} plotEffects (@var{mdl}) ## @deftypefnx {LinearModel} {} plotEffects (@var{ax}, @var{mdl}) ## @deftypefnx {LinearModel} {@var{h} =} plotEffects (@dots{}) ## ## Plot the main effects of each predictor in a fitted linear regression ## model. ## ## @code{plotEffects (@var{mdl})} creates a horizontal dot-and-line plot ## with one row per predictor. Each dot shows the estimated main effect on ## the response from changing that predictor from its minimum observed value ## to its maximum observed value, while holding all other predictors fixed ## at their observed means. A horizontal line through each dot shows the ## 95% confidence interval for that effect. ## ## The main effect for predictor @var{xs} is defined as ## @math{g(x_{s,\max}) - g(x_{s,\min})}, where the adjusted response ## function @math{g} evaluates the model at the specified value of ## @var{xs} with all other predictors set to their observed means. ## For numeric predictors the sign of the effect can be positive or ## negative depending on the direction of the relationship. ## ## @code{plotEffects (@var{ax}, @var{mdl})} creates the plot in the axes ## object @var{ax} instead of the current axes returned by @code{gca}. ## ## @code{@var{h} = plotEffects (@dots{})} returns a vector of ## @math{p+1} graphics handles where @math{p} is the number of predictors. ## @code{h(1)} is the line object containing the effect estimate markers ## (one circle per predictor, plotted as a single line object with ## @code{XData} of length @math{p} and @code{YData = 1:p}). ## @code{h(j+1)} is the confidence interval line for predictor @math{j}, ## with @code{XData = [ci_lo, ci_hi]} and @code{YData = [j, j]}. ## ## The y-axis tick labels follow the format ## @qcode{'varname: min to max'}, showing the predictor name and the ## minimum and maximum observed values used to compute the effect. ## ## @end deftypefn function h = plotEffects (this, varargin) [ax, mdl, args] = lm_plot_axes (this, varargin); if (! isempty (args)) error ("plotEffects: Wrong number of arguments."); endif p = mdl.NumPredictors; if (! any (any (mdl.TermsMatrix(:, 1:end-1) != 0))) error ("plotEffects: Model has no predictors."); endif if (isempty (ax)) ax = gca (); endif DEF_COLOR = [0.1490, 0.5490, 0.8660]; pred = mdl.PredictorNames; V = mdl.CoefficientCovariance; beta = mdl.Coefficients.Estimate; t_crit = tinv (0.975, mdl.DFE); cinfo = mdl.CatLevelInfo; C = mdl.EffectContrasts; effects = zeros (1, p); ci_lo = zeros (1, p); ci_hi = zeros (1, p); for j = 1:p effects(j) = C(j,:) * beta; SE = sqrt (max (0, C(j,:) * V * C(j,:)')); ci_lo(j) = effects(j) - t_crit * SE; ci_hi(j) = effects(j) + t_crit * SE; endfor hold (ax, 'on'); h(1) = plot (ax, effects, 1:p, ... 'LineStyle', 'none', ... 'Marker', 'o', ... 'MarkerSize', 6, ... 'Color', DEF_COLOR); for j = 1:p h(j+1) = line ([ci_lo(j), ci_hi(j)], [j, j], ... 'LineStyle', '-', ... 'Marker', 'none', ... 'Color', DEF_COLOR, ... 'Parent', ax); endfor hold (ax, 'off'); rn = mdl.VariableInfo.Properties.RowNames; ytl = cell (p, 1); for j = 1:p ci = []; if (! isempty (cinfo) && isfield (cinfo, 'names') && ! isempty (cinfo.names)) ci = find (strcmp (cinfo.names, pred{j})); endif vidx = find (strcmp (rn, pred{j})); rng = mdl.VariableInfo.Range{vidx}; if (! isempty (ci)) levels_j = cinfo.levels{ci}; lo_str = char (levels_j{1}); hi_str = char (levels_j{end}); else lo_str = num2str (rng(1), '%g'); hi_str = num2str (rng(2), '%g'); endif ytl{j} = [pred{j}, ': ', lo_str, ' to ', hi_str]; endfor set (ax, 'YTick', 1:p, 'YTickLabel', ytl, 'YDir', 'reverse'); ylim (ax, [0.5, p + 0.5]); xlabel (ax, 'Main Effect'); ylabel (ax, ''); title (ax, 'Main Effects Plot'); if (nargout == 0) clear h; endif endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {} plotAdjustedResponse (@var{mdl}, @var{var}) ## @deftypefnx {LinearModel} {} plotAdjustedResponse (@var{mdl}, @var{var}, @var{Name}, @var{Value}) ## @deftypefnx {LinearModel} {} plotAdjustedResponse (@var{ax}, @dots{}) ## @deftypefnx {LinearModel} {@var{h} =} plotAdjustedResponse (@dots{}) ## ## Plot the adjusted response of a fitted linear regression model against ## a single predictor. ## ## @code{plotAdjustedResponse (@var{mdl}, @var{var})} creates an adjusted ## response plot for the predictor @var{var} in the linear regression ## model @var{mdl}. @var{var} is a character vector or string naming a ## predictor in @code{mdl.PredictorNames}, or a positive integer indexing ## into @code{mdl.VariableNames}. ## ## An adjusted response function describes the fitted response as a ## function of a single predictor, with the other predictors averaged out ## by averaging the fitted values over the observations used in the fit. ## For a model @math{y_i = f (x_{1i}, x_{2i}, @dots{}, x_{pi}) + r_i}, the ## adjusted response function for @math{x_1} is ## @math{g (x_1) = (1/n) \sum_{i=1}^n f (x_1, x_{2i}, x_{3i}, @dots{}, ## x_{pi})}, where @math{n} is the number of observations used to fit the ## model. The adjusted response data value for observation @math{i} is ## @math{\tilde y_i = g (x_{1i}) + r_i}. ## ## For a numeric predictor, the adjusted response function is evaluated ## on an evenly spaced grid of 100 points spanning the minimum to the ## maximum observed value of @var{var}. For a categorical predictor, the ## adjusted response function is evaluated at each category level. ## ## Excluded or missing observations appear as @code{NaN} in the adjusted ## data and produce gaps in the plotted data points. ## ## @code{plotAdjustedResponse (@var{mdl}, @var{var}, @var{Name}, ## @var{Value})} specifies additional Name-Value arguments applied to the ## adjusted data points (@code{h(1)}). The following are accepted: ## ## @multitable @columnfractions 0.28 0.70 ## @headitem Name @tab Description and default ## ## @item @qcode{'Color'} @tab ## Marker color. Default: @code{[0.1490 0.5490 0.8660]}. ## ## @item @qcode{'Marker'} @tab ## Marker symbol. Default: @qcode{'x'}. ## ## @item @qcode{'MarkerSize'} @tab ## Marker size in points. Default: @code{6}. ## ## @item @qcode{'MarkerEdgeColor'} @tab ## Marker edge color. Default: @qcode{'auto'}. ## ## @item @qcode{'MarkerFaceColor'} @tab ## Marker fill color. Default: @qcode{'none'}. ## ## @item @qcode{'LineWidth'} @tab ## Width of the marker edge in points. Default: @code{0.5}. ## @end multitable ## ## @code{plotAdjustedResponse (@var{ax}, @dots{})} plots into the axes ## object @var{ax} instead of the current axes returned by @code{gca}. ## ## @code{@var{h} = plotAdjustedResponse (@dots{})} returns a 2-by-1 ## vector of line handles. @code{h(1)} corresponds to the adjusted ## response data points and @code{h(2)} corresponds to the adjusted ## response function. Name-Value arguments only affect @code{h(1)}. ## ## @end deftypefn function h = plotAdjustedResponse (this, varargin) [ax, mdl, args] = lm_plot_axes (this, varargin); if (isempty (args)) error ("plotAdjustedResponse: Not enough input arguments."); endif var = args{1}; args = args(2:end); vnames = mdl.VariableNames; pred = mdl.PredictorNames; if (ischar (var) || isstring (var)) vname = char (var); if (isempty (find (strcmp (vnames, vname)))) error ("plotAdjustedResponse: '%s' is not a variable for this fit.", vname); endif elseif (isnumeric (var) && isscalar (var)) if (var != fix (var) || var < 1) error (strcat ("plotAdjustedResponse: Variable must be specified", ... " as a name or a positive integer.")); endif if (var > numel (vnames)) error ("plotAdjustedResponse: This model only contains %d variables.", ... numel (vnames)); endif vname = vnames{var}; else error (strcat ("plotAdjustedResponse: Variable must be specified", ... " as a name or a positive integer.")); endif if (strcmp (vname, mdl.ResponseName)) error ("plotAdjustedResponse: The variable '%s' is the response in this model.", ... vname); endif j = find (strcmp (pred, vname)); pname = pred{j}; act = mdl.ObservationInfo.Subset; cinfo = mdl.CatLevelInfo; n_act = sum (act); p = numel (pred); [X_act, is_cat, cat_lvls] = lm_encode_active_predictors (mdl, act, pred, cinfo); props = lm_plot_props (args); if (isempty (ax)) ax = gca (); endif FIT_COLOR = [0.9600, 0.4660, 0.1600]; n_total = numel (act); beta = mdl.Coefficients.Estimate; resid = mdl.Residuals.Raw; xdata = NaN (n_total, 1); ydata = NaN (n_total, 1); xdata(act) = X_act(:, j); if (is_cat(j)) levels = cat_lvls{j}; n_lvl = numel (levels); fit_y = zeros (n_lvl, 1); for L = 1:n_lvl X_rows = X_act; X_rows(:,j) = L; X_enc = reencode_predictors (X_rows, pred, mdl.CatLevelInfo, mdl.EncPredictorNames); D = build_design (mdl.TermsMatrix, X_enc); fit_y(L) = mean (D * beta); endfor fit_x = (1:n_lvl)'; ydata(act) = fit_y(X_act(:, j)) + resid(act); else x_active = X_act(:, j); g_active = zeros (n_act, 1); for i = 1:n_act X_rows = X_act; X_rows(:,j) = x_active(i); X_enc = reencode_predictors (X_rows, pred, mdl.CatLevelInfo, mdl.EncPredictorNames); D = build_design (mdl.TermsMatrix, X_enc); g_active(i) = mean (D * beta); endfor ydata(act) = g_active + resid(act); fit_x = linspace (min (x_active), max (x_active), 100)'; fit_y = zeros (100, 1); for k = 1:100 X_rows = X_act; X_rows(:,j) = fit_x(k); X_enc = reencode_predictors (X_rows, pred, mdl.CatLevelInfo, mdl.EncPredictorNames); D = build_design (mdl.TermsMatrix, X_enc); fit_y(k) = mean (D * beta); endfor endif hold (ax, 'on'); h(1) = lm_plot_data (ax, xdata, ydata, props); set (h(1), 'DisplayName', 'Adjusted data'); h(2) = line (fit_x, fit_y, 'Color', FIT_COLOR, 'LineStyle', '-', ... 'Marker', 'none', 'Parent', ax, ... 'DisplayName', 'Adjusted fit'); hold (ax, 'off'); if (is_cat(j)) set (ax, 'XTick', 1:n_lvl, 'XTickLabel', levels); endif xlabel (ax, pname); ylabel (ax, ['Adjusted ', mdl.ResponseName]); title (ax, 'Adjusted response plot'); hleg = legend (ax, 'show'); set (hleg, 'Location', lm_legend_corner (xdata, ydata)); if (nargout == 0) clear h; endif endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {} plotAdded (@var{mdl}) ## @deftypefnx {LinearModel} {} plotAdded (@var{mdl}, @var{coef}) ## @deftypefnx {LinearModel} {} plotAdded (@var{mdl}, @var{coef}, @var{Name}, @var{Value}) ## @deftypefnx {LinearModel} {} plotAdded (@var{ax}, @dots{}) ## @deftypefnx {LinearModel} {@var{h} =} plotAdded (@dots{}) ## ## Create an added variable plot for a fitted linear regression model. ## ## @code{plotAdded (@var{mdl})} creates an added variable plot for the ## whole model @var{mdl} except the constant (intercept) term. ## ## @code{plotAdded (@var{mdl}, @var{coef})} creates an added variable ## plot for the coefficients specified by @var{coef}. @var{coef} is a ## character vector or string naming a single coefficient in ## @code{mdl.CoefficientNames}, the name of a categorical predictor in ## @code{mdl.PredictorNames} (which selects that predictor's whole group ## of indicator coefficients), or a vector of positive integers indexing ## into @code{mdl.CoefficientNames}. ## ## An added variable plot, also known as a partial regression leverage ## plot, illustrates the incremental effect on the response of the ## selected terms after removing the effects of all other terms. For a ## single selected predictor @math{x_1}, the response @math{y} and ## @math{x_1} are each fit to all other terms: ## @math{y_i = g_y (x_{2i}, @dots{}, x_{pi}) + r_{yi}}, ## @math{x_{1i} = g_x (x_{2i}, @dots{}, x_{pi}) + r_{xi}}. The adjusted ## values are @math{\tilde y_i = \bar y + r_{yi}} and ## @math{\tilde x_{1i} = \bar x_1 + r_{xi}}. When @var{coef} selects ## more than one coefficient, the selected columns of the design matrix ## are combined into a single direction using the unit vector ## @math{u = \beta / \lVert \beta \rVert}, and the added variable plot is ## created for that combined direction. ## ## Excluded or missing observations appear as @code{NaN} in the adjusted ## data and produce gaps in the plotted data points. ## ## @code{plotAdded (@var{mdl}, @var{coef}, @var{Name}, @var{Value})} ## specifies additional Name-Value arguments applied to the adjusted data ## points (@code{h(1)}). The following are accepted: ## ## @multitable @columnfractions 0.28 0.70 ## @headitem Name @tab Description and default ## ## @item @qcode{'Color'} @tab ## Marker color. Default: @code{[0.1490 0.5490 0.8660]}. ## ## @item @qcode{'Marker'} @tab ## Marker symbol. Default: @qcode{'x'}. ## ## @item @qcode{'MarkerSize'} @tab ## Marker size in points. Default: @code{6}. ## ## @item @qcode{'MarkerEdgeColor'} @tab ## Marker edge color. Default: @qcode{'auto'}. ## ## @item @qcode{'MarkerFaceColor'} @tab ## Marker fill color. Default: @qcode{'none'}. ## ## @item @qcode{'LineWidth'} @tab ## Width of the marker edge in points. Default: @code{0.5}. ## @end multitable ## ## @code{plotAdded (@var{ax}, @dots{})} plots into the axes object ## @var{ax} instead of the current axes returned by @code{gca}. ## ## @code{@var{h} = plotAdded (@dots{})} returns a 3-by-1 vector of line ## handles. @code{h(1)}, @code{h(2)}, and @code{h(3)} correspond to the ## adjusted data points, the fitted line, and the 95% confidence bounds ## of the fitted line, respectively. Name-Value arguments only affect ## @code{h(1)}. ## ## @end deftypefn function h = plotAdded (this, varargin) [ax, mdl, args] = lm_plot_axes (this, varargin); cnames = mdl.CoefficientNames; pred = mdl.PredictorNames; ncoef = numel (cnames); if (! isempty (args) && ! (ischar (args{1}) || isstring (args{1}) ... || isnumeric (args{1}))) error (strcat ("plotAdded: COEF must be a coefficient name or a", ... " vector of coefficient numbers.")); endif if (isempty (args)) J = 2:ncoef; if (numel (J) == 1) label = cnames{J}; else label = 'whole model'; endif else coefarg = args{1}; args = args(2:end); if (ischar (coefarg) || isstring (coefarg)) cname = char (coefarg); k = find (strcmp (cnames, cname)); if (! isempty (k)) J = k; else cinfo = mdl.CatLevelInfo; ci = []; if (! isempty (cinfo) && isfield (cinfo, 'names') ... && ! isempty (cinfo.names)) ci = find (strcmp (cinfo.names, cname)); endif if (isempty (ci)) error ("plotAdded: Bad coefficient name."); endif levels_ci = cinfo.levels{ci}; J = zeros (1, numel (levels_ci) - 1); for L = 2:numel (levels_ci) cn = sprintf ("%s_%s", cname, char (levels_ci{L})); J(L-1) = find (strcmp (cnames, cn)); endfor endif else J = coefarg(:)'; if (any (J != fix (J)) || any (J < 1) || any (J > ncoef)) error ("plotAdded: Bad coefficient number."); endif endif if (numel (J) == 1) label = cnames{J}; elseif (isequal (sort (J(:)'), 2:ncoef)) ## Every non-intercept coefficient: MATLAB names this the whole ## model, and reserves 'specified terms' for any other selection. label = 'whole model'; else label = 'specified terms'; cinfo = mdl.CatLevelInfo; if (! isempty (cinfo) && isfield (cinfo, 'names') ... && ! isempty (cinfo.names)) for ci = 1:numel (cinfo.names) levels_ci = cinfo.levels{ci}; Jc = zeros (1, numel (levels_ci) - 1); for L = 2:numel (levels_ci) cn = sprintf ("%s_%s", cinfo.names{ci}, char (levels_ci{L})); Jc(L-1) = find (strcmp (cnames, cn)); endfor if (isequal (sort (J), sort (Jc))) label = cinfo.names{ci}; break; endif endfor endif endif endif if (isempty (J)) error ("plotAdded: Bad coefficient number."); endif act = mdl.ObservationInfo.Subset; cinfo = mdl.CatLevelInfo; n_act = sum (act); p = numel (pred); [X_act, ~, ~] = lm_encode_active_predictors (mdl, act, pred, cinfo); D = reencode_predictors (X_act, pred, cinfo, mdl.EncPredictorNames); D = build_design (mdl.TermsMatrix, D); beta = mdl.Coefficients.Estimate; y_act = mdl.Variables{act, mdl.ResponseName}; Jc = setdiff (1:ncoef, J); if (numel (J) == 1) x1_act = D(:, J); slope = beta(J); else u = beta(J) / norm (beta(J)); x1_act = D(:, J) * u; slope = norm (beta(J)); endif bx = D(:,Jc) \ x1_act; rx = x1_act - D(:,Jc) * bx; if (! isempty (mdl.Robust)) w_act = mdl.Robust.Weights(act); else w_act = mdl.ObservationInfo.Weights(act); endif x1bar_w = sum (w_act .* x1_act) / sum (w_act); ybar_w = sum (w_act .* y_act) / sum (w_act); ry = mdl.Residuals.Raw(act) + slope * rx; xtilde = x1bar_w + rx; ytilde = ybar_w + ry; intercept_avp = mean (ytilde) - slope * mean (xtilde); props = lm_plot_props (args); if (isempty (ax)) ax = gca (); endif FIT_COLOR = [0.9600, 0.4660, 0.1600]; n_total = numel (act); xdata = NaN (n_total, 1); ydata = NaN (n_total, 1); xdata(act) = xtilde; ydata(act) = ytilde; fit_x = linspace (min (xtilde), max (xtilde), 100)'; fit_y = intercept_avp + slope * fit_x; Sxx = sum ((xtilde - mean (xtilde)) .^ 2); tcrit = tinv (0.975, mdl.DFE); se_pred = sqrt (mdl.MSE * (1/n_act + (fit_x - mean (xtilde)).^2 / Sxx)); halfw = tcrit * se_pred; bound_x = [fit_x; NaN; fit_x]; bound_y = [fit_y + halfw; NaN; fit_y - halfw]; hold (ax, 'on'); h(1) = lm_plot_data (ax, xdata, ydata, props); set (h(1), 'DisplayName', 'Adjusted data'); h(2) = line (fit_x, fit_y, 'Color', FIT_COLOR, 'LineStyle', '-', ... 'Marker', 'none', 'Parent', ax, ... 'DisplayName', sprintf ("Fit: y = %g*x", slope)); h(3) = line (bound_x, bound_y, 'Color', FIT_COLOR, 'LineStyle', ':', ... 'Marker', 'none', 'Parent', ax, ... 'DisplayName', '95% conf. bounds'); hold (ax, 'off'); xlabel (ax, ['Adjusted ', label]); ylabel (ax, ['Adjusted ', mdl.ResponseName]); title (ax, ['Added variable plot for ', label]); hleg = legend (ax, 'show'); set (hleg, 'Location', lm_legend_corner (xdata, ydata)); if (nargout == 0) clear h; endif endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {} plot (@var{mdl}) ## @deftypefnx {LinearModel} {} plot (@var{ax}, @var{mdl}) ## @deftypefnx {LinearModel} {@var{h} =} plot (@dots{}) ## ## Create a default diagnostic plot for a fitted linear regression model. ## ## @code{plot (@var{mdl})} creates a plot whose type depends on the ## number of predictors in @var{mdl}. If @var{mdl} has two or more ## predictors, @code{plot} creates an added variable plot for the whole ## model except the constant (intercept) term, equivalent to ## @code{plotAdded (@var{mdl})}. If @var{mdl} has exactly one ## predictor, @code{plot} creates a scatter plot of the data together ## with the fitted curve and its 95% confidence bounds. If @var{mdl} ## has no predictors, @code{plot} creates a histogram of the residuals, ## equivalent to @code{plotResiduals (@var{mdl})}. ## ## For the single-predictor case, the fitted curve and confidence ## bounds are computed with @code{predict}, evaluated at 100 equally ## spaced points spanning the observed range of the predictor when the ## predictor is numeric, or at each level of the predictor when it is ## categorical. Excluded or missing observations appear as @code{NaN} ## in the data and produce gaps in the plotted points. ## ## @code{plot (@var{ax}, @var{mdl})} plots into the axes object ## @var{ax} instead of the current axes returned by @code{gca}. ## ## @code{@var{h} = plot (@dots{})} returns a vector of graphics object ## handles. For the two-or-more-predictor and no-predictor cases, see ## @code{plotAdded} and @code{plotResiduals}, respectively, for the ## meaning of @var{h}. For the single-predictor case, @var{h}(1), ## @var{h}(2), and @var{h}(3) correspond to the data points, the fitted ## curve, and the 95% confidence bounds of the fitted curve, ## respectively. ## ## @end deftypefn function h = plot (this, varargin) [ax, mdl, args] = lm_plot_axes (this, varargin); if (! isempty (args)) error ("plot: Too many input arguments."); endif pred = mdl.PredictorNames; cinfo = mdl.CatLevelInfo; enc_names = mdl.EncPredictorNames; enc_active = any (mdl.TermsMatrix(:, 1:end-1) != 0, 1); active_pred = false (1, numel (pred)); for c = find (enc_active) j = find (strcmp (pred, enc_names{c}), 1); if (isempty (j) && ! isempty (cinfo) && isfield (cinfo, 'names')) for k = 1:numel (cinfo.names) prefix = [cinfo.names{k}, '_']; if (strncmp (enc_names{c}, prefix, numel (prefix))) j = find (strcmp (pred, cinfo.names{k}), 1); break; endif endfor endif if (! isempty (j)) active_pred(j) = true; endif endfor n_active = sum (active_pred); if (n_active == 0) if (isempty (ax)) h = plotResiduals (mdl); else h = plotResiduals (ax, mdl); endif elseif (n_active >= 2) if (isempty (ax)) h = plotAdded (mdl); else h = plotAdded (ax, mdl); endif else j1 = find (active_pred, 1); act = mdl.ObservationInfo.Subset; n_act = sum (act); ci = []; if (! isempty (cinfo) && isfield (cinfo, 'names') ... && ! isempty (cinfo.names)) ci = find (strcmp (cinfo.names, pred{j1})); endif col = mdl.Variables{act, pred{j1}}; if (! isempty (ci)) levels_1 = cinfo.levels{ci}; col_str = lm_col_to_str (col); x_act = zeros (n_act, 1); for L = 1:numel (levels_1) x_act(strcmp (col_str, char (levels_1{L}))) = L; endfor x_grid = (1:numel (levels_1))'; else x_act = double (col(:)); x_grid = linspace (min (x_act), max (x_act), 100)'; endif y_act = mdl.Variables{act, mdl.ResponseName}; [y_fit, yci] = mdl.predict (x_grid); if (isempty (ax)) ax = gca (); endif FIT_COLOR = [0.9600, 0.4660, 0.1600]; n_total = numel (act); xdata = NaN (n_total, 1); ydata = NaN (n_total, 1); xdata(act) = x_act; ydata(act) = y_act; bound_x = [x_grid; NaN; x_grid]; bound_y = [yci(:,1); NaN; yci(:,2)]; props = lm_plot_props ({}); hold (ax, 'on'); h(1) = lm_plot_data (ax, xdata, ydata, props); set (h(1), 'DisplayName', 'Data'); h(2) = line (x_grid, y_fit, 'Color', FIT_COLOR, 'LineStyle', '-', ... 'Marker', 'none', 'Parent', ax, 'DisplayName', 'Fit'); h(3) = line (bound_x, bound_y, 'Color', FIT_COLOR, 'LineStyle', ':', ... 'Marker', 'none', 'Parent', ax, ... 'DisplayName', '95% conf. bounds'); hold (ax, 'off'); if (! isempty (ci)) set (ax, 'XTick', 1:numel (levels_1), 'XTickLabel', levels_1); endif xlabel (ax, pred{j1}); ylabel (ax, mdl.ResponseName); title (ax, [mdl.ResponseName, ' vs. ', pred{j1}]); hleg = legend (ax, 'show'); set (hleg, 'Location', lm_legend_corner (xdata, ydata)); endif if (nargout == 0) clear h; endif endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {} plotInteraction (@var{mdl}, @var{var1}, @var{var2}) ## @deftypefnx {LinearModel} {} plotInteraction (@var{mdl}, @var{var1}, @var{var2}, @var{ptype}) ## @deftypefnx {LinearModel} {} plotInteraction (@var{ax}, @dots{}) ## @deftypefnx {LinearModel} {@var{h} =} plotInteraction (@dots{}) ## ## Plot the interaction effects of two predictors in a fitted linear ## regression model. ## ## @code{plotInteraction (@var{mdl}, @var{var1}, @var{var2})} creates a ## plot of the main effects of @var{var1} and @var{var2} together with ## their conditional effects, with horizontal lines through each effect ## value indicating its 95% confidence interval. @var{var1} and ## @var{var2} are each a character vector or string naming a variable in ## @code{mdl.VariableNames}, or a positive integer indexing into ## @code{mdl.VariableNames}; neither may name the response variable, and ## they must be different variables. ## ## The main effect of a predictor is the change in the adjusted response ## between the two predictor values that produce the minimum and maximum ## adjusted response, with the other predictor averaged over its own ## observed values row by row. For a numeric predictor these two values ## are its observed minimum and maximum; for a categorical predictor ## every level is evaluated and the levels producing the minimum and ## maximum adjusted response are used, so the effect is always ## nonnegative. ## ## The conditional effect of @var{var1} is its effect recomputed with ## @var{var2} additionally held fixed at each of a small set of ## conditioning values, and likewise the conditional effect of ## @var{var2} holds @var{var1} fixed. The conditioning values are the ## observed minimum, mean of the minimum and maximum, and maximum for a ## numeric predictor, or every level for a categorical predictor. When ## the main effect and conditional effect points for a predictor do not ## align vertically, the model exhibits an interaction between ## @var{var1} and @var{var2}. ## ## @code{plotInteraction (@var{mdl}, @var{var1}, @var{var2}, @var{ptype})} ## selects the plot type. @var{ptype} is @qcode{'effects'} (default), as ## described above, or @qcode{'predictions'}, which instead plots the ## adjusted response as a function of @var{var2} for each conditioning ## value of @var{var1} held fixed, evaluated over 101 equally spaced ## points spanning the observed range of @var{var2} when @var{var2} is ## numeric, or at each level of @var{var2} when it is categorical. ## ## @code{plotInteraction (@var{ax}, @dots{})} plots into the axes object ## @var{ax} instead of the current axes returned by @code{gca}. ## ## @code{@var{h} = plotInteraction (@dots{})} returns a vector of line ## handles. When @var{ptype} is @qcode{'effects'}, @code{h(1)} is the ## marker line through the two main effect points, @code{h(2)} and ## @code{h(3)} are the confidence interval lines for the main effects of ## @var{var1} and @var{var2}, and the remaining entries are the ## conditional effect points and their confidence intervals, tagged ## @qcode{'conditional1'} for @var{var1} and @qcode{'conditional2'} for ## @var{var2}. The main effect line objects are tagged @qcode{'main'}. ## When @var{ptype} is @qcode{'predictions'}, each entry in @var{h} ## corresponds to one adjusted response curve, one per conditioning ## value of @var{var1}. ## ## @end deftypefn function h = plotInteraction (this, varargin) [ax, mdl, args] = lm_plot_axes (this, varargin); if (numel (args) < 2) error ("plotInteraction: Not enough input arguments."); endif var1 = args{1}; var2 = args{2}; args = args(3:end); ptype = 'effects'; if (! isempty (args) && (ischar (args{1}) || isstring (args{1}))) ptype = lower (char (args{1})); args = args(2:end); if (! any (strcmp (ptype, {'effects', 'predictions'}))) error ("plotInteraction: PTYPE must be 'effects' or 'predictions'."); endif endif if (! isempty (args)) error ("plotInteraction: Too many input arguments."); endif vnames = mdl.VariableNames; if (ischar (var1) || isstring (var1)) v1name = char (var1); if (isempty (find (strcmp (vnames, v1name)))) error ("plotInteraction: '%s' is not a variable for this fit.", v1name); endif elseif (isnumeric (var1) && isscalar (var1)) if (var1 != fix (var1) || var1 < 1) error (strcat ("plotInteraction: Variable must be specified as a", ... " name or a positive integer.")); endif if (var1 > numel (vnames)) error ("plotInteraction: This model only contains %d variables.", numel (vnames)); endif v1name = vnames{var1}; else error (strcat ("plotInteraction: Variable must be specified as a", ... " name or a positive integer.")); endif if (strcmp (v1name, mdl.ResponseName)) error ("plotInteraction: The variable '%s' is the response in this model.", v1name); endif if (ischar (var2) || isstring (var2)) v2name = char (var2); if (isempty (find (strcmp (vnames, v2name)))) error ("plotInteraction: '%s' is not a variable for this fit.", v2name); endif elseif (isnumeric (var2) && isscalar (var2)) if (var2 != fix (var2) || var2 < 1) error (strcat ("plotInteraction: Variable must be specified as a", ... " name or a positive integer.")); endif if (var2 > numel (vnames)) error ("plotInteraction: This model only contains %d variables.", numel (vnames)); endif v2name = vnames{var2}; else error (strcat ("plotInteraction: Variable must be specified as a", ... " name or a positive integer.")); endif if (strcmp (v2name, mdl.ResponseName)) error ("plotInteraction: The variable '%s' is the response in this model.", v2name); endif if (strcmp (v1name, v2name)) error ("plotInteraction: VAR1 and VAR2 must be different variables."); endif pred = mdl.PredictorNames; cinfo = mdl.CatLevelInfo; ename = mdl.EncPredictorNames; terms = mdl.TermsMatrix; act = mdl.ObservationInfo.Subset; n_act = sum (act); p = numel (pred); beta = mdl.Coefficients.Estimate; V = mdl.CoefficientCovariance; t_crit = tinv (0.975, mdl.DFE); [X_act, is_cat, cat_lvls] = lm_encode_active_predictors (mdl, act, pred, cinfo); j1 = find (strcmp (pred, v1name)); j2 = find (strcmp (pred, v2name)); if (is_cat(j1)) levels_1 = cat_lvls{j1}; n_lv1 = numel (levels_1); g_lv1 = zeros (n_lv1, 1); for L = 1:n_lv1 c_row = lm_interaction_row (X_act, j1, L, pred, cinfo, ename, terms); g_lv1(L) = c_row * beta; endfor [~, i_lo1] = min (g_lv1); [~, i_hi1] = max (g_lv1); lo1 = i_lo1; hi1 = i_hi1; lbl1 = [v1name, ': ', char(levels_1{i_lo1}), ' to ', char(levels_1{i_hi1})]; grid1 = (1:n_lv1)'; grid1_lbls = cellfun (@(s) char (s), levels_1, 'UniformOutput', false); else lo1 = min (X_act(:,j1)); hi1 = max (X_act(:,j1)); lbl1 = [v1name, ': ', num2str(lo1), ' to ', num2str(hi1)]; grid1 = [lo1; (lo1+hi1)/2; hi1]; grid1_lbls = arrayfun (@(v) num2str(v,'%g'), grid1, 'UniformOutput', false); endif if (is_cat(j2)) levels_2 = cat_lvls{j2}; n_lv2 = numel (levels_2); g_lv2 = zeros (n_lv2, 1); for L = 1:n_lv2 c_row = lm_interaction_row (X_act, j2, L, pred, cinfo, ename, terms); g_lv2(L) = c_row * beta; endfor [~, i_lo2] = min (g_lv2); [~, i_hi2] = max (g_lv2); lo2 = i_lo2; hi2 = i_hi2; lbl2 = [v2name, ': ', char(levels_2{i_lo2}), ' to ', char(levels_2{i_hi2})]; grid2 = (1:n_lv2)'; grid2_lbls = cellfun (@(s) char (s), levels_2, 'UniformOutput', false); else lo2 = min (X_act(:,j2)); hi2 = max (X_act(:,j2)); lbl2 = [v2name, ': ', num2str(lo2), ' to ', num2str(hi2)]; grid2 = [lo2; (lo2+hi2)/2; hi2]; grid2_lbls = arrayfun (@(v) num2str(v,'%g'), grid2, 'UniformOutput', false); endif c_hi1 = lm_interaction_row (X_act, j1, hi1, pred, cinfo, ename, terms); c_lo1 = lm_interaction_row (X_act, j1, lo1, pred, cinfo, ename, terms); eff1 = (c_hi1 - c_lo1) * beta; se1 = sqrt (max (0, (c_hi1 - c_lo1) * V * (c_hi1 - c_lo1)')); c_hi2 = lm_interaction_row (X_act, j2, hi2, pred, cinfo, ename, terms); c_lo2 = lm_interaction_row (X_act, j2, lo2, pred, cinfo, ename, terms); eff2 = (c_hi2 - c_lo2) * beta; se2 = sqrt (max (0, (c_hi2 - c_lo2) * V * (c_hi2 - c_lo2)')); n2 = numel (grid2); eff_c1 = zeros (n2, 1); se_c1 = zeros (n2, 1); for k = 1:n2 c_hi = lm_interaction_row (X_act, [j1, j2], [hi1, grid2(k)], pred, cinfo, ename, terms); c_lo = lm_interaction_row (X_act, [j1, j2], [lo1, grid2(k)], pred, cinfo, ename, terms); eff_c1(k) = (c_hi - c_lo) * beta; se_c1(k) = sqrt (max (0, (c_hi - c_lo) * V * (c_hi - c_lo)')); endfor n1 = numel (grid1); eff_c2 = zeros (n1, 1); se_c2 = zeros (n1, 1); for k = 1:n1 c_hi = lm_interaction_row (X_act, [j2, j1], [hi2, grid1(k)], pred, cinfo, ename, terms); c_lo = lm_interaction_row (X_act, [j2, j1], [lo2, grid1(k)], pred, cinfo, ename, terms); eff_c2(k) = (c_hi - c_lo) * beta; se_c2(k) = sqrt (max (0, (c_hi - c_lo) * V * (c_hi - c_lo)')); endfor if (isempty (ax)) ax = gca (); endif cla (ax); DEF_COLOR = [0.1490, 0.5490, 0.8660]; FIT_COLOR = [0.9600, 0.4660, 0.1600]; if (strcmp (ptype, 'effects')) y_main1 = 1; y_cond1 = (2:(1+n2))'; y_main2 = n2 + 4; y_cond2 = ((n2+5):(n2+4+n1))'; hold (ax, 'on'); line ([0, 0], [0.5, n2 + n1 + 4.5], 'LineStyle', ':', 'Marker', 'none', ... 'Color', [0, 0, 0], 'Parent', ax); h(1) = plot (ax, [eff1, eff2], [y_main1, y_main2], ... 'LineStyle', 'none', 'Marker', 'o', 'Color', DEF_COLOR, ... 'Tag', 'main'); h(2) = line ([eff1 - t_crit*se1, eff1 + t_crit*se1], [y_main1, y_main1], ... 'LineStyle', '-', 'Marker', 'none', 'Color', DEF_COLOR, ... 'Parent', ax, 'Tag', 'main'); h(3) = line ([eff2 - t_crit*se2, eff2 + t_crit*se2], [y_main2, y_main2], ... 'LineStyle', '-', 'Marker', 'none', 'Color', DEF_COLOR, ... 'Parent', ax, 'Tag', 'main'); h(4) = plot (ax, eff_c1, y_cond1, ... 'LineStyle', 'none', 'Marker', 'o', 'Color', FIT_COLOR, ... 'Tag', 'conditional1'); for k = 1:n2 h(4+k) = line ([eff_c1(k) - t_crit*se_c1(k), eff_c1(k) + t_crit*se_c1(k)], ... [y_cond1(k), y_cond1(k)], ... 'LineStyle', '-', 'Marker', 'none', 'Color', FIT_COLOR, ... 'Parent', ax, 'Tag', 'conditional1'); endfor h(5+n2) = plot (ax, eff_c2, y_cond2, ... 'LineStyle', 'none', 'Marker', 'o', 'Color', FIT_COLOR, ... 'Tag', 'conditional2'); for k = 1:n1 h(5+n2+k) = line ([eff_c2(k) - t_crit*se_c2(k), eff_c2(k) + t_crit*se_c2(k)], ... [y_cond2(k), y_cond2(k)], ... 'LineStyle', '-', 'Marker', 'none', 'Color', FIT_COLOR, ... 'Parent', ax, 'Tag', 'conditional2'); endfor hold (ax, 'off'); ytl = cell (2 + n1 + n2, 1); ytl{1} = lbl1; for k = 1:n2 ytl{1+k} = [v2name, '=', grid2_lbls{k}]; endfor ytl{2+n2} = lbl2; for k = 1:n1 ytl{2+n2+k} = [v1name, '=', grid1_lbls{k}]; endfor set (ax, 'YTick', [y_main1; y_cond1; y_main2; y_cond2], ... 'YTickLabel', ytl, 'YDir', 'reverse'); ylim (ax, [0.5, n2 + n1 + 4.5]); xlabel (ax, 'Effect'); ylabel (ax, ''); title (ax, ['Interaction of ', v1name, ' and ', v2name]); else ## 'predictions' if (is_cat(j2)) x_grid2 = (1:n_lv2)'; else x_grid2 = linspace (lo2, hi2, 101)'; endif hold (ax, 'on'); line (NaN, NaN, 'Color', 'none', 'Parent', ax, 'DisplayName', v1name); colors = get (ax, 'ColorOrder'); n_colors = rows (colors); for k = 1:n1 y_curve = zeros (numel (x_grid2), 1); for m = 1:numel (x_grid2) c_row = lm_interaction_row (X_act, [j1, j2], [grid1(k), x_grid2(m)], ... pred, cinfo, ename, terms); y_curve(m) = c_row * beta; endfor h(k) = line (x_grid2, y_curve, ... 'Color', colors(mod(k-1, n_colors)+1, :), ... 'LineStyle', '-', 'Marker', 'none', 'Parent', ax, ... 'DisplayName', grid1_lbls{k}); endfor hold (ax, 'off'); if (is_cat(j2)) set (ax, 'XTick', 1:n_lv2, 'XTickLabel', grid2_lbls); endif xlabel (ax, v2name); ylabel (ax, ['Adjusted ', mdl.ResponseName]); title (ax, ['Interaction of ', v1name, ' and ', v2name]); legend (ax, 'show'); endif if (nargout == 0) clear h; endif endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {@var{cmdl} =} compact (@var{mdl}) ## ## Create a compact version of a fitted linear regression model. ## ## @code{@var{cmdl} = compact (@var{mdl})} returns a ## @code{CompactLinearModel} object that retains the coefficient ## estimates, coefficient covariance, fit statistics, model formula, and ## fitting method information of @var{mdl}, but discards the training ## data and everything derived from it. Specifically, the following ## properties of @var{mdl} are not carried over and are unavailable on ## @var{cmdl}: @code{Fitted}, @code{Residuals}, @code{Diagnostics}, ## @code{ObservationInfo}, @code{ObservationNames}, @code{Variables}, ## @code{Steps}, and @code{ModelFitVsNullModel}. ## ## If @var{mdl} was fit using robust regression, the @code{Robust} ## structure is retained on @var{cmdl} except for its @code{Weights} ## field, which is always emptied; @code{RobustWgtFun} and @code{Tune} ## are preserved unchanged. ## ## A @code{CompactLinearModel} object consumes less memory than a ## @code{LinearModel} object and can still be used with @code{predict}, ## @code{feval}, @code{random}, @code{coefCI}, and @code{coefTest}, but ## does not support methods that require the original training data or ## refitting, such as @code{addTerms}, @code{removeTerms}, @code{step}, ## and @code{dwtest}. ## ## @seealso{LinearModel, CompactLinearModel} ## @end deftypefn function CVMdl = compact (this) CVMdl = CompactLinearModel (this); endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {@var{tbl} =} anova (@var{mdl}) ## @deftypefnx {LinearModel} {@var{tbl} =} anova (@var{mdl}, @var{anovatype}) ## @deftypefnx {LinearModel} {@var{tbl} =} anova (@var{mdl}, @qcode{"components"}, @var{sstype}) ## ## Analysis of variance for a linear regression model. ## ## @code{anova (@var{mdl})} returns a table @var{tbl} with component ## ANOVA statistics for every term in @var{mdl} except the constant ## term, computed with hierarchical (@qcode{"h"}) sums of squares. Each ## row gives @code{SumSq}, @code{DF}, @code{MeanSq}, @code{F}, and ## @code{pValue} for the corresponding term; the trailing @qcode{Error} ## row gives @code{SumSq = @var{mdl}.SSE}, @code{DF = @var{mdl}.DFE}, ## @code{MeanSq = @var{mdl}.MSE}, and @code{NaN} for @code{F} and ## @code{pValue}. ## ## MATLAB reports @code{F = 1} and @code{pValue = 0.5} on that ## @qcode{Error} row instead. Those are not results: the row's @code{F} ## is its own @code{MeanSq} divided by itself, so it is @code{1} for every ## data set, and the @code{pValue} follows. MATLAB does not use them ## consistently either, reporting @code{NaN} for the same quantity on the ## @qcode{Residual} row of its summary table. This implementation reports ## @code{NaN} in both places. Every other value in both tables agrees ## with MATLAB. ## ## @code{anova (@var{mdl}, @var{anovatype})} selects ## @qcode{"components"} (default) or @qcode{"summary"}. For ## @qcode{"summary"}, @var{tbl} always contains rows @qcode{Total}, ## @qcode{Model}, and @qcode{Residual}, and additionally @qcode{. Linear} ## and @qcode{. Nonlinear} whenever @var{mdl} contains an interaction ## term or a continuous term of degree greater than 1. @qcode{Total} ## reports @code{@var{mdl}.SST} with @code{DF = NumObservations - 1}; ## @qcode{Model} reports @code{@var{mdl}.SSR} with @code{DF = ## NumCoefficients - HasIntercept}; @qcode{Residual} reports ## @code{@var{mdl}.SSE} with @code{DF = @var{mdl}.DFE}. Whenever the ## data contains two or more observations sharing identical predictor ## values, @var{tbl} additionally contains @qcode{. Lack of fit} and ## @qcode{. Pure error}, splitting @qcode{Residual} into the part ## explained by replicated observations and the remainder. ## ## @code{anova (@var{mdl}, @qcode{"components"}, @var{sstype})} selects ## the sum of squares used for the component table: @code{1} (sequential, ## reduction from adding each term in formula order), @code{2} (reduction ## from adding the term to a model containing every term that does not ## contain it), @qcode{"h"} (default; as Type 2, but a higher-degree ## term in the same continuous variable, such as a squared term, is also ## treated as containing the lower-degree term), or @code{3} (reduction ## from adding the term to a model containing every other term, with ## categorical predictors recoded using sum-to-zero deviation contrasts ## instead of @var{mdl}'s reference-level coding). Because Type 3 uses a ## different coding, its @qcode{Error} row can differ from @var{mdl}.SSE ## and @var{mdl}.DFE when @var{mdl} is missing a lower-order relative of ## one of its terms (e.g. an interaction without one of its main ## effects, or a categorical predictor fit without an intercept). ## ## @end deftypefn function tbl = anova (mdl, varargin) if (numel (varargin) > 2) error ("anova: too many input arguments."); endif anovatype = 'components'; if (numel (varargin) >= 1) anovatype = varargin{1}; if (! ischar (anovatype) ... || ! any (strcmpi (anovatype, {'summary', 'components'}))) error ("anova: ANOVATYPE must be 'summary' or 'components'."); endif endif sstype = 'h'; if (numel (varargin) == 2) if (! strcmpi (anovatype, 'components')) error ("anova: SSTYPE can only be specified with ANOVATYPE 'components'."); endif sstype = varargin{2}; valid_n = isnumeric (sstype) && isscalar (sstype) && any (sstype == [1, 2, 3]); valid_h = ischar (sstype) && strcmpi (sstype, 'h'); if (! valid_n && ! valid_h) error ("anova: SSTYPE must be 1, 2, or 3."); endif endif groups = mdl.TermGroups; nterm = numel (groups); term_cols = {groups.Cols}; term_name = {groups.Name}; if (mdl.HasIntercept) icol = find (strcmp (mdl.CoefficientNames, '(Intercept)')); else icol = []; endif X = mdl.DesignMatrix; y = mdl.ResponseVector (mdl.SubsetMask); w = mdl.WeightVector (mdl.SubsetMask); all_cols = 1:columns (X); if (strcmpi (anovatype, 'summary')) is_nonlinear = false (nterm, 1); for k = 1:nterm parts = strsplit (term_name{k}, ':'); is_nonlinear(k) = (numel (parts) > 1) || ! isempty (strfind (parts{1}, '^')); endfor SumSq = [mdl.SST; mdl.SSR]; DF = [mdl.NumObservations - 1; mdl.NumCoefficients - mdl.HasIntercept]; RowNm = {'Total', 'Model'}; if (any (is_nonlinear)) lin_cols = union (icol, cell2mat (term_cols(! is_nonlinear))); SS_nl = anova_delta_sse (X, y, w, lin_cols, all_cols); DF_nl = numel (all_cols) - numel (lin_cols); SumSq = [SumSq; SumSq(2) - SS_nl; SS_nl]; DF = [DF; DF(2) - DF_nl; DF_nl]; RowNm = [RowNm, {'. Linear', '. Nonlinear'}]; endif SumSq = [SumSq; mdl.SSE]; DF = [DF; mdl.DFE]; RowNm = [RowNm, {'Residual'}]; MeanSq = SumSq ./ DF; F = NaN (numel (SumSq), 1); pValue = NaN (numel (SumSq), 1); for r = 2:(numel (SumSq) - 1) F(r) = MeanSq(r) / mdl.MSE; pValue(r) = f_pvalue (mdl.DFE, DF(r), F(r)); endfor pred_names = mdl.PredictorNames; cat_info = mdl.CatLevelInfo; p_raw = mdl.NumPredictors; tbl_sub = mdl.Variables (mdl.SubsetMask, :); X_raw = anova_decode_raw (tbl_sub, pred_names, cat_info); [~, ~, gidx] = unique (X_raw, 'rows'); pe_ss = 0; pe_df = 0; for g = 1:max (gidx) rows_g = (gidx == g); w_g = w(rows_g); y_g = y(rows_g); wmean = sum (w_g .* y_g) / sum (w_g); pe_ss = pe_ss + sum (w_g .* (y_g - wmean).^2); pe_df = pe_df + sum (rows_g) - 1; endfor if (pe_df > 0) lof_ss = mdl.SSE - pe_ss; lof_df = mdl.DFE - pe_df; lof_ms = lof_ss / lof_df; pe_ms = pe_ss / pe_df; lof_F = lof_ms / pe_ms; lof_p = f_pvalue (pe_df, lof_df, lof_F); SumSq = [SumSq; lof_ss; pe_ss]; DF = [DF; lof_df; pe_df]; MeanSq = [MeanSq; lof_ms; pe_ms]; F = [F; lof_F; NaN]; pValue = [pValue; lof_p; NaN]; RowNm = [RowNm, {'. Lack of fit', '. Pure error'}]; endif else use_seq = isnumeric (sstype) && sstype == 1; use_type3 = isnumeric (sstype) && sstype == 3; extended = ischar (sstype) && strcmpi (sstype, 'h'); if (use_type3) pred_names = mdl.PredictorNames; cat_info = mdl.CatLevelInfo; enc_names = mdl.EncPredictorNames; p_raw = mdl.NumPredictors; if (! isempty (mdl.EncodedPredMatrix)) X_enc = mdl.EncodedPredMatrix; else tbl_sub = mdl.Variables (mdl.SubsetMask, :); X_raw = anova_decode_raw (tbl_sub, pred_names, cat_info); X_enc = reencode_predictors (X_raw, pred_names, cat_info, enc_names); endif ## deviation (effects) coding: reference level becomes -1 enc_pos = 0; for j = 1:p_raw ci = find (strcmp (cat_info.names, pred_names{j})); if (isempty (ci)) enc_pos = enc_pos + 1; else n_lev = numel (cat_info.levels{ci}); block = enc_pos + (1:(n_lev - 1)); is_ref = 1 - sum (X_enc(:, block), 2); X_enc(:, block) = X_enc(:, block) - is_ref; enc_pos = enc_pos + n_lev - 1; endif endfor D_eff = build_design (mdl.TermsMatrix, X_enc); Mm = X \ D_eff; is_hier = (max (max (abs (D_eff - X * Mm))) < 1e-8 * max (max (abs (D_eff)))) ... && (rcond (Mm) > eps); if (is_hier) Hinv = inv (Mm); b = mdl.Coefficients.Estimate; V = mdl.CoefficientCovariance; SumSq = zeros (nterm, 1); DF = zeros (nterm, 1); F = zeros (nterm, 1); pValue = zeros (nterm, 1); for k = 1:nterm Hk = Hinv(term_cols{k}, :); Hb = Hk * b; HVH = Hk * V * Hk'; DF(k) = numel (term_cols{k}); F(k) = (Hb' * (HVH \ Hb)) / DF(k); SumSq(k) = F(k) * DF(k) * mdl.MSE; pValue(k) = f_pvalue (mdl.DFE, DF(k), F(k)); endfor MeanSq = SumSq ./ DF; errSumSq = mdl.SSE; errDF = mdl.DFE; errMeanSq = mdl.MSE; else fit_eff = LinearModel.lm_fit (D_eff, y, w, false); all_cols = 1:columns (D_eff); SumSq = zeros (nterm, 1); DF = zeros (nterm, 1); for k = 1:nterm cmp_cols = setdiff (all_cols, term_cols{k}); [SumSq(k), ~, fit_r] = anova_delta_sse ( ... D_eff, y, w, cmp_cols, all_cols, fit_eff); DF(k) = fit_eff.rank_X - fit_r.rank_X; endfor errSumSq = fit_eff.SSE; errDF = fit_eff.DFE; errMeanSq = fit_eff.MSE; MeanSq = SumSq ./ DF; F = MeanSq / errMeanSq; pValue = NaN (nterm, 1); ok = DF > 0; pValue(ok) = f_pvalue (errDF, DF(ok), F(ok)); endif SumSq = [SumSq; errSumSq]; DF = [DF; errDF]; MeanSq = [MeanSq; errMeanSq]; else if (! use_seq) contain_mx = anova_containment (groups, extended); endif SumSq = zeros (nterm, 1); DF = zeros (nterm, 1); for k = 1:nterm if (use_seq) cmp_cols = union (icol, cell2mat (term_cols(1:k-1))); else keep = true (1, nterm); keep(k) = false; for j = 1:nterm if (j != k && contain_mx(k, j)) keep(j) = false; endif endfor cmp_cols = union (icol, cell2mat (term_cols(keep))); endif full_cols = union (cmp_cols, term_cols{k}); SumSq(k) = anova_delta_sse (X, y, w, cmp_cols, full_cols); DF(k) = numel (term_cols{k}); endfor MeanSq = SumSq ./ DF; F = MeanSq / mdl.MSE; pValue = f_pvalue (mdl.DFE, DF, F); SumSq = [SumSq; mdl.SSE]; DF = [DF; mdl.DFE]; MeanSq = [MeanSq; mdl.MSE]; endif F = [F; NaN]; pValue = [pValue; NaN]; RowNm = [term_name, {'Error'}]; endif tbl = table (SumSq, DF, MeanSq, F, pValue, ... 'VariableNames', {'SumSq', 'DF', 'MeanSq', 'F', 'pValue'}, ... 'RowNames', RowNm(:)); endfunction ## -*- texinfo -*- ## @deftypefn {LinearModel} {@var{NewMdl} =} step (@var{mdl}) ## @deftypefnx {LinearModel} {@var{NewMdl} =} step (@var{mdl}, @var{Name}, @var{Value}) ## ## Improve a fitted linear regression model by one or more steps of ## stepwise term selection. ## ## @code{step} examines whether adding or removing a single term from ## @var{mdl} improves the fit, and returns the resulting model as ## @var{NewMdl}. The original model @var{mdl} is never modified. Unlike ## @code{stepwiselm}, @code{step} performs only one such improvement step ## by default; pass @code{'NSteps'} to allow more. ## ## @code{step} accepts the same @code{'Criterion'}, @code{'PEnter'}, ## @code{'PRemove'}, @code{'NSteps'}, @code{'Verbose'}, @code{'Lower'}, ## and @code{'Upper'} Name-Value options as @code{stepwiselm}, with the ## same defaults, except @code{'NSteps'} defaults to @code{1} rather than ## unlimited. @var{mdl}'s own predictors, weights, excluded observations, ## and categorical variable settings are carried over automatically as ## the starting point for the search. ## ## @code{step} is not available for a model fitted with robust ## regression. ## ## @seealso{stepwiselm, addTerms, removeTerms} ## @end deftypefn function NewMdl = step (mdl, varargin) if (nargin < 1) error ("step: Not enough input arguments."); endif if (! isempty (mdl.Robust)) error ("step: The STEP method is not available with a robust fit."); endif if (mod (numel (varargin), 2) != 0) error ("step: Name-Value arguments must be in pairs."); endif has_nsteps = false; for i = 1:2:numel (varargin) - 1 if ((ischar (varargin{i}) || isstring (varargin{i})) ... && strcmpi (char (varargin{i}), 'NSteps')) has_nsteps = true; endif endfor extra = varargin; if (! has_nsteps) extra = [extra, {'NSteps', 1}]; endif ## A model already fitted stepwise carries the settings the earlier ## search ran under, and 'step' continues that search rather than ## starting a fresh one, so anything not named again is inherited. ## 'Upper' is not: it bounds this call alone. if (! isempty (mdl.Steps)) for nm = {'Criterion', 'Lower', 'PEnter', 'PRemove'} if (! sw_named (extra, nm{1}) && ! isempty (mdl.Steps.(nm{1}))) v = mdl.Steps.(nm{1}); if (isa (v, 'LinearFormula')) v = char (v); endif extra = [extra, {nm{1}, v}]; endif endfor endif cat_vars = {}; if (! isempty (mdl.CatLevelInfo) && isfield (mdl.CatLevelInfo, 'names')) cat_vars = mdl.CatLevelInfo.names; endif nv_list = {'PredictorVars', mdl.PredictorNamesRaw}; if (! isempty (mdl.OrigOpts.Weights)) nv_list = [nv_list, {'Weights', mdl.OrigOpts.Weights}]; endif if (! isempty (mdl.OrigOpts.Exclude)) nv_list = [nv_list, {'Exclude', mdl.OrigOpts.Exclude}]; endif if (! isempty (cat_vars)) nv_list = [nv_list, {'CategoricalVars', cat_vars}]; endif if (mdl.HasIntercept) formula_str = [mdl.ResponseName, ' ~ ', mdl.Formula.LinearPredictor]; elseif (isempty (mdl.Formula.LinearPredictor)) formula_str = [mdl.ResponseName, ' ~ -1']; else formula_str = [mdl.ResponseName, ' ~ ', mdl.Formula.LinearPredictor, ' - 1']; endif NewMdl = stepwiselm (mdl.Variables, formula_str, nv_list{:}, extra{:}); ## Steps.Start is the model stepped from, and the history is appended to ## whatever the model already carried rather than begun again; where it ## carried none, the thresholds are left unreported, as MATLAB leaves ## them. steps = NewMdl.Steps; steps.Start = mdl.Formula; if (isempty (mdl.Steps)) if (! sw_named (extra, 'PEnter')) steps.PEnter = []; endif if (! sw_named (extra, 'PRemove')) steps.PRemove = []; endif else steps.History = [mdl.Steps.History; steps.History(2:end, :)]; endif NewMdl = setSteps (NewMdl, steps); endfunction endmethods methods(Access = public, Static, Hidden) ## weighted least-squares via pivoted QR; returns fit struct function fit = lm_fit (X, y, w, compute_H) if (nargin < 4) compute_H = true; endif n = rows (X); p = columns (X); w = w(:); W_sqrt = sqrt (w); Xw = X .* W_sqrt; yw = y .* W_sqrt; [Q, R, Pperm] = qr (Xw, 0); if (isvector (Pperm)) P_vec = double (Pperm(:)'); else [~, P_vec] = max (Pperm, [], 1); endif dr = abs (diag (R)); if (isempty (dr) || dr(1) == 0) rank_X = 0; else tol = max (size (Xw)) * eps (dr(1)); rank_X = sum (dr > tol); endif beta = zeros (p, 1); active_cols = P_vec(1:rank_X); if (rank_X > 0) R11 = R(1:rank_X, 1:rank_X); Q1 = Q(:, 1:rank_X); gamma = R11 \ (Q1' * yw); beta(active_cols) = gamma; endif Fitted = X * beta; Raw = y - Fitted; n_eff = sum (w > 0); SumLogW = sum (log (w(w > 0))); SSE = sum (w .* Raw.^2); wmean = sum (w .* y) / max (sum (w), eps); SST = sum (w .* (y - wmean).^2); SSR = SST - SSE; DFE = n_eff - rank_X; if (DFE > 0) MSE = SSE / DFE; RMSE = sqrt (MSE); else MSE = NaN; RMSE = NaN; endif CovBeta = zeros (p, p); if (rank_X > 0) R11_inv = R11 \ eye (rank_X); CovBeta(active_cols, active_cols) = MSE * (R11_inv * R11_inv'); endif ## Compute hat matrix and leverage if (rank_X > 0) leverage = sum (Q1.^2, 2); if (compute_H) Q1t = Q1 * Q1'; H = (Q1t ./ W_sqrt) .* W_sqrt'; else H = []; endif else leverage = zeros (n, 1); if (compute_H) H = zeros (n, n); else H = []; endif endif fit.beta = beta; fit.H = H; fit.leverage = leverage; fit.SSE = SSE; fit.SSR = SSR; fit.SST = SST; fit.DFE = DFE; fit.MSE = MSE; fit.RMSE = RMSE; fit.CovBeta = CovBeta; fit.rank_X = rank_X; fit.active_cols = active_cols; fit.Fitted = Fitted; fit.Raw = Raw; fit.SumLogW = SumLogW; fit.n_eff = n_eff; fit.SSW = SSE; endfunction function crit = lm_criteria (fit, has_intercept) p = fit.rank_X; SSE = fit.SSE; SSR = fit.SSR; SST = fit.SST; DFE = fit.DFE; MSE = fit.MSE; ## Observations with zero weight contribute to neither SSE nor SumLogW, ## so the likelihood, and every criterion built on it, is over the n_eff ## observations that carry weight. MATLAB counts all of them and drops ## the 0.5 * sum (log (w)) term; see the LogLikelihood property. n_obs = fit.n_eff; ## The Gaussian log-likelihood at variance SSE/n_obs. SSW is the ## weighted residual sum, which equals SSE for a least-squares fit and ## collapses the middle term to n_obs/2; a robust fit's SSE is a scale ## estimate instead, so the two differ and both are needed. s2 = SSE / n_obs; LogLikelihood = -(n_obs / 2) * log (2 * pi * s2) - fit.SSW / (2 * s2) ... + 0.5 * fit.SumLogW; AIC = -2 * LogLikelihood + 2 * p; dAIC = n_obs - p - 1; if (dAIC > 0) AICc = AIC + (2 * p * (p + 1)) / dAIC; else AICc = Inf; endif BIC = -2 * LogLikelihood + p * log (n_obs); CAIC = BIC + p; R2_ord = SSR / max (SST, eps); if (n_obs > 1 && DFE > 0) R2_adj = 1 - (SSE / DFE) / (SST / (n_obs - 1)); else R2_adj = NaN; endif if (has_intercept && p > 1) df1 = p - 1; Fstat = (SSR / df1) / max (MSE, eps); elseif (! has_intercept && p > 0) df1 = p; Fstat = (SSR / df1) / max (MSE, eps); else df1 = 0; Fstat = NaN; endif if (df1 > 0 && DFE > 0 && Fstat >= 0) Fpval = betainc (DFE / (DFE + df1 * Fstat), DFE / 2, df1 / 2); else Fpval = NaN; endif crit.LogLikelihood = LogLikelihood; crit.AIC = AIC; crit.AICc = AICc; crit.BIC = BIC; crit.CAIC = CAIC; crit.Rsquared = R2_ord; crit.AdjRsquared = R2_adj; crit.Fstat = Fstat; crit.Fpval = Fpval; endfunction function info = sw_extract (mdl0) pred_names = mdl0.PredictorNamesRaw; p_raw = numel (pred_names); cat_info = mdl0.CatLevelInfo; enc_names = mdl0.EncPredictorNames; y_sub = mdl0.ResponseVector (mdl0.SubsetMask); w_sub = mdl0.WeightVector (mdl0.SubsetMask); if (! isempty (mdl0.EncodedPredMatrix)) X_enc_sub = mdl0.EncodedPredMatrix; else tbl_sub = mdl0.Variables (mdl0.SubsetMask, :); X_raw = zeros (rows (tbl_sub), p_raw); for j = 1:p_raw col = tbl_sub.(pred_names{j}); if (iscell (col)) ci = find (strcmp (cat_info.names, pred_names{j})); levels_j = cat_info.levels{ci}; codes = zeros (rows (tbl_sub), 1); for k = 1:numel (levels_j) codes(strcmp (col, levels_j{k})) = k; endfor X_raw(:, j) = codes; elseif (isa (col, 'categorical')) ci = find (strcmp (cat_info.names, pred_names{j})); levels_j = cat_info.levels{ci}; [~, X_raw(:, j)] = ismember (cellstr (col), levels_j); else X_raw(:, j) = double (col); endif endfor X_enc_sub = reencode_predictors (X_raw, pred_names, cat_info, enc_names); endif info.terms_enc = mdl0.TermsMatrix; info.cat_info = cat_info; info.enc_names = enc_names; info.pred_names = pred_names; info.p_raw = p_raw; info.X_enc = X_enc_sub; info.y = y_sub; info.w = w_sub; info.has_intercept = mdl0.HasIntercept; info.n_obs = mdl0.NumObservations; info.orig_opts = mdl0.OrigOpts; info.variables = mdl0.Variables; info.response_name = mdl0.ResponseName; endfunction endmethods endclassdef ## True if NAME appears as a parameter name in the Name-Value list ARGS. function tf = sw_named (args, name) tf = false; for k = 1:2:numel (args) - 1 if ((ischar (args{k}) || isstring (args{k})) ... && strcmpi (char (args{k}), name)) tf = true; return; endif endfor endfunction function opts = lm_parse_nv (nv_args) opt_names = {'Intercept', 'Weights', 'Exclude', 'RobustOpts', ... 'VarNames', 'CategoricalVars', 'ResponseVar', 'PredictorVars'}; def_vals = {true, [], [], [], {}, [], '', {}}; [intercept, weights, exclude, robustopts, varnames, catvars, ... respvar, predvars, rem_args] = parsePairedArguments (opt_names, def_vals, nv_args); opts.PredictorVarsGiven = false; for i = 1:2:numel (nv_args) if ((ischar (nv_args{i}) || isstring (nv_args{i})) ... && strcmpi (char (nv_args{i}), 'PredictorVars')) opts.PredictorVarsGiven = true; endif endfor if (! isempty (rem_args)) error ("LinearModel: Unknown option '%s'.", rem_args{1}); endif opts.Intercept = logical (intercept); opts.Exclude = exclude; opts.CategoricalVars = catvars; opts.ResponseVar = char (respvar); if (isempty (robustopts) || (ischar (robustopts) && strcmpi (robustopts, 'off')) ... || (islogical (robustopts) && ! robustopts)) opts.RobustOpts = []; else rname = 'bisquare'; rtune = []; if (isstruct (robustopts)) if (isfield (robustopts, 'RobustWgtFun') && ! isempty (robustopts.RobustWgtFun)) rname = robustopts.RobustWgtFun; endif if (isfield (robustopts, 'Tune')) rtune = robustopts.Tune; endif elseif (is_function_handle (robustopts)) rname = robustopts; elseif (ischar (robustopts) && ! strcmpi (robustopts, 'on')) rname = robustopts; elseif (! (ischar (robustopts) && strcmpi (robustopts, 'on'))) error ("LinearModel: invalid RobustOpts value."); endif if (is_function_handle (rname)) wfun = rname; if (isempty (rtune)) rtune = 1; endif else switch (lower (char (rname))) case 'andrews' wfun = @(r) (abs (r) < pi) .* sin (max (sqrt (eps), abs (r))) ... ./ max (sqrt (eps), abs (r)); def_tune = 1.339; case 'bisquare' wfun = @(r) (abs (r) < 1) .* (1 - r.^2).^2; def_tune = 4.685; case 'cauchy' wfun = @(r) 1 ./ (1 + r.^2); def_tune = 2.385; case 'fair' wfun = @(r) 1 ./ (1 + abs (r)); def_tune = 1.400; case 'huber' wfun = @(r) 1 ./ max (1, abs (r)); def_tune = 1.345; case 'logistic' wfun = @(r) tanh (max (sqrt (eps), abs (r))) ./ max (sqrt (eps), abs (r)); def_tune = 1.205; case 'ols' wfun = @(r) ones (size (r)); def_tune = 1; case 'talwar' wfun = @(r) 1 * (abs (r) < 1); def_tune = 2.795; case 'welsch' wfun = @(r) exp (-(r.^2)); def_tune = 2.985; otherwise error ("LinearModel: unrecognised RobustWgtFun '%s'.", char (rname)); endswitch if (isempty (rtune)) rtune = def_tune; endif endif opts.RobustOpts = struct ('WgtFun', wfun, 'Tune', rtune); endif if (isempty (weights)) opts.Weights = []; else opts.Weights = double (weights(:)); endif if (isempty (varnames)) opts.VarNames = {}; else opts.VarNames = cellstr (varnames); endif if (isempty (predvars)) opts.PredictorVars = {}; elseif (ischar (predvars) || iscellstr (predvars) || isstring (predvars)) opts.PredictorVars = cellstr (predvars); else opts.PredictorVars = predvars; endif endfunction ## observation-level influence statistics; returns D struct function D = lm_diagnostics (X, y, fit, w) n = rows (X); p = fit.rank_X; h = fit.leverage; Raw = fit.Raw; DFE = fit.DFE; MSE = fit.MSE; RMSE = fit.RMSE; S2_i = (DFE * MSE - w .* Raw.^2 ./ max (1 - h, eps)) / max (DFE - 1, 1); r_std = Raw ./ max (RMSE .* sqrt (max (1 - h, eps)), eps); r_stu = Raw ./ max (sqrt (max (S2_i, eps)) .* sqrt (max (1 - h, eps)), eps); CooksDistance = (w / max (p, 1)) .* r_std.^2 .* h ./ max (1 - h, eps); Dffits = r_stu .* sqrt (h ./ max (1 - h, eps)) .* sqrt (w); CovRatio = (S2_i ./ max (MSE, eps)).^p ./ max (1 - h, eps); p_full = columns (X); active = fit.active_cols; CovB_act = fit.CovBeta(active, active); XtXinv_d = diag (CovB_act) / max (MSE, eps); Dfbetas = NaN (n, p_full); if (p > 0) for i = 1:n xi_act = X(i, active)'; infl = (CovB_act / max (MSE, eps)) * xi_act; denom_base = (1 - h(i)) * sqrt (max (S2_i(i), eps)); for jj = 1:p se_jj = sqrt (max (XtXinv_d(jj), eps)); Dfbetas(i, active(jj)) = infl(jj) * Raw(i) / max (denom_base * se_jj, eps); endfor endfor endif D.Leverage = h; D.CooksDistance = CooksDistance; D.Dffits = Dffits; D.S2_i = S2_i; D.CovRatio = CovRatio; D.Dfbetas = Dfbetas; D.HatMatrix = fit.H; endfunction function mdl2 = lm_refit (mdl, new_terms) opts = mdl.OrigOpts; has_int = any (all (new_terms(:, 1:end-1) == 0, 2)); if (! isempty (mdl.CatLevelInfo) && isfield (mdl.CatLevelInfo, 'names') ... && ! isempty (mdl.CatLevelInfo.names)) cat_vars = mdl.CatLevelInfo.names; else cat_vars = opts.CategoricalVars; endif nv_list = {'Intercept', has_int}; if (! isempty (opts.Weights)) nv_list = [nv_list, {'Weights', opts.Weights}]; endif if (! isempty (opts.Exclude)) nv_list = [nv_list, {'Exclude', opts.Exclude}]; endif if (! isempty (cat_vars)) nv_list = [nv_list, {'CategoricalVars', cat_vars}]; endif mdl2 = fitlm (mdl.Variables, mdl.ResponseName, new_terms, nv_list{:}); endfunction function [ax, mdl, args] = lm_plot_axes (this, rest) if (isscalar (this) && isgraphics (this, 'axes')) ax = this; mdl = rest{1}; args = rest(2:end); else ax = []; mdl = this; args = rest; endif endfunction function props = lm_plot_props (nv_args) opt_names = {'Color', 'Marker', 'MarkerSize', 'MarkerEdgeColor', ... 'MarkerFaceColor', 'LineWidth'}; def_vals = {[0.1490, 0.5490, 0.8660], 'x', 6, 'auto', 'none', 0.5}; [color, marker, markersize, mec, mfc, lw, rem_args] = ... parsePairedArguments (opt_names, def_vals, nv_args); if (! isempty (rem_args)) if (ischar (rem_args{1}) || isstring (rem_args{1})) error ("lm_plot_props: unrecognized property '%s'.", ... char (rem_args{1})); else error (strcat ("lm_plot_props: property name must be a character", ... " vector or string scalar, not a %s."), ... class (rem_args{1})); endif endif props.Color = color; props.Marker = marker; props.MarkerSize = markersize; props.MarkerEdgeColor = mec; props.MarkerFaceColor = mfc; props.LineWidth = lw; endfunction function h = lm_plot_data (ax, xdata, ydata, props) h = plot (ax, xdata, ydata, ... 'LineStyle', 'none', ... 'Color', props.Color, ... 'Marker', props.Marker, ... 'MarkerSize', props.MarkerSize, ... 'MarkerEdgeColor', props.MarkerEdgeColor, ... 'MarkerFaceColor', props.MarkerFaceColor, ... 'LineWidth', props.LineWidth); endfunction function loc = lm_legend_corner (xdata, ydata) xr = xdata(! isnan (xdata)); yr = ydata(! isnan (ydata)); xmid = (min (xr) + max (xr)) / 2; ymid = (min (yr) + max (yr)) / 2; counts = [sum(xr >= xmid & yr >= ymid), sum(xr < xmid & yr >= ymid), ... sum(xr >= xmid & yr < ymid), sum(xr < xmid & yr < ymid)]; locs = {'northeast', 'northwest', 'southeast', 'southwest'}; [~, best_idx] = min (counts); loc = locs{best_idx}; endfunction function c_row = lm_interaction_row (X_act, fix_cols, fix_vals, pred, cinfo, ename, terms) X_rows = X_act; for f = 1:numel (fix_cols) X_rows(:, fix_cols(f)) = fix_vals(f); endfor X_enc = reencode_predictors (X_rows, pred, cinfo, ename); D = build_design (terms, X_enc); c_row = mean (D, 1); endfunction function col_str = lm_col_to_str (col) if (iscell (col)) col_str = col; elseif (isa (col, 'categorical')) col_str = cellstr (col); elseif (isa (col, 'string')) col_str = cellstr (col); else col_str = cellstr (num2str (col(:))); endif endfunction function [X_act, is_cat, cat_lvls] = lm_encode_active_predictors (mdl, act, pred, cinfo) n_act = sum (act); p = numel (pred); X_act = zeros (n_act, p); is_cat = false (1, p); cat_lvls = cell (1, p); for k = 1:p ci = []; if (! isempty (cinfo) && isfield (cinfo, 'names') ... && ! isempty (cinfo.names)) ci = find (strcmp (cinfo.names, pred{k})); endif col = mdl.Variables{act, pred{k}}; if (! isempty (ci)) is_cat(k) = true; levels_k = cinfo.levels{ci}; cat_lvls{k} = levels_k; col_str = lm_col_to_str (col); codes = zeros (n_act, 1); for L = 1:numel (levels_k) codes(strcmp (col_str, char (levels_k{L}))) = L; endfor X_act(:,k) = codes; else X_act(:,k) = double (col(:)); endif endfor endfunction function used = lm_predictors_in_model (pred_all, cinfo, ename) n = numel (pred_all); used = false (1, n); for k = 1:n ci = []; if (! isempty (cinfo) && ! isempty (cinfo.names)) ci = find (strcmp (cinfo.names, pred_all{k})); endif if (isempty (ci)) used(k) = any (strcmp (ename, pred_all{k})); else levels_k = cinfo.levels{ci}; for L = 2:numel (levels_k) nm = sprintf ("%s_%s", pred_all{k}, char (levels_k{L})); if (any (strcmp (ename, nm))) used(k) = true; endif endfor endif endfor endfunction function C = lm_effects_contrasts (mdl) if (! any (any (mdl.TermsMatrix(:, 1:end-1) != 0))) C = []; return; endif pred_all = mdl.PredictorNames; cinfo = mdl.CatLevelInfo; ename = mdl.EncPredictorNames; pred = pred_all (lm_predictors_in_model (pred_all, cinfo, ename)); p = numel (pred); act = mdl.ObservationInfo.Subset; [X_act, is_cat, cat_lvls] = lm_encode_active_predictors (mdl, act, pred, cinfo); C = zeros (p, mdl.NumCoefficients); for j = 1:p if (is_cat(j)) x_lo = 1; x_hi = numel (cat_lvls{j}); else x_lo = min (X_act(:,j)); x_hi = max (X_act(:,j)); endif X_hi_rows = X_act; X_hi_rows(:,j) = x_hi; X_lo_rows = X_act; X_lo_rows(:,j) = x_lo; X_hi_enc = reencode_predictors (X_hi_rows, pred, cinfo, ename); X_lo_enc = reencode_predictors (X_lo_rows, pred, cinfo, ename); D_hi = build_design (mdl.TermsMatrix, X_hi_enc); D_lo = build_design (mdl.TermsMatrix, X_lo_enc); C(j,:) = mean (D_hi - D_lo, 1); endfor endfunction function IC = lm_interaction_contrasts (mdl) pred_all = mdl.PredictorNames; cinfo = mdl.CatLevelInfo; ename = mdl.EncPredictorNames; pred = pred_all (lm_predictors_in_model (pred_all, cinfo, ename)); p = numel (pred); IC = struct ('OwnGridRows', {cell(1, p)}, 'Pairs', {cell(p, p)}); if (p < 2) return; endif act = mdl.ObservationInfo.Subset; terms = mdl.TermsMatrix; tpred = terms(:, 1:p); [X_act, is_cat, cat_lvls] = lm_encode_active_predictors (mdl, act, pred, cinfo); grids = cell (1, p); for j = 1:p if (is_cat(j)) n_lv = numel (cat_lvls{j}); grids{j} = (1:n_lv)'; rows_j = zeros (n_lv, mdl.NumCoefficients); for L = 1:n_lv rows_j(L,:) = lm_interaction_row (X_act, j, L, pred, cinfo, ename, terms); endfor IC.OwnGridRows{j} = rows_j; else lo = min (X_act(:,j)); hi = max (X_act(:,j)); grids{j} = [lo; (lo + hi) / 2; hi]; endif endfor for j1 = 1:p for j2 = 1:p if (j1 == j2) continue; endif shared = tpred(:,j1) > 0 & tpred(:,j2) > 0; if (! any (shared)) continue; endif grid1 = grids{j1}; if (is_cat(j2)) grid2 = grids{j2}; else lo2 = min (X_act(:,j2)); hi2 = max (X_act(:,j2)); mid2 = (lo2 + hi2) / 2; deg2 = max (tpred(shared, j2)); extra_n = max (0, deg2 + 1 - 3); if (extra_n > 0) q = linspace (lo2, hi2, extra_n + 2); grid2 = sort ([lo2; mid2; hi2; q(2:end-1)']); else grid2 = [lo2; mid2; hi2]; endif endif n1 = numel (grid1); n2 = numel (grid2); rows = zeros (n1 * n2, mdl.NumCoefficients); idx = 0; for a = 1:n1 for b = 1:n2 idx = idx + 1; rows(idx,:) = lm_interaction_row (X_act, [j1, j2], [grid1(a), grid2(b)], ... pred, cinfo, ename, terms); endfor endfor IC.Pairs{j1,j2} = struct ('grid1', grid1, 'grid2', grid2, 'rows', rows); endfor endfor endfunction function fit = lm_robust_fit (X, y, w, wgtfun, tune) n = rows (X); p = columns (X); w = w(:); sw = sqrt (w); ## A robust fit's SSE is sigma^2 * DFE, a robust scale estimate rather than ## the weighted residual sum, so the two enter the log-likelihood ## separately; lm_criteria takes the residual sum from SSW. The ## 0.5 * sum (log (w)) correction of the OLS path does not apply here, sigma ## being formed from residuals already divided by sqrt (w) and so carrying ## no dependence on the scale of the weights. SumLogW = 0; n_eff = n; Xw = X .* sw; yw = y .* sw; beta = Xw \ yw; [~, R] = qr (Xw, 0); E = Xw / R; h = min (0.9999, sum (E.^2, 2)); adjfactor = 1 ./ sqrt (max (1 - h, eps)); DFE = n - p; if (DFE <= 0) fit.beta = beta; fit.H = zeros (n, n); fit.leverage = zeros (n, 1); fit.SSE = NaN; fit.SSR = NaN; fit.SST = NaN; fit.DFE = DFE; fit.MSE = NaN; fit.RMSE = NaN; fit.CovBeta = NaN (p, p); fit.rank_X = p; fit.active_cols = 1:p; fit.Fitted = X * beta; fit.Raw = y - fit.Fitted; fit.RobustWeights = ones (n, 1); fit.SumLogW = SumLogW; fit.n_eff = n_eff; fit.SSW = sum (w .* fit.Raw.^2); return; endif ols_s = norm (y - X * beta) / sqrt (DFE); tiny_s = 1e-6 * std (y); tolD = sqrt (eps); iterlim = 50; iter = 0; beta0 = zeros (size (beta)); wts = ones (n, 1); while (iter == 0 || any (abs (beta - beta0) > tolD * max (abs (beta), abs (beta0)))) iter = iter + 1; if (iter > iterlim) break; endif ## The prior weights enter through the fit and the leverage; the weight ## function scores the raw residual, as MATLAB's does. Dividing by ## sqrt (w) here would make a precisely measured observation less likely ## to be flagged as an outlier, not more. r = (y - X * beta); radj = r .* adjfactor; rs = sort (abs (radj)); s = median (rs(max (1, p):end)) / 0.6745; wts = wgtfun(radj / (max (s, tiny_s) * tune)); beta0 = beta; ww = sqrt (w .* wts); beta = (X .* ww) \ (y .* ww); endwhile r = (y - X * beta); radj = r .* adjfactor; rs = sort (abs (radj)); mad_s = median (rs(max (1, p):end)) / 0.6745; if (all (wts < tolD | wts > 1 - tolD)) included = wts > 1 - tolD; robust_s = norm (r(included)) / sqrt (max (sum (included) - p, eps)); else st = max (mad_s, tiny_s) * tune; u = radj / st; phi = u .* wgtfun(u); delta = 0.0001; u1 = u - delta; phi0 = u1 .* wgtfun(u1); u1 = u + delta; phi1 = u1 .* wgtfun(u1); dphi = (phi1 - phi0) / (2 * delta); m1 = mean (dphi); m2 = sum ((1 - h) .* phi.^2) / (n - p); K = 1 + (p / n) * (1 - m1) / m1; robust_s = K * sqrt (m2) * st / m1; endif sigma = max (robust_s, sqrt ((ols_s^2 * p^2 + robust_s^2 * n) / (p^2 + n))); RI = R \ eye (p); CovBeta = (RI * RI') * sigma^2; ww = sqrt (w .* wts); Xwf = X .* ww; [Qf, ~] = qr (Xwf, 0); Q1t = Qf * Qf'; H = (Q1t ./ ww) .* ww'; Fitted = X * beta; Raw = y - Fitted; ybar = mean (y); SSR = sum ((Fitted - ybar).^2); SSE = sigma^2 * DFE; SST = SSE + SSR; fit.beta = beta; fit.H = H; fit.leverage = h; fit.SSE = SSE; fit.SSR = SSR; fit.SST = SST; fit.DFE = DFE; fit.MSE = sigma^2; fit.RMSE = sigma; fit.CovBeta = CovBeta; fit.rank_X = p; fit.active_cols = 1:p; fit.Fitted = Fitted; fit.Raw = Raw; fit.RobustWeights = wts; fit.SumLogW = SumLogW; fit.n_eff = n_eff; fit.SSW = sum (w .* Raw.^2); endfunction function [delta, fit_f, fit_r] = anova_delta_sse (X, y, w, cols_reduced, cols_full, fit_f) if (nargin < 6 || isempty (fit_f)) fit_f = LinearModel.lm_fit (X(:, cols_full), y, w, false); endif fit_r = LinearModel.lm_fit (X(:, cols_reduced), y, w, false); delta = fit_r.SSE - fit_f.SSE; endfunction function contain_mx = anova_containment (groups, extended) nterm = numel (groups); factor_list = cell (nterm, 1); for k = 1:nterm parts = strsplit (groups(k).Name, ':'); fl = struct ('var', {}, 'exp', {}); for f = 1:numel (parts) p = strsplit (parts{f}, '^'); if (numel (p) == 2) fl(end+1) = struct ('var', p{1}, 'exp', str2double (p{2})); else fl(end+1) = struct ('var', p{1}, 'exp', 1); endif endfor factor_list{k} = fl; endfor contain_mx = false (nterm, nterm); for i = 1:nterm for j = 1:nterm if (i == j) continue; endif fi = factor_list{i}; fj = factor_list{j}; ok = true; for f = 1:numel (fi) match = false; for g = 1:numel (fj) if (strcmp (fi(f).var, fj(g).var)) if (extended) match = (fj(g).exp >= fi(f).exp); else match = (fj(g).exp == fi(f).exp); endif if (match) break; endif endif endfor if (! match) ok = false; break; endif endfor contain_mx(i, j) = ok; endfor endfor endfunction function X_raw = anova_decode_raw (tbl_sub, pred_names, cat_info) p_raw = numel (pred_names); X_raw = zeros (rows (tbl_sub), p_raw); for j = 1:p_raw col = tbl_sub.(pred_names{j}); if (iscell (col)) ci = find (strcmp (cat_info.names, pred_names{j})); levels_j = cat_info.levels{ci}; codes = zeros (rows (tbl_sub), 1); for k = 1:numel (levels_j) codes(strcmp (col, levels_j{k})) = k; endfor X_raw(:, j) = codes; elseif (isa (col, 'categorical')) ci = find (strcmp (cat_info.names, pred_names{j})); levels_j = cat_info.levels{ci}; [~, X_raw(:, j)] = ismember (cellstr (col), levels_j); else X_raw(:, j) = double (col); endif endfor endfunction function p = f_pvalue (dfe, df, Fstat) p = betainc (dfe ./ (dfe + df .* Fstat), dfe / 2, df / 2); endfunction %!demo %! %! ## Simple linear regression with a single predictor. %! ## Ten runners record their weekly training distance and their finish %! ## time in a 10k race. We fit a straight line through this data and %! ## look at the fitted coefficients, then use predict to estimate the %! ## finish time for a runner who trains a distance not in the sample. %! Distance = [10; 15; 20; 25; 30; 35; 40; 45; 50; 55]; %! Time = [58; 55; 52; 50; 47; 45; 43; 41; 40; 38]; %! X = Distance; %! y = Time; %! %! ## Fit the model and inspect the estimated slope and intercept. %! mdl = fitlm (X, y) %! %! ## Predict the finish time for a runner training 32 km per week. %! ypred = predict (mdl, 32) %!demo %! %! ## Multiple linear regression with two predictors, followed by a %! ## confidence interval on the coefficients. %! ## Thirteen coffee shops report their weekly foot traffic and the %! ## number of items on their menu, along with weekly revenue. We fit a %! ## model with both predictors, then use coefCI to see how precisely %! ## each coefficient is estimated. %! Traffic = [120; 150; 90; 200; 175; 60; 220; 140; 100; 190; 80; 210; 130]; %! MenuSize = [8; 12; 6; 15; 10; 5; 18; 9; 7; 14; 6; 16; 11]; %! Revenue = [1450; 1820; 1010; 2400; 2050; 700; 2650; 1700; 1150; ... %! 2300; 900; 2500; 1600]; %! X = [Traffic, MenuSize]; %! y = Revenue; %! %! ## Fit the model with both predictors together. %! mdl = fitlm (X, y) %! %! ## Check how tight the 95% confidence interval is on each coefficient. %! ci = coefCI (mdl) %!demo %! %! ## Growing a model with addTerms and predicting with the richer model. %! ## We model fuel economy from the carsmall data set using weight and %! ## horsepower as main effects only. addTerms then brings in the %! ## weight-horsepower interaction without needing to refit by hand, and %! ## predict shows how the estimate for a new car changes once that %! ## interaction is included. %! load carsmall %! X = [Weight, Horsepower]; %! y = MPG; %! %! ## Fit the additive model first. %! mdl = fitlm (X, y); %! %! ## Add the interaction between weight and horsepower. %! mdl2 = addTerms (mdl, 'x1:x2'); %! %! ## Compare predictions from both models for the same new car. %! Xnew = [3200, 120]; %! ypred1 = predict (mdl, Xnew) %! ypred2 = predict (mdl2, Xnew) %!demo %! %! ## Simplifying a model with removeTerms and comparing fit quality. %! ## We fit the full Hald cement model with all four ingredients, then %! ## use removeTerms to drop the weakest predictor and refit %! ## automatically. Comparing SSE before and after shows how little %! ## explanatory power that ingredient was actually contributing. %! load hald %! X = ingredients; %! y = heat; %! %! ## Fit the model with all four ingredients. %! mdl = fitlm (X, y); %! %! ## Drop the third ingredient and refit on the same data. %! mdl2 = removeTerms (mdl, 'x3'); %! %! ## Compare how much the error sum of squares changed. %! sse_full = mdl.SSE %! sse_reduced = mdl2.SSE %!demo %! %! ## Testing a linear hypothesis and checking residual autocorrelation. %! ## Twelve patients are given a drug at different doses over different %! ## treatment durations, and a recovery score is recorded. coefTest %! ## checks whether the dose and duration coefficients are actually %! ## equal, and dwtest separately checks whether the residuals still %! ## carry a leftover pattern the model failed to capture. %! Dose = [10; 15; 20; 25; 30; 35; 12; 18; 22; 28; 32; 38]; %! Duration = [5; 7; 9; 11; 13; 15; 6; 8; 10; 12; 14; 16]; %! Recovery = [42; 48; 55; 60; 68; 74; 45; 52; 58; 65; 71; 78]; %! X = [Dose, Duration]; %! y = Recovery; %! mdl = fitlm (X, y); %! %! ## Test H0: the Dose and Duration coefficients are equal. %! H = [0 1 -1]; %! [p, F, r] = coefTest (mdl, H) %! %! ## Check for autocorrelation left over in the residuals. %! [pdw, dw] = dwtest (mdl) %!demo %! %! ## Checking residuals against fitted values. %! ## We fit a mileage model on the carsmall data set using weight and %! ## horsepower, then plot the raw residuals against the fitted values. %! ## A pattern in this plot, rather than a random scatter, would suggest %! ## the linear model is missing some curvature in the relationship. %! load carsmall %! X = [Weight, Horsepower]; %! y = MPG; %! mdl = fitlm (X, y); %! plotResiduals (mdl, 'fitted') %!demo %! %! ## Spotting influential observations with Cook's distance. %! ## Sixteen houses are matched by size and age to a sale price, but one %! ## house was sold far above what its size and age would predict. After %! ## fitting the model, plotDiagnostics with the cookd option highlights %! ## that single observation as having outsized influence on the fit. %! Size = [80; 95; 110; 120; 65; 140; 100; 130; 90; 150; 75; 105; ... %! 115; 85; 135; 125]; %! Age = [5; 10; 3; 8; 20; 2; 15; 6; 12; 1; 18; 9; 4; 14; 7; 11]; %! Price = [200; 230; 260; 280; 150; 320; 240; 300; 210; 340; 170; ... %! 250; 270; 190; 500; 290]; %! X = [Size, Age]; %! y = Price; %! mdl = fitlm (X, y); %! plotDiagnostics (mdl, 'cookd') %!demo %! %! ## Comparing the size of each predictor's effect, alongside a %! ## hypothesis test on the model as a whole. %! ## We fit a mileage model on the carsmall data set using weight and %! ## horsepower. plotEffects draws each coefficient's estimate with its %! ## confidence interval side by side, and coefTest checks whether %! ## weight's effect is significantly different from horsepower's. %! load carsmall %! X = [Weight, Horsepower]; %! y = MPG; %! mdl = fitlm (X, y); %! %! ## Visualize the relative size of each predictor's effect. %! plotEffects (mdl) %! %! ## Test whether the two coefficients differ significantly. %! H = [0 1 -1]; %! [p, F, r] = coefTest (mdl, H) %!shared mdl, X, y, n %! n = 20; %! X = [1:n; (1:n).^2]' / n; %! y = X * [3; -1] + 0.2 * sin ((1:n)'); %! mdl = fitlm (X, y); %!test %! ## scalar fit-quality %! assert_equal (mdl.NumObservations, 20); %! assert_equal (mdl.NumCoefficients, 3); %! assert_equal (mdl.NumVariables, 3); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.NumEstimatedCoefficients, 3); %! assert_equal (mdl.DFE, 17); %! assert_equal (mdl.SSE, 0.386545331386823, 1e-9); %! assert_equal (mdl.SSR, 583.523874670959, 1e-6); %! assert_equal (mdl.SST, 583.910420002346, 1e-6); %! assert_equal (mdl.MSE, 0.0227379606698351, 1e-10); %! assert_equal (mdl.RMSE, 0.150791116017606, 1e-10); %! assert_equal (mdl.Rsquared.Ordinary, 0.999338005765704, 1e-10); %! assert_equal (mdl.Rsquared.Adjusted, 0.999260124091081, 1e-10); %! assert_equal (mdl.LogLikelihood, 11.0836133807695, 1e-6); %! assert_equal (mdl.ModelCriterion.AIC, -16.1672267615389, 1e-6); %! assert_equal (mdl.ModelCriterion.AICc, -14.6672267615389, 1e-6); %! assert_equal (mdl.ModelCriterion.BIC, -13.180029940877, 1e-6); %! assert_equal (mdl.ModelCriterion.CAIC, -10.180029940877, 1e-6); %! assert_equal (mdl.ModelFitVsNullModel.Fstat, 12831.4909842738, 1e-4); %! assert_equal (strcmp (mdl.ModelFitVsNullModel.NullModel, 'constant'), true); %!test %! ## Steps is empty for a non-stepwise fit, as in GeneralizedLinearModel %! assert_equal (size (mdl.Steps), [0, 0]); %!test %! ## a table column the formula never mentions is not a predictor of the fit %! xa = (1:n)' / n; %! xb = cos ((1:n)'); %! gc = categorical (mod ((1:n)', 3)); %! Tu = table (xa, xb, gc, y, 'VariableNames', {'xa', 'xb', 'gc', 'y'}); %! m = fitlm (Tu, 'y ~ xa + xb'); %! assert_equal (m.PredictorNames, {'xa'; 'xb'}); %! assert_equal (m.NumPredictors, 2); %! assert_equal (m.VariableNames, {'xa'; 'xb'; 'gc'; 'y'}); %! assert_equal (m.NumVariables, 4); %! assert_equal (m.VariableInfo.InModel', [true, true, false, false]); %!test %! ## the unused variable being categorical no longer breaks type 3 sums of %! ## squares, which indexed past the end of the encoded matrix %! xa = (1:n)' / n; %! xb = cos ((1:n)'); %! gc = categorical (mod ((1:n)', 3)); %! Tu = table (xa, xb, gc, y, 'VariableNames', {'xa', 'xb', 'gc', 'y'}); %! m = fitlm (Tu, 'y ~ xa + xb'); %! t = anova (m, 'components', 3); %! assert_equal (t.Properties.RowNames, {'xa'; 'xb'; 'Error'}); %! assert_equal (numel (t.SumSq), 3); %!test %! ## a model fit with no predictors at all reports none %! mc = fitlm (X, y, 'constant'); %! assert_equal (isempty (mc.PredictorNames), true); %! assert_equal (mc.NumPredictors, 0); %! assert_equal (any (mc.VariableInfo.InModel), false); %!test %! ## a terms matrix that zeroes a predictor out drops it, as a formula does %! mt = fitlm (X, y, [0 0 0; 1 0 0]); %! mf = fitlm (X, y, 'y ~ x1'); %! assert_equal (mt.PredictorNames, {'x1'}); %! assert_equal (mt.NumPredictors, 1); %! assert_equal (mt.VariableInfo.InModel', [true, false, false]); %! assert_equal (mf.PredictorNames, {'x1'}); %! assert_equal (mf.NumPredictors, 1); %!test %! ## a predictor reached only through an interaction is used, and stays %! mi = fitlm (X, y, 'y ~ x1 + x1:x2'); %! assert_equal (mi.PredictorNames, {'x1'; 'x2'}); %! assert_equal (mi.NumPredictors, 2); %! assert_equal (mi.VariableInfo.InModel', [true, true, false]); %!test %! ## addTerms and stepwise selection search the candidates, not the chosen %! mc = fitlm (X, y, 'constant'); %! m1 = addTerms (mc, 'x1'); %! assert_equal (m1.PredictorNames, {'x1'}); %! assert_equal (m1.NumCoefficients, 2); %!test %! ## constant-only model: SSR is exactly zero, SSE equals SST %! mc = fitlm (X, y, 'constant'); %! assert_equal (mc.SSE, 583.910420002346, 1e-6); %! assert_equal (mc.SSR, 0, 1e-12); %! assert_equal (mc.SSE, mc.SST, 1e-12); %!test %! ## coefficient estimates, SE, tStat, names, covariance, schema %! assert_equal (mdl.Coefficients.Estimate, [0.1161886778; 2.508451491; -0.9788353298], 1e-7); %! assert_equal (mdl.Coefficients.SE, [0.112185831; 0.4920818186; 0.02276108523], 1e-8); %! assert_equal (mdl.Coefficients.tStat, [1.035680502; 5.097630913; -43.00477415], 1e-6); %! assert_equal (all (mdl.Coefficients.pValue >= 0 & mdl.Coefficients.pValue <= 1), true); %! assert_equal (isequal (mdl.CoefficientNames, {'(Intercept)', 'x1', 'x2'}), true); %! assert_equal (isequal (mdl.CoefficientNames, mdl.Coefficients.Properties.RowNames(:)'), true); %! assert_equal (size (mdl.CoefficientCovariance), [3, 3]); %! assert_equal (diag (mdl.CoefficientCovariance), [0.0125857; 0.242145; 0.000518067], 1e-6); %! assert_equal (width (mdl.Coefficients), 4); %! assert_equal (isequal (mdl.Coefficients.Properties.VariableNames, ... %! {'Estimate','SE','tStat','pValue'}), true); %!test %! ## fitted values, predict(), residual columns (obs 1-3), schema %! assert_equal (mdl.Fitted, y - mdl.Residuals.Raw, 1e-10); %! yp = predict (mdl, X); %! assert_equal (size (yp), [20, 1]); %! assert_equal (yp(1), 0.192669485827491, 1e-10); %! assert_equal (yp(2), 0.171266760882256, 1e-10); %! assert_equal (mdl.Residuals.Raw(1:3), [0.075624711134088; 0.110592724482880; -0.023756501342530], 1e-10); %! assert_equal (mdl.Residuals.Pearson(1:3), [0.501519672586403; 0.733416711830473; -0.157545762442370], 1e-9); %! assert_equal (mdl.Residuals.Standardized(1:3), [0.632246516521578; 0.844226394951239; -0.172381368754725], 1e-8); %! assert_equal (mdl.Residuals.Studentized(1:3), [0.620710275056923; 0.836747864205268; -0.167380843634378], 1e-6); %! assert_equal (width (mdl.Residuals), 4); %! assert_equal (isequal (mdl.Residuals.Properties.VariableNames, ... %! {'Raw','Pearson','Studentized','Standardized'}), true); %!test %! ## diagnostics %! H = mdl.Diagnostics.HatMatrix; %! assert_equal (size (H), [20, 20]); %! assert_equal (H, H', 1e-10); %! assert_equal (H * H, H, 1e-8); %! assert_equal (H(1,1), 0.370779220779221, 1e-10); %! assert_equal (H(1,2), 0.298051948051948, 1e-10); %! assert_equal (mdl.Diagnostics.Leverage(1:3), [0.370779220779221; 0.245283663704716; 0.164718614718615], 1e-10); %! assert_equal (mdl.Diagnostics.CooksDistance(1:3), [0.078517048682575; 0.077211407930332; 0.001953301452841], 1e-8); %! assert_equal (mdl.Diagnostics.S2_i(1:3), [0.023591009857798; 0.023146223303229; 0.024116854077430], 1e-8); %! assert_equal (mdl.Diagnostics.CovRatio(1:3), [1.774933176573401; 1.397661919176034; 1.428481535363283], 1e-6); %! assert_equal (mdl.Diagnostics.Dffits(1:3), [0.476480465355394; 0.477020506700835; -0.074329411030064], 1e-6); %! assert_equal (size (mdl.Diagnostics.Dfbetas), [20, 3]); %! assert_equal (width (mdl.Diagnostics), 7); %! assert_equal (isequal (mdl.Diagnostics.Properties.VariableNames, ... %! {'Leverage','CooksDistance','Dffits','S2_i', ... %! 'CovRatio','Dfbetas','HatMatrix'}), true); %!test %! ## ObservationInfo, VariableInfo, names, Formula, Variables %! assert_equal (width (mdl.ObservationInfo), 4); %! assert_equal (height (mdl.ObservationInfo), 20); %! assert_equal (isequal (mdl.ObservationInfo.Properties.VariableNames, ... %! {'Weights','Excluded','Missing','Subset'}), true); %! assert_equal (all (mdl.ObservationInfo.Weights == 1), true); %! assert_equal (all (mdl.ObservationInfo.Subset == ... %! (! mdl.ObservationInfo.Missing & ! mdl.ObservationInfo.Excluded)), true); %! assert_equal (width (mdl.VariableInfo), 4); %! assert_equal (height (mdl.VariableInfo), 3); %! assert_equal (isequal (mdl.VariableInfo.Properties.VariableNames, ... %! {'Class','Range','InModel','IsCategorical'}), true); %! assert_equal (mdl.VariableInfo.InModel(strcmp (mdl.VariableNames, 'y')), false); %! assert_equal (all (mdl.VariableInfo.InModel(! strcmp (mdl.VariableNames, 'y'))), true); %! assert_equal (mdl.ResponseName, 'y'); %! assert_equal (isequal (mdl.PredictorNames, {'x1';'x2'}), true); %! assert_equal (isequal (mdl.VariableNames, {'x1';'x2';'y'}), true); %! assert_equal (mdl.Formula.HasIntercept, true); %! assert_equal (mdl.Formula.LinearPredictor, '1 + x1 + x2'); %! assert_equal (mdl.Formula.NTerms, 3); %! assert_equal (strcmp (mdl.Variables.Properties.VariableNames{end}, 'y'), true); %!test %! ## NaN in predictor drops the row from the fit %! X2 = X; X2(2,1) = NaN; %! m = fitlm (X2, y); %! assert_equal (m.NumObservations, 19); %! assert_equal (m.ObservationInfo.Missing(2), true); %! assert_equal (m.ObservationInfo.Subset(2), false); %! assert_equal (isnan (m.Fitted(2)), true); %! assert_equal (m.SSE, 0.370339572851658, 1e-9); %! assert_equal (m.SST, 547.616796178045, 1e-6); %! assert_equal (m.Coefficients.Estimate, ... %! [0.0641300185953764; 2.68263140079657; -0.985345792254554], 1e-7); %! yp = predict (m, X2); %! assert_equal (isnan (yp(2)), true); %! assert_equal (! isnan (yp(1)), true); %! assert_equal (size (m.Diagnostics.HatMatrix), [20, 20]); %! assert_equal (m.Diagnostics.Leverage(1), 0.488485648300892, 1e-8); %! assert_equal (m.Diagnostics.CooksDistance(1), 0.38266162627176, 1e-6); %!test %! ## NaN in response drops the row but predict still works normally since X has no NaN %! y3 = y; y3(5) = NaN; %! m = fitlm (X, y3); %! assert_equal (m.NumObservations, 19); %! assert_equal (m.ObservationInfo.Missing(5), true); %! assert_equal (m.Fitted(5), -0.457777594993880, 1e-10); %! assert_equal (isnan (m.Residuals.Raw(5)), true); %! assert_equal (m.SSE, 0.337042910721425, 1e-9); %! assert_equal (m.SST, 558.654961265991, 1e-6); %! assert_equal (m.Coefficients.Estimate, ... %! [0.145131680993155; 2.4865383021829; -0.979635081226208], 1e-7); %! yp = predict (m, X); %! assert_equal (yp(5), -0.45777759499388, 1e-8); %! assert_equal (yp(1), 0.22047684204099, 1e-8); %! assert_equal (m.Fitted, yp, 1e-12); %! assert_equal (size (m.Diagnostics.HatMatrix), [20, 20]); %! assert_equal (m.Diagnostics.Leverage(1), 0.386399650026734, 1e-8); %!test %! ## multiple NaN rows drop all affected observations from the fit %! X4 = X; X4([2,8,14],2) = NaN; %! m = fitlm (X4, y); %! assert_equal (sum (m.ObservationInfo.Missing), 3); %! assert_equal (m.NumObservations, 17); %! assert_equal (m.SSE, 0.261285495635633, 1e-9); %! assert_equal (m.SSR, 527.635694805749, 1e-6); %! assert_equal (m.SST, 527.896980301385, 1e-6); %! assert_equal (m.Coefficients.Estimate, ... %! [0.0986395043600395; 2.3735792982821; -0.97106191310122], 1e-7); %! assert_equal (size (m.Diagnostics.HatMatrix), [20, 20]); %! assert_equal (sum (m.Diagnostics.Leverage), 3, 1e-8); %!test %! ## exclude by index and exclude by logical vector give identical results %! m = fitlm (X, y, 'Exclude', [3, 7]); %! excl = false (n, 1); excl([3, 7]) = true; %! m2 = fitlm (X, y, 'Exclude', excl); %! assert_equal (m.NumObservations, 18); %! assert_equal (sum (m.ObservationInfo.Excluded), 2); %! assert_equal (m.Fitted(3), 0.045673486217021, 1e-10); %! assert_equal (m.Fitted(7), -1.416779188276219, 1e-10); %! assert_equal (isnan (m.Residuals.Raw(3)), true); %! assert_equal (isnan (m.Residuals.Raw(7)), true); %! assert_equal (m.Coefficients.Estimate, m2.Coefficients.Estimate, 1e-12); %! assert_equal (m.Coefficients.Estimate, ... %! [0.118938102486219; 2.43606890944554; -0.974833228191174], 1e-7); %! ype = predict (m); %! assert_equal (size (ype), [20, 1]); %! assert_equal (! isnan (ype(3)) && ! isnan (ype(7)), true); %! [~, yci] = predict (m); %! assert_equal (yci(1,1), -0.0283122762458446, 1e-10); %! assert_equal (yci(1,2), 0.412312049343719, 1e-10); %! assert_equal (size (m.Diagnostics.HatMatrix), [20, 20]); %! assert_equal (m.Diagnostics.Leverage(1), 0.437780279893411, 1e-8); %! assert_equal (m.Diagnostics.CooksDistance(1), 0.110112457355807, 1e-7); %!test %! ## an excluded row is fitted, but keeps neither residuals nor diagnostics %! m = fitlm (X, y, 'Exclude', [3, 7]); %! assert_equal (m.Fitted(3), predict (m, X(3,:)), 1e-12); %! assert_equal (isnan (m.Residuals.Pearson(3)), true); %! assert_equal (isnan (m.Residuals.Studentized(3)), true); %! assert_equal (isnan (m.Residuals.Standardized(3)), true); %! assert_equal (m.Diagnostics.Leverage(3), 0); %! assert_equal (isnan (m.Diagnostics.CooksDistance(3)), true); %!test %! ## weighting does not change which rows carry a fitted value %! m = fitlm (X, y, 'Weights', ones (n, 1) / n, 'Exclude', [1, 3]); %! assert_equal (m.Fitted(1), 0.146089374212009, 1e-10); %! assert_equal (m.Fitted(2), 0.133765115244874, 1e-10); %! assert_equal (isnan (m.Residuals.Raw(1)), true); %!test %! ## a missing predictor is the one case that leaves the fitted value NaN %! X2 = X; X2(2,1) = NaN; %! m = fitlm (X2, y); %! assert_equal (isnan (m.Fitted(2)), true); %! assert_equal (m.Fitted(1), 0.148994299022478, 1e-10); %! assert_equal (isnan (m.Residuals.Raw(2)), true); %!test %! ## NaN and exclude together remove both the missing and the excluded row %! X6 = X; X6(1,1) = NaN; %! m = fitlm (X6, y, 'Exclude', [2]); %! assert_equal (m.NumObservations, 18); %! assert_equal (m.ObservationInfo.Missing(1), true); %! assert_equal (m.ObservationInfo.Excluded(2), true); %! assert_equal (m.SSE, 0.342515396265007, 1e-9); %! assert_equal (m.Coefficients.Estimate, ... %! [-0.0735450184226009; 3.17679029176988; -1.0045827469016], 1e-7); %!test %! ## weighted least squares produces different SSE and stores the weights %! w = abs (sin ((1:n)')) + 0.1; %! m = fitlm (X, y, 'Weights', w); %! assert_equal (m.SSE, 0.363519720897775, 1e-10); %! assert_equal (m.ObservationInfo.Weights, w, 1e-15); %! assert_equal (m.SST, 4.419834786423099e+02, 1e-8); %! [yp, yci] = predict (m, [0.5 0.25; 1.0 1.0]); %! assert_equal (yp(1), 1.106748776307639, 1e-10); %! assert_equal (yp(2), 1.593185531572655, 1e-10); %! assert_equal (yci(1,1), 0.763985050242272, 1e-10); %! assert_equal (yci(1,2), 1.449512502373006, 1e-10); %! assert_equal (m.Diagnostics.Leverage(1), 0.421642939812731, 1e-8); %! assert_equal (m.Diagnostics.Leverage(2), 0.301314342928707, 1e-8); %! assert_equal (m.Diagnostics.HatMatrix(1,1), 0.421642939812731, 1e-8); %! assert_equal (m.Diagnostics.CooksDistance(1), 0.0728569335883748, 1e-7); %! assert_equal (m.Diagnostics.CovRatio(1), 1.96611264276187, 1e-6); %!test %! ## uniform weights scale internals but leave point estimates unchanged %! m = fitlm (X, y, 'Weights', 2 * ones (n, 1)); %! assert_equal (m.Coefficients.Estimate, mdl.Coefficients.Estimate, 1e-10); %!test %! ## the weighted log-likelihood does not depend on the scale of the weights %! ## (MATLAB's does, moving by n/2 * log (c); see the LogLikelihood property) %! xw = [1;2;3;4;5;6;7;8]; %! yw = [2.1;3.9;6.2;7.8;10.1;12.2;13.8;16.1]; %! w = [0.5;1;2;1;3;1;0.25;4]; %! m1 = fitlm (xw, yw, 'Weights', w); %! m2 = fitlm (xw, yw, 'Weights', 100 * w); %! assert_equal (m2.LogLikelihood, m1.LogLikelihood, 1e-12); %! assert_equal (m2.ModelCriterion.AIC, m1.ModelCriterion.AIC, 1e-12); %!test %! ## the weighted log-likelihood is R's logLik.lm, term for term %! xw = [1;2;3;4;5;6;7;8]; %! yw = [2.1;3.9;6.2;7.8;10.1;12.2;13.8;16.1]; %! w = [0.5;1;2;1;3;1;0.25;4]; %! m = fitlm (xw, yw, 'Weights', w); %! nw = 8; %! rL = 0.5 * (sum (log (w)) - nw * (log (2*pi) + 1 - log (nw) + log (m.SSE))); %! assert_equal (m.LogLikelihood, rL, 1e-12); %! assert_equal (m.LogLikelihood, 4.55223335786031, 1e-12); %!test %! ## a zero weight drops the observation from n as well as from DFE, as in R %! xw = [1;2;3;4;5;6;7;8]; %! yw = [2.1;3.9;6.2;7.8;10.1;12.2;13.8;16.1]; %! w = [0.5;1;0;1;3;1;0.25;4]; %! m = fitlm (xw, yw, 'Weights', w); %! wp = w(w > 0); %! nw = numel (wp); %! rL = 0.5 * (sum (log (wp)) - nw * (log (2*pi) + 1 - log (nw) + log (m.SSE))); %! assert_equal (m.LogLikelihood, rL, 1e-12); %! assert_equal (m.LogLikelihood, 4.68069254977738, 1e-12); %! assert_equal (m.DFE, 5); %! assert_equal (m.NumObservations, 8); %!test %! ## an unweighted fit carries no weight term and agrees with R2024a %! xw = [1;2;3;4;5;6;7;8]; %! yw = [2.1;3.9;6.2;7.8;10.1;12.2;13.8;16.1]; %! m = fitlm (xw, yw); %! assert_equal (m.LogLikelihood, 3.51016777175681, 1e-12); %! assert_equal (m.ModelCriterion.AIC, -3.02033554351362, 1e-12); %!test %! ## an unweighted robust fit's log-likelihood matches R2024a exactly %! xr = [1;2;3;4;5;6;7;8;9;10]; %! yr = [2.1;3.9;6.2;7.8;10.1;12.2;13.8;16.1;18.0;30.0]; %! m = fitlm (xr, yr, 'RobustOpts', 'bisquare'); %! assert_equal (m.LogLikelihood, -38.7915287023322, 1e-10); %! assert_equal (m.SSE, 18.9400079498975, 1e-10); %!test %! ## a weighted robust fit matches R2024a in coefficients, SSE and likelihood %! xr = [1;2;3;4;5;6;7;8;9;10]; %! yr = [2.1;3.9;6.2;7.8;10.1;12.2;13.8;16.1;18.0;30.0]; %! w = [0.5;1;2;1;3;1;0.25;4;1;2]; %! m = fitlm (xr, yr, 'RobustOpts', 'bisquare', 'Weights', w); %! assert_equal (m.Coefficients.Estimate, ... %! [0.0606142999263569; 2.00273390115041], 1e-10); %! assert_equal (m.SSE, 19.6032720154597, 1e-10); %! assert_equal (m.MSE, 2.45040900193246, 1e-10); %! assert_equal (m.LogLikelihood, -62.7197604655733, 1e-9); %!test %! ## the robust log-likelihood takes SSE and the residual sum separately %! xr = [1;2;3;4;5;6;7;8;9;10]; %! yr = [2.1;3.9;6.2;7.8;10.1;12.2;13.8;16.1;18.0;30.0]; %! m = fitlm (xr, yr, 'RobustOpts', 'bisquare'); %! r = m.Residuals.Raw; %! nr = 10; %! s2 = m.SSE / nr; %! assert_equal (m.LogLikelihood, ... %! -(nr/2) * log (2*pi*s2) - sum (r.^2) / (2*s2), 1e-12); %!test %! ## constant linear and default modelspecs behave as expected %! m = fitlm (X, y, 'constant'); %! assert_equal (m.NumCoefficients, 1); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! m2 = fitlm (X, y, 'linear'); %! m3 = fitlm (X, y, []); %! assert_equal (m2.NumCoefficients, 3); %! assert_equal (m2.Coefficients.Estimate, mdl.Coefficients.Estimate, 1e-12); %! assert_equal (m3.Coefficients.Estimate, mdl.Coefficients.Estimate, 1e-12); %!test %! ## purequadratic modelspec produces the expected term count %! m = fitlm (X, y, 'purequadratic'); %! assert_equal (m.NumCoefficients, 5); %!test %! ## interactions modelspec term count and coefficients are verified %! m = fitlm (X, y, 'interactions'); %! assert_equal (m.NumCoefficients, 4); %! assert_equal (m.SSE, 0.383859187927621, 1e-9); %! assert_equal (m.Coefficients.Estimate, ... %! [0.157640728038039; 2.08542680311791; -0.929682701072813; -0.031208018255475], 1e-7); %!test %! ## quadratic modelspec is rank deficient for this design and drops one coefficient %! m = fitlm (X, y, 'quadratic'); %! assert_equal (m.NumCoefficients, 6); %! assert_equal (m.SSE, 0.315784637443501, 1e-9); %! assert_equal (m.Coefficients.Estimate, ... %! [0.447436249544699; -2.44859403731902; -0.0121968798776254; ... %! -1.36755100280532; 0; 0.0318176901083297], 1e-7); %! drop = find (m.Coefficients.SE == 0); %! assert_equal (numel (drop), 1); %! assert_equal (isnan (m.Coefficients.tStat(drop)), true); %!test %! ## full modelspec with two predictors matches interactions exactly %! m = fitlm (X, y, 'full'); %! m2 = fitlm (X, y, 'interactions'); %! assert_equal (m.NumCoefficients, 4); %! assert_equal (m.Coefficients.Estimate, m2.Coefficients.Estimate, 1e-10); %! assert_equal (m.Coefficients.Estimate, ... %! [0.157640728038039; 2.08542680311791; -0.929682701072813; -0.031208018255475], 1e-7); %!test %! ## full modelspec with three predictors includes the three way interaction term %! X3 = [X, cos((1:n)' * pi / n)]; %! m = fitlm (X3, y, 'full'); %! assert_equal (m.NumCoefficients, 8); %! assert_equal (any (strcmp (m.CoefficientNames, 'x1:x2:x3')), true); %! assert_equal (m.SSE, 0.231331066631196, 1e-8); %! idx3 = find (strcmp (m.CoefficientNames, 'x1:x2:x3')); %! assert_equal (m.Coefficients.Estimate(idx3), 0.514890561912964, 1e-6); %!test %! ## full modelspec without an intercept drops the intercept coefficient %! m = fitlm (X, y, 'full', 'Intercept', false); %! assert_equal (m.NumCoefficients, 3); %! assert_equal (! any (strcmp (m.CoefficientNames, '(Intercept)')), true); %! assert_equal (m.Coefficients.Estimate, ... %! [3.232987312533958; -1.041484635851565; 0.0324190990982863], 1e-7); %!test %! ## a p column terms matrix produces a model with no intercept %! m = fitlm (X, y, [1 0; 0 1]); %! assert_equal (m.NumCoefficients, 2); %! assert_equal (! any (strcmp (m.CoefficientNames, '(Intercept)')), true); %! assert_equal (m.Coefficients.Estimate, [2.96142161317611; -0.997248749443286], 1e-7); %!test %! ## a p plus one column terms matrix produces a model with an intercept %! m = fitlm (X, y, [0 0 0; 1 0 0; 0 1 0]); %! assert_equal (m.NumCoefficients, 3); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.Coefficients.Estimate, ... %! [0.116188677790207; 2.50845149057086; -0.978835329825186], 1e-7); %!test %! ## a table with a Wilkinson formula fits the same model and predicts on a table %! T = table (X(:,1), X(:,2), y, 'VariableNames', {'a','b','resp'}); %! m = fitlm (T, 'resp ~ a + b'); %! assert_equal (m.NumCoefficients, 3); %! assert_equal (m.ResponseName, 'resp'); %! assert_equal (m.Coefficients.Estimate, mdl.Coefficients.Estimate, 1e-8); %! Xt = table ([0.5;1.0], [0.25;1.0], 'VariableNames', {'a','b'}); %! yp = predict (m, Xt); %! assert_equal (yp(1), 1.125705590619342, 1e-10); %! assert_equal (yp(2), 1.645804838535884, 1e-10); %!test %! ## a matrix with a Wilkinson formula string fits the same model as the matrix alone %! m = fitlm (X, y, 'y ~ x1 + x2'); %! assert_equal (m.NumCoefficients, 3); %! assert_equal (m.Coefficients.Estimate, mdl.Coefficients.Estimate, 1e-8); %!test %! ## a pure interaction formula keeps EncPredictorNames aligned with TermsMatrix %! mi = fitlm (X, y, 'y ~ x1:x2'); %! assert_equal (numel (mi.EncPredictorNames), columns (mi.TermsMatrix) - 1); %! assert_equal (mi.NumCoefficients, 2); %! assert_equal (mi.CoefficientNames, {'(Intercept)', 'x1:x2'}); %! assert_equal (mi.Coefficients.Estimate, ... %! [-0.755813941484483; -0.876953077491396], 1e-9); %! assert_equal (predict (mi), mi.Fitted, 1e-10); %! fig = figure ('visible', 'off'); %! h = plot (mi); %! assert_equal (numel (h), 3); %! assert_equal (get (get (gca, 'Title'), 'String'), 'Added variable plot for x1:x2'); %! assert_equal (get (get (gca, 'XLabel'), 'String'), 'Adjusted x1:x2'); %! assert_equal (get (h(2), 'DisplayName'), 'Fit: y = -0.876953*x'); %! close (fig); %!test %! ## a table input with the default formula fits the same model as the matrix %! T3 = table (X(:,1), X(:,2), y, 'VariableNames', {'x1','x2','y'}); %! m = fitlm (T3); %! assert_equal (m.ResponseName, 'y'); %! assert_equal (m.Coefficients.Estimate, mdl.Coefficients.Estimate, 1e-8); %!test %! ## VarNames sets custom names and ResponseVar overrides the response name %! m = fitlm (X, y, 'VarNames', {'alpha','beta','resp'}); %! assert_equal (m.ResponseName, 'resp'); %! assert_equal (isequal (m.PredictorNames, {'alpha';'beta'}), true); %! assert_equal (any (strcmp (m.CoefficientNames, 'alpha')), true); %! assert_equal (any (strcmp (m.CoefficientNames, 'beta')), true); %! m2 = fitlm (X, y, 'VarNames', {'a','b','r'}, 'ResponseVar', 'r'); %! assert_equal (m2.ResponseName, 'r'); %!test %! ## a rank deficient design matrix leaves the dropped coefficients as NaN across the board %! X_rd = [ones(n,1), X, X(:,1)+X(:,2)]; %! m = fitlm (X_rd, y); %! assert_equal (m.NumCoefficients, 5); %! assert_equal (m.NumEstimatedCoefficients, 3); %! drop = find (m.Coefficients.SE == 0); %! assert_equal (numel (drop), 2); %! assert_equal (all (isnan (m.Coefficients.tStat(drop))), true); %! assert_equal (all (isnan (m.Coefficients.pValue(drop))), true); %! assert_equal (m.SST, 5.839104200023459e+02, 1e-8); %! assert_equal (all (all (m.CoefficientCovariance(drop,:) == 0)), true); %! yp = predict (m, X_rd); %! assert_equal (size (yp), [n, 1]); %! assert_equal (! any (isnan (yp)), true); %! assert_equal (size (m.Diagnostics.Dfbetas), [20, 5]); %! assert_equal (all (isnan (m.Diagnostics.Dfbetas(:, drop)(:))), true); %! assert_equal (m.Diagnostics.Leverage(1), 0.370779220779221, 1e-8); %!test %! ## Intercept=false %! mni = fitlm (X, y, 'Intercept', false); %! assert_equal (mni.NumCoefficients, 2); %! assert_equal (mni.Formula.HasIntercept, false); %! assert_equal (! any (strcmp (mni.CoefficientNames, '(Intercept)')), true); %! [yp, yci] = predict (mni, [0.5 0.25; 1.0 1.0]); %! assert_equal (yp(1), 1.231398619227234, 1e-10); %! assert_equal (yp(2), 1.964172863732825, 1e-10); %! assert_equal (yci(1,1), 1.001262470857215, 1e-10); %!test %! ## p-column terms matrix %! m_p = fitlm (X, y, [1 0; 0 1]); %! assert_equal (m_p.NumCoefficients, 2); %! assert_equal (! any (strcmp (m_p.CoefficientNames, '(Intercept)')), true); %!test %! ## p+1 column terms matrix %! m_p1 = fitlm (X, y, [0 0 0; 1 0 0; 0 1 0]); %! assert_equal (m_p1.NumCoefficients, 3); %! assert_equal (m_p1.CoefficientNames{1}, '(Intercept)'); %!test %! ## table Wilkinson formula %! T = table (X(:,1), X(:,2), y, 'VariableNames', {'a','b','resp'}); %! mf = fitlm (T, 'resp ~ a + b'); %! assert_equal (mf.NumCoefficients, 3); %! assert_equal (mf.ResponseName, 'resp'); %! assert_equal (mf.Coefficients.Estimate, mdl.Coefficients.Estimate, 1e-8); %! Xt = table ([0.5;1.0], [0.25;1.0], 'VariableNames', {'a','b'}); %! yp = predict (mf, Xt); %! assert_equal (yp(1), 1.125705590619342, 1e-10); %! assert_equal (yp(2), 1.645804838535884, 1e-10); %!test %! ## matrix Wilkinson formula %! mfm = fitlm (X, y, 'y ~ x1 + x2'); %! assert_equal (mfm.NumCoefficients, 3); %! assert_equal (mfm.Coefficients.Estimate, mdl.Coefficients.Estimate, 1e-8); %!test %! ## table default %! T3 = table (X(:,1), X(:,2), y, 'VariableNames', {'x1','x2','y'}); %! mt = fitlm (T3); %! assert_equal (mt.ResponseName, 'y'); %! assert_equal (mt.Coefficients.Estimate, mdl.Coefficients.Estimate, 1e-8); %!test %! ## VarNames sets custom names %! vn = fitlm (X, y, 'VarNames', {'alpha','beta','resp'}); %! assert_equal (vn.ResponseName, 'resp'); %! assert_equal (isequal (vn.PredictorNames, {'alpha';'beta'}), true); %! assert_equal (any (strcmp (vn.CoefficientNames, 'alpha')), true); %! assert_equal (any (strcmp (vn.CoefficientNames, 'beta')), true); %!test %! ## ResponseVar overrides VarNames %! rv = fitlm (X, y, 'VarNames', {'a','b','r'}, 'ResponseVar', 'r'); %! assert_equal (rv.ResponseName, 'r'); %!test %! ## rank-deficient matrix %! X_rd = [ones(n,1), X, X(:,1)+X(:,2)]; %! m_rd = fitlm (X_rd, y); %! assert_equal (m_rd.NumCoefficients, 5); %! assert_equal (m_rd.NumEstimatedCoefficients, 3); %! drop = find (m_rd.Coefficients.SE == 0); %! assert_equal (numel (drop), 2); %! assert_equal (all (isnan (m_rd.Coefficients.tStat(drop))), true); %! assert_equal (all (isnan (m_rd.Coefficients.pValue(drop))), true); %! assert_equal (m_rd.SST, 5.839104200023459e+02, 1e-8); %! assert_equal (all (all (m_rd.CoefficientCovariance(drop,:) == 0)), true); %! yp = predict (m_rd, X_rd); %! assert_equal (size (yp), [n, 1]); %! assert_equal (! any (isnan (yp)), true); %! assert_equal (yp(1:5), [0.192669485827486; 0.171266760882252; ... %! 0.0519805029545; -0.165189287955771; ... %! -0.480242611848561], 1e-10); %!test %! ## predict: ypred and default CI at new points %! [yp, yci] = predict (mdl, [0.5 0.25; 1.0 1.0]); %! assert_equal (yp(1), 1.125705590619347, 1e-10); %! assert_equal (yp(2), 1.645804838535894, 1e-10); %! assert_equal (yci(1,1), 0.810180780547215, 1e-10); %! assert_equal (yci(1,2), 1.441230400691478, 1e-10); %! assert_equal (yci(2,1), 0.858229321851723, 1e-10); %! assert_equal (yci(2,2), 2.433380355220066, 1e-10); %!test %! ## predict: observation interval %! [~, yci] = predict (mdl, [0.5 0.25; 1.0 1.0], 'Prediction', 'observation'); %! assert_equal (yci(1,1), 0.677632064105988, 1e-10); %! assert_equal (yci(1,2), 1.573779117132706, 1e-10); %!test %! ## predict: alpha 0.01 %! [~, yci] = predict (mdl, [0.5 0.25; 1.0 1.0], 'Alpha', 0.01); %! assert_equal (yci(1,1), 0.692272619570008, 1e-10); %! assert_equal (yci(1,2), 1.559138561668685, 1e-10); %!test %! ## predict: simultaneous CI %! [~, yci] = predict (mdl, [0.5 0.25; 1.0 1.0], 'Simultaneous', true); %! assert_equal (yci(1,1), 0.662572505689338, 1e-10); %! assert_equal (yci(1,2), 1.588838675549355, 1e-10); %!test %! ## predict: no Xnew returns all rows including training %! [yp, yci] = predict (mdl); %! assert_equal (size (yp), [20, 1]); %! assert_equal (size (yci), [20, 2]); %! assert_equal (yp(1), 0.192669485827490, 1e-10); %! assert_equal (yp(2), 0.171266760882255, 1e-10); %! assert_equal (yci(1,1), -0.001052067982566, 1e-10); %! assert_equal (yci(1,2), 0.386391039637546, 1e-10); %!test %! ## predict: NaN predictor propagates to NaN output and CI %! [yp, yci] = predict (mdl, [0.5 0.25; NaN 1.0; 1.0 1.0]); %! assert_equal (yp(1), 1.125705590619347, 1e-10); %! assert_equal (isnan (yp(2)), true); %! assert_equal (yp(3), 1.645804838535894, 1e-10); %! assert_equal (isnan (yci(2,1)), true); %! assert_equal (isnan (yci(2,2)), true); %!test %! ## predict: categorical model predictions at group centres %! Xc = [1;1;1;2;2;2;3;3;3]; %! yc = [2.1;2.3;1.9; 4.1;3.9;4.2; 6.3;5.8;6.1]; %! m_cat = fitlm (Xc, yc, 'linear', 'CategoricalVars', 1); %! [yp, yci] = predict (m_cat, [1;2;3]); %! assert_equal (yp(1), 2.099999999999998, 1e-10); %! assert_equal (yp(2), 4.066666666666667, 1e-10); %! assert_equal (yp(3), 6.066666666666666, 1e-10); %! assert_equal (yci(1,1), 1.80971256321669, 1e-10); %! assert_equal (yci(1,2), 2.3902874367833, 1e-10); %! assert_equal (yci(2,1), 3.77637922988336, 1e-10); %! assert_equal (yci(2,2), 4.35695410344997, 1e-10); %! assert_equal (yci(3,1), 5.77637922988336, 1e-10); %! assert_equal (yci(3,2), 6.35695410344997, 1e-10); %!test %! ## predict: interaction model %! [yp, yci] = predict (fitlm (X, y, 'interactions'), [0.5 0.25; 1.0 1.0]); %! assert_equal (yp(1), 0.964032452046850, 1e-10); %! assert_equal (yp(2), 1.282176811827644, 1e-10); %! assert_equal (yci(1,1), -0.110763003580605, 1e-10); %! assert_equal (yci(1,2), 2.038827907674306, 1e-10); %!test %! ## predict: weighted model, ypred and CI %! w = (1:n)' / sum (1:n); %! mw = fitlm (X, y, 'Weights', w); %! [yp, yci] = predict (mw, [0.5 0.25; 1.0 1.0]); %! assert_equal (yp(1), 1.15833357370544, 1e-10); %! assert_equal (yp(2), 1.74408669002694, 1e-10); %! assert_equal (yci(1,1), 0.802165170771357, 1e-10); %! assert_equal (yci(1,2), 1.51450197663953, 1e-10); %! assert_equal (yci(2,1), 0.69968979253134, 1e-10); %! assert_equal (yci(2,2), 2.78848358752254, 1e-10); %!test %! ## predict: no-intercept model, ypred and CI %! mni = fitlm (X, y, 'Intercept', false); %! [yp, yci] = predict (mni, [0.5 0.25; 1.0 1.0]); %! assert_equal (yp(1), 1.23139861922723, 1e-10); %! assert_equal (yp(2), 1.96417286373283, 1e-10); %! assert_equal (yci(1,1), 1.00126247085704, 1e-10); %! assert_equal (yci(1,2), 1.46153476759743, 1e-10); %! assert_equal (yci(2,1), 1.51833851162232, 1e-10); %! assert_equal (yci(2,2), 2.41000721584333, 1e-10); %!test %! ## predict: observation interval combined with simultaneous bound %! [~, yci] = predict (mdl, [0.5 0.25; 1.0 1.0], ... %! 'Prediction', 'observation', 'Simultaneous', true); %! assert_equal (yci(1,1), 0.46801507632267, 1e-10); %! assert_equal (yci(1,2), 1.78339610491601, 1e-10); %! assert_equal (yci(2,1), 0.399032373599106, 1e-10); %! assert_equal (yci(2,2), 2.89257730347266, 1e-10); %!test %! ## output is 2x1 double column vector %! ysim = random (mdl, [0.5, 0.25; 1.0, 1.0]); %! assert_equal (size (ysim), [2, 1]); %! assert_equal (class (ysim), 'double'); %! assert_equal (iscolumn (ysim), true); %!test %! ## single row input gives 1x1 output %! assert_equal (size (random (mdl, [0.5, 0.25])), [1, 1]); %!test %! ## predict values are exact and noise added is finite %! ypred = predict (mdl, [0.5, 0.25; 1.0, 1.0]); %! ysim = random (mdl, [0.5, 0.25; 1.0, 1.0]); %! assert_equal (ypred(1), 1.125705590619342, 1e-10); %! assert_equal (ypred(2), 1.645804838535884, 1e-10); %! assert_equal (all (isfinite (ysim - ypred)), true); %!test %! ## NaN predictor row gives NaN output, other rows stay finite %! ysim = random (mdl, [0.5, 0.25; NaN, 1.0; 1.0, 1.0]); %! assert_equal (size (ysim), [3, 1]); %! assert_equal (isfinite (ysim(1)), true); %! assert_equal (isnan (ysim(2)), true); %! assert_equal (isfinite (ysim(3)), true); %!test %! ## two sequential calls produce different output %! ya = random (mdl, [0.5, 0.25]); %! yb = random (mdl, [0.5, 0.25]); %! assert_equal (! isequal (ya, yb), true); %!test %! ## random: table input, full training data, weighted and no-intercept %! ## models all give finite output of the expected size %! Xt = table ([0.5;1.0], [0.25;1.0], 'VariableNames', {'x1','x2'}); %! mw = fitlm (X, y, 'Weights', (1:n)' / sum (1:n)); %! mni = fitlm (X, y, 'Intercept', false); %! assert_equal (size (random (mdl, Xt)), [2, 1]); %! assert_equal (all (isfinite (random (mdl, Xt))), true); %! assert_equal (size (random (mdl, X)), [20, 1]); %! assert_equal (sum (isnan (random (mdl, X))), 0); %! assert_equal (all (isfinite (random (mw, [0.5, 0.25; 1.0, 1.0]))), true); %! assert_equal (all (isfinite (random (mni, [0.5, 0.25; 1.0, 1.0]))), true); %!test %! yf = feval (mdl, [0.5 0.25; 1.0 1.0; 0.2 0.04]); %! assert_equal (size (yf), [3, 1]); %! assert_equal (class (yf), 'double'); %! assert_equal (yf(1), 1.125705590619342, 1e-10); %! assert_equal (yf(2), 1.645804838535884, 1e-10); %! assert_equal (yf(3), 0.578725562711373, 1e-10); %! assert_equal (yf, predict (mdl, [0.5 0.25; 1.0 1.0; 0.2 0.04]), 1e-10); %!test %! yf = feval (mdl, [0.5; 1.0; 0.2], [0.25; 1.0; 0.04]); %! assert_equal (size (yf), [3, 1]); %! assert_equal (iscolumn (yf), true); %! assert_equal (yf, predict (mdl, [0.5 0.25; 1.0 1.0; 0.2 0.04]), 1e-10); %!test %! yf = feval (mdl, [0.5, 1.0, 0.2], [0.25, 1.0, 0.04]); %! assert_equal (size (yf), [1, 3]); %! assert_equal (isrow (yf), true); %! assert_equal (yf(:), predict (mdl, [0.5 0.25; 1.0 1.0; 0.2 0.04]), 1e-10); %!test %! yf = feval (mdl, 0.5, 0.25); %! assert_equal (size (yf), [1, 1]); %! assert_equal (yf, 1.125705590619342, 1e-10); %! assert_equal (yf, predict (mdl, [0.5 0.25]), 1e-10); %!test %! yf = feval (mdl, 0.5, [0.1; 0.2; 0.3]); %! assert_equal (size (yf), [3, 1]); %! assert_equal (yf(1), 1.272530890093120, 1e-10); %! assert_equal (yf(2), 1.174647357110602, 1e-10); %! assert_equal (yf(3), 1.076763824128083, 1e-10); %! assert_equal (yf, predict (mdl, [0.5 0.1; 0.5 0.2; 0.5 0.3]), 1e-10); %!test %! yf = feval (mdl, [0.1; 0.5; 0.9], 0.25); %! assert_equal (size (yf), [3, 1]); %! assert_equal (yf(1), 0.122324994390997, 1e-10); %! assert_equal (yf(2), 1.125705590619342, 1e-10); %! assert_equal (yf(3), 2.129086186847688, 1e-10); %! assert_equal (yf, predict (mdl, [0.1 0.25; 0.5 0.25; 0.9 0.25]), 1e-10); %!test %! Weight = [2000;2100;2200;2300;2400;2500;2600;2700;2800;2900;3000; ... %! 3100;3200;3300;3400;3500;3600;3700;3800;3900]; %! Year = categorical ([70;70;70;70;70;76;76;76;76;76;76;76;82;82; ... %! 82;82;82;82;82;82]); %! MPG = [30;29;28;27;26;25;24;23;22;21;20;19;18;17;16;15;14;13;12;11]; %! m = fitlm (table (MPG, Weight, Year), 'MPG ~ Weight + Year'); %! yf = feval (m, [2500;3000], '76'); %! assert_equal (yf(1), 25.000000000000000, 1e-9); %! assert_equal (yf(2), 20.000000000000004, 1e-9); %! yf2 = feval (m, [2500;3000], categorical (70)); %! assert_equal (yf2(1), 24.999999999999996, 1e-9); %! assert_equal (yf2(2), 20.000000000000000, 1e-9); %! assert_equal (feval (m, 2800, '82'), 21.999999999999996, 1e-9); %! assert_equal (isnan (feval (m, 2500, '99')), true); %!test %! m = fitlm ((1:n)' / n, 2 * (1:n)' / n + 0.1 * sin ((1:n)')); %! assert_equal (size (feval (m, 0.5)), [1, 1]); %! assert_equal (size (feval (m, [0.3; 0.5; 0.9])), [3, 1]); %! assert_equal (feval (m, 0.5), predict (m, 0.5), 1e-10); %! assert_equal (feval (m, [0.3; 0.5; 0.9]), predict (m, [0.3; 0.5; 0.9]), 1e-10); %!test %! T = table ([0.5; 1.0; 0.2], [0.25; 1.0; 0.04], 'VariableNames', {'x1', 'x2'}); %! yf = feval (mdl, T); %! assert_equal (size (yf), [3, 1]); %! assert_equal (yf, predict (mdl, [0.5 0.25; 1.0 1.0; 0.2 0.04]), 1e-10); %!test %! yf = feval (mdl, [0.5 0.25; NaN 1.0; 1.0 1.0]); %! assert_equal (isfinite (yf(1)), true); %! assert_equal (isnan (yf(2)), true); %! assert_equal (isfinite (yf(3)), true); %!test %! yf = feval (mdl, [0.5; NaN; 1.0], [0.25; 1.0; 1.0]); %! assert_equal (isnan (yf(2)), true); %! yf = feval (mdl, [0.5; 1.0; 1.0], [0.25; NaN; 1.0]); %! assert_equal (isnan (yf(2)), true); %!test %! yf = feval (mdl, X); %! assert_equal (size (yf), [20, 1]); %! assert_equal (yf, mdl.Fitted, 1e-10); %!test %! m = fitlm (X, y, 'Intercept', false); %! yf = feval (m, [0.5 0.25; 1.0 1.0]); %! assert_equal (yf, predict (m, [0.5 0.25; 1.0 1.0]), 1e-10); %! assert_equal (feval (m, [0.5; 1.0], [0.25; 1.0]), yf, 1e-10); %!test %! m = fitlm (X, y, 'interactions'); %! yf = feval (m, [0.5 0.25; 1.0 1.0]); %! assert_equal (yf, predict (m, [0.5 0.25; 1.0 1.0]), 1e-10); %! assert_equal (feval (m, [0.5; 1.0], [0.25; 1.0]), yf, 1e-10); %!test %! m = fitlm ([1;1;1;2;2;2;3;3;3], [2.1;2.3;1.9;4.1;3.9;4.2;6.3;5.8;6.1], ... %! 'linear', 'CategoricalVars', 1); %! yf = feval (m, [1; 2; 3]); %! assert_equal (yf(1), 2.099999999999998, 1e-10); %! assert_equal (yf(2), 4.066666666666667, 1e-10); %! assert_equal (yf(3), 6.066666666666666, 1e-10); %!test %! ci = coefCI (mdl); %! assert_equal (size (ci), [3, 2]); %! assert_equal (class (ci), 'double'); %! assert_equal (all (ci(:,1) < ci(:,2)), true); %! assert_equal (ci(1,1), -0.120502736154050, 1e-10); %! assert_equal (ci(1,2), 0.352880091734465, 1e-10); %! assert_equal (ci(2,1), 1.470249604061007, 1e-10); %! assert_equal (ci(2,2), 3.546653377080718, 1e-10); %! assert_equal (ci(3,1), -1.026857022014626, 1e-10); %! assert_equal (ci(3,2), -0.930813637635746, 1e-10); %!test %! ## midpoints equal estimates %! ci = coefCI (mdl); %! t = tinv (0.975, mdl.DFE); %! assert_equal ((ci(:,1) + ci(:,2)) / 2, mdl.Coefficients.Estimate, 1e-10); %! assert_equal (ci(:,2) - ci(:,1), 2 * t * mdl.Coefficients.SE, 1e-10); %!test %! assert_equal (coefCI (mdl, 0.05), coefCI (mdl)); %!test %! ci = coefCI (mdl); %! ci01 = coefCI (mdl, 0.01); %! t01 = tinv (0.995, mdl.DFE); %! assert_equal (size (ci01), [3, 2]); %! assert_equal (ci01(1,1), -0.208951721610638, 1e-10); %! assert_equal (ci01(1,2), 0.441329077191052, 1e-10); %! assert_equal (ci01(2,1), 1.08228494564489, 1e-10); %! assert_equal (ci01(2,2), 3.934618035496833, 1e-10); %! assert_equal (ci01(3,1), -1.044802201703589, 1e-10); %! assert_equal (ci01(3,2), -0.912868457946783, 1e-10); %! assert_equal (all ((ci01(:,2) - ci01(:,1)) > (ci(:,2) - ci(:,1))), true); %! assert_equal (ci01(:,2) - ci01(:,1), 2 * t01 * mdl.Coefficients.SE, 1e-10); %!test %! ci0 = coefCI (mdl, 0); %! assert_equal (all (ci0(:,1) == -Inf), true); %! assert_equal (all (ci0(:,2) == +Inf), true); %!test %! ## alpha=1 collapses to point estimates %! ci1 = coefCI (mdl, 1); %! assert_equal (ci1(:,1), mdl.Coefficients.Estimate, 1e-10); %! assert_equal (ci1(:,2), mdl.Coefficients.Estimate, 1e-10); %!test %! m = fitlm (X, y, 'Intercept', false); %! ci = coefCI (m); %! t = tinv (0.975, m.DFE); %! assert_equal (size (ci), [2, 2]); %! assert_equal (ci(1,1), 2.486679110991696, 1e-10); %! assert_equal (ci(1,2), 3.436164115360526, 1e-10); %! assert_equal (ci(2,1), -1.027166590567854, 1e-10); %! assert_equal (ci(2,2), -0.967330908318718, 1e-10); %! assert_equal ((ci(:,1) + ci(:,2)) / 2, m.Coefficients.Estimate, 1e-10); %! assert_equal (ci(:,2) - ci(:,1), 2 * t * m.Coefficients.SE, 1e-10); %!test %! m = fitlm (X, y, 'interactions'); %! ci = coefCI (m); %! t = tinv (0.975, m.DFE); %! assert_equal (size (ci), [4, 2]); %! assert_equal (ci(1,1), -0.201030907566802, 1e-10); %! assert_equal (ci(1,2), 0.516312363642881, 1e-10); %! assert_equal ((ci(:,1) + ci(:,2)) / 2, m.Coefficients.Estimate, 1e-10); %! assert_equal (ci(:,2) - ci(:,1), 2 * t * m.Coefficients.SE, 1e-10); %!test %! ## constant model (1 coefficient) %! m = fitlm (X, y, 'constant'); %! ci = coefCI (m); %! t = tinv (0.975, m.DFE); %! assert_equal (size (ci), [1, 2]); %! assert_equal ((ci(1,1) + ci(1,2)) / 2, m.Coefficients.Estimate, 1e-10); %! assert_equal (ci(1,2) - ci(1,1), 2 * t * m.Coefficients.SE, 1e-10); %!test %! m = fitlm (X, y, 'Weights', (1:n)' / sum (1:n)); %! ci = coefCI (m); %! t = tinv (0.975, m.DFE); %! assert_equal (size (ci), [3, 2]); %! assert_equal (ci(1,1), -0.355978167660141, 1e-10); %! assert_equal (ci(1,2), 0.516619434992026, 1e-10); %! assert_equal ((ci(:,1) + ci(:,2)) / 2, m.Coefficients.Estimate, 1e-10); %! assert_equal (ci(:,2) - ci(:,1), 2 * t * m.Coefficients.SE, 1e-10); %!test %! ## rank-deficient: dropped rows give [0,0], active rows are finite %! m = fitlm ([ones(n,1), X, X(:,1)+X(:,2)], y); %! ci = coefCI (m); %! drop = find (m.Coefficients.SE == 0); %! assert_equal (size (ci), [5, 2]); %! assert_equal (all (all (ci(drop, :) == 0)), true); %! assert_equal (all (all (isfinite (ci(setdiff (1:5, drop'), :)))), true); %!test %! m = fitlm ([1;1;1;2;2;2;3;3;3], [2.1;2.3;1.9;4.1;3.9;4.2;6.3;5.8;6.1], ... %! 'linear', 'CategoricalVars', 1); %! ci = coefCI (m); %! assert_equal (size (ci), [3, 2]); %! assert_equal (ci(1,1), 1.80971256321669, 1e-10); %! assert_equal (ci(1,2), 2.3902874367833, 1e-10); %! assert_equal (ci(2,1), 1.55613823658119, 1e-10); %! assert_equal (ci(2,2), 2.37719509675214, 1e-10); %! assert_equal (ci(3,1), 3.55613823658119, 1e-10); %! assert_equal (ci(3,2), 4.37719509675214, 1e-10); %!test %! [p, F, r] = coefTest (mdl); %! assert_equal (size (p), [1, 1]); %! assert_equal (class (p), 'double'); %! assert_equal (p >= 0 && p <= 1, true); %! assert_equal (F >= 0, true); %! assert_equal (p, 9.489880832170599e-28, -1e-8); %! assert_equal (F, 1.283149098426142e+04, -1e-8); %! assert_equal (r, 2); %!test %! ## formula identity %! [p, F] = coefTest (mdl); %! k = mdl.NumCoefficients; %! H0 = [zeros(k-1, 1), eye(k-1)]; %! b = mdl.Coefficients.Estimate; %! V = mdl.CoefficientCovariance; %! Hb = H0 * b; %! Fm = (Hb' * ((H0 * V * H0') \ Hb)) / (k - 1); %! pm = betainc (mdl.DFE / (mdl.DFE + (k-1) * Fm), mdl.DFE/2, (k-1)/2); %! assert_equal (F, Fm, -1e-10); %! assert_equal (p, pm, -1e-10); %!test %! ## explicit H matches default %! k = mdl.NumCoefficients; %! H_exp = [zeros(k-1, 1), eye(k-1)]; %! [p1, F1, r1] = coefTest (mdl); %! [p2, F2, r2] = coefTest (mdl, H_exp); %! assert_equal (p2, p1, -1e-10); %! assert_equal (F2, F1, -1e-10); %! assert_equal (r2, size (H_exp, 1)); %!test %! ## pinned single and joint H %! [p1, F1, r1] = coefTest (mdl, [1 0 0]); %! assert_equal (p1, 0.314859866747774, -1e-8); %! assert_equal (F1, 1.072634101844537, -1e-8); %! assert_equal (r1, 1); %! [p2, F2, r2] = coefTest (mdl, [0 1 0]); %! assert_equal (p2, 8.937794169018252e-05, -1e-8); %! assert_equal (F2, 25.985840929474932, -1e-8); %! assert_equal (r2, 1); %! [p3, F3, r3] = coefTest (mdl, [0 0 1]); %! assert_equal (p3, 8.656938305821102e-19, -1e-8); %! assert_equal (F3, 1.849410599855684e+03, -1e-8); %! assert_equal (r3, 1); %! [pm, Fm, rm] = coefTest (mdl, [0 1 0; 0 0 1]); %! assert_equal (pm, 9.489880832170599e-28, -1e-8); %! assert_equal (Fm, 1.283149098426142e+04, -1e-8); %! assert_equal (rm, 2); %!test %! ## trivial hypothesis and C=0 %! b = mdl.Coefficients.Estimate; %! [p0, F0] = coefTest (mdl, [0 1 0], b(2)); %! assert_equal (F0 < 1e-12, true); %! assert_equal (p0, 1, 1e-10); %! [pa, Fa] = coefTest (mdl, [0 1 0], 0); %! [pb, Fb] = coefTest (mdl, [0 1 0]); %! assert_equal (pa, pb, -1e-10); %! assert_equal (Fa, Fb, -1e-10); %!test %! ## H with C %! [pc, Fc, rc] = coefTest (mdl, [0 1 0; 0 0 1], [1.5; -1.0]); %! assert_equal (pc, 2.833788304242915e-09, -1e-8); %! assert_equal (Fc, 77.603887650386312, -1e-8); %! assert_equal (rc, 2); %! [pr, Fr] = coefTest (mdl, [0 1 0; 0 0 1], [1.5, -1.0]); %! assert_equal (pr, pc, -1e-10); %! assert_equal (Fr, Fc, -1e-10); %! [ps, Fs] = coefTest (mdl, [0 1 0], 1.5); %! assert_equal (ps, 0.056184159363707, -1e-8); %! assert_equal (Fs, 4.199865537706047, -1e-8); %!test %! ## no-intercept model %! m = fitlm (X, y, 'Intercept', false); %! [p, F, r] = coefTest (m); %! assert_equal (p, 6.060655830723051e-32, -1e-8); %! assert_equal (F, 2.646694317541346e+04, -1e-8); %! assert_equal (r, m.NumCoefficients); %! [p2, F2] = coefTest (m, eye (m.NumCoefficients)); %! assert_equal (p2, p, -1e-10); %! assert_equal (F2, F, -1e-10); %!test %! ## interaction model %! m = fitlm (X, y, 'interactions'); %! [p, F, r] = coefTest (m); %! assert_equal (p, 1.164196605688161e-25, -1e-8); %! assert_equal (F, 8.107508574885546e+03, -1e-8); %! assert_equal (r, m.NumCoefficients - 1); %! assert_equal (r != m.NumPredictors, true); %!test %! ## weighted model %! m = fitlm (X, y, 'Weights', (1:n)' / sum (1:n)); %! [p, F, r] = coefTest (m); %! assert_equal (p, 1.481920976389473e-27, -1e-8); %! assert_equal (F, 1.217557180481257e+04, -1e-8); %! assert_equal (r, 2); %! assert_equal (p, m.ModelFitVsNullModel.Pvalue, -1e-8); %!test %! ## categorical model %! m = fitlm ([1;1;1;2;2;2;3;3;3], [2.1;2.3;1.9;4.1;3.9;4.2;6.3;5.8;6.1], ... %! 'linear', 'CategoricalVars', 1); %! [p, F, r] = coefTest (m); %! assert_equal (p, 1.197590680415813e-06, -1e-8); %! assert_equal (F, 2.795000000000035e+02, -1e-8); %! assert_equal (r, 2); %! [p1, F1] = coefTest (m, [1 0 0]); %! assert_equal (F1, 3.133421052631613e+02, -1e-8); %! assert_equal (p1, 2.087464608380450e-06, -1e-8); %! [p2, F2] = coefTest (m, [0 1 0]); %! assert_equal (F2, 1.374078947368438e+02, -1e-8); %! assert_equal (p2, 2.325514143662469e-05, -1e-8); %! [p3, F3] = coefTest (m, [0 0 1]); %! assert_equal (F3, 5.589868421052698e+02, -1e-8); %! assert_equal (p3, 3.757733067786492e-07, -1e-8); %!test %! ## constant model %! m = fitlm (X, y, 'constant'); %! [p, F, r] = coefTest (m); %! assert_equal (p, 0.000239936408695073, -1e-8); %! assert_equal (F, 20.3359164947506, -1e-8); %! assert_equal (r, 1); %!test %! ## rank-deficient model %! m = fitlm ([ones(n,1), X, X(:,1)+X(:,2)], y); %! [p, F] = coefTest (m); %! assert_equal (isnan (p), true); %! assert_equal (isnan (F), true); %! drop = find (m.Coefficients.SE == 0); %! keep = setdiff (2:m.NumCoefficients, drop'); %! H = zeros (numel (keep), m.NumCoefficients); %! for i = 1:numel (keep) %! H(i, keep(i)) = 1; %! endfor %! [p2, F2, r2] = coefTest (m, H); %! assert_equal (p2, 6.70657058643085e-30, -1e-8); %! assert_equal (F2, 17716.1864263456, -1e-8); %! assert_equal (r2, numel (keep)); %!test %! p = dwtest (mdl); %! assert_equal (size (p), [1, 1]); %! assert_equal (class (p), 'double'); %! [p, DW] = dwtest (mdl); %! assert_equal (size (DW), [1, 1]); %! assert_equal (p >= 0 && p <= 1, true); %! assert_equal (DW >= 0 && DW <= 4, true); %! assert_equal (p, 4.702593821571290e-04, -1e-6); %! assert_equal (DW, 0.870000704251173, 1e-12); %!test %! [p1, DW1] = dwtest (mdl); %! [p2, DW2] = dwtest (mdl, 'exact', 'both'); %! assert_equal (p1, p2, 1e-14); %! assert_equal (DW1, DW2, 1e-14); %!test %! ## DW is the same for all method and tail options %! [~, d1] = dwtest (mdl, 'exact', 'both'); %! [~, d2] = dwtest (mdl, 'exact', 'right'); %! [~, d3] = dwtest (mdl, 'exact', 'left'); %! [~, d4] = dwtest (mdl, 'approximate', 'both'); %! [~, d5] = dwtest (mdl, 'approximate', 'right'); %! [~, d6] = dwtest (mdl, 'approximate', 'left'); %! assert_equal (d1, 0.870000704251173, 1e-12); %! assert_equal (d2, 0.870000704251173, 1e-12); %! assert_equal (d3, 0.870000704251173, 1e-12); %! assert_equal (d4, 0.870000704251173, 1e-12); %! assert_equal (d5, 0.870000704251173, 1e-12); %! assert_equal (d6, 0.870000704251173, 1e-12); %!test %! ## one-sided p-values sum to 1 and two-sided equals twice the smaller %! pb = dwtest (mdl, 'exact', 'both'); %! pr = dwtest (mdl, 'exact', 'right'); %! pl = dwtest (mdl, 'exact', 'left'); %! assert_equal (pr + pl, 1, 1e-12); %! assert_equal (pb, 4.702593821571290e-04, 1e-12); %!test %! ## all six method and tail combinations pinned %! assert_equal (dwtest (mdl, 'exact', 'both'), 4.702593821571290e-04, -1e-6); %! assert_equal (dwtest (mdl, 'exact', 'right'), 2.351296910785645e-04, -1e-6); %! assert_equal (dwtest (mdl, 'exact', 'left'), 0.999764870308921, -1e-6); %! assert_equal (dwtest (mdl, 'approximate', 'both'), 0.001058795514879, -1e-6); %! assert_equal (dwtest (mdl, 'approximate', 'right'), 5.293977574395035e-04, -1e-6); %! assert_equal (dwtest (mdl, 'approximate', 'left'), 0.999470602242560, -1e-6); %!test %! ## no-intercept model %! m = fitlm (X, y, 'Intercept', false); %! [p, DW] = dwtest (m, 'exact', 'both'); %! assert_equal (DW, 0.841468411374128, 1e-12); %! assert_equal (p, 0.001402191159200, -1e-6); %! assert_equal (dwtest (m, 'exact', 'right'), 7.010955795999754e-04, -1e-6); %! assert_equal (dwtest (m, 'approximate', 'right'), 0.001350534002321, -1e-6); %!test %! ## weighted model %! m = fitlm (X, y, 'Weights', (1:n)' / sum (1:n)); %! [p, DW] = dwtest (m, 'exact', 'both'); %! assert_equal (DW, 0.871162354803032, 1e-12); %! assert_equal (p, 4.771641146603785e-04, -1e-6); %! assert_equal (dwtest (m, 'exact', 'right'), 2.385820573301892e-04, -1e-6); %! assert_equal (dwtest (m, 'approximate', 'right'), 5.346779629058873e-04, -1e-6); %!test %! ## positive autocorrelation model %! m = fitlm ((1:n)'/n, sin (pi * (1:n)'/n)); %! [~, DW] = dwtest (m, 'exact', 'both'); %! pr = dwtest (m, 'exact', 'right'); %! pl = dwtest (m, 'exact', 'left'); %! assert_equal (DW, 0.118112272685229, 1e-10); %! assert_equal (DW < 1, true); %! assert_equal (pr < pl, true); %! assert_equal (pr < 1e-10, true); %!test %! ## negative autocorrelation model %! m = fitlm ((1:n)'/n, 2*(1:n)'/n + repmat ([1; -1], n/2, 1)); %! [pb, DW] = dwtest (m, 'exact', 'both'); %! pl = dwtest (m, 'exact', 'left'); %! pr = dwtest (m, 'exact', 'right'); %! assert_equal (pb, 4.205713999283489e-09, 1e-10); %! assert_equal (DW, 3.825974025974026, 1e-10); %! assert_equal (DW > 2, true); %! assert_equal (pl < pr, true); %! assert_equal (pb, 2 * pl, 1e-10); %! assert_equal (pb < 1e-7, true); %!test %! m = addTerms (mdl, 'x1:x2'); %! assert_equal (isa (m, 'LinearModel'), true); %! assert_equal (mdl.NumCoefficients, 3); %! assert_equal (m.NumCoefficients, 4); %! assert_equal (m.NumPredictors, 2); %! assert_equal (m.NumObservations, 20); %! assert_equal (m.DFE, 16); %! assert_equal (m.Coefficients.Estimate(1), 0.157640728038039, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), 2.085426803117909, -1e-8); %! assert_equal (m.Coefficients.Estimate(3), -0.929682701072813, -1e-8); %! assert_equal (m.Coefficients.Estimate(4), -0.031208018255475, -1e-8); %! assert_equal (m.Coefficients.SE(1), 0.169192291625763, -1e-8); %! assert_equal (m.Coefficients.SE(2), 1.361534257888685, -1e-8); %! assert_equal (m.Coefficients.SE(3), 0.148744319911833, -1e-8); %! assert_equal (m.Coefficients.SE(4), 0.0932669056882381, -1e-8); %! assert_equal (m.Coefficients.tStat(1), 0.931725237144526, -1e-8); %! assert_equal (m.Coefficients.tStat(2), 1.531674132351069, -1e-8); %! assert_equal (m.Coefficients.tStat(3), -6.250206405353000, -1e-8); %! assert_equal (m.Coefficients.tStat(4), -0.334609774230031, -1e-8); %! assert_equal (m.Coefficients.pValue(1), 0.365325503492671, -1e-8); %! assert_equal (m.Coefficients.pValue(2), 0.145134783727025, -1e-8); %! assert_equal (m.Coefficients.pValue(3), 1.159217784590233e-05, -1e-8); %! assert_equal (m.Coefficients.pValue(4), 0.742265736761240, -1e-8); %! assert_equal (m.SSE, 0.383859187927621, -1e-8); %! assert_equal (m.MSE, 0.023991199245515, -1e-8); %! assert_equal (m.RMSE, 0.154890926930905, -1e-8); %! assert_equal (m.Rsquared.Ordinary, 0.999342606032059, -1e-8); %! assert_equal (m.Rsquared.Adjusted, 0.999219344663070, -1e-8); %! assert_equal (m.LogLikelihood, 11.153346988927943, -1e-8); %! assert_equal (m.ModelFitVsNullModel.Fstat, 8.107508574898859e+03, -1e-6); %! assert_equal (m.ModelFitVsNullModel.Pvalue, 1.164196605672873e-25, -1e-6); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.CoefficientNames{3}, 'x2'); %! assert_equal (m.CoefficientNames{4}, 'x1:x2'); %!test %! ## x1*x2 crossing gives same result as x1:x2 when main effects exist %! m = addTerms (mdl, 'x1*x2'); %! assert_equal (m.NumCoefficients, 4); %! assert_equal (m.DFE, 16); %! assert_equal (m.SSE, 0.383859187927621, -1e-8); %! assert_equal (m.Coefficients.Estimate(1), 0.157640728038039, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), 2.085426803117909, -1e-8); %! assert_equal (m.Coefficients.Estimate(3), -0.929682701072813, -1e-8); %! assert_equal (m.Coefficients.Estimate(4), -0.031208018255475, -1e-8); %! assert_equal (m.CoefficientNames{4}, 'x1:x2'); %!test %! m = addTerms (mdl, 'x1 + x1:x2'); %! assert_equal (m.NumCoefficients, 4); %! assert_equal (m.DFE, 16); %! assert_equal (m.SSE, 0.383859187927621, -1e-8); %! assert_equal (m.Coefficients.Estimate(1), 0.157640728038039, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), 2.085426803117909, -1e-8); %! assert_equal (m.Coefficients.Estimate(3), -0.929682701072813, -1e-8); %! assert_equal (m.Coefficients.Estimate(4), -0.031208018255475, -1e-8); %!test %! ## adding existing term returns equivalent model %! ws = warning ('off', 'all'); %! m = addTerms (mdl, 'x1'); %! warning (ws); %! assert_equal (m.NumCoefficients, 3); %! assert_equal (m.DFE, 17); %! assert_equal (m.Coefficients.Estimate(1), 0.116188677790207, 1e-7); %! assert_equal (m.Coefficients.Estimate(2), 2.508451490570863, 1e-7); %! assert_equal (m.Coefficients.Estimate(3), -0.978835329825186, 1e-7); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.CoefficientNames{3}, 'x2'); %!test %! m = addTerms (mdl, 'x2^2'); %! assert_equal (m.NumCoefficients, 4); %! assert_equal (m.DFE, 16); %! assert_equal (m.SSE, 0.386103933724971, -1e-8); %! assert_equal (m.Coefficients.Estimate(1), 0.130152473216993, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), 2.380771990884563, -1e-8); %! assert_equal (m.Coefficients.Estimate(3), -0.967672823484773, -1e-8); %! assert_equal (m.Coefficients.Estimate(4), -2.991483322469043e-04, -1e-8); %! assert_equal (m.Coefficients.SE(1), 0.154974488176692, -1e-8); %! assert_equal (m.Coefficients.SE(2), 1.071554310049276, -1e-8); %! assert_equal (m.Coefficients.SE(3), 0.085801310569364, -1e-8); %! assert_equal (m.Coefficients.SE(4), 0.002211890858232, -1e-8); %! assert_equal (m.CoefficientNames{4}, 'x2^2'); %!test %! ## x1^2 rank-deficient: DFE unchanged SE zero for dropped term %! m = addTerms (mdl, 'x1^2'); %! assert_equal (m.NumCoefficients, 4); %! assert_equal (m.DFE, 17); %! assert_equal (m.SSE, 0.386545331386823, -1e-8); %! assert_equal (m.Coefficients.Estimate(4), 0); %! assert_equal (m.Coefficients.SE(4), 0); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.CoefficientNames{3}, 'x2'); %! assert_equal (m.CoefficientNames{4}, 'x1^2'); %!test %! ## numeric matrix [1,1,0] same as string x1:x2 %! m = addTerms (mdl, [1, 1, 0]); %! assert_equal (m.NumCoefficients, 4); %! assert_equal (m.DFE, 16); %! assert_equal (m.SSE, 0.383859187927621, -1e-8); %! assert_equal (m.Coefficients.Estimate(1), 0.157640728038039, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), 2.085426803117909, -1e-8); %! assert_equal (m.Coefficients.Estimate(3), -0.929682701072813, -1e-8); %! assert_equal (m.Coefficients.Estimate(4), -0.031208018255475, -1e-8); %!test %! ## numeric matrix [1,1] auto-padded to [1,1,0] %! m = addTerms (mdl, [1, 1]); %! assert_equal (m.NumCoefficients, 4); %! assert_equal (m.DFE, 16); %! assert_equal (m.SSE, 0.383859187927621, -1e-8); %! assert_equal (m.Coefficients.Estimate(1), 0.157640728038039, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), 2.085426803117909, -1e-8); %! assert_equal (m.Coefficients.Estimate(3), -0.929682701072813, -1e-8); %! assert_equal (m.Coefficients.Estimate(4), -0.031208018255475, -1e-8); %!test %! m = addTerms (mdl, [1, 1, 0; 0, 2, 0]); %! assert_equal (m.NumCoefficients, 5); %! assert_equal (m.DFE, 15); %! assert_equal (m.SSE, 0.315784637443501, -1e-8); %! assert_equal (m.CoefficientNames{4}, 'x1:x2'); %! assert_equal (m.CoefficientNames{5}, 'x2^2'); %!test %! mc = fitlm (X, y, 'constant'); %! m = addTerms (mc, 'x1'); %! assert_equal (m.NumCoefficients, 2); %! assert_equal (m.DFE, 18); %! assert_equal (m.Coefficients.Estimate(1), 3.884704697617172, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), -18.047090435758047, -1e-8); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %!test %! ## step from constant to full linear model %! mc = fitlm (X, y, 'constant'); %! mc1 = addTerms (mc, 'x1'); %! mc2 = addTerms (mc1, 'x2'); %! assert_equal (mc2.NumCoefficients, 3); %! assert_equal (mc2.DFE, 17); %! assert_equal (mc2.Coefficients.Estimate(1), 0.116188677790207, 1e-7); %! assert_equal (mc2.Coefficients.Estimate(2), 2.508451490570863, 1e-7); %! assert_equal (mc2.Coefficients.Estimate(3), -0.978835329825186, 1e-7); %! assert_equal (mc2.CoefficientNames{1}, '(Intercept)'); %! assert_equal (mc2.CoefficientNames{2}, 'x1'); %! assert_equal (mc2.CoefficientNames{3}, 'x2'); %!test %! ## adding intercept to no-intercept model %! mni = fitlm (X, y, 'Intercept', false); %! m = addTerms (mni, '1'); %! assert_equal (m.NumCoefficients, 3); %! assert_equal (m.DFE, 17); %! assert_equal (m.Coefficients.Estimate(1), 0.116188677790207, 1e-7); %! assert_equal (m.Coefficients.Estimate(2), 2.508451490570863, 1e-7); %! assert_equal (m.Coefficients.Estimate(3), -0.978835329825186, 1e-7); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.CoefficientNames{3}, 'x2'); %!test %! ## weighted model weights preserved %! mw = fitlm (X, y, 'Weights', (1:n)' / sum (1:n)); %! m = addTerms (mw, 'x1:x2'); %! assert_equal (m.NumCoefficients, 4); %! assert_equal (m.DFE, 16); %! assert_equal (m.SSE, 0.019230053719402, -1e-8); %! assert_equal (m.Coefficients.Estimate(1), -0.122645849510537, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), 4.125799311652051, -1e-8); %! assert_equal (m.Coefficients.Estimate(3), -1.128467507844852, -1e-8); %! assert_equal (m.Coefficients.Estimate(4), 0.081921546574140, -1e-8); %! assert_equal (m.Coefficients.SE(1), 0.354926338845420, -1e-8); %! assert_equal (m.Coefficients.SE(2), 2.205951720932299, -1e-8); %! assert_equal (m.Coefficients.SE(3), 0.205033663966776, -1e-8); %! assert_equal (m.Coefficients.SE(4), 0.115523382309399, -1e-8); %! assert_equal (m.CoefficientNames{4}, 'x1:x2'); %!test %! ## excluded observations preserved %! me = fitlm (X, y, 'Exclude', [1, 2]); %! m = addTerms (me, 'x1:x2'); %! assert_equal (m.NumObservations, 18); %! assert_equal (m.DFE, 14); %! assert_equal (m.NumCoefficients, 4); %! assert_equal (m.Coefficients.Estimate(1), -0.345521184099998, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), 5.139185607268283, -1e-8); %! assert_equal (m.Coefficients.Estimate(3), -1.198851436671170, -1e-8); %! assert_equal (m.Coefficients.Estimate(4), 0.112619530301200, -1e-8); %! assert_equal (m.CoefficientNames{4}, 'x1:x2'); %!test %! ## remove two predictors by string from a 4-predictor model %! Xh = [7 26 6 60; 1 29 15 52; 11 56 8 20; 11 31 8 47; 7 52 6 33; ... %! 11 55 9 22; 3 71 17 6; 1 31 22 44; 2 54 18 22; 21 47 4 26; ... %! 1 40 23 34; 11 66 9 12; 10 68 8 12]; %! yh = [78.5;74.3;104.3;87.6;95.9;109.2;102.7;72.5;93.1;115.9;83.8;113.3;109.4]; %! m = removeTerms (fitlm (Xh, yh), 'x3 + x4'); %! assert_equal (m.NumCoefficients, 3); %! assert_equal (m.NumEstimatedCoefficients, 3); %! assert_equal (m.DFE, 10); %! assert_equal (m.NumObservations, 13); %! assert_equal (m.NumVariables, 5); %! assert_equal (m.Coefficients.Estimate(1), 52.577348882089481, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), 1.468305742215555, -1e-8); %! assert_equal (m.Coefficients.Estimate(3), 0.662250491274645, -1e-8); %! assert_equal (m.Coefficients.SE(1), 2.286174334503340, -1e-8); %! assert_equal (m.Coefficients.SE(2), 0.121300923606266, -1e-8); %! assert_equal (m.Coefficients.SE(3), 0.045854721468522, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.Coefficients.tStat(1), 22.997961305305111, -1e-8); %! assert_equal (m.Coefficients.tStat(2), 12.104654264476748, -1e-8); %! assert_equal (m.Coefficients.tStat(3), 14.442362096327519, -1e-8); %! assert_equal (m.Coefficients.pValue(1), 5.456570901490983e-10, -1e-7); %! assert_equal (m.Coefficients.pValue(2), 2.692212179685427e-07, -1e-8); %! assert_equal (m.Coefficients.pValue(3), 5.028960315638413e-08, -1e-8); %! assert_equal (m.SSE, 57.904483176113658, -1e-8); %! assert_equal (m.RMSE, 2.40633503852047, -1e-8); %! assert_equal (m.MSE, 5.790448317611299, 1e-12); %! assert_equal (m.SST, 2.715763076923078e+03, 1e-8); %! assert_equal (m.Rsquared.Ordinary, 0.978678374535632, -1e-8); %! assert_equal (m.Rsquared.Adjusted, 0.974414049442758, -1e-8); %! assert_equal (size (m.CoefficientCovariance), [3, 3]); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.CoefficientNames{3}, 'x2'); %! assert_equal (m.Formula.HasIntercept, true); %! assert_equal (m.Formula.LinearPredictor, '1 + x1 + x2'); %! assert_equal (height (m.Diagnostics), 13); %! assert_equal (sum (m.Diagnostics.Leverage), 3, 1e-10); %! assert_equal (m.Residuals.Raw, yh - m.Fitted, 1e-10); %!test %! m = removeTerms (mdl, 'x2'); %! assert_equal (m.NumCoefficients, 2); %! assert_equal (m.NumEstimatedCoefficients, 2); %! assert_equal (m.DFE, 18); %! assert_equal (m.NumObservations, 20); %! assert_equal (m.Coefficients.Estimate(1), 3.88470469761717, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), -18.047090435758, -1e-8); %! assert_equal (m.Coefficients.SE(2), 1.19086428900602, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.Coefficients.tStat(2), -15.1546155194741, -1e-8); %! assert_equal (m.SSE, 42.4383708132815, -1e-8); %! assert_equal (m.SST, 583.910420002346, -1e-8); %! assert_equal (m.Rsquared.Ordinary, 0.927320408474452, -1e-8); %! assert_equal (size (m.CoefficientCovariance), [2, 2]); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.Formula.HasIntercept, true); %! assert_equal (m.Formula.LinearPredictor, '1 + x1'); %! assert_equal (height (m.Diagnostics), 20); %! assert_equal (m.Residuals.Raw, y - m.Fitted, 1e-10); %!test %! ## removing the intercept via string '1' %! m = removeTerms (mdl, '1'); %! assert_equal (m.NumCoefficients, 2); %! assert_equal (m.NumEstimatedCoefficients, 2); %! assert_equal (m.DFE, 18); %! assert_equal (m.NumObservations, 20); %! assert_equal (m.Formula.HasIntercept, false); %! assert_equal (m.Formula.LinearPredictor, 'x1 + x2'); %! assert_equal (m.Coefficients.Estimate(1), 2.96142161317611, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), -0.997248749443286, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.SSE, 0.410934843407688, -1e-8); %! assert_equal (size (m.CoefficientCovariance), [2, 2]); %! assert_equal (m.CoefficientNames{1}, 'x1'); %! assert_equal (m.CoefficientNames{2}, 'x2'); %! assert_equal (! any (strcmp (m.CoefficientNames, '(Intercept)')), true); %! assert_equal (height (m.Diagnostics), 20); %!test %! ## removing both predictors leaves only the intercept %! m = removeTerms (mdl, 'x1 + x2'); %! assert_equal (m.NumCoefficients, 1); %! assert_equal (m.NumEstimatedCoefficients, 1); %! assert_equal (m.DFE, 19); %! assert_equal (m.NumObservations, 20); %! assert_equal (m.Formula.HasIntercept, true); %! assert_equal (m.Formula.LinearPredictor, '1'); %! assert_equal (m.Coefficients.Estimate(1), -5.5900177811558, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.SSE, 583.910420002346, -1e-8); %! assert_equal (m.SST, 583.910420002346, -1e-8); %! assert_equal (m.SSR, 0, 1e-20); %! assert_equal (size (m.CoefficientCovariance), [1, 1]); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (height (m.Diagnostics), 20); %! assert_equal (sum (m.Diagnostics.Leverage), 1, 1e-10); %! assert_equal (m.Residuals.Raw, y - m.Fitted, 1e-10); %!test %! ws = warning ('off', 'all'); %! m = removeTerms (mdl, 'x1:x2'); %! warning (ws); %! assert_equal (m.NumCoefficients, mdl.NumCoefficients); %! assert_equal (m.NumEstimatedCoefficients, mdl.NumEstimatedCoefficients); %! assert_equal (m.DFE, mdl.DFE); %! assert_equal (m.SSE, mdl.SSE, 1e-15); %! assert_equal (m.SSR, mdl.SSR, 1e-15); %! assert_equal (m.SST, mdl.SST, 1e-15); %! assert_equal (m.RMSE, mdl.RMSE, 1e-15); %! assert_equal (m.Coefficients.Estimate, mdl.Coefficients.Estimate, 1e-15); %! assert_equal (m.Coefficients.SE, mdl.Coefficients.SE, 1e-15); %! assert_equal (m.CoefficientCovariance, mdl.CoefficientCovariance, 1e-15); %! assert_equal (isequal (m.CoefficientNames, mdl.CoefficientNames), true); %! assert_equal (m.Formula.LinearPredictor, mdl.Formula.LinearPredictor); %! assert_equal (m.Formula.HasIntercept, mdl.Formula.HasIntercept); %!test %! m = removeTerms (mdl, [0 1 0]); %! assert_equal (m.NumCoefficients, 2); %! assert_equal (m.NumEstimatedCoefficients, 2); %! assert_equal (m.DFE, 18); %! assert_equal (m.NumObservations, 20); %! assert_equal (m.Coefficients.Estimate(1), 3.88470469761717, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), -18.047090435758, -1e-8); %! assert_equal (m.Coefficients.SE(2), 1.19086428900602, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.Coefficients.tStat(2), -15.1546155194741, -1e-8); %! assert_equal (m.SSE, 42.4383708132815, -1e-8); %! assert_equal (m.SST, 583.910420002346, -1e-8); %! assert_equal (m.Rsquared.Ordinary, 0.927320408474452, -1e-8); %! assert_equal (size (m.CoefficientCovariance), [2, 2]); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.Formula.HasIntercept, true); %! assert_equal (m.Formula.LinearPredictor, '1 + x1'); %! assert_equal (height (m.Diagnostics), 20); %! assert_equal (sum (m.Diagnostics.Leverage), 2, 1e-10); %!test %! ## auto-padded matrix [0 1] gives identical result to [0 1 0] %! m = removeTerms (mdl, [0 1]); %! assert_equal (m.NumCoefficients, 2); %! assert_equal (m.NumEstimatedCoefficients, 2); %! assert_equal (m.DFE, 18); %! assert_equal (m.Coefficients.Estimate(1), 3.88470469761717, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), -18.047090435758, -1e-8); %! assert_equal (m.Coefficients.SE(2), 1.19086428900602, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.SSE, 42.4383708132815, -1e-8); %! assert_equal (m.SST, 583.910420002346, -1e-8); %! assert_equal (m.Rsquared.Ordinary, 0.927320408474452, -1e-8); %! assert_equal (size (m.CoefficientCovariance), [2, 2]); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.Formula.LinearPredictor, '1 + x1'); %! assert_equal (height (m.Diagnostics), 20); %! assert_equal (sum (m.Diagnostics.Leverage), 2, 1e-10); %!test %! ## multi-row matrix removes two terms same as the string form %! Xh = [7 26 6 60; 1 29 15 52; 11 56 8 20; 11 31 8 47; 7 52 6 33; ... %! 11 55 9 22; 3 71 17 6; 1 31 22 44; 2 54 18 22; 21 47 4 26; ... %! 1 40 23 34; 11 66 9 12; 10 68 8 12]; %! yh = [78.5;74.3;104.3;87.6;95.9;109.2;102.7;72.5;93.1;115.9;83.8;113.3;109.4]; %! m = removeTerms (fitlm (Xh, yh), [0 0 1 0 0; 0 0 0 1 0]); %! assert_equal (m.NumCoefficients, 3); %! assert_equal (m.NumEstimatedCoefficients, 3); %! assert_equal (m.DFE, 10); %! assert_equal (m.NumObservations, 13); %! assert_equal (m.Coefficients.Estimate(1), 52.577348882089481, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), 1.468305742215555, -1e-8); %! assert_equal (m.Coefficients.Estimate(3), 0.662250491274645, -1e-8); %! assert_equal (m.Coefficients.SE(1), 2.286174334503340, -1e-8); %! assert_equal (m.Coefficients.SE(2), 0.121300923606266, -1e-8); %! assert_equal (m.Coefficients.SE(3), 0.045854721468522, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.Coefficients.tStat(2), 12.104654264476748, -1e-8); %! assert_equal (m.Coefficients.tStat(3), 14.442362096327519, -1e-8); %! assert_equal (m.Coefficients.pValue(2), 2.692212179685427e-07, -1e-8); %! assert_equal (m.SSE, 57.904483176113658, -1e-8); %! assert_equal (m.Rsquared.Ordinary, 0.978678374535632, -1e-8); %! assert_equal (m.Rsquared.Adjusted, 0.974414049442758, -1e-8); %! assert_equal (size (m.CoefficientCovariance), [3, 3]); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.CoefficientNames{3}, 'x2'); %! assert_equal (m.Formula.HasIntercept, true); %! assert_equal (m.Formula.LinearPredictor, '1 + x1 + x2'); %! assert_equal (height (m.Diagnostics), 13); %! assert_equal (sum (m.Diagnostics.Leverage), 3, 1e-10); %! assert_equal (m.Residuals.Raw, yh - m.Fitted, 1e-10); %!test %! ## observation weights carry through to the refitted model %! w = (1:n)' / sum (1:n); %! mw = fitlm (X, y, 'Weights', w); %! m = removeTerms (mw, 'x2'); %! assert_equal (m.NumCoefficients, 2); %! assert_equal (m.NumEstimatedCoefficients, 2); %! assert_equal (m.DFE, 18); %! assert_equal (m.NumObservations, 20); %! assert_equal (m.Coefficients.Estimate(1), 6.29263960898714, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), -21.5708976231287, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.SSE, 1.41763159723151, -1e-8); %! assert_equal (size (m.CoefficientCovariance), [2, 2]); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.Formula.HasIntercept, true); %! assert_equal (m.Formula.LinearPredictor, '1 + x1'); %! assert_equal (m.ObservationInfo.Weights, w, 1e-15); %! assert_equal (sum (m.ObservationInfo.Weights), 1, 1e-12); %! assert_equal (height (m.Diagnostics), 20); %! assert_equal (sum (m.Diagnostics.Leverage), 2, 1e-10); %! assert_equal (m.SSE != removeTerms (mdl, 'x2').SSE, true); %!test %! ## excluded rows are preserved and reduce effective sample size %! me = fitlm (X, y, 'Exclude', [1, 3]); %! m = removeTerms (me, 'x2'); %! assert_equal (m.NumObservations, 18); %! assert_equal (m.DFE, 16); %! assert_equal (m.NumCoefficients, 2); %! assert_equal (m.NumEstimatedCoefficients, 2); %! assert_equal (m.Coefficients.Estimate(1), 4.96609542902066, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), -19.5618050042778, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (size (m.CoefficientCovariance), [2, 2]); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.Formula.LinearPredictor, '1 + x1'); %! assert_equal (m.ObservationInfo.Excluded(1), true); %! assert_equal (m.ObservationInfo.Excluded(3), true); %! assert_equal (m.ObservationInfo.Excluded(2), false); %! assert_equal (m.ObservationInfo.Missing(1), false); %! assert_equal (height (m.Diagnostics), 20); %! assert_equal (m.Fitted(1), 3.988005178806767, 1e-10); %! assert_equal (m.Fitted(3), 2.031824678378986, 1e-10); %! assert_equal (m.Fitted(2), 3.009914928592877, 1e-10); %! assert_equal (isnan (m.Residuals.Raw(1)), true); %!test %! ## removing x2 from a no-intercept model gives one slope term %! mni = fitlm (X, y, 'Intercept', false); %! m = removeTerms (mni, 'x2'); %! assert_equal (m.NumCoefficients, 1); %! assert_equal (m.NumEstimatedCoefficients, 1); %! assert_equal (m.DFE, 19); %! assert_equal (m.NumObservations, 20); %! assert_equal (m.Formula.HasIntercept, false); %! assert_equal (m.Formula.LinearPredictor, 'x1'); %! assert_equal (m.Coefficients.Estimate(1), -12.362156731928, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.SSE, 112.371951585499, -1e-8); %! assert_equal (size (m.CoefficientCovariance), [1, 1]); %! assert_equal (m.CoefficientCovariance(1,1) > 0, true); %! assert_equal (m.CoefficientNames{1}, 'x1'); %! assert_equal (! any (strcmp (m.CoefficientNames, '(Intercept)')), true); %! assert_equal (height (m.Diagnostics), 20); %! assert_equal (all (isfinite (m.Fitted)), true); %! assert_equal (sum (m.Diagnostics.Leverage), 1, 1e-10); %!test %! ## removing the interaction term recovers the plain linear model %! mi = fitlm (X, y, 'interactions'); %! m = removeTerms (mi, 'x1:x2'); %! assert_equal (m.NumCoefficients, 3); %! assert_equal (m.NumEstimatedCoefficients, 3); %! assert_equal (m.DFE, 17); %! assert_equal (m.NumObservations, 20); %! assert_equal (m.Coefficients.Estimate(1), 0.116188677790207, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), 2.508451490570863, -1e-8); %! assert_equal (m.Coefficients.Estimate(3), -0.978835329825186, -1e-8); %! assert_equal (m.Coefficients.SE(1), 0.112185831, -1e-7); %! assert_equal (m.Coefficients.SE(2), 0.4920818186, -1e-7); %! assert_equal (m.Coefficients.SE(3), 0.02276108523, -1e-7); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.Coefficients.tStat(1), 1.035680502, -1e-6); %! assert_equal (m.Coefficients.tStat(2), 5.097630913, -1e-6); %! assert_equal (m.Coefficients.tStat(3), -43.00477415, -1e-6); %! assert_equal (m.SSE, 0.386545331386823, -1e-8); %! assert_equal (size (m.CoefficientCovariance), [3, 3]); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.CoefficientNames{3}, 'x2'); %! assert_equal (m.Formula.HasIntercept, true); %! assert_equal (m.Formula.LinearPredictor, '1 + x1 + x2'); %! assert_equal (height (m.Diagnostics), 20); %! assert_equal (sum (m.Diagnostics.Leverage), 3, 1e-10); %!test %! ## removing a quadratic term refits on the remaining terms %! mq = fitlm (X, y, 'quadratic'); %! m = removeTerms (mq, 'x2^2'); %! assert_equal (m.NumEstimatedCoefficients, 4); %! assert_equal (m.DFE, 16); %! assert_equal (m.SSE, 0.383859187927621, -1e-8); %! assert_equal (size (m.CoefficientCovariance, 1), m.NumCoefficients); %! assert_equal (size (m.CoefficientCovariance, 2), m.NumCoefficients); %! assert_equal (m.Formula.HasIntercept, true); %! assert_equal (height (m.Diagnostics), 20); %! assert_equal (sum (m.Diagnostics.Leverage), 4, 1e-10); %! assert_equal (m.SSE >= mq.SSE, true); %! assert_equal (! any (strcmp (m.CoefficientNames, 'x2^2')), true); %!test %! ## star notation removes main effects and interaction in one call %! mi = fitlm (X, y, 'interactions'); %! m = removeTerms (mi, 'x1*x2'); %! assert_equal (m.NumCoefficients, 1); %! assert_equal (m.NumEstimatedCoefficients, 1); %! assert_equal (m.DFE, 19); %! assert_equal (m.NumObservations, 20); %! assert_equal (m.Formula.HasIntercept, true); %! assert_equal (m.Formula.LinearPredictor, '1'); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.Coefficients.Estimate(1), -5.5900177811558, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.SSE, 583.910420002346, -1e-8); %! assert_equal (m.SST, 583.910420002346, -1e-8); %! assert_equal (m.SSR, 0, 1e-20); %! assert_equal (size (m.CoefficientCovariance), [1, 1]); %! assert_equal (height (m.Diagnostics), 20); %! assert_equal (sum (m.Diagnostics.Leverage), 1, 1e-10); %!test %! ## 3-predictor model: removing one term matches a direct two-predictor fit %! X3 = [X, sin((1:n)' * pi / n)]; %! y3 = X3 * [3; -1; 2] + 0.1 * cos ((1:n)' * pi / 7); %! m = removeTerms (fitlm (X3, y3), 'x3'); %! r = fitlm (X, y3); %! assert_equal (m.NumCoefficients, 3); %! assert_equal (m.NumEstimatedCoefficients, 3); %! assert_equal (m.DFE, 17); %! assert_equal (m.NumObservations, 20); %! assert_equal (m.Coefficients.Estimate, r.Coefficients.Estimate, 1e-10); %! assert_equal (m.Coefficients.SE, r.Coefficients.SE, 1e-10); %! assert_equal (m.Coefficients.tStat, r.Coefficients.tStat, 1e-10); %! assert_equal (m.Coefficients.pValue, r.Coefficients.pValue, 1e-10); %! assert_equal (m.SSE, r.SSE, 1e-12); %! assert_equal (m.SSR, r.SSR, 1e-12); %! assert_equal (m.SST, r.SST, 1e-12); %! assert_equal (m.MSE, r.MSE, 1e-12); %! assert_equal (m.RMSE, r.RMSE, 1e-12); %! assert_equal (m.Rsquared.Ordinary, r.Rsquared.Ordinary, 1e-12); %! assert_equal (m.Rsquared.Adjusted, r.Rsquared.Adjusted, 1e-12); %! assert_equal (m.CoefficientCovariance, r.CoefficientCovariance, 1e-12); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.CoefficientNames{3}, 'x2'); %! assert_equal (m.Formula.HasIntercept, true); %! assert_equal (m.Formula.LinearPredictor, '1 + x1 + x2'); %! assert_equal (height (m.Diagnostics), 20); %! assert_equal (sum (m.Diagnostics.Leverage), 3, 1e-10); %!test %! ## 3-predictor model: removing two terms matches a direct one-predictor fit %! X3 = [X, sin((1:n)' * pi / n)]; %! y3 = X3 * [3; -1; 2] + 0.1 * cos ((1:n)' * pi / 7); %! m = removeTerms (fitlm (X3, y3), 'x2 + x3'); %! r = fitlm (X(:,1), y3); %! assert_equal (m.NumCoefficients, 2); %! assert_equal (m.NumEstimatedCoefficients, 2); %! assert_equal (m.DFE, 18); %! assert_equal (m.NumObservations, 20); %! assert_equal (m.Coefficients.Estimate, r.Coefficients.Estimate, 1e-10); %! assert_equal (m.Coefficients.SE, r.Coefficients.SE, 1e-10); %! assert_equal (m.Coefficients.tStat, r.Coefficients.tStat, 1e-10); %! assert_equal (m.Coefficients.pValue, r.Coefficients.pValue, 1e-10); %! assert_equal (m.SSE, r.SSE, 1e-12); %! assert_equal (m.SST, r.SST, 1e-12); %! assert_equal (m.MSE, r.MSE, 1e-12); %! assert_equal (m.RMSE, r.RMSE, 1e-12); %! assert_equal (m.Rsquared.Ordinary, r.Rsquared.Ordinary, 1e-12); %! assert_equal (m.Rsquared.Adjusted, r.Rsquared.Adjusted, 1e-12); %! assert_equal (m.CoefficientCovariance, r.CoefficientCovariance, 1e-12); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.Formula.HasIntercept, true); %! assert_equal (m.Formula.LinearPredictor, '1 + x1'); %! assert_equal (height (m.Diagnostics), 20); %! assert_equal (sum (m.Diagnostics.Leverage), 2, 1e-10); %!test %! Xh = [7 26 6 60; 1 29 15 52; 11 56 8 20; 11 31 8 47; 7 52 6 33; ... %! 11 55 9 22; 3 71 17 6; 1 31 22 44; 2 54 18 22; 21 47 4 26; ... %! 1 40 23 34; 11 66 9 12; 10 68 8 12]; %! yh = [78.5;74.3;104.3;87.6;95.9;109.2;102.7;72.5;93.1;115.9;83.8;113.3;109.4]; %! m = removeTerms (fitlm (Xh, yh), 'x3 + x4'); %! r = removeTerms (removeTerms (fitlm (Xh, yh), 'x4'), 'x3'); %! assert_equal (r.NumCoefficients, 3); %! assert_equal (r.DFE, 10); %! assert_equal (r.SSE, m.SSE, 1e-12); %! assert_equal (r.SSR, m.SSR, 1e-12); %! assert_equal (r.SST, m.SST, 1e-12); %! assert_equal (r.RMSE, m.RMSE, 1e-12); %! assert_equal (r.Coefficients.Estimate, m.Coefficients.Estimate, 1e-10); %! assert_equal (r.Coefficients.SE, m.Coefficients.SE, 1e-10); %! assert_equal (r.Coefficients.tStat, m.Coefficients.tStat, 1e-10); %! assert_equal (r.Coefficients.pValue, m.Coefficients.pValue, 1e-10); %! assert_equal (r.CoefficientCovariance, m.CoefficientCovariance, 1e-12); %! assert_equal (isequal (r.CoefficientNames, m.CoefficientNames), true); %! assert_equal (r.Rsquared.Ordinary, m.Rsquared.Ordinary, 1e-12); %! assert_equal (r.Rsquared.Adjusted, m.Rsquared.Adjusted, 1e-12); %! assert_equal (r.Formula.LinearPredictor, m.Formula.LinearPredictor); %!test %! m = removeTerms (mdl, [0 0 0]); %! r = removeTerms (mdl, '1'); %! assert_equal (m.NumCoefficients, 2); %! assert_equal (m.Formula.HasIntercept, false); %! assert_equal (m.Formula.LinearPredictor, 'x1 + x2'); %! assert_equal (m.Coefficients.Estimate(1), 2.96142161317611, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), -0.997248749443286, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.SSE, r.SSE, 1e-15); %! assert_equal (m.SSR, r.SSR, 1e-15); %! assert_equal (m.SST, r.SST, 1e-15); %! assert_equal (m.Coefficients.Estimate, r.Coefficients.Estimate, 1e-15); %! assert_equal (m.Coefficients.SE, r.Coefficients.SE, 1e-15); %! assert_equal (m.CoefficientCovariance, r.CoefficientCovariance, 1e-15); %! assert_equal (isequal (m.CoefficientNames, r.CoefficientNames), true); %! assert_equal (height (m.Diagnostics), 20); %! assert_equal (sum (m.Diagnostics.Leverage), 2, 1e-10); %!test %! ## removing a categorical predictor drops all its indicator variables at once %! Xc = [1;1;1;2;2;2;3;3;3]; %! yc = [2.1;2.3;1.9; 4.1;3.9;4.2; 6.3;5.8;6.1]; %! mc = fitlm (Xc, yc, 'linear', 'CategoricalVars', 1); %! m = removeTerms (mc, 'x1'); %! assert_equal (m.NumCoefficients, 1); %! assert_equal (m.NumEstimatedCoefficients, 1); %! assert_equal (m.DFE, 8); %! assert_equal (m.NumObservations, 9); %! assert_equal (m.Formula.HasIntercept, true); %! assert_equal (m.Formula.LinearPredictor, '1'); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.Coefficients.Estimate(1), 4.07777777777778, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.SSR, 0, 1e-20); %! assert_equal (size (m.CoefficientCovariance), [1, 1]); %! assert_equal (height (m.Diagnostics), 9); %! assert_equal (all (isfinite (m.Fitted)), true); %! assert_equal (sum (m.Diagnostics.Leverage), 1, 1e-10); %! assert_equal (m.Residuals.Raw, yc - m.Fitted, 1e-10); %!test %! ## matrix row removes x4 from hald leaving intercept plus x1 x2 x3 %! Xh = [7 26 6 60; 1 29 15 52; 11 56 8 20; 11 31 8 47; 7 52 6 33; ... %! 11 55 9 22; 3 71 17 6; 1 31 22 44; 2 54 18 22; 21 47 4 26; ... %! 1 40 23 34; 11 66 9 12; 10 68 8 12]; %! yh = [78.5;74.3;104.3;87.6;95.9;109.2;102.7;72.5;93.1;115.9;83.8;113.3;109.4]; %! m = removeTerms (fitlm (Xh, yh), [0 0 0 1 0]); %! assert_equal (m.NumCoefficients, 4); %! assert_equal (m.NumEstimatedCoefficients, 4); %! assert_equal (m.DFE, 9); %! assert_equal (m.NumObservations, 13); %! assert_equal (m.Coefficients.Estimate(1), 48.1936343180437, -1e-8); %! assert_equal (m.Coefficients.Estimate(2), 1.69589016748479, -1e-8); %! assert_equal (m.Coefficients.Estimate(3), 0.656914878270554, -1e-8); %! assert_equal (m.Coefficients.Estimate(4), 0.250017606680009, -1e-8); %! assert_equal (m.Coefficients.tStat, m.Coefficients.Estimate ./ m.Coefficients.SE, 1e-10); %! assert_equal (m.SSE, 48.1106140726532, -1e-8); %! assert_equal (size (m.CoefficientCovariance), [4, 4]); %! assert_equal (m.CoefficientNames{1}, '(Intercept)'); %! assert_equal (m.CoefficientNames{2}, 'x1'); %! assert_equal (m.CoefficientNames{3}, 'x2'); %! assert_equal (m.CoefficientNames{4}, 'x3'); %! assert_equal (m.Formula.HasIntercept, true); %! assert_equal (m.Formula.LinearPredictor, '1 + x1 + x2 + x3'); %! assert_equal (height (m.Diagnostics), 13); %! assert_equal (all (isfinite (m.Fitted)), true); %! assert_equal (sum (m.Diagnostics.Leverage), 4, 1e-10); %! assert_equal (m.Residuals.Raw, yh - m.Fitted, 1e-10); %!test %! ## default call creates a histogram with correct bin count and density %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, mdl); %! xd = get (h(1), 'XData'); %! yd = get (h(1), 'YData'); %! r = mdl.Residuals.Raw(! isnan (mdl.Residuals.Raw)); %! bw = xd(3,1) - xd(1,1); %! assert_equal (numel (h), 1); %! assert_equal (get (h(1), 'type'), 'patch'); %! assert_equal (size (xd, 2) > 0, true); %! assert_equal (sum (yd(2,:)) * bw, 1, 1e-10); %! assert_equal (all (yd(1,:) == 0) && all (yd(4,:) == 0), true); %! assert_equal (get (get (ax, 'xlabel'), 'string'), 'Residuals'); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Probability density'); %! assert_equal (get (get (ax, 'title'), 'string'), 'Histogram of residuals'); %! close (fig); %!test %! ## histogram bar color changes when FaceColor is passed %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, mdl, 'histogram', 'FaceColor', [0 1 0]); %! assert_equal (get (h(1), 'FaceColor'), [0 1 0], 1e-10); %! close (fig); %!test %! ## fitted plot shows residuals against fitted values with a zero reference line %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, mdl, 'fitted'); %! assert_equal (numel (h), 2); %! assert_equal (get (h(1), 'XData'), mdl.Fitted', 1e-15); %! assert_equal (get (h(1), 'YData'), mdl.Residuals.Raw', 1e-15); %! assert_equal (get (h(1), 'LineStyle'), 'none'); %! assert_equal (get (h(1), 'Marker'), 'x'); %! assert_equal (get (h(2), 'YData'), [0 0]); %! assert_equal (get (h(2), 'LineStyle'), ':'); %! assert_equal (get (get (ax, 'xlabel'), 'string'), 'Fitted values'); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Residuals'); %! assert_equal (get (get (ax, 'title'), 'string'), 'Plot of residuals vs. fitted values'); %! close (fig); %!test %! ## custom color applies to data points but leaves the reference line unchanged %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, mdl, 'fitted', 'Color', [1 0 0]); %! assert_equal (get (h(1), 'Color'), [1 0 0], 1e-10); %! assert_equal (get (h(2), 'Color'), [0.8510 0.8510 0.8510], 1e-4); %! assert_equal (get (h(2), 'LineStyle'), ':'); %! close (fig); %!test %! ## excluded rows appear as gaps in the fitted plot %! me = fitlm (X, y, 'Exclude', [3, 8]); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, me, 'fitted'); %! yd = get (h(1), 'YData'); %! assert_equal (numel (yd), 20); %! assert_equal (isnan (yd(3)), true); %! assert_equal (isnan (yd(8)), true); %! assert_equal (! isnan (yd(1)), true); %! close (fig); %!test %! ## case order plot covers all rows and shows gaps where rows were excluded %! me = fitlm (X, y, 'Exclude', [2, 5]); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, me, 'caseorder'); %! xd = get (h(1), 'XData'); %! yd = get (h(1), 'YData'); %! assert_equal (xd, 1:20); %! assert_equal (isnan (yd(2)), true); %! assert_equal (isnan (yd(5)), true); %! assert_equal (! isnan (yd(1)), true); %! assert_equal (get (h(2), 'YData'), [0 0]); %! assert_equal (get (get (ax, 'xlabel'), 'string'), 'Row number'); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Residuals'); %! assert_equal (get (get (ax, 'title'), 'string'), 'Case order plot of residuals'); %! close (fig); %!test %! ## lagged plot shows each residual against the previous one with two reference lines %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, mdl, 'lagged'); %! r = mdl.Residuals.Raw; %! assert_equal (numel (h), 3); %! assert_equal (get (h(1), 'XData'), r(1:end-1)', 1e-15); %! assert_equal (get (h(1), 'YData'), r(2:end)', 1e-15); %! assert_equal (get (h(2), 'YData'), [0 0]); %! assert_equal (get (h(3), 'XData'), [0 0]); %! assert_equal (get (h(2), 'LineStyle'), ':'); %! assert_equal (get (get (ax, 'xlabel'), 'string'), 'Residual(t-1)'); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Residual(t)'); %! assert_equal (get (get (ax, 'title'), 'string'), 'Plot of residuals vs. lagged residuals'); %! close (fig); %!test %! ## probability plot uses sorted active residuals as its data %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, mdl, 'probability'); %! r_s = sort (mdl.Residuals.Raw(! isnan (mdl.Residuals.Raw))); %! assert_equal (get (h(1), 'XData'), r_s', 1e-15); %! assert_equal (get (get (ax, 'xlabel'), 'string'), 'Residuals'); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Probability'); %! assert_equal (get (get (ax, 'title'), 'string'), 'Normal probability plot of residuals'); %! close (fig); %!test %! ## observed plot connects each point to the reference line with a vertical segment %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, mdl, 'observed'); %! obs = mdl.Variables{:, mdl.ResponseName}; %! assert_equal (numel (h), 3); %! assert_equal (get (h(1), 'XData'), mdl.Fitted', 1e-15); %! assert_equal (get (h(1), 'YData'), obs', 1e-15); %! assert_equal (isequal (get (h(2), 'XData'), get (h(2), 'YData')), true); %! xd3 = get (h(3), 'XData'); %! assert_equal (numel (xd3), 3 * mdl.NumObservations); %! assert_equal (sum (isnan (xd3)), mdl.NumObservations); %! assert_equal (get (get (ax, 'xlabel'), 'string'), 'Fitted values'); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Observed response values'); %! assert_equal (get (get (ax, 'title'), 'string'), 'Plot of observed vs. fitted values'); %! close (fig); %!test %! ## symmetry plot measures distance from median in both tails %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, mdl, 'symmetry'); %! r_s = sort (mdl.Residuals.Raw(! isnan (mdl.Residuals.Raw))); %! med = median (r_s); %! m = floor (numel (r_s) / 2); %! x_sym = sort (med - r_s(1:m)); %! y_sym = sort (r_s(end-m+1:end) - med); %! assert_equal (numel (h), 2); %! assert_equal (get (h(1), 'XData'), x_sym', 1e-15); %! assert_equal (get (h(1), 'YData'), y_sym', 1e-15); %! assert_equal (isequal (get (h(2), 'XData'), get (h(2), 'YData')), true); %! assert_equal (get (h(2), 'LineStyle'), ':'); %! assert_equal (get (get (ax, 'xlabel'), 'string'), 'Lower tail'); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Upper tail'); %! close (fig); %!test %! ## switching to pearson residuals changes plotted values but not x positions %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, mdl, 'fitted', 'ResidualType', 'pearson'); %! assert_equal (get (h(1), 'YData'), mdl.Residuals.Pearson', 1e-15); %! assert_equal (get (h(1), 'XData'), mdl.Fitted', 1e-15); %! assert_equal (! isequal (get (h(1), 'YData'), mdl.Residuals.Raw'), true); %! close (fig); %!test %! ## standardized and studentized residuals produce different values %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h1 = plotResiduals (ax, mdl, 'caseorder', 'ResidualType', 'standardized'); %! h2 = plotResiduals (ax, mdl, 'caseorder', 'ResidualType', 'studentized'); %! assert_equal (get (h1(1), 'YData'), mdl.Residuals.Standardized', 1e-15); %! assert_equal (get (h2(1), 'YData'), mdl.Residuals.Studentized', 1e-15); %! assert_equal (! isequal (get (h1(1), 'YData'), get (h2(1), 'YData')), true); %! close (fig); %!test %! ## marker style and size apply to data points but not to the reference line %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, mdl, 'fitted', 'Marker', 's', 'MarkerSize', 10); %! assert_equal (get (h(1), 'Marker'), 's'); %! assert_equal (get (h(1), 'MarkerSize'), 10); %! assert_equal (get (h(2), 'Marker'), 'none'); %! close (fig); %!test %! ## weighted model residuals differ from unweighted residuals in the fitted plot %! mw = fitlm (X, y, 'Weights', (1:n)' / sum (1:n)); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, mw, 'fitted'); %! assert_equal (get (h(1), 'YData'), mw.Residuals.Raw', 1e-15); %! assert_equal (! isequal (get (h(1), 'YData'), mdl.Residuals.Raw'), true); %! close (fig); %!test %! ## calling without an axes handle plots into the current axes %! fig = figure ('visible', 'off'); %! h = plotResiduals (mdl, 'fitted'); %! assert_equal (isgraphics (get (h(1), 'Parent'), 'axes'), true); %! assert_equal (isequal (get (h(1), 'Parent'), gca ()), true); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotDiagnostics (ax, mdl); %! yd = get (h(1), 'YData'); %! assert_equal (numel (h), 2); %! assert_equal (get (h(1), 'XData'), 1:n); %! assert_equal (yd(1), 0.370779220779221, -1e-10); %! assert_equal (yd(2), 0.245283663704716, -1e-10); %! assert_equal (yd(3), 0.164718614718615, -1e-10); %! assert_equal (yd(4), 0.118147641831852, -1e-10); %! assert_equal (yd(5), 0.0960013670539986, -1e-10); %! assert_equal (yd(6), 0.0900774663932558, -1e-10); %! assert_equal (yd(7), 0.0935406698564593, -1e-10); %! assert_equal (yd(8), 0.100922761449077, -1e-10); %! assert_equal (yd(9), 0.108122579175211, -1e-10); %! assert_equal (yd(10), 0.112406015037594, -1e-10); %! assert_equal (yd(11), 0.112406015037594, -1e-10); %! assert_equal (yd(12), 0.108122579175211, -1e-10); %! assert_equal (yd(13), 0.100922761449077, -1e-10); %! assert_equal (yd(14), 0.0935406698564592, -1e-10); %! assert_equal (yd(15), 0.0900774663932559, -1e-10); %! assert_equal (yd(16), 0.0960013670539986, -1e-10); %! assert_equal (yd(17), 0.118147641831852, -1e-10); %! assert_equal (yd(18), 0.164718614718615, -1e-10); %! assert_equal (yd(19), 0.245283663704716, -1e-10); %! assert_equal (yd(20), 0.370779220779221, -1e-10); %! assert_equal (get (h(2), 'YData'), [0.3, 0.3], 1e-12); %! assert_equal (get (h(2), 'XData'), [0, n]); %! assert_equal (get (h(2), 'LineStyle'), ':'); %! assert_equal (get (get (ax, 'xlabel'), 'string'), 'Row number'); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Leverage'); %! assert_equal (get (get (ax, 'title'), 'string'), 'Case order plot of leverage'); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotDiagnostics (ax, mdl, 'leverage', 'Color', [1 0 0]); %! assert_equal (get (h(1), 'Color'), [1 0 0], 1e-10); %! assert_equal (get (h(2), 'Color'), [0.8510 0.8510 0.8510], 1e-4); %! assert_equal (get (h(2), 'LineStyle'), ':'); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotDiagnostics (ax, mdl, 'cookd'); %! yd = get (h(1), 'YData'); %! assert_equal (numel (h), 2); %! assert_equal (yd(1), 0.078517048682575, -1e-8); %! assert_equal (yd(2), 0.077211407930332, -1e-8); %! assert_equal (yd(3), 0.001953301452841, -1e-7); %! assert_equal (get (h(2), 'YData'), [0.1668641787, 0.1668641787], -1e-8); %! assert_equal (get (h(2), 'XData'), [0, n]); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Cook''s distance'); %! assert_equal (get (get (ax, 'title'), 'string'), 'Case order plot of Cook''s distance'); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotDiagnostics (ax, mdl, 'covratio'); %! yd = get (h(1), 'YData'); %! yv = get (h(2), 'YData'); %! xv = get (h(2), 'XData'); %! assert_equal (numel (h), 2); %! assert_equal (yd(1), 1.774933177, -1e-8); %! assert_equal (yd(2), 1.397661919, -1e-8); %! assert_equal (yd(3), 1.428481535, -1e-8); %! assert_equal (numel (xv), 5); %! assert_equal (sum (isnan (xv)), 1); %! assert_equal (yv(1), 0.55, 1e-12); %! assert_equal (yv(2), 0.55, 1e-12); %! assert_equal (yv(4), 1.45, 1e-12); %! assert_equal (yv(5), 1.45, 1e-12); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Covariance ratio'); %! assert_equal (get (get (ax, 'title'), 'string'), 'Case order plot of covariance ratio'); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotDiagnostics (ax, mdl, 'dfbetas'); %! p = mdl.NumCoefficients; %! yv = get (h(p+1), 'YData'); %! xv = get (h(p+1), 'XData'); %! assert_equal (numel (h), p + 1); %! assert_equal (numel (get (h(1), 'YData')), n); %! assert_equal (numel (get (h(2), 'YData')), n); %! assert_equal (numel (get (h(3), 'YData')), n); %! assert_equal (numel (xv), 5); %! assert_equal (sum (isnan (xv)), 1); %! assert_equal (yv(1), -0.6708203932, -1e-8); %! assert_equal (yv(end), 0.6708203932, -1e-8); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Scaled change in coefficients'); %! assert_equal (get (get (ax, 'title'), 'string'), 'Case order plot of scaled change in coefficients'); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotDiagnostics (ax, mdl, 'dfbetas', 'Color', [1 0 0]); %! p = mdl.NumCoefficients; %! for k = 1:p %! assert_equal (get (h(k), 'Color'), [1 0 0], 1e-10); %! endfor %! assert_equal (get (h(p+1), 'Color'), [0.8510 0.8510 0.8510], 1e-4); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotDiagnostics (ax, mdl, 'dffits'); %! yd = get (h(1), 'YData'); %! yv = get (h(2), 'YData'); %! xv = get (h(2), 'XData'); %! assert_equal (numel (h), 2); %! assert_equal (yd(1), 0.476480465355394, -1e-8); %! assert_equal (yd(2), 0.477020506700835, -1e-8); %! assert_equal (yd(3), -0.074329411030064, -1e-7); %! assert_equal (sum (isnan (xv)), 1); %! assert_equal (yv(1), -0.7745966692, -1e-8); %! assert_equal (yv(end), 0.7745966692, -1e-8); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Scaled change in fit'); %! assert_equal (get (get (ax, 'title'), 'string'), 'Case order plot of scaled change in fit'); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotDiagnostics (ax, mdl, 's2_i'); %! yd = get (h(1), 'YData'); %! assert_equal (numel (h), 2); %! assert_equal (yd(1), 0.02359100986, -1e-8); %! assert_equal (yd(2), 0.02314622330, -1e-8); %! assert_equal (yd(3), 0.02411685408, -1e-8); %! assert_equal (get (h(2), 'YData'), [0.02273796067, 0.02273796067], -1e-8); %! assert_equal (get (h(2), 'XData'), [0, n]); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Leave-one-out variance'); %! assert_equal (get (get (ax, 'title'), 'string'), 'Case order plot of leave-one-out variance'); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotDiagnostics (ax, mdl, 'contour'); %! yd = get (h(1), 'YData'); %! xd = get (h(1), 'XData'); %! assert_equal (numel (h), 2); %! assert_equal (xd(1), 0.3707792208, -1e-8); %! assert_equal (xd(2), 0.2452836637, -1e-8); %! assert_equal (yd(1), 0.07562471113, -1e-8); %! assert_equal (yd(2), 0.11059272450, -1e-8); %! assert_equal (get (h(1), 'LineStyle'), 'none'); %! assert_equal (get (h(1), 'Marker'), 'x'); %! assert_equal (isgraphics (h(2)), true); %! assert_equal (get (get (ax, 'xlabel'), 'string'), 'Leverage'); %! assert_equal (get (get (ax, 'ylabel'), 'string'), 'Residual'); %! assert_equal (get (get (ax, 'title'), 'string'), 'Cook''s distance factorization'); %! close (fig); %!test %! me = fitlm (X, y, 'Exclude', [2, 7]); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h_ex = plotDiagnostics (ax, me, 'cookd'); %! h_un = plotDiagnostics (ax, mdl, 'cookd'); %! ref_ex = get (h_ex(2), 'YData'); %! ref_un = get (h_un(2), 'YData'); %! assert_equal (! isequal (ref_ex, ref_un), true); %! assert_equal (ref_ex(1), 3 * mean (me.Diagnostics.CooksDistance, 'omitnan'), 1e-12); %! close (fig); %!test %! mw = fitlm (X, y, 'Weights', (1:n)' / sum (1:n)); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! hw = plotDiagnostics (ax, mw, 'leverage'); %! hu = plotDiagnostics (ax, mdl, 'leverage'); %! ydw = get (hw(1), 'YData'); %! ydu = get (hu(1), 'YData'); %! assert_equal (ydw(1) != ydu(1), true); %! assert_equal (! isequal (ydw, ydu), true); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! h = plotDiagnostics (mdl); %! assert_equal (isequal (get (h(1), 'Parent'), gca ()), true); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotEffects (ax, mdl); %! xd1 = get (h(1), 'XData'); %! yd1 = get (h(1), 'YData'); %! xd2 = get (h(2), 'XData'); %! yd2 = get (h(2), 'YData'); %! xd3 = get (h(3), 'XData'); %! yd3 = get (h(3), 'YData'); %! ytl = get (ax, 'YTickLabel'); %! assert_equal (numel (h), 3); %! assert_equal (xd1(1), 2.38302891604232, -1e-10); %! assert_equal (xd1(2), -19.5277648300125, -1e-10); %! assert_equal (yd1, [1 2]); %! assert_equal (xd2(1), 1.39673712385796, -1e-10); %! assert_equal (xd2(2), 3.36932070822668, -1e-10); %! assert_equal (yd2, [1 1]); %! assert_equal (xd3(1), -20.4857975891918, -1e-10); %! assert_equal (xd3(2), -18.5697320708331, -1e-10); %! assert_equal (yd3, [2 2]); %! assert_equal (get (h(1), 'Color'), [0.1490 0.5490 0.8660], 1e-4); %! assert_equal (get (h(2), 'Color'), [0.1490 0.5490 0.8660], 1e-4); %! assert_equal (get (h(3), 'Color'), [0.1490 0.5490 0.8660], 1e-4); %! assert_equal (get (h(1), 'Marker'), 'o'); %! assert_equal (get (h(1), 'LineStyle'), 'none'); %! assert_equal (get (h(2), 'LineStyle'), '-'); %! assert_equal (get (h(2), 'Marker'), 'none'); %! assert_equal (get (h(3), 'LineStyle'), '-'); %! assert_equal (get (h(3), 'Marker'), 'none'); %! assert_equal (mean (xd2), xd1(1), 1e-10); %! assert_equal (mean (xd3), xd1(2), 1e-10); %! assert_equal (get (get (ax, 'xlabel'), 'string'), 'Main Effect'); %! assert_equal (get (get (ax, 'ylabel'), 'string'), ''); %! assert_equal (get (get (ax, 'title'), 'string'), 'Main Effects Plot'); %! assert_equal (get (ax, 'YTick'), [1 2]); %! assert_equal (ytl{1}, 'x1: 0.05 to 1'); %! assert_equal (ytl{2}, 'x2: 0.05 to 20'); %! close (fig); %!test %! ## 3-predictor model %! X3 = [X, sin((1:n)' * pi / n)]; %! y3 = X3 * [3; -1; 2] + 0.1 * cos ((1:n)' * pi / 7); %! m3 = fitlm (X3, y3); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotEffects (ax, m3); %! xd1 = get (h(1), 'XData'); %! yd1 = get (h(1), 'YData'); %! xd2 = get (h(2), 'XData'); %! yd2 = get (h(2), 'YData'); %! xd3 = get (h(3), 'XData'); %! yd3 = get (h(3), 'YData'); %! xd4 = get (h(4), 'XData'); %! yd4 = get (h(4), 'YData'); %! ytl = get (ax, 'YTickLabel'); %! assert_equal (numel (h), 4); %! assert_equal (xd1(1), 8.10687671732127, -1e-10); %! assert_equal (xd1(2), -25.4487243632125, -1e-10); %! assert_equal (xd1(3), 0.661302203942261, -1e-10); %! assert_equal (yd1, [1 2 3]); %! assert_equal (xd2(1), 0.565266595687836, -1e-10); %! assert_equal (xd2(2), 15.6484868389547, -1e-10); %! assert_equal (yd2, [1 1]); %! assert_equal (xd3(1), -33.3368582824351, -1e-10); %! assert_equal (xd3(2), -17.5605904439899, -1e-10); %! assert_equal (yd3, [2 2]); %! assert_equal (xd4(1), -1.25582490831999, -1e-10); %! assert_equal (xd4(2), 2.57842931620451, -1e-10); %! assert_equal (yd4, [3 3]); %! assert_equal (get (ax, 'YTick'), [1 2 3]); %! assert_equal (ytl{1}, 'x1: 0.05 to 1'); %! assert_equal (ytl{2}, 'x2: 0.05 to 20'); %! assert_equal (ytl{3}, 'x3: 1.22465e-16 to 1'); %! assert_equal (mean (xd2), xd1(1), 1e-10); %! assert_equal (mean (xd3), xd1(2), 1e-10); %! assert_equal (mean (xd4), xd1(3), 1e-10); %! close (fig); %!test %! me = fitlm (X, y, 'Exclude', [2, 7]); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotEffects (ax, me); %! xd1 = get (h(1), 'XData'); %! yd1 = get (h(1), 'YData'); %! xd2 = get (h(2), 'XData'); %! yd2 = get (h(2), 'YData'); %! xd3 = get (h(3), 'XData'); %! yd3 = get (h(3), 'YData'); %! ytl = get (ax, 'YTickLabel'); %! assert_equal (numel (h), 3); %! assert_equal (xd1(1), 2.50035744908398, -1e-10); %! assert_equal (xd1(2), -19.5912988214488, -1e-10); %! assert_equal (yd1, [1 2]); %! assert_equal (xd2(1), 1.40421088339552, -1e-10); %! assert_equal (xd2(2), 3.59650401477245, -1e-10); %! assert_equal (yd2, [1 1]); %! assert_equal (xd3(1), -20.6333076647782, -1e-10); %! assert_equal (xd3(2), -18.5492899781194, -1e-10); %! assert_equal (yd3, [2 2]); %! assert_equal (ytl{1}, 'x1: 0.05 to 1'); %! assert_equal (ytl{2}, 'x2: 0.05 to 20'); %! assert_equal (mean (xd2), xd1(1), 1e-10); %! assert_equal (mean (xd3), xd1(2), 1e-10); %! close (fig); %!test %! mw = fitlm (X, y, 'Weights', (1:n)' / sum (1:n)); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotEffects (ax, mw); %! xd1 = get (h(1), 'XData'); %! yd1 = get (h(1), 'YData'); %! xd2 = get (h(2), 'XData'); %! yd2 = get (h(2), 'YData'); %! xd3 = get (h(3), 'XData'); %! yd3 = get (h(3), 'YData'); %! ytl = get (ax, 'YTickLabel'); %! assert_equal (numel (h), 3); %! assert_equal (xd1(1), 2.51587141860715, -1e-10); %! assert_equal (xd1(2), -19.6411669663483, -1e-10); %! assert_equal (yd1, [1 2]); %! assert_equal (xd2(1), 1.08491557053384, -1e-10); %! assert_equal (xd2(2), 3.94682726668046, -1e-10); %! assert_equal (yd2, [1 1]); %! assert_equal (xd3(1), -20.8383905241664, -1e-10); %! assert_equal (xd3(2), -18.4439434085302, -1e-10); %! assert_equal (yd3, [2 2]); %! assert_equal (ytl{1}, 'x1: 0.05 to 1'); %! assert_equal (ytl{2}, 'x2: 0.05 to 20'); %! assert_equal (mean (xd2), xd1(1), 1e-10); %! assert_equal (mean (xd3), xd1(2), 1e-10); %! close (fig); %!test %! mni = fitlm (X, y, 'Intercept', false); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotEffects (ax, mni); %! xd1 = get (h(1), 'XData'); %! yd1 = get (h(1), 'YData'); %! xd2 = get (h(2), 'XData'); %! yd2 = get (h(2), 'YData'); %! xd3 = get (h(3), 'XData'); %! yd3 = get (h(3), 'YData'); %! ytl = get (ax, 'YTickLabel'); %! assert_equal (numel (h), 3); %! assert_equal (xd1(1), 2.81335053251731, -1e-10); %! assert_equal (xd1(2), -19.8951125513936, -1e-10); %! assert_equal (yd1, [1 2]); %! assert_equal (xd2(1), 2.36234515544211, -1e-10); %! assert_equal (xd2(2), 3.26435590959250, -1e-10); %! assert_equal (yd2, [1 1]); %! assert_equal (xd3(1), -20.4919734818287, -1e-10); %! assert_equal (xd3(2), -19.2982516209584, -1e-10); %! assert_equal (yd3, [2 2]); %! assert_equal (ytl{1}, 'x1: 0.05 to 1'); %! assert_equal (ytl{2}, 'x2: 0.05 to 20'); %! assert_equal (mean (xd2), xd1(1), 1e-10); %! assert_equal (mean (xd3), xd1(2), 1e-10); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotEffects (ax, mdl); %! assert_equal (isequal (get (h(1), 'Parent'), ax), true); %! assert_equal (get (h(1), 'XData'), [2.38302891604232, -19.5277648300125], -1e-10); %! close (fig); %!test %! fig = figure ('visible', 'off'); %! h = plotEffects (mdl); %! assert_equal (isequal (get (h(1), 'Parent'), gca ()), true); %! assert_equal (get (h(1), 'XData'), [2.38302891604232, -19.5277648300125], -1e-10); %! close (fig); %!test %! ## numeric predictor adjusted data %! fig = figure ('visible', 'off'); %! h = plotAdjustedResponse (mdl, 'x1'); %! assert_equal (get (h(1), 'XData'), X(:,1)', 1e-10); %! assert_equal (get (h(1), 'YData'), ... %! [-6.70590752804287, -6.54551694016554, -6.5544435914624, -6.59143572669715, ... %! -6.49138418414686, -6.21712299745016, -5.89359961368025, -5.69299878673044, ... %! -5.67643670865536, -5.73777106454765, -5.70118778736348, -5.48284370035446, ... %! -5.16795154710756, -4.93243578806991, -4.88118846293094, -4.95163193306634, ... %! -4.97125247389768, -4.81620859768203, -4.52519034621851, -4.26384784484646], 1e-8); %! xf = get (h(2), 'XData'); %! yf = get (h(2), 'YData'); %! assert_equal (numel (xf), 100); %! assert_equal (xf(1:5), ... %! [0.05, 0.0595959595959596, 0.0691919191919192, 0.0787878787878788, 0.0883838383838384], 1e-10); %! assert_equal (yf(1:5), ... %! [-6.78153223917696, -6.75746124002502, -6.73339024087308, -6.70931924172113, -6.68524824256919], 1e-8); %! assert_equal (xf(end-4:end), ... %! [0.961616161616162, 0.971212121212121, 0.980808080808081, 0.99040404040404, 1], 1e-10); %! assert_equal (yf(end-4:end), ... %! [-4.49478731974241, -4.47071632059047, -4.44664532143853, -4.42257432228658, -4.39850332313464], 1e-8); %! close (fig); %!test %! ## title and axis labels follow the standard convention %! fig = figure ('visible', 'off'); %! plotAdjustedResponse (mdl, 'x1'); %! assert_equal (get (get (gca, 'Title'), 'String'), 'Adjusted response plot'); %! assert_equal (get (get (gca, 'XLabel'), 'String'), 'x1'); %! assert_equal (get (get (gca, 'YLabel'), 'String'), 'Adjusted y'); %! close (fig); %!test %! ## ax routing and a second predictor %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotAdjustedResponse (ax, mdl, 'x2'); %! assert_equal (isequal (get (h(1), 'Parent'), ax), true); %! assert_equal (get (h(1), 'XData'), X(:,2)', 1e-8); %! assert_equal (get (h(1), 'YData'), ... %! [1.45980865498274, 1.34795136885775, 0.968893310576047, 0.463886235373945, ... %! -0.00196069502564064, -0.391481514261341, -0.829623669406342, -1.48857191435397, ... %! -2.42944244115883, -3.5460929349136, -4.66270932857441, -5.6954484453929, ... %! -6.72952302895603, -7.94085753971093, -9.43434401734702, -11.14740482324, ... %! -12.9075262328114, -14.5908667583184, -16.23611644156, -18.0089254078756], 1e-7); %! close (fig); %!test %! ## name-value arguments style the data points only %! fig = figure ('visible', 'off'); %! h = plotAdjustedResponse (mdl, 'x1', 'Marker', 's', 'MarkerSize', 10, 'Color', 'r'); %! assert_equal (get (h(1), 'Marker'), 's'); %! assert_equal (get (h(1), 'MarkerSize'), 10); %! assert_equal (get (h(1), 'Color'), [1 0 0]); %! assert_equal (get (h(2), 'Marker'), 'none'); %! close (fig); %!test %! yn = y; %! yn(3) = NaN; %! mn = fitlm (X, yn); %! fig = figure ('visible', 'off'); %! h = plotAdjustedResponse (mn, 'x1'); %! xd = get (h(1), 'XData'); %! yd = get (h(1), 'YData'); %! assert_equal (isnan (xd(3)), true); %! assert_equal (isnan (yd(3)), true); %! assert_equal (yd([1 2 4]), [-7.04679028889336, -6.88651148335619, -6.93287739924846], 1e-8); %! close (fig); %!test %! w = mod ((1:n)', 3) + 1; %! mw = fitlm (X, y, 'Weights', w); %! fig = figure ('visible', 'off'); %! h = plotAdjustedResponse (mw, 'x1'); %! assert_equal (get (h(1), 'YData'), ... %! [-6.66184168801736, -6.5023788020353, -6.51285162315763, -6.55200839614801, ... %! -6.45473995928354, -6.18388034620285, -5.86437700397912, -5.66841468650568, ... %! -5.65710958583715, -5.72431938706618, -5.69423002314892, -5.482998317337, ... %! -5.17583701321739, -4.9486705712372, -4.90639103108588, -4.98642075413911, ... %! -5.01624601581846, -4.87202532838101, -4.59244873362587, -4.34316635689238], 1e-7); %! close (fig); %!test %! ## robust regression %! mr = fitlm (X, y, 'RobustOpts', 'on'); %! fig = figure ('visible', 'off'); %! h = plotAdjustedResponse (mr, 'x1'); %! assert_equal (get (h(1), 'YData'), ... %! [-6.69986109212516, -6.53959779763556, -6.54873660457867, -6.58602575771814, ... %! -6.48635609533107, -6.2125616510561, -5.88958987196639, -5.68962551195529, ... %! -5.67378476307741, -5.7359253104254, -5.70023308695542, -5.48286491591908, ... %! -5.16903354090336, -4.93466342235539, -4.88464659996458, -4.95640543510664, ... %! -4.97742620320314, -4.82386741651113, -4.53441911682976, -4.27473142949835], 1e-7); %! close (fig); %!test %! ## numeric predictor averaged over a categorical predictor %! wt = [3504;3693;3436;3433;3449;3672;3705;3288;3092;2500;2700;3100]; %! yr = categorical ([70;70;70;70;70;76;76;76;82;82;82;82]); %! mg = [18;15;18;16;17;20;22;24;30;32;28;26]; %! tc = table (mg, wt, yr, 'VariableNames', {'MPG','Weight','Year'}); %! mc = fitlm (tc, 'MPG ~ Year + Weight'); %! fig = figure ('visible', 'off'); %! h = plotAdjustedResponse (mc, 'Weight'); %! assert_equal (get (h(1), 'XData'), wt', 1e-10); %! assert_equal (get (h(1), 'YData'), ... %! [22.1247351073949, 19.1247351073949, 22.1247351073949, 20.1247351073949, ... %! 21.1247351073949, 18.6102199722546, 20.6102199722546, 22.6102199722546, ... %! 25.8864161365654, 27.8864161365654, 23.8864161365654, 21.8864161365654], 1e-8); %! xf = get (h(2), 'XData'); %! yf = get (h(2), 'YData'); %! assert_equal (xf(1:5), ... %! [2500, 2512.17171717172, 2524.34343434343, 2536.51515151515, 2548.68686868687], 1e-8); %! assert_equal (yf(1:5), ... %! [26.9912481948118, 26.9176291703666, 26.8440101459214, 26.7703911214761, 26.6967720970309], 1e-8); %! close (fig); %!test %! ## categorical predictor evaluated per level %! wt = [3504;3693;3436;3433;3449;3672;3705;3288;3092;2500;2700;3100]; %! yr = categorical ([70;70;70;70;70;76;76;76;82;82;82;82]); %! mg = [18;15;18;16;17;20;22;24;30;32;28;26]; %! tc = table (mg, wt, yr, 'VariableNames', {'MPG','Weight','Year'}); %! mc = fitlm (tc, 'MPG ~ Year + Weight'); %! fig = figure ('visible', 'off'); %! h = plotAdjustedResponse (mc, 'Year'); %! assert_equal (get (h(1), 'XData'), [1 1 1 1 1 2 2 2 3 3 3 3]); %! assert_equal (get (h(1), 'YData'), ... %! [19.2479799272553, 17.3911214761304, 18.8366909043795, 16.8185458004291, ... %! 17.9153196881646, 22.2641057484776, 24.463701891932, 23.9415324428265, ... %! 28.7560523180671, 27.1754184718549, 24.3850920685482, 24.8044392619348], 1e-7); %! assert_equal (get (h(2), 'XData'), [1 2 3]); %! assert_equal (get (h(2), 'YData'), [18.0419315592718, 23.5564466944121, 26.2802505301012], 1e-8); %! assert_equal (get (gca, 'XTickLabel'), {'70'; '76'; '82'}); %! close (fig); %!test %! ## added variable plot for the whole model %! fig = figure ('visible', 'off'); %! h = plotAdded (mdl); %! assert_equal (get (get (gca, 'Title'), 'String'), 'Added variable plot for whole model'); %! assert_equal (get (get (gca, 'XLabel'), 'String'), 'Adjusted whole model'); %! assert_equal (get (h(1), 'XData'), ... %! [0.0284033824838481, 0.0204548552857604, -0.0238455815942649, -0.104497928156226, ... %! -0.221502184400124, -0.374858350325959, -0.564566425933731, -0.79062641122344, ... %! -1.05303830619508, -1.35180211084867, -1.68691782518418, -2.05838544920164, ... %! -2.46620498290103, -2.91037642628236, -3.39089977934563, -3.90777504209083, ... %! -4.46100221451797, -5.05058129662704, -5.67651228841805, -6.338795189891], 1e-7); %! assert_equal (get (h(1), 'YData'), ... %! [0.268294196961578, 0.281859485365135, 0.0282240016119717, -0.351360499061586, ... %! -0.691784854932629, -0.955883099639786, -1.26860268025624, -1.80212835067532, ... %! -2.61757630295165, -3.60880422217788, -4.59999804131014, -5.50731458360009, ... %! -6.41596659263467, -7.50187852886103, -8.86994243196858, -10.457580663333, ... %! -12.0922794983759, -13.6501974493543, -15.1700245580674, -16.8174109498545], 1e-7); %! assert_equal (get (h(2), 'DisplayName'), 'Fit: y = 2.69267*x'); %! close (fig); %!test %! ## added variable plot for just the intercept term %! fig = figure ('visible', 'off'); %! h = plotAdded (mdl, 1); %! assert_equal (get (get (gca, 'Title'), 'String'), 'Added variable plot for (Intercept)'); %! assert_equal (get (h(1), 'YData'), ... %! [-5.41993222738086, -5.40485070721962, -5.55724508427077, -5.73586360329798, ... %! -5.77559710257835, -5.63927961575051, -5.45185858988763, -5.38551877888305, ... %! -5.50137637479139, -5.6932890627053, -5.78544277558092, -5.6939943366699, ... %! -5.50415648955918, -5.3918536946959, -5.4619779917695, -5.65195174215564, ... %! -5.78926122127593, -5.75006494138741, -5.57305294428921, -5.42387535532067], 1e-7); %! assert_equal (get (h(2), 'DisplayName'), 'Fit: y = 0.116189*x'); %! close (fig); %!test %! ## added variable plot for one predictor picked by index %! fig = figure ('visible', 'off'); %! h = plotAdded (mdl, 2); %! assert_equal (get (get (gca, 'Title'), 'String'), 'Added variable plot for x1'); %! assert_equal (get (h(1), 'YData'), ... %! [-5.90289714234183, -5.75941203626873, -5.79651449057265, -5.87295275001727, ... %! -5.82361765287968, -5.6113432327985, -5.36107693684693, -5.24500351891828, ... %! -5.32423917106718, -5.49264157838629, -5.57439667383173, -5.48566128065516, ... %! -5.31164814244354, -5.22828171964398, -5.34045405194592, -5.58558750072505, ... %! -5.79116834140295, -5.83335508623668, -5.75083777702536, -5.70926653910833], 1e-7); %! assert_equal (get (h(2), 'DisplayName'), 'Fit: y = 2.50845*x'); %! xf = get (h(2), 'XData'); %! yf = get (h(2), 'YData'); %! assert_equal (xf(1:3), [0.370121951219512, 0.372449462532903, 0.374776973846294], 1e-9); %! assert_equal (yf(1:3), [-5.97852185347592, -5.97268340425253, -5.96684495502913], 1e-7); %! close (fig); %!test %! ## added variable plot for the other predictor, a negative slope this time %! fig = figure ('visible', 'off'); %! h = plotAdded (mdl, 3); %! assert_equal (get (get (gca, 'Title'), 'String'), 'Added variable plot for x2'); %! assert_equal (get (h(1), 'YData'), ... %! [-8.3040737600235, -7.38815394983204, -6.7394349117973, -6.21666489068295, ... %! -5.65473472476609, -5.01647844768535, -4.4268435065139, -4.05801465514508, ... %! -3.9711080856335, -4.05998148307182, -4.14882078041619, -4.15378280091823, ... %! -4.16008028816491, -4.34363770260337, -4.80934708392301, -5.49463079349955, ... %! -6.22697510675454, -6.88253853594507, -7.50001112287024, -8.2450429928694], 1e-7); %! assert_equal (get (h(2), 'DisplayName'), 'Fit: y = -0.978835*x'); %! close (fig); %!test %! ## ax argument sends the plot to that axes %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotAdded (ax, mdl, 2); %! assert_equal (isequal (get (h(1), 'Parent'), ax), true); %! close (fig); %!test %! ## name-value styling only changes the data points, not the fit line %! fig = figure ('visible', 'off'); %! h = plotAdded (mdl, 2, 'Marker', 's', 'MarkerSize', 10, 'Color', 'r'); %! assert_equal (get (h(1), 'Marker'), 's'); %! assert_equal (get (h(1), 'MarkerSize'), 10); %! assert_equal (get (h(1), 'Color'), [1 0 0]); %! assert_equal (get (h(2), 'Marker'), 'none'); %! close (fig); %!test %! ## a missing observation leaves a gap in the adjusted data %! yn = y; %! yn(3) = NaN; %! mn = fitlm (X, yn); %! fig = figure ('visible', 'off'); %! h = plotAdded (mn, 2); %! xd = get (h(1), 'XData'); %! yd = get (h(1), 'YData'); %! assert_equal (isnan (xd(3)), true); %! assert_equal (isnan (yd(3)), true); %! close (fig); %!test %! ## weighted fit still gives a proper added variable plot %! w = mod ((1:n)', 3) + 1; %! mw = fitlm (X, y, 'Weights', w); %! fig = figure ('visible', 'off'); %! h = plotAdded (mw, 2); %! assert_equal (get (h(1), 'YData'), ... %! [-6.04899282212585, -5.90562605001686, -5.9429257275943, -6.01964009962184, ... %! -5.97066000437658, -5.75881947549715, -5.50906596005672, -5.39358421194863, ... %! -5.4734904232275, -5.64264227898597, -5.7252257121802, -5.63739754606181, ... %! -5.46437052421778, -5.38206910709522, -5.49538533438357, -5.74174156745852, ... %! -5.94862408174164, -5.99219138949, -5.91113353250271, -5.87110063611913], 1e-7); %! assert_equal (get (h(2), 'DisplayName'), 'Fit: y = 2.38836*x'); %! close (fig); %!test %! ## robust fit still gives a proper added variable plot %! mr = fitlm (X, y, 'RobustOpts', 'on'); %! fig = figure ('visible', 'off'); %! h = plotAdded (mr, 2); %! assert_equal (get (h(1), 'YData'), ... %! [-5.89409568732029, -5.75060102429134, -5.78768755033552, -5.86410351021651, ... %! -5.81473974221138, -5.60243027995878, -5.35212257053188, -5.23600136782402, ... %! -5.31518286388981, -5.4835247438219, -5.56521294057644, -5.47640427740507, ... %! -5.30231149789475, -5.2188590624926, -5.33093901088806, -5.5759737044568, ... %! -5.78144941862042, -5.82352466563597, -5.74088948730258, -5.69919400895959], 1e-7); %! assert_equal (get (h(2), 'DisplayName'), 'Fit: y = 2.48815*x'); %! close (fig); %!test %! ## added variable plot for the whole model %! load carsmall %! Year = categorical (Model_Year); %! tbl = table (MPG, Weight, Year); %! mdl1 = fitlm (tbl, 'MPG ~ Year + Weight^2'); %! fig = figure ('visible', 'off'); %! h = plotAdded (mdl1); %! assert_equal (get (get (gca, 'Title'), 'String'), 'Added variable plot for whole model'); %! assert_equal (get (get (gca, 'XLabel'), 'String'), 'Adjusted whole model'); %! xd = get (h(1), 'XData'); %! yd = get (h(1), 'YData'); %! assert_equal (xd(1:5), ... %! [-4.54006581304461, -4.65629283432076, -4.49502736854252, ... %! -4.49300111649177, -4.50376945388336], 1e-7); %! assert_equal (yd(1:5), [18, 15, 18, 16, 17], 1e-8); %! assert_equal (get (h(2), 'DisplayName'), 'Fit: y = 8.44866*x'); %! xf = get (h(2), 'XData'); %! yf = get (h(2), 'YData'); %! assert_equal (xf(1:3), [-5.06005142297709, -5.03050027197254, -5.000949120968], 1e-7); %! assert_equal (yf(1:3), [11.4556384009199, 11.7053059986795, 11.9549735964392], 1e-7); %! close (fig); %!test %! ## selecting every non-intercept coefficient is the whole model; any %! ## other multi-coefficient selection is a list of specified terms %! X3 = [X, cos((1:n)')]; %! m3 = fitlm (X3, y); %! fig = figure ('visible', 'off'); %! plotAdded (m3, [2 3 4]); %! assert_equal (get (get (gca, 'Title'), 'String'), ... %! 'Added variable plot for whole model'); %! assert_equal (get (get (gca, 'XLabel'), 'String'), 'Adjusted whole model'); %! clf; %! plotAdded (m3, [2 3]); %! assert_equal (get (get (gca, 'Title'), 'String'), ... %! 'Added variable plot for specified terms'); %! close (fig); %!test %! ## added variable plot for the weight terms picked as a pair %! load carsmall %! Year = categorical (Model_Year); %! tbl = table (MPG, Weight, Year); %! mdl1 = fitlm (tbl, 'MPG ~ Year + Weight^2'); %! fig = figure ('visible', 'off'); %! h = plotAdded (mdl1, [2 5]); %! assert_equal (get (get (gca, 'Title'), 'String'), 'Added variable plot for specified terms'); %! assert_equal (get (get (gca, 'XLabel'), 'String'), 'Adjusted specified terms'); %! xd = get (h(1), 'XData'); %! yd = get (h(1), 'YData'); %! assert_equal (xd(1:5), ... %! [-2181.48546848357, -2241.34798193195, -2158.28850051435, ... %! -2157.24488312395, -2162.79109548355], 1e-6); %! assert_equal (yd(1:5), ... %! [24.0284299339692, 21.0284299339692, 24.0284299339692, ... %! 22.0284299339692, 23.0284299339692], 1e-7); %! assert_equal (get (h(2), 'DisplayName'), 'Fit: y = 0.0164036*x'); %! close (fig); %!test %! ## multiple predictors delegate to plotAdded %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h1 = plot (ax, mdl); %! h2 = plotAdded (ax, mdl); %! assert_equal (get (h1(1), 'XData'), get (h2(1), 'XData')); %! assert_equal (get (h1(1), 'YData'), get (h2(1), 'YData')); %! assert_equal (get (h1(2), 'YData'), get (h2(2), 'YData')); %! assert_equal (get (h1(3), 'YData'), get (h2(3), 'YData')); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Added variable plot for whole model'); %! close (fig); %!test %! ## no axes argument uses the current axes %! fig = figure ('visible', 'off'); %! h = plot (mdl); %! assert_equal (isequal (get (h(1), 'Parent'), gca ()), true); %! close (fig); %!test %! ## no predictors delegate to plotResiduals %! mc = fitlm (X, y, 'constant'); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h1 = plot (ax, mc); %! h2 = plotResiduals (ax, mc); %! assert_equal (get (h1(1), 'type'), 'patch'); %! assert_equal (get (h1(1), 'YData'), get (h2(1), 'YData')); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Histogram of residuals'); %! close (fig); %!test %! ## single predictor: data, fit line, and confidence bounds %! m1 = fitlm (X(:,1), y); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plot (ax, m1); %! assert_equal (numel (h), 3); %! assert_equal (get (h(1), 'XData'), X(:,1)', 1e-15); %! assert_equal (get (h(1), 'YData'), y', 1e-15); %! xf = get (h(2), 'XData'); %! yf = get (h(2), 'YData'); %! assert_equal (numel (xf), 100); %! assert_equal (xf(1:3), [0.05, 0.0595959595959596, 0.0691919191919192], 1e-12); %! assert_equal (xf(end-2:end), [0.980808080808081, 0.99040404040404, 1], 1e-12); %! assert_equal (yf(1:3), [2.98235017582927, 2.80917102518311, 2.63599187453694], 1e-7); %! assert_equal (yf(end-2:end), [-13.8160274368485, -13.9892065874947, -14.1623857381409], 1e-7); %! yb = get (h(3), 'YData'); %! assert_equal (numel (yb), 201); %! assert_equal (isnan (yb(101)), true); %! assert_equal (yb(1:3), [1.59215527128182, 1.43944293975561, 1.28661387851973], 1e-7); %! assert_equal (yb(102:104), [4.37254508037672, 4.1788991106106, 3.98536987055415], 1e-7); %! assert_equal (yb(end-2:end), [-12.4666494408313, -12.6194785020672, -12.7721908335934], 1e-7); %! assert_equal (yb(1:100) + yb(102:201), 2 * yf, 1e-10); %! [yp, ~] = predict (m1, xf'); %! assert_equal (yf', yp, 1e-10); %! assert_equal (get (get (ax, 'Title'), 'String'), 'y vs. x1'); %! assert_equal (get (get (ax, 'XLabel'), 'String'), 'x1'); %! assert_equal (get (get (ax, 'YLabel'), 'String'), 'y'); %! close (fig); %!test %! ## real dataset with missing rows leaves gaps in the data %! load carsmall %! tbl2 = table (MPG, Weight); %! mdl2 = fitlm (tbl2, 'MPG ~ Weight'); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plot (ax, mdl2); %! xd = get (h(1), 'XData'); %! yd = get (h(1), 'YData'); %! assert_equal (numel (xd), 100); %! assert_equal (any (isnan (xd)), true); %! assert_equal (any (isnan (yd)), true); %! assert_equal (xd(1:3), [3504, 3693, 3436], 1e-10); %! assert_equal (xd(end-2:end), [2295, 2625, 2720], 1e-10); %! assert_equal (yd(1:3), [18, 15, 18], 1e-10); %! assert_equal (yd(end-2:end), [32, 28, 31], 1e-10); %! xf = get (h(2), 'XData'); %! yf = get (h(2), 'YData'); %! assert_equal (numel (xf), 100); %! assert_equal (xf(1:3), [1795, 1824.666667, 1854.333333], 1e-4); %! assert_equal (xf(end-2:end), [4672.666667, 4702.333333, 4732], 1e-4); %! assert_equal (yf(1:3), [33.77920696, 33.52371956, 33.26823216], 1e-6); %! assert_equal (yf(end-2:end), [8.996929296, 8.741441898, 8.485954499], 1e-6); %! yb = get (h(3), 'YData'); %! assert_equal (numel (yb), 201); %! assert_equal (yb(1:3), [32.2768265, 32.0472587, 31.81746955], 1e-6); %! assert_equal (yb(end-2:end), [11.0004001, 10.77351303, 10.54671087], 1e-6); %! assert_equal (get (get (ax, 'Title'), 'String'), 'MPG vs. Weight'); %! assert_equal (get (get (ax, 'XLabel'), 'String'), 'Weight'); %! assert_equal (get (get (ax, 'YLabel'), 'String'), 'MPG'); %! close (fig); %!test %! ## categorical predictor: group codes with per-level fit and bounds %! yv = [4.73087805313537; 7.43361607881479; 4.48799323173627; 5.16869512618961; ... %! 6.50195295169252; 3.73839298415678; 4.3512762614163; 7.45869429457297; ... %! 4.08830081430877; 5.37758996783874; 6.70425002011403; 4.92219481850025; ... %! 5.90846112458998; 6.93808332486038; 3.44469932246968; 4.65954705986638; ... %! 7.00708466319882; 3.97022469477047; 4.66949449276983; 7.15297545753449; ... %! 3.79547107710474; 4.35907258855759; 6.85754858526846; 3.96760657205439; ... %! 5.50019159441948; 6.59933535084439; 4.04590000762589; 5.13690239596885; ... %! 5.93326622029102; 4.20198864027471]; %! grp = categorical (repmat ({'A';'B';'C'}, 10, 1)); %! tbl4 = table (yv, grp, 'VariableNames', {'Response','Group'}); %! mdl4 = fitlm (tbl4, 'Response ~ Group'); %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plot (ax, mdl4); %! assert_equal (numel (h), 3); %! assert_equal (get (h(1), 'XData'), repmat ([1 2 3], 1, 10)); %! assert_equal (get (h(1), 'YData'), yv', 1e-14); %! assert_equal (get (h(2), 'XData'), [1 2 3]); %! assert_equal (get (h(2), 'YData'), [4.98621086647521, 6.85868069471919, 4.0662772163002], 1e-10); %! assert_equal (get (h(3), 'XData'), [1 2 3 NaN 1 2 3]); %! assert_equal (get (h(3), 'YData'), ... %! [4.68577486025387, 6.55824468849784, 3.76584121007885, NaN, ... %! 5.28664687269656, 7.15911670094053, 4.36671322252154], 1e-10); %! assert_equal (get (ax, 'XTick'), [1 2 3]); %! assert_equal (get (ax, 'XTickLabel'), {'A'; 'B'; 'C'}); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Response vs. Group'); %! assert_equal (get (get (ax, 'XLabel'), 'String'), 'Group'); %! assert_equal (get (get (ax, 'YLabel'), 'String'), 'Response'); %! close (fig); %!test %! ## continuous by continuous, effects mode %! mi = fitlm (X, y, 'y ~ x1*x2'); %! fig = figure ('visible', 'off'); %! h = plotInteraction (mi, 'x1', 'x2'); %! assert_equal (numel (h), 11); %! assert_equal (get (h(1), 'XData'), [1.76843380852813, -18.8740348676059], 1e-9); %! assert_equal (get (h(1), 'YData'), [1, 7]); %! assert_equal (get (h(2), 'XData'), [-2.25617028020123, 5.79303789725749], 1e-9); %! assert_equal (get (h(3), 'XData'), [-23.1321080641968, -14.615961671015], 1e-9); %! assert_equal (get (h(4), 'XData'), ... %! [1.97967308209488, 1.68393809910143, 1.38820311610799], 1e-9); %! assert_equal (get (h(4), 'YData'), [2, 3, 4]); %! assert_equal (get (h(5), 'XData'), [-0.771057026434185, 4.73040319062394], 1e-9); %! assert_equal (get (h(6), 'XData'), [-2.86059582662229, 6.22847202482516], 1e-9); %! assert_equal (get (h(7), 'XData'), [-4.99614754441031, 7.77255377662629], 1e-9); %! assert_equal (get (h(8), 'XData'), ... %! [-18.5782998846125, -18.8740348676059, -19.1697698505994], 1e-9); %! assert_equal (get (h(8), 'YData'), [8, 9, 10]); %! assert_equal (get (h(9), 'XData'), [-24.674318784563, -12.4822809846619], 1e-9); %! assert_equal (get (h(10), 'XData'), [-23.1321080641968, -14.615961671015], 1e-9); %! assert_equal (get (h(11), 'XData'), [-21.6439971135684, -16.6955425876303], 1e-9); %! assert_equal (get (h(1), 'Tag'), 'main'); %! assert_equal (get (h(4), 'Tag'), 'conditional1'); %! assert_equal (get (h(8), 'Tag'), 'conditional2'); %! ax = gca (); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Interaction of x1 and x2'); %! assert_equal (get (get (ax, 'XLabel'), 'String'), 'Effect'); %! assert_equal (get (ax, 'YTick'), [1, 2, 3, 4, 7, 8, 9, 10]); %! assert_equal (get (ax, 'YTickLabel'), ... %! {'x1: 0.05 to 1'; 'x2=0.05'; 'x2=10.025'; 'x2=20'; ... %! 'x2: 0.05 to 20'; 'x1=0.05'; 'x1=0.525'; 'x1=1'}); %! assert_equal (get (ax, 'YLim'), [0.5, 10.5]); %! close (fig); %!test %! ## continuous by continuous, predictions mode %! mi = fitlm (X, y, 'y ~ x1*x2'); %! fig = figure ('visible', 'off'); %! h = plotInteraction (mi, 'x1', 'x2', 'predictions'); %! assert_equal (numel (h), 3); %! xd = get (h(1), 'XData'); %! assert_equal (numel (xd), 101); %! assert_equal (xd(1:3), [0.05, 0.2495, 0.449], 1e-9); %! assert_equal (xd(end-2:end), [19.601, 19.8005, 20], 1e-9); %! yd1 = get (h(1), 'YData'); %! assert_equal (yd1(1:3), ... %! [0.215349913094656, 0.0295669142485309, -0.156216084597594], 1e-9); %! assert_equal (yd1(end-2:end), ... %! [-17.9913839738256, -18.1771669726717, -18.3629499715178], 1e-9); %! yd2 = get (h(2), 'YData'); %! assert_equal (yd2(1:3), [1.20518645414209, 1.01644610546604, 0.827705756789976], 1e-9); %! assert_equal (yd2(end-2:end), ... %! [-17.2913677161117, -17.4801080647878, -17.6688484134638], 1e-9); %! yd3 = get (h(3), 'YData'); %! assert_equal (yd3(1:3), [2.19502299518953, 2.00332529668354, 1.81162759817755], 1e-9); %! assert_equal (yd3(end-2:end), ... %! [-16.5913514583978, -16.7830491569038, -16.9747468554098], 1e-9); %! assert_equal (get (h(1), 'DisplayName'), '0.05'); %! assert_equal (get (h(2), 'DisplayName'), '0.525'); %! assert_equal (get (h(3), 'DisplayName'), '1'); %! ax = gca (); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Interaction of x1 and x2'); %! assert_equal (get (get (ax, 'XLabel'), 'String'), 'x2'); %! assert_equal (get (get (ax, 'YLabel'), 'String'), 'Adjusted y'); %! close (fig); %!test %! ## swapping var1/var2 order swaps roles and title %! mi = fitlm (X, y, 'y ~ x1*x2'); %! fig = figure ('visible', 'off'); %! h = plotInteraction (mi, 'x2', 'x1'); %! assert_equal (numel (h), 11); %! assert_equal (get (h(1), 'XData'), [-18.8740348676059, 1.76843380852813], 1e-9); %! assert_equal (get (h(4), 'XData'), ... %! [-18.5782998846125, -18.8740348676059, -19.1697698505994], 1e-9); %! assert_equal (get (h(8), 'XData'), ... %! [1.97967308209488, 1.68393809910143, 1.38820311610799], 1e-9); %! ax = gca (); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Interaction of x2 and x1'); %! assert_equal (get (ax, 'YTickLabel'), ... %! {'x2: 0.05 to 20'; 'x1=0.05'; 'x1=0.525'; 'x1=1'; ... %! 'x1: 0.05 to 1'; 'x2=0.05'; 'x2=10.025'; 'x2=20'}); %! close (fig); %!test %! ## interaction effects: variables given as indices into VariableNames %! mi = fitlm (X, y, 'y ~ x1*x2'); %! fig = figure ('visible', 'off'); %! h = plotInteraction (mi, 1, 2); %! assert_equal (numel (h), 11); %! assert_equal (get (h(1), 'XData'), [1.76843380852813, -18.8740348676059], 1e-9); %! assert_equal (get (h(1), 'YData'), [1, 7]); %! ax = gca (); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Interaction of x1 and x2'); %! close (fig); %!test %! ## interaction effects: explicit axes argument is honored %! mi = fitlm (X, y, 'y ~ x1*x2'); %! fig = figure ('visible', 'off'); %! axtarget = axes (fig); %! h = plotInteraction (axtarget, mi, 'x1', 'x2'); %! assert_equal (numel (h), 11); %! assert_equal (isequal (get (h(1), 'Parent'), axtarget), true); %! assert_equal (isequal (gca (), axtarget), true); %! close (fig); %!test %! ## no interaction term: conditional effects collapse to the main effect %! mn = fitlm (X, y, 'y ~ x1 + x2'); %! fig = figure ('visible', 'off'); %! h = plotInteraction (mn, 'x1', 'x2'); %! xd1 = get (h(1), 'XData'); %! eff1 = xd1(1); %! eff2 = xd1(2); %! assert_equal (eff1, 2.38302891604232, 1e-9); %! assert_equal (eff2, -19.5277648300125, 1e-9); %! assert_equal (get (h(4), 'XData'), [eff1, eff1, eff1], 1e-9); %! assert_equal (get (h(8), 'XData'), [eff2, eff2, eff2], 1e-9); %! close (fig); %!test %! ## categorical by continuous, effects mode %! xc = (1:30)' / 30; %! grp = categorical (repmat ({'A';'B';'C'}, 10, 1)); %! yv = 2*xc + 3*double (grp == 'B') - 1*double (grp == 'C') + ... %! 1.5*xc.*double (grp == 'B') + 0.3*sin ((1:30)'); %! tblc = table (yv, xc, grp, 'VariableNames', {'Response','Xc','Group'}); %! mdlc = fitlm (tblc, 'Response ~ Xc*Group'); %! fig = figure ('visible', 'off'); %! h = plotInteraction (mdlc, 'Group', 'Xc'); %! assert_equal (numel (h), 11); %! assert_equal (get (h(1), 'XData'), [4.7896970899464, 2.32528157787528], 1e-9); %! assert_equal (get (h(2), 'XData'), [4.56862113373247, 5.01077304616034], 1e-9); %! assert_equal (get (h(3), 'XData'), [2.02254960835685, 2.62801354739371], 1e-9); %! assert_equal (get (h(4), 'XData'), ... %! [4.08328517685389, 4.7896970899464, 5.49610900303892], 1e-9); %! assert_equal (get (h(5), 'XData'), [3.64076372661607, 4.52580662709171], 1e-9); %! assert_equal (get (h(6), 'XData'), [4.56862113373247, 5.01077304616034], 1e-9); %! assert_equal (get (h(7), 'XData'), [5.07555715247022, 5.91666085360761], 1e-9); %! assert_equal (get (h(8), 'XData'), ... %! [1.88553612240401, 3.25156621870343, 1.8387423925184], 1e-9); %! assert_equal (get (h(9), 'XData'), [1.36118897012269, 2.40988327468532], 1e-9); %! assert_equal (get (h(10), 'XData'), [2.72721906642211, 3.77591337098474], 1e-9); %! assert_equal (get (h(11), 'XData'), [1.31439524023708, 2.36308954479972], 1e-9); %! ax = gca (); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Interaction of Group and Xc'); %! assert_equal (get (ax, 'YTick'), [1, 2, 3, 4, 7, 8, 9, 10]); %! assert_equal (get (ax, 'YTickLabel'), ... %! {'Group: C to B'; 'Xc=0.0333333'; 'Xc=0.516667'; 'Xc=1'; ... %! 'Xc: 0.033333 to 1'; 'Group=A'; 'Group=B'; 'Group=C'}); %! close (fig); %!test %! ## categorical by continuous, predictions mode %! xc = (1:30)' / 30; %! grp = categorical (repmat ({'A';'B';'C'}, 10, 1)); %! yv = 2*xc + 3*double (grp == 'B') - 1*double (grp == 'C') + ... %! 1.5*xc.*double (grp == 'B') + 0.3*sin ((1:30)'); %! tblc = table (yv, xc, grp, 'VariableNames', {'Response','Xc','Group'}); %! mdlc = fitlm (tblc, 'Response ~ Xc*Group'); %! fig = figure ('visible', 'off'); %! h = plotInteraction (mdlc, 'Group', 'Xc', 'predictions'); %! assert_equal (numel (h), 3); %! xd = get (h(1), 'XData'); %! assert_equal (numel (xd), 101); %! assert_equal (xd(1:3), [0.0333333333333333, 0.043, 0.0526666666666667], 1e-9); %! assert_equal (xd(end-2:end), [0.980666666666667, 0.990333333333333, 1], 1e-9); %! yd1 = get (h(1), 'YData'); %! assert_equal (yd1(1:3), [0.107201421526318, 0.126056782750359, 0.144912143974399], 1e-9); %! assert_equal (yd1(end-2:end), [1.95502682148225, 1.97388218270629, 1.99273754393033], 1e-9); %! yd2 = get (h(2), 'YData'); %! assert_equal (yd2(1:3), [3.18658823804771, 3.21910390023474, 3.25161956242178], 1e-9); %! assert_equal (yd2(end-2:end), [6.37312313237707, 6.4056387945641, 6.43815445675114], 1e-9); %! yd3 = get (h(3), 'YData'); %! assert_equal (yd3(1:3), [-0.896696938806178, -0.878309514880994, -0.85992209095581], 1e-9); %! assert_equal (yd3(end-2:end), [0.905270605861853, 0.923658029787037, 0.942045453712221], 1e-9); %! assert_equal (get (h(1), 'DisplayName'), 'A'); %! assert_equal (get (h(2), 'DisplayName'), 'B'); %! assert_equal (get (h(3), 'DisplayName'), 'C'); %! ax = gca (); %! assert_equal (get (get (ax, 'Title'), 'String'), 'Interaction of Group and Xc'); %! assert_equal (get (get (ax, 'XLabel'), 'String'), 'Xc'); %! assert_equal (get (get (ax, 'YLabel'), 'String'), 'Adjusted Response'); %! close (fig); %!test %! ## default h on shared fixture, no polynomial hierarchy present %! t = anova (mdl); %! assert_equal (t.Properties.RowNames, {'x1'; 'x2'; 'Error'}); %! assert_equal (t.SumSq(1), 0.590865029026992, -1e-9); %! assert_equal (t.DF(1), 1); %! assert_equal (t.MeanSq(1), 0.590865029026992, -1e-9); %! assert_equal (t.F(1), 25.9858409295, -1e-8); %! assert_equal (t.pValue(1), 8.93779416897245e-05, -1e-8); %! assert_equal (t.SumSq(2), 42.0518254818947, -1e-9); %! assert_equal (t.DF(2), 1); %! assert_equal (t.MeanSq(2), 42.0518254818947, -1e-9); %! assert_equal (t.F(2), 1849.41059985747, -1e-7); %! assert_equal (t.pValue(2), 8.65693830575066e-19, -1e-8); %! assert_equal (t.SumSq(3), 0.386545331386823, -1e-9); %! assert_equal (t.DF(3), 17); %! assert_equal (t.MeanSq(3), 0.0227379606698131, -1e-9); %! assert_equal (isnan (t.F(3)), true); %! assert_equal (isnan (t.pValue(3)), true); %!test %! ## explicit Type 2 on shared fixture reaches the identical result as h %! t = anova (mdl, 'components', 2); %! assert_equal (t.SumSq(1), 0.590865029026992, -1e-9); %! assert_equal (t.MeanSq(1), 0.590865029026992, -1e-9); %! assert_equal (t.F(1), 25.9858409295, -1e-8); %! assert_equal (t.pValue(1), 8.93779416897245e-05, -1e-8); %! assert_equal (t.SumSq(2), 42.0518254818947, -1e-9); %! assert_equal (t.MeanSq(2), 42.0518254818947, -1e-9); %! assert_equal (t.F(2), 1849.41059985747, -1e-7); %! assert_equal (t.pValue(2), 8.65693830575066e-19, -1e-8); %! assert_equal (t.SumSq(3), 0.386545331386823, -1e-9); %! assert_equal (t.MeanSq(3), 0.0227379606698131, -1e-9); %!test %! ## explicit Type 1 on shared fixture, order-dependent x1 row differs from h %! t = anova (mdl, 'components', 1); %! assert_equal (t.SumSq(1), 541.472049189064, -1e-9); %! assert_equal (t.MeanSq(1), 541.472049189064, -1e-9); %! assert_equal (t.F(1), 23813.5713686901, -1e-7); %! assert_equal (t.pValue(1), 3.41699381539186e-28, -1e-8); %! assert_equal (t.SumSq(2), 42.0518254818947, -1e-9); %! assert_equal (t.F(2), 1849.41059985747, -1e-7); %! assert_equal (t.SumSq(3), 0.386545331386823, -1e-9); %! assert_equal (t.DF(3), 17); %!test %! ## summary table on shared fixture, no Linear/Nonlinear split %! t = anova (mdl, 'summary'); %! assert_equal (t.Properties.RowNames, {'Total'; 'Model'; 'Residual'}); %! assert_equal (t.SumSq(1), 583.910420002346, -1e-9); %! assert_equal (t.DF(1), 19); %! assert_equal (t.MeanSq(1), 30.7321273685445, -1e-9); %! assert_equal (isnan (t.F(1)), true); %! assert_equal (t.SumSq(2), 583.523874670959, -1e-9); %! assert_equal (t.DF(2), 2); %! assert_equal (t.MeanSq(2), 291.761937335479, -1e-9); %! assert_equal (t.F(2), 12831.4909842738, -1e-7); %! assert_equal (t.pValue(2), 9.48988083209278e-28, -1e-8); %! assert_equal (t.SumSq(3), 0.386545331386823, -1e-9); %! assert_equal (t.DF(3), 17); %! assert_equal (t.MeanSq(3), 0.0227379606698131, -1e-9); %!test %! ## polynomial hierarchy, h and Type 2 diverge only on the Age row %! Age = [25;31;42;29;55;38;46;33;27;50;41;36;48;30;44]; %! Sex = categorical ({'M';'F';'F';'M';'M';'F';'M';'F';'F';'M';'F';'M';'F';'M';'F'}); %! BP = [118;122;135;120;150;128;140;124;119;145;130;126;138;121;136]; %! T = table (Age, Sex, BP); %! m = fitlm (T, 'BP ~ Sex + Age^2'); %! t = anova (m); %! t2 = anova (m, 'components', 2); %! assert_equal (t.Properties.RowNames, {'Age'; 'Sex'; 'Age^2'; 'Error'}); %! assert_equal (t.SumSq(1), 1363.92365918468, -1e-9); %! assert_equal (t.DF(1), 1); %! assert_equal (t.MeanSq(1), 1363.92365918468, -1e-9); %! assert_equal (t.F(1), 707.420098987802, -1e-7); %! assert_equal (t.pValue(1), 2.46488912775244e-11, -1e-8); %! assert_equal (t.SumSq(2), 1.90509548061236, -1e-9); %! assert_equal (t.F(2), 0.988107233422164, -1e-9); %! assert_equal (t.pValue(2), 0.341568882808456, -1e-9); %! assert_equal (t.SumSq(3), 8.58235117456131, -1e-9); %! assert_equal (t.F(3), 4.45136916320193, -1e-8); %! assert_equal (t.pValue(3), 0.0585944334523874, -1e-9); %! assert_equal (t.SumSq(4), 21.2082753550521, -1e-9); %! assert_equal (t.DF(4), 11); %! assert_equal (t.MeanSq(4), 1.92802503227746, -1e-9); %! assert_equal (t2.SumSq(1), 0.209727728295816, -1e-8); %! assert_equal (t2.F(1), 0.108778529731057, -1e-9); %! assert_equal (t2.pValue(1), 0.747734511582268, -1e-9); %! assert_equal (t2.SumSq(2), 1.90509548061236, -1e-9); %! assert_equal (t2.SumSq(3), 8.58235117456131, -1e-9); %! assert_equal (t2.SumSq(4), 21.2082753550521, -1e-9); %!test %! ## summary table with Linear/Nonlinear split, same polynomial model %! Age = [25;31;42;29;55;38;46;33;27;50;41;36;48;30;44]; %! Sex = categorical ({'M';'F';'F';'M';'M';'F';'M';'F';'F';'M';'F';'M';'F';'M';'F'}); %! BP = [118;122;135;120;150;128;140;124;119;145;130;126;138;121;136]; %! T = table (Age, Sex, BP); %! m = fitlm (T, 'BP ~ Sex + Age^2'); %! t = anova (m, 'summary'); %! assert_equal (t.Properties.RowNames, ... %! {'Total'; 'Model'; '. Linear'; '. Nonlinear'; 'Residual'}); %! assert_equal (t.SumSq(1), 1415.73333333333, -1e-9); %! assert_equal (t.DF(1), 14); %! assert_equal (t.MeanSq(1), 101.12380952381, -1e-9); %! assert_equal (t.SumSq(2), 1394.52505797828, -1e-9); %! assert_equal (t.DF(2), 3); %! assert_equal (t.F(2), 241.097329241452, -1e-7); %! assert_equal (t.pValue(2), 2.58928117898032e-10, -1e-8); %! assert_equal (t.SumSq(3), 1385.94270680372, -1e-9); %! assert_equal (t.DF(3), 2); %! assert_equal (t.F(3), 359.420309280577, -1e-7); %! assert_equal (t.pValue(3), 9.54785035758658e-11, -1e-8); %! assert_equal (t.SumSq(4), 8.58235117456131, -1e-9); %! assert_equal (t.DF(4), 1); %! assert_equal (t.F(4), 4.45136916320193, -1e-8); %! assert_equal (t.pValue(4), 0.0585944334523874, -1e-9); %! assert_equal (t.SumSq(5), 21.2082753550521, -1e-9); %! assert_equal (t.DF(5), 11); %! assert_equal (t.MeanSq(5), 1.92802503227746, -1e-9); %!test %! ## unbalanced categorical interaction, hierarchical model, default h %! grpA = categorical ([1;1;2;1;2;1;1;2;1;1;2;1;2;2;1;1;2;1;1;2]); %! grpB = categorical ([1;2;1;1;2;2;1;1;2;1;1;2;2;1;2;1;1;2;2;1]); %! xv = (1:20)' / 10; %! yv = 2*(grpA=='2') + 1.5*(grpB=='2') + 0.7*xv + ... %! 0.9*(grpA=='2').*(grpB=='2') + ... %! [0.1;-0.2;0.05;0.15;-0.1;0.2;-0.05;0.1;0.0;-0.15; ... %! 0.1;0.05;-0.2;0.15;0.0;-0.1;0.05;0.2;-0.05;0.1]; %! T = table (grpA, grpB, xv, yv); %! m = fitlm (T, 'yv ~ grpA*grpB'); %! t = anova (m); %! t2 = anova (m, 'components', 1); %! assert_equal (t.Properties.RowNames, {'grpA'; 'grpB'; 'grpA:grpB'; 'Error'}); %! assert_equal (t.SumSq(1), 26.0224323327616, -1e-9); %! assert_equal (t.F(1), 138.504723473419, -1e-8); %! assert_equal (t.pValue(1), 2.72592668860896e-09, -1e-8); %! assert_equal (t.SumSq(2), 15.2365308176101, -1e-9); %! assert_equal (t.F(2), 81.0966269640544, -1e-8); %! assert_equal (t.pValue(2), 1.15578182042777e-07, -1e-8); %! assert_equal (t.SumSq(3), 0.0142868014375564, -1e-8); %! assert_equal (t.F(3), 0.0760416803903897, -1e-8); %! assert_equal (t.pValue(3), 0.786264832724038, -1e-9); %! assert_equal (t.SumSq(4), 3.00609904761905, -1e-9); %! assert_equal (t.DF(4), 16); %! assert_equal (t.MeanSq(4), 0.18788119047619, -1e-9); %! assert_equal (t2.SumSq(1), 16.3540833333333, -1e-9); %! assert_equal (t2.F(1), 87.0448142886652, -1e-8); %! assert_equal (t2.pValue(1), 7.14746180184104e-08, -1e-8); %! assert_equal (t2.SumSq(2), 15.2365308176101, -1e-9); %! assert_equal (t2.SumSq(3), 0.0142868014375564, -1e-8); %!test %! ## non-hierarchical model, missing grpB main effect, h still succeeds %! grpA = categorical ([1;1;2;1;2;1;1;2;1;1;2;1;2;2;1;1;2;1;1;2]); %! grpB = categorical ([1;2;1;1;2;2;1;1;2;1;1;2;2;1;2;1;1;2;2;1]); %! xv = (1:20)' / 10; %! yv = 2*(grpA=='2') + 1.5*(grpB=='2') + 0.7*xv + ... %! 0.9*(grpA=='2').*(grpB=='2') + ... %! [0.1;-0.2;0.05;0.15;-0.1;0.2;-0.05;0.1;0.0;-0.15; ... %! 0.1;0.05;-0.2;0.15;0.0;-0.1;0.05;0.2;-0.05;0.1]; %! T = table (grpA, grpB, xv, yv); %! m = fitlm (T, 'yv ~ grpA + grpA:grpB'); %! t = anova (m); %! assert_equal (t.Properties.RowNames, {'grpA'; 'grpA:grpB'; 'Error'}); %! assert_equal (t.SumSq(1), 16.3540833333333, -1e-9); %! assert_equal (t.F(1), 22.0110535802411, -1e-8); %! assert_equal (t.pValue(1), 0.000209917579961884, -1e-8); %! assert_equal (t.SumSq(2), 5.62601666666667, -1e-9); %! assert_equal (t.F(2), 7.57208776360619, -1e-8); %! assert_equal (t.pValue(2), 0.0136181560209473, -1e-9); %! assert_equal (t.SumSq(3), 12.6309, -1e-9); %! assert_equal (t.DF(3), 17); %! assert_equal (t.MeanSq(3), 0.742994117647059, -1e-9); %!test %! ## no-intercept model %! grpA = categorical ([1;1;2;1;2;1;1;2;1;1;2;1;2;2;1;1;2;1;1;2]); %! grpB = categorical ([1;2;1;1;2;2;1;1;2;1;1;2;2;1;2;1;1;2;2;1]); %! xv = (1:20)' / 10; %! yv = 2*(grpA=='2') + 1.5*(grpB=='2') + 0.7*xv + ... %! 0.9*(grpA=='2').*(grpB=='2') + ... %! [0.1;-0.2;0.05;0.15;-0.1;0.2;-0.05;0.1;0.0;-0.15; ... %! 0.1;0.05;-0.2;0.15;0.0;-0.1;0.05;0.2;-0.05;0.1]; %! T = table (grpA, grpB, xv, yv); %! m = fitlm (T, 'yv ~ grpA + grpB - 1'); %! t = anova (m); %! assert_equal (t.SumSq(1), 63.3745141509434, -1e-8); %! assert_equal (t.F(1), 178.349190204051, -1e-7); %! assert_equal (t.pValue(1), 3.91187977275845e-12, -1e-8); %! assert_equal (t.SumSq(2), 15.2365308176101, -1e-8); %! assert_equal (t.F(2), 85.7575941763448, -1e-7); %! assert_equal (t.pValue(2), 4.71596005128041e-08, -1e-8); %! assert_equal (t.SumSq(3), 3.02038584905660, -1e-8); %! assert_equal (t.DF(3), 17); %! assert_equal (t.MeanSq(3), 0.177669755826859, -1e-9); %!test %! ## weighted fit %! grpA = categorical ([1;1;2;1;2;1;1;2;1;1;2;1;2;2;1;1;2;1;1;2]); %! grpB = categorical ([1;2;1;1;2;2;1;1;2;1;1;2;2;1;2;1;1;2;2;1]); %! xv = (1:20)' / 10; %! yv = 2*(grpA=='2') + 1.5*(grpB=='2') + 0.7*xv + ... %! 0.9*(grpA=='2').*(grpB=='2') + ... %! [0.1;-0.2;0.05;0.15;-0.1;0.2;-0.05;0.1;0.0;-0.15; ... %! 0.1;0.05;-0.2;0.15;0.0;-0.1;0.05;0.2;-0.05;0.1]; %! T = table (grpA, grpB, xv, yv); %! w = [1.2;0.8;1.5;1.0;0.9;1.1;1.3;0.7;1.0;1.4; ... %! 0.8;1.2;1.0;1.1;0.9;1.3;0.7;1.5;1.0;1.2]; %! m = fitlm (T, 'yv ~ grpA + grpB', 'Weights', w); %! t = anova (m); %! assert_equal (t.SumSq(1), 26.6364861423312, -1e-9); %! assert_equal (t.F(1), 134.770391630163, -1e-8); %! assert_equal (t.pValue(1), 1.66729622333192e-09, -1e-8); %! assert_equal (t.SumSq(2), 17.4880599694593, -1e-9); %! assert_equal (t.F(2), 88.4828681359069, -1e-8); %! assert_equal (t.pValue(2), 3.76674631526712e-08, -1e-8); %! assert_equal (t.SumSq(3), 3.35993877395756, -1e-9); %! assert_equal (t.DF(3), 17); %! assert_equal (t.MeanSq(3), 0.197643457291621, -1e-9); %!test %! grp3 = categorical ([1;1;1;1;1;2;2;2;2;2;2;2;2;2;3;3;3;3;3;3]); %! yy = [2.1;1.9;2.3;2.0;1.8;4.1;4.3;3.9;4.0;4.2; ... %! 4.4;3.8;4.1;4.0;6.1;5.9;6.3;6.0;5.8;6.2]; %! T = table (grp3, yy); %! m = fitlm (T, 'yy ~ grp3'); %! t = anova (m); %! assert_equal (t.Properties.RowNames, {'grp3'; 'Error'}); %! assert_equal (t.DF(1), 2); %! assert_equal (t.SumSq(1), 44.3761111111111, -1e-9); %! assert_equal (t.MeanSq(1), 22.1880555555556, -1e-9); %! assert_equal (t.F(1), 616.446794988197, -1e-7); %! assert_equal (t.pValue(1), 1.36582477075565e-16, -1e-8); %! assert_equal (t.SumSq(2), 0.611888888888889, -1e-9); %! assert_equal (t.DF(2), 17); %! assert_equal (t.MeanSq(2), 0.0359934640522876, -1e-9); %!test %! ## type 3 gives different numbers than type 2 here %! G3 = categorical ([1;1;1;1;2;2;2;3;3;3;3;1;2;3]); %! G2 = categorical ([1;1;2;2;1;2;2;1;1;2;2;2;1;1]); %! y1 = [10;12;15;14;9;11;13;16;18;20;22;17;10;19]; %! T1 = table (G3, G2, y1); %! mdl1 = fitlm (T1, 'y1 ~ G3*G2'); %! t1 = anova (mdl1, 'components', 3); %! assert_equal (t1.Properties.RowNames, {'G3'; 'G2'; 'G3:G2'; 'Error'}); %! assert_equal (t1.SumSq(1), 176.678431372549, -1e-9); %! assert_equal (t1.F(1), 44.6345510835913, -1e-8); %! assert_equal (t1.pValue(1), 4.57572925304662e-05, -1e-8); %! assert_equal (t1.SumSq(2), 38.7604166666666, -1e-9); %! assert_equal (t1.F(2), 19.5842105263158, -1e-8); %! assert_equal (t1.pValue(2), 0.00221050293676794, -1e-9); %! assert_equal (t1.SumSq(3), 1.85490196078431, -1e-9); %! assert_equal (t1.SumSq(4), 15.8333333333333, -1e-9); %! assert_equal (t1.DF(4), 8); %! assert_equal (t1.MeanSq(4), 1.97916666666667, -1e-9); %!test %! ## no intercept; every level is coded, so the error row equals mdl.SSE %! G = categorical ([1;1;1;2;2;2;3;3;3;3]); %! y1 = [10;12;11;20;22;19;15;17;16;14]; %! T = table (G, y1); %! mdl1 = fitlm (T, 'y1 ~ G - 1'); %! t = anova (mdl1, 'components', 3); %! assert_equal (mdl1.SSE, 11.6666666666667, -1e-9); %! assert_equal (t.SumSq(1), 2564.33333333333, -1e-8); %! assert_equal (t.DF(1), 3); %! assert_equal (t.F(1), 512.866666666667, -1e-7); %! assert_equal (t.pValue(1), 1.45297790506045e-08, -1e-8); %! assert_equal (t.SumSq(2), 11.6666666666667, -1e-9); %! assert_equal (t.DF(2), 7); %! assert_equal (t.MeanSq(2), 1.66666666666667, -1e-9); %!test %! ## duplicate group column, so type 3 should show zero DF %! G1 = categorical ([1;1;1;2;2;2;3;3;3;3]); %! G2 = G1; %! y1 = [10;12;11;20;22;19;15;17;16;14]; %! T = table (G1, G2, y1); %! mdl1 = fitlm (T, 'y1 ~ G1 + G2'); %! t = anova (mdl1, 'components', 3); %! assert_equal (t.Properties.RowNames, {'G1'; 'G2'; 'Error'}); %! assert_equal (t.SumSq(1), 0, -1e-9); %! assert_equal (t.DF(1), 0); %! assert_equal (isnan (t.F(1)), true); %! assert_equal (isnan (t.pValue(1)), true); %! assert_equal (t.SumSq(2), 0, -1e-9); %! assert_equal (t.DF(2), 0); %! assert_equal (t.SumSq(3), 11.6666666666667, -1e-9); %! assert_equal (t.DF(3), 7); %! assert_equal (t.MeanSq(3), 1.66666666666667, -1e-9); %!test %! ## just an intercept, so only the error row shows up %! y1 = [5.1;4.9;5.2;4.8;5.1;4.9;5.2;4.8;5.1;4.9]; %! T = table (y1); %! mdl1 = fitlm (T, 'y1 ~ 1'); %! t = anova (mdl1, 'components', 3); %! assert_equal (t.Properties.RowNames, {'Error'}); %! assert_equal (t.SumSq(1), 0.22, -1e-9); %! assert_equal (t.DF(1), 9); %! assert_equal (t.MeanSq(1), 0.0244444444444444, -1e-9); %!test %! ## type 3 with a robust fit %! G = categorical ([1;1;1;1;2;2;2;2;3;3;3;3]); %! x = (1:12)' / 2; %! y1 = 5 + 2*(G=='2') + 4*(G=='3') + 0.3*x; %! y1(10) = y1(10) + 20; %! T = table (G, x, y1); %! mdl1 = fitlm (T, 'y1 ~ G + x', 'RobustOpts', 'on'); %! t = anova (mdl1, 'components', 3); %! assert_equal (t.SumSq(1), 3.35664335664336, -1e-8); %! assert_equal (t.F(1), 0.0801017164653529, -1e-8); %! assert_equal (t.pValue(1), 0.923753304031669, -1e-9); %! assert_equal (t.SumSq(2), 0.337499999999999, -1e-8); %! assert_equal (t.F(2), 0.0161079545454545, -1e-8); %! assert_equal (t.pValue(2), 0.902138027698746, -1e-9); %! assert_equal (t.SumSq(3), 167.619047619048, -1e-7); %! assert_equal (t.DF(3), 8); %! assert_equal (t.MeanSq(3), 20.9523809523809, -1e-8); %!test %! ## repeated x values, so lack of fit rows should show up %! x = [1;1;1;2;3;3;4;5;5;5;6;7]; %! y1 = [10.1;9.9;10.3;12.0;15.2;14.8;17.5;20.1;19.9;20.3;22.0;25.5]; %! mdl1 = fitlm (x, y1); %! t = anova (mdl1, 'summary'); %! assert_equal (t.Properties.RowNames, ... %! {'Total'; 'Model'; 'Residual'; '. Lack of fit'; '. Pure error'}); %! assert_equal (t.SumSq(3), 1.03026642984015, -1e-9); %! assert_equal (t.DF(3), 10); %! assert_equal (t.SumSq(4), 0.790266429840146, -1e-9); %! assert_equal (t.DF(4), 5); %! assert_equal (t.F(4), 3.2927767910006, -1e-9); %! assert_equal (t.pValue(4), 0.108434644423991, -1e-9); %! assert_equal (t.SumSq(5), 0.24, -1e-9); %! assert_equal (t.DF(5), 5); %! assert_equal (t.MeanSq(5), 0.0480000000000001, -1e-9); %!test %! ## same as above but weighted %! x = repmat ((1:4)', 6, 1); %! G = categorical (repmat ([1;1;2;2], 6, 1)); %! y1 = 3 + 0.7*x + 2*(G=='2') + ... %! [0.1;-0.1;0.2;-0.2;0.15;-0.15;0.1;-0.1;-0.05;0.05;0.2;-0.2; ... %! 0.1;-0.1;-0.15;0.15;0.2;-0.2;0.05;-0.05;-0.1;0.1;0.15;-0.15]; %! w = 1 + mod ((0:23)', 3); %! T = table (x, G, y1); %! mdl1 = fitlm (T, 'y1 ~ x + G', 'Weights', w); %! t = anova (mdl1, 'summary'); %! assert_equal (t.Properties.RowNames, ... %! {'Total'; 'Model'; 'Residual'; '. Lack of fit'; '. Pure error'}); %! assert_equal (t.SumSq(3), 0.626927083333335, -1e-9); %! assert_equal (t.DF(3), 21); %! assert_equal (t.SumSq(4), 0.00880208333333332, -1e-9); %! assert_equal (t.DF(4), 1); %! assert_equal (t.F(4), 0.284799460734748, -1e-9); %! assert_equal (t.pValue(4), 0.599454252702211, -1e-9); %! assert_equal (t.SumSq(5), 0.618125000000001, -1e-9); %! assert_equal (t.DF(5), 20); %! assert_equal (t.MeanSq(5), 0.0309062500000001, -1e-9); %!test %! w = (1:n)'; %! mdl0 = fitlm (X, y, 'Weights', w); %! m = step (mdl0, 'Upper', 'quadratic', 'Verbose', 0); %! assert_equal (m.NumObservations, 20); %! assert_equal (m.NumCoefficients, 3); %! assert_equal (m.DFE, 17); %! assert_equal (m.SSE, 4.16523310465, 1e-8); %! assert_equal (m.CoefficientNames, {'(Intercept)','x1','x2'}); %! assert_equal (m.Coefficients.Estimate, [0.080321; 2.6483; -0.98452], 1e-4); %! assert_equal (m.Formula.LinearPredictor, '1 + x1 + x2'); %!test %! mdl0 = fitlm (X, y, 'Exclude', [3 7 15]); %! m = step (mdl0, 'Upper', 'quadratic', 'Verbose', 0); %! assert_equal (m.NumObservations, 17); %! assert_equal (m.NumCoefficients, 3); %! assert_equal (m.DFE, 14); %! assert_equal (m.SSE, 0.340968585251, 1e-8); %! assert_equal (m.CoefficientNames, {'(Intercept)','x1','x2'}); %! assert_equal (m.Coefficients.Estimate, [0.13263; 2.3566; -0.97211], 1e-4); %! assert_equal (m.Formula.LinearPredictor, '1 + x1 + x2'); %!test %! ## 'step' continues the history it inherits rather than beginning a new %! ## one, and reports the model it stepped from as the start. %! s1 = ((1:48)' - 24.5)/12; %! s2 = sin ((1:48)'/5); %! s3 = cos ((1:48)'/7); %! sy = 4 + 2.5*s1 - 1.1*s2 + 0.6*s1.^2 + 0.2*sin ((1:48)'/3); %! m1 = stepwiselm ([s1, s2, s3], sy, 'constant', 'Upper', 'linear', ... %! 'Verbose', 0); %! m2 = step (m1, 'Upper', 'quadratic', 'NSteps', 1, 'Verbose', 0); %! assert_equal (size (m1.Steps.History, 1), 4); %! assert_equal (size (m2.Steps.History, 1), 5); %! assert_equal (m2.Steps.History.TermName, ... %! {'1'; 'x1'; 'x2'; 'x3'; 'x1:x3'}); %! assert_equal (m2.Steps.History.DF, (1:5)'); %! assert_equal (char (m2.Steps.Start), 'y ~ 1 + x1 + x2 + x3'); %!test %! ## Stepping a model that was not fitted stepwise starts the history at %! ## that model, and leaves the thresholds unreported. %! s1 = ((1:48)' - 24.5)/12; %! s2 = sin ((1:48)'/5); %! s3 = cos ((1:48)'/7); %! sy = 4 + 2.5*s1 - 1.1*s2 + 0.6*s1.^2 + 0.2*sin ((1:48)'/3); %! m0 = fitlm ([s1, s2, s3], sy); %! m1 = step (m0, 'Upper', 'linear', 'NSteps', 1, 'Verbose', 0); %! assert_equal (isempty (m0.Steps), true); %! assert_equal (size (m1.Steps.History, 1), 1); %! assert_equal (m1.Steps.History.TermName{1}, '1 + x1 + x2 + x3'); %! assert_equal (m1.Steps.History.DF, 4); %! assert_equal (isempty (m1.Steps.PEnter), true); %! assert_equal (isempty (m1.Steps.PRemove), true); %!test %! ## An unasked-for criterion is inherited from the search being continued, %! ## and the appended step is scored by it. %! s1 = ((1:48)' - 24.5)/12; %! s2 = sin ((1:48)'/5); %! s3 = cos ((1:48)'/7); %! sy = 4 + 2.5*s1 - 1.1*s2 + 0.6*s1.^2 + 0.2*sin ((1:48)'/3); %! m1 = stepwiselm ([s1, s2, s3], sy, 'constant', 'Upper', 'linear', ... %! 'Criterion', 'bic', 'Verbose', 0); %! m2 = step (m1, 'Upper', 'quadratic', 'NSteps', 1, 'Verbose', 0); %! assert_equal (m2.Steps.Criterion, 'bic'); %! assert_equal (m2.Steps.History.Properties.VariableNames, ... %! {'Action', 'TermName', 'Terms', 'DF', 'delDF', 'BIC'}); %! assert_equal (m2.Steps.History.TermName, ... %! {'1'; 'x1'; 'x2'; 'x3'; 'x1:x3'}); %! assert_equal (m2.Steps.History.BIC, ... %! [245.562553617099; 116.628368564602; 109.023785194728; ... %! -6.28640176660815; -83.7225948440224], 1e-9); %!test %! ## Stepping a model that holds a power term is not a 'PredictorVars' %! ## conflict: the factor 'x1^2' names the variable 'x1'. %! s1 = ((1:48)' - 24.5)/12; %! s2 = sin ((1:48)'/5); %! sy = 4 + 2.5*s1 - 1.1*s2 + 0.6*s1.^2 + 0.2*sin ((1:48)'/3); %! m1 = stepwiselm ([s1, s2], sy, 'y ~ x1 + x1^2', 'Upper', 'quadratic', ... %! 'NSteps', 0, 'Verbose', 0); %! m2 = step (m1, 'Upper', 'quadratic', 'NSteps', 1, 'Verbose', 0); %! assert_equal (any (strcmp (m2.CoefficientNames, 'x1^2')), true); %!test %! ## So is the lower bound, which would otherwise silently widen to the %! ## constant model and let a protected term be dropped. %! s1 = ((1:48)' - 24.5)/12; %! s2 = sin ((1:48)'/5); %! s3 = cos ((1:48)'/7); %! sy = 4 + 2.5*s1 - 1.1*s2 + 0.6*s1.^2 + 0.2*sin ((1:48)'/3); %! m1 = stepwiselm ([s1, s2, s3], sy, 'y ~ x1 + x2', 'Lower', 'y ~ x2', ... %! 'Upper', 'quadratic', 'NSteps', 1, 'Verbose', 0); %! m2 = step (m1, 'Upper', 'quadratic', 'NSteps', 1, 'Verbose', 0); %! assert_equal (char (m1.Steps.Lower), 'y ~ 1 + x2'); %! assert_equal (char (m2.Steps.Lower), 'y ~ 1 + x2'); %! assert_equal (m2.Steps.History.TermName, {'1 + x1 + x2'; 'x1^2'; 'x2^2'}); %!test %! mdl0 = fitlm (X, y, 'Intercept', false); %! m = step (mdl0, 'Upper', 'quadratic', 'Verbose', 0); %! assert_equal (m.NumObservations, 20); %! assert_equal (m.NumCoefficients, 2); %! assert_equal (m.DFE, 18); %! assert_equal (m.SSE, 0.410934843408, 1e-8); %! assert_equal (m.Formula.HasIntercept, false); %! assert_equal (m.CoefficientNames, {'x1','x2'}); %! assert_equal (m.Coefficients.Estimate, [2.9614; -0.99725], 1e-4); %! assert_equal (m.Formula.LinearPredictor, 'x1 + x2'); %!test %! load hald %! Xh = ingredients; %! yh = heat; %! mdlh = fitlm (Xh, yh); %! assert_equal (mdlh.Coefficients.Estimate, ... %! [62.405369299918; 1.55110264750845; 0.510167579684912; ... %! 0.101909403579662; -0.144061029071018], 1e-9); %! assert_equal (mdlh.Coefficients.SE, ... %! [70.0709592085362; 0.744769867130993; 0.72378800183518; ... %! 0.754709045051309; 0.70905206344651], 1e-9); %! assert_equal (mdlh.Coefficients.tStat, ... %! [0.890602469336764; 2.0826603169159; 0.704857746178952; ... %! 0.135031379639465; -0.203174120065001], 1e-8); %! assert_equal (mdlh.Coefficients.pValue, ... %! [0.399133563385561; 0.0708216874297252; 0.500901103474289; ... %! 0.895922690510107; 0.844071473291884], 1e-8); %! assert_equal (mdlh.CoefficientNames, {'(Intercept)', 'x1', 'x2', 'x3', 'x4'}); %! assert_equal (mdlh.NumCoefficients, 5); %! assert_equal (mdlh.NumEstimatedCoefficients, 5); %! assert_equal (mdlh.DFE, 8); %! assert_equal (mdlh.SSE, 47.863639350499, 1e-8); %! assert_equal (mdlh.SSR, 2667.89943757258, 1e-6); %! assert_equal (mdlh.SST, 2715.76307692308, 1e-6); %! assert_equal (mdlh.MSE, 5.98295491881254, 1e-9); %! assert_equal (mdlh.RMSE, 2.44600795559061, 1e-9); %! assert_equal (mdlh.Rsquared.Ordinary, 0.98237562040768, 1e-9); %! assert_equal (mdlh.Rsquared.Adjusted, 0.97356343061152, 1e-9); %! assert_equal (mdlh.LogLikelihood, -26.918344895826, 1e-8); %! assert_equal (mdlh.ModelCriterion.AIC, 63.8366897916521, 1e-7); %! assert_equal (mdlh.ModelCriterion.AICc, 72.4081183630806, 1e-7); %! assert_equal (mdlh.ModelCriterion.BIC, 66.6614365789598, 1e-7); %! assert_equal (mdlh.ModelCriterion.CAIC, 71.6614365789598, 1e-7); %! assert_equal (mdlh.Fitted(1:5), ... %! [78.4952395815018; 72.7887993002909; 105.970937532083; ... %! 89.3271002550427; 95.649244438227], 1e-8); %! assert_equal (mdlh.Residuals.Raw(1:5), ... %! [0.00476041849822195; 1.51120069970906; -1.67093753208295; ... %! -1.72710025504266; 0.250755561773033], 1e-8); %! assert_equal (mdlh.Residuals.Pearson(1:5), ... %! [0.00194619910672879; 0.617823297040002; -0.683128412670876; ... %! -0.706089385807266; 0.102516249466771], 1e-8); %! assert_equal (mdlh.Residuals.Studentized(1:5), ... %! [0.00271470565323249; 0.734526653667679; -1.05809320265782; ... %! -0.824036396702643; 0.119767490249399], 1e-8); %! assert_equal (mdlh.Residuals.Standardized(1:5), ... %! [0.00290214088954622; 0.756624558354514; -1.05027405557414; ... %! -0.841081414787206; 0.127905848829164], 1e-8); %! assert_equal (mdlh.Diagnostics.Leverage(1:5), ... %! [0.550284813713987; 0.333242829857405; 0.576942476415795; ... %! 0.29523667959374; 0.357601364034465], 1e-8); %! assert_equal (mdlh.Diagnostics.CooksDistance(1:5), ... %! [2.06118491039641e-06; 0.0572247602223712; 0.300862709270433; ... %! 0.0592697490074745; 0.00182140011900327], 1e-8); %! assert_equal (mdlh.Diagnostics.Dffits(1:5), ... %! [0.00300294746488125; 0.519283016757055; -1.23563576459509; ... %! -0.533347058289184; 0.0893585735072963], 1e-7); %! assert_equal (mdlh.Diagnostics.S2_i(1:5), ... %! [6.83765556564705; 6.34835899957971; 5.89485540180634; ... %! 6.23302709557492; 6.82367982420565], 1e-7); %! assert_equal (mdlh.Diagnostics.CovRatio(1:5), ... %! [4.33530738335252; 2.01725612858557; 2.19476339013102; ... %! 1.74129811023362; 3.00406926806094], 1e-6); %! assert_equal (coefCI (mdlh), ... %! [-99.178552392689 223.989290992525; -0.166339745871082 3.26854504088797; ... %! -1.15889054555817 2.179225704928; -1.63845277518465 1.84227158234397; ... %! -1.77913801945372 1.49101596131168], 1e-7); %! assert_equal (coefCI (mdlh, 0.1), ... %! [-67.8949453842232 192.705683984059; 0.166167302672858 2.93603799234403; ... %! -0.835750978716108 1.85608613808593; -1.30150832005232 1.50532712721164; ... %! -1.46257740216021 1.17445534401817], 1e-7); %! [p, F, r] = coefTest (mdlh); %! assert_equal (p, 4.75618174559791e-07, 1e-12); %! assert_equal (F, 111.479171821258, 1e-6); %! assert_equal (r, 4); %! [dw, pdw] = dwtest (mdlh); %! assert_equal (dw, 0.842123108585363, 1e-9); %! assert_equal (pdw, 2.05259693286049, 1e-8); %!test %! load hald %! Xq = ingredients; %! yq = heat; %! mdlq = fitlm (Xq, yq, 'purequadratic'); %! assert_equal (mdlq.Coefficients.Estimate, ... %! [-210.864812527187; 4.01775196981369; 5.27927179495849; 4.98703005469684; ... %! 1.18967556414545; -0.00475259718542981; -0.0278555063988026; ... %! -0.0885225308739459; 0.0125632231921875], 1e-8); %! assert_equal (mdlq.Coefficients.SE, ... %! [62.2492955088454; 0.709968572684776; 0.928573133899706; 1.15325562045609; ... %! 0.559753134550446; 0.0119258668009245; 0.00570193167405062; ... %! 0.0224063172978396; 0.0040139580330777], 1e-8); %! assert_equal (mdlq.Coefficients.tStat, ... %! [-3.38742488253901; 5.6590560827508; 5.68535918413584; 4.3243058748108; ... %! 2.12535757410437; -0.398511677579811; -4.88527537528597; ... %! -3.95078449069724; 3.12988404180068], 1e-7); %! assert_equal (mdlq.Coefficients.pValue, ... %! [0.0275955163014823; 0.00480588842249351; 0.00472567672152044; ... %! 0.0124052274432915; 0.100732674864514; 0.710609610173011; ... %! 0.00812965117162355; 0.0168069450605605; 0.0351891921636521], 1e-7); %! assert_equal (mdlq.CoefficientNames, ... %! {'(Intercept)', 'x1', 'x2', 'x3', 'x4', 'x1^2', 'x2^2', 'x3^2', 'x4^2'}); %! assert_equal (mdlq.NumCoefficients, 9); %! assert_equal (mdlq.DFE, 4); %! assert_equal (mdlq.Rsquared.Ordinary, 0.998060528051984, 1e-9); %! assert_equal (mdlq.Rsquared.Adjusted, 0.994181584155951, 1e-9); %! assert_equal (mdlq.SSE, 5.26714630515049, 1e-7); %! assert_equal (mdlq.SSR, 2710.49593061793, 1e-6); %! assert_equal (mdlq.SST, 2715.76307692308, 1e-6); %! Xnewq = mean (Xq, 1); %! [ypredq, yciq] = predict (mdlq, Xnewq); %! assert_equal (ypredq, 101.90428629036, 1e-7); %! assert_equal (yciq, [97.996264336243, 105.812308244477], 1e-6); %! [ypredq2, yciq2] = predict (mdlq, Xnewq, 'Alpha', 0.01); %! assert_equal (ypredq2, 101.90428629036, 1e-7); %! assert_equal (yciq2, [95.4237317847484, 108.384840795971], 1e-6); %! yfeq = feval (mdlq, Xnewq(1), Xnewq(2), Xnewq(3), Xnewq(4)); %! assert_equal (yfeq, 101.90428629036, 1e-7); %! ysimq = random (mdlq, Xnewq); %! assert_equal (isscalar (ysimq), true); %! assert_equal (isnumeric (ysimq), true); %! mdlq2 = removeTerms (mdlq, 'x1^2'); %! assert_equal (mdlq2.Coefficients.Estimate, ... %! [180.815670525319; 0.228012666917658; -2.0654020024496; -1.90840414992816; ... %! -0.0108001957000414; 0.0257318558607856; 0.00699693273966028], 1e-8); %! assert_equal (mdlq2.CoefficientNames, ... %! {'(Intercept)', 'x2', 'x3', 'x4', 'x2^2', 'x3^2', 'x4^2'}); %! assert_equal (mdlq2.NumCoefficients, 7); %! mdlq3 = addTerms (mdlq2, 'x1^2'); %! assert_equal (mdlq3.Coefficients.Estimate, mdlq.Coefficients.Estimate, 1e-8); %! assert_equal (mdlq3.CoefficientNames, mdlq.CoefficientNames); %! assert_equal (mdlq3.SSE, 5.26714630515049, 1e-7); %!test %! load hald %! Xr = ingredients; %! yr = heat; %! mdlr = fitlm (Xr, yr, 'RobustOpts', 'bisquare'); %! assert_equal (mdlr.Coefficients.Estimate, ... %! [60.0897358816096; 1.57529551556915; 0.532199192097796; ... %! 0.133455378556458; -0.120521170556001], 1e-8); %! assert_equal (mdlr.Coefficients.SE, ... %! [75.8175597390933; 0.805849306629754; 0.783146694256936; ... %! 0.816603608044244; 0.767202244491812], 1e-8); %! assert_equal (mdlr.Coefficients.tStat, ... %! [0.792556976093573; 1.95482642053437; 0.679565138945976; ... %! 0.163427368238162; -0.157091785668371], 1e-7); %! assert_equal (mdlr.Coefficients.pValue, ... %! [0.450897370203866; 0.0863457969332376; 0.515957116726031; ... %! 0.874235088124976; 0.879064839096153], 1e-7); %! assert_equal (mdlr.CoefficientNames, {'(Intercept)', 'x1', 'x2', 'x3', 'x4'}); %! assert_equal (is_function_handle (mdlr.Robust.RobustWgtFun), true); %! assert_equal (mdlr.Robust.Tune, 4.685, 1e-10); %! assert_equal (size (mdlr.Robust.Weights), [13, 1]); %! assert_equal (isnumeric (mdlr.Robust.Weights), true); %! assert_equal (mdlr.SSE, 56.0362670671825, 1e-6); %! assert_equal (mdlr.MSE, 7.00453338339782, 1e-8); %! assert_equal (mdlr.RMSE, 2.64660790133291, 1e-8); %! assert_equal (mdlr.Rsquared.Ordinary, 0.97929734395902, 1e-9); %! assert_equal (mdlr.Rsquared.Adjusted, 0.96894601593853, 1e-9); %! assert_equal (mdlr.DFE, 8); %! H = [0 1 -1 0 0]; %! [p1, F1, r1] = coefTest (mdlr, H); %! assert_equal (p1, 0.00308748318894346, 1e-11); %! assert_equal (F1, 17.4568343157849, 1e-7); %! assert_equal (r1, 1); %! [pd1, dw1] = dwtest (mdlr, 'exact', 'both'); %! assert_equal (pd1, 0.844119247360191, 1e-9); %! assert_equal (dw1, 2.05387711905232, 1e-8); %! [pd2, dw2] = dwtest (mdlr, 'approximate', 'right'); %! assert_equal (pd2, 0.425180546504485, 1e-9); %! assert_equal (dw2, 2.05387711905232, 1e-8); %! Xnewr = mean (Xr, 1); %! [ypredr, ycir] = predict (mdlr, Xnewr, 'Simultaneous', true); %! assert_equal (ypredr, 95.4263340097424, 1e-7); %! assert_equal (ycir, [92.2744598719279, 98.5782081475569], 1e-6); %! [ypredr2, ycir2] = predict (mdlr, Xnewr, 'Simultaneous', true, 'Alpha', 0.1); %! assert_equal (ypredr2, 95.4263340097424, 1e-7); %! assert_equal (ycir2, [92.7161333049321, 98.1365347145527], 1e-6); %!warning ... %! m = addTerms (mdl, 'x1'); %!warning ... %! m = removeTerms (mdl, 'x1:x2'); %!error fitlm (X, y, 'NotAKey', 1) %!error fitlm (X, y, 'VarNames', {'a','b','c','d'}) %!error fitlm (X, y, [1 2 3 4; 5 6 7 8]) %!error fitlm (X, y, [1 2 1; 0 1 1]) %!error fitlm (NaN (5, 2), NaN (5, 1)) %!error fitlm (NaN (3, 2), [1; 2; 3]) %!error fitlm ([1 2; 3 4; 5 6], NaN (3, 1)) %!error fitlm (X, y, 'Exclude', (1:n)') %!error fitlm () %!error fitlm ('hello', y) %!error fitlm ({'a';'b'}, [1; 2]) %!error fitlm (X) %!error fitlm (X, 'Weights', [1;1;1]) %!error fitlm (X, [1; 2]) %!error fitlm (X, [1 2]) %!error mdl(1) %!error mdl {1} %!error predict (mdl, [0.5 0.25], 'BadOption', 1) %!error predict (mdl, [0.5 0.25], 'Alpha', -0.1) %!error predict (mdl, [0.5 0.25], 'Alpha', 1.5) %!error predict (mdl, [0.5 0.25], 'Alpha', [0.01 0.05]) %!error predict (mdl, [0.5 0.25], 'Prediction', 'bad') %!error predict (mdl, ones (3, 5)) %!error predict (mdl, ones (3, 1)) %!error predict (mdl, table ([1;2], 'VariableNames', {'z'})) %!error random (mdl) %!error random (mdl, [0.5, 0.25], 'extra') %!error random (mdl, ones (3, 5)) %!error random (mdl, []) %!error feval (mdl) %!error feval (mdl, [0.5; 1.0], [0.25; 1.0], [0.1; 0.2]) %!error feval (mdl, ones (3, 1)) %!error feval (mdl, [0.5; 1.0; 0.2], [0.25; 1.0]) %!error feval (mdl, table ([1; 2], 'VariableNames', {'z'})) %!error feval (mdl, []) %!error feval (mdl, '0.5', 0.25) %!error coefCI (mdl, 0.05, 'extra') %!error coefCI (mdl, 1.5) %!error coefCI (mdl, -0.1) %!error coefCI (mdl, [0.01 0.05]) %!error coefCI (mdl, NaN) %!error coefCI (mdl, 'abc') %!error coefTest (mdl, [1 0]) %!error coefTest (mdl, 'abc') %!error coefTest (mdl, [0 1 0], 'abc') %!error coefTest (mdl, [0 1 0; 0 0 1], [1]) %!error coefTest (mdl, [0 NaN 0]) %!error coefTest (mdl, [0 1 0], 0, 'extra') %!error [a, b, c, d] = coefTest (mdl) %!error dwtest (mdl, 'badmethod', 'both') %!error dwtest (mdl, 123, 'both') %!error dwtest (mdl, 'exact', 'both', 'extra') %!error [a, b, c] = dwtest (mdl) %!error addTerms (mdl) %!error addTerms (mdl, 'x1:x2', 'extra') %!error addTerms (mdl, 'z') %!error addTerms (mdl, 'X1') %!error addTerms (mdl, 'x1:z') %!error addTerms (mdl, 'x1*z') %!error addTerms (mdl, [1, 1, 1, 0]) %!error addTerms (mdl, []) %!error addTerms (mdl, {}) %!error removeTerms (mdl) %!error removeTerms (mdl, 'x1', 'extra') %!error removeTerms (mdl, 'z1') %!error removeTerms (mdl, [0 1 0 0]) %!error removeTerms (mdl, []) %!error removeTerms (mdl, {'x1'}) %!error plotResiduals (mdl, 'badtype') %!error plotResiduals (mdl, 'fitted', 'ResidualType', 'bad') %!error plotDiagnostics (mdl, 'badtype') %!error plotDiagnostics (mdl, 'leverage', 'BadProp', 1) %!error plotEffects (mdl, 'extra') %!error plotEffects (mdl, 'a', 'b') %!error plotEffects (fitlm (X(:,1), y, 'constant')) %!error fitlm (X, y, 'RobustOpts', 'notarealfunction') %!error fitlm (X, y, 'RobustOpts', 42) %!error plotAdjustedResponse (mdl) %!error plotAdjustedResponse (mdl, 'z') %!error plotAdjustedResponse (mdl, 3) %!error plotAdjustedResponse (mdl, 99) %!error plotAdjustedResponse (mdl, 1.5) %!error plotAdjustedResponse (mdl, 'x1', 'BadOption', 5) %!error plotAdded (mdl, {'x1', 'x2'}) %!error plotResiduals (mdl, 'fitted', {1}, 5) %!test %! ## a rejected plot property must not leave a figure behind: the properties %! ## are parsed before the axes is taken, gca creating a figure when none is %! ## current %! b = findall (0, 'type', 'figure'); %! try %! plotResiduals (mdl, 'fitted', {1}, 5); %! catch %! end %! assert_equal (isempty (setdiff (findall (0, 'type', 'figure'), b)), true); %!test %! ## a histogram forwards its properties to patch, which takes its own %! fig = figure ('visible', 'off'); %! ax = axes (fig); %! h = plotResiduals (ax, mdl, 'histogram', 'FaceColor', [0 1 0]); %! assert_equal (get (h(1), 'FaceColor'), [0 1 0], 1e-10); %! close (fig); %!error plotAdded (mdl, 99) %!error plotAdded (mdl, 'NotACoef') %!error plotAdded (mdl, 2, 'BadOpt', 5) %!error mdl0 = fitlm (ones (n, 1), y, 'Intercept', false); plotAdded (mdl0) %!error plot (mdl, 'extra') %!error plotInteraction (mdl) %!error plotInteraction (mdl, 'x1') %!error plotInteraction (mdl, 'x1', 'x2', 'badtype') %!error plotInteraction (mdl, 'x1', 'x2', 'effects', 'extra') %!error plotInteraction (mdl, 'z', 'x2') %!error plotInteraction (mdl, 'x1', 'z') %!error plotInteraction (mdl, 99, 'x2') %!error plotInteraction (mdl, 1.5, 'x2') %!error plotInteraction (mdl, 'y', 'x2') %!error plotInteraction (mdl, 'x1', 'y') %!error plotInteraction (mdl, 'x1', 'x1') %!error compact (mdl, 'extra') %!error anova (mdl, 'components', 'h', 'extra') %!error anova (mdl, 'bogus') %!error anova (mdl, 'summary', 2) %!error anova (mdl, 'components', 4) %!error ... %! mdl0 = fitlm (X, y, 'RobustOpts', 'on'); step (mdl0) %!error step (mdl, 'Verbose') ## A factor reaching the model only through an interaction or a power has a ## column of the terms matrix without ever being a coefficient, so anything ## indexed by those columns must be built from their own names. %!shared tf, tv, tr, ttbl %! tf = [1;2;3;4;5;6;7;8;9;10;11;12]; %! tv = [2;1;4;3;6;5;8;7;10;9;12;11]; %! tr = [3.1;4.2;5.3;6.4;7.5;8.6;9.7;10.8;11.9;13.0;14.1;15.2]; %! ttbl = table (tf, tv, tr, 'VariableNames', {'u', 'v', 'resp'}); %!test # a power keeps its exponent in the variable's own column %! m = fitlm (ttbl, 'resp ~ 1 + u*v + u^2 + v^2'); %! assert_equal (char (m.Formula), 'resp ~ 1 + u*v + u^2 + v^2'); %! assert_equal (m.Formula.Terms, ... %! [0 0 0; 1 0 0; 0 1 0; 1 1 0; 2 0 0; 0 2 0]); %! assert_equal (m.Formula.TermNames, ... %! {'(Intercept)'; 'u'; 'v'; 'u:v'; 'u^2'; 'v^2'}); %!test # an interaction survives when one factor is not a main effect %! m = fitlm (ttbl, 'resp ~ 1 + u + u:v'); %! assert_equal (char (m.Formula), 'resp ~ 1 + u + u:v'); %! assert_equal (m.Formula.Terms, [0 0 0; 1 0 0; 1 1 0]); %! assert_equal (m.Formula.TermNames, {'(Intercept)'; 'u'; 'u:v'}); %! assert_equal (m.CoefficientNames, {'(Intercept)', 'u', 'u:v'}); %!test # a power alone still names the variable it belongs to %! m = fitlm (ttbl, 'resp ~ 1 + v^2 + u^2'); %! assert_equal (char (m.Formula), 'resp ~ 1 + u + v + u^2 + v^2'); %! assert_equal (m.Formula.NTerms, 5); %! assert_equal (m.Formula.PredictorNames, {'u', 'v'}); ## Dropping the intercept gives the first categorical predictor every one of ## its levels -- the cell-means parameterisation -- on both the formula and the ## keyword path. MATLAB drops the reference level either way and so cannot fit ## the reference group; see the note in the class documentation. %!shared cf, ch, cg, ct1, ct2 %! cf = [1;2;3;4;5;6;7;8;9;10;11;12]; %! ch = {'b';'c';'a';'b';'c';'a';'b';'c';'a';'b';'c';'a'}; %! cg = {'lo';'hi';'lo';'hi';'lo';'hi';'lo';'hi';'lo';'hi';'lo';'hi'}; %! ct1 = table (cf, ch, [3.1;4.2;5.3;6.4;7.5;8.6;9.7;10.8;11.9;13;14.1;15.2], ... %! 'VariableNames', {'u', 'h2', 'resp'}); %! ct2 = table (ch, cg, [3.1;4.2;5.3;6.4;7.5;8.6;9.7;10.8;11.9;13;14.1;15.2], ... %! 'VariableNames', {'h2', 'g', 'resp'}); %!test # every level is coded, and the estimates are the group means %! m = fitlm (ct1, 'resp ~ h2 - 1'); %! assert_equal (m.CoefficientNames, {'h2_b', 'h2_c', 'h2_a'}); %! assert_equal (m.Coefficients.Estimate', [8.05, 9.15, 10.25], 1e-12); %! assert_equal (m.Rsquared.Ordinary > 0, true); %!test # the keyword path codes it the same way %! m = fitlm (ct1, 'linear', 'Intercept', false); %! assert_equal (m.CoefficientNames, {'u', 'h2_b', 'h2_c', 'h2_a'}); %! assert_equal (m.NumCoefficients, m.NumEstimatedCoefficients); %!test # a second categorical stays reference coded, keeping the design full rank %! for spec = {'resp ~ h2 + g - 1', 'linear'} %! if (strcmp (spec{1}, 'linear')) %! m = fitlm (ct2, spec{1}, 'Intercept', false); %! else %! m = fitlm (ct2, spec{1}); %! endif %! assert_equal (m.CoefficientNames, {'h2_b', 'h2_c', 'h2_a', 'g_hi'}); %! assert_equal (m.NumCoefficients, m.NumEstimatedCoefficients); %! endfor %!test # an intercept still takes the reference level with it %! m = fitlm (ct1, 'resp ~ 1 + h2'); %! assert_equal (m.CoefficientNames, {'(Intercept)', 'h2_c', 'h2_a'}); %! m = fitlm (ct1, 'linear'); %! assert_equal (m.CoefficientNames, {'(Intercept)', 'u', 'h2_c', 'h2_a'}); ## A predictor that groups its observations is coded the same way whatever its ## column type, and VariableInfo reports it as categorical with its levels in ## the order the design codes them. %!shared vu, vg, vb, vk, vr %! vu = [1;2;3;4;5;6;7;8;9;10;11;12]; %! vg = {'lo';'hi';'lo';'hi';'lo';'hi';'lo';'hi';'lo';'hi';'lo';'hi'}; %! vb = logical ([0;1;0;1;0;1;0;1;0;1;0;1]); %! vk = [5;3;5;3;5;3;5;3;5;3;5;3]; %! vr = [3.1;4.2;5.3;6.4;7.5;8.6;9.7;10.8;11.9;13;14.1;15.2]; %!test # a logical column is coded by value, with false as the reference %! t = table (vu, vb, vr, 'VariableNames', {'u', 'b', 'resp'}); %! m = fitlm (t, 'resp ~ 1 + u + b'); %! assert_equal (m.CoefficientNames, {'(Intercept)', 'u', 'b_1'}); %! assert_equal (m.VariableInfo.IsCategorical, [false; true; false]); %! assert_equal (m.VariableInfo.Class, {'double'; 'logical'; 'double'}); %! assert_equal (m.VariableInfo.Range{2}, [false, true]); %! ## and the keyword path codes it identically %! assert_equal (fitlm (t, 'linear').CoefficientNames, ... %! {'(Intercept)', 'u', 'b_1'}); %!test # 'CategoricalVars' is honoured on the formula path too %! t = table (vu, vk, vr, 'VariableNames', {'u', 'k', 'resp'}); %! m = fitlm (t, 'resp ~ 1 + u + k', 'CategoricalVars', {'k'}); %! assert_equal (m.CoefficientNames, {'(Intercept)', 'u', 'k_5'}); %! assert_equal (m.VariableInfo.IsCategorical, [false; true; false]); %! assert_equal (m.VariableInfo.Range{2}, [3, 5]); %! ## without the declaration it stays numeric %! m = fitlm (t, 'resp ~ 1 + u + k'); %! assert_equal (m.CoefficientNames, {'(Intercept)', 'u', 'k'}); %!test # a string column groups like a cellstr one %! t = table (vu, string (vg), vr, 'VariableNames', {'u', 'g', 'resp'}); %! m = fitlm (t, 'resp ~ 1 + u + g'); %! assert_equal (m.CoefficientNames, {'(Intercept)', 'u', 'g_hi'}); %! assert_equal (m.VariableInfo.IsCategorical, [false; true; false]); %! assert_equal (class (m.VariableInfo.Range{2}), 'string'); %!test # Range keeps each variable's own type and the design's level order %! t = table (vu, vg, vr, 'VariableNames', {'u', 'g', 'resp'}); %! m = fitlm (t, 'resp ~ 1 + u + g'); %! assert_equal (m.VariableInfo.Range{2}, {'lo', 'hi'}); %! assert_equal (m.VariableInfo.Range{1}, [1, 12]); %! t = table (vu, categorical (vg), vr, 'VariableNames', {'u', 'g', 'resp'}); %! m = fitlm (t, 'resp ~ 1 + u + g'); %! assert_equal (class (m.VariableInfo.Range{2}), 'categorical'); %! assert_equal (cellstr (m.VariableInfo.Range{2}), {'hi', 'lo'}); %!test %! ## removeTerms names a term on a table model whose formula uses only some %! ## of the table's variables. The model's own terms are only as wide as the %! ## predictors it uses, while the refit runs against the whole table, and the %! ## two spaces have to be reconciled. Measured on R2024a: y ~ 1 + x1. %! x1 = [1; 2; 3; 4; 5; 6; 7; 8]; %! x2 = [2; 1; 4; 3; 6; 5; 8; 7]; %! x3 = [1; 1; 2; 2; 3; 3; 4; 4]; %! x4 = [8; 7; 6; 5; 4; 3; 2; 1]; %! y = 3 * x1 - 2 * x4; %! T = table (x1, x2, x3, x4, y); %! mdl = fitlm (T, "y ~ x1 + x4"); %! assert_equal (removeTerms (mdl, "x4").Formula.LinearPredictor, "1 + x1"); %! assert_equal (removeTerms (mdl, "x1").Formula.LinearPredictor, "1 + x4"); %!test %! ## The intercept goes by name on such a model too. %! x1 = [1; 2; 3; 4; 5; 6; 7; 8]; %! x2 = [2; 1; 4; 3; 6; 5; 8; 7]; %! x4 = [8; 7; 6; 5; 4; 3; 2; 1]; %! y = 3 * x1 - 2 * x4; %! T = table (x1, x2, x4, y); %! mdl = fitlm (T, "y ~ x1 + x4"); %! assert_equal (removeTerms (mdl, "1").Formula.LinearPredictor, "x1 + x4"); %!test %! ## Adding a variable the formula left out and removing it again returns %! ## the model to where it started. %! x1 = [1; 2; 3; 4; 5; 6; 7; 8]; %! x2 = [2; 1; 4; 3; 6; 5; 8; 7]; %! x4 = [8; 7; 6; 5; 4; 3; 2; 1]; %! y = 3 * x1 - 2 * x4; %! T = table (x1, x2, x4, y); %! mdl = fitlm (T, "y ~ x1"); %! grown = addTerms (mdl, "x4"); %! assert_equal (grown.Formula.LinearPredictor, "1 + x1 + x4"); %! assert_equal (removeTerms (grown, "x4").Formula.LinearPredictor, "1 + x1"); %!test %! ## step reaches a variable the current model does not use, which is what %! ## R2024a does: a model at y ~ 1 + x1 gains x4 with no Upper given. %! x1 = [1; 2; 3; 4; 5; 6; 7; 8]; %! x2 = [2; 1; 4; 3; 6; 5; 8; 7]; %! x4 = [8; 7; 6; 5; 4; 3; 2; 1] + [0; 0.3; -0.2; 0.1; 0.4; -0.1; 0.2; 0]; %! y = 3 * x1 - 2 * x4; %! T = table (x1, x2, x4, y); %! mdl = fitlm (T, "y ~ x1"); %! grown = step (mdl, "Verbose", 0); %! assert_equal (any (strcmp (grown.PredictorNames, "x4")), true); statistics-release-1.9.2/inst/Regression/NonLinearModel.m000066400000000000000000001204101524624707500234660ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{mdl} =} NonLinearModel (@dots{}) ## ## Nonlinear regression model class. ## ## A @code{NonLinearModel} object holds a nonlinear regression fitted by ## @code{fitnlm}, together with its coefficients, fit statistics, and methods ## for inference, prediction, and diagnostics. Construct one with ## @code{fitnlm}, which documents the accepted inputs and ## @var{Name}/@var{Value} pairs. ## ## The estimated coefficients and their statistics are in the ## @code{Coefficients} table; @code{Rsquared}, @code{ModelCriterion}, ## @code{LogLikelihood}, @code{RMSE}, @code{SSE}, @code{SST}, and @code{SSR} ## summarize the fit. The methods @code{predict}, @code{feval}, @code{random}, ## @code{coefCI}, @code{coefTest}, @code{plotResiduals}, @code{plotDiagnostics}, ## and @code{plotSlice} operate on the fitted model. ## ## @subheading Fit statistics ## ## The fit statistics follow MATLAB's conventions. @code{SSE} is the residual ## sum of squares, @code{SST} the total sum of squares of the response about its ## (weighted) mean, and @code{SSR} the regression sum of squares of the fitted ## values about that mean; because the model is nonlinear, @code{SST} does ## @emph{not} in general equal @code{SSR + SSE}. @code{Rsquared.Ordinary} is ## @code{1 - @var{SSE} / @var{SST}} and @code{Rsquared.Adjusted} corrects for ## the error degrees of freedom. @code{RMSE} is @code{sqrt (@var{MSE})}, and ## the Gaussian @code{LogLikelihood} uses the maximum-likelihood error variance ## @code{@var{SSE} / n}. The information criteria in @code{ModelCriterion} ## (@code{AIC}, @code{AICc}, @code{BIC}, @code{CAIC}) count the @math{p} ## coefficients as the only parameters -- the error variance is @emph{not} ## counted. @code{coefTest} is a Wald test: for a contrast matrix @var{H} it ## forms @code{(@var{H}*b)' * inv (@var{H}*@var{V}*@var{H}') * (@var{H}*b) / r} ## with @var{V} the coefficient covariance and @math{r} the number of rows of ## @var{H}, referred to an @math{F} distribution on @math{r} and @var{DFE} ## degrees of freedom. The summary printed by @code{disp} instead reports an ## @math{F} statistic versus the zero model, formed from the uncorrected ## regression sum of squares (the sum of the squared fitted values). ## ## @seealso{fitnlm, nlinfit, nlparci, nlpredci, LinearModel, ## GeneralizedLinearModel} ## @end deftypefn classdef NonLinearModel properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} Coefficients ## ## Coefficient estimates and their statistics ## ## A table with one row per coefficient, its row names taken from ## @qcode{CoefficientNames}, and the variables @qcode{Estimate}, ## @qcode{SE}, @qcode{tStat} and @qcode{pValue}. @qcode{tStat} is the ## estimate divided by its standard error and @qcode{pValue} is the ## two-sided @math{t} test of a zero coefficient on @qcode{DFE} degrees ## of freedom. This property is read-only. ## ## @end deftp Coefficients = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} CoefficientNames ## ## Names of the coefficients ## ## A cell array of character vectors with one name per coefficient, in ## the order the model function expects them. The names default to ## @qcode{'b1'}, @qcode{'b2'} and so on, unless @code{fitnlm} was given a ## list of its own. This property is read-only. ## ## @end deftp CoefficientNames = {}; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} CoefficientCovariance ## ## Estimated covariance of the coefficients ## ## A square numeric matrix, one row and column per coefficient, holding ## the estimated covariance of the estimates in @qcode{Coefficients}. ## The square roots of its diagonal are the standard errors reported in ## column @qcode{SE} of that table. This property is read-only. ## ## @end deftp CoefficientCovariance = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} NumCoefficients ## ## Number of coefficients ## ## A positive integer counting the coefficients of the model, which is ## the number of elements of the starting vector handed to ## @code{fitnlm}. This property is read-only. ## ## @end deftp NumCoefficients = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} NumEstimatedCoefficients ## ## Number of estimated coefficients ## ## A positive integer counting the coefficients estimated from the data. ## A nonlinear fit estimates every coefficient it carries, so this equals ## @qcode{NumCoefficients}. This property is read-only. ## ## @end deftp NumEstimatedCoefficients = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} NumPredictors ## ## Number of predictors ## ## A positive integer counting the predictor variables, that is the ## columns of the predictor matrix used for the fit. This property is ## read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} NumObservations ## ## Number of observations ## ## A positive integer counting the observations used for the fit, after ## the rows named by @qcode{'Exclude'} and the rows carrying missing ## values have been dropped. This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} DFE ## ## Error degrees of freedom ## ## A nonnegative integer, @qcode{NumObservations} less ## @qcode{NumCoefficients}. This property is read-only. ## ## @end deftp DFE = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} MSE ## ## Mean squared error ## ## A positive scalar holding the estimated variance of the error term. ## This property is read-only. ## ## @end deftp MSE = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} RMSE ## ## Root mean squared error ## ## A positive scalar, the square root of @qcode{MSE}. This property is ## read-only. ## ## @end deftp RMSE = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} SSE ## ## Error sum of squares ## ## A nonnegative scalar, the sum of the squared residuals weighted by the ## observation weights. This property is read-only. ## ## @end deftp SSE = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} SST ## ## Total sum of squares ## ## A nonnegative scalar, the weighted sum of squared deviations of the ## response about its weighted mean. Because the model is nonlinear, ## @qcode{SST} does not in general equal @qcode{SSR} plus @qcode{SSE}. ## This property is read-only. ## ## @end deftp SST = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} SSR ## ## Regression sum of squares ## ## A nonnegative scalar, the weighted sum of squared deviations of the ## fitted values about the weighted mean of the response. This property ## is read-only. ## ## @end deftp SSR = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} LogLikelihood ## ## Log-likelihood of the fitted model ## ## A scalar, the Gaussian log-likelihood at the estimates, formed with ## the maximum-likelihood error variance @qcode{SSE} divided by ## @qcode{NumObservations}. This property is read-only. ## ## @end deftp LogLikelihood = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} ModelCriterion ## ## Information criteria ## ## A scalar structure with the fields @qcode{AIC}, @qcode{AICc}, ## @qcode{BIC} and @qcode{CAIC}. All four count the coefficients as the ## only parameters; the error variance is not counted. This property is ## read-only. ## ## @end deftp ModelCriterion = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} Rsquared ## ## Coefficient of determination ## ## A scalar structure with the fields @qcode{Ordinary} and ## @qcode{Adjusted}. @qcode{Ordinary} is one less the ratio of ## @qcode{SSE} to @qcode{SST}, and @qcode{Adjusted} corrects that ratio ## for the error degrees of freedom. This property is read-only. ## ## @end deftp Rsquared = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} Residuals ## ## Residuals of the fitted model ## ## A table with one row per observation used for the fit and the ## variables @qcode{Raw}, @qcode{Pearson}, @qcode{Standardized} and ## @qcode{Studentized}. This property is read-only. ## ## @end deftp Residuals = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} Fitted ## ## Fitted response values ## ## A numeric column vector with one fitted value per observation used for ## the fit. This property is read-only. ## ## @end deftp Fitted = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} Robust ## ## Robust fitting options ## ## Empty when the model was fitted by ordinary least squares. When ## @code{fitnlm} was given a robust weight function, a scalar structure ## whose field @qcode{RobustWgtFun} names it. This property is ## read-only. ## ## @end deftp Robust = []; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} Formula ## ## Model formula ## ## A character vector showing the fitted model, built from the model ## function together with the coefficient names and the response name. ## This property is read-only. ## ## @end deftp Formula = ''; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} ResponseName ## ## Name of the response variable ## ## A character vector naming the response. It defaults to @qcode{'y'} ## for a fit from matrices and is the name of the response column for a ## fit from a table. This property is read-only. ## ## @end deftp ResponseName = 'y'; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} PredictorNames ## ## Names of the predictor variables ## ## A cell array of character vectors with one name per predictor. The ## names default to @qcode{'x1'}, @qcode{'x2'} and so on for a fit from ## matrices, and are the column names for a fit from a table. This ## property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {NonLinearModel} {property} VariableNames ## ## Names of all variables ## ## A cell array of character vectors holding @qcode{PredictorNames} ## followed by @qcode{ResponseName}. This property is read-only. ## ## @end deftp VariableNames = {}; endproperties ## Kept out of the documented surface. MATLAB carries the same information ## on its own class and does not expose it, so reading it here is an ## extension of ours. properties (GetAccess = public, SetAccess = protected, Hidden) ## The error model information that @code{nlinfit} returns for the fit. ErrorModelInfo = []; endproperties properties (Access = private, Hidden) modelfun_ = []; # model function handle @(b, X) beta_ = []; # fitted coefficient vector X_ = []; # numeric predictor matrix used for the fit y_ = []; # response vector used for the fit w_ = []; # observation weights (effective) J_ = []; # Jacobian at the solution R_ = []; # raw residuals at the solution leverage_ = []; # hat-matrix diagonal istable_ = false; # true when the model was fit from a table endproperties methods (Hidden) ## Custom display of the object with its variable name. function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ("%s =\n", in_name); endif disp (this); endfunction ## Custom display of the model summary. function disp (this) fprintf ("\n Nonlinear regression model:\n"); if (! isempty (this.Formula)) fprintf (" %s\n", this.Formula); endif if (! isempty (this.Coefficients)) fprintf ("\n Estimated Coefficients:\n\n"); disp (this.Coefficients); endif fprintf ("\n"); if (! isempty (this.NumObservations) && ! isempty (this.DFE)) fprintf (strcat ("Number of observations: %d,", ... " Error degrees of freedom: %d\n"), ... this.NumObservations, this.DFE); endif if (! isempty (this.RMSE)) fprintf ("Root Mean Squared Error: %.3g\n", this.RMSE); endif if (! isempty (this.Rsquared)) fprintf ("R-Squared: %.3g, Adjusted R-Squared %.3g\n", ... this.Rsquared.Ordinary, this.Rsquared.Adjusted); endif if (! isempty (this.beta_) && ! isempty (this.MSE)) ## F versus the zero model uses the uncorrected regression sum of ## squares (a nonlinear model has no guaranteed intercept term). yhat = this.Fitted; Fstat = (sum (yhat .^ 2) / this.NumCoefficients) / this.MSE; pval = 1 - fcdf (Fstat, this.NumCoefficients, this.DFE); fprintf ("F-statistic vs. zero model: %.3g, p-value = %.3g\n", ... Fstat, pval); endif endfunction ## Class specific subscripted reference. function varargout = subsref (this, s) chain_s = s(2:end); s = s(1); switch (s.type) case '()' error (strcat ("NonLinearModel: () indexing is not supported.", ... " Use dot notation for properties.")); case '{}' error (strcat ("NonLinearModel: {} indexing is not supported.", ... " Use dot notation for properties.")); case '.' if (! ischar (s.subs)) error (strcat ("NonLinearModel.subsref: property name must be", ... " a character vector.")); endif if (ismethod (this, s.subs)) [varargout{1:nargout}] = builtin ('subsref', this, [s, chain_s]); return; endif try out = this.(s.subs); catch error (strcat ("NonLinearModel.subsref: unknown property", ... " '%s'."), s.subs); end_try_catch endswitch if (! isempty (chain_s)) out = subsref (out, chain_s); endif varargout{1} = out; endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {NonLinearModel} {@var{mdl} =} NonLinearModel (@var{data}, @var{resp}, @var{modelfun}, @var{beta0}) ## @deftypefnx {NonLinearModel} {@var{mdl} =} NonLinearModel (@dots{}, @var{Name}, @var{Value}) ## ## Fit a nonlinear regression model. Prefer the @code{fitnlm} function, ## which documents the accepted inputs and @var{Name}/@var{Value} pairs. ## ## @end deftypefn function this = NonLinearModel (data, resp, modelfun, beta0, varargin) if (nargin == 0) return; # empty object endif if (nargin < 4) error ("NonLinearModel: DATA, RESP, MODELFUN, and BETA0 are required."); endif if (! is_function_handle (modelfun)) error ("NonLinearModel: MODELFUN must be a function handle."); endif if (! (isnumeric (beta0) && isvector (beta0) && isreal (beta0))) error ("NonLinearModel: BETA0 must be a real numeric vector."); endif opts = nlm_parse_nv (varargin); beta0 = beta0(:); p = numel (beta0); ## ---------------------------------------------------------------- ## ## Intake: resolve the predictor matrix, response, and names. ## ---------------------------------------------------------------- ## if (istable (data)) this.istable_ = true; col_names = data.Properties.VariableNames; if (! isempty (opts.ResponseVar)) resp_name = opts.ResponseVar; else resp_name = col_names{end}; endif if (! isempty (opts.PredictorVars)) pred_names = opts.PredictorVars; else pred_names = col_names(! strcmp (col_names, resp_name)); endif y_full = double (data.(resp_name)(:)); X_full = zeros (numel (y_full), numel (pred_names)); for j = 1:numel (pred_names) X_full(:,j) = double (data.(pred_names{j})(:)); endfor else if (! (isnumeric (data) && isreal (data) && ismatrix (data))) error ("NonLinearModel: X must be a real matrix."); endif if (! (isnumeric (resp) && isreal (resp) && isvector (resp))) error ("NonLinearModel: Y must be a real vector."); endif X_full = double (data); y_full = double (resp(:)); if (rows (X_full) != numel (y_full)) error (strcat ("NonLinearModel: X and Y must have the same", ... " number of observations.")); endif p_raw = columns (X_full); if (! isempty (opts.VarNames)) if (numel (opts.VarNames) != p_raw + 1) error ("NonLinearModel: VarNames must have %d elements.", ... p_raw + 1); endif pred_names = opts.VarNames(1:p_raw)(:)'; resp_name = opts.VarNames{end}; else pred_names = arrayfun (@(k) sprintf ("x%d", k), 1:p_raw, ... 'UniformOutput', false); resp_name = 'y'; endif if (! isempty (opts.ResponseVar)) resp_name = opts.ResponseVar; endif endif ## Exclude and drop missing rows. n_total = numel (y_full); excluded = false (n_total, 1); if (! isempty (opts.Exclude)) ex = opts.Exclude(:); if (islogical (ex)) excluded(1:numel (ex)) = ex; else excluded(ex) = true; endif endif missing = any (isnan (X_full), 2) | isnan (y_full); keep = ! excluded & ! missing; X = X_full(keep,:); y = y_full(keep); n = numel (y); if (n <= p) error (strcat ("NonLinearModel: not enough observations to fit", ... " %d coefficients."), p); endif ## Coefficient names. if (! isempty (opts.CoefficientNames)) if (numel (opts.CoefficientNames) != p) error ("NonLinearModel: CoefficientNames must have %d elements.", p); endif coef_names = opts.CoefficientNames(:)'; else coef_names = arrayfun (@(k) sprintf ("b%d", k), 1:p, ... 'UniformOutput', false); endif ## ---------------------------------------------------------------- ## ## Fit via nlinfit, forwarding the fitting options. ## ---------------------------------------------------------------- ## nvfit = {}; if (! isempty (opts.Weights)) nvfit(end+1:end+2) = {"Weights", opts.Weights(keep)}; endif if (! isempty (opts.ErrorModel)) nvfit(end+1:end+2) = {"ErrorModel", opts.ErrorModel}; endif if (! isempty (opts.RobustWgtFun)) nvfit(end+1:end+2) = {"RobustWgtFun", opts.RobustWgtFun}; endif if (! isempty (opts.Tune)) nvfit(end+1:end+2) = {"Tune", opts.Tune}; endif if (! isempty (opts.Options)) nvfit(end+1:end+2) = {"Options", opts.Options}; endif [beta, R, J, CovB, MSE, EMI] = nlinfit (X, y, modelfun, beta0, nvfit{:}); ## ---------------------------------------------------------------- ## ## Populate the fit statistics. ## ---------------------------------------------------------------- ## dfe = n - p; yhat = y - R; if (isempty (opts.Weights)) w = ones (n, 1); else w = double (opts.Weights(keep)(:)); endif SSE = sum (w .* R .^ 2); ybar = sum (w .* y) / sum (w); SST = sum (w .* (y - ybar) .^ 2); SSR = sum (w .* (yhat - ybar) .^ 2); se = sqrt (diag (CovB)); tstat = beta ./ se; pval = 2 * tcdf (-abs (tstat), dfe); ## Hat-matrix diagonal from the weighted Jacobian. Jw = sqrt (w) .* J; H = Jw * pinv (Jw' * Jw) * Jw'; lev = diag (H); LL = -0.5 * n * (log (2 * pi) + log (SSE / n) + 1); AIC = -2 * LL + 2 * p; AICc = AIC + 2 * p * (p + 1) / max (n - p - 1, 1); BIC = -2 * LL + p * log (n); CAIC = -2 * LL + p * (log (n) + 1); ## Residuals table (raw, Pearson, standardized, studentized). rmse = sqrt (MSE); raw = R; pear = R .* sqrt (w); stnd = R ./ (rmse .* sqrt (max (1 - lev, eps))); s_i = sqrt (max ((dfe * MSE - (R .^ 2 .* w) ./ max (1 - lev, eps)) ... / max (dfe - 1, 1), 0)); stud = R ./ (s_i .* sqrt (max (1 - lev, eps)) + eps); ## Assemble the properties. this.modelfun_ = modelfun; this.beta_ = beta; this.X_ = X; this.y_ = y; this.w_ = w; this.J_ = J; this.R_ = R; this.leverage_ = lev; this.Coefficients = table (beta, se, tstat, pval, ... 'VariableNames', {'Estimate', 'SE', 'tStat', 'pValue'}, ... 'RowNames', coef_names); this.CoefficientNames = coef_names; this.CoefficientCovariance = CovB; this.NumCoefficients = p; this.NumEstimatedCoefficients = p; this.NumPredictors = columns (X); this.NumObservations = n; this.DFE = dfe; this.MSE = MSE; this.RMSE = rmse; this.SSE = SSE; this.SST = SST; this.SSR = SSR; this.LogLikelihood = LL; this.ModelCriterion = struct ('AIC', AIC, 'AICc', AICc, ... 'BIC', BIC, 'CAIC', CAIC); this.Rsquared = struct ('Ordinary', 1 - SSE / SST, ... 'Adjusted', 1 - (SSE / dfe) / (SST / (n - 1))); this.Residuals = table (raw, pear, stnd, stud, 'VariableNames', ... {'Raw', 'Pearson', 'Standardized', 'Studentized'}); this.Fitted = yhat; this.ErrorModelInfo = EMI; this.Robust = ternary (isempty (opts.RobustWgtFun), [], ... struct ('RobustWgtFun', opts.RobustWgtFun)); this.ResponseName = resp_name; this.PredictorNames = pred_names; this.VariableNames = [pred_names, {resp_name}]; this.Formula = build_formula_string (modelfun, coef_names, resp_name); endfunction ## -*- texinfo -*- ## @deftypefn {NonLinearModel} {@var{yhat} =} predict (@var{mdl}, @var{Xnew}) ## @deftypefnx {NonLinearModel} {[@var{yhat}, @var{yci}] =} predict (@var{mdl}, @var{Xnew}) ## @deftypefnx {NonLinearModel} {[@dots{}] =} predict (@dots{}, @var{Name}, @var{Value}) ## ## Predict responses of the nonlinear model @var{mdl} at the new predictor ## values @var{Xnew} (a numeric matrix or a table). With two outputs it ## also returns the confidence intervals @var{yci}. Accepts @qcode{'Alpha'} ## (default 0.05), @qcode{'Prediction'} (@qcode{'curve'} or ## @qcode{'observation'}), and @qcode{'Simultaneous'} (a logical). ## ## @end deftypefn function [yhat, yci] = predict (mdl, Xnew, varargin) alpha = 0.05; predtype = 'curve'; simul = false; for k = 1:2:numel (varargin) switch (lower (varargin{k})) case 'alpha' alpha = varargin{k+1}; case 'prediction' predtype = lower (varargin{k+1}); case 'simultaneous' simul = varargin{k+1}; otherwise error ("NonLinearModel.predict: unknown parameter '%s'.", ... varargin{k}); endswitch endfor Xq = mdl.resolve_predictors (Xnew); yhat = mdl.modelfun_ (mdl.beta_, Xq); yhat = yhat(:); if (nargout > 1) simopt = 'off'; if (simul) simopt = 'on'; endif [~, delta] = nlpredci (mdl.modelfun_, Xq, mdl.beta_, mdl.R_, ... 'Jacobian', mdl.J_, 'MSE', mdl.MSE, ... 'PredOpt', predtype, 'SimOpt', simopt, ... 'Alpha', alpha); yci = [yhat - delta, yhat + delta]; endif endfunction ## -*- texinfo -*- ## @deftypefn {NonLinearModel} {@var{yhat} =} feval (@var{mdl}, @var{X}) ## ## Evaluate the fitted model at the predictor values @var{X}, given either ## as a single matrix/table or as separate column arguments (one per ## predictor). ## ## @end deftypefn function yhat = feval (mdl, varargin) if (numel (varargin) == 1) Xq = mdl.resolve_predictors (varargin{1}); else Xq = cell2mat (cellfun (@(v) v(:), varargin, 'UniformOutput', false)); endif yhat = mdl.modelfun_ (mdl.beta_, Xq); yhat = yhat(:); endfunction ## -*- texinfo -*- ## @deftypefn {NonLinearModel} {@var{ysim} =} random (@var{mdl}, @var{Xnew}) ## ## Simulate responses from the fitted model at @var{Xnew} (default: the ## training predictors), adding Gaussian noise with the model's error ## standard deviation. ## ## @end deftypefn function ysim = random (mdl, Xnew) if (nargin < 2) Xq = mdl.X_; else Xq = mdl.resolve_predictors (Xnew); endif yhat = mdl.modelfun_ (mdl.beta_, Xq); yhat = yhat(:); ysim = yhat + sqrt (mdl.MSE) * randn (numel (yhat), 1); endfunction ## -*- texinfo -*- ## @deftypefn {NonLinearModel} {@var{ci} =} coefCI (@var{mdl}) ## @deftypefnx {NonLinearModel} {@var{ci} =} coefCI (@var{mdl}, @var{alpha}) ## ## Confidence intervals for the coefficients at level ## @math{100 (1 - @var{alpha})%} (default @var{alpha} = 0.05). ## ## @end deftypefn function ci = coefCI (mdl, alpha) if (nargin < 2) alpha = 0.05; endif se = mdl.Coefficients.SE; b = mdl.Coefficients.Estimate; t = tinv (1 - alpha / 2, mdl.DFE); ci = [b - t .* se, b + t .* se]; endfunction ## -*- texinfo -*- ## @deftypefn {NonLinearModel} {[@var{p}, @var{F}, @var{df}] =} coefTest (@var{mdl}) ## @deftypefnx {NonLinearModel} {[@dots{}] =} coefTest (@var{mdl}, @var{H}) ## ## Wald test of a linear hypothesis on the coefficients. With no @var{H} it ## tests that all coefficients are zero (the model versus the zero model) ## and returns the @math{p}-value @var{p}, the @math{F} statistic @var{F}, ## and its numerator degrees of freedom @var{df}. @var{H} is an ## @math{r}-by-@math{p} contrast matrix testing @code{@var{H} * @var{beta} ## = 0}. ## ## @end deftypefn function [p, F, df] = coefTest (mdl, H) b = mdl.beta_; if (nargin < 2) H = eye (numel (b)); endif df = rows (H); M = H * mdl.CoefficientCovariance * H'; F = ((H * b)' * pinv (M) * (H * b)) / df; p = 1 - fcdf (F, df, mdl.DFE); endfunction ## -*- texinfo -*- ## @deftypefn {NonLinearModel} {@var{h} =} plotResiduals (@var{mdl}) ## @deftypefnx {NonLinearModel} {@var{h} =} plotResiduals (@var{mdl}, @var{plottype}) ## ## Plot the model residuals. @var{plottype} is @qcode{'histogram'} ## (default), ## @qcode{'fitted'}, @qcode{'caseorder'}, or @qcode{'probability'}. ## ## @end deftypefn function h = plotResiduals (mdl, plottype, varargin) if (nargin < 2) plottype = 'histogram'; endif r = mdl.Residuals.Raw; switch (lower (plottype)) case 'histogram' h = hist (r); xlabel ("Residuals"); ylabel ("Frequency"); case 'fitted' h = plot (mdl.Fitted, r, 'o'); xlabel ("Fitted values"); ylabel ("Residuals"); hold on; plot (xlim (), [0, 0], 'k:'); hold off; case 'caseorder' h = plot (1:numel (r), r, 'o-'); xlabel ("Case order"); ylabel ("Residuals"); case 'probability' h = plot (sort (r), ((1:numel (r)) - 0.5) / numel (r), 'o'); xlabel ("Residuals"); ylabel ("Probability"); otherwise error ("NonLinearModel.plotResiduals: unknown plot type '%s'.", ... plottype); endswitch title ("Residuals"); endfunction ## -*- texinfo -*- ## @deftypefn {NonLinearModel} {@var{h} =} plotDiagnostics (@var{mdl}) ## @deftypefnx {NonLinearModel} {@var{h} =} plotDiagnostics (@var{mdl}, @var{plottype}) ## ## Plot fit diagnostics. @var{plottype} is @qcode{'leverage'} (default) or ## @qcode{'cookd'} (Cook's distance). ## ## @end deftypefn function h = plotDiagnostics (mdl, plottype) if (nargin < 2) plottype = 'leverage'; endif switch (lower (plottype)) case 'leverage' h = stem (mdl.leverage_); xlabel ("Observation"); ylabel ("Leverage"); case 'cookd' lev = mdl.leverage_; cd = (mdl.R_ .^ 2 ./ (mdl.NumCoefficients * mdl.MSE)) ... .* (lev ./ max (1 - lev, eps) .^ 2); h = stem (cd); xlabel ("Observation"); ylabel ("Cook's distance"); otherwise error ("NonLinearModel.plotDiagnostics: unknown plot type '%s'.", ... plottype); endswitch title ("Diagnostics"); endfunction ## -*- texinfo -*- ## @deftypefn {NonLinearModel} {@var{h} =} plotSlice (@var{mdl}) ## ## Plot the fitted response as each predictor is varied over its observed ## range with the others held at their means. ## ## @end deftypefn function h = plotSlice (mdl) pn = mdl.NumPredictors; xm = mean (mdl.X_, 1); h = zeros (pn, 1); for j = 1:pn subplot (1, pn, j); xj = linspace (min (mdl.X_(:,j)), max (mdl.X_(:,j)), 50)'; Xg = repmat (xm, numel (xj), 1); Xg(:,j) = xj; yg = mdl.modelfun_ (mdl.beta_, Xg); h(j) = plot (xj, yg(:)); xlabel (mdl.PredictorNames{j}); ylabel (mdl.ResponseName); endfor endfunction endmethods methods (Access = private) ## Resolve new predictor values from a matrix or a table to a numeric matrix ## matching the columns used at fit time. function Xq = resolve_predictors (mdl, Xnew) if (istable (Xnew)) Xq = zeros (height (Xnew), numel (mdl.PredictorNames)); for j = 1:numel (mdl.PredictorNames) Xq(:,j) = double (Xnew.(mdl.PredictorNames{j})(:)); endfor else Xq = double (Xnew); endif endfunction endmethods endclassdef ## --------------------------------------------------------------------------- ## Parse the fitnlm/NonLinearModel Name/Value options. function opts = nlm_parse_nv (nv) opts = struct ("CoefficientNames", [], "Weights", [], "ErrorModel", [], ... "RobustWgtFun", [], "Tune", [], "Options", [], ... "PredictorVars", [], "ResponseVar", [], "VarNames", [], ... "Exclude", []); if (mod (numel (nv), 2) != 0) error ("NonLinearModel: Name/Value arguments must come in pairs."); endif for k = 1:2:numel (nv) name = nv{k}; if (! ischar (name)) error ("NonLinearModel: parameter names must be character vectors."); endif switch (lower (name)) case 'coefficientnames' opts.CoefficientNames = nv{k+1}; case 'weights' opts.Weights = nv{k+1}; case 'errormodel' opts.ErrorModel = nv{k+1}; case 'robustwgtfun' opts.RobustWgtFun = nv{k+1}; case 'tune' opts.Tune = nv{k+1}; case 'options' opts.Options = nv{k+1}; case 'predictorvars' opts.PredictorVars = nv{k+1}; case 'responsevar' opts.ResponseVar = nv{k+1}; case 'varnames' opts.VarNames = nv{k+1}; case 'exclude' opts.Exclude = nv{k+1}; otherwise error ("NonLinearModel: unknown parameter name '%s'.", name); endswitch endfor endfunction ## --------------------------------------------------------------------------- ## Build the display formula "resp ~ body" from an anonymous model function, ## substituting b(k) -> coefname and stripping element-wise dots. function s = build_formula_string (modelfun, coef_names, resp_name) fstr = func2str (modelfun); ## Extract the argument names and the body of "@(b, x) body". tok = regexp (fstr, '^@\(([^)]*)\)(.*)$', 'tokens'); if (isempty (tok)) s = sprintf ("%s ~ f(b, X)", resp_name); return; endif args = strtrim (strsplit (tok{1}{1}, ',')); ## Strip whitespace first: func2str may render "b (1)" with a space. body = strrep (tok{1}{2}, " ", ""); bname = args{1}; ## Replace b(k) with the k-th coefficient name (longest index first). for k = numel (coef_names):-1:1 body = strrep (body, sprintf ("%s(%d)", bname, k), coef_names{k}); endfor ## Drop element-wise operator dots for a cleaner display. body = strrep (body, ".*", "*"); body = strrep (body, "./", "/"); body = strrep (body, ".^", "^"); s = sprintf ("%s ~ %s", resp_name, body); endfunction ## --------------------------------------------------------------------------- function out = ternary (cond, a, b) if (cond) out = a; else out = b; endif endfunction %!demo %! ## Fit an exponential growth model y = b1 * exp (b2 * x) and inspect it. %! x = (1:10)'; %! y = [2.1; 2.9; 4.2; 5.3; 7.1; 9.4; 12.8; 16.5; 22.1; 29.8]; %! modelfun = @(b, x) b(1) .* exp (b(2) .* x); %! mdl = fitnlm (x, y, modelfun, [1; 0.3]); %! disp (mdl.Coefficients) %! printf ("RMSE = %g, R^2 = %g\n", mdl.RMSE, mdl.Rsquared.Ordinary); ## Comprehensive property and method coverage %!shared X, y, modelfun, beta0 %! X = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]; %! y = [2.1; 2.9; 4.2; 5.3; 7.1; 9.4; 12.8; 16.5; 22.1; 29.8]; %! modelfun = @(b, x) b(1) .* exp (b(2) .* x); %! beta0 = [1; 0.3]; %!test # coefficient table (estimate, SE, tStat) verified against MATLAB %! mdl = fitnlm (X, y, modelfun, beta0); %! assert_equal (mdl.Coefficients.Estimate, [1.683747025; 0.286911087], 1e-6); %! assert_equal (mdl.Coefficients.SE, [0.035194899; 0.002350913], 1e-6); %! assert_equal (mdl.Coefficients.tStat, [47.8406555; 122.042406], -1e-4); %! assert_equal (mdl.Coefficients.tStat, ... %! mdl.Coefficients.Estimate ./ mdl.Coefficients.SE, 1e-8); %!test # sums of squares and their internal relationships %! mdl = fitnlm (X, y, modelfun, beta0); %! bhat = mdl.Coefficients.Estimate; fit = modelfun (bhat, X); %! assert_equal (mdl.Fitted, fit, 1e-8); %! assert_equal (mdl.SSE, sum ((y - fit) .^ 2), 1e-8); %! assert_equal (mdl.SST, sum ((y - mean (y)) .^ 2), 1e-6); %! assert_equal (mdl.SSR, sum ((fit - mean (y)) .^ 2), 1e-6); %! assert_equal (mdl.SSE, 0.233771954, 1e-7); %! assert_equal (mdl.SST, 750.976, 1e-3); %!test # MSE/RMSE/DFE and the coefficient of determination %! mdl = fitnlm (X, y, modelfun, beta0); %! assert_equal (mdl.DFE, 8); %! assert_equal (mdl.MSE, mdl.SSE / mdl.DFE, 1e-12); %! assert_equal (mdl.RMSE, sqrt (mdl.MSE), 1e-12); %! assert_equal (mdl.RMSE, 0.170942956, 1e-7); %! assert_equal (mdl.Rsquared.Ordinary, 1 - mdl.SSE / mdl.SST, 1e-12); %! assert_equal (mdl.Rsquared.Ordinary, 0.999688709, 1e-8); %! assert_equal (mdl.Rsquared.Adjusted, 0.999649798, 1e-8); %!test # log-likelihood and information criteria (values and identities) %! mdl = fitnlm (X, y, modelfun, beta0); %! ll = mdl.LogLikelihood; k = mdl.NumEstimatedCoefficients; n = 10; %! assert_equal (ll, 4.590586096, 1e-6); %! assert_equal (mdl.ModelCriterion.AIC, -5.181172193, 1e-6); %! assert_equal (mdl.ModelCriterion.BIC, -4.576002007, 1e-6); %! assert_equal (mdl.ModelCriterion.AIC, -2 * ll + 2 * k, 1e-9); %! assert_equal (mdl.ModelCriterion.BIC, -2 * ll + k * log (n), 1e-9); %! assert_equal (mdl.ModelCriterion.AICc, ... %! -2 * ll + 2 * k + 2 * k * (k + 1) / (n - k - 1), 1e-9); %!test # count/size properties and default names %! mdl = fitnlm (X, y, modelfun, beta0); %! assert_equal (mdl.NumCoefficients, 2); %! assert_equal (mdl.NumEstimatedCoefficients, 2); %! assert_equal (mdl.NumPredictors, 1); %! assert_equal (mdl.NumObservations, 10); %! assert_equal (mdl.CoefficientNames, {'b1', 'b2'}); %! assert_equal (mdl.ResponseName, "y"); %!test # the coefficient covariance is symmetric with SE^2 on the diagonal %! mdl = fitnlm (X, y, modelfun, beta0); %! C = mdl.CoefficientCovariance; %! assert_equal (size (C), [2, 2]); %! assert_equal (C, C', 1e-14); %! assert_equal (diag (C), mdl.Coefficients.SE .^ 2, 1e-12); %!test # raw residuals are response minus fit %! mdl = fitnlm (X, y, modelfun, beta0); %! assert_equal (class (mdl.Residuals), "table"); %! assert_equal (mdl.Residuals.Raw, y - mdl.Fitted, 1e-10); %!test # predict returns fitted values (verified against MATLAB) with CIs %! mdl = fitnlm (X, y, modelfun, beta0); %! [yhat, yci] = predict (mdl, [2.5; 5.5; 8.5]); %! assert_equal (yhat, [3.449741842; 8.158274281; 19.293455074], 1e-6); %! assert_equal (yci(:,1), [3.329126146; 7.997938483; 19.121921613], 1e-5); %! assert_equal (yci(:,2), [3.570357538; 8.318610079; 19.464988535], 1e-5); %! assert_equal (all (yci(:,1) <= yhat & yhat <= yci(:,2)), true); %!test # predict at the training data reproduces the fitted response %! mdl = fitnlm (X, y, modelfun, beta0); %! assert_equal (predict (mdl, X), mdl.Fitted, 1e-8); %!test # feval agrees with predict; random draws match the response size %! mdl = fitnlm (X, y, modelfun, beta0); %! assert_equal (feval (mdl, [2.5; 5.5]), predict (mdl, [2.5; 5.5]), 1e-12); %! ysim = random (mdl); %! assert_equal (size (ysim), [10, 1]); %!test # coefCI matches beta +/- t * SE and honours a custom alpha %! mdl = fitnlm (X, y, modelfun, beta0); %! b = mdl.Coefficients.Estimate; se = mdl.Coefficients.SE; %! t95 = tinv (0.975, mdl.DFE); %! assert_equal (coefCI (mdl), [b - t95 * se, b + t95 * se], 1e-12); %! t90 = tinv (0.95, mdl.DFE); %! assert_equal (coefCI (mdl, 0.10), [b - t90 * se, b + t90 * se], 1e-12); %!test # coefTest reports a Wald F statistic versus the zero model %! mdl = fitnlm (X, y, modelfun, beta0); %! [p, F, df] = coefTest (mdl); %! assert_equal (df, 2); %! assert_equal (F > 1e5, true); %! assert_equal (p < 1e-10, true); %!test # table input gives the same fit as matrix input %! tbl = table (X, y, "VariableNames", {'x', 'y'}); %! mdl = fitnlm (tbl, modelfun, beta0); %! assert_equal (mdl.Coefficients.Estimate, [1.683747025; 0.286911087], 1e-6); %! assert_equal (mdl.CoefficientNames, {'b1', 'b2'}); %!test # custom coefficient names are stored and used %! mdl = fitnlm (X, y, modelfun, beta0, "CoefficientNames", {'A', 'k'}); %! assert_equal (mdl.CoefficientNames, {'A', 'k'}); %!test # disp prints the model header and the coefficient table %! mdl = fitnlm (X, y, modelfun, beta0); %! s = evalc ("disp (mdl)"); %! assert_equal (isempty (strfind (s, "Nonlinear regression model")), false); %! assert_equal (isempty (strfind (s, "Estimate")), false); %!test # chained subsref reaches property -> table column -> element %! mdl = fitnlm (X, y, modelfun, beta0); %! assert_equal (numel (mdl.Coefficients.Estimate), 2); %! assert_equal (mdl.Coefficients.Estimate(1), 1.683747025, 1e-6); %!test # the residual and slice plots run without error %! mdl = fitnlm (X, y, modelfun, beta0); %! hf = figure ("visible", "off"); %! unwind_protect %! plotResiduals (mdl); %! plotResiduals (mdl, "fitted"); %! plotDiagnostics (mdl); %! plotSlice (mdl); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error NonLinearModel (1) %!error ... %! NonLinearModel ([1; 2], [1; 2], "bad", [1]) %!error ... %! mdl = fitnlm ([1;2;3;4], [1;2;3;4], @(b, x) b(1) * x, 1); mdl(1); statistics-release-1.9.2/inst/Regression/coxphfit.m000066400000000000000000001074311524624707500224540ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{b} =} coxphfit (@var{X}, @var{T}) ## @deftypefnx {statistics} {@var{b} =} coxphfit (@var{X}, @var{T}, @var{name}, @var{value}, @dots{}) ## @deftypefnx {statistics} {[@var{b}, @var{logl}] =} coxphfit (@dots{}) ## @deftypefnx {statistics} {[@var{b}, @var{logl}, @var{H}] =} coxphfit (@dots{}) ## @deftypefnx {statistics} {[@var{b}, @var{logl}, @var{H}, @var{stats}] =} coxphfit (@dots{}) ## ## Fit a Cox proportional hazards regression model. ## ## @code{@var{b} = coxphfit (@var{X}, @var{T})} returns the @math{p}-by-1 vector ## of coefficients @var{b} of the Cox model ## ## @tex ## $$ h(x_i, t) = h_0(t)\exp\left(\sum_{j=1}^{p} x_{ij} b_j\right) $$ ## @end tex ## @ifnottex ## @math{h(x_i, t) = h_0(t) exp (x_i' b)} ## @end ifnottex ## ## fitted to the @math{n}-by-@math{p} matrix of predictors @var{X} and the ## @math{n}-by-1 vector of event times @var{T}. @var{T} may instead be an ## @math{n}-by-2 matrix whose rows give a @math{(start, stop]} interval of ## exposure, the counting process form, in which an observation joins the risk ## set only after its start time. @math{h_0(t)} is the baseline ## hazard, which is left unspecified: the coefficients are estimated by ## maximizing the Cox partial likelihood, which does not involve it. ## ## @strong{@var{X} must not contain a column of ones.} The model has no ## constant term, since any constant is absorbed into the baseline hazard. A ## constant column is detected, reported by a warning, and given a zero ## coefficient. ## ## Rows of @var{X}, @var{T} or @qcode{"Frequency"} holding @qcode{NaN} are ## removed before fitting. ## ## @code{[@var{b}, @var{logl}, @var{H}, @var{stats}] = coxphfit (@dots{})} ## additionally returns the maximized partial log-likelihood @var{logl}, the ## estimated baseline cumulative hazard @var{H}, and a structure @var{stats} of ## coefficient statistics and residuals. ## ## @var{H} is a two-column matrix whose first column holds the distinct event ## times and whose second holds the estimated cumulative hazard at those times, ## evaluated at the predictor values given by @qcode{"Baseline"}. Its first ## row is the first event time with a cumulative hazard of zero; an observation ## censored before any event contributes no row. In a stratified model ## @var{H} gains a third column carrying the stratum, the blocks appear in ## ascending stratum order, and each block leads with its own zero row at its ## own first event time. A stratum holding no event contributes a single row ## of @qcode{NaN} with its label. ## ## The following @var{name}/@var{value} pairs are accepted: ## ## @multitable @columnfractions 0.18 0.82 ## @headitem Name @tab Value ## @item @qcode{"Baseline"} @tab The @var{X} values at which the baseline ## hazard is computed, either a scalar or a 1-by-@math{p} vector. The default ## is the mean of @var{X} weighted by @qcode{"Frequency"} and taken within each ## stratum, so the hazard is that of an average observation of its stratum; ## pass @qcode{0} for a hazard relative to the origin. A value given ## explicitly is used for every stratum. The coefficients do not depend on ## this choice, only @var{H} does. ## @item @qcode{"Censoring"} @tab A logical or 0/1 vector of length @math{n}, ## where 1 marks an observation right-censored at its recorded time. The ## default is a vector of zeros, so every observation is a recorded event. ## @item @qcode{"Frequency"} @tab A vector of length @math{n} of non-negative ## values giving the number of observations each row represents, or a weight. ## The default is a vector of ones. ## @item @qcode{"Ties"} @tab The method of handling tied event times, either ## @qcode{"breslow"} (default) or @qcode{"efron"}. ## @item @qcode{"B0"} @tab The starting value of the iteration, a vector of ## length @math{p}. The default is @code{0.01 ./ std (@var{X})}. ## @item @qcode{"Options"} @tab A structure of iteration settings, as built by ## @code{statset ("coxphfit")}. The fields used are @qcode{"MaxIter"}, ## @qcode{"TolX"} and @qcode{"Display"}. ## @item @qcode{"Strata"} @tab A vector of length @math{n} of stratum labels. ## Each stratum carries its own baseline hazard and its own risk sets, while ## the coefficients are shared across all of them. A predictor that does not ## vary within any stratum cannot be estimated from a stratified fit; it is ## reported by a warning and held at zero. ## @end multitable ## ## The fields of @var{stats} are: ## ## @multitable @columnfractions 0.24 0.76 ## @headitem Field @tab Contents ## @item @qcode{"covb"} @tab The estimated covariance matrix of @var{b}. ## @item @qcode{"beta"} @tab The coefficients, as returned in @var{b}. ## @item @qcode{"se"} @tab The standard errors of the coefficients. ## @item @qcode{"z"} @tab The @math{z} statistics, @var{b} over its standard ## error. ## @item @qcode{"p"} @tab The two-sided @math{p}-values of the @math{z} ## statistics. ## @item @qcode{"csres"} @tab The Cox-Snell residuals. ## @item @qcode{"devres"} @tab The deviance residuals. ## @item @qcode{"martres"} @tab The martingale residuals. ## @item @qcode{"schres"} @tab The Schoenfeld residuals, @qcode{NaN} for a ## censored observation. The mean an event is measured against follows ## @qcode{"Ties"}: under @qcode{"efron"} a tied death is measured against the ## mean over the sub-risk sets that approximation splits the tie into, so that ## every tied death at one time shares one mean and the residual does not ## depend on the order the tie was recorded in. Without a tie the two methods ## agree. ## @item @qcode{"sschres"} @tab The scaled Schoenfeld residuals. ## @item @qcode{"scores"} @tab The score residuals. ## @item @qcode{"sscores"} @tab The scaled score residuals. ## @item @qcode{"LikelihoodRatioTestP"} @tab The @math{p}-value of the ## likelihood ratio test against the model with no predictors. ## @end multitable ## ## @strong{Two documented deviations, both where R2024a disagrees with ## itself.} The martingale residual is defined as the event indicator minus ## the cumulative hazard the observation actually experienced, so ## @qcode{"csres"} and @qcode{"martres"} must sum to that indicator. They do ## here, always. ## ## Under @qcode{"efron"} ties MATLAB's do not: its @qcode{"martres"} comes from ## a cumulative hazard agreeing neither with its own @qcode{"csres"} nor with ## the @var{H} it returns, and the two sum to 1.0437 and @math{-0.0414} where ## they must give 1 and 0. ## ## In the counting process form MATLAB's @qcode{"martres"} correctly subtracts ## the hazard accrued before the observation entered, but its @qcode{"csres"} ## does not, so the two disagree by exactly that amount for any row whose start ## time follows an event. Here both account for it, so @qcode{"csres"} ## differs from MATLAB's by @math{\Lambda(start) \exp (x'b)} and the identity ## is preserved. ## ## The score residuals inherit the first of those two deviations, being an ## integral against the martingale residual: under @qcode{"efron"} ties they ## differ from MATLAB's, whose own do not sum to the score at the maximum, ## while these sum to zero under both tie methods, weighted by ## @qcode{"Frequency"} where one is given. ## ## Every other output agrees with R2024a to machine precision, across ## censoring, weights, both tie methods, stratification, and the counting ## process form. ## ## @seealso{statset, ecdf, fitlm} ## @end deftypefn function [b, logl, H, stats] = coxphfit (X, T, varargin) if (nargin < 2) print_usage (); endif ## --- X and T ----------------------------------------------------------- if (! (isnumeric (X) && isreal (X) && ismatrix (X) && ! isempty (X))) error ("coxphfit: X must be a real numeric matrix."); endif if (! (isnumeric (T) && isreal (T) && ! isempty (T))) error ("coxphfit: T must be a real numeric vector."); endif ## T is either a vector of event times or, in the counting process form, an ## N-by-2 matrix whose rows give a (start, stop] interval of exposure. if (columns (T) == 2 && rows (T) == rows (X)) Tstart = T(:,1); T = T(:,2); if (any (Tstart >= T)) error (strcat ("coxphfit: each row of T must give a (start, stop]", ... " interval with start strictly less than stop.")); endif elseif (isvector (T)) T = T(:); Tstart = -Inf (numel (T), 1); else error (strcat ("coxphfit: T must be a vector of event times or an", ... " N-by-2 matrix of (start, stop] intervals.")); endif n = numel (T); if (rows (X) != n) error ("coxphfit: T must have one element for each row of X."); endif p = columns (X); ## --- name/value pairs -------------------------------------------------- Baseline = []; Censoring = zeros (n, 1); Frequency = ones (n, 1); Ties = 'breslow'; B0 = []; Options = []; Strata = []; if (mod (numel (varargin), 2) != 0) error ("coxphfit: optional arguments must occur in NAME/VALUE pairs."); endif for i = 1:2:numel (varargin) name = varargin{i}; if (! ischar (name)) error ("coxphfit: parameter name must be a character vector."); endif switch (lower (name)) case 'baseline' Baseline = varargin{i+1}; case 'censoring' Censoring = varargin{i+1}; case 'frequency' Frequency = varargin{i+1}; case 'ties' Ties = varargin{i+1}; case 'b0' B0 = varargin{i+1}; case 'options' Options = varargin{i+1}; case 'strata' Strata = varargin{i+1}; otherwise error ("coxphfit: unknown parameter name '%s'.", name); endswitch endfor if (! (ischar (Ties) && any (strcmpi (Ties, {'breslow', 'efron'})))) error (strcat ("coxphfit: 'Ties' must be either 'breslow' or", ... " 'efron'.")); endif Ties = lower (Ties); if (! (isnumeric (Censoring) || islogical (Censoring)) || numel (Censoring) != n) error (strcat ("coxphfit: 'Censoring' must have one element for each", ... " row of X.")); endif Censoring = logical (Censoring(:)); if (! (isnumeric (Frequency) && isreal (Frequency)) || numel (Frequency) != n) error (strcat ("coxphfit: 'Frequency' must have one element for each", ... " row of X.")); endif Frequency = Frequency(:); if (any (Frequency < 0)) error (strcat ("coxphfit: 'Frequency' must be a vector of", ... " non-negative values.")); endif if (isempty (Strata)) Strata = ones (n, 1); endif if (! (isnumeric (Strata) || islogical (Strata)) || numel (Strata) != n) error (strcat ("coxphfit: 'Strata' must have one element for each row", ... " of X.")); endif Strata = double (Strata(:)); if (! isempty (B0) && (! isnumeric (B0) || numel (B0) != p)) error (strcat ("coxphfit: 'B0' must be a real vector with %d", ... " elements."), p); endif ## Any option the caller leaves out falls back on this function's own ## defaults, which is what statset's merge already means by an empty field. if (isempty (Options)) Options = statset ('coxphfit'); elseif (isstruct (Options)) Options = statset (statset ('coxphfit'), Options); else error ("coxphfit: 'Options' must be a structure."); endif maxiter = statget (Options, 'MaxIter', 100); tolx = statget (Options, 'TolX', 1e-8); display = statget (Options, 'Display', 'off'); ## --- drop incomplete rows ---------------------------------------------- ok = all (isfinite (X), 2) & isfinite (T) & isfinite (Frequency) ... & ! isnan (Strata); if (! all (ok)) X = X(ok,:); T = T(ok); Tstart = Tstart(ok); Censoring = Censoring(ok); Frequency = Frequency(ok); Strata = Strata(ok); n = numel (T); endif if (n == 0) error ("coxphfit: no complete observations remain after removing NaNs."); endif ## A column with no variation carries no information. Globally, that is a ## constant term, which the Cox model has no place for; within a stratified ## model it is a column constant inside every stratum, which the partial ## likelihood never compares across strata and so cannot estimate either. ## Both are held at zero rather than estimated. usc = unique (Strata); const = false (1, p); gconst = false (1, p); for j = 1:p gconst(j) = all (X(:,j) == X(1,j)); cj = true; for si = 1:numel (usc) xs = X(Strata == usc(si), j); if (! all (xs == xs(1))) cj = false; break; endif endfor const(j) = cj; endfor if (any (gconst)) warning ("coxphfit: the Cox model cannot have a constant term in X."); elseif (any (const)) warning (strcat ("coxphfit: a column of X is constant within every", ... " stratum and cannot be estimated.")); endif ## The default baseline is the mean of X weighted by 'Frequency', taken ## within each stratum rather than over the whole sample -- each stratum ## carries its own baseline hazard, so each is centred on its own average ## observation. MathWorks documents it as mean (X) without qualification; ## the weighted, per-stratum mean is what R2024a computes. The two agree ## whenever every frequency is 1 and there is a single stratum, so the ## documentation is partial rather than wrong. baseline_default = isempty (Baseline); if (baseline_default) Baseline = sum (Frequency .* X, 1) / sum (Frequency); endif if (! (isnumeric (Baseline) && isreal (Baseline) && (isscalar (Baseline) || numel (Baseline) == p))) error (strcat ("coxphfit: 'Baseline' must be a scalar or a vector", ... " with %d elements."), p); endif if (isscalar (Baseline)) Baseline = repmat (Baseline, 1, p); endif Baseline = Baseline(:)'; if (isempty (B0)) s = std (X, 0, 1); s(s == 0) = 1; B0 = (0.01 ./ s)'; endif B0 = B0(:); B0(const) = 0; event = ! Censoring; w = Frequency; free = ! const; ## --- Newton-Raphson on the partial likelihood -------------------------- b = B0; for iter = 1:maxiter [logl, g, F] = partial_lik (b, X, T, Tstart, event, w, Strata, Ties); if (all (! free)) break; endif step = zeros (p, 1); step(free) = F(free,free) \ g(free); if (! all (isfinite (step))) warning (strcat ("coxphfit: estimation stopped, the maximum", ... " likelihood estimate may not be finite.")); break; endif b += step; if (strcmp (display, 'iter')) printf ("coxphfit: iteration %d, log-likelihood %g\n", iter, logl); endif if (max (abs (step)) < tolx) break; endif endfor [logl, g, F] = partial_lik (b, X, T, Tstart, event, w, Strata, Ties); b(const) = 0; if (nargout < 3) return; endif ## --- baseline cumulative hazard ---------------------------------------- ## H is computed at X == Baseline, so the linear predictor is centred there; ## this scales the hazard by exp (Baseline * b) and leaves b untouched. eta = (X - Baseline) * b; ex = w .* exp (eta); ## The hazard uses the Breslow estimator for both tie methods. 'Ties' enters ## only through the partial likelihood, and so through b; MATLAB does the ## same, which is why its Efron and Breslow hazards differ by the fit alone. ## Strata are emitted in ascending label order, each block leading with a ## zero row at its own first event time -- not at its earliest observation, ## since one censored before any event contributes no row. A stratum holding ## no event at all contributes a single row of NaN. us = unique (Strata); H = []; for si = 1:numel (us) ins = Strata == us(si); ut = unique (T(event & ins)); if (isempty (ut)) blk = [NaN, NaN]; else if (baseline_default) bs = sum (w(ins) .* X(ins,:), 1) / sum (w(ins)); else bs = Baseline; endif exs = w .* exp ((X - bs) * b); h = zeros (numel (ut), 1); for k = 1:numel (ut) D = event & ins & T == ut(k); R = ins & Tstart < ut(k) & T >= ut(k); h(k) = sum (w(D)) / sum (exs(R)); endfor blk = [ut(1), 0; ut, cumsum(h)]; endif if (numel (us) > 1) blk = [blk, repmat(us(si), rows (blk), 1)]; endif H = [H; blk]; endfor if (nargout < 4) return; endif ## --- coefficient statistics -------------------------------------------- covb = zeros (p); covb(free,free) = inv (F(free,free)); se = sqrt (diag (covb)); z = b ./ se; pval = 2 * normcdf (-abs (z)); ## Likelihood ratio against the model with no predictors. logl0 = partial_lik (zeros (p, 1), X, T, Tstart, event, w, Strata, Ties); df = sum (free); lrtp = 1 - chi2cdf (2 * (logl - logl0), df); ## --- residuals ---------------------------------------------------------- ## Each stratum has its own baseline hazard, so every risk set below is ## restricted to the stratum of the observation it belongs to. exu = w .* exp (X * b); us = unique (Strata); Hi = zeros (n, 1); xbar = NaN (n, p); # risk-set mean at each observation's time sh = cell (numel (us), 1); # per-stratum event times, hazards and means for si = 1:numel (us) ins = Strata == us(si); ut = unique (T(event & ins)); h0 = zeros (numel (ut), 1); xb = zeros (numel (ut), p); # Breslow mean, paired with h0 xbev = zeros (numel (ut), p); # mean an event at that time is measured against for k = 1:numel (ut) D = event & ins & T == ut(k); R = ins & Tstart < ut(k) & T >= ut(k); SR = sum (exu(R)); AR = exu(R)' * X(R,:); h0(k) = sum (w(D)) / SR; xb(k,:) = AR / SR; ## The mean an event is measured against follows the tie method the fit ## used. Efron's approximation splits a tie of d deaths into d terms, ## the l-th removing a fraction l/d of the tied deaths from the risk ## set, so the mean is averaged over those d sub-risk sets. Without a ## tie the two coincide, the sum having only its l = 0 term. ## ## Only the event term takes it. The hazard term below pairs with h0, ## which is the Breslow estimator under either method, and must use the ## Breslow mean to match: it is that pairing, and only that pairing, ## which leaves the score residuals summing to the score at the maximum, ## namely zero. dk = sum (D); if (strcmp (Ties, 'efron') && dk > 1) SD = sum (exu(D)); AD = exu(D)' * X(D,:); acc = zeros (1, p); for l = 0:dk-1 acc += (AR - (l / dk) * AD) / (SR - (l / dk) * SD); endfor xbev(k,:) = acc / dk; else xbev(k,:) = xb(k,:); endif endfor sh{si} = {ut, h0, cumsum(h0), xb}; for i = find (ins)' j = find (ut <= T(i), 1, 'last'); if (! isempty (j)) Hi(i) = sh{si}{3}(j); endif ## In the counting process form the observation is only exposed over ## (start, stop], so the hazard accrued before it entered is not its own. j0 = find (ut <= Tstart(i), 1, 'last'); if (! isempty (j0)) Hi(i) -= sh{si}{3}(j0); endif j = find (ut == T(i), 1); if (! isempty (j)) xbar(i,:) = xbev(j,:); endif endfor endfor martres = double (event) - Hi .* exp (X * b); csres = double (event) - martres; ## Deviance residuals: the martingale residual symmetrized about zero. devres = zeros (n, 1); for i = 1:n m = martres(i); e = double (event(i)); if (e - m <= 0) devres(i) = sign (m) * sqrt (-2 * m); else devres(i) = sign (m) * sqrt (-2 * (m + e * log (e - m))); endif endfor ## Schoenfeld residuals: the predictor minus the risk-set weighted mean at ## the event time. A censored observation contributes none. schres = NaN (n, p); schres(event,:) = X(event,:) - xbar(event,:); nev = sum (event); sschres = repmat (b', n, 1) + nev * (schres * covb); ## Score residuals: the per-observation contribution to the score. scores = zeros (n, p); for i = 1:n si = find (us == Strata(i), 1); ut = sh{si}{1}; h0 = sh{si}{2}; xb = sh{si}{4}; acc = zeros (1, p); if (event(i)) acc += X(i,:) - xbar(i,:); endif for k = find (ut <= T(i) & ut > Tstart(i))' acc -= (X(i,:) - xb(k,:)) * exp (X(i,:) * b) * h0(k); endfor scores(i,:) = acc; endfor sscores = scores * covb; stats = struct ('covb', covb, 'beta', b, 'se', se, 'z', z, 'p', pval, ... 'csres', csres, 'devres', devres, 'martres', martres, ... 'schres', schres, 'sschres', sschres, 'scores', scores, ... 'sscores', sscores, 'LikelihoodRatioTestP', lrtp); endfunction ## Cox partial log-likelihood, its gradient, and the observed information. function [l, g, F] = partial_lik (b, X, T, Tstart, event, w, Strata, ties) p = columns (X); eta = X * b; ex = w .* exp (eta); l = 0; g = zeros (p, 1); F = zeros (p); us = unique (Strata); ## Each stratum carries its own baseline hazard, so the partial likelihood is ## the sum of the within-stratum likelihoods and the risk sets never cross. for si = 1:numel (us) ins = Strata == us(si); ut = unique (T(event & ins)); for k = 1:numel (ut) tk = ut(k); D = find (event & ins & T == tk); R = find (ins & Tstart < tk & T >= tk); wD = w(D); WD = sum (wD); l += sum (wD .* eta(D)); g += X(D,:)' * wD; XR = X(R,:); exR = ex(R); if (strcmp (ties, 'breslow')) S0 = sum (exR); S1 = XR' * exR; S2 = XR' * (XR .* exR); l -= WD * log (S0); g -= WD * (S1 / S0); F += WD * (S2 / S0 - (S1 / S0) * (S1 / S0)'); else m = numel (D); XD = X(D,:); exD = ex(D); S0 = sum (exR); S0d = sum (exD); S1 = XR' * exR; S1d = XD' * exD; S2 = XR' * (XR .* exR); S2d = XD' * (XD .* exD); for r = 0:(m-1) f = r / m; A0 = S0 - f * S0d; A1 = S1 - f * S1d; A2 = S2 - f * S2d; l -= (WD / m) * log (A0); g -= (WD / m) * (A1 / A0); F += (WD / m) * (A2 / A0 - (A1 / A0) * (A1 / A0)'); endfor endif endfor endfor endfunction %!demo %! ## Fit a Cox model to right-censored survival data %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! censored = [0; 0; 1; 0; 0; 1; 0; 0; 1; 0]; %! [b, logl] = coxphfit (X, T, 'Censoring', censored) %!demo %! ## Compare the two methods of handling tied event times %! T = [4; 4; 6; 6; 8; 8; 11; 11; 13; 13]; %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! breslow = coxphfit (X, T, 'Ties', 'breslow'); %! efron = coxphfit (X, T, 'Ties', 'efron'); %! [breslow, efron] %!shared X, T, C, Tt, F %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! C = [0; 0; 1; 0; 0; 1; 0; 0; 1; 0]; %! Tt = [4; 4; 6; 6; 8; 8; 11; 11; 13; 13]; %! F = [1; 2; 1; 1; 3; 1; 1; 2; 1; 1]; ## Coefficients and log-likelihood, against R2024a %!test %! [b, logl] = coxphfit (X, T); %! assert_equal (b, [-1.3886093196382836; 4.3814437183613322], 1e-8); %! assert_equal (logl, -8.8069639381632356, 1e-10); %!test %! [b, logl] = coxphfit (X, T, 'Censoring', C); %! assert_equal (b, [-1.0422777707095543; 3.374484233216088], 1e-8); %! assert_equal (logl, -7.6973587461887778, 1e-10); %!test %! [b, logl] = coxphfit (X, Tt, 'Censoring', C, 'Ties', 'breslow'); %! assert_equal (b, [-0.71807146728479765; 3.0751600100470267], 1e-8); %! assert_equal (logl, -9.9446106821057487, 1e-10); %!test %! [b, logl] = coxphfit (X, Tt, 'Censoring', C, 'Ties', 'efron'); %! assert_equal (b, [-0.78425008234710136; 3.3784787697856826], 1e-8); %! assert_equal (logl, -9.2459071792218612, 1e-10); %!test %! [b, logl] = coxphfit (X, T, 'Censoring', C, 'Frequency', F); %! assert_equal (b, [-1.1439301196172516; 3.7057550554856507], 1e-8); %! assert_equal (logl, -15.957308443384392, 1e-10); %!test %! b = coxphfit (X(:,1), T, 'Censoring', C); %! assert_equal (b, -0.28958528273410233, 1e-8); ## Breslow is the default method for ties %!test %! assert_equal (coxphfit (X, Tt, 'Censoring', C), ... %! coxphfit (X, Tt, 'Censoring', C, 'Ties', 'breslow')); ## The coefficients do not depend on Baseline or on the starting value %!test %! b = coxphfit (X, T, 'Censoring', C, 'Baseline', 0); %! assert_equal (b, [-1.0422777707095543; 3.374484233216088], 1e-8); %!test %! b = coxphfit (X, T, 'Censoring', C, 'B0', [0.1; -0.1]); %! assert_equal (b, [-1.0422777707095543; 3.374484233216088], 1e-8); ## The baseline cumulative hazard %!test %! [b, logl, H] = coxphfit (X, T, 'Censoring', C); %! assert_equal (size (H), [8, 2]); %! assert_equal (H(1,:), [4, 0]); %! assert_equal (H(:,1)', [4, 4, 6, 11, 13, 18, 21, 30]); %! assert_equal (H(end,2), 16.21202553, 1e-6); ## The leading row is the first event time, not the earliest observation %!test %! Cc = [1; 0; 1; 0; 0; 1; 0; 0; 1; 0]; %! [b, logl, H] = coxphfit (X, T, 'Censoring', Cc); %! assert_equal (b, [-0.94457242311897405; 3.4887572101921012], 1e-8); %! assert_equal (logl, -6.4545533447841414, 1e-10); %! assert_equal (size (H), [7, 2]); %! assert_equal (H(1,:), [6, 0]); %! assert_equal (H(:,1)', [6, 6, 11, 13, 18, 21, 30]); ## Baseline scales the hazard by exp (Baseline * b) and nothing else %!test %! [~, ~, Hm] = coxphfit (X, T, 'Censoring', C); %! [b, ~, H0] = coxphfit (X, T, 'Censoring', C, 'Baseline', 0); %! assert_equal (Hm(2:end,2) ./ H0(2:end,2), ... %! repmat (exp (mean (X) * b), 7, 1), 1e-10); ## Coefficient statistics %!test %! [b, logl, H, stats] = coxphfit (X, T); %! assert_equal (stats.beta, b); %! assert_equal (stats.se, [0.52737766743917369; 1.8537400210498443], 1e-8); %! assert_equal (stats.z, [-2.6330453589001133; 2.3635696853973904], 1e-8); %! assert_equal (stats.p, [0.0084623045168834322; 0.018099822319697333], 1e-10); %!test %! [~, ~, ~, stats] = coxphfit (X, T); %! assert_equal (stats.covb, [0.27812720411358371, -0.88932589928247008; ... %! -0.88932589928247008, 3.4363520656418767], 1e-8); %!test %! [~, ~, ~, stats] = coxphfit (X, T); %! assert_equal (stats.LikelihoodRatioTestP, 0.0018409958426933715, 1e-10); ## The stats fields, in MATLAB's own order %!test %! [~, ~, ~, stats] = coxphfit (X, T); %! assert_equal (fieldnames (stats)', {'covb', 'beta', 'se', 'z', 'p', ... %! 'csres', 'devres', 'martres', 'schres', 'sschres', 'scores', 'sscores', ... %! 'LikelihoodRatioTestP'}); ## Residuals %!test %! [~, ~, ~, stats] = coxphfit (X, T); %! assert_equal (stats.martres(1), 0.6260391348376092, 1e-8); %! assert_equal (stats.csres(1), 0.3739608651623908, 1e-8); ## The Cox-Snell and martingale residuals partition the event indicator %!test %! [~, ~, ~, stats] = coxphfit (X, T, 'Censoring', C); %! assert_equal (stats.csres + stats.martres, double (! C), 1e-12); ## The defining identity holds under 'efron' too, where MATLAB's does not %!test %! [~, ~, ~, stats] = coxphfit (X, Tt, 'Censoring', C, 'Ties', 'efron'); %! assert_equal (stats.csres + stats.martres, double (! C), 1e-12); ## A censored observation has no Schoenfeld residual %!test %! [~, ~, ~, stats] = coxphfit (X, T, 'Censoring', C); %! assert_equal (all (isnan (stats.schres(logical (C),:))(:)), true); %! assert_equal (any (isnan (stats.schres(! logical (C),:))(:)), false); ## The Schoenfeld residual follows the tie method: the two tied deaths at ## t = 4 are measured against Efron's mean over its sub-risk sets, not the ## Breslow mean of the whole risk set, which would give -2.7871 and 0.2129 %!test %! [~, ~, ~, stats] = coxphfit (X, Tt, 'Censoring', C, 'Ties', 'efron'); %! assert_equal (stats.schres(1,:), ... %! [-2.8979126429870807, -0.66427498564818011], 1e-8); %! assert_equal (stats.schres(2,:), ... %! [0.10208735701291927, 0.33572501435181989], 1e-8); %! assert_equal (stats.schres(7,:), ... %! [-1.438004413666758, -0.52896263729197179], 1e-8); ## Both tied deaths are measured against the same mean, so the residual does ## not depend on the order the tie was recorded in %!test %! [~, ~, ~, stats] = coxphfit (X, Tt, 'Censoring', C, 'Ties', 'efron'); %! assert_equal (X(1,:) - stats.schres(1,:), X(2,:) - stats.schres(2,:), 1e-12); ## Without a tie the two methods measure against the same mean %!test %! [~, ~, ~, sb] = coxphfit (X, T, 'Censoring', C); %! [~, ~, ~, se] = coxphfit (X, T, 'Censoring', C, 'Ties', 'efron'); %! assert_equal (X - sb.schres, X - se.schres, 1e-8); ## The scaled Schoenfeld residuals follow from them %!test %! [~, ~, ~, stats] = coxphfit (X, Tt, 'Censoring', C, 'Ties', 'efron'); %! assert_equal (stats.sschres(1,:), ... %! [-1.2234488884655339, 2.2036060014505958], 1e-6); ## The score residuals sum to the score at the maximum, which is zero %!test %! for tie = {'breslow', 'efron'} %! [~, ~, ~, stats] = coxphfit (X, Tt, 'Censoring', C, 'Ties', tie{1}); %! assert_equal (sum (stats.scores, 1), [0, 0], 1e-8); %! endfor ## Weighted, they sum to zero against their own weights %!test %! [~, ~, ~, stats] = coxphfit (X, Tt, 'Censoring', C, 'Ties', 'efron', ... %! 'Frequency', F); %! assert_equal (sum (F .* stats.scores, 1), [0, 0], 1e-8); ## Stratified and counting-process fits keep the identity %!test %! S = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! [~, ~, ~, stats] = coxphfit (X, Tt, 'Censoring', C, 'Ties', 'efron', ... %! 'Strata', S); %! assert_equal (sum (stats.scores, 1), [0, 0], 1e-8); %!test %! T2 = [0 4; 0 4; 2 6; 0 6; 3 8; 0 8; 5 11; 0 11; 7 13; 0 13]; %! [~, ~, ~, stats] = coxphfit (X, T2, 'Censoring', C, 'Ties', 'efron'); %! assert_equal (sum (stats.scores, 1), [0, 0], 1e-8); %! assert_equal (stats.schres(1,:), ... %! [-2.9010414762242362, -0.66966185365223341], 1e-6); ## Error conditions %!error coxphfit (X) %!error ... %! coxphfit (X, T(1:5)) %!error ... %! coxphfit (X, T, 'Ties', 'nosuch') %!error ... %! coxphfit (X, T, 'NoSuchOption', 1) %!error ... %! coxphfit (X, T, 'Censoring', C(1:5)) %!error ... %! coxphfit (X, T, 'Frequency', -ones (10, 1)) %!error ... %! coxphfit (X, T, 'B0', [1; 2; 3]) %!error ... %! coxphfit (X, T, 'Options', 5) %!error ... %! coxphfit (X, T, 'Ties') %!error ... %! coxphfit (X, T, 'Strata', ones (5, 1)) %!error ... %! coxphfit (X, [T, T]) %!error ... %! coxphfit (X, ones (10, 3)) ## Stratified fits, against R2024a %!test %! S = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! [b, logl, H] = coxphfit (X, T, 'Censoring', C, 'Strata', S); %! assert_equal (b, [-0.77050463891752163; 3.118196154424218], 1e-8); %! assert_equal (logl, -5.1821575033369704, 1e-10); %! assert_equal (size (H), [9, 3]); ## Each stratum leads with a zero row at its own first event time %!test %! S = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! [~, ~, H] = coxphfit (X, T, 'Censoring', C, 'Strata', S); %! assert_equal (H(1,:), [4, 0, 1]); %! assert_equal (H(6,:), [18, 0, 2]); %! assert_equal (H(:,3)', [1, 1, 1, 1, 1, 2, 2, 2, 2]); ## Strata are emitted in ascending label order, whatever the input order %!test %! Sa = [2; 1; 2; 1; 2; 1; 2; 1; 2; 1]; %! warning ('off', 'Octave:coxphfit-nostratvar', 'local'); %! [b, logl, H] = coxphfit (X, T, 'Censoring', C, 'Strata', Sa); %! assert_equal (H(:,3)', [1, 1, 1, 1, 1, 2, 2, 2, 2]); %! assert_equal (H(1,1), 6); %! assert_equal (H(6,1), 4); ## A column with no within-stratum variation is not estimable %!test %! Sa = [2; 1; 2; 1; 2; 1; 2; 1; 2; 1]; %! [b, logl] = coxphfit (X, T, 'Censoring', C, 'Strata', Sa); %! assert_equal (b(2), 0); %! assert_equal (isfinite (logl), true); %!warning ... %! coxphfit (X, T, 'Censoring', C, 'Strata', [2;1;2;1;2;1;2;1;2;1]); ## A stratum holding no event contributes a single NaN row %!test %! S = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! Call = [0; 0; 1; 0; 0; 1; 1; 1; 1; 1]; %! [b, logl, H] = coxphfit (X, T, 'Censoring', Call, 'Strata', S); %! assert_equal (b, [-1.0429819735537043; 4.1236894294226589], 1e-6); %! assert_equal (size (H), [6, 3]); %! assert_equal (isnan (H(6,1:2)), [true, true]); %! assert_equal (H(6,3), 2); ## Stratified fit with efron ties %!test %! S = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! [b, logl] = coxphfit (X, Tt, 'Censoring', C, 'Strata', S, 'Ties', 'efron'); %! assert_equal (b, [-0.55718778093112831; 3.2450469646639726], 1e-8); %! assert_equal (logl, -5.9561243977816423, 1e-10); ## The counting process form of T %!test %! T2 = [0 4; 0 6; 2 8; 0 11; 3 13; 0 16; 5 18; 0 21; 7 25; 0 30]; %! [b, logl, H] = coxphfit (X, T2, 'Censoring', C); %! assert_equal (b, [-1.0103738367681818; 3.2523078880768508], 1e-8); %! assert_equal (logl, -7.6542119934420016, 1e-10); %! assert_equal (size (H), [8, 2]); ## A row leaves the risk set before its start time %!test %! T3 = [0 4; 1 6; 2 8; 3 11; 4 13; 5 16; 6 18; 7 21; 8 25; 9 30]; %! [b, logl] = coxphfit (X, T3, 'Censoring', C); %! assert_equal (b, [-0.91180514501458609; 2.9397105827259509], 1e-6); %! assert_equal (logl, -7.4995427161575758, 1e-10); ## Strata and the counting process form together %!test %! T3 = [0 4; 1 6; 2 8; 3 11; 4 13; 5 16; 6 18; 7 21; 8 25; 9 30]; %! S = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! [b, logl, H] = coxphfit (X, T3, 'Censoring', C, 'Strata', S); %! assert_equal (b, [-0.72488691909164049; 2.9281191508655646], 1e-8); %! assert_equal (logl, -5.1261267996693967, 1e-10); %! assert_equal (size (H), [9, 3]); ## A single stratum is the unstratified fit, and H keeps two columns %!test %! [b1, l1, H1] = coxphfit (X, T, 'Censoring', C); %! [b2, l2, H2] = coxphfit (X, T, 'Censoring', C, 'Strata', ones (10, 1)); %! assert_equal (b2, b1, 1e-12); %! assert_equal (l2, l1, 1e-12); %! assert_equal (H2, H1, 1e-12); ## The residual identity holds in the counting process form too %!test %! T2 = [0 4; 0 6; 2 8; 0 11; 3 13; 0 16; 5 18; 0 21; 7 25; 0 30]; %! [~, ~, ~, stats] = coxphfit (X, T2, 'Censoring', C); %! assert_equal (stats.csres + stats.martres, double (! C), 1e-12); ## With every start at zero the counting form reduces to the plain one %!test %! [b1, l1, H1, s1] = coxphfit (X, T, 'Censoring', C); %! [b2, l2, H2, s2] = coxphfit (X, [zeros(10,1), T], 'Censoring', C); %! assert_equal (b2, b1, 1e-12); %! assert_equal (l2, l1, 1e-12); %! assert_equal (H2, H1, 1e-12); %! assert_equal (s2.csres, s1.csres, 1e-12); ## Each stratum is centred on its own mean unless Baseline is given %!test %! S = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! [~, ~, Ha] = coxphfit (X, T, 'Censoring', C, 'Strata', S); %! [~, ~, Hb] = coxphfit (X, T, 'Censoring', C, 'Strata', S, 'Baseline', 0); %! assert_equal (size (Ha), size (Hb)); %! assert_equal (any (abs (Ha(:,2) - Hb(:,2)) > 1e-12), true); ## A constant column is reported, not silently absorbed %!warning ... %! coxphfit ([X, ones(10,1)], T); statistics-release-1.9.2/inst/Regression/doc-cache000066400000000000000000011627201524624707500222060ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 291 # name: # type: sq_string # elements: 1 # length: 18 CompactLinearModel # name: # type: sq_string # elements: 1 # length: 3435 statistics: CompactLinearModel Compact linear regression model The CompactLinearModel class stores a fitted linear regression model without the training data. A CompactLinearModel object is returned by the compact method of a LinearModel object, and retains everything needed to inspect, predict from, and run inference on the fit, while discarding the observations and per-observation diagnostics that a LinearModel object carries. This makes a CompactLinearModel object smaller to store than the LinearModel it was compacted from. The properties of a CompactLinearModel object fall into four groups: Group Properties Coefficient estimates Coefficients (a table of estimates, standard errors, t-statistics, and p-values for each term), CoefficientCovariance , CoefficientNames , and the coefficient counts NumCoefficients and NumEstimatedCoefficients . Summary statistics of the fit DFE , MSE , RMSE , Rsquared (ordinary and adjusted), SSE , SSR , SST , LogLikelihood , and ModelCriterion (AIC, BIC, etc.). Fitting method information Robust , which records the weighting function and tuning constant used when the model is fit by robust regression, and is empty for an ordinary least squares fit. Input data properties Formula , NumObservations , NumPredictors , NumVariables , PredictorNames , ResponseName , VariableInfo , and VariableNames . Because the training data is discarded, a CompactLinearModel object has no Fitted , Residuals , Diagnostics , or ObservationInfo properties, and none of its methods refit the model. Once created, the following methods are available on a CompactLinearModel object: Method Description predict Predict responses at new predictor values given in a matrix or table. Can also return pointwise or simultaneous confidence intervals alongside the point predictions. feval Predict responses given predictors as separate scalar or vector arguments (one per predictor variable) instead of a single matrix, so a CompactLinearModel object can be evaluated the same way as a plain function handle. Returns point predictions only. random Simulate new response values at new predictor locations by adding independent Gaussian noise, drawn from the estimated error variance MSE , to the fitted response. coefCI Return Wald confidence intervals for every fitted coefficient at a chosen significance level (default 0.05 ). coefTest Test a linear hypothesis on the fitted coefficients. With no arguments, tests the overall model F-test that all non-intercept coefficients are zero; a custom hypothesis can be given as a contrast matrix and, if needed, right-hand-side values. Returns the p-value, and optionally the F-statistic and its numerator degrees of freedom. plotEffects Plot the estimated main effect and 95% confidence interval of each predictor, evaluated between its observed minimum and maximum with all other predictors held at their observed means. plotInteraction Plot the main and conditional effects of two predictors, or the adjusted response as a function of one predictor for several fixed values of the other, to visualize whether the two predictors interact. anova Analysis of variance for the fitted model. Type 3 raises an error on a model missing a lower-order relative of one of its terms, since a CompactLinearModel object has no data to refit with. Create a CompactLinearModel object by using the compact method of a fitted LinearModel object. See also: LinearModel, compact # name: # type: sq_string # elements: 1 # length: 31 Compact linear regression model # name: # type: sq_string # elements: 1 # length: 40 CompactLinearModel.CoefficientCovariance # name: # type: sq_string # elements: 1 # length: 289 CompactLinearModel: property CoefficientCovariance Covariance matrix of coefficient estimates A p -by- p numeric matrix of covariance values for the coefficient estimates, where p is the number of coefficients in the fitted model as given by NumCoefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Covariance matrix of coefficient estimates # name: # type: sq_string # elements: 1 # length: 35 CompactLinearModel.CoefficientNames # name: # type: sq_string # elements: 1 # length: 226 CompactLinearModel: property CoefficientNames Coefficient names A cell array of character vectors, each containing the name of the corresponding model term (e.g., '(Intercept)' , 'x1' , 'x1:x2' ). This property is read-only. # name: # type: sq_string # elements: 1 # length: 17 Coefficient names # name: # type: sq_string # elements: 1 # length: 31 CompactLinearModel.Coefficients # name: # type: sq_string # elements: 1 # length: 416 CompactLinearModel: property Coefficients Coefficient values A table with one row for each coefficient and four columns: Estimate - estimated coefficient value SE - standard error of the estimate tStat - t-statistic for a two-sided test pValue - p-value for the t-statistic Coefficients that are dropped due to rank deficiency have Estimate = 0 , SE = 0 , tStat = NaN , pValue = NaN . This property is read-only. # name: # type: sq_string # elements: 1 # length: 18 Coefficient values # name: # type: sq_string # elements: 1 # length: 37 CompactLinearModel.CompactLinearModel # name: # type: sq_string # elements: 1 # length: 866 CompactLinearModel: cmdl = CompactLinearModel () CompactLinearModel: cmdl = CompactLinearModel ( mdl ) Create a compact linear regression model. cmdl = CompactLinearModel () returns a CompactLinearModel object with all properties empty. cmdl = CompactLinearModel ( mdl ) copies the coefficient estimates, fit statistics, and input data description from the fitted LinearModel object mdl into a new CompactLinearModel object cmdl , discarding the training data, per-observation diagnostics, and stepwise fitting history. If mdl was fit using robust regression, the Weights field of cmdl .Robust is emptied, although the rest of the Robust structure is retained. The usual way to obtain a CompactLinearModel object is to call the compact method on an already-fitted LinearModel object, rather than calling this constructor directly. See also: LinearModel, compact # name: # type: sq_string # elements: 1 # length: 41 Create a compact linear regression model. # name: # type: sq_string # elements: 1 # length: 22 CompactLinearModel.DFE # name: # type: sq_string # elements: 1 # length: 241 CompactLinearModel: property DFE Degrees of freedom for error A positive integer equal to the number of observations minus the number of estimated coefficients: DFE = NumObservations - NumEstimatedCoefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Degrees of freedom for error # name: # type: sq_string # elements: 1 # length: 26 CompactLinearModel.Formula # name: # type: sq_string # elements: 1 # length: 274 CompactLinearModel: property Formula Model formula information A structure representing the model formula with fields including ResponseName , LinearPredictor , PredictorNames , TermNames , HasIntercept , Terms (the terms matrix), and InModel . This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Model formula information # name: # type: sq_string # elements: 1 # length: 32 CompactLinearModel.LogLikelihood # name: # type: sq_string # elements: 1 # length: 322 CompactLinearModel: property LogLikelihood Log-likelihood of the fitted model A scalar numeric value equal to the log-likelihood of the response values, assuming each response is normally distributed with mean equal to the fitted value and variance equal to SSE/n (the MLE variance estimate). This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Log-likelihood of the fitted model # name: # type: sq_string # elements: 1 # length: 22 CompactLinearModel.MSE # name: # type: sq_string # elements: 1 # length: 209 CompactLinearModel: property MSE Mean squared error A scalar numeric value equal to SSE / DFE , where SSE is the sum of squared errors and DFE is the degrees of freedom for error. This property is read-only. # name: # type: sq_string # elements: 1 # length: 18 Mean squared error # name: # type: sq_string # elements: 1 # length: 33 CompactLinearModel.ModelCriterion # name: # type: sq_string # elements: 1 # length: 498 CompactLinearModel: property ModelCriterion Model comparison criteria A structure with four fields: AIC - Akaike information criterion: -2 × logL + 2 × m AICc - AIC corrected for sample size: AIC + (2×m×(m+1))/(n-m-1) BIC - Bayesian information criterion: -2 × logL + m × log(n) CAIC - Consistent AIC: -2 × logL + m × (log(n) + 1) Here logL is LogLikelihood , m is NumEstimatedCoefficients , and n is NumObservations . This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Model comparison criteria # name: # type: sq_string # elements: 1 # length: 34 CompactLinearModel.NumCoefficients # name: # type: sq_string # elements: 1 # length: 266 CompactLinearModel: property NumCoefficients Number of model coefficients A positive integer giving the total number of coefficients in the fitted model, including any coefficients set to zero because the model terms are rank deficient. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Number of model coefficients # name: # type: sq_string # elements: 1 # length: 43 CompactLinearModel.NumEstimatedCoefficients # name: # type: sq_string # elements: 1 # length: 306 CompactLinearModel: property NumEstimatedCoefficients Number of estimated coefficients A positive integer giving the number of coefficients actually estimated, i.e., not set to zero due to rank deficiency. NumEstimatedCoefficients equals the degrees of freedom for regression. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Number of estimated coefficients # name: # type: sq_string # elements: 1 # length: 34 CompactLinearModel.NumObservations # name: # type: sq_string # elements: 1 # length: 309 CompactLinearModel: property NumObservations Number of observations used in the fit A positive integer giving the number of observations actually used in fitting the original model. Rows with missing values and rows excluded via the 'Exclude' name-value argument are not counted. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Number of observations used in the fit # name: # type: sq_string # elements: 1 # length: 32 CompactLinearModel.NumPredictors # name: # type: sq_string # elements: 1 # length: 185 CompactLinearModel: property NumPredictors Number of predictor variables A positive integer giving the number of predictor variables used to fit the model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Number of predictor variables # name: # type: sq_string # elements: 1 # length: 31 CompactLinearModel.NumVariables # name: # type: sq_string # elements: 1 # length: 274 CompactLinearModel: property NumVariables Number of variables in the input data A positive integer giving the total number of variables in the input data used to fit the original model, counting predictors, the response, and any unused columns. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Number of variables in the input data # name: # type: sq_string # elements: 1 # length: 33 CompactLinearModel.PredictorNames # name: # type: sq_string # elements: 1 # length: 207 CompactLinearModel: property PredictorNames Names of predictor variables A cell array of character vectors containing the names of the predictor variables used to fit the model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Names of predictor variables # name: # type: sq_string # elements: 1 # length: 23 CompactLinearModel.RMSE # name: # type: sq_string # elements: 1 # length: 131 CompactLinearModel: property RMSE Root mean squared error A scalar numeric value equal to sqrt(MSE) . This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Root mean squared error # name: # type: sq_string # elements: 1 # length: 31 CompactLinearModel.ResponseName # name: # type: sq_string # elements: 1 # length: 159 CompactLinearModel: property ResponseName Response variable name A character vector containing the name of the response variable. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 25 CompactLinearModel.Robust # name: # type: sq_string # elements: 1 # length: 445 CompactLinearModel: property Robust Robust fit information A structure with three fields: WgtFun - robust weighting function name, e.g. 'bisquare' Tune - tuning constant; empty if WgtFun is 'ols' or a function handle with the default tuning constant Weights - vector of final iteration weights; always empty for a CompactLinearModel object This structure is empty unless the model was fit using robust regression. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Robust fit information # name: # type: sq_string # elements: 1 # length: 27 CompactLinearModel.Rsquared # name: # type: sq_string # elements: 1 # length: 274 CompactLinearModel: property Rsquared R-squared goodness-of-fit statistics A structure with two fields: Ordinary - coefficient of determination: R^2 = SSR / SST Adjusted - adjusted R^2 that accounts for the number of coefficients in the model This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 R-squared goodness-of-fit statistics # name: # type: sq_string # elements: 1 # length: 22 CompactLinearModel.SSE # name: # type: sq_string # elements: 1 # length: 251 CompactLinearModel: property SSE Sum of squared errors A scalar numeric value equal to the sum of squared residuals. For a model with an intercept, SST = SSE + SSR . For weighted fits, this is the weighted sum of squares. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Sum of squared errors # name: # type: sq_string # elements: 1 # length: 22 CompactLinearModel.SSR # name: # type: sq_string # elements: 1 # length: 307 CompactLinearModel: property SSR Regression sum of squares A scalar numeric value equal to the sum of squared deviations of the fitted values from the mean of the response. For a model with an intercept, SST = SSE + SSR . For weighted fits, this is the weighted sum of squares. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Regression sum of squares # name: # type: sq_string # elements: 1 # length: 22 CompactLinearModel.SST # name: # type: sq_string # elements: 1 # length: 356 CompactLinearModel: property SST Total sum of squares A scalar numeric value equal to the sum of squared deviations of the response from its mean. For a model with an intercept, SST = SSE + SSR . For a robust fit, SST = SSE + SSR rather than the deviation from the mean. For weighted fits, this is the weighted sum of squares. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Total sum of squares # name: # type: sq_string # elements: 1 # length: 31 CompactLinearModel.VariableInfo # name: # type: sq_string # elements: 1 # length: 519 CompactLinearModel: property VariableInfo Information about input variables A table with one row per variable including any unused variables, and four columns: Class - variable class as a character vector, e.g. 'double' or 'categorical' Range - for continuous variables, a two-element vector [min, max] ; for categorical variables, a vector of the distinct values InModel - logical; true if the variable is in the fitted model IsCategorical - logical; true if the variable is categorical This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Information about input variables # name: # type: sq_string # elements: 1 # length: 32 CompactLinearModel.VariableNames # name: # type: sq_string # elements: 1 # length: 275 CompactLinearModel: property VariableNames Names of all variables in the input data A cell array of character vectors containing the names of all variables used to fit the original model, including predictors, the response, and unused variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 40 Names of all variables in the input data # name: # type: sq_string # elements: 1 # length: 24 CompactLinearModel.anova # name: # type: sq_string # elements: 1 # length: 2725 CompactLinearModel: tbl = anova ( mdl ) CompactLinearModel: tbl = anova ( mdl , anovatype ) CompactLinearModel: tbl = anova ( mdl , "components" , sstype ) Analysis of variance for a compact linear regression model. anova ( mdl ) returns a table tbl with component ANOVA statistics for every term in mdl except the constant term, computed with hierarchical ( "h" ) sums of squares. Each row gives SumSq , DF , MeanSq , F , and pValue for the corresponding term; the trailing Error row gives SumSq = mdl .SSE , DF = mdl .DFE , MeanSq = mdl .MSE , and NaN for F and pValue . MATLAB reports F = 1 and pValue = 0.5 on that Error row instead. Those are not results: the row’s F is its own MeanSq divided by itself, so it is 1 for every data set, and the pValue follows. MATLAB does not use them consistently either, reporting NaN for the same quantity on the Residual row of its summary table. This implementation reports NaN in both places. Every statistic is computed from mdl .Coefficients and mdl .CoefficientCovariance alone; a CompactLinearModel never refits, because it does not retain the training data. anova ( mdl , anovatype ) selects "components" (default) or "summary" . For "summary" , tbl always contains rows Total , Model , and Residual , and additionally . Linear and . Nonlinear whenever mdl contains an interaction term or a continuous term of degree greater than 1. Total reports mdl .SST with DF = NumObservations - 1 ; Model reports mdl .SSR with DF = NumCoefficients - HasIntercept ; Residual reports mdl .SSE with DF = mdl .DFE . Unlike anova on a LinearModel , tbl never contains . Lack of fit or . Pure error rows, since identifying observations with identical predictor values requires the training data that a CompactLinearModel does not retain. anova ( mdl , "components" , sstype ) selects the sum of squares used for the component table: 1 (sequential, reduction from adding each term in formula order), 2 (reduction from adding the term to a model containing every term that does not contain it), "h" (default; as Type 2, but a higher-degree term in the same continuous variable, such as a squared term, is also treated as containing the lower-degree term), or 3 (reduction from adding the term to a model containing every other term, with categorical predictors recoded using sum-to-zero deviation contrasts instead of mdl ’s reference-level coding). sstype is ignored when anovatype is "summary" . If mdl is missing a lower-order relative of one of its terms (e.g. an interaction without one of its main effects, or a categorical predictor fit without an intercept), type 3 raises an error, since a CompactLinearModel has no data to refit with. See also: LinearModel, coefTest # name: # type: sq_string # elements: 1 # length: 59 Analysis of variance for a compact linear regression model. # name: # type: sq_string # elements: 1 # length: 25 CompactLinearModel.coefCI # name: # type: sq_string # elements: 1 # length: 1330 CompactLinearModel: ci = coefCI ( mdl ) CompactLinearModel: ci = coefCI ( mdl , alpha ) Confidence intervals for the coefficient estimates of a fitted linear regression model. ci = coefCI ( mdl ) returns 95% confidence intervals for every coefficient in mdl using a default significance level of 0.05 . ci = coefCI ( mdl , alpha ) uses the significance level alpha , a scalar in [0, 1] . The resulting intervals have coverage 100(1-\alpha)\% . Setting alpha to 0 produces intervals of infinite width; setting it to 1 collapses each interval to the corresponding point estimate. The output ci is a k -by-2 numeric matrix where k = mdl .NumCoefficients . Row j contains the interval for the j -th coefficient, whose name is stored in mdl .CoefficientNames{j} . Column 1 is the lower bound and column 2 is the upper bound. The midpoint of each interval equals the corresponding point estimate in mdl .Coefficients.Estimate . Intervals use the Wald method: b_j \pm t_{(1-\alpha/2,\,\mathrm{DFE})}\,\mathrm{SE}(b_j) , where b_j is the coefficient estimate, \mathrm{SE}(b_j) is its standard error from mdl .Coefficients.SE , and the critical value is the 1-\alpha/2 quantile of the t -distribution with mdl .DFE degrees of freedom. In rank-deficient models, aliased coefficients have \mathrm{SE} = 0 and their row in ci is [0, 0] . # name: # type: sq_string # elements: 1 # length: 87 Confidence intervals for the coefficient estimates of a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 27 CompactLinearModel.coefTest # name: # type: sq_string # elements: 1 # length: 2057 CompactLinearModel: p = coefTest ( mdl ) CompactLinearModel: p = coefTest ( mdl , H ) CompactLinearModel: p = coefTest ( mdl , H , C ) CompactLinearModel: [ p , F ] = coefTest (…) CompactLinearModel: [ p , F , r ] = coefTest (…) Linear hypothesis test on the coefficients of a fitted linear regression model. coefTest tests whether one or more linear combinations of the fitted coefficients equal specified constants. Each linear combination is encoded as a row of the contrast matrix H , and the right-hand side is given by C . p = coefTest ( mdl ) performs the overall model F-test: it tests the joint null hypothesis that every coefficient except the intercept is zero. The returned p-value matches the F-statistic line printed at the bottom of the model display. p = coefTest ( mdl , H ) tests the null hypothesis H \beta = 0 , where \beta is the full coefficient vector of length k = mdl .NumCoefficients . H must be a full-rank numeric matrix with k columns; each row specifies one linear constraint. To test a single coefficient, use a row vector with a 1 in that coefficient’s position and zeros elsewhere; the resulting F-statistic equals the square of the corresponding t-statistic in mdl .Coefficients . To test a categorical predictor that expands to multiple indicator columns, include one row per indicator in H . p = coefTest ( mdl , H , C ) tests H \beta = C instead of zero. C must be a numeric vector with the same number of elements as rows of H ; both row and column vectors are accepted. The second output F is the value of the F-statistic: F = (H\hat{\beta} - C)^\prime (H V H^\prime)^{-1} (H\hat{\beta} - C) / r , where V is mdl .CoefficientCovariance and r is the number of rows of H . The third output r is that numerator degrees of freedom; the denominator degrees of freedom is mdl .DFE . Under the null hypothesis F follows an F(r, \mathrm{DFE}) distribution and the p-value is the upper-tail probability. When H is rank-deficient but contains no NaN , both p and F are returned as NaN without an error. # name: # type: sq_string # elements: 1 # length: 79 Linear hypothesis test on the coefficients of a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 24 CompactLinearModel.feval # name: # type: sq_string # elements: 1 # length: 1373 CompactLinearModel: ypred = feval ( mdl , X ) CompactLinearModel: ypred = feval ( mdl , x1 , x2 , …, xp ) Predict responses of a fitted linear regression model using separate predictor inputs. ypred = feval ( mdl , X ) accepts a single numeric matrix X with one column per predictor in the same order as the training data, or a table whose column names match mdl .PredictorNames . The output is an n -by-1 column vector. Rows that contain NaN in any predictor column are returned as NaN . ypred = feval ( mdl , x1 , x2 , …, xp ) accepts exactly mdl .NumPredictors separate arguments, one per predictor variable. All non-scalar arguments must have the same size; a scalar argument is broadcast to that size automatically. The output shape follows the shape of the non-scalar inputs: column vector inputs give a column vector output, row vector inputs give a row vector output, and all-scalar inputs give a scalar. This form is convenient when predictor data is already stored in separate vectors rather than a combined matrix. feval gives the same numerical predictions as predict but does not support confidence intervals. Use predict when you also need bounds on the response. Because a CompactLinearModel object behaves like a function through feval , it can be passed directly to routines that accept a function handle, such as fminsearch or integral . # name: # type: sq_string # elements: 1 # length: 86 Predict responses of a fitted linear regression model using separate predictor inputs. # name: # type: sq_string # elements: 1 # length: 30 CompactLinearModel.plotEffects # name: # type: sq_string # elements: 1 # length: 1789 CompactLinearModel: plotEffects ( mdl ) CompactLinearModel: plotEffects ( ax , mdl ) CompactLinearModel: h = plotEffects (…) Plot the main effects of each predictor in a compact linear regression model. plotEffects ( mdl ) creates a horizontal dot-and-line plot with one row per predictor. Each dot shows the estimated main effect on the response from changing that predictor from its minimum observed value to its maximum observed value, while holding all other predictors fixed at their observed means. A horizontal line through each dot shows the 95% confidence interval for that effect. The main effect for predictor xs is defined as g(x_{s,\max}) - g(x_{s,\min}) , where the adjusted response function g evaluates the model at the specified value of xs with all other predictors set to their observed means. For numeric predictors the sign of the effect can be positive or negative depending on the direction of the relationship. Because a CompactLinearModel does not retain the training data, these values come from a summary computed once when the model was fitted, rather than recomputed from the original observations. plotEffects ( ax , mdl ) creates the plot in the axes object ax instead of the current axes returned by gca . h = plotEffects (…) returns a vector of p+1 graphics handles where p is the number of predictors. h(1) is the line object containing the effect estimate markers (one circle per predictor, plotted as a single line object with XData of length p and YData = 1:p ). h(j+1) is the confidence interval line for predictor j , with XData = [ci_lo, ci_hi] and YData = [j, j] . The y-axis tick labels follow the format 'varname: min to max' , showing the predictor name and the minimum and maximum observed values used to compute the effect. # name: # type: sq_string # elements: 1 # length: 77 Plot the main effects of each predictor in a compact linear regression model. # name: # type: sq_string # elements: 1 # length: 34 CompactLinearModel.plotInteraction # name: # type: sq_string # elements: 1 # length: 3026 CompactLinearModel: plotInteraction ( mdl , var1 , var2 ) CompactLinearModel: plotInteraction ( mdl , var1 , var2 , ptype ) CompactLinearModel: plotInteraction ( ax , …) CompactLinearModel: h = plotInteraction (…) Plot the interaction effects of two predictors in a compact linear regression model. plotInteraction ( mdl , var1 , var2 ) creates a plot of the main effects of var1 and var2 together with their conditional effects, with horizontal lines through each effect value indicating its 95% confidence interval. var1 and var2 are each a character vector or string naming a variable in mdl.VariableNames , or a positive integer indexing into mdl.VariableNames ; neither may name the response variable, and they must be different variables. The main effect of a predictor is the change in the adjusted response between the two predictor values that produce the minimum and maximum adjusted response, with the other predictor averaged over its own observed values row by row. For a numeric predictor these two values are its observed minimum and maximum; for a categorical predictor every level is evaluated and the levels producing the minimum and maximum adjusted response are used, so the effect is always nonnegative. The conditional effect of var1 is its effect recomputed with var2 additionally held fixed at each of a small set of conditioning values, and likewise the conditional effect of var2 holds var1 fixed. The conditioning values are the observed minimum, mean of the minimum and maximum, and maximum for a numeric predictor, or every level for a categorical predictor. When the main effect and conditional effect points for a predictor do not align vertically, the model exhibits an interaction between var1 and var2 . Because a CompactLinearModel does not retain the training data, these values come from a summary computed once when the model was fitted, rather than recomputed from the original observations. plotInteraction ( mdl , var1 , var2 , ptype ) selects the plot type. ptype is 'effects' (default), as described above, or 'predictions' , which instead plots the adjusted response as a function of var2 for each conditioning value of var1 held fixed, evaluated over 101 equally spaced points spanning the observed range of var2 when var2 is numeric, or at each level of var2 when it is categorical. plotInteraction ( ax , …) plots into the axes object ax instead of the current axes returned by gca . h = plotInteraction (…) returns a vector of line handles. When ptype is 'effects' , h(1) is the marker line through the two main effect points, h(2) and h(3) are the confidence interval lines for the main effects of var1 and var2 , and the remaining entries are the conditional effect points and their confidence intervals, tagged 'conditional1' for var1 and 'conditional2' for var2 . The main effect line objects are tagged 'main' . When ptype is 'predictions' , each entry in h corresponds to one adjusted response curve, one per conditioning value of var1 . # name: # type: sq_string # elements: 1 # length: 84 Plot the interaction effects of two predictors in a compact linear regression model. # name: # type: sq_string # elements: 1 # length: 26 CompactLinearModel.predict # name: # type: sq_string # elements: 1 # length: 1826 CompactLinearModel: ypred = predict ( mdl , Xnew ) CompactLinearModel: [ ypred , yci ] = predict ( mdl , Xnew ) CompactLinearModel: [ ypred , yci ] = predict ( mdl , Xnew , Name , Value ) Predict responses from a fitted linear regression model. ypred = predict ( mdl , Xnew ) returns the fitted response values at the new predictor locations in Xnew . Xnew can be a numeric matrix with one column per predictor in the same order as the training data, or a table whose column names match mdl .PredictorNames . Rows containing NaN are returned as NaN without error. Unlike LinearModel , Xnew is required: a CompactLinearModel object does not store the training data, so there is no default to fall back on when it is omitted. [ ypred , yci ] = predict (…) also returns yci , an n -by-2 matrix of confidence bounds where column 1 is the lower bound and column 2 is the upper bound. By default these are 95% pointwise confidence intervals on the mean response. Name-Value pair arguments: Name Value 'Alpha' Significance level for the confidence interval, specified as a scalar in [0,1] . The interval has coverage 100(1-\alpha)\% . Default is 0.05 , giving a 95% interval. 'Prediction' Type of interval to compute. "curve" (default) gives a confidence interval on the mean response f(x) . "observation" gives a wider prediction interval for a single future observation y = f(x) + \varepsilon , which accounts for both estimation uncertainty and irreducible noise; it adds mdl .MSE to the variance before computing the half-width. 'Simultaneous' Logical flag controlling whether the bounds are simultaneous or pointwise. When true , Scheff'{e}’s method is used so the entire predicted curve lies within the band with 100(1-\alpha)\% confidence; these bands are always wider than pointwise ones. Default is false . # name: # type: sq_string # elements: 1 # length: 56 Predict responses from a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 25 CompactLinearModel.random # name: # type: sq_string # elements: 1 # length: 1022 CompactLinearModel: ysim = random ( mdl , Xnew ) Simulate responses with random noise from a fitted linear regression model. ysim = random ( mdl , Xnew ) computes the fitted response at each row of Xnew and then adds independent Gaussian noise to each value. The noise is drawn from N(0, \sigma^2) where \sigma^2 is the estimated error variance mdl .MSE (mean squared error of the fit). The result is a column vector of the same length as the number of rows in Xnew . Xnew is required and must be non-empty. It can be a numeric matrix with one column per predictor in the same order as the training data, or a table whose column names match mdl .PredictorNames . Because the added noise is drawn freshly on every call, two calls with the same Xnew will generally produce different output. To get reproducible results, set the random seed with rand ('state', s) before calling random . For deterministic predictions without noise, use predict or feval . predict also provides confidence intervals on the mean response. # name: # type: sq_string # elements: 1 # length: 75 Simulate responses with random noise from a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 8 CoxModel # name: # type: sq_string # elements: 1 # length: 2288 statistics: CoxModel Cox proportional hazards regression model class. A CoxModel object encapsulates a Cox proportional hazards model of a survival time on one or more predictors, fitted by maximizing the Cox partial likelihood. It is the object counterpart of coxphfit and is normally created with the fitcox function. The model states that an observation with predictor values x has hazard $$ h(x, t) = h_0(t)\exp\left(\sum_{j=1}^{p} x_{j} b_j\right) $$ where h_0(t) is an unspecified baseline hazard. The model carries no constant term: any constant is absorbed into that baseline. The most useful properties are Coefficients (a table of estimates, standard errors, z -statistics and p-values), Hazard (the estimated baseline cumulative hazard), LogLikelihood , Residuals , and the three p-values LikelihoodRatioTestPValue , ProportionalHazardsPValue and ProportionalHazardsPValueGlobal . Fitted models support the survival , hazardratio , coefci , linhyptest , plotSurvival and discardResiduals methods. A categorical predictor expands to indicator columns, one per level bar the first, which the baseline hazard carries; the indicator columns are named name _ level and enter the default baseline as zero, while a numeric predictor enters it as its mean. ProportionalHazardsPValue is a Grambsch-Therneau test of each coefficient against the mid-ranks of the event times, and ProportionalHazardsPValueGlobal the same test taken over the whole model. A small p-value is evidence that the hazard ratio moves with time, which is what proportionality denies. Deviations from MATLAB, all in naming. MATLAB derives the names reported by a fitted model from three different places and they need not agree with one another: with default predictor names its Formula reads 'y ~ x1 + x2' in lower case while PredictorNames holds 'X1' and 'X2' , and supplying 'PredictorNames' changes ResponseName from 'y' to the name of the variable passed as the response. Here the names are consistent by construction: ResponseName is 'y' unless the data came from a table, the Formula is built from PredictorNames and ResponseName , and neither depends on which optional arguments were given. Every fitted quantity agrees with MATLAB. See also: fitcox, coxphfit, GeneralizedLinearModel, LinearModel # name: # type: sq_string # elements: 1 # length: 48 Cox proportional hazards regression model class. # name: # type: sq_string # elements: 1 # length: 17 CoxModel.Baseline # name: # type: sq_string # elements: 1 # length: 296 CoxModel: property Baseline Predictor values the baseline hazard is evaluated at The baseline the fit used, one row per stratum. It is reported as it was given when fitcox was given one, a scalar staying a scalar, and otherwise holds the rows the fit was centred on. This property is read-only. # name: # type: sq_string # elements: 1 # length: 52 Predictor values the baseline hazard is evaluated at # name: # type: sq_string # elements: 1 # length: 30 CoxModel.CoefficientCovariance # name: # type: sq_string # elements: 1 # length: 253 CoxModel: property CoefficientCovariance Estimated covariance of the coefficients A square numeric matrix, one row and column per encoded predictor column, holding the estimated covariance of the estimates in Coefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 40 Estimated covariance of the coefficients # name: # type: sq_string # elements: 1 # length: 21 CoxModel.Coefficients # name: # type: sq_string # elements: 1 # length: 244 CoxModel: property Coefficients Coefficient estimates and their statistics A table with one row per encoded predictor column, its row names the encoded column names, and the variables Beta , SE , zStat and pValue . This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Coefficient estimates and their statistics # name: # type: sq_string # elements: 1 # length: 17 CoxModel.CoxModel # name: # type: sq_string # elements: 1 # length: 2226 CoxModel: mdl = CoxModel ( X , T ) CoxModel: mdl = CoxModel ( tbl , respvar ) CoxModel: mdl = CoxModel (…, Name , Value ) Fit a Cox proportional hazards regression model. mdl = CoxModel ( X , T ) fits the model to the n -by- p numeric predictor matrix X and the n -by-1 vector of event times T . T may instead be an n -by-2 matrix giving a (start, stop] interval of exposure, the counting process form. mdl = CoxModel ( tbl , respvar ) takes the data from the table tbl , using the variable named respvar as the response and every other variable as a predictor. A categorical variable is encoded as indicator columns. X must not contain a constant column: the model has no constant term, since any constant is absorbed into the baseline hazard. The following Name / Value pairs are accepted: Name Value "Baseline" The X values at which the baseline hazard is computed, either a scalar or a 1-by- p vector. The default is the mean of each numeric predictor and zero for each indicator column of a categorical predictor, taken within each stratum. "Beta" The starting value of the iteration, a vector of length p . The default is 0.01 ./ std ( X ) . "CategoricalPredictors" The predictors to treat as categorical, given as column indices, a logical vector, or a cell array of predictor names. Table variables of class categorical are detected without this argument. "Censoring" A logical or 0/1 vector of length n , where 1 marks an observation right-censored at its recorded time. The default is a vector of zeros. "Frequency" A vector of length n of non-negative values giving the number of observations each row represents, or a weight. The default is a vector of ones. "OptimizationOptions" A structure of iteration settings, as built by statset ("fitcox") . The fields used are "MaxIter" , "TolX" and "Display" . "PredictorNames" A cell array of p predictor names. The default is "X1" , "X2" , and so on, or the table variable names. "Stratification" A vector of length n of stratum labels. Each stratum carries its own baseline hazard and its own risk sets, while the coefficients are shared across all of them. "TieBreakMethod" The method of handling tied event times, either "breslow" (default) or "efron" . # name: # type: sq_string # elements: 1 # length: 48 Fit a Cox proportional hazards regression model. # name: # type: sq_string # elements: 1 # length: 16 CoxModel.Formula # name: # type: sq_string # elements: 1 # length: 128 CoxModel: property Formula Model formula A LinearFormula object describing the terms of the model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 13 Model formula # name: # type: sq_string # elements: 1 # length: 15 CoxModel.Hazard # name: # type: sq_string # elements: 1 # length: 273 CoxModel: property Hazard Estimated baseline cumulative hazard A numeric matrix of event times in its first column and the cumulative hazard at them in its second. A stratified model adds a third column holding the stratum each row belongs to. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Estimated baseline cumulative hazard # name: # type: sq_string # elements: 1 # length: 34 CoxModel.LikelihoodRatioTestPValue # name: # type: sq_string # elements: 1 # length: 206 CoxModel: property LikelihoodRatioTestPValue Likelihood ratio test against the null model A scalar p value comparing the fitted model with the model that carries no predictors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Likelihood ratio test against the null model # name: # type: sq_string # elements: 1 # length: 22 CoxModel.LogLikelihood # name: # type: sq_string # elements: 1 # length: 149 CoxModel: property LogLikelihood Log-likelihood of the fitted model A scalar, the maximised Cox partial log-likelihood. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Log-likelihood of the fitted model # name: # type: sq_string # elements: 1 # length: 22 CoxModel.NumPredictors # name: # type: sq_string # elements: 1 # length: 194 CoxModel: property NumPredictors Number of predictors A positive integer counting the predictor variables of the model, before any categorical predictor is encoded. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 23 CoxModel.PredictorNames # name: # type: sq_string # elements: 1 # length: 159 CoxModel: property PredictorNames Names of the predictor variables A cell array of character vectors with one name per predictor. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Names of the predictor variables # name: # type: sq_string # elements: 1 # length: 34 CoxModel.ProportionalHazardsPValue # name: # type: sq_string # elements: 1 # length: 314 CoxModel: property ProportionalHazardsPValue Proportional hazards test, one predictor at a time A numeric vector with one p value per predictor, testing whether that predictor’s effect is constant over time. A small value is evidence against the proportional hazards assumption. This property is read-only. # name: # type: sq_string # elements: 1 # length: 50 Proportional hazards test, one predictor at a time # name: # type: sq_string # elements: 1 # length: 40 CoxModel.ProportionalHazardsPValueGlobal # name: # type: sq_string # elements: 1 # length: 216 CoxModel: property ProportionalHazardsPValueGlobal Proportional hazards test over the whole model A scalar p value testing the proportional hazards assumption for all predictors at once. This property is read-only. # name: # type: sq_string # elements: 1 # length: 46 Proportional hazards test over the whole model # name: # type: sq_string # elements: 1 # length: 18 CoxModel.Residuals # name: # type: sq_string # elements: 1 # length: 234 CoxModel: property Residuals Residuals of the fitted model A table with one row per observation and the variables CoxSnell , Deviance , Martingale , Schoenfeld , ScaledSchoenfeld , Score and ScaledScore . This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Residuals of the fitted model # name: # type: sq_string # elements: 1 # length: 21 CoxModel.ResponseName # name: # type: sq_string # elements: 1 # length: 159 CoxModel: property ResponseName Name of the response variable A character vector naming the response, which is the survival time. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Name of the response variable # name: # type: sq_string # elements: 1 # length: 22 CoxModel.StandardError # name: # type: sq_string # elements: 1 # length: 220 CoxModel: property StandardError Standard errors of the coefficients A numeric column vector, the square roots of the diagonal of CoefficientCovariance , which is column SE of Coefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 35 Standard errors of the coefficients # name: # type: sq_string # elements: 1 # length: 23 CoxModel.Stratification # name: # type: sq_string # elements: 1 # length: 199 CoxModel: property Stratification Stratification levels used in the fit The distinct levels of the stratification variable. It is empty when the model is not stratified. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Stratification levels used in the fit # name: # type: sq_string # elements: 1 # length: 21 CoxModel.VariableInfo # name: # type: sq_string # elements: 1 # length: 224 CoxModel: property VariableInfo Information about the variables A table with one row per variable, its row names the variable names, and the variables Class , Range , InModel and IsCategorical . This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Information about the variables # name: # type: sq_string # elements: 1 # length: 15 CoxModel.coefci # name: # type: sq_string # elements: 1 # length: 407 CoxModel: ci = coefci ( obj ) CoxModel: ci = coefci ( obj , level ) Confidence intervals for the coefficients of a Cox model. ci = coefci ( obj ) returns a two-column matrix with one row per coefficient, holding the 95% confidence interval of each. ci = coefci ( obj , level ) uses a 100 (1 - level ) % interval. level must be a positive scalar smaller than 1; it is a significance level, not a coverage. # name: # type: sq_string # elements: 1 # length: 57 Confidence intervals for the coefficients of a Cox model. # name: # type: sq_string # elements: 1 # length: 25 CoxModel.discardResiduals # name: # type: sq_string # elements: 1 # length: 401 CoxModel: obj = discardResiduals ( obj ) Drop the stored residuals of a Cox model. obj = discardResiduals ( obj ) returns the model with an empty Residuals property. The residual table holds one row per observation and is the largest thing a fitted model carries, so discarding it makes a model that is only going to be used for prediction considerably smaller. Nothing else about the model changes. # name: # type: sq_string # elements: 1 # length: 41 Drop the stored residuals of a Cox model. # name: # type: sq_string # elements: 1 # length: 20 CoxModel.hazardratio # name: # type: sq_string # elements: 1 # length: 773 CoxModel: hr = hazardratio ( obj , X ) CoxModel: hr = hazardratio ( obj , X , S ) CoxModel: hr = hazardratio (…, "Baseline" , B ) Hazard of a Cox model relative to its baseline. hr = hazardratio ( obj , X ) returns the hazard at the predictor values X relative to the baseline the model was fitted with, exp (( X - B ) * b ) . X has one row per evaluation point and is a numeric matrix, or a table when the model was fitted from one. hr = hazardratio ( obj , X , S ) gives the stratum of each row of X , and is required when the model is stratified, each stratum having its own baseline. hr = hazardratio (…, "Baseline", B ) evaluates the ratio against the baseline B instead, either a scalar or a row vector with one element per encoded predictor column. # name: # type: sq_string # elements: 1 # length: 47 Hazard of a Cox model relative to its baseline. # name: # type: sq_string # elements: 1 # length: 19 CoxModel.linhyptest # name: # type: sq_string # elements: 1 # length: 587 CoxModel: tbl = linhyptest ( obj ) Sequential tests on the coefficients of a Cox model. tbl = linhyptest ( obj ) returns a table with one row per predictor, whose k -th row tests the hypothesis that the coefficients of the k -th and every later predictor are jointly zero. The Predictor column names the predictors the hypothesis leaves in the model, so its first row reads "Empty Model" and tests every coefficient at once, and its last row tests the last coefficient alone, reproducing that coefficient’s own p-value. Each test is a Wald test on the fitted model, not a refit. # name: # type: sq_string # elements: 1 # length: 52 Sequential tests on the coefficients of a Cox model. # name: # type: sq_string # elements: 1 # length: 21 CoxModel.plotSurvival # name: # type: sq_string # elements: 1 # length: 565 CoxModel: plotSurvival ( obj ) CoxModel: plotSurvival ( obj , X ) CoxModel: plotSurvival ( obj , X , S ) CoxModel: h = plotSurvival (…) Plot the survival function of a Cox model. plotSurvival ( obj ) draws the survival function at the model’s baseline as a stairstep plot. plotSurvival ( obj , X ) draws it at the predictor values X , one curve per row, and S gives the stratum of each row when the model is stratified. A stratified model with no X draws one curve per stratum. h = plotSurvival (…) returns the handles of the stairstep lines. # name: # type: sq_string # elements: 1 # length: 42 Plot the survival function of a Cox model. # name: # type: sq_string # elements: 1 # length: 17 CoxModel.survival # name: # type: sq_string # elements: 1 # length: 1160 CoxModel: s = survival ( obj ) CoxModel: s = survival ( obj , X ) CoxModel: s = survival ( obj , X , S ) CoxModel: s = survival (…, Name , Value ) CoxModel: [ s , T ] = survival (…) Survival function of a Cox model. s = survival ( obj ) returns the survival probability at the model’s baseline, evaluated at each row of the Hazard property. s = survival ( obj , X ) evaluates it at the predictor values X , and S gives the stratum of each row when the model is stratified. For a stratified model s is a cell array holding one column vector per curve. [ s , T ] = survival (…) also returns the times the probabilities refer to. The following Name / Value pairs are accepted: Name Value "Time" The times at which to evaluate the survival function. The default is the model’s own event times. The baseline survival is interpolated linearly between them and raised to the hazard ratio of X . "ExtrapolationMethod" How to evaluate a time outside the model’s event times: "nearest" (default), "linear" , "next" , "previous" , or "none" . "none" returns NaN outside the range, as do "next" above it and "previous" below it. # name: # type: sq_string # elements: 1 # length: 33 Survival function of a Cox model. # name: # type: sq_string # elements: 1 # length: 27 GeneralizedLinearMixedModel # name: # type: sq_string # elements: 1 # length: 729 statistics: GeneralizedLinearMixedModel Generalized linear mixed-effects model fitted to data. A GeneralizedLinearMixedModel object represents a fitted generalized linear mixed-effects model: a generalized linear model whose linear predictor X*beta + Z*b includes normally distributed random effects b ~ N(0, Psi) . Objects are created with fitglme . The model is fitted by penalized quasi-likelihood. The fixed-effects estimates and their statistics are available through the Coefficients table, the covariance parameters through covarianceParameters , and predictions, residuals, and hypothesis tests through the predict , residuals , anova , coefTest , and coefCI methods. See also: fitglme, fitlme, GeneralizedLinearModel # name: # type: sq_string # elements: 1 # length: 54 Generalized linear mixed-effects model fitted to data. # name: # type: sq_string # elements: 1 # length: 49 GeneralizedLinearMixedModel.CoefficientCovariance # name: # type: sq_string # elements: 1 # length: 274 GeneralizedLinearMixedModel: property CoefficientCovariance Covariance of the fixed-effects estimates A square numeric matrix, one row and column per fixed-effects coefficient, holding the estimated covariance of the estimates in Coefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 41 Covariance of the fixed-effects estimates # name: # type: sq_string # elements: 1 # length: 44 GeneralizedLinearMixedModel.CoefficientNames # name: # type: sq_string # elements: 1 # length: 203 GeneralizedLinearMixedModel: property CoefficientNames Names of the fixed-effects coefficients A cell array of character vectors with one name per fixed-effects coefficient. This property is read-only. # name: # type: sq_string # elements: 1 # length: 39 Names of the fixed-effects coefficients # name: # type: sq_string # elements: 1 # length: 40 GeneralizedLinearMixedModel.Coefficients # name: # type: sq_string # elements: 1 # length: 344 GeneralizedLinearMixedModel: property Coefficients Fixed-effects estimates and their statistics A table with one row per fixed-effects coefficient, its row names taken from CoefficientNames , and the variables Estimate , SE , tStat , DF , pValue , Lower and Upper . Lower and Upper bound a 95% confidence interval. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Fixed-effects estimates and their statistics # name: # type: sq_string # elements: 1 # length: 31 GeneralizedLinearMixedModel.DFE # name: # type: sq_string # elements: 1 # length: 161 GeneralizedLinearMixedModel: property DFE Residual degrees of freedom A nonnegative integer, NumObservations less NumCoefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Residual degrees of freedom # name: # type: sq_string # elements: 1 # length: 38 GeneralizedLinearMixedModel.Dispersion # name: # type: sq_string # elements: 1 # length: 202 GeneralizedLinearMixedModel: property Dispersion Dispersion parameter A positive scalar. It is estimated for a normal response and fixed at 1 for a binomial or Poisson one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Dispersion parameter # name: # type: sq_string # elements: 1 # length: 40 GeneralizedLinearMixedModel.Distribution # name: # type: sq_string # elements: 1 # length: 207 GeneralizedLinearMixedModel: property Distribution Response distribution A character vector naming the distribution of the response, one of 'binomial' , 'poisson' and 'normal' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Response distribution # name: # type: sq_string # elements: 1 # length: 37 GeneralizedLinearMixedModel.FitMethod # name: # type: sq_string # elements: 1 # length: 217 GeneralizedLinearMixedModel: property FitMethod Estimation method A character vector naming the method that fitted the model, one of 'MPL' , 'REMPL' , 'Laplace' and 'ApproximateLaplace' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 17 Estimation method # name: # type: sq_string # elements: 1 # length: 35 GeneralizedLinearMixedModel.Formula # name: # type: sq_string # elements: 1 # length: 206 GeneralizedLinearMixedModel: property Formula Model formula A character vector describing the model. It is empty for a fit built from design matrices, which carries no formula. This property is read-only. # name: # type: sq_string # elements: 1 # length: 13 Model formula # name: # type: sq_string # elements: 1 # length: 55 GeneralizedLinearMixedModel.GeneralizedLinearMixedModel # name: # type: sq_string # elements: 1 # length: 202 GeneralizedLinearMixedModel: glme = GeneralizedLinearMixedModel ( info ) Construct from a fitted-model info struct. Used internally by fitglme ; call that function rather than the constructor directly. # name: # type: sq_string # elements: 1 # length: 42 Construct from a fitted-model info struct. # name: # type: sq_string # elements: 1 # length: 32 GeneralizedLinearMixedModel.Link # name: # type: sq_string # elements: 1 # length: 162 GeneralizedLinearMixedModel: property Link Link function A character vector naming the link, one of 'logit' , 'log' and 'identity' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 13 Link function # name: # type: sq_string # elements: 1 # length: 41 GeneralizedLinearMixedModel.LogLikelihood # name: # type: sq_string # elements: 1 # length: 254 GeneralizedLinearMixedModel: property LogLikelihood Log-likelihood of the fitted model A scalar. It is a pseudo log-likelihood when FitMethod is 'MPL' or 'REMPL' , and a Laplace approximation to the log-likelihood otherwise. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Log-likelihood of the fitted model # name: # type: sq_string # elements: 1 # length: 42 GeneralizedLinearMixedModel.ModelCriterion # name: # type: sq_string # elements: 1 # length: 302 GeneralizedLinearMixedModel: property ModelCriterion Information criteria A scalar structure with the fields AIC , BIC , LogLikelihood and Deviance . The parameter count behind AIC and BIC holds the fixed-effects coefficients, the covariance parameters and the dispersion. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Information criteria # name: # type: sq_string # elements: 1 # length: 43 GeneralizedLinearMixedModel.NumCoefficients # name: # type: sq_string # elements: 1 # length: 193 GeneralizedLinearMixedModel: property NumCoefficients Number of fixed-effects coefficients A positive integer counting the fixed-effects coefficients of the model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Number of fixed-effects coefficients # name: # type: sq_string # elements: 1 # length: 43 GeneralizedLinearMixedModel.NumObservations # name: # type: sq_string # elements: 1 # length: 169 GeneralizedLinearMixedModel: property NumObservations Number of observations A positive integer counting the observations used for the fit. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 40 GeneralizedLinearMixedModel.ResponseName # name: # type: sq_string # elements: 1 # length: 200 GeneralizedLinearMixedModel: property ResponseName Name of the response variable A character vector naming the response. It is empty for a fit built from design matrices. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Name of the response variable # name: # type: sq_string # elements: 1 # length: 33 GeneralizedLinearMixedModel.anova # name: # type: sq_string # elements: 1 # length: 164 GeneralizedLinearMixedModel: tbl = anova ( glme ) Analysis-of-deviance table of F-tests for the fixed-effects terms, using residual denominator degrees of freedom. # name: # type: sq_string # elements: 1 # length: 113 Analysis-of-deviance table of F-tests for the fixed-effects terms, using residual denominator degrees of freedom. # name: # type: sq_string # elements: 1 # length: 34 GeneralizedLinearMixedModel.coefCI # name: # type: sq_string # elements: 1 # length: 115 GeneralizedLinearMixedModel: ci = coefCI ( glme , alpha ) Confidence intervals for the fixed-effects coefficients. # name: # type: sq_string # elements: 1 # length: 56 Confidence intervals for the fixed-effects coefficients. # name: # type: sq_string # elements: 1 # length: 36 GeneralizedLinearMixedModel.coefTest # name: # type: sq_string # elements: 1 # length: 120 GeneralizedLinearMixedModel: [ p , F , df1 , df2 ] = coefTest ( glme , H ) F-test of the linear hypothesis H*beta = 0 . # name: # type: sq_string # elements: 1 # length: 43 F-test of the linear hypothesis H*beta = 0. # name: # type: sq_string # elements: 1 # length: 48 GeneralizedLinearMixedModel.covarianceParameters # name: # type: sq_string # elements: 1 # length: 142 GeneralizedLinearMixedModel: [ psi , disp ] = covarianceParameters ( glme ) Return the random-effects covariance matrices and the dispersion. # name: # type: sq_string # elements: 1 # length: 65 Return the random-effects covariance matrices and the dispersion. # name: # type: sq_string # elements: 1 # length: 40 GeneralizedLinearMixedModel.designMatrix # name: # type: sq_string # elements: 1 # length: 138 GeneralizedLinearMixedModel: D = designMatrix ( glme , type ) Return the fixed ( "Fixed" , default) or random ( "Random" ) design matrix. # name: # type: sq_string # elements: 1 # length: 71 Return the fixed ("Fixed", default) or random ("Random") design matrix. # name: # type: sq_string # elements: 1 # length: 34 GeneralizedLinearMixedModel.fitted # name: # type: sq_string # elements: 1 # length: 119 GeneralizedLinearMixedModel: yf = fitted ( glme ) Return the fitted mean response (conditional on the random effects). # name: # type: sq_string # elements: 1 # length: 68 Return the fitted mean response (conditional on the random effects). # name: # type: sq_string # elements: 1 # length: 40 GeneralizedLinearMixedModel.fixedEffects # name: # type: sq_string # elements: 1 # length: 138 GeneralizedLinearMixedModel: [ beta , names ] = fixedEffects ( glme ) Return the fixed-effects coefficients and, optionally, their names. # name: # type: sq_string # elements: 1 # length: 67 Return the fixed-effects coefficients and, optionally, their names. # name: # type: sq_string # elements: 1 # length: 35 GeneralizedLinearMixedModel.predict # name: # type: sq_string # elements: 1 # length: 272 GeneralizedLinearMixedModel: ypred = predict ( glme , Xnew , Znew , Gnew ) Predict the mean response at new data. With "Conditional" true (default) the random effects of known grouping levels are added; unknown levels fall back to the marginal (fixed-effects) prediction. # name: # type: sq_string # elements: 1 # length: 38 Predict the mean response at new data. # name: # type: sq_string # elements: 1 # length: 41 GeneralizedLinearMixedModel.randomEffects # name: # type: sq_string # elements: 1 # length: 117 GeneralizedLinearMixedModel: b = randomEffects ( glme ) Return the estimated random-effects (the conditional modes). # name: # type: sq_string # elements: 1 # length: 60 Return the estimated random-effects (the conditional modes). # name: # type: sq_string # elements: 1 # length: 37 GeneralizedLinearMixedModel.residuals # name: # type: sq_string # elements: 1 # length: 175 GeneralizedLinearMixedModel: r = residuals ( glme ) GeneralizedLinearMixedModel: r = residuals ( glme , "ResidualType" , type ) Return "Raw" (default) or "Pearson" residuals. # name: # type: sq_string # elements: 1 # length: 46 Return "Raw" (default) or "Pearson" residuals. # name: # type: sq_string # elements: 1 # length: 22 GeneralizedLinearModel # name: # type: sq_string # elements: 1 # length: 2772 statistics: GeneralizedLinearModel Generalized linear regression model class. A GeneralizedLinearModel object encapsulates a generalized linear model (GLM) of a response on one or more predictors, fitted by iteratively reweighted least squares. It is the GLM counterpart of LinearModel and is normally created with the fitglm function. The response is modelled through a distribution from the exponential family ( 'normal' , 'binomial' , 'poisson' , 'gamma' , or 'inverse gaussian' ) and a link function g relating the mean mu to the linear predictor eta = g (mu) . The most useful properties are Coefficients (a table of estimates, standard errors, t -statistics and p-values), Deviance , Dispersion , Residuals , Fitted , Diagnostics , Distribution , and Link . ObservationInfo records which rows were weighted, excluded, or missing, and Variables holds the data the model was built from. For a binomial response given as an n -by- 2 matrix of successes and trials, Variables holds the success count alone, that being the response the model fits; MATLAB stores both columns there. Fitted models support the predict and feval methods for prediction. Fitted , Residuals , Diagnostics , and ObservationInfo have one row per input observation, not per fitted observation. Rows that were excluded with the 'Exclude' pair still carry a fitted value and a residual, since the model can be evaluated there; rows dropped because a variable was missing carry NaN . For a binomial response carrying a number of trials N – given either by the 'BinomialSize' pair or as the second column of a two-column response – the response is the number of successes , as fitglm documents. Fitted.Response is then the fitted count N p and Residuals.Raw is on that same count scale, while Fitted.Probability carries p itself. predict returns the probability, never a count: a trial count belongs to an observation, and new predictor values do not carry one. A categorical predictor expands to indicator columns, one per level bar the reference level, which the intercept carries. When the model has no intercept, the first categorical predictor is given an indicator for every one of its levels instead, so that its coefficients are the group means; any further categorical predictor stays reference coded, which keeps the design full rank. This differs from MATLAB, which omits the reference level whether or not an intercept is present and so cannot fit the reference group at all – for a three-level grouping variable g , MATLAB fits y ~ g - 1 with two coefficients, predicts exactly 0 for every observation in the omitted group, and reports a negative R^2 . This implementation returns three coefficients, one per group. See also: fitglm, LinearModel, glmfit, glmval # name: # type: sq_string # elements: 1 # length: 42 Generalized linear regression model class. # name: # type: sq_string # elements: 1 # length: 44 GeneralizedLinearModel.CoefficientCovariance # name: # type: sq_string # elements: 1 # length: 351 GeneralizedLinearModel: property CoefficientCovariance Covariance matrix of the coefficient estimates A square matrix with one row and column per coefficient, whose diagonal is the square of Coefficients.SE . It is scaled by Dispersion , so it is the covariance under the estimated dispersion wherever one was estimated. This property is read-only. # name: # type: sq_string # elements: 1 # length: 46 Covariance matrix of the coefficient estimates # name: # type: sq_string # elements: 1 # length: 39 GeneralizedLinearModel.CoefficientNames # name: # type: sq_string # elements: 1 # length: 400 GeneralizedLinearModel: property CoefficientNames Names of the coefficients A cell array of character vectors, one per coefficient, in the order the coefficients appear. The intercept is '(Intercept)' , an interaction joins its factors with a colon, and a categorical predictor contributes one name per indicator, spelled name _ level , so these are not the term names. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Names of the coefficients # name: # type: sq_string # elements: 1 # length: 35 GeneralizedLinearModel.Coefficients # name: # type: sq_string # elements: 1 # length: 653 GeneralizedLinearModel: property Coefficients Coefficient values A table with one row per coefficient and four columns: Estimate - estimated coefficient value SE - standard error of the estimate tStat - the estimate divided by its standard error pValue - p-value of that statistic The statistic is referred to the normal distribution where the dispersion is fixed, as it is for the binomial and Poisson families, and to a t -distribution on DFE degrees of freedom where it is estimated. Coefficients dropped as rank deficient have Estimate = 0 , SE = 0 , and NaN for both statistics. Row names are the coefficient names. This property is read-only. # name: # type: sq_string # elements: 1 # length: 18 Coefficient values # name: # type: sq_string # elements: 1 # length: 26 GeneralizedLinearModel.DFE # name: # type: sq_string # elements: 1 # length: 163 GeneralizedLinearModel: property DFE Error degrees of freedom A nonnegative integer, NumObservations less NumEstimatedCoefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Error degrees of freedom # name: # type: sq_string # elements: 1 # length: 31 GeneralizedLinearModel.Deviance # name: # type: sq_string # elements: 1 # length: 348 GeneralizedLinearModel: property Deviance Deviance of the fitted model A nonnegative scalar, twice the difference between the log-likelihood of the saturated model and that of this one. It is the generalized linear model’s counterpart of the residual sum of squares, and it is what a nested-model test compares. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Deviance of the fitted model # name: # type: sq_string # elements: 1 # length: 34 GeneralizedLinearModel.Diagnostics # name: # type: sq_string # elements: 1 # length: 400 GeneralizedLinearModel: property Diagnostics Per-observation diagnostics A table with one row per input observation and three columns: Leverage , the diagonal of the weighted hat matrix; CooksDistance , the influence of the observation on every fitted value at once; and HatMatrix , that observation’s row of the hat matrix. Rows not used in the fit contain NaN . This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Per-observation diagnostics # name: # type: sq_string # elements: 1 # length: 33 GeneralizedLinearModel.Dispersion # name: # type: sq_string # elements: 1 # length: 369 GeneralizedLinearModel: property Dispersion Dispersion parameter A positive scalar. It is estimated from the Pearson statistic for the normal, gamma, and inverse Gaussian families, and fixed at 1 for the binomial and Poisson families unless 'DispersionFlag' asked otherwise. CoefficientCovariance and the standard errors are scaled by it. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Dispersion parameter # name: # type: sq_string # elements: 1 # length: 42 GeneralizedLinearModel.DispersionEstimated # name: # type: sq_string # elements: 1 # length: 323 GeneralizedLinearModel: property DispersionEstimated Whether the dispersion was estimated A logical scalar, true where Dispersion was estimated from the data and false where it was held at 1 . It decides whether a coefficient’s statistic is referred to the normal or the t distribution. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Whether the dispersion was estimated # name: # type: sq_string # elements: 1 # length: 35 GeneralizedLinearModel.Distribution # name: # type: sq_string # elements: 1 # length: 393 GeneralizedLinearModel: property Distribution The response distribution A structure with three fields: Name , the distribution’s name; DevianceFunction , a function handle giving the deviance contribution of an observation from its response and mean; and VarianceFunction , a function handle giving the variance of an observation as a function of its mean. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 The response distribution # name: # type: sq_string # elements: 1 # length: 29 GeneralizedLinearModel.Fitted # name: # type: sq_string # elements: 1 # length: 454 GeneralizedLinearModel: property Fitted Fitted values A table with one row per input observation and two columns, Response on the scale of the response and LinearPredictor on the scale of the link. A binomial fit gains a third, Probability , since its Response is a count of successes while the fit works in the proportion. Rows kept out of the fit by 'Exclude' still carry a prediction; rows dropped as missing carry NaN . This property is read-only. # name: # type: sq_string # elements: 1 # length: 13 Fitted values # name: # type: sq_string # elements: 1 # length: 30 GeneralizedLinearModel.Formula # name: # type: sq_string # elements: 1 # length: 408 GeneralizedLinearModel: property Formula The model formula A LinearFormula object describing the fitted model, with properties including ResponseName , LinearPredictor , PredictorNames , TermNames , Terms , HasIntercept , and Link . Its terms are expressed over the model’s variables, so a categorical predictor contributes one term however many indicators it expands to. This property is read-only. # name: # type: sq_string # elements: 1 # length: 17 The model formula # name: # type: sq_string # elements: 1 # length: 45 GeneralizedLinearModel.GeneralizedLinearModel # name: # type: sq_string # elements: 1 # length: 280 GeneralizedLinearModel: mdl = GeneralizedLinearModel ( data , resp , modelspec ) GeneralizedLinearModel: mdl = GeneralizedLinearModel (…, Name , Value ) Fit a generalized linear model. Prefer the fitglm function, which documents the accepted inputs and Name / Value pairs. # name: # type: sq_string # elements: 1 # length: 31 Fit a generalized linear model. # name: # type: sq_string # elements: 1 # length: 40 GeneralizedLinearModel.LikelihoodPenalty # name: # type: sq_string # elements: 1 # length: 261 GeneralizedLinearModel: property LikelihoodPenalty Penalty applied to the likelihood A character vector, always 'none' : no penalized-likelihood fitting is offered, so the coefficients are always the plain maximum-likelihood ones. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Penalty applied to the likelihood # name: # type: sq_string # elements: 1 # length: 27 GeneralizedLinearModel.Link # name: # type: sq_string # elements: 1 # length: 341 GeneralizedLinearModel: property Link The link function A structure with four fields: Name , the link’s name; Link , a function handle mapping the mean to the linear predictor; Derivative , a handle giving that map’s derivative; and Inverse , a handle mapping the linear predictor back to the mean. This property is read-only. # name: # type: sq_string # elements: 1 # length: 17 The link function # name: # type: sq_string # elements: 1 # length: 36 GeneralizedLinearModel.LogLikelihood # name: # type: sq_string # elements: 1 # length: 311 GeneralizedLinearModel: property LogLikelihood Log-likelihood of the fitted model A scalar, the log-likelihood of the observations under the fitted coefficients and the family’s own density. It is what the information criteria and the likelihood-ratio R^2 are computed from. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Log-likelihood of the fitted model # name: # type: sq_string # elements: 1 # length: 37 GeneralizedLinearModel.ModelCriterion # name: # type: sq_string # elements: 1 # length: 329 GeneralizedLinearModel: property ModelCriterion Information criteria A structure with four fields, AIC , AICc , BIC , and CAIC , each penalising LogLikelihood by a different function of the coefficient count and the sample size. AICc is Inf where the correction’s denominator is not positive. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Information criteria # name: # type: sq_string # elements: 1 # length: 38 GeneralizedLinearModel.NumCoefficients # name: # type: sq_string # elements: 1 # length: 274 GeneralizedLinearModel: property NumCoefficients Number of coefficients A positive integer counting every coefficient the model carries, those dropped as rank deficient included. A categorical predictor with L levels contributes L - 1 of them. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of coefficients # name: # type: sq_string # elements: 1 # length: 47 GeneralizedLinearModel.NumEstimatedCoefficients # name: # type: sq_string # elements: 1 # length: 370 GeneralizedLinearModel: property NumEstimatedCoefficients Number of coefficients actually estimated A positive integer counting the coefficients that carry a degree of freedom, which is NumCoefficients less however many were dropped as rank deficient. It is the number the degrees of freedom and the information criteria are computed from. This property is read-only. # name: # type: sq_string # elements: 1 # length: 41 Number of coefficients actually estimated # name: # type: sq_string # elements: 1 # length: 38 GeneralizedLinearModel.NumObservations # name: # type: sq_string # elements: 1 # length: 292 GeneralizedLinearModel: property NumObservations Number of observations used in the fit A positive integer giving the number of observations the fit actually used. Rows holding a missing value and rows named by the 'Exclude' name-value argument are not counted. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Number of observations used in the fit # name: # type: sq_string # elements: 1 # length: 36 GeneralizedLinearModel.NumPredictors # name: # type: sq_string # elements: 1 # length: 312 GeneralizedLinearModel: property NumPredictors Number of predictor variables A nonnegative integer counting the predictors the model was given, whether or not each appears in a term. It counts variables, so a categorical predictor counts once however many indicators it expands to. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Number of predictor variables # name: # type: sq_string # elements: 1 # length: 35 GeneralizedLinearModel.NumVariables # name: # type: sq_string # elements: 1 # length: 239 GeneralizedLinearModel: property NumVariables Number of variables A positive integer, the number of elements of VariableNames : the predictors and the response together, whether or not each appears in a term. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Number of variables # name: # type: sq_string # elements: 1 # length: 38 GeneralizedLinearModel.ObservationInfo # name: # type: sq_string # elements: 1 # length: 373 GeneralizedLinearModel: property ObservationInfo Per-observation status A table with one row per input observation and four columns: Weights , the weight it was given; Excluded , true where 'Exclude' named it; Missing , true where its data are incomplete; and Subset , true where it was used in the fit, which is neither excluded nor missing. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Per-observation status # name: # type: sq_string # elements: 1 # length: 39 GeneralizedLinearModel.ObservationNames # name: # type: sq_string # elements: 1 # length: 213 GeneralizedLinearModel: property ObservationNames Names of the observations A cell array of character vectors, one per input observation, and empty unless the data carried row names. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Names of the observations # name: # type: sq_string # elements: 1 # length: 29 GeneralizedLinearModel.Offset # name: # type: sq_string # elements: 1 # length: 322 GeneralizedLinearModel: property Offset Offset added to the linear predictor A column vector with one element per input observation, added to the linear predictor with a coefficient fixed at one, so that it shifts the fit without being estimated. It is all zeros where no 'Offset' was given. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Offset added to the linear predictor # name: # type: sq_string # elements: 1 # length: 37 GeneralizedLinearModel.PredictorNames # name: # type: sq_string # elements: 1 # length: 266 GeneralizedLinearModel: property PredictorNames Names of the predictor variables A cell array of character vectors naming the predictors in the order the data lists them. A predictor matrix gives them the names 'x1' , 'x2' , and so on. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Names of the predictor variables # name: # type: sq_string # elements: 1 # length: 32 GeneralizedLinearModel.Residuals # name: # type: sq_string # elements: 1 # length: 577 GeneralizedLinearModel: property Residuals Residuals for the fitted model A table with one row per input observation and five columns: Raw - observed minus fitted, on the response scale LinearPredictor - the working residual, on the link scale Pearson - raw residuals divided by the estimated standard deviation of the observation Anscombe - the transform that makes the residuals as nearly normal as the family allows Deviance - the signed square root of each observation’s contribution to Deviance Rows not used in the fit contain NaN . This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 Residuals for the fitted model # name: # type: sq_string # elements: 1 # length: 35 GeneralizedLinearModel.ResponseName # name: # type: sq_string # elements: 1 # length: 263 GeneralizedLinearModel: property ResponseName Name of the response variable A character vector. It is taken from the table column, the 'ResponseVar' or 'VarNames' argument, or the formula, and defaults to 'y' for a predictor matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Name of the response variable # name: # type: sq_string # elements: 1 # length: 31 GeneralizedLinearModel.Rsquared # name: # type: sq_string # elements: 1 # length: 471 GeneralizedLinearModel: property Rsquared Measures of fit A structure with five fields: Ordinary and Adjusted , computed from the sums of squares on the response scale; Deviance , one less the ratio of the model’s deviance to the null model’s; LLR , the same ratio taken over log-likelihoods; and AdjGeneralized , the Nagelkerke measure, which rescales the generalized R^2 by its own attainable maximum so that it can reach one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Measures of fit # name: # type: sq_string # elements: 1 # length: 26 GeneralizedLinearModel.SSE # name: # type: sq_string # elements: 1 # length: 291 GeneralizedLinearModel: property SSE Error sum of squares A nonnegative scalar, the weighted sum of squared raw residuals on the response scale. For a generalized linear model this is a descriptive quantity rather than the fitted criterion, which is Deviance . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Error sum of squares # name: # type: sq_string # elements: 1 # length: 26 GeneralizedLinearModel.SSR # name: # type: sq_string # elements: 1 # length: 243 GeneralizedLinearModel: property SSR Regression sum of squares A nonnegative scalar, the weighted sum of squared differences between the fitted values and the weighted mean of the response, on the response scale. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Regression sum of squares # name: # type: sq_string # elements: 1 # length: 26 GeneralizedLinearModel.SST # name: # type: sq_string # elements: 1 # length: 290 GeneralizedLinearModel: property SST Total sum of squares A nonnegative scalar, the weighted sum of squared differences between the response and its weighted mean. Unlike a linear model, a generalized linear model does not in general satisfy SST = SSE + SSR . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Total sum of squares # name: # type: sq_string # elements: 1 # length: 28 GeneralizedLinearModel.Steps # name: # type: sq_string # elements: 1 # length: 1266 GeneralizedLinearModel: property Steps Stepwise fitting information A structure recording the term-selection trace, populated whenever the model was fit by stepwiseglm and [] otherwise. It has seven fields: Field Contents Start a LinearFormula for the model the search started from. Lower a LinearFormula for the smallest model considered; its terms are never removed. Upper a LinearFormula for the largest model considered. Criterion the selection criterion, such as 'deviance_chi2' . PEnter the threshold a term must beat to enter, empty unless one was given. PRemove the threshold above which a term leaves, empty unless one was given. History a table with one row per step. History always carries Action ( 'Start' , 'Add' , or 'Remove' ), TermName , Terms (the terms matrix after the step, over the model’s variables), DF (the coefficient count after the step), and delDF (the change in it, negative for a removal). The remaining columns follow the criterion: Deviance , then Chi2Stat or FStat , then PValue under 'Deviance' ; FStat and pValue under 'sse' ; and a single column named AIC or BIC holding the criterion’s value after the step otherwise. The first row is the starting model, named by its right-hand side. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Stepwise fitting information # name: # type: sq_string # elements: 1 # length: 35 GeneralizedLinearModel.VariableInfo # name: # type: sq_string # elements: 1 # length: 394 GeneralizedLinearModel: property VariableInfo Per-variable information A table with one row per variable, named by it, and four columns: Class , the class of the data column; Range , its two-element range or, for a categorical, the list of its levels; InModel , true where the variable appears in a term; and IsCategorical , true where it was coded as indicators. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Per-variable information # name: # type: sq_string # elements: 1 # length: 36 GeneralizedLinearModel.VariableNames # name: # type: sq_string # elements: 1 # length: 238 GeneralizedLinearModel: property VariableNames Names of all the variables A cell array of character vectors naming every variable the model was given, the response included, in the order the data lists them. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Names of all the variables # name: # type: sq_string # elements: 1 # length: 32 GeneralizedLinearModel.Variables # name: # type: sq_string # elements: 1 # length: 309 GeneralizedLinearModel: property Variables The data the model was built from A table holding every variable, the response included, with one row per input observation. A model fitted from a predictor matrix gets a table assembled from it, so this property is a table either way. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 The data the model was built from # name: # type: sq_string # elements: 1 # length: 29 GeneralizedLinearModel.coefCI # name: # type: sq_string # elements: 1 # length: 323 GeneralizedLinearModel: ci = coefCI ( mdl ) GeneralizedLinearModel: ci = coefCI ( mdl , alpha ) Confidence intervals for the coefficient estimates. ci is a k -by-2 matrix of 100 (1 - alpha )% intervals (default alpha = 0.05). The t distribution is used when the dispersion was estimated, the normal distribution otherwise. # name: # type: sq_string # elements: 1 # length: 51 Confidence intervals for the coefficient estimates. # name: # type: sq_string # elements: 1 # length: 31 GeneralizedLinearModel.coefTest # name: # type: sq_string # elements: 1 # length: 426 GeneralizedLinearModel: p = coefTest ( mdl ) GeneralizedLinearModel: [ p , stat , df ] = coefTest ( mdl , H ) Wald test of the linear hypothesis H b = 0 on the coefficients. H is an m -by- k contrast matrix; when omitted it tests that all coefficients except the intercept are zero (the model versus the constant model). Returns the p-value p , and optionally the test statistic stat and its numerator degrees of freedom df . # name: # type: sq_string # elements: 1 # length: 63 Wald test of the linear hypothesis H b = 0 on the coefficients. # name: # type: sq_string # elements: 1 # length: 35 GeneralizedLinearModel.devianceTest # name: # type: sq_string # elements: 1 # length: 365 GeneralizedLinearModel: tbl = devianceTest ( mdl ) Likelihood-ratio (deviance) test of the fitted model against the intercept-only model. Returns a table with the deviance, degrees of freedom, and p-value of each model, the last row giving the chi-square statistic (the drop in deviance) and its p-value. Each row is named by the formula of the model it describes. # name: # type: sq_string # elements: 1 # length: 86 Likelihood-ratio (deviance) test of the fitted model against the intercept-only model. # name: # type: sq_string # elements: 1 # length: 28 GeneralizedLinearModel.feval # name: # type: sq_string # elements: 1 # length: 266 GeneralizedLinearModel: yhat = feval ( mdl , x1 , x2 , …) Predict the response by passing each predictor as a separate argument (a scalar or column vector), returning point predictions on the mean scale. Equivalent to predict ( mdl , [ x1 , x2 , …]) . # name: # type: sq_string # elements: 1 # length: 145 Predict the response by passing each predictor as a separate argument (a scalar or column vector), returning point predictions on the mean scale. # name: # type: sq_string # elements: 1 # length: 32 GeneralizedLinearModel.plotAdded # name: # type: sq_string # elements: 1 # length: 281 GeneralizedLinearModel: h = plotAdded ( mdl , var ) Added-variable (partial-regression) plot for the predictor var (a name or index): the response residuals from the model without var against the residuals of var regressed on the remaining predictors. Returns the graphics handle. # name: # type: sq_string # elements: 1 # length: 199 Added-variable (partial-regression) plot for the predictor var (a name or index): the response residuals from the model without var against the residuals of var regressed on the remaining predictors. # name: # type: sq_string # elements: 1 # length: 43 GeneralizedLinearModel.plotAdjustedResponse # name: # type: sq_string # elements: 1 # length: 292 GeneralizedLinearModel: h = plotAdjustedResponse ( mdl , var ) Adjusted-response plot for the predictor var (a name or index): the fitted mean response as var sweeps its observed range with other predictors held at their means, overlaid on the partial residuals. Returns the graphics handle. # name: # type: sq_string # elements: 1 # length: 199 Adjusted-response plot for the predictor var (a name or index): the fitted mean response as var sweeps its observed range with other predictors held at their means, overlaid on the partial residuals. # name: # type: sq_string # elements: 1 # length: 38 GeneralizedLinearModel.plotDiagnostics # name: # type: sq_string # elements: 1 # length: 287 GeneralizedLinearModel: h = plotDiagnostics ( mdl ) GeneralizedLinearModel: h = plotDiagnostics ( mdl , plottype ) Plot observation diagnostics. plottype is 'leverage' (default) or 'cookd' (Cook’s distance). A reference line marks the usual threshold. Returns the graphics handle. # name: # type: sq_string # elements: 1 # length: 29 Plot observation diagnostics. # name: # type: sq_string # elements: 1 # length: 34 GeneralizedLinearModel.plotEffects # name: # type: sq_string # elements: 1 # length: 241 GeneralizedLinearModel: h = plotEffects ( mdl ) Main-effects plot: for each predictor, the change in the fitted mean response as that predictor sweeps its observed range while the others are held at their means. Returns the graphics handle. # name: # type: sq_string # elements: 1 # length: 163 Main-effects plot: for each predictor, the change in the fitted mean response as that predictor sweeps its observed range while the others are held at their means. # name: # type: sq_string # elements: 1 # length: 36 GeneralizedLinearModel.plotResiduals # name: # type: sq_string # elements: 1 # length: 435 GeneralizedLinearModel: h = plotResiduals ( mdl ) GeneralizedLinearModel: h = plotResiduals ( mdl , plottype ) GeneralizedLinearModel: h = plotResiduals (…, 'ResidualType' , rt ) Plot the model residuals. plottype is one of 'histogram' (default), 'caseorder' , 'fitted' , 'lagged' , or 'probability' . 'ResidualType' picks the residual column ( 'Raw' default, 'Pearson' , 'Deviance' , 'Anscombe' ). Returns the graphics handle. # name: # type: sq_string # elements: 1 # length: 25 Plot the model residuals. # name: # type: sq_string # elements: 1 # length: 30 GeneralizedLinearModel.predict # name: # type: sq_string # elements: 1 # length: 483 GeneralizedLinearModel: yhat = predict ( mdl , Xnew ) GeneralizedLinearModel: [ yhat , yci ] = predict ( mdl , Xnew ) GeneralizedLinearModel: […] = predict (…, Name , Value ) Predict the response of the model mdl at the new predictor data Xnew (a numeric matrix or a table). Predictions are on the mean (response) scale. With two outputs, yci is an m -by-2 matrix of confidence intervals. The 'Alpha' pair sets the confidence level to 100 (1 - Alpha )% (default 0.05). # name: # type: sq_string # elements: 1 # length: 99 Predict the response of the model mdl at the new predictor data Xnew (a numeric matrix or a table). # name: # type: sq_string # elements: 1 # length: 29 GeneralizedLinearModel.random # name: # type: sq_string # elements: 1 # length: 326 GeneralizedLinearModel: ysim = random ( mdl ) GeneralizedLinearModel: ysim = random ( mdl , Xnew ) Simulate responses from the fitted model. With one argument the fitted values are used; otherwise the mean is predicted at the new predictor data Xnew . A random draw from the response distribution about that mean is returned. # name: # type: sq_string # elements: 1 # length: 41 Simulate responses from the fitted model. # name: # type: sq_string # elements: 1 # length: 13 LinearFormula # name: # type: sq_string # elements: 1 # length: 1093 statistics: LinearFormula Model formula of a linear or generalized linear regression. A LinearFormula object describes the terms of a fitted model: which variables the model draws on, how they combine into terms, and how the whole thing reads back as a formula. It is the class of the Formula property of a LinearModel and of a GeneralizedLinearModel , and is normally obtained from a fitted model rather than built directly. The object is defined by its terms matrix and the names of the variables that matrix is written over; every other property is derived from those two. Each row of Terms is one term of the model and each column is one variable, the entry giving the power that variable carries in that term. An all-zero row is the intercept. The response variable occupies a column of its own, which is always zero. Converting the object with char renders the whole formula, response included, as "y ~ 1 + x1 + x2" ; the LinearPredictor property holds the right-hand side on its own. For a generalized linear model the response carries its link function, as in "logit(y) ~ 1 + x1" . # name: # type: sq_string # elements: 1 # length: 59 Model formula of a linear or generalized linear regression. # name: # type: sq_string # elements: 1 # length: 27 LinearFormula.FunctionCalls # name: # type: sq_string # elements: 1 # length: 202 LinearFormula: property FunctionCalls Functions called from within the formula A cell array of character vectors, empty unless the formula applies a function to a variable. This property is read-only. # name: # type: sq_string # elements: 1 # length: 40 Functions called from within the formula # name: # type: sq_string # elements: 1 # length: 26 LinearFormula.HasIntercept # name: # type: sq_string # elements: 1 # length: 162 LinearFormula: property HasIntercept Whether the model carries an intercept A logical scalar, true when Terms holds an all-zero row. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Whether the model carries an intercept # name: # type: sq_string # elements: 1 # length: 21 LinearFormula.InModel # name: # type: sq_string # elements: 1 # length: 248 LinearFormula: property InModel Which variables take part in the model A logical row vector with one element per variable of VariableNames , true where that variable appears in at least one term. The response is false. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Which variables take part in the model # name: # type: sq_string # elements: 1 # length: 27 LinearFormula.LinearFormula # name: # type: sq_string # elements: 1 # length: 1170 LinearFormula: obj = LinearFormula () LinearFormula: obj = LinearFormula ( terms , varnames ) LinearFormula: obj = LinearFormula ( terms , varnames , name , value , …) Create a model formula from a terms matrix. obj = LinearFormula () returns an empty formula. obj = LinearFormula ( terms , varnames ) builds a formula whose terms matrix is terms and whose variables are named by the cell array of character vectors varnames . terms must have one column per element of varnames ; each row is one term and each entry the power its variable carries in that term. An all-zero row is the intercept. The remaining properties are derived from these two arguments, except those given as name - value pairs: name value "ResponseName" A character vector naming the response variable. It must be one of varnames . "Link" The link function applied to the response, as a name, a numeric exponent, or a structure of function handles. It defaults to "identity" . "ModelFun" A function handle computing the linear predictor from the coefficients and the design matrix. "FunctionCalls" A cell array of character vectors naming functions the formula applies to its variables. # name: # type: sq_string # elements: 1 # length: 43 Create a model formula from a terms matrix. # name: # type: sq_string # elements: 1 # length: 29 LinearFormula.LinearPredictor # name: # type: sq_string # elements: 1 # length: 315 LinearFormula: property LinearPredictor Right-hand side of the formula A character vector rendering the model’s terms, such as "1 + x1 + x2" . A pair of variables appearing both on their own and as an interaction is written as a product, so that "x1 + x2 + x1:x2" reads "x1*x2" . This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 Right-hand side of the formula # name: # type: sq_string # elements: 1 # length: 18 LinearFormula.Link # name: # type: sq_string # elements: 1 # length: 250 LinearFormula: property Link Link function applied to the response The link of a generalized linear model, given as its name, its numeric exponent, or a structure of function handles. It is "identity" for a linear model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Link function applied to the response # name: # type: sq_string # elements: 1 # length: 22 LinearFormula.ModelFun # name: # type: sq_string # elements: 1 # length: 173 LinearFormula: property ModelFun Function computing the linear predictor A function handle taking the coefficient vector and the design matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 39 Function computing the linear predictor # name: # type: sq_string # elements: 1 # length: 25 LinearFormula.NPredictors # name: # type: sq_string # elements: 1 # length: 165 LinearFormula: property NPredictors Number of variables the model uses A non-negative integer, the number of true elements of InModel . This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Number of variables the model uses # name: # type: sq_string # elements: 1 # length: 20 LinearFormula.NTerms # name: # type: sq_string # elements: 1 # length: 143 LinearFormula: property NTerms Number of terms in the model A non-negative integer, the number of rows of Terms . This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Number of terms in the model # name: # type: sq_string # elements: 1 # length: 19 LinearFormula.NVars # name: # type: sq_string # elements: 1 # length: 168 LinearFormula: property NVars Number of variables available to the model A non-negative integer, the number of elements of VariableNames . This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Number of variables available to the model # name: # type: sq_string # elements: 1 # length: 28 LinearFormula.PredictorNames # name: # type: sq_string # elements: 1 # length: 223 LinearFormula: property PredictorNames Names of the variables the model actually uses A cell array of character vectors holding those elements of VariableNames that appear in at least one term. This property is read-only. # name: # type: sq_string # elements: 1 # length: 46 Names of the variables the model actually uses # name: # type: sq_string # elements: 1 # length: 26 LinearFormula.ResponseName # name: # type: sq_string # elements: 1 # length: 173 LinearFormula: property ResponseName Name of the response variable A character vector naming the variable on the left-hand side of the formula. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Name of the response variable # name: # type: sq_string # elements: 1 # length: 23 LinearFormula.TermNames # name: # type: sq_string # elements: 1 # length: 476 LinearFormula: property TermNames Name of each term of the model A column cell array of character vectors, one per row of Terms , naming the term over the model’s variables : the intercept is "(Intercept)" , an interaction joins its factors with a colon, and a power is written with a caret. A categorical variable contributes one term under its own name however many indicator columns it expands to, so these are not the coefficient names. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 Name of each term of the model # name: # type: sq_string # elements: 1 # length: 19 LinearFormula.Terms # name: # type: sq_string # elements: 1 # length: 269 LinearFormula: property Terms Terms matrix of the model A numeric matrix with one row per term and one column per variable of VariableNames , each entry giving the power that variable carries in that term. An all-zero row is the intercept. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Terms matrix of the model # name: # type: sq_string # elements: 1 # length: 27 LinearFormula.VariableNames # name: # type: sq_string # elements: 1 # length: 277 LinearFormula: property VariableNames Names of all variables available to the model A cell array of character vectors naming every variable the model was given, whether or not it is used, in the order the data lists them. The response is included. This property is read-only. # name: # type: sq_string # elements: 1 # length: 45 Names of all variables available to the model # name: # type: sq_string # elements: 1 # length: 18 LinearFormula.char # name: # type: sq_string # elements: 1 # length: 270 LinearFormula: str = char ( obj ) Render a model formula as a character vector. str = char ( obj ) returns the whole formula, response included, as in "y ~ 1 + x1 + x2" . The response carries the link function of a generalized linear model, as in "logit(y) ~ 1 + x1" . # name: # type: sq_string # elements: 1 # length: 45 Render a model formula as a character vector. # name: # type: sq_string # elements: 1 # length: 20 LinearFormula.string # name: # type: sq_string # elements: 1 # length: 137 LinearFormula: str = string ( obj ) Render a model formula as a string scalar. str = string ( obj ) is the string counterpart of char . # name: # type: sq_string # elements: 1 # length: 42 Render a model formula as a string scalar. # name: # type: sq_string # elements: 1 # length: 16 LinearMixedModel # name: # type: sq_string # elements: 1 # length: 707 statistics: LinearMixedModel Linear mixed-effects model fitted to data. A LinearMixedModel object represents a fitted linear mixed-effects model $$ y = X\beta + Zb + \varepsilon, $$ with fixed effects beta , random effects b distributed as N(0, Psi) , and independent errors N(0, sigma2) . Objects are created with fitlmematrix (from design matrices). The estimated fixed effects and their statistics are available through the Coefficients table; the covariance parameters through covarianceParameters ; the random-effect BLUPs through randomEffects ; and predictions, residuals, and hypothesis tests through the predict , residuals , anova , coefTest , and coefCI methods. See also: fitlmematrix, fitlm # name: # type: sq_string # elements: 1 # length: 42 Linear mixed-effects model fitted to data. # name: # type: sq_string # elements: 1 # length: 38 LinearMixedModel.CoefficientCovariance # name: # type: sq_string # elements: 1 # length: 263 LinearMixedModel: property CoefficientCovariance Covariance of the fixed-effects estimates A square numeric matrix, one row and column per fixed-effects coefficient, holding the estimated covariance of the estimates in Coefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 41 Covariance of the fixed-effects estimates # name: # type: sq_string # elements: 1 # length: 33 LinearMixedModel.CoefficientNames # name: # type: sq_string # elements: 1 # length: 192 LinearMixedModel: property CoefficientNames Names of the fixed-effects coefficients A cell array of character vectors with one name per fixed-effects coefficient. This property is read-only. # name: # type: sq_string # elements: 1 # length: 39 Names of the fixed-effects coefficients # name: # type: sq_string # elements: 1 # length: 29 LinearMixedModel.Coefficients # name: # type: sq_string # elements: 1 # length: 333 LinearMixedModel: property Coefficients Fixed-effects estimates and their statistics A table with one row per fixed-effects coefficient, its row names taken from CoefficientNames , and the variables Estimate , SE , tStat , DF , pValue , Lower and Upper . Lower and Upper bound a 95% confidence interval. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Fixed-effects estimates and their statistics # name: # type: sq_string # elements: 1 # length: 20 LinearMixedModel.DFE # name: # type: sq_string # elements: 1 # length: 150 LinearMixedModel: property DFE Residual degrees of freedom A nonnegative integer, NumObservations less NumCoefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Residual degrees of freedom # name: # type: sq_string # elements: 1 # length: 26 LinearMixedModel.FitMethod # name: # type: sq_string # elements: 1 # length: 225 LinearMixedModel: property FitMethod Estimation method A character vector, either 'ML' for maximum likelihood or 'REML' for restricted maximum likelihood, naming the method that fitted the model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 17 Estimation method # name: # type: sq_string # elements: 1 # length: 24 LinearMixedModel.Formula # name: # type: sq_string # elements: 1 # length: 195 LinearMixedModel: property Formula Model formula A character vector describing the model. It is empty for a fit built from design matrices, which carries no formula. This property is read-only. # name: # type: sq_string # elements: 1 # length: 13 Model formula # name: # type: sq_string # elements: 1 # length: 33 LinearMixedModel.LinearMixedModel # name: # type: sq_string # elements: 1 # length: 223 LinearMixedModel: lme = LinearMixedModel ( info ) Construct a LinearMixedModel from a fitted-model info struct. This constructor is used internally by fitlmematrix ; call that function rather than the constructor directly. # name: # type: sq_string # elements: 1 # length: 61 Construct a LinearMixedModel from a fitted-model info struct. # name: # type: sq_string # elements: 1 # length: 30 LinearMixedModel.LogLikelihood # name: # type: sq_string # elements: 1 # length: 215 LinearMixedModel: property LogLikelihood Log-likelihood of the fitted model A scalar, the maximised log-likelihood, or the maximised restricted log-likelihood when FitMethod is 'REML' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Log-likelihood of the fitted model # name: # type: sq_string # elements: 1 # length: 20 LinearMixedModel.MSE # name: # type: sq_string # elements: 1 # length: 142 LinearMixedModel: property MSE Residual variance estimate A positive scalar, the estimate of the error variance. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Residual variance estimate # name: # type: sq_string # elements: 1 # length: 31 LinearMixedModel.ModelCriterion # name: # type: sq_string # elements: 1 # length: 298 LinearMixedModel: property ModelCriterion Information criteria A scalar structure with the fields AIC , BIC , LogLikelihood and Deviance . The parameter count behind AIC and BIC holds the fixed-effects coefficients, the covariance parameters and the residual variance. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Information criteria # name: # type: sq_string # elements: 1 # length: 32 LinearMixedModel.NumCoefficients # name: # type: sq_string # elements: 1 # length: 182 LinearMixedModel: property NumCoefficients Number of fixed-effects coefficients A positive integer counting the fixed-effects coefficients of the model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Number of fixed-effects coefficients # name: # type: sq_string # elements: 1 # length: 41 LinearMixedModel.NumEstimatedCoefficients # name: # type: sq_string # elements: 1 # length: 295 LinearMixedModel: property NumEstimatedCoefficients Number of estimated fixed-effects coefficients A positive integer counting the fixed-effects coefficients estimated from the data. Every coefficient the model carries is estimated, so this equals NumCoefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 46 Number of estimated fixed-effects coefficients # name: # type: sq_string # elements: 1 # length: 32 LinearMixedModel.NumObservations # name: # type: sq_string # elements: 1 # length: 158 LinearMixedModel: property NumObservations Number of observations A positive integer counting the observations used for the fit. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 29 LinearMixedModel.ResponseName # name: # type: sq_string # elements: 1 # length: 189 LinearMixedModel: property ResponseName Name of the response variable A character vector naming the response. It is empty for a fit built from design matrices. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Name of the response variable # name: # type: sq_string # elements: 1 # length: 25 LinearMixedModel.Rsquared # name: # type: sq_string # elements: 1 # length: 267 LinearMixedModel: property Rsquared Coefficient of determination A scalar structure with the fields Ordinary and Adjusted . Ordinary is one less the ratio of SSE to SST , and Adjusted corrects that ratio for the error degrees of freedom. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Coefficient of determination # name: # type: sq_string # elements: 1 # length: 20 LinearMixedModel.SSE # name: # type: sq_string # elements: 1 # length: 184 LinearMixedModel: property SSE Error sum of squares A nonnegative scalar, the sum of the squared differences between the response and the conditional fit. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Error sum of squares # name: # type: sq_string # elements: 1 # length: 20 LinearMixedModel.SSR # name: # type: sq_string # elements: 1 # length: 197 LinearMixedModel: property SSR Regression sum of squares A nonnegative scalar, the sum of the squared deviations of the conditional fit about the mean of the response. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Regression sum of squares # name: # type: sq_string # elements: 1 # length: 20 LinearMixedModel.SST # name: # type: sq_string # elements: 1 # length: 118 LinearMixedModel: property SST Total sum of squares A nonnegative scalar, SSE plus SSR . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Total sum of squares # name: # type: sq_string # elements: 1 # length: 22 LinearMixedModel.anova # name: # type: sq_string # elements: 1 # length: 298 LinearMixedModel: tbl = anova ( lme ) LinearMixedModel: tbl = anova ( lme , "DFMethod" , method ) Analysis-of-variance table of F-tests for the fixed-effects terms. Each row tests one coefficient. method selects the denominator degrees of freedom: "Residual" (default, n - p ) or "Satterthwaite" . # name: # type: sq_string # elements: 1 # length: 66 Analysis-of-variance table of F-tests for the fixed-effects terms. # name: # type: sq_string # elements: 1 # length: 23 LinearMixedModel.coefCI # name: # type: sq_string # elements: 1 # length: 242 LinearMixedModel: ci = coefCI ( lme ) LinearMixedModel: ci = coefCI ( lme , alpha ) Confidence intervals for the fixed-effects coefficients at level 1 - alpha (default alpha = 0.05). Row j holds the lower and upper bounds for coefficient j . # name: # type: sq_string # elements: 1 # length: 98 Confidence intervals for the fixed-effects coefficients at level 1 - alpha (default alpha = 0.05). # name: # type: sq_string # elements: 1 # length: 25 LinearMixedModel.coefTest # name: # type: sq_string # elements: 1 # length: 441 LinearMixedModel: p = coefTest ( lme ) LinearMixedModel: p = coefTest ( lme , H ) LinearMixedModel: [ p , F , df1 , df2 ] = coefTest (…) Test the linear hypothesis H*beta = 0 with an F-test. With no H , tests that all non-intercept coefficients are zero (intercept is taken to be the first coefficient). Returns the p-value and, optionally, the F-statistic and its numerator and denominator degrees of freedom (denominator = n - p ). # name: # type: sq_string # elements: 1 # length: 53 Test the linear hypothesis H*beta = 0 with an F-test. # name: # type: sq_string # elements: 1 # length: 37 LinearMixedModel.covarianceParameters # name: # type: sq_string # elements: 1 # length: 193 LinearMixedModel: [ psi , mse ] = covarianceParameters ( lme ) Return the estimated random-effects covariance matrices psi (a cell array, one per grouping term) and the residual variance mse . # name: # type: sq_string # elements: 1 # length: 128 Return the estimated random-effects covariance matrices psi (a cell array, one per grouping term) and the residual variance mse. # name: # type: sq_string # elements: 1 # length: 29 LinearMixedModel.designMatrix # name: # type: sq_string # elements: 1 # length: 183 LinearMixedModel: D = designMatrix ( lme , type ) Return the fixed-effects design matrix ( type = "Fixed" , default) or the expanded random-effects design matrix ( type = "Random" ). # name: # type: sq_string # elements: 1 # length: 128 Return the fixed-effects design matrix (type = "Fixed", default) or the expanded random-effects design matrix (type = "Random"). # name: # type: sq_string # elements: 1 # length: 23 LinearMixedModel.fitted # name: # type: sq_string # elements: 1 # length: 261 LinearMixedModel: yf = fitted ( lme ) LinearMixedModel: yf = fitted ( lme , "Conditional" , tf ) Return the fitted values. With "Conditional" true (the default) the fit includes the random effects ( X*beta + Z*b ); with false it is the marginal fit ( X*beta ). # name: # type: sq_string # elements: 1 # length: 25 Return the fitted values. # name: # type: sq_string # elements: 1 # length: 29 LinearMixedModel.fixedEffects # name: # type: sq_string # elements: 1 # length: 187 LinearMixedModel: beta = fixedEffects ( lme ) LinearMixedModel: [ beta , names ] = fixedEffects ( lme ) Return the estimated fixed-effects coefficients beta and, optionally, their names. # name: # type: sq_string # elements: 1 # length: 82 Return the estimated fixed-effects coefficients beta and, optionally, their names. # name: # type: sq_string # elements: 1 # length: 24 LinearMixedModel.predict # name: # type: sq_string # elements: 1 # length: 598 LinearMixedModel: ypred = predict ( lme , Xnew , Znew , Gnew ) LinearMixedModel: [ ypred , yci ] = predict (…) LinearMixedModel: […] = predict (…, name , value ) Predict the response at new fixed-effects design Xnew , random-effects design Znew , and grouping Gnew . By default the prediction is conditional on the estimated random effects (levels of Gnew not seen in the fit fall back to the marginal prediction). With "Conditional" false the marginal prediction Xnew*beta is returned. The second output yci gives 95% (or "Alpha" ) confidence intervals for the marginal mean. # name: # type: sq_string # elements: 1 # length: 101 Predict the response at new fixed-effects design Xnew, random-effects design Znew, and grouping Gnew. # name: # type: sq_string # elements: 1 # length: 30 LinearMixedModel.randomEffects # name: # type: sq_string # elements: 1 # length: 238 LinearMixedModel: b = randomEffects ( lme ) LinearMixedModel: [ b , names ] = randomEffects ( lme ) Return the best linear unbiased predictors (BLUPs) of the random effects b and, optionally, a cell array of group:level:predictor labels. # name: # type: sq_string # elements: 1 # length: 137 Return the best linear unbiased predictors (BLUPs) of the random effects b and, optionally, a cell array of group:level:predictor labels. # name: # type: sq_string # elements: 1 # length: 26 LinearMixedModel.residuals # name: # type: sq_string # elements: 1 # length: 304 LinearMixedModel: r = residuals ( lme ) LinearMixedModel: r = residuals ( lme , "ResidualType" , type ) Return the conditional residuals y - (X*beta + Z*b) . type is "Raw" (default), "Pearson" (raw divided by sqrt (sigma2) ), or "Standardized" (raw divided by the square root of its estimated variance). # name: # type: sq_string # elements: 1 # length: 52 Return the conditional residuals y - (X*beta + Z*b). # name: # type: sq_string # elements: 1 # length: 11 LinearModel # name: # type: sq_string # elements: 1 # length: 6990 statistics: LinearModel Linear regression model The LinearModel class represents a least-squares (or, optionally, robust) linear regression fit of a response variable to one or more predictor variables. A LinearModel object is returned by the fitlm function and holds everything about the fit in one place: the fitted coefficients, the data and specification used to produce them, and the diagnostics needed to assess the quality of the fit. The properties of a LinearModel object fall into four groups: Group Properties Coefficient estimates Coefficients (a table of estimates, standard errors, t-statistics, and p-values for each term), CoefficientCovariance , CoefficientNames , and the coefficient counts NumCoefficients and NumEstimatedCoefficients . Summary statistics of the fit DFE , Fitted , Residuals (raw, Pearson, Studentized, and standardized), Diagnostics (leverage, Cook’s distance, and other per-observation influence measures), MSE , RMSE , Rsquared (ordinary and adjusted), SSE , SSR , SST , LogLikelihood , ModelCriterion (AIC, BIC, etc.), and ModelFitVsNullModel (the F-test of the fitted model against an intercept-only model). Fitting method information Robust , which records the weighting function and tuning constant used when the model is fit by robust regression, and is empty for an ordinary least squares fit, and Steps , which records the stepwise fitting information whenever the model was fit using stepwise regression, and is currently always empty. Input data properties Formula , NumObservations , NumPredictors , NumVariables , ObservationInfo (which observations were used, excluded, missing, or weighted), ObservationNames , PredictorNames , ResponseName , VariableInfo , VariableNames , and Variables . A categorical predictor expands to indicator columns, one per level bar the reference level, which the intercept carries. When the model has no intercept, the first categorical predictor is given an indicator for every one of its levels instead, so that its coefficients are the group means; any further categorical predictor stays reference coded, which keeps the design full rank. This differs from MATLAB, which omits the reference level whether or not an intercept is present and so cannot fit the reference group at all – for a three-level grouping variable g , MATLAB fits y ~ g - 1 with two coefficients, predicts exactly 0 for every observation in the omitted group, and reports a negative R^2 . This implementation returns three coefficients, one per group. A LinearModel object supports categorical predictors, which are automatically encoded internally as indicator (dummy) variables, observation weights for a weighted least squares fit, excluding specific observations from the fit, and robust regression using iteratively reweighted least squares. Once fitted, the following methods are available on a LinearModel object: Method Description predict Predict responses at new predictor values given in a matrix or table, or reproduce the training fitted values when called with no new data. Can also return pointwise or simultaneous confidence or prediction intervals alongside the point predictions. feval Predict responses given predictors as separate scalar or vector arguments (one per predictor variable) instead of a single matrix, so a LinearModel object can be evaluated the same way as a plain function handle. Returns point predictions only. random Simulate new response values at new predictor locations by adding independent Gaussian noise, drawn from the estimated error variance MSE , to the fitted response. coefCI Return Wald confidence intervals for every fitted coefficient at a chosen significance level (default 0.05 ). coefTest Test a linear hypothesis on the fitted coefficients. With no arguments, tests the overall model F-test that all non-intercept coefficients are zero; a custom hypothesis can be given as a contrast matrix and, if needed, right-hand-side values. Returns the p-value, and optionally the F-statistic and its numerator degrees of freedom. dwtest Durbin-Watson test for first-order autocorrelation among the model residuals, with a choice of exact or approximate p-value computation and a one- or two-sided alternative. addTerms Return a new, refitted LinearModel with terms added to the current model specification, given as a Wilkinson formula fragment or a terms matrix. Weights, excluded rows, and categorical encodings carry over automatically; the original model object is left unmodified. removeTerms Return a new, refitted LinearModel with terms removed from the current model specification, given as a Wilkinson formula fragment or a terms matrix. Weights, excluded rows, and categorical encodings carry over automatically; the original model object is left unmodified. plotResiduals Plot the model residuals. Default is a probability density histogram; other supported plot types are 'fitted' , 'caseorder' , 'lagged' , 'probability' , and 'observed' . plotDiagnostics Plot per-observation influence diagnostics. Default is leverage by observation row number; other supported plot types are 'cookd' , 'covratio' , 'dfbetas' , 'dffits' , 's2_i' , and 'contour' (standardized residuals against leverage with Cook’s distance contours). plotEffects Plot the estimated main effect and 95% confidence interval of each predictor, evaluated between its observed minimum and maximum with all other predictors held at their observed means. plotAdjustedResponse Plot the fitted response against a single predictor, with the other predictors averaged out by averaging the fitted values over the observations used in the fit. plotAdded Plot the incremental effect of one or more terms on the response, after removing the effects of all other terms, along with the fitted line and its 95% confidence bounds. plot Plot a default view of the model. Creates an added variable plot for the whole model when more than one predictor is included, a scatter plot of the data with a fitted curve and 95% confidence bounds when exactly one predictor is included, or a histogram of the residuals when no predictors are included. plotInteraction Plot the main and conditional effects of two predictors, or the adjusted response as a function of one predictor for several fixed values of the other, to visualize whether the two predictors interact. compact Return a CompactLinearModel that discards the training data and per-observation diagnostics while retaining the coefficient estimates and fit statistics needed for prediction and inference. anova Analysis of variance for the fitted model, reporting either the per-term breakdown of sums of squares or a summary table of the model against the total and residual variation. step Improve the fitted model by one or more steps of stepwise term selection, returning a new, refitted LinearModel without modifying the original. Create a LinearModel object by using the fitlm function or the class constructor directly. See also: fitlm # name: # type: sq_string # elements: 1 # length: 23 Linear regression model # name: # type: sq_string # elements: 1 # length: 33 LinearModel.CoefficientCovariance # name: # type: sq_string # elements: 1 # length: 282 LinearModel: property CoefficientCovariance Covariance matrix of coefficient estimates A p -by- p numeric matrix of covariance values for the coefficient estimates, where p is the number of coefficients in the fitted model as given by NumCoefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Covariance matrix of coefficient estimates # name: # type: sq_string # elements: 1 # length: 28 LinearModel.CoefficientNames # name: # type: sq_string # elements: 1 # length: 219 LinearModel: property CoefficientNames Coefficient names A cell array of character vectors, each containing the name of the corresponding model term (e.g., '(Intercept)' , 'x1' , 'x1:x2' ). This property is read-only. # name: # type: sq_string # elements: 1 # length: 17 Coefficient names # name: # type: sq_string # elements: 1 # length: 24 LinearModel.Coefficients # name: # type: sq_string # elements: 1 # length: 409 LinearModel: property Coefficients Coefficient values A table with one row for each coefficient and four columns: Estimate - estimated coefficient value SE - standard error of the estimate tStat - t-statistic for a two-sided test pValue - p-value for the t-statistic Coefficients that are dropped due to rank deficiency have Estimate = 0 , SE = 0 , tStat = NaN , pValue = NaN . This property is read-only. # name: # type: sq_string # elements: 1 # length: 18 Coefficient values # name: # type: sq_string # elements: 1 # length: 15 LinearModel.DFE # name: # type: sq_string # elements: 1 # length: 234 LinearModel: property DFE Degrees of freedom for error A positive integer equal to the number of observations minus the number of estimated coefficients: DFE = NumObservations - NumEstimatedCoefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Degrees of freedom for error # name: # type: sq_string # elements: 1 # length: 23 LinearModel.Diagnostics # name: # type: sq_string # elements: 1 # length: 788 LinearModel: property Diagnostics Observation diagnostics A table with one row per observation and seven columns: Leverage - diagonal of the hat matrix H CooksDistance - Cook’s distance, a measure of scaled change in fitted values Dffits - delete-1 scaled differences in fitted values S2_i - delete-1 residual variance estimate CovRatio - ratio of the determinant of the coefficient covariance matrix with and without each observation Dfbetas - n -by- p matrix of scaled changes in coefficient estimates when each observation is deleted in turn HatMatrix - n -by- n projection matrix such that Fitted = HatMatrix * y Rows not used in fitting have NaN in CooksDistance , Dffits , S2_i , and CovRatio , and zeros in Leverage , Dfbetas , and HatMatrix . This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Observation diagnostics # name: # type: sq_string # elements: 1 # length: 18 LinearModel.Fitted # name: # type: sq_string # elements: 1 # length: 694 LinearModel: property Fitted Fitted response values An n -by-1 numeric vector of predicted response values based on the training data, where n is the total number of observations, excluded and missing rows included. Every observation whose predictors are available carries a fitted value, whether or not it was used in the fit, so an excluded row and a row missing only its response are both fitted; only a row whose predictors are missing is NaN . The corresponding Residuals are NaN for any row not used in the fit, so Fitted and Residuals.Raw do not add back to the response there. Use predict to obtain predictions for new data or to compute confidence bounds. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Fitted response values # name: # type: sq_string # elements: 1 # length: 19 LinearModel.Formula # name: # type: sq_string # elements: 1 # length: 334 LinearModel: property Formula Model formula information A LinearFormula object representing the model formula, with properties including ResponseName , LinearPredictor , PredictorNames , TermNames , HasIntercept , Terms (the terms matrix), and InModel . Converting it with char renders the whole formula. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Model formula information # name: # type: sq_string # elements: 1 # length: 23 LinearModel.LinearModel # name: # type: sq_string # elements: 1 # length: 5394 LinearModel: mdl = LinearModel ( X , y ) LinearModel: mdl = LinearModel ( tbl , resp_input ) LinearModel: mdl = LinearModel (…, modelspec ) LinearModel: mdl = LinearModel (…, Name , Value , …) Create a LinearModel class object representing a linear regression model. mdl = LinearModel ( X , y ) returns a LinearModel object fit to the response y and the predictor data X . Unless removed via the 'Intercept' option, the fitted model contains a constant (intercept) term and one linear term for every column of X . X is an N×P numeric or logical matrix of predictor data, where rows correspond to observations and columns correspond to variables. By default, the predictors are named 'x1' , 'x2' , …, 'xP' . y is an N×1 numeric or logical vector of response values, and must have the same number of observations (rows) as X . By default, the response is named 'y' . mdl = LinearModel ( tbl , resp_input ) fits a model using the variables in the table (or dataset) tbl as predictors. resp_input selects the response and can be a character vector naming a variable in tbl , or a numeric vector the same height as tbl to use as an external response. If resp_input is left empty, the last variable in tbl is used as the response. Variables that are categorical arrays, cell arrays of character vectors, or logical arrays are automatically treated as categorical predictors. mdl = LinearModel (…, modelspec ) additionally specifies the terms of the model to fit. modelspec can be any of the following. Value Description 'constant' Model contains only an intercept term. 'linear' Model contains an intercept and one term for each predictor variable. This is the default when modelspec is not specified. 'interactions' Model contains an intercept, all linear terms, and all pairwise products of distinct predictor variables (no squared terms). 'purequadratic' Model contains an intercept, all linear terms, and all squared terms. 'quadratic' Model contains an intercept, all linear terms, all pairwise products of distinct predictor variables, and all squared terms. 'full' Model contains an intercept and all terms up to and including the full P -way interaction of the predictor variables. terms matrix A T×P or T×(P+1) numeric matrix, where T is the number of terms and P is the number of predictor variables. Each row represents one term, and the value in column j is the exponent to which predictor j is raised in that term; a row of all zeros represents the intercept. If a T×(P+1) matrix is supplied, its last column (representing the response variable) must be all zeros. Wilkinson formula A character vector of the form 'y ~ terms' describing the response and predictor terms using Wilkinson notation. For table input, the variable to the left of '~' is used as the response, overriding resp_input . mdl = LinearModel (…, Name , Value , …) specifies additional options using one or more Name-Value pair arguments as described below. Name Value 'Intercept' A logical scalar indicating whether to include a constant (intercept) term in the model. Default is true . Ignored when modelspec is a Wilkinson formula. 'Weights' A numeric vector of nonnegative observation weights, with one element per observation, used to fit a weighted least squares model. Default is a vector of ones. 'Exclude' A numeric or logical vector specifying observations to exclude from the fit, given as row indices or a logical mask. Excluded observations, together with any observation containing a missing value, are recorded in ObservationInfo but do not contribute to the fit. 'CategoricalVars' Specifies which predictor variables are treated as categorical, given as a vector of column indices, a logical vector, or a cell array of variable names. Each categorical predictor with L categories is expanded into L-1 indicator (dummy) variables, using the first category as the reference level. 'VarNames' A cell array of character vectors naming the predictor and response variables, in order, with the response variable name last. Only applies to matrix input, since table variables already carry their own names. 'ResponseVar' A character vector naming the response variable, used to override the response variable name that would otherwise be used. 'PredictorVars' A cell array of character vectors naming which variables in tbl to use as predictors. By default, all variables other than the response variable are used. 'RobustOpts' Selects ordinary least squares or robust regression fitting. This value can be 'off' (default, ordinary least squares), 'on' (robust fitting using the 'bisquare' weighting function), the name of one of the weighting functions below, a function handle for a custom weighting function, or a scalar structure with fields RobustWgtFun and Tune specifying the weighting function and its tuning constant. Robust fitting uses Iteratively Reweighted Least Squares (IRLS), refitting the model with updated observation weights until the coefficients converge. Supported weighting function names: 'andrews' , 'bisquare' , 'cauchy' , 'fair' , 'huber' , 'logistic' , 'ols' , 'talwar' , 'welsch' , each with its own default tuning constant. mdl is returned as a LinearModel object. If 'RobustOpts' is anything other than 'off' , the returned model is a robust fit rather than an ordinary least squares fit. # name: # type: sq_string # elements: 1 # length: 73 Create a LinearModel class object representing a linear regression model. # name: # type: sq_string # elements: 1 # length: 25 LinearModel.LogLikelihood # name: # type: sq_string # elements: 1 # length: 1613 LinearModel: property LogLikelihood Log-likelihood of the fitted model A scalar numeric value equal to the log-likelihood of the response values, assuming each response is normally distributed with mean equal to the fitted value and variance equal to SSE/n (the MLE variance estimate). This property is read-only. For a weighted fit, observation i is taken to have variance s^2/w_i , so the log-likelihood carries the term 0.5 × sum (log (w)) and n counts only the observations with nonzero weight: logL = -n/2 × (1 + log (2×pi×SSE/n)) + 0.5 × sum (log (w)) This makes the value invariant to the scale of the weights, as it must be: multiplying every weight by a constant rescales the estimated variance by the same constant and leaves the fit unchanged. MATLAB omits the 0.5 × sum (log (w)) term and counts every observation in n , so its LogLikelihood moves by n/2 × log (c) when the weights are multiplied by c , and the ModelCriterion values built on it move with it. This implementation follows R’s logLik.lm instead. Unweighted fits are unaffected, and agree with MATLAB. A robust fit carries no weight term. Its SSE is a robust scale estimate rather than a weighted residual sum, so the two enter separately and the general form is used: logL = -n/2 × log (2×pi×SSE/n) - sum (w .× r.^2) / (2×SSE/n) which is what MATLAB computes, and which reduces to the expression above whenever sum (w .× r.^2) equals SSE , as it does for any least-squares fit. Robust fits therefore agree with MATLAB exactly, weighted or not. # name: # type: sq_string # elements: 1 # length: 34 Log-likelihood of the fitted model # name: # type: sq_string # elements: 1 # length: 15 LinearModel.MSE # name: # type: sq_string # elements: 1 # length: 202 LinearModel: property MSE Mean squared error A scalar numeric value equal to SSE / DFE , where SSE is the sum of squared errors and DFE is the degrees of freedom for error. This property is read-only. # name: # type: sq_string # elements: 1 # length: 18 Mean squared error # name: # type: sq_string # elements: 1 # length: 26 LinearModel.ModelCriterion # name: # type: sq_string # elements: 1 # length: 715 LinearModel: property ModelCriterion Model comparison criteria A structure with four fields: AIC - Akaike information criterion: -2 × logL + 2 × m AICc - AIC corrected for sample size: AIC + (2×m×(m+1))/(n-m-1) BIC - Bayesian information criterion: -2 × logL + m × log(n) CAIC - Consistent AIC: -2 × logL + m × (log(n) + 1) Here logL is LogLikelihood , m is NumEstimatedCoefficients , and n is the number of observations with nonzero weight, which is NumObservations unless some weight is zero. This property is read-only. Because these are built on LogLikelihood , they inherit its treatment of weights; see that property for how it differs from MATLAB’s. # name: # type: sq_string # elements: 1 # length: 25 Model comparison criteria # name: # type: sq_string # elements: 1 # length: 31 LinearModel.ModelFitVsNullModel # name: # type: sq_string # elements: 1 # length: 337 LinearModel: property ModelFitVsNullModel F-test of the fitted model versus the null model A structure with three fields: Fstat - F-statistic of the fitted model versus a null model containing only a constant term Pvalue - p-value for the F-statistic NullModel - character vector describing the null model This property is read-only. # name: # type: sq_string # elements: 1 # length: 48 F-test of the fitted model versus the null model # name: # type: sq_string # elements: 1 # length: 27 LinearModel.NumCoefficients # name: # type: sq_string # elements: 1 # length: 259 LinearModel: property NumCoefficients Number of model coefficients A positive integer giving the total number of coefficients in the fitted model, including any coefficients set to zero because the model terms are rank deficient. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Number of model coefficients # name: # type: sq_string # elements: 1 # length: 36 LinearModel.NumEstimatedCoefficients # name: # type: sq_string # elements: 1 # length: 299 LinearModel: property NumEstimatedCoefficients Number of estimated coefficients A positive integer giving the number of coefficients actually estimated, i.e., not set to zero due to rank deficiency. NumEstimatedCoefficients equals the degrees of freedom for regression. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Number of estimated coefficients # name: # type: sq_string # elements: 1 # length: 27 LinearModel.NumObservations # name: # type: sq_string # elements: 1 # length: 283 LinearModel: property NumObservations Number of observations used in the fit A positive integer giving the number of observations actually used in fitting. Rows with missing values and rows excluded via the 'Exclude' name-value argument are not counted. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Number of observations used in the fit # name: # type: sq_string # elements: 1 # length: 25 LinearModel.NumPredictors # name: # type: sq_string # elements: 1 # length: 178 LinearModel: property NumPredictors Number of predictor variables A positive integer giving the number of predictor variables used to fit the model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Number of predictor variables # name: # type: sq_string # elements: 1 # length: 24 LinearModel.NumVariables # name: # type: sq_string # elements: 1 # length: 236 LinearModel: property NumVariables Number of variables in the input data A positive integer giving the total number of variables in the input data, counting predictors, the response, and any unused columns. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Number of variables in the input data # name: # type: sq_string # elements: 1 # length: 27 LinearModel.ObservationInfo # name: # type: sq_string # elements: 1 # length: 450 LinearModel: property ObservationInfo Per-observation metadata An n -by-4 table where n is the total number of rows in the input data. The four columns are: Weights - observation weight, default is 1 Excluded - logical; true if excluded via the 'Exclude' argument Missing - logical; true if the row contains any NaN value Subset - logical; true if the observation was used in the fit, i.e. not excluded and not missing This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Per-observation metadata # name: # type: sq_string # elements: 1 # length: 28 LinearModel.ObservationNames # name: # type: sq_string # elements: 1 # length: 284 LinearModel: property ObservationNames Observation names A cell array of character vectors containing the names of the observations. If the fit was based on a table that has row names, this property holds those names. Otherwise it is an empty cell array. This property is read-only. # name: # type: sq_string # elements: 1 # length: 17 Observation names # name: # type: sq_string # elements: 1 # length: 26 LinearModel.PredictorNames # name: # type: sq_string # elements: 1 # length: 200 LinearModel: property PredictorNames Names of predictor variables A cell array of character vectors containing the names of the predictor variables used to fit the model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Names of predictor variables # name: # type: sq_string # elements: 1 # length: 16 LinearModel.RMSE # name: # type: sq_string # elements: 1 # length: 124 LinearModel: property RMSE Root mean squared error A scalar numeric value equal to sqrt(MSE) . This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Root mean squared error # name: # type: sq_string # elements: 1 # length: 21 LinearModel.Residuals # name: # type: sq_string # elements: 1 # length: 579 LinearModel: property Residuals Residuals for the fitted model A table with one row per observation and four columns: Raw - observed minus fitted values Pearson - raw residuals divided by RMSE Standardized - internally studentized residuals; raw residuals divided by their estimated standard deviation using the full-model MSE Studentized - externally studentized residuals; each raw residual divided by an estimate of the standard deviation based on all observations except that one, using the delete-1 S2_i Rows not used in the fit contain NaN . This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 Residuals for the fitted model # name: # type: sq_string # elements: 1 # length: 24 LinearModel.ResponseName # name: # type: sq_string # elements: 1 # length: 152 LinearModel: property ResponseName Response variable name A character vector containing the name of the response variable. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 18 LinearModel.Robust # name: # type: sq_string # elements: 1 # length: 431 LinearModel: property Robust Robust fit information A structure with three fields: WgtFun - robust weighting function name, e.g. 'bisquare' Tune - tuning constant; empty if WgtFun is 'ols' or a function handle with the default tuning constant Weights - vector of final iteration weights; empty for a CompactLinearModel object This structure is empty unless the model was fit using robust regression. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Robust fit information # name: # type: sq_string # elements: 1 # length: 20 LinearModel.Rsquared # name: # type: sq_string # elements: 1 # length: 267 LinearModel: property Rsquared R-squared goodness-of-fit statistics A structure with two fields: Ordinary - coefficient of determination: R^2 = SSR / SST Adjusted - adjusted R^2 that accounts for the number of coefficients in the model This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 R-squared goodness-of-fit statistics # name: # type: sq_string # elements: 1 # length: 15 LinearModel.SSE # name: # type: sq_string # elements: 1 # length: 244 LinearModel: property SSE Sum of squared errors A scalar numeric value equal to the sum of squared residuals. For a model with an intercept, SST = SSE + SSR . For weighted fits, this is the weighted sum of squares. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Sum of squared errors # name: # type: sq_string # elements: 1 # length: 15 LinearModel.SSR # name: # type: sq_string # elements: 1 # length: 300 LinearModel: property SSR Regression sum of squares A scalar numeric value equal to the sum of squared deviations of the fitted values from the mean of the response. For a model with an intercept, SST = SSE + SSR . For weighted fits, this is the weighted sum of squares. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Regression sum of squares # name: # type: sq_string # elements: 1 # length: 15 LinearModel.SST # name: # type: sq_string # elements: 1 # length: 349 LinearModel: property SST Total sum of squares A scalar numeric value equal to the sum of squared deviations of the response from its mean. For a model with an intercept, SST = SSE + SSR . For a robust fit, SST = SSE + SSR rather than the deviation from the mean. For weighted fits, this is the weighted sum of squares. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Total sum of squares # name: # type: sq_string # elements: 1 # length: 17 LinearModel.Steps # name: # type: sq_string # elements: 1 # length: 1255 LinearModel: property Steps Stepwise fitting information A structure recording the term-selection trace, populated whenever the model was fit by stepwiselm or improved by step , and [] otherwise. It has seven fields: Field Contents Start a LinearFormula for the model the search started from. Lower a LinearFormula for the smallest model considered; its terms are never removed. Upper a LinearFormula for the largest model considered. Criterion the selection criterion, such as 'SSE' . PEnter the threshold a term must beat to enter. PRemove the threshold above which a term leaves. History a table with one row per step. History carries the columns Action ( 'Start' , 'Add' , or 'Remove' ), TermName , Terms (the terms matrix after the step, over the model’s variables), DF (the coefficient count after the step), and delDF (the change in it, negative for a removal). The remaining columns follow the criterion: FStat and pValue under 'SSE' , and otherwise a single column named for the criterion ( AIC , BIC , Rsquared , or AdjRsquared ) holding its value after the step. The first row is the starting model, named by its right-hand side, and step appends to the history it inherits rather than starting a new one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Stepwise fitting information # name: # type: sq_string # elements: 1 # length: 24 LinearModel.VariableInfo # name: # type: sq_string # elements: 1 # length: 512 LinearModel: property VariableInfo Information about input variables A table with one row per variable including any unused variables, and four columns: Class - variable class as a character vector, e.g. 'double' or 'categorical' Range - for continuous variables, a two-element vector [min, max] ; for categorical variables, a vector of the distinct values InModel - logical; true if the variable is in the fitted model IsCategorical - logical; true if the variable is categorical This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Information about input variables # name: # type: sq_string # elements: 1 # length: 25 LinearModel.VariableNames # name: # type: sq_string # elements: 1 # length: 388 LinearModel: property VariableNames Names of all variables in the input data A cell array of character vectors containing the names of all variables, including predictors, the response, and unused variables. For table input these are the table column names. For matrix input these are the values given by 'VarNames' , defaulting to {'x1','x2',...,'xp','y'} . This property is read-only. # name: # type: sq_string # elements: 1 # length: 40 Names of all variables in the input data # name: # type: sq_string # elements: 1 # length: 21 LinearModel.Variables # name: # type: sq_string # elements: 1 # length: 320 LinearModel: property Variables Input data as a table A table containing predictor and response values for all observations, including unused variables. For table input this is the full input table. For matrix input this is a table constructed from the predictor matrix and response vector. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Input data as a table # name: # type: sq_string # elements: 1 # length: 20 LinearModel.addTerms # name: # type: sq_string # elements: 1 # length: 1858 LinearModel: NewMdl = addTerms ( mdl , terms ) Add terms to a fitted linear regression model. addTerms returns a new LinearModel refitted on the same data and settings as mdl with the specified terms appended to the model formula. The original model mdl is never modified; all settings including observation weights, excluded rows, and categorical variable encodings are carried over automatically. To update a model in place, reassign the result: mdl = addTerms ( mdl , terms ) . terms may be a character vector in Wilkinson notation. Use 'x1' for a main effect, 'x1:x2' for a two-way interaction, 'x1*x2' to add both main effects and their interaction in one step, 'x1 + x2^2' to add several terms at once, or '1' to add an intercept to a no-intercept model. A bare power term 'x1^2' adds x1 together with x1^2 (and any intermediate powers), matching the Wilkinson hierarchy convention; power notation used inside an interaction, e.g. 'x1:x2^2' , adds only that exact interaction term. All variable names must match entries in mdl .PredictorNames . terms may also be a numeric matrix of size t -by- v , where t is the number of terms to add and v equals mdl .NumVariables . Entry T(i,j) is the exponent of variable j in term i . For example, in a model with variables x1 , x2 , y : [0 0 0] is the intercept, [0 1 0] is x2 , [1 1 0] is x1:x2 , and [2 0 0] is x1^2 . The last column (response) is always zero. A matrix with mdl .NumPredictors columns is also accepted and is automatically padded with a trailing zero column for the response. Terms that are already present in mdl are silently skipped. If every specified term already exists, a warning is issued and mdl is returned unchanged. For a categorical predictor, addTerms adds the full group of indicator variables for that predictor in one step rather than adding individual indicator columns. # name: # type: sq_string # elements: 1 # length: 46 Add terms to a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 17 LinearModel.anova # name: # type: sq_string # elements: 1 # length: 2529 LinearModel: tbl = anova ( mdl ) LinearModel: tbl = anova ( mdl , anovatype ) LinearModel: tbl = anova ( mdl , "components" , sstype ) Analysis of variance for a linear regression model. anova ( mdl ) returns a table tbl with component ANOVA statistics for every term in mdl except the constant term, computed with hierarchical ( "h" ) sums of squares. Each row gives SumSq , DF , MeanSq , F , and pValue for the corresponding term; the trailing Error row gives SumSq = mdl .SSE , DF = mdl .DFE , MeanSq = mdl .MSE , and NaN for F and pValue . MATLAB reports F = 1 and pValue = 0.5 on that Error row instead. Those are not results: the row’s F is its own MeanSq divided by itself, so it is 1 for every data set, and the pValue follows. MATLAB does not use them consistently either, reporting NaN for the same quantity on the Residual row of its summary table. This implementation reports NaN in both places. Every other value in both tables agrees with MATLAB. anova ( mdl , anovatype ) selects "components" (default) or "summary" . For "summary" , tbl always contains rows Total , Model , and Residual , and additionally . Linear and . Nonlinear whenever mdl contains an interaction term or a continuous term of degree greater than 1. Total reports mdl .SST with DF = NumObservations - 1 ; Model reports mdl .SSR with DF = NumCoefficients - HasIntercept ; Residual reports mdl .SSE with DF = mdl .DFE . Whenever the data contains two or more observations sharing identical predictor values, tbl additionally contains . Lack of fit and . Pure error , splitting Residual into the part explained by replicated observations and the remainder. anova ( mdl , "components" , sstype ) selects the sum of squares used for the component table: 1 (sequential, reduction from adding each term in formula order), 2 (reduction from adding the term to a model containing every term that does not contain it), "h" (default; as Type 2, but a higher-degree term in the same continuous variable, such as a squared term, is also treated as containing the lower-degree term), or 3 (reduction from adding the term to a model containing every other term, with categorical predictors recoded using sum-to-zero deviation contrasts instead of mdl ’s reference-level coding). Because Type 3 uses a different coding, its Error row can differ from mdl .SSE and mdl .DFE when mdl is missing a lower-order relative of one of its terms (e.g. an interaction without one of its main effects, or a categorical predictor fit without an intercept). # name: # type: sq_string # elements: 1 # length: 51 Analysis of variance for a linear regression model. # name: # type: sq_string # elements: 1 # length: 18 LinearModel.coefCI # name: # type: sq_string # elements: 1 # length: 1316 LinearModel: ci = coefCI ( mdl ) LinearModel: ci = coefCI ( mdl , alpha ) Confidence intervals for the coefficient estimates of a fitted linear regression model. ci = coefCI ( mdl ) returns 95% confidence intervals for every coefficient in mdl using a default significance level of 0.05 . ci = coefCI ( mdl , alpha ) uses the significance level alpha , a scalar in [0, 1] . The resulting intervals have coverage 100(1-\alpha)\% . Setting alpha to 0 produces intervals of infinite width; setting it to 1 collapses each interval to the corresponding point estimate. The output ci is a k -by-2 numeric matrix where k = mdl .NumCoefficients . Row j contains the interval for the j -th coefficient, whose name is stored in mdl .CoefficientNames{j} . Column 1 is the lower bound and column 2 is the upper bound. The midpoint of each interval equals the corresponding point estimate in mdl .Coefficients.Estimate . Intervals use the Wald method: b_j \pm t_{(1-\alpha/2,\,\mathrm{DFE})}\,\mathrm{SE}(b_j) , where b_j is the coefficient estimate, \mathrm{SE}(b_j) is its standard error from mdl .Coefficients.SE , and the critical value is the 1-\alpha/2 quantile of the t -distribution with mdl .DFE degrees of freedom. In rank-deficient models, aliased coefficients have \mathrm{SE} = 0 and their row in ci is [0, 0] . # name: # type: sq_string # elements: 1 # length: 87 Confidence intervals for the coefficient estimates of a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 20 LinearModel.coefTest # name: # type: sq_string # elements: 1 # length: 2022 LinearModel: p = coefTest ( mdl ) LinearModel: p = coefTest ( mdl , H ) LinearModel: p = coefTest ( mdl , H , C ) LinearModel: [ p , F ] = coefTest (…) LinearModel: [ p , F , r ] = coefTest (…) Linear hypothesis test on the coefficients of a fitted linear regression model. coefTest tests whether one or more linear combinations of the fitted coefficients equal specified constants. Each linear combination is encoded as a row of the contrast matrix H , and the right-hand side is given by C . p = coefTest ( mdl ) performs the overall model F-test: it tests the joint null hypothesis that every coefficient except the intercept is zero. The returned p-value matches the F-statistic line printed at the bottom of the model display. p = coefTest ( mdl , H ) tests the null hypothesis H \beta = 0 , where \beta is the full coefficient vector of length k = mdl .NumCoefficients . H must be a full-rank numeric matrix with k columns; each row specifies one linear constraint. To test a single coefficient, use a row vector with a 1 in that coefficient’s position and zeros elsewhere; the resulting F-statistic equals the square of the corresponding t-statistic in mdl .Coefficients . To test a categorical predictor that expands to multiple indicator columns, include one row per indicator in H . p = coefTest ( mdl , H , C ) tests H \beta = C instead of zero. C must be a numeric vector with the same number of elements as rows of H ; both row and column vectors are accepted. The second output F is the value of the F-statistic: F = (H\hat{\beta} - C)^\prime (H V H^\prime)^{-1} (H\hat{\beta} - C) / r , where V is mdl .CoefficientCovariance and r is the number of rows of H . The third output r is that numerator degrees of freedom; the denominator degrees of freedom is mdl .DFE . Under the null hypothesis F follows an F(r, \mathrm{DFE}) distribution and the p-value is the upper-tail probability. When H is rank-deficient but contains no NaN , both p and F are returned as NaN without an error. # name: # type: sq_string # elements: 1 # length: 79 Linear hypothesis test on the coefficients of a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 19 LinearModel.compact # name: # type: sq_string # elements: 1 # length: 1088 LinearModel: cmdl = compact ( mdl ) Create a compact version of a fitted linear regression model. cmdl = compact ( mdl ) returns a CompactLinearModel object that retains the coefficient estimates, coefficient covariance, fit statistics, model formula, and fitting method information of mdl , but discards the training data and everything derived from it. Specifically, the following properties of mdl are not carried over and are unavailable on cmdl : Fitted , Residuals , Diagnostics , ObservationInfo , ObservationNames , Variables , Steps , and ModelFitVsNullModel . If mdl was fit using robust regression, the Robust structure is retained on cmdl except for its Weights field, which is always emptied; RobustWgtFun and Tune are preserved unchanged. A CompactLinearModel object consumes less memory than a LinearModel object and can still be used with predict , feval , random , coefCI , and coefTest , but does not support methods that require the original training data or refitting, such as addTerms , removeTerms , step , and dwtest . See also: LinearModel, CompactLinearModel # name: # type: sq_string # elements: 1 # length: 61 Create a compact version of a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 18 LinearModel.dwtest # name: # type: sq_string # elements: 1 # length: 2164 LinearModel: p = dwtest ( mdl ) LinearModel: p = dwtest ( mdl , method ) LinearModel: p = dwtest ( mdl , method , tail ) LinearModel: [ p , DW ] = dwtest (…) Durbin-Watson test for serial autocorrelation of linear regression residuals. dwtest checks whether the raw residuals of mdl are correlated with their immediate neighbours in observation order, which would violate the independence assumption of ordinary least squares. The null hypothesis is that there is no autocorrelation. A small p-value gives evidence against this and suggests that the residuals are not independent. This test is most meaningful when the observations have a natural ordering, such as a time series. The test is based on the Durbin-Watson statistic DW = \sum_{i=1}^{n-1}(e_{i+1}-e_i)^2 / \sum_{i=1}^{n}e_i^2 , where e_i are the raw residuals of the active (non-excluded) observations. The statistic always lies in [0, 4] : values near 2 indicate no autocorrelation, values well below 2 indicate positive autocorrelation (adjacent residuals tend to have the same sign), and values well above 2 indicate negative autocorrelation (adjacent residuals tend to alternate in sign). method controls how the p-value is computed and defaults to 'exact' . 'exact' uses the eigenvalues of the projected differencing matrix together with Imhof’s numerical integration to obtain a precise p-value; this is slower but accurate for any sample size. 'approximate' uses a normal approximation based on the first two moments of the DW distribution under the null; this is faster and adequate for large samples but less reliable for small ones. The argument is case-insensitive. tail selects the alternative hypothesis and defaults to 'both' . 'right' tests for positive autocorrelation ( DW < 2 ), 'left' tests for negative autocorrelation ( DW > 2 ), and 'both' tests for autocorrelation in either direction. The one-sided p-values always satisfy p_{\mathrm{right}} + p_{\mathrm{left}} = 1 , and the two-sided p-value equals 2\min(p_{\mathrm{right}}, p_{\mathrm{left}}) . The second output DW is the value of the Durbin-Watson statistic itself; it does not depend on method or tail . # name: # type: sq_string # elements: 1 # length: 77 Durbin-Watson test for serial autocorrelation of linear regression residuals. # name: # type: sq_string # elements: 1 # length: 17 LinearModel.feval # name: # type: sq_string # elements: 1 # length: 1352 LinearModel: ypred = feval ( mdl , X ) LinearModel: ypred = feval ( mdl , x1 , x2 , …, xp ) Predict responses of a fitted linear regression model using separate predictor inputs. ypred = feval ( mdl , X ) accepts a single numeric matrix X with one column per predictor in the same order as the training data, or a table whose column names match mdl .PredictorNames . The output is an n -by-1 column vector. Rows that contain NaN in any predictor column are returned as NaN . ypred = feval ( mdl , x1 , x2 , …, xp ) accepts exactly mdl .NumPredictors separate arguments, one per predictor variable. All non-scalar arguments must have the same size; a scalar argument is broadcast to that size automatically. The output shape follows the shape of the non-scalar inputs: column vector inputs give a column vector output, row vector inputs give a row vector output, and all-scalar inputs give a scalar. This form is convenient when predictor data is already stored in separate vectors rather than a combined matrix. feval gives the same numerical predictions as predict but does not support confidence intervals. Use predict when you also need bounds on the response. Because a LinearModel object behaves like a function through feval , it can be passed directly to routines that accept a function handle, such as fminsearch or integral . # name: # type: sq_string # elements: 1 # length: 86 Predict responses of a fitted linear regression model using separate predictor inputs. # name: # type: sq_string # elements: 1 # length: 16 LinearModel.plot # name: # type: sq_string # elements: 1 # length: 1478 LinearModel: plot ( mdl ) LinearModel: plot ( ax , mdl ) LinearModel: h = plot (…) Create a default diagnostic plot for a fitted linear regression model. plot ( mdl ) creates a plot whose type depends on the number of predictors in mdl . If mdl has two or more predictors, plot creates an added variable plot for the whole model except the constant (intercept) term, equivalent to plotAdded ( mdl ) . If mdl has exactly one predictor, plot creates a scatter plot of the data together with the fitted curve and its 95% confidence bounds. If mdl has no predictors, plot creates a histogram of the residuals, equivalent to plotResiduals ( mdl ) . For the single-predictor case, the fitted curve and confidence bounds are computed with predict , evaluated at 100 equally spaced points spanning the observed range of the predictor when the predictor is numeric, or at each level of the predictor when it is categorical. Excluded or missing observations appear as NaN in the data and produce gaps in the plotted points. plot ( ax , mdl ) plots into the axes object ax instead of the current axes returned by gca . h = plot (…) returns a vector of graphics object handles. For the two-or-more-predictor and no-predictor cases, see plotAdded and plotResiduals , respectively, for the meaning of h . For the single-predictor case, h (1), h (2), and h (3) correspond to the data points, the fitted curve, and the 95% confidence bounds of the fitted curve, respectively. # name: # type: sq_string # elements: 1 # length: 70 Create a default diagnostic plot for a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 21 LinearModel.plotAdded # name: # type: sq_string # elements: 1 # length: 2477 LinearModel: plotAdded ( mdl ) LinearModel: plotAdded ( mdl , coef ) LinearModel: plotAdded ( mdl , coef , Name , Value ) LinearModel: plotAdded ( ax , …) LinearModel: h = plotAdded (…) Create an added variable plot for a fitted linear regression model. plotAdded ( mdl ) creates an added variable plot for the whole model mdl except the constant (intercept) term. plotAdded ( mdl , coef ) creates an added variable plot for the coefficients specified by coef . coef is a character vector or string naming a single coefficient in mdl.CoefficientNames , the name of a categorical predictor in mdl.PredictorNames (which selects that predictor’s whole group of indicator coefficients), or a vector of positive integers indexing into mdl.CoefficientNames . An added variable plot, also known as a partial regression leverage plot, illustrates the incremental effect on the response of the selected terms after removing the effects of all other terms. For a single selected predictor x_1 , the response y and x_1 are each fit to all other terms: y_i = g_y (x_{2i}, …, x_{pi}) + r_{yi} , x_{1i} = g_x (x_{2i}, …, x_{pi}) + r_{xi} . The adjusted values are \tilde y_i = \bar y + r_{yi} and \tilde x_{1i} = \bar x_1 + r_{xi} . When coef selects more than one coefficient, the selected columns of the design matrix are combined into a single direction using the unit vector u = \beta / \lVert \beta \rVert , and the added variable plot is created for that combined direction. Excluded or missing observations appear as NaN in the adjusted data and produce gaps in the plotted data points. plotAdded ( mdl , coef , Name , Value ) specifies additional Name-Value arguments applied to the adjusted data points ( h(1) ). The following are accepted: Name Description and default 'Color' Marker color. Default: [0.1490 0.5490 0.8660] . 'Marker' Marker symbol. Default: 'x' . 'MarkerSize' Marker size in points. Default: 6 . 'MarkerEdgeColor' Marker edge color. Default: 'auto' . 'MarkerFaceColor' Marker fill color. Default: 'none' . 'LineWidth' Width of the marker edge in points. Default: 0.5 . plotAdded ( ax , …) plots into the axes object ax instead of the current axes returned by gca . h = plotAdded (…) returns a 3-by-1 vector of line handles. h(1) , h(2) , and h(3) correspond to the adjusted data points, the fitted line, and the 95% confidence bounds of the fitted line, respectively. Name-Value arguments only affect h(1) . # name: # type: sq_string # elements: 1 # length: 67 Create an added variable plot for a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 32 LinearModel.plotAdjustedResponse # name: # type: sq_string # elements: 1 # length: 2335 LinearModel: plotAdjustedResponse ( mdl , var ) LinearModel: plotAdjustedResponse ( mdl , var , Name , Value ) LinearModel: plotAdjustedResponse ( ax , …) LinearModel: h = plotAdjustedResponse (…) Plot the adjusted response of a fitted linear regression model against a single predictor. plotAdjustedResponse ( mdl , var ) creates an adjusted response plot for the predictor var in the linear regression model mdl . var is a character vector or string naming a predictor in mdl.PredictorNames , or a positive integer indexing into mdl.VariableNames . An adjusted response function describes the fitted response as a function of a single predictor, with the other predictors averaged out by averaging the fitted values over the observations used in the fit. For a model y_i = f (x_{1i}, x_{2i}, …, x_{pi}) + r_i , the adjusted response function for x_1 is g (x_1) = (1/n) \sum_{i=1}^n f (x_1, x_{2i}, x_{3i}, …, x_{pi}) , where n is the number of observations used to fit the model. The adjusted response data value for observation i is \tilde y_i = g (x_{1i}) + r_i . For a numeric predictor, the adjusted response function is evaluated on an evenly spaced grid of 100 points spanning the minimum to the maximum observed value of var . For a categorical predictor, the adjusted response function is evaluated at each category level. Excluded or missing observations appear as NaN in the adjusted data and produce gaps in the plotted data points. plotAdjustedResponse ( mdl , var , Name , Value ) specifies additional Name-Value arguments applied to the adjusted data points ( h(1) ). The following are accepted: Name Description and default 'Color' Marker color. Default: [0.1490 0.5490 0.8660] . 'Marker' Marker symbol. Default: 'x' . 'MarkerSize' Marker size in points. Default: 6 . 'MarkerEdgeColor' Marker edge color. Default: 'auto' . 'MarkerFaceColor' Marker fill color. Default: 'none' . 'LineWidth' Width of the marker edge in points. Default: 0.5 . plotAdjustedResponse ( ax , …) plots into the axes object ax instead of the current axes returned by gca . h = plotAdjustedResponse (…) returns a 2-by-1 vector of line handles. h(1) corresponds to the adjusted response data points and h(2) corresponds to the adjusted response function. Name-Value arguments only affect h(1) . # name: # type: sq_string # elements: 1 # length: 90 Plot the adjusted response of a fitted linear regression model against a single predictor. # name: # type: sq_string # elements: 1 # length: 27 LinearModel.plotDiagnostics # name: # type: sq_string # elements: 1 # length: 3831 LinearModel: plotDiagnostics ( mdl ) LinearModel: plotDiagnostics ( mdl , plottype ) LinearModel: plotDiagnostics ( mdl , plottype , Name , Value ) LinearModel: plotDiagnostics ( ax , …) LinearModel: h = plotDiagnostics (…) Plot observation diagnostics of a fitted linear regression model. plotDiagnostics ( mdl ) creates a case-order plot of the leverage of each observation. The x-axis is the observation row number running from 1 to the total number of rows including any excluded rows. A dotted horizontal reference line marks the recommended threshold 2p/n , where p is mdl.NumCoefficients and n is mdl.NumObservations . plotDiagnostics ( mdl , plottype ) creates the diagnostic plot specified by plottype . For all types except "contour" , the x-axis is the row number and covers all rows including excluded ones. Excluded rows produce NaN values in the diagnostic vectors, which appear as natural gaps in the plot with no special handling required. plottype must be one of: 'leverage' (default) Leverage of each observation ( mdl.Diagnostics.Leverage ). One dotted horizontal reference line at 2p/n . Returns two handles: h(1) is the data scatter and h(2) is the reference line. 'cookd' Cook’s distance for each observation ( mdl.Diagnostics.CooksDistance ). One dotted reference line at 3 \times \mathrm{mean(CooksDistance)} , where the mean ignores NaN values. Returns two handles: h(1) data, h(2) reference. 'covratio' Delete-1 ratio of the determinant of the coefficient covariance matrix ( mdl.Diagnostics.CovRatio ). Two dotted reference lines at 1 - 3p/n (lower bound) and 1 + 3p/n (upper bound). Both bounds are stored as a single NaN -separated line object. Returns two handles: h(1) data, h(2) combined reference. 'dfbetas' Delete-1 scaled change in each coefficient estimate ( mdl.Diagnostics.Dfbetas , one column per coefficient). One line object is drawn per coefficient. Two dotted reference lines at \pm 3/\sqrt{n} are stored as a single NaN -separated line object. Returns p+1 handles: h(1) through h(p) are the per-coefficient data lines and h(p+1) is the combined reference. Name-Value arguments are applied to all p data handles. 'dffits' Delete-1 scaled change in the fitted value ( mdl.Diagnostics.Dffits ). Two dotted reference lines at \pm 2\sqrt{p/n} stored as a single NaN -separated line. Returns two handles: h(1) data, h(2) combined reference. 's2_i' Delete-1 variance estimate ( mdl.Diagnostics.S2_i ). One dotted reference line at mdl.MSE . Returns two handles: h(1) data, h(2) reference. 'contour' Standardized residuals on the y-axis against leverage on the x-axis, with Cook’s distance contours overlaid at levels [0.05, 0.10, 0.15, 0.20, 0.25] . The contour surface is computed on a 31-by-30 grid over the range of the active leverage and residual values. Returns two handles: h(1) is the data scatter (a line object) and h(2) is the contour object. plotDiagnostics ( ax , …) targets the axes object ax instead of the current axes returned by gca . h = plotDiagnostics (…) returns a vector of graphics handles. The number of handles depends on plottype as described above. Name-Value arguments are applied to the data handle h(1) , except for "dfbetas" where they are applied to all p coefficient handles. Reference line handles are never affected by Name-Value arguments. Name Description and default 'Color' Marker color for data points. For "dfbetas" this color is applied to all p coefficient line objects. Default: [0.1490 0.5490 0.8660] . 'Marker' Marker symbol. Any symbol accepted by plot is valid. Default: 'x' . 'MarkerSize' Marker size in points. Default: 6 . 'MarkerEdgeColor' Marker edge color. Default: 'auto' . 'MarkerFaceColor' Marker fill color. Default: 'none' . 'LineWidth' Width of the marker edge in points. Default: 0.5 . # name: # type: sq_string # elements: 1 # length: 65 Plot observation diagnostics of a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 23 LinearModel.plotEffects # name: # type: sq_string # elements: 1 # length: 1574 LinearModel: plotEffects ( mdl ) LinearModel: plotEffects ( ax , mdl ) LinearModel: h = plotEffects (…) Plot the main effects of each predictor in a fitted linear regression model. plotEffects ( mdl ) creates a horizontal dot-and-line plot with one row per predictor. Each dot shows the estimated main effect on the response from changing that predictor from its minimum observed value to its maximum observed value, while holding all other predictors fixed at their observed means. A horizontal line through each dot shows the 95% confidence interval for that effect. The main effect for predictor xs is defined as g(x_{s,\max}) - g(x_{s,\min}) , where the adjusted response function g evaluates the model at the specified value of xs with all other predictors set to their observed means. For numeric predictors the sign of the effect can be positive or negative depending on the direction of the relationship. plotEffects ( ax , mdl ) creates the plot in the axes object ax instead of the current axes returned by gca . h = plotEffects (…) returns a vector of p+1 graphics handles where p is the number of predictors. h(1) is the line object containing the effect estimate markers (one circle per predictor, plotted as a single line object with XData of length p and YData = 1:p ). h(j+1) is the confidence interval line for predictor j , with XData = [ci_lo, ci_hi] and YData = [j, j] . The y-axis tick labels follow the format 'varname: min to max' , showing the predictor name and the minimum and maximum observed values used to compute the effect. # name: # type: sq_string # elements: 1 # length: 76 Plot the main effects of each predictor in a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 27 LinearModel.plotInteraction # name: # type: sq_string # elements: 1 # length: 2804 LinearModel: plotInteraction ( mdl , var1 , var2 ) LinearModel: plotInteraction ( mdl , var1 , var2 , ptype ) LinearModel: plotInteraction ( ax , …) LinearModel: h = plotInteraction (…) Plot the interaction effects of two predictors in a fitted linear regression model. plotInteraction ( mdl , var1 , var2 ) creates a plot of the main effects of var1 and var2 together with their conditional effects, with horizontal lines through each effect value indicating its 95% confidence interval. var1 and var2 are each a character vector or string naming a variable in mdl.VariableNames , or a positive integer indexing into mdl.VariableNames ; neither may name the response variable, and they must be different variables. The main effect of a predictor is the change in the adjusted response between the two predictor values that produce the minimum and maximum adjusted response, with the other predictor averaged over its own observed values row by row. For a numeric predictor these two values are its observed minimum and maximum; for a categorical predictor every level is evaluated and the levels producing the minimum and maximum adjusted response are used, so the effect is always nonnegative. The conditional effect of var1 is its effect recomputed with var2 additionally held fixed at each of a small set of conditioning values, and likewise the conditional effect of var2 holds var1 fixed. The conditioning values are the observed minimum, mean of the minimum and maximum, and maximum for a numeric predictor, or every level for a categorical predictor. When the main effect and conditional effect points for a predictor do not align vertically, the model exhibits an interaction between var1 and var2 . plotInteraction ( mdl , var1 , var2 , ptype ) selects the plot type. ptype is 'effects' (default), as described above, or 'predictions' , which instead plots the adjusted response as a function of var2 for each conditioning value of var1 held fixed, evaluated over 101 equally spaced points spanning the observed range of var2 when var2 is numeric, or at each level of var2 when it is categorical. plotInteraction ( ax , …) plots into the axes object ax instead of the current axes returned by gca . h = plotInteraction (…) returns a vector of line handles. When ptype is 'effects' , h(1) is the marker line through the two main effect points, h(2) and h(3) are the confidence interval lines for the main effects of var1 and var2 , and the remaining entries are the conditional effect points and their confidence intervals, tagged 'conditional1' for var1 and 'conditional2' for var2 . The main effect line objects are tagged 'main' . When ptype is 'predictions' , each entry in h corresponds to one adjusted response curve, one per conditioning value of var1 . # name: # type: sq_string # elements: 1 # length: 83 Plot the interaction effects of two predictors in a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 25 LinearModel.plotResiduals # name: # type: sq_string # elements: 1 # length: 4379 LinearModel: plotResiduals ( mdl ) LinearModel: plotResiduals ( mdl , plottype ) LinearModel: plotResiduals ( mdl , plottype , Name , Value ) LinearModel: plotResiduals ( ax , …) LinearModel: h = plotResiduals (…) Plot residuals of a fitted linear regression model. plotResiduals ( mdl ) creates a probability density histogram of the raw residuals. Bin width follows Scott’s rule h = 3.5 \hat\sigma n^{-1/3} and is rounded to a visually clean value. The bar areas sum to 1. plotResiduals ( mdl , plottype ) creates the type of residual plot given by plottype . For all types except "histogram" and "probability" , the full observation vector including excluded rows is passed to the plot. Excluded or missing rows appear as NaN in the plotted data and produce visible gaps. plottype must be one of: 'histogram' (default) Probability density histogram. Only active observations are used. Returns one patch handle. Accepts FaceColor , EdgeColor , FaceAlpha , and LineWidth Name-Value arguments. 'fitted' Residuals on the y-axis against fitted values on the x-axis. A dotted horizontal reference line marks y = 0 . Returns two line handles: h(1) is the data scatter and h(2) is the reference line. 'caseorder' Residuals on the y-axis against observation row number on the x-axis, covering all rows from 1 to n_total . A dotted horizontal reference line marks y = 0 . Returns two line handles: h(1) is the data and h(2) is the reference line. 'lagged' Each residual r(t) on the y-axis against the preceding residual r(t-1) on the x-axis. Two dotted reference lines mark y = 0 and x = 0 . Returns three line handles: h(1) is the scatter, h(2) is the horizontal reference, and h(3) is the vertical reference. 'probability' Normal probability plot of the sorted active residuals produced by normplot . Returns two handles: h(1) is the data line and h(2) is the fitted reference line produced by normplot . Name-Value arguments are not applied for this plot type. 'observed' Observed response values on the y-axis against fitted values on the x-axis. A dotted y = x reference line is drawn through the origin. Vertical segments connect each observed point down to the reference line. Returns three handles: h(1) is the scatter, h(2) is the y = x reference, and h(3) is the vertical segment line (stored as a single NaN -separated line object). 'symmetry' Upper-tail distances from the median plotted against lower-tail distances from the median. Each point (x, y) satisfies x = \mathrm{med} - r_{(i)} and y = r_{(n+1-i)} - \mathrm{med} , using the \lfloor n/2 \rfloor most extreme observations on each side. A perfectly symmetric distribution falls on the dotted y = x reference line. Returns two handles: h(1) is the scatter and h(2) is the reference line. plotResiduals ( ax , …) targets the axes object ax instead of the current axes returned by gca . h = plotResiduals (…) returns a vector of graphics handles. The number of handles depends on plottype as described above. Name-Value arguments are applied to the data handle h(1) only. Reference lines are always drawn with the default style and are not affected by Name-Value arguments. The following Name-Value arguments are accepted. Arguments marked histogram only are passed directly to the patch object and have no effect on other plot types. Arguments marked non-histogram are applied to the scatter marker and have no effect on the histogram. Name Description and default 'ResidualType' Type of residual to plot. One of 'raw' (default), 'pearson' , 'standardized' , or 'studentized' . Case-insensitive. Selects the corresponding column of mdl.Residuals . 'Color' ( non-histogram ) Marker color. Default: [0.1490 0.5490 0.8660] . 'Marker' ( non-histogram ) Marker symbol. Any symbol accepted by plot is valid. Default: 'x' . 'MarkerSize' ( non-histogram ) Marker size in points. Default: 6 . 'MarkerEdgeColor' ( non-histogram ) Marker edge color. Default: 'auto' . 'MarkerFaceColor' ( non-histogram ) Marker fill color. Default: 'none' . 'LineWidth' ( non-histogram ) Width of the marker edge in points. Default: 0.5 . 'FaceColor' ( histogram only ) Fill color of the histogram bars. Default: [0.1490 0.5490 0.8660] . 'EdgeColor' ( histogram only ) Edge color of the histogram bars. 'FaceAlpha' ( histogram only ) Transparency of the histogram bars, specified as a scalar in [0, 1] . # name: # type: sq_string # elements: 1 # length: 51 Plot residuals of a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 19 LinearModel.predict # name: # type: sq_string # elements: 1 # length: 1927 LinearModel: ypred = predict ( mdl , Xnew ) LinearModel: ypred = predict ( mdl ) LinearModel: [ ypred , yci ] = predict ( mdl , Xnew ) LinearModel: [ ypred , yci ] = predict ( mdl , Xnew , Name , Value ) Predict responses from a fitted linear regression model. ypred = predict ( mdl , Xnew ) returns the fitted response values at the new predictor locations in Xnew . Xnew can be a numeric matrix with one column per predictor in the same order as the training data, or a table whose column names match mdl .PredictorNames . Rows containing NaN are returned as NaN without error. ypred = predict ( mdl ) omits Xnew and returns fitted values for the original training observations in their original row order. Rows that were excluded or contained missing values are returned as NaN . The result is identical to mdl .Fitted . [ ypred , yci ] = predict (…) also returns yci , an n -by-2 matrix of confidence bounds where column 1 is the lower bound and column 2 is the upper bound. By default these are 95% pointwise confidence intervals on the mean response. Name-Value pair arguments: Name Value 'Alpha' Significance level for the confidence interval, specified as a scalar in [0,1] . The interval has coverage 100(1-\alpha)\% . Default is 0.05 , giving a 95% interval. 'Prediction' Type of interval to compute. "curve" (default) gives a confidence interval on the mean response f(x) . "observation" gives a wider prediction interval for a single future observation y = f(x) + \varepsilon , which accounts for both estimation uncertainty and irreducible noise; it adds mdl .MSE to the variance before computing the half-width. 'Simultaneous' Logical flag controlling whether the bounds are simultaneous or pointwise. When true , Scheff'{e}’s method is used so the entire predicted curve lies within the band with 100(1-\alpha)\% confidence; these bands are always wider than pointwise ones. Default is false . # name: # type: sq_string # elements: 1 # length: 56 Predict responses from a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 18 LinearModel.random # name: # type: sq_string # elements: 1 # length: 1122 LinearModel: ysim = random ( mdl , Xnew ) Simulate responses with random noise from a fitted linear regression model. ysim = random ( mdl , Xnew ) computes the fitted response at each row of Xnew and then adds independent Gaussian noise to each value. The noise is drawn from N(0, \sigma^2) where \sigma^2 is the estimated error variance mdl .MSE (mean squared error of the fit). The result is a column vector of the same length as the number of rows in Xnew . Xnew is required and must be non-empty. It can be a numeric matrix with one column per predictor in the same order as the training data, or a table whose column names match mdl .PredictorNames . Unlike predict , there is no no-argument form; the predictor locations must always be supplied explicitly. Because the added noise is drawn freshly on every call, two calls with the same Xnew will generally produce different output. To get reproducible results, set the random seed with rand ('state', s) before calling random . For deterministic predictions without noise, use predict or feval . predict also provides confidence intervals on the mean response. # name: # type: sq_string # elements: 1 # length: 75 Simulate responses with random noise from a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 23 LinearModel.removeTerms # name: # type: sq_string # elements: 1 # length: 1909 LinearModel: NewMdl = removeTerms ( mdl , terms ) Remove terms from a fitted linear regression model. removeTerms returns a new LinearModel refitted on the same data and settings as mdl , but with the specified terms dropped from the model formula. The original model mdl is never modified; all settings including observation weights, excluded rows, and categorical variable encodings are carried over automatically. To update a model in place, reassign the result: mdl = removeTerms ( mdl , terms ) . terms may be a character vector in Wilkinson notation. Use 'x2' to remove a main effect, 'x1:x2' to remove an interaction, '1' to remove the intercept, or 'x1 + x2^2' to remove several terms at once. A bare power term 'x1^2' removes x1 together with x1^2 (and any intermediate powers), matching the Wilkinson hierarchy convention; power notation used inside an interaction, e.g. 'x1:x2^2' , removes only that exact interaction term. The star operator 'x1*x2' removes the main effects x1 and x2 together with their interaction x1:x2 in a single call, following the same expansion rule as addTerms . All variable names must match entries in mdl .PredictorNames . terms may also be a numeric matrix of size t -by- v , where t is the number of terms to remove and v equals mdl .NumVariables . Entry T(i,j) is the exponent of variable j in term i . For example, in a model with variables x1 , x2 , y : [0 0 0] is the intercept, [0 1 0] is x2 , [1 1 0] is x1:x2 , and [2 0 0] is x1^2 . A matrix with mdl .NumPredictors columns is also accepted and is automatically padded with a trailing zero column for the response. Terms specified but absent from mdl are silently skipped. A warning is issued and mdl is returned unchanged only when every single specified term is absent from the model. For a categorical predictor, removeTerms removes the full group of indicator variables for that predictor in one step. # name: # type: sq_string # elements: 1 # length: 51 Remove terms from a fitted linear regression model. # name: # type: sq_string # elements: 1 # length: 16 LinearModel.step # name: # type: sq_string # elements: 1 # length: 942 LinearModel: NewMdl = step ( mdl ) LinearModel: NewMdl = step ( mdl , Name , Value ) Improve a fitted linear regression model by one or more steps of stepwise term selection. step examines whether adding or removing a single term from mdl improves the fit, and returns the resulting model as NewMdl . The original model mdl is never modified. Unlike stepwiselm , step performs only one such improvement step by default; pass 'NSteps' to allow more. step accepts the same 'Criterion' , 'PEnter' , 'PRemove' , 'NSteps' , 'Verbose' , 'Lower' , and 'Upper' Name-Value options as stepwiselm , with the same defaults, except 'NSteps' defaults to 1 rather than unlimited. mdl ’s own predictors, weights, excluded observations, and categorical variable settings are carried over automatically as the starting point for the search. step is not available for a model fitted with robust regression. See also: stepwiselm, addTerms, removeTerms # name: # type: sq_string # elements: 1 # length: 89 Improve a fitted linear regression model by one or more steps of stepwise term selection. # name: # type: sq_string # elements: 1 # length: 14 NonLinearModel # name: # type: sq_string # elements: 1 # length: 1837 statistics: mdl = NonLinearModel (…) Nonlinear regression model class. A NonLinearModel object holds a nonlinear regression fitted by fitnlm , together with its coefficients, fit statistics, and methods for inference, prediction, and diagnostics. Construct one with fitnlm , which documents the accepted inputs and Name / Value pairs. The estimated coefficients and their statistics are in the Coefficients table; Rsquared , ModelCriterion , LogLikelihood , RMSE , SSE , SST , and SSR summarize the fit. The methods predict , feval , random , coefCI , coefTest , plotResiduals , plotDiagnostics , and plotSlice operate on the fitted model. Fit statistics The fit statistics follow MATLAB’s conventions. SSE is the residual sum of squares, SST the total sum of squares of the response about its (weighted) mean, and SSR the regression sum of squares of the fitted values about that mean; because the model is nonlinear, SST does not in general equal SSR + SSE . Rsquared.Ordinary is 1 - SSE / SST and Rsquared.Adjusted corrects for the error degrees of freedom. RMSE is sqrt ( MSE ) , and the Gaussian LogLikelihood uses the maximum-likelihood error variance SSE / n . The information criteria in ModelCriterion ( AIC , AICc , BIC , CAIC ) count the p coefficients as the only parameters – the error variance is not counted. coefTest is a Wald test: for a contrast matrix H it forms ( H *b)' * inv ( H * V * H ') * ( H *b) / r with V the coefficient covariance and r the number of rows of H , referred to an F distribution on r and DFE degrees of freedom. The summary printed by disp instead reports an F statistic versus the zero model, formed from the uncorrected regression sum of squares (the sum of the squared fitted values). See also: fitnlm, nlinfit, nlparci, nlpredci, LinearModel, GeneralizedLinearModel # name: # type: sq_string # elements: 1 # length: 33 Nonlinear regression model class. # name: # type: sq_string # elements: 1 # length: 36 NonLinearModel.CoefficientCovariance # name: # type: sq_string # elements: 1 # length: 340 NonLinearModel: property CoefficientCovariance Estimated covariance of the coefficients A square numeric matrix, one row and column per coefficient, holding the estimated covariance of the estimates in Coefficients . The square roots of its diagonal are the standard errors reported in column SE of that table. This property is read-only. # name: # type: sq_string # elements: 1 # length: 40 Estimated covariance of the coefficients # name: # type: sq_string # elements: 1 # length: 31 NonLinearModel.CoefficientNames # name: # type: sq_string # elements: 1 # length: 295 NonLinearModel: property CoefficientNames Names of the coefficients A cell array of character vectors with one name per coefficient, in the order the model function expects them. The names default to 'b1' , 'b2' and so on, unless fitnlm was given a list of its own. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Names of the coefficients # name: # type: sq_string # elements: 1 # length: 27 NonLinearModel.Coefficients # name: # type: sq_string # elements: 1 # length: 381 NonLinearModel: property Coefficients Coefficient estimates and their statistics A table with one row per coefficient, its row names taken from CoefficientNames , and the variables Estimate , SE , tStat and pValue . tStat is the estimate divided by its standard error and pValue is the two-sided t test of a zero coefficient on DFE degrees of freedom. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Coefficient estimates and their statistics # name: # type: sq_string # elements: 1 # length: 18 NonLinearModel.DFE # name: # type: sq_string # elements: 1 # length: 145 NonLinearModel: property DFE Error degrees of freedom A nonnegative integer, NumObservations less NumCoefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Error degrees of freedom # name: # type: sq_string # elements: 1 # length: 21 NonLinearModel.Fitted # name: # type: sq_string # elements: 1 # length: 164 NonLinearModel: property Fitted Fitted response values A numeric column vector with one fitted value per observation used for the fit. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Fitted response values # name: # type: sq_string # elements: 1 # length: 22 NonLinearModel.Formula # name: # type: sq_string # elements: 1 # length: 210 NonLinearModel: property Formula Model formula A character vector showing the fitted model, built from the model function together with the coefficient names and the response name. This property is read-only. # name: # type: sq_string # elements: 1 # length: 13 Model formula # name: # type: sq_string # elements: 1 # length: 28 NonLinearModel.LogLikelihood # name: # type: sq_string # elements: 1 # length: 242 NonLinearModel: property LogLikelihood Log-likelihood of the fitted model A scalar, the Gaussian log-likelihood at the estimates, formed with the maximum-likelihood error variance SSE divided by NumObservations . This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Log-likelihood of the fitted model # name: # type: sq_string # elements: 1 # length: 18 NonLinearModel.MSE # name: # type: sq_string # elements: 1 # length: 145 NonLinearModel: property MSE Mean squared error A positive scalar holding the estimated variance of the error term. This property is read-only. # name: # type: sq_string # elements: 1 # length: 18 Mean squared error # name: # type: sq_string # elements: 1 # length: 29 NonLinearModel.ModelCriterion # name: # type: sq_string # elements: 1 # length: 244 NonLinearModel: property ModelCriterion Information criteria A scalar structure with the fields AIC , AICc , BIC and CAIC . All four count the coefficients as the only parameters; the error variance is not counted. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Information criteria # name: # type: sq_string # elements: 1 # length: 29 NonLinearModel.NonLinearModel # name: # type: sq_string # elements: 1 # length: 257 NonLinearModel: mdl = NonLinearModel ( data , resp , modelfun , beta0 ) NonLinearModel: mdl = NonLinearModel (…, Name , Value ) Fit a nonlinear regression model. Prefer the fitnlm function, which documents the accepted inputs and Name / Value pairs. # name: # type: sq_string # elements: 1 # length: 33 Fit a nonlinear regression model. # name: # type: sq_string # elements: 1 # length: 30 NonLinearModel.NumCoefficients # name: # type: sq_string # elements: 1 # length: 226 NonLinearModel: property NumCoefficients Number of coefficients A positive integer counting the coefficients of the model, which is the number of elements of the starting vector handed to fitnlm . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of coefficients # name: # type: sq_string # elements: 1 # length: 39 NonLinearModel.NumEstimatedCoefficients # name: # type: sq_string # elements: 1 # length: 271 NonLinearModel: property NumEstimatedCoefficients Number of estimated coefficients A positive integer counting the coefficients estimated from the data. A nonlinear fit estimates every coefficient it carries, so this equals NumCoefficients . This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Number of estimated coefficients # name: # type: sq_string # elements: 1 # length: 30 NonLinearModel.NumObservations # name: # type: sq_string # elements: 1 # length: 246 NonLinearModel: property NumObservations Number of observations A positive integer counting the observations used for the fit, after the rows named by 'Exclude' and the rows carrying missing values have been dropped. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 28 NonLinearModel.NumPredictors # name: # type: sq_string # elements: 1 # length: 204 NonLinearModel: property NumPredictors Number of predictors A positive integer counting the predictor variables, that is the columns of the predictor matrix used for the fit. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 29 NonLinearModel.PredictorNames # name: # type: sq_string # elements: 1 # length: 282 NonLinearModel: property PredictorNames Names of the predictor variables A cell array of character vectors with one name per predictor. The names default to 'x1' , 'x2' and so on for a fit from matrices, and are the column names for a fit from a table. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Names of the predictor variables # name: # type: sq_string # elements: 1 # length: 19 NonLinearModel.RMSE # name: # type: sq_string # elements: 1 # length: 127 NonLinearModel: property RMSE Root mean squared error A positive scalar, the square root of MSE . This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Root mean squared error # name: # type: sq_string # elements: 1 # length: 24 NonLinearModel.Residuals # name: # type: sq_string # elements: 1 # length: 213 NonLinearModel: property Residuals Residuals of the fitted model A table with one row per observation used for the fit and the variables Raw , Pearson , Standardized and Studentized . This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Residuals of the fitted model # name: # type: sq_string # elements: 1 # length: 27 NonLinearModel.ResponseName # name: # type: sq_string # elements: 1 # length: 243 NonLinearModel: property ResponseName Name of the response variable A character vector naming the response. It defaults to 'y' for a fit from matrices and is the name of the response column for a fit from a table. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Name of the response variable # name: # type: sq_string # elements: 1 # length: 21 NonLinearModel.Robust # name: # type: sq_string # elements: 1 # length: 245 NonLinearModel: property Robust Robust fitting options Empty when the model was fitted by ordinary least squares. When fitnlm was given a robust weight function, a scalar structure whose field RobustWgtFun names it. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Robust fitting options # name: # type: sq_string # elements: 1 # length: 23 NonLinearModel.Rsquared # name: # type: sq_string # elements: 1 # length: 265 NonLinearModel: property Rsquared Coefficient of determination A scalar structure with the fields Ordinary and Adjusted . Ordinary is one less the ratio of SSE to SST , and Adjusted corrects that ratio for the error degrees of freedom. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Coefficient of determination # name: # type: sq_string # elements: 1 # length: 18 NonLinearModel.SSE # name: # type: sq_string # elements: 1 # length: 171 NonLinearModel: property SSE Error sum of squares A nonnegative scalar, the sum of the squared residuals weighted by the observation weights. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Error sum of squares # name: # type: sq_string # elements: 1 # length: 18 NonLinearModel.SSR # name: # type: sq_string # elements: 1 # length: 207 NonLinearModel: property SSR Regression sum of squares A nonnegative scalar, the weighted sum of squared deviations of the fitted values about the weighted mean of the response. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Regression sum of squares # name: # type: sq_string # elements: 1 # length: 18 NonLinearModel.SST # name: # type: sq_string # elements: 1 # length: 258 NonLinearModel: property SST Total sum of squares A nonnegative scalar, the weighted sum of squared deviations of the response about its weighted mean. Because the model is nonlinear, SST does not in general equal SSR plus SSE . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Total sum of squares # name: # type: sq_string # elements: 1 # length: 28 NonLinearModel.VariableNames # name: # type: sq_string # elements: 1 # length: 175 NonLinearModel: property VariableNames Names of all variables A cell array of character vectors holding PredictorNames followed by ResponseName . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Names of all variables # name: # type: sq_string # elements: 1 # length: 21 NonLinearModel.coefCI # name: # type: sq_string # elements: 1 # length: 173 NonLinearModel: ci = coefCI ( mdl ) NonLinearModel: ci = coefCI ( mdl , alpha ) Confidence intervals for the coefficients at level 100 (1 - alpha )% (default alpha = 0.05). # name: # type: sq_string # elements: 1 # length: 91 Confidence intervals for the coefficients at level 100 (1 - alpha)% (default alpha = 0.05). # name: # type: sq_string # elements: 1 # length: 23 NonLinearModel.coefTest # name: # type: sq_string # elements: 1 # length: 385 NonLinearModel: [ p , F , df ] = coefTest ( mdl ) NonLinearModel: […] = coefTest ( mdl , H ) Wald test of a linear hypothesis on the coefficients. With no H it tests that all coefficients are zero (the model versus the zero model) and returns the p -value p , the F statistic F , and its numerator degrees of freedom df . H is an r -by- p contrast matrix testing H * beta = 0 . # name: # type: sq_string # elements: 1 # length: 53 Wald test of a linear hypothesis on the coefficients. # name: # type: sq_string # elements: 1 # length: 20 NonLinearModel.feval # name: # type: sq_string # elements: 1 # length: 186 NonLinearModel: yhat = feval ( mdl , X ) Evaluate the fitted model at the predictor values X , given either as a single matrix/table or as separate column arguments (one per predictor). # name: # type: sq_string # elements: 1 # length: 143 Evaluate the fitted model at the predictor values X, given either as a single matrix/table or as separate column arguments (one per predictor). # name: # type: sq_string # elements: 1 # length: 30 NonLinearModel.plotDiagnostics # name: # type: sq_string # elements: 1 # length: 190 NonLinearModel: h = plotDiagnostics ( mdl ) NonLinearModel: h = plotDiagnostics ( mdl , plottype ) Plot fit diagnostics. plottype is 'leverage' (default) or 'cookd' (Cook’s distance). # name: # type: sq_string # elements: 1 # length: 21 Plot fit diagnostics. # name: # type: sq_string # elements: 1 # length: 28 NonLinearModel.plotResiduals # name: # type: sq_string # elements: 1 # length: 200 NonLinearModel: h = plotResiduals ( mdl ) NonLinearModel: h = plotResiduals ( mdl , plottype ) Plot the model residuals. plottype is 'histogram' (default), 'fitted' , 'caseorder' , or 'probability' . # name: # type: sq_string # elements: 1 # length: 25 Plot the model residuals. # name: # type: sq_string # elements: 1 # length: 24 NonLinearModel.plotSlice # name: # type: sq_string # elements: 1 # length: 152 NonLinearModel: h = plotSlice ( mdl ) Plot the fitted response as each predictor is varied over its observed range with the others held at their means. # name: # type: sq_string # elements: 1 # length: 113 Plot the fitted response as each predictor is varied over its observed range with the others held at their means. # name: # type: sq_string # elements: 1 # length: 22 NonLinearModel.predict # name: # type: sq_string # elements: 1 # length: 445 NonLinearModel: yhat = predict ( mdl , Xnew ) NonLinearModel: [ yhat , yci ] = predict ( mdl , Xnew ) NonLinearModel: […] = predict (…, Name , Value ) Predict responses of the nonlinear model mdl at the new predictor values Xnew (a numeric matrix or a table). With two outputs it also returns the confidence intervals yci . Accepts 'Alpha' (default 0.05), 'Prediction' ( 'curve' or 'observation' ), and 'Simultaneous' (a logical). # name: # type: sq_string # elements: 1 # length: 108 Predict responses of the nonlinear model mdl at the new predictor values Xnew (a numeric matrix or a table). # name: # type: sq_string # elements: 1 # length: 21 NonLinearModel.random # name: # type: sq_string # elements: 1 # length: 201 NonLinearModel: ysim = random ( mdl , Xnew ) Simulate responses from the fitted model at Xnew (default: the training predictors), adding Gaussian noise with the model’s error standard deviation. # name: # type: sq_string # elements: 1 # length: 149 Simulate responses from the fitted model at Xnew (default: the training predictors), adding Gaussian noise with the model's error standard deviation. # name: # type: sq_string # elements: 1 # length: 8 coxphfit # name: # type: sq_string # elements: 1 # length: 5779 statistics: b = coxphfit ( X , T ) statistics: b = coxphfit ( X , T , name , value , …) statistics: [ b , logl ] = coxphfit (…) statistics: [ b , logl , H ] = coxphfit (…) statistics: [ b , logl , H , stats ] = coxphfit (…) Fit a Cox proportional hazards regression model. b = coxphfit ( X , T ) returns the p -by-1 vector of coefficients b of the Cox model $$ h(x_i, t) = h_0(t)\exp\left(\sum_{j=1}^{p} x_{ij} b_j\right) $$ fitted to the n -by- p matrix of predictors X and the n -by-1 vector of event times T . T may instead be an n -by-2 matrix whose rows give a (start, stop] interval of exposure, the counting process form, in which an observation joins the risk set only after its start time. h_0(t) is the baseline hazard, which is left unspecified: the coefficients are estimated by maximizing the Cox partial likelihood, which does not involve it. X must not contain a column of ones. The model has no constant term, since any constant is absorbed into the baseline hazard. A constant column is detected, reported by a warning, and given a zero coefficient. Rows of X , T or "Frequency" holding NaN are removed before fitting. [ b , logl , H , stats ] = coxphfit (…) additionally returns the maximized partial log-likelihood logl , the estimated baseline cumulative hazard H , and a structure stats of coefficient statistics and residuals. H is a two-column matrix whose first column holds the distinct event times and whose second holds the estimated cumulative hazard at those times, evaluated at the predictor values given by "Baseline" . Its first row is the first event time with a cumulative hazard of zero; an observation censored before any event contributes no row. In a stratified model H gains a third column carrying the stratum, the blocks appear in ascending stratum order, and each block leads with its own zero row at its own first event time. A stratum holding no event contributes a single row of NaN with its label. The following name / value pairs are accepted: Name Value "Baseline" The X values at which the baseline hazard is computed, either a scalar or a 1-by- p vector. The default is the mean of X weighted by "Frequency" and taken within each stratum, so the hazard is that of an average observation of its stratum; pass 0 for a hazard relative to the origin. A value given explicitly is used for every stratum. The coefficients do not depend on this choice, only H does. "Censoring" A logical or 0/1 vector of length n , where 1 marks an observation right-censored at its recorded time. The default is a vector of zeros, so every observation is a recorded event. "Frequency" A vector of length n of non-negative values giving the number of observations each row represents, or a weight. The default is a vector of ones. "Ties" The method of handling tied event times, either "breslow" (default) or "efron" . "B0" The starting value of the iteration, a vector of length p . The default is 0.01 ./ std ( X ) . "Options" A structure of iteration settings, as built by statset ("coxphfit") . The fields used are "MaxIter" , "TolX" and "Display" . "Strata" A vector of length n of stratum labels. Each stratum carries its own baseline hazard and its own risk sets, while the coefficients are shared across all of them. A predictor that does not vary within any stratum cannot be estimated from a stratified fit; it is reported by a warning and held at zero. The fields of stats are: Field Contents "covb" The estimated covariance matrix of b . "beta" The coefficients, as returned in b . "se" The standard errors of the coefficients. "z" The z statistics, b over its standard error. "p" The two-sided p -values of the z statistics. "csres" The Cox-Snell residuals. "devres" The deviance residuals. "martres" The martingale residuals. "schres" The Schoenfeld residuals, NaN for a censored observation. The mean an event is measured against follows "Ties" : under "efron" a tied death is measured against the mean over the sub-risk sets that approximation splits the tie into, so that every tied death at one time shares one mean and the residual does not depend on the order the tie was recorded in. Without a tie the two methods agree. "sschres" The scaled Schoenfeld residuals. "scores" The score residuals. "sscores" The scaled score residuals. "LikelihoodRatioTestP" The p -value of the likelihood ratio test against the model with no predictors. Two documented deviations, both where R2024a disagrees with itself. The martingale residual is defined as the event indicator minus the cumulative hazard the observation actually experienced, so "csres" and "martres" must sum to that indicator. They do here, always. Under "efron" ties MATLAB’s do not: its "martres" comes from a cumulative hazard agreeing neither with its own "csres" nor with the H it returns, and the two sum to 1.0437 and -0.0414 where they must give 1 and 0. In the counting process form MATLAB’s "martres" correctly subtracts the hazard accrued before the observation entered, but its "csres" does not, so the two disagree by exactly that amount for any row whose start time follows an event. Here both account for it, so "csres" differs from MATLAB’s by \Lambda(start) \exp (x'b) and the identity is preserved. The score residuals inherit the first of those two deviations, being an integral against the martingale residual: under "efron" ties they differ from MATLAB’s, whose own do not sum to the score at the maximum, while these sum to zero under both tie methods, weighted by "Frequency" where one is given. Every other output agrees with R2024a to machine precision, across censoring, weights, both tie methods, stratification, and the counting process form. See also: statset, ecdf, fitlm # name: # type: sq_string # elements: 1 # length: 48 Fit a Cox proportional hazards regression model. # name: # type: sq_string # elements: 1 # length: 6 fitcox # name: # type: sq_string # elements: 1 # length: 3126 statistics: mdl = fitcox ( X , T ) statistics: mdl = fitcox ( tbl , respvar ) statistics: mdl = fitcox (…, Name , Value ) Fit a Cox proportional hazards regression model. mdl = fitcox ( X , T ) fits the Cox proportional hazards model $$ h(x_i, t) = h_0(t)\exp\left(\sum_{j=1}^{p} x_{ij} b_j\right) $$ to the n -by- p numeric matrix of predictors X and the n -by-1 vector of event times T , and returns a CoxModel object. T may instead be an n -by-2 matrix whose rows give a (start, stop] interval of exposure, the counting process form, in which an observation joins the risk set only after its start time. h_0(t) is the baseline hazard, which is left unspecified: the coefficients are estimated by maximizing the Cox partial likelihood, which does not involve it. X must not contain a column of ones , the model having no constant term, since any constant is absorbed into that baseline. mdl = fitcox ( tbl , respvar ) takes the data from the table tbl , using the variable named respvar as the response and every other variable as a predictor. A categorical variable is encoded as indicator columns, one per level bar the first, which the baseline hazard carries. The following Name / Value pairs are accepted: Name Value "Baseline" The X values at which the baseline hazard is computed, either a scalar or a 1-by- p vector. The default is the mean of each numeric predictor and zero for each indicator column of a categorical predictor, taken within each stratum. Pass 0 for a hazard relative to the origin. The coefficients do not depend on this choice. "Beta" The starting value of the iteration, a vector of length p . The default is 0.01 ./ std ( X ) . "CategoricalPredictors" The predictors to treat as categorical, given as column indices, a logical vector, or a cell array of predictor names. Table variables of class categorical are detected without this argument. "Censoring" A logical or 0/1 vector of length n , where 1 marks an observation right-censored at its recorded time. The default is a vector of zeros, so every observation is a recorded event. "Frequency" A vector of length n of non-negative values giving the number of observations each row represents, or a weight. The default is a vector of ones. "OptimizationOptions" A structure of iteration settings, as built by statset ("fitcox") . The fields used are "MaxIter" , "TolX" and "Display" . "PredictorNames" A cell array of p predictor names. The default is "X1" , "X2" , and so on, or the table variable names. "Stratification" A vector of length n of stratum labels. Each stratum carries its own baseline hazard and its own risk sets, while the coefficients are shared across all of them. "TieBreakMethod" The method of handling tied event times, either "breslow" (default) or "efron" . fitcox is the object interface to coxphfit , which fits the same model and returns the estimates as plain arrays. The two agree exactly; the object additionally reports the proportional hazards assumption tests and carries the survival , hazardratio , coefci , linhyptest and plotSurvival methods. See also: CoxModel, coxphfit, ecdf, statset # name: # type: sq_string # elements: 1 # length: 48 Fit a Cox proportional hazards regression model. # name: # type: sq_string # elements: 1 # length: 6 fitglm # name: # type: sq_string # elements: 1 # length: 3540 statistics: mdl = fitglm ( X , y ) statistics: mdl = fitglm ( X , y , modelspec ) statistics: mdl = fitglm ( tbl ) statistics: mdl = fitglm ( tbl , modelspec ) statistics: mdl = fitglm (…, Name , Value ) Fit a generalized linear regression model. mdl = fitglm ( X , y ) fits a generalized linear model of the response vector y on the columns of the n -by- p numeric predictor matrix X , and returns a GeneralizedLinearModel object. mdl = fitglm ( tbl ) instead takes the predictors and response from the table tbl (the last column is the response unless overridden). By default the response is 'normal' with an identity link, an intercept is included, and the model is additive in the predictors. For the 'binomial' distribution y holds the number of successes , and the number of trials is given either by the 'BinomialSize' pair or by passing y as an n -by- 2 matrix whose first column holds the successes and whose second holds the trials. The two forms describe the same model; when both are given, the trials supplied with the response are used. A trial count must be a positive integer, while a success count need not be whole. modelspec selects the model terms. It is either a Wilkinson formula string (e.g. 'y ~ x1 + x2*x3' ), a keyword ( 'constant' , 'linear' , 'interactions' , 'purequadratic' , 'quadratic' , or 'full' ), or a terms matrix. The following Name / Value pairs are accepted: Name Value 'Distribution' the response distribution: 'normal' (default), 'binomial' , 'poisson' , 'gamma' , or 'inverse gaussian' . 'Link' the link function. Defaults to the canonical link of the distribution; accepts any link name understood by glmfit or a numeric exponent for a power link. 'Weights' a vector of nonnegative observation weights. 'Offset' a vector added as a fixed term to the linear predictor. 'BinomialSize' for the 'binomial' distribution, the number of trials (a scalar or a per-observation vector); y holds the number of successes. Changed in 1.9.0 : y was previously read as the proportion of successes. Multiply an existing proportion by the trials to keep its meaning. 'Intercept' a logical value (default true ) whether to include an intercept term. 'DispersionFlag' a logical value forcing the dispersion parameter to be estimated ( true ) or held at 1 ( false ). 'CategoricalVars' predictors to treat as categorical (a logical vector, numeric indices, or a cell array of names). 'Exclude' observations to exclude from the fit (a logical vector or numeric indices). 'VarNames' a cell array of p + 1 variable names (predictors followed by the response) for numeric X . 'PredictorVars' , 'ResponseVar' for table input, the predictor and response variable names. A categorical predictor expands to indicator columns, one per level bar the reference level, which the intercept carries. When the model has no intercept, the first categorical predictor is given an indicator for every one of its levels instead, so that its coefficients are the group means; any further categorical predictor stays reference coded, which keeps the design full rank. This differs from MATLAB, which omits the reference level whether or not an intercept is present and so cannot fit the reference group at all – for a three-level grouping variable g , MATLAB fits y ~ g - 1 with two coefficients, predicts exactly 0 for every observation in the omitted group, and reports a negative R^2 . This implementation returns three coefficients, one per group. See also: GeneralizedLinearModel, fitlm, glmfit, glmval, lassoglm # name: # type: sq_string # elements: 1 # length: 42 Fit a generalized linear regression model. # name: # type: sq_string # elements: 1 # length: 7 fitglme # name: # type: sq_string # elements: 1 # length: 1242 statistics: glme = fitglme ( tbl , formula ) statistics: glme = fitglme (…, name , value ) Fit a generalized linear mixed-effects model specified by a formula. fitglme ( tbl , formula ) fits the generalized linear mixed-effects model described by formula to the table tbl and returns a GeneralizedLinearMixedModel object. formula uses the same syntax as fitlme : a response, a fixed-effects part, and one or more random-effects terms ( expr | group ) , for example "y ~ x + (1 | g)" . The model is fitted by penalized quasi-likelihood. The following name / value pairs are accepted: "Distribution" The response distribution: "normal" (default), "binomial" , or "poisson" . "Link" The link function: "identity" , "logit" , or "log" . The default is the canonical link of the chosen distribution. "FitMethod" "MPL" (maximum pseudo-likelihood, the default), "REMPL" (restricted MPL), "Laplace" , or "ApproximateLaplace" . The first two differ in the pseudo-likelihood used for the covariance parameters; the last two report the Laplace-approximated marginal log-likelihood. Only the canonical links and the full (unstructured) random-effects covariance are currently supported. See also: GeneralizedLinearMixedModel, fitlme, fitglm # name: # type: sq_string # elements: 1 # length: 68 Fit a generalized linear mixed-effects model specified by a formula. # name: # type: sq_string # elements: 1 # length: 5 fitlm # name: # type: sq_string # elements: 1 # length: 9616 statistics: mdl = fitlm ( X , y ) statistics: mdl = fitlm ( tbl ) statistics: mdl = fitlm ( tbl , ResponseVarName ) statistics: mdl = fitlm ( tbl , y ) statistics: mdl = fitlm (…, modelspec ) statistics: mdl = fitlm (…, Name , Value , …) Fit a linear regression model to data and return a LinearModel object. The returned object stores the fitted coefficients, their standard errors, t-statistics, and p-values, summary statistics of the fit ( R^2 , RMSE, F-statistic, etc.), and the residuals and diagnostics of the fit, and exposes methods such as predict , plotResiduals , coefTest , addTerms , and removeTerms for further analysis of the fitted model. Basic Syntax mdl = fitlm ( X , y ) fits a linear regression model of the response y to the predictor data X . Unless removed via the 'Intercept' option, the fitted model contains a constant (intercept) term and one linear term for every column of X . X is an N×P numeric or logical matrix of predictor data, where rows correspond to observations and columns correspond to variables. By default, the predictors are named 'x1' , 'x2' , …, 'xP' . X can also be a categorical vector of length N , representing a single categorical predictor. In this case y must be supplied as the next argument, and the predictor is named 'x1' by default. y is an N×1 numeric or logical vector of response values, and must have the same number of observations (rows) as X . By default, the response is named 'y' . mdl = fitlm ( tbl ) fits a linear regression model using the variables contained in the table (or dataset) tbl . By default, the last variable in tbl is used as the response and all other variables are used as predictors. Variables that are categorical arrays, cell arrays of character vectors, or logical arrays are automatically treated as categorical predictors. mdl = fitlm ( tbl , ResponseVarName ) fits a model using the variable named ResponseVarName in tbl as the response, and all remaining variables in tbl as predictors. mdl = fitlm ( tbl , y ) fits a model using the variables in tbl as predictors and the external numeric vector y as the response. y must have height ( tbl ) elements. Model Specification mdl = fitlm (…, modelspec ) additionally specifies the terms of the model to fit, using any of the input combinations shown above. modelspec can be any of the following. Value Description 'constant' Model contains only an intercept term. 'linear' Model contains an intercept and one term for each predictor variable. This is the default when modelspec is not specified. 'interactions' Model contains an intercept, all linear terms, and all pairwise products of distinct predictor variables (no squared terms). 'purequadratic' Model contains an intercept, all linear terms, and all squared terms. 'quadratic' Model contains an intercept, all linear terms, all pairwise products of distinct predictor variables, and all squared terms. 'full' Model contains an intercept and all terms up to and including the full P -way interaction of the predictor variables, i.e. every combination of one or more distinct predictors. terms matrix A T×P or T×(P+1) numeric matrix, where T is the number of terms and P is the number of predictor variables. Each row represents one term, and the value in column j is the exponent to which predictor j is raised in that term; a row of all zeros represents the intercept. If a T×(P+1) matrix is supplied, its last column (representing the response variable) must be all zeros. Wilkinson formula A character vector of the form 'y ~ terms' describing the response and predictor terms using Wilkinson notation. The variable name to the left of '~' is used as the response, overriding any response implied elsewhere in the call. When modelspec is given as a Wilkinson formula, the following operators may be used on its right-hand side to build up terms : Operator Meaning Example + add a term 'x1 + x2' adds x1 and x2 as separate terms - remove a term 'x1*x2 - x1:x2' removes the interaction, leaving only x1 and x2 * cross two terms 'x1*x2' expands to x1 , x2 , x1:x2 : interaction only 'x1:x2' adds only the interaction term between x1 and x2 ^ power / crossing limit 'x^2' adds x and x^2 ; '(x1+x2)^2' expands to x1 , x2 , x1:x2 -1 remove intercept 'x1 + x2 - 1' fits the model without a constant term A formula includes an intercept term by default; append '- 1' to the formula to omit it. For a categorical predictor, fitlm generates the necessary indicator (dummy) variables automatically from the formula, so a formula does not need to be changed when the underlying design matrix changes. Options mdl = fitlm (…, Name , Value , …) specifies additional options using one or more Name-Value pair arguments, which may be combined with modelspec or used on their own. Name Value 'Intercept' A logical scalar indicating whether to include a constant (intercept) term in the model. Default is true . This option only applies when modelspec is a character vector model name (or omitted); it is ignored when modelspec is a terms matrix or a Wilkinson formula, where the intercept is instead controlled by the matrix/formula itself. 'Weights' A numeric vector of nonnegative observation weights, with one element per observation, used to fit a weighted least squares model. Default is a vector of ones, i.e. an unweighted ordinary least squares fit. 'Exclude' A numeric or logical vector specifying observations to exclude from the fit, given as row indices into the original data or as a logical mask the same length as the number of observations. Excluded observations, together with any observation that contains a missing ( NaN ) value in a predictor or the response, are recorded in the ObservationInfo property of the fitted model but do not contribute to the fitted coefficients or summary statistics. 'CategoricalVars' Specifies which predictor variables are treated as categorical, given as a vector of column indices, a logical vector, or a cell array of variable names (only valid for table input). Each categorical predictor with L distinct categories is expanded into L-1 indicator (dummy) variables, using the first category (in sorted or original order) as the reference level that is omitted from the design matrix. Variables that are already categorical arrays or cell arrays of character vectors are always treated as categorical, regardless of this option. 'VarNames' A cell array of character vectors naming the predictor and response variables, listed in order with the response variable name last, e.g. {"x1", "x2", "y"} for two predictors. Only applies when X and y (or a categorical vector and y ) are supplied directly, since table variables already carry their own names. By default, predictors are named 'x1' , 'x2' , etc. and the response is named 'y' . 'ResponseVar' A character vector naming the response variable, used to override the response variable name that would otherwise be inferred (the last table variable, or 'y' for matrix input). 'PredictorVars' A cell array of character vectors naming which variables in tbl to use as predictors. By default, all variables in tbl other than the response variable are used as predictors. 'RobustOpts' Selects ordinary least squares or robust regression fitting. This value can be 'off' (default, ordinary least squares), 'on' (robust fitting using the 'bisquare' weighting function), the name of one of the weighting functions below, a function handle for a custom weighting function, or a scalar structure with fields RobustWgtFun and Tune specifying the weighting function and its tuning constant. Robust fitting uses Iteratively Reweighted Least Squares (IRLS), refitting the model with updated observation weights until the coefficients converge. Supported weighting function names: 'andrews' , 'bisquare' , 'cauchy' , 'fair' , 'huber' , 'logistic' , 'ols' , 'talwar' , 'welsch' , each with its own default tuning constant. Algorithm fitlm solves the (weighted) least squares problem by applying a pivoted QR decomposition to the design matrix, which remains numerically stable even when predictors are collinear; coefficients corresponding to columns beyond the numerically detected rank of the design matrix are set to zero. Robust fits refine this ordinary least squares solution using IRLS as described above. Observations with missing values in any variable used by the model, or explicitly excluded via 'Exclude' , are omitted from the fit entirely and flagged in ObservationInfo , but are otherwise not counted as errors. mdl is returned as a LinearModel object. If 'RobustOpts' is anything other than 'off' , the returned model is a robust fit rather than an ordinary least squares fit, and its Robust property is populated accordingly. A categorical predictor expands to indicator columns, one per level bar the reference level, which the intercept carries. When the model has no intercept, the first categorical predictor is given an indicator for every one of its levels instead, so that its coefficients are the group means; any further categorical predictor stays reference coded, which keeps the design full rank. This differs from MATLAB, which omits the reference level whether or not an intercept is present and so cannot fit the reference group at all – for a three-level grouping variable g , MATLAB fits y ~ g - 1 with two coefficients, predicts exactly 0 for every observation in the omitted group, and reports a negative R^2 . This implementation returns three coefficients, one per group. See also: LinearModel # name: # type: sq_string # elements: 1 # length: 70 Fit a linear regression model to data and return a LinearModel object. # name: # type: sq_string # elements: 1 # length: 6 fitlme # name: # type: sq_string # elements: 1 # length: 1148 statistics: lme = fitlme ( tbl , formula ) statistics: lme = fitlme (…, name , value ) Fit a linear mixed-effects model specified by a formula. fitlme ( tbl , formula ) fits the linear mixed-effects model described by formula to the variables in the table tbl , and returns a LinearMixedModel object. formula is a character vector in Wilkinson notation extended with random-effects terms, for example "y ~ x1 + x2 + (1 | g)" . The part to the left of ~ names the response; the fixed-effects part uses the usual operators ( + , * , : , ^ , and -1 to drop the intercept); and each random-effects term ( expr | group ) adds random intercepts and slopes expr grouped by the factor group (or an interaction of factors, e.g. g1:g2 ). As with fixed effects, a random intercept is implicit unless suppressed with 0 or -1 . Rows of tbl with missing values in any model variable are removed before fitting. The following name / value pairs are accepted: "FitMethod" The estimation criterion, "ML" (maximum likelihood, the default) or "REML" (restricted maximum likelihood). See also: LinearMixedModel, fitlmematrix, fitlm, parseWilkinsonFormula # name: # type: sq_string # elements: 1 # length: 56 Fit a linear mixed-effects model specified by a formula. # name: # type: sq_string # elements: 1 # length: 12 fitlmematrix # name: # type: sq_string # elements: 1 # length: 1877 statistics: lme = fitlmematrix ( X , y , Z , G ) statistics: lme = fitlmematrix (…, name , value ) Fit a linear mixed-effects model from design matrices. fitlmematrix ( X , y , Z , G ) fits the linear mixed-effects model $$ y = X\beta + Zb + \varepsilon $$ with fixed-effects design X , response y , random-effects design Z , and grouping variable G . The random effects b are normally distributed with mean zero and an unstructured covariance Psi (shared across the levels of the grouping variable), and the observation errors are independent N(0, sigma2) . X is an n -by- p numeric matrix and y an n -by-1 response vector. Z is an n -by- q random-effects design and G an n -by-1 grouping variable (numeric, logical, char, cell array of strings, or categorical). To specify several grouping terms, pass Z and G as cell arrays of the same length, one design and one grouping variable per term. The following name / value pairs are accepted: "FitMethod" The estimation criterion, either "ML" (maximum likelihood, the default) or "REML" (restricted maximum likelihood). "FixedEffectPredictors" A cell array of p names for the columns of X (default {"x1", …, "xp"} ). "RandomEffectPredictors" A cell array (one entry per grouping term) of cell arrays naming the columns of each Z (default z1, z2, … ). "RandomEffectGroups" A cell array of names for the grouping terms (default g1, g2 , etc.). The returned lme is a LinearMixedModel object describing the fitted model: the estimated fixed effects and their statistics ( lme.Coefficients ), the covariance parameters ( covarianceParameters ), the random-effects BLUPs ( randomEffects ), the log-likelihood, and methods for prediction, residuals, and hypothesis tests. Only the full (unstructured) random-effects covariance is currently supported. See also: LinearMixedModel, fitlm, parseWilkinsonFormula # name: # type: sq_string # elements: 1 # length: 54 Fit a linear mixed-effects model from design matrices. # name: # type: sq_string # elements: 1 # length: 6 fitnlm # name: # type: sq_string # elements: 1 # length: 1509 statistics: mdl = fitnlm ( X , y , modelfun , beta0 ) statistics: mdl = fitnlm ( tbl , modelfun , beta0 ) statistics: mdl = fitnlm (…, Name , Value ) Fit a nonlinear regression model. mdl = fitnlm ( X , y , modelfun , beta0 ) fits the nonlinear regression model y = modelfun ( beta , X ) to the response vector y and the n -by- p predictor matrix X , starting the iterative fit from the coefficient vector beta0 , and returns a NonLinearModel object. modelfun is a function handle @( b , X ) returning the fitted responses. mdl = fitnlm ( tbl , modelfun , beta0 ) takes the predictors and response from the table tbl ; the last column is the response unless overridden by 'ResponseVar' . The following Name / Value pairs are accepted: Name Value 'CoefficientNames' a cell array of names for the coefficients (default 'b1' , 'b2' , …). 'Weights' a vector of nonnegative observation weights. 'ErrorModel' the error-variance model: 'constant' (default), 'proportional' , or 'combined' . 'RobustWgtFun' the name of a robust weight function, enabling robust fitting (see nlinfit ). 'Options' a statset-style options structure controlling the iterative fit ( MaxIter , TolFun , TolX ). 'PredictorVars' , 'ResponseVar' for table input, the predictor and response variable names. 'VarNames' a cell array of p + 1 variable names (predictors followed by the response) for numeric X . 'Exclude' observations to exclude from the fit. See also: NonLinearModel, nlinfit, nlparci, nlpredci, fitlm, fitglm # name: # type: sq_string # elements: 1 # length: 33 Fit a nonlinear regression model. # name: # type: sq_string # elements: 1 # length: 6 glmfit # name: # type: sq_string # elements: 1 # length: 4208 statistics: b = glmfit ( X , y , distribution ) statistics: b = glmfit ( X , y , distribution , Name , Value ) statistics: [ b , dev ] = glmfit (…) statistics: [ b , dev , stats ] = glmfit (…) Perform generalized linear model fitting. b = glmfit ( X , y , distribution ) returns a vector b of coefficient estimates for a generalized linear regression model of the responses in y on the predictors in X , using the distribution defined in distribution . X is an n×p numeric matrix of predictor variables with n observations and p predictors. y is an n×1 numeric vector of responses for all supported distributions, except for the ’binomial’ distribution in which case y can be either a numeric or logical n×1 vector or an n×2 matrix, where the first column contains the number of successes and the second column contains the number of trials. distribution is a character vector specifying the distribution of the response variable. Supported distributions are 'normal' , 'binomial' , 'poisson' , 'gamma' , and 'inverse gaussian' . b = glmfit (…, Name , Value ) specifies additional options using Name-Value pair arguments. Name Value 'B0' A numeric vector specifying initial values for the coefficient estimates. By default, the initial values are fitted values fitted from the data. 'Constant' A character vector specifying whether to include a constant term in the model. Valid options are "on" (default) and "off" . 'EstDisp' A character vector specifying whether to compute dispersion parameter. Valid options are "on" and "off" . For 'binomial' and 'poisson' distributions the default is "off" , whereas for the 'normal' , 'gamma' , and 'inverse gaussian' distributions the default is "on" . 'link' A character vector specifying the name of a canonical link function or a numeric scalar for specifying a 'power' link function. Supported canonical link functions include 'identity' (default for 'normal' distribution), 'log' (default for 'poisson' distribution), 'logit' (default for 'binomial' distribution), 'probit' , 'loglog' , 'comploglog' , and 'reciprocal' (default for the 'gamma' distribution). The 'power' link function is the default for the 'inverse gaussian' distribution with p = -2 . For custom link functions, the user can provide cell array with three function handles: the link function, its derivative, and its inverse, or alternatively a structure S with three fields: S.Link , S.Derivative , and S.Inverse . Each field can either contain a function handle or a character vector with the name of an existing function. All custom link functions must accept a vector of inputs and return a vector of the same size. 'Offset' A numeric vector of the same length as the response y specifying an offset variable in the fit. It is used as an additional predictor with a coefficient value fixed at 1. 'Options' A scalar structure containing the fields MaxIter and TolX . MaxIter must be a scalar positive integer specifying the maximum number of iteration allowed for fitting the model, and TolX must be a positive scalar value specifying the termination tolerance. 'Weights' An n×1 numeric vector of nonnegative values, where n is the number of observations in X . By default, it is ones (n, 1) . [ b , dev ] = glmfit (…) also returns the deviance of the fit as a numeric value in dev . Deviance is a generalization of the residual sum of squares. It measures the goodness of fit compared to a saturated model. [ b , dev , stats ] = glmfit (…) also returns the structure stats , which contains the model statistics in the following fields: beta - Coefficient estimates b dfe - Degrees of freedom for error sfit - Estimated dispersion parameter s - Theoretical or estimated dispersion parameter estdisp - false when 'EstDisp' is 'off' and true when 'EstDisp' is 'on' covb - Estimated covariance matrix for b se - Vector of standard errors of the coefficient estimates b coeffcorr - Correlation matrix for b t - t statistics for b p - p -values for b resid - Vector of residuals residp - Vector of Pearson residuals residd - Vector of deviance residuals resida - Vector of Anscombe residuals See also: glmval # name: # type: sq_string # elements: 1 # length: 41 Perform generalized linear model fitting. # name: # type: sq_string # elements: 1 # length: 6 glmval # name: # type: sq_string # elements: 1 # length: 1745 statistics: yhat = glmval ( b , X , link ) statistics: [ yhat , y_lo , y_hi ] = glmval ( b , X , link , stats ) statistics: […] = glmval (…, Name , Value ) Predict values for a generalized linear model. yhat = glmval ( b , X , link ) returns the predicted values for the generalized linear model with a vector of coefficient estimates b , a matrix of predictors X , in which each column corresponds to a distinct predictor variable, and a link function link , which can be any of the character vectors, numeric scalar, or custom-defined link functions used as values for the 'link' name-value pair argument in the glmfit function. [ yhat , y_lo , y_hi ] = glmval ( b , X , link , stats ) also returns the 95% confidence intervals for the predicted values according to the model’s statistics contained in the stats structure, which is the output of the glmfit function. By default, the confidence intervals are nonsimultaneous, and apply to the fitted curve instead of new observations. […] = glmval (…, Name , Value ) specifies additional options using Name-Value pair arguments. Name Value 'confidence' A scalar value between 0 and 1 specifying the confidence level for the confidence bounds. 'Constant' A character vector specifying whether to include a constant term in the model. Valid options are "on" (default) and "off" . 'simultaneous' A logical or numeric ( 0 or 1 ) scalar specifying whether the confidence bounds are simultaneous. The default is false , which yields nonsimultaneous (pointwise) bounds. 'size' A numeric scalar or a vector with one value for each row of X specifying the size parameter N for a binomial model. 'BinomialSize' is accepted as an alias for 'size' . See also: glmfit # name: # type: sq_string # elements: 1 # length: 46 Predict values for a generalized linear model. # name: # type: sq_string # elements: 1 # length: 7 invpred # name: # type: sq_string # elements: 1 # length: 1641 statistics: x0 = invpred ( x , y , y0 ) statistics: [ x0 , dxlo , dxup ] = invpred ( x , y , y0 ) statistics: […] = invpred (…, name , value ) Inverse prediction from a simple linear regression. x0 = invpred ( x , y , y0 ) fits the simple linear regression of y on x and returns, for each element of y0 , the value of the predictor at which the fitted line takes that response. x and y must be vectors of real values of the same length; y0 may be of any size and x0 is returned with the same size. Observations where either x or y is NaN are dropped in pairs before the fit. [ x0 , dxlo , dxup ] = invpred (…) also returns the width of a confidence interval on either side of x0 , so that the interval is [ x0 - dxlo , x0 + dxup ] . The bounds follow Fieller’s theorem and are therefore not symmetric about x0 . They are not simultaneous over the elements of y0 , and they need not be finite: when the slope is not significantly different from zero at the requested level the interval is unbounded, and dxlo and dxup are both Inf . […] = invpred (…, name , value ) accepts the following name-value pairs: "alpha" is the significance level of the interval, a scalar strictly between 0 and 1, so that the interval has confidence 100 × (1 - alpha )% . The default is 0.05 . "predopt" selects what the interval covers. With "observation" , the default, it covers a new observation whose response is y0 . With "curve" , it covers the point at which the true regression line takes the value y0 , and is narrower because it carries no new-observation variance. See also: regress, fitlm, polyfit # name: # type: sq_string # elements: 1 # length: 51 Inverse prediction from a simple linear regression. # name: # type: sq_string # elements: 1 # length: 5 lasso # name: # type: sq_string # elements: 1 # length: 2546 statistics: B = lasso ( X , y ) statistics: [ B , FitInfo ] = lasso ( X , y ) statistics: […] = lasso (…, Name , Value ) Lasso and elastic-net regularized least-squares regression. B = lasso ( X , y ) fits a series of regularized linear models of the response y on the predictor matrix X by lasso, over a sequence of values of the regularization parameter Lambda . B is a P×L matrix whose column k holds the coefficient estimates for the k -th Lambda , in ascending order of Lambda . [ B , FitInfo ] = lasso (…) additionally returns a structure FitInfo with fields Intercept , Lambda , Alpha , DF (number of non-zero coefficients), and MSE (mean squared error), one entry per value of Lambda . The following Name-Value pairs are supported: Name Value 'Alpha' The elastic-net mixing parameter in (0, 1] . 1 (default) is the lasso penalty; smaller values add a ridge penalty. 'Lambda' A vector of non-negative regularization parameters. By default a geometric sequence of 'NumLambda' values is used, from the smallest value that drives all coefficients to zero down to 'LambdaRatio' times that value. 'NumLambda' The number of Lambda values in the default sequence (default 100 ). 'LambdaRatio' The ratio of the smallest to the largest Lambda in the default sequence (default 1e-4 , or 1e-2 when the number of observations is below the number of predictors). 'Standardize' Whether to standardize X to zero mean and unit variance before fitting (default true ). Coefficients are always returned on the original scale. 'Weights' A vector of non-negative observation weights. 'RelTol' Convergence tolerance for the coordinate descent (default 1e-4 ). 'MaxIter' Maximum number of coordinate-descent iterations (default 1e5 ). 'DFmax' The maximum number of non-zero coefficients; the default sequence stops once this is exceeded. 'Intercept' Whether to fit a constant term (default true ). 'PredictorNames' A cell array of predictor names, kept in FitInfo . 'CV' The number of folds K for K -fold cross-validation of the mean squared error, or a cvpartition object. 'MCReps' The number of Monte-Carlo repetitions of the cross-validation (default 1 ). When 'CV' is used, FitInfo .MSE is the cross-validated error, plus SE , LambdaMinMSE , IndexMinMSE , Lambda1SE , and Index1SE , which report the Lambda with the lowest error and the largest Lambda within one standard error of it. The fold assignment is random, so these selections are not reproducible without fixing the random seed. See also: ridge, regress, lassoglm # name: # type: sq_string # elements: 1 # length: 59 Lasso and elastic-net regularized least-squares regression. # name: # type: sq_string # elements: 1 # length: 8 lassoglm # name: # type: sq_string # elements: 1 # length: 3315 statistics: B = lassoglm ( X , y ) statistics: B = lassoglm ( X , y , distr ) statistics: B = lassoglm ( X , y , distr , Name , Value ) statistics: [ B , FitInfo ] = lassoglm (…) Lasso and elastic-net regularized generalized linear model regression. B = lassoglm ( X , y , distr ) returns fitted least-squares regression coefficients for a generalized linear model of the response y on the predictor data X , penalized by the lasso (L1) or elastic-net penalty. X is an n -by- p numeric matrix of p predictors at each of n observations, and y is a numeric vector of n responses. distr names the distribution of the response: 'normal' (default), 'binomial' , 'poisson' , 'gamma' , or 'inverse gaussian' . The canonical link function of the chosen distribution is used unless overridden by the 'Link' option. B is a p -by- L matrix, where L is the number of regularization ( 'Lambda' ) values used; column k holds the coefficients for the k -th value of lambda , in order of ascending lambda . [ B , FitInfo ] = lassoglm (…) also returns a structure FitInfo with information about the fitted models: Intercept a 1 -by- L vector of intercept terms Lambda the 1 -by- L vector of lambda values, in ascending order Alpha the elastic-net mixing value used DF the number of nonzero coefficients in each column of B Deviance the deviance of the fitted model at each lambda ; when cross-validation is requested this is instead the cross-validated mean deviance When cross-validation is requested (see 'CV' below), FitInfo additionally contains SE (standard error of the cross-validated deviance), LambdaMinDeviance and IndexMinDeviance (the lambda with minimum cross-validated deviance and its index), and Lambda1SE and Index1SE (the largest lambda within one standard error of that minimum). lassoglm accepts the following Name / Value pairs: Name Value 'Alpha' the elastic-net mixing parameter, a scalar in (0, 1] . 'Alpha' = 1 is the lasso penalty (default); values towards 0 approach ridge regression. 'Lambda' a vector of non-negative lambda values. 'Standardize' a logical value (default true ) specifying whether the predictors are standardized before fitting. 'Weights' a vector of non-negative observation weights. 'Size' for the 'binomial' distribution, the number of trials (a scalar or a per-observation vector); y holds the number of successes. Default is 1 (Bernoulli responses). 'Link' the link function to use instead of the family’s canonical link. Accepts any link name understood by glmfit (e.g. 'log' , 'probit' ) or a numeric exponent for a power link. 'Offset' a numeric vector, one value per observation, added as a fixed term to the linear predictor (not penalized or fitted). 'RelTol' convergence tolerance for the coordinate descent. 'MaxIter' maximum number of iterations. 'DFmax' maximum number of nonzero coefficients. 'Intercept' a logical value (default true ) whether to fit an intercept term. 'PredictorNames' a cell array of predictor names. 'CV' the number of folds K for K -fold cross-validation, or a cvpartition object. The fold assignment is random, so the selected lambda values are not reproducible without a fixed random seed. 'MCReps' the number of Monte-Carlo repetitions of the cross-validation (default 1). See also: lasso, glmfit, glmval, cvpartition # name: # type: sq_string # elements: 1 # length: 70 Lasso and elastic-net regularized generalized linear model regression. # name: # type: sq_string # elements: 1 # length: 19 logistic_regression # name: # type: sq_string # elements: 1 # length: 1982 statistics: [ intercept , slope , dev , dl , d2l , P , stats ] = logistic_regression ( y , x , print , intercept , slope ) Perform ordinal logistic regression. Suppose y takes values in k ordered categories, and let P_i ( x ) be the cumulative probability that y falls in one of the first i categories given the covariate x . Then [ intercept , slope ] = logistic_regression ( y , x ) fits the model logit (P_i ( x )) = x * slope + intercept _i, i = 1 … k-1 The number of ordinal categories, k, is taken to be the number of distinct values of round ( y ) . If k equals 2, y is binary and the model is ordinary logistic regression. The matrix x is assumed to have full column rank. Given y only, intercept = logistic_regression ( y ) fits the model with baseline logit odds only. The full form is [ intercept , slope , dev , dl , d2l , P , ... stats ] = logistic_regression ( y , x , print , ... intercept , slope ) in which all output arguments and all input arguments except y are optional. Setting print to 1 requests summary information about the fitted model to be displayed. Setting print to 2 requests information about convergence at each iteration. Other values request no information to be displayed. The input arguments intercept and slope give initial estimates for intercept and slope . The returned value dev holds minus twice the log-likelihood. The returned values dl and d2l are the vector of first and the matrix of second derivatives of the log-likelihood with respect to intercept and slope . P holds estimates for the conditional distribution of y given x . stats returns a structure that contains the following fields: "intercept": intercept coefficients "slope": slope coefficients "coeff": regression coefficients (intercepts and slops) "covb": estimated covariance matrix for coefficients (coeff) "coeffcorr": correlation matrix for coeff "se": standard errors of the coeff "z": z statistics for coeff "pval": p-values for coeff # name: # type: sq_string # elements: 1 # length: 36 Perform ordinal logistic regression. # name: # type: sq_string # elements: 1 # length: 6 mnrfit # name: # type: sq_string # elements: 1 # length: 2578 statistics: B = mnrfit ( X , Y ) statistics: B = mnrfit ( X , Y , name , value ) statistics: [ B , dev ] = mnrfit (…) statistics: [ B , dev , stats ] = mnrfit (…) Fit a multinomial logistic regression model. Nominal models are fitted with a baseline-category multinomial logit, using the last category of Y as the reference. Ordinal models are fitted with a cumulative link model and hierarchical models with a sequential (continuation-ratio) link model, both honouring the 'link' option below. Nominal models always use the logit link. B = mnrfit ( X , Y ) returns a matrix, B , of coefficient estimates for a multinomial logistic regression of the nominal responses in Y on the predictors in X . X is an N×P numeric matrix the observations on predictor variables, where N corresponds to the number of observations and P corresponds to predictor variables. Y contains the response category labels and it either be an N×P categorical or numerical matrix (containing only 1s and 0s) or an N×1 numeric vector with positive integer values, a cell array of character vectors and a logical vector. Y can also be defined as a character matrix with each row corresponding to an observation of X . B = mnrfit ( X , Y , name , value ) returns a matrix, B , of coefficient estimates for a multinomial model fit with additional parameters specified Name-Value pair arguments. Name Value 'model' The type of model to fit: 'nominal' (default) for a baseline-category model, 'ordinal' for a cumulative model, or 'hierarchical' for a sequential (continuation-ratio) model. 'link' The link function for ordinal and hierarchical models: 'logit' (default), 'probit' , 'comploglog' , or 'loglog' . Nominal models always use the logit link. 'estdisp' 'on' to estimate a dispersion parameter, scaling the coefficient standard errors by it and testing the coefficients against the t distribution, or 'off' (default) for the theoretical dispersion of 1 . 'display' A flag to enable/disable displaying information about the fitted model. Default is 'off' . [ B , dev , stats ] = mnrfit (…) also returns the deviance of the fit, dev , and a structure stats with the fitted coefficients 'beta' (same as B ), their standard errors 'se' , covariance matrix 'covb' , correlation matrix 'coeffcorr' , error degrees of freedom 'dfe' , the coefficient t statistics 't' and p -values 'p' , the dispersion parameters 's' , 'sfit' , and 'estdisp' , and the raw, Pearson, and deviance residuals 'resid' , 'residp' , and 'residd' . See also: mnrval, logistic_regression # name: # type: sq_string # elements: 1 # length: 44 Fit a multinomial logistic regression model. # name: # type: sq_string # elements: 1 # length: 6 mnrval # name: # type: sq_string # elements: 1 # length: 2736 statistics: pihat = mnrval ( B , X ) statistics: [ pihat , dlo , dhi ] = mnrval ( B , X , stats ) statistics: yhat = mnrval ( B , X , ssize ) statistics: [ yhat , dlo , dhi ] = mnrval ( B , X , ssize , stats ) statistics: […] = mnrval (…, name , value ) Predict values for a multinomial logistic regression model. pihat = mnrval ( B , X ) returns the predicted category probabilities pihat of a multinomial logistic regression with coefficients B , evaluated at the predictor values in X . X is an N×P numeric matrix of N observations on P predictors. pihat is an N×K matrix, where K is the number of response categories and each row sums to one. B is the coefficient matrix returned by mnrfit (see below for its shape under each model). mnrval is the prediction companion of mnrfit . Unlike the current mnrfit , which only fits ordinal and two-category nominal models, mnrval evaluates all three model types, so a coefficient matrix B obtained elsewhere (e.g. MATLAB) can be used for prediction. yhat = mnrval ( B , X , ssize ) returns predicted category counts instead of probabilities, for the sample sizes in ssize (a scalar or an N×1 vector). [ pihat , dlo , dhi ] = mnrval ( B , X , stats ) also returns 95% confidence bounds on the predictions. stats is the structure returned by mnrfit ; its 'covb' field (the coefficient covariance matrix) is required. The confidence interval for each prediction is [ pihat - dlo , pihat + dhi ] . The bounds are nonsimultaneous and apply to the fitted values, not to new observations. The following Name-Value pairs control the model: Name Value 'model' The model type: 'nominal' (default), 'ordinal' , or 'hierarchical' . 'interactions' 'on' to include category-specific coefficients, or 'off' for a common set of coefficients with category-specific intercepts only. Default is 'on' for nominal and hierarchical models and 'off' for ordinal models. With 'interactions','on' , B is a (P+1)×(K-1) matrix. With 'interactions','off' , B is a (K-1+P)×1 vector holding the K-1 intercepts followed by the P common slopes. 'link' The link function for ordinal and hierarchical models: 'logit' (default), 'probit' , 'comploglog' , or 'loglog' . Nominal models always use the multinomial logit link. 'type' The kind of probability returned: 'category' (default, N×K category probabilities), 'cumulative' ( N×(K-1) cumulative probabilities of the first K-1 categories), or 'conditional' ( N×(K-1) conditional probabilities of each category given membership in that or a later category). 'confidence' The confidence level for dlo and dhi , a scalar in the range (0,1) . Default is 0.95 . See also: mnrfit, glmval, logistic_regression # name: # type: sq_string # elements: 1 # length: 59 Predict values for a multinomial logistic regression model. # name: # type: sq_string # elements: 1 # length: 15 monotone_smooth # name: # type: sq_string # elements: 1 # length: 1467 statistics: yy = monotone_smooth ( x , y , h ) Produce a smooth monotone increasing approximation to a sampled functional dependence. A kernel method is used (an Epanechnikov smoothing kernel is applied to y(x); this is integrated to yield the monotone increasing form. See Reference 1 for details.) Arguments x is a vector of values of the independent variable. y is a vector of values of the dependent variable, of the same size as x . For best performance, it is recommended that the y already be fairly smooth, e.g. by applying a kernel smoothing to the original values if they are noisy. h is the kernel bandwidth to use. If h is not given, a "reasonable" value is computed. Return values yy is the vector of smooth monotone increasing function values at x . Examples x = 0:0.1:10; y = (x .^ 2) + 3 * randn(size(x)); # typically non-monotonic from the added noise ys = ([y(1) y(1:(end-1))] + y + [y(2:end) y(end)])/3; # crudely smoothed via moving average, but still typically non-monotonic yy = monotone_smooth(x, ys); # yy is monotone increasing in x plot(x, y, '+', x, ys, x, yy) References Holger Dette, Natalie Neumeyer and Kay F. Pilz (2006), A simple nonparametric estimator of a strictly monotone regression function, Bernoulli , 12:469-490 Regine Scheder (2007), R Package ’monoProc’, Version 1.0-6, http://cran.r-project.org/web/packages/monoProc/monoProc.pdf (The implementation here is based on the monoProc function mono.1d) # name: # type: sq_string # elements: 1 # length: 86 Produce a smooth monotone increasing approximation to a sampled functional dependence. # name: # type: sq_string # elements: 1 # length: 9 mvregress # name: # type: sq_string # elements: 1 # length: 1790 statistics: beta = mvregress ( X , Y ) statistics: beta = mvregress (…, name , value ) statistics: [ beta , Sigma , E , CovB , logL ] = mvregress (…) Multivariate (multiple-response) linear regression by maximum likelihood. mvregress ( X , Y ) fits the multivariate normal regression of the n -by- d response matrix Y on the design X and returns the coefficient estimates beta . X is either a numeric n -by- p matrix, in which case the same p predictors apply to every response and beta is returned as a p -by- d matrix; or a cell array of n design matrices, each d -by- K , in which case beta is a K -by-1 vector. Missing responses ( NaN entries of Y ) are handled according to the estimation algorithm. The following name / value pairs are accepted: "algorithm" "mvn" (multivariate normal; observations with any missing response are discarded), "ecm" (expectation-conditional-maximization, using every observed response), or "cwls" (covariance-weighted least squares, with the weight given by "covar0" ). The default is "mvn" when Y has no missing values and "ecm" otherwise. "covar0" The d -by- d covariance weight for "cwls" (default the identity), or the initial covariance for "ecm" . "maxiter" Maximum number of iterations (default 100). "tolbeta" , "tolobj" Convergence tolerances on the coefficients and the objective (defaults 1e-8 and 1e-8 ). The additional outputs are the estimated residual covariance Sigma ( d -by- d ), the residuals E ( n -by- d ), the covariance CovB of the coefficient estimates, and the log-likelihood logL . (With missing data and the "ecm" algorithm, CovB is the standard observed-information covariance and can differ from MATLAB ’s value at the 1e-3 level; all other outputs agree.) See also: mvregresslike, regress, fitlm # name: # type: sq_string # elements: 1 # length: 73 Multivariate (multiple-response) linear regression by maximum likelihood. # name: # type: sq_string # elements: 1 # length: 13 mvregresslike # name: # type: sq_string # elements: 1 # length: 1384 statistics: nlogL = mvregresslike ( X , Y , beta , Sigma , alg ) statistics: [ nlogL , COVB ] = mvregresslike (…) Negative log-likelihood for a multivariate regression model. mvregresslike ( X , Y , beta , Sigma , alg ) returns the negative log-likelihood nlogL of the multivariate normal regression model with responses Y (an n -by- d matrix, one row per observation), coefficients beta , and residual covariance Sigma ( d -by- d ). X specifies the design. It is either a numeric n -by- p matrix, in which case the same p predictors apply to every response and beta is p -by- d ; or a cell array of n design matrices, each d -by- K , in which case beta is K -by-1. alg selects how missing responses ( NaN entries of Y ) are handled: "ecm" (the default) and "cwls" use every observed response through the marginal likelihood of the observed components, while "mvn" discards any observation that has a missing response. With no missing data all three agree. The optional second output COVB is the covariance matrix of the coefficient estimates, computed as the inverse of the observed Fisher information at beta and Sigma . With missing data and the "ecm" / "cwls" algorithms this is the standard observed-data covariance and can differ from MATLAB ’s value (which uses a different information convention) at the 1e-3 level; nlogL agrees exactly. See also: mvregress # name: # type: sq_string # elements: 1 # length: 60 Negative log-likelihood for a multivariate regression model. # name: # type: sq_string # elements: 1 # length: 7 nlinfit # name: # type: sq_string # elements: 1 # length: 4273 statistics: beta = nlinfit ( X , y , modelfun , beta0 ) statistics: beta = nlinfit (…, options ) statistics: beta = nlinfit (…, Name , Value ) statistics: [ beta , R , J , CovB , MSE , ErrorModelInfo ] = nlinfit (…) Fit a nonlinear regression model. beta = nlinfit ( X , y , modelfun , beta0 ) estimates the coefficients of the nonlinear regression model y = modelfun ( beta , X ) by iteratively minimizing the (possibly weighted) sum of squared residuals, starting from the initial coefficient vector beta0 . The fit uses the Levenberg-Marquardt algorithm with a numerically computed Jacobian. X is a matrix of predictor values. nlinfit does not interpret the columns of X ; the array is passed unchanged as the second argument of modelfun , so its shape is whatever modelfun expects. y is a numeric vector of responses, one element per observation. modelfun is a function handle @( b , X ) returning a vector of fitted responses the same size as y . beta0 is a numeric vector of initial values for the coefficients. Additional options are given either as a statset-style options structure or as Name / Value pairs (or both). The supported options are: Name Value 'Weights' A vector of nonnegative observation weights, or a function handle @( yhat ) returning such a vector. Weighted least squares is used. 'ErrorModel' The form of the error variance: 'constant' (default), 'proportional' , or 'combined' . 'ErrorParameters' Initial values for the error-model parameters. 'RobustWgtFun' The name of a robust weight function ( 'andrews' , 'bisquare' , 'cauchy' , 'fair' , 'huber' , 'logistic' , 'talwar' , or 'welsch' ), enabling robust iteratively reweighted least squares. MATLAB accepts this name only inside an 'Options' structure; taking it as a Name / Value pair as well is an Octave extension. 'Tune' The tuning constant for the robust weight function. 'Options' A statset-style structure whose MaxIter , TolFun , TolX , and DerivStep fields override the corresponding defaults, and whose RobustWgtFun , Robust , WgtFun and Tune fields select a robust fit. RobustWgtFun names the weight function on its own and takes precedence over the other two; the older WgtFun is read only when Robust is 'on' . The structure statset ('nlinfit') returns carries WgtFun 'bisquare' beside Robust 'off' , and so leaves the fit unweighted. The remaining outputs describe the converged fit: R is the vector of raw residuals y - modelfun ( beta , X ) , J is the Jacobian of modelfun with respect to beta at the solution, CovB is the estimated covariance matrix of the coefficients, MSE is the mean squared error, and ErrorModelInfo is a structure describing the fitted error model. Algorithm The coefficients are estimated by the Levenberg-Marquardt algorithm using a numerically computed (forward-difference) Jacobian. For an ordinary or weighted fit the coefficient covariance is CovB = MSE * inv ( J ' * W * J ) , where W is the diagonal matrix of observation weights and MSE is the weighted residual sum of squares divided by the error degrees of freedom n - p (with p coefficients). A non-constant 'ErrorModel' is fitted by generalized least squares, re-deriving the observation weights from the fitted values each iteration; the 'proportional' model weights each observation by the inverse squared fitted value, and MSE then estimates the proportionality constant of the variance. For a robust fit ( 'RobustWgtFun' ) the coefficients are found by iteratively reweighted least squares applied to leverage-adjusted residuals (the leverage is taken from the ordinary fit and held fixed). The robust coefficient covariance follows the Street-Carroll-Ruppert convention, the same one used by robustfit : CovB = s^2 * inv ( J ' * J ) and MSE = s^2 , where the scale s blends the ordinary-fit scale ols_s with the robust scale robust_s at the solution as s^2 = (p^2 × ols_s^2 + n × robust_s^2) / (n + p^2) , taken to be at least robust_s . The robust MSE and CovB differ from MATLAB’s by up to a few tenths of a percent, because the shared robust scale does; robustfit documents that difference and why it is left in place. The coefficients themselves agree to about 1e-8. See also: fitnlm, nlparci, nlpredci, NonLinearModel, robustfit # name: # type: sq_string # elements: 1 # length: 33 Fit a nonlinear regression model. # name: # type: sq_string # elements: 1 # length: 7 nlparci # name: # type: sq_string # elements: 1 # length: 1072 statistics: ci = nlparci ( beta , resid , 'covar' , CovB ) statistics: ci = nlparci ( beta , resid , 'jacobian' , J ) statistics: ci = nlparci (…, 'alpha' , alpha ) Confidence intervals for the coefficients of a nonlinear regression. ci = nlparci ( beta , resid , 'covar' , CovB ) returns the 100 (1 - alpha )% confidence intervals for the fitted coefficients beta of a nonlinear regression, given the residual vector resid and the estimated coefficient covariance matrix CovB (both produced by nlinfit ). ci is a p -by- 2 matrix whose rows are the lower and upper bounds for the corresponding coefficient. ci = nlparci ( beta , resid , 'jacobian' , J ) instead derives the coefficient covariance from the Jacobian J and the residuals. A legacy positional form nlparci ( beta , resid , J ) is also accepted. The confidence level defaults to 95% ; pass 'alpha' , alpha for a 100 (1 - alpha )% interval. The intervals use Student’s t distribution with numel ( resid ) - numel ( beta ) degrees of freedom. See also: nlinfit, nlpredci, fitnlm, NonLinearModel # name: # type: sq_string # elements: 1 # length: 68 Confidence intervals for the coefficients of a nonlinear regression. # name: # type: sq_string # elements: 1 # length: 8 nlpredci # name: # type: sq_string # elements: 1 # length: 1994 statistics: [ ypred , delta ] = nlpredci ( modelfun , X , beta , resid , 'Jacobian' , J ) statistics: [ ypred , delta ] = nlpredci ( modelfun , X , beta , resid , 'Covar' , CovB ) statistics: [ ypred , delta ] = nlpredci (…, Name , Value ) Confidence intervals for predictions of a nonlinear regression. [ ypred , delta ] = nlpredci ( modelfun , X , beta , resid , 'Jacobian' , J ) returns the predicted responses ypred of the model modelfun ( beta , X ) at the new predictor values X , together with the half-widths delta of the 100 (1 - alpha )% confidence intervals, so that ypred - delta and ypred + delta bound the response. beta , resid (the residuals) and J (the Jacobian) come from nlinfit . Instead of the Jacobian, an estimated coefficient covariance may be supplied with 'Covar' , CovB . The following Name / Value pairs are also accepted: Name Value 'MSE' The mean squared error from nlinfit , required with 'Covar' for observation (prediction) intervals. 'PredOpt' 'curve' (default) for confidence intervals on the fitted curve, or 'observation' for prediction intervals on a new observation. 'SimOpt' 'off' (default) for pointwise intervals, or 'on' for simultaneous (Scheffe) intervals. 'Alpha' The significance level; the interval has confidence 100 (1 - alpha )% (default alpha = 0.05). Algorithm Each half-width is delta = c * sqrt (v) . The variance v of the fitted curve is diag ( Jnew * V * Jnew ') , where V is the coefficient covariance (either CovB , or MSE * inv ( J ' * J ) when a Jacobian is supplied) and Jnew is the Jacobian of modelfun at X ; an 'observation' interval adds the error variance MSE to v . The critical value c is the Student’s t quantile at 1 - alpha /2 with the error degrees of freedom for a pointwise interval, or the Scheffe value sqrt (k * finv (1 - alpha , k, dfe)) for a simultaneous interval, where k is the number of coefficients (plus one for an observation interval). See also: nlinfit, nlparci, fitnlm, NonLinearModel # name: # type: sq_string # elements: 1 # length: 63 Confidence intervals for predictions of a nonlinear regression. # name: # type: sq_string # elements: 1 # length: 10 plsregress # name: # type: sq_string # elements: 1 # length: 4466 statistics: [ xload , yload ] = plsregress ( X , Y ) statistics: [ xload , yload ] = plsregress ( X , Y , NCOMP ) statistics: [ xload , yload , xscore , yscore , coef , pctVar , mse , stats ] = plsregress ( X , Y , NCOMP ) statistics: [ xload , yload , xscore , yscore , coef , pctVar , mse , stats ] = plsregress (…, Name , Value ) Calculate partial least squares regression using SIMPLS algorithm. plsregress uses the SIMPLS algorithm, and first centers X and Y by subtracting off column means to get centered variables. However, it does not rescale the columns. To perform partial least squares regression with standardized variables, use zscore to normalize X and Y . [ xload , yload ] = plsregress ( X , Y ) computes a partial least squares regression of Y on X , using NCOMP PLS components, which by default are calculated as min (size ( X , 1) - 1, size( X , 2)) , and returns the the predictor and response loadings in xload and yload , respectively. X is an N×P matrix of predictor variables, with rows corresponding to observations, and columns corresponding to variables. Y is an N×M response matrix. xload is a P×NCOMP matrix of predictor loadings, where each row of xload contains coefficients that define a linear combination of PLS components that approximate the original predictor variables. yload is an M×NCOMP matrix of response loadings, where each row of yload contains coefficients that define a linear combination of PLS components that approximate the original response variables. [ xload , yload ] = plsregress ( X , Y , NCOMP ) defines the desired number of PLS components to use in the regression. NCOMP , a scalar positive integer, must not exceed the default calculated value. [ xload , yload , xscore , yscore , coef , pctVar , mse , stats ] = plsregress ( X , Y , NCOMP ) also returns the following arguments: xscore is an N×NCOMP orthonormal matrix with the predictor scores, i.e., the PLS components that are linear combinations of the variables in X , with rows corresponding to observations and columns corresponding to components. yscore is an N×NCOMP orthonormal matrix with the response scores, i.e., the linear combinations of the responses with which the PLS components xscore have maximum covariance, with rows corresponding to observations and columns corresponding to components. coef is a (P+1)×M matrix with the PLS regression coefficients, containing the intercepts in the first row. pctVar is a 2×NCOMP matrix containing the percentage of the variance explained by the model with the first row containing the percentage of explained varianced in X by each PLS component and the second row containing the percentage of explained variance in Y . mse is a 2×(NCOMP+1) matrix containing the estimated mean squared errors for PLS models with 0: NCOMP components with the first row containing the squared errors for the predictor variables in X and the second row containing the mean squared errors for the response variable(s) in Y . stats is a structure with the following fields: stats .W is a P×NCOMP matrix of PLS weights. stats .T2 is the T^2 statistics for each point in xscore . stats .Xresiduals is an N×P matrix with the predictor residuals. stats .Yresiduals is an N×M matrix with the response residuals. […] = plsregress (…, Name , Value , …) specifies one or more of the following Name / Value pairs: Name Value 'CV' The method used to compute mse . When Value is a positive integer K , plsregress uses K -fold cross-validation. Set Value to a cross-validation partition, created using cvpartition , to use other forms of cross-validation. Set Value to 'resubstitution' to use both X and Y to fit the model and to estimate the mean squared errors, without cross-validation. By default, Value = "resubstitution" . 'MCReps' A positive integer indicating the number of Monte-Carlo repetitions for cross-validation. By default, Value = 1 . A different 'MCReps' value is only meaningful when using the 'HoldOut' method for cross-validation, previously set by a cvpartition object. If no cross-validation method is used, then 'MCReps' must be 1 . Further information about the PLS regression can be found at https://en.wikipedia.org/wiki/Partial_least_squares_regression References SIMPLS: An alternative approach to partial least squares regression. Chemometrics and Intelligent Laboratory Systems (1993) # name: # type: sq_string # elements: 1 # length: 66 Calculate partial least squares regression using SIMPLS algorithm. # name: # type: sq_string # elements: 1 # length: 7 regress # name: # type: sq_string # elements: 1 # length: 1135 statistics: [ b , bint , r , rint , stats ] = regress ( y , X , [ alpha ]) Multiple Linear Regression using Least Squares Fit of y on X with the model y = X * beta + e . Here, y is a column vector of observed values X is a matrix of regressors, with the first column filled with the constant value 1 beta is a column vector of regression parameters e is a column vector of random errors Arguments are y is the y in the model X is the X in the model alpha is the significance level used to calculate the confidence intervals bint and rint (see ‘Return values’ below). If not specified, ALPHA defaults to 0.05 Return values are b is the beta in the model bint is the confidence interval for b r is a column vector of residuals rint is the confidence interval for r stats is a row vector containing: The R^2 statistic The F statistic The p value for the full model The estimated error variance r and rint can be passed to rcoplot to visualize the residual intervals and identify outliers. NaN values in y and X are removed before calculation begins. See also: regress_gp, regression_ftest, regression_ttest # name: # type: sq_string # elements: 1 # length: 93 Multiple Linear Regression using Least Squares Fit of y on X with the model y = X * beta + e. # name: # type: sq_string # elements: 1 # length: 10 regress_gp # name: # type: sq_string # elements: 1 # length: 3474 statistics: [ Yfit , Yint , m , K ] = regress_gp ( X , Y , Xfit ) statistics: [ Yfit , Yint , m , K ] = regress_gp ( X , Y , Xfit , 'linear' ) statistics: [ Yfit , Yint , Ysd ] = regress_gp ( X , Y , Xfit , 'rbf' ) statistics: […] = regress_gp ( X , Y , Xfit , 'linear' , Sp ) statistics: […] = regress_gp ( X , Y , Xfit , Sp ) statistics: […] = regress_gp ( X , Y , Xfit , 'rbf' , theta ) statistics: […] = regress_gp ( X , Y , Xfit , 'rbf' , theta , g ) statistics: […] = regress_gp ( X , Y , Xfit , 'rbf' , theta , g , alpha ) statistics: […] = regress_gp ( X , Y , Xfit , theta ) statistics: […] = regress_gp ( X , Y , Xfit , theta , g ) statistics: […] = regress_gp ( X , Y , Xfit , theta , g , alpha ) Regression using Gaussian Processes. [ Yfit , Yint , m , K ] = regress_gp ( X , Y , Xfit ) will estimate a linear Gaussian Process model m in the form Y = X ' * m , where X is an N×P matrix with N observations in P dimensional space and Y is an N×1 column vector as the dependent variable. The information about errors of the predictions (interpolation/extrapolation) is given by the covariance matrix K . By default, the linear model defines the prior covariance of m as Sp = 100 * eye (size ( X , 2) + 1) . A custom prior covariance matrix can be passed as Sp , which must be a P+1×P+1 positive definite matrix. The model is evaluated for input Xfit , which must have the same columns as X , and the estimates are returned in Yfit along with the estimated variation in Yint . Yint (:,1) contains the lower boundary and Yint (:,2) the upper boundary of the interval about Yfit , at the confidence level 1 - alpha . [ Yfit , Yint , Ysd ] = regress_gp ( X , Y , Xfit , 'rbf' ) will estimate a Gaussian Process model with a Radial Basis Function (RBF) kernel with default parameters theta = 5 and g = 0.01 , which corresponds to the nugget effect, and alpha = 0.05 which defines the confidence level for the estimated intervals returned in Yint . The function also returns the predictive covariance matrix in Ysd . For multidimensional predictors X the function will automatically normalize each column to a zero mean and a standard deviation to one. Four things about the RBF kernel are worth stating, because they decide what the numbers mean. theta is not the characteristic lengthscale. The kernel is exp (-d^2 / theta ) with d the distance between two points, so theta is twice the square of a lengthscale l , and theta = 2 * l^2 . The intervals in Yint are prediction intervals for a new observation , not confidence intervals on the mean: the nugget is carried in the predictive variance, so the noise of an observation is included. The predictors are centred and scaled only when there is more than one of them, so theta is measured in the units of X for a single predictor and in standard deviations for several. A nugget of zero is accepted and gives an interpolating process, one that reproduces Y at the training points and reports almost no uncertainty there. It also leaves the kernel matrix rank deficient whenever two inputs are close, so in that case the covariance is applied through its pseudoinverse and the fit is the minimum-norm solution. This is a genuine answer rather than a refusal, but a small nugget is the better way to ask for a smooth fit. Run demo regress_gp to see examples. See also: fitrgp, RegressionGP, regress, regression_ftest, regression_ttest # name: # type: sq_string # elements: 1 # length: 36 Regression using Gaussian Processes. # name: # type: sq_string # elements: 1 # length: 5 ridge # name: # type: sq_string # elements: 1 # length: 1270 statistics: b = ridge ( y , X , k ) statistics: b = ridge ( y , X , k , scaled ) Ridge regression. b = ridge ( y , X , k ) returns the vector of coefficient estimates by applying ridge regression from the predictor matrix X to the response vector y . Each value of b is the coefficient for the respective ridge parameter given k . By default, b is calculated after centering and scaling the predictors to have a zero mean and standard deviation 1. b = ridge ( y , X , k , scaled ) performs the regression with the specified scaling of the coefficient estimates b . When scaled = 0 , the function restores the coefficients to the scale of the original data thus is more useful for making predictions. When scaled = 1 , the coefficient estimates correspond to the scaled centered data. y must be an N×1 numeric vector with the response data. X must be an N×p numeric matrix with the predictor data. k must be a numeric vector with the ridge parameters. scaled must be a numeric scalar indicating whether the coefficient estimates in b are restored to the scale of the original data. By default, scaled = 1 . Further information about Ridge regression can be found at https://en.wikipedia.org/wiki/Ridge_regression See also: lasso, stepwisefit, regress # name: # type: sq_string # elements: 1 # length: 17 Ridge regression. # name: # type: sq_string # elements: 1 # length: 9 robustfit # name: # type: sq_string # elements: 1 # length: 2097 statistics: b = robustfit ( X , y ) statistics: b = robustfit ( X , y , wfun ) statistics: b = robustfit ( X , y , wfun , tune ) statistics: b = robustfit ( X , y , wfun , tune , const ) statistics: [ b , stats ] = robustfit (…) Robust linear regression. b = robustfit ( X , y ) returns the coefficient vector b of a linear regression of the response y on the predictors X , fitted by robust M-estimation (iteratively reweighted least squares) so that outlying observations are downweighted. A column of ones is added to X by default, so b (1) is the intercept. b = robustfit ( X , y , wfun , tune , const ) selects the weight function wfun , its tuning constant tune , and whether a constant term is included. wfun is one of 'bisquare' (default), 'andrews' , 'cauchy' , 'fair' , 'huber' , 'logistic' , 'ols' , 'talwar' , 'welsch' , or a function handle @(r) giving the weights as a function of the scaled residual. tune defaults to the value that gives 95% efficiency for each weight function. const is 'on' (default) to include a constant term or 'off' to omit. [ b , stats ] = robustfit (…) also returns a structure stats with fields ols_s , robust_s , mad_s , s , se , covb , coeffcorr , t , p , w , R , dfe , h , and resid . The coefficients and the fields ols_s , mad_s , dfe , h , w , and resid match MATLAB. The standard errors and quantities derived from them ( se , t , p , covb ) agree with MATLAB to within a small fraction of a percent; robust_s is the Street-Carroll-Ruppert robust scale estimate and differs from MATLAB’s by about 1.5%, measured, with a negligible effect on the standard errors. That difference is left in place deliberately. The squared influence is averaged here over n , where the estimator as it is usually published averages over n-p ; taking that published form moves the result further from MATLAB rather than closer, so MATLAB implements neither, and matching it would mean reproducing an undocumented variant. The same scale serves nlinfit , which is why its robust MSE and CovB carry the same difference. See also: regress, fitlm # name: # type: sq_string # elements: 1 # length: 25 Robust linear regression. # name: # type: sq_string # elements: 1 # length: 11 stepwisefit # name: # type: sq_string # elements: 1 # length: 3057 statistics: stepwisefit ( X , y ) statistics: b = stepwisefit ( X , y ) statistics: b , se , pval , finalmodel , stats , nextstep , history = stepwisefit ( X , y , varargin ) Perform stepwise linear regression using conditional p-value criteria. stepwisefit fits a linear regression model to response vector y using predictor matrix X and performs stepwise variable selection based on hypothesis tests for individual regression coefficients. At each iteration, predictors not currently in the model are tested for inclusion using partial F- or t-tests. The predictor with the smallest p-value below the entry threshold is added. Predictors currently in the model (excluding forced predictors) are then tested for removal, and the predictor with the largest p-value exceeding the removal threshold is removed. The procedure repeats until the model stabilizes or the maximum number of iterations is reached. After variable selection, the final regression model is refit using regress to compute coefficient estimates and inferential statistics for both included and excluded predictors. Arguments X is an n -by- p numeric matrix of predictor variables. y is an n -by-1 numeric response vector. Optional Name-Value pairs may be supplied to control the stepwise selection procedure. Name-Value Arguments 'InModel' Logical row vector of length p specifying predictors that are initially included in the model. 'Keep' Logical row vector of length p specifying predictors that must remain in the model and are never removed during stepwise selection. 'PEnter' Scalar significance level in the open interval (0,1) specifying the maximum p-value required for a predictor to enter the model. Default is 0.05 . 'PRemove' Scalar significance level in the open interval (0,1) specifying the minimum p-value required for a predictor to be removed from the model. If not specified, a default value greater than or equal to 'PEnter' is used. 'MaxIter' Positive integer specifying the maximum number of stepwise iterations. Default is Inf . 'Scale' Either 'on' or 'off' . When enabled, predictors are standardized prior to stepwise selection only. Final regression coefficients are always reported on the original data scale. 'Display' Either 'on' or 'off' . Accepted for compatibility but currently does not affect output. Return Values b is a p -by-1 vector of regression coefficients. Coefficients for excluded predictors are computed conditionally. se is a p -by-1 vector of standard errors. pval is a p -by-1 vector of two-sided p-values. finalmodel is a logical row vector indicating which predictors are included in the final model. stats is a structure containing regression diagnostics, including sums of squares, degrees of freedom, residuals, covariance estimates, F-statistic, and related quantities. nextstep is a scalar indicating whether an additional stepwise iteration is recommended. Currently always zero. history is a structure summarizing the final model state, including selected predictors and coefficient history. See also: regress # name: # type: sq_string # elements: 1 # length: 70 Perform stepwise linear regression using conditional p-value criteria. # name: # type: sq_string # elements: 1 # length: 11 stepwiseglm # name: # type: sq_string # elements: 1 # length: 4632 statistics: mdl = stepwiseglm ( X , y ) statistics: mdl = stepwiseglm ( X , y , modelspec ) statistics: mdl = stepwiseglm ( tbl ) statistics: mdl = stepwiseglm ( tbl , modelspec ) statistics: mdl = stepwiseglm (…, Name , Value ) Fit a generalized linear regression model by stepwise term selection. mdl = stepwiseglm ( X , y ) starts from a model given by modelspec and repeatedly adds or removes terms, one at a time, until no further move improves the selection criterion. It returns the fitted GeneralizedLinearModel object mdl , whose Steps property records the term-selection trace. X is an n -by- p numeric predictor matrix and y the response; mdl = stepwiseglm ( tbl ) instead takes the predictors and response from the table tbl (the last column is the response unless overridden). modelspec is the starting model. It is a Wilkinson formula string (e.g. 'y ~ x1 + x2' ), a keyword ( 'constant' (default), 'linear' , 'interactions' , 'purequadratic' , 'quadratic' , or 'full' ), or a terms matrix. The candidate terms available to the search are bounded below by 'Lower' and above by 'Upper' . The following Name / Value pairs control the stepwise search: Name Value 'Lower' the smallest model considered (terms in it are never removed). Defaults to 'constant' . 'Upper' the largest model considered (the candidate term universe). Defaults to 'interactions' . 'Criterion' the selection criterion: 'Deviance' (default), 'sse' , 'aic' , or 'bic' . Under 'Deviance' and 'sse' , terms enter or leave by a chi-squared or F test on the change in deviance; under 'aic' / 'bic' the move that most reduces the information criterion is taken. 'PEnter' the p -value (or criterion margin) below which a term is added. Defaults to 0.05 for 'Deviance' and 'sse' , and 0 for 'aic' / 'bic' . 'PRemove' the p -value (or criterion margin) above which a term is removed. Defaults to 0.10 for 'Deviance' and 'sse' , and 0 for 'aic' / 'bic' . 'NSteps' the maximum number of steps. Defaults to Inf (run to convergence). 'Verbose' 0 to run silently, or 1 (default) to print each accepted step. As in fitglm , a 'binomial' response is the number of successes, and the trials come either from 'BinomialSize' or from passing y as an n -by- 2 matrix of successes and trials. Changed in 1.9.0 : y was previously read as the proportion. All Name / Value pairs accepted by fitglm (such as 'Distribution' , 'Link' , 'Weights' , 'Offset' , 'BinomialSize' , 'Intercept' , 'DispersionFlag' , 'CategoricalVars' , and 'Exclude' ) are also accepted and forwarded to the fit. The Steps property The returned model’s Steps property records the trace, as a structure with seven fields. Start , Lower , and Upper are LinearFormula objects for the starting model and the two bounds; Criterion is the criterion as it was asked for; PEnter and PRemove are the thresholds it ran under; and History is a table with one row per step. History always carries Action ( 'Start' , 'Add' , or 'Remove' ), TermName , Terms (the terms matrix after the step, over the model’s variables), DF (the coefficient count after the step), and delDF (the change in it, negative for a removal). The remaining columns follow the criterion, which is why a history is read by name and not by position: Criterion Further columns 'Deviance' Deviance , then Chi2Stat or FStat as the dispersion is fixed or estimated, then PValue . 'sse' FStat and pValue . 'aic' , 'bic' one column, AIC or BIC , holding the criterion’s value after the step, the starting model included. The first row is the starting model, named by its right-hand side. Categorical predictors A categorical predictor with L levels contributes L - 1 indicator columns, the first level being the omitted reference, and the search treats that whole group as a single term : it is added or removed in one step, worth L - 1 degrees of freedom, and never one indicator at a time. An interaction naming a categorical predictor behaves the same way, contributing one column per indicator and entering as one term. Steps.History names such a term by the predictor ( 'g' , or 'x1:g' for the interaction) rather than by its indicators, while CoefficientNames names the indicators ( 'g_B' , 'x1:g_C' ). In a table, every column that groups its observations is taken as categorical: a cell array of character vectors, a categorical array, a string array, or a logical column. 'CategoricalVars' adds to these, and is the only way to mark a column of a predictor matrix ; it takes predictor names, column indices, or a logical vector. See also: GeneralizedLinearModel, fitglm, stepwisefit, glmfit, glmval # name: # type: sq_string # elements: 1 # length: 69 Fit a generalized linear regression model by stepwise term selection. # name: # type: sq_string # elements: 1 # length: 10 stepwiselm # name: # type: sq_string # elements: 1 # length: 9276 statistics: mdl = stepwiselm ( tbl ) statistics: mdl = stepwiselm ( tbl , ResponseVarName ) statistics: mdl = stepwiselm ( tbl , y ) statistics: mdl = stepwiselm ( X , y ) statistics: mdl = stepwiselm (…, InitialModel ) statistics: mdl = stepwiselm (…, Name , Value , …) Fit a linear regression model using stepwise regression and return a LinearModel object. stepwiselm starts from an initial model and repeatedly searches for a term to add to, or remove from, the current model, based on the value of the 'Criterion' option, until no single addition or removal improves the model any further. Basic Syntax mdl = stepwiselm ( tbl ) fits a stepwise model using the variables in the table (or dataset) tbl , starting from a constant model. By default, the last variable in tbl is used as the response and all other variables are candidate predictors. Variables that are categorical arrays, cell arrays of character vectors, or logical arrays are automatically treated as categorical predictors. mdl = stepwiselm ( tbl , ResponseVarName ) uses the variable named ResponseVarName in tbl as the response, and all remaining variables in tbl as candidate predictors. mdl = stepwiselm ( tbl , y ) uses the variables in tbl as candidate predictors and the external numeric vector y as the response. mdl = stepwiselm ( X , y ) fits a stepwise model of the response y to the predictor data X , an N×P numeric or logical matrix. By default, the predictors are named 'x1' , 'x2' , …, 'xP' and the response is named 'y' . Initial Model, and Lower/Upper Bounds mdl = stepwiselm (…, InitialModel ) additionally specifies the model to start the stepwise search from, using any of the input combinations shown above. InitialModel can be any of the following, and the same set of values can also be used for the 'Lower' and 'Upper' options below, which bound the smallest and largest set of terms stepwiselm is allowed to reach. Value Description 'constant' Model contains only an intercept term. This is the default InitialModel and default 'Lower' bound. 'linear' Model contains an intercept and one term for each predictor variable. 'interactions' Model contains an intercept, all linear terms, and all pairwise products of distinct predictor variables (no squared terms). This is the default 'Upper' bound. 'purequadratic' Model contains an intercept, all linear terms, and all squared terms. 'quadratic' Model contains an intercept, all linear terms, all pairwise products of distinct predictor variables, and all squared terms. 'polyijk' Model is a polynomial with maximum degree i in the first predictor, j in the second, and so on, given as a run of single-digit numerals, one per predictor (e.g. 'poly21' for two predictors). The model contains interaction terms, but the degree of each interaction term never exceeds the largest of the specified per-predictor degrees. terms matrix A T×P or T×(P+1) numeric matrix, where T is the number of terms and P is the number of predictor variables, following the same convention as fitlm ’s terms matrix. When InitialModel is given as a terms matrix, the 'PredictorVars' option may not also be used. Wilkinson formula A character vector of the form 'y ~ terms' . When a formula is combined with 'ResponseVar' or 'PredictorVars' , the formula’s response and predictor terms must agree with those options, or stepwiselm errors. Options mdl = stepwiselm (…, Name , Value , …) specifies additional options using one or more Name-Value pair arguments. Name Value 'Criterion' Criterion used to decide whether a term is added or removed at each step. One of 'sse' (default), 'aic' , 'bic' , 'rsquared' , or 'adjrsquared' . For 'sse' , the p-value of an F-test comparing the model with and without the candidate term is used; for the others, the raw change in the named quantity is used directly against 'PEnter' / 'PRemove' . 'PEnter' Threshold to add a term. Defaults depend on 'Criterion' : 0.05 for 'sse' , 0 for 'aic' / 'bic' , 0.1 for 'rsquared' , 0 for 'adjrsquared' . 'PRemove' Threshold to remove a term. Defaults depend on 'Criterion' : 0.10 for 'sse' , 0.01 for 'aic' / 'bic' , 0.05 for 'rsquared' , -0.05 for 'adjrsquared' . 'NSteps' Maximum number of add/remove steps to take, given as a nonnegative integer. Default is unlimited. 'NSteps' set to 0 returns the initial model unchanged. 'Lower' Model specification (in the same form as InitialModel , above) describing terms that may never be removed from the model. Terms in 'Lower' are protected from removal, but are not automatically added if absent from InitialModel . Default is 'constant' . 'Upper' Model specification (in the same form as InitialModel , above) describing the largest set of terms stepwiselm may add. Default is 'interactions' . 'Verbose' Controls how much progress information is printed while stepping. 0 suppresses all output, 1 (default) prints the action taken at each step, 2 additionally prints the p-value or criterion value considered for every candidate term examined at each step. 'Intercept' A logical scalar indicating whether the initial model includes a constant (intercept) term. Only applies when InitialModel is a character vector model name (or omitted); ignored when InitialModel is a terms matrix or formula. Default is true . 'Weights' A numeric vector of nonnegative observation weights, one per observation. Default is a vector of ones. 'Exclude' A numeric or logical vector specifying observations to exclude from the fit. 'CategoricalVars' Specifies which predictor variables are treated as categorical, given as a vector of column indices, a logical vector, or a cell array of variable names (table input only). Each categorical predictor with L distinct categories is expanded into L-1 indicator variables, and stepwiselm always adds or removes that entire group of indicator variables together, in a single step, treating the categorical predictor as one term. 'VarNames' A cell array of character vectors naming the predictor and response variables, response last. Only applies when X and y are supplied directly, not table input. 'ResponseVar' A character vector naming the response variable, overriding the response that would otherwise be inferred (the last table variable, or 'y' for matrix input). 'PredictorVars' A cell array of character vectors naming which variables in tbl to consider as candidate predictors. By default, all variables in tbl other than the response are used. May not be combined with a terms-matrix InitialModel , and must agree with any formula-based InitialModel . The Steps property The returned model’s Steps property records the trace, as a structure with seven fields. Start , Lower , and Upper are LinearFormula objects for the starting model and the two bounds; Criterion is the criterion as it was asked for; PEnter and PRemove are the thresholds it ran under; and History is a table with one row per step. History always carries Action ( 'Start' , 'Add' , or 'Remove' ), TermName , Terms (the terms matrix after the step, over the model’s variables), DF (the coefficient count after the step), and delDF (the change in it, negative for a removal). The remaining columns follow the criterion, which is why a history is read by name and not by position: Criterion Further columns 'SSE' FStat and pValue . 'AIC' , 'BIC' , 'Rsquared' , 'AdjRsquared' one column named for the criterion, holding its value after the step, the starting model included. The first row is the starting model, named by its right-hand side. step appends to the history it inherits rather than beginning a new one, and inherits Criterion , Lower , PEnter , and PRemove from it; Steps.Start is then the model stepped from. Algorithm At each step, stepwiselm examines every term not currently in the model but within the 'Upper' bound, and every term currently in the model but not protected by the 'Lower' bound. If any term outside the model would improve it by at least 'PEnter' , the best such term is added; otherwise, if any term inside the model falls short of 'PRemove' , the worst such term is removed. The process repeats until neither an addition nor a removal improves the model, or until 'NSteps' steps have been taken. stepwiselm never adds a higher-order term unless all of its lower-order marginal terms are already in the model (e.g. it will not add x1:x2^2 unless both x1 and x2^2 are already present), and correspondingly never removes a lower-order term that a higher-order term still in the model depends on. At every step, if a term in the current model is found to be exactly redundant (linearly dependent on the other terms already in the model), it is removed immediately regardless of the 'Criterion' value. Because the final model depends on the initial model and the order in which terms are considered, stepwiselm finds a locally, but not necessarily globally, optimal model. Robust fitting cannot be combined with stepwise regression; do not pass 'RobustOpts' to stepwiselm . mdl is returned as a LinearModel object. See also the step method of LinearModel , which performs a single bounded round of stepwise search starting from an already-fitted model. See also: LinearModel, fitlm # name: # type: sq_string # elements: 1 # length: 88 Fit a linear regression model using stepwise regression and return a LinearModel object. statistics-release-1.9.2/inst/Regression/fitcox.m000066400000000000000000000200261524624707500221160ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{mdl} =} fitcox (@var{X}, @var{T}) ## @deftypefnx {statistics} {@var{mdl} =} fitcox (@var{tbl}, @var{respvar}) ## @deftypefnx {statistics} {@var{mdl} =} fitcox (@dots{}, @var{Name}, @var{Value}) ## ## Fit a Cox proportional hazards regression model. ## ## @code{@var{mdl} = fitcox (@var{X}, @var{T})} fits the Cox proportional ## hazards model ## ## @tex ## $$ h(x_i, t) = h_0(t)\exp\left(\sum_{j=1}^{p} x_{ij} b_j\right) $$ ## @end tex ## @ifnottex ## @math{h(x_i, t) = h_0(t) exp (x_i' b)} ## @end ifnottex ## ## to the @math{n}-by-@math{p} numeric matrix of predictors @var{X} and the ## @math{n}-by-1 vector of event times @var{T}, and returns a @code{CoxModel} ## object. @var{T} may instead be an @math{n}-by-2 matrix whose rows give a ## @math{(start, stop]} interval of exposure, the counting process form, in ## which an observation joins the risk set only after its start time. ## ## @math{h_0(t)} is the baseline hazard, which is left unspecified: the ## coefficients are estimated by maximizing the Cox partial likelihood, which ## does not involve it. @strong{@var{X} must not contain a column of ones}, ## the model having no constant term, since any constant is absorbed into that ## baseline. ## ## @code{@var{mdl} = fitcox (@var{tbl}, @var{respvar})} takes the data from the ## table @var{tbl}, using the variable named @var{respvar} as the response and ## every other variable as a predictor. A @code{categorical} variable is ## encoded as indicator columns, one per level bar the first, which the baseline ## hazard carries. ## ## The following @var{Name}/@var{Value} pairs are accepted: ## ## @multitable @columnfractions 0.25 0.75 ## @headitem Name @tab Value ## @item @qcode{"Baseline"} @tab The @var{X} values at which the baseline hazard ## is computed, either a scalar or a 1-by-@math{p} vector. The default is the ## mean of each numeric predictor and zero for each indicator column of a ## categorical predictor, taken within each stratum. Pass @qcode{0} for a ## hazard relative to the origin. The coefficients do not depend on this ## choice. ## @item @qcode{"Beta"} @tab The starting value of the iteration, a vector of ## length @math{p}. The default is @code{0.01 ./ std (@var{X})}. ## @item @qcode{"CategoricalPredictors"} @tab The predictors to treat as ## categorical, given as column indices, a logical vector, or a cell array of ## predictor names. Table variables of class @code{categorical} are detected ## without this argument. ## @item @qcode{"Censoring"} @tab A logical or 0/1 vector of length @math{n}, ## where 1 marks an observation right-censored at its recorded time. The ## default is a vector of zeros, so every observation is a recorded event. ## @item @qcode{"Frequency"} @tab A vector of length @math{n} of non-negative ## values giving the number of observations each row represents, or a weight. ## The default is a vector of ones. ## @item @qcode{"OptimizationOptions"} @tab A structure of iteration settings, ## as built by @code{statset ("fitcox")}. The fields used are ## @qcode{"MaxIter"}, @qcode{"TolX"} and @qcode{"Display"}. ## @item @qcode{"PredictorNames"} @tab A cell array of @math{p} predictor names. ## The default is @qcode{"X1"}, @qcode{"X2"}, and so on, or the table variable ## names. ## @item @qcode{"Stratification"} @tab A vector of length @math{n} of stratum ## labels. Each stratum carries its own baseline hazard and its own risk sets, ## while the coefficients are shared across all of them. ## @item @qcode{"TieBreakMethod"} @tab The method of handling tied event times, ## either @qcode{"breslow"} (default) or @qcode{"efron"}. ## @end multitable ## ## @code{fitcox} is the object interface to @code{coxphfit}, which fits the same ## model and returns the estimates as plain arrays. The two agree exactly; the ## object additionally reports the proportional hazards assumption tests and ## carries the @code{survival}, @code{hazardratio}, @code{coefci}, ## @code{linhyptest} and @code{plotSurvival} methods. ## ## @seealso{CoxModel, coxphfit, ecdf, statset} ## @end deftypefn function mdl = fitcox (X, T, varargin) if (nargin < 2) print_usage (); endif mdl = CoxModel (X, T, varargin{:}); endfunction %!demo %! ## Fit a Cox proportional hazards model to right-censored survival times %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! C = [0; 0; 1; 0; 0; 1; 0; 0; 1; 0]; %! mdl = fitcox (X, T, 'Censoring', C) %!demo %! ## The hazard of each observation relative to the average one %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! hazardratio (mdl, X) %!demo %! ## Each stratum carries its own baseline hazard %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! S = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! mdl = fitcox (X, T, 'Stratification', S); %! mdl.Baseline %!shared X, T, C, S %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! C = [0; 0; 1; 0; 0; 1; 0; 0; 1; 0]; %! S = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; ## fitcox returns a CoxModel object %!test %! mdl = fitcox (X, T); %! assert_equal (class (mdl), 'CoxModel'); %! assert_equal (mdl.Coefficients.Beta, ... %! [-1.3886093196382836; 4.3814437183613322], 1e-8); ## It fits the same model as coxphfit %!test %! mdl = fitcox (X, T, 'Censoring', C); %! [b, logl] = coxphfit (X, T, 'Censoring', C); %! assert_equal (mdl.Coefficients.Beta, b, 1e-12); %! assert_equal (mdl.LogLikelihood, logl, 1e-12); ## The name-value pairs are fitcox's, not coxphfit's %!test %! mdl = fitcox (X, T, 'Censoring', C, 'TieBreakMethod', 'efron'); %! b = coxphfit (X, T, 'Censoring', C, 'Ties', 'efron'); %! assert_equal (mdl.Coefficients.Beta, b, 1e-12); %!test %! mdl = fitcox (X, T, 'Censoring', C, 'Stratification', S); %! b = coxphfit (X, T, 'Censoring', C, 'Strata', S); %! assert_equal (mdl.Coefficients.Beta, b, 1e-12); %!test %! mdl = fitcox (X, T, 'Beta', [0.1; -0.1]); %! b = coxphfit (X, T, 'B0', [0.1; -0.1]); %! assert_equal (mdl.Coefficients.Beta, b, 1e-12); ## A table names its own variables %!test %! tbl = table (X(:,1), X(:,2), T, 'VariableNames', {'age', 'trt', 'time'}); %! mdl = fitcox (tbl, 'time'); %! assert_equal (mdl.PredictorNames, {'age', 'trt'}); %! assert_equal (mdl.ResponseName, 'time'); %! assert_equal (char (mdl.Formula), 'time ~ age + trt'); %! assert_equal (mdl.Coefficients.Beta, ... %! [-1.3886093196382836; 4.3814437183613322], 1e-8); ## A categorical predictor is reference coded against its first level %!test %! g = categorical ({'a'; 'b'; 'a'; 'b'; 'a'; 'b'; 'a'; 'b'; 'a'; 'b'}); %! tbl = table (X(:,1), g, T, 'VariableNames', {'age', 'grp', 'time'}); %! mdl = fitcox (tbl, 'time'); %! assert_equal (mdl.PredictorNames, {'age', 'grp'}); %! assert_equal (mdl.Coefficients.Properties.RowNames, {'age'; 'grp_b'}); %! assert_equal (mdl.Coefficients.Beta, ... %! [-1.3886093196382836; 4.3814437183613322], 1e-8); %! assert_equal (mdl.Baseline, [5.9, 0], 1e-12); %! assert_equal (mdl.VariableInfo.IsCategorical, [false; true; false]); ## Errors %!error fitcox (1) %!error ... %! fitcox (table ([1; 2], [3; 4]), 'time') statistics-release-1.9.2/inst/Regression/fitglm.m000066400000000000000000000237151524624707500221140ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{mdl} =} fitglm (@var{X}, @var{y}) ## @deftypefnx {statistics} {@var{mdl} =} fitglm (@var{X}, @var{y}, @var{modelspec}) ## @deftypefnx {statistics} {@var{mdl} =} fitglm (@var{tbl}) ## @deftypefnx {statistics} {@var{mdl} =} fitglm (@var{tbl}, @var{modelspec}) ## @deftypefnx {statistics} {@var{mdl} =} fitglm (@dots{}, @var{Name}, @var{Value}) ## ## Fit a generalized linear regression model. ## ## @code{@var{mdl} = fitglm (@var{X}, @var{y})} fits a generalized linear model ## of the response vector @var{y} on the columns of the @math{n}-by-@math{p} ## numeric predictor matrix @var{X}, and returns a @code{GeneralizedLinearModel} ## object. @code{@var{mdl} = fitglm (@var{tbl})} instead takes the predictors ## and response from the table @var{tbl} (the last column is the response unless ## overridden). By default the response is @qcode{'normal'} with an identity ## link, an intercept is included, and the model is additive in the predictors. ## ## For the @qcode{'binomial'} distribution @var{y} holds the @emph{number of ## successes}, and the number of trials is given either by the ## @qcode{'BinomialSize'} pair or by passing @var{y} as an ## @math{n}-by-@math{2} matrix whose first column holds the successes and ## whose second holds the trials. The two forms describe the same model; when ## both are given, the trials supplied with the response are used. A trial ## count must be a positive integer, while a success count need not be whole. ## ## @var{modelspec} selects the model terms. It is either a Wilkinson formula ## string (e.g.@: @qcode{'y ~ x1 + x2*x3'}), a keyword (@qcode{'constant'}, ## @qcode{'linear'}, @qcode{'interactions'}, @qcode{'purequadratic'}, ## @qcode{'quadratic'}, or @qcode{'full'}), or a terms matrix. ## ## The following @var{Name}/@var{Value} pairs are accepted: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'Distribution'} @tab the response distribution: ## @qcode{'normal'} (default), @qcode{'binomial'}, @qcode{'poisson'}, ## @qcode{'gamma'}, or @qcode{'inverse gaussian'}. ## @item @qcode{'Link'} @tab the link function. Defaults to the canonical link ## of the distribution; accepts any link name understood by @code{glmfit} or a ## numeric exponent for a power link. ## @item @qcode{'Weights'} @tab a vector of nonnegative observation weights. ## @item @qcode{'Offset'} @tab a vector added as a fixed term to the linear ## predictor. ## @item @qcode{'BinomialSize'} @tab for the @qcode{'binomial'} distribution, ## the number of trials (a scalar or a per-observation vector); @var{y} holds ## the number of successes. @strong{Changed in 1.9.0}: @var{y} was previously ## read as the proportion of successes. Multiply an existing proportion by ## the trials to keep its meaning. ## @item @qcode{'Intercept'} @tab a logical value (default @qcode{true}) whether ## to include an intercept term. ## @item @qcode{'DispersionFlag'} @tab a logical value forcing the dispersion ## parameter to be estimated (@qcode{true}) or held at 1 (@qcode{false}). ## @item @qcode{'CategoricalVars'} @tab predictors to treat as categorical (a ## logical vector, numeric indices, or a cell array of names). ## @item @qcode{'Exclude'} @tab observations to exclude from the fit (a logical ## vector or numeric indices). ## @item @qcode{'VarNames'} @tab a cell array of @math{p + 1} variable names ## (predictors followed by the response) for numeric @var{X}. ## @item @qcode{'PredictorVars'}, @qcode{'ResponseVar'} @tab for table input, ## the predictor and response variable names. ## @end multitable ## ## A categorical predictor expands to indicator columns, one per level bar the ## reference level, which the intercept carries. When the model has no ## intercept, the @emph{first} categorical predictor is given an indicator for ## every one of its levels instead, so that its coefficients are the group ## means; any further categorical predictor stays reference coded, which keeps ## the design full rank. This differs from MATLAB, which omits the reference ## level whether or not an intercept is present and so cannot fit the reference ## group at all -- for a three-level grouping variable @code{g}, MATLAB fits ## @code{y ~ g - 1} with two coefficients, predicts exactly 0 for every ## observation in the omitted group, and reports a negative @math{R^2}. This ## implementation returns three coefficients, one per group. ## ## @seealso{GeneralizedLinearModel, fitlm, glmfit, glmval, lassoglm} ## @end deftypefn function mdl = fitglm (varargin) if (nargin < 1) print_usage (); endif arg1 = varargin{1}; if (istable (arg1)) [modelspec, nv] = split_modelspec (varargin(2:end)); mdl = GeneralizedLinearModel (arg1, [], modelspec, nv{:}); else if (nargin < 2) print_usage (); endif [modelspec, nv] = split_modelspec (varargin(3:end)); mdl = GeneralizedLinearModel (arg1, varargin{2}, modelspec, nv{:}); endif endfunction ## Split an optional leading model specification from the Name/Value pairs. function [modelspec, nv] = split_modelspec (rest) modelspec = 'linear'; nv = rest; if (! isempty (rest)) a = rest{1}; if ((ischar (a) && ! is_param_name (a)) || isnumeric (a)) modelspec = a; nv = rest(2:end); endif endif endfunction ## True if S names one of fitglm's Name/Value parameters. function tf = is_param_name (s) tf = ischar (s) && any (strcmpi (s, {'Distribution', 'Link', 'Weights', ... 'Offset', 'BinomialSize', 'Intercept', 'DispersionFlag', ... 'CategoricalVars', 'Exclude', 'VarNames', 'PredictorVars', ... 'ResponseVar'})); endfunction %!demo %! ## Poisson regression of counts on two predictors. %! X = [0.1, 1.2; 0.4, 0.7; 1.1, 0.2; 1.5, 1.9; 0.3, 0.5; 1.8, 1.1; 0.9, 0.3]; %! y = [1; 0; 2; 3; 1; 4; 2]; %! mdl = fitglm (X, y, 'Distribution', 'poisson') %!demo %! ## Logistic regression with an interaction, specified by a formula. %! X = [0.1, 1.2; 0.4, 0.7; 1.1, 0.2; 1.5, 1.9; 0.3, 0.5; 1.8, 1.1; 0.9, 0.3]; %! y = [0; 0; 1; 1; 0; 1; 1]; %! tbl = array2table ([X, y], 'VariableNames', {'x1', 'x2', 'y'}); %! mdl = fitglm (tbl, 'y ~ x1 + x2 + x1:x2', 'Distribution', 'binomial') %!shared X, yp, yb %! X = [ 0.37, 0.06, 1.76; -0.76, -1.52, 0.84; 0.76, -0.19, -0.47; ... %! -0.80, -2.74, -0.90; 0.08, 0.39, 1.05; -0.41, -0.03, 0.74; ... %! 0.23, 1.21, 0.35; 0.66, 0.94, 0.13; 0.66, -0.12, -0.06; ... %! 2.09, 1.33, -0.71; 1.50, 0.08, -0.52; 0.59, 0.07, -1.13; ... %! -1.17, -0.35, -1.28; 0.68, 0.63, -0.80; -0.69, 0.08, 0.41; ... %! 2.04, 0.96, -0.56]; %! yp = [5 2 0 3 1 1 0 1 2 1 3 0 0 1 1 3]'; %! yb = [1 1 1 0 0 1 1 1 1 1 1 0 0 0 0 1]'; ## Test results (values verified against MATLAB's fitglm) %!test %! ## Poisson fit: coefficients, deviance, log-likelihood, AIC. %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! assert_equal (mdl.Coefficients.Estimate, ... %! [-0.3420955; 1.2804868; -1.0743272; 0.8395779], 1e-6); %! assert_equal (mdl.Deviance, 7.403008, 1e-5); %! assert_equal (mdl.LogLikelihood, -18.543280, 1e-5); %! assert_equal (mdl.ModelCriterion.AIC, 45.086559, 1e-5); %! assert_equal (mdl.Rsquared.Deviance, 0.6627677, 1e-6); %!test %! ## coefTest reports a Wald F statistic versus the constant model. %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! [p, F, df] = coefTest (mdl); %! assert_equal (F, 3.685312, 1e-5); %! assert_equal (p, 0.04331745, 1e-7); %! assert_equal (df, 3); %!test %! ## coefCI uses the t distribution with the error degrees of freedom. %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! ci = coefCI (mdl); %! b = mdl.Coefficients.Estimate; se = mdl.Coefficients.SE; %! t = tinv (0.975, mdl.DFE); %! assert_equal (ci, [b - t .* se, b + t .* se], 1e-12); %!test %! ## devianceTest chi-square equals the drop from the null deviance. %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! dt = devianceTest (mdl); %! assert_equal (dt.chi2Stat(2), dt.Deviance(1) - dt.Deviance(2), 1e-10); %!test %! ## An interaction model names the cross term x1:x2. %! mdl = fitglm (X, yp, "interactions", "Distribution", "poisson"); %! assert_equal (any (strcmp (mdl.CoefficientNames, "x1:x2")), true); %! assert_equal (mdl.NumCoefficients, 7); %!test %! ## Leverage sums to the number of coefficients. %! mdl = fitglm (X, yb, "Distribution", "binomial"); %! assert_equal (sum (mdl.Diagnostics.Leverage), mdl.NumCoefficients, 1e-9); %!test # plotting methods and random run without error %! mdl = fitglm (X, yp, "Distribution", "poisson"); %! hf = figure ("visible", "off"); %! unwind_protect %! plotResiduals (mdl); %! plotResiduals (mdl, "fitted", "ResidualType", "Pearson"); %! plotDiagnostics (mdl); %! plotDiagnostics (mdl, "cookd"); %! plotEffects (mdl); %! plotAdjustedResponse (mdl, 1); %! plotAdded (mdl, "x2"); %! assert_equal (numel (random (mdl)), 16); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error fitglm () %!error ... %! fitglm ("a", [1;2]) %!error ... %! fitglm ([1, 2; 3, 4], [1; 0], 'Distribution', 'wibble') %!error ... %! fitglm ([1, 2; 3, 4], [1; 0], 'linear', 'foo', 1) statistics-release-1.9.2/inst/Regression/fitglme.m000066400000000000000000000276141524624707500222630ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{glme} =} fitglme (@var{tbl}, @var{formula}) ## @deftypefnx {statistics} {@var{glme} =} fitglme (@dots{}, @var{name}, @var{value}) ## ## Fit a generalized linear mixed-effects model specified by a formula. ## ## @code{fitglme (@var{tbl}, @var{formula})} fits the generalized linear ## mixed-effects model described by @var{formula} to the table @var{tbl} and ## returns a @code{GeneralizedLinearMixedModel} object. ## ## @var{formula} uses the same syntax as @code{fitlme}: a response, a ## fixed-effects part, and one or more random-effects terms ## @code{(@var{expr} | @var{group})}, for example ## @qcode{"y ~ x + (1 | g)"}. The model is fitted by penalized ## quasi-likelihood. ## ## The following @var{name}/@var{value} pairs are accepted: ## ## @table @asis ## @item @qcode{"Distribution"} ## The response distribution: @qcode{"normal"} (default), @qcode{"binomial"}, or ## @qcode{"poisson"}. ## ## @item @qcode{"Link"} ## The link function: @qcode{"identity"}, @qcode{"logit"}, or @qcode{"log"}. ## The default is the canonical link of the chosen distribution. ## ## @item @qcode{"FitMethod"} ## @qcode{"MPL"} (maximum pseudo-likelihood, the default), @qcode{"REMPL"} ## (restricted MPL), @qcode{"Laplace"}, or @qcode{"ApproximateLaplace"}. The ## first two differ in the pseudo-likelihood used for the covariance parameters; ## the last two report the Laplace-approximated marginal log-likelihood. ## @end table ## ## Only the canonical links and the full (unstructured) random-effects ## covariance are currently supported. ## ## @seealso{GeneralizedLinearMixedModel, fitlme, fitglm} ## @end deftypefn function glme = fitglme (tbl, formula, varargin) if (nargin < 2) print_usage (); endif if (! isa (tbl, "table")) error ("fitglme: TBL must be a table."); endif if (! (ischar (formula) || (isstring (formula) && isscalar (formula)))) error ("fitglme: FORMULA must be a character vector."); endif formula = char (formula); ## --- options --- distr = "normal"; link = ""; method = "MPL"; if (mod (numel (varargin), 2) != 0) error ("fitglme: name/value arguments must come in pairs."); endif for i = 1:2:numel (varargin) switch (lower (char (varargin{i}))) case "distribution" distr = lower (char (varargin{i+1})); case "link" link = lower (char (varargin{i+1})); case "fitmethod" method = char (varargin{i+1}); otherwise error ("fitglme: unknown option '%s'.", char (varargin{i})); endswitch endfor if (! any (strcmp (distr, {"normal", "binomial", "poisson"}))) error ("fitglme: Distribution must be 'normal', 'binomial', or 'poisson'."); endif if (isempty (link)) link = canonical_link (distr); endif mkey = lower (method); if (! any (strcmp (mkey, {"mpl", "rempl", "laplace", "approximatelaplace"}))) error (strcat ("fitglme: FitMethod must be 'MPL', 'REMPL', 'Laplace',", ... " or 'ApproximateLaplace'.")); endif ## --- decompose the formula --- S = parseWilkinsonFormula (formula, "mixed"); if (! S.HasRandom) error (strcat ("fitglme: FORMULA must contain a random-effects term", ... " '(...|...)'; use fitglm for fixed-effects models.")); endif if (isempty (S.Response)) error ("fitglme: FORMULA must specify a response variable."); endif ## --- drop rows with missing values in any model variable --- vars = collect_vars (S); tvars = tbl.Properties.VariableNames; mask = true (height (tbl), 1); for i = 1:numel (vars) if (! ismember (vars{i}, tvars)) error ("fitglme: variable '%s' is not in the table.", vars{i}); endif col = tbl.(vars{i}); if (isnumeric (col)) mask = mask & all (! isnan (col), 2); endif endfor tbl = tbl(mask, :); ## --- fixed design (formula-term order) and random designs --- [X, y, fenames] = parseWilkinsonFormula (S.FixedFormula, "model_matrix", tbl); perm = formula_order (fenames, S.FixedTerms, S.FixedIntercept); X = X(:, perm); fenames = fenames(perm); nt = numel (S.Random); Z = cell (1, nt); G = cell (1, nt); renames = cell (1, nt); grnames = cell (1, nt); for k = 1:nt rhs = terms_to_rhs (S.Random(k).Terms, S.Random(k).Intercept); [Zk, ~, znames] = parseWilkinsonFormula (["~ ", rhs], "model_matrix", tbl); Z{k} = Zk; renames{k} = znames; G{k} = combine_groups (tbl, S.Random(k).GroupVars); grnames{k} = S.Random(k).Group; endfor ## --- fit --- fit = __glmefit__ (X, y, Z, G, distr, link, mkey); info = fit; info.X = X; info.y = y; info.CoefficientNames = fenames; info.GroupNames = grnames; info.REPred = renames; info.FitMethod = method; info.Formula = formula; info.ResponseName = S.Response; glme = GeneralizedLinearMixedModel (info); endfunction function lnk = canonical_link (distr) switch (distr) case "normal", lnk = "identity"; case "binomial", lnk = "logit"; case "poisson", lnk = "log"; endswitch endfunction ## --- formula helpers (shared conventions with fitlme) --- function vars = collect_vars (S) vars = {}; if (! isempty (S.Response)), vars{end+1} = strtrim (S.Response); endif vars = [vars, flatten_terms(S.FixedTerms)]; for k = 1:numel (S.Random) vars = [vars, flatten_terms(S.Random(k).Terms), S.Random(k).GroupVars]; endfor vars = unique (vars); endfunction function names = flatten_terms (terms) names = {}; for i = 1:numel (terms), names = [names, terms{i}]; endfor endfunction function perm = formula_order (names, terms, intercept) assigned = false (size (names)); perm = []; if (intercept) j = find (strcmp (names, "(Intercept)"), 1); if (! isempty (j)), perm(end+1) = j; assigned(j) = true; endif endif for t = 1:numel (terms) tvars = sort (terms{t}); for j = 1:numel (names) if (assigned(j)), continue; endif if (isequal (sort (strsplit (names{j}, ":")), tvars)) perm(end+1) = j; assigned(j) = true; endif endfor endfor perm = [perm, find(! assigned)']; endfunction function rhs = terms_to_rhs (terms, intercept) parts = cell (1, numel (terms)); for i = 1:numel (terms), parts{i} = strjoin (terms{i}, ":"); endfor if (intercept) if (isempty (parts)), rhs = "1"; else, rhs = strjoin (parts, " + "); endif else rhs = [strjoin(parts, " + "), " - 1"]; endif endfunction function g = combine_groups (tbl, gvars) if (numel (gvars) == 1) g = tbl.(gvars{1}); else keys = []; for i = 1:numel (gvars) [~, ~, idx] = unique (tbl.(gvars{i})); keys = [keys, idx(:)]; endfor [~, ~, g] = unique (keys, "rows"); endif endfunction %!demo %! ## Poisson mixed model with a random intercept per group. %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! g = reshape (repmat (1:6, 7, 1), [], 1); %! x = randn (42, 1); %! y = poissrnd (exp (0.3 + 0.5 * x + 0.2 * reshape (repmat (randn (1, 6), 7, 1), [], 1))); %! tbl = table (y, x, g); %! glme = fitglme (tbl, "y ~ x + (1 | g)", "Distribution", "poisson"); %! disp (glme.Coefficients); ## MATLAB-verified parity (fitglme R2026a) on the reference GLME data. %!shared tbl %! xL = [0.032760004 0.70410822 -0.8646718 -0.28869454 0.51276678 -1.4975462 ... %! -1.4527871 -0.80013541 -1.644209 1.5137701 0.72905543 0.20880758 1.0856145 ... %! 0.62862577 -0.87409978 1.9178276 0.09748204 0.50697633 1.0247569 ... %! -0.92789896 -0.88921018 -0.98322849 -0.031378913 0.86875961 -0.91481141 ... %! 0.034324163 -0.25025257 -1.0575644 -0.86131607 -0.35355444 0.82950729 ... %! -0.36874363 0.061580868 0.55803564 -0.1763803 1.0482413 1.0137831 ... %! -0.94876976 -0.010703972 -0.35149845 -1.6828735 -1.0493301]'; %! yBin = [0 0 0 0 1 0 0 1 0 1 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 1 1 ... %! 0 1 1 0 1 1 0 0]'; %! yPois = [3 3 1 1 1 1 1 2 1 5 2 0 5 0 1 5 0 2 0 0 1 0 0 5 3 0 1 0 0 1 1 1 2 2 ... %! 1 1 4 0 1 0 0 1]'; %! g = [1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 ... %! 6 1 2 3 4 5 6]'; %! tbl = table (yBin, yPois, xL, g); %!test # binomial logit, MPL -- matches MATLAB fitglme %! glme = fitglme (tbl, "yBin ~ xL + (1 | g)", "Distribution", "binomial"); %! assert_equal (isa (glme, "GeneralizedLinearMixedModel"), true); %! assert_equal (glme.Coefficients.Estimate, [-0.55912; 0.76062], 1e-3); %! assert_equal (glme.Coefficients.SE, [0.33856; 0.39971], 1e-3); %! assert_equal (glme.LogLikelihood, -92.58872, 1e-2); %!test # poisson log, REMPL -- non-degenerate random-effect variance %! glme = fitglme (tbl, "yPois ~ xL + (1 | g)", "Distribution", "poisson", ... %! "FitMethod", "REMPL"); %! assert_equal (glme.Coefficients.Estimate, [0.23092; 0.67809], 1e-3); %! [psi, ~] = covarianceParameters (glme); %! assert_equal (psi{1}, 0.015918, 1e-3); %! assert_equal (glme.LogLikelihood, -56.90298, 1e-2); %!test # Laplace reports the marginal log-likelihood %! glme = fitglme (tbl, "yBin ~ xL + (1 | g)", "Distribution", "binomial", ... %! "FitMethod", "Laplace"); %! assert_equal (glme.LogLikelihood, -25.36010, 1e-2); %!test # coefficient stats: DF = n - p, tStat = Estimate / SE %! glme = fitglme (tbl, "yPois ~ xL + (1 | g)", "Distribution", "poisson"); %! C = glme.Coefficients; %! assert_equal (C.DF, [40; 40]); %! assert_equal (C.tStat, C.Estimate ./ C.SE, 1e-10); %!test # metadata %! glme = fitglme (tbl, "yBin ~ xL + (1 | g)", "Distribution", "binomial"); %! assert_equal (glme.Distribution, "binomial"); %! assert_equal (glme.Link, "logit"); %! assert_equal (glme.FitMethod, "MPL"); %! assert_equal (glme.ResponseName, "yBin"); %!test # all 8 fits (binomial/poisson x MPL/REMPL/Laplace/ApproximateLaplace) %! fits = { "yBin", "MPL", [-0.5591172; 0.7606217], [0.3385596; 0.3997127], -92.58872; ... %! "yBin", "REMPL", [-0.5591172; 0.7606217], [0.3385596; 0.3997127], -92.75162; ... %! "yBin", "Laplace", [-0.5591172; 0.7606217], [0.3385596; 0.3997127], -25.3601; ... %! "yBin", "ApproximateLaplace", [-0.5591172; 0.7606217], [0.3385596; 0.3997127], -25.3601; ... %! "yPois", "MPL", [0.2317919; 0.6848705], [0.1450691; 0.1496462], -55.13664; ... %! "yPois", "REMPL", [0.2309183; 0.6780919], [0.1539524; 0.1521982], -56.90296; ... %! "yPois", "Laplace", [0.2317919; 0.6848705], [0.1450691; 0.1496462], -59.2123; ... %! "yPois", "ApproximateLaplace", [0.2317919; 0.6848705], [0.1450691; 0.1496462], -59.2123 }; %! for k = 1:rows (fits) %! dist = "binomial"; %! if (strcmp (fits{k,1}, "yPois")), dist = "poisson"; endif %! glme = fitglme (tbl, [fits{k,1} " ~ xL + (1 | g)"], "Distribution", dist, ... %! "FitMethod", fits{k,2}); %! assert_equal (glme.Coefficients.Estimate, fits{k,3}, 1e-3); %! assert_equal (glme.Coefficients.SE, fits{k,4}, 1e-3); %! assert_equal (glme.LogLikelihood, fits{k,5}, 1e-2); %! endfor ## Input validation %!error fitglme (table ()) %!error fitglme (magic (3), "y ~ x + (1|g)") %!error fitglme (table ((1:3)', "VariableNames", {"y"}), "y ~ (1|y)", "Distribution", "xxx") %!error fitglme (table ((1:3)', "VariableNames", {"y"}), "y ~ 1") %!error fitglme (table ((1:3)', "VariableNames", {"y"}), "y ~ (1|y)", "bogus", 1) statistics-release-1.9.2/inst/Regression/fitlm.m000066400000000000000000001007641524624707500217450ustar00rootroot00000000000000## Copyright (C) 2022 Andrew Penn ## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{mdl} =} fitlm (@var{X}, @var{y}) ## @deftypefnx {statistics} {@var{mdl} =} fitlm (@var{tbl}) ## @deftypefnx {statistics} {@var{mdl} =} fitlm (@var{tbl}, @var{ResponseVarName}) ## @deftypefnx {statistics} {@var{mdl} =} fitlm (@var{tbl}, @var{y}) ## @deftypefnx {statistics} {@var{mdl} =} fitlm (@dots{}, @var{modelspec}) ## @deftypefnx {statistics} {@var{mdl} =} fitlm (@dots{}, @var{Name}, @var{Value}, @dots{}) ## ## Fit a linear regression model to data and return a @code{LinearModel} ## object. ## ## The returned object stores the fitted coefficients, their standard errors, ## t-statistics, and p-values, summary statistics of the fit (@math{R^2}, ## RMSE, F-statistic, etc.), and the residuals and diagnostics of the fit, and ## exposes methods such as @code{predict}, @code{plotResiduals}, ## @code{coefTest}, @code{addTerms}, and @code{removeTerms} for further ## analysis of the fitted model. ## ## @subheading Basic Syntax ## ## @code{@var{mdl} = fitlm (@var{X}, @var{y})} fits a linear regression model ## of the response @var{y} to the predictor data @var{X}. Unless removed via ## the @qcode{'Intercept'} option, the fitted model contains a constant ## (intercept) term and one linear term for every column of @var{X}. ## ## @itemize ## @item ## @var{X} is an @math{N*P} numeric or logical matrix of predictor data, where ## rows correspond to observations and columns correspond to variables. By ## default, the predictors are named @qcode{'x1'}, @qcode{'x2'}, @dots{}, ## @qcode{'xP'}. ## @item ## @var{X} can also be a categorical vector of length @math{N}, representing a ## single categorical predictor. In this case @var{y} must be supplied as the ## next argument, and the predictor is named @qcode{'x1'} by default. ## @item ## @var{y} is an @math{N*1} numeric or logical vector of response values, and ## must have the same number of observations (rows) as @var{X}. By default, ## the response is named @qcode{'y'}. ## @end itemize ## ## @code{@var{mdl} = fitlm (@var{tbl})} fits a linear regression model using ## the variables contained in the table (or dataset) @var{tbl}. By default, ## the last variable in @var{tbl} is used as the response and all other ## variables are used as predictors. Variables that are @code{categorical} ## arrays, cell arrays of character vectors, or logical arrays are ## automatically treated as categorical predictors. ## ## @code{@var{mdl} = fitlm (@var{tbl}, @var{ResponseVarName})} fits a model ## using the variable named @var{ResponseVarName} in @var{tbl} as the ## response, and all remaining variables in @var{tbl} as predictors. ## ## @code{@var{mdl} = fitlm (@var{tbl}, @var{y})} fits a model using the ## variables in @var{tbl} as predictors and the external numeric vector ## @var{y} as the response. @var{y} must have @code{height (@var{tbl})} ## elements. ## ## @subheading Model Specification ## ## @code{@var{mdl} = fitlm (@dots{}, @var{modelspec})} additionally specifies ## the terms of the model to fit, using any of the input combinations shown ## above. @var{modelspec} can be any of the following. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Value} @tab @var{Description} ## ## @item @qcode{'constant'} @tab Model contains only an intercept term. ## ## @item @qcode{'linear'} @tab Model contains an intercept and one term ## for each predictor variable. This is the default when @var{modelspec} is ## not specified. ## ## @item @qcode{'interactions'} @tab Model contains an intercept, all ## linear terms, and all pairwise products of distinct predictor variables ## (no squared terms). ## ## @item @qcode{'purequadratic'} @tab Model contains an intercept, all ## linear terms, and all squared terms. ## ## @item @qcode{'quadratic'} @tab Model contains an intercept, all linear ## terms, all pairwise products of distinct predictor variables, and all ## squared terms. ## ## @item @qcode{'full'} @tab Model contains an intercept and all terms up ## to and including the full @math{P}-way interaction of the predictor ## variables, i.e. every combination of one or more distinct predictors. ## ## @item terms matrix @tab A @math{T*P} or @math{T*(P+1)} numeric matrix, ## where @math{T} is the number of terms and @math{P} is the number of ## predictor variables. Each row represents one term, and the value in ## column @math{j} is the exponent to which predictor @math{j} is raised in ## that term; a row of all zeros represents the intercept. If a ## @math{T*(P+1)} matrix is supplied, its last column (representing the ## response variable) must be all zeros. ## ## @item Wilkinson formula @tab A character vector of the form ## @qcode{'y ~ terms'} describing the response and predictor terms using ## Wilkinson notation. The variable name to the left of @qcode{'~'} is used ## as the response, overriding any response implied elsewhere in the call. ## @end multitable ## ## When @var{modelspec} is given as a Wilkinson formula, the following ## operators may be used on its right-hand side to build up @code{terms}: ## ## @multitable @columnfractions 0.12 0.38 0.5 ## @headitem Operator @tab Meaning @tab Example ## @item @code{+} @tab add a term @tab @qcode{'x1 + x2'} adds @code{x1} and ## @code{x2} as separate terms ## @item @code{-} @tab remove a term @tab @qcode{'x1*x2 - x1:x2'} removes the ## interaction, leaving only @code{x1} and @code{x2} ## @item @code{*} @tab cross two terms @tab @qcode{'x1*x2'} expands to ## @code{x1}, @code{x2}, @code{x1:x2} ## @item @code{:} @tab interaction only @tab @qcode{'x1:x2'} adds only the ## interaction term between @code{x1} and @code{x2} ## @item @code{^} @tab power / crossing limit @tab @qcode{'x^2'} adds ## @code{x} and @code{x^2}; @qcode{'(x1+x2)^2'} expands to @code{x1}, ## @code{x2}, @code{x1:x2} ## @item @code{-1} @tab remove intercept @tab @qcode{'x1 + x2 - 1'} fits the ## model without a constant term ## @end multitable ## ## A formula includes an intercept term by default; append @qcode{'- 1'} to ## the formula to omit it. For a categorical predictor, @code{fitlm} ## generates the necessary indicator (dummy) variables automatically from the ## formula, so a formula does not need to be changed when the underlying ## design matrix changes. ## ## @subheading Options ## ## @code{@var{mdl} = fitlm (@dots{}, @var{Name}, @var{Value}, @dots{})} ## specifies additional options using one or more @qcode{Name-Value} pair ## arguments, which may be combined with @var{modelspec} or used on their own. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Intercept'} @tab A logical scalar indicating whether to ## include a constant (intercept) term in the model. Default is @qcode{true}. ## This option only applies when @var{modelspec} is a character vector model ## name (or omitted); it is ignored when @var{modelspec} is a terms matrix or ## a Wilkinson formula, where the intercept is instead controlled by the ## matrix/formula itself. ## ## @item @qcode{'Weights'} @tab A numeric vector of nonnegative ## observation weights, with one element per observation, used to fit a ## weighted least squares model. Default is a vector of ones, i.e. an ## unweighted ordinary least squares fit. ## ## @item @qcode{'Exclude'} @tab A numeric or logical vector specifying ## observations to exclude from the fit, given as row indices into the ## original data or as a logical mask the same length as the number of ## observations. Excluded observations, together with any observation that ## contains a missing (@qcode{NaN}) value in a predictor or the response, are ## recorded in the @code{ObservationInfo} property of the fitted model but do ## not contribute to the fitted coefficients or summary statistics. ## ## @item @qcode{'CategoricalVars'} @tab Specifies which predictor ## variables are treated as categorical, given as a vector of column indices, ## a logical vector, or a cell array of variable names (only valid for table ## input). Each categorical predictor with @math{L} distinct categories is ## expanded into @math{L-1} indicator (dummy) variables, using the first ## category (in sorted or original order) as the reference level that is ## omitted from the design matrix. Variables that are already ## @code{categorical} arrays or cell arrays of character vectors are always ## treated as categorical, regardless of this option. ## ## @item @qcode{'VarNames'} @tab A cell array of character vectors ## naming the predictor and response variables, listed in order with the ## response variable name last, e.g. @code{@{"x1", "x2", "y"@}} for two ## predictors. Only applies when @var{X} and @var{y} (or a categorical ## vector and @var{y}) are supplied directly, since table variables already ## carry their own names. By default, predictors are named @qcode{'x1'}, ## @qcode{'x2'}, etc. and the response is named @qcode{'y'}. ## ## @item @qcode{'ResponseVar'} @tab A character vector naming the ## response variable, used to override the response variable name that would ## otherwise be inferred (the last table variable, or @qcode{'y'} for matrix ## input). ## ## @item @qcode{'PredictorVars'} @tab A cell array of character vectors ## naming which variables in @var{tbl} to use as predictors. By default, all ## variables in @var{tbl} other than the response variable are used as ## predictors. ## ## @item @qcode{'RobustOpts'} @tab Selects ordinary least squares or ## robust regression fitting. This value can be @qcode{'off'} (default, ## ordinary least squares), @qcode{'on'} (robust fitting using the ## @qcode{'bisquare'} weighting function), the name of one of the weighting ## functions below, a function handle for a custom weighting function, or a ## scalar structure with fields @qcode{RobustWgtFun} and @qcode{Tune} ## specifying the weighting function and its tuning constant. Robust fitting ## uses Iteratively Reweighted Least Squares (IRLS), refitting the model with ## updated observation weights until the coefficients converge. Supported ## weighting function names: @qcode{'andrews'}, @qcode{'bisquare'}, ## @qcode{'cauchy'}, @qcode{'fair'}, @qcode{'huber'}, @qcode{'logistic'}, ## @qcode{'ols'}, @qcode{'talwar'}, @qcode{'welsch'}, each with its own default ## tuning constant. ## @end multitable ## ## @subheading Algorithm ## ## @code{fitlm} solves the (weighted) least squares problem by applying a ## pivoted QR decomposition to the design matrix, which remains numerically ## stable even when predictors are collinear; coefficients corresponding to ## columns beyond the numerically detected rank of the design matrix are set ## to zero. Robust fits refine this ordinary least squares solution using ## IRLS as described above. Observations with missing values in any variable ## used by the model, or explicitly excluded via @qcode{'Exclude'}, are ## omitted from the fit entirely and flagged in @code{ObservationInfo}, but ## are otherwise not counted as errors. ## ## @var{mdl} is returned as a @code{LinearModel} object. If ## @qcode{'RobustOpts'} is anything other than @qcode{'off'}, the returned ## model is a robust fit rather than an ordinary least squares fit, and its ## @code{Robust} property is populated accordingly. ## ## A categorical predictor expands to indicator columns, one per level bar the ## reference level, which the intercept carries. When the model has no ## intercept, the @emph{first} categorical predictor is given an indicator for ## every one of its levels instead, so that its coefficients are the group ## means; any further categorical predictor stays reference coded, which keeps ## the design full rank. This differs from MATLAB, which omits the reference ## level whether or not an intercept is present and so cannot fit the reference ## group at all -- for a three-level grouping variable @code{g}, MATLAB fits ## @code{y ~ g - 1} with two coefficients, predicts exactly 0 for every ## observation in the omitted group, and reports a negative @math{R^2}. This ## implementation returns three coefficients, one per group. ## ## @seealso{LinearModel} ## @end deftypefn function mdl = fitlm (varargin) if (nargin < 1) error ("fitlm: Not enough input arguments."); endif ## List of Name-Value keys used to check if the response variable y is missing. nv_keys = {'varnames', 'intercept', 'responsevar', 'predictorvars', ... 'categoricalvars', 'exclude', 'weights', 'robustopts'}; is_nv = @(s) (ischar (s) || isstring (s)) && ... any (strcmpi (char (s), nv_keys)); arg1 = varargin{1}; rest = varargin(2:end); if (isa (arg1, 'categorical')) if (! isvector (arg1)) error (strcat ("fitlm: Predictor variables must be numeric vectors,", ... " numeric matrices, or categorical vectors.")); endif if (isempty (rest) || is_nv(rest{1})) error ("fitlm: Y argument is required unless X is a dataset or table."); endif y_arg = rest{1}; if (numel (y_arg) != size (arg1, 1)) error ("fitlm: Predictor and response variables must have the same length."); endif if (! isvector (y_arg) || (! isnumeric (y_arg) && ! islogical (y_arg))) error ("fitlm: Response variable must be a numeric vector."); endif pred_name = 'x1'; resp_name = 'y'; tail = rest(2:end); keep = true (1, numel (tail)); for k = 1:2:numel (tail)-1 if (ischar (tail{k}) && strcmpi (tail{k}, 'VarNames') && iscell (tail{k+1})) vn = tail{k+1}; if (numel (vn) >= 1); pred_name = vn{1}; endif if (numel (vn) >= 2); resp_name = vn{2}; endif keep(k:k+1) = false; endif endfor tail = tail(keep); tbl = table (arg1(:), double (y_arg(:)), 'VariableNames', {pred_name, resp_name}); mdl = fitlm (tbl, tail{:}); return; endif if (istable (arg1)) response = []; modelspec = []; nv_args = {}; if (! isempty (rest)) ## Even length starting with Name-Value key means all are Name-Value pairs if (mod (numel (rest), 2) == 0 && is_nv(rest{1})) nv_args = rest; else arg2 = rest{1}; after_arg2 = rest(2:end); n_rows = height (arg1); n_cols = width (arg1); col_names = arg1.Properties.VariableNames; if (ischar (arg2) || isstring (arg2)) s = char (arg2); if (any (s == '~')) ## Wilkinson formula string modelspec = s; if (mod (numel (after_arg2), 2) != 0) error ("fitlm: Name-Value arguments must be in pairs."); endif nv_args = after_arg2; elseif (any (strcmp (s, col_names))) ## Response variable name; ODD/EVEN for remainder response = s; [modelspec, nv_args] = lm_split_args (after_arg2); else ## Modelspec keyword or invalid string; LinearModel will validate modelspec = s; if (mod (numel (after_arg2), 2) != 0) error ("fitlm: Name-Value arguments must be in pairs."); endif nv_args = after_arg2; endif elseif (isnumeric (arg2) || islogical (arg2)) [nr2, nc2] = size (arg2); if (isempty (arg2)) error (strcat ("fitlm: The terms matrix must have one column", ... " for each variable in the dataset or table.")); elseif (nc2 == n_cols) ## Column count matches table, so it is a terms matrix if (! any (all (double (arg2) == 0, 1))) error (strcat ("fitlm: Cannot determine the response", ... " variable from the terms matrix.")); endif modelspec = double (arg2); if (mod (numel (after_arg2), 2) != 0) error ("fitlm: Name-Value arguments must be in pairs."); endif nv_args = after_arg2; elseif (nc2 == 1 && nr2 == n_rows) ## Single column matching table height is an external y vector response = double (arg2(:)); [modelspec, nv_args] = lm_split_args (after_arg2); else error ("fitlm: Predictor and response variables must have the same length."); endif else error ("fitlm: invalid second argument for table input."); endif endif endif mdl = LinearModel (arg1, response, modelspec, nv_args{:}); elseif ((isnumeric (arg1) || islogical (arg1)) && ismatrix (arg1)) n = size (arg1, 1); ## Ensure the response variable y is provided. if (isempty (rest) || is_nv(rest{1})) error ("fitlm: Y argument is required unless X is a dataset or table."); endif arg2 = rest{1}; ## Ensure the response variable has the correct length. if (max ([size(arg2), 0]) != n) error ("fitlm: Predictor and response variables must have the same length."); endif if (! isvector (arg2)) error ("fitlm: Response variable must be a numeric vector."); endif if (! isnumeric (arg2) && ! islogical (arg2)) error ("fitlm: Response variable must be a numeric vector."); endif y = double (arg2(:)); [modelspec, nv_args] = lm_split_args (rest(2:end)); mdl = LinearModel (arg1, y, modelspec, nv_args{:}); else error (strcat ("fitlm: Predictor variables must be numeric vectors,", ... " numeric matrices, or categorical vectors.")); endif endfunction ## If count is odd, the first element is the modelspec and the rest are Name-Value pairs. ## If count is even, there is no modelspec and all elements are Name-Value pairs. function [modelspec, nv_args] = lm_split_args (remaining) if (isempty (remaining)) modelspec = []; nv_args = {}; elseif (mod (numel (remaining), 2) == 1) modelspec = remaining{1}; nv_args = remaining(2:end); else modelspec = []; nv_args = remaining; endif endfunction %!demo %! %! ## The simplest call: a matrix of predictor data and a response vector. %! ## Ten students study for varying numbers of hours before an exam, with %! ## their resulting scores recorded. With no `modelspec`, `fitlm` fits an %! ## intercept plus one linear term per column of the predictor matrix. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Score = [52;55;61;64;70;73;77;81;85;90]; %! mdl = fitlm (Hours, Score) %!demo %! %! ## Table input with a Wilkinson formula, and a categorical predictor %! ## detected automatically from its cell-array type. %! ## Nine stores in three regions report ad spend and sales. `Region` is %! ## a cell array of strings, so it is expanded into indicator columns %! ## without needing `'CategoricalVars'` to say so explicitly. %! AdSpend = [10;20;30;15;25;35;12;22;32]; %! Region = {'North';'North';'North';'South';'South';'South'; ... %! 'East';'East';'East'}; %! Sales = [15;18;24;20;27;33;12;19;26]; %! T = table (AdSpend, Region, Sales, ... %! 'VariableNames', {'AdSpend','Region','Sales'}); %! mdl = fitlm (T, 'Sales ~ AdSpend + Region') %!demo %! %! ## A terms matrix used directly as modelspec, instead of a keyword or a %! ## formula string. %! ## Weekly sales depend on temperature and humidity. Each row of the %! ## terms matrix is one term, and each column is the exponent of one %! ## predictor in that term -- here, both main effects plus their %! ## interaction. %! Temp = [60;65;70;75;80;85;90]; %! Humidity = [30;35;40;45;50;55;60]; %! Sales = [200;230;260;300;340;370;410]; %! T_terms = [0 0; 1 0; 0 1; 1 1]; %! mdl = fitlm ([Temp, Humidity], Sales, T_terms) %!demo %! %! ## Observation weights and an excluded observation used together. %! ## Ten observations follow a roughly linear trend, except one clear %! ## outlier. `'Exclude'` leaves that observation out of the fit %! ## entirely, while `'Weights'` gives the remaining observations %! ## unequal influence on the fit. %! x = [1;2;3;4;5;6;7;8;9;10]; %! y = [10;13;15;40;22;25;29;33;35;39]; %! w = [1;1;2;1;2;1;2;1;2;1]; %! mdl = fitlm (x, y, 'Weights', w, 'Exclude', 4) %!demo %! %! ## Robust regression versus an ordinary fit, on data with a planted %! ## outlier. %! ## Twelve observations follow a linear trend with a small amount of %! ## noise, except one observation shifted far off the line. The %! ## ordinary fit is pulled toward the outlier; the robust fit, using %! ## iteratively reweighted least squares with the bisquare weighting %! ## function, downweights it instead. %! x = (1:12)'; %! y = 3 + 2*x + 0.5*sin (x); %! y(10) = y(10) + 30; %! mdl_ols = fitlm (x, y) %! mdl_robust = fitlm (x, y, 'RobustOpts', 'bisquare') %!demo %! %! ## Selecting specific predictors and a specific response by name from a %! ## larger table, using the `carsmall` data set. %! ## `'ResponseVar'` and `'PredictorVars'` pick out exactly which %! ## columns of the table to use, regardless of how many other variables %! ## the table also contains. %! load carsmall %! T = table (Weight, Acceleration, MPG); %! mdl = fitlm (T, 'ResponseVar', 'MPG', ... %! 'PredictorVars', {'Weight', 'Acceleration'}) %!demo %! y = [ 8.706 10.362 11.552 6.941 10.983 10.092 6.421 14.943 15.931 ... %! 22.968 18.590 16.567 15.944 21.637 14.492 17.965 18.851 22.891 ... %! 22.028 16.884 17.252 18.325 25.435 19.141 21.238 22.196 18.038 ... %! 22.628 31.163 26.053 24.419 32.145 28.966 30.207 29.142 33.212 ... %! 25.694 ]'; %! X = [1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5]'; %! %! mdl = fitlm (X, y, 'linear', 'CategoricalVars', 1) %!demo %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! brands = {'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'}; %! popper = {'oil', 'oil', 'oil'; 'oil', 'oil', 'oil'; 'oil', 'oil', 'oil'; ... %! 'air', 'air', 'air'; 'air', 'air', 'air'; 'air', 'air', 'air'}; %! %! T = table (brands(:), popper(:), 'VariableNames', {'brands', 'popper'}); %! mdl = fitlm (T, popcorn(:), 'interactions') %!test %! y = [ 8.706 10.362 11.552 6.941 10.983 10.092 6.421 14.943 15.931 ... %! 22.968 18.590 16.567 15.944 21.637 14.492 17.965 18.851 22.891 ... %! 22.028 16.884 17.252 18.325 25.435 19.141 21.238 22.196 18.038 ... %! 22.628 31.163 26.053 24.419 32.145 28.966 30.207 29.142 33.212 ... %! 25.694 ]'; %! X = [1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5]'; %! fitlm (X, y, 'CategoricalVars', 1); %! fitlm (X, y, 'constant', 'CategoricalVars', 1); %! fitlm (X, y, 'linear', 'CategoricalVars', 1); %! mdl = fitlm (X, y, 'linear', 'CategoricalVars', 1); %! assert_equal (mdl.Coefficients.Estimate(1), 10, 1e-04); %! assert_equal (mdl.Coefficients.Estimate(2), 7.99999999999999, 1e-09); %! assert_equal (mdl.Coefficients.Estimate(3), 8.99999999999999, 1e-09); %! assert_equal (mdl.Coefficients.Estimate(4), 11.0001428571429, 1e-09); %! assert_equal (mdl.Coefficients.Estimate(5), 19.0001111111111, 1e-09); %! assert_equal (mdl.Coefficients.SE(1), 1.01775379540949, 1e-09); %! assert_equal (mdl.Coefficients.SE(2), 1.64107868458008, 1e-09); %! assert_equal (mdl.Coefficients.SE(3), 1.43932122062479, 1e-09); %! assert_equal (mdl.Coefficients.SE(4), 1.48983900477565, 1e-09); %! assert_equal (mdl.Coefficients.SE(5), 1.3987687997822, 1e-09); %! assert_equal (mdl.Coefficients.tStat(1), 9.82555903510687, 1e-09); %! assert_equal (mdl.Coefficients.tStat(2), 4.87484242844031, 1e-09); %! assert_equal (mdl.Coefficients.tStat(3), 6.25294748040552, 1e-09); %! assert_equal (mdl.Coefficients.tStat(4), 7.38344399756088, 1e-09); %! assert_equal (mdl.Coefficients.tStat(5), 13.5834536158296, 1e-09); %! assert_equal (mdl.Coefficients.pValue(2), 2.85812420217862e-05, 1e-12); %! assert_equal (mdl.Coefficients.pValue(3), 5.22936741204002e-07, 1e-06); %! assert_equal (mdl.Coefficients.pValue(4), 2.12794763209106e-08, 1e-07); %! assert_equal (mdl.Coefficients.pValue(5), 7.82091664406755e-15, 1e-08); %!test %! popcorn = [5.5, 4.5, 3.5; 5.5, 4.5, 4.0; 6.0, 4.0, 3.0; ... %! 6.5, 5.0, 4.0; 7.0, 5.5, 5.0; 7.0, 5.0, 4.5]; %! brands = bsxfun (@times, ones (6, 1), [1, 2, 3]); %! popper = bsxfun (@times, [1; 1; 1; 2; 2; 2], ones (1, 3)); %! X = [brands(:), popper(:)]; %! mdl = fitlm (X, popcorn(:), 'interactions', 'CategoricalVars', [1, 2]); %! assert_equal (mdl.Coefficients.Estimate(1), 5.66666666666667, 1e-09); %! assert_equal (mdl.Coefficients.Estimate(2), -1.33333333333333, 1e-09); %! assert_equal (mdl.Coefficients.Estimate(3), -2.16666666666667, 1e-09); %! assert_equal (mdl.Coefficients.Estimate(4), 1.16666666666667, 1e-09); %! assert_equal (mdl.Coefficients.Estimate(6), -0.333333333333334, 1e-09); %! assert_equal (mdl.Coefficients.Estimate(7), -0.166666666666667, 1e-09); %! assert_equal (mdl.Coefficients.SE(1), 0.215165741455965, 1e-09); %! assert_equal (mdl.Coefficients.SE(2), 0.304290309725089, 1e-09); %! assert_equal (mdl.Coefficients.SE(3), 0.304290309725089, 1e-09); %! assert_equal (mdl.Coefficients.SE(4), 0.304290309725089, 1e-09); %! assert_equal (mdl.Coefficients.SE(6), 0.43033148291193, 1e-09); %! assert_equal (mdl.Coefficients.SE(7), 0.43033148291193, 1e-09); %! assert_equal (mdl.Coefficients.tStat(1), 26.3362867542108, 1e-09); %! assert_equal (mdl.Coefficients.tStat(2), -4.38178046004138, 1e-09); %! assert_equal (mdl.Coefficients.tStat(3), -7.12039324756724, 1e-09); %! assert_equal (mdl.Coefficients.tStat(4), 3.83405790253621, 1e-09); %! assert_equal (mdl.Coefficients.tStat(6), -0.774596669241495, 1e-09); %! assert_equal (mdl.Coefficients.tStat(7), -0.387298334620748, 1e-09); %! assert_equal (mdl.Coefficients.pValue(1), 5.49841502258254e-12, 1e-09); %! assert_equal (mdl.Coefficients.pValue(2), 0.000893505495903642, 1e-09); %! assert_equal (mdl.Coefficients.pValue(3), 1.21291454302428e-05, 1e-09); %! assert_equal (mdl.Coefficients.pValue(4), 0.00237798044119407, 1e-09); %! assert_equal (mdl.Coefficients.pValue(6), 0.453570536021938, 1e-09); %! assert_equal (mdl.Coefficients.pValue(7), 0.705316781644046, 1e-09); %! brands = {'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'; ... %! 'Gourmet', 'National', 'Generic'}; %! popper = {'oil', 'oil', 'oil'; 'oil', 'oil', 'oil'; 'oil', 'oil', 'oil'; ... %! 'air', 'air', 'air'; 'air', 'air', 'air'; 'air', 'air', 'air'}; %! T = table (brands(:), popper(:), 'VariableNames', {'brands', 'popper'}); %! mdl = fitlm (T, popcorn(:), 'interactions'); %!test %! load carsmall %! X = [Weight, Horsepower, Acceleration]; %! fitlm (X, MPG, 'constant'); %! mdl = fitlm (X, MPG, 'linear'); %! assert_equal (mdl.Coefficients.Estimate(1), 47.9767628118615, 1e-09); %! assert_equal (mdl.Coefficients.Estimate(2), -0.00654155878851796, 1e-09); %! assert_equal (mdl.Coefficients.Estimate(3), -0.0429433065881864, 1e-09); %! assert_equal (mdl.Coefficients.Estimate(4), -0.0115826516894871, 1e-09); %! assert_equal (mdl.Coefficients.SE(1), 3.87851641748551, 1e-09); %! assert_equal (mdl.Coefficients.SE(2), 0.00112741016370336, 1e-09); %! assert_equal (mdl.Coefficients.SE(3), 0.0243130608813806, 1e-09); %! assert_equal (mdl.Coefficients.SE(4), 0.193325043113178, 1e-09); %! assert_equal (mdl.Coefficients.tStat(1), 12.369874881944, 1e-09); %! assert_equal (mdl.Coefficients.tStat(2), -5.80228828790225, 1e-09); %! assert_equal (mdl.Coefficients.tStat(3), -1.76626492228599, 1e-09); %! assert_equal (mdl.Coefficients.tStat(4), -0.0599128364487485, 1e-09); %! assert_equal (mdl.Coefficients.pValue(1), 4.89570341688996e-21, 1e-09); %! assert_equal (mdl.Coefficients.pValue(2), 9.87424814144e-08, 1e-09); %! assert_equal (mdl.Coefficients.pValue(3), 0.0807803098213114, 1e-09); %! assert_equal (mdl.Coefficients.pValue(4), 0.952359384151778, 1e-09); %!shared X, y, yl, T1, T2, T3, C %! X = [1 2; 3 4; 5 6]; %! y = [2; 4; 5]; %! yl = logical ([1; 0; 1]); %! T1 = table ([1;2;3], [4;5;6], 'VariableNames', {'x1','x2'}); %! T2 = table ([1;2;3], [4;5;6], 'VariableNames', {'x1','y'}); %! T3 = table ([1;2;3], [4;5;6], [2;4;5], 'VariableNames', {'x1','x2','y'}); %! C = categorical ({'a';'b';'a'}); %!test %! assert_equal (class (fitlm (X, y)), 'LinearModel'); %!test %! assert_equal (class (fitlm (X, yl)), 'LinearModel'); %!test %! assert_equal (class (fitlm (X, y, 'linear')), 'LinearModel'); %!test %! assert_equal (class (fitlm (X, y, [1 0; 0 1])), 'LinearModel'); %!test %! assert_equal (class (fitlm (X, y, 'Intercept', false)), 'LinearModel'); %!test %! assert_equal (class (fitlm (X, y, 'linear', 'Weights', [1;2;1])), 'LinearModel'); %!test %! mdl = fitlm (C, y); %! assert_equal (class (mdl), 'LinearModel'); %! assert_equal (mdl.VariableNames, {'x1'; 'y'}); %!test %! mdl = fitlm (C, y, 'VarNames', {'grp', 'score'}); %! assert_equal (mdl.VariableNames, {'grp'; 'score'}); %!test %! assert_equal (class (fitlm (C, y, 'Intercept', false)), 'LinearModel'); %!test %! assert_equal (class (fitlm (T2)), 'LinearModel'); %!test %! assert_equal (class (fitlm (T3)), 'LinearModel'); %!test %! assert_equal (class (fitlm (T3, 'Exclude', [2])), 'LinearModel'); %!test %! assert_equal (class (fitlm (T2, 'y')), 'LinearModel'); %!test %! assert_equal (class (fitlm (T3, 'x1')), 'LinearModel'); %!test %! assert_equal (class (fitlm (T3, 'y ~ x1 + x2')), 'LinearModel'); %!test %! assert_equal (class (fitlm (T1, 'linear')), 'LinearModel'); %!test %! assert_equal (class (fitlm (T1, [2;4;5])), 'LinearModel'); %!test %! assert_equal (class (fitlm (T2, [0 0; 1 0])), 'LinearModel'); %!test %! assert_equal (class (fitlm (T2, 'y', 'linear', 'Intercept', false)), 'LinearModel'); %!error fitlm () %!error ... %! fitlm ('hello', y) %!error ... %! fitlm (struct ('a', 1), [1;2]) %!error ... %! fitlm (categorical ([1 2; 3 4]), y) %!error ... %! fitlm (C) %!error ... %! fitlm (C, 'Intercept', false) %!error ... %! fitlm (C, [1;2]) %!error fitlm (C, {'a';'b';'a'}) %!error fitlm (X) %!error ... %! fitlm (X, 'Weights', [1;1;1]) %!error ... %! fitlm (X, []) %!error ... %! fitlm (X, [1;2]) %!error fitlm (X, ones (3, 2)) %!error fitlm (X, {'1';'2';'3'}) %!error ... %! fitlm (T1, []) %!error ... %! fitlm (T1, ones (1, 2)) %!error ... %! fitlm (T1, ones (4, 1)) %!error ... %! fitlm (T1, ones (2, 3)) %!error fitlm (T1, {1, 2}) %!error fitlm (T2, 'y ~ x1', 'linear') %!error fitlm (T1, 'linear', 'Weights') %!error fitlm (T2, [0 0; 1 0], 'Weights') statistics-release-1.9.2/inst/Regression/fitlme.m000066400000000000000000000316511524624707500221100ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{lme} =} fitlme (@var{tbl}, @var{formula}) ## @deftypefnx {statistics} {@var{lme} =} fitlme (@dots{}, @var{name}, @var{value}) ## ## Fit a linear mixed-effects model specified by a formula. ## ## @code{fitlme (@var{tbl}, @var{formula})} fits the linear mixed-effects model ## described by @var{formula} to the variables in the table @var{tbl}, and ## returns a @code{LinearMixedModel} object. ## ## @var{formula} is a character vector in Wilkinson notation extended with ## random-effects terms, for example @qcode{"y ~ x1 + x2 + (1 | g)"}. The part ## to the left of @code{~} names the response; the fixed-effects part uses the ## usual operators (@code{+}, @code{*}, @code{:}, @code{^}, and @code{-1} to ## drop the intercept); and each random-effects term ## @code{(@var{expr} | @var{group})} ## adds random intercepts and slopes @var{expr} grouped by the factor ## @var{group} (or an interaction of factors, e.g.@: @code{g1:g2}). As with ## fixed effects, a random intercept is implicit unless suppressed with ## @code{0} or @code{-1}. ## ## Rows of @var{tbl} with missing values in any model variable are removed ## before fitting. ## ## The following @var{name}/@var{value} pairs are accepted: ## ## @table @asis ## @item @qcode{"FitMethod"} ## The estimation criterion, @qcode{"ML"} (maximum likelihood, the default) or ## @qcode{"REML"} (restricted maximum likelihood). ## @end table ## ## @seealso{LinearMixedModel, fitlmematrix, fitlm, parseWilkinsonFormula} ## @end deftypefn function lme = fitlme (tbl, formula, varargin) if (nargin < 2) print_usage (); endif if (! isa (tbl, "table")) error ("fitlme: TBL must be a table."); endif if (! (ischar (formula) || (isstring (formula) && isscalar (formula)))) error ("fitlme: FORMULA must be a character vector."); endif formula = char (formula); ## --- options --- method = "ML"; if (mod (numel (varargin), 2) != 0) error ("fitlme: name/value arguments must come in pairs."); endif for i = 1:2:numel (varargin) switch (lower (char (varargin{i}))) case "fitmethod" method = upper (char (varargin{i+1})); if (! any (strcmp (method, {"ML", "REML"}))) error ("fitlme: FitMethod must be 'ML' or 'REML'."); endif otherwise error ("fitlme: unknown option '%s'.", char (varargin{i})); endswitch endfor ## --- decompose the formula --- S = parseWilkinsonFormula (formula, "mixed"); if (! S.HasRandom) error (strcat ("fitlme: FORMULA must contain a random-effects term", ... " '(...|...)'; use fitlm for fixed-effects models.")); endif if (isempty (S.Response)) error ("fitlme: FORMULA must specify a response variable."); endif if (! isempty (strfind (S.Response, ","))) error ("fitlme: multiple responses are not supported."); endif ## --- drop rows with missing values in any model variable --- vars = collect_vars (S); tvars = tbl.Properties.VariableNames; mask = true (height (tbl), 1); for i = 1:numel (vars) if (! ismember (vars{i}, tvars)) error ("fitlme: variable '%s' is not in the table.", vars{i}); endif col = tbl.(vars{i}); if (isnumeric (col)) mask = mask & all (! isnan (col), 2); endif endfor tbl = tbl(mask, :); ## --- fixed-effects design (reordered from the alphabetical 'model_matrix' ## column order to the formula's term order, to match MATLAB) --- [X, y, fenames] = parseWilkinsonFormula (S.FixedFormula, "model_matrix", tbl); perm = formula_order (fenames, S.FixedTerms, S.FixedIntercept); X = X(:, perm); fenames = fenames(perm); ## --- random-effects designs and grouping --- nt = numel (S.Random); Z = cell (1, nt); G = cell (1, nt); renames = cell (1, nt); grnames = cell (1, nt); for k = 1:nt rhs = terms_to_rhs (S.Random(k).Terms, S.Random(k).Intercept); [Zk, ~, znames] = parseWilkinsonFormula (["~ ", rhs], "model_matrix", tbl); Z{k} = Zk; renames{k} = znames; G{k} = combine_groups (tbl, S.Random(k).GroupVars); grnames{k} = S.Random(k).Group; endfor ## --- fit --- lme = fitlmematrix (X, y, Z, G, "FitMethod", method, ... "FixedEffectPredictors", fenames, "RandomEffectPredictors", renames, ... "RandomEffectGroups", grnames, "Formula", formula, ... "ResponseName", S.Response); endfunction ## All variable names referenced by the decomposed formula. function vars = collect_vars (S) vars = {}; if (! isempty (S.Response)) vars{end+1} = strtrim (S.Response); endif vars = [vars, flatten_terms(S.FixedTerms)]; for k = 1:numel (S.Random) vars = [vars, flatten_terms(S.Random(k).Terms), S.Random(k).GroupVars]; endfor vars = unique (vars); endfunction function names = flatten_terms (terms) names = {}; for i = 1:numel (terms) names = [names, terms{i}]; endfor endfunction ## Permutation putting the alphabetically-ordered model_matrix columns into the ## formula's term order: intercept first, then each fixed term (matched by its ## set of variables), with any unmatched columns (e.g. categorical dummies) ## appended in their original order. function perm = formula_order (names, terms, intercept) assigned = false (size (names)); perm = []; if (intercept) j = find (strcmp (names, "(Intercept)"), 1); if (! isempty (j)) perm(end+1) = j; assigned(j) = true; endif endif for t = 1:numel (terms) tvars = sort (terms{t}); for j = 1:numel (names) if (assigned(j)) continue; endif cvars = sort (strsplit (names{j}, ":")); if (isequal (cvars, tvars)) perm(end+1) = j; assigned(j) = true; endif endfor endfor perm = [perm, find(! assigned)']; endfunction ## Formula RHS string for a random-effects design expression. function rhs = terms_to_rhs (terms, intercept) parts = cell (1, numel (terms)); for i = 1:numel (terms) parts{i} = strjoin (terms{i}, ":"); endfor if (intercept) if (isempty (parts)) rhs = "1"; else rhs = strjoin (parts, " + "); endif else rhs = [strjoin(parts, " + "), " - 1"]; endif endfunction ## Grouping index for one random-effects term: a single grouping variable, or ## the observed combinations of an interaction of grouping variables. function g = combine_groups (tbl, gvars) if (numel (gvars) == 1) g = tbl.(gvars{1}); else keys = []; for i = 1:numel (gvars) [~, ~, idx] = unique (tbl.(gvars{i})); keys = [keys, idx(:)]; endfor [~, ~, g] = unique (keys, "rows"); endif endfunction %!demo %! ## Random-intercept model: sleep-study-like data with per-subject intercepts. %! subject = reshape (repmat (1:6, 5, 1), [], 1); %! days = repmat ((0:4)', 6, 1); %! b0 = reshape (repmat ([1 -1 0.5 -0.5 0.2 -0.2], 5, 1), [], 1); %! y = 250 + 10 * days + 15 * b0 + 3 * sin (1:30)'; %! tbl = table (y, days, subject); %! lme = fitlme (tbl, "y ~ days + (1 | subject)", "FitMethod", "REML"); %! disp (lme.Coefficients); ## --- MATLAB-verified parity: the formula path reproduces the design-matrix fit ## and MATLAB's fitlme (R2026a) on the reference 42-row data set. --- %!shared tbl, xL %! xL = [0.032760004 0.70410822 -0.8646718 -0.28869454 0.51276678 -1.4975462 ... %! -1.4527871 -0.80013541 -1.644209 1.5137701 0.72905543 0.20880758 1.0856145 ... %! 0.62862577 -0.87409978 1.9178276 0.09748204 0.50697633 1.0247569 ... %! -0.92789896 -0.88921018 -0.98322849 -0.031378913 0.86875961 -0.91481141 ... %! 0.034324163 -0.25025257 -1.0575644 -0.86131607 -0.35355444 0.82950729 ... %! -0.36874363 0.061580868 0.55803564 -0.1763803 1.0482413 1.0137831 ... %! -0.94876976 -0.010703972 -0.35149845 -1.6828735 -1.0493301]'; %! x2 = [0.68979276 0.0074354814 -0.45697437 -0.5636481 1.4567202 -0.97829955 ... %! -1.12922 -0.030542479 1.5847779 -0.87837755 0.24121762 0.68747601 ... %! -0.56728765 0.98895053 -0.39350661 0.85326015 0.36524343 0.15824977 ... %! -1.7665212 0.59808246 -0.55763708 -1.1982294 -2.1473319 0.22521416 ... %! 0.37034398 -1.880586 0.052941033 -0.70016994 0.2174853 -1.7797082 ... %! 0.51971317 -0.35551286 1.9845963 -1.3498848 -0.63514097 -0.78794714 ... %! 1.3681179 1.4423152 -0.51233905 0.30238864 2.0458136 0.17326323]'; %! yL = [3.5635971 -0.36763498 1.4192715 3.4471073 2.2760668 3.6420287 ... %! 4.4481522 1.5292777 5.437158 0.44016873 0.7259756 2.9771749 1.2941347 ... %! 0.26407508 1.9673466 0.77589984 1.2176078 0.8528384 -0.86522876 2.8651388 ... %! 2.0127931 3.1242841 -0.49164361 1.3192295 5.3782287 -0.40680701 2.3989882 ... %! 3.606108 1.9193197 1.6713765 2.4383124 1.9314844 3.5112706 0.91644553 ... %! 0.034923706 -0.15510033 2.1765206 2.5327412 3.1421219 3.3472574 5.343425 ... %! 3.8775899]'; %! g = [1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 ... %! 5 6 1 2 3 4 5 6]'; %! g2 = [1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 ... %! 2 3 1 2 3 1 2 3]'; %! tbl = table (yL, xL, x2, g, g2); %!test # every random-effects syntax form reproduces MATLAB's log-likelihood %! forms = { "yL ~ xL + x2 + (1|g)", -49.016009; ... %! "yL ~ xL + x2 + (1 + xL|g)", -48.280962; ... %! "yL ~ xL + x2 + (1|g) + (xL-1|g)", -48.947341; ... %! "yL ~ xL + x2 + (xL-1|g)", -61.635296; ... %! "yL ~ xL + x2 + (-1 + xL|g)", -61.635296; ... %! "yL ~ xL + x2 + (1|g) + (1|g2)", -45.924711; ... %! "yL ~ xL + x2 + (1|g) + (1|g:g2)", -49.016009; ... %! "yL ~ xL*x2 + (1|g)", -50.366415; ... %! "yL ~ xL + x2 + (1 + xL + x2|g)", -48.162461 }; %! for k = 1:rows (forms) %! lme = fitlme (tbl, forms{k,1}, "FitMethod", "REML"); %! assert_equal (lme.LogLikelihood, forms{k,2}, 1e-4); %! endfor %!test # random intercept via formula -- matches MATLAB fitlme %! lme = fitlme (tbl, "yL ~ xL + x2 + (1 | g)", "FitMethod", "REML"); %! assert_equal (isa (lme, "LinearMixedModel"), true); %! assert_equal (lme.Coefficients.Estimate, ... %! [1.96839; -1.37926; 0.811747], 1e-4); %! assert_equal (lme.Coefficients.SE, [0.376509; 0.113264; 0.0966571], 1e-5); %! assert_equal (lme.LogLikelihood, -49.016009, 1e-4); %! [psi, mse] = covarianceParameters (lme); %! assert_equal (psi{1}, 0.79429917, 1e-3); %! assert_equal (mse, 0.38539058, 1e-3); %!test # correlated random intercept + slope via formula -- matches MATLAB %! lme = fitlme (tbl, "yL ~ xL + x2 + (1 + xL | g)", "FitMethod", "REML"); %! assert_equal (lme.Coefficients.Estimate, ... %! [2.0034354; -1.3374715; 0.83788802], 1e-3); %! assert_equal (lme.LogLikelihood, -48.280962, 1e-3); %! psi = covarianceParameters (lme); %! assert_equal (psi{1}, ... %! [0.7846112, -0.14287556; -0.14287556, 0.026017248], 1e-3); %!test # formula fit equals the equivalent fitlmematrix fit %! lme_f = fitlme (tbl, "yL ~ xL + x2 + (1 | g)", "FitMethod", "REML"); %! X = [ones(42,1), xL, tbl.x2]; %! lme_m = fitlmematrix (X, tbl.yL, ones (42, 1), tbl.g, "FitMethod", "REML"); %! assert_equal (lme_f.Coefficients.Estimate, ... %! lme_m.Coefficients.Estimate, 1e-8); %! assert_equal (lme_f.LogLikelihood, lme_m.LogLikelihood, 1e-8); %!test # slope-only random term (no random intercept) %! lme = fitlme (tbl, "yL ~ xL + x2 + (xL - 1 | g)", "FitMethod", "REML"); %! [psi, ~] = covarianceParameters (lme); %! assert_equal (isscalar (psi{1}), true); # 1x1 covariance (slope only) %!test # metadata: Formula and ResponseName are stored %! lme = fitlme (tbl, "yL ~ xL + (1 | g)"); %! assert_equal (lme.Formula, "yL ~ xL + (1 | g)"); %! assert_equal (lme.ResponseName, "yL"); %! assert_equal (lme.FitMethod, "ML"); # default %!test # rows with missing values are dropped before fitting %! t2 = tbl; %! t2.yL(3) = NaN; t2.xL(10) = NaN; %! lme = fitlme (t2, "yL ~ xL + x2 + (1 | g)"); %! assert_equal (lme.NumObservations, 40); ## Input validation %!error fitlme (table ()) %!error fitlme (magic (3), "y ~ x + (1|g)") %!error fitlme (table (), 5) %!error fitlme (table ((1:3)', "VariableNames", {"y"}), "y ~ 1") %!error fitlme (table ((1:3)', "VariableNames", {"y"}), "y ~ (1|y)", "bogus", 1) %!error fitlme (table ((1:3)', "VariableNames", {"y"}), "y ~ (1|y)", "FitMethod", "xxx") statistics-release-1.9.2/inst/Regression/fitlmematrix.m000066400000000000000000000335401524624707500233340ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{lme} =} fitlmematrix (@var{X}, @var{y}, @var{Z}, @var{G}) ## @deftypefnx {statistics} {@var{lme} =} fitlmematrix (@dots{}, @var{name}, @var{value}) ## ## Fit a linear mixed-effects model from design matrices. ## ## @code{fitlmematrix (@var{X}, @var{y}, @var{Z}, @var{G})} fits the linear ## mixed-effects model ## @tex ## $$ y = X\beta + Zb + \varepsilon $$ ## ## @end tex ## @ifnottex ## @code{y = X*beta + Z*b + e} ## @end ifnottex ## with fixed-effects design @var{X}, response @var{y}, random-effects design ## @var{Z}, and grouping variable @var{G}. The random effects @var{b} are ## normally distributed with mean zero and an unstructured covariance @var{Psi} ## (shared across the levels of the grouping variable), and the observation ## errors are independent @code{N(0, sigma2)}. ## ## @var{X} is an @var{n}-by-@var{p} numeric matrix and @var{y} an @var{n}-by-1 ## response vector. @var{Z} is an @var{n}-by-@var{q} random-effects design and ## @var{G} an @var{n}-by-1 grouping variable (numeric, logical, char, cell array ## of strings, or categorical). To specify several grouping terms, pass @var{Z} ## and @var{G} as cell arrays of the same length, one design and one grouping ## variable per term. ## ## The following @var{name}/@var{value} pairs are accepted: ## ## @table @asis ## @item @qcode{"FitMethod"} ## The estimation criterion, either @qcode{"ML"} (maximum likelihood, the ## default) or @qcode{"REML"} (restricted maximum likelihood). ## ## @item @qcode{"FixedEffectPredictors"} ## A cell array of @var{p} names for the columns of @var{X} (default ## @code{@{"x1", @dots{}, "xp"@}}). ## ## @item @qcode{"RandomEffectPredictors"} ## A cell array (one entry per grouping term) of cell arrays naming the columns ## of each @var{Z} (default @code{z1, z2, @dots{}}). ## ## @item @qcode{"RandomEffectGroups"} ## A cell array of names for the grouping terms (default @code{g1, g2}, etc.). ## @end table ## ## The returned @var{lme} is a @code{LinearMixedModel} object describing the ## fitted model: the estimated fixed effects and their statistics ## (@code{lme.Coefficients}), the covariance parameters ## (@code{covarianceParameters}), the random-effects BLUPs (@code{randomEffects}), ## the log-likelihood, and methods for prediction, residuals, and hypothesis ## tests. ## ## Only the full (unstructured) random-effects covariance is currently ## supported. ## ## @seealso{LinearMixedModel, fitlm, parseWilkinsonFormula} ## @end deftypefn function lme = fitlmematrix (X, y, Z, G, varargin) if (nargin < 4) print_usage (); endif ## --- fixed-effects design and response --- if (! (isnumeric (X) && ismatrix (X) && isreal (X))) error ("fitlmematrix: X must be a real numeric matrix."); endif if (! (isnumeric (y) && isreal (y) && isvector (y))) error ("fitlmematrix: y must be a real numeric vector."); endif y = y(:); n = rows (X); if (numel (y) != n) error ("fitlmematrix: y must have as many elements as X has rows."); endif ## --- random-effects designs and grouping (normalise to cells) --- if (! iscell (Z)) Z = {Z}; endif if (! iscell (G)) G = {G}; endif if (numel (Z) != numel (G)) error ("fitlmematrix: Z and G must have the same number of terms."); endif nt = numel (Z); for k = 1:nt if (! (isnumeric (Z{k}) && ismatrix (Z{k}) && isreal (Z{k}))) error ("fitlmematrix: each Z must be a real numeric matrix."); endif if (rows (Z{k}) != n) error ("fitlmematrix: each Z must have as many rows as X."); endif G{k} = G{k}(:); if (numel (G{k}) != n) error ("fitlmematrix: each G must have as many elements as X has rows."); endif endfor ## --- name/value options --- method = "ML"; fenames = {}; renames = {}; grnames = {}; formula_str = ""; respname = ""; if (mod (numel (varargin), 2) != 0) error ("fitlmematrix: name/value arguments must come in pairs."); endif for i = 1:2:numel (varargin) name = varargin{i}; value = varargin{i+1}; if (! (ischar (name) || (isstring (name) && isscalar (name)))) error ("fitlmematrix: option names must be strings."); endif switch (lower (char (name))) case "fitmethod" method = upper (char (value)); if (! any (strcmp (method, {"ML", "REML"}))) error ("fitlmematrix: FitMethod must be 'ML' or 'REML'."); endif case "fixedeffectpredictors" fenames = cellstr (value); case "randomeffectpredictors" renames = value; case "randomeffectgroups" grnames = cellstr (value); case "formula" formula_str = char (value); case "responsename" respname = char (value); case "covariancepattern" pat = lower (char (value)); if (! any (strcmp (pat, {"fullcholesky", "full"}))) error (strcat ("fitlmematrix: only the 'FullCholesky' covariance", ... " pattern is currently supported.")); endif otherwise error ("fitlmematrix: unknown option '%s'.", char (name)); endswitch endfor p = columns (X); if (isempty (fenames)) fenames = arrayfun (@(j) sprintf ("x%d", j), 1:p, "UniformOutput", false); elseif (numel (fenames) != p) error ("fitlmematrix: FixedEffectPredictors must name every column of X."); endif if (isempty (grnames)) grnames = arrayfun (@(k) sprintf ("g%d", k), 1:nt, "UniformOutput", false); endif if (isempty (renames)) renames = cell (1, nt); for k = 1:nt renames{k} = arrayfun (@(j) sprintf ("z%d", j), 1:columns (Z{k}), ... "UniformOutput", false); endfor endif ## --- fit --- fit = __lmefit__ (X, y, Z, G, method); ## --- build the info struct and wrap it in a LinearMixedModel object --- info = fit; info.X = X; info.y = y; info.Zcell = Z; info.Gcell = G; info.CoefficientNames = fenames; info.GroupNames = grnames; info.REPred = renames; info.method = method; info.Formula = formula_str; info.ResponseName = respname; lme = LinearMixedModel (info); endfunction %!demo %! ## A random-intercept model on a small balanced data set: five subjects %! ## measured at four values of a predictor x. %! x = repmat ([1 2 3 4]', 5, 1); %! subject = reshape (repmat (1:5, 4, 1), [], 1); %! y = 2 + 0.8 * x + reshape (repmat ([1 -1 0.5 -0.5 0]', 1, 4)', [], 1) ... %! + 0.1 * sin (1:20)'; %! X = [ones(20,1), x]; %! lme = fitlmematrix (X, y, ones (20, 1), subject, "FitMethod", "REML"); %! beta = fixedEffects (lme); %! [psi, mse] = covarianceParameters (lme); %! printf ("intercept = %.4f, slope = %.4f\n", beta); %! printf ("between-subject var = %.4f, residual var = %.4f\n", psi{1}, mse); ## --- Balanced one-way random-intercept model: closed-form REML/ANOVA check --- ## For a balanced one-way design (a groups of r observations) the REML estimates ## coincide with the ANOVA estimates: sigma2 = MSE, tau2 = (MSB - MSE)/r, and ## the fixed intercept is the grand mean. %!test %! a = 5; r = 6; n = a*r; %! grp = reshape (repmat (1:a, r, 1), [], 1); %! yv = [ 4.1 4.5 3.8 4.3 4.0 4.2, 6.0 5.7 6.3 5.9 6.1 6.2, ... %! 2.2 2.5 2.0 2.4 2.1 2.3, 5.1 4.9 5.3 5.0 5.2 4.8, ... %! 3.3 3.6 3.1 3.4 3.2 3.5 ]'; %! X = ones (n, 1); %! lme = fitlmematrix (X, yv, ones (n, 1), grp, "FitMethod", "REML"); %! gm = mean (yv); %! gmeans = accumarray (grp, yv, [], @mean); %! SSB = r * sum ((gmeans - gm) .^ 2); MSB = SSB / (a - 1); %! SSE = sum ((yv - gmeans(grp)) .^ 2); MSE = SSE / (n - a); %! tau2 = (MSB - MSE) / r; %! [psi, mse] = covarianceParameters (lme); %! assert_equal (fixedEffects (lme), gm, 1e-8); %! assert_equal (mse, MSE, 1e-6); %! assert_equal (psi{1}, tau2, 1e-6); ## --- Internal consistency: sigma2 and covbeta satisfy their defining GLS ## relations at the returned fit. --- %!test %! a = 4; r = 5; n = a*r; %! grp = reshape (repmat (1:a, r, 1), [], 1); %! x = (1:n)' / n; %! yv = 1 + 2*x + reshape (repmat ([0.5 -0.5 0.2 -0.2], r, 1), [], 1) ... %! + 0.05 * cos (1:n)'; %! X = [ones(n,1), x]; %! lme = fitlmematrix (X, yv, ones (n, 1), grp, "FitMethod", "REML"); %! ## rebuild V = Psi over groups + sigma2 I and check the GLS identities %! [psi, mse] = covarianceParameters (lme); %! Zx = zeros (n, a); %! for l = 1:a, Zx(grp==l, l) = 1; end %! V = psi{1} * (Zx * Zx') + mse * eye (n); %! Vi = inv (V); %! beta = (X' * Vi * X) \ (X' * Vi * yv); %! assert_equal (fixedEffects (lme), beta, 1e-6); %! assert_equal (lme.CoefficientCovariance, inv (X' * Vi * X), 1e-5); ## --- MATLAB-verified parity: unbalanced random-intercept fit (ML). --- ## Reference values are MATLAB fitlmematrix (R2026a) on this exact data. %!shared X, yL, grp, xL, x2 %! xL = [0.032760004 0.70410822 -0.8646718 -0.28869454 0.51276678 -1.4975462 ... %! -1.4527871 -0.80013541 -1.644209 1.5137701 0.72905543 0.20880758 1.0856145 ... %! 0.62862577 -0.87409978 1.9178276 0.09748204 0.50697633 1.0247569 ... %! -0.92789896 -0.88921018 -0.98322849 -0.031378913 0.86875961 -0.91481141 ... %! 0.034324163 -0.25025257 -1.0575644 -0.86131607 -0.35355444 0.82950729 ... %! -0.36874363 0.061580868 0.55803564 -0.1763803 1.0482413 1.0137831 ... %! -0.94876976 -0.010703972 -0.35149845 -1.6828735 -1.0493301]'; %! x2 = [0.68979276 0.0074354814 -0.45697437 -0.5636481 1.4567202 -0.97829955 ... %! -1.12922 -0.030542479 1.5847779 -0.87837755 0.24121762 0.68747601 ... %! -0.56728765 0.98895053 -0.39350661 0.85326015 0.36524343 0.15824977 ... %! -1.7665212 0.59808246 -0.55763708 -1.1982294 -2.1473319 0.22521416 ... %! 0.37034398 -1.880586 0.052941033 -0.70016994 0.2174853 -1.7797082 ... %! 0.51971317 -0.35551286 1.9845963 -1.3498848 -0.63514097 -0.78794714 ... %! 1.3681179 1.4423152 -0.51233905 0.30238864 2.0458136 0.17326323]'; %! yL = [3.5635971 -0.36763498 1.4192715 3.4471073 2.2760668 3.6420287 ... %! 4.4481522 1.5292777 5.437158 0.44016873 0.7259756 2.9771749 1.2941347 ... %! 0.26407508 1.9673466 0.77589984 1.2176078 0.8528384 -0.86522876 2.8651388 ... %! 2.0127931 3.1242841 -0.49164361 1.3192295 5.3782287 -0.40680701 2.3989882 ... %! 3.606108 1.9193197 1.6713765 2.4383124 1.9314844 3.5112706 0.91644553 ... %! 0.034923706 -0.15510033 2.1765206 2.5327412 3.1421219 3.3472574 5.343425 ... %! 3.8775899]'; %! grp = [1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 ... %! 5 6 1 2 3 4 5 6]'; %! X = [ones(42,1), xL, x2]; %!test # returns a LinearMixedModel object %! lme = fitlmematrix (X, yL, ones (42, 1), grp, "FitMethod", "ML"); %! assert_equal (isa (lme, "LinearMixedModel"), true); %!test # random intercept, ML -- matches MATLAB fitlmematrix %! lme = fitlmematrix (X, yL, ones (42, 1), grp, "FitMethod", "ML"); %! [psi, mse] = covarianceParameters (lme); %! assert_equal (fixedEffects (lme), [1.9685543; -1.3771207; 0.81016147], 1e-4); %! assert_equal (mse, 0.36422151, 1e-4); %! assert_equal (psi{1}, 0.65265828, 1e-4); %! assert_equal (lme.LogLikelihood, -46.203282, 1e-4); %!test # correlated random intercept + slope, REML -- matches MATLAB %! Z = [ones(42,1), xL]; %! lme = fitlmematrix (X, yL, Z, grp, "FitMethod", "REML"); %! [psi, mse] = covarianceParameters (lme); %! assert_equal (fixedEffects (lme), [2.0034354; -1.3374715; 0.83788802], 1e-3); %! assert_equal (mse, 0.36649172, 1e-3); %! assert_equal (lme.LogLikelihood, -48.280962, 1e-3); %! assert_equal (psi{1}, ... %! [0.7846112, -0.14287556; -0.14287556, 0.026017248], 1e-3); %!test # ML and REML give different (both sensible) variance components %! lme_ml = fitlmematrix (X, yL, ones (42, 1), grp, "FitMethod", "ML"); %! lme_re = fitlmematrix (X, yL, ones (42, 1), grp, "FitMethod", "REML"); %! pml = covarianceParameters (lme_ml); %! pre = covarianceParameters (lme_re); %! assert_equal (pre{1} > pml{1}, true); %! # close, not equal %! assert_equal (fixedEffects (lme_ml), fixedEffects (lme_re), 5e-3); %!test # default FitMethod is ML %! l1 = fitlmematrix (X, yL, ones (42, 1), grp); %! l2 = fitlmematrix (X, yL, ones (42, 1), grp, "FitMethod", "ML"); %! assert_equal (l1.LogLikelihood, l2.LogLikelihood, 1e-10); %!test # names propagate to the fitted object %! lme = fitlmematrix (X, yL, ones (42, 1), grp, ... %! "FixedEffectPredictors", {"Int", "x", "x2"}); %! assert_equal (lme.CoefficientNames, {"Int", "x", "x2"}); ## Input validation %!error fitlmematrix (1, 2) %!error fitlmematrix ({1}, 1, 1, 1) %!error fitlmematrix (ones (3), {1}, 1, 1) %!error fitlmematrix (ones (3, 2), [1 2], ones (3, 1), [1 2 3]) %!error fitlmematrix (ones (3, 2), [1;2;3], {ones(3,1), ones(3,1)}, {[1;2;3]}) %!error fitlmematrix (ones (3, 2), [1;2;3], ones (2, 1), [1;2;3]) %!error fitlmematrix (ones (3, 2), [1;2;3], ones (3, 1), [1;2;3], "FitMethod", "xxx") %!error fitlmematrix (ones (3, 2), [1;2;3], ones (3, 1), [1;2;3], "FitMethod") %!error fitlmematrix (ones (3, 2), [1;2;3], ones (3, 1), [1;2;3], "bogus", 1) %!error fitlmematrix (ones (3, 2), [1;2;3], ones (3, 1), [1;2;3], "CovariancePattern", "Diagonal") statistics-release-1.9.2/inst/Regression/fitnlm.m000066400000000000000000000156761524624707500221320ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{mdl} =} fitnlm (@var{X}, @var{y}, @var{modelfun}, @var{beta0}) ## @deftypefnx {statistics} {@var{mdl} =} fitnlm (@var{tbl}, @var{modelfun}, @var{beta0}) ## @deftypefnx {statistics} {@var{mdl} =} fitnlm (@dots{}, @var{Name}, @var{Value}) ## ## Fit a nonlinear regression model. ## ## @code{@var{mdl} = fitnlm (@var{X}, @var{y}, @var{modelfun}, @var{beta0})} ## fits the nonlinear regression model @code{@var{y} = @var{modelfun} ## (@var{beta}, @var{X})} to the response vector @var{y} and the ## @math{n}-by-@math{p} predictor matrix @var{X}, starting the iterative fit ## from the coefficient vector ## @var{beta0}, and returns a @code{NonLinearModel} object. @var{modelfun} is a ## function handle @code{@@(@var{b}, @var{X})} returning the fitted responses. ## ## @code{@var{mdl} = fitnlm (@var{tbl}, @var{modelfun}, @var{beta0})} takes the ## predictors and response from the table @var{tbl}; the last column is the ## response unless overridden by @qcode{'ResponseVar'}. ## ## The following @var{Name}/@var{Value} pairs are accepted: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'CoefficientNames'} @tab a cell array of names for the ## coefficients (default @qcode{'b1'}, @qcode{'b2'}, @dots{}). ## @item @qcode{'Weights'} @tab a vector of nonnegative observation weights. ## @item @qcode{'ErrorModel'} @tab the error-variance model: @qcode{'constant'} ## (default), @qcode{'proportional'}, or @qcode{'combined'}. ## @item @qcode{'RobustWgtFun'} @tab the name of a robust weight function, ## enabling robust fitting (see @code{nlinfit}). ## @item @qcode{'Options'} @tab a statset-style options structure controlling ## the iterative fit (@qcode{MaxIter}, @qcode{TolFun}, @qcode{TolX}). ## @item @qcode{'PredictorVars'}, @qcode{'ResponseVar'} @tab for table input, ## the predictor and response variable names. ## @item @qcode{'VarNames'} @tab a cell array of @math{p + 1} variable names ## (predictors followed by the response) for numeric @var{X}. ## @item @qcode{'Exclude'} @tab observations to exclude from the fit. ## @end multitable ## ## @seealso{NonLinearModel, nlinfit, nlparci, nlpredci, fitlm, fitglm} ## @end deftypefn function mdl = fitnlm (varargin) if (nargin < 3) print_usage (); endif if (istable (varargin{1})) mdl = NonLinearModel (varargin{1}, [], varargin{2}, varargin{3}, ... varargin{4:end}); else if (nargin < 4) print_usage (); endif mdl = NonLinearModel (varargin{1}, varargin{2}, varargin{3}, ... varargin{4}, varargin{5:end}); endif endfunction %!demo %! ## Fit an exponential growth model and inspect the summary. %! x = [1:10]'; %! y = [2.1;2.9;4.2;5.3;7.1;9.4;12.8;16.5;22.1;29.8]; %! modelfun = @(b, x) b(1) .* exp (b(2) .* x); %! mdl = fitnlm (x, y, modelfun, [1; 0.3]) %!demo %! ## Predictions with 95% confidence intervals on the fitted curve. %! x = [1:10]'; %! y = [2.1;2.9;4.2;5.3;7.1;9.4;12.8;16.5;22.1;29.8]; %! modelfun = @(b, x) b(1) .* exp (b(2) .* x); %! mdl = fitnlm (x, y, modelfun, [1; 0.3]); %! [ypred, yci] = predict (mdl, [2.5; 5.5; 8.5]) %!shared X, y, modelfun, beta0 %! X = [1;2;3;4;5;6;7;8;9;10]; %! y = [2.1;2.9;4.2;5.3;7.1;9.4;12.8;16.5;22.1;29.8]; %! modelfun = @(b, x) b(1) .* exp (b(2) .* x); %! beta0 = [1; 0.3]; ## Values verified against MATLAB's fitnlm. %!test %! mdl = fitnlm (X, y, modelfun, beta0); %! assert_equal (mdl.Coefficients.Estimate, ... %! [1.683747025; 0.286911087], 1e-6); %! assert_equal (mdl.Coefficients.SE, [0.035194899; 0.002350913], 1e-6); %! assert_equal (mdl.Coefficients.tStat, [47.8406555; 122.042406], -1e-4); %! assert_equal (mdl.RMSE, 0.170942956, 1e-7); %! assert_equal (mdl.SSE, 0.233771954, 1e-7); %! assert_equal (mdl.SST, 750.976, 1e-3); %!test %! mdl = fitnlm (X, y, modelfun, beta0); %! assert_equal (mdl.Rsquared.Ordinary, 0.999688709, 1e-8); %! assert_equal (mdl.Rsquared.Adjusted, 0.999649798, 1e-8); %! assert_equal (mdl.LogLikelihood, 4.590586096, 1e-6); %! assert_equal (mdl.ModelCriterion.AIC, -5.181172193, 1e-6); %! assert_equal (mdl.ModelCriterion.BIC, -4.576002007, 1e-6); %!test %! ## coefCI matches beta +/- t * SE with the error degrees of freedom. %! mdl = fitnlm (X, y, modelfun, beta0); %! ci = coefCI (mdl); %! b = mdl.Coefficients.Estimate; se = mdl.Coefficients.SE; %! t = tinv (0.975, mdl.DFE); %! assert_equal (ci, [b - t .* se, b + t .* se], 1e-12); %!test %! ## Table input gives the same fit as matrix input. %! tbl = table (X, y, "VariableNames", {"x", "y"}); %! mdl = fitnlm (tbl, modelfun, beta0); %! assert_equal (mdl.Coefficients.Estimate, [1.683747025; 0.286911087], 1e-6); %! assert_equal (mdl.CoefficientNames, {"b1", "b2"}); %!test %! ## Custom coefficient names. %! mdl = fitnlm (X, y, modelfun, beta0, "CoefficientNames", {"A", "k"}); %! assert_equal (mdl.CoefficientNames, {"A", "k"}); %!test %! ## coefTest reports a Wald F statistic versus the zero model. %! mdl = fitnlm (X, y, modelfun, beta0); %! [p, F, df] = coefTest (mdl); %! assert_equal (df, 2); %! assert_equal (F > 1e5, true); %! assert_equal (p < 1e-10, true); %!test %! ## predict returns the fitted values and confidence intervals. %! mdl = fitnlm (X, y, modelfun, beta0); %! [yp, yci] = predict (mdl, [2.5; 5.5; 8.5]); %! assert_equal (yp, [3.449741842; 8.158274281; 19.293455074], 1e-6); %! assert_equal (yci(:,1), [3.329126146; 7.997938483; 19.121921613], 1e-5); %! assert_equal (yci(:,2), [3.570357538; 8.318610079; 19.464988535], 1e-5); %!test # plotting methods, feval, and random run without error %! mdl = fitnlm (X, y, modelfun, beta0); %! assert_equal (feval (mdl, [2.5; 5.5]), predict (mdl, [2.5; 5.5]), 1e-12); %! assert_equal (numel (random (mdl)), 10); %! hf = figure ("visible", "off"); %! unwind_protect %! plotResiduals (mdl); %! plotResiduals (mdl, "fitted"); %! plotDiagnostics (mdl); %! plotSlice (mdl); %! unwind_protect_cleanup %! close (hf); %! end_unwind_protect ## Test input validation %!error fitnlm () %!error fitnlm ([1, 2], [1; 2]) %!error ... %! fitnlm ([1;2], [1;2], "bad", [1]) %!error ... %! fitnlm ([1;2;3], [1;2;3], @(b, x) b(1) * x, 1, "foo", 1) statistics-release-1.9.2/inst/Regression/glmfit.m000066400000000000000000000763451524624707500221230ustar00rootroot00000000000000## Copyright (C) 2024 Ruchika Sonagote ## Copyright (C) 2025 Swayam Shah ## Copyright (C) 2024-2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{b} =} glmfit (@var{X}, @var{y}, @var{distribution}) ## @deftypefnx {statistics} {@var{b} =} glmfit (@var{X}, @var{y}, @var{distribution}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{b}, @var{dev}] =} glmfit (@dots{}) ## @deftypefnx {statistics} {[@var{b}, @var{dev}, @var{stats}] =} glmfit (@dots{}) ## ## Perform generalized linear model fitting. ## ## @code{@var{b} = glmfit (@var{X}, @var{y}, @var{distribution})} returns a ## vector @var{b} of coefficient estimates for a generalized linear regression ## model of the responses in @var{y} on the predictors in @var{X}, using the ## distribution defined in @var{distribution}. ## ## @itemize ## @item @var{X} is an @math{n*p} numeric matrix of predictor variables with ## @math{n} observations and @math{p} predictors. ## @item @var{y} is an @math{n*1} numeric vector of responses for all supported ## distributions, except for the 'binomial' distribution in which case @var{y} ## can be either a numeric or logical @math{n*1} vector or an @math{n*2} ## matrix, where the first column contains the number of successes and the ## second column contains the number of trials. ## @item @var{distribution} is a character vector specifying the distribution of ## the response variable. Supported distributions are @qcode{'normal'}, ## @qcode{'binomial'}, @qcode{'poisson'}, @qcode{'gamma'}, and @qcode{'inverse ## gaussian'}. ## @end itemize ## ## @code{@var{b} = glmfit (@dots{}, @var{Name}, @var{Value})} specifies ## additional options using @qcode{Name-Value} pair arguments. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'B0'} @tab A numeric vector specifying initial values for ## the coefficient estimates. By default, the initial values are fitted values ## fitted from the data. ## ## @item @qcode{'Constant'} @tab A character vector specifying whether to ## include a constant term in the model. Valid options are @var{"on"} (default) ## and @var{"off"}. ## ## @item @qcode{'EstDisp'} @tab A character vector specifying whether to ## compute dispersion parameter. Valid options are @var{"on"} and @var{"off"}. ## For @qcode{'binomial'} and @qcode{'poisson'} distributions the default is ## @var{"off"}, whereas for the @qcode{'normal'}, @qcode{'gamma'}, and ## @qcode{'inverse gaussian'} distributions the default is @var{"on"}. ## ## @item @qcode{'link'} @tab A character vector specifying the name of a ## canonical link function or a numeric scalar for specifying a @qcode{'power'} ## link function. Supported canonical link functions include @qcode{'identity'} ## (default for @qcode{'normal'} distribution), @qcode{'log'} (default for ## @qcode{'poisson'} distribution), @qcode{'logit'} (default for ## @qcode{'binomial'} distribution), @qcode{'probit'}, @qcode{'loglog'}, ## @qcode{'comploglog'}, and @qcode{'reciprocal'} (default for the ## @qcode{'gamma'} distribution). The @qcode{'power'} link function is the ## default for the @qcode{'inverse gaussian'} distribution with @math{p = -2}. ## For custom link functions, the user can provide cell array with three ## function handles: the link function, its derivative, and its inverse, or ## alternatively a structure @var{S} with three fields: @qcode{S.Link}, ## @qcode{S.Derivative}, and @qcode{S.Inverse}. Each field can either contain a ## function handle or a character vector with the name of an existing function. ## All custom link functions must accept a vector of inputs and return a vector ## of the same size. ## ## @item @qcode{'Offset'} @tab A numeric vector of the same length as the ## response @var{y} specifying an offset variable in the fit. It is used as an ## additional predictor with a coefficient value fixed at 1. ## ## @item @qcode{'Options'} @tab A scalar structure containing the fields ## @qcode{MaxIter} and @qcode{TolX}. @qcode{MaxIter} must be a scalar positive ## integer specifying the maximum number of iteration allowed for fitting the ## model, and @qcode{TolX} must be a positive scalar value specifying the ## termination tolerance. ## ## @item @qcode{'Weights'} @tab An @math{n*1} numeric vector of nonnegative ## values, where @math{n} is the number of observations in @var{X}. By default, ## it is @code{ones (n, 1)}. ## @end multitable ## ## @code{[@var{b}, @var{dev}] = glmfit (@dots{})} also returns the deviance of ## the fit as a numeric value in @var{dev}. Deviance is a generalization of the ## residual sum of squares. It measures the goodness of fit compared to a ## saturated model. ## ## @code{[@var{b}, @var{dev}, @var{stats}] = glmfit (@dots{})} also returns the ## structure @var{stats}, which contains the model statistics in the following ## fields: ## ## @itemize ## @item @qcode{beta} - Coefficient estimates @var{b} ## @item @qcode{dfe} - Degrees of freedom for error ## @item @qcode{sfit} - Estimated dispersion parameter ## @item @qcode{s} - Theoretical or estimated dispersion parameter ## @item @qcode{estdisp} - @code{false} when @qcode{'EstDisp'} is @qcode{'off'} ## and @code{true} when @qcode{'EstDisp'} is @qcode{'on'} ## @item @qcode{covb} - Estimated covariance matrix for @var{b} ## @item @qcode{se} - Vector of standard errors of the coefficient estimates ## @var{b} ## @item @qcode{coeffcorr} - Correlation matrix for @var{b} ## @item @qcode{t} - @math{t} statistics for @var{b} ## @item @qcode{p} - @math{p}-values for @var{b} ## @item @qcode{resid} - Vector of residuals ## @item @qcode{residp} - Vector of Pearson residuals ## @item @qcode{residd} - Vector of deviance residuals ## @item @qcode{resida} - Vector of Anscombe residuals ## @end itemize ## ## @seealso{glmval} ## @end deftypefn function [b, dev, stats] = glmfit (X, y, distribution, varargin) ## Check input arguments if (nargin < 3) error ("glmfit: too few input arguments."); elseif (mod (nargin - 3, 2) != 0) error ("glmfit: Name-Value arguments must be in pairs."); elseif (! isnumeric (X) || isempty (X)) error ("glmfit: X must be a numeric matrix."); elseif (! (isnumeric (y) || islogical (y)) || isempty (y)) error ("glmfit: Y must be either a numeric matrix or a logical vector."); elseif (size (X, 1) != size (y, 1)) error ("glmfit: X and Y must have the same number of observations."); elseif (! ischar (distribution)) error ("glmfit: DISTRIBUTION must be a character vector."); endif ## Remove missing values xymissing = any (isnan (y), 2) | any (isnan (X), 2); y(xymissing) = []; X(xymissing,:) = []; [ny, cy] = size (y); [nx, cx] = size (X); ## Check y dimensions based on distribution if (strcmpi (distribution, 'binomial')) if (cy > 2) error (strcat ("glmfit: for a 'binomial' distribution,", " Y must be an n-by-1 or n-by-2 matrix.")); ## Get y and N for binomial distribution elseif (cy == 2) if (! isnumeric (y)) error (strcat ("glmfit: n-by-2 matrix Y for 'binomial' distribution", " must be numeric.")); endif N = y(:, 2); y = y(:, 1) ./ N; else if (islogical (y)) y = double (y); endif N = ones (size (y)); endif else if (cy != 1) error (strcat ("glmfit: for distributions other than 'binomial',", " Y must be an n-by-1 column vector.")); endif endif ## Set default link, variance, and deviance functions ## Set defaults for estimating dispersion parameter and limiting mu switch (tolower (distribution)) case 'normal' [flink, dlink, ilink] = getlinkfunctions ('identity'); varFun = @(mu) ones (size (mu)); devFun = @(mu, y) (y - mu) .^ 2; estDisp = true; case 'binomial' [flink, dlink, ilink] = getlinkfunctions ('logit'); varFun = @(mu, N) sqrt (mu) .* sqrt (1 - mu) ./ sqrt (N); devFun = @(mu, y, N) 2 * N .* (y .* log ((y + (y == 0)) ./ mu) + ... (1 - y) .* log ((1 - y + (y == 1)) ./ (1 - mu))); estDisp = false; muLimits = [eps, 1-eps]; case 'poisson' [flink, dlink, ilink] = getlinkfunctions ('log'); varFun = @(mu) sqrt (mu); devFun = @(mu, y) 2 * (y .* (log ((y + (y == 0)) ./ mu)) - (y - mu)); estDisp = false; muLimits = realmin; case 'gamma' [flink, dlink, ilink] = getlinkfunctions ('reciprocal'); varFun = @(mu) mu; devFun = @(mu, y) 2 * (-log (y ./ mu) + (y - mu) ./ mu); estDisp = true; muLimits = realmin; case 'inverse gaussian' [flink, dlink, ilink] = getlinkfunctions (-2); varFun = @(mu) mu .^ (3 / 2); devFun = @(mu, y) (((y - mu) ./ mu) .^ 2) ./ y; estDisp = true; muLimits = realmin; otherwise error ("glmfit: unsupported distribution."); endswitch ## Set defaults B0 = []; constant = true; offset = zeros (nx, 1); weight = ones (nx, 1); MaxIter = 100; TolX = 1e-6; ## Parse extra parameters while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'b0' B0 = varargin {2}; if (! (isnumeric (B0) && isequal (size (B0), size (xymissing)))) error ("glmfit: 'B0' must be a numeric vector of the same size as Y."); endif B0(xymissing) = []; case 'constant' constant = tolower (varargin {2}); if (strcmpi (constant, 'on')) constant = true; elseif (strcmpi (constant, 'off')) constant = false; else error ("glmfit: 'Constant' should be either 'on' or 'off'."); endif case 'estdisp' estDisp = tolower (varargin {2}); if (strcmpi (estDisp, 'on')) estDisp = true; elseif (strcmpi (estDisp, 'off')) estDisp = false; else error ("glmfit: 'EstDisp' should be either 'on' or 'off'."); endif case 'link' linkArg = varargin {2}; ## Input validation is performed in private function [flink, dlink, ilink, errmsg] = getlinkfunctions (linkArg); if (! isempty (errmsg)) error ("glmfit: %s", errmsg); endif case 'options' options = varargin {2}; rf = {'MaxIter', 'TolX'}; if (! (isstruct (options) && all (ismember (rf, fieldnames (options))))) error (strcat ("glmfit: 'Options' must be a structure containing", " the fields 'MaxIter', and 'TolX'.")); endif MaxIter = options.MaxIter; TolX = options.TolX; if (! isscalar (MaxIter) || MaxIter <= 0 || fix (MaxIter) != MaxIter) error (strcat ("glmfit: 'MaxIter' in 'Options' structure must", " be a positive integer.")); endif if (! isscalar (TolX) || TolX <= 0) error (strcat ("glmfit: 'TolX' in 'Options' structure must", " be a positive scalar.")); endif case 'offset' offset = varargin {2}; if (! (isnumeric (offset) && isequal (size (offset), size (xymissing)))) error (strcat ("glmfit: 'Offset' must be a numeric vector", " of the same size as Y.")); endif offset(xymissing) = []; case 'weights' weight = varargin {2}; if (! (isnumeric (weight) && isequal (size (weight), size (xymissing)))) error (strcat ("glmfit: 'Weights' must be a numeric vector", " of the same size as Y.")); endif weight(xymissing) = []; otherwise error ("glmfit: unknown parameter name."); endswitch varargin(1:2) = []; endwhile ## Adjust X based on constant if (constant) X = [ones(nx, 1), X]; cx += 1; endif ## Check X for rank deficiency [~, R, P] = qr (X .* weight(:, ones (1, cx)), 0); if (isempty (R)) rankX = 0; else rankX = sum (abs (diag (R)) > abs (R(1)) * max (nx, cx) * eps); endif if (rankX < cx) warning ("glmfit: X is ill-conditioned."); P = P(1:rankX); X = X(:,P); else P = [1:cx]; endif ## Adjust number of observations for zero weights if (any (weight == 0)) nx = nx - sum (weight == 0); endif ## Initialize mu and eta if (isempty (B0)) # from y switch (distribution) case 'binomial' mu = (N .* y + 0.5) ./ (N + 1); case 'poisson' mu = y + 0.25; case {'gamma', 'inverse gaussian'} mu = max (y, eps); otherwise mu = y; endswitch eta = flink(mu); else # from coefficient estimates eta = offset + X * B0(:); mu = ilink(eta); endif ## Initialize coefficient vector and iterations numc = size (X, 2); Bnew = zeros (numc, 1); seps = sqrt (eps); iter = 0; while (iter < MaxIter) iter += 1; Bold = Bnew; ## Compute iteratively reweighted least squares weights d_eta = dlink(mu); if (strcmpi (distribution, 'binomial')) IRLS = abs (d_eta) .* varFun(mu, N); else IRLS = abs (d_eta) .* varFun(mu); endif squaredWeight = sqrt (weight) ./ IRLS; ## Estimate coefficients z_off = eta + (y - mu) .* d_eta - offset; yweighted = (z_off) .* squaredWeight; Xweighted = X .* squaredWeight(:, ones (1, numc)); [Q, R] = qr (Xweighted, 0); Bnew = R \ (Q' * yweighted); ## Compute predicted mean using current linear predictor eta = offset + X * Bnew; mu = ilink(eta); ## Force predicted mean within distribution support limits if (strcmpi (distribution, 'normal')) mu = mu; elseif (strcmpi (distribution, 'binomial')) mu = max (min (mu, muLimits(2)), muLimits(1)); else # for "poisson", "gamma", and "inverse gaussian" distributions mu = max (mu, muLimits(1)); endif ## Break if TolX is reached if (! any (abs (Bnew - Bold) > TolX * max (seps, abs (Bold)))) break; endif endwhile ## Warn if iteration limit is reached if (iter == MaxIter) warning ("glmfit: maximum number of iterations has been reached."); endif ## Return estimated coefficients if (rankX < cx) b = zeros (cx, 1); b(P) = Bnew; else b = Bnew; endif ## Compute deviance if (nargout > 1) if (strcmpi (distribution, 'binomial')) devn = devFun(mu, y, N); dev = sum (weight .* devn); else devn = devFun(mu, y); dev = sum (weight .* devn); endif endif ## Compute stats if (nargout > 2) ## Store coefficient estimates stats.beta = b; ## Compute degrees of freedom for error stats.dfe = max (nx - numc, 0); ## Compute estimated dispersion parameter if (stats.dfe > 0) switch (tolower (distribution)) case 'normal' stats.sfit = sum (weight .* (y - mu) .^ 2) / stats.dfe; case 'binomial' stats.sfit = sum (weight .* (y - mu) .^ 2 ./ ... (mu .* (1 - mu) ./ N)) / stats.dfe; case 'poisson' stats.sfit = sum (weight .* (y - mu) .^ 2 ./ mu) / stats.dfe; case 'gamma' stats.sfit = sum (weight .* ((y - mu) ./ mu) .^ 2) / stats.dfe; case 'inverse gaussian' stats.sfit = sum (weight .* ((y - mu) ./ mu .^ (3 / 2)) .^ 2) / ... stats.dfe; endswitch else stats.sfit = NaN; endif ## Store theoretical or estimated dispersion parameter if (estDisp) stats.s = stats.sfit; stats.estdisp = estDisp; else stats.s = 1; stats.estdisp = estDisp; endif ## Compute covariance matrix, standard errors, correlation matrix, ## t-statistic, and p-value for coefficient estimates if (isnan (stats.s)) stats.covb = NaN (numel (b)); stats.se = NaN (size (b)); stats.coeffcorr = NaN (numel (b)); stats.t = NaN (size (b)); stats.p = NaN (size (b)); else stats.covb = zeros (cx, cx); stats.se = zeros (cx, 1); stats.coeffcorr = zeros (cx, cx); stats.t = NaN (cx, 1); stats.p = NaN (cx, 1); RI = R \ eye (numc); C = RI * RI'; if (estDisp) C = C * stats.s; endif stats.covb(P,P) = C; se = sqrt (diag (C)); se = se(:); stats.se(P) = se; C = C ./ (se * se'); stats.coeffcorr(P,P) = C; stats.t(P) = b(P) ./ se; if (estDisp) stats.p = 2 * tcdf (-abs (stats.t), stats.dfe); else stats.p = 2 * normcdf (-abs (stats.t)); endif endif ## Compute residuals stats.resid = NaN (size (xymissing)); # Vector of residuals stats.residp = NaN (size (xymissing)); # Vector of Pearson residuals stats.residd = NaN (size (xymissing)); # Vector of deviance residuals stats.resida = NaN (size (xymissing)); # Vector of Anscombe residuals if (isequal (distribution, 'binomial')) stats.resid(! xymissing) = (y - mu) .* N; stats.residp(! xymissing) = (y - mu) ./ (varFun(mu, N) + (y == mu)); else stats.resid(! xymissing) = y - mu; stats.residp(! xymissing) = (y - mu) ./ (varFun(mu) + (y == mu)); endif stats.residd(! xymissing) = sign (y - mu) .* sqrt (max (0, devn)); switch (tolower (distribution)) case 'normal' stats.resida(! xymissing) = y - mu; case 'binomial' ab = 2 / 3; stats.resida(! xymissing) = beta (ab, ab) ... * (betainc (y, ab, ab) - betainc (mu, ab, ab)) ... ./ ((mu .* (1 - mu)) .^ (1 / 6) ./ sqrt (N)); case 'poisson' stats.resida(! xymissing) = 1.5 * ((y .^ (2 / 3) - mu .^ (2 / 3)) ... ./ mu .^ (1 / 6)); case 'gamma' pwr = 1 / 3; stats.resida(! xymissing) = 3 * (y .^ pwr - mu .^ pwr) ./ mu .^ pwr; case 'inverse gaussian' stats.resida(! xymissing) = (log (y) - log (mu)) ./ mu; endswitch endif endfunction %!demo %! x = [210, 230, 250, 270, 290, 310, 330, 350, 370, 390, 410, 430]'; %! n = [48, 42, 31, 34, 31, 21, 23, 23, 21, 16, 17, 21]'; %! y = [1, 2, 0, 3, 8, 8, 14, 17, 19, 15, 17, 21]'; %! b = glmfit (x, [y n], 'binomial', 'Link', 'probit'); %! yfit = glmval (b, x, 'probit', 'Size', n); %! plot (x, y./n, 'o', x, yfit ./ n, '-') %!demo %! load fisheriris %! X = meas (51:end, :); %! y = strcmp ('versicolor', species(51:end)); %! b = glmfit (X, y, 'binomial', 'link', 'logit') ## Test output %!test %! load fisheriris; %! X = meas(51:end,:); %! y = strcmp ('versicolor', species(51:end)); %! b = glmfit (X, y, 'binomial', 'link', 'logit'); %! assert_equal (b, [42.6379; 2.4652; 6.6809; -9.4294; -18.2861], 1e-4); ## Requesting the stats output must not clobber the coefficient vector b; the ## binomial Anscombe-residual computation previously reused the name "b". %!test %! load fisheriris; %! X = meas(51:end,:); %! y = strcmp ('versicolor', species(51:end)); %! [b, dev, stats] = glmfit (X, y, 'binomial', 'link', 'logit'); %! assert_equal (b, [42.6379; 2.4652; 6.6809; -9.4294; -18.2861], 1e-4); %! assert_equal (numel (stats.se), numel (b)); %!test %! ## For an estimated-dispersion family the coefficient covariance is scaled by %! ## the dispersion (not its square), so the standard errors match ordinary %! ## least squares for the identity-link normal case. %! X = [1, 2; 2, 1; 3, 4; 4, 3; 5, 6; 6, 5]; %! y = [2.1; 1.9; 4.2; 3.8; 6.1; 5.9]; %! [b, dev, stats] = glmfit (X, y, 'normal'); %! Xd = [ones(6, 1), X]; %! mse = sum ((y - Xd * b) .^ 2) / (6 - 3); %! se_ols = sqrt (mse * diag (inv (Xd' * Xd))); %! assert_equal (stats.se, se_ols, 1e-12); %!test %! X = [1.2, 2.3, 3.4, 4.5, 5.6, 6.7, 7.8, 8.9, 9.0, 10.1]'; %! y = [0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4]'; %! [Bnew, dev] = glmfit (X, y, 'gamma', 'link', 'log'); %! b_matlab = [-0.7631; 0.1113]; %! dev_matlab = 0.0111; %! assert_equal (Bnew, b_matlab, 0.001); %! assert_equal (dev, dev_matlab, 0.001); %!test %! X = [1.2, 2.3, 3.4, 4.5, 5.6, 6.7, 7.8, 8.9, 9.0, 10.1]'; %! y = [0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4]'; %! p_input = 1; %! [Bnew, dev] = glmfit (X, y, 'inverse gaussian', 'link', p_input); %! b_matlab = [0.3813; 0.0950]; %! dev_matlab = 0.0051; %! assert_equal (Bnew, b_matlab, 0.001); %! assert_equal (dev, dev_matlab, 0.001); %!test %! ## a rank deficient design drops the redundant column: zero coefficient and %! ## standard error, NaN test statistics, and the rest as the reduced fit %! x = [1.2; 2.3; 3.4; 4.5; 5.6; 6.7; 7.8; 8.9; 9.0; 10.1]; %! y = [0.5; 0.6; 0.7; 0.8; 0.9; 1.0; 1.1; 1.2; 1.3; 1.4]; %! status = warning; %! warning ('off'); %! [b, dev, stats] = glmfit ([x, x], y, 'normal'); %! warning (status); %! [br, devr, statsr] = glmfit (x, y, 'normal'); %! assert_equal (b, [br; 0], -1e-12); %! assert_equal (dev, devr, -1e-12); %! assert_equal (stats.se, [statsr.se; 0], -1e-12); %! assert_equal (stats.t, [statsr.t; NaN], -1e-12); %! assert_equal (stats.p, [statsr.p; NaN], -1e-12); ## Test input validation %!error glmfit () %!error glmfit (1) %!error glmfit (1, 2) %!error ... %! glmfit (rand (6, 1), rand (6, 1), 'poisson', 'link') %!error ... %! glmfit ('abc', rand (6, 1), 'poisson') %!error ... %! glmfit ([], rand (6, 1), 'poisson') %!error ... %! glmfit (rand (5, 2), 'abc', 'poisson') %!error ... %! glmfit (rand (5, 2), [], 'poisson') %!error ... %! glmfit (rand (5, 2), rand (6, 1), 'poisson') %!error ... %! glmfit (rand (6, 2), rand (6, 1), 3) %!error ... %! glmfit (rand (6, 2), rand (6, 1), {'poisson'}) %!error ... %! glmfit (rand (5, 2), rand (5, 3), 'binomial') %!error ... %! glmfit (rand (2, 2), [true, true; false, false], 'binomial') %!error ... %! glmfit (rand (5, 2), rand (5, 2), 'normal') %!error ... %! glmfit (rand (5, 2), rand (5, 1), 'chebychev') %!error ... %! glmfit (rand (5, 2), rand (5, 1), 'normal', 'B0', [1; 2; 3; 4]) %!error ... %! glmfit (rand (5, 2), rand (5, 1), 'normal', 'constant', 1) %!error ... %! glmfit (rand (5, 2), rand (5, 1), 'normal', 'constant', 'o') %!error ... %! glmfit (rand (5, 2), rand (5, 1), 'normal', 'constant', true) %!error ... %! glmfit (rand (5, 2), rand (5, 1), 'normal', 'estdisp', 1) %!error ... %! glmfit (rand (5, 2), rand (5, 1), 'normal', 'estdisp', 'o') %!error ... %! glmfit (rand (5, 2), rand (5, 1), 'normal', 'estdisp', true) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', {1, 2})) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', 'norminv')) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', 'some', 'Derivative', @(x)x, 'Inverse', 'normcdf')) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', 1, 'Derivative', @(x)x, 'Inverse', 'normcdf')) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', @(x) [x, x], 'Derivative', @(x)x, 'Inverse', 'normcdf')) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', 'what', 'Derivative', @(x)x, 'Inverse', 'normcdf')) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', @(x)x, 'Derivative', 'some', 'Inverse', 'normcdf')) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', @(x)x, 'Derivative', 1, 'Inverse', 'normcdf')) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', @(x)x, 'Derivative', @(x) [x, x], 'Inverse', 'normcdf')) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', @(x)x, 'Derivative', 'what', 'Inverse', 'normcdf')) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', @(x)x, 'Derivative', 'normcdf', 'Inverse', 'some')) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', @(x)x, 'Derivative', 'normcdf', 'Inverse', 1)) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', @(x)x, 'Derivative', 'normcdf', 'Inverse', @(x) [x, x])) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', struct ('Link', @(x)x, 'Derivative', 'normcdf', 'Inverse', 'what')) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', {'log'}) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', {'log', 'hijy'}) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', {1, 2, 3, 4}) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', {'log', 'dfv', 'dfgvd'}) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', {@(x) [x, x], 'dfv', 'dfgvd'}) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', {@(x) what (x), 'dfv', 'dfgvd'}) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', {@(x) x, 'dfv', 'dfgvd'}) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', {@(x) x, @(x) [x, x], 'dfgvd'}) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', {@(x) x, @(x) what (x), 'dfgvd'}) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', {@(x) x, @(x) x, 'dfgvd'}) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', {@(x) x, @(x) x, @(x) [x, x]}) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', {@(x) x, @(x) x, @(x) what (x)}) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', NaN) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', [1, 2]) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', [1i]) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', ['log'; 'log1']) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', 'somelinkfunction') %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'link', true) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'options', true) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'options', struct ('MaxIter', 100)) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'options', struct ('MaxIter', 4.5, 'TolX', 1e-6)) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'options', struct ('MaxIter', 0, 'TolX', 1e-6)) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'options', struct ('MaxIter', -100, 'TolX', 1e-6)) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'options', struct ('MaxIter', [50 ,50], 'TolX', 1e-6)) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'options', struct ('MaxIter', 100, 'TolX', 0)) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'options', struct ('MaxIter', 100, 'TolX', -1e-6)) %!error ... %! glmfit (rand (5,2), rand (5,1), 'poisson', 'options', struct ('MaxIter', 100, 'TolX', [1e-6, 1e-6])) %!error ... %! glmfit (rand (5, 2), rand (5, 1), 'normal', 'offset', [1; 2; 3; 4]) %!error ... %! glmfit (rand (5, 2), rand (5, 1), 'normal', 'offset', 'asdfg') %!error ... %! glmfit (rand (5, 2), rand (5, 1), 'normal', 'weights', [1; 2; 3; 4]) %!error ... %! glmfit (rand (5, 2), rand (5, 1), 'normal', 'weights', 'asdfg') statistics-release-1.9.2/inst/Regression/glmval.m000066400000000000000000000407631524624707500221160ustar00rootroot00000000000000## Copyright (C) 2025 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{yhat} =} glmval (@var{b}, @var{X}, @var{link}) ## @deftypefnx {statistics} {[@var{yhat}, @var{y_lo}, @var{y_hi}] =} glmval (@var{b}, @var{X}, @var{link}, @var{stats}) ## @deftypefnx {statistics} {[@dots{}] =} glmval (@dots{}, @var{Name}, @var{Value}) ## ## Predict values for a generalized linear model. ## ## @code{@var{yhat} = glmval (@var{b}, @var{X}, @var{link})} returns the ## predicted values for the generalized linear model with a vector of ## coefficient estimates @var{b}, a matrix of predictors @var{X}, in which each ## column corresponds to a distinct predictor variable, and a link function ## @var{link}, which can be any of the character vectors, numeric scalar, or ## custom-defined link functions used as values for the @qcode{'link'} ## name-value pair argument in the @code{glmfit} function. ## ## @code{[@var{yhat}, @var{y_lo}, @var{y_hi}] = glmval (@var{b}, @var{X}, ## @var{link}, @var{stats})} also returns the 95% confidence intervals for the ## predicted values according to the model's statistics contained in the ## @var{stats} structure, which is the output of the @code{glmfit} function. ## By default, the confidence intervals are nonsimultaneous, and apply to the ## fitted curve instead of new observations. ## ## @code{[@dots{}] = glmval (@dots{}, @var{Name}, @var{Value})} specifies ## additional options using @qcode{Name-Value} pair arguments. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'confidence'} @tab A scalar value between 0 and 1 ## specifying the confidence level for the confidence bounds. ## ## @item @qcode{'Constant'} @tab A character vector specifying whether to ## include a constant term in the model. Valid options are @var{"on"} (default) ## and @var{"off"}. ## ## @item @qcode{'simultaneous'} @tab A logical or numeric (@code{0} or ## @code{1}) scalar specifying whether the confidence bounds are simultaneous. ## The default is @code{false}, which yields nonsimultaneous (pointwise) bounds. ## ## @item @qcode{'size'} @tab A numeric scalar or a vector with one value ## for each row of @var{X} specifying the size parameter @math{N} for a binomial ## model. @qcode{'BinomialSize'} is accepted as an alias for @qcode{'size'}. ## @end multitable ## ## @seealso{glmfit} ## @end deftypefn function [yhat, y_lo, y_hi] = glmval (b, X, link, varargin) ## Check input arguments if (nargin < 3) error ("glmval: too few input arguments."); elseif (! (isnumeric (b) && isvector (b)) || isempty (b)) error ("glmval: B must be a numeric vector of coefficient estimates."); elseif (! isnumeric (X) || isempty (X)) error ("glmval: X must be a numeric matrix."); endif ## Get inverse link (input validation is performed in private function) [~, ~, ilink, errmsg] = getlinkfunctions (link); if (! isempty (errmsg)) error ("glmval: %s", errmsg); endif ## Check if fourth input argument is a STATS structure stats = []; if (nargin > 3) if (isstruct (varargin{1})) stats = varargin{1}; rf = {'s', 'se', 'coeffcorr', 'estdisp', 'dfe'}; if (! all (ismember (rf, fieldnames (stats)))) error ("glmval: invalid 'stats' structure."); endif varargin(1) = []; endif endif ## Set defaults confidence = 0.95; constant = true; offset = zeros (size (X, 1), 1); simultaneous = false; N = 1; ## Parse extra parameters if (mod (numel (varargin), 2) != 0) error ("glmval: Name-Value arguments must be in pairs."); endif while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'confidence' confidence = varargin {2}; if (! (isscalar (confidence) && isnumeric (confidence) && confidence > 0 && confidence < 1)) error ("glmval: 'Confidence' must be a scalar between 0 and 1."); endif case 'constant' constant = tolower (varargin {2}); if (strcmpi (constant, 'on')) constant = true; elseif (strcmpi (constant, 'off')) constant = false; else error ("glmval: 'Constant' should be either 'on' or 'off'."); endif case 'offset' offset = varargin {2}; if (! (isnumeric (offset) && isequal (numel (offset), size (X, 1)))) error (strcat ("glmval: 'Offset' must be a numeric vector", " of the same length as the rows in X.")); endif offset = offset(:); case 'simultaneous' simultaneous = varargin {2}; if (! isscalar (simultaneous) ... || ! (islogical (simultaneous) || isnumeric (simultaneous)) ... || ! any (simultaneous == [0, 1])) error (strcat ("glmval: 'simultaneous' must be a logical or", ... " numeric (0 or 1) scalar.")); endif simultaneous = logical (simultaneous); case {'size', 'binomialsize'} N = varargin {2}; if (! isnumeric (N) || ! (isscalar (N) || isvector (N) && isequal (numel (N), size (X, 1)))) error (strcat ("glmval: 'size' must be a scalar or a vector with", " one value for each row of X.")); endif N = N(:); otherwise error ("glmval: unknown parameter name."); endswitch varargin(1:2) = []; endwhile ## Adjust X based on constant if (constant) X = [ones(size (X, 1), 1), X]; endif ## Predict yhat eta = X * b + offset; yhat = N .* ilink(eta); ## Compute lower and upper bounds if (nargout > 1) if (isempty (stats)) error (strcat ("glmval: cannot compute confidence", " intervals without STATS structure.")); endif if (isnan (stats.s)) y_lo = NaN (size (yhat)); y_hi = NaN (size (yhat)); else V = (stats.se * stats.se') .* stats.coeffcorr; XVX = sum ((X * V) .* X, 2); if (simultaneous) dof = length (b); if (stats.estdisp) Xcv = sqrt (dof * finv (confidence, dof, stats.dfe)); else Xcv = sqrt (chi2inv (confidence, dof)); endif elseif (stats.estdisp) Xcv = tinv ((1 + confidence) / 2, stats.dfe); else Xcv = norminv ((1 + confidence) / 2); endif interval = Xcv * sqrt (XVX); int_hilo = [N.*ilink(eta-interval) N.*ilink(eta+interval)]; y_lo = yhat - min (int_hilo, [], 2); y_hi = max (int_hilo, [], 2) - yhat; endif endif endfunction %!demo %! x = [210, 230, 250, 270, 290, 310, 330, 350, 370, 390, 410, 430]'; %! n = [48, 42, 31, 34, 31, 21, 23, 23, 21, 16, 17, 21]'; %! y = [1, 2, 0, 3, 8, 8, 14, 17, 19, 15, 17, 21]'; %! b = glmfit (x, [y n], 'binomial', 'Link', 'probit'); %! yfit = glmval (b, x, 'probit', 'Size', n); %! plot (x, y./n, 'o', x, yfit ./ n, '-') ## Test input validation %!error glmval () %!error glmval (1) %!error glmval (1, 2) %!error ... %! glmval ('asd', [1; 1; 1], 'probit') %!error ... %! glmval ([], [1; 1; 1], 'probit') %!error ... %! glmval ([0.1; 0.3; 0.4], [], 'probit') %!error ... %! glmval ([0.1; 0.3; 0.4], 'asd', 'probit') %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', {1, 2})) %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', 'norminv')) %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', 'some', 'Derivative', @(x)x, 'Inverse', 'normcdf')) %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', 1, 'Derivative', @(x)x, 'Inverse', 'normcdf')) %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', @(x) [x, x], 'Derivative', @(x)x, 'Inverse', 'normcdf')) %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', 'what', 'Derivative', @(x)x, 'Inverse', 'normcdf')) %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', @(x)x, 'Derivative', 'some', 'Inverse', 'normcdf')) %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', @(x)x, 'Derivative', 1, 'Inverse', 'normcdf')) %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', @(x)x, 'Derivative', @(x) [x, x], 'Inverse', 'normcdf')) %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', @(x)x, 'Derivative', 'what', 'Inverse', 'normcdf')) %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', @(x)x, 'Derivative', 'normcdf', 'Inverse', 'some')) %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', @(x)x, 'Derivative', 'normcdf', 'Inverse', 1)) %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', @(x)x, 'Derivative', 'normcdf', 'Inverse', @(x) [x, x])) %!error ... %! glmval (rand (3,1), rand (5,2), struct ('Link', @(x)x, 'Derivative', 'normcdf', 'Inverse', 'what')) %!error ... %! glmval (rand (3,1), rand (5,2), {'log'}) %!error ... %! glmval (rand (3,1), rand (5,2), {'log', 'hijy'}) %!error ... %! glmval (rand (3,1), rand (5,2), {1, 2, 3, 4}) %!error ... %! glmval (rand (3,1), rand (5,2), {'log', 'dfv', 'dfgvd'}) %!error ... %! glmval (rand (3,1), rand (5,2), {@(x) [x, x], 'dfv', 'dfgvd'}) %!error ... %! glmval (rand (3,1), rand (5,2), {@(x) what (x), 'dfv', 'dfgvd'}) %!error ... %! glmval (rand (3,1), rand (5,2), {@(x) x, 'dfv', 'dfgvd'}) %!error ... %! glmval (rand (3,1), rand (5,2), {@(x) x, @(x) [x, x], 'dfgvd'}) %!error ... %! glmval (rand (3,1), rand (5,2), {@(x) x, @(x) what (x), 'dfgvd'}) %!error ... %! glmval (rand (3,1), rand (5,2), {@(x) x, @(x) x, 'dfgvd'}) %!error ... %! glmval (rand (3,1), rand (5,2), {@(x) x, @(x) x, @(x) [x, x]}) %!error ... %! glmval (rand (3,1), rand (5,2), {@(x) x, @(x) x, @(x) what (x)}) %!error ... %! glmval (rand (3,1), rand (5,2), NaN) %!error ... %! glmval (rand (3,1), rand (5,2), [1, 2]) %!error ... %! glmval (rand (3,1), rand (5,2), [1i]) %!error ... %! glmval (rand (3,1), rand (5,2), ['log'; 'log1']) %!error ... %! glmval (rand (3,1), rand (5,2), 'somelinkfunction') %!error ... %! glmval (rand (3,1), rand (5,2), true) %!error ... %! glmval (rand (3,1), rand (5,2), 'probit', struct ('s', 1)) %!error ... %! glmval (rand (3,1), rand (5,2), 'probit', 'confidence') %!error ... %! glmval (rand (3,1), rand (5,2), 'probit', 'confidence', 0) %!error ... %! glmval (rand (3,1), rand (5,2), 'probit', 'confidence', 1.2) %!error ... %! glmval (rand (3,1), rand (5,2), 'probit', 'confidence', [0.9, 0.95]) %!error ... %! glmval (rand (3, 1), rand (5, 2), 'probit', 'constant', 1) %!error ... %! glmval (rand (3, 1), rand (5, 2), 'probit', 'constant', 'o') %!error ... %! glmval (rand (3, 1), rand (5, 2), 'probit', 'constant', true) %!error ... %! glmval (rand (3, 1), rand (5, 2), 'probit', 'offset', [1; 2; 3; 4]) %!error ... %! glmval (rand (3, 1), rand (5, 2), 'probit', 'offset', 'asdfg') %!test # numeric 0/1 are accepted for 'simultaneous' (MATLAB compatibility) %! b = [0.2; 0.5]; %! X = [1; 2; 3]; %! assert_equal (numel (glmval (b, X, 'logit', 'simultaneous', 0)), 3); %! assert_equal (numel (glmval (b, X, 'logit', 'simultaneous', 1)), 3); %!test # 'BinomialSize' is accepted as an alias for 'size' %! b = [0.2; 0.5; -0.3]; %! X = [0.1 0.2; 0.3 0.4; 0.5 0.6; 0.7 0.8]; %! assert_equal (glmval (b, X, 'logit', 'BinomialSize', 10), ... %! glmval (b, X, 'logit', 'size', 10)); %!test # CI: fixed-dispersion uses the normal quantile; rows are independent %! X = [1 2; 2 1; 3 3; 4 2; 5 4; 6 5]; %! y = [1; 0; 2; 3; 2; 4]; %! [b, dev, stats] = glmfit (X, y, 'poisson'); %! Xnew = [2 2; 4 3]; %! [yh, ylo, yhi] = glmval (b, Xnew, 'log', stats); %! Xd = [ones(2, 1), Xnew]; %! se_eta = sqrt (sum ((Xd * stats.covb) .* Xd, 2)); %! eta = Xd * b; %! z = norminv (0.975); %! lo = exp (eta) - exp (eta - z * se_eta); %! hi = exp (eta + z * se_eta) - exp (eta); %! assert_equal (ylo, lo, 1e-10); %! assert_equal (yhi, hi, 1e-10); %! [~, l1, h1] = glmval (b, Xnew(1,:), 'log', stats); %! assert_equal ([ylo(1); yhi(1)], [l1; h1], 1e-12); %!error ... %! glmval (rand (3, 1), rand (5, 2), 'probit', 'simultaneous', 'asdfg') %!error ... %! glmval (rand (3, 1), rand (5, 2), 'probit', 'simultaneous', [true, false]) %!error ... %! glmval (rand (3, 1), rand (5, 2), 'probit', 'size', 'asd') %!error ... %! glmval (rand (3, 1), rand (5, 2), 'probit', 'size', [2, 3, 4]) %!error ... %! glmval (rand (3, 1), rand (5, 2), 'probit', 'size', [2; 3; 4]) %!error ... %! glmval (rand (3, 1), rand (5, 2), 'probit', 'size', ones (3)) %!error ... %! glmval (rand (3, 1), rand (5, 2), 'probit', 'someparam', 4) %!error ... %! [y,lo,hi] = glmval (rand (3, 1), rand (5, 2), 'probit') statistics-release-1.9.2/inst/Regression/invpred.m000066400000000000000000000257171524624707500223050ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## Octave is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## Octave is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Octave; see the file COPYING. If not, ## see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x0} =} invpred (@var{x}, @var{y}, @var{y0}) ## @deftypefnx {statistics} {[@var{x0}, @var{dxlo}, @var{dxup}] =} invpred (@var{x}, @var{y}, @var{y0}) ## @deftypefnx {statistics} {[@dots{}] =} invpred (@dots{}, @var{name}, @var{value}) ## ## Inverse prediction from a simple linear regression. ## ## @code{@var{x0} = invpred (@var{x}, @var{y}, @var{y0})} fits the simple ## linear regression of @var{y} on @var{x} and returns, for each element of ## @var{y0}, the value of the predictor at which the fitted line takes that ## response. @var{x} and @var{y} must be vectors of real values of the same ## length; @var{y0} may be of any size and @var{x0} is returned with the same ## size. Observations where either @var{x} or @var{y} is @code{NaN} are ## dropped in pairs before the fit. ## ## @code{[@var{x0}, @var{dxlo}, @var{dxup}] = invpred (@dots{})} also returns ## the width of a confidence interval on either side of @var{x0}, so that the ## interval is @code{[@var{x0} - @var{dxlo}, @var{x0} + @var{dxup}]}. The ## bounds follow Fieller's theorem and are therefore not symmetric about ## @var{x0}. They are not simultaneous over the elements of @var{y0}, and ## they need not be finite: when the slope is not significantly different from ## zero at the requested level the interval is unbounded, and @var{dxlo} and ## @var{dxup} are both @code{Inf}. ## ## @code{[@dots{}] = invpred (@dots{}, @var{name}, @var{value})} accepts the ## following name-value pairs: ## ## @itemize ## @item ## @qcode{"alpha"} is the significance level of the interval, a scalar ## strictly between 0 and 1, so that the interval has confidence ## @math{100 * (1 - @var{alpha})%}. The default is @qcode{0.05}. ## ## @item ## @qcode{"predopt"} selects what the interval covers. With ## @qcode{"observation"}, the default, it covers a new observation whose ## response is @var{y0}. With @qcode{"curve"}, it covers the point at which ## the true regression line takes the value @var{y0}, and is narrower because ## it carries no new-observation variance. ## @end itemize ## ## @seealso{regress, fitlm, polyfit} ## @end deftypefn function [x0, dxlo, dxup] = invpred (x, y, y0, varargin) ## Check for valid number of input arguments if (nargin < 3) print_usage (); endif if (! (isvector (x) && isnumeric (x) && isreal (x))) error ("invpred: X must be a vector of real values."); endif if (! (isvector (y) && isnumeric (y) && isreal (y))) error ("invpred: Y must be a vector of real values."); endif if (numel (x) != numel (y)) error ("invpred: X and Y must have the same length."); endif if (! (isnumeric (y0) && isreal (y0))) error ("invpred: Y0 must be numeric and real."); endif ## Parse optional name-value pairs alpha = 0.05; predopt = "observation"; if (mod (numel (varargin), 2) != 0) error ("invpred: optional arguments must be name-value pairs."); endif for i = 1:2:numel (varargin) name = varargin{i}; value = varargin{i+1}; if (! (ischar (name) && isrow (name))) error ("invpred: parameter name must be a character vector."); endif switch (lower (name)) case 'alpha' alpha = value; case 'predopt' predopt = value; otherwise error ("invpred: invalid parameter name: %s.", name); endswitch endfor if (! (isnumeric (alpha) && isreal (alpha) && isscalar (alpha) ... && alpha > 0 && alpha < 1)) error ("invpred: ALPHA must be a scalar strictly between 0 and 1."); endif if (! (ischar (predopt) && isrow (predopt)) ... || ! any (strcmpi (predopt, {"curve", "observation"}))) error ("invpred: PREDOPT value must be 'curve' or 'observation'."); endif ## Drop observations that are missing in either variable x = x(:); y = y(:); keep = ! (isnan (x) | isnan (y)); x = x(keep); y = y(keep); n = numel (x); xbar = mean (x); ybar = mean (y); sxx = sum ((x - xbar) .^ 2); if (sxx == 0) error ("invpred: cannot compute inverse predictions if X is constant."); endif ## Fit the line and take the residual variance about it slope = sum ((x - xbar) .* (y - ybar)) / sxx; s2 = sum ((y - (ybar + slope * (x - xbar))) .^ 2) / (n - 2); ## The point estimate simply inverts the fitted line offset = (y0 - ybar) / slope; x0 = xbar + offset; if (nargout < 2) return; endif ## Fieller's interval. The factor g compares the sampling variability of ## the slope with the slope itself; once it reaches 1 the line cannot be ## distinguished from a horizontal one and the interval is unbounded. t_crit = tinv (1 - alpha / 2, n - 2); g = t_crit ^ 2 * s2 / (slope ^ 2 * sxx); if (g >= 1) dxlo = Inf (size (y0)); dxup = Inf (size (y0)); return; endif ## A new observation carries its own variance; a point on the curve does not extra = double (strcmpi (predopt, "observation")); centre = offset / (1 - g); halfwidth = (t_crit * sqrt (s2) / (abs (slope) * (1 - g))) ... .* sqrt (offset .^ 2 / sxx + (1 - g) * (1 / n + extra)); dxlo = x0 - (xbar + centre - halfwidth); dxup = (xbar + centre + halfwidth) - x0; endfunction %!demo %! ## Estimate the predictor value at which a fitted line reaches a response %! ## of 20, with a 95% confidence interval either side of it. %! %! x = (1:10)'; %! y = 2 + 3 * x + [0.5; -0.3; 0.2; 0.8; -0.6; 0.1; -0.4; 0.7; -0.2; 0.3]; %! [x0, dxlo, dxup] = invpred (x, y, 20) %! interval = [x0 - dxlo, x0 + dxup] ## The point estimate inverts the fitted line. %!test %! x = (1:10)'; %! y = 2 + 3 * x + [0.5; -0.3; 0.2; 0.8; -0.6; 0.1; -0.4; 0.7; -0.2; 0.3]; %! x0 = invpred (x, y, 20); %! assert_equal (x0, 5.964741641337386, 1e-12); ## A confidence interval is returned on either side of the estimate. %!test %! x = (1:10)'; %! y = 2 + 3 * x + [0.5; -0.3; 0.2; 0.8; -0.6; 0.1; -0.4; 0.7; -0.2; 0.3]; %! [x0, dxlo, dxup] = invpred (x, y, 20); %! assert_equal (x0, 5.964741641337386, 1e-12); %! assert_equal (dxlo, 0.408566704275405, 1e-12); %! assert_equal (dxup, 0.410279499114103, 1e-12); ## The interval is not symmetric about the estimate. %!test %! x = (1:10)'; %! y = 2 + 3 * x + [0.5; -0.3; 0.2; 0.8; -0.6; 0.1; -0.4; 0.7; -0.2; 0.3]; %! [~, dxlo, dxup] = invpred (x, y, 20); %! assert_equal (dxlo != dxup, true); ## Y0 may hold several values, and the output follows its shape. %!test %! x = (1:10)'; %! y = 2 + 3 * x + [0.5; -0.3; 0.2; 0.8; -0.6; 0.1; -0.4; 0.7; -0.2; 0.3]; %! [x0, dxlo, dxup] = invpred (x, y, [10; 20; 30]); %! assert_equal (x0, [2.621276595744681; 5.964741641337386; ... %! 9.308206686930092], 1e-12); %! assert_equal (dxlo, [0.432537159571881; 0.408566704275405; ... %! 0.433439039304474], 1e-12); %! assert_equal (dxup, [0.421927689383959; 0.410279499114103; ... %! 0.447474099169791], 1e-12); %! [x0r, dxlor, dxupr] = invpred (x, y, [10, 20, 30]); %! assert_equal (x0r, x0'); %! assert_equal (dxlor, dxlo'); %! assert_equal (dxupr, dxup'); ## A matrix of responses is returned as a matrix. %!test %! x = (1:10)'; %! y = 2 + 3 * x + [0.5; -0.3; 0.2; 0.8; -0.6; 0.1; -0.4; 0.7; -0.2; 0.3]; %! x0 = invpred (x, y, [10, 20; 30, 40]); %! assert_equal (size (x0), [2, 2]); %! assert_equal (x0, [2.621276595744681, 5.964741641337386; ... %! 9.308206686930092, 12.651671732522798], 1e-12); ## An empty Y0 gives an empty result. %!test %! x = (1:10)'; %! y = 2 + 3 * x + [0.5; -0.3; 0.2; 0.8; -0.6; 0.1; -0.4; 0.7; -0.2; 0.3]; %! x0 = invpred (x, y, []); %! assert_equal (size (x0), [0, 0]); ## ALPHA widens the interval without moving the estimate. %!test %! x = (1:10)'; %! y = 2 + 3 * x + [0.5; -0.3; 0.2; 0.8; -0.6; 0.1; -0.4; 0.7; -0.2; 0.3]; %! [x0, dxlo, dxup] = invpred (x, y, 20, 'alpha', 0.01); %! assert_equal (x0, 5.964741641337386, 1e-12); %! assert_equal (dxlo, 0.594536204654897, 1e-12); %! assert_equal (dxup, 0.598170042325828, 1e-12); ## An interval on the curve is narrower than one on a new observation. %!test %! x = (1:10)'; %! y = 2 + 3 * x + [0.5; -0.3; 0.2; 0.8; -0.6; 0.1; -0.4; 0.7; -0.2; 0.3]; %! [x0, dxlo, dxup] = invpred (x, y, 20, 'predopt', 'curve'); %! assert_equal (x0, 5.964741641337386, 1e-12); %! assert_equal (dxlo, 0.124048892442359, 1e-12); %! assert_equal (dxup, 0.125761687281057, 1e-12); %! [~, obslo, obsup] = invpred (x, y, 20, 'predopt', 'observation'); %! assert_equal (dxlo < obslo && dxup < obsup, true); ## A slope indistinguishable from zero leaves the interval unbounded. %!test %! [x0, dxlo, dxup] = invpred ((1:5)', [1; 5; 2; 8; 3], 4); %! assert_equal (x0, 3.285714285714286, 1e-12); %! assert_equal (dxlo, Inf); %! assert_equal (dxup, Inf); ## Rows and columns are accepted alike. %!test %! x = (1:10)'; %! y = 2 + 3 * x + [0.5; -0.3; 0.2; 0.8; -0.6; 0.1; -0.4; 0.7; -0.2; 0.3]; %! [x0, dxlo, dxup] = invpred (x', y', 20); %! assert_equal (x0, 5.964741641337386, 1e-12); %! assert_equal (dxlo, 0.408566704275405, 1e-12); %! assert_equal (dxup, 0.410279499114103, 1e-12); ## Observations missing in either variable are dropped in pairs. %!test %! x = (1:10)'; %! y = 2 + 3 * x + [0.5; -0.3; 0.2; 0.8; -0.6; 0.1; -0.4; 0.7; -0.2; 0.3]; %! xn = x; xn(3) = NaN; %! assert_equal (invpred (xn, y, 20), 5.967084254482929, 1e-12); %! yn = y; yn(4) = NaN; %! assert_equal (invpred (x, yn, 20), 5.988352745424295, 1e-12); %!error invpred ((1:10)', (1:10)') %!error ... %! invpred (ones (10, 2), (1:10)', 5) %!error ... %! invpred ((1:10)', ones (10, 2), 5) %!error ... %! invpred ((1:10)', (1:5)', 5) %!error ... %! invpred ((1:10)', (1:10)', 5, 'alpha', 5) %!error ... %! invpred ((1:10)', (1:10)', 5, 'alpha', [0.05, 0.01]) %!error ... %! invpred ((1:10)', (1:10)', 5, 'predopt', 'bogus') %!error ... %! invpred ((1:10)', (1:10)', 5, 'badname', 1) %!error ... %! invpred ((1:10)', (1:10)', 5, 'alpha') %!error ... %! invpred (ones (10, 1), (1:10)', 5) statistics-release-1.9.2/inst/Regression/lasso.m000066400000000000000000000375701524624707500217570ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{B} =} lasso (@var{X}, @var{y}) ## @deftypefnx {statistics} {[@var{B}, @var{FitInfo}] =} lasso (@var{X}, @var{y}) ## @deftypefnx {statistics} {[@dots{}] =} lasso (@dots{}, @var{Name}, @var{Value}) ## ## Lasso and elastic-net regularized least-squares regression. ## ## @code{@var{B} = lasso (@var{X}, @var{y})} fits a series of regularized linear ## models of the response @var{y} on the predictor matrix @var{X} by lasso, over ## a sequence of values of the regularization parameter @var{Lambda}. @var{B} ## is a @math{P*L} matrix whose column @math{k} holds the coefficient estimates ## for the @math{k}-th @var{Lambda}, in ascending order of @var{Lambda}. ## ## @code{[@var{B}, @var{FitInfo}] = lasso (@dots{})} additionally returns a ## structure @var{FitInfo} with fields @code{Intercept}, @code{Lambda}, ## @code{Alpha}, @code{DF} (number of non-zero coefficients), and @code{MSE} ## (mean squared error), one entry per value of @var{Lambda}. ## ## The following @qcode{Name-Value} pairs are supported: ## ## @multitable @columnfractions 0.18 0.82 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Alpha'} @tab The elastic-net mixing parameter in ## @math{(0, 1]}. @math{1} (default) is the lasso penalty; smaller values add a ## ridge penalty. ## ## @item @qcode{'Lambda'} @tab A vector of non-negative regularization ## parameters. By default a geometric sequence of @qcode{'NumLambda'} values is ## used, from the smallest value that drives all coefficients to zero down to ## @qcode{'LambdaRatio'} times that value. ## ## @item @qcode{'NumLambda'} @tab The number of @var{Lambda} values in the ## default sequence (default @math{100}). ## ## @item @qcode{'LambdaRatio'} @tab The ratio of the smallest to the largest ## @var{Lambda} in the default sequence (default @math{1e-4}, or @math{1e-2} ## when the number of observations is below the number of predictors). ## ## @item @qcode{'Standardize'} @tab Whether to standardize @var{X} to zero mean ## and unit variance before fitting (default @qcode{true}). Coefficients are ## always returned on the original scale. ## ## @item @qcode{'Weights'} @tab A vector of non-negative observation weights. ## ## @item @qcode{'RelTol'} @tab Convergence tolerance for the coordinate descent ## (default @math{1e-4}). ## ## @item @qcode{'MaxIter'} @tab Maximum number of coordinate-descent iterations ## (default @math{1e5}). ## ## @item @qcode{'DFmax'} @tab The maximum number of non-zero coefficients; the ## default sequence stops once this is exceeded. ## ## @item @qcode{'Intercept'} @tab Whether to fit a constant term (default ## @qcode{true}). ## ## @item @qcode{'PredictorNames'} @tab A cell array of predictor names, kept in ## @var{FitInfo}. ## ## @item @qcode{'CV'} @tab The number of folds @math{K} for @math{K}-fold ## cross-validation of the mean squared error, or a @code{cvpartition} object. ## ## @item @qcode{'MCReps'} @tab The number of Monte-Carlo repetitions of the ## cross-validation (default @math{1}). ## @end multitable ## ## When @qcode{'CV'} is used, @var{FitInfo}@code{.MSE} is the cross-validated ## error, plus @code{SE}, @code{LambdaMinMSE}, @code{IndexMinMSE}, ## @code{Lambda1SE}, and @code{Index1SE}, which report the @var{Lambda} with the ## lowest error and the largest @var{Lambda} within one standard error of it. ## The fold assignment is random, so these selections are not reproducible ## without fixing the random seed. ## ## @seealso{ridge, regress, lassoglm} ## @end deftypefn function [B, FitInfo] = lasso (X, y, varargin) if (nargin < 2) print_usage (); endif if (! (isnumeric (X) && isreal (X) && ismatrix (X))) error ("lasso: X must be a real matrix."); endif if (! (isnumeric (y) && isreal (y) && isvector (y))) error ("lasso: Y must be a real vector."); endif y = y(:); if (rows (X) != numel (y)) error ("lasso: X and Y must have the same number of observations."); endif ## Defaults and Name-Value parsing. alpha = 1; lambda = []; numlambda = 100; lambdaratio = []; standardize = true; weights = []; reltol = 1e-4; maxiter = 1e5; dfmax = columns (X); intercept = true; prednames = {}; cvarg = []; mcreps = 1; if (mod (numel (varargin), 2) != 0) error ("lasso: optional arguments must be Name-Value pairs."); endif for k = 1:2:numel (varargin) if (! ischar (varargin{k})) error ("lasso: parameter names must be character vectors."); endif switch (lower (varargin{k})) case "alpha"; alpha = varargin{k+1}; case "lambda"; lambda = varargin{k+1}; case "numlambda"; numlambda = varargin{k+1}; case "lambdaratio"; lambdaratio = varargin{k+1}; case "standardize"; standardize = logical (varargin{k+1}); case "weights"; weights = varargin{k+1}; case "reltol"; reltol = varargin{k+1}; case "maxiter"; maxiter = varargin{k+1}; case "dfmax"; dfmax = varargin{k+1}; case "intercept"; intercept = logical (varargin{k+1}); case "predictornames"; prednames = varargin{k+1}; case "cv"; cvarg = varargin{k+1}; case "mcreps"; mcreps = varargin{k+1}; otherwise error ("lasso: unknown parameter name '%s'.", varargin{k}); endswitch endfor if (! (isnumeric (alpha) && isscalar (alpha) && alpha > 0 && alpha <= 1)) error ("lasso: 'Alpha' must be a scalar in (0, 1]."); endif if (! isempty (lambda) && (! isnumeric (lambda) || any (lambda < 0))) error ("lasso: 'Lambda' must be a vector of non-negative values."); endif if (! intercept && standardize) warning (["lasso: when the 'Intercept' value is false, the " ... "'Standardize' value is set to false."]); standardize = false; endif ## Drop observations with missing values. ok = ! (any (isnan (X), 2) | isnan (y)); X = X(ok,:); y = y(ok); [n, p] = size (X); ## Observation weights, normalised to sum to one. if (isempty (weights)) w = ones (n, 1) / n; else if (! (isnumeric (weights) && isvector (weights) && numel (weights) == n && all (weights >= 0) && any (weights > 0))) error ("lasso: 'Weights' must be a non-negative vector, one per observation."); endif w = weights(:) / sum (weights); endif ## Centre and (optionally) standardise the predictors; centre the response. if (intercept) mux = w' * X; muy = w' * y; else mux = zeros (1, p); muy = 0; endif Xc = X - mux; if (standardize) sig = sqrt (w' * (Xc .^ 2)); sig(sig == 0) = 1; else sig = ones (1, p); endif Xs = Xc ./ sig; yc = y - muy; ## Column "norms" d_j and the response correlations for the path bounds. d = (w' * (Xs .^ 2))'; xy = Xs' * (w .* yc); lambda_max = max (abs (xy)) / alpha; ## Regularization path. userlambda = ! isempty (lambda); if (userlambda) lampath = sort (lambda(:)', "descend"); # fit large -> small (warm start) else if (isempty (lambdaratio)) lambdaratio = ifelse (n < p, 1e-2, 1e-4); endif lampath = lambda_max * lambdaratio .^ ((0:numlambda-1) / (numlambda - 1)); endif nullmse = w' * (yc .^ 2); soft = @(z, g) sign (z) .* max (abs (z) - g, 0); ## Fit along the path, warm-started from the previous solution. L = numel (lampath); Bstd = zeros (p, L); bs = zeros (p, 1); nkeep = L; for kk = 1:L lam = lampath(kk); for it = 1:maxiter bprev = bs; for j = 1:p rj = (w .* (yc - Xs * bs + Xs(:,j) * bs(j))); rho = Xs(:,j)' * rj; bs(j) = soft (rho, lam * alpha) / (d(j) + lam * (1 - alpha)); endfor if (max (abs (bs - bprev)) <= reltol * max (max (abs (bs)), 1)) break; endif endfor Bstd(:,kk) = bs; ## Trim the default path once the fit is essentially exact or too dense. if (! userlambda) bo = bs ./ sig'; mse = w' * ((yc - Xs * bs) .^ 2); if (sum (bs != 0) > dfmax) nkeep = kk - 1; break; elseif (mse < 1e-3 * nullmse) nkeep = kk; break; endif endif endfor Bstd = Bstd(:,1:nkeep); lampath = lampath(1:nkeep); ## Unwind to the original scale; assemble outputs in ascending Lambda order. B = flip (Bstd ./ sig', 2); lampath = flip (lampath); icept = zeros (1, columns (B)); mse = zeros (1, columns (B)); for kk = 1:columns (B) icept(kk) = muy - mux * B(:,kk); mse(kk) = w' * ((y - icept(kk) - X * B(:,kk)) .^ 2); endfor FitInfo.Intercept = icept; FitInfo.Lambda = lampath; FitInfo.Alpha = alpha; FitInfo.DF = sum (B != 0, 1); FitInfo.MSE = mse; if (! isempty (prednames)) FitInfo.PredictorNames = prednames; endif ## Cross-validation of the mean squared error along the fitted path. The ## fold assignment is random, so the selected lambdas are not reproducible ## across runs (or identical to MATLAB) without a fixed random seed. if (! isempty (cvarg)) lam = FitInfo.Lambda; foldmse = []; for rep = 1:mcreps if (isnumeric (cvarg)) cvp = cvpartition (n, "KFold", cvarg); elseif (rep == 1) cvp = cvarg; else cvp = repartition (cvp); endif for f = 1:cvp.NumTestSets tr = training (cvp, f); te = test (cvp, f); wtr = w(tr) / sum (w(tr)); yhat = lasso_foldpredict_ (X(tr,:), y(tr), wtr, X(te,:), lam, alpha, ... standardize, intercept, reltol, maxiter); foldmse(end+1,:) = mean ((y(te) - yhat) .^ 2, 1); endfor endfor cvmse = mean (foldmse, 1); cvse = std (foldmse, 0, 1) / sqrt (rows (foldmse)); FitInfo.MSE = cvmse; FitInfo.SE = cvse; [~, imin] = min (cvmse); FitInfo.LambdaMinMSE = lam(imin); FitInfo.IndexMinMSE = imin; i1se = max (find (cvmse <= cvmse(imin) + cvse(imin))); FitInfo.Lambda1SE = lam(i1se); FitInfo.Index1SE = i1se; endif endfunction ## Fit the lasso path on training data and predict at the test rows, for the ## fixed sequence LAM (returns an ntest-by-numel(LAM) matrix of predictions). function yhat = lasso_foldpredict_ (Xtr, ytr, w, Xte, lam, alpha, ... standardize, intercept, reltol, maxiter) [n, p] = size (Xtr); if (intercept) mux = w' * Xtr; muy = w' * ytr; else mux = zeros (1, p); muy = 0; endif Xc = Xtr - mux; if (standardize) sig = sqrt (w' * (Xc .^ 2)); sig(sig == 0) = 1; else sig = ones (1, p); endif Xs = Xc ./ sig; yc = ytr - muy; d = (w' * (Xs .^ 2))'; soft = @(z, g) sign (z) .* max (abs (z) - g, 0); [lamd, ord] = sort (lam(:)', "descend"); # warm-start from large lambda L = numel (lamd); Bstd = zeros (p, L); bs = zeros (p, 1); for kk = 1:L for it = 1:maxiter bprev = bs; for j = 1:p rj = w .* (yc - Xs * bs + Xs(:,j) * bs(j)); bs(j) = soft (Xs(:,j)' * rj, lamd(kk) * alpha) ... / (d(j) + lamd(kk) * (1 - alpha)); endfor if (max (abs (bs - bprev)) <= reltol * max (max (abs (bs)), 1)) break; endif endfor Bstd(:,kk) = bs; endfor Bo = Bstd ./ sig'; yhat = zeros (rows (Xte), L); yhat(:,ord) = (muy - mux * Bo) + Xte * Bo; endfunction function out = ifelse (cond, a, b) if (cond) out = a; else out = b; endif endfunction %!demo %! ## Lasso path drives coefficients to zero as the penalty grows %! rng (42); %! X = rand (50, 6); %! b = [3; 0; -2; 0; 1.5; 0]; %! y = X * b + 0.1 * randn (50, 1); %! [B, FitInfo] = lasso (X, y); %! plot (log (FitInfo.Lambda), B'); %! xlabel ("log (Lambda)"); ylabel ("coefficient"); %!shared X, y %! n = 20; %! X = zeros (n, 6); %! for j = 1:6 %! X(:,j) = mod ((1:n)' * j, 7) + cos ((1:n)' * j); %! endfor %! y = X * [3;0;-2;0;1.5;0] + 0.1 * sin ((1:n)' * 3); %!test # MATLAB parity: coefficients and intercept at explicit Lambda (lasso) %! lam = [2 1 0.5 0.2 0.1 0.05 0.01]; %! [B, I] = lasso (X, y, "Lambda", lam); %! assert_equal (B(:,1), [2.99553372017526; 0; -1.99263231698882; ... %! 0.00211715208971491; 1.49324410153201; 0], 1e-3); %! assert_equal (B(:,7), [2.23897682145602; 0; -0.482753193552206; ... %! 0.0267252048528861; 0.118128986514695; 0], 1e-3); %! assert_equal (I.Intercept(1), 0.00566677333597632, 1e-3); %! assert_equal (I.Intercept(7), 2.00069783707236, 1e-3); %! assert_equal (I.DF, [4 4 4 4 4 4 4]); %! assert_equal (I.MSE(1), 0.00590963740808093, 1e-3); %!test # MATLAB parity: Lambda is ascending and columns follow it %! [B, I] = lasso (X, y, "Lambda", [2 1 0.5 0.2 0.1 0.05 0.01]); %! assert_equal (I.Lambda, [0.01 0.05 0.1 0.2 0.5 1 2], 1e-12); %! assert_equal (issorted (I.Lambda), true); %!test # MATLAB parity: default path endpoints and length %! [B, I] = lasso (X, y); %! assert_equal (I.Lambda(end), 7.18535290566157, 1e-4); %! assert_equal (I.Lambda(1), 0.119858910419061, 1e-4); %! assert_equal (numel (I.Lambda), 45); %!test # MATLAB parity: elastic net (Alpha = 0.5) %! B = lasso (X, y, "Lambda", [2 1 0.5 0.2 0.1 0.05 0.01], "Alpha", 0.5); %! assert_equal (B(:,1), [2.945477; -0.009332; -1.940313; ... %! 0.053970; 1.485977; -0.049272], 2e-3); %!test # MATLAB parity: Standardize = false %! [B, I] = lasso (X, y, "Lambda", [2 1 0.5 0.2 0.1 0.05 0.01], ... %! "Standardize", false); %! assert_equal (B(:,1), [2.99774227822918; 0; -1.99668154187853; ... %! 0.00196231656035102; 1.49691209089871; 0], 2e-3); %!test # at Lambda -> 0 the lasso solution approaches least squares %! B = lasso (X, y, "Lambda", [1 0.1 0.001]); # columns ascend in Lambda %! bols = regress (y - mean (y), (X - mean (X))); %! assert_equal (B(:,1), bols, 1e-2); # smallest Lambda ~ OLS %!test # a large Lambda drives all coefficients to exactly zero %! B = lasso (X, y, "Lambda", 100); %! assert_equal (B, zeros (6, 1)); %!test # DFmax limits the number of non-zero coefficients on the path %! [B, I] = lasso (X, y, "DFmax", 2); %! assert_equal (all (I.DF <= 2), true); %!test # cross-validation adds the selection fields and they are consistent %! rand ("seed", 7); %! [B, I] = lasso (X, y, "CV", 5); %! assert_equal (numel (I.MSE), numel (I.Lambda)); %! assert_equal (numel (I.SE), numel (I.Lambda)); %! assert_equal (all (I.SE >= 0), true); %! assert_equal (I.LambdaMinMSE, I.Lambda(I.IndexMinMSE), 1e-12); %! assert_equal (I.Lambda1SE, I.Lambda(I.Index1SE), 1e-12); %! assert_equal (I.Index1SE >= I.IndexMinMSE, true); # 1-SE picks a larger Lambda %! assert_equal (I.MSE(I.IndexMinMSE), min (I.MSE), 1e-12); %!test # cross-validated MSE at the min is within one SE at the 1-SE lambda %! rand ("seed", 3); %! [~, I] = lasso (X, y, "CV", 4, "MCReps", 2); %! thr = I.MSE(I.IndexMinMSE) + I.SE(I.IndexMinMSE); %! assert_equal (I.MSE(I.Index1SE) <= thr + 1e-9, true); %!warning ... %! lasso (X, y, "Lambda", 0.1, "Intercept", false); ## Test input validation %!error lasso (1) %!error ... %! lasso ([1 2; 3 4], [1 2 3]') %!error lasso (X, y, "Alpha", 0) %!error ... %! lasso (X, y, "Lambda", [-1 2]) statistics-release-1.9.2/inst/Regression/lassoglm.m000066400000000000000000000622721524624707500224540ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{B} =} lassoglm (@var{X}, @var{y}) ## @deftypefnx {statistics} {@var{B} =} lassoglm (@var{X}, @var{y}, @var{distr}) ## @deftypefnx {statistics} {@var{B} =} lassoglm (@var{X}, @var{y}, @var{distr}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{B}, @var{FitInfo}] =} lassoglm (@dots{}) ## ## Lasso and elastic-net regularized generalized linear model regression. ## ## @code{@var{B} = lassoglm (@var{X}, @var{y}, @var{distr})} returns fitted ## least-squares regression coefficients for a generalized linear model of the ## response @var{y} on the predictor data @var{X}, penalized by the lasso (L1) ## or elastic-net penalty. @var{X} is an @math{n}-by-@math{p} numeric matrix of ## @math{p} predictors at each of @math{n} observations, and @var{y} is a ## numeric vector of @math{n} responses. @var{distr} names the distribution of ## the response: @qcode{'normal'} (default), @qcode{'binomial'}, ## @qcode{'poisson'}, @qcode{'gamma'}, or @qcode{'inverse gaussian'}. The ## canonical link function of the chosen distribution is used unless overridden ## by the @qcode{'Link'} option. ## ## @var{B} is a @math{p}-by-@math{L} matrix, where @math{L} is the number of ## regularization (@qcode{'Lambda'}) values used; column @math{k} holds the ## coefficients for the @math{k}-th value of @math{lambda}, in order of ## ascending @math{lambda}. ## ## @code{[@var{B}, @var{FitInfo}] = lassoglm (@dots{})} also returns a structure ## @var{FitInfo} with information about the fitted models: ## ## @multitable @columnfractions 0.2 0.75 ## @item @qcode{Intercept} @tab a @math{1}-by-@math{L} vector of intercept terms ## @item @qcode{Lambda} @tab the @math{1}-by-@math{L} vector of @math{lambda} ## values, in ascending order ## @item @qcode{Alpha} @tab the elastic-net mixing value used ## @item @qcode{DF} @tab the number of nonzero coefficients in each column of ## @var{B} ## @item @qcode{Deviance} @tab the deviance of the fitted model at each ## @math{lambda}; when cross-validation is requested this is instead the ## cross-validated mean deviance ## @end multitable ## ## When cross-validation is requested (see @qcode{'CV'} below), @var{FitInfo} ## additionally contains @qcode{SE} (standard error of the cross-validated ## deviance), @qcode{LambdaMinDeviance} and @qcode{IndexMinDeviance} (the ## @math{lambda} with minimum cross-validated deviance and its index), and ## @qcode{Lambda1SE} and @qcode{Index1SE} (the largest @math{lambda} within one ## standard error of that minimum). ## ## @code{lassoglm} accepts the following @var{Name}/@var{Value} pairs: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'Alpha'} @tab the elastic-net mixing parameter, a scalar in ## @math{(0, 1]}. @qcode{'Alpha'} = 1 is the lasso penalty (default); ## values towards 0 approach ridge regression. ## @item @qcode{'Lambda'} @tab a vector of non-negative @math{lambda} values. ## @item @qcode{'Standardize'} @tab a logical value (default @qcode{true}) ## specifying whether the predictors are standardized before fitting. ## @item @qcode{'Weights'} @tab a vector of non-negative observation weights. ## @item @qcode{'Size'} @tab for the @qcode{'binomial'} distribution, the number ## of trials (a scalar or a per-observation vector); @var{y} holds the number of ## successes. Default is 1 (Bernoulli responses). ## @item @qcode{'Link'} @tab the link function to use instead of the family's ## canonical link. Accepts any link name understood by @code{glmfit} (e.g. ## @qcode{'log'}, @qcode{'probit'}) or a numeric exponent for a power link. ## @item @qcode{'Offset'} @tab a numeric vector, one value per observation, ## added as a fixed term to the linear predictor (not penalized or fitted). ## @item @qcode{'RelTol'} @tab convergence tolerance for the coordinate descent. ## @item @qcode{'MaxIter'} @tab maximum number of iterations. ## @item @qcode{'DFmax'} @tab maximum number of nonzero coefficients. ## @item @qcode{'Intercept'} @tab a logical value (default @qcode{true}) whether ## to fit an intercept term. ## @item @qcode{'PredictorNames'} @tab a cell array of predictor names. ## @item @qcode{'CV'} @tab the number of folds @math{K} for @math{K}-fold ## cross-validation, or a @code{cvpartition} object. The fold assignment is ## random, so the selected @math{lambda} values are not reproducible without a ## fixed random seed. ## @item @qcode{'MCReps'} @tab the number of Monte-Carlo repetitions of the ## cross-validation (default 1). ## @end multitable ## ## @seealso{lasso, glmfit, glmval, cvpartition} ## @end deftypefn function [B, FitInfo] = lassoglm (X, y, distr, varargin) if (nargin < 2) print_usage (); endif if (! (isnumeric (X) && isreal (X) && ismatrix (X))) error ("lassoglm: X must be a real matrix."); endif if (! (isnumeric (y) && isreal (y) && isvector (y))) error ("lassoglm: Y must be a real vector."); endif y = y(:); if (rows (X) != numel (y)) error ("lassoglm: X and Y must have the same number of observations."); endif if (nargin < 3 || isempty (distr)) distr = 'normal'; endif if (! (ischar (distr) && isrow (distr))) error ("lassoglm: DISTR must be a character vector."); endif distr = lower (distr); if (! any (strcmp (distr, {'normal', 'binomial', 'poisson', 'gamma', ... 'inverse gaussian'}))) error ("lassoglm: unknown distribution '%s'.", distr); endif ## Defaults and Name-Value parsing. alpha = 1; lambda = []; numlambda = 100; lambdaratio = []; standardize = true; weights = []; reltol = 1e-4; maxiter = 1e5; dfmax = columns (X); intercept = true; prednames = {}; binomsize = []; linkarg = []; offset = []; cvarg = []; mcreps = 1; usecov = false; if (mod (numel (varargin), 2) != 0) error ("lassoglm: optional arguments must be Name-Value pairs."); endif for k = 1:2:numel (varargin) if (! ischar (varargin{k})) error ("lassoglm: parameter names must be character vectors."); endif switch (lower (varargin{k})) case "alpha"; alpha = varargin{k+1}; case "lambda"; lambda = varargin{k+1}; case "numlambda"; numlambda = varargin{k+1}; case "lambdaratio"; lambdaratio = varargin{k+1}; case "standardize"; standardize = logical (varargin{k+1}); case "weights"; weights = varargin{k+1}; case "size"; binomsize = varargin{k+1}; case "link"; linkarg = varargin{k+1}; case "offset"; offset = varargin{k+1}; case "reltol"; reltol = varargin{k+1}; case "maxiter"; maxiter = varargin{k+1}; case "dfmax"; dfmax = varargin{k+1}; case "intercept"; intercept = logical (varargin{k+1}); case "predictornames"; prednames = varargin{k+1}; case "cv"; cvarg = varargin{k+1}; case "mcreps"; mcreps = varargin{k+1}; case "usecovariance"; usecov = logical (varargin{k+1}); otherwise error ("lassoglm: unknown parameter name '%s'.", varargin{k}); endswitch endfor if (! (isnumeric (alpha) && isscalar (alpha) && alpha > 0 && alpha <= 1)) error ("lassoglm: 'Alpha' must be a scalar in (0, 1]."); endif if (! isempty (lambda) && (! isnumeric (lambda) || any (lambda < 0))) error ("lassoglm: 'Lambda' must be a vector of non-negative values."); endif ## Drop observations with missing values. ok = ! (any (isnan (X), 2) | isnan (y)); X = X(ok,:); y = y(ok); [n, p] = size (X); ## Number of binomial trials per observation (Size), default one. if (strcmp (distr, 'binomial')) if (isempty (binomsize)) N = ones (n, 1); elseif (isscalar (binomsize)) N = binomsize * ones (n, 1); else N = binomsize(:); N = N(ok); endif if (! (isnumeric (N) && all (N > 0))) error ("lassoglm: 'Size' must be a positive scalar or vector."); endif else N = ones (n, 1); endif ## Prior observation weights. PW keeps the raw weights for the deviance sum; ## O is normalised to sum to one and sets the scale of the penalty (as in ## LASSO for the Gaussian case). if (isempty (weights)) pw = ones (n, 1); else if (! (isnumeric (weights) && isvector (weights) && numel (weights) == n && all (weights >= 0) && any (weights > 0))) error ("lassoglm: 'Weights' must be a non-negative vector, one per observation."); endif pw = weights(:); endif o = pw / sum (pw); ## Offset: a fixed per-observation term added to the linear predictor. if (isempty (offset)) off = zeros (n, 1); else if (! (isnumeric (offset) && isvector (offset) && numel (offset) == numel (ok))) error ("lassoglm: 'Offset' must be a numeric vector, one per observation."); endif off = offset(:); off = off(ok); endif ## Response-domain checks for the chosen family. switch (distr) case 'binomial' if (any (y < 0) || any (y > N)) error (strcat ("lassoglm: for the 'binomial' distribution Y must be", ... " between 0 and 'Size'.")); endif case 'poisson' if (any (y < 0)) error ("lassoglm: for the 'poisson' distribution Y must be non-negative."); endif case {'gamma', 'inverse gaussian'} if (any (y <= 0)) error ("lassoglm: for the '%s' distribution Y must be positive.", distr); endif endswitch ## Link, variance, and (unit) deviance functions for the chosen family. A ## user-supplied 'Link' overrides the family's canonical link. [flink, dlink, ilink, varfun, devfun, mulims] = glmfamily (distr); if (! isempty (linkarg)) [flink, dlink, ilink, errmsg] = getlinkfunctions (linkarg); if (! isempty (errmsg)) error ("lassoglm: %s", errmsg); endif endif ## Standardise the predictors using the prior weights (fixed for all fits). mux = o' * X; Xc = X - mux; if (standardize) sig = sqrt (o' * (Xc .^ 2)); sig(sig == 0) = 1; else sig = ones (1, p); endif Xs = Xc ./ sig; ## Intercept-only (null) fit -- offset-aware. Supplies the starting ## intercept B0, the null deviance (the path-trim scale), and the null-model ## fitted mean MU0 used for the LAMBDA_MAX gradient. [b0, mu0] = glm_null_fit (off, y, N, o, flink, dlink, ilink, varfun, ... mulims, intercept, maxiter, reltol); nulldev = sum (pw .* devfun (mu0, y ./ N, N)); ## Regularization path. soft = @(z, g) sign (z) .* max (abs (z) - g, 0); userlambda = ! isempty (lambda); if (userlambda) lampath = sort (lambda(:)', "descend"); else deta0 = dlink (mu0); grad = Xs' * (o .* (y ./ N - mu0) ./ (deta0 .* varfun (mu0, N))); lambda_max = max (abs (grad)) / alpha; if (isempty (lambdaratio)) lambdaratio = ifelse (n < p, 1e-2, 1e-4); endif lampath = lambda_max * lambdaratio .^ ((0:numlambda-1) / (numlambda - 1)); endif ## Fit along the path, warm-started from the previous (larger-lambda) fit. L = numel (lampath); Bstd = zeros (p, L); icept_eta = zeros (1, L); bs = zeros (p, 1); nkeep = L; for kk = 1:L lam = lampath(kk); for outer = 1:maxiter bouter = bs; ## IRLS: quadratic (working-response) approximation at the current fit. eta = off + b0 + Xs * bs; mu = ilink (eta); mu = max (min (mu, mulims(2)), mulims(1)); deta = dlink (mu); ## Working response with the offset removed (fitted by intercept + Xs). zc = eta - off + (y ./ N - mu) .* deta; W = o ./ (deta .^ 2 .* varfun (mu, N)); sumW = sum (W); ## Inner coordinate descent on the penalized weighted least squares. for it = 1:maxiter bprev = bs; if (intercept) b0 = sum (W .* (zc - Xs * bs)) / sumW; endif for j = 1:p rj = zc - b0 - Xs * bs + Xs(:,j) * bs(j); rho = sum (W .* Xs(:,j) .* rj); dj = sum (W .* Xs(:,j) .^ 2); bs(j) = soft (rho, lam * alpha) / (dj + lam * (1 - alpha)); endfor if (max (abs (bs - bprev)) <= reltol * max (max (abs (bs)), 1)) break; endif endfor if (max (abs (bs - bouter)) <= reltol * max (max (abs (bs)), 1)) break; endif endfor ## Snap coordinate-descent residue at the KKT boundary to exact zero, so a ## coefficient that is numerically negligible (e.g. ~1e-16 at LAMBDA_MAX) ## does not count towards the degrees of freedom. bs(abs (bs) < 1e-9) = 0; Bstd(:,kk) = bs; icept_eta(kk) = b0; ## Trim the default path once the model saturates (DFmax) or the fit is ## essentially exact (deviance a negligible fraction of the null deviance). if (! userlambda) if (sum (bs != 0) > dfmax) nkeep = kk - 1; break; endif eta = off + b0 + Xs * bs; mu = max (min (ilink (eta), mulims(2)), mulims(1)); devk = sum (pw .* devfun (mu, y ./ N, N)); if (devk < 1e-3 * nulldev) nkeep = kk; break; endif endif endfor Bstd = Bstd(:,1:nkeep); icept_eta = icept_eta(1:nkeep); lampath = lampath(1:nkeep); ## Unwind to the original predictor scale; assemble ascending-lambda outputs. B = flip (Bstd ./ sig', 2); icept_eta = flip (icept_eta); lampath = flip (lampath); Lk = columns (B); icept = zeros (1, Lk); dev = zeros (1, Lk); for kk = 1:Lk icept(kk) = icept_eta(kk) - mux * B(:,kk); eta = off + icept(kk) + X * B(:,kk); mu = ilink (eta); mu = max (min (mu, mulims(2)), mulims(1)); dev(kk) = sum (pw .* devfun (mu, y ./ N, N)); endfor FitInfo.Intercept = icept; FitInfo.Lambda = lampath; FitInfo.Alpha = alpha; FitInfo.DF = sum (B != 0, 1); FitInfo.Deviance = dev; ## Cross-validation of the mean deviance along the fitted path. The fold ## assignment is random, so the selected lambdas are self-consistent but not ## reproducible across runs (or identical to MATLAB) without a fixed seed. if (! isempty (cvarg)) lam = FitInfo.Lambda; folddev = []; for rep = 1:mcreps if (isnumeric (cvarg)) cvp = cvpartition (n, "KFold", cvarg); elseif (rep == 1) cvp = cvarg; else cvp = repartition (cvp); endif for f = 1:cvp.NumTestSets tr = training (cvp, f); te = test (cvp, f); folddev(end+1,:) = lassoglm_folddev_ ( ... X(tr,:), y(tr), N(tr), off(tr), pw(tr), ... X(te,:), y(te), N(te), off(te), lam, distr, alpha, ... standardize, intercept, reltol, maxiter, linkarg); endfor endfor cvdev = mean (folddev, 1); cvse = std (folddev, 0, 1) / sqrt (rows (folddev)); FitInfo.Deviance = cvdev; FitInfo.SE = cvse; [~, imin] = min (cvdev); FitInfo.LambdaMinDeviance = lam(imin); FitInfo.IndexMinDeviance = imin; i1se = max (find (cvdev <= cvdev(imin) + cvse(imin))); FitInfo.Lambda1SE = lam(i1se); FitInfo.Index1SE = i1se; endif FitInfo.PredictorNames = prednames; FitInfo.UseCovariance = usecov; endfunction ## Family link, variance, deviance, mean-initialiser, and mean-support limits. ## The link triplet is taken from the shared private helper GETLINKFUNCTIONS; ## the variance and deviance formulas mirror those in GLMFIT. function [flink, dlink, ilink, varfun, devfun, mulims] = glmfamily (distr) switch (distr) case 'normal' [flink, dlink, ilink] = getlinkfunctions ('identity'); varfun = @(mu, N) ones (size (mu)); devfun = @(mu, y, N) (y - mu) .^ 2; mulims = [-Inf, Inf]; case 'binomial' [flink, dlink, ilink] = getlinkfunctions ('logit'); varfun = @(mu, N) mu .* (1 - mu) ./ N; devfun = @(mu, y, N) 2 * N .* (y .* log ((y + (y == 0)) ./ mu) + ... (1 - y) .* log ((1 - y + (y == 1)) ./ (1 - mu))); seps = sqrt (eps); mulims = [seps, 1 - seps]; case 'poisson' [flink, dlink, ilink] = getlinkfunctions ('log'); varfun = @(mu, N) mu; devfun = @(mu, y, N) 2 * (y .* log ((y + (y == 0)) ./ mu) - (y - mu)); mulims = [realmin, Inf]; case 'gamma' [flink, dlink, ilink] = getlinkfunctions ('reciprocal'); varfun = @(mu, N) mu .^ 2; devfun = @(mu, y, N) 2 * (- log (y ./ mu) + (y - mu) ./ mu); mulims = [realmin, Inf]; case 'inverse gaussian' [flink, dlink, ilink] = getlinkfunctions (-2); varfun = @(mu, N) mu .^ 3; devfun = @(mu, y, N) ((y - mu) ./ mu) .^ 2 ./ y; mulims = [realmin, Inf]; endswitch endfunction ## Intercept-only (null) fit by IRLS, honouring the offset. Returns the fitted ## intercept B0 (linear-predictor scale) and the fitted mean vector MU0. When ## there is no intercept the model is just the offset. function [b0, mu0] = glm_null_fit (off, y, N, o, flink, dlink, ilink, ... varfun, mulims, intercept, maxiter, reltol) if (! intercept) b0 = 0; mu0 = max (min (ilink (off), mulims(2)), mulims(1)); return; endif ## Initialise from the marginal mean of the response. b0 = flink (o' * (y ./ N)); for it = 1:maxiter eta = off + b0; mu = max (min (ilink (eta), mulims(2)), mulims(1)); deta = dlink (mu); zc = eta - off + (y ./ N - mu) .* deta; W = o ./ (deta .^ 2 .* varfun (mu, N)); b0new = sum (W .* zc) / sum (W); if (abs (b0new - b0) <= reltol * max (abs (b0), 1)) b0 = b0new; break; endif b0 = b0new; endfor mu0 = max (min (ilink (off + b0), mulims(2)), mulims(1)); endfunction ## Mean held-out deviance (per test observation, one value per LAMBDA) for a ## single cross-validation fold: fit on the training rows at the fixed LAMBDA ## path, then score the deviance on the test rows. function dev = lassoglm_folddev_ (Xtr, ytr, Ntr, offtr, wtr, Xte, yte, Nte, ... offte, lam, distr, alpha, standardize, ... intercept, reltol, maxiter, linkarg) args = {"Lambda", lam, "Alpha", alpha, "Standardize", standardize, ... "Intercept", intercept, "RelTol", reltol, "MaxIter", maxiter, ... "Weights", wtr, "Offset", offtr}; if (strcmp (distr, "binomial")) args = [args, {"Size", Ntr}]; endif if (! isempty (linkarg)) args = [args, {"Link", linkarg}]; endif [Btr, Ftr] = lassoglm (Xtr, ytr, distr, args{:}); [flink, dlink, ilink, varfun, devfun, mulims] = glmfamily (distr); if (! isempty (linkarg)) [flink, dlink, ilink] = getlinkfunctions (linkarg); endif dev = zeros (1, columns (Btr)); for k = 1:columns (Btr) eta = offte + Ftr.Intercept(k) + Xte * Btr(:,k); mu = max (min (ilink (eta), mulims(2)), mulims(1)); dev(k) = mean (devfun (mu, yte ./ Nte, Nte)); endfor endfunction function out = ifelse (cond, a, b) if (cond) out = a; else out = b; endif endfunction %!demo %! ## Logistic regression with a lasso penalty on a small binary dataset. %! X = [0.1, 1.2; 0.4, 0.7; 1.1, 0.2; 1.5, 1.9; 0.3, 0.5; 1.8, 1.1]; %! y = [0; 0; 1; 1; 0; 1]; %! [B, FitInfo] = lassoglm (X, y, 'binomial', 'Lambda', [0.01, 0.1]) %!demo %! ## Poisson (count) regression with an elastic-net penalty. %! X = [0.1, 1.2; 0.4, 0.7; 1.1, 0.2; 1.5, 1.9; 0.3, 0.5; 1.8, 1.1; 0.9, 0.3]; %! y = [1; 0; 2; 3; 1; 4; 2]; %! [B, FitInfo] = lassoglm (X, y, 'poisson', 'Lambda', [0.05, 0.2], ... %! 'Alpha', 0.6) %!demo %! ## Choosing the penalty by cross-validation. Passing a 'CV' fold count makes %! ## lassoglm cross-validate the deviance along the lambda path. It then %! ## reports the lambda with the lowest mean deviance (LambdaMinDeviance) and %! ## the largest lambda within one standard error of it (Lambda1SE) -- the %! ## sparser "one-standard-error" model that is often preferred. %! ## %! ## Here only the first two of eight predictors truly drive the response, so a %! ## good fit should keep few nonzero coefficients. The fold assignment is %! ## random; the seed below just makes the printed numbers reproducible. %! rng (42); %! X = randn (60, 8); %! beta = [1.5; -2; zeros(6, 1)]; %! y = double (rand (60, 1) < 1 ./ (1 + exp (- X * beta))); %! [B, FitInfo] = lassoglm (X, y, 'binomial', 'CV', 5); %! printf ('LambdaMinDeviance = %.4f (%d nonzero)\n', ... %! FitInfo.LambdaMinDeviance, FitInfo.DF(FitInfo.IndexMinDeviance)); %! printf ('Lambda1SE = %.4f (%d nonzero)\n', ... %! FitInfo.Lambda1SE, FitInfo.DF(FitInfo.Index1SE)); %! ## Coefficients of the one-standard-error model. %! coef_1SE = B(:, FitInfo.Index1SE) %!shared X, yb, yp, yn %! X = [ 0.12, -0.16, 1.35, 0.48; 0.39, 0.72, -0.91, -0.12; ... %! 0.55, -0.11, -0.28, -0.28; -1.32, 0.80, -1.25, 2.14; ... %! -0.24, 0.77, -2.62, 0.66; 0.05, 0.70, -1.55, 0.06; ... %! 1.05, 0.84, -0.16, 0.54; 0.55, -0.50, -0.25, 0.07; ... %! 0.31, -0.58, -0.77, -0.50; 1.09, 0.44, -0.47, -0.26; ... %! 1.08, -1.67, 2.08, 0.29; -1.52, 1.51, -2.46, -1.41; ... %! -0.55, 1.40, -0.59, -0.17; 1.35, -0.16, -0.38, -0.26]; %! yb = [1 1 1 0 0 1 1 1 1 1 1 0 0 1]'; %! yp = [1 0 1 1 0 1 1 2 1 1 1 0 0 0]'; %! yn = [0.7 -0.86 0.21 -1.38 -0.81 -1.16 0.12 1.33 0.87 0.65 4.18 -4.57 ... %! -2.76 1.51]'; ## Test results (values verified against MATLAB's lassoglm) %!test %! ## Gaussian lassoglm coincides with lasso and matches MATLAB. %! [Bg, Fg] = lassoglm (X, yn, "normal", "Lambda", [0.3, 0.1, 0.02]); %! [Bl, Fl] = lasso (X, yn, "Lambda", [0.3, 0.1, 0.02]); %! assert_equal (Bg, Bl, 1e-4); %! assert_equal (Fg.Intercept, Fl.Intercept, 1e-4); %!test %! ## Poisson regression against R2024a's own intercept and deviance. The %! ## coordinate descent converges to a slightly different point, so 1e-4. %! [B, F] = lassoglm (X, yp, "poisson", "Lambda", [0.2, 0.05, 0.01]); %! assert_equal (F.Intercept, ... %! [-0.408479371164708, -0.318644470147402, ... %! -0.297271780712625], 1e-4); %! assert_equal (F.Deviance, ... %! [7.068880870474705, 7.267211985220860, ... %! 8.458049628150873], 1e-4); %! assert_equal (F.DF, [3, 3, 1]); %!test %! ## Binomial (logistic) regression against R2024a's own values. The %! ## largest lambda is the loosest fit and agrees least closely, at 5e-4. %! [B, F] = lassoglm (X, yb, "binomial", "Lambda", [0.2, 0.1, 0.05, 0.01]); %! assert_equal (F.Intercept, ... %! [2.282419079745844, 1.388013352791727, ... %! 0.964941260795408, 0.834029178373540], 1e-3); %! assert_equal (F.Deviance, ... %! [1.146705233151688, 3.739184081182877, ... %! 6.057648047528616, 9.585820201838006], 1e-3); %! assert_equal (F.DF, [3, 3, 3, 1]); %!test %! ## Default path: DF is zero at lambda_max and lambda is ascending. %! [B, F] = lassoglm (X, yb, "binomial"); %! assert_equal (F.DF(end), 0); %! assert_equal (issorted (F.Lambda), true); %! assert_equal (all (F.Deviance(1:end-1) <= F.Deviance(2:end)), true); %!test %! ## Cross-validation adds the expected FitInfo fields and the 1-SE rule %! ## selects a lambda no smaller than the minimum-deviance lambda. %! cvp = cvpartition (14, "KFold", 5); %! [B, F] = lassoglm (X, yb, "binomial", "CV", cvp); %! assert_equal (isfield (F, "SE") && isfield (F, "LambdaMinDeviance"), true); %! assert_equal (numel (F.SE), numel (F.Lambda)); %! assert_equal (F.Lambda1SE >= F.LambdaMinDeviance, true); %! assert_equal (F.Index1SE >= F.IndexMinDeviance, true); ## Test input validation %!error lassoglm (1) %!error lassoglm ("a", [1;2]) %!error lassoglm ([1, 2; 3, 4], "a") %!error ... %! lassoglm ([1, 2; 3, 4], [1; 2; 3]) %!error ... %! lassoglm ([1, 2; 3, 4], [1; 0], 'wibble') %!error ... %! lassoglm ([1, 2; 3, 4], [1; -1], 'poisson') %!error ... %! lassoglm ([1, 2; 3, 4], [1; 0], 'gamma') %!error ... %! lassoglm ([1, 2; 3, 4], [1; 3], 'binomial') %!error ... %! lassoglm ([1, 2; 3, 4], [1; 0], 'binomial', 'Offset', [1, 2, 3]) %!error ... %! lassoglm ([1, 2; 3, 4], [1; 0], 'binomial', 'Alpha', 0) %!error ... %! lassoglm ([1, 2; 3, 4], [1; 0], 'binomial', 'Lambda', -1) %!error ... %! lassoglm ([1, 2; 3, 4], [1; 0], 'binomial', 'Lambda') %!error ... %! lassoglm ([1, 2; 3, 4], [1; 0], 'binomial', 'foo', 1) statistics-release-1.9.2/inst/Regression/logistic_regression.m000066400000000000000000000261241524624707500247040ustar00rootroot00000000000000## Copyright (C) 1995-2017 Kurt Hornik ## Copyright (C) 2022 Andrew Penn ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{intercept}, @var{slope}, @var{dev}, @var{dl}, @var{d2l}, @var{P}, @var{stats}] =} logistic_regression (@var{y}, @var{x}, @var{print}, @var{intercept}, @var{slope}) ## ## Perform ordinal logistic regression. ## ## Suppose @var{y} takes values in k ordered categories, and let ## @code{P_i (@var{x})} be the cumulative probability that @var{y} ## falls in one of the first i categories given the covariate ## @var{x}. Then ## ## @example ## [@var{intercept}, @var{slope}] = logistic_regression (@var{y}, @var{x}) ## @end example ## ## @noindent ## fits the model ## ## @example ## logit (P_i (@var{x})) = @var{x} * @var{slope} + @var{intercept}_i, ## i = 1 @dots{} k-1 ## @end example ## ## The number of ordinal categories, k, is taken to be the number ## of distinct values of @code{round (@var{y})}. If k equals 2, ## @var{y} is binary and the model is ordinary logistic regression. The ## matrix @var{x} is assumed to have full column rank. ## ## Given @var{y} only, @code{@var{intercept} = logistic_regression (@var{y})} ## fits the model with baseline logit odds only. ## ## The full form is ## ## @example ## @group ## [@var{intercept}, @var{slope}, @var{dev}, @var{dl}, @var{d2l}, @var{P}, ... ## @var{stats}] = logistic_regression (@var{y}, @var{x}, @var{print}, ... ## @var{intercept}, @var{slope}) ## @end group ## @end example ## ## @noindent ## in which all output arguments and all input arguments except @var{y} ## are optional. ## ## Setting @var{print} to 1 requests summary information about the fitted ## model to be displayed. Setting @var{print} to 2 requests information ## about convergence at each iteration. Other values request no ## information to be displayed. The input arguments @var{intercept} and ## @var{slope} give initial estimates for @var{intercept} and @var{slope}. ## ## The returned value @var{dev} holds minus twice the log-likelihood. ## ## The returned values @var{dl} and @var{d2l} are the vector of first ## and the matrix of second derivatives of the log-likelihood with ## respect to @var{intercept} and @var{slope}. ## ## @var{P} holds estimates for the conditional distribution of @var{y} ## given @var{x}. ## ## @var{stats} returns a structure that contains the following fields: ## @itemize ## @item ## "intercept": intercept coefficients ## @item ## "slope": slope coefficients ## @item ## "coeff": regression coefficients (intercepts and slops) ## @item ## "covb": estimated covariance matrix for coefficients (coeff) ## @item ## "coeffcorr": correlation matrix for coeff ## @item ## "se": standard errors of the coeff ## @item ## "z": z statistics for coeff ## @item ## "pval": p-values for coeff ## @end itemize ## @end deftypefn function [intercept, slope, dev, dl, d2l, P, stats] = logistic_regression (y, x, print, intercept, slope) ## check input y = round (y(:)); if (nargin < 2) x = zeros (length (y), 0); endif; xymissing = (isnan (y) | any (isnan (x), 2)); y(xymissing) = []; x(xymissing,:) = []; [my, ny] = size (y); [mx, nx] = size (x); if (mx != my) error ("logistic_regression: X and Y must have the same number of observations"); endif ## initial calculations tol = 1e-12; incr = 10; decr = 2; ymin = min (y); ymax = max (y); yrange = ymax - ymin; z = (y * ones (1, yrange)) == ((y * 0 + 1) * (ymin : (ymax - 1))); z1 = (y * ones (1, yrange)) == ((y * 0 + 1) * ((ymin + 1) : ymax)); z = z(:, any (z)); z1 = z1(:, any (z1)); [mz, nz] = size (z); ## starting values if (nargin < 3) print = 0; endif; if (nargin < 4) g = cumsum (sum (z))' ./ my; intercept = log (g ./ (1 - g)); endif; if (nargin < 5) slope = zeros (nx, 1); endif; tb = [intercept; slope]; ## likelihood and derivatives at starting values [g, g1, p, dev] = logistic_regression_likelihood (y, x, tb, z, z1); [dl, d2l] = logistic_regression_derivatives (x, z, z1, g, g1, p); epsilon = std (vec (d2l)) / 1000; ## maximize likelihood using Levenberg modified Newton's method iter = 0; while (abs (dl' * (d2l \ dl) / length (dl)) > tol) iter += 1; tbold = tb; devold = dev; tb = tbold - d2l \ dl; [g, g1, p, dev] = logistic_regression_likelihood (y, x, tb, z, z1); if ((dev - devold) / (dl' * (tb - tbold)) < 0) epsilon /= decr; else while ((dev - devold) / (dl' * (tb - tbold)) > 0) epsilon *= incr; if (epsilon > 1e+15) error ("logistic_regression: epsilon too large"); endif tb = tbold - (d2l - epsilon * eye (size (d2l))) \ dl; [g, g1, p, dev] = logistic_regression_likelihood (y, x, tb, z, z1); disp ('epsilon'); disp (epsilon); endwhile endif [dl, d2l] = logistic_regression_derivatives (x, z, z1, g, g1, p); if (print == 2) disp ('Iteration'); disp (iter); disp ('Deviance'); disp (dev); disp ('First derivative'); disp (dl'); disp ('Eigenvalues of second derivative'); disp (eig (d2l)'); endif endwhile ## tidy up output intercept = tb(1 : nz, 1); slope = tb((nz + 1) : (nz + nx), 1); cov = inv (-d2l); se = sqrt (diag (cov)); if (nargout > 5) ## Compute predicted probabilities (P) if (nx > 0) e = ((x * slope) * ones (1, nz)) + ((y * 0 + 1) * intercept'); else e = (y * 0 + 1) * intercept'; endif P = diff ([(y * 0), (exp (e) ./ (1 + exp (e))), (y * 0 + 1)]')'; endif if (nargout > 6) ## Create stats structure dfe = mx - nx - 1; zstat = tb ./ se; coeffcorr = cov2corr (cov); resid = y - P(:,2); stats = struct ('intercept', intercept, ... 'slope', slope, ... 'coeff', tb, ... 'cov', cov, ... 'coeffcorr', coeffcorr, ... 'se', se, ... 'z', zstat, ... 'pval', 2 * normcdf (-abs (zstat))); endif if (print >= 1) printf ("\n"); printf ("Logistic Regression Results:\n"); printf ("\n"); printf ("Number of Iterations: %d\n", iter); printf ("Deviance: %f\n", dev); printf ("Parameter Estimates:\n"); printf (" Intercept S.E.\n"); for i = 1 : nz printf (" %8.4f %8.4f\n", tb(i), se(i)); endfor if (nx > 0) printf (" Slope S.E.\n"); for i = (nz + 1) : (nz + nx) printf (" %8.4f %8.4f\n", tb(i), se(i)); endfor endif endif endfunction function [g, g1, p, dev] = logistic_regression_likelihood (y, x, slope, z, z1) ## Calculate the likelihood for the ordinal logistic regression model. e = exp ([z, x] * slope); e1 = exp ([z1, x] * slope); g = e ./ (1 + e); g1 = e1 ./ (1 + e1); g = max (y == max (y), g); g1 = min (y > min (y), g1); p = g - g1; dev = -2 * sum (log (p)); endfunction function [dl, d2l] = logistic_regression_derivatives (x, z, z1, g, g1, p) ## Calculate derivatives of the log-likelihood for ordinal logistic regression ## first derivative v = g .* (1 - g) ./ p; v1 = g1 .* (1 - g1) ./ p; dlogp = [(diag (v) * z - diag (v1) * z1), (diag (v - v1) * x)]; dl = sum (dlogp)'; ## second derivative w = v .* (1 - 2 * g); w1 = v1 .* (1 - 2 * g1); d2l = [z, x]' * diag (w) * [z, x] - [z1, x]' * diag (w1) * [z1, x] ... - dlogp' * dlogp; endfunction function R = cov2corr (vcov) ## Convert covariance matrix to correlation matrix sed = sqrt (diag (vcov)); R = vcov ./ (sed * sed'); R = (R + R') / 2; # This step ensures that the matrix is positive definite endfunction %!test %! # Output compared to following MATLAB commands %! # [B, DEV, STATS] = mnrfit(X,Y+1,'model','ordinal'); %! # P = mnrval(B,X) %! X = [1.489381332449196, 1.1534152241851305; ... %! 1.8110085304863965, 0.9449666896938425; ... %! -0.04453299665130296, 0.34278203449678646; ... %! -0.36616019468850347, 1.130254275908322; ... %! 0.15339143291005095, -0.7921044310668951; ... %! -1.6031878794469698, -1.8343471035233376; ... %! -0.14349521143198166, -0.6762996896828459; ... %! -0.4403818557740143, -0.7921044310668951; ... %! -0.7372685001160434, -0.027793137932169563; ... %! -0.11875465773681024, 0.5512305689880763]; %! Y = [1,1,1,1,1,0,0,0,0,0]'; %! [INTERCEPT, SLOPE, DEV, DL, D2L, P] = logistic_regression (Y, X, false); #%! assert_equal (DEV, 5.680728861124, 1e-05); #%! assert_equal (INTERCEPT(1), -1.10999599948243, 1e-05); #%! assert_equal (SLOPE(1), -9.12480634225699, 1e-05); #%! assert_equal (SLOPE(2), -2.18746124517476, 1e-05); #%! assert_equal (corr(P(:,1),Y), -0.786673288976468, 1e-05); %!test %! # Output compared to following MATLAB commands %! # [B, DEV, STATS] = mnrfit(X,Y+1,'model','ordinal'); %! load carbig %! X = [Acceleration Displacement Horsepower Weight]; %! miles = [1,1,1,1,1,1,1,1,1,1,NaN,NaN,NaN,NaN,NaN,1,1,NaN,1,1,2,2,1,2,2,2, ... %! 2,2,2,2,2,1,1,1,1,2,2,2,2,NaN,2,1,1,2,1,1,1,1,1,1,1,1,1,2,2,1,2, ... %! 2,3,3,3,3,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,2,2,2,2,2, ... %! 2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,2,2,2,1,2,2, ... %! 2,1,1,3,2,2,2,1,2,2,1,2,2,2,1,3,2,3,2,1,1,1,1,1,1,1,1,3,2,2,3,3, ... %! 2,2,2,2,2,3,2,1,1,1,1,1,1,1,1,1,1,1,2,2,1,3,2,2,2,2,2,2,1,3,2,2, ... %! 2,2,2,3,2,2,2,2,2,1,1,1,1,2,2,2,2,3,2,3,3,2,1,1,1,3,3,2,2,2,1,2, ... %! 2,1,1,1,1,1,3,3,3,2,3,1,1,1,1,1,2,2,1,1,1,1,1,3,2,2,2,3,3,3,3,2, ... %! 2,2,4,3,3,4,3,2,2,2,2,2,2,2,2,2,2,2,1,1,2,1,1,1,3,2,2,3,2,2,2,2, ... %! 2,1,2,1,3,3,2,2,2,2,2,1,1,1,1,1,1,2,1,3,3,3,2,2,2,2,2,3,3,3,3,2, ... %! 2,2,3,4,3,3,3,2,2,2,2,3,3,3,3,3,4,2,4,4,4,3,3,4,4,3,3,3,2,3,2,3, ... %! 2,2,2,2,3,4,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,NaN,3,2,2,2,2,2,1,2, ... %! 2,3,3,3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,2,3,2,2,3,3,2,2,4,3,2,3]'; %! [INTERCEPT, SLOPE, DEV, DL, D2L, P] = logistic_regression (miles, X, false); %! assert_equal (DEV, 433.197174495549, 1e-05); %! assert_equal (INTERCEPT(1), -16.6895155618903, 1e-05); %! assert_equal (INTERCEPT(2), -11.7207818178493, 1e-05); %! assert_equal (INTERCEPT(3), -8.0605768506075, 1e-05); %! assert_equal (SLOPE(1), 0.104762463756714, 1e-05); %! assert_equal (SLOPE(2), 0.0103357623191891, 1e-05); %! assert_equal (SLOPE(3), 0.0645199313242276, 1e-05); %! assert_equal (SLOPE(4), 0.00166377028388103, 1e-05); statistics-release-1.9.2/inst/Regression/mnrfit.m000066400000000000000000000613601524624707500221270ustar00rootroot00000000000000## Copyright (C) 2024 Andrew C Penn ## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{B} =} mnrfit (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{B} =} mnrfit (@var{X}, @var{Y}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{B}, @var{dev}] =} mnrfit (@dots{}) ## @deftypefnx {statistics} {[@var{B}, @var{dev}, @var{stats}] =} mnrfit (@dots{}) ## ## Fit a multinomial logistic regression model. ## ## Nominal models are fitted with a baseline-category multinomial logit, using ## the last category of @var{Y} as the reference. Ordinal models are fitted ## with a cumulative link model and hierarchical models with a sequential ## (continuation-ratio) link model, both honouring the @qcode{'link'} option ## below. Nominal models always use the logit link. ## ## @code{@var{B} = mnrfit (@var{X}, @var{Y})} returns a matrix, @var{B}, of ## coefficient estimates for a multinomial logistic regression of the nominal ## responses in @var{Y} on the predictors in @var{X}. @var{X} is an @math{N*P} ## numeric matrix the observations on predictor variables, where @math{N} ## corresponds to the number of observations and @math{P} corresponds to ## predictor variables. @var{Y} contains the response category labels and it ## either be an @math{N*P} categorical or numerical matrix (containing only 1s ## and 0s) or an @math{N*1} numeric vector with positive integer values, a cell ## array of character vectors and a logical vector. @var{Y} can also be defined ## as a character matrix with each row corresponding to an observation of ## @var{X}. ## ## @code{@var{B} = mnrfit (@var{X}, @var{Y}, @var{name}, @var{value})} returns a ## matrix, @var{B}, of coefficient estimates for a multinomial model fit with ## additional parameters specified @qcode{Name-Value} pair arguments. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'model'} @tab The type of model to fit: @qcode{'nominal'} ## (default) for a baseline-category model, @qcode{'ordinal'} for a cumulative ## model, or @qcode{'hierarchical'} for a sequential (continuation-ratio) model. ## ## @item @qcode{'link'} @tab The link function for ordinal and hierarchical ## models: @qcode{'logit'} (default), @qcode{'probit'}, @qcode{'comploglog'}, or ## @qcode{'loglog'}. Nominal models always use the logit link. ## ## @item @qcode{'estdisp'} @tab @qcode{'on'} to estimate a dispersion ## parameter, scaling the coefficient standard errors by it and testing the ## coefficients against the @math{t} distribution, or @qcode{'off'} (default) ## for the theoretical dispersion of @math{1}. ## ## @item @qcode{'display'} @tab A flag to enable/disable displaying ## information about the fitted model. Default is @qcode{'off'}. ## @end multitable ## ## @code{[@var{B}, @var{dev}, @var{stats}] = mnrfit (@dots{})} also returns the ## deviance of the fit, @var{dev}, and a structure @var{stats} with the fitted ## coefficients @qcode{'beta'} (same as @var{B}), their standard errors ## @qcode{'se'}, covariance matrix @qcode{'covb'}, correlation matrix ## @qcode{'coeffcorr'}, error degrees of freedom @qcode{'dfe'}, the coefficient ## @math{t} statistics @qcode{'t'} and @math{p}-values @qcode{'p'}, the ## dispersion parameters @qcode{'s'}, @qcode{'sfit'}, and @qcode{'estdisp'}, and ## the raw, Pearson, and deviance residuals @qcode{'resid'}, @qcode{'residp'}, ## and @qcode{'residd'}. ## ## @seealso{mnrval, logistic_regression} ## @end deftypefn function [B, DEV, STATS] = mnrfit (X, Y, varargin) ## Check input arguments X and Y if (nargin < 2) error ("mnrfit: too few input arguments."); endif if (! isnumeric (X)) error ("mnrfit: Predictors must be numeric.") endif if (isscalar (X) || (ndims (X) > 2)) error ("mnrfit: Predictors must be a vector or a 2D matrix.") endif if (isscalar (Y) || (ndims (Y) > 2)) error ("mnrfit: Response must be a vector or a 2D matrix.") endif [N, P] = size (X); [n, K] = size (Y); if (N == 1) ## if X is a row vector, make it a column vector X = X(:); N = P; P = 1; endif if (n != N) error ("mnrfit: Y must have the same number of rows as X.") endif if (! (isnumeric (Y) || islogical (Y) || ischar (Y) || iscellstr (Y))) error (strcat ("mnrfit: Response labels must be a character array,", ... " a cell vector of strings, \nor a vector or", ... " matrix of doubles, singles or logical values.")); endif ## Check supplied parameters if (mod (numel (varargin), 2) != 0) error ("mnrfit: optional arguments must be in pairs.") endif MODELTYPE = 'nominal'; DISPLAY = 'off'; LINK = 'logit'; ESTDISP = 'off'; while (numel (varargin) > 0) name = varargin{1}; value = varargin{2}; switch (lower (name)) case 'model' MODELTYPE = value; case 'display' DISPLAY = value; case 'link' LINK = value; case 'estdisp' ESTDISP = value; otherwise warning (sprintf ("mnrfit: parameter %s will be ignored", name)); endswitch varargin(1:2) = []; endwhile LINK = lower (LINK); if (! any (strcmp (LINK, {'logit', 'probit', 'comploglog', 'loglog'}))) error ("mnrfit: unrecognised 'link' value."); endif if (! (ischar (ESTDISP) && any (strcmpi (ESTDISP, {'on', 'off'})))) error ("mnrfit: 'estdisp' must be 'on' or 'off'."); endif estdisp = strcmpi (ESTDISP, 'on'); ## Evaluate display input argument switch (lower (DISPLAY)) case 'on' dispopt = true; case 'off' dispopt = false; endswitch ## Categorize Y if it is a cellstring array if (iscellstr (Y)) if (K > 1) error ("mnrfit: Y must be a column vector when given as cellstr."); endif ## Get groups in Y [YN, ~, UY] = grp2idx (Y); # this will also catch "" as missing values ## Remove missing values from X and Y RowsUsed = ! logical (sum (isnan ([X, YN]), 2)); Y = Y(RowsUsed); X = X(RowsUsed, :); ## Renew groups in Y [YN, ~, UY] = grp2idx (Y); # in case a category is removed due to NaNs in X n = numel (UY); endif ## Categorize Y if it is a character array if (ischar (Y)) ## Get groups in Y [YN, ~, UY] = grp2idx (Y); # this will also catch "" as missing values ## Remove missing values from X and Y RowsUsed = ! logical (sum (isnan ([X, YN]), 2)); Y = Y(RowsUsed); X = X(RowsUsed, :); ## Renew groups in Y [YN, ~, UY] = grp2idx (Y); # in case a category is removed due to NaNs in X n = numel (UY); endif if (K > 1) ## So far, if K > 1, Y must be a matrix of logical, singles or doubles if (! all (all (Y == 0 | Y == 1))) error ("mnrfit: Y must contain only 1 and 0 when given as a 2D matrix."); endif ## Convert Y to a vector of positive integer categories Y = sum (bsxfun (@times, (1:K), Y), 2); endif ## Categorize Y in all other cases if (! iscellstr (Y)) RowsUsed = ! logical (sum (isnan ([X, Y]), 2)); Y = Y(RowsUsed); X = X(RowsUsed, :); [UY, ~, YN] = unique (Y); ## find unique categories in the response n = numel (UY); ## number of unique response categories endif if (isnumeric (Y)) if (! (all (Y > 0) && all (fix (Y) == Y))) error ("mnrfit: Y must contain positive integer category numbers.") endif endif ## Fit the requested model type switch (lower (MODELTYPE)) case 'nominal' if (! strcmp (LINK, 'logit')) error ("mnrfit: nominal models support only the logit link."); endif [B, DEV, STATS] = mnrfit_nominal_ (X, YN, n, dispopt); case 'ordinal' if (strcmp (LINK, 'logit')) [INTERCEPT, SLOPE, DEV, ~, ~, ~, S] = logistic_regression (YN - 1, X, ... dispopt); B = cat (1, INTERCEPT, SLOPE); se = S.se; STATS = struct ('beta', B, ... 'dfe', rows (X) * (n - 1) - numel (B), ... 's', 1, ... 'sfit', 1, ... 'estdisp', false, ... 'coeffcorr', S.coeffcorr, ... 'covb', S.cov, ... 'se', se, ... 't', B ./ se, ... 'p', 2 * normcdf (- abs (B ./ se)), ... 'resid', [], ... 'residp', [], ... 'residd', []); else [B, DEV, STATS] = mnrfit_ordinal_ (X, YN, n, LINK); endif case 'hierarchical' [B, DEV, STATS] = mnrfit_hierarchical_ (X, YN, n, LINK); otherwise error ("mnrfit: model type not recognised."); endswitch ## Residuals and dispersion, computed from the fitted category probabilities ## (common to every model type; individual responses have sample size 1) pihat = mnrval (B, X, 'model', lower (MODELTYPE), 'link', LINK); [STATS.resid, STATS.residp, STATS.residd] = mnrfit_residuals_ (pihat, YN, n); STATS.estdisp = estdisp; if (! isempty (STATS.dfe) && STATS.dfe > 0) ## Standard Pearson dispersion estimate STATS.sfit = sqrt (sum (STATS.resid(:) .^ 2 ./ pihat(:)) / STATS.dfe); if (estdisp) ## Scale the coefficient covariance and standard errors by the estimated ## dispersion and test the coefficients against the t distribution. STATS.s = STATS.sfit; STATS.covb = STATS.covb * STATS.s ^ 2; STATS.se = STATS.se * STATS.s; STATS.t = STATS.beta ./ STATS.se; STATS.p = 2 * tcdf (- abs (STATS.t), STATS.dfe); endif endif endfunction ## Fit a baseline-category multinomial logit model by Newton-Raphson. The last ## category is the reference; B is a (P+1)-by-(K-1) matrix whose column j holds ## the intercept and slopes contrasting category j against the reference. function [B, dev, stats] = mnrfit_nominal_ (X, YN, k, dispopt) [nobs, p] = size (X); Z = [ones(nobs, 1), X]; ## design with intercept, nobs-by-q q = p + 1; ncat = k - 1; ## non-reference categories (1..k-1) ## Response indicator matrix for categories 1..k-1 (reference = category k) Yind = double (YN(:) == (1:ncat)); ## nobs-by-ncat ## Newton-Raphson on the multinomial logit log-likelihood beta = zeros (q, ncat); maxiter = 100; tol = 1e-8; converged = false; for iter = 1:maxiter [P, ~] = mnrfit_softmax_ (Z, beta, nobs); grad = Z' * (Yind - P); ## q-by-ncat ## Hessian of the log-likelihood (block form, negative definite) H = zeros (q * ncat); for a = 1:ncat for b = 1:ncat if (a == b) w = P(:,a) .* (1 - P(:,a)); else w = - P(:,a) .* P(:,b); endif H((a-1)*q+(1:q), (b-1)*q+(1:q)) = - (Z' * (Z .* w)); endfor endfor ## A vanishing Hessian condition signals (quasi-)separable data with no ## finite maximum likelihood estimate; stop and keep the current estimate. if (rcond (H) < eps) break; endif step = - (H \ grad(:)); ## Newton step (maximise the likelihood) if (! all (isfinite (step))) break; endif beta(:) = beta(:) + step; if (max (abs (step)) < tol) converged = true; break; endif endfor if (! converged) warning ("mnrfit: iteration limit reached; results may be unreliable."); endif B = beta; ## Deviance = -2 * log-likelihood (saturated log-likelihood is 0 for ## individual responses) [~, Pall] = mnrfit_softmax_ (Z, beta, nobs); idx = sub2ind ([nobs, k], (1:nobs)', YN(:)); dev = -2 * sum (log (Pall(idx))); ## Coefficient covariance from the inverse negative Hessian at the MLE covb = inv (-H); se_v = sqrt (diag (covb)); se = reshape (se_v, q, ncat); coeffcorr = covb ./ (se_v * se_v'); tstat = B ./ se; pval = 2 * normcdf (- abs (tstat)); stats = struct ('beta', B, ... 'dfe', nobs * ncat - numel (B), ... 's', 1, ... 'sfit', 1, ... 'estdisp', false, ... 'coeffcorr', coeffcorr, ... 'covb', covb, ... 'se', se, ... 't', tstat, ... 'p', pval, ... 'resid', [], ... 'residp', [], ... 'residd', []); endfunction ## Fit a hierarchical (sequential / continuation-ratio) model. Category j is ## contrasted against the later categories using only the observations with ## Y >= j, giving K-1 independent binary logit fits. B is a (P+1)-by-(K-1) ## matrix whose column j holds the intercept and slopes of the conditional ## logit for category j. function [B, dev, stats] = mnrfit_hierarchical_ (X, YN, k, link) YN = YN(:); p = columns (X); q = p + 1; ncat = k - 1; B = zeros (q, ncat); se = zeros (q, ncat); covb = zeros (q * ncat); dev = 0; usedobs = 0; for j = 1:ncat mask = (YN >= j); ## Each stage is a binary GLM of category j against the later categories ysucc = double (YN(mask) == j); [Bj, devj, sj] = glmfit (X(mask, :), ysucc, 'binomial', 'link', link); B(:, j) = Bj; se(:, j) = sj.se; covb((j-1)*q+(1:q), (j-1)*q+(1:q)) = sj.covb; dev += devj; usedobs += sum (mask); endfor se_v = se(:); coeffcorr = covb ./ (se_v * se_v'); tstat = B ./ se; pval = 2 * normcdf (- abs (tstat)); stats = struct ('beta', B, ... 'dfe', usedobs - numel (B), ... 's', 1, ... 'sfit', 1, ... 'estdisp', false, ... 'coeffcorr', coeffcorr, ... 'covb', covb, ... 'se', se, ... 't', tstat, ... 'p', pval, ... 'resid', [], ... 'residp', [], ... 'residd', []); endfunction ## Numerically stable baseline-category softmax. Returns the nobs-by-(K-1) ## matrix P of non-reference category probabilities and, optionally, the full ## nobs-by-K matrix Pall including the reference category in the last column. function [P, Pall] = mnrfit_softmax_ (Z, beta, nobs) eta = Z * beta; ## nobs-by-(k-1) m = max ([eta, zeros(nobs, 1)], [], 2); ## row max including baseline 0 ee = exp (eta - m); base = exp (-m); ## reference category, unnormalised den = base + sum (ee, 2); P = ee ./ den; if (nargout > 1) Pall = [P, base ./ den]; endif endfunction ## Raw, Pearson, and deviance residuals for individual multinomial responses. ## PIHAT is the nobs-by-K matrix of fitted category probabilities and YN holds ## the observed category indices. resid and residp are nobs-by-K; residd is ## nobs-by-1 (the per-observation deviance contribution). function [resid, residp, residd] = mnrfit_residuals_ (pihat, YN, k) nobs = rows (pihat); yind = double (YN(:) == (1:k)); ## observed indicators, nobs-by-K resid = yind - pihat; residp = resid ./ sqrt (pihat .* (1 - pihat)); residp(isnan (residp)) = 0; ## 0/0 at a saturated probability -> 0 pobs = pihat (sub2ind ([nobs, k], (1:nobs)', YN(:))); residd = -2 * log (max (pobs, realmin)); endfunction ## Fit a cumulative-link (proportional-odds) ordinal model for a non-logit link ## by Newton-Raphson with numerical derivatives. B is a (K-1+P)-by-1 vector of ## K-1 thresholds followed by the P shared slopes, matching the logit path. function [B, dev, stats] = mnrfit_ordinal_ (X, YN, k, link) nobs = rows (X); p = columns (X); ncat = k - 1; npar = ncat + p; ilink = mnrfit_ilink_ (link); ## Initial thresholds from cumulative category frequencies, slopes at zero counts = accumarray (YN(:), 1, [k, 1]); cumfreq = cumsum (counts(1:ncat)) / nobs; cumfreq = min (max (cumfreq, 1e-3), 1 - 1e-3); params = [mnrfit_flink_(link, cumfreq)(:); zeros(p, 1)]; ll_fn = @(pp) mnrfit_ord_ll_ (pp, X, YN, k, ilink); maxiter = 200; tol = 1e-9; converged = false; for iter = 1:maxiter [g, H] = mnrfit_numderiv_ (ll_fn, params); if (rcond (H) < eps || ! all (isfinite (g(:)))) break; endif step = - (H \ g); if (! all (isfinite (step))) break; endif ## Damp large steps for the nonlinear links if (max (abs (step)) > 5) step *= 5 / max (abs (step)); endif params += step; if (max (abs (step)) < tol) converged = true; break; endif endfor if (! converged) warning ("mnrfit: iteration limit reached; results may be unreliable."); endif B = params; dev = -2 * ll_fn (params); covb = inv (-H); se = sqrt (diag (covb)); stats = struct ('beta', B, ... 'dfe', nobs * ncat - npar, ... 's', 1, ... 'sfit', 1, ... 'estdisp', false, ... 'coeffcorr', covb ./ (se * se'), ... 'covb', covb, ... 'se', se, ... 't', B ./ se, ... 'p', 2 * normcdf (- abs (B ./ se)), ... 'resid', [], ... 'residp', [], ... 'residd', []); endfunction ## Log-likelihood of the cumulative-link ordinal model at parameter vector PP ## ([thresholds; slopes]); ILINK is the inverse link. function ll = mnrfit_ord_ll_ (pp, X, YN, k, ilink) ncat = k - 1; theta = pp(1:ncat)(:).'; beta = pp(ncat+1:end); gamma = ilink (X * beta + theta); ## cumulative probabilities Pall = [gamma(:,1), diff(gamma, 1, 2), 1 - gamma(:,end)]; nobs = rows (X); pobs = max (Pall(sub2ind ([nobs, k], (1:nobs)', YN(:))), realmin); ll = sum (log (pobs)); endfunction ## Central-difference gradient and Hessian of a scalar function FN at X. function [g, H] = mnrfit_numderiv_ (fn, x) n = numel (x); h = 1e-5 * max (abs (x), 1); g = zeros (n, 1); for i = 1:n xp = x; xp(i) += h(i); xm = x; xm(i) -= h(i); g(i) = (fn (xp) - fn (xm)) / (2 * h(i)); endfor H = zeros (n); for i = 1:n for j = i:n xpp = x; xpp(i) += h(i); xpp(j) += h(j); xpm = x; xpm(i) += h(i); xpm(j) -= h(j); xmp = x; xmp(i) -= h(i); xmp(j) += h(j); xmm = x; xmm(i) -= h(i); xmm(j) -= h(j); H(i,j) = (fn (xpp) - fn (xpm) - fn (xmp) + fn (xmm)) / (4 * h(i) * h(j)); H(j,i) = H(i,j); endfor endfor endfunction ## Inverse link (mean function) handle; matches the links used by mnrval. function f = mnrfit_ilink_ (link) switch (link) case 'logit' f = @(e) 1 ./ (1 + exp (-e)); case 'probit' f = @(e) normcdf (e); case 'comploglog' f = @(e) 1 - exp (-exp (e)); case 'loglog' f = @(e) exp (-exp (e)); endswitch endfunction ## Forward link, used only to seed the initial thresholds. function e = mnrfit_flink_ (link, g) switch (link) case 'logit' e = log (g ./ (1 - g)); case 'probit' e = norminv (g); case 'comploglog' e = log (- log (1 - g)); case 'loglog' e = log (- log (g)); endswitch endfunction ## Test nominal multinomial logit fitting %!test # nominal MLE reproduces the observed category totals %! X = [-2; -1; 0; 1; 2; -2; -1; 0; 1; 2; -1.5; 1.5]; %! Y = [1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3]; %! [B, dev, stats] = mnrfit (X, Y, 'model', 'nominal'); %! assert_equal (size (B), [2, 2]); %! P = mnrval (B, X); %! assert_equal (sum (P, 2), ones (12, 1), 1e-10); %! assert_equal (sum (P, 1), [sum(Y == 1), sum(Y == 2), sum(Y == 3)], 1e-6); %!test # binary nominal agrees with the ordinal cumulative-logit fit %! X = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]; %! Y = [1; 1; 1; 1; 2; 1; 2; 2; 2; 2]; %! assert_equal (mnrfit (X, Y, 'model', 'nominal'), ... %! mnrfit (X, Y, 'model', 'ordinal'), 1e-5); ## Test hierarchical (sequential) fitting %!test # hierarchical fit yields valid probabilities through mnrval %! X = [-2; -1; 0; 1; 2; -2; -1; 0; 1; 2; -1.5; 1.5]; %! Y = [1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3]; %! [B, dev, stats] = mnrfit (X, Y, 'model', 'hierarchical'); %! assert_equal (size (B), [2, 2]); %! P = mnrval (B, X, 'model', 'hierarchical'); %! assert_equal (sum (P, 2), ones (12, 1), 1e-10); %! assert_equal (all (P(:) >= 0 & P(:) <= 1), true); %!test # first hierarchical stage is the binary logit of category 1 vs. rest %! X = [-2; -1; 0; 1; 2; -2; -1; 0; 1; 2; -1.5; 1.5]; %! Y = [1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3]; %! Bh = mnrfit (X, Y, 'model', 'hierarchical'); %! Bb = mnrfit (X, 2 - double (Y == 1), 'model', 'nominal'); %! assert_equal (Bh(:,1), Bb, 1e-8); ## Test link functions for ordinal and hierarchical models %!test # non-logit ordinal fits round-trip through mnrval with valid probs %! X = [-2; -1; 0; 1; 2; -1.5; 0.5; 1.2; -0.7; 0.3; -0.4; 0.8]; %! Y = [1; 1; 2; 2; 3; 1; 2; 3; 3; 2; 1; 3]; %! for lk = {'probit', 'comploglog', 'loglog'} %! B = mnrfit (X, Y, 'model', 'ordinal', 'link', lk{1}); %! P = mnrval (B, X, 'model', 'ordinal', 'link', lk{1}); %! assert_equal (sum (P, 2), ones (12, 1), 1e-9); %! assert_equal (all (P(:) >= -1e-12 & P(:) <= 1 + 1e-12), true); %! endfor %!test # non-logit hierarchical fit runs and round-trips through mnrval %! X = [-2; -1; 0; 1; 2; -1.5; 0.5; 1.2; -0.7; 0.3; -0.4; 0.8]; %! Y = [1; 1; 2; 2; 3; 1; 2; 3; 3; 2; 1; 3]; %! B = mnrfit (X, Y, 'model', 'hierarchical', 'link', 'probit'); %! P = mnrval (B, X, 'model', 'hierarchical', 'link', 'probit'); %! assert_equal (sum (P, 2), ones (12, 1), 1e-9); ## Test residual outputs %!test # residuals have the right shape and are internally consistent %! X = [-2; -1; 0; 1; 2; -2; -1; 0; 1; 2; -1.5; 1.5]; %! Y = [1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3]; %! [B, dev, stats] = mnrfit (X, Y, 'model', 'nominal'); %! assert_equal (size (stats.resid), [12, 3]); %! assert_equal (size (stats.residp), [12, 3]); %! assert_equal (size (stats.residd), [12, 1]); %! assert_equal (sum (stats.resid, 2), zeros (12, 1), 1e-10); %! assert_equal (sum (stats.residd), dev, 1e-8); %!test # dispersion estimate sfit = sqrt (Pearson X2 / dfe) matches MATLAB %! X = [-2; -1; 0; 1; 2; -2; -1; 0; 1; 2; -1.5; 1.5]; %! Y = [1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3]; %! [~, ~, sn] = mnrfit (X, Y, 'model', 'nominal'); %! assert_equal (sn.sfit, 1.0954, 1e-3); %! [~, ~, so] = mnrfit (X, Y, 'model', 'ordinal'); %! assert_equal (so.sfit, 1.0691, 1e-3); ## Test the EstDisp option %!test # EstDisp on scales se/covb by the dispersion and uses t-based p-values %! X = [-2; -1; 0; 1; 2; -2; -1; 0; 1; 2; -1.5; 1.5]; %! Y = [1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3]; %! [~, ~, s0] = mnrfit (X, Y, 'model', 'nominal'); %! [~, ~, s1] = mnrfit (X, Y, 'model', 'nominal', 'estdisp', 'on'); %! assert_equal (s0.s, 1); %! assert_equal (s1.s, s1.sfit); %! assert_equal (s1.se, s0.se * s1.sfit, 1e-12); %! assert_equal (s1.covb, s0.covb * s1.sfit ^ 2, 1e-10); %! assert_equal (s1.p, 2 * tcdf (- abs (s1.t), s1.dfe), 1e-12); ## Test input validation %!error mnrfit (ones (50,1)) %!error ... %! mnrfit ({1 ;2 ;3 ;4 ;5}, ones (5,1)) %!error ... %! mnrfit (ones (50, 4, 2), ones (50, 1)) %!error ... %! mnrfit (ones (50, 4), ones (50, 1, 3)) %!error ... %! mnrfit (ones (50, 4), ones (45,1)) %!error ... %! mnrfit (ones (5, 4), {1 ;2 ;3 ;4 ;5}) %!error ... %! mnrfit (ones (5, 4), ones (5, 1), 'model') %!error ... %! mnrfit (ones (5, 4), {'q','q';'w','w';'q','q';'w','w';'q','q'}) %!error ... %! mnrfit (ones (5, 4), [1, 2; 1, 2; 1, 2; 1, 2; 1, 2]) %!error ... %! mnrfit (ones (5, 4), [1; -1; 1; 2; 1]) %!error ... %! mnrfit (ones (5, 4), [1; 2; 3; 2; 1], 'model', 'whatever') %!error ... %! mnrfit (ones (5, 4), [1; 2; 1; 2; 1], 'link', 'cauchit') %!error ... %! mnrfit (ones (5, 4), [1; 2; 1; 2; 1], 'model', 'nominal', 'link', 'probit') %!error ... %! mnrfit (ones (5, 4), [1; 2; 1; 2; 1], 'estdisp', 'maybe') statistics-release-1.9.2/inst/Regression/mnrval.m000066400000000000000000000406521524624707500221300ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{pihat} =} mnrval (@var{B}, @var{X}) ## @deftypefnx {statistics} {[@var{pihat}, @var{dlo}, @var{dhi}] =} mnrval (@var{B}, @var{X}, @var{stats}) ## @deftypefnx {statistics} {@var{yhat} =} mnrval (@var{B}, @var{X}, @var{ssize}) ## @deftypefnx {statistics} {[@var{yhat}, @var{dlo}, @var{dhi}] =} mnrval (@var{B}, @var{X}, @var{ssize}, @var{stats}) ## @deftypefnx {statistics} {[@dots{}] =} mnrval (@dots{}, @var{name}, @var{value}) ## ## Predict values for a multinomial logistic regression model. ## ## @code{@var{pihat} = mnrval (@var{B}, @var{X})} returns the predicted ## category probabilities @var{pihat} of a multinomial logistic regression with ## coefficients @var{B}, evaluated at the predictor values in @var{X}. @var{X} ## is an @math{N*P} numeric matrix of @math{N} observations on @math{P} ## predictors. @var{pihat} is an @math{N*K} matrix, where @math{K} is the number ## of response categories and each row sums to one. @var{B} is the coefficient ## matrix returned by @code{mnrfit} (see below for its shape under each model). ## ## @code{mnrval} is the prediction companion of @code{mnrfit}. Unlike the ## current @code{mnrfit}, which only fits ordinal and two-category nominal ## models, @code{mnrval} evaluates all three model types, so a coefficient ## matrix @var{B} obtained elsewhere (e.g.@: MATLAB) can be used for prediction. ## ## @code{@var{yhat} = mnrval (@var{B}, @var{X}, @var{ssize})} returns predicted ## category counts instead of probabilities, for the sample sizes in @var{ssize} ## (a scalar or an @math{N*1} vector). ## ## @code{[@var{pihat}, @var{dlo}, @var{dhi}] = mnrval (@var{B}, @var{X}, ## @var{stats})} also returns @math{95%} confidence bounds on the predictions. ## @var{stats} is the structure returned by @code{mnrfit}; its @qcode{'covb'} ## field (the coefficient covariance matrix) is required. The confidence ## interval for each prediction is @code{[@var{pihat} - @var{dlo}, @var{pihat} + ## @var{dhi}]}. The bounds are nonsimultaneous and apply to the fitted values, ## not to new observations. ## ## The following @qcode{Name-Value} pairs control the model: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'model'} @tab The model type: @qcode{'nominal'} (default), ## @qcode{'ordinal'}, or @qcode{'hierarchical'}. ## ## @item @qcode{'interactions'} @tab @qcode{'on'} to include category-specific ## coefficients, or @qcode{'off'} for a common set of coefficients with ## category-specific intercepts only. Default is @qcode{'on'} for nominal and ## hierarchical models and @qcode{'off'} for ordinal models. With ## @qcode{'interactions','on'}, @var{B} is a @math{(P+1)*(K-1)} matrix. With ## @qcode{'interactions','off'}, @var{B} is a @math{(K-1+P)*1} vector holding the ## @math{K-1} intercepts followed by the @math{P} common slopes. ## ## @item @qcode{'link'} @tab The link function for ordinal and hierarchical ## models: @qcode{'logit'} (default), @qcode{'probit'}, @qcode{'comploglog'}, or ## @qcode{'loglog'}. Nominal models always use the multinomial logit link. ## ## @item @qcode{'type'} @tab The kind of probability returned: ## @qcode{'category'} (default, @math{N*K} category probabilities), ## @qcode{'cumulative'} (@math{N*(K-1)} cumulative probabilities of the first ## @math{K-1} categories), or @qcode{'conditional'} (@math{N*(K-1)} conditional ## probabilities of each category given membership in that or a later category). ## ## @item @qcode{'confidence'} @tab The confidence level for @var{dlo} and ## @var{dhi}, a scalar in the range @math{(0,1)}. Default is @math{0.95}. ## @end multitable ## ## @seealso{mnrfit, glmval, logistic_regression} ## @end deftypefn function [pihat, dlo, dhi] = mnrval (B, X, varargin) if (nargin < 2) error ("mnrval: too few input arguments."); endif if (! isnumeric (B) || ! isreal (B)) error ("mnrval: B must be a real numeric matrix."); endif if (! isnumeric (X) || ! isreal (X) || ndims (X) > 2) error ("mnrval: X must be a real numeric 2D matrix."); endif ## Peel off the optional positional arguments (SSIZE and/or STATS), which ## precede any Name-Value pairs. SSIZE is numeric, STATS is a struct. ssize = []; stats = []; args = varargin; while (numel (args) > 0 && ! ischar (args{1})) a = args{1}; if (isstruct (a)) stats = a; elseif (isnumeric (a)) ssize = a; else error ("mnrval: invalid optional positional argument."); endif args(1) = []; endwhile ## Parse Name-Value pairs if (mod (numel (args), 2) != 0) error ("mnrval: optional arguments must be in Name-Value pairs."); endif model = 'nominal'; interactions = []; ## resolved to a default per model below link = 'logit'; type = 'category'; conf = 0.95; while (numel (args) > 0) name = args{1}; value = args{2}; switch (lower (name)) case 'model' model = value; case 'interactions' interactions = value; case 'link' link = value; case 'type' type = value; case 'confidence' conf = value; otherwise error ("mnrval: unknown parameter name '%s'.", name); endswitch args(1:2) = []; endwhile ## Validate and normalise the options model = lower (model); if (! any (strcmp (model, {'nominal', 'ordinal', 'hierarchical'}))) error ("mnrval: unrecognised 'model' value."); endif if (isempty (interactions)) if (strcmp (model, 'ordinal')) interactions = 'off'; else interactions = 'on'; endif endif interactions = lower (interactions); if (! any (strcmp (interactions, {'on', 'off'}))) error ("mnrval: 'interactions' must be 'on' or 'off'."); endif link = lower (link); if (! any (strcmp (link, {'logit', 'probit', 'comploglog', 'loglog'}))) error ("mnrval: unrecognised 'link' value."); endif if (strcmp (model, 'nominal') && ! strcmp (link, 'logit')) error ("mnrval: nominal models use the multinomial logit link only."); endif type = lower (type); if (! any (strcmp (type, {'category', 'cumulative', 'conditional'}))) error ("mnrval: unrecognised 'type' value."); endif if (! (isscalar (conf) && isreal (conf) && conf > 0 && conf < 1)) error ("mnrval: 'confidence' must be a scalar in the range (0,1)."); endif [n, P] = size (X); ## Determine K-1 and validate the shape of B against X and 'interactions' if (strcmp (interactions, 'on')) if (rows (B) != P + 1) error (strcat ("mnrval: with 'interactions','on', B must have one", ... " more row than the number of columns of X.")); endif Km1 = columns (B); else B = B(:); if (numel (B) <= P) error (strcat ("mnrval: with 'interactions','off', B must have more", ... " elements than the number of columns of X.")); endif Km1 = numel (B) - P; endif if (Km1 < 1) error ("mnrval: B implies fewer than two response categories."); endif ## Validate SSIZE and STATS if supplied if (! isempty (ssize)) if (! (isscalar (ssize) || (isvector (ssize) && numel (ssize) == n))) error ("mnrval: SSIZE must be a scalar or an N-element vector."); endif ssize = ssize(:); endif if (nargout > 1 && isempty (stats)) error ("mnrval: STATS is required to compute confidence bounds."); endif ## Point predictions pihat = mnr_predict (B, X, model, interactions, link, type, ssize, P); ## Confidence bounds by the delta method on the coefficient covariance if (nargout > 1) if (! isfield (stats, 'covb')) error ("mnrval: STATS must contain a 'covb' field."); endif covb = stats.covb; b0 = B(:); np = numel (b0); if (! isequal (size (covb), [np, np])) error ("mnrval: size of STATS.covb does not match the number of coefficients."); endif ncol = columns (pihat); ## Central-difference Jacobian of the returned quantity with respect to B J = zeros (n * ncol, np); h = 1e-6 * max (abs (b0), 1); for j = 1:np bp = b0; bp(j) += h(j); bm = b0; bm(j) -= h(j); fp = mnr_predict (reshape (bp, size (B)), X, model, interactions, ... link, type, ssize, P); fm = mnr_predict (reshape (bm, size (B)), X, model, interactions, ... link, type, ssize, P); J(:,j) = (fp(:) - fm(:)) / (2 * h(j)); endfor v = sum ((J * covb) .* J, 2); ## variance of each output element se = reshape (sqrt (max (v, 0)), n, ncol); z = norminv (1 - (1 - conf) / 2); dlo = z * se; dhi = z * se; endif endfunction ## Core prediction: returns the requested quantity (category/cumulative/ ## conditional probabilities, optionally scaled to counts by SSIZE). function out = mnr_predict (B, X, model, interactions, link, type, ssize, P) n = rows (X); ## Linear predictors ETA (n-by-(K-1)) if (strcmp (interactions, 'on')) Km1 = columns (B); eta = [ones(n, 1), X] * B; else Km1 = numel (B) - P; icept = B(1:Km1)(:).'; slope = B(Km1+1:end); if (P > 0) eta = X * slope + repmat (icept, n, 1); else eta = repmat (icept, n, 1); endif endif ## Inverse link (mean function) for ordinal and hierarchical models switch (link) case 'logit' G = 1 ./ (1 + exp (-eta)); case 'probit' G = normcdf (eta); case 'comploglog' G = 1 - exp (-exp (eta)); case 'loglog' G = exp (-exp (eta)); endswitch ## Category probabilities PCAT (n-by-K) switch (model) case 'nominal' ee = exp (eta); den = 1 + sum (ee, 2); pcat = [ee ./ den, 1 ./ den]; case 'ordinal' pcat = [G(:,1), diff(G, 1, 2), 1 - G(:,end)]; case 'hierarchical' ## G holds the conditional probabilities P(Y = j | Y >= j) surv = cumprod ([ones(n, 1), 1 - G], 2); ## surv(:,j) = P(Y >= j) pcat = [G .* surv(:,1:Km1), surv(:,end)]; endswitch ## Requested probability type switch (type) case 'category' out = pcat; case 'cumulative' c = cumsum (pcat, 2); out = c(:,1:end-1); case 'conditional' surv = fliplr (cumsum (fliplr (pcat), 2)); ## surv(:,j) = P(Y >= j) out = pcat(:,1:end-1) ./ surv(:,1:end-1); endswitch ## Scale to counts when sample sizes are supplied if (! isempty (ssize)) out = out .* ssize; endif endfunction %!demo %! ## Fit an ordinal model and predict the category probabilities %! X = [1; 2; 3; 4; 5; 6; 7; 8]; %! Y = [1; 1; 1; 2; 2; 2; 3; 3]; %! B = mnrfit (X, Y, 'model', 'ordinal'); %! pihat = mnrval (B, X, 'model', 'ordinal') ## Round-trip against logistic_regression: mnrval must reproduce the fitted ## probabilities of the ordinal model that mnrfit wraps. %!test %! X = [1.489381332449196, 1.1534152241851305; ... %! 1.8110085304863965, 0.9449666896938425; ... %! -0.04453299665130296, 0.34278203449678646; ... %! -0.36616019468850347, 1.130254275908322; ... %! 0.15339143291005095, -0.7921044310668951; ... %! -1.6031878794469698, -1.8343471035233376; ... %! -0.14349521143198166, -0.6762996896828459; ... %! -0.4403818557740143, -0.7921044310668951; ... %! -0.7372685001160434, -0.027793137932169563; ... %! -0.11875465773681024, 0.5512305689880763]; %! Y = [1;1;1;1;1;0;0;0;0;0]; %! B = mnrfit (X, Y + 1, 'model', 'ordinal'); %! [~, ~, ~, ~, ~, Pref] = logistic_regression (Y, X, false); %! assert_equal (mnrval (B, X, 'model', 'ordinal'), Pref, 1e-10); ## For a binary response the default nominal call equals the ordinal call %!test %! X = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]; %! Y = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! B = mnrfit (X, Y, 'model', 'ordinal'); %! p_nom = mnrval (B, X); %! p_ord = mnrval (B, X, 'model', 'ordinal'); %! assert_equal (p_nom, p_ord, 1e-12); ## Every row of the category probabilities sums to one, for all three models %!test %! X = randn (20, 2); %! Bnom = [0.5, -0.2; 1.0, 0.3; -0.4, 0.1]; # (P+1)-by-(K-1), K = 3 %! assert_equal (sum (mnrval (Bnom, X), 2), ones (20, 1), 1e-12); %! assert_equal (sum (mnrval (Bnom, X, 'model', 'hierarchical'), 2), ... %! ones (20, 1), 1e-12); %! Bord = [-1; 1; 0.5; -0.3]; # (K-1+P)-by-1, K = 3, P = 2 %! assert_equal (sum (mnrval (Bord, X, 'model', 'ordinal'), 2), ... %! ones (20, 1), 1e-12); ## Probabilities lie in [0, 1] %!test %! X = randn (15, 2); %! Bord = [-1; 0.8; 0.5; -0.3]; %! p = mnrval (Bord, X, 'model', 'ordinal'); %! assert_equal (all (p(:) >= 0 & p(:) <= 1), true); ## Cumulative type equals the running sum of the category probabilities %!test %! X = randn (12, 2); %! Bord = [-0.5; 1.2; 0.4; -0.2]; %! pc = mnrval (Bord, X, 'model', 'ordinal', 'type', 'category'); %! cu = mnrval (Bord, X, 'model', 'ordinal', 'type', 'cumulative'); %! assert_equal (cu, cumsum (pc(:,1:end-1), 2), 1e-12); ## Predicted counts equal probabilities times the sample size %!test %! X = randn (10, 2); %! Bord = [-0.5; 1.0; 0.4; -0.2]; %! p = mnrval (Bord, X, 'model', 'ordinal'); %! y = mnrval (Bord, X, 100, 'model', 'ordinal'); %! assert_equal (y, p * 100, 1e-10); ## Confidence bounds have the same size as the prediction and are non-negative %!test %! X = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]; %! Y = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! [B, ~, stats] = mnrfit (X, Y, 'model', 'ordinal'); %! [p, dlo, dhi] = mnrval (B, X, stats, 'model', 'ordinal'); %! assert_equal (size (dlo), size (p)); %! assert_equal (size (dhi), size (p)); %! assert_equal (all (dlo(:) >= 0) && all (dhi(:) >= 0), true); ## Every link produces category probabilities that sum to one %!test %! X = randn (10, 1); %! Bord = [-0.5; 0.7; 0.4]; %! for lnk = {'logit', 'probit', 'comploglog', 'loglog'} %! p = mnrval (Bord, X, 'model', 'ordinal', 'link', lnk{1}); %! assert_equal (sum (p, 2), ones (10, 1), 1e-10); %! endfor ## Increasing links with ordered intercepts keep probabilities in [0, 1] %!test %! X = randn (10, 1); %! Bord = [-0.5; 0.7; 0.4]; %! for lnk = {'logit', 'probit', 'comploglog'} %! p = mnrval (Bord, X, 'model', 'ordinal', 'link', lnk{1}); %! assert_equal (all (p(:) >= 0 & p(:) <= 1), true); %! endfor ## The loglog link is decreasing, so a valid ordinal model needs its ## intercepts in decreasing order %!test %! X = randn (10, 1); %! Bll = [1.0; -0.5; 0.4]; %! p = mnrval (Bll, X, 'model', 'ordinal', 'link', 'loglog'); %! assert_equal (sum (p, 2), ones (10, 1), 1e-10); %! assert_equal (all (p(:) >= 0 & p(:) <= 1), true); ## Test input validation %!error mnrval (1) %!error mnrval ({1}, ones (3, 1)) %!error ... %! mnrval ([1; 1], ones (3, 3, 3)) %!error ... %! mnrval ([1; 1], ones (3, 1), 'model') %!error ... %! mnrval ([1; 1], ones (3, 1), 'foo', 'bar') %!error ... %! mnrval ([1; 1], ones (3, 1), 'model', 'whatever') %!error ... %! mnrval ([1; 1], ones (3, 1), 'interactions', 'maybe') %!error ... %! mnrval ([1; 1], ones (3, 1), 'model', 'ordinal', 'link', 'foo') %!error ... %! mnrval ([1; 1], ones (3, 1), 'link', 'probit') %!error ... %! mnrval ([1; 1], ones (3, 1), 'type', 'foo') %!error ... %! mnrval ([1; 1], ones (3, 1), 'confidence', 2) %!error ... %! mnrval ([1; 1; 1], ones (3, 1)) %!error ... %! [p, dlo] = mnrval ([1; 1], ones (3, 1)); %!error ... %! [p, dlo] = mnrval ([1; 1], ones (3, 1), struct ('x', 1)); statistics-release-1.9.2/inst/Regression/monotone_smooth.m000066400000000000000000000146351524624707500240620ustar00rootroot00000000000000## Copyright (C) 2011 Nir Krakauer ## Copyright (C) 2011 Carnë Draug ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{yy} =} monotone_smooth (@var{x}, @var{y}, @var{h}) ## ## Produce a smooth monotone increasing approximation to a sampled functional ## dependence. ## ## A kernel method is used (an Epanechnikov smoothing kernel is applied to y(x); ## this is integrated to yield the monotone increasing form. See Reference 1 ## for details.) ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{x} is a vector of values of the independent variable. ## ## @item ## @var{y} is a vector of values of the dependent variable, of the same size as ## @var{x}. For best performance, it is recommended that the @var{y} already be ## fairly smooth, e.g. by applying a kernel smoothing to the original values if ## they are noisy. ## ## @item ## @var{h} is the kernel bandwidth to use. If @var{h} is not given, ## a "reasonable" value is computed. ## ## @end itemize ## ## @subheading Return values ## ## @itemize @bullet ## @item ## @var{yy} is the vector of smooth monotone increasing function values at ## @var{x}. ## ## @end itemize ## ## @subheading Examples ## ## @example ## @group ## x = 0:0.1:10; ## y = (x .^ 2) + 3 * randn(size(x)); # typically non-monotonic from the added ## noise ## ys = ([y(1) y(1:(end-1))] + y + [y(2:end) y(end)])/3; # crudely smoothed via ## moving average, but still typically non-monotonic ## yy = monotone_smooth(x, ys); # yy is monotone increasing in x ## plot(x, y, '+', x, ys, x, yy) ## @end group ## @end example ## ## @subheading References ## ## @enumerate ## @item ## Holger Dette, Natalie Neumeyer and Kay F. Pilz (2006), A simple nonparametric ## estimator of a strictly monotone regression function, @cite{Bernoulli}, ## 12:469-490 ## @item ## Regine Scheder (2007), R Package 'monoProc', Version 1.0-6, ## @url{http://cran.r-project.org/web/packages/monoProc/monoProc.pdf} (The ## implementation here is based on the monoProc function mono.1d) ## @end enumerate ## @end deftypefn function yy = monotone_smooth (x, y, h) if (nargin < 2 || nargin > 3) print_usage (); elseif (! isnumeric (x) || ! isvector (x)) error ("monotone_smooth: X must be a numeric vector."); elseif (! isnumeric (y) || ! isvector (y)) error ("monotone_smooth: Y must be a numeric vector."); elseif (numel (x) != numel (y)) error ("monotone_smooth: X and Y must have the same number of elements."); elseif (nargin == 3 && (! isscalar (h) || ! isnumeric (h))) error ("monotone_smooth: H (kernel bandwidth) must a numeric scalar."); endif n = numel (x); ## Set filter bandwidth at a reasonable default value, if not specified if (nargin != 3) s = std (x); h = s / (n ^ 0.2); endif x_min = min (x); x_max = max (x); y_min = min (y); y_max = max (y); ## Transform range of X to [0, 1] xl = (x - x_min) / (x_max - x_min); yy = ones (size (y)); ## Epanechnikov smoothing kernel (with finite support) ## K_epanech_kernel = @(z) (3/4) * ((1 - z).^2) .* (abs(z) < 1); K_epanech_int = @(z) mean (((abs (z) < 1)/2) - (3/4) * (z .* (abs (z) < 1) ... - (1/3) * (z.^3) .* (abs (z) < 1)) + (z < -1)); ## Integral of kernels up to t monotone_inverse = @(t) K_epanech_int((y - t) / h); ## Find the value of the monotone smooth function at each point in X niter_max = 150; # maxIter for estimating each value (adequate for most cases) for l = 1:n tmax = y_max; tmin = y_min; wmin = monotone_inverse(tmin); wmax = monotone_inverse(tmax); if (wmax == wmin) yy(l) = tmin; else wt = xl(l); iter_max_reached = 1; for i = 1:niter_max wt_scaled = (wt - wmin) / (wmax - wmin); tn = tmin + wt_scaled * (tmax - tmin) ; wn = monotone_inverse(tn); wn_scaled = (wn - wmin) / (wmax - wmin); ## if (abs(wt-wn) < 1E-4) || (tn < (y_min-0.1)) || (tn > (y_max+0.1)) ## criterion for break in the R code -- replaced by the following line ## to hopefully be less dependent on the scale of y if ((abs (wt_scaled-wn_scaled) < 1E-4) || (wt_scaled < -0.1) || (wt_scaled > 1.1)) iter_max_reached = 0; break endif if (wn > wt) tmax = tn; wmax = wn; else tmin = tn; wmin = wn; endif endfor if (iter_max_reached) msg = sprintf (strcat ("at x = %%g, maximum number of iterations", ... " %%d reached without convergence;", ... " approximation may not be optimal")); warning (msg, x(l), niter_max) endif yy(l) = tmin + (wt - wmin) * (tmax - tmin) / (wmax - wmin); endif endfor endfunction ## Test input validation %!error ... %! monotone_smooth (1) %!error ... %! monotone_smooth ('char', 1) %!error ... %! monotone_smooth ({1,2,3}, 1) %!error ... %! monotone_smooth (ones (20,3), 1) %!error ... %! monotone_smooth (1, 'char') %!error ... %! monotone_smooth (1, {1,2,3}) %!error ... %! monotone_smooth (1, ones (20,3)) %!error monotone_smooth (ones (10,1), ones (10,1), [1, 2]) %!error monotone_smooth (ones (10,1), ones (10,1), {2}) %!error monotone_smooth (ones (10,1), ones (10,1), 'char') statistics-release-1.9.2/inst/Regression/mvregress.m000066400000000000000000000375351524624707500226540ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{beta} =} mvregress (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{beta} =} mvregress (@dots{}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{beta}, @var{Sigma}, @var{E}, @var{CovB}, @var{logL}] =} mvregress (@dots{}) ## ## Multivariate (multiple-response) linear regression by maximum likelihood. ## ## @code{mvregress (@var{X}, @var{Y})} fits the multivariate normal regression ## of the @var{n}-by-@var{d} response matrix @var{Y} on the design @var{X} and ## returns the coefficient estimates @var{beta}. ## ## @var{X} is either a numeric @var{n}-by-@var{p} matrix, in which case the same ## @var{p} predictors apply to every response and @var{beta} is returned as a ## @var{p}-by-@var{d} matrix; or a cell array of @var{n} design matrices, each ## @var{d}-by-@var{K}, in which case @var{beta} is a @var{K}-by-1 vector. ## ## Missing responses (@code{NaN} entries of @var{Y}) are handled according to ## the estimation algorithm. ## ## The following @var{name}/@var{value} pairs are accepted: ## ## @table @asis ## @item @qcode{"algorithm"} ## @qcode{"mvn"} (multivariate normal; observations with any missing response ## are discarded), @qcode{"ecm"} (expectation-conditional-maximization, using ## every observed response), or @qcode{"cwls"} (covariance-weighted least ## squares, with the weight given by @qcode{"covar0"}). The default is ## @qcode{"mvn"} when @var{Y} has no missing values and @qcode{"ecm"} otherwise. ## ## @item @qcode{"covar0"} ## The @var{d}-by-@var{d} covariance weight for @qcode{"cwls"} (default the ## identity), or the initial covariance for @qcode{"ecm"}. ## ## @item @qcode{"maxiter"} ## Maximum number of iterations (default 100). ## ## @item @qcode{"tolbeta"}, @qcode{"tolobj"} ## Convergence tolerances on the coefficients and the objective (defaults ## @code{1e-8} and @code{1e-8}). ## @end table ## ## The additional outputs are the estimated residual covariance @var{Sigma} ## (@var{d}-by-@var{d}), the residuals @var{E} (@var{n}-by-@var{d}), the ## covariance @var{CovB} of the coefficient estimates, and the log-likelihood ## @var{logL}. (With missing data and the @qcode{"ecm"} algorithm, @var{CovB} ## is the standard observed-information covariance and can differ from ## @sc{matlab}'s value at the @code{1e-3} level; all other outputs agree.) ## ## @seealso{mvregresslike, regress, fitlm} ## @end deftypefn function [beta, Sigma, E, CovB, logL] = mvregress (X, Y, varargin) if (nargin < 2) print_usage (); endif if (! (isnumeric (Y) && ismatrix (Y) && isreal (Y))) error ("mvregress: Y must be a real numeric matrix."); endif [n, d] = size (Y); ## --- normalise the design to per-observation d-by-K matrices --- is_numeric = ! iscell (X); if (is_numeric) if (rows (X) != n) error ("mvregress: X must have as many rows as Y."); endif p = columns (X); K = p * d; Xcell = cell (n, 1); for i = 1:n Xcell{i} = kron (eye (d), X(i, :)); endfor else if (numel (X) != n) error ("mvregress: X must have one design matrix per observation."); endif K = columns (X{1}); Xcell = X(:); endif ## --- options --- hasnan = any (isnan (Y(:))); alg = ""; covar0 = []; maxiter = 100; tolbeta = 1e-8; tolobj = 1e-8; if (mod (numel (varargin), 2) != 0) error ("mvregress: name/value arguments must come in pairs."); endif for i = 1:2:numel (varargin) switch (lower (char (varargin{i}))) case "algorithm" alg = lower (char (varargin{i+1})); case "covar0" covar0 = varargin{i+1}; case "maxiter" maxiter = varargin{i+1}; case {"tolbeta"} tolbeta = varargin{i+1}; case {"tolobj"} tolobj = varargin{i+1}; otherwise error ("mvregress: unknown option '%s'.", char (varargin{i})); endswitch endfor if (isempty (alg)) if (hasnan) alg = "ecm"; else alg = "mvn"; endif endif if (! any (strcmp (alg, {"ecm", "cwls", "mvn"}))) error ("mvregress: algorithm must be 'ecm', 'cwls', or 'mvn'."); endif ## 'mvn' discards observations with any missing response. keep = true (n, 1); if (strcmp (alg, "mvn")) keep = all (! isnan (Y), 2); endif ## --- fit --- ## All three algorithms produce the maximum-likelihood estimates ('mvn' on the ## kept rows, 'ecm'/'cwls' using every observed response). They differ only ## in how the reported log-likelihood and coefficient covariance are weighted: ## 'cwls' uses the fixed weight covar0 (default the identity), the others use ## the estimated residual covariance. if (isempty (covar0)), covar0 = eye (d); endif if (strcmp (alg, "mvn")) [b, Sigma] = ml_fit (Xcell, Y, eye (d), keep, maxiter, tolbeta, tolobj); Wsig = Sigma; else [b, Sigma] = ml_fit (Xcell, Y, covar0, keep, maxiter, tolbeta, tolobj); if (strcmp (alg, "cwls")) Wsig = covar0; ## logL / CovB use the fixed weight else Wsig = Sigma; endif endif E = residuals (Xcell, Y, b, n, d); ## --- outputs --- logL = -nll_obs (Xcell, Y, b, Wsig, keep); CovB = inv (info_beta (Xcell, Y, Wsig, keep)); if (is_numeric) beta = reshape (b, p, d); else beta = b; endif endfunction ## GLS coefficient estimate with a fixed covariance W (over the kept rows). function b = gls_beta (Xcell, Y, W, keep) K = columns (Xcell{1}); A = zeros (K); rhs = zeros (K, 1); for i = 1:numel (Xcell) if (! keep(i)), continue; endif o = ! isnan (Y(i, :)); if (! any (o)), continue; endif Xio = Xcell{i}(o, :); Wo = W(o, o); A += Xio' * (Wo \ Xio); rhs += Xio' * (Wo \ Y(i, o)'); endfor b = A \ rhs; endfunction ## Maximum-likelihood fit by expectation-conditional-maximization. Reduces to ## ordinary least squares for a complete common design. function [b, Sigma] = ml_fit (Xcell, Y, Sigma, keep, maxiter, tolbeta, tolobj) n = numel (Xcell); d = columns (Y); b = gls_beta (Xcell, Y, Sigma, keep); prev_obj = Inf; for iter = 1:maxiter Si = inv (Sigma); K = numel (b); A = zeros (K); rhs = zeros (K, 1); Scov = zeros (d); nu = 0; ## E-step: impute missing responses; M-step accumulation. for i = 1:n if (! keep(i)), continue; endif o = ! isnan (Y(i, :)); if (! any (o)), continue; endif m = ! o; Xi = Xcell{i}; yfull = zeros (d, 1); yfull(o) = Y(i, o)'; Ccond = zeros (d); if (any (m)) ro = Y(i, o)' - Xi(o, :) * b; yfull(m) = Xi(m, :) * b + Sigma(m, o) * (Sigma(o, o) \ ro); Ccond(m, m) = Sigma(m, m) - Sigma(m, o) * (Sigma(o, o) \ Sigma(o, m)); endif A += Xi' * Si * Xi; rhs += Xi' * Si * yfull; nu += 1; ## store per-observation info to rebuild the residual after beta update imp{i} = yfull; cc{i} = Ccond; endfor b = A \ rhs; for i = 1:n if (! keep(i) || ! any (! isnan (Y(i, :)))), continue; endif r = imp{i} - Xcell{i} * b; Scov += r * r' + cc{i}; endfor Sigma = Scov / nu; obj = nll_obs (Xcell, Y, b, Sigma, keep); if (max (abs (A \ rhs - b)) < tolbeta && abs (prev_obj - obj) < tolobj) break; endif prev_obj = obj; endfor endfunction ## Residuals Y - X*beta (NaN preserved where Y is missing). function E = residuals (Xcell, Y, b, n, d) E = nan (n, d); for i = 1:n o = ! isnan (Y(i, :)); E(i, o) = Y(i, o)' - Xcell{i}(o, :) * b; endfor endfunction ## Negative log-likelihood of the observed responses. function nll = nll_obs (Xcell, Y, b, Sigma, keep) nll = 0; for i = 1:numel (Xcell) if (! keep(i)), continue; endif o = ! isnan (Y(i, :)); if (! any (o)), continue; endif r = Y(i, o)' - Xcell{i}(o, :) * b; So = Sigma(o, o); nll += 0.5 * (sum (o) * log (2*pi) + 2*sum (log (diag (chol (So)))) ... + r' * (So \ r)); endfor endfunction ## Fisher information for the coefficients. function I = info_beta (Xcell, Y, Sigma, keep) K = columns (Xcell{1}); I = zeros (K); for i = 1:numel (Xcell) if (! keep(i)), continue; endif o = ! isnan (Y(i, :)); if (! any (o)), continue; endif Xio = Xcell{i}(o, :); I += Xio' * (Sigma(o, o) \ Xio); endfor endfunction %!demo %! ## Two correlated responses regressed on a common predictor. %! rng (42); %! X = [ones(30,1), (1:30)'/30]; %! B = [1 -1; 2 0.5]; %! E = [0.3 0.1; 0.1 0.2]; %! Y = X * B + randn (30, 2) * chol (E); %! [beta, Sigma] = mvregress (X, Y) %!shared X, Ycomp, Ymiss, Btrue %! X = [ones(12,1), linspace(-1, 1, 12)']; %! Btrue = [1 -0.5 2; 0.3 1.2 -0.8]; %! Ycomp = X * Btrue + 0.05 * cos ((1:12)' * [1 2 3]); %! Ymiss = Ycomp; Ymiss(3,2) = NaN; Ymiss(8,3) = NaN; %!test # numeric design returns a p-by-d beta equal to OLS (complete data) %! [beta, Sigma, E] = mvregress (X, Ycomp, "algorithm", "mvn"); %! assert_equal (size (beta), [2, 3]); %! assert_equal (beta, (X'*X)\(X'*Ycomp), 1e-8); %! assert_equal (Sigma, E'*E/12, 1e-8); %!test # cell design returns the vectorised (K-by-1) beta %! Xc = cell (12, 1); %! for i = 1:12, Xc{i} = kron (eye (3), X(i,:)); end %! bnum = mvregress (X, Ycomp); %! bcell = mvregress (Xc, Ycomp); %! assert_equal (bcell, bnum(:), 1e-8); %!test # logL equals -mvregresslike at the fit (mvn, complete) %! [beta, Sigma, E, CovB, logL] = mvregress (X, Ycomp, "algorithm", "mvn"); %! assert_equal (logL, -mvregresslike (X, Ycomp, beta, Sigma, "mvn"), 1e-8); %! assert_equal (CovB, kron (Sigma, inv (X'*X)), 1e-8); %!test # ecm uses all observed data; the log-likelihood improves on listwise %! b_ecm = mvregress (X, Ymiss, "algorithm", "ecm"); %! b_mvn = mvregress (X, Ymiss, "algorithm", "mvn"); %! assert_equal (! isequal (b_ecm, b_mvn), true); # different estimates %!test # default algorithm: mvn without missing data, ecm with %! [b1, ~, ~, ~, L1] = mvregress (X, Ycomp); %! [b2, ~, ~, ~, L2] = mvregress (X, Ycomp, "algorithm", "mvn"); %! assert_equal (L1, L2, 1e-10); %!test # cwls: logL and CovB use the identity weight %! [beta, Sigma, E, CovB, logL] = mvregress (X, Ycomp, "algorithm", "cwls"); %! assert_equal (logL, -mvregresslike (X, Ycomp, beta, eye (3), "cwls"), 1e-8); %! assert_equal (CovB, kron (eye (3), inv (X'*X)), 1e-8); ## MATLAB-verified parity (mvregress R2026a): 25 observations, 3 responses. %!shared Xc, Yc, Ym %! Xc = [1 -0.5382438937; ... %! 1 0.8672321576; ... %! 1 0.9759864635; ... %! 1 0.3373902524; ... %! 1 -0.9960940966; ... %! 1 -0.5232140317; ... %! 1 -1.297447477; ... %! 1 0.9173885891; ... %! 1 0.1766016286; ... %! 1 0.7551799357; ... %! 1 -0.5914999598; ... %! 1 1.844389637; ... %! 1 1.816922249; ... %! 1 -0.1238333503; ... %! 1 -1.110601355; ... %! 1 -0.6809058803; ... %! 1 0.0141693264; ... %! 1 -0.05955061046; ... %! 1 -0.6610110145; ... %! 1 0.3059509151; ... %! 1 -0.4090578458; ... %! 1 -1.281854823; ... %! 1 -0.2849028295; ... %! 1 -0.06478685589; ... %! 1 1.000383189]; %! Yc = [0.03719753357 -1.298719829 2.592641544; ... %! -0.8732979121 0.2860080857 0.07019509407; ... %! 0.9664790872 2.93781363 2.07467313; ... %! 1.928227972 0.000735060807 1.023278991; ... %! 1.099525737 -1.790260694 1.621821039; ... %! 0.8212389245 -1.973919957 3.698786362; ... %! 0.1466274959 -1.562219847 2.319552949; ... %! 1.316125907 0.7610511082 0.9228417259; ... %! 3.19879516 1.119205548 3.035118373; ... %! 3.324100363 1.40533639 2.521210797; ... %! -0.5492763312 -2.535610011 -0.009321693727; ... %! 0.7506317092 1.042220218 -0.1580843947; ... %! 2.65978063 1.200673839 0.1312316546; ... %! 1.060827996 -1.429695245 2.854978118; ... %! 0.3193218045 -0.6292560647 3.238543733; ... %! 0.08284787504 -0.7632940769 3.223147972; ... %! 1.943818586 -2.359810723 2.132345279; ... %! -1.007337725 0.4325819195 1.790333779; ... %! 0.5483627737 -2.463255725 2.103406495; ... %! 0.4463418718 0.4193706852 1.806900862; ... %! 2.151700093 -0.6796218054 0.2488978108; ... %! 1.601013282 -0.7661239912 3.004258316; ... %! 1.51658466 -0.6677671869 2.37476419; ... %! 0.8655905179 -1.911105684 1.609121594; ... %! -1.036725518 0.3884447934 0.2753353942]; %! Ym = [0.03719753357 -1.298719829 2.592641544; ... %! -0.8732979121 NaN 0.07019509407; ... %! 0.9664790872 2.93781363 2.07467313; ... %! 1.928227972 0.000735060807 1.023278991; ... %! 1.099525737 -1.790260694 NaN; ... %! 0.8212389245 -1.973919957 3.698786362; ... %! 0.1466274959 NaN 2.319552949; ... %! 1.316125907 0.7610511082 0.9228417259; ... %! 3.19879516 1.119205548 3.035118373; ... %! 3.324100363 1.40533639 2.521210797; ... %! -0.5492763312 -2.535610011 -0.009321693727; ... %! 0.7506317092 1.042220218 -0.1580843947; ... %! 2.65978063 1.200673839 0.1312316546; ... %! 1.060827996 NaN 2.854978118; ... %! 0.3193218045 -0.6292560647 3.238543733; ... %! 0.08284787504 -0.7632940769 3.223147972; ... %! 1.943818586 -2.359810723 2.132345279; ... %! -1.007337725 0.4325819195 1.790333779; ... %! 0.5483627737 -2.463255725 NaN; ... %! 0.4463418718 0.4193706852 1.806900862; ... %! 2.151700093 -0.6796218054 0.2488978108; ... %! 1.601013282 -0.7661239912 3.004258316; ... %! 1.51658466 -0.6677671869 2.37476419; ... %! 0.8655905179 -1.911105684 1.609121594; ... %! -1.036725518 0.3884447934 0.2753353942]; %!test # complete data: beta (=OLS), Sigma (ML), logL, CovB vs MATLAB %! [beta, Sigma, ~, CovB, logL] = mvregress (Xc, Yc, "algorithm", "mvn"); %! assert_equal (beta, [0.9290602475, -0.4513207572, 1.79229326; ... %! 0.2367436851, 1.147222011, -0.7755015141], 1e-6); %! assert_equal (Sigma, [1.332860184, 0.1860841493, 0.3727350368; ... %! 0.1860841493, 0.9491200208, 0.328607071; ... %! 0.3727350368, 0.328607071, 0.8648578221], 1e-6); %! assert_equal (logL, -104.1503454, 1e-5); %! assert_equal (CovB, kron (Sigma, inv (Xc'*Xc)), 1e-8); %!test # cwls reports the log-likelihood under the identity weight %! [~, ~, ~, ~, logL] = mvregress (Xc, Yc, "algorithm", "cwls"); %! assert_equal (logL, -108.2558653, 1e-5); %!test # missing data, ecm (uses every observed response) vs MATLAB %! [beta, Sigma, ~, ~, logL] = mvregress (Xc, Ym, "algorithm", "ecm"); %! assert_equal (beta, [0.9290602475, -0.4380875727, 1.82736137; ... %! 0.2367436851, 1.173891485, -0.825576035], 1e-5); %! assert_equal (Sigma, [1.332860184, 0.2265958964, 0.3918376495; ... %! 0.2265958964, 1.034602545, 0.4028401374; ... %! 0.3918376495, 0.4028401374, 0.8842430898], 1e-5); %! assert_equal (logL, -98.2574859, 1e-5); %!test # missing data, mvn (listwise deletion) vs MATLAB %! [beta, ~, ~, ~, logL] = mvregress (Xc, Ym, "algorithm", "mvn"); %! assert_equal (beta, [1.025969216, -0.3367657906, 1.885558095; ... %! 0.3142894292, 1.09915421, -0.8366732999], 1e-5); %! assert_equal (logL, -85.21502995, 1e-5); %!test # missing data, cwls logL under identity weight vs MATLAB %! [~, ~, ~, ~, logL] = mvregress (Xc, Ym, "algorithm", "cwls"); %! assert_equal (logL, -102.6628017, 1e-5); %!error mvregress (1) %!error mvregress (ones (3), {1}) %!error mvregress (ones (2, 2), ones (3, 2)) %!error mvregress (ones (3, 2), ones (3, 2), "algorithm", "xxx") %!error mvregress (ones (3, 2), ones (3, 2), "bogus", 1) statistics-release-1.9.2/inst/Regression/mvregresslike.m000066400000000000000000000244001524624707500235040ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{nlogL} =} mvregresslike (@var{X}, @var{Y}, @var{beta}, @var{Sigma}, @var{alg}) ## @deftypefnx {statistics} {[@var{nlogL}, @var{COVB}] =} mvregresslike (@dots{}) ## ## Negative log-likelihood for a multivariate regression model. ## ## @code{mvregresslike (@var{X}, @var{Y}, @var{beta}, @var{Sigma}, @var{alg})} ## returns the negative log-likelihood @var{nlogL} of the multivariate normal ## regression model with responses @var{Y} (an @var{n}-by-@var{d} matrix, one ## row per observation), coefficients @var{beta}, and residual covariance ## @var{Sigma} (@var{d}-by-@var{d}). ## ## @var{X} specifies the design. It is either a numeric @var{n}-by-@var{p} ## matrix, in which case the same @var{p} predictors apply to every response and ## @var{beta} is @var{p}-by-@var{d}; or a cell array of @var{n} design matrices, ## each @var{d}-by-@var{K}, in which case @var{beta} is @var{K}-by-1. ## ## @var{alg} selects how missing responses (@code{NaN} entries of @var{Y}) are ## handled: @qcode{"ecm"} (the default) and @qcode{"cwls"} use every observed ## response through the marginal likelihood of the observed components, while ## @qcode{"mvn"} discards any observation that has a missing response. With no ## missing data all three agree. ## ## The optional second output @var{COVB} is the covariance matrix of the ## coefficient estimates, computed as the inverse of the observed Fisher ## information at @var{beta} and @var{Sigma}. With missing data and the ## @qcode{"ecm"}/@qcode{"cwls"} algorithms this is the standard observed-data ## covariance and can differ from @sc{matlab}'s value (which uses a different ## information convention) at the @code{1e-3} level; @var{nlogL} agrees exactly. ## ## @seealso{mvregress} ## @end deftypefn function [nlogL, COVB] = mvregresslike (X, Y, beta, Sigma, alg) if (nargin < 4) print_usage (); endif if (nargin < 5 || isempty (alg)) alg = "ecm"; endif alg = lower (char (alg)); if (! any (strcmp (alg, {"ecm", "cwls", "mvn"}))) error ("mvregresslike: ALG must be 'ecm', 'cwls', or 'mvn'."); endif [Xcell, bvec, n, d, K] = mvr_design (X, Y, beta); if (! isequal (size (Sigma), [d, d])) error ("mvregresslike: Sigma must be a %d-by-%d matrix.", d, d); endif ## 'mvn' discards observations with any missing response. listwise = strcmp (alg, "mvn"); nlogL = 0; Info = zeros (K, K); for i = 1:n o = ! isnan (Y(i, :)); if (! any (o) || (listwise && ! all (o))) continue; endif Xio = Xcell{i}(o, :); r = Y(i, o)' - Xio * bvec; So = Sigma(o, o); nlogL += 0.5 * (sum (o) * log (2*pi) + logdet (So) + r' * (So \ r)); Info += Xio' * (So \ Xio); endfor if (nargout > 1) COVB = inv (Info); endif endfunction ## Normalise the design to a cell array of per-observation d-by-K matrices and ## the coefficient vector to K-by-1. function [Xcell, bvec, n, d, K] = mvr_design (X, Y, beta) if (! (isnumeric (Y) && ismatrix (Y))) error ("mvregresslike: Y must be a numeric matrix."); endif [n, d] = size (Y); if (iscell (X)) if (numel (X) != n) error ("mvregresslike: X must have one design matrix per observation."); endif K = columns (X{1}); Xcell = X(:); bvec = beta(:); else if (rows (X) != n) error ("mvregresslike: X must have as many rows as Y."); endif p = columns (X); K = p * d; Xcell = cell (n, 1); for i = 1:n Xcell{i} = kron (eye (d), X(i, :)); endfor bvec = beta(:); endif if (numel (bvec) != K) error ("mvregresslike: beta has the wrong number of elements."); endif endfunction ## log(det(A)) via the Cholesky factor (A is a positive-definite covariance). function ld = logdet (A) ld = 2 * sum (log (diag (chol (A)))); endfunction %!demo %! ## Negative log-likelihood of a two-response regression at the true params. %! rng (42); %! X = [ones(20,1), (1:20)'/20]; %! B = [1 -2; 0.5 3]; %! Y = X * B + 0.3 * randn (20, 2); %! nll = mvregresslike (X, Y, B, cov (Y - X*B)) %!test # complete data: nll agrees across algorithms %! X = [ones(15,1), linspace(-1,1,15)']; %! B = [2 -1 0.5; 1 0.3 -0.4]; %! Y = X * B + 0.1 * cos ((1:15)' * [1 2 3]); %! S = cov (Y - X*B); %! n1 = mvregresslike (X, Y, B, S, "ecm"); %! n2 = mvregresslike (X, Y, B, S, "cwls"); %! n3 = mvregresslike (X, Y, B, S, "mvn"); %! assert_equal (n1, n2, 1e-12); %! assert_equal (n1, n3, 1e-12); %!test # COVB of complete common design equals kron (Sigma, inv (X'X)) %! X = [ones(15,1), linspace(-1,1,15)']; %! B = [2 -1 0.5; 1 0.3 -0.4]; %! Y = X * B + 0.1 * cos ((1:15)' * [1 2 3]); %! S = cov (Y - X*B); %! [~, COVB] = mvregresslike (X, Y, B, S, "ecm"); %! assert_equal (COVB, kron (S, inv (X'*X)), 1e-10); %!test # cell and numeric designs give the same nll %! X = [ones(10,1), (1:10)']; %! B = [1 2; -1 0.5]; %! Y = X * B + 0.2 * sin ((1:10)' * [1 2]); %! S = cov (Y - X*B); %! Xc = cell (10, 1); %! for i = 1:10, Xc{i} = kron (eye (2), X(i,:)); end %! assert_equal (mvregresslike (Xc, Y, B(:), S), ... %! mvregresslike (X, Y, B, S), 1e-12); ## MATLAB-verified parity (mvregresslike R2026a): 25 observations, 3 responses, ## a shared 2-column design, at four anchor points and complete/missing data. %!shared Xc, Yc, Ym, anch %! Xc = [1 -0.5382438937; ... %! 1 0.8672321576; ... %! 1 0.9759864635; ... %! 1 0.3373902524; ... %! 1 -0.9960940966; ... %! 1 -0.5232140317; ... %! 1 -1.297447477; ... %! 1 0.9173885891; ... %! 1 0.1766016286; ... %! 1 0.7551799357; ... %! 1 -0.5914999598; ... %! 1 1.844389637; ... %! 1 1.816922249; ... %! 1 -0.1238333503; ... %! 1 -1.110601355; ... %! 1 -0.6809058803; ... %! 1 0.0141693264; ... %! 1 -0.05955061046; ... %! 1 -0.6610110145; ... %! 1 0.3059509151; ... %! 1 -0.4090578458; ... %! 1 -1.281854823; ... %! 1 -0.2849028295; ... %! 1 -0.06478685589; ... %! 1 1.000383189]; %! Yc = [0.03719753357 -1.298719829 2.592641544; ... %! -0.8732979121 0.2860080857 0.07019509407; ... %! 0.9664790872 2.93781363 2.07467313; ... %! 1.928227972 0.000735060807 1.023278991; ... %! 1.099525737 -1.790260694 1.621821039; ... %! 0.8212389245 -1.973919957 3.698786362; ... %! 0.1466274959 -1.562219847 2.319552949; ... %! 1.316125907 0.7610511082 0.9228417259; ... %! 3.19879516 1.119205548 3.035118373; ... %! 3.324100363 1.40533639 2.521210797; ... %! -0.5492763312 -2.535610011 -0.009321693727; ... %! 0.7506317092 1.042220218 -0.1580843947; ... %! 2.65978063 1.200673839 0.1312316546; ... %! 1.060827996 -1.429695245 2.854978118; ... %! 0.3193218045 -0.6292560647 3.238543733; ... %! 0.08284787504 -0.7632940769 3.223147972; ... %! 1.943818586 -2.359810723 2.132345279; ... %! -1.007337725 0.4325819195 1.790333779; ... %! 0.5483627737 -2.463255725 2.103406495; ... %! 0.4463418718 0.4193706852 1.806900862; ... %! 2.151700093 -0.6796218054 0.2488978108; ... %! 1.601013282 -0.7661239912 3.004258316; ... %! 1.51658466 -0.6677671869 2.37476419; ... %! 0.8655905179 -1.911105684 1.609121594; ... %! -1.036725518 0.3884447934 0.2753353942]; %! Ym = [0.03719753357 -1.298719829 2.592641544; ... %! -0.8732979121 NaN 0.07019509407; ... %! 0.9664790872 2.93781363 2.07467313; ... %! 1.928227972 0.000735060807 1.023278991; ... %! 1.099525737 -1.790260694 NaN; ... %! 0.8212389245 -1.973919957 3.698786362; ... %! 0.1466274959 NaN 2.319552949; ... %! 1.316125907 0.7610511082 0.9228417259; ... %! 3.19879516 1.119205548 3.035118373; ... %! 3.324100363 1.40533639 2.521210797; ... %! -0.5492763312 -2.535610011 -0.009321693727; ... %! 0.7506317092 1.042220218 -0.1580843947; ... %! 2.65978063 1.200673839 0.1312316546; ... %! 1.060827996 NaN 2.854978118; ... %! 0.3193218045 -0.6292560647 3.238543733; ... %! 0.08284787504 -0.7632940769 3.223147972; ... %! 1.943818586 -2.359810723 2.132345279; ... %! -1.007337725 0.4325819195 1.790333779; ... %! 0.5483627737 -2.463255725 NaN; ... %! 0.4463418718 0.4193706852 1.806900862; ... %! 2.151700093 -0.6796218054 0.2488978108; ... %! 1.601013282 -0.7661239912 3.004258316; ... %! 1.51658466 -0.6677671869 2.37476419; ... %! 0.8655905179 -1.911105684 1.609121594; ... %! -1.036725518 0.3884447934 0.2753353942]; %! Bt = [1 -0.5 2; ... %! 0.3 1.2 -0.8]; %! S0 = cov (Yc); %! anch = {Bt(:), S0; Bt(:)*0, eye(3); ... %! Bt(:)+0.25, 1.5*S0; Bt(:)-0.4, S0+0.2*eye(3)}; %!test # nll vs MATLAB, complete data (all three algorithms agree) %! ref = [111.8681339; 179.5391646; 120.2543928; 116.9416069]; %! for k = 1:4 %! for alg = {"ecm", "cwls", "mvn"} %! assert_equal (mvregresslike (Xc, Yc, anch{k,1}, anch{k,2}, alg{1}), ... %! ref(k), 1e-6); %! endfor %! endfor %!test # nll vs MATLAB, missing data: ecm/cwls use all observed, mvn is listwise %! ref_ecm = [105.2323495; 169.1339808; 112.8053693; 110.1131617]; %! ref_mvn = [91.35955956; 146.830606; 97.1466466; 96.69358201]; %! for k = 1:4 %! assert_equal (mvregresslike (Xc, Ym, anch{k,1}, anch{k,2}, "ecm"), ... %! ref_ecm(k), 1e-6); %! assert_equal (mvregresslike (Xc, Ym, anch{k,1}, anch{k,2}, "cwls"), ... %! ref_ecm(k), 1e-6); %! assert_equal (mvregresslike (Xc, Ym, anch{k,1}, anch{k,2}, "mvn"), ... %! ref_mvn(k), 1e-6); %! endfor %!test # COVB vs MATLAB (complete data) = kron (Sigma, inv (X'X)) %! for k = 1:4 %! [~, COVB] = mvregresslike (Xc, Yc, anch{k,1}, anch{k,2}, "ecm"); %! assert_equal (COVB, kron (anch{k,2}, inv (Xc'*Xc)), 1e-8); %! endfor %!error mvregresslike (1, 2, 3) %!error mvregresslike ([1;2], [1;2], 1, 1, "xxx") %!error mvregresslike (ones(3,2), ones(3,2), ones(2,2), 1) statistics-release-1.9.2/inst/Regression/nlinfit.m000066400000000000000000000605201524624707500222700ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{beta} =} nlinfit (@var{X}, @var{y}, @var{modelfun}, @var{beta0}) ## @deftypefnx {statistics} {@var{beta} =} nlinfit (@dots{}, @var{options}) ## @deftypefnx {statistics} {@var{beta} =} nlinfit (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {statistics} {[@var{beta}, @var{R}, @var{J}, @var{CovB}, @var{MSE}, @var{ErrorModelInfo}] =} nlinfit (@dots{}) ## ## Fit a nonlinear regression model. ## ## @code{@var{beta} = nlinfit (@var{X}, @var{y}, @var{modelfun}, @var{beta0})} ## estimates the coefficients of the nonlinear regression model ## @code{@var{y} = @var{modelfun} (@var{beta}, @var{X})} by iteratively ## minimizing the (possibly weighted) sum of squared residuals, starting from ## the initial coefficient vector @var{beta0}. The fit uses the ## Levenberg-Marquardt algorithm with a numerically computed Jacobian. ## ## @itemize ## @item @var{X} is a matrix of predictor values. @code{nlinfit} does not ## interpret the columns of @var{X}; the array is passed unchanged as the second ## argument of @var{modelfun}, so its shape is whatever @var{modelfun} expects. ## @item @var{y} is a numeric vector of responses, one element per observation. ## @item @var{modelfun} is a function handle @code{@@(@var{b}, @var{X})} ## returning a vector of fitted responses the same size as @var{y}. ## @item @var{beta0} is a numeric vector of initial values for the coefficients. ## @end itemize ## ## Additional options are given either as a statset-style @var{options} ## structure or as @qcode{Name}/@qcode{Value} pairs (or both). The supported ## options are: ## ## @multitable @columnfractions 0.2 0.78 ## @headitem @var{Name} @tab @var{Value} ## @item @qcode{'Weights'} @tab A vector of nonnegative observation weights, or ## a function handle @code{@@(@var{yhat})} returning such a vector. Weighted ## least squares is used. ## @item @qcode{'ErrorModel'} @tab The form of the error variance: ## @qcode{'constant'} (default), @qcode{'proportional'}, or @qcode{'combined'}. ## @item @qcode{'ErrorParameters'} @tab Initial values for the error-model ## parameters. ## @item @qcode{'RobustWgtFun'} @tab The name of a robust weight function ## (@qcode{'andrews'}, @qcode{'bisquare'}, @qcode{'cauchy'}, @qcode{'fair'}, ## @qcode{'huber'}, @qcode{'logistic'}, @qcode{'talwar'}, or @qcode{'welsch'}), ## enabling robust iteratively reweighted least squares. MATLAB accepts this ## name only inside an @qcode{'Options'} structure; taking it as a ## @qcode{Name}/@qcode{Value} pair as well is an Octave extension. ## @item @qcode{'Tune'} @tab The tuning constant for the robust weight function. ## @item @qcode{'Options'} @tab A statset-style structure whose @qcode{MaxIter}, ## @qcode{TolFun}, @qcode{TolX}, and @qcode{DerivStep} fields override the ## corresponding defaults, and whose @qcode{RobustWgtFun}, @qcode{Robust}, ## @qcode{WgtFun} and @qcode{Tune} fields select a robust fit. ## @qcode{RobustWgtFun} names the weight function on its own and takes ## precedence over the other two; the older @qcode{WgtFun} is read only when ## @qcode{Robust} is @qcode{'on'}. The structure @code{statset ('nlinfit')} ## returns carries @qcode{WgtFun} @qcode{'bisquare'} beside @qcode{Robust} ## @qcode{'off'}, and so leaves the fit unweighted. ## @end multitable ## ## The remaining outputs describe the converged fit: @var{R} is the vector of ## raw residuals @code{@var{y} - @var{modelfun} (@var{beta}, @var{X})}, @var{J} ## is the Jacobian of @var{modelfun} with respect to @var{beta} at the solution, ## @var{CovB} is the estimated covariance matrix of the coefficients, @var{MSE} ## is the mean squared error, and @var{ErrorModelInfo} is a structure describing ## the fitted error model. ## ## @subheading Algorithm ## ## The coefficients are estimated by the Levenberg-Marquardt algorithm using a ## numerically computed (forward-difference) Jacobian. For an ordinary or ## weighted fit the coefficient covariance is @code{@var{CovB} = @var{MSE} * ## inv (@var{J}' * @var{W} * @var{J})}, where @var{W} is the diagonal matrix of ## observation weights and @var{MSE} is the weighted residual sum of squares ## divided by the error degrees of freedom @math{n - p} (with @math{p} ## coefficients). A non-constant @qcode{'ErrorModel'} is fitted by generalized ## least squares, re-deriving the observation weights from the fitted values ## each iteration; the @qcode{'proportional'} model weights each observation by ## the inverse squared fitted value, and @var{MSE} then estimates the ## proportionality constant of the variance. ## ## For a robust fit (@qcode{'RobustWgtFun'}) the coefficients are found by ## iteratively reweighted least squares applied to leverage-adjusted residuals ## (the leverage is taken from the ordinary fit and held fixed). The robust ## coefficient covariance follows the Street-Carroll-Ruppert convention, the ## same one used by @code{robustfit}: @code{@var{CovB} = s^2 * inv (@var{J}' * ## @var{J})} and @math{@var{MSE} = s^2}, where the scale @code{s} blends the ## ordinary-fit scale @code{ols_s} with the robust scale @code{robust_s} at the ## solution as @math{s^2 = (p^2 * ols_s^2 + n * robust_s^2) / (n + p^2)}, taken ## to be at least @code{robust_s}. ## ## The robust @code{MSE} and @code{CovB} differ from MATLAB's by up to a few ## tenths of a percent, because the shared robust scale does; @code{robustfit} ## documents that difference and why it is left in place. The coefficients ## themselves agree to about 1e-8. ## ## @seealso{fitnlm, nlparci, nlpredci, NonLinearModel, robustfit} ## @end deftypefn function [beta, R, J, CovB, MSE, ErrorModelInfo] = nlinfit (X, y, modelfun, ... beta0, varargin) if (nargin < 4) print_usage (); endif ## Validate the core inputs. if (! is_function_handle (modelfun)) error ("nlinfit: MODELFUN must be a function handle."); endif if (! (isnumeric (beta0) && isvector (beta0) && isreal (beta0))) error ("nlinfit: BETA0 must be a real numeric vector."); endif if (! (isnumeric (y) && isreal (y))) error ("nlinfit: Y must be a real numeric vector."); endif beta0 = beta0(:); y = y(:); n = numel (y); p = numel (beta0); ## Parse the options structure and the Name/Value pairs. opts = parse_options (varargin); ## Resolve observation weights (constant unless a vector/handle is given). if (isempty (opts.Weights)) w = ones (n, 1); elseif (is_function_handle (opts.Weights)) w = []; # deferred: depends on the fitted values else w = check_weights (opts.Weights, n); endif ## Fit. A robust weight function triggers iteratively reweighted least ## squares; a non-constant error model triggers generalized least squares ## with variance-dependent weights; both wrap the weighted LM solver. dfe = n - p; if (! isempty (opts.RobustWgtFun)) ## Robust fit: MSE and CovB use the Street-Carroll-Ruppert robust scale ## (as in robustfit), CovB = MSE * inv (J' * J) at the robust solution. [wname, tune] = robust_weight_function (opts.RobustWgtFun, opts.Tune); [beta, R, J, ols_s, adj] = robust_fit (X, y, modelfun, beta0, w, ... wname, tune, opts); [MSE, CovB] = robust_covariance (R, J, adj, ols_s, wname, tune, n, p); else if (! strcmp (opts.ErrorModel, "constant")) [beta, R, J, extraw] = errormodel_fit (X, y, modelfun, beta0, w, opts); else [beta, R, J] = lm_fit (X, y, modelfun, beta0, w, opts); extraw = ones (n, 1); endif ## Effective per-observation weights combine the user weights (which may be ## a function of the fitted values) with any error-model weights. if (is_function_handle (opts.Weights)) w = check_weights (opts.Weights (modelfun (beta, X)), n); endif weff = w .* extraw; ## Mean squared error and CovB = MSE * inv (J' * Weff * J). if (dfe > 0) MSE = sum (weff .* R .^ 2) / dfe; else MSE = NaN; endif Jw = sqrt (weff) .* J; CovB = MSE * pinv (Jw' * Jw); CovB = (CovB + CovB') / 2; # symmetrise against round-off endif ErrorModelInfo = error_model_info (opts, MSE, p); endfunction ## --------------------------------------------------------------------------- ## Generalized least squares for a non-constant error model. The error weights ## depend on the fitted values and are refreshed each outer iteration. ## proportional: var_i = phi * mu_i^2 -> weight 1 / mu_i^2 ## combined: var_i = phi * (a + |mu_i|)^2 -> weight 1 / (a + |mu_i|)^2 function [beta, R, J, ew] = errormodel_fit (X, y, modelfun, beta0, wuser, opts) if (isempty (wuser)) wuser = ones (numel (y), 1); endif beta = beta0; ew = ones (numel (y), 1); for outer = 1:opts.MaxIter ewprev = ew; [beta, R, J] = lm_fit (X, y, modelfun, beta, wuser .* ew, opts); mu = y - R; ew = errormodel_weights (opts.ErrorModel, mu); if (max (abs (ew - ewprev) ./ (ewprev + eps)) < opts.TolX) break; endif endfor endfunction ## --------------------------------------------------------------------------- ## Per-observation error-model weights from the current fitted values MU. function ew = errormodel_weights (errmodel, mu) am = abs (mu); am(am < eps) = eps; # guard against division by zero switch (errmodel) case "proportional" ew = 1 ./ am .^ 2; case "combined" ## a is a small offset relative to the scale of the fitted values. a = 0.5 * mean (am); ew = 1 ./ (a + am) .^ 2; otherwise ew = ones (size (mu)); endswitch endfunction ## --------------------------------------------------------------------------- ## Plain (optionally weighted) Levenberg-Marquardt nonlinear least squares. ## Returns the coefficients, raw residuals, and Jacobian at the solution. function [beta, R, J] = lm_fit (X, y, modelfun, beta0, w, opts) if (isempty (w)) w = ones (numel (y), 1); endif sw = sqrt (w); beta = beta0; yhat = modelfun (beta, X); R = y - yhat; sse = sum (w .* R .^ 2); lambda = 1e-2; # Marquardt damping J = nlfun_jacobian (modelfun, beta, X, opts.DerivStep); for iter = 1:opts.MaxIter Jw = sw .* J; Rw = sw .* R; JtJ = Jw' * Jw; Jtr = Jw' * Rw; diagJtJ = diag (diag (JtJ)); ## Try damped steps, increasing lambda until the objective decreases. stepok = false; for inner = 1:20 A = JtJ + lambda * diagJtJ; delta = pinv (A) * Jtr; bnew = beta + delta; rnew = y - modelfun (bnew, X); ssenew = sum (w .* rnew .^ 2); if (isfinite (ssenew) && ssenew < sse) stepok = true; break; endif lambda = lambda * 10; endfor if (! stepok) break; # cannot improve: converged/stalled endif ## Accept the step and relax the damping. relchg = abs (ssenew - sse) / (sse + eps); stepsz = max (abs (delta) ./ (abs (beta) + eps)); beta = bnew; R = rnew; sse = ssenew; lambda = max (lambda / 10, 1e-12); J = nlfun_jacobian (modelfun, beta, X, opts.DerivStep); if (relchg < opts.TolFun || stepsz < opts.TolX) break; endif endfor endfunction ## --------------------------------------------------------------------------- ## Robust iteratively reweighted least squares around LM_FIT, following the ## robustfit scheme: residuals are leverage-adjusted before scaling, and the ## leverage (from the ordinary fit) is held fixed. Returns the ordinary-fit ## scale OLS_S and the leverage adjustment ADJ for the covariance step. function [beta, R, J, ols_s, adj] = robust_fit (X, y, modelfun, beta0, w, ... wname, tune, opts) if (isempty (w)) w = ones (numel (y), 1); endif n = numel (y); p = numel (beta0); ## Ordinary weighted fit: OLS scale and the (fixed) leverage adjustment. [beta, R, J] = lm_fit (X, y, modelfun, beta0, w, opts); ols_s = norm (R) / sqrt (max (n - p, 1)); Jw = sqrt (w) .* J; h = min (0.9999, diag (Jw * pinv (Jw' * Jw) * Jw')); adj = 1 ./ sqrt (1 - h); for iter = 1:opts.MaxIter betaprev = beta; s = madsigma (R .* adj, p, y); rw = max (robustwfun (R .* adj ./ (s * tune), wname), 0); [beta, R, J] = lm_fit (X, y, modelfun, beta, w .* rw, opts); if (all (abs (beta - betaprev) <= sqrt (eps) * max (abs (beta), ... abs (betaprev)))) break; endif endfor endfunction ## --------------------------------------------------------------------------- ## Street-Carroll-Ruppert robust coefficient covariance (matching robustfit). ## The scale blends the ordinary-fit scale with the robust scale, and ## CovB = s^2 * inv (J' * J) at the robust solution. function [MSE, CovB] = robust_covariance (R, J, adj, ols_s, wname, tune, n, p) radj = R .* adj; mad_s = madsigma (radj, p); if (mad_s == 0) mad_s = 1; endif z = radj ./ (mad_s * tune); [w, psi, psip] = robustwfun (z, wname); if (all (w == 1)) robust_s = ols_s; else K = 1 + (p / n) * var (psip) / mean (psip) ^ 2; robust_s = tune * mad_s * sqrt (mean (psi .^ 2)) / mean (psip) * K; endif s = sqrt ((p ^ 2 * ols_s ^ 2 + n * robust_s ^ 2) / (n + p ^ 2)); s = max (s, robust_s); MSE = s ^ 2; CovB = MSE * pinv (J' * J); CovB = (CovB + CovB') / 2; endfunction ## --------------------------------------------------------------------------- ## Validate the robust weight function name and settle its tuning constant. ## The weight function itself and the tuning constants live in the shared ## private helpers robustwfun and robusttune, which robustfit also uses. function [wname, tune] = robust_weight_function (name, tune) names = {"andrews", "bisquare", "cauchy", "fair", "huber", "logistic", ... "talwar", "welsch"}; if (! ischar (name)) error ("nlinfit: RobustWgtFun must be a character vector."); endif if (! any (strcmp (lower (name), names))) error ("nlinfit: unknown RobustWgtFun '%s'.", name); endif wname = lower (name); if (isempty (tune)) tune = robusttune (wname); endif endfunction ## --------------------------------------------------------------------------- ## Assemble the ErrorModelInfo structure returned as the sixth output. The ## Scheffe dimension for simultaneous prediction is p for a non-constant error ## model and p + 1 for the constant model (the extra observation error term). function info = error_model_info (opts, MSE, p) info = struct (); info.ErrorModel = opts.ErrorModel; info.ErrorParameters = sqrt (MSE); if (strcmp (opts.ErrorModel, "proportional")) info.ErrorVariance = @(x) MSE * abs (x) .^ 2; else info.ErrorVariance = @(x) MSE * ones (size (x, 1), 1); endif info.MSE = MSE; info.ScheffeSimPred = p + strcmp (opts.ErrorModel, "constant"); info.WeightFunction = is_function_handle (opts.Weights); info.FixedWeights = (! isempty (opts.Weights) ... && ! is_function_handle (opts.Weights)); info.RobustWeightFunction = ! isempty (opts.RobustWgtFun); endfunction ## --------------------------------------------------------------------------- ## Validate a numeric weight vector. function w = check_weights (w, n) if (! (isnumeric (w) && isvector (w) && isreal (w))) error ("nlinfit: WEIGHTS must be a real numeric vector."); endif w = w(:); if (numel (w) != n) error ("nlinfit: WEIGHTS must have one element per observation."); endif if (any (w < 0) || any (isnan (w))) error ("nlinfit: WEIGHTS must be nonnegative."); endif endfunction ## --------------------------------------------------------------------------- ## Parse an optional leading statset structure followed by Name/Value pairs. function opts = parse_options (args) ## Defaults. The statset-backed options come from statset ("nlinfit"); the ## rest are nlinfit's own Name/Value arguments. sopts = statset ("nlinfit"); opts = struct ("Weights", [], "ErrorModel", "constant", ... "ErrorParameters", []); ## An options structure may lead the Name/Value pairs. if (! isempty (args) && isstruct (args{1})) sopts = merge_options (sopts, args{1}); args = args(2:end); endif if (mod (numel (args), 2) != 0) error ("nlinfit: Name/Value arguments must come in pairs."); endif for k = 1:2:numel (args) name = args{k}; val = args{k+1}; if (! ischar (name)) error ("nlinfit: parameter names must be character vectors."); endif switch (lower (name)) case 'weights' opts.Weights = val; case 'errormodel' opts.ErrorModel = lower (val); case 'errorparameters' opts.ErrorParameters = val; case 'robustwgtfun' sopts.RobustWgtFun = val; case 'tune' sopts.Tune = val; case 'options' sopts = merge_options (sopts, val); otherwise error ("nlinfit: unknown parameter name '%s'.", name); endswitch endfor if (! any (strcmp (opts.ErrorModel, ... {'constant', 'proportional', 'combined'}))) error ("nlinfit: unknown ErrorModel '%s'.", opts.ErrorModel); endif ## Resolve the robust weight function. 'RobustWgtFun' selects it on its own, ## whatever 'Robust' says, and wins when both are given. The legacy ## 'WgtFun' applies only when 'Robust' is "on", so statset ("nlinfit") -- ## which carries WgtFun "bisquare" beside Robust "off" -- leaves the fit ## unweighted. All three measured against R2024a. opts.RobustWgtFun = sopts.RobustWgtFun; if (isempty (opts.RobustWgtFun) && ischar (sopts.Robust) ... && strcmpi (sopts.Robust, "on")) opts.RobustWgtFun = sopts.WgtFun; if (isempty (opts.RobustWgtFun)) opts.RobustWgtFun = "bisquare"; endif endif opts.Tune = sopts.Tune; opts.MaxIter = sopts.MaxIter; opts.TolFun = sopts.TolFun; opts.TolX = sopts.TolX; opts.DerivStep = sopts.DerivStep; endfunction ## --------------------------------------------------------------------------- ## Merge a statset-style structure into the options, under nlinfit's own name. function sopts = merge_options (sopts, s) if (! (isstruct (s) && isscalar (s))) error ("nlinfit: OPTIONS must be a structure."); endif sopts = statset (sopts, s); endfunction %!demo %! ## Fit an exponential growth model y = b1 * exp (b2 * x). %! x = [1:10]'; %! y = [2.1;2.9;4.2;5.3;7.1;9.4;12.8;16.5;22.1;29.8]; %! modelfun = @(b, x) b(1) .* exp (b(2) .* x); %! beta = nlinfit (x, y, modelfun, [1; 0.3]) %!demo %! ## Robust fitting downweights a gross outlier (5th point corrupted). %! x = [1:10]'; %! y = [2.1;2.9;4.2;5.3;7.1;9.4;12.8;16.5;22.1;29.8]; %! y(5) = 30; %! modelfun = @(b, x) b(1) .* exp (b(2) .* x); %! beta_ols = nlinfit (x, y, modelfun, [1; 0.3]); %! beta_rob = nlinfit (x, y, modelfun, [1; 0.3], 'RobustWgtFun', 'bisquare'); %! [beta_ols, beta_rob] %!shared x, y, modelfun, beta0 %! x = [1;2;3;4;5;6;7;8;9;10]; %! y = [2.1;2.9;4.2;5.3;7.1;9.4;12.8;16.5;22.1;29.8]; %! modelfun = @(b, x) b(1) .* exp (b(2) .* x); %! beta0 = [1; 0.3]; ## The literals below are R2024a's own values, measured, and each tolerance ## states how closely ours agree with them. %!test %! [beta, R, J, CovB, MSE] = nlinfit (x, y, modelfun, beta0); %! assert_equal (beta, [1.683747024946374; 0.286911087203926], 1e-6); %! assert_equal (MSE, 0.029221494299720, 1e-9); %! assert_equal (sqrt (diag (CovB)), ... %! [0.035194898750254; 0.002350913062791], 1e-6); %!test %! ## The returned Jacobian equals the analytic model Jacobian. %! [beta, R, J] = nlinfit (x, y, modelfun, beta0); %! Ja = [exp(beta(2).*x), beta(1).*x.*exp(beta(2).*x)]; %! assert_equal (J, Ja, -1e-4); %!test %! ## Proportional error model reweights by 1 / mu^2 and changes the fit. %! [beta, R, J, CovB, MSE, EMI] = nlinfit (x, y, modelfun, beta0, ... %! "ErrorModel", "proportional"); %! assert_equal (beta, [1.650639591; 0.289917266], 1e-6); %! assert_equal (MSE, 9.988359e-4, 1e-9); %! assert_equal (EMI.ErrorModel, "proportional"); %! assert_equal (EMI.ScheffeSimPred, 2); %!test %! ## Constant error model: ErrorModelInfo fields. %! [beta, R, J, CovB, MSE, EMI] = nlinfit (x, y, modelfun, beta0); %! assert_equal (EMI.ErrorModel, "constant"); %! assert_equal (EMI.ScheffeSimPred, 3); %! assert_equal (EMI.ErrorParameters, 0.170942956, 1e-7); %!test %! ## Observation weights. %! w = (1:10)'; %! [beta, R, J, CovB, MSE] = nlinfit (x, y, modelfun, beta0, "Weights", w); %! assert_equal (beta, [1.677485713; 0.287323059], 1e-6); %! assert_equal (MSE, 0.178838470, 1e-6); %!test %! ## Robust fitting resists a gross outlier: the estimate stays close to the %! ## clean-data fit, far closer than ordinary least squares. Coefficients, %! ## MSE, and covariance match MATLAB's nlinfit (Street-Carroll-Ruppert scale). %! yo = y; yo(5) = 30; %! [br, Rr, Jr, Cr, Mr] = nlinfit (x, yo, modelfun, beta0, ... %! "RobustWgtFun", "bisquare"); %! bo = nlinfit (x, yo, modelfun, beta0); %! bc = nlinfit (x, y, modelfun, beta0); %! assert_equal (max (abs (br - bc)) < max (abs (bo - bc)), true); %! assert_equal (br, [1.679154218; 0.287198922], 1e-5); %! assert_equal (Mr, 15.892255, 1e-2); %! assert_equal (Cr, [0.671544, -0.044220; -0.044220, 0.003012], 1e-3); %!test %! ## The huber robust fit also matches MATLAB's coefficients. %! yo = y; yo(5) = 30; %! bh = nlinfit (x, yo, modelfun, beta0, "RobustWgtFun", "huber"); %! assert_equal (bh, [1.716586997; 0.284865506], 1e-5); ## Test input validation %!error nlinfit (1, 2, @(b, x) x) %!error nlinfit (1, 2, 3, 4) %!error ... %! nlinfit ([1;2], [1;2], @(b, x) b(1) * ones (2, 1), "a") %!error ... %! nlinfit ([1;2], [1;2], @(b, x) b(1) * ones (2, 1), 1, "foo", 1) %!error ... %! nlinfit ([1;2], [1;2], @(b, x) b(1) * ones (2, 1), 1, "ErrorModel", "bad") ## An options structure naming no robust weight function leaves the fit ## unweighted: the legacy WgtFun acts only when Robust is "on" (R2024a) %!test %! x = (1:10)'; %! y = [2.1; 3.9; 6.2; 7.8; 10.1; 12.2; 13.8; 16.1; 18.0; 100.0]; %! modelfun = @(b, xx) b(1) + b(2) * xx; %! bols = nlinfit (x, y, modelfun, [0; 1]); %! bdef = nlinfit (x, y, modelfun, [0; 1], statset ("nlinfit")); %! boff = nlinfit (x, y, modelfun, [0; 1], statset (statset ("nlinfit"), ... %! "Robust", "off", ... %! "WgtFun", "huber")); %! assert_equal (bols, [-15.959999998553135; 6.359999999774407], 1e-8); %! assert_equal (bdef, bols, 1e-12); %! assert_equal (boff, bols, 1e-12); ## Robust "on" activates WgtFun, and RobustWgtFun wins where both are given %!test %! x = (1:10)'; %! y = [2.1; 3.9; 6.2; 7.8; 10.1; 12.2; 13.8; 16.1; 18.0; 100.0]; %! modelfun = @(b, xx) b(1) + b(2) * xx; %! opts = statset ("nlinfit"); %! bon = nlinfit (x, y, modelfun, [0; 1], statset (opts, "Robust", "on")); %! bhub = nlinfit (x, y, modelfun, [0; 1], statset (opts, "Robust", "on", ... %! "WgtFun", "huber")); %! bboth = nlinfit (x, y, modelfun, [0; 1], statset (opts, "Robust", "on", ... %! "WgtFun", "bisquare", ... %! "RobustWgtFun", "huber")); %! assert_equal (bon, [0.040259809225918; 1.996768212418451], 1e-7); %! assert_equal (bhub, [-0.032803877294096; 2.016488145372473], 1e-7); %! assert_equal (bboth, bhub, 1e-12); %!error ... %! nlinfit ([1;2], [1;2], @(b, x) b(1) * ones (2, 1), 1, "RobustWgtFun", "bad") statistics-release-1.9.2/inst/Regression/nlparci.m000066400000000000000000000135531524624707500222610ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{ci} =} nlparci (@var{beta}, @var{resid}, @qcode{'covar'}, @var{CovB}) ## @deftypefnx {statistics} {@var{ci} =} nlparci (@var{beta}, @var{resid}, @qcode{'jacobian'}, @var{J}) ## @deftypefnx {statistics} {@var{ci} =} nlparci (@dots{}, @qcode{'alpha'}, @var{alpha}) ## ## Confidence intervals for the coefficients of a nonlinear regression. ## ## @code{@var{ci} = nlparci (@var{beta}, @var{resid}, @qcode{'covar'}, ## @var{CovB})} returns the @math{100 (1 - @var{alpha})%} confidence intervals ## for the fitted coefficients @var{beta} of a nonlinear regression, given the ## residual vector @var{resid} and the estimated coefficient covariance matrix ## @var{CovB} (both produced by @code{nlinfit}). @var{ci} is a ## @math{p}-by-@math{2} matrix whose rows are the lower and upper bounds for the ## corresponding coefficient. ## ## @code{@var{ci} = nlparci (@var{beta}, @var{resid}, @qcode{'jacobian'}, ## @var{J})} instead derives the coefficient covariance from the Jacobian ## @var{J} and the residuals. A legacy positional form @code{nlparci ## (@var{beta}, @var{resid}, @var{J})} is also accepted. ## ## The confidence level defaults to @math{95%}; pass @code{@qcode{'alpha'}, ## @var{alpha}} for a @math{100 (1 - @var{alpha})%} interval. The intervals use ## Student's @math{t} distribution with @code{numel (@var{resid}) - numel ## (@var{beta})} degrees of freedom. ## ## @seealso{nlinfit, nlpredci, fitnlm, NonLinearModel} ## @end deftypefn function ci = nlparci (beta, resid, varargin) if (nargin < 3) print_usage (); endif if (! (isnumeric (beta) && isvector (beta) && isreal (beta))) error ("nlparci: BETA must be a real numeric vector."); endif if (! (isnumeric (resid) && isreal (resid))) error ("nlparci: RESID must be a real numeric vector."); endif beta = beta(:); resid = resid(:); p = numel (beta); dfe = numel (resid) - p; if (dfe <= 0) error ("nlparci: not enough residuals to estimate the coefficients."); endif ## Parse the covariance source and the optional confidence level. The third ## argument may be a bare Jacobian (legacy) or a 'covar'/'jacobian' keyword. alpha = 0.05; CovB = []; J = []; args = varargin; if (! ischar (args{1})) J = args{1}; args = args(2:end); endif if (mod (numel (args), 2) != 0) error ("nlparci: Name/Value arguments must come in pairs."); endif for k = 1:2:numel (args) switch (lower (args{k})) case 'covar' CovB = args{k+1}; case 'jacobian' J = args{k+1}; case 'alpha' alpha = args{k+1}; otherwise error ("nlparci: unknown parameter name '%s'.", args{k}); endswitch endfor if (! (isscalar (alpha) && isreal (alpha) && alpha > 0 && alpha < 1)) error ("nlparci: ALPHA must be a scalar in the range (0, 1)."); endif ## Standard errors from either the supplied covariance or the Jacobian. if (! isempty (CovB)) se = sqrt (diag (CovB)); elseif (! isempty (J)) rmse = sqrt (sum (resid .^ 2) / dfe); se = rmse * sqrt (diag (pinv (J' * J))); else error ("nlparci: a covariance matrix or a Jacobian is required."); endif delta = se * tinv (1 - alpha / 2, dfe); ci = [beta - delta, beta + delta]; endfunction %!demo %! ## 95% confidence intervals for the coefficients of an exponential fit. %! x = [1:10]'; %! y = [2.1;2.9;4.2;5.3;7.1;9.4;12.8;16.5;22.1;29.8]; %! modelfun = @(b, x) b(1) .* exp (b(2) .* x); %! [beta, R, J, CovB] = nlinfit (x, y, modelfun, [1; 0.3]); %! ci = nlparci (beta, R, 'covar', CovB) %!shared beta, R, J, CovB %! x = [1;2;3;4;5;6;7;8;9;10]; %! y = [2.1;2.9;4.2;5.3;7.1;9.4;12.8;16.5;22.1;29.8]; %! modelfun = @(b, xx) b(1) .* exp (b(2) .* xx); %! [beta, R, J, CovB] = nlinfit (x, y, modelfun, [1; 0.3]); ## The interval below is R2024a's own, measured; ours agrees to 1e-5, the ## difference coming through nlinfit's covariance rather than from nlparci. %!test %! ci = nlparci (beta, R, "covar", CovB); %! assert_equal (ci, [1.602587442890197, 1.764906607002551; ... %! 0.281489871959626, 0.292332302448227], 1e-5); %!test %! ## The Jacobian and covariance forms agree. %! ci1 = nlparci (beta, R, "covar", CovB); %! ci2 = nlparci (beta, R, "jacobian", J); %! assert_equal (ci1, ci2, 1e-8); %!test %! ## The legacy positional Jacobian form matches the named form. %! ci = nlparci (beta, R, J); %! assert_equal (ci, nlparci (beta, R, "jacobian", J), 1e-12); %!test %! ## A 90% interval is narrower than the default 95% interval. %! ci95 = nlparci (beta, R, "covar", CovB); %! ci90 = nlparci (beta, R, "covar", CovB, "alpha", 0.10); %! assert_equal (all (diff (ci90, 1, 2) < diff (ci95, 1, 2), 'all'), true); ## Test input validation %!error nlparci (1, 2) %!error ... %! nlparci ([1;2], [1;2;3], "foo", 1) %!error ... %! nlparci ([1;2], [1;2;3], "covar", eye (2), "alpha", 2) %!error ... %! nlparci ([1;2], [1;2;3], "alpha", 0.05) %!error nlparci ([1;2], 1, "covar", eye (2)) statistics-release-1.9.2/inst/Regression/nlpredci.m000066400000000000000000000221771524624707500224330ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{ypred}, @var{delta}] =} nlpredci (@var{modelfun}, @var{X}, @var{beta}, @var{resid}, @qcode{'Jacobian'}, @var{J}) ## @deftypefnx {statistics} {[@var{ypred}, @var{delta}] =} nlpredci (@var{modelfun}, @var{X}, @var{beta}, @var{resid}, @qcode{'Covar'}, @var{CovB}) ## @deftypefnx {statistics} {[@var{ypred}, @var{delta}] =} nlpredci (@dots{}, @var{Name}, @var{Value}) ## ## Confidence intervals for predictions of a nonlinear regression. ## ## @code{[@var{ypred}, @var{delta}] = nlpredci (@var{modelfun}, @var{X}, ## @var{beta}, @var{resid}, @qcode{'Jacobian'}, @var{J})} returns the predicted ## responses @var{ypred} of the model @code{@var{modelfun} (@var{beta}, ## @var{X})} at the new predictor values @var{X}, together with the half-widths ## @var{delta} of the @math{100 (1 - @var{alpha})%} confidence intervals, so ## that @code{@var{ypred} - @var{delta}} and @code{@var{ypred} + @var{delta}} ## bound the response. @var{beta}, @var{resid} (the residuals) and @var{J} (the ## Jacobian) come from @code{nlinfit}. ## ## Instead of the Jacobian, an estimated coefficient covariance may be supplied ## with @code{@qcode{'Covar'}, @var{CovB}}. The following @qcode{Name}/ ## @qcode{Value} pairs are also accepted: ## ## @multitable @columnfractions 0.2 0.78 ## @headitem @var{Name} @tab @var{Value} ## @item @qcode{'MSE'} @tab The mean squared error from @code{nlinfit}, required ## with @qcode{'Covar'} for observation (prediction) intervals. ## @item @qcode{'PredOpt'} @tab @qcode{'curve'} (default) for confidence ## intervals on the fitted curve, or @qcode{'observation'} for prediction ## intervals on a new observation. ## @item @qcode{'SimOpt'} @tab @qcode{'off'} (default) for pointwise intervals, ## or @qcode{'on'} for simultaneous (Scheffe) intervals. ## @item @qcode{'Alpha'} @tab The significance level; the interval has ## confidence @math{100 (1 - @var{alpha})%} (default @var{alpha} = 0.05). ## @end multitable ## ## @subheading Algorithm ## ## Each half-width is @code{@var{delta} = c * sqrt (v)}. The variance @code{v} ## of the fitted curve is @code{diag (@var{Jnew} * @var{V} * @var{Jnew}')}, ## where @var{V} is the coefficient covariance (either @var{CovB}, or ## @code{@var{MSE} * inv (@var{J}' * @var{J})} when a Jacobian is supplied) and ## @var{Jnew} is the Jacobian of @var{modelfun} at @var{X}; an ## @qcode{'observation'} interval adds the error variance @var{MSE} to @code{v}. ## The critical value @code{c} is the Student's @math{t} quantile at ## @math{1 - @var{alpha}/2} with the error degrees of freedom for a pointwise ## interval, or the Scheffe value @code{sqrt (k * finv (1 - @var{alpha}, k, ## dfe))} for a simultaneous interval, where @math{k} is the number of ## coefficients (plus one for an observation interval). ## ## @seealso{nlinfit, nlparci, fitnlm, NonLinearModel} ## @end deftypefn function [ypred, delta] = nlpredci (modelfun, X, beta, resid, varargin) if (nargin < 5) print_usage (); endif if (! is_function_handle (modelfun)) error ("nlpredci: MODELFUN must be a function handle."); endif if (! (isnumeric (beta) && isvector (beta) && isreal (beta))) error ("nlpredci: BETA must be a real numeric vector."); endif if (! (isnumeric (resid) && isreal (resid))) error ("nlpredci: RESID must be a real numeric vector."); endif beta = beta(:); resid = resid(:); p = numel (beta); dfe = numel (resid) - p; if (dfe <= 0) error ("nlpredci: not enough residuals to estimate the error variance."); endif ## Parse the covariance source and options. A bare Jacobian may follow the ## residuals (legacy), otherwise 'Jacobian'/'Covar' keywords select it. J = []; CovB = []; MSE = []; predopt = 'curve'; simopt = 'off'; alpha = 0.05; args = varargin; if (! ischar (args{1})) J = args{1}; args = args(2:end); endif if (mod (numel (args), 2) != 0) error ("nlpredci: Name/Value arguments must come in pairs."); endif for k = 1:2:numel (args) switch (lower (args{k})) case 'jacobian' J = args{k+1}; case 'covar' CovB = args{k+1}; case 'mse' MSE = args{k+1}; case 'predopt' predopt = lower (args{k+1}); case 'simopt' simopt = lower (args{k+1}); case 'alpha' alpha = args{k+1}; case {'weights', 'errormodelinfo'} ## accepted for compatibility; not needed for the covariance path otherwise error ("nlpredci: unknown parameter name '%s'.", args{k}); endswitch endfor if (! (isscalar (alpha) && isreal (alpha) && alpha > 0 && alpha < 1)) error ("nlpredci: ALPHA must be a scalar in the range (0, 1)."); endif if (! any (strcmp (predopt, {'curve', 'observation'}))) error ("nlpredci: PREDOPT must be 'curve' or 'observation'."); endif if (! any (strcmp (simopt, {'on', 'off'}))) error ("nlpredci: SIMOPT must be 'on' or 'off'."); endif ## Fitted responses and the Jacobian of the model at the new points. ypred = modelfun (beta, X); ypred = ypred(:); Jnew = nlfun_jacobian (modelfun, beta, X, eps ^ (1/3)); ## Coefficient covariance and the error variance for observation intervals. if (! isempty (CovB)) Vbeta = CovB; if (isempty (MSE)) errvar = sum (resid .^ 2) / dfe; else errvar = MSE; endif elseif (! isempty (J)) if (isempty (MSE)) errvar = sum (resid .^ 2) / dfe; else errvar = MSE; endif Vbeta = errvar * pinv (J' * J); else error ("nlpredci: a Jacobian or a covariance matrix is required."); endif ## Variance of the fitted curve, plus the error variance for observations. varpred = sum ((Jnew * Vbeta) .* Jnew, 2); varpred = max (varpred, 0); if (strcmp (predopt, 'observation')) varpred = varpred + errvar; endif ## Critical value: Student's t (pointwise) or Scheffe (simultaneous). The ## Scheffe dimension is the number of coefficients, plus one for a new ## observation's own error term. if (strcmp (simopt, 'on')) nsim = p + strcmp (predopt, 'observation'); crit = sqrt (nsim * finv (1 - alpha, nsim, dfe)); else crit = tinv (1 - alpha / 2, dfe); endif delta = crit * sqrt (varpred); endfunction %!demo %! ## Prediction intervals for an exponential fit at three new x-values. %! x = [1:10]'; %! y = [2.1;2.9;4.2;5.3;7.1;9.4;12.8;16.5;22.1;29.8]; %! modelfun = @(b, x) b(1) .* exp (b(2) .* x); %! [beta, R, J] = nlinfit (x, y, modelfun, [1; 0.3]); %! [ypred, delta] = nlpredci (modelfun, [2.5; 5.5; 8.5], beta, R, 'Jacobian', J) %!shared modelfun, beta, R, J, CovB, MSE, xp %! x = [1;2;3;4;5;6;7;8;9;10]; %! y = [2.1;2.9;4.2;5.3;7.1;9.4;12.8;16.5;22.1;29.8]; %! modelfun = @(b, xx) b(1) .* exp (b(2) .* xx); %! [beta, R, J, CovB, MSE] = nlinfit (x, y, modelfun, [1; 0.3]); %! xp = [2.5; 5.5; 8.5]; ## Values verified against MATLAB's nlpredci. %!test %! [yp, dc] = nlpredci (modelfun, xp, beta, R, "Jacobian", J); %! assert_equal (yp, [3.449741734; 8.158274148; 19.293455050], 1e-6); %! assert_equal (dc, [0.120614865; 0.160334882; 0.171532624], 1e-6); %!test %! ## Observation (prediction) intervals exceed the curve intervals by MSE. %! [yp, dc] = nlpredci (modelfun, xp, beta, R, "Jacobian", J); %! [yp, dp] = nlpredci (modelfun, xp, beta, R, "Jacobian", J, ... %! "PredOpt", "observation"); %! assert_equal (dp, [0.412235094; 0.425555051; 0.429899138], 1e-6); %! assert_equal (all (dp > dc, 'all'), true); %!test %! ## Simultaneous (Scheffe) intervals are wider than the pointwise ones. %! [yp, dc] = nlpredci (modelfun, xp, beta, R, "Jacobian", J); %! [yp, ds] = nlpredci (modelfun, xp, beta, R, "Jacobian", J, "SimOpt", "on"); %! assert_equal (ds, [0.156197123; 0.207634833; 0.222135990], 1e-6); %! assert_equal (all (ds > dc, 'all'), true); %!test %! ## The covariance form matches the Jacobian form. %! [yp, dj] = nlpredci (modelfun, xp, beta, R, "Jacobian", J); %! [yp, dv] = nlpredci (modelfun, xp, beta, R, "Covar", CovB, "MSE", MSE); %! assert_equal (dj, dv, 1e-8); ## Test input validation %!error nlpredci (@(b, x) x, [1;2], [1;2], [1;2]) %!error ... %! nlpredci (1, [1;2], [1;2], [1;2;3], "Jacobian", eye (2)) %!error ... %! nlpredci (@(b, x) x, [1;2], [1;2], [1;2;3], "Jacobian", eye (2), ... %! "PredOpt", "bad") %!error ... %! nlpredci (@(b, x) x, [1;2], [1;2], [1;2;3], "foo", 1) statistics-release-1.9.2/inst/Regression/plsregress.m000066400000000000000000000503121524624707500230140ustar00rootroot00000000000000## Copyright (C) 2012-2019 Fernando Damian Nieuwveldt ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License ## as published by the Free Software Foundation; either version 3 ## of the License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{xload}, @var{yload}] =} plsregress (@var{X}, @var{Y}) ## @deftypefnx {statistics} {[@var{xload}, @var{yload}] =} plsregress (@var{X}, @var{Y}, @var{NCOMP}) ## @deftypefnx {statistics} {[@var{xload}, @var{yload}, @var{xscore}, @var{yscore}, @var{coef}, @var{pctVar}, @var{mse}, @var{stats}] =} plsregress (@var{X}, @var{Y}, @var{NCOMP}) ## @deftypefnx {statistics} {[@var{xload}, @var{yload}, @var{xscore}, @var{yscore}, @var{coef}, @var{pctVar}, @var{mse}, @var{stats}] =} plsregress (@dots{}, @var{Name}, @var{Value}) ## ## Calculate partial least squares regression using SIMPLS algorithm. ## ## @code{plsregress} uses the SIMPLS algorithm, and first centers @var{X} and ## @var{Y} by subtracting off column means to get centered variables. However, ## it does not rescale the columns. To perform partial least squares regression ## with standardized variables, use @code{zscore} to normalize @var{X} and ## @var{Y}. ## ## @code{[@var{xload}, @var{yload}] = plsregress (@var{X}, @var{Y})} computes a ## partial least squares regression of @var{Y} on @var{X}, using @var{NCOMP} ## PLS components, which by default are calculated as ## @qcode{min (size (@var{X}, 1) - 1, size(@var{X}, 2))}, and returns the ## the predictor and response loadings in @var{xload} and @var{yload}, ## respectively. ## @itemize ## @item @var{X} is an @math{N*P} matrix of predictor variables, with rows ## corresponding to observations, and columns corresponding to variables. ## @item @var{Y} is an @math{N*M} response matrix. ## @item @var{xload} is a @math{P*NCOMP} matrix of predictor loadings, where ## each row of @var{xload} contains coefficients that define a linear ## combination of PLS components that approximate the original predictor ## variables. ## @item @var{yload} is an @math{M*NCOMP} matrix of response loadings, where ## each row of @var{yload} contains coefficients that define a linear ## combination of PLS components that approximate the original response ## variables. ## @end itemize ## ## @code{[@var{xload}, @var{yload}] = plsregress (@var{X}, @var{Y}, ## @var{NCOMP})} defines the desired number of PLS components to use in the ## regression. @var{NCOMP}, a scalar positive integer, must not exceed the ## default calculated value. ## ## @code{[@var{xload}, @var{yload}, @var{xscore}, @var{yscore}, @var{coef}, ## @var{pctVar}, @var{mse}, @var{stats}] = plsregress (@var{X}, @var{Y}, ## @var{NCOMP})} also returns the following arguments: ## @itemize ## @item @var{xscore} is an @math{N*NCOMP} orthonormal matrix with the predictor ## scores, i.e., the PLS components that are linear combinations of the ## variables in @var{X}, with rows corresponding to observations and columns ## corresponding to components. ## @item @var{yscore} is an @math{N*NCOMP} orthonormal matrix with the response ## scores, i.e., the linear combinations of the responses with which the PLS ## components @var{xscore} have maximum covariance, with rows corresponding to ## observations and columns corresponding to components. ## @item @var{coef} is a @math{(P+1)*M} matrix with the PLS regression ## coefficients, containing the intercepts in the first row. ## @item @var{pctVar} is a @math{2*NCOMP} matrix containing the percentage of ## the variance explained by the model with the first row containing the ## percentage of explained varianced in @var{X} by each PLS component and the ## second row containing the percentage of explained variance in @var{Y}. ## @item @var{mse} is a @math{2*(NCOMP+1)} matrix containing the estimated mean ## squared errors for PLS models with @qcode{0:@var{NCOMP}} components with the ## first row containing the squared errors for the predictor variables in ## @var{X} and the second row containing the mean squared errors for the ## response variable(s) in @var{Y}. ## @item @var{stats} is a structure with the following fields: ## @itemize ## @item @var{stats}@qcode{.W} is a @math{P*NCOMP} matrix of PLS weights. ## @item @var{stats}@qcode{.T2} is the @math{T^2} statistics for each point in ## @var{xscore}. ## @item @var{stats}@qcode{.Xresiduals} is an @math{N*P} matrix with the ## predictor residuals. ## @item @var{stats}@qcode{.Yresiduals} is an @math{N*M} matrix with the ## response residuals. ## @end itemize ## @end itemize ## ## @code{[@dots{}] = plsregress (@dots{}, @var{Name}, @var{Value}, @dots{})} ## specifies one or more of the following @var{Name}/@var{Value} pairs: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Name} @tab @var{Value} ## @item @qcode{'CV'} @tab The method used to compute @var{mse}. When ## @var{Value} is a positive integer @math{K}, @code{plsregress} uses ## @math{K}-fold cross-validation. Set @var{Value} to a cross-validation ## partition, created using @code{cvpartition}, to use other forms of ## cross-validation. Set @var{Value} to @qcode{'resubstitution'} to use both ## @var{X} and @var{Y} to fit the model and to estimate the mean squared errors, ## without cross-validation. By default, @qcode{@var{Value} = "resubstitution"}. ## @item @qcode{'MCReps'} @tab A positive integer indicating the number of ## Monte-Carlo repetitions for cross-validation. By default, ## @qcode{@var{Value} = 1}. A different @qcode{'MCReps'} value is only ## meaningful when using the @qcode{'HoldOut'} method for cross-validation, ## previously set by a @code{cvpartition} object. If no cross-validation method ## is used, then @qcode{'MCReps'} must be @qcode{1}. ## @end multitable ## ## Further information about the PLS regression can be found at ## @url{https://en.wikipedia.org/wiki/Partial_least_squares_regression} ## ## @subheading References ## @enumerate ## @item ## SIMPLS: An alternative approach to partial least squares regression. ## Chemometrics and Intelligent Laboratory Systems (1993) ## ## @end enumerate ## @end deftypefn function [xload, yload, xscore, yscore, coef, pctVar, mse, stats] = ... plsregress (X, Y, NCOMP, varargin) ## Check input arguments and add defaults if (nargin < 2) error ("plsregress: function called with too few input arguments."); endif if (! isnumeric (X) || ! isnumeric (Y)) error ("plsregress: X and Y must be real matrices."); endif ## Get size of predictor and response inputs [nobs, npred] = size (X); [Yobs, nresp] = size (Y); if (nobs != Yobs) error ("plsregress: X and Y observations mismatch."); endif ## Calculate max number of components NCOMPmax = min (nobs - 1, npred); if (nargin < 3) NCOMP = NCOMPmax; elseif (! isnumeric (NCOMP) || ! isscalar (NCOMP) || NCOMP != fix (NCOMP) || NCOMP <= 0) error ("plsregress: invalid value for NCOMP."); elseif (NCOMP > NCOMPmax) error ("plsregress: NCOMP exceeds maximum components for X."); endif ## Add default optional arguments CV = false; mcreps = 1; ## Parse additional Name-Value pairs while (numel (varargin) > 0) if (strcmpi (varargin{1}, 'cv')) cvarg = varargin{2}; if (isa (cvarg, 'cvpartition')) CV = true; elseif (isscalar (cvarg) && cvarg == fix (cvarg) && cvarg > 0) CV = true; elseif (! strcmpi (cvarg, 'resubstitution')) error ("plsregress: invalid VALUE for 'cv' optional argument."); endif elseif (strcmpi (varargin{1}, 'mcreps')) mcreps = varargin{2}; if (! (isscalar (mcreps) && mcreps == fix (mcreps) && mcreps > 0)) error ("plsregress: invalid VALUE for 'mcreps' optional argument."); endif else error ("plsregress: invalid NAME argument."); endif varargin(1:2) = []; endwhile ## Check MCREPS = 1 when "resubstitution" is set for cross validation if (! CV && mcreps != 1) error (strcat ("plsregress: 'mcreps' must be 1 when 'resubstitution'", ... " is specified for cross validation.")); endif ## Check number of output arguments if (nargout > 8) print_usage (); endif ## Mean centering Data matrix Xmeans = mean (X); X0 = bsxfun (@minus, X, Xmeans); ## Mean centering responses Ymeans = mean (Y); Y0 = bsxfun (@minus, Y, Ymeans); [P, Q, T, U, W] = simpls (X0, Y0, NCOMP); ## Store output arguments xload = P; yload = Q; xscore = T; yscore = U; ## Compute regression coefficients if (nargout > 4) coef = W * Q'; coef = [Ymeans - Xmeans * coef; coef]; endif ## Compute the percent of variance explained for X and Y if (nargout > 5) XVar = sum (abs (xload) .^ 2, 1) ./ sum (sum (abs (X0) .^ 2, 1)); YVar = sum (abs (yload) .^ 2, 1) ./ sum (sum (abs (Y0) .^ 2, 1)); pctVar = [XVar; YVar]; endif ## Estimate the mean squared errors if (nargout > 6) ## Compute MSE by cross-validation if (CV) mse = NaN (2, NCOMP + 1); ## Check crossval method and recalculate max number of components if isa (cvarg, 'cvpartition') type = 'Partition'; NCOMPmax = min (min (cvarg.TrainSize)-1,npred); ts = sum (cvarg.TestSize); else type = 'Kfold'; NCOMPmax = min (floor ((nobs * (cvarg - 1) / cvarg) -1), npred); ts = nobs; endif if (NCOMP > NCOMPmax) warning (strcat ("plsregress: NCOMP exceeds maximum components", ... " for cross validation.")); NCOMP = NCOMPmax; endif ## Create function handle with NCOMP extra argument F = @(xtr, ytr, xte, yte) sseCV (xtr, ytr, xte, yte, NCOMP); ## Apply cross validation sse = crossval (F, X, Y, type, cvarg, 'mcreps', mcreps); ## Compute MSE from the SSEs collected from each cross validation set mse(:,1:NCOMP+1) = reshape (sum (sse, 1) / (ts * mcreps), [2, NCOMP+1]); ## Computed fitted if residuals are requested if (nargout > 7) xfitted = xscore * xload'; yfitted = xscore * yload'; endif ## Compute MSE by resubstitution else mse = zeros (2, NCOMP + 1); ## Model with 0 components mse(1,1) = sum (sum (abs (X0) .^ 2, 2)); mse(2,1) = sum (sum (abs (Y0) .^ 2, 2)); ## Models with 1:NCOMP components for i = 1:NCOMP xfitted = xscore(:,1:i) * xload(:,1:i)'; yfitted = xscore(:,1:i) * yload(:,1:i)'; mse(1,i+1) = sum (sum (abs (X0 - xfitted) .^ 2, 2)); mse(2,i+1) = sum (sum (abs (Y0 - yfitted) .^ 2, 2)); endfor ## Compute the mean of the sum of squares above mse = mse / nobs; endif endif ## Compute stats if (nargout > 7) ## Save weights stats.W = W; ## Compute T-squared stats.T2 = sum (bsxfun (@rdivide, abs (xscore) .^ 2, ... var (xscore, [], 1)) , 2); ## Compute residuals for X and Y stats.Xresiduals = X0 - xfitted; stats.Yresiduals = Y0 - yfitted; endif endfunction ## SIMPLS algorithm function [P, Q, T, U, W] = simpls (X0, Y0, NCOMP) ## Get size of predictor and response inputs [nobs, npred] = size (X0); [Yobs, nresp] = size (Y0); ## Compute covariance S = X0' * Y0; ## Preallocate matrices W = P = V = zeros (npred, NCOMP); T = U = zeros (nobs, NCOMP); Q = zeros (nresp, NCOMP); ## Models with 1:NCOMP components for a = 1:NCOMP [eigvec, eigval] = eig (S' * S); # Y factor weights ## Get max eigenvector domindex = find (diag (eigval) == max (diag (eigval))); q = eigvec(:,domindex); w = S * q; # X block factor weights t = X0 * w; # X block factor scores t = t - mean (t); nt = sqrt (t' * t); # compute norm t = t / nt; w = w / nt; # normalize p = X0' * t; # X block factor loadings q = Y0' * t; # Y block factor loadings u = Y0 * q; # Y block factor scores v = p; ## Ensure orthogonality if (a > 1) v = v - V * (V' * p); u = u - T * (T' * u); endif v = v / sqrt (v' * v); # normalize orthogonal loadings S = S - v * (v' * S); # deflate S wrt loadings V(:,a) = v; ## Store data P(:,a) = p; # Xloads Q(:,a) = q; # Yloads T(:,a) = t; # xscore U(:,a) = u; # Yscores W(:,a) = w; # Weights endfor endfunction ## Helper function for SSE cross-validation function sse = sseCV (XTR, YTR, XTE, YTE, NCOMP) ## Center train data XTRmeans = mean (XTR); YTRmeans = mean (YTR); X0TR = bsxfun (@minus, XTR, XTRmeans); Y0TR = bsxfun (@minus, YTR, YTRmeans); ## Center test data X0TE = bsxfun (@minus, XTE, XTRmeans); Y0TE = bsxfun (@minus, YTE, YTRmeans); ## Fit the full model [xload, yload, ~, ~, W] = simpls (X0TR, Y0TR, NCOMP); XTEscore = X0TE * W; ## Preallocate SSE matrix sse = zeros (2, NCOMP + 1); ## Model with 0 components sse(1,1) = sum (sum (abs (X0TE) .^ 2, 2)); sse(2,1) = sum (sum (abs (Y0TE) .^ 2, 2)); ## Models with 1:NCOMP components for i = 1:NCOMP X0fitted = XTEscore(:,1:i) * xload(:,1:i)'; sse(1,i+1) = sum (sum (abs (X0TE - X0fitted) .^ 2, 2)); Y0fitted = XTEscore(:,1:i) * yload(:,1:i)'; sse(2,i+1) = sum (sum (abs (Y0TE - Y0fitted) .^ 2, 2)); endfor ## crossval collects a row per test set, and the caller reshapes the summed ## rows back to [2, NCOMP+1] sse = sse(:)'; endfunction %!demo %! ## Perform Partial Least-Squares Regression %! %! ## Load the spectra data set and use the near infrared (NIR) spectral %! ## intensities (NIR) as the predictor and the corresponding octave %! ## ratings (octave) as the response. %! load spectra %! %! ## Perform PLS regression with 10 components %! [xload, yload, xscore, yscore, coef, ptcVar] = plsregress (NIR, octane, 10); %! %! ## Plot the percentage of explained variance in the response variable %! ## (PCTVAR) as a function of the number of components. %! plot (1:10, cumsum (100 * ptcVar(2,:)), '-ro'); %! xlim ([1, 10]); %! xlabel ('Number of PLS components'); %! ylabel ('Percentage of Explained Variance in octane'); %! title ('Explained Variance per PLS components'); %! %! ## Compute the fitted response and display the residuals. %! octane_fitted = [ones(size(NIR,1),1), NIR] * coef; %! residuals = octane - octane_fitted; %! figure %! stem (residuals, 'color', 'r', 'markersize', 4, 'markeredgecolor', 'r') %! xlabel ('Observations'); %! ylabel ('Residuals'); %! title ('Residuals in octane''s fitted response'); %!demo %! ## Calculate Variable Importance in Projection (VIP) for PLS Regression %! %! ## Load the spectra data set and use the near infrared (NIR) spectral %! ## intensities (NIR) as the predictor and the corresponding octave %! ## ratings (octave) as the response. Variables with a VIP score greater than %! ## 1 are considered important for the projection of the PLS regression model. %! load spectra %! %! ## Perform PLS regression with 10 components %! [xload, yload, xscore, yscore, coef, pctVar, mse, stats] = ... %! plsregress (NIR, octane, 10); %! %! ## Calculate the normalized PLS weights %! W0 = stats.W ./ sqrt (sum (stats.W.^2,1)); %! %! ## Calculate the VIP scores for 10 components %! nobs = size (xload, 1); %! SS = sum (xscore .^ 2, 1) .* sum (yload .^ 2, 1); %! VIPscore = sqrt (nobs * sum (SS .* (W0 .^ 2), 2) ./ sum (SS, 2)); %! %! ## Find variables with a VIP score greater than or equal to 1 %! VIPidx = find (VIPscore >= 1); %! %! ## Plot the VIP scores %! scatter (1:length (VIPscore), VIPscore, 'xb'); %! hold on %! scatter (VIPidx, VIPscore(VIPidx), 'xr'); %! plot ([1, length(VIPscore)], [1, 1], '--k'); %! hold off %! axis ('tight'); %! xlabel ('Predictor Variables'); %! ylabel ('VIP scores'); %! title ('VIP scores for each predictor variable with 10 components'); ## Test output %!test %! load spectra %! [xload, yload, xscore, yscore, coef, pctVar] = plsregress (NIR, octane, 10); %! xload1_out = [-0.0170, 0.0039, 0.0095, 0.0258, 0.0025, ... %! -0.0075, 0.0000, 0.0018, -0.0027, 0.0020]; %! yload_out = [6.6384, 9.3106, 2.0505, 0.6471, 0.9625, ... %! 0.5905, 0.4244, 0.2437, 0.3516, 0.2548]; %! xscore1_out = [-0.0401, -0.1764, -0.0340, 0.1669, 0.1041, ... %! -0.2067, 0.0457, 0.1565, 0.0706, -0.1471]; %! yscore1_out = [-12.4635, -15.0003, 0.0638, 0.0652, -0.0070, ... %! -0.0634, 0.0062, -0.0012, -0.0151, -0.0173]; %! assert_equal (xload(1,:), xload1_out, 1e-4); %! assert_equal (yload, yload_out, 1e-4); %! assert_equal (xscore(1,:), xscore1_out, 1e-4); %! assert_equal (yscore(1,:), yscore1_out, 1e-4); %!test %! load spectra %! [xload, yload, xscore, yscore, coef, pctVar] = plsregress (NIR, octane, 5); %! xload1_out = [-0.0170, 0.0039, 0.0095, 0.0258, 0.0025]; %! yload_out = [6.6384, 9.3106, 2.0505, 0.6471, 0.9625]; %! xscore1_out = [-0.0401, -0.1764, -0.0340, 0.1669, 0.1041]; %! yscore1_out = [-12.4635, -15.0003, 0.0638, 0.0652, -0.0070]; %! assert_equal (xload(1,:), xload1_out, 1e-4); %! assert_equal (yload, yload_out, 1e-4); %! assert_equal (xscore(1,:), xscore1_out, 1e-4); %! assert_equal (yscore(1,:), yscore1_out, 1e-4); ## Test input validation %!error %! plsregress (1) %!error plsregress (1, 'asd') %!error plsregress (1, {1,2,3}) %!error plsregress ('asd', 1) %!error plsregress ({1,2,3}, 1) %!error ... %! plsregress (ones (20,3), ones (15,1)) %!error ... %! plsregress (ones (20,3), ones (20,1), 0) %!error ... %! plsregress (ones (20,3), ones (20,1), -5) %!error ... %! plsregress (ones (20,3), ones (20,1), 3.2) %!error ... %! plsregress (ones (20,3), ones (20,1), [2, 3]) %!error ... %! plsregress (ones (20,3), ones (20,1), 4) %!error ... %! plsregress (ones (20,3), ones (20,1), 3, 'cv', 4.5) %!error ... %! plsregress (ones (20,3), ones (20,1), 3, 'cv', -1) %!error ... %! plsregress (ones (20,3), ones (20,1), 3, 'cv', 'somestring') %!error ... %! plsregress (ones (20,3), ones (20,1), 3, 'cv', 3, 'mcreps', 2.2) %!error ... %! plsregress (ones (20,3), ones (20,1), 3, 'cv', 3, 'mcreps', -2) %!error ... %! plsregress (ones (20,3), ones (20,1), 3, 'cv', 3, 'mcreps', [1, 2]) %!error ... %! plsregress (ones (20,3), ones (20,1), 3, 'Name', 3, 'mcreps', 1) %!error ... %! plsregress (ones (20,3), ones (20,1), 3, 'cv', 3, 'Name', 1) %!error ... %! plsregress (ones (20,3), ones (20,1), 3, 'mcreps', 2) %!error ... %! plsregress (ones (20,3), ones (20,1), 3, 'cv', 'resubstitution', 'mcreps', 2) %!test %! ## A single output returns the predictor loadings, as MATLAB allows; %! ## fewer than two outputs used to be refused outright. %! X = [1, 2, 3; 2, 3, 5; 3, 5, 8; 4, 7, 11; 5, 11, 16; 6, 13, 19]; %! Y = [1; 2; 3; 4; 5; 6]; %! xl = plsregress (X, Y, 2); %! [xl2, yl2] = plsregress (X, Y, 2); %! assert_equal (xl, xl2); %! assert_equal (size (xl), [3, 2]); statistics-release-1.9.2/inst/Regression/private/000077500000000000000000000000001524624707500221165ustar00rootroot00000000000000statistics-release-1.9.2/inst/Regression/private/__glmefit__.m000066400000000000000000000334341524624707500245260ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{M} =} __glmefit__ (@var{X}, @var{y}, @var{Z}, @var{G}, @var{distr}, @var{link}, @var{method}) ## ## Estimation core for generalized linear mixed-effects models. Undocumented ## internal function for @code{fitglme}/@code{GeneralizedLinearMixedModel}. ## ## Fits the model by penalized quasi-likelihood (PQL): it alternates between ## forming the generalized-linear working response and iterative weights, and ## fitting a weighted linear mixed model to that pseudo-response, until ## convergence. @var{method} @qcode{"mpl"} uses a maximum-likelihood inner fit, ## @qcode{"rempl"} a restricted-maximum-likelihood inner fit; @qcode{"laplace"} ## and @qcode{"approximatelaplace"} use the same point estimates but report the ## Laplace-approximated marginal log-likelihood. ## ## @end deftypefn function M = __glmefit__ (X, y, Z, G, distr, link, method) n = rows (X); p = columns (X); method = lower (method); distr = lower (distr); link = lower (link); ## Build the expanded random design + per-term bookkeeping (cf. __lmefit__). [Zx, qk, nlev, levels, gidx] = expand_random (Z, G, n); ## Fixed dispersion for binomial/poisson; estimated for normal/gamma/invgauss. fixed_disp = any (strcmp (distr, {"binomial", "poisson"})); isreml = strcmp (method, "rempl"); ## --- initialise beta from a plain GLM (no random effects) --- beta = glm_irls (X, y, distr, link); b = zeros (columns (Zx), 1); theta = init_theta (qk); disp0 = 1; ## --- PQL outer iterations --- for outer = 1:100 eta = X * beta + Zx * b; [mu, dmu] = inv_link (eta, link); v = var_fun (mu, distr); w = dmu .^ 2 ./ v; ## iterative weights (disp = 1) zt = eta + (y - mu) ./ dmu; ## working response ## inner weighted (RE)ML fit of the pseudo-response zt, Var(e)=disp/w [theta, disp0] = inner_fit (theta, X, zt, Zx, qk, nlev, w, isreml, ... fixed_disp); [beta_n, b, Psi, covbeta, V] = inner_solve (theta, X, zt, Zx, qk, nlev, ... w, disp0); if (max (abs (beta_n - beta)) < 1e-8) beta = beta_n; break; endif beta = beta_n; endfor ## --- final quantities --- eta = X * beta + Zx * b; [mu, dmu] = inv_link (eta, link); v = var_fun (mu, distr); w = dmu .^ 2 ./ v; zt = eta + (y - mu) ./ dmu; ## pseudo (RE)ML log-likelihood of the final weighted linear mixed model ## (REML uses n - p in place of n in the constant term). if (isreml), ndf = n - p; else, ndf = n; endif pll = -0.5 * (ndf * log (2*pi) ... + weighted_dev (theta, X, zt, Zx, qk, nlev, w, isreml, disp0)); if (any (strcmp (method, {"laplace", "approximatelaplace"}))) loglik = laplace_loglik (X, y, beta, Zx, qk, nlev, gidx, Psi, distr, link); else loglik = pll; endif M = struct (); M.beta = beta; M.b = b; M.Psi = Psi; M.dispersion = disp0; M.covbeta = covbeta; M.loglik = loglik; M.mu = mu; M.fitted = mu; M.resid_raw = y - mu; M.resid_pearson = (y - mu) ./ sqrt (var_fun (mu, distr)); M.Zx = Zx; M.qk = qk; M.nlev = nlev; M.levels = levels; M.gidx = gidx; M.n = n; M.p = p; M.dfe = n - p; M.distr = distr; M.link = link; M.method = method; endfunction ## ---- link functions (canonical) ---- function [mu, dmu] = inv_link (eta, link) switch (link) case "logit" mu = 1 ./ (1 + exp (-eta)); dmu = mu .* (1 - mu); case "log" mu = exp (eta); dmu = mu; case "identity" mu = eta; dmu = ones (size (eta)); otherwise error ("__glmefit__: unsupported link '%s'.", link); endswitch endfunction function v = var_fun (mu, distr) switch (distr) case "binomial" v = mu .* (1 - mu); case "poisson" v = mu; case "normal" v = ones (size (mu)); otherwise error ("__glmefit__: unsupported distribution '%s'.", distr); endswitch endfunction ## plain GLM by iteratively reweighted least squares (no random effects) function beta = glm_irls (X, y, distr, link) beta = zeros (columns (X), 1); for it = 1:100 eta = X * beta; [mu, dmu] = inv_link (eta, link); w = dmu .^ 2 ./ var_fun (mu, distr); z = eta + (y - mu) ./ dmu; bn = (X' * (w .* X)) \ (X' * (w .* z)); if (max (abs (bn - beta)) < 1e-10) beta = bn; break; endif beta = bn; endfor endfunction ## ---- expanded random design (shared with the LMM engine's convention) ---- function [Zx, qk, nlev, levels, gidx] = expand_random (Z, G, n) nt = numel (Z); Zx = []; qk = zeros (1, nt); nlev = zeros (1, nt); levels = cell (1, nt); gidx = cell (1, nt); for k = 1:nt qk(k) = columns (Z{k}); [lev, ~, gi] = unique (G{k}(:)); nlev(k) = numel (lev); levels{k} = lev; gidx{k} = gi; blk = zeros (n, qk(k) * nlev(k)); for l = 1:nlev(k) blk (gi == l, (l-1)*qk(k) + (1:qk(k))) = Z{k}(gi == l, :); endfor Zx = [Zx, blk]; endfor endfunction ## ---- weighted mixed-model inner solver / objective ---- function [theta, disp0] = inner_fit (theta0, X, z, Zx, qk, nlev, w, ... isreml, fixed_disp) obj = @(th) weighted_dev (th, X, z, Zx, qk, nlev, w, isreml, 1); ## The weighted deviance is differentiable in theta and its gradient is ## closed form, so hand it over rather than let the optimiser difference an ## objective whose curvature in the variance components is far below the ## step a finite difference has to take. See __lmefit__ for the same ## treatment of the linear case. opts = optimset ("TolX", 1e-10, "TolFun", 1e-10, "MaxFunEvals", 2000, ... "Display", "off", "GradObj", "on"); theta = fminunc (obj, theta0, opts); if (fixed_disp) disp0 = 1; else disp0 = profile_dispersion (theta, X, z, Zx, qk, nlev, w, isreml); endif endfunction ## Weighted -2*log-likelihood at the covariance parameters theta, and its ## gradient. Only Zx*D*Zx' in V depends on theta, so dV/dtheta is ## Zx*(dD/dtheta)*Zx'; beta drops out because it is the GLS minimiser, and ## D = L*L' gives dD = E_ij*L' + L*E_ij', so every entry is 2*(S*L)(i,j) for ## the matching accumulated S. The dispersion is held at one here and ## profiled afterwards, so there is no scale term to differentiate through. function [dev, grad] = weighted_dev (theta, X, z, Zx, qk, nlev, w, isreml, ... disp0) n = rows (X); p = columns (X); V = build_V (theta, qk, nlev, Zx, w, disp0); [Rc, flag] = chol (V); if (flag != 0) dev = Inf; grad = zeros (size (theta)); return; endif ## V is Rc'*Rc, so X'*inv (V)*X is (Rc'\X)'*(Rc'\X): whitening once halves ## the triangular solves and makes XtViX symmetric by construction. W = Rc' \ X; zz = Rc' \ z; XtViX = W' * W; beta = W \ zz; r = z - X * beta; u = Rc' \ r; dev = 2*sum (log (diag (Rc))) + u' * u; if (isreml) Rx = chol (XtViX); dev += 2 * sum (log (diag (Rx))); endif if (nargout < 2) return; endif ## One further solve carries the gradient, all of it in the random effects ## dimension: Vv'*Vv is Zx'*inv (V)*Zx, Vv'*u is Zx'*inv (V)*r, Vv'*W is ## Zx'*inv (V)*X. Vv = Rc' \ Zx; c = Vv' * u; B = Vv' * W; if (isreml) T = Rx' \ B'; endif grad = zeros (size (theta)); off = 0; col = 0; for k = 1:numel (qk) q = qk(k); m = q*(q+1)/2; Lk = tril_from_theta (theta(off+(1:m)), q); Sa = zeros (q); Sc = zeros (q); Sp = zeros (q); for l = 1:nlev(k) sel = col + (1:q); col += q; Vs = Vv(:,sel); Sa += Vs' * Vs; cs = c(sel); Sc += cs * cs'; if (isreml) Ts = T(:,sel); Sp += Ts' * Ts; endif endfor Ga = 2 * (Sa * Lk); Gc = 2 * (Sc * Lk); if (isreml) Gp = 2 * (Sp * Lk); else Gp = zeros (q); endif idx = 0; for j = 1:q for i = j:q idx += 1; grad(off+idx) = Ga(i,j) - Gc(i,j) - Gp(i,j); endfor endfor off += m; endfor endfunction ## The dispersion at the fitted covariance parameters: the weighted residual ## sum of squares over its degrees of freedom. Whitened, as weighted_dev is, ## rather than through an explicit inverse of V. function d = profile_dispersion (theta, X, z, Zx, qk, nlev, w, isreml) n = rows (X); p = columns (X); V = build_V (theta, qk, nlev, Zx, w, 1); Rc = chol (V); W = Rc' \ X; zz = Rc' \ z; beta = W \ zz; r = z - X * beta; u = Rc' \ r; if (isreml) d = (u' * u) / (n - p); else d = (u' * u) / n; endif endfunction function [beta, b, Psi, covbeta, V] = inner_solve (theta, X, z, Zx, qk, ... nlev, w, disp0) V = build_V (theta, qk, nlev, Zx, w, disp0); ## Whitened, as weighted_dev and profile_dispersion are: V is Rc'*Rc, so ## X'*inv (V)*X is (Rc'\X)'*(Rc'\X) and beta is the least squares solution ## of the whitened system. inv (V) is never formed; the BLUPs need ## inv (V)*r in full, which is the one place both solves are used. Rc = chol (V); W = Rc' \ X; zz = Rc' \ z; XtViX = W' * W; beta = W \ zz; Dfull = build_Dfull (theta, qk, nlev); Vir = Rc \ (Rc' \ (z - X * beta)); b = Dfull * (Zx' * Vir); ## The covariance of the fixed effects is genuinely an inverse, but it is ## the inverse of a matrix already factored, so take it from the factor. Rx = chol (XtViX); covbeta = Rx \ (Rx' \ eye (columns (X))); ## per-term covariance matrices Psi = cell (1, numel (qk)); off = 0; for k = 1:numel (qk) m = qk(k)*(qk(k)+1)/2; Lk = tril_from_theta (theta(off+(1:m)), qk(k)); Psi{k} = Lk * Lk'; off += m; endfor endfunction ## V = Zx * blkdiag(Psi) * Zx' + disp * diag(1/w) function V = build_V (theta, qk, nlev, Zx, w, disp0) n = rows (Zx); Dfull = build_Dfull (theta, qk, nlev); ## Zx * Dfull * Zx' is symmetric in exact arithmetic but not bitwise, since ## the two products are separate BLAS calls. 'chol' reads one triangle, so ## a threaded or blocked BLAS can disagree with a reference one on which ## factor comes out. Symmetrise before it is handed to 'chol'. V = Zx * Dfull * Zx' + disp0 * diag (1 ./ w); V = (V + V') / 2; endfunction function Dfull = build_Dfull (theta, qk, nlev) blocks = {}; off = 0; for k = 1:numel (qk) m = qk(k)*(qk(k)+1)/2; Lk = tril_from_theta (theta(off+(1:m)), qk(k)); Dk = Lk * Lk'; for l = 1:nlev(k), blocks{end+1} = Dk; endfor off += m; endfor Dfull = blkdiag (blocks{:}); endfunction function L = tril_from_theta (th, q) L = zeros (q, q); idx = 0; for j = 1:q for i = j:q idx += 1; L(i, j) = th(idx); endfor endfor endfunction function theta0 = init_theta (qk) theta0 = []; for k = 1:numel (qk) for j = 1:qk(k) for i = j:qk(k) theta0(end+1) = 0.1 * (i == j); endfor endfor endfor theta0 = theta0(:); endfunction ## ---- Laplace-approximated marginal log-likelihood (single grouping term) ---- ## Per group: logL_g = log p(y_g | b_hat) - 0.5*b_hat'*inv(P)*b_hat ## - 0.5*log|I + P*(Z_g'*W*Z_g)|, with W the GLM weights at ## the mode b_hat. The last two terms are combined into a single log-det that ## stays finite as the random-effect variance goes to zero. function ll = laplace_loglik (X, y, beta, Zx, qk, nlev, gidx, Psi, distr, link) if (numel (qk) != 1) ll = NaN; ## only single-term supported return; endif q = qk(1); P = Psi{1}; ## A negligible random-effect variance pins the group modes at zero, so the ## marginal likelihood is the plain GLM likelihood (as MATLAB reports). degenerate = (max (abs (diag (P))) < 1e-8); if (! degenerate), Pi = inv (P); endif gi = gidx{1}; ll = 0; for g = 1:nlev(1) idx = (gi == g); Xg = X(idx, :); Zg = Zx(idx, (g-1)*q + (1:q)); yg = y(idx); bg = zeros (q, 1); if (! degenerate) for it = 1:100 ## Newton for the group mode eta = Xg * beta + Zg * bg; [mu, dmu] = inv_link (eta, link); W = dmu .^ 2 ./ var_fun (mu, distr); gr = Zg' * (yg - mu) - Pi * bg; ## canonical-link score H = -(Zg' * (W .* Zg)) - Pi; step = H \ gr; bg = bg - step; if (max (abs (step)) < 1e-10), break; endif endfor endif eta = Xg * beta + Zg * bg; [mu, dmu] = inv_link (eta, link); W = dmu .^ 2 ./ var_fun (mu, distr); if (degenerate) pen = 0; else pen = 0.5 * (bg' * (P \ bg)); endif ll += log_pmf (yg, mu, distr) - pen ... - 0.5 * logdet_spd (eye (q) + P * (Zg' * (W .* Zg))); endfor endfunction function lp = log_pmf (y, mu, distr) switch (distr) case "binomial" lp = sum (y .* log (mu) + (1 - y) .* log (1 - mu)); case "poisson" lp = sum (y .* log (mu) - mu - gammaln (y + 1)); case "normal" lp = sum (-0.5 * (y - mu) .^ 2 - 0.5 * log (2*pi)); otherwise lp = NaN; endswitch endfunction function ld = logdet_spd (A) ## A is built from products and is not bitwise symmetric; see build_V. A = (A + A') / 2; ld = 2 * sum (log (diag (chol (A)))); endfunction statistics-release-1.9.2/inst/Regression/private/__lme_dfsatt__.m000066400000000000000000000104301524624707500252100ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{df} =} __lme_dfsatt__ (@var{X}, @var{y}, @var{Zx}, @var{qk}, @var{nlev}, @var{Psi}, @var{sigma2}, @var{method}, @var{L}) ## ## Satterthwaite denominator degrees of freedom for the single-row contrasts in ## the rows of @var{L}. Undocumented internal helper for ## @code{LinearMixedModel}. ## ## Uses the delta method: for a contrast @var{l}, the denominator variance is ## @code{l*C*l'} with @code{C} the fixed-effect covariance; its sampling ## variance is @code{g'*Veta*g} where @code{g} is the gradient of @code{l*C*l'} ## with respect to the covariance parameters @var{eta} (the lower-triangle ## entries of each @var{Psi} block followed by @var{sigma2}) and @code{Veta} is ## the asymptotic covariance of @var{eta} (twice the inverse of the numerical ## Hessian of the -2 log-(RE)likelihood). The df is ## @code{2*(l*C*l')^2 / (g'*Veta*g)}. ## ## @end deftypefn function df = __lme_dfsatt__ (X, y, Zx, qk, nlev, Psi, sigma2, method, L) is_reml = strcmp (upper (method), "REML"); n = rows (X); p = columns (X); ## Pack the natural covariance parameters: vech(Psi_k) per term, then sigma2. eta = []; for k = 1:numel (qk) eta = [eta; vech_lower(Psi{k})]; endfor eta = [eta; sigma2]; ne = numel (eta); dev = @(e) reml_deviance (e, qk, nlev, X, y, Zx, n, p, is_reml); cov = @(e) fixed_cov (e, qk, nlev, X, Zx, n); ## Asymptotic covariance of eta: 2 * inv (Hessian of the -2 log-likelihood). hs = 1e-4 * max (abs (eta), 1); H = zeros (ne); for i = 1:ne for j = i:ne ei = zeros (ne, 1); ei(i) = hs(i); ej = zeros (ne, 1); ej(j) = hs(j); H(i,j) = (dev (eta+ei+ej) - dev (eta+ei-ej) ... - dev (eta-ei+ej) + dev (eta-ei-ej)) / (4 * hs(i) * hs(j)); H(j,i) = H(i,j); endfor endfor Veta = 2 * inv (H); C0 = cov (eta); df = zeros (rows (L), 1); for r = 1:rows (L) l = L(r,:); lcl = l * C0 * l'; g = zeros (ne, 1); for i = 1:ne ei = zeros (ne, 1); ei(i) = hs(i); g(i) = (l * cov (eta+ei) * l' - l * cov (eta-ei) * l') / (2 * hs(i)); endfor df(r) = 2 * lcl^2 / (g' * Veta * g); endfor endfunction ## Column-major lower-triangle entries of a symmetric matrix. function v = vech_lower (A) q = rows (A); v = []; for j = 1:q for i = j:q v(end+1, 1) = A(i, j); endfor endfor endfunction ## Rebuild the marginal covariance V = Zx*Dabs*Zx' + sigma2*I from eta. function V = build_V (eta, qk, nlev, Zx, n) blocks = {}; off = 0; for k = 1:numel (qk) q = qk(k); m = q*(q+1)/2; Pk = zeros (q, q); idx = 0; for j = 1:q for i = j:q idx += 1; Pk(i, j) = eta(off+idx); Pk(j, i) = eta(off+idx); endfor endfor for l = 1:nlev(k) blocks{end+1} = Pk; endfor off += m; endfor sigma2 = eta(end); V = Zx * blkdiag (blocks{:}) * Zx' + sigma2 * eye (n); endfunction function d = reml_deviance (eta, qk, nlev, X, y, Zx, n, p, is_reml) V = build_V (eta, qk, nlev, Zx, n); [Rc, flag] = chol (V); if (flag != 0) d = Inf; return; endif ViX = Rc \ (Rc' \ X); Viy = Rc \ (Rc' \ y); XtViX = X' * ViX; beta = XtViX \ (X' * Viy); r = y - X * beta; rVir = r' * (Rc \ (Rc' \ r)); logdetV = 2 * sum (log (diag (Rc))); if (is_reml) d = logdetV + rVir + 2*sum (log (diag (chol (XtViX)))) + (n-p)*log (2*pi); else d = logdetV + rVir + n*log (2*pi); endif endfunction function C = fixed_cov (eta, qk, nlev, X, Zx, n) V = build_V (eta, qk, nlev, Zx, n); C = inv (X' * (V \ X)); endfunction statistics-release-1.9.2/inst/Regression/private/__lmefit__.m000066400000000000000000000230261524624707500243530ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{M} =} __lmefit__ (@var{X}, @var{y}, @var{Z}, @var{G}, @var{method}) ## ## Estimation core for linear mixed-effects models. Undocumented internal ## function for @code{fitlmematrix}/@code{fitlme}/@code{LinearMixedModel}. ## ## Fits @code{y = X*beta + Z*b + e} with @code{b ~ N(0, Psi)} and ## @code{e ~ N(0, sigma2*I)} by maximum likelihood (@var{method} = @qcode{"ML"}) ## or restricted maximum likelihood (@qcode{"REML"}). @var{Z} and @var{G} are ## cell arrays, one entry per grouping term: @var{Z}@{k@} is the @var{n}-by-q_k ## random-effects design and @var{G}@{k@} the @var{n}-by-1 grouping index of ## term k. Each grouping term has a full (unstructured) q_k-by-q_k covariance, ## shared across the levels of that term. ## ## Returns a struct with the fitted quantities: @code{beta}, @code{Psi} (cell, ## one covariance per term), @code{sigma2}, @code{b} (BLUPs), @code{loglik}, ## @code{covbeta}, the profiled covariance parameters @code{theta}, and the ## bookkeeping needed to rebuild the random design (@code{Zx}, @code{qk}, ## @code{nlev}, @code{levels}). ## ## @end deftypefn function M = __lmefit__ (X, y, Z, G, method) if (nargin < 5 || isempty (method)) method = "ML"; endif method = upper (method); is_reml = strcmp (method, "REML"); n = rows (X); p = columns (X); nt = numel (Z); ## Build the expanded random design Zx (n-by-N, N = sum_k q_k*nlev_k) and the ## per-term bookkeeping. Column block for (term k, level l) holds the rows of ## Z{k} that belong to level l and zeros elsewhere. Zx = []; qk = zeros (1, nt); nlev = zeros (1, nt); levels = cell (1, nt); gidx = cell (1, nt); for k = 1:nt qk(k) = columns (Z{k}); [lev, ~, gi] = unique (G{k}(:)); nlev(k) = numel (lev); levels{k} = lev; gidx{k} = gi; blk = zeros (n, qk(k) * nlev(k)); for l = 1:nlev(k) rows_l = (gi == l); cols_l = (l-1)*qk(k) + (1:qk(k)); blk(rows_l, cols_l) = Z{k}(rows_l, :); endfor Zx = [Zx, blk]; endfor ## theta layout: lower-triangular Cholesky entries of the *relative* ## covariance D_k = L_k*L_k' (Psi_k = sigma2*D_k) per term, concatenated. theta0 = init_theta (qk); obj = @(th) profiled_deviance (th, X, y, Zx, qk, nlev, n, p, is_reml); ## The profiled deviance is differentiable in theta and the gradient is ## closed form, so give it to the optimiser rather than let it difference. ## A finite difference takes a step near 1e-8 on an objective whose ## curvature in the variance components is around 1e-13, which makes the ## search path, and the estimate it stops at, sensitive to the last bits of ## the linear algebra and so to the BLAS in use. opts = optimset ("TolX", 1e-10, "TolFun", 1e-10, "MaxFunEvals", 2000, ... "MaxIter", 1000, "Display", "off", "GradObj", "on"); [theta, dev] = fminunc (obj, theta0, opts); ## Recover everything at the optimum. Dfull = build_Dfull (theta, qk, nlev); ## Zx * Dfull * Zx' is symmetric in exact arithmetic but not bitwise, since ## the two products are separate BLAS calls. 'chol' reads one triangle, so ## which one it reads decides the factor and a threaded or blocked BLAS can ## disagree with a reference one. Symmetrise before every factorisation. Mrel = eye (n) + Zx * Dfull * Zx'; Mrel = (Mrel + Mrel') / 2; Rc = chol (Mrel); ## Whitened once, as in profiled_deviance above. Mir is still formed in ## full because the BLUPs need inv (Mrel)*r and not just its norm. W = Rc' \ X; z = Rc' \ y; XtMiX = W' * W; beta = W \ z; r = y - X * beta; u = Rc' \ r; Mir = Rc \ u; rMir = u' * u; if (is_reml) sigma2 = rMir / (n - p); else sigma2 = rMir / n; endif ## BLUPs and covariance of the fixed effects. The covariance is genuinely ## an inverse, but XtMiX is symmetric positive definite and W'*W already ## has its factor, so take it from there rather than from a general inverse. b = Dfull * (Zx' * Mir); Rx = chol (XtMiX); covbeta = sigma2 * (Rx \ (Rx' \ eye (p))); loglik = -0.5 * dev; ## Per-term covariance matrices Psi_k = sigma2 * D_k. Psi = cell (1, nt); off = 0; for k = 1:nt m = qk(k)*(qk(k)+1)/2; Lk = tril_from_theta (theta(off+(1:m)), qk(k)); Psi{k} = sigma2 * (Lk * Lk'); off += m; endfor M = struct (); M.beta = beta; M.Psi = Psi; M.sigma2 = sigma2; M.b = b; M.loglik = loglik; M.covbeta = covbeta; M.theta = theta; M.method = method; M.Zx = Zx; M.qk = qk; M.nlev = nlev; M.levels = levels; M.gidx = gidx; M.fitted = X * beta + Zx * b; ## conditional fitted values M.fitted_marg = X * beta; ## marginal (fixed-effects-only) fit M.resid = y - M.fitted; ## raw conditional residuals M.n = n; M.p = p; M.dfe = n - p; endfunction ## Profiled -2*log-likelihood at the covariance parameters theta, and its ## gradient. With M = I + Zx*D*Zx' and nu = n or n-p, the deviance is ## nu*log (rMir/nu) + logdet (M) [+ logdet (X'*inv (M)*X)] + const, and each ## term differentiates through dM/dtheta = Zx*(dD/dtheta)*Zx'. Because beta ## is the GLS minimiser, its own dependence on theta drops out. Every ## derivative then contracts to the random effects dimension: with ## V = Rc'\Zx, the three quantities needed are V'*V, V'*u and V'*W, and since ## D = L*L' gives dD = E_ij*L' + L*E_ij', each gradient entry is 2*(S*L)(i,j) ## for the matching accumulated S. function [dev, grad] = profiled_deviance (theta, X, y, Zx, qk, nlev, n, p, ... is_reml) Dfull = build_Dfull (theta, qk, nlev); Mrel = eye (n) + Zx * Dfull * Zx'; Mrel = (Mrel + Mrel') / 2; [Rc, flag] = chol (Mrel); if (flag != 0) dev = Inf; grad = zeros (size (theta)); return; endif ## Whiten once rather than solving twice. Mrel is Rc'*Rc, so ## X'*inv (Mrel)*X is (Rc'\X)'*(Rc'\X): three triangular solves here where ## six were needed, and XtMiX comes out symmetric to the last bit by ## construction rather than needing to be symmetrised. beta is then the ## least squares solution of the whitened system, which avoids forming the ## normal equations and squaring the condition number with them. W = Rc' \ X; z = Rc' \ y; XtMiX = W' * W; beta = W \ z; r = y - X * beta; u = Rc' \ r; rMir = u' * u; logdetM = 2 * sum (log (diag (Rc))); if (is_reml) nu = n - p; Rx = chol (XtMiX); ldXMiX = 2 * sum (log (diag (Rx))); dev = nu*log (rMir/nu) + logdetM + rMir/(rMir/nu) + ldXMiX + nu*log (2*pi); else nu = n; dev = nu*log (rMir/nu) + logdetM + rMir/(rMir/nu) + nu*log (2*pi); endif if (nargout < 2) return; endif ## One further triangular solve carries the whole gradient: V'*V is ## Zx'*inv (M)*Zx, V'*u is Zx'*inv (M)*r, and V'*W is Zx'*inv (M)*X, so ## nothing works in the observation dimension beyond this point. V = Rc' \ Zx; c = V' * u; B = V' * W; if (is_reml) T = Rx' \ B'; endif grad = zeros (size (theta)); off = 0; col = 0; for k = 1:numel (qk) q = qk(k); m = q*(q+1)/2; Lk = tril_from_theta (theta(off+(1:m)), q); Sa = zeros (q); Sc = zeros (q); Sp = zeros (q); for l = 1:nlev(k) S = col + (1:q); col += q; Vs = V(:,S); Sa += Vs' * Vs; cs = c(S); Sc += cs * cs'; if (is_reml) Ts = T(:,S); Sp += Ts' * Ts; endif endfor ## dD = E_ij*L' + L*E_ij', so tr (dD*S) and v'*dD*v are both 2*(S*L)(i,j) ## for symmetric S. logdet (M) rises with it, rMir and logdet (X'MiX) ## fall. Ga = 2 * (Sa * Lk); Gc = 2 * (Sc * Lk); if (is_reml) Gp = 2 * (Sp * Lk); else Gp = zeros (q); endif idx = 0; for j = 1:q for i = j:q idx += 1; grad(off+idx) = -nu * Gc(i,j) / rMir + Ga(i,j) - Gp(i,j); endfor endfor off += m; endfor endfunction ## Block-diagonal relative covariance: D_k repeated over the nlev_k levels. function Dfull = build_Dfull (theta, qk, nlev) blocks = {}; off = 0; for k = 1:numel (qk) m = qk(k)*(qk(k)+1)/2; Lk = tril_from_theta (theta(off+(1:m)), qk(k)); Dk = Lk * Lk'; for l = 1:nlev(k) blocks{end+1} = Dk; endfor off += m; endfor Dfull = blkdiag (blocks{:}); endfunction ## Lower-triangular q-by-q factor from its q*(q+1)/2 entries (column-major ## lower triangle). function L = tril_from_theta (th, q) L = zeros (q, q); idx = 0; for j = 1:q for i = j:q idx += 1; L(i, j) = th(idx); endfor endfor endfunction ## Starting covariance parameters: identity relative factors (D_k = I). function theta0 = init_theta (qk) theta0 = []; for k = 1:numel (qk) L = eye (qk(k)); th = []; for j = 1:qk(k) for i = j:qk(k) th(end+1) = L(i, j); endfor endfor theta0 = [theta0, th]; endfor theta0 = theta0(:); endfunction statistics-release-1.9.2/inst/Regression/private/build_design.m000066400000000000000000000041501524624707500247240ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{X_design} =} build_design (@var{terms}, @var{X_enc}) ## ## Build a model design matrix from a terms matrix and an encoded predictor ## matrix. ## ## @var{X_enc} is the @math{n}-by-@math{p} encoded predictor matrix (categorical ## columns already expanded to indicator variables) and @var{terms} is an ## @math{m}-by-(@math{p}+1) matrix whose @math{t}-th row gives the exponents of ## each encoded predictor in the @math{t}-th model term (the trailing column, ## reserved for the response, is ignored). A row of all zeros denotes the ## intercept. ## ## The returned @var{X_design} is @math{n}-by-@math{m}; column @math{t} is the ## element-wise product of the encoded predictors raised to their exponents in ## term @math{t}. ## ## This helper is shared by the @code{LinearModel} and ## @code{GeneralizedLinearModel} classes. ## ## @end deftypefn function X_design = build_design (terms, X_enc) n_obs = rows (X_enc); n_coef = rows (terms); p_enc = columns (X_enc); X_design = zeros (n_obs, n_coef); for t = 1:n_coef term_row = terms(t, 1:p_enc); col_t = ones (n_obs, 1); for j = find (term_row != 0) col_t = col_t .* (X_enc(:, j) .^ term_row(j)); endfor X_design(:, t) = col_t; endfor endfunction statistics-release-1.9.2/inst/Regression/private/encode_categorical.m000066400000000000000000000076361524624707500261020ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{X_enc}, @var{enc_names}, @var{cat_info}] =} encode_categorical (@var{X_num}, @var{cat_cols}, @var{pred_names}, @var{cat_levels}, @var{has_intercept}) ## ## Encode categorical predictors as reference-coded indicator (dummy) variables. ## ## @var{X_num} is the @math{n}-by-@math{p} numeric predictor matrix (categorical ## columns hold 1-based level codes), @var{cat_cols} is a logical vector marking ## the categorical columns, @var{pred_names} is the cell array of predictor ## names, and @var{cat_levels} is a cell array whose @math{j}-th entry lists the ## level labels of predictor @math{j} (empty to infer them from the data). ## ## @var{has_intercept} states whether the model carries an intercept and ## defaults to true. ## ## Numeric predictors are copied through unchanged. Each categorical predictor ## with @math{k} levels expands to @math{k - 1} indicator columns (the first ## level is the omitted reference), named @qcode{@var{name}_@var{level}}. ## ## Without an intercept the @emph{first} categorical predictor takes its place ## and is given all @math{k} indicator columns, which is the cell-means ## parameterisation that dropping the intercept asks for. Any further ## categorical predictor stays reference coded: giving two of them a full set of ## indicators would make the design rank deficient, since each set sums to the ## intercept column. ## ## @var{X_enc} is the encoded design matrix, @var{enc_names} the cell array of ## its column names, and @var{cat_info} a structure with fields @qcode{names} ## and @qcode{levels} recording the categorical predictors and the level labels ## used, so the same encoding can be reproduced for new data. ## ## This helper is shared by the @code{LinearModel} and ## @code{GeneralizedLinearModel} classes. ## ## @end deftypefn function [X_enc, enc_names, cat_info] = encode_categorical ( ... X_num, cat_cols, pred_names, cat_levels, has_intercept) if (nargin < 5) has_intercept = true; endif ## Set once the intercept's place has been taken, whether by an actual ## intercept or by the first categorical predictor standing in for it. reference_coded = has_intercept; X_enc = zeros (rows (X_num), 0); enc_names = {}; cat_info.names = {}; cat_info.levels = {}; for j = 1:numel (pred_names) if (! cat_cols(j)) X_enc = [X_enc, X_num(:, j)]; enc_names = [enc_names, pred_names{j}]; else levels_j = cat_levels{j}; if (isempty (levels_j)) uvals = sort (unique (X_num(isfinite (X_num(:,j)), j))); levels_j = strtrim (cellstr (num2str (uvals(:)))); endif n_lev = numel (levels_j); if (reference_coded) first_lev = 2; else first_lev = 1; reference_coded = true; endif for L = first_lev:n_lev dummy = double (X_num(:, j) == L); X_enc = [X_enc, dummy]; enc_names = [enc_names, [pred_names{j}, '_', char(levels_j{L})]]; endfor cat_info.names{end+1} = pred_names{j}; cat_info.levels{end+1} = levels_j; endif endfor endfunction statistics-release-1.9.2/inst/Regression/private/encodednames_to_row.m000066400000000000000000000053541524624707500263210ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{map}, @var{pow}] =} encodednames_to_row (@var{col_names}, @var{pred_names}, @var{cat_info}) ## ## Map each encoded design column back onto the predictor it came from. ## ## @var{col_names} lists the names of the columns to be mapped, @var{pred_names} ## the model's predictors, and @var{cat_info} the structure of categorical names ## and levels used to encode them. ## ## @var{map} is a row vector holding, for each encoded column, the index of its ## predictor in @var{pred_names}, or zero when no predictor claims it. A ## categorical predictor's indicator columns are named @qcode{@var{name}_@var{level}}, ## so a column is matched by the longest predictor name it starts with, which ## keeps @qcode{ab_x} with @qcode{ab} rather than with @qcode{a}. ## ## @var{pow} is a row vector holding the exponent each column name carries, so ## that a column named @qcode{u^2} maps onto predictor @qcode{u} with an ## exponent of 2. It is 1 for a name carrying no exponent. ## ## This helper is shared by the @code{LinearModel} and ## @code{GeneralizedLinearModel} classes. ## ## @end deftypefn function [m, pow] = encodednames_to_row (col_names, pred_names, cat_info) m = zeros (1, numel (col_names)); pow = ones (1, numel (col_names)); for k = 1:numel (col_names) nm = col_names{k}; ## A column named 'u^2' belongs to predictor 'u' and carries the exponent. tok = regexp (nm, '^(.*)\^(\d+)$', 'tokens'); if (! isempty (tok)) nm = tok{1}{1}; pow(k) = str2double (tok{1}{2}); endif idx = find (strcmp (pred_names, nm), 1); if (isempty (idx)) idx = 0; best_len = 0; for j = 1:numel (pred_names) pj = pred_names{j}; if (numel (nm) > numel (pj) + 1 ... && strncmp (nm, [pj, '_'], numel (pj) + 1) ... && numel (pj) > best_len) idx = j; best_len = numel (pj); endif endfor endif m(k) = idx; endfor endfunction statistics-release-1.9.2/inst/Regression/private/getlinkfunctions.m000066400000000000000000000143201524624707500256620ustar00rootroot00000000000000## Copyright (C) 2025 Andreas Bertsatos ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{flink}, @var{dlink}, @var{ilink}, @var{errmsg}] =} getlinkfunctions (@var{linkArg}) ## ## Return the link function and its derivative and inverse base. ## ## @end deftypefn function [flink, dlink, ilink, errmsg] = getlinkfunctions (linkArg) flink = dlink = ilink = []; errmsg = ''; ## linkArg is a scalar structure if (isstruct (linkArg)) if (! isscalar (linkArg)) errmsg = 'structure with custom link functions must be a scalar.'; return; endif rf = {'Link', 'Derivative', 'Inverse'}; if (! all (ismember (rf, fieldnames (linkArg)))) errmsg = ['structure with custom link functions requires', ... ' the fields ''Link'', ''Derivative'', and ''Inverse''.']; return; endif if (ischar (linkArg.Link) && ! isempty (which (linkArg.Link))) flink = @(mu) feval (linkArg.Link, mu); elseif (isa (linkArg.Link, 'function_handle')) flink = linkArg.Link; else errmsg = ['bad ''Link'' function in custom link function structure.']; return; endif errmsg = testLinkFunction (flink, 'Link'); if (! isempty (errmsg)) return; endif if (ischar (linkArg.Derivative) && ! isempty (which (linkArg.Derivative))) dlink = @(mu) feval (linkArg.Derivative, mu); elseif (isa (linkArg.Derivative, 'function_handle')) dlink = linkArg.Derivative; else errmsg = ['bad ''Derivative'' function in custom link function structure.']; return; endif errmsg = testLinkFunction (dlink, 'Derivative'); if (! isempty (errmsg)) return; endif if (ischar (linkArg.Inverse) && ! isempty (which (linkArg.Inverse))) ilink = @(mu) feval (linkArg.Inverse, mu); elseif (isa (linkArg.Inverse, 'function_handle')) ilink = linkArg.Inverse; else errmsg = ['bad ''Inverse'' function in custom link function structure.']; return; endif errmsg = testLinkFunction (ilink, 'Inverse'); if (! isempty (errmsg)) return; endif ## linkArg is a cell array elseif (iscell (linkArg)) if (numel (linkArg) != 3) errmsg = 'cell array with custom link functions must have three elements.'; return; endif if (isa (linkArg{1}, 'function_handle')) flink = linkArg{1}; else errmsg = ['bad ''Link'' function in custom link function cell array.']; return; endif errmsg = testLinkFunction (flink, 'Link'); if (! isempty (errmsg)) return; endif if (isa (linkArg{2}, 'function_handle')) dlink = linkArg{2}; else errmsg = ['bad ''Derivative'' function in custom link function cell array.']; return; endif errmsg = testLinkFunction (dlink, 'Derivative'); if (! isempty (errmsg)) return; endif if (isa (linkArg{3}, 'function_handle')) ilink = linkArg{3}; else errmsg = ['bad ''Inverse'' function in custom link function cell array.']; return; endif errmsg = testLinkFunction (ilink, 'Inverse'); if (! isempty (errmsg)) return; endif ## linkArg is a scalar value elseif (isnumeric (linkArg)) if (! (isscalar (linkArg) && isfinite (linkArg) && isreal (linkArg))) errmsg = ['numeric input for custom link function', ... ' must be a finite real scalar value.']; return; endif flink = @(mu) mu .^ linkArg; dlink = @(mu) linkArg .* mu .^ (linkArg - 1); ilink = @(eta) eta .^ (1 / linkArg); ## linkArg is character vector elseif (ischar (linkArg)) if (! isvector (linkArg)) errmsg = 'canonical link function name must be a character vector.'; return; endif supported_link_functions = {'identity', 'log', 'logit', 'probit', ... 'loglog', 'comploglog', 'reciprocal'}; if (! any (strcmpi (linkArg, supported_link_functions))) errmsg = sprintf ("canonical link function '%s' is not supported.", ... linkArg); return; endif ## Select a canonical link function switch (linkArg) case 'identity' flink = @(mu) mu; dlink = @(mu) 1; ilink = @(eta) eta; case 'log' flink = @(mu) log (mu); dlink = @(mu) 1 ./ (mu); ilink = @(eta) exp (eta); case 'logit' flink = @(mu) log (mu ./ (1 - mu)); dlink = @(mu) 1 ./ (mu .* (1 - mu)); ilink = @(eta) 1 ./ (1 + exp (- eta)); case 'probit' flink = @(mu) norminv (mu); dlink = @(mu) 1 ./ normpdf (norminv (mu)); ilink = @(eta) normcdf (eta); case 'loglog' flink = @(mu) log (- log (mu)); dlink = @(mu) 1 ./ (mu .* log (mu)); ilink = @(eta) exp (- exp (eta)); case 'comploglog' flink = @(mu) log (- log1p (- mu)); dlink = @(mu) 1 ./ - ((1 - mu) .* log1p (- mu)); ilink = @(eta) -expm1 (- exp (eta)); case 'reciprocal' flink = @(mu) 1 ./ mu; dlink = @(mu) -1 ./ (mu .^ 2); ilink = @(eta) 1 ./ (eta); endswitch else errmsg = 'invalid value for custom link function.'; endif endfunction function errmsg = testLinkFunction (flink, linkname); errmsg = ''; testInput = [1; 2; 3; 4; 5]; try testOutput = flink(testInput); if (! isequal (size (testInput), size (testOutput))) errmsg = sprintf (['custom ''%s'' function must return an output', ... ' of the same size as input.'], linkname); endif catch errmsg = sprintf ("invalid custom '%s' function.", linkname); end_try_catch endfunction statistics-release-1.9.2/inst/Regression/private/madsigma.m000066400000000000000000000043611524624707500240620ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{s} =} madsigma (@var{r}, @var{p}) ## @deftypefnx {Private Function} {@var{s} =} madsigma (@var{r}, @var{p}, @var{y}) ## ## Robust scale from the median absolute deviation of the residuals @var{r}, ## dropping the @math{@var{p}-1} smallest to account for the fitted parameters. ## ## Given the response @var{y}, the scale is floored at @code{1e-6 * std (y)}, ## which is what an iteration needs and what the reported scale must not have. ## ## @seealso{robustfit, nlinfit} ## @end deftypefn function s = madsigma (r, p, y) rs = sort (abs (r)); s = median (rs(max (1, p):end)) / 0.6745; ## The scale that weights an iteration is floored relative to the response, ## so a fit that is already exact does not grade its weights out of rounding ## noise: on y = 3x through the origin the residuals are 1e-15 and the ## unfloored scale is 1e-15 with them, leaving the ratios order one and the ## weights graded where MATLAB returns ones. Measured on R2024a, on a ## fixture where std (Y), norm (Y) and max (abs (Y)) differ by six orders of ## magnitude: the floor is 1e-6 * std (Y). The scale reported back to a ## caller is the unfloored one, so the covariance step asks for two ## arguments and only an iteration asks for three. if (nargin > 2) tiny = 1e-6 * std (y); if (tiny == 0) tiny = 1; endif s = max (s, tiny); endif endfunction statistics-release-1.9.2/inst/Regression/private/modelspec_has_intercept.m000066400000000000000000000040561524624707500271640ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{tf} =} modelspec_has_intercept (@var{modelspec}, @var{intercept_nv}) ## ## Report whether a model specification carries an intercept. ## ## @var{modelspec} is a keyword, a numeric terms matrix, or empty, and ## @var{intercept_nv} is the @qcode{'Intercept'} name-value flag. ## ## @var{tf} is true when the fitted model will hold an intercept term. Every ## keyword specification supplies one, so for those the flag decides alone; a ## numeric terms matrix supplies one only if it holds an all-zero row. ## ## The answer is needed @emph{before} the predictors are encoded, because ## whether a categorical predictor is given all its indicator columns or only ## @math{k - 1} of them depends on it. It is available that early: an all-zero ## row is all-zero whether or not the matrix carries its optional trailing ## response column, so the encoded width is never needed to decide. ## ## This helper is shared by the @code{LinearModel} and ## @code{GeneralizedLinearModel} classes. ## ## @end deftypefn function tf = modelspec_has_intercept (modelspec, intercept_nv) if (! intercept_nv) tf = false; elseif (isnumeric (modelspec) && ! isempty (modelspec)) tf = any (all (double (modelspec) == 0, 2)); else tf = true; endif endfunction statistics-release-1.9.2/inst/Regression/private/nlfun_jacobian.m000066400000000000000000000030501524624707500252420ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{J} =} nlfun_jacobian (@var{modelfun}, @var{beta}, @var{X}, @var{derivstep}) ## ## Numeric Jacobian of @var{modelfun} with respect to @var{beta} by forward ## differences, evaluated at @var{X}. Internal helper shared by @code{nlinfit} ## and @code{nlpredci}; not intended to be called directly. ## ## @end deftypefn function J = nlfun_jacobian (modelfun, beta, X, derivstep) beta = beta(:); f0 = modelfun (beta, X); f0 = f0(:); n = numel (f0); p = numel (beta); J = zeros (n, p); for j = 1:p h = derivstep * max (abs (beta(j)), 1); if (h == 0) h = derivstep; endif bj = beta; bj(j) = bj(j) + h; fj = modelfun (bj, X); J(:,j) = (fj(:) - f0) / h; endfor endfunction statistics-release-1.9.2/inst/Regression/private/parse_modelspec.m000066400000000000000000000117431524624707500254470ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{terms}, @var{has_intercept}, @var{coef_names}, @var{errmsg}] =} parse_modelspec (@var{modelspec}, @var{pred_names}, @var{n_preds}, @var{intercept_nv}) ## ## Turn a model specification into a terms matrix and coefficient names. ## ## @var{modelspec} is either a keyword (@qcode{'constant'}, @qcode{'linear'}, ## @qcode{'interactions'}, @qcode{'purequadratic'}, @qcode{'quadratic'}, or ## @qcode{'full'}) or a numeric terms matrix; @var{pred_names} lists the ## @var{n_preds} encoded predictor names, and @var{intercept_nv} is a logical ## flag requesting an intercept term. ## ## @var{terms} is the resulting @math{m}-by-(@var{n_preds}+1) terms matrix, ## @var{has_intercept} indicates whether an intercept row is present, and ## @var{coef_names} are the term (coefficient) names. On invalid input the ## outputs are empty and @var{errmsg} holds a diagnostic message body; the ## caller should emit it under its own function name. @var{errmsg} is empty on ## success. ## ## This helper is shared by the @code{LinearModel} and ## @code{GeneralizedLinearModel} classes. ## ## @end deftypefn function [terms, has_intercept, coef_names, errmsg] = parse_modelspec ( ... modelspec, pred_names, n_preds, intercept_nv) terms = []; has_intercept = []; coef_names = {}; errmsg = ''; p = n_preds; if (isempty (modelspec) ... || (ischar (modelspec) && strcmpi (modelspec, 'linear'))) terms = [zeros(1, p+1); [eye(p), zeros(p, 1)]]; elseif (ischar (modelspec) && strcmpi (modelspec, 'constant')) terms = zeros (1, p+1); elseif (ischar (modelspec) && strcmpi (modelspec, 'interactions')) linear_part = [zeros(1, p+1); [eye(p), zeros(p, 1)]]; inter_part = zeros (0, p+1); for i = 1:p for j = i+1:p row = zeros (1, p+1); row(i) = 1; row(j) = 1; inter_part = [inter_part; row]; endfor endfor terms = [linear_part; inter_part]; elseif (ischar (modelspec) && strcmpi (modelspec, 'purequadratic')) linear_part = [zeros(1, p+1); [eye(p), zeros(p, 1)]]; quad_part = zeros (p, p+1); for j = 1:p quad_part(j, j) = 2; endfor terms = [linear_part; quad_part]; elseif (ischar (modelspec) && strcmpi (modelspec, 'quadratic')) linear_part = [zeros(1, p+1); [eye(p), zeros(p, 1)]]; quad_part = zeros (p, p+1); for j = 1:p quad_part(j, j) = 2; endfor inter_part = zeros (0, p+1); for i = 1:p for j = i+1:p row = zeros (1, p+1); row(i) = 1; row(j) = 1; inter_part = [inter_part; row]; endfor endfor terms = [linear_part; inter_part; quad_part]; elseif (ischar (modelspec) && strcmpi (modelspec, 'full')) terms = zeros (1, p+1); for k = 1:p idx_mat = nchoosek (1:p, k); for j = 1:rows (idx_mat) row = zeros (1, p+1); row(idx_mat(j,:)) = 1; terms = [terms; row]; endfor endfor elseif (isnumeric (modelspec)) terms = double (modelspec); if (size (terms, 2) == p) terms = [terms, zeros(rows (terms), 1)]; elseif (size (terms, 2) == p + 1) if (! all (terms(:, end) == 0)) terms = []; errmsg = "Last column of terms matrix must be all zeros."; return; endif else terms = []; errmsg = sprintf ("Terms matrix must have %d or %d columns.", p, p+1); return; endif else errmsg = "Unknown model specification."; return; endif if (! intercept_nv) int_rows = all (terms(:, 1:end-1) == 0, 2); terms = terms(! int_rows, :); endif has_intercept = any (all (terms(:, 1:end-1) == 0, 2)); n_terms = rows (terms); coef_names = cell (1, n_terms); for t = 1:n_terms term_row = terms(t, 1:end-1); if (all (term_row == 0)) coef_names{t} = '(Intercept)'; else parts_t = {}; for j = 1:numel (term_row) if (term_row(j) != 0) if (term_row(j) == 1) parts_t{end+1} = pred_names{j}; else parts_t{end+1} = sprintf ("%s^%d", pred_names{j}, term_row(j)); endif endif endfor coef_names{t} = strjoin (parts_t, ':'); endif endfor endfunction statistics-release-1.9.2/inst/Regression/private/raw_to_codes.m000066400000000000000000000057301524624707500247510ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{X_num}, @var{cat_levels}] =} raw_to_codes (@var{data}, @var{X_raw}, @var{tbl}, @var{pred_names}, @var{cat_logical}, @var{n_total}) ## ## Convert raw predictor data to numeric level codes for categorical columns. ## ## @var{data} is the predictor data as the caller received it, and decides which ## branch is taken: a table is read variable by variable through @var{tbl}, ## anything else column by column through the numeric matrix @var{X_raw}. ## @var{pred_names} names the predictors, @var{cat_logical} marks the ones to ## treat as categorical, and @var{n_total} is the number of observations. ## ## @var{X_num} holds the numeric predictors unchanged and 1-based level codes ## for the categorical ones, and @var{cat_levels} lists the level labels of each ## predictor, empty for a numeric one. The output feeds ## @code{encode_categorical}, which turns the codes into indicator columns. ## ## This helper is shared by the @code{CoxModel} and ## @code{GeneralizedLinearModel} classes. ## ## @end deftypefn function [X_num, cat_levels] = raw_to_codes (data, X_raw, tbl, pred_names, ... cat_logical, n_total) p = numel (pred_names); X_num = zeros (n_total, p); cat_levels = cell (1, p); for j = 1:p if (istable (data)) col = tbl.(pred_names{j}); if (iscell (col)) ## Appearance order, so that the omitted reference level is the one the ## data shows first; see parseWilkinsonFormula for the formula path. [cat_levels{j}, ~, ic] = unique (col, 'stable'); X_num(:, j) = ic; elseif (isa (col, 'categorical')) cat_levels{j} = categories (col); [~, ic] = ismember (cellstr (col), cat_levels{j}); X_num(:, j) = ic; else X_num(:, j) = double (col(:)); cat_levels{j} = {}; endif else if (cat_logical(j)) uvals = sort (unique (X_raw(isfinite (X_raw(:,j)), j))); cat_levels{j} = strtrim (cellstr (num2str (uvals(:)))); [~, ic] = ismember (X_raw(:,j), uvals); X_num(:, j) = ic; else X_num(:, j) = X_raw(:, j); cat_levels{j} = {}; endif endif endfor endfunction statistics-release-1.9.2/inst/Regression/private/reencode_predictors.m000066400000000000000000000060231524624707500263170ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{X_enc} =} reencode_predictors (@var{X_raw}, @var{pred_names}, @var{cat_info}, @var{enc_names}) ## ## Re-encode raw predictor data to match a fitted model's encoded columns. ## ## Given new raw predictor data @var{X_raw} (numeric, one column per predictor ## in @var{pred_names}), the categorical level information @var{cat_info} ## recorded at fit time (fields @qcode{names} and @qcode{levels}), and the ## target encoded column names @var{enc_names}, return the ## @math{n}-by-@code{numel (enc_names)} matrix @var{X_enc} whose columns ## reproduce, in order, the encoding used when the model was fitted. ## ## Each encoded name is matched as a plain predictor, a power term ## @qcode{name^k}, or a categorical indicator @qcode{name_level}, so the result ## aligns with the fitted design. When @var{enc_names} is omitted it defaults ## to @var{pred_names} (plain pass-through). ## ## This helper is shared by the @code{LinearModel} and ## @code{GeneralizedLinearModel} classes. ## ## @end deftypefn function X_enc = reencode_predictors (X_raw, pred_names, cat_info, enc_names) if (nargin < 4) enc_names = pred_names; endif n = rows (X_raw); X_enc = zeros (n, numel (enc_names)); for c = 1:numel (enc_names) name = enc_names{c}; j = find (strcmp (pred_names, name), 1); if (! isempty (j)) X_enc(:, c) = X_raw(:, j); continue; endif tok = regexp (name, '^(.+)\^(\d+)$', 'tokens'); if (! isempty (tok)) j = find (strcmp (pred_names, tok{1}{1}), 1); k = str2double (tok{1}{2}); X_enc(:, c) = X_raw(:, j) .^ k; continue; endif found = false; for j = 1:numel (pred_names) ci = []; if (! isempty (cat_info.names)) ci = find (strcmp (cat_info.names, pred_names{j})); endif if (isempty (ci)) continue; endif levels_j = cat_info.levels{ci}; for L = 2:numel (levels_j) if (strcmp (name, sprintf ("%s_%s", pred_names{j}, char (levels_j{L})))) X_enc(:, c) = double (X_raw(:, j) == L); found = true; break; endif endfor if (found) break; endif endfor endfor endfunction statistics-release-1.9.2/inst/Regression/private/robusttune.m000066400000000000000000000032041524624707500245050ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{t} =} robusttune (@var{wfun}) ## ## Default tuning constant, giving 95% efficiency, for a robust weight function. ## ## @var{wfun} is one of the names @code{robustwfun} accepts, or a function ## handle, which carries no default and takes 1. ## ## @seealso{robustfit, nlinfit, robustwfun} ## @end deftypefn function t = robusttune (wfun) if (is_function_handle (wfun)) t = 1; return; endif switch (wfun) case "andrews"; t = 1.339; case "bisquare"; t = 4.685; case "cauchy"; t = 2.385; case "fair"; t = 1.400; case "huber"; t = 1.345; case "logistic"; t = 1.205; case "ols"; t = 1; case "talwar"; t = 2.795; case "welsch"; t = 2.985; endswitch endfunction statistics-release-1.9.2/inst/Regression/private/robustwfun.m000066400000000000000000000064511524624707500245200ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{w}, @var{psi}, @var{psip}] =} robustwfun (@var{z}, @var{wfun}) ## ## Weight, influence function and its derivative for a robust weight function. ## ## @var{z} is the vector of residuals already scaled by the robust scale and the ## tuning constant, and @var{wfun} is one of @qcode{"andrews"}, ## @qcode{"bisquare"}, @qcode{"cauchy"}, @qcode{"fair"}, @qcode{"huber"}, ## @qcode{"logistic"}, @qcode{"ols"}, @qcode{"talwar"} or @qcode{"welsch"}, or a ## function handle returning the weights. @var{w} is the weight, @var{psi} is ## @code{@var{z} .* @var{w}} and @var{psip} is its derivative, computed ## analytically for a named function and by central difference for a handle. ## ## @qcode{"andrews"} and @qcode{"logistic"} take their limiting value of 1 at ## @code{@var{z} == 0}, where the quotient defining them is @math{0/0}. ## ## @seealso{robustfit, nlinfit, robusttune} ## @end deftypefn function [w, psi, psip] = robustwfun (z, wfun) if (is_function_handle (wfun)) w = wfun (z); if (nargout > 1) d = 1e-6; psi = z .* w; psip = ((z + d) .* wfun (z + d) - (z - d) .* wfun (z - d)) / (2 * d); endif return; endif a = abs (z); switch (wfun) case "andrews" in = a < pi; w = (sin (z) ./ (z + (z == 0))) .* in; w(z == 0) = 1; if (nargout > 1); psi = sin (z) .* in; psip = cos (z) .* in; endif case "bisquare" in = a < 1; w = (1 - z .^ 2) .^ 2 .* in; if (nargout > 1) psi = z .* w; psip = (1 - z .^ 2) .* (1 - 5 * z .^ 2) .* in; endif case "cauchy" w = 1 ./ (1 + z .^ 2); if (nargout > 1) psi = z .* w; psip = (1 - z .^ 2) ./ (1 + z .^ 2) .^ 2; endif case "fair" w = 1 ./ (1 + a); if (nargout > 1); psi = z .* w; psip = 1 ./ (1 + a) .^ 2; endif case "huber" w = 1 ./ max (1, a); if (nargout > 1); psi = z .* w; psip = double (a < 1); endif case "logistic" th = tanh (z); w = th ./ (z + (z == 0)); w(z == 0) = 1; if (nargout > 1); psi = th; psip = 1 - th .^ 2; endif case "ols" w = ones (size (z)); if (nargout > 1); psi = z; psip = ones (size (z)); endif case "talwar" in = a < 1; w = double (in); if (nargout > 1); psi = z .* in; psip = double (in); endif case "welsch" e = exp (- z .^ 2); w = e; if (nargout > 1); psi = z .* e; psip = (1 - 2 * z .^ 2) .* e; endif endswitch endfunction statistics-release-1.9.2/inst/Regression/private/terms_from_coefnames.m000066400000000000000000000077451524624707500265060ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{terms}, @var{cat_info}, @var{col_names}] =} terms_from_coefnames (@var{coef_names}, @var{pred_names}, @var{cat_logical}, @var{data}, @var{tbl_sub}) ## ## Recover a terms matrix and the categorical level information from the ## coefficient names a model formula produced. ## ## @var{coef_names} lists the coefficient names, @var{pred_names} the model's ## predictors, @var{cat_logical} marks the categorical ones, @var{data} is the ## data as given, and @var{tbl_sub} the table of rows kept for the fit. ## ## @var{terms} is the terms matrix over the encoded design columns, ## @var{cat_info} a structure with fields @qcode{names} and @qcode{levels}, and ## @var{col_names} the name of each column of @var{terms}. @var{col_names} is ## @emph{not} @var{coef_names}: a factor appearing only inside an interaction or ## a power gets a column of its own without ever being a coefficient, so the two ## lists diverge and anything indexed by the columns of @var{terms} must be ## built from @var{col_names}. The ## levels of a character or string grouping column are taken in the order the ## data presents them, matching the order the design matrix was built in; a ## @code{categorical} column carries its own category order. ## ## This helper is shared by the @code{LinearModel} and ## @code{GeneralizedLinearModel} classes. ## ## @end deftypefn function [terms, cat_info, col_names] = terms_from_coefnames (coef_names, pred_names, ... cat_logical, data, tbl_sub) cat_info.names = {}; cat_info.levels = {}; if (istable (data)) for j = 1:numel (pred_names) if (cat_logical(j)) col = tbl_sub.(pred_names{j}); if (iscell (col) || isa (col, 'string')) ## In the order the data presents them, as the design matrix was ## built: these levels re-encode new data at prediction time, so a ## sorted list here would silently map it onto the wrong indicators. levels_j = cellstr (unique (col(:), 'stable')); elseif (isa (col, 'categorical')) levels_j = categories (col); elseif (islogical (col) || isnumeric (col)) ## Logical, or numeric and declared categorical: coded by ascending ## value, so the reference level is the smallest. vals = double (col(:)); vals = sort (unique (vals(! isnan (vals)))); levels_j = arrayfun (@(x) strtrim (num2str (x)), vals, ... 'UniformOutput', false); else levels_j = {}; endif cat_info.names{end+1} = pred_names{j}; cat_info.levels{end+1} = levels_j; endif endfor endif nc = numel (coef_names); atomic = {}; for t = 1:nc if (strcmp (coef_names{t}, '(Intercept)')) continue; endif for f = strsplit (coef_names{t}, ':') if (! any (strcmp (atomic, f{1}))) atomic{end+1} = f{1}; endif endfor endfor terms = zeros (nc, numel (atomic) + 1); for t = 1:nc if (strcmp (coef_names{t}, '(Intercept)')) continue; endif for f = strsplit (coef_names{t}, ':') terms(t, strcmp (atomic, f{1})) = 1; endfor endfor col_names = [atomic, {''}]; endfunction statistics-release-1.9.2/inst/Regression/private/variable_class_and_range.m000066400000000000000000000046651524624707500272570ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{cname}, @var{range}] =} variable_class_and_range (@var{col}) ## ## Report the class of a model variable and the range of values it takes. ## ## @var{col} is one variable of the data as it was given. ## ## @var{cname} is its class. @var{range} is the two-element vector ## @code{[min, max]} for a numeric or logical variable, and otherwise the list ## of levels it takes, as a row and in the variable's own type: a ## @code{categorical} keeps its category order and comes back a ## @code{categorical}, while a @code{string} or a cell array of character ## vectors lists its levels in the order the data presents them and comes back ## as a @code{string} or a cell array respectively. ## ## The level order matters: it is the order the design matrix codes them in, so ## the reference level is the first entry. ## ## This helper is shared by the @code{LinearModel} and ## @code{GeneralizedLinearModel} classes. ## ## @end deftypefn function [cname, range] = variable_class_and_range (col) cname = class (col); if (isnumeric (col) || islogical (col)) fv = double (col(:)); fv = fv(isfinite (fv)); if (isempty (fv)) range = [NaN, NaN]; else range = [min(fv), max(fv)]; endif ## The range keeps the variable's own type, as the level list does. if (islogical (col)) range = logical (range); endif elseif (isa (col, 'categorical')) lv = categories (col); range = categorical (lv(:)', lv); elseif (isa (col, 'string')) range = unique (col(:)', 'stable'); elseif (iscell (col)) lv = unique (col, 'stable'); range = lv(:)'; else range = {}; endif endfunction statistics-release-1.9.2/inst/Regression/private/variable_level_terms.m000066400000000000000000000052371524624707500264710ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{T} =} variable_level_terms (@var{terms_enc}, @var{enc2raw}, @var{var_idx}, @var{n_vars}, @var{col_pow}) ## ## Fold a terms matrix over encoded design columns onto one over the model's ## variables. ## ## @var{terms_enc} is the terms matrix expressed over the encoded design ## columns, @var{enc2raw} maps each of those columns onto a predictor, ## @var{var_idx} maps each predictor onto a variable, and @var{n_vars} is the ## number of variables. @var{col_pow} gives the exponent each column name ## carries and defaults to all ones; it is what distinguishes a column named ## @qcode{u^2} from one named @qcode{u}. ## ## @var{enc2raw} and @var{col_pow} are indexed by the @emph{columns of ## @var{terms_enc}}, so they must be built from the names of those columns and ## not from the coefficient names, which differ whenever a factor appears only ## inside an interaction or a power. ## ## @var{T} has one column per variable and one row per distinct term. The ## several indicator columns of a categorical predictor collapse onto the single ## variable they came from, so the duplicate rows this produces are dropped. ## ## This helper is shared by the @code{LinearModel} and ## @code{GeneralizedLinearModel} classes. ## ## @end deftypefn function T = variable_level_terms (terms_enc, enc2raw, var_idx, n_vars, col_pow) if (nargin < 5) col_pow = ones (1, columns (terms_enc)); endif n_terms = rows (terms_enc); T = zeros (n_terms, n_vars); for t = 1:n_terms for j = 1:columns (terms_enc) if (terms_enc(t, j) != 0 && enc2raw(j) > 0 && var_idx(enc2raw(j)) > 0) c = var_idx(enc2raw(j)); T(t, c) = max (T(t, c), terms_enc(t, j) * col_pow(j)); endif endfor endfor keep = true (n_terms, 1); for t = 2:n_terms if (any (all (T(1:t-1, :) == T(t, :), 2))) keep(t) = false; endif endfor T = T(keep, :); endfunction statistics-release-1.9.2/inst/Regression/regress.m000066400000000000000000000155451524624707500223060ustar00rootroot00000000000000## Copyright (C) 2005, 2006 William Poetra Yoga Hadisoeseno ## Copyright (C) 2011 Nir Krakauer ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{b}, @var{bint}, @var{r}, @var{rint}, @var{stats}] =} regress (@var{y}, @var{X}, [@var{alpha}]) ## ## Multiple Linear Regression using Least Squares Fit of @var{y} on @var{X} ## with the model @code{y = X * beta + e}. ## ## Here, ## ## @itemize ## @item ## @code{y} is a column vector of observed values ## @item ## @code{X} is a matrix of regressors, with the first column filled with ## the constant value 1 ## @item ## @code{beta} is a column vector of regression parameters ## @item ## @code{e} is a column vector of random errors ## @end itemize ## ## Arguments are ## ## @itemize ## @item ## @var{y} is the @code{y} in the model ## @item ## @var{X} is the @code{X} in the model ## @item ## @var{alpha} is the significance level used to calculate the confidence ## intervals @var{bint} and @var{rint} (see `Return values' below). If not ## specified, ALPHA defaults to 0.05 ## @end itemize ## ## Return values are ## ## @itemize ## @item ## @var{b} is the @code{beta} in the model ## @item ## @var{bint} is the confidence interval for @var{b} ## @item ## @var{r} is a column vector of residuals ## @item ## @var{rint} is the confidence interval for @var{r} ## @item ## @var{stats} is a row vector containing: ## ## @itemize ## @item The R^2 statistic ## @item The F statistic ## @item The p value for the full model ## @item The estimated error variance ## @end itemize ## @end itemize ## ## @var{r} and @var{rint} can be passed to @code{rcoplot} to visualize ## the residual intervals and identify outliers. ## ## NaN values in @var{y} and @var{X} are removed before calculation begins. ## ## @seealso{regress_gp, regression_ftest, regression_ttest} ## @end deftypefn function [b, bint, r, rint, stats] = regress (y, X, alpha) if (nargin < 2 || nargin > 3) print_usage; endif if (! ismatrix (y)) error ("regress: y must be a numeric matrix"); endif if (! ismatrix (X)) error ("regress: X must be a numeric matrix"); endif if (columns (y) != 1) error ("regress: y must be a column vector"); endif if (rows (y) != rows (X)) error ("regress: y and X must contain the same number of rows"); endif if (nargin < 3) alpha = 0.05; elseif (! isscalar (alpha)) error ("regress: alpha must be a scalar value") endif notnans = ! logical (sum (isnan ([y X]), 2)); y = y(notnans); X = X(notnans,:); [Xq Xr] = qr (X, 0); pinv_X = Xr \ Xq'; b = pinv_X * y; if (nargout > 1) n = rows (X); p = columns (X); dof = n - p; t_alpha_2 = tinv (alpha / 2, dof); r = y - X * b; # added -- Nir SSE = sum (r .^ 2); v = SSE / dof; # c = diag (inv (X' * X)) using the (economy) QR factor Xr = R. Since # X' * X == R' * R, we have inv (X' * X) == inv (R) * inv (R)', whose # diagonal is the row-wise sum of squares of inv (R). Forming R' * R # would square the condition number (and warns "matrix singular to # machine precision" for ill-conditioned designs, e.g. the Longley data); # working from inv (R) directly avoids that. Xri = inv (Xr); c = sum (Xri .^ 2, 2); db = t_alpha_2 * sqrt (v * c); bint = [b + db, b - db]; endif if (nargout > 3) dof1 = n - p - 1; h = sum (X.*pinv_X', 2); #added -- Nir (same as diag(X*pinv_X), without doing the matrix multiply) # From Matlab's documentation on Multiple Linear Regression, # sigmaihat2 = norm (r) ^ 2 / dof1 - r .^ 2 / (dof1 * (1 - h)); # dr = -tinv (1 - alpha / 2, dof) * sqrt (sigmaihat2 .* (1 - h)); # Substitute # norm (r) ^ 2 == sum (r .^ 2) == SSE # -tinv (1 - alpha / 2, dof) == tinv (alpha / 2, dof) == t_alpha_2 # We get # sigmaihat2 = (SSE - r .^ 2 / (1 - h)) / dof1; # dr = t_alpha_2 * sqrt (sigmaihat2 .* (1 - h)); # Combine, we get # dr = t_alpha_2 * sqrt ((SSE * (1 - h) - (r .^ 2)) / dof1); dr = t_alpha_2 * sqrt ((SSE * (1 - h) - (r .^ 2)) / dof1); rint = [r + dr, r - dr]; endif if (nargout > 4) R2 = 1 - SSE / sum ((y - mean (y)) .^ 2); # F = (R2 / (p - 1)) / ((1 - R2) / dof); F = dof / (p - 1) / (1 / R2 - 1); pval = 1 - fcdf (F, p - 1, dof); stats = [R2 F pval v]; endif endfunction %!test %! % Longley data from the NIST Statistical Reference Dataset %! Z = [ 60323 83.0 234289 2356 1590 107608 1947 %! 61122 88.5 259426 2325 1456 108632 1948 %! 60171 88.2 258054 3682 1616 109773 1949 %! 61187 89.5 284599 3351 1650 110929 1950 %! 63221 96.2 328975 2099 3099 112075 1951 %! 63639 98.1 346999 1932 3594 113270 1952 %! 64989 99.0 365385 1870 3547 115094 1953 %! 63761 100.0 363112 3578 3350 116219 1954 %! 66019 101.2 397469 2904 3048 117388 1955 %! 67857 104.6 419180 2822 2857 118734 1956 %! 68169 108.4 442769 2936 2798 120445 1957 %! 66513 110.8 444546 4681 2637 121950 1958 %! 68655 112.6 482704 3813 2552 123366 1959 %! 69564 114.2 502601 3931 2514 125368 1960 %! 69331 115.7 518173 4806 2572 127852 1961 %! 70551 116.9 554894 4007 2827 130081 1962 ]; %! % Results certified by NIST using 500 digit arithmetic %! % b and standard error in b %! V = [ -3482258.63459582 890420.383607373 %! 15.0618722713733 84.9149257747669 %! -0.358191792925910E-01 0.334910077722432E-01 %! -2.02022980381683 0.488399681651699 %! -1.03322686717359 0.214274163161675 %! -0.511041056535807E-01 0.226073200069370 %! 1829.15146461355 455.478499142212 ]; %! Rsq = 0.995479004577296; %! F = 330.285339234588; %! y = Z(:,1); X = [ones(rows(Z),1), Z(:,2:end)]; %! alpha = 0.05; %! [b, bint, r, rint, stats] = regress (y, X, alpha); %! assert_equal (b,V(:,1),4e-6); %! assert_equal (stats(1),Rsq,1e-12); %! assert_equal (stats(2),F,3e-8); %! assert_equal (((bint(:,1)-bint(:,2))/2)/tinv (alpha/2,9),V(:,2),-1e-11); statistics-release-1.9.2/inst/Regression/regress_gp.m000066400000000000000000000731011524624707500227640ustar00rootroot00000000000000## Copyright (c) 2012 Juan Pablo Carbajal ## Copyright (C) 2023-2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {[@var{Yfit}, @var{Yint}, @var{m}, @var{K}] =} regress_gp (@var{X}, @var{Y}, @var{Xfit}) ## @deftypefnx {statistics} {[@var{Yfit}, @var{Yint}, @var{m}, @var{K}] =} regress_gp (@var{X}, @var{Y}, @var{Xfit}, @qcode{'linear'}) ## @deftypefnx {statistics} {[@var{Yfit}, @var{Yint}, @var{Ysd}] =} regress_gp (@var{X}, @var{Y}, @var{Xfit}, @qcode{'rbf'}) ## @deftypefnx {statistics} {[@dots{}] =} regress_gp (@var{X}, @var{Y}, @var{Xfit}, @qcode{'linear'}, @var{Sp}) ## @deftypefnx {statistics} {[@dots{}] =} regress_gp (@var{X}, @var{Y}, @var{Xfit}, @var{Sp}) ## @deftypefnx {statistics} {[@dots{}] =} regress_gp (@var{X}, @var{Y}, @var{Xfit}, @qcode{'rbf'}, @var{theta}) ## @deftypefnx {statistics} {[@dots{}] =} regress_gp (@var{X}, @var{Y}, @var{Xfit}, @qcode{'rbf'}, @var{theta}, @var{g}) ## @deftypefnx {statistics} {[@dots{}] =} regress_gp (@var{X}, @var{Y}, @var{Xfit}, @qcode{'rbf'}, @var{theta}, @var{g}, @var{alpha}) ## @deftypefnx {statistics} {[@dots{}] =} regress_gp (@var{X}, @var{Y}, @var{Xfit}, @var{theta}) ## @deftypefnx {statistics} {[@dots{}] =} regress_gp (@var{X}, @var{Y}, @var{Xfit}, @var{theta}, @var{g}) ## @deftypefnx {statistics} {[@dots{}] =} regress_gp (@var{X}, @var{Y}, @var{Xfit}, @var{theta}, @var{g}, @var{alpha}) ## ## Regression using Gaussian Processes. ## ## @code{[@var{Yfit}, @var{Yint}, @var{m}, @var{K}] = regress_gp (@var{X}, ## @var{Y}, @var{Xfit})} will estimate a linear Gaussian Process model @var{m} ## in the form @qcode{@var{Y} = @var{X}' * @var{m}}, where @var{X} is an ## @math{N*P} matrix with @math{N} observations in @math{P} dimensional space ## and @var{Y} is an @math{N*1} column vector as the dependent variable. The ## information about errors of the predictions (interpolation/extrapolation) is ## given by the covariance matrix @var{K}. ## By default, the linear model defines the prior covariance of @var{m} as ## @code{@var{Sp} = 100 * eye (size (@var{X}, 2) + 1)}. A custom prior ## covariance matrix can be passed as @var{Sp}, which must be a @math{P+1*P+1} ## positive definite matrix. The model is evaluated for input @var{Xfit}, which ## must have the same columns as @var{X}, and the estimates are returned in ## @var{Yfit} along with the estimated variation in @var{Yint}. ## @qcode{@var{Yint}(:,1)} contains the lower boundary and ## @qcode{@var{Yint}(:,2)} the upper boundary of the interval about ## @var{Yfit}, at the confidence level @qcode{1 - @var{alpha}}. ## ## @code{[@var{Yfit}, @var{Yint}, @var{Ysd}] = regress_gp (@var{X}, ## @var{Y}, @var{Xfit}, @qcode{'rbf'})} will estimate a Gaussian Process model ## with a Radial Basis Function (RBF) kernel with default parameters ## @qcode{@var{theta} = 5} and @qcode{@var{g} = 0.01}, which corresponds to the ## nugget effect, and ## @qcode{@var{alpha} = 0.05} which defines the confidence level for the ## estimated intervals returned in @var{Yint}. The function also returns the ## predictive covariance matrix in @var{Ysd}. For multidimensional predictors ## @var{X} the function will automatically normalize each column to a zero mean ## and a standard deviation to one. ## ## Four things about the RBF kernel are worth stating, because they decide ## what the numbers mean. ## ## @itemize ## @item ## @var{theta} is not the characteristic lengthscale. The kernel is ## @code{exp (-d^2 / @var{theta})} with @math{d} the distance between two ## points, so @var{theta} is twice the square of a lengthscale @math{l}, and ## @code{@var{theta} = 2 * l^2}. ## ## @item ## The intervals in @var{Yint} are prediction intervals for a @emph{new ## observation}, not confidence intervals on the mean: the nugget is carried ## in the predictive variance, so the noise of an observation is included. ## ## @item ## The predictors are centred and scaled only when there is more than one of ## them, so @var{theta} is measured in the units of @var{X} for a single ## predictor and in standard deviations for several. ## ## @item ## A nugget of zero is accepted and gives an interpolating process, one that ## reproduces @var{Y} at the training points and reports almost no uncertainty ## there. It also leaves the kernel matrix rank deficient whenever two inputs ## are close, so in that case the covariance is applied through its ## pseudoinverse and the fit is the minimum-norm solution. This is a genuine ## answer rather than a refusal, but a small nugget is the better way to ask ## for a smooth fit. ## @end itemize ## ## Run @code{demo regress_gp} to see examples. ## ## @seealso{fitrgp, RegressionGP, regress, regression_ftest, ## regression_ttest} ## @end deftypefn function [Yfit, Yint, varargout] = regress_gp (X, Y, Xfit, varargin) ## Check input arguments if (nargin < 3) print_usage; endif if (ndims (X) != 2) error ("regress_gp: X must be a 2-D matrix."); endif if (! isvector (Y) || size (Y, 2) != 1) error ("regress_gp: Y must be a column vector."); endif if (size (X, 1) != length (Y)) error ("regress_gp: rows in X must equal the length of Y."); endif if (size (X, 2) != size (Xfit, 2)) error ("regress_gp: X and XI must have the same number of columns."); endif ## Add defaults kernel = 'linear'; Sp = 100 * eye (size (X, 2) + 1); theta = 5; g = 0.01; alpha = 0.05; ## Parse extra arguments if (nargin > 3) tmp = varargin{1}; if (ischar (tmp) && strcmpi (tmp, 'linear')) kernel = 'linear'; sinput = true; elseif (ischar (tmp) && strcmpi (tmp, 'rbf')) kernel = 'rbf'; sinput = true; elseif (isnumeric (tmp) && ! isscalar (tmp)) kernel = 'linear'; sinput = false; Sp = checkSp (tmp, size (X, 2)); elseif (isnumeric (tmp) && isscalar (tmp)) kernel = 'rbf'; sinput = false; theta = tmp; else error ("regress_gp: invalid 4th argument."); endif endif if (nargin > 4) tmp = varargin{2}; if (sinput) if (isnumeric (tmp) && ! isscalar (tmp)) if (strcmpi (kernel, 'rbf')) error ("regress_gp: theta must be a scalar when using RBF kernel."); endif Sp = checkSp (tmp, size (X, 2)); elseif (isnumeric (tmp) && isscalar (tmp)) if (strcmpi (kernel, 'linear')) error ("regress_gp: wrong size for prior covariance matrix Sp."); endif theta = tmp; else error ("regress_gp: invalid 5th argument."); endif else if (strcmpi (kernel, 'linear')) error ("regress_gp: invalid 5th argument."); endif ## Every other argument in this function is validated before it is ## used. This one was not, so a non-numeric nugget reached the kernel ## and failed inside a matrix product with a message about ## nonconformant arguments, naming neither g nor this function. if (! (isnumeric (tmp) && isscalar (tmp))) error ("regress_gp: invalid 5th argument."); endif g = tmp; endif endif if (nargin > 5) tmp = varargin{3}; if (isnumeric (tmp) && isscalar (tmp) && sinput) g = tmp; elseif (isnumeric (tmp) && isscalar (tmp) && ! sinput) alpha = tmp; else error ("regress_gp: invalid 6th argument."); endif endif if (nargin > 6) tmp = varargin{4}; if (isnumeric (tmp) && isscalar (tmp) && sinput) alpha = tmp; else error ("regress_gp: invalid 7th argument."); endif endif ## User linear kernel if (strcmpi (kernel, 'linear')) ## Add constant vector X = [ones(1,size(X,1)); X']; ## Juan Pablo Carbajal ## Note that in the book the equation (below 2.11) for the A reads ## A = (1/sy^2)*X*X' + inv (Vp); ## where sy is the scalar variance of the of the residuals (i.e Y = X' * w + epsilon) ## and epsilon is drawn from N(0,sy^2). Vp is the variance of the parameters w. ## Note that ## (sy^2 * A)^{-1} = (1/sy^2)*A^{-1} = (X*X' + sy^2 * inv(Vp))^{-1}; ## and that the formula for the w mean is ## (1/sy^2)*A^{-1}*X*Y ## Then one obtains ## inv(X*X' + sy^2 * inv(Vp))*X*Y ## Looking at the formula below we see that Sp = (1/sy^2)*Vp ## making the regression depend on only one parameter, Sp, and not two. ## Xsq = sum (X' .^ 2); ## [n, d] = size (X); ## sigma = 1/sqrt(2); ## Ks = exp (-(Xsq' * ones (1, n) -ones (n, 1) * Xsq + 2 * X * X') / (2 * sigma ^ 2)); ## Sp and A are both positive definite, so their inverses are taken ## through a Cholesky factor rather than by inv. A is built from a Gram ## matrix and inherits the square of the predictors' conditioning, which is ## exactly where a general inverse loses digits and a triangular solve does ## not. K is still formed because it is the fourth output. Rp = chol (Sp); A = X * X' + Rp \ (Rp' \ eye (rows (Sp))); Ra = chol (A); K = Ra \ (Ra' \ eye (rows (A))); wm = Ra \ (Ra' \ (X * Y)); ## Add constant vector Xfit = [ones(size(Xfit,1),1), Xfit]; ## Compute predictions Yfit = Xfit*wm; ## Only the diagonal of Xfit * K * Xfit' is ever read here, so the full ## m-by-m matrix is never formed: with A = Ra' * Ra the product is U' * U ## for U = Ra' \ Xfit', whose diagonal is the squared column norms of U. ## Exact, not an approximation, and O(m) in place of O(m^2). U = Ra' \ Xfit'; Ysd = sum (U .^ 2, 1)'; ## The diagonal of a covariance is a variance, so it is the square root ## that scales the interval, and the interval is that many standard ## deviations wide for the confidence level asked for. This branch used ## the variance itself, applied no level at all, and returned its columns ## in the opposite order to the other branch. dy = norminv (1 - alpha / 2) * sqrt (Ysd); Yint = [Yfit-dy, Yfit+dy]; if (nargout > 2) varargout{1} = wm; endif if (nargout > 3) varargout{2} = K; endif endif ## User RBF kernel if (strcmpi (kernel, 'rbf')) ## Normalize predictors if (size (X, 2) > 1) [X, MU, SIGMA] = zscore (X); Xfit = (Xfit - MU) ./SIGMA; endif ## Get number of training samples n = size (X, 1); ## Calculate squared distance matrix of training input D = squareform (pdist (X) .^2); ## Compute kernel covariance for training quantities S = exp (-D / theta) + g * eye (n); ## Compute kernel covariance for prediction Dx = pdist2 (Xfit, X) .^ 2; Sx = exp (-Dx / theta); ## S is symmetric, and positive definite whenever the nugget is, so every ## quantity below goes through a solve rather than through inv (S). The ## inverse was not merely slower: at g = 0 it warns "matrix singular to ## machine precision" with an rcond of 1e-18 and returns what that implies, ## on an argument the function accepts. SinvY = Y; SinvSx = Sx'; if (rcond (S) > eps) SinvY = S \ Y; SinvSx = S \ Sx'; else ## A nugget of zero on repeated or near-repeated inputs leaves S rank ## deficient. The minimum-norm solution is the defensible answer here ## and pinv gives it without the warning inv emitted. Sinv = pinv (S); SinvY = Sinv * Y; SinvSx = Sinv * Sx'; endif ## Calculate response output Yfit = Sx * SinvY; ## Estimate scale parameter for predictive variance scale = (Y' * SinvY) / size (Y, 1); ## The interval needs only the diagonal of the predictive covariance, and ## the test-test prior contributes exactly 1 + g to it, since a point is at ## zero distance from itself. So neither the m-by-m distance matrix nor ## the full covariance is built unless the caller asks for the covariance ## itself as the third output. ysd1 = sqrt (scale * ((1 + g) - sum (Sx' .* SinvSx, 1)')); if (nargout > 2) Dxi = squareform (pdist (Xfit) .^ 2); Sxi = exp (-Dxi / theta) + g * eye (rows (Xfit)); Ysd = scale * (Sxi - Sx * SinvSx); endif ## Calculate prediction intervals. A two sided interval puts alpha/2 in ## each tail, so the quantile is of 1 - alpha/2; taking it at alpha ## returned a 100*(1-2*alpha) per cent interval under a 100*(1-alpha) per ## cent name, which at the default made every interval a 90 per cent one. dy = norminv (1 - alpha / 2) * ysd1; Yint = [Yfit-dy, Yfit+dy]; if (nargout > 2) varargout{1} = Ysd; endif endif endfunction ## Validate the prior covariance of the weights. Both argument positions that ## accept Sp check it here, because they did not check the same things: the ## fourth-argument form validated nothing at all, and the fifth validated the ## size but not the definiteness the documentation requires. A singular Sp is ## inverted to infinities and every prediction comes back NaN, warning about a ## matrix the caller never handed to inv. function Sp = checkSp (Sp, p) if (! isequal (size (Sp), (p + 1) * [1, 1])) error ("regress_gp: wrong size for prior covariance matrix Sp."); endif [~, spd] = chol (Sp); if (spd != 0) error (strcat ("regress_gp: prior covariance matrix Sp must be", ... " positive definite.")); endif endfunction %!demo %! ## Linear fitting of 1D Data %! rng (42); %! X = 2 * rand (5, 1) - 1; %! Y = 2 * X - 1 + 0.3 * randn (5, 1); %! %! ## Points for interpolation/extrapolation %! Xfit = linspace (-2, 2, 10)'; %! %! ## Fit regression model %! [Yfit, Yint, m] = regress_gp (X, Y, Xfit); %! %! ## Plot fitted data %! plot (X, Y, 'xk', Xfit, Yfit, 'r-', Xfit, Yint, 'b-'); %! title ('Gaussian process regression with linear kernel'); %!demo %! ## Linear fitting of 2D Data %! rng (42); %! X = 2 * rand (4, 2) - 1; %! Y = 2 * X(:,1) - 3 * X(:,2) - 1 + 1 * randn (4, 1); %! %! ## Mesh for interpolation/extrapolation %! [x1, x2] = meshgrid (linspace (-1, 1, 10)); %! Xfit = [x1(:), x2(:)]; %! %! ## Fit regression model %! [Ypred, Yint, m] = regress_gp (X, Y, Xfit); %! Ypred = reshape (Ypred, 10, 10); %! YintL = reshape (Yint(:,1), 10, 10); %! YintU = reshape (Yint(:,2), 10, 10); %! %! ## Plot fitted data %! plot3 (X(:,1), X(:,2), Y, '.k', 'markersize', 16); %! hold on; %! h = mesh (x1, x2, Ypred, zeros (10, 10)); %! set (h, 'facecolor', 'none', 'edgecolor', 'yellow'); %! h = mesh (x1, x2, YintU, ones (10, 10)); %! set (h, 'facecolor', 'none', 'edgecolor', 'cyan'); %! h = mesh (x1, x2, YintL, ones (10, 10)); %! set (h, 'facecolor', 'none', 'edgecolor', 'cyan'); %! hold off %! axis tight %! view (75, 25) %! title ('Gaussian process regression with linear kernel'); %!demo %! ## Projection over basis function with linear kernel %! rng (42); %! pp = [2, 2, 0.3, 1]; %! n = 10; %! X = 2 * rand (n, 1) - 1; %! Y = polyval (pp, X) + 0.3 * randn (n, 1); %! %! ## Powers %! px = [sqrt(abs(X)), X, X.^2, X.^3]; %! %! ## Points for interpolation/extrapolation %! Xfit = linspace (-1, 1, 100)'; %! pxi = [sqrt(abs(Xfit)), Xfit, Xfit.^2, Xfit.^3]; %! %! ## Define a prior covariance assuming that the sqrt component is not present %! Sp = 100 * eye (size (px, 2) + 1); %! Sp(2,2) = 1; # We don't believe the sqrt(abs(X)) is present %! %! ## Fit regression model %! [Yfit, Yint, m] = regress_gp (px, Y, pxi, Sp); %! %! ## Plot fitted data %! plot (X, Y, 'xk;Data;', Xfit, Yfit, 'r-;Estimation;', ... %! Xfit, polyval (pp, Xfit), 'g-;True;'); %! axis tight %! axis manual %! hold on %! plot (Xfit, Yint(:,1), 'b-;Lower bound;', ... %! Xfit, Yint(:,2), 'm-;Upper bound;'); %! hold off %! title ('Linear kernel over basis function with prior covariance'); %!demo %! ## Projection over basis function with linear kernel %! rng (42); %! pp = [2, 2, 0.3, 1]; %! n = 10; %! X = 2 * rand (n, 1) - 1; %! Y = polyval (pp, X) + 0.3 * randn (n, 1); %! %! ## Powers %! px = [sqrt(abs(X)), X, X.^2, X.^3]; %! %! ## Points for interpolation/extrapolation %! Xfit = linspace (-1, 1, 100)'; %! pxi = [sqrt(abs(Xfit)), Xfit, Xfit.^2, Xfit.^3]; %! %! ## Fit regression model without any assumption on prior covariance %! [Yfit, Yint, m] = regress_gp (px, Y, pxi); %! %! ## Plot fitted data %! plot (X, Y, 'xk;Data;', Xfit, Yfit, 'r-;Estimation;', ... %! Xfit, polyval (pp, Xfit), 'g-;True;'); %! axis tight %! axis manual %! hold on %! plot (Xfit, Yint(:,1), 'b-;Lower bound;', ... %! Xfit, Yint(:,2), 'm-;Upper bound;'); %! hold off %! title ('Linear kernel over basis function without prior covariance'); %!demo %! ## Projection over basis function with rbf kernel %! rng (42); %! pp = [2, 2, 0.3, 1]; %! n = 10; %! X = 2 * rand (n, 1) - 1; %! Y = polyval (pp, X) + 0.3 * randn (n, 1); %! %! ## Powers %! px = [sqrt(abs(X)), X, X.^2, X.^3]; %! %! ## Points for interpolation/extrapolation %! Xfit = linspace (-1, 1, 100)'; %! pxi = [sqrt(abs(Xfit)), Xfit, Xfit.^2, Xfit.^3]; %! %! ## Fit regression model with RBF kernel (standard parameters) %! [Yfit, Yint, Ysd] = regress_gp (px, Y, pxi, 'rbf'); %! %! ## Plot fitted data %! plot (X, Y, 'xk;Data;', Xfit, Yfit, 'r-;Estimation;', ... %! Xfit, polyval (pp, Xfit), 'g-;True;'); %! axis tight %! axis manual %! hold on %! plot (Xfit, Yint(:,1), 'b-;Lower bound;', ... %! Xfit, Yint(:,2), 'm-;Upper bound;'); %! hold off %! title ('RBF kernel over basis function with standard parameters'); %! text (-0.5, 4, "theta = 5\n g = 0.01"); %!demo %! ## Projection over basis function with rbf kernel %! rng (42); %! pp = [2, 2, 0.3, 1]; %! n = 10; %! X = 2 * rand (n, 1) - 1; %! Y = polyval (pp, X) + 0.3 * randn (n, 1); %! %! ## Powers %! px = [sqrt(abs(X)), X, X.^2, X.^3]; %! %! ## Points for interpolation/extrapolation %! Xfit = linspace (-1, 1, 100)'; %! pxi = [sqrt(abs(Xfit)), Xfit, Xfit.^2, Xfit.^3]; %! %! ## Fit regression model with RBF kernel with different parameters %! [Yfit, Yint, Ysd] = regress_gp (px, Y, pxi, 'rbf', 10, 0.01); %! %! ## Plot fitted data %! plot (X, Y, 'xk;Data;', Xfit, Yfit, 'r-;Estimation;', ... %! Xfit, polyval (pp, Xfit), 'g-;True;'); %! axis tight %! axis manual %! hold on %! plot (Xfit, Yint(:,1), 'b-;Lower bound;', ... %! Xfit, Yint(:,2), 'm-;Upper bound;'); %! hold off %! title ('GP regression with RBF kernel and non default parameters'); %! text (-0.5, 4, "theta = 10\n g = 0.01"); %! %! ## Fit regression model with RBF kernel with different parameters %! [Yfit, Yint, Ysd] = regress_gp (px, Y, pxi, 'rbf', 50, 0.01); %! %! ## Plot fitted data %! figure %! plot (X, Y, 'xk;Data;', Xfit, Yfit, 'r-;Estimation;', ... %! Xfit, polyval (pp, Xfit), 'g-;True;'); %! axis tight %! axis manual %! hold on %! plot (Xfit, Yint(:,1), 'b-;Lower bound;', ... %! Xfit, Yint(:,2), 'm-;Upper bound;'); %! hold off %! title ('GP regression with RBF kernel and non default parameters'); %! text (-0.5, 4, "theta = 50\n g = 0.01"); %! %! ## Fit regression model with RBF kernel with different parameters %! [Yfit, Yint, Ysd] = regress_gp (px, Y, pxi, 'rbf', 50, 0.001); %! %! ## Plot fitted data %! figure %! plot (X, Y, 'xk;Data;', Xfit, Yfit, 'r-;Estimation;', ... %! Xfit, polyval (pp, Xfit), 'g-;True;'); %! axis tight %! axis manual %! hold on %! plot (Xfit, Yint(:,1), 'b-;Lower bound;', ... %! Xfit, Yint(:,2), 'm-;Upper bound;'); %! hold off %! title ('GP regression with RBF kernel and non default parameters'); %! text (-0.5, 4, "theta = 50\n g = 0.001"); %! %! ## Fit regression model with RBF kernel with different parameters %! [Yfit, Yint, Ysd] = regress_gp (px, Y, pxi, 'rbf', 50, 0.05); %! %! ## Plot fitted data %! figure %! plot (X, Y, 'xk;Data;', Xfit, Yfit, 'r-;Estimation;', ... %! Xfit, polyval (pp, Xfit), 'g-;True;'); %! axis tight %! axis manual %! hold on %! plot (Xfit, Yint(:,1), 'b-;Lower bound;', ... %! Xfit, Yint(:,2), 'm-;Upper bound;'); %! hold off %! title ('GP regression with RBF kernel and non default parameters'); %! text (-0.5, 4, "theta = 50\n g = 0.05"); %!demo %! ## RBF fitting on noiseless 1D Data %! rng (42); %! x = [0:2*pi/7:2*pi]'; %! y = 5 * sin (x); %! %! ## Predictive grid of 500 equally spaced locations %! xi = [-0.5:(2*pi+1)/499:2*pi+0.5]'; %! %! ## Fit regression model with RBF kernel %! [Yfit, Yint, Ysd] = regress_gp (x, y, xi, 'rbf'); %! %! ## Plot fitted data %! r = mvnrnd (Yfit, diag (Ysd)', 50); %! plot (xi, r', 'c-'); %! hold on %! plot (xi, Yfit, 'r-;Estimation;', xi, Yint, 'b-;Confidence interval;'); %! plot (x, y, '.k;Predictor points;', 'markersize', 20) %! plot (xi, 5 * sin (xi), '-y;True Function;'); %! xlim ([-0.5,2*pi+0.5]); %! ylim ([-10,10]); %! hold off %! title ('GP regression with RBF kernel on noiseless 1D data'); %! text (0, -7, "theta = 5\n g = 0.01"); %!demo %! ## RBF fitting on noisy 1D Data %! rng (42); %! x = [0:2*pi/7:2*pi]'; %! x = [x; x]; %! y = 5 * sin (x) + randn (size (x)); %! %! ## Predictive grid of 500 equally spaced locations %! xi = [-0.5:(2*pi+1)/499:2*pi+0.5]'; %! %! ## Fit regression model with RBF kernel %! [Yfit, Yint, Ysd] = regress_gp (x, y, xi, 'rbf'); %! %! ## Plot fitted data %! r = mvnrnd (Yfit, diag (Ysd)', 50); %! plot (xi, r', 'c-'); %! hold on %! plot (xi, Yfit, 'r-;Estimation;', xi, Yint, 'b-;Confidence interval;'); %! plot (x, y, '.k;Predictor points;', 'markersize', 20) %! plot (xi, 5 * sin (xi), '-y;True Function;'); %! xlim ([-0.5,2*pi+0.5]); %! ylim ([-10,10]); %! hold off %! title ('GP regression with RBF kernel on noisy 1D data'); %! text (0, -7, "theta = 5\n g = 0.01"); ## Test input validation ## The value tests this function never had. Its two branches are checked ## against closed forms computed here, and the RBF branch additionally against ## RegressionGP, whose arithmetic is verified against MATLAB. %!test %! ## The RBF mean is the Gaussian process posterior mean, which RegressionGP %! ## computes too. Matching the parameterisations: this function writes the %! ## kernel as exp (-d2 / theta) with a noise variance g added, where %! ## RegressionGP writes it as SigmaF^2 * exp (-0.5 * d2 / SigmaL^2) with a %! ## noise standard deviation, so SigmaL = sqrt (theta/2), SigmaF = 1 and %! ## Sigma = sqrt (g). %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x); %! xq = [0.15; 0.45; 0.75]; %! theta = 0.5; %! g = 0.01; %! yf = regress_gp (x, y, xq, 'rbf', theta, g); %! Mdl = RegressionGP (x, y, 'KernelFunction', 'squaredexponential', ... %! 'KernelParameters', [sqrt(theta/2); 1], ... %! 'Sigma', sqrt (g), 'BasisFunction', 'none', ... %! 'FitMethod', 'none'); %! assert_equal (yf, predict (Mdl, xq), 1e-10); %!test %! ## The RBF interval is the normal quantile of the level times the standard %! ## deviation, on both sides of the fit %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x); %! xq = [0.2; 0.5; 0.8]; %! [yf, yi, ys] = regress_gp (x, y, xq, 'rbf'); %! sd = sqrt (diag (ys)); %! assert_equal (yi(:,1), yf - norminv (0.975) * sd, 1e-12); %! assert_equal (yi(:,2), yf + norminv (0.975) * sd, 1e-12); %!test %! ## A looser level gives a narrower interval, and the level reaches the %! ## interval at all, which it did not before %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x); %! xq = [0.2; 0.5; 0.8]; %! [~, yi95] = regress_gp (x, y, xq, 'rbf', 5, 0.01, 0.05); %! [~, yi80] = regress_gp (x, y, xq, 'rbf', 5, 0.01, 0.20); %! assert (all (yi80(:,2) - yi80(:,1) < yi95(:,2) - yi95(:,1))); %!test %! ## The linear mean is the Bayesian linear regression posterior mean %! x = linspace (0, 1, 15)'; %! y = 2 * x + 0.5; %! xq = [0.25; 0.75]; %! Sp = 100 * eye (2); %! yf = regress_gp (x, y, xq); %! Xd = [ones(1, 15); x']; %! wm = inv (Xd * Xd' + inv (Sp)) * Xd * y; %! assert_equal (yf, [ones(2, 1), xq] * wm, 1e-12); %!test %! ## The linear interval is built from a standard deviation and carries the %! ## confidence level, where it used to be a bare variance %! x = linspace (0, 1, 15)'; %! y = 2 * x + 0.5; %! xq = [0.25; 0.75]; %! [yf, yi, wm, K] = regress_gp (x, y, xq); %! sd = sqrt (diag ([ones(2, 1), xq] * K * [ones(2, 1), xq]')); %! assert_equal (yi(:,1), yf - norminv (0.975) * sd, 1e-12); %! assert_equal (yi(:,2), yf + norminv (0.975) * sd, 1e-12); %!test %! ## Both branches order the interval the same way, lower bound first, as %! ## every other prediction interval in the package does %! x = linspace (0, 1, 15)'; %! y = sin (3*x); %! xq = [0.3; 0.6]; %! [~, yiL] = regress_gp (x, y, xq, 'linear'); %! [~, yiR] = regress_gp (x, y, xq, 'rbf'); %! assert (all (yiL(:,1) < yiL(:,2))); %! assert (all (yiR(:,1) < yiR(:,2))); ## The Cholesky path and the predictive covariance must agree: the interval is ## built from the diagonal computed directly, the third output from the full ## matrix, and the two are formed by different routes. %!test %! rand ('seed', 91); %! x = 2 * rand (12, 1) - 1; %! y = sin (3 * x); %! xq = linspace (-1, 1, 6)'; %! [yf, yi, S] = regress_gp (x, y, xq, 'rbf'); %! assert_equal (yi(:,2) - yf, norminv (0.975) * sqrt (diag (S)), 1e-12); %! assert_equal (yf - yi(:,1), norminv (0.975) * sqrt (diag (S)), 1e-12); ## Asking for the covariance must not change the fit or the interval. %!test %! rand ('seed', 91); %! x = 2 * rand (12, 1) - 1; %! y = sin (3 * x); %! xq = linspace (-1, 1, 6)'; %! [yf2, yi2] = regress_gp (x, y, xq, 'rbf'); %! [yf3, yi3, S] = regress_gp (x, y, xq, 'rbf'); %! assert_equal (yf2, yf3); %! assert_equal (yi2, yi3); ## Without a nugget the process interpolates its training data. This used to ## be computed through inv () on a kernel matrix with an rcond of 1e-18, which ## warned and returned what that implies. %!test %! rand ('seed', 3); %! x = sort (2 * rand (10, 1) - 1); %! y = sin (3 * x) + 0.1; %! [yf, yi] = regress_gp (x, y, x, 'rbf', 1, 0); %! assert_equal (yf, y, 1e-4); %! assert (max (abs (yi(:,2) - yf)) < 1e-2); ## The linear branch reports the posterior covariance of the weights as its ## fourth output, and the interval must follow that same matrix. %!test %! rand ('seed', 17); %! x = 2 * rand (10, 2) - 1; %! y = x(:,1) - 2 * x(:,2) + 0.1; %! xq = 2 * rand (4, 2) - 1; %! [yf, yi, wm, K] = regress_gp (x, y, xq); %! xa = [ones(4, 1), xq]; %! assert_equal (yi(:,2) - yf, ... %! norminv (0.975) * sqrt (diag (xa * K * xa')), 1e-12); %!error regress_gp (ones (20, 2)) %!error regress_gp (ones (20, 2), ones (20, 1)) %!error ... %! regress_gp (ones (20, 2, 3), ones (20, 1), ones (20, 2)) %!error ... %! regress_gp (ones (20, 2), ones (20, 2), ones (20, 2)) %!error ... %! regress_gp (ones (20, 2), ones (15, 1), ones (20, 2)) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (20, 3)) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), {[3]}) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 'kernel') %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 'rbf', ones (4)) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (20, 2), 5, 'junk') %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (20, 2), zeros (3, 3)) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (20, 2), 'linear', zeros (3, 3)) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (20, 2), eye (4)) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 'linear', 1) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 'rbf', 'value') %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 'rbf', {5}) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), eye (3), 5) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 'linear', 5) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 'rbf', 5, {5}) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 'rbf', 5, ones (2)) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 5, 0.01, [1, 1]) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 5, 0.01, 'f') %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 5, 0.01, 'f') %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 'rbf', 5, 0.01, 'f') %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 'rbf', 5, 0.01, [1, 1]) %!error ... %! regress_gp (ones (20, 2), ones (20, 1), ones (10, 2), 'linear', 1) statistics-release-1.9.2/inst/Regression/ridge.m000066400000000000000000000157031524624707500217220ustar00rootroot00000000000000## Copyright (C) 2023 Mohammed Azmat Khan ## ## This file is part of the statistics package for GNU Octave. ## ## Octave is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## Octave is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Octave; see the file COPYING. If not, ## see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{b} =} ridge (@var{y}, @var{X}, @var{k}) ## @deftypefnx {statistics} {@var{b} =} ridge (@var{y}, @var{X}, @var{k}, @var{scaled}) ## ## Ridge regression. ## ## @code{@var{b} = ridge (@var{y}, @var{X}, @var{k})} returns the vector of ## coefficient estimates by applying ridge regression from the predictor matrix ## @var{X} to the response vector @var{y}. Each value of @var{b} is the ## coefficient for the respective ridge parameter given @var{k}. By default, ## @var{b} is calculated after centering and scaling the predictors to have a ## zero mean and standard deviation 1. ## ## @code{@var{b} = ridge (@var{y}, @var{X}, @var{k}, @var{scaled})} performs the ## regression with the specified scaling of the coefficient estimates @var{b}. ## When @qcode{@var{scaled} = 0}, the function restores the coefficients to the ## scale of the original data thus is more useful for making predictions. When ## @qcode{@var{scaled} = 1}, the coefficient estimates correspond to the scaled ## centered data. ## ## @itemize ## @item ## @code{y} must be an @math{N*1} numeric vector with the response data. ## @item ## @code{X} must be an @math{N*p} numeric matrix with the predictor data. ## @item ## @code{k} must be a numeric vector with the ridge parameters. ## @item ## @code{scaled} must be a numeric scalar indicating whether the coefficient ## estimates in @var{b} are restored to the scale of the original data. By ## default, @qcode{@var{scaled} = 1}. ## @end itemize ## ## Further information about Ridge regression can be found at ## @url{https://en.wikipedia.org/wiki/Ridge_regression} ## ## @seealso{lasso, stepwisefit, regress} ## @end deftypefn function b = ridge (y, X, k, scaled) ## Check input arguments if (nargin < 3) error ("ridge: function called with too few input arguments."); endif if (! isvector (y) || columns (y) != 1 || isempty (y)) error ("ridge: Y must be a numeric column vector."); endif if (! ismatrix (X) || isempty (X)) error ("ridge: X must be a numeric matrix."); endif if (rows (y) != rows (X)) error ("ridge: Y and X must contain the same number of rows."); endif ## Parse 4th input argument if (nargin < 4 || isempty (scaled)) unscale = false; elseif (scaled == 1) unscale = false; elseif (scaled == 0) unscale = true; else error ("ridge: wrong value for SCALED argument."); endif ## Force y to a column vector y = y(:); ## Remove any missing values notnans = ! logical (sum (isnan ([y, X]), 2)); y = y(notnans); X = X(notnans,:); ## Scale and center X to zero mean and StD = 1 m = mean (X); stdx = std (X, 0, 1); z = (X - m) ./ stdx; ## Add pseudo observations p = columns (X); I = eye (p); Z_pseudo = [z; (sqrt(k(1)) .* I)]; Y_pseudo = [y; zeros(p, 1)]; ## Compute coefficients b = Z_pseudo \ Y_pseudo; nk = numel (k); ## Compute the coefficient estimates for additional ridge parameters. if (nk >= 2) ## Adding a multiple of the identity matrix to the last p rows. ## b is set to 0 for the current ridge parameter value b(end,nk) = 0; for i=2:nk Z_pseudo(end-p+1:end, :) = sqrt (k(i)) .* I; b(:,i) = Z_pseudo \ Y_pseudo; endfor endif ## Changing back to the scale if (unscale) b = b ./ repmat (stdx', 1, nk); b = [mean(y)-m*b; b]; endif endfunction %!demo %! ## Perform ridge regression for a range of ridge parameters and observe %! ## how the coefficient estimates change based on the acetylene dataset. %! %! load acetylene %! %! X = [x1, x2, x3]; %! %! x1x2 = x1 .* x2; %! x1x3 = x1 .* x3; %! x2x3 = x2 .* x3; %! %! D = [x1, x2, x3, x1x2, x1x3, x2x3]; %! %! k = 0:1e-5:5e-3; %! %! b = ridge (y, D, k); %! %! figure %! plot (k, b, 'LineWidth', 2) %! ylim ([-100, 100]) %! grid on %! xlabel ('Ridge Parameter') %! ylabel ('Standardized Coefficient') %! title ('Ridge Trace') %! legend ('x1', 'x2', 'x3', 'x1x2', 'x1x3', 'x2x3') %! %!demo %! %! rng (42); %! load carbig %! X = [Acceleration Weight Displacement Horsepower]; %! y = MPG; %! %! n = length (y); %! %! %! c = cvpartition (n,'HoldOut',0.3); %! idxTrain = training(c,1); %! idxTest = ! idxTrain; %! %! idxTrain = training(c,1); %! idxTest = ! idxTrain; %! %! k = 5; %! b = ridge (y(idxTrain),X(idxTrain,:),k,0); %! %! % Predict MPG values for the test data using the model. %! yhat = b(1) + X(idxTest,:)*b(2:end); %! scatter (y(idxTest),yhat) %! %! hold on %! plot (y(idxTest),y(idxTest),'r') %! xlabel ('Actual MPG') %! ylabel ('Predicted MPG') %! hold off %! ## Test output %!test %! b = ridge ([1 2 3 4]', [1 2 3 4; 2 3 4 5]', 1); %! assert_equal (b, [0.5533; 0.5533], 1e-4); %!test %! b = ridge ([1 2 3 4]', [1 2 3 4; 2 3 4 5]', 2); %! assert_equal (b, [0.4841; 0.4841], 1e-4); %!test %! load acetylene %! x = [x1, x2, x3]; %! b = ridge (y, x, 0); %! assert_equal (b,[10.2273;1.97128;-0.601818],1e-4); %!test %! load acetylene %! x = [x1, x2, x3]; %! b = ridge (y, x, 0.0005); %! assert_equal (b,[10.2233;1.9712;-0.6056],1e-4); %!test %! load acetylene %! x = [x1, x2, x3]; %! b = ridge (y, x, 0.001); %! assert_equal (b,[10.2194;1.9711;-0.6094],1e-4); %!test %! load acetylene %! x = [x1, x2, x3]; %! b = ridge (y, x, 0.002); %! assert_equal (b,[10.2116;1.9709;-0.6169],1e-4); %!test %! load acetylene %! x = [x1, x2, x3]; %! b = ridge (y, x, 0.005); %! assert_equal (b,[10.1882;1.9704;-0.6393],1e-4); %!test %! load acetylene %! x = [x1, x2, x3]; %! b = ridge (y, x, 0.01); %! assert_equal (b,[10.1497;1.9695;-0.6761],1e-4); ## Test input validation %!error ridge (1) %!error ridge (1, 2) %!error ridge (ones (3), ones (3), 2) %!error ridge ([1, 2], ones (2), 2) %!error ridge ([], ones (3), 2) %!error ridge (ones (5,1), [], 2) %!error ... %! ridge ([1; 2; 3; 4; 5], ones (3), 3) %!error ... %! ridge ([1; 2; 3], ones (3), 3, 2) %!error ... %! ridge ([1; 2; 3], ones (3), 3, 'some') statistics-release-1.9.2/inst/Regression/robustfit.m000066400000000000000000000314111524624707500226430ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{b} =} robustfit (@var{X}, @var{y}) ## @deftypefnx {statistics} {@var{b} =} robustfit (@var{X}, @var{y}, @var{wfun}) ## @deftypefnx {statistics} {@var{b} =} robustfit (@var{X}, @var{y}, @var{wfun}, @var{tune}) ## @deftypefnx {statistics} {@var{b} =} robustfit (@var{X}, @var{y}, @var{wfun}, @var{tune}, @var{const}) ## @deftypefnx {statistics} {[@var{b}, @var{stats}] =} robustfit (@dots{}) ## ## Robust linear regression. ## ## @code{@var{b} = robustfit (@var{X}, @var{y})} returns the coefficient vector ## @var{b} of a linear regression of the response @var{y} on the predictors ## @var{X}, fitted by robust M-estimation (iteratively reweighted least squares) ## so that outlying observations are downweighted. A column of ones is added to ## @var{X} by default, so @code{@var{b}(1)} is the intercept. ## ## @code{@var{b} = robustfit (@var{X}, @var{y}, @var{wfun}, @var{tune}, ## @var{const})} selects the weight function @var{wfun}, its tuning constant ## @var{tune}, and whether a constant term is included. @var{wfun} is one of ## @qcode{'bisquare'} (default), @qcode{'andrews'}, @qcode{'cauchy'}, ## @qcode{'fair'}, @qcode{'huber'}, @qcode{'logistic'}, @qcode{'ols'}, ## @qcode{'talwar'}, @qcode{'welsch'}, or a function handle @code{@@(r)} giving ## the weights as a function of the scaled residual. @var{tune} defaults to the ## value that gives 95% efficiency for each weight function. @var{const} is ## @qcode{'on'} (default) to include a constant term or @qcode{'off'} to omit. ## ## @code{[@var{b}, @var{stats}] = robustfit (@dots{})} also returns a structure ## @var{stats} with fields @code{ols_s}, @code{robust_s}, @code{mad_s}, ## @code{s}, @code{se}, @code{covb}, @code{coeffcorr}, @code{t}, @code{p}, ## @code{w}, @code{R}, @code{dfe}, @code{h}, and @code{resid}. The coefficients ## and the fields @code{ols_s}, @code{mad_s}, @code{dfe}, @code{h}, @code{w}, ## and @code{resid} match MATLAB. The standard errors and quantities derived ## from them (@code{se}, @code{t}, @code{p}, @code{covb}) agree with MATLAB to ## within a small fraction of a percent; @code{robust_s} is the ## Street-Carroll-Ruppert robust scale estimate and differs from MATLAB's by ## about 1.5%, measured, with a negligible effect on the standard errors. ## ## That difference is left in place deliberately. The squared influence is ## averaged here over @math{n}, where the estimator as it is usually published ## averages over @math{n-p}; taking that published form moves the result ## further from MATLAB rather than closer, so MATLAB implements neither, and ## matching it would mean reproducing an undocumented variant. The same scale ## serves @code{nlinfit}, which is why its robust @code{MSE} and @code{CovB} ## carry the same difference. ## ## @seealso{regress, fitlm} ## @end deftypefn function [b, stats] = robustfit (X, y, wfun, tune, const) if (nargin < 2) print_usage (); endif if (! (isnumeric (X) && isreal (X) && ismatrix (X))) error ("robustfit: X must be a real matrix."); endif if (! (isnumeric (y) && isreal (y) && isvector (y))) error ("robustfit: Y must be a real vector."); endif y = y(:); if (isvector (X) && rows (X) == 1 && columns (X) == numel (y)) X = X(:); endif if (rows (X) != numel (y)) error ("robustfit: X and Y must have the same number of observations."); endif if (nargin < 3 || isempty (wfun)) wfun = "bisquare"; endif if (ischar (wfun)) wfun = lower (wfun); if (! any (strcmp (wfun, {"bisquare", "andrews", "cauchy", "fair", ... "huber", "logistic", "ols", "talwar", "welsch"}))) error ("robustfit: unrecognised weight function '%s'.", wfun); endif elseif (! is_function_handle (wfun)) error ("robustfit: WFUN must be a name or a function handle."); endif if (nargin < 4 || isempty (tune)) tune = robusttune (wfun); elseif (! (isnumeric (tune) && isscalar (tune) && isreal (tune) && tune > 0)) error ("robustfit: TUNE must be a positive scalar."); endif if (nargin < 5 || isempty (const)) const = "on"; endif if (! (ischar (const) && any (strcmpi (const, {"on", "off"})))) error ("robustfit: CONST must be 'on' or 'off'."); endif addconst = strcmpi (const, "on"); ## Drop observations with missing values. ok = ! (any (isnan (X), 2) | isnan (y)); n0 = numel (y); X = X(ok,:); y = y(ok); if (addconst) X = [ones(rows (X), 1), X]; endif [n, p] = size (X); if (n <= p) error ("robustfit: not enough observations for the number of parameters."); endif ## Ordinary least squares start, leverages, and the OLS scale. [Q, R, perm] = qr (X, 0); b = zeros (p, 1); b(perm) = R \ (Q' * y); hlev = min (0.9999, sum (Q .^ 2, 2)); adj = 1 ./ sqrt (1 - hlev); dfe = n - p; ols_s = norm (y - X * b) / sqrt (dfe); ## Iteratively reweighted least squares. bprev = b; for iter = 1:50 r = y - X * b; radj = r .* adj; s = madsigma (radj, p, y); w = robustwfun (radj / (s * tune), wfun); sw = sqrt (w); b(perm) = (X(:,perm) .* sw) \ (y .* sw); if (all (abs (b - bprev) <= sqrt (eps) * max (abs (b), abs (bprev)))) break; endif bprev = b; endfor if (nargout < 2) return; endif ## Robust scale and coefficient covariance. The reported weights stay the ## ones the last iteration actually used, which came from the floored ## scale; the scale below is computed from the unfloored one, which is what ## MATLAB reports as mad_s and what its robust_s is built from. Recomputing ## the weights here and reporting those instead graded them out of rounding ## noise on a fit that is already exact. r = y - X * b; radj = r .* adj; mad_s = madsigma (radj, p); [wc, psi, psip] = robustwfun (radj / (mad_s * tune), wfun); if (all (wc == 1)) robust_s = ols_s; else K = 1 + (p / n) * var (psip) / mean (psip) ^ 2; robust_s = tune * mad_s * sqrt (mean (psi .^ 2)) / mean (psip) * K; endif ## Combine the OLS and robust scales (larger of the two, blended by n and p). s = sqrt ((p ^ 2 * ols_s ^ 2 + n * robust_s ^ 2) / (n + p ^ 2)); s = max (s, robust_s); covb = s ^ 2 * inv (X' * X); se = sqrt (diag (covb)); se_outer = se * se'; coeffcorr = covb ./ se_outer; t = b ./ se; pval = 2 * tcdf (-abs (t), dfe); stats.ols_s = ols_s; stats.robust_s = robust_s; stats.mad_s = mad_s; stats.s = s; stats.se = se; stats.covb = covb; stats.coeffcorr = coeffcorr; stats.t = t; stats.p = pval; stats.w = unfilter (w, ok, n0); stats.R = R; stats.dfe = dfe; stats.h = unfilter (hlev, ok, n0); stats.resid = unfilter (r, ok, n0); endfunction ## Scatter a per-observation vector back to the original length, with NaN for ## observations that were dropped for missing values. function out = unfilter (v, ok, n0) if (all (ok)) out = v; else out = NaN (n0, 1); out(ok) = v; endif endfunction %!demo %! ## Robust fit is resistant to an outlier that pulls the OLS line %! x = (1:10)'; %! y = 2 * x + 1; %! y(10) = 0; # an outlier %! b_ols = regress (y, [ones(10,1), x]); %! b_rob = robustfit (x, y); %! plot (x, y, "o", x, [ones(10,1) x]*b_ols, "r-", ... %! x, [ones(10,1) x]*b_rob, "b-"); %! legend ("data", "OLS", "robust", "location", "northwest"); %!shared X, y %! X = [1;2;3;4;5;6;7;8;9;10]; %! y = [3.1;5.2;6.9;9.1;11.0;12.9;15.2;17.1;19.0;5.0]; %!test # MATLAB parity: bisquare coefficients and exact stats fields %! [b, st] = robustfit (X, y); %! assert_equal (b, [1.08223788958791; 1.9947584179332], 1e-8); %! assert_equal (st.ols_s, 4.58673317428878, 1e-8); %! assert_equal (st.mad_s, 0.219326216641263, 1e-8); %! assert_equal (st.dfe, 8); %! assert_equal (st.h(1), 0.345454545454545, 1e-9); %! assert_equal (st.w(10), 0, 1e-10); %! assert_equal (st.resid(10), -16.0298220689199, 1e-6); %!test # MATLAB parity: standard errors, t, p within a fraction of a percent %! [b, st] = robustfit (X, y); %! assert_equal (st.s, 2.45430591997485, 5e-3); %! assert_equal (st.se, [1.67661012843903; 0.270210188642742], 2e-3); %! assert_equal (st.t, [0.64549168064224; 7.38224723483898], 5e-3); %! assert_equal (st.p, [0.536678473587217; 7.75017584465372e-05], 5e-3); %! assert_equal (st.covb, [2.81102152278434, -0.401574503254905; ... %! -0.401574503254905, 0.0730135460463465], 1e-2); %!test # weight-function coefficients match MATLAB %! assert_equal (robustfit (X, y, "huber"), ... %! [1.13942072329047; 1.97894586334502], 1e-6); %! assert_equal (robustfit (X, y, "andrews"), ... %! [1.08226666865865; 1.99475428605778], 1e-6); %! assert_equal (robustfit (X, y, "cauchy"), ... %! [1.08721258646419; 1.99357974946387], 1e-6); %! assert_equal (robustfit (X, y, "fair"), ... %! [1.25156505474148; 1.94736889966414], 1e-6); %! assert_equal (robustfit (X, y, "logistic"), ... %! [1.14596301338789; 1.9773188568744], 1e-6); %! assert_equal (robustfit (X, y, "talwar"), [1.08055555555555; 1.995], 1e-6); %! assert_equal (robustfit (X, y, "welsch"), ... %! [1.08260376495793; 1.99470613677464], 1e-6); %!test # 'ols' weight reproduces ordinary least squares %! assert_equal (robustfit (X, y, "ols"), regress (y, [ones(10,1), X]), 1e-10); %!test # custom tuning constant and const='off' %! assert_equal (robustfit (X, y, "bisquare", 3), ... %! [1.08512259456964; 1.99435302345156], 1e-6); %! [b, st] = robustfit (X, y, "bisquare", 4.685, "off"); %! assert_equal (b, 2.16460032959659, 1e-6); %! assert_equal (st.dfe, 9); %!test # MATLAB parity: an exactly zero residual weighs 1, not 0 %! ## andrews and logistic are w = sin (z) / z and tanh (z) / z, both 0/0 at %! ## the origin. With no constant term the middle residual is exactly zero %! ## whatever the coefficient, so this reaches the limit every time. %! xz = [-1; 0; 1]; %! yz = [-2; 0; 3]; %! [bz, sz] = robustfit (xz, yz, "andrews", [], "off"); %! assert_equal (sz.resid(2), 0); %! assert_equal (sz.w, [0.958241991423609; 1; 0.958241991423609], 1e-12); %! [bz, sz] = robustfit (xz, yz, "logistic", [], "off"); %! assert_equal (sz.resid(2), 0); %! assert_equal (sz.w, [0.907175968590982; 1; 0.907175968590982], 1e-12); %!test # a planted outlier is downweighted relative to OLS %! x = (1:20)'; %! yy = 3 * x - 5; yy(7) = yy(7) + 100; %! b = robustfit (x, yy); %! assert_equal (b, [-5; 3], 0.1); %!test # missing observations are dropped, weights padded with NaN %! x = (1:10)'; yy = 2*x + 1; yy(4) = NaN; %! [b, st] = robustfit (x, yy); %! assert_equal (b, [1; 2], 1e-8); %! assert_equal (isnan (st.w(4)), true); %! assert_equal (st.dfe, 7); ## A fit that is already exact keeps every weight at 1. The scale that ## weights an iteration is floored at 1e-6 * std (Y), so residuals that are ## nothing but rounding noise cannot grade the weights out of it; R2024a ## returns ones here and used to differ from us by 0.24 on the outermost. %!test %! xf = [-2; -1; 0; 1; 2]; %! yf = 3 * xf; %! [bf, stats] = robustfit (xf, yf, 'andrews', [], 'off'); %! assert_equal (bf, 3, 1e-12); %! assert_equal (stats.w, ones (5, 1), 1e-8); ## Just below the floor the weights are graded, but only slightly, and these ## are R2024a's own numbers. %!test %! xf = [-2; -1; 0; 1; 2]; %! yf = 3 * xf + 1e-6 * [1; -1; 0; 1; -1]; %! [bf, stats] = robustfit (xf, yf, 'andrews', [], 'off'); %! assert_equal (stats.w', [0.997523, 0.993403, 1, 0.993403, 0.997523], 1e-6); ## Well above the floor nothing is floored and the weights are the ordinary ## ones, again R2024a's. %!test %! xf = [-2; -1; 0; 1; 2]; %! yf = 3 * xf + 1e-3 * [1; -1; 0; 1; -1]; %! [bf, stats] = robustfit (xf, yf, 'andrews', [], 'off'); %! assert_equal (stats.w', [0.958242, 0.868203, 1, 0.868203, 0.958242], 1e-6); ## Test input validation %!error robustfit (1) %!error ... %! robustfit ([1;2;3], [1;2]) %!error ... %! robustfit (X, y, "foo") %!error ... %! robustfit (X, y, "huber", -1) %!error ... %! robustfit (X, y, "huber", 1.345, "maybe") statistics-release-1.9.2/inst/Regression/stepwisefit.m000066400000000000000000000522671524624707500232040ustar00rootroot00000000000000## Copyright (C) 2013-2021 Nir Krakauer ## Copyright (C) 2014 Mikael Kurula ## Copyright (C) 2025 Jayant Chauhan <0001jayant@gmail.com> ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {} stepwisefit (@var{X}, @var{y}) ## @deftypefnx {statistics} {@var{b} =} stepwisefit (@var{X}, @var{y}) ## @deftypefnx {statistics} {@var{b}, @var{se}, @var{pval}, @var{finalmodel}, @var{stats}, @var{nextstep}, @var{history} =} stepwisefit (@var{X}, @var{y}, @var{varargin}) ## ## Perform stepwise linear regression using conditional p-value criteria. ## ## @code{stepwisefit} fits a linear regression model to response vector ## @var{y} using predictor matrix @var{X} and performs stepwise variable ## selection based on hypothesis tests for individual regression coefficients. ## ## At each iteration, predictors not currently in the model are tested for ## inclusion using partial F- or t-tests. The predictor with the smallest ## p-value below the entry threshold is added. Predictors currently in the ## model (excluding forced predictors) are then tested for removal, and the ## predictor with the largest p-value exceeding the removal threshold is ## removed. The procedure repeats until the model stabilizes or the maximum ## number of iterations is reached. ## ## After variable selection, the final regression model is refit using ## @code{regress} to compute coefficient estimates and inferential statistics ## for both included and excluded predictors. ## ## @subheading Arguments ## ## @itemize @bullet ## @item ## @var{X} is an @var{n}-by-@var{p} numeric matrix of predictor variables. ## ## @item ## @var{y} is an @var{n}-by-1 numeric response vector. ## ## @item ## Optional Name-Value pairs may be supplied to control the stepwise ## selection procedure. ## @end itemize ## ## @subheading Name-Value Arguments ## ## @table @asis ## @item @qcode{'InModel'} ## Logical row vector of length @var{p} specifying predictors that are initially ## included in the model. ## ## @item @qcode{'Keep'} ## Logical row vector of length @var{p} specifying predictors that must remain ## in the model and are never removed during stepwise selection. ## ## @item @qcode{'PEnter'} ## Scalar significance level in the open interval (0,1) specifying the maximum ## p-value required for a predictor to enter the model. Default is @code{0.05}. ## ## @item @qcode{'PRemove'} ## Scalar significance level in the open interval (0,1) specifying the minimum ## p-value required for a predictor to be removed from the model. If not ## specified, a default value greater than or equal to @qcode{'PEnter'} is used. ## ## @item @qcode{'MaxIter'} ## Positive integer specifying the maximum number of stepwise iterations. ## Default is @code{Inf}. ## ## @item @qcode{'Scale'} ## Either @qcode{'on'} or @qcode{'off'}. When enabled, predictors are ## standardized prior to stepwise selection only. Final regression ## coefficients are always reported on the original data scale. ## ## @item @qcode{'Display'} ## Either @qcode{'on'} or @qcode{'off'}. Accepted for compatibility but ## currently does not affect output. ## @end table ## ## @subheading Return Values ## ## @itemize @bullet ## @item ## @var{b} is a @var{p}-by-1 vector of regression coefficients. Coefficients ## for excluded predictors are computed conditionally. ## ## @item ## @var{se} is a @var{p}-by-1 vector of standard errors. ## ## @item ## @var{pval} is a @var{p}-by-1 vector of two-sided p-values. ## ## @item ## @var{finalmodel} is a logical row vector indicating which predictors are ## included in the final model. ## ## @item ## @var{stats} is a structure containing regression diagnostics, including ## sums of squares, degrees of freedom, residuals, covariance estimates, ## F-statistic, and related quantities. ## ## @item ## @var{nextstep} is a scalar indicating whether an additional stepwise ## iteration is recommended. Currently always zero. ## ## @item ## @var{history} is a structure summarizing the final model state, including ## selected predictors and coefficient history. ## @end itemize ## ## @seealso{regress} ## @end deftypefn function [b, se, pval, finalmodel, stats, nextstep, history] = ... stepwisefit (X, y, varargin) b = []; se = []; pval = []; finalmodel = []; stats = struct (); nextstep = 0; history = struct (); ## Input validation (positional) if (nargin < 2) error ("stepwisefit: at least two input arguments required"); endif if (! ismatrix (X) || ! isvector (y)) error ("stepwisefit: X must be a matrix and y a vector"); endif y = y(:); ## Validate row compatibility BEFORE any concatenation if (rows (X) != rows (y)) error ("stepwisefit: X must be a matrix and y a vector"); endif ## Parse Name-Value pairs InModel = []; Display = 'on'; ## MATLAB-compatible defaults PEnter = 0.05; PRemove = []; Scale = 'off'; MaxIter = Inf; Keep = []; ## Parse Name-Value paired arguments optNames = {'InModel', 'Display', 'PEnter', 'PRemove', ... 'Scale', 'MaxIter', 'Keep'}; dfValues = {[], 'on', 0.05, [], 'off', Inf, []}; [InModel, Display, PEnter, PRemove, Scale, MaxIter, Keep, args] = ... parsePairedArguments (optNames, dfValues, varargin(:)); ## Semantic validation for Name-Value options ## Validate Display if (! any (strcmpi (Display, {'on', 'off'}))) error ("stepwisefit: Display must be 'on' or 'off'"); endif Display = lower (Display); ## Validate Scale if (! any (strcmpi (Scale, {'on', 'off'}))) error ("stepwisefit: Scale must be 'on' or 'off'"); endif Scale = lower (Scale); ## Validate PEnter if (! (isscalar (PEnter) && isnumeric (PEnter) && PEnter > 0 && PEnter < 1)) error ("stepwisefit: PEnter must be a scalar strictly between 0 and 1"); endif ## Validate PRemove (if provided) if (! isempty (PRemove)) if (! (isscalar (PRemove) && isnumeric (PRemove) && PRemove > 0 && PRemove < 1)) error ("stepwisefit: PRemove must be a scalar strictly between 0 and 1"); endif if (PRemove < PEnter) error ("stepwisefit: PRemove must be greater than or equal to PEnter"); endif endif ## Validate MaxIter if (! (isscalar (MaxIter) && isnumeric (MaxIter) && MaxIter > 0 && fix (MaxIter) == MaxIter)) error ("stepwisefit: MaxIter must be a positive integer"); endif ## Handle missing values wasnan = any (isnan ([X y]), 2); Xc = X(! wasnan, :); yc = y(! wasnan); n = rows (Xc); p = columns (Xc); ## Validate Keep and InModel type (if provided) if (! isempty (Keep) && ! islogical (Keep)) error ("stepwisefit: Keep must be a logical vector"); endif if (! isempty (InModel) && ! islogical (InModel)) error ("stepwisefit: InModel must be a logical vector"); endif ## Validate lengths (these already exist in your file, but keep them here for order) if (! isempty (Keep) && numel (Keep) != p) error ("stepwisefit: Keep length must match number of predictors"); endif if (! isempty (InModel) && numel (InModel) != p) error ("stepwisefit: InModel length must match number of predictors"); endif if (! isempty (args)) error ("stepwisefit: unrecognized input arguments"); endif if (isempty (Keep)) Keep = false (1, p); endif ## Default PRemove if unset if (isempty (PRemove)) PRemove = max (PEnter, 0.1); endif if (PRemove < PEnter) error ("stepwisefit: PRemove must be greater than or equal to PEnter"); endif if (strcmp (Scale, 'on')) muX = mean (Xc, 1); sigX = std (Xc, 0, 1); sigX(sigX == 0) = 1; ## prevent division by zero Xs = (Xc - muX) ./ sigX; else Xs = Xc; endif ## Validate InModel if (! isempty (InModel)) cur = logical (InModel(:).'); else cur = false (1, p); endif ## Ensure Keep predictors are always in model (already set) cur(Keep) = true; prev = cur; iter = 0; ## Iterative selection: each iteration attempts ADD then REMOVE. while (iter < MaxIter) iter = iter + 1; ## ADD phase: evaluate candidates by conditional p-value candidates = find (! cur); if (! isempty (candidates)) best_p = Inf; best_j = -1; cols = find (cur); ## current included predictors (may be empty) for idx = 1:numel (candidates) j = candidates(idx); ## Build trial design: intercept, current included (if any), candidate j if (isempty (cols)) Xtry = [ones(n,1), Xs(:, j)]; else Xtry = [ones(n,1), Xs(:, cols), Xs(:, j)]; endif ## Regress and compute candidate p-value; skip singular/failed fits try [btry, binttry] = regress (yc, Xtry); catch continue; end_try_catch df_try = n - columns (Xtry); if (df_try <= 0) continue; endif se_try = (binttry(end,2) - btry(end)) / tinv (0.975, df_try); if (se_try <= 0 || ! isfinite (se_try)) continue; endif tstat = btry(end) / se_try; p_candidate = 2 * (1 - tcdf (abs (tstat), df_try)); ## Deterministic tie-break: first encountered when nearly equal if (p_candidate < best_p - eps) best_p = p_candidate; best_j = j; endif endfor ## Add best candidate only if it meets the PEnter threshold if (best_j > 0 && best_p < PEnter) cur(best_j) = true; endif endif ## REMOVE phase: compute conditional p-values for included predictors, ## remove worst (largest p) among removable predictors included = find (cur); removable = setdiff (included, find (Keep)); ## never remove Keep if (! isempty (removable)) Xfull = [ones(n,1), Xs(:, included)]; ## Regress current full model; guard against singular / failed fits try [bfull, bintfull] = regress (yc, Xfull); catch bfull = NaN (columns (Xfull), 1); bintfull = NaN (columns (Xfull), 2); end_try_catch df_full = n - columns (Xfull); pvals_included = Inf (1, numel (included)); for ii = 1:numel (included) if (df_full <= 0) pvals_included(ii) = Inf; else se_i = (bintfull(ii+1,2) - bfull(ii+1)) / tinv (0.975, df_full); if (se_i > 0 && isfinite (se_i)) t_i = bfull(ii+1) / se_i; pvals_included(ii) = 2 * (1 - tcdf (abs (t_i), df_full)); else pvals_included(ii) = Inf; endif endif endfor ## Map removable predictors to positions within 'included' [~, removable_positions] = ismember (removable, included); [maxp, pos] = max (pvals_included(removable_positions)); if (maxp > PRemove) cur(removable(pos)) = false; endif endif ## Convergence: structural (model unchanged) if (isequal (cur, prev)) break; endif prev = cur; endwhile ## Final set of selected predictors X_use = find (cur); ## Final regression on selected predictors Xfinal = [ones(n,1), Xc(:, X_use)]; ## regstats intentionally unused; retained for MATLAB parity [B, BINT, R, RINT, regstats] = regress (yc, Xfinal); Rresid = R(:); ## freeze residual vector ## Allocate outputs b = zeros (p,1); se = zeros (p,1); pval = zeros (p,1); df = n - columns (Xfinal); if (df <= 0) ## Not enough residual degrees of freedom to estimate SE reliably. se(:) = NaN; pval(:) = NaN; endif ## Included predictors b(X_use) = B(2:end); se(X_use) = (BINT(2:end,2) - B(2:end)) ./ tinv (0.975, df); pval(X_use) = 2 * (1 - tcdf (abs (B(2:end) ./ se(X_use)), df)); ## Excluded predictors: conditional refit excluded = setdiff (1:p, X_use); for j = excluded Xj = [ones(n,1), Xc(:, [X_use j])]; [Bj, BjINT] = regress (yc, Xj); bj = Bj(end); sej = (BjINT(end,2) - bj) ./ tinv (0.975, n - columns (Xj)); b(j) = bj; se(j) = sej; pval(j) = 2 * (1 - tcdf (abs (bj / sej), n - columns (Xj))); endfor ## Final model indicator finalmodel = false (1,p); finalmodel(X_use) = true; ## Stats structure stats = struct (); stats.source = 'stepwisefit'; stats.df0 = numel (X_use); stats.dfe = n - stats.df0 - 1; stats.SStotal = sum ((yc - mean (yc)).^2); stats.SSresid = sum (Rresid.^2); stats.rmse = sqrt (stats.SSresid / stats.dfe); stats.intercept = B(1); stats.wasnan = wasnan; stats.yr = Rresid; stats.B = b; stats.SE = se; stats.TSTAT = b ./ se; stats.PVAL = pval; stats.TSTAT (! isfinite (stats.TSTAT)) = NaN; excluded = setdiff (1:p, X_use); xr = zeros (n, numel (excluded)); if (! isempty (X_use)) Z = [ones(n,1), Xc(:, X_use)]; P = Z / (Z' * Z) * Z'; ## projection matrix for k = 1:numel (excluded) j = excluded(k); xr(:,k) = Xc(:,j) - P * Xc(:,j); endfor else ## intercept-only case for k = 1:numel (excluded) j = excluded(k); xr(:,k) = Xc(:,j) - mean (Xc(:,j)); endfor endif stats.xr = xr; covb = NaN (p+1, p+1); covB = (stats.rmse^2) * pinv (Xfinal' * Xfinal); idx = [1, X_use + 1]; covb(idx, idx) = covB; stats.covb = covb; if (stats.df0 > 0) stats.fstat = ((stats.SStotal - stats.SSresid) / stats.df0) ... / (stats.SSresid / stats.dfe); stats.pval = 1 - fcdf (stats.fstat, stats.df0, stats.dfe); else stats.fstat = NaN; stats.pval = NaN; endif history = struct (); history.in = finalmodel; history.df0 = stats.df0; history.rmse = stats.rmse; ## Coefficient history (excluding intercept) ## MATLAB stores this as p-by-k; here k = 1 Bhist = zeros (p, 1); Bhist(finalmodel) = b(finalmodel); history.B = Bhist; ## Placeholders for future phases nextstep = 0; endfunction %!test %! X = [7 26 6 60; %! 1 29 15 52; %! 11 56 8 20; %! 11 31 8 47; %! 7 52 6 33; %! 11 55 9 22; %! 3 71 17 6; %! 1 31 22 44; %! 2 54 18 22; %! 21 47 4 26; %! 1 40 23 34; %! 11 66 9 12; %! 10 68 8 12]; %! y = [78.5; 74.3; 104.3; 87.6; 95.9; 109.2; %! 102.7; 72.5; 93.1; 115.9; 83.8; 113.3; 109.4]; %! [b,se,pval,finalmodel,stats] = stepwisefit (X,y); %! assert_equal (finalmodel, [true false false true]); %! assert_equal (b, [1.4400; 0.4161; -0.4100; -0.6140], 1e-4); %! assert_equal (se, [0.1384; 0.1856; 0.1992; 0.0486], 1e-4); %! assert_equal (pval, [0; 0.0517; 0.0697; 0], 1e-4); %! assert_equal (stats.rmse, 2.7343, 1e-4); %! assert_equal (stats.SStotal, 2715.7631, 1e-3); %! assert_equal (stats.SSresid, 74.7621, 1e-4); %! assert_equal (stats.df0, 2); %! assert_equal (stats.dfe, 10); %! assert_equal (stats.intercept, 103.0974, 1e-4); %!test %! X = [ %! 12.0 4 120 95 2600; %! 11.5 6 200 110 3000; %! 10.5 8 300 150 3600; %! 13.0 4 140 100 2800; %! 12.5 6 180 120 3200; %! 11.0 8 250 140 3500; %! 14.0 4 130 98 2700; %! 13.5 6 210 115 3100; %! 12.2 8 320 160 3800; %! 11.8 4 150 105 2900 %! ]; %! y = [28; 22; 18; 27; 23; 19; 29; 21; 17; 26]; %! %! [b,se,pval,finalmodel,stats] = stepwisefit (X,y); %! %! assert_equal (islogical (finalmodel), true); %! assert_equal (numel (finalmodel) == 5, true); %! assert_equal (sum (finalmodel) >= 1, true); %! assert_equal (isnumeric (b), true); %! assert_equal (isnumeric (se), true); %! assert_equal (isnumeric (pval), true); %! assert_equal (stats.rmse > 0, true); %! assert_equal (isfinite (stats.intercept), true); %!test %! X = randn (30, 4); %! y = randn (30, 1); %! [~,~,~,~,stats] = stepwisefit (X, y); %! %! required_fields = { %! 'source', 'df0', 'dfe', 'SStotal', 'SSresid', 'fstat', 'pval', ... %! 'rmse', 'xr', 'yr', 'B', 'SE', 'TSTAT', 'PVAL', 'covb', ... %! 'intercept', 'wasnan' %! }; %! %! for k = 1:numel (required_fields) %! assert_equal (isfield (stats, required_fields{k}), true); %! endfor %!test %! X = randn (40, 5); %! y = randn (40, 1); %! [b,se,pval,finalmodel,stats] = stepwisefit (X, y); %! %! p = columns (X); %! n = rows (X(! stats.wasnan, :)); %! %! assert_equal (size (stats.yr), [n, 1]); %! assert_equal (rows (stats.B) == p, true); %! assert_equal (rows (stats.SE) == p, true); %! assert_equal (rows (stats.TSTAT) == p, true); %! assert_equal (rows (stats.PVAL) == p, true); %! assert_equal (size (stats.covb), [p+1, p+1]); %!test %! X = randn (25, 3); %! y = randn (25, 1); %! [~,~,~,~,stats] = stepwisefit (X, y); %! %! SSresid_calc = sum (stats.yr .^ 2); %! assert_equal (SSresid_calc, stats.SSresid, 1e-10); %! %! rmse_calc = sqrt (stats.SSresid / stats.dfe); %! assert_equal (rmse_calc, stats.rmse, 1e-10); %!test %! X = randn (50, 6); %! y = randn (50, 1); %! [~,~,~,~,stats] = stepwisefit (X, y); %! %! if (stats.df0 > 0) %! F_calc = ((stats.SStotal - stats.SSresid) / stats.df0) ... %! / (stats.SSresid / stats.dfe); %! %! assert_equal (F_calc, stats.fstat, 1e-10); %! assert_equal (stats.pval >= 0 && stats.pval <= 1, true); %! else %! assert_equal (isnan (stats.fstat), true); %! assert_equal (isnan (stats.pval), true); %! endif %!test %! X = randn (35, 4); %! y = randn (35, 1); %! [~,~,~,finalmodel,stats] = stepwisefit (X, y); %! p = columns (X); %! k = sum (finalmodel); %! assert_equal (size (stats.xr, 2) == p - k, true); %! assert_equal (all (isfinite (stats.xr(:))), true); %!test %! X = randn (35, 4); %! y = randn (35, 1); %! [~,~,~,finalmodel,stats] = stepwisefit (X, y); %! %! Xc = X(! stats.wasnan, :); %! Xfinal = [ones(rows (Xc),1), Xc(:, finalmodel)]; %! %! for j = 1:columns (stats.xr) %! ortho = Xfinal' * stats.xr(:,j); %! assert_equal (max (abs (ortho(:))) < 1e-6, true); %! endfor %!test %! X = randn (40, 5); %! y = randn (40, 1); %! [~,~,~,finalmodel,stats,nextstep,history] = stepwisefit (X, y); %! %! assert_equal (nextstep == 0, true); %! assert_equal (isstruct (history), true); %! assert_equal (isfield (history, 'in'), true); %! assert_equal (isfield (history, 'df0'), true); %! assert_equal (isfield (history, 'rmse'), true); %! assert_equal (isfield (history, 'B'), true); %! assert_equal (isequal (history.in, finalmodel), true); %! assert_equal (history.df0 == stats.df0, true); %! assert_equal (history.rmse == stats.rmse, true); %! assert_equal (rows (history.B) == columns (X), true); %!test %! X = randn (20,4); %! y = randn (20,1); %! stepwisefit (X,y,'Keep',[true false true false]); %!test %! X = randn (30, 4); %! y = randn (30, 1); %! keep = [true false false false]; %! [~,~,~,finalmodel] = stepwisefit (X, y, 'Keep', keep); %! assert_equal (finalmodel(1) == true, true); %!test %! X = randn (40, 6); %! y = randn (40, 1); %! [~,~,~,finalmodel] = stepwisefit (X, y, 'MaxIter', 1); %! assert_equal (islogical (finalmodel), true); %!test %! X = randn (50, 5); %! y = randn (50, 1); %! [b1] = stepwisefit (X, y); %! [b2] = stepwisefit (X, y, 'Scale', 'on'); %! assert_equal (rows (b1) == rows (b2), true); %!test %! X = randn (20,4); %! y = randn (20,1); %! fail ('stepwisefit (X,y,''Keep'',[true false])'); ## Test input validation %!error ... %! stepwisefit () %!error ... %! stepwisefit (ones (2,2,2), [1;2]) %!error ... %! stepwisefit (ones (3,2), ones (2,1)) %!error ... %! stepwisefit (randn (10,2), randn (10,1), 'UnknownOpt', 5) %!error ... %! stepwisefit (randn (10,2), randn (10,1), 'Display', 'maybe') %!error ... %! stepwisefit (randn (10,2), randn (10,1), 'Scale', 123) %!error ... %! stepwisefit (randn (10,2), randn (10,1), 'PEnter', -0.1) %!error ... %! stepwisefit (randn (10,2), randn (10,1), 'PRemove', 1.5) %!error ... %! stepwisefit (randn (10,2), randn (10,1), ... %! 'PEnter', 0.05, 'PRemove', 0.01) %!error ... %! stepwisefit (randn (10,2), randn (10,1), 'MaxIter', -2) %!error ... %! stepwisefit (randn (10,2), randn (10,1), 'MaxIter', 2.5) %!error ... %! stepwisefit (randn (10,2), randn (10,1), 'Keep', [1 0]) %!error ... %! stepwisefit (randn (10,2), randn (10,1), 'InModel', [1 0]) %!error ... %! stepwisefit (randn (10,4), randn (10,1), 'Keep', [true false]) %!error ... %! stepwisefit (randn (10,4), randn (10,1), 'InModel', true) statistics-release-1.9.2/inst/Regression/stepwiseglm.m000066400000000000000000001607441524624707500232010ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{mdl} =} stepwiseglm (@var{X}, @var{y}) ## @deftypefnx {statistics} {@var{mdl} =} stepwiseglm (@var{X}, @var{y}, @var{modelspec}) ## @deftypefnx {statistics} {@var{mdl} =} stepwiseglm (@var{tbl}) ## @deftypefnx {statistics} {@var{mdl} =} stepwiseglm (@var{tbl}, @var{modelspec}) ## @deftypefnx {statistics} {@var{mdl} =} stepwiseglm (@dots{}, @var{Name}, @var{Value}) ## ## Fit a generalized linear regression model by stepwise term selection. ## ## @code{@var{mdl} = stepwiseglm (@var{X}, @var{y})} starts from a model given ## by @var{modelspec} and repeatedly adds or removes terms, one at a time, until ## no further move improves the selection criterion. It returns the fitted ## @code{GeneralizedLinearModel} object @var{mdl}, whose @code{Steps} property ## records the term-selection trace. @var{X} is an @math{n}-by-@math{p} numeric ## predictor matrix and @var{y} the response; @code{@var{mdl} = stepwiseglm ## (@var{tbl})} instead takes the predictors and response from the table ## @var{tbl} (the last column is the response unless overridden). ## ## @var{modelspec} is the @emph{starting} model. It is a Wilkinson formula ## string (e.g.@: @qcode{'y ~ x1 + x2'}), a keyword (@qcode{'constant'} ## (default), @qcode{'linear'}, @qcode{'interactions'}, @qcode{'purequadratic'}, ## @qcode{'quadratic'}, or @qcode{'full'}), or a terms matrix. The candidate ## terms available to the search are bounded below by @qcode{'Lower'} and above ## by @qcode{'Upper'}. ## ## The following @var{Name}/@var{Value} pairs control the stepwise search: ## ## @multitable @columnfractions 0.2 0.75 ## @headitem Name @tab Value ## @item @qcode{'Lower'} @tab the smallest model considered (terms in it are ## never removed). Defaults to @qcode{'constant'}. ## @item @qcode{'Upper'} @tab the largest model considered (the candidate term ## universe). Defaults to @qcode{'interactions'}. ## @item @qcode{'Criterion'} @tab the selection criterion: @qcode{'Deviance'} ## (default), @qcode{'sse'}, @qcode{'aic'}, or @qcode{'bic'}. Under ## @qcode{'Deviance'} and @qcode{'sse'}, terms enter or leave by a chi-squared ## or @math{F} test on the change in deviance; under @qcode{'aic'}/@qcode{'bic'} ## the move that most reduces the information criterion is taken. ## @item @qcode{'PEnter'} @tab the @math{p}-value (or criterion margin) below ## which a term is added. Defaults to @math{0.05} for @qcode{'Deviance'} and ## @qcode{'sse'}, and @math{0} for @qcode{'aic'}/@qcode{'bic'}. ## @item @qcode{'PRemove'} @tab the @math{p}-value (or criterion margin) above ## which a term is removed. Defaults to @math{0.10} for @qcode{'Deviance'} and ## @qcode{'sse'}, and @math{0} for @qcode{'aic'}/@qcode{'bic'}. ## @item @qcode{'NSteps'} @tab the maximum number of steps. Defaults to ## @code{Inf} (run to convergence). ## @item @qcode{'Verbose'} @tab @math{0} to run silently, or @math{1} (default) ## to print each accepted step. ## @end multitable ## ## As in @code{fitglm}, a @qcode{'binomial'} response is the number of ## successes, and the trials come either from @qcode{'BinomialSize'} or from ## passing @var{y} as an @math{n}-by-@math{2} matrix of successes and trials. ## @strong{Changed in 1.9.0}: @var{y} was previously read as the proportion. ## ## All @var{Name}/@var{Value} pairs accepted by @code{fitglm} (such as ## @qcode{'Distribution'}, @qcode{'Link'}, @qcode{'Weights'}, @qcode{'Offset'}, ## @qcode{'BinomialSize'}, @qcode{'Intercept'}, @qcode{'DispersionFlag'}, ## @qcode{'CategoricalVars'}, and @qcode{'Exclude'}) are also accepted and ## forwarded to the fit. ## ## @subsubheading The @code{Steps} property ## ## The returned model's @code{Steps} property records the trace, as a ## structure with seven fields. @code{Start}, @code{Lower}, and @code{Upper} ## are @code{LinearFormula} objects for the starting model and the two bounds; ## @code{Criterion} is the criterion as it was asked for; @code{PEnter} and ## @code{PRemove} are the thresholds it ran under; and @code{History} is a ## table with one row per step. ## ## @code{History} always carries @code{Action} (@qcode{'Start'}, ## @qcode{'Add'}, or @qcode{'Remove'}), @code{TermName}, @code{Terms} (the ## terms matrix after the step, over the model's variables), @code{DF} (the ## coefficient count after the step), and @code{delDF} (the change in it, ## @emph{negative} for a removal). The remaining columns follow the ## criterion, which is why a history is read by name and not by position: ## ## @multitable @columnfractions 0.25 0.7 ## @headitem Criterion @tab Further columns ## @item @qcode{'Deviance'} @tab @code{Deviance}, then @code{Chi2Stat} or ## @code{FStat} as the dispersion is fixed or estimated, then @code{PValue}. ## @item @qcode{'sse'} @tab @code{FStat} and @code{pValue}. ## @item @qcode{'aic'}, @qcode{'bic'} @tab one column, @code{AIC} or ## @code{BIC}, holding the criterion's value after the step, the starting ## model included. ## @end multitable ## ## The first row is the starting model, named by its right-hand side. ## ## @subsubheading Categorical predictors ## ## A categorical predictor with @math{L} levels contributes @math{L - 1} ## indicator columns, the first level being the omitted reference, and the ## search treats that whole group as a @emph{single term}: it is added or ## removed in one step, worth @math{L - 1} degrees of freedom, and never one ## indicator at a time. An interaction naming a categorical predictor behaves ## the same way, contributing one column per indicator and entering as one ## term. @code{Steps.History} names such a term by the predictor ## (@qcode{'g'}, or @qcode{'x1:g'} for the interaction) rather than by its ## indicators, while @code{CoefficientNames} names the indicators ## (@qcode{'g_B'}, @qcode{'x1:g_C'}). ## ## In a table, every column that groups its observations is taken as ## categorical: a cell array of character vectors, a @code{categorical} array, ## a string array, or a logical column. @qcode{'CategoricalVars'} adds to ## these, and is the only way to mark a column of a predictor @emph{matrix}; ## it takes predictor names, column indices, or a logical vector. ## ## @seealso{GeneralizedLinearModel, fitglm, stepwisefit, glmfit, glmval} ## @end deftypefn function mdl = stepwiseglm (varargin) if (nargin < 1) print_usage (); endif ## ------------------------------------------------------------------------ ## ## Separate the data/response, the (optional) starting model, and the pairs. ## ------------------------------------------------------------------------ ## arg1 = varargin{1}; if (istable (arg1)) data = arg1; resp = []; rest = varargin(2:end); else if (nargin < 2) print_usage (); endif data = arg1; resp = varargin{2}; rest = varargin(3:end); endif start_spec = 'constant'; if (! isempty (rest)) a = rest{1}; if ((ischar (a) && ! is_param_name (a)) || isnumeric (a)) start_spec = a; rest = rest(2:end); endif endif ## Split the pairs into stepwise controls and forwarded fitglm options. [sw, opts, glmnv] = parse_pairs (rest); ## ------------------------------------------------------------------------ ## ## Intake: numeric predictor matrix, response, names, and fitting subset. ## ------------------------------------------------------------------------ ## ## Trial counts taken from a two-column binomial response, empty otherwise. binom_2col = []; start_is_formula = ischar (start_spec) && any (start_spec == '~'); if (istable (data)) col_names = data.Properties.VariableNames; if (ischar (resp) && ! isempty (resp)) resp_name = resp; elseif (! isempty (opts.ResponseVar)) resp_name = opts.ResponseVar; elseif (start_is_formula) resp_name = strtrim (strsplit (start_spec, '~'){1}); else resp_name = col_names{end}; endif if (! isempty (opts.PredictorVars)) pred_names = opts.PredictorVars; else pred_names = col_names(! strcmp (col_names, resp_name)); endif p = numel (pred_names); ## Filled in by RAW_TO_CODES below, which reads the table itself. X = zeros (height (data), p); y = double (data.(resp_name)(:)); else if (! (isnumeric (data) && isreal (data) && ismatrix (data))) error ("stepwiseglm: X must be a real matrix."); endif ## A binomial response may be given as an N-by-2 matrix of successes and ## trials, as MATLAB accepts and as GeneralizedLinearModel accepts; the ## trials are carried alongside and the final model is handed the original ## response, which validates it. is_2col = strcmp (opts.Distribution, 'binomial') && isnumeric (resp) ... && isreal (resp) && ismatrix (resp) && ! isvector (resp) ... && columns (resp) == 2; if (! (is_2col || (isnumeric (resp) && isreal (resp) && isvector (resp)))) error ("stepwiseglm: Y must be a real vector."); endif X = double (data); if (is_2col) binom_2col = double (resp(:,2)); y = double (resp(:,1)); else y = double (resp(:)); endif p = columns (X); if (rows (X) != numel (y)) error (strcat ("stepwiseglm: X and Y must have the same number of", ... " observations.")); endif if (! isempty (opts.VarNames)) pred_names = opts.VarNames(1:p)(:)'; resp_name = opts.VarNames{end}; else pred_names = arrayfun (@(k) sprintf ("x%d", k), 1:p, ... 'UniformOutput', false); resp_name = 'y'; endif if (! isempty (opts.ResponseVar)) resp_name = opts.ResponseVar; endif endif ## ------------------------------------------------------------------------ ## ## Categorical predictors. Each is coded 1..L and expanded to L-1 indicator ## columns, exactly as GeneralizedLinearModel encodes them, so that the final ## model refits the design the search scored. The search itself stays over ## the model's variables, which is what makes a categorical enter and leave ## as one term of L-1 degrees of freedom rather than one indicator at a time. ## ------------------------------------------------------------------------ ## cat_cols = categorical_flags (opts.CategoricalVars, pred_names, p, data); [X, cat_levels] = raw_to_codes (data, X, data, pred_names, cat_cols, ... rows (X)); [X_enc, enc_names, cat_info] = encode_categorical (X, cat_cols, ... pred_names, cat_levels, opts.Intercept); ## Distribution and criterion validation. known_distr = {'normal', 'binomial', 'poisson', 'gamma', ... 'inverse gaussian'}; distr = opts.Distribution; if (! any (strcmp (distr, known_distr))) error ("stepwiseglm: unknown distribution '%s'.", distr); endif crit = lower (sw.Criterion); if (! any (strcmp (crit, {'deviance', 'sse', 'aic', 'bic'}))) error ("stepwiseglm: unknown criterion '%s'.", sw.Criterion); endif ## Fitting subset (drop missing and excluded rows). missing_mask = any (isnan (X), 2) | isnan (y); excluded_mask = false (rows (X), 1); if (! isempty (opts.Exclude)) ex = opts.Exclude(:); if (islogical (ex)) excluded_mask(1:numel (ex)) = ex; else excluded_mask(ex) = true; endif endif subset = ! missing_mask & ! excluded_mask; if (! any (subset)) error (strcat ("stepwiseglm: no observations remain after removing", ... " missing/excluded rows.")); endif Xs = X_enc(subset, :); ys = y(subset); n = rows (Xs); ## Weights, offset, and binomial trial counts, subset to the fitting rows. w_sub = []; if (! isempty (opts.Weights)) w_sub = double (opts.Weights(:))(subset); endif off_sub = []; if (! isempty (opts.Offset)) off_sub = double (opts.Offset(:))(subset); endif N_sub = []; N_all = []; if (strcmp (distr, 'binomial')) if (! isempty (binom_2col)) ## The trials came with the response and win over 'BinomialSize'. N_all = binom_2col; elseif (! isempty (opts.BinomialSize)) N_all = opts.BinomialSize(:); if (isscalar (N_all)) N_all = N_all * ones (rows (X), 1); endif endif endif if (! isempty (N_all)) N_sub = N_all(subset); endif w_ll = w_sub; if (isempty (w_ll)) w_ll = ones (n, 1); endif ## Link function and glmfit argument list (shared by every candidate fit). if (! isempty (opts.Link)) linkspec = opts.Link; else linkspec = default_link (distr); endif [~, ~, ilink] = getlinkfunctions (linkspec); gargs = {'link', linkspec, 'constant', 'off'}; if (! isempty (w_sub)) gargs = [gargs, {'weights', w_sub}]; endif if (! isempty (off_sub)) gargs = [gargs, {'offset', off_sub}]; endif if (! isempty (opts.DispersionFlag)) if (opts.DispersionFlag) gargs = [gargs, {'estdisp', 'on'}]; else gargs = [gargs, {'estdisp', 'off'}]; endif endif yfit = ys; if (strcmp (distr, 'binomial') && ! isempty (N_sub)) ## YS is the number of successes: GLMFIT takes [successes, trials], while ## the log-likelihood below takes the proportion. yfit = [ys, N_sub]; ys = ys ./ N_sub; endif ## Whether dispersion is estimated (chooses the F- vs chi-squared test and ## the internal criterion label). if (! isempty (opts.DispersionFlag)) estdisp = logical (opts.DispersionFlag); else estdisp = any (strcmp (distr, {'normal', 'gamma', 'inverse gaussian'})); endif ## Package the fitting context passed to the inner fitter. ctx = struct ('X', Xs, 'yfit', yfit, 'y', ys, 'distr', distr, ... 'gargs', {gargs}, 'ilink', ilink, 'off', off_sub, ... 'N', N_sub, 'w', w_ll, 'n', n, 'p', p, ... 'pred_names', {pred_names}, 'cat_info', cat_info, ... 'enc_names', {enc_names}); ## ------------------------------------------------------------------------ ## ## Resolve the starting, lower, and upper models to terms matrices. ## ------------------------------------------------------------------------ ## T_start = resolve_terms (start_spec, pred_names, p, opts.Intercept); T_lower = resolve_terms (sw.Lower, pred_names, p, opts.Intercept); T_upper = resolve_terms (sw.Upper, pred_names, p, opts.Intercept); ## The search operates within [Lower, Upper]; the start must contain Lower. T = union_rows (T_start, T_lower); ## Entry/removal thresholds. if (any (strcmp (crit, {'deviance', 'sse'}))) penter = ternary (isempty (sw.PEnter), 0.05, sw.PEnter); premove = ternary (isempty (sw.PRemove), 0.10, sw.PRemove); else penter = ternary (isempty (sw.PEnter), 0, sw.PEnter); premove = ternary (isempty (sw.PRemove), 0, sw.PRemove); endif ## ------------------------------------------------------------------------ ## ## Stepwise search. ## ------------------------------------------------------------------------ ## ws = warning (); # silence per-candidate glmfit warnings warning ("off", "all"); unwind_protect f0 = fit_terms (T, ctx); if (any (strcmp (crit, {'aic', 'bic'}))) start_stat = crit_value (f0, crit, ctx); else start_stat = NaN; endif hist = start_history (T, f0.dev, f0.ncoef, ... start_name (T, pred_names, p), start_stat); stepnum = 0; do stepnum += 1; [T, hstep] = one_step (T, T_lower, T_upper, ctx, p, crit, estdisp, ... penter, premove, pred_names); if (isempty (hstep)) break; endif hist(end+1) = hstep; if (sw.Verbose) print_step (stepnum, hstep, crit, estdisp, pred_names, p); endif until (stepnum >= sw.NSteps) unwind_protect_cleanup warning (ws); end_unwind_protect ## ------------------------------------------------------------------------ ## ## Build the final object (canonical term order) and attach the trace. ## ------------------------------------------------------------------------ ## T_final = canonical_sort (T, p); ## With categorical predictors the constructor reads a terms matrix over the ## encoded columns, so the selected terms are expanded and the resolved names ## are handed over -- an index or a table column detected here would ## otherwise be re-derived, and need not agree. if (isempty (cat_info.names)) spec_final = T_final; glmnv_final = glmnv; else spec_final = expand_terms (T_final, ctx); glmnv_final = set_categorical (glmnv, cat_info.names); endif mdl = GeneralizedLinearModel (data, resp, spec_final, glmnv_final{:}); var_names = [pred_names, {resp_name}]; steps = struct (); steps.Start = LinearFormula (T_start, var_names, 'ResponseName', ... resp_name, 'Link', linkspec); steps.Lower = LinearFormula (T_lower, var_names, 'ResponseName', ... resp_name, 'Link', linkspec); steps.Upper = LinearFormula (T_upper, var_names, 'ResponseName', ... resp_name, 'Link', linkspec); steps.Criterion = criterion_label (crit, estdisp); steps.PEnter = sw.PEnter; steps.PRemove = sw.PRemove; steps.History = history_table (hist, crit, estdisp, pred_names, p); mdl = setSteps (mdl, steps); endfunction ## ========================================================================== ## ## Argument parsing ## ========================================================================== ## ## True if S names a stepwiseglm or forwarded fitglm parameter. function tf = is_param_name (s) tf = ischar (s) && any (strcmpi (s, {'Lower', 'Upper', 'Criterion', ... 'PEnter', 'PRemove', 'NSteps', 'Verbose', 'Distribution', 'Link', ... 'Weights', 'Offset', 'BinomialSize', 'Intercept', 'DispersionFlag', ... 'CategoricalVars', 'Exclude', 'VarNames', 'PredictorVars', ... 'ResponseVar'})); endfunction ## Split Name/Value pairs into stepwise controls (SW), the fit options this ## function needs (OPTS), and the raw pairs forwarded to GeneralizedLinearModel ## (GLMNV). function [sw, opts, glmnv] = parse_pairs (pairs) sw = struct ('Lower', 'constant', 'Upper', 'interactions', ... 'Criterion', 'Deviance', 'PEnter', [], 'PRemove', [], ... 'NSteps', Inf, 'Verbose', 1); opts = struct ('Distribution', 'normal', 'Link', [], 'Weights', [], ... 'Offset', [], 'BinomialSize', [], 'DispersionFlag', [], ... 'CategoricalVars', [], 'Exclude', [], 'Intercept', true, ... 'VarNames', {{}}, 'PredictorVars', [], 'ResponseVar', []); glmnv = {}; if (mod (numel (pairs), 2) != 0) error ("stepwiseglm: Name/Value arguments must come in pairs."); endif for k = 1:2:numel (pairs) name = pairs{k}; val = pairs{k+1}; if (! ischar (name)) error ("stepwiseglm: parameter names must be character vectors."); endif switch (lower (name)) case 'lower' sw.Lower = val; case 'upper' sw.Upper = val; case 'criterion' sw.Criterion = val; case 'penter' sw.PEnter = val; case 'premove' sw.PRemove = val; case 'nsteps' sw.NSteps = val; case 'verbose' sw.Verbose = val; otherwise ## Fit option: record what we need and forward the pair unchanged. switch (lower (name)) case 'distribution'; opts.Distribution = lower (val); case 'link'; opts.Link = val; case 'weights'; opts.Weights = val; case 'offset'; opts.Offset = val; case 'binomialsize'; opts.BinomialSize = val; case 'dispersionflag'; opts.DispersionFlag = val; case 'categoricalvars'; opts.CategoricalVars = val; case 'exclude'; opts.Exclude = val; case 'intercept'; opts.Intercept = val; case 'varnames'; opts.VarNames = val; case 'predictorvars'; opts.PredictorVars = val; case 'responsevar'; opts.ResponseVar = val; otherwise error ("stepwiseglm: unknown parameter name '%s'.", name); endswitch glmnv = [glmnv, {name, val}]; endswitch endfor endfunction ## ========================================================================== ## ## Term-selection engine ## ========================================================================== ## ## Perform one step: try to add, then to remove. Returns the updated terms T ## and a history record HSTEP ([] if no move was accepted). function [T, hstep] = one_step (T, T_lower, T_upper, ctx, p, crit, estdisp, ... penter, premove, pred_names) hstep = []; fC = fit_terms (T, ctx); if (any (strcmp (crit, {'deviance', 'sse'}))) ## --- Add phase: pick the addable term with the smallest p-value. ------- addable = candidates_add (T, T_upper, p); best_p = Inf; best = []; for i = 1:rows (addable) r = addable(i, :); fN = fit_terms ([T; r], ctx); [stat, pval, ddf] = nested_test (fC.dev, fC.dfe, fN.dev, fN.dfe, estdisp); if (pval < best_p) best_p = pval; best = struct ('row', r, 'stat', stat, 'pval', pval, ... 'ddf', ddf, 'dev', fN.dev, 'df', fN.ncoef); endif endfor if (! isempty (best) && best_p < penter) T = [T; best.row]; hstep = mkhist ('Add', best.row, T, best.df, best.ddf, best.dev, ... best.stat, best.pval, p); return; endif ## --- Remove phase: pick the removable term with the largest p-value. --- removable = candidates_remove (T, T_lower, p); worst_p = -Inf; worst = []; for i = 1:rows (removable) r = removable(i, :); Tn = remove_row (T, r); fN = fit_terms (Tn, ctx); [stat, pval, ddf] = nested_test (fN.dev, fN.dfe, fC.dev, fC.dfe, estdisp); if (pval > worst_p) worst_p = pval; worst = struct ('row', r, 'stat', stat, 'pval', pval, ... 'ddf', ddf, 'dev', fN.dev, 'df', fN.ncoef, 'Tn', Tn); endif endfor if (! isempty (worst) && worst_p > premove) T = worst.Tn; hstep = mkhist ('Remove', worst.row, T, worst.df, -worst.ddf, ... worst.dev, worst.stat, worst.pval, p); endif else ## --- Information criterion: take the single best neighbouring move. ---- curval = crit_value (fC, crit, ctx); best_val = curval; best = []; addable = candidates_add (T, T_upper, p); for i = 1:rows (addable) r = addable(i, :); fN = fit_terms ([T; r], ctx); v = crit_value (fN, crit, ctx); if (v < best_val - 1e-12) best_val = v; best = struct ('row', r, 'act', 'Add', 'T', [T; r], 'dev', fN.dev, ... 'df', fN.ncoef, 'ddf', fN.ncoef - fC.ncoef, 'val', v); endif endfor removable = candidates_remove (T, T_lower, p); for i = 1:rows (removable) r = removable(i, :); Tn = remove_row (T, r); fN = fit_terms (Tn, ctx); v = crit_value (fN, crit, ctx); if (v < best_val - 1e-12) best_val = v; best = struct ('row', r, 'act', 'Remove', 'T', Tn, 'dev', fN.dev, ... 'df', fN.ncoef, 'ddf', fN.ncoef - fC.ncoef, 'val', v); endif endfor if (! isempty (best)) T = best.T; hstep = mkhist (best.act, best.row, T, best.df, best.ddf, best.dev, ... best.val, NaN, p); endif endif endfunction ## Fit a candidate model given by terms matrix T. function f = fit_terms (T, ctx) T_enc = expand_terms (T, ctx); Xd = build_design (T_enc, ctx.X); [b, dev, stats] = glmfit (Xd, ctx.yfit, ctx.distr, ctx.gargs{:}); f = struct ('b', b, 'dev', dev, 'dfe', stats.dfe, 's', stats.s, ... 'ncoef', rows (T_enc), 'X', Xd); endfunction ## Nested-model test between the smaller (A) and larger (B) model. Returns the ## chi-squared or F statistic, its p-value, and the degrees of freedom change. function [stat, pval, ddf] = nested_test (devA, dfeA, devB, dfeB, estdisp) ddf = dfeA - dfeB; # number of coefficients that differ (>= 1) drop = devA - devB; # deviance reduction from A to B if (estdisp) stat = (drop / ddf) / (devB / dfeB); pval = fcdf (stat, ddf, dfeB, 'upper'); else stat = drop; pval = chi2cdf (stat, ddf, 'upper'); endif endfunction ## Information-criterion value (AIC or BIC) of a fitted candidate. function v = crit_value (f, crit, ctx) eta = f.X * f.b; if (! isempty (ctx.off)) eta = eta + ctx.off; endif mu = ctx.ilink (eta); LL = glm_loglik (ctx.distr, ctx.y, mu, ctx.N, ctx.w, f.s); k = f.ncoef; switch (crit) case 'aic' v = -2 * LL + 2 * k; case 'bic' v = -2 * LL + k * log (ctx.n); endswitch endfunction ## ========================================================================== ## ## Candidate generation and hierarchy ## ========================================================================== ## ## Rows of the upper model that may be added to T (not present and with every ## one-step-down parent present, so the model hierarchy is preserved). function C = candidates_add (T, T_upper, p) C = zeros (0, columns (T)); for i = 1:rows (T_upper) r = T_upper(i, :); if (row_member (r, T)) continue; endif if (parents_present (r, T, p)) C = [C; r]; endif endfor endfunction ## Rows of T that may be removed (not the intercept, not in the lower model, ## and not a required parent of any higher-order term present in T). function C = candidates_remove (T, T_lower, p) C = zeros (0, columns (T)); for i = 1:rows (T) r = T(i, :); if (all (r(1:p) == 0)) # intercept continue; endif if (row_member (r, T_lower)) continue; endif if (has_superset (r, T, p)) continue; endif C = [C; r]; endfor endfunction ## True if every one-step-down parent of term R is present in T. function tf = parents_present (r, T, p) tf = true; for j = find (r(1:p) != 0) par = r; par(j) -= 1; if (any (par(1:p) != 0) && ! row_member (par, T)) tf = false; return; endif endfor endfunction ## True if some other row of T is a strict superset of term R. function tf = has_superset (r, T, p) tf = false; for i = 1:rows (T) s = T(i, :); if (isequal (s, r)) continue; endif if (all (s(1:p) >= r(1:p)) && any (s(1:p) > r(1:p))) tf = true; return; endif endfor endfunction function tf = row_member (r, T) tf = ! isempty (T) && any (all (T == r, 2)); endfunction function T = remove_row (T, r) T(all (T == r, 2), :) = []; endfunction function U = union_rows (A, B) U = A; for i = 1:rows (B) if (! row_member (B(i, :), U)) U = [U; B(i, :)]; endif endfor endfunction ## ========================================================================== ## ## Categorical predictors ## ========================================================================== ## ## Which predictors are categorical. An explicit 'CategoricalVars' may name ## them, index them, or mark them with a logical vector; a table additionally ## contributes every column whose class groups its observations. function cat_cols = categorical_flags (cv, pred_names, p, data) cat_cols = false (1, p); if (! isempty (cv)) if (islogical (cv)) n = min (numel (cv), p); cat_cols(1:n) = cv(1:n); elseif (isnumeric (cv)) cat_cols(cv(cv > 0 & cv <= p)) = true; elseif (iscellstr (cv) || ischar (cv)) cv = cellstr (cv); for i = 1:numel (cv) j = find (strcmp (pred_names, cv{i})); if (isempty (j)) error ("stepwiseglm: unknown categorical predictor '%s'.", cv{i}); endif cat_cols(j) = true; endfor else error (strcat ("stepwiseglm: 'CategoricalVars' must be predictor", ... " names, indices, or a logical vector.")); endif endif if (istable (data)) for j = 1:p col = data.(pred_names{j}); if (iscell (col) || isa (col, 'categorical') || islogical (col) ... || isa (col, 'string')) cat_cols(j) = true; endif endfor endif endfunction ## Expand a terms matrix over the P predictors into one over the encoded ## indicator columns. A term naming a categorical predictor becomes one row ## per indicator, so the whole group is fitted, added, and dropped together. function T_enc = expand_terms (T, ctx) if (isempty (ctx.cat_info.names)) T_enc = T; return; endif T_enc = zeros (0, numel (ctx.enc_names) + 1); for r = 1:rows (T) G = expand_term_row (T(r, 1:ctx.p), ctx); T_enc = [T_enc; G, zeros(rows (G), 1)]; endfor endfunction ## The encoded rows of one term. Two categorical factors in an interaction ## multiply out, giving one row per pair of indicators. function G = expand_term_row (row, ctx) p_enc = numel (ctx.enc_names); G = zeros (1, p_enc); for j = find (row != 0) ci = find (strcmp (ctx.cat_info.names, ctx.pred_names{j}), 1); if (isempty (ci)) G(:, find (strcmp (ctx.enc_names, ctx.pred_names{j}), 1)) = row(j); else cols = indicator_cols (ctx.pred_names{j}, ctx.cat_info.levels{ci}, ... ctx.enc_names); Gn = zeros (rows (G) * numel (cols), p_enc); k = 0; for i = 1:rows (G) for c = cols k += 1; Gn(k, :) = G(i, :); Gn(k, c) = 1; endfor endfor G = Gn; endif endfor endfunction ## Encoded columns of a categorical predictor. The reference level has no ## column of its own unless the predictor stood in for a dropped intercept, so ## the levels are looked up by name rather than assumed to start at the second. function cols = indicator_cols (name, levels, enc_names) cols = []; for L = 1:numel (levels) c = find (strcmp (enc_names, [name, '_', char(levels{L})]), 1); if (! isempty (c)) cols(end+1) = c; endif endfor endfunction ## Replace any 'CategoricalVars' pair forwarded to the constructor with the ## resolved predictor names. function nv = set_categorical (nv, names) keep = true (1, numel (nv)); for k = 1:2:numel (nv) - 1 if (strcmpi (nv{k}, 'CategoricalVars')) keep(k:k+1) = false; endif endfor nv = [nv(keep), {'CategoricalVars', names}]; endfunction ## ========================================================================== ## ## Spec resolution and term naming ## ========================================================================== ## ## Resolve a model specification (keyword, terms matrix, or formula) to a terms ## matrix over the P predictors, with the trailing response column. function T = resolve_terms (spec, pred_names, p, intercept) if (ischar (spec) && any (spec == '~')) res = parseWilkinsonFormula (spec, 'expand'); model = res.model; T = zeros (0, p + 1); has_int = false; for i = 1:numel (model) if (isempty (model{i})) has_int = true; else row = term_row (model{i}, pred_names, p); T = [T; row]; endif endfor if (has_int && intercept) T = [zeros(1, p + 1); T]; endif else [T, ~, ~, emsg] = parse_modelspec (spec, pred_names, p, intercept); if (! isempty (emsg)) error ("stepwiseglm: %s", emsg); endif endif endfunction ## Build a terms-matrix row from a cell of factor strings (e.g. {'x1','x2^2'}). function row = term_row (factors, pred_names, p) row = zeros (1, p + 1); for k = 1:numel (factors) f = factors{k}; pw = 1; hat = strfind (f, '^'); if (! isempty (hat)) pw = str2double (f(hat(1)+1:end)); f = f(1:hat(1)-1); endif j = find (strcmp (pred_names, f)); if (isempty (j)) error ("stepwiseglm: unknown predictor '%s' in formula.", f); endif row(j) += pw; endfor endfunction ## Display name of a terms-matrix row (e.g. 'x1:x2', 'x3^2', '(Intercept)'). function nm = term_name (r, pred_names, p) if (all (r(1:p) == 0)) nm = '(Intercept)'; return; endif parts = {}; for j = find (r(1:p) != 0) if (r(j) == 1) parts{end+1} = pred_names{j}; else parts{end+1} = sprintf ("%s^%d", pred_names{j}, r(j)); endif endfor nm = strjoin (parts, ':'); endfunction ## Wilkinson-style formula string for a terms matrix (used for Steps.Start etc). function s = terms_formula (T, resp_name, pred_names, p) has_int = false; parts = {}; for i = 1:rows (T) if (all (T(i, 1:p) == 0)) has_int = true; else parts{end+1} = term_name (T(i, :), pred_names, p); endif endfor if (has_int) rhs = strjoin ([{'1'}, parts], ' + '); elseif (isempty (parts)) rhs = '1'; else rhs = strjoin (parts, ' + '); endif s = sprintf ("%s ~ %s", resp_name, rhs); endfunction ## Sort terms into canonical order: intercept first, then by degree, then by ## the sorted list of predictor indices (matching fitglm's coefficient order). function T = canonical_sort (T, p) m = rows (T); deg = zeros (m, 1); idx = zeros (m, p); # padded index lists for i = 1:m lst = []; for j = find (T(i, 1:p) != 0) lst = [lst, repmat(j, 1, T(i, j))]; endfor deg(i) = numel (lst); idx(i, 1:numel (lst)) = lst; endfor [~, ord] = sortrows ([deg, idx]); T = T(ord, :); endfunction ## ========================================================================== ## ## History ## ========================================================================== ## function h = start_history (T, dev, ncoef, name, stat) h = struct ('Action', 'Start', 'TermName', name, 'Terms', T, ... 'DF', ncoef, 'delDF', NaN, 'Deviance', dev, ... 'Stat', stat, 'PValue', NaN); endfunction ## Name of the Start row: the right-hand side of the starting model, which is ## '1' for a constant start and the whole term list for any other. function nm = start_name (T, pred_names, p) s = terms_formula (T, 'y', pred_names, p); nm = strtrim (s(index (s, '~') + 1:end)); endfunction function h = mkhist (action, r, T, df, ddf, dev, stat, pval, p) ## pred_names not needed here: the term name is resolved by the caller h = struct ('Action', action, 'TermName', r, 'Terms', T, 'DF', df, ... 'delDF', ddf, 'Deviance', dev, 'Stat', stat, 'PValue', pval); endfunction ## Assemble the History table, naming the statistic column after the criterion. function tbl = history_table (hist, crit, estdisp, pred_names, p) m = numel (hist); Action = cell (m, 1); TermName = cell (m, 1); Terms = cell (m, 1); DF = zeros (m, 1); delDF = zeros (m, 1); Deviance = zeros (m, 1); Stat = zeros (m, 1); PValue = zeros (m, 1); for i = 1:m Action{i} = hist(i).Action; if (ischar (hist(i).TermName)) TermName{i} = hist(i).TermName; else TermName{i} = term_name (hist(i).TermName, pred_names, p); endif ## The model's own term order, not the order the search added them in: ## MATLAB's last history row equals the fitted model's terms matrix. Terms{i} = canonical_sort (hist(i).Terms, p); DF(i) = hist(i).DF; delDF(i) = hist(i).delDF; Deviance(i) = hist(i).Deviance; Stat(i) = hist(i).Stat; PValue(i) = hist(i).PValue; endfor ## Which columns the table carries is decided by the criterion: a p-value ## criterion reports the test that drove the step, an information criterion ## reports its own value after the step and has no test to report. The ## deviance is reported only where it is the criterion. switch (crit) case 'deviance' statname = ternary (estdisp, 'FStat', 'Chi2Stat'); tbl = table (Action, TermName, Terms, DF, delDF, Deviance, Stat, ... PValue, 'VariableNames', {'Action', 'TermName', 'Terms', ... 'DF', 'delDF', 'Deviance', statname, 'PValue'}); case 'sse' tbl = table (Action, TermName, Terms, DF, delDF, Stat, PValue, ... 'VariableNames', {'Action', 'TermName', 'Terms', 'DF', ... 'delDF', 'FStat', 'pValue'}); otherwise tbl = table (Action, TermName, Terms, DF, delDF, Stat, ... 'VariableNames', {'Action', 'TermName', 'Terms', 'DF', ... 'delDF', upper(crit)}); endswitch endfunction ## Print an accepted step to the console (Verbose = 1). function print_step (stepnum, h, crit, estdisp, pred_names, p) nm = term_name (h.TermName, pred_names, p); if (strcmp (h.Action, 'Add')) verb = 'Adding'; else verb = 'Removing'; endif switch (crit) case 'deviance' if (estdisp) printf ("%d. %s %s, Deviance = %g, FStat = %g, PValue = %g\n", ... stepnum, verb, nm, h.Deviance, h.Stat, h.PValue); else printf ("%d. %s %s, Deviance = %g, Chi2Stat = %g, PValue = %g\n", ... stepnum, verb, nm, h.Deviance, h.Stat, h.PValue); endif case 'sse' printf ("%d. %s %s, FStat = %g, pValue = %g\n", ... stepnum, verb, nm, h.Stat, h.PValue); case 'aic' printf ("%d. %s %s, AIC = %g\n", stepnum, verb, nm, h.Stat); case 'bic' printf ("%d. %s %s, BIC = %g\n", stepnum, verb, nm, h.Stat); endswitch endfunction ## ========================================================================== ## ## Small helpers ported from GeneralizedLinearModel ## ========================================================================== ## function s = criterion_label (crit, estdisp) switch (crit) case 'deviance' s = ternary (estdisp, 'deviance_f', 'deviance_chi2'); otherwise s = crit; endswitch endfunction function spec = default_link (distr) switch (distr) case 'normal'; spec = 'identity'; case 'binomial'; spec = 'logit'; case 'poisson'; spec = 'log'; case 'gamma'; spec = 'reciprocal'; case 'inverse gaussian'; spec = -2; endswitch endfunction function ll = glm_loglik (distr, y, mu, N, w, phi) rmin = realmin; switch (distr) case 'normal' ne = sum (w); s2 = sum (w .* (y - mu) .^ 2) / ne; ll = -0.5 * ne * (log (2 * pi * s2) + 1); case 'poisson' ll = sum (w .* (y .* log (max (mu, rmin)) - mu - gammaln (y + 1))); case 'binomial' if (isempty (N)) N = ones (size (y)); endif yc = y .* N; ll = sum (w .* (gammaln (N + 1) - gammaln (yc + 1) ... - gammaln (N - yc + 1) + yc .* log (max (mu, rmin)) ... + (N - yc) .* log (max (1 - mu, rmin)))); case 'gamma' a = 1 ./ phi; ll = sum (w .* (a .* log (a) - a .* log (mu) + (a - 1) .* log (y) ... - a .* y ./ mu - gammaln (a))); case 'inverse gaussian' ll = sum (w .* (-0.5 * (log (2 * pi * phi .* y .^ 3) ... + (y - mu) .^ 2 ./ (phi .* mu .^ 2 .* y)))); endswitch endfunction function out = ternary (cond, a, b) if (cond) out = a; else out = b; endif endfunction %!demo %! ## Stepwise Poisson regression: start from a constant model and let the %! ## search add the predictors that matter. %! X = [0.83, -0.68; 0.22, 0.93; -0.12, 0.72; 0.55, -2.55; 1.89, 1.39; ... %! -1.46, -1.18; 1.06, -0.75; -0.89, 0.85; 0.19, -0.71; -0.43, -0.57]; %! y = [2; 2; 2; 1; 9; 0; 2; 1; 1; 1]; %! mdl = stepwiseglm (X, y, 'constant', 'Distribution', 'poisson', ... %! 'Upper', 'linear') %!demo %! ## Stepwise logistic regression selected by AIC, reported from a table. %! X = [0.83, 1.02; 0.22, 0.29; -0.12, 0.09; 0.55, 0.56; 1.89, 1.96; ... %! -1.46, -0.46; 1.06, -0.87; -0.89, 1.18; 0.19, -1.00; -0.43, -0.04]; %! y = [1; 1; 0; 1; 1; 0; 1; 0; 1; 0]; %! tbl = array2table ([X, y], 'VariableNames', {'x1', 'x2', 'y'}); %! mdl = stepwiseglm (tbl, 'constant', 'Distribution', 'binomial', ... %! 'Criterion', 'aic', 'Verbose', 0) ## Shared test data (identical to the MATLAB stepwiseglm verification probes). ## CT and CU carry a three-level categorical predictor; CU adds a second one, ## H, that the response does not depend on, so that it can be dropped. %!shared X, yb, yp, yn, CT, CU, CN %! X = [ 0.83, -0.68, 1.02, 0.02; 0.22, 0.93, 0.29, -0.57; ... %! -0.12, 0.72, 0.09, -1.03; 0.55, -2.55, 0.56, 0.97; ... %! 1.89, 1.39, 1.96, 0.16; -1.46, -1.18, -0.46, 0.33; ... %! 1.06, -0.75, -0.87, 0.08; -0.89, 0.85, 1.18, -1.04; ... %! 0.19, -0.71, -1.00, 1.85; -0.43, -0.57, -0.04, 0.75; ... %! -0.90, -1.75, -0.61, -0.68; 1.52, -0.10, 0.43, -1.16; ... %! 0.58, -1.63, 0.08, -0.73; 0.11, 0.87, -0.40, -0.15; ... %! 1.26, -0.42, -0.95, -1.07; -0.02, 1.09, 1.03, 1.54; ... %! 0.80, 0.97, 0.53, 0.62; -0.40, -1.18, 2.83, -1.36; ... %! -0.61, -0.44, -1.85, -1.57; 1.22, -0.94, -0.26, -0.75; ... %! -0.84, 0.11, -0.55, -0.42; 1.66, -1.27, -1.05, -0.98; ... %! 0.29, 0.59, -0.36, -0.74; -1.10, -1.29, 0.40, 0.35; ... %! 0.08, -0.90, -0.74, -0.51; 1.48, -0.04, -0.11, -1.47; ... %! 0.06, 0.05, -0.09, -1.55; 0.84, 0.13, -1.11, -1.24; ... %! -0.72, -0.02, 0.75, -0.39; 1.11, 0.34, -1.32, -0.18; ... %! 1.48, -1.55, -0.06, 1.28; -0.16, -0.24, 1.34, -1.92; ... %! -0.67, 0.80, 1.01, -0.07; -0.26, -1.10, -0.33, -0.88; ... %! -0.26, -2.42, 0.77, -1.44; -0.62, -0.38, -0.38, -1.77; ... %! 0.41, 0.90, 0.85, 0.72; -1.16, 0.99, 0.74, -2.00; ... %! -0.44, -1.22, 1.06, -1.14; 1.85, 0.21, 0.65, -0.11]; %! yb = [1 1 0 1 1 0 1 0 1 0 0 1 0 1 1 1 1 1 0 1 0 0 0 0 0 1 0 1 0 1 ... %! 0 1 0 0 1 0 1 0 0 1]'; %! yp = [2 2 2 1 9 0 2 1 1 1 0 3 1 2 2 2 3 1 1 2 1 2 2 0 1 3 1 2 1 3 ... %! 2 1 1 1 0 1 3 1 0 5]'; %! yn = [3.17 1.66 0.99 3.87 6.22 1.34 4.8 0.58 4.05 2.06 2.81 3.97 3.84 ... %! 1.11 4.26 2.57 3.07 -1.07 1.7 4.58 0.47 6.41 1.55 1.02 3.29 3.81 ... %! 1.58 2.65 0.81 2.94 5.93 1.09 1.06 2.55 1.44 0.97 2.8 -0.47 0.81 ... %! 4.7]'; %! lev = {'A', 'B', 'C'}'; %! a1 = ((1:48)' - 24.5)/12; %! a2 = sin ((1:48)'/5); %! ag = lev(mod ((0:47)', 3) + 1); %! ay = [0 1 0 1 2 0 1 2 0 1 2 1 1 2 1 1 3 1 1 3 1 1 3 1 1 4 1 2 5 1 3 8 ... %! 2 4 11 3 5 15 4 7 18 4 8 20 4 8 20 5]'; %! CT = table (a1, a2, ag, ay, 'VariableNames', {'x1', 'x2', 'g', 'y'}); %! b1 = ((1:60)' - 30.5)/15; %! bg = lev(mod ((0:59)', 3) + 1); %! bh = lev(mod (floor ((0:59)'/2), 3) + 1); %! by = [2 0 7 2 0 6 2 0 6 2 1 5 2 1 5 2 1 5 2 1 4 3 2 4 3 2 4 3 3 3 3 4 ... %! 3 4 5 3 4 6 3 4 8 2 5 11 2 5 14 2 5 18 2 6 23 2 6 30 2 7 39 2]'; %! CU = table (b1, bg, bh, by, 'VariableNames', {'x1', 'g', 'h', 'y'}); %! CN = table (b1, bg, bh, by / 4, 'VariableNames', {'x1', 'g', 'h', 'y'}); ## Test results (values verified against MATLAB's stepwiseglm) %!test %! ## Binomial: the Deviance criterion selects x1 and x3. %! mdl = stepwiseglm (X, yb, 'constant', 'Distribution', 'binomial', ... %! 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)', 'x1', 'x3'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [-0.567847008659897; 2.24005553968806; 1.10257647012], 1e-8); %! assert_equal (mdl.Deviance, 35.424118776089266, 1e-9); %! assert_equal (mdl.LogLikelihood, -17.712059388044633, 1e-9); %!test %! ## Poisson: selects x1 and x2 (interaction not significant). %! mdl = stepwiseglm (X, yp, 'constant', 'Distribution', 'poisson', ... %! 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)', 'x1', 'x2'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [0.242293607853268; 0.672082935896644; 0.459103523205052], 1e-8); %! assert_equal (mdl.Deviance, 5.62441316952502, 1e-9); %!test %! ## Normal (estimated dispersion, F-test): mains plus x1:x4 and x2:x3. %! mdl = stepwiseglm (X, yn, 'constant', 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, ... %! {'(Intercept)', 'x1', 'x2', 'x3', 'x4', 'x1:x4', 'x2:x3'}); %! assert_equal (mdl.NumCoefficients, 7); %! assert_equal (any (strcmp (mdl.CoefficientNames, 'x2:x3')), true); %!test %! ## The AIC criterion is greedier and also brings in x3:x4. %! mdl = stepwiseglm (X, yn, 'linear', 'Upper', 'interactions', ... %! 'Criterion', 'aic', 'Verbose', 0); %! assert_equal (mdl.NumCoefficients, 8); %! assert_equal (any (strcmp (mdl.CoefficientNames, 'x3:x4')), true); %!test %! ## BIC reaches the same eight-coefficient model here. %! mdl = stepwiseglm (X, yn, 'constant', 'Criterion', 'bic', 'Verbose', 0); %! assert_equal (mdl.NumCoefficients, 8); %! assert_equal (any (strcmp (mdl.CoefficientNames, 'x3:x4')), true); %!test %! ## A formula-valued Upper bounds the candidate universe. %! mdl = stepwiseglm (X, yp, 'y ~ x1', 'Distribution', 'poisson', ... %! 'Upper', 'y ~ x1 + x2 + x3', 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)', 'x1', 'x2'}); %!test %! ## Lower/Upper keywords: search within [linear, quadratic] adds interactions. %! mdl = stepwiseglm (X, yn, 'linear', 'Lower', 'linear', ... %! 'Upper', 'quadratic', 'Verbose', 0); %! assert_equal (mdl.NumCoefficients, 7); %! assert_equal (any (strcmp (mdl.CoefficientNames, 'x2:x3')), true); %! assert_equal (any (strcmp (mdl.CoefficientNames, 'x1:x4')), true); %!test %! ## The Steps property records the trace; History matches the deviance test. %! mdl = stepwiseglm (X, yb, 'constant', 'Distribution', 'binomial', ... %! 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.Steps.Criterion, 'deviance_chi2'); %! assert_equal (size (mdl.Steps.History, 1), 3); %! assert_equal (mdl.Steps.History.Action{1}, 'Start'); %! assert_equal (mdl.Steps.History.Chi2Stat(2), 14.8398950335268, 1e-6); %!test %! ## Table input, response taken from the last column by default. %! tbl = array2table ([X(:,1:2), yp], 'VariableNames', {'a', 'b', 'y'}); %! mdl = stepwiseglm (tbl, 'constant', 'Distribution', 'poisson', ... %! 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)', 'a', 'b'}); %!test %! ## Collinear predictors are fitted, not refused; the redundant column is %! ## dropped rather than entering the model. %! mdl = stepwiseglm ([X(:,1), X(:,1)], yn, 'constant', 'Verbose', 0); %! assert_equal (class (mdl), 'GeneralizedLinearModel'); %! assert_equal (mdl.CoefficientNames, {'(Intercept)', 'x1'}); %!test %! ## A cell-array column of a table is taken as categorical without being %! ## named, and the fit matches MATLAB R2024a. %! mdl = stepwiseglm (CT, 'y ~ 1', 'Upper', 'y ~ x1 + x2 + g', ... %! 'Distribution', 'poisson', 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, ... %! {'(Intercept)', 'x1', 'x2', 'g_B', 'g_C'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [0.603329330791641; 0.796711055930047; 0.285851899335031; ... %! 0.915480956228751; -0.550531653341791], 1e-10); %! assert_equal (mdl.Deviance, 5.49511881200541, 1e-10); %!test %! ## The indicators of a categorical predictor enter as one term, so the %! ## step is worth L-1 degrees of freedom and is named for the predictor. %! mdl = stepwiseglm (CT, 'y ~ 1', 'Upper', 'y ~ x1 + x2 + g', ... %! 'Distribution', 'poisson', 'Verbose', 0); %! assert_equal (mdl.Steps.History.TermName, {'1'; 'x1'; 'g'; 'x2'}); %! assert_equal (mdl.Steps.History.DF, [1; 2; 4; 5]); %! assert_equal (mdl.Steps.History.delDF, [NaN; 1; 2; 1]); %!test %! ## The deviance test of a grouped term is the joint test of its indicators. %! mdl = stepwiseglm (CT, 'y ~ 1', 'Upper', 'y ~ x1 + x2 + g', ... %! 'Distribution', 'poisson', 'Verbose', 0); %! assert_equal (mdl.Steps.History.Deviance, ... %! [220.977093395079; 81.2408117834874; 10.3794318146782; ... %! 5.49511881200541], 1e-9); %! assert_equal (mdl.Steps.History.Chi2Stat, ... %! [NaN; 139.736281611591; 70.8613799688091; ... %! 4.88431300267280], 1e-9); %! assert_equal (mdl.Steps.History.PValue(4), 0.0271018179857880, 1e-12); %!test %! ## An interaction naming a categorical predictor is also one term, and its %! ## coefficients are named for the indicators. %! mdl = stepwiseglm (CU, 'y ~ 1', 'Upper', 'y ~ x1*g', ... %! 'Distribution', 'poisson', 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)', 'x1', 'g_B', 'g_C', ... %! 'x1:g_B', 'x1:g_C'}); %! assert_equal (mdl.Steps.History.TermName, {'1'; 'x1'; 'g'; 'x1:g'}); %! assert_equal (mdl.Steps.History.delDF, [NaN; 1; 2; 2]); %! assert_equal (mdl.Coefficients.Estimate, ... %! [1.21521326020005; 0.372340711075598; -0.0523666888775920; ... %! 0; 0.950243386703989; -0.744681422151197], 1e-9); %!test %! ## A grouped term leaves in one step too, and its delDF is negative. %! mdl = stepwiseglm (CU, 'y ~ x1 + g + h', 'Lower', 'y ~ 1', 'Upper', ... %! 'y ~ x1 + g + h', 'Distribution', 'poisson', ... %! 'Verbose', 0); %! assert_equal (mdl.Formula.LinearPredictor, '1 + x1 + g'); %! assert_equal (mdl.Steps.History.Action, {'Start'; 'Remove'}); %! assert_equal (mdl.Steps.History.TermName, {'1 + x1 + g + h'; 'h'}); %! assert_equal (mdl.Steps.History.DF, [6; 4]); %! assert_equal (mdl.Steps.History.delDF, [NaN; -2]); %! assert_equal (mdl.Steps.History.Chi2Stat, [NaN; 0.914420851291737], 1e-10); %!test %! ## A categorical array is coded by its categories, a cell array by the %! ## order its labels appear; on this fixture the two agree. %! ct = CT; %! ct.g = categorical (ct.g); %! m1 = stepwiseglm (ct, 'y ~ 1', 'Upper', 'y ~ x1 + x2 + g', ... %! 'Distribution', 'poisson', 'Verbose', 0); %! m2 = stepwiseglm (CT, 'y ~ 1', 'Upper', 'y ~ x1 + x2 + g', ... %! 'Distribution', 'poisson', 'Verbose', 0); %! assert_equal (m1.CoefficientNames, m2.CoefficientNames); %! assert_equal (m1.Coefficients.Estimate, m2.Coefficients.Estimate, 1e-12); %!test %! ## A column of a predictor matrix is categorical only when named, and the %! ## levels are then its sorted distinct values. %! Xm = [((1:60)' - 30.5)/15, mod((0:59)', 3) + 1, ... %! mod(floor((0:59)'/2), 3) + 1]; %! ym = CU.y; %! mdl = stepwiseglm (Xm, ym, 'constant', 'Upper', 'linear', ... %! 'Distribution', 'poisson', ... %! 'CategoricalVars', logical ([0, 1, 1]), 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)', 'x1', 'x2_2', 'x2_3'}); %! assert_equal (mdl.Steps.History.TermName, {'1'; 'x1'; 'x2'}); %! assert_equal (mdl.Steps.History.delDF, [NaN; 1; 2]); %!test %! ## Indices and a logical vector mark the same columns. %! Xm = [((1:60)' - 30.5)/15, mod((0:59)', 3) + 1, ... %! mod(floor((0:59)'/2), 3) + 1]; %! ym = CU.y; %! m1 = stepwiseglm (Xm, ym, 'constant', 'Upper', 'linear', ... %! 'Distribution', 'poisson', 'CategoricalVars', [2, 3], ... %! 'Verbose', 0); %! m2 = stepwiseglm (Xm, ym, 'constant', 'Upper', 'linear', ... %! 'Distribution', 'poisson', ... %! 'CategoricalVars', logical ([0, 1, 1]), 'Verbose', 0); %! assert_equal (m1.Coefficients.Estimate, m2.Coefficients.Estimate, 1e-12); %!test %! ## The Start row names the starting model, not the constant, and reports %! ## the coefficient count it was fitted with. %! mdl = stepwiseglm (CU, 'y ~ x1 + g', 'Lower', 'y ~ x1 + g', 'Upper', ... %! 'y ~ x1 + g', 'Distribution', 'poisson', 'Verbose', 0); %! assert_equal (mdl.Steps.History.TermName, {'1 + x1 + g'}); %! assert_equal (mdl.Steps.History.DF, 4); %!test %! ## An estimated dispersion turns the deviance test into an F test, and the %! ## criterion is labelled for it. %! mdl = stepwiseglm (CN, 'y ~ 1', 'Upper', 'y ~ x1 + g + h', ... %! 'Distribution', 'normal', 'Verbose', 0); %! assert_equal (mdl.Steps.Criterion, 'deviance_f'); %! assert_equal (mdl.Steps.History.Properties.VariableNames, ... %! {'Action', 'TermName', 'Terms', 'DF', 'delDF', 'Deviance', ... %! 'FStat', 'PValue'}); %! assert_equal (mdl.Steps.History.FStat, ... %! [NaN; 15.1516937210352; 4.75429941449063], 1e-10); %!test %! ## Under 'sse' the history reports the F test alone, with MATLAB's own %! ## lower-case column name. %! mdl = stepwiseglm (CN, 'y ~ 1', 'Upper', 'y ~ x1 + g + h', ... %! 'Distribution', 'normal', 'Criterion', 'sse', ... %! 'Verbose', 0); %! assert_equal (mdl.Steps.History.Properties.VariableNames, ... %! {'Action', 'TermName', 'Terms', 'DF', 'delDF', 'FStat', ... %! 'pValue'}); %! assert_equal (mdl.Steps.History.pValue, ... %! [NaN; 0.000258691112717; 0.012385457865093], 1e-12); %!test %! ## An information criterion has no test to report, so the history carries %! ## its value instead, the starting model included. %! mdl = stepwiseglm (CU, 'y ~ 1', 'Upper', 'y ~ x1 + g + h', ... %! 'Distribution', 'poisson', 'Criterion', 'aic', ... %! 'Verbose', 0); %! assert_equal (mdl.Steps.History.Properties.VariableNames, ... %! {'Action', 'TermName', 'Terms', 'DF', 'delDF', 'AIC'}); %! assert_equal (mdl.Steps.History.AIC, ... %! [504.960173118363; 394.245450753972; 341.770323510125], 1e-9); %!test %! ## A p-value far below eps is computed on the upper tail, not as one minus %! ## the lower, so it does not round to zero. %! mdl = stepwiseglm (CU, 'y ~ 1', 'Upper', 'y ~ x1*g', ... %! 'Distribution', 'poisson', 'Verbose', 0); %! assert_equal (mdl.Steps.History.PValue(2), 2.49165e-26, 1e-30); %! assert_equal (mdl.Steps.History.PValue(4), 1.19350e-32, 1e-36); %!test %! ## Each history row carries the terms in the model's own order, not in the %! ## order the search added them, so the last row is the fitted model's. %! mdl = stepwiseglm (CT, 'y ~ 1', 'Upper', 'y ~ x1 + x2 + g', ... %! 'Distribution', 'poisson', 'Verbose', 0); %! assert_equal (mdl.Steps.History.TermName, {'1'; 'x1'; 'g'; 'x2'}); %! assert_equal (mdl.Steps.History.Terms{end}, mdl.Formula.Terms); %! assert_equal (mdl.Steps.History.Terms{end}, ... %! [0, 0, 0, 0; 1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0]); %!test %! ## Start, Lower, and Upper are formula objects carrying the link. %! mdl = stepwiseglm (CT, 'y ~ 1', 'Upper', 'y ~ x1 + x2 + g', ... %! 'Distribution', 'poisson', 'Verbose', 0); %! assert_equal (class (mdl.Steps.Start), 'LinearFormula'); %! assert_equal (char (mdl.Steps.Start), 'log(y) ~ 1'); %! assert_equal (char (mdl.Steps.Upper), 'log(y) ~ 1 + x1 + x2 + g'); %! assert_equal (mdl.Steps.Lower.LinearPredictor, '1'); ## Test input validation %!error stepwiseglm () %!error stepwiseglm ("a", [1;2]) %!error ... %! stepwiseglm ([1, 2; 3, 4], [1; 0], 'Distribution', 'wibble') %!error ... %! stepwiseglm ([1, 2; 3, 4], [1; 0], 'Criterion', 'nope') %!error ... %! stepwiseglm ([1, 2; 3, 4], [1; 0], 'linear', 'foo', 1) %!error ... %! stepwiseglm ([1, 2; 3, 4], [1; 0], 'CategoricalVars', {'nope'}) %!error ... %! stepwiseglm ([1, 2; 3, 4], [1; 0], 'CategoricalVars', {1}) %!test # a two-column binomial response matches MATLAB R2024a %! x = (1:10)'; %! S = [0 1 1 2 3 4 6 7 9 9]'; %! mdl = stepwiseglm (x, [S, 10 * ones(10, 1)], 'constant', 'upper', ... %! 'linear', 'Distribution', 'binomial', 'Verbose', 0); %! assert_equal (mdl.Coefficients.Estimate, ... %! [-4.07619632416318; 0.639874139429377], 1e-10); %! assert_equal (mdl.Deviance, 1.37328920133713, 1e-10); %!test # the two-column and BinomialSize forms select the same model %! x = (1:10)'; %! S = [0 1 1 2 3 4 6 7 9 9]'; %! N = 10 * ones (10, 1); %! m1 = stepwiseglm (x, [S, N], 'constant', 'upper', 'linear', ... %! 'Distribution', 'binomial', 'Verbose', 0); %! m2 = stepwiseglm (x, S, 'constant', 'upper', 'linear', ... %! 'Distribution', 'binomial', 'BinomialSize', N, ... %! 'Verbose', 0); %! assert_equal (m2.Coefficients.Estimate, m1.Coefficients.Estimate, 1e-12); statistics-release-1.9.2/inst/Regression/stepwiselm.m000066400000000000000000003063601524624707500230260ustar00rootroot00000000000000## Copyright (C) 2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{mdl} =} stepwiselm (@var{tbl}) ## @deftypefnx {statistics} {@var{mdl} =} stepwiselm (@var{tbl}, @var{ResponseVarName}) ## @deftypefnx {statistics} {@var{mdl} =} stepwiselm (@var{tbl}, @var{y}) ## @deftypefnx {statistics} {@var{mdl} =} stepwiselm (@var{X}, @var{y}) ## @deftypefnx {statistics} {@var{mdl} =} stepwiselm (@dots{}, @var{InitialModel}) ## @deftypefnx {statistics} {@var{mdl} =} stepwiselm (@dots{}, @var{Name}, @var{Value}, @dots{}) ## ## Fit a linear regression model using stepwise regression and return a ## @code{LinearModel} object. ## ## @code{stepwiselm} starts from an initial model and repeatedly searches for ## a term to add to, or remove from, the current model, based on the value of ## the @qcode{'Criterion'} option, until no single addition or removal ## improves the model any further. ## ## @subheading Basic Syntax ## ## @itemize ## @item ## @code{@var{mdl} = stepwiselm (@var{tbl})} fits a stepwise model using the ## variables in the table (or dataset) @var{tbl}, starting from a constant ## model. By default, the last variable in @var{tbl} is used as the response ## and all other variables are candidate predictors. Variables that are ## @code{categorical} arrays, cell arrays of character vectors, or logical ## arrays are automatically treated as categorical predictors. ## @item ## @code{@var{mdl} = stepwiselm (@var{tbl}, @var{ResponseVarName})} uses the ## variable named @var{ResponseVarName} in @var{tbl} as the response, and all ## remaining variables in @var{tbl} as candidate predictors. ## @item ## @code{@var{mdl} = stepwiselm (@var{tbl}, @var{y})} uses the variables in ## @var{tbl} as candidate predictors and the external numeric vector @var{y} ## as the response. ## @item ## @code{@var{mdl} = stepwiselm (@var{X}, @var{y})} fits a stepwise model of ## the response @var{y} to the predictor data @var{X}, an @math{N*P} numeric ## or logical matrix. By default, the predictors are named @qcode{'x1'}, ## @qcode{'x2'}, @dots{}, @qcode{'xP'} and the response is named ## @qcode{'y'}. ## @end itemize ## ## @subheading Initial Model, and Lower/Upper Bounds ## ## @code{@var{mdl} = stepwiselm (@dots{}, @var{InitialModel})} additionally ## specifies the model to start the stepwise search from, using any of the ## input combinations shown above. @var{InitialModel} can be any of the ## following, and the same set of values can also be used for the ## @qcode{'Lower'} and @qcode{'Upper'} options below, which bound the ## smallest and largest set of terms @code{stepwiselm} is allowed to reach. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Value} @tab @var{Description} ## ## @item @qcode{'constant'} @tab Model contains only an intercept term. This ## is the default @var{InitialModel} and default @qcode{'Lower'} bound. ## ## @item @qcode{'linear'} @tab Model contains an intercept and one term ## for each predictor variable. ## ## @item @qcode{'interactions'} @tab Model contains an intercept, all ## linear terms, and all pairwise products of distinct predictor variables ## (no squared terms). This is the default @qcode{'Upper'} bound. ## ## @item @qcode{'purequadratic'} @tab Model contains an intercept, all ## linear terms, and all squared terms. ## ## @item @qcode{'quadratic'} @tab Model contains an intercept, all linear ## terms, all pairwise products of distinct predictor variables, and all ## squared terms. ## ## @item @qcode{'polyijk'} @tab Model is a polynomial with maximum degree ## @math{i} in the first predictor, @math{j} in the second, and so on, given ## as a run of single-digit numerals, one per predictor (e.g. ## @qcode{'poly21'} for two predictors). The model contains interaction ## terms, but the degree of each interaction term never exceeds the largest ## of the specified per-predictor degrees. ## ## @item terms matrix @tab A @math{T*P} or @math{T*(P+1)} numeric matrix, ## where @math{T} is the number of terms and @math{P} is the number of ## predictor variables, following the same convention as @code{fitlm}'s ## terms matrix. When @var{InitialModel} is given as a terms matrix, the ## @qcode{'PredictorVars'} option may not also be used. ## ## @item Wilkinson formula @tab A character vector of the form ## @qcode{'y ~ terms'}. When a formula is combined with @qcode{'ResponseVar'} ## or @qcode{'PredictorVars'}, the formula's response and predictor terms ## must agree with those options, or @code{stepwiselm} errors. ## @end multitable ## ## @subheading Options ## ## @code{@var{mdl} = stepwiselm (@dots{}, @var{Name}, @var{Value}, @dots{})} ## specifies additional options using one or more @qcode{Name-Value} pair ## arguments. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Criterion'} @tab Criterion used to decide whether a term is ## added or removed at each step. One of @qcode{'sse'} (default), ## @qcode{'aic'}, @qcode{'bic'}, @qcode{'rsquared'}, or @qcode{'adjrsquared'}. ## For @qcode{'sse'}, the p-value of an F-test comparing the model with and ## without the candidate term is used; for the others, the raw change in the ## named quantity is used directly against @qcode{'PEnter'}/@qcode{'PRemove'}. ## ## @item @qcode{'PEnter'} @tab Threshold to add a term. Defaults depend on ## @qcode{'Criterion'}: @qcode{0.05} for @qcode{'sse'}, @qcode{0} for ## @qcode{'aic'}/@qcode{'bic'}, @qcode{0.1} for @qcode{'rsquared'}, @qcode{0} ## for @qcode{'adjrsquared'}. ## ## @item @qcode{'PRemove'} @tab Threshold to remove a term. Defaults depend ## on @qcode{'Criterion'}: @qcode{0.10} for @qcode{'sse'}, @qcode{0.01} for ## @qcode{'aic'}/@qcode{'bic'}, @qcode{0.05} for @qcode{'rsquared'}, ## @qcode{-0.05} for @qcode{'adjrsquared'}. ## ## @item @qcode{'NSteps'} @tab Maximum number of add/remove steps to take, ## given as a nonnegative integer. Default is unlimited. @qcode{'NSteps'} ## set to @qcode{0} returns the initial model unchanged. ## ## @item @qcode{'Lower'} @tab Model specification (in the same form as ## @var{InitialModel}, above) describing terms that may never be removed from ## the model. Terms in @qcode{'Lower'} are protected from removal, but are ## not automatically added if absent from @var{InitialModel}. Default is ## @qcode{'constant'}. ## ## @item @qcode{'Upper'} @tab Model specification (in the same form as ## @var{InitialModel}, above) describing the largest set of terms ## @code{stepwiselm} may add. Default is @qcode{'interactions'}. ## ## @item @qcode{'Verbose'} @tab Controls how much progress information is ## printed while stepping. @qcode{0} suppresses all output, @qcode{1} ## (default) prints the action taken at each step, @qcode{2} additionally ## prints the p-value or criterion value considered for every candidate term ## examined at each step. ## ## @item @qcode{'Intercept'} @tab A logical scalar indicating whether the ## initial model includes a constant (intercept) term. Only applies when ## @var{InitialModel} is a character vector model name (or omitted); ignored ## when @var{InitialModel} is a terms matrix or formula. Default is ## @qcode{true}. ## ## @item @qcode{'Weights'} @tab A numeric vector of nonnegative observation ## weights, one per observation. Default is a vector of ones. ## ## @item @qcode{'Exclude'} @tab A numeric or logical vector specifying ## observations to exclude from the fit. ## ## @item @qcode{'CategoricalVars'} @tab Specifies which predictor variables ## are treated as categorical, given as a vector of column indices, a logical ## vector, or a cell array of variable names (table input only). Each ## categorical predictor with @math{L} distinct categories is expanded into ## @math{L-1} indicator variables, and @code{stepwiselm} always adds or ## removes that entire group of indicator variables together, in a single ## step, treating the categorical predictor as one term. ## ## @item @qcode{'VarNames'} @tab A cell array of character vectors naming the ## predictor and response variables, response last. Only applies when ## @var{X} and @var{y} are supplied directly, not table input. ## ## @item @qcode{'ResponseVar'} @tab A character vector naming the response ## variable, overriding the response that would otherwise be inferred (the ## last table variable, or @qcode{'y'} for matrix input). ## ## @item @qcode{'PredictorVars'} @tab A cell array of character vectors ## naming which variables in @var{tbl} to consider as candidate predictors. ## By default, all variables in @var{tbl} other than the response are used. ## May not be combined with a terms-matrix @var{InitialModel}, and must ## agree with any formula-based @var{InitialModel}. ## @end multitable ## ## @subsubheading The @code{Steps} property ## ## The returned model's @code{Steps} property records the trace, as a ## structure with seven fields. @code{Start}, @code{Lower}, and @code{Upper} ## are @code{LinearFormula} objects for the starting model and the two bounds; ## @code{Criterion} is the criterion as it was asked for; @code{PEnter} and ## @code{PRemove} are the thresholds it ran under; and @code{History} is a ## table with one row per step. ## ## @code{History} always carries @code{Action} (@qcode{'Start'}, ## @qcode{'Add'}, or @qcode{'Remove'}), @code{TermName}, @code{Terms} (the ## terms matrix after the step, over the model's variables), @code{DF} (the ## coefficient count after the step), and @code{delDF} (the change in it, ## @emph{negative} for a removal). The remaining columns follow the ## criterion, which is why a history is read by name and not by position: ## ## @multitable @columnfractions 0.25 0.7 ## @headitem Criterion @tab Further columns ## @item @qcode{'SSE'} @tab @code{FStat} and @code{pValue}. ## @item @qcode{'AIC'}, @qcode{'BIC'}, @qcode{'Rsquared'}, ## @qcode{'AdjRsquared'} @tab one column named for the criterion, holding its ## value after the step, the starting model included. ## @end multitable ## ## The first row is the starting model, named by its right-hand side. ## @code{step} appends to the history it inherits rather than beginning a new ## one, and inherits @code{Criterion}, @code{Lower}, @code{PEnter}, and ## @code{PRemove} from it; @code{Steps.Start} is then the model stepped from. ## ## @subheading Algorithm ## ## At each step, @code{stepwiselm} examines every term not currently in the ## model but within the @qcode{'Upper'} bound, and every term currently in ## the model but not protected by the @qcode{'Lower'} bound. If any term ## outside the model would improve it by at least @qcode{'PEnter'}, the best ## such term is added; otherwise, if any term inside the model falls short of ## @qcode{'PRemove'}, the worst such term is removed. The process repeats ## until neither an addition nor a removal improves the model, or until ## @qcode{'NSteps'} steps have been taken. ## ## @code{stepwiselm} never adds a higher-order term unless all of its ## lower-order marginal terms are already in the model (e.g. it will not add ## @code{x1:x2^2} unless both @code{x1} and @code{x2^2} are already present), ## and correspondingly never removes a lower-order term that a higher-order ## term still in the model depends on. At every step, if a term in the ## current model is found to be exactly redundant (linearly dependent on the ## other terms already in the model), it is removed immediately regardless of ## the @qcode{'Criterion'} value. ## ## Because the final model depends on the initial model and the order in ## which terms are considered, @code{stepwiselm} finds a locally, but not ## necessarily globally, optimal model. ## ## Robust fitting cannot be combined with stepwise regression; do not pass ## @qcode{'RobustOpts'} to @code{stepwiselm}. ## ## @var{mdl} is returned as a @code{LinearModel} object. See also the ## @code{step} method of @code{LinearModel}, which performs a single bounded ## round of stepwise search starting from an already-fitted model. ## ## @seealso{LinearModel, fitlm} ## @end deftypefn function mdl = stepwiselm (varargin) if (nargin < 1) error ("stepwiselm: Not enough input arguments."); endif sw_keys = {'criterion','penter','premove','nsteps','verbose','lower','upper'}; is_sw = @(s) (ischar (s) || isstring (s)) && any (strcmpi (char (s), sw_keys)); criterion = 'sse'; ## Steps.Criterion reports the criterion as it was asked for; unasked, it ## reports MATLAB's own spelling of the default. crit_label = 'SSE'; penter = []; premove = []; nsteps = Inf; verbose = 1; lower_sp = 'constant'; upper_sp = 'interactions'; fit_args = {}; i = 1; n = numel (varargin); while (i <= n) a = varargin{i}; if (is_sw (a)) if (i == n) error ("stepwiselm: Name-Value arguments must be in pairs."); endif key = lower (char (a)); val = varargin{i+1}; if (strcmp (key, 'criterion')) if (! (ischar (val) || isstring (val)) || ... ! any (strcmpi (char (val), {'sse','aic','bic','rsquared','adjrsquared'}))) error (["stepwiselm: '" char(val) "' is not a valid value for the" ... " 'Criterion' argument. Valid values are: 'AIC', 'BIC'," ... " 'Rsquared', 'AdjRsquared', 'SSE'."]); endif criterion = lower (char (val)); crit_label = char (val); elseif (strcmp (key, 'penter')) penter = double (val); elseif (strcmp (key, 'premove')) premove = double (val); elseif (strcmp (key, 'nsteps')) nsteps = double (val); elseif (strcmp (key, 'verbose')) verbose = double (val); elseif (strcmp (key, 'lower')) lower_sp = val; elseif (strcmp (key, 'upper')) upper_sp = val; endif i += 2; else fit_args{end+1} = a; i += 1; endif endwhile if (isempty (penter)) pe_tbl = struct ('sse', 0.05, 'aic', 0, 'bic', 0, 'rsquared', 0.1, 'adjrsquared', 0); penter = pe_tbl.(criterion); endif if (isempty (premove)) pr_tbl = struct ('sse', 0.10, 'aic', 0.01, 'bic', 0.01, 'rsquared', 0.05, 'adjrsquared', -0.05); premove = pr_tbl.(criterion); endif ## A term must not qualify to enter and to leave at the same time. Where ## the thresholds are p-values PEnter sits below PRemove; where they are an ## improvement in the criterion the sense is reversed. Without this the ## search adds and drops one term forever, NSteps being unlimited by ## default, so the call never returns. if (any (strcmp (criterion, {'sse', 'aic', 'bic'}))) if (penter >= premove) error (strcat ("stepwiselm: PEnter (%g) must be less than PRemove", ... " (%g) for the '%s' criterion."), penter, premove, criterion); endif elseif (penter <= premove) error (strcat ("stepwiselm: PEnter (%g) must be greater than PRemove", ... " (%g) for the '%s' criterion."), penter, premove, criterion); endif fit_args = sw_ensure_modelspec (fit_args); sw_predictorvars_matrix_conflict (fit_args); [positional, first_nv] = sw_split_positional (fit_args); init_spec = positional{end}; nv_part = fit_args(first_nv:end); resp_name = sw_response_name (positional, nv_part); probe_positional = positional; probe_positional{end} = 'constant'; probe_nv = sw_set_responsevar (nv_part, resp_name); probe_args = [probe_positional, probe_nv]; mdl0 = fitlm (probe_args{:}); if (! isempty (mdl0.Robust)) error ("stepwiselm: Robust fitting cannot be combined with stepwise regression."); endif info = LinearModel.sw_extract (mdl0); cat_mask = false (1, info.p_raw); if (! isempty (info.cat_info.names)) for j = 1:info.p_raw cat_mask(j) = any (strcmp (info.cat_info.names, info.pred_names{j})); endfor endif T_init_raw = sw_resolve_bound (init_spec, info.pred_names, cat_mask, info); T_lower_raw = sw_resolve_bound (lower_sp, info.pred_names, cat_mask, info); T_upper_raw = sw_resolve_bound (upper_sp, info.pred_names, cat_mask, info); terms_cur = sw_raw_terms_to_encoded (T_init_raw, info.pred_names, info.cat_info, info.enc_names); T_start_enc = terms_cur; T_lower_enc = sw_raw_terms_to_encoded (T_lower_raw, info.pred_names, info.cat_info, info.enc_names); T_upper_enc = sw_raw_terms_to_encoded (T_upper_raw, info.pred_names, info.cat_info, info.enc_names); groups_upper = sw_group_encoded_terms (T_upper_enc, info.pred_names, info.cat_info, info.enc_names); groups_lower = sw_group_encoded_terms (T_lower_enc, info.pred_names, info.cat_info, info.enc_names); cur_hi = any (all (terms_cur(:,1:end-1) == 0, 2)); cur_fit = LinearModel.lm_fit (build_design (terms_cur, info.X_enc), info.y, info.w, false); ## The trace reported as Steps.History. Its first row is the starting ## model, named by its right-hand side and carrying no test. hist = sw_hist_row ('Start', sw_terms_rhs (terms_cur, info), terms_cur, ... rows (terms_cur), NaN, ... sw_start_stat (criterion, cur_fit, cur_hi), NaN, info); step_no = 0; any_step = false; while (step_no < nsteps) groups_cur = sw_group_encoded_terms (terms_cur, info.pred_names, info.cat_info, info.enc_names); cur_raws = sw_group_raws (groups_cur); add_cands = sw_add_candidates (groups_upper, cur_raws); best_add = []; best_add_score = []; for c = 1:numel (add_cands) g = add_cands{c}; trial_terms = [terms_cur; g.term_rows]; trial_hi = any (all (trial_terms(:,1:end-1) == 0, 2)); trial_fit = LinearModel.lm_fit (build_design (trial_terms, info.X_enc), info.y, info.w, false); score = sw_criterion_score (criterion, info.n_obs, cur_hi, trial_hi, cur_fit, trial_fit); if (verbose >= 2) sw_print_candidate (criterion, 'add', g.name, score); endif if (isempty (best_add) || sw_prefer (criterion, 'add', score, best_add_score)) best_add = g; best_add.fit = trial_fit; best_add.hi = trial_hi; best_add_score = score; endif endfor if (isempty (add_cands) && verbose >= 2) printf ("No candidate terms to add\n"); endif if (! isempty (best_add) && sw_threshold_ok (criterion, 'add', best_add_score, penter)) prev_df = rows (terms_cur); terms_cur = [terms_cur; best_add.term_rows]; cur_fit = best_add.fit; cur_hi = best_add.hi; [hs, hp] = sw_hist_stat (criterion, 'add', best_add_score); hist(end+1) = sw_hist_row ('Add', best_add.name, terms_cur, ... rows (terms_cur), ... rows (terms_cur) - prev_df, hs, hp, info); step_no += 1; any_step = true; if (verbose >= 1) sw_print_action (criterion, step_no, 'Adding', best_add.name, best_add_score); endif continue; endif groups_cur = sw_group_encoded_terms (terms_cur, info.pred_names, info.cat_info, info.enc_names); cur_raws = sw_group_raws (groups_cur); lower_raws = sw_group_raws (groups_lower); rem_cands = sw_remove_candidates (groups_cur, lower_raws, cur_raws); redundant_g = []; for c = 1:numel (rem_cands) g = rem_cands{c}; keep = true (rows (terms_cur), 1); keep (g.idx) = false; trial_terms = terms_cur (keep, :); trial_fit = LinearModel.lm_fit (build_design (trial_terms, info.X_enc), info.y, info.w, false); if (trial_fit.rank_X == cur_fit.rank_X) redundant_g = g; redundant_g.fit = trial_fit; redundant_g.hi = any (all (trial_terms(:,1:end-1) == 0, 2)); break; endif endfor if (! isempty (redundant_g)) keep = true (rows (terms_cur), 1); keep (redundant_g.idx) = false; prev_df = rows (terms_cur); terms_cur = terms_cur (keep, :); cur_fit = redundant_g.fit; cur_hi = redundant_g.hi; hist(end+1) = sw_hist_row ('Remove', redundant_g.name, terms_cur, ... rows (terms_cur), ... rows (terms_cur) - prev_df, Inf, NaN, info); step_no += 1; any_step = true; if (verbose >= 1) printf ("%d. Removing %s, FStat = Inf, pValue = NaN\n", step_no, redundant_g.name); endif continue; endif best_rem = []; best_rem_score = []; for c = 1:numel (rem_cands) g = rem_cands{c}; keep = true (rows (terms_cur), 1); keep (g.idx) = false; trial_terms = terms_cur (keep, :); trial_hi = any (all (trial_terms(:,1:end-1) == 0, 2)); trial_fit = LinearModel.lm_fit (build_design (trial_terms, info.X_enc), info.y, info.w, false); score = sw_criterion_score (criterion, info.n_obs, trial_hi, cur_hi, trial_fit, cur_fit); if (verbose >= 2) sw_print_candidate (criterion, 'remove', g.name, score); endif if (isempty (best_rem) || sw_prefer (criterion, 'remove', score, best_rem_score)) best_rem = g; best_rem.fit = trial_fit; best_rem.hi = trial_hi; best_rem_score = score; endif endfor if (isempty (rem_cands) && verbose >= 2) printf (" No candidate terms to remove\n"); endif if (! isempty (best_rem) && sw_threshold_ok (criterion, 'remove', best_rem_score, premove)) keep = true (rows (terms_cur), 1); keep (best_rem.idx) = false; prev_df = rows (terms_cur); terms_cur = terms_cur (keep, :); cur_fit = best_rem.fit; cur_hi = best_rem.hi; [hs, hp] = sw_hist_stat (criterion, 'remove', best_rem_score); hist(end+1) = sw_hist_row ('Remove', best_rem.name, terms_cur, ... rows (terms_cur), ... rows (terms_cur) - prev_df, hs, hp, info); step_no += 1; any_step = true; if (verbose >= 1) sw_print_action (criterion, step_no, 'Removing', best_rem.name, best_rem_score); endif continue; endif break; endwhile if (! any_step && verbose >= 1) printf ("No terms to add to or remove from initial model.\n"); endif int_mask = all (terms_cur(:, 1:end-1) == 0, 2); body = terms_cur(! int_mask, :); n_nonzero = sum (body(:, 1:end-1) != 0, 2); degree = sum (body(:, 1:end-1), 2); tier = zeros (rows (body), 1); tier(n_nonzero == 1 & degree == 1) = 1; tier(n_nonzero == 2) = 2; tier(n_nonzero == 1 & degree > 1) = 3; bitmask = zeros (rows (body), 1); for i = 1:rows (body) bitmask(i) = sum (2 .^ (find (body(i, 1:end-1)) - 1)); endfor [~, order] = sortrows ([tier, bitmask]); terms_cur = [terms_cur(int_mask, :); body(order, :)]; groups_final = sw_group_encoded_terms (terms_cur, info.pred_names, info.cat_info, info.enc_names); raws_final = sw_group_raws (groups_final); if (isempty (raws_final)) used_mask = false (1, numel (info.pred_names)); else used_mask = any (raws_final != 0, 1); endif used_pred_names = info.pred_names(used_mask); used_cols = any (terms_cur(:, 1:end-1) != 0, 1); terms_cur = terms_cur(:, [used_cols, true]); nv_list = {'Intercept', cur_hi, 'PredictorVars', used_pred_names}; if (! isempty (info.orig_opts.Weights)) nv_list = [nv_list, {'Weights', info.orig_opts.Weights}]; endif if (! isempty (info.orig_opts.Exclude)) nv_list = [nv_list, {'Exclude', info.orig_opts.Exclude}]; endif if (! isempty (info.cat_info.names)) nv_list = [nv_list, {'CategoricalVars', info.cat_info.names}]; endif mdl = fitlm (info.variables, info.response_name, terms_cur, nv_list{:}); mdl = setSteps (mdl, sw_steps_struct (hist, crit_label, penter, premove, ... T_start_enc, T_lower_enc, ... T_upper_enc, criterion, info)); endfunction ## ========================================================================== ## ## Stepwise history ## ========================================================================== ## ## One row of Steps.History. DF is the coefficient count after the step and ## TERMS the model's terms matrix over its variables, both as MATLAB reports ## them; a grouped categorical therefore counts once in TERMS and once per ## indicator in DF. function h = sw_hist_row (action, name, terms_enc, ncoef, ddf, stat, pval, info) h = struct ('Action', action, 'TermName', name, ... 'Terms', sw_history_terms (terms_enc, info), 'DF', ncoef, ... 'delDF', ddf, 'Stat', stat, 'PValue', pval); endfunction ## The statistic a step reports. Under 'sse' it is the F test that drove the ## move; under an information criterion it is the criterion's own value after ## the move, which is the model kept -- the trial model for an addition and ## the reduced model for a removal. function [stat, pval] = sw_hist_stat (criterion, mode, score) if (strcmp (criterion, 'sse')) stat = score.Fstat; pval = score.pvalue; elseif (strcmp (mode, 'add')) stat = score.abs_with; pval = NaN; else stat = score.abs_without; pval = NaN; endif endfunction ## The starting model has no test to report, but an information criterion has ## a value for it. function stat = sw_start_stat (criterion, fit, hi) if (strcmp (criterion, 'sse')) stat = NaN; return; endif c = LinearModel.lm_criteria (fit, hi); switch (criterion) case 'aic' stat = c.AIC; case 'bic' stat = c.BIC; case 'rsquared' stat = c.Rsquared; case 'adjrsquared' stat = c.AdjRsquared; endswitch endfunction ## Fold a terms matrix over the encoded columns onto one over the model's ## variables, in the order the model itself lists its terms. function T = sw_history_terms (terms_enc, info) groups = sw_group_encoded_terms (terms_enc, info.pred_names, ... info.cat_info, info.enc_names); raws = sw_group_raws (groups); T = sw_order_terms (sw_raws_to_variables (raws, info), ... sw_response_column (info)); endfunction ## Spread a matrix of per-predictor exponents over the model's variables, ## which include the response and any predictor the model never used. function T = sw_raws_to_variables (raws, info) var_names = info.variables.Properties.VariableNames; T = zeros (rows (raws), numel (var_names)); for j = 1:numel (info.pred_names) c = find (strcmp (var_names, info.pred_names{j}), 1); if (! isempty (c)) T(:, c) = raws(:, j); endif endfor endfunction function c = sw_response_column (info) c = find (strcmp (info.variables.Properties.VariableNames, ... info.response_name), 1); endfunction ## Order terms as the fitted model orders its coefficients: the intercept, ## then the linear terms, then the interactions, then the powers, each group ## by predictor index. function T = sw_order_terms (T, resp_col) cols = true (1, columns (T)); if (! isempty (resp_col)) cols(resp_col) = false; endif body_cols = T(:, cols); int_mask = all (body_cols == 0, 2); body = body_cols(! int_mask, :); n_nonzero = sum (body != 0, 2); degree = sum (body, 2); tier = zeros (rows (body), 1); tier(n_nonzero == 1 & degree == 1) = 1; tier(n_nonzero == 2) = 2; tier(n_nonzero == 1 & degree > 1) = 3; bitmask = zeros (rows (body), 1); for i = 1:rows (body) bitmask(i) = sum (2 .^ (find (body(i, :)) - 1)); endfor [~, order] = sortrows ([tier, bitmask]); rest = find (! int_mask); T = T([find(int_mask); rest(order)], :); endfunction ## Right-hand side of a model, used to name the Start row. function rhs = sw_terms_rhs (terms_enc, info) T = sw_history_terms (terms_enc, info); cols = true (1, columns (T)); rc = sw_response_column (info); if (! isempty (rc)) cols(rc) = false; endif names = info.variables.Properties.VariableNames(cols); body = T(:, cols); parts = {}; has_int = false; for i = 1:rows (body) if (all (body(i, :) == 0)) has_int = true; else parts{end+1} = sw_term_name (body(i, :), names); endif endfor if (has_int) parts = [{'1'}, parts]; elseif (isempty (parts)) parts = {'1'}; endif rhs = strjoin (parts, ' + '); endfunction ## Assemble the Steps structure attached to the returned model. function steps = sw_steps_struct (hist, crit_label, penter, premove, ... T_start, T_lower, T_upper, criterion, info) var_names = info.variables.Properties.VariableNames; resp_col = sw_response_column (info); steps = struct (); steps.Start = LinearFormula (sw_order_terms (sw_raws_to_variables ( ... sw_group_raws (sw_group_encoded_terms (T_start, ... info.pred_names, info.cat_info, info.enc_names)), info), ... resp_col), var_names, 'ResponseName', info.response_name); steps.Lower = LinearFormula (sw_order_terms (sw_raws_to_variables ( ... sw_group_raws (sw_group_encoded_terms (T_lower, ... info.pred_names, info.cat_info, info.enc_names)), info), ... resp_col), var_names, 'ResponseName', info.response_name); steps.Upper = LinearFormula (sw_order_terms (sw_raws_to_variables ( ... sw_group_raws (sw_group_encoded_terms (T_upper, ... info.pred_names, info.cat_info, info.enc_names)), info), ... resp_col), var_names, 'ResponseName', info.response_name); steps.Criterion = crit_label; steps.PEnter = penter; steps.PRemove = premove; steps.History = sw_history_table (hist, criterion); endfunction ## Build Steps.History. Which columns it carries is decided by the criterion: ## 'sse' reports the F test that drove each step, while an information ## criterion reports its own value after the step and has no test to report. function tbl = sw_history_table (hist, criterion) m = numel (hist); Action = cell (m, 1); TermName = cell (m, 1); Terms = cell (m, 1); DF = zeros (m, 1); delDF = zeros (m, 1); Stat = zeros (m, 1); pValue = zeros (m, 1); for i = 1:m Action{i} = hist(i).Action; TermName{i} = hist(i).TermName; Terms{i} = hist(i).Terms; DF(i) = hist(i).DF; delDF(i) = hist(i).delDF; Stat(i) = hist(i).Stat; pValue(i) = hist(i).PValue; endfor if (strcmp (criterion, 'sse')) tbl = table (Action, TermName, Terms, DF, delDF, Stat, pValue, ... 'VariableNames', {'Action', 'TermName', 'Terms', 'DF', ... 'delDF', 'FStat', 'pValue'}); else switch (criterion) case 'aic' cname = 'AIC'; case 'bic' cname = 'BIC'; case 'rsquared' cname = 'Rsquared'; case 'adjrsquared' cname = 'AdjRsquared'; endswitch tbl = table (Action, TermName, Terms, DF, delDF, Stat, ... 'VariableNames', {'Action', 'TermName', 'Terms', 'DF', ... 'delDF', cname}); endif endfunction function [positional, first_nv] = sw_split_positional (fit_args) nv_keys_fitlm = {'varnames','intercept','responsevar','predictorvars', ... 'categoricalvars','exclude','weights','robustopts'}; is_nv = @(s) (ischar (s) || isstring (s)) && any (strcmpi (char (s), nv_keys_fitlm)); n = numel (fit_args); first_nv = n + 1; for i = 1:n if (is_nv (fit_args{i})) first_nv = i; break; endif endfor positional = fit_args(1:first_nv-1); endfunction function fit_args = sw_ensure_modelspec (fit_args) [positional, first_nv] = sw_split_positional (fit_args); if (isempty (positional)) return; endif if (istable (positional{1})) tbl = positional{1}; col_names = tbl.Properties.VariableNames; if (numel (positional) >= 2) arg2 = positional{2}; if ((ischar (arg2) || isstring (arg2)) && ! any (char (arg2) == '~') ... && any (strcmp (char (arg2), col_names))) fit_args = [{tbl}, fit_args(3:first_nv-1), ... {'ResponseVar', char(arg2)}, fit_args(first_nv:end)]; [positional, first_nv] = sw_split_positional (fit_args); elseif (isnumeric (arg2) || islogical (arg2)) [nr2, nc2] = size (arg2); if (nc2 != width (tbl) && nc2 == 1 && nr2 == height (tbl)) ## arg2 is an external y vector, not a terms matrix n_cols = width (tbl); col_data = cell (1, n_cols); for k = 1:n_cols col_data{k} = tbl.(col_names{k}); endfor tbl_ext = table (col_data{:}, double (arg2(:)), ... 'VariableNames', [col_names, {'y'}]); fit_args = [{tbl_ext}, fit_args(3:first_nv-1), ... {'ResponseVar', 'y'}, fit_args(first_nv:end)]; [positional, first_nv] = sw_split_positional (fit_args); endif endif endif min_pos = 1; else min_pos = 2; endif if (numel (positional) <= min_pos) fit_args = [fit_args(1:first_nv-1), {'constant'}, fit_args(first_nv:end)]; endif endfunction function resp_name = sw_response_name (positional, nv_part) resp_name = ''; for i = 1:2:numel (nv_part)-1 if (strcmpi (char (nv_part{i}), 'ResponseVar')) resp_name = char (nv_part{i+1}); endif endfor if (! isempty (resp_name)) return; endif spec = positional{end}; if ((ischar (spec) || isstring (spec)) && any (char (spec) == '~')) tparts = strsplit (char (spec), '~'); resp_name = strtrim (tparts{1}); return; endif if (istable (positional{1})) resp_name = positional{1}.Properties.VariableNames{end}; endif endfunction function nv_out = sw_set_responsevar (nv_part, resp_name) nv_out = nv_part; if (isempty (resp_name)) return; endif found = false; for i = 1:2:numel (nv_out)-1 if (strcmpi (char (nv_out{i}), 'ResponseVar')) nv_out{i+1} = resp_name; found = true; endif endfor if (! found) nv_out = [nv_out, {'ResponseVar', resp_name}]; endif endfunction function T_raw = sw_reduce_to_raw_padded (mdl_bound, pred_names_full) groups_bound = sw_group_encoded_terms (mdl_bound.TermsMatrix, ... mdl_bound.PredictorNames, mdl_bound.CatLevelInfo, mdl_bound.EncPredictorNames); raws_bound = sw_group_raws (groups_bound); n_full = numel (pred_names_full); T_raw = zeros (rows (raws_bound), n_full + 1); for r = 1:rows (raws_bound) for k = 1:numel (mdl_bound.PredictorNames) e = raws_bound (r, k); if (e == 0) continue; endif j = find (strcmp (pred_names_full, mdl_bound.PredictorNames{k}), 1); T_raw (r, j) = e; endfor endfor endfunction function sw_predictorvars_matrix_conflict (fit_args) [positional, first_nv] = sw_split_positional (fit_args); n = numel (fit_args); pv_value = {}; rv_value = ''; for i = first_nv:2:n-1 if ((ischar (fit_args{i}) || isstring (fit_args{i})) ... && strcmpi (char (fit_args{i}), 'PredictorVars')) pv_value = fit_args{i+1}; endif if ((ischar (fit_args{i}) || isstring (fit_args{i})) ... && strcmpi (char (fit_args{i}), 'ResponseVar')) rv_value = char (fit_args{i+1}); endif endfor if (isempty (pv_value) || isempty (positional)) return; endif if (istable (positional{1})) min_pos = 1; else min_pos = 2; endif if (numel (positional) <= min_pos) return; endif last_extra = positional{end}; if (isnumeric (last_extra) || islogical (last_extra)) error ("stepwiselm: You may not specify PredictorVars with a terms matrix."); elseif ((ischar (last_extra) || isstring (last_extra)) ... && any (char (last_extra) == '~')) result = parseWilkinsonFormula (char (last_extra), 'expand'); conflict_msg = strcat ("stepwiselm: 'PredictorVars' or 'ResponseVar'", ... " values conflict with the formula character", ... " vector or string scalar."); formula_resp = ''; if (! isempty (result.response) && ! isempty (result.response{1})) formula_resp = result.response{1}{1}; endif if (! isempty (rv_value) && ! isempty (formula_resp) ... && ! strcmp (rv_value, formula_resp)) error (conflict_msg); endif vars_in_formula = {}; for t = 1:numel (result.model) vars_in_formula = [vars_in_formula, result.model{t}]; endfor ## A factor carries its exponent, as in 'x1^2', where 'PredictorVars' ## names variables, so compare the variable and not the power of it. ## Otherwise stepping any model holding a power term reads as a conflict. for k = 1:numel (vars_in_formula) hat = index (vars_in_formula{k}, '^'); if (hat > 0) vars_in_formula{k} = vars_in_formula{k}(1:hat-1); endif endfor vars_in_formula = unique (vars_in_formula); if (! all (ismember (vars_in_formula, pv_value))) error (conflict_msg); endif endif endfunction function T_raw = sw_resolve_bound_formula (raw_spec, info) nv_list = {}; if (! isempty (info.orig_opts.Weights)) nv_list = [nv_list, {'Weights', info.orig_opts.Weights}]; endif if (! isempty (info.orig_opts.Exclude)) nv_list = [nv_list, {'Exclude', info.orig_opts.Exclude}]; endif if (! isempty (info.cat_info.names)) nv_list = [nv_list, {'CategoricalVars', info.cat_info.names}]; endif mdl_bound = fitlm (info.variables, info.response_name, raw_spec, nv_list{:}); T_raw = sw_reduce_to_raw_padded (mdl_bound, info.pred_names); endfunction function T_raw = sw_resolve_bound (spec, pred_names, cat_mask, info) p = numel (pred_names); if (isempty (spec)) spec = 'constant'; endif if (isnumeric (spec) || islogical (spec)) T = double (spec); if (columns (T) == p) T = [T, zeros(rows (T), 1)]; elseif (columns (T) != p + 1) error ("stepwiselm: Lower/Upper terms matrix must have %d or %d columns.", p, p+1); endif T_raw = T; return; endif if (! (ischar (spec) || isstring (spec))) error ("stepwiselm: Lower/Upper must be a model name, terms matrix, or polynomial specification."); endif raw_spec = char (spec); if (any (raw_spec == '~')) T_raw = sw_resolve_bound_formula (raw_spec, info); return; endif s = lower (strtrim (raw_spec)); poly_tok = regexp (s, '^poly([0-9]+)$', 'tokens'); if (! isempty (poly_tok)) dstr = poly_tok{1}{1}; if (numel (dstr) != p) error (strcat ("stepwiselm: the number of digits in a 'polyijk'", ... " specification must match the number of predictors.")); endif digits = dstr - '0'; T_raw = sw_poly_terms (digits, p, cat_mask); return; endif switch (s) case 'constant' T_raw = zeros (1, p + 1); case 'linear' T_raw = [zeros(1,p+1); [eye(p), zeros(p,1)]]; case {'interactions', 'purequadratic', 'quadratic', 'full'} T_raw = sw_keyword_terms (s, p, cat_mask); otherwise error ("stepwiselm: '%s' is not a valid model specification.", s); endswitch endfunction function T = sw_keyword_terms (keyword, p, cat_mask) lin = [eye(p), zeros(p,1)]; T = [zeros(1,p+1); lin]; if (strcmp (keyword, 'linear')) return; endif if (any (strcmp (keyword, {'interactions','quadratic','full'}))) for i = 1:p for j = i+1:p row = zeros (1, p+1); row(i) = 1; row(j) = 1; T = [T; row]; endfor endfor endif if (any (strcmp (keyword, {'purequadratic','quadratic'}))) for j = 1:p if (! cat_mask(j)) row = zeros (1, p+1); row(j) = 2; T = [T; row]; endif endfor endif if (strcmp (keyword, 'full')) T = zeros (1, p+1); for k = 1:p idx_mat = nchoosek (1:p, k); for r = 1:rows (idx_mat) row = zeros (1, p+1); row(idx_mat(r,:)) = 1; T = [T; row]; endfor endfor endif endfunction function T = sw_poly_terms (digits, p, cat_mask) ranges = cell (1, p); for k = 1:p if (cat_mask(k)) ranges{k} = 0:min (digits(k), 1); else ranges{k} = 0:digits(k); endif endfor grids = cell (1, p); [grids{:}] = ndgrid (ranges{:}); n = numel (grids{1}); combos = zeros (n, p); for k = 1:p gk = grids{k}; combos(:,k) = gk(:); endfor keep = false (n, 1); for r = 1:n e = combos (r, :); nz = find (e != 0); if (isempty (nz)) continue; endif total_deg = sum (e(nz)); cap = max (digits(nz)); if (total_deg <= cap) keep(r) = true; endif endfor T = [zeros(1,p); combos(keep, :)]; T = [T, zeros(rows (T), 1)]; endfunction function T_enc = sw_raw_terms_to_encoded (T_raw, pred_names, cat_info, enc_names) p_enc = numel (enc_names); T_enc = zeros (0, p_enc + 1); for r = 1:rows (T_raw) G = sw_expand_conceptual_term (T_raw(r, 1:end-1), pred_names, cat_info, enc_names); G = [G, zeros(rows (G), 1)]; T_enc = [T_enc; G]; endfor endfunction function Genc = sw_expand_conceptual_term (term_row, pred_names, cat_info, enc_names) p_enc = numel (enc_names); Genc = zeros (1, p_enc); for j = find (term_row != 0) e = term_row(j); ci = []; if (! isempty (cat_info.names)) ci = find (strcmp (cat_info.names, pred_names{j})); endif if (isempty (ci)) col = find (strcmp (enc_names, pred_names{j}), 1); Genc (:, col) = e; else levels_j = cat_info.levels{ci}; cols_j = []; for L = 2:numel (levels_j) nm = sprintf ("%s_%s", pred_names{j}, char (levels_j{L})); col = find (strcmp (enc_names, nm), 1); cols_j(end+1) = col; endfor new_rows = zeros (numel (cols_j) * rows (Genc), p_enc); r_out = 0; for existing = 1:rows (Genc) for c = 1:numel (cols_j) r_out += 1; row = Genc(existing, :); row (cols_j(c)) = 1; new_rows (r_out, :) = row; endfor endfor Genc = new_rows; endif endfor endfunction function groups = sw_group_encoded_terms (terms_enc, pred_names, cat_info, enc_names) n_pred = numel (pred_names); n_rows = rows (terms_enc); raw_of = zeros (n_rows, n_pred); for r = 1:n_rows row_raw = zeros (1, n_pred); for j = 1:n_pred ci = []; if (! isempty (cat_info.names)) ci = find (strcmp (cat_info.names, pred_names{j})); endif if (isempty (ci)) col = find (strcmp (enc_names, pred_names{j}), 1); if (! isempty (col)) row_raw(j) = terms_enc (r, col); endif ## A design built from a formula names a power with its own column, ## 'x1^2' beside 'x1', rather than carrying the exponent in the 'x1' ## column. Read it off the name: otherwise the term folds onto the ## all-zero row and is silently lost as a duplicate intercept. for c = 1:numel (enc_names) nm = enc_names{c}; hat = index (nm, '^'); if (hat > 1 && strcmp (nm(1:hat-1), pred_names{j}) ... && terms_enc (r, c) != 0) e = str2double (nm(hat+1:end)); if (! isnan (e)) row_raw(j) = max (row_raw(j), terms_enc (r, c) * e); endif endif endfor else levels_j = cat_info.levels{ci}; present = false; for L = 2:numel (levels_j) nm = sprintf ("%s_%s", pred_names{j}, char (levels_j{L})); col = find (strcmp (enc_names, nm), 1); if (! isempty (col) && terms_enc (r, col) != 0) present = true; endif endfor if (present) row_raw(j) = 1; endif endif endfor raw_of (r, :) = row_raw; endfor groups = {}; used = false (n_rows, 1); for r = 1:n_rows if (used(r)) continue; endif match = false (n_rows, 1); for r2 = r:n_rows if (! used(r2) && isequal (raw_of(r2,:), raw_of(r,:))) match(r2) = true; endif endfor used = used | match; g.raw = raw_of(r, :); g.idx = find (match)'; g.term_rows = terms_enc (g.idx, :); g.name = sw_term_name (g.raw, pred_names); groups{end+1} = g; endfor endfunction function nm = sw_term_name (raw_row, pred_names) if (all (raw_row == 0)) nm = '(Intercept)'; return; endif parts = {}; for j = find (raw_row != 0) if (raw_row(j) == 1) parts{end+1} = pred_names{j}; else parts{end+1} = sprintf ("%s^%d", pred_names{j}, raw_row(j)); endif endfor nm = strjoin (parts, ':'); endfunction function raws = sw_group_raws (groups) if (isempty (groups)) raws = []; return; endif raws = zeros (numel (groups), numel (groups{1}.raw)); for k = 1:numel (groups) raws (k, :) = groups{k}.raw; endfor endfunction function tf = sw_row_present (row, mat) tf = false; for k = 1:rows (mat) if (isequal (mat(k,:), row)) tf = true; return; endif endfor endfunction function ok = sw_hierarchy_ok_add (term_raw, cur_raws) nz = find (term_raw != 0); if (numel (nz) == 0) ok = true; return; endif if (numel (nz) == 1) j = nz(1); e = term_raw(j); if (e <= 1) ok = true; return; endif pred_row = zeros (size (term_raw)); pred_row(j) = e - 1; ok = sw_row_present (pred_row, cur_raws); return; endif ok = true; for k = 1:numel (nz) j = nz(k); reduced = term_raw; reduced(j) = 0; if (! sw_row_present (reduced, cur_raws)) ok = false; return; endif endfor endfunction function ok = sw_hierarchy_ok_remove (term_raw, cur_raws) ok = true; for k = 1:rows (cur_raws) s = cur_raws(k, :); if (isequal (s, term_raw)) continue; endif if (sw_is_predecessor (term_raw, s)) ok = false; return; endif endfor endfunction function tf = sw_is_predecessor (pred_row, term_raw) nz = find (term_raw != 0); tf = false; if (numel (nz) == 0) return; endif if (numel (nz) == 1) j = nz(1); e = term_raw(j); if (e <= 1) return; endif want = zeros (size (term_raw)); want(j) = e - 1; tf = isequal (want, pred_row); return; endif for k = 1:numel (nz) j = nz(k); reduced = term_raw; reduced(j) = 0; if (isequal (reduced, pred_row)) tf = true; return; endif endfor endfunction function cand = sw_add_candidates (groups_upper, cur_raws) cand = {}; for u = 1:numel (groups_upper) ug = groups_upper{u}; if (sw_row_present (ug.raw, cur_raws)) continue; endif if (sw_hierarchy_ok_add (ug.raw, cur_raws)) cand{end+1} = ug; endif endfor endfunction function cand = sw_remove_candidates (groups_cur, lower_raws, cur_raws) cand = {}; for c = 1:numel (groups_cur) cg = groups_cur{c}; if (sw_row_present (cg.raw, lower_raws)) continue; endif if (sw_hierarchy_ok_remove (cg.raw, cur_raws)) cand{end+1} = cg; endif endfor endfunction function [Fstat, pval, df1] = sw_ftest (fit_small, fit_big) df1 = fit_big.rank_X - fit_small.rank_X; if (df1 <= 0 || fit_big.DFE <= 0) Fstat = NaN; pval = NaN; return; endif Fstat = ((fit_small.SSE - fit_big.SSE) / df1) / max (fit_big.MSE, eps); if (Fstat < 0 || isnan (Fstat)) pval = NaN; else pval = betainc (fit_big.DFE / (fit_big.DFE + df1 * Fstat), fit_big.DFE / 2, df1 / 2); endif endfunction function score = sw_criterion_score (criterion, n_obs, hi_without, hi_with, fit_without, fit_with) score = struct (); if (strcmp (criterion, 'sse')) [Fs, p, ~] = sw_ftest (fit_without, fit_with); score.pvalue = p; score.Fstat = Fs; return; endif crit_without = LinearModel.lm_criteria (fit_without, hi_without); crit_with = LinearModel.lm_criteria (fit_with, hi_with); switch (criterion) case 'aic' score.benefit = crit_with.AIC - crit_without.AIC; score.abs_with = crit_with.AIC; score.abs_without = crit_without.AIC; case 'bic' score.benefit = crit_with.BIC - crit_without.BIC; score.abs_with = crit_with.BIC; score.abs_without = crit_without.BIC; case 'rsquared' score.benefit = crit_with.Rsquared - crit_without.Rsquared; score.abs_with = crit_with.Rsquared; score.abs_without = crit_without.Rsquared; case 'adjrsquared' score.benefit = crit_with.AdjRsquared - crit_without.AdjRsquared; score.abs_with = crit_with.AdjRsquared; score.abs_without = crit_without.AdjRsquared; endswitch endfunction function tf = sw_threshold_ok (criterion, mode, score, thresh) if (strcmp (criterion, 'sse')) if (strcmp (mode, 'add')) tf = ! isnan (score.pvalue) && score.pvalue < thresh; else tf = ! isnan (score.pvalue) && score.pvalue > thresh; endif return; endif if (isnan (score.benefit)) tf = false; return; endif is_aic_bic = any (strcmp (criterion, {'aic','bic'})); if (strcmp (mode, 'add')) if (is_aic_bic) tf = score.benefit < thresh; else tf = score.benefit > thresh; endif else if (is_aic_bic) tf = score.benefit > thresh; else tf = score.benefit < thresh; endif endif endfunction function tf = sw_prefer (criterion, mode, score_a, score_b) if (strcmp (criterion, 'sse')) if (strcmp (mode, 'add')) tf = score_a.pvalue < score_b.pvalue; else tf = score_a.pvalue > score_b.pvalue; endif return; endif is_aic_bic = any (strcmp (criterion, {'aic','bic'})); if (strcmp (mode, 'add')) if (is_aic_bic) tf = score_a.benefit < score_b.benefit; else tf = score_a.benefit > score_b.benefit; endif else if (is_aic_bic) tf = score_a.benefit > score_b.benefit; else tf = score_a.benefit < score_b.benefit; endif endif endfunction function sw_print_candidate (criterion, mode, name, score) if (strcmp (mode, 'add')) verb = 'adding'; else verb = 'removing'; endif if (strcmp (criterion, 'sse')) printf (" pValue for %s %s is %.6g\n", verb, name, score.pvalue); return; endif labels = struct ('aic','AIC','bic','BIC','rsquared','Rsquared', 'adjrsquared','AdjRsquared'); lbl = labels.(criterion); if (strcmp (mode, 'add')) val = score.benefit; else val = -score.benefit; endif printf (" Change in %s for %s %s is %.6g\n", lbl, verb, name, val); endfunction function sw_print_action (criterion, step_no, verb, name, score) if (strcmp (criterion, 'sse')) printf ("%d. %s %s, FStat = %.6g, pValue = %.7g\n", step_no, verb, name, score.Fstat, score.pvalue); return; endif labels = struct ('aic','AIC','bic','BIC','rsquared','Rsquared', 'adjrsquared','AdjRsquared'); lbl = labels.(criterion); if (strcmp (verb, 'Adding')) val = score.abs_with; else val = score.abs_without; endif printf ("%d. %s %s, %s = %.6g\n", step_no, verb, name, lbl, val); endfunction %!demo %! %! ## Stepwise search from a constant model, with a custom entry threshold. %! ## Twenty apartments are described by their size, floor number, and %! ## distance from the city center, along with their monthly rent. %! ## Starting from an empty model, `stepwiselm` adds one predictor at a %! ## time as long as it improves the fit by at least `PEnter`, then checks %! ## whether anything already in the model should come back out. %! Size = [45 50 38 62 70 55 48 40 65 58 42 72 35 68 52 60 46 66 39 54]'; %! Floor = [3 5 2 8 10 4 1 6 9 7 3 12 2 11 5 6 4 10 1 5]'; %! Distance = [12 8 15 5 3 9 18 10 4 7 14 2 20 3 8 6 11 4 17 9]'; %! Rent = 200 + 12*Size - 15*Distance + 2*Floor + 5*sin ((1:20)'/2); %! X = [Size, Floor, Distance]; %! %! ## Fit with a looser entry threshold, printing every candidate examined. %! mdl = stepwiselm (X, Rent, 'PEnter', 0.06, 'Verbose', 2) %!demo %! %! ## Starting from a formula, with the response, predictors, and %! ## categorical variable all named explicitly. %! ## Eighteen coffee shops report their weekly ad spend, staff count, and %! ## sales season, along with weekly sales. The search starts already %! ## containing `AdSpend`, with `Employees` and the categorical `Season` %! ## left to consider adding. %! AdSpend = [200 350 500 220 370 520 240 390 540 260 410 560 280 430 580 300 450 600]'; %! Employees = [4 5 6 4 5 6 4 5 6 4 5 6 4 5 6 4 5 6]'; %! Season = {'Low';'Mid';'Peak';'Low';'Mid';'Peak';'Low';'Mid';'Peak'; ... %! 'Low';'Mid';'Peak';'Low';'Mid';'Peak';'Low';'Mid';'Peak'}; %! SeasonEffect = [0;150;400;0;150;400;0;150;400;0;150;400;0;150;400;0;150;400]; %! Sales = 1000 + 0.8*AdSpend + 5*Employees + SeasonEffect + 6*sin ((1:18)'); %! T = table (AdSpend, Employees, Season, Sales, ... %! 'VariableNames', {'AdSpend','Employees','Season','Sales'}); %! %! ## Fit, treating Season as categorical and naming the response explicitly. %! mdl = stepwiselm (T, 'Sales ~ 1 + AdSpend', 'ResponseVar', 'Sales', ... %! 'PredictorVars', {'AdSpend','Employees','Season'}, ... %! 'CategoricalVars', {'Season'}, 'Verbose', 1) %!demo %! %! ## Bounding the search with terms matrices instead of model-name %! ## keywords. %! ## Twelve potted plants are given varying hours of sunlight and amounts %! ## of water, and their growth is measured. `T_initial` is a constant %! ## model and `T_upper` allows the two main effects plus their %! ## interaction, using the same terms-matrix convention as `fitlm`. %! Sunlight = [2 4 6 8 3 5 7 9 2.5 4.5 6.5 8.5]'; %! Water = [100 150 200 250 120 170 220 270 110 160 210 260]'; %! Growth = 5 + 0.3*Sunlight + 0.06*Water + 0.4*sin ((1:12)'); %! X = [Sunlight, Water]; %! T_initial = [0 0 0]; %! T_upper = [0 0 0; 1 0 0; 0 1 0; 1 1 0]; %! %! ## Fit, printing the p-value considered for every candidate term. %! mdl = stepwiselm (X, Growth, T_initial, 'Upper', T_upper, 'Verbose', 2) %!demo %! %! ## A categorical predictor is added or removed as one indicator group, %! ## never one indicator column at a time. %! ## Eighteen plots receive varying fertilizer amounts across three %! ## regions, and crop yield is recorded. The upper bound `poly21` allows %! ## a constant, `Fertilizer`, `Fertilizer^2`, `Region`, and their %! ## interaction; `stepwiselm` treats the two indicator columns generated %! ## by the three-level `Region` as a single term throughout. %! Fertilizer = repmat ([10;20;30;40;50;60], 3, 1); %! Region = [repmat({'A'},6,1); repmat({'B'},6,1); repmat({'C'},6,1)]; %! RegionEffect = [zeros(6,1); 15*ones(6,1); 35*ones(6,1)]; %! Yield = 20 + 0.5*Fertilizer + RegionEffect + 1.5*sin ((1:18)'); %! T = table (Fertilizer, Region, Yield); %! %! ## Fit, printing every candidate considered at each step. %! mdl = stepwiselm (T, 'Yield ~ Fertilizer', 'Upper', 'poly21', 'Verbose', 2) %!demo %! %! ## Capping the search with `NSteps` before it converges on its own. %! ## Twenty observations depend on three predictors, all with genuine %! ## effects. Left alone, `stepwiselm` would add all three; limiting %! ## `NSteps` to 1 stops the search after only the single best addition. %! x1 = (1:20)'; %! x2 = mod ((0:19)', 5); %! x3 = mod ((0:19)', 3); %! y = 5 + 2*x1 + 1.5*x2 + 3*x3 + 2*sin ((1:20)'/1.7); %! X = [x1, x2, x3]; %! %! ## Fit, but stop after the very first step. %! mdl = stepwiselm (X, y, 'Upper', 'linear', 'NSteps', 1, 'Verbose', 1) %!demo %! %! ## Raw matrix input with default `x1, x2, ...` naming, and custom entry %! ## and removal thresholds. %! ## Fifteen observations depend mainly on `x1`. `PEnter` and `PRemove` %! ## are set explicitly rather than relying on the defaults for the SSE %! ## criterion. %! x1 = (1:15)'; %! x2 = mod ((0:14)', 4); %! y = 8 + 0.5*x1 + 0.3*x2 + 2.5*sin ((1:15)'/1.2); %! X = [x1, x2]; %! %! ## Fit with a wider entry threshold and a stricter removal threshold. %! mdl = stepwiselm (X, y, 'PEnter', 0.2, 'PRemove', 0.3, 'Verbose', 1) %!demo %! %! ## The `hald` cement data, mirroring MATLAB's own reference example for %! ## `stepwiselm`. %! ## Four chemical percentages in cement (`ingredients`) are used to %! ## predict heat given off while hardening (`heat`). Starting from a %! ## constant model, `stepwiselm` adds three terms and then removes one %! ## of them again once it becomes redundant. %! load hald %! %! ## Fit with a looser entry threshold than the default. %! mdl = stepwiselm (ingredients, heat, 'PEnter', 0.06, 'Verbose', 1) %!demo %! %! ## The `carsmall` data, mirroring MATLAB's own reference example for %! ## `stepwiselm` with a terms-matrix bound. %! ## Fuel economy (`MPG`) is predicted from acceleration and weight. %! ## `T_initial` is a constant model and `T_upper` allows both main %! ## effects plus their interaction. %! load carsmall %! X = [Acceleration, Weight]; %! T_initial = [0 0 0]; %! T_upper = [0 0 0; 1 0 0; 0 1 0; 1 1 0]; %! %! ## Fit, printing the p-value considered for every candidate term. %! mdl = stepwiselm (X, MPG, T_initial, 'Upper', T_upper, 'Verbose', 2) %!shared x1, x2, x3, y, X, tbl %! n = 24; %! x1 = (1:n)'/n; x2 = sin((1:n)'/4); x3 = cos((1:n)'/5); %! y = 4 + 2*x1 - x2 + 0.5*x3 + 0.15*sin((1:n)'*0.8); %! X = [x1, x2, x3]; %! tbl = table (x1, x2, x3, y, 'VariableNames', {'x1','x2','x3','y'}); %!test %! mdl = stepwiselm (tbl, 'Verbose', 0); %! assert_equal (mdl.NumObservations, 24); %! assert_equal (mdl.NumCoefficients, 4); %! assert_equal (mdl.NumVariables, 4); %! assert_equal (mdl.NumPredictors, 3); %! assert_equal (mdl.DFE, 20); %! assert_equal (mdl.SSE, 0.237882441508, 1e-9); %! assert_equal (mdl.SSR, 25.3955253054, 1e-7); %! assert_equal (mdl.SST, 25.6334077469, 1e-7); %! assert_equal (mdl.MSE, 0.0118941220754, 1e-10); %! assert_equal (mdl.RMSE, 0.109060176395, 1e-9); %! assert_equal (mdl.Rsquared.Ordinary, 0.990719827662, 1e-9); %! assert_equal (mdl.Rsquared.Adjusted, 0.989327801811, 1e-9); %! assert_equal (mdl.LogLikelihood, 21.3138652143, 1e-6); %! assert_equal (mdl.ModelCriterion.AIC, -34.6277304285, 1e-6); %! assert_equal (mdl.ModelCriterion.AICc, -32.5224672707, 1e-6); %! assert_equal (mdl.ModelCriterion.BIC, -29.9155151072, 1e-6); %! assert_equal (mdl.ModelCriterion.CAIC, -25.9155151072, 1e-6); %! assert_equal (mdl.ModelFitVsNullModel.Fstat, 711.710796991, 1e-5); %! assert_equal (mdl.ModelFitVsNullModel.NullModel, 'constant'); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [4.11199897343; 1.79245385284; -1.06961941012; 0.51029628797], 1e-9); %! assert_equal (mdl.Coefficients.SE, ... %! [0.0761375845531; 0.144191944451; 0.0575954799856; 0.0489829266327], 1e-9); %! assert_equal (mdl.Coefficients.tStat, ... %! [54.007478666; 12.4310262939; -18.5712387566; 10.4178399097], 1e-6); %! assert_equal (mdl.Coefficients.pValue, ... %! [3.79220676881e-23; 7.26932473407e-11; 4.42354835071e-14; 1.58256682968e-09], 1e-9); %! assert_equal (mdl.ResponseName, 'y'); %! assert_equal (mdl.PredictorNames, {'x1';'x2';'x3'}); %! assert_equal (mdl.VariableNames, {'x1';'x2';'x3';'y'}); %! assert_equal (mdl.Formula.HasIntercept, true); %! assert_equal (mdl.Formula.LinearPredictor, '1 + x1 + x2 + x3'); %! assert_equal (mdl.Formula.NTerms, 4); %!test %! mdl = stepwiselm (tbl, 'y', 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.SSE, 0.237882441508, 1e-9); %! assert_equal (mdl.ResponseName, 'y'); %! assert_equal (mdl.PredictorNames, {'x1';'x2';'x3'}); %!test %! tt = table (x1, x2, x3, 'VariableNames', {'x1','x2','x3'}); %! mdl = stepwiselm (tt, y, 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.SSE, 0.237882441508, 1e-9); %! assert_equal (mdl.ResponseName, 'y'); %! assert_equal (mdl.VariableNames, {'x1';'x2';'x3';'y'}); %!test %! mdl = stepwiselm (X, y, 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [4.11199897343; 1.79245385284; -1.06961941012; 0.51029628797], 1e-9); %! assert_equal (mdl.ResponseName, 'y'); %! assert_equal (mdl.VariableNames, {'x1';'x2';'x3';'y'}); %!test %! mdl = stepwiselm (tbl, 'y ~ x1', 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.SSE, 0.237882441508, 1e-9); %! assert_equal (mdl.Formula.LinearPredictor, '1 + x1 + x2 + x3'); %!test %! mdl = stepwiselm (X, y, 'constant', 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.SSE, 0.237882441508, 1e-9); %!test %! mdl = stepwiselm (X, y, 'linear', 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.SSE, 0.237882441508, 1e-9); %!test %! mdl = stepwiselm (X, y, 'constant', 'Upper', 'purequadratic', 'Verbose', 0); %! assert_equal (mdl.NumObservations, 24); %! assert_equal (mdl.NumCoefficients, 4); %! assert_equal (mdl.NumVariables, 4); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.DFE, 20); %! assert_equal (mdl.SSE, 0.22345826866, 1e-9); %! assert_equal (mdl.SSR, 25.4099494782, 1e-7); %! assert_equal (mdl.SST, 25.6334077469, 1e-7); %! assert_equal (mdl.MSE, 0.011172913433, 1e-10); %! assert_equal (mdl.RMSE, 0.105702002975, 1e-9); %! assert_equal (mdl.Rsquared.Ordinary, 0.991282537583, 1e-9); %! assert_equal (mdl.Rsquared.Adjusted, 0.98997491822, 1e-9); %! assert_equal (mdl.LogLikelihood, 22.0644883645, 1e-6); %! assert_equal (mdl.ModelCriterion.AIC, -36.1289767289, 1e-6); %! assert_equal (mdl.ModelCriterion.AICc, -34.023713571, 1e-6); %! assert_equal (mdl.ModelCriterion.BIC, -31.4167614075, 1e-6); %! assert_equal (mdl.ModelCriterion.CAIC, -27.4167614075, 1e-6); %! assert_equal (mdl.ModelFitVsNullModel.Fstat, 758.081874544, 1e-5); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x1^2'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [4.79002491626; -1.87038904608; -0.881529179216; 3.14370698956], 1e-9); %! assert_equal (mdl.Coefficients.SE, ... %! [0.0903999262734; 0.328540613357; 0.0534940080998; 0.290849609965], 1e-9); %! assert_equal (mdl.Coefficients.tStat, ... %! [52.9870445002; -5.6930223237; -16.4790265402; 10.8087027861], 1e-6); %! assert_equal (mdl.Coefficients.pValue, ... %! [5.53960001242e-23; 1.4288675776e-05; 4.19790256549e-13; 8.42439951369e-10], 1e-9); %! assert_equal (mdl.PredictorNames, {'x1';'x2'}); %! assert_equal (mdl.Formula.LinearPredictor, '1 + x1 + x2 + x1^2'); %!test %! mdl = stepwiselm (X, y, 'constant', 'Upper', 'quadratic', 'Verbose', 0); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x1^2'}); %! assert_equal (mdl.SSE, 0.22345826866, 1e-9); %! assert_equal (mdl.PredictorNames, {'x1';'x2'}); %!test %! T0 = [0 0 0]; %! T1 = [0 0 0; 1 0 0; 0 1 0; 1 1 0]; %! mdl = stepwiselm (X(:,1:2), y, T0, 'Upper', T1, 'Verbose', 0); %! assert_equal (mdl.NumObservations, 24); %! assert_equal (mdl.NumCoefficients, 4); %! assert_equal (mdl.NumVariables, 3); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.DFE, 20); %! assert_equal (mdl.SSE, 1.11546087527, 1e-8); %! assert_equal (mdl.SSR, 24.5179468716, 1e-7); %! assert_equal (mdl.SST, 25.6334077469, 1e-7); %! assert_equal (mdl.MSE, 0.0557730437634, 1e-10); %! assert_equal (mdl.RMSE, 0.236163171903, 1e-9); %! assert_equal (mdl.Rsquared.Ordinary, 0.956484097383, 1e-9); %! assert_equal (mdl.Rsquared.Adjusted, 0.94995671199, 1e-9); %! assert_equal (mdl.LogLikelihood, 2.77090924057, 1e-6); %! assert_equal (mdl.ModelCriterion.AIC, 2.45818151886, 1e-6); %! assert_equal (mdl.ModelCriterion.AICc, 4.56344467675, 1e-6); %! assert_equal (mdl.ModelCriterion.BIC, 7.17039684025, 1e-6); %! assert_equal (mdl.ModelCriterion.CAIC, 11.1703968402, 1e-6); %! assert_equal (mdl.ModelFitVsNullModel.Fstat, 146.534031599, 1e-5); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x1:x2'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [4.00100246651; 1.35895695997; -0.23206474908; -1.2780495686], 1e-9); %! assert_equal (mdl.Coefficients.SE, ... %! [0.181530230599; 0.299744817513; 0.272053305331; 0.469486828642], 1e-9); %! assert_equal (mdl.Coefficients.tStat, ... %! [22.0404196772; 4.53371294703; -0.853012055108; -2.72222667524], 1e-6); %! assert_equal (mdl.Coefficients.pValue, ... %! [1.67738549624e-15; 0.000202251230338; 0.403753045528; 0.0131231039551], 1e-9); %! assert_equal (mdl.PredictorNames, {'x1';'x2'}); %! assert_equal (mdl.VariableNames, {'x1';'x2';'y'}); %!test %! mdl = stepwiselm (X, y, 'Criterion', 'aic', 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.ModelCriterion.AIC, -34.6277304285, 1e-6); %!test %! mdl = stepwiselm (X, y, 'Criterion', 'bic', 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.ModelCriterion.BIC, -29.9155151072, 1e-6); %!test %! mdl = stepwiselm (X, y, 'Criterion', 'rsquared', 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.NumCoefficients, 2); %! assert_equal (mdl.NumPredictors, 1); %! assert_equal (mdl.DFE, 22); %! assert_equal (mdl.SSE, 2.69619594281, 1e-8); %! assert_equal (mdl.Rsquared.Ordinary, 0.894817108617, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x2'}); %! assert_equal (mdl.Coefficients.Estimate, [4.93053729606; -1.35113880263], 1e-9); %! assert_equal (mdl.Coefficients.SE, [0.0714593428648; 0.0987629466731], 1e-9); %! assert_equal (mdl.Coefficients.tStat, [68.9977978861; -13.6806246486], 1e-6); %! assert_equal (mdl.Coefficients.pValue, [3.2882259984e-27; 3.08475274476e-12], 1e-9); %! assert_equal (mdl.PredictorNames, {'x2'}); %!test %! mdl = stepwiselm (X, y, 'Criterion', 'adjrsquared', 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.NumCoefficients, 5); %! assert_equal (mdl.DFE, 19); %! assert_equal (mdl.SSE, 0.224234467489, 1e-9); %! assert_equal (mdl.Rsquared.Adjusted, 0.989410626691, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3','x1:x3'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [4.58266504435; 1.22125846816; -1.38808827902; 0.143991325796; 1.04450806346], 1e-8); %! assert_equal (mdl.Coefficients.SE, ... %! [0.444198853188; 0.55023668447; 0.301652972275; 0.344106973415; 0.971297090441], 1e-8); %! assert_equal (mdl.Coefficients.tStat, ... %! [10.3166971537; 2.21951480632; -4.6016065035; 0.418449310593; 1.07537443872], 1e-6); %! assert_equal (mdl.Coefficients.pValue, ... %! [3.1776147586e-09; 0.0388206335538; 0.000194754077847; 0.680310511095; 0.295673895373], 1e-8); %! assert_equal (mdl.PredictorNames, {'x1';'x2';'x3'}); %!test %! p1 = (1:24)'/24; p2 = sin ((1:24)'/3.5); %! pr = 3 + 0.9*p1 - 0.2*p2 + 0.1*sin ((1:24)'*1.1); %! mdl = stepwiselm (p1, pr, 'Verbose', 0); %! assert_equal (mdl.NumObservations, 24); %! assert_equal (mdl.NumCoefficients, 2); %! assert_equal (mdl.DFE, 22); %! assert_equal (mdl.SSE, 0.422994127869, 1e-9); %! assert_equal (mdl.SSR, 2.71741844039, 1e-8); %! assert_equal (mdl.SST, 3.14041256826, 1e-8); %! assert_equal (mdl.Rsquared.Ordinary, 0.865306191886, 1e-9); %! assert_equal (mdl.Rsquared.Adjusted, 0.859183746062, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1'}); %! assert_equal (mdl.Coefficients.Estimate, [2.85858604186; 1.16664998725], 1e-9); %! assert_equal (mdl.Coefficients.SE, [0.0584250816201; 0.0981336947312], 1e-9); %! assert_equal (mdl.Coefficients.tStat, [48.9273778074; 11.8883732081], 1e-6); %! assert_equal (mdl.Coefficients.pValue, [6.03142199019e-24; 4.75617587395e-11], 1e-9); %!test %! p1 = (1:24)'/24; p2 = sin ((1:24)'/3.5); %! pr = 3 + 0.9*p1 - 0.2*p2 + 0.1*sin ((1:24)'*1.1); %! mdl = stepwiselm (p1, pr, 'PEnter', 0.5, 'PRemove', 0.6, 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1'}); %! assert_equal (mdl.SSE, 0.422994127869, 1e-9); %!test %! mdl = stepwiselm (X, y, 'Upper', 'interactions', 'NSteps', 1, 'Verbose', 0); %! assert_equal (mdl.NumCoefficients, 2); %! assert_equal (mdl.DFE, 22); %! assert_equal (mdl.SSE, 2.69619594281, 1e-8); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x2'}); %! assert_equal (mdl.Coefficients.Estimate, [4.93053729606; -1.35113880263], 1e-9); %! assert_equal (mdl.PredictorNames, {'x2'}); %!test %! mdl = stepwiselm (X, y, 'NSteps', 0, 'Verbose', 0); %! assert_equal (mdl.NumCoefficients, 1); %! assert_equal (mdl.NumPredictors, 0); %! assert_equal (mdl.DFE, 23); %! assert_equal (mdl.SSE, 25.6334077469, 1e-7); %! assert_equal (mdl.SSR, 0, 1e-12); %! assert_equal (mdl.Rsquared.Ordinary, 0, 1e-12); %! assert_equal (isnan (mdl.ModelFitVsNullModel.Fstat), true); %! assert_equal (isnan (mdl.ModelFitVsNullModel.NullModel), true); %! assert_equal (mdl.CoefficientNames, {'(Intercept)'}); %! assert_equal (mdl.Coefficients.Estimate, 4.92948000444, 1e-9); %! assert_equal (mdl.Coefficients.SE, 0.215493231622, 1e-9); %! assert_equal (mdl.Coefficients.tStat, 22.8753356537, 1e-6); %! assert_equal (mdl.Coefficients.pValue, 2.53817914154e-17, 1e-9); %! assert_equal (isempty (mdl.PredictorNames), true); %! assert_equal (mdl.Formula.LinearPredictor, '1'); %!test %! w = 1400 + (1:45)'*9; %! g = {'A','B','C'}(mod ((0:44), 3) + 1)'; %! m = 55 - 0.005*w + [0 5 -3](mod ((0:44), 3) + 1)' + 0.3*sin ((1:45)'/6); %! t = table (w, g, m, 'VariableNames', {'Weight','Group','MPG'}); %! mdl = stepwiselm (t, 'MPG ~ Weight', 'CategoricalVars', {'Group'}, 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.NumObservations, 45); %! assert_equal (mdl.NumCoefficients, 4); %! assert_equal (mdl.NumVariables, 3); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.DFE, 41); %! assert_equal (mdl.SSE, 1.7056787721, 1e-8); %! assert_equal (mdl.SSR, 512.932602391, 1e-6); %! assert_equal (mdl.SST, 514.638281163, 1e-6); %! assert_equal (mdl.MSE, 0.0416019212713, 1e-10); %! assert_equal (mdl.RMSE, 0.203965490393, 1e-9); %! assert_equal (mdl.Rsquared.Ordinary, 0.996685674513, 1e-9); %! assert_equal (mdl.Rsquared.Adjusted, 0.996443162891, 1e-9); %! assert_equal (mdl.LogLikelihood, 9.78350141322, 1e-6); %! assert_equal (mdl.ModelCriterion.AIC, -11.5670028264, 1e-6); %! assert_equal (mdl.ModelCriterion.AICc, -10.5670028264, 1e-6); %! assert_equal (mdl.ModelCriterion.BIC, -4.34035286736, 1e-6); %! assert_equal (mdl.ModelCriterion.CAIC, -0.340352867356, 1e-6); %! assert_equal (mdl.ModelFitVsNullModel.Fstat, 4109.84706734, 1e-4); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','Weight','Group_B','Group_C'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [56.0078644136; -0.00561620967245; 5.01185752618; -2.97710174861], 1e-8); %! assert_equal (mdl.Coefficients.SE, ... %! [0.41983060818; 0.000260647333731; 0.074514600823; 0.0746252935319], 1e-8); %! assert_equal (mdl.Coefficients.tStat, ... %! [133.40586256; -21.5471594973; 67.2600734732; -39.8940038652], 1e-5); %! assert_equal (mdl.Coefficients.pValue, ... %! [1.00643907454e-55; 5.62403610622e-24; 1.37550401987e-43; 1.97956717929e-34], 1e-8); %! assert_equal (mdl.ResponseName, 'MPG'); %! assert_equal (mdl.PredictorNames, {'Weight';'Group'}); %! assert_equal (mdl.Formula.LinearPredictor, '1 + Weight + Group'); %!test %! w = 1400 + (1:45)'*9; %! code = mod ((0:44), 3)' + 1; %! m = 55 - 0.005*w + [0 5 -3](mod ((0:44), 3) + 1)' + 0.3*sin ((1:45)'/6); %! mdl = stepwiselm ([w, code], m, 'CategoricalVars', 2, 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.DFE, 41); %! assert_equal (mdl.SSE, 1.7056787721, 1e-8); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2_2','x2_3'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [56.007864413564; -0.00561620967245463; 5.01185752617851; -2.97710174860745], 1e-8); %! assert_equal (mdl.PredictorNames, {'x1';'x2'}); %!test %! ex = false (24, 1); %! ex([2 9 15]) = true; %! mdl = stepwiselm (X, y, 'Upper', 'interactions', 'Exclude', ex, 'Verbose', 0); %! assert_equal (mdl.NumObservations, 21); %! assert_equal (mdl.DFE, 17); %! assert_equal (mdl.SSE, 0.198175777, 1e-9); %! assert_equal (mdl.SSR, 23.9528827198, 1e-7); %! assert_equal (mdl.SST, 24.1510584968, 1e-7); %! assert_equal (mdl.Rsquared.Ordinary, 0.991794323341, 1e-9); %! assert_equal (mdl.Rsquared.Adjusted, 0.990346262754, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [4.10558649487; 1.78751811424; -1.08307909721; 0.499781080674], 1e-9); %! assert_equal (mdl.Coefficients.SE, ... %! [0.0832423204478; 0.15196995208; 0.0630609308312; 0.0532045604032], 1e-9); %! assert_equal (mdl.Coefficients.tStat, ... %! [49.320903992; 11.762312811; -17.1751206798; 9.39357598083], 1e-6); %! assert_equal (mdl.Coefficients.pValue, ... %! [8.57561115348e-20; 1.36568780583e-09; 3.54709582656e-12; 3.84225548219e-08], 1e-9); %! assert_equal (mdl.PredictorNames, {'x1';'x2';'x3'}); %!test %! mdl = stepwiselm (X, y, 'Upper', 'interactions', 'Exclude', [2 9 15], 'Verbose', 0); %! assert_equal (mdl.NumObservations, 21); %! assert_equal (mdl.SSE, 0.198175777, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %!test %! wt = 0.6 + mod ((1:24)', 4)*0.25; %! mdl = stepwiselm (X, y, 'Upper', 'interactions', 'Weights', wt, 'Verbose', 0); %! assert_equal (mdl.NumObservations, 24); %! assert_equal (mdl.DFE, 20); %! assert_equal (mdl.SSE, 0.238797389485, 1e-9); %! assert_equal (mdl.SSR, 24.6325434536, 1e-7); %! assert_equal (mdl.SST, 24.8713408431, 1e-7); %! assert_equal (mdl.Rsquared.Ordinary, 0.990398692576, 1e-9); %! assert_equal (mdl.Rsquared.Adjusted, 0.988958496462, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [4.13717568894; 1.74127146188; -1.08433885985; 0.50630139226], 1e-9); %! assert_equal (mdl.Coefficients.SE, ... %! [0.0789083883313; 0.150888905488; 0.0591696809619; 0.0500332545132], 1e-9); %! assert_equal (mdl.Coefficients.tStat, ... %! [52.4301126462; 11.5400894204; -18.3259203401; 10.1192975989], 1e-6); %! assert_equal (mdl.Coefficients.pValue, ... %! [6.83329714397e-23; 2.70346109195e-10; 5.69051367149e-14; 2.59082042824e-09], 1e-9); %!test %! mdl = stepwiselm (X, y, 'constant', 'Intercept', false, 'Upper', 'linear', 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.SSE, 0.237882441508, 1e-9); %! assert_equal (mdl.Formula.HasIntercept, true); %!test %! mdl = stepwiselm (tbl, 'ResponseVar', 'y', 'PredictorVars', {'x1','x2'}, 'Verbose', 0); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.SSE, 1.11546087527, 1e-8); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x1:x2'}); %! assert_equal (mdl.PredictorNames, {'x1';'x2'}); %!test %! mdl = stepwiselm (tbl, 'ResponseVar', 'y', 'PredictorVars', [1 2], 'Verbose', 0); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.SSE, 1.11546087527, 1e-8); %! assert_equal (mdl.PredictorNames, {'x1';'x2'}); %!test %! mdl = stepwiselm (tbl, 'ResponseVar', 'y', 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.NumPredictors, 3); %! assert_equal (mdl.SSE, 0.237882441508, 1e-9); %! assert_equal (mdl.ResponseName, 'y'); %!test %! mdl = stepwiselm (X, y, 'VarNames', {'Alpha','Beta','Gamma','Score'}, 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','Alpha','Beta','Gamma'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [4.11199897343; 1.79245385284; -1.06961941012; 0.51029628797], 1e-9); %! assert_equal (mdl.ResponseName, 'Score'); %! assert_equal (mdl.PredictorNames, {'Alpha';'Beta';'Gamma'}); %! assert_equal (mdl.VariableNames, {'Alpha';'Beta';'Gamma';'Score'}); %! assert_equal (mdl.Formula.LinearPredictor, '1 + Alpha + Beta + Gamma'); %!test %! mdl = stepwiselm (tbl, 'y ~ x1 + x2', 'Lower', 'y ~ x2', 'Upper', 'y ~ x1+x2+x3', 'PRemove', 0.99, 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [4.11199897343; 1.79245385284; -1.06961941012; 0.51029628797], 1e-9); %!test %! xu = (1:50)'/50; %! idx = mod ((1:50), 2); %! gu = {'Hi','Lo'}(idx + 1)'; %! sl = [7 1](idx + 1)'; %! yu = 2 + sl.*xu + 0.15*sin ((1:50)'/3); %! t = table (xu, gu, yu, 'VariableNames', {'X','G','Y'}); %! mdl = stepwiselm (t, 'Y ~ X + G', 'Upper', 'quadratic', 'Verbose', 0); %! assert_equal (mdl.NumObservations, 50); %! assert_equal (mdl.NumCoefficients, 4); %! assert_equal (mdl.NumVariables, 3); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.DFE, 46); %! assert_equal (mdl.SSE, 0.540477312442, 1e-9); %! assert_equal (mdl.SSR, 225.563187153, 1e-6); %! assert_equal (mdl.SST, 226.103664466, 1e-6); %! assert_equal (mdl.Rsquared.Ordinary, 0.997609603923, 1e-9); %! assert_equal (mdl.Rsquared.Adjusted, 0.997453708527, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','X','G_Hi','X:G_Hi'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [2.02876253697; 0.971349141035; 0.0058812055616; 5.98354193726], 1e-8); %! assert_equal (mdl.Coefficients.SE, ... %! [0.0433841054684; 0.0751585081172; 0.0622864091272; 0.106290181507], 1e-8); %! assert_equal (mdl.Coefficients.tStat, ... %! [46.7628066792; 12.9240077453; 0.0944219717272; 56.2943994677], 1e-5); %! assert_equal (mdl.Coefficients.pValue, ... %! [1.96152617542e-40; 6.42479379426e-17; 0.92518406634; 4.45945899483e-44], 1e-8); %! assert_equal (mdl.PredictorNames, {'X';'G'}); %!test %! idx3 = mod ((1:48), 3); idx2 = mod ((1:48), 2); %! g1 = {'A','B','C'}(idx3 + 1)'; %! g2 = {'P','Q'}(idx2 + 1)'; %! base = [0 4 -3](idx3 + 1)'; qeff = [5 0](idx2 + 1)'; %! cross = 6*((idx3 == 1) & (idx2 == 0))'; %! y2 = 10 + base + qeff + cross + 0.2*sin ((1:48)'/4); %! t = table (g1, g2, y2, 'VariableNames', {'G1','G2','Y'}); %! mdl = stepwiselm (t, 'Y ~ G1 + G2', 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.NumObservations, 48); %! assert_equal (mdl.NumCoefficients, 6); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.DFE, 42); %! assert_equal (mdl.SSE, 1.00031269326, 1e-8); %! assert_equal (mdl.SSR, 1526.57402706, 1e-6); %! assert_equal (mdl.SST, 1527.57433975, 1e-6); %! assert_equal (mdl.Rsquared.Ordinary, 0.999345162676, 1e-9); %! assert_equal (mdl.Rsquared.Adjusted, 0.999267205851, 1e-9); %! assert_equal (mdl.CoefficientNames, ... %! {'(Intercept)','G1_C','G1_A','G2_P','G1_C:G2_P','G1_A:G2_P'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [14.0072303291; -7.00943486401; -4.00436689813; 10.9931096893; -5.98569634506; -6.00058514115], 1e-8); %! assert_equal (mdl.Coefficients.SE, ... %! [0.0545630013001; 0.0771637364424; 0.0771637364424; 0.0771637364424; 0.1091260026; 0.1091260026], 1e-8); %! assert_equal (mdl.Coefficients.tStat, ... %! [256.716639395; -90.8384584155; -51.8944141736; 142.464714594; -54.8512380408; -54.9876747811], 1e-5); %! assert_equal (mdl.Coefficients.pValue, ... %! [9.40196273084e-69; 7.64513283056e-50; 1.00685653504e-39; 5.03307771166e-58; 1.01525097736e-40; 9.15939827666e-41], 1e-8); %! assert_equal (mdl.PredictorNames, {'G1';'G2'}); %!test %! idxb = mod ((1:48), 2); %! ga = {'M','F'}(idxb + 1)'; %! gb = ga; %! y3 = 50 - 10*strcmp (ga, 'F') + 0.3*sin ((1:48)'/5); %! t = table (ga, gb, y3, 'VariableNames', {'GA','GB','Y'}); %! mdl = stepwiselm (t, 'Y ~ 1 + GB', 'ResponseVar', 'Y', ... %! 'PredictorVars', {'GA','GB'}, 'CategoricalVars', {'GA','GB'}, 'Verbose', 0); %! assert_equal (mdl.NumObservations, 48); %! assert_equal (mdl.NumCoefficients, 2); %! assert_equal (mdl.NumPredictors, 1); %! assert_equal (mdl.DFE, 46); %! assert_equal (mdl.SSE, 1.94300386071, 1e-8); %! assert_equal (mdl.SSR, 1199.4398757, 1e-6); %! assert_equal (mdl.SST, 1201.38287956, 1e-6); %! assert_equal (mdl.Rsquared.Ordinary, 0.998382693899, 1e-9); %! assert_equal (mdl.Rsquared.Adjusted, 0.998347535071, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','GB_M'}); %! assert_equal (mdl.Coefficients.Estimate, [40.0624369099; 9.99766587633], 1e-8); %! assert_equal (mdl.Coefficients.SE, [0.041951963781; 0.0593290361473], 1e-8); %! assert_equal (mdl.Coefficients.tStat, [954.959751562; 168.512191088], 1e-5); %! assert_equal (mdl.Coefficients.pValue, [1.70539389023e-100; 7.42606982902e-66], 1e-8); %! assert_equal (mdl.PredictorNames, {'GB'}); %! assert_equal (mdl.Formula.LinearPredictor, '1 + GB'); %!test %! e1 = (1:30)'/30; e2 = sin ((1:30)'/5); e3 = e1 + e2; %! ye = 3 + 1.5*e1 - e2 + 0.3*sin ((1:30)'/4); %! mdl = stepwiselm ([e1,e2,e3], ye, 'linear', 'Upper', 'linear', 'Verbose', 0); %! assert_equal (mdl.NumObservations, 30); %! assert_equal (mdl.NumCoefficients, 3); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.DFE, 27); %! assert_equal (mdl.SSE, 0.736469147619, 1e-9); %! assert_equal (mdl.SSR, 30.2842015322, 1e-7); %! assert_equal (mdl.SST, 31.0206706798, 1e-7); %! assert_equal (mdl.Rsquared.Ordinary, 0.976258761288, 1e-9); %! assert_equal (mdl.Rsquared.Adjusted, 0.974500151013, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x2','x3'}); %! assert_equal (mdl.Coefficients.Estimate, [2.83853512391; -2.5770987124; 1.87079571697], 1e-8); %! assert_equal (mdl.Coefficients.SE, [0.101176877319; 0.131763778382; 0.186693438852], 1e-8); %! assert_equal (mdl.Coefficients.tStat, [28.0551762332; -19.5584761158; 10.0206827218], 1e-6); %! assert_equal (mdl.Coefficients.pValue, [1.65333811724e-21; 1.79264031168e-17; 1.35825364979e-10], 1e-9); %! assert_equal (mdl.PredictorNames, {'x2';'x3'}); %!test %! h1 = [7;1;11;11;7;11;3;1;2;21;1;11;10]; %! h2 = [26;29;56;31;52;55;71;31;54;47;40;66;68]; %! h3 = [6;15;8;8;6;9;17;22;18;4;23;9;8]; %! h4 = [60;52;20;47;33;22;6;44;22;26;34;12;12]; %! yh = [78.5;74.3;104.3;87.6;95.9;109.2;102.7;72.5;93.1;115.9;83.8;113.3;109.4]; %! mdl = stepwiselm ([h1,h2,h3,h4], yh, 'PEnter', 0.06, 'Verbose', 0); %! assert_equal (mdl.NumObservations, 13); %! assert_equal (mdl.NumCoefficients, 3); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.DFE, 10); %! assert_equal (mdl.SSE, 57.9044831761, 1e-8); %! assert_equal (mdl.SSR, 2657.85859375, 1e-5); %! assert_equal (mdl.SST, 2715.76307692, 1e-5); %! assert_equal (mdl.Rsquared.Ordinary, 0.978678374536, 1e-9); %! assert_equal (mdl.Rsquared.Adjusted, 0.974414049443, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2'}); %! assert_equal (mdl.Coefficients.Estimate, [52.5773488821; 1.46830574222; 0.662250491275], 1e-8); %! assert_equal (mdl.Coefficients.SE, [2.2861743345; 0.121300923606; 0.0458547214685], 1e-8); %! assert_equal (mdl.Coefficients.tStat, [22.9979613053; 12.1046542645; 14.4423620963], 1e-6); %! assert_equal (mdl.Coefficients.pValue, [5.45657090149e-10; 2.69221217969e-07; 5.02896031564e-08], 1e-9); %! assert_equal (mdl.PredictorNames, {'x1';'x2'}); %! assert_equal (mdl.VariableNames, {'x1';'x2';'x3';'x4';'y'}); %! assert_equal (mdl.Formula.LinearPredictor, '1 + x1 + x2'); %!test %! w = 1400 + (1:45)'*9; %! g = {'A','B','C'}(mod ((0:44), 3) + 1)'; %! m = 55 - 0.005*w + [0 5 -3](mod ((0:44), 3) + 1)' + 0.3*sin ((1:45)'/6); %! t = table (w, g, m, 'VariableNames', {'Weight','Group','MPG'}); %! mdl0 = fitlm (t, 'MPG ~ Weight'); %! mdl = step (mdl0, 'Verbose', 0); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','Weight','Group_B','Group_C'}); %! assert_equal (mdl.SSE, 1.7056787721, 1e-8); %! assert_equal (mdl.Coefficients.Estimate, ... %! [56.0078644136; -0.00561620967245; 5.01185752618; -2.97710174861], 1e-8); %!test %! w = 1400 + (1:45)'*9; %! g = {'A','B','C'}(mod ((0:44), 3) + 1)'; %! m = 55 - 0.005*w + [0 5 -3](mod ((0:44), 3) + 1)' + 0.3*sin ((1:45)'/6); %! t = table (w, g, m, 'VariableNames', {'Weight','Group','MPG'}); %! mdl0 = fitlm (t, 'MPG ~ Weight'); %! mdl = step (mdl0, 'Upper', 'quadratic', 'NSteps', 5, 'Verbose', 0); %! assert_equal (mdl.NumCoefficients, 5); %! assert_equal (mdl.DFE, 40); %! assert_equal (mdl.SSE, 0.987020309951, 1e-8); %! assert_equal (mdl.Rsquared.Ordinary, 0.998082108646, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','Weight','Group_B','Group_C','Weight^2'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [82.5932822956; -0.0388795480431; 5.01269583682; -2.97710174861; 1.03495141166e-05], 1e-6); %!test %! w = 1400 + (1:45)'*9; %! code = mod ((0:44), 3)' + 1; %! m = 55 - 0.005*w + [0 5 -3](mod ((0:44), 3) + 1)' + 0.3*sin ((1:45)'/6); %! mdl0 = fitlm ([w, code], m); %! mdl = step (mdl0, 'Criterion', 'bic', 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.NumCoefficients, 2); %! assert_equal (mdl.DFE, 43); %! assert_equal (mdl.SSE, 443.573715939, 1e-6); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x2'}); %! assert_equal (mdl.Coefficients.Estimate, [50.739060918498; -1.53909676135581], 1e-6); %!test %! tbl_noy = table (x1, x2, x3, 'VariableNames', {'x1','x2','x3'}); %! mdl = stepwiselm (tbl_noy, y, 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.NumObservations, 24); %! assert_equal (mdl.NumCoefficients, 4); %! assert_equal (mdl.NumPredictors, 3); %! assert_equal (mdl.DFE, 20); %! assert_equal (mdl.SSE, 0.237882441508, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [4.1119989734334; 1.79245385284216; -1.06961941011697; 0.510296287970133], 1e-9); %! assert_equal (mdl.ResponseName, 'y'); %! assert_equal (mdl.PredictorNames, {'x1';'x2';'x3'}); %!test %! T_initial = [0 0 0 0]; %! T_upper = [0 0 0 0; 1 0 0 0; 0 1 0 0; 1 1 0 0]; %! mdl = stepwiselm (X, y, T_initial, 'Upper', T_upper, 'Verbose', 0); %! assert_equal (mdl.NumCoefficients, 4); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.DFE, 20); %! assert_equal (mdl.SSE, 1.11546087527, 1e-8); %! assert_equal (mdl.SSR, 24.5179468716, 1e-6); %! assert_equal (mdl.Rsquared.Ordinary, 0.956484097383, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x1:x2'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [4.00100246650923; 1.35895695996517; -0.232064749079705; -1.27804956860349], 1e-8); %! assert_equal (mdl.Coefficients.SE, ... %! [0.181530230599361; 0.299744817513483; 0.272053305331473; 0.469486828642072], 1e-8); %! assert_equal (mdl.Coefficients.tStat, ... %! [22.0404196772024; 4.53371294702715; -0.853012055107928; -2.72222667524045], 1e-6); %! assert_equal (mdl.Coefficients.pValue, ... %! [1.67738549623508e-15; 0.000202251230338429; 0.403753045528205; 0.0131231039551409], 1e-8); %! assert_equal (mdl.PredictorNames, {'x1';'x2'}); %!test %! mdl = stepwiselm (X, y, 'Upper', 'poly110', 'Verbose', 0); %! assert_equal (mdl.NumCoefficients, 3); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.DFE, 21); %! assert_equal (mdl.SSE, 1.52876802397, 1e-8); %! assert_equal (mdl.SSR, 24.1046397229, 1e-6); %! assert_equal (mdl.Rsquared.Ordinary, 0.940360328246, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [4.21600780425081; 1.37121494151013; -0.897420541182461], 1e-8); %! assert_equal (mdl.Coefficients.SE, ... %! [0.186735917268724; 0.342414105749106; 0.136495702980697], 1e-8); %! assert_equal (mdl.Coefficients.tStat, ... %! [22.577380216489; 4.00455156048637; -6.57471643125185], 1e-6); %! assert_equal (mdl.Coefficients.pValue, ... %! [3.27879166781824e-16; 0.000642688076385025; 1.64115221424634e-06], 1e-8); %! assert_equal (mdl.PredictorNames, {'x1';'x2'}); %!test %! p1 = (1:15)'/15; %! code = repmat ([1;2;3], 5, 1); %! resp = [5.18127588719375; 9.35081376514746; 3.49974949866041; 5.6242630760159; ... %! 9.72651388107706; 3.81411200080599; 5.89825501056437; 9.99098641713587; ... %! 4.10224698823349; 6.23744090586702; 10.3961126341096; 4.57205845018011; ... %! 6.75484533214212; 10.9323653265385; 5.09379999767747]; %! mdl = stepwiselm ([p1, code], resp, 'CategoricalVars', logical ([0 1]), ... %! 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.NumObservations, 15); %! assert_equal (mdl.NumCoefficients, 4); %! assert_equal (mdl.NumPredictors, 2); %! assert_equal (mdl.DFE, 11); %! assert_equal (mdl.SSE, 0.0670192081399, 1e-8); %! assert_equal (mdl.Rsquared.Ordinary, 0.999296834962, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2_2','x2_3'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [5.04173312790335; 1.92317767382845; 4.01193051752317; -1.97924634508893], 1e-8); %! assert_equal (mdl.Coefficients.SE, ... %! [0.0482103221773782; 0.0712545629266561; 0.0495946318075163; 0.050272494208676], 1e-8); %! assert_equal (mdl.Coefficients.tStat, ... %! [104.577876691085; 26.990238868043; 80.8944510989422; -39.3703629836483], 1e-5); %! assert_equal (mdl.Coefficients.pValue, ... %! [7.63821769167572e-18; 2.10244709730975e-11; 1.28303158624727e-16; 3.44025340004292e-13], 1e-8); %! assert_equal (mdl.PredictorNames, {'x1';'x2'}); %!test %! x1n = x1; yn = y; %! x1n(5) = NaN; %! yn(12) = NaN; %! mdl = stepwiselm ([x1n, x2, x3], yn, 'Upper', 'interactions', 'Verbose', 0); %! assert_equal (mdl.NumObservations, 22); %! assert_equal (mdl.NumCoefficients, 4); %! assert_equal (mdl.NumPredictors, 3); %! assert_equal (mdl.DFE, 18); %! assert_equal (mdl.SSE, 0.220518784715, 1e-8); %! assert_equal (mdl.SSR, 23.3502845138, 1e-6); %! assert_equal (mdl.Rsquared.Ordinary, 0.990644409445, 1e-9); %! assert_equal (mdl.CoefficientNames, {'(Intercept)','x1','x2','x3'}); %! assert_equal (mdl.Coefficients.Estimate, ... %! [4.11761577459912; 1.79579438452674; -1.05927400080053; 0.512812488395983], 1e-8); %! assert_equal (mdl.Coefficients.SE, ... %! [0.0775488744294778; 0.146527417121933; 0.0594402526920882; 0.0517068469441139], 1e-8); %! assert_equal (mdl.Coefficients.tStat, ... %! [53.0970411226747; 12.2556885243692; -17.8208192735615; 9.91769018424668], 1e-6); %! assert_equal (mdl.Coefficients.pValue, ... %! [3.09620365929824e-21; 3.59135935529609e-10; 6.98703068441474e-13; 1.01407825601795e-08], 1e-8); %! assert_equal (mdl.PredictorNames, {'x1';'x2';'x3'}); %!test %! ## Steps carries MATLAB's seven fields, in its order. %! s1 = ((1:48)' - 24.5)/12; %! s2 = sin ((1:48)'/5); %! s3 = cos ((1:48)'/7); %! sy = 4 + 2.5*s1 - 1.1*s2 + 0.6*s1.^2 + 0.2*sin ((1:48)'/3); %! mdl = stepwiselm ([s1, s2, s3], sy, 'constant', 'Upper', 'quadratic', ... %! 'Verbose', 0); %! assert_equal (fieldnames (mdl.Steps), {'Start'; 'Lower'; 'Upper'; ... %! 'Criterion'; 'PEnter'; ... %! 'PRemove'; 'History'}); %!test %! ## The history records every step, and matches MATLAB R2024a throughout. %! s1 = ((1:48)' - 24.5)/12; %! s2 = sin ((1:48)'/5); %! s3 = cos ((1:48)'/7); %! sy = 4 + 2.5*s1 - 1.1*s2 + 0.6*s1.^2 + 0.2*sin ((1:48)'/3); %! mdl = stepwiselm ([s1, s2, s3], sy, 'constant', 'Upper', 'quadratic', ... %! 'Verbose', 0); %! assert_equal (mdl.Steps.History.TermName, ... %! {'1'; 'x1'; 'x1^2'; 'x2'; 'x2^2'; 'x3'; 'x2:x3'; 'x1:x2'; ... %! 'x1:x3'}); %! assert_equal (mdl.Steps.History.DF, (1:9)'); %! assert_equal (mdl.Steps.History.FStat, ... %! [NaN; 685.736350968728; 12.6370657930220; ... %! 1231.00720701963; 124.872101651316; 13.4519287960430; ... %! 107.087846311142; 34.9838482569170; 420.274658550522], 1e-8); %!test %! ## The default criterion is reported as MATLAB spells it, with the %! ## thresholds it resolved. %! s1 = ((1:48)' - 24.5)/12; %! sy = 4 + 2.5*s1 + 0.2*sin ((1:48)'/3); %! mdl = stepwiselm (s1, sy, 'constant', 'Upper', 'linear', 'Verbose', 0); %! assert_equal (mdl.Steps.Criterion, 'SSE'); %! assert_equal (mdl.Steps.PEnter, 0.05); %! assert_equal (mdl.Steps.PRemove, 0.10); %!test %! ## A criterion asked for is reported as it was asked for, not canonicalised. %! s1 = ((1:48)' - 24.5)/12; %! sy = 4 + 2.5*s1 + 0.2*sin ((1:48)'/3); %! m1 = stepwiselm (s1, sy, 'constant', 'Upper', 'linear', 'Criterion', ... %! 'aic', 'Verbose', 0); %! m2 = stepwiselm (s1, sy, 'constant', 'Upper', 'linear', 'Criterion', ... %! 'AIC', 'Verbose', 0); %! assert_equal (m1.Steps.Criterion, 'aic'); %! assert_equal (m2.Steps.Criterion, 'AIC'); %!test %! ## An information criterion has no test to report, so the history carries %! ## its value after each step, the starting model included. %! s1 = ((1:48)' - 24.5)/12; %! s2 = sin ((1:48)'/5); %! s3 = cos ((1:48)'/7); %! sy = 4 + 2.5*s1 - 1.1*s2 + 0.6*s1.^2 + 0.2*sin ((1:48)'/3); %! mdl = stepwiselm ([s1, s2, s3], sy, 'constant', 'Upper', 'quadratic', ... %! 'Criterion', 'aic', 'Verbose', 0); %! assert_equal (mdl.Steps.History.Properties.VariableNames, ... %! {'Action', 'TermName', 'Terms', 'DF', 'delDF', 'AIC'}); %! assert_equal (mdl.Steps.History.AIC(1), 243.691352606191, 1e-9); %! assert_equal (mdl.Steps.History.AIC(end), -339.898392996306, 1e-9); %!test %! ## A removal takes degrees of freedom away, so its delDF is negative. %! s1 = ((1:48)' - 24.5)/12; %! s2 = sin ((1:48)'/5); %! s3 = cos ((1:48)'/7); %! sy = 4 + 2.5*s1 - 1.1*s2 + 0.6*s1.^2 + 0.2*sin ((1:48)'/3); %! mdl = stepwiselm ([s1, s2, s3], sy, 'constant', 'Upper', 'quadratic', ... %! 'Criterion', 'aic', 'Verbose', 0); %! assert_equal (mdl.Steps.History.Action{end}, 'Remove'); %! assert_equal (mdl.Steps.History.TermName{end}, 'x2^2'); %! assert_equal (mdl.Steps.History.delDF(end), -1); %! assert_equal (mdl.Steps.History.DF(end), 9); %!test %! ## Under 'rsquared' the column is named for the criterion too. %! s1 = ((1:48)' - 24.5)/12; %! s2 = sin ((1:48)'/5); %! s3 = cos ((1:48)'/7); %! sy = 4 + 2.5*s1 - 1.1*s2 + 0.6*s1.^2 + 0.2*sin ((1:48)'/3); %! mdl = stepwiselm ([s1, s2, s3], sy, 'constant', 'Upper', 'quadratic', ... %! 'Criterion', 'rsquared', 'Verbose', 0); %! assert_equal (mdl.Steps.History.Properties.VariableNames, ... %! {'Action', 'TermName', 'Terms', 'DF', 'delDF', 'Rsquared'}); %! assert_equal (mdl.Steps.History.Rsquared, [0; 0.937135827762142], 1e-12); %!test %! ## A categorical predictor is one step worth L-1 degrees of freedom, named %! ## for the predictor rather than for its indicators. %! w = 1400 + (1:45)'*9; %! g = {'A','B','C'}(mod ((0:44), 3) + 1)'; %! m = 55 - 0.005*w + [0 5 -3](mod ((0:44), 3) + 1)' + 0.3*sin ((1:45)'/6); %! t = table (w, g, m, 'VariableNames', {'Weight','Group','MPG'}); %! mdl = stepwiselm (t, 'MPG ~ 1', 'Upper', 'MPG ~ Weight + Group', ... %! 'Verbose', 0); %! assert_equal (mdl.Steps.History.TermName, {'1'; 'Group'; 'Weight'}); %! assert_equal (mdl.Steps.History.DF, [1; 3; 4]); %! assert_equal (mdl.Steps.History.delDF, [NaN; 2; 1]); %! assert_equal (mdl.Steps.History.FStat, ... %! [NaN; 493.133397072530; 464.280082409591], 1e-8); %!test %! ## The Terms of the last row are the terms of the model returned. %! w = 1400 + (1:45)'*9; %! g = {'A','B','C'}(mod ((0:44), 3) + 1)'; %! m = 55 - 0.005*w + [0 5 -3](mod ((0:44), 3) + 1)' + 0.3*sin ((1:45)'/6); %! t = table (w, g, m, 'VariableNames', {'Weight','Group','MPG'}); %! mdl = stepwiselm (t, 'MPG ~ 1', 'Upper', 'MPG ~ Weight + Group', ... %! 'Verbose', 0); %! assert_equal (mdl.Steps.History.Terms{end}, mdl.Formula.Terms); %! assert_equal (mdl.Steps.History.Terms{end}, [0, 0, 0; 1, 0, 0; 0, 1, 0]); %!test %! ## The Start row names the starting model, not the constant. %! w = 1400 + (1:45)'*9; %! g = {'A','B','C'}(mod ((0:44), 3) + 1)'; %! m = 55 - 0.005*w + [0 5 -3](mod ((0:44), 3) + 1)' + 0.3*sin ((1:45)'/6); %! t = table (w, g, m, 'VariableNames', {'Weight','Group','MPG'}); %! mdl = stepwiselm (t, 'MPG ~ Weight + Group', 'Lower', 'MPG ~ Weight', ... %! 'Upper', 'MPG ~ Weight + Group', 'Verbose', 0); %! assert_equal (mdl.Steps.History.TermName, {'1 + Weight + Group'}); %! assert_equal (mdl.Steps.History.DF, 4); %!test %! ## Start, Lower, and Upper are formula objects over the model's variables. %! s1 = ((1:48)' - 24.5)/12; %! s2 = sin ((1:48)'/5); %! sy = 4 + 2.5*s1 - 1.1*s2 + 0.2*sin ((1:48)'/3); %! mdl = stepwiselm ([s1, s2], sy, 'constant', 'Upper', 'linear', ... %! 'Verbose', 0); %! assert_equal (class (mdl.Steps.Start), 'LinearFormula'); %! assert_equal (char (mdl.Steps.Start), 'y ~ 1'); %! assert_equal (char (mdl.Steps.Upper), 'y ~ 1 + x1 + x2'); %! assert_equal (mdl.Steps.Start.VariableNames, {'x1', 'x2', 'y'}); %!test %! ## A power term in a starting formula survives. Folding it onto the %! ## all-zero row once discarded it as a duplicate intercept, so %! ## 'y ~ x1 + x1^2' fitted '1 + x1'. %! s1 = ((1:48)' - 24.5)/12; %! s2 = sin ((1:48)'/5); %! sy = 4 + 2.5*s1 - 1.1*s2 + 0.6*s1.^2 + 0.2*sin ((1:48)'/3); %! mdl = stepwiselm ([s1, s2], sy, 'y ~ x1 + x1^2', 'Upper', 'quadratic', ... %! 'NSteps', 0, 'Verbose', 0); %! assert_equal (mdl.Formula.LinearPredictor, '1 + x1 + x1^2'); %! assert_equal (mdl.Steps.History.TermName, {'1 + x1 + x1^2'}); %! assert_equal (mdl.Steps.History.DF, 3); %!test %! ## Capping the search at no steps leaves the history with its Start row. %! s1 = ((1:48)' - 24.5)/12; %! sy = 4 + 2.5*s1 + 0.2*sin ((1:48)'/3); %! mdl = stepwiselm (s1, sy, 'constant', 'Upper', 'linear', 'NSteps', 0, ... %! 'Verbose', 0); %! assert_equal (size (mdl.Steps.History, 1), 1); %! assert_equal (mdl.Steps.History.Action{1}, 'Start'); %!error stepwiselm () %!error stepwiselm (X, y, 'Verbose') %!error ... %! stepwiselm (X, y, 'Criterion', 'bogus') %!error ... %! stepwiselm (X, y, 'RobustOpts', 'on') %!error ... %! stepwiselm (X, y, [0 0], 'PredictorVars', {'x1', 'x2'}) %!error ... %! stepwiselm (tbl, 'y ~ x1', 'PredictorVars', {'x2'}) %!error ... %! stepwiselm (tbl, 'y ~ x1', 'ResponseVar', 'x2', 'PredictorVars', {'x1'}) %!error stepwiselm (X, y, 'Upper', [0 0 0 0 0]) %!error ... %! stepwiselm (X, y, 'Upper', {1, 2}) %!error stepwiselm (X, y, 'Upper', 'poly1') %!error stepwiselm (X, y, 'Upper', 'bogusmodel') %!error stepwiselm (X, y, 'PEnter', 0.5) %!error stepwiselm (X, y, 'PEnter', 0.1, 'PRemove', 0.1) %!error stepwiselm (X, y, 'Criterion', 'aic', 'PEnter', 0.5, 'PRemove', 0.1) %!error stepwiselm (X, y, 'Criterion', 'rsquared', 'PEnter', 0.001, 'PRemove', 0.5) %!test %! ## sse's default thresholds, 0.05 and 0.10, do not trip the guard %! assert_equal (isa (stepwiselm (X, y, 'Criterion', 'sse', 'Verbose', 0), ... %! 'LinearModel'), true); %!test %! ## aic's, 0 and 0.01 %! assert_equal (isa (stepwiselm (X, y, 'Criterion', 'aic', 'Verbose', 0), ... %! 'LinearModel'), true); %!test %! ## bic's, 0 and 0.01 %! assert_equal (isa (stepwiselm (X, y, 'Criterion', 'bic', 'Verbose', 0), ... %! 'LinearModel'), true); %!test %! ## rsquared's, 0.1 and 0.05, which run the other way round %! assert_equal (isa (stepwiselm (X, y, 'Criterion', 'rsquared', 'Verbose', 0), ... %! 'LinearModel'), true); %!test %! ## adjrsquared's, 0 and -0.05, the negative one %! assert_equal (isa (stepwiselm (X, y, 'Criterion', 'adjrsquared', 'Verbose', 0), ... %! 'LinearModel'), true); statistics-release-1.9.2/inst/Supervised_Learning/000077500000000000000000000000001524624707500222745ustar00rootroot00000000000000statistics-release-1.9.2/inst/Supervised_Learning/ClassificationDiscriminant.m000066400000000000000000005055341524624707500277660ustar00rootroot00000000000000## Copyright (C) 2024 Ruchika Sonagote ## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef ClassificationDiscriminant ## -*- texinfo -*- ## @deftp {statistics} ClassificationDiscriminant ## ## Discriminant analysis classification ## ## The @code{ClassificationDiscriminant} class implements a ## discriminant analysis classifier object, which can predict responses for ## new data using the @code{predict} method. ## ## Discriminant analysis classification is a statistical method used to ## classify observations into predefined groups based on their ## characteristics. It estimates the parameters of different distributions ## for each class and predicts the class of new observations by finding the ## one with the smallest misclassification cost. ## ## Create a @code{ClassificationDiscriminant} object by using the ## @code{fitcdiscr} function or the class constructor. ## ## ## Six discriminant types are available, in two families. The linear family, ## @qcode{'linear'}, @qcode{'diagLinear'} and @qcode{'pseudoLinear'}, pools ## one covariance across the classes and separates them with a hyperplane. ## The quadratic family, @qcode{'quadratic'}, @qcode{'diagQuadratic'} and ## @qcode{'pseudoQuadratic'}, estimates a covariance per class and separates ## them with a quadric. A @qcode{'diag'} type keeps only the variances, ## which is the same model as a @qcode{Gamma} of 1, and a @qcode{'pseudo'} ## type inverts a singular covariance rather than refusing it. ## ## @qcode{DiscrimType} may be assigned after fitting, but @emph{only within ## its own family}: the family is fixed when the model is fitted, because it ## decides which covariances the fit has to estimate. Assigning it, or ## @qcode{Gamma}, re-derives @qcode{Sigma}, @qcode{LogDetSigma} and ## @qcode{Coeffs} without refitting. ## ## @seealso{fitcdiscr} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} W ## ## Observation weights ## ## A numeric column vector with one entry per observation used for fitting. ## Every observation carries the same weight, so the vector sums ## to one. This property is read-only. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} X ## ## Predictor data ## ## A numeric matrix containing the unstandardized predictor data. Each ## column of @var{X} represents one predictor (variable), and each row ## represents one observation. This property is read-only. ## ## @end deftp X = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} Y ## ## Class labels ## ## Specified as a logical or numeric column vector, or as a character array ## or a cell array of character vectors with the same number of rows as the ## predictor data. Each row in @var{Y} is the observed class label for ## the corresponding row in @var{X}. This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} NumObservations ## ## Number of observations ## ## A positive integer value specifying the number of observations in the ## training dataset used for training the ClassificationDiscriminant model. ## This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} RowsUsed ## ## Rows used for fitting ## ## A logical column vector with the same length as the observations in the ## original predictor data @var{X}, true for each row that was used for ## fitting the ClassificationDiscriminant model. It is empty, @qcode{[]}, ## when every observation was used, so a non-empty value means that rows ## holding missing values were dropped. This property is read-only. ## ## @end deftp RowsUsed = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} NumPredictors ## ## Number of predictors ## ## A positive integer value specifying the number of predictors in the ## training dataset used for training the ClassificationDiscriminant model. ## This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} PredictorNames ## ## Names of predictor variables ## ## A cell array of character vectors specifying the names of the predictor ## variables. The names are in the order in which they appear in the ## training dataset. This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} BetweenSigma ## ## Between-class covariance matrix ## ## A @math{P}-by-@math{P} matrix holding the covariance of the class means ## about the overall mean, weighted by how many observations each class ## contributes. With @math{n_k} observations in class @math{k}, ## @math{p_k = n_k / n} and @math{\bar{\mu} = \sum_k p_k \mu_k}, it is ## ## @example ## @group ## BetweenSigma = sum_k n_k (Mu(k,:) - mubar)' * (Mu(k,:) - mubar) ## / (n * (1 - sum_k p_k^2)) ## @end group ## @end example ## ## The denominator is the unbiased one for a weighted covariance, so a ## balanced fit divides by @math{n (K-1) / K}. It reads the @strong{class ## sizes}, not @qcode{Prior}: assigning a prior leaves it where it was. It ## is estimated for every discriminant type, the quadratic family included, ## since it describes the classes rather than the fit. This property is ## read-only. ## ## @end deftp BetweenSigma = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector of column indices into @code{X} naming the predictors ## treated as categorical, and empty when none is. This property is ## read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} ExpandedPredictorNames ## ## Names of the predictors as the model expanded them ## ## A cell array of character vectors. It matches @code{PredictorNames} ## unless a categorical predictor was expanded into indicator variables. ## This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} ResponseName ## ## Response variable name ## ## A character vector specifying the name of the response variable @var{Y}. ## This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} ClassNames ## ## Names of classes in the response variable ## ## An array of unique values of the response variable @var{Y}, which has the ## same data types as the data in @var{Y}. This property is read-only. ## @qcode{ClassNames} can have any of the following datatypes: ## ## @itemize ## @item Cell array of character vectors ## @item Character array ## @item Logical vector ## @item Numeric vector ## @end itemize ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} Sigma ## ## Within-class covariance ## ## A numeric array whose shape follows @qcode{DiscrimType}, with @math{P} ## predictors and @math{K} classes: ## ## @multitable @columnfractions 0.4 0.25 0.35 ## @headitem @var{DiscrimType} @tab @var{Sigma} @tab @var{LogDetSigma} ## @item @qcode{'linear'}, @qcode{'pseudoLinear'} @tab @math{PxP} ## @tab scalar ## @item @qcode{'quadratic'}, @qcode{'pseudoQuadratic'} @tab @math{PxPxK} ## @tab @math{Kx1} ## @item @qcode{'diagLinear'} @tab @math{1xP} @tab scalar ## @item @qcode{'diagQuadratic'} @tab @math{1xPxK} @tab @math{Kx1} ## @end multitable ## ## The linear family pools one covariance across the classes and the ## quadratic family estimates one per class. This property is read-only, ## but it is re-derived whenever @qcode{DiscrimType} or @qcode{Gamma} is ## assigned. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} Mu ## ## Class means ## ## A @math{K*P} numeric matrix specifying the mean of the multivariate ## normal distribution of each corresponding class, where @math{K} is the ## number of classes and @math{P} is the number of predictors in @var{X}. ## This property is read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} Coeffs ## ## Coefficient matrices ## ## A @math{K*K} structure containing the coefficient matrices, where ## @math{K} is the number of classes. If the @qcode{'FillCoeffs'} parameter ## was set to @qcode{'off'} in either the @code{fitcdiscr} function or the ## @code{ClassificationDiscriminant} constructor, then @qcode{Coeffs} is ## empty @qcode{([])}. This property is read-only. ## ## @qcode{Coeffs(i,j)} contains the coefficients of the boundary between ## the classes @code{i} and @code{j} in the following fields: ## ## @itemize ## @item @qcode{DiscrimType} - A character vector ## @item @qcode{Class1} - @qcode{@var{ClassNames}(i)} ## @item @qcode{Class2} - @qcode{@var{ClassNames}(j)} ## @item @qcode{Const} - A scalar ## @item @qcode{Linear} - A vector with length as the number of predictors. ## @item @qcode{Quadratic} - The quadratic family only. A @math{PxP} ## matrix, or a @math{1xP} vector for @qcode{'diagQuadratic'}, following ## the shape of @qcode{Sigma}. ## @end itemize ## ## The diagonal entries carry the two class names and nothing else. The ## structure is rebuilt whenever @qcode{DiscrimType}, @qcode{Gamma} or ## @qcode{Prior} is assigned. ## ## @end deftp Coeffs = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} DeltaPredictor ## ## Minimum Delta at which each predictor drops out ## ## A row vector with one entry per predictor, the value of @qcode{Delta} at ## which that predictor's coefficient is zero for every class and the ## predictor leaves the model altogether. It is all zeros for the ## quadratic family, which has no linear coefficients to eliminate. ## ## This property is read-only, and it describes the fit rather than the ## threshold: assigning @qcode{Delta} does not move it. ## ## @end deftp DeltaPredictor = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} MinGamma ## ## Minimum value for the Gamma regularization parameter ## ## A scalar from 0 to 1, the least regularization that leaves the ## correlation matrix invertible. It is 0 when the matrix is already ## invertible, and positive when the predictors are collinear, in which ## case a plain @qcode{'linear'} or @qcode{'quadratic'} fit is raised to it ## rather than failing. Assigning a @qcode{Gamma} below it is refused. ## ## This property is read-only. ## ## @end deftp MinGamma = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} LogDetSigma ## ## Logarithm of the determinant of the within-class covariance matrix ## ## A scalar for the linear family and a @math{Kx1} vector for the quadratic ## one, one entry per class. It is computed in correlation space, as the ## sum of the logarithms of the predictor variances plus the log ## determinant of the correlation matrix, which is far better conditioned ## than the covariance when the data are nearly collinear. A predictor ## with no variance contributes nothing rather than an infinity, and the ## @qcode{'pseudo'} types sum only over the directions that carry variance. ## ## This property is read-only. ## ## @end deftp LogDetSigma = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} XCentered ## ## Predictor data with class means subtracted ## ## A matrix of the same size as @var{X} and the values in @var{X} with the ## corresponding class means subtracted. This property is read-only. ## ## @end deftp XCentered = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} BinEdges ## ## Bin edges of the predictors ## ## A cell array with one entry per predictor, holding that predictor's bin ## edges where the learner discretized it before fitting. It is empty here ## and stays empty: this learner fits the predictors as they are, and ## MATLAB's reports an empty cell for it as well. ## ## This property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} ModelParameters ## ## Fitting options, as they were given ## ## A structure holding the parameters of the fit: @qcode{DiscrimType}, ## @qcode{Gamma}, @qcode{Delta}, @qcode{FillCoeffs}, and the ## @qcode{Version}, @qcode{Method} and @qcode{Type} tags. ## ## MATLAB reports a @qcode{SaveMemory} field beside these. This class ## has no such option and always stores the full covariance, so there is ## no setting to report and the field is absent rather than answering for ## a knob that does not exist. This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} HyperparameterOptimizationResults ## ## Results of the hyperparameter optimization ## ## @strong{Always empty.} It is declared for MATLAB compatibility, where ## it holds what an automatic search over the hyperparameters found. This ## class fits the parameters it is given and runs no such search, so there ## is nothing to report. This property is read-only. ## ## @end deftp HyperparameterOptimizationResults = []; endproperties ## Properties a user may set after the model is fitted. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} DiscrimType ## ## Discriminant type ## ## A character vector naming the discriminant model, one of ## @qcode{'linear'}, @qcode{'quadratic'}, @qcode{'diagLinear'}, ## @qcode{'diagQuadratic'}, @qcode{'pseudoLinear'} or ## @qcode{'pseudoQuadratic'}. A linear type pools one covariance across ## the classes; a quadratic type estimates one per class. A ## @qcode{'diag'} type keeps only the variances, and a @qcode{'pseudo'} ## type inverts a singular covariance instead of refusing it. ## ## This property may be assigned, but @emph{only within its own family}: ## the three linear types interchange freely and so do the three quadratic ## ones, while no assignment moves a model between the two. The family is ## fixed when the model is fitted, because it decides which covariances the ## fit has to estimate. Assigning re-derives @qcode{Sigma}, ## @qcode{LogDetSigma}, @qcode{Gamma} and @qcode{Coeffs}. ## ## @end deftp DiscrimType = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} Gamma ## ## Gamma regularization parameter ## ## A scalar from 0 to 1 shrinking the covariance towards its diagonal. ## @qcode{Gamma} and @qcode{DiscrimType} are one state: a value of 1 is the ## diagonal type, so assigning it renames @qcode{DiscrimType} to ## @qcode{'diagLinear'} or @qcode{'diagQuadratic'}, and assigning a ## diagonal type sets @qcode{Gamma} to 1. ## ## The quadratic family admits 0 and 1 only. A value below ## @qcode{MinGamma} is refused, since it would leave the covariance ## singular. Assigning re-derives @qcode{Sigma}, @qcode{LogDetSigma} and ## @qcode{Coeffs}. ## ## @end deftp Gamma = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} Delta ## ## Delta threshold for the linear coefficients ## ## A nonnegative scalar that eliminates predictors. A per-class linear ## coefficient is set to zero when it falls below @qcode{Delta}, and the ## comparison is made on the @strong{standardized} coefficient, the ## coefficient times the within-class standard deviation of its predictor. ## Scaling matters here: a threshold on the raw coefficients would depend ## on the units each predictor is measured in, so the same model in ## centimetres and in metres would drop different predictors. ## ## @qcode{DeltaPredictor} reports, per predictor, the value at which it ## drops out of every class at once. ## ## It applies to the linear family only, a quadratic discriminant having no ## linear coefficients to eliminate. Assigning it rebuilds @qcode{Coeffs} ## and changes what @code{predict} answers. ## ## @end deftp Delta = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} Cost ## ## Cost of Misclassification ## ## A square matrix specifying the cost of misclassification of a point. ## @qcode{Cost(i,j)} is the cost of classifying a point into class @qcode{j} ## if its true class is @qcode{i} (that is, the rows correspond to the true ## class and the columns correspond to the predicted class). The order of ## the rows and columns in @qcode{Cost} corresponds to the order of the ## classes in @qcode{ClassNames}. The number of rows and columns in ## @qcode{Cost} is the number of unique classes in the response. By ## default, @qcode{Cost(i,j) = 1} if @qcode{i != j}, and ## @qcode{Cost(i,j) = 0} if @qcode{i = j}. In other words, the cost is 0 ## for correct classification and 1 for incorrect classification. ## ## Add or change the @qcode{Cost} property using dot notation as in: ## @itemize ## @item @qcode{@var{obj}.Cost = @var{costMatrix}} ## @end itemize ## ## ## A cost may also be given as a struct with the fields ## @qcode{ClassNames} and @qcode{ClassificationCosts}, which names the ## order its own matrix is written in. That matrix is permuted into the ## order of @qcode{ClassNames} above, so a caller need not know which ## order the classes were sorted into. It must name every class. ## ## A cost must be floating point, not sparse, not complex, non-negative ## and zero down its diagonal, and must hold no @qcode{NaN} or ## @qcode{Inf}. A @code{single} is widened to @code{double}. ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} Prior ## ## Prior probability for each class ## ## A numeric vector specifying the prior probabilities for each class. The ## order of the elements in @qcode{Prior} corresponds to the order of the ## classes in @qcode{ClassNames}. ## ## Add or change the @qcode{Prior} property using dot notation as in: ## @itemize ## @item @qcode{@var{obj}.Prior = @var{priorVector}} ## @end itemize ## ## Specified as a row vector with one entry per class, in the order of ## @qcode{ClassNames}, and rescaled to sum to one. It may be given as ## @qcode{'empirical'}, @qcode{'uniform'}, a numeric vector, or a ## structure with @qcode{ClassNames} and @qcode{ClassProbs} fields, which ## assigns each probability by class name rather than by position. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {ClassificationDiscriminant} {property} ScoreTransform ## ## Transformation function for classification scores ## ## Specified as a function handle for transforming the classification ## scores. Add or change the @qcode{ScoreTransform} property using dot ## notation as in: ## ## @itemize ## @item @qcode{@var{obj}.ScoreTransform = 'function_name'} ## @item @qcode{@var{obj}.ScoreTransform = @@function_handle} ## @end itemize ## ## When specified as a character vector, it can be any of the following ## built-in functions. Nevertheless, the @qcode{ScoreTransform} property ## always stores their function handle equivalent. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'doublelogit'} @tab @math{1 ./ (1 + exp (-2 * x))} ## @item @qcode{'invlogit'} @tab @math{log (x ./ (1 - x))} ## @item @qcode{'ismax'} @tab Sets the score for the class with the ## largest score to 1, and for all other classes to 0 ## @item @qcode{'logit'} @tab @math{1 ./ (1 + exp (-x))} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'sign'} @tab ## @math{-1 for x < 0, 0 for x = 0, 1 for x > ## 0} ## @item @qcode{'symmetric'} @tab @math{2 * x - 1} ## @item @qcode{'symmetricismax'} @tab Sets the score for the class ## with the largest score to 1, and for all other classes to -1 ## @item @qcode{'symmetriclogit'} @tab @math{2 ./ (1 + exp (-x)) - 1} ## @end multitable ## ## @end deftp ScoreTransform = 'none'; endproperties ## Readable by the compact counterpart, which copies it, and kept out of ## the documented surface. MATLAB hides its own equivalent the same way. properties (GetAccess = public, SetAccess = protected, Hidden) STfun = @(x) x; ## The unregularized within-class covariance the fit estimated, PxP for the ## linear family and PxPxK for the quadratic one. Sigma, LogDetSigma and ## Coeffs are all derived from it, so assigning DiscrimType within the ## family re-derives rather than refits. BaseSigma = []; endproperties ## Set methods for the properties a user may assign after fitting. methods (Hidden) function this = set.Cost (this, Cost) gnY = uniqueLabels (this.Y); if (isempty (Cost)) this.Cost = cast (! eye (classCount (gnY)), 'double'); else ## Everything a cost must be, and the struct form, which ## is permuted into this model's class order. [Cost, errmsg] = costMatrix (Cost, gnY); if (! isempty (errmsg)) error ("ClassificationDiscriminant: %s", errmsg); endif this.Cost = Cost; endif endfunction function this = set.Prior (this, Prior) [~, gnY, gY] = uniqueLabels (this.Y); if (isstruct (Prior)) Prior = priorFromStruct (Prior, this.ClassNames, ... 'ClassificationDiscriminant'); endif ## Set prior if (strcmpi ('uniform', Prior)) this.Prior = ones (1, numel (gnY)) ./ numel (gnY); elseif (isempty (Prior) || strcmpi ('empirical', Prior)) pr = []; for i = 1:numel (gnY) pr = [pr; sum(gY==i)]; endfor this.Prior = pr(:)' ./ sum (pr); elseif (isnumeric (Prior)) if (numel (gnY) != numel (Prior)) error (strcat ("ClassificationDiscriminant: the elements", ... " in 'Prior' do not correspond to the", ... " selected classes in Y.")); endif this.Prior = Prior(:)' ./ sum (Prior); endif ## Rebuild the coefficients, whose constant term reads the priors. ## Delegating rather than restating the linear formula is what keeps the ## six discriminant types in step: the formula here was linear only, and ## a quadratic model's Sigma is not even the same shape. if (! isempty (this.Coeffs) && ! isempty (this.BaseSigma)) ## Derived here rather than read off Sigma, so the covariance and the ## type always agree even when the object is part way through a load. [Sg, Ld] = discrimderive (this.BaseSigma, this.DiscrimType, ... this.Gamma, this.MinGamma); this.Coeffs = discrimcoeffs (this.Mu, Sg, Ld, this.Prior, ... this.DiscrimType, this.Delta, ... this.ClassNames); endif endfunction ## DiscrimType, Gamma and Delta are one state, and these three methods are ## the only place it is changed. Assigning the type or the regularization ## re-derives Sigma, LogDetSigma and Coeffs from BaseSigma, the covariance ## the fit estimated, so no assignment ever refits. Each writes its own ## property directly, which does not re-enter its own set method, and ## reaches the others through theirs, which is what keeps the three ## consistent whichever one the user assigns. function this = set.DiscrimType (this, val) [t, fam] = discrimcanon (val); ## The family a fit belongs to is written in BaseSigma's shape: one ## covariance for the linear family, one per class for the quadratic. ## Reading it there rather than from the stored type is what lets a ## loaded model take its own type back, the stub it is loaded into ## carrying the default type until the file has been read. if (! isempty (this.BaseSigma) && size (this.BaseSigma, 3) > 1) cur = 'quadratic'; elseif (! isempty (this.BaseSigma)) cur = 'linear'; else [~, cur] = discrimcanon (this.DiscrimType); endif if (isempty (t) || (! isempty (cur) && ! strcmp (fam, cur))) ## The family was fixed by the fit, so only three types are on offer, ## and an unrecognized name is refused by the same message. if (strcmp (cur, 'quadratic')) ok = "quadratic, diagQuadratic, or pseudoQuadratic"; else ok = "linear, diagLinear, or pseudoLinear"; endif error (strcat ("ClassificationDiscriminant: 'DiscrimType' can only", ... " be set to one of: %s."), ok); endif this.DiscrimType = t; if (isempty (this.BaseSigma)) return; # not fitted yet, nothing to derive endif ## A diagonal type is Gamma of 1 and every other type starts from 0, ## which is what the oracle reports for each transition. The derivation ## raises it to MinGamma where the covariance needs it. g = double (strncmp (t, 'diag', 4)); [~, ~, g] = discrimderive (this.BaseSigma, t, g, this.MinGamma); this.Gamma = g; endfunction function this = set.Gamma (this, val) if (! (isnumeric (val) && isscalar (val) && val >= 0 && val <= 1)) error (strcat ("ClassificationDiscriminant: 'Gamma' must be a", ... " scalar between 0 and 1.")); endif if (isempty (this.BaseSigma)) this.Gamma = val; return; endif [~, fam] = discrimcanon (this.DiscrimType); if (strcmp (fam, 'quadratic') && val > 0 && val < 1) error (strcat ("ClassificationDiscriminant: cannot set 'Gamma' to", ... " any value but 0 or 1 for a quadratic discriminant.")); endif ## Gamma of 1 IS the diagonal type. Naming the type does the rest, and ## comes back here with a value that no longer needs the rename. if (val == 1 && ! strncmp (this.DiscrimType, 'diag', 4)) if (strcmp (fam, 'quadratic')) this.DiscrimType = 'diagQuadratic'; else this.DiscrimType = 'diagLinear'; endif return; elseif (val < 1 && strncmp (this.DiscrimType, 'diag', 4)) if (strcmp (fam, 'quadratic')) this.DiscrimType = 'quadratic'; else this.DiscrimType = 'linear'; endif endif ## The MinGamma floor belongs to the one type that regularizes its way ## out of a singular covariance. A pseudo type is Gamma of 0 by ## definition, inverting the rank it has instead; a diagonal type is ## Gamma of 1 and never near the floor; and the quadratic family refuses ## a singular class covariance outright rather than ridging it. Mg = this.MinGamma; [Sg, Ld, Ge] = discrimderive (this.BaseSigma, this.DiscrimType, val, Mg); if (val < Mg && strcmp (this.DiscrimType, 'linear')) error (strcat ("ClassificationDiscriminant: 'Gamma' must be", ... " between %g and 1."), Mg); endif this.Gamma = Ge; this.Sigma = Sg; this.LogDetSigma = Ld; ## The delta at which a predictor drops out is read off the linear ## coefficients, and regularizing the covariance moves them, so this ## follows Gamma. It does NOT follow Delta, which is a threshold ## applied to those coefficients rather than a change to them. if (strcmp (fam, 'linear')) [~, ~, this.DeltaPredictor] = discrimlinear (this.Mu, Sg, ... this.Prior, this.DiscrimType, 0); endif if (! isempty (this.Coeffs)) this.Coeffs = discrimcoeffs (this.Mu, Sg, Ld, this.Prior, ... this.DiscrimType, this.Delta, ... this.ClassNames); endif endfunction function this = set.Delta (this, val) if (! (isnumeric (val) && isscalar (val) && val >= 0)) error (strcat ("ClassificationDiscriminant: 'Delta' must be a", ... " nonnegative scalar.")); endif [~, fam] = discrimcanon (this.DiscrimType); if (val > 0 && strcmp (fam, 'quadratic')) error (strcat ("ClassificationDiscriminant: cannot eliminate", ... " linear predictors in a quadratic discriminant.")); endif this.Delta = val; if (! isempty (this.BaseSigma) && ! isempty (this.Coeffs)) this.Coeffs = discrimcoeffs (this.Mu, this.Sigma, this.LogDetSigma, ... this.Prior, this.DiscrimType, val, ... this.ClassNames); endif endfunction function this = set.ScoreTransform (this, val) [f, nm] = parseScoreTransform (val, 'ClassificationDiscriminant'); this.ScoreTransform = nm; this.STfun = f; endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n ClassificationDiscriminant\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); if (iscellstr (this.ClassNames)) str = repmat ({'''%s'''}, 1, numel (this.ClassNames)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.ClassNames{:}); elseif (ischar (this.ClassNames)) str = repmat ({'''%s'''}, 1, rows (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, cellstr (this.ClassNames){:}); else # single, double, logical str = repmat ({'%d'}, 1, numel (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.ClassNames); endif fprintf ("%+25s: %s\n", 'ClassNames', str); fprintf ("%+25s: '%s'\n", 'ScoreTransform', this.ScoreTransform); fprintf ("%+25s: %d\n", 'NumObservations', this.NumObservations); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); fprintf ("%+25s: '%s'\n", 'DiscrimType', this.DiscrimType); fprintf ("%+25s: [%dx%d double]\n", 'Mu', size (this.Mu)); ## Coeffs is KxK, which is Sigma's shape only for a linear discriminant. fprintf ("%+25s: [%dx%d struct]\n\n", 'Coeffs', ... rows (this.ClassNames), rows (this.ClassNames)); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} ClassificationDiscriminant (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{obj} =} ClassificationDiscriminant (@dots{}, @var{name}, @var{value}) ## ## Create a @qcode{ClassificationDiscriminant} class object containing a ## discriminant analysis model. ## ## @code{@var{obj} = ClassificationDiscriminant (@var{X}, @var{Y})} returns ## a ClassificationDiscriminant object, with @var{X} as the predictor data ## and @var{Y} containing the class labels of observations in @var{X}. ## ## @itemize ## @item ## @code{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. @var{X} will be used to train the discriminant model. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} can contain any type ## of categorical data. @var{Y} must have the same number of rows as ## @var{X}. ## @end itemize ## ## @code{@var{obj} = ClassificationDiscriminant (@dots{}, @var{name}, ## @var{value})} returns a ClassificationDiscriminant object with parameters ## specified by the following @qcode{@var{name}, @var{value}} paired input ## arguments: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PredictorNames'} @tab A cell array of character ## vectors specifying the names of the predictors. The length of this array ## must match the number of columns in @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector specifying the ## name of the response variable. ## ## @item @qcode{'ClassNames'} @tab Names of the classes in the class ## labels, @var{Y}, used for fitting the Discriminant model. ## @qcode{ClassNames} are of the same type as the class labels in @var{Y}. ## ## @item @qcode{'Cost'} @tab An @math{N*R} numeric matrix containing ## misclassification cost for the corresponding instances in @var{X}, where ## @math{R} is the number of unique categories in @var{Y}. If an instance ## is correctly classified into its category the cost is calculated to be 1, ## otherwise 0. The cost matrix can be altered by using ## @code{@var{Mdl}.cost = somecost}. By default, its value is ## @qcode{@var{cost} = ones (rows (X), numel (unique (Y)))}. ## ## @item @qcode{'Prior'} @tab A numeric vector specifying the prior ## probabilities for each class. The order of the elements in @qcode{Prior} ## corresponds to the order of the classes in @qcode{ClassNames}. ## Alternatively, you can specify @qcode{'empirical'} to use the empirical ## class probabilities or @qcode{'uniform'} to assume equal class ## probabilities. ## ## @item @qcode{'ScoreTransform'} @tab A user-defined function handle ## or a character vector specifying one of the following builtin functions ## specifying the transformation applied to predicted classification scores. ## Supported values include @qcode{'doublelogit'}, @qcode{'invlogit'}, ## @qcode{'ismax'}, @qcode{'logit'}, @qcode{'none'}, @qcode{'identity'}, ## @qcode{'sign'}, @qcode{'symmetric'}, @qcode{'symmetricismax'}, and ## @qcode{'symmetriclogit'}. ## ## @item @qcode{'DiscrimType'} @tab A character vector or string scalar ## specifying the type of discriminant analysis to perform. The only ## supported value is @qcode{'linear'}. ## ## @item @qcode{'FillCoeffs'} @tab A character vector or string scalar ## with values @qcode{'on'} or @qcode{'off'} specifying whether to fill the ## coefficients after fitting. If set to @qcode{'on'}, the coefficients are ## computed during model fitting, which can be useful for prediction. ## ## @item @qcode{'Gamma'} @tab A numeric scalar specifying the ## regularization parameter for the covariance matrix. It adjusts the linear ## discriminant analysis to make the model more stable in the presence of ## multicollinearity or small sample sizes. A value of 0 corresponds to no ## regularization, while a value of 1 corresponds to ## a completely regularized model. ## @end multitable ## ## @seealso{fitcdiscr} ## @end deftypefn function this = ClassificationDiscriminant (X, Y, varargin) ## Check for appropriate number of input arguments if (nargin < 2) error ("ClassificationDiscriminant: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationDiscriminant: Name-Value", ... " arguments must be in pairs.")); endif ## Validate X if (! isnumeric (X)) error ("ClassificationDiscriminant: X must be a numeric matrix."); endif ## Check X and Y have the same number of observations if (rows (X) != rows (Y)) error (strcat ("ClassificationDiscriminant: number", ... " of rows in X and Y must be equal.")); endif ## Assign original X and Y data this.X = X; this.Y = Y; ## Get groups in Y [gY, gnY, glY] = grp2idx (Y); ## Set default values before parsing optional parameters ClassNames = []; Cost = []; DiscrimType = 'linear'; Gamma = 0; Delta = 0; NumPredictors = []; PredictorNames = {}; ResponseName = 'Y'; Prior = 'empirical'; FillCoeffs = 'on'; ## Parse optional parameters while (numel (varargin) > 0) switch (lower (varargin{1})) case 'predictornames' PredictorNames = varargin{2}; if (! iscellstr (PredictorNames)) error (strcat ("ClassificationDiscriminant: 'PredictorNames'", ... " must be supplied as a cellstring array.")); elseif (numel (PredictorNames) != columns (X)) error (strcat ("ClassificationDiscriminant: 'PredictorNames'", ... " must equal the number of columns in X.")); endif case 'responsename' ResponseName = varargin{2}; if (! ischar (ResponseName)) error (strcat ("ClassificationDiscriminant: 'ResponseName'", ... " must be a character vector.")); endif case 'classnames' ClassNames = varargin{2}; if (! (iscellstr (ClassNames) || isnumeric (ClassNames) || islogical (ClassNames) || ischar (ClassNames))) error (strcat ("ClassificationDiscriminant: 'ClassNames'", ... " must be a cell array of character vectors,", ... " a logical vector, a numeric vector,", ... " or a character array.")); endif ## Check that all class names are available in gnY if (iscellstr (ClassNames) || ischar (ClassNames)) ClassNames = cellstr (ClassNames); if (! all (cell2mat (cellfun (@(x) any (strcmp (x, gnY)), ClassNames, 'UniformOutput', false)))) error (strcat ("ClassificationDiscriminant: not all", ... " 'ClassNames' are present in Y.")); endif else if (! all (cell2mat (arrayfun (@(x) any (x == glY), ClassNames, 'UniformOutput', false)))) error (strcat ("ClassificationDiscriminant: not all", ... " 'ClassNames' are present in Y.")); endif endif case 'prior' Prior = varargin{2}; if (! (isstruct (Prior) || (isnumeric (Prior) && isvector (Prior)) || (ischar (Prior) && (strcmpi (Prior, 'empirical') || strcmpi (Prior, 'uniform'))))) error (strcat ("ClassificationDiscriminant: 'Prior' must", ... " be either a numeric or a character vector.")); endif case 'cost' Cost = varargin{2}; ## A struct carrying its own class order is a cost too, ## and is resolved by the property's own set method. if (! (isstruct (Cost) || (isnumeric (Cost) && issquare (Cost)))) error (strcat ("ClassificationDiscriminant: 'Cost'", ... " must be a numeric square matrix.")); endif case 'scoretransform' this.ScoreTransform = varargin{2}; case 'discrimtype' DiscrimType = discrimcanon (varargin{2}); if (isempty (DiscrimType)) error (strcat ("ClassificationDiscriminant: 'DiscrimType'", ... " must be one of the following: linear,", ... " quadratic, diagLinear, diagQuadratic,", ... " pseudoLinear, or pseudoQuadratic.")); endif case 'fillcoeffs' FillCoeffs = tolower (varargin{2}); if (! any (strcmpi (FillCoeffs, {'on', 'off'}))) error (strcat ("ClassificationDiscriminant: 'FillCoeffs'", ... " must be 'on' or 'off'.")); endif case 'gamma' Gamma = varargin{2}; if (! (isnumeric (Gamma) && isscalar (Gamma) && Gamma >= 0 && Gamma <= 1)) error (strcat ("ClassificationDiscriminant: 'Gamma'", ... " must be a scalar between 0 and 1.")); endif case 'delta' Delta = varargin{2}; if (! (isnumeric (Delta) && isscalar (Delta) && Delta >= 0)) error (strcat ("ClassificationDiscriminant: 'Delta'", ... " must be a nonnegative scalar.")); endif otherwise error (strcat ("ClassificationDiscriminant: invalid", ... " parameter name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## Generate default predictors and response variable names (if necessary) NumPredictors = columns (X); if (isempty (PredictorNames)) for i = 1:NumPredictors PredictorNames {i} = strcat ("x", num2str (i)); endfor endif if (isempty (ResponseName)) ResponseName = 'Y'; endif ## Assign predictors and response variable names this.NumPredictors = NumPredictors; this.PredictorNames = PredictorNames; this.CategoricalPredictors = []; this.ExpandedPredictorNames = PredictorNames; this.ResponseName = ResponseName; ## Handle class names if (! isempty (ClassNames)) ## Anything textual is matched as whole names, gnY being grp2idx's ## own cellstr of them. A character matrix is not a cellstr, and ## ismember between two of them compares character by character, so ## it would answer a question nobody asked. if (iscellstr (ClassNames) || ischar (ClassNames)) ru = find (! ismember (gnY, cellstr (ClassNames))); else ru = find (! ismember (glY, ClassNames)); endif for i = 1:numel (ru) gY(gY == ru(i)) = NaN; endfor endif ## An observation is dropped only when its response is missing. A row ## whose predictors hold missing values is kept, and each estimate below ## uses whatever part of it is present. RowsUsed = ! isnan (gY); Y = Y(RowsUsed, :); X = X(RowsUsed, :); ## Store the retained observations this.X = X; this.Y = Y; ## Renew groups in Y, get classes ordered, keep the same type [this.ClassNames, gnY, gY] = uniqueLabels (Y); ## Check X contains valid data if (! (isnumeric (X) && ! any (isinf (X(:))))) error ("ClassificationDiscriminant: invalid values in X."); endif ## Assign the number of observations and their corresponding indices ## on the original data, which will be used for training the model, ## to the ClassificationDiscriminant object this.NumObservations = rows (X); ## RowsUsed is left empty when every observation was used, as in MATLAB if (all (RowsUsed)) this.RowsUsed = []; else this.RowsUsed = RowsUsed; endif ## Handle Cost and Prior this.Cost = Cost; this.Prior = Prior; ## A discriminant weighs every observation alike; the prior enters ## prediction rather than the fit, which is what MATLAB reports. this.W = ones (this.NumObservations, 1) / this.NumObservations; ## Assign DiscrimType ## Reconcile the type with the regularization before anything is ## estimated. Gamma of 1 IS the diagonal type, in both directions, and ## the quadratic family admits neither an intermediate Gamma nor a Delta. [~, fam] = discrimcanon (DiscrimType); if (Gamma == 1 && ! strncmp (DiscrimType, 'diag', 4)) if (strcmp (fam, 'linear')) DiscrimType = 'diagLinear'; else DiscrimType = 'diagQuadratic'; endif elseif (Gamma > 0 && Gamma < 1 && strcmp (fam, 'quadratic')) error (strcat ("ClassificationDiscriminant: cannot set 'Gamma' to", ... " any value but 0 or 1 for a quadratic discriminant.")); endif if (Delta > 0 && strcmp (fam, 'quadratic')) error (strcat ("ClassificationDiscriminant: cannot set linear", ... " coefficients to zero for a quadratic discriminant.")); endif this.DiscrimType = DiscrimType; this.Delta = Delta; this.Gamma = Gamma; num_classes = rows (this.ClassNames); num_features = columns (X); ## Each class mean uses every observation where that predictor is ## present, so a row missing one predictor still counts towards the rest this.Mu = zeros (num_classes, num_features); for i = 1:num_classes Xi = X(gY == i, :); for j = 1:num_features xj = Xi(:, j); this.Mu(i, j) = mean (xj(! isnan (xj))); endfor endfor ## The between-class covariance of the means about the overall mean, ## weighted by class size and divided by the unbiased weighted-covariance ## denominator. It reads the class sizes rather than Prior, which is why ## assigning a prior does not move it. nk = accumarray (gY(:), 1, [num_classes, 1]); pk = nk ./ sum (nk); D = this.Mu - pk' * this.Mu; this.BetweenSigma = (D' * (D .* nk)) ./ (sum (nk) * (1 - sum (pk .^ 2))); ## Center the predictors (XCentered), keeping the missing entries this.XCentered = X - this.Mu(gY, :); ## Estimate the covariance the family calls for. A linear family pools ## one covariance across the classes and a quadratic family estimates one ## per class; every type in a family is derived from what is estimated ## here, which is what lets DiscrimType be assigned after the fit without ## the model having to keep both. [~, fam] = discrimcanon (this.DiscrimType); cobs = ! any (isnan (X), 2); ## The pooled covariance is estimated whatever the family, because ## MinGamma is taken from it. Only complete observations enter it, ## reweighted so that each class keeps the total weight it carried ## before any was dropped. cw = zeros (rows (X), 1); Wk = zeros (num_classes, 1); for i = 1:num_classes Wk(i) = sum (gY == i) / rows (X); ci = (gY == i) & cobs; cw(ci) = Wk(i) / sum (ci); endfor den = 1; for i = 1:num_classes ci = (gY == i) & cobs; den -= sum (cw(ci) .^ 2) / Wk(i); endfor Zc = this.XCentered(cobs, :); pooled = (Zc .* cw(cobs))' * Zc / den; this.MinGamma = discrimmingamma (pooled); if (strcmp (fam, 'linear')) this.BaseSigma = pooled; ## A predictor with no within-class variance cannot be inverted, so the ## plain type refuses it and names the two types that can take it. zwcv = find (diag (this.BaseSigma) == 0); if (! isempty (zwcv) && strcmp (this.DiscrimType, 'linear')) error (strcat ("ClassificationDiscriminant: predictor '%s'", ... " has zero within-class variance. Either exclude", ... " this predictor or set 'DiscrimType' to", ... " 'pseudoLinear' or 'diagLinear'."), ... PredictorNames{zwcv(1)}); endif else this.BaseSigma = zeros (num_features, num_features, num_classes); for i = 1:num_classes ci = (gY == i) & cobs; Zi = this.XCentered(ci, :); this.BaseSigma(:,:,i) = (Zi' * Zi) / (sum (ci) - 1); zwcv = find (diag (this.BaseSigma(:,:,i)) == 0); if (! isempty (zwcv) && strcmp (this.DiscrimType, 'quadratic')) ## ClassNames keeps the type of Y, so the name is rendered rather ## than indexed as though it were always a cell. if (iscellstr (this.ClassNames)) cname = this.ClassNames{i}; elseif (ischar (this.ClassNames)) cname = this.ClassNames(i,:); else cname = num2str (this.ClassNames(i)); endif error (strcat ("ClassificationDiscriminant: predictor '%s' has", ... " zero variance for class '%s'. Either exclude", ... " this predictor or set 'DiscrimType' to", ... " 'pseudoQuadratic' or 'diagQuadratic'."), ... PredictorNames{zwcv(1)}, cname); endif endfor endif ## Derive Sigma, its log determinant and the regularization from the ## estimate above. The type and Gamma are one state, so both come back. [this.Sigma, this.LogDetSigma, this.Gamma] = ... discrimderive (this.BaseSigma, this.DiscrimType, this.Gamma, ... this.MinGamma); ## A singular class covariance is what the plain quadratic type cannot ## take; the pseudo and diagonal ones answer it. if (strcmp (this.DiscrimType, 'quadratic')) for i = 1:num_classes if (rcond (this.Sigma(:,:,i)) < num_features * eps) error (strcat ("ClassificationDiscriminant: cannot use", ... " 'quadratic' type because one or more classes", ... " have singular covariance matrices.")); endif endfor endif ## Delta is a threshold on the linear coefficients, so the quadratic ## family reports it as zeros and has nothing to eliminate. if (strcmp (fam, 'linear')) [~, ~, this.DeltaPredictor] = discrimlinear (this.Mu, this.Sigma, ... this.Prior, this.DiscrimType, 0); else this.DeltaPredictor = zeros (1, num_features); endif if (strcmpi (FillCoeffs, 'on')) this.Coeffs = discrimcoeffs (this.Mu, this.Sigma, this.LogDetSigma, ... this.Prior, this.DiscrimType, ... this.Delta, this.ClassNames); endif ## The fit as it was asked for: Gamma and Delta are the values given, ## which the properties of the same name do not always keep, this class ## forcing Gamma to 0 for a pseudo type and to 1 for a diagonal one. ## MATLAB reports the given value here. FillCoeffs is taken on and off ## and reported as the flag it is. this.ModelParameters = struct ( ... 'DiscrimType', this.DiscrimType, ... 'Gamma', Gamma, ... 'Delta', Delta, ... 'FillCoeffs', strcmpi (FillCoeffs, 'on'), ... 'Version', 1, ... 'Method', 'Discriminant', ... 'Type', 'classification'); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{n} =} nLinearCoeffs (@var{obj}) ## @deftypefnx {ClassificationDiscriminant} {@var{n} =} nLinearCoeffs (@var{obj}, @var{delta}) ## ## Number of nonzero linear coefficients at a regularization threshold. ## ## @code{@var{n} = nLinearCoeffs (@var{obj})} returns the number of ## predictors the discriminant keeps at its own @code{Delta}. ## ## @code{@var{n} = nLinearCoeffs (@var{obj}, @var{delta})} returns the ## number it would keep at each threshold in @var{delta}, as a column ## vector however @var{delta} is shaped. ## ## A predictor survives a threshold when its @code{DeltaPredictor} reaches ## it, the comparison including equality, so @var{delta} at exactly a ## predictor's own value still counts it. A threshold above every ## @code{DeltaPredictor} therefore leaves nothing and returns zero. ## ## The count is taken whatever the @code{DiscrimType}, as MATLAB takes it, ## even though @code{Delta} regularizes the linear types alone. ## ## @seealso{fitcdiscr, ClassificationDiscriminant, ## CompactClassificationDiscriminant} ## @end deftypefn function n = nLinearCoeffs (this, delta) if (nargin < 1 || nargin > 2) print_usage (); endif if (nargin < 2) delta = this.Delta; endif if (! (isnumeric (delta) && isreal (delta))) error (strcat ("ClassificationDiscriminant.nLinearCoeffs: DELTA", ... " must be a real numeric value.")); endif ## One column per threshold, one row per predictor, summed down. n = sum (this.DeltaPredictor(:) >= delta(:)', 1)'; endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{label} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {ClassificationDiscriminant} {[@var{label}, @var{score}, @var{cost}] =} predict (@var{obj}, @var{XC}) ## ## Classify new data points into categories using the discriminant ## analysis model from a ClassificationDiscriminant object. ## ## @code{@var{label} = predict (@var{obj}, @var{XC})} returns the vector of ## labels predicted for the corresponding instances in @var{XC}, using the ## predictor data in @code{obj.X} and corresponding labels, @code{obj.Y}, ## stored in the ClassificationDiscriminant model, @var{obj}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{ClassificationDiscriminant} class object. ## @item ## @var{XC} must be an @math{M*P} numeric matrix with the same number of ## features @math{P} as the corresponding predictors of the discriminant ## model in @var{obj}. ## @end itemize ## ## @code{[@var{label}, @var{score}, @var{cost}] = predict (@var{obj}, ## @var{XC})} also returns @var{score}, which contains the predicted class ## scores or posterior probabilities for each instance of the corresponding ## unique classes, and @var{cost}, which is a matrix containing the expected ## cost of the classifications. ## ## The @var{score} matrix contains the posterior probabilities for each ## class, calculated using the multivariate normal probability density ## function and the prior probabilities of each class. These scores are ## normalized to ensure they sum to 1 for each observation. ## ## The @var{cost} matrix contains the expected classification cost for each ## class, computed based on the posterior probabilities and the specified ## misclassification costs. ## ## @seealso{ClassificationDiscriminant, fitcdiscr} ## @end deftypefn function [label, score, cost] = predict (this, XC) ## Check for sufficient input arguments if (nargin < 2) error ("ClassificationDiscriminant.predict: too few input arguments."); endif ## Check for valid XC if (isempty (XC)) error ("ClassificationDiscriminant.predict: XC is empty."); elseif (columns (this.X) != columns (XC)) error (strcat ("ClassificationDiscriminant.predict: XC must have ", ... " the same number of predictors as the trained model.")); endif ## Get training data and labels X = this.X; Y = this.Y; numObservations = rows (XC); numClasses = rows (this.ClassNames); score = zeros (numObservations, numClasses); cost = zeros (numObservations, numClasses); ## Score from the inverse covariance and its log determinant rather than ## through mvnpdf. A pseudo type's covariance is deliberately singular ## and mvnpdf refuses it, and LogDetSigma has to be the value the ## property reports, so the score and the property cannot drift apart. [~, fam] = discrimcanon (this.DiscrimType); logscore = zeros (numObservations, numClasses); if (strcmp (fam, 'linear')) ## The linear family scores from its per-class coefficients, which is ## the same function with the terms common to every class dropped, and ## is the only form in which Delta can eliminate a predictor. [Z, b] = discrimlinear (this.Mu, this.Sigma, this.Prior, ... this.DiscrimType, this.Delta); logscore = XC * Z' + b; else SigmaInv = discriminv (this.Sigma, this.DiscrimType); nInv = size (SigmaInv, 3); logdet = this.LogDetSigma; if (isscalar (logdet)) logdet = repmat (logdet, numClasses, 1); endif for i = 1:numClasses Si = SigmaInv(:,:,min (i, nInv)); Zc = XC - this.Mu(i, :); logscore(:, i) = -0.5 * sum ((Zc * Si) .* Zc, 2) ... - 0.5 * logdet(i) + log (this.Prior(i)); endfor endif ## The shared 2*pi factor cancels in the normalization, and subtracting ## the row maximum first keeps a well separated observation a posterior ## rather than a ratio of two underflowed zeros. logscore = logscore - max (logscore, [], 2); score = exp (logscore); score = score ./ sum (score, 2); score(isnan (score)) = 0; ## Calculate expected classification cost for i = 1:numClasses cost(:, i) = sum (bsxfun (@times, score, this.Cost(:, i)'), 2); endfor ## Predict the class labels based on the minimum cost [~, minIdx] = min (cost, [], 2); label = this.ClassNames(minIdx,:); ## Apply ScoreTransform once to the whole matrix, after the label and ## the cost have been taken from the untransformed posteriors: a ## transform reshapes what predict reports and must not move what it ## decides. score = this.STfun (score); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationDiscriminant} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Compute loss for a trained ClassificationDiscriminant object. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} computes the loss, ## @var{L}, using the default loss function @qcode{'mincost'}. ## ## @itemize ## @item ## @code{obj} is a @var{ClassificationDiscriminant} object trained on ## @code{X} and @code{Y}. ## @item ## @code{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} must have same ## numbers of Rows as @var{X}. ## @end itemize ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} allows ## additional options specified by @var{name}-@var{value} pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'LossFun'} @tab Specifies the loss function to use. ## Can be a function handle with four input arguments (C, S, W, Cost) ## which returns a scalar value or one of: ## 'binodeviance', 'classifcost', 'classiferror', 'exponential', ## 'hinge', 'logit','mincost', 'quadratic'. ## @itemize ## @item ## @code{C} is a logical matrix of size @math{N*K}, where @math{N} is the ## number of observations and @math{K} is the number of classes. ## The element @code{C(i,j)} is true if the class label of the i-th ## observation is equal to the j-th class. ## @item ## @code{S} is a numeric matrix of size @math{N*K}, where each element ## represents the classification score for the corresponding class. ## @item ## @code{W} is a numeric vector of length @math{N}, representing ## the observation weights. ## @item ## @code{Cost} is a @math{K*K} matrix representing the misclassification ## costs. ## @end itemize ## ## @item @qcode{'Weights'} @tab Specifies observation weights, must be ## a numeric vector of length equal to the number of rows in X. ## Default is @code{ones (size (X, 1))}. loss normalizes the weights so that ## observation weights in each class sum to the prior probability of that ## class. When you supply Weights, loss computes the weighted ## classification loss. ## ## @end multitable ## ## @seealso{ClassificationDiscriminant} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("ClassificationDiscriminant.loss: too few input arguments."); elseif (mod (nargin - 3, 2) != 0) error (strcat ("ClassificationDiscriminant.loss: name-value", ... " arguments must be in pairs.")); elseif (nargin > 7) error ("ClassificationDiscriminant.loss: too many input arguments."); endif ## Check for valid X if (isempty (X)) error ("ClassificationDiscriminant.loss: X is empty."); elseif (columns (this.X) != columns (X)) error (strcat ("ClassificationDiscriminant.loss: X must have the", ... " same number of predictors as the trained model.")); endif ## Default values LossFun = 'mincost'; Weights = []; ## Validate Y valid_types = {'char', 'string', 'logical', 'single', 'double', 'cell'}; if (! (any (strcmp (class (Y), valid_types)))) error ("ClassificationDiscriminant.loss: Y must be of a valid type."); endif ## Validate size of Y if (size (Y, 1) != size (X, 1)) error (strcat ("ClassificationDiscriminant.loss: Y must", ... " have the same number of rows as X.")); endif ## Parse name-value arguments while (numel (varargin) > 0) Value = varargin{2}; switch (tolower (varargin{1})) case 'lossfun' lf_opt = {'binodeviance', 'classifcost', 'classiferror', ... 'exponential', 'hinge','logit', 'mincost', 'quadratic'}; if (isa (Value, 'function_handle')) ## Check if the loss function is valid if (nargin (Value) != 4) error (strcat ("ClassificationDiscriminant.loss: custom", ... " loss function must accept exactly four", ... " input arguments.")); endif try n = 1; K = 2; C_test = false (n, K); S_test = zeros (n, K); W_test = ones (n, 1); Cost_test = ones (K) - eye (K); test_output = Value(C_test, S_test, W_test, Cost_test); if (! isscalar (test_output)) error (strcat ("ClassificationDiscriminant.loss:", ... " custom loss function must return", ... " a scalar value.")); endif catch error (strcat ("ClassificationDiscriminant.loss: custom", ... " loss function is not valid or does not", ... " produce correct output.")); end_try_catch LossFun = Value; elseif (ischar (Value) && any (strcmpi (Value, lf_opt))) LossFun = Value; else error ("ClassificationDiscriminant.loss: invalid loss function."); endif case 'weights' if (isnumeric (Value) && isvector (Value)) if (numel (Value) != size (X ,1)) error (strcat ("ClassificationDiscriminant.loss: number", ... " of 'Weights' must be equal to the", ... " number of rows in X.")); elseif (numel (Value) == size (X, 1)) Weights = Value; endif else error ("ClassificationDiscriminant.loss: invalid 'Weights'."); endif otherwise error (strcat ("ClassificationDiscriminant.loss: invalid", ... " parameter name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## Check for missing values in X if (! isa (LossFun, 'function_handle')) lossfun = tolower (LossFun); if (! strcmp (lossfun, 'mincost') && ! strcmp (lossfun, 'classiferror') && ! strcmp (lossfun, 'classifcost') && any (isnan (X(:)))) L = NaN; return; endif endif ## If Y is a char array convert it to a cell array of character vectors classes = this.ClassNames; if (ischar (Y) && ischar (classes)) Y = cellstr (Y); classes = cellstr (classes); endif ## Check that Y is of the same type as ClassNames if (! strcmp (class (Y), class (classes))) error (strcat ("ClassificationDiscriminant.loss: Y must be", ... " the same data type as the model's ClassNames.")); endif ## Check if Y contains correct classes if (! labelsKnown (Y, classes)) error (strcat ("ClassificationDiscriminant.loss: Y must contain", ... " only the classes in model's ClassNames.")); endif ## Set default weights if not specified if (isempty (Weights)) Weights = ones (size (X, 1), 1); endif ## Normalize Weights K = classCount (classes); class_prior_probs = this.Prior; norm_weights = zeros (size (Weights)); for i = 1:K class_idx = ismember (Y, classes(i)); if (sum (Weights(class_idx)) > 0) norm_weights(class_idx) = ... Weights(class_idx) * class_prior_probs(i) / sum (Weights(class_idx)); endif endfor Weights = norm_weights / sum (norm_weights); ## Number of observations n = size (X, 1); ## Predict classification scores [label, scores] = predict (this, X); ## C is vector of K-1 zeros, with 1 in the ## position corresponding to the true class C = false (n, K); ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:n class_idx = gYidx(i); C(i, class_idx) = true; endfor Y_new = C'; ## Compute the loss using custom loss function if (isa (LossFun, 'function_handle')) L = LossFun(C, scores, Weights, this.Cost); return; endif ## Compute the scalar classification score for each observation m_j = zeros (n, 1); for i = 1:n m_j(i) = scores(i,:) * Y_new(:,i); endfor ## Compute the loss switch (tolower (LossFun)) case 'binodeviance' b = log (1 + exp (-2 * m_j)); L = (Weights') * b; case 'hinge' h = max (0, 1 - m_j); L = (Weights') * h; case 'exponential' e = exp (-m_j); L = (Weights') * e; case 'logit' l = log (1 + exp (-m_j)); L = (Weights') * l; case 'quadratic' q = (1 - m_j) .^ 2; L = (Weights') * q; case 'classiferror' L = 0; for i = 1:n L = L + Weights(i) * (! isequal (Y(i), label(i))); endfor case 'mincost' Cost = this.Cost; L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:n f_Xj = scores(i, :); gamma_jk = f_Xj * Cost; [~, min_cost_class] = min (gamma_jk); cj = Cost(gYidx(i), min_cost_class); L = L + Weights(i) * cj; endfor case 'classifcost' Cost = this.Cost; L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:n y_idx = gYidx(i); y_hat_idx = find (ismember (classes, label(i))); L = L + Weights(i) * Cost(y_idx, y_hat_idx); endfor otherwise error ("ClassificationDiscriminant.loss: invalid loss function."); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{m} =} margin (@var{obj}, @var{X}, @var{Y}) ## ## Classification margins for discriminant analysis classifier. ## ## @code{@var{m} = margin (@var{obj}, @var{X}, @var{Y})} returns ## the classification margins for @var{obj} with data @var{X} and ## classification @var{Y}. @var{m} is a numeric vector of length size (X,1). ## ## @itemize ## @item ## @code{obj} is a @var{ClassificationDiscriminant} object trained on ## @code{X} ## and @code{Y}. ## @item ## @code{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} must have same ## numbers of Rows as @var{X}. ## @end itemize ## ## The classification margin for each observation is the difference between ## the classification score for the true class and the maximal ## classification score for the false classes. ## ## @seealso{fitcdiscr, ClassificationDiscriminant} ## @end deftypefn function m = margin (this, X, Y) ## Check for sufficient input arguments if (nargin < 3) error ("ClassificationDiscriminant.margin: too few input arguments."); endif ## Check for valid X if (isempty (X)) error ("ClassificationDiscriminant.margin: X is empty."); elseif (columns (this.X) != columns (X)) error (strcat ("ClassificationDiscriminant.margin: X must have the", ... " same number of predictors as the trained model.")); endif ## Validate Y valid_types = {'char', 'string', 'logical', 'single', 'double', 'cell'}; if (! (any (strcmp (class (Y), valid_types)))) error ("ClassificationDiscriminant.margin: Y must be of a valid type."); endif ## Validate X valid_types = {'single', 'double'}; if (! (any (strcmp (class (X), valid_types)))) error ("ClassificationDiscriminant.margin: X must be of a valid type."); endif ## Validate size of Y if (size (Y, 1) != size (X, 1)) error (strcat ("ClassificationDiscriminant.margin: Y must", ... " have the same number of rows as X.")); endif ## If Y is a char array convert it to a cell array of character vectors classes = this.ClassNames; if (ischar (Y) && ischar (classes)) Y = cellstr (Y); classes = cellstr (classes); endif ## Check that Y is of the same type as ClassNames if (! strcmp (class (Y), class (classes))) error (strcat ("ClassificationDiscriminant.margin: Y must be", ... " the same data type as the model's ClassNames.")); endif ## Check if Y contains correct classes if (! labelsKnown (Y, classes)) error (strcat ("ClassificationDiscriminant.margin: Y must", ... " contain only the classes in model's ClassNames.")); endif ## Number of Observations n = size (X, 1); ## Initialize the margin vector m = zeros (n, 1); ## Calculate the classification scores [~, scores] = predict (this, X); ## Loop over each observation to compute the margin ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:n ## True class index true_class_idx = gYidx(i); ## Score for the true class true_class_score = scores(i, true_class_idx); ## Get the maximal score for the false classes scores(i, true_class_idx) = -Inf; # Temporarily max_false_class_score = max (scores(i, :)); if (max_false_class_score == -Inf) m = NaN; return; endif scores(i, true_class_idx) = true_class_score; # Restore ## Calculate the margin m(i) = true_class_score - max_false_class_score; endfor endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{CVMdl} =} crossval (@var{obj}) ## @deftypefnx {ClassificationDiscriminant} {@var{CVMdl} =} crossval (@dots{}, @var{Name}, @var{Value}) ## ## Cross Validate a Discriminant classification object. ## ## @code{@var{CVMdl} = crossval (@var{obj})} returns a cross-validated model ## object, @var{CVMdl}, from a trained model, @var{obj}, using 10-fold ## cross-validation by default. ## ## @code{@var{CVMdl} = crossval (@var{obj}, @var{name}, @var{value})} ## specifies additional name-value pair arguments to customize the ## cross-validation process. ## ## @multitable @columnfractions 0.28 0.7 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'KFold'} @tab Specify the number of folds to use in ## k-fold cross-validation. @code{"KFold", @var{k}}, where @var{k} is an ## integer greater than 1. ## ## @item @qcode{'Holdout'} @tab Specify the fraction of the data to ## hold out for testing. @code{"Holdout", @var{p}}, where @var{p} is a ## scalar in the range @math{(0,1)}. ## ## @item @qcode{'Leaveout'} @tab Specify whether to perform ## leave-one-out cross-validation. @code{"Leaveout", @var{Value}}, where ## @var{Value} is 'on' or 'off'. ## ## @item @qcode{'CVPartition'} @tab Specify a @qcode{cvpartition} ## object used for cross-validation. @code{"CVPartition", @var{cv}}, where ## @code{isa (@var{cv}, "cvpartition")} = 1. ## ## @end multitable ## ## @seealso{fitcdiscr, ClassificationDiscriminant, cvpartition, ## ClassificationPartitionedModel} ## @end deftypefn function CVMdl = crossval (this, varargin) ## Check input if (nargin < 1) error ("ClassificationDiscriminant.crossval: too few input arguments."); endif if (numel (varargin) == 1) error (strcat ("ClassificationDiscriminant.crossval: Name-Value", ... " arguments must be in pairs.")); elseif (numel (varargin) > 2) error (strcat ("ClassificationDiscriminant.crossval: specify only", ... " one of the optional Name-Value paired arguments.")); endif ## Add default values if (this.NumObservations < 10) numFolds = this.NumObservations; else numFolds = 10; endif Holdout = []; Leaveout = 'off'; CVPartition = []; ## Parse extra parameters while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'kfold' numFolds = varargin{2}; if (! (isnumeric (numFolds) && isscalar (numFolds) && (numFolds == fix (numFolds)) && numFolds > 1)) error (strcat ("ClassificationDiscriminant.crossval: 'KFold'", ... " must be an integer value greater than 1.")); endif case 'holdout' Holdout = varargin{2}; if (! (isnumeric (Holdout) && isscalar (Holdout) && Holdout > 0 && Holdout < 1)) error (strcat ("ClassificationDiscriminant.crossval: 'Holdout'", ... " must be a numeric value between 0 and 1.")); endif case 'leaveout' Leaveout = varargin{2}; if (! (ischar (Leaveout) && (strcmpi (Leaveout, 'on') || strcmpi (Leaveout, 'off')))) error (strcat ("ClassificationDiscriminant.crossval:", ... " 'Leaveout' must be either 'on' or 'off'.")); endif case 'cvpartition' CVPartition = varargin{2}; if (! (isa (CVPartition, 'cvpartition'))) error (strcat ("ClassificationDiscriminant.crossval:",... " 'CVPartition' must be a 'cvpartition' object.")); endif otherwise error (strcat ("ClassificationDiscriminant.crossval: invalid",... " parameter name in optional paired arguments.")); endswitch varargin(1:2) = []; endwhile ## Determine the cross-validation method to use. The partition covers ## the observations actually trained on: a row dropped for a missing ## value is not one the folds can use, and including it would leave the ## partition, the stored data and NumObservations disagreeing. The ## response is passed rather than a count so the folds stay stratified. Yused = this.Y; if (! isempty (CVPartition)) partition = CVPartition; elseif (! isempty (Holdout)) partition = cvpartition (Yused, 'Holdout', Holdout); elseif (strcmpi (Leaveout, 'on')) partition = cvpartition (numel (this.Y), 'LeaveOut'); else partition = cvpartition (Yused, 'KFold', numFolds); endif ## Create a cross-validated model object CVMdl = ClassificationPartitionedModel (this, partition); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{CVMdl} =} compact (@var{obj}) ## ## Create a CompactClassificationDiscriminant object. ## ## @code{@var{CVMdl} = compact (@var{obj})} creates a compact version of the ## ClassificationDiscriminant object, @var{obj}. ## ## @seealso{fitcdiscr, ClassificationDiscriminant, ## CompactClassificationDiscriminant} ## @end deftypefn function CVMdl = compact (this) ## Create a compact model CVMdl = CompactClassificationDiscriminant (this); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{e} =} edge (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationDiscriminant} {@var{e} =} edge (@dots{}, @qcode{"Weights"}, @var{w}) ## ## Classification edge, the mean of the classification margins. ## ## @code{@var{e} = edge (@var{obj}, @var{X}, @var{Y})} reduces the vector ## that @code{margin} returns to a single number, the mean margin over the ## rows of @var{X}. It says how far the model puts the true class ahead of ## its nearest rival on average, so a larger edge is a better model, and ## unlike a loss it is not bounded above and rewards confidence rather than ## bare correctness. ## ## @code{@var{e} = edge (@dots{}, @qcode{"Weights"}, @var{w})} takes the ## weighted mean instead, with one weight per row of @var{X}. ## ## @end deftypefn function e = edge (this, X, Y, varargin) if (nargin < 3) error ("ClassificationDiscriminant.edge: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationDiscriminant.edge: Name-Value", ... " arguments must be in pairs.")); endif ## The weights are parsed before anything is computed, so a bad ## Name-Value pair is reported as such rather than after a margin. W = edgeWeights (varargin, Y, this.ClassNames, this.Prior, ... "ClassificationDiscriminant", "edge"); m = margin (this, X, Y); e = sum (W .* m(:)) / sum (W); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{M} =} mahal (@var{obj}, @var{X}) ## @deftypefnx {ClassificationDiscriminant} {@var{M} =} mahal (@dots{}, @qcode{'ClassLabels'}, @var{labels}) ## ## Squared Mahalanobis distance to the class means. ## ## @code{@var{M} = mahal (@var{obj}, @var{X})} returns an @math{NxK} ## matrix whose element @math{(i,j)} is the squared Mahalanobis distance ## from observation @math{i} to the mean of class @math{j}, measured ## against the covariance that class carries: the one shared covariance ## for a linear discriminant and the class's own for a quadratic one. ## ## @itemize ## @item ## @var{obj} must be a @qcode{ClassificationDiscriminant} object. ## @item ## @var{X} must be an @math{NxP} numeric matrix with one column per ## predictor of the trained model. ## @end itemize ## ## @code{@var{M} = mahal (@dots{}, @qcode{'ClassLabels'}, @var{labels})} ## returns an @math{Nx1} vector instead, holding for each observation the ## distance to the mean of the class @var{labels} names for it. ## @var{labels} must have one entry per row of @var{X}, each of them one ## of @code{ClassNames}. ## ## The distance is measured against the covariance the model reports, so ## a regularized model is measured against its regularized covariance. ## The prior does not enter it. ## ## @end deftypefn function M = mahal (this, X, varargin) ## Check for sufficient input arguments if (nargin < 2) error (strcat ("ClassificationDiscriminant.mahal:", ... " too few input arguments.")); endif ## Check for valid X if (isempty (X)) error (strcat ("ClassificationDiscriminant.mahal:", ... " X is empty.")); elseif (! (isnumeric (X) && isreal (X))) error (strcat ("ClassificationDiscriminant.mahal:", ... " X must be a real numeric matrix.")); elseif (columns (this.X) != columns (X)) error (strcat ("ClassificationDiscriminant.mahal:", ... " X must have the same number of predictors as the", ... " trained model.")); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationDiscriminant.mahal:", ... " Name-Value arguments must be in pairs.")); endif labels = []; while (numel (varargin) > 0) if (! (ischar (varargin{1}) && isrow (varargin{1}))) error (strcat ("ClassificationDiscriminant.mahal:", ... " parameter name must be a character vector.")); endif switch (tolower (varargin{1})) case 'classlabels' labels = varargin{2}; otherwise error (strcat ("ClassificationDiscriminant.mahal:", ... " invalid parameter name in optional paired", ... " arguments.")); endswitch varargin(1:2) = []; endwhile M = discrimmahal (X, this.Mu, this.Sigma, this.DiscrimType); if (isempty (labels)) return; endif ## One distance per observation, to the mean of the class named for it if (ischar (labels)) labels = cellstr (labels); elseif (isnumeric (labels) || islogical (labels)) labels = cellstr (num2str (labels(:))); elseif (! iscellstr (labels)) error (strcat ("ClassificationDiscriminant.mahal:", ... " 'ClassLabels' must be of a valid type.")); endif if (numel (labels) != rows (X)) error (strcat ("ClassificationDiscriminant.mahal:", ... " 'ClassLabels' must have one entry per row of X.")); endif classes = this.ClassNames; if (isnumeric (classes) || islogical (classes)) classes = cellstr (num2str (classes(:))); elseif (ischar (classes)) classes = cellstr (classes); endif [tf, idx] = ismember (strtrim (labels(:)), strtrim (classes)); if (! all (tf)) error (strcat ("ClassificationDiscriminant.mahal:", ... " every 'ClassLabels' entry must be one of", ... " ClassNames.")); endif M = M(sub2ind (size (M), (1:rows (X))', idx)); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{lp} =} logp (@var{obj}, @var{X}) ## ## Log unconditional probability density of the observations. ## ## @code{@var{lp} = logp (@var{obj}, @var{X})} returns an @math{Nx1} ## vector holding, for each row of @var{X}, the natural logarithm of ## @math{P(x) = sum_k P(k) P(x|k)}, the density of the observation summed ## over the classes with each class weighted by its prior @math{P(k)}. ## Each @math{P(x|k)} is the multivariate normal density of class ## @math{k}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{ClassificationDiscriminant} object. ## @item ## @var{X} must be an @math{NxP} numeric matrix with one column per ## predictor of the trained model. ## @end itemize ## ## An unusually low value marks an observation the model finds unlikely ## under every class, which is what makes this an outlier test rather ## than a classification. ## ## @end deftypefn function lp = logp (this, X) ## Check for sufficient input arguments if (nargin < 2) error (strcat ("ClassificationDiscriminant.logp:", ... " too few input arguments.")); endif ## Check for valid X if (isempty (X)) error (strcat ("ClassificationDiscriminant.logp:", ... " X is empty.")); elseif (! (isnumeric (X) && isreal (X))) error (strcat ("ClassificationDiscriminant.logp:", ... " X must be a real numeric matrix.")); elseif (columns (this.X) != columns (X)) error (strcat ("ClassificationDiscriminant.logp:", ... " X must have the same number of predictors as the", ... " trained model.")); endif lp = discrimlogp (X, this.Mu, this.Sigma, this.LogDetSigma, ... this.Prior, this.DiscrimType); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{label} =} resubPredict (@var{obj}) ## @deftypefnx {ClassificationDiscriminant} {[@var{label}, @var{score}, @var{cost}] =} resubPredict (@var{obj}) ## ## Classify the training data with the model fitted to it. ## ## @code{@var{label} = resubPredict (@var{obj})} is @code{predict} applied ## to the observations the model was fitted on, which it holds in ## @qcode{X}. Handing them over yourself is not the same thing: a row ## dropped for a missing response is not in @qcode{X}, so the original ## matrix and the model's own are different data. ## ## The result measures fit and not generalization, and is optimistic by ## construction. @code{crossval} is what estimates performance on data the ## model has not seen. ## ## @end deftypefn function [label, score, cost] = resubPredict (this) [label, score, cost] = predict (this, this.X); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{m} =} resubMargin (@var{obj}) ## ## Classification margins of the model on its own training data. ## ## @code{@var{m} = resubMargin (@var{obj})} is @code{margin} applied to the ## observations the model was fitted on, one number per observation. Being ## a resubstitution quantity it is optimistic by construction. ## ## @end deftypefn function m = resubMargin (this) m = margin (this, this.X, this.Y); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{e} =} resubEdge (@var{obj}) ## ## Classification edge of the model on its own training data. ## ## @code{@var{e} = resubEdge (@var{obj})} is @code{edge} applied to the ## observations the model was fitted on, the mean of @code{resubMargin}. ## ## @end deftypefn function e = resubEdge (this) e = edge (this, this.X, this.Y); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{L} =} resubLoss (@var{obj}) ## @deftypefnx {ClassificationDiscriminant} {@var{L} =} resubLoss (@dots{}, @var{name}, @var{value}) ## ## Classification loss of the model on its own training data. ## ## @code{@var{L} = resubLoss (@var{obj})} is @code{loss} applied to the ## observations the model was fitted on, defaulting to ## @qcode{'mincost'}, and it accepts the same @qcode{Name-Value} pairs. ## ## Being a resubstitution quantity it is a lower bound on the error rather ## than an estimate of it. It is worth least on a lazy learner: a ## one-neighbour @code{ClassificationKNN} has a resubstitution loss of ## exactly zero, every training point being its own nearest neighbour. ## ## @end deftypefn function L = resubLoss (this, varargin) L = loss (this, this.X, this.Y, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {} savemodel (@var{obj}, @var{filename}) ## ## Save a ClassificationDiscriminant object. ## ## @code{savemodel (@var{obj}, @var{filename})} saves each property of a ## ClassificationDiscriminant object into an Octave binary file, the name of ## which is specified in @var{filename}, along with an extra variable, which ## defines the type classification object these variables constitute. Use ## @code{loadmodel} in order to load a classification object into Octave's ## workspace. ## ## @seealso{loadmodel, fitcdiscr, ClassificationDiscriminant} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error ("ClassificationDiscriminant.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error ("ClassificationDiscriminant.savemodel: FNAME must be a character vector."); endif ## Generate variable for class name classdef_name = 'ClassificationDiscriminant'; ## Create variables from model properties X = this.X; Y = this.Y; NumObservations = this.NumObservations; W = this.W; RowsUsed = this.RowsUsed; NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; ResponseName = this.ResponseName; ClassNames = this.ClassNames; Prior = this.Prior; Cost = this.Cost; ScoreTransform = this.ScoreTransform; Sigma = this.Sigma; BinEdges = this.BinEdges; BaseSigma = this.BaseSigma; Mu = this.Mu; Coeffs = this.Coeffs; Delta = this.Delta; DiscrimType = this.DiscrimType; Gamma = this.Gamma; MinGamma = this.MinGamma; LogDetSigma = this.LogDetSigma; XCentered = this.XCentered; BetweenSigma = this.BetweenSigma; ModelParameters = this.ModelParameters; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; STfun = this.STfun; ## Save classdef name and all model properties as individual variables HyperparameterOptimizationResults = this.HyperparameterOptimizationResults; save ('-binary', fname, 'classdef_name', 'X', 'Y', 'NumObservations', ... 'W', 'RowsUsed', 'NumPredictors', 'PredictorNames', 'BinEdges', ... 'ResponseName', ... 'ClassNames', 'ScoreTransform', 'Prior', 'Cost', 'Sigma', ... 'BaseSigma', 'Mu', ... 'Coeffs', 'Delta', 'DiscrimType', 'Gamma', 'MinGamma', ... 'LogDetSigma', 'XCentered', 'BetweenSigma', 'ModelParameters', ... 'CategoricalPredictors', 'ExpandedPredictorNames', 'STfun', ... 'HyperparameterOptimizationResults'); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationDiscriminant} {@var{err} =} cvshrink (@var{obj}) ## @deftypefnx {ClassificationDiscriminant} {[@var{err}, @var{gamma}] =} cvshrink (@var{obj}) ## @deftypefnx {ClassificationDiscriminant} {[@var{err}, @var{gamma}, @var{delta}] =} cvshrink (@var{obj}) ## @deftypefnx {ClassificationDiscriminant} {[@var{err}, @var{gamma}, @var{delta}, @var{numpred}] =} cvshrink (@var{obj}) ## @deftypefnx {ClassificationDiscriminant} {[@dots{}] =} cvshrink (@dots{}, @var{Name}, @var{Value}) ## ## Cross validate the regularization of a discriminant. ## ## @code{@var{err} = cvshrink (@var{obj})} cross validates @var{obj} over a ## grid of @qcode{Gamma} values and returns the misclassification rate at ## each of them, so that a regularization can be chosen by what it costs ## on held-out data rather than on the data it was fitted to. ## ## @code{[@var{err}, @var{gamma}, @var{delta}, @var{numpred}] = cvshrink ## (@var{obj})} also returns the grid itself and the number of predictors ## surviving at each point of it. @var{gamma} is a column with one entry ## per @qcode{Gamma}; @var{err}, @var{delta} and @var{numpred} carry one ## row per @qcode{Gamma} and one column per @qcode{Delta}. ## ## @multitable @columnfractions 0.28 0.72 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'NumGamma'} @tab The number of @qcode{Gamma} intervals, a ## positive integer, 10 by default, giving @qcode{NumGamma + 1} values ## evenly spaced from 0 to 1. ## ## @item @qcode{'NumDelta'} @tab The number of @qcode{Delta} intervals, a ## non-negative integer, 0 by default. For each @qcode{Gamma} the ## @qcode{Delta} values run from 0 to the point at which every predictor ## has been eliminated, so the grid is not the same in every row. ## ## @item @qcode{'Gamma'} @tab The @qcode{Gamma} values to try, given ## explicitly as a vector, in place of @qcode{'NumGamma'}. ## ## @item @qcode{'Delta'} @tab The @qcode{Delta} values to try, given ## explicitly, in place of @qcode{'NumDelta'}: a vector used for every ## @qcode{Gamma}, or a matrix with one row per @qcode{Gamma}. ## @end multitable ## ## Every point of the grid is cross validated against the same partition, ## so the errors differ by the regularization and not by the split. The ## partition is drawn at random, so @var{err} is not reproducible across ## runs and does not match MATLAB's; @var{gamma}, @var{delta} and ## @var{numpred} are deterministic and do. ## ## @seealso{ClassificationDiscriminant, fitcdiscr, nLinearCoeffs} ## @end deftypefn function [err, gamma, delta, numpred] = cvshrink (this, varargin) if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationDiscriminant.cvshrink: Name/Value", ... " arguments must be given in pairs.")); endif NumGamma = 10; NumDelta = 0; Gamma = []; Delta = []; for k = 1:2:numel (varargin) switch (lower (varargin{k})) case 'numgamma' NumGamma = varargin{k+1}; if (! (isnumeric (NumGamma) && isscalar (NumGamma) && NumGamma >= 1 && fix (NumGamma) == NumGamma)) error (strcat ("ClassificationDiscriminant.cvshrink:", ... " 'NumGamma' must be a positive integer.")); endif case 'numdelta' NumDelta = varargin{k+1}; if (! (isnumeric (NumDelta) && isscalar (NumDelta) && NumDelta >= 0 && fix (NumDelta) == NumDelta)) error (strcat ("ClassificationDiscriminant.cvshrink:", ... " 'NumDelta' must be a non-negative integer.")); endif case 'gamma' Gamma = varargin{k+1}; if (! (isnumeric (Gamma) && isvector (Gamma) && ! isempty (Gamma) && all (Gamma >= 0) && all (Gamma <= 1))) error (strcat ("ClassificationDiscriminant.cvshrink:", ... " 'Gamma' must be a vector of values between", ... " 0 and 1.")); endif case 'delta' Delta = varargin{k+1}; if (! (isnumeric (Delta) && ! isempty (Delta) && all (Delta(:) >= 0))) error (strcat ("ClassificationDiscriminant.cvshrink:", ... " 'Delta' must be non-negative.")); endif otherwise error (strcat ("ClassificationDiscriminant.cvshrink: unknown", ... " parameter name '%s'."), varargin{k}); endswitch endfor if (isempty (Gamma)) gamma = linspace (0, 1, NumGamma + 1)'; else gamma = Gamma(:); endif ng = numel (gamma); if (isempty (Delta)) nd = NumDelta + 1; elseif (isvector (Delta)) nd = numel (Delta); else if (rows (Delta) != ng) error (strcat ("ClassificationDiscriminant.cvshrink: a matrix", ... " 'Delta' must have one row per Gamma value, %d", ... " here, not %d."), ng, rows (Delta)); endif nd = columns (Delta); endif err = zeros (ng, nd); delta = zeros (ng, nd); numpred = zeros (ng, nd); ## One partition for the whole grid: comparing points cross validated ## against different splits would measure the split as much as the ## regularization. part = cvpartition (this.Y, "KFold", 10); for i = 1:ng m = this; m.Gamma = gamma(i); if (isempty (Delta)) ## Up to the point where the last predictor has gone, which moves ## with Gamma because regularizing the covariance moves the ## coefficients the threshold is applied to. dmax = max (m.DeltaPredictor); if (nd == 1) delta(i,:) = 0; else delta(i,:) = linspace (0, dmax, nd); endif elseif (isvector (Delta)) delta(i,:) = Delta(:)'; else delta(i,:) = Delta(i,:); endif for j = 1:nd mj = m; mj.Delta = delta(i,j); err(i,j) = kfoldLoss (crossval (mj, "CVPartition", part)); numpred(i,j) = nLinearCoeffs (mj, delta(i,j)); endfor endfor endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a ClassificationDiscriminant object ## Built without coefficients: every set method rebuilds Coeffs when it ## finds one, and a stub's would be rebuilt against half a loaded model. mdl = ClassificationDiscriminant (1, 1, 'FillCoeffs', 'off'); ## Copy the saved data into the object. Iterate over what was ## saved rather than over fieldnames (mdl): a private property such ## as STfun is written out by savemodel but is not reported by ## fieldnames, so comparing the two sets could never match and every ## load failed. Assignment is legal here because this is a method of ## the class itself. names = fieldnames (data); ## The set methods for these read other properties, and some of them ## rebuild Sigma or Coeffs, so they are assigned once everything else is ## in place rather than in the order the file happens to list them. ## Order matters within the late group as well: Prior and Cost before ## DiscrimType, because assigning the type rebuilds Coeffs and Coeffs ## reads the priors; and DiscrimType before Gamma, because assigning the ## type implies a Gamma and would overwrite the saved one if it came ## second. Coeffs is installed last of all: the set methods rebuild it ## whenever it is already there, and rebuilding it from a half-loaded ## model is how a diagonal Sigma met a linear formula. order = {'Cost', 'Prior', 'ScoreTransform', 'ResponseTransform', ... 'DiscrimType', 'Gamma', 'Delta', 'Coeffs'}; late = ismember (names, order); tail = {}; for k = 1:numel (order) if (any (strcmp (names, order{k}))) tail{end+1} = order{k}; endif endfor names = [names(! late); tail(:)]; for i = 1:numel (names) try mdl.(names{i}) = data.(names{i}); catch msg = 'ClassificationDiscriminant.load_model: invalid model in ''%s''.'; error (msg, filename); end_try_catch endfor endfunction endmethods methods(Access = private) endmethods endclassdef %!demo %! ## Create discriminant classifier %! ## Evaluate some model predictions on new data. %! %! load fisheriris %! x = meas; %! y = species; %! xc = [min(x); mean(x); max(x)]; %! obj = fitcdiscr (x, y); %! [label, score, cost] = predict (obj, xc); %!demo %! load fisheriris %! model = fitcdiscr (meas, species); %! X = mean (meas); %! Y = {'versicolor'}; %! ## Compute loss for discriminant model %! L = loss (model, X, Y) %!demo %! load fisheriris %! mdl = fitcdiscr (meas, species); %! X = mean (meas); %! Y = {'versicolor'}; %! ## Margin for discriminant model %! m = margin (mdl, X, Y) %!demo %! load fisheriris %! x = meas; %! y = species; %! obj = fitcdiscr (x, y, 'gamma', 0.4); %! ## Cross-validation for discriminant model %! CVMdl = crossval (obj) ## Test constructor %!test %! load fisheriris %! x = meas; %! y = species; %! PredictorNames = {'Sepal Length', 'Sepal Width', 'Petal Length', 'Petal Width'}; %! Mdl = ClassificationDiscriminant (x, y, 'PredictorNames', PredictorNames); %! sigma = [0.265008, 0.092721, 0.167514, 0.038401; ... %! 0.092721, 0.115388, 0.055244, 0.032710; ... %! 0.167514, 0.055244, 0.185188, 0.042665; ... %! 0.038401, 0.032710, 0.042665, 0.041882]; %! mu = [5.0060, 3.4280, 1.4620, 0.2460; ... %! 5.9360, 2.7700, 4.2600, 1.3260; ... %! 6.5880, 2.9740, 5.5520, 2.0260]; %! xCentered = [ 9.4000e-02, 7.2000e-02, -6.2000e-02, -4.6000e-02; ... %! -1.0600e-01, -4.2800e-01, -6.2000e-02, -4.6000e-02; ... %! -3.0600e-01, -2.2800e-01, -1.6200e-01, -4.6000e-02]; %! assert_equal (class (Mdl), "ClassificationDiscriminant"); %! assert_equal ({Mdl.X, Mdl.Y, Mdl.NumObservations}, {x, y, 150}) %! assert_equal ({Mdl.DiscrimType, Mdl.ResponseName}, {'linear', 'Y'}) %! assert_equal ({Mdl.Gamma, Mdl.MinGamma}, {0, 0}, 1e-15) %! assert_equal (Mdl.ClassNames, unique (species)) %! assert_equal (Mdl.Sigma, sigma, 1e-6) %! assert_equal (Mdl.Mu, mu, 1e-14) %! assert_equal (Mdl.XCentered([1:3],:), xCentered, 1e-14) %! assert_equal (Mdl.LogDetSigma, -9.9585, 1e-4) %! assert_equal (Mdl.PredictorNames, PredictorNames) %!test %! load fisheriris %! x = meas; %! y = species; %! Mdl = ClassificationDiscriminant (x, y, 'Gamma', 0.5); %! sigma = [0.265008, 0.046361, 0.083757, 0.019201; ... %! 0.046361, 0.115388, 0.027622, 0.016355; ... %! 0.083757, 0.027622, 0.185188, 0.021333; ... %! 0.019201, 0.016355, 0.021333, 0.041882]; %! mu = [5.0060, 3.4280, 1.4620, 0.2460; ... %! 5.9360, 2.7700, 4.2600, 1.3260; ... %! 6.5880, 2.9740, 5.5520, 2.0260]; %! xCentered = [ 9.4000e-02, 7.2000e-02, -6.2000e-02, -4.6000e-02; ... %! -1.0600e-01, -4.2800e-01, -6.2000e-02, -4.6000e-02; ... %! -3.0600e-01, -2.2800e-01, -1.6200e-01, -4.6000e-02]; %! assert_equal (class (Mdl), "ClassificationDiscriminant"); %! assert_equal ({Mdl.X, Mdl.Y, Mdl.NumObservations}, {x, y, 150}) %! assert_equal ({Mdl.DiscrimType, Mdl.ResponseName}, {'linear', 'Y'}) %! assert_equal ({Mdl.Gamma, Mdl.MinGamma}, {0.5, 0}) %! assert_equal (Mdl.ClassNames, unique (species)) %! assert_equal (Mdl.Sigma, sigma, 1e-6) %! assert_equal (Mdl.Mu, mu, 1e-14) %! assert_equal (Mdl.XCentered([1:3],:), xCentered, 1e-14) %! assert_equal (Mdl.LogDetSigma, -8.6884, 1e-4) ## Test input validation for constructor %!shared X, Y, MODEL %! X = rand (10,2); %! Y = [ones(5,1);2*ones(5,1)]; %! MODEL = ClassificationDiscriminant (X, Y); ## nLinearCoeffs counts the predictors a threshold keeps. R2024a on ## fisheriris reports 4 at the model's own Delta of zero. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! assert_equal (nLinearCoeffs (Mdl), 4); ## The comparison includes equality, which only a threshold sitting exactly ## on a DeltaPredictor can show: R2024a returns 4, 3, 2, 1 at the four ## sorted values, where a strict comparison would return 3, 2, 1, 0. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! dps = sort (Mdl.DeltaPredictor); %! assert_equal (nLinearCoeffs (Mdl, dps), [4; 3; 2; 1]); ## A threshold above every DeltaPredictor keeps nothing, and the result is a ## column however the thresholds were shaped. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! assert_equal (nLinearCoeffs (Mdl, [0, 1e6]), [4; 0]); %! assert_equal (size (nLinearCoeffs (Mdl, [0, 1, 2])), [3, 1]); ## The count is taken whatever the DiscrimType, as MATLAB takes it, even ## though Delta regularizes the linear types alone. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, "DiscrimType", "quadratic"); %! assert_equal (nLinearCoeffs (Mdl), 4); ## A response naming its classes in the rows of a character matrix is one ## of the documented types and MATLAB accepts it on every classifier. The ## whole surface below was broken and untested, which is why it stayed so. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcdiscr (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcdiscr (Xch, Ycell); %! assert_equal (size (Mc.ClassNames), [2, 10]); %! assert_equal (cellstr (Mc.ClassNames), Ms.ClassNames); ## predict returns whole names, not their first letters. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcdiscr (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcdiscr (Xch, Ycell); %! pch = predict (Mc, Xch); %! assert_equal (columns (pch), 10); %! assert_equal (cellstr (pch), predict (Ms, Xch)); ## loss, margin and edge read a character response as the same response. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcdiscr (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcdiscr (Xch, Ycell); %! assert_equal (loss (Mc, Xch, Ych), loss (Ms, Xch, Ycell), 1e-12); %! assert_equal (margin (Mc, Xch, Ych), margin (Ms, Xch, Ycell), 1e-12); %! assert_equal (edge (Mc, Xch, Ych), edge (Ms, Xch, Ycell), 1e-12); ## A character matrix pads its rows out to the longest name, and the padding ## is part of the name: R2024a reports ClassNames of ['ab '; 'abcd']. %!test %! Xpad = [1 2; 3 4; 1.1 2.1; 3.1 4.1; 1.2 2.2; 3.2 4.2]; %! Ypad = char ({"ab", "abcd", "ab", "abcd", "ab", "abcd"}); %! rand ("state", 1); randn ("state", 1); %! Mp = fitcdiscr (Xpad, Ypad); %! assert_equal (size (Mp.ClassNames), [2, 4]); %! assert_equal (Mp.ClassNames(1,:), "ab "); ## A row dropped for a missing predictor is the only case that exercises ## indexing the response by row rather than by element. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! Xmiss = Xch; Xmiss(3,2) = NaN; %! rand ("state", 1); randn ("state", 1); Md = fitcdiscr (Xmiss, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcdiscr (Xmiss, Ycell); %! assert_equal (size (Md.ClassNames), [2, 10]); %! assert_equal (cellstr (Md.ClassNames), Ms.ClassNames); ## ClassNames may itself be given as a character matrix, which selects the ## classes by whole name: ismember between two character matrices compares ## them character by character and would select by letter. %!test %! load fisheriris %! rand ("state", 1); randn ("state", 1); %! Mf = fitcdiscr (meas, char (species), ... %! "ClassNames", char ({"versicolor", "virginica"})); %! assert_equal (rows (Mf.ClassNames), 2); %! assert_equal (cellstr (Mf.ClassNames), {"versicolor"; "virginica"}); ## A model fitted from a character response comes back off disk unchanged. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcdiscr (Xch, Ych); %! fname = tempname (); %! savemodel (Mc, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.ClassNames, Mc.ClassNames); %! assert_equal (predict (M2, Xch), predict (Mc, Xch)); ## crossval carries a character response through cvpartition and back. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcdiscr (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcdiscr (Xch, Ycell); %! rand ("state", 2); cvc = crossval (Mc, "KFold", 3); %! rand ("state", 2); cvs = crossval (Ms, "KFold", 3); %! assert_equal (cellstr (kfoldPredict (cvc)), kfoldPredict (cvs)); %!error ... %! load fisheriris %! nLinearCoeffs (fitcdiscr (meas, species), "a") %!error ClassificationDiscriminant () %!error ... %! ClassificationDiscriminant (ones (4, 1)) %!error ... %! ClassificationDiscriminant (X, Y, 'prior') %!error ... %! ClassificationDiscriminant (ones (4,2), ones (1,4)) %!error ... %! ClassificationDiscriminant (X, Y, 'PredictorNames', ['A']) %!error ... %! ClassificationDiscriminant (X, Y, 'PredictorNames', 'A') %!error ... %! ClassificationDiscriminant (X, Y, 'PredictorNames', {'A', 'B', 'C'}) %!error ... %! ClassificationDiscriminant (X, Y, 'ResponseName', {'Y'}) %!error ... %! ClassificationDiscriminant (X, Y, 'ResponseName', 1) %!error ... %! ClassificationDiscriminant (X, Y, 'ClassNames', @(x)x) %!error ... %! ClassificationDiscriminant (X, Y, 'ClassNames', {1}) %!error ... %! ClassificationDiscriminant (X, ones (10,1), 'ClassNames', [1, 2]) %!error ... %! ClassificationDiscriminant ([1;2;3;4;5], ['a';'b';'a';'a';'b'], 'ClassNames', ['a';'c']) %!error ... %! ClassificationDiscriminant ([1;2;3;4;5], {'a';'b';'a';'a';'b'}, 'ClassNames', {'a','c'}) %!error ... %! ClassificationDiscriminant (X, logical (ones (10,1)), 'ClassNames', [true, false]) %!error ... %! ClassificationDiscriminant (X, Y, 'Prior', {'1', '2'}) %!error ... %! ClassificationDiscriminant (X, ones (10,1), 'Prior', [1 2]) ## What a misclassification cost may be, measured against MATLAB R2024a. ## The guard is shared by every class with a settable Cost; the battery is ## here, the other classes each check that they are behind it. %!test %! ## A single cost is widened to double rather than refused %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = single ([0, 1, 2; 3, 0, 4; 5, 6, 0]); %! assert_equal (class (Mdl.Cost), 'double'); %! assert_equal (Mdl.Cost, [0, 1, 2; 3, 0, 4; 5, 6, 0]); %!test %! ## A cost need not be symmetric, and is stored as it was given %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = [0, 1, 2; 3, 0, 4; 5, 6, 0]; %! assert_equal (Mdl.Cost, [0, 1, 2; 3, 0, 4; 5, 6, 0]); %!test %! ## Complex means a nonzero imaginary part: a zero one is accepted and %! ## stored as a double, so the guard is not ! isreal %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = complex ([0, 1, 2; 3, 0, 4; 5, 6, 0], 0); %! assert_equal (class (Mdl.Cost), 'double'); %! assert_equal (Mdl.Cost, [0, 1, 2; 3, 0, 4; 5, 6, 0]); %!test %! ## A struct names the order its matrix is written in, and the matrix is %! ## permuted into the model's order entry by entry %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! S = struct ('ClassNames', {{'virginica'; 'setosa'; 'versicolor'}}, ... %! 'ClassificationCosts', [0, 1, 2; 3, 0, 4; 5, 6, 0]); %! Mdl.Cost = S; %! assert_equal (Mdl.Cost, [0, 4, 3; 6, 0, 5; 1, 2, 0]); %!test %! ## A struct already in the model's order permutes to itself %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! S = struct ('ClassNames', {{'setosa'; 'versicolor'; 'virginica'}}, ... %! 'ClassificationCosts', [0, 1, 2; 3, 0, 4; 5, 6, 0]); %! Mdl.Cost = S; %! assert_equal (Mdl.Cost, [0, 1, 2; 3, 0, 4; 5, 6, 0]); %!test %! ## The constructor takes the struct form too %! load fisheriris %! S = struct ('ClassNames', {{'virginica'; 'setosa'; 'versicolor'}}, ... %! 'ClassificationCosts', [0, 1, 2; 3, 0, 4; 5, 6, 0]); %! Mdl = fitcdiscr (meas, species, 'Cost', S); %! assert_equal (Mdl.Cost, [0, 4, 3; 6, 0, 5; 1, 2, 0]); %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = int32 ([0, 1, 2; 3, 0, 4; 5, 6, 0]); %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = ! logical (eye (3)); %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = sparse ([0, 1, 2; 3, 0, 4; 5, 6, 0]); %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = [0, 1, 2; 3, 0, 4i; 5, 6, 0]; %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = [0, -1, 2; 3, 0, 4; 5, 6, 0]; %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = ones (3); %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = [0, 1, 2; 3, 0, NaN; 5, 6, 0]; %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = [0, 1, 2; 3, 0, Inf; 5, 6, 0]; %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = struct ('ClassificationCosts', [0, 1, 2; 3, 0, 4; 5, 6, 0]); %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = struct ('ClassNames', {{'setosa'; 'versicolor'}}, ... %! 'ClassificationCosts', [0, 1; 2, 0]); %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = struct ('ClassNames', {{'setosa'; 'versicolor'; 'virginica'}}, ... %! 'ClassificationCosts', [0, 1; 2, 0]); %!error ... %! ClassificationDiscriminant (X, Y, 'Cost', [1, 2]) %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Cost = 1:9; %!error ... %! ClassificationDiscriminant (X, Y, 'Cost', 'string') %!error ... %! ClassificationDiscriminant (X, Y, 'Cost', {eye(2)}) %!error ... %! ClassificationDiscriminant (X, Y, 'Cost', ones (3)) %!error ... %! ClassificationDiscriminant (ones (5,2), [1; 1; 2; 2; 2]) %!error ... %! ClassificationDiscriminant (ones (5,2), [1; 1; 2; 2; 2], 'PredictorNames', {'A', 'B'}) %!error ... %! ClassificationDiscriminant ([1,2;2,2;3,2;4,2;5,2], ones (5, 1)) %!error ... %! ClassificationDiscriminant ([1,2;2,2;3,2;4,2;5,2], ones (5, 1), 'PredictorNames', {'A', 'B'}) ## Test predict method %!test %! load fisheriris %! x = meas; %! y = species; %! Mdl = fitcdiscr (meas, species, 'Gamma', 0.5); %! [label, score, cost] = predict (Mdl, [2, 2, 2, 2]); %! assert_equal (label, {'versicolor'}) %! assert_equal (score, [0, 0.9999, 0.0001], 1e-4) %! assert_equal (cost, [1, 0.0001, 0.9999], 1e-4) %! [label, score, cost] = predict (Mdl, [2.5, 2.5, 2.5, 2.5]); %! assert_equal (label, {'versicolor'}) %! assert_equal (score, [0, 0.6368, 0.3632], 1e-4) %! assert_equal (cost, [1, 0.3632, 0.6368], 1e-4) %!test %! load fisheriris %! x = meas; %! y = species; %! xc = [min(x); mean(x); max(x)]; %! Mdl = fitcdiscr (x, y); %! [label, score, cost] = predict (Mdl, xc); %! l = {'setosa'; 'versicolor'; 'virginica'}; %! s = [1, 0, 0; 0, 1, 0; 0, 0, 1]; %! c = [0, 1, 1; 1, 0, 1; 1, 1, 0]; %! assert_equal (label, l) %! assert_equal (score, s, 1e-4) %! assert_equal (cost, c, 1e-4) ## Test input validation for predict method %!error ... %! predict (MODEL) %!error ... %! predict (MODEL, []) %!error ... %! predict (MODEL, 1) ## Test loss method %!test %! load fisheriris %! model = fitcdiscr (meas, species); %! x = mean (meas); %! y = {'versicolor'}; %! L = loss (model, x, y); %! assert_equal (L, 0) %!test %! x = [1, 2; 3, 4; 5, 6]; %! y = {'A'; 'B'; 'A'}; %! model = fitcdiscr (x, y, 'Gamma', 0.4); %! x_test = [1, 6; 3, 3]; %! y_test = {'A'; 'B'}; %! L = loss (model, x_test, y_test); %! assert_equal (L, 0.3333, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6; 7, 8]; %! y = ['1'; '2'; '3'; '1']; %! model = fitcdiscr (x, y, 'gamma' , 0.5); %! x_test = [3, 3]; %! y_test = ['1']; %! L = loss (model, x_test, y_test, 'LossFun', 'quadratic'); %! assert_equal (L, 0.2423, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6; 7, 8]; %! y = ['1'; '2'; '3'; '1']; %! model = fitcdiscr (x, y, 'gamma' , 0.5); %! x_test = [3, 3; 5, 7]; %! y_test = ['1'; '2']; %! L = loss (model, x_test, y_test, 'LossFun', 'classifcost'); %! assert_equal (L, 0.3333, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6; 7, 8]; %! y = ['1'; '2'; '3'; '1']; %! model = fitcdiscr (x, y, 'gamma' , 0.5); %! x_test = [3, 3; 5, 7]; %! y_test = ['1'; '2']; %! L = loss (model, x_test, y_test, 'LossFun', 'hinge'); %! assert_equal (L, 0.5886, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6; 7, 8]; %! y = ['1'; '2'; '3'; '1']; %! model = fitcdiscr (x, y, 'gamma' , 0.5); %! x_test = [3, 3; 5, 7]; %! y_test = ['1'; '2']; %! W = [1; 2]; %! L = loss (model, x_test, y_test, 'LossFun', 'logit', 'Weights', W); %! assert_equal (L, 0.5107, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6]; %! y = {'A'; 'B'; 'A'}; %! model = fitcdiscr (x, y, 'gamma' , 0.5); %! x_with_nan = [1, 2; NaN, 4]; %! y_test = {'A'; 'B'}; %! L = loss (model, x_with_nan, y_test); %! assert_equal (L, 0.3333, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6]; %! y = {'A'; 'B'; 'A'}; %! model = fitcdiscr (x, y); %! x_with_nan = [1, 2; NaN, 4]; %! y_test = {'A'; 'B'}; %! L = loss (model, x_with_nan, y_test, 'LossFun', 'logit'); %! assert_equal (isnan (L), true) %!test %! x = [1, 2; 3, 4; 5, 6]; %! y = {'A'; 'B'; 'A'}; %! model = fitcdiscr (x, y); %! customLossFun = @(C, S, W, Cost) sum (W .* sum (abs (C - S), 2)); %! L = loss (model, x, y, 'LossFun', customLossFun); %! assert_equal (L, 0.8889, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6]; %! y = [1; 2; 1]; %! model = fitcdiscr (x, y); %! L = loss (model, x, y, 'LossFun', 'classiferror'); %! assert_equal (L, 0.3333, 1e-4) ## Test input validation for loss method %!error ... %! loss (MODEL) %!error ... %! loss (MODEL, ones (4,2)) %!error ... %! loss (MODEL, [], zeros (2)) %!error ... %! loss (MODEL, 1, zeros (2)) %!error ... %! loss (MODEL, ones (4,2), ones (4,1), 'LossFun') %!error ... %! loss (MODEL, ones (4,2), ones (3,1)) %!error ... %! loss (MODEL, ones (4,2), ones (4,1), 'LossFun', 'a') %!error ... %! loss (MODEL, ones (4,2), ones (4,1), 'Weights', 'w') ## Test margin method %! load fisheriris %! mdl = fitcdiscr (meas, species); %! X = mean (meas); %! Y = {'versicolor'}; %! m = margin (mdl, X, Y); %! assert_equal (m, 1, 1e-6) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = [1; 2; 1]; %! mdl = fitcdiscr (X, Y, 'gamma', 0.5); %! m = margin (mdl, X, Y); %! assert_equal (m, [0.3333; -0.3333; 0.3333], 1e-4) ## Test input validation for margin method %!error ... %! margin (MODEL) %!error ... %! margin (MODEL, ones (4,2)) %!error ... %! margin (MODEL, [], zeros (2)) %!error ... %! margin (MODEL, 1, zeros (2)) %!error ... %! margin (MODEL, ones (4,2), ones (3,1)) ## Test crossval method %!shared x, y, obj %! load fisheriris %! x = meas; %! y = species; %! obj = fitcdiscr (x, y, 'gamma', 0.4); %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (obj); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (CVMdl.KFold == 10, true) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationDiscriminant") %! assert_equal (CVMdl.CrossValidatedModel, "Discriminant") %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (obj, 'KFold', 3); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (CVMdl.KFold == 3, true) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationDiscriminant") %! assert_equal (CVMdl.CrossValidatedModel, "Discriminant") %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (obj, 'HoldOut', 0.2); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationDiscriminant") %! assert_equal (CVMdl.CrossValidatedModel, "Discriminant") %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (obj, 'LeaveOut', 'on'); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationDiscriminant") %! assert_equal (CVMdl.CrossValidatedModel, "Discriminant") %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! partition = cvpartition (y, 'KFold', 3); %! warning (status); %! CVMdl = crossval (obj, 'cvPartition', partition); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal (CVMdl.KFold == 3, true) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationDiscriminant") %! assert_equal (CVMdl.CrossValidatedModel, "Discriminant") ## Test input validation for crossval method %!error ... %! crossval (obj, 'kfold') %!error... %! crossval (obj, 'kfold', 12, 'holdout', 0.2) %!error ... %! crossval (obj, 'kfold', 'a') %!error ... %! crossval (obj, 'holdout', 2) %!error ... %! crossval (obj, 'leaveout', 1) %!error ... %! crossval (obj, 'cvpartition', 1) %!error ... %! savemodel (ClassificationDiscriminant ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])) %!error ... %! savemodel (ClassificationDiscriminant ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), 1) %!error ... %! savemodel (ClassificationDiscriminant ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), ['ab'; 'cd']) ## A ScoreTransform can be assigned, and is stored as a function handle. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.ScoreTransform = 'symmetric'; %! assert_equal (class (Mdl.ScoreTransform), 'char'); %! assert_equal (Mdl.ScoreTransform, 'symmetric'); ## RowsUsed is empty when every observation was used. %!test %! load fisheriris %! X = meas; %! Y = grp2idx (species); %! Mdl = fitcdiscr (X, Y); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (class (Mdl.RowsUsed), 'double'); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (rows (Mdl.X), 150); ## A missing response drops its observation and RowsUsed marks it. %!test %! load fisheriris %! X = meas; %! Y = grp2idx (species); %! Y(5) = NaN; %! Mdl = fitcdiscr (X, Y); %! assert_equal (class (Mdl.RowsUsed), 'logical'); %! assert_equal (size (Mdl.RowsUsed), [150, 1]); %! assert_equal (sum (Mdl.RowsUsed), 149); %! assert_equal (Mdl.RowsUsed(5), false); %! assert_equal (Mdl.NumObservations, 149); %! assert_equal (rows (Mdl.X), 149); ## A missing predictor keeps its observation, so RowsUsed stays empty. %!test %! load fisheriris %! X = meas; %! X(3,2) = NaN; %! Y = grp2idx (species); %! Mdl = fitcdiscr (X, Y); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (rows (Mdl.X), 150); %! assert_equal (sum (isnan (Mdl.X(:))), 1); ## Prior is a row of class frequencies and W stays uniform: a discriminant ## applies the prior when it predicts, not to the fit. Values from R2024a. %!test %! load fisheriris %! i3 = [1:50, 51:80, 101:120]; %! Mdl = fitcdiscr (meas(i3,:), species(i3)); %! assert_equal (size (Mdl.Prior), [1, 3]); %! assert_equal (Mdl.Prior, [0.5, 0.3, 0.2], 1e-14); %! assert_equal (Mdl.W, ones (100, 1) / 100, 1e-14); ## A uniform prior leaves a discriminant's weights alone. %!test %! load fisheriris %! i3 = [1:50, 51:80, 101:120]; %! Mdl = fitcdiscr (meas(i3,:), species(i3), 'Prior', 'uniform'); %! assert_equal (Mdl.Prior, [1, 1, 1] / 3, 1e-14); %! assert_equal (Mdl.W, ones (100, 1) / 100, 1e-14); ## A structure Prior assigns by class name, whatever order it names them in. %!test %! load fisheriris %! i3 = [1:50, 51:80, 101:120]; %! p = struct ('ClassNames', {{'setosa'; 'virginica'; 'versicolor'}}, ... %! 'ClassProbs', [0.2, 0.3, 0.5]); %! Mdl = fitcdiscr (meas(i3,:), species(i3), 'Prior', p); %! assert_equal (Mdl.Prior, [0.2, 0.5, 0.3], 1e-14); ## An unnormalized Prior is rescaled on assignment. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Prior = [2, 3, 5]; %! assert_equal (Mdl.Prior, [0.2, 0.3, 0.5], 1e-14); %! Mdl.Cost = [0, 2, 3; 1, 0, 1; 1, 1, 0]; %! assert_equal (Mdl.Cost, [0, 2, 3; 1, 0, 1; 1, 1, 0]); ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'ClassificationDiscriminant'); %! assert_equal (M2.NumObservations, Mdl.NumObservations); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ScoreTransform), class (Mdl.ScoreTransform)); %! assert_equal (predict (M2, meas(1:5,:)), predict (Mdl, meas(1:5,:))); ## The six discriminant types. Every value below was measured on MATLAB ## R2024a; see DISCRIMINANT_LEDGER.md for the probes that produced them. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'linear'); %! assert_equal (Mdl.DiscrimType, 'linear'); %! assert_equal (size (Mdl.Sigma), [4, 4]); %! assert_equal (Mdl.LogDetSigma, -9.9585, 1e-4); %! assert_equal (Mdl.Gamma, 0); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'quadratic'); %! assert_equal (Mdl.DiscrimType, 'quadratic'); %! assert_equal (size (Mdl.Sigma), [4, 4, 3]); %! assert_equal (Mdl.LogDetSigma', [-13.0674, -10.8743, -8.9271], 1e-4); %! assert_equal (Mdl.Gamma, 0); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'diagLinear'); %! assert_equal (Mdl.DiscrimType, 'diagLinear'); %! assert_equal (size (Mdl.Sigma), [1, 4]); %! assert_equal (Mdl.LogDetSigma, -8.3467, 1e-4); %! assert_equal (Mdl.Gamma, 1); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'diagQuadratic'); %! assert_equal (Mdl.DiscrimType, 'diagQuadratic'); %! assert_equal (size (Mdl.Sigma), [1, 4, 3]); %! assert_equal (Mdl.LogDetSigma', [-12.0271, -8.3925, -6.9421], 1e-4); %! assert_equal (Mdl.Gamma, 1); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'pseudoLinear'); %! assert_equal (Mdl.DiscrimType, 'pseudoLinear'); %! assert_equal (size (Mdl.Sigma), [4, 4]); %! assert_equal (Mdl.LogDetSigma, -9.9585, 1e-4); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'pseudoQuadratic'); %! assert_equal (Mdl.DiscrimType, 'pseudoQuadratic'); %! assert_equal (size (Mdl.Sigma), [4, 4, 3]); %! assert_equal (Mdl.LogDetSigma', [-13.0674, -10.8743, -8.9271], 1e-4); ## The posterior of the one iris the model is unsure about separates the six ## types from one another, which a correctly classified row does not. %!test %! load fisheriris %! [~, s] = predict (fitcdiscr (meas, species), meas(71,:)); %! assert_equal (s, [0, 0.2532, 0.7468], 1e-4); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'quadratic'); %! [~, s] = predict (Mdl, meas(71,:)); %! assert_equal (s, [0, 0.3359, 0.6641], 1e-4); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'diagLinear'); %! [~, s] = predict (Mdl, meas(71,:)); %! assert_equal (s, [0, 0.2646, 0.7354], 1e-4); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'diagQuadratic'); %! [~, s] = predict (Mdl, meas(71,:)); %! assert_equal (s, [0, 0.1609, 0.8391], 1e-4); ## Exact coefficients on a fixture small enough that every figure is closed ## form: the pooled covariance is 13.75/6 and each class covariance n_k-1. %!test %! x = [1, 2; 2, 1; 3, 4; 4, 3; 5, 7; 7, 5; 8, 9; 9, 8]; %! y = [1; 1; 1; 1; 2; 2; 2; 2]; %! Mdl = fitcdiscr (x, y, 'DiscrimType', 'linear'); %! assert_equal (Mdl.Sigma, [2.2917, 1.125; 1.125, 2.2917], 1e-4); %! assert_equal (Mdl.LogDetSigma, 1.3828, 1e-4); %! assert_equal (Mdl.Coeffs(1,2).Const, 13.5549, 1e-4); %! assert_equal (Mdl.Coeffs(1,2).Linear', [-1.3902, -1.3902], 1e-4); %!test %! x = [1, 2; 2, 1; 3, 4; 4, 3; 5, 7; 7, 5; 8, 9; 9, 8]; %! y = [1; 1; 1; 1; 2; 2; 2; 2]; %! Mdl = fitcdiscr (x, y, 'DiscrimType', 'quadratic'); %! assert_equal (Mdl.Sigma(:,:,1), [1.6667, 1; 1, 1.6667], 1e-4); %! assert_equal (Mdl.Coeffs(1,2).Const, 10.9525, 1e-4); %! assert_equal (Mdl.Coeffs(1,2).Linear', [-0.8025, -0.8025], 1e-4); %! assert_equal (Mdl.Coeffs(1,2).Quadratic, ... %! [-0.2587, 0.1912; 0.1912, -0.2587], 1e-4); %!test %! x = [1, 2; 2, 1; 3, 4; 4, 3; 5, 7; 7, 5; 8, 9; 9, 8]; %! y = [1; 1; 1; 1; 2; 2; 2; 2]; %! Mdl = fitcdiscr (x, y, 'DiscrimType', 'diagQuadratic'); %! assert_equal (Mdl.Sigma(:,:,1), [1.6667, 1.6667], 1e-4); %! assert_equal (Mdl.Coeffs(1,2).Const, 14.8310, 1e-4); %! assert_equal (Mdl.Coeffs(1,2).Quadratic, [-0.1286, -0.1286], 1e-4); ## The quadratic family reports a Quadratic field and the linear family does ## not, which is how a user tells the two apart from the structure alone. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! assert_equal (fieldnames (Mdl.Coeffs)', ... %! {'DiscrimType', 'Const', 'Linear', 'Class1', 'Class2'}); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'quadratic'); %! assert_equal (fieldnames (Mdl.Coeffs)', {'DiscrimType', 'Const', ... %! 'Linear', 'Quadratic', 'Class1', 'Class2'}); ## DiscrimType is assignable within its family and re-derives the covariance. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.DiscrimType = 'diagLinear'; %! assert_equal (size (Mdl.Sigma), [1, 4]); %! assert_equal (Mdl.Gamma, 1); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'diagLinear'); %! Mdl.DiscrimType = 'linear'; %! assert_equal (size (Mdl.Sigma), [4, 4]); %! assert_equal (Mdl.Gamma, 0); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'quadratic'); %! Mdl.DiscrimType = 'diagQuadratic'; %! assert_equal (size (Mdl.Sigma), [1, 4, 3]); ## Assigning the type rebuilds the coefficients rather than leaving the ones ## the previous type produced. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! before = Mdl.Coeffs(1,2).Const; %! Mdl.DiscrimType = 'diagLinear'; %! assert (abs (Mdl.Coeffs(1,2).Const - before) > 1); ## Gamma and the diagonal type are one state, in both directions. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Gamma = 1; %! assert_equal (Mdl.DiscrimType, 'diagLinear'); %! assert_equal (size (Mdl.Sigma), [1, 4]); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'quadratic'); %! Mdl.Gamma = 1; %! assert_equal (Mdl.DiscrimType, 'diagQuadratic'); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'Gamma', 1); %! assert_equal (Mdl.DiscrimType, 'diagLinear'); ## An intermediate Gamma shrinks the covariance towards its diagonal, which ## the resubstitution error follows. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Gamma = 0.25; %! assert_equal (loss (Mdl, meas, species), 0.0267, 1e-4); ## A collinear fit is raised to MinGamma rather than failing, and cannot be ## brought back below it. MinGamma lands a few eps above zero, so ## LogDetSigma is the logarithm of a rounding-level eigenvalue: it moves by ## 0.02 between platforms where doubling MinGamma moves it by log (2). %!test %! load fisheriris %! Mdl = fitcdiscr ([meas(:,1:3), meas(:,3)], species); %! assert (Mdl.MinGamma > 0); %! assert_equal (Mdl.Gamma, Mdl.MinGamma); %! assert_equal (Mdl.LogDetSigma, -41.3112, 0.1); ## The pseudo types answer a singular covariance where the plain one is ## regularized and the diagonal one drops the correlations. %!test %! load fisheriris %! Mdl = fitcdiscr ([meas(:,1:3), meas(:,3)], species, ... %! 'DiscrimType', 'pseudoLinear'); %! assert_equal (Mdl.LogDetSigma, -7.3470, 1e-4); %! assert_equal (Mdl.Gamma, 0); %!test %! load fisheriris %! Mdl = fitcdiscr ([meas(:,1:3), meas(:,3)], species, ... %! 'DiscrimType', 'pseudoQuadratic'); %! assert_equal (Mdl.LogDetSigma', [-11.2116, -7.2229, -6.4619], 1e-4); ## A predictor with no variance is dropped rather than inverted, so its ## coefficient is zero. %!test %! load fisheriris %! Mdl = fitcdiscr ([meas(:,1:3), ones(150,1)], species, ... %! 'DiscrimType', 'pseudoLinear'); %! assert_equal (Mdl.Coeffs(1,2).Linear(4), 0); %! assert_equal (Mdl.LogDetSigma, -6.3538, 1e-4); ## The compact model answers identically for every type. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'quadratic'); %! CMdl = compact (Mdl); %! assert_equal (predict (CMdl, meas), predict (Mdl, meas)); %! assert_equal (CMdl.Sigma, Mdl.Sigma); ## A saved quadratic model comes back able to change its type, which it can ## only do if the covariance the fit estimated was saved with it. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'quadratic'); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (predict (M2, meas), predict (Mdl, meas)); %! M2.DiscrimType = 'diagQuadratic'; %! assert_equal (size (M2.Sigma), [1, 4, 3]); ## Five observations per class over two predictors, enough for a quadratic ## discriminant to have a covariance it can invert. %!function x = dfix () %! x = [1, 2; 2, 1; 3, 4; 4, 3; 2, 3; 5, 7; 7, 5; 8, 9; 9, 8; 7, 8]; %!endfunction ## Delta eliminates predictors, and the threshold is on the standardized ## coefficient: the raw one would depend on the units each predictor is ## measured in. Values measured on MATLAB R2024a. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! assert_equal (Mdl.DeltaPredictor, ... %! [3.2508, 4.1236, 7.2926, 4.2506], 1e-4); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'diagLinear'); %! assert_equal (Mdl.DeltaPredictor, ... %! [1.6266, 1.0912, 5.3354, 4.6584], 1e-4); ## The quadratic family has no linear coefficients to eliminate. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'quadratic'); %! assert_equal (Mdl.DeltaPredictor, [0, 0, 0, 0]); ## A coefficient drops out one class at a time, and the boundary follows. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Delta = 2.25; %! assert_equal (Mdl.Coeffs(1,2).Linear', ... %! [6.3148, 12.1393, -16.9464, -20.7701], 1e-4); %! assert_equal (Mdl.Coeffs(1,2).Const, -14.3792, 1e-4); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Delta = 5; %! assert_equal (Mdl.Coeffs(1,2).Linear', [0, 0, -16.9464, 0], 1e-4); %! assert_equal (loss (Mdl, meas, species), 0.08, 1e-10); ## Past the largest DeltaPredictor every predictor is gone and the model ## answers from the priors alone. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.Delta = 8; %! assert_equal (Mdl.Coeffs(1,2).Linear', [0, 0, 0, 0]); %! assert_equal (loss (Mdl, meas, species), 2/3, 1e-10); ## Delta changes what predict answers, which is the whole point of it. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! before = loss (Mdl, meas, species); %! Mdl.Delta = 5; %! assert_equal (before, 0.02, 1e-10); %! assert (loss (Mdl, meas, species) > before); ## DeltaPredictor describes the fit, not the threshold, so assigning Delta ## leaves it where it was. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! before = Mdl.DeltaPredictor; %! Mdl.Delta = 5; %! assert_equal (Mdl.DeltaPredictor, before); %!error ... %! fitcdiscr (ones (10, 2), [1;1;1;1;1;2;2;2;2;2], 'DiscrimType', 'bogus') %!error ... %! Mdl = fitcdiscr (dfix (), [1;1;1;1;1;2;2;2;2;2]); ... %! Mdl.DiscrimType = 'quadratic'; %!error ... %! Mdl = fitcdiscr (dfix (), [1;1;1;1;1;2;2;2;2;2], ... %! 'DiscrimType', 'quadratic'); ... %! Mdl.DiscrimType = 'linear'; %!error ... %! Mdl = fitcdiscr (dfix (), [1;1;1;1;1;2;2;2;2;2]); Mdl.DiscrimType = 'bogus'; %!error ... %! Mdl = fitcdiscr (dfix (), [1;1;1;1;1;2;2;2;2;2], ... %! 'DiscrimType', 'quadratic'); ... %! Mdl.Gamma = 0.5; %!error ... %! fitcdiscr (dfix (), [1;1;1;1;1;2;2;2;2;2], ... %! 'DiscrimType', 'quadratic', 'Gamma', 0.5) %!error ... %! Mdl = fitcdiscr (dfix (), [1;1;1;1;1;2;2;2;2;2]); Mdl.Gamma = 1.5; %!error ... %! Mdl = fitcdiscr (dfix (), [1;1;1;1;1;2;2;2;2;2], ... %! 'DiscrimType', 'quadratic'); ... %! Mdl.Delta = 0.5; %!error ... %! fitcdiscr (dfix (), [1;1;1;1;1;2;2;2;2;2], ... %! 'DiscrimType', 'quadratic', 'Delta', 0.5) %!error ... %! fitcdiscr (dfix (), [1;1;1;1;1;2;2;2;2;2], 'Delta', -1) %!error ... %! Mdl = fitcdiscr (dfix (), [1;1;1;1;1;2;2;2;2;2]); Mdl.Delta = [1, 2]; %!error ... %! load fisheriris; ... %! fitcdiscr ([meas(:,1:3), meas(:,3)], species, 'DiscrimType', 'quadratic') %!error ... %! load fisheriris; ... %! fitcdiscr ([meas(:,1:3), ones(150,1)], species, 'DiscrimType', 'quadratic') ## MinGamma describes the data rather than the type, so a pseudo fit reports ## the same value as a plain one on the same predictors while regularizing by ## nothing at all. %!test %! load fisheriris %! Mdl = fitcdiscr ([meas(:,1:3), meas(:,3)], species, ... %! 'DiscrimType', 'pseudoLinear'); %! assert (Mdl.MinGamma > 0); %! assert_equal (Mdl.Gamma, 0); %!test %! load fisheriris %! Mdl = fitcdiscr ([meas(:,1:3), meas(:,3)], species, ... %! 'DiscrimType', 'diagLinear'); %! assert (Mdl.MinGamma > 0); %! assert_equal (Mdl.Gamma, 1); ## edge, resubPredict, resubMargin, resubEdge and resubLoss. Every value ## below was measured on MATLAB R2024a; the discriminant's scores agree with ## it exactly, so these are the oracle's own numbers and not ours. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! assert_equal (edge (Mdl, meas, species), 0.9454289377, 1e-9); ## The edge is the mean of the margins, by definition. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! m = margin (Mdl, meas, species); %! assert_equal (edge (Mdl, meas, species), mean (m), 1e-12); ## Weights are normalized within each class to that class's prior, which is ## not the same as dividing by their total: that would give 0.9269539697. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! assert_equal (edge (Mdl, meas, species, 'Weights', (1:150)'), ... %! 0.9438468986, 1e-9); ## A weight constant within a class therefore changes nothing. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! w = [ones(50,1); 7 * ones(50,1); 0.5 * ones(50,1)]; %! assert_equal (edge (Mdl, meas, species, 'Weights', w), ... %! edge (Mdl, meas, species), 1e-12); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! assert_equal (resubEdge (Mdl), 0.9454289377, 1e-9); %! assert_equal (resubLoss (Mdl), 0.02, 1e-12); %! assert_equal (sum (resubMargin (Mdl)), 141.8143406564, 1e-8); ## resubPredict is predict on the data the model kept. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! [label, score, cost] = resubPredict (Mdl); %! [l2, s2, c2] = predict (Mdl, meas); %! assert_equal (label, l2); %! assert_equal (score, s2); %! assert_equal (cost, c2); %! assert_equal (numel (label), 150); ## The compact model answers the same edge as the full one. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! assert_equal (edge (compact (Mdl), meas, species), 0.9454289377, 1e-9); %!error ... %! load fisheriris; edge (fitcdiscr (meas, species), meas) %!error ... %! load fisheriris; ... %! edge (fitcdiscr (meas, species), meas, species, 'Weights') %!error ... %! load fisheriris; ... %! edge (fitcdiscr (meas, species), meas, species, 'Weights', ones (3, 1)) %!error ... %! load fisheriris; ... %! edge (fitcdiscr (meas, species), meas, species, 'Nope', 1) ## BinEdges is an empty cell, which is what MATLAB reports for this ## learner as well: it fits the predictors as they are. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! assert_equal (class (Mdl.BinEdges), 'cell'); %! assert_equal (Mdl.BinEdges, {}); ## CategoricalPredictors and ExpandedPredictorNames, shapes measured on ## R2024a: an empty double and one name per predictor. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! assert_equal (Mdl.CategoricalPredictors, []); %! assert_equal (size (Mdl.CategoricalPredictors), [0, 0]); %! assert_equal (Mdl.ExpandedPredictorNames, Mdl.PredictorNames); %! assert_equal (size (Mdl.ExpandedPredictorNames), [1, 4]); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.CategoricalPredictors, Mdl.CategoricalPredictors); %! assert_equal (M2.ExpandedPredictorNames, Mdl.ExpandedPredictorNames); ## BetweenSigma, measured on MATLAB R2024a. It weights the class means by ## class size, not by Prior, and the denominator is the unbiased one for a ## weighted covariance. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! assert_equal (Mdl.BetweenSigma, ... %! [0.632121333333333, -0.199526666666667, ... %! 1.652483999999999, 0.712793333333333; %! -0.199526666666667, 0.113449333333333, ... %! -0.572395999999999, -0.229326666666666; %! 1.652483999999999, -0.572395999999999, ... %! 4.371027999999996, 1.867739999999998; %! 0.712793333333333, -0.229326666666666, ... %! 1.867739999999998, 0.804133333333333], 1e-12); ## Unequal class sizes: 50, 30 and 10 of the three species. %!test %! load fisheriris %! k = [1:50, 51:80, 101:110]; %! Mdl = fitcdiscr (meas(k,:), species(k)); %! assert_equal (Mdl.BetweenSigma, ... %! [0.651346086956520, -0.299426956521739, ... %! 1.775435652173911, 0.711567826086955; %! -0.299426956521739, 0.160084347826087, ... %! -0.811819130434784, -0.318816086956522; %! 1.775435652173911, -0.811819130434784, ... %! 4.840319130434781, 1.941198695652173; %! 0.711567826086955, -0.318816086956522, ... %! 1.941198695652173, 0.780424347826086], 1e-12); ## A row missing a predictor still counts towards the others, as it does for ## Mu; only a missing response drops one. %!test %! load fisheriris %! X = meas; X(3,2) = NaN; X(77,4) = NaN; %! Mdl = fitcdiscr (X, species); %! assert_equal (Mdl.BetweenSigma(1,2), -0.201474748299320, 1e-12); %! assert_equal (Mdl.BetweenSigma(4,4), 0.803942801055115, 1e-12); ## It describes the classes, not the fit, so the quadratic family reports it ## and assigning a prior does not move it. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'quadratic'); %! assert_equal (Mdl.BetweenSigma(1,1), 0.632121333333333, 1e-12); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! before = Mdl.BetweenSigma; %! Mdl.Prior = [0.6, 0.2, 0.2]; %! assert_equal (Mdl.BetweenSigma, before); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.BetweenSigma, Mdl.BetweenSigma); ## mahal is the squared distance to every class mean. Measured on R2024a. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! M = mahal (Mdl, meas(1:5,:)); %! assert_equal (size (M), [5, 3]); %! assert_equal (M(1,:), [0.2910898404344, 98.8847494279393, ... %! 191.788642179719], 1e-10); %! assert_equal (M(5,:), [0.5956300391717, 100.923170228959, ... %! 193.854037009331], 1e-10); ## 'ClassLabels' picks one class mean per observation. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! M = mahal (Mdl, meas(1:5,:), 'ClassLabels', species(1:5)); %! assert_equal (size (M), [5, 1]); %! assert_equal (M, [0.291089840434356; 2.031345104042097; ... %! 0.553281423559203; 2.086697905677364; ... %! 0.595630039171744], 1e-12); ## A quadratic model measures against each class's own covariance. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'quadratic'); %! M = mahal (Mdl, meas(1:5,:)); %! assert_equal (M(1,:), [0.4491137892273, 114.804489260461, ... %! 182.935908699285], 1e-10); ## The distance follows the regularized covariance the model reports. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'Gamma', 0.5); %! M = mahal (Mdl, meas(1:5,:)); %! assert_equal (M(1,:), [0.1926136709121, 79.4757202663154, ... %! 163.294484902038], 1e-10); ## The prior does not enter the distance. %!test %! load fisheriris %! M1 = mahal (fitcdiscr (meas, species), meas(1:5,:)); %! Mdl = fitcdiscr (meas, species, 'Prior', [0.6, 0.2, 0.2]); %! assert_equal (mahal (Mdl, meas(1:5,:)), M1, 1e-12); ## logp is the log density summed over the classes at their priors. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! lp = logp (Mdl, meas(1:5,:)); %! assert_equal (size (lp), [5, 1]); %! assert_equal (lp, [0.059358043320009; -0.810769588483861; ... %! -0.071737748242414; -0.838445989301495; ... %! -0.092912056048684], 1e-12); ## logp on a quadratic model. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'quadratic'); %! lp = logp (Mdl, meas(1:5,:)); %! assert_equal (lp, [1.534756847193468; 0.718766664165731; ... %! 1.117146185488189; 0.906210252665867; ... %! 1.378471050607217], 1e-12); ## The prior reweights the density, unlike the distance. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'Prior', [0.6, 0.2, 0.2]); %! lp = logp (Mdl, meas(1:5,:)); %! assert_equal (lp, [0.647144708222129; -0.222982923581741; ... %! 0.516048916659706; -0.250659324399375; ... %! 0.494874608853435], 1e-12); %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! mahal (Mdl, []) %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! mahal (Mdl, ones (3, 2)) %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! mahal (Mdl, {1, 2, 3, 4}) %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! mahal (Mdl, meas(1:5,:), 'ClassLabels') %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! mahal (Mdl, meas(1:5,:), 5, 1) %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! mahal (Mdl, meas(1:5,:), 'bogus', 1) %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! mahal (Mdl, meas(1:5,:), 'ClassLabels', species(1:3)) %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! mahal (Mdl, meas(1:2,:), 'ClassLabels', {'setosa'; 'nosuchspecies'}) %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! logp (Mdl, []) %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! logp (Mdl, ones (3, 2)) %!error ... %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! logp (Mdl, {1, 2, 3, 4}) %!test %! ## DeltaPredictor follows Gamma: regularizing the covariance moves the %! ## coefficients, and the delta at which a predictor drops out with them. %! ## Measured against R2024a at four points of the grid. %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! got = zeros (1, 4); %! g = [0, 1/3, 2/3, 1]; %! for k = 1:4 %! m = Mdl; %! m.Gamma = g(k); %! got(k) = max (m.DeltaPredictor); %! endfor %! assert_equal (got, [7.2926, 5.2537, 4.8816, 5.3354], 1e-4); %!test %! ## cvshrink returns a column per output over the default grid of eleven %! ## Gamma values. %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! [err, gamma, delta, numpred] = cvshrink (Mdl); %! assert_equal (size (err), [11, 1]); %! assert_equal (size (gamma), [11, 1]); %! assert_equal (size (delta), [11, 1]); %! assert_equal (size (numpred), [11, 1]); %! assert_equal (gamma, (0:0.1:1)', 1e-12); %! assert_equal (delta, zeros (11, 1)); %! assert_equal (numpred, repmat (4, 11, 1)); %! assert_equal (all (err >= 0 & err <= 1), true); %!test %! ## NumGamma sets the number of intervals, so one more value than that. %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! [err, gamma] = cvshrink (Mdl, "NumGamma", 4); %! assert_equal (size (err), [5, 1]); %! assert_equal (gamma, [0; 0.25; 0.5; 0.75; 1], 1e-12); %!test %! ## The Delta grid runs to the point where the last predictor is gone, and %! ## that point moves with Gamma. Measured against R2024a. %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! [err, gamma, delta, numpred] = cvshrink (Mdl, "NumGamma", 3, "NumDelta", 2); %! assert_equal (size (err), [4, 3]); %! assert_equal (size (delta), [4, 3]); %! assert_equal (delta, [0, 3.6463, 7.2926; 0, 2.6269, 5.2537; ... %! 0, 2.4408, 4.8816; 0, 2.6677, 5.3354], 1e-4); %! ## every predictor is in at Delta of zero, and they leave as it grows %! assert_equal (numpred(:,1), repmat (4, 4, 1)); %! assert_equal (all (numpred(:,3) < numpred(:,1)), true); %!test %! ## An explicit Gamma is used as given. %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! [err, gamma] = cvshrink (Mdl, "Gamma", [0, 0.5, 1]); %! assert_equal (size (err), [3, 1]); %! assert_equal (gamma, [0; 0.5; 1], 1e-12); %!test %! ## An explicit Delta vector is used for every Gamma. %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! [err, gamma, delta] = cvshrink (Mdl, "Gamma", [0, 1], "Delta", [0, 2]); %! assert_equal (size (delta), [2, 2]); %! assert_equal (delta, [0, 2; 0, 2]); %!error ... %! cvshrink (fitcdiscr (ones (6, 2) + [1;2;3;4;5;6], [1;1;1;2;2;2]), "NumGamma") %!error ... %! cvshrink (fitcdiscr (ones (6, 2) + [1;2;3;4;5;6], [1;1;1;2;2;2]), "NumGamma", 0) %!error ... %! cvshrink (fitcdiscr (ones (6, 2) + [1;2;3;4;5;6], [1;1;1;2;2;2]), "NumDelta", -1) %!error ... %! cvshrink (fitcdiscr (ones (6, 2) + [1;2;3;4;5;6], [1;1;1;2;2;2]), "Gamma", 2) %!error ... %! cvshrink (fitcdiscr (ones (6, 2) + [1;2;3;4;5;6], [1;1;1;2;2;2]), "bogus", 1) ## HyperparameterOptimizationResults is declared for MATLAB compatibility and ## stays empty, this class running no search over its hyperparameters. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! assert_equal (isempty (Mdl.HyperparameterOptimizationResults), true); ## ModelParameters records the fit as it was asked for. The field list and ## its order are MATLAB's, measured on R2024a, less SaveMemory, which this ## class does not offer. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! assert_equal (fieldnames (Mdl.ModelParameters)', {'DiscrimType', ... %! 'Gamma', 'Delta', 'FillCoeffs', 'Version', 'Method', 'Type'}); %!test %! load fisheriris %! MP = fitcdiscr (meas, species).ModelParameters; %! assert_equal (MP.DiscrimType, 'linear'); %! assert_equal (MP.Gamma, 0); %! assert_equal (MP.Delta, 0); %! assert_equal (MP.FillCoeffs, true); %!test %! load fisheriris %! MP = fitcdiscr (meas, species).ModelParameters; %! assert_equal (MP.Version, 1); %! assert_equal (MP.Method, 'Discriminant'); %! assert_equal (MP.Type, 'classification'); ## FillCoeffs is reported as the flag it is, not as the 'on'/'off' it is ## given as. %!test %! load fisheriris %! MP = fitcdiscr (meas, species, 'FillCoeffs', 'off').ModelParameters; %! assert_equal (MP.FillCoeffs, false); %! assert_equal (class (MP.FillCoeffs), 'logical'); %!test %! load fisheriris %! MP = fitcdiscr (meas, species, 'DiscrimType', 'pseudoLinear', ... %! 'Gamma', 0.3, 'Delta', 0.1).ModelParameters; %! assert_equal (MP.DiscrimType, 'pseudoLinear'); %! assert_equal (MP.Gamma, 0.3); %! assert_equal (MP.Delta, 0.1); ## The Gamma property is forced by the type, 0 for a pseudo type and 1 for a ## diagonal one, but the fit was asked for 0.3 and that is what is recorded. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'diagLinear', 'Gamma', 0.3); %! assert_equal (Mdl.ModelParameters.Gamma, 0.3); %! assert_equal (Mdl.Gamma, 1); ## A compact model keeps what predict needs and drops the record of the fit, ## as MATLAB's does. %!test %! load fisheriris %! CMdl = compact (fitcdiscr (meas, species)); %! assert_equal (isprop (CMdl, 'ModelParameters'), false); ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/ClassificationGAM.m000066400000000000000000004734651524624707500257550ustar00rootroot00000000000000## Copyright (C) 2024 Ruchika Sonagote ## Copyright (C) 2025 Swayam Shah ## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef ClassificationGAM ## -*- texinfo -*- ## @deftp {statistics} ClassificationGAM ## ## Generalized additive model classification ## ## The @code{ClassificationGAM} class implements a gradient boosting ## algorithm for classification. This approach allows the model to capture ## non-linear relationships between predictors and the binary response ## variable. ## ## Generalized additive model classification is a statistical method that ## extends linear models by allowing non-linear relationships between each ## predictor and the response variable through smooth functions. It combines ## the interpretability of linear models with the flexibility of ## non-parametric methods. ## ## Create a @code{ClassificationGAM} object by using the @code{fitcgam} ## function or the class constructor. ## ## Two weak learners are available, selected by @code{FitMethod}. ## ## @qcode{'boostedtrees'}, the default, boosts one shallow decision tree per ## predictor in each round, which is the scheme MATLAB's generalized ## additive model uses. A second phase then boosts trees over pairs of ## predictors, where interactions are asked for. ## ## @qcode{'splines'} boosts a smoothing spline per predictor over ## @code{NumIterations} passes. It has no MATLAB counterpart and is an ## Octave extension, kept because a smooth additive fit is a genuinely ## different and often better answer than a staircase of stumps. ## ## The two take different arguments, and an argument meant for one is ## refused by the other rather than ignored. ## ## The choice is visible in the properties. @code{Knots}, @code{Order}, ## @code{DoF}, @code{Formula}, @code{LearningRate}, @code{NumIterations}, ## @code{BaseModel}, @code{ModelwInt} and @code{IntMatrix} describe a spline ## fit and are empty under the boosted-tree engine, while ## @code{ModelParameters}, @code{ReasonForTermination}, @code{BinEdges}, ## @code{PairDetectionBinEdges} and @code{TreeModel} describe a tree fit and ## are empty under the spline engine. ## ## Fitted values are not expected to equal MATLAB's even under ## @qcode{'boostedtrees'}. The stopping rule and the step-reduction limit ## are not recoverable from anything MATLAB reports, so this engine ## documents its own; what the two share is the estimator and the reported ## surface, not the arithmetic. ## ## @seealso{fitcgam} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} X ## ## Predictor data ## ## A numeric matrix containing the unstandardized predictor data. Each ## column of @var{X} represents one predictor (variable), and each row ## represents one observation. This property is read-only. ## ## @end deftp X = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} Y ## ## Class labels ## ## Specified as a logical or numeric column vector, or as a character array ## or a cell array of character vectors with the same number of rows as the ## predictor data. Each row in @var{Y} is the observed class label for ## the corresponding row in @var{X}. This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} NumObservations ## ## Number of observations ## ## A positive integer value specifying the number of observations in the ## training dataset used for training the ClassificationGAM model. ## This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} RowsUsed ## ## Rows used for fitting ## ## A logical column vector with the same length as the observations in the ## original predictor data @var{X}, true for each row that was used for ## fitting the ClassificationGAM model. It is empty, @qcode{[]}, ## when every observation was used, so a non-empty value means that rows ## holding missing values were dropped. This property is read-only. ## ## @end deftp RowsUsed = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} NumPredictors ## ## Number of predictors ## ## A positive integer value specifying the number of predictors in the ## training dataset used for training the ClassificationGAM model. ## This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} PredictorNames ## ## Names of predictor variables ## ## A cell array of character vectors specifying the names of the predictor ## variables. The names are in the order in which they appear in the ## training dataset. This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} ResponseName ## ## Response variable name ## ## A character vector specifying the name of the response variable @var{Y}. ## This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} ClassNames ## ## Names of classes in the response variable ## ## An array of unique values of the response variable @var{Y}, which has the ## same data types as the data in @var{Y}. This property is read-only. ## @qcode{ClassNames} can have any of the following datatypes: ## ## @itemize ## @item Cell array of character vectors ## @item Character array ## @item Logical vector ## @item Numeric vector ## @end itemize ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} Prior ## ## Prior probability for each class ## ## A 2-element numeric vector specifying the prior probabilities for each ## class. The order of the elements in @qcode{Prior} corresponds to the ## order of the classes in @qcode{ClassNames}. This property is read-only. ## ## Specified as a row vector with one entry per class, in the order of ## @qcode{ClassNames}, and rescaled to sum to one. It may be given as ## @qcode{'empirical'}, @qcode{'uniform'}, a numeric vector, or a ## structure with @qcode{ClassNames} and @qcode{ClassProbs} fields, which ## assigns each probability by class name rather than by position. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} Formula ## ## Model specification formula ## ## A character vector specifying the model formula in the form ## @qcode{'Y ~ terms'} where @qcode{Y} represents the response variable and ## @qcode{terms} specifies the predictor variables and interaction terms. ## This property is read-only. ## ## @end deftp Formula = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} Interactions ## ## Two-way interaction terms of the fitted model ## ## A @math{Kx2} matrix of predictor index pairs, one row per two-way term ## the model carries, and @code{zeros (0, 2)} when it carries none. It ## reports what was fitted rather than what was asked for, so a count of ## terms, @qcode{'all'}, a logical matrix and a formula all leave the same ## kind of value behind. This property is read-only. ## ## A main effect names one predictor and a higher-order term names three ## or more, and neither has a two-column form, so neither appears here. ## @code{IntMatrix} remains the complete record of every term fitted. ## ## @end deftp Interactions = zeros (0, 2); ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} Knots ## ## Knots for spline fitting ## ## A scalar or row vector specifying the number of knots for each predictor ## variable in the spline fitting. This property is read-only. ## ## @end deftp Knots = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} Order ## ## Order of spline fitting ## ## A scalar or row vector specifying the order of the spline for each ## predictor variable. This property is read-only. ## ## @end deftp Order = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} DoF ## ## Degrees of freedom for spline fitting ## ## A scalar or row vector specifying the degrees of freedom for each ## predictor variable in the spline fitting. This property is read-only. ## ## @end deftp DoF = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} LearningRate ## ## Learning rate for gradient boosting ## ## A scalar value between 0 and 1 specifying the learning rate used in the ## gradient boosting algorithm. This property is read-only. ## ## @end deftp LearningRate = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} NumIterations ## ## Maximum number of iterations ## ## A positive integer specifying the maximum number of iterations for the ## gradient boosting algorithm. This property is read-only. ## ## @end deftp NumIterations = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} Intercept ## ## Intercept of the fitted model ## ## A numeric scalar, the log-odds of the response mean, which every ## additive term is measured against. This property is read-only. ## ## @end deftp Intercept = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} W ## ## Observation weights ## ## A numeric column vector with one entry per observation used for ## training, normalised to sum to one. This property is read-only. ## ## Each class carries its prior spread evenly over its own observations, ## so an observation of a class weighs @qcode{Prior} for that class ## divided by the number of observations it holds. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector holding the column of each predictor treated as ## categorical, and empty when none is. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} ExpandedPredictorNames ## ## Names of the expanded predictor variables ## ## A cell array of character vectors naming the predictors as the model ## sees them. It matches @code{PredictorNames} unless a categorical ## predictor was expanded into dummy variables. This property is ## read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} BaseModel ## ## Base model parameters ## ## A structure containing the parameters of the base model without any ## interaction terms. The base model represents the generalized additive ## model with only the main effects (predictor terms) included. ## This property is read-only. ## ## @end deftp BaseModel = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} ModelwInt ## ## Model parameters with interactions ## ## A structure containing the parameters of the model that includes ## interaction terms. This model extends the base model by adding ## interaction terms between predictors. This property is read-only. ## ## @end deftp ModelwInt = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} IntMatrix ## ## Every term the model fits ## ## A logical matrix with one row per term and one column per predictor, ## true wherever the term multiplies that predictor. A row naming one ## predictor is a main effect, two an interaction, and three or more a ## higher-order term. This property is read-only. ## ## It is the complete record, where @code{Interactions} reports only the ## two-way terms, in the form MATLAB reports them. It is also the form ## the @qcode{'Interactions'} option takes back, so passing it to the ## constructor rebuilds a model over the same terms. ## ## @end deftp IntMatrix = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} BinEdges ## ## Bin edges of the predictors ## ## A cell array with one entry per predictor, holding that predictor's bin ## edges where the model discretized it before fitting. It is empty here ## and stays empty: this generalized additive model is built from splines, ## which take the predictors as they are, where MATLAB's is built from ## boosted trees and bins them. That difference is described in the class ## documentation. ## ## This property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} PairDetectionBinEdges ## ## Bin edges used to detect interactions ## ## A cell array with one row vector per predictor, holding the coarse cut ## points the residuals of the predictor phase were laid on while pairs ## were being tested. The grid is eight equal-frequency bins whatever the ## sample size, as MATLAB's is. It is empty when the model carries no ## interaction terms, and empty throughout under the spline engine, which ## does not bin. ## ## This property is read-only. ## ## @end deftp PairDetectionBinEdges = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} ModelParameters ## ## Parameters the model was fitted with ## ## A structure holding the fitting parameters. Under the boosted-tree ## engine it carries MATLAB's own fields: @qcode{NumPrint}, ## @qcode{MaxPValue}, @qcode{InitialLearnRateForPredictors}, ## @qcode{InitialLearnRateForInteractions}, ## @qcode{NumTreesPerPredictor}, @qcode{NumTreesPerInteraction}, ## @qcode{MaxNumSplitsPerPredictor}, @qcode{MaxNumSplitsPerInteraction}, ## @qcode{VerbosityLevel}, @qcode{Interactions}, @qcode{Version}, ## @qcode{Method} and @qcode{Type}. @qcode{Interactions} here is the ## request as it was made, a count or @qcode{'all'}, where the ## @qcode{Interactions} property of the model is the pairs actually ## selected. ## ## Under the spline engine it describes that scheme instead, carrying ## @qcode{Knots}, @qcode{Order}, @qcode{DoF}, @qcode{Formula}, ## @qcode{Interactions}, @qcode{LearningRate} and @qcode{NumIterations}, ## since none of the tree vocabulary applies to it. ## ## This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} ReasonForTermination ## ## Why each fitting phase stopped ## ## A structure with the fields @qcode{PredictorTrees} and ## @qcode{InteractionTrees}, each a character vector saying why that phase ## of the fit ended: that it trained the trees it was asked for, or that ## it could no longer improve the model. A phase that never ran reports ## an empty character vector, which is what a model with no interaction ## terms shows for the second field. ## ## It is empty under the spline engine, which has no tree budget to ## exhaust. ## ## This property is read-only. ## ## @end deftp ReasonForTermination = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} FitMethod ## ## Which engine fitted the model ## ## A character vector, either @qcode{'boostedtrees'} or ## @qcode{'splines'}. The default is @qcode{'boostedtrees'}, which is the ## scheme MATLAB's generalized additive model uses and the one the ## tree-shaped properties above describe. ## ## @qcode{'splines'} selects the penalised-spline engine instead, which is ## an Octave extension with no MATLAB counterpart. It is the scheme this ## class fitted before version 1.9.0, and it is kept because a smooth ## additive fit is a genuinely different and often better answer than a ## staircase of stumps. The two engines take different arguments and an ## argument meant for one is refused by the other rather than ignored. ## ## This property is read-only. ## ## @end deftp FitMethod = 'boostedtrees'; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} TreeModel ## ## The fitted shape functions and interaction surfaces ## ## A structure holding what the boosted-tree engine fitted, with fields ## @qcode{ShapeValues}, one column vector per predictor giving that ## predictor's contribution in each of its bins, @qcode{PairValues}, one ## matrix per selected pair, and @qcode{Pairs}, the predictor indices those ## matrices belong to. A shape function is a step function, so these are ## the whole of the fit however many trees produced them. ## ## MATLAB exposes no equivalent: it reports the bin edges but never the ## values on them, so its shape functions can only be reached through ## @code{predict}. This property is an Octave extension, and it is empty ## under the spline engine, whose fit lives in @code{BaseModel} and ## @code{ModelwInt}. ## ## This property is read-only. ## ## @end deftp TreeModel = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} HyperparameterOptimizationResults ## ## Results of the hyperparameter optimization ## ## @strong{Always empty.} It is declared for MATLAB compatibility, where ## it holds what an automatic search over the hyperparameters found. This ## class fits the parameters it is given and runs no such search, so there ## is nothing to report. This property is read-only. ## ## @end deftp HyperparameterOptimizationResults = []; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} Cost ## ## Cost of Misclassification ## ## A square matrix specifying the cost of misclassification of a point. ## @qcode{Cost(i,j)} is the cost of classifying a point into class @qcode{j} ## if its true class is @qcode{i} (that is, the rows correspond to the true ## class and the columns correspond to the predicted class). The order of ## the rows and columns in @qcode{Cost} corresponds to the order of the ## classes in @qcode{ClassNames}. The number of rows and columns in ## @qcode{Cost} is the number of unique classes in the response. By ## default, @qcode{Cost(i,j) = 1} if @qcode{i != j}, and ## @qcode{Cost(i,j) = 0} if @qcode{i = j}. In other words, the cost is 0 ## for correct classification and 1 for incorrect classification. ## ## Add or change the @qcode{Cost} property using dot notation as in: ## @itemize ## @item @qcode{@var{obj}.Cost = @var{costMatrix}} ## @end itemize ## ## ## A cost may also be given as a struct with the fields ## @qcode{ClassNames} and @qcode{ClassificationCosts}, which names the ## order its own matrix is written in. That matrix is permuted into the ## order of @qcode{ClassNames} above, so a caller need not know which ## order the classes were sorted into. It must name every class. ## ## A cost must be floating point, not sparse, not complex, non-negative ## and zero down its diagonal, and must hold no @qcode{NaN} or ## @qcode{Inf}. A @code{single} is widened to @code{double}. ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {ClassificationGAM} {property} ScoreTransform ## ## Transformation function for classification scores ## ## Specified as a function handle for transforming the classification ## scores. Add or change the @qcode{ScoreTransform} property using dot ## notation as in: ## ## @itemize ## @item @qcode{@var{obj}.ScoreTransform = 'function_name'} ## @item @qcode{@var{obj}.ScoreTransform = @@function_handle} ## @end itemize ## ## When specified as a character vector, it can be any of the following ## built-in functions. Nevertheless, the @qcode{ScoreTransform} property ## always stores their function handle equivalent. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'doublelogit'} @tab @math{1 ./ (1 + exp (-2 * x))} ## @item @qcode{'invlogit'} @tab @math{log (x ./ (1 - x))} ## @item @qcode{'ismax'} @tab Sets the score for the class with the ## largest score to 1, and for all other classes to 0 ## @item @qcode{'logit'} @tab @math{1 ./ (1 + exp (-x))} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'sign'} @tab ## @math{-1 for x < 0, 0 for x = 0, 1 for x > ## 0} ## @item @qcode{'symmetric'} @tab @math{2 * x - 1} ## @item @qcode{'symmetricismax'} @tab Sets the score for the class ## with the largest score to 1, and for all other classes to -1 ## @item @qcode{'symmetriclogit'} @tab @math{2 ./ (1 + exp (-x)) - 1} ## @end multitable ## ## The default is @qcode{'logit'}, as in MATLAB. This model's raw ## score is a log-odds, reported as the pair @math{[-f, f]} whose two ## columns sum to zero, and the transform is what turns it into the ## posterior probabilities that sum to one. Every transform therefore ## composes on the log-odds and not on the probabilities, so ## @qcode{'none'} returns the log-odds themselves. ## ## @end deftp ScoreTransform = 'logit'; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) STfun = @(x) 1 ./ (1 + exp (-x)); ## How many trees each boosting phase actually fitted, which the ## budget in ModelParameters does not say: a phase may stop early. ## Hidden because MATLAB reports it on the partitioned classes and ## not on the model, and that is where this is read from. NumTrainedTrees = []; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.Cost (this, val) gnY = this.ClassNames; if (isempty (val)) this.Cost = cast (! eye (classCount (gnY)), 'double'); else ## Everything a cost must be, and the struct form, which ## is permuted into this model's class order. [val, errmsg] = costMatrix (val, gnY); if (! isempty (errmsg)) error ("ClassificationGAM: %s", errmsg); endif this.Cost = val; endif endfunction function this = set.ScoreTransform (this, val) [f, nm] = parseScoreTransform (val, 'ClassificationGAM'); this.ScoreTransform = nm; this.STfun = f; endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n ClassificationGAM\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); if (iscellstr (this.ClassNames)) str = repmat ({'''%s'''}, 1, numel (this.ClassNames)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.ClassNames{:}); elseif (ischar (this.ClassNames)) str = repmat ({'''%s'''}, 1, rows (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, cellstr (this.ClassNames){:}); else # single, double, logical str = repmat ({'%d'}, 1, numel (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.ClassNames); endif fprintf ("%+25s: %s\n", 'ClassNames', str); fprintf ("%+25s: '%s'\n", 'ScoreTransform', this.ScoreTransform); fprintf ("%+25s: %d\n", 'NumObservations', this.NumObservations); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); if (! isempty (this.Formula)) fprintf ("%+25s: '%s'\n", 'Formula', this.Formula); endif if (! isempty (this.Interactions)) fprintf ("%+25s: [%dx%d %s]\n", 'Interactions', ... size (this.Interactions, 1), size (this.Interactions, 2), ... class (this.Interactions)); endif endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} ClassificationGAM (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{obj} =} ClassificationGAM (@dots{}, @var{name}, @var{value}) ## ## Create a @qcode{ClassificationGAM} class object containing a generalized ## additive classification model. ## ## @code{@var{obj} = ClassificationGAM (@var{X}, @var{Y})} returns ## a ClassificationGAM object, with @var{X} as the predictor data ## and @var{Y} containing the class labels of observations in @var{X}. ## ## @itemize ## @item ## @code{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. @var{X} will be used to train the GAM model. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} can contain any type ## of categorical data. @var{Y} must have the same number of rows as ## @var{X}. ## @end itemize ## ## @code{@var{obj} = ClassificationGAM (@dots{}, @var{name}, ## @var{value})} returns a ClassificationGAM object with parameters ## specified by the following @qcode{@var{name}, @var{value}} paired input ## arguments: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PredictorNames'} @tab A cell array of character ## vectors specifying the names of the predictors. The length of this array ## must match the number of columns in @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector specifying the ## name of the response variable. ## ## @item @qcode{'ClassNames'} @tab Names of the classes in the class ## labels, @var{Y}, used for fitting the GAM model. ## @qcode{ClassNames} are of the same type as the class labels in @var{Y}. ## ## @item @qcode{'Cost'} @tab An @math{N*R} numeric matrix containing ## misclassification cost for the corresponding instances in @var{X}, where ## @math{R} is the number of unique categories in @var{Y}. If an instance ## is correctly classified into its category the cost is calculated to be 1, ## otherwise 0. The cost matrix can be altered by using ## @code{@var{Mdl}.cost = somecost}. By default, its value is ## @qcode{@var{cost} = ones (rows (X), numel (unique (Y)))}. ## ## @item @qcode{'Prior'} @tab A numeric vector specifying the prior ## probabilities for each class. The order of the elements in @qcode{Prior} ## corresponds to the order of the classes in @qcode{ClassNames}. ## Alternatively, you can specify @qcode{'empirical'} to use the empirical ## class probabilities or @qcode{'uniform'} to assume equal class ## probabilities. ## ## @item @qcode{'ScoreTransform'} @tab A user-defined function handle ## or a character vector specifying one of the following builtin functions ## specifying the transformation applied to predicted classification scores. ## Supported values include @qcode{'doublelogit'}, @qcode{'invlogit'}, ## @qcode{'ismax'}, @qcode{'logit'}, @qcode{'none'}, @qcode{'identity'}, ## @qcode{'sign'}, @qcode{'symmetric'}, @qcode{'symmetricismax'}, and ## @qcode{'symmetriclogit'}. ## ## @item @qcode{'Formula'} @tab (spline option) A character vector ## specifying the model ## formula in the form @qcode{'Y ~ terms'} where @qcode{Y} represents the ## response variable and @qcode{terms} specifies the predictor variables and ## interaction terms. ## ## @item @qcode{'Interactions'} @tab A logical matrix, a positive ## integer scalar, or the string @qcode{'all'} for defining the interactions ## between predictor variables. ## ## @item @qcode{'Knots'} @tab (spline option) A scalar or row vector ## specifying the ## number of knots for each predictor variable in the spline fitting. ## ## @item @qcode{'Order'} @tab (spline option) A scalar or row vector ## specifying the ## order of the spline for each predictor variable. ## ## @item @qcode{'DoF'} @tab (spline option) A scalar or row vector ## specifying the ## degrees of freedom for each predictor variable in the spline fitting. ## ## @item @qcode{'LearningRate'} @tab (spline option) A scalar value between ## 0 and 1 ## specifying the learning rate used in the gradient boosting algorithm. ## ## @item @qcode{'NumIterations'} @tab (spline option) A positive integer ## specifying ## the maximum number of iterations for the gradient boosting algorithm. ## @end multitable ## ## A row marked @qcode{(spline option)} belongs to the spline ## engine and requires @qcode{'FitMethod', 'splines'}; passing one ## under the default boosted-tree engine is an error rather than ## being ignored. The boosted-tree engine's own options are ## documented under @code{fitcgam}. ## ## @seealso{fitcgam} ## @end deftypefn function this = ClassificationGAM (X, Y, varargin) ## Check for sufficient number of input arguments if (nargin < 2) error ("ClassificationGAM: too few input arguments."); endif ## Check X and Y have the same number of observations if (rows (X) != rows (Y)) error ("ClassificationGAM: number of rows in X and Y must be equal."); endif nsample = rows (X); ndims_X = columns (X); ## Assign original X and Y data this.X = X; this.Y = Y; ## Get groups in Y [gY, gnY, glY] = grp2idx (Y); ## Set default values before parsing optional parameters PredictorNames = {}; ResponseName = []; Formula = []; Interactions = []; ClassNames = []; Prior = 'empirical'; DoF = ones (1, ndims_X) * 8; Order = ones (1, ndims_X) * 3; Knots = ones (1, ndims_X) * 5; LearningRate = 0.1; NumIterations = 100; Cost = []; ## Boosted-tree defaults, MATLAB's own. They are reported through ## ModelParameters, so they are part of the surface being matched and ## are not ours to improve: every other boosted additive model shrinks ## far harder than a step of 1, scikit-learn, gbm and mboost defaulting ## to 0.1 and the Explainable Boosting Machine to 0.015. The docstring ## says so rather than the default being quietly changed. FitMethod = 'boostedtrees'; NumTreesPerPredictor = 300; NumTreesPerInteraction = 100; MaxNumSplitsPerPredictor = 1; MaxNumSplitsPerInteraction = 4; InitialLearnRateForPredictors = 1; InitialLearnRateForInteractions = 1; MaxPValue = 1; Verbose = 0; NumPrint = 10; ## Every name the caller asked for, so an argument meant for the other ## engine is refused instead of quietly doing nothing. namesGiven = {}; ## Number of parameters for Knots, DoF, Order (maximum 2 allowed) KOD = 0; ## Number of parameters for Formula, Interactions (maximum 1 allowed) F_I = 0; ## Parse extra parameters while (numel (varargin) > 0) namesGiven{end+1} = tolower (varargin{1}); switch (tolower (varargin {1})) case 'predictornames' PredictorNames = varargin{2}; if (! iscellstr (PredictorNames)) error (strcat ("ClassificationGAM: 'PredictorNames'", ... " must be supplied as a cellstring array.")); elseif (numel (PredictorNames) != columns (X)) error (strcat ("ClassificationGAM: 'PredictorNames'", ... " must equal the number of columns in X.")); endif case 'responsename' ResponseName = varargin{2}; if (! ischar (ResponseName)) error (strcat ("ClassificationGAM: 'ResponseName'", ... " must be a character vector.")); endif case 'classnames' ClassNames = varargin{2}; if (! (iscellstr (ClassNames) || isnumeric (ClassNames) || islogical (ClassNames) || ischar (ClassNames))) error (strcat ("ClassificationGAM: 'ClassNames' must be a", ... " cell array of character vectors, a logical", ... " vector, a numeric vector, or a character array.")); endif ## Check that all class names are available in gnY if (iscellstr (ClassNames) || ischar (ClassNames)) ClassNames = cellstr (ClassNames); if (! all (cell2mat (cellfun (@(x) any (strcmp (x, gnY)), ClassNames, 'UniformOutput', false)))) error (strcat ("ClassificationGAM: not all 'ClassNames'", ... " are present in Y.")); endif else if (! all (cell2mat (arrayfun (@(x) any (x == glY), ClassNames, 'UniformOutput', false)))) error (strcat ("ClassificationGAM: not all 'ClassNames'", ... " are present in Y.")); endif endif case 'prior' Prior = varargin{2}; if (! (isstruct (Prior) || isnumeric (Prior) || ischar (Prior))) error (strcat ("ClassificationGAM: 'Prior' must be", ... " a numeric vector or a string.")); endif if (ischar (Prior) && ! any (strcmpi (Prior, {'empirical', 'uniform'}))) error (strcat ("ClassificationGAM: 'Prior' must be", ... " 'empirical', 'uniform', or a numeric vector.")); endif if (isnumeric (Prior) && numel (Prior) != 2 && ! isstruct (Prior)) error ("ClassificationGAM: 'Prior' must be a 2-element vector."); endif case 'cost' Cost = varargin{2}; ## A struct carrying its own class order is a cost too, ## and is resolved by the property's own set method. if (! (isstruct (Cost) || (isnumeric (Cost) && issquare (Cost)))) error (strcat ("ClassificationGAM: 'Cost' must be", ... " a numeric square matrix.")); endif case 'scoretransform' name = 'ClassificationGAM'; this.ScoreTransform = varargin{2}; case 'formula' if (F_I < 1) Formula = varargin{2}; if (! ischar (Formula) && ! islogical (Formula)) error ("ClassificationGAM: 'Formula' must be a string."); endif F_I += 1; else error (strcat ("ClassificationGAM: 'Interactions'", ... " have already been defined.")); endif case 'interactions' if (F_I < 1) tmp = varargin{2}; if (isnumeric (tmp) && isscalar (tmp) && tmp == fix (tmp) && tmp >= 0) Interactions = tmp; elseif (islogical (tmp)) Interactions = tmp; elseif (ischar (tmp) && strcmpi (tmp, 'all')) Interactions = tmp; else error ("ClassificationGAM: invalid 'Interactions' parameter."); endif F_I += 1; else error ("ClassificationGAM: 'Formula' has already been defined."); endif case 'knots' if (KOD < 2) Knots = varargin{2}; if (! isnumeric (Knots) || ! (isscalar (Knots) || isequal (size (Knots), [1, ndims_X]))) error ("ClassificationGAM: invalid value for 'Knots'."); endif DoF = Knots + Order; Order = DoF - Knots; KOD += 1; else error (strcat ("ClassificationGAM: 'DoF' and 'Order'", ... " have been set already.")); endif case 'order' if (KOD < 2) Order = varargin{2}; if (! isnumeric (Order) || ! (isscalar (Order) || isequal (size (Order), [1, ndims_X]))) error ("ClassificationGAM: invalid value for 'Order'."); endif DoF = Knots + Order; Knots = DoF - Order; KOD += 1; else error (strcat ("ClassificationGAM: 'DoF' and 'Knots'", ... " have been set already.")); endif case 'dof' if (KOD < 2) DoF = varargin{2}; if (! isnumeric (DoF) || ! (isscalar (DoF) || isequal (size (DoF), [1, ndims_X]))) error ("ClassificationGAM: invalid value for 'DoF'."); endif Knots = DoF - Order; Order = DoF - Knots; KOD += 1; else error (strcat ("ClassificationGAM: 'Knots' and 'Order'", ... " have been set already.")); endif case 'learningrate' LearningRate = varargin{2}; if (LearningRate > 1 || LearningRate <= 0) error (strcat ("ClassificationGAM: 'LearningRate'", ... " must be between 0 and 1.")); endif case 'numiterations' NumIterations = varargin{2}; if (! isnumeric (NumIterations) || NumIterations <= 0) error (strcat ("ClassificationGAM: 'NumIterations'", ... " must be a positive integer value.")); endif case 'fitmethod' FitMethod = varargin{2}; if (! (ischar (FitMethod) && isrow (FitMethod)) || ! any (strcmpi (FitMethod, {'boostedtrees', 'splines'}))) error (strcat ("ClassificationGAM: 'FitMethod' must be", ... " 'boostedtrees' or 'splines'.")); endif FitMethod = tolower (FitMethod); case 'numtreesperpredictor' NumTreesPerPredictor = varargin{2}; if (! isnumeric (NumTreesPerPredictor) || ! isscalar (NumTreesPerPredictor) || NumTreesPerPredictor < 1 || fix (NumTreesPerPredictor) != NumTreesPerPredictor) error (strcat ("ClassificationGAM:", ... " 'NumTreesPerPredictor' must be a positive", ... " integer value.")); endif case 'numtreesperinteraction' NumTreesPerInteraction = varargin{2}; if (! isnumeric (NumTreesPerInteraction) || ! isscalar (NumTreesPerInteraction) || NumTreesPerInteraction < 1 || fix (NumTreesPerInteraction) != NumTreesPerInteraction) error (strcat ("ClassificationGAM:", ... " 'NumTreesPerInteraction' must be a", ... " positive integer value.")); endif case 'maxnumsplitsperpredictor' MaxNumSplitsPerPredictor = varargin{2}; if (! isnumeric (MaxNumSplitsPerPredictor) || ! isscalar (MaxNumSplitsPerPredictor) || MaxNumSplitsPerPredictor < 1 || fix (MaxNumSplitsPerPredictor) != MaxNumSplitsPerPredictor) error (strcat ("ClassificationGAM:", ... " 'MaxNumSplitsPerPredictor' must be a", ... " positive integer value.")); endif case 'maxnumsplitsperinteraction' MaxNumSplitsPerInteraction = varargin{2}; if (! isnumeric (MaxNumSplitsPerInteraction) || ! isscalar (MaxNumSplitsPerInteraction) || MaxNumSplitsPerInteraction < 1 || fix (MaxNumSplitsPerInteraction) != MaxNumSplitsPerInteraction) error (strcat ("ClassificationGAM:", ... " 'MaxNumSplitsPerInteraction' must be a", ... " positive integer value.")); endif case 'initiallearnrateforpredictors' InitialLearnRateForPredictors = varargin{2}; if (! isnumeric (InitialLearnRateForPredictors) || ! isscalar (InitialLearnRateForPredictors) || InitialLearnRateForPredictors <= 0 || InitialLearnRateForPredictors > 1) error (strcat ("ClassificationGAM:", ... " 'InitialLearnRateForPredictors' must be", ... " greater than 0 and at most 1.")); endif case 'initiallearnrateforinteractions' InitialLearnRateForInteractions = varargin{2}; if (! isnumeric (InitialLearnRateForInteractions) || ! isscalar (InitialLearnRateForInteractions) || InitialLearnRateForInteractions <= 0 || InitialLearnRateForInteractions > 1) error (strcat ("ClassificationGAM:", ... " 'InitialLearnRateForInteractions' must be", ... " greater than 0 and at most 1.")); endif case 'verbose' Verbose = varargin{2}; if (! isnumeric (Verbose) || ! isscalar (Verbose) || Verbose < 0 || fix (Verbose) != Verbose) error (strcat ("ClassificationGAM: 'Verbose' must be a", ... " non-negative integer value.")); endif case 'numprint' NumPrint = varargin{2}; if (! isnumeric (NumPrint) || ! isscalar (NumPrint) || NumPrint < 1 || fix (NumPrint) != NumPrint) error (strcat ("ClassificationGAM: 'NumPrint' must be a", ... " positive integer value.")); endif case 'maxpvalue' MaxPValue = varargin{2}; if (! isnumeric (MaxPValue) || ! isscalar (MaxPValue) || MaxPValue < 0 || MaxPValue > 1) error (strcat ("ClassificationGAM: 'MaxPValue' must be", ... " between 0 and 1.")); endif otherwise error (strcat ("ClassificationGAM: invalid parameter", ... " name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## An argument belongs to one engine or the other, and asking for one ## the chosen engine cannot honour is refused rather than ignored. The ## alternative is the trap MATLAB sets with a name it accepts and never ## reads: the caller gets a fit that quietly disregarded what it asked ## for. splineOnly = {'knots', 'order', 'dof', 'formula', ... 'learningrate', 'numiterations'}; treeOnly = {'numtreesperpredictor', 'numtreesperinteraction', ... 'maxnumsplitsperpredictor', 'maxnumsplitsperinteraction', ... 'initiallearnrateforpredictors', ... 'initiallearnrateforinteractions', 'maxpvalue', ... 'verbose', 'numprint'}; if (strcmp (FitMethod, 'boostedtrees')) clash = intersect (namesGiven, splineOnly); if (! isempty (clash)) error (strcat ("ClassificationGAM: '", clash{1}, "' is a", ... " parameter of the spline engine and cannot be", ... " used with 'FitMethod' 'boostedtrees'.")); endif else clash = intersect (namesGiven, treeOnly); if (! isempty (clash)) error (strcat ("ClassificationGAM: '", clash{1}, "' is a", ... " parameter of the boosted-tree engine and cannot", ... " be used with 'FitMethod' 'splines'.")); endif endif ## Generate default predictors and response variable names (if necessary) if (isempty (PredictorNames)) for i = 1:columns (X) PredictorNames {i} = strcat ("x", num2str (i)); endfor endif if (isempty (ResponseName)) ResponseName = 'Y'; endif ## Assign predictors and response variable names this.PredictorNames = PredictorNames; this.ResponseName = ResponseName; ## Handle class names if (! isempty (ClassNames)) ## Anything textual is matched as whole names, gnY being grp2idx's ## own cellstr of them. A character matrix is not a cellstr, and ## ismember between two of them compares character by character, so ## it would answer a question nobody asked. if (iscellstr (ClassNames) || ischar (ClassNames)) ru = find (! ismember (gnY, cellstr (ClassNames))); else ru = find (! ismember (glY, ClassNames)); endif for i = 1:numel (ru) gY(gY == ru(i)) = NaN; endfor endif ## An observation is dropped only when its response is missing. A row ## whose predictors hold missing values is kept and reported as used, ## while the fit below draws on the complete observations alone. RowsUsed = ! isnan (gY); ## Index the rows and not the elements: a response naming its ## classes in the rows of a character matrix has one column per ## character, and a linear index flattens the names into single ## letters. Every other accepted type is a column, for which ## the two forms agree. Yret = Y(RowsUsed, :); Xret = X(RowsUsed, :); this.X = Xret; this.Y = Yret; cobs = ! any (isnan (Xret), 2); Y = Yret(cobs, :); X = Xret(cobs, :); ## Renew groups in Y. The third output of grp2idx holds the levels in ## the type of Y, where the second is always a cell array of character ## vectors, so a numeric or logical response keeps its own type. grp2idx ## orders a character or cell response by first appearance where MATLAB ## sorts the classes, so the levels are sorted and the indices remapped. [gY, gnY, glY] = grp2idx (Y); if (ischar (glY)) [glY, sidx] = sortrows (glY); else [glY, sidx] = sort (glY); endif remap(sidx) = 1:numel (sidx); gY = remap(gY)(:); gnY = gnY(sidx); this.ClassNames = glY; ## Check that we are dealing only with binary classification if (numel (gnY) > 2) error ("ClassificationGAM: can only be used for binary classification."); endif ## Force Y into the 0/1 coding the fitter needs. This used to be done ## only for a response that was not already numeric, so a numeric one ## reached the fitter as it was given: a response of 1 and 2 seeded the ## model with log (mean (Y) / (1 - mean (Y))) at a mean of 1.5, whose ## argument is negative, and the intercept came back NaN with every ## score along with it. gY indexes the sorted class names, so gY - 1 is ## the coding for any binary response and reproduces a 0/1 one exactly. Y = gY - 1; this.NumObservations = rows (this.X); ## RowsUsed is left empty when every observation was used, as in MATLAB if (all (RowsUsed)) this.RowsUsed = []; else this.RowsUsed = RowsUsed; endif ## Assign the number of original predictors to the ClassificationGAM object this.NumPredictors = ndims_X; ## Assign Cost and compute Prior this.Cost = Cost; if (isstruct (Prior)) Prior = priorFromStruct (Prior, this.ClassNames, ... 'ClassificationGAM'); endif if (ischar (Prior)) if (strcmpi (Prior, 'uniform')) this.Prior = [0.5, 0.5]; elseif (strcmpi (Prior, 'empirical')) counts = histc (gY, 1:2); this.Prior = counts(:)' / sum (counts); endif else ## Numeric prior - normalize to sum to 1 this.Prior = Prior(:)' / sum (Prior); endif ## A scalar 'Knots', 'Order' or 'DoF' applies to every predictor, which ## the validation above accepts but nothing expanded, so the fit indexed ## past the end of a scalar for the second predictor onwards. if (isscalar (Knots)) Knots = repmat (Knots, 1, ndims_X); endif if (isscalar (Order)) Order = repmat (Order, 1, ndims_X); endif DoF = Knots + Order; ## Assign remaining optional parameters this.Formula = Formula; this.Interactions = Interactions; this.Knots = Knots; this.Order = Order; this.DoF = DoF; this.LearningRate = LearningRate; this.NumIterations = NumIterations; this.FitMethod = FitMethod; ## Bookkeeping MATLAB reports alongside the fit, whichever engine ran this.W = priorWeights (this.Prior, gY, ... this.NumObservations); this.CategoricalPredictors = []; this.ExpandedPredictorNames = this.PredictorNames; if (strcmp (FitMethod, 'boostedtrees')) ## The spline parameters describe a scheme that did not run, so they ## are left empty rather than reporting numbers nothing used. this.Knots = []; this.Order = []; this.DoF = []; this.LearningRate = []; this.NumIterations = []; ## The engine wants the response as zeros and ones. Y itself is ## only converted when it was not numeric to begin with, so a numeric ## label of 1 and 2 would arrive unconverted; the group index is the ## encoding that is always right. this = this.fitBoosted (X, gY(:) - 1, Interactions, ... NumTreesPerPredictor, ... NumTreesPerInteraction, ... MaxNumSplitsPerPredictor, ... MaxNumSplitsPerInteraction, ... InitialLearnRateForPredictors, ... InitialLearnRateForInteractions, MaxPValue, ... Verbose, NumPrint); else ## Fit the basic model Inter = mean (Y); [iter, param, res, RSS, intercept] = this.fitGAM (X, Y, Inter, ... Knots, Order, ... LearningRate, ... NumIterations); this.BaseModel.Intercept = intercept; this.BaseModel.Parameters = param; this.BaseModel.Iterations = iter; this.BaseModel.Residuals = res; this.BaseModel.RSS = RSS; this.Intercept = intercept; ## Handle interaction terms (if given) if (F_I > 0) this = this.fitModelwInt (X, Y, Inter, Knots, Order, DoF, ... LearningRate, NumIterations); endif ## The property MATLAB reports is the two-way terms the fitted model ## carries, as predictor index pairs, whatever form they were asked ## for in. The term matrix stays the complete record: it also holds ## the main effects a formula names and any term above two ## predictors, neither of which has a two-column form. this.Interactions = interactionPairs (this.IntMatrix); ## The spline scheme has no tree vocabulary to report, so its ## parameter struct describes itself instead. this.ModelParameters = struct ('Knots', this.Knots, ... 'Order', this.Order, ... 'DoF', this.DoF, ... 'Formula', this.Formula, ... 'Interactions', this.Interactions, ... 'LearningRate', this.LearningRate, ... 'NumIterations', this.NumIterations); endif endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationGAM} {@var{obj} =} addInteractions (@var{obj}, @var{interactions}) ## ## Add interaction terms to a fitted model. ## ## @code{@var{obj} = addInteractions (@var{obj}, @var{interactions})} fits ## the interaction terms named by @var{interactions} on top of the terms ## the model already carries and returns the updated model. The univariate ## fit is left alone, so @code{predict} with ## @qcode{'IncludeInteractions'} set @qcode{false} answers exactly as it ## answered before. ## ## @var{interactions} takes the forms the constructor's ## @qcode{'Interactions'} option takes: a nonnegative integer count of ## terms, a logical matrix with a column per predictor, or @qcode{'all'}. ## ## A model already carrying interaction terms is not extended, which is ## what MATLAB refuses too. A model fitted from a @qcode{'Formula'} names ## every term it has, interactions among them, and is refused for the same ## reason. ## ## Which terms a count selects is this implementation's own: they are ## taken in the order @code{nchoosek} lists the pairs, where MATLAB ranks ## them by how much each contributes. The constructor's option chooses ## the same way, so the two agree with each other. ## ## @seealso{fitcgam, ClassificationGAM} ## @end deftypefn function this = addInteractions (this, interactions) if (nargin != 2) print_usage (); endif ## Which store already holds interaction terms depends on the engine. hasInt = ! isempty (this.IntMatrix); if (strcmp (this.FitMethod, 'boostedtrees') && ! isempty (this.TreeModel)) hasInt = ! isempty (this.TreeModel.Pairs); endif if (hasInt) error (strcat ("ClassificationGAM.addInteractions: adding", ... " interaction terms to a model that already", ... " includes them is not supported.")); endif if (! ((isnumeric (interactions) && isscalar (interactions) && interactions == fix (interactions) && interactions >= 0) || islogical (interactions) || (ischar (interactions) && strcmpi (interactions, 'all')))) error (strcat ("ClassificationGAM.addInteractions: invalid", ... " 'Interactions' parameter.")); endif ## Under the boosted-tree engine the interaction phase simply runs now, ## starting from the predictor phase the model already carries, which is ## the same order the constructor would have run them in. The predictor ## phase is not refitted, so the main effects are untouched and a model ## with interactions added is the model the constructor would have built ## had it been asked for them. if (strcmp (this.FitMethod, 'boostedtrees')) cobs = ! any (isnan (this.X), 2); Xfit = this.X(cobs, :); [~, ~, gY] = uniqueLabels (this.Y(cobs, :)); Yfit = gY(:) - 1; MP = this.ModelParameters; lrInter = MP.InitialLearnRateForInteractions; this = this.fitBoostedInteractions (Xfit, Yfit, interactions, ... MP.NumTreesPerInteraction, ... MP.MaxNumSplitsPerInteraction, ... lrInter, MP.MaxPValue); return; endif ## parseInteractions reads the specification from the property, which ## afterwards holds the pairs the fit settled on, exactly as the ## constructor leaves it. this.Interactions = interactions; this.IntMatrix = this.parseInteractions (); ## The fit sees the complete observations and the response in the ## coding the boosting works in, prepared as the constructor prepares ## them. Knots, Order and DoF are held unexpanded on the object and ## are widened to the interaction columns by fitModelwInt. cobs = ! any (isnan (this.X), 2); Xfit = this.X(cobs, :); [~, ~, gY] = uniqueLabels (this.Y(cobs, :)); Yfit = gY(:) - 1; this = this.fitModelwInt (Xfit, Yfit, mean (Yfit), this.Knots, ... this.Order, this.DoF, this.LearningRate, ... this.NumIterations); this.Interactions = interactionPairs (this.IntMatrix); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationGAM} {@var{label} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {ClassificationGAM} {[@var{label}, @var{score}] =} predict (@var{obj}, @var{XC}) ## @deftypefnx {ClassificationGAM} {[@var{label}, @var{score}] =} predict (@dots{}, @qcode{'IncludeInteractions'}, @var{includeInteractions}) ## ## Predict labels for new data using the Generalized Additive Model (GAM) ## stored in a ClassificationGAM object. ## ## @code{@var{label} = predict (@var{obj}, @var{XC})} returns the predicted ## labels for the data in @var{XC} based on the model stored in the ## ClassificationGAM object, @var{obj}. ## ## @code{[@var{label}, @var{score}] = predict (@var{obj}, @var{XC})} also ## returns @var{score}, which contains the predicted class scores or ## posterior probabilities for each observation. ## ## @code{[@var{label}, @var{score}] = predict (@var{obj}, @var{XC}, ## 'IncludeInteractions', @var{includeInteractions})} allows you to specify ## whether interaction terms should be included when making predictions. ## ## @itemize ## @item ## @var{obj} must be a @qcode{ClassificationGAM} class object. ## @item ## @var{XC} must be an @math{M*P} numeric matrix where each row is an ## observation and each column corresponds to a predictor variable. ## @item ## @var{includeInteractions} is a logical scalar indicating whether to ## include interaction terms in the predictions. ## @end itemize ## ## @seealso{ClassificationGAM, fitcgam} ## @end deftypefn function [labels, scores] = predict (this, XC, varargin) ## Check for sufficient input arguments if (nargin < 2) error ("ClassificationGAM.predict: too few input arguments."); endif ## Check for valid XC if (isempty (XC)) error ("ClassificationGAM.predict: XC is empty."); elseif (this.NumPredictors != columns (XC)) error (strcat ("ClassificationGAM.predict:", ... " XC must have the same number of", ... " predictors as the trained model.")); endif ## Clean XC data notnansf = ! logical (sum (isnan (XC), 2)); XC = XC(notnansf, :); ## Default values for Name-Value Pairs ## Which store holds the interaction terms depends on the engine: the ## spline scheme keeps them as extra columns described by IntMatrix, ## the boosted-tree scheme as surfaces over predictor pairs. hasInt = ! isempty (this.IntMatrix); if (strcmp (this.FitMethod, 'boostedtrees') && ! isempty (this.TreeModel)) hasInt = ! isempty (this.TreeModel.Pairs); endif incInt = hasInt; Cost = this.Cost; ## Parse optional arguments while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'includeinteractions' tmpInt = varargin{2}; if (! islogical (tmpInt) || (tmpInt != 0 && tmpInt != 1)) error (strcat ("ClassificationGAM.predict:", ... " includeinteractions must be a logical value.")); endif ## Check model for interactions if (tmpInt && ! hasInt) error (strcat ("ClassificationGAM.predict: trained model", ... " does not include any interactions.")); endif incInt = tmpInt; otherwise error (strcat ("ClassificationGAM.predict: invalid NAME in", ... " optional pairs of arguments.")); endswitch varargin(1:2) = []; endwhile ## The boosted-tree engine keeps its fit as step functions over bins, ## so a term is a lookup rather than a spline evaluation and the whole ## prediction is one call. It shares everything after it: the cost ## matrix, the label, and the transform. ## An empty TreeModel means no tree fit is present, whatever FitMethod ## says: a default-constructed object has one, and so would a model ## saved before the engine existed. Such an object falls through to ## the spline path rather than indexing into nothing. if (strcmp (this.FitMethod, 'boostedtrees') && ! isempty (this.TreeModel)) ## Excluding the interactions means excluding the constant they ## handed the intercept as well. interc = this.Intercept; if (! incInt && isfield (this.TreeModel, 'PairIntercept')) interc = interc - this.TreeModel.PairIntercept; endif if (! incInt || isempty (this.TreeModel.Pairs)) scores = gamboostpredict (this.BinEdges, ... this.TreeModel.ShapeValues, XC, ... interc); else scores = gamboostpredict (this.BinEdges, ... this.TreeModel.ShapeValues, XC, ... interc, 0, ... this.PairDetectionBinEdges, ... this.TreeModel.PairValues, ... this.TreeModel.Pairs); endif scores = [-scores, scores]; post = 1 ./ (1 + exp (-scores)); numObservations = size (XC, 1); CE = zeros (numObservations, 2); for k = 1:2 for i = 1:2 CE(:, k) = CE(:, k) + post(:, i) * Cost(k, i); endfor endfor [~, minIdx] = min (CE, [], 2); labels = labelsFromIndex (this.ClassNames, minIdx); scores = this.STfun (scores); return; endif ## Choose whether interactions must be included if (incInt) ## Which construction path the model took: an interaction ## list appends its terms to the predictors, a formula ## names every term the model has and replaces them. if (isempty (this.Formula)) ## Append interaction terms to the predictor matrix for i = 1:rows (this.IntMatrix) tindex = logical (this.IntMatrix(i,:)); Xterms = XC(:,tindex); Xinter = ones (rows (XC), 1); for c = 1:sum (tindex) Xinter = Xinter .* Xterms(:,c); endfor ## Append interaction terms XC = [XC, Xinter]; endfor else ## Add selected predictors and interaction terms XN = []; for i = 1:rows (this.IntMatrix) tindex = logical (this.IntMatrix(i,:)); Xterms = XC(:,tindex); Xinter = ones (rows (XC), 1); for c = 1:sum (tindex) Xinter = Xinter .* Xterms(:,c); endfor ## Append selected predictors and interaction terms XN = [XN, Xinter]; endfor XC = XN; endif ## Get parameters and intercept vectors from model with interactions params = this.ModelwInt.Parameters; Interc = this.ModelwInt.Intercept; else ## Get parameters and intercept vectors from base model params = this.BaseModel.Parameters; Interc = this.BaseModel.Intercept; endif ## Predict the raw score from testing data scores = predict_val (params, XC, Interc); ## Expected misclassification cost is defined on the posteriors, which ## the score becomes only under the logistic link, so the label is ## taken from those and not from the score the caller is handed. post = 1 ./ (1 + exp (-scores)); ## Compute the expected misclassification cost matrix numObservations = size (XC, 1); CE = zeros (numObservations, 2); for k = 1:2 for i = 1:2 CE(:, k) = CE(:, k) + post(:, i) * Cost(k, i); endfor endfor ## Select the class with the minimum expected misclassification cost [~, minIdx] = min (CE, [], 2); labels = labelsFromIndex (this.ClassNames, minIdx); ## Apply ScoreTransform scores = this.STfun (scores); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationGAM} {@var{CVMdl} =} crossval (@var{obj}) ## @deftypefnx {ClassificationGAM} {@var{CVMdl} =} crossval (@dots{}, @var{name}, @var{value}) ## ## Cross Validate a Generalized Additive Model classification object. ## ## @code{@var{CVMdl} = crossval (@var{obj})} returns a cross-validated model ## object, @var{CVMdl}, from a trained model, @var{obj}, using 10-fold ## cross-validation by default. ## ## @code{@var{CVMdl} = crossval (@var{obj}, @var{name}, @var{value})} ## specifies additional name-value pair arguments to customize the ## cross-validation process. ## ## @multitable @columnfractions 0.28 0.7 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'KFold'} @tab Specify the number of folds to use in ## k-fold cross-validation. @code{"KFold", @var{k}}, where @var{k} is an ## integer greater than 1. ## ## @item @qcode{'Holdout'} @tab Specify the fraction of the data to ## hold out for testing. @code{"Holdout", @var{p}}, where @var{p} is a ## scalar in the range @math{(0,1)}. ## ## @item @qcode{'Leaveout'} @tab Specify whether to perform ## leave-one-out cross-validation. @code{"Leaveout", @var{Value}}, where ## @var{Value} is 'on' or 'off'. ## ## @item @qcode{'CVPartition'} @tab Specify a @qcode{cvpartition} ## object used for cross-validation. @code{"CVPartition", @var{cv}}, where ## @code{isa (@var{cv}, "cvpartition")} = 1. ## ## @end multitable ## ## @seealso{fitcgam, ClassificationGAM, cvpartition, ## ClassificationPartitionedModel} ## @end deftypefn function CVMdl = crossval (this, varargin) ## Check input if (nargin < 1) error ("ClassificationGAM.crossval: too few input arguments."); endif if (numel (varargin) == 1) error (strcat ("ClassificationGAM.crossval: Name-Value", ... " arguments must be in pairs.")); elseif (numel (varargin) > 2) error (strcat ("ClassificationGAM.crossval: specify only", ... " one of the optional Name-Value paired arguments.")); endif ## Add default values if (this.NumObservations < 10) numFolds = this.NumObservations; else numFolds = 10; endif Holdout = []; Leaveout = 'off'; CVPartition = []; ## Parse extra parameters while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'kfold' numFolds = varargin{2}; if (! (isnumeric (numFolds) && isscalar (numFolds) && (numFolds == fix (numFolds)) && numFolds > 1)) error (strcat ("ClassificationGAM.crossval: 'KFold'", ... " must be an integer value greater than 1.")); endif case 'holdout' Holdout = varargin{2}; if (! (isnumeric (Holdout) && isscalar (Holdout) && Holdout > 0 && Holdout < 1)) error (strcat ("ClassificationGAM.crossval: 'Holdout'", ... " must be a numeric value between 0 and 1.")); endif case 'leaveout' Leaveout = varargin{2}; if (! (ischar (Leaveout) && (strcmpi (Leaveout, 'on') || strcmpi (Leaveout, 'off')))) error (strcat ("ClassificationGAM.crossval: 'Leaveout'", ... " must be either 'on' or 'off'.")); endif case 'cvpartition' CVPartition = varargin{2}; if (! (isa (CVPartition, 'cvpartition'))) error (strcat ("ClassificationGAM.crossval: 'CVPartition'",... " must be a 'cvpartition' object.")); endif otherwise error (strcat ("ClassificationGAM.crossval: invalid",... " parameter name in optional paired arguments.")); endswitch varargin(1:2) = []; endwhile ## Determine the cross-validation method to use. The partition covers ## the observations actually trained on: a row dropped for a missing ## value is not one the folds can use, and including it would leave the ## partition, the stored data and NumObservations disagreeing. The ## response is passed rather than a count so the folds stay stratified. Yused = this.Y; if (! isempty (CVPartition)) partition = CVPartition; elseif (! isempty (Holdout)) partition = cvpartition (Yused, 'Holdout', Holdout); elseif (strcmpi (Leaveout, 'on')) partition = cvpartition (this.NumObservations, 'LeaveOut'); else partition = cvpartition (Yused, 'KFold', numFolds); endif ## Create a cross-validated model object CVMdl = ClassificationPartitionedModel (this, partition); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationGAM} {@var{CVMdl} =} compact (@var{obj}) ## ## Create a CompactClassificationGAM object. ## ## @code{@var{CVMdl} = compact (@var{obj})} creates a compact version of the ## ClassificationGAM object, @var{obj}. ## ## @seealso{fitcgam, ClassificationGAM, CompactClassificationGAM} ## @end deftypefn function CVMdl = compact (this) ## Create a compact model CVMdl = CompactClassificationGAM (this); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationGAM} {@var{m} =} margin (@var{obj}, @var{X}, @var{Y}) ## ## Classification margin of a generalized additive model. ## ## @code{@var{m} = margin (@var{obj}, @var{X}, @var{Y})} returns a column ## vector holding, for each row of @var{X}, the score the model gives its ## true class in @var{Y} less the score it gives the other class. A ## positive margin means the observation is classified correctly, and the ## larger it is the more confidently so. ## ## @seealso{ClassificationGAM, edge, loss, predict} ## @end deftypefn function m = margin (this, X, Y) ## Check for sufficient input arguments if (nargin < 3) error ("ClassificationGAM.margin: too few input arguments."); endif [X, Y] = checkXY_ (this, X, Y, "margin"); [~, scores] = predict (this, X); classes = this.ClassNames; m = zeros (rows (X), 1); ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) idx = gYidx(i); if (isempty (idx)) m(i) = NaN; continue; endif true_score = scores(i, idx); scores(i, idx) = -Inf; m(i) = true_score - max (scores(i,:)); scores(i, idx) = true_score; endfor endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationGAM} {@var{e} =} edge (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationGAM} {@var{e} =} edge (@dots{}, @qcode{"Weights"}, @var{w}) ## ## Classification edge of a generalized additive model. ## ## @code{@var{e} = edge (@var{obj}, @var{X}, @var{Y})} returns the mean of ## the classification margins over the rows of @var{X}. ## ## @code{@var{e} = edge (@dots{}, @qcode{"Weights"}, @var{w})} takes the ## weighted mean instead, with one weight per row of @var{X}. ## ## @seealso{ClassificationGAM, margin, loss, predict} ## @end deftypefn function e = edge (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("ClassificationGAM.edge: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationGAM.edge: Name-Value arguments", ... " must be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, "edge"); ## The weights are normalized within each class to that class's prior, ## which is what the oracle does and is not the same as dividing by ## their total. This used to divide by the total. ## The weights are parsed before anything is computed, so a bad ## Name-Value pair is reported as such rather than after a margin. W = edgeWeights (varargin, Y, this.ClassNames, this.Prior, ... "ClassificationGAM", "edge"); m = margin (this, X, Y); e = sum (W .* m(:)) / sum (W); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationGAM} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationGAM} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Classification loss of a generalized additive model. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} returns the loss of ## the model on the rows of @var{X} against the true labels @var{Y}. ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} accepts the ## following name-value pairs: ## ## @itemize ## @item ## @qcode{"LossFun"} selects the loss. Supported values are ## @qcode{"mincost"}, the default, @qcode{"binodeviance"}, ## @qcode{"classifcost"}, @qcode{"classiferror"}, @qcode{"exponential"}, ## @qcode{"hinge"}, @qcode{"logit"} and @qcode{"quadratic"}. ## @qcode{"mincost"} assigns each observation to the class of least ## expected cost and charges what that assignment costs, so it reads the ## scores as a posterior, which is what this model returns; ## @qcode{"classifcost"} charges what the model's own prediction costs. ## ## @item ## @qcode{"Weights"} holds one weight per row of @var{X}, normalised to ## sum to one before it is applied. ## @end itemize ## ## @seealso{ClassificationGAM, margin, edge, predict} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("ClassificationGAM.loss: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationGAM.loss: Name-Value arguments", ... " must be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, "loss"); ## Parse optional arguments LossFun = 'mincost'; lossnames = {'binodeviance', 'classifcost', 'classiferror', ... 'exponential', 'hinge', 'logit', 'mincost', 'quadratic'}; args = varargin; keep = true (1, numel (args)); for i = 1:2:numel (args) if (strcmpi (args{i}, 'lossfun')) LossFun = args{i+1}; if (! (ischar (LossFun) && isrow (LossFun))) error (strcat ("ClassificationGAM.loss: 'LossFun' must be", ... " a character vector.")); endif LossFun = tolower (LossFun); if (! any (strcmpi (LossFun, lossnames))) error ("ClassificationGAM.loss: unsupported Loss function."); endif keep(i:i+1) = false; endif endfor W = getWeights_ (this, args(keep), rows (X), "loss"); W = W(:) / sum (W); [label, scores] = predict (this, X); classes = this.ClassNames; ## Membership of the true class, as an indicator per class Yind = zeros (rows (X), classCount (classes)); ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) idx = gYidx(i); if (isempty (idx)) L = NaN; return; endif Yind(i, idx) = 1; endfor ## The scalar score of the true class of each observation mj = sum (scores .* Yind, 2); switch (LossFun) case 'classiferror' wrong = zeros (rows (X), 1); for i = 1:rows (X) wrong(i) = ! isequal (label(i), Y(i)); endfor L = sum (W .* wrong); case 'binodeviance' L = sum (W .* log (1 + exp (-2 * mj))); case 'hinge' L = sum (W .* max (0, 1 - mj)); case 'exponential' L = sum (W .* exp (-mj)); case 'logit' L = sum (W .* log (1 + exp (-mj))); case 'quadratic' L = sum (W .* (1 - mj) .^ 2); case 'mincost' ## Each observation is assigned to the class of least expected ## cost, and charged what that assignment actually costs given its ## true class. L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) [~, k] = min (scores(i,:) * this.Cost); true_idx = gYidx(i); L = L + W(i) * this.Cost(true_idx, k); endfor case 'classifcost' ## What the model's own prediction costs, given the true class L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) true_idx = gYidx(i); pred_idx = find (ismember (classes, label(i))); L = L + W(i) * this.Cost(true_idx, pred_idx); endfor endswitch endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationGAM} {@var{label} =} resubPredict (@var{obj}) ## @deftypefnx {ClassificationGAM} {[@var{label}, @var{score}] =} resubPredict (@var{obj}) ## ## Classify the training data with the generalized additive model it was ## fitted on. ## ## @code{@var{label} = resubPredict (@var{obj})} is @code{predict} applied ## to the observations the model was fitted on. ## ## @seealso{ClassificationGAM, predict} ## @end deftypefn function [labels, scores] = resubPredict (this) used = true (rows (this.X), 1); [labels, scores] = predict (this, this.X(used, :)); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationGAM} {@var{m} =} resubMargin (@var{obj}) ## ## Classification margin of a generalized additive model on its training ## data. ## ## @seealso{ClassificationGAM, margin} ## @end deftypefn function m = resubMargin (this) used = true (rows (this.X), 1); m = margin (this, this.X(used, :), this.Y(used)); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationGAM} {@var{e} =} resubEdge (@var{obj}) ## ## Classification edge of a generalized additive model on its training ## data. ## ## @seealso{ClassificationGAM, edge} ## @end deftypefn function e = resubEdge (this) used = true (rows (this.X), 1); e = edge (this, this.X(used, :), this.Y(used)); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationGAM} {@var{L} =} resubLoss (@var{obj}) ## @deftypefnx {ClassificationGAM} {@var{L} =} resubLoss (@dots{}, @var{name}, @var{value}) ## ## Classification loss of a generalized additive model on its training ## data. ## ## @seealso{ClassificationGAM, loss} ## @end deftypefn function L = resubLoss (this, varargin) used = true (rows (this.X), 1); L = loss (this, this.X(used, :), this.Y(used), varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationGAM} {} savemodel (@var{obj}, @var{filename}) ## ## Save a ClassificationGAM object. ## ## @code{savemodel (@var{obj}, @var{filename})} saves each property of a ## ClassificationGAM object into an Octave binary file, the name of which is ## specified in @var{filename}, along with an extra variable, which defines ## the type classification object these variables constitute. Use ## @code{loadmodel} in order to load a classification object into Octave's ## workspace. ## ## @seealso{loadmodel, fitcgam, ClassificationGAM} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error ("ClassificationGAM.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error ("ClassificationGAM.savemodel: FNAME must be a character vector."); endif ## Generate variable for class name classdef_name = 'ClassificationGAM'; ## Create variables from model properties X = this.X; Y = this.Y; NumObservations = this.NumObservations; RowsUsed = this.RowsUsed; NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; BinEdges = this.BinEdges; ResponseName = this.ResponseName; ClassNames = this.ClassNames; Prior = this.Prior; Cost = this.Cost; ScoreTransform = this.ScoreTransform; Formula = this.Formula; Interactions = this.Interactions; Knots = this.Knots; Order = this.Order; DoF = this.DoF; BaseModel = this.BaseModel; ModelwInt = this.ModelwInt; IntMatrix = this.IntMatrix; Intercept = this.Intercept; W = this.W; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; STfun = this.STfun; FitMethod = this.FitMethod; TreeModel = this.TreeModel; ModelParameters = this.ModelParameters; ReasonForTermination = this.ReasonForTermination; PairDetectionBinEdges = this.PairDetectionBinEdges; ## Save classdef name and all model properties as individual variables LearningRate = this.LearningRate; NumIterations = this.NumIterations; HyperparameterOptimizationResults = this.HyperparameterOptimizationResults; save ('-binary', fname, 'classdef_name', 'X', 'Y', 'NumObservations', ... 'RowsUsed', 'NumPredictors', 'PredictorNames', 'BinEdges', ... 'ResponseName', ... 'ClassNames', 'Prior', 'Cost', 'ScoreTransform', 'Formula', ... 'Interactions', 'Knots', 'Order', 'DoF', 'BaseModel', ... 'ModelwInt', 'IntMatrix', 'LearningRate', 'NumIterations', ... 'Intercept', 'W', 'CategoricalPredictors', ... 'ExpandedPredictorNames', 'STfun', 'FitMethod', ... 'TreeModel', 'ModelParameters', 'ReasonForTermination', ... 'PairDetectionBinEdges', ... 'HyperparameterOptimizationResults'); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationGAM} {@var{Mdl} =} resume (@var{obj}, @var{numTrees}) ## ## Resume training a generalized additive model. ## ## @code{@var{Mdl} = resume (@var{obj}, @var{numTrees})} adds ## @var{numTrees} more trees to @var{obj} and returns the result. The ## original model is not modified. ## ## Training continues in the phase that ran last, which is what MATLAB ## does: a model carrying interaction terms gains interaction trees and ## its predictor shape functions are left alone, while a model without ## them gains predictor trees. A round starts at its initial learning ## rate whatever its number, so the model this returns is the model a ## single fit of the combined budget would have produced. ## ## @var{numTrees} must be a positive integer scalar. Resuming raises ## where there is nothing left to gain, rather than returning the model ## unchanged, and it is not available under ## @qcode{'FitMethod', 'splines'}: a backfit that has converged to its ## tolerance has no budget to extend. ## ## @seealso{ClassificationGAM, fitcgam, addInteractions} ## @end deftypefn function Mdl = resume (this, numTrees) if (nargin < 2) error ("ClassificationGAM.resume: Not enough input arguments."); endif if (! strcmp (this.FitMethod, 'boostedtrees')) error (strcat ("ClassificationGAM.resume: resuming is available", ... " only under 'FitMethod', 'boostedtrees'; a spline", ... " backfit stops at its tolerance and has no budget", ... " to extend.")); endif if (! (isnumeric (numTrees) && isscalar (numTrees) && isreal (numTrees) && numTrees > 0 && fix (numTrees) == numTrees)) error (strcat ("ClassificationGAM.resume: NUMTREES must be a", ... " positive integer scalar.")); endif ## The rows the fit saw, coded as the engine takes them. cobs = ! any (isnan (this.X), 2); X = this.X(cobs, :); [~, ~, gY] = uniqueLabels (this.Y(cobs, :)); Y = gY(:) - 1; Mdl = this; MP = this.ModelParameters; reason = this.ReasonForTermination; ntrees = this.NumTrainedTrees; if (isempty (this.TreeModel.Pairs)) ## No interaction phase ever ran, so the predictor phase is the one ## still open. The engine is handed the prediction reached so far and ## returns the increment to add to it. f = gamboostpredict (this.BinEdges, this.TreeModel.ShapeValues, X, ... this.Intercept); M = gamboosttrain (X, Y, 1, numTrees, ... MP.InitialLearnRateForPredictors, ... MP.MaxNumSplitsPerPredictor, 0, MP.NumPrint, f(:)); if (M.NumTrees == 0) error (strcat ("ClassificationGAM.resume: unable to resume", ... " training because the software was unable to", ... " improve the model fit.")); endif sv = this.TreeModel.ShapeValues; for j = 1:numel (sv) sv{j} = sv{j} + M.ShapeValues{j}; endfor Mdl.TreeModel.ShapeValues = sv; Mdl.Intercept = this.Intercept + M.Intercept; MP.NumTreesPerPredictor = MP.NumTreesPerPredictor + M.NumTrees; reason.PredictorTrees = M.ReasonForTermination; ntrees.PredictorTrees = ntrees.PredictorTrees + M.NumTrees; else ## The interaction phase ran last, so it is the one extended. The ## running prediction includes the surfaces already fitted, and the ## new ones are added to them. f = gamboostpredict (this.BinEdges, this.TreeModel.ShapeValues, X, ... this.Intercept, 0, ... this.PairDetectionBinEdges, ... this.TreeModel.PairValues, ... this.TreeModel.Pairs); I = gamboostinter (X, Y, f(:), 1, this.TreeModel.Pairs, numTrees, ... MP.InitialLearnRateForInteractions, ... MP.MaxNumSplitsPerInteraction); if (I.NumTrees == 0) error (strcat ("ClassificationGAM.resume: unable to resume", ... " training because the software was unable to", ... " improve the model fit.")); endif pv = this.TreeModel.PairValues; for k = 1:numel (pv) pv{k} = pv{k} + I.PairValues{k}; endfor Mdl.TreeModel.PairValues = pv; Mdl.TreeModel.PairIntercept = this.TreeModel.PairIntercept ... + I.Intercept; Mdl.Intercept = this.Intercept + I.Intercept; MP.NumTreesPerInteraction = MP.NumTreesPerInteraction + I.NumTrees; reason.InteractionTrees = I.ReasonForTermination; ntrees.InteractionTrees = ntrees.InteractionTrees + I.NumTrees; endif Mdl.ModelParameters = MP; Mdl.ReasonForTermination = reason; Mdl.NumTrainedTrees = ntrees; endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a ClassificationGAM object mdl = ClassificationGAM (1, 1); ## Copy the saved data into the object. Iterate over what was ## saved rather than over fieldnames (mdl): a private property such ## as STfun is written out by savemodel but is not reported by ## fieldnames, so comparing the two sets could never match and every ## load failed. Assignment is legal here because this is a method of ## the class itself. names = fieldnames (data); ## The set methods for these read other properties, and one of them ## rebuilds Coeffs, so they are assigned once everything else is in ## place rather than in the order the file happens to list them. late = ismember (names, {'Cost', 'Prior', 'ScoreTransform', ... 'ResponseTransform'}); names = [names(! late); names(late)]; for i = 1:numel (names) try mdl.(names{i}) = data.(names{i}); catch error ("ClassificationGAM.load_model: invalid model in '%s'.", filename) end_try_catch endfor ## A model written before RowsUsed became a mask stored it as a ## double, which is a valid subscript for nothing. An empty RowsUsed ## means every observation was used and stays an empty double. if (! isempty (mdl.RowsUsed)) mdl.RowsUsed = logical (mdl.RowsUsed); endif endfunction endmethods ## Helper functions methods(Access = private) ## Shared validation for the assessment methods, so each reports under ## its own name. function [X, Y] = checkXY_ (this, X, Y, caller) if (isempty (X)) error ("ClassificationGAM.%s: X is empty.", caller); elseif (this.NumPredictors != columns (X)) error (strcat ("ClassificationGAM.%s: X must have the same number", ... " of predictors as the trained model."), caller); endif if (isempty (Y)) error ("ClassificationGAM.%s: Y is empty.", caller); elseif (rows (X) != rows (Y)) error (strcat ("ClassificationGAM.%s: Y must have the same number", ... " of rows as X."), caller); endif endfunction ## Pull a "Weights" pair out of the optional arguments, defaulting to a ## uniform weight, and reject any other name. function W = getWeights_ (this, args, n, caller) W = ones (n, 1); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("ClassificationGAM.%s: parameter name must be", ... " a character vector."), caller); endif if (strcmpi (args{i}, 'weights')) W = args{i+1}; if (! (isnumeric (W) && isvector (W))) error (strcat ("ClassificationGAM.%s: 'Weights' must be a", ... " numeric vector."), caller); endif if (numel (W) != n) error (strcat ("ClassificationGAM.%s: size of 'Weights' must", ... " equal the number of rows in X."), caller); endif else error (strcat ("ClassificationGAM.%s: invalid parameter name in", ... " optional paired arguments."), caller); endif endfor endfunction ## Determine interactions from Interactions optional parameter ## Fit the model that carries the interaction terms. The constructor and ## Run the interaction phase alone, over a model whose predictor phase is ## already fitted. Shared by addInteractions and by the constructor, so ## that asking for interactions after the fact gives the same model as ## asking for them up front. function this = fitBoostedInteractions (this, X, Y, Interactions, NTI, ... MSI, LRI, MaxPValue) f = gamboostpredict (this.BinEdges, this.TreeModel.ShapeValues, X, ... this.Intercept); ## Residuals of the predictor phase, which is what pairs are tested on. res = Y - 1 ./ (1 + exp (-f)); wanted = -1; pairs = zeros (0, 2); if (ischar (Interactions)) wanted = Inf; elseif (isscalar (Interactions) && ! isempty (Interactions)) wanted = Interactions; elseif (! isempty (Interactions)) pairs = interactionPairs (logical (Interactions)); endif if (wanted > 0 && columns (X) > 1) S = gamboostpairs (X, res); pval = 1 - fcdf (S.F, S.DF1, S.DF2); pval(S.DF1 <= 0) = 1; [pval, ord] = sort (pval); ranked = S.Pairs(ord, :); ranked = ranked(pval <= MaxPValue, :); if (isfinite (wanted) && rows (ranked) > wanted) ranked = ranked(1:wanted, :); endif pairs = ranked; this.PairDetectionBinEdges = S.BinEdges(:); if (isempty (pairs)) warning (strcat ("ClassificationGAM: model does not include", ... " interaction terms because all interaction", ... " terms have p-values greater than the", ... " 'MaxPValue' value, or the software was unable", ... " to improve the model fit.")); endif endif reason = this.ReasonForTermination; ntrees = this.NumTrainedTrees; if (! isempty (pairs)) I = gamboostinter (X, Y, f, 1, pairs, NTI, LRI, MSI); this.Intercept = this.Intercept + I.Intercept; this.PairDetectionBinEdges = I.PairBinEdges(:); this.TreeModel.PairValues = I.PairValues; this.TreeModel.PairIntercept = I.Intercept; reason.InteractionTrees = I.ReasonForTermination; ntrees.InteractionTrees = I.NumTrees; endif this.TreeModel.Pairs = pairs; if (isempty (pairs)) this.TreeModel.PairIntercept = 0; endif this.Interactions = pairs; this.ReasonForTermination = reason; this.NumTrainedTrees = ntrees; MP = this.ModelParameters; if (ischar (Interactions)) MP.Interactions = Interactions; elseif (isempty (Interactions)) MP.Interactions = 0; else MP.Interactions = Interactions; endif this.ModelParameters = MP; endfunction ## Drive the boosted-tree engine: the predictor phase, then a search for ## interactions worth adding, then the interaction phase over whichever ## pairs survived. The two phases share a running fit, so the second ## continues from the prediction the first left rather than starting over, ## which is how MATLAB's own trace behaves. function this = fitBoosted (this, X, Y, Interactions, NTP, NTI, MSP, ... MSI, LRP, LRI, MaxPValue, Verb, NPrint) ## The predictor phase. M = gamboosttrain (X, Y, 1, NTP, LRP, MSP, Verb, NPrint); f = gamboostpredict (M.BinEdges, M.ShapeValues, X, M.Intercept); this.BinEdges = M.BinEdges(:); ## a column cell, as MATLAB reports it this.Intercept = M.Intercept; reason = struct ('PredictorTrees', M.ReasonForTermination, ... 'InteractionTrees', ''); ntrees = struct ('PredictorTrees', M.NumTrees, ... 'InteractionTrees', 0); pairs = zeros (0, 2); pairValues = {}; pairShift = 0; ## How many pairs were asked for. A count or 'all' means search; an ## explicit Nx2 matrix names the pairs outright and skips the test, as ## MATLAB takes a matrix at its word. wanted = -1; if (ischar (Interactions)) wanted = Inf; elseif (isscalar (Interactions) && ! isempty (Interactions)) wanted = Interactions; elseif (! isempty (Interactions)) ## A matrix names the terms outright. This class has always taken ## that as a term matrix, one row per term and one column per ## predictor, so it is converted to the pairs the engine works in ## rather than being mistaken for a two-column list of indices. pairs = interactionPairs (logical (Interactions)); endif if (wanted > 0 && columns (X) > 1) S = gamboostpairs (X, M.Residuals); ## The F ratio becomes a probability through the package's own fcdf, ## which is verified against MATLAB; the engine deliberately does not ## carry a second incomplete beta of its own. pval = 1 - fcdf (S.F, S.DF1, S.DF2); pval(S.DF1 <= 0) = 1; [pval, ord] = sort (pval); ranked = S.Pairs(ord, :); keep = pval <= MaxPValue; ranked = ranked(keep, :); if (isfinite (wanted) && rows (ranked) > wanted) ranked = ranked(1:wanted, :); endif pairs = ranked; this.PairDetectionBinEdges = S.BinEdges(:); if (isempty (pairs)) warning (strcat ("ClassificationGAM: model does not include", ... " interaction terms because all interaction", ... " terms have p-values greater than the", ... " 'MaxPValue' value, or the software was unable", ... " to improve the model fit.")); endif endif if (! isempty (pairs)) I = gamboostinter (X, Y, f, 1, pairs, NTI, LRI, MSI); this.Intercept = this.Intercept + I.Intercept; pairShift = I.Intercept; this.PairDetectionBinEdges = I.PairBinEdges(:); pairValues = I.PairValues; reason.InteractionTrees = I.ReasonForTermination; ntrees.InteractionTrees = I.NumTrees; endif this.Interactions = pairs; this.ReasonForTermination = reason; this.NumTrainedTrees = ntrees; ## The constant the interaction surfaces gave up when they were ## recentred is kept apart from the predictor phase's intercept. The ## Intercept property still reports their sum, as MATLAB's does, but ## predicting without the interactions has to take this part back out ## or it would answer with a constant the main effects never earned. this.TreeModel = struct ('ShapeValues', {M.ShapeValues}, ... 'PairValues', {pairValues}, ... 'Pairs', pairs, ... 'PairIntercept', pairShift); ## MATLAB reports the request rather than the selection here, and the ## selection through the Interactions property. NumPrint and ## VerbosityLevel are reported at their defaults: this engine keeps no ## printed trace, so they are stated rather than accepted and ignored. if (ischar (Interactions)) request = Interactions; elseif (isempty (Interactions)) request = 0; else request = Interactions; endif this.ModelParameters = struct ( ... 'NumPrint', NPrint, ... 'MaxPValue', MaxPValue, ... 'InitialLearnRateForPredictors', LRP, ... 'InitialLearnRateForInteractions', LRI, ... 'NumTreesPerPredictor', NTP, ... 'NumTreesPerInteraction', NTI, ... 'MaxNumSplitsPerPredictor', MSP, ... 'MaxNumSplitsPerInteraction', MSI, ... 'VerbosityLevel', Verb, ... 'Interactions', request, ... 'Version', 1, ... 'Method', 'GAM', ... 'Type', 'classification'); endfunction ## addInteractions both arrive here with IntMatrix already decided and the ## predictors and response prepared as the fit wants them, so the two ## cannot drift: a model given its interactions after the fact is the ## model it would have been had they been asked for at the outset. function this = fitModelwInt (this, X, Y, Inter, Knots, Order, DoF, ... LearningRate, NumIterations) if (isempty (this.Formula)) ## Analyze Interactions optional parameter this.IntMatrix = this.parseInteractions (); ## Append interaction terms to the predictor matrix for i = 1:rows (this.IntMatrix) tindex = logical (this.IntMatrix(i,:)); Xterms = X(:,tindex); Xinter = ones (this.NumObservations, 1); for c = 1:sum (tindex) Xinter = Xinter .* Xterms(:,c); endfor ## Append interaction terms X = [X, Xinter]; endfor else ## Analyze Formula optional parameter this.IntMatrix = this.parseFormula (); ## Add selected predictors and interaction terms XN = []; for i = 1:rows (this.IntMatrix) tindex = logical (this.IntMatrix(i,:)); Xterms = X(:,tindex); Xinter = ones (this.NumObservations, 1); for c = 1:sum (tindex) Xinter = Xinter .* Xterms(:,c); endfor ## Append selected predictors and interaction terms XN = [XN, Xinter]; endfor X = XN; endif ## Update length of Knots, Order, and DoF vectors to match ## the columns of X with the interaction terms Knots = ones (1, columns (X)) * Knots(1); # Knots Order = ones (1, columns (X)) * Order(1); # Order of spline DoF = ones (1, columns (X)) * DoF(1); # Degrees of freedom ## Fit the model with interactions [iter, param, res, RSS, intercept] = this.fitGAM (X, Y, Inter, Knots, ... Order, LearningRate, ... NumIterations); this.ModelwInt.Intercept = intercept; this.ModelwInt.Parameters = param; this.ModelwInt.Iterations = iter; this.ModelwInt.Residuals = res; this.ModelwInt.RSS = RSS; endfunction function intMat = parseInteractions (this) if (islogical (this.Interactions)) ## Check that interaction matrix corresponds to predictors if (numel (this.PredictorNames) != columns (this.Interactions)) error (strcat ("ClassificationGAM: columns in 'Interactions'", ... " matrix must equal to the number of predictors.")); endif intMat = this.Interactions; elseif (isnumeric (this.Interactions)) ## Need to measure the effect of all interactions to keep the best ## performing. Just check that the given number is not higher than ## p*(p-1)/2, where p is the number of predictors. p = this.NumPredictors; if (this.Interactions > p * (p - 1) / 2) error (strcat ("ClassificationGAM: number of interaction terms", ... " requested is larger than all possible", ... " combinations of predictors in X.")); endif ## The pairs are not ranked by how much each contributes, so the ## first ones asked for are taken in the order nchoosek lists them. intMat = pairTerms (p)(1:this.Interactions, :); elseif (strcmpi (this.Interactions, 'all')) ## Calculate all p*(p-1)/2 interaction terms intMat = pairTerms (this.NumPredictors); endif endfunction ## Determine interactions from formula function intMat = parseFormula (this) intMat = []; ## Check formula for syntax if (isempty (strfind (this.Formula, '~'))) error ("ClassificationGAM: invalid syntax in 'Formula'."); endif ## Split formula and keep predictor terms formulaParts = strsplit (this.Formula, '~'); ## Check there is some string after '~' if (numel (formulaParts) < 2) error ("ClassificationGAM: no predictor terms in 'Formula'."); endif predictorString = strtrim (formulaParts{2}); if (isempty (predictorString)) error ("ClassificationGAM: no predictor terms in 'Formula'."); endif ## Split additive terms (between + sign) aterms = strtrim (strsplit (predictorString, '+')); ## Process all terms for i = 1:numel (aterms) ## Find individual terms (string missing ':') if (isempty (strfind (aterms(i), ':'){:})) ## Search PredictorNames to associate with column in X sterms = strcmp (this.PredictorNames, aterms(i)); ## Append to interactions matrix intMat = [intMat; sterms]; else ## Split interaction terms (string contains ':') mterms = strsplit (aterms{i}, ':'); ## Add each individual predictor to interaction term vector iterms = logical (zeros (1, this.NumPredictors)); for t = 1:numel (mterms) iterms = iterms | strcmp (this.PredictorNames, mterms(t)); endfor ## Check that all predictors have been identified if (sum (iterms) != t) error (strcat ("ClassificationGAM: some predictors", ... " have not been identified.")); endif ## Append to interactions matrix intMat = [intMat; iterms]; endif endfor ## Check that all terms have been identified if (! all (sum (intMat, 2) > 0)) error ("ClassificationGAM: some terms have not been identified."); endif endfunction ## Fit the model function [iter, param, res, RSS, intercept] = fitGAM (this, X, Y, Inter, ... Knots, Order, learning_rate, num_iterations) ## The fit is performed by the shared spline engine, which builds and ## factorises each predictor's design once and reduces a boosting round ## to two products against the factors. Mdl = gamtrain (X, Y, Knots, Order, 1, Inter, learning_rate, ... num_iterations); iter = Mdl.Iterations; param = Mdl.Parameters; res = Mdl.Residuals; RSS = Mdl.RSS; intercept = Mdl.Intercept; endfunction ## Set cost endmethods endclassdef ## Helper function function scores = predict_val (params, XC, intercept) ## The shared prediction engine evaluates every additive term and sums ## them. That sum is the log-odds of the second class, so the raw score of ## the first is its negative and the pair sums to zero, as MATLAB's does. f = gampredict (params, XC, intercept, 0); scores = [-f, f]; endfunction %!demo %! ## Train a GAM classifier for binary classification %! ## using specific data and plot the decision boundaries. %! %! ## Define specific data %! X = [1, 2; 2, 3; 3, 3; 4, 5; 5, 5; ... %! 6, 7; 7, 8; 8, 8; 9, 9; 10, 10]; %! Y = [0; 0; 0; 0; 0; ... %! 1; 1; 1; 1; 1]; %! %! ## Train the GAM model %! obj = fitcgam (X, Y, 'Interactions', 'all') %! %! ## Create a grid of values for prediction %! x1 = [min(X(:,1)):0.1:max(X(:,1))]; %! x2 = [min(X(:,2)):0.1:max(X(:,2))]; %! [x1G, x2G] = meshgrid (x1, x2); %! XGrid = [x1G(:), x2G(:)]; %! [labels, score] = predict (obj, XGrid); ## Test constructor %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = [0; 0; 1; 1]; %! PredictorNames = {'Feature1', 'Feature2', 'Feature3'}; %! a = ClassificationGAM (x, y, 'FitMethod', 'splines', ... %! 'PredictorNames', PredictorNames); %! assert_equal (class (a), "ClassificationGAM"); %! assert_equal ({a.X, a.Y, a.NumObservations}, {x, y, 4}) %! assert_equal ({a.NumPredictors, a.ResponseName}, {3, 'Y'}) %! assert_equal (a.ClassNames, [0; 1]) %! assert_equal (a.PredictorNames, PredictorNames) %! assert_equal (a.BaseModel.Intercept, 0) %!test %! load fisheriris %! inds = strcmp (species,'versicolor') | strcmp (species,'virginica'); %! X = meas(inds, :); %! Y = species(inds, :)'; %! Y = strcmp (Y, 'virginica')'; %! a = ClassificationGAM (X, Y, 'FitMethod', 'splines', ... %! 'Formula', 'Y ~ x1 + x2 + x3 + x4 + x1:x2 + x2:x3'); %! assert_equal (class (a), "ClassificationGAM"); %! assert_equal ({a.X, a.Y, a.NumObservations}, {X, Y, 100}) %! assert_equal ({a.NumPredictors, a.ResponseName}, {4, 'Y'}) %! assert_equal (a.ClassNames, logical ([0; 1])) %! assert_equal (a.Formula, 'Y ~ x1 + x2 + x3 + x4 + x1:x2 + x2:x3') %! assert_equal (a.PredictorNames, {'x1', 'x2', 'x3', 'x4'}) %! assert_equal (a.ModelwInt.Intercept, 0) %!test %! X = [2, 3, 5; 4, 6, 8; 1, 2, 3; 7, 8, 9; 5, 4, 3]; %! Y = [0; 1; 0; 1; 1]; %! a = ClassificationGAM (X, Y, 'FitMethod', 'splines', ... %! 'Knots', [4, 4, 4], 'Order', [3, 3, 3]); %! assert_equal (class (a), "ClassificationGAM"); %! assert_equal ({a.X, a.Y, a.NumObservations}, {X, Y, 5}) %! assert_equal ({a.NumPredictors, a.ResponseName}, {3, 'Y'}) %! assert_equal (a.ClassNames, [0; 1]) %! assert_equal (a.PredictorNames, {'x1', 'x2', 'x3'}) %! assert_equal (a.Knots, [4, 4, 4]) %! assert_equal (a.Order, [3, 3, 3]) %! assert_equal (a.DoF, [7, 7, 7]) %! assert_equal (a.BaseModel.Intercept, 0.4055, 1e-1) ## Test Prior calculation %!test %! ## Test uniform prior %! x = [1, 2; 3, 4; 5, 6; 7, 8]; %! y = [0; 0; 1; 1]; %! a = ClassificationGAM (x, y, 'Prior', 'uniform'); %! assert_equal (a.Prior, [0.5, 0.5], 1e-6); %!test %! ## Test empirical prior %! x = [1, 2; 3, 4; 5, 6; 7, 8; 9, 10]; %! y = [0; 0; 0; 1; 1]; %! a = ClassificationGAM (x, y, 'Prior', 'empirical'); %! assert_equal (a.Prior, [0.6, 0.4], 1e-6); %!test %! ## Test numeric prior %! x = [1, 2; 3, 4; 5, 6; 7, 8]; %! y = [0; 0; 1; 1]; %! a = ClassificationGAM (x, y, 'Prior', [0.7, 0.3]); %! assert_equal (a.Prior, [0.7, 0.3], 1e-6); %!test %! ## Test default prior (empirical) %! x = [1, 2; 3, 4; 5, 6; 7, 8; 9, 10; 11, 12]; %! y = [0; 0; 0; 1; 1; 1]; %! a = ClassificationGAM (x, y); %! assert_equal (a.Prior, [0.5, 0.5], 1e-6); %!test %! ## Test prior normalization %! x = [1, 2; 3, 4; 5, 6; 7, 8]; %! y = [0; 0; 1; 1]; %! a = ClassificationGAM (x, y, 'Prior', [2, 1]); %! assert_equal (a.Prior, [2/3, 1/3], 1e-6); ## Test input validation for Prior ## Interactions reports the two-way terms the fitted model carries, as ## predictor index pairs. R2024a's GAM with 'Interactions', 'all' over three ## predictors returns [1 2; 1 3; 2 3], which is what this matches. %!test %! k = (1:60)'; %! X = [mod(k*7,11)-5, mod(k*3,11)-5, mod(k*5,11)-5]; %! y = double (X(:,1).*X(:,2) > 0) + 1; %! Mdl = fitcgam (X, y, "Interactions", "all"); %! assert_equal (Mdl.Interactions, [1, 2; 1, 3; 2, 3]); ## No interactions is an empty list of pairs, keeping its two columns, and ## not an empty matrix of no width. %!test %! k = (1:60)'; %! X = [mod(k*7,11)-5, mod(k*3,11)-5, mod(k*5,11)-5]; %! y = double (X(:,1).*X(:,2) > 0) + 1; %! Mdl = fitcgam (X, y); %! assert_equal (size (Mdl.Interactions), [0, 2]); %! assert_equal (class (Mdl.Interactions), "double"); ## The same kind of value follows whatever form the request took. %!test %! k = (1:60)'; %! X = [mod(k*7,11)-5, mod(k*3,11)-5, mod(k*5,11)-5]; %! y = double (X(:,1).*X(:,2) > 0) + 1; %! Mc = fitcgam (X, y, "Interactions", 2); %! Ml = fitcgam (X, y, "Interactions", logical ([1, 1, 0; 0, 1, 1])); %! assert_equal (Mc.Interactions, [1, 2; 1, 3]); %! assert_equal (Ml.Interactions, [1, 2; 2, 3]); ## A formula names its main effects as terms of the model, and a main effect ## is not an interaction: the term matrix holds all three, Interactions the ## one two-way term among them. %!test %! k = (1:60)'; %! X = [mod(k*7,11)-5, mod(k*3,11)-5, mod(k*5,11)-5]; %! y = double (X(:,1).*X(:,2) > 0) + 1; %! Mdl = fitcgam (X, y, "FitMethod", "splines", ... %! "Formula", "Y ~ x1 + x2 + x1:x2"); %! assert_equal (Mdl.Interactions, [1, 2]); %! assert_equal (rows (Mdl.IntMatrix), 3); ## The compact model carries the same pairs. %!test %! k = (1:60)'; %! X = [mod(k*7,11)-5, mod(k*3,11)-5, mod(k*5,11)-5]; %! y = double (X(:,1).*X(:,2) > 0) + 1; %! Mdl = fitcgam (X, y, "Interactions", "all"); %! assert_equal (compact (Mdl).Interactions, Mdl.Interactions); ## A response naming its classes in the rows of a character matrix is one ## of the documented types and MATLAB accepts it on every classifier. The ## whole surface below was broken and untested, which is why it stayed so. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcgam (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcgam (Xch, Ycell); %! assert_equal (size (Mc.ClassNames), [2, 10]); %! assert_equal (cellstr (Mc.ClassNames), Ms.ClassNames); ## predict returns whole names, not their first letters. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcgam (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcgam (Xch, Ycell); %! pch = predict (Mc, Xch); %! assert_equal (columns (pch), 10); %! assert_equal (cellstr (pch), predict (Ms, Xch)); ## loss, margin and edge read a character response as the same response. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcgam (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcgam (Xch, Ycell); %! assert_equal (loss (Mc, Xch, Ych), loss (Ms, Xch, Ycell), 1e-12); %! assert_equal (margin (Mc, Xch, Ych), margin (Ms, Xch, Ycell), 1e-12); %! assert_equal (edge (Mc, Xch, Ych), edge (Ms, Xch, Ycell), 1e-12); ## A character matrix pads its rows out to the longest name, and the padding ## is part of the name: R2024a reports ClassNames of ['ab '; 'abcd']. %!test %! Xpad = [1 2; 3 4; 1.1 2.1; 3.1 4.1; 1.2 2.2; 3.2 4.2]; %! Ypad = char ({"ab", "abcd", "ab", "abcd", "ab", "abcd"}); %! rand ("state", 1); randn ("state", 1); %! Mp = fitcgam (Xpad, Ypad); %! assert_equal (size (Mp.ClassNames), [2, 4]); %! assert_equal (Mp.ClassNames(1,:), "ab "); ## A row dropped for a missing predictor is the only case that exercises ## indexing the response by row rather than by element. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! Xmiss = Xch; Xmiss(3,2) = NaN; %! rand ("state", 1); randn ("state", 1); Md = fitcgam (Xmiss, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcgam (Xmiss, Ycell); %! assert_equal (size (Md.ClassNames), [2, 10]); %! assert_equal (cellstr (Md.ClassNames), Ms.ClassNames); ## ClassNames may itself be given as a character matrix, which selects the ## classes by whole name: ismember between two character matrices compares ## them character by character and would select by letter. %!test %! load fisheriris %! rand ("state", 1); randn ("state", 1); %! Mf = fitcgam (meas, char (species), ... %! "ClassNames", char ({"versicolor", "virginica"})); %! assert_equal (rows (Mf.ClassNames), 2); %! assert_equal (cellstr (Mf.ClassNames), {"versicolor"; "virginica"}); ## A model fitted from a character response comes back off disk unchanged. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcgam (Xch, Ych); %! fname = tempname (); %! savemodel (Mc, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.ClassNames, Mc.ClassNames); %! assert_equal (predict (M2, Xch), predict (Mc, Xch)); ## crossval carries a character response through cvpartition and back. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcgam (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcgam (Xch, Ycell); %! rand ("state", 2); cvc = crossval (Mc, "KFold", 3); %! rand ("state", 2); cvs = crossval (Ms, "KFold", 3); %! assert_equal (cellstr (kfoldPredict (cvc)), kfoldPredict (cvs)); ## addInteractions fits the interaction terms onto a model that already has ## its univariate ones. The result is the model that would have been fitted ## had the terms been asked for at the outset: both go through one private ## method, so the two cannot drift apart. %!test %! load fisheriris %! bai = ! strcmp (species, "setosa"); %! Xai = meas(bai,2:4); Yai = species(bai); %! Aai = addInteractions (fitcgam (Xai, Yai), "all"); %! Bai = fitcgam (Xai, Yai, "Interactions", "all"); %! assert_equal (Aai.Interactions, Bai.Interactions); %! assert_equal (Aai.ModelwInt, Bai.ModelwInt); %! assert_equal (predict (Aai, Xai), predict (Bai, Xai)); ## The univariate fit is left alone, which is what MATLAB leaves alone too. %!test %! load fisheriris %! bai = ! strcmp (species, "setosa"); %! Xai = meas(bai,2:4); Yai = species(bai); %! Cai = fitcgam (Xai, Yai); %! Aai = addInteractions (Cai, "all"); %! assert_equal (predict (Aai, Xai, "IncludeInteractions", false), ... %! predict (Cai, Xai)); ## A count and a logical matrix name terms as the constructor's option does. %!test %! load fisheriris %! bai = ! strcmp (species, "setosa"); %! Xai = meas(bai,2:4); Yai = species(bai); %! Aai = addInteractions (fitcgam (Xai, Yai, 'FitMethod', 'splines'), 2); %! assert_equal (Aai.Interactions, [1, 2; 1, 3]); %! Lai = addInteractions (fitcgam (Xai, Yai, 'FitMethod', 'splines'), ... %! logical ([1 1 0; 0 1 1])); %! assert_equal (Lai.Interactions, [1, 2; 2, 3]); ## The boosted-tree engine names the same terms from the same specifications, ## but a count takes the pairs the interaction search ranked highest rather ## than the first in index order, so the two engines select differently. ## ## Only the top of the ranking is pinned, and deliberately. On this fixture ## one pair carries an interaction and the other two do not: (2,3) scores ## F 3.25 at p 5.5e-06, while (1,2) and (1,3) score F 0.5599 and F 0.4331, ## both below one, meaning their cells explain less than the noise within ## them. Their p values sit at 0.974 and 0.998, so which of the two the ## search ranks second is decided by 2% of the scale at the very top of it ## and turns over on any change that moves the predictor-phase residuals at ## all. Pinning that order pins nothing about the search, and it broke when ## the tree learner gained its minimum leaf size. What is worth holding is ## that the pair with signal comes first, that a count does not simply take ## the first pair in index order, and that 'all' names every pair. %!test %! load fisheriris %! bai = ! strcmp (species, "setosa"); %! Xai = meas(bai,2:4); Yai = species(bai); %! Aai = addInteractions (fitcgam (Xai, Yai), 2); %! assert_equal (rows (Aai.Interactions), 2); %! assert_equal (Aai.Interactions(1,:), [2, 3]); %! Lai = addInteractions (fitcgam (Xai, Yai), logical ([1 1 0; 0 1 1])); %! assert_equal (Lai.Interactions, [1, 2; 2, 3]); %! All = addInteractions (fitcgam (Xai, Yai), "all"); %! assert_equal (All.Interactions(1,:), [2, 3]); %! assert_equal (sortrows (All.Interactions), [1, 2; 1, 3; 2, 3]); ## A model that already carries interaction terms is not extended, and a ## model fitted from a formula names every term it has, interactions among ## them, so it is refused for the same reason. R2024a refuses both. ## resume continues the phase that ran last, and a model with no interactions ## has only the predictor phase open. Resuming reproduces the model a single ## fit of the combined budget would have produced, which is the oracle every ## test here uses. %!test %! load fisheriris %! X = meas(51:150,:); %! Y = species(51:150); %! A = fitcgam (X, Y, 'NumTreesPerPredictor', 5); %! B = resume (A, 10); %! C = fitcgam (X, Y, 'NumTreesPerPredictor', 15); %! assert_equal (B.ModelParameters.NumTreesPerPredictor, 15); %! [~, sB] = predict (B, X); %! [~, sC] = predict (C, X); %! assert_equal (sB, sC, 1e-12); %!test %! ## A model carrying interactions gains interaction trees, and its predictor %! ## shape functions are left where they were. %! load fisheriris %! X = meas(51:150,:); %! Y = species(51:150); %! A = fitcgam (X, Y, 'NumTreesPerPredictor', 5, 'Interactions', 3, ... %! 'NumTreesPerInteraction', 4); %! B = resume (A, 10); %! assert_equal (B.ModelParameters.NumTreesPerPredictor, 5); %! assert_equal (B.ModelParameters.NumTreesPerInteraction, 14); %! assert_equal (B.TreeModel.ShapeValues, A.TreeModel.ShapeValues); %! C = fitcgam (X, Y, 'NumTreesPerPredictor', 5, 'Interactions', 3, ... %! 'NumTreesPerInteraction', 14); %! [~, sB] = predict (B, X); %! [~, sC] = predict (C, X); %! assert_equal (sB, sC, 1e-12); %!test %! ## The selected pairs survive, and resuming twice accumulates. %! load fisheriris %! X = meas(51:150,:); %! Y = species(51:150); %! A = fitcgam (X, Y, 'NumTreesPerPredictor', 5, 'Interactions', 3, ... %! 'NumTreesPerInteraction', 4); %! B = resume (resume (A, 10), 6); %! assert_equal (B.Interactions, A.Interactions); %! assert_equal (B.ModelParameters.NumTreesPerInteraction, 20); %! assert_equal (B.ModelParameters.NumTreesPerPredictor, 5); %!test %! ## The model handed in is not modified. %! load fisheriris %! X = meas(51:150,:); %! Y = species(51:150); %! A = fitcgam (X, Y, 'NumTreesPerPredictor', 5); %! B = resume (A, 10); %! assert_equal (A.ModelParameters.NumTreesPerPredictor, 5); %! assert_equal (B.ModelParameters.NumTreesPerPredictor, 15); %!error ... %! load fisheriris; ... %! resume (fitcgam (meas(51:150,:), species(51:150), 'NumTreesPerPredictor', 5)) %!error ... %! load fisheriris; ... %! resume (fitcgam (meas(51:150,:), species(51:150), 'FitMethod', 'splines'), 5) %!error ... %! load fisheriris; ... %! resume (fitcgam (meas(51:150,:), species(51:150), 'NumTreesPerPredictor', 5), 0) %!error ... %! load fisheriris; ... %! resume (fitcgam (meas(51:150,:), species(51:150), 'NumTreesPerPredictor', 5), 2.5) %!error ... %! load fisheriris; ... %! resume (fitcgam (meas(51:150,:), species(51:150), 'NumTreesPerPredictor', 5), [1, 2]) %!error ... %! load fisheriris %! bai = ! strcmp (species, "setosa"); %! Mai = fitcgam (meas(bai,2:4), species(bai), "Interactions", 2); %! addInteractions (Mai, "all") %!error ... %! load fisheriris %! bai = ! strcmp (species, "setosa"); %! addInteractions (fitcgam (meas(bai,2:4), species(bai), ... %! "FitMethod", "splines", ... %! "Formula", "Y ~ x1 + x2 + x1:x2"), "all") %!error ... %! load fisheriris %! bai = ! strcmp (species, "setosa"); %! addInteractions (fitcgam (meas(bai,2:4), species(bai)), {1}) %!error ... %! ClassificationGAM (ones (4,2), ones (4,1), 'Prior', [1]) %!error ... %! ClassificationGAM (ones (4,2), ones (4,1), 'Prior', [1, 2, 3]) %!error ... %! ClassificationGAM (ones (4,2), ones (4,1), 'Prior', {1, 2}) %!error ... %! ClassificationGAM (ones (4,2), ones (4,1), 'Prior', 'invalid') ## Test input validation for constructor %!error ClassificationGAM () %!error ... %! ClassificationGAM (ones (4, 1)) %!error ... %! ClassificationGAM (ones (4,2), ones (1,4)) %!error ... %! ClassificationGAM (ones (5,2), ones (5,1), 'PredictorNames', ['A']) %!error ... %! ClassificationGAM (ones (5,2), ones (5,1), 'PredictorNames', 'A') %!error ... %! ClassificationGAM (ones (5,2), ones (5,1), 'PredictorNames', {'A', 'B', 'C'}) %!error ... %! ClassificationGAM (ones (5,2), ones (5,1), 'ResponseName', {'Y'}) %!error ... %! ClassificationGAM (ones (5,2), ones (5,1), 'ResponseName', 1) %!error ... %! ClassificationGAM (ones (10,2), ones (10,1), 'ClassNames', @(x)x) %!error ... %! ClassificationGAM (ones (10,2), ones (10,1), 'ClassNames', {1}) %!error ... %! ClassificationGAM (ones (10,2), ones (10,1), 'ClassNames', [1, 2]) %!error ... %! ClassificationGAM (ones (5,2), ['a';'b';'a';'a';'b'], 'ClassNames', ['a';'c']) %!error ... %! ClassificationGAM (ones (5,2), {'a';'b';'a';'a';'b'}, 'ClassNames', {'a','c'}) %!error ... %! ClassificationGAM (ones (10,2), logical (ones (10,1)), 'ClassNames', [true, false]) %!error ... %! ClassificationGAM (ones (5,2), ones (5,1), 'Cost', [1, 2]) %!error ... %! Mdl = fitcgam ([1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1], [0; 0; 1; 1]); %! Mdl.Cost = 1:4; %!error ... %! ClassificationGAM (ones (5,2), ones (5,1), 'Cost', 'string') %!error ... %! ClassificationGAM (ones (5,2), ones (5,1), 'Cost', {eye(2)}) ## Test predict method %!test %! x = [1, 2; 3, 4; 5, 6; 7, 8; 9, 10]; %! y = [1; 0; 1; 0; 1]; %! a = ClassificationGAM (x, y, 'FitMethod', 'splines', ... %! 'interactions', 'all'); %! l = [1; 0; 1; 0; 1]; %! s = [0.0334, 0.9666; 0.9648, 0.0352; 0.0334, 0.9666; ... %! 0.9648, 0.0352; 0.0334, 0.9666]; %! [labels, scores] = predict (a, x); %! assert_equal (class (a), "ClassificationGAM"); %! assert_equal ({a.X, a.Y, a.NumObservations}, {x, y, 5}) %! assert_equal ({a.NumPredictors, a.ResponseName}, {2, 'Y'}) %! assert_equal (a.ClassNames, [0; 1]) %! assert_equal (a.PredictorNames, {'x1', 'x2'}) %! assert_equal (a.ModelwInt.Intercept, 0.4055, 1e-1) %! assert_equal (labels, l) %! assert_equal (scores, s, 1e-1) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = [0; 0; 1; 1]; %! interactions = [false, true, false; true, false, true; false, true, false]; %! a = fitcgam (x, y, 'FitMethod', 'splines', ... %! 'learningrate', 0.2, 'interactions', interactions); %! [label, score] = predict (a, x, 'includeinteractions', true); %! l = [0; 0; 1; 1]; %! s = [0.9725, 0.0275; 0.9895, 0.0105; 0.0070, 0.9930; 0.0238, 0.9762]; %! assert_equal (class (a), "ClassificationGAM"); %! assert_equal ({a.X, a.Y, a.NumObservations}, {x, y, 4}) %! assert_equal ({a.NumPredictors, a.ResponseName}, {3, 'Y'}) %! assert_equal (a.ClassNames, [0; 1]) %! assert_equal (a.PredictorNames, {'x1', 'x2', 'x3'}) %! assert_equal (a.ModelwInt.Intercept, 0) %! assert_equal (label, l) %! assert_equal (score, s, 1e-1) ## Test input validation for predict method %!error ... %! predict (ClassificationGAM (ones (4,2), ones (4,1))) %!error ... %! predict (ClassificationGAM (ones (4,2), ones (4,1)), []) %!error ... %! predict (ClassificationGAM (ones (4,2), ones (4,1)), 1) ## Test crossval method ## A numeric response is coded 0/1 for the fitter whatever its own labels ## are. A response of 1 and 2 used to reach the fitter as it was given: the ## seed is log (mean (Y) / (1 - mean (Y))), whose argument is negative at a ## mean of 1.5, so the intercept came back NaN and took every score with it, ## and predict answered class 1 for every row. %!test %! Xn = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! Mn = fitcgam (Xn, [1; 1; 2; 2], 'FitMethod', 'splines'); %! assert_equal (Mn.BaseModel.Intercept, 0, 1e-12); %! [label, score] = predict (Mn, Xn); %! assert_equal (label, [1; 1; 2; 2]); %! assert_equal (all (isfinite (score(:))), true); ## Labels nowhere near 0 and 1 fit the same way, the coding being the class ## index rather than the label. %!test %! Xn = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! Mn = fitcgam (Xn, [5; 5; 9; 9], 'FitMethod', 'splines'); %! assert_equal (Mn.BaseModel.Intercept, 0, 1e-12); %! assert_equal (predict (Mn, Xn), [5; 5; 9; 9]); ## A response already coded 0 and 1 is unchanged by that coding. %!test %! Xn = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! M0 = fitcgam (Xn, [0; 0; 1; 1], 'FitMethod', 'splines'); %! assert_equal (M0.BaseModel.Intercept, 0, 1e-12); %! assert_equal (predict (M0, Xn), [0; 0; 1; 1]); ## The boosted engine fits its predictors by local scoring: the working ## response and the weights are formed once per cycle and held fixed across ## it, so the first predictor sees the residual it would see fitted alone and ## each one after sees what the earlier ones took out. Measured against ## R2024a over two predictors at one tree each, on the two overlapping species ## with every third label flipped. %!test %! load fisheriris %! X = meas(51:150,1:2); %! Y = species(51:150); %! f = 1:3:100; %! t = Y(f); %! t(strcmp (t, 'versicolor')) = {'ZZ'}; %! t(strcmp (t, 'virginica')) = {'versicolor'}; %! t(strcmp (t, 'ZZ')) = {'virginica'}; %! Y(f) = t; %! o = {'NumTreesPerPredictor', 1, 'MaxNumSplitsPerPredictor', 1, ... %! 'Interactions', 0}; %! g1 = (4.8:0.1:8.0)'; %! g2 = (1.9:0.1:3.9)'; %! M = fitcgam (X, Y, o{:}); %! M.ScoreTransform = 'none'; %! [~, s0] = predict (M, [4.8, 1.9]); %! [~, sA] = predict (M, [g1, repmat(1.9, numel (g1), 1)]); %! [~, sB] = predict (M, [repmat(4.8, numel (g2), 1), g2]); %! ## the first predictor is what it is fitted alone, the second is not %! assert_equal (max (sA(:,2)) - s0(1,2), 1.19047619047619, 1e-12); %! assert_equal (max (sB(:,2)) - s0(1,2), 1.04232804232804, 1e-12); %! A1 = fitcgam (X(:,1), Y, o{:}); %! A1.ScoreTransform = 'none'; %! [~, a0] = predict (A1, 4.8); %! [~, aA] = predict (A1, g1); %! assert_equal (max (aA(:,2)) - a0(1,2), 1.19047619047619, 1e-12); %! A2 = fitcgam (X(:,2), Y, o{:}); %! A2.ScoreTransform = 'none'; %! [~, b0] = predict (A2, 1.9); %! [~, bB] = predict (A2, g2); %! assert_equal (max (bB(:,2)) - b0(1,2), 1.33333333333333, 1e-12); ## Swapping the predictors swaps which of them is fitted first, so both shape ## functions change. R2024a again. %!test %! load fisheriris %! X = meas(51:150,1:2); %! Y = species(51:150); %! f = 1:3:100; %! t = Y(f); %! t(strcmp (t, 'versicolor')) = {'ZZ'}; %! t(strcmp (t, 'virginica')) = {'versicolor'}; %! t(strcmp (t, 'ZZ')) = {'virginica'}; %! Y(f) = t; %! o = {'NumTreesPerPredictor', 1, 'MaxNumSplitsPerPredictor', 1, ... %! 'Interactions', 0}; %! S = fitcgam (X(:,[2, 1]), Y, o{:}); %! S.ScoreTransform = 'none'; %! g1 = (4.8:0.1:8.0)'; %! g2 = (1.9:0.1:3.9)'; %! [~, t0] = predict (S, [1.9, 4.8]); %! [~, tA] = predict (S, [repmat(1.9, numel (g1), 1), g1]); %! [~, tB] = predict (S, [g2, repmat(4.8, numel (g2), 1)]); %! assert_equal (max (tB(:,2)) - t0(1,2), 1.33333333333333, 1e-12); %! assert_equal (max (tA(:,2)) - t0(1,2), 1.04497354497354, 1e-12); ## The intercept is the working response's own weighted mean and not the base ## log-odds of the response. A balanced fixture cannot tell them apart, both ## being zero, so this one is unbalanced: 42 against 28, whose log-odds is ## -0.40546510810816 and whose first-cycle intercept R2024a reports as exactly ## -0.4. Two and three cycles are pinned too, the weights being refreshed at ## the start of each. %!test %! load fisheriris %! ii = [51:100, 101:120]'; %! X = meas(ii,1:2); %! Y = species(ii); %! f = 1:4:numel (ii); %! t = Y(f); %! t(strcmp (t, 'versicolor')) = {'ZZ'}; %! t(strcmp (t, 'virginica')) = {'versicolor'}; %! t(strcmp (t, 'ZZ')) = {'virginica'}; %! Y(f) = t; %! assert_equal (sum (strcmp (Y, 'virginica')), 28); %! M1 = fitcgam (X, Y, 'NumTreesPerPredictor', 1, ... %! 'MaxNumSplitsPerPredictor', 1, 'Interactions', 0); %! assert_equal (M1.Intercept, -0.4, 1e-12); %! M2 = fitcgam (X, Y, 'NumTreesPerPredictor', 2, ... %! 'MaxNumSplitsPerPredictor', 1, 'Interactions', 0); %! assert_equal (M2.Intercept, -0.43634347574616, 1e-11); %! M3 = fitcgam (X, Y, 'NumTreesPerPredictor', 3, ... %! 'MaxNumSplitsPerPredictor', 1, 'Interactions', 0); %! assert_equal (M3.Intercept, -0.4460203997322, 1e-11); ## The coding of a numeric response is a property of the class, not of either ## engine: the boosted-tree engine reads labels of 1 and 2, or 5 and 9, the ## same way and returns them in their own values. ## ## Four observations cannot be split at all under the tree learner's minimum ## leaf size, so the boosted fit here is a constant and every row comes back ## as the first class. R2024a answers exactly the same, [1 1 1 1], [5 5 5 5] ## and [0 0 0 0], and takes its first split at ten observations as this does. ## What the test pins is therefore the coding and not the separation: a ## response of 5 and 9 comes back as 5 rather than as a 0/1 index. The ## spline engine above, which does not fit trees, still separates these four. %!test %! Xn = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! assert_equal (predict (fitcgam (Xn, [1; 1; 2; 2]), Xn), [1; 1; 1; 1]); %! assert_equal (predict (fitcgam (Xn, [5; 5; 9; 9]), Xn), [5; 5; 5; 5]); %! assert_equal (predict (fitcgam (Xn, [0; 0; 1; 1]), Xn), [0; 0; 0; 0]); ## Ten observations is where a boosted fit first splits, two leaves of the ## minimum five. Measured against R2024a, which does the same. %!test %! Y9 = repmat ({'a'}, 9, 1); Y9(6:9) = {'b'}; %! M9 = fitcgam ((1:9)', Y9, 'NumTreesPerPredictor', 1, ... %! 'MaxNumSplitsPerPredictor', 1, 'Interactions', 0); %! M9.ScoreTransform = 'none'; %! [~, s9] = predict (M9, (1:9)'); %! assert_equal (max (s9(:,2)) - min (s9(:,2)), 0, 1e-12); %! Y10 = repmat ({'a'}, 10, 1); Y10(6:10) = {'b'}; %! M10 = fitcgam ((1:10)', Y10, 'NumTreesPerPredictor', 1, ... %! 'MaxNumSplitsPerPredictor', 1, 'Interactions', 0); %! M10.ScoreTransform = 'none'; %! [~, s10] = predict (M10, (1:10)'); %! assert_equal (max (s10(:,2)) - min (s10(:,2)) > 1, true); %!shared x, y, obj %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1; 4, 5, 6]; %! y = [0; 0; 1; 1; 0]; %! obj = fitcgam (x, y); %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (obj); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (CVMdl.KFold == 5, true) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationGAM") %! assert_equal (CVMdl.CrossValidatedModel, "GAM") %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (obj, 'KFold', 2); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (CVMdl.KFold == 2, true) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationGAM") %! assert_equal (CVMdl.CrossValidatedModel, "GAM") %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (obj, 'HoldOut', 0.2); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationGAM") %! assert_equal (CVMdl.CrossValidatedModel, "GAM") %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! partition = cvpartition (y, 'KFold', 3); %! warning (status); %! CVMdl = crossval (obj, 'cvPartition', partition); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal (CVMdl.KFold == 3, true) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationGAM") %! assert_equal (CVMdl.CrossValidatedModel, "GAM") ## Test input validation for crossval method %!error ... %! crossval (obj, 'kfold') %!error... %! crossval (obj, 'kfold', 12, 'holdout', 0.2) %!error ... %! crossval (obj, 'kfold', 'a') %!error ... %! crossval (obj, 'holdout', 2) %!error ... %! crossval (obj, 'leaveout', 1) %!error ... %! crossval (obj, 'cvpartition', 1) %!error ... %! savemodel (ClassificationGAM ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])) %!error ... %! savemodel (ClassificationGAM ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), 1) %!error ... %! savemodel (ClassificationGAM ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), ['ab'; 'cd']) ## A ScoreTransform is stored under its own name. That it reaches the ## scores is covered above, through predict. %!test %! Mdl = fitcgam ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]); %! Mdl.ScoreTransform = 'symmetric'; %! assert_equal (class (Mdl.ScoreTransform), 'char'); %! assert_equal (Mdl.ScoreTransform, 'symmetric'); ## The new properties MATLAB reports are carried and saved. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds), 'FitMethod', 'splines'); %! assert_equal (Mdl.Intercept, Mdl.BaseModel.Intercept); %! assert_equal (size (Mdl.W), [Mdl.NumObservations, 1]); %! assert_equal (sum (Mdl.W), 1, 1e-12); %! assert_equal (Mdl.CategoricalPredictors, []); %! assert_equal (Mdl.ExpandedPredictorNames, Mdl.PredictorNames); ## ClassNames keeps the type of the response, as MATLAB has it. %!test %! assert_equal (fitcgam ([1;2;3;4], [7;3;7;3]).ClassNames, [3; 7]); %! assert_equal (fitcgam ([1;2;3;4], logical ([1;0;1;0])).ClassNames, ... %! logical ([0; 1])); %! assert_equal (fitcgam ([1;2;3;4], {'b';'a';'b';'a'}).ClassNames, ... %! {'a'; 'b'}); %! assert_equal (fitcgam ([1;2;3;4], ['b';'a';'b';'a']).ClassNames, ['a';'b']); ## An assigned ScoreTransform reaches the scores predict returns, and it ## composes on the raw log-odds rather than on the posteriors, as measured ## on MATLAB R2024a. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds)); %! Mdl.ScoreTransform = 'none'; %! [~, raw] = predict (Mdl, meas(inds,:)); %! Mdl.ScoreTransform = 'symmetric'; %! [~, s1] = predict (Mdl, meas(inds,:)); %! assert_equal (s1, 2 * raw - 1, 1e-12); ## The edge is the mean of the margins, and weights reweight that mean. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds,:); %! Y = species(inds); %! Mdl = fitcgam (X, Y); %! m = margin (Mdl, X, Y); %! assert_equal (edge (Mdl, X, Y), mean (m), 1e-12); %! ## The weights are normalized within each class to that class's prior, %! ## not divided by their total, so a class keeps the influence its prior %! ## gives it however the weights inside it are spread. %! w = [ones(50, 1); 3 * ones(50, 1)]; %! wn = w; %! wn(1:50) = w(1:50) / sum (w(1:50)) * Mdl.Prior(1); %! wn(51:100) = w(51:100) / sum (w(51:100)) * Mdl.Prior(2); %! assert_equal (edge (Mdl, X, Y, 'Weights', w), ... %! sum (wn .* m) / sum (wn), 1e-12); %! ## Scaling a whole class leaves the edge where it was. %! assert_equal (edge (Mdl, X, Y, 'Weights', w), ... %! edge (Mdl, X, Y, 'Weights', [ones(50,1); ones(50,1)]), 1e-12); ## The resubstitution methods are the assessment methods on the training data. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds,:); %! Y = species(inds); %! Mdl = fitcgam (X, Y); %! assert_equal (resubPredict (Mdl), predict (Mdl, X)); %! assert_equal (resubMargin (Mdl), margin (Mdl, X, Y)); %! assert_equal (resubEdge (Mdl), edge (Mdl, X, Y)); %! assert_equal (resubLoss (Mdl), loss (Mdl, X, Y)); ## Every loss function returns a finite scalar. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds,:); %! Y = species(inds); %! Mdl = fitcgam (X, Y); %! names = {'binodeviance', 'classifcost', 'classiferror', 'exponential', ... %! 'hinge', 'logit', 'mincost', 'quadratic'}; %! for k = 1:numel (names) %! L = loss (Mdl, X, Y, 'LossFun', names{k}); %! assert_equal (isscalar (L) && isfinite (L), true); %! endfor ## A 'Cost' matching the number of classes is accepted. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds)); %! Mdl.Cost = [0, 2; 5, 0]; %! assert_equal (Mdl.Cost, [0, 2; 5, 0]); ## A saved and reloaded model carries every property, and predicts alike. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds,:); %! Mdl = fitcgam (X, species(inds), 'FitMethod', 'splines', ... %! 'Interactions', 'all', 'NumIterations', 20); %! Mdl.ScoreTransform = 'symmetric'; %! fname = tempname (); %! savemodel (Mdl, fname); %! Mdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (Mdl2.Intercept, Mdl.Intercept); %! assert_equal (Mdl2.W, Mdl.W); %! assert_equal (Mdl2.ExpandedPredictorNames, Mdl.ExpandedPredictorNames); %! assert_equal (Mdl2.ModelwInt.Parameters(1).coefs, ... %! Mdl.ModelwInt.Parameters(1).coefs); %! assert_equal (Mdl2.ScoreTransform, 'symmetric'); %! [label, score] = predict (Mdl, X); %! [label2, score2] = predict (Mdl2, X); %! assert_equal (label2, label); %! assert_equal (score2, score); %!shared x, y, Mdl %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! x = meas(inds,:); %! y = species(inds); %! Mdl = fitcgam (x, y); ## Test input validation for margin method %!error ... %! margin (Mdl, x) %!error ... %! margin (Mdl, [], y) %!error ... %! margin (Mdl, 1, y) %!error ... %! margin (Mdl, x, []) %!error ... %! margin (Mdl, x, y(1:10)) ## Test input validation for edge method %!error ... %! edge (Mdl, x) %!error ... %! edge (Mdl, x, y, 'Weights') %!error ... %! edge (Mdl, x, y, 'LossFun', 'hinge') %!error ... %! edge (Mdl, x, y, 'Weights', 'a') %!error ... %! edge (Mdl, x, y, 'Weights', [1, 2, 3]) ## Test input validation for loss method %!error ... %! loss (Mdl, x) %!error ... %! loss (Mdl, x, y, 'LossFun') %!error ... %! loss (Mdl, x, y, 'LossFun', 1) %!error ... %! loss (Mdl, x, y, 'LossFun', 'nonsense') ## RowsUsed is empty when every observation was used. %!test %! load fisheriris %! X = meas(1:100,:); %! Y = grp2idx (species(1:100)); %! Mdl = fitcgam (X, Y); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (class (Mdl.RowsUsed), 'double'); %! assert_equal (Mdl.NumObservations, 100); %! assert_equal (rows (Mdl.X), 100); %! assert_equal (rows (Mdl.W), 100); ## A missing response drops its observation and RowsUsed marks it. %!test %! load fisheriris %! X = meas(1:100,:); %! Y = grp2idx (species(1:100)); %! Y(5) = NaN; %! Mdl = fitcgam (X, Y); %! assert_equal (class (Mdl.RowsUsed), 'logical'); %! assert_equal (size (Mdl.RowsUsed), [100, 1]); %! assert_equal (sum (Mdl.RowsUsed), 99); %! assert_equal (Mdl.RowsUsed(5), false); %! assert_equal (Mdl.NumObservations, 99); %! assert_equal (rows (Mdl.X), 99); %! assert_equal (rows (Mdl.W), 99); ## A missing predictor keeps its observation, so RowsUsed stays empty. %!test %! load fisheriris %! X = meas(1:100,:); %! X(3,2) = NaN; %! Y = grp2idx (species(1:100)); %! Mdl = fitcgam (X, Y); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (Mdl.NumObservations, 100); %! assert_equal (rows (Mdl.X), 100); %! assert_equal (sum (isnan (Mdl.X(:))), 1); ## The prior reweights the observations of each class. Values from R2024a. %!test %! load fisheriris %! i2 = [1:50, 51:80]; %! Mdl = fitcgam (meas(i2,:), species(i2)); %! assert_equal (Mdl.Prior, [0.625, 0.375], 1e-14); %! assert_equal (Mdl.W(1), 0.0125, 1e-14); %! Mdl = fitcgam (meas(i2,:), species(i2), 'Prior', 'uniform'); %! assert_equal (Mdl.W(1), 0.01, 1e-14); %! assert_equal (Mdl.W(51), 1/60, 1e-14); ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds)); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'ClassificationGAM'); %! assert_equal (M2.NumObservations, Mdl.NumObservations); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ScoreTransform), class (Mdl.ScoreTransform)); %! assert_equal (predict (M2, meas(1:5,:)), predict (Mdl, meas(1:5,:))); ## The default transform is 'logit', as MATLAB's is, so the scores predict ## reports are posterior probabilities that sum to one. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds)); %! assert_equal (Mdl.ScoreTransform, 'logit'); %! [~, scores] = predict (Mdl, meas(1:6,:)); %! assert_equal (sum (scores, 2), ones (6, 1), 1e-12); %! assert_equal (all (scores(:) >= 0 & scores(:) <= 1), true); ## The untransformed score is the log-odds pair whose columns sum to zero, ## and the default transform is what maps it to the posteriors. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds)); %! [~, post] = predict (Mdl, meas(1:6,:)); %! Mdl.ScoreTransform = 'none'; %! [~, raw] = predict (Mdl, meas(1:6,:)); %! assert_equal (sum (raw, 2), zeros (6, 1), 1e-12); %! assert_equal (1 ./ (1 + exp (-raw)), post, 1e-12); ## BinEdges reports the cut points the boosted-tree engine binned each ## predictor at, as MATLAB's generalized additive model does. Under the ## spline engine, which does no binning, it stays the empty cell MATLAB ## reports for every learner that does none. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds)); %! assert_equal (class (Mdl.BinEdges), 'cell'); %! assert_equal (numel (Mdl.BinEdges), 4); %! assert_equal (numel (Mdl.BinEdges{1}), 27); %! Msp = fitcgam (meas(inds,:), species(inds), 'FitMethod', 'splines'); %! assert_equal (Msp.BinEdges, {}); ## The shared cost guard is in force here too, and the struct form is ## permuted into this model's class order. The battery is on ## ClassificationDiscriminant. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds)); %! S = struct ('ClassNames', {{'versicolor'; 'setosa'}}, ... %! 'ClassificationCosts', [0, 1; 2, 0]); %! Mdl.Cost = S; %! assert_equal (Mdl.Cost, [0, 2; 1, 0]); %!error ... %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds)); %! Mdl.Cost = ones (2); ## The boosted-tree engine is reachable by name while the default is still ## the spline engine, and it reports the surface MATLAB reports. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds), 'FitMethod', 'boostedtrees'); %! assert_equal (Mdl.FitMethod, 'boostedtrees'); %! assert_equal (numel (Mdl.BinEdges), 4); %! assert_equal (numel (Mdl.BinEdges{1}), 27); %! assert_equal (numel (fieldnames (Mdl.ModelParameters)), 13); %! assert_equal (Mdl.ModelParameters.Type, 'classification'); %! assert_equal (Mdl.ModelParameters.Method, 'GAM'); ## Its defaults are MATLAB's own, reported through ModelParameters. %!test %! Mdl = fitcgam ([1, 2; 2, 3; 3, 4; 4, 5; 5, 6; 6, 7], [0;0;0;1;1;1], ... %! 'FitMethod', 'boostedtrees'); %! MP = Mdl.ModelParameters; %! assert_equal (MP.NumTreesPerPredictor, 300); %! assert_equal (MP.NumTreesPerInteraction, 100); %! assert_equal (MP.MaxNumSplitsPerPredictor, 1); %! assert_equal (MP.MaxNumSplitsPerInteraction, 4); %! assert_equal (MP.InitialLearnRateForPredictors, 1); %! assert_equal (MP.InitialLearnRateForInteractions, 1); %! assert_equal (MP.MaxPValue, 1); %! assert_equal (MP.NumPrint, 10); %! assert_equal (MP.VerbosityLevel, 0); ## Each phase reports why it stopped; a phase that never ran reports nothing. %!test %! Mdl = fitcgam ([1, 2; 2, 3; 3, 4; 4, 5; 5, 6; 6, 7], [0;0;0;1;1;1], ... %! 'FitMethod', 'boostedtrees'); %! assert_equal (isfield (Mdl.ReasonForTermination, 'PredictorTrees'), true); %! assert_equal (isfield (Mdl.ReasonForTermination, 'InteractionTrees'), true); %! assert_equal (Mdl.ReasonForTermination.InteractionTrees, ''); ## Interactions are detected, held on their own coarse grid, and the second ## phase reports its own termination. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds), 'FitMethod', 'boostedtrees', ... %! 'Interactions', 'all'); %! assert_equal (rows (Mdl.Interactions), 6); %! assert_equal (columns (Mdl.Interactions), 2); %! assert_equal (numel (Mdl.PairDetectionBinEdges{1}), 7); %! assert_equal (! isempty (Mdl.ReasonForTermination.InteractionTrees), true); ## A tree-fitted model predicts, and its scores are posteriors summing to one ## under the default transform. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds,:); %! Mdl = fitcgam (X, species(inds), 'FitMethod', 'boostedtrees'); %! [label, score] = predict (Mdl, X); %! assert_equal (numel (label), rows (X)); %! assert_equal (sum (score, 2), ones (rows (X), 1), 1e-12); ## compact carries the engine and every property it needs to predict alike. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds,:); %! Mdl = fitcgam (X, species(inds), 'FitMethod', 'boostedtrees'); %! CMdl = compact (Mdl); %! assert_equal (CMdl.FitMethod, 'boostedtrees'); %! assert_equal (CMdl.BinEdges, Mdl.BinEdges); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); ## savemodel and loadmodel carry the tree fit, so a reloaded model predicts ## identically rather than coming back with an empty engine. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds,:); %! Mdl = fitcgam (X, species(inds), 'FitMethod', 'boostedtrees'); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.FitMethod, 'boostedtrees'); %! assert_equal (M2.TreeModel.ShapeValues, Mdl.TreeModel.ShapeValues); %! assert_equal (predict (M2, X), predict (Mdl, X)); ## The spline engine is unchanged and still reachable by name. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds), 'FitMethod', 'splines'); %! assert_equal (Mdl.FitMethod, 'splines'); %! assert_equal (Mdl.BinEdges, {}); %! assert_equal (isempty (Mdl.TreeModel), true); %! assert_equal (Mdl.Knots, [5, 5, 5, 5]); ## An argument belonging to the other engine is refused, not ignored. %!error ... %! fitcgam ([1;2;3;4], [0;0;1;1], 'FitMethod', 'nonsense') %!error ... %! fitcgam ([1;2;3;4], [0;0;1;1], 'FitMethod', 5) %!error ... %! fitcgam ([1;2;3;4], [0;0;1;1], 'FitMethod', 'boostedtrees', 'Knots', 4) %!error ... %! fitcgam ([1;2;3;4], [0;0;1;1], 'FitMethod', 'splines', 'MaxPValue', 0.5) %!error ... %! fitcgam ([1;2;3;4], [0;0;1;1], 'FitMethod', 'boostedtrees', ... %! 'NumTreesPerPredictor', 0) %!error ... %! fitcgam ([1;2;3;4], [0;0;1;1], 'FitMethod', 'boostedtrees', ... %! 'NumTreesPerInteraction', 1.5) %!error ... %! fitcgam ([1;2;3;4], [0;0;1;1], 'FitMethod', 'boostedtrees', ... %! 'MaxNumSplitsPerPredictor', -1) %!error ... %! fitcgam ([1;2;3;4], [0;0;1;1], 'FitMethod', 'boostedtrees', ... %! 'MaxNumSplitsPerInteraction', 'a') %!error ... %! fitcgam ([1;2;3;4], [0;0;1;1], 'FitMethod', 'boostedtrees', ... %! 'InitialLearnRateForPredictors', 0) %!error ... %! fitcgam ([1;2;3;4], [0;0;1;1], 'FitMethod', 'boostedtrees', ... %! 'InitialLearnRateForInteractions', 2) %!error ... %! fitcgam ([1;2;3;4], [0;0;1;1], 'FitMethod', 'boostedtrees', 'Verbose', -1) %!error ... %! fitcgam ([1;2;3;4], [0;0;1;1], 'FitMethod', 'boostedtrees', 'NumPrint', 0) %!error ... %! fitcgam ([1;2;3;4], [0;0;1;1], 'FitMethod', 'boostedtrees', 'MaxPValue', 2) ## HyperparameterOptimizationResults is declared for MATLAB compatibility and ## stays empty, this class running no search over its hyperparameters. %!test %! load fisheriris %! b = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(b,:), species(b)); %! assert_equal (isempty (Mdl.HyperparameterOptimizationResults), true); ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = fitcgam (meas, strcmp (species, 'setosa')); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = fitcgam (meas, strcmp (species, 'setosa')); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/ClassificationKNN.m000066400000000000000000005521221524624707500257630ustar00rootroot00000000000000## Copyright (C) 2023 Mohammed Azmat Khan ## Copyright (C) 2024 Ruchika Sonagote ## Copyright (C) 2023-2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef ClassificationKNN ## -*- texinfo -*- ## @deftp {statistics} ClassificationKNN ## ## K-nearest neighbors classification ## ## The @code{ClassificationKNN} class implements a K-nearest neighbor ## classifier object, which can predict responses for new data using the ## @code{predict} method. The implemented algorithm allows you choose a range ## of different distance metrics, the number of nearest neighbors, as well as ## the searching algorithm. ## ## The K-nearest neighbors (k-NN) classifier is a simple, non-parametric ## machine learning algorithm used for classification tasks. It classifies a ## data point based on the majority class of its k closest neighbors in the ## feature space. ## ## Create a @code{ClassificationKNN} object by using the @code{fitcknn} ## function or the class constructor. ## ## @seealso{fitcknn} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} W ## ## Observation weights ## ## A numeric column vector with one entry per observation used for fitting. ## Each class carries its prior spread evenly over its own ## observations, so an observation of class @math{k} weighs ## @qcode{Prior(k)} divided by the number of observations in that class. This property is read-only. ## ## Each class carries its prior spread evenly over its own observations, ## so an observation of a class weighs @qcode{Prior} for that class ## divided by the number of observations it holds. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} X ## ## Predictor data ## ## A numeric matrix containing the unstandardized predictor data. Each ## column of @var{X} represents one predictor (variable), and each row ## represents one observation. This property is read-only. ## ## @end deftp X = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} Y ## ## Class labels ## ## Specified as a logical or numeric column vector, or as a character array ## or a cell array of character vectors with the same number of rows as the ## predictor data. Each row in @var{Y} is the observed class label for ## the corresponding row in @var{X}. This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} NumObservations ## ## Number of observations ## ## A positive integer value specifying the number of observations in the ## training dataset used for training the ClassificationKNN model. ## This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} RowsUsed ## ## Rows used for fitting ## ## A logical column vector with the same length as the observations in the ## original predictor data @var{X}, true for each row that was used for ## fitting the ClassificationKNN model. It is empty, @qcode{[]}, ## when every observation was used, so a non-empty value means that rows ## holding missing values were dropped. This property is read-only. ## ## @end deftp RowsUsed = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} NumPredictors ## ## Number of predictors ## ## A positive integer value specifying the number of predictors in the ## training dataset used for training the ClassificationKNN model. ## This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} PredictorNames ## ## Names of predictor variables ## ## A cell array of character vectors specifying the names of the predictor ## variables. The names are in the order in which they appear in the ## training dataset. This property is read-only. ## ## @end deftp PredictorNames = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector of column indices into @code{X} naming the predictors ## treated as categorical, and empty when none is. This property is ## read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} ExpandedPredictorNames ## ## Names of the predictors as the model expanded them ## ## A cell array of character vectors. It matches @code{PredictorNames} ## unless a categorical predictor was expanded into indicator variables. ## This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} ResponseName ## ## Response variable name ## ## A character vector specifying the name of the response variable @var{Y}. ## This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} ClassNames ## ## Names of classes in the response variable ## ## An array of unique values of the response variable @var{Y}, which has the ## same data types as the data in @var{Y}. This property is read-only. ## @qcode{ClassNames} can have any of the following datatypes: ## ## @itemize ## @item Cell array of character vectors ## @item Character array ## @item Logical vector ## @item Numeric vector ## @end itemize ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} Sigma ## ## Predictor standard deviations ## ## A numeric vector of the same length as the columns in @var{X} with the ## standard deviations corresponding to each predictor. If the predictor ## variables have not been standardized, then @qcode{'obj.Sigma'} is empty. ## This property is read-only. ## ## Each predictor is summarized from every observation where that ## predictor is present, so a row holding a missing value in another ## predictor still contributes to this one. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} Mu ## ## Predictor means ## ## A numeric vector of the same length as the columns in @var{X} with the ## mean values corresponding to each predictor. If the predictor variables ## have not been standardized, then @qcode{'obj.Mu'} is empty. This ## property is read-only. ## ## Each predictor is summarized from every observation where that ## predictor is present, so a row holding a missing value in another ## predictor still contributes to this one. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} NSMethod ## ## Nearest neighbor search method ## ## A character vector specified as either @qcode{'kdtree'}, which creates ## and uses a Kd-tree to find nearest neighbors, or @qcode{'exhaustive'}, ## which uses the exhaustive search algorithm by computing the distance ## values from all points in @var{X} to find nearest neighbors. ## ## Change the @qcode{NSMethod} property using dot notation as in: ## @itemize ## @item @qcode{@var{obj}.NSMethod = @var{newNSMethod}} ## @end itemize ## ## @end deftp NSMethod = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} BucketSize ## ## Maximum data points in each node ## ## A positive integer scalar specifying the maximum number of data points in ## the leaf node of the Kd-tree. @qcode{BucketSize} only applies when the ## @qcode{NSMethod} property is @qcode{'kdtree'}. ## ## Change the @qcode{BucketSize} property using dot notation as in: ## @itemize ## @item @qcode{@var{obj}.BucketSize = @var{maxnum}} ## @end itemize ## ## @end deftp BucketSize = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} BinEdges ## ## Bin edges of the predictors ## ## A cell array with one entry per predictor, holding that predictor's bin ## edges where the learner discretized it before fitting. It is empty here ## and stays empty: this learner fits the predictors as they are, and ## MATLAB's reports an empty cell for it as well. ## ## This property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} ModelParameters ## ## Fitting options, as they were given ## ## A structure holding the parameters of the fit: @qcode{NumNeighbors}, ## @qcode{NSMethod}, @qcode{Distance}, @qcode{BucketSize}, ## @qcode{IncludeTies}, @qcode{DistanceWeight}, @qcode{BreakTies}, ## @qcode{Exponent}, @qcode{Cov}, @qcode{Scale}, @qcode{StandardizeData}, ## and the @qcode{Version}, @qcode{Method} and @qcode{Type} tags. ## ## Each of the three distance parameters belongs to one metric and is ## empty under the others: @qcode{Exponent} to @qcode{'minkowski'}, ## @qcode{Cov} to @qcode{'mahalanobis'} and @qcode{Scale} to ## @qcode{'seuclidean'}. @qcode{Cov} and @qcode{Scale} hold what was ## passed and stay empty otherwise, while @qcode{Exponent} carries its ## default of 2 for a @qcode{'minkowski'} fit that did not name one. ## What the fit used in every case is the @qcode{DistParameter} property. ## @qcode{BucketSize} is likewise empty unless the search is ## @qcode{'kdtree'}, the only method that reads it. This property is ## read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} HyperparameterOptimizationResults ## ## Results of the hyperparameter optimization ## ## @strong{Always empty.} It is declared for MATLAB compatibility, where ## it holds what an automatic search over the hyperparameters found. This ## class fits the parameters it is given and runs no such search, so there ## is nothing to report. This property is read-only. ## ## @end deftp HyperparameterOptimizationResults = []; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} NumNeighbors ## ## Number of nearest neighbors ## ## A positive integer value specifyingNumber of nearest neighbors in @var{X} ## used to classify each point during prediction. Change the ## @qcode{NumNeighbors} property using dot notation as in: ## @itemize ## @item @qcode{@var{obj}.NumNeighbors = @var{newNumNeighbors}} ## @end itemize ## ## ## This property may be assigned after fitting. A value larger than ## @qcode{NumObservations} is reduced to it rather than refused. ## @end deftp NumNeighbors = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} Distance ## ## Distance metric ## ## A character vector specifying the distance metric used by the ## neighbor-searcher method, or a function handle to a custom distance ## function. See the available distance metrics in @code{knnsearch} for ## more info. A custom distance function must have the form ## @qcode{@var{D2} = @var{distfun} (@var{ZI}, @var{ZJ})}, where ## @var{ZI} is a @math{1*N} vector containing one row of the predictor ## data, @var{ZJ} is an @math{M2*N} matrix containing multiple rows of the ## predictor data, and @var{D2} is an @math{M2*1} vector of distances ## whose @math{k}-th element is the distance between the observations ## @var{ZI} and @qcode{@var{ZJ}(@var{k},:)}. A custom distance function ## carries no @qcode{DistParameter}. Change the @qcode{Distance} ## property using dot notation as in: ## @itemize ## @item @qcode{@var{obj}.Distance = @var{newDistance}} ## @end itemize ## ## ## This property may be assigned after fitting. @qcode{NSMethod} is ## read-only and constrains it: a @qcode{'kdtree'} model takes ## @qcode{'euclidean'}, @qcode{'cityblock'}, @qcode{'chebychev'} and ## @qcode{'minkowski'} only, and never a function handle. Assigning a ## different metric recomputes @qcode{DistParameter}, since a parameter ## belonging to one metric means nothing under another. ## @end deftp Distance = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} DistanceWeight ## ## Distance weighting function ## ## A character vector or a function handle specifying the distance weighting ## function, which can be any of the following values: ## ## @itemize ## @item @qcode{'equal'}, which corresponds to @code{@@(d) d}. ## @item @qcode{'inverse'}, which corresponds to @code{@@(d) 1/d}. ## @item @qcode{'squaredinverse'}, which corresponds to @code{@@(d) 1/d.^2}. ## @item @qcode{@@fcn}, which is a function handle that accepts a matrix of ## nonnegative distances, and returns a matrix the same size containing ## nonnegative distance weights. ## @end itemize ## ## Change the @qcode{DistanceWeight} property ## using dot notation as in: ## @itemize ## @item @qcode{@var{obj}.DistanceWeight = @var{newDistanceWeight}} ## @end itemize ## ## ## A character vector naming the weight, or the @code{func2str} form of a ## supplied handle. This property may be assigned after fitting. ## @end deftp DistanceWeight = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} BreakTies ## ## Tie-breaking algorithm ## ## A character vector specifying the tie-breaking algorithm used by the ## @code{predict} method, when multiple classes have the same smallest cost. ## It can be one of the following: ## ## @itemize ## @item @qcode{'smallest'} (default), which favors the class with the ## smallest index among the tied groups, i.e. the one that appears first in ## the training labelled data. ## @item @qcode{'nearest'}, which favors the class with the nearest neighbor ## among the tied groups, i.e. the class with the closest member point ## according to the distance metric used. ## @item @qcode{'random'}, which randomly picks one class among the tied ## groups. ## @end itemize ## ## The tie-breaking algorithm is only used when @qcode{IncludeTies} is ## @qcode{false}. Change the @qcode{BreakTies} property using dot notation ## as in: ## @itemize ## @item @qcode{@var{obj}.BreakTies = @var{algorithm}} ## @end itemize ## ## ## This property may be assigned after fitting. It decides the label ## when two classes hold the same weight among the neighbours, and it ## applies whether or not @qcode{IncludeTies} is set. ## @end deftp BreakTies = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} IncludeTies ## ## Flag for handling ties ## ## A logical scalar specifying whether prediction includes all the neighbors ## whose distance values are equal to the @math{k^th} smallest distance. If ## @qcode{IncludeTies} is @qcode{true}, prediction includes all of these ## neighbors. Otherwise, prediction uses exactly @math{k} neighbors. ## ## Change the @qcode{IncludeTies} property using dot notation as in: ## @itemize ## @item @qcode{@var{obj}.IncludeTies = @var{flag}} ## @end itemize ## ## ## This property may be assigned after fitting. ## @end deftp IncludeTies = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} DistParameter ## ## Parameter for distance metric ## ## A positive definite covariance matrix, a positive scalar, or a vector of ## positive scale values specifying the parameter for the corresponding ## distance metric as shown below: ## ## @itemize ## @item @qcode{'mahalanobis'} accepts a positive definite covariance ## matrix. ## @item @qcode{'minkowski'} accepts a positive scalar as the Minkowski ## distance exponent. ## @item @qcode{'seuclidean'} accepts a vector of positive scale values of ## equal length as the number of predictors in @var{X}. ## @end itemize ## ## For any other distance metric, @qcode{DistParameter} is empty ## @qcode{([])}. Change the @qcode{DistParameter} property using dot ## notation as in: ## @itemize ## @item @qcode{@var{obj}.DistParameter = @var{distParam}} ## @end itemize ## ## ## This property may be assigned after fitting, but only under the three ## metrics that carry one: @qcode{'minkowski'}, @qcode{'seuclidean'} and ## @qcode{'mahalanobis'}. Under any other metric there is nothing for it ## to mean and the assignment is refused. ## ## @strong{Deviation from MATLAB.} A @qcode{'seuclidean'} scale of zeros ## is refused here. MATLAB accepts it, then warns from inside its distance ## routine at predict time and answers anyway, which contradicts its own ## message that the scale must hold positive values. A zero scale divides ## that predictor by nothing, so it is rejected where it is given rather ## than surfacing later as a warning attached to an answer. ## @end deftp DistParameter = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} Cost ## ## Cost of Misclassification ## ## A square matrix specifying the cost of misclassification of a point. ## @qcode{Cost(i,j)} is the cost of classifying a point into class @qcode{j} ## if its true class is @qcode{i} (that is, the rows correspond to the true ## class and the columns correspond to the predicted class). The order of ## the rows and columns in @qcode{Cost} corresponds to the order of the ## classes in @qcode{ClassNames}. The number of rows and columns in ## @qcode{Cost} is the number of unique classes in the response. By ## default, @qcode{Cost(i,j) = 1} if @qcode{i != j}, and ## @qcode{Cost(i,j) = 0} if @qcode{i = j}. In other words, the cost is 0 ## for correct classification and 1 for incorrect classification. ## ## Add or change the @qcode{Cost} property using dot notation as in: ## @itemize ## @item @qcode{@var{obj}.Cost = @var{costMatrix}} ## @end itemize ## ## ## A cost may also be given as a struct with the fields ## @qcode{ClassNames} and @qcode{ClassificationCosts}, which names the ## order its own matrix is written in. That matrix is permuted into the ## order of @qcode{ClassNames} above, so a caller need not know which ## order the classes were sorted into. It must name every class. ## ## A cost must be floating point, not sparse, not complex, non-negative ## and zero down its diagonal, and must hold no @qcode{NaN} or ## @qcode{Inf}. A @code{single} is widened to @code{double}. ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} Prior ## ## Prior probability for each class ## ## A numeric vector specifying the prior probabilities for each class. The ## order of the elements in @qcode{Prior} corresponds to the order of the ## classes in @qcode{ClassNames}. ## ## Add or change the @qcode{Prior} property using dot notation as in: ## @itemize ## @item @qcode{@var{obj}.Prior = @var{priorVector}} ## @end itemize ## ## Specified as a row vector with one entry per class, in the order of ## @qcode{ClassNames}, and rescaled to sum to one. It may be given as ## @qcode{'empirical'}, @qcode{'uniform'}, a numeric vector, or a ## structure with @qcode{ClassNames} and @qcode{ClassProbs} fields, which ## assigns each probability by class name rather than by position. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} ScoreTransform ## ## Transformation function for classification scores ## ## Specified as a function handle for transforming the classification ## scores. Add or change the @qcode{ScoreTransform} property using dot ## notation as in: ## ## @itemize ## @item @qcode{@var{obj}.ScoreTransform = 'function_name'} ## @item @qcode{@var{obj}.ScoreTransform = @@function_handle} ## @end itemize ## ## When specified as a character vector, it can be any of the following ## built-in functions. Nevertheless, the @qcode{ScoreTransform} property ## always stores their function handle equivalent. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'doublelogit'} @tab @math{1 ./ (1 + exp (-2 * x))} ## @item @qcode{'invlogit'} @tab @math{log (x ./ (1 - x))} ## @item @qcode{'ismax'} @tab Sets the score for the class with the ## largest score to 1, and for all other classes to 0 ## @item @qcode{'logit'} @tab @math{1 ./ (1 + exp (-x))} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'sign'} @tab ## @math{-1 for x < 0, 0 for x = 0, 1 for x > ## 0} ## @item @qcode{'symmetric'} @tab @math{2 * x - 1} ## @item @qcode{'symmetricismax'} @tab Sets the score for the class ## with the largest score to 1, and for all other classes to -1 ## @item @qcode{'symmetriclogit'} @tab @math{2 ./ (1 + exp (-x)) - 1} ## @end multitable ## ## @end deftp ScoreTransform = 'none'; ## -*- texinfo -*- ## @deftp {ClassificationKNN} {property} CacheSize ## ## Size of the Gram matrix cache ## ## A positive scalar giving the cache size in megabytes, 1000 by default. ## Change the @qcode{CacheSize} property using dot notation as in: ## @itemize ## @item @qcode{@var{obj}.CacheSize = @var{newCacheSize}} ## @end itemize ## ## This property is stored and reported for compatibility and ## @strong{does not affect the fit or any prediction}. A nearest-neighbour ## model keeps no Gram matrix to cache: it holds the training data and ## computes each distance when asked. Assigning it changes nothing but ## the value read back. ## ## MATLAB carries the same property and hides it from @code{properties}, ## where this package reports it, so that a value a user may set is a ## value a user can find. ## @end deftp CacheSize = 1000; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) ## The callable that DistanceWeight names. The property itself holds the ## text, as MATLAB reports it, and predict reaches for this. DWfun = @(d) ones (size (d)); ## Raised once the constructor is done. The set methods validate a user's ## assignment; the values the fit computes for itself are already checked ## where they are built, and one of them, the Mahalanobis covariance, is ## legitimately singular on data whose predictors are collinear. Fitted = false; STfun = @(x) x; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.Cost (this, val) gnY = uniqueLabels (this.Y); if (isempty (val)) this.Cost = cast (! eye (classCount (gnY)), 'double'); else ## Everything a cost must be, and the struct form, which ## is permuted into this model's class order. [val, errmsg] = costMatrix (val, gnY); if (! isempty (errmsg)) error ("ClassificationKNN: %s", errmsg); endif this.Cost = val; endif endfunction function this = set.Prior (this, val) [~, gnY, gY] = uniqueLabels (this.Y); if (isstruct (val)) val = priorFromStruct (val, this.ClassNames, 'ClassificationKNN'); endif if (strcmpi ('uniform', val)) this.Prior = ones (1, numel (gnY)) ./ numel (gnY); elseif (isempty (val) || strcmpi ('empirical', val)) pr = []; for i = 1:numel (gnY) pr = [pr; sum(gY==i)]; endfor this.Prior = pr(:)' ./ sum (pr); elseif (isnumeric (val)) if (numel (gnY) != numel (val)) error (strcat ("ClassificationKNN: the elements in 'Prior' do", ... " not correspond to the selected classes in Y.")); endif this.Prior = val(:)' ./ sum (val); endif ## The weights follow the prior, so they are rebuilt with it this.W = priorWeights (this.Prior, gY, numel (gY)); endfunction ## The six properties a fitted model may be reassigned. All of them are ## live: a model whose Distance or NumNeighbors is changed predicts exactly ## as one refitted with the new value would, because a lazy learner stores ## its training data and does the work at predict time. function this = set.NumNeighbors (this, val) if (! (isnumeric (val) && isscalar (val) && val > 0 && fix (val) == val)) error (strcat ("ClassificationKNN: 'NumNeighbors' must be a", ... " positive integer.")); endif ## More neighbours than there are observations is reduced rather than ## refused, which is what the fit does with the same value. if (! isempty (this.X)) val = min (val, rows (this.X)); endif this.NumNeighbors = val; endfunction function this = set.Distance (this, val) ## The same five the constructor builds a kd-tree for. 'manhattan' is ## a synonym of 'cityblock' and was missing here, so a kd-tree model ## fitted with it could be neither reassigned nor reloaded. kdt = {'euclidean', 'cityblock', 'manhattan', 'chebychev', ... 'minkowski'}; all = [kdt, {'seuclidean', 'mahalanobis', 'manhattan', 'cosine', ... 'correlation', 'spearman', 'hamming', 'jaccard'}]; ## A kd-tree can only be searched with the metrics it was built for, and ## NSMethod is read-only, so the metric is what has to give way. if (strcmpi (this.NSMethod, 'kdtree')) if (! (ischar (val) && any (strcmpi (kdt, val)))) error (strcat ("ClassificationKNN: 'Distance' for a kd-tree", ... " model can only be 'euclidean', 'cityblock',", ... " 'manhattan', 'chebychev', or 'minkowski'.")); endif elseif (! (is_function_handle (val) || (ischar (val) && any (strcmpi (all, val))))) error ("ClassificationKNN: unsupported distance metric."); endif ## A custom metric is probed here as the constructor probes it: called ## on one observation against the training data, it must return one ## distance per row. Taken unchecked, it would defer the failure to ## predict, which names neither the property nor the user's function. if (is_function_handle (val) && ! isempty (this.X)) try D2 = val (this.X(1,:), this.X); catch error (strcat ("ClassificationKNN: invalid function", ... " handle for distance metric.")); end_try_catch if (! isequal (size (D2), [rows(this.X), 1])) error (strcat ("ClassificationKNN: custom distance", ... " function produces wrong output size.")); endif endif if (ischar (val) && ischar (this.Distance) && strcmpi (val, this.Distance)) return; # unchanged, so the parameter it carries is kept endif this.Distance = val; ## The parameter belongs to the metric, so it is recomputed and whatever ## the previous metric held is discarded. if (! isempty (this.X)) this.DistParameter = knndistparam (val, this.X, ! isempty (this.Mu)); endif endfunction function this = set.DistanceWeight (this, val) [f, dw] = parseDistanceWeight (val, 'ClassificationKNN'); this.DistanceWeight = dw; this.DWfun = f; endfunction function this = set.BreakTies (this, val) if (! (ischar (val) && any (strcmpi ({'smallest', 'random', ... 'nearest'}, val)))) error (strcat ("ClassificationKNN: 'BreakTies' must be", ... " 'smallest', 'random' or 'nearest'.")); endif this.BreakTies = lower (val); endfunction function this = set.IncludeTies (this, val) if (! (islogical (val) && isscalar (val))) error (strcat ("ClassificationKNN: 'IncludeTies' must be a", ... " logical scalar.")); endif this.IncludeTies = val; endfunction function this = set.DistParameter (this, val) ## An empty value is the state a metric without a parameter is in, so it ## is always allowed; anything else has to belong to the current metric. if (! isempty (val) && this.Fitted) if (! (ischar (this.Distance) && any (strcmpi ({'minkowski', 'seuclidean', ... 'mahalanobis'}, this.Distance)))) error (strcat ("ClassificationKNN: 'DistParameter' can only be", ... " provided when 'Distance' is 'minkowski',", ... " 'mahalanobis', or 'seuclidean'.")); endif switch (lower (this.Distance)) case 'minkowski' if (! (isnumeric (val) && isscalar (val) && val > 0)) error (strcat ("ClassificationKNN: the exponent for the", ... " Minkowski distance must be a positive", ... " scalar.")); endif case 'seuclidean' if (! (isnumeric (val) && isvector (val) && all (val > 0) && numel (val) == columns (this.X))) error (strcat ("ClassificationKNN: the scale for the", ... " standardized Euclidean distance must be a", ... " vector of positive values, with length", ... " equal to the number of columns in X.")); endif case 'mahalanobis' if (! (isnumeric (val) && issquare (val) && columns (val) == columns (this.X))) error (strcat ("ClassificationKNN: the covariance for the", ... " Mahalanobis distance must be a square", ... " matrix with the same number of columns", ... " as X.")); endif if (! (isequal (val, val') && all (eig (val) > 0))) error (strcat ("ClassificationKNN: the covariance for the", ... " Mahalanobis distance must be symmetric and", ... " positive definite.")); endif endswitch endif this.DistParameter = val; endfunction function this = set.CacheSize (this, val) if (! (isnumeric (val) && isscalar (val) && isreal (val) && isfinite (val) && val > 0)) error (strcat ("ClassificationKNN: 'CacheSize' must be a", ... " positive finite scalar.")); endif this.CacheSize = val; endfunction function this = set.ScoreTransform (this, val) [f, nm] = parseScoreTransform (val, 'ClassificationKNN'); this.ScoreTransform = nm; this.STfun = f; endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n ClassificationKNN\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); if (iscellstr (this.ClassNames)) str = repmat ({'''%s'''}, 1, numel (this.ClassNames)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.ClassNames{:}); elseif (ischar (this.ClassNames)) str = repmat ({'''%s'''}, 1, rows (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, cellstr (this.ClassNames){:}); else # single, double, logical str = repmat ({'%d'}, 1, numel (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.ClassNames); endif fprintf ("%+25s: %s\n", 'ClassNames', str); fprintf ("%+25s: '%s'\n", 'ScoreTransform', this.ScoreTransform); fprintf ("%+25s: %d\n", 'NumObservations', this.NumObservations); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); if (is_function_handle (this.Distance)) fprintf ("%+25s: %s\n", 'Distance', func2str (this.Distance)); else fprintf ("%+25s: '%s'\n", 'Distance', this.Distance); endif fprintf ("%+25s: '%s'\n", 'NSMethod', this.NSMethod); fprintf ("%+25s: %d\n", 'NumNeighbors', this.NumNeighbors); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} ClassificationKNN (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{obj} =} ClassificationKNN (@dots{}, @var{name}, @var{value}) ## ## Create a @qcode{ClassificationKNN} class object containing a k-Nearest ## Neighbor classification model. ## ## @code{@var{obj} = ClassificationKNN (@var{X}, @var{Y})} returns a ## ClassificationKNN object, with @var{X} as the predictor data and @var{Y} ## containing the class labels of observations in @var{X}. ## ## @itemize ## @item ## @code{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. @var{X} will be used to train the kNN model. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} can contain any type ## of categorical data. @var{Y} must have same numbers of Rows as @var{X}. ## @end itemize ## ## @code{@var{obj} = ClassificationKNN (@dots{}, @var{name}, @var{value})} ## returns a ClassificationKNN object with parameters specified by the ## following @qcode{@var{name}, @var{value}} paired input arguments: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PredictorNames'} @tab A cell array of character ## vectors specifying the names of the predictors. The length of this array ## must match the number of columns in @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector specifying the ## name of the response variable. ## ## @item @qcode{'ClassNames'} @tab Names of the classes in the class ## labels, @var{Y}, used for fitting the GAM model. ## @qcode{ClassNames} are of the same type as the class labels in @var{Y}. ## ## @item @qcode{'Cost'} @tab An @math{N*R} numeric matrix containing ## misclassification cost for the corresponding instances in @var{X}, where ## @math{R} is the number of unique categories in @var{Y}. If an instance ## is correctly classified into its category the cost is calculated to be 1, ## otherwise 0. The cost matrix can be altered by using ## @code{@var{Mdl}.cost = somecost}. By default, its value is ## @qcode{@var{cost} = ones (rows (X), numel (unique (Y)))}. ## ## @item @qcode{'Prior'} @tab A numeric vector specifying the prior ## probabilities for each class. The order of the elements in @qcode{Prior} ## corresponds to the order of the classes in @qcode{ClassNames}. ## Alternatively, you can specify @qcode{'empirical'} to use the empirical ## class probabilities or @qcode{'uniform'} to assume equal class ## probabilities. ## ## @item @qcode{'ScoreTransform'} @tab A user-defined function handle ## or a character vector specifying one of the following builtin functions ## specifying the transformation applied to predicted classification scores. ## Supported values include @qcode{'doublelogit'}, @qcode{'invlogit'}, ## @qcode{'ismax'}, @qcode{'logit'}, @qcode{'none'}, @qcode{'identity'}, ## @qcode{'sign'}, @qcode{'symmetric'}, @qcode{'symmetricismax'}, and ## @qcode{'symmetriclogit'}. ## ## @item @qcode{'BreakTies'} @tab A character vector specifying the ## tie-breaking algorithm used by @code{predict} method, when multiple ## classes have the same smallest cost. Available options are ## @qcode{'smallest'} (default), which uses the smallest index among tied ## groups, @qcode{'nearest'}, which uses the class with the nearest neighbor ## among tied groups, and @qcode{'random'}, which randomly selects one of ## the tied groups. ## ## @item @qcode{'NumNeighbors'} @tab A positive integer value that ## specifies the number of nearest neighbors to be found in the kNN search ## algorithm for classifying each point during prediction. By default, ## it is 1. ## ## @item @qcode{'Distance'} @tab Any valid distance metric supported by ## the @code{pdist2} function. Note that the allowable distance metrics ## depend on the selected nearest neighbor search method. ## ## @item @qcode{'DistanceWeight'} @tab Either a distance weighting ## function, specified either as a function handle, which accepts a matrix ## of nonnegative distances and returns a matrix the same size containing ## nonnegative distance weights, or a character vector with one of the ## following values: @qcode{'equal'}, which corresponds to no weighting; ## @qcode{'inverse'}, which corresponds to a weight equal to ## @math{1/distance}; @qcode{'squaredinverse'}, which corresponds to a ## weight equal to @math{1/distance^2}. ## ## @item @qcode{'Cov'} @tab A square matrix with the same number of ## columns @var{X} specifying the covariance matrix for computing the ## mahalanobis distance. This must be a positive definite matrix matching. ## This argument is only valid when the selected distance metric is ## @qcode{'mahalanobis'}. ## ## @item @qcode{'Exponent'} @tab A positive scalar (usually an integer) ## specifying the Minkowski distance exponent. This argument is only valid ## when the selected distance metric is @qcode{'minkowski'}. By default, ## it is 2. ## ## @item @qcode{'Scale'} @tab A nonnegative numeric vector specifying ## the scale parameters for the standardized Euclidean distance. The vector ## length must be equal to the number of columns in @var{X}. This argument ## is only valid when the selected distance metric is @qcode{'seuclidean'}, ## in which case each coordinate of @var{X} is scaled by the corresponding ## element of @qcode{'scale'}, as is each query point in @var{Y}. By ## default, the scale parameter is the standard deviation of each coordinate ## in @var{X}. If a variable in @var{X} is constant, i.e. zero variance, ## this value is forced to 1 to avoid division by zero. This is the ## equivalent of this variable not being standardized. ## ## @item @qcode{'NSMethod'} @tab A character vector specifying the ## nearest neighbor search method used by @code{knnsearch}, which can be ## @qcode{'kdtree'} or @qcode{'exhaustive'}. See @code{knnsearch} for more ## information about default values and allowable distance metrics for each ## search method. ## ## @item @qcode{'BucketSize'} @tab A positive integer value specifying ## the maximum number of data points in the leaf node of the Kd-tree. This ## argument is meaningful only when the selected nearest neighbor search ## method is @qcode{'kdtree'}. By default, it is 50. ## @end multitable ## ## @seealso{fitcknn, knnsearch, rangesearch, pdist2} ## @end deftypefn function this = ClassificationKNN (X, Y, varargin) ## Check for sufficient number of input arguments if (nargin < 2) error ("ClassificationKNN: too few input arguments."); endif ## Check X and Y have the same number of observations if (rows (X) != rows (Y)) error ("ClassificationKNN: number of rows in X and Y must be equal."); endif ## Assign original X and Y data to the ClassificationKNN object this.X = X; this.Y = Y; ## Get groups in Y [gY, gnY, glY] = grp2idx (Y); ## Set default values before parsing optional parameters Standardize = false; PredictorNames = []; ResponseName = []; ClassNames = []; Prior = []; Cost = []; Scale = []; # Distance scale for 'seuclidean' Cov = []; # Covariance matrix for 'mahalanobis' Exponent = []; # Exponent for 'minkowski' BreakTies = []; NumNeighbors = []; Distance = []; DistanceWeight = []; DistParameter = []; NSMethod = []; IncludeTies = false; BucketSize = 50; CacheSize = 1000; ## Number of parameters for Standardize, Scale, Cov (maximum 1 allowed) SSC = 0; ## Parse extra parameters while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'standardize' if (SSC < 1) Standardize = varargin{2}; if (! (Standardize == true || Standardize == false)) error (strcat ("ClassificationKNN: 'Standardize' must", ... " be either true or false.")); endif SSC += 1; else error (strcat ("ClassificationKNN: 'Standardize' cannot", ... " simultaneously be specified with either", ... " Scale or Cov.")); endif case 'predictornames' PredictorNames = varargin{2}; if (! iscellstr (PredictorNames)) error (strcat ("ClassificationKNN: 'PredictorNames' must", ... " be supplied as a cellstring array.")); elseif (columns (PredictorNames) != columns (X)) error (strcat ("ClassificationKNN: 'PredictorNames' must", ... " have the same number of columns as X.")); endif case 'responsename' ResponseName = varargin{2}; if (! ischar (ResponseName)) error (strcat ("ClassificationKNN: 'ResponseName'", ... " must be a character vector.")); endif case 'classnames' ClassNames = varargin{2}; if (! (iscellstr (ClassNames) || isnumeric (ClassNames) || islogical (ClassNames) || ischar (ClassNames))) error (strcat ("ClassificationKNN: 'ClassNames' must be a", ... " cell array of character vectors, a logical", ... " vector, a numeric vector, or a character array.")); endif ## Check that all class names are available in gnY if (iscellstr (ClassNames) || ischar (ClassNames)) ClassNames = cellstr (ClassNames); if (! all (cell2mat (cellfun (@(x) any (strcmp (x, gnY)), ClassNames, 'UniformOutput', false)))) error (strcat ("ClassificationKNN: not all 'ClassNames'", ... " are present in Y.")); endif else if (! all (cell2mat (arrayfun (@(x) any (x == glY), ClassNames, 'UniformOutput', false)))) error (strcat ("ClassificationKNN: not all 'ClassNames'", ... " are present in Y.")); endif endif case 'prior' Prior = varargin{2}; if (! (isstruct (Prior) || (isnumeric (Prior) && isvector (Prior)) || (ischar (Prior) && (strcmpi (Prior, 'empirical') || strcmpi (Prior, 'uniform'))))) error (strcat ("ClassificationKNN: 'Prior' must be either", ... " a numeric vector or a character vector.")); endif case 'cost' Cost = varargin{2}; ## A struct carrying its own class order is a cost too, ## and is resolved by the property's own set method. if (! (isstruct (Cost) || (isnumeric (Cost) && issquare (Cost)))) error (strcat ("ClassificationKNN: 'Cost' must be", ... " a numeric square matrix.")); endif case 'scoretransform' name = 'ClassificationKNN'; this.ScoreTransform = varargin{2}; case 'breakties' BreakTies = varargin{2}; if (! ischar (BreakTies)) error (strcat ("ClassificationKNN: 'BreakTies'", ... " must be a character vector.")); endif ## Check that all class names are available in gnY BTs = {'smallest', 'nearest', 'random'}; if (! any (strcmpi (BTs, BreakTies))) error ("ClassificationKNN: invalid value for 'BreakTies'."); endif case 'numneighbors' NumNeighbors = varargin{2}; if (! (isnumeric (NumNeighbors) && isscalar (NumNeighbors) && NumNeighbors > 0 && fix (NumNeighbors) == NumNeighbors)) error (strcat ("ClassificationKNN: 'NumNeighbors'", ... " must be a positive integer.")); endif case 'distance' Distance = varargin{2}; DMs = {'euclidean', 'seuclidean', 'mahalanobis', 'minkowski', ... 'cityblock', 'manhattan', 'chebychev', 'cosine', ... 'correlation', 'spearman', 'hamming', 'jaccard'}; if (ischar (Distance)) if (! any (strcmpi (DMs, Distance))) error ("ClassificationKNN: unsupported distance metric."); endif elseif (is_function_handle (Distance)) ## Check the input output sizes of the user function D2 = []; try D2 = Distance(X(1,:), X); catch ME error (strcat ("ClassificationKNN: invalid function", ... " handle for distance metric.")); end_try_catch Xrows = rows (X); if (! isequal (size (D2), [Xrows, 1])) error (strcat ("ClassificationKNN: custom distance", ... " function produces wrong output size.")); endif else error ("ClassificationKNN: invalid distance metric."); endif case 'distanceweight' [this.DWfun, this.DistanceWeight] = ... parseDistanceWeight (varargin{2}, 'ClassificationKNN'); DistanceWeight = this.DistanceWeight; case 'scale' if (SSC < 1) Scale = varargin{2}; if (! (isnumeric (Scale) && isvector (Scale))) error ("ClassificationKNN: 'Scale' must be a numeric vector."); endif SSC += 1; else error (strcat ("ClassificationKNN: 'Scale' cannot", ... " simultaneously be specified with either", ... " 'Standardize' or 'Cov'.")); endif case 'cov' if (SSC < 1) Cov = varargin{2}; [~, p] = chol (Cov); if (p != 0) error (strcat ("ClassificationKNN: 'Cov' must be a", ... " symmetric positive definite matrix.")); endif SSC += 1; else error (strcat ("ClassificationKNN: 'Cov' cannot", ... " simultaneously be specified with either", ... " 'Standardize' or 'Scale'.")); endif case 'exponent' Exponent = varargin{2}; if (! (isnumeric (Exponent) && isscalar (Exponent) && Exponent > 0 && fix (Exponent) == Exponent)) error (strcat ("ClassificationKNN: 'Exponent'", ... " must be a positive integer.")); endif case 'nsmethod' NSMethod = varargin{2}; NSM = {'kdtree', 'exhaustive'}; if (! ischar (NSMethod)) error (strcat ("ClassificationKNN: 'NSMethod' must", ... " be a character vector.")); endif if (! any (strcmpi (NSM, NSMethod))) error (strcat ("ClassificationKNN: 'NSMethod' must", ... " be either 'kdtree' or 'exhaustive'.")); endif case 'cachesize' CacheSize = varargin{2}; if (! (isnumeric (CacheSize) && isscalar (CacheSize) && isreal (CacheSize) && isfinite (CacheSize) && CacheSize > 0)) error (strcat ("ClassificationKNN: 'CacheSize' must be a", ... " positive finite scalar.")); endif case 'includeties' IncludeTies = varargin{2}; if (! (IncludeTies == true || IncludeTies == false)) error (strcat ("ClassificationKNN: 'IncludeTies'", ... " must be either true or false.")); endif case 'bucketsize' BucketSize = varargin{2}; if (! (isnumeric (BucketSize) && isscalar (BucketSize) && BucketSize > 0 && fix (BucketSize) == BucketSize)) error (strcat ("ClassificationKNN: 'BucketSize'", ... " must be a positive integer.")); endif otherwise error (strcat ("ClassificationKNN: invalid parameter",... " name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## Generate default predictors and response variable names (if necessary) NumPredictors = columns (X); if (isempty (PredictorNames)) for i = 1:NumPredictors PredictorNames {i} = strcat ("x", num2str (i)); endfor endif if (isempty (ResponseName)) ResponseName = 'Y'; endif ## Assign predictors and response variable names this.NumPredictors = NumPredictors; this.PredictorNames = PredictorNames; this.CategoricalPredictors = []; this.ExpandedPredictorNames = PredictorNames; this.ResponseName = ResponseName; ## Handle class names if (! isempty (ClassNames)) ## Anything textual is matched as whole names, gnY being grp2idx's ## own cellstr of them. A character matrix is not a cellstr, and ## ismember between two of them compares character by character, so ## it would answer a question nobody asked. if (iscellstr (ClassNames) || ischar (ClassNames)) ru = find (! ismember (gnY, cellstr (ClassNames))); else ru = find (! ismember (glY, ClassNames)); endif for i = 1:numel (ru) gY(gY == ru(i)) = NaN; endfor endif ## An observation is dropped only when its response is missing. A row ## whose predictors hold missing values is kept and reported as used, ## while the fit below draws on the complete observations alone. RowsUsed = ! isnan (gY); Yret = Y(RowsUsed, :); Xret = X(RowsUsed, :); this.X = Xret; this.Y = Yret; cobs = ! any (isnan (Xret), 2); Y = Yret(cobs, :); X = Xret(cobs, :); ## Renew groups in Y over the retained observations, so a class held ## only by a row with missing predictors is still a class of the model [this.ClassNames, gnY, gret] = uniqueLabels (Yret); gY = gret(cobs); ## Check X contains valid data if (! (isnumeric (X) && isfinite (X))) error ("ClassificationKNN: invalid values in X."); endif ## Assign the number of observations and their corresponding indices ## on the original data, which will be used for training the model, ## to the ClassificationKNN object this.NumObservations = rows (this.X); ## RowsUsed is left empty when every observation was used, as in MATLAB if (all (RowsUsed)) this.RowsUsed = []; else this.RowsUsed = RowsUsed; endif ## Handle the Standardize option if (Standardize) ## A lazy learner standardizes at predict time, so each predictor is ## summarized from every observation where it is present, whatever is ## missing elsewhere in the row. This is what MATLAB reports. this.Mu = zeros (1, columns (this.X)); this.Sigma = zeros (1, columns (this.X)); for j = 1:columns (this.X) xj = this.X(:,j); xj = xj(! isnan (xj)); this.Mu(j) = mean (xj); this.Sigma(j) = std (xj); endfor this.Sigma(this.Sigma == 0) = 1; # predictor is constant else this.Sigma = []; this.Mu = []; endif ## Handle BreakTies if (isempty (BreakTies)) this.BreakTies = 'smallest'; else this.BreakTies = BreakTies; endif ## Handle Cost and Prior this.Cost = Cost; this.Prior = Prior; ## Each class carries its prior, spread over its own observations this.W = priorWeights (this.Prior, gY, this.NumObservations); ## Get number of neighbors if (isempty (NumNeighbors)) this.NumNeighbors = 1; else ## There are only as many neighbors available as there are training ## samples, so a larger request is capped at that. Asking for more ## raised an internal nonconformant operator error at prediction. this.NumNeighbors = min (NumNeighbors, rows (X)); endif ## Get distance metric if (isempty (Distance)) Distance = 'euclidean'; endif this.Distance = Distance; ## Get distance weight if (isempty (DistanceWeight)) [this.DWfun, this.DistanceWeight] = ... parseDistanceWeight ('equal', 'ClassificationKNN'); endif ## Handle distance metric parameters (Scale, Cov, Exponent) if (! isempty (Scale)) if (! strcmpi (Distance, 'seuclidean')) error (strcat ("ClassificationKNN: 'Scale' is only valid", ... " when distance metric is seuclidean.")); endif if (numel (Scale) != NumPredictors) error (strcat ("ClassificationKNN: 'Scale' vector must have", ... " equal length to the number of columns in X.")); endif if (any (Scale < 0)) error (strcat ("ClassificationKNN: 'Scale' vector must", ... " contain nonnegative scalar values.")); endif this.DistParameter = Scale; else if (strcmpi (Distance, 'seuclidean')) if (Standardize) this.DistParameter = ones (1, NumPredictors); else this.DistParameter = std (X, [], 1); endif endif endif if (! isempty (Cov)) if (! strcmpi (Distance, 'mahalanobis')) error (strcat ("ClassificationKNN: 'Cov' is only valid", ... " when distance metric is 'mahalanobis'.")); endif if (columns (Cov) != NumPredictors) error (strcat ("ClassificationKNN: 'Cov' matrix", ... " must have equal columns as X.")); endif this.DistParameter = Cov; else if (strcmpi (Distance, 'mahalanobis')) this.DistParameter = cov (X); endif endif if (! isempty (Exponent)) if (! strcmpi (Distance, 'minkowski')) error (strcat ("ClassificationKNN: 'Exponent' is only", ... " valid when distance metric is 'minkowski'.")); endif this.DistParameter = Exponent; else if (strcmpi (Distance, 'minkowski')) this.DistParameter = 2; endif endif ## Get Nearest neighbor search method kdm = {'euclidean', 'cityblock', 'manhattan', 'minkowski', 'chebychev'}; if (! isempty (NSMethod)) if (strcmpi ('kdtree', NSMethod) && (! any (strcmpi (kdm, Distance)))) error (strcat ("ClassificationKNN: 'kdtree' method is only va", ... "lid for 'euclidean', 'cityblock', 'manhattan',", ... " 'minkowski', and 'chebychev' distance metrics.")); endif this.NSMethod = NSMethod; else if (any (strcmpi (kdm, Distance)) && NumPredictors <= 10) this.NSMethod = 'kdtree'; else this.NSMethod = 'exhaustive'; endif endif ## Assign IncludeTies, BucketSize and CacheSize properties this.IncludeTies = IncludeTies; this.BucketSize = BucketSize; this.CacheSize = CacheSize; ## The fit as it was asked for, not as it was resolved: Exponent, Cov ## and Scale hold what was passed and nothing else, DistParameter ## holding what was used, and BucketSize applies to the kd-tree alone. if (strcmpi (this.NSMethod, 'kdtree')) BucketSizeIn = this.BucketSize; else BucketSizeIn = []; endif ## Exponent is the exception among the three: MATLAB fills its default ## of 2 for a 'minkowski' fit, where Cov and Scale stay empty until ## they are passed. Measured on R2024a. if (strcmpi (this.Distance, 'minkowski')) ExponentIn = this.DistParameter; else ExponentIn = []; endif this.ModelParameters = struct ( ... 'NumNeighbors', this.NumNeighbors, ... 'NSMethod', this.NSMethod, ... 'Distance', this.Distance, ... 'BucketSize', BucketSizeIn, ... 'IncludeTies', this.IncludeTies, ... 'DistanceWeight', this.DistanceWeight, ... 'BreakTies', this.BreakTies, ... 'Exponent', ExponentIn, ... 'Cov', Cov, ... 'Scale', Scale, ... 'StandardizeData', logical (Standardize), ... 'Version', 1, ... 'Method', 'KNN', ... 'Type', 'classification'); this.Fitted = true; endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKNN} {@var{labels} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {ClassificationKNN} {[@var{labels}, @var{scores}, @var{cost}] =} predict (@var{obj}, @var{XC}) ## ## Classify new data points into categories using the kNN algorithm from a ## k-Nearest Neighbor classification model. ## ## @code{@var{labels} = predict (@var{obj}, @var{XC})} returns the matrix of ## labels predicted for the corresponding instances in @var{XC}, using the ## predictor data in @code{obj.X} and corresponding labels, @code{obj.Y}, ## stored in the k-Nearest Neighbor classification model, @var{obj}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{ClassificationKNN} class object. ## @item ## @var{XC} must be an @math{M*P} numeric matrix with the same number of ## features @math{P} as the corresponding predictors of the SVM model in ## @var{obj}. ## @end itemize ## ## @code{[@var{labels}, @var{scores}, @var{cost}] = predict (@var{obj}, ## @var{XC})} also returns @var{scores}, which contains the predicted class ## scores or posterior probabilities for each instance of the corresponding ## unique classes, and @var{cost}, which is a matrix containing the expected ## cost of the classifications. By default, @var{scores} returns the ## posterior probabilities for KNN models, unless a specific ScoreTransform ## function has been specified. See @code{fitcknn} for more info. ## ## Note! @code{predict} is explicitly using @qcode{'exhaustive'} as the ## nearest search method due to the very slow implementation of ## @qcode{'kdtree'} in the @code{knnsearch} function. ## ## @seealso{fitcknn, ClassificationKNN, knnsearch} ## @end deftypefn function [labels, scores, cost] = predict (this, XC) ## Check for sufficient input arguments if (nargin < 2) error ("ClassificationKNN.predict: too few input arguments."); endif ## Check for valid XC if (isempty (XC)) error ("ClassificationKNN.predict: XC is empty."); elseif (this.NumPredictors != columns (XC)) error (strcat ("ClassificationKNN.predict:", ... " XC must have the same number of", ... " predictors as the trained model.")); endif ## Get training data and labels used = true (rows (this.X), 1); X = this.X(used,:); Y = this.Y(used,:); ## Standardize (if necessary) if (! isempty (this.Mu)) X = (X - this.Mu) ./ this.Sigma; XC = (XC - this.Mu) ./ this.Sigma; endif ## Train kNN if (strcmpi (this.Distance, 'seuclidean')) [idx, dist] = knnsearch (X, XC, 'k', this.NumNeighbors, ... 'NSMethod', this.NSMethod, 'Distance', 'seuclidean', ... 'Scale', this.DistParameter, 'sortindices', true, ... 'includeties', this.IncludeTies, ... 'bucketsize', this.BucketSize); elseif (strcmpi (this.Distance, 'mahalanobis')) [idx, dist] = knnsearch (X, XC, 'k', this.NumNeighbors, ... 'NSMethod', this.NSMethod, 'Distance', 'mahalanobis', ... 'cov', this.DistParameter, 'sortindices', true, ... 'includeties', this.IncludeTies, ... 'bucketsize', this.BucketSize); elseif (strcmpi (this.Distance, 'minkowski')) [idx, dist] = knnsearch (X, XC, 'k', this.NumNeighbors, ... 'NSMethod', this.NSMethod, 'Distance', 'minkowski', ... 'P', this.DistParameter, 'sortindices', true, ... 'includeties',this.IncludeTies, ... 'bucketsize', this.BucketSize); else [idx, dist] = knnsearch (X, XC, 'k', this.NumNeighbors, ... 'NSMethod', this.NSMethod, 'Distance', this.Distance, ... 'sortindices', true, 'includeties', this.IncludeTies, ... 'bucketsize', this.BucketSize); endif ## Make prediction if (iscellstr (this.ClassNames)) labels = {}; elseif (ischar (this.ClassNames)) labels = ''; else labels = []; endif scores = []; cost = []; ## Get IDs of labels for each point in training data [~, ~, gY] = uniqueLabels (Y); ## Evaluate the K nearest neighbours for each new point for i = 1:rows (idx) ## Get K nearest neighbours if (this.IncludeTies) NN_idx = idx{i}; NNdist = dist{i}; else NN_idx = idx(i,:); NNdist = dist(i,:); endif k = numel (NN_idx); kNNgY = gY(NN_idx); ## Weight each neighbour by its distance and normalize, which is what ## DistanceWeight selects. Equal weighting reduces to the vote count ## this used to be, and is the default. kNNgY = kNNgY(:)'; w = this.DWfun (NNdist(:)'); if (any (isinf (w))) ## A neighbour sitting on the query takes the whole vote rather than ## turning every share into a ratio of infinities. w = double (isinf (w)); endif for c = 1:rows (this.ClassNames) freq(c) = sum (w(kNNgY == c)); endfor freq = freq ./ sum (freq); ## Get labels according to BreakTies if (strcmpi (this.BreakTies, 'smallest')) [~, idl] = max (freq); else idl = find (freq == max (freq)); tgn = numel (idl); if (tgn > 1) if (strcmpi (this.BreakTies, 'nearest')) for t = 1:tgn tgs(t) = find (gY(NN_idx) == idl(t)); endfor [~, idm] = min (tgs); idl = idl(idm); else # "random" idl = idl(randperm (numel (idl))(1)); endif endif endif labels = [labels; this.ClassNames(idl,:)]; ## The expected cost of assigning to each class is the posterior ## weighted by the cost matrix, sum_j P(j) * Cost(j,k). It was ## 1 - freq, which is that product only for the default matrix and ## ignored any other. scores = [scores; freq]; cost = [cost; freq * this.Cost]; endfor ## Apply ScoreTransform once to the whole matrix. Inside the loop it ## was applied to everything accumulated so far, so observation i of n ## came back transformed n-i+1 times. scores = this.STfun (scores); ## Convert double to logical if ClassNames are logical if (islogical (this.ClassNames)) labels = logical (labels); endif endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKNN} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationKNN} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Compute loss for a trained ClassificationKNN object. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} computes the loss, ## @var{L}, using the default loss function @qcode{'mincost'}. ## ## @itemize ## @item ## @code{obj} is a @var{ClassificationKNN} object trained on @code{X} and ## @code{Y}. ## @item ## @code{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} must have same ## numbers of Rows as @var{X}. ## @end itemize ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} allows ## additional options specified by @var{name}-@var{value} pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'LossFun'} @tab Specifies the loss function to use. ## Can be a function handle with four input arguments (C, S, W, Cost) ## which returns a scalar value or one of: ## 'binodeviance', 'classifcost', 'classiferror', 'exponential', ## 'hinge', 'logit','mincost', 'quadratic'. ## @itemize ## @item ## @code{C} is a logical matrix of size @math{N*K}, where @math{N} is the ## number of observations and @math{K} is the number of classes. ## The element @code{C(i,j)} is true if the class label of the i-th ## observation is equal to the j-th class. ## @item ## @code{S} is a numeric matrix of size @math{N*K}, where each element ## represents the classification score for the corresponding class. ## @item ## @code{W} is a numeric vector of length @math{N}, representing ## the observation weights. ## @item ## @code{Cost} is a @math{K*K} matrix representing the misclassification ## costs. ## @end itemize ## ## @item @qcode{'Weights'} @tab Specifies observation weights, must be ## a numeric vector of length equal to the number of rows in X. ## Default is @code{ones (size (X, 1))}. loss normalizes the weights so that ## observation weights in each class sum to the prior probability of that ## class. When you supply Weights, loss computes the weighted ## classification loss. ## ## @end multitable ## ## @seealso{fitcknn, ClassificationKNN} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("ClassificationKNN.loss: too few input arguments."); elseif (mod (nargin - 3, 2) != 0) error (strcat ("ClassificationKNN.loss: name-value", ... " arguments must be in pairs.")); elseif (nargin > 7) error ("ClassificationKNN.loss: too many input arguments."); endif ## Check for valid X if (isempty (X)) error ("ClassificationKNN.loss: X is empty."); elseif (columns (this.X) != columns (X)) error (strcat ("ClassificationKNN.loss: X must have the same", ... " number of predictors as the trained model.")); endif ## Default values LossFun = 'mincost'; Weights = []; ## Validate Y valid_types = {'char', 'string', 'logical', 'single', 'double', 'cell'}; if (! (any (strcmp (class (Y), valid_types)))) error ("ClassificationKNN.loss: Y must be of a valid type."); endif ## Validate size of Y if (size (Y, 1) != size (X, 1)) error (strcat ("ClassificationKNN.loss: Y must have", ... " the same number of rows as X.")); endif ## Parse name-value arguments while (numel (varargin) > 0) Value = varargin{2}; switch (tolower (varargin{1})) case 'lossfun' if (isa (Value, 'function_handle')) ## Check if the loss function is valid if (nargin (Value) != 4) error (strcat ("ClassificationKNN.loss: custom loss function", ... " must accept exactly four input arguments.")); endif try n = 1; K = 2; C_test = false (n, K); S_test = zeros (n, K); W_test = ones (n, 1); Cost_test = ones (K) - eye (K); test_output = Value(C_test, S_test, W_test, Cost_test); if (! isscalar (test_output)) error (strcat ("ClassificationKNN.loss: custom loss", ... " function must return a scalar value.")); endif catch error (strcat ("ClassificationKNN.loss: custom loss", ... " function is not valid or does not", ... " produce correct output.")); end_try_catch LossFun = Value; elseif (ischar (Value) && any (strcmpi (Value, {'binodeviance', ... 'classifcost', 'classiferror', 'exponential', 'hinge', ... 'logit', 'mincost', 'quadratic'}))) LossFun = Value; else error ("ClassificationKNN.loss: invalid loss function."); endif case 'weights' if (isnumeric (Value) && isvector (Value)) if (numel (Value) != size (X ,1)) error ("ClassificationKNN.loss: size of Weights must", ... ' be equal to the number of rows in X.'); elseif (numel (Value) == size (X, 1)) Weights = Value; endif else error ("ClassificationKNN.loss: invalid Weights."); endif otherwise error ("ClassificationKNN.loss: invalid name-value arguments."); endswitch varargin(1:2) = []; endwhile ## Check for missing values in X if (! isa (LossFun, 'function_handle')) lossfun = tolower (LossFun); if (! strcmp (lossfun, 'mincost') && ! strcmp (lossfun, ... 'classiferror') && ! strcmp (lossfun, 'classifcost') ... && any (isnan (X(:)))) L = NaN; return; endif endif ## If Y is a char array convert it to a cell array of character vectors classes = this.ClassNames; if (ischar (Y) && ischar (classes)) Y = cellstr (Y); classes = cellstr (classes); endif ## Check that Y is of the same type as ClassNames if (! strcmp (class (Y), class (classes))) error (strcat ("ClassificationKNN.loss: Y must be the", ... " same data type as the model's ClassNames.")); endif ## Check if Y contains correct classes if (! labelsKnown (Y, this.ClassNames)) error (strcat ("ClassificationKNN.loss: Y must contain only", ... " the classes in model's ClassNames.")); endif ## Set default weights if not specified if (isempty (Weights)) Weights = ones (size (X, 1), 1); endif ## Normalize Weights K = classCount (classes); class_prior_probs = this.Prior; norm_weights = zeros (size (Weights)); for i = 1:K class_idx = ismember (Y, classes(i)); if (sum (Weights(class_idx)) > 0) norm_weights(class_idx) = ... Weights(class_idx) * class_prior_probs(i) / sum (Weights(class_idx)); endif endfor Weights = norm_weights / sum (norm_weights); ## Number of observations n = size (X, 1); ## Predict classification scores [label, scores] = predict (this, X); if (ischar (label)) label = cellstr (label); endif ## C is vector of K-1 zeros, with 1 in the ## position corresponding to the true class C = false (n, K); ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:n class_idx = gYidx(i); C(i, class_idx) = true; endfor Y_new = C'; ## Compute the loss using custom loss function if (isa (LossFun, 'function_handle')) L = LossFun(C, scores, Weights, this.Cost); return; endif ## Compute the scalar classification score for each observation m_j = zeros (n, 1); for i = 1:n m_j(i) = scores(i,:) * Y_new(:,i); endfor ## Compute the loss switch (tolower (LossFun)) case 'binodeviance' b = log (1 + exp (-2 * m_j)); L = (Weights') * b; case 'hinge' h = max (0, 1 - m_j); L = (Weights') * h; case 'exponential' e = exp (-m_j); L = (Weights') * e; case 'logit' l = log (1 + exp (-m_j)); L = (Weights') * l; case 'quadratic' q = (1 - m_j) .^ 2; L = (Weights') * q; case 'classiferror' L = 0; for i = 1:n L = L + Weights(i) * (! isequal (Y(i), label(i))); endfor case 'mincost' Cost = this.Cost; L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:n f_Xj = scores(i, :); gamma_jk = f_Xj * Cost; [~, min_cost_class] = min (gamma_jk); cj = Cost(gYidx(i), min_cost_class); L = L + Weights(i) * cj; endfor case 'classifcost' Cost = this.Cost; L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:n y_idx = gYidx(i); y_hat_idx = find (ismember (classes, label(i))); L = L + Weights(i) * Cost(y_idx, y_hat_idx); endfor otherwise error ("ClassificationKNN.loss: invalid loss function."); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKNN} {@var{m} =} margin (@var{obj}, @var{X}, @var{Y}) ## ## @code{@var{m} = margin (@var{obj}, @var{X}, @var{Y})} returns ## the classification margins for @var{obj} with data @var{X} and ## classification @var{Y}. @var{m} is a numeric vector of length size (X,1). ## ## @itemize ## @item ## @code{obj} is a @var{ClassificationKNN} object trained on @code{X} ## and @code{Y}. ## @item ## @code{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} must have same ## numbers of Rows as @var{X}. ## @end itemize ## ## The classification margin for each observation is the difference between ## the classification score for the true class and the maximal ## classification score for the false classes. ## ## @seealso{fitcknn, ClassificationKNN} ## @end deftypefn function m = margin (this, X, Y) ## Check for sufficient input arguments if (nargin < 3) error ("ClassificationKNN.margin: too few input arguments."); endif ## Check for valid X if (isempty (X)) error ("ClassificationKNN.margin: X is empty."); elseif (columns (this.X) != columns (X)) error (strcat ("ClassificationKNN.margin: X must have the same", ... " number of predictors as the trained model.")); endif ## Validate Y valid_types = {'char', 'string', 'logical', 'single', 'double', 'cell'}; if (! (any (strcmp (class (Y), valid_types)))) error ("ClassificationKNN.margin: Y must be of a valid type."); endif ## Validate X valid_types = {'single', 'double'}; if (! (any (strcmp (class (X), valid_types)))) error ("ClassificationKNN.margin: X must be of a valid type."); endif ## Validate size of Y if (size (Y, 1) != size (X, 1)) error (strcat ("ClassificationKNN.margin: Y must have", ... " the same number of rows as X.")); endif ## If Y is a char array convert it to a cell array of character vectors classes = this.ClassNames; if (ischar (Y) && ischar (classes)) Y = cellstr (Y); classes = cellstr (classes); endif ## Check that Y is of the same type as ClassNames if (! strcmp (class (Y), class (classes))) error (strcat ("ClassificationKNN.margin: Y must be the", ... " same data type as the model's ClassNames.")); endif ## Check if Y contains correct classes if (! labelsKnown (Y, classes)) error (strcat ("ClassificationKNN.margin: Y must contain", ... " only the classes in model's ClassNames.")); endif ## Number of Observations n = size (X, 1); ## Initialize the margin vector m = zeros (n, 1); ## Calculate the classification scores [~, scores] = predict (this, X); ## Loop over each observation to compute the margin ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:n ## True class index true_class_idx = gYidx(i); ## Score for the true class true_class_score = scores(i, true_class_idx); ## Get the maximal score for the false classes scores(i, true_class_idx) = -Inf; ## Temporarily max_false_class_score = max (scores(i, :)); if (max_false_class_score == -Inf) m = NaN; return; endif scores(i, true_class_idx) = true_class_score; ## Restore ## Calculate the margin m(i) = true_class_score - max_false_class_score; endfor endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKNN} {@var{[pd, x, y]} =} partialDependence (@var{obj}, @var{Vars}, @var{Labels}) ## @deftypefnx {ClassificationKNN} {@var{[pd, x, y]} =} partialDependence (@dots{}, @var{Data}) ## @deftypefnx {ClassificationKNN} {@var{[pd, x, y]} =} partialDependence (@dots{}, @var{name}, @var{value}) ## ## Compute partial dependence for a trained ClassificationKNN object. ## ## @code{@var{[pd, x, y]} = partialDependence (@var{obj}, @var{Vars}, ## @var{Labels})} ## computes the partial dependence of the classification scores on the ## variables @var{Vars} for the specified class @var{Labels}. ## ## @itemize ## @item ## @code{obj} is a trained @var{ClassificationKNN} object. ## @item ## @code{Vars} is a vector of positive integers, character vector, ## string array, or cell array of character ## vectors representing predictor variables (it can be indices of ## predictor variables in @var{obj.X}). ## @item ## @code{Labels} is a character vector, logical vector, numeric vector, ## or cell array of character vectors representing class ## labels. (column vector) ## @end itemize ## ## @code{@var{[pd, x, y]} = partialDependence (@dots{}, @var{Data})} ## specifies new predictor data to use for computing the partial dependence. ## ## @code{@var{[pd, x, y]} = partialDependence (@dots{}, @var{name}, ## @var{value})} allows additional options specified by name-value pairs: ## ## @multitable @columnfractions 0.32 0.7 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'NumObservationsToSample'} @tab Number of ## observations to sample. Must be a positive integer. Defaults to the ## number of observations in the training data. ## @item @qcode{'QueryPoints'} @tab Points at which to evaluate ## the partial dependence. ## Must be a numeric column vector, numeric two-column matrix, or ## cell array of character column vectors. ## @item @qcode{'UseParallel'} @tab Logical value indicating ## whether to perform computations in parallel. ## Defaults to @code{false}. ## @end multitable ## ## @subheading Return Values ## @itemize ## @item @code{pd}: Partial dependence values. ## @item @code{x}: Query points for the first predictor variable in Vars. ## @item @code{y}: Query points for the second predictor variable in ## Vars (if applicable). ## @end itemize ## ## @seealso{fitcknn, ClassificationKNN} ## @end deftypefn function [pd, x, y] = partialDependence (this, Vars, Labels, varargin) if (nargin < 3) error ("ClassificationKNN.partialDependence: too few input arguments."); endif ## Validate Vars if (isnumeric (Vars)) if (! all (Vars > 0) || ! (numel (Vars) == 1 || numel (Vars) == 2)) error ("ClassificationKNN.partialDependence: VARS must be a", ... ' positive integer or vector of two positive integers.'); endif elseif (iscellstr (Vars)) if (! (numel (Vars) == 1 || numel (Vars) == 2)) error (strcat ("ClassificationKNN.partialDependence: VARS must", ... " be a string array or cell array of one or two", ... " character vectors.")); endif Vars = cellfun (@(v) find (strcmp (this.PredictorNames, v)), Vars); elseif (ischar (Vars)) Vars = find (strcmp (this.PredictorNames, Vars)); if (isempty (Vars)) error (strcat ("ClassificationKNN.partialDependence: VARS", ... " must match one of the predictor names.")); endif else error (strcat ("ClassificationKNN.partialDependence: VARS", ... " must be a string, or cell array.")); endif ## Validate Labels if (! (ischar (Labels) || islogical (Labels) || ... isnumeric (Labels) || iscellstr (Labels) || islogical (Labels))) error ("ClassificationKNN.partialDependence: invalid type for LABELS."); endif ## If Labels is a char array convert it to a cell array of character vectors classes = this.ClassNames; if (ischar (Labels) && ischar (classes)) Labels = cellstr (Labels); classes = cellstr (classes); endif ## Check that Y is of the same type as ClassNames if (! strcmp (class (Labels), class (classes))) error (strcat ("ClassificationKNN.margin: LABELS must be the", ... " same data type as the model's ClassNames.")); endif ## Additional validation to match ClassNames if (! all (ismember (Labels, classes))) error (strcat ("ClassificationKNN.partialDependence: LABELS must", ... " match the class names in the model's ClassNames.")); endif ## Default values Data = this.X; UseParallel = false; NumObservationsToSample = size (Data, 1); QueryPoints = []; ## Check for Data and other optional arguments if (nargin > 3) if (size (varargin{1}) == size (this.X)) Data = varargin{1}; ## Ensure Data consistency if (! all (size (Data, 2) == numel (this.PredictorNames))) error (strcat ("ClassificationKNN.partialDependence: DATA must", ... " have the same number and order of columns as", ... " the predictor variables.")); endif ## Ensure Name-Value pairs are even length if (mod (nargin - 4, 2) != 0) error (strcat ("ClassificationKNN.partialDependence:", ... " name-value arguments must be in pairs.")); endif ## Set the number of observations to sample NumObservationsToSample = size (Data, 1); idx = 2; else ## Ensure Name-Value pairs are even length if (mod (nargin - 3, 2) != 0) error (strcat ("ClassificationKNN.partialDependence:", ... " name-value arguments must be in pairs.")); endif idx = 1; endif ## Handle name-value pair arguments for i = idx:2:length (varargin) if (! ischar (varargin{i})) error (strcat ("ClassificationKNN.partialDependence: name", ... " arguments must be strings.")); endif Value = varargin{i+1}; ## Parse name-value pairs switch (lower (varargin{i})) case 'numobservationstosample' if (! isnumeric (Value) || Value <= 0 || Value != round (Value)) error (strcat ("ClassificationKNN.partialDependence:", ... " NumObservationsToSample must be a", ... " positive integer.")); endif NumObservationsToSample = Value; if (Value > size (Data, 1)) NumObservationsToSample = size (Data, 1); endif case 'querypoints' if (! isnumeric (Value) && ! iscell (Value)) error (strcat ("ClassificationKNN.partialDependence:", ... " QueryPoints must be a numeric column", ... " vector, numeric two-column matrix, or", ... " cell array of character column vectors.")); endif QueryPoints = Value; case 'useparallel' if (! islogical (UseParallel)) error (strcat ("ClassificationKNN.partialDependence:", ... " UseParallel must be a logical value.")); endif UseParallel = Value; otherwise error (strcat ("ClassificationKNN.partialDependence:", ... " name-value pair argument not recognized.")); endswitch endfor endif ## Sample observations if needed if (NumObservationsToSample < size (Data, 1)) Data = datasample (Data, NumObservationsToSample, 'Replace', false); endif ## Generate QueryPoints if not specified if (isempty (QueryPoints)) if (numel (Vars) == 1) if (isnumeric (Data(:, Vars))) QueryPoints = linspace (min (Data(:, Vars)), ... max (Data(:, Vars)), 100)'; else QueryPoints = unique (Data(:, Vars)); endif else QueryPoints = cell (1, numel (Vars)); for j = 1:numel (Vars) if (isnumeric (Data(:, Vars(j)))) QueryPoints{j} = linspace (min (Data(:, Vars(j))), ... max (Data(:, Vars(j))), 100)'; else QueryPoints{j} = unique (Data(:, Vars(j))); endif endfor endif endif ## Prepare grid points for predictions if (numel (Vars) == 1) gridPoints = QueryPoints; else if (ischar (QueryPoints)) [X1, X2] = meshgrid (QueryPoints(1), QueryPoints(2)); else [X1, X2] = meshgrid (QueryPoints{1}, QueryPoints{2}); endif gridPoints = [X1(:), X2(:)]; endif ## Predict responses for the grid points numClasses = classCount (classes); numQueryPoints = size (gridPoints, 1); predictions = zeros (numQueryPoints, numClasses); if (UseParallel) parfor i = 1:numQueryPoints tempData = Data; for j = 1:numel (Vars) tempData(:, Vars(j)) = repmat (gridPoints(i, j), ... NumObservationsToSample, 1); endfor [~, scores] = predict (this, tempData); predictions(i, :) = mean (scores, 1); endparfor else for i = 1:numQueryPoints tempData = Data; for j = 1:numel (Vars) tempData(:, Vars(j)) = repmat (gridPoints(i, j), ... NumObservationsToSample, 1); endfor [~, scores] = predict (this, tempData); predictions(i, :) = mean (scores, 1); endfor endif ## Compute partial dependence if (numel (Vars) == 1) if (numel (Labels) == 1) classIndex = find (ismember (classes, Labels)); pd = predictions(:, classIndex)'; else pd = zeros (numel (Labels), numel (QueryPoints)); for j = 1:numel (Labels) classIndex = find (ismember (classes, Labels(j))); pd(j, :) = predictions(:, classIndex)'; endfor endif x = QueryPoints; y = []; else if (numel (Labels) == 1) classIndex = find (ismember (classes, Labels)); pd = reshape (predictions(:, classIndex), numel (QueryPoints{1}), ... numel (QueryPoints{2})); else pd = zeros (numel (Labels), numel (QueryPoints{1}), ... numel (QueryPoints{2})); for j = 1:numel (Labels) classIndex = find (ismember (classes, Labels(j))); pd(j, :, :) = reshape (predictions(:, classIndex), ... numel (QueryPoints{1}), ... numel (QueryPoints{2})); endfor endif x = QueryPoints{1}; y = QueryPoints{2}; endif endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKNN} {@var{CVMdl} =} crossval (@var{obj}) ## @deftypefnx {ClassificationKNN} {@var{CVMdl} =} crossval (@dots{}, @var{Name}, @var{Value}) ## ## Cross Validate a ClassificationKNN object. ## ## @code{@var{CVMdl} = crossval (@var{obj})} returns a cross-validated model ## object, @var{CVMdl}, from a trained model, @var{obj}, using 10-fold ## cross-validation by default. ## ## @code{@var{CVMdl} = crossval (@var{obj}, @var{name}, @var{value})} ## specifies additional name-value pair arguments to customize the ## cross-validation process. ## ## @multitable @columnfractions 0.28 0.7 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'KFold'} @tab Specify the number of folds to use in ## k-fold cross-validation. @code{"KFold", @var{k}}, where @var{k} is an ## integer greater than 1. ## ## @item @qcode{'Holdout'} @tab Specify the fraction of the data to ## hold out for testing. @code{"Holdout", @var{p}}, where @var{p} is a ## scalar in the range @math{(0,1)}. ## ## @item @qcode{'Leaveout'} @tab Specify whether to perform ## leave-one-out cross-validation. @code{"Leaveout", @var{Value}}, where ## @var{Value} is 'on' or 'off'. ## ## @item @qcode{'CVPartition'} @tab Specify a @qcode{cvpartition} ## object used for cross-validation. @code{"CVPartition", @var{cv}}, where ## @code{isa (@var{cv}, "cvpartition")} = 1. ## ## @end multitable ## ## @seealso{fitcknn, ClassificationKNN, cvpartition, ## ClassificationPartitionedModel} ## @end deftypefn function CVMdl = crossval (this, varargin) ## Check input if (nargin < 1) error ("ClassificationKNN.crossval: too few input arguments."); endif if (numel (varargin) == 1) error (strcat ("ClassificationKNN.crossval: Name-Value", ... " arguments must be in pairs.")); elseif (numel (varargin) > 2) error (strcat ("ClassificationKNN.crossval: specify only one", ... " of the optional Name-Value paired arguments.")); endif ## Add default values if (this.NumObservations < 10) numFolds = this.NumObservations; else numFolds = 10; endif Holdout = []; Leaveout = 'off'; CVPartition = []; ## Parse extra parameters while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'kfold' numFolds = varargin{2}; if (! (isnumeric (numFolds) && isscalar (numFolds) && (numFolds == fix (numFolds)) && numFolds > 1)) error (strcat ("ClassificationKNN.crossval: 'KFold' must", ... " be an integer value greater than 1.")); endif case 'holdout' Holdout = varargin{2}; if (! (isnumeric (Holdout) && isscalar (Holdout) && Holdout > 0 && Holdout < 1)) error (strcat ("ClassificationKNN.crossval: 'Holdout' must", ... " be a numeric value between 0 and 1.")); endif case 'leaveout' Leaveout = varargin{2}; if (! (ischar (Leaveout) && (strcmpi (Leaveout, 'on') || strcmpi (Leaveout, 'off')))) error (strcat ("ClassificationKNN.crossval: 'Leaveout'", ... " must be either 'on' or 'off'.")); endif case 'cvpartition' CVPartition = varargin{2}; if (! (isa (CVPartition, 'cvpartition'))) error (strcat ("ClassificationKNN.crossval: 'CVPartition'",... " must be a 'cvpartition' object.")); endif otherwise error (strcat ("ClassificationKNN.crossval: invalid",... " parameter name in optional paired arguments.")); endswitch varargin(1:2) = []; endwhile ## Determine the cross-validation method to use. The partition covers ## the observations actually trained on: a row dropped for a missing ## value is not one the folds can use, and including it would leave the ## partition, the stored data and NumObservations disagreeing. The ## response is passed rather than a count so the folds stay stratified. Yused = this.Y; if (! isempty (CVPartition)) partition = CVPartition; elseif (! isempty (Holdout)) partition = cvpartition (Yused, 'Holdout', Holdout); elseif (strcmpi (Leaveout, 'on')) partition = cvpartition (this.NumObservations, 'LeaveOut'); else partition = cvpartition (Yused, 'KFold', numFolds); endif ## Create a cross-validated model object CVMdl = ClassificationPartitionedModel (this, partition); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKNN} {@var{e} =} edge (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationKNN} {@var{e} =} edge (@dots{}, @qcode{"Weights"}, @var{w}) ## ## Classification edge, the mean of the classification margins. ## ## @code{@var{e} = edge (@var{obj}, @var{X}, @var{Y})} reduces the vector ## that @code{margin} returns to a single number, the mean margin over the ## rows of @var{X}. It says how far the model puts the true class ahead of ## its nearest rival on average, so a larger edge is a better model, and ## unlike a loss it is not bounded above and rewards confidence rather than ## bare correctness. ## ## @code{@var{e} = edge (@dots{}, @qcode{"Weights"}, @var{w})} takes the ## weighted mean instead, with one weight per row of @var{X}. ## ## @end deftypefn function e = edge (this, X, Y, varargin) if (nargin < 3) error ("ClassificationKNN.edge: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationKNN.edge: Name-Value", ... " arguments must be in pairs.")); endif ## The weights are parsed before anything is computed, so a bad ## Name-Value pair is reported as such rather than after a margin. W = edgeWeights (varargin, Y, this.ClassNames, this.Prior, ... "ClassificationKNN", "edge"); m = margin (this, X, Y); e = sum (W .* m(:)) / sum (W); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKNN} {@var{label} =} resubPredict (@var{obj}) ## @deftypefnx {ClassificationKNN} {[@var{label}, @var{score}, @var{cost}] =} resubPredict (@var{obj}) ## ## Classify the training data with the model fitted to it. ## ## @code{@var{label} = resubPredict (@var{obj})} is @code{predict} applied ## to the observations the model was fitted on, which it holds in ## @qcode{X}. Handing them over yourself is not the same thing: a row ## dropped for a missing response is not in @qcode{X}, so the original ## matrix and the model's own are different data. ## ## The result measures fit and not generalization, and is optimistic by ## construction. @code{crossval} is what estimates performance on data the ## model has not seen. ## ## @end deftypefn function [label, score, cost] = resubPredict (this) [label, score, cost] = predict (this, this.X); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKNN} {@var{m} =} resubMargin (@var{obj}) ## ## Classification margins of the model on its own training data. ## ## @code{@var{m} = resubMargin (@var{obj})} is @code{margin} applied to the ## observations the model was fitted on, one number per observation. Being ## a resubstitution quantity it is optimistic by construction. ## ## @end deftypefn function m = resubMargin (this) m = margin (this, this.X, this.Y); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKNN} {@var{e} =} resubEdge (@var{obj}) ## ## Classification edge of the model on its own training data. ## ## @code{@var{e} = resubEdge (@var{obj})} is @code{edge} applied to the ## observations the model was fitted on, the mean of @code{resubMargin}. ## ## @end deftypefn function e = resubEdge (this) e = edge (this, this.X, this.Y); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKNN} {@var{L} =} resubLoss (@var{obj}) ## @deftypefnx {ClassificationKNN} {@var{L} =} resubLoss (@dots{}, @var{name}, @var{value}) ## ## Classification loss of the model on its own training data. ## ## @code{@var{L} = resubLoss (@var{obj})} is @code{loss} applied to the ## observations the model was fitted on, defaulting to ## @qcode{'mincost'}, and it accepts the same @qcode{Name-Value} pairs. ## ## Being a resubstitution quantity it is a lower bound on the error rather ## than an estimate of it. It is worth least on a lazy learner: a ## one-neighbour @code{ClassificationKNN} has a resubstitution loss of ## exactly zero, every training point being its own nearest neighbour. ## ## @end deftypefn function L = resubLoss (this, varargin) L = loss (this, this.X, this.Y, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKNN} {} savemodel (@var{obj}, @var{filename}) ## ## Save a ClassificationKNN object. ## ## @code{savemodel (@var{obj}, @var{filename})} saves each property of a ## ClassificationKNN object into an Octave binary file, the name of which is ## specified in @var{filename}, along with an extra variable, which defines ## the type classification object these variables constitute. Use ## @code{loadmodel} in order to load a classification object into Octave's ## workspace. ## ## @seealso{loadmodel, fitcknn, ClassificationKNN} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error ("ClassificationKNN.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error ("ClassificationKNN.savemodel: FNAME must be a character vector."); endif ## Generate variable for class name classdef_name = 'ClassificationKNN'; ## Create variables from model properties X = this.X; Y = this.Y; NumObservations = this.NumObservations; W = this.W; RowsUsed = this.RowsUsed; Sigma = this.Sigma; BinEdges = this.BinEdges; Mu = this.Mu; NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; ResponseName = this.ResponseName; ClassNames = this.ClassNames; Prior = this.Prior; Cost = this.Cost; ScoreTransform = this.ScoreTransform; BreakTies = this.BreakTies; NumNeighbors = this.NumNeighbors; Distance = this.Distance; DistanceWeight = this.DistanceWeight; DWfun = this.DWfun; DistParameter = this.DistParameter; NSMethod = this.NSMethod; IncludeTies = this.IncludeTies; BucketSize = this.BucketSize; CacheSize = this.CacheSize; ModelParameters = this.ModelParameters; STfun = this.STfun; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; ## Save classdef name and all model properties as individual variables HyperparameterOptimizationResults = this.HyperparameterOptimizationResults; save ('-binary', fname, 'classdef_name', 'X', 'Y', 'NumObservations', ... 'W', 'RowsUsed', 'Sigma', 'Mu', 'NumPredictors', ... 'PredictorNames', 'BinEdges', 'ResponseName', 'ClassNames', ... 'Prior', 'Cost', ... 'ScoreTransform', 'BreakTies', 'NumNeighbors', 'Distance', ... 'DistanceWeight', 'DWfun', 'DistParameter', 'NSMethod', ... 'IncludeTies', ... 'BucketSize', 'CacheSize', 'ModelParameters', ... 'CategoricalPredictors', ... 'ExpandedPredictorNames', 'STfun', ... 'HyperparameterOptimizationResults'); endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a ClassificationKNN object mdl = ClassificationKNN (1, 1); ## Copy the saved data into the object. Iterate over what was ## saved rather than over fieldnames (mdl): a private property such ## as STfun is written out by savemodel but is not reported by ## fieldnames, so comparing the two sets could never match and every ## load failed. Assignment is legal here because this is a method of ## the class itself. names = fieldnames (data); ## set.Distance validates the metric against NSMethod, which the model ## built above still holds at its default of 'kdtree', so a saved model ## searched exhaustively with a metric no kd-tree can take was refused ## by its own loader until NSMethod is in place first. early = ismember (names, {'NSMethod'}); ## The set methods for these read other properties, and one of them ## rebuilds Coeffs, so they are assigned once everything else is in ## place rather than in the order the file happens to list them. late = ismember (names, {'Cost', 'Prior', 'ScoreTransform', ... 'ResponseTransform'}); names = [names(early); names(! early & ! late); names(late)]; for i = 1:numel (names) try mdl.(names{i}) = data.(names{i}); catch error ("ClassificationKNN.load_model: invalid model in '%s'.", filename) end_try_catch endfor endfunction endmethods methods(Access = private) endmethods endclassdef ## Helper functions for ScoreTransform function out = ismax (score) out = score; out(score == max (score)) = 1; out(score != max (score)) = 0; endfunction function out = symmetricismax (score) out = score; out(score == max (score)) = 1; out(score != max (score)) = -1; endfunction %!demo %! ## Create a k-nearest neighbor classifier for Fisher's iris data with k = 5. %! ## Evaluate some model predictions on new data. %! %! load fisheriris %! x = meas; %! y = species; %! xc = [min(x); mean(x); max(x)]; %! obj = fitcknn (x, y, 'NumNeighbors', 5, 'Standardize', 1); %! [label, score, cost] = predict (obj, xc) %!demo %! load fisheriris %! x = meas; %! y = species; %! obj = fitcknn (x, y, 'NumNeighbors', 5, 'Standardize', 1); %! %! ## Create a cross-validated model %! CVMdl = crossval (obj) %!demo %! load fisheriris %! x = meas; %! y = species; %! covMatrix = cov (x); %! %! ## Fit the k-NN model using the 'mahalanobis' distance %! ## and the custom covariance matrix %! obj = fitcknn (x, y, 'NumNeighbors', 5, 'Distance','mahalanobis', ... %! 'Cov', covMatrix); %! %! ## Create a partition model using cvpartition %! Partition = cvpartition (size (x, 1), 'kfold', 12); %! %! ## Create cross-validated model using 'cvPartition' name-value argument %! CVMdl = crossval (obj, 'cvPartition', Partition) %! %! ## Access the trained model from first fold of cross-validation %! CVMdl.Trained{1} %!demo %! X = [1, 2; 3, 4; 5, 6]; %! Y = {'A'; 'B'; 'A'}; %! model = fitcknn (X, Y); %! customLossFun = @(C, S, W, Cost) sum (W .* sum (abs (C - S), 2)); %! ## Calculate loss using custom loss function %! L = loss (model, X, Y, 'LossFun', customLossFun) %!demo %! X = [1, 2; 3, 4; 5, 6]; %! Y = {'A'; 'B'; 'A'}; %! model = fitcknn (X, Y); %! ## Calculate loss using 'mincost' loss function %! L = loss (model, X, Y, 'LossFun', 'mincost') %!demo %! X = [1, 2; 3, 4; 5, 6]; %! Y = ['1'; '2'; '3']; %! model = fitcknn (X, Y); %! X_test = [3, 3; 5, 7]; %! Y_test = ['1'; '2']; %! ## Specify custom Weights %! W = [1; 2]; %! L = loss (model, X_test, Y_test, 'LossFun', 'logit', 'Weights', W); %!demo %! load fisheriris %! mdl = fitcknn (meas, species); %! X = mean (meas); %! Y = {'versicolor'}; %! m = margin (mdl, X, Y) %!demo %! X = [1, 2; 4, 5; 7, 8; 3, 2]; %! Y = [2; 1; 3; 2]; %! ## Train the model %! mdl = fitcknn (X, Y); %! ## Specify Vars and Labels %! Vars = 1; %! Labels = 2; %! ## Calculate partialDependence %! [pd, x, y] = partialDependence (mdl, Vars, Labels); %!demo %! X = [1, 2; 4, 5; 7, 8; 3, 2]; %! Y = [2; 1; 3; 2]; %! ## Train the model %! mdl = fitcknn (X, Y); %! ## Specify Vars and Labels %! Vars = 1; %! Labels = 1; %! queryPoints = [linspace(0, 1, 3)', linspace(0, 1, 3)']; %! ## Calculate partialDependence using queryPoints %! [pd, x, y] = partialDependence (mdl, Vars, Labels, 'QueryPoints', ... %! queryPoints) ## Test constructor with custom Distance as function_handle (issue #459) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! f = @(d1,d2) sqrt (sum ((d1 - d2) .^ 2, 2)); %! a = ClassificationKNN (x, y, 'Distance', f); %!error ... %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! f = @(d1,d2) sqrt (dot (d1, d2)); %! a = ClassificationKNN (x, y, 'Distance', f); %!error ... %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! f = @(d1,d2) sqrt (sum ((d1 - d2) .^ 2, 1)); %! a = ClassificationKNN (x, y, 'Distance', f); %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! f = @(d1,d2) sqrt (sum ((d1 - d2) .^ 2, 2)); %! a = ClassificationKNN (x, y, 'NSMethod', 'exhaustive', 'Distance', ... %! 'cityblock'); %! a.Distance = f; %! assert_equal (a.Distance, f); %! assert_equal (isempty (a.DistParameter), true); %!error ... %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = ClassificationKNN (x, y, 'NSMethod', 'exhaustive'); %! a.Distance = @(d1,d2) sqrt (dot (d1, d2)); %!error ... %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = ClassificationKNN (x, y, 'NSMethod', 'exhaustive'); %! a.Distance = @(d1,d2) sqrt (sum ((d1 - d2) .^ 2, 1)); %!error ... %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = ClassificationKNN (x, y); %! a.Distance = @(d1,d2) sqrt (sum ((d1 - d2) .^ 2, 2)); ## Test constructor with NSMethod and NumNeighbors parameters %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = ClassificationKNN (x, y); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 1}) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = ClassificationKNN (x, y, 'NSMethod', 'exhaustive'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 1}) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! k = 10; %! a = ClassificationKNN (x, y, 'NumNeighbors' ,k); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 4}) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = ones (4, 11); %! y = ['a'; 'a'; 'b'; 'b']; %! k = 10; %! a = ClassificationKNN (x, y, 'NumNeighbors' ,k); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 4}) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! k = 10; %! a = ClassificationKNN (x, y, 'NumNeighbors' ,k, 'NSMethod', 'exhaustive'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 4}) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! k = 10; %! a = ClassificationKNN (x, y, 'NumNeighbors' ,k, 'Distance', 'hamming'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 4}) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'hamming'}) %! assert_equal ({a.BucketSize}, {50}) ## Test constructor with Standardize and DistParameter parameters %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! weights = ones (4,1); %! a = ClassificationKNN (x, y, 'Standardize', 1); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 1}) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.Sigma}, {std(x, [], 1)}) %! assert_equal ({a.Mu}, {[3.75, 4.25, 4.75]}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! weights = ones (4,1); %! a = ClassificationKNN (x, y, 'Standardize', false); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 1}) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.Sigma}, {[]}) %! assert_equal ({a.Mu}, {[]}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! s = ones (1, 3); %! a = ClassificationKNN (x, y, 'Scale' , s, 'Distance', 'seuclidean'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.DistParameter}, {s}) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'seuclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = ClassificationKNN (x, y, 'Exponent' , 5, 'Distance', 'minkowski'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal (a.DistParameter, 5) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'minkowski'}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = ClassificationKNN (x, y, 'Exponent' , 5, 'Distance', 'minkowski', ... %! 'NSMethod', 'exhaustive'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal (a.DistParameter, 5) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'minkowski'}) ## Test constructor with BucketSize and IncludeTies parameters %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = ClassificationKNN (x, y, 'BucketSize' , 20, 'distance', 'mahalanobis'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'mahalanobis'}) %! assert_equal ({a.BucketSize}, {20}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = ClassificationKNN (x, y, 'IncludeTies', true); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal (a.IncludeTies, true); %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = ClassificationKNN (x, y); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal (a.IncludeTies, false); %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) ## Test constructor with Prior and Cost parameters %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = ClassificationKNN (x, y); %! assert_equal (class (a), "ClassificationKNN") %! assert_equal (a.Prior, [0.5, 0.5]) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! prior = [0.5, 0.5]; %! a = ClassificationKNN (x, y, 'Prior', 'empirical'); %! assert_equal (class (a), "ClassificationKNN") %! assert_equal (a.Prior, prior) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'a'; 'b']; %! prior = [0.75, 0.25]; %! a = ClassificationKNN (x, y, 'Prior', 'empirical'); %! assert_equal (class (a), "ClassificationKNN") %! assert_equal (a.Prior, prior) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'a'; 'b']; %! prior = [0.5, 0.5]; %! a = ClassificationKNN (x, y, 'Prior', 'uniform'); %! assert_equal (class (a), "ClassificationKNN") %! assert_equal (a.Prior, prior) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! cost = [0, 1; 1, 0]; %! a = ClassificationKNN (x, y, 'Cost', cost); %! assert_equal (class (a), "ClassificationKNN") %! assert_equal (a.Cost, [0, 1; 1, 0]) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! cost = [0, 1; 1, 0]; %! a = ClassificationKNN (x, y, 'Cost', cost, 'Distance', 'hamming' ); %! assert_equal (class (a), "ClassificationKNN") %! assert_equal (a.Cost, [0, 1; 1, 0]) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'hamming'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2; 3, 4; 5,6; 5, 8]; %! y = {'9'; '9'; '6'; '7'}; %! a = ClassificationKNN (x, y); %! assert_equal (a.Prior, [0.25, 0.25, 0.5]) ## Test constructor with ClassNames parameter %!test %! load fisheriris %! x = meas; %! y = species; %! ClassNames = {'setosa', 'versicolor', 'virginica'}; %! a = ClassificationKNN (x, y, 'ClassNames', ClassNames); %! assert_equal (a.ClassNames, ClassNames') ## Test input validation for constructor ## A response naming its classes in the rows of a character matrix is one ## of the documented types and MATLAB accepts it on every classifier. The ## whole surface below was broken and untested, which is why it stayed so. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcknn (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcknn (Xch, Ycell); %! assert_equal (size (Mc.ClassNames), [2, 10]); %! assert_equal (cellstr (Mc.ClassNames), Ms.ClassNames); ## predict returns whole names, not their first letters. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcknn (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcknn (Xch, Ycell); %! pch = predict (Mc, Xch); %! assert_equal (columns (pch), 10); %! assert_equal (cellstr (pch), predict (Ms, Xch)); ## loss, margin and edge read a character response as the same response. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcknn (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcknn (Xch, Ycell); %! assert_equal (loss (Mc, Xch, Ych), loss (Ms, Xch, Ycell), 1e-12); %! assert_equal (margin (Mc, Xch, Ych), margin (Ms, Xch, Ycell), 1e-12); %! assert_equal (edge (Mc, Xch, Ych), edge (Ms, Xch, Ycell), 1e-12); ## A character matrix pads its rows out to the longest name, and the padding ## is part of the name: R2024a reports ClassNames of ['ab '; 'abcd']. %!test %! Xpad = [1 2; 3 4; 1.1 2.1; 3.1 4.1; 1.2 2.2; 3.2 4.2]; %! Ypad = char ({"ab", "abcd", "ab", "abcd", "ab", "abcd"}); %! rand ("state", 1); randn ("state", 1); %! Mp = fitcknn (Xpad, Ypad); %! assert_equal (size (Mp.ClassNames), [2, 4]); %! assert_equal (Mp.ClassNames(1,:), "ab "); ## A row dropped for a missing predictor is the only case that exercises ## indexing the response by row rather than by element. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! Xmiss = Xch; Xmiss(3,2) = NaN; %! rand ("state", 1); randn ("state", 1); Md = fitcknn (Xmiss, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcknn (Xmiss, Ycell); %! assert_equal (size (Md.ClassNames), [2, 10]); %! assert_equal (cellstr (Md.ClassNames), Ms.ClassNames); ## ClassNames may itself be given as a character matrix, which selects the ## classes by whole name: ismember between two character matrices compares ## them character by character and would select by letter. %!test %! load fisheriris %! rand ("state", 1); randn ("state", 1); %! Mf = fitcknn (meas, char (species), ... %! "ClassNames", char ({"versicolor", "virginica"})); %! assert_equal (rows (Mf.ClassNames), 2); %! assert_equal (cellstr (Mf.ClassNames), {"versicolor"; "virginica"}); ## A model fitted from a character response comes back off disk unchanged. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcknn (Xch, Ych); %! fname = tempname (); %! savemodel (Mc, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.ClassNames, Mc.ClassNames); %! assert_equal (predict (M2, Xch), predict (Mc, Xch)); ## crossval carries a character response through cvpartition and back. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcknn (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcknn (Xch, Ycell); %! rand ("state", 2); cvc = crossval (Mc, "KFold", 3); %! rand ("state", 2); cvs = crossval (Ms, "KFold", 3); %! assert_equal (cellstr (kfoldPredict (cvc)), kfoldPredict (cvs)); %!test %! ## CacheSize defaults to 1000 and is reported, where MATLAB hides it. %! X = [1, 2; 3, 4; 5, 6; 7, 8; 2, 2]; %! Mdl = fitcknn (X, [1; 1; 2; 2; 1]); %! assert_equal (Mdl.CacheSize, 1000); %! assert_equal (any (strcmp (properties (Mdl), 'CacheSize')), true); %!test %! ## It is settable after fitting and through fitcknn alike. %! X = [1, 2; 3, 4; 5, 6; 7, 8; 2, 2]; %! Mdl = fitcknn (X, [1; 1; 2; 2; 1]); %! Mdl.CacheSize = 5000; %! assert_equal (Mdl.CacheSize, 5000); %! Mdl2 = fitcknn (X, [1; 1; 2; 2; 1], "CacheSize", 250); %! assert_equal (Mdl2.CacheSize, 250); %!test %! ## It changes nothing: the model keeps no cache to size. %! X = [1, 2; 3, 4; 5, 6; 7, 8; 2, 2]; %! y = [1; 1; 2; 2; 1]; %! Mdl = fitcknn (X, y, "CacheSize", 1); %! Mdl2 = fitcknn (X, y, "CacheSize", 1e6); %! [l1, s1] = predict (Mdl, X); %! [l2, s2] = predict (Mdl2, X); %! assert_equal (l1, l2); %! assert_equal (s1, s2); %!error ... %! fitcknn (ones (5, 2), [1; 1; 1; 2; 2], "CacheSize", 0) %!error ... %! fitcknn (ones (5, 2), [1; 1; 1; 2; 2], "CacheSize", [1, 2]) %!error ... %! fitcknn (ones (5, 2), [1; 1; 1; 2; 2], "CacheSize", Inf) %!test %! Mdl = fitcknn (ones (5, 2), [1; 1; 1; 2; 2]); %! fail ("Mdl.CacheSize = -1", ... %! "ClassificationKNN: 'CacheSize' must be a positive finite scalar."); %!error ClassificationKNN () %!error ... %! ClassificationKNN (ones (4, 1)) %!error ... %! ClassificationKNN (ones (4,2), ones (1,4)) %!error ... %! ClassificationKNN (ones (5,3), ones (5,1), 'standardize', 'a') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'scale', [1 1], 'standardize', true) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'PredictorNames', ['A']) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'PredictorNames', 'A') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'PredictorNames', {'A', 'B', 'C'}) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'ResponseName', {'Y'}) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'ResponseName', 1) %!error ... %! ClassificationKNN (ones (10,2), ones (10,1), 'ClassNames', @(x)x) %!error ... %! ClassificationKNN (ones (10,2), ones (10,1), 'ClassNames', {1}) %!error ... %! ClassificationKNN (ones (10,2), ones (10,1), 'ClassNames', [1, 2]) %!error ... %! ClassificationKNN (ones (5,2), ['a';'b';'a';'a';'b'], 'ClassNames', ['a';'c']) %!error ... %! ClassificationKNN (ones (5,2), {'a';'b';'a';'a';'b'}, 'ClassNames', {'a','c'}) %!error ... %! ClassificationKNN (ones (10,2), logical (ones (10,1)), 'ClassNames', [true, false]) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'BreakTies', 1) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'BreakTies', {'1'}) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'BreakTies', 'some') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Prior', {'1', '2'}) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Cost', [1, 2]) %!error ... %! Mdl = fitcknn ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]); %! Mdl.Cost = 1:4; %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Cost', 'string') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Cost', {eye(2)}) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'NumNeighbors', 0) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'NumNeighbors', 15.2) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'NumNeighbors', 'asd') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Distance', 'somemetric') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Distance', ... %! @(v,m)sqrt (repmat (v,rows (m),1)-m,2)) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Distance', ... %! @(v,m)sqrt (sum (sumsq (repmat (v,rows (m),1)-m,2)))) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Distance', [1 2 3]) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Distance', {'mahalanobis'}) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Distance', logical (5)) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'DistanceWeight', @(x)sum (x)) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'DistanceWeight', 'text') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'DistanceWeight', [1 2 3]) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Scale', 'scale') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Scale', {[1 2 3]}) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'standardize', true, 'scale', [1 1]) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Cov', ones (2), 'Distance', 'mahalanobis') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'scale', [1 1], 'Cov', ones (2)) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Exponent', 12.5) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Exponent', -3) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Exponent', 'three') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Exponent', {3}) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'NSMethod', {'kdtree'}) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'NSMethod', 3) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'NSMethod', 'some') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'IncludeTies', 'some') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'BucketSize', 42.5) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'BucketSize', -50) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'BucketSize', 'some') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'BucketSize', {50}) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'some', 'some') %!error ... %! ClassificationKNN ([1;2;3;'a';4], ones (5,1)) %!error ... %! ClassificationKNN ([1;2;3;Inf;4], ones (5,1)) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Prior', [1 2]) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Cost', [1 2; 1 3]) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Scale', [1 1]) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Scale', [1 1 1], 'Distance', 'seuclidean') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Scale', [1 -1], 'Distance', 'seuclidean') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Cov', eye (2)) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Cov', eye (3), 'Distance', 'mahalanobis') %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Exponent', 3) %!error ... %! ClassificationKNN (ones (5,2), ones (5,1), 'Distance', 'hamming', 'NSMethod', 'kdtree') ## Test output for predict method %!shared x, y %! load fisheriris %! x = meas; %! y = species; %!test %! xc = [min(x); mean(x); max(x)]; %! obj = fitcknn (x, y, 'NumNeighbors', 5); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'setosa'; 'versicolor'; 'virginica'}) %! assert_equal (s, [1, 0, 0; 0, 1, 0; 0, 0, 1]) %! assert_equal (c, [0, 1, 1; 1, 0, 1; 1, 1, 0]) %!test %! xc = [min(x); mean(x); max(x)]; %! obj = fitcknn (x, y, 'NumNeighbors', 5, 'Standardize', 1); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'versicolor'; 'versicolor'; 'virginica'}) %! assert_equal (s, [0.4, 0.6, 0; 0, 1, 0; 0, 0, 1]) %! assert_equal (c, [0.6, 0.4, 1; 1, 0, 1; 1, 1, 0]) %!test %! xc = [min(x); mean(x); max(x)]; %! obj = fitcknn (x, y, 'NumNeighbors', 10, 'distance', 'mahalanobis'); %! [l, s, c] = predict (obj, xc); %! assert_equal (s, [0.3, 0.7, 0; 0, 0.9, 0.1; 0.2, 0.2, 0.6], 1e-4) %! assert_equal (c, [0.7, 0.3, 1; 1, 0.1, 0.9; 0.8, 0.8, 0.4], 1e-4) %!test %! xc = [min(x); mean(x); max(x)]; %! obj = fitcknn (x, y, 'NumNeighbors', 10, 'distance', 'cosine'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'setosa'; 'versicolor'; 'virginica'}) %! assert_equal (s, [1, 0, 0; 0, 1, 0; 0, 0.3, 0.7], 1e-4) %! assert_equal (c, [0, 1, 1; 1, 0, 1; 1, 0.7, 0.3], 1e-4) %!test %! xc = [5.2, 4.1, 1.5, 0.1; 5.1, 3.8, 1.9, 0.4; ... %! 5.1, 3.8, 1.5, 0.3; 4.9, 3.6, 1.4, 0.1]; %! obj = fitcknn (x, y, 'NumNeighbors', 5); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'setosa'; 'setosa'; 'setosa'; 'setosa'}) %! assert_equal (s, [1, 0, 0; 1, 0, 0; 1, 0, 0; 1, 0, 0]) %! assert_equal (c, [0, 1, 1; 0, 1, 1; 0, 1, 1; 0, 1, 1]) %!test %! xc = [5, 3, 5, 1.45]; %! obj = fitcknn (x, y, 'NumNeighbors', 5); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'versicolor'}) %! assert_equal (s, [0, 0.6, 0.4], 1e-4) %! assert_equal (c, [1, 0.4, 0.6], 1e-4) %!test %! xc = [5, 3, 5, 1.45]; %! obj = fitcknn (x, y, 'NumNeighbors', 10, 'distance', 'minkowski', 'Exponent', 5); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'versicolor'}) %! assert_equal (s, [0, 0.5, 0.5], 1e-4) %! assert_equal (c, [1, 0.5, 0.5], 1e-4) %!test %! xc = [5, 3, 5, 1.45]; %! obj = fitcknn (x, y, 'NumNeighbors', 10, 'distance', 'jaccard'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'setosa'}) %! assert_equal (s, [0.9, 0.1, 0], 1e-4) %! assert_equal (c, [0.1, 0.9, 1], 1e-4) %!test %! xc = [5, 3, 5, 1.45]; %! obj = fitcknn (x, y, 'NumNeighbors', 10, 'distance', 'mahalanobis'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'versicolor'}) %! assert_equal (s, [0.1000, 0.5000, 0.4000], 1e-4) %! assert_equal (c, [0.9000, 0.5000, 0.6000], 1e-4) %!test %! xc = [5, 3, 5, 1.45]; %! obj = fitcknn (x, y, 'NumNeighbors', 5, 'distance', 'jaccard'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'setosa'}) %! assert_equal (s, [0.8, 0.2, 0], 1e-4) %! assert_equal (c, [0.2, 0.8, 1], 1e-4) %!test %! xc = [5, 3, 5, 1.45]; %! obj = fitcknn (x, y, 'NumNeighbors', 5, 'distance', 'seuclidean'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'versicolor'}) %! assert_equal (s, [0, 1, 0], 1e-4) %! assert_equal (c, [1, 0, 1], 1e-4) %!test %! xc = [5, 3, 5, 1.45]; %! obj = fitcknn (x, y, 'NumNeighbors', 10, 'distance', 'chebychev'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'versicolor'}) %! assert_equal (s, [0, 0.7, 0.3], 1e-4) %! assert_equal (c, [1, 0.3, 0.7], 1e-4) %!test %! xc = [5, 3, 5, 1.45]; %! obj = fitcknn (x, y, 'NumNeighbors', 10, 'distance', 'cityblock'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'versicolor'}) %! assert_equal (s, [0, 0.6, 0.4], 1e-4) %! assert_equal (c, [1, 0.4, 0.6], 1e-4) %!test %! xc = [5, 3, 5, 1.45]; %! obj = fitcknn (x, y, 'NumNeighbors', 10, 'distance', 'cosine'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'virginica'}) %! assert_equal (s, [0, 0.1, 0.9], 1e-4) %! assert_equal (c, [1, 0.9, 0.1], 1e-4) %!test %! xc = [5, 3, 5, 1.45]; %! obj = fitcknn (x, y, 'NumNeighbors', 10, 'distance', 'correlation'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'virginica'}) %! assert_equal (s, [0, 0.1, 0.9], 1e-4) %! assert_equal (c, [1, 0.9, 0.1], 1e-4) %!test %! xc = [5, 3, 5, 1.45]; %! obj = fitcknn (x, y, 'NumNeighbors', 30, 'distance', 'spearman'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'versicolor'}) %! assert_equal (s, [0, 1, 0], 1e-4) %! assert_equal (c, [1, 0, 1], 1e-4) %!test %! xc = [5, 3, 5, 1.45]; %! obj = fitcknn (x, y, 'NumNeighbors', 30, 'distance', 'hamming'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'setosa'}) %! assert_equal (s, [0.4333, 0.3333, 0.2333], 1e-4) %! assert_equal (c, [0.5667, 0.6667, 0.7667], 1e-4) %!test %! xc = [5, 3, 5, 1.45]; %! obj = fitcknn (x, y, 'NumNeighbors', 5, 'distance', 'hamming'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'setosa'}) %! assert_equal (s, [0.8, 0.2, 0], 1e-4) %! assert_equal (c, [0.2, 0.8, 1], 1e-4) %!test %! xc = [min(x); mean(x); max(x)]; %! obj = fitcknn (x, y, 'NumNeighbors', 10, 'distance', 'correlation'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'setosa'; 'versicolor'; 'virginica'}) %! assert_equal (s, [1, 0, 0; 0, 1, 0; 0, 0.4, 0.6], 1e-4) %! assert_equal (c, [0, 1, 1; 1, 0, 1; 1, 0.6, 0.4], 1e-4) %!test %! xc = [min(x); mean(x); max(x)]; %! obj = fitcknn (x, y, 'NumNeighbors', 10, 'distance', 'hamming'); %! [l, s, c] = predict (obj, xc); %! assert_equal (l, {'setosa';'setosa';'setosa'}) %! assert_equal (s, [0.9, 0.1, 0; 1, 0, 0; 0.5, 0, 0.5], 1e-4) %! assert_equal (c, [0.1, 0.9, 1; 0, 1, 1; 0.5, 1, 0.5], 1e-4) ## Test input validation for predict method %!error ... %! predict (ClassificationKNN (ones (4,2), ones (4,1))) %!error ... %! predict (ClassificationKNN (ones (4,2), ones (4,1)), []) %!error ... %! predict (ClassificationKNN (ones (4,2), ones (4,1)), 1) ## Test output for loss method %!test %! load fisheriris %! model = fitcknn (meas, species, 'NumNeighbors', 5); %! X = mean (meas); %! Y = {'versicolor'}; %! L = loss (model, X, Y); %! assert_equal (L, 0) %!test %! load fisheriris %! model = fitcknn (meas, species, 'NumNeighbors', 5); %! L = loss (model, meas, species, 'LossFun', 'binodeviance'); %! assert_equal (L, 0.1413, 1e-4) %!test %! load fisheriris %! model = fitcknn (meas, species); %! L = loss (model, meas, species, 'LossFun', 'binodeviance'); %! assert_equal (L, 0.1269, 1e-4) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = {'A'; 'B'; 'A'}; %! model = fitcknn (X, Y); %! X_test = [1, 6; 3, 3]; %! Y_test = {'A'; 'B'}; %! L = loss (model, X_test, Y_test); %! assert_equal (abs (L - 0.6667) > 1e-5, true) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = {'A'; 'B'; 'A'}; %! model = fitcknn (X, Y); %! X_with_nan = [1, 2; NaN, 4]; %! Y_test = {'A'; 'B'}; %! L = loss (model, X_with_nan, Y_test); %! assert_equal (abs (L - 0.3333) < 1e-4, true) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = {'A'; 'B'; 'A'}; %! model = fitcknn (X, Y); %! X_with_nan = [1, 2; NaN, 4]; %! Y_test = {'A'; 'B'}; %! L = loss (model, X_with_nan, Y_test, 'LossFun', 'logit'); %! assert_equal (isnan (L), true) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = {'A'; 'B'; 'A'}; %! model = fitcknn (X, Y); %! customLossFun = @(C, S, W, Cost) sum (W .* sum (abs (C - S), 2)); %! L = loss (model, X, Y, 'LossFun', customLossFun); %! assert_equal (L, 0) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = [1; 2; 1]; %! model = fitcknn (X, Y); %! L = loss (model, X, Y, 'LossFun', 'classiferror'); %! assert_equal (L, 0) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = [true; false; true]; %! model = fitcknn (X, Y); %! L = loss (model, X, Y, 'LossFun', 'binodeviance'); %! assert_equal (abs (L - 0.1269) < 1e-4, true) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = ['1'; '2'; '1']; %! model = fitcknn (X, Y); %! L = loss (model, X, Y, 'LossFun', 'classiferror'); %! assert_equal (L, 0) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = ['1'; '2'; '3']; %! model = fitcknn (X, Y); %! X_test = [3, 3]; %! Y_test = ['1']; %! L = loss (model, X_test, Y_test, 'LossFun', 'quadratic'); %! assert_equal (L, 1) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = ['1'; '2'; '3']; %! model = fitcknn (X, Y); %! X_test = [3, 3; 5, 7]; %! Y_test = ['1'; '2']; %! L = loss (model, X_test, Y_test, 'LossFun', 'classifcost'); %! assert_equal (L, 1) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = ['1'; '2'; '3']; %! model = fitcknn (X, Y); %! X_test = [3, 3; 5, 7]; %! Y_test = ['1'; '2']; %! L = loss (model, X_test, Y_test, 'LossFun', 'hinge'); %! assert_equal (L, 1) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = ['1'; '2'; '3']; %! model = fitcknn (X, Y); %! X_test = [3, 3; 5, 7]; %! Y_test = ['1'; '2']; %! W = [1; 2]; %! L = loss (model, X_test, Y_test, 'LossFun', 'logit', 'Weights', W); %! assert_equal (abs (L - 0.6931) < 1e-4, true) ## Test input validation for loss method %!error ... %! loss (ClassificationKNN (ones (4,2), ones (4,1))) %!error ... %! loss (ClassificationKNN (ones (4,2), ones (4,1)), ones (4,2)) %!error ... %! loss (ClassificationKNN (ones (40,2), randi ([1, 2], 40, 1)), [], zeros (2)) %!error ... %! loss (ClassificationKNN (ones (40,2), randi ([1, 2], 40, 1)), 1, zeros (2)) %!error ... %! loss (ClassificationKNN (ones (4,2), ones (4,1)), ones (4,2), ... %! ones (4,1), 'LossFun') %!error ... %! loss (ClassificationKNN (ones (4,2), ones (4,1)), ones (4,2), ones (3,1)) %!error ... %! loss (ClassificationKNN (ones (4,2), ones (4,1)), ones (4,2), ... %! ones (4,1), 'LossFun', 'a') %!error ... %! loss (ClassificationKNN (ones (4,2), ones (4,1)), ones (4,2), ... %! ones (4,1), 'Weights', 'w') ## Test output for margin method %!test %! load fisheriris %! mdl = fitcknn (meas, species, 'NumNeighbors', 5); %! X = mean (meas); %! Y = {'versicolor'}; %! m = margin (mdl, X, Y); %! assert_equal (m, 1) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = [1; 2; 3]; %! mdl = fitcknn (X, Y); %! m = margin (mdl, X, Y); %! assert_equal (m, [1; 1; 1]) %!test %! X = [7, 8; 9, 10]; %! Y = ['1'; '2']; %! mdl = fitcknn (X, Y); %! m = margin (mdl, X, Y); %! assert_equal (m, [1; 1]) %!test %! X = [11, 12]; %! Y = {'1'}; %! mdl = fitcknn (X, Y); %! m = margin (mdl, X, Y); %! assert_equal (isnan (m), true) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = [1; 2; 3]; %! mdl = fitcknn (X, Y); %! X1 = [15, 16]; %! Y1 = [1]; %! m = margin (mdl, X1, Y1); %! assert_equal (m, -1) ## Test input validation for margin method %!error ... %! margin (ClassificationKNN (ones (4,2), ones (4,1))) %!error ... %! margin (ClassificationKNN (ones (4,2), ones (4,1)), ones (4,2)) %!error ... %! margin (ClassificationKNN (ones (40,2), randi ([1, 2], 40, 1)), [], zeros (2)) %!error ... %! margin (ClassificationKNN (ones (40,2), randi ([1, 2], 40, 1)), 1, zeros (2)) %!error ... %! margin (ClassificationKNN (ones (4,2), ones (4,1)), ones (4,2), ones (3,1)) ## Test output for partialDependence %!shared X, Y, mdl %! X = [1, 2; 4, 5; 7, 8; 3, 2]; %! Y = [2; 1; 3; 2]; %! mdl = fitcknn (X, Y); %!test %! Vars = 1; %! Labels = 2; %! [pd, x, y] = partialDependence (mdl, Vars, Labels); %! pdm = [0.7500, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000]; %! assert_equal (pd, pdm) %!test %! Vars = 1; %! Labels = 2; %! [pd, x, y] = partialDependence (mdl, Vars, Labels, ... %! 'NumObservationsToSample', 5); %! pdm = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... %! 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... %! 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... %! 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... %! 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; %! assert_equal (all ((abs (pdm - pd) < 1)(:)), true) %!test %! Vars = 1; %! Labels = 2; %! [pd, x, y] = partialDependence (mdl, Vars, Labels, 'UseParallel', true); %! pdm = [0.7500, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000]; %! assert_equal (pd, pdm) %!test %! Vars = [1, 2]; %! Labels = 1; %! queryPoints = {linspace(0, 1, 3)', linspace(0, 1, 3)'}; %! [pd, x, y] = partialDependence (mdl, Vars, Labels, 'QueryPoints', ... %! queryPoints, 'UseParallel', true); %! pdm = [0, 0, 0; 0, 0, 0; 0, 0, 0]; %! assert_equal (pd, pdm) %!test %! Vars = 1; %! Labels = [1; 2]; %! [pd, x, y] = partialDependence (mdl, Vars, Labels); %! pdm = [0.2500, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.2500, 0.2500, 0.2500, ... %! 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, ... %! 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, ... %! 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, ... %! 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, ... %! 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, ... %! 0.2500, 0.2500; 0.7500, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, ... %! 0.5000, 0.5000, 0.5000]; %! assert_equal (pd, pdm) %!test %! Vars = [1, 2]; %! Labels = [1; 2]; %! queryPoints = {linspace(0, 1, 3)', linspace(0, 1, 3)'}; %! [pd, x, y] = partialDependence (mdl, Vars, Labels, 'QueryPoints', queryPoints); %! pdm(:,:,1) = [0, 0, 0; 1, 1, 1]; %! pdm(:,:,2) = [0, 0, 0; 1, 1, 1]; %! pdm(:,:,3) = [0, 0, 0; 1, 1, 1]; %! assert_equal (pd, pdm) %!test %! X1 = [1; 2; 4; 5; 7; 8; 3; 2]; %! X2 = ['2'; '3'; '1'; '3'; '1'; '3'; '2'; '2']; %! X = [X1, double(X2)]; %! Y = [1; 2; 3; 3; 2; 1; 2; 1]; %! mdl = fitcknn (X, Y, 'ClassNames', {'1', '2', '3'}); %! Vars = 1; %! Labels = 1; %! [pd, x, y] = partialDependence (mdl, Vars, Labels); %! pdm = [1.0000, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, ... %! 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, ... %! 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... %! 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... %! 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.3750, ... %! 0.3750, 0.3750, 0.3750, 0.3750, 0.3750, 0.3750, 0.3750, 0.3750, 0.3750, ... %! 0.3750, 0.3750, 0.3750, 0.3750, 0.7500, 0.7500, 0.7500, 0.7500, 0.7500, ... %! 0.7500, 0.7500, 0.7500]; %! assert_equal (pd, pdm) %!test %! X1 = [1; 2; 4; 5; 7; 8; 3; 2]; %! X2 = ['2'; '3'; '1'; '3'; '1'; '3'; '2'; '2']; %! X = [X1, double(X2)]; %! Y = [1; 2; 3; 3; 2; 1; 2; 1]; %! predictorNames = {'Feature1', 'Feature2'}; %! mdl = fitcknn (X, Y, 'PredictorNames', predictorNames); %! Vars = 'Feature1'; %! Labels = 1; %! [pd, x, y] = partialDependence (mdl, Vars, Labels); %! pdm = [1.0000, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, ... %! 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, ... %! 0.6250, 0.6250, 0.6250, 0.6250, 0.6250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... %! 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... %! 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.3750, ... %! 0.3750, 0.3750, 0.3750, 0.3750, 0.3750, 0.3750, 0.3750, 0.3750, 0.3750, ... %! 0.3750, 0.3750, 0.3750, 0.3750, 0.7500, 0.7500, 0.7500, 0.7500, 0.7500, ... %! 0.7500, 0.7500, 0.7500]; %! assert_equal (pd, pdm) %!test %! X1 = [1; 2; 4; 5; 7; 8; 3; 2]; %! X2 = ['2'; '3'; '1'; '3'; '1'; '3'; '2'; '2']; %! X = [X1, double(X2)]; %! Y = [1; 2; 3; 3; 2; 1; 2; 1]; %! predictorNames = {'Feature1', 'Feature2'}; %! mdl = fitcknn (X, Y, 'PredictorNames', predictorNames); %! new_X1 = [10; 5; 6; 8; 9; 20; 35; 6]; %! new_X2 = ['2'; '2'; '1'; '2'; '1'; '3'; '3'; '2']; %! new_X = [new_X1, double(new_X2)]; %! Vars = 'Feature1'; %! Labels = 1; %! [pd, x, y] = partialDependence (mdl, Vars, Labels, new_X); %! pdm = [0, 0, 0, 0, 0, 0.2500, 0.2500, 0.2500, 0.2500, 0.7500, 0.7500, ... %! 0.7500, 0.7500, 0.7500, 0.7500, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, ... %! 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, ... %! 1.0000, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ... %! 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ... %! 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; %! assert_equal (pd, pdm) ## Test input validation for partialDependence method %!error ... %! partialDependence (ClassificationKNN (ones (4,2), ones (4,1))) %!error ... %! partialDependence (ClassificationKNN (ones (4,2), ones (4,1)), 1) %!error ... %! partialDependence (ClassificationKNN (ones (4,2), ones (4,1)), 1, ... %! ones (4,1), 'NumObservationsToSample') %!error ... %! partialDependence (ClassificationKNN (ones (4,2), ones (4,1)), 1, ... %! ones (4,1), 2) ## Test output for crossval method %!shared x, y, obj %! load fisheriris %! x = meas; %! y = species; %! covMatrix = cov (x); %! obj = fitcknn (x, y, 'NumNeighbors', 5, 'Distance', ... %! 'mahalanobis', 'Cov', covMatrix); %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (obj); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (CVMdl.KFold == 10, true) %! assert_equal (CVMdl.ModelParameters.NumNeighbors == 5, true) %! assert_equal (strcmp (CVMdl.ModelParameters.Distance, 'mahalanobis'), true) %! assert_equal (class (CVMdl.Trained{1}), "ClassificationKNN") %! assert_equal (isempty (CVMdl.Trained{1}.Mu), true) %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (obj, 'KFold', 5); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (CVMdl.KFold == 5, true) %! assert_equal (CVMdl.ModelParameters.NumNeighbors == 5, true) %! assert_equal (strcmp (CVMdl.ModelParameters.Distance, 'mahalanobis'), true) %! assert_equal (class (CVMdl.Trained{1}), "ClassificationKNN") %! assert_equal (isempty (CVMdl.Trained{1}.Mu), isempty (obj.Mu)) %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (obj, 'HoldOut', 0.2); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (CVMdl.ModelParameters.NumNeighbors == 5, true) %! assert_equal (strcmp (CVMdl.ModelParameters.Distance, 'mahalanobis'), true) %! assert_equal (class (CVMdl.Trained{1}), "ClassificationKNN") %! assert_equal (isempty (CVMdl.Trained{1}.Mu), isempty (obj.Mu)) %!test %! obj = fitcknn (x, y, 'NumNeighbors', 10, 'Distance', 'cityblock'); %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (obj, 'HoldOut', 0.2); %! warning (status); %! CVMdl = crossval (obj, 'LeaveOut', 'on'); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (CVMdl.ModelParameters.NumNeighbors == 10, true) %! assert_equal (strcmp (CVMdl.ModelParameters.Distance, 'cityblock'), true) %! assert_equal (class (CVMdl.Trained{1}), "ClassificationKNN") %! assert_equal (isempty (CVMdl.Trained{1}.Mu), isempty (obj.Mu)) %!test %! obj = fitcknn (x, y, 'NumNeighbors', 10, 'Distance', 'cityblock'); %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! partition = cvpartition (y, 'KFold', 3); %! warning (status); %! CVMdl = crossval (obj, 'cvPartition', partition); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal (CVMdl.KFold == 3, true) %! assert_equal (CVMdl.ModelParameters.NumNeighbors == 10, true) %! assert_equal (strcmp (CVMdl.ModelParameters.Distance, 'cityblock'), true) %! assert_equal (class (CVMdl.Trained{1}), "ClassificationKNN") %! assert_equal (isempty (CVMdl.Trained{1}.Mu), isempty (obj.Mu)) ## Test input validation for crossval method %!error ... %! crossval (ClassificationKNN (ones (4,2), ones (4,1)), 'kfold') %!error... %! crossval (ClassificationKNN (ones (4,2), ones (4,1)), 'kfold', 12, 'holdout', 0.2) %!error ... %! crossval (ClassificationKNN (ones (4,2), ones (4,1)), 'kfold', 'a') %!error ... %! crossval (ClassificationKNN (ones (4,2), ones (4,1)), 'holdout', 2) %!error ... %! crossval (ClassificationKNN (ones (4,2), ones (4,1)), 'leaveout', 1) %!error ... %! crossval (ClassificationKNN (ones (4,2), ones (4,1)), 'cvpartition', 1) %!error ... %! savemodel (ClassificationKNN ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])) %!error ... %! savemodel (ClassificationKNN ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), 1) %!error ... %! savemodel (ClassificationKNN ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), ['ab'; 'cd']) ## There are only as many neighbors as training samples, so a larger ## NumNeighbors is capped at that. It used to be stored as requested and then ## fail at prediction with a nonconformant operator error. %!test %! x = [1, 2; 3, 4; 5, 6; 7, 8; 2, 3]; %! y = [1; 2; 1; 2; 1]; %! for k = [5, 6, 20] %! a = fitcknn (x, y, 'NumNeighbors', k); %! assert_equal (a.NumNeighbors, 5); %! assert_equal (predict (a, [2, 2; 6, 6]), [1; 1]); %! endfor ## An assigned ScoreTransform reaches the scores predict returns. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NumNeighbors', 5); %! [~, s0] = predict (Mdl, meas(60,:)); %! Mdl.ScoreTransform = 'logit'; %! [~, s1] = predict (Mdl, meas(60,:)); %! assert_equal (s1, 1 ./ (1 + exp (-s0)), 1e-12); ## The transform is applied once per prediction, not once per observation. ## It used to sit inside the loop over the query rows and be applied to ## everything accumulated so far, so row i of n came back transformed n-i+1 ## times and only the last row was right. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NumNeighbors', 5); %! [~, s0] = predict (Mdl, meas(1:6,:)); %! Mdl.ScoreTransform = 'logit'; %! [~, s1] = predict (Mdl, meas(1:6,:)); %! assert_equal (s1, 1 ./ (1 + exp (-s0)), 1e-12); ## A row predicted on its own and the same row predicted among others get the ## same scores, which is what applying the transform once per row means. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'ScoreTransform', 'doublelogit'); %! [~, s1] = predict (Mdl, meas(1,:)); %! [~, s6] = predict (Mdl, meas(1:6,:)); %! assert_equal (s6(1,:), s1, 1e-12); ## The values are R2024a's: a default KNN scores the first four observations ## [1 0 0] and doublelogit takes that to [0.8808 0.5 0.5], every row alike. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'ScoreTransform', 'doublelogit'); %! [~, s] = predict (Mdl, meas(1:4,:)); %! assert_equal (s, repmat ([0.880797077977882, 0.5, 0.5], 4, 1), 1e-12); ## 'logit' is the logistic function and 'invlogit' its inverse, as MATLAB has ## them, and not the other way about. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NumNeighbors', 4); %! [~, s0] = predict (Mdl, meas(60,:)); %! Mdl.ScoreTransform = 'invlogit'; %! [~, s] = predict (Mdl, meas(60,:)); %! assert_equal (s, log (s0 ./ (1 - s0)), 1e-12); ## Setting ScoreTransform to 'none' leaves predict working, the transform being ## a function handle like every other. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NumNeighbors', 5); %! [~, s0] = predict (Mdl, meas(1:3,:)); %! Mdl.ScoreTransform = 'none'; %! assert_equal (class (Mdl.ScoreTransform), 'char'); %! [~, s1] = predict (Mdl, meas(1:3,:)); %! assert_equal (s1, s0); ## RowsUsed is empty when every observation was used. %!test %! load fisheriris %! X = meas; %! Y = grp2idx (species); %! Mdl = fitcknn (X, Y); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (class (Mdl.RowsUsed), 'double'); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (rows (Mdl.X), 150); ## A missing response drops its observation and RowsUsed marks it. %!test %! load fisheriris %! X = meas; %! Y = grp2idx (species); %! Y(5) = NaN; %! Mdl = fitcknn (X, Y); %! assert_equal (class (Mdl.RowsUsed), 'logical'); %! assert_equal (size (Mdl.RowsUsed), [150, 1]); %! assert_equal (sum (Mdl.RowsUsed), 149); %! assert_equal (Mdl.RowsUsed(5), false); %! assert_equal (Mdl.NumObservations, 149); %! assert_equal (rows (Mdl.X), 149); ## A missing predictor keeps its observation, so RowsUsed stays empty. %!test %! load fisheriris %! X = meas; %! X(3,2) = NaN; %! Y = grp2idx (species); %! Mdl = fitcknn (X, Y); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (rows (Mdl.X), 150); %! assert_equal (sum (isnan (Mdl.X(:))), 1); ## Mu and Sigma are empty unless the predictors are standardized. %!test %! load fisheriris %! Mdl = fitcknn (meas, species); %! assert_equal (Mdl.Mu, []); %! assert_equal (Mdl.Sigma, []); ## Each predictor is summarized from every observation that has it. Column 3 ## is complete, so its mean spans all 150 rows. Values from MATLAB R2024a. %!test %! load fisheriris %! X = meas; %! X(3,2) = NaN; X(17,4) = NaN; X(140,1) = NaN; %! Mdl = fitcknn (X, species, 'Standardize', true); %! assert_equal (Mdl.Mu, [5.8362416107382593, 3.0563758389261766, ... %! 3.7580000000000031, 1.2046979865771823], 1e-13); %! assert_equal (Mdl.Sigma, [0.82627581654893234, 0.43717801242449683, ... %! 1.7652982332594667, 0.76196186774754338], 1e-13); %! assert_equal (Mdl.Mu(3), mean (meas(:,3)), 1e-13); ## Each class carries its prior spread over its own observations. Values ## from R2024a. %!test %! load fisheriris %! i3 = [1:50, 51:80, 101:120]; %! Mdl = fitcknn (meas(i3,:), species(i3), 'Prior', 'uniform'); %! assert_equal (Mdl.Prior, [1, 1, 1] / 3, 1e-14); %! assert_equal (Mdl.W(1), 1/150, 1e-14); %! assert_equal (Mdl.W(51), 1/90, 1e-14); %! assert_equal (Mdl.W(81), 1/60, 1e-14); ## Reassigning the prior rebuilds the weights with it. %!test %! load fisheriris %! i3 = [1:50, 51:80, 101:120]; %! Mdl = fitcknn (meas(i3,:), species(i3)); %! assert_equal (Mdl.W(1), 0.01, 1e-14); %! Mdl.Prior = [0.98, 0.01, 0.01]; %! assert_equal (Mdl.W(1), 0.0196, 1e-14); ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! Mdl = fitcknn (meas, species); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'ClassificationKNN'); %! assert_equal (M2.NumObservations, Mdl.NumObservations); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ScoreTransform), class (Mdl.ScoreTransform)); %! assert_equal (predict (M2, meas(1:5,:)), predict (Mdl, meas(1:5,:))); ## The six settable properties. Every value below was measured on MATLAB ## R2024a; see KNN_SETTABLE_LEDGER.md for the probes that produced them. ## A lazy learner does its work at predict time, so a reassigned model ## predicts exactly as one refitted with the same options. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NumNeighbors', 1); %! Mdl.NumNeighbors = 50; %! Mdl.DistanceWeight = 'inverse'; %! Mdl.Distance = 'cityblock'; %! M2 = fitcknn (meas, species, 'NumNeighbors', 50, ... %! 'DistanceWeight', 'inverse', 'Distance', 'cityblock'); %! assert_equal (predict (Mdl, meas), predict (M2, meas)); ## More neighbours than observations is reduced, not refused. %!test %! load fisheriris %! Mdl = fitcknn (meas, species); %! Mdl.NumNeighbors = 1000; %! assert_equal (Mdl.NumNeighbors, 150); ## NSMethod is read-only and constrains the metric: a kd-tree can only be ## searched with the four metrics it was built for. %!test %! load fisheriris %! Mdl = fitcknn (meas, species); %! assert_equal (Mdl.NSMethod, 'kdtree'); %! Mdl.Distance = 'minkowski'; %! assert_equal (Mdl.NSMethod, 'kdtree'); %! assert_equal (Mdl.DistParameter, 2); ## The parameter belongs to the metric, so changing the metric recomputes it ## and whatever the previous one held is discarded. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'Distance', 'minkowski', ... %! 'NSMethod', 'exhaustive'); %! Mdl.DistParameter = 3; %! Mdl.Distance = 'euclidean'; %! assert_equal (Mdl.DistParameter, []); %! Mdl.Distance = 'minkowski'; %! assert_equal (Mdl.DistParameter, 2); ## Reassigning the same metric is not a change, so the parameter survives. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'Distance', 'minkowski', ... %! 'NSMethod', 'exhaustive'); %! Mdl.DistParameter = 3; %! Mdl.Distance = 'minkowski'; %! assert_equal (Mdl.DistParameter, 3); %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NSMethod', 'exhaustive'); %! Mdl.Distance = 'seuclidean'; %! assert_equal (Mdl.DistParameter, std (meas), 1e-12); %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NSMethod', 'exhaustive'); %! Mdl.Distance = 'mahalanobis'; %! assert_equal (Mdl.DistParameter, cov (meas), 1e-12); ## DistanceWeight is the text, as MATLAB reports it, and it weights the vote. %!test %! Mdl = fitcknn ([1, 0; 2, 0; 50, 0; 51, 0], {'b';'a';'a';'b'}, ... %! 'NumNeighbors', 2, 'NSMethod', 'exhaustive'); %! assert_equal (Mdl.DistanceWeight, 'equal'); %! [label, score] = predict (Mdl, [0, 0]); %! assert_equal (label, {'a'}); %! assert_equal (score, [0.5, 0.5], 1e-6); %!test %! Mdl = fitcknn ([1, 0; 2, 0; 50, 0; 51, 0], {'b';'a';'a';'b'}, ... %! 'NumNeighbors', 2, 'DistanceWeight', 'inverse', ... %! 'NSMethod', 'exhaustive'); %! [label, score] = predict (Mdl, [0, 0]); %! assert_equal (label, {'b'}); %! assert_equal (score, [1/3, 2/3], 1e-6); %!test %! Mdl = fitcknn ([1, 0; 2, 0; 50, 0; 51, 0], {'b';'a';'a';'b'}, ... %! 'NumNeighbors', 2, 'DistanceWeight', 'squaredinverse', ... %! 'NSMethod', 'exhaustive'); %! [label, score] = predict (Mdl, [0, 0]); %! assert_equal (label, {'b'}); %! assert_equal (score, [0.2, 0.8], 1e-6); ## BreakTies decides the label when two classes hold the same weight: the ## first class name, or the class of the nearest of the tied neighbours. %!test %! Mdl = fitcknn ([1, 0; 2, 0; 50, 0; 51, 0], {'b';'a';'a';'b'}, ... %! 'NumNeighbors', 2, 'NSMethod', 'exhaustive'); %! Mdl.BreakTies = 'smallest'; %! assert_equal (predict (Mdl, [0, 0]), {'a'}); %! Mdl.BreakTies = 'nearest'; %! assert_equal (predict (Mdl, [0, 0]), {'b'}); ## IncludeTies takes every neighbour sitting at the kth distance, which turns ## a single winner into a split. %!test %! x = [0, 0; 0, 0; 1, 1; 1, 1; 2, 2; 2, 2]; %! y = {'a'; 'b'; 'a'; 'b'; 'a'; 'b'}; %! Mdl = fitcknn (x, y, 'NumNeighbors', 1, 'NSMethod', 'exhaustive'); %! [~, score] = predict (Mdl, [0.5, 0.5]); %! assert_equal (score, [1, 0]); %! Mdl.IncludeTies = true; %! [~, score] = predict (Mdl, [0.5, 0.5]); %! assert_equal (score, [0.5, 0.5]); ## A saved model comes back with the weight it was given. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'DistanceWeight', 'squaredinverse'); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.DistanceWeight, 'squaredinverse'); %! assert_equal (predict (M2, meas), predict (Mdl, meas)); %!error ... %! Mdl = fitcknn (ones (5, 2), [1;1;1;2;2]); Mdl.NumNeighbors = 0; %!error ... %! Mdl = fitcknn (ones (5, 2), [1;1;1;2;2]); Mdl.NumNeighbors = 2.5; %!error ... %! Mdl = fitcknn (ones (5, 2), [1;1;1;2;2]); Mdl.BreakTies = 'bogus'; %!error ... %! Mdl = fitcknn (ones (5, 2), [1;1;1;2;2]); Mdl.IncludeTies = 2; %!error ... %! Mdl = fitcknn (ones (5, 2), [1;1;1;2;2]); Mdl.DistanceWeight = 'bogus'; %!error ... %! load fisheriris; Mdl = fitcknn (meas, species); ... %! Mdl.Distance = 'mahalanobis'; %!error ... %! load fisheriris; ... %! Mdl = fitcknn (meas, species, 'NSMethod', 'exhaustive'); ... %! Mdl.DistParameter = 2; %!error ... %! load fisheriris; ... %! Mdl = fitcknn (meas, species, 'Distance', 'minkowski', ... %! 'NSMethod', 'exhaustive'); ... %! Mdl.DistParameter = 0; %!error ... %! load fisheriris; ... %! Mdl = fitcknn (meas, species, 'Distance', 'seuclidean', ... %! 'NSMethod', 'exhaustive'); ... %! Mdl.DistParameter = ones (1, 3); %!error ... %! load fisheriris; ... %! Mdl = fitcknn (meas, species, 'Distance', 'mahalanobis', ... %! 'NSMethod', 'exhaustive'); ... %! Mdl.DistParameter = -eye (4); ## A zero scale is refused, where MATLAB accepts it and warns at predict time. ## The message MATLAB raises for a negative scale already promises positive ## values, so this keeps the property and its own contract in agreement. %!error ... %! load fisheriris; ... %! Mdl = fitcknn (meas, species, 'Distance', 'seuclidean', ... %! 'NSMethod', 'exhaustive'); ... %! Mdl.DistParameter = zeros (1, 4); ## edge, resubPredict, resubMargin, resubEdge and resubLoss. Measured on ## MATLAB R2024a, whose posteriors this class reproduces exactly. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NumNeighbors', 5); %! assert_equal (edge (Mdl, meas, species), 0.9253333333, 1e-9); %! assert_equal (edge (Mdl, meas, species), ... %! mean (margin (Mdl, meas, species)), 1e-12); ## Weights normalized within class to the prior, not divided by their total, ## which would give 0.9006799117. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NumNeighbors', 5); %! assert_equal (edge (Mdl, meas, species, 'Weights', (1:150)'), ... %! 0.9265719286, 1e-9); %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NumNeighbors', 5); %! assert_equal (resubEdge (Mdl), 0.9253333333, 1e-9); %! assert_equal (resubLoss (Mdl), 1/30, 1e-12); %! assert_equal (sum (resubMargin (Mdl)), 138.8, 1e-9); ## A one-neighbour model is its own nearest neighbour everywhere, so the ## resubstitution numbers are perfect and say nothing about the model. %!test %! load fisheriris %! Mdl = fitcknn (meas, species); %! assert_equal (resubLoss (Mdl), 0); %! assert_equal (resubEdge (Mdl), 1); %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NumNeighbors', 5); %! [label, score, cost] = resubPredict (Mdl); %! [l2, s2, c2] = predict (Mdl, meas); %! assert_equal (label, l2); %! assert_equal (score, s2); %! assert_equal (cost, c2); %!error ... %! load fisheriris; edge (fitcknn (meas, species), meas) %!error ... %! load fisheriris; ... %! edge (fitcknn (meas, species), meas, species, 'Weights', ones (3, 1)) %!error ... %! load fisheriris; edge (fitcknn (meas, species), meas, species, 'Nope', 1) ## BinEdges is an empty cell, which is what MATLAB reports for this ## learner as well: it fits the predictors as they are. %!test %! load fisheriris %! Mdl = fitcknn (meas, species); %! assert_equal (class (Mdl.BinEdges), 'cell'); %! assert_equal (Mdl.BinEdges, {}); ## CategoricalPredictors and ExpandedPredictorNames, shapes measured on ## R2024a: an empty double and one name per predictor. %!test %! load fisheriris %! Mdl = fitcknn (meas, species); %! assert_equal (Mdl.CategoricalPredictors, []); %! assert_equal (size (Mdl.CategoricalPredictors), [0, 0]); %! assert_equal (Mdl.ExpandedPredictorNames, Mdl.PredictorNames); %! assert_equal (size (Mdl.ExpandedPredictorNames), [1, 4]); %!test %! load fisheriris %! Mdl = fitcknn (meas, species); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.CategoricalPredictors, Mdl.CategoricalPredictors); %! assert_equal (M2.ExpandedPredictorNames, Mdl.ExpandedPredictorNames); ## The shared cost guard is in force here too, and the struct form is ## permuted into this model's class order. The battery is on ## ClassificationDiscriminant. %!test %! load fisheriris %! Mdl = fitcknn (meas, species); %! S = struct ('ClassNames', {{'virginica'; 'setosa'; 'versicolor'}}, ... %! 'ClassificationCosts', [0, 1, 2; 3, 0, 4; 5, 6, 0]); %! Mdl.Cost = S; %! assert_equal (Mdl.Cost, [0, 4, 3; 6, 0, 5; 1, 2, 0]); %!error ... %! load fisheriris %! Mdl = fitcknn (meas, species); %! Mdl.Cost = ones (3); ## HyperparameterOptimizationResults is declared for MATLAB compatibility and ## stays empty, this class running no search over its hyperparameters. %!test %! load fisheriris %! Mdl = fitcknn (meas, species); %! assert_equal (isempty (Mdl.HyperparameterOptimizationResults), true); ## ModelParameters records the fit as it was asked for. The field list and ## its order are MATLAB's, measured on R2024a. %!test %! load fisheriris %! Mdl = fitcknn (meas, species); %! assert_equal (fieldnames (Mdl.ModelParameters)', {'NumNeighbors', ... %! 'NSMethod', 'Distance', 'BucketSize', 'IncludeTies', 'DistanceWeight', ... %! 'BreakTies', 'Exponent', 'Cov', 'Scale', 'StandardizeData', 'Version', ... %! 'Method', 'Type'}); %!test %! load fisheriris %! MP = fitcknn (meas, species).ModelParameters; %! assert_equal (MP.NumNeighbors, 1); %! assert_equal (MP.NSMethod, 'kdtree'); %! assert_equal (MP.Distance, 'euclidean'); %! assert_equal (MP.BucketSize, 50); %! assert_equal (MP.IncludeTies, false); %! assert_equal (MP.DistanceWeight, 'equal'); %! assert_equal (MP.BreakTies, 'smallest'); %!test %! load fisheriris %! MP = fitcknn (meas, species).ModelParameters; %! assert_equal (MP.Version, 1); %! assert_equal (MP.Method, 'KNN'); %! assert_equal (MP.Type, 'classification'); ## BucketSize belongs to the kd-tree alone, so an exhaustive search reports ## none, as MATLAB does. %!test %! load fisheriris %! MP = fitcknn (meas, species, 'NSMethod', 'exhaustive').ModelParameters; %! assert_equal (isempty (MP.BucketSize), true); ## The three distance parameters hold what was passed, never what was ## computed: a mahalanobis fit given no Cov reports none, while ## DistParameter carries the covariance the fit used. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'Distance', 'mahalanobis'); %! assert_equal (isempty (Mdl.ModelParameters.Cov), true); %! assert_equal (size (Mdl.DistParameter), [4, 4]); %!test %! load fisheriris %! C = cov (meas); %! MP = fitcknn (meas, species, 'Distance', 'mahalanobis', ... %! 'Cov', C).ModelParameters; %! assert_equal (MP.Cov, C); %!test %! load fisheriris %! MP = fitcknn (meas, species, 'Distance', 'minkowski', ... %! 'Exponent', 3).ModelParameters; %! assert_equal (MP.Exponent, 3); %! assert_equal (isempty (MP.Cov), true); ## Exponent is the one of the three that carries a default: a 'minkowski' ## fit that names none reports 2, where Cov and Scale would stay empty. %!test %! load fisheriris %! MP = fitcknn (meas, species, 'Distance', 'minkowski').ModelParameters; %! assert_equal (MP.Exponent, 2); %!test %! load fisheriris %! MP = fitcknn (meas, species, 'Distance', 'seuclidean').ModelParameters; %! assert_equal (isempty (MP.Scale), true); ## An explicit BucketSize is still not reported when the search cannot use ## it, as MATLAB does. %!test %! load fisheriris %! MP = fitcknn (meas, species, 'NSMethod', 'exhaustive', ... %! 'BucketSize', 30).ModelParameters; %! assert_equal (isempty (MP.BucketSize), true); %!test %! load fisheriris %! MP = fitcknn (meas, species, 'NSMethod', 'kdtree', ... %! 'BucketSize', 30).ModelParameters; %! assert_equal (MP.BucketSize, 30); %!test %! load fisheriris %! S = std (meas); %! MP = fitcknn (meas, species, 'Distance', 'seuclidean', ... %! 'Scale', S).ModelParameters; %! assert_equal (MP.Scale, S); %!test %! load fisheriris %! MP = fitcknn (meas, species, 'Standardize', true).ModelParameters; %! assert_equal (MP.StandardizeData, true); %! assert_equal (class (MP.StandardizeData), 'logical'); %!test %! load fisheriris %! MP = fitcknn (meas, species, 'NumNeighbors', 10, ... %! 'DistanceWeight', 'inverse', ... %! 'BreakTies', 'nearest', 'IncludeTies', true).ModelParameters; %! assert_equal (MP.NumNeighbors, 10); %! assert_equal (MP.DistanceWeight, 'inverse'); %! assert_equal (MP.BreakTies, 'nearest'); %! assert_equal (MP.IncludeTies, true); ## A model searched exhaustively with a metric no kd-tree can take survives ## the round trip. Its loader used to refuse it: the metric was assigned ## while NSMethod still held the default the empty model is built with. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'Distance', 'seuclidean', ... %! 'NSMethod', 'exhaustive'); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.NSMethod, 'exhaustive'); %! assert_equal (M2.Distance, 'seuclidean'); %! assert_equal (M2.DistParameter, Mdl.DistParameter); %! assert_equal (predict (M2, meas(1:5,:)), predict (Mdl, meas(1:5,:))); %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'Distance', 'mahalanobis'); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.Distance, 'mahalanobis'); %! assert_equal (M2.DistParameter, Mdl.DistParameter); %! assert_equal (predict (M2, meas(1:5,:)), predict (Mdl, meas(1:5,:))); ## 'manhattan' names the same metric as 'cityblock' and a kd-tree is built ## for it, so it may be assigned to one and a model fitted with it reloads. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'Distance', 'manhattan'); %! assert_equal (Mdl.NSMethod, 'kdtree'); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.Distance, 'manhattan'); %! assert_equal (predict (M2, meas(1:5,:)), predict (Mdl, meas(1:5,:))); ## The third output is the expected cost of each assignment, the posterior ## weighted by the cost matrix. A default matrix cannot tell that apart from ## 1 - posterior, so the fixture below sets an asymmetric one. %!test %! load fisheriris %! b = strcmp (species, 'setosa'); %! Mdl = fitcknn (meas, b, 'Cost', [0, 2; 5, 0]); %! [label, ~, cost] = predict (Mdl, meas([1, 51],:)); %! assert_equal (label, [true; false]); %! assert_equal (cost, [5, 0; 0, 2]); %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NumNeighbors', 5); %! [~, score, cost] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (cost, 1 - score); ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NumNeighbors', 5); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = fitcknn (meas, species, 'NumNeighbors', 5); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/ClassificationKernel.m000066400000000000000000001744731524624707500265660ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftp {statistics} ClassificationKernel ## ## Gaussian kernel binary classifier for large data. ## ## A @qcode{ClassificationKernel} object maps the predictors into a ## randomized feature space whose inner product approximates a Gaussian ## kernel, and then fits a linear model there. A kernel classifier is ## therefore as nonlinear as a support vector machine with a Gaussian kernel, ## while costing what a linear fit costs: nothing of size @math{NxN} is ever ## formed. ## ## The expansion is the random Fourier basis of Rahimi and Recht, drawn once ## when the model is fitted and kept with it, so @code{predict} maps new data ## through the same basis. MATLAB approximates the same kernel by the ## Fastfood construction, which reaches the same distribution more cheaply; ## the two are interchangeable in distribution but not draw by draw, and the ## draws come from different generators in any case, so the scores of a model ## fitted here and one fitted in MATLAB differ even from the same seed. ## What does not differ is what they estimate. ## ## Like @qcode{ClassificationLinear} the object holds no copy of the training ## data. It does hold the basis and the coefficients, so it is bounded by ## the number of expansion dimensions rather than by the number of ## observations. ## ## Create a @qcode{ClassificationKernel} object with @code{fitckernel}. ## ## @seealso{fitckernel, ClassificationLinear, ClassificationSVM} ## @end deftp classdef ClassificationKernel properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} BoxConstraint ## ## Box constraint of the support vector machine ## ## A positive scalar. It is the reciprocal of the product of ## @qcode{Lambda} and the number of observations, so setting either of ## the two in the constructor fixes the other, and giving both is an ## error. This property is read-only. ## ## @end deftp BoxConstraint = 1; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} ClassNames ## ## Names of the two classes ## ## A column of the same type as the response supplied to the constructor. ## The second of the two is the positive class, the one a positive score ## belongs to. This property is read-only. ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} Prior ## ## Prior probability of each class ## ## A numeric row vector with one element per class, in the order of ## @qcode{ClassNames} and summing to one. It defaults to the class ## frequencies of the training data. This property is read-only. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} Cost ## ## Cost of misclassifying an observation ## ## A square numeric matrix with one row and one column per class, whose ## @math{(i,j)} element is the cost of classifying an observation of ## class @math{i} into class @math{j}. It defaults to one everywhere ## except the diagonal, which is zero. This property is read-only, as it ## is in MATLAB; a cost matrix is given to the constructor instead. ## ## The costs are folded into the prior before the observations are ## weighted, so a class that is costlier to misclassify weighs more in ## the fit. They are read again by the @qcode{'mincost'} and ## @qcode{'classifcost'} losses. ## ## @end deftp Cost = []; endproperties properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} ScoreTransform ## ## Transformation applied to the predicted scores ## ## A character vector naming a transformation, or the text of the ## function handle that was supplied. Assigning to it accepts either. ## It defaults to @qcode{'logit'} for a logistic learner and to ## @qcode{'none'} for a support vector machine. ## ## @end deftp ScoreTransform = 'none'; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} PredictorNames ## ## Names of the predictors ## ## A cell array of character vectors with one name per column of the ## training data, defaulting to @qcode{'x1'}, @qcode{'x2'} and so on. ## This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A row vector of column indices, empty when every predictor is ## numeric. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} ResponseName ## ## Name of the response ## ## A character vector, defaulting to @qcode{'Y'}. This property is ## read-only. ## ## @end deftp ResponseName = 'Y'; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} ExpandedPredictorNames ## ## Names of the predictors as the fit saw them ## ## A cell array of character vectors. These name the original ## predictors, not the expansion dimensions, which have no names. This ## property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} NumExpansionDimensions ## ## Number of dimensions of the expanded space ## ## A positive integer scalar. It defaults to ## @code{2 .^ ceil (min (log2 (@var{p}) + 5, 15))} for @var{p} ## predictors, so four predictors give 128 dimensions. More dimensions ## approximate the kernel more closely and cost proportionally more. ## This property is read-only. ## ## @end deftp NumExpansionDimensions = []; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} FittedLoss ## ## Loss function the fit minimized ## ## @qcode{'hinge'} for a support vector machine and @qcode{'logit'} for a ## logistic regression. This property is read-only. ## ## @end deftp FittedLoss = 'hinge'; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} Lambda ## ## Regularization strength ## ## A nonnegative scalar, the reciprocal of the product of ## @qcode{BoxConstraint} and the number of observations. This property ## is read-only. ## ## @end deftp Lambda = []; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} ModelParameters ## ## Fitting options, as they were given ## ## A structure holding every parameter of the fit, with the ## @qcode{'auto'} values as they were given rather than as they were ## resolved. This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} Regularization ## ## Penalty on the coefficients ## ## Always @qcode{'ridge (L2)'}: a kernel model fits in the expanded ## space, where a lasso penalty has nothing to select. This property is ## read-only. ## ## @end deftp Regularization = 'ridge (L2)'; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} KernelScale ## ## Scale of the Gaussian kernel ## ## A positive scalar dividing every predictor before the expansion, so a ## larger scale makes the kernel wider and the classifier smoother. This ## property is read-only. ## ## @end deftp KernelScale = 1; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} Learner ## ## Linear model fitted in the expanded space ## ## Either @qcode{'svm'} or @qcode{'logistic'}. This property is ## read-only. ## ## @end deftp Learner = 'svm'; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} Mu ## ## Predictor means used to standardize ## ## A row vector with one element per predictor, or empty when the model ## was fitted without standardizing. This property is read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {ClassificationKernel} {property} Sigma ## ## Predictor standard deviations used to standardize ## ## A row vector with one element per predictor, or empty when the model ## was fitted without standardizing. This property is read-only. ## ## @end deftp Sigma = []; endproperties properties (GetAccess = public, SetAccess = protected, Hidden) ## The callable behind ScoreTransform. STfun = @(s) s; ## The random basis and the coefficients fitted in the space it spans. ## MATLAB keeps all three out of sight, so they are hidden here too, but ## they are the model: without the basis the coefficients index nothing. Basis_ = []; Beta_ = []; Bias_ = []; ## Number of predictors and of observations the fit saw. Neither is a ## property of MATLAB's class, but predict needs the first to validate ## its input and resume needs the second to keep Lambda and BoxConstraint ## reciprocal. NumPredictors_ = []; NumObservations_ = []; ## What the fit reported, so that fitckernel can hand it back as its ## second output. FitInfo_ = []; endproperties methods (Access = public) ## -*- texinfo -*- ## @deftypefn {ClassificationKernel} {@var{obj} =} ClassificationKernel (@var{X}, @var{Y}) ## @deftypefnx {ClassificationKernel} {@var{obj} =} ClassificationKernel (@dots{}, @var{name}, @var{value}) ## ## Fit a Gaussian kernel binary classifier. ## ## @code{@var{obj} = ClassificationKernel (@var{X}, @var{Y})} fits a ## support vector machine in a randomized Gaussian kernel space to the ## @math{NxP} predictor matrix @var{X} and the @math{Nx1} response ## @var{Y}, which must name exactly two classes. ## ## @code{@var{obj} = ClassificationKernel (@dots{}, @var{name}, ## @var{value})} takes the following @qcode{Name-Value} pairs. ## ## @multitable @columnfractions 0.28 0.72 ## @headitem Name @tab Value ## ## @item @qcode{'Learner'} @tab @qcode{'svm'}, the default, or ## @qcode{'logistic'}. ## ## @item @qcode{'NumExpansionDimensions'} @tab @qcode{'auto'}, the ## default, or a positive integer. ## ## @item @qcode{'KernelScale'} @tab @qcode{1} by default, a positive ## scalar, or @qcode{'auto'}, which takes the median distance between the ## observations. ## ## @item @qcode{'Lambda'} @tab @qcode{'auto'}, the default, which is the ## reciprocal of the number of observations, or a nonnegative scalar. It ## cannot be given beside @qcode{'BoxConstraint'}. ## ## @item @qcode{'BoxConstraint'} @tab A positive scalar, @qcode{1} by ## default. It applies to a support vector machine alone. ## ## @item @qcode{'Standardize'} @tab Whether to centre and scale the ## predictors, false by default. ## ## @item @qcode{'BetaTolerance'} @tab Relative tolerance on the ## coefficients, @qcode{1e-4} by default. ## ## @item @qcode{'GradientTolerance'} @tab Absolute tolerance on the ## gradient's infinity norm, @qcode{1e-6} by default. ## ## @item @qcode{'IterationLimit'} @tab Largest number of iterations, ## @qcode{1000} by default. ## ## @item @qcode{'HessianHistorySize'} @tab Number of curvature pairs the ## solver keeps, @qcode{15} by default. ## ## @item @qcode{'BlockSize'} @tab Memory the expansion may occupy, in ## megabytes, @qcode{4e3} by default. ## ## @item @qcode{'ClassNames'} @tab The classes to keep, given in the type ## of @var{Y}. ## ## @item @qcode{'Cost'} @tab A square misclassification cost matrix. ## ## @item @qcode{'Prior'} @tab @qcode{'empirical'}, the default, ## @qcode{'uniform'}, a vector of probabilities, or a structure with ## @qcode{ClassNames} and @qcode{ClassProbs} fields. ## ## @item @qcode{'ScoreTransform'} @tab A transformation applied to the ## scores, named or given as a function handle. ## ## @item @qcode{'Weights'} @tab One nonnegative weight per observation. ## ## @item @qcode{'PredictorNames'} @tab One name per predictor. ## ## @item @qcode{'ResponseName'} @tab A name for the response. ## ## @item @qcode{'CategoricalPredictors'} @tab Indices of the categorical ## predictors. ## @end multitable ## ## The fit is always by limited-memory BFGS, the only solver MATLAB ## offers a kernel model, and always under a ridge penalty. ## ## @seealso{fitckernel, ClassificationLinear} ## @end deftypefn function this = ClassificationKernel (X, Y, varargin) if (nargin < 2) error ("ClassificationKernel: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationKernel: optional arguments must", ... " be given in Name-Value pairs.")); endif ## Defaults Learner = 'svm'; NumDimsIn = 'auto'; KernelScaleIn = 1; LambdaIn = 'auto'; BoxConstraint = 1; BoxGiven = false; LambdaGiven = false; Standardize = false; BetaTolerance = 1e-4; GradientTolerance = 1e-6; IterationLimit = 1000; HessianHistorySize = 15; BlockSize = 4e3; Verbose = 0; ClassNames = []; CostIn = []; Prior = []; ScoreTransform = []; Weights = []; PredictorNames = {}; ResponseName = 'Y'; CategoricalPredictors = []; while (numel (varargin) > 0) switch (lower (varargin{1})) case 'learner' Learner = varargin{2}; if (! (ischar (Learner) && any (strcmpi (Learner, {'svm', 'logistic'})))) error (strcat ("ClassificationKernel: 'Learner' must be", ... " either 'svm' or 'logistic'.")); endif Learner = lower (Learner); case 'numexpansiondimensions' NumDimsIn = varargin{2}; if (! ((ischar (NumDimsIn) && strcmpi (NumDimsIn, 'auto')) || (isnumeric (NumDimsIn) && isscalar (NumDimsIn) && isreal (NumDimsIn) && NumDimsIn > 0 && fix (NumDimsIn) == NumDimsIn))) error (strcat ("ClassificationKernel:", ... " 'NumExpansionDimensions' must be 'auto'", ... " or a positive integer scalar.")); endif case 'kernelscale' KernelScaleIn = varargin{2}; if (! ((ischar (KernelScaleIn) && strcmpi (KernelScaleIn, 'auto')) || (isnumeric (KernelScaleIn) && isscalar (KernelScaleIn) && isreal (KernelScaleIn) && KernelScaleIn > 0))) error (strcat ("ClassificationKernel: 'KernelScale' must", ... " be 'auto' or a positive scalar.")); endif case 'lambda' LambdaIn = varargin{2}; LambdaGiven = true; if (! ((ischar (LambdaIn) && strcmpi (LambdaIn, 'auto')) || (isnumeric (LambdaIn) && isscalar (LambdaIn) && isreal (LambdaIn) && LambdaIn >= 0 && isfinite (LambdaIn)))) error (strcat ("ClassificationKernel: 'Lambda' must be", ... " 'auto' or a nonnegative finite scalar.")); endif case 'boxconstraint' BoxConstraint = varargin{2}; BoxGiven = true; if (! (isnumeric (BoxConstraint) && isscalar (BoxConstraint) && isreal (BoxConstraint) && BoxConstraint > 0 && isfinite (BoxConstraint))) error (strcat ("ClassificationKernel: 'BoxConstraint' must", ... " be a positive finite scalar.")); endif case 'standardize' Standardize = varargin{2}; if (! (islogical (Standardize) || (isnumeric (Standardize) && isscalar (Standardize) && any (Standardize == [0, 1])))) error (strcat ("ClassificationKernel: 'Standardize' must", ... " be either true or false.")); endif Standardize = logical (Standardize); case 'betatolerance' BetaTolerance = varargin{2}; if (! (isnumeric (BetaTolerance) && isscalar (BetaTolerance) && isreal (BetaTolerance) && BetaTolerance >= 0)) error (strcat ("ClassificationKernel: 'BetaTolerance' must", ... " be a nonnegative scalar.")); endif case 'gradienttolerance' GradientTolerance = varargin{2}; if (! (isnumeric (GradientTolerance) && isscalar (GradientTolerance) && isreal (GradientTolerance) && GradientTolerance >= 0)) error (strcat ("ClassificationKernel: 'GradientTolerance'", ... " must be a nonnegative scalar.")); endif case 'iterationlimit' IterationLimit = varargin{2}; if (! (isnumeric (IterationLimit) && isscalar (IterationLimit) && isreal (IterationLimit) && IterationLimit > 0 && fix (IterationLimit) == IterationLimit)) error (strcat ("ClassificationKernel: 'IterationLimit'", ... " must be a positive integer scalar.")); endif case 'hessianhistorysize' HessianHistorySize = varargin{2}; if (! (isnumeric (HessianHistorySize) && isscalar (HessianHistorySize) && isreal (HessianHistorySize) && HessianHistorySize > 0 && fix (HessianHistorySize) == HessianHistorySize)) error (strcat ("ClassificationKernel:", ... " 'HessianHistorySize' must be a positive", ... " integer scalar.")); endif case 'blocksize' BlockSize = varargin{2}; if (! (isnumeric (BlockSize) && isscalar (BlockSize) && isreal (BlockSize) && BlockSize > 0)) error (strcat ("ClassificationKernel: 'BlockSize' must be", ... " a positive scalar.")); endif case 'verbose' Verbose = varargin{2}; if (! (isnumeric (Verbose) && isscalar (Verbose) && isreal (Verbose) && any (Verbose == [0, 1]))) error (strcat ("ClassificationKernel: 'Verbose' must be 0", ... " or 1.")); endif case 'classnames' ClassNames = varargin{2}; if (! (iscellstr (ClassNames) || isnumeric (ClassNames) || islogical (ClassNames) || ischar (ClassNames))) error (strcat ("ClassificationKernel: 'ClassNames' must be", ... " a cell array of character vectors, a", ... " logical vector, a numeric vector, or a", ... " character array.")); endif case 'cost' CostIn = varargin{2}; if (! (isnumeric (CostIn) && isreal (CostIn) && ismatrix (CostIn) && ndims (CostIn) == 2 && rows (CostIn) == columns (CostIn))) error (strcat ("ClassificationKernel: 'Cost' must be a", ... " square numeric matrix.")); endif case 'prior' Prior = varargin{2}; if (! ((ischar (Prior) && any (strcmpi (Prior, {'empirical', ... 'uniform'}))) || (isnumeric (Prior) && isreal (Prior) && isvector (Prior) && all (Prior >= 0)) || (isstruct (Prior) && isscalar (Prior)))) error (strcat ("ClassificationKernel: 'Prior' must be", ... " 'empirical', 'uniform', a vector of", ... " nonnegative values, or a structure.")); endif case 'scoretransform' ScoreTransform = varargin{2}; case 'weights' Weights = varargin{2}; if (! (isnumeric (Weights) && isreal (Weights) && isvector (Weights) && all (Weights >= 0))) error (strcat ("ClassificationKernel: 'Weights' must be a", ... " vector of nonnegative values.")); endif case 'predictornames' PredictorNames = varargin{2}; if (! (iscellstr (PredictorNames) && isvector (PredictorNames))) error (strcat ("ClassificationKernel: 'PredictorNames'", ... " must be a cell array of character", ... " vectors.")); endif case 'responsename' ResponseName = varargin{2}; if (! (ischar (ResponseName) && isrow (ResponseName))) error (strcat ("ClassificationKernel: 'ResponseName' must", ... " be a character vector.")); endif case 'categoricalpredictors' CategoricalPredictors = varargin{2}; if (! ((isnumeric (CategoricalPredictors) && isvector (CategoricalPredictors) && all (fix (CategoricalPredictors) == CategoricalPredictors) && all (CategoricalPredictors > 0)) || islogical (CategoricalPredictors) || isempty (CategoricalPredictors))) error (strcat ("ClassificationKernel:", ... " 'CategoricalPredictors' must be a vector", ... " of positive integers or a logical", ... " vector.")); endif otherwise error (strcat ("ClassificationKernel: invalid parameter name", ... " in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## Lambda and the box constraint are reciprocal, so naming both ## overdetermines the fit rather than describing it twice. if (LambdaGiven && BoxGiven) error (strcat ("ClassificationKernel: 'Lambda' and", ... " 'BoxConstraint' cannot be given together, one", ... " being the reciprocal of the other times the", ... " number of observations.")); endif if (BoxGiven && strcmp (Learner, 'logistic')) error (strcat ("ClassificationKernel: 'BoxConstraint' applies to", ... " a support vector machine only.")); endif ## Validate the data, resolve the classes, the prior, the cost and ## the observation weights. The four linear and kernel classifiers ## share that opening, so it lives in one place. F = classFrame (X, Y, ClassNames, Prior, CostIn, Weights, ... 'ClassificationKernel'); X = F.X; y = F.y; W = F.W; n = F.n; p = F.p; this.ClassNames = F.ClassNames; this.Prior = F.Prior; this.Cost = F.Cost; ## Standardize before anything is measured off the predictors, so the ## kernel scale and the expansion both see the same data predict will. if (Standardize) this.Mu = mean (X, 1); this.Sigma = std (X, 0, 1); this.Sigma(this.Sigma == 0) = 1; X = (X - this.Mu) ./ this.Sigma; endif ## Resolve the expansion if (ischar (NumDimsIn)) m = 2 .^ ceil (min (log2 (p) + 5, 15)); else m = NumDimsIn; endif if (ischar (KernelScaleIn)) sigma = autoKernelScale (X); else sigma = KernelScaleIn; endif ## Resolve Lambda and the box constraint from whichever was given if (BoxGiven) Lambda = 1 / (n * BoxConstraint); elseif (LambdaGiven && ! ischar (LambdaIn)) Lambda = LambdaIn; BoxConstraint = 1 / (n * Lambda); else Lambda = 1 / n; BoxConstraint = 1 / (n * Lambda); endif ## Draw the basis, map the data through it, and fit a linear model ## there. The basis is what makes the fit nonlinear; everything after ## it is the linear machinery. basis = kernelBasis (p, m, sigma); T = kernelExpand (X, basis); P = struct (); P.Learner = Learner; P.LossFunction = 'hinge'; if (strcmp (Learner, 'logistic')) P.LossFunction = 'logit'; endif P.Epsilon = []; P.Regularization = 'ridge'; P.Lambda = Lambda; P.Solver = 'lbfgs'; P.FitBias = true; P.PostFitBias = false; P.BetaTolerance = BetaTolerance; P.GradientTolerance = GradientTolerance; P.DeltaGradientTolerance = []; P.IterationLimit = IterationLimit; P.PassLimit = 1; P.BatchSize = 10; P.BatchLimit = []; P.LearnRate = 1; P.OptimizeLearnRate = true; P.TruncationPeriod = 10; P.NumCheckConvergence = 5; P.HessianHistorySize = HessianHistorySize; P.InitialBeta = zeros (m, 1); P.InitialBias = 0; [Beta, Bias, S] = linearSolve (T, y, W, P); ## Fill in the model this.BoxConstraint = BoxConstraint; this.PredictorNames = PredictorNames; if (isempty (this.PredictorNames)) this.PredictorNames = ... arrayfun (@(k) sprintf ("x%d", k), 1:p, ... 'UniformOutput', false); elseif (numel (this.PredictorNames) != p) error (strcat ("ClassificationKernel: 'PredictorNames' must have", ... " one name per predictor.")); endif this.ExpandedPredictorNames = this.PredictorNames; this.CategoricalPredictors = CategoricalPredictors; this.ResponseName = ResponseName; this.NumExpansionDimensions = m; this.FittedLoss = P.LossFunction; this.Lambda = Lambda; this.KernelScale = sigma; this.Learner = Learner; this.Basis_ = basis; this.Beta_ = Beta; this.Bias_ = Bias; this.NumPredictors_ = p; this.NumObservations_ = n; if (isempty (ScoreTransform)) if (strcmp (Learner, 'logistic')) ScoreTransform = 'logit'; else ScoreTransform = 'none'; endif endif this.ScoreTransform = ScoreTransform; this.ModelParameters = struct ('BetaTolerance', BetaTolerance, ... 'BlockSize', BlockSize, 'BoxConstraint', BoxConstraint, ... 'Epsilon', 'auto', 'NumExpansionDimensions', NumDimsIn, ... 'GradientTolerance', GradientTolerance, ... 'HessianHistorySize', HessianHistorySize, ... 'IterationLimit', IterationLimit, 'KernelScale', KernelScaleIn, ... 'Lambda', LambdaIn, 'Learner', Learner, ... 'LossFunction', P.LossFunction, 'Stream', [], ... 'VerbosityLevel', Verbose, 'StandardizeData', Standardize, ... 'Version', 1, 'Method', 'Kernel', 'Type', 'classification'); this.FitInfo_ = kernelFitInfo (S, P.LossFunction, Lambda, ... BetaTolerance, GradientTolerance); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {ClassificationKernel} {@var{labels} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {ClassificationKernel} {[@var{labels}, @var{scores}] =} predict (@var{obj}, @var{XC}) ## ## Classify new observations. ## ## @code{@var{labels} = predict (@var{obj}, @var{XC})} maps each row of ## @var{XC} through the model's own random basis and returns the class of ## largest score. ## ## @code{[@var{labels}, @var{scores}] = predict (@var{obj}, @var{XC})} ## also returns the @math{Nx2} scores, whose columns follow ## @qcode{ClassNames}, after @qcode{ScoreTransform} has been applied. ## ## @end deftypefn function [labels, scores] = predict (this, XC) if (nargin < 2) error ("ClassificationKernel.predict: too few input arguments."); endif if (isempty (XC)) error ("ClassificationKernel.predict: XC is empty."); endif if (! (isnumeric (XC) && isreal (XC) && ismatrix (XC))) error ("ClassificationKernel.predict: invalid values in XC."); endif if (columns (XC) != this.NumPredictors_) error (strcat ("ClassificationKernel.predict: XC must have the", ... " same number of predictors as the trained model.")); endif f = rawScore (this, XC); labels = labelsFromIndex (this.ClassNames, 1 + (f > 0)); if (nargout > 1) scores = this.STfun ([-f, f]); endif endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKernel} {@var{m} =} margin (@var{obj}, @var{X}, @var{Y}) ## ## Classification margin of each observation. ## ## @code{@var{m} = margin (@var{obj}, @var{X}, @var{Y})} returns the ## score of the true class less the score of the other one. A positive ## margin is a correct classification. ## ## @end deftypefn function m = margin (this, X, Y) if (nargin < 3) error ("ClassificationKernel.margin: too few input arguments."); endif [gY, errmsg] = labelIndices (this.ClassNames, Y); if (! isempty (errmsg)) error ("ClassificationKernel.margin: %s", errmsg); endif if (rows (X) != numel (gY)) error (strcat ("ClassificationKernel.margin: number of rows in X", ... " and Y must be equal.")); endif [~, s] = predict (this, X); n = rows (s); strue = s(sub2ind (size (s), (1:n)', gY)); sother = s(sub2ind (size (s), (1:n)', 3 - gY)); m = strue - sother; endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKernel} {@var{e} =} edge (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationKernel} {@var{e} =} edge (@dots{}, @qcode{'Weights'}, @var{W}) ## ## Weighted mean of the classification margins. ## ## The weights are normalized within each class to that class's prior ## before they are applied. ## ## @end deftypefn function e = edge (this, X, Y, varargin) if (nargin < 3) error ("ClassificationKernel.edge: too few input arguments."); endif W = edgeWeights (varargin, Y, this.ClassNames, this.Prior, ... 'ClassificationKernel', 'edge'); e = sum (W .* margin (this, X, Y)); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKernel} {@var{l} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationKernel} {@var{l} =} loss (@dots{}, @var{name}, @var{value}) ## ## Classification loss on new data. ## ## @code{@var{l} = loss (@var{obj}, @var{X}, @var{Y})} returns the ## misclassification rate. ## ## @code{@var{l} = loss (@dots{}, @var{name}, @var{value})} takes ## @qcode{'LossFun'}, one of @qcode{'binodeviance'}, ## @qcode{'classifcost'}, @qcode{'classiferror'}, @qcode{'exponential'}, ## @qcode{'hinge'}, @qcode{'logit'}, @qcode{'mincost'} and ## @qcode{'quadratic'}, and @qcode{'Weights'}. ## ## @end deftypefn function l = loss (this, X, Y, varargin) if (nargin < 3) error ("ClassificationKernel.loss: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationKernel.loss: optional arguments", ... " must be given in Name-Value pairs.")); endif LossFun = 'classiferror'; Weights = []; while (numel (varargin) > 0) switch (lower (varargin{1})) case 'lossfun' LossFun = varargin{2}; valid = {'binodeviance', 'classifcost', 'classiferror', ... 'exponential', 'hinge', 'logit', 'mincost', ... 'quadratic'}; if (! (ischar (LossFun) && any (strcmpi (LossFun, valid)))) error (strcat ("ClassificationKernel.loss: 'LossFun' must", ... " be 'binodeviance', 'classifcost',", ... " 'classiferror', 'exponential', 'hinge',", ... " 'logit', 'mincost', or 'quadratic'.")); endif LossFun = lower (LossFun); case 'weights' Weights = varargin{2}; if (! (isnumeric (Weights) && isreal (Weights) && isvector (Weights) && all (Weights >= 0))) error (strcat ("ClassificationKernel.loss: 'Weights' must", ... " be a vector of nonnegative values.")); endif otherwise error (strcat ("ClassificationKernel.loss: invalid parameter", ... " name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile [gY, errmsg] = labelIndices (this.ClassNames, Y); if (! isempty (errmsg)) error ("ClassificationKernel.loss: %s", errmsg); endif if (rows (X) != numel (gY)) error (strcat ("ClassificationKernel.loss: number of rows in X", ... " and Y must be equal.")); endif if (isempty (Weights)) w = ones (numel (gY), 1); else w = Weights(:); if (numel (w) != numel (gY)) error (strcat ("ClassificationKernel.loss: 'Weights' must have", ... " one element per observation.")); endif endif w = w / sum (w); [~, s] = predict (this, X); l = classificationLoss (LossFun, s, gY, w, this.Cost); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKernel} {@var{obj} =} resume (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationKernel} {@var{obj} =} resume (@dots{}, @var{name}, @var{value}) ## ## Continue fitting a kernel classifier. ## ## @code{@var{obj} = resume (@var{obj}, @var{X}, @var{Y})} restarts the ## optimization from the coefficients the model already carries, through ## the basis it already holds, and returns the model it reaches. It ## takes @qcode{'BetaTolerance'}, @qcode{'GradientTolerance'} and ## @qcode{'IterationLimit'}, each defaulting to what the model was ## fitted with, and @qcode{'Weights'}. ## ## @var{X} and @var{Y} must be the data the model was fitted to; the ## object keeps no copy of them, which is what makes it small. Neither ## does it keep the observation weights, so a model fitted with ## @qcode{'Weights'} must be given them again here or it will resume ## against uniform ones. MATLAB behaves the same way: measured on ## R2024a, resuming a weighted fit without passing the weights back ## reaches the objective of the @emph{unweighted} fit. ## ## @end deftypefn function this = resume (this, X, Y, varargin) if (nargin < 3) error ("ClassificationKernel.resume: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationKernel.resume: optional arguments", ... " must be given in Name-Value pairs.")); endif BetaTolerance = this.ModelParameters.BetaTolerance; GradientTolerance = this.ModelParameters.GradientTolerance; IterationLimit = this.ModelParameters.IterationLimit; Weights = []; while (numel (varargin) > 0) switch (lower (varargin{1})) case 'weights' Weights = varargin{2}; if (! (isnumeric (Weights) && isreal (Weights) && isvector (Weights) && all (Weights >= 0))) error (strcat ("ClassificationKernel.resume: 'Weights'", ... " must be a vector of nonnegative values.")); endif case 'betatolerance' BetaTolerance = varargin{2}; if (! (isnumeric (BetaTolerance) && isscalar (BetaTolerance) && isreal (BetaTolerance) && BetaTolerance >= 0)) error (strcat ("ClassificationKernel.resume:", ... " 'BetaTolerance' must be a nonnegative", ... " scalar.")); endif case 'gradienttolerance' GradientTolerance = varargin{2}; if (! (isnumeric (GradientTolerance) && isscalar (GradientTolerance) && isreal (GradientTolerance) && GradientTolerance >= 0)) error (strcat ("ClassificationKernel.resume:", ... " 'GradientTolerance' must be a", ... " nonnegative scalar.")); endif case 'iterationlimit' IterationLimit = varargin{2}; if (! (isnumeric (IterationLimit) && isscalar (IterationLimit) && isreal (IterationLimit) && IterationLimit > 0 && fix (IterationLimit) == IterationLimit)) error (strcat ("ClassificationKernel.resume:", ... " 'IterationLimit' must be a positive", ... " integer scalar.")); endif otherwise error (strcat ("ClassificationKernel.resume: invalid", ... " parameter name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile [T, y, W] = resumeData (this, X, Y, Weights, 'resume'); P = resumeOptions (this, BetaTolerance, GradientTolerance, ... IterationLimit); P.InitialBeta = this.Beta_; P.InitialBias = this.Bias_; [Beta, Bias, S] = linearSolve (T, y, W, P); this.Beta_ = Beta; this.Bias_ = Bias; this.ModelParameters.BetaTolerance = BetaTolerance; this.ModelParameters.GradientTolerance = GradientTolerance; this.ModelParameters.IterationLimit = IterationLimit; this.FitInfo_ = kernelFitInfo (S, this.FittedLoss, this.Lambda, ... BetaTolerance, GradientTolerance); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationKernel} {} savemodel (@var{obj}, @var{filename}) ## ## Save a kernel classifier to a file. ## ## @code{savemodel (@var{obj}, @var{filename})} saves the model ## @var{obj} into @var{filename} in a form @code{loadmodel} can read ## back, the random basis included. ## ## @end deftypefn function savemodel (obj, fname) classdef_name = 'ClassificationKernel'; BoxConstraint = obj.BoxConstraint; ClassNames = obj.ClassNames; Prior = obj.Prior; Cost = obj.Cost; ScoreTransform = obj.ScoreTransform; PredictorNames = obj.PredictorNames; CategoricalPredictors = obj.CategoricalPredictors; ResponseName = obj.ResponseName; ExpandedPredictorNames = obj.ExpandedPredictorNames; NumExpansionDimensions = obj.NumExpansionDimensions; FittedLoss = obj.FittedLoss; Lambda = obj.Lambda; ModelParameters = obj.ModelParameters; Regularization = obj.Regularization; KernelScale = obj.KernelScale; Learner = obj.Learner; Mu = obj.Mu; Sigma = obj.Sigma; Basis_ = obj.Basis_; Beta_ = obj.Beta_; Bias_ = obj.Bias_; NumPredictors_ = obj.NumPredictors_; NumObservations_ = obj.NumObservations_; save ('-binary', fname, 'classdef_name', 'BoxConstraint', ... 'ClassNames', 'Prior', 'Cost', 'ScoreTransform', ... 'PredictorNames', 'CategoricalPredictors', 'ResponseName', ... 'ExpandedPredictorNames', 'NumExpansionDimensions', ... 'FittedLoss', 'Lambda', 'ModelParameters', 'Regularization', ... 'KernelScale', 'Learner', 'Mu', 'Sigma', 'Basis_', 'Beta_', ... 'Bias_', 'NumPredictors_', 'NumObservations_'); endfunction endmethods methods (Access = public, Hidden) function display (this) in_name = inputname (1); if (! isempty (in_name)) printf ('%s =\n', in_name); endif disp (this); endfunction function disp (this) printf ("\n ClassificationKernel\n\n"); printf ("%+26s: '%s'\n", 'ResponseName', this.ResponseName); printf ("%+26s: %s\n", 'ClassNames', ... classNameListing (this.ClassNames)); printf ("%+26s: '%s'\n", 'Learner', this.Learner); printf ("%+26s: %d\n", 'NumExpansionDimensions', ... this.NumExpansionDimensions); printf ("%+26s: %g\n", 'KernelScale', this.KernelScale); printf ("%+26s: %g\n", 'Lambda', this.Lambda); printf ("%+26s: %g\n", 'BoxConstraint', this.BoxConstraint); printf ("\n"); endfunction ## What the fit reported, which fitckernel returns as its second output. function S = fitInfo_ (this) S = this.FitInfo_; endfunction ## Custom setter, so that assigning a name or a handle updates both the ## text the property reports and the callable predict uses. function this = set.ScoreTransform (this, val) [this.STfun, this.ScoreTransform] = ... parseScoreTransform (val, 'ClassificationKernel'); endfunction endmethods methods (Access = private) ## The raw model value of each row of XC, standardized and expanded ## through the model's own basis first. function f = rawScore (this, XC) if (! isempty (this.Mu)) XC = (XC - this.Mu) ./ this.Sigma; endif f = kernelExpand (XC, this.Basis_) * this.Beta_ + this.Bias_; endfunction ## The expanded predictors, the signed response and the weights of a ## resume call, validated the way the constructor validated them. function [T, y, W] = resumeData (this, X, Y, Weights, caller) if (! (isnumeric (X) && isreal (X) && ismatrix (X))) error ("ClassificationKernel.%s: invalid values in X.", caller); endif if (columns (X) != this.NumPredictors_) error (strcat ("ClassificationKernel.%s: X must have the same", ... " number of predictors as the trained model."), ... caller); endif [gY, errmsg] = labelIndices (this.ClassNames, Y); if (! isempty (errmsg)) error ("ClassificationKernel.%s: %s", caller, errmsg); endif if (rows (X) != numel (gY)) error (strcat ("ClassificationKernel.%s: number of rows in X and", ... " Y must be equal."), caller); endif y = -ones (numel (gY), 1); y(gY == 2) = 1; if (isempty (Weights)) Weights = ones (numel (gY), 1); else Weights = Weights(:); if (numel (Weights) != numel (gY)) error (strcat ("ClassificationKernel.%s: 'Weights' must have", ... " one element per observation."), caller); endif endif adjPrior = this.Prior .* sum (this.Cost, 2)'; adjPrior = adjPrior / sum (adjPrior); W = zeros (numel (gY), 1); for k = 1:numel (this.Prior) idx = (gY == k); tot = sum (Weights(idx)); if (tot > 0) W(idx) = Weights(idx) / tot * adjPrior(k); endif endfor W = W / sum (W); if (! isempty (this.Mu)) X = (X - this.Mu) ./ this.Sigma; endif T = kernelExpand (X, this.Basis_); endfunction ## The option structure linearSolve takes, filled from the model. function P = resumeOptions (this, BetaTol, GradTol, IterLimit) P = struct (); P.Learner = this.Learner; P.LossFunction = this.FittedLoss; P.Epsilon = []; P.Regularization = 'ridge'; P.Lambda = this.Lambda; P.Solver = 'lbfgs'; P.FitBias = true; P.PostFitBias = false; P.BetaTolerance = BetaTol; P.GradientTolerance = GradTol; P.DeltaGradientTolerance = []; P.IterationLimit = IterLimit; P.PassLimit = 1; P.BatchSize = 10; P.BatchLimit = []; P.LearnRate = 1; P.OptimizeLearnRate = true; P.TruncationPeriod = 10; P.NumCheckConvergence = 5; P.HessianHistorySize = this.ModelParameters.HessianHistorySize; endfunction endmethods methods (Static, Hidden) function mdl = load_model (filename, data) mdl = ClassificationKernel (zeros (2, 1), [0; 1]); fields = fieldnames (data); for k = 1:numel (fields) mdl.(fields{k}) = data.(fields{k}); endfor endfunction endmethods endclassdef %!demo %! ## Separate the two overlapping iris species through a randomized %! ## Gaussian kernel, and read the model the fit produced. %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationKernel (X, Y) %! predict (Mdl, X([1, 51],:)) %!demo %! ## The box constraint and the regularization strength are two names for %! ## the same quantity: setting either fixes the other. %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationKernel (X, Y, 'BoxConstraint', 4); %! Mdl.BoxConstraint %! Mdl.Lambda %!test %! ## The model reports the surface MATLAB reports %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationKernel (X, Y); %! assert_equal (class (Mdl), 'ClassificationKernel'); %! assert_equal (Mdl.ClassNames, {'versicolor'; 'virginica'}); %! assert_equal (Mdl.Learner, 'svm'); %! assert_equal (Mdl.FittedLoss, 'hinge'); %! assert_equal (Mdl.Regularization, 'ridge (L2)'); %! assert_equal (Mdl.ScoreTransform, 'none'); %! assert_equal (Mdl.KernelScale, 1); %! assert_equal (Mdl.BoxConstraint, 1); %! assert_equal (Mdl.Lambda, 0.01); %! assert_equal (Mdl.NumExpansionDimensions, 128); %! assert_equal (Mdl.Mu, []); %! assert_equal (Mdl.Sigma, []); %! assert_equal (Mdl.PredictorNames, {'x1', 'x2', 'x3', 'x4'}); %!test %! ## The properties are the ones MATLAB lists, in its order %! load fisheriris %! Mdl = ClassificationKernel (meas(51:end,:), species(51:end)); %! assert_equal (sort (properties (Mdl)), ... %! sort ({'BoxConstraint'; 'ClassNames'; 'Prior'; 'Cost'; ... %! 'ScoreTransform'; 'PredictorNames'; ... %! 'CategoricalPredictors'; 'ResponseName'; ... %! 'ExpandedPredictorNames'; 'NumExpansionDimensions'; ... %! 'FittedLoss'; 'Lambda'; 'ModelParameters'; ... %! 'Regularization'; 'KernelScale'; 'Learner'; 'Mu'; ... %! 'Sigma'})); %!test %! ## The default expansion is MATLAB's, two to the power of five more than %! ## the base two logarithm of the predictors, capped at fifteen %! X = randn (40, 2); %! Y = [ones(20, 1); 2 * ones(20, 1)]; %! assert_equal (ClassificationKernel (X, Y).NumExpansionDimensions, 64); %! assert_equal (ClassificationKernel (randn (40, 32), Y) ... %! .NumExpansionDimensions, 1024); %!test %! ## Lambda and the box constraint are reciprocal through the number of %! ## observations, and either one may be the one that is given %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mb = ClassificationKernel (X, Y, 'BoxConstraint', 3); %! assert_equal (Mb.Lambda, 1 / 300, 1e-15); %! assert_equal (Mb.BoxConstraint, 3); %! Ml = ClassificationKernel (X, Y, 'Lambda', 0.05); %! assert_equal (Ml.Lambda, 0.05); %! assert_equal (Ml.BoxConstraint, 0.2, 1e-15); %!test %! ## Standardizing records the means and deviations MATLAB records %! load fisheriris %! Mdl = ClassificationKernel (meas(51:end,:), species(51:end), ... %! 'Standardize', true); %! assert_equal (Mdl.Mu, [6.262, 2.872, 4.906, 1.676], 1e-12); %! assert_equal (Mdl.Sigma, [0.662834440074967, 0.332751006494695, ... %! 0.82557846264289, 0.424768504986284], 1e-12); %!test %! ## A logistic learner fits the deviance and reports posteriors %! load fisheriris %! X = meas(51:end,:); %! Mdl = ClassificationKernel (X, species(51:end), 'Learner', 'logistic'); %! assert_equal (Mdl.FittedLoss, 'logit'); %! assert_equal (Mdl.ScoreTransform, 'logit'); %! [~, score] = predict (Mdl, X(1:5,:)); %! assert_equal (sum (score, 2), ones (5, 1), 1e-12); %!test %! ## A support vector machine leaves the scores untransformed, so they are %! ## a value and its negative %! load fisheriris %! X = meas(51:end,:); %! Mdl = ClassificationKernel (X, species(51:end)); %! [~, score] = predict (Mdl, X(1:5,:)); %! assert_equal (score(:,1), -score(:,2), 1e-12); %!test %! ## The fit separates the two species it was given, whatever basis it drew %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationKernel (X, Y); %! assert_equal (loss (Mdl, X, Y) < 0.15, true); %! assert_equal (edge (Mdl, X, Y) > 0, true); %!test %! ## margin is the true class score less the other, and the labels follow %! ## the sign of the raw score %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationKernel (X, Y); %! m = margin (Mdl, X, Y); %! assert_equal (size (m), [100, 1]); %! assert_equal (mean (m > 0), 1 - loss (Mdl, X, Y), 1e-12); %!test %! ## Predicting through the model's own basis is what makes it a model at %! ## all: the same rows give the same scores every time it is asked %! load fisheriris %! X = meas(51:end,:); %! Mdl = ClassificationKernel (X, species(51:end)); %! [~, s1] = predict (Mdl, X(1:10,:)); %! [~, s2] = predict (Mdl, X(1:10,:)); %! assert_equal (s1, s2); %!test %! ## A wider kernel gives a smoother rule, so it fits the training data %! ## less closely than a narrow one does %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mnarrow = ClassificationKernel (X, Y, 'KernelScale', 0.5, ... %! 'Lambda', 1e-4); %! Mwide = ClassificationKernel (X, Y, 'KernelScale', 20, 'Lambda', 1e-4); %! assert_equal (loss (Mnarrow, X, Y) <= loss (Mwide, X, Y), true); %!test %! ## resume continues from the coefficients the model already holds, so it %! ## cannot leave the objective higher than it found it %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationKernel (X, Y, 'IterationLimit', 3); %! before = Mdl.FitInfo_.ObjectiveValue; %! Mdl = resume (Mdl, X, Y, 'IterationLimit', 500); %! assert_equal (class (Mdl), 'ClassificationKernel'); %! assert_equal (Mdl.FitInfo_.ObjectiveValue <= before, true); %! assert_equal (Mdl.ModelParameters.IterationLimit, 500); %!test %! ## The fit information is MATLAB's kernel structure, not its linear one %! load fisheriris %! Mdl = ClassificationKernel (meas(51:end,:), species(51:end)); %! F = Mdl.FitInfo_; %! assert_equal (fieldnames (F), {'Solver'; 'LossFunction'; 'Lambda'; ... %! 'BetaTolerance'; 'GradientTolerance'; ... %! 'ObjectiveValue'; 'GradientMagnitude'; ... %! 'RelativeChangeInBeta'; 'FitTime'; ... %! 'History'}); %! assert_equal (F.Solver, 'LBFGS-fast'); %! assert_equal (F.LossFunction, 'hinge'); %! assert_equal (F.Lambda, 0.01); %!test %! ## A cost matrix reaches the fit through the prior, as it does for the %! ## linear classifier %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationKernel (X, Y, 'Cost', [0, 4; 1, 0]); %! assert_equal (Mdl.Cost, [0, 4; 1, 0]); %! assert_equal (Mdl.Prior, [0.5, 0.5]); %!test %! ## A saved model reads back as the same model, the random basis included %! load fisheriris %! X = meas(51:end,:); %! Mdl = ClassificationKernel (X, species(51:end)); %! fname = tempname (); %! savemodel (Mdl, fname); %! Mnew = loadmodel (fname); %! delete (fname); %! assert_equal (class (Mnew), 'ClassificationKernel'); %! [~, s1] = predict (Mdl, X(1:5,:)); %! [~, s2] = predict (Mnew, X(1:5,:)); %! assert_equal (s1, s2); %!test %! ## The labels come back in the type the response was given in, a %! ## character matrix included %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mchar = ClassificationKernel (X, char (Y)); %! assert_equal (size (Mchar.ClassNames), [2, 10]); %! assert_equal (size (predict (Mchar, X(1:3,:))), [3, 10]); %! Mnum = ClassificationKernel (X, double (strcmp (Y, 'virginica'))); %! assert_equal (Mnum.ClassNames, [0; 1]); %! assert_equal (class (predict (Mnum, X(1:3,:))), 'double'); %!test %! ## A character matrix response names one class per row, and every method %! ## answers the same through it as through the equivalent cell array %! load fisheriris %! X = meas(51:end,:); %! Yc = species(51:end); %! Ym = char (Yc); %! rand ('seed', 11); randn ('seed', 11); %! Mc = ClassificationKernel (X, Yc); %! rand ('seed', 11); randn ('seed', 11); %! Mm = ClassificationKernel (X, Ym); %! assert_equal (cellstr (Mm.ClassNames), Mc.ClassNames); %! assert_equal (cellstr (predict (Mm, X)), predict (Mc, X)); %! assert_equal (margin (Mm, X, Ym), margin (Mc, X, Yc)); %! assert_equal (edge (Mm, X, Ym), edge (Mc, X, Yc)); %! assert_equal (loss (Mm, X, Ym), loss (Mc, X, Yc)); %!test %! ## 'ClassNames' selects a subset when it is given as a character matrix %! load fisheriris %! Mdl = ClassificationKernel (meas, char (species), 'ClassNames', ... %! char ({'versicolor', 'virginica'})); %! assert_equal (size (Mdl.ClassNames), [2, 10]); %! assert_equal (cellstr (Mdl.ClassNames), {'versicolor'; 'virginica'}); %!test %! ## resume keeps no observation weights, because the model keeps no data: %! ## a weighted fit resumed without them continues against uniform ones, %! ## and passing them back restores the weighted fit. MATLAB behaves the %! ## same way, measured on R2024a. %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! w = (1:100)'; %! rand ('seed', 4); randn ('seed', 4); %! Mdl = ClassificationKernel (X, Y, 'IterationLimit', 5, 'Weights', w); %! kept = resume (Mdl, X, Y, 'IterationLimit', 400, 'Weights', w); %! lost = resume (Mdl, X, Y, 'IterationLimit', 400); %! assert_equal (kept.FitInfo_.ObjectiveValue ... %! != lost.FitInfo_.ObjectiveValue, true); %! rand ('seed', 4); randn ('seed', 4); %! direct = ClassificationKernel (X, Y, 'IterationLimit', 405, 'Weights', w); %! assert_equal (kept.FitInfo_.ObjectiveValue, ... %! direct.FitInfo_.ObjectiveValue, 1e-3); %!test %! ## A character matrix response survives a round trip through savemodel %! ## and loadmodel, the random basis with it %! load fisheriris %! X = meas(51:end,:); %! Ym = char (species(51:end)); %! Mdl = ClassificationKernel (X, Ym); %! fname = tempname (); %! savemodel (Mdl, fname); %! Mnew = loadmodel (fname); %! delete (fname); %! assert_equal (Mnew.ClassNames, Mdl.ClassNames); %! assert_equal (predict (Mnew, X(1:5,:)), predict (Mdl, X(1:5,:))); ## Test input validation %!error ... %! ClassificationKernel (ones (5, 2)) %!error ... %! ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Learner') %!error ... %! ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Learner', ... %! 'tree') %!error ... %! ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'NumExpansionDimensions', 0) %!error ... %! ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'KernelScale', -1) %!error ... %! ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Lambda', -1) %!error ... %! ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'BoxConstraint', 0) %!error ... %! ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Lambda', ... %! 0.1, 'BoxConstraint', 2) %!error ... %! ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Learner', ... %! 'logistic', 'BoxConstraint', 2) %!error ... %! ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'Standardize', 'yes') %!error ... %! ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'IterationLimit', -5) %!error ... %! ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Verbose', 2) %!error ... %! ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Nonsense', 1) %!error ... %! ClassificationKernel ({1, 2; 3, 4}, [1; 2]) %!error ClassificationKernel ([], []) %!error ... %! ClassificationKernel (ones (10, 2), [1; 2]) %!error ... %! ClassificationKernel (ones (9, 2), [1; 1; 1; 2; 2; 2; 3; 3; 3]) %!error ... %! ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Cost', ... %! ones (3)) %!error ... %! predict (ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)]), ... %! ones (3, 5)) %!error ... %! predict (ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)]), []) %!error ... %! margin (ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)]), ... %! ones (3, 2)) %!error ... %! loss (ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)]), ... %! ones (10, 2), [ones(5,1); 2*ones(5,1)], 'LossFun', 'mse') %!error ... %! resume (ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)]), ... %! ones (10, 2)) %!error ... %! resume (ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)]), ... %! ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'IterationLimit', 0) %!error ... %! resume (ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)]), ... %! ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Nonsense', 1) %!error ... %! resume (ClassificationKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)]), ... %! ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Weights', -1) ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = fitckernel (meas, strcmp (species, 'setosa')); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = fitckernel (meas, strcmp (species, 'setosa')); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/ClassificationLinear.m000066400000000000000000002352251524624707500265510ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftp {statistics} ClassificationLinear ## ## Linear binary classifier for high dimensional data. ## ## A @qcode{ClassificationLinear} object fits a linear model, ## @code{@var{X} * Beta + Bias}, to a two class problem by minimizing a ## regularized average loss. The loss is the hinge loss for a support vector ## machine and the deviance for a logistic regression, and the penalty is ## either a ridge or a lasso one. ## ## Unlike the other classifiers of this package the object holds no copy of ## the training data: the coefficients, the intercept and the fitting options ## are the whole model. That is what makes it suited to data with more ## predictors than an in memory kernel matrix could carry, and it is why the ## class has no @code{compact} method and no resubstitution methods. ## ## A vector of regularization strengths fits one model per value in a single ## object. @qcode{Beta} is then a @math{PxL} matrix and @qcode{Bias} a ## @math{1xL} row, every method returns one column per strength, and ## @code{selectModels} narrows the object down to the strengths worth ## keeping. ## ## Create a @qcode{ClassificationLinear} object with @code{fitclinear}. ## ## @seealso{fitclinear, ClassificationKernel, ClassificationSVM} ## @end deftp classdef ClassificationLinear properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} ClassNames ## ## Names of the two classes ## ## A column of the same type as the response supplied to the constructor: ## a cell array of character vectors, a numeric vector, a logical vector ## or a character matrix. The second of the two is the positive class, ## the one a positive score belongs to. This property is read-only. ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} Prior ## ## Prior probability of each class ## ## A numeric row vector with one element per class, in the order of ## @qcode{ClassNames} and summing to one. It defaults to the class ## frequencies of the training data. This property is read-only. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} Cost ## ## Cost of misclassifying an observation ## ## A square numeric matrix with one row and one column per class, whose ## @math{(i,j)} element is the cost of classifying an observation of ## class @math{i} into class @math{j}. It defaults to one everywhere ## except the diagonal, which is zero. This property is read-only: ## MATLAB refuses an assignment into it on this class, as it does on the ## support vector machine, so a cost matrix is given to the constructor ## instead. ## ## The cost matrix takes no part in the fit and none in @code{predict}, ## which returns the class of largest score. It is read by the ## @qcode{'mincost'} and @qcode{'classifcost'} losses alone. ## ## @end deftp Cost = []; endproperties properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} ScoreTransform ## ## Transformation applied to the predicted scores ## ## A character vector naming a transformation, or the text of the ## function handle that was supplied. Assigning to it accepts either. ## It defaults to @qcode{'logit'} for a logistic learner, which turns the ## scores into posterior probabilities, and to @qcode{'none'} for a ## support vector machine. ## ## @end deftp ScoreTransform = 'none'; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} PredictorNames ## ## Names of the predictors ## ## A cell array of character vectors with one name per column of the ## training data, defaulting to @qcode{'x1'}, @qcode{'x2'} and so on. ## This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A row vector of column indices, empty when every predictor is ## numeric. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} ResponseName ## ## Name of the response ## ## A character vector, defaulting to @qcode{'Y'}. This property is ## read-only. ## ## @end deftp ResponseName = 'Y'; ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} ExpandedPredictorNames ## ## Names of the predictors as the fit saw them ## ## A cell array of character vectors. It equals @qcode{PredictorNames} ## unless categorical predictors were expanded into indicator variables. ## This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} Learner ## ## Linear classification model that was fitted ## ## Either @qcode{'svm'} or @qcode{'logistic'}. This property is ## read-only. ## ## @end deftp Learner = 'svm'; ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} Beta ## ## Fitted linear coefficients ## ## A @math{Px1} column, or a @math{PxL} matrix with one column per ## regularization strength when @qcode{Lambda} holds more than one. This ## property is read-only. ## ## @end deftp Beta = []; ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} Bias ## ## Fitted intercept ## ## A scalar, or a @math{1xL} row with one element per regularization ## strength. It is zero throughout when the model was fitted with ## @qcode{'FitBias'} set to false. This property is read-only. ## ## @end deftp Bias = []; ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} FittedLoss ## ## Loss function the fit minimized ## ## @qcode{'hinge'} for a support vector machine and @qcode{'logit'} for a ## logistic regression. This is the loss of the objective, which is not ## the loss @code{loss} reports unless it is asked for. This property is ## read-only. ## ## @end deftp FittedLoss = 'hinge'; ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} Lambda ## ## Regularization strength ## ## A nonnegative scalar, or a @math{1xL} row of them in ascending order. ## It defaults to the reciprocal of the number of observations used to ## train the model. This property is read-only. ## ## @end deftp Lambda = []; ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} ModelParameters ## ## Fitting options, as they were given ## ## A structure holding every parameter of the fit, including the ones ## that a different solver would have used and the @qcode{'auto'} values ## before they were resolved. This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {ClassificationLinear} {property} Regularization ## ## Penalty on the coefficients ## ## @qcode{'ridge (L2)'} or @qcode{'lasso (L1)'}. This property is ## read-only. ## ## @end deftp Regularization = 'ridge (L2)'; endproperties properties (GetAccess = public, SetAccess = protected, Hidden) ## The callable behind ScoreTransform. The public property is the text ## MATLAB stores; this is what predict actually applies. STfun = @(s) s; ## Number of predictors the fit saw, which the class does not report ## because MATLAB does not, but predict needs to validate its input. NumPredictors_ = []; ## What the fit reported, so that fitclinear can hand it back as its ## second output. MATLAB returns it from the fitting function rather ## than storing it on the model, and a constructor cannot have a second ## output argument. FitInfo_ = []; endproperties methods (Access = public) ## -*- texinfo -*- ## @deftypefn {ClassificationLinear} {@var{obj} =} ClassificationLinear (@var{X}, @var{Y}) ## @deftypefnx {ClassificationLinear} {@var{obj} =} ClassificationLinear (@dots{}, @var{name}, @var{value}) ## ## Fit a linear binary classifier. ## ## @code{@var{obj} = ClassificationLinear (@var{X}, @var{Y})} fits a ## linear support vector machine to the @math{NxP} predictor matrix ## @var{X} and the @math{Nx1} response @var{Y}, which must name exactly ## two classes. ## ## @code{@var{obj} = ClassificationLinear (@dots{}, @var{name}, ## @var{value})} takes the following @qcode{Name-Value} pairs. ## ## @multitable @columnfractions 0.28 0.72 ## @headitem Name @tab Value ## ## @item @qcode{'Learner'} @tab @qcode{'svm'}, the default, or ## @qcode{'logistic'}. The first minimizes the hinge loss and the second ## the deviance. ## ## @item @qcode{'Regularization'} @tab @qcode{'ridge'} or ## @qcode{'lasso'}. It defaults to @qcode{'lasso'} when the solver is ## @qcode{'sparsa'} and to @qcode{'ridge'} otherwise. ## ## @item @qcode{'Lambda'} @tab @qcode{'auto'}, the default, which is the ## reciprocal of the number of observations, or a nonnegative scalar, or ## a vector of them. A vector fits one model per value. ## ## @item @qcode{'Solver'} @tab One of @qcode{'sgd'}, @qcode{'asgd'}, ## @qcode{'dual'}, @qcode{'bfgs'}, @qcode{'lbfgs'} and @qcode{'sparsa'}, ## or a cell array of them applied in turn, each warm starting the next. ## The default depends on the data and the penalty, as described below. ## ## @item @qcode{'Beta'} @tab Initial coefficients, a @math{Px1} column or ## a @math{PxL} matrix. It defaults to zeros. ## ## @item @qcode{'Bias'} @tab Initial intercept, a scalar or a @math{1xL} ## row. It defaults to the weighted average of the class labels for a ## logistic learner and to zero for a support vector machine. ## ## @item @qcode{'FitBias'} @tab Whether to fit an intercept at all, true ## by default. ## ## @item @qcode{'PostFitBias'} @tab Whether to refit the intercept once ## the coefficients are settled, false by default. ## ## @item @qcode{'ObservationsIn'} @tab @qcode{'rows'}, the default, or ## @qcode{'columns'}, which transposes @var{X} before fitting. ## ## @item @qcode{'BetaTolerance'} @tab Relative tolerance on the ## coefficients, @qcode{1e-4} by default. ## ## @item @qcode{'GradientTolerance'} @tab Absolute tolerance on the ## gradient's infinity norm, @qcode{1e-6} by default. ## ## @item @qcode{'DeltaGradientTolerance'} @tab Tolerance on the ## complementarity gap of the @qcode{'dual'} solver, @qcode{1} by ## default for a hinge loss. MathWorks documents @qcode{0.1}, which is ## the default of the @emph{regression} counterpart; R2024a and R2026a ## both report @qcode{1} here. ## ## @item @qcode{'IterationLimit'} @tab Largest number of iterations, ## @qcode{1000} by default. ## ## @item @qcode{'PassLimit'} @tab Largest number of passes over the data ## for the stochastic solvers, @qcode{1} by default, and @qcode{10} for ## @qcode{'dual'}. ## ## @item @qcode{'BatchSize'} @tab Mini-batch size of the stochastic ## solvers, @qcode{10} by default. ## ## @item @qcode{'BatchLimit'} @tab Largest number of mini-batches. ## ## @item @qcode{'LearnRate'} @tab Step size of the stochastic solvers. ## ## @item @qcode{'OptimizeLearnRate'} @tab Whether to halve the step size ## when the objective rises, true by default. ## ## @item @qcode{'TruncationPeriod'} @tab Number of mini-batches between ## soft thresholdings under a lasso penalty, @qcode{10} by default. ## ## @item @qcode{'NumCheckConvergence'} @tab Number of passes between ## convergence checks of the @qcode{'dual'} solver, @qcode{2} by ## default. MathWorks documents @qcode{5}; R2024a and R2026a both ## report @qcode{2}, so the documentation is stale rather than the ## releases being inconsistent. ## ## @item @qcode{'HessianHistorySize'} @tab Number of curvature pairs the ## quasi-Newton solvers keep, @qcode{15} by default. ## ## @item @qcode{'ClassNames'} @tab The classes to keep, given in the type ## of @var{Y}. Observations of any other class are dropped. ## ## @item @qcode{'Cost'} @tab A square misclassification cost matrix. ## ## @item @qcode{'Prior'} @tab @qcode{'empirical'}, the default, ## @qcode{'uniform'}, a vector of probabilities, or a structure with ## @qcode{ClassNames} and @qcode{ClassProbs} fields. ## ## @item @qcode{'ScoreTransform'} @tab A transformation applied to the ## scores, named or given as a function handle. ## ## @item @qcode{'Weights'} @tab One nonnegative weight per observation. ## ## @item @qcode{'PredictorNames'} @tab One name per predictor. ## ## @item @qcode{'ResponseName'} @tab A name for the response. ## ## @item @qcode{'CategoricalPredictors'} @tab Indices of the categorical ## predictors. ## @end multitable ## ## The default solver is @qcode{'sparsa'} under a lasso penalty. Under a ## ridge penalty it is @qcode{'bfgs'} when there are no more than 100 ## predictors, and beyond that @qcode{'dual'} for a support vector ## machine and @qcode{'sgd'} for a logistic regression. ## ## @seealso{fitclinear, ClassificationKernel} ## @end deftypefn function this = ClassificationLinear (X, Y, varargin) ## Check for sufficient number of input arguments if (nargin < 2) error ("ClassificationLinear: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationLinear: optional arguments must", ... " be given in Name-Value pairs.")); endif ## Defaults, before the optional arguments are parsed. Anything left ## empty here is resolved once the data is known. Learner = 'svm'; Regularization = []; Lambda = 'auto'; Solver = []; BetaIn = []; BiasIn = []; FitBias = true; PostFitBias = false; ObservationsIn = 'rows'; BetaTolerance = 1e-4; GradientTolerance = 1e-6; DeltaGradientTolerance = 1; IterationLimit = 1000; PassLimit = []; BatchSize = 10; BatchLimit = []; LearnRate = []; OptimizeLearnRate = true; TruncationPeriod = 10; NumCheckConvergence = 2; HessianHistorySize = 15; Verbose = 0; ClassNames = []; CostIn = []; Prior = []; ScoreTransform = []; Weights = []; PredictorNames = {}; ResponseName = 'Y'; CategoricalPredictors = []; ## Parse optional parameters while (numel (varargin) > 0) switch (lower (varargin{1})) case 'learner' Learner = varargin{2}; if (! (ischar (Learner) && any (strcmpi (Learner, {'svm', 'logistic'})))) error (strcat ("ClassificationLinear: 'Learner' must be", ... " either 'svm' or 'logistic'.")); endif Learner = lower (Learner); case 'regularization' Regularization = varargin{2}; if (! (ischar (Regularization) && any (strcmpi (Regularization, {'ridge', 'lasso'})))) error (strcat ("ClassificationLinear: 'Regularization'", ... " must be either 'ridge' or 'lasso'.")); endif Regularization = lower (Regularization); case 'lambda' Lambda = varargin{2}; if (! ((ischar (Lambda) && strcmpi (Lambda, 'auto')) || (isnumeric (Lambda) && isreal (Lambda) && isvector (Lambda) && ! isempty (Lambda) && all (Lambda >= 0) && all (isfinite (Lambda))))) error (strcat ("ClassificationLinear: 'Lambda' must be", ... " 'auto' or a vector of nonnegative", ... " finite values.")); endif case 'solver' Solver = varargin{2}; if (ischar (Solver)) Solver = {Solver}; endif valid = {'sgd', 'asgd', 'dual', 'bfgs', 'lbfgs', 'sparsa'}; if (! (iscellstr (Solver) && ! isempty (Solver) && all (cellfun (@(s) any (strcmpi (s, valid)), Solver)))) error (strcat ("ClassificationLinear: 'Solver' must be one", ... " of 'sgd', 'asgd', 'dual', 'bfgs',", ... " 'lbfgs' and 'sparsa', or a cell array of", ... " them.")); endif Solver = lower (Solver); case 'beta' BetaIn = varargin{2}; if (! (isnumeric (BetaIn) && isreal (BetaIn) && ismatrix (BetaIn) && ! isempty (BetaIn))) error (strcat ("ClassificationLinear: 'Beta' must be a", ... " real numeric matrix.")); endif case 'bias' BiasIn = varargin{2}; if (! (isnumeric (BiasIn) && isreal (BiasIn) && isvector (BiasIn) && ! isempty (BiasIn))) error (strcat ("ClassificationLinear: 'Bias' must be a", ... " real numeric vector.")); endif case 'fitbias' FitBias = varargin{2}; if (! (islogical (FitBias) || (isnumeric (FitBias) && isscalar (FitBias) && any (FitBias == [0, 1])))) error (strcat ("ClassificationLinear: 'FitBias' must be", ... " either true or false.")); endif FitBias = logical (FitBias); case 'postfitbias' PostFitBias = varargin{2}; if (! (islogical (PostFitBias) || (isnumeric (PostFitBias) && isscalar (PostFitBias) && any (PostFitBias == [0, 1])))) error (strcat ("ClassificationLinear: 'PostFitBias' must", ... " be either true or false.")); endif PostFitBias = logical (PostFitBias); case 'observationsin' ObservationsIn = varargin{2}; if (! (ischar (ObservationsIn) && any (strcmpi (ObservationsIn, {'rows', 'columns'})))) error (strcat ("ClassificationLinear: 'ObservationsIn'", ... " must be either 'rows' or 'columns'.")); endif ObservationsIn = lower (ObservationsIn); case 'betatolerance' BetaTolerance = varargin{2}; if (! (isnumeric (BetaTolerance) && isscalar (BetaTolerance) && isreal (BetaTolerance) && BetaTolerance >= 0)) error (strcat ("ClassificationLinear: 'BetaTolerance'", ... " must be a nonnegative scalar.")); endif case 'gradienttolerance' GradientTolerance = varargin{2}; if (! (isnumeric (GradientTolerance) && isscalar (GradientTolerance) && isreal (GradientTolerance) && GradientTolerance >= 0)) error (strcat ("ClassificationLinear:", ... " 'GradientTolerance' must be a", ... " nonnegative scalar.")); endif case 'deltagradienttolerance' DeltaGradientTolerance = varargin{2}; if (! (isnumeric (DeltaGradientTolerance) && isscalar (DeltaGradientTolerance) && isreal (DeltaGradientTolerance) && DeltaGradientTolerance >= 0)) error (strcat ("ClassificationLinear:", ... " 'DeltaGradientTolerance' must be a", ... " nonnegative scalar.")); endif case 'iterationlimit' IterationLimit = varargin{2}; if (! (isnumeric (IterationLimit) && isscalar (IterationLimit) && isreal (IterationLimit) && IterationLimit > 0 && fix (IterationLimit) == IterationLimit)) error (strcat ("ClassificationLinear: 'IterationLimit'", ... " must be a positive integer scalar.")); endif case 'passlimit' PassLimit = varargin{2}; if (! (isnumeric (PassLimit) && isscalar (PassLimit) && isreal (PassLimit) && PassLimit > 0 && fix (PassLimit) == PassLimit)) error (strcat ("ClassificationLinear: 'PassLimit' must be", ... " a positive integer scalar.")); endif case 'batchsize' BatchSize = varargin{2}; if (! (isnumeric (BatchSize) && isscalar (BatchSize) && isreal (BatchSize) && BatchSize > 0 && fix (BatchSize) == BatchSize)) error (strcat ("ClassificationLinear: 'BatchSize' must be", ... " a positive integer scalar.")); endif case 'batchlimit' BatchLimit = varargin{2}; if (! (isnumeric (BatchLimit) && isscalar (BatchLimit) && isreal (BatchLimit) && BatchLimit > 0 && fix (BatchLimit) == BatchLimit)) error (strcat ("ClassificationLinear: 'BatchLimit' must", ... " be a positive integer scalar.")); endif case 'learnrate' LearnRate = varargin{2}; if (! (isnumeric (LearnRate) && isscalar (LearnRate) && isreal (LearnRate) && LearnRate > 0)) error (strcat ("ClassificationLinear: 'LearnRate' must be", ... " a positive scalar.")); endif case 'optimizelearnrate' OptimizeLearnRate = varargin{2}; if (! (islogical (OptimizeLearnRate) || (isnumeric (OptimizeLearnRate) && isscalar (OptimizeLearnRate) && any (OptimizeLearnRate == [0, 1])))) error (strcat ("ClassificationLinear:", ... " 'OptimizeLearnRate' must be either true", ... " or false.")); endif OptimizeLearnRate = logical (OptimizeLearnRate); case 'truncationperiod' TruncationPeriod = varargin{2}; if (! (isnumeric (TruncationPeriod) && isscalar (TruncationPeriod) && isreal (TruncationPeriod) && TruncationPeriod > 0 && fix (TruncationPeriod) == TruncationPeriod)) error (strcat ("ClassificationLinear:", ... " 'TruncationPeriod' must be a positive", ... " integer scalar.")); endif case 'numcheckconvergence' NumCheckConvergence = varargin{2}; if (! (isnumeric (NumCheckConvergence) && isscalar (NumCheckConvergence) && isreal (NumCheckConvergence) && NumCheckConvergence > 0 && fix (NumCheckConvergence) == NumCheckConvergence)) error (strcat ("ClassificationLinear:", ... " 'NumCheckConvergence' must be a positive", ... " integer scalar.")); endif case 'hessianhistorysize' HessianHistorySize = varargin{2}; if (! (isnumeric (HessianHistorySize) && isscalar (HessianHistorySize) && isreal (HessianHistorySize) && HessianHistorySize > 0 && fix (HessianHistorySize) == HessianHistorySize)) error (strcat ("ClassificationLinear:", ... " 'HessianHistorySize' must be a positive", ... " integer scalar.")); endif case 'verbose' Verbose = varargin{2}; if (! (isnumeric (Verbose) && isscalar (Verbose) && isreal (Verbose) && any (Verbose == [0, 1, 2]))) error (strcat ("ClassificationLinear: 'Verbose' must be", ... " 0, 1, or 2.")); endif case 'classnames' ClassNames = varargin{2}; if (! (iscellstr (ClassNames) || isnumeric (ClassNames) || islogical (ClassNames) || ischar (ClassNames))) error (strcat ("ClassificationLinear: 'ClassNames' must", ... " be a cell array of character vectors, a", ... " logical vector, a numeric vector, or a", ... " character array.")); endif case 'cost' CostIn = varargin{2}; if (! (isnumeric (CostIn) && isreal (CostIn) && ismatrix (CostIn) && ndims (CostIn) == 2 && rows (CostIn) == columns (CostIn))) error (strcat ("ClassificationLinear: 'Cost' must be a", ... " square numeric matrix.")); endif case 'prior' Prior = varargin{2}; if (! ((ischar (Prior) && any (strcmpi (Prior, {'empirical', ... 'uniform'}))) || (isnumeric (Prior) && isreal (Prior) && isvector (Prior) && all (Prior >= 0)) || (isstruct (Prior) && isscalar (Prior)))) error (strcat ("ClassificationLinear: 'Prior' must be", ... " 'empirical', 'uniform', a vector of", ... " nonnegative values, or a structure.")); endif case 'scoretransform' ScoreTransform = varargin{2}; case 'weights' Weights = varargin{2}; if (! (isnumeric (Weights) && isreal (Weights) && isvector (Weights) && all (Weights >= 0))) error (strcat ("ClassificationLinear: 'Weights' must be a", ... " vector of nonnegative values.")); endif case 'predictornames' PredictorNames = varargin{2}; if (! (iscellstr (PredictorNames) && isvector (PredictorNames))) error (strcat ("ClassificationLinear: 'PredictorNames'", ... " must be a cell array of character", ... " vectors.")); endif case 'responsename' ResponseName = varargin{2}; if (! (ischar (ResponseName) && isrow (ResponseName))) error (strcat ("ClassificationLinear: 'ResponseName' must", ... " be a character vector.")); endif case 'categoricalpredictors' CategoricalPredictors = varargin{2}; if (! ((isnumeric (CategoricalPredictors) && isvector (CategoricalPredictors) && all (fix (CategoricalPredictors) == CategoricalPredictors) && all (CategoricalPredictors > 0)) || islogical (CategoricalPredictors) || isempty (CategoricalPredictors))) error (strcat ("ClassificationLinear:", ... " 'CategoricalPredictors' must be a vector", ... " of positive integers or a logical", ... " vector.")); endif otherwise error (strcat ("ClassificationLinear: invalid parameter", ... " name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## Observations may be given down the columns, which only means the ## predictor matrix arrives transposed. if (strcmp (ObservationsIn, 'columns')) X = X'; endif ## Validate the data, resolve the classes, the prior, the cost and ## the observation weights. The four linear and kernel classifiers ## share that opening, so it lives in one place. F = classFrame (X, Y, ClassNames, Prior, CostIn, Weights, ... 'ClassificationLinear'); X = F.X; y = F.y; W = F.W; n = F.n; p = F.p; this.ClassNames = F.ClassNames; this.Prior = F.Prior; this.Cost = F.Cost; ## Resolve the penalty and the solver against one another, since each ## has a default that depends on the other. if (isempty (Regularization)) if (! isempty (Solver) && numel (Solver) == 1 && strcmp (Solver{1}, 'sparsa')) Regularization = 'lasso'; else Regularization = 'ridge'; endif endif if (isempty (Solver)) if (strcmp (Regularization, 'lasso')) Solver = {'sparsa'}; elseif (p <= 100) Solver = {'bfgs'}; elseif (strcmp (Learner, 'svm')) Solver = {'dual'}; else Solver = {'sgd'}; endif endif for k = 1:numel (Solver) if (strcmp (Regularization, 'lasso') && ! any (strcmp (Solver{k}, {'sgd', 'asgd', 'sparsa'}))) error (strcat ("ClassificationLinear: the '%s' solver fits a", ... " ridge penalty only."), Solver{k}); endif if (strcmp (Regularization, 'ridge') && strcmp (Solver{k}, 'sparsa')) error (strcat ("ClassificationLinear: the 'sparsa' solver", ... " fits a lasso penalty only.")); endif if (strcmp (Solver{k}, 'dual') && strcmp (Learner, 'logistic')) error (strcat ("ClassificationLinear: the 'dual' solver fits", ... " a hinge loss only, so it needs 'Learner' set", ... " to 'svm'.")); endif endfor ## Resolve Lambda, which the class reports as a number even when it ## was given as 'auto', and which ModelParameters keeps as given. LambdaIn = Lambda; if (ischar (Lambda)) Lambda = 1 / n; else Lambda = sort (Lambda(:)'); endif L = numel (Lambda); ## Resolve the starting point if (isempty (BetaIn)) Beta0 = zeros (p, L); else if (rows (BetaIn) != p) error (strcat ("ClassificationLinear: 'Beta' must have one", ... " row per predictor.")); endif if (columns (BetaIn) == 1) Beta0 = repmat (BetaIn, 1, L); elseif (columns (BetaIn) == L) Beta0 = BetaIn; else error (strcat ("ClassificationLinear: 'Beta' must have one", ... " column, or one per value of 'Lambda'.")); endif endif if (isempty (BiasIn)) if (strcmp (Learner, 'logistic')) Bias0 = repmat (sum (W .* y), 1, L); else Bias0 = zeros (1, L); endif else BiasIn = BiasIn(:)'; if (numel (BiasIn) == 1) Bias0 = repmat (BiasIn, 1, L); elseif (numel (BiasIn) == L) Bias0 = BiasIn; else error (strcat ("ClassificationLinear: 'Bias' must be a", ... " scalar, or hold one value per value of", ... " 'Lambda'.")); endif endif if (isempty (PassLimit)) if (any (strcmp (Solver, 'dual'))) PassLimit = 10; else PassLimit = 1; endif endif if (isempty (LearnRate)) ## MATLAB's default: the reciprocal root of one plus the largest ## squared length of an observation, so a step never overshoots the ## widest row of the data. LearnRate = 1 / sqrt (1 + max (sum (X .^ 2, 2))); endif ## Fit one model per regularization strength, each warm starting the ## next, which is what makes an ascending Lambda cheaper than the same ## values fitted apart. Beta = zeros (p, L); Bias = zeros (1, L); info = struct ([]); P = struct (); P.Learner = Learner; P.LossFunction = 'hinge'; if (strcmp (Learner, 'logistic')) P.LossFunction = 'logit'; endif P.Epsilon = []; P.Regularization = Regularization; P.FitBias = FitBias; P.PostFitBias = PostFitBias; P.BetaTolerance = BetaTolerance; P.GradientTolerance = GradientTolerance; P.DeltaGradientTolerance = DeltaGradientTolerance; P.IterationLimit = IterationLimit; P.PassLimit = PassLimit; P.BatchSize = BatchSize; P.BatchLimit = BatchLimit; P.LearnRate = LearnRate; P.OptimizeLearnRate = OptimizeLearnRate; P.TruncationPeriod = TruncationPeriod; P.NumCheckConvergence = NumCheckConvergence; P.HessianHistorySize = HessianHistorySize; for l = 1:L P.Lambda = Lambda(l); b = Beta0(:,l); b0 = Bias0(l); if (l > 1 && isempty (BetaIn)) b = Beta(:,l-1); b0 = Bias(l-1); endif for k = 1:numel (Solver) P.Solver = Solver{k}; P.InitialBeta = b; P.InitialBias = b0; [b, b0, S] = linearSolve (X, y, W, P); endfor Beta(:,l) = b; Bias(l) = b0; S.Lambda = Lambda(l); if (l == 1) info = S; else info(l) = S; endif endfor ## Fill in the model this.PredictorNames = PredictorNames; if (isempty (this.PredictorNames)) this.PredictorNames = ... arrayfun (@(k) sprintf ("x%d", k), 1:p, ... 'UniformOutput', false); elseif (numel (this.PredictorNames) != p) error (strcat ("ClassificationLinear: 'PredictorNames' must", ... " have one name per predictor.")); endif this.ExpandedPredictorNames = this.PredictorNames; this.CategoricalPredictors = CategoricalPredictors; this.ResponseName = ResponseName; this.Learner = Learner; this.Beta = Beta; this.Bias = Bias; this.FittedLoss = P.LossFunction; this.Lambda = Lambda; this.NumPredictors_ = p; if (strcmp (Regularization, 'ridge')) this.Regularization = 'ridge (L2)'; else this.Regularization = 'lasso (L1)'; endif if (isempty (ScoreTransform)) if (strcmp (Learner, 'logistic')) ScoreTransform = 'logit'; else ScoreTransform = 'none'; endif endif this.ScoreTransform = ScoreTransform; this.ModelParameters = linearModelParams (P, Solver, LambdaIn, ... [], Beta0, Bias0, ... Verbose, 'classification'); this.FitInfo_ = linearFitInfo (info, BetaTolerance, ... GradientTolerance, ... DeltaGradientTolerance, ... IterationLimit, Solver, PassLimit, ... BatchLimit); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {ClassificationLinear} {@var{labels} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {ClassificationLinear} {[@var{labels}, @var{scores}] =} predict (@var{obj}, @var{XC}) ## ## Classify new observations. ## ## @code{@var{labels} = predict (@var{obj}, @var{XC})} returns the class ## of largest score for each row of @var{XC}, in the type of the ## response the model was fitted to. With @math{L} regularization ## strengths @var{labels} has one column per strength. ## ## @code{[@var{labels}, @var{scores}] = predict (@var{obj}, @var{XC})} ## also returns the scores, an @math{Nx2} matrix whose columns follow ## @qcode{ClassNames}, or an @math{Nx2xL} array with more than one ## strength. The scores are @math{-f} and @math{+f} for the raw model ## value @math{f}, after @qcode{ScoreTransform} has been applied. ## ## @end deftypefn function [labels, scores] = predict (this, XC) if (nargin < 2) error ("ClassificationLinear.predict: too few input arguments."); endif if (isempty (XC)) error ("ClassificationLinear.predict: XC is empty."); endif if (! (isnumeric (XC) && isreal (XC) && ismatrix (XC))) error ("ClassificationLinear.predict: invalid values in XC."); endif if (columns (XC) != this.NumPredictors_) error (strcat ("ClassificationLinear.predict: XC must have the", ... " same number of predictors as the trained model.")); endif f = XC * this.Beta + this.Bias; ## A character matrix holds one name per row and cannot hold a grid of ## them, so a response given that way has nowhere to put the labels of ## more than one regularization strength. Refused rather than ## returned in a shape nothing can read. if (ischar (this.ClassNames) && numel (this.Lambda) > 1) error (strcat ("ClassificationLinear.predict: a character matrix", ... " response cannot carry labels for more than one", ... " value of 'Lambda'; give the response as a cell", ... " array of character vectors.")); endif labels = labelsFromIndex (this.ClassNames, 1 + (f > 0)); if (nargout > 1) L = numel (this.Lambda); scores = zeros (rows (XC), 2, L); for l = 1:L scores(:,:,l) = this.STfun ([-f(:,l), f(:,l)]); endfor if (L == 1) scores = scores(:,:,1); endif endif endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationLinear} {@var{m} =} margin (@var{obj}, @var{X}, @var{Y}) ## ## Classification margin of each observation. ## ## @code{@var{m} = margin (@var{obj}, @var{X}, @var{Y})} returns the ## score of the true class less the score of the other one, one row per ## observation and one column per regularization strength. A positive ## margin is a correct classification. ## ## @end deftypefn function m = margin (this, X, Y) if (nargin < 3) error ("ClassificationLinear.margin: too few input arguments."); endif [gY, errmsg] = labelIndices (this.ClassNames, Y); if (! isempty (errmsg)) error ("ClassificationLinear.margin: %s", errmsg); endif if (rows (X) != numel (gY)) error (strcat ("ClassificationLinear.margin: number of rows in", ... " X and Y must be equal.")); endif [~, scores] = predict (this, X); L = numel (this.Lambda); m = zeros (rows (X), L); for l = 1:L s = scores(:,:,l); strue = s(sub2ind (size (s), (1:rows (s))', gY)); sother = s(sub2ind (size (s), (1:rows (s))', 3 - gY)); m(:,l) = strue - sother; endfor endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationLinear} {@var{e} =} edge (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationLinear} {@var{e} =} edge (@dots{}, @qcode{'Weights'}, @var{W}) ## ## Weighted mean of the classification margins. ## ## @code{@var{e} = edge (@var{obj}, @var{X}, @var{Y})} returns one value ## per regularization strength. The weights are normalized within each ## class to that class's prior before they are applied. ## ## @end deftypefn function e = edge (this, X, Y, varargin) if (nargin < 3) error ("ClassificationLinear.edge: too few input arguments."); endif W = edgeWeights (varargin, Y, this.ClassNames, this.Prior, ... 'ClassificationLinear', 'edge'); m = margin (this, X, Y); e = sum (W .* m, 1); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationLinear} {@var{l} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationLinear} {@var{l} =} loss (@dots{}, @var{name}, @var{value}) ## ## Classification loss on new data. ## ## @code{@var{l} = loss (@var{obj}, @var{X}, @var{Y})} returns the ## misclassification rate, one value per regularization strength. ## ## @code{@var{l} = loss (@dots{}, @var{name}, @var{value})} takes ## @qcode{'LossFun'}, one of @qcode{'binodeviance'}, ## @qcode{'classifcost'}, @qcode{'classiferror'}, @qcode{'exponential'}, ## @qcode{'hinge'}, @qcode{'logit'}, @qcode{'mincost'} and ## @qcode{'quadratic'}, and @qcode{'Weights'}. ## ## @end deftypefn function l = loss (this, X, Y, varargin) if (nargin < 3) error ("ClassificationLinear.loss: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationLinear.loss: optional arguments", ... " must be given in Name-Value pairs.")); endif LossFun = 'classiferror'; Weights = []; while (numel (varargin) > 0) switch (lower (varargin{1})) case 'lossfun' LossFun = varargin{2}; valid = {'binodeviance', 'classifcost', 'classiferror', ... 'exponential', 'hinge', 'logit', 'mincost', ... 'quadratic'}; if (! (ischar (LossFun) && any (strcmpi (LossFun, valid)))) error (strcat ("ClassificationLinear.loss: 'LossFun' must", ... " be 'binodeviance', 'classifcost',", ... " 'classiferror', 'exponential', 'hinge',", ... " 'logit', 'mincost', or 'quadratic'.")); endif LossFun = lower (LossFun); case 'weights' Weights = varargin{2}; if (! (isnumeric (Weights) && isreal (Weights) && isvector (Weights) && all (Weights >= 0))) error (strcat ("ClassificationLinear.loss: 'Weights' must", ... " be a vector of nonnegative values.")); endif otherwise error (strcat ("ClassificationLinear.loss: invalid", ... " parameter name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile [gY, errmsg] = labelIndices (this.ClassNames, Y); if (! isempty (errmsg)) error ("ClassificationLinear.loss: %s", errmsg); endif if (rows (X) != numel (gY)) error (strcat ("ClassificationLinear.loss: number of rows in X", ... " and Y must be equal.")); endif if (isempty (Weights)) w = ones (numel (gY), 1); else w = Weights(:); if (numel (w) != numel (gY)) error (strcat ("ClassificationLinear.loss: 'Weights' must", ... " have one element per observation.")); endif endif w = w / sum (w); [~, scores] = predict (this, X); L = numel (this.Lambda); l = zeros (1, L); for k = 1:L s = scores(:,:,k); l(k) = classificationLoss (LossFun, s, gY, w, this.Cost); endfor endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationLinear} {@var{sub} =} selectModels (@var{obj}, @var{idx}) ## ## Keep a subset of the fitted regularization strengths. ## ## @code{@var{sub} = selectModels (@var{obj}, @var{idx})} returns a model ## holding only the strengths @var{idx} names, which may be indices into ## @qcode{Lambda} or a logical vector over it. ## ## @end deftypefn function sub = selectModels (this, idx) if (nargin < 2) error (strcat ("ClassificationLinear.selectModels: too few", ... " input arguments.")); endif L = numel (this.Lambda); if (islogical (idx)) if (numel (idx) != L) error (strcat ("ClassificationLinear.selectModels: a logical", ... " IDX must have one element per value of", ... " 'Lambda'.")); endif idx = find (idx); endif if (! (isnumeric (idx) && isreal (idx) && isvector (idx) && ! isempty (idx) && all (fix (idx) == idx) && all (idx >= 1) && all (idx <= L))) error (strcat ("ClassificationLinear.selectModels: IDX must", ... " hold integers between 1 and %d."), L); endif sub = this; sub.Lambda = this.Lambda(idx); sub.Beta = this.Beta(:,idx); sub.Bias = this.Bias(idx); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationLinear} {} savemodel (@var{obj}, @var{filename}) ## ## Save a linear classifier to a file. ## ## @code{savemodel (@var{obj}, @var{filename})} saves the model ## @var{obj} into @var{filename} in a form @code{loadmodel} can read ## back. ## ## @end deftypefn function savemodel (obj, fname) classdef_name = 'ClassificationLinear'; ClassNames = obj.ClassNames; Prior = obj.Prior; Cost = obj.Cost; ScoreTransform = obj.ScoreTransform; PredictorNames = obj.PredictorNames; CategoricalPredictors = obj.CategoricalPredictors; ResponseName = obj.ResponseName; ExpandedPredictorNames = obj.ExpandedPredictorNames; Learner = obj.Learner; Beta = obj.Beta; Bias = obj.Bias; FittedLoss = obj.FittedLoss; Lambda = obj.Lambda; ModelParameters = obj.ModelParameters; Regularization = obj.Regularization; NumPredictors_ = obj.NumPredictors_; save ('-binary', fname, 'classdef_name', 'ClassNames', 'Prior', ... 'Cost', 'ScoreTransform', 'PredictorNames', ... 'CategoricalPredictors', 'ResponseName', ... 'ExpandedPredictorNames', 'Learner', 'Beta', 'Bias', ... 'FittedLoss', 'Lambda', 'ModelParameters', 'Regularization', ... 'NumPredictors_'); endfunction endmethods methods (Access = public, Hidden) function display (this) in_name = inputname (1); if (! isempty (in_name)) printf ('%s =\n', in_name); endif disp (this); endfunction function disp (this) printf ("\n ClassificationLinear\n\n"); printf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); printf ("%+25s: %s\n", 'ClassNames', classNameListing (this.ClassNames)); printf ("%+25s: '%s'\n", 'ScoreTransform', this.ScoreTransform); printf ("%+25s: [%dx%d double]\n", 'Beta', rows (this.Beta), ... columns (this.Beta)); if (numel (this.Bias) == 1) printf ("%+25s: %g\n", 'Bias', this.Bias); printf ("%+25s: %g\n", 'Lambda', this.Lambda); else printf ("%+25s: [1x%d double]\n", 'Bias', numel (this.Bias)); printf ("%+25s: [1x%d double]\n", 'Lambda', numel (this.Lambda)); endif printf ("%+25s: '%s'\n", 'Learner', this.Learner); printf ("\n"); endfunction ## What the fit reported, which fitclinear returns as its second output. function S = fitInfo_ (this) S = this.FitInfo_; endfunction ## Custom setter, so that assigning a name or a handle updates both the ## text the property reports and the callable predict uses. function this = set.ScoreTransform (this, val) [this.STfun, this.ScoreTransform] = ... parseScoreTransform (val, 'ClassificationLinear'); endfunction endmethods methods (Static, Hidden) function mdl = load_model (filename, data) mdl = ClassificationLinear (zeros (2, 1), [0; 1]); fields = fieldnames (data); for k = 1:numel (fields) mdl.(fields{k}) = data.(fields{k}); endfor endfunction endmethods endclassdef %!demo %! ## Separate the two overlapping iris species with a linear classifier and %! ## read the posterior probability it gives each observation. %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationLinear (X, Y, 'Learner', 'logistic') %! [label, score] = predict (Mdl, X([1, 51],:)) %!demo %! ## One object can hold a whole regularization path. A stronger penalty %! ## shrinks the coefficients, and every method reports one column per %! ## strength. %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationLinear (X, Y, 'Lambda', [0.001, 0.01, 0.1]); %! Mdl.Beta %! loss (Mdl, X, Y) %!test %! ## The model reports the surface MATLAB reports %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationLinear (X, Y); %! assert_equal (class (Mdl), 'ClassificationLinear'); %! assert_equal (Mdl.ClassNames, {'versicolor'; 'virginica'}); %! assert_equal (Mdl.Learner, 'svm'); %! assert_equal (Mdl.FittedLoss, 'hinge'); %! assert_equal (Mdl.Regularization, 'ridge (L2)'); %! assert_equal (Mdl.ScoreTransform, 'none'); %! assert_equal (Mdl.ResponseName, 'Y'); %! assert_equal (Mdl.PredictorNames, {'x1', 'x2', 'x3', 'x4'}); %! assert_equal (Mdl.ExpandedPredictorNames, {'x1', 'x2', 'x3', 'x4'}); %! assert_equal (Mdl.CategoricalPredictors, []); %! assert_equal (Mdl.Prior, [0.5, 0.5]); %! assert_equal (Mdl.Cost, [0, 1; 1, 0]); %! assert_equal (Mdl.Lambda, 0.01); %! assert_equal (size (Mdl.Beta), [4, 1]); %!test %! ## The properties are the ones MATLAB lists, in its order %! load fisheriris %! Mdl = ClassificationLinear (meas(51:end,:), species(51:end)); %! assert_equal (sort (properties (Mdl)), ... %! sort ({'ClassNames'; 'Prior'; 'Cost'; 'ScoreTransform'; ... %! 'PredictorNames'; 'CategoricalPredictors'; ... %! 'ResponseName'; 'ExpandedPredictorNames'; ... %! 'Learner'; 'Beta'; 'Bias'; 'FittedLoss'; 'Lambda'; ... %! 'ModelParameters'; 'Regularization'})); %!test %! ## A logistic ridge fit reproduces R2024a's coefficients %! load fisheriris %! Mdl = ClassificationLinear (meas(51:end,:), species(51:end), ... %! 'Learner', 'logistic', 'Solver', 'lbfgs', ... %! 'BetaTolerance', 0, 'GradientTolerance', ... %! 1e-12, ... %! 'IterationLimit', 20000); %! assert_equal (Mdl.Beta, [-0.394433478724131; -0.513277404049627; ... %! 2.9307513837558; 2.41703218835114], 1e-7); %! assert_equal (Mdl.Bias, -14.4307581801051, 1e-7); %! assert_equal (Mdl.FittedLoss, 'logit'); %! assert_equal (Mdl.ScoreTransform, 'logit'); %!test %! ## The default score transform follows the learner, and only the logistic %! ## one turns the scores into posterior probabilities %! load fisheriris %! X = meas(51:end,:); %! Ml = ClassificationLinear (X, species(51:end), 'Learner', 'logistic'); %! Ms = ClassificationLinear (X, species(51:end), 'Learner', 'svm'); %! assert_equal (Ml.ScoreTransform, 'logit'); %! assert_equal (Ms.ScoreTransform, 'none'); %! [~, sl] = predict (Ml, X(1:5,:)); %! [~, ss] = predict (Ms, X(1:5,:)); %! assert_equal (sum (sl, 2), ones (5, 1), 1e-12); %! assert_equal (ss(:,1), -ss(:,2), 1e-12); %!test %! ## The scores of a logistic fit are R2024a's posteriors %! load fisheriris %! X = meas(51:end,:); %! Mdl = ClassificationLinear (X, species(51:end), 'Learner', 'logistic', ... %! 'Solver', 'lbfgs', 'BetaTolerance', 0, ... %! 'GradientTolerance', 1e-12, ... %! 'IterationLimit', 20000); %! [label, score] = predict (Mdl, X(1:2,:)); %! assert_equal (label, {'versicolor'; 'versicolor'}); %! assert_equal (score, [0.842361345526662, 0.157638654473338; ... %! 0.856151985655854, 0.143848014344146], 1e-7); %!test %! ## margin is the true class score less the other, and edge their weighted %! ## mean. Both halves are asserted: the values against R2024a, and the %! ## relationship against the scores this model actually reports. The %! ## values alone would not catch a margin computed consistently wrongly, %! ## since a wrong-but-stable margin agrees with its own past output %! ## forever; the relationship alone would not catch our agreeing with %! ## ourselves about the wrong quantity. %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationLinear (X, Y, 'Learner', 'logistic', ... %! 'Solver', 'lbfgs', 'BetaTolerance', 0, ... %! 'GradientTolerance', 1e-12, ... %! 'IterationLimit', 20000); %! assert_equal (margin (Mdl, X(1,:), Y(1)), 0.684722691053324, 1e-7); %! assert_equal (edge (Mdl, X, Y), 0.726067240395032, 1e-7); %! ## the invariant, independent of any solver output %! [~, score] = predict (Mdl, X); %! virg = strcmp (Y, 'virginica'); %! strue = score(:,1); %! strue(virg) = score(virg,2); %! sother = score(:,2); %! sother(virg) = score(virg,1); %! assert_equal (margin (Mdl, X, Y), strue - sother, 1e-15); %! assert_equal (edge (Mdl, X, Y), mean (margin (Mdl, X, Y)), 1e-15); %!test %! ## Every loss reproduces R2024a, and each is a function of the score the %! ## model gives the true class rather than of the margin %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationLinear (X, Y, 'Learner', 'logistic', ... %! 'Solver', 'lbfgs', 'BetaTolerance', 0, ... %! 'GradientTolerance', 1e-12, ... %! 'IterationLimit', 20000); %! assert_equal (loss (Mdl, X, Y), 0.04, 1e-12); %! assert_equal (loss (Mdl, X, Y, 'LossFun', 'hinge'), ... %! 0.136966379802484, 1e-7); %! assert_equal (loss (Mdl, X, Y, 'LossFun', 'logit'), ... %! 0.354333320227268, 1e-7); %! assert_equal (loss (Mdl, X, Y, 'LossFun', 'binodeviance'), ... %! 0.170050306584128, 1e-7); %! assert_equal (loss (Mdl, X, Y, 'LossFun', 'exponential'), ... %! 0.426899191073676, 1e-7); %! assert_equal (loss (Mdl, X, Y, 'LossFun', 'quadratic'), ... %! 0.040701761220091, 1e-7); %!test %! ## Under the default cost both cost based losses reduce to the error %! ## rate: the class of least expected cost is then the class of largest %! ## score, which is what predict returns %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationLinear (X, Y, 'Learner', 'logistic'); %! assert_equal (loss (Mdl, X, Y, 'LossFun', 'classifcost'), ... %! loss (Mdl, X, Y), 1e-12); %! assert_equal (loss (Mdl, X, Y, 'LossFun', 'mincost'), ... %! loss (Mdl, X, Y), 1e-12); %!test %! ## A cost that is not symmetric moves the least cost assignment away from %! ## the largest score, so the two cost based losses part company %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationLinear (X, Y, 'Learner', 'logistic', ... %! 'Cost', [0, 2; 3, 0]); %! assert_equal (loss (Mdl, X, Y, 'LossFun', 'classifcost') > 0, true); %! assert_equal (loss (Mdl, X, Y, 'LossFun', 'mincost') > 0, true); %!test %! ## A cost matrix reaches the fit through the prior, which is what MATLAB %! ## does: a fit costing four times as much to miss the first class equals %! ## one given the prior that scaling implies %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mc = ClassificationLinear (X, Y, 'Cost', [0, 4; 1, 0]); %! Mp = ClassificationLinear (X, Y, 'Prior', [0.8, 0.2]); %! assert_equal (Mc.Beta, Mp.Beta, 1e-12); %! assert_equal (Mc.Bias, Mp.Bias, 1e-12); %! assert_equal (Mc.Prior, [0.5, 0.5]); %!test %! ## Lambda defaults to the reciprocal of the observations that were used %! load fisheriris %! Mdl = ClassificationLinear (meas(51:end,:), species(51:end)); %! assert_equal (Mdl.Lambda, 1 / 100, 1e-15); %!test %! ## A vector of strengths fits one model per value, sorted ascending, and %! ## every method reports one column per value %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationLinear (X, Y, 'Lambda', [0.1, 0.001, 0.01]); %! assert_equal (Mdl.Lambda, [0.001, 0.01, 0.1]); %! assert_equal (size (Mdl.Beta), [4, 3]); %! assert_equal (size (Mdl.Bias), [1, 3]); %! [label, score] = predict (Mdl, X(1:4,:)); %! assert_equal (size (label), [4, 3]); %! assert_equal (size (score), [4, 2, 3]); %! assert_equal (size (margin (Mdl, X, Y)), [100, 3]); %! assert_equal (size (edge (Mdl, X, Y)), [1, 3]); %! assert_equal (size (loss (Mdl, X, Y)), [1, 3]); %!test %! ## A stronger penalty shrinks the coefficients %! load fisheriris %! Mdl = ClassificationLinear (meas(51:end,:), species(51:end), ... %! 'Learner', 'logistic', 'Lambda', [0.001, 1]); %! assert_equal (norm (Mdl.Beta(:,1)) > norm (Mdl.Beta(:,2)), true); %!test %! ## selectModels keeps the strengths it is given and drops the rest %! load fisheriris %! Mdl = ClassificationLinear (meas(51:end,:), species(51:end), ... %! 'Lambda', [0.001, 0.01, 0.1]); %! sub = selectModels (Mdl, [1, 3]); %! assert_equal (sub.Lambda, [0.001, 0.1]); %! assert_equal (size (sub.Beta), [4, 2]); %! assert_equal (sub.Beta, Mdl.Beta(:,[1, 3])); %! sub = selectModels (Mdl, logical ([0, 1, 0])); %! assert_equal (sub.Lambda, 0.01); %!test %! ## A lasso penalty drives coefficients to exactly zero, which a ridge %! ## penalty never does %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Ml = ClassificationLinear (X, Y, 'Learner', 'logistic', ... %! 'Regularization', 'lasso', 'Lambda', 0.05); %! Mr = ClassificationLinear (X, Y, 'Learner', 'logistic', ... %! 'Regularization', 'ridge', 'Lambda', 0.05); %! assert_equal (Ml.Regularization, 'lasso (L1)'); %! assert_equal (sum (Ml.Beta == 0) > 0, true); %! assert_equal (any (Mr.Beta == 0), false); %!test %! ## The default solver follows the penalty and the width of the data %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! [~, F] = fitclinear (X, Y); %! assert_equal (F.Solver, {'bfgs'}); %! [~, F] = fitclinear (X, Y, 'Regularization', 'lasso'); %! assert_equal (F.Solver, {'sparsa'}); %!test %! ## FitBias false leaves the intercept at zero %! load fisheriris %! Mdl = ClassificationLinear (meas(51:end,:), species(51:end), ... %! 'FitBias', false); %! assert_equal (Mdl.Bias, 0); %!test %! ## Observations may be given down the columns instead %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mr = ClassificationLinear (X, Y, 'Learner', 'logistic'); %! Mc = ClassificationLinear (X', Y, 'Learner', 'logistic', ... %! 'ObservationsIn', 'columns'); %! assert_equal (Mr.Beta, Mc.Beta, 1e-12); %!test %! ## A row with a missing predictor or a missing response is dropped, and %! ## Lambda follows the count that survived %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! X(3,2) = NaN; %! Mdl = ClassificationLinear (X, Y); %! assert_equal (Mdl.Lambda, 1 / 99, 1e-15); %!test %! ## ScoreTransform is settable and reaches predict %! load fisheriris %! X = meas(51:end,:); %! Mdl = ClassificationLinear (X, species(51:end)); %! Mdl.ScoreTransform = 'logit'; %! assert_equal (Mdl.ScoreTransform, 'logit'); %! [~, score] = predict (Mdl, X(1:3,:)); %! assert_equal (sum (score, 2), ones (3, 1), 1e-12); %!test %! ## A saved model reads back as the same model %! load fisheriris %! X = meas(51:end,:); %! Mdl = ClassificationLinear (X, species(51:end), 'Learner', 'logistic'); %! fname = tempname (); %! savemodel (Mdl, fname); %! Mnew = loadmodel (fname); %! delete (fname); %! assert_equal (class (Mnew), 'ClassificationLinear'); %! assert_equal (Mnew.Beta, Mdl.Beta); %! assert_equal (Mnew.Bias, Mdl.Bias); %! assert_equal (predict (Mnew, X(1:5,:)), predict (Mdl, X(1:5,:))); %!test %! ## The labels come back in the type the response was given in, a %! ## character matrix included: its rows name the classes, and indexing it %! ## by element rather than by row would flatten it into single characters %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mcell = ClassificationLinear (X, Y); %! assert_equal (class (predict (Mcell, X(1:3,:))), 'cell'); %! Mchar = ClassificationLinear (X, char (Y)); %! assert_equal (size (Mchar.ClassNames), [2, 10]); %! assert_equal (size (predict (Mchar, X(1:3,:))), [3, 10]); %! assert_equal (loss (Mchar, X, char (Y)), loss (Mcell, X, Y), 1e-12); %! Ynum = double (strcmp (Y, 'virginica')); %! Mnum = ClassificationLinear (X, Ynum); %! assert_equal (class (predict (Mnum, X(1:3,:))), 'double'); %! assert_equal (Mnum.ClassNames, [0; 1]); %! Mlog = ClassificationLinear (X, logical (Ynum)); %! assert_equal (class (predict (Mlog, X(1:3,:))), 'logical'); %!test %! ## Observation weights reach the fit, and the prior follows them. Both %! ## numbers are R2024a's for the weights 1 to 100. %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! Mdl = ClassificationLinear (X, Y, 'Learner', 'logistic', ... %! 'Solver', 'lbfgs', 'Weights', (1:100)', ... %! 'BetaTolerance', 0, ... %! 'GradientTolerance', 1e-12, ... %! 'IterationLimit', 20000); %! assert_equal (Mdl.Prior, [0.252475247524752, 0.747524752475248], 1e-12); %! assert_equal (Mdl.Beta, [-0.0907861095243143; -0.344147850956068; ... %! 2.73449884653132; 2.20876095012392], 1e-7); %! assert_equal (Mdl.Bias, -14.6291356484861, 1e-6); %! assert_equal (Mdl.FitInfo_.Objective, 0.208158687811647, 1e-10); %!test %! ## A character matrix response names one class per row, and a model %! ## fitted from one is the model fitted from the equivalent cell array. %! ## The check is worth its length: a linear index into a character matrix %! ## flattens the names into single characters, and so does an ismember %! ## between two character matrices, and neither shows up at the property %! ## that is usually looked at. %! load fisheriris %! X = meas(51:end,:); %! Yc = species(51:end); %! Ym = char (Yc); %! Mc = ClassificationLinear (X, Yc, 'Learner', 'logistic'); %! Mm = ClassificationLinear (X, Ym, 'Learner', 'logistic'); %! assert_equal (cellstr (Mm.ClassNames), Mc.ClassNames); %! assert_equal (Mm.Prior, Mc.Prior); %! assert_equal (Mm.Beta, Mc.Beta); %! assert_equal (Mm.Bias, Mc.Bias); %!test %! ## Every method answers the same through a character matrix response as %! ## through the cell array it came from %! load fisheriris %! X = meas(51:end,:); %! Yc = species(51:end); %! Ym = char (Yc); %! Mc = ClassificationLinear (X, Yc, 'Learner', 'logistic'); %! Mm = ClassificationLinear (X, Ym, 'Learner', 'logistic'); %! assert_equal (cellstr (predict (Mm, X)), predict (Mc, X)); %! assert_equal (margin (Mm, X, Ym), margin (Mc, X, Yc)); %! assert_equal (edge (Mm, X, Ym), edge (Mc, X, Yc)); %! assert_equal (edge (Mm, X, Ym, 'Weights', (1:100)'), ... %! edge (Mc, X, Yc, 'Weights', (1:100)')); %! assert_equal (loss (Mm, X, Ym), loss (Mc, X, Yc)); %! assert_equal (loss (Mm, X, Ym, 'LossFun', 'hinge'), ... %! loss (Mc, X, Yc, 'LossFun', 'hinge')); %! assert_equal (loss (Mm, X, Ym, 'LossFun', 'mincost'), ... %! loss (Mc, X, Yc, 'LossFun', 'mincost')); %!test %! ## 'ClassNames' selects a subset when it is given as a character matrix %! ## too, which needs both sides of the comparison turned into names %! load fisheriris %! Mcell = ClassificationLinear (meas, species, ... %! 'ClassNames', {'versicolor', 'virginica'}); %! Mchar = ClassificationLinear (meas, char (species), 'ClassNames', ... %! char ({'versicolor', 'virginica'})); %! assert_equal (cellstr (Mchar.ClassNames), Mcell.ClassNames); %! assert_equal (Mchar.Beta, Mcell.Beta); %! assert_equal (Mchar.Lambda, Mcell.Lambda); %!test %! ## Names of unequal length are padded by the character matrix and the %! ## padding is not part of the name %! load fisheriris %! X = meas(51:end,:); %! Y = [repmat({'ab'}, 50, 1); repmat({'abcd'}, 50, 1)]; %! Mcell = ClassificationLinear (X, Y); %! Mchar = ClassificationLinear (X, char (Y)); %! assert_equal (cellstr (Mchar.ClassNames), Mcell.ClassNames); %! assert_equal (cellstr (predict (Mchar, X)), predict (Mcell, X)); %!test %! ## The default fit stops on the coefficients, as MATLAB's does, which is %! ## only true once the engine offers BetaTolerance. Before it did, this %! ## fit ran on to the gradient tolerance and reported NaN here. %! load fisheriris %! Mdl = ClassificationLinear (meas(51:end,:), species(51:end), ... %! 'Learner', 'logistic'); %! assert_equal (Mdl.FitInfo_.BetaTolerance, 1e-4); %! assert_equal (Mdl.FitInfo_.TerminationCode, 1); %! assert_equal (Mdl.FitInfo_.TerminationStatus, ... %! {'Tolerance on coefficients satisfied.'}); %! assert_equal (isfinite (Mdl.FitInfo_.RelativeChangeInBeta), true); %!test %! ## A line search that cannot improve the objective says so, rather than %! ## reporting the iteration limit it never reached. Code and wording %! ## measured on R2024a. Where the search gives up varies by platform, so %! ## the count is only asserted to be short of the limit. %! load fisheriris %! Mdl = ClassificationLinear (meas(51:end,:), species(51:end), ... %! 'Learner', 'logistic', 'Lambda', 1e-10, ... %! 'BetaTolerance', 0, 'GradientTolerance', 0); %! assert_equal (Mdl.FitInfo_.TerminationCode, -11); %! assert_equal (Mdl.FitInfo_.TerminationStatus, ... %! {'Unable to find a step decreasing the objective.'}); %! S = Mdl.fitInfo_ (); %! assert_equal (S.NumIterations < S.IterationLimit, true); %!test %! ## A tolerance of exactly zero switches its test off and the quantity it %! ## governs comes back NaN, which is what MATLAB reports. Not "never %! ## satisfied": not computed. %! load fisheriris %! Mdl = ClassificationLinear (meas(51:end,:), species(51:end), ... %! 'BetaTolerance', 0, 'GradientTolerance', 1e-8); %! assert_equal (isnan (Mdl.FitInfo_.RelativeChangeInBeta), true); %! Mdl = ClassificationLinear (meas(51:end,:), species(51:end), ... %! 'GradientTolerance', 0); %! assert_equal (isnan (Mdl.FitInfo_.GradientNorm), true); %! assert_equal (Mdl.FitInfo_.GradientTolerance, 0); %!test %! ## A character matrix response survives a round trip through savemodel %! ## and loadmodel, names and all %! load fisheriris %! X = meas(51:end,:); %! Ym = char (species(51:end)); %! Mdl = ClassificationLinear (X, Ym, 'Learner', 'logistic'); %! fname = tempname (); %! savemodel (Mdl, fname); %! Mnew = loadmodel (fname); %! delete (fname); %! assert_equal (Mnew.ClassNames, Mdl.ClassNames); %! assert_equal (size (Mnew.ClassNames), [2, 10]); %! assert_equal (predict (Mnew, X(1:5,:)), predict (Mdl, X(1:5,:))); %! assert_equal (loss (Mnew, X, Ym), loss (Mdl, X, Ym), 1e-12); %!test %! ## ModelParameters reports every option but gives a value only to the %! ## ones the chosen solver can act on, which is MATLAB's behaviour and was %! ## measured one fit per solver on R2024a. Two of these are easy to get %! ## wrong: HessianHistorySize is empty for 'bfgs' and 15 for 'lbfgs', a %! ## full quasi-Newton method having no limited memory to size, and the two %! ## solvers that run no gradient test report a tolerance of zero. %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! b = ClassificationLinear (X, Y, 'Solver', 'bfgs').ModelParameters; %! assert_equal (numel (fieldnames (b)), 28); %! assert_equal (b.HessianHistorySize, []); %! assert_equal (b.BatchSize, []); %! assert_equal (b.PassLimit, []); %! assert_equal (b.IterationLimit, 1000); %! assert_equal (b.LineSearch, 'strongwolfe'); %! l = ClassificationLinear (X, Y, 'Solver', 'lbfgs').ModelParameters; %! assert_equal (l.HessianHistorySize, 15); %! g = ClassificationLinear (X, Y, 'Solver', 'sgd').ModelParameters; %! assert_equal (g.BatchSize, 10); %! assert_equal (g.IterationLimit, []); %! assert_equal (g.GradientTolerance, 0); %! assert_equal (g.LineSearch, []); %! d = ClassificationLinear (X, Y, 'Solver', 'dual').ModelParameters; %! assert_equal (isempty (d.DeltaGradientTolerance), false); %! assert_equal (d.PassLimit, 10); %! assert_equal (d.HessianHistorySize, []); %! assert_equal (d.GradientTolerance, 0); %!test %! ## The default learning rate is MATLAB's own formula, the reciprocal root %! ## of one plus the largest squared observation norm. R2024a reports %! ## 0.0896365435911655 on this fixture. %! load fisheriris %! X = meas(51:end,:); %! Mdl = ClassificationLinear (X, species(51:end), 'Solver', 'sgd'); %! assert_equal (Mdl.ModelParameters.LearnRate, ... %! 1 / sqrt (1 + max (sum (X .^ 2, 2))), 1e-15); %! assert_equal (Mdl.ModelParameters.LearnRate, 0.0896365435911655, 1e-12); %!test %! ## The dual solver's two defaults, measured on R2024a and confirmed on %! ## R2026a through MATLAB Online. Both differ from what MathWorks %! ## documents, and the first differs between the two learners: the %! ## complementarity tolerance is 1 for a hinge loss and 0.1 for an %! ## epsilon-insensitive one, which is the 0.1 the documentation quotes. %! load fisheriris %! Mdl = ClassificationLinear (meas(51:end,:), species(51:end), ... %! 'Solver', 'dual'); %! assert_equal (Mdl.ModelParameters.DeltaGradientTolerance, 1); %! assert_equal (Mdl.ModelParameters.NumCheckConvergence, 2); %! assert_equal (Mdl.ModelParameters.PassLimit, 10); ## Test input validation %!error ... %! ClassificationLinear (ones (5, 2)) %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Learner') %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Learner', ... %! 'tree') %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'Regularization', 'elastic') %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Lambda', -1) %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Solver', ... %! 'newton') %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'FitBias', ... %! 'yes') %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'ObservationsIn', 'pages') %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'BetaTolerance', -1) %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'IterationLimit', 2.5) %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Verbose', 3) %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Nonsense', 1) %!error ... %! ClassificationLinear ({1, 2; 3, 4}, [1; 2]) %!error ClassificationLinear ([], []) %!error ... %! ClassificationLinear (ones (10, 2), [1; 2]) %!error ... %! ClassificationLinear (ones (9, 2), [1; 1; 1; 2; 2; 2; 3; 3; 3]) %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Prior', ... %! [0.2, 0.3, 0.5]) %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Cost', ... %! ones (3)) %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'Regularization', 'ridge', 'Solver', 'sparsa') %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'Regularization', 'lasso', 'Solver', 'lbfgs') %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Learner', ... %! 'logistic', 'Solver', 'dual') %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'PredictorNames', {'a', 'b', 'c'}) %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Beta', ... %! ones (3, 1)) %!error ... %! ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Bias', [1, 2]) %!error ... %! predict (ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)])) %!error ... %! predict (ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)]), []) %!error ... %! predict (ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)]), ... %! ones (3, 5)) %!error ... %! margin (ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)]), ... %! ones (3, 2)) %!error ... %! loss (ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)]), ... %! ones (10, 2), [ones(5,1); 2*ones(5,1)], 'LossFun', 'mse') %!error ... %! loss (ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)]), ... %! ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Nonsense', 1) %!error ... %! loss (ClassificationLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)]), ... %! ones (10, 2), 3*ones (10, 1)) %!error ... %! selectModels (ClassificationLinear (ones (10, 2), ... %! [ones(5,1); 2*ones(5,1)], 'Lambda', [0.1, 0.2, 0.3]), 5) %!error ... %! selectModels (ClassificationLinear (ones (10, 2), ... %! [ones(5,1); 2*ones(5,1)], 'Lambda', [0.1, 0.2, ... %! 0.3]), logical ([1, 0])) %!error ... %! predict (ClassificationLinear (ones (10, 2), ... %! ['a';'a';'a';'a';'a';'b';'b';'b';'b';'b'], 'Lambda', ... %! [0.1, 0.2]), ones (3, 2)) ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = fitclinear (meas, strcmp (species, 'setosa')); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = fitclinear (meas, strcmp (species, 'setosa')); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/ClassificationNaiveBayes.m000066400000000000000000002452321524624707500273640ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef ClassificationNaiveBayes ## -*- texinfo -*- ## @deftp {statistics} ClassificationNaiveBayes ## ## Naive Bayes classification ## ## The @code{ClassificationNaiveBayes} class implements a naive Bayes ## classifier object, which can predict responses for new data using the ## @code{predict} method. ## ## A naive Bayes classifier estimates one univariate density per class and ## per predictor, and treats the predictors as conditionally independent ## given the class. The joint likelihood of an observation is therefore the ## product of its per-predictor densities, and the posterior follows from ## the class prior by Bayes' rule. The independence assumption is rarely ## true, but it costs only one density per predictor rather than one joint ## density over all of them, which is what makes the model usable when the ## predictors are many and the observations few. ## ## Create a @code{ClassificationNaiveBayes} object by using the ## @code{fitcnb} function or the class constructor. ## ## Each predictor carries its own distribution, named in ## @qcode{DistributionNames}, and the fitted parameters of class @math{k} and ## predictor @math{j} are held in @code{DistributionParameters@{k,j@}}. A ## @qcode{'normal'} predictor stores a two element column vector, the class ## conditional mean and standard deviation; a @qcode{'kernel'} predictor ## stores a @code{prob.KernelDistribution} object. ## ## @seealso{fitcnb} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} Y ## ## Class labels ## ## Specified as a logical or numeric column vector, or as a character array ## or a cell array of character vectors with the same number of rows as the ## predictor data. Each row in @var{Y} is the observed class label for the ## corresponding row in @var{X}. This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} X ## ## Predictor data ## ## A numeric matrix containing the predictor data. Each column of @var{X} ## represents one predictor (variable), and each row represents one ## observation. This property is read-only. ## ## @end deftp X = []; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} RowsUsed ## ## Rows used for fitting ## ## A logical column vector with the same length as the observations in the ## original predictor data @var{X}, true for each row that was used for ## fitting the model. It is empty, @qcode{[]}, when every observation was ## used, so a non-empty value means that rows holding missing values were ## dropped. This property is read-only. ## ## @end deftp RowsUsed = []; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} W ## ## Observation weights ## ## A numeric column vector with one entry per observation used for fitting, ## summing to one. Each class contributes its prior, spread evenly over ## the observations belonging to it. This property is read-only. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} ModelParameters ## ## What was fitted, and how ## ## A structure carrying @qcode{DistributionNames}, @qcode{Kernel}, ## @qcode{Support}, @qcode{Width}, @qcode{StandardizeData}, ## @qcode{Version}, @qcode{Method} and @qcode{Type}. ## ## It records the arguments as they were @emph{given}, where the ## properties of the same name record what they were resolved to: a model ## fitted with no @qcode{'DistributionNames'} argument reports the single ## name @qcode{'normal'} here and one name per predictor there. The ## kernel settings are filled in with their defaults when a kernel density ## was asked for, and left empty when none was. This property is ## read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} NumObservations ## ## Number of observations ## ## A positive integer specifying the number of observations used to train ## the model, after any row holding a missing value has been dropped. This ## property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} BinEdges ## ## Bin edges ## ## A cell array with one entry per predictor, holding that predictor's bin ## edges where the learner discretized it before fitting. A naive Bayes ## model fits a density to each predictor as it stands and bins nothing, so ## this is always an empty cell. It is kept because the cross-validated ## model carries it across, and because code that reaches into it with ## @code{cellfun} must find a cell rather than an empty matrix. This ## property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} PredictorNames ## ## Predictor variable names ## ## A cell array of character vectors naming the predictors, in the order in ## which they appear in @var{X}. The default names are @qcode{'x1'}, ## @qcode{'x2'}, and so on. This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} CategoricalPredictors ## ## Categorical predictor indices ## ## A numeric row vector of the column indices of @var{X} treated as ## categorical, or empty when none is. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} ResponseName ## ## Response variable name ## ## A character vector naming the response variable, @qcode{'Y'} by default. ## This property is read-only. ## ## @end deftp ResponseName = 'Y'; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} ExpandedPredictorNames ## ## Expanded predictor variable names ## ## A cell array of character vectors naming the predictors as the model ## sees them. It equals @qcode{PredictorNames} unless a categorical ## predictor has been expanded into indicator variables. This property is ## read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} ClassNames ## ## Class labels of the fitted model ## ## A cell array of character vectors, a logical or numeric column vector, ## or a character array, holding the distinct classes the model was fitted ## on, in the order the other per-class properties use. This property is ## read-only. ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} HyperparameterOptimizationResults ## ## Results of the hyperparameter optimization ## ## @strong{Always empty.} It is declared for MATLAB compatibility, where ## it holds what an automatic search over the hyperparameters found. This ## class fits the parameters it is given and runs no such search, so there ## is nothing to report. This property is read-only. ## ## @end deftp HyperparameterOptimizationResults = []; endproperties ## Properties a user may set after the model is fitted. Each one is ## validated by its set method below. They sit between the two read-only ## blocks so that 'properties' reports MATLAB's own order. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} Prior ## ## Class prior probabilities ## ## A numeric row vector with one entry per class, in the order of ## @qcode{ClassNames}, summing to one. It may be assigned after fitting, ## as a numeric vector, as a structure carrying @qcode{ClassNames} and ## @qcode{ClassProbs}, or as @qcode{'empirical'} or @qcode{'uniform'}. ## Assigning it re-derives @qcode{W}. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} Cost ## ## Misclassification cost ## ## A square numeric matrix with one row and column per class, in the order ## of @qcode{ClassNames}. @code{Cost(i,j)} is the cost of classifying an ## observation of class @math{i} into class @math{j}, and the default is ## one off the diagonal and zero on it. It may be assigned after fitting, ## as a matrix or as a structure carrying @qcode{ClassNames} and ## @qcode{ClassificationCosts}. ## ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} ScoreTransform ## ## Score transformation ## ## A character vector naming the function applied to the posterior returned ## by @code{predict}, or a function handle taking and returning a matrix of ## the same size. The default, @qcode{'none'}, leaves the posterior ## untouched. ## ## @end deftp ScoreTransform = 'none'; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} DistributionNames ## ## Predictor distributions ## ## A cell array of character vectors with one entry per predictor, naming ## the distribution fitted to it: @qcode{'normal'} or @qcode{'kernel'}. ## This property is read-only. ## ## @end deftp DistributionNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} Mu ## ## Predictor means ## ## The means used to center the predictors, when the model standardizes ## them, and empty otherwise. These are @emph{not} the class conditional ## means, which are held in @qcode{DistributionParameters}. This property ## is read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} Sigma ## ## Predictor standard deviations ## ## The standard deviations used to scale the predictors, when the model ## standardizes them, and empty otherwise. These are @emph{not} the class ## conditional standard deviations, which are held in ## @qcode{DistributionParameters}. This property is read-only. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} DistributionParameters ## ## Fitted distribution parameters ## ## A cell array with one row per class and one column per predictor. ## @code{DistributionParameters@{k,j@}} holds the parameters fitted to ## predictor @math{j} within class @math{k}: a two element column vector, ## the mean and the standard deviation, for a @qcode{'normal'} predictor, ## and a @code{prob.KernelDistribution} object for a @qcode{'kernel'} one. ## This property is read-only. ## ## @end deftp DistributionParameters = {}; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} CategoricalLevels ## ## Levels of the categorical predictors ## ## A cell array with one entry per predictor, holding the distinct levels ## of each categorical predictor and empty for every other. This property ## is read-only. ## ## @end deftp CategoricalLevels = {}; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} Kernel ## ## Kernel smoothing functions ## ## A cell array with one entry per predictor naming the smoothing kernel ## used by a @qcode{'kernel'} predictor, and empty for every other. This ## property is read-only. ## ## @end deftp Kernel = {}; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} Support ## ## Kernel smoothing supports ## ## A cell array with one entry per predictor giving the support of a ## @qcode{'kernel'} predictor's density, and empty for every other. This ## property is read-only. ## ## @end deftp Support = {}; ## -*- texinfo -*- ## @deftp {ClassificationNaiveBayes} {property} Width ## ## Kernel smoothing bandwidths ## ## A numeric matrix with one row per class and one column per predictor, ## giving the bandwidth of each @qcode{'kernel'} predictor's density, and ## empty when no predictor uses one. This property is read-only. ## ## @end deftp Width = []; endproperties ## Readable by the compact counterpart, which copies it, and kept out of the ## documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) ## The parsed ScoreTransform, applied to the posterior by predict. STfun = []; endproperties ## Set methods for the properties a user may assign after fitting. methods (Hidden) function this = set.Cost (this, Cost) [C, errmsg] = costMatrix (Cost, this.ClassNames); if (! isempty (errmsg)) error ('ClassificationNaiveBayes.Cost: %s', errmsg); endif this.Cost = C; endfunction function this = set.Prior (this, Prior) P = Prior; if (isstruct (P)) P = priorFromStruct (P, this.ClassNames, ... 'ClassificationNaiveBayes.Prior'); elseif (ischar (P)) if (strcmpi (P, 'uniform')) P = ones (1, rows (this.Cost)) / rows (this.Cost); elseif (! strcmpi (P, 'empirical')) error (strcat ("ClassificationNaiveBayes.Prior: a character", ... " vector must be 'empirical' or 'uniform'.")); else return; # 'empirical' after fitting is what is already stored endif endif if (! (isnumeric (P) && isvector (P) && isreal (P))) error (strcat ("ClassificationNaiveBayes.Prior: must be a real", ... " numeric vector, a structure, 'empirical', or", ... " 'uniform'.")); endif if (numel (P) != rows (this.Cost)) error (strcat ("ClassificationNaiveBayes.Prior: must have one", ... " element per class.")); endif if (any (P < 0) || ! (sum (P) > 0)) error (strcat ("ClassificationNaiveBayes.Prior: must be", ... " nonnegative and must not be all zero.")); endif this.Prior = P(:)' / sum (P); ## The weights follow the prior, so reassigning one re-derives the other. if (! isempty (this.Y)) gY = labelIndices (this.ClassNames, this.Y); keep = gY > 0; this.W = priorWeights (this.Prior, gY(keep), sum (keep)); endif endfunction function this = set.ScoreTransform (this, val) [f, st] = parseScoreTransform (val, 'ClassificationNaiveBayes'); this.ScoreTransform = st; this.STfun = f; endfunction function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction function disp (this) fprintf ('\n ClassificationNaiveBayes\n\n'); fprintf ('%22s: %s\n', 'ResponseName', this.ResponseName); fprintf ('%22s: %s\n', 'CategoricalPredictors', ... mat2str (this.CategoricalPredictors)); fprintf ('%22s: %s\n', 'ClassNames', classNameListing (this.ClassNames)); fprintf ('%22s: %s\n', 'ScoreTransform', this.ScoreTransform); fprintf ('%22s: %d\n', 'NumObservations', this.NumObservations); fprintf ('%22s: %s\n', 'DistributionNames', ... classNameListing (this.DistributionNames)); fprintf ('%22s: {%dx%d cell}\n', 'DistributionParameters', ... size (this.DistributionParameters)); fprintf ('\n'); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {ClassificationNaiveBayes} {@var{obj} =} ClassificationNaiveBayes (@var{X}, @var{Y}) ## @deftypefnx {ClassificationNaiveBayes} {@var{obj} =} ClassificationNaiveBayes (@dots{}, @var{name}, @var{value}) ## ## Create a @code{ClassificationNaiveBayes} object. ## ## @code{@var{obj} = ClassificationNaiveBayes (@var{X}, @var{Y})} fits a ## naive Bayes classifier to the predictor data @var{X} and the class ## labels @var{Y}. The supported @qcode{Name}/@qcode{Value} pairs are ## those of @code{fitcnb}, which is the documented way to reach this ## constructor. ## ## @seealso{fitcnb} ## @end deftypefn function this = ClassificationNaiveBayes (X, Y, varargin) ## Check for appropriate number of input arguments if (nargin < 2) error ("ClassificationNaiveBayes: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationNaiveBayes: Name-Value", ... " arguments must be in pairs.")); endif ## Validate X if (! (isnumeric (X) && isreal (X) && ismatrix (X) && ! isempty (X))) error (strcat ("ClassificationNaiveBayes: X must be a", ... " non-empty real numeric matrix.")); endif ## Check X and Y have the same number of observations if (rows (X) != rows (Y)) error (strcat ("ClassificationNaiveBayes: number of rows in", ... " X and Y must be equal.")); endif nPred = columns (X); ## Set default values before parsing optional parameters CatPreds = []; ClassNames = []; Cost = []; DistNames = []; Kernel = []; PredictorNames = {}; Prior = 'empirical'; ResponseName = 'Y'; ScoreTransform = 'none'; Support = []; Width = []; ## Parse optional parameters while (numel (varargin) > 0) switch (lower (varargin{1})) case 'predictornames' PredictorNames = varargin{2}; if (! iscellstr (PredictorNames)) error (strcat ("ClassificationNaiveBayes: 'PredictorNames'", ... " must be supplied as a cellstring array.")); elseif (numel (PredictorNames) != nPred) error (strcat ("ClassificationNaiveBayes: 'PredictorNames'", ... " must equal the number of columns in X.")); endif case 'responsename' ResponseName = varargin{2}; if (! (ischar (ResponseName) && isrow (ResponseName))) error (strcat ("ClassificationNaiveBayes: 'ResponseName'", ... " must be a character vector.")); endif case 'classnames' ClassNames = varargin{2}; if (! (iscellstr (ClassNames) || isnumeric (ClassNames) || islogical (ClassNames) || ischar (ClassNames))) error (strcat ("ClassificationNaiveBayes: 'ClassNames'", ... " must be a cell array of character vectors,", ... " a logical vector, a numeric vector, or a", ... " character array.")); endif case 'prior' Prior = varargin{2}; case 'cost' Cost = varargin{2}; case 'scoretransform' ScoreTransform = varargin{2}; case 'categoricalpredictors' CatPreds = varargin{2}; case 'distributionnames' DistNames = varargin{2}; case 'kernel' Kernel = varargin{2}; case 'support' Support = varargin{2}; case 'width' Width = varargin{2}; otherwise error (strcat ("ClassificationNaiveBayes: invalid parameter", ... sprintf (" name '%s'.", varargin{1}))); endswitch varargin(1:2) = []; endwhile ## Resolve the categorical predictors and the distribution of every ## predictor. The two are tied: naming a predictor categorical makes ## it multivariate multinomial unless a distribution is named for it, ## and naming that distribution makes the predictor categorical, which ## MATLAB warns about rather than silently accepting. catidx = nbCategorical (CatPreds, nPred, 'ClassificationNaiveBayes'); if (isempty (DistNames) && ! isempty (catidx)) DistNames = repmat ({'normal'}, 1, nPred); DistNames(catidx) = {'mvmn'}; endif D = nbDistNames (DistNames, nPred, 'ClassificationNaiveBayes'); if (iscell (D)) mvidx = find (strcmp (D, 'mvmn')); if (! all (ismember (mvidx, catidx))) warning (strcat ("ClassificationNaiveBayes: the 'mvmn'", ... " distribution was named for a predictor that is", ... " not in 'CategoricalPredictors', which is", ... " updated to include every 'mvmn' predictor.")); catidx = sort (unique ([catidx, mvidx])); endif endif this.DistributionNames = D; this.CategoricalPredictors = catidx; ## Store the data as supplied, then drop the rows that are not complete this.X = X; this.Y = Y; ok = ! any (isnan (X), 2); if (! all (ok)) this.RowsUsed = ok; endif Xf = X(ok, :); Yf = Y(ok, :); if (isempty (Xf)) error (strcat ("ClassificationNaiveBayes: no observation is", ... " free of missing values.")); endif ## Resolve the classes. Naming them keeps only those observations, so ## the rows of the others are dropped exactly as a missing value is. if (isempty (ClassNames)) C = uniqueLabels (Yf); else C = ClassNames; if (ischar (C) && isrow (C)) C = cellstr (C); endif ## The classes are held one per row, whichever orientation they were ## given in: every per-class property is counted by rows. if (! ischar (C)) C = C(:); endif if (! labelsKnown (C, uniqueLabels (Yf))) error (strcat ("ClassificationNaiveBayes: not all 'ClassNames'", ... " are present in Y.")); endif endif ## A label outside the classes indexes as zero. That is a fault only ## when the classes came from the response itself; naming a subset of ## them is a request to keep those observations and drop the rest. [gY, errmsg] = labelIndices (C, Yf); if (! isempty (errmsg) && isempty (ClassNames)) error ('ClassificationNaiveBayes: %s', errmsg); endif inC = gY > 0; if (! all (inC)) Xf = Xf(inC, :); Yf = Yf(inC, :); gY = gY(inC); ok(ok) = inC; this.RowsUsed = ok; if (isempty (Xf)) error (strcat ("ClassificationNaiveBayes: no observation", ... " belongs to the named classes.")); endif endif this.ClassNames = C; nObs = rows (Xf); nCls = rows (C); this.NumObservations = nObs; this.ResponseName = ResponseName; if (isempty (PredictorNames)) PredictorNames = arrayfun (@(k) sprintf ('x%d', k), 1:nPred, ... 'UniformOutput', false); endif this.PredictorNames = PredictorNames; this.ExpandedPredictorNames = PredictorNames; ## Cost first: the Prior set method reads its size to count the classes if (isempty (Cost)) this.Cost = ones (nCls) - eye (nCls); else this.Cost = Cost; endif ## Prior, and the observation weights that follow from it if (ischar (Prior) && strcmpi (Prior, 'empirical')) this.Prior = accumarray (gY, 1, [nCls, 1])' / nObs; elseif (ischar (Prior) && strcmpi (Prior, 'uniform')) this.Prior = ones (1, nCls) / nCls; else this.Prior = Prior; endif this.ScoreTransform = ScoreTransform; ## Fit one density per class and per predictor [this.DistributionParameters, this.Kernel, this.Support, ... this.Width, this.CategoricalLevels] = ... nbFit (Xf, gY, nCls, this.DistributionNames, this.W, ... Kernel, Support, Width, 'ClassificationNaiveBayes', ... this.ClassNames, this.PredictorNames); ## The arguments as they were given. The fields are assigned one at a ## time: a cell handed to struct() would make a structure array of its ## elements rather than one structure holding the cell. mp = struct (); if (isempty (DistNames)) mp.DistributionNames = 'normal'; else mp.DistributionNames = DistNames; endif ## The kernel settings carry their defaults only where a kernel density ## was actually asked for, and stay empty otherwise. if (iscell (this.DistributionNames) && any (strcmp (this.DistributionNames, 'kernel'))) mp.Kernel = nbGivenOr (Kernel, 'normal'); mp.Support = nbGivenOr (Support, 'unbounded'); mp.Width = nbGivenOr (Width, NaN); mp.StandardizeData = 0; else mp.Kernel = []; mp.Support = []; mp.Width = []; mp.StandardizeData = []; endif mp.Version = 1; mp.Method = 'NaiveBayes'; mp.Type = 'classification'; this.ModelParameters = mp; endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNaiveBayes} {@var{label} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {ClassificationNaiveBayes} {[@var{label}, @var{score}, @var{cost}] =} predict (@var{obj}, @var{XC}) ## ## Classify new data with a trained @code{ClassificationNaiveBayes} object. ## ## @code{@var{label} = predict (@var{obj}, @var{XC})} returns the predicted ## class label for each row of @var{XC}, which must have as many columns as ## the predictor data the model was fitted on. ## ## @code{[@var{label}, @var{score}, @var{cost}] = predict (@var{obj}, ## @var{XC})} also returns @var{score}, the posterior probability of each ## class, and @var{cost}, the expected misclassification cost of assigning ## each observation to each class. The label of an observation is the ## class of least expected cost. ## ## @end deftypefn function [label, score, cost] = predict (this, XC) if (nargin < 2) error ("ClassificationNaiveBayes.predict: too few input arguments."); endif if (isempty (XC)) error ("ClassificationNaiveBayes.predict: XC is empty."); endif if (! (isnumeric (XC) && isreal (XC) && ismatrix (XC))) error (strcat ("ClassificationNaiveBayes.predict: XC must be a", ... " real numeric matrix.")); endif if (columns (this.X) != columns (XC)) error (strcat ("ClassificationNaiveBayes.predict: XC must have", ... " the same number of predictors as the trained", ... " model.")); endif nCls = numel (this.Prior); ## Work in logs: the product over predictors underflows for even a ## moderate number of them, and the posterior only needs the differences. logscore = nbLogLik (XC, this.DistributionNames, ... this.DistributionParameters, nCls, ... this.CategoricalLevels); logscore = logscore + log (this.Prior); ## Subtracting the row maximum keeps a well separated observation a ## posterior rather than a ratio of two underflowed zeros. ## An observation no class can account for, a categorical level the ## model never saw among them, leaves the likelihood at -Inf for every ## class alike. Its posterior is the prior: what remains when the data ## says nothing. Measured against R2024a, which returns the prior and ## not a uniform distribution, and does so for the whole observation ## even when its other predictors are perfectly informative. noinfo = all (logscore == -Inf, 2); logscore = logscore - max (logscore, [], 2); score = exp (logscore); score = score ./ sum (score, 2); score(isnan (score)) = 0; if (any (noinfo)) score(noinfo,:) = repmat (this.Prior, sum (noinfo), 1); endif ## Expected misclassification cost, and the label of least cost cost = score * this.Cost; [~, minIdx] = min (cost, [], 2); label = labelsFromIndex (this.ClassNames, minIdx); ## The transform is applied once, to the assembled posterior score = this.STfun (score); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNaiveBayes} {@var{CVMdl} =} crossval (@var{obj}) ## @deftypefnx {ClassificationNaiveBayes} {@var{CVMdl} =} crossval (@dots{}, @var{name}, @var{value}) ## ## Cross-validate a trained naive Bayes model. ## ## @code{@var{CVMdl} = crossval (@var{obj})} partitions the training data ## into ten folds, or into as many folds as there are observations when ## there are fewer than ten, refits the model on each fold's training part ## and returns a @code{ClassificationPartitionedModel}. ## ## @code{@var{CVMdl} = crossval (@dots{}, @var{name}, @var{value})} takes ## exactly one of @qcode{'KFold'}, @qcode{'Holdout'}, @qcode{'Leaveout'} ## or @qcode{'CVPartition'}. ## ## @seealso{ClassificationPartitionedModel, cvpartition} ## @end deftypefn function CVMdl = crossval (this, varargin) if (nargin < 1) error (strcat ("ClassificationNaiveBayes.crossval: too few", ... " input arguments.")); endif if (numel (varargin) == 1) error (strcat ("ClassificationNaiveBayes.crossval: Name-Value", ... " arguments must be in pairs.")); elseif (numel (varargin) > 2) error (strcat ("ClassificationNaiveBayes.crossval: specify only", ... " one of the optional Name-Value paired arguments.")); endif if (this.NumObservations < 10) numFolds = this.NumObservations; else numFolds = 10; endif Holdout = []; Leaveout = 'off'; CVPartition = []; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'kfold' numFolds = varargin{2}; if (! (isnumeric (numFolds) && isscalar (numFolds) && (numFolds == fix (numFolds)) && numFolds > 1)) error (strcat ("ClassificationNaiveBayes.crossval: 'KFold'", ... " must be an integer value greater than 1.")); endif case 'holdout' Holdout = varargin{2}; if (! (isnumeric (Holdout) && isscalar (Holdout) && Holdout > 0 && Holdout < 1)) error (strcat ("ClassificationNaiveBayes.crossval:", ... " 'Holdout' must be a numeric value between", ... " 0 and 1.")); endif case 'leaveout' Leaveout = varargin{2}; if (! (ischar (Leaveout) && any (strcmpi (Leaveout, {'on', 'off'})))) error (strcat ("ClassificationNaiveBayes.crossval:", ... " 'Leaveout' must be either 'on' or 'off'.")); endif case 'cvpartition' CVPartition = varargin{2}; if (! (isa (CVPartition, 'cvpartition'))) error (strcat ("ClassificationNaiveBayes.crossval:", ... " 'CVPartition' must be a 'cvpartition'", ... " object.")); endif otherwise error (strcat ("ClassificationNaiveBayes.crossval: invalid", ... " parameter name in optional paired arguments.")); endswitch varargin(1:2) = []; endwhile ## The partition covers the observations actually trained on: a row ## dropped for a missing value is not one the folds can use. The ## response is passed rather than a count so the folds stay stratified. [~, Yused] = nbTrainX (this); if (! isempty (CVPartition)) partition = CVPartition; elseif (! isempty (Holdout)) partition = cvpartition (Yused, 'Holdout', Holdout); elseif (strcmpi (Leaveout, 'on')) partition = cvpartition (numel (Yused), 'LeaveOut'); else partition = cvpartition (Yused, 'KFold', numFolds); endif CVMdl = ClassificationPartitionedModel (this, partition); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNaiveBayes} {@var{CMdl} =} compact (@var{obj}) ## ## Drop the training data from a trained model. ## ## @code{@var{CMdl} = compact (@var{obj})} returns a ## @code{CompactClassificationNaiveBayes} object carrying the fitted ## densities and everything @code{predict} needs, but not the observations ## the model was fitted on. It classifies new data identically and is far ## smaller to keep or to ship. ## ## @seealso{CompactClassificationNaiveBayes} ## @end deftypefn function CMdl = compact (this) CMdl = CompactClassificationNaiveBayes (this); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNaiveBayes} {@var{m} =} margin (@var{obj}, @var{X}, @var{Y}) ## ## Classification margin on new data. ## ## @code{@var{m} = margin (@var{obj}, @var{X}, @var{Y})} returns one margin ## per observation: the posterior the model gives the observation's true ## class, less the largest posterior it gives any other class. A positive ## margin means the observation is classified correctly, and a larger one ## means it is classified more confidently. ## ## @end deftypefn function m = margin (this, X, Y) if (nargin < 3) error ("ClassificationNaiveBayes.margin: too few input arguments."); endif [gY, errmsg] = labelIndices (this.ClassNames, Y); if (! isempty (errmsg)) error ("ClassificationNaiveBayes.margin: %s", errmsg); endif if (rows (X) != numel (gY)) error (strcat ("ClassificationNaiveBayes.margin: number of rows", ... " in X and Y must be equal.")); endif [~, s] = predict (this, X); m = marginsOf (s, gY, 1); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNaiveBayes} {@var{e} =} edge (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationNaiveBayes} {@var{e} =} edge (@dots{}, @qcode{'Weights'}, @var{w}) ## ## Classification edge on new data. ## ## @code{@var{e} = edge (@var{obj}, @var{X}, @var{Y})} returns the weighted ## mean of the margins, a single number summarising how confidently the ## model classifies the data. ## ## The weights are normalized within each class to that class's prior ## before they are applied. ## ## @end deftypefn function e = edge (this, X, Y, varargin) if (nargin < 3) error ("ClassificationNaiveBayes.edge: too few input arguments."); endif W = edgeWeights (varargin, Y, this.ClassNames, this.Prior, ... 'ClassificationNaiveBayes', 'edge'); e = sum (W .* margin (this, X, Y)); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNaiveBayes} {@var{l} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationNaiveBayes} {@var{l} =} loss (@dots{}, @var{name}, @var{value}) ## ## Classification loss on new data. ## ## @code{@var{l} = loss (@var{obj}, @var{X}, @var{Y})} returns the minimum ## expected misclassification cost. ## ## @code{@var{l} = loss (@dots{}, @var{name}, @var{value})} takes the ## following options. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'LossFun'} @tab One of @qcode{'binodeviance'}, ## @qcode{'classifcost'}, @qcode{'classiferror'}, @qcode{'exponential'}, ## @qcode{'hinge'}, @qcode{'logit'}, @qcode{'mincost'} (default) or ## @qcode{'quadratic'}. ## ## @item @qcode{'Weights'} @tab A numeric vector of observation weights, ## one per row of @var{X}. ## ## @end multitable ## ## @end deftypefn function l = loss (this, X, Y, varargin) if (nargin < 3) error ("ClassificationNaiveBayes.loss: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationNaiveBayes.loss: name-value", ... " arguments must be in pairs.")); endif LossFun = 'mincost'; Weights = []; lf_opt = {'binodeviance', 'classifcost', 'classiferror', ... 'exponential', 'hinge', 'logit', 'mincost', 'quadratic'}; while (numel (varargin) > 0) Value = varargin{2}; switch (tolower (varargin{1})) case 'lossfun' if (! (ischar (Value) && any (strcmpi (Value, lf_opt)))) error (strcat ("ClassificationNaiveBayes.loss: invalid", ... " loss function.")); endif LossFun = tolower (Value); case 'weights' if (! (isnumeric (Value) && isvector (Value))) error ("ClassificationNaiveBayes.loss: invalid 'Weights'."); endif Weights = Value; otherwise error (strcat ("ClassificationNaiveBayes.loss: invalid", ... " parameter name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile [gY, errmsg] = labelIndices (this.ClassNames, Y); if (! isempty (errmsg)) error ("ClassificationNaiveBayes.loss: %s", errmsg); endif if (rows (X) != numel (gY)) error (strcat ("ClassificationNaiveBayes.loss: number of rows in", ... " X and Y must be equal.")); endif if (isempty (Weights)) w = ones (numel (gY), 1); else w = Weights(:); if (numel (w) != numel (gY)) error (strcat ("ClassificationNaiveBayes.loss: 'Weights' must", ... " have one element per observation.")); endif endif ## The weights are normalized within each class to that class's prior, ## so that a loss and an edge weight the classes the same way. w = priorNormalize (w, gY, this.Prior); [~, s] = predict (this, X); l = classificationLoss (LossFun, s, gY, w, this.Cost); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNaiveBayes} {@var{lp} =} logp (@var{obj}, @var{X}) ## ## Log unconditional probability density of new data. ## ## @code{@var{lp} = logp (@var{obj}, @var{X})} returns one value per ## observation, the logarithm of its density under the fitted model taken ## over all the classes, each weighted by its prior. A markedly low value ## marks an observation the model finds unlike anything it was trained on, ## whatever class it would be assigned to. ## ## @end deftypefn function lp = logp (this, X) if (nargin < 2) error ("ClassificationNaiveBayes.logp: too few input arguments."); endif if (isempty (X)) error ("ClassificationNaiveBayes.logp: X is empty."); endif if (columns (this.X) != columns (X)) error (strcat ("ClassificationNaiveBayes.logp: X must have the", ... " same number of predictors as the trained model.")); endif nCls = numel (this.Prior); L = nbLogLik (X, this.DistributionNames, ... this.DistributionParameters, nCls, ... this.CategoricalLevels); L = L + log (this.Prior); ## Sum the classes in logs: the largest term is factored out so that a ## density small enough to underflow still contributes its logarithm. ## Factoring out the largest term keeps a density small enough to ## underflow contributing its logarithm. Where every class is ## impossible the largest term is -Inf and the factoring is 0/0, so the ## answer is written directly: the density really is zero there. Lmax = max (L, [], 2); lp = Lmax + log (sum (exp (L - Lmax), 2)); lp(Lmax == -Inf) = -Inf; ## predict skips a missing predictor and classifies on the rest, but a ## density is not defined for an observation that is not fully observed, ## so this reports NaN where predict reports a class. R2024a does the ## same. lp(any (isnan (X), 2)) = NaN; endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNaiveBayes} {@var{label} =} resubPredict (@var{obj}) ## @deftypefnx {ClassificationNaiveBayes} {[@var{label}, @var{score}, @var{cost}] =} resubPredict (@var{obj}) ## ## Classify the training data with the trained model. ## ## The same as calling @code{predict} on the data the model was fitted on, ## with the rows that were dropped for missing values left out. ## ## @end deftypefn function [label, score, cost] = resubPredict (this) if (nargin < 1) error (strcat ("ClassificationNaiveBayes.resubPredict:", ... " too few input arguments.")); endif [label, score, cost] = predict (this, nbTrainX (this)); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNaiveBayes} {@var{m} =} resubMargin (@var{obj}) ## ## Classification margin on the training data. ## ## @end deftypefn function m = resubMargin (this) if (nargin < 1) error (strcat ("ClassificationNaiveBayes.resubMargin:", ... " too few input arguments.")); endif [X, Y] = nbTrainX (this); m = margin (this, X, Y); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNaiveBayes} {@var{e} =} resubEdge (@var{obj}) ## ## Classification edge on the training data. ## ## @end deftypefn function e = resubEdge (this) if (nargin < 1) error (strcat ("ClassificationNaiveBayes.resubEdge:", ... " too few input arguments.")); endif [X, Y] = nbTrainX (this); e = edge (this, X, Y); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNaiveBayes} {@var{l} =} resubLoss (@var{obj}) ## @deftypefnx {ClassificationNaiveBayes} {@var{l} =} resubLoss (@dots{}, @var{name}, @var{value}) ## ## Classification loss on the training data. ## ## Takes the same options as @code{loss}. ## ## @end deftypefn function l = resubLoss (this, varargin) if (nargin < 1) error (strcat ("ClassificationNaiveBayes.resubLoss:", ... " too few input arguments.")); endif [X, Y] = nbTrainX (this); l = loss (this, X, Y, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNaiveBayes} {} savemodel (@var{obj}, @var{filename}) ## ## Save a ClassificationNaiveBayes object. ## ## @code{savemodel (@var{obj}, @var{filename})} saves each property of a ## ClassificationNaiveBayes object into an Octave binary file, the name of ## which is specified in @var{filename}, along with an extra variable, which ## defines the type classification object these variables constitute. Use ## @code{loadmodel} in order to load a classification object into Octave's ## workspace. ## ## @seealso{loadmodel, fitcnb, ClassificationNaiveBayes} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error (strcat ("ClassificationNaiveBayes.savemodel:", ... " too few input arguments.")); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error (strcat ("ClassificationNaiveBayes.savemodel:", ... " FNAME must be a character vector.")); endif ## Generate variable for class name classdef_name = 'ClassificationNaiveBayes'; ## Create variables from model properties X = this.X; Y = this.Y; RowsUsed = this.RowsUsed; W = this.W; ModelParameters = this.ModelParameters; NumObservations = this.NumObservations; BinEdges = this.BinEdges; PredictorNames = this.PredictorNames; CategoricalPredictors = this.CategoricalPredictors; ResponseName = this.ResponseName; ExpandedPredictorNames = this.ExpandedPredictorNames; ClassNames = this.ClassNames; Prior = this.Prior; Cost = this.Cost; ScoreTransform = this.ScoreTransform; DistributionNames = this.DistributionNames; Mu = this.Mu; Sigma = this.Sigma; CategoricalLevels = this.CategoricalLevels; ## A kernel predictor's density is a classdef object, which Octave's ## save cannot serialize, so it goes out as the sample it was fitted to ## and load_model rebuilds it from that and the recorded bandwidth. DistributionParameters = nbKernelPack (this.DistributionParameters, ... this.DistributionNames); Kernel = this.Kernel; Support = this.Support; Width = this.Width; STfun = this.STfun; ## Save classdef name and all model properties as individual variables HyperparameterOptimizationResults = this.HyperparameterOptimizationResults; save ('-binary', fname, 'classdef_name', 'X', 'Y', 'RowsUsed', 'W', ... 'ModelParameters', 'NumObservations', 'BinEdges', ... 'PredictorNames', 'CategoricalPredictors', 'ResponseName', ... 'ExpandedPredictorNames', 'ClassNames', 'Prior', 'Cost', ... 'ScoreTransform', 'DistributionNames', 'Mu', 'Sigma', ... 'DistributionParameters', 'CategoricalLevels', 'Kernel', ... 'Support', 'Width', 'STfun', ... 'HyperparameterOptimizationResults'); endfunction endmethods methods (Static, Hidden) function mdl = load_model (filename, data) ## The smallest fit the class accepts, filled property by property ## below. Two observations per class give every distribution something ## to estimate from, and nothing of the stub survives the copy. mdl = ClassificationNaiveBayes ([0; 1; 2; 3], [1; 1; 2; 2]); ## Copy the saved data into the object. Iterate over what was saved ## rather than over fieldnames (mdl): a private property such as STfun ## is written out by savemodel but is not reported by fieldnames, so ## comparing the two sets could never match and every load failed. ## Assignment is legal here because this is a method of the class ## itself. names = fieldnames (data); ## These three are assigned once everything else is in place, and in ## this order rather than the file's. Cost comes before Prior because ## set.Prior counts the classes by the rows of Cost and not by ## ClassNames, so a prior arriving first is measured against the stub's. order = {'Cost', 'Prior', 'ScoreTransform'}; late = ismember (names, order); tail = order(ismember (order, names)); names = [names(! late); tail(:)]; for i = 1:numel (names) try mdl.(names{i}) = data.(names{i}); catch msg = 'ClassificationNaiveBayes.load_model: invalid model in ''%s''.'; error (msg, filename); end_try_catch endfor mdl.DistributionParameters = nbKernelUnpack ( ... mdl.DistributionParameters, mdl.DistributionNames, ... mdl.Kernel, mdl.Support, mdl.Width); endfunction endmethods endclassdef ## Tests %!test # MATLAB parity: the surface a default fit reports %! load fisheriris %! Mdl = fitcnb (meas, species); %! assert_equal (class (Mdl), 'ClassificationNaiveBayes'); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (Mdl.ClassNames, unique (species)); %! assert_equal (Mdl.Prior, [1/3, 1/3, 1/3], 1e-15); %! assert_equal (Mdl.Cost, [0, 1, 1; 1, 0, 1; 1, 1, 0]); %! assert_equal (Mdl.ResponseName, 'Y'); %! assert_equal (Mdl.PredictorNames, {'x1', 'x2', 'x3', 'x4'}); %! assert_equal (Mdl.ExpandedPredictorNames, {'x1', 'x2', 'x3', 'x4'}); %! assert_equal (Mdl.DistributionNames, {'normal', 'normal', 'normal', 'normal'}); %! assert_equal (Mdl.ScoreTransform, 'none'); %! assert_equal (Mdl.CategoricalPredictors, []); %! assert_equal (Mdl.RowsUsed, []); ## Mu and Sigma are the standardization parameters and stay empty, where the ## class conditional ones live in DistributionParameters. BinEdges is a cell ## rather than an empty matrix, as code reaching into it with cellfun needs. %!test # MATLAB parity: the properties a normal-distribution fit leaves unset %! load fisheriris %! Mdl = fitcnb (meas, species); %! assert_equal (Mdl.Mu, []); %! assert_equal (Mdl.Sigma, []); %! assert_equal (class (Mdl.BinEdges), 'cell'); %! assert_equal (Mdl.BinEdges, {}); %! assert_equal (Mdl.Width, NaN (3, 4)); %! assert_equal (Mdl.CategoricalLevels, cell (1, 4)); %!test # MATLAB parity: the fitted normal parameters and the weights %! load fisheriris %! Mdl = fitcnb (meas, species); %! assert_equal (size (Mdl.DistributionParameters), [3, 4]); %! assert_equal (Mdl.DistributionParameters{1,1}, ... %! [5.005999999999998; 0.352489687213451], 1e-13); %! assert_equal (Mdl.DistributionParameters{2,3}, ... %! [4.260000000000001; 0.469910977239958], 1e-13); %! assert_equal (Mdl.W, repmat (1/150, 150, 1), 1e-15); %! assert_equal (sum (Mdl.W), 1, 1e-14); %!test # MATLAB parity: predict returns the label, posterior and cost %! load fisheriris %! Mdl = fitcnb (meas, species); %! [label, score, cost] = predict (Mdl, meas(1:3,:)); %! assert_equal (label, {'setosa'; 'setosa'; 'setosa'}); %! assert_equal (score, repmat ([1, 0, 0], 3, 1), 1e-12); %! assert_equal (cost, repmat ([0, 1, 1], 3, 1), 1e-12); %!test # MATLAB parity: resubstitution loss, edge and margin %! load fisheriris %! Mdl = fitcnb (meas, species); %! assert_equal (resubLoss (Mdl), 0.04, 1e-14); %! assert_equal (resubEdge (Mdl), 0.894430597464877, 1e-12); %! assert_equal (sum (resubMargin (Mdl)), 134.164589619731402, 1e-10); %! assert_equal (resubMargin (Mdl)(1:5), ones (5, 1), 1e-12); %!test # MATLAB parity: resubPredict agrees with predict on the training data %! load fisheriris %! Mdl = fitcnb (meas, species); %! [rl, rs, rc] = resubPredict (Mdl); %! [pl, ps, pc] = predict (Mdl, meas); %! assert_equal (rl, pl); %! assert_equal (rs, ps); %! assert_equal (rc, pc); %!test # MATLAB parity: every one of the eight loss functions %! load fisheriris %! Mdl = fitcnb (meas, species); %! assert_equal (loss (Mdl, meas, species), 0.04, 1e-14); %! assert_equal (loss (Mdl, meas, species, 'LossFun', 'mincost'), 0.04, 1e-14); %! assert_equal (loss (Mdl, meas, species, 'LossFun', 'classiferror'), ... %! 0.04, 1e-14); %! assert_equal (loss (Mdl, meas, species, 'LossFun', 'classifcost'), ... %! 0.04, 1e-14); %! assert_equal (loss (Mdl, meas, species, 'LossFun', 'binodeviance'), ... %! 0.149545751140664, 1e-12); %! assert_equal (loss (Mdl, meas, species, 'LossFun', 'exponential'), ... %! 0.395395632839880, 1e-12); %! assert_equal (loss (Mdl, meas, species, 'LossFun', 'hinge'), ... %! 0.052784701267562, 1e-12); %! assert_equal (loss (Mdl, meas, species, 'LossFun', 'logit'), ... %! 0.331045591275855, 1e-12); %! assert_equal (loss (Mdl, meas, species, 'LossFun', 'quadratic'), ... %! 0.033005648597277, 1e-12); %!test # MATLAB parity: weighted loss and edge %! load fisheriris %! Mdl = fitcnb (meas, species); %! w = (1:150)' / sum (1:150); %! assert_equal (loss (Mdl, meas, species, 'Weights', w), ... %! 0.037013271417641, 1e-12); %! assert_equal (edge (Mdl, meas, species, 'Weights', w), ... %! 0.898902916457462, 1e-12); %! assert_equal (edge (Mdl, meas, species), 0.894430597464877, 1e-12); %!test # MATLAB parity: logp over all the classes %! load fisheriris %! Mdl = fitcnb (meas, species); %! lp = logp (Mdl, meas); %! assert_equal (numel (lp), 150); %! assert_equal (lp(1), 1.026591235856343, 1e-12); %! assert_equal (lp(5), 0.977098877116533, 1e-12); %! assert_equal (sum (lp), -309.559846128495394, 1e-10); %!test # MATLAB parity: a given prior reweights the observations and the loss %! load fisheriris %! Mdl = fitcnb (meas, species, 'Prior', [0.2, 0.3, 0.5]); %! assert_equal (Mdl.Prior, [0.2, 0.3, 0.5], 1e-15); %! assert_equal (Mdl.W(1), 0.004, 1e-15); %! assert_equal (Mdl.W(51), 0.006, 1e-15); %! assert_equal (Mdl.W(101), 0.010, 1e-15); %! assert_equal (resubLoss (Mdl), 0.054, 1e-14); %! assert_equal (edge (Mdl, meas, species), 0.873545897578786, 1e-12); %!test # MATLAB parity: a uniform prior, and a given cost %! load fisheriris %! Mdl = fitcnb (meas, species, 'Prior', 'uniform'); %! assert_equal (Mdl.Prior, [1/3, 1/3, 1/3], 1e-15); %! Mdl = fitcnb (meas, species, 'Cost', [0, 1, 2; 1, 0, 1; 2, 1, 0]); %! assert_equal (Mdl.Cost, [0, 1, 2; 1, 0, 1; 2, 1, 0]); %!test # MATLAB parity: ScoreTransform is applied to the posterior %! load fisheriris %! Mdl = fitcnb (meas, species, 'ScoreTransform', 'logit'); %! assert_equal (Mdl.ScoreTransform, 'logit'); %! [~, score] = predict (Mdl, meas(1:2,:)); %! assert_equal (score, repmat ([0.731058578630005, 0.5, 0.5], 2, 1), 1e-12); %!test # MATLAB parity: the kernel densities and their default bandwidths %! load fisheriris %! Mdl = fitcnb (meas, species, 'DistributionNames', 'kernel'); %! assert_equal (Mdl.DistributionNames, ... %! {'kernel', 'kernel', 'kernel', 'kernel'}); %! assert_equal (Mdl.Kernel, {'normal', 'normal', 'normal', 'normal'}); %! assert_equal (Mdl.Support, ... %! {'unbounded', 'unbounded', 'unbounded', 'unbounded'}); %! assert_equal (class (Mdl.DistributionParameters{1,1}), ... %! 'prob.KernelDistribution'); %! width = [0.143628884694882, 0.179536105868602, 0.071814442347441, ... %! 0.242194206816745; ... %! 0.251350548216043, 0.143628884694882, 0.251350548216043, ... %! 0.107721663521161; ... %! 0.287257769389764, 0.143628884694882, 0.323164990563484, ... %! 0.143628884694882]; %! assert_equal (Mdl.Width, width, 1e-12); ## The bandwidth belongs to the space the density is smoothed in, so a bounded ## support takes it in the transformed one. %!test # MATLAB parity: an explicit bandwidth, and a positive support %! load fisheriris %! Mdl = fitcnb (meas, species, 'DistributionNames', 'kernel', ... %! 'Kernel', 'triangle', 'Width', 0.5); %! assert_equal (Mdl.Kernel, {'triangle', 'triangle', 'triangle', 'triangle'}); %! assert_equal (Mdl.Width, repmat (0.5, 3, 4), 1e-15); %! x = [0.13; 0.41; 0.22; 1.87; 0.55; 0.09; 2.94; 0.31; 0.68; 0.17; ... %! 0.44; 3.61; 0.26; 0.72; 0.05; 1.13; 0.38; 0.91; 0.19; 4.52]; %! y = [1.02; 1.44; 0.87; 1.19; 2.63; 1.07; 0.95; 1.31; 1.76; 1.12; ... %! 0.99; 1.28; 3.41; 1.05; 1.21; 0.93; 1.38; 1.14; 1.09; 2.02]; %! G = [repmat({'a'}, 20, 1); repmat({'b'}, 20, 1)]; %! Mdl = fitcnb ([x; y], G, 'DistributionNames', 'kernel'); %! assert_equal (Mdl.Width, ... %! [0.237209723894721; 0.138012930266019], 1e-12); %! Mdl = fitcnb ([x; y], G, 'DistributionNames', 'kernel', ... %! 'Support', 'positive'); %! assert_equal (Mdl.Width, ... %! [0.675582146901479; 0.127329555528534], 1e-12); %!test # MATLAB parity: a kernel model classifies as MATLAB does %! x = [0.13; 0.41; 0.22; 1.87; 0.55; 0.09; 2.94; 0.31; 0.68; 0.17; ... %! 0.44; 3.61; 0.26; 0.72; 0.05; 1.13; 0.38; 0.91; 0.19; 4.52]; %! y = [1.02; 1.44; 0.87; 1.19; 2.63; 1.07; 0.95; 1.31; 1.76; 1.12; ... %! 0.99; 1.28; 3.41; 1.05; 1.21; 0.93; 1.38; 1.14; 1.09; 2.02]; %! G = [repmat({'a'}, 20, 1); repmat({'b'}, 20, 1)]; %! Mdl = fitcnb ([x; y], G, 'DistributionNames', 'kernel'); %! [label, score] = predict (Mdl, [0.5; 1.5; 3.0]); %! assert_equal (label, {'a'; 'b'; 'a'}); %! assert_equal (score, [0.991485542298633, 0.008514457701367; ... %! 0.121739189175926, 0.878260810824074; ... %! 0.936549935287367, 0.063450064712633], 1e-12); %! assert_equal (resubLoss (Mdl), 0.075, 1e-14); %!test # MATLAB parity: one distribution per predictor %! load fisheriris %! Mdl = fitcnb (meas, species, 'DistributionNames', ... %! {'normal', 'kernel', 'normal', 'kernel'}); %! assert_equal (Mdl.DistributionNames, ... %! {'normal', 'kernel', 'normal', 'kernel'}); %! assert_equal (class (Mdl.DistributionParameters{1,1}), 'double'); %! assert_equal (class (Mdl.DistributionParameters{1,2}), ... %! 'prob.KernelDistribution'); %! assert_equal (Mdl.DistributionParameters{1,2}.Bandwidth, ... %! 0.179536105868602, 1e-12); ## ModelParameters records the arguments as they were given, where the ## properties of the same name record what they were resolved to. %!test # MATLAB parity: ModelParameters %! load fisheriris %! Mdl = fitcnb (meas, species); %! assert_equal (fieldnames (Mdl.ModelParameters), ... %! {'DistributionNames'; 'Kernel'; 'Support'; 'Width'; ... %! 'StandardizeData'; 'Version'; 'Method'; 'Type'}); %! assert_equal (Mdl.ModelParameters.DistributionNames, 'normal'); %! assert_equal (Mdl.ModelParameters.Kernel, []); %! assert_equal (Mdl.ModelParameters.Method, 'NaiveBayes'); %! assert_equal (Mdl.ModelParameters.Type, 'classification'); %! assert_equal (Mdl.ModelParameters.Version, 1); %! Mdl = fitcnb (meas, species, 'DistributionNames', 'kernel'); %! assert_equal (Mdl.ModelParameters.DistributionNames, 'kernel'); %! assert_equal (Mdl.ModelParameters.Kernel, 'normal'); %! assert_equal (Mdl.ModelParameters.Support, 'unbounded'); %! assert_equal (isnan (Mdl.ModelParameters.Width), true); %! assert_equal (Mdl.ModelParameters.StandardizeData, 0); %!test # naming a subset of the classes keeps only those observations %! load fisheriris %! Mdl = fitcnb (meas, species, 'ClassNames', {'setosa', 'virginica'}); %! assert_equal (Mdl.ClassNames, {'setosa'; 'virginica'}); %! assert_equal (Mdl.NumObservations, 100); %! assert_equal (sum (Mdl.RowsUsed), 100); %! assert_equal (size (Mdl.DistributionParameters), [2, 4]); %! assert_equal (Mdl.Prior, [0.5, 0.5], 1e-15); %!test # a row holding a missing value is dropped, and RowsUsed says so %! load fisheriris %! X = meas; %! X(3,2) = NaN; %! X(77,4) = NaN; %! Mdl = fitcnb (X, species); %! assert_equal (Mdl.NumObservations, 148); %! assert_equal (sum (Mdl.RowsUsed), 148); %! assert_equal (Mdl.RowsUsed([3, 77]), [false; false]); %! assert_equal (size (Mdl.X), [150, 4]); %!test # PredictorNames and ResponseName are carried through %! load fisheriris %! Mdl = fitcnb (meas, species, 'PredictorNames', {'a', 'b', 'c', 'd'}, ... %! 'ResponseName', 'flower'); %! assert_equal (Mdl.PredictorNames, {'a', 'b', 'c', 'd'}); %! assert_equal (Mdl.ExpandedPredictorNames, {'a', 'b', 'c', 'd'}); %! assert_equal (Mdl.ResponseName, 'flower'); %!test # Cost and Prior may be assigned after fitting, and W follows the prior %! load fisheriris %! Mdl = fitcnb (meas, species); %! Mdl.Cost = [0, 2, 2; 2, 0, 2; 2, 2, 0]; %! assert_equal (Mdl.Cost, [0, 2, 2; 2, 0, 2; 2, 2, 0]); %! Mdl.Prior = [0.2, 0.3, 0.5]; %! assert_equal (Mdl.Prior, [0.2, 0.3, 0.5], 1e-15); %! assert_equal (Mdl.W(1), 0.004, 1e-15); %! assert_equal (Mdl.W(101), 0.010, 1e-15); %!test # a numeric response is classified as a numeric response %! X = [1, 2; 1.1, 2.1; 5, 6; 5.2, 6.1; 0.9, 1.8; 5.1, 6.2]; %! Y = [1; 1; 2; 2; 1; 2]; %! Mdl = fitcnb (X, Y); %! assert_equal (Mdl.ClassNames, [1; 2]); %! assert_equal (predict (Mdl, [1, 2; 5, 6]), [1; 2]); %!test # MATLAB parity: compact classifies identically to the model it came from %! load fisheriris %! Mdl = fitcnb (meas, species); %! CMdl = compact (Mdl); %! assert_equal (class (CMdl), 'CompactClassificationNaiveBayes'); %! [ml, ms] = predict (Mdl, meas); %! [cl, cs] = predict (CMdl, meas); %! assert_equal (cl, ml); %! assert_equal (cs, ms); %! assert_equal (loss (CMdl, meas, species), 0.04, 1e-14); %! assert_equal (edge (CMdl, meas, species), 0.894430597464877, 1e-12); %!test # MATLAB parity: leave-one-out cross-validation %! load fisheriris %! Mdl = fitcnb (meas, species); %! CVMdl = crossval (Mdl, 'Leaveout', 'on'); %! assert_equal (class (CVMdl), 'ClassificationPartitionedModel'); %! assert_equal (CVMdl.KFold, 150); %! assert_equal (class (CVMdl.Trained{1}), 'CompactClassificationNaiveBayes'); %! assert_equal (kfoldLoss (CVMdl), 0.046666666666667, 1e-12); %! assert_equal (sum (strcmp (kfoldPredict (CVMdl), species)), 143); %!test # a k-fold partition holds k folds, each a model of its own %! load fisheriris %! CVMdl = crossval (fitcnb (meas, species), 'KFold', 3); %! assert_equal (CVMdl.KFold, 3); %! assert_equal (numel (CVMdl.Trained), 3); %! assert_equal (class (CVMdl.Trained{3}), 'CompactClassificationNaiveBayes'); %! assert_equal (CVMdl.CrossValidatedModel, 'NaiveBayes'); ## The categorical distributions. Every level's count is raised by one before ## it is normalized, so a level a class never took stays possible. %!test # MATLAB parity: mvmn fits a smoothed distribution over the levels %! X = [1, 2; 1, 3; 2, 2; 2, 3; 1, 2; 3, 1; 3, 3; 2, 1; 1, 1; 3, 2; ... %! 2, 2; 1, 3; 3, 1; 2, 3; 1, 1; 3, 2; 2, 1; 1, 2; 3, 3; 2, 2]; %! Y = [repmat({'a'}, 10, 1); repmat({'b'}, 10, 1)]; %! Mdl = fitcnb (X, Y, 'DistributionNames', 'mvmn', ... %! 'CategoricalPredictors', [1, 2]); %! assert_equal (Mdl.DistributionNames, {'mvmn', 'mvmn'}); %! assert_equal (Mdl.CategoricalPredictors, [1, 2]); %! assert_equal (Mdl.CategoricalLevels{1}, [1; 2; 3]); %! assert_equal (Mdl.CategoricalLevels{2}, [1; 2; 3]); %! assert_equal (Mdl.DistributionParameters{1,1}, ... %! [0.384615384615385; 0.307692307692308; 0.307692307692308], ... %! 1e-12); %! assert_equal (Mdl.DistributionParameters{2,2}(2), ... %! 0.384615384615385, 1e-12); %! assert_equal (all (isnan (Mdl.Width(:))), true); %! assert_equal (size (Mdl.Width), [2, 2]); %!test # MATLAB parity: an mvmn model classifies as MATLAB does %! X = [1, 2; 1, 3; 2, 2; 2, 3; 1, 2; 3, 1; 3, 3; 2, 1; 1, 1; 3, 2; ... %! 2, 2; 1, 3; 3, 1; 2, 3; 1, 1; 3, 2; 2, 1; 1, 2; 3, 3; 2, 2]; %! Y = [repmat({'a'}, 10, 1); repmat({'b'}, 10, 1)]; %! Mdl = fitcnb (X, Y, 'DistributionNames', 'mvmn', ... %! 'CategoricalPredictors', [1, 2]); %! [label, score] = predict (Mdl, [1, 1; 3, 3; 2, 2]); %! assert_equal (label, {'a'; 'a'; 'b'}); %! assert_equal (score(1,1), 0.555555555555556, 1e-12); %! assert_equal (score(3,2), 0.555555555555556, 1e-12); %! assert_equal (resubLoss (Mdl), 0.45, 1e-14); %! assert_equal (logp (Mdl, [1, 1; 3, 3]), ... %! [-2.239526957026909; -2.357309992683292], 1e-12); %!test # MATLAB parity: a level a class never took keeps a probability %! Z = [1, 2; 1, 3; 1, 2; 1, 3; 1, 2; 2, 1; 2, 3; 2, 1; 2, 3; 2, 2]; %! G = [repmat({'p'}, 5, 1); repmat({'q'}, 5, 1)]; %! Mdl = fitcnb (Z, G, 'DistributionNames', 'mvmn', ... %! 'CategoricalPredictors', [1, 2]); %! assert_equal (Mdl.CategoricalLevels{1}, [1; 2]); %! assert_equal (Mdl.DistributionParameters{1,1}, ... %! [0.857142857142857; 0.142857142857143], 1e-12); %! assert_equal (Mdl.DistributionParameters{2,1}, ... %! [0.142857142857143; 0.857142857142857], 1e-12); %! [label, score] = predict (Mdl, [2, 2; 1, 1]); %! assert_equal (label, {'q'; 'p'}); %! assert_equal (score(1,:), [0.25, 0.75], 1e-12); %!test # MATLAB parity: naming a predictor categorical makes it mvmn %! X = [1, 2; 1, 3; 2, 2; 2, 3; 1, 2; 3, 1; 3, 3; 2, 1; 1, 1; 3, 2; ... %! 2, 2; 1, 3; 3, 1; 2, 3; 1, 1; 3, 2; 2, 1; 1, 2; 3, 3; 2, 2]; %! Y = [repmat({'a'}, 10, 1); repmat({'b'}, 10, 1)]; %! Mdl = fitcnb (X, Y, 'CategoricalPredictors', [1, 2]); %! assert_equal (Mdl.DistributionNames, {'mvmn', 'mvmn'}); %! assert_equal (Mdl.CategoricalPredictors, [1, 2]); %! assert_equal (Mdl.DistributionParameters{1,1}(1), ... %! 0.384615384615385, 1e-12); %!test # MATLAB parity: a normal and a categorical predictor side by side %! X = [1, 2; 1, 3; 2, 2; 2, 3; 1, 2; 3, 1; 3, 3; 2, 1; 1, 1; 3, 2; ... %! 2, 2; 1, 3; 3, 1; 2, 3; 1, 1; 3, 2; 2, 1; 1, 2; 3, 3; 2, 2]; %! Y = [repmat({'a'}, 10, 1); repmat({'b'}, 10, 1)]; %! Mdl = fitcnb (X, Y, 'DistributionNames', {'normal', 'mvmn'}, ... %! 'CategoricalPredictors', 2); %! assert_equal (Mdl.DistributionNames, {'normal', 'mvmn'}); %! assert_equal (isempty (Mdl.CategoricalLevels{1}), true); %! assert_equal (Mdl.CategoricalLevels{2}, [1; 2; 3]); %! assert_equal (Mdl.DistributionParameters{1,1}, ... %! [1.900000000000001; 0.875595035770913], 1e-12); %! assert_equal (Mdl.DistributionParameters{1,2}(2), ... %! 0.384615384615385, 1e-12); ## The multinomial reads a row as token counts and is one distribution over ## the whole predictor vector, so its DistributionNames stays a character ## vector where every other distribution reports one name per predictor. %!test # MATLAB parity: mn over token counts %! C = [2, 0, 1; 1, 3, 0; 0, 1, 4; 3, 1, 0; ... %! 0, 2, 2; 1, 0, 3; 4, 1, 1; 0, 3, 2]; %! L = [repmat({'x'}, 4, 1); repmat({'y'}, 4, 1)]; %! Mdl = fitcnb (C, L, 'DistributionNames', 'mn'); %! assert_equal (Mdl.DistributionNames, 'mn'); %! assert_equal (class (Mdl.DistributionNames), 'char'); %! assert_equal (size (Mdl.DistributionParameters), [2, 3]); %! assert_equal (Mdl.DistributionParameters{1,1}, 0.368421052631579, 1e-12); %! assert_equal (Mdl.DistributionParameters{1,2}, 0.315789473684211, 1e-12); %! assert_equal (Mdl.DistributionParameters{2,3}, 0.409090909090909, 1e-12); %! assert_equal (Mdl.CategoricalPredictors, []); %!test # MATLAB parity: an mn model classifies as MATLAB does %! C = [2, 0, 1; 1, 3, 0; 0, 1, 4; 3, 1, 0; ... %! 0, 2, 2; 1, 0, 3; 4, 1, 1; 0, 3, 2]; %! L = [repmat({'x'}, 4, 1); repmat({'y'}, 4, 1)]; %! Mdl = fitcnb (C, L, 'DistributionNames', 'mn'); %! [label, score] = predict (Mdl, [1, 1, 1; 4, 0, 0]); %! assert_equal (label, {'x'; 'x'}); %! assert_equal (score(1,1), 0.508585484679865, 1e-12); %! assert_equal (score(2,1), 0.769060987976952, 1e-12); %! assert_equal (resubLoss (Mdl), 0.25, 1e-14); ## An observation carrying a level the model never saw tells it nothing, so ## its posterior is the prior. One such predictor decides the whole ## observation, whatever the others say. %!test # MATLAB parity: an unseen level falls back to the prior %! Z = [1, 1; 1, 1; 1, 2; 2, 2; 2, 2; 2, 1; 1, 1; 2, 2]; %! G = [repmat({'p'}, 4, 1); repmat({'q'}, 4, 1)]; %! Mdl = fitcnb (Z, G, 'DistributionNames', 'mvmn', ... %! 'CategoricalPredictors', [1, 2]); %! [~, score] = predict (Mdl, [1, 1]); %! assert_equal (score, [0.666666666666667, 0.333333333333333], 1e-12); %! [~, score, cost] = predict (Mdl, [3, 1]); %! assert_equal (score, [0.5, 0.5], 1e-12); %! assert_equal (cost, [0.5, 0.5], 1e-12); %! [~, score] = predict (Mdl, [3, 3]); %! assert_equal (score, [0.5, 0.5], 1e-12); %! assert_equal (logp (Mdl, [1, 1]), -1.386294361119891, 1e-12); %! assert_equal (logp (Mdl, [3, 1]), -Inf); %!test # MATLAB parity: the fallback is the prior, not a uniform distribution %! Z = [1, 1; 1, 1; 1, 2; 1, 2; 1, 1; 1, 2; 2, 2; 2, 1]; %! G = [repmat({'p'}, 6, 1); repmat({'q'}, 2, 1)]; %! Mdl = fitcnb (Z, G, 'DistributionNames', 'mvmn', ... %! 'CategoricalPredictors', [1, 2]); %! assert_equal (Mdl.Prior, [0.75, 0.25], 1e-15); %! [~, score] = predict (Mdl, [3, 1]); %! assert_equal (score, [0.75, 0.25], 1e-12); %! Mdl = fitcnb (Z, G, 'DistributionNames', 'mvmn', ... %! 'CategoricalPredictors', [1, 2], 'Prior', [0.2, 0.8]); %! [label, score] = predict (Mdl, [3, 1]); %! assert_equal (score, [0.2, 0.8], 1e-12); %! assert_equal (label, {'q'}); %!test # MATLAB parity: an informative predictor cannot rescue the row %! Z = [1, 1; 1, 1; 1, 2; 2, 2; 2, 2; 2, 1; 1, 1; 2, 2]; %! G = [repmat({'p'}, 4, 1); repmat({'q'}, 4, 1)]; %! Mdl = fitcnb (Z, G, 'DistributionNames', {'normal', 'mvmn'}, ... %! 'CategoricalPredictors', 2); %! [~, score] = predict (Mdl, [1, 3]); %! assert_equal (score, [0.5, 0.5], 1e-12); %! assert_equal (logp (Mdl, [1, 3]), -Inf); ## Naming 'mvmn' for a predictor makes it categorical, which MATLAB reports ## rather than doing silently. %!warning ... %! fitcnb ([1, 2; 2, 1; 1, 1; 2, 2], [1; 1; 2; 2], ... %! 'DistributionNames', 'mvmn'); %!test # the warning is not raised when the predictor is already categorical %! Mdl = fitcnb ([1, 2; 2, 1; 1, 1; 2, 2], [1; 1; 2; 2], ... %! 'DistributionNames', 'mvmn', 'CategoricalPredictors', 'all'); %! assert_equal (Mdl.CategoricalPredictors, [1, 2]); %! assert_equal (Mdl.DistributionNames, {'mvmn', 'mvmn'}); ## A fitted model survives savemodel and loadmodel: every property comes back ## as it was and it predicts the same. %!test %! load fisheriris %! Mdl = fitcnb (meas, species); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'ClassificationNaiveBayes'); %! p = properties (Mdl); %! for i = 1:numel (p) %! assert_equal (M2.(p{i}), Mdl.(p{i})); %! endfor %! assert_equal (predict (M2, meas(1:10,:)), predict (Mdl, meas(1:10,:))); ## A kernel predictor stores a classdef object, which Octave's save cannot ## serialize. It is written as the sample and refitted at the recorded ## bandwidth, so the density comes back identical and not merely close. %!test %! load fisheriris %! Mdl = fitcnb (meas, species, 'DistributionNames', 'kernel'); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2.DistributionParameters{1,1}), ... %! 'prob.KernelDistribution'); %! assert_equal (M2.Width, Mdl.Width); %! assert_equal (predict (M2, meas(1:10,:)), predict (Mdl, meas(1:10,:))); %! assert_equal (logp (M2, meas(1:10,:)), logp (Mdl, meas(1:10,:)), 1e-12); ## Mixed distributions, a categorical predictor among them, and a model ## carrying a transform and a non-default cost. %!test %! load fisheriris %! X = [meas, round(meas(:,1))]; %! Mdl = fitcnb (X, species, 'DistributionNames', ... %! {'normal', 'kernel', 'normal', 'kernel', 'mvmn'}, ... %! 'CategoricalPredictors', 5, ... %! 'Cost', [0, 2, 1; 1, 0, 1; 1, 1, 0]); %! Mdl.ScoreTransform = 'logit'; %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.Cost, Mdl.Cost); %! assert_equal (M2.ScoreTransform, 'logit'); %! assert_equal (M2.CategoricalLevels, Mdl.CategoricalLevels); %! [l1, s1] = predict (Mdl, X(1:10,:)); %! [l2, s2] = predict (M2, X(1:10,:)); %! assert_equal (l2, l1); %! assert_equal (s2, s1); %!error ... %! savemodel (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])) %!error ... %! savemodel (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), 1) %!error ... %! savemodel (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), ['ab'; 'cd']) %!error ... %! fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], 'DistributionNames', {'mn', 'normal'}) %!error ... %! fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], 'CategoricalPredictors', 'some') %!error ... %! fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], 'CategoricalPredictors', [true, true, true]) %!error ... %! fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], 'CategoricalPredictors', 5) ## A missing predictor is skipped, not propagated: the predictors are ## conditionally independent given the class, so the others still describe the ## observation. An observation missing every predictor falls back to the ## prior, by the same arithmetic that gives an unseen level the prior. %!test # MATLAB parity: predict skips a NaN predictor %! load fisheriris %! Mdl = fitcnb (meas, species); %! [label, score, cost] = predict (Mdl, [NaN, 3, 1, 0.2]); %! assert_equal (label, {'setosa'}); %! assert_equal (score, [1, 0, 0], 1e-12); %! assert_equal (cost, [0, 1, 1], 1e-12); %! [~, s1] = predict (Mdl, [5.1, 3.5, 1.4, 0.2]); %! [~, s2] = predict (Mdl, [5.1, NaN, 1.4, 0.2]); %! assert_equal (s2, s1, 1e-12); %! [~, s3] = predict (Mdl, [NaN, NaN, NaN, NaN]); %! assert_equal (s3, Mdl.Prior, 1e-12); ## logp reports NaN where predict reports a class: predict can classify on the ## predictors it has, but a density is not defined for a partial observation. %!test # MATLAB parity: logp of an incomplete observation is NaN %! load fisheriris %! Mdl = fitcnb (meas, species); %! assert_equal (isnan (logp (Mdl, [NaN, 3, 1, 0.2])), true); %! assert_equal (isnan (logp (Mdl, [5.1, NaN, 1.4, 0.2])), true); %! assert_equal (logp (Mdl, [5.1, 3.5, 1.4, 0.2]), 1.026591235856343, 1e-12); %!test # MATLAB parity: a row with a missing predictor still counts in a loss %! load fisheriris %! Mdl = fitcnb (meas, species); %! X = [meas; NaN, 3, 1, 0.2]; %! Y = [species; {'setosa'}]; %! assert_equal (loss (Mdl, X, Y), 0.04, 1e-14); %! assert_equal (loss (Mdl, X, Y, 'LossFun', 'classiferror'), 0.04, 1e-14); ## A predictor that does not vary within a class has no normal density to fit. ## MATLAB refuses the combination rather than answering from a distribution it ## never fitted, and so do we. The refusal is per class and predictor, not per ## model: a kernel on the same column fits, which is the caller's way out. %!test # MATLAB parity: a kernel fits data a normal cannot %! X = [1, 2; 2, 3; 3, 4; 10, 20; 10, 25]; %! Y = [1; 1; 1; 2; 2]; %! Mdl = fitcnb (X, Y, 'DistributionNames', 'kernel'); %! assert_equal (class (Mdl), 'ClassificationNaiveBayes'); %! assert_equal (Mdl.Width(2,1), 1, 1e-12); %! Mdl = fitcnb (X, Y, 'DistributionNames', 'mvmn', ... %! 'CategoricalPredictors', [1, 2]); %! assert_equal (Mdl.CategoricalLevels{1}, [1; 2; 3; 10]); ## The row that proves the guard is per combination rather than per model: ## give the degenerate column a kernel and leave the other normal, and the fit ## goes through. Widening the guard to the whole model would fail this. %!test # MATLAB parity: only the degenerate combination is refused %! X = [1, 2; 2, 3; 3, 4; 10, 20; 10, 25]; %! Y = [1; 1; 1; 2; 2]; %! Mdl = fitcnb (X, Y, 'DistributionNames', {'kernel', 'normal'}); %! assert_equal (Mdl.DistributionNames, {'kernel', 'normal'}); %! assert_equal (Mdl.DistributionParameters{2,2}, [22.5; 3.535533905932738], ... %! 1e-12); ## Test input validation %!error ... %! ClassificationNaiveBayes ([1, 2; 2, 3; 3, 4; 4, 5]) %!error ... %! ClassificationNaiveBayes ([1, 2; 2, 3; 3, 4; 4, 5], ones (4, 1), 'Prior') %!error ... %! ClassificationNaiveBayes ('a', ones (4, 1)) %!error ... %! ClassificationNaiveBayes ([1, 2; 2, 3; 3, 4; 4, 5], ones (3, 1)) %!error ... %! ClassificationNaiveBayes ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], 'PredictorNames', 5) %!error ... %! ClassificationNaiveBayes ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], 'PredictorNames', {'a'}) %!error ... %! ClassificationNaiveBayes ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], 'ResponseName', 5) %!error ... %! ClassificationNaiveBayes ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], 'ClassNames', [1, 3]) %!error ... %! ClassificationNaiveBayes ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], 'nope', 1) %!error ... %! ClassificationNaiveBayes ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], ... %! 'DistributionNames', 'poisson') %!error ... %! ClassificationNaiveBayes ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], ... %! 'DistributionNames', {'normal'}) %!error ... %! ClassificationNaiveBayes ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], ... %! 'DistributionNames', 'kernel', 'Kernel', 'cosine') %!error ... %! ClassificationNaiveBayes ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], ... %! 'DistributionNames', 'kernel', 'Width', -1) %!error ... %! predict (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])) %!error ... %! predict (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), []) %!error ... %! predict (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), ones (2, 3)) %!error ... %! loss (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), [1, 2; 2, 3; 3, 4; 4, 5]) %!error ... %! loss (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), [1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], ... %! 'LossFun', 'nope') %!error ... %! loss (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), [1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], ... %! 'Weights', [1, 2]) %!error ... %! margin (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), [1, 2; 2, 3; 3, 4; 4, 5]) %!error ... %! edge (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), [1, 2; 2, 3; 3, 4; 4, 5]) %!error ... %! logp (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), []) %!error ... %! crossval (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), 'KFold', 1) %!error ... %! crossval (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), 'Leaveout', 5) %!error ... %! crossval (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), 'KFold', 2, 'Leaveout', 'on') %!error ... %! fitcnb ([1, 2; 2, 3; 3, 4; 10, 20], [1; 1; 1; 2]) %!error ... %! fitcnb ([1, 2; 2, 3; 3, 4; 10, 20; 10, 25], [1; 1; 1; 2; 2]) %!error ... %! fitcnb ([1, 2; 2, 3; 3, 4; 10, 20; 10, 25], ... %! [{'alpha'}; {'alpha'}; {'alpha'}; {'beta'}; {'beta'}]) %!error ... %! fitcnb ([1, 2; 2, 3; 3, 4; 10, 20; 10, 25], [1; 1; 1; 2; 2], ... %! 'PredictorNames', {'height', 'weight'}) %!error ... %! fitcnb ([5, 2; 5, 3; 5, 4; 10, 20; 11, 25], [1; 1; 1; 2; 2]) ## HyperparameterOptimizationResults is declared for MATLAB compatibility and ## stays empty, this class running no search over its hyperparameters. %!test %! load fisheriris %! Mdl = fitcnb (meas, species); %! assert_equal (isempty (Mdl.HyperparameterOptimizationResults), true); ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = fitcnb (meas, species); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = fitcnb (meas, species); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/ClassificationNeuralNetwork.m000066400000000000000000003544471524624707500301470ustar00rootroot00000000000000## Copyright (C) 2024 Pallav Purbia ## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef ClassificationNeuralNetwork ## -*- texinfo -*- ## @deftp {statistics} ClassificationNeuralNetwork ## ## Neural network classification ## ## The @code{ClassificationNeuralNetwork} class implements a neural network ## classifier object, which can predict responses for new data using the ## @code{predict} method. ## ## Neural network classification is a machine learning method that uses ## interconnected nodes in multiple layers to learn complex patterns in data. ## It processes inputs through hidden layers with activation functions to ## produce classification outputs. ## ## Create a @code{ClassificationNeuralNetwork} object by using the ## @code{fitcnet} function or the class constructor. ## ## @seealso{fitcnet} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} X ## ## Predictor data ## ## A numeric matrix containing the unstandardized predictor data. Each ## column of @var{X} represents one predictor (variable), and each row ## represents one observation. This property is read-only. ## ## @end deftp X = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} Y ## ## Class labels ## ## Specified as a logical or numeric column vector, or as a character array ## or a cell array of character vectors with the same number of rows as the ## predictor data. Each row in @var{Y} is the observed class label for ## the corresponding row in @var{X}. This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} NumObservations ## ## Number of observations ## ## A positive integer value specifying the number of observations in the ## training dataset used for training the ClassificationNeuralNetwork model. ## This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} RowsUsed ## ## Rows used for fitting ## ## A logical column vector with the same length as the observations in the ## original predictor data @var{X}, true for each row that was used for ## fitting the ClassificationNeuralNetwork model. It is empty, @qcode{[]}, ## when every observation was used, so a non-empty value means that rows ## holding missing values were dropped. This property is read-only. ## ## @end deftp RowsUsed = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} NumPredictors ## ## Number of predictors ## ## A positive integer value specifying the number of predictors in the ## training dataset used for training the ClassificationNeuralNetwork model. ## This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} PredictorNames ## ## Names of predictor variables ## ## A cell array of character vectors specifying the names of the predictor ## variables. The names are in the order in which they appear in the ## training dataset. This property is read-only. ## ## @end deftp PredictorNames = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} ResponseName ## ## Response variable name ## ## A character vector specifying the name of the response variable @var{Y}. ## This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} ClassNames ## ## Names of classes in the response variable ## ## An array of unique values of the response variable @var{Y}, which has the ## same data types as the data in @var{Y}. This property is read-only. ## @qcode{ClassNames} can have any of the following datatypes: ## ## @itemize ## @item Cell array of character vectors ## @item Character array ## @item Logical vector ## @item Numeric vector ## @end itemize ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} Sigma ## ## Predictor standard deviations ## ## A numeric vector containing the standard deviations of the predictors ## used for standardization. Empty when the predictor data were not ## standardized. ## This property is read-only. ## ## Only observations with no missing predictor enter the estimate, and ## they are weighted so that each class keeps the share of the ## observation weight it carried before any row was set aside. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} Mu ## ## Predictor means ## ## A numeric vector containing the means of the predictors used for ## standardization. Empty when the predictor data were not standardized. ## This property is read-only. ## ## Only observations with no missing predictor enter the estimate, and ## they are weighted so that each class keeps the share of the ## observation weight it carried before any row was set aside. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} LayerSizes ## ## Sizes of fully connected layers ## ## A positive integer vector specifying the sizes of the fully connected ## layers in the neural network model. The i-th element of ## @qcode{LayerSizes} is the number of outputs in the i-th fully connected ## layer of the neural network model. @qcode{LayerSizes} does not include ## the size of the final fully connected layer. This layer always has K ## outputs, where K is the number of classes in Y. This property is ## read-only. ## ## @end deftp LayerSizes = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} Activations ## ## Activation functions for hidden layers ## ## A character vector or cell array of character vectors specifying the ## activation functions used in the hidden layers of the neural network. ## Supported activation functions include: @qcode{'linear'}, ## @qcode{'sigmoid'}, @qcode{'relu'}, @qcode{'tanh'}, @qcode{'softmax'}, ## @qcode{'lrelu'}, @qcode{'prelu'}, @qcode{'elu'}, and @qcode{'gelu'}. ## This property is read-only. ## ## @end deftp Activations = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} OutputLayerActivation ## ## Activation function for output layer ## ## A character vector specifying the activation function of the output layer ## of the neural network. Supported activation functions are the same as ## for the @qcode{Activations} property. The default, @qcode{softmax}, ## reports a probability over the classes; the network is then trained ## against cross entropy rather than the mean squared error. This ## property is read-only. ## ## @end deftp OutputLayerActivation = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} LearningRate ## ## Learning rate for gradient descent ## ## A positive scalar value defining the learning rate used by the gradient ## descent algorithm during training. This property is read-only. ## ## @end deftp LearningRate = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} IterationLimit ## ## Maximum number of training iterations ## ## A positive integer value defining the maximum number of epochs for ## training the model. This property is read-only. ## ## @end deftp IterationLimit = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} ModelParameters ## ## Neural network model parameters ## ## A structure holding the fit as it was asked for: @qcode{LayerSizes}, ## @qcode{Activations}, @qcode{OutputLayerActivation}, ## @qcode{LayerWeightsInitializers}, @qcode{Solver}, ## @qcode{LearningRate}, @qcode{IterationLimit}, ## @qcode{GradientTolerance}, @qcode{LossTolerance}, ## @qcode{StepTolerance}, @qcode{DisplayInfo}, @qcode{StandardizeData}, ## and the @qcode{Version}, @qcode{Method} and @qcode{Type} tags. ## ## What came out of the fit is elsewhere: the @qcode{LayerWeights} and ## @qcode{LayerBiases} properties hold the network, @qcode{TrainingHistory} ## the series and @qcode{ConvergenceInfo} where it stopped. ## ## @qcode{LayerWeightsInitializers} names the scheme each layer's weights ## were drawn with, the output layer last: @qcode{'he'} for a rectifying ## activation and @qcode{'glorot'} for a symmetric one. It is a report, ## not a setting, the engine choosing per layer from the activation and ## offering no way to override it. ## ## @qcode{OutputLayerActivation}, @qcode{Solver} and @qcode{LearningRate} ## are this package's own; MATLAB has no counterpart for them. The fields ## it reports that this class does not accept as arguments ## (@qcode{Lambda}, the validation set and its patience and frequency, ## @qcode{InitialStepSize} and the two initializer settings) are absent. ## This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} ConvergenceInfo ## ## Training convergence information ## ## A structure containing convergence information of the neural network ## classifier model with the following fields: ## ## @itemize ## @item @qcode{Accuracy} - The prediction accuracy at each iteration ## during training ## @item @qcode{TrainingLoss} - The loss value recorded at each iteration ## during training ## @item @qcode{Time} - The cumulative time taken for all iterations in ## seconds ## @end itemize ## ## This property is read-only. ## ## ## Under @qcode{'lbfgs'} the structure carries @code{Gradient} and ## @code{Step}, the two quantities the solver measured to decide it had ## converged, and @code{ConvergenceCriterion}, naming the test that ## stopped it. It carries no @code{Accuracy}: MATLAB reports none, and ## measuring it would cost a pass over the whole training set at every ## iteration. ## @end deftp ConvergenceInfo = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} DisplayInfo ## ## Display training information flag ## ## A boolean flag indicating whether to print information during training. ## This property is read-only. ## ## @end deftp DisplayInfo = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} Solver ## ## Solver used for training ## ## A character vector specifying the solver algorithm used to train the ## neural network model, either @qcode{'Gradient Descent'} for the ## stochastic solver or @qcode{'LBFGS'} for the full-batch one. This ## property is read-only. ## ## @end deftp Solver = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} LayerWeights ## ## Learned weights of each fully connected layer ## ## A cell array holding one weight matrix per layer, the output layer ## included. @code{LayerWeights@{i@}} has one row per neuron of layer ## @math{i} and one column per input it receives. This property is ## read-only. ## ## @end deftp LayerWeights = {}; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} LayerBiases ## ## Learned bias of each fully connected layer ## ## A cell array holding one column vector per layer, the output layer ## included, with one entry per neuron of that layer. This property is ## read-only. ## ## @end deftp LayerBiases = {}; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} TrainingHistory ## ## Iteration by iteration record of training ## ## A table with one row per iteration, holding the iteration number, the ## training loss and the training accuracy recorded at it. This property ## is read-only. ## ## ## The columns follow the solver. Under @qcode{'sgd'} they are ## @code{Iteration} and @code{TrainingLoss}, with @code{TrainingAccuracy} ## for a classifier. Under @qcode{'lbfgs'} they are @code{Iteration}, ## @code{TrainingLoss}, @code{Gradient} and @code{Step}, as MATLAB's are. ## @end deftp TrainingHistory = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} Prior ## ## Prior probability of each class ## ## A numeric vector with one entry per class, in the order of ## @code{ClassNames}, summing to one. It defaults to the relative ## frequency of each class in the training data. This property is ## read-only, as MATLAB documents it; pass @qcode{'Prior'} to ## @code{fitcnet} to set it. ## ## Specified as a row vector with one entry per class, in the order of ## @qcode{ClassNames}, and rescaled to sum to one. It may be given as ## @qcode{'empirical'}, @qcode{'uniform'}, a numeric vector, or a ## structure with @qcode{ClassNames} and @qcode{ClassProbs} fields, which ## assigns each probability by class name rather than by position. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} W ## ## Observation weights ## ## A numeric column vector with one entry per training observation. It ## defaults to a uniform weight for every observation. This property is ## read-only. ## ## Each class carries its prior spread evenly over its own observations, ## so an observation of a class weighs @qcode{Prior} for that class ## divided by the number of observations it holds. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector of column indices into @code{X} naming the predictors ## treated as categorical, and empty when none is. This property is ## read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} ExpandedPredictorNames ## ## Names of the predictors as the model expanded them ## ## A cell array of character vectors. It matches @code{PredictorNames} ## unless a categorical predictor was expanded into indicator variables. ## This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} BinEdges ## ## Bin edges of the predictors ## ## A cell array with one entry per predictor, holding that predictor's bin ## edges where the learner discretized it before fitting. It is empty here ## and stays empty: this learner fits the predictors as they are, and ## MATLAB's reports an empty cell for it as well. ## ## This property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} HyperparameterOptimizationResults ## ## Results of the hyperparameter optimization ## ## @strong{Always empty.} It is declared for MATLAB compatibility, where ## it holds what an automatic search over the hyperparameters found. This ## class fits the parameters it is given and runs no such search, so there ## is nothing to report. This property is read-only. ## ## @end deftp HyperparameterOptimizationResults = []; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} Cost ## ## Cost of misclassification ## ## A numeric matrix with one row and one column per class, where ## @code{Cost(i,j)} is the cost of classifying an observation of class ## @math{i} as class @math{j}. The default has zeros on the diagonal and ## ones elsewhere. Change it on a trained model with dot notation, as ## in @qcode{@var{obj}.Cost = @var{cost}}. ## ## ## A cost may also be given as a struct with the fields ## @qcode{ClassNames} and @qcode{ClassificationCosts}, which names the ## order its own matrix is written in. That matrix is permuted into the ## order of @qcode{ClassNames} above, so a caller need not know which ## order the classes were sorted into. It must name every class. ## ## A cost must be floating point, not sparse, not complex, non-negative ## and zero down its diagonal, and must hold no @qcode{NaN} or ## @qcode{Inf}. A @code{single} is widened to @code{double}. ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {ClassificationNeuralNetwork} {property} ScoreTransform ## ## Transformation function for classification scores ## ## Specified as a function handle for transforming the classification ## scores. Add or change the @qcode{ScoreTransform} property using dot ## notation as in: ## ## @itemize ## @item @qcode{@var{obj}.ScoreTransform = 'function_name'} ## @item @qcode{@var{obj}.ScoreTransform = @@function_handle} ## @end itemize ## ## When specified as a character vector, it can be any of the following ## built-in functions. Nevertheless, the @qcode{ScoreTransform} property ## always stores their function handle equivalent. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'doublelogit'} @tab @math{1 ./ (1 + exp (-2 * x))} ## @item @qcode{'invlogit'} @tab @math{log (x ./ (1 - x))} ## @item @qcode{'ismax'} @tab Sets the score for the class with the ## largest score to 1, and for all other classes to 0 ## @item @qcode{'logit'} @tab @math{1 ./ (1 + exp (-x))} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'sign'} @tab ## @math{-1 for x < 0, 0 for x = 0, 1 for x > ## 0} ## @item @qcode{'symmetric'} @tab @math{2 * x - 1} ## @item @qcode{'symmetricismax'} @tab Sets the score for the class ## with the largest score to 1, and for all other classes to -1 ## @item @qcode{'symmetriclogit'} @tab @math{2 ./ (1 + exp (-x)) - 1} ## @end multitable ## ## @end deftp ScoreTransform = 'none'; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) STfun = @(x) x; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.ScoreTransform (this, val) name = 'ClassificationNeuralNetwork'; [this.STfun, this.ScoreTransform] = parseScoreTransform (val, ... name); endfunction function this = set.Cost (this, val) gnY = this.ClassNames; if (isempty (val)) this.Cost = cast (! eye (classCount (gnY)), 'double'); else ## Everything a cost must be, and the struct form, which ## is permuted into this model's class order. [val, errmsg] = costMatrix (val, gnY); if (! isempty (errmsg)) error ("ClassificationNeuralNetwork: %s", errmsg); endif this.Cost = val; endif endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n ClassificationNeuralNetwork\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); if (iscellstr (this.ClassNames)) str = repmat ({'''%s'''}, 1, numel (this.ClassNames)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.ClassNames{:}); elseif (ischar (this.ClassNames)) str = repmat ({'''%s'''}, 1, rows (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, cellstr (this.ClassNames){:}); else # single, double, logical str = repmat ({'%d'}, 1, numel (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.ClassNames); endif fprintf ("%+25s: %s\n", 'ClassNames', str); fprintf ("%+25s: '%s'\n", 'ScoreTransform', this.ScoreTransform); fprintf ("%+25s: %d\n", 'NumObservations', this.NumObservations); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); str = repmat ({'%d'}, 1, numel (this.LayerSizes)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.LayerSizes); fprintf ("%+25s: %s\n", 'LayerSizes', str); if (iscellstr (this.Activations)) str = repmat ({'''%s'''}, 1, numel (this.Activations)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.Activations{:}); fprintf ("%+25s: %s\n", 'Activations', str); else # character vector fprintf ("%+25s: '%s'\n", 'Activations', this.Activations); endif fprintf ("%+25s: '%s'\n", 'OutputLayerActivation', ... this.OutputLayerActivation); fprintf ("%+25s: '%s'\n", 'Solver', this.Solver); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} ClassificationNeuralNetwork (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{obj} =} ClassificationNeuralNetwork (@dots{}, @var{name}, @var{value}) ## ## Create a @qcode{ClassificationNeuralNetwork} class object containing a ## neural network classification model. ## ## @code{@var{obj} = ClassificationNeuralNetwork (@var{X}, @var{Y})} returns ## a ClassificationNeuralNetwork object, with @var{X} as the predictor data ## and @var{Y} containing the class labels of observations in @var{X}. ## ## @itemize ## @item ## @code{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. @var{X} will be used to train the neural network model. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} can contain any type ## of categorical data. @var{Y} must have the same number of rows as ## @var{X}. ## @end itemize ## ## @code{@var{obj} = ClassificationNeuralNetwork (@dots{}, @var{name}, ## @var{value})} returns a ClassificationNeuralNetwork object with ## parameters specified by the following @qcode{@var{name}, @var{value}} ## paired input arguments: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PredictorNames'} @tab A cell array of character ## vectors specifying the names of the predictors. The length of this array ## must match the number of columns in @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector specifying the ## name of the response variable. ## ## @item @qcode{'ClassNames'} @tab Names of the classes in the class ## labels, @var{Y}, used for fitting the neural network model. ## @qcode{ClassNames} are of the same type as the class labels in @var{Y}. ## ## @item @qcode{'ScoreTransform'} @tab A user-defined function handle ## or a character vector specifying one of the following builtin functions ## specifying the transformation applied to predicted classification scores. ## Supported values include @qcode{'doublelogit'}, @qcode{'invlogit'}, ## @qcode{'ismax'}, @qcode{'logit'}, @qcode{'none'}, @qcode{'identity'}, ## @qcode{'sign'}, @qcode{'symmetric'}, @qcode{'symmetricismax'}, and ## @qcode{'symmetriclogit'}. ## ## @item @qcode{'Standardize'} @tab A logical scalar specifying whether ## to standardize the predictor data. When @qcode{true}, the predictors are ## centered and scaled to have zero mean and unit variance. ## ## @item @qcode{'LayerSizes'} @tab A positive integer vector specifying ## the sizes of the fully connected layers in the neural network. The ## default is 10. ## ## @item @qcode{'Activations'} @tab A character vector or cell array of ## character vectors specifying the activation functions for the hidden ## layers. Supported values include @qcode{'linear'}, @qcode{'sigmoid'}, ## @qcode{'relu'}, @qcode{'tanh'}, @qcode{'softmax'}, @qcode{'lrelu'}, ## @qcode{'prelu'}, @qcode{'elu'}, and @qcode{'gelu'}. The default is ## @qcode{'relu'}, whose gradient is one wherever a unit is active and so ## does not shrink as it passes back through the layers, where a sigmoid ## multiplies it by at most a quarter at every one. ## ## @item @qcode{'OutputLayerActivation'} @tab A character vector ## specifying the activation function for the output layer. Supported ## values are the same as for @qcode{'Activations'}. The default is ## @qcode{'softmax'}, which makes the scores a probability over the ## classes and trains the network against cross entropy; any other value ## trains it against the mean squared error. ## ## @item @qcode{'LearningRate'} @tab A positive scalar specifying the ## learning rate for gradient descent. The default is 0.003. A larger ## rate can drive every unit of a hidden layer negative, after which a ## rectifier passes no gradient and the network stops training. ## Applies only when @qcode{'Solver'} is @qcode{'sgd'}. ## ## @item @qcode{'Solver'} @tab A character vector naming the solver that ## trains the network, either @qcode{'lbfgs'} or @qcode{'sgd'}. The ## default is @qcode{'lbfgs'}, which minimizes the loss over the whole ## training set at once by limited-memory BFGS, as MATLAB does. It takes ## no learning rate, stops on the three tolerances below, and reaches a ## lower training loss in fewer passes over the data, though each of its ## iterations costs several passes where an epoch costs one. ## @qcode{'sgd'} visits the samples one at a time and steps down the ## gradient of each, running for @qcode{'IterationLimit'} epochs; it was ## the default before version 1.9.0. ## ## @item @qcode{'GradientTolerance'} @tab A nonnegative scalar. Training ## stops once the gradient's infinity norm falls to or below it, which is ## the quantity MATLAB tests too. The default is @qcode{1e-6}. Applies ## only when @qcode{'Solver'} is @qcode{'lbfgs'}. ## ## @item @qcode{'StepTolerance'} @tab A nonnegative scalar. Training ## stops once the step's infinity norm falls to or below it, which is the ## quantity MATLAB tests too. The default is @qcode{1e-6}. Applies only ## when @qcode{'Solver'} is @qcode{'lbfgs'}. ## ## @item @qcode{'LossTolerance'} @tab A real scalar. Training stops once ## the training loss falls to or below it. The test is on the loss ## itself and not on its change, matching MATLAB; pass @code{-Inf} to ## switch it off. The default is @qcode{1e-6}. Applies only when ## @qcode{'Solver'} is @qcode{'lbfgs'}. ## ## @item @qcode{'IterationLimit'} @tab A positive integer specifying ## the maximum number of training iterations. The default is 1000. ## Under @qcode{'sgd'} this counts epochs, under ## @qcode{'lbfgs'} solver iterations. ## ## @item @qcode{'DisplayInfo'} @tab A logical scalar specifying whether ## to display training information. The default is @qcode{false}. ## @end multitable ## ## @seealso{fitcnet} ## @end deftypefn function this = ClassificationNeuralNetwork (X, Y, varargin) ## Check for sufficient number of input arguments if (nargin < 2) error ("ClassificationNeuralNetwork: too few input arguments."); endif ## Check X and Y have the same number of observations if (rows (X) != rows (Y)) error (strcat ("ClassificationNeuralNetwork: number of", ... " rows in X and Y must be equal.")); endif ## Assign original X and Y data to the ClassificationNeuralNetwork object this.X = X; this.Y = Y; ## Get groups in Y [gY, gnY, glY] = grp2idx (Y); ## Set default values before parsing optional parameters Standardize = false; ResponseName = []; PredictorNames = []; ClassNames = []; LayerSizes = 10; Activations = 'relu'; OutputLayerActivation = 'softmax'; LearningRate = 0.003; IterationLimit = 1000; DisplayInfo = false; Solver = 'lbfgs'; GradientTolerance = 1e-6; LossTolerance = 1e-6; StepTolerance = 1e-6; ## Which of the solver-specific options the caller actually named, so ## that one meant for the other solver can be refused by name. GivenTols = {}; LearningRateGiven = false; ## Supported activation functions acList = {'linear', 'none', 'sigmoid', 'relu', 'tanh', 'softmax', ... 'lrelu', 'prelu', 'elu', 'gelu'}; ## Parse extra parameters Prior = []; Cost = []; while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'standardize' Standardize = varargin{2}; if (! (Standardize == true || Standardize == false)) error (strcat ("ClassificationNeuralNetwork:", ... " 'Standardize' must be either true or false.")); endif case 'predictornames' PredictorNames = varargin{2}; if (! iscellstr (PredictorNames)) error (strcat ("ClassificationNeuralNetwork: 'PredictorNames'", ... " must be supplied as a cellstring array.")); elseif (columns (PredictorNames) != columns (X)) error (strcat ("ClassificationNeuralNetwork: 'PredictorNames'", ... " must have the same number of columns as X.")); endif case 'responsename' ResponseName = varargin{2}; if (! ischar (ResponseName)) error (strcat ("ClassificationNeuralNetwork: 'ResponseName'", ... " must be a character vector.")); endif case 'classnames' ClassNames = varargin{2}; if (! (iscellstr (ClassNames) || isnumeric (ClassNames) || islogical (ClassNames) || ischar (ClassNames))) error (strcat ("ClassificationNeuralNetwork: 'ClassNames'", ... " must be a cell array of character vectors,", ... " a logical vector, a numeric vector,", ... " or a character array.")); endif ## Check that all class names are available in gnY if (iscellstr (ClassNames)) ClassNames = cellstr (ClassNames); if (! all (cell2mat (cellfun (@(x) any (strcmp (x, gnY)), ClassNames, 'UniformOutput', false)))) error (strcat ("ClassificationNeuralNetwork: not all", ... " 'ClassNames' are present in Y.")); endif else if (! all (cell2mat (arrayfun (@(x) any (x == glY), ClassNames, 'UniformOutput', false)))) error (strcat ("ClassificationNeuralNetwork: not all", ... " 'ClassNames' are present in Y.")); endif endif case 'scoretransform' name = 'ClassificationNeuralNetwork'; [this.STfun, this.ScoreTransform] = parseScoreTransform ... (varargin{2}, name); case 'prior' Prior = varargin{2}; case 'cost' Cost = varargin{2}; case 'layersizes' LayerSizes = varargin{2}; if (! (isnumeric (LayerSizes) && isvector (LayerSizes) && all (LayerSizes > 0) && all (mod (LayerSizes, 1) == 0))) error (strcat ("ClassificationNeuralNetwork: 'LayerSizes'", ... " must be a positive integer vector.")); endif case 'learningrate' LearningRate = varargin{2}; LearningRateGiven = true; if (! (isnumeric (LearningRate) && isscalar (LearningRate) && LearningRate > 0)) error (strcat ("ClassificationNeuralNetwork:", ... " 'LearningRate' must be a positive scalar.")); endif case 'activations' Activations = varargin{2}; if (! (ischar (Activations) || iscellstr (Activations))) error (strcat ("ClassificationNeuralNetwork: 'Activations'", ... " must be a character vector or a cellstring vector.")); endif if (ischar (Activations)) if (! any (strcmpi (Activations, acList))) error (strcat ("ClassificationNeuralNetwork: unsupported", ... " 'Activation' function.")); endif else if (! all (cell2mat (cellfun (@(x) any (strcmpi (x, acList)), Activations, 'UniformOutput', false)))) error (strcat ("ClassificationNeuralNetwork: unsupported", ... " 'Activation' functions.")); endif endif Activations = tolower (Activations); case 'outputlayeractivation' OutputLayerActivation = varargin{2}; if (! (ischar (OutputLayerActivation))) error (strcat ("ClassificationNeuralNetwork:", ... " 'OutputLayerActivation' must be a character vector.")); endif if (! any (strcmpi (OutputLayerActivation, acList))) error (strcat ("ClassificationNeuralNetwork: unsupported", ... " 'OutputLayerActivation' function.")); endif OutputLayerActivation = tolower (OutputLayerActivation); case 'iterationlimit' IterationLimit = varargin{2}; if (! (isnumeric (IterationLimit) && isscalar (IterationLimit) && (IterationLimit > 0) && mod (IterationLimit, 1) == 0)) error (strcat ("ClassificationNeuralNetwork:", ... " 'IterationLimit' must be a positive integer.")); endif case 'solver' Solver = varargin{2}; if (! (ischar (Solver) && any (strcmpi (Solver, {'sgd', ... 'lbfgs'})))) error (strcat ("ClassificationNeuralNetwork: 'Solver' must", ... " be either 'sgd' or 'lbfgs'.")); endif Solver = tolower (Solver); case 'gradienttolerance' GradientTolerance = varargin{2}; GivenTols{end+1} = 'GradientTolerance'; if (! (isnumeric (GradientTolerance) && isscalar (GradientTolerance) && GradientTolerance >= 0)) error (strcat ("ClassificationNeuralNetwork:", ... " 'GradientTolerance' must be a nonnegative", ... " scalar.")); endif case 'losstolerance' LossTolerance = varargin{2}; GivenTols{end+1} = 'LossTolerance'; if (! (isnumeric (LossTolerance) && isscalar (LossTolerance) && ! isnan (LossTolerance))) error (strcat ("ClassificationNeuralNetwork:", ... " 'LossTolerance' must be a real scalar.")); endif case 'steptolerance' StepTolerance = varargin{2}; GivenTols{end+1} = 'StepTolerance'; if (! (isnumeric (StepTolerance) && isscalar (StepTolerance) && StepTolerance >= 0)) error (strcat ("ClassificationNeuralNetwork:", ... " 'StepTolerance' must be a nonnegative", ... " scalar.")); endif case 'displayinfo' DisplayInfo = varargin{2}; if (! (DisplayInfo == true || DisplayInfo == false)) error (strcat ("ClassificationNeuralNetwork: 'DisplayInfo'", ... " must be either true or false.")); endif otherwise error (strcat ("ClassificationNeuralNetwork: invalid",... " parameter name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## Generate default predictors and response variable names (if necessary) NumPredictors = columns (X); if (isempty (PredictorNames)) for i = 1:NumPredictors PredictorNames {i} = strcat ("x", num2str (i)); endfor endif if (isempty (ResponseName)) ResponseName = 'Y'; endif ## Assign predictors and response variable names this.NumPredictors = NumPredictors; this.PredictorNames = PredictorNames; this.ExpandedPredictorNames = PredictorNames; this.ResponseName = ResponseName; ## Handle class names if (! isempty (ClassNames)) ## Anything textual is matched as whole names, gnY being grp2idx's ## own cellstr of them. A character matrix is not a cellstr, and ## ismember between two of them compares character by character, so ## it would answer a question nobody asked. if (iscellstr (ClassNames) || ischar (ClassNames)) ru = find (! ismember (gnY, cellstr (ClassNames))); else ru = find (! ismember (glY, ClassNames)); endif for i = 1:numel (ru) gY(gY == ru(i)) = NaN; endfor endif ## An observation is dropped only when its response is missing. A row ## whose predictors hold missing values is kept and reported as used, ## while the fit below draws on the complete observations alone. RowsUsed = ! isnan (gY); ## Index the rows and not the elements: a response naming its ## classes in the rows of a character matrix has one column per ## character, and a linear index flattens the names into single ## letters. Every other accepted type is a column, for which ## the two forms agree. Yret = Y(RowsUsed, :); Xret = X(RowsUsed, :); this.X = Xret; this.Y = Yret; cobs = ! any (isnan (Xret), 2); Y = Yret(cobs, :); X = Xret(cobs, :); ## Renew groups in Y over the retained observations, so a class held ## only by a row with missing predictors is still a class of the model [this.ClassNames, gnY, gret] = uniqueLabels (Yret); gY = gret(cobs); ## Check X contains valid data if (! (isnumeric (X) && isfinite (X))) error ("ClassificationNeuralNetwork: invalid values in X."); endif ## Assign the number of observations and their corresponding indices ## on the original data, which will be used for training the model, ## to the ClassificationNeuralNetwork object this.NumObservations = rows (this.X); ## RowsUsed is left empty when every observation was used, as in MATLAB if (all (RowsUsed)) this.RowsUsed = []; else this.RowsUsed = RowsUsed; endif ## Cost, Prior and the observation weights. Cost defaults to zero on ## the diagonal and one elsewhere, Prior to the class frequencies of the ## training data, and each class spreads its prior over its own ## observations. Prior cannot be changed once the model is built, so ## the option here is the only way to weigh the classes differently. nclasses = classCount (this.ClassNames); if (isempty (Cost)) this.Cost = ones (nclasses) - eye (nclasses); else ## A struct carrying its own class order is a cost too, and is ## resolved by the property's own set method. if (! (isstruct (Cost) || (isnumeric (Cost) && issquare (Cost) && rows (Cost) == nclasses))) error (strcat ("ClassificationNeuralNetwork: 'Cost' must be a", ... " numeric square matrix with one row and column", ... " per class.")); endif this.Cost = Cost; endif if (isstruct (Prior)) Prior = priorFromStruct (Prior, this.ClassNames, ... 'ClassificationNeuralNetwork'); endif if (isempty (Prior) || (ischar (Prior) && strcmpi (Prior, 'empirical'))) this.Prior = accumarray (gY(:), 1, [nclasses, 1])' / numel (gY); elseif (ischar (Prior) && strcmpi (Prior, 'uniform')) this.Prior = ones (1, nclasses) / nclasses; elseif (isnumeric (Prior) && isreal (Prior) && isvector (Prior) && numel (Prior) == nclasses && all (Prior >= 0) && sum (Prior) > 0) this.Prior = Prior(:)' / sum (Prior); else error (strcat ("ClassificationNeuralNetwork: 'Prior' must be", ... " 'empirical', 'uniform', or a non-negative numeric", ... " vector with one entry per class.")); endif this.W = priorWeights (this.Prior, gY, this.NumObservations); ## No predictor is treated as categorical, so the expanded names are ## the predictor names themselves. this.CategoricalPredictors = []; ## Handle the Standardize option if (Standardize) ## Mu and Sigma weight the complete observations so that each class ## keeps the share of the observation weight it carried before any row ## was set aside, which is what MATLAB reports. sw = zeros (rows (X), 1); for k = 1:numel (gnY) ck = (gY == k); if (any (ck)) sw(ck) = (sum (gret == k) / numel (gret)) / sum (ck); endif endfor sw = sw / sum (sw); this.Mu = sum (sw .* X, 1); Zs = X - this.Mu; this.Sigma = sqrt (sum (sw .* Zs .^ 2, 1) / (1 - sum (sw .^ 2))); this.Sigma(this.Sigma == 0) = 1; # predictor is constant ## Train on the scale the model predicts on: predict, resubPredict ## and loss all standardize their input from Mu and Sigma, so the ## training data must be standardized here as well. X = (X - this.Mu) ./ this.Sigma; else this.Sigma = []; this.Mu = []; endif ## An option that cannot act is refused rather than ignored. The three ## tolerances are how the lbfgs solver decides it has converged and mean ## nothing to the epoch loop, which runs to 'IterationLimit' whatever ## they say; a learning rate is what the epoch loop scales its step by ## and means nothing to a line search, which finds its own. if (strcmp (Solver, 'sgd') && ! isempty (GivenTols)) error (strcat ("ClassificationNeuralNetwork: '", GivenTols{1}, ... "' applies only when 'Solver' is 'lbfgs'.")); endif if (strcmp (Solver, 'lbfgs') && LearningRateGiven) error (strcat ("ClassificationNeuralNetwork: 'LearningRate'", ... " applies only when 'Solver' is 'sgd'.")); endif if (strcmp (Solver, 'lbfgs')) this.Solver = 'LBFGS'; else this.Solver = 'Gradient Descent'; endif ## Store training parameters this.LayerSizes = LayerSizes; this.Activations = Activations; this.OutputLayerActivation = OutputLayerActivation; this.LearningRate = LearningRate; this.IterationLimit = IterationLimit; this.DisplayInfo = DisplayInfo; ## Start the training process NumThreads = nproc (); cnn_timer_ = tic; ## A softmax output reports a probability over the classes, and the ## loss that belongs with it is cross entropy: paired that way the two ## gradients compose to y - t. Any other output layer keeps the mean ## squared error. LossFunction = double (strcmp (OutputLayerActivation, 'softmax')); SolverOptions = struct ('Solver', Solver, ... 'GradientTolerance', GradientTolerance, ... 'LossTolerance', LossTolerance, ... 'StepTolerance', StepTolerance); ## The engine names the layers itself; this check stays here so the ## count is reported under the class rather than under fcnntrain. if (! ischar (Activations) && numel (LayerSizes) != numel (Activations)) error (strcat ("ClassificationNeuralNetwork: 'Activations'", ... " vector does not match the number of layers.")); endif Mdl = fcnntrain (X, gY, LayerSizes, Activations, ... OutputLayerActivation, NumThreads, ... LearningRate, IterationLimit, DisplayInfo, ... LossFunction, SolverOptions); ## The solver records a value per iteration. The series belongs to the ## history and ConvergenceInfo reports where the fit ended up, as ## MATLAB divides them; SERIES carries the columns to both. series = struct ('TrainingLoss', Mdl.Loss(:)); Mdl = rmfield (Mdl, 'Loss'); ## What the fit recorded depends on which solver ran. The epoch loop ## scores the network once an epoch and reports the accuracy; lbfgs ## reports the gradient and step it measured to decide it had stopped, ## and which test stopped it, as MATLAB does. if (strcmp (Solver, 'lbfgs')) series.Gradient = Mdl.Gradient(:); series.Step = Mdl.Step(:); criterion = Mdl.Criterion; Mdl = rmfield (Mdl, {'Gradient', 'Step', 'Criterion'}); else series.Accuracy = Mdl.Accuracy(:); criterion = ''; Mdl = rmfield (Mdl, 'Accuracy'); endif ConvergenceInfo = convergenceStruct (series, toc (cnn_timer_), ... criterion); ## The fit as it was asked for. What came out of it is the ## LayerWeights and LayerBiases properties, TrainingHistory and ## ConvergenceInfo; this structure holds what went in. The weight ## initializer of each layer is decided by that layer's activation ## inside the engine and cannot be chosen, so it is recorded rather ## than taken from an argument. initz = fcnnInitializers (this.Activations, numel (this.LayerSizes), ... this.OutputLayerActivation); this.ModelParameters = struct ( ... 'LayerSizes', this.LayerSizes, ... 'Activations', {this.Activations}, ... 'OutputLayerActivation', this.OutputLayerActivation, ... 'LayerWeightsInitializers', {initz}, ... 'Solver', this.Solver, ... 'LearningRate', this.LearningRate, ... 'IterationLimit', this.IterationLimit, ... 'GradientTolerance', GradientTolerance, ... 'LossTolerance', LossTolerance, ... 'StepTolerance', StepTolerance, ... 'DisplayInfo', logical (this.DisplayInfo), ... 'StandardizeData', logical (Standardize), ... 'Version', 1, 'Method', 'NeuralNetwork', ... 'Type', 'classification'); this.ConvergenceInfo = ConvergenceInfo; ## fcnntrain packs each neuron as [weights, bias] in one row, so the ## last column of every layer's matrix is its bias. nlay = numel (Mdl.LayerWeights); this.LayerWeights = cell (1, nlay); this.LayerBiases = cell (1, nlay); for i = 1:nlay Wb = Mdl.LayerWeights{i}; this.LayerWeights{i} = Wb(:, 1:end-1); this.LayerBiases{i} = Wb(:, end); endfor ## Iteration by iteration record of the fit this.TrainingHistory = trainingTable (series); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNeuralNetwork} {@var{label} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {ClassificationNeuralNetwork} {[@var{label}, @var{score}] =} predict (@var{obj}, @var{XC}) ## ## Classify new data points into categories using the neural network ## classification model from a ClassificationNeuralNetwork object. ## ## @code{@var{label} = predict (@var{obj}, @var{XC})} returns the vector of ## labels predicted for the corresponding instances in @var{XC}, using the ## predictor data in @code{obj.X} and corresponding labels, @code{obj.Y}, ## stored in the ClassificationNeuralNetwork model, @var{obj}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{ClassificationNeuralNetwork} class object. ## @item ## @var{XC} must be an @math{M*P} numeric matrix with the same number of ## features @math{P} as the corresponding predictors of the neural network ## model in @var{obj}. ## @end itemize ## ## @code{[@var{label}, @var{score}] = predict (@var{obj}, @var{XC})} also ## returns @var{score}, which contains the predicted class scores or ## posterior probabilities for each instance of the corresponding unique ## classes. ## ## The @var{score} matrix contains the classification scores for each class. ## For each observation in @var{XC}, the predicted class label is the one ## with the highest score among all classes. If the @qcode{ScoreTransform} ## property is set to a transformation function, the scores are transformed ## accordingly before being returned. ## ## @seealso{ClassificationNeuralNetwork, fitcnet} ## @end deftypefn function [labels, scores] = predict (this, XC) ## Check for sufficient input arguments if (nargin < 2) error ("ClassificationNeuralNetwork.predict: too few input arguments."); endif ## Check for valid XC if (isempty (XC)) error ("ClassificationNeuralNetwork.predict: XC is empty."); elseif (this.NumPredictors != columns (XC)) error (strcat ("ClassificationNeuralNetwork.predict: XC must have", ... " the same number of predictors as the trained model.")); endif ## Standardize (if necessary) if (! isempty (this.Mu)) XC = (XC - this.Mu) ./ this.Sigma; endif ## Predict labels from new data NumThreads = nproc (); [labels, scores] = fcnnpredict (this.LayerWeights, this.LayerBiases, ... this.Activations, ... this.OutputLayerActivation, ... XC, NumThreads); # Get class labels labels = labelsFromIndex (this.ClassNames, labels); ## Apply ScoreTransform scores = this.STfun (scores); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNeuralNetwork} {@var{label} =} resubPredict (@var{obj}) ## @deftypefnx {ClassificationNeuralNetwork} {[@var{label}, @var{score}] =} resubPredict (@var{obj}) ## ## Classify the training data using the trained neural network ## classification object. ## ## @code{@var{label} = resubPredict (@var{obj})} returns the vector of ## labels predicted for the corresponding instances in the training data, ## using the predictor data in @code{obj.X} and corresponding labels, ## @code{obj.Y}, stored in the neural network classification model, ## @var{obj}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{ClassificationNeuralNetwork} class object. ## @end itemize ## ## @code{[@var{label}, @var{score}] = resubPredict (@var{obj})} also ## returns @var{score}, which contains the predicted class scores or ## posterior probabilities for each instance of the corresponding unique ## classes. ## ## @seealso{ClassificationNeuralNetwork, fitcnet} ## @end deftypefn function [labels, scores] = resubPredict (this) ## Get used rows X = this.X; ## Standardize (if necessary) if (! isempty (this.Mu)) X = (X - this.Mu) ./ this.Sigma; endif ## Predict labels from existing data NumThreads = nproc (); [labels, scores] = fcnnpredict (this.LayerWeights, this.LayerBiases, ... this.Activations, ... this.OutputLayerActivation, ... X, NumThreads); # Get class labels labels = labelsFromIndex (this.ClassNames, labels); ## Apply ScoreTransform scores = this.STfun (scores); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNeuralNetwork} {@var{m} =} margin (@var{obj}, @var{X}, @var{Y}) ## ## Classification margin of a neural network classifier. ## ## @code{@var{m} = margin (@var{obj}, @var{X}, @var{Y})} returns a column ## vector holding, for each row of @var{X}, the score the model gives its ## true class in @var{Y} less the largest score it gives any other class. ## A positive margin means the observation is classified correctly, and ## the larger it is the more confidently so. ## ## @seealso{ClassificationNeuralNetwork, edge, loss, predict} ## @end deftypefn function m = margin (this, X, Y) ## Check for sufficient input arguments if (nargin < 3) error ("ClassificationNeuralNetwork.margin: too few input arguments."); endif [X, Y] = checkXY_ (this, X, Y, "margin"); [~, scores] = predict (this, X); classes = this.ClassNames; m = zeros (rows (X), 1); ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) idx = gYidx(i); if (isempty (idx)) m(i) = NaN; continue; endif true_score = scores(i, idx); scores(i, idx) = -Inf; m(i) = true_score - max (scores(i,:)); scores(i, idx) = true_score; endfor endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNeuralNetwork} {@var{e} =} edge (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationNeuralNetwork} {@var{e} =} edge (@dots{}, @qcode{"Weights"}, @var{w}) ## ## Classification edge of a neural network classifier. ## ## @code{@var{e} = edge (@var{obj}, @var{X}, @var{Y})} returns the mean of ## the classification margins over the rows of @var{X}. ## ## @code{@var{e} = edge (@dots{}, @qcode{"Weights"}, @var{w})} takes the ## weighted mean instead, @var{w} holding one weight per row of @var{X}. ## The weights are normalised to sum to one before they are applied. ## ## @seealso{ClassificationNeuralNetwork, margin, loss, predict} ## @end deftypefn function e = edge (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("ClassificationNeuralNetwork.edge: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationNeuralNetwork.edge: Name-Value", ... " arguments must be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, "edge"); ## The weights are normalized within each class to that class's prior, ## which is what the oracle does and is not the same as dividing by ## their total. This used to divide by the total. ## The weights are parsed before anything is computed, so a bad ## Name-Value pair is reported as such rather than after a margin. W = edgeWeights (varargin, Y, this.ClassNames, this.Prior, ... "ClassificationNeuralNetwork", "edge"); m = margin (this, X, Y); e = sum (W .* m(:)) / sum (W); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNeuralNetwork} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationNeuralNetwork} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Classification loss of a neural network classifier. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} returns the ## proportion of the rows of @var{X} the model misclassifies against the ## true labels @var{Y}. ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} accepts the ## following name-value pairs: ## ## @itemize ## @item ## @qcode{"LossFun"} selects the loss. Supported values are ## @qcode{"mincost"}, the default, @qcode{"binodeviance"}, ## @qcode{"classifcost"}, @qcode{"classiferror"}, @qcode{"crossentropy"}, ## @qcode{"exponential"}, @qcode{"hinge"}, @qcode{"logit"} and ## @qcode{"quadratic"}. @qcode{"mincost"} assigns each observation to ## the class of least expected cost and charges what that assignment ## costs, so it reads the scores as a posterior; @qcode{"classifcost"} ## charges what the model's own prediction costs. @qcode{"crossentropy"} ## is defined for a network only. Note that the default differs from the ## other classifiers in this package, which default to ## @qcode{"classiferror"}, and follows MATLAB's for this class. ## ## @item ## @qcode{"Weights"} holds one weight per row of @var{X}, normalised to ## sum to one before it is applied. ## @end itemize ## ## @seealso{ClassificationNeuralNetwork, margin, edge, predict} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("ClassificationNeuralNetwork.loss: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationNeuralNetwork.loss: Name-Value", ... " arguments must be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, "loss"); ## Parse optional arguments LossFun = 'mincost'; lossnames = {'binodeviance', 'classifcost', 'classiferror', ... 'crossentropy', 'exponential', 'hinge', 'logit', ... 'mincost', 'quadratic'}; args = varargin; keep = true (1, numel (args)); for i = 1:2:numel (args) if (strcmpi (args{i}, 'lossfun')) LossFun = args{i+1}; if (! (ischar (LossFun) && isrow (LossFun))) error (strcat ("ClassificationNeuralNetwork.loss: 'LossFun'", ... " must be a character vector.")); endif LossFun = tolower (LossFun); if (! any (strcmpi (LossFun, lossnames))) error (strcat ("ClassificationNeuralNetwork.loss: unsupported", ... " Loss function.")); endif keep(i:i+1) = false; endif endfor W = getWeights_ (this, args(keep), rows (X), "loss"); W = W(:) / sum (W); [label, scores] = predict (this, X); classes = this.ClassNames; K = classCount (classes); ## Membership of the true class, as a +1/-1 indicator per class Yind = zeros (rows (X), K); ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) idx = gYidx(i); if (isempty (idx)) L = NaN; return; endif Yind(i, idx) = 1; endfor ## The scalar score of the true class of each observation mj = sum (scores .* Yind, 2); switch (LossFun) case 'classiferror' wrong = zeros (rows (X), 1); for i = 1:rows (X) wrong(i) = ! isequal (label(i), Y(i)); endfor L = sum (W .* wrong); case 'binodeviance' L = sum (W .* log (1 + exp (-2 * mj))); case 'hinge' L = sum (W .* max (0, 1 - mj)); case 'exponential' L = sum (W .* exp (-mj)); case 'logit' L = sum (W .* log (1 + exp (-mj))); case 'quadratic' L = sum (W .* (1 - mj) .^ 2); case 'mincost' ## Each observation is assigned to the class of least expected ## cost, and charged what that assignment actually costs given its ## true class. L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) [~, k] = min (scores(i,:) * this.Cost); true_idx = gYidx(i); L = L + W(i) * this.Cost(true_idx, k); endfor case 'classifcost' ## What the model's own prediction costs, given the true class L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) true_idx = gYidx(i); pred_idx = find (ismember (classes, label(i))); L = L + W(i) * this.Cost(true_idx, pred_idx); endfor case 'crossentropy' ## Defined for a network only, whose scores are a posterior. The ## weights are rescaled to sum to the number of observations, as ## MATLAB documents, and the sum is taken over classes as well. Wn = W * rows (X); L = -sum (Wn .* log (max (mj, realmin))) / (K * rows (X)); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNeuralNetwork} {@var{m} =} resubMargin (@var{obj}) ## ## Classification margin of a neural network classifier on its training ## data. ## ## @code{@var{m} = resubMargin (@var{obj})} is @code{margin} applied to ## the observations the model was fitted on. ## ## @seealso{ClassificationNeuralNetwork, margin} ## @end deftypefn function m = resubMargin (this) if (nargin != 1) error (strcat ("ClassificationNeuralNetwork.resubMargin:", ... " too many input arguments.")); endif m = margin (this, this.X, this.Y); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNeuralNetwork} {@var{e} =} resubEdge (@var{obj}) ## ## Classification edge of a neural network classifier on its training ## data. ## ## @code{@var{e} = resubEdge (@var{obj})} is @code{edge} applied to the ## observations the model was fitted on, weighted by @code{obj.W}. ## ## @seealso{ClassificationNeuralNetwork, edge} ## @end deftypefn function e = resubEdge (this) if (nargin != 1) error (strcat ("ClassificationNeuralNetwork.resubEdge:", ... " too many input arguments.")); endif e = edge (this, this.X, this.Y, 'Weights', this.W); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNeuralNetwork} {@var{L} =} resubLoss (@var{obj}) ## @deftypefnx {ClassificationNeuralNetwork} {@var{L} =} resubLoss (@dots{}, @var{name}, @var{value}) ## ## Classification loss of a neural network classifier on its training ## data. ## ## @code{@var{L} = resubLoss (@var{obj})} is @code{loss} applied to the ## observations the model was fitted on, weighted by @code{obj.W}. It ## takes the same @qcode{"LossFun"} name-value pair. ## ## @seealso{ClassificationNeuralNetwork, loss} ## @end deftypefn function L = resubLoss (this, varargin) if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationNeuralNetwork.resubLoss: Name-Value", ... " arguments must be in pairs.")); endif L = loss (this, this.X, this.Y, varargin{:}, 'Weights', this.W); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNeuralNetwork} {@var{CVMdl} =} crossval (@var{obj}) ## @deftypefnx {ClassificationNeuralNetwork} {@var{CVMdl} =} crossval (@dots{}, @var{Name}, @var{Value}) ## ## Cross Validate a Neural Network classification object. ## ## @code{@var{CVMdl} = crossval (@var{obj})} returns a cross-validated model ## object, @var{CVMdl}, from a trained model, @var{obj}, using 10-fold ## cross-validation by default. ## ## @code{@var{CVMdl} = crossval (@var{obj}, @var{name}, @var{value})} ## specifies additional name-value pair arguments to customize the ## cross-validation process. ## ## @multitable @columnfractions 0.28 0.7 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'KFold'} @tab Specify the number of folds to use in ## k-fold cross-validation. @code{"KFold", @var{k}}, where @var{k} is an ## integer greater than 1. ## ## @item @qcode{'Holdout'} @tab Specify the fraction of the data to ## hold out for testing. @code{"Holdout", @var{p}}, where @var{p} is a ## scalar in the range @math{(0,1)}. ## ## @item @qcode{'Leaveout'} @tab Specify whether to perform ## leave-one-out cross-validation. @code{"Leaveout", @var{Value}}, where ## @var{Value} is 'on' or 'off'. ## ## @item @qcode{'CVPartition'} @tab Specify a @qcode{cvpartition} ## object used for cross-validation. @code{"CVPartition", @var{cv}}, where ## @code{isa (@var{cv}, "cvpartition")} = 1. ## ## @end multitable ## ## @seealso{fitcnet, ClassificationNeuralNetwork, cvpartition, ## ClassificationPartitionedModel} ## @end deftypefn function CVMdl = crossval (this, varargin) ## Check for sufficient input arguments if (nargin < 1) error ("ClassificationNeuralNetwork.crossval: too few input arguments."); endif if (numel (varargin) == 1) error (strcat ("ClassificationNeuralNetwork.crossval: Name-Value", ... " arguments must be in pairs.")); elseif (numel (varargin) > 2) error (strcat ("ClassificationNeuralNetwork.crossval:", ... " specify only one of the optional", ... " Name-Value paired arguments.")); endif ## Add default values if (this.NumObservations < 10) numFolds = this.NumObservations; else numFolds = 10; endif Holdout = []; Leaveout = 'off'; CVPartition = []; ## Parse extra parameters while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'kfold' numFolds = varargin{2}; if (! (isnumeric (numFolds) && isscalar (numFolds) && (numFolds == fix (numFolds)) && numFolds > 1)) error (strcat ("ClassificationNeuralNetwork.crossval:", ... " 'KFold' must be an integer value", ... " greater than 1.")); endif case 'holdout' Holdout = varargin{2}; if (! (isnumeric (Holdout) && isscalar (Holdout) && Holdout > 0 && Holdout < 1)) error (strcat ("ClassificationNeuralNetwork.crossval:", ... " 'Holdout' must be a numeric value", ... " between 0 and 1.")); endif case 'leaveout' Leaveout = varargin{2}; if (! (ischar (Leaveout) && (strcmpi (Leaveout, 'on') || strcmpi (Leaveout, 'off')))) error (strcat ("ClassificationNeuralNetwork.crossval:", ... " 'Leaveout' must be either 'on' or 'off'.")); endif case 'cvpartition' CVPartition = varargin{2}; if (! (isa (CVPartition, 'cvpartition'))) error (strcat ("ClassificationNeuralNetwork.crossval:", ... " 'CVPartition' must be a 'cvpartition' object.")); endif otherwise error (strcat ("ClassificationNeuralNetwork.crossval: invalid",... " parameter name in optional paired arguments.")); endswitch varargin(1:2) = []; endwhile ## Determine the cross-validation method to use. The partition covers ## the observations actually trained on: a row dropped for a missing ## value is not one the folds can use, and including it would leave the ## partition, the stored data and NumObservations disagreeing. The ## response is passed rather than a count so the folds stay stratified. Yused = this.Y; if (! isempty (CVPartition)) partition = CVPartition; elseif (! isempty (Holdout)) partition = cvpartition (Yused, 'Holdout', Holdout); elseif (strcmpi (Leaveout, 'on')) partition = cvpartition (this.NumObservations, 'LeaveOut'); else partition = cvpartition (Yused, 'KFold', numFolds); endif ## Create a cross-validated model object CVMdl = ClassificationPartitionedModel (this, partition); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNeuralNetwork} {@var{CVMdl} =} compact (@var{obj}) ## ## Create a CompactClassificationNeuralNetwork object. ## ## @code{@var{CVMdl} = compact (@var{obj})} creates a compact version of the ## ClassificationNeuralNetwork object, @var{obj}. ## ## @seealso{fitcnet, ClassificationNeuralNetwork, ## CompactClassificationNeuralNetwork} ## @end deftypefn function CVMdl = compact (this) ## Create a compact model CVMdl = CompactClassificationNeuralNetwork (this); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationNeuralNetwork} {} savemodel (@var{obj}, @var{filename}) ## ## Save a ClassificationNeuralNetwork object. ## ## @code{savemodel (@var{obj}, @var{filename})} saves each property of a ## ClassificationNeuralNetwork object into an Octave binary file, the name ## of which is specified in @var{filename}, along with an extra variable, ## which defines the type classification object these variables constitute. ## Use ## @code{loadmodel} in order to load a classification object into Octave's ## workspace. ## ## @seealso{loadmodel, fitcnet, ClassificationNeuralNetwork} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error ("ClassificationNeuralNetwork.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error ("ClassificationNeuralNetwork.savemodel: FNAME must be a character vector."); endif ## Generate variable for class name classdef_name = 'ClassificationNeuralNetwork'; ## Create variables from model properties X = this.X; Y = this.Y; NumObservations = this.NumObservations; RowsUsed = this.RowsUsed; NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; ResponseName = this.ResponseName; ClassNames = this.ClassNames; ScoreTransform = this.ScoreTransform; Sigma = this.Sigma; BinEdges = this.BinEdges; Mu = this.Mu; LayerSizes = this.LayerSizes; Activations = this.Activations; OutputLayerActivation = this.OutputLayerActivation; LearningRate = this.LearningRate; IterationLimit = this.IterationLimit; ModelParameters = this.ModelParameters; ConvergenceInfo = this.ConvergenceInfo; DisplayInfo = this.DisplayInfo; Solver = this.Solver; LayerWeights = this.LayerWeights; LayerBiases = this.LayerBiases; Cost = this.Cost; Prior = this.Prior; W = this.W; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; STfun = this.STfun; TrainingHistory = this.TrainingHistory; ## Save classdef name and all model properties as individual variables ## The history is a table and ConvergenceInfo holds another; both lose ## their class on the way to the file and load_model rebuilds them, so ## the warning that says so is expected and is not shown. ws_ = warning ('off', 'Octave:save:classdef:unsupported'); unwind_protect HyperparameterOptimizationResults = this.HyperparameterOptimizationResults; save ('-binary', fname, 'classdef_name', 'X', 'Y', 'NumObservations', ... 'RowsUsed', 'NumPredictors', 'PredictorNames', 'BinEdges', ... 'ResponseName', ... 'ClassNames', 'ScoreTransform', 'Sigma', 'Mu', ... 'LayerSizes', 'Activations', 'OutputLayerActivation', ... 'LearningRate', 'IterationLimit', 'Solver', 'ModelParameters', ... 'ConvergenceInfo', 'TrainingHistory', 'DisplayInfo', ... 'LayerWeights', 'LayerBiases', ... 'Cost', 'Prior', 'W', 'CategoricalPredictors', ... 'ExpandedPredictorNames', 'STfun', ... 'HyperparameterOptimizationResults'); unwind_protect_cleanup warning (ws_); end_unwind_protect endfunction endmethods methods (Access = private) ## Shared validation for the assessment methods, so each reports under ## its own name. function [X, Y] = checkXY_ (this, X, Y, caller) if (isempty (X)) error ("ClassificationNeuralNetwork.%s: X is empty.", caller); elseif (columns (this.X) != columns (X)) error (strcat ("ClassificationNeuralNetwork.%s: X must have the", ... " same number of predictors as the trained model."), ... caller); endif if (isempty (Y)) error ("ClassificationNeuralNetwork.%s: Y is empty.", caller); elseif (rows (X) != rows (Y)) error (strcat ("ClassificationNeuralNetwork.%s: Y must have the", ... " same number of rows as X."), caller); endif endfunction ## Pull a "Weights" pair out of the optional arguments, defaulting to a ## uniform weight, and reject any other name. function W = getWeights_ (this, args, n, caller) W = ones (n, 1); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("ClassificationNeuralNetwork.%s: parameter name", ... " must be a character vector."), caller); endif if (strcmpi (args{i}, 'weights')) W = args{i+1}; if (! (isnumeric (W) && isvector (W))) error (strcat ("ClassificationNeuralNetwork.%s: 'Weights'", ... " must be a numeric vector."), caller); endif if (numel (W) != n) error (strcat ("ClassificationNeuralNetwork.%s: size of", ... " 'Weights' must equal the number of", ... " rows in X."), caller); endif else error (strcat ("ClassificationNeuralNetwork.%s: invalid", ... " parameter name in optional paired", ... " arguments."), caller); endif endfor endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a ClassificationNeuralNetwork object mdl = ClassificationNeuralNetwork (1, 1); ## Get fieldnames from DATA (including private properties) names = fieldnames (data); ## The set methods for these read other properties, and one of them ## rebuilds Coeffs, so they are assigned once everything else is in ## place rather than in the order the file happens to list them. late = ismember (names, {'Cost', 'Prior', 'ScoreTransform', ... 'ResponseTransform'}); names = [names(! late); names(late)]; ## Copy data into object. A table loses its class on the way to a ## binary file and comes back a structure, so it is rebuilt before the ## property it belongs to is assigned. for i = 1:numel (names) ## Check fieldnames in DATA match properties in ClassificationNeuralNetwork try mdl.(names{i}) = restore_tables (data.(names{i})); catch error (strcat ("ClassificationNeuralNetwork.load_model:", ... " invalid model in '%s'."), filename) end_try_catch endfor ## A model saved before the history was written out carries the series ## as vectors in ConvergenceInfo instead; rebuild from those, so that an ## older file still loads and loads as the current shape. if (isempty (mdl.TrainingHistory) && ! isempty (mdl.ConvergenceInfo)) mdl = restoreOlderModel (mdl); endif endfunction endmethods endclassdef ## The recorded history, whose columns follow the solver that produced it. ## Building it in one place keeps the fit and the model reloaded from disk ## from drifting apart. function T = trainingTable (series) iter = (1:numel (series.TrainingLoss))'; ## Time is NaN because neither solver records a per-iteration figure; the ## total the fit took is ConvergenceInfo.Time. The validation pair is NaN ## because no validation set can be given, which is what MATLAB reports ## when none is. Both are present so the columns are MATLAB's. pad = NaN (numel (iter), 1); if (isfield (series, 'Gradient')) T = table (iter, series.TrainingLoss(:), series.Gradient(:), ... series.Step(:), pad, pad, pad, 'VariableNames', ... {'Iteration', 'TrainingLoss', 'Gradient', 'Step', 'Time', ... 'ValidationLoss', 'ValidationChecks'}); else T = table (iter, series.TrainingLoss(:), series.Accuracy(:), ... pad, pad, pad, 'VariableNames', ... {'Iteration', 'TrainingLoss', 'TrainingAccuracy', 'Time', ... 'ValidationLoss', 'ValidationChecks'}); endif endfunction ## ConvergenceInfo reports where the fit ended: the last value of each series ## as a scalar, beside the whole series as History. MATLAB divides the two ## the same way, and a vector here would repeat what History already holds. function ci = convergenceStruct (series, elapsed, criterion) ## The fields are assigned in MATLAB's own order, which fieldnames reports. ci.Iterations = numel (series.TrainingLoss); ci.TrainingLoss = lastValue (series.TrainingLoss); if (isfield (series, 'Gradient')) ci.Gradient = lastValue (series.Gradient); ci.Step = lastValue (series.Step); else ci.Accuracy = lastValue (series.Accuracy); endif ci.Time = elapsed; ci.ValidationLoss = NaN; ci.ValidationChecks = NaN; if (isfield (series, 'Gradient')) ci.ConvergenceCriterion = criterion; endif ci.History = trainingTable (series); endfunction ## Where the fit ended. A fit that took no iteration recorded nothing, so ## the value it ended at is empty rather than an error. function v = lastValue (x) if (isempty (x)) v = []; else v = x(end); endif endfunction ## The series as it was persisted, or, from a model saved before the series ## became a property of its own, rebuilt from the vectors ConvergenceInfo ## used to carry. ## Read a model written before ConvergenceInfo reported where the fit ended ## rather than the whole series: the vectors it carries are the history. function mdl = restoreOlderModel (mdl) ci = mdl.ConvergenceInfo; series = struct ('TrainingLoss', ci.TrainingLoss(:)); if (isfield (ci, 'Gradient')) series.Gradient = ci.Gradient(:); series.Step = ci.Step(:); criterion = ci.ConvergenceCriterion; else series.Accuracy = ci.Accuracy(:); criterion = ''; endif mdl.TrainingHistory = trainingTable (series); mdl.ConvergenceInfo = convergenceStruct (series, ci.Time, criterion); endfunction ## Test input validation for constructor ## The full-batch solver is selected by name and says so. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, "IterationLimit", 50, "Solver", "lbfgs"); %! assert_equal (Mdl.Solver, "LBFGS"); %! assert_equal (columns (Mdl.TrainingHistory), 7); %! assert_equal (Mdl.TrainingHistory.Properties.VariableNames, ... %! {"Iteration", "TrainingLoss", "Gradient", "Step", ... %! "Time", "ValidationLoss", "ValidationChecks"}); ## It records what it measured to decide it had stopped, and no accuracy. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, "IterationLimit", 50, "Solver", "lbfgs"); %! ci = Mdl.ConvergenceInfo; %! assert_equal (isfield (ci, "Gradient"), true); %! assert_equal (isfield (ci, "Step"), true); %! assert_equal (isfield (ci, "ConvergenceCriterion"), true); %! assert_equal (isfield (ci, "Accuracy"), false); ## The stochastic solver still reports accuracy, and is still reached by name. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, "Solver", "sgd", "IterationLimit", 20); %! assert_equal (Mdl.Solver, "Gradient Descent"); %! assert_equal (isfield (Mdl.ConvergenceInfo, "Accuracy"), true); %! assert_equal (columns (Mdl.TrainingHistory), 6); ## The default solver is lbfgs, which reports the quantities it converges on ## rather than an accuracy. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, "IterationLimit", 20); %! assert_equal (Mdl.Solver, "LBFGS"); %! assert_equal (isfield (Mdl.ConvergenceInfo, "Accuracy"), false); %! assert_equal (fieldnames (Mdl.ConvergenceInfo), ... %! {"Iterations"; "TrainingLoss"; "Gradient"; "Step"; ... %! "Time"; "ValidationLoss"; "ValidationChecks"; ... %! "ConvergenceCriterion"; "History"}); %! assert_equal (Mdl.TrainingHistory.Properties.VariableNames, ... %! {"Iteration", "TrainingLoss", "Gradient", "Step", ... %! "Time", "ValidationLoss", "ValidationChecks"}); %! assert_equal (Mdl.ConvergenceInfo.Iterations, rows (Mdl.TrainingHistory)); ## From the same starting weights it reaches a lower training loss in fewer ## passes over the data, which is the reason for offering it. %!test %! load fisheriris %! rand ("state", 3); randn ("state", 3); %! Ms = fitcnet (meas, species, "IterationLimit", 200, "Solver", "sgd"); %! rand ("state", 3); randn ("state", 3); %! Ml = fitcnet (meas, species, "IterationLimit", 200, "Solver", "lbfgs"); %! ls = Ms.ConvergenceInfo.TrainingLoss(end); %! ll = Ml.ConvergenceInfo.TrainingLoss(end); %! assert_equal (ll < ls, true); %! assert_equal (height (Ml.TrainingHistory) < height (Ms.TrainingHistory), ... %! true); ## A model trained by lbfgs comes back off disk with its own four columns. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, "IterationLimit", 30, "Solver", "lbfgs"); %! fname = tempname (); %! savemodel (Mdl, fname); %! Mdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (table2cell (Mdl2.TrainingHistory), ... %! table2cell (Mdl.TrainingHistory)); ## An option that cannot act is refused rather than ignored. ## A response naming its classes in the rows of a character matrix is one ## of the documented types and MATLAB accepts it on every classifier. The ## whole surface below was broken and untested, which is why it stayed so. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcnet (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcnet (Xch, Ycell); %! assert_equal (size (Mc.ClassNames), [2, 10]); %! assert_equal (cellstr (Mc.ClassNames), Ms.ClassNames); ## predict returns whole names, not their first letters. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcnet (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcnet (Xch, Ycell); %! pch = predict (Mc, Xch); %! assert_equal (columns (pch), 10); %! assert_equal (cellstr (pch), predict (Ms, Xch)); ## loss, margin and edge read a character response as the same response. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcnet (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcnet (Xch, Ycell); %! assert_equal (loss (Mc, Xch, Ych), loss (Ms, Xch, Ycell), 1e-12); %! assert_equal (margin (Mc, Xch, Ych), margin (Ms, Xch, Ycell), 1e-12); %! assert_equal (edge (Mc, Xch, Ych), edge (Ms, Xch, Ycell), 1e-12); ## A character matrix pads its rows out to the longest name, and the padding ## is part of the name: R2024a reports ClassNames of ['ab '; 'abcd']. %!test %! Xpad = [1 2; 3 4; 1.1 2.1; 3.1 4.1; 1.2 2.2; 3.2 4.2]; %! Ypad = char ({"ab", "abcd", "ab", "abcd", "ab", "abcd"}); %! rand ("state", 1); randn ("state", 1); %! Mp = fitcnet (Xpad, Ypad); %! assert_equal (size (Mp.ClassNames), [2, 4]); %! assert_equal (Mp.ClassNames(1,:), "ab "); ## A row dropped for a missing predictor is the only case that exercises ## indexing the response by row rather than by element. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! Xmiss = Xch; Xmiss(3,2) = NaN; %! rand ("state", 1); randn ("state", 1); Md = fitcnet (Xmiss, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcnet (Xmiss, Ycell); %! assert_equal (size (Md.ClassNames), [2, 10]); %! assert_equal (cellstr (Md.ClassNames), Ms.ClassNames); ## ClassNames may itself be given as a character matrix, which selects the ## classes by whole name: ismember between two character matrices compares ## them character by character and would select by letter. %!test %! load fisheriris %! rand ("state", 1); randn ("state", 1); %! Mf = fitcnet (meas, char (species), ... %! "ClassNames", char ({"versicolor", "virginica"})); %! assert_equal (rows (Mf.ClassNames), 2); %! assert_equal (cellstr (Mf.ClassNames), {"versicolor"; "virginica"}); ## A model fitted from a character response comes back off disk unchanged. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcnet (Xch, Ych); %! fname = tempname (); %! savemodel (Mc, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.ClassNames, Mc.ClassNames); %! assert_equal (predict (M2, Xch), predict (Mc, Xch)); ## crossval carries a character response through cvpartition and back. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcnet (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcnet (Xch, Ycell); %! rand ("state", 2); cvc = crossval (Mc, "KFold", 3); %! rand ("state", 2); cvs = crossval (Ms, "KFold", 3); %! assert_equal (cellstr (kfoldPredict (cvc)), kfoldPredict (cvs)); %!error ... %! fitcnet (ones (5, 2), [1; 1; 2; 2; 2], "Solver", "sgd", ... %! "GradientTolerance", 1e-8) %!error ... %! fitcnet (ones (5, 2), [1; 1; 2; 2; 2], "Solver", "sgd", "LossTolerance", 1) %!error ... %! fitcnet (ones (5, 2), [1; 1; 2; 2; 2], "Solver", "sgd", ... %! "StepTolerance", 1e-8) %!error ... %! fitcnet (ones (5, 2), [1; 1; 2; 2; 2], "Solver", "lbfgs", "LearningRate", 0.1) %!error ... %! fitcnet (ones (5, 2), [1; 1; 2; 2; 2], "Solver", "bogus") %!error ... %! fitcnet (ones (5, 2), [1; 1; 2; 2; 2], "Solver", "lbfgs", "GradientTolerance", -1) %!error ... %! fitcnet (ones (5, 2), [1; 1; 2; 2; 2], "Solver", "lbfgs", "StepTolerance", -1) %!error ... %! fitcnet (ones (5, 2), [1; 1; 2; 2; 2], "Solver", "lbfgs", "LossTolerance", NaN) %!error ... %! ClassificationNeuralNetwork () %!error ... %! ClassificationNeuralNetwork (ones (10,2)) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (5,1)) %!error ... %! ClassificationNeuralNetwork (ones (5,3), ones (5,1), 'standardize', 'a') %!error ... %! ClassificationNeuralNetwork (ones (5,2), ones (5,1), 'PredictorNames', ['A']) %!error ... %! ClassificationNeuralNetwork (ones (5,2), ones (5,1), 'PredictorNames', 'A') %!error ... %! ClassificationNeuralNetwork (ones (5,2), ones (5,1), 'PredictorNames', {'A', 'B', 'C'}) %!error ... %! ClassificationNeuralNetwork (ones (5,2), ones (5,1), 'ResponseName', {'Y'}) %!error ... %! ClassificationNeuralNetwork (ones (5,2), ones (5,1), 'ResponseName', 1) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'ClassNames', @(x)x) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'ClassNames', {1}) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'ClassNames', [1, 2]) %!error ... %! ClassificationNeuralNetwork (ones (5,2), ['a';'b';'a';'a';'b'], 'ClassNames', ['a';'c']) %!error ... %! ClassificationNeuralNetwork (ones (5,2), {'a';'b';'a';'a';'b'}, 'ClassNames', {'a','c'}) %!error ... %! ClassificationNeuralNetwork (ones (10,2), logical (ones (10,1)), 'ClassNames', [true, false]) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'LayerSizes', -1) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'LayerSizes', 0.5) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'LayerSizes', [1,-2]) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'LayerSizes', [10,20,30.5]) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'LearningRate', -0.1) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'LearningRate', [0.1, 0.01]) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'LearningRate', 'a') %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'Activations', 123) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'Activations', 'unsupported_type') %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'LayerSizes', [10, 5], ... %! 'Activations', {'sigmoid', 'unsupported_type'}) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'Activations', {'sigmoid', 'relu', 'softmax'}) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'OutputLayerActivation', 123) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'OutputLayerActivation', 'unsupported_type') %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'IterationLimit', -1) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'IterationLimit', 0.5) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'IterationLimit', [1,2]) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'ScoreTransform', [1,2]) %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'ScoreTransform', 'unsupported_type') %!error ... %! ClassificationNeuralNetwork (ones (10,2), ones (10,1), 'some', 'some') %!error ... %! ClassificationNeuralNetwork ([1;2;3;'a';4], ones (5,1)) %!error ... %! ClassificationNeuralNetwork ([1;2;3;Inf;4], ones (5,1)) ## Test input validation for subsasgn method %!shared x, y, objST, Mdl %! load fisheriris %! x = meas; %! y = grp2idx (species); %! Mdl = fitcnet (x, y, 'IterationLimit', 100); %!error ... %! Mdl.ScoreTransform = 'a'; ## Test input validation for predict method %!error ... %! predict (Mdl) %!error ... %! predict (Mdl, []) %!error ... %! predict (Mdl, 1) ## Test output for crossval method %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (Mdl, 'KFold', 5); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (CVMdl.KFold == 5, true) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationNeuralNetwork") %! assert_equal (CVMdl.CrossValidatedModel, "NeuralNetwork") %!test %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (Mdl, 'HoldOut', 0.2); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationNeuralNetwork") %! assert_equal (CVMdl.CrossValidatedModel, "NeuralNetwork") ## Test input validation for crossval method %!error ... %! crossval (Mdl, 'KFold') %!error ... %! crossval (Mdl, 'KFold', 5, 'leaveout', 'on') %!error ... %! crossval (Mdl, 'KFold', 'a') %!error ... %! crossval (Mdl, 'KFold', 1) %!error ... %! crossval (Mdl, 'KFold', -1) %!error ... %! crossval (Mdl, 'KFold', 11.5) %!error ... %! crossval (Mdl, 'KFold', [1,2]) %!error ... %! crossval (Mdl, 'Holdout', 'a') %!error ... %! crossval (Mdl, 'Holdout', 11.5) %!error ... %! crossval (Mdl, 'Holdout', -1) %!error ... %! crossval (Mdl, 'Holdout', 0) %!error ... %! crossval (Mdl, 'Holdout', 1) %!error ... %! crossval (Mdl, 'Leaveout', 1) %!error ... %! crossval (Mdl, 'CVPartition', 1) %!error ... %! crossval (Mdl, 'CVPartition', 'a') %!error ... %! crossval (Mdl, 'some', 'some') ## A saved and reloaded model carries the trained parameters, not those of ## the placeholder object load_model starts from. %!test %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], ... %! 'LayerSizes', [3, 2], 'IterationLimit', 20); %! fname = tempname (); %! savemodel (Mdl, fname); %! Mdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (Mdl2.LayerWeights, Mdl.LayerWeights); %! assert_equal (Mdl2.LayerBiases, Mdl.LayerBiases); ## Cost, Prior, W and the predictor bookkeeping survive a save and load. %!test %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], ... %! 'IterationLimit', 20, 'Prior', [0.25, 0.75]); %! Mdl.Cost = [0, 3; 5, 0]; %! fname = tempname (); %! savemodel (Mdl, fname); %! Mdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (Mdl2.Cost, [0, 3; 5, 0]); %! assert_equal (Mdl2.Prior, [0.25, 0.75]); %! assert_equal (Mdl2.W, Mdl.W); %! assert_equal (Mdl2.ExpandedPredictorNames, Mdl.ExpandedPredictorNames); %! assert_equal (Mdl2.CategoricalPredictors, Mdl.CategoricalPredictors); ## TrainingHistory comes back a table, rebuilt from ConvergenceInfo because ## Octave cannot write a classdef object to a binary file. %!test %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], ... %! 'Solver', 'sgd', 'IterationLimit', 25); %! fname = tempname (); %! savemodel (Mdl, fname); %! Mdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (istable (Mdl2.TrainingHistory), true); %! assert_equal (height (Mdl2.TrainingHistory), 25); %! assert_equal (table2cell (Mdl2.TrainingHistory), ... %! table2cell (Mdl.TrainingHistory)); ## A reloaded model predicts exactly what the original did. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, 'IterationLimit', 20); %! fname = tempname (); %! savemodel (Mdl, fname); %! Mdl2 = loadmodel (fname); %! delete (fname); %! [label, score] = predict (Mdl, meas); %! [label2, score2] = predict (Mdl2, meas); %! assert_equal (label2, label); %! assert_equal (score2, score); ## A non-default ScoreTransform survives a save and load. %!test %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], 'IterationLimit', 20); %! Mdl.ScoreTransform = 'symmetric'; %! fname = tempname (); %! savemodel (Mdl, fname); %! Mdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (Mdl2.ScoreTransform, 'symmetric'); %! [~, s1] = predict (Mdl2, [1, 2; 4, 5]); %! [~, s2] = predict (Mdl, [1, 2; 4, 5]); %! assert_equal (s1, s2); %!error ... %! savemodel (ClassificationNeuralNetwork ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])) %!error ... %! savemodel (ClassificationNeuralNetwork ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), 1) %!error ... %! savemodel (ClassificationNeuralNetwork ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), ['ab'; 'cd']) ## A trained network returns a posterior, not a fraction of one. A backward ## pass that settles at half the target leaves every label right and every ## score halved, so the scores and their row sums are what catch it. %!test %! rand ('seed', 42); %! randn ('seed', 42); %! X = [randn(40, 2) * 0.3 + 3; randn(40, 2) * 0.3 - 3]; %! Y = [ones(40, 1); 2 * ones(40, 1)]; %! Mdl = fitcnet (X, Y, 'LayerSizes', [8, 8], 'IterationLimit', 400); %! [label, score] = predict (Mdl, [3, 3; -3, -3]); %! assert_equal (label, [1; 2]); %! assert_equal (all (abs (sum (score, 2) - 1) < 0.1), true); %! assert_equal (max (score(1,:)) > 0.8, true); %! assert_equal (max (score(2,:)) > 0.8, true); ## The defaults follow MATLAB: rectified hidden layers and a softmax output. %!test %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]); %! assert_equal (Mdl.Activations, 'relu'); %! assert_equal (Mdl.OutputLayerActivation, 'softmax'); ## Training reports a history that was recorded, one entry per iteration. %!test %! rand ('seed', 42); %! randn ('seed', 42); %! X = [randn(30, 2) * 0.4 + 2; randn(30, 2) * 0.4 - 2]; %! Y = [ones(30, 1); 2 * ones(30, 1)]; %! Mdl = fitcnet (X, Y, 'Solver', 'sgd', 'IterationLimit', 100); %! loss = Mdl.ConvergenceInfo.History.TrainingLoss; %! acc = Mdl.ConvergenceInfo.History.TrainingAccuracy; %! assert_equal (numel (loss), 100); %! assert_equal (numel (acc), 100); %! assert_equal (any (loss != 0), true); %! assert_equal (loss(end) < loss(1), true); %! ## ConvergenceInfo itself reports where the fit ended. %! assert_equal (Mdl.ConvergenceInfo.TrainingLoss, loss(end)); %! assert_equal (Mdl.ConvergenceInfo.Accuracy, acc(end)); %! assert_equal (mean (predict (Mdl, X) == Y) > 0.95, true); ## Every activation trains to a usable posterior on separable data. %!test %! rand ('seed', 7); %! randn ('seed', 7); %! X = [randn(30, 2) * 0.3 + 3; randn(30, 2) * 0.3 - 3]; %! Y = [ones(30, 1); 2 * ones(30, 1)]; %! names = {'linear', 'sigmoid', 'relu', 'tanh', 'lrelu', 'elu', 'gelu'}; %! for k = 1:numel (names) %! Mdl = fitcnet (X, Y, 'LayerSizes', 8, 'Activations', names{k}, ... %! 'IterationLimit', 300); %! [label, score] = predict (Mdl, [3, 3; -3, -3]); %! assert_equal (label, [1; 2]); %! assert_equal (all (isfinite (score(:))), true); %! endfor ## The trained parameters are reachable, one matrix and one bias per layer. %!test %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], ... %! 'LayerSizes', [3, 2], 'IterationLimit', 20); %! assert_equal (numel (Mdl.LayerWeights), 3); %! assert_equal (numel (Mdl.LayerBiases), 3); %! assert_equal (size (Mdl.LayerWeights{1}), [3, 2]); %! assert_equal (size (Mdl.LayerWeights{2}), [2, 3]); %! assert_equal (size (Mdl.LayerWeights{3}), [2, 2]); %! assert_equal (size (Mdl.LayerBiases{1}), [3, 1]); %! assert_equal (size (Mdl.LayerBiases{3}), [2, 1]); ## Cost, Prior, W and the predictor bookkeeping carry their defaults. %!test %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], 'IterationLimit', 20); %! assert_equal (Mdl.Cost, [0, 1; 1, 0]); %! assert_equal (Mdl.Prior, [0.5, 0.5]); %! assert_equal (size (Mdl.W), [4, 1]); %! assert_equal (sum (Mdl.W), 1, 1e-12); %! assert_equal (Mdl.CategoricalPredictors, []); %! assert_equal (Mdl.ExpandedPredictorNames, Mdl.PredictorNames); ## TrainingHistory holds one row per iteration. %!test %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], ... %! 'Solver', 'sgd', 'IterationLimit', 25); %! assert_equal (istable (Mdl.TrainingHistory), true); %! assert_equal (height (Mdl.TrainingHistory), 25); %! assert_equal (Mdl.TrainingHistory.Properties.VariableNames, ... %! {'Iteration', 'TrainingLoss', 'TrainingAccuracy', ... %! 'Time', 'ValidationLoss', 'ValidationChecks'}); %! assert_equal (Mdl.TrainingHistory.Iteration', 1:25); ## A margin is positive wherever the model is right, and edge averages it. %!test %! rand ('seed', 42); %! randn ('seed', 42); %! X = [randn(30, 2) * 0.4 + 2; randn(30, 2) * 0.4 - 2]; %! Y = [ones(30, 1); 2 * ones(30, 1)]; %! Mdl = fitcnet (X, Y, 'IterationLimit', 200); %! m = margin (Mdl, X, Y); %! assert_equal (size (m), [60, 1]); %! assert_equal (all (m > 0), true); %! assert_equal (edge (Mdl, X, Y), mean (m), 1e-12); %! assert_equal (resubMargin (Mdl), m, 1e-12); %! assert_equal (resubEdge (Mdl), edge (Mdl, X, Y, 'Weights', Mdl.W), 1e-12); ## Every documented loss function is accepted and finite. %!test %! rand ('seed', 42); %! randn ('seed', 42); %! X = [randn(30, 2) * 0.4 + 2; randn(30, 2) * 0.4 - 2]; %! Y = [ones(30, 1); 2 * ones(30, 1)]; %! Mdl = fitcnet (X, Y, 'IterationLimit', 200); %! names = {'binodeviance', 'classifcost', 'classiferror', 'crossentropy', ... %! 'exponential', 'hinge', 'logit', 'mincost', 'quadratic'}; %! for k = 1:numel (names) %! L = loss (Mdl, X, Y, 'LossFun', names{k}); %! assert_equal (isscalar (L) && isfinite (L) && L >= 0, true); %! endfor %! assert_equal (loss (Mdl, X, Y), loss (Mdl, X, Y, 'LossFun', 'mincost')); %! assert_equal (resubLoss (Mdl), loss (Mdl, X, Y, 'Weights', Mdl.W), 1e-12); ## Cost is assigned with dot notation, and an empty restores the default. %!test %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 1; 2], 'IterationLimit', 20); %! assert_equal (Mdl.Prior, [0.75, 0.25], 1e-12); %! Mdl.Cost = [0, 2; 1, 0]; %! assert_equal (Mdl.Cost, [0, 2; 1, 0]); %! Mdl.Cost = []; %! assert_equal (Mdl.Cost, [0, 1; 1, 0]); ## Prior is taken at the fit and follows into the observation weights. %!test %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 1; 2], ... %! 'IterationLimit', 20, 'Prior', [0.25, 0.75]); %! assert_equal (Mdl.Prior, [0.25, 0.75]); %! assert_equal (Mdl.W, [0.25/3; 0.25/3; 0.25/3; 0.75], 1e-12); %!error ... %! margin (fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), [1, 2]) %!error ... %! loss (fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), [], [1]) %!error ... %! loss (fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), [1, 2, 3], [1]) %!error ... %! loss (fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), [1, 2], [1; 2]) %!error ... %! loss (fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), [1, 2], 1, 'LossFun', 'bogus') %!error ... %! edge (fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), [1, 2], 1, 'bogus', 1) %!error ... %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]); %! Mdl.Cost = [0, 1, 2]; %!error ... %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]); %! Mdl.Cost = 1:4; ## RowsUsed is empty when every observation was used. %!test %! load fisheriris %! X = meas; %! Y = grp2idx (species); %! Mdl = fitcnet (X, Y, 'IterationLimit', 20); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (class (Mdl.RowsUsed), 'double'); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (rows (Mdl.X), 150); %! assert_equal (rows (Mdl.W), 150); ## A missing response drops its observation and RowsUsed marks it. %!test %! load fisheriris %! X = meas; %! Y = grp2idx (species); %! Y(5) = NaN; %! Mdl = fitcnet (X, Y, 'IterationLimit', 20); %! assert_equal (class (Mdl.RowsUsed), 'logical'); %! assert_equal (size (Mdl.RowsUsed), [150, 1]); %! assert_equal (sum (Mdl.RowsUsed), 149); %! assert_equal (Mdl.RowsUsed(5), false); %! assert_equal (Mdl.NumObservations, 149); %! assert_equal (rows (Mdl.X), 149); %! assert_equal (rows (Mdl.W), 149); ## A missing predictor keeps its observation, so RowsUsed stays empty. %!test %! load fisheriris %! X = meas; %! X(3,2) = NaN; %! Y = grp2idx (species); %! Mdl = fitcnet (X, Y, 'IterationLimit', 20); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (rows (Mdl.X), 150); %! assert_equal (sum (isnan (Mdl.X(:))), 1); ## Mu and Sigma are empty unless the predictors are standardized. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, 'IterationLimit', 20); %! assert_equal (Mdl.Mu, []); %! assert_equal (Mdl.Sigma, []); ## Standardizing weights the complete observations by each class's original ## share of the observation weight. Values from MATLAB R2024a. %!test %! load fisheriris %! X = meas; %! X(3,2) = NaN; X(17,4) = NaN; X(140,1) = NaN; %! Mdl = fitcnet (X, species, 'Standardize', true, 'IterationLimit', 20); %! assert_equal (Mdl.Mu, [5.8405997732426291, 3.0547817460317455, ... %! 3.7612840136054406, 1.1980799319727886], 1e-13); %! assert_equal (Mdl.Sigma, [0.82803317153591371, 0.43533400398915184, ... %! 1.7640762592568813, 0.76297286301694878], 1e-13); ## fitcnet takes Prior and Cost, and the prior reweights the observations. ## Values from R2024a. %!test %! load fisheriris %! i3 = [1:50, 51:80, 101:120]; %! Mdl = fitcnet (meas(i3,:), species(i3), 'IterationLimit', 20, ... %! 'Prior', [0.2, 0.3, 0.5]); %! assert_equal (Mdl.Prior, [0.2, 0.3, 0.5], 1e-14); %! assert_equal (Mdl.W(1), 0.004, 1e-14); %! assert_equal (Mdl.W(51), 0.01, 1e-14); %! assert_equal (Mdl.W(81), 0.025, 1e-14); ## Cost may be assigned after the fit. %!test %! load fisheriris %! i3 = [1:50, 51:80, 101:120]; %! Mdl = fitcnet (meas(i3,:), species(i3), 'IterationLimit', 20); %! Mdl.Cost = [0, 2, 3; 1, 0, 1; 1, 1, 0]; %! assert_equal (Mdl.Cost, [0, 2, 3; 1, 0, 1; 1, 1, 0]); ## Prior is fixed at construction and cannot be assigned afterwards. The ## refusal comes from the property attributes, so the message is core ## Octave's and is not pinned here. %!error ... %! load fisheriris; ... %! Mdl = fitcnet (meas, species, 'IterationLimit', 20); ... %! Mdl.Prior = [0.2, 0.3, 0.5]; ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, 'IterationLimit', 20); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'ClassificationNeuralNetwork'); %! assert_equal (M2.NumObservations, Mdl.NumObservations); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ScoreTransform), class (Mdl.ScoreTransform)); %! assert_equal (predict (M2, meas(1:5,:)), predict (Mdl, meas(1:5,:))); ## BinEdges is an empty cell, which is what MATLAB reports for this ## learner as well: it fits the predictors as they are. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, 'IterationLimit', 10); %! assert_equal (class (Mdl.BinEdges), 'cell'); %! assert_equal (Mdl.BinEdges, {}); ## The shared cost guard is in force here too, and the struct form is ## permuted into this model's class order. The battery is on ## ClassificationDiscriminant. %!test %! load fisheriris %! Mdl = fitcnet (meas, species); %! S = struct ('ClassNames', {{'virginica'; 'setosa'; 'versicolor'}}, ... %! 'ClassificationCosts', [0, 1, 2; 3, 0, 4; 5, 6, 0]); %! Mdl.Cost = S; %! assert_equal (Mdl.Cost, [0, 4, 3; 6, 0, 5; 1, 2, 0]); %!error ... %! load fisheriris %! Mdl = fitcnet (meas, species); %! Mdl.Cost = ones (3); ## HyperparameterOptimizationResults is declared for MATLAB compatibility and ## stays empty, this class running no search over its hyperparameters. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, 'IterationLimit', 20); %! assert_equal (isempty (Mdl.HyperparameterOptimizationResults), true); ## ModelParameters records the fit as it was asked for, not the network that ## came out of it. The field list is MATLAB's where the quantity is the same, ## with OutputLayerActivation, Solver and LearningRate added, which MATLAB has ## no counterpart for. %!test %! load fisheriris %! Mdl = fitcnet (meas, species); %! assert_equal (fieldnames (Mdl.ModelParameters)', {'LayerSizes', ... %! 'Activations', 'OutputLayerActivation', 'LayerWeightsInitializers', ... %! 'Solver', 'LearningRate', 'IterationLimit', 'GradientTolerance', ... %! 'LossTolerance', 'StepTolerance', 'DisplayInfo', 'StandardizeData', ... %! 'Version', 'Method', 'Type'}); %!test %! load fisheriris %! MP = fitcnet (meas, species).ModelParameters; %! assert_equal (MP.LayerSizes, 10); %! assert_equal (MP.Activations, 'relu'); %! assert_equal (MP.OutputLayerActivation, 'softmax'); %! assert_equal (MP.Solver, 'LBFGS'); %! assert_equal (MP.IterationLimit, 1000); %! assert_equal (MP.GradientTolerance, 1e-6); %! assert_equal (MP.LossTolerance, 1e-6); %! assert_equal (MP.StepTolerance, 1e-6); %! assert_equal (MP.StandardizeData, false); %!test %! load fisheriris %! MP = fitcnet (meas, species).ModelParameters; %! assert_equal (MP.Version, 1); %! assert_equal (MP.Method, 'NeuralNetwork'); %! assert_equal (MP.Type, 'classification'); ## Every value tracks the argument that set it, and a per-layer Activations ## stays a cellstr rather than splitting the structure into an array. %!test %! load fisheriris %! MP = fitcnet (meas, species, 'LayerSizes', [5, 3], 'Activations', ... %! {'relu', 'tanh'}, 'Standardize', true, ... %! 'IterationLimit', 50).ModelParameters; %! assert_equal (size (MP), [1, 1]); %! assert_equal (MP.LayerSizes, [5, 3]); %! assert_equal (MP.Activations, {'relu', 'tanh'}); %! assert_equal (MP.StandardizeData, true); %! assert_equal (MP.IterationLimit, 50); ## ModelParameters reports the weight initializer each layer was built with, ## the output layer last. The engine picks it from the activation and it ## cannot be chosen, so the report is the only way to see it. %!test %! load fisheriris %! MP = fitcnet (meas, species).ModelParameters; %! assert_equal (MP.LayerWeightsInitializers, {'he', 'glorot'}); ## A rectifier takes He and a symmetric activation takes Glorot, so a network ## whose layers differ is built with both. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, 'LayerSizes', [5, 3], ... %! 'Activations', {'relu', 'tanh'}); %! assert_equal (Mdl.ModelParameters.LayerWeightsInitializers, ... %! {'he', 'glorot', 'glorot'}); %!test %! load fisheriris %! MP = fitcnet (meas, species, 'Activations', 'sigmoid').ModelParameters; %! assert_equal (MP.LayerWeightsInitializers, {'glorot', 'glorot'}); ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = fitcnet (meas, species); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = fitcnet (meas, species); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/ClassificationPartitionedKernel.m000066400000000000000000000765511524624707500307670ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftp {statistics} ClassificationPartitionedKernel ## ## Cross-validated Gaussian kernel binary classifier. ## ## A @qcode{ClassificationPartitionedKernel} object holds one ## @qcode{ClassificationKernel} per fold of a partition, each fitted to the ## observations the fold trains on. Every @code{kfold} method predicts each ## observation with the fold that held it @emph{out}, so the estimate it ## returns is an out-of-sample one. ## ## A @qcode{ClassificationKernel} stores no copy of its training data and so ## has no resubstitution methods and no @code{compact} form. This class is ## what takes their place: cross-validation is the way a linear model is ## asked how it would do on data it has not seen. ## ## When the fold models carry a whole regularization path, every method ## returns one column per strength, in the order of the @qcode{'Lambda'} ## that was asked for. ## ## Create one with @code{fitclinear} and a cross-validation option, or ## directly. ## ## @seealso{fitclinear, ClassificationKernel, ClassificationPartitionedKernel} ## @end deftp classdef ClassificationPartitionedKernel properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} ClassNames ## ## Names of the two classes ## ## A column of the same type as the response, shared by every fold. ## This property is read-only. ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} Cost ## ## Cost of misclassifying an observation ## ## A square numeric matrix with one row and one column per class. It is ## handed to every fold rather than re-derived by each. This property is ## read-only. ## ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} Prior ## ## Prior probability of each class ## ## A numeric row vector summing to one, in the order of ## @qcode{ClassNames}. Like the cost it is the parent's and is handed to ## every fold, so a fold of an unbalanced problem does not quietly adopt ## a prior of its own. This property is read-only. ## ## @end deftp Prior = []; endproperties properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} ScoreTransform ## ## Transformation applied to the predicted scores ## ## A character vector naming a transformation, or the text of the ## function handle that was supplied, which may be assigned after the ## model is built. It is applied once to the assembled scores and is ## not handed to the folds. A transform the learner implies, as ## @qcode{'logistic'} implies @qcode{'logit'}, does stay with the folds, ## and this one is then applied on top of it. ## ## @end deftp ScoreTransform = 'none'; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} CrossValidatedModel ## ## Name of the model that was cross-validated ## ## Always @qcode{'Linear'}, the short name MATLAB uses. This property is ## read-only. ## ## @end deftp CrossValidatedModel = 'Kernel'; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} NumObservations ## ## Number of observations the partition covers ## ## A positive integer scalar, counting the rows that survived the removal ## of missing values. This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} Y ## ## Response of the retained observations ## ## In the type it was supplied in. This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} W ## ## Observation weights ## ## An @math{Nx1} numeric vector summing to one, normalized within each ## class to that class's cost-adjusted prior. This property is ## read-only. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} PredictorNames ## ## Names of the predictors ## ## A cell array of character vectors. This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A row vector of column indices, empty when every predictor is ## numeric. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} ResponseName ## ## Name of the response ## ## A character vector, defaulting to @qcode{'Y'}. This property is ## read-only. ## ## @end deftp ResponseName = 'Y'; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} Trained ## ## The models fitted to the folds ## ## A cell column with one @qcode{ClassificationKernel} per fold, each ## fitted to the observations its fold trains on. This property is ## read-only. ## ## @end deftp Trained = {}; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} KFold ## ## Number of folds ## ## A positive integer scalar. A holdout partition has one fold and a ## leave-one-out partition has as many as there are observations. This ## property is read-only. ## ## @end deftp KFold = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} Partition ## ## The partition itself ## ## A @code{cvpartition} object over the retained observations. This ## property is read-only. ## ## @end deftp Partition = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedKernel} {property} ModelParameters ## ## What was cross-validated, and how ## ## A structure holding the parameters the folds were fitted with, carried ## through from the learner that was cross validated, beside ## @qcode{NLearn}, the number of folds, and the @qcode{Version}, ## @qcode{Method} and @qcode{Type} tags of this class, with ## @qcode{LearnerTemplates} naming the backing. The ## learner's own tags are replaced rather than kept, so a cross-validated ## SVM reports @qcode{Method} as @qcode{'PartitionedKernel'} and not ## @qcode{'SVM'}. ## ## @strong{Deviation from MATLAB.} MATLAB reports the parameter record of ## the cross-validation @emph{ensemble} here rather than of the learner, ## so it says nothing at all about how the folds were fitted: of its ## eighteen fields only the fold count, its partitioner and a fit template ## carry anything, and the rest are boosting settings left inert. Nor can ## the parameters be reached through the folds, a compact model carrying ## none in MATLAB. This class reports the fit instead, which is strictly ## more than MATLAB offers, and everything MATLAB's record does carry is ## published here as the @qcode{KFold}, @qcode{Partition}, @qcode{X}, ## @qcode{Y}, @qcode{W} and @qcode{CrossValidatedModel} properties. ## ## This property is read-only. ## ## @end deftp ModelParameters = []; endproperties properties (GetAccess = public, SetAccess = protected, Hidden) ## The callable behind ScoreTransform. STfun = @(s) s; ## The predictors of the retained observations. MATLAB does not report ## them and neither do we, but the kfold methods have to predict from ## something and the fold models hold no data of their own. X_ = []; ## Number of regularization strengths the fold models carry, so that ## every method knows how many columns to return without asking one. NumLambda_ = 1; endproperties methods (Access = public) ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedKernel} {@var{obj} =} ClassificationPartitionedKernel (@var{X}, @var{Y}) ## @deftypefnx {ClassificationPartitionedKernel} {@var{obj} =} ClassificationPartitionedKernel (@dots{}, @var{name}, @var{value}) ## ## Cross-validate a linear binary classifier. ## ## @code{@var{obj} = ClassificationPartitionedKernel (@var{X}, @var{Y})} ## partitions the data into ten stratified folds and fits a ## @qcode{ClassificationKernel} to each. ## ## @code{@var{obj} = ClassificationPartitionedKernel (@dots{}, ## @var{name}, @var{value})} takes one of @qcode{'KFold'}, ## @qcode{'Holdout'}, @qcode{'Leaveout'} and @qcode{'CVPartition'} to say ## how to partition, and any option @code{ClassificationKernel} takes to ## say how to fit. @qcode{'CrossVal'} is accepted and has no effect ## here, this class being cross-validated by construction. ## ## The classes, the prior and the cost are resolved once over the whole ## data and handed to every fold. Anything left as @qcode{'auto'} is ## not: each fold resolves @qcode{'Lambda'} and @qcode{'KernelScale'} ## against its own training rows, so ten folds of a hundred ## observations each get a @qcode{Lambda} of one ninetieth rather than ## one hundredth. Both are MATLAB's behaviour, measured. ## ## @seealso{fitclinear, ClassificationKernel} ## @end deftypefn function this = ClassificationPartitionedKernel (X, Y, varargin) if (nargin < 2) error (strcat ("ClassificationPartitionedKernel: too few input", ... " arguments.")); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationPartitionedKernel: optional", ... " arguments must be given in Name-Value pairs.")); endif ## Split the argument list three ways: what says how to partition, ## what the parent owns, and what each fold is fitted with. [P, args] = partitionedArgs (varargin, ... 'ClassificationPartitionedKernel'); F = classFrame (X, Y, P.ClassNames, P.Prior, P.Cost, P.Weights, ... 'ClassificationPartitionedKernel'); [part, args] = cvPartitionOf (args, F.Y, F.n, ... 'ClassificationPartitionedKernel'); ## A transform asked for by name belongs to the parent, which applies ## it once to the assembled scores, and the folds are not given it. ## Measured on R2024a, where a cross-validated model fitted with a ## ScoreTransform reports it and leaves every Trained@{k@} at 'none'. ## A transform the learner implies is a different thing: a logistic ## fold reports posteriors and carries 'logit' of its own, which ## R2024a also does, and the parent's transform composes on top of it. fargs = [args, {'ClassNames', F.ClassNames, 'Prior', F.Prior, ... 'Cost', F.Cost}]; ## Built field by field rather than with one struct call: a cell ## array response passed to struct () makes a struct array of that ## size instead of a scalar struct holding the cell. G = struct (); G.X = F.X; G.Y = F.Y; G.Weights = F.Weights; this.Trained = foldModels ('ClassificationKernel', G, part, fargs); this.ClassNames = F.ClassNames; this.Cost = F.Cost; this.Prior = F.Prior; this.NumObservations = F.n; this.Y = F.Y; this.W = F.W; this.X_ = F.X; this.KFold = part.NumTestSets; this.Partition = part; this.NumLambda_ = numel (this.Trained{1}.Lambda); this.PredictorNames = this.Trained{1}.PredictorNames; this.CategoricalPredictors = this.Trained{1}.CategoricalPredictors; this.ResponseName = this.Trained{1}.ResponseName; if (isempty (P.ScoreTransform)) P.ScoreTransform = 'none'; endif [this.STfun, this.ScoreTransform] = ... parseScoreTransform (P.ScoreTransform, ... 'ClassificationPartitionedKernel'); ## The learner's parameters, under this class's own tags. MATLAB ## reports an EnsembleParams here instead and so says nothing about ## the fit; see the ModelParameters property for the deviation. this.ModelParameters = partitionedModelParams (this.Trained{1}, ... this.KFold, 'PartitionedKernel', ... 'classification', 'Kernel'); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedKernel} {@var{labels} =} kfoldPredict (@var{obj}) ## @deftypefnx {ClassificationPartitionedKernel} {[@var{labels}, @var{scores}] =} kfoldPredict (@var{obj}) ## ## Out-of-fold class of every observation. ## ## Each observation is classified by the fold that held it out, so the ## labels are out-of-sample. An observation that no fold held out, which ## under a holdout partition is most of them, comes back missing rather ## than classified, and its scores come back @qcode{NaN}. ## ## @end deftypefn function [labels, scores] = kfoldPredict (this) [labels, raw] = kfoldScores (this.Trained, this.Partition, this.X_, ... this.Y, this.ClassNames, ... this.NumLambda_); if (nargout > 1) scores = transformScores (this, raw); endif endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedKernel} {@var{m} =} kfoldMargin (@var{obj}) ## ## Out-of-fold classification margin of every observation. ## ## The score the out-of-fold model gives the true class, less the score ## it gives the other one. An observation no fold held out comes back ## @qcode{NaN}. ## ## @end deftypefn function m = kfoldMargin (this) [~, raw] = kfoldScores (this.Trained, this.Partition, this.X_, ... this.Y, this.ClassNames, this.NumLambda_); s = transformScores (this, raw); [gY, errmsg] = labelIndices (this.ClassNames, this.Y); if (! isempty (errmsg)) error ("ClassificationPartitionedKernel.kfoldMargin: %s", errmsg); endif m = marginsOf (s, gY, this.NumLambda_); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedKernel} {@var{e} =} kfoldEdge (@var{obj}) ## @deftypefnx {ClassificationPartitionedKernel} {@var{e} =} kfoldEdge (@dots{}, @var{name}, @var{value}) ## ## Weighted mean of the out-of-fold classification margins. ## ## @code{@var{e} = kfoldEdge (@dots{}, @var{name}, @var{value})} takes ## @qcode{'Folds'}, a subset of the folds to average over, and ## @qcode{'Mode'}, either @qcode{'average'}, the default, or ## @qcode{'individual'}, which returns one row per fold instead. ## ## @end deftypefn function e = kfoldEdge (this, varargin) O = kfoldOpts (varargin, {}, 'ClassificationPartitionedKernel', ... 'kfoldEdge', this.KFold); [~, raw] = kfoldScores (this.Trained, this.Partition, this.X_, ... this.Y, this.ClassNames, this.NumLambda_); s = transformScores (this, raw); [gY, errmsg] = labelIndices (this.ClassNames, this.Y); if (! isempty (errmsg)) error ("ClassificationPartitionedKernel.kfoldEdge: %s", errmsg); endif m = marginsOf (s, gY, this.NumLambda_); sets = foldSets (this.Partition, O.Folds, O.Mode, ... this.NumObservations); e = zeros (numel (sets), this.NumLambda_); for i = 1:numel (sets) idx = sets{i}; w = this.W(idx); w = w / sum (w); e(i,:) = sum (w .* m(idx,:), 1); endfor endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedKernel} {@var{l} =} kfoldLoss (@var{obj}) ## @deftypefnx {ClassificationPartitionedKernel} {@var{l} =} kfoldLoss (@dots{}, @var{name}, @var{value}) ## ## Out-of-fold classification loss. ## ## @code{@var{l} = kfoldLoss (@var{obj})} returns the out-of-fold ## misclassification rate. ## ## @code{@var{l} = kfoldLoss (@dots{}, @var{name}, @var{value})} takes ## @qcode{'LossFun'}, one of @qcode{'binodeviance'}, ## @qcode{'classifcost'}, @qcode{'classiferror'}, @qcode{'exponential'}, ## @qcode{'hinge'}, @qcode{'logit'}, @qcode{'mincost'} and ## @qcode{'quadratic'}; @qcode{'Folds'}; and @qcode{'Mode'}. ## ## @end deftypefn function l = kfoldLoss (this, varargin) valid = {'binodeviance', 'classifcost', 'classiferror', ... 'exponential', 'hinge', 'logit', 'mincost', 'quadratic'}; O = kfoldOpts (varargin, valid, 'ClassificationPartitionedKernel', ... 'kfoldLoss', this.KFold); if (isempty (O.LossFun)) O.LossFun = 'classiferror'; endif [~, raw] = kfoldScores (this.Trained, this.Partition, this.X_, ... this.Y, this.ClassNames, this.NumLambda_); s = transformScores (this, raw); [gY, errmsg] = labelIndices (this.ClassNames, this.Y); if (! isempty (errmsg)) error ("ClassificationPartitionedKernel.kfoldLoss: %s", errmsg); endif sets = foldSets (this.Partition, O.Folds, O.Mode, ... this.NumObservations); l = zeros (numel (sets), this.NumLambda_); for i = 1:numel (sets) idx = sets{i}; w = this.W(idx); w = w / sum (w); for k = 1:this.NumLambda_ if (this.NumLambda_ == 1) sk = s(idx,:); else sk = s(idx,:,k); endif l(i,k) = classificationLoss (O.LossFun, sk, gY(idx), w, this.Cost); endfor endfor endfunction endmethods methods (Access = public, Hidden) function this = set.ScoreTransform (this, val) [f, nm] = parseScoreTransform (val, 'ClassificationPartitionedKernel'); this.ScoreTransform = nm; this.STfun = f; endfunction function display (this) in_name = inputname (1); if (! isempty (in_name)) printf ('%s =\n', in_name); endif disp (this); endfunction function disp (this) printf ("\n ClassificationPartitionedKernel\n\n"); printf ("%+25s: '%s'\n", 'CrossValidatedModel', ... this.CrossValidatedModel); printf ("%+25s: %s\n", 'ClassNames', ... classNameListing (this.ClassNames)); printf ("%+25s: '%s'\n", 'ScoreTransform', this.ScoreTransform); printf ("%+25s: %d\n", 'NumObservations', this.NumObservations); printf ("%+25s: %d\n", 'KFold', this.KFold); printf ("\n"); endfunction endmethods methods (Access = private) ## Apply the parent's score transform to the assembled scores, whatever ## shape they came back in. function s = transformScores (this, raw) if (this.NumLambda_ == 1) s = this.STfun (raw); else s = raw; for k = 1:this.NumLambda_ s(:,:,k) = this.STfun (raw(:,:,k)); endfor endif endfunction endmethods endclassdef %!demo %! ## Cross-validate a Gaussian kernel classifier on the two overlapping %! ## iris species and read the out-of-sample error rate. %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = ClassificationPartitionedKernel (X, Y, 'KFold', 5) %! outOfSample = kfoldLoss (CVMdl) %!test %! ## The model reports the surface MATLAB reports %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = ClassificationPartitionedKernel (X, Y, 'KFold', 5); %! assert_equal (class (CVMdl), 'ClassificationPartitionedKernel'); %! assert_equal (CVMdl.CrossValidatedModel, 'Kernel'); %! assert_equal (CVMdl.KFold, 5); %! assert_equal (CVMdl.NumObservations, 100); %! assert_equal (CVMdl.ClassNames, {'versicolor'; 'virginica'}); %! assert_equal (CVMdl.Prior, [0.5, 0.5]); %! assert_equal (class (CVMdl.Trained{1}), 'ClassificationKernel'); %! assert_equal (CVMdl.ModelParameters.Method, 'PartitionedKernel'); %! assert_equal (CVMdl.ModelParameters.LearnerTemplates, 'Kernel'); %! assert_equal (CVMdl.ModelParameters.NLearn, 5); %!test %! ## The properties are the ones MATLAB lists, in its order %! load fisheriris %! CVMdl = ClassificationPartitionedKernel (meas(51:end,:), species(51:end)); %! assert_equal (sort (properties (CVMdl)), ... %! sort ({'ClassNames'; 'Cost'; 'Prior'; 'ScoreTransform'; ... %! 'CrossValidatedModel'; 'NumObservations'; 'Y'; 'W'; ... %! 'PredictorNames'; 'CategoricalPredictors'; ... %! 'ResponseName'; 'Trained'; 'KFold'; 'Partition'; ... %! 'ModelParameters'})); %!test %! ## Each fold resolves 'Lambda' against its own training rows, which is %! ## one eightieth of five folds over a hundred observations. R2024a's %! ## number. %! load fisheriris %! CVMdl = ClassificationPartitionedKernel (meas(51:end,:), ... %! species(51:end), 'KFold', 5); %! assert_equal (CVMdl.Trained{1}.Lambda, 1 / 80, 1e-15); %! assert_equal (CVMdl.Trained{1}.NumExpansionDimensions, 128); %! assert_equal (CVMdl.Trained{1}.KernelScale, 1); %!test %! ## Every fold draws its own basis, so two folds hold different %! ## expansions of the same kernel %! load fisheriris %! CVMdl = ClassificationPartitionedKernel (meas(51:end,:), ... %! species(51:end), 'KFold', 5); %! X = meas(51:53,:); %! [~, s1] = predict (CVMdl.Trained{1}, X); %! [~, s2] = predict (CVMdl.Trained{2}, X); %! assert_equal (isequal (s1, s2), false); %!test %! ## kfoldPredict classifies each observation with the fold that held it %! ## out, and doing the same partition by hand agrees %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! part = cvpartition (Y, 'KFold', 4); %! CVMdl = ClassificationPartitionedKernel (X, Y, 'CVPartition', part); %! label = kfoldPredict (CVMdl); %! byhand = cell (100, 1); %! for k = 1:4 %! te = test (part, k); %! byhand(te) = predict (CVMdl.Trained{k}, X(te,:)); %! endfor %! assert_equal (label, byhand); %!test %! ## The fit separates the two species out of sample %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = ClassificationPartitionedKernel (X, Y, 'KFold', 5); %! assert_equal (kfoldLoss (CVMdl) < 0.2, true); %! assert_equal (kfoldEdge (CVMdl) > 0, true); %! assert_equal (numel (kfoldMargin (CVMdl)), 100); %!test %! ## 'Mode', 'individual' gives one row per fold, and averaging pools the %! ## observations rather than the fold values %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = ClassificationPartitionedKernel (X, Y, 'KFold', 5); %! assert_equal (size (kfoldLoss (CVMdl, 'Mode', 'individual')), [5, 1]); %! label = kfoldPredict (CVMdl); %! assert_equal (kfoldLoss (CVMdl), mean (! strcmp (label, Y)), 1e-12); %!test %! ## A logistic fit reports posteriors, and the transform stays with the %! ## fold models as it does in MATLAB %! load fisheriris %! CVMdl = ClassificationPartitionedKernel (meas(51:end,:), ... %! species(51:end), 'KFold', 5, ... %! 'Learner', 'logistic'); %! assert_equal (CVMdl.ScoreTransform, 'none'); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'logit'); %! [~, score] = kfoldPredict (CVMdl); %! assert_equal (sum (score, 2), ones (100, 1), 1e-12); %!test %! ## An observation that no fold held out is not classified %! load fisheriris %! CVMdl = ClassificationPartitionedKernel (meas(51:end,:), ... %! species(51:end), 'Holdout', 0.3); %! assert_equal (CVMdl.KFold, 1); %! [label, score] = kfoldPredict (CVMdl); %! assert_equal (sum (cellfun (@isempty, label)), 70); %! assert_equal (sum (isnan (score(:,1))), 70); %!test %! ## A character matrix response carries through cross-validation %! load fisheriris %! X = meas(51:end,:); %! Yc = species(51:end); %! part = cvpartition (Yc, 'KFold', 4); %! CVm = ClassificationPartitionedKernel (X, char (Yc), 'CVPartition', part); %! assert_equal (size (CVm.ClassNames), [2, 10]); %! assert_equal (cellstr (CVm.ClassNames), {'versicolor'; 'virginica'}); %! assert_equal (size (kfoldPredict (CVm)), [100, 10]); %! assert_equal (isfinite (kfoldLoss (CVm)), true); %! assert_equal (isfinite (kfoldEdge (CVm)), true); %!test %! ## A transform asked for by name goes to the parent and not to the folds, %! ## and is applied once to the assembled scores. R2024a's arrangement. %! ## The baseline is read from the same object with the transform switched %! ## off, a second fit being no baseline at all here: the random feature %! ## expansion differs between two fits of the same data. %! load fisheriris %! CVMdl = ClassificationPartitionedKernel (meas(51:end,:), species(51:end), ... %! 'KFold', 5, ... %! 'ScoreTransform', 'doublelogit'); %! assert_equal (CVMdl.ScoreTransform, 'doublelogit'); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'none'); %! [~, s1] = kfoldPredict (CVMdl); %! CVMdl.ScoreTransform = 'none'; %! [~, s0] = kfoldPredict (CVMdl); %! assert_equal (s1, 1 ./ (1 + exp (-2 * s0)), 1e-12); %!test %! ## It can be assigned after the model is built, and reaches kfoldPredict %! ## without being carried into the folds %! load fisheriris %! CVMdl = ClassificationPartitionedKernel (meas(51:end,:), species(51:end), ... %! 'KFold', 5); %! [~, s0] = kfoldPredict (CVMdl); %! CVMdl.ScoreTransform = 'doublelogit'; %! [~, s1] = kfoldPredict (CVMdl); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'none'); %! assert_equal (s1, 1 ./ (1 + exp (-2 * s0)), 1e-12); %!test %! ## A transform the learner implies stays with the folds, and an assigned %! ## one is applied on top of it rather than replacing it. Measured on %! ## R2024a, where the folds keep 'logit' and the parent's transform %! ## composes. %! load fisheriris %! CVMdl = ClassificationPartitionedKernel (meas(51:end,:), species(51:end), ... %! 'KFold', 5, ... %! 'Learner', 'logistic'); %! [~, s0] = kfoldPredict (CVMdl); %! CVMdl.ScoreTransform = 'doublelogit'; %! [~, s1] = kfoldPredict (CVMdl); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'logit'); %! assert_equal (s1, 1 ./ (1 + exp (-2 * s0)), 1e-12); %!test %! ## 'none' is the identity, so assigning it transforms nothing %! load fisheriris %! CVMdl = ClassificationPartitionedKernel (meas(51:end,:), species(51:end), ... %! 'KFold', 5); %! [~, s0] = kfoldPredict (CVMdl); %! CVMdl.ScoreTransform = 'none'; %! [~, s1] = kfoldPredict (CVMdl); %! assert_equal (s1, s0); %!error ... %! load fisheriris %! CVMdl = ClassificationPartitionedKernel (meas(51:end,:), species(51:end), ... %! 'KFold', 5); %! CVMdl.ScoreTransform = 'nosuchtransform'; ## Test input validation %!error ... %! ClassificationPartitionedKernel (ones (10, 2)) %!error ... %! ClassificationPartitionedKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'KFold') %!error ... %! ClassificationPartitionedKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'KFold', 0) %!error ... %! ClassificationPartitionedKernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'KFold', 2, 'Leaveout', 'on') %!error ... %! kfoldLoss (ClassificationPartitionedKernel (ones (10, 2), ... %! [ones(5,1); 2*ones(5,1)], 'KFold', 2), 'LossFun', 'mse') %!error ... %! kfoldEdge (ClassificationPartitionedKernel (ones (10, 2), ... %! [ones(5,1); 2*ones(5,1)], 'KFold', 2), 'Folds', 0) ## ModelParameters carries the learner's parameters beside the tags this ## class reports for itself. %!test %! load fisheriris %! CVMdl = fitckernel (meas, strcmp (species, 'setosa'), 'KFold', 3); %! MP = CVMdl.ModelParameters; %! assert_equal (MP.Method, 'PartitionedKernel'); %! assert_equal (MP.LearnerTemplates, 'Kernel'); %! assert_equal (MP.NLearn, 3); %! assert_equal (MP.Learner, 'svm'); %! assert_equal (MP.BlockSize, 4000); ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = fitckernel (meas, strcmp (species, 'setosa'), 'KFold', 3); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = kfoldPredict (Mdl); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = kfoldPredict (Mdl); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = kfoldPredict (Mdl); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = kfoldPredict (Mdl); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = fitckernel (meas, strcmp (species, 'setosa'), 'KFold', 3); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = kfoldPredict (Mdl); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = kfoldPredict (Mdl); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/ClassificationPartitionedLinear.m000066400000000000000000001153061524624707500307510ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftp {statistics} ClassificationPartitionedLinear ## ## Cross-validated linear binary classifier. ## ## A @qcode{ClassificationPartitionedLinear} object holds one ## @qcode{ClassificationLinear} per fold of a partition, each fitted to the ## observations the fold trains on. Every @code{kfold} method predicts each ## observation with the fold that held it @emph{out}, so the estimate it ## returns is an out-of-sample one. ## ## A @qcode{ClassificationLinear} stores no copy of its training data and so ## has no resubstitution methods and no @code{compact} form. This class is ## what takes their place: cross-validation is the way a linear model is ## asked how it would do on data it has not seen. ## ## When the fold models carry a whole regularization path, every method ## returns one column per strength, in the order of the @qcode{'Lambda'} ## that was asked for. ## ## Create one with @code{fitclinear} and a cross-validation option, or ## directly. ## ## @seealso{fitclinear, ClassificationLinear, ClassificationPartitionedKernel} ## @end deftp classdef ClassificationPartitionedLinear properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} ClassNames ## ## Names of the two classes ## ## A column of the same type as the response, shared by every fold. ## This property is read-only. ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} Cost ## ## Cost of misclassifying an observation ## ## A square numeric matrix with one row and one column per class. It is ## handed to every fold rather than re-derived by each. This property is ## read-only. ## ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} Prior ## ## Prior probability of each class ## ## A numeric row vector summing to one, in the order of ## @qcode{ClassNames}. Like the cost it is the parent's and is handed to ## every fold, so a fold of an unbalanced problem does not quietly adopt ## a prior of its own. This property is read-only. ## ## @end deftp Prior = []; endproperties properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} ScoreTransform ## ## Transformation applied to the predicted scores ## ## A character vector naming a transformation, or the text of the ## function handle that was supplied, which may be assigned after the ## model is built. It is applied once to the assembled scores and is ## not handed to the folds. A transform the learner implies, as ## @qcode{'logistic'} implies @qcode{'logit'}, does stay with the folds, ## and this one is then applied on top of it. ## ## @end deftp ScoreTransform = 'none'; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} CrossValidatedModel ## ## Name of the model that was cross-validated ## ## Always @qcode{'Linear'}, the short name MATLAB uses. This property is ## read-only. ## ## @end deftp CrossValidatedModel = 'Linear'; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} NumObservations ## ## Number of observations the partition covers ## ## A positive integer scalar, counting the rows that survived the removal ## of missing values. This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} Y ## ## Response of the retained observations ## ## In the type it was supplied in. This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} W ## ## Observation weights ## ## An @math{Nx1} numeric vector summing to one, normalized within each ## class to that class's cost-adjusted prior. This property is ## read-only. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} PredictorNames ## ## Names of the predictors ## ## A cell array of character vectors. This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A row vector of column indices, empty when every predictor is ## numeric. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} ResponseName ## ## Name of the response ## ## A character vector, defaulting to @qcode{'Y'}. This property is ## read-only. ## ## @end deftp ResponseName = 'Y'; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} Trained ## ## The models fitted to the folds ## ## A cell column with one @qcode{ClassificationLinear} per fold, each ## fitted to the observations its fold trains on. This property is ## read-only. ## ## @end deftp Trained = {}; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} KFold ## ## Number of folds ## ## A positive integer scalar. A holdout partition has one fold and a ## leave-one-out partition has as many as there are observations. This ## property is read-only. ## ## @end deftp KFold = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} Partition ## ## The partition itself ## ## A @code{cvpartition} object over the retained observations. This ## property is read-only. ## ## @end deftp Partition = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedLinear} {property} ModelParameters ## ## What was cross-validated, and how ## ## A structure holding the parameters the folds were fitted with, carried ## through from the learner that was cross validated, beside ## @qcode{NLearn}, the number of folds, and the @qcode{Version}, ## @qcode{Method} and @qcode{Type} tags of this class, with ## @qcode{LearnerTemplates} naming the backing. The ## learner's own tags are replaced rather than kept, so a cross-validated ## SVM reports @qcode{Method} as @qcode{'PartitionedLinear'} and not ## @qcode{'SVM'}. ## ## @strong{Deviation from MATLAB.} MATLAB reports the parameter record of ## the cross-validation @emph{ensemble} here rather than of the learner, ## so it says nothing at all about how the folds were fitted: of its ## eighteen fields only the fold count, its partitioner and a fit template ## carry anything, and the rest are boosting settings left inert. Nor can ## the parameters be reached through the folds, a compact model carrying ## none in MATLAB. This class reports the fit instead, which is strictly ## more than MATLAB offers, and everything MATLAB's record does carry is ## published here as the @qcode{KFold}, @qcode{Partition}, @qcode{X}, ## @qcode{Y}, @qcode{W} and @qcode{CrossValidatedModel} properties. ## ## This property is read-only. ## ## @end deftp ModelParameters = []; endproperties properties (GetAccess = public, SetAccess = protected, Hidden) ## The callable behind ScoreTransform. STfun = @(s) s; ## The predictors of the retained observations. MATLAB does not report ## them and neither do we, but the kfold methods have to predict from ## something and the fold models hold no data of their own. X_ = []; ## Number of regularization strengths the fold models carry, so that ## every method knows how many columns to return without asking one. NumLambda_ = 1; endproperties methods (Access = public) ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedLinear} {@var{obj} =} ClassificationPartitionedLinear (@var{X}, @var{Y}) ## @deftypefnx {ClassificationPartitionedLinear} {@var{obj} =} ClassificationPartitionedLinear (@dots{}, @var{name}, @var{value}) ## ## Cross-validate a linear binary classifier. ## ## @code{@var{obj} = ClassificationPartitionedLinear (@var{X}, @var{Y})} ## partitions the data into ten stratified folds and fits a ## @qcode{ClassificationLinear} to each. ## ## @code{@var{obj} = ClassificationPartitionedLinear (@dots{}, ## @var{name}, @var{value})} takes one of @qcode{'KFold'}, ## @qcode{'Holdout'}, @qcode{'Leaveout'} and @qcode{'CVPartition'} to say ## how to partition, and any option @code{ClassificationLinear} takes to ## say how to fit. @qcode{'CrossVal'} is accepted and has no effect ## here, this class being cross-validated by construction. ## ## The classes, the prior and the cost are resolved once over the whole ## data and handed to every fold. Anything left as @qcode{'auto'} is ## not: each fold resolves @qcode{'Lambda'} against its own training ## rows, so ten folds of a hundred observations each get one ninetieth ## rather than one hundredth. Both are MATLAB's behaviour, measured. ## ## @seealso{fitclinear, ClassificationLinear} ## @end deftypefn function this = ClassificationPartitionedLinear (X, Y, varargin) if (nargin < 2) error (strcat ("ClassificationPartitionedLinear: too few input", ... " arguments.")); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationPartitionedLinear: optional", ... " arguments must be given in Name-Value pairs.")); endif ## Split the argument list three ways: what says how to partition, ## what the parent owns, and what each fold is fitted with. [P, args] = partitionedArgs (varargin, ... 'ClassificationPartitionedLinear'); F = classFrame (X, Y, P.ClassNames, P.Prior, P.Cost, P.Weights, ... 'ClassificationPartitionedLinear'); [part, args] = cvPartitionOf (args, F.Y, F.n, ... 'ClassificationPartitionedLinear'); ## A transform asked for by name belongs to the parent, which applies ## it once to the assembled scores, and the folds are not given it. ## Measured on R2024a, where a cross-validated model fitted with a ## ScoreTransform reports it and leaves every Trained@{k@} at 'none'. ## A transform the learner implies is a different thing: a logistic ## fold reports posteriors and carries 'logit' of its own, which ## R2024a also does, and the parent's transform composes on top of it. fargs = [args, {'ClassNames', F.ClassNames, 'Prior', F.Prior, ... 'Cost', F.Cost}]; ## Built field by field rather than with one struct call: a cell ## array response passed to struct () makes a struct array of that ## size instead of a scalar struct holding the cell. G = struct (); G.X = F.X; G.Y = F.Y; G.Weights = F.Weights; this.Trained = foldModels ('ClassificationLinear', G, part, fargs); this.ClassNames = F.ClassNames; this.Cost = F.Cost; this.Prior = F.Prior; this.NumObservations = F.n; this.Y = F.Y; this.W = F.W; this.X_ = F.X; this.KFold = part.NumTestSets; this.Partition = part; this.NumLambda_ = numel (this.Trained{1}.Lambda); this.PredictorNames = this.Trained{1}.PredictorNames; this.CategoricalPredictors = this.Trained{1}.CategoricalPredictors; this.ResponseName = this.Trained{1}.ResponseName; if (isempty (P.ScoreTransform)) P.ScoreTransform = 'none'; endif [this.STfun, this.ScoreTransform] = ... parseScoreTransform (P.ScoreTransform, ... 'ClassificationPartitionedLinear'); ## The learner's parameters, under this class's own tags. MATLAB ## reports an EnsembleParams here instead and so says nothing about ## the fit; see the ModelParameters property for the deviation. this.ModelParameters = partitionedModelParams (this.Trained{1}, ... this.KFold, 'PartitionedLinear', ... 'classification', 'Linear'); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedLinear} {@var{labels} =} kfoldPredict (@var{obj}) ## @deftypefnx {ClassificationPartitionedLinear} {[@var{labels}, @var{scores}] =} kfoldPredict (@var{obj}) ## ## Out-of-fold class of every observation. ## ## Each observation is classified by the fold that held it out, so the ## labels are out-of-sample. An observation that no fold held out, which ## under a holdout partition is most of them, comes back missing rather ## than classified, and its scores come back @qcode{NaN}. ## ## With @math{L} regularization strengths @var{labels} has one column per ## strength and @var{scores} is @math{Nx2xL}. ## ## @end deftypefn function [labels, scores] = kfoldPredict (this) [labels, raw] = kfoldScores (this.Trained, this.Partition, this.X_, ... this.Y, this.ClassNames, ... this.NumLambda_); if (nargout > 1) scores = transformScores (this, raw); endif endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedLinear} {@var{m} =} kfoldMargin (@var{obj}) ## ## Out-of-fold classification margin of every observation. ## ## The score the out-of-fold model gives the true class, less the score ## it gives the other one. An observation no fold held out comes back ## @qcode{NaN}. With @math{L} regularization strengths @var{m} has one ## column per strength. ## ## @end deftypefn function m = kfoldMargin (this) [~, raw] = kfoldScores (this.Trained, this.Partition, this.X_, ... this.Y, this.ClassNames, this.NumLambda_); s = transformScores (this, raw); [gY, errmsg] = labelIndices (this.ClassNames, this.Y); if (! isempty (errmsg)) error ("ClassificationPartitionedLinear.kfoldMargin: %s", errmsg); endif m = marginsOf (s, gY, this.NumLambda_); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedLinear} {@var{e} =} kfoldEdge (@var{obj}) ## @deftypefnx {ClassificationPartitionedLinear} {@var{e} =} kfoldEdge (@dots{}, @var{name}, @var{value}) ## ## Weighted mean of the out-of-fold classification margins. ## ## @code{@var{e} = kfoldEdge (@dots{}, @var{name}, @var{value})} takes ## @qcode{'Folds'}, a subset of the folds to average over, and ## @qcode{'Mode'}, either @qcode{'average'}, the default, or ## @qcode{'individual'}, which returns one row per fold instead. ## ## @end deftypefn function e = kfoldEdge (this, varargin) O = kfoldOpts (varargin, {}, 'ClassificationPartitionedLinear', ... 'kfoldEdge', this.KFold); [~, raw] = kfoldScores (this.Trained, this.Partition, this.X_, ... this.Y, this.ClassNames, this.NumLambda_); s = transformScores (this, raw); [gY, errmsg] = labelIndices (this.ClassNames, this.Y); if (! isempty (errmsg)) error ("ClassificationPartitionedLinear.kfoldEdge: %s", errmsg); endif m = marginsOf (s, gY, this.NumLambda_); sets = foldSets (this.Partition, O.Folds, O.Mode, ... this.NumObservations); e = zeros (numel (sets), this.NumLambda_); for i = 1:numel (sets) idx = sets{i}; w = this.W(idx); w = w / sum (w); e(i,:) = sum (w .* m(idx,:), 1); endfor endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedLinear} {@var{l} =} kfoldLoss (@var{obj}) ## @deftypefnx {ClassificationPartitionedLinear} {@var{l} =} kfoldLoss (@dots{}, @var{name}, @var{value}) ## ## Out-of-fold classification loss. ## ## @code{@var{l} = kfoldLoss (@var{obj})} returns the out-of-fold ## misclassification rate. ## ## @code{@var{l} = kfoldLoss (@dots{}, @var{name}, @var{value})} takes ## @qcode{'LossFun'}, one of @qcode{'binodeviance'}, ## @qcode{'classifcost'}, @qcode{'classiferror'}, @qcode{'exponential'}, ## @qcode{'hinge'}, @qcode{'logit'}, @qcode{'mincost'} and ## @qcode{'quadratic'}; @qcode{'Folds'}; and @qcode{'Mode'}. ## ## @end deftypefn function l = kfoldLoss (this, varargin) valid = {'binodeviance', 'classifcost', 'classiferror', ... 'exponential', 'hinge', 'logit', 'mincost', 'quadratic'}; O = kfoldOpts (varargin, valid, 'ClassificationPartitionedLinear', ... 'kfoldLoss', this.KFold); if (isempty (O.LossFun)) O.LossFun = 'classiferror'; endif [~, raw] = kfoldScores (this.Trained, this.Partition, this.X_, ... this.Y, this.ClassNames, this.NumLambda_); s = transformScores (this, raw); [gY, errmsg] = labelIndices (this.ClassNames, this.Y); if (! isempty (errmsg)) error ("ClassificationPartitionedLinear.kfoldLoss: %s", errmsg); endif sets = foldSets (this.Partition, O.Folds, O.Mode, ... this.NumObservations); l = zeros (numel (sets), this.NumLambda_); for i = 1:numel (sets) idx = sets{i}; w = this.W(idx); w = w / sum (w); for k = 1:this.NumLambda_ if (this.NumLambda_ == 1) sk = s(idx,:); else sk = s(idx,:,k); endif l(i,k) = classificationLoss (O.LossFun, sk, gY(idx), w, this.Cost); endfor endfor endfunction endmethods methods (Access = public, Hidden) function this = set.ScoreTransform (this, val) [f, nm] = parseScoreTransform (val, 'ClassificationPartitionedLinear'); this.ScoreTransform = nm; this.STfun = f; endfunction function display (this) in_name = inputname (1); if (! isempty (in_name)) printf ('%s =\n', in_name); endif disp (this); endfunction function disp (this) printf ("\n ClassificationPartitionedLinear\n\n"); printf ("%+25s: '%s'\n", 'CrossValidatedModel', ... this.CrossValidatedModel); printf ("%+25s: %s\n", 'ClassNames', ... classNameListing (this.ClassNames)); printf ("%+25s: '%s'\n", 'ScoreTransform', this.ScoreTransform); printf ("%+25s: %d\n", 'NumObservations', this.NumObservations); printf ("%+25s: %d\n", 'KFold', this.KFold); printf ("\n"); endfunction endmethods methods (Access = private) ## Apply the parent's score transform to the assembled scores, whatever ## shape they came back in. function s = transformScores (this, raw) if (this.NumLambda_ == 1) s = this.STfun (raw); else s = raw; for k = 1:this.NumLambda_ s(:,:,k) = this.STfun (raw(:,:,k)); endfor endif endfunction endmethods endclassdef %!demo %! ## Cross-validate a linear classifier on the two overlapping iris %! ## species and read the out-of-sample error rate. %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = ClassificationPartitionedLinear (X, Y, 'KFold', 5) %! outOfSample = kfoldLoss (CVMdl) %!test %! ## The model reports the surface MATLAB reports %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = ClassificationPartitionedLinear (X, Y, 'KFold', 5); %! assert_equal (class (CVMdl), 'ClassificationPartitionedLinear'); %! assert_equal (CVMdl.CrossValidatedModel, 'Linear'); %! assert_equal (CVMdl.KFold, 5); %! assert_equal (CVMdl.NumObservations, 100); %! assert_equal (CVMdl.ClassNames, {'versicolor'; 'virginica'}); %! assert_equal (CVMdl.Prior, [0.5, 0.5]); %! assert_equal (CVMdl.Cost, [0, 1; 1, 0]); %! assert_equal (CVMdl.ScoreTransform, 'none'); %! assert_equal (CVMdl.ResponseName, 'Y'); %! assert_equal (CVMdl.PredictorNames, {'x1', 'x2', 'x3', 'x4'}); %! assert_equal (size (CVMdl.Trained), [5, 1]); %! assert_equal (class (CVMdl.Trained{1}), 'ClassificationLinear'); %! assert_equal (sum (CVMdl.W), 1, 1e-12); %!test %! ## The properties are the ones MATLAB lists, in its order %! load fisheriris %! CVMdl = ClassificationPartitionedLinear (meas(51:end,:), species(51:end)); %! assert_equal (sort (properties (CVMdl)), ... %! sort ({'ClassNames'; 'Cost'; 'Prior'; 'ScoreTransform'; ... %! 'CrossValidatedModel'; 'NumObservations'; 'Y'; 'W'; ... %! 'PredictorNames'; 'CategoricalPredictors'; ... %! 'ResponseName'; 'Trained'; 'KFold'; 'Partition'; ... %! 'ModelParameters'})); %!test %! ## The default is ten stratified folds %! load fisheriris %! CVMdl = ClassificationPartitionedLinear (meas(51:end,:), species(51:end)); %! assert_equal (CVMdl.KFold, 10); %! assert_equal (numel (CVMdl.Trained), 10); %!test %! ## Each fold resolves 'Lambda' against its own training rows, not the %! ## parent's count: eighty of a hundred observations train each fold of %! ## five, so the strength is one eightieth. This is R2024a's number. %! load fisheriris %! CVMdl = ClassificationPartitionedLinear (meas(51:end,:), ... %! species(51:end), 'KFold', 5); %! assert_equal (CVMdl.Trained{1}.Lambda, 1 / 80, 1e-15); %! assert_equal (CVMdl.Trained{3}.Lambda, 1 / 80, 1e-15); %!test %! ## The class names, the prior and the cost are the parent's and are %! ## handed to every fold, so an unbalanced problem does not give each fold %! ## a prior of its own. Also R2024a's behaviour. %! load fisheriris %! X = meas([51:100, 101:120],:); %! Y = species([51:100, 101:120]); %! CVMdl = ClassificationPartitionedLinear (X, Y, 'KFold', 5); %! assert_equal (CVMdl.Prior, [50/70, 20/70], 1e-12); %! assert_equal (CVMdl.Trained{1}.Prior, CVMdl.Prior); %! assert_equal (CVMdl.Trained{4}.Prior, CVMdl.Prior); %! assert_equal (CVMdl.Trained{1}.ClassNames, CVMdl.ClassNames); %!test %! ## kfoldPredict predicts each observation with the fold that held it out. %! ## Doing the same partition by hand must give the same answers, which is %! ## what pins the assembly rather than the fit. %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! part = cvpartition (Y, 'KFold', 4); %! CVMdl = ClassificationPartitionedLinear (X, Y, 'CVPartition', part); %! [label, score] = kfoldPredict (CVMdl); %! byhand = cell (100, 1); %! for k = 1:4 %! tr = training (part, k); %! te = test (part, k); %! m = ClassificationLinear (X(tr,:), Y(tr), 'ClassNames', ... %! CVMdl.ClassNames, 'Prior', CVMdl.Prior, ... %! 'Cost', CVMdl.Cost); %! byhand(te) = predict (m, X(te,:)); %! endfor %! assert_equal (label, byhand); %! assert_equal (size (score), [100, 2]); %!test %! ## The margin is the out-of-fold score of the true class less the other, %! ## and the edge its weighted mean %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = ClassificationPartitionedLinear (X, Y, 'KFold', 5); %! [~, score] = kfoldPredict (CVMdl); %! m = kfoldMargin (CVMdl); %! virg = strcmp (Y, 'virginica'); %! expect = score(:,1) - score(:,2); %! expect(virg) = score(virg,2) - score(virg,1); %! assert_equal (m, expect, 1e-12); %! assert_equal (mean (m > 0), 1 - kfoldLoss (CVMdl), 1e-12); %!test %! ## 'Mode', 'individual' gives one row per fold %! load fisheriris %! CVMdl = ClassificationPartitionedLinear (meas(51:end,:), ... %! species(51:end), 'KFold', 5); %! assert_equal (size (kfoldLoss (CVMdl, 'Mode', 'individual')), [5, 1]); %! assert_equal (size (kfoldEdge (CVMdl, 'Mode', 'individual')), [5, 1]); %!test %! ## Averaging pools the observations rather than averaging the per-fold %! ## values, which is not the same thing once the folds differ in size. %! ## The pooled reading is the one R2024a returns. %! load fisheriris %! X = meas([51:100, 101:143],:); %! Y = species([51:100, 101:143]); %! CVMdl = ClassificationPartitionedLinear (X, Y, 'KFold', 4); %! [~, score] = kfoldPredict (CVMdl); %! gY = 1 + strcmp (Y, 'virginica'); %! wrong = mean (score(sub2ind (size (score), (1:93)', gY)) ... %! <= score(sub2ind (size (score), (1:93)', 3 - gY))); %! assert_equal (kfoldLoss (CVMdl), wrong, 1e-12); %!test %! ## 'Folds' reports over the observations of the folds it names %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = ClassificationPartitionedLinear (X, Y, 'KFold', 5); %! two = kfoldLoss (CVMdl, 'Folds', [1, 2]); %! idx = test (CVMdl.Partition, 1) | test (CVMdl.Partition, 2); %! [label, ~] = kfoldPredict (CVMdl); %! assert_equal (two, mean (! strcmp (label(idx), Y(idx))), 1e-12); %!test %! ## Every classification loss is offered, and each reads the out-of-fold %! ## score of the true class %! load fisheriris %! CVMdl = ClassificationPartitionedLinear (meas(51:end,:), ... %! species(51:end), 'KFold', 5); %! for f = {'binodeviance', 'classifcost', 'classiferror', 'exponential', ... %! 'hinge', 'logit', 'mincost', 'quadratic'} %! assert_equal (isfinite (kfoldLoss (CVMdl, 'LossFun', f{1})), true); %! endfor %!test %! ## A logistic fit reports posteriors, and the transform that produces %! ## them stays with the fold models: the cross-validated model reports %! ## none of its own, which is R2024a's arrangement and not the reverse %! load fisheriris %! CVMdl = ClassificationPartitionedLinear (meas(51:end,:), ... %! species(51:end), 'KFold', 5, ... %! 'Learner', 'logistic'); %! assert_equal (CVMdl.ScoreTransform, 'none'); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'logit'); %! [~, score] = kfoldPredict (CVMdl); %! assert_equal (sum (score, 2), ones (100, 1), 1e-12); %!test %! ## A whole regularization path gives one column per strength everywhere %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = ClassificationPartitionedLinear (X, Y, 'KFold', 5, ... %! 'Lambda', [0.001, 0.01, 0.1]); %! [label, score] = kfoldPredict (CVMdl); %! assert_equal (size (label), [100, 3]); %! assert_equal (size (score), [100, 2, 3]); %! assert_equal (size (kfoldMargin (CVMdl)), [100, 3]); %! assert_equal (size (kfoldLoss (CVMdl)), [1, 3]); %! assert_equal (size (kfoldEdge (CVMdl)), [1, 3]); %! assert_equal (size (kfoldLoss (CVMdl, 'Mode', 'individual')), [5, 3]); %!test %! ## An observation that no fold held out is not classified: under a %! ## holdout partition that is the training rows, and they come back %! ## missing rather than carrying a class %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = ClassificationPartitionedLinear (X, Y, 'Holdout', 0.3); %! assert_equal (CVMdl.KFold, 1); %! [label, score] = kfoldPredict (CVMdl); %! assert_equal (sum (cellfun (@isempty, label)), 70); %! assert_equal (sum (isnan (score(:,1))), 70); %! assert_equal (isfinite (kfoldLoss (CVMdl)), true); %!test %! ## Leave-one-out gives as many folds as there are observations %! load fisheriris %! X = meas([51:70, 101:120],:); %! Y = species([51:70, 101:120]); %! CVMdl = ClassificationPartitionedLinear (X, Y, 'Leaveout', 'on'); %! assert_equal (CVMdl.KFold, 40); %! assert_equal (numel (kfoldPredict (CVMdl)), 40); %!test %! ## A row with a missing predictor is dropped before the partition, so %! ## every index below refers to the same set of observations %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! X(3,2) = NaN; %! CVMdl = ClassificationPartitionedLinear (X, Y, 'KFold', 5); %! assert_equal (CVMdl.NumObservations, 99); %! assert_equal (numel (CVMdl.Y), 99); %! assert_equal (CVMdl.Partition.NumObservations, 99); %!test %! ## Observation weights reach both the folds and the reported prior %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = ClassificationPartitionedLinear (X, Y, 'KFold', 5, ... %! 'Weights', (1:100)'); %! assert_equal (CVMdl.Prior, [0.252475247524752, 0.747524752475248], 1e-12); %! assert_equal (CVMdl.Trained{1}.Prior, CVMdl.Prior); %! assert_equal (sum (CVMdl.W), 1, 1e-12); %!test %! ## A character matrix response carries through cross-validation: the %! ## class names, the fold models, the assembled labels and every kfold %! ## method answer as they do for the equivalent cell array %! load fisheriris %! X = meas(51:end,:); %! Yc = species(51:end); %! Ym = char (Yc); %! part = cvpartition (Yc, 'KFold', 4); %! CVc = ClassificationPartitionedLinear (X, Yc, 'CVPartition', part); %! CVm = ClassificationPartitionedLinear (X, Ym, 'CVPartition', part); %! assert_equal (cellstr (CVm.ClassNames), CVc.ClassNames); %! assert_equal (size (CVm.ClassNames), [2, 10]); %! assert_equal (cellstr (CVm.Trained{1}.ClassNames), CVc.ClassNames); %! assert_equal (cellstr (kfoldPredict (CVm)), kfoldPredict (CVc)); %! assert_equal (kfoldMargin (CVm), kfoldMargin (CVc)); %! assert_equal (kfoldEdge (CVm), kfoldEdge (CVc)); %! assert_equal (kfoldLoss (CVm), kfoldLoss (CVc)); %! assert_equal (kfoldLoss (CVm, 'LossFun', 'hinge'), ... %! kfoldLoss (CVc, 'LossFun', 'hinge')); %!test %! ## Names of unequal length are padded by the character matrix and the %! ## padding is not part of the name, through the cross-validated path too %! load fisheriris %! X = meas(51:end,:); %! Y = [repmat({'ab'}, 50, 1); repmat({'abcd'}, 50, 1)]; %! part = cvpartition (Y, 'KFold', 4); %! CVc = ClassificationPartitionedLinear (X, Y, 'CVPartition', part); %! CVm = ClassificationPartitionedLinear (X, char (Y), 'CVPartition', part); %! assert_equal (cellstr (CVm.ClassNames), CVc.ClassNames); %! assert_equal (cellstr (kfoldPredict (CVm)), kfoldPredict (CVc)); %!test %! ## A row dropped for a missing predictor is dropped before the partition, %! ## through the character path, which is the case that exercises the row %! ## indexing rather than the label comparison %! load fisheriris %! X = meas(51:end,:); %! X(7,3) = NaN; %! CVMdl = ClassificationPartitionedLinear (X, char (species(51:end)), ... %! 'KFold', 4); %! assert_equal (CVMdl.NumObservations, 99); %! assert_equal (size (CVMdl.Y), [99, 10]); %! assert_equal (numel (kfoldPredict (CVMdl)) / 10, 99); %!test %! ## A transform asked for by name goes to the parent and not to the folds, %! ## and is applied once to the assembled scores. R2024a's arrangement. %! load fisheriris %! part = cvpartition (species(51:end), 'KFold', 5); %! plain = ClassificationPartitionedLinear (meas(51:end,:), species(51:end), ... %! 'CVPartition', part); %! CVMdl = ClassificationPartitionedLinear (meas(51:end,:), species(51:end), ... %! 'CVPartition', part, ... %! 'ScoreTransform', 'doublelogit'); %! assert_equal (CVMdl.ScoreTransform, 'doublelogit'); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'none'); %! [~, s0] = kfoldPredict (plain); %! [~, s1] = kfoldPredict (CVMdl); %! assert_equal (s1, 1 ./ (1 + exp (-2 * s0)), 1e-12); %!test %! ## It can be assigned after the model is built, and reaches kfoldPredict %! ## without being carried into the folds %! load fisheriris %! CVMdl = ClassificationPartitionedLinear (meas(51:end,:), species(51:end), ... %! 'KFold', 5); %! [~, s0] = kfoldPredict (CVMdl); %! CVMdl.ScoreTransform = 'doublelogit'; %! [~, s1] = kfoldPredict (CVMdl); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'none'); %! assert_equal (s1, 1 ./ (1 + exp (-2 * s0)), 1e-12); %!test %! ## A transform the learner implies stays with the folds, and an assigned %! ## one is applied on top of it rather than replacing it. Measured on %! ## R2024a, where the folds keep 'logit' and the parent's transform %! ## composes. %! load fisheriris %! CVMdl = ClassificationPartitionedLinear (meas(51:end,:), species(51:end), ... %! 'KFold', 5, ... %! 'Learner', 'logistic'); %! [~, s0] = kfoldPredict (CVMdl); %! CVMdl.ScoreTransform = 'doublelogit'; %! [~, s1] = kfoldPredict (CVMdl); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'logit'); %! assert_equal (s1, 1 ./ (1 + exp (-2 * s0)), 1e-12); %!test %! ## 'none' is the identity, so assigning it transforms nothing %! load fisheriris %! CVMdl = ClassificationPartitionedLinear (meas(51:end,:), species(51:end), ... %! 'KFold', 5); %! [~, s0] = kfoldPredict (CVMdl); %! CVMdl.ScoreTransform = 'none'; %! [~, s1] = kfoldPredict (CVMdl); %! assert_equal (s1, s0); %!error ... %! load fisheriris %! CVMdl = ClassificationPartitionedLinear (meas(51:end,:), species(51:end), ... %! 'KFold', 5); %! CVMdl.ScoreTransform = 'nosuchtransform'; ## Test input validation %!error ... %! ClassificationPartitionedLinear (ones (10, 2)) %!error ... %! ClassificationPartitionedLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'KFold') %!error ... %! ClassificationPartitionedLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'KFold', 1) %!error ... %! ClassificationPartitionedLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'Holdout', 1.5) %!error ... %! ClassificationPartitionedLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'Leaveout', 1) %!error ... %! ClassificationPartitionedLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'CVPartition', 3) %!error ... %! ClassificationPartitionedLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'KFold', 2, 'Holdout', 0.2) %!error ... %! ClassificationPartitionedLinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'CVPartition', cvpartition (20, 'KFold', 2)) %!error ... %! ClassificationPartitionedLinear (ones (9, 2), ... %! [1; 1; 1; 2; 2; 2; 3; 3; 3], 'KFold', 3) %!error ... %! kfoldLoss (ClassificationPartitionedLinear (ones (10, 2), ... %! [ones(5,1); 2*ones(5,1)], 'KFold', 2), 'Mode', 'each') %!error ... %! kfoldLoss (ClassificationPartitionedLinear (ones (10, 2), ... %! [ones(5,1); 2*ones(5,1)], 'KFold', 2), 'Folds', 7) %!error ... %! kfoldLoss (ClassificationPartitionedLinear (ones (10, 2), ... %! [ones(5,1); 2*ones(5,1)], 'KFold', 2), 'LossFun', 'mse') %!error ... %! kfoldEdge (ClassificationPartitionedLinear (ones (10, 2), ... %! [ones(5,1); 2*ones(5,1)], 'KFold', 2), 'LossFun', ... %! 'hinge') ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = fitclinear (meas, strcmp (species, 'setosa'), 'KFold', 3); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = kfoldPredict (Mdl); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = kfoldPredict (Mdl); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = kfoldPredict (Mdl); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = kfoldPredict (Mdl); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = fitclinear (meas, strcmp (species, 'setosa'), 'KFold', 3); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = kfoldPredict (Mdl); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = kfoldPredict (Mdl); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/ClassificationPartitionedModel.m000066400000000000000000003076631524624707500306100ustar00rootroot00000000000000## Copyright (C) 2024 Ruchika Sonagote ## Copyright (C) 2024 Pallav Purbia ## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef ClassificationPartitionedModel ## -*- texinfo -*- ## @deftp {statistics} ClassificationPartitionedModel ## ## Cross-validated classification model ## ## The @code{ClassificationPartitionedModel} class stores cross-validated ## classification models trained on different partitions of the data. ## It can predict responses for observations not used for training using ## the @code{kfoldPredict} method. ## ## Create a @code{ClassificationPartitionedModel} object by using the ## @code{crossval} function. ## ## @seealso{crossval} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} ClassNames ## ## Names of classes in the response variable ## ## An array of unique values of the response variable @var{Y}, which has the ## same data types as the data in @var{Y}. This property is read-only. ## @qcode{ClassNames} can have any of the following datatypes: ## ## @itemize ## @item Cell array of character vectors ## @item Character array ## @item Logical vector ## @item Numeric vector ## @end itemize ## ## @end deftp ClassNames = []; endproperties properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} Cost ## ## Cost of Misclassification ## ## A square matrix specifying the cost of misclassification of a point. ## @qcode{Cost(i,j)} is the cost of classifying a point into class @qcode{j} ## if its true class is @qcode{i} (that is, the rows correspond to the true ## class and the columns correspond to the predicted class). The order of ## the rows and columns in @qcode{Cost} corresponds to the order of the ## classes in @qcode{ClassNames}. The number of rows and columns in ## @qcode{Cost} is the number of unique classes in the response. By ## default, @qcode{Cost(i,j) = 1} if @qcode{i != j}, and ## @qcode{Cost(i,j) = 0} if @qcode{i = j}. In other words, the cost is 0 ## for correct classification and 1 for incorrect classification. ## ## Assigning @qcode{Cost} rebuilds it on every fold in @qcode{Trained}, so ## @code{kfoldPredict} and @code{kfoldLoss} answer under the new costs. It ## is refused on a cross-validated @code{ClassificationSVM}, whose costs ## enter the box constraint while it is being fitted: a model already fitted ## under one cost matrix cannot be made to describe another. ## ## ## A cost may also be given as a struct with the fields ## @qcode{ClassNames} and @qcode{ClassificationCosts}, which names the ## order its own matrix is written in. That matrix is permuted into the ## order of @qcode{ClassNames} above, so a caller need not know which ## order the classes were sorted into. It must name every class. ## ## A cost must be floating point, not sparse, not complex, non-negative ## and zero down its diagonal, and must hold no @qcode{NaN} or ## @qcode{Inf}. A @code{single} is widened to @code{double}. ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} Prior ## ## Prior probability for each class ## ## A numeric vector specifying the prior probabilities for each class. The ## order of the elements in @qcode{Prior} corresponds to the order of the ## classes in @qcode{ClassNames}. ## ## It may be assigned only on a cross-validated ## @code{ClassificationDiscriminant} or @code{ClassificationNaiveBayes}, ## the two learners that score from the priors they are given rather than ## consuming them while they fit: the discriminant re-derives its ## coefficients from them and the naive Bayes weights its class densities ## by them. Every other learner cannot revisit them afterwards. ## Assigning it rebuilds the priors on every fold in @qcode{Trained}. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} ScoreTransform ## ## Transformation function for classification scores ## ## Specified as a function handle for transforming the classification ## scores. ## ## @end deftp ScoreTransform = []; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} CrossValidatedModel ## ## Cross-validated model class ## ## A character vector holding the short name of the learner that was ## cross validated, as MATLAB reports it: @qcode{'Discriminant'}, ## @qcode{'GAM'}, @qcode{'KNN'}, @qcode{'NeuralNetwork'} or ## @qcode{'SVM'}. It is not the class name of that learner, and the ## regression side uses the same names. This property is read-only. ## ## @end deftp CrossValidatedModel = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} PredictorNames ## ## Names of predictor variables ## ## A cell array of character vectors specifying the names of the predictor ## variables. The names are in the order in which they appear in the ## training dataset. This property is read-only. ## ## @end deftp PredictorNames = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} CategoricalPredictors ## ## Indices of categorical predictors ## ## A vector of positive integers specifying the indices of categorical ## predictors. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} ResponseName ## ## Response variable name ## ## A character vector specifying the name of the response variable @var{Y}. ## This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} NumObservations ## ## Number of observations ## ## A positive integer value specifying the number of observations in the ## training dataset used for training the cross-validated model. ## This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} X ## ## Predictor data ## ## A numeric matrix containing the unstandardized predictor data. Each ## column of @var{X} represents one predictor (variable), and each row ## represents one observation. This property is read-only. ## ## @end deftp X = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} Y ## ## Class labels ## ## Specified as a logical or numeric column vector, or as a character array ## or a cell array of character vectors with the same number of rows as the ## predictor data. Each row in @var{Y} is the observed class label for ## the corresponding row in @var{X}. This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} W ## ## Observation weights ## ## A numeric column vector with one entry per observation, carried over ## from the model that was cross validated. This property is read-only. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} ModelParameters ## ## Model parameters ## ## A structure holding the parameters the folds were fitted with, carried ## through from the learner that was cross validated, beside ## @qcode{NLearn}, the number of folds, and the @qcode{Version}, ## @qcode{Method} and @qcode{Type} tags of this class. The ## learner's own tags are replaced rather than kept, so a cross-validated ## SVM reports @qcode{Method} as @qcode{'PartitionedModel'} and not ## @qcode{'SVM'}. ## ## @strong{Deviation from MATLAB.} MATLAB reports the parameter record of ## the cross-validation @emph{ensemble} here rather than of the learner, ## so it says nothing at all about how the folds were fitted: of its ## eighteen fields only the fold count, its partitioner and a fit template ## carry anything, and the rest are boosting settings left inert. Nor can ## the parameters be reached through the folds, a compact model carrying ## none in MATLAB. This class reports the fit instead, which is strictly ## more than MATLAB offers, and everything MATLAB's record does carry is ## published here as the @qcode{KFold}, @qcode{Partition}, @qcode{X}, ## @qcode{Y}, @qcode{W} and @qcode{CrossValidatedModel} properties. ## ## This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} Trained ## ## Models trained on each fold ## ## A cell array of models trained on each fold. Each cell contains a model ## trained on the minus-one fold of the data (all but one fold used for ## training and the remaining fold used for validation). This property is ## read-only. ## ## @end deftp Trained = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} KFold ## ## Number of cross-validated folds ## ## A positive integer value specifying the number of cross-validated folds. ## This property is read-only. ## ## @end deftp KFold = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} Partition ## ## Partition configuration ## ## A @code{cvpartition} object specifying the partition configuration used ## for cross-validation. This field stores the cvpartition instance that ## describes how the data was split into training and validation sets. ## This property is read-only. ## ## @end deftp Partition = []; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} BinEdges ## ## Bin edges ## ## A cell array with one entry per predictor, holding that predictor's ## bin edges where the learner discretized it before fitting. It is ## carried over from the model that was cross validated, and is empty ## whenever that model did no binning, which is every learner this package ## implements: MATLAB fills it only for its GAM, which bins because it is ## built from boosted trees where ours is built from splines. ## ## This property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {ClassificationPartitionedModel} {property} NumTrainedPerFold ## ## How many trees each fold fitted ## ## A scalar structure with fields @qcode{PredictorTrees} and ## @qcode{InteractionTrees}, each a row with one entry per fold, for a ## generalized additive model backing, and empty for every other. ## ## It reports what each fold actually fitted, which the budget in ## @qcode{ModelParameters} does not: a phase stops early when it can no ## longer improve the fit, and the folds need not stop at the same place. ## ## MATLAB carries this on its per-learner partitioned GAM classes, which ## this package deliberately does not have (see @code{crossval}), so like ## @qcode{IsStandardDeviationFit} it is declared here for every backing ## and left empty where it does not apply. ## ## This property is read-only. ## ## @end deftp NumTrainedPerFold = []; endproperties ## Copied from the parent model and kept out of the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) STfun = @(x) x; ## Raised once the constructor is done. Cost and Prior are refused for ## the learners MATLAB refuses them for, but the constructor has to carry ## whatever the cross validated model held, so the guards only apply to a ## user's assignment. Fitted = false; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Shaped after RegressionPartitionedModel's, with the classifier's own ## fields: the classes it separates and the transform its scores carry. function disp (this) fprintf ("\n ClassificationPartitionedModel\n\n"); fprintf ("%+25s: '%s'\n", 'CrossValidatedModel', ... this.CrossValidatedModel); fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); if (iscellstr (this.ClassNames)) str = repmat ({'''%s'''}, 1, numel (this.ClassNames)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.ClassNames{:}); elseif (ischar (this.ClassNames)) str = repmat ({'''%s'''}, 1, rows (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, cellstr (this.ClassNames){:}); else # single, double, logical str = repmat ({'%d'}, 1, numel (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.ClassNames); endif fprintf ("%+25s: %s\n", 'ClassNames', str); fprintf ("%+25s: %d\n", 'NumObservations', this.NumObservations); fprintf ("%+25s: %d\n", 'KFold', this.KFold); fprintf ("%+25s: '%s'\n\n", 'ScoreTransform', this.ScoreTransform); endfunction ## MATLAB refuses this one on a cross-validated SVM alone, and so do we: ## the SVM's costs enter its box constraint while it is being fitted, so a ## model fitted under one cost matrix cannot be made to report another. function this = set.Cost (this, val) if (this.Fitted && strcmp (this.CrossValidatedModel, 'SVM')) error (strcat ("ClassificationPartitionedModel: cannot assign", ... " 'Cost' on a cross-validated ClassificationSVM,", ... " whose costs are consumed while it is fitted.")); endif gnY = this.ClassNames; if (isempty (val)) this.Cost = cast (! eye (classCount (gnY)), 'double'); else ## Everything a cost must be, and the struct form, which ## is permuted into this model's class order. [val, errmsg] = costMatrix (val, gnY); if (! isempty (errmsg)) error ("ClassificationPartitionedModel: %s", errmsg); endif this.Cost = val; endif this = pushToFolds (this, 'Cost', this.Cost); endfunction ## A generative learner scores from the priors it holds, so its folds can ## honour a prior assigned after the fit: the discriminant re-derives its ## coefficients from them and the naive Bayes weights its class densities ## by them. The discriminative learners fitted them in and cannot. ## MATLAB draws the same line, refusing the KNN, the network, the GAM and ## the SVM. function this = set.Prior (this, val) takes_prior = {'Discriminant', 'NaiveBayes'}; if (this.Fitted && ! any (strcmp (this.CrossValidatedModel, takes_prior))) error (strcat ("ClassificationPartitionedModel: 'Prior' can only", ... " be assigned on a cross-validated", ... " ClassificationDiscriminant or", ... " ClassificationNaiveBayes.")); endif if (! this.Fitted) this.Prior = val; return; endif if (isstruct (val)) val = priorFromStruct (val, this.ClassNames, ... 'ClassificationPartitionedModel'); endif if (ischar (val) && strcmpi ('uniform', val)) n = classCount (this.ClassNames); this.Prior = ones (1, n) ./ n; elseif (isempty (val) || (ischar (val) && strcmpi ('empirical', val))) [~, gnY, gY] = uniqueLabels (this.Y); pr = accumarray (gY(:), 1, [numel(gnY), 1]); this.Prior = pr(:)' ./ sum (pr); elseif (isnumeric (val)) if (classCount (this.ClassNames) != numel (val)) error (strcat ("ClassificationPartitionedModel: the elements", ... " in 'Prior' do not correspond to the selected", ... " classes in Y.")); endif this.Prior = val(:)' ./ sum (val); else error (strcat ("ClassificationPartitionedModel: invalid value", ... " for 'Prior'.")); endif this = pushToFolds (this, 'Prior', this.Prior); endfunction function this = set.ScoreTransform (this, val) [f, nm] = parseScoreTransform (val, 'ClassificationPartitionedModel'); this.ScoreTransform = nm; this.STfun = f; endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedModel} {@var{this} =} ClassificationPartitionedModel (@var{Mdl}, @var{Partition}) ## ## Create a @code{ClassificationPartitionedModel} class object for ## cross-validation of classification models. ## ## @code{@var{this} = ClassificationPartitionedModel (@var{Mdl}, ## @var{Partition})} returns a ClassificationPartitionedModel object, with ## @var{Mdl} as the trained classification model object and ## @var{Partition} as the partitioning object obtained using ## @code{cvpartition} ## function. ## ## @seealso{cvpartition} ## @end deftypefn function this = ClassificationPartitionedModel (Mdl, Partition) ## Check input arguments if (nargin < 2) error ("ClassificationPartitionedModel: too few input arguments."); endif ## Check for valid Classification object validTypes = {'ClassificationDiscriminant', 'ClassificationGAM', ... 'ClassificationKNN', 'ClassificationNaiveBayes', ... 'ClassificationNeuralNetwork', 'ClassificationSVM'}; if (! any (strcmp (class (Mdl), validTypes))) error ("ClassificationPartitionedModel: unsupported model type."); endif ## Check for valid cvpartition object if (! strcmp (class (Partition), 'cvpartition')) error ("ClassificationPartitionedModel: invalid 'cvpartition' object."); endif ## Set properties. The rows dropped for missing values are outside the ## partition, so they are dropped here too and every index below, the ## partition's included, refers to the same set of observations. this.X = Mdl.X; this.Y = Mdl.Y; this.KFold = Partition.NumTestSets; this.Trained = cell (this.KFold, 1); this.ClassNames = Mdl.ClassNames; this.ResponseName = Mdl.ResponseName; this.NumObservations = rows (this.X); this.PredictorNames = Mdl.PredictorNames; this.Partition = Partition; ## MATLAB stores a short name here, shared with the regression ## side: 'Discriminant', 'GAM', 'KNN', 'NeuralNetwork', 'SVM'. this.CrossValidatedModel = strrep (class (Mdl), 'Classification', ''); this.ScoreTransform = Mdl.ScoreTransform; this.STfun = Mdl.STfun; ## Every classifier reports a prior and a cost now, so they are carried ## whatever was cross validated; this used to name three of the five. this.Prior = Mdl.Prior; this.Cost = Mdl.Cost; this.W = Mdl.W; this.BinEdges = Mdl.BinEdges; ## Switch Classification object types switch (this.CrossValidatedModel) case 'Discriminant' ## Arguments to pass in fitcdiscr args = {}; ## List of acceptable parameters for fitcdiscr DiscrParams = {'PredictorNames', 'ResponseName', 'ClassNames', ... 'Cost', 'Prior', 'DiscrimType', 'Gamma'}; ## Set parameters for i = 1:numel (DiscrParams) paramName = DiscrParams{i}; paramValue = Mdl.(paramName); if (! isempty (paramValue)) args = [args, {paramName, paramValue}]; endif endfor ## Add 'FillCoeffs' parameter if (isempty (Mdl.Coeffs)) args = [args, {'FillCoeffs', 'off'}]; endif ## Train model according to partition object for k = 1:this.KFold idx = training (this.Partition, k); tmp = fitcdiscr (this.X(idx, :), this.Y(idx,:), args{:}); this.Trained{k} = compact (tmp); endfor case 'GAM' ## Arguments to pass in fitcgam args = {}; ## List of acceptable parameters for fitcdiscr ## Which parameters a fold takes depends on which engine fitted ## the parent: the two have disjoint argument surfaces and each ## refuses the other's, so the fold is refitted with its own. if (strcmp (Mdl.FitMethod, 'boostedtrees')) GAMparams = {'PredictorNames', 'ResponseName', 'ClassNames', ... 'Cost'}; else GAMparams = {'PredictorNames', 'ResponseName', 'ClassNames', ... 'Cost', 'Formula', 'Knots', 'Order', ... 'LearningRate', 'NumIterations'}; endif args = [args, {'FitMethod', Mdl.FitMethod}]; ## Set parameters for i = 1:numel (GAMparams) paramName = GAMparams{i}; paramValue = Mdl.(paramName); if (! isempty (paramValue)) args = [args, {paramName, paramValue}]; endif endfor ## Interactions now holds the fitted pairs, which the constructor ## does not take as a specification. The term matrix does, and it ## reproduces the parent's terms exactly rather than re-selecting ## them. A formula names its own terms and is passed instead, so ## this must not be passed alongside one. if (strcmp (Mdl.FitMethod, 'boostedtrees')) ## The tree engine holds its interactions as predictor pairs and ## takes them back as a term matrix, so the pairs are widened ## into one before they are handed to the fold. if (! isempty (Mdl.Interactions)) IM = false (rows (Mdl.Interactions), Mdl.NumPredictors); for q = 1:rows (Mdl.Interactions) IM(q, Mdl.Interactions(q,:)) = true; endfor args = [args, {'Interactions', IM}]; endif elseif (isempty (Mdl.Formula) && ! isempty (Mdl.IntMatrix)) args = [args, {'Interactions', Mdl.IntMatrix}]; endif ## Train model according to partition object. The GAM is the one ## backing whose fold prior is not the parent's: MATLAB carries the ## prior as observation weight and refits it from the rows the fold ## kept, so a fold holding a class in a different proportion from ## the whole reports a different prior. The other five take the ## parent's value unchanged. [~, ~, gY] = uniqueLabels (this.Y); nclass = classCount (this.ClassNames); for k = 1:this.KFold idx = training (this.Partition, k); pf = gamFoldPrior (this.Prior, gY, nclass, idx); tmp = fitcgam (this.X(idx, :), this.Y(idx,:), args{:}, ... 'Prior', pf); this.Trained{k} = compact (tmp); endfor ## The model's own, not a list restated here. The names this ## branch used to gather were the spline engine's, and the fit has ## been boosted trees by default since 2026-08-24, so the struct it ## built described a scheme the folds were no longer fitted under. case 'KNN' ## Arguments to pass in fitcknn args = {}; ## List of acceptable parameters for fitcknn. ScoreTransform is ## deliberately absent: the parent applies it to the assembled ## scores, so a fold carrying it too would apply it twice. KNNparams = {'PredictorNames', 'ResponseName', 'ClassNames', ... 'Prior', 'Cost', 'BreakTies', ... 'NSMethod', 'BucketSize', 'CacheSize', ... 'NumNeighbors', 'Exponent', ... 'Scale', 'Cov', 'Distance', 'DistanceWeight', ... 'IncludeTies'}; ## Set parameters for i = 1:numel (KNNparams) paramName = KNNparams{i}; if (isprop (Mdl, paramName)) paramValue = Mdl.(paramName); if (! isempty (paramValue)) args = [args, {paramName, paramValue}]; endif else switch (paramName) case 'Cov' if (strcmpi (Mdl.Distance, 'mahalanobis') && ... (! isempty (Mdl.DistParameter))) args = [args, {'Cov', Mdl.DistParameter}]; endif case 'Exponent' if (strcmpi (Mdl.Distance,'minkowski') && ... (! isempty (Mdl.DistParameter))) args = [args, {'Exponent', Mdl.DistParameter}]; endif case 'Scale' if (strcmpi (Mdl.Distance,'seuclidean') && ... (! isempty (Mdl.DistParameter))) args = [args, {'Scale', Mdl.DistParameter}]; endif endswitch endif endfor ## Standardization is told by Mu, and is passed on: leaving it out ## refitted every fold on the raw scale while the parent reported ## itself standardized. It is passed only when it is on, the ## constructor taking at most one of Standardize, Scale and Cov, so ## a model carrying either of those was never standardized. if (! isempty (Mdl.Mu)) args = [args, {'Standardize', true}]; endif ## Train model according to partition object for k = 1:this.KFold idx = training (this.Partition, k); this.Trained{k} = fitcknn (this.X(idx, :), this.Y(idx,:), args{:}); endfor case 'NaiveBayes' ## Arguments to pass in fitcnb. ScoreTransform is deliberately ## absent, as it is for the KNN above: the parent applies it to the ## assembled scores, so a fold carrying it too would apply it twice. args = {}; NBparams = {'PredictorNames', 'ResponseName', 'ClassNames', ... 'Prior', 'Cost', 'DistributionNames'}; for i = 1:numel (NBparams) paramName = NBparams{i}; paramValue = Mdl.(paramName); if (! isempty (paramValue)) args = [args, {paramName, paramValue}]; endif endfor ## The kernel settings are taken from the request the model records ## rather than from the resolved properties: a fold is fitted the ## way the model was asked for, and resolving them again per fold is ## what lets each fold choose its own bandwidth, as it must. MP = Mdl.ModelParameters; if (! isempty (MP.Kernel)) args = [args, {'Kernel', MP.Kernel}]; endif if (! isempty (MP.Support)) args = [args, {'Support', MP.Support}]; endif if (! (isempty (MP.Width) || all (isnan (MP.Width(:))))) args = [args, {'Width', MP.Width}]; endif ## Train model according to partition object. The fold is stored ## compact, as MATLAB stores it: a naive Bayes fold is a set of ## fitted densities and needs none of the observations it was fitted ## on. Measured on R2024a, where Trained{k} is a ## CompactClassificationNaiveBayes. for k = 1:this.KFold idx = training (this.Partition, k); tmp = fitcnb (this.X(idx, :), this.Y(idx,:), args{:}); this.Trained{k} = compact (tmp); endfor ## Store ModelParameters to ClassificationPartitionedModel object case 'NeuralNetwork' ## Arguments to pass in fitcnet args = {}; ## List of acceptable parameters for fitcnet. ScoreTransform is ## deliberately absent, as it is for the KNN above. NNparams = {'PredictorNames', 'ResponseName', 'ClassNames', ... 'Cost', 'Prior', 'LayerSizes', ... 'Activations', 'OutputLayerActivation', ... 'IterationLimit', 'DisplayInfo'}; ## Set parameters for i = 1:numel (NNparams) paramName = NNparams{i}; paramValue = Mdl.(paramName); if (! isempty (paramValue)) args = [args, {paramName, paramValue}]; endif endfor ## The folds must be trained by the solver the parent was trained ## by, and the solver decides which of the remaining options are ## legal: a learning rate belongs to the epoch loop alone. Solver ## reports a display name, so it is mapped back to the option. if (strcmp (Mdl.Solver, 'LBFGS')) args = [args, {'Solver', 'lbfgs'}]; else args = [args, {'Solver', 'sgd', 'LearningRate', Mdl.LearningRate}]; endif stdz = ! isempty (Mdl.Mu); args = [args, {'Standardize', stdz}]; ## Train model according to partition object for k = 1:this.KFold idx = training (this.Partition, k); tmp = fitcnet (this.X(idx, :), this.Y(idx,:), args{:}); this.Trained{k} = compact (tmp); endfor ## The model's own, rather than a list of names restated here. case 'SVM' ## Get ModelParameters structure from ClassificationSVM object params = Mdl.ModelParameters; ## The polynomial order is recorded for the polynomial kernel ## alone, so it is passed only when the parent carries one. korder = {}; if (! isempty (params.KernelPolynomialOrder)) korder = {'PolynomialOrder', params.KernelPolynomialOrder}; endif ## Train model according to partition object for k = 1:this.KFold idx = training (this.Partition, k); ## Pass all arguments directly to fitcsvm tmp = fitcsvm (this.X(idx, :), this.Y(idx,:), ... 'Standardize', ! isempty (Mdl.Mu), ... 'PredictorNames', Mdl.PredictorNames, ... 'ResponseName', Mdl.ResponseName, ... 'ClassNames', Mdl.ClassNames, ... 'Cost', Mdl.Cost, 'Prior', Mdl.Prior, ... 'SVMtype', params.SVMtype, ... 'KernelFunction', params.KernelFunction, ... korder{:}, ... 'KernelScale', params.KernelScale, ... 'KernelOffset', params.KernelOffset, ... 'BoxConstraint', params.BoxConstraint, ... 'Nu', params.Nu, ... 'CacheSize', params.CacheSize, ... 'Tolerance', params.Tolerance, ... 'Shrinking', params.Shrinking); this.Trained{k} = compact (tmp); endfor ## The model's own, rather than the argument struct this branch ## assembled to refit the folds with. endswitch ## The learner's parameters, under this class's own tags. MATLAB ## reports an EnsembleParams here instead and so says nothing about the ## fit; see the ModelParameters property for the deviation. this.ModelParameters = partitionedModelParams (Mdl, this.KFold, ... 'PartitionedModel', 'classification'); ## No fold carries the transform: the parent applies it once to the ## assembled score. Cleared here rather than at each fit call so that ## a backing added later cannot reintroduce the double application by ## inheriting a class default of its own, which is how a cross ## validated GAM came to transform twice. The KNN case stores a full ## ClassificationKNN where the other four store compacts, and both ## accept the assignment. ## ## Safe because the transform here is a caller's preference and not ## fitted content. A fitted transform would not be: fitPosterior ## installs a sigmoid it has estimated, so if that is ever implemented ## through crossval it must not be cleared here. this = pushToFolds (this, 'ScoreTransform', 'none'); ## Gathered across the folds, which is the shape MATLAB reports: one ## structure carrying a row per phase rather than a structure per fold. if (strcmp (this.CrossValidatedModel, 'GAM')) pt = zeros (1, this.KFold); it = zeros (1, this.KFold); boosted = true; for k = 1:this.KFold nt = this.Trained{k}.NumTrainedTrees; ## A spline fit counts no trees, so there is nothing to report and ## the property stays empty rather than claiming zero of them. if (isempty (nt)) boosted = false; break; endif pt(k) = nt.PredictorTrees; it(k) = nt.InteractionTrees; endfor if (boosted) this.NumTrainedPerFold = struct ('PredictorTrees', pt, ... 'InteractionTrees', it); endif endif this.Fitted = true; endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedModel} {@var{label} =} kfoldPredict (@var{this}) ## @deftypefnx {ClassificationPartitionedModel} {[@var{label}, @var{score}, @var{cost}] =} kfoldPredict (@var{this}) ## ## Predict responses for observations not used for training in a ## cross-validated classification model. ## ## @code{@var{[label, Score, Cost]} = kfoldPredict (@var{this})} ## returns the predicted class labels, classification scores, and ## classification costs for the data used ## to train the cross-validated model @var{this}. ## ## @var{this} is a @code{ClassificationPartitionedModel} object. ## The function predicts the response for each observation that was ## held out during training in the cross-validation process. ## ## An observation that no fold held out is not predicted at all: its ## scores and costs are @qcode{NaN} and its label is missing, an empty ## character vector for a cell array of strings and @qcode{NaN} for a ## numeric response. Under a @qcode{'Holdout'} partition that is every ## observation outside the holdout set. @strong{This differs from ## MATLAB}, which reports @qcode{NaN} scores for those rows as we do but ## labels every one of them with the @emph{first} class, whatever their ## response: that label is the least-cost class of a row of @qcode{NaN} ## costs rather than a prediction any model made, and naming a class for ## an observation nothing scored would be wrong. A logical response has ## no missing value to give, so those rows stay @qcode{false}. ## ## @multitable @columnfractions 0.28 0.7 ## @headitem @var{Output} @tab @var{Description} ## ## @item @qcode{label} @tab Predicted class labels, returned as a ## vector or cell array. The type of @var{label} matches the type of ## @var{Y} in the original training data. Each element of @var{label} ## corresponds to the predicted class ## label for the corresponding row in @var{X}. ## ## @item @qcode{Score} @tab Classification scores, returned as a ## numeric matrix. Each row of @var{Score} corresponds to an observation, ## and each column corresponds to a class. The value in row @var{i} and ## column @var{j} is the ## classification score for class @var{j} for observation @var{i}. ## ## @item @qcode{Cost} @tab Classification costs, returned as a ## numeric matrix. Each row of @var{Cost} corresponds to an observation, ## and each column corresponds to a class. The value in row @var{i} ## and column @var{j} is the classification cost for class @var{j} for ## observation @var{i}. This output is optional and only returned if ## requested. ## @end multitable ## ## @seealso{ClassificationKNN, ClassificationSVM, ## ClassificationPartitionedModel} ## @end deftypefn function [label, Score, Cost] = kfoldPredict (this) ## Input validation ## A cross-validated GAM reports no cost in MATLAB either: predict ## refuses a third output on the full and the compact class alike, and ## so does kfoldPredict. Measured on R2024a. refuse_cost = {'GAM'}; if (any (strcmp (this.CrossValidatedModel, refuse_cost)) && nargout > 2) error (strcat ("ClassificationPartitionedModel.kfoldPredict:", ... " 'Cost' output is not supported for %s cross", ... " validated models."), this.CrossValidatedModel); endif ## The label vector starts missing rather than empty, so an observation ## no fold tests is reported as missing instead of carrying a class. ## Under a holdout partition that is most of them. A logical response ## has no missing value to give, so those rows stay false. if (iscellstr (this.Y)) label = repmat ({''}, this.NumObservations, 1); elseif (islogical (this.Y)) label = false (this.NumObservations, 1); elseif (isnumeric (this.Y)) label = nan (this.NumObservations, 1); elseif (ischar (this.Y)) label = repmat (' ', this.NumObservations, size (this.Y, 2)); endif ## Initialize the score and cost matrices Score = nan (this.NumObservations, classCount (this.ClassNames)); Cost = nan (this.NumObservations, classCount (this.ClassNames)); ## Predict label, score, and cost (if applicable) for each KFold partition for k = 1:this.KFold ## Get data and trained model for this fold testIdx = test (this.Partition, k); model = this.Trained{k}; ## Train. A fold whose predict returns two outputs still carries a ## posterior in its score, so the expected cost is formed here rather ## than asked of the fold. MATLAB reports one for a network backing ## although its own network predict refuses the output. no_cost_models = {'GAM', 'NeuralNetwork'}; # two-output predict if (any (strcmp (this.CrossValidatedModel, no_cost_models))) [predictedLabel, score] = predict (model, this.X(testIdx, :)); if (nargout > 2) cost = score * this.Cost; endif else [predictedLabel, score, cost] = predict (model, this.X(testIdx, :)); endif ## Convert cell array of labels to appropriate type (if applicable) if (iscell (predictedLabel)) if (isnumeric (this.Y)) predictedLabel = cellfun (@str2num, predictedLabel); elseif (islogical (this.Y)) predictedLabel = cellfun (@logical, predictedLabel); elseif (iscellstr (this.Y)) predictedLabel = predictedLabel; endif endif ## Get labels, score, and cost (if applicable). The rows are ## assigned and not the elements: a character matrix of labels was ## allocated one name per row above, and a linear index would write ## down its first column. label(testIdx, :) = predictedLabel; Score(testIdx, :) = score; if (nargout > 2) Cost(testIdx, :) = cost; endif endfor ## The folds never carry the transform: assigning ScoreTransform on a ## cross-validated model leaves every Trained{k} at 'none', in MATLAB as ## here, and it is applied once to the assembled scores instead. It used ## to be accepted and never read, so the property was inert on every ## cross-validated classifier. kfoldLoss reads these scores, so the ## margin-based losses follow the transform as they should. Score = this.STfun (Score); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedModel} {@var{L} =} kfoldLoss (@var{obj}) ## @deftypefnx {ClassificationPartitionedModel} {@var{L} =} kfoldLoss (@dots{}, @var{name}, @var{value}) ## ## Compute the cross-validated classification loss. ## ## @code{@var{L} = kfoldLoss (@var{obj})} returns the fraction of ## observations the folds misclassify, each answered for by the fold's ## model that did not see it, which is what @code{kfoldPredict} returns. ## ## @itemize ## @item ## @var{obj} must be a @qcode{ClassificationPartitionedModel} object. ## @end itemize ## ## @code{@var{L} = kfoldLoss (@dots{}, @var{name}, @var{value})} accepts ## the following @qcode{Name-Value} pairs. ## ## @multitable @columnfractions 0.24 0.76 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'LossFun'} @tab @qcode{'classiferror'}, the default, ## @qcode{'classifcost'}, @qcode{'mincost'}, or a function handle called ## as ## @code{@var{lossfun} (@var{C}, @var{S}, @var{W}, @var{Cost})}, where ## @var{C} is a logical matrix with one true per row marking the true ## class, @var{S} the scores, @var{W} the weights and @var{Cost} the ## misclassification cost. ## ## @item @qcode{'Mode'} @tab @qcode{'average'}, the default, which returns ## one number over the observations of every fold asked for, or ## @qcode{'individual'}, which returns one number per fold. ## ## @item @qcode{'Folds'} @tab A vector of fold indices to restrict the ## loss to. It defaults to every fold. ## @end multitable ## ## @seealso{ClassificationPartitionedModel, kfoldPredict} ## @end deftypefn function L = kfoldLoss (this, varargin) if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationPartitionedModel.kfoldLoss:", ... " Name-Value arguments must be in pairs.")); endif ## Defaults, then the optional pairs LossFun = 'classiferror'; Mode = 'average'; Folds = 1:this.KFold; ## Only the losses whose value has been measured against R2024a are ## offered. The margin-based ones are not: ours came out 2x MATLAB's ## for hinge and 4x for quadratic, the square of the same factor, which ## says its margin is not the score difference this would use. Shipping ## a number that close to right and not right is worse than not ## shipping it. names = {'classifcost', 'classiferror', 'mincost'}; while (numel (varargin) > 0) if (! (ischar (varargin{1}) && isrow (varargin{1}))) error (strcat ("ClassificationPartitionedModel.kfoldLoss:", ... " parameter name must be a character vector.")); endif switch (tolower (varargin{1})) case 'lossfun' LossFun = varargin{2}; if (! (is_function_handle (LossFun) || (ischar (LossFun) && isrow (LossFun)))) error (strcat ("ClassificationPartitionedModel.kfoldLoss:", ... " 'LossFun' must be a character vector or a", ... " function handle.")); endif if (ischar (LossFun)) LossFun = tolower (LossFun); if (! any (strcmp (LossFun, names))) error (strcat ("ClassificationPartitionedModel.kfoldLoss:", ... " unsupported 'LossFun' value.")); endif endif case 'mode' Mode = varargin{2}; if (! (ischar (Mode) && isrow (Mode) && any (strcmpi (Mode, {'average', 'individual'})))) error (strcat ("ClassificationPartitionedModel.kfoldLoss:", ... " 'Mode' must be either 'average' or", ... " 'individual'.")); endif case 'folds' Folds = varargin{2}; if (! (isnumeric (Folds) && isvector (Folds) && all (Folds == fix (Folds)) && all (Folds >= 1) && all (Folds <= this.KFold))) error (strcat ("ClassificationPartitionedModel.kfoldLoss:", ... " 'Folds' must be a vector of fold indices", ... " between 1 and KFold.")); endif otherwise error (strcat ("ClassificationPartitionedModel.kfoldLoss:", ... " invalid parameter name in optional paired", ... " arguments.")); endswitch varargin(1:2) = []; endwhile ## Every observation answered for by the fold that held it out [label, Score] = kfoldPredict (this); classes = this.ClassNames; K = classCount (classes); n = rows (this.X); ## Index of the true and the predicted class, so the cost matrix and ## the score matrix can be addressed by the same column. true_idx = zeros (n, 1); pred_idx = zeros (n, 1); for i = 1:n t = find (ismember (classes, this.Y(i))); p = find (ismember (classes, label(i))); if (! isempty (t)) true_idx(i) = t(1); endif if (! isempty (p)) pred_idx(i) = p(1); endif endfor if (strcmpi (Mode, 'individual')) L = nan (numel (Folds), 1); for i = 1:numel (Folds) idx = test (this.Partition, Folds(i)); L(i) = foldLoss_ (this, idx, Score, true_idx, pred_idx, LossFun, K); endfor else idx = false (n, 1); for i = 1:numel (Folds) idx = idx | test (this.Partition, Folds(i)); endfor L = foldLoss_ (this, idx, Score, true_idx, pred_idx, LossFun, K); endif endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedModel} {@var{m} =} kfoldMargin (@var{obj}) ## ## Classification margins of the cross-validated observations. ## ## @code{@var{m} = kfoldMargin (@var{obj})} returns an @math{Nx1} vector ## holding, for every observation, the score its own fold's model gave the ## true class less the largest score that model gave any other class. A ## larger margin is a more confident correct answer and a negative one is ## a misclassification. Every observation is scored by the fold that held ## it out, so no model answers for a row it was trained on. ## ## @itemize ## @item ## @var{obj} must be a @qcode{ClassificationPartitionedModel} object. ## @end itemize ## ## Where the fold that held an observation out produced no score for it, ## the margin is @qcode{NaN}. This method takes no optional arguments, ## as MATLAB's does not. ## ## @end deftypefn function m = kfoldMargin (this) m = foldMargin_ (this); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedModel} {@var{e} =} kfoldEdge (@var{obj}) ## @deftypefnx {ClassificationPartitionedModel} {@var{e} =} kfoldEdge (@dots{}, @var{name}, @var{value}) ## ## Classification edge of the cross-validated observations. ## ## @code{@var{e} = kfoldEdge (@var{obj})} returns the mean of the ## classification margins over every cross-validated observation, which is ## the mean of @code{kfoldMargin (@var{obj})}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{ClassificationPartitionedModel} object. ## @end itemize ## ## @code{@var{e} = kfoldEdge (@dots{}, @var{name}, @var{value})} accepts ## the following @qcode{Name-Value} pairs. ## ## @multitable @columnfractions 0.24 0.76 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Mode'} @tab @qcode{'average'}, the default, which returns ## one number over the observations of every fold asked for, or ## @qcode{'individual'}, which returns one number per fold. ## ## @item @qcode{'Folds'} @tab A vector of fold indices to restrict the ## edge to. It defaults to every fold. ## @end multitable ## ## The observations of a selection are weighted uniformly and normalized ## over that selection, so a subset of folds is an average rather than a ## sum, exactly as @code{kfoldLoss} does. ## ## @end deftypefn function e = kfoldEdge (this, varargin) if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationPartitionedModel.kfoldEdge:", ... " Name-Value arguments must be in pairs.")); endif ## Defaults, then the optional pairs Mode = 'average'; Folds = 1:this.KFold; while (numel (varargin) > 0) if (! (ischar (varargin{1}) && isrow (varargin{1}))) error (strcat ("ClassificationPartitionedModel.kfoldEdge:", ... " parameter name must be a character vector.")); endif switch (tolower (varargin{1})) case 'mode' Mode = varargin{2}; if (! (ischar (Mode) && isrow (Mode) && any (strcmpi (Mode, {'average', 'individual'})))) error (strcat ("ClassificationPartitionedModel.kfoldEdge:", ... " 'Mode' must be either 'average' or", ... " 'individual'.")); endif case 'folds' Folds = varargin{2}; if (! (isnumeric (Folds) && isvector (Folds) && all (Folds == fix (Folds)) && all (Folds >= 1) && all (Folds <= this.KFold))) error (strcat ("ClassificationPartitionedModel.kfoldEdge:", ... " 'Folds' must be a vector of fold indices", ... " between 1 and KFold.")); endif otherwise error (strcat ("ClassificationPartitionedModel.kfoldEdge:", ... " invalid parameter name in optional paired", ... " arguments.")); endswitch varargin(1:2) = []; endwhile m = foldMargin_ (this); n = rows (this.X); if (strcmpi (Mode, 'individual')) e = nan (numel (Folds), 1); for i = 1:numel (Folds) idx = test (this.Partition, Folds(i)); e(i) = foldEdge_ (this, m, idx); endfor else idx = false (n, 1); for i = 1:numel (Folds) idx = idx | test (this.Partition, Folds(i)); endfor e = foldEdge_ (this, m, idx); endif endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationPartitionedModel} {@var{vals} =} kfoldfun (@var{obj}, @var{fun}) ## ## Apply a function to each fold of a cross-validated model. ## ## @code{@var{vals} = kfoldfun (@var{obj}, @var{fun})} calls @var{fun} once ## per fold and returns a @math{K*M} numeric matrix whose row @math{k} is ## what @var{fun} returned for fold @math{k}. ## ## @var{fun} is a function handle taking seven inputs and returning a ## numeric vector of the same length every time it is called: ## ## @example ## @var{testvals} = @var{fun} (@var{M}, @var{Xtrain}, @var{Ytrain}, @var{Wtrain}, @dots{} ## @var{Xtest}, @var{Ytest}, @var{Wtest}) ## @end example ## ## @var{M} is the model the fold was fitted with, taken from ## @code{@var{obj}.Trained@{k@}}; @var{Xtrain}, @var{Ytrain} and ## @var{Wtrain} are the predictors, response and weights of the ## observations that fold was trained on, and @var{Xtest}, @var{Ytest} and ## @var{Wtest} those of the observations it held out. ## ## @seealso{ClassificationPartitionedModel, kfoldPredict, kfoldLoss, kfoldMargin, kfoldEdge} ## @end deftypefn function vals = kfoldfun (this, fun) if (nargin < 2) error ("ClassificationPartitionedModel.kfoldfun: too few input arguments."); endif if (! is_function_handle (fun)) error ("ClassificationPartitionedModel.kfoldfun: FUN must be a function handle."); endif vals = []; for k = 1:this.KFold trIdx = training (this.Partition, k); teIdx = test (this.Partition, k); tv = fun (this.Trained{k}, this.X(trIdx,:), this.Y(trIdx,:), ... this.W(trIdx), this.X(teIdx,:), this.Y(teIdx,:), ... this.W(teIdx)); ## The returned values become one row, so they have to be numeric and ## of one length: a fold answering with a different width could not be ## stacked with the others, and finding that out at the concatenation ## would name neither the fold nor the reason. if (! ((isnumeric (tv) || islogical (tv)) && isvector (tv))) error (strcat ("ClassificationPartitionedModel.kfoldfun: FUN must", ... " return a numeric vector; fold %d returned a %s."), ... k, class (tv)); endif tv = tv(:)'; if (! isempty (vals) && numel (tv) != columns (vals)) error (strcat ("ClassificationPartitionedModel.kfoldfun: FUN must", ... " return the same number of values for every fold;", ... " fold %d returned %d where the first returned", ... " %d."), k, numel (tv), columns (vals)); endif vals = [vals; tv]; endfor endfunction endmethods methods (Access = private) ## Margin of every observation from the fold that held it out: the score ## of the true class less the largest score given any other class. NaN ## where the response is not one of the trained classes, since a margin ## needs a true class to measure from. function m = foldMargin_ (this) [~, Score] = kfoldPredict (this); classes = this.ClassNames; K = classCount (classes); n = rows (this.X); m = nan (n, 1); for i = 1:n t = find (ismember (classes, this.Y(i))); if (isempty (t)) continue; endif t = t(1); if (K < 2) ## Degenerate single-class model: nothing competes, so the score ## itself is the whole margin. m(i) = Score(i, t); else other = Score(i, [1:t-1, t+1:K]); m(i) = Score(i, t) - max (other); endif endfor endfunction ## Edge over the observations selected by IDX: the margins weighted ## uniformly and normalized over that selection, so a subset of folds is ## an average rather than a sum, as foldLoss_ does for the losses. function e = foldEdge_ (this, m, idx) if (! any (idx)) e = NaN; return; endif mi = m(idx); e = sum (mi) / numel (mi); endfunction ## Loss over the observations selected by IDX, weighted uniformly and ## normalized over that selection, so a subset of folds is an average ## rather than a sum. function L = foldLoss_ (this, idx, Score, true_idx, pred_idx, LossFun, K) if (! any (idx) || ! any (true_idx(idx))) L = NaN; return; endif S = Score(idx, :); ti = true_idx(idx); pi_ = pred_idx(idx); m = sum (idx); W = ones (m, 1) / m; if (is_function_handle (LossFun)) C = false (m, K); C(sub2ind ([m, K], (1:m)', ti)) = true; L = LossFun (C, S, W, this.Cost); if (! (isnumeric (L) && isscalar (L))) error (strcat ("ClassificationPartitionedModel.kfoldLoss:", ... " 'LossFun' must return a numeric scalar.")); endif return; endif switch (LossFun) case 'classiferror' L = sum (W .* (pi_ != ti)); case 'classifcost' L = 0; for i = 1:m L = L + W(i) * this.Cost(ti(i), pi_(i)); endfor case 'mincost' L = 0; for i = 1:m [~, k] = min (S(i,:) * this.Cost); L = L + W(i) * this.Cost(ti(i), k); endfor endswitch endfunction ## Carry an assigned property down into every fold. MATLAB's Cost and ## Prior reach the fold models, so kfoldPredict and kfoldLoss answer under ## what was assigned rather than under what was fitted. The folds are ## absent while the constructor is still running, and the two properties ## are only ever pushed for the learners that accept the assignment. function this = pushToFolds (this, name, val) for k = 1:numel (this.Trained) if (! isempty (this.Trained{k})) this.Trained{k}.(name) = val; endif endfor endfunction endmethods endclassdef ## The prior a GAM fold is fitted with. The parent's prior is carried as an ## observation weight, prior(k) over the class count, and the fold's prior is ## the weight it retained, renormalised. Measured on R2024a over ten folds of ## three and seven with five distinct compositions, exact to eight digits; an ## empirical parent prior makes the weight the same for every class, so the ## fold's prior collapses to its own proportions, which is what a default fit ## reports. function pf = gamFoldPrior (prior, gY, nclass, idx) n = accumarray (gY(:), 1, [nclass, 1])'; gf = gY(idx); nf = accumarray (gf(:), 1, [nclass, 1])'; pf = prior(:)' .* (nf ./ n); pf = pf ./ sum (pf); endfunction %!demo %! %! load fisheriris %! x = meas; %! y = species; %! %! ## Create a KNN classifier model %! obj = fitcknn (x, y, 'NumNeighbors', 5, 'Standardize', 1); %! %! ## Create a partition for 5-fold cross-validation %! partition = cvpartition (y, 'KFold', 5); %! %! ## Create the ClassificationPartitionedModel object %! cvModel = crossval (obj, 'cvPartition', partition) %!demo %! %! load fisheriris %! x = meas; %! y = species; %! %! ## Create a KNN classifier model %! obj = fitcknn (x, y, 'NumNeighbors', 5, 'Standardize', 1); %! %! ## Create the ClassificationPartitionedModel object %! cvModel = crossval (obj); %! %! ## Predict the class labels for the observations not used for training %! [label, score, cost] = kfoldPredict (cvModel); %! fprintf ("Cross-validated accuracy = %1.2f%% (%d/%d)\n", ... %! sum (strcmp (label, y)) / numel (y) *100, ... %! sum (strcmp (label, y)), numel (y)) ## Tests %!test %! load fisheriris %! a = fitcdiscr (meas, species, 'gamma', 0.3); %! cvModel = crossval (a, 'KFold', 5); %! assert_equal (class (cvModel), "ClassificationPartitionedModel"); %! assert_equal (cvModel.NumObservations, 150); %! assert_equal (numel (cvModel.Trained), 5); %! assert_equal (class (cvModel.Trained{1}), "CompactClassificationDiscriminant"); %! assert_equal (cvModel.CrossValidatedModel, "Discriminant"); %! assert_equal (cvModel.KFold, 5); %!test %! load fisheriris %! a = fitcdiscr (meas, species, 'gamma', 0.5, 'fillcoeffs', 'off'); %! cvModel = crossval (a, 'HoldOut', 0.3); %! assert_equal (class (cvModel), "ClassificationPartitionedModel"); %! assert_equal ({cvModel.X, cvModel.Y}, {meas, species}); %! assert_equal (cvModel.NumObservations, 150); %! assert_equal (numel (cvModel.Trained), 1); %! assert_equal (class (cvModel.Trained{1}), "CompactClassificationDiscriminant"); %! assert_equal (cvModel.CrossValidatedModel, "Discriminant"); %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = fitcgam (x, y, 'Interactions', 'all'); %! cvModel = crossval (a, 'KFold', 2); %! assert_equal (class (cvModel), "ClassificationPartitionedModel"); %! assert_equal (cvModel.NumObservations, 4); %! assert_equal (numel (cvModel.Trained), 2); %! assert_equal (class (cvModel.Trained{1}), "CompactClassificationGAM"); %! assert_equal (cvModel.CrossValidatedModel, "GAM"); %! assert_equal (cvModel.KFold, 2); %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = fitcgam (x, y); %! cvModel = crossval (a, 'LeaveOut', 'on'); %! assert_equal (class (cvModel), "ClassificationPartitionedModel"); %! assert_equal ({cvModel.X, cvModel.Y}, {x, y}); %! assert_equal (cvModel.NumObservations, 4); %! assert_equal (numel (cvModel.Trained), 4); %! assert_equal (class (cvModel.Trained{1}), "CompactClassificationGAM"); %! assert_equal (cvModel.CrossValidatedModel, "GAM"); %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = fitcknn (x, y); %! partition = cvpartition (y, 'KFold', 2); %! cvModel = ClassificationPartitionedModel (a, partition); %! assert_equal (class (cvModel), "ClassificationPartitionedModel"); %! assert_equal (class (cvModel.Trained{1}), "ClassificationKNN"); %! assert_equal (cvModel.NumObservations, 4); %! assert_equal (cvModel.ModelParameters.NumNeighbors, 1); %! assert_equal (cvModel.ModelParameters.NSMethod, "kdtree"); %! assert_equal (cvModel.ModelParameters.Distance, "euclidean"); %! assert_equal (isempty (cvModel.Trained{1}.Mu), true); %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = fitcknn (x, y, 'NSMethod', 'exhaustive'); %! partition = cvpartition (y, 'HoldOut', 0.2); %! cvModel = ClassificationPartitionedModel (a, partition); %! assert_equal (class (cvModel), "ClassificationPartitionedModel"); %! assert_equal (class (cvModel.Trained{1}), "ClassificationKNN"); %! assert_equal ({cvModel.X, cvModel.Y}, {x, y}); %! assert_equal (cvModel.NumObservations, 4); %! assert_equal (cvModel.ModelParameters.NumNeighbors, 1); %! assert_equal (cvModel.ModelParameters.NSMethod, "exhaustive"); %! assert_equal (cvModel.ModelParameters.Distance, "euclidean"); %! assert_equal (isempty (cvModel.Trained{1}.Mu), true); %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! k = 2; %! a = fitcknn (x, y, 'NumNeighbors' ,k); %! partition = cvpartition (numel (y), 'LeaveOut'); %! cvModel = ClassificationPartitionedModel (a, partition); %! assert_equal (class (cvModel), "ClassificationPartitionedModel"); %! assert_equal (class (cvModel.Trained{1}), "ClassificationKNN"); %! assert_equal ({cvModel.X, cvModel.Y}, {x, y}); %! assert_equal (cvModel.NumObservations, 4); %! assert_equal (cvModel.ModelParameters.NumNeighbors, k); %! assert_equal (cvModel.ModelParameters.NSMethod, "kdtree"); %! assert_equal (cvModel.ModelParameters.Distance, "euclidean"); %! assert_equal (isempty (cvModel.Trained{1}.Mu), true); %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = {'a'; 'a'; 'b'; 'b'}; %! a = fitcnet (x, y, 'IterationLimit', 50); %! cvModel = crossval (a, 'KFold', 2); %! assert_equal (class (cvModel), "ClassificationPartitionedModel"); %! assert_equal (cvModel.NumObservations, 4); %! assert_equal (numel (cvModel.Trained), 2); %! assert_equal (class (cvModel.Trained{1}), "CompactClassificationNeuralNetwork"); %! assert_equal (cvModel.CrossValidatedModel, "NeuralNetwork"); %! assert_equal (cvModel.KFold, 2); %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = {'a'; 'a'; 'b'; 'b'}; %! a = fitcnet (x, y, 'LayerSizes', [5, 3]); %! cvModel = crossval (a, 'LeaveOut', 'on'); %! assert_equal (class (cvModel), "ClassificationPartitionedModel"); %! assert_equal ({cvModel.X, cvModel.Y}, {x, y}); %! assert_equal (cvModel.NumObservations, 4); %! assert_equal (numel (cvModel.Trained), 4); %! assert_equal (class (cvModel.Trained{1}), "CompactClassificationNeuralNetwork"); %! assert_equal (cvModel.CrossValidatedModel, "NeuralNetwork"); %!test %! load fisheriris %! inds = ! strcmp (species, 'setosa'); %! x = meas(inds, 3:4); %! y = grp2idx (species(inds)); %! SVMModel = fitcsvm (x,y); %! CVMdl = crossval (SVMModel, 'KFold', 5); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (CVMdl.KFold == 5, true) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationSVM") %! assert_equal (CVMdl.CrossValidatedModel, "SVM"); %!test %! load fisheriris %! inds = ! strcmp (species, 'setosa'); %! x = meas(inds, 3:4); %! y = grp2idx (species(inds)); %! obj = fitcsvm (x, y); %! CVMdl = crossval (obj, 'HoldOut', 0.2); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationSVM") %! assert_equal (CVMdl.CrossValidatedModel, "SVM"); %!test %! load fisheriris %! inds = ! strcmp (species, 'setosa'); %! x = meas(inds, 3:4); %! y = grp2idx (species(inds)); %! obj = fitcsvm (x, y); %! CVMdl = crossval (obj, 'LeaveOut', 'on'); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationSVM") %! assert_equal (CVMdl.CrossValidatedModel, "SVM"); ## The KNN short name, the one of the five that no test pinned. %!test %! load fisheriris %! CVMdl = crossval (fitcknn (meas, species), 'KFold', 5); %! assert_equal (CVMdl.CrossValidatedModel, "KNN"); ## Test input validation for ClassificationPartitionedModel ## Cross-validating a GAM rebuilds each fold from the term matrix, the ## interaction pairs no longer being a form the constructor accepts. Every ## fold carries the parent's terms. %!test %! k = (1:60)'; %! X = [mod(k*7,11)-5, mod(k*3,11)-5, mod(k*5,11)-5]; %! y = double (X(:,1).*X(:,2) > 0) + 1; %! Mdl = fitcgam (X, y, "Interactions", "all"); %! cv = crossval (Mdl, "KFold", 3); %! assert_equal (numel (cv.Trained), 3); %! assert_equal (cv.Trained{1}.Interactions, [1, 2; 1, 3; 2, 3]); %! assert_equal (cv.Trained{3}.Interactions, Mdl.Interactions); ## kfoldPredict writes whole names into its result: the labels are laid out ## one name per row, and assigning by element would fill the first column. %!test %! load fisheriris %! b = ! strcmp (species, "setosa"); %! X = meas(b,:); Ys = species(b); Yc = char (Ys); %! rand ("state", 1); Mc = fitcdiscr (X, Yc); %! rand ("state", 1); Ms = fitcdiscr (X, Ys); %! rand ("state", 2); cvc = crossval (Mc, "KFold", 3); %! rand ("state", 2); cvs = crossval (Ms, "KFold", 3); %! p = kfoldPredict (cvc); %! assert_equal (columns (p), 10); %! assert_equal (cellstr (p), kfoldPredict (cvs)); %!error ... %! ClassificationPartitionedModel () %!error ... %! ClassificationPartitionedModel (ClassificationKNN (ones (4,2), ones (4,1))) %!error ... %! ClassificationPartitionedModel (RegressionGAM (ones (40,2), ... %! randi ([1, 2], 40, 1)), cvpartition (randi ([1, 2], 40, 1), 'Holdout', 0.3)) %!error ... %! ClassificationPartitionedModel (ClassificationKNN (ones (4,2), ... %! ones (4,1)), 'Holdout') ## Test for kfoldPredict %!test %! load fisheriris %! a = fitcdiscr (meas, species, 'gamma', 0.5, 'fillcoeffs', 'off'); %! cvModel = crossval (a, 'Kfold', 4); %! [label, score, cost] = kfoldPredict (cvModel); %! assert_equal (class (cvModel), "ClassificationPartitionedModel"); %! assert_equal ({cvModel.X, cvModel.Y}, {meas, species}); %! assert_equal (cvModel.NumObservations, 150); %!# assert_equal (label, {"b"; "b"; "a"; "a"}); %!# assert_equal (score, [4.5380e-01, 5.4620e-01; 2.4404e-01, 7.5596e-01; ... %!# 9.9392e-01, 6.0844e-03; 9.9820e-01, 1.8000e-03], 1e-4); %!# assert_equal (cost, [5.4620e-01, 4.5380e-01; 7.5596e-01, 2.4404e-01; ... %!# 6.0844e-03, 9.9392e-01; 1.8000e-03, 9.9820e-01], 1e-4); %!test %! x = ones (4, 11); %! y = {'a'; 'a'; 'b'; 'b'}; %! k = 3; %! a = fitcknn (x, y, 'NumNeighbors', k); %! partition = cvpartition (numel (y), 'LeaveOut'); %! cvModel = ClassificationPartitionedModel (a, partition); %! [label, score, cost] = kfoldPredict (cvModel); %! assert_equal (class (cvModel), "ClassificationPartitionedModel"); %! assert_equal ({cvModel.X, cvModel.Y}, {x, y}); %! assert_equal (cvModel.NumObservations, 4); %! assert_equal (cvModel.ModelParameters.NumNeighbors, k); %! assert_equal (cvModel.ModelParameters.NSMethod, "exhaustive"); %! assert_equal (cvModel.ModelParameters.Distance, "euclidean"); %! assert_equal (isempty (cvModel.Trained{1}.Mu), true); %! assert_equal (label, {'b'; 'b'; 'a'; 'a'}); %! assert_equal (score, [0.3333, 0.6667; 0.3333, 0.6667; 0.6667, 0.3333; ... %! 0.6667, 0.3333], 1e-4); %! assert_equal (cost, [0.6667, 0.3333; 0.6667, 0.3333; 0.3333, 0.6667; ... %! 0.3333, 0.6667], 1e-4); ## Test input validation for kfoldPredict ## The partition, the stored data and NumObservations all describe the same ## set of observations. A row dropped for a missing value is outside all ## three: before this the partition covered it, NumObservations did not, and ## the folds trained on rows their own fit then discarded. %!test %! randn ('seed', 42); %! lab = double (randn (40, 1) > 0) + 1; %! X = [randn(40, 2); NaN, 1; 2, NaN]; %! Y = [lab; 1; 2]; %! Mdl = fitcsvm (X, Y); %! assert_equal (Mdl.NumObservations, 42); %! assert_equal (Mdl.RowsUsed, []); %! CVMdl = crossval (Mdl, 'KFold', 4); %! assert_equal (CVMdl.NumObservations, 42); %! assert_equal (rows (CVMdl.X), 42); %! assert_equal (rows (CVMdl.Y), 42); %! assert_equal (CVMdl.Partition.NumObservations, 42); %! assert_equal (numel (kfoldPredict (CVMdl)), 42); ## The folds stay stratified, the response being passed to cvpartition ## rather than a bare count. %!test %! randn ('seed', 7); %! X = randn (60, 2); %! Y = [ones(20, 1); 2 * ones(40, 1)]; %! CVMdl = crossval (fitcsvm (X, Y), 'KFold', 4); %! for k = 1:4 %! idx = test (CVMdl.Partition, k); %! assert_equal (sum (CVMdl.Y(idx) == 1) >= 4, true); %! assert_equal (sum (CVMdl.Y(idx) == 2) >= 8, true); %! endfor ## kfoldLoss defaults to the misclassification rate of the out-of-fold ## answers, which is the identity R2024a shows, and holds for every model ## type the class accepts. %!test %! load fisheriris %! X = meas(51:150,:); %! Y = species(51:150); %! rand ('seed', 42); %! cvp = cvpartition (Y, 'KFold', 5); %! for f = {'fitcsvm', 'fitcnet', 'fitcknn', 'fitcdiscr', 'fitcgam'} %! CVMdl = crossval (feval (f{1}, X, Y), 'CVPartition', cvp); %! assert_equal (kfoldLoss (CVMdl), ... %! mean (! strcmp (kfoldPredict (CVMdl), CVMdl.Y)), 1e-12); %! endfor ## Leave-one-out on a two-class fixture agrees with R2024a to the digit for ## every loss offered: it reports 0.02 for all three. %!test %! load fisheriris %! idx = [51:75, 101:125]; %! CVMdl = crossval (fitcdiscr (meas(idx,:), species(idx)), 'Leaveout', 'on'); %! assert_equal (CVMdl.KFold, 50); %! assert_equal (kfoldLoss (CVMdl), 0.02, 1e-12); %! assert_equal (kfoldLoss (CVMdl, 'LossFun', 'classifcost'), 0.02, 1e-12); %! assert_equal (kfoldLoss (CVMdl, 'LossFun', 'mincost'), 0.02, 1e-12); ## 'individual' is one number per fold, 'Folds' restricts to those named, and ## a handle is called with the class indicator, the scores, the weights and ## the cost, its weights summing to one as MATLAB's do. %!test %! load fisheriris %! X = meas(51:150,:); %! Y = species(51:150); %! rand ('seed', 42); %! cvp = cvpartition (Y, 'KFold', 5); %! CVMdl = crossval (fitcsvm (X, Y), 'CVPartition', cvp); %! L = kfoldLoss (CVMdl, 'Mode', 'individual'); %! assert_equal (size (L), [5, 1]); %! lab = kfoldPredict (CVMdl); %! k2 = test (cvp, 2); %! assert_equal (L(2), mean (! strcmp (lab(k2), CVMdl.Y(k2))), 1e-12); %! sel = test (cvp, 1) | test (cvp, 3); %! assert_equal (kfoldLoss (CVMdl, 'Folds', [1, 3]), ... %! mean (! strcmp (lab(sel), CVMdl.Y(sel))), 1e-12); %! assert_equal (numel (kfoldLoss (CVMdl, 'Folds', [2, 4], ... %! 'Mode', 'individual')), 2); %! assert_equal (kfoldLoss (CVMdl, 'LossFun', @(C, S, W, Cost) sum (W)), ... %! 1, 1e-12); ## Cost reaches the cost-aware losses. %!test %! load fisheriris %! X = meas(51:150,:); %! Y = species(51:150); %! rand ('seed', 42); %! CVMdl = crossval (fitcsvm (X, Y, 'Cost', [0, 4; 1, 0]), 'KFold', 5); %! assert_equal (CVMdl.Cost, [0, 4; 1, 0]); %! assert_equal (kfoldLoss (CVMdl, 'LossFun', 'classifcost') >= ... %! kfoldLoss (CVMdl, 'LossFun', 'classiferror'), true); ## Test input validation for kfoldLoss %!shared CVK %! load fisheriris %! rand ('seed', 42); %! CVK = crossval (fitcsvm (meas(51:150,:), species(51:150)), 'KFold', 4); %!error ... %! kfoldLoss (CVK, 'Mode') %!error ... %! kfoldLoss (CVK, 5, 1) %!error ... %! kfoldLoss (CVK, 'LossFun', 5) %!error ... %! kfoldLoss (CVK, 'LossFun', 'hinge') %!error ... %! kfoldLoss (CVK, 'LossFun', @(C, S, W, Cost) [1, 2]) %!error ... %! kfoldLoss (CVK, 'Mode', 'nope') %!error ... %! kfoldLoss (CVK, 'Folds', 0) %!error ... %! kfoldLoss (CVK, 'Nope', 1) ## Standardization reaches the folds. It did not for the KNN, whose refit ## arguments omitted it, so every fold trained on the raw scale while the ## parent reported itself standardized and nothing said so. %!test %! load fisheriris %! X = meas(:,1:3); %! X(:,1) = X(:,1) * 1000; %! CVMdl = crossval (fitcknn (X, species, 'Standardize', true), 'KFold', 5); %! assert_equal (isempty (CVMdl.Trained{1}.Mu), false); %!test %! load fisheriris %! X = meas(:,1:3); %! X(:,1) = X(:,1) * 1000; %! cvp = cvpartition (species, 'KFold', 5); %! CVMdl = crossval (fitcknn (X, species, 'Standardize', true), ... %! 'CVPartition', cvp); %! hit = 0; %! for k = 1:5 %! tr = training (cvp, k); %! te = test (cvp, k); %! Mdl = fitcknn (X(tr,:), species(tr), 'Standardize', true); %! hit += sum (strcmp (predict (Mdl, X(te,:)), species(te))); %! endfor %! assert_equal (sum (strcmp (kfoldPredict (CVMdl), species)), hit); ## The weights of the model that was cross validated are carried over, as ## the regression counterpart already did. %!test %! load fisheriris %! Mdl = fitcknn (meas, species); %! CVMdl = crossval (Mdl, 'KFold', 3); %! assert_equal (CVMdl.W, Mdl.W); %! assert_equal (size (CVMdl.W), [150, 1]); ## BinEdges is an empty cell, and a cell rather than an empty matrix: code ## that reaches into it with cellfun works against MATLAB and used to fail ## here on the type alone. %!test %! load fisheriris %! CVMdl = crossval (fitcknn (meas, species), 'KFold', 3); %! assert_equal (class (CVMdl.BinEdges), 'cell'); %! assert_equal (CVMdl.BinEdges, {}); ## An assigned Cost reaches every fold, so the folds predict under what was ## assigned rather than under what was fitted. Values measured on R2024a. %!test %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species), 'KFold', 3); %! C = double (! eye (3)); C(1,2) = 4; %! CVMdl.Cost = C; %! assert_equal (CVMdl.Trained{1}.Cost, C); %! assert_equal (CVMdl.Trained{3}.Cost, C); %!test %! load fisheriris %! CVMdl = crossval (fitcknn (meas, species), 'KFold', 3); %! C = double (! eye (3)); C(1,2) = 4; %! CVMdl.Cost = C; %! assert_equal (CVMdl.Trained{2}.Cost, C); ## The two generative learners take a prior after the fit, and it reaches the ## folds too. %!test %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species), 'KFold', 3); %! CVMdl.Prior = [0.6, 0.2, 0.2]; %! assert_equal (CVMdl.Prior, [0.6, 0.2, 0.2], 1e-15); %! assert_equal (CVMdl.Trained{1}.Prior, [0.6, 0.2, 0.2], 1e-15); %!test %! load fisheriris %! CVMdl = crossval (fitcnb (meas, species), 'KFold', 3); %! CVMdl.Prior = [0.6, 0.2, 0.2]; %! assert_equal (CVMdl.Prior, [0.6, 0.2, 0.2], 1e-15); %! assert_equal (CVMdl.Trained{1}.Prior, [0.6, 0.2, 0.2], 1e-15); ## The folds do not merely store the prior, they score by it. Iris is too ## well separated to show it, its posteriors sitting at 0 and 1, so the two ## overlapping species on their two weakest predictors are used instead. %!test %! load fisheriris %! X = meas(51:150,1:2); %! Y = species(51:150); %! CVMdl = crossval (fitcnb (X, Y), 'KFold', 5); %! before = kfoldPredict (CVMdl); %! CVMdl.Prior = [0.9, 0.1]; %! after = kfoldPredict (CVMdl); %! assert_equal (sum (! strcmp (before, after)) > 0, true); ## Priors are normalized, as they are on the learner itself. %!test %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species), 'KFold', 3); %! CVMdl.Prior = [3, 1, 1]; %! assert_equal (CVMdl.Prior, [0.6, 0.2, 0.2], 1e-15); %!test %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species), 'KFold', 3); %! CVMdl.Prior = 'uniform'; %! assert_equal (CVMdl.Prior, [1, 1, 1] / 3, 1e-15); ## An SVM's costs enter its box constraint while it is fitted, so MATLAB ## refuses them afterwards on the cross-validated model and so do we. %!error ... %! load fisheriris; ... %! b = ismember (species, {'setosa', 'versicolor'}); ... %! CVMdl = crossval (fitcsvm (meas(b,:), species(b)), 'KFold', 3); ... %! CVMdl.Cost = [0, 4; 1, 0]; %!error ... %! load fisheriris; ... %! CVMdl = crossval (fitcknn (meas, species), 'KFold', 3); ... %! CVMdl.Prior = [0.6, 0.2, 0.2]; %!error ... %! load fisheriris; ... %! CVMdl = crossval (fitcnet (meas, species), 'KFold', 3); ... %! CVMdl.Prior = [0.6, 0.2, 0.2]; %!error ... %! load fisheriris; ... %! CVMdl = crossval (fitcdiscr (meas, species), 'KFold', 3); ... %! CVMdl.Prior = [0.5, 0.5]; ## A GAM backing transforms once, like every other. Its class default is ## 'logit', so the fold has to be pinned at 'none' or the parent's transform ## lands on scores the fold has already transformed and the rows sum to about ## 1.23 instead of one. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! CVMdl = crossval (fitcgam (meas(inds,:), species(inds)), 'KFold', 3); %! assert_equal (CVMdl.ScoreTransform, 'logit'); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'none'); %! [~, s] = kfoldPredict (CVMdl); %! assert_equal (sum (s, 2), ones (rows (s), 1), 1e-12); ## ScoreTransform is applied to the assembled scores, not carried into the ## folds. MATLAB leaves every Trained{k} at 'none' and transforms at the ## wrapper; the property used to be stored here and never read. %!test %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species), 'KFold', 3); %! [~, s0] = kfoldPredict (CVMdl); %! CVMdl.ScoreTransform = 'doublelogit'; %! [~, s1] = kfoldPredict (CVMdl); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'none'); %! assert_equal (s1, 1 ./ (1 + exp (-2 * s0)), 1e-12); ## The transform reaches kfoldLoss, which reads those scores. A loss that ## sums them shows it; mincost and classiferror do not move on this fixture, ## since a monotone transform leaves the chosen labels where they were. %!test %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species), 'KFold', 3); %! f = @(C, S, W, Cost) sum (S(:)); %! [~, s0] = kfoldPredict (CVMdl); %! assert_equal (kfoldLoss (CVMdl, 'LossFun', f), 150, 1e-12); %! CVMdl.ScoreTransform = 'doublelogit'; %! assert_equal (kfoldLoss (CVMdl, 'LossFun', f), ... %! sum (sum (1 ./ (1 + exp (-2 * s0)))), 1e-12); ## 'none' is the identity, so the default transforms nothing. %!test %! load fisheriris %! CVMdl = crossval (fitcknn (meas, species), 'KFold', 3); %! [~, s0] = kfoldPredict (CVMdl); %! CVMdl.ScoreTransform = 'none'; %! [~, s1] = kfoldPredict (CVMdl); %! assert_equal (s1, s0); ## A transform carried by the model being cross-validated moves to the parent ## and is not handed to the folds. R2024a leaves every Trained@{k@} at 'none' ## for all five backings; the KNN and the network used to be given it as well ## and so applied it a second time. %!test %! load fisheriris %! CVMdl = crossval (fitcknn (meas, species, ... %! 'ScoreTransform', 'doublelogit'), 'KFold', 3); %! assert_equal (CVMdl.ScoreTransform, 'doublelogit'); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'none'); %!test %! load fisheriris %! CVMdl = crossval (fitcnet (meas, species, ... %! 'ScoreTransform', 'doublelogit'), 'KFold', 3); %! assert_equal (CVMdl.ScoreTransform, 'doublelogit'); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'none'); %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! CVMdl = crossval (fitcsvm (meas(inds,:), species(inds), ... %! 'ScoreTransform', 'doublelogit'), 'KFold', 3); %! assert_equal (CVMdl.ScoreTransform, 'doublelogit'); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'none'); ## And it is applied once, not twice. A KNN is deterministic given the ## partition, so the two fits share their folds and the untransformed one is ## an exact baseline: doublelogit applied twice would not match it. %!test %! load fisheriris %! c = cvpartition (species, 'KFold', 3); %! CV0 = crossval (fitcknn (meas, species), 'CVPartition', c); %! CV1 = crossval (fitcknn (meas, species, ... %! 'ScoreTransform', 'doublelogit'), 'CVPartition', c); %! [~, s0] = kfoldPredict (CV0); %! [~, s1] = kfoldPredict (CV1); %! assert_equal (s1, 1 ./ (1 + exp (-2 * s0)), 1e-12); %!test %! load fisheriris %! CVMdl = crossval (fitcnet (meas, species, ... %! 'ScoreTransform', 'doublelogit'), 'KFold', 3); %! [~, s1] = kfoldPredict (CVMdl); %! CVMdl.ScoreTransform = 'none'; %! [~, s0] = kfoldPredict (CVMdl); %! assert_equal (s1, 1 ./ (1 + exp (-2 * s0)), 1e-12); %!error ... %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species), 'KFold', 3); %! CVMdl.Cost = 1:9; ## foldLoss_ is a private helper and stays out of the method list. %!test %! assert_equal (any (strcmp (methods ("ClassificationPartitionedModel"), ... %! "foldLoss_")), false); ## kfoldMargin answers for every observation, from the fold that held it out. %!test %! load fisheriris %! CVMdl = crossval (fitcknn (meas, species), 'Leaveout', 'on'); %! m = kfoldMargin (CVMdl); %! assert_equal (size (m), [150, 1]); %! assert_equal (all (m >= -1 & m <= 1), true); ## kfoldEdge is the mean of the margins. R2024a returns 0.92 on this ## leave-one-out KNN and ours reproduces it exactly. %!test %! load fisheriris %! CVMdl = crossval (fitcknn (meas, species), 'Leaveout', 'on'); %! assert_equal (kfoldEdge (CVMdl), 0.92, 1e-14); %! assert_equal (kfoldEdge (CVMdl), mean (kfoldMargin (CVMdl)), 1e-14); ## 'Mode' selects one number per fold or one over them all. %!test %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species), 'KFold', 5); %! assert_equal (size (kfoldEdge (CVMdl, 'Mode', 'individual')), [5, 1]); %! assert_equal (isscalar (kfoldEdge (CVMdl, 'Mode', 'average')), true); ## 'Folds' restricts the selection, and one fold's edge is that fold's mean. %!test %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species), 'KFold', 5); %! m = kfoldMargin (CVMdl); %! idx = test (CVMdl.Partition, 2); %! assert_equal (kfoldEdge (CVMdl, 'Folds', 2), mean (m(idx)), 1e-12); %! assert_equal (kfoldEdge (CVMdl, 'Folds', 1:5), kfoldEdge (CVMdl), 1e-12); %!error ... %! load fisheriris %! kfoldEdge (crossval (fitcdiscr (meas, species), 'KFold', 3), 'Mode') %!error ... %! load fisheriris %! kfoldEdge (crossval (fitcdiscr (meas, species), 'KFold', 3), 5, 'average') %!error ... %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species), 'KFold', 3); %! kfoldEdge (CVMdl, 'Mode', 'cumulative') %!error ... %! load fisheriris %! kfoldEdge (crossval (fitcdiscr (meas, species), 'KFold', 3), 'Folds', 7) %!error ... %! load fisheriris %! kfoldEdge (crossval (fitcdiscr (meas, species), 'KFold', 3), 'bogus', 1) ## A holdout partition predicts the holdout set and leaves the rest missing. %!test %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species), 'Holdout', 0.2); %! idx = test (CVMdl.Partition, 1); %! [label, Score] = kfoldPredict (CVMdl); %! assert_equal (iscellstr (label), true); %! assert_equal (all (strcmp (label(! idx), '')), true); %! assert_equal (any (strcmp (label(idx), '')), false); %! assert_equal (all (all (isnan (Score(! idx, :)))), true); %! assert_equal (any (any (isnan (Score(idx, :)))), false); ## The holdout predictions are the fold's own, not a stand-in class. %!test %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species), 'Holdout', 0.2); %! idx = test (CVMdl.Partition, 1); %! label = kfoldPredict (CVMdl); %! assert_equal (label(idx), predict (CVMdl.Trained{1}, meas(idx,:))); %! assert_equal (mean (strcmp (label(idx), species(idx))) > 0.9, true); ## A numeric response reports a missing label as NaN. %!test %! load fisheriris %! y = grp2idx (species); %! CVMdl = crossval (fitcdiscr (meas, y), 'Holdout', 0.2); %! idx = test (CVMdl.Partition, 1); %! label = kfoldPredict (CVMdl); %! assert_equal (all (isnan (label(! idx))), true); %! assert_equal (any (isnan (label(idx))), false); ## kfoldLoss and kfoldEdge answer over the holdout set alone. %!test %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species), 'Holdout', 0.2); %! idx = test (CVMdl.Partition, 1); %! label = kfoldPredict (CVMdl); %! assert_equal (kfoldLoss (CVMdl), ... %! mean (! strcmp (label(idx), species(idx))), 1e-12); %! m = kfoldMargin (CVMdl); %! assert_equal (all (isnan (m(! idx))), true); %! assert_equal (kfoldEdge (CVMdl), mean (m(idx)), 1e-12); ## The shared cost guard is in force here too, and the struct form is ## permuted into this model's class order. The battery is on ## ClassificationDiscriminant. %!test %! load fisheriris %! Mdl = crossval (fitcdiscr (meas, species), 'KFold', 3); %! S = struct ('ClassNames', {{'virginica'; 'setosa'; 'versicolor'}}, ... %! 'ClassificationCosts', [0, 1, 2; 3, 0, 4; 5, 6, 0]); %! Mdl.Cost = S; %! assert_equal (Mdl.Cost, [0, 4, 3; 6, 0, 5; 1, 2, 0]); %!error ... %! load fisheriris %! Mdl = crossval (fitcdiscr (meas, species), 'KFold', 3); %! Mdl.Cost = ones (3); ## A fold is refitted with the argument set of the engine that fitted the ## parent: the two surfaces are disjoint and each refuses the other's, so a ## tree-fitted parent must not hand its folds Knots and Order. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds), 'FitMethod', 'boostedtrees'); %! CVMdl = crossval (Mdl, 'KFold', 3); %! assert_equal (class (CVMdl), 'ClassificationPartitionedModel'); %! assert_equal (numel (CVMdl.Trained), 3); %! assert_equal (CVMdl.Trained{1}.FitMethod, 'boostedtrees'); %! assert_equal (isempty (CVMdl.Trained{1}.TreeModel), false); ## The fold still carries no transform of its own, whatever engine fitted it: ## the parent applies it once to the assembled score. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds), 'FitMethod', 'boostedtrees'); %! CVMdl = crossval (Mdl, 'KFold', 3); %! assert_equal (CVMdl.ScoreTransform, 'logit'); %! assert_equal (CVMdl.Trained{1}.ScoreTransform, 'none'); %! [~, s] = kfoldPredict (CVMdl); %! assert_equal (sum (s, 2), ones (rows (s), 1), 1e-12); ## A tree-fitted parent holds its interactions as predictor pairs where the ## constructor takes a term matrix, so the pairs are widened before the fold ## is refitted and every fold carries the same terms as the parent. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds), 'FitMethod', 'boostedtrees', ... %! 'Interactions', 2); %! CVMdl = crossval (Mdl, 'KFold', 3); %! assert_equal (rows (Mdl.Interactions), 2); %! assert_equal (rows (CVMdl.Trained{1}.Interactions), 2); ## A spline-fitted parent is unaffected: its folds take the spline parameters ## and are fitted by the same engine. They do not report the parameters ## themselves, a compact model keeping no record of its fitting. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds), 'FitMethod', 'splines', ... %! 'Knots', 4); %! CVMdl = crossval (Mdl, 'KFold', 3); %! assert_equal (CVMdl.Trained{1}.FitMethod, 'splines'); %! assert_equal (isfield (CVMdl.Trained{1}.BaseModel, 'Intercept'), true); %!test %! ## kfoldfun hands the fold's model, its training data and its held-out %! ## data to the function, seven arguments in that order. %! load fisheriris %! CV = crossval (fitcknn (meas, species), "KFold", 3); %! seen = kfoldfun (CV, @(M, Xtr, Ytr, Wtr, Xte, Yte, Wte) ... %! [rows(Xtr), rows(Ytr), rows(Wtr), ... %! rows(Xte), rows(Yte), rows(Wte)]); %! assert_equal (size (seen), [3, 6]); %! ## the three training counts agree with each other, as do the three test %! ## counts, and each row accounts for every observation %! assert_equal (seen(:,1), seen(:,2)); %! assert_equal (seen(:,1), seen(:,3)); %! assert_equal (seen(:,4), seen(:,5)); %! assert_equal (seen(:,4), seen(:,6)); %! assert_equal (seen(:,1) + seen(:,4), repmat (150, 3, 1)); %!test %! ## The result is one row per fold, whatever the width. %! load fisheriris %! CV = crossval (fitcknn (meas, species), "KFold", 4); %! assert_equal (size (kfoldfun (CV, @(varargin) 1)), [4, 1]); %! assert_equal (size (kfoldfun (CV, @(varargin) [1, 2, 3])), [4, 3]); %!test %! ## The use it exists for: a count of errors on each held-out fold. %! load fisheriris %! CV = crossval (fitcknn (meas, species), "KFold", 3); %! f = @(M, Xtr, Ytr, Wtr, Xte, Yte, Wte) sum (! strcmp (predict (M, Xte), Yte)); %! n = kfoldfun (CV, f); %! assert_equal (size (n), [3, 1]); %! assert_equal (all (n >= 0 & n <= 150), true); %!test %! ## The model handed over is the one the fold was fitted with. %! load fisheriris %! CV = crossval (fitcknn (meas, species), "KFold", 3); %! same = kfoldfun (CV, @(M, varargin) isequal (M, CV.Trained{1})); %! assert_equal (same(1), 1); %!error ... %! kfoldfun (crossval (fitcknn (ones (6, 2), [1;1;1;2;2;2]), "KFold", 2)) %!error ... %! kfoldfun (crossval (fitcknn (ones (6, 2), [1;1;1;2;2;2]), "KFold", 2), 42) %!error ... %! kfoldfun (crossval (fitcknn (ones (6, 2), [1;1;1;2;2;2]), "KFold", 2), ... %! @(varargin) "x") %!test %! load fisheriris %! CV = crossval (fitcknn (meas, species), "KFold", 3); %! g = @(M, Xtr, varargin) ones (1, rows (Xtr)); %! fail ("kfoldfun (CV, g)", ... %! "must return the same number of values for every fold"); %!test %! ## The property order is MATLAB's, measured on R2024a. It is one list per %! ## class there and does not vary with the backing, so one fixture pins it. %! load fisheriris %! CVMdl = crossval (fitcknn (meas, species), "KFold", 3); %! assert_equal (sort (properties (CVMdl)), ... %! sort ({'ClassNames'; 'Cost'; 'Prior'; 'ScoreTransform'; ... %! 'CrossValidatedModel'; 'PredictorNames'; ... %! 'CategoricalPredictors'; 'ResponseName'; ... %! 'NumObservations'; 'X'; 'Y'; 'W'; ... %! 'ModelParameters'; 'Trained'; 'KFold'; 'Partition'; ... %! 'BinEdges'; 'NumTrainedPerFold'})); %!test %! ## NumTrainedPerFold reports what each fold actually fitted, which the %! ## budget in ModelParameters does not: boosting stops early and the folds %! ## need not stop together. Structure and semantics follow R2024a; the %! ## counts do not and are not expected to, the engine being this package's. %! load fisheriris %! b = strcmp (species, "setosa"); %! CVMdl = crossval (fitcgam (meas(:,2:4), b), "KFold", 3); %! n = CVMdl.NumTrainedPerFold; %! assert_equal (isstruct (n), true); %! assert_equal (sort (fieldnames (n)), {"InteractionTrees"; "PredictorTrees"}); %! assert_equal (size (n.PredictorTrees), [1, 3]); %! assert_equal (size (n.InteractionTrees), [1, 3]); %! ## early stopping, so under the budget rather than at it %! assert_equal (all (n.PredictorTrees < CVMdl.ModelParameters.NumTreesPerPredictor), true); %! ## no interactions were asked for, so none were fitted %! assert_equal (n.InteractionTrees, [0, 0, 0]); %!test %! ## A backing that fits no trees reports nothing rather than zero of them. %! load fisheriris %! CVMdl = crossval (fitcknn (meas, species), "KFold", 3); %! assert_equal (isempty (CVMdl.NumTrainedPerFold), true); %!test %! ## Nor does a GAM fitted by splines, which counts no trees either. %! load fisheriris %! b = strcmp (species, "setosa"); %! Mdl = fitcgam (meas(1:30,2:4), b(1:30), "FitMethod", "splines"); %! CVMdl = crossval (Mdl, "KFold", 3); %! assert_equal (isempty (CVMdl.NumTrainedPerFold), true); ## A polynomial-kernel SVM refits its folds through the recorded order, which ## the parent carries only under that kernel. %!test %! load fisheriris %! Mdl = fitcsvm (meas, strcmp (species, 'setosa'), ... %! 'KernelFunction', 'polynomial', 'PolynomialOrder', 2); %! CVMdl = crossval (Mdl, 'KFold', 3); %! assert_equal (CVMdl.ModelParameters.KernelPolynomialOrder, 2); %! assert_equal (numel (CVMdl.Trained), 3); ## ModelParameters carries the learner's parameters under this class's own ## tags, so a cross-validated SVM says what it was fitted with and still ## reports itself as a partitioned model. %!test %! load fisheriris %! CVMdl = crossval (fitcsvm (meas, strcmp (species, 'setosa')), 'KFold', 3); %! MP = CVMdl.ModelParameters; %! assert_equal (MP.Method, 'PartitionedModel'); %! assert_equal (MP.Type, 'classification'); %! assert_equal (MP.Version, 1); %! assert_equal (MP.NLearn, 3); %! assert_equal (MP.SVMtype, 'c_svc'); ## The discriminant's parameters come through whole. They were assembled ## from a name list before, which dropped FillCoeffs because this class has ## no property of that name to read it from. %!test %! load fisheriris %! CVMdl = crossval (fitcdiscr (meas, species, 'FillCoeffs', 'off'), ... %! 'KFold', 3); %! MP = CVMdl.ModelParameters; %! assert_equal (MP.FillCoeffs, false); %! assert_equal (MP.DiscrimType, 'linear'); %! assert_equal (MP.Method, 'PartitionedModel'); %!test %! load fisheriris %! MP = crossval (fitcknn (meas, species, 'Distance', 'minkowski'), ... %! 'KFold', 3).ModelParameters; %! assert_equal (MP.Exponent, 2); %! assert_equal (MP.Method, 'PartitionedModel'); ## A network fold's score is a posterior, so the expected cost is formed from ## it here. MATLAB reports the same for a network backing, although its own ## network predict refuses a third output as ours does. %!test %! load fisheriris %! CVMdl = crossval (fitcnet (meas, strcmp (species, 'setosa'), ... %! 'Cost', [0, 2; 5, 0]), 'KFold', 3); %! [label, score, cost] = kfoldPredict (CVMdl); %! assert_equal (size (cost), [150, 2]); %! assert_equal (cost, score * [0, 2; 5, 0], 1e-12); %!test %! load fisheriris %! CVMdl = crossval (fitcnet (meas, species), 'KFold', 3); %! [~, score, cost] = kfoldPredict (CVMdl); %! assert_equal (cost, 1 - score, 1e-12); ## A cross-validated GAM reports no cost, MATLAB's refusing it as well. %!error ... %! load fisheriris; ... %! [l, s, c] = kfoldPredict (crossval (fitcgam (meas, strcmp (species, 'setosa')), 'KFold', 3)) ## An SVM backing reports its expected cost, the fold models carrying one. %!test %! load fisheriris %! CVMdl = crossval (fitcsvm (meas, strcmp (species, 'setosa'), ... %! 'Cost', [0, 2; 5, 0]), 'KFold', 3); %! [~, ~, cost] = kfoldPredict (CVMdl); %! assert_equal (size (cost), [150, 2]); %! assert_equal (unique (cost, 'rows'), [0, 2; 5, 0]); ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = crossval (fitcknn (meas, species), 'KFold', 3); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = kfoldPredict (Mdl); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = kfoldPredict (Mdl); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = kfoldPredict (Mdl); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = kfoldPredict (Mdl); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = crossval (fitcknn (meas, species), 'KFold', 3); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = kfoldPredict (Mdl); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = kfoldPredict (Mdl); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); ## The GAM is the one backing whose fold prior is not the parent's: the prior ## is carried as observation weight and refitted from the rows the fold kept, ## which MATLAB does and the other five backings do not. The values below are ## that rule applied to each fold's own composition, and each was measured on ## R2024a for the composition it belongs to. %!test %! load fisheriris %! b = strcmp (species, 'setosa'); %! CVMdl = crossval (fitcgam (meas, b, 'Prior', [0.3, 0.7]), 'KFold', 3); %! for k = 1:3 %! tr = training (CVMdl.Partition, k); %! n = [sum(b(tr) == 0), sum(b(tr) == 1)]; %! w = [0.3, 0.7] .* (n ./ [100, 50]); %! assert_equal (CVMdl.Trained{k}.Prior, w ./ sum (w), 1e-12); %! endfor ## An empirical parent prior gives every class the same weight, so the fold's ## prior collapses to its own proportions. %!test %! load fisheriris %! b = strcmp (species, 'setosa'); %! CVMdl = crossval (fitcgam (meas, b), 'KFold', 3); %! for k = 1:3 %! tr = training (CVMdl.Partition, k); %! n = [sum(b(tr) == 0), sum(b(tr) == 1)]; %! assert_equal (CVMdl.Trained{k}.Prior, n ./ sum (n), 1e-12); %! endfor statistics-release-1.9.2/inst/Supervised_Learning/ClassificationSVM.m000066400000000000000000004136211524624707500260020ustar00rootroot00000000000000## Copyright (C) 2024 Pallav Purbia ## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef ClassificationSVM ## -*- texinfo -*- ## @deftp {statistics} ClassificationSVM ## ## Support Vector Machine classification ## ## The @code{ClassificationSVM} class implements a Support Vector Machine ## classifier object for one-class or two-class problems, which can predict ## responses for new data using the @code{predict} method. ## ## Support Vector Machine classification is a supervised learning method used ## for classification tasks. It works by finding the optimal hyperplane that ## separates classes in the feature space with the maximum margin. For ## non-linearly separable data, it uses kernel functions to map data to a ## higher-dimensional space where separation is possible. ## ## Create a @code{ClassificationSVM} object by using the @code{fitcsvm} ## function or the class constructor. ## ## @seealso{fitcsvm} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} X ## ## Predictor data ## ## A numeric matrix containing the unstandardized predictor data. Each ## column of @var{X} represents one predictor (variable), and each row ## represents one observation. This property is read-only. ## ## @end deftp X = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} Y ## ## Class labels ## ## Specified as a logical or numeric column vector, or as a character array ## or a cell array of character vectors with the same number of rows as the ## predictor data. Each row in @var{Y} is the observed class label for ## the corresponding row in @var{X}. This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} NumObservations ## ## Number of observations ## ## A positive integer value specifying the number of observations in the ## training dataset used for training the ClassificationSVM model. ## This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} RowsUsed ## ## Rows used for fitting ## ## A logical column vector with the same length as the observations in the ## original predictor data @var{X}, true for each row that was used for ## fitting the ClassificationSVM model. It is empty, @qcode{[]}, ## when every observation was used, so a non-empty value means that rows ## holding missing values were dropped. This property is read-only. ## ## @end deftp RowsUsed = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} NumPredictors ## ## Number of predictors ## ## A positive integer value specifying the number of predictors in the ## training dataset used for training the ClassificationSVM model. ## This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} PredictorNames ## ## Names of predictor variables ## ## A cell array of character vectors specifying the names of the predictor ## variables. The names are in the order in which they appear in the ## training dataset. This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} ResponseName ## ## Response variable name ## ## A character vector specifying the name of the response variable @var{Y}. ## This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} ClassNames ## ## Names of classes in the response variable ## ## An array of unique values of the response variable @var{Y}, which has the ## same data types as the data in @var{Y}. This property is read-only. ## @qcode{ClassNames} can have any of the following datatypes: ## ## @itemize ## @item Cell array of character vectors ## @item Character array ## @item Logical vector ## @item Numeric vector ## @end itemize ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} Sigma ## ## Predictor standard deviations ## ## A numeric vector of the same length as the columns in @var{X} containing ## the standard deviations of predictor variables. If the predictor ## variables have not been standardized, then @qcode{Sigma} is empty. ## This property is read-only. ## ## Only observations with no missing predictor enter the estimate, and ## they are weighted so that each class keeps the share of the ## observation weight it carried before any row was set aside. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} Mu ## ## Predictor means ## ## A numeric vector of the same length as the columns in @var{X} containing ## the means of predictor variables. If the predictor variables have not ## been standardized, then @qcode{Mu} is empty. This property is read-only. ## ## Only observations with no missing predictor enter the estimate, and ## they are weighted so that each class keeps the share of the ## observation weight it carried before any row was set aside. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} ModelParameters ## ## SVM training parameters ## ## A structure holding the parameters the fit was given. The engine is ## LIBSVM and the record is LIBSVM's, so @qcode{SVMtype} names its ## formulation and @qcode{Tolerance} and @qcode{Shrinking} are its own ## controls; the parameters MathWorks reports for its SMO and ISDA ## solvers are absent, this class running neither. ## ## @qcode{KernelPolynomialOrder} belongs to the polynomial kernel alone ## and is empty under every other, as it is in MATLAB. ## ## A structure containing the parameters used to train the SVM model with ## the following fields: @code{SVMtype}, @code{BoxConstraint}, ## @code{CacheSize}, @code{KernelScale}, @code{KernelOffset}, ## @code{KernelFunction}, @code{PolynomialOrder}, @code{Nu}, ## @code{Tolerance}, and @code{Shrinking}. This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} Alpha ## ## Trained classifier coefficients ## ## The coefficients of the trained SVM classifier specified as an @math{s*1} ## numeric vector, where @math{s} is the number of support vectors equal to ## @qcode{sum (obj.IsSupportVector)}. They are the magnitudes of the dual ## coefficients and are never negative; the class each belongs to is given ## by the corresponding entry of @qcode{SupportVectorLabels}. ## @qcode{Alpha} is populated for every kernel function. This property is ## read-only. ## ## @end deftp Alpha = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} Beta ## ## Linear predictor coefficients ## ## The linear predictor coefficients specified as a @math{p*1} numeric ## vector, where @math{p} is the number of predictors. @qcode{Beta} is ## the primal representation of the fitted hyperplane and exists only when ## the SVM classifier was trained with a @qcode{'linear'} kernel function; ## for any other kernel there is no such representation and @qcode{Beta} is ## empty. It equals ## @qcode{obj.SupportVectors' * (obj.Alpha .* obj.SupportVectorLabels)}. ## This property is read-only. ## ## @end deftp Beta = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} Bias ## ## Bias term ## ## The bias term specified as a scalar. This property is read-only. ## ## @end deftp Bias = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} IsSupportVector ## ## Support vector indicator ## ## An @math{N*1} logical vector that flags whether a corresponding ## observation in the predictor data matrix is a Support Vector. @math{N} ## is the number of observations in the training data. This property is ## read-only. ## ## @end deftp IsSupportVector = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} SupportVectorLabels ## ## Support vector class labels ## ## The support vector class labels specified as an @math{s*1} numeric ## vector, where @math{s} is the number of support vectors equal to ## @qcode{sum (obj.IsSupportVector)}. A value of +1 in ## @code{SupportVectorLabels} indicates that the corresponding support ## vector ## belongs to the positive class @qcode{(ClassNames@{2@})}. A value of -1 ## indicates that the corresponding support vector belongs to the negative ## class @qcode{(ClassNames@{1@})}. This property is read-only. ## ## @end deftp SupportVectorLabels = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} SupportVectors ## ## Support vectors ## ## The support vectors of the trained SVM classifier specified an @math{s*p} ## numeric matrix, where @math{s} is the number of support vectors equal to ## @qcode{sum (obj.IsSupportVector)}, and @math{p} is the number of ## predictor ## variables in the predictor data. This property is read-only. ## ## @end deftp SupportVectors = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} Prior ## ## Prior probabilities of the classes ## ## A numeric row vector with one entry per class, in the order of ## @code{ClassNames}, summing to one. It defaults to the class ## frequencies of the training data. This property is read-only. ## ## Specified as a row vector with one entry per class, in the order of ## @qcode{ClassNames}, and rescaled to sum to one. It may be given as ## @qcode{'empirical'}, @qcode{'uniform'}, a numeric vector, or a ## structure with @qcode{ClassNames} and @qcode{ClassProbs} fields, which ## assigns each probability by class name rather than by position. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} Cost ## ## Cost of misclassification ## ## A numeric square matrix, where @code{Cost(i,j)} is the cost of ## classifying an observation of class @math{i} as class @math{j}. It ## defaults to zero on the diagonal and one elsewhere. This property is ## read-only. ## ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} W ## ## Observation weights ## ## A numeric column vector with one entry per training observation, ## normalized to sum to one, as MATLAB reports it. This property is ## read-only. ## ## Each class carries its prior spread evenly over its own observations, ## so an observation of a class weighs @qcode{Prior} for that class ## divided by the number of observations it holds. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} KernelParameters ## ## Parameters of the kernel function ## ## A structure with fields @qcode{Function} and @qcode{Scale}, and ## @qcode{Order} for a polynomial kernel. @qcode{Function} names the ## kernel as MATLAB names it, so a radial basis kernel reports ## @qcode{'gaussian'} whichever spelling was given; the kernel the fit was ## handed is unchanged in @qcode{ModelParameters}. This property is ## read-only. ## ## @end deftp KernelParameters = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} BoxConstraints ## ## Box constraints ## ## A numeric column vector with one entry per observation, holding the ## box constraint the fit applied to it. It is @qcode{BoxConstraint} for ## every observation unless @qcode{Prior} or @qcode{Cost} reweighted the ## classes, in which case each class is scaled by the weight it carried ## into the fit, normalized so the weights average to one. This property ## is read-only. ## ## @end deftp BoxConstraints = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} OutlierFraction ## ## Expected fraction of outliers in the training data ## ## A scalar in @code{[0, 1)}, zero unless one was asked for. ## ## @strong{Deviation from MATLAB.} The value is reported as it was given, ## but it reaches the fit by a different route: MATLAB removes outliers ## iteratively and reports @qcode{Solver} as @qcode{'ISDA'}, where a ## nonzero fraction here selects LIBSVM's @math{\nu}-SVC, in which ## @math{\nu} bounds the fraction of margin errors. The two agree on what ## the number means and not on how the fit reaches it. This property is ## read-only. ## ## @end deftp OutlierFraction = 0; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} Nu ## ## Nu parameter for one-class learning ## ## A positive scalar, and empty unless the model is a one-class learner, ## which is what MATLAB reports. This property is read-only. ## ## @end deftp Nu = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector of column indices into @code{X} naming the predictors ## treated as categorical, and empty when none is. This property is ## read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} ExpandedPredictorNames ## ## Names of the predictors as the model expanded them ## ## A cell array of character vectors. It matches @code{PredictorNames} ## unless a categorical predictor was expanded into indicator variables. ## This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} BinEdges ## ## Bin edges of the predictors ## ## A cell array with one entry per predictor, holding that predictor's bin ## edges where the learner discretized it before fitting. It is empty here ## and stays empty: this learner fits the predictors as they are, and ## MATLAB's reports an empty cell for it as well. ## ## This property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} HyperparameterOptimizationResults ## ## Results of the hyperparameter optimization ## ## @strong{Always empty.} It is declared for MATLAB compatibility, where ## it holds what an automatic search over the hyperparameters found. This ## class fits the parameters it is given and runs no such search, so there ## is nothing to report. This property is read-only. ## ## @end deftp HyperparameterOptimizationResults = []; endproperties ## The LIBSVM structure the engine works in. It is ours alone, with no ## MATLAB counterpart, so it is kept out of the property listing while ## staying readable for anyone who needs the raw model. properties (Hidden, GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} Model ## ## Trained SVM model ## ## A structure containing the trained model in @qcode{'libsvm'} format. ## This property is read-only. ## ## It is the engine's own structure and has no MATLAB counterpart, ## so it is kept out of @code{properties} and out of the online ## documentation. Reading it works exactly as it always did. ## ## @end deftp Model = []; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {ClassificationSVM} {property} ScoreTransform ## ## Transformation function for classification scores ## ## Specified as a function handle for transforming the classification ## scores. Add or change the @qcode{ScoreTransform} property using dot ## notation as in: ## ## @itemize ## @item @qcode{@var{obj}.ScoreTransform = 'function_name'} ## @item @qcode{@var{obj}.ScoreTransform = @@function_handle} ## @end itemize ## ## When specified as a character vector, it can be any of the following ## built-in functions. Nevertheless, the @qcode{ScoreTransform} property ## always stores their function handle equivalent. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'doublelogit'} @tab @math{1 ./ (1 + exp (-2 * x))} ## @item @qcode{'invlogit'} @tab @math{log (x ./ (1 - x))} ## @item @qcode{'ismax'} @tab Sets the score for the class with the ## largest score to 1, and for all other classes to 0 ## @item @qcode{'logit'} @tab @math{1 ./ (1 + exp (-x))} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'sign'} @tab ## @math{-1 for x < 0, 0 for x = 0, 1 for x > ## 0} ## @item @qcode{'symmetric'} @tab @math{2 * x - 1} ## @item @qcode{'symmetricismax'} @tab Sets the score for the class ## with the largest score to 1, and for all other classes to -1 ## @item @qcode{'symmetriclogit'} @tab @math{2 ./ (1 + exp (-x)) - 1} ## @end multitable ## ## @end deftp ScoreTransform = 'none'; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) STfun = @(x) x; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.ScoreTransform (this, val) name = 'ClassificationSVM'; try [this.STfun, this.ScoreTransform] = parseScoreTransform ... (val, name); catch error (strcat ("ClassificationSVM.subsasgn: 'ScoreTransform'", ... " must be a 'function_handle' object.")); end_try_catch endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n ClassificationSVM\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); if (iscellstr (this.ClassNames)) str = repmat ({'''%s'''}, 1, numel (this.ClassNames)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.ClassNames{:}); elseif (ischar (this.ClassNames)) str = repmat ({'''%s'''}, 1, rows (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, cellstr (this.ClassNames){:}); else # single, double, logical str = repmat ({'%d'}, 1, numel (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.ClassNames); endif fprintf ("%+25s: %s\n", 'ClassNames', str); fprintf ("%+25s: '%s'\n", 'ScoreTransform', this.ScoreTransform); fprintf ("%+25s: %d\n", 'NumObservations', this.NumObservations); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); fprintf ("%+25s: [%dx1 double]\n", 'Alpha', numel (this.Alpha)); if (! isempty (this.Beta)) fprintf ("%+25s: [%dx1 double]\n", 'Beta', numel (this.Beta)); endif fprintf ("%+25s: %f\n", 'Bias', this.Bias); fprintf ("%+25s: [1x1 struct]\n", 'KernelParameters'); if (! isempty (this.Mu)) if (numel (this.Mu) < 6) str = repmat ({'''%0.4f'''}, 1, numel (this.Mu)); str = strcat ('[', strjoin (str, ' '), ']'); out = sprintf (str, this.Mu); fprintf ("%+25s: %s\n", 'Mu', out); out = sprintf (str, this.Sigma); fprintf ("%+25s: %s\n", 'Sigma', out); else fprintf ("%+25s: [1x%d double]\n", 'Mu', numel (this.Mu)); fprintf ("%+25s: [1x%d double]\n", 'Sigma', numel (this.Sigma)); endif endif endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} ClassificationSVM (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{obj} =} ClassificationSVM (@dots{}, @var{name}, @var{value}) ## ## Create a @qcode{ClassificationSVM} class object containing a Support ## Vector Machine classification model for one-class or two-class problems. ## ## @code{@var{obj} = ClassificationSVM (@var{X}, @var{Y})} returns a ## ClassificationSVM object, with @var{X} as the predictor data and @var{Y} ## containing the class labels of observations in @var{X}. ## ## @itemize ## @item ## @code{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. @var{X} will be used to train the SVM model. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} can be either ## numeric, logical, or cell array of character vectors. It must have same ## numbers of rows as @var{X}. ## @end itemize ## ## @code{@var{obj} = ClassificationSVM (@dots{}, @var{name}, @var{value})} ## returns a ClassificationSVM object with parameters specified by the ## following @qcode{@var{name}, @var{value}} paired input arguments: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PredictorNames'} @tab A cell array of character ## vectors specifying the names of the predictors. The length of this array ## must match the number of columns in @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector specifying the ## name of the response variable. ## ## @item @qcode{'ClassNames'} @tab Names of the classes in the class ## labels, @var{Y}, used for fitting the SVM model. @qcode{ClassNames} are ## of the same type as the class labels in @var{Y}. ## ## @item @qcode{'ScoreTransform'} @tab A user-defined function handle ## or a character vector specifying one of the following builtin functions ## specifying the transformation applied to predicted classification scores. ## Supported values include @qcode{'doublelogit'}, @qcode{'invlogit'}, ## @qcode{'ismax'}, @qcode{'logit'}, @qcode{'none'}, @qcode{'identity'}, ## @qcode{'sign'}, @qcode{'symmetric'}, @qcode{'symmetricismax'}, and ## @qcode{'symmetriclogit'}. ## ## @item @qcode{'Standardize'} @tab A logical scalar specifying whether ## to standardize the predictor variables. Default is @qcode{false}. ## ## @item @qcode{'SVMtype'} @tab A character vector specifying the type ## of SVM to use. Supported values are @qcode{'c_svc'} (C-support vector ## classification), @qcode{'nu_svc'} (nu-support vector classification), and ## @qcode{'one_class_svm'} (one-class SVM). ## ## @item @qcode{'KernelFunction'} @tab A character vector specifying ## the kernel function to use. Supported values are @qcode{'linear'}, ## @qcode{'rbf'} or @qcode{'gaussian'}, @qcode{'polynomial'}, and ## @qcode{'sigmoid'}. ## ## @item @qcode{'PolynomialOrder'} @tab A positive integer specifying ## the order of the polynomial kernel function. Default is 3. ## ## @item @qcode{'KernelScale'} @tab A positive scalar specifying the ## kernel scale parameter. Default is 1. ## ## @item @qcode{'KernelOffset'} @tab A non-negative scalar specifying ## the kernel offset parameter. Default is 0. ## ## @item @qcode{'BoxConstraint'} @tab A positive scalar specifying the ## box constraint parameter. Default is 1. ## ## @item @qcode{'Nu'} @tab A positive scalar in the range (0,1] ## specifying the nu parameter for nu-SVM and one-class SVM. Default is 0.5. ## ## @item @qcode{'CacheSize'} @tab A positive scalar specifying the ## cache size in MB. Default is 1000. ## ## @item @qcode{'Tolerance'} @tab A positive scalar specifying the ## tolerance of termination criterion. Default is 1e-6. ## ## @item @qcode{'Shrinking'} @tab Either 0 or 1 specifying whether to ## use the shrinking heuristics. Default is 1. ## ## @item @qcode{'OutlierFraction'} @tab A positive scalar in the range ## [0,1) specifying the fraction of outliers for one-class SVM. ## @end multitable ## ## @seealso{fitcsvm} ## @end deftypefn function this = ClassificationSVM (X, Y, varargin) ## Check for sufficient number of input arguments if (nargin < 2) error ("ClassificationSVM: too few input arguments."); endif ## Check X and Y have the same number of observations if (rows (X) != rows (Y)) error ("ClassificationSVM: number of rows in X and Y must be equal."); endif ## Assign original X and Y data to the ClassificationSVM object this.X = X; this.Y = Y; ## Get groups in Y [gY, gnY, glY] = grp2idx (Y); ## Set default values before parsing optional parameters SVMtype = 'c_svc'; KernelFunction = []; KernelScale = 1; KernelOffset = 0; PolynomialOrder = 3; BoxConstraint = 1; Nu = 0.5; OutlierFraction = 0; CacheSize = 1000; Tolerance = 1e-6; Shrinking = 1; Standardize = false; ResponseName = []; PredictorNames = []; ClassNames = []; Prior = []; Cost = []; ## Parse extra parameters SVMtype_override = true; while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'standardize' Standardize = varargin{2}; if (! (Standardize == true || Standardize == false)) error (strcat ("ClassificationSVM: 'Standardize' must", ... " be either true or false.")); endif case 'predictornames' PredictorNames = varargin{2}; if (! iscellstr (PredictorNames)) error (strcat ("ClassificationSVM: 'PredictorNames' must", ... " be supplied as a cellstring array.")); elseif (columns (PredictorNames) != columns (X)) error (strcat ("ClassificationSVM: 'PredictorNames' must", ... " have the same number of columns as X.")); endif case 'responsename' ResponseName = varargin{2}; if (! ischar (ResponseName)) error (strcat ("ClassificationSVM: 'ResponseName' must", ... " be a character vector.")); endif case 'classnames' ClassNames = varargin{2}; if (! (iscellstr (ClassNames) || isnumeric (ClassNames) || islogical (ClassNames) || ischar (ClassNames))) error (strcat ("ClassificationSVM: 'ClassNames' must be a", ... " cell array of character vectors, a logical", ... " vector, a numeric vector, or a character array.")); endif ## Check that all class names are available in gnY if (iscellstr (ClassNames) || ischar (ClassNames)) ClassNames = cellstr (ClassNames); if (! all (cell2mat (cellfun (@(x) any (strcmp (x, gnY)), ClassNames, 'UniformOutput', false)))) error (strcat ("ClassificationSVM: not all 'ClassNames'", ... " are present in Y.")); endif else if (! all (cell2mat (arrayfun (@(x) any (x == glY), ClassNames, 'UniformOutput', false)))) error (strcat ("ClassificationSVM: not all 'ClassNames'", ... " are present in Y.")); endif endif case 'prior' Prior = varargin{2}; if (! (isstruct (Prior) || (isnumeric (Prior) && isvector (Prior) && all (Prior >= 0) && any (Prior > 0)) || (ischar (Prior) && any (strcmpi (Prior, {'empirical', 'uniform'}))))) error (strcat ("ClassificationSVM: 'Prior' must be a", ... " non-negative numeric vector, 'empirical'", ... " or 'uniform'.")); endif case 'cost' Cost = varargin{2}; if (! (isnumeric (Cost) && issquare (Cost) && all (Cost(:) >= 0))) error (strcat ("ClassificationSVM: 'Cost' must be a", ... " non-negative square matrix.")); endif case 'scoretransform' name = 'ClassificationSVM'; [this.STfun, this.ScoreTransform] = parseScoreTransform ... (varargin{2}, name); case 'svmtype' SVMtype = varargin{2}; SVMtype_override = false; if (! any (strcmp (SVMtype, {'c_svc', 'nu_svc', 'one_class_svm'}))) error (strcat ("ClassificationSVM: 'SVMtype' must be", ... " 'c_svc', 'nu_svc', or 'one_class_svm'.")); endif case 'outlierfraction' Nu = varargin{2}; OutlierFraction = Nu; if (! (isscalar (Nu) && Nu >= 0 && Nu < 1)) error (strcat ("ClassificationSVM: 'OutlierFraction' must", ... " be a positive scalar in the range 0 =<", ... " OutlierFraction < 1.")); endif if (Nu > 0) SVMtype = 'nu_svc'; endif case 'kernelfunction' KernelFunction = varargin{2}; if (! ischar (KernelFunction)) error (strcat ("ClassificationSVM: 'KernelFunction' must", ... " be a character vector.")); endif KernelFunction = tolower (KernelFunction); if (! any (strcmpi (KernelFunction, ... {'linear', 'rbf', 'gaussian', 'polynomial', 'sigmoid'}))) error ("ClassificationSVM: unsupported Kernel function."); endif case 'polynomialorder' PolynomialOrder = varargin{2}; if (! (isnumeric (PolynomialOrder) && isscalar (PolynomialOrder) && PolynomialOrder > 0 && mod (PolynomialOrder, 1) == 0)) error (strcat ("ClassificationSVM: 'PolynomialOrder' must", ... " be a positive integer.")); endif case 'kernelscale' KernelScale = varargin{2}; if (! (isscalar (KernelScale) && KernelScale > 0)) error (strcat ("ClassificationSVM: 'KernelScale'", ... " must be a positive scalar.")); endif case 'kerneloffset' KernelOffset = varargin{2}; if (! (isnumeric (KernelOffset) && isscalar (KernelOffset) && KernelOffset >= 0)) error (strcat ("ClassificationSVM: 'KernelOffset' must", ... " be a non-negative scalar.")); endif case 'boxconstraint' BoxConstraint = varargin{2}; if (! (isscalar (BoxConstraint) && BoxConstraint > 0)) error (strcat ("ClassificationSVM: 'BoxConstraint' must", ... " be a positive scalar.")); endif case 'nu' Nu = varargin{2}; if (SVMtype_override) SVMtype = 'one_class_svm'; endif if (! (isscalar (Nu) && Nu > 0 && Nu <= 1)) error (strcat ("ClassificationSVM: 'Nu' must be a positive", ... " scalar in the range 0 < Nu <= 1.")); endif case 'cachesize' CacheSize = varargin{2}; if (! (isscalar (CacheSize) && CacheSize > 0)) error (strcat ("ClassificationSVM: 'CacheSize' must", ... " be a positive scalar.")); endif case 'tolerance' Tolerance = varargin{2}; if (! (isscalar (Tolerance) && Tolerance >= 0)) error (strcat ("ClassificationSVM: 'Tolerance' must", ... " be a positive scalar.")); endif case 'shrinking' Shrinking = varargin{2}; if (! (ismember (Shrinking, [0, 1]) && isscalar (Shrinking))) error ("ClassificationSVM: 'Shrinking' must be either 0 or 1."); endif otherwise error (strcat ("ClassificationSVM: invalid parameter name", ... " in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## Get number of variables in training data ndims_X = columns (X); ## Assign the number of predictors to the ClassificationSVM object this.NumPredictors = ndims_X; ## Handle class names if (! isempty (ClassNames)) ## Anything textual is matched as whole names, gnY being grp2idx's ## own cellstr of them. A character matrix is not a cellstr, and ## ismember between two of them compares character by character, so ## it would answer a question nobody asked. if (iscellstr (ClassNames) || ischar (ClassNames)) ru = find (! ismember (gnY, cellstr (ClassNames))); else ru = find (! ismember (glY, ClassNames)); endif for i = 1:numel (ru) gY(gY == ru(i)) = NaN; endfor endif ## An observation is dropped only when its response is missing. A row ## whose predictors hold missing values is kept and reported as used, ## while the fit below draws on the complete observations alone. RowsUsed = ! isnan (gY); ## Index the rows and not the elements: a response naming its ## classes in the rows of a character matrix has one column per ## character, and a linear index flattens the names into single ## letters. Every other accepted type is a column, for which ## the two forms agree. Yret = Y(RowsUsed, :); Xret = X(RowsUsed, :); this.X = Xret; this.Y = Yret; cobs = ! any (isnan (Xret), 2); Y = Yret(cobs, :); X = Xret(cobs, :); ## Renew groups in Y over the retained observations, so a class held ## only by a row with missing predictors is still a class of the model [gret, gnY, glY] = grp2idx (Yret); gY = gret(cobs); nclasses = numel (gnY); this.ClassNames = glY; # Keep the same type as Y ## Resolve Prior and Cost against the classes that survived. Prior ## defaults to the frequencies of the training data and Cost to zero on ## the diagonal and one elsewhere, which is what MATLAB reports. if (isstruct (Prior)) Prior = priorFromStruct (Prior, this.ClassNames, ... 'ClassificationSVM'); endif freq = accumarray (gY(:), 1, [nclasses, 1])' / numel (gY); if (isempty (Prior) || (ischar (Prior) && strcmpi (Prior, 'empirical'))) Prior = freq; elseif (ischar (Prior) && strcmpi (Prior, 'uniform')) Prior = ones (1, nclasses) / nclasses; else if (numel (Prior) != nclasses) error (strcat ("ClassificationSVM: 'Prior' must have one entry", ... " per class.")); endif Prior = Prior(:)' / sum (Prior); endif if (isempty (Cost)) Cost = ones (nclasses) - eye (nclasses); elseif (rows (Cost) != nclasses) error (strcat ("ClassificationSVM: the number of rows and columns", ... " in 'Cost' must correspond to the classes in Y.")); endif this.Prior = Prior; this.Cost = Cost; ## If only one class available, force 'SVMtype' to 'one_class_svm' if (nclasses == 1) if (! SVMtype_override && ! strcmp (SVMtype, 'one_class_svm')) error (strcat ("ClassificationSVM: cannot train a binary", ... " problem with only one class available.")); endif SVMtype = 'one_class_svm'; if (isempty (KernelFunction)) KernelFunction = 'rbf'; endif else if (isempty (KernelFunction)) KernelFunction = 'linear'; endif endif ## Check that we are dealing only with one-class or binary classification if (nclasses > 2) error (strcat ("ClassificationSVM: can only be used for", ... " one-class or two-class learning.")); endif ## Force Y into numeric if (! isnumeric (Y)) Y = gY; endif ## Force Y labels to -1 and +1 to avoid numeric issues with different ## compiling options; see https://github.com/cjlin1/libsvm/issues/220 if (nclasses == 2) Y(Y == 2) = -1; endif ## Check X contains valid data if (! (isnumeric (X) && isfinite (X))) error ("ClassificationSVM: invalid values in X."); endif ## Assign the number of observations and their corresponding indices ## on the original data, which will be used for training the model, ## to the ClassificationSVM object this.NumObservations = rows (this.X); ## RowsUsed is left empty when every observation was used, as in MATLAB if (all (RowsUsed)) this.RowsUsed = []; else this.RowsUsed = RowsUsed; endif ## Handle the Standardize option. The model must be fitted on the ## scale it predicts on: predict and resubPredict standardize their ## input from Mu and Sigma, so the training data is standardized here ## as well. ## The support vectors are therefore stored standardized too, which is ## the scale svmpredict receives. if (Standardize) ## Mu and Sigma weight the complete observations so that each class ## keeps the share of the observation weight it carried before any row ## was set aside, which is what MATLAB reports. sw = zeros (rows (X), 1); for k = 1:numel (gnY) ck = (gY == k); if (any (ck)) sw(ck) = (sum (gret == k) / numel (gret)) / sum (ck); endif endfor sw = sw / sum (sw); this.Mu = sum (sw .* X, 1); Zs = X - this.Mu; this.Sigma = sqrt (sum (sw .* Zs .^ 2, 1) / (1 - sum (sw .^ 2))); this.Sigma(this.Sigma == 0) = 1; # predictor is constant X = (X - this.Mu) ./ this.Sigma; else this.Sigma = []; this.Mu = []; endif ## Generate default predictors and response variable names (if necessary) if (isempty (PredictorNames)) for i = 1:ndims_X PredictorNames {i} = strcat ("x", num2str (i)); endfor endif if (isempty (ResponseName)) ResponseName = 'Y'; endif ## Assign predictors and response variable names this.PredictorNames = PredictorNames; this.ResponseName = ResponseName; ## No predictor is treated as categorical, so the expanded names are the ## predictor names themselves, and every observation carries the same ## weight, normalized to sum to one as MATLAB reports it. this.CategoricalPredictors = []; this.ExpandedPredictorNames = PredictorNames; this.W = priorWeights (this.Prior, gY, this.NumObservations); ## Set svmtrain parameters for SVMtype and KernelFunction switch (SVMtype) case 'c_svc' s = 0; case 'nu_svc' s = 1; case 'one_class_svm' s = 2; endswitch switch (KernelFunction) case 'linear' t = 0; case 'polynomial' t = 1; case {'rbf', 'gaussian'} t = 2; case 'sigmoid' t = 3; endswitch ## Set svmtrain parameters for gamma g = KernelScale / ndims_X; ## svmpredict: ## '-s': SVMtype ## '-t': KernelFunction ## '-g': Gamma ## '-d': PolynomialOrder ## '-r': KernelOffset ## '-c': BoxConstraint ## '-n': Nu ## '-m': CacheSize ## '-e': Tolerance ## '-h': Shrinking ## Build options string for svmtrain function str_options = strcat ("-s %d -t %d -g %f -d %d -r %f", ... " -c %f -n %f -m %f -e %e -h %d -q"); svm_options = sprintf (str_options, s, t, g, PolynomialOrder, ... KernelOffset, BoxConstraint, Nu, ... CacheSize, Tolerance, Shrinking); ## Prior and Cost enter the fit through LIBSVM's per-class weights, ## which scale the box constraint of each class: the weight of class i ## is the cost it carries, Prior(i) times the cost of getting it wrong, ## divided by how often it actually occurs. The default prior is the ## observed frequency and the default cost is one, so the default weight ## is one for every class and the fit is the same as with no weights. cw = ones (1, nclasses); if (nclasses == 2 && ! strcmp (SVMtype, 'one_class_svm')) cw = (Prior .* sum (Cost, 2)') ./ max (freq, eps); cw = cw / mean (cw); if (any (abs (cw - 1) > 1e-12)) ## LIBSVM names the classes by the labels it was given, which are ## +1 and -1 here, in the order of ClassNames. svm_options = sprintf ("%s -w1 %.16g -w-1 %.16g", svm_options, ... cw(1), cw(2)); endif endif ## Train the SVM model using svmtrain from libsvm Model = svmtrain (Y, X, svm_options); this.Model = Model; ## Populate ClassificationSVM object properties. LIBSVM returns the ## dual coefficients already multiplied by the class sign, whereas ## MATLAB keeps their magnitudes in ALPHA and the sign in ## SUPPORTVECTORLABELS, so the two are separated here. this.Alpha = abs (Model.sv_coef); this.Bias = Model.rho; ## One label per support vector, in the order of SupportVectors, taking ## the sign from the coefficients themselves. LIBSVM's sign is opposite ## to the labelling MATLAB reports. this.SupportVectorLabels = -sign (Model.sv_coef); ## BETA holds the primal coefficients, one per predictor, and exists ## only for a linear kernel; for any other kernel there is no primal ## representation and MATLAB leaves it empty. if (t == 0) this.Beta = Model.SVs' * (this.Alpha .* this.SupportVectorLabels); else this.Beta = []; endif this.IsSupportVector = false (this.NumObservations, 1); this.IsSupportVector(Model.sv_indices) = true; this.SupportVectors = Model.SVs; ## The kernel, the per-observation box constraints and the two ## one-class parameters, in the shapes MATLAB reports them. The box ## constraints are the scalar scaled by the very weights the fit was ## given above, so they describe what was solved rather than what was ## asked for. this.KernelParameters = svmKernelParams (KernelFunction, KernelScale, ... PolynomialOrder); this.BoxConstraints = BoxConstraint * cw(gY)(:); this.OutlierFraction = OutlierFraction; if (strcmp (SVMtype, 'one_class_svm')) this.Nu = Nu; endif ## Populate ModelParameters structure. The polynomial order belongs to ## the polynomial kernel alone and is reported under no other, as ## MATLAB reports it. if (strcmpi (KernelFunction, 'polynomial')) KPOrder = PolynomialOrder; else KPOrder = []; endif params = struct ('SVMtype', SVMtype, 'BoxConstraint', BoxConstraint, ... 'CacheSize', CacheSize, 'KernelScale', KernelScale, ... 'KernelOffset', KernelOffset, 'KernelFunction', ... KernelFunction, 'KernelPolynomialOrder', KPOrder, ... 'Nu', Nu, 'Tolerance', Tolerance, ... 'Shrinking', Shrinking, ... 'OutlierFraction', OutlierFraction, ... 'StandardizeData', logical (Standardize), ... 'Version', 1, 'Method', 'SVM', ... 'Type', 'classification'); this.ModelParameters = params; endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationSVM} {@var{obj} =} discardSupportVectors (@var{obj}) ## ## Discard the support vectors of a linear SVM model. ## ## @code{@var{obj} = discardSupportVectors (@var{obj})} empties ## @code{Alpha}, @code{SupportVectors} and ## @code{SupportVectorLabels}, leaving @code{Beta} and @code{Bias} to ## decide every prediction. A linear kernel needs nothing else, so the ## returned model predicts what it predicted before while carrying one ## vector in place of many. ## ## The kernel must be linear. Under any other the support vectors are ## part of the decision function and cannot be dropped. Discarding twice ## is not an error and changes nothing. ## ## @seealso{fitcsvm, ClassificationSVM, CompactClassificationSVM} ## @end deftypefn function this = discardSupportVectors (this) if (nargin != 1) print_usage (); endif if (! strcmpi (this.ModelParameters.KernelFunction, 'linear')) error (strcat ("ClassificationSVM.discardSupportVectors: you", ... " cannot discard support vectors for a non-linear", ... " kernel.")); endif ## The engine keeps its own copy of the support vectors, so emptying ## the properties alone would free nothing. Collapsing the model onto ## the single vector that decides it leaves every scoring path as it ## was, svmpredict going on being the engine over one vector. this.Model = discardSVs (this.Model); this.Alpha = []; this.SupportVectors = []; this.SupportVectorLabels = []; endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationSVM} {@var{label} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {ClassificationSVM} {[@var{label}, @var{score}] =} predict (@var{obj}, @var{XC}) ## @deftypefnx {ClassificationSVM} {[@var{label}, @var{score}, @var{cost}] =} predict (@var{obj}, @var{XC}) ## ## Classify new data points into categories using the Support Vector Machine ## classification model from a ClassificationSVM object. ## ## @code{@var{label} = predict (@var{obj}, @var{XC})} returns the vector of ## labels predicted for the corresponding instances in @var{XC}, using the ## predictor data in @code{obj.X} and corresponding labels, @code{obj.Y}, ## stored in the ClassificationSVM model, @var{obj}. For one-class SVM ## model, +1 or -1 is returned. ## ## @itemize ## @item ## @var{obj} must be a @qcode{ClassificationSVM} class object. ## @item ## @var{XC} must be an @math{M*P} numeric matrix with the same number of ## features @math{P} as the corresponding predictors of the SVM model in ## @var{obj}. ## @end itemize ## ## @code{[@var{label}, @var{score}] = predict (@var{obj}, @var{XC})} also ## returns @var{score}, which contains the decision values for each ## prediction. A @qcode{ScoreTransform} assigned to @var{obj} is applied ## to them, so @var{score} holds whatever that transform returns. Posterior ## probabilities need a transform fitted to the model, which this package ## does not compute yet. ## ## @seealso{ClassificationSVM, fitcsvm} ## ## @strong{Deviation from MATLAB.} @var{cost} is the expected cost of ## each assignment, @math{sum_j P(j) Cost(j,k)}. An SVM score is a ## signed distance to the boundary and not a posterior, so the only ## distribution available is the one concentrated on the predicted class ## and @var{cost} is the row of @qcode{Cost} belonging to it. MATLAB ## returns the @strong{column} instead, which is the same matrix read the ## wrong way and contradicts its own @code{ClassificationKNN}, ## @code{ClassificationDiscriminant} and @code{ClassificationNaiveBayes} ## on any asymmetric cost matrix; the two agree wherever @qcode{Cost} is ## symmetric, the default included. Measured on R2024a. ## ## @end deftypefn function [labels, scores, cost] = predict (this, XC) ## Check for sufficient input arguments if (nargin < 2) error ("ClassificationSVM.predict: too few input arguments."); endif ## Check for valid XC if (isempty (XC)) error ("ClassificationSVM.predict: XC is empty."); elseif (this.NumPredictors != columns (XC)) error (strcat ("ClassificationSVM.predict:", ... " XC must have the same number of", ... " predictors as the trained model.")); endif ## Standardize (if necessary) if (! isempty (this.Mu)) XC = (XC - this.Mu) ./ this.Sigma; endif ## Predict labels and scores from new data [out, ~, scores] = svmpredict (ones (rows (XC), 1), XC, this.Model, '-q'); ## Expand scores for two classes if (classCount (this.ClassNames) == 2) scores = [scores, -scores]; endif ## Translate labels to classnames. Indexing the class names by a ## per-observation class number keeps every response type on one path: ## assigning into a preallocated result instead has to know that a ## character matrix holds a name per row and not per element. idx = 2 - (out == 1); labels = labelsFromIndex (this.ClassNames, idx); ## The expected cost of each assignment, sum_j P(j) * Cost(j,k). An ## SVM score is a signed distance and not a posterior, so the only ## honest distribution is the one concentrated on the predicted class, ## which leaves the row of Cost belonging to it. ## ## MATLAB returns the *column* here, Cost(:,k)', which is the same ## matrix read the wrong way and disagrees with its own KNN, ## discriminant and naive Bayes on any asymmetric cost matrix. Both ## agree when Cost is symmetric, the default included. cost = this.Cost(idx, :); ## Apply ScoreTransform scores = this.STfun (scores); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationSVM} {@var{label} =} resubPredict (@var{obj}) ## @deftypefnx {ClassificationSVM} {[@var{label}, @var{score}] =} resubPredict (@var{obj}) ## @deftypefnx {ClassificationSVM} {[@var{label}, @var{score}, @var{cost}] =} resubPredict (@var{obj}) ## ## Classify the training data using the trained Support Vector Machine ## classification object. ## ## @code{@var{label} = resubPredict (@var{obj})} returns the vector of ## labels predicted for the corresponding instances in the training data, ## using the predictor data in @code{obj.X} and corresponding labels, ## @code{obj.Y}, stored in the Support Vector Machine classification model, ## @var{obj}. For one-class model, +1 or -1 is returned. ## ## @itemize ## @item ## @var{obj} must be a @qcode{ClassificationSVM} class object. ## @end itemize ## ## @code{[@var{label}, @var{scores}] = resubPredict (@var{obj}} also ## returns @var{scores}, which contains the decision values for each ## prediction. A @qcode{ScoreTransform} assigned to @var{obj} is applied ## to them, so @var{scores} holds whatever that transform returns. Posterior ## probabilities need a transform fitted to the model, which this package ## does not compute yet. ## ## @seealso{fitcsvm} ## ## @strong{Deviation from MATLAB.} @var{cost} is the expected cost of ## each assignment, @math{sum_j P(j) Cost(j,k)}. An SVM score is a ## signed distance to the boundary and not a posterior, so the only ## distribution available is the one concentrated on the predicted class ## and @var{cost} is the row of @qcode{Cost} belonging to it. MATLAB ## returns the @strong{column} instead, which is the same matrix read the ## wrong way and contradicts its own @code{ClassificationKNN}, ## @code{ClassificationDiscriminant} and @code{ClassificationNaiveBayes} ## on any asymmetric cost matrix; the two agree wherever @qcode{Cost} is ## symmetric, the default included. Measured on R2024a. ## ## @end deftypefn function [labels, scores, cost] = resubPredict (this) ## X and Y hold exactly the observations the model retained X = this.X; Y = this.Y; ## Standardize (if necessary) if (! isempty (this.Mu)) X = (X - this.Mu) ./ this.Sigma; endif ## Predict labels and scores from new data [out, ~, scores] = svmpredict (ones (rows (X), 1), X, this.Model, '-q'); ## Expand scores for two classes if (classCount (this.ClassNames) == 2) scores = [scores, -scores]; endif ## Translate labels to classnames if (iscellstr (this.Y)) labels = cell (rows (X), 1); labels(out==1) = this.ClassNames{1}; labels(out!=1) = this.ClassNames{2}; elseif (islogical (this.Y)) labels = false (rows (X), 1); elseif (isnumeric (this.Y)) labels = zeros (rows (X), 1); elseif (ischar (this.Y)) labels = char (zeros (rows (X), size (this.Y, 2))); endif if (! iscellstr (this.Y)) labels(out==1) = this.ClassNames(1); labels(out!=1) = this.ClassNames(2); endif ## The expected cost of each assignment, as in predict above: an SVM ## score is a signed distance and not a posterior, so the row of Cost ## belonging to the predicted class is the whole of it. MATLAB returns ## the column instead, which its own KNN, discriminant and naive Bayes ## contradict on any asymmetric cost matrix. cost = this.Cost(2 - (out == 1), :); ## Apply ScoreTransform scores = this.STfun (scores); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationSVM} {@var{m} =} margin (@var{obj}, @var{X}, @var{Y}) ## ## Classification margins for Support Vector Machine classifier. ## ## @code{@var{m} = margin (@var{obj}, @var{X}, @var{Y})} returns ## the classification margins for @var{obj} with data @var{X} and ## classification @var{Y}. @var{m} is a numeric vector of length size (X,1). ## ## @itemize ## @item ## @var{obj} is a @var{ClassificationSVM} object trained on @code{X} ## and @code{Y}. ## @item ## @var{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. ## @item ## @var{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} must have same ## numbers of Rows as @var{X}. ## @end itemize ## ## The classification margin for each observation is the difference between ## the classification score for the true class and the maximal ## classification score for the false classes. ## ## @seealso{fitcsvm, ClassificationSVM} ## @end deftypefn function m = margin (this, X, Y) ## Check for sufficient input arguments if (nargin < 3) error ("ClassificationSVM.margin: too few input arguments."); endif ## Check for valid X if (isempty (X)) error ("ClassificationSVM.margin: X is empty."); elseif (columns (this.X) != columns (X)) error (strcat ("ClassificationSVM.margin: X must have the same", ... " number of predictors as the trained model.")); endif ## Check for valid Y if (isempty (Y)) error ("ClassificationSVM.margin: Y is empty."); elseif (rows (X) != rows (Y)) error (strcat ("ClassificationSVM.margin: Y must have", ... " the same number of rows as X.")); endif ## Y may be the class labels, which is what this method documents and ## what MATLAB accepts, or already the +1/-1 coding the solver works in. ## It used to be the latter only, so passing the labels the docstring ## promises reached LIBSVM as a cell array and raised its own message. Ypm = svmPlusMinus (Y, this.ClassNames); [~, ~, dec_values_L] = svmpredict (Ypm, X, this.Model, '-q'); m = 2 * Ypm .* dec_values_L; endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationSVM} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationSVM} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Compute loss for a trained ClassificationSVM object. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} computes the loss, ## @var{L}, using the default loss function @qcode{'classiferror'}. ## ## @itemize ## @item ## @code{obj} is a @var{ClassificationSVM} object trained on ## @code{X} and @code{Y}. ## @item ## @code{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} must have same ## numbers of Rows as @var{X}. ## @end itemize ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} allows ## additional options specified by @var{name}-@var{value} pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'LossFun'} @tab Specifies the loss function to use. ## Can be a function handle with four input arguments (C, S, W, Cost) ## which returns a scalar value or one of: ## 'binodeviance', 'classifcost', 'classiferror', 'exponential', ## 'hinge', 'logit','mincost', 'quadratic'. ## @itemize ## @item ## @code{C} is a logical matrix of size @math{N*K}, where @math{N} is the ## number of observations and @math{K} is the number of classes. ## The element @code{C(i,j)} is true if the class label of the i-th ## observation is equal to the j-th class. ## @item ## @code{S} is a numeric matrix of size @math{N*K}, where each element ## represents the classification score for the corresponding class. ## @item ## @code{W} is a numeric vector of length @math{N}, representing ## the observation weights. ## @item ## @code{Cost} is a @math{K*K} matrix representing the misclassification ## costs. ## @end itemize ## ## @item @qcode{'Weights'} @tab Specifies observation weights, must be ## a numeric vector of length equal to the number of rows in X. ## Default is @code{ones (size (X, 1))}. loss normalizes the weights so that ## observation weights in each class sum to the prior probability of that ## class. When you supply Weights, loss computes the weighted ## classification loss. ## ## @end multitable ## ## @seealso{ClassificationSVM} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("ClassificationSVM.loss: too few input arguments."); endif if (mod (nargin, 2) == 0) error ("ClassificationSVM.loss: Name-Value arguments must be in pairs."); endif ## Check for valid X if (isempty (X)) error ("ClassificationSVM.loss: X is empty."); elseif (columns (this.X) != columns (X)) error (strcat ("ClassificationSVM.loss: X must have the same", ... " number of predictors as the trained model.")); endif ## Check for valid Y if (isempty (Y)) error ("ClassificationSVM.loss: Y is empty."); elseif (rows (X)!= rows (Y)) error (strcat ("ClassificationSVM.loss: Y must have the same", ... " number of rows as X.")); endif ## Set default values before parsing optional parameters LossFun = 'classiferror'; Weights = ones (size (X, 1), 1); ## Parse extra parameters while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'lossfun' LossFun = varargin{2}; if (! (ischar (LossFun))) error (strcat ("ClassificationSVM.loss: 'LossFun'", ... " must be a character vector.")); endif LossFun = tolower (LossFun); if (! any (strcmpi (LossFun, {'binodeviance', 'classiferror', ... 'classifcost', 'exponential', ... 'hinge', 'logit', 'mincost', ... 'quadratic'}))) error ("ClassificationSVM.loss: unsupported Loss function."); endif case 'weights' Weights = varargin{2}; ## Validate if weights is a numeric vector if (! (isnumeric (Weights) && isvector (Weights))) error (strcat ("ClassificationSVM.loss: 'Weights'", ... " must be a numeric vector.")); endif ## Check if the size of weights matches the number of rows in X if (numel (Weights) != size (X, 1)) error (strcat ("ClassificationSVM.loss: size of 'Weights'", ... " must be equal to the number of rows in X.")); endif otherwise error (strcat ("ClassificationSVM.loss: invalid parameter", ... " name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## Y may be the class labels, which is what this method documents and ## what MATLAB accepts, or already the +1/-1 coding the solver works in. ## margin was given this treatment and loss was missed, so the labels ## the docstring promises reached LIBSVM unmapped. Ypm = svmPlusMinus (Y, this.ClassNames); ## Compute the classification score [~, ~, dec_values_L] = svmpredict (Ypm, X, this.Model, '-q'); ## Compute the margin margin = Ypm .* dec_values_L; ## Compute the loss based on the specified loss function switch (LossFun) case 'classiferror' L = mean ((margin <= 0) .* Weights); case 'hinge' L = mean (max (0, 1 - margin) .* Weights); case 'logit' L = mean (log (1 + exp (-margin)) .* Weights); case 'exponential' L = mean (exp (-margin) .* Weights); case 'quadratic' L = mean (((1 - margin) .^2) .* Weights); case 'binodeviance' L = mean (log (1 + exp (-2 * margin)) .* Weights); case 'mincost' ## Each observation is assigned to the class of least expected ## cost, and charged what that assignment actually costs given ## its true class. Y is the +1/-1 coding the margin above uses, ## in which +1 is the first of ClassNames and -1 the second. [~, scores] = predict (this, X); true_idx = ones (rows (X), 1); true_idx(Y == -1) = 2; L = 0; for i = 1:rows (X) [~, k] = min (scores(i,:) * this.Cost); L = L + Weights(i) * this.Cost(true_idx(i), k); endfor L = L / rows (X); case 'classifcost' ## What the model's own prediction costs, given the true class pred_idx = ones (rows (X), 1); pred_idx(dec_values_L <= 0) = 2; true_idx = ones (rows (X), 1); true_idx(Y == -1) = 2; L = 0; for i = 1:rows (X) L = L + Weights(i) * this.Cost(true_idx(i), pred_idx(i)); endfor L = L / rows (X); otherwise error ("ClassificationSVM.loss: unsupported Loss function."); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationSVM} {@var{L} =} resubLoss (@var{obj}) ## @deftypefnx {ClassificationSVM} {@var{L} =} resubLoss (@dots{}, @var{name}, @var{value}) ## ## Compute resubstitution loss for a trained ClassificationSVM object. ## ## @code{@var{L} = resubLoss (@var{obj})} computes the resubstitution loss, ## @var{L}, using the default loss function @qcode{'classiferror'}. ## ## @itemize ## @item ## @code{obj} is a @var{ClassificationSVM} object trained on ## @code{X} and @code{Y}. ## @end itemize ## ## @code{@var{L} = resubLoss (@dots{}, @var{name}, @var{value})} allows ## additional options specified by @var{name}-@var{value} pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'LossFun'} @tab Specifies the loss function to use. ## Can be a function handle with four input arguments (C, S, W, Cost) ## which returns a scalar value or one of: ## 'binodeviance', 'classifcost', 'classiferror', 'exponential', ## 'hinge', 'logit','mincost', 'quadratic'. ## @itemize ## @item ## @code{C} is a logical matrix of size @math{N*K}, where @math{N} is the ## number of observations and @math{K} is the number of classes. ## The element @code{C(i,j)} is true if the class label of the i-th ## observation is equal to the j-th class. ## @item ## @code{S} is a numeric matrix of size @math{N*K}, where each element ## represents the classification score for the corresponding class. ## @item ## @code{W} is a numeric vector of length @math{N}, representing ## the observation weights. ## @item ## @code{Cost} is a @math{K*K} matrix representing the misclassification ## costs. ## @end itemize ## ## @item @qcode{'Weights'} @tab Specifies observation weights, must be ## a numeric vector of length equal to the number of rows in X. ## Default is @code{ones (size (X, 1))}. loss normalizes the weights so that ## observation weights in each class sum to the prior probability of that ## class. When you supply Weights, loss computes the weighted ## classification loss. ## ## @end multitable ## ## @seealso{ClassificationSVM} ## @end deftypefn function L = resubLoss (this, varargin) if (mod (nargin, 2) != 1) error (strcat ("ClassificationSVM.resubLoss: Name-Value", ... " arguments must be in pairs.")); endif ## Set default values before parsing optional parameters LossFun = 'classiferror'; Weights = ones (size (this.X, 1), 1); ## Parse extra parameters while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'lossfun' LossFun = varargin{2}; if (! ischar (LossFun)) error (strcat ("ClassificationSVM.resubLoss: 'LossFun'", ... " must be a character vector.")); endif LossFun = tolower (LossFun); if (! any (strcmpi (LossFun, {'binodeviance', 'classiferror', ... 'classifcost', 'exponential', ... 'hinge', 'logit', 'mincost', ... 'quadratic'}))) error (strcat ("ClassificationSVM.resubLoss: unsupported", ... " Loss function.")); endif case 'weights' Weights = varargin{2}; ## Validate if weights is a numeric vector if (! (isnumeric (Weights) && isvector (Weights))) error (strcat ("ClassificationSVM.resubLoss: 'Weights'", ... " must be a numeric vector.")); endif ## Check if the size of weights matches the number of rows in X if (numel (Weights) != size (this.X, 1)) error (strcat ("ClassificationSVM.resubLoss: size", ... " of 'Weights' must be equal to the", ... " number of rows in X.")); endif otherwise error (strcat ("ClassificationSVM.resubLoss: invalid", ... " parameter name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## The loss of the model on its own training data. This used to ## recompute every loss here from this.Y, which holds the labels as ## they were given: the margin wants the +1/-1 coding, so a 1/2 coded ## response scored 0.49 where the answer was 0.01, and a logical one ## did not reach LIBSVM at all. The rows dropped for missing values ## were included as well. used = true (rows (this.X), 1); Xu = this.X(used, :); gY = grp2idx (this.Y(used, :)); Ypm = ones (rows (Xu), 1); Ypm(gY == 2) = -1; L = loss (this, Xu, Ypm, 'LossFun', LossFun, 'Weights', Weights); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationSVM} {@var{CVMdl} =} crossval (@var{obj}) ## @deftypefnx {ClassificationSVM} {@var{CVMdl} =} crossval (@dots{}, @var{name}, @var{value}) ## ## Cross Validate a Support Vector Machine classification object. ## ## @code{@var{CVMdl} = crossval (@var{obj})} returns a cross-validated model ## object, @var{CVMdl}, from a trained model, @var{obj}, using 10-fold ## cross-validation by default. ## ## @code{@var{CVMdl} = crossval (@var{obj}, @var{name}, @var{value})} ## specifies additional name-value pair arguments to customize the ## cross-validation process. ## ## @multitable @columnfractions 0.28 0.7 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'KFold'} @tab Specify the number of folds to use in ## k-fold cross-validation. @code{"KFold", @var{k}}, where @var{k} is an ## integer greater than 1. ## ## @item @qcode{'Holdout'} @tab Specify the fraction of the data to ## hold out for testing. @code{"Holdout", @var{p}}, where @var{p} is a ## scalar in the range @math{(0,1)}. ## ## @item @qcode{'Leaveout'} @tab Specify whether to perform ## leave-one-out cross-validation. @code{"Leaveout", @var{Value}}, where ## @var{Value} is 'on' or 'off'. ## ## @item @qcode{'CVPartition'} @tab Specify a @qcode{cvpartition} ## object used for cross-validation. @code{"CVPartition", @var{cv}}, where ## @code{isa (@var{cv}, "cvpartition")} = 1. ## ## @end multitable ## ## @seealso{fitcsvm, ClassificationSVM, cvpartition, ## ClassificationPartitionedModel} ## @end deftypefn function CVMdl = crossval (this, varargin) ## Check for sufficient input arguments if (nargin < 1) error ("ClassificationSVM.crossval: too few input arguments."); endif if (numel (varargin) == 1) error (strcat ("ClassificationSVM.crossval: Name-Value arguments", ... " must be in pairs.")); elseif (numel (varargin) > 2) error (strcat ("ClassificationSVM.crossval: specify only one of", ... " the optional Name-Value paired arguments.")); endif ## Add default values if (this.NumObservations < 10) numFolds = this.NumObservations; else numFolds = 10; endif Holdout = []; Leaveout = 'off'; CVPartition = []; ## Parse extra parameters while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'kfold' numFolds = varargin{2}; if (! (isnumeric (numFolds) && isscalar (numFolds) && (numFolds == fix (numFolds)) && numFolds > 1)) error (strcat ("ClassificationSVM.crossval: 'KFold' must", ... " be an integer value greater than 1.")); endif case 'holdout' Holdout = varargin{2}; if (! (isnumeric (Holdout) && isscalar (Holdout) && Holdout > 0 && Holdout < 1)) error (strcat ("ClassificationSVM.crossval: 'Holdout' must", ... " be a numeric value between 0 and 1.")); endif case 'leaveout' Leaveout = varargin{2}; if (! (ischar (Leaveout) && (strcmpi (Leaveout, 'on') || strcmpi (Leaveout, 'off')))) error (strcat ("ClassificationSVM.crossval: 'Leaveout'", ... " must be either 'on' or 'off'.")); endif case 'cvpartition' CVPartition = varargin{2}; if (! (isa (CVPartition, 'cvpartition'))) error (strcat ("ClassificationSVM.crossval: 'CVPartition'",... " must be a 'cvpartition' object.")); endif otherwise error (strcat ("ClassificationSVM.crossval: invalid",... " parameter name in optional paired arguments.")); endswitch varargin(1:2) = []; endwhile ## Determine the cross-validation method to use. The partition covers ## the observations actually trained on: a row dropped for a missing ## value is not one the folds can use, and including it would leave the ## partition, the stored data and NumObservations disagreeing. The ## response is passed rather than a count so the folds stay stratified. Yused = this.Y; if (! isempty (CVPartition)) partition = CVPartition; elseif (! isempty (Holdout)) partition = cvpartition (Yused, 'Holdout', Holdout); elseif (strcmpi (Leaveout, 'on')) partition = cvpartition (this.NumObservations, 'LeaveOut'); else partition = cvpartition (Yused, 'KFold', numFolds); endif ## Create a cross-validated model object CVMdl = ClassificationPartitionedModel (this, partition); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationSVM} {@var{CVMdl} =} compact (@var{obj}) ## ## Create a CompactClassificationSVM object. ## ## @code{@var{CVMdl} = compact (@var{obj})} creates a compact version of the ## ClassificationSVM object, @var{obj}. ## ## @seealso{fitcsvm, ClassificationSVM, CompactClassificationSVM} ## @end deftypefn function CVMdl = compact (this) ## Create a compact model CVMdl = CompactClassificationSVM (this); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationSVM} {@var{e} =} edge (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {ClassificationSVM} {@var{e} =} edge (@dots{}, @qcode{"Weights"}, @var{w}) ## ## Classification edge, the mean of the classification margins. ## ## @code{@var{e} = edge (@var{obj}, @var{X}, @var{Y})} reduces the vector ## that @code{margin} returns to a single number, the mean margin over the ## rows of @var{X}. It says how far the model puts the true class ahead of ## its nearest rival on average, so a larger edge is a better model, and ## unlike a loss it is not bounded above and rewards confidence rather than ## bare correctness. ## ## @code{@var{e} = edge (@dots{}, @qcode{"Weights"}, @var{w})} takes the ## weighted mean instead, with one weight per row of @var{X}. ## ## @end deftypefn function e = edge (this, X, Y, varargin) if (nargin < 3) error ("ClassificationSVM.edge: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("ClassificationSVM.edge: Name-Value", ... " arguments must be in pairs.")); endif ## The weights are parsed before anything is computed, so a bad ## Name-Value pair is reported as such rather than after a margin. W = edgeWeights (varargin, Y, this.ClassNames, this.Prior, ... "ClassificationSVM", "edge"); m = margin (this, X, Y); e = sum (W .* m(:)) / sum (W); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationSVM} {@var{m} =} resubMargin (@var{obj}) ## ## Classification margins of the model on its own training data. ## ## @code{@var{m} = resubMargin (@var{obj})} is @code{margin} applied to the ## observations the model was fitted on, one number per observation. Being ## a resubstitution quantity it is optimistic by construction. ## ## @end deftypefn function m = resubMargin (this) m = margin (this, this.X, this.Y); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationSVM} {@var{e} =} resubEdge (@var{obj}) ## ## Classification edge of the model on its own training data. ## ## @code{@var{e} = resubEdge (@var{obj})} is @code{edge} applied to the ## observations the model was fitted on, the mean of @code{resubMargin}. ## ## @end deftypefn function e = resubEdge (this) e = edge (this, this.X, this.Y); endfunction ## -*- texinfo -*- ## @deftypefn {ClassificationSVM} {} savemodel (@var{obj}, @var{filename}) ## ## Save a ClassificationSVM object. ## ## @code{savemodel (@var{obj}, @var{filename})} saves each property of a ## ClassificationSVM object into an Octave binary file, the name of which is ## specified in @var{filename}, along with an extra variable, which defines ## the type classification object these variables constitute. Use ## @code{loadmodel} in order to load a classification object into Octave's ## workspace. ## ## @seealso{loadmodel, fitcsvm, ClassificationSVM} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error ("ClassificationSVM.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error ("ClassificationSVM.savemodel: FNAME must be a character vector."); endif ## Generate variable for class name classdef_name = 'ClassificationSVM'; ## Create variables from model properties X = this.X; Y = this.Y; NumObservations = this.NumObservations; RowsUsed = this.RowsUsed; NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; ResponseName = this.ResponseName; ClassNames = this.ClassNames; ScoreTransform = this.ScoreTransform; Sigma = this.Sigma; BinEdges = this.BinEdges; Mu = this.Mu; ModelParameters = this.ModelParameters; KernelParameters = this.KernelParameters; BoxConstraints = this.BoxConstraints; OutlierFraction = this.OutlierFraction; Nu = this.Nu; Model = this.Model; Alpha = this.Alpha; Beta = this.Beta; Bias = this.Bias; IsSupportVector = this.IsSupportVector; SupportVectorLabels = this.SupportVectorLabels; SupportVectors = this.SupportVectors; W = this.W; Prior = this.Prior; Cost = this.Cost; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; ## Save classdef name and all model properties as individual variables STfun = this.STfun; HyperparameterOptimizationResults = this.HyperparameterOptimizationResults; save ('-binary', fname, 'classdef_name', 'X', 'Y', 'NumObservations', ... 'RowsUsed', 'NumPredictors', 'PredictorNames', 'BinEdges', ... 'ResponseName', ... 'ClassNames', 'ScoreTransform', 'Sigma', 'Mu', ... 'ModelParameters', 'Model', 'Alpha', 'Beta', 'Bias', ... 'IsSupportVector', 'SupportVectorLabels', 'SupportVectors', ... 'W', 'Prior', 'Cost', 'CategoricalPredictors', ... 'ExpandedPredictorNames', 'KernelParameters', ... 'BoxConstraints', 'OutlierFraction', 'Nu', 'STfun', ... 'HyperparameterOptimizationResults'); endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a ClassificationSVM object mdl = ClassificationSVM (1, 1); ## Copy the saved data into the object. Iterate over what was ## saved rather than over fieldnames (mdl): a private property such ## as STfun is written out by savemodel but is not reported by ## fieldnames, so comparing the two sets could never match and every ## load failed. Assignment is legal here because this is a method of ## the class itself. names = fieldnames (data); ## The set methods for these read other properties, and one of them ## rebuilds Coeffs, so they are assigned once everything else is in ## place rather than in the order the file happens to list them. late = ismember (names, {'Cost', 'Prior', 'ScoreTransform', ... 'ResponseTransform'}); names = [names(! late); names(late)]; for i = 1:numel (names) try mdl.(names{i}) = data.(names{i}); catch error ("ClassificationSVM.load_model: invalid model in '%s'.", filename) end_try_catch endfor ## A model written before RowsUsed became a mask stored it as a ## double, which is a valid subscript for nothing. An empty RowsUsed ## means every observation was used and stays an empty double. if (! isempty (mdl.RowsUsed)) mdl.RowsUsed = logical (mdl.RowsUsed); endif endfunction endmethods endclassdef %!demo %! ## Create a Support Vector Machine classifier and determine margin for test %! ## data. %! load fisheriris %! %! ## Select indices of the non-setosa species %! inds = ! strcmp (species, 'setosa'); %! %! ## Select features and labels for non-setosa species %! X = meas(inds, 3:4); %! Y = grp2idx (species(inds)); %! %! ## Convert labels to +1 and -1 %! unique_classes = unique (Y); %! Y(Y == unique_classes(1)) = -1; %! Y(Y == unique_classes(2)) = 1; %! %! ## Partition data for training and testing %! cv = cvpartition (Y, 'HoldOut', 0.15); %! X_train = X(training(cv), :); %! Y_train = Y(training(cv)); %! X_test = X(test (cv), :); %! Y_test = Y(test (cv)); %! %! ## Train the SVM model %! CVSVMModel = fitcsvm (X_train, Y_train); %! %! ## Calculate margins %! m = margin (CVSVMModel, X_test, Y_test); %! disp (m); %!demo %! ## Create a Support Vector Machine classifier and determine loss for test %! ## data. %! load fisheriris %! %! ## Select indices of the non-setosa species %! inds = ! strcmp (species, 'setosa'); %! %! ## Select features and labels for non-setosa species %! X = meas(inds, 3:4); %! Y = grp2idx (species(inds)); %! %! ## Convert labels to +1 and -1 %! unique_classes = unique (Y); %! Y(Y == unique_classes(1)) = -1; %! Y(Y == unique_classes(2)) = 1; %! %! ## Randomly partition the data into training and testing sets %! cv = cvpartition (Y, 'HoldOut', 0.3); # 30% data for testing, 60% for training %! %! X_train = X(training(cv), :); %! Y_train = Y(training(cv)); %! %! X_test = X(test (cv), :); %! Y_test = Y(test (cv)); %! %! ## Train the SVM model %! SVMModel = fitcsvm (X_train, Y_train); %! %! ## Calculate loss %! %! L = loss (SVMModel,X_test,Y_test,'LossFun','binodeviance') %! L = loss (SVMModel,X_test,Y_test,'LossFun','classiferror') %! L = loss (SVMModel,X_test,Y_test,'LossFun','exponential') %! L = loss (SVMModel,X_test,Y_test,'LossFun','hinge') %! L = loss (SVMModel,X_test,Y_test,'LossFun','logit') %! L = loss (SVMModel,X_test,Y_test,'LossFun','quadratic') ## Test output of constructor %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1; 4, 5, 6; 7, 8, 9; ... %! 3, 2, 1; 4, 5, 6; 7, 8, 9; 3, 2, 1; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = [1; 2; 3; 4; 2; 3; 4; 2; 3; 4; 2; 3; 4]; %! a = ClassificationSVM (x, y, 'ClassNames', [1, 2]); %! assert_equal (class (a), "ClassificationSVM"); %! m = logical ([1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0]'); %! assert_equal (a.RowsUsed, m); %! assert_equal ({a.X, a.Y}, {x(m,:), y(m)}) %! assert_equal (a.NumObservations, 5) %! assert_equal ({a.ResponseName, a.PredictorNames}, {'Y', {'x1', 'x2', 'x3'}}) %! assert_equal ({a.ClassNames, a.ModelParameters.SVMtype}, {[1; 2], 'c_svc'}) %!test %! x = [1, 2; 2, 3; 3, 4; 4, 5; 2, 3; 3, 4; 2, 3; 3, 4; 2, 3; 3, 4]; %! y = [1; 1; -1; -1; 1; -1; -1; -1; -1; -1]; %! a = ClassificationSVM (x, y); %! assert_equal (class (a), "ClassificationSVM"); %! assert_equal ({a.X, a.Y, a.ModelParameters.KernelFunction}, {x, y, 'linear'}) %! assert_equal (a.ModelParameters.BoxConstraint, 1) %! assert_equal (a.ClassNames, [-1; 1]) %! assert_equal (a.ModelParameters.KernelOffset, 0) %!test %! x = [1, 2; 2, 3; 3, 4; 4, 5; 2, 3; 3, 4; 2, 3; 3, 4; 2, 3; 3, 4]; %! y = [1; 1; -1; -1; 1; -1; -1; -1; -1; -1]; %! a = ClassificationSVM (x, y, 'KernelFunction', 'rbf', 'BoxConstraint', 2, ... %! 'KernelOffset', 2); %! assert_equal (class (a), "ClassificationSVM"); %! assert_equal ({a.X, a.Y, a.ModelParameters.KernelFunction}, {x, y, 'rbf'}) %! assert_equal (a.ModelParameters.BoxConstraint, 2) %! assert_equal (a.ModelParameters.KernelOffset, 2) %!test %! x = [1, 2; 2, 3; 3, 4; 4, 5; 2, 3; 3, 4; 2, 3; 3, 4; 2, 3; 3, 4]; %! y = [1; 1; -1; -1; 1; -1; -1; -1; -1; -1]; %! a = ClassificationSVM (x, y, 'KernelFunction', 'polynomial', ... %! 'PolynomialOrder', 3); %! assert_equal (class (a), "ClassificationSVM"); %! assert_equal ({a.X, a.Y, a.ModelParameters.KernelFunction}, {x, y, 'polynomial'}) %! assert_equal (a.ModelParameters.KernelPolynomialOrder, 3) ## RowsUsed is the logical mask its documentation describes, so the documented ## use of it works. It was a double, for which a 0 is not a subscript at all. %!test %! randn ('seed', 42); %! lab = double (randn (40, 1) > 0) + 1; %! X = [randn(40, 3); NaN, 1, 1]; %! Y = [lab; 1]; %! Mdl = fitcsvm (X, Y); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (rows (Mdl.X), Mdl.NumObservations); ## resubPredict answers about the rows it was trained on, all their columns. ## Selecting them with a bare mask took the first column only, and the model ## answered about data it was never given, without complaint. %!test %! randn ('seed', 42); %! lab = double (randn (40, 1) > 0) + 1; %! X = [randn(40, 3); NaN, 1, 1]; %! Y = [lab; 1]; %! Mdl = fitcsvm (X, Y); %! assert_equal (Mdl.NumObservations, 41); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (resubPredict (Mdl), predict (Mdl, Mdl.X)); ## A model saved before RowsUsed became a mask still reads back as one. %!test %! randn ('seed', 42); %! X = randn (31, 2); %! Y = [double(randn (30, 1) > 0) + 1; NaN]; %! Mdl = fitcsvm (X, Y); %! fname = tempname (); %! savemodel (Mdl, fname); %! d = load (fname); %! d.RowsUsed = double (d.RowsUsed); %! save ('-binary', fname, '-struct', 'd'); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2.RowsUsed), 'logical'); %! assert_equal (rows (M2.X(M2.RowsUsed, :)), M2.NumObservations); ## Prior defaults to the class frequencies and Cost to zero on the diagonal ## and one elsewhere, both measured against R2024a. %!test %! load fisheriris %! Yb = strcmp (species, 'setosa'); %! Mdl = fitcsvm (meas, Yb); %! assert_equal (Mdl.Prior, [2/3, 1/3], 1e-12); %! assert_equal (Mdl.Cost, [0, 1; 1, 0]); %! Yu = [repmat({'a'}, 120, 1); repmat({'b'}, 30, 1)]; %! M2 = fitcsvm (meas, Yu); %! assert_equal (M2.Prior, [0.8, 0.2], 1e-12); %! assert_equal (fitcsvm (meas, Yu, 'Prior', 'uniform').Prior, [0.5, 0.5]); %! assert_equal (fitcsvm (meas, Yu, 'Prior', [3, 1]).Prior, [0.75, 0.25]); ## Prior and Cost reach the fit, as they do in MATLAB, through the per-class ## box constraint. Asking for the defaults explicitly changes nothing. %!test %! load fisheriris %! Yu = [repmat({'a'}, 120, 1); repmat({'b'}, 30, 1)]; %! base = fitcsvm (meas, Yu); %! assert_equal (isequal (fitcsvm (meas, Yu, 'Prior', [0.5, 0.5]).Alpha, ... %! base.Alpha), false); %! assert_equal (isequal (fitcsvm (meas, Yu, 'Cost', [0, 5; 1, 0]).Alpha, ... %! base.Alpha), false); %! same = fitcsvm (meas, Yu, 'Prior', 'empirical', 'Cost', [0, 1; 1, 0]); %! assert_equal (same.Alpha, base.Alpha); %! assert_equal (same.Bias, base.Bias); ## The cost-aware losses agree with R2024a, which returns 0.01 for all three ## on this fixture and 0.04 once an error is charged four times over. %!test %! load fisheriris %! X = meas(51:150,:); %! Yb = strcmp (species(51:150), 'versicolor'); %! Ypm = ones (100, 1); %! Ypm(Yb) = -1; %! Mdl = fitcsvm (X, Yb, 'KernelFunction', 'linear'); %! assert_equal (loss (Mdl, X, Ypm, 'LossFun', 'classiferror'), 0.01, 1e-12); %! assert_equal (loss (Mdl, X, Ypm, 'LossFun', 'classifcost'), 0.01, 1e-12); %! assert_equal (loss (Mdl, X, Ypm, 'LossFun', 'mincost'), 0.01, 1e-12); %! Mc = fitcsvm (X, Yb, 'KernelFunction', 'linear', 'Cost', [0, 4; 1, 0]); %! assert_equal (loss (Mc, X, Ypm, 'LossFun', 'classifcost'), 0.04, 1e-12); %! assert_equal (loss (Mc, X, Ypm, 'LossFun', 'mincost'), 0.04, 1e-12); ## resubLoss is the loss of the model on its own training rows, whatever type ## the labels are. It formed the margin from the labels as given, so a 1/2 ## coded response scored 0.49 where the answer is 0.01, and a logical one did ## not reach LIBSVM at all. %!test %! load fisheriris %! X = meas(51:150,:); %! Yb = strcmp (species(51:150), 'versicolor'); %! assert_equal (resubLoss (fitcsvm (X, Yb, 'KernelFunction', 'linear')), ... %! 0.01, 1e-12); %! assert_equal (resubLoss (fitcsvm (X, double (Yb) + 1, ... %! 'KernelFunction', 'linear')), 0.01, 1e-12); %! assert_equal (resubLoss (fitcsvm (X, species(51:150), ... %! 'KernelFunction', 'linear')), 0.01, 1e-12); ## The compact model carries both, and its cost-aware losses agree with the ## model it was compacted from. %!test %! load fisheriris %! X = meas(51:150,:); %! Yb = strcmp (species(51:150), 'versicolor'); %! Ypm = ones (100, 1); %! Ypm(Yb) = -1; %! Mdl = fitcsvm (X, Yb, 'KernelFunction', 'linear', 'Cost', [0, 4; 1, 0]); %! CMdl = compact (Mdl); %! assert_equal (CMdl.Prior, Mdl.Prior); %! assert_equal (CMdl.Cost, Mdl.Cost); %! for f = {'classiferror', 'classifcost', 'mincost'} %! assert_equal (loss (CMdl, X, Ypm, 'LossFun', f{1}), ... %! loss (Mdl, X, Ypm, 'LossFun', f{1}), 1e-12); %! endfor ## discardSupportVectors empties what R2024a empties and keeps what it ## keeps: Alpha and the support vectors go, Beta, Bias and IsSupportVector ## stay, and the class is unchanged. %!test %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,:); y = species(keep); %! Mdl = fitcsvm (X, y, "KernelFunction", "linear"); %! D = discardSupportVectors (Mdl); %! assert_equal (class (D), "ClassificationSVM"); %! assert_equal (isempty (D.Alpha), true); %! assert_equal (isempty (D.SupportVectors), true); %! assert_equal (isempty (D.SupportVectorLabels), true); %! assert_equal (D.Beta, Mdl.Beta); %! assert_equal (D.Bias, Mdl.Bias); %! assert_equal (D.IsSupportVector, Mdl.IsSupportVector); ## A linear decision needs only Beta and Bias, so the model predicts what it ## predicted before. %!test %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,:); y = species(keep); %! Mdl = fitcsvm (X, y, "KernelFunction", "linear"); %! D = discardSupportVectors (Mdl); %! assert_equal (predict (D, X), predict (Mdl, X), 1e-10); ## The saving is real rather than cosmetic: the engine keeps its own copy of ## the support vectors, and it collapses to the one vector that decides a ## linear model. Emptying the properties alone would free nothing. %!test %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,:); y = species(keep); %! Mdl = fitcsvm (X, y, "KernelFunction", "linear"); %! D = discardSupportVectors (Mdl); %! assert_equal (rows (Mdl.Model.SVs) > 1, true); %! assert_equal (rows (D.Model.SVs), 1); %! assert_equal (predict (discardSupportVectors (D), X), predict (D, X)); ## A response naming its classes in the rows of a character matrix is one ## of the documented types and MATLAB accepts it on every classifier. The ## whole surface below was broken and untested, which is why it stayed so. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcsvm (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcsvm (Xch, Ycell); %! assert_equal (size (Mc.ClassNames), [2, 10]); %! assert_equal (cellstr (Mc.ClassNames), Ms.ClassNames); ## predict returns whole names, not their first letters. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcsvm (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcsvm (Xch, Ycell); %! pch = predict (Mc, Xch); %! assert_equal (columns (pch), 10); %! assert_equal (cellstr (pch), predict (Ms, Xch)); ## loss, margin and edge read a character response as the same response. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcsvm (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcsvm (Xch, Ycell); %! assert_equal (loss (Mc, Xch, Ych), loss (Ms, Xch, Ycell), 1e-12); %! assert_equal (margin (Mc, Xch, Ych), margin (Ms, Xch, Ycell), 1e-12); %! assert_equal (edge (Mc, Xch, Ych), edge (Ms, Xch, Ycell), 1e-12); ## A character matrix pads its rows out to the longest name, and the padding ## is part of the name: R2024a reports ClassNames of ['ab '; 'abcd']. %!test %! Xpad = [1 2; 3 4; 1.1 2.1; 3.1 4.1; 1.2 2.2; 3.2 4.2]; %! Ypad = char ({"ab", "abcd", "ab", "abcd", "ab", "abcd"}); %! rand ("state", 1); randn ("state", 1); %! Mp = fitcsvm (Xpad, Ypad); %! assert_equal (size (Mp.ClassNames), [2, 4]); %! assert_equal (Mp.ClassNames(1,:), "ab "); ## A row dropped for a missing predictor is the only case that exercises ## indexing the response by row rather than by element. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! Xmiss = Xch; Xmiss(3,2) = NaN; %! rand ("state", 1); randn ("state", 1); Md = fitcsvm (Xmiss, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcsvm (Xmiss, Ycell); %! assert_equal (size (Md.ClassNames), [2, 10]); %! assert_equal (cellstr (Md.ClassNames), Ms.ClassNames); ## ClassNames may itself be given as a character matrix, which selects the ## classes by whole name: ismember between two character matrices compares ## them character by character and would select by letter. %!test %! load fisheriris %! rand ("state", 1); randn ("state", 1); %! Mf = fitcsvm (meas, char (species), ... %! "ClassNames", char ({"versicolor", "virginica"})); %! assert_equal (rows (Mf.ClassNames), 2); %! assert_equal (cellstr (Mf.ClassNames), {"versicolor"; "virginica"}); ## A model fitted from a character response comes back off disk unchanged. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcsvm (Xch, Ych); %! fname = tempname (); %! savemodel (Mc, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.ClassNames, Mc.ClassNames); %! assert_equal (predict (M2, Xch), predict (Mc, Xch)); ## crossval carries a character response through cvpartition and back. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Mc = fitcsvm (Xch, Ych); %! rand ("state", 1); randn ("state", 1); Ms = fitcsvm (Xch, Ycell); %! rand ("state", 2); cvc = crossval (Mc, "KFold", 3); %! rand ("state", 2); cvs = crossval (Ms, "KFold", 3); %! assert_equal (cellstr (kfoldPredict (cvc)), kfoldPredict (cvs)); %!error ... %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,:); y = species(keep); %! discardSupportVectors (fitcsvm (X, y, "KernelFunction", "rbf")) %!error ... %! fitcsvm (ones (10,2), [1;1;1;1;1;2;2;2;2;2], 'Prior', 'nope') %!error ... %! fitcsvm (ones (10,2), [1;1;1;1;1;2;2;2;2;2], 'Cost', [0, 1]) %!error ... %! fitcsvm (ones (10,2), [1;1;1;1;1;2;2;2;2;2], 'Prior', [1, 1, 1]) %!error ... %! fitcsvm (ones (10,2), [1;1;1;1;1;2;2;2;2;2], 'Cost', eye (3)) ## The model reports the observation weights and the expanded predictor ## names MATLAB reports, W normalized to sum to one. %!test %! load fisheriris %! Yb = strcmp (species, 'setosa'); %! Mdl = fitcsvm (meas, Yb); %! assert_equal (size (Mdl.W), [150, 1]); %! assert_equal (sum (Mdl.W), 1, 1e-12); %! assert_equal (Mdl.W, ones (150, 1) / 150, 1e-12); %! assert_equal (Mdl.CategoricalPredictors, []); %! assert_equal (Mdl.ExpandedPredictorNames, Mdl.PredictorNames); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.W, Mdl.W); %! assert_equal (M2.ExpandedPredictorNames, Mdl.ExpandedPredictorNames); ## Standardize fits on the scale it predicts on. A model fitted on raw data ## and asked about standardized data is not merely worse, it is wrong: on a ## problem whose second predictor is a thousand times the first it answered at ## chance, where the same data standardized by hand reaches 0.9. %!test %! rand ('seed', 42); %! randn ('seed', 42); %! n = 80; %! A = [randn(n,1) + 1, (randn(n,1) + 1) * 1000]; %! B = [randn(n,1) - 1, (randn(n,1) - 1) * 1000]; %! X = [A; B]; %! Y = [ones(n,1); 2 * ones(n,1)]; %! Mdl = fitcsvm (X, Y, 'Standardize', true, 'KernelFunction', 'rbf'); %! assert_equal (mean (resubPredict (Mdl) == Y) > 0.85, true); %! assert_equal (predict (Mdl, X), resubPredict (Mdl)); %! Mu = mean (X, 1); %! Sg = std (X, [], 1); %! byhand = fitcsvm ((X - Mu) ./ Sg, Y, 'Standardize', false, ... %! 'KernelFunction', 'rbf'); %! assert_equal (mean (resubPredict (Mdl) == Y), ... %! mean (resubPredict (byhand) == Y)); ## Test input validation for constructor %!error ClassificationSVM () %!error ... %! ClassificationSVM (ones (10,2)) %!error ... %! ClassificationSVM (ones (10,2), ones (5,1)) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'Standardize', 'a') %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'PredictorNames', ['x1';'x2']) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'PredictorNames', {'x1','x2','x3'}) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'ResponseName', {'Y'}) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'ResponseName', 21) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'ClassNames', @(x)x) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'ClassNames', {1}) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'ClassNames', [1, 2]) %!error ... %! ClassificationSVM (ones (5,2), ['a';'b';'a';'a';'b'], 'ClassNames', ['a';'c']) %!error ... %! ClassificationSVM (ones (5,2), {'a';'b';'a';'a';'b'}, 'ClassNames', {'a','c'}) %!error ... %! ClassificationSVM (ones (10,2), logical (ones (10,1)), 'ClassNames', [true, false]) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'svmtype', 123) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'svmtype', 'some_type') %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'OutlierFraction', -1) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'KernelFunction', 123) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'KernelFunction', 'fcn') %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'PolynomialOrder', -1) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'PolynomialOrder', 0.5) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'PolynomialOrder', [1,2]) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'KernelScale', -1) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'KernelScale', 0) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'KernelScale', [1, 2]) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'KernelScale', 'invalid') %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'KernelOffset', -1) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'KernelOffset', [1,2]) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'BoxConstraint', -1) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'BoxConstraint', 0) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'BoxConstraint', [1, 2]) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'BoxConstraint', 'invalid') %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'nu', -0.5) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'nu', 0) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'nu', 1.5) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'CacheSize', -1) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'CacheSize', [1,2]) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'Tolerance', -0.1) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'Tolerance', [0.1,0.2]) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'shrinking', 2) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'shrinking', -1) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'shrinking', [1 0]) %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'invalid_name', 'c_svc') %!error ... %! ClassificationSVM (ones (10,2), ones (10,1), 'SVMtype', 'c_svc') %!error ... %! ClassificationSVM (ones (10,2), [1;1;1;1;2;2;2;2;3;3]) %!error ... %! ClassificationSVM ([ones(9,2);2,Inf], ones (10,1)) ## Test output for predict method %!shared x, y, x_train, x_test, y_train, y_test, objST %! load fisheriris %! inds = ! strcmp (species, 'setosa'); %! x = meas(inds, 3:4); %! y = grp2idx (species(inds)); %!test %! xc = [min(x); mean(x); max(x)]; %! obj = fitcsvm (x, y, 'KernelFunction', 'rbf', 'Tolerance', 1e-7); %! assert_equal (isempty (obj.Beta), true) %! assert_equal (sum (obj.IsSupportVector), numel (obj.Alpha)) %! [label, score] = predict (obj, xc); %! assert_equal (label, [1; 2; 2]); %! assert_equal (score(:,1), [0.99285; -0.080296; -0.93694], 2e-5); %! assert_equal (score(:,1), -score(:,2), eps) %!test %! obj = fitcsvm (x, y); %! assert_equal (obj.Beta, [2.182926829268275; 2.253658536585344], 1e-5) %! assert_equal (sum (obj.IsSupportVector), numel (obj.Alpha)) %! assert_equal (numel (obj.Alpha), 24) %! assert_equal (obj.Bias, -14.415, 1e-3) %! xc = [min(x); mean(x); max(x)]; %! label = predict (obj, xc); %! assert_equal (label, [1; 2; 2]); ## Values below are R2024a's, measured 2026-08-17. %!test %! ## a linear kernel has a primal representation, one coefficient per predictor %! obj = fitcsvm (x, y); %! assert_equal (size (obj.Beta), [2, 1]); %! assert_equal (obj.Beta, [2.182926829268275; 2.253658536585344], 1e-5); %! assert_equal (obj.Beta, ... %! obj.SupportVectors' * (obj.Alpha .* obj.SupportVectorLabels)); %!test %! ## a nonlinear kernel has none, but keeps the dual coefficients %! obj = fitcsvm (x, y, 'KernelFunction', 'rbf'); %! assert_equal (isempty (obj.Beta), true); %! assert_equal (numel (obj.Alpha), sum (obj.IsSupportVector)); %!test %! ## the dual coefficients are magnitudes; their class is in the labels %! obj = fitcsvm (x, y); %! assert_equal (any (obj.Alpha < 0), false); %! assert_equal (max (obj.Alpha) <= 1, true); %! assert_equal (size (obj.SupportVectorLabels), [24, 1]); %! assert_equal (unique (obj.SupportVectorLabels)', [-1, 1]); %!test %! ## the support vector indicator is logical, one entry per observation %! obj = fitcsvm (x, y); %! assert_equal (class (obj.IsSupportVector), 'logical'); %! assert_equal (size (obj.IsSupportVector), [100, 1]); %! assert_equal (sum (obj.IsSupportVector), 24); ## A single-observation query used to corrupt the heap and abort the ## interpreter; each row on its own must agree with the batch answer. %!test %! obj = fitcsvm (x, y); %! xc = [min(x); mean(x); max(x)]; %! batch = predict (obj, xc); %! for i = 1:rows (xc) %! assert_equal (predict (obj, xc(i,:)), batch(i)); %! endfor %!test %! obj = fitcsvm (x, y, 'KernelFunction', 'rbf', 'Tolerance', 1e-7); %! xc = [min(x); mean(x); max(x)]; %! [bl, bs] = predict (obj, xc); %! [l1, s1] = predict (obj, xc(1,:)); %! assert_equal (l1, bl(1)); %! assert_equal (size (l1), [1, 1]); %! assert_equal (size (s1), [1, 2]); %! assert_equal (s1, bs(1,:), 2e-5); %!test %! obj = compact (fitcsvm (x, y)); %! xc = [min(x); mean(x); max(x)]; %! batch = predict (obj, xc); %! assert_equal (predict (obj, xc(1,:)), batch(1)); ## Test input validation for predict method %!error ... %! predict (ClassificationSVM (ones (40,2), ones (40,1))) %!error ... %! predict (ClassificationSVM (ones (40,2), ones (40,1)), []) %!error ... %! predict (ClassificationSVM (ones (40,2), ones (40,1)), 1) %!test %! objST = fitcsvm (x, y); %!error ... %! objST.ScoreTransform = 'a'; %! [labels, scores] = predict (objST, x); ## Test input validation for resubPredict method %! [labels, scores] = resubPredict (objST); ## Test output for margin method %!test %! rand ('seed', 1); %! CVSVMModel = fitcsvm (x, y, 'KernelFunction', 'rbf', 'HoldOut', 0.15, ... %! 'Tolerance', 1e-7); %! obj = CVSVMModel.Trained{1}; %! testInds = test (CVSVMModel.Partition); %! ## Every one of these fifteen is classified correctly, so every margin is %! ## positive. They used to read -4.0000 downwards for the second class: %! ## the margin was formed from the response as given, so a 1/2 coding %! ## scaled that class by four instead of negating it, and the model looked %! ## as though it misclassified every observation of it. %! ## %! ## The values themselves are this engine's own and have no oracle: the %! ## partition comes from a seeded rand and the fit from LIBSVM, neither of %! ## which MATLAB can reproduce. They moved in the third decimal when the %! ## folds began inheriting the model's prior, an 85-row training split of %! ## a balanced 100 not being exactly even; what the block asserts is %! ## unchanged. %! expected_margin = [2.000000; 0.856067; 1.666246; 3.419288; ... %! 3.461257; 2.664258; 3.529112; 2.000000; ... %! 3.168674; 3.223841; 1.528738; 3.744702; ... %! 0.836534; 2.810381; 3.673740]; %! computed_margin = margin (obj, x(testInds,:), y(testInds,:)); %! assert_equal (computed_margin, expected_margin, 1e-4); %! assert (all (computed_margin > 0)); ## Test input validation for margin method %!error ... %! margin (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1))) %!error ... %! margin (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2)) %!error ... %! margin (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), [], zeros (2)) %!error ... %! margin (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), 1, zeros (2)) %!error ... %! margin (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2), []) %!error ... %! margin (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2), 1) ## Test output for loss method %!test %! rand ('seed', 1); %! CVSVMModel = fitcsvm (x, y, 'KernelFunction', 'rbf', 'HoldOut', 0.15); %! obj = CVSVMModel.Trained{1}; %! testInds = test (CVSVMModel.Partition); %! L1 = loss (obj, x(testInds,:), y(testInds,:), 'LossFun', 'binodeviance'); %! L2 = loss (obj, x(testInds,:), y(testInds,:), 'LossFun', 'classiferror'); %! L3 = loss (obj, x(testInds,:), y(testInds,:), 'LossFun', 'exponential'); %! L4 = loss (obj, x(testInds,:), y(testInds,:), 'LossFun', 'hinge'); %! L5 = loss (obj, x(testInds,:), y(testInds,:), 'LossFun', 'logit'); %! L6 = loss (obj, x(testInds,:), y(testInds,:), 'LossFun', 'quadratic'); %! ## These changed when loss stopped handing the response to LIBSVM %! ## unmapped: it used the labels 1 and 2 where the margin's sign wants +1 %! ## and -1, so every loss but the error rate was scaled by the labels. %! ## margin had already been given svmPlusMinus and loss had been missed. %! ## Cross-checked against R2024a on a deterministic half-and-half split, %! ## where ours reads 0.1800, 0.0800, 0.3984, 0.1785, 0.3184, 0.2939 and %! ## MATLAB reads 0.1812, 0.0800, 0.4107, 0.1520, 0.3297, 0.1981: the %! ## error rate agrees exactly and the rest sit within the LIBSVM against %! ## SMO difference of section 1. The old values were an order of %! ## magnitude out, a 53%% error rate among them. %! assert_equal (L1, 0.1122, 1e-4); %! assert_equal (L2, 0.0000, 1e-4); %! assert_equal (L3, 0.3135, 1e-4); %! assert_equal (L4, 0.1037, 1e-4); %! assert_equal (L5, 0.2652, 1e-4); %! ## L6 moved from 0.3218 when the folds began inheriting the model's %! ## prior; the other five stayed inside their tolerance. %! assert_equal (L6, 0.3215, 1e-4); ## Test input validation for loss method %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1))) %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2)) %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2), ... %! ones (2,1), 'LossFun') %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), [], zeros (2)) %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), 1, zeros (2)) %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2), []) %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2), 1) %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2), ... %! ones (2,1), 'LossFun', 1) %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2), ... %! ones (2,1), 'LossFun', 'some') %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2), ... %! ones (2,1), 'Weights', ['a','b']) %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2), ... %! ones (2,1), 'Weights', 'a') %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2), ... %! ones (2,1), 'Weights', [1,2,3]) %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2), ... %! ones (2,1), 'Weights', 3) %!error ... %! loss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), zeros (2), ... %! ones (2,1), 'some', 'some') ## Test input validation for resubLoss method %!error ... %! resubLoss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), 'LossFun') %!error ... %! resubLoss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), 'LossFun', 1) %!error ... %! resubLoss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), 'LossFun', 'some') %!error ... %! resubLoss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), 'Weights', ['a','b']) %!error ... %! resubLoss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), 'Weights', 'a') %!error ... %! resubLoss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), 'Weights', [1,2,3]) %!error ... %! resubLoss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), 'Weights', 3) %!error ... %! resubLoss (ClassificationSVM (ones (40,2), randi ([1, 2], 40, 1)), 'some', 'some') ## Test output for crossval method %!test %! SVMModel = fitcsvm (x, y); %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (SVMModel, 'KFold', 5); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (CVMdl.KFold == 5, true) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationSVM") %! assert_equal (CVMdl.CrossValidatedModel, "SVM") %!test %! obj = fitcsvm (x, y); %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (obj, 'HoldOut', 0.2); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationSVM") %! assert_equal (CVMdl.CrossValidatedModel, "SVM") %!test %! obj = fitcsvm (x, y); %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! CVMdl = crossval (obj, 'LeaveOut', 'on'); %! warning (status); %! assert_equal (class (CVMdl), "ClassificationPartitionedModel") %! assert_equal ({CVMdl.X, CVMdl.Y}, {x, y}) %! assert_equal (class (CVMdl.Trained{1}), "CompactClassificationSVM") %! assert_equal (CVMdl.CrossValidatedModel, "SVM") ## Test input validation for crossval method %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'KFold') %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), ... %! 'KFold', 5, 'leaveout', 'on') %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'KFold', 'a') %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'KFold', 1) %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'KFold', -1) %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'KFold', 11.5) %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'KFold', [1,2]) %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'Holdout', 'a') %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'Holdout', 11.5) %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'Holdout', -1) %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'Holdout', 0) %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'Holdout', 1) %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'Leaveout', 1) %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'CVPartition', 1) %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'CVPartition', 'a') %!error ... %! crossval (ClassificationSVM (ones (40,2),randi ([1, 2], 40, 1)), 'some', 'some') %!error ... %! savemodel (ClassificationSVM ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])) %!error ... %! savemodel (ClassificationSVM ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), 1) %!error ... %! savemodel (ClassificationSVM ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]), ['ab'; 'cd']) ## RowsUsed is empty when every observation was used. %!test %! load fisheriris %! X = meas(1:100,:); %! Y = grp2idx (species(1:100)); %! Mdl = fitcsvm (X, Y); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (class (Mdl.RowsUsed), 'double'); %! assert_equal (Mdl.NumObservations, 100); %! assert_equal (rows (Mdl.X), 100); %! assert_equal (rows (Mdl.W), 100); ## A missing response drops its observation and RowsUsed marks it. %!test %! load fisheriris %! X = meas(1:100,:); %! Y = grp2idx (species(1:100)); %! Y(5) = NaN; %! Mdl = fitcsvm (X, Y); %! assert_equal (class (Mdl.RowsUsed), 'logical'); %! assert_equal (size (Mdl.RowsUsed), [100, 1]); %! assert_equal (sum (Mdl.RowsUsed), 99); %! assert_equal (Mdl.RowsUsed(5), false); %! assert_equal (Mdl.NumObservations, 99); %! assert_equal (rows (Mdl.X), 99); %! assert_equal (rows (Mdl.W), 99); ## A missing predictor keeps its observation, so RowsUsed stays empty. %!test %! load fisheriris %! X = meas(1:100,:); %! X(3,2) = NaN; %! Y = grp2idx (species(1:100)); %! Mdl = fitcsvm (X, Y); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (Mdl.NumObservations, 100); %! assert_equal (rows (Mdl.X), 100); %! assert_equal (sum (isnan (Mdl.X(:))), 1); ## Mu and Sigma are empty unless the predictors are standardized. %!test %! load fisheriris %! Mdl = fitcsvm (meas(1:100,:), species(1:100)); %! assert_equal (Mdl.Mu, []); %! assert_equal (Mdl.Sigma, []); ## Standardizing weights the complete observations by each class's original ## share of the observation weight. Values from MATLAB R2024a. %!test %! load fisheriris %! X = meas(1:100,:); %! X(3,2) = NaN; X(17,4) = NaN; %! Mdl = fitcsvm (X, species(1:100), 'Standardize', true); %! assert_equal (Mdl.Mu, [5.4700833333333314, 3.0964583333333331, ... %! 2.864374999999999, 0.78487499999999988], 1e-13); %! assert_equal (Mdl.Sigma, [0.64239212471819018, 0.47709034264660688, ... %! 1.4464268842305728, 0.56625850081877471], 1e-13); ## The prior reweights the observations of each class. Values from R2024a. %!test %! load fisheriris %! i2 = [1:50, 51:80]; %! Mdl = fitcsvm (meas(i2,:), species(i2)); %! assert_equal (Mdl.Prior, [0.625, 0.375], 1e-14); %! assert_equal (Mdl.W(1), 0.0125, 1e-14); %! Mdl = fitcsvm (meas(i2,:), species(i2), 'Prior', 'uniform'); %! assert_equal (Mdl.W(1), 0.01, 1e-14); %! assert_equal (Mdl.W(51), 1/60, 1e-14); ## Neither Prior nor Cost may be assigned after the fit. The refusal comes ## from the property attributes, so the message is core Octave's and is not ## pinned here. %!error ... %! load fisheriris; ... %! Mdl = fitcsvm (meas(1:80,:), species(1:80)); ... %! Mdl.Prior = [0.5, 0.5]; %!error ... %! load fisheriris; ... %! Mdl = fitcsvm (meas(1:80,:), species(1:80)); ... %! Mdl.Cost = [0, 2; 1, 0]; ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcsvm (meas(inds,:), species(inds)); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'ClassificationSVM'); %! assert_equal (M2.NumObservations, Mdl.NumObservations); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ScoreTransform), class (Mdl.ScoreTransform)); %! assert_equal (predict (M2, meas(1:5,:)), predict (Mdl, meas(1:5,:))); ## edge, resubMargin and resubEdge. The absolute value is not pinned to the ## oracle here: this class fits through LIBSVM where MATLAB uses SMO, and the ## two disagree on the support vector set, so the edge reads 3.6942705869 ## against MATLAB's 3.6935623843 on the iris pair. What is pinned is what the ## methods mean. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds,:); %! Y = species(inds); %! Mdl = fitcsvm (X, Y); %! assert_equal (edge (Mdl, X, Y), mean (margin (Mdl, X, Y)), 1e-12); %! assert_equal (resubEdge (Mdl), edge (Mdl, X, Y), 1e-12); %! assert_equal (resubMargin (Mdl), margin (Mdl, X, Y), 1e-12); ## margin takes the class labels, as it documents, and the +1/-1 coding the ## solver works in gives the same answer. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds,:); %! Y = species(inds); %! Mdl = fitcsvm (X, Y); %! Ypm = ones (100, 1); %! Ypm(strcmp (Y, Mdl.ClassNames{2})) = -1; %! assert_equal (margin (Mdl, X, Y), margin (Mdl, X, Ypm), 1e-12); ## The compact model answers the same edge as the full one. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds,:); %! Y = species(inds); %! Mdl = fitcsvm (X, Y); %! assert_equal (edge (compact (Mdl), X, Y), edge (Mdl, X, Y), 1e-12); %!error ... %! load fisheriris; ... %! inds = ! strcmp (species, 'virginica'); ... %! edge (fitcsvm (meas(inds,:), species(inds)), meas(inds,:)) ## BinEdges is an empty cell, which is what MATLAB reports for this ## learner as well: it fits the predictors as they are. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcsvm (meas(inds,:), species(inds)); %! assert_equal (class (Mdl.BinEdges), 'cell'); %! assert_equal (Mdl.BinEdges, {}); ## KernelParameters, BoxConstraints, OutlierFraction and Nu, all measured on ## MATLAB R2024a. A radial basis kernel reports MATLAB's name for it. %!test %! load fisheriris %! b = ismember (species, {'setosa', 'versicolor'}); %! Mdl = fitcsvm (meas(b,:), species(b)); %! assert_equal (Mdl.KernelParameters, struct ('Function', 'linear', ... %! 'Scale', 1)); %! assert_equal (Mdl.BoxConstraints, ones (100, 1)); %! assert_equal (Mdl.OutlierFraction, 0); %! assert_equal (Mdl.Nu, []); %!test %! load fisheriris %! b = ismember (species, {'setosa', 'versicolor'}); %! Mdl = fitcsvm (meas(b,:), species(b), 'KernelFunction', 'rbf', ... %! 'KernelScale', 2.5, 'BoxConstraint', 3); %! assert_equal (Mdl.KernelParameters.Function, 'gaussian'); %! assert_equal (Mdl.KernelParameters.Scale, 2.5); %! assert_equal (unique (Mdl.BoxConstraints), 3); %!test %! load fisheriris %! b = ismember (species, {'setosa', 'versicolor'}); %! Mdl = fitcsvm (meas(b,:), species(b), 'KernelFunction', 'polynomial', ... %! 'PolynomialOrder', 3); %! assert_equal (fieldnames (Mdl.KernelParameters), ... %! {'Function'; 'Scale'; 'Order'}); %! assert_equal (Mdl.KernelParameters.Order, 3); ## A cost matrix reweights the classes, and the box constraints report what ## the fit actually solved: 1.6 and 0.4 against MATLAB's 1.6 and 0.4. %!test %! load fisheriris %! b = ismember (species, {'setosa', 'versicolor'}); %! Mdl = fitcsvm (meas(b,:), species(b), 'Cost', [0, 4; 1, 0]); %! assert_equal (Mdl.BoxConstraints(1), 1.6, 1e-12); %! assert_equal (Mdl.BoxConstraints(51), 0.4, 1e-12); %! assert_equal (mean (Mdl.BoxConstraints), 1, 1e-12); %!test %! load fisheriris %! b = ismember (species, {'setosa', 'versicolor'}); %! Mdl = fitcsvm (meas(b,:), species(b), 'OutlierFraction', 0.05); %! assert_equal (Mdl.OutlierFraction, 0.05); %!test %! load fisheriris %! b = ismember (species, {'setosa', 'versicolor'}); %! Mdl = fitcsvm (meas(b,:), species(b)); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.KernelParameters, Mdl.KernelParameters); %! assert_equal (M2.BoxConstraints, Mdl.BoxConstraints); ## HyperparameterOptimizationResults is declared for MATLAB compatibility and ## stays empty, this class running no search over its hyperparameters. %!test %! load fisheriris %! b = ! strcmp (species, 'virginica'); %! Mdl = fitcsvm (meas(b,:), species(b)); %! assert_equal (isempty (Mdl.HyperparameterOptimizationResults), true); ## ModelParameters records what LIBSVM was given. The field list and its ## order are ours, MATLAB's SMO and ISDA parameters having no counterpart ## here. %!test %! load fisheriris %! Mdl = fitcsvm (meas, strcmp (species, 'setosa')); %! assert_equal (fieldnames (Mdl.ModelParameters)', {'SVMtype', ... %! 'BoxConstraint', 'CacheSize', 'KernelScale', 'KernelOffset', ... %! 'KernelFunction', 'KernelPolynomialOrder', 'Nu', 'Tolerance', ... %! 'Shrinking', 'OutlierFraction', 'StandardizeData', 'Version', ... %! 'Method', 'Type'}); %!test %! load fisheriris %! MP = fitcsvm (meas, strcmp (species, 'setosa')).ModelParameters; %! assert_equal (MP.OutlierFraction, 0); %! assert_equal (MP.StandardizeData, false); %! assert_equal (MP.Version, 1); %! assert_equal (MP.Method, 'SVM'); %! assert_equal (MP.Type, 'classification'); ## The polynomial order belongs to the polynomial kernel alone, as it does in ## MATLAB, and carries its default there rather than staying empty. %!test %! load fisheriris %! b = strcmp (species, 'setosa'); %! assert_equal (isempty (fitcsvm (meas, b).ModelParameters. ... %! KernelPolynomialOrder), true); %! assert_equal (fitcsvm (meas, b, 'KernelFunction', ... %! 'polynomial').ModelParameters.KernelPolynomialOrder, 3); %!test %! load fisheriris %! MP = fitcsvm (meas, strcmp (species, 'setosa'), 'Standardize', true, ... %! 'OutlierFraction', 0.05).ModelParameters; %! assert_equal (MP.StandardizeData, true); %! assert_equal (MP.OutlierFraction, 0.05); ## The third output is the expected cost of each assignment. An SVM has no ## posterior, so it is the row of Cost belonging to the predicted class; ## MATLAB returns the column, which only an asymmetric matrix reveals. %!test %! load fisheriris %! b = strcmp (species, 'setosa'); %! Mdl = fitcsvm (meas, b, 'Cost', [0, 2; 5, 0]); %! [label, ~, cost] = predict (Mdl, meas([1, 51],:)); %! assert_equal (label, [true; false]); %! assert_equal (cost, [5, 0; 0, 2]); %!test %! load fisheriris %! b = strcmp (species, 'setosa'); %! Mdl = fitcsvm (meas, b); %! [~, ~, cost] = predict (Mdl, meas([1, 51],:)); %! assert_equal (cost, [1, 0; 0, 1]); %!test %! load fisheriris %! b = strcmp (species, 'setosa'); %! Mdl = fitcsvm (meas, b, 'Cost', [0, 2; 5, 0]); %! [~, ~, cost] = resubPredict (Mdl); %! assert_equal (size (cost), [150, 2]); %! assert_equal (cost([1, 51],:), [5, 0; 0, 2]); ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = fitcsvm (meas, strcmp (species, 'setosa')); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = fitcsvm (meas, strcmp (species, 'setosa')); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/CompactClassificationDiscriminant.m000066400000000000000000002545411524624707500312740ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef CompactClassificationDiscriminant ## -*- texinfo -*- ## @deftp {statistics} CompactClassificationDiscriminant ## ## Compact discriminant analysis classification ## ## The @code{CompactClassificationDiscriminant} class implements a compact ## version of a linear discriminant analysis classifier object, which can ## predict responses for new data using the @code{predict} method but does not ## store the training data. ## ## A @code{CompactClassificationDiscriminant} object is a compact version of a ## discriminant analysis model, @code{ClassificationDiscriminant}. It does ## not include the training data resulting in a smaller classifier size, which ## can be used for making predictions from new data, but not for tasks such as ## cross validation. It can only be created from a ## @code{ClassificationDiscriminant} model by using the @code{compact} object ## method. ## ## Create a @code{CompactClassificationDiscriminant} object by using the ## @code{compact} method of a @code{ClassificationDiscriminant} object. ## ## ## Six discriminant types are available, in two families. The linear family, ## @qcode{'linear'}, @qcode{'diagLinear'} and @qcode{'pseudoLinear'}, pools ## one covariance across the classes and separates them with a hyperplane. ## The quadratic family, @qcode{'quadratic'}, @qcode{'diagQuadratic'} and ## @qcode{'pseudoQuadratic'}, estimates a covariance per class and separates ## them with a quadric. A @qcode{'diag'} type keeps only the variances, ## which is the same model as a @qcode{Gamma} of 1, and a @qcode{'pseudo'} ## type inverts a singular covariance rather than refusing it. ## ## @qcode{DiscrimType} may be assigned after fitting, but @emph{only within ## its own family}: the family is fixed when the model is fitted, because it ## decides which covariances the fit has to estimate. Assigning it, or ## @qcode{Gamma}, re-derives @qcode{Sigma}, @qcode{LogDetSigma} and ## @qcode{Coeffs} without refitting. ## ## @seealso{fitcdiscr, ClassificationDiscriminant} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} NumPredictors ## ## Number of predictors ## ## A positive integer value specifying the number of predictors in the ## training dataset used for training the CompactClassificationDiscriminant ## model. This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} PredictorNames ## ## Names of predictor variables ## ## A cell array of character vectors specifying the names of the predictor ## variables. The names are in the order in which they appear in the ## training dataset. This property is read-only. ## ## @end deftp PredictorNames = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} BetweenSigma ## ## Between-class covariance matrix ## ## A @math{P}-by-@math{P} matrix holding the covariance of the class means ## about the overall mean, weighted by how many observations each class ## contributes. With @math{n_k} observations in class @math{k}, ## @math{p_k = n_k / n} and @math{\bar{\mu} = \sum_k p_k \mu_k}, it is ## ## @example ## @group ## BetweenSigma = sum_k n_k (Mu(k,:) - mubar)' * (Mu(k,:) - mubar) ## / (n * (1 - sum_k p_k^2)) ## @end group ## @end example ## ## The denominator is the unbiased one for a weighted covariance, so a ## balanced fit divides by @math{n (K-1) / K}. It reads the @strong{class ## sizes}, not @qcode{Prior}: assigning a prior leaves it where it was. It ## is estimated for every discriminant type, the quadratic family included, ## since it describes the classes rather than the fit. This property is ## read-only. ## ## @end deftp BetweenSigma = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector of column indices into @code{X} naming the predictors ## treated as categorical, and empty when none is. This property is ## read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} ExpandedPredictorNames ## ## Names of the predictors as the model expanded them ## ## A cell array of character vectors. It matches @code{PredictorNames} ## unless a categorical predictor was expanded into indicator variables. ## This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} ResponseName ## ## Response variable name ## ## A character vector specifying the name of the response variable @var{Y}. ## This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} ClassNames ## ## Names of classes in the response variable ## ## An array of unique values of the response variable @var{Y}, which has the ## same data types as the data in @var{Y}. This property is read-only. ## @qcode{ClassNames} can have any of the following datatypes: ## ## @itemize ## @item Cell array of character vectors ## @item Character array ## @item Logical vector ## @item Numeric vector ## @end itemize ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} Sigma ## ## Within-class covariance ## ## A numeric array whose shape follows @qcode{DiscrimType}, with @math{P} ## predictors and @math{K} classes: ## ## @multitable @columnfractions 0.4 0.25 0.35 ## @headitem @var{DiscrimType} @tab @var{Sigma} @tab @var{LogDetSigma} ## @item @qcode{'linear'}, @qcode{'pseudoLinear'} @tab @math{PxP} ## @tab scalar ## @item @qcode{'quadratic'}, @qcode{'pseudoQuadratic'} @tab @math{PxPxK} ## @tab @math{Kx1} ## @item @qcode{'diagLinear'} @tab @math{1xP} @tab scalar ## @item @qcode{'diagQuadratic'} @tab @math{1xPxK} @tab @math{Kx1} ## @end multitable ## ## The linear family pools one covariance across the classes and the ## quadratic family estimates one per class. This property is read-only, ## but it is re-derived whenever @qcode{DiscrimType} or @qcode{Gamma} is ## assigned. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} Mu ## ## Class means ## ## A @math{K*P} numeric matrix specifying the mean of the multivariate ## normal distribution of each corresponding class, where @math{K} is the ## number of classes and @math{P} is the number of predictors. This property ## is read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} Coeffs ## ## Coefficient matrices ## ## A @math{K*K} structure containing the coefficient matrices, where ## @math{K} is the number of classes. If the @qcode{'FillCoeffs'} parameter ## was set to @qcode{'off'} in the original ## @code{ClassificationDiscriminant} model, then @qcode{Coeffs} is empty ## @qcode{([])}. This property is read-only. ## ## @qcode{Coeffs(i,j)} contains the coefficients of the boundary between ## the classes @code{i} and @code{j} in the following fields: ## ## @itemize ## @item @qcode{DiscrimType} - A character vector ## @item @qcode{Class1} - @qcode{@var{ClassNames}(i)} ## @item @qcode{Class2} - @qcode{@var{ClassNames}(j)} ## @item @qcode{Const} - A scalar ## @item @qcode{Linear} - A vector with length as the number of predictors. ## @item @qcode{Quadratic} - The quadratic family only. A @math{PxP} ## matrix, or a @math{1xP} vector for @qcode{'diagQuadratic'}, following ## the shape of @qcode{Sigma}. ## @end itemize ## ## The diagonal entries carry the two class names and nothing else. The ## structure is rebuilt whenever @qcode{DiscrimType}, @qcode{Gamma} or ## @qcode{Prior} is assigned. ## ## @end deftp Coeffs = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} DeltaPredictor ## ## Minimum Delta at which each predictor drops out ## ## A row vector with one entry per predictor, the value of @qcode{Delta} at ## which that predictor's coefficient is zero for every class and the ## predictor leaves the model altogether. It is all zeros for the ## quadratic family, which has no linear coefficients to eliminate. ## ## This property is read-only, and it describes the fit rather than the ## threshold: assigning @qcode{Delta} does not move it. ## ## @end deftp DeltaPredictor = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} MinGamma ## ## Minimum value for the Gamma regularization parameter ## ## A scalar from 0 to 1, the least regularization that leaves the ## correlation matrix invertible. It is 0 when the matrix is already ## invertible, and positive when the predictors are collinear, in which ## case a plain @qcode{'linear'} or @qcode{'quadratic'} fit is raised to it ## rather than failing. Assigning a @qcode{Gamma} below it is refused. ## ## This property is read-only. ## ## @end deftp MinGamma = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} LogDetSigma ## ## Logarithm of the determinant of the within-class covariance matrix ## ## A scalar for the linear family and a @math{Kx1} vector for the quadratic ## one, one entry per class. It is computed in correlation space, as the ## sum of the logarithms of the predictor variances plus the log ## determinant of the correlation matrix, which is far better conditioned ## than the covariance when the data are nearly collinear. A predictor ## with no variance contributes nothing rather than an infinity, and the ## @qcode{'pseudo'} types sum only over the directions that carry variance. ## ## This property is read-only. ## ## @end deftp LogDetSigma = []; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} DiscrimType ## ## Discriminant type ## ## A character vector naming the discriminant model, one of ## @qcode{'linear'}, @qcode{'quadratic'}, @qcode{'diagLinear'}, ## @qcode{'diagQuadratic'}, @qcode{'pseudoLinear'} or ## @qcode{'pseudoQuadratic'}. A linear type pools one covariance across ## the classes; a quadratic type estimates one per class. A ## @qcode{'diag'} type keeps only the variances, and a @qcode{'pseudo'} ## type inverts a singular covariance instead of refusing it. ## ## This property may be assigned, but @emph{only within its own family}: ## the three linear types interchange freely and so do the three quadratic ## ones, while no assignment moves a model between the two. The family is ## fixed when the model is fitted, because it decides which covariances the ## fit has to estimate. Assigning re-derives @qcode{Sigma}, ## @qcode{LogDetSigma}, @qcode{Gamma} and @qcode{Coeffs}. ## ## @end deftp DiscrimType = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} Gamma ## ## Gamma regularization parameter ## ## A scalar from 0 to 1 shrinking the covariance towards its diagonal. ## @qcode{Gamma} and @qcode{DiscrimType} are one state: a value of 1 is the ## diagonal type, so assigning it renames @qcode{DiscrimType} to ## @qcode{'diagLinear'} or @qcode{'diagQuadratic'}, and assigning a ## diagonal type sets @qcode{Gamma} to 1. ## ## The quadratic family admits 0 and 1 only. A value below ## @qcode{MinGamma} is refused, since it would leave the covariance ## singular. Assigning re-derives @qcode{Sigma}, @qcode{LogDetSigma} and ## @qcode{Coeffs}. ## ## @end deftp Gamma = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} Delta ## ## Delta threshold for the linear coefficients ## ## A nonnegative scalar that eliminates predictors. A per-class linear ## coefficient is set to zero when it falls below @qcode{Delta}, and the ## comparison is made on the @strong{standardized} coefficient, the ## coefficient times the within-class standard deviation of its predictor. ## Scaling matters here: a threshold on the raw coefficients would depend ## on the units each predictor is measured in, so the same model in ## centimetres and in metres would drop different predictors. ## ## @qcode{DeltaPredictor} reports, per predictor, the value at which it ## drops out of every class at once. ## ## It applies to the linear family only, a quadratic discriminant having no ## linear coefficients to eliminate. Assigning it rebuilds @qcode{Coeffs} ## and changes what @code{predict} answers. ## ## @end deftp Delta = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} Cost ## ## Cost of Misclassification ## ## A square matrix specifying the cost of misclassification of a point. ## @qcode{Cost(i,j)} is the cost of classifying a point into class @qcode{j} ## if its true class is @qcode{i} (that is, the rows correspond to the true ## class and the columns correspond to the predicted class). The order of ## the rows and columns in @qcode{Cost} corresponds to the order of the ## classes in @qcode{ClassNames}. The number of rows and columns in ## @qcode{Cost} is the number of unique classes in the response. By ## default, @qcode{Cost(i,j) = 1} if @qcode{i != j}, and ## @qcode{Cost(i,j) = 0} if @qcode{i = j}. In other words, the cost is 0 ## for correct classification and 1 for incorrect classification. ## ## This property is read-only. ## ## ## A cost may also be given as a struct with the fields ## @qcode{ClassNames} and @qcode{ClassificationCosts}, which names the ## order its own matrix is written in. That matrix is permuted into the ## order of @qcode{ClassNames} above, so a caller need not know which ## order the classes were sorted into. It must name every class. ## ## A cost must be floating point, not sparse, not complex, non-negative ## and zero down its diagonal, and must hold no @qcode{NaN} or ## @qcode{Inf}. A @code{single} is widened to @code{double}. ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} Prior ## ## Prior probability for each class ## ## A numeric vector specifying the prior probabilities for each class. The ## order of the elements in @qcode{Prior} corresponds to the order of the ## classes in @qcode{ClassNames}. ## ## This property is read-only. ## ## Specified as a row vector with one entry per class, in the order of ## @qcode{ClassNames}, and rescaled to sum to one. It may be given as ## @qcode{'empirical'}, @qcode{'uniform'}, a numeric vector, or a ## structure with @qcode{ClassNames} and @qcode{ClassProbs} fields, which ## assigns each probability by class name rather than by position. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {CompactClassificationDiscriminant} {property} ScoreTransform ## ## Transformation function for classification scores ## ## Specified as a function handle for transforming the classification ## scores. This property is read-only. ## ## When specified as a character vector, it can be any of the following ## built-in functions. Nevertheless, the @qcode{ScoreTransform} property ## always stores their function handle equivalent. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'doublelogit'} @tab @math{1 ./ (1 + exp (-2 * x))} ## @item @qcode{'invlogit'} @tab @math{log (x ./ (1 - x))} ## @item @qcode{'ismax'} @tab Sets the score for the class with the ## largest score to 1, and for all other classes to 0 ## @item @qcode{'logit'} @tab @math{1 ./ (1 + exp (-x))} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'sign'} @tab ## @math{-1 for x < 0, 0 for x = 0, 1 for x > ## 0} ## @item @qcode{'symmetric'} @tab @math{2 * x - 1} ## @item @qcode{'symmetricismax'} @tab Sets the score for the class ## with the largest score to 1, and for all other classes to -1 ## @item @qcode{'symmetriclogit'} @tab @math{2 ./ (1 + exp (-x)) - 1} ## @end multitable ## ## @end deftp ScoreTransform = 'none'; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) ## The unregularized within-class covariance the fit estimated, carried ## over from the full model so that assigning DiscrimType re-derives here ## exactly as it does there. BaseSigma = []; STfun = @(x) x; endproperties ## Set methods for the properties a user may assign. methods (Hidden) ## DiscrimType, Gamma and Delta are one state, and these three methods are ## the only place it is changed. Assigning the type or the regularization ## re-derives Sigma, LogDetSigma and Coeffs from BaseSigma, the covariance ## the fit estimated, so no assignment ever refits. Each writes its own ## property directly, which does not re-enter its own set method, and ## reaches the others through theirs, which is what keeps the three ## consistent whichever one the user assigns. function this = set.DiscrimType (this, val) [t, fam] = discrimcanon (val); ## The family a fit belongs to is written in BaseSigma's shape: one ## covariance for the linear family, one per class for the quadratic. ## Reading it there rather than from the stored type is what lets a ## loaded model take its own type back, the stub it is loaded into ## carrying the default type until the file has been read. if (! isempty (this.BaseSigma) && size (this.BaseSigma, 3) > 1) cur = 'quadratic'; elseif (! isempty (this.BaseSigma)) cur = 'linear'; else [~, cur] = discrimcanon (this.DiscrimType); endif if (isempty (t) || (! isempty (cur) && ! strcmp (fam, cur))) ## The family was fixed by the fit, so only three types are on offer, ## and an unrecognized name is refused by the same message. if (strcmp (cur, 'quadratic')) ok = "quadratic, diagQuadratic, or pseudoQuadratic"; else ok = "linear, diagLinear, or pseudoLinear"; endif error (strcat ("CompactClassificationDiscriminant:", ... " 'DiscrimType' can only be set to one of: %s."), ok); endif this.DiscrimType = t; if (isempty (this.BaseSigma)) return; # not fitted yet, nothing to derive endif ## A diagonal type is Gamma of 1 and every other type starts from 0, ## which is what the oracle reports for each transition. The derivation ## raises it to MinGamma where the covariance needs it. g = double (strncmp (t, 'diag', 4)); [~, ~, g] = discrimderive (this.BaseSigma, t, g, this.MinGamma); this.Gamma = g; endfunction function this = set.Gamma (this, val) if (! (isnumeric (val) && isscalar (val) && val >= 0 && val <= 1)) error (strcat ("CompactClassificationDiscriminant: 'Gamma' must", ... " be a scalar between 0 and 1.")); endif if (isempty (this.BaseSigma)) this.Gamma = val; return; endif [~, fam] = discrimcanon (this.DiscrimType); if (strcmp (fam, 'quadratic') && val > 0 && val < 1) error (strcat ("CompactClassificationDiscriminant: cannot set", ... " 'Gamma' to any value but 0 or 1 for a quadratic", ... " discriminant.")); endif ## Gamma of 1 IS the diagonal type. Naming the type does the rest, and ## comes back here with a value that no longer needs the rename. if (val == 1 && ! strncmp (this.DiscrimType, 'diag', 4)) if (strcmp (fam, 'quadratic')) this.DiscrimType = 'diagQuadratic'; else this.DiscrimType = 'diagLinear'; endif return; elseif (val < 1 && strncmp (this.DiscrimType, 'diag', 4)) if (strcmp (fam, 'quadratic')) this.DiscrimType = 'quadratic'; else this.DiscrimType = 'linear'; endif endif ## The MinGamma floor belongs to the one type that regularizes its way ## out of a singular covariance. A pseudo type is Gamma of 0 by ## definition, inverting the rank it has instead; a diagonal type is ## Gamma of 1 and never near the floor; and the quadratic family refuses ## a singular class covariance outright rather than ridging it. Mg = this.MinGamma; [Sg, Ld, Ge] = discrimderive (this.BaseSigma, this.DiscrimType, val, Mg); if (val < Mg && strcmp (this.DiscrimType, 'linear')) error (strcat ("CompactClassificationDiscriminant: 'Gamma' must be", ... " between %g and 1."), Mg); endif this.Gamma = Ge; this.Sigma = Sg; this.LogDetSigma = Ld; if (! isempty (this.Coeffs)) this.Coeffs = discrimcoeffs (this.Mu, Sg, Ld, this.Prior, ... this.DiscrimType, this.Delta, ... this.ClassNames); endif endfunction function this = set.Delta (this, val) if (! (isnumeric (val) && isscalar (val) && val >= 0)) error (strcat ("CompactClassificationDiscriminant: 'Delta' must", ... " be a nonnegative scalar.")); endif [~, fam] = discrimcanon (this.DiscrimType); if (val > 0 && strcmp (fam, 'quadratic')) error (strcat ("CompactClassificationDiscriminant: cannot", ... " eliminate linear predictors in a quadratic", ... " discriminant.")); endif this.Delta = val; if (! isempty (this.BaseSigma) && ! isempty (this.Coeffs)) this.Coeffs = discrimcoeffs (this.Mu, this.Sigma, this.LogDetSigma, ... this.Prior, this.DiscrimType, val, ... this.ClassNames); endif endfunction function this = set.ScoreTransform (this, val) name = 'CompactClassificationDiscriminant'; [this.STfun, this.ScoreTransform] = parseScoreTransform (val, name); endfunction function this = set.Cost (this, val) gnY = this.ClassNames; if (isempty (val)) this.Cost = cast (! eye (classCount (gnY)), 'double'); else ## Everything a cost must be, and the struct form, which ## is permuted into this model's class order. [val, errmsg] = costMatrix (val, gnY); if (! isempty (errmsg)) error ("CompactClassificationDiscriminant: %s", errmsg); endif this.Cost = val; endif endfunction function this = set.Prior (this, val) K = classCount (this.ClassNames); if (isstruct (val)) val = priorFromStruct (val, this.ClassNames, ... 'CompactClassificationDiscriminant'); endif if (ischar (val) && strcmpi (val, 'uniform')) this.Prior = ones (1, K) / K; elseif (isnumeric (val) && isreal (val) && isvector (val) && numel (val) == K && all (val >= 0) && sum (val) > 0) this.Prior = val(:)' / sum (val); else error (strcat ("CompactClassificationDiscriminant: 'Prior' must be", ... " 'uniform' or a non-negative numeric vector with", ... " one entry per class.")); endif endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationDiscriminant} {@var{obj} =} CompactClassificationDiscriminant (@var{Mdl}) ## @deftypefnx {CompactClassificationDiscriminant} {@var{obj} =} CompactClassificationDiscriminant () ## ## Create a @code{CompactClassificationDiscriminant} object. ## ## @var{Mdl} is the @code{ClassificationDiscriminant} object to ## compact. The documented way to reach this constructor is the ## @code{compact} method. ## ## Called with no arguments it returns an object with its properties ## empty, which is how a saved model is rebuilt before its values are ## filled in. ## ## @end deftypefn function this = CompactClassificationDiscriminant (Mdl = []) ## Check for appropriate class if (isempty (Mdl)) return; elseif (! strcmpi (class (Mdl), 'ClassificationDiscriminant')) error (strcat ("CompactClassificationDiscriminant: invalid", ... " classification object.")); endif ## Save properties to compact model this.NumPredictors = Mdl.NumPredictors; this.PredictorNames = Mdl.PredictorNames; this.CategoricalPredictors = Mdl.CategoricalPredictors; this.ExpandedPredictorNames = Mdl.ExpandedPredictorNames; this.ResponseName = Mdl.ResponseName; this.ClassNames = Mdl.ClassNames; this.Cost = Mdl.Cost; this.Prior = Mdl.Prior; this.ScoreTransform = Mdl.ScoreTransform; this.STfun = Mdl.STfun; this.Sigma = Mdl.Sigma; this.Mu = Mdl.Mu; this.BetweenSigma = Mdl.BetweenSigma; this.Coeffs = Mdl.Coeffs; this.Delta = Mdl.Delta; this.DiscrimType = Mdl.DiscrimType; this.Gamma = Mdl.Gamma; this.MinGamma = Mdl.MinGamma; this.LogDetSigma = Mdl.LogDetSigma; this.DeltaPredictor = Mdl.DeltaPredictor; this.BaseSigma = Mdl.BaseSigma; endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n CompactClassificationDiscriminant\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); if (iscellstr (this.ClassNames)) str = repmat ({'''%s'''}, 1, numel (this.ClassNames)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.ClassNames{:}); else # numeric str = repmat ({'%d'}, 1, numel (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.ClassNames); endif fprintf ("%+25s: %s\n", 'ClassNames', str); fprintf ("%+25s: '%s'\n", 'ScoreTransform', this.ScoreTransform); fprintf ("%+25s: '%d'\n", 'NumPredictors', this.NumPredictors); fprintf ("%+25s: '%s'\n", 'DiscrimType', this.DiscrimType); fprintf ("%+25s: [%dx%d double]\n", 'Mu', size (this.Mu)); ## Coeffs is KxK, which is Sigma's shape only for a linear discriminant. fprintf ("%+25s: [%dx%d struct]\n\n", 'Coeffs', ... rows (this.ClassNames), rows (this.ClassNames)); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {CompactClassificationDiscriminant} {@var{n} =} nLinearCoeffs (@var{obj}) ## @deftypefnx {CompactClassificationDiscriminant} {@var{n} =} nLinearCoeffs (@var{obj}, @var{delta}) ## ## Number of nonzero linear coefficients at a regularization threshold. ## ## @code{@var{n} = nLinearCoeffs (@var{obj})} returns the number of ## predictors the discriminant keeps at its own @code{Delta}. ## ## @code{@var{n} = nLinearCoeffs (@var{obj}, @var{delta})} returns the ## number it would keep at each threshold in @var{delta}, as a column ## vector however @var{delta} is shaped. ## ## A predictor survives a threshold when its @code{DeltaPredictor} reaches ## it, the comparison including equality, so @var{delta} at exactly a ## predictor's own value still counts it. A threshold above every ## @code{DeltaPredictor} therefore leaves nothing and returns zero. ## ## The count is taken whatever the @code{DiscrimType}, as MATLAB takes it, ## even though @code{Delta} regularizes the linear types alone. ## ## @seealso{fitcdiscr, ClassificationDiscriminant, ## CompactClassificationDiscriminant} ## @end deftypefn function n = nLinearCoeffs (this, delta) if (nargin < 1 || nargin > 2) print_usage (); endif if (nargin < 2) delta = this.Delta; endif if (! (isnumeric (delta) && isreal (delta))) error (strcat ("CompactClassificationDiscriminant.nLinearCoeffs:", ... " DELTA must be a real numeric value.")); endif ## One column per threshold, one row per predictor, summed down. n = sum (this.DeltaPredictor(:) >= delta(:)', 1)'; endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationDiscriminant} {@var{label} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {CompactClassificationDiscriminant} {[@var{label}, @var{score}, @var{cost}] =} predict (@var{obj}, @var{XC}) ## ## Classify new data points into categories using the discriminant ## analysis model from a CompactClassificationDiscriminant object. ## ## @code{@var{label} = predict (@var{obj}, @var{XC})} returns the vector of ## labels predicted for the corresponding instances in @var{XC}, using the ## corresponding labels from the trained @qcode{ClassificationDiscriminant}, ## model, @var{obj}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{CompactClassificationDiscriminant} class ## object. ## @item ## @var{XC} must be an @math{M*P} numeric matrix with the same number of ## features @math{P} as the corresponding predictors of the discriminant ## model in @var{obj}. ## @end itemize ## ## @code{[@var{label}, @var{score}, @var{cost}] = predict (@var{obj}, ## @var{XC})} also returns @var{score}, which contains the predicted class ## scores or posterior probabilities for each instance of the corresponding ## unique classes, and @var{cost}, which is a matrix containing the expected ## cost of the classifications. ## ## The @var{score} matrix contains the posterior probabilities for each ## class, calculated using the multivariate normal probability density ## function and the prior probabilities of each class. These scores are ## normalized to ensure they sum to 1 for each observation. ## ## The @var{cost} matrix contains the expected classification cost for each ## class, computed based on the posterior probabilities and the specified ## misclassification costs. ## ## @seealso{CompactClassificationDiscriminant, fitcdiscr} ## @end deftypefn function [label, score, cost] = predict (this, XC) ## Check for sufficient input arguments if (nargin < 2) error (strcat ("CompactClassificationDiscriminant.predict:", ... " too few input arguments.")); endif ## Check for valid XC if (isempty (XC)) error ("CompactClassificationDiscriminant.predict: XC is empty."); elseif (this.NumPredictors != columns (XC)) error (strcat ("CompactClassificationDiscriminant.predict: XC", ... " must have the same number of features as the", ... " trained model.")); endif ## Initialize matrices numObservations = rows (XC); numClasses = classCount (this.ClassNames); score = zeros (numObservations, numClasses); cost = zeros (numObservations, numClasses); ## Score from the inverse covariance and its log determinant rather than ## through mvnpdf. A pseudo type's covariance is deliberately singular ## and mvnpdf refuses it, and LogDetSigma has to be the value the ## property reports, so the score and the property cannot drift apart. [~, fam] = discrimcanon (this.DiscrimType); logscore = zeros (numObservations, numClasses); if (strcmp (fam, 'linear')) ## The linear family scores from its per-class coefficients, which is ## the same function with the terms common to every class dropped, and ## is the only form in which Delta can eliminate a predictor. [Z, b] = discrimlinear (this.Mu, this.Sigma, this.Prior, ... this.DiscrimType, this.Delta); logscore = XC * Z' + b; else SigmaInv = discriminv (this.Sigma, this.DiscrimType); nInv = size (SigmaInv, 3); logdet = this.LogDetSigma; if (isscalar (logdet)) logdet = repmat (logdet, numClasses, 1); endif for i = 1:numClasses Si = SigmaInv(:,:,min (i, nInv)); Zc = XC - this.Mu(i, :); logscore(:, i) = -0.5 * sum ((Zc * Si) .* Zc, 2) ... - 0.5 * logdet(i) + log (this.Prior(i)); endfor endif ## The shared 2*pi factor cancels in the normalization, and subtracting ## the row maximum first keeps a well separated observation a posterior ## rather than a ratio of two underflowed zeros. logscore = logscore - max (logscore, [], 2); score = exp (logscore); score = score ./ sum (score, 2); score(isnan (score)) = 0; ## Calculate expected classification cost for i = 1:numClasses cost(:, i) = sum (bsxfun (@times, score, this.Cost(:, i)'), 2); endfor ## Predict the class labels based on the minimum cost [~, minIdx] = min (cost, [], 2); label = labelsFromIndex (this.ClassNames, minIdx); ## Apply ScoreTransform once to the whole matrix, after the label and ## the cost have been taken from the untransformed posteriors: a ## transform reshapes what predict reports and must not move what it ## decides. score = this.STfun (score); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationDiscriminant} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactClassificationDiscriminant} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Compute loss for a trained CompactClassificationDiscriminant object. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} computes the loss, ## @var{L}, using the default loss function @qcode{'mincost'}. ## ## @itemize ## @item ## @code{obj} is a @var{CompactClassificationDiscriminant} object. ## @item ## @code{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} must have same ## numbers of rows as @var{X}. ## @end itemize ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} allows ## additional options specified by @var{name}-@var{value} pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'LossFun'} @tab Specifies the loss function to use. ## Can be a function handle with four input arguments (C, S, W, Cost) ## which returns a scalar value or one of: ## 'binodeviance', 'classifcost', 'classiferror', 'exponential', ## 'hinge', 'logit','mincost', 'quadratic'. ## @itemize ## @item ## @code{C} is a logical matrix of size @math{N*K}, where @math{N} is the ## number of observations and @math{K} is the number of classes. ## The element @code{C(i,j)} is true if the class label of the i-th ## observation is equal to the j-th class. ## @item ## @code{S} is a numeric matrix of size @math{N*K}, where each element ## represents the classification score for the corresponding class. ## @item ## @code{W} is a numeric vector of length @math{N}, representing ## the observation weights. ## @item ## @code{Cost} is a @math{K*K} matrix representing the misclassification ## costs. ## @end itemize ## ## @item @qcode{'Weights'} @tab Specifies observation weights, must be ## a numeric vector of length equal to the number of rows in X. ## Default is @code{ones (size (X, 1))}. loss normalizes the weights so that ## observation weights in each class sum to the prior probability of that ## class. When you supply Weights, loss computes the weighted ## classification loss. ## ## @end multitable ## ## @seealso{CompactClassificationDiscriminant} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error (strcat ("CompactClassificationDiscriminant.loss:", ... " too few input arguments.")); elseif (mod (nargin - 3, 2) != 0) error (strcat ("CompactClassificationDiscriminant.loss:", ... " name-value arguments must be in pairs.")); elseif (nargin > 7) error (strcat ("CompactClassificationDiscriminant.loss:", ... " too many input arguments.")); endif ## Default values LossFun = 'mincost'; Weights = []; ## Validate Y valid_types = {'char', 'string', 'logical', 'single', 'double', 'cell'}; if (! (any (strcmp (class (Y), valid_types)))) error (strcat ("CompactClassificationDiscriminant.loss:", ... " Y must be of a valid type.")); endif ## Validate size of Y if (size (Y, 1) != size (X, 1)) error (strcat ("CompactClassificationDiscriminant.loss: Y must", ... " have the same number of rows as X.")); endif ## Parse name-value arguments while (numel (varargin) > 0) Value = varargin{2}; switch (tolower (varargin{1})) case 'lossfun' lf_opt = {'binodeviance', 'classifcost', 'classiferror', ... 'exponential', 'hinge','logit', 'mincost', 'quadratic'}; if (isa (Value, 'function_handle')) ## Check if the loss function is valid if (nargin (Value) != 4) error (strcat ("CompactClassificationDiscriminant.loss:", ... " custom loss function must accept", ... " exactly four input arguments.")); endif try n = 1; K = 2; C_test = false (n, K); S_test = zeros (n, K); W_test = ones (n, 1); Cost_test = ones (K) - eye (K); test_output = Value(C_test, S_test, W_test, Cost_test); if (! isscalar (test_output)) error (strcat ("CompactClassificationDiscriminant.loss:", ... " custom loss function must return", ... " a scalar value.")); endif catch error (strcat ("CompactClassificationDiscriminant.loss:", ... " custom loss function is not valid or", ... " does not produce correct output.")); end_try_catch LossFun = Value; elseif (ischar (Value) && any (strcmpi (Value, lf_opt))) LossFun = Value; else error (strcat ("CompactClassificationDiscriminant.loss:", ... " invalid loss function.")); endif case 'weights' if (isnumeric (Value) && isvector (Value)) if (numel (Value) != size (X ,1)) error (strcat ("CompactClassificationDiscriminant.loss:", ... " number of 'Weights' must be equal to", ... " the number of rows in X.")); elseif (numel (Value) == size (X, 1)) Weights = Value; endif else error (strcat ("CompactClassificationDiscriminant.loss:", ... " invalid 'Weights'.")); endif otherwise error (strcat ("CompactClassificationDiscriminant.loss:", ... " invalid parameter name in optional pair", ... " arguments.")); endswitch varargin(1:2) = []; endwhile ## Check for missing values in X if (! isa (LossFun, 'function_handle')) lossfun = tolower (LossFun); if (! strcmp (lossfun, 'mincost') && ! strcmp (lossfun, 'classiferror') && ! strcmp (lossfun, 'classifcost') && any (isnan (X(:)))) L = NaN; return; endif endif ## Convert Y to a cell array of strings if (ischar (Y)) Y = cellstr (Y); elseif (isnumeric (Y)) Y = cellstr (num2str (Y)); elseif (islogical (Y)) Y = cellstr (num2str (double (Y))); elseif (iscell (Y)) Y = cellfun (@num2str, Y, 'UniformOutput', false); else error (strcat ("CompactClassificationDiscriminant.loss: Y must be", ... " a numeric, logical, char, string, or cell array.")); endif ## Check if Y contains correct classes if (! labelsKnown (Y, this.ClassNames)) error (strcat ("CompactClassificationDiscriminant.loss: Y must", ... " contain only the classes in ClassNames.")); endif ## Set default weights if not specified if (isempty (Weights)) Weights = ones (size (X, 1), 1); endif ## Normalize Weights unique_classes = this.ClassNames; class_prior_probs = this.Prior; norm_weights = zeros (size (Weights)); for i = 1:classCount (unique_classes) class_idx = classMembers (Y, unique_classes, i); if (sum (Weights(class_idx)) > 0) norm_weights(class_idx) = ... Weights(class_idx) * class_prior_probs(i) / sum (Weights(class_idx)); endif endfor Weights = norm_weights / sum (norm_weights); ## Number of observations n = size (X, 1); ## Predict classification scores [label, scores] = predict (this, X); ## C is vector of K-1 zeros, with 1 in the ## position corresponding to the true class K = classCount (this.ClassNames); C = false (n, K); ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (this.ClassNames, Y); for i = 1:n class_idx = gYidx(i); C(i, class_idx) = true; endfor Y_new = C'; ## Compute the loss using custom loss function if (isa (LossFun, 'function_handle')) L = LossFun(C, scores, Weights, this.Cost); return; endif ## Compute the scalar classification score for each observation m_j = zeros (n, 1); for i = 1:n m_j(i) = scores(i,:) * Y_new(:,i); endfor ## Compute the loss switch (tolower (LossFun)) case 'binodeviance' b = log (1 + exp (-2 * m_j)); L = (Weights') * b; case 'hinge' h = max (0, 1 - m_j); L = (Weights') * h; case 'exponential' e = exp (-m_j); L = (Weights') * e; case 'logit' l = log (1 + exp (-m_j)); L = (Weights') * l; case 'quadratic' q = (1 - m_j) .^ 2; L = (Weights') * q; case 'classiferror' L = 0; for i = 1:n L = L + Weights(i) * (! isequal (Y(i), label(i))); endfor case 'mincost' Cost = this.Cost; L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (this.ClassNames, Y); for i = 1:n f_Xj = scores(i, :); gamma_jk = f_Xj * Cost; [~, min_cost_class] = min (gamma_jk); cj = Cost(gYidx(i), min_cost_class); L = L + Weights(i) * cj; endfor case 'classifcost' Cost = this.Cost; L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (this.ClassNames, Y); for i = 1:n y_idx = gYidx(i); y_hat_idx = find (ismember (this.ClassNames, label(i))); L = L + Weights(i) * Cost(y_idx, y_hat_idx); endfor otherwise error (strcat ("CompactClassificationDiscriminant.loss:", ... " invalid loss function.")); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationDiscriminant} {@var{m} =} margin (@var{obj}, @var{X}, @var{Y}) ## ## Classification margins for discriminant analysis classifier. ## ## @code{@var{m} = margin (@var{obj}, @var{X}, @var{Y})} returns ## the classification margins for @var{obj} with data @var{X} and ## classification @var{Y}. @var{m} is a numeric vector of length size (X,1). ## ## @itemize ## @item ## @code{obj} is a @var{CompactClassificationDiscriminant} object. ## @item ## @code{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} must have same ## numbers of rows as @var{X}. ## @end itemize ## ## The classification margin for each observation is the difference between ## the classification score for the true class and the maximal ## classification score for the false classes. ## ## @seealso{fitcdiscr, CompactClassificationDiscriminant} ## @end deftypefn function m = margin (this, X, Y) ## Check for sufficient input arguments if (nargin < 3) error (strcat ("CompactClassificationDiscriminant.margin:", ... " too few input arguments.")); endif ## Validate Y valid_types = {'char', 'string', 'logical', 'single', 'double', 'cell'}; if (! (any (strcmp (class (Y), valid_types)))) error (strcat ("CompactClassificationDiscriminant.margin:", ... " Y must be of a valid type.")); endif ## Validate X valid_types = {'single', 'double'}; if (! (any (strcmp (class (X), valid_types)))) error (strcat ("CompactClassificationDiscriminant.margin:", ... " X must be of a valid type.")); endif ## Validate size of Y if (size (Y, 1) != size (X, 1)) error (strcat ("CompactClassificationDiscriminant.margin: Y must", ... " have the same number of rows as X.")); endif ## Convert Y to a cell array of strings if (ischar (Y)) Y = cellstr (Y); elseif (isnumeric (Y)) Y = cellstr (num2str (Y)); elseif (islogical (Y)) Y = cellstr (num2str (double (Y))); elseif (iscell (Y)) Y = cellfun (@num2str, Y, 'UniformOutput', false); else error (strcat ("CompactClassificationDiscriminant.margin: Y must", ... " be a numeric, logical, char, string, or cell array.")); endif ## Check if Y contains correct classes if (! labelsKnown (Y, this.ClassNames)) error (strcat ("CompactClassificationDiscriminant.margin: Y must", ... " contain only the classes in ClassNames.")); endif ## Number of Observations n = size (X, 1); ## Initialize the margin vector m = zeros (n, 1); ## Calculate the classification scores [~, scores] = predict (this, X); ## Loop over each observation to compute the margin ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (this.ClassNames, Y); for i = 1:n ## True class index true_class_idx = gYidx(i); ## Score for the true class true_class_score = scores(i, true_class_idx); ## Get the maximal score for the false classes scores(i, true_class_idx) = -Inf; # Temporarily max_false_class_score = max (scores(i, :)); if (max_false_class_score == -Inf) m = NaN; return; endif scores(i, true_class_idx) = true_class_score; # Restore ## Calculate the margin m(i) = true_class_score - max_false_class_score; endfor endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationDiscriminant} {@var{e} =} edge (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactClassificationDiscriminant} {@var{e} =} edge (@dots{}, @qcode{"Weights"}, @var{w}) ## ## Classification edge, the mean of the classification margins. ## ## @code{@var{e} = edge (@var{obj}, @var{X}, @var{Y})} reduces the vector ## that @code{margin} returns to a single number, the mean margin over the ## rows of @var{X}. It says how far the model puts the true class ahead of ## its nearest rival on average, so a larger edge is a better model, and ## unlike a loss it is not bounded above and rewards confidence rather than ## bare correctness. ## ## @code{@var{e} = edge (@dots{}, @qcode{"Weights"}, @var{w})} takes the ## weighted mean instead, with one weight per row of @var{X}. ## ## @end deftypefn function e = edge (this, X, Y, varargin) if (nargin < 3) error (strcat ("CompactClassificationDiscriminant.edge: too few", ... " input arguments.")); endif if (mod (numel (varargin), 2) != 0) error (strcat ("CompactClassificationDiscriminant.edge: Name-Value", ... " arguments must be in pairs.")); endif ## The weights are parsed before anything is computed, so a bad ## Name-Value pair is reported as such rather than after a margin. W = edgeWeights (varargin, Y, this.ClassNames, this.Prior, ... "CompactClassificationDiscriminant", "edge"); m = margin (this, X, Y); e = sum (W .* m(:)) / sum (W); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationDiscriminant} {@var{M} =} mahal (@var{obj}, @var{X}) ## @deftypefnx {CompactClassificationDiscriminant} {@var{M} =} mahal (@dots{}, @qcode{'ClassLabels'}, @var{labels}) ## ## Squared Mahalanobis distance to the class means. ## ## @code{@var{M} = mahal (@var{obj}, @var{X})} returns an @math{NxK} ## matrix whose element @math{(i,j)} is the squared Mahalanobis distance ## from observation @math{i} to the mean of class @math{j}, measured ## against the covariance that class carries: the one shared covariance ## for a linear discriminant and the class's own for a quadratic one. ## ## @itemize ## @item ## @var{obj} must be a @qcode{CompactClassificationDiscriminant} object. ## @item ## @var{X} must be an @math{NxP} numeric matrix with one column per ## predictor of the trained model. ## @end itemize ## ## @code{@var{M} = mahal (@dots{}, @qcode{'ClassLabels'}, @var{labels})} ## returns an @math{Nx1} vector instead, holding for each observation the ## distance to the mean of the class @var{labels} names for it. ## @var{labels} must have one entry per row of @var{X}, each of them one ## of @code{ClassNames}. ## ## The distance is measured against the covariance the model reports, so ## a regularized model is measured against its regularized covariance. ## The prior does not enter it. ## ## @end deftypefn function M = mahal (this, X, varargin) ## Check for sufficient input arguments if (nargin < 2) error (strcat ("CompactClassificationDiscriminant.mahal:", ... " too few input arguments.")); endif ## Check for valid X if (isempty (X)) error (strcat ("CompactClassificationDiscriminant.mahal:", ... " X is empty.")); elseif (! (isnumeric (X) && isreal (X))) error (strcat ("CompactClassificationDiscriminant.mahal:", ... " X must be a real numeric matrix.")); elseif (this.NumPredictors != columns (X)) error (strcat ("CompactClassificationDiscriminant.mahal:", ... " X must have the same number of predictors as the", ... " trained model.")); endif if (mod (numel (varargin), 2) != 0) error (strcat ("CompactClassificationDiscriminant.mahal:", ... " Name-Value arguments must be in pairs.")); endif labels = []; while (numel (varargin) > 0) if (! (ischar (varargin{1}) && isrow (varargin{1}))) error (strcat ("CompactClassificationDiscriminant.mahal:", ... " parameter name must be a character vector.")); endif switch (tolower (varargin{1})) case 'classlabels' labels = varargin{2}; otherwise error (strcat ("CompactClassificationDiscriminant.mahal:", ... " invalid parameter name in optional paired", ... " arguments.")); endswitch varargin(1:2) = []; endwhile M = discrimmahal (X, this.Mu, this.Sigma, this.DiscrimType); if (isempty (labels)) return; endif ## One distance per observation, to the mean of the class named for it if (ischar (labels)) labels = cellstr (labels); elseif (isnumeric (labels) || islogical (labels)) labels = cellstr (num2str (labels(:))); elseif (! iscellstr (labels)) error (strcat ("CompactClassificationDiscriminant.mahal:", ... " 'ClassLabels' must be of a valid type.")); endif if (numel (labels) != rows (X)) error (strcat ("CompactClassificationDiscriminant.mahal:", ... " 'ClassLabels' must have one entry per row of X.")); endif classes = this.ClassNames; if (isnumeric (classes) || islogical (classes)) classes = cellstr (num2str (classes(:))); elseif (ischar (classes)) classes = cellstr (classes); endif [tf, idx] = ismember (strtrim (labels(:)), strtrim (classes)); if (! all (tf)) error (strcat ("CompactClassificationDiscriminant.mahal:", ... " every 'ClassLabels' entry must be one of", ... " ClassNames.")); endif M = M(sub2ind (size (M), (1:rows (X))', idx)); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationDiscriminant} {@var{lp} =} logp (@var{obj}, @var{X}) ## ## Log unconditional probability density of the observations. ## ## @code{@var{lp} = logp (@var{obj}, @var{X})} returns an @math{Nx1} ## vector holding, for each row of @var{X}, the natural logarithm of ## @math{P(x) = sum_k P(k) P(x|k)}, the density of the observation summed ## over the classes with each class weighted by its prior @math{P(k)}. ## Each @math{P(x|k)} is the multivariate normal density of class ## @math{k}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{CompactClassificationDiscriminant} object. ## @item ## @var{X} must be an @math{NxP} numeric matrix with one column per ## predictor of the trained model. ## @end itemize ## ## An unusually low value marks an observation the model finds unlikely ## under every class, which is what makes this an outlier test rather ## than a classification. ## ## @end deftypefn function lp = logp (this, X) ## Check for sufficient input arguments if (nargin < 2) error (strcat ("CompactClassificationDiscriminant.logp:", ... " too few input arguments.")); endif ## Check for valid X if (isempty (X)) error (strcat ("CompactClassificationDiscriminant.logp:", ... " X is empty.")); elseif (! (isnumeric (X) && isreal (X))) error (strcat ("CompactClassificationDiscriminant.logp:", ... " X must be a real numeric matrix.")); elseif (this.NumPredictors != columns (X)) error (strcat ("CompactClassificationDiscriminant.logp:", ... " X must have the same number of predictors as the", ... " trained model.")); endif lp = discrimlogp (X, this.Mu, this.Sigma, this.LogDetSigma, ... this.Prior, this.DiscrimType); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationDiscriminant} {} savemodel (@var{obj}, @var{filename}) ## ## Save a CompactClassificationDiscriminant object. ## ## @code{savemodel (@var{obj}, @var{filename})} saves each property of a ## CompactClassificationDiscriminant object into an Octave binary file, the ## name of which is specified in @var{filename}, along with an extra ## variable, which defines the type classification object these variables ## constitute. Use @code{loadmodel} in order to load a classification object ## into Octave's workspace. ## ## @seealso{loadmodel, fitcdiscr, ClassificationDiscriminant} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error ("CompactClassificationDiscriminant.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error ("CompactClassificationDiscriminant.savemodel: FNAME must be a character vector."); endif ## Generate variable for class name classdef_name = 'CompactClassificationDiscriminant'; ## Create variables from model properties NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; ResponseName = this.ResponseName; ClassNames = this.ClassNames; Prior = this.Prior; Cost = this.Cost; ScoreTransform = this.ScoreTransform; STfun = this.STfun; BetweenSigma = this.BetweenSigma; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; Sigma = this.Sigma; BaseSigma = this.BaseSigma; Mu = this.Mu; Coeffs = this.Coeffs; Delta = this.Delta; DiscrimType = this.DiscrimType; Gamma = this.Gamma; MinGamma = this.MinGamma; LogDetSigma = this.LogDetSigma; ## Save classdef name and all model properties as individual variables save ('-binary', fname, 'classdef_name', 'NumPredictors', ... 'PredictorNames', 'ResponseName', 'ClassNames', 'Prior', ... 'Cost', 'ScoreTransform', 'STfun', 'Sigma', 'BaseSigma', ... 'Mu', 'Coeffs', ... 'Delta', 'DiscrimType', 'Gamma', 'MinGamma', 'LogDetSigma', ... 'BetweenSigma', 'CategoricalPredictors', ... 'ExpandedPredictorNames'); endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a CompactClassificationDiscriminant object mdl = CompactClassificationDiscriminant (); ## Copy the saved data into the object. Iterate over what was ## saved rather than over fieldnames (mdl): a private property such ## as STfun is written out by savemodel but is not reported by ## fieldnames, so comparing the two sets could never match and every ## load failed. Assignment is legal here because this is a method of ## the class itself. names = fieldnames (data); ## The set methods for these read other properties, and some of them ## rebuild Sigma or Coeffs, so they are assigned once everything else is ## in place rather than in the order the file happens to list them. ## Order matters within the late group as well: Prior and Cost before ## DiscrimType, because assigning the type rebuilds Coeffs and Coeffs ## reads the priors; and DiscrimType before Gamma, because assigning the ## type implies a Gamma and would overwrite the saved one if it came ## second. Coeffs is installed last of all: the set methods rebuild it ## whenever it is already there, and rebuilding it from a half-loaded ## model is how a diagonal Sigma met a linear formula. order = {'Cost', 'Prior', 'ScoreTransform', 'ResponseTransform', ... 'DiscrimType', 'Gamma', 'Delta', 'Coeffs'}; late = ismember (names, order); tail = {}; for k = 1:numel (order) if (any (strcmp (names, order{k}))) tail{end+1} = order{k}; endif endfor names = [names(! late); tail(:)]; for i = 1:numel (names) try mdl.(names{i}) = data.(names{i}); catch msg = strcat ("CompactClassificationDiscriminant.load_model:", ... " invalid model in '%s'."); error (msg, filename); end_try_catch endfor endfunction endmethods endclassdef %!demo %! ## Create a discriminant analysis classifier and its compact version %! # and compare their size %! %! load fisheriris %! X = meas; %! Y = species; %! %! Mdl = fitcdiscr (X, Y, 'ClassNames', unique (species)) %! CMdl = crossval (Mdl) ## Test constructor %!test %! load fisheriris %! x = meas; %! y = species; %! PredictorNames = {'Sepal Length', 'Sepal Width', 'Petal Length', 'Petal Width'}; %! Mdl = fitcdiscr (x, y, 'PredictorNames', PredictorNames); %! CMdl = compact (Mdl); %! sigma = [0.265008, 0.092721, 0.167514, 0.038401; ... %! 0.092721, 0.115388, 0.055244, 0.032710; ... %! 0.167514, 0.055244, 0.185188, 0.042665; ... %! 0.038401, 0.032710, 0.042665, 0.041882]; %! mu = [5.0060, 3.4280, 1.4620, 0.2460; ... %! 5.9360, 2.7700, 4.2600, 1.3260; ... %! 6.5880, 2.9740, 5.5520, 2.0260]; %! xCentered = [ 9.4000e-02, 7.2000e-02, -6.2000e-02, -4.6000e-02; ... %! -1.0600e-01, -4.2800e-01, -6.2000e-02, -4.6000e-02; ... %! -3.0600e-01, -2.2800e-01, -1.6200e-01, -4.6000e-02]; %! assert_equal (class (CMdl), "CompactClassificationDiscriminant"); %! assert_equal ({CMdl.DiscrimType, CMdl.ResponseName}, {'linear', 'Y'}) %! assert_equal ({CMdl.Gamma, CMdl.MinGamma}, {0, 0}, 1e-15) %! assert_equal (CMdl.ClassNames, unique (species)) %! assert_equal (CMdl.Sigma, sigma, 1e-6) %! assert_equal (CMdl.Mu, mu, 1e-14) %! assert_equal (CMdl.LogDetSigma, -9.9585, 1e-4) %! assert_equal (CMdl.PredictorNames, PredictorNames) %!test %! load fisheriris %! x = meas; %! y = species; %! Mdl = fitcdiscr (x, y, 'Gamma', 0.5); %! CMdl = compact (Mdl); %! sigma = [0.265008, 0.046361, 0.083757, 0.019201; ... %! 0.046361, 0.115388, 0.027622, 0.016355; ... %! 0.083757, 0.027622, 0.185188, 0.021333; ... %! 0.019201, 0.016355, 0.021333, 0.041882]; %! mu = [5.0060, 3.4280, 1.4620, 0.2460; ... %! 5.9360, 2.7700, 4.2600, 1.3260; ... %! 6.5880, 2.9740, 5.5520, 2.0260]; %! xCentered = [ 9.4000e-02, 7.2000e-02, -6.2000e-02, -4.6000e-02; ... %! -1.0600e-01, -4.2800e-01, -6.2000e-02, -4.6000e-02; ... %! -3.0600e-01, -2.2800e-01, -1.6200e-01, -4.6000e-02]; %! assert_equal (class (CMdl), "CompactClassificationDiscriminant"); %! assert_equal ({CMdl.DiscrimType, CMdl.ResponseName}, {'linear', 'Y'}) %! assert_equal ({CMdl.Gamma, CMdl.MinGamma}, {0.5, 0}) %! assert_equal (CMdl.ClassNames, unique (species)) %! assert_equal (CMdl.Sigma, sigma, 1e-6) %! assert_equal (CMdl.Mu, mu, 1e-14) %! assert_equal (CMdl.LogDetSigma, -8.6884, 1e-4) ## Test input validation for constructor ## nLinearCoeffs counts the predictors a threshold keeps. R2024a on ## fisheriris reports 4 at the model's own Delta of zero. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! assert_equal (nLinearCoeffs (Mdl), 4); ## The comparison includes equality, which only a threshold sitting exactly ## on a DeltaPredictor can show: R2024a returns 4, 3, 2, 1 at the four ## sorted values, where a strict comparison would return 3, 2, 1, 0. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! dps = sort (Mdl.DeltaPredictor); %! assert_equal (nLinearCoeffs (Mdl, dps), [4; 3; 2; 1]); ## A threshold above every DeltaPredictor keeps nothing, and the result is a ## column however the thresholds were shaped. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! assert_equal (nLinearCoeffs (Mdl, [0, 1e6]), [4; 0]); %! assert_equal (size (nLinearCoeffs (Mdl, [0, 1, 2])), [3, 1]); ## The count is taken whatever the DiscrimType, as MATLAB takes it, even ## though Delta regularizes the linear types alone. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species, "DiscrimType", "quadratic")); %! assert_equal (nLinearCoeffs (Mdl), 4); ## A compact model keeps the character class names and predicts whole names. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Cc = compact (fitcdiscr (Xch, Ych)); %! rand ("state", 1); randn ("state", 1); Cs = compact (fitcdiscr (Xch, Ycell)); %! assert_equal (cellstr (Cc.ClassNames), Cs.ClassNames); %! assert_equal (cellstr (predict (Cc, Xch)), predict (Cs, Xch)); ## and reads one back in its assessment methods. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Cc = compact (fitcdiscr (Xch, Ych)); %! rand ("state", 1); randn ("state", 1); Cs = compact (fitcdiscr (Xch, Ycell)); %! assert_equal (loss (Cc, Xch, Ych), loss (Cs, Xch, Ycell), 1e-12); %!error ... %! load fisheriris %! nLinearCoeffs (compact (fitcdiscr (meas, species)), "a") %!error ... %! CompactClassificationDiscriminant (1) ## Test predict method %!test %! load fisheriris %! x = meas; %! y = species; %! Mdl = fitcdiscr (meas, species, 'Gamma', 0.5); %! CMdl = compact (Mdl); %! [label, score, cost] = predict (CMdl, [2, 2, 2, 2]); %! assert_equal (label, {'versicolor'}) %! assert_equal (score, [0, 0.9999, 0.0001], 1e-4) %! assert_equal (cost, [1, 0.0001, 0.9999], 1e-4) %! [label, score, cost] = predict (CMdl, [2.5, 2.5, 2.5, 2.5]); %! assert_equal (label, {'versicolor'}) %! assert_equal (score, [0, 0.6368, 0.3632], 1e-4) %! assert_equal (cost, [1, 0.3632, 0.6368], 1e-4) %!test %! load fisheriris %! x = meas; %! y = species; %! xc = [min(x); mean(x); max(x)]; %! Mdl = fitcdiscr (x, y); %! CMdl = compact (Mdl); %! [label, score, cost] = predict (CMdl, xc); %! l = {'setosa'; 'versicolor'; 'virginica'}; %! s = [1, 0, 0; 0, 1, 0; 0, 0, 1]; %! c = [0, 1, 1; 1, 0, 1; 1, 1, 0]; %! assert_equal (label, l) %! assert_equal (score, s, 1e-4) %! assert_equal (cost, c, 1e-4) %!shared MODEL %! X = rand (10,2); %! Y = [ones(5,1);2*ones(5,1)]; %! MODEL = compact (ClassificationDiscriminant (X, Y)); ## Test input validation for predict method %!error ... %! predict (MODEL) %!error ... %! predict (MODEL, []) %!error ... %! predict (MODEL, 1) ## Test loss method %!test %! load fisheriris %! model = fitcdiscr (meas, species); %! x = mean (meas); %! y = {'versicolor'}; %! L = loss (model, x, y); %! assert_equal (L, 0) %!test %! x = [1, 2; 3, 4; 5, 6]; %! y = {'A'; 'B'; 'A'}; %! model = fitcdiscr (x, y, 'Gamma', 0.4); %! x_test = [1, 6; 3, 3]; %! y_test = {'A'; 'B'}; %! L = loss (model, x_test, y_test); %! assert_equal (L, 0.3333, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6; 7, 8]; %! y = ['1'; '2'; '3'; '1']; %! model = fitcdiscr (x, y, 'gamma' , 0.5); %! x_test = [3, 3]; %! y_test = ['1']; %! L = loss (model, x_test, y_test, 'LossFun', 'quadratic'); %! assert_equal (L, 0.2423, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6; 7, 8]; %! y = ['1'; '2'; '3'; '1']; %! model = fitcdiscr (x, y, 'gamma' , 0.5); %! x_test = [3, 3; 5, 7]; %! y_test = ['1'; '2']; %! L = loss (model, x_test, y_test, 'LossFun', 'classifcost'); %! assert_equal (L, 0.3333, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6; 7, 8]; %! y = ['1'; '2'; '3'; '1']; %! model = fitcdiscr (x, y, 'gamma' , 0.5); %! x_test = [3, 3; 5, 7]; %! y_test = ['1'; '2']; %! L = loss (model, x_test, y_test, 'LossFun', 'hinge'); %! assert_equal (L, 0.5886, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6; 7, 8]; %! y = ['1'; '2'; '3'; '1']; %! model = fitcdiscr (x, y, 'gamma' , 0.5); %! x_test = [3, 3; 5, 7]; %! y_test = ['1'; '2']; %! W = [1; 2]; %! L = loss (model, x_test, y_test, 'LossFun', 'logit', 'Weights', W); %! assert_equal (L, 0.5107, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6]; %! y = {'A'; 'B'; 'A'}; %! model = fitcdiscr (x, y, 'gamma' , 0.5); %! x_with_nan = [1, 2; NaN, 4]; %! y_test = {'A'; 'B'}; %! L = loss (model, x_with_nan, y_test); %! assert_equal (L, 0.3333, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6]; %! y = {'A'; 'B'; 'A'}; %! model = fitcdiscr (x, y); %! x_with_nan = [1, 2; NaN, 4]; %! y_test = {'A'; 'B'}; %! L = loss (model, x_with_nan, y_test, 'LossFun', 'logit'); %! assert_equal (isnan (L), true) %!test %! x = [1, 2; 3, 4; 5, 6]; %! y = {'A'; 'B'; 'A'}; %! model = fitcdiscr (x, y); %! customLossFun = @(C, S, W, Cost) sum (W .* sum (abs (C - S), 2)); %! L = loss (model, x, y, 'LossFun', customLossFun); %! assert_equal (L, 0.8889, 1e-4) %!test %! x = [1, 2; 3, 4; 5, 6]; %! y = [1; 2; 1]; %! model = fitcdiscr (x, y); %! L = loss (model, x, y, 'LossFun', 'classiferror'); %! assert_equal (L, 0.3333, 1e-4) ## Test input validation for loss method %!error ... %! loss (MODEL) %!error ... %! loss (MODEL, ones (4,2)) %!error ... %! loss (MODEL, ones (4,2), ones (4,1), 'LossFun') %!error ... %! loss (MODEL, ones (4,2), ones (3,1)) %!error ... %! loss (MODEL, ones (4,2), ones (4,1), 'LossFun', 'a') %!error ... %! loss (MODEL, ones (4,2), ones (4,1), 'Weights', 'w') ## Test margin method %! load fisheriris %! mdl = fitcdiscr (meas, species); %! X = mean (meas); %! Y = {'versicolor'}; %! m = margin (mdl, X, Y); %! assert_equal (m, 1, 1e-6) %!test %! X = [1, 2; 3, 4; 5, 6]; %! Y = [1; 2; 1]; %! mdl = fitcdiscr (X, Y, 'gamma', 0.5); %! m = margin (mdl, X, Y); %! assert_equal (m, [0.3333; -0.3333; 0.3333], 1e-4) ## Test input validation for margin method %!error ... %! margin (MODEL) %!error ... %! margin (MODEL, ones (4,2)) %!error ... %! margin (MODEL, ones (4,2), ones (3,1)) %!error ... %! savemodel (CompactClassificationDiscriminant ()) %!error ... %! savemodel (CompactClassificationDiscriminant (), 1) %!error ... %! savemodel (CompactClassificationDiscriminant (), ['ab'; 'cd']) ## A ScoreTransform can be assigned, and is stored as a function handle. %!test %! load fisheriris %! CMdl = compact (fitcdiscr (meas, species)); %! CMdl.ScoreTransform = 'symmetric'; %! assert_equal (class (CMdl.ScoreTransform), 'char'); %! assert_equal (CMdl.ScoreTransform, 'symmetric'); ## Prior and Cost are settable on the compact model, as they are on the full ## one, and an unnormalized prior is rescaled. %!test %! load fisheriris %! CMdl = compact (fitcdiscr (meas, species)); %! CMdl.Prior = [2, 3, 5]; %! assert_equal (CMdl.Prior, [0.2, 0.3, 0.5], 1e-14); %! CMdl.Cost = [0, 2, 3; 1, 0, 1; 1, 1, 0]; %! assert_equal (CMdl.Cost, [0, 2, 3; 1, 0, 1; 1, 1, 0]); ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'CompactClassificationDiscriminant'); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ScoreTransform), class (Mdl.ScoreTransform)); %! assert_equal (predict (M2, meas(1:5,:)), predict (Mdl, meas(1:5,:))); ## The compact model carries the discriminant type and answers identically to ## the full one, for every type. Values measured on MATLAB R2024a; see ## DISCRIMINANT_LEDGER.md. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'quadratic'); %! CMdl = compact (Mdl); %! assert_equal (CMdl.DiscrimType, 'quadratic'); %! assert_equal (size (CMdl.Sigma), [4, 4, 3]); %! assert_equal (predict (CMdl, meas), predict (Mdl, meas)); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'diagQuadratic'); %! CMdl = compact (Mdl); %! assert_equal (size (CMdl.Sigma), [1, 4, 3]); %! assert_equal (predict (CMdl, meas), predict (Mdl, meas)); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'pseudoLinear'); %! CMdl = compact (Mdl); %! assert_equal (CMdl.LogDetSigma, -9.9585, 1e-4); %! assert_equal (predict (CMdl, meas), predict (Mdl, meas)); ## The type is assignable on a compact model too, under the same family rule, ## which it can only do because the fit's covariance came across with it. %!test %! load fisheriris %! CMdl = compact (fitcdiscr (meas, species, 'DiscrimType', 'quadratic')); %! CMdl.DiscrimType = 'diagQuadratic'; %! assert_equal (size (CMdl.Sigma), [1, 4, 3]); %! assert_equal (CMdl.Gamma, 1); %!test %! load fisheriris %! CMdl = compact (fitcdiscr (meas, species)); %! CMdl.Gamma = 1; %! assert_equal (CMdl.DiscrimType, 'diagLinear'); %! assert_equal (size (CMdl.Sigma), [1, 4]); %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'DiscrimType', 'diagQuadratic'); %! CMdl = compact (Mdl); %! fname = tempname (); %! savemodel (CMdl, fname); %! C2 = loadmodel (fname); %! delete (fname); %! assert_equal (predict (C2, meas), predict (CMdl, meas)); %! C2.DiscrimType = 'quadratic'; %! assert_equal (size (C2.Sigma), [4, 4, 3]); %!error ... %! load fisheriris; ... %! CMdl = compact (fitcdiscr (meas, species, 'DiscrimType', 'quadratic')); ... %! CMdl.DiscrimType = 'linear'; %!error ... %! load fisheriris; ... %! CMdl = compact (fitcdiscr (meas, species, 'DiscrimType', 'quadratic')); ... %! CMdl.Gamma = 0.5; %!error ... %! load fisheriris; ... %! CMdl = compact (fitcdiscr (meas, species, 'DiscrimType', 'quadratic')); ... %! CMdl.Delta = 0.5; %!error ... %! load fisheriris; ... %! CMdl = compact (fitcdiscr (meas, species)); CMdl.Delta = -1; ## edge, measured on MATLAB R2024a, whose discriminant scores this class ## reproduces exactly. %!test %! load fisheriris %! CMdl = compact (fitcdiscr (meas, species)); %! assert_equal (edge (CMdl, meas, species), 0.9454289377, 1e-9); %! assert_equal (edge (CMdl, meas, species), ... %! mean (margin (CMdl, meas, species)), 1e-12); ## Weights are normalized within each class to that class's prior. %!test %! load fisheriris %! CMdl = compact (fitcdiscr (meas, species)); %! assert_equal (edge (CMdl, meas, species, 'Weights', (1:150)'), ... %! 0.9438468986, 1e-9); %!error ... %! load fisheriris; edge (compact (fitcdiscr (meas, species)), meas) ## Both carry across to the compact form, and survive a round trip. %!test %! load fisheriris %! CMdl = compact (fitcdiscr (meas, species)); %! assert_equal (CMdl.CategoricalPredictors, []); %! assert_equal (CMdl.ExpandedPredictorNames, CMdl.PredictorNames); %! assert_equal (size (CMdl.ExpandedPredictorNames), [1, 4]); %!test %! load fisheriris %! CMdl = compact (fitcdiscr (meas, species)); %! fname = tempname (); %! savemodel (CMdl, fname); %! C2 = loadmodel (fname); %! delete (fname); %! assert_equal (C2.CategoricalPredictors, CMdl.CategoricalPredictors); %! assert_equal (C2.ExpandedPredictorNames, CMdl.ExpandedPredictorNames); ## BetweenSigma comes across with the compact form and survives a round trip. %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species); %! CMdl = compact (Mdl); %! assert_equal (CMdl.BetweenSigma, Mdl.BetweenSigma); %! assert_equal (CMdl.BetweenSigma(1,1), 0.632121333333333, 1e-12); %!test %! load fisheriris %! CMdl = compact (fitcdiscr (meas, species)); %! fname = tempname (); %! savemodel (CMdl, fname); %! C2 = loadmodel (fname); %! delete (fname); %! assert_equal (C2.BetweenSigma, CMdl.BetweenSigma); %!error ... %! load fisheriris %! CMdl = compact (fitcdiscr (meas, species)); %! CMdl.Cost = 1:9; ## mahal is the squared distance to every class mean. Measured on R2024a. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! M = mahal (Mdl, meas(1:5,:)); %! assert_equal (size (M), [5, 3]); %! assert_equal (M(1,:), [0.2910898404344, 98.8847494279393, ... %! 191.788642179719], 1e-10); %! assert_equal (M(5,:), [0.5956300391717, 100.923170228959, ... %! 193.854037009331], 1e-10); ## 'ClassLabels' picks one class mean per observation. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! M = mahal (Mdl, meas(1:5,:), 'ClassLabels', species(1:5)); %! assert_equal (size (M), [5, 1]); %! assert_equal (M, [0.291089840434356; 2.031345104042097; ... %! 0.553281423559203; 2.086697905677364; ... %! 0.595630039171744], 1e-12); ## A quadratic model measures against each class's own covariance. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species, 'DiscrimType', 'quadratic')); %! M = mahal (Mdl, meas(1:5,:)); %! assert_equal (M(1,:), [0.4491137892273, 114.804489260461, ... %! 182.935908699285], 1e-10); ## The distance follows the regularized covariance the model reports. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species, 'Gamma', 0.5)); %! M = mahal (Mdl, meas(1:5,:)); %! assert_equal (M(1,:), [0.1926136709121, 79.4757202663154, ... %! 163.294484902038], 1e-10); ## The prior does not enter the distance. %!test %! load fisheriris %! M1 = mahal (compact (fitcdiscr (meas, species)), meas(1:5,:)); %! Mdl = compact (fitcdiscr (meas, species, 'Prior', [0.6, 0.2, 0.2])); %! assert_equal (mahal (Mdl, meas(1:5,:)), M1, 1e-12); ## logp is the log density summed over the classes at their priors. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! lp = logp (Mdl, meas(1:5,:)); %! assert_equal (size (lp), [5, 1]); %! assert_equal (lp, [0.059358043320009; -0.810769588483861; ... %! -0.071737748242414; -0.838445989301495; ... %! -0.092912056048684], 1e-12); ## logp on a quadratic model. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species, 'DiscrimType', 'quadratic')); %! lp = logp (Mdl, meas(1:5,:)); %! assert_equal (lp, [1.534756847193468; 0.718766664165731; ... %! 1.117146185488189; 0.906210252665867; ... %! 1.378471050607217], 1e-12); ## The prior reweights the density, unlike the distance. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species, 'Prior', [0.6, 0.2, 0.2])); %! lp = logp (Mdl, meas(1:5,:)); %! assert_equal (lp, [0.647144708222129; -0.222982923581741; ... %! 0.516048916659706; -0.250659324399375; ... %! 0.494874608853435], 1e-12); %!error ... %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! mahal (Mdl, []) %!error ... %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! mahal (Mdl, ones (3, 2)) %!error ... %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! mahal (Mdl, {1, 2, 3, 4}) %!error ... %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! mahal (Mdl, meas(1:5,:), 'ClassLabels') %!error ... %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! mahal (Mdl, meas(1:5,:), 5, 1) %!error ... %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! mahal (Mdl, meas(1:5,:), 'bogus', 1) %!error ... %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! mahal (Mdl, meas(1:5,:), 'ClassLabels', species(1:3)) %!error ... %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! mahal (Mdl, meas(1:2,:), 'ClassLabels', {'setosa'; 'nosuchspecies'}) %!error ... %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! logp (Mdl, []) %!error ... %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! logp (Mdl, ones (3, 2)) %!error ... %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! logp (Mdl, {1, 2, 3, 4}) ## The shared cost guard is in force here too, and the struct form is ## permuted into this model's class order. The battery is on ## ClassificationDiscriminant. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! S = struct ('ClassNames', {{'virginica'; 'setosa'; 'versicolor'}}, ... %! 'ClassificationCosts', [0, 1, 2; 3, 0, 4; 5, 6, 0]); %! Mdl.Cost = S; %! assert_equal (Mdl.Cost, [0, 4, 3; 6, 0, 5; 1, 2, 0]); %!error ... %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! Mdl.Cost = ones (3); ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = compact (fitcdiscr (meas, species)); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/CompactClassificationGAM.m000066400000000000000000001514471524624707500272550ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef CompactClassificationGAM ## -*- texinfo -*- ## @deftp {statistics} CompactClassificationGAM ## ## Compact generalized additive model classification ## ## The @code{CompactClassificationGAM} class is a compact version of a ## Generalized Additive Model classifier, @code{ClassificationGAM}. It does ## not include the training data, resulting in a smaller classifier size that ## can be used for making predictions from new data, but not for tasks such as ## cross validation. ## ## A @code{CompactClassificationGAM} object can only be created from a ## @code{ClassificationGAM} model by using the @code{compact} method. ## ## The engine that fitted the model is carried over in @code{FitMethod}, ## and the compact model predicts by the same scheme the full one did. ## Under @qcode{'boostedtrees'}, the default, the fit is described by ## @code{TreeModel}, @code{BinEdges} and @code{PairDetectionBinEdges}. ## Under @qcode{'splines'} it is described by @code{Formula}, ## @code{BaseModel}, @code{ModelwInt} and @code{IntMatrix}, which MATLAB's ## compact model does not carry. Whichever fitted the model, the other ## set is empty. ## ## @seealso{ClassificationGAM, fitcgam} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} NumPredictors ## ## Number of predictors ## ## A positive integer value specifying the number of predictors in the ## training dataset used for training the ClassificationGAM model. ## This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} PredictorNames ## ## Names of predictor variables ## ## A cell array of character vectors specifying the names of the predictor ## variables. The names are in the order in which they appear in the ## training dataset. This property is read-only. ## ## @end deftp PredictorNames = []; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} ResponseName ## ## Response variable name ## ## A character vector specifying the name of the response variable @var{Y}. ## This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} ClassNames ## ## Names of classes in the response variable ## ## An array of unique values of the response variable @var{Y}, which has the ## same data types as the data in @var{Y}. This property is read-only. ## @qcode{ClassNames} can have any of the following datatypes: ## ## @itemize ## @item Cell array of character vectors ## @item Character array ## @item Logical vector ## @item Numeric vector ## @end itemize ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} Prior ## ## Prior probability for each class ## ## A 2-element numeric vector specifying the prior probabilities for each ## class. The order of the elements in @qcode{Prior} corresponds to the ## order of the classes in @qcode{ClassNames}. This property is read-only. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} Formula ## ## Model specification formula ## ## A character vector specifying the model formula in the form ## @qcode{'Y ~ terms'} where @qcode{Y} represents the response variable and ## @qcode{terms} specifies the predictor variables and interaction terms. ## This property is read-only. ## ## @end deftp Formula = []; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} Interactions ## ## Two-way interaction terms of the fitted model ## ## A @math{Kx2} matrix of predictor index pairs, one row per two-way term ## the model carries, and @code{zeros (0, 2)} when it carries none. It ## reports what was fitted rather than what was asked for, so a count of ## terms, @qcode{'all'}, a logical matrix and a formula all leave the same ## kind of value behind. This property is read-only. ## ## A main effect names one predictor and a higher-order term names three ## or more, and neither has a two-column form, so neither appears here. ## @code{IntMatrix} remains the complete record of every term fitted. ## ## @end deftp Interactions = zeros (0, 2); ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} BaseModel ## ## Base model parameters ## ## A structure containing the parameters of the base model without any ## interaction terms. The base model represents the generalized additive ## model with only the main effects (predictor terms) included. ## This property is read-only. ## ## @end deftp BaseModel = []; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} ModelwInt ## ## Model parameters with interactions ## ## A structure containing the parameters of the model that includes ## interaction terms. This model extends the base model by adding ## interaction terms between predictors. This property is read-only. ## ## @end deftp ModelwInt = []; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} IntMatrix ## ## Every term the model fits ## ## A logical matrix with one row per term and one column per predictor, ## true wherever the term multiplies that predictor. A row naming one ## predictor is a main effect, two an interaction, and three or more a ## higher-order term. This property is read-only. ## ## It is the complete record, where @code{Interactions} reports only the ## two-way terms, in the form MATLAB reports them. It is also the form ## the @qcode{'Interactions'} option takes back, so passing it to the ## constructor rebuilds a model over the same terms. ## ## @end deftp IntMatrix = []; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} Intercept ## ## Intercept of the fitted model ## ## A numeric scalar, the log-odds of the response mean, which every ## additive term is measured against. This property is read-only. ## ## @end deftp Intercept = []; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector holding the column of each predictor treated as ## categorical, and empty when none is. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} ExpandedPredictorNames ## ## Names of the expanded predictor variables ## ## A cell array of character vectors naming the predictors as the model ## sees them. It matches @code{PredictorNames} unless a categorical ## predictor was expanded into dummy variables. This property is ## read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} BinEdges ## ## Bin edges of the fitted shape functions ## ## A cell array with one row vector per predictor, holding the cut points ## the boosted-tree engine binned it at. It is the empty cell under the ## spline engine, which does no binning. This property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} PairDetectionBinEdges ## ## Bin edges the interaction terms are held on ## ## A cell array with one coarse row vector per predictor, empty when the ## model carries no interaction terms. This property is read-only. ## ## @end deftp PairDetectionBinEdges = []; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} FitMethod ## ## Which engine fitted the model ## ## Either @qcode{'boostedtrees'} or @qcode{'splines'}, as the model it was ## compacted from was fitted. This property is read-only. ## ## @end deftp FitMethod = 'boostedtrees'; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} TreeModel ## ## The fitted shape functions and interaction surfaces ## ## The structure the full model reports, carried over unchanged, and ## empty under the spline engine. This property is read-only. ## ## @end deftp TreeModel = []; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} Cost ## ## Cost of Misclassification ## ## A square matrix specifying the cost of misclassification of a point. ## @qcode{Cost(i,j)} is the cost of classifying a point into class @qcode{j} ## if its true class is @qcode{i} (that is, the rows correspond to the true ## class and the columns correspond to the predicted class). The order of ## the rows and columns in @qcode{Cost} corresponds to the order of the ## classes in @qcode{ClassNames}. The number of rows and columns in ## @qcode{Cost} is the number of unique classes in the response. By ## default, @qcode{Cost(i,j) = 1} if @qcode{i != j}, and ## @qcode{Cost(i,j) = 0} if @qcode{i = j}. In other words, the cost is 0 ## for correct classification and 1 for incorrect classification. ## ## Add or change the @qcode{Cost} property using dot notation as in: ## @itemize ## @item @qcode{@var{obj}.Cost = @var{costMatrix}} ## @end itemize ## ## ## A cost may also be given as a struct with the fields ## @qcode{ClassNames} and @qcode{ClassificationCosts}, which names the ## order its own matrix is written in. That matrix is permuted into the ## order of @qcode{ClassNames} above, so a caller need not know which ## order the classes were sorted into. It must name every class. ## ## A cost must be floating point, not sparse, not complex, non-negative ## and zero down its diagonal, and must hold no @qcode{NaN} or ## @qcode{Inf}. A @code{single} is widened to @code{double}. ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {CompactClassificationGAM} {property} ScoreTransform ## ## Transformation function for classification scores ## ## Specified as a function handle for transforming the classification ## scores. Add or change the @qcode{ScoreTransform} property using dot ## notation as in: ## ## @itemize ## @item @qcode{@var{obj}.ScoreTransform = 'function_name'} ## @item @qcode{@var{obj}.ScoreTransform = @@function_handle} ## @end itemize ## ## When specified as a character vector, it can be any of the following ## built-in functions. Nevertheless, the @qcode{ScoreTransform} property ## always stores their function handle equivalent. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'doublelogit'} @tab @math{1 ./ (1 + exp (-2 * x))} ## @item @qcode{'invlogit'} @tab @math{log (x ./ (1 - x))} ## @item @qcode{'ismax'} @tab Sets the score for the class with the ## largest score to 1, and for all other classes to 0 ## @item @qcode{'logit'} @tab @math{1 ./ (1 + exp (-x))} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'sign'} @tab ## @math{-1 for x < 0, 0 for x = 0, 1 for x > ## 0} ## @item @qcode{'symmetric'} @tab @math{2 * x - 1} ## @item @qcode{'symmetricismax'} @tab Sets the score for the class ## with the largest score to 1, and for all other classes to -1 ## @item @qcode{'symmetriclogit'} @tab @math{2 ./ (1 + exp (-x)) - 1} ## @end multitable ## ## The default is @qcode{'logit'}, as in MATLAB. This model's raw ## score is a log-odds, reported as the pair @math{[-f, f]} whose two ## columns sum to zero, and the transform is what turns it into the ## posterior probabilities that sum to one. Every transform therefore ## composes on the log-odds and not on the probabilities, so ## @qcode{'none'} returns the log-odds themselves. ## ## @end deftp ScoreTransform = 'logit'; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) ## Carried from the fitted model so a fold, which is stored ## compact, can still say how many trees it fitted. NumTrainedTrees = []; STfun = @(x) 1 ./ (1 + exp (-x)); endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.Cost (this, val) gnY = this.ClassNames; if (isempty (val)) this.Cost = cast (! eye (classCount (gnY)), 'double'); else ## Everything a cost must be, and the struct form, which ## is permuted into this model's class order. [val, errmsg] = costMatrix (val, gnY); if (! isempty (errmsg)) error ("CompactClassificationGAM: %s", errmsg); endif this.Cost = val; endif endfunction function this = set.ScoreTransform (this, val) [f, nm] = parseScoreTransform (val, 'CompactClassificationGAM'); this.ScoreTransform = nm; this.STfun = f; endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationGAM} {@var{obj} =} CompactClassificationGAM (@var{Mdl}) ## @deftypefnx {CompactClassificationGAM} {@var{obj} =} CompactClassificationGAM () ## ## Create a @code{CompactClassificationGAM} object. ## ## @var{Mdl} is the @code{ClassificationGAM} object to ## compact. The documented way to reach this constructor is the ## @code{compact} method. ## ## Called with no arguments it returns an object with its properties ## empty, which is how a saved model is rebuilt before its values are ## filled in. ## ## @end deftypefn function this = CompactClassificationGAM (Mdl = []) ## Check for appropriate class if (isempty (Mdl)) return; elseif (! strcmpi (class (Mdl), 'ClassificationGAM')) error ("CompactClassificationGAM: invalid classification object."); endif ## Save properties to compact model this.NumPredictors = Mdl.NumPredictors; this.PredictorNames = Mdl.PredictorNames; this.ResponseName = Mdl.ResponseName; this.ClassNames = Mdl.ClassNames; this.Cost = Mdl.Cost; this.Prior = Mdl.Prior; this.ScoreTransform = Mdl.ScoreTransform; this.STfun = Mdl.STfun; this.Formula = Mdl.Formula; this.Interactions = Mdl.Interactions; this.BaseModel = Mdl.BaseModel; this.ModelwInt = Mdl.ModelwInt; this.IntMatrix = Mdl.IntMatrix; this.Intercept = Mdl.Intercept; this.FitMethod = Mdl.FitMethod; this.TreeModel = Mdl.TreeModel; this.BinEdges = Mdl.BinEdges; this.PairDetectionBinEdges = Mdl.PairDetectionBinEdges; this.NumTrainedTrees = Mdl.NumTrainedTrees; this.CategoricalPredictors = Mdl.CategoricalPredictors; this.ExpandedPredictorNames = Mdl.ExpandedPredictorNames; endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n CompactClassificationGAM\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); if (iscellstr (this.ClassNames)) str = repmat ({'''%s'''}, 1, numel (this.ClassNames)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.ClassNames{:}); elseif (ischar (this.ClassNames)) str = repmat ({'''%s'''}, 1, rows (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, cellstr (this.ClassNames){:}); else # single, double, logical str = repmat ({'%d'}, 1, numel (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.ClassNames); endif fprintf ("%+25s: %s\n", 'ClassNames', str); fprintf ("%+25s: '%s'\n", 'ScoreTransform', this.ScoreTransform); fprintf ("%+25s: %d\n", 'NumObservations', this.NumObservations); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); if (! isempty (this.Formula)) fprintf ("%+25s: '%s'\n", 'Formula', this.Formula); endif if (! isempty (this.Interactions)) fprintf ("%+25s: [%dx%d %s]\n", 'Interactions', ... size (this.Interactions, 1), size (this.Interactions, 2), ... class (this.Interactions)); endif endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {CompactClassificationGAM} {@var{label} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {CompactClassificationGAM} {[@var{label}, @var{score}] =} predict (@var{obj}, @var{XC}) ## @deftypefnx {CompactClassificationGAM} {[@var{label}, @var{score}] =} predict (@dots{}, @qcode{'IncludeInteractions'}, @var{includeInteractions}) ## ## Predict labels for new data using the Generalized Additive Model (GAM) ## stored in a CompactClassificationGAM object. ## ## @code{@var{label} = predict (@var{obj}, @var{XC})} returns the predicted ## labels for the data in @var{XC} based on the model stored in the ## CompactClassificationGAM object, @var{obj}. ## ## @code{[@var{label}, @var{score}] = predict (@var{obj}, @var{XC})} also ## returns @var{score}, which contains the predicted class scores or ## posterior probabilities for each observation. ## ## @code{[@var{label}, @var{score}] = predict (@var{obj}, @var{XC}, ## 'IncludeInteractions', @var{includeInteractions})} allows you to specify ## whether interaction terms should be included when making predictions. ## ## @itemize ## @item ## @var{obj} must be a @qcode{CompactClassificationGAM} class object. ## @item ## @var{XC} must be an @math{M*P} numeric matrix where each row is an ## observation and each column corresponds to a predictor variable. ## @item ## @var{includeInteractions} is a logical scalar indicating whether to ## include interaction terms in the predictions. ## @end itemize ## ## @seealso{CompactClassificationGAM, ClassificationGAM, fitcgam} ## @end deftypefn function [labels, scores] = predict (this, XC, varargin) ## Check for sufficient input arguments if (nargin < 2) error ("CompactClassificationGAM.predict: too few input arguments."); endif ## Check for valid XC if (isempty (XC)) error ("CompactClassificationGAM.predict: XC is empty."); elseif (this.NumPredictors != columns (XC)) error (strcat ("CompactClassificationGAM.predict: XC must have", ... " the same number of features as the trained model.")); endif ## Clean XC data notnansf = ! logical (sum (isnan (XC), 2)); XC = XC(notnansf, :); ## Default values for Name-Value Pairs ## Which store holds the interaction terms depends on the engine: the ## spline scheme keeps them as extra columns described by IntMatrix, ## the boosted-tree scheme as surfaces over predictor pairs. hasInt = ! isempty (this.IntMatrix); if (strcmp (this.FitMethod, 'boostedtrees') && ! isempty (this.TreeModel)) hasInt = ! isempty (this.TreeModel.Pairs); endif incInt = hasInt; Cost = this.Cost; ## Parse optional arguments while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'includeinteractions' tmpInt = varargin{2}; if (! islogical (tmpInt) || (tmpInt != 0 && tmpInt != 1)) error (strcat ("CompactClassificationGAM.predict:", ... " includeinteractions must be a logical value.")); endif ## Check model for interactions if (tmpInt && ! hasInt) error (strcat ("CompactClassificationGAM.predict: trained", ... " model does not include any interactions.")); endif incInt = tmpInt; otherwise error (strcat ("CompactClassificationGAM.predict: invalid", ... " NAME in optional pairs of arguments.")); endswitch varargin(1:2) = []; endwhile ## The boosted-tree engine keeps its fit as step functions over bins, so ## a term is a lookup rather than a spline evaluation and the whole ## prediction is one call. Everything after it is shared: the cost ## matrix, the label, and the transform. ## An empty TreeModel means no tree fit is present, whatever FitMethod ## says: a default-constructed object has one, and so would a model ## saved before the engine existed. Such an object falls through to ## the spline path rather than indexing into nothing. if (strcmp (this.FitMethod, 'boostedtrees') && ! isempty (this.TreeModel)) ## Excluding the interactions means excluding the constant they ## handed the intercept as well. interc = this.Intercept; if (! incInt && isfield (this.TreeModel, 'PairIntercept')) interc = interc - this.TreeModel.PairIntercept; endif if (! incInt || isempty (this.TreeModel.Pairs)) scores = gamboostpredict (this.BinEdges, ... this.TreeModel.ShapeValues, XC, ... interc); else scores = gamboostpredict (this.BinEdges, ... this.TreeModel.ShapeValues, XC, ... interc, 0, ... this.PairDetectionBinEdges, ... this.TreeModel.PairValues, ... this.TreeModel.Pairs); endif scores = [-scores, scores]; post = 1 ./ (1 + exp (-scores)); numObservations = size (XC, 1); CE = zeros (numObservations, 2); for k = 1:2 for i = 1:2 CE(:, k) = CE(:, k) + post(:, i) * Cost(k, i); endfor endfor [~, minIdx] = min (CE, [], 2); labels = labelsFromIndex (this.ClassNames, minIdx); scores = this.STfun (scores); return; endif ## Choose whether interactions must be included if (incInt) ## Which construction path the model took: an interaction ## list appends its terms to the predictors, a formula ## names every term the model has and replaces them. if (isempty (this.Formula)) ## Append interaction terms to the predictor matrix for i = 1:rows (this.IntMatrix) tindex = logical (this.IntMatrix(i,:)); Xterms = XC(:,tindex); Xinter = ones (rows (XC), 1); for c = 1:sum (tindex) Xinter = Xinter .* Xterms(:,c); endfor ## Append interaction terms XC = [XC, Xinter]; endfor else ## Add selected predictors and interaction terms XN = []; for i = 1:rows (this.IntMatrix) tindex = logical (this.IntMatrix(i,:)); Xterms = XC(:,tindex); Xinter = ones (rows (XC), 1); for c = 1:sum (tindex) Xinter = Xinter .* Xterms(:,c); endfor ## Append selected predictors and interaction terms XN = [XN, Xinter]; endfor XC = XN; endif ## Get parameters and intercept vectors from model with interactions params = this.ModelwInt.Parameters; Interc = this.ModelwInt.Intercept; else ## Get parameters and intercept vectors from base model params = this.BaseModel.Parameters; Interc = this.BaseModel.Intercept; endif ## Predict the raw score from testing data scores = predict_val (params, XC, Interc); ## Expected misclassification cost is defined on the posteriors, which ## the score becomes only under the logistic link, so the label is ## taken from those and not from the score the caller is handed. post = 1 ./ (1 + exp (-scores)); ## Compute the expected misclassification cost matrix numObservations = size (XC, 1); CE = zeros (numObservations, 2); for k = 1:2 for i = 1:2 CE(:, k) = CE(:, k) + post(:, i) * Cost(k, i); endfor endfor ## Select the class with the minimum expected misclassification cost [~, minIdx] = min (CE, [], 2); labels = labelsFromIndex (this.ClassNames, minIdx); ## Apply ScoreTransform scores = this.STfun (scores); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationGAM} {@var{m} =} margin (@var{obj}, @var{X}, @var{Y}) ## ## Classification margin of a compact generalized additive model. ## ## @code{@var{m} = margin (@var{obj}, @var{X}, @var{Y})} returns a column ## vector holding, for each row of @var{X}, the score the model gives its ## true class in @var{Y} less the score it gives the other class. A ## positive margin means the observation is classified correctly, and the ## larger it is the more confidently so. ## ## @seealso{CompactClassificationGAM, ClassificationGAM, edge, loss, ## predict} ## @end deftypefn function m = margin (this, X, Y) ## Check for sufficient input arguments if (nargin < 3) error ("CompactClassificationGAM.margin: too few input arguments."); endif [X, Y] = checkXY_ (this, X, Y, "margin"); [~, scores] = predict (this, X); classes = this.ClassNames; m = zeros (rows (X), 1); ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) idx = gYidx(i); if (isempty (idx)) m(i) = NaN; continue; endif true_score = scores(i, idx); scores(i, idx) = -Inf; m(i) = true_score - max (scores(i,:)); scores(i, idx) = true_score; endfor endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationGAM} {@var{e} =} edge (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactClassificationGAM} {@var{e} =} edge (@dots{}, @qcode{"Weights"}, @var{w}) ## ## Classification edge of a compact generalized additive model. ## ## @code{@var{e} = edge (@var{obj}, @var{X}, @var{Y})} returns the mean of ## the classification margins over the rows of @var{X}. ## ## @code{@var{e} = edge (@dots{}, @qcode{"Weights"}, @var{w})} takes the ## weighted mean instead, with one weight per row of @var{X}. ## ## @seealso{CompactClassificationGAM, ClassificationGAM, margin, loss, ## predict} ## @end deftypefn function e = edge (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("CompactClassificationGAM.edge: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("CompactClassificationGAM.edge: Name-Value", ... " arguments must be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, "edge"); ## The weights are normalized within each class to that class's prior, ## which is what the oracle does and is not the same as dividing by ## their total. This used to divide by the total. ## The weights are parsed before anything is computed, so a bad ## Name-Value pair is reported as such rather than after a margin. W = edgeWeights (varargin, Y, this.ClassNames, this.Prior, ... "CompactClassificationGAM", "edge"); m = margin (this, X, Y); e = sum (W .* m(:)) / sum (W); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationGAM} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactClassificationGAM} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Classification loss of a compact generalized additive model. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} returns the loss of ## the model on the rows of @var{X} against the true labels @var{Y}. ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} accepts the ## following name-value pairs: ## ## @itemize ## @item ## @qcode{"LossFun"} selects the loss. Supported values are ## @qcode{"mincost"}, the default, @qcode{"binodeviance"}, ## @qcode{"classifcost"}, @qcode{"classiferror"}, @qcode{"exponential"}, ## @qcode{"hinge"}, @qcode{"logit"} and @qcode{"quadratic"}. ## @qcode{"mincost"} assigns each observation to the class of least ## expected cost and charges what that assignment costs, so it reads the ## scores as a posterior, which is what this model returns; ## @qcode{"classifcost"} charges what the model's own prediction costs. ## ## @item ## @qcode{"Weights"} holds one weight per row of @var{X}, normalised to ## sum to one before it is applied. ## @end itemize ## ## @seealso{CompactClassificationGAM, ClassificationGAM, margin, edge, ## predict} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("CompactClassificationGAM.loss: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("CompactClassificationGAM.loss: Name-Value", ... " arguments must be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, "loss"); ## Parse optional arguments LossFun = 'mincost'; lossnames = {'binodeviance', 'classifcost', 'classiferror', ... 'exponential', 'hinge', 'logit', 'mincost', 'quadratic'}; args = varargin; keep = true (1, numel (args)); for i = 1:2:numel (args) if (strcmpi (args{i}, 'lossfun')) LossFun = args{i+1}; if (! (ischar (LossFun) && isrow (LossFun))) error (strcat ("CompactClassificationGAM.loss: 'LossFun'", ... " must be a character vector.")); endif LossFun = tolower (LossFun); if (! any (strcmpi (LossFun, lossnames))) error ("CompactClassificationGAM.loss: unsupported Loss function."); endif keep(i:i+1) = false; endif endfor W = getWeights_ (this, args(keep), rows (X), "loss"); W = W(:) / sum (W); [label, scores] = predict (this, X); classes = this.ClassNames; ## Membership of the true class, as an indicator per class Yind = zeros (rows (X), classCount (classes)); ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) idx = gYidx(i); if (isempty (idx)) L = NaN; return; endif Yind(i, idx) = 1; endfor ## The scalar score of the true class of each observation mj = sum (scores .* Yind, 2); switch (LossFun) case 'classiferror' wrong = zeros (rows (X), 1); for i = 1:rows (X) wrong(i) = ! isequal (label(i), Y(i)); endfor L = sum (W .* wrong); case 'binodeviance' L = sum (W .* log (1 + exp (-2 * mj))); case 'hinge' L = sum (W .* max (0, 1 - mj)); case 'exponential' L = sum (W .* exp (-mj)); case 'logit' L = sum (W .* log (1 + exp (-mj))); case 'quadratic' L = sum (W .* (1 - mj) .^ 2); case 'mincost' ## Each observation is assigned to the class of least expected ## cost, and charged what that assignment actually costs given its ## true class. L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) [~, k] = min (scores(i,:) * this.Cost); true_idx = gYidx(i); L = L + W(i) * this.Cost(true_idx, k); endfor case 'classifcost' ## What the model's own prediction costs, given the true class L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) true_idx = gYidx(i); pred_idx = find (ismember (classes, label(i))); L = L + W(i) * this.Cost(true_idx, pred_idx); endfor endswitch endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationGAM} {} savemodel (@var{obj}, @var{filename}) ## ## Save a CompactClassificationGAM object. ## ## @code{savemodel (@var{obj}, @var{filename})} saves each property of a ## CompactClassificationGAM object into an Octave binary file, the name of ## which is specified in @var{filename}, along with an extra variable, ## which defines the type classification object these variables constitute. ## Use @code{loadmodel} in order to load a classification object into ## Octave's workspace. ## ## @seealso{loadmodel, fitcgam, ClassificationGAM, CompactClassificationGAM} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error ("CompactClassificationGAM.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error ("CompactClassificationGAM.savemodel: FNAME must be a character vector."); endif ## Generate variable for class name classdef_name = 'CompactClassificationGAM'; ## Create variables from model properties NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; ResponseName = this.ResponseName; ClassNames = this.ClassNames; Prior = this.Prior; Cost = this.Cost; ScoreTransform = this.ScoreTransform; Intercept = this.Intercept; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; STfun = this.STfun; Formula = this.Formula; Interactions = this.Interactions; BaseModel = this.BaseModel; ModelwInt = this.ModelwInt; IntMatrix = this.IntMatrix; FitMethod = this.FitMethod; TreeModel = this.TreeModel; BinEdges = this.BinEdges; PairDetectionBinEdges = this.PairDetectionBinEdges; ## Save classdef name and all model properties as individual variables save ('-binary', fname, 'classdef_name', 'NumPredictors', ... 'PredictorNames', 'ResponseName', 'ClassNames', 'Prior', 'Cost', ... 'ScoreTransform', 'STfun', 'Intercept', ... 'CategoricalPredictors', 'ExpandedPredictorNames', ... 'Formula', 'Interactions', 'BaseModel', 'ModelwInt', ... 'IntMatrix', 'FitMethod', 'TreeModel', 'BinEdges', ... 'PairDetectionBinEdges'); endfunction endmethods methods (Access = private) ## Shared validation for the assessment methods, so each reports under ## its own name. function [X, Y] = checkXY_ (this, X, Y, caller) if (isempty (X)) error ("CompactClassificationGAM.%s: X is empty.", caller); elseif (this.NumPredictors != columns (X)) error (strcat ("CompactClassificationGAM.%s: X must have the", ... " same number of predictors as the trained", ... " model."), caller); endif if (isempty (Y)) error ("CompactClassificationGAM.%s: Y is empty.", caller); elseif (rows (X) != rows (Y)) error (strcat ("CompactClassificationGAM.%s: Y must have the", ... " same number of rows as X."), caller); endif endfunction ## Pull a "Weights" pair out of the optional arguments, defaulting to a ## uniform weight, and reject any other name. function W = getWeights_ (this, args, n, caller) W = ones (n, 1); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("CompactClassificationGAM.%s: parameter name", ... " must be a character vector."), caller); endif if (strcmpi (args{i}, 'weights')) W = args{i+1}; if (! (isnumeric (W) && isvector (W))) error (strcat ("CompactClassificationGAM.%s: 'Weights'", ... " must be a numeric vector."), caller); endif if (numel (W) != n) error (strcat ("CompactClassificationGAM.%s: size of", ... " 'Weights' must equal the number of rows", ... " in X."), caller); endif else error (strcat ("CompactClassificationGAM.%s: invalid parameter", ... " name in optional paired arguments."), caller); endif endfor endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a CompactClassificationGAM object mdl = CompactClassificationGAM (); ## Copy the saved data into the object. Iterate over what was ## saved rather than over fieldnames (mdl): a private property such ## as STfun is written out by savemodel but is not reported by ## fieldnames, so comparing the two sets could never match and every ## load failed. Assignment is legal here because this is a method of ## the class itself. names = fieldnames (data); ## The set methods for these read other properties, and one of them ## rebuilds Coeffs, so they are assigned once everything else is in ## place rather than in the order the file happens to list them. late = ismember (names, {'Cost', 'Prior', 'ScoreTransform', ... 'ResponseTransform'}); names = [names(! late); names(late)]; for i = 1:numel (names) try mdl.(names{i}) = data.(names{i}); catch error ("CompactClassificationGAM.load_model: invalid model in '%s'.", ... filename) end_try_catch endfor endfunction endmethods methods(Access = private) ## Set cost endmethods endclassdef ## Helper function function scores = predict_val (params, XC, intercept) ## The shared prediction engine evaluates every additive term and sums ## them. That sum is the log-odds of the second class, so the raw score of ## the first is its negative and the pair sums to zero, as MATLAB's does. f = gampredict (params, XC, intercept, 0); scores = [-f, f]; endfunction %!demo %! ## Create a generalized additive model classifier and its compact version %! # and compare their size %! %! load fisheriris %! X = meas; %! Y = species; %! %! Mdl = fitcdiscr (X, Y, 'ClassNames', unique (species)) %! CMdl = crossval (Mdl) ## Test constructor %!test %! Mdl = CompactClassificationGAM (); %! assert_equal (class (Mdl), "CompactClassificationGAM") %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = [0; 0; 1; 1]; %! PredictorNames = {'Feature1', 'Feature2', 'Feature3'}; %! Mdl = fitcgam (x, y, 'FitMethod', 'splines', ... %! 'PredictorNames', PredictorNames); %! CMdl = compact (Mdl); %! assert_equal (class (CMdl), "CompactClassificationGAM"); %! assert_equal ({CMdl.NumPredictors, CMdl.ResponseName}, {3, 'Y'}) %! assert_equal (CMdl.ClassNames, [0; 1]) %! assert_equal (CMdl.PredictorNames, PredictorNames) %! assert_equal (CMdl.BaseModel.Intercept, 0) %!test %! load fisheriris %! inds = strcmp (species,'versicolor') | strcmp (species,'virginica'); %! X = meas(inds, :); %! Y = species(inds, :)'; %! Y = strcmp (Y, 'virginica')'; %! Mdl = fitcgam (X, Y, 'FitMethod', 'splines', ... %! 'Formula', 'Y ~ x1 + x2 + x3 + x4 + x1:x2 + x2:x3'); %! CMdl = compact (Mdl); %! assert_equal (class (CMdl), "CompactClassificationGAM"); %! assert_equal ({CMdl.NumPredictors, CMdl.ResponseName}, {4, 'Y'}) %! assert_equal (CMdl.ClassNames, logical ([0; 1])) %! assert_equal (CMdl.Formula, 'Y ~ x1 + x2 + x3 + x4 + x1:x2 + x2:x3') %! assert_equal (CMdl.PredictorNames, {'x1', 'x2', 'x3', 'x4'}) %! assert_equal (CMdl.ModelwInt.Intercept, 0) %!test %! X = [2, 3, 5; 4, 6, 8; 1, 2, 3; 7, 8, 9; 5, 4, 3]; %! Y = [0; 1; 0; 1; 1]; %! Mdl = fitcgam (X, Y, 'FitMethod', 'splines', ... %! 'Knots', [4, 4, 4], 'Order', [3, 3, 3]); %! CMdl = compact (Mdl); %! assert_equal (class (CMdl), "CompactClassificationGAM"); %! assert_equal ({CMdl.NumPredictors, CMdl.ResponseName}, {3, 'Y'}) %! assert_equal (CMdl.ClassNames, [0; 1]) %! assert_equal (CMdl.PredictorNames, {'x1', 'x2', 'x3'}) %! assert_equal (CMdl.BaseModel.Intercept, 0.4055, 1e-1) ## Test input validation for constructor ## A compact model keeps the character class names and predicts whole names. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Cc = compact (fitcgam (Xch, Ych)); %! rand ("state", 1); randn ("state", 1); Cs = compact (fitcgam (Xch, Ycell)); %! assert_equal (cellstr (Cc.ClassNames), Cs.ClassNames); %! assert_equal (cellstr (predict (Cc, Xch)), predict (Cs, Xch)); ## and reads one back in its assessment methods. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Cc = compact (fitcgam (Xch, Ych)); %! rand ("state", 1); randn ("state", 1); Cs = compact (fitcgam (Xch, Ycell)); %! assert_equal (loss (Cc, Xch, Ych), loss (Cs, Xch, Ycell), 1e-12); %!error ... %! CompactClassificationGAM (1) ## Test predict method %!test %! x = [1, 2; 3, 4; 5, 6; 7, 8; 9, 10]; %! y = [1; 0; 1; 0; 1]; %! Mdl = fitcgam (x, y, 'FitMethod', 'splines', 'interactions', 'all'); %! CMdl = compact (Mdl); %! l = [1; 0; 1; 0; 1]; %! s = [0.0334, 0.9666; 0.9648, 0.0352; 0.0334, 0.9666; ... %! 0.9648, 0.0352; 0.0334, 0.9666]; %! [labels, scores] = predict (CMdl, x); %! assert_equal (class (CMdl), "CompactClassificationGAM"); %! assert_equal ({CMdl.NumPredictors, CMdl.ResponseName}, {2, 'Y'}) %! assert_equal (CMdl.ClassNames, [0; 1]) %! assert_equal (CMdl.PredictorNames, {'x1', 'x2'}) %! assert_equal (CMdl.ModelwInt.Intercept, 0.4055, 1e-1) %! assert_equal (labels, l) %! assert_equal (scores, s, 1e-1) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = [0; 0; 1; 1]; %! interactions = [false, true, false; true, false, true; false, true, false]; %! Mdl = fitcgam (x, y, 'FitMethod', 'splines', ... %! 'learningrate', 0.2, 'interactions', interactions); %! CMdl = compact (Mdl); %! [label, score] = predict (CMdl, x, 'includeinteractions', true); %! l = [0; 0; 1; 1]; %! s = [0.9725, 0.0275; 0.9895, 0.0105; 0.0070, 0.9930; 0.0238, 0.9762]; %! assert_equal (class (CMdl), "CompactClassificationGAM"); %! assert_equal ({CMdl.NumPredictors, CMdl.ResponseName}, {3, 'Y'}) %! assert_equal (CMdl.ClassNames, [0; 1]) %! assert_equal (CMdl.PredictorNames, {'x1', 'x2', 'x3'}) %! assert_equal (CMdl.ModelwInt.Intercept, 0) %! assert_equal (label, l) %! assert_equal (score, s, 1e-1) ## Test input validation for predict method %!shared CMdl %! Mdl = fitcgam (ones (4,2), ones (4,1)); %! CMdl = compact (Mdl); %!error ... %! predict (CMdl) %!error ... %! predict (CMdl, []) %!error ... %! predict (CMdl, 1) %!error ... %! savemodel (CompactClassificationGAM ()) %!error ... %! savemodel (CompactClassificationGAM (), 1) %!error ... %! savemodel (CompactClassificationGAM (), ['ab'; 'cd']) ## The compact model carries the full model's transform, which defaults to ## 'logit' as MATLAB's does, so its scores are posteriors summing to one. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! CMdl = compact (fitcgam (meas(inds,:), species(inds))); %! assert_equal (CMdl.ScoreTransform, 'logit'); %! [~, scores] = predict (CMdl, meas(1:6,:)); %! assert_equal (sum (scores, 2), ones (6, 1), 1e-12); ## A ScoreTransform is stored under its own name. That it reaches the ## scores is covered above, through predict. %!test %! CMdl = compact (fitcgam ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])); %! CMdl.ScoreTransform = 'symmetric'; %! assert_equal (class (CMdl.ScoreTransform), 'char'); %! assert_equal (CMdl.ScoreTransform, 'symmetric'); ## The compact model carries what MATLAB's compact model reports. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = fitcgam (meas(inds,:), species(inds)); %! CMdl = compact (Mdl); %! assert_equal (CMdl.Intercept, Mdl.Intercept); %! assert_equal (CMdl.CategoricalPredictors, Mdl.CategoricalPredictors); %! assert_equal (CMdl.ExpandedPredictorNames, Mdl.ExpandedPredictorNames); ## margin, edge and loss agree with the model it was compacted from. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds,:); %! Y = species(inds); %! Mdl = fitcgam (X, Y); %! CMdl = compact (Mdl); %! assert_equal (margin (CMdl, X, Y), margin (Mdl, X, Y)); %! assert_equal (edge (CMdl, X, Y), edge (Mdl, X, Y)); %! assert_equal (loss (CMdl, X, Y), loss (Mdl, X, Y)); ## A saved and reloaded compact model carries every property it holds. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! CMdl = compact (fitcgam (meas(inds,:), species(inds), ... %! 'FitMethod', 'splines')); %! fname = tempname (); %! savemodel (CMdl, fname); %! CMdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (CMdl2.Intercept, CMdl.Intercept); %! assert_equal (CMdl2.ClassNames, CMdl.ClassNames); %! assert_equal (CMdl2.BaseModel.Parameters(1).coefs, ... %! CMdl.BaseModel.Parameters(1).coefs); %! assert_equal (predict (CMdl2, meas(inds,:)), predict (CMdl, meas(inds,:))); ## The same round trip under the boosted-tree engine, whose fit lives in ## TreeModel rather than BaseModel. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! CMdl = compact (fitcgam (meas(inds,:), species(inds), ... %! 'FitMethod', 'boostedtrees')); %! fname = tempname (); %! savemodel (CMdl, fname); %! CMdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (CMdl2.Intercept, CMdl.Intercept); %! assert_equal (CMdl2.TreeModel.ShapeValues, CMdl.TreeModel.ShapeValues); %! assert_equal (CMdl2.BinEdges, CMdl.BinEdges); %! assert_equal (predict (CMdl2, meas(inds,:)), predict (CMdl, meas(inds,:))); %!shared x2, y2, CM %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! x2 = meas(inds,:); %! y2 = species(inds); %! CM = compact (fitcgam (x2, y2)); ## Test input validation for the assessment methods %!error ... %! margin (CM, x2) %!error ... %! margin (CM, [], y2) %!error ... %! edge (CM, x2, y2, 'Weights') %!error ... %! loss (CM, x2, y2, 'LossFun', 'nonsense') ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = compact (fitcgam (meas(inds,:), species(inds))); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'CompactClassificationGAM'); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ScoreTransform), class (Mdl.ScoreTransform)); %! assert_equal (predict (M2, meas(1:5,:)), predict (Mdl, meas(1:5,:))); %!error ... %! Mdl = fitcgam ([1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1], [0; 0; 1; 1]); %! CMdl = compact (Mdl); %! CMdl.Cost = 1:4; ## The shared cost guard is in force here too, and the struct form is ## permuted into this model's class order. The battery is on ## ClassificationDiscriminant. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = compact (fitcgam (meas(inds,:), species(inds))); %! S = struct ('ClassNames', {{'versicolor'; 'setosa'}}, ... %! 'ClassificationCosts', [0, 1; 2, 0]); %! Mdl.Cost = S; %! assert_equal (Mdl.Cost, [0, 2; 1, 0]); %!error ... %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = compact (fitcgam (meas(inds,:), species(inds))); %! Mdl.Cost = ones (2); ## A compacted tree-fitted model carries the engine and predicts alike. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds,:); %! Mdl = fitcgam (X, species(inds), 'FitMethod', 'boostedtrees'); %! CMdl = compact (Mdl); %! assert_equal (CMdl.FitMethod, 'boostedtrees'); %! assert_equal (CMdl.TreeModel.ShapeValues, Mdl.TreeModel.ShapeValues); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); %! fname = tempname (); %! savemodel (CMdl, fname); %! C2 = loadmodel (fname); %! delete (fname); %! assert_equal (predict (C2, X), predict (CMdl, X)); ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = compact (fitcgam (meas, strcmp (species, 'setosa'))); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = compact (fitcgam (meas, strcmp (species, 'setosa'))); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/CompactClassificationNaiveBayes.m000066400000000000000000001016561524624707500306740ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## classdef CompactClassificationNaiveBayes ## -*- texinfo -*- ## @deftp {statistics} CompactClassificationNaiveBayes ## ## Compact naive Bayes classification ## ## A @code{CompactClassificationNaiveBayes} object carries the fitted ## densities of a @code{ClassificationNaiveBayes} model and everything ## @code{predict} needs, but not the observations the model was fitted on. ## It classifies new data identically to the model it came from, and is far ## smaller to keep or to ship. ## ## Create one with the @code{compact} method of a ## @code{ClassificationNaiveBayes} object. Because it holds no training ## data, it has no @code{resub} methods and cannot be cross-validated. ## ## @seealso{ClassificationNaiveBayes, fitcnb} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} DistributionNames ## ## Predictor distributions ## ## A cell array of character vectors with one entry per predictor, naming ## the distribution fitted to it. This property is read-only. ## ## @end deftp DistributionNames = {}; ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} Mu ## ## Predictor means ## ## The means used to center the predictors, when the model standardizes ## them, and empty otherwise. This property is read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} Sigma ## ## Predictor standard deviations ## ## The standard deviations used to scale the predictors, when the model ## standardizes them, and empty otherwise. This property is read-only. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} DistributionParameters ## ## Fitted distribution parameters ## ## A cell array with one row per class and one column per predictor, ## holding the parameters fitted to each. This property is read-only. ## ## @end deftp DistributionParameters = {}; ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} CategoricalLevels ## ## Levels of the categorical predictors ## ## A cell array with one entry per predictor, holding the distinct levels ## of each categorical predictor and empty for every other. This property ## is read-only. ## ## @end deftp CategoricalLevels = {}; ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} Kernel ## ## Kernel smoothing functions ## ## A cell array naming the smoothing kernel of each kernel predictor, and ## empty for every other. This property is read-only. ## ## @end deftp Kernel = {}; ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} Support ## ## Kernel smoothing supports ## ## A cell array giving the support of each kernel predictor's density, and ## empty for every other. This property is read-only. ## ## @end deftp Support = {}; ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} Width ## ## Kernel smoothing bandwidths ## ## A numeric matrix with one row per class and one column per predictor, ## and empty when no predictor uses a kernel density. This property is ## read-only. ## ## @end deftp Width = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} ClassNames ## ## Class labels of the fitted model ## ## The distinct classes the model was fitted on, in the order the other ## per-class properties use. This property is read-only. ## ## @end deftp ClassNames = []; endproperties ## Properties a user may set after the model is fitted. They sit between ## the read-only blocks so that 'properties' reports MATLAB's own order. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} Prior ## ## Class prior probabilities ## ## A numeric row vector with one entry per class, in the order of ## @qcode{ClassNames}, summing to one. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} Cost ## ## Misclassification cost ## ## A square numeric matrix where @code{Cost(i,j)} is the cost of ## classifying an observation of class @math{i} into class @math{j}. ## ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} ScoreTransform ## ## Score transformation ## ## A character vector naming the function applied to the posterior ## returned by @code{predict}, or a function handle. ## ## @end deftp ScoreTransform = 'none'; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} PredictorNames ## ## Predictor variable names ## ## A cell array of character vectors naming the predictors. This property ## is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} CategoricalPredictors ## ## Categorical predictor indices ## ## The column indices treated as categorical, or empty when none is. This ## property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} ResponseName ## ## Response variable name ## ## A character vector naming the response variable. This property is ## read-only. ## ## @end deftp ResponseName = 'Y'; ## -*- texinfo -*- ## @deftp {CompactClassificationNaiveBayes} {property} ExpandedPredictorNames ## ## Expanded predictor variable names ## ## A cell array of character vectors naming the predictors as the model ## sees them. This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; endproperties properties (GetAccess = public, SetAccess = protected, Hidden) ## The parsed ScoreTransform, applied to the posterior by predict. STfun = []; endproperties methods (Hidden) function this = set.Cost (this, Cost) [C, errmsg] = costMatrix (Cost, this.ClassNames); if (! isempty (errmsg)) error ('CompactClassificationNaiveBayes.Cost: %s', errmsg); endif this.Cost = C; endfunction function this = set.Prior (this, Prior) P = Prior; if (isstruct (P)) P = priorFromStruct (P, this.ClassNames, ... 'CompactClassificationNaiveBayes.Prior'); elseif (ischar (P)) if (strcmpi (P, 'uniform')) P = ones (1, rows (this.Cost)) / rows (this.Cost); elseif (strcmpi (P, 'empirical')) return; else error (strcat ("CompactClassificationNaiveBayes.Prior: a", ... " character vector must be 'empirical' or", ... " 'uniform'.")); endif endif if (! (isnumeric (P) && isvector (P) && isreal (P))) error (strcat ("CompactClassificationNaiveBayes.Prior: must be a", ... " real numeric vector, a structure, 'empirical', or", ... " 'uniform'.")); endif if (any (P < 0) || ! (sum (P) > 0)) error (strcat ("CompactClassificationNaiveBayes.Prior: must be", ... " nonnegative and must not be all zero.")); endif this.Prior = P(:)' / sum (P); endfunction function this = set.ScoreTransform (this, val) [f, st] = parseScoreTransform (val, 'CompactClassificationNaiveBayes'); this.ScoreTransform = st; this.STfun = f; endfunction function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction function disp (this) fprintf ('\n CompactClassificationNaiveBayes\n\n'); fprintf ('%22s: %s\n', 'ResponseName', this.ResponseName); fprintf ('%22s: %s\n', 'CategoricalPredictors', ... mat2str (this.CategoricalPredictors)); fprintf ('%22s: %s\n', 'ClassNames', classNameListing (this.ClassNames)); fprintf ('%22s: %s\n', 'ScoreTransform', this.ScoreTransform); fprintf ('%22s: %s\n', 'DistributionNames', ... classNameListing (this.DistributionNames)); fprintf ('%22s: {%dx%d cell}\n', 'DistributionParameters', ... size (this.DistributionParameters)); fprintf ('\n'); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationNaiveBayes} {@var{obj} =} CompactClassificationNaiveBayes (@var{Mdl}) ## ## Create a @code{CompactClassificationNaiveBayes} object. ## ## @var{Mdl} is the @code{ClassificationNaiveBayes} object to compact. The ## documented way to reach this constructor is the @code{compact} method. ## ## @seealso{ClassificationNaiveBayes} ## @end deftypefn function this = CompactClassificationNaiveBayes (Mdl) if (nargin < 1) error (strcat ("CompactClassificationNaiveBayes: too few", ... " input arguments.")); endif if (! isa (Mdl, 'ClassificationNaiveBayes')) error (strcat ("CompactClassificationNaiveBayes: MDL must be a", ... " ClassificationNaiveBayes object.")); endif this.DistributionNames = Mdl.DistributionNames; this.Mu = Mdl.Mu; this.Sigma = Mdl.Sigma; this.DistributionParameters = Mdl.DistributionParameters; this.CategoricalLevels = Mdl.CategoricalLevels; this.Kernel = Mdl.Kernel; this.Support = Mdl.Support; this.Width = Mdl.Width; this.ClassNames = Mdl.ClassNames; this.Cost = Mdl.Cost; this.Prior = Mdl.Prior; this.ScoreTransform = Mdl.ScoreTransform; this.PredictorNames = Mdl.PredictorNames; this.CategoricalPredictors = Mdl.CategoricalPredictors; this.ResponseName = Mdl.ResponseName; this.ExpandedPredictorNames = Mdl.ExpandedPredictorNames; endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {CompactClassificationNaiveBayes} {@var{label} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {CompactClassificationNaiveBayes} {[@var{label}, @var{score}, @var{cost}] =} predict (@var{obj}, @var{XC}) ## ## Classify new data with a compact naive Bayes model. ## ## The same classification the model it came from would give: the label of ## least expected cost, the posterior of each class, and the expected ## misclassification cost of each. ## ## @end deftypefn function [label, score, cost] = predict (this, XC) if (nargin < 2) error (strcat ("CompactClassificationNaiveBayes.predict: too few", ... " input arguments.")); endif if (isempty (XC)) error ("CompactClassificationNaiveBayes.predict: XC is empty."); endif if (! (isnumeric (XC) && isreal (XC) && ismatrix (XC))) error (strcat ("CompactClassificationNaiveBayes.predict: XC must", ... " be a real numeric matrix.")); endif if (numel (this.PredictorNames) != columns (XC)) error (strcat ("CompactClassificationNaiveBayes.predict: XC must", ... " have the same number of predictors as the", ... " trained model.")); endif nCls = numel (this.Prior); logscore = nbLogLik (XC, this.DistributionNames, ... this.DistributionParameters, nCls, ... this.CategoricalLevels); logscore = logscore + log (this.Prior); ## An observation no class can account for, a categorical level the ## model never saw among them, leaves the likelihood at -Inf for every ## class alike. Its posterior is the prior: what remains when the data ## says nothing. Measured against R2024a, which returns the prior and ## not a uniform distribution, and does so for the whole observation ## even when its other predictors are perfectly informative. noinfo = all (logscore == -Inf, 2); logscore = logscore - max (logscore, [], 2); score = exp (logscore); score = score ./ sum (score, 2); score(isnan (score)) = 0; if (any (noinfo)) score(noinfo,:) = repmat (this.Prior, sum (noinfo), 1); endif cost = score * this.Cost; [~, minIdx] = min (cost, [], 2); label = labelsFromIndex (this.ClassNames, minIdx); score = this.STfun (score); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationNaiveBayes} {@var{m} =} margin (@var{obj}, @var{X}, @var{Y}) ## ## Classification margin on new data. ## ## @end deftypefn function m = margin (this, X, Y) if (nargin < 3) error (strcat ("CompactClassificationNaiveBayes.margin: too few", ... " input arguments.")); endif [gY, errmsg] = labelIndices (this.ClassNames, Y); if (! isempty (errmsg)) error ("CompactClassificationNaiveBayes.margin: %s", errmsg); endif if (rows (X) != numel (gY)) error (strcat ("CompactClassificationNaiveBayes.margin: number of", ... " rows in X and Y must be equal.")); endif [~, s] = predict (this, X); m = marginsOf (s, gY, 1); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationNaiveBayes} {@var{e} =} edge (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactClassificationNaiveBayes} {@var{e} =} edge (@dots{}, @qcode{'Weights'}, @var{w}) ## ## Classification edge on new data. ## ## @end deftypefn function e = edge (this, X, Y, varargin) if (nargin < 3) error (strcat ("CompactClassificationNaiveBayes.edge: too few", ... " input arguments.")); endif W = edgeWeights (varargin, Y, this.ClassNames, this.Prior, ... 'CompactClassificationNaiveBayes', 'edge'); e = sum (W .* margin (this, X, Y)); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationNaiveBayes} {@var{l} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactClassificationNaiveBayes} {@var{l} =} loss (@dots{}, @var{name}, @var{value}) ## ## Classification loss on new data. ## ## Takes the @qcode{'LossFun'} and @qcode{'Weights'} options that ## @code{ClassificationNaiveBayes.loss} takes. ## ## @end deftypefn function l = loss (this, X, Y, varargin) if (nargin < 3) error (strcat ("CompactClassificationNaiveBayes.loss: too few", ... " input arguments.")); endif if (mod (numel (varargin), 2) != 0) error (strcat ("CompactClassificationNaiveBayes.loss: name-value", ... " arguments must be in pairs.")); endif LossFun = 'mincost'; Weights = []; lf_opt = {'binodeviance', 'classifcost', 'classiferror', ... 'exponential', 'hinge', 'logit', 'mincost', 'quadratic'}; while (numel (varargin) > 0) Value = varargin{2}; switch (tolower (varargin{1})) case 'lossfun' if (! (ischar (Value) && any (strcmpi (Value, lf_opt)))) error (strcat ("CompactClassificationNaiveBayes.loss:", ... " invalid loss function.")); endif LossFun = tolower (Value); case 'weights' if (! (isnumeric (Value) && isvector (Value))) error (strcat ("CompactClassificationNaiveBayes.loss:", ... " invalid 'Weights'.")); endif Weights = Value; otherwise error (strcat ("CompactClassificationNaiveBayes.loss: invalid", ... " parameter name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile [gY, errmsg] = labelIndices (this.ClassNames, Y); if (! isempty (errmsg)) error ("CompactClassificationNaiveBayes.loss: %s", errmsg); endif if (rows (X) != numel (gY)) error (strcat ("CompactClassificationNaiveBayes.loss: number of", ... " rows in X and Y must be equal.")); endif if (isempty (Weights)) w = ones (numel (gY), 1); else w = Weights(:); if (numel (w) != numel (gY)) error (strcat ("CompactClassificationNaiveBayes.loss:", ... " 'Weights' must have one element per", ... " observation.")); endif endif w = priorNormalize (w, gY, this.Prior); [~, s] = predict (this, X); l = classificationLoss (LossFun, s, gY, w, this.Cost); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationNaiveBayes} {@var{lp} =} logp (@var{obj}, @var{X}) ## ## Log unconditional probability density of new data. ## ## @end deftypefn function lp = logp (this, X) if (nargin < 2) error (strcat ("CompactClassificationNaiveBayes.logp: too few", ... " input arguments.")); endif if (isempty (X)) error ("CompactClassificationNaiveBayes.logp: X is empty."); endif if (numel (this.PredictorNames) != columns (X)) error (strcat ("CompactClassificationNaiveBayes.logp: X must have", ... " the same number of predictors as the trained", ... " model.")); endif nCls = numel (this.Prior); L = nbLogLik (X, this.DistributionNames, ... this.DistributionParameters, nCls, ... this.CategoricalLevels); L = L + log (this.Prior); ## Factoring out the largest term keeps a density small enough to ## underflow contributing its logarithm. Where every class is ## impossible the largest term is -Inf and the factoring is 0/0, so the ## answer is written directly: the density really is zero there. Lmax = max (L, [], 2); lp = Lmax + log (sum (exp (L - Lmax), 2)); lp(Lmax == -Inf) = -Inf; ## predict skips a missing predictor and classifies on the rest, but a ## density is not defined for an observation that is not fully observed, ## so this reports NaN where predict reports a class. R2024a does the ## same. lp(any (isnan (X), 2)) = NaN; endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationNaiveBayes} {} savemodel (@var{obj}, @var{filename}) ## ## Save a CompactClassificationNaiveBayes object. ## ## @code{savemodel (@var{obj}, @var{filename})} saves each property of a ## CompactClassificationNaiveBayes object into an Octave binary file, the ## name of which is specified in @var{filename}, along with an extra ## variable, which defines the type classification object these variables ## constitute. Use @code{loadmodel} in order to load a classification ## object into Octave's workspace. ## ## @seealso{loadmodel, fitcnb, CompactClassificationNaiveBayes} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error (strcat ("CompactClassificationNaiveBayes.savemodel:", ... " too few input arguments.")); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error (strcat ("CompactClassificationNaiveBayes.savemodel:", ... " FNAME must be a character vector.")); endif ## Generate variable for class name classdef_name = 'CompactClassificationNaiveBayes'; ## Create variables from model properties DistributionNames = this.DistributionNames; Mu = this.Mu; Sigma = this.Sigma; CategoricalLevels = this.CategoricalLevels; ## A kernel predictor's density is a classdef object, which Octave's ## save cannot serialize, so it goes out as the sample it was fitted to ## and load_model rebuilds it from that and the recorded bandwidth. DistributionParameters = nbKernelPack (this.DistributionParameters, ... this.DistributionNames); Kernel = this.Kernel; Support = this.Support; Width = this.Width; ClassNames = this.ClassNames; Prior = this.Prior; Cost = this.Cost; ScoreTransform = this.ScoreTransform; PredictorNames = this.PredictorNames; CategoricalPredictors = this.CategoricalPredictors; ResponseName = this.ResponseName; ExpandedPredictorNames = this.ExpandedPredictorNames; STfun = this.STfun; ## Save classdef name and all model properties as individual variables save ('-binary', fname, 'classdef_name', 'DistributionNames', 'Mu', ... 'Sigma', 'DistributionParameters', 'CategoricalLevels', ... 'Kernel', 'Support', 'Width', 'ClassNames', 'Prior', 'Cost', ... 'ScoreTransform', 'PredictorNames', 'CategoricalPredictors', ... 'ResponseName', 'ExpandedPredictorNames', 'STfun'); endfunction endmethods methods (Static, Hidden) function mdl = load_model (filename, data) ## The compact model is built from a full one and has no training data ## of its own, so the smallest fit the full class accepts is compacted ## and then filled property by property. mdl = CompactClassificationNaiveBayes ( ... ClassificationNaiveBayes ([0; 1; 2; 3], [1; 1; 2; 2])); ## Copy the saved data into the object. Iterate over what was saved ## rather than over fieldnames (mdl): a private property such as STfun ## is written out by savemodel but is not reported by fieldnames, so ## comparing the two sets could never match and every load failed. ## Assignment is legal here because this is a method of the class ## itself. names = fieldnames (data); ## These three are assigned once everything else is in place, and in ## this order rather than the file's. Cost comes before Prior because ## set.Prior counts the classes by the rows of Cost and not by ## ClassNames, so a prior arriving first is measured against the stub's. order = {'Cost', 'Prior', 'ScoreTransform'}; late = ismember (names, order); tail = order(ismember (order, names)); names = [names(! late); tail(:)]; for i = 1:numel (names) try mdl.(names{i}) = data.(names{i}); catch msg = strcat ("CompactClassificationNaiveBayes.load_model:", ... " invalid model in '%s'."); error (msg, filename); end_try_catch endfor mdl.DistributionParameters = nbKernelUnpack ( ... mdl.DistributionParameters, mdl.DistributionNames, ... mdl.Kernel, mdl.Support, mdl.Width); endfunction endmethods endclassdef ## Tests %!test # MATLAB parity: the surface a compact model reports %! load fisheriris %! CMdl = compact (fitcnb (meas, species)); %! assert_equal (class (CMdl), 'CompactClassificationNaiveBayes'); %! assert_equal (CMdl.ClassNames, unique (species)); %! assert_equal (CMdl.Prior, [1/3, 1/3, 1/3], 1e-15); %! assert_equal (CMdl.Cost, [0, 1, 1; 1, 0, 1; 1, 1, 0]); %! assert_equal (CMdl.ResponseName, 'Y'); %! assert_equal (CMdl.PredictorNames, {'x1', 'x2', 'x3', 'x4'}); %! assert_equal (CMdl.DistributionNames, ... %! {'normal', 'normal', 'normal', 'normal'}); %! assert_equal (CMdl.ScoreTransform, 'none'); ## A compact model keeps no observations, so it has neither the training data ## nor anything derived from it. %!test # the properties a compact model does not carry %! load fisheriris %! CMdl = compact (fitcnb (meas, species)); %! assert_equal (isprop (CMdl, 'X'), false); %! assert_equal (isprop (CMdl, 'Y'), false); %! assert_equal (isprop (CMdl, 'W'), false); %! assert_equal (isprop (CMdl, 'NumObservations'), false); %! assert_equal (isprop (CMdl, 'RowsUsed'), false); %! assert_equal (isprop (CMdl, 'ModelParameters'), false); %!test # MATLAB parity: it classifies exactly as the model it came from %! load fisheriris %! Mdl = fitcnb (meas, species); %! CMdl = compact (Mdl); %! [ml, ms, mc] = predict (Mdl, meas); %! [cl, cs, cc] = predict (CMdl, meas); %! assert_equal (cl, ml); %! assert_equal (cs, ms); %! assert_equal (cc, mc); %!test # MATLAB parity: loss, edge, margin and logp %! load fisheriris %! CMdl = compact (fitcnb (meas, species)); %! assert_equal (loss (CMdl, meas, species), 0.04, 1e-14); %! assert_equal (loss (CMdl, meas, species, 'LossFun', 'hinge'), ... %! 0.052784701267562, 1e-12); %! assert_equal (edge (CMdl, meas, species), 0.894430597464877, 1e-12); %! assert_equal (sum (margin (CMdl, meas, species)), ... %! 134.164589619731402, 1e-10); %! assert_equal (logp (CMdl, meas)(1), 1.026591235856343, 1e-12); %!test # a kernel model compacts, densities and all %! load fisheriris %! Mdl = fitcnb (meas, species, 'DistributionNames', 'kernel'); %! CMdl = compact (Mdl); %! assert_equal (CMdl.Kernel, {'normal', 'normal', 'normal', 'normal'}); %! assert_equal (CMdl.Width, Mdl.Width); %! assert_equal (class (CMdl.DistributionParameters{1,1}), ... %! 'prob.KernelDistribution'); %! assert_equal (predict (CMdl, meas), predict (Mdl, meas)); %!test # Cost and Prior may be assigned on a compact model too %! load fisheriris %! CMdl = compact (fitcnb (meas, species)); %! CMdl.Cost = [0, 2, 2; 2, 0, 2; 2, 2, 0]; %! assert_equal (CMdl.Cost, [0, 2, 2; 2, 0, 2; 2, 2, 0]); %! CMdl.Prior = [0.2, 0.3, 0.5]; %! assert_equal (CMdl.Prior, [0.2, 0.3, 0.5], 1e-15); %! CMdl.ScoreTransform = 'logit'; %! assert_equal (CMdl.ScoreTransform, 'logit'); %!test # a categorical model compacts, levels and all %! X = [1, 2; 1, 3; 2, 2; 2, 3; 1, 2; 3, 1; 3, 3; 2, 1; 1, 1; 3, 2; ... %! 2, 2; 1, 3; 3, 1; 2, 3; 1, 1; 3, 2; 2, 1; 1, 2; 3, 3; 2, 2]; %! Y = [repmat({'a'}, 10, 1); repmat({'b'}, 10, 1)]; %! Mdl = fitcnb (X, Y, 'DistributionNames', 'mvmn', ... %! 'CategoricalPredictors', [1, 2]); %! CMdl = compact (Mdl); %! assert_equal (CMdl.CategoricalLevels{1}, [1; 2; 3]); %! assert_equal (CMdl.CategoricalPredictors, [1, 2]); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); %! assert_equal (loss (CMdl, X, Y), 0.45, 1e-14); %!test # a multinomial model compacts, and keeps its character DistributionNames %! C = [2, 0, 1; 1, 3, 0; 0, 1, 4; 3, 1, 0; ... %! 0, 2, 2; 1, 0, 3; 4, 1, 1; 0, 3, 2]; %! L = [repmat({'x'}, 4, 1); repmat({'y'}, 4, 1)]; %! CMdl = compact (fitcnb (C, L, 'DistributionNames', 'mn')); %! assert_equal (CMdl.DistributionNames, 'mn'); %! [label, score] = predict (CMdl, [1, 1, 1; 4, 0, 0]); %! assert_equal (label, {'x'; 'x'}); %! assert_equal (score(2,1), 0.769060987976952, 1e-12); %!test # an unseen level falls back to the prior on a compact model too %! Z = [1, 1; 1, 1; 1, 2; 1, 2; 1, 1; 1, 2; 2, 2; 2, 1]; %! G = [repmat({'p'}, 6, 1); repmat({'q'}, 2, 1)]; %! CMdl = compact (fitcnb (Z, G, 'DistributionNames', 'mvmn', ... %! 'CategoricalPredictors', [1, 2])); %! [~, score] = predict (CMdl, [3, 1]); %! assert_equal (score, [0.75, 0.25], 1e-12); %! assert_equal (logp (CMdl, [3, 1]), -Inf); ## A compact model survives savemodel and loadmodel, kernel densities and all, ## although it has no training data of its own to be rebuilt from. %!test %! load fisheriris %! CMdl = compact (fitcnb (meas, species)); %! fname = tempname (); %! savemodel (CMdl, fname); %! C2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (C2), 'CompactClassificationNaiveBayes'); %! p = properties (CMdl); %! for i = 1:numel (p) %! assert_equal (C2.(p{i}), CMdl.(p{i})); %! endfor %! assert_equal (predict (C2, meas(1:10,:)), predict (CMdl, meas(1:10,:))); %!test %! load fisheriris %! CMdl = compact (fitcnb (meas, species, 'DistributionNames', 'kernel')); %! fname = tempname (); %! savemodel (CMdl, fname); %! C2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (C2.DistributionParameters{1,1}), ... %! 'prob.KernelDistribution'); %! assert_equal (C2.Width, CMdl.Width); %! assert_equal (predict (C2, meas(1:10,:)), predict (CMdl, meas(1:10,:))); %!error ... %! savemodel (compact (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2]))) %!error ... %! savemodel (compact (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])), 1) %!error ... %! savemodel (compact (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])), ... %! ['ab'; 'cd']) ## Test input validation %!error ... %! CompactClassificationNaiveBayes () %!error ... %! CompactClassificationNaiveBayes (5) %!error ... %! predict (compact (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])), []) %!error ... %! predict (compact (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])), ones (2, 3)) %!error ... %! loss (compact (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])), [1, 2; 2, 3; 3, 4; 4, 5]) %!error ... %! loss (compact (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])), [1, 2; 2, 3; 3, 4; 4, 5], ... %! [1; 1; 2; 2], 'LossFun', 'nope') %!error ... %! margin (compact (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])), [1, 2; 2, 3; 3, 4; 4, 5]) %!error ... %! edge (compact (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])), [1, 2; 2, 3; 3, 4; 4, 5]) %!error ... %! logp (compact (fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])), []) ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = compact (fitcnb (meas, species)); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = compact (fitcnb (meas, species)); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/CompactClassificationNeuralNetwork.m000066400000000000000000001305231524624707500314410ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef CompactClassificationNeuralNetwork ## -*- texinfo -*- ## @deftp {statistics} CompactClassificationNeuralNetwork ## ## Compact neural network classification ## ## The @code{CompactClassificationNeuralNetwork} class implements a compact ## version of the neural network classifier object, which can predict ## responses for new data using the @code{predict} method, but does not store ## the training data. ## ## A compact neural network classification model is a smaller version of the ## full @code{ClassificationNeuralNetwork} model that does not include the ## training data. It consumes less memory than the full model, but cannot ## perform tasks that require the training data, such as cross-validation. ## ## Create a @code{CompactClassificationNeuralNetwork} object by using the ## @code{compact} method on a @code{ClassificationNeuralNetwork} object. ## ## @seealso{ClassificationNeuralNetwork, fitcnet} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} NumPredictors ## ## Number of predictors ## ## A positive integer value specifying the number of predictors in the ## training dataset used for training the neural network model. ## This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} PredictorNames ## ## Names of predictor variables ## ## A cell array of character vectors specifying the names of the predictor ## variables. The names are in the order in which they appear in the ## training dataset. This property is read-only. ## ## @end deftp PredictorNames = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} ResponseName ## ## Response variable name ## ## A character vector specifying the name of the response variable @var{Y}. ## This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} ClassNames ## ## Names of classes in the response variable ## ## An array of unique values of the response variable @var{Y}, which has the ## same data types as the data in @var{Y}. This property is read-only. ## @qcode{ClassNames} can have any of the following datatypes: ## ## @itemize ## @item Cell array of character vectors ## @item Character array ## @item Logical vector ## @item Numeric vector ## @end itemize ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} Sigma ## ## Predictor standard deviations ## ## A numeric vector containing the standard deviations of the predictors ## used for standardization. Empty when the predictor data were not ## standardized. ## This property is read-only. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} Mu ## ## Predictor means ## ## A numeric vector containing the means of the predictors used for ## standardization. Empty when the predictor data were not standardized. ## This property is read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} LayerSizes ## ## Sizes of fully connected layers ## ## A positive integer vector specifying the sizes of the fully connected ## layers in the neural network model. The i-th element of ## @qcode{LayerSizes} is the number of outputs in the i-th fully connected ## layer of the neural network model. @qcode{LayerSizes} does not include ## the size of the final fully connected layer. This layer always has K ## outputs, where K is the number of classes in Y. This property is ## read-only. ## ## @end deftp LayerSizes = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} Activations ## ## Activation functions for hidden layers ## ## A character vector or cell array of character vectors specifying the ## activation functions used in the hidden layers of the neural network. ## Supported activation functions include: @qcode{'linear'}, ## @qcode{'sigmoid'}, @qcode{'relu'}, @qcode{'tanh'}, @qcode{'softmax'}, ## @qcode{'lrelu'}, @qcode{'prelu'}, @qcode{'elu'}, and @qcode{'gelu'}. ## This property is read-only. ## ## @end deftp Activations = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} OutputLayerActivation ## ## Activation function for output layer ## ## A character vector specifying the activation function of the output layer ## of the neural network. Supported activation functions are the same as ## for the @qcode{Activations} property. This property is read-only. ## ## @end deftp OutputLayerActivation = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} LayerWeights ## ## Learned weights of each fully connected layer ## ## A cell array holding one matrix per layer, the output layer included, ## with one row per neuron of that layer and one column per input it ## takes. This property is read-only. ## ## @end deftp LayerWeights = {}; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} LayerBiases ## ## Learned bias of each fully connected layer ## ## A cell array holding one column vector per layer, the output layer ## included, with one entry per neuron of that layer. This property is ## read-only. ## ## @end deftp LayerBiases = {}; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} Prior ## ## Prior probability of each class ## ## A numeric vector with one entry per class, in the order of ## @code{ClassNames}, summing to one. It is taken from the model this ## object was compacted from. This property is read-only. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector holding the column of each predictor treated as ## categorical, and empty when none is. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} ExpandedPredictorNames ## ## Names of the expanded predictor variables ## ## A cell array of character vectors naming the predictors as the model ## sees them. It matches @code{PredictorNames} unless a categorical ## predictor was expanded into dummy variables. This property is ## read-only. ## ## @end deftp ExpandedPredictorNames = {}; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} Cost ## ## Cost of misclassification ## ## A numeric matrix with one row and one column per class, where ## @code{Cost(i,j)} is the cost of classifying an observation of class ## @math{i} as class @math{j}. It is taken from the model this object was ## compacted from. This property is read-only. ## ## ## A cost may also be given as a struct with the fields ## @qcode{ClassNames} and @qcode{ClassificationCosts}, which names the ## order its own matrix is written in. That matrix is permuted into the ## order of @qcode{ClassNames} above, so a caller need not know which ## order the classes were sorted into. It must name every class. ## ## A cost must be floating point, not sparse, not complex, non-negative ## and zero down its diagonal, and must hold no @qcode{NaN} or ## @qcode{Inf}. A @code{single} is widened to @code{double}. ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {CompactClassificationNeuralNetwork} {property} ScoreTransform ## ## Transformation function for classification scores ## ## Specified as a function handle for transforming the classification ## scores. Add or change the @qcode{ScoreTransform} property using dot ## notation as in: ## ## @itemize ## @item @qcode{@var{obj}.ScoreTransform = 'function_name'} ## @item @qcode{@var{obj}.ScoreTransform = @@function_handle} ## @end itemize ## ## When specified as a character vector, it can be any of the following ## built-in functions. Nevertheless, the @qcode{ScoreTransform} property ## always stores their function handle equivalent. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'doublelogit'} @tab @math{1 ./ (1 + exp (-2 * x))} ## @item @qcode{'invlogit'} @tab @math{log (x ./ (1 - x))} ## @item @qcode{'ismax'} @tab Sets the score for the class with the ## largest score to 1, and for all other classes to 0 ## @item @qcode{'logit'} @tab @math{1 ./ (1 + exp (-x))} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'sign'} @tab ## @math{-1 for x < 0, 0 for x = 0, 1 for x > ## 0} ## @item @qcode{'symmetric'} @tab @math{2 * x - 1} ## @item @qcode{'symmetricismax'} @tab Sets the score for the class ## with the largest score to 1, and for all other classes to -1 ## @item @qcode{'symmetriclogit'} @tab @math{2 ./ (1 + exp (-x)) - 1} ## @end multitable ## ## @end deftp ScoreTransform = 'none'; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) STfun = @(x) x; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.ScoreTransform (this, val) name = 'CompactClassificationNeuralNetwork'; [this.STfun, this.ScoreTransform] = parseScoreTransform (val, name); endfunction function this = set.Cost (this, val) gnY = this.ClassNames; if (isempty (val)) this.Cost = cast (! eye (classCount (gnY)), 'double'); else ## Everything a cost must be, and the struct form, which ## is permuted into this model's class order. [val, errmsg] = costMatrix (val, gnY); if (! isempty (errmsg)) error ("CompactClassificationNeuralNetwork: %s", errmsg); endif this.Cost = val; endif endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationNeuralNetwork} {@var{obj} =} CompactClassificationNeuralNetwork (@var{Mdl}) ## @deftypefnx {CompactClassificationNeuralNetwork} {@var{obj} =} CompactClassificationNeuralNetwork () ## ## Create a @code{CompactClassificationNeuralNetwork} object. ## ## @var{Mdl} is the @code{ClassificationNeuralNetwork} object to ## compact. The documented way to reach this constructor is the ## @code{compact} method. ## ## Called with no arguments it returns an object with its properties ## empty, which is how a saved model is rebuilt before its values are ## filled in. ## ## @end deftypefn function this = CompactClassificationNeuralNetwork (Mdl = []) ## Check for appropriate class if (isempty (Mdl)) return; elseif (! strcmpi (class (Mdl), 'ClassificationNeuralNetwork')) error (strcat ("CompactClassificationNeuralNetwork: invalid", ... " classification object.")); endif ## Save properties to compact model this.NumPredictors = Mdl.NumPredictors; this.PredictorNames = Mdl.PredictorNames; this.ResponseName = Mdl.ResponseName; this.ClassNames = Mdl.ClassNames; this.ScoreTransform = Mdl.ScoreTransform; this.STfun = Mdl.STfun; this.Sigma = Mdl.Sigma; this.Mu = Mdl.Mu; this.LayerSizes = Mdl.LayerSizes; this.Activations = Mdl.Activations; this.OutputLayerActivation = Mdl.OutputLayerActivation; this.LayerWeights = Mdl.LayerWeights; this.LayerBiases = Mdl.LayerBiases; this.Cost = Mdl.Cost; this.Prior = Mdl.Prior; this.CategoricalPredictors = Mdl.CategoricalPredictors; this.ExpandedPredictorNames = Mdl.ExpandedPredictorNames; endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n CompactClassificationNeuralNetwork\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); if (iscellstr (this.ClassNames)) str = repmat ({'''%s'''}, 1, numel (this.ClassNames)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.ClassNames{:}); elseif (ischar (this.ClassNames)) str = repmat ({'''%s'''}, 1, rows (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, cellstr (this.ClassNames){:}); else # single, double, logical str = repmat ({'%d'}, 1, numel (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.ClassNames); endif fprintf ("%+25s: %s\n", 'ClassNames', str); fprintf ("%+25s: '%s'\n", 'ScoreTransform', this.ScoreTransform); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); str = repmat ({'%d'}, 1, numel (this.LayerSizes)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.LayerSizes); fprintf ("%+25s: %s\n", 'LayerSizes', str); if (iscellstr (this.Activations)) str = repmat ({'''%s'''}, 1, numel (this.Activations)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.Activations{:}); fprintf ("%+25s: %s\n", 'Activations', str); else # character vector fprintf ("%+25s: '%s'\n", 'Activations', this.Activations); endif fprintf ("%+25s: '%s'\n", 'OutputLayerActivation', ... this.OutputLayerActivation); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {CompactClassificationNeuralNetwork} {@var{label} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {CompactClassificationNeuralNetwork} {[@var{label}, @var{score}] =} predict (@var{obj}, @var{XC}) ## ## Classify new data points into categories using the neural network ## classification model from a CompactClassificationNeuralNetwork object. ## ## @code{@var{label} = predict (@var{obj}, @var{XC})} returns the vector of ## labels predicted for the corresponding instances in @var{XC}, using the ## neural network model stored in the CompactClassificationNeuralNetwork ## model, @var{obj}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{CompactClassificationNeuralNetwork} class ## object. ## @item ## @var{XC} must be an @math{M*P} numeric matrix with the same number of ## features @math{P} as the corresponding predictors of the neural network ## model in @var{obj}. ## @end itemize ## ## @code{[@var{label}, @var{score}] = predict (@var{obj}, @var{XC})} also ## returns @var{score}, which contains the predicted class scores or ## posterior probabilities for each instance of the corresponding unique ## classes. ## ## The @var{score} matrix contains the classification scores for each class. ## For each observation in @var{XC}, the predicted class label is the one ## with the highest score among all classes. If the @qcode{ScoreTransform} ## property is set to a transformation function, the scores are transformed ## accordingly before being returned. ## ## @seealso{CompactClassificationNeuralNetwork, ## ClassificationNeuralNetwork, fitcnet} ## @end deftypefn function [labels, scores] = predict (this, XC) ## Check for sufficient input arguments if (nargin < 2) error (strcat ("CompactClassificationNeuralNetwork.predict:", ... " too few input arguments.")); endif ## Check for valid XC if (isempty (XC)) error ("CompactClassificationNeuralNetwork.predict: XC is empty."); elseif (this.NumPredictors != columns (XC)) error (strcat ("CompactClassificationNeuralNetwork.predict:", ... " XC must have the same number of predictors", ... " as the trained neural network model.")); endif ## Standardize (if necessary) if (! isempty (this.Mu)) XC = (XC - this.Mu) ./ this.Sigma; endif ## Predict labels from new data NumThreads = nproc (); [labels, scores] = fcnnpredict (this.LayerWeights, this.LayerBiases, ... this.Activations, ... this.OutputLayerActivation, ... XC, NumThreads); # Get class labels labels = labelsFromIndex (this.ClassNames, labels); ## Apply ScoreTransform scores = this.STfun (scores); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationNeuralNetwork} {@var{m} =} margin (@var{obj}, @var{X}, @var{Y}) ## ## Classification margin of a compact neural network classifier. ## ## @code{@var{m} = margin (@var{obj}, @var{X}, @var{Y})} returns a column ## vector holding, for each row of @var{X}, the score the model gives its ## true class in @var{Y} less the largest score it gives any other class. ## A positive margin means the observation is classified correctly, and ## the larger it is the more confidently so. ## ## @seealso{CompactClassificationNeuralNetwork, ## ClassificationNeuralNetwork, edge, loss, predict} ## @end deftypefn function m = margin (this, X, Y) ## Check for sufficient input arguments if (nargin < 3) error (strcat ("CompactClassificationNeuralNetwork.margin:", ... " too few input arguments.")); endif [X, Y] = checkXY_ (this, X, Y, "margin"); [~, scores] = predict (this, X); classes = this.ClassNames; m = zeros (rows (X), 1); ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) idx = gYidx(i); if (isempty (idx)) m(i) = NaN; continue; endif true_score = scores(i, idx); scores(i, idx) = -Inf; m(i) = true_score - max (scores(i,:)); scores(i, idx) = true_score; endfor endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationNeuralNetwork} {@var{e} =} edge (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactClassificationNeuralNetwork} {@var{e} =} edge (@dots{}, @qcode{"Weights"}, @var{w}) ## ## Classification edge of a compact neural network classifier. ## ## @code{@var{e} = edge (@var{obj}, @var{X}, @var{Y})} returns the mean of ## the classification margins over the rows of @var{X}. ## ## @code{@var{e} = edge (@dots{}, @qcode{"Weights"}, @var{w})} takes the ## weighted mean instead, with one weight per row of @var{X}. ## ## @seealso{CompactClassificationNeuralNetwork, ## ClassificationNeuralNetwork, margin, loss, predict} ## @end deftypefn function e = edge (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error (strcat ("CompactClassificationNeuralNetwork.edge:", ... " too few input arguments.")); endif if (mod (numel (varargin), 2) != 0) error (strcat ("CompactClassificationNeuralNetwork.edge:", ... " Name-Value arguments must be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, "edge"); ## The weights are normalized within each class to that class's prior, ## which is what the oracle does and is not the same as dividing by ## their total. This used to divide by the total. ## The weights are parsed before anything is computed, so a bad ## Name-Value pair is reported as such rather than after a margin. W = edgeWeights (varargin, Y, this.ClassNames, this.Prior, ... "CompactClassificationNeuralNetwork", "edge"); m = margin (this, X, Y); e = sum (W .* m(:)) / sum (W); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationNeuralNetwork} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactClassificationNeuralNetwork} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Classification loss of a compact neural network classifier. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} returns the loss of ## the model on the rows of @var{X} against the true labels @var{Y}. ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} accepts the ## following name-value pairs: ## ## @itemize ## @item ## @qcode{"LossFun"} selects the loss. Supported values are ## @qcode{"mincost"}, the default, @qcode{"binodeviance"}, ## @qcode{"classifcost"}, @qcode{"classiferror"}, @qcode{"crossentropy"}, ## @qcode{"exponential"}, @qcode{"hinge"}, @qcode{"logit"} and ## @qcode{"quadratic"}. @qcode{"mincost"} assigns each observation to ## the class of least expected cost and charges what that assignment ## costs, so it reads the scores as a posterior; @qcode{"classifcost"} ## charges what the model's own prediction costs. @qcode{"crossentropy"} ## is defined for a network only. Note that the default differs from the ## other classifiers in this package, which default to ## @qcode{"classiferror"}, and follows MATLAB's for this class. ## ## @item ## @qcode{"Weights"} holds one weight per row of @var{X}, normalised to ## sum to one before it is applied. ## @end itemize ## ## @seealso{CompactClassificationNeuralNetwork, ## ClassificationNeuralNetwork, margin, edge, predict} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error (strcat ("CompactClassificationNeuralNetwork.loss:", ... " too few input arguments.")); endif if (mod (numel (varargin), 2) != 0) error (strcat ("CompactClassificationNeuralNetwork.loss:", ... " Name-Value arguments must be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, "loss"); ## Parse optional arguments LossFun = 'mincost'; lossnames = {'binodeviance', 'classifcost', 'classiferror', ... 'crossentropy', 'exponential', 'hinge', 'logit', ... 'mincost', 'quadratic'}; args = varargin; keep = true (1, numel (args)); for i = 1:2:numel (args) if (strcmpi (args{i}, 'lossfun')) LossFun = args{i+1}; if (! (ischar (LossFun) && isrow (LossFun))) error (strcat ("CompactClassificationNeuralNetwork.loss:", ... " 'LossFun' must be a character vector.")); endif LossFun = tolower (LossFun); if (! any (strcmpi (LossFun, lossnames))) error (strcat ("CompactClassificationNeuralNetwork.loss:", ... " unsupported Loss function.")); endif keep(i:i+1) = false; endif endfor W = getWeights_ (this, args(keep), rows (X), "loss"); W = W(:) / sum (W); [label, scores] = predict (this, X); classes = this.ClassNames; K = classCount (classes); ## Membership of the true class, as a +1/-1 indicator per class Yind = zeros (rows (X), K); ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) idx = gYidx(i); if (isempty (idx)) L = NaN; return; endif Yind(i, idx) = 1; endfor ## The scalar score of the true class of each observation mj = sum (scores .* Yind, 2); switch (LossFun) case 'classiferror' wrong = zeros (rows (X), 1); for i = 1:rows (X) wrong(i) = ! isequal (label(i), Y(i)); endfor L = sum (W .* wrong); case 'binodeviance' L = sum (W .* log (1 + exp (-2 * mj))); case 'hinge' L = sum (W .* max (0, 1 - mj)); case 'exponential' L = sum (W .* exp (-mj)); case 'logit' L = sum (W .* log (1 + exp (-mj))); case 'quadratic' L = sum (W .* (1 - mj) .^ 2); case 'mincost' ## Each observation is assigned to the class of least expected ## cost, and charged what that assignment actually costs given its ## true class. L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) [~, k] = min (scores(i,:) * this.Cost); true_idx = gYidx(i); L = L + W(i) * this.Cost(true_idx, k); endfor case 'classifcost' ## What the model's own prediction costs, given the true class L = 0; ## Resolve every observation's class once, rather than once per ## iteration: the lookup does not depend on i. [gYidx, ~] = labelIndices (classes, Y); for i = 1:rows (X) true_idx = gYidx(i); pred_idx = find (ismember (classes, label(i))); L = L + W(i) * this.Cost(true_idx, pred_idx); endfor case 'crossentropy' ## Defined for a network only, whose scores are a posterior. The ## weights are rescaled to sum to the number of observations, as ## MATLAB documents, and the sum is taken over classes as well. Wn = W * rows (X); L = -sum (Wn .* log (max (mj, realmin))) / (K * rows (X)); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationNeuralNetwork} {} savemodel (@var{obj}, @var{filename}) ## ## Save a CompactClassificationNeuralNetwork object. ## ## @code{savemodel (@var{obj}, @var{filename})} saves each property of a ## CompactClassificationNeuralNetwork object into an Octave binary file, the ## name of which is specified in @var{filename}, along with an extra ## variable, which defines the type classification object these variables ## constitute. Use @code{loadmodel} in order to load a classification ## object into Octave's workspace. ## ## @seealso{loadmodel, fitcnet, ClassificationNeuralNetwork} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error ("CompactClassificationNeuralNetwork.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error ("CompactClassificationNeuralNetwork.savemodel: FNAME must be a character vector."); endif ## Generate variable for class name classdef_name = 'CompactClassificationNeuralNetwork'; ## Create variables from model properties NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; ResponseName = this.ResponseName; ClassNames = this.ClassNames; ScoreTransform = this.ScoreTransform; Sigma = this.Sigma; Mu = this.Mu; LayerSizes = this.LayerSizes; Activations = this.Activations; OutputLayerActivation = this.OutputLayerActivation; LayerWeights = this.LayerWeights; LayerBiases = this.LayerBiases; Cost = this.Cost; Prior = this.Prior; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; STfun = this.STfun; ## Save classdef name and all model properties as individual variables save ('-binary', fname, 'classdef_name', 'NumPredictors', ... 'PredictorNames', 'ResponseName', 'ClassNames', ... 'ScoreTransform', 'Sigma', 'Mu', 'LayerSizes', ... 'Activations', 'OutputLayerActivation', ... ... 'LayerWeights', 'LayerBiases', ... 'Cost', 'Prior', 'CategoricalPredictors', ... 'ExpandedPredictorNames', 'STfun'); endfunction endmethods methods (Access = private) ## Shared validation for the assessment methods, so each reports under ## its own name. function [X, Y] = checkXY_ (this, X, Y, caller) if (isempty (X)) error ("CompactClassificationNeuralNetwork.%s: X is empty.", caller); elseif (this.NumPredictors != columns (X)) error (strcat ("CompactClassificationNeuralNetwork.%s: X must have", ... " the same number of predictors as the trained", ... " neural network model."), caller); endif if (isempty (Y)) error ("CompactClassificationNeuralNetwork.%s: Y is empty.", caller); elseif (rows (X) != rows (Y)) error (strcat ("CompactClassificationNeuralNetwork.%s: Y must have", ... " the same number of rows as X."), caller); endif endfunction ## Pull a "Weights" pair out of the optional arguments, defaulting to a ## uniform weight, and reject any other name. function W = getWeights_ (this, args, n, caller) W = ones (n, 1); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("CompactClassificationNeuralNetwork.%s: parameter", ... " name must be a character vector."), caller); endif if (strcmpi (args{i}, 'weights')) W = args{i+1}; if (! (isnumeric (W) && isvector (W))) error (strcat ("CompactClassificationNeuralNetwork.%s:", ... " 'Weights' must be a numeric vector."), caller); endif if (numel (W) != n) error (strcat ("CompactClassificationNeuralNetwork.%s: size of", ... " 'Weights' must equal the number of", ... " rows in X."), caller); endif else error (strcat ("CompactClassificationNeuralNetwork.%s: invalid", ... " parameter name in optional paired", ... " arguments."), caller); endif endfor endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a ClassificationNeuralNetwork object mdl = CompactClassificationNeuralNetwork (); ## Get fieldnames from DATA (including private properties) names = fieldnames (data); ## The set methods for these read other properties, and one of them ## rebuilds Coeffs, so they are assigned once everything else is in ## place rather than in the order the file happens to list them. late = ismember (names, {'Cost', 'Prior', 'ScoreTransform', ... 'ResponseTransform'}); names = [names(! late); names(late)]; ## Copy data into object for i = 1:numel (names) ## Check fieldnames in DATA match properties in ## CompactClassificationNeuralNetwork try mdl.(names{i}) = data.(names{i}); catch error (strcat ("CompactClassificationNeuralNetwork.load_model:", ... " invalid model in '%s'."), filename) end_try_catch endfor endfunction endmethods endclassdef %!demo %! ## Train a neural network classifier and take its compact version, which %! ## drops the training data but predicts identically. %! %! load fisheriris %! X = meas; %! Y = species; %! %! Mdl = fitcnet (X, Y, 'IterationLimit', 100) %! CMdl = compact (Mdl) %! %! ## The compact model keeps no training data %! isprop (Mdl, 'X') %! isprop (CMdl, 'X') %! %! ## and predicts the same labels %! isequal (predict (Mdl, X), predict (CMdl, X)) ## Test input validation for constructor ## A compact model keeps the character class names and predicts whole names. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Cc = compact (fitcnet (Xch, Ych)); %! rand ("state", 1); randn ("state", 1); Cs = compact (fitcnet (Xch, Ycell)); %! assert_equal (cellstr (Cc.ClassNames), Cs.ClassNames); %! assert_equal (cellstr (predict (Cc, Xch)), predict (Cs, Xch)); ## and reads one back in its assessment methods. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Cc = compact (fitcnet (Xch, Ych)); %! rand ("state", 1); randn ("state", 1); Cs = compact (fitcnet (Xch, Ycell)); %! assert_equal (loss (Cc, Xch, Ych), loss (Cs, Xch, Ycell), 1e-12); %!error ... %! CompactClassificationNeuralNetwork (1) ## Test output for predict method %!shared x, y, CMdl %! load fisheriris %! x = meas; %! y = grp2idx (species); %! Mdl = fitcnet (x, y, 'IterationLimit', 100); %! CMdl = compact (Mdl); ## Test input validation for predict method %!error ... %! predict (CMdl) %!error ... %! predict (CMdl, []) %!error ... %! predict (CMdl, 1) ## Test input validation for assigning a new ScoreTransform %!error ... %! CMdl.ScoreTransform = 'a'; ## The compact model carries the trained parameters and the class bookkeeping. %!test %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], ... %! 'LayerSizes', [3, 2], 'IterationLimit', 20); %! CMdl = compact (Mdl); %! assert_equal (CMdl.LayerWeights, Mdl.LayerWeights); %! assert_equal (CMdl.LayerBiases, Mdl.LayerBiases); %! assert_equal (CMdl.Cost, Mdl.Cost); %! assert_equal (CMdl.Prior, Mdl.Prior); %! assert_equal (CMdl.CategoricalPredictors, Mdl.CategoricalPredictors); %! assert_equal (CMdl.ExpandedPredictorNames, Mdl.ExpandedPredictorNames); ## margin, edge and loss agree with the model the object was compacted from. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, 'IterationLimit', 20); %! CMdl = compact (Mdl); %! assert_equal (margin (CMdl, meas, species), margin (Mdl, meas, species)); %! assert_equal (edge (CMdl, meas, species), edge (Mdl, meas, species)); %! assert_equal (loss (CMdl, meas, species), loss (Mdl, meas, species)); ## Every loss function agrees with the full model's. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, 'IterationLimit', 20); %! CMdl = compact (Mdl); %! names = {'binodeviance', 'classifcost', 'classiferror', 'crossentropy', ... %! 'exponential', 'hinge', 'logit', 'mincost', 'quadratic'}; %! for k = 1:numel (names) %! assert_equal (loss (CMdl, meas, species, 'LossFun', names{k}), ... %! loss (Mdl, meas, species, 'LossFun', names{k})); %! endfor ## A weighted edge weights the margins it averages. %!test %! load fisheriris %! CMdl = compact (fitcnet (meas, species, 'IterationLimit', 20)); %! ## The weights are normalized within each class to that class's prior, %! ## not divided by their total. A weight constant within a class therefore %! ## leaves the edge exactly where the unweighted one is. %! w = [ones(50, 1); 2 * ones(50, 1); 3 * ones(50, 1)]; %! m = margin (CMdl, meas, species); %! assert_equal (edge (CMdl, meas, species, 'Weights', w), ... %! edge (CMdl, meas, species), 1e-12); ## Test input validation for margin method %!error ... %! margin (CMdl) %!error ... %! margin (CMdl, x) %!error ... %! margin (CMdl, [], y) %!error ... %! margin (CMdl, 1, y) %!error ... %! margin (CMdl, x, []) %!error ... %! margin (CMdl, x, y(1:10)) ## Test input validation for edge method %!error ... %! edge (CMdl, x) %!error ... %! edge (CMdl, x, y, 'Weights') %!error ... %! edge (CMdl, x, y, 'LossFun', 'hinge') %!error ... %! edge (CMdl, x, y, 'Weights', 'a') %!error ... %! edge (CMdl, x, y, 'Weights', [1, 2, 3]) ## Test input validation for loss method %!error ... %! loss (CMdl, x) %!error ... %! loss (CMdl, x, y, 'LossFun') %!error ... %! loss (CMdl, x, y, 'LossFun', 1) %!error ... %! loss (CMdl, x, y, 'LossFun', 'nonsense') ## A saved and reloaded compact model carries every property it holds. %!test %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], ... %! 'LayerSizes', [3, 2], 'IterationLimit', 20); %! CMdl = compact (Mdl); %! fname = tempname (); %! savemodel (CMdl, fname); %! CMdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (CMdl2.LayerWeights, CMdl.LayerWeights); %! assert_equal (CMdl2.LayerBiases, CMdl.LayerBiases); %! assert_equal (CMdl2.LayerSizes, CMdl.LayerSizes); %! assert_equal (CMdl2.ClassNames, CMdl.ClassNames); ## A reloaded compact model predicts exactly what the full model did. %!test %! load fisheriris %! Mdl = fitcnet (meas, species, 'IterationLimit', 20); %! fname = tempname (); %! savemodel (compact (Mdl), fname); %! CMdl2 = loadmodel (fname); %! delete (fname); %! [label, score] = predict (Mdl, meas); %! [label2, score2] = predict (CMdl2, meas); %! assert_equal (label2, label); %! assert_equal (score2, score); ## A non-default ScoreTransform survives compacting and a save and load. %!test %! Mdl = fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2], 'IterationLimit', 20); %! Mdl.ScoreTransform = 'symmetric'; %! fname = tempname (); %! savemodel (compact (Mdl), fname); %! CMdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (CMdl2.ScoreTransform, 'symmetric'); %! [~, s1] = predict (CMdl2, [1, 2; 4, 5]); %! [~, s2] = predict (compact (Mdl), [1, 2; 4, 5]); %! assert_equal (s1, s2); %!error ... %! savemodel (CompactClassificationNeuralNetwork ()) %!error ... %! savemodel (CompactClassificationNeuralNetwork (), 1) %!error ... %! savemodel (CompactClassificationNeuralNetwork (), ['ab'; 'cd']) ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! Mdl = compact (fitcnet (meas, species, 'IterationLimit', 20)); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'CompactClassificationNeuralNetwork'); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ScoreTransform), class (Mdl.ScoreTransform)); %! assert_equal (predict (M2, meas(1:5,:)), predict (Mdl, meas(1:5,:))); %!error ... %! CMdl = compact (fitcnet ([1, 2; 2, 3; 3, 4; 4, 5], [1; 1; 2; 2])); %! CMdl.Cost = 1:4; ## The shared cost guard is in force here too, and the struct form is ## permuted into this model's class order. The battery is on ## ClassificationDiscriminant. %!test %! load fisheriris %! Mdl = compact (fitcnet (meas, species)); %! S = struct ('ClassNames', {{'virginica'; 'setosa'; 'versicolor'}}, ... %! 'ClassificationCosts', [0, 1, 2; 3, 0, 4; 5, 6, 0]); %! Mdl.Cost = S; %! assert_equal (Mdl.Cost, [0, 4, 3; 6, 0, 5; 1, 2, 0]); %!error ... %! load fisheriris %! Mdl = compact (fitcnet (meas, species)); %! Mdl.Cost = ones (3); ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = compact (fitcnet (meas, species)); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = compact (fitcnet (meas, species)); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/CompactClassificationSVM.m000066400000000000000000001501711524624707500273070ustar00rootroot00000000000000## Copyright (C) 2024-2026 Andreas Bertsatos ## Copyright (C) 2025 Swayam Shah ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef CompactClassificationSVM ## -*- texinfo -*- ## @deftp {statistics} CompactClassificationSVM ## ## Compact Support Vector Machine classification ## ## The @code{CompactClassificationSVM} class implements a compact version of a ## Support Vector Machine classifier object for one-class or two-class ## problems, which can predict responses for new data using the @code{predict} ## method. ## ## A @code{CompactClassificationSVM} object is a compact version of a support ## vector machine model, @code{ClassificationSVM}. It does not include the ## training data resulting in a smaller classifier size, which can be used for ## making predictions from new data, but not for tasks such as cross ## validation. It can only be created from a @code{ClassificationSVM} model ## by using the @code{compact} object method. ## ## @seealso{ClassificationSVM} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} NumPredictors ## ## Number of predictors ## ## A positive integer value specifying the number of predictors in the ## training dataset used for training the SVM model. This property is ## read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} PredictorNames ## ## Names of predictor variables ## ## A cell array of character vectors specifying the names of the predictor ## variables. The names are in the order in which they appear in the ## training dataset. This property is read-only. ## ## @end deftp PredictorNames = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} KernelParameters ## ## Parameters of the kernel function ## ## A structure with fields @qcode{Function} and @qcode{Scale}, and ## @qcode{Order} for a polynomial kernel. @qcode{Function} names the ## kernel as MATLAB names it, so a radial basis kernel reports ## @qcode{'gaussian'} whichever spelling was given. This property is ## read-only. ## ## @end deftp KernelParameters = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector of column indices into @code{X} naming the predictors ## treated as categorical, and empty when none is. This property is ## read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} ExpandedPredictorNames ## ## Names of the predictors as the model expanded them ## ## A cell array of character vectors. It matches @code{PredictorNames} ## unless a categorical predictor was expanded into indicator variables. ## This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} ResponseName ## ## Response variable name ## ## A character vector specifying the name of the response variable @var{Y}. ## This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} ClassNames ## ## Names of classes in the response variable ## ## An array of unique values of the response variable @var{Y}, which has the ## same data types as the data in @var{Y}. This property is read-only. ## @qcode{ClassNames} can have any of the following datatypes: ## ## @itemize ## @item Cell array of character vectors ## @item Character array ## @item Logical vector ## @item Numeric vector ## @end itemize ## ## @end deftp ClassNames = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} Prior ## ## Prior probabilities of the classes ## ## A numeric row vector with one entry per class, in the order of ## @code{ClassNames}, summing to one. This property is read-only. ## ## @end deftp Prior = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} Cost ## ## Cost of misclassification ## ## A numeric square matrix, where @code{Cost(i,j)} is the cost of ## classifying an observation of class @math{i} as class @math{j}. This ## property is read-only. ## ## @end deftp Cost = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} Sigma ## ## Predictor standard deviations ## ## A numeric vector of the same length as the columns in @var{X} containing ## the standard deviations of predictor variables. If the predictor ## variables have not been standardized, then @qcode{Sigma} is empty. ## This property is read-only. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} Mu ## ## Predictor means ## ## A numeric vector of the same length as the columns in @var{X} containing ## the means of predictor variables. If the predictor variables have not ## been standardized, then @qcode{Mu} is empty. This property is read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} Alpha ## ## Trained classifier coefficients ## ## The coefficients of the trained SVM classifier specified as an @math{s*1} ## numeric vector, where @math{s} is the number of support vectors, ## @qcode{rows (obj.SupportVectors)}. If the SVM classifier was trained ## with a kernel function other than @qcode{'linear'}, then @qcode{Alpha} is ## empty. This property is read-only. ## ## @end deftp Alpha = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} Beta ## ## Linear predictor coefficients ## ## The linear predictor coefficients specified as an @math{s*1} numeric ## vector, where @math{s} is the number of support vectors, ## @qcode{rows (obj.SupportVectors)}. If the SVM classifier was trained ## with a @qcode{'linear'} kernel function, then @qcode{Beta} is empty. ## This property is read-only. ## ## @end deftp Beta = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} Bias ## ## Bias term ## ## The bias term specified as a scalar. This property is read-only. ## ## @end deftp Bias = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} SupportVectorLabels ## ## Support vector class labels ## ## The support vector class labels specified as an @math{s*1} numeric ## vector, where @math{s} is the number of support vectors, ## @qcode{rows (obj.SupportVectors)}. A value of +1 in ## @code{SupportVectorLabels} indicates that the corresponding support ## vector belongs to the positive class @qcode{(ClassNames@{2@})}. A value ## of -1 indicates that the corresponding support vector belongs to the ## negative class @qcode{(ClassNames@{1@})}. This property is read-only. ## ## @end deftp SupportVectorLabels = []; ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} SupportVectors ## ## Support vectors ## ## The support vectors of the trained SVM classifier specified an @math{s*p} ## numeric matrix, where @math{s} is the number of support vectors, ## @qcode{rows (obj.SupportVectors)}, and @math{p} is the number of ## predictor variables in the predictor data. This property is read-only. ## ## @end deftp SupportVectors = []; endproperties ## The LIBSVM structure the engine works in. It is ours alone, with no ## MATLAB counterpart, so it is kept out of the property listing while ## staying readable for anyone who needs the raw model. properties (Hidden, GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} Model ## ## Trained SVM model ## ## A structure containing the trained model in @qcode{'libsvm'} format. ## This property is read-only. ## ## It is the engine's own structure and has no MATLAB counterpart, ## so it is kept out of @code{properties} and out of the online ## documentation. Reading it works exactly as it always did. ## ## @end deftp Model = []; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {CompactClassificationSVM} {property} ScoreTransform ## ## Transformation function for classification scores ## ## Specified as a function handle for transforming the classification ## scores. Add or change the @qcode{ScoreTransform} property using dot ## notation as in: ## ## @itemize ## @item @qcode{@var{obj}.ScoreTransform = 'function_name'} ## @item @qcode{@var{obj}.ScoreTransform = @@function_handle} ## @end itemize ## ## When specified as a character vector, it can be any of the following ## built-in functions. Nevertheless, the @qcode{ScoreTransform} property ## always stores their function handle equivalent. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'doublelogit'} @tab @math{1 ./ (1 + exp (-2 * x))} ## @item @qcode{'invlogit'} @tab @math{log (x ./ (1 - x))} ## @item @qcode{'ismax'} @tab Sets the score for the class with the ## largest score to 1, and for all other classes to 0 ## @item @qcode{'logit'} @tab @math{1 ./ (1 + exp (-x))} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'sign'} @tab ## @math{-1 for x < 0, 0 for x = 0, 1 for x > ## 0} ## @item @qcode{'symmetric'} @tab @math{2 * x - 1} ## @item @qcode{'symmetricismax'} @tab Sets the score for the class ## with the largest score to 1, and for all other classes to -1 ## @item @qcode{'symmetriclogit'} @tab @math{2 ./ (1 + exp (-x)) - 1} ## @end multitable ## ## @end deftp ScoreTransform = 'none'; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) STfun = @(x) x; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.ScoreTransform (this, val) name = 'CompactClassificationSVM'; try [this.STfun, this.ScoreTransform] = parseScoreTransform (val, name); catch error (strcat ("CompactClassificationSVM.subsasgn:", ... " 'ScoreTransform' must be a", ... " 'function_handle' object.")); end_try_catch endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationSVM} {@var{obj} =} CompactClassificationSVM (@var{Mdl}) ## @deftypefnx {CompactClassificationSVM} {@var{obj} =} CompactClassificationSVM () ## ## Create a @code{CompactClassificationSVM} object. ## ## @var{Mdl} is the @code{ClassificationSVM} object to ## compact. The documented way to reach this constructor is the ## @code{compact} method. ## ## Called with no arguments it returns an object with its properties ## empty, which is how a saved model is rebuilt before its values are ## filled in. ## ## @end deftypefn function this = CompactClassificationSVM (Mdl = []) ## Check for appropriate class if (isempty (Mdl)) return; elseif (! strcmpi (class (Mdl), 'ClassificationSVM')) error (strcat ("CompactClassificationSVM: invalid", ... " classification object.")); endif ## Save properties to compact model this.NumPredictors = Mdl.NumPredictors; this.PredictorNames = Mdl.PredictorNames; this.CategoricalPredictors = Mdl.CategoricalPredictors; this.ExpandedPredictorNames = Mdl.ExpandedPredictorNames; this.ResponseName = Mdl.ResponseName; this.ClassNames = Mdl.ClassNames; this.Prior = Mdl.Prior; this.Cost = Mdl.Cost; this.ScoreTransform = Mdl.ScoreTransform; this.STfun = Mdl.STfun; this.Sigma = Mdl.Sigma; this.Mu = Mdl.Mu; this.KernelParameters = Mdl.KernelParameters; this.Model = Mdl.Model; this.Alpha = Mdl.Alpha; this.Beta = Mdl.Beta; this.Bias = Mdl.Bias; this.SupportVectorLabels = Mdl.SupportVectorLabels; this.SupportVectors = Mdl.SupportVectors; endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n CompactClassificationSVM\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); if (iscellstr (this.ClassNames)) str = repmat ({'''%s'''}, 1, numel (this.ClassNames)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.ClassNames{:}); elseif (ischar (this.ClassNames)) str = repmat ({'''%s'''}, 1, rows (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, cellstr (this.ClassNames){:}); else # single, double, logical str = repmat ({'%d'}, 1, numel (this.ClassNames)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.ClassNames); endif fprintf ("%+25s: %s\n", 'ClassNames', str); fprintf ("%+25s: '%s'\n", 'ScoreTransform', this.ScoreTransform); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); fprintf ("%+25s: [%dx1 double]\n", 'Alpha', numel (this.Alpha)); if (! isempty (this.Beta)) fprintf ("%+25s: [%dx1 double]\n", 'Beta', numel (this.Beta)); endif fprintf ("%+25s: %f\n", 'Bias', this.Bias); fprintf ("%+25s: '%s'\n", 'KernelFunction', ... this.KernelParameters.Function); fprintf ("%+25s: [%dx%d double]\n", 'SupportVectors', ... rows (this.SupportVectors), columns (this.SupportVectors)); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {CompactClassificationSVM} {@var{obj} =} discardSupportVectors (@var{obj}) ## ## Discard the support vectors of a linear SVM model. ## ## @code{@var{obj} = discardSupportVectors (@var{obj})} empties ## @code{Alpha}, @code{SupportVectors} and ## @code{SupportVectorLabels}, leaving @code{Beta} and @code{Bias} to ## decide every prediction. A linear kernel needs nothing else, so the ## returned model predicts what it predicted before while carrying one ## vector in place of many. ## ## The kernel must be linear. Under any other the support vectors are ## part of the decision function and cannot be dropped. Discarding twice ## is not an error and changes nothing. ## ## @seealso{fitcsvm, ClassificationSVM, CompactClassificationSVM} ## @end deftypefn function this = discardSupportVectors (this) if (nargin != 1) print_usage (); endif if (! strcmpi (this.KernelParameters.Function, 'linear')) error (strcat ("CompactClassificationSVM.discardSupportVectors:", ... " you cannot discard support vectors for a", ... " non-linear kernel.")); endif ## The engine keeps its own copy of the support vectors, so emptying ## the properties alone would free nothing. Collapsing the model onto ## the single vector that decides it leaves every scoring path as it ## was, svmpredict going on being the engine over one vector. this.Model = discardSVs (this.Model); this.Alpha = []; this.SupportVectors = []; this.SupportVectorLabels = []; endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationSVM} {@var{label} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {CompactClassificationSVM} {[@var{label}, @var{score}] =} predict (@var{obj}, @var{XC}) ## @deftypefnx {CompactClassificationSVM} {[@var{label}, @var{score}, @var{cost}] =} predict (@var{obj}, @var{XC}) ## ## Classify new data points into categories using the Support Vector Machine ## classification model from a CompactClassificationSVM object. ## ## @code{@var{label} = predict (@var{obj}, @var{XC})} returns the vector of ## labels predicted for the corresponding instances in @var{XC}, using the ## predictor data in the CompactClassificationSVM model, @var{obj}. For ## one-class SVM model, +1 or -1 is returned. ## ## @itemize ## @item ## @var{obj} must be a @qcode{CompactClassificationSVM} class object. ## @item ## @var{XC} must be an @math{M*P} numeric matrix with the same number of ## features @math{P} as the corresponding predictors of the SVM model in ## @var{obj}. ## @end itemize ## ## @code{[@var{label}, @var{score}] = predict (@var{obj}, @var{XC})} also ## returns @var{score}, which contains the decision values for each ## prediction. A @qcode{ScoreTransform} assigned to @var{obj} is applied ## to them, so @var{score} holds whatever that transform returns. Posterior ## probabilities need a transform fitted to the model, which this package ## does not compute yet. ## ## @seealso{CompactClassificationSVM, ClassificationSVM} ## ## @strong{Deviation from MATLAB.} @var{cost} is the expected cost of ## each assignment, @math{sum_j P(j) Cost(j,k)}. An SVM score is a ## signed distance to the boundary and not a posterior, so the only ## distribution available is the one concentrated on the predicted class ## and @var{cost} is the row of @qcode{Cost} belonging to it. MATLAB ## returns the @strong{column} instead, which is the same matrix read the ## wrong way and contradicts its own @code{ClassificationKNN}, ## @code{ClassificationDiscriminant} and @code{ClassificationNaiveBayes} ## on any asymmetric cost matrix; the two agree wherever @qcode{Cost} is ## symmetric, the default included. Measured on R2024a. ## ## @end deftypefn function [labels, scores, cost] = predict (this, XC) ## Check for sufficient input arguments if (nargin < 2) error ("CompactClassificationSVM.predict: too few input arguments."); endif ## Check for valid XC if (isempty (XC)) error ("CompactClassificationSVM.predict: XC is empty."); elseif (this.NumPredictors != columns (XC)) error (strcat ("CompactClassificationSVM.predict: XC must have", ... " the same number of predictors as the trained", ... " SVM model.")); endif ## Standardize (if necessary) if (! isempty (this.Mu)) XC = (XC - this.Mu) ./ this.Sigma; endif ## Predict labels and scores from new data [out, ~, scores] = svmpredict (ones (rows (XC), 1), XC, this.Model, '-q'); ## Expand scores for two classes if (classCount (this.ClassNames) == 2) scores = [scores, -scores]; endif ## Translate labels to classnames. Indexing the class names by a ## per-observation class number keeps every response type on one path: ## assigning into a preallocated result instead has to know that a ## character matrix holds a name per row and not per element. idx = 2 - (out == 1); labels = labelsFromIndex (this.ClassNames, idx); ## The expected cost of each assignment, sum_j P(j) * Cost(j,k). An ## SVM score is a signed distance and not a posterior, so the only ## honest distribution is the one concentrated on the predicted class, ## which leaves the row of Cost belonging to it. ## ## MATLAB returns the *column* here, Cost(:,k)', which is the same ## matrix read the wrong way and disagrees with its own KNN, ## discriminant and naive Bayes on any asymmetric cost matrix. Both ## agree when Cost is symmetric, the default included. cost = this.Cost(idx, :); if (nargout > 1) ## Apply ScoreTransform scores = this.STfun (scores); endif endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationSVM} {@var{m} =} margin (@var{obj}, @var{X}, @var{Y}) ## ## Classification margins for Support Vector Machine classifier. ## ## @code{@var{m} = margin (@var{obj}, @var{X}, @var{Y})} returns ## the classification margins for @var{obj} with data @var{X} and ## classification @var{Y}. @var{m} is a numeric vector of length size (X,1). ## ## @itemize ## @item ## @var{obj} is a @var{CompactClassificationSVM} object. ## @item ## @var{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. ## @item ## @var{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} must have same ## numbers of Rows as @var{X}. ## @end itemize ## ## The classification margin for each observation is the difference between ## the classification score for the true class and the maximal ## classification score for the false classes. ## ## @seealso{CompactClassificationSVM} ## @end deftypefn function m = margin (this, X, Y) ## Check for sufficient input arguments if (nargin < 3) error ("CompactClassificationSVM.margin: too few input arguments."); endif ## Check for valid X if (isempty (X)) error ("CompactClassificationSVM.margin: X is empty."); elseif (this.NumPredictors != columns (X)) error (strcat ("CompactClassificationSVM.margin: X must", ... " have the same number of predictors as", ... " the trained SVM model.")); endif ## Check for valid Y if (isempty (Y)) error ("CompactClassificationSVM.margin: Y is empty."); elseif (rows (X) != rows (Y)) error (strcat ("CompactClassificationSVM.margin: Y must have", ... " the same number of rows as X.")); endif ## Y may be the class labels, which is what this method documents and ## what MATLAB accepts, or already the +1/-1 coding the solver works in. ## It used to be the latter only, so passing the labels the docstring ## promises reached LIBSVM as a cell array and raised its own message. Ypm = svmPlusMinus (Y, this.ClassNames); [~, ~, dec_values_L] = svmpredict (Ypm, X, this.Model, '-q'); m = 2 * Ypm .* dec_values_L; endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationSVM} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactClassificationSVM} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Compute loss for a trained CompactClassificationSVM object. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} computes the loss, ## @var{L}, using the default loss function @qcode{'classiferror'}. ## ## @itemize ## @item ## @code{obj} is a @var{CompactClassificationSVM} object. ## @item ## @code{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or ## variables. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels ## of corresponding predictor data in @var{X}. @var{Y} must have same ## numbers of Rows as @var{X}. ## @end itemize ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} allows ## additional options specified by @var{name}-@var{value} pairs: ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'LossFun'} @tab Specifies the loss function to use. ## Can be a function handle with four input arguments (C, S, W, Cost) ## which returns a scalar value or one of: ## 'binodeviance', 'classifcost', 'classiferror', 'exponential', ## 'hinge', 'logit','mincost', 'quadratic'. ## @itemize ## @item ## @code{C} is a logical matrix of size @math{N*K}, where @math{N} is the ## number of observations and @math{K} is the number of classes. ## The element @code{C(i,j)} is true if the class label of the i-th ## observation is equal to the j-th class. ## @item ## @code{S} is a numeric matrix of size @math{N*K}, where each element ## represents the classification score for the corresponding class. ## @item ## @code{W} is a numeric vector of length @math{N}, representing ## the observation weights. ## @item ## @code{Cost} is a @math{K*K} matrix representing the misclassification ## costs. ## @end itemize ## ## @item @qcode{'Weights'} @tab Specifies observation weights, must be ## a numeric vector of length equal to the number of rows in X. ## Default is @code{ones (size (X, 1))}. loss normalizes the weights so that ## observation weights in each class sum to the prior probability of that ## class. When you supply Weights, loss computes the weighted ## classification loss. ## ## @end multitable ## ## @seealso{CompactClassificationSVM} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("CompactClassificationSVM.loss: too few input arguments."); endif if (mod (nargin, 2) == 0) error (strcat ("CompactClassificationSVM.loss: Name-Value", ... " arguments must be in pairs.")); endif ## Check for valid X if (isempty (X)) error ("CompactClassificationSVM.loss: X is empty."); elseif (this.NumPredictors != columns (X)) error (strcat ("CompactClassificationSVM.loss: X must", ... " have the same number of predictors as", ... " the trained SVM model.")); endif ## Check for valid Y if (isempty (Y)) error ("CompactClassificationSVM.loss: Y is empty."); elseif (rows (X)!= rows (Y)) error (strcat ("CompactClassificationSVM.loss: Y must have", ... " the same number of rows as X.")); endif ## Set default values before parsing optional parameters LossFun = 'classiferror'; Weights = ones (size (X, 1), 1); ## Parse extra parameters while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'lossfun' LossFun = varargin{2}; if (! (ischar (LossFun))) error (strcat ("CompactClassificationSVM.loss: 'LossFun'", ... " must be a character vector.")); endif LossFun = tolower (LossFun); if (! any (strcmpi (LossFun, {'binodeviance', 'classiferror', ... 'classifcost', 'exponential', ... 'hinge', 'logit', 'mincost', ... 'quadratic'}))) error (strcat ("CompactClassificationSVM.loss:", ... " unsupported Loss function.")); endif case 'weights' Weights = varargin{2}; ## Validate if weights is a numeric vector if (! (isnumeric (Weights) && isvector (Weights))) error (strcat ("CompactClassificationSVM.loss: 'Weights'", ... " must be a numeric vector.")); endif ## Check if the size of weights matches the number of rows in X if (numel (Weights) != size (X, 1)) error (strcat ("CompactClassificationSVM.loss: size of", ... " 'Weights' must be equal to the number", ... " of rows in X.")); endif otherwise error (strcat ("CompactClassificationSVM.loss: invalid", ... " parameter name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## Compute the classification score ## Y may be the class labels, as this documents and MATLAB ## accepts, or already the solver's own +1/-1 coding. Ypm = svmPlusMinus (Y, this.ClassNames); [~, ~, dec_values_L] = svmpredict (Ypm, X, this.Model, '-q'); ## Compute the margin margin = Ypm .* dec_values_L; ## Compute the loss based on the specified loss function switch (LossFun) case 'classiferror' L = mean ((margin <= 0) .* Weights); case 'hinge' L = mean (max (0, 1 - margin) .* Weights); case 'logit' L = mean (log (1 + exp (-margin)) .* Weights); case 'exponential' L = mean (exp (-margin) .* Weights); case 'quadratic' L = mean (((1 - margin) .^2) .* Weights); case 'binodeviance' L = mean (log (1 + exp (-2 * margin)) .* Weights); case 'mincost' ## Each observation is assigned to the class of least expected ## cost, and charged what that assignment actually costs given ## its true class. Y is the +1/-1 coding the margin above uses, ## in which +1 is the first of ClassNames and -1 the second. [~, scores] = predict (this, X); true_idx = ones (rows (X), 1); true_idx(Y == -1) = 2; L = 0; for i = 1:rows (X) [~, k] = min (scores(i,:) * this.Cost); L = L + Weights(i) * this.Cost(true_idx(i), k); endfor L = L / rows (X); case 'classifcost' ## What the model's own prediction costs, given the true class pred_idx = ones (rows (X), 1); pred_idx(dec_values_L <= 0) = 2; true_idx = ones (rows (X), 1); true_idx(Y == -1) = 2; L = 0; for i = 1:rows (X) L = L + Weights(i) * this.Cost(true_idx(i), pred_idx(i)); endfor L = L / rows (X); otherwise error ("CompactClassificationSVM.loss: unsupported Loss function."); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationSVM} {@var{e} =} edge (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactClassificationSVM} {@var{e} =} edge (@dots{}, @qcode{"Weights"}, @var{w}) ## ## Classification edge, the mean of the classification margins. ## ## @code{@var{e} = edge (@var{obj}, @var{X}, @var{Y})} reduces the vector ## that @code{margin} returns to a single number, the mean margin over the ## rows of @var{X}. It says how far the model puts the true class ahead of ## its nearest rival on average, so a larger edge is a better model, and ## unlike a loss it is not bounded above and rewards confidence rather than ## bare correctness. ## ## @code{@var{e} = edge (@dots{}, @qcode{"Weights"}, @var{w})} takes the ## weighted mean instead, with one weight per row of @var{X}. ## ## @end deftypefn function e = edge (this, X, Y, varargin) if (nargin < 3) error ("CompactClassificationSVM.edge: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("CompactClassificationSVM.edge: Name-Value", ... " arguments must be in pairs.")); endif ## The weights are parsed before anything is computed, so a bad ## Name-Value pair is reported as such rather than after a margin. W = edgeWeights (varargin, Y, this.ClassNames, this.Prior, ... "CompactClassificationSVM", "edge"); m = margin (this, X, Y); e = sum (W .* m(:)) / sum (W); endfunction ## -*- texinfo -*- ## @deftypefn {CompactClassificationSVM} {} savemodel (@var{obj}, @var{filename}) ## ## Save a CompactClassificationSVM object. ## ## @code{savemodel (@var{obj}, @var{filename})} saves each property of a ## CompactClassificationSVM object into an Octave binary file, the name of ## which is specified in @var{filename}, along with an extra variable, ## which defines the type classification object these variables constitute. ## Use @code{loadmodel} in order to load a classification object into ## Octave's workspace. ## ## @seealso{loadmodel, ClassificationSVM, CompactClassificationSVM} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error ("CompactClassificationSVM.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error ("CompactClassificationSVM.savemodel: FNAME must be a character vector."); endif ## Generate variable for class name classdef_name = 'CompactClassificationSVM'; ## Create variables from model properties NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; ResponseName = this.ResponseName; ClassNames = this.ClassNames; Prior = this.Prior; Cost = this.Cost; ScoreTransform = this.ScoreTransform; Sigma = this.Sigma; Mu = this.Mu; KernelParameters = this.KernelParameters; Model = this.Model; Alpha = this.Alpha; Beta = this.Beta; Bias = this.Bias; SupportVectorLabels = this.SupportVectorLabels; SupportVectors = this.SupportVectors; STfun = this.STfun; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; ## Save classdef name and all model properties as individual variables save ('-binary', fname, 'classdef_name', 'NumPredictors', ... 'PredictorNames', 'ResponseName', 'ClassNames', ... 'Prior', 'Cost', ... 'ScoreTransform', 'Sigma', 'Mu', ... 'Model', 'Alpha', 'Beta', 'Bias', ... 'SupportVectorLabels', 'SupportVectors', ... 'CategoricalPredictors', 'ExpandedPredictorNames', ... 'KernelParameters', 'STfun'); endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a ClassificationSVM object mdl = CompactClassificationSVM (); ## Copy the saved data into the object. Iterate over what was ## saved rather than over fieldnames (mdl): a private property such ## as STfun is written out by savemodel but is not reported by ## fieldnames, so comparing the two sets could never match and every ## load failed. Assignment is legal here because this is a method of ## the class itself. names = fieldnames (data); ## The set methods for these read other properties, and one of them ## rebuilds Coeffs, so they are assigned once everything else is in ## place rather than in the order the file happens to list them. late = ismember (names, {'Cost', 'Prior', 'ScoreTransform', ... 'ResponseTransform'}); names = [names(! late); names(late)]; for i = 1:numel (names) try mdl.(names{i}) = data.(names{i}); catch msg = 'CompactClassificationSVM.load_model: invalid model in ''%s''.'; error (msg, filename); end_try_catch endfor endfunction endmethods endclassdef %!demo %! ## Create a support vectors machine classifier and its compact version %! rng (42); %! # and compare their size %! %! load fisheriris %! X = meas; %! Y = species; %! %! selected_classes = unique (Y)(randperm (3, 2)); %! selected_indices = ismember (Y, selected_classes); %! X_selected = X(selected_indices, :); %! Y_selected = Y(selected_indices); %! Mdl = fitcsvm (X_selected, Y_selected, 'ClassNames', selected_classes); %! CMdl = crossval (Mdl) ## Test input validation for constructor ## discardSupportVectors empties what R2024a empties and keeps what it ## keeps: Alpha and the support vectors go, Beta, Bias and IsSupportVector ## stay, and the class is unchanged. %!test %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,:); y = species(keep); %! Mdl = compact (fitcsvm (X, y, "KernelFunction", "linear")); %! D = discardSupportVectors (Mdl); %! assert_equal (class (D), "CompactClassificationSVM"); %! assert_equal (isempty (D.Alpha), true); %! assert_equal (isempty (D.SupportVectors), true); %! assert_equal (isempty (D.SupportVectorLabels), true); %! assert_equal (D.Beta, Mdl.Beta); %! assert_equal (D.Bias, Mdl.Bias); ## A linear decision needs only Beta and Bias, so the model predicts what it ## predicted before. %!test %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,:); y = species(keep); %! Mdl = compact (fitcsvm (X, y, "KernelFunction", "linear")); %! D = discardSupportVectors (Mdl); %! assert_equal (predict (D, X), predict (Mdl, X), 1e-10); ## The saving is real rather than cosmetic: the engine keeps its own copy of ## the support vectors, and it collapses to the one vector that decides a ## linear model. Emptying the properties alone would free nothing. %!test %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,:); y = species(keep); %! Mdl = compact (fitcsvm (X, y, "KernelFunction", "linear")); %! D = discardSupportVectors (Mdl); %! assert_equal (rows (Mdl.Model.SVs) > 1, true); %! assert_equal (rows (D.Model.SVs), 1); %! assert_equal (predict (discardSupportVectors (D), X), predict (D, X)); ## A compact model keeps the character class names and predicts whole names. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Cc = compact (fitcsvm (Xch, Ych)); %! rand ("state", 1); randn ("state", 1); Cs = compact (fitcsvm (Xch, Ycell)); %! assert_equal (cellstr (Cc.ClassNames), Cs.ClassNames); %! assert_equal (cellstr (predict (Cc, Xch)), predict (Cs, Xch)); ## and reads one back in its assessment methods. %!test %! load fisheriris %! bch = ! strcmp (species, "setosa"); %! Xch = meas(bch,:); Ycell = species(bch); Ych = char (Ycell); %! rand ("state", 1); randn ("state", 1); Cc = compact (fitcsvm (Xch, Ych)); %! rand ("state", 1); randn ("state", 1); Cs = compact (fitcsvm (Xch, Ycell)); %! assert_equal (loss (Cc, Xch, Ych), loss (Cs, Xch, Ycell), 1e-12); %!error ... %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,:); y = species(keep); %! discardSupportVectors (compact (fitcsvm (X, y, "KernelFunction", "rbf"))) %!error ... %! CompactClassificationSVM (1) ## Test output for predict method %!shared x, y, CMdl %! load fisheriris %! inds = ! strcmp (species, 'setosa'); %! x = meas(inds, 3:4); %! y = grp2idx (species(inds)); %!test %! xc = [min(x); mean(x); max(x)]; %! Mdl = fitcsvm (x, y, 'KernelFunction', 'rbf', 'Tolerance', 1e-7); %! CMdl = compact (Mdl); %! assert_equal (isempty (CMdl.Beta), true) %! assert_equal (rows (CMdl.SupportVectors), numel (CMdl.Alpha)) %! [label, score] = predict (CMdl, xc); %! assert_equal (label, [1; 2; 2]); %! assert_equal (score(:,1), [0.99285; -0.080296; -0.93694], 1e-5); %! assert_equal (score(:,1), -score(:,2), eps) %!test %! Mdl = fitcsvm (x, y); %! CMdl = compact (Mdl); %! assert_equal (CMdl.Beta, [2.182926829268275; 2.253658536585344], 1e-5) %! assert_equal (rows (CMdl.SupportVectors), numel (CMdl.Alpha)) %! assert_equal (numel (CMdl.Alpha), 24) %! assert_equal (CMdl.Bias, -14.415, 1e-3) %! xc = [min(x); mean(x); max(x)]; %! label = predict (CMdl, xc); %! assert_equal (label, [1; 2; 2]); ## Test input validation for predict method %!error ... %! predict (CMdl) %!error ... %! predict (CMdl, []) %!error ... %! predict (CMdl, 1) %!error ... %! CMdl.ScoreTransform = 'a'; ## Every property the documentation calls read-only is read-only. Without a ## subsasgn of its own the class took any assignment: Bias could be replaced ## outright, and a character vector could be stored where predict expects a ## function handle. ## A ScoreTransform set by name and the same one set by handle agree, the ## name survives a save, and chained reference still reaches through. %!test %! load fisheriris %! Yb = strcmp (species, 'setosa'); %! CMdl2 = compact (fitcsvm (meas, Yb)); %! assert_equal (CMdl2.KernelParameters.Function, 'linear'); %! CMdl2.ScoreTransform = 'logit'; %! [~, s1] = predict (CMdl2, meas(1:3,:)); %! CMdl2.ScoreTransform = @(x) 1 ./ (1 + exp (-x)); %! [~, s2] = predict (CMdl2, meas(1:3,:)); %! assert_equal (s1, s2); %! CMdl2.ScoreTransform = 'logit'; %! fname = tempname (); %! savemodel (CMdl2, fname); %! M2 = loadmodel (fname); %! delete (fname); %! [~, s3] = predict (M2, meas(1:3,:)); %! assert_equal (s3, s1); ## Test output for margin method %!test %! rand ('seed', 1); %! C = cvpartition (y, 'HoldOut', 0.15); %! Mdl = fitcsvm (x(training (C),:), y(training (C)), ... %! 'KernelFunction', 'rbf', 'Tolerance', 1e-7); %! CMdl = compact (Mdl); %! testInds = test (C); %! ## Every one of these fifteen is classified correctly, so every margin is %! ## positive. They used to read -4.0000 downwards for the second class: %! ## the margin was formed from the response as given, so a 1/2 coding %! ## scaled that class by four instead of negating it, and the model looked %! ## as though it misclassified every observation of it. %! expected_margin = [2.0000; 0.8579; 1.6690; 3.4141; 3.4552; ... %! 2.6605; 3.5251; 2.0000; 3.1705; 3.2256; ... %! 1.5266; 3.7527; 0.8350; 2.8113; 3.6820]; %! computed_margin = margin (CMdl, x(testInds,:), y(testInds,:)); %! assert_equal (computed_margin, expected_margin, 1e-4); %! assert (all (computed_margin > 0)); ## Test input validation for margin method %!error ... %! margin (CMdl) %!error ... %! margin (CMdl, zeros (2)) %!error ... %! margin (CMdl, [], 1) %!error ... %! margin (CMdl, 1, 1) %!error ... %! margin (CMdl, [1, 2], []) %!error ... %! margin (CMdl, [1, 2], [1; 2]) ## Test output for loss method %!test %! rand ('seed', 1); %! C = cvpartition (y, 'HoldOut', 0.15); %! Mdl = fitcsvm (x(training (C),:), y(training (C)), ... %! 'KernelFunction', 'rbf', 'Tolerance', 1e-7); %! CMdl = compact (Mdl); %! testInds = test (C); %! L1 = loss (CMdl, x(testInds,:), y(testInds,:), 'LossFun', 'binodeviance'); %! L2 = loss (CMdl, x(testInds,:), y(testInds,:), 'LossFun', 'classiferror'); %! L3 = loss (CMdl, x(testInds,:), y(testInds,:), 'LossFun', 'exponential'); %! L4 = loss (CMdl, x(testInds,:), y(testInds,:), 'LossFun', 'hinge'); %! L5 = loss (CMdl, x(testInds,:), y(testInds,:), 'LossFun', 'logit'); %! L6 = loss (CMdl, x(testInds,:), y(testInds,:), 'LossFun', 'quadratic'); %! ## These changed when loss stopped handing the response to LIBSVM %! ## unmapped: it used the labels 1 and 2 where the margin's sign wants +1 %! ## and -1, so every loss but the error rate was scaled by the labels. %! ## margin had already been given svmPlusMinus and loss had been missed. %! ## Cross-checked against R2024a on a deterministic half-and-half split, %! ## where ours reads 0.1800, 0.0800, 0.3984, 0.1785, 0.3184, 0.2939 and %! ## MATLAB reads 0.1812, 0.0800, 0.4107, 0.1520, 0.3297, 0.1981: the %! ## error rate agrees exactly and the rest sit within the LIBSVM against %! ## SMO difference of section 1. The old values were an order of %! ## magnitude out, a 53%% error rate among them. %! assert_equal (L1, 0.1122, 1e-4); %! assert_equal (L2, 0.0000, 1e-4); %! assert_equal (L3, 0.3135, 1e-4); %! assert_equal (L4, 0.1037, 1e-4); %! assert_equal (L5, 0.2652, 1e-4); %! assert_equal (L6, 0.3218, 1e-4); ## Test input validation for loss method %!error ... %! loss (CMdl) %!error ... %! loss (CMdl, zeros (2)) %!error ... %! loss (CMdl, [1, 2], 1, 'LossFun') %!error ... %! loss (CMdl, [], zeros (2)) %!error ... %! loss (CMdl, 1, zeros (2)) %!error ... %! loss (CMdl, [1, 2], []) %!error ... %! loss (CMdl, [1, 2], [1; 2]) %!error ... %! loss (CMdl, [1, 2], 1, 'LossFun', 1) %!error ... %! loss (CMdl, [1, 2], 1, 'LossFun', 'some') %!error ... %! loss (CMdl, [1, 2], 1, 'Weights', ['a', 'b']) %!error ... %! loss (CMdl, [1, 2], 1, 'Weights', 'a') %!error ... %! loss (CMdl, [1, 2], 1, 'Weights', [1, 2]) %!error ... %! loss (CMdl, [1, 2], 1, 'some', 'some') %!error ... %! savemodel (CompactClassificationSVM ()) %!error ... %! savemodel (CompactClassificationSVM (), 1) %!error ... %! savemodel (CompactClassificationSVM (), ['ab'; 'cd']) ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! Mdl = compact (fitcsvm (meas(inds,:), species(inds))); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'CompactClassificationSVM'); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ScoreTransform), class (Mdl.ScoreTransform)); %! assert_equal (predict (M2, meas(1:5,:)), predict (Mdl, meas(1:5,:))); ## edge. The absolute value is not pinned to the oracle: this class fits ## through LIBSVM where MATLAB uses SMO and the two disagree on the support ## vector set, so what is pinned is that the edge is the mean of the margins ## and that the compact model answers as the full one does. %!test %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds,:); %! Y = species(inds); %! Mdl = fitcsvm (X, Y); %! CMdl = compact (Mdl); %! assert_equal (edge (CMdl, X, Y), mean (margin (CMdl, X, Y)), 1e-12); %! assert_equal (edge (CMdl, X, Y), edge (Mdl, X, Y), 1e-12); %!error ... %! load fisheriris; ... %! inds = ! strcmp (species, 'virginica'); ... %! edge (compact (fitcsvm (meas(inds,:), species(inds))), meas(inds,:)) ## Both carry across to the compact form, and survive a round trip. %!test %! load fisheriris %! b = ismember (species, {'setosa', 'versicolor'}); %! CMdl = compact (fitcsvm (meas(b,:), species(b))); %! assert_equal (CMdl.CategoricalPredictors, []); %! assert_equal (CMdl.ExpandedPredictorNames, CMdl.PredictorNames); %! assert_equal (size (CMdl.ExpandedPredictorNames), [1, 4]); %!test %! load fisheriris %! b = ismember (species, {'setosa', 'versicolor'}); %! CMdl = compact (fitcsvm (meas(b,:), species(b))); %! fname = tempname (); %! savemodel (CMdl, fname); %! C2 = loadmodel (fname); %! delete (fname); %! assert_equal (C2.CategoricalPredictors, CMdl.CategoricalPredictors); %! assert_equal (C2.ExpandedPredictorNames, CMdl.ExpandedPredictorNames); ## KernelParameters comes across with the compact form. %!test %! load fisheriris %! b = ismember (species, {'setosa', 'versicolor'}); %! Mdl = fitcsvm (meas(b,:), species(b), 'KernelFunction', 'rbf'); %! CMdl = compact (Mdl); %! assert_equal (CMdl.KernelParameters, Mdl.KernelParameters); %! assert_equal (CMdl.KernelParameters.Function, 'gaussian'); ## A compact model answers exactly as the model it was compacted from. %!test %! load fisheriris %! b = strcmp (species, 'setosa'); %! Mdl = fitcsvm (meas, b, 'Cost', [0, 2; 5, 0]); %! [~, ~, cost] = predict (compact (Mdl), meas([1, 51],:)); %! assert_equal (cost, [5, 0; 0, 2]); ## Every documented score transform reaches the scores that are reported, and ## none of them moves the label: a transform reshapes what is reported, not ## what is decided. %!test %! load fisheriris %! Mdl = compact (fitcsvm (meas, strcmp (species, 'setosa'))); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! T = {'identity', @(x) x; 'doublelogit', @(x) 1 ./ (1 + exp (-2 * x)); ... %! 'invlogit', @(x) log (x ./ (1 - x)); ... %! 'logit', @(x) 1 ./ (1 + exp (-x)); ... %! 'sign', @(x) sign (x); 'symmetric', @(x) 2 * x - 1; ... %! 'symmetriclogit', @(x) 2 ./ (1 + exp (-x)) - 1}; %! for i = 1:rows (T) %! Mdl.ScoreTransform = T{i,1}; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, T{i,2}(raw), 1e-12); %! assert_equal (l, label); %! endfor %! ## ismax marks the largest score of each observation, ties to the first. %! [~, k] = max (raw, [], 2); %! e = zeros (size (raw)); %! e(sub2ind (size (raw), (1:rows (raw))', k)) = 1; %! Mdl.ScoreTransform = 'ismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, e); %! Mdl.ScoreTransform = 'symmetricismax'; %! [~, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, 2 * e - 1); ## A function handle is taken as given and applied to the scores. %!test %! load fisheriris %! Mdl = compact (fitcsvm (meas, strcmp (species, 'setosa'))); %! Mdl.ScoreTransform = 'none'; %! [label, raw] = predict (Mdl, meas([1, 60, 120],:)); %! Mdl.ScoreTransform = @(x) x .^ 2; %! [l, s] = predict (Mdl, meas([1, 60, 120],:)); %! assert_equal (s, raw .^ 2, 1e-12); %! assert_equal (l, label); statistics-release-1.9.2/inst/Supervised_Learning/CompactRegressionGAM.m000066400000000000000000001022201524624707500264230ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef CompactRegressionGAM ## -*- texinfo -*- ## @deftp {statistics} CompactRegressionGAM ## ## Compact generalized additive model regression ## ## The @code{CompactRegressionGAM} class implements a compact version of the ## generalized additive model regression object, which predicts responses for ## new data with the @code{predict} method but does not store the training ## data. ## ## A compact model consumes less memory than the full @code{RegressionGAM} ## model, but cannot perform tasks that need the training data, such as ## computing a resubstitution loss or the standard deviation of a prediction. ## ## Create a @code{CompactRegressionGAM} object by using the @code{compact} ## method on a @code{RegressionGAM} object. ## ## The engine that fitted the model is carried over in @code{FitMethod}, ## and the compact model predicts by the same scheme the full one did. ## Under @qcode{'boostedtrees'}, the default, the fit is described by ## @code{TreeModel}, @code{BinEdges} and @code{PairDetectionBinEdges}. ## Under @qcode{'splines'} it is described by @code{Formula}, ## @code{BaseModel}, @code{ModelwInt} and @code{IntMatrix}, which MATLAB's ## compact model does not carry. ## Whichever fitted the model, the other set is empty. A standard ## deviation is available from the spline engine alone. ## ## @seealso{RegressionGAM, fitrgam} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} NumPredictors ## ## Number of predictors ## ## A positive integer, the number of predictors of the training data. ## This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} PredictorNames ## ## Names of the predictor variables ## ## A cell array of character vectors naming the predictors, in the order ## they appear in the training data. This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} ResponseName ## ## Response variable name ## ## A character vector naming the response variable @var{Y}. This ## property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector holding the column of each predictor treated as ## categorical, and empty when none is. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} ExpandedPredictorNames ## ## Names of the expanded predictor variables ## ## A cell array of character vectors naming the predictors as the model ## sees them. It matches @code{PredictorNames} unless a categorical ## predictor was expanded into dummy variables. This property is ## read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} Intercept ## ## Intercept of the fitted model ## ## A numeric scalar, the mean of the response, which every additive term ## is measured against. This property is read-only. ## ## @end deftp Intercept = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} Formula ## ## Formula of the model ## ## A character vector naming the response and the terms of the model, as ## in @qcode{'Y ~ x1 + x2 + x1:x2'}, or empty when the model was not ## given one. This property is read-only. ## ## @end deftp Formula = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} Interactions ## ## Two-way interaction terms of the fitted model ## ## A @math{Kx2} matrix of predictor index pairs, one row per two-way term ## the model carries, and @code{zeros (0, 2)} when it carries none. It ## reports what was fitted rather than what was asked for, so a count of ## terms, @qcode{'all'}, a logical matrix and a formula all leave the same ## kind of value behind. This property is read-only. ## ## A main effect names one predictor and a higher-order term names three ## or more, and neither has a two-column form, so neither appears here. ## @code{IntMatrix} remains the complete record of every term fitted. ## ## @end deftp Interactions = zeros (0, 2); ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} IsStandardDeviationFit ## ## Flag for a fitted standard deviation model ## ## A boolean flag, always @qcode{false}, as this class estimates the ## standard deviation of a prediction from the residuals of the fit ## rather than fitting a model for it. This property is read-only. ## ## @end deftp IsStandardDeviationFit = false; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} BaseModel ## ## Model without interaction terms ## ## A structure holding the intercept, the piecewise polynomial of each ## predictor, the number of backfitting cycles, the residuals and the ## residual sum of squares of the model fitted without interaction ## terms. This property is read-only. ## ## @end deftp BaseModel = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} ModelwInt ## ## Model with interaction terms ## ## A structure of the same fields as @code{BaseModel}, for the model ## fitted with the interaction terms, and empty when none was asked for. ## This property is read-only. ## ## @end deftp ModelwInt = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} IntMatrix ## ## Every term the model fits ## ## A logical matrix with one row per term and one column per predictor, ## true wherever the term multiplies that predictor. A row naming one ## predictor is a main effect, two an interaction, and three or more a ## higher-order term. This property is read-only. ## ## It is the complete record, where @code{Interactions} reports only the ## two-way terms, in the form MATLAB reports them. It is also the form ## the @qcode{'Interactions'} option takes back, so passing it to the ## constructor rebuilds a model over the same terms. ## ## @end deftp IntMatrix = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} BinEdges ## ## Bin edges of the fitted shape functions, empty under the spline ## engine. This property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} PairDetectionBinEdges ## ## Coarse bin edges the interaction terms are held on, empty when the ## model carries none. This property is read-only. ## ## @end deftp PairDetectionBinEdges = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} FitMethod ## ## Which engine fitted the model, @qcode{'boostedtrees'} or ## @qcode{'splines'}. This property is read-only. ## ## @end deftp FitMethod = 'boostedtrees'; ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} TreeModel ## ## The fitted shape functions and interaction surfaces, empty under the ## spline engine. This property is read-only. ## ## @end deftp TreeModel = []; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {CompactRegressionGAM} {property} ResponseTransform ## ## Transformation applied to the predicted response ## ## A function handle applied to the response the model predicts. Add or ## change it using dot notation, as in ## @qcode{@var{obj}.ResponseTransform = 'log'} or ## @qcode{@var{obj}.ResponseTransform = @@function_handle}. It defaults ## to @qcode{'none'}, the identity. ## ## @end deftp ResponseTransform = @(x) x; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) ## Carried from the fitted model so a fold, which is stored ## compact, can still say how many trees it fitted. NumTrainedTrees = []; RTfun = @(y) y; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.ResponseTransform (this, val) [this.RTfun, this.ResponseTransform] = parseResponseTransform ... (val, 'CompactRegressionGAM'); endfunction ## -*- texinfo -*- ## @deftypefn {CompactRegressionGAM} {@var{obj} =} CompactRegressionGAM (@var{Mdl}) ## @deftypefnx {CompactRegressionGAM} {@var{obj} =} CompactRegressionGAM () ## ## Create a @code{CompactRegressionGAM} object. ## ## @var{Mdl} is the @code{RegressionGAM} object to ## compact. The documented way to reach this constructor is the ## @code{compact} method. ## ## Called with no arguments it returns an object with its properties ## empty, which is how a saved model is rebuilt before its values are ## filled in. ## ## @end deftypefn function this = CompactRegressionGAM (Mdl = []) ## Check for appropriate class if (isempty (Mdl)) return; elseif (! strcmpi (class (Mdl), 'RegressionGAM')) error ("CompactRegressionGAM: invalid regression object."); endif ## Save properties to compact model this.NumPredictors = Mdl.NumPredictors; this.PredictorNames = Mdl.PredictorNames; this.ResponseName = Mdl.ResponseName; this.CategoricalPredictors = Mdl.CategoricalPredictors; this.ExpandedPredictorNames = Mdl.ExpandedPredictorNames; this.ResponseTransform = Mdl.ResponseTransform; this.Intercept = Mdl.Intercept; this.Formula = Mdl.Formula; this.Interactions = Mdl.Interactions; this.IsStandardDeviationFit = Mdl.IsStandardDeviationFit; this.BaseModel = Mdl.BaseModel; this.ModelwInt = Mdl.ModelwInt; this.IntMatrix = Mdl.IntMatrix; this.RTfun = Mdl.RTfun; this.FitMethod = Mdl.FitMethod; this.TreeModel = Mdl.TreeModel; this.BinEdges = Mdl.BinEdges; this.PairDetectionBinEdges = Mdl.PairDetectionBinEdges; this.NumTrainedTrees = Mdl.NumTrainedTrees; endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n CompactRegressionGAM\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); fprintf ("%+25s: '%s'\n", 'ResponseTransform', this.ResponseTransform); fprintf ("%+25s: %g\n", 'Intercept', this.Intercept); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {CompactRegressionGAM} {@var{yFit} =} predict (@var{obj}, @var{Xfit}) ## @deftypefnx {CompactRegressionGAM} {@var{yFit} =} predict (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {CompactRegressionGAM} {[@var{yFit}, @var{ySD}, @var{yInt}] =} predict (@dots{}) ## ## Predict new data points using generalized additive model regression ## object. ## ## @code{@var{yFit} = predict (@var{obj}, @var{Xfit}} returns a vector of ## predicted responses, @var{yFit}, for the predictor data in matrix ## @var{Xfit} based on the Generalized Additive Model in @var{obj}. ## @var{Xfit} must have the same number of features/variables as the ## training data in @var{obj}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{CompactRegressionGAM} class object. ## @end itemize ## ## @code{[@var{yFit}, @var{ySD}, @var{yInt}] = predict (@var{obj}, ## @var{Xfit}} ## also returns the standard deviations, @var{ySD}, and prediction ## intervals, ## @var{yInt}, of the response variable @var{yFit}, evaluated at each ## observation in the predictor data @var{Xfit}. ## ## @code{@var{yFit} = predict (@dots{}, @var{Name}, @var{Value})} returns ## the ## aforementioned results with additional properties specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.28 0.7 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'alpha'} @tab significance level of the prediction ## intervals @var{yInt}, specified as scalar in range @qcode{[0,1]}. The ## default value is 0.05, which corresponds to 95% prediction intervals. ## ## @item @qcode{'includeinteractions'} @tab a boolean flag to include ## interactions to predict new values based on @var{Xfit}. By default, ## @qcode{'includeinteractions'} is @qcode{true} when the GAM model in ## @var{obj} ## contains a @qcode{obj.Formula} or @qcode{obj.Interactions} fields. ## Otherwise, is set to @qcode{false}. If set to @qcode{true} when no ## interactions are present in the trained model, it will result to an ## error. If set to ## @qcode{false} when using a model that includes interactions, the ## predictions ## will be made on the basic model without any interaction terms. This way ## you can make predictions from the same GAM model without having to ## retrain it. ## @end multitable ## ## @seealso{fitrgam, RegressionGAM} ## @end deftypefn function yFit = predict (this, Xfit, varargin) ## Check for sufficient input arguments if (nargin < 2) error ("CompactRegressionGAM.predict: too few arguments."); endif ## Check for valid XC if (isempty (Xfit)) error ("CompactRegressionGAM.predict: Xfit is empty."); elseif (this.NumPredictors != columns (Xfit)) error (strcat ("CompactRegressionGAM.predict: Xfit must have the", ... " same number of features (columns) as in the GAM model.")); endif ## Clean Xfit data notnansf = ! logical (sum (isnan (Xfit), 2)); Xfit = Xfit(notnansf, :); ## Default values for Name-Value Pairs alpha = 0.05; hasInt = ! isempty (this.IntMatrix); if (strcmp (this.FitMethod, 'boostedtrees') && ! isempty (this.TreeModel)) hasInt = ! isempty (this.TreeModel.Pairs); endif if (! hasInt) incInt = false; else incInt = true; endif ## Parse optional arguments while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'includeinteractions' tmpInt = varargin{2}; if (! islogical (tmpInt) || (tmpInt != 0 && tmpInt != 1)) error (strcat ("CompactRegressionGAM.predict:", ... " includeinteractions must be a logical value.")); endif ## Check model for interactions if (tmpInt && ! hasInt) error (strcat ("CompactRegressionGAM.predict: trained model", ... " does not include any interactions.")); endif incInt = tmpInt; case 'alpha' alpha = varargin{2}; if (! (isnumeric (alpha) && isscalar (alpha) && alpha > 0 && alpha < 1)) error (strcat ("CompactRegressionGAM.predict: alpha must be", ... " a scalar value between 0 and 1.")); endif otherwise error (strcat ("CompactRegressionGAM.predict: invalid NAME in", ... " optional pairs of arguments.")); endswitch varargin(1:2) = []; endwhile ## Choose whether interactions must be included ## The boosted-tree engine keeps its fit as step functions over bins, so ## a term is a lookup rather than a spline evaluation. if (strcmp (this.FitMethod, 'boostedtrees') && ! isempty (this.TreeModel)) ## Excluding the interactions means excluding the constant they ## handed the intercept as well. interc = this.Intercept; if (! incInt && isfield (this.TreeModel, 'PairIntercept')) interc = interc - this.TreeModel.PairIntercept; endif if (! incInt || isempty (this.TreeModel.Pairs)) yFit = gamboostpredict (this.BinEdges, ... this.TreeModel.ShapeValues, Xfit, ... interc); else yFit = gamboostpredict (this.BinEdges, ... this.TreeModel.ShapeValues, Xfit, ... interc, 0, ... this.PairDetectionBinEdges, ... this.TreeModel.PairValues, ... this.TreeModel.Pairs); endif yFit = this.RTfun (yFit); return; endif if (incInt) ## Which construction path the model took: an interaction ## list appends its terms to the predictors, a formula ## names every term the model has and replaces them. if (isempty (this.Formula)) ## Append interaction terms to the predictor matrix for i = 1:rows (this.IntMatrix) tindex = logical (this.IntMatrix(i,:)); Xterms = Xfit(:,tindex); Xinter = ones (rows (Xfit), 1); for c = 1:sum (tindex) Xinter = Xinter .* Xterms(:,c); endfor ## Append interaction terms Xfit = [Xfit, Xinter]; endfor else ## Add selected predictors and interaction terms XN = []; for i = 1:rows (this.IntMatrix) tindex = logical (this.IntMatrix(i,:)); Xterms = Xfit(:,tindex); Xinter = ones (rows (Xfit), 1); for c = 1:sum (tindex) Xinter = Xinter .* Xterms(:,c); endfor ## Append selected predictors and interaction terms XN = [XN, Xinter]; endfor Xfit = XN; endif ## Get parameters and intercept vectors from model with interactions params = this.ModelwInt.Parameters; Interc = this.ModelwInt.Intercept; else ## Get parameters and intercept vectors from base model params = this.BaseModel.Parameters; Interc = this.BaseModel.Intercept; endif ## Predict values from testing data yFit = predict_val (params, Xfit, Interc); yFit = this.RTfun (yFit); endfunction ## -*- texinfo -*- ## @deftypefn {CompactRegressionGAM} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactRegressionGAM} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Regression loss of a generalized additive model. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} returns the weighted ## mean squared error of the model on the rows of @var{X} against the true ## response @var{Y}. ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} accepts the ## following name-value pairs: ## ## @itemize ## @item ## @qcode{"LossFun"} selects the loss, either @qcode{"mse"}, the default, ## or a function handle taking the true response, the predicted response ## and the weights, and returning a numeric scalar. ## ## @item ## @qcode{"Weights"} holds one weight per row of @var{X}, normalised to ## sum to one before it is applied. ## @end itemize ## ## @seealso{CompactRegressionGAM, RegressionGAM, fitrgam, predict} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("CompactRegressionGAM.loss: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("CompactRegressionGAM.loss: Name-Value arguments", ... " must be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, 'loss'); ## Defaults, then the optional pairs LossFun = 'mse'; args = varargin; keep = true (1, numel (args)); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("CompactRegressionGAM.loss: parameter name must", ... " be a character vector.")); endif if (strcmpi (args{i}, 'lossfun')) LossFun = args{i+1}; if (! (is_function_handle (LossFun) || (ischar (LossFun) && isrow (LossFun)))) error (strcat ("CompactRegressionGAM.loss: 'LossFun' must be a", ... " character vector or a function handle.")); endif if (ischar (LossFun) && ! strcmpi (LossFun, 'mse')) error ("CompactRegressionGAM.loss: unsupported 'LossFun' value."); endif keep(i:i+1) = false; endif endfor W = getWeights_ (this, args(keep), rows (X), 'loss'); ## Weights are normalized to sum to one, as MATLAB does, so a loss is ## a weighted average rather than a weighted sum. W = W(:) / sum (W); yFit = predict (this, X); Y = Y(:); if (is_function_handle (LossFun)) L = LossFun (Y, yFit, W); if (! (isnumeric (L) && isscalar (L))) error (strcat ("CompactRegressionGAM.loss: 'LossFun' must", ... " return a numeric scalar.")); endif else L = sum (W .* (Y - yFit) .^ 2); endif endfunction ## -*- texinfo -*- ## @deftypefn {CompactRegressionGAM} {} savemodel (@var{obj}, @var{filename}) ## ## Save a CompactRegressionGAM object. ## ## @code{savemodel (@var{obj}, @var{filename})} saves each property of a ## CompactRegressionGAM object into an Octave binary file, the name of ## which is specified in @var{filename}, along with an extra variable which ## defines the type of object these variables constitute. Use ## @code{loadmodel} in order to load the object back into Octave. ## ## @seealso{loadmodel, fitrgam, RegressionGAM} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error ("CompactRegressionGAM.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error (strcat ("CompactRegressionGAM.savemodel: FNAME must be a", ... " character vector.")); endif ## Generate variable for class name classdef_name = 'CompactRegressionGAM'; ## Create variables from model properties NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; ResponseName = this.ResponseName; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; ResponseTransform = this.ResponseTransform; Intercept = this.Intercept; Formula = this.Formula; Interactions = this.Interactions; IsStandardDeviationFit = this.IsStandardDeviationFit; BaseModel = this.BaseModel; ModelwInt = this.ModelwInt; IntMatrix = this.IntMatrix; RTfun = this.RTfun; FitMethod = this.FitMethod; TreeModel = this.TreeModel; BinEdges = this.BinEdges; PairDetectionBinEdges = this.PairDetectionBinEdges; ## Save classdef name and all model properties as individual variables save ('-binary', fname, 'classdef_name', 'NumPredictors', ... 'PredictorNames', 'ResponseName', 'CategoricalPredictors', ... 'ExpandedPredictorNames', 'ResponseTransform', 'Intercept', ... 'Formula', 'Interactions', 'IsStandardDeviationFit', ... 'BaseModel', 'ModelwInt', 'IntMatrix', 'RTfun', 'FitMethod', ... 'TreeModel', 'BinEdges', 'PairDetectionBinEdges'); endfunction endmethods methods(Access = private) ## Shared validation for the assessment methods, so each reports under ## its own name. function [X, Y] = checkXY_ (this, X, Y, caller) if (isempty (X)) error ("CompactRegressionGAM.%s: X is empty.", caller); elseif (this.NumPredictors != columns (X)) error (strcat ("CompactRegressionGAM.%s: X must have the same", ... " number of predictors as the trained model."), caller); endif if (isempty (Y)) error ("CompactRegressionGAM.%s: Y is empty.", caller); elseif (rows (X) != rows (Y)) error (strcat ("CompactRegressionGAM.%s: Y must have the same", ... " number of rows as X."), caller); endif endfunction ## Pull a "Weights" pair out of the optional arguments, defaulting to a ## uniform weight, and reject any other name. function W = getWeights_ (this, args, n, caller) W = ones (n, 1); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("CompactRegressionGAM.%s: parameter name must be", ... " a character vector."), caller); endif if (strcmpi (args{i}, 'weights')) W = args{i+1}; if (! (isnumeric (W) && isvector (W))) error (strcat ("CompactRegressionGAM.%s: 'Weights' must be a", ... " numeric vector."), caller); endif if (numel (W) != n) error (strcat ("CompactRegressionGAM.%s: size of 'Weights'", ... " must equal the number of rows in X."), caller); endif else error (strcat ("CompactRegressionGAM.%s: invalid parameter name", ... " in optional paired arguments."), caller); endif endfor endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a CompactRegressionGAM object mdl = CompactRegressionGAM (); ## Get fieldnames from DATA (including private properties) names = fieldnames (data); ## The set methods for these read other properties, and one of them ## rebuilds Coeffs, so they are assigned once everything else is in ## place rather than in the order the file happens to list them. late = ismember (names, {'Cost', 'Prior', 'ScoreTransform', ... 'ResponseTransform'}); names = [names(! late); names(late)]; ## Copy data into object for i = 1:numel (names) try mdl.(names{i}) = data.(names{i}); catch error ("CompactRegressionGAM.load_model: invalid model in '%s'.", ... filename); end_try_catch endfor endfunction endmethods endclassdef ## Helper function function ypred = predict_val (params, X, intercept) ## The shared prediction engine evaluates every additive term and adds the ## intercept. ypred = gampredict (params, X, intercept); endfunction %!demo %! ## Take the compact version of a fitted model and predict with it %! %! load fisheriris %! X = meas(:,1:3); %! Y = meas(:,4); %! %! mdl = fitrgam (X, Y) %! cmdl = compact (mdl) ## Test input validation for constructor %!error ... %! CompactRegressionGAM (1) ## The compact model carries what MATLAB's compact model reports. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,1:3), meas(:,4)); %! CMdl = compact (Mdl); %! assert_equal (class (CMdl), 'CompactRegressionGAM'); %! assert_equal (CMdl.Intercept, Mdl.Intercept); %! assert_equal (CMdl.CategoricalPredictors, Mdl.CategoricalPredictors); %! assert_equal (CMdl.ExpandedPredictorNames, Mdl.ExpandedPredictorNames); %! assert_equal (CMdl.IsStandardDeviationFit, false); %! assert_equal (isprop (CMdl, 'X'), false); ## predict and loss agree with the model it was compacted from. %!test %! load fisheriris %! X = meas(:,1:3); %! Y = meas(:,4); %! Mdl = fitrgam (X, Y, 'Interactions', 'all'); %! CMdl = compact (Mdl); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); %! assert_equal (loss (CMdl, X, Y), loss (Mdl, X, Y)); ## A saved and reloaded compact model carries every property it holds. %!test %! load fisheriris %! X = meas(:,1:3); %! CMdl = compact (fitrgam (X, meas(:,4))); %! fname = tempname (); %! savemodel (CMdl, fname); %! CMdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (CMdl2), 'CompactRegressionGAM'); %! assert_equal (CMdl2.Intercept, CMdl.Intercept); %! assert_equal (predict (CMdl2, X), predict (CMdl, X)); ## An assigned ResponseTransform reaches the predicted response. %!test %! load fisheriris %! X = meas(:,1:3); %! CMdl = compact (fitrgam (X, meas(:,4))); %! y0 = predict (CMdl, X); %! CMdl.ResponseTransform = 'log'; %! assert_equal (predict (CMdl, X), log (y0), 1e-12); ## Test input validation %!shared xc, yc, CMr %! load fisheriris %! xc = meas(:,1:3); %! yc = meas(:,4); %! CMr = compact (fitrgam (xc, yc)); %!error ... %! predict (CMr) %!error ... %! predict (CMr, []) %!error ... %! loss (CMr, xc) %!error ... %! loss (CMr, xc, yc, 'LossFun', 'mad') %!error ... %! savemodel (CompactRegressionGAM ()) %!error ... %! savemodel (CompactRegressionGAM (), 1) ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Mdl = compact (fitrgam (X, Y, 'FitMethod', 'splines')); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'CompactRegressionGAM'); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ResponseTransform), class (Mdl.ResponseTransform)); %! assert_equal (M2.BaseModel.Parameters(1).coefs, ... %! Mdl.BaseModel.Parameters(1).coefs); %! assert_equal (predict (M2, X(1:5,:)), predict (Mdl, X(1:5,:)), 1e-12); ## The same round trip under the boosted-tree engine. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Mdl = compact (fitrgam (X, Y, 'FitMethod', 'boostedtrees')); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.FitMethod, 'boostedtrees'); %! assert_equal (M2.TreeModel.ShapeValues, Mdl.TreeModel.ShapeValues); %! assert_equal (predict (M2, X(1:5,:)), predict (Mdl, X(1:5,:)), 1e-12); ## A compacted tree-fitted model predicts as the full model does. %!test %! load fisheriris %! X = meas(:,2:4); %! CMdl = compact (fitrgam (X, meas(:,1), 'FitMethod', 'boostedtrees')); %! assert_equal (CMdl.FitMethod, 'boostedtrees'); %! assert_equal (numel (CMdl.BinEdges), 3); %! assert_equal (numel (predict (CMdl, X)), rows (X)); ## Every documented response transform reaches the response that is reported. %!test %! load fisheriris %! Mdl = compact (fitrgam (meas(:,2:4), meas(:,1))); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! T = {'identity', @(x) x; 'exp', @(x) exp (x); 'log', @(x) log (x)}; %! for i = 1:rows (T) %! Mdl.ResponseTransform = T{i,1}; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, T{i,2}(raw), 1e-12); %! endfor ## A function handle is taken as given and applied to the response. %!test %! load fisheriris %! Mdl = compact (fitrgam (meas(:,2:4), meas(:,1))); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! Mdl.ResponseTransform = @(x) x .^ 2; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, raw .^ 2, 1e-12); statistics-release-1.9.2/inst/Supervised_Learning/CompactRegressionGP.m000066400000000000000000000617441524624707500263440ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftp {statistics} CompactRegressionGP ## ## Compact Gaussian process regression ## ## A @code{CompactRegressionGP} object holds a Gaussian process regression ## model without its training data, keeping what is needed to predict and ## dropping the rest. ## ## Create a @code{CompactRegressionGP} object by using the @code{compact} ## method of a @code{RegressionGP} object. ## ## A compact model keeps the active set it predicts from, the prediction ## weights, the covariance function and its parameters, the explicit basis and ## its coefficients, the noise standard deviation and the standardizing ## location and scale. It drops the response, the observation weights, the ## rows used, the count of observations and the maximized log likelihood, so ## it can predict but cannot be cross validated, refitted, or asked for its ## resubstitution loss or its post-fit statistics. ## ## The standard deviation and the prediction intervals remain available, ## because the active set of an exactly fitted model is the whole of the ## training predictors and the factorization can be rebuilt from it. ## ## @seealso{RegressionGP, fitrgp} ## @end deftp classdef CompactRegressionGP properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} PredictorNames ## ## Predictor variable names ## ## A cell array of character vectors. This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} ExpandedPredictorNames ## ## Expanded predictor variable names ## ## A cell array of character vectors. This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} ResponseName ## ## Response variable name ## ## A character vector. This property is read-only. ## ## @end deftp ResponseName = 'Y'; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A vector of positive integers, or empty. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} FitMethod ## ## Method used to estimate the parameters ## ## @qcode{'Exact'} or @qcode{'None'}. This property is read-only. ## ## @end deftp FitMethod = 'Exact'; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} BasisFunction ## ## Explicit basis of the model ## ## A character vector or a function handle. This property is read-only. ## ## @end deftp BasisFunction = 'Constant'; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} Beta ## ## Estimated coefficients of the explicit basis ## ## A numeric vector, empty when the basis is @qcode{'None'}. This property ## is read-only. ## ## @end deftp Beta = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} Sigma ## ## Estimated noise standard deviation ## ## A positive scalar. This property is read-only. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} KernelFunction ## ## Form of the covariance function ## ## A character vector or a function handle. This property is read-only. ## ## @end deftp KernelFunction = 'SquaredExponential'; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} KernelInformation ## ## Covariance function and its parameters ## ## A structure with fields @qcode{Name}, @qcode{KernelParameters} and ## @qcode{KernelParameterNames}. This property is read-only. ## ## @end deftp KernelInformation = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} PredictMethod ## ## Method used to make predictions ## ## @qcode{'Exact'}. This property is read-only. ## ## @end deftp PredictMethod = 'Exact'; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} Alpha ## ## Weights the predictions are made from ## ## A numeric vector with one weight per active set vector. This property ## is read-only. ## ## @end deftp Alpha = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} ActiveSetVectors ## ## Subset of the training data used for predictions ## ## An @math{MxP} numeric matrix, standardized where the model standardized ## its predictors. This property is read-only. ## ## @end deftp ActiveSetVectors = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} ActiveSetMethod ## ## Method used to select the active set ## ## @qcode{'Random'}. This property is read-only. ## ## @end deftp ActiveSetMethod = 'Random'; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} ActiveSetSize ## ## Size of the active set ## ## A positive integer scalar. This property is read-only. ## ## @end deftp ActiveSetSize = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} PredictorLocation ## ## Means the predictors were centred by ## ## A @math{1xP} numeric vector, or empty. This property is read-only. ## ## @end deftp PredictorLocation = []; ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} PredictorScale ## ## Standard deviations the predictors were scaled by ## ## A @math{1xP} numeric vector, or empty. This property is read-only. ## ## @end deftp PredictorScale = []; endproperties properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {CompactRegressionGP} {property} ResponseTransform ## ## Transformation applied to the predicted response ## ## A character vector, or the text of the function handle that was ## supplied. Assigning to it accepts either. ## ## @end deftp ResponseTransform = 'none'; endproperties properties (GetAccess = public, SetAccess = protected, Hidden) ## The callable behind ResponseTransform. RTfun = @(y) y; endproperties methods (Access = public) ## -*- texinfo -*- ## @deftypefn {CompactRegressionGP} {@var{yFit} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {CompactRegressionGP} {[@var{yFit}, @var{ySD}, @var{yInt}] =} predict (@var{obj}, @var{XC}) ## @deftypefnx {CompactRegressionGP} {[@dots{}] =} predict (@dots{}, @qcode{'Alpha'}, @var{alpha}) ## ## Predict the response for new data with a compact Gaussian process model. ## ## @code{@var{yFit} = predict (@var{obj}, @var{XC})} returns the predicted ## response of the @qcode{CompactRegressionGP} model @var{obj} at the ## points in @var{XC}, and the further outputs are the standard deviation ## of each predicted response and the prediction intervals, exactly as the ## full model returns them. ## ## @end deftypefn function [yFit, ySD, yInt] = predict (this, XC, varargin) if (nargin < 2) error ("CompactRegressionGP.predict: too few input arguments."); endif if (isempty (XC)) error ("CompactRegressionGP.predict: XC is empty."); endif if (columns (XC) != columns (this.ActiveSetVectors)) error (strcat ("CompactRegressionGP.predict: XC must have the same", ... " number of predictors as the trained model.")); endif CIAlpha = 0.05; while (numel (varargin) > 0) if (numel (varargin) < 2) error (strcat ("CompactRegressionGP.predict: optional arguments", ... " must be given in Name-Value pairs.")); endif switch (lower (varargin{1})) case 'alpha' CIAlpha = varargin{2}; if (! (isnumeric (CIAlpha) && isscalar (CIAlpha) && ... CIAlpha >= 0 && CIAlpha <= 1)) error (strcat ("CompactRegressionGP.predict: 'Alpha' must", ... " be a scalar between 0 and 1.")); endif otherwise error (strcat ("CompactRegressionGP.predict: invalid NAME in", ... " optional pairs of arguments.")); endswitch varargin(1:2) = []; endwhile M = struct ('X', this.ActiveSetVectors, 'Alpha', this.Alpha, ... 'KernelFunction', this.KernelFunction, ... 'Theta', this.KernelInformation.KernelParameters, ... 'BasisFunction', this.BasisFunction, 'Beta', this.Beta, ... 'Sigma', this.Sigma, 'Location', this.PredictorLocation, ... 'Scale', this.PredictorScale, 'CIAlpha', CIAlpha); if (nargout < 2) yFit = this.RTfun (gpPredict (XC, M)); elseif (nargout < 3) [yFit, ySD] = gpPredict (XC, M); yFit = this.RTfun (yFit); else [yFit, ySD, yInt] = gpPredict (XC, M); yFit = this.RTfun (yFit); yInt = this.RTfun (yInt); endif endfunction ## -*- texinfo -*- ## @deftypefn {CompactRegressionGP} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactRegressionGP} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Compute the regression loss of a compact Gaussian process model. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} returns the mean ## squared error of the model @var{obj} on the data @var{X} and @var{Y}, ## and accepts the same @qcode{'LossFun'} and @qcode{'Weights'} pairs the ## full model accepts. ## ## @end deftypefn function L = loss (this, X, Y, varargin) if (nargin < 3) error ("CompactRegressionGP.loss: too few input arguments."); endif if (! (isnumeric (X) && isreal (X) && ismatrix (X))) error ("CompactRegressionGP.loss: invalid values in X."); endif if (! (isnumeric (Y) && isreal (Y) && isvector (Y))) error ("CompactRegressionGP.loss: invalid values in Y."); endif Y = Y(:); if (rows (X) != rows (Y)) error (strcat ("CompactRegressionGP.loss: number of rows in X and", ... " Y must be equal.")); endif LossFun = 'mse'; Weights = ones (rows (X), 1); Epsilon = 0; while (numel (varargin) > 0) if (numel (varargin) < 2) error (strcat ("CompactRegressionGP.loss: optional arguments", ... " must be given in Name-Value pairs.")); endif switch (lower (varargin{1})) case 'lossfun' LossFun = varargin{2}; if (! (ischar (LossFun) || is_function_handle (LossFun))) error (strcat ("CompactRegressionGP.loss: 'LossFun' must be", ... " a character vector or a function handle.")); endif if (ischar (LossFun) && ... ! any (strcmpi (LossFun, {'mse', 'mae', ... 'epsiloninsensitive'}))) error (strcat ("CompactRegressionGP.loss: unsupported", ... " 'LossFun' value.")); endif case 'weights' Weights = varargin{2}; if (! (isnumeric (Weights) && isvector (Weights) && ... numel (Weights) == rows (X) && all (Weights >= 0))) error (strcat ("CompactRegressionGP.loss: 'Weights' must be", ... " a vector of non-negative values with one", ... " element per observation.")); endif Weights = Weights(:); case 'epsilon' Epsilon = varargin{2}; otherwise error (strcat ("CompactRegressionGP.loss: invalid NAME in", ... " optional pairs of arguments.")); endswitch varargin(1:2) = []; endwhile yFit = this.predict (X); if (is_function_handle (LossFun)) L = LossFun (Y, yFit); return; endif switch (lower (LossFun)) case 'mse' L = sum (Weights .* (Y - yFit) .^ 2) / sum (Weights); case 'mae' L = sum (Weights .* abs (Y - yFit)) / sum (Weights); case 'epsiloninsensitive' e = max (0, abs (Y - yFit) - Epsilon); L = sum (Weights .* e) / sum (Weights); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {CompactRegressionGP} {} savemodel (@var{obj}, @var{filename}) ## ## Save a compact Gaussian process model to a file. ## ## @code{savemodel (@var{obj}, @var{filename})} saves the model @var{obj} ## into @var{filename} in a form @code{loadmodel} can read back. ## ## @end deftypefn function savemodel (obj, fname) classdef_name = 'CompactRegressionGP'; PredictorNames = obj.PredictorNames; ExpandedPredictorNames = obj.ExpandedPredictorNames; ResponseName = obj.ResponseName; CategoricalPredictors = obj.CategoricalPredictors; FitMethod = obj.FitMethod; BasisFunction = obj.BasisFunction; Beta = obj.Beta; Sigma = obj.Sigma; KernelFunction = obj.KernelFunction; KernelInformation = obj.KernelInformation; PredictMethod = obj.PredictMethod; Alpha = obj.Alpha; ActiveSetVectors = obj.ActiveSetVectors; ActiveSetMethod = obj.ActiveSetMethod; ActiveSetSize = obj.ActiveSetSize; PredictorLocation = obj.PredictorLocation; PredictorScale = obj.PredictorScale; ResponseTransform = obj.ResponseTransform; save ('-binary', fname, 'classdef_name', 'PredictorNames', ... 'ExpandedPredictorNames', 'ResponseName', ... 'CategoricalPredictors', 'FitMethod', 'BasisFunction', 'Beta', ... 'Sigma', 'KernelFunction', ... 'KernelInformation', 'PredictMethod', 'Alpha', ... 'ActiveSetVectors', 'ActiveSetMethod', 'ActiveSetSize', ... 'PredictorLocation', 'PredictorScale', 'ResponseTransform'); endfunction endmethods methods (Access = public, Hidden) function display (this) in_name = inputname (1); if (! isempty (in_name)) printf ('%s =\n', in_name); endif disp (this); endfunction function disp (this) printf ("\n CompactRegressionGP\n\n"); printf ("%25s: '%s'\n", 'ResponseName', this.ResponseName); printf ("%25s: %d\n", 'NumPredictors', ... columns (this.ActiveSetVectors)); if (is_function_handle (this.KernelFunction)) printf ("%25s: '%s'\n", 'KernelFunction', ... func2str (this.KernelFunction)); else printf ("%25s: '%s'\n", 'KernelFunction', this.KernelFunction); endif printf ("%25s: '%s'\n", 'PredictMethod', this.PredictMethod); printf ("%25s: %g\n", 'Sigma', this.Sigma); printf ("\n"); endfunction function this = set.ResponseTransform (this, val) [this.RTfun, this.ResponseTransform] = ... parseResponseTransform (val, 'CompactRegressionGP'); endfunction ## -*- texinfo -*- ## @deftypefn {CompactRegressionGP} {@var{obj} =} CompactRegressionGP (@var{Mdl}) ## ## Create a @code{CompactRegressionGP} object. ## ## @var{Mdl} is the @code{RegressionGP} object to ## compact, and is required: the compact model has no training data to ## build itself from. The documented way to reach this constructor is ## the @code{compact} method. ## ## @end deftypefn function this = CompactRegressionGP (Mdl) if (nargin < 1) error ("CompactRegressionGP: too few input arguments."); endif if (! isa (Mdl, 'RegressionGP')) error (strcat ("CompactRegressionGP: MDL must be a RegressionGP", ... " object.")); endif this.PredictorNames = Mdl.PredictorNames; this.ExpandedPredictorNames = Mdl.ExpandedPredictorNames; this.ResponseName = Mdl.ResponseName; this.CategoricalPredictors = Mdl.CategoricalPredictors; this.FitMethod = Mdl.FitMethod; this.BasisFunction = Mdl.BasisFunction; this.Beta = Mdl.Beta; this.Sigma = Mdl.Sigma; this.KernelFunction = Mdl.KernelFunction; this.KernelInformation = Mdl.KernelInformation; this.PredictMethod = Mdl.PredictMethod; this.Alpha = Mdl.Alpha; this.ActiveSetVectors = Mdl.ActiveSetVectors; this.ActiveSetMethod = Mdl.ActiveSetMethod; this.ActiveSetSize = Mdl.ActiveSetSize; this.PredictorLocation = Mdl.PredictorLocation; this.PredictorScale = Mdl.PredictorScale; this.ResponseTransform = Mdl.ResponseTransform; endfunction endmethods methods (Static, Hidden) function mdl = load_model (filename, data) ## The compact model is rebuilt field by field: it has no training data ## to construct itself from, so an empty shell is filled directly. mdl = CompactRegressionGP.empty_model (); fields = fieldnames (data); for k = 1:numel (fields) mdl.(fields{k}) = data.(fields{k}); endfor endfunction ## An unfitted shell, which only load_model needs. function mdl = empty_model () Mdl = RegressionGP ([0; 1], [0; 1], 'FitMethod', 'none'); mdl = CompactRegressionGP (Mdl); endfunction endmethods endclassdef %!demo %! ## A compact model predicts what the full model predicts, and carries none %! ## of the training data. %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.05 * cos (9*x); %! Mdl = fitrgp (x, y); %! CMdl = compact (Mdl) %! xq = [0.15; 0.55; 0.85]; %! [yq, ysd] = predict (CMdl, xq) %!test %! ## The compact model carries the fitted surface and nothing that describes %! ## the training data %! x = linspace (0, 1, 15)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! Mdl = RegressionGP (x, y); %! CMdl = compact (Mdl); %! assert_equal (class (CMdl), 'CompactRegressionGP'); %! assert_equal (CMdl.Beta, Mdl.Beta); %! assert_equal (CMdl.Sigma, Mdl.Sigma); %! assert_equal (CMdl.KernelFunction, Mdl.KernelFunction); %! assert_equal (CMdl.KernelInformation, Mdl.KernelInformation); %! assert_equal (CMdl.Alpha, Mdl.Alpha); %! assert_equal (CMdl.ActiveSetVectors, Mdl.ActiveSetVectors); %! assert_equal (CMdl.ResponseName, Mdl.ResponseName); %! assert_equal (CMdl.PredictorNames, Mdl.PredictorNames); %!test %! ## It predicts exactly what the full model predicts, standard deviation %! ## and interval included, because it predicts through the same code %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.1 * cos (7*x); %! Mdl = RegressionGP (x, y); %! CMdl = compact (Mdl); %! xq = [0.05; 0.33; 0.5; 0.77; 0.95]; %! [y1, s1, i1] = predict (Mdl, xq); %! [y2, s2, i2] = predict (CMdl, xq); %! assert_equal (y2, y1, 1e-14); %! assert_equal (s2, s1, 1e-14); %! assert_equal (i2, i1, 1e-14); %!test %! ## The R2024a values, reached through the compact model %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.1 * cos (7*x); %! CMdl = compact (RegressionGP (x, y)); %! xq = [0.05; 0.33; 0.5; 0.77; 0.95]; %! assert_equal (predict (CMdl, xq), ... %! [0.404401855406521; 0.809355242891053; ... %! -0.093708552144171; -0.928420803239529; ... %! -0.217104024154071], 1e-7); %!test %! ## The interval level is settable here as it is on the full model %! x = linspace (0, 1, 15)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! CMdl = compact (RegressionGP (x, y)); %! xq = [0.2; 0.6]; %! [yp, ysd, yint] = predict (CMdl, xq); %! assert_equal (yint(:,2) - yp, norminv (0.975) * ysd, 1e-12); %! [~, ~, yint90] = predict (CMdl, xq, 'Alpha', 0.10); %! assert (all (yint90(:,2) - yint90(:,1) < yint(:,2) - yint(:,1))); %!test %! ## Standardization is carried over, so the compact model transforms new %! ## data the way the full model did %! X = [linspace(0, 10, 20)', linspace(-5, 5, 20)']; %! y = 0.3 * X(:,1) - 0.2 * X(:,2); %! Mdl = RegressionGP (X, y, 'Standardize', true); %! CMdl = compact (Mdl); %! assert_equal (CMdl.PredictorLocation, Mdl.PredictorLocation); %! assert_equal (CMdl.PredictorScale, Mdl.PredictorScale); %! assert_equal (predict (CMdl, X), predict (Mdl, X), 1e-14); %!test %! ## loss on the compact model agrees with loss on the full one %! x = linspace (0, 1, 15)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! Mdl = RegressionGP (x, y); %! CMdl = compact (Mdl); %! assert_equal (loss (CMdl, x, y), loss (Mdl, x, y), 1e-14); %! assert_equal (loss (CMdl, x, y, 'LossFun', 'mae'), ... %! loss (Mdl, x, y, 'LossFun', 'mae'), 1e-14); %! w = linspace (1, 2, 15)'; %! assert_equal (loss (CMdl, x, y, 'Weights', w), ... %! loss (Mdl, x, y, 'Weights', w), 1e-14); %!test %! ## A compact model saved and loaded predicts what it predicted before %! x = linspace (0, 1, 15)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! CMdl = compact (RegressionGP (x, y)); %! fname = tempname (); %! savemodel (CMdl, fname); %! CMdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (CMdl2), 'CompactRegressionGP'); %! assert_equal (CMdl2.Beta, CMdl.Beta); %! assert_equal (CMdl2.Sigma, CMdl.Sigma); %! assert_equal (CMdl2.Alpha, CMdl.Alpha); %! assert_equal (CMdl2.ActiveSetVectors, CMdl.ActiveSetVectors); %! assert_equal (predict (CMdl2, x), predict (CMdl, x), 1e-14); %!test %! ## A response transform survives compacting %! x = linspace (0, 1, 12)'; %! y = cos (3*x); %! Mdl = RegressionGP (x, y, 'ResponseTransform', 'exp'); %! CMdl = compact (Mdl); %! assert_equal (CMdl.ResponseTransform, 'exp'); %! assert_equal (predict (CMdl, x), predict (Mdl, x), 1e-14); ## Test input validation for the constructor %!error CompactRegressionGP () %!error ... %! CompactRegressionGP (5) ## Test input validation for the predict method %!error ... %! predict (compact (RegressionGP (ones (5, 2), ones (5, 1)))) %!error ... %! predict (compact (RegressionGP (ones (5, 2), ones (5, 1))), []) %!error ... %! predict (compact (RegressionGP (ones (5, 2), ones (5, 1))), ones (3, 3)) %!error ... %! predict (compact (RegressionGP (ones (5, 2), ones (5, 1))), ... %! ones (3, 2), 'Alpha', 2) %!error ... %! predict (compact (RegressionGP (ones (5, 2), ones (5, 1))), ... %! ones (3, 2), 'bogus', 1) ## Test input validation for the loss method %!error ... %! loss (compact (RegressionGP (ones (5, 2), ones (5, 1))), ones (3, 2)) %!error ... %! loss (compact (RegressionGP (ones (5, 2), ones (5, 1))), 'a', ones (3, 1)) %!error ... %! loss (compact (RegressionGP (ones (5, 2), ones (5, 1))), ... %! ones (3, 2), ones (2, 1)) %!error ... %! loss (compact (RegressionGP (ones (5, 2), ones (5, 1))), ones (3, 2), ... %! ones (3, 1), 'LossFun', 'bogus') ## Every documented response transform reaches the response that is reported. %!test %! load fisheriris %! Mdl = compact (fitrgp (meas(:,2:4), meas(:,1))); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! T = {'identity', @(x) x; 'exp', @(x) exp (x); 'log', @(x) log (x)}; %! for i = 1:rows (T) %! Mdl.ResponseTransform = T{i,1}; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, T{i,2}(raw), 1e-12); %! endfor ## A function handle is taken as given and applied to the response. %!test %! load fisheriris %! Mdl = compact (fitrgp (meas(:,2:4), meas(:,1))); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! Mdl.ResponseTransform = @(x) x .^ 2; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, raw .^ 2, 1e-12); statistics-release-1.9.2/inst/Supervised_Learning/CompactRegressionNeuralNetwork.m000066400000000000000000000720151524624707500306270ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftp {statistics} CompactRegressionNeuralNetwork ## ## Compact neural network regression ## ## A @code{CompactRegressionNeuralNetwork} object holds a neural network ## regression model that has dropped its training data. ## ## Create a @code{CompactRegressionNeuralNetwork} object by using the ## @code{compact} method of a @code{RegressionNeuralNetwork} object. ## ## The compact model keeps what is needed to answer about new data, the layer ## weights and biases, the activations, the standardization and the response ## transform, and drops what only describes the fit: the predictor and response ## data, the observation weights, the rows used, the number of observations and ## the iteration by iteration training history. @code{predict} and ## @code{loss} therefore agree with the full model to the last digit, while ## @code{resubPredict} and @code{resubLoss} do not exist here, there being no ## training data left to resubstitute. ## ## @seealso{RegressionNeuralNetwork, fitrnet} ## @end deftp classdef CompactRegressionNeuralNetwork properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CompactRegressionNeuralNetwork} {property} NumPredictors ## ## Number of predictors ## ## A positive integer scalar. This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {CompactRegressionNeuralNetwork} {property} PredictorNames ## ## Names of the predictors ## ## A cell array of character vectors. This property is read-only. ## ## @end deftp PredictorNames = []; ## -*- texinfo -*- ## @deftp {CompactRegressionNeuralNetwork} {property} ResponseName ## ## Name of the response variable ## ## A character vector. This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {CompactRegressionNeuralNetwork} {property} Sigma ## ## Standard deviation of the predictors ## ## A row vector with one entry per predictor, used for standardization. ## Empty when the predictor data were not standardized. This property is ## read-only. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {CompactRegressionNeuralNetwork} {property} Mu ## ## Mean of the predictors ## ## A row vector with one entry per predictor, used for standardization. ## Empty when the predictor data were not standardized. This property is ## read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {CompactRegressionNeuralNetwork} {property} LayerSizes ## ## Sizes of the fully connected hidden layers ## ## A row vector of positive integers, one per hidden layer. This property ## is read-only. ## ## @end deftp LayerSizes = []; ## -*- texinfo -*- ## @deftp {CompactRegressionNeuralNetwork} {property} Activations ## ## Activation functions of the hidden layers ## ## A character vector, or a cell array of character vectors with one entry ## per hidden layer. This property is read-only. ## ## @end deftp Activations = []; ## -*- texinfo -*- ## @deftp {CompactRegressionNeuralNetwork} {property} OutputLayerActivation ## ## Activation function of the output layer ## ## A character vector. @qcode{'none'} applies the identity, so a ## prediction is an unrestricted real number. This property is read-only. ## ## @end deftp OutputLayerActivation = []; ## -*- texinfo -*- ## @deftp {CompactRegressionNeuralNetwork} {property} LayerWeights ## ## Weights the network learned ## ## A cell array with one entry per layer, the output layer included. This ## property is read-only. ## ## @end deftp LayerWeights = {}; ## -*- texinfo -*- ## @deftp {CompactRegressionNeuralNetwork} {property} LayerBiases ## ## Biases the network learned ## ## A cell array with one entry per layer, the output layer included. This ## property is read-only. ## ## @end deftp LayerBiases = {}; ## -*- texinfo -*- ## @deftp {CompactRegressionNeuralNetwork} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector of column indices, and empty when none is. This ## property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {CompactRegressionNeuralNetwork} {property} ExpandedPredictorNames ## ## Names of the predictors as the model expanded them ## ## A cell array of character vectors. This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {CompactRegressionNeuralNetwork} {property} ResponseTransform ## ## Transformation applied to the predicted response ## ## A function handle, applied by @code{predict} to the network's output. ## It may be set after construction, either to a handle or to the name of ## a supported transformation. ## ## @end deftp ResponseTransform = 'none'; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) RTfun = @(y) y; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.ResponseTransform (this, val) name = 'CompactRegressionNeuralNetwork'; [this.RTfun, this.ResponseTransform] = parseResponseTransform (val, name); endfunction ## -*- texinfo -*- ## @deftypefn {CompactRegressionNeuralNetwork} {@var{obj} =} CompactRegressionNeuralNetwork (@var{Mdl}) ## @deftypefnx {CompactRegressionNeuralNetwork} {@var{obj} =} CompactRegressionNeuralNetwork () ## ## Create a @code{CompactRegressionNeuralNetwork} object. ## ## @var{Mdl} is the @code{RegressionNeuralNetwork} object to ## compact. The documented way to reach this constructor is the ## @code{compact} method. ## ## Called with no arguments it returns an object with its properties ## empty, which is how a saved model is rebuilt before its values are ## filled in. ## ## @end deftypefn function this = CompactRegressionNeuralNetwork (Mdl = []) ## Check for appropriate class if (isempty (Mdl)) return; elseif (! strcmpi (class (Mdl), 'RegressionNeuralNetwork')) error (strcat ("CompactRegressionNeuralNetwork: invalid", ... " regression object.")); endif ## Save properties to compact model. The training data, the observation ## weights, the rows used, the observation count and the training ## history are deliberately left behind: they describe the fit, not the ## model, and keeping them is what "compact" exists to avoid. this.NumPredictors = Mdl.NumPredictors; this.PredictorNames = Mdl.PredictorNames; this.ResponseName = Mdl.ResponseName; this.ResponseTransform = Mdl.ResponseTransform; this.RTfun = Mdl.RTfun; this.Sigma = Mdl.Sigma; this.Mu = Mdl.Mu; this.LayerSizes = Mdl.LayerSizes; this.Activations = Mdl.Activations; this.OutputLayerActivation = Mdl.OutputLayerActivation; this.LayerWeights = Mdl.LayerWeights; this.LayerBiases = Mdl.LayerBiases; this.CategoricalPredictors = Mdl.CategoricalPredictors; this.ExpandedPredictorNames = Mdl.ExpandedPredictorNames; endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n CompactRegressionNeuralNetwork\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); str = repmat ({'%d'}, 1, numel (this.LayerSizes)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.LayerSizes); fprintf ("%+25s: %s\n", 'LayerSizes', str); if (iscellstr (this.Activations)) str = repmat ({'''%s'''}, 1, numel (this.Activations)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.Activations{:}); fprintf ("%+25s: %s\n", 'Activations', str); else # character vector fprintf ("%+25s: '%s'\n", 'Activations', this.Activations); endif fprintf ("%+25s: '%s'\n", 'OutputLayerActivation', ... this.OutputLayerActivation); fprintf ("%+25s: '%s'\n", 'ResponseTransform', this.ResponseTransform); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {CompactRegressionNeuralNetwork} {@var{yFit} =} predict (@var{obj}, @var{XC}) ## ## Predict the response for new data with a compact neural network ## regression model. ## ## @code{@var{yFit} = predict (@var{obj}, @var{XC})} returns a column ## vector holding the predicted response for each row of @var{XC}. It ## agrees with the full model this object was compacted from. ## ## @itemize ## @item ## @var{obj} must be a @qcode{CompactRegressionNeuralNetwork} class object. ## @item ## @var{XC} must be a numeric matrix with the same number of predictors as ## the data the model was trained on. ## @end itemize ## ## @seealso{CompactRegressionNeuralNetwork, RegressionNeuralNetwork} ## @end deftypefn function yFit = predict (this, XC) ## Check for sufficient input arguments if (nargin < 2) error (strcat ("CompactRegressionNeuralNetwork.predict:", ... " too few input arguments.")); endif ## Check for valid XC if (isempty (XC)) error ("CompactRegressionNeuralNetwork.predict: XC is empty."); elseif (this.NumPredictors != columns (XC)) error (strcat ("CompactRegressionNeuralNetwork.predict:", ... " XC must have the same number of predictors", ... " as the trained neural network model.")); endif ## Standardize (if necessary) if (! isempty (this.Mu)) XC = (XC - this.Mu) ./ this.Sigma; endif ## The network's output is the second return value; the first is an ## index of the largest output, constant for a single regression unit. NumThreads = nproc (); [~, yFit] = fcnnpredict (this.LayerWeights, this.LayerBiases, ... this.Activations, this.OutputLayerActivation, ... XC, NumThreads); ## Apply ResponseTransform yFit = this.RTfun (yFit); endfunction ## -*- texinfo -*- ## @deftypefn {CompactRegressionNeuralNetwork} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactRegressionNeuralNetwork} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Compute the regression loss of a compact neural network model. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} returns the ## weighted mean squared error between the response @var{Y} and the ## response the model predicts for @var{X}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{CompactRegressionNeuralNetwork} class object. ## @item ## @var{X} must be a numeric matrix with the same number of predictors as ## the data the model was trained on. ## @item ## @var{Y} must be a numeric vector with as many rows as @var{X}. ## @end itemize ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} accepts the ## following @qcode{Name-Value} pairs. ## ## @multitable @columnfractions 0.28 0.72 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'LossFun'} @tab @qcode{'mse'}, the default, or a function ## handle called as @code{@var{lossfun} (@var{Y}, @var{yFit}, @var{W})} ## and returning a scalar. ## ## @item @qcode{'Weights'} @tab A numeric vector of observation weights ## with one entry per row of @var{X}. It defaults to a uniform weight. ## The weights are normalized to sum to one before the loss is formed. ## @end multitable ## ## @seealso{CompactRegressionNeuralNetwork, RegressionNeuralNetwork} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error (strcat ("CompactRegressionNeuralNetwork.loss:", ... " too few input arguments.")); endif if (mod (numel (varargin), 2) != 0) error (strcat ("CompactRegressionNeuralNetwork.loss: Name-Value", ... " arguments must be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, 'loss'); ## Defaults, then the optional pairs LossFun = 'mse'; args = varargin; keep = true (1, numel (args)); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("CompactRegressionNeuralNetwork.loss: parameter", ... " name must be a character vector.")); endif if (strcmpi (args{i}, 'lossfun')) LossFun = args{i+1}; if (! (is_function_handle (LossFun) || (ischar (LossFun) && isrow (LossFun)))) error (strcat ("CompactRegressionNeuralNetwork.loss: 'LossFun'", ... " must be a character vector or a function", ... " handle.")); endif if (ischar (LossFun) && ! strcmpi (LossFun, 'mse')) error (strcat ("CompactRegressionNeuralNetwork.loss:", ... " unsupported 'LossFun' value.")); endif keep(i:i+1) = false; endif endfor W = getWeights_ (this, args(keep), rows (X), 'loss'); ## Weights are normalized to sum to one, as MATLAB does, so a loss is ## a weighted average rather than a weighted sum. W = W(:) / sum (W); yFit = predict (this, X); Y = Y(:); if (is_function_handle (LossFun)) L = LossFun (Y, yFit, W); if (! (isnumeric (L) && isscalar (L))) error (strcat ("CompactRegressionNeuralNetwork.loss: 'LossFun'", ... " must return a numeric scalar.")); endif else L = sum (W .* (Y - yFit) .^ 2); endif endfunction ## -*- texinfo -*- ## @deftypefn {CompactRegressionNeuralNetwork} {} savemodel (@var{obj}, @var{filename}) ## ## Save a compact neural network regression model to a file. ## ## @code{savemodel (@var{obj}, @var{filename})} saves every property of ## the @qcode{CompactRegressionNeuralNetwork} object @var{obj} into ## @var{filename} in binary format, so that it can be read back with ## @code{loadmodel}. ## ## @seealso{loadmodel, CompactRegressionNeuralNetwork} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error (strcat ("CompactRegressionNeuralNetwork.savemodel:", ... " too few input arguments.")); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error (strcat ("CompactRegressionNeuralNetwork.savemodel: FNAME", ... " must be a character vector.")); endif ## Generate variable for class name classdef_name = 'CompactRegressionNeuralNetwork'; ## Create variables from model properties NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; ResponseName = this.ResponseName; ResponseTransform = this.ResponseTransform; Sigma = this.Sigma; Mu = this.Mu; LayerSizes = this.LayerSizes; Activations = this.Activations; OutputLayerActivation = this.OutputLayerActivation; LayerWeights = this.LayerWeights; LayerBiases = this.LayerBiases; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; RTfun = this.RTfun; ## Save classdef name and all model properties as individual variables save ('-binary', fname, 'classdef_name', 'NumPredictors', ... 'PredictorNames', 'ResponseName', 'ResponseTransform', ... 'Sigma', 'Mu', 'LayerSizes', ... 'Activations', 'OutputLayerActivation', ... ... 'LayerWeights', 'LayerBiases', ... 'CategoricalPredictors', 'ExpandedPredictorNames', 'RTfun'); endfunction endmethods methods (Access = private) ## Shared validation for the assessment methods, so each reports under ## its own name. function [X, Y] = checkXY_ (this, X, Y, caller) if (isempty (X)) error ("CompactRegressionNeuralNetwork.%s: X is empty.", caller); elseif (this.NumPredictors != columns (X)) error (strcat ("CompactRegressionNeuralNetwork.%s: X must have the", ... " same number of predictors as the trained model."), ... caller); endif if (isempty (Y)) error ("CompactRegressionNeuralNetwork.%s: Y is empty.", caller); elseif (! (isnumeric (Y) && isreal (Y))) error (strcat ("CompactRegressionNeuralNetwork.%s: Y must be a", ... " real numeric vector."), caller); elseif (rows (X) != numel (Y)) error (strcat ("CompactRegressionNeuralNetwork.%s: Y must have the", ... " same number of rows as X."), caller); endif endfunction ## Pull a "Weights" pair out of the optional arguments, defaulting to a ## uniform weight, and reject any other name. function W = getWeights_ (this, args, n, caller) W = ones (n, 1); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("CompactRegressionNeuralNetwork.%s: parameter", ... " name must be a character vector."), caller); endif if (strcmpi (args{i}, 'weights')) W = args{i+1}; if (! (isnumeric (W) && isvector (W))) error (strcat ("CompactRegressionNeuralNetwork.%s: 'Weights'", ... " must be a numeric vector."), caller); endif if (numel (W) != n) error (strcat ("CompactRegressionNeuralNetwork.%s: size of", ... " 'Weights' must equal the number of", ... " rows in X."), caller); endif else error (strcat ("CompactRegressionNeuralNetwork.%s: invalid", ... " parameter name in optional paired", ... " arguments."), caller); endif endfor endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a CompactRegressionNeuralNetwork object mdl = CompactRegressionNeuralNetwork (); ## Get fieldnames from DATA (including private properties) names = fieldnames (data); ## The set methods for these read other properties, and one of them ## rebuilds Coeffs, so they are assigned once everything else is in ## place rather than in the order the file happens to list them. late = ismember (names, {'Cost', 'Prior', 'ScoreTransform', ... 'ResponseTransform'}); names = [names(! late); names(late)]; ## Copy data into object for i = 1:numel (names) ## Check fieldnames in DATA match the class properties try mdl.(names{i}) = data.(names{i}); catch error (strcat ("CompactRegressionNeuralNetwork.load_model:", ... " invalid model in '%s'."), filename) end_try_catch endfor endfunction endmethods endclassdef ## The compact model keeps what answers about new data and drops the fit. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = [randn(40, 2); randn(40, 2) + 3]; %! Y = X(:,1) - 2 * X(:,2); %! Mdl = fitrnet (X, Y, 'IterationLimit', 100); %! CMdl = compact (Mdl); %! assert_equal (class (CMdl), 'CompactRegressionNeuralNetwork'); %! kept = {'NumPredictors', 'PredictorNames', 'ResponseName', ... %! 'ResponseTransform', 'Sigma', 'Mu', 'LayerSizes', ... %! 'Activations', 'OutputLayerActivation', 'LayerWeights', ... %! 'LayerBiases', 'CategoricalPredictors', 'ExpandedPredictorNames'}; %! assert_equal (all (ismember (kept, properties (CMdl))), true); %! dropped = {'X', 'Y', 'W', 'NumObservations', 'RowsUsed', 'TrainingHistory'}; %! assert_equal (any (ismember (dropped, properties (CMdl))), false); ## A compact model predicts exactly what the model it came from predicts. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = [randn(50, 2); randn(50, 2) + 2]; %! Y = 3 * X(:,1) + X(:,2); %! Mdl = fitrnet (X, Y, 'LayerSizes', [8, 6], 'IterationLimit', 200); %! CMdl = compact (Mdl); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); %! assert_equal (loss (CMdl, X, Y), loss (Mdl, X, Y)); %! assert_equal (loss (CMdl, X, Y), resubLoss (Mdl), 1e-12); %! assert_equal (CMdl.LayerWeights, Mdl.LayerWeights); %! assert_equal (CMdl.LayerBiases, Mdl.LayerBiases); ## Standardization travels with the compact model, so it predicts on the ## scale the network was trained on. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = [randn(60, 1), randn(60, 1) * 1000]; %! Y = X(:,1) + X(:,2) / 1000; %! Mdl = fitrnet (X, Y, 'Standardize', true, 'IterationLimit', 200); %! CMdl = compact (Mdl); %! assert_equal (CMdl.Mu, Mdl.Mu); %! assert_equal (CMdl.Sigma, Mdl.Sigma); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); ## loss takes the same options as the full model's. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = linspace (0, 1, 30)'; %! Y = 3 * X + 1; %! CMdl = compact (fitrnet (X, Y, 'IterationLimit', 100)); %! yFit = predict (CMdl, X); %! assert_equal (loss (CMdl, X, Y), mean ((Y - yFit) .^ 2), 1e-12); %! assert_equal (loss (CMdl, X, Y, 'LossFun', 'mse'), loss (CMdl, X, Y), 1e-12); %! w = rand (30, 1) + 0.1; %! assert_equal (loss (CMdl, X, Y, 'Weights', w), ... %! loss (CMdl, X, Y, 'Weights', 5 * w), 1e-12); %! f = @(y, yf, ww) sum (ww .* abs (y - yf)); %! assert_equal (loss (CMdl, X, Y, 'LossFun', f), mean (abs (Y - yFit)), 1e-12); ## ResponseTransform travels, and can be replaced on the compact model. %!test %! rand ('seed', 42); %! X = linspace (0, 1, 20)'; %! Mdl = fitrnet (X, 2 * X + 1, 'ResponseTransform', 'exp', ... %! 'IterationLimit', 50); %! CMdl = compact (Mdl); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); %! CMdl.ResponseTransform = 'none'; %! assert_equal (predict (CMdl, X), log (predict (Mdl, X)), 1e-12); ## A saved compact model comes back carrying its own numbers. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = linspace (0, 1, 30)'; %! Y = 4 * X - 1; %! CMdl = compact (fitrnet (X, Y, 'LayerSizes', [6, 4], ... %! 'IterationLimit', 60)); %! fname = tempname (); %! savemodel (CMdl, fname); %! C2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (C2), 'CompactRegressionNeuralNetwork'); %! assert_equal (C2.LayerWeights, CMdl.LayerWeights); %! assert_equal (C2.LayerBiases, CMdl.LayerBiases); %! assert_equal (C2.NumPredictors, CMdl.NumPredictors); %! assert_equal (C2.LayerSizes, CMdl.LayerSizes); %! assert_equal (predict (C2, X), predict (CMdl, X)); ## Test input validation for the constructor %!error ... %! CompactRegressionNeuralNetwork (1) %!error ... %! CompactRegressionNeuralNetwork (fitcnet (ones (4, 2), [1; 1; 2; 2])) ## Test input validation for predict and loss %!shared CRNN %! rand ('seed', 42); %! CRNN = compact (fitrnet ([1; 2; 3; 4], [2; 4; 6; 8], 'IterationLimit', 10)); %!error ... %! predict (CRNN) %!error ... %! predict (CRNN, []) %!error ... %! predict (CRNN, ones (2, 3)) %!error ... %! loss (CRNN) %!error ... %! loss (CRNN, [1; 2], [2; 4], 'Weights') %!error ... %! loss (CRNN, [], [2; 4]) %!error ... %! loss (CRNN, ones (2, 3), [2; 4]) %!error ... %! loss (CRNN, [1; 2], []) %!error ... %! loss (CRNN, [1; 2], {'a'; 'b'}) %!error ... %! loss (CRNN, [1; 2], [2; 4; 6]) %!error ... %! loss (CRNN, [1; 2], [2; 4], 'LossFun', 5) %!error ... %! loss (CRNN, [1; 2], [2; 4], 'LossFun', 'mae') %!error ... %! loss (CRNN, [1; 2], [2; 4], 'LossFun', @(y, yf, w) [1, 2]) %!error ... %! loss (CRNN, [1; 2], [2; 4], 'Weights', {'a'}) %!error ... %! loss (CRNN, [1; 2], [2; 4], 'Weights', [1; 2; 3]) %!error ... %! loss (CRNN, [1; 2], [2; 4], 'Nope', 1) ## Test input validation for savemodel %!error ... %! savemodel (CRNN) %!error ... %! savemodel (CRNN, 5) %!error ... %! CRNN.ResponseTransform = 'nope'; %!error ... %! CRNN.ResponseTransform = @(y) [y; y]; ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Mdl = compact (fitrnet (X, Y, 'IterationLimit', 20)); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'CompactRegressionNeuralNetwork'); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ResponseTransform), class (Mdl.ResponseTransform)); %! assert_equal (predict (M2, X(1:5,:)), predict (Mdl, X(1:5,:)), 1e-12); ## Every documented response transform reaches the response that is reported. %!test %! load fisheriris %! Mdl = compact (fitrnet (meas(:,2:4), meas(:,1))); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! T = {'identity', @(x) x; 'exp', @(x) exp (x); 'log', @(x) log (x)}; %! for i = 1:rows (T) %! Mdl.ResponseTransform = T{i,1}; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, T{i,2}(raw), 1e-12); %! endfor ## A function handle is taken as given and applied to the response. %!test %! load fisheriris %! Mdl = compact (fitrnet (meas(:,2:4), meas(:,1))); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! Mdl.ResponseTransform = @(x) x .^ 2; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, raw .^ 2, 1e-12); statistics-release-1.9.2/inst/Supervised_Learning/CompactRegressionSVM.m000066400000000000000000001022751524624707500264760ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef CompactRegressionSVM ## -*- texinfo -*- ## @deftp {statistics} CompactRegressionSVM ## ## Compact Support Vector Machine regression ## ## A @code{CompactRegressionSVM} object holds a support vector regression ## model that has dropped its training data. ## ## Create a @code{CompactRegressionSVM} object by using the @code{compact} ## method of a @code{RegressionSVM} object. ## ## The compact model keeps what is needed to answer about new data, the ## support vectors and their coefficients, the intercept, the kernel, the ## standardization and the response transform, and drops what only describes ## the fit: the predictor and response data, the observation weights, the rows ## used, the observation count, and which training rows became support ## vectors. @code{predict} and @code{loss} therefore agree with the full ## model to the last digit, while @code{resubPredict} and @code{resubLoss} do ## not exist here, there being no training data left to resubstitute. ## ## @seealso{RegressionSVM, fitrsvm} ## @end deftp properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} NumPredictors ## ## Number of predictors ## ## A positive integer scalar. This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} PredictorNames ## ## Names of the predictors ## ## A cell array of character vectors. This property is read-only. ## ## @end deftp PredictorNames = []; ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} ResponseName ## ## Name of the response variable ## ## A character vector. This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} Epsilon ## ## Half-width of the insensitive tube ## ## A non-negative scalar, carried over from the model this one was ## compacted from. It is what the @qcode{'epsiloninsensitive'} loss ## charges against. This property is read-only. ## ## @end deftp Epsilon = []; ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} Sigma ## ## Standard deviation of the predictors ## ## A row vector with one entry per predictor, used for standardization. ## Empty when the predictor data were not standardized. This property is ## read-only. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} Mu ## ## Mean of the predictors ## ## A row vector with one entry per predictor, used for standardization. ## Empty when the predictor data were not standardized. This property is ## read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} Alpha ## ## Dual coefficients of the support vectors ## ## A numeric column vector with one entry per support vector, signed, as ## in the model this one was compacted from. This property is read-only. ## ## @end deftp Alpha = []; ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} Beta ## ## Primal coefficients, one per predictor ## ## A numeric column vector, equal to ## @code{obj.SupportVectors' * obj.Alpha}, and empty for any kernel other ## than linear. This property is read-only. ## ## @end deftp Beta = []; ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} Bias ## ## Intercept of the fitted function ## ## A numeric scalar. With a linear kernel the prediction is ## @code{X * obj.Beta + obj.Bias}. This property is read-only. ## ## @end deftp Bias = []; ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} SupportVectors ## ## The support vectors themselves ## ## A numeric matrix with one row per support vector, on the scale the ## model was trained on. This property is read-only. ## ## @end deftp SupportVectors = []; ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} KernelParameters ## ## Parameters of the kernel function ## ## A structure with fields @qcode{Function} and @qcode{Scale}, and ## @qcode{Order} for a polynomial kernel. @qcode{Function} names the ## kernel as MATLAB names it, so a radial basis kernel reports ## @qcode{'gaussian'} whichever spelling was given. This property is ## read-only. ## ## @end deftp KernelParameters = []; ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector of column indices, and empty when none is. This ## property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} ExpandedPredictorNames ## ## Names of the predictors as the model expanded them ## ## A cell array of character vectors. This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; endproperties ## The LIBSVM structure the engine works in. It is ours alone, with no ## MATLAB counterpart, so it is kept out of the property listing while ## staying readable for anyone who needs the raw model. properties (Hidden, GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} Model ## ## The trained LIBSVM model ## ## A structure as returned by @code{svmtrain} and consumed by ## @code{svmpredict}. This property is read-only. ## ## It is the engine's own structure and has no MATLAB counterpart, ## so it is kept out of @code{properties} and out of the online ## documentation. Reading it works exactly as it always did. ## ## @end deftp Model = []; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {CompactRegressionSVM} {property} ResponseTransform ## ## Transformation applied to the predicted response ## ## A function handle, applied by @code{predict} to the model's output. It ## may be set after construction, either to a handle or to the name of a ## supported transformation. ## ## @end deftp ResponseTransform = 'none'; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) RTfun = @(y) y; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.ResponseTransform (this, val) name = 'CompactRegressionSVM'; [this.RTfun, this.ResponseTransform] = parseResponseTransform (val, name); endfunction ## -*- texinfo -*- ## @deftypefn {CompactRegressionSVM} {@var{obj} =} CompactRegressionSVM (@var{Mdl}) ## @deftypefnx {CompactRegressionSVM} {@var{obj} =} CompactRegressionSVM () ## ## Create a @code{CompactRegressionSVM} object. ## ## @var{Mdl} is the @code{RegressionSVM} object to ## compact. The documented way to reach this constructor is the ## @code{compact} method. ## ## Called with no arguments it returns an object with its properties ## empty, which is how a saved model is rebuilt before its values are ## filled in. ## ## @end deftypefn function this = CompactRegressionSVM (Mdl = []) ## Check for appropriate class if (isempty (Mdl)) return; elseif (! strcmpi (class (Mdl), 'RegressionSVM')) error ("CompactRegressionSVM: invalid regression object."); endif ## Save properties to compact model. The training data, the observation ## weights, the rows used, the observation count and IsSupportVector are ## deliberately left behind: each is sized to the training set, which is ## what "compact" exists not to carry. this.NumPredictors = Mdl.NumPredictors; this.PredictorNames = Mdl.PredictorNames; this.ResponseName = Mdl.ResponseName; this.ResponseTransform = Mdl.ResponseTransform; this.RTfun = Mdl.RTfun; this.Epsilon = Mdl.Epsilon; this.Sigma = Mdl.Sigma; this.Mu = Mdl.Mu; this.KernelParameters = Mdl.KernelParameters; this.Model = Mdl.Model; this.Alpha = Mdl.Alpha; this.Beta = Mdl.Beta; this.Bias = Mdl.Bias; this.SupportVectors = Mdl.SupportVectors; this.CategoricalPredictors = Mdl.CategoricalPredictors; this.ExpandedPredictorNames = Mdl.ExpandedPredictorNames; endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n CompactRegressionSVM\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); fprintf ("%+25s: '%s'\n", 'ResponseTransform', this.ResponseTransform); fprintf ("%+25s: %g\n", 'Epsilon', this.Epsilon); fprintf ("%+25s: [%dx1 double]\n", 'Alpha', numel (this.Alpha)); if (! isempty (this.Beta)) fprintf ("%+25s: [%dx1 double]\n", 'Beta', numel (this.Beta)); endif fprintf ("%+25s: %f\n", 'Bias', this.Bias); fprintf ("%+25s: '%s'\n", 'KernelFunction', ... this.KernelParameters.Function); fprintf ("%+25s: [%dx%d double]\n", 'SupportVectors', ... rows (this.SupportVectors), columns (this.SupportVectors)); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {CompactRegressionSVM} {@var{obj} =} discardSupportVectors (@var{obj}) ## ## Discard the support vectors of a linear SVM model. ## ## @code{@var{obj} = discardSupportVectors (@var{obj})} empties ## @code{Alpha} and @code{SupportVectors}, leaving @code{Beta} and ## @code{Bias} to decide every prediction. A linear kernel needs ## nothing else, so the returned model predicts what it predicted ## before while carrying one vector in place of many. ## ## The kernel must be linear. Under any other the support vectors are ## part of the decision function and cannot be dropped. Discarding twice ## is not an error and changes nothing. ## ## @seealso{fitrsvm, RegressionSVM, CompactRegressionSVM} ## @end deftypefn function this = discardSupportVectors (this) if (nargin != 1) print_usage (); endif if (! strcmpi (this.KernelParameters.Function, 'linear')) error (strcat ("CompactRegressionSVM.discardSupportVectors: you", ... " cannot discard support vectors for a non-linear", ... " kernel.")); endif ## The engine keeps its own copy of the support vectors, so emptying ## the properties alone would free nothing. Collapsing the model onto ## the single vector that decides it leaves every scoring path as it ## was, svmpredict going on being the engine over one vector. this.Model = discardSVs (this.Model); this.Alpha = []; this.SupportVectors = []; endfunction ## -*- texinfo -*- ## @deftypefn {CompactRegressionSVM} {@var{yFit} =} predict (@var{obj}, @var{XC}) ## ## Predict the response for new data with a compact support vector ## regression model. ## ## @code{@var{yFit} = predict (@var{obj}, @var{XC})} returns a column ## vector holding the predicted response for each row of @var{XC}. It ## agrees with the full model this object was compacted from. ## ## @itemize ## @item ## @var{obj} must be a @qcode{CompactRegressionSVM} class object. ## @item ## @var{XC} must be a numeric matrix with the same number of predictors as ## the data the model was trained on. ## @end itemize ## ## @seealso{CompactRegressionSVM, RegressionSVM} ## @end deftypefn function yFit = predict (this, XC) ## Check for sufficient input arguments if (nargin < 2) error ("CompactRegressionSVM.predict: too few input arguments."); endif ## Check for valid XC if (isempty (XC)) error ("CompactRegressionSVM.predict: XC is empty."); elseif (this.NumPredictors != columns (XC)) error (strcat ("CompactRegressionSVM.predict: XC must have the", ... " same number of predictors as the trained model.")); endif ## Standardize (if necessary) if (! isempty (this.Mu)) XC = (XC - this.Mu) ./ this.Sigma; endif ## LIBSVM returns the fitted response as its first output for a ## regression model, there being no label to decide. yFit = svmpredict (zeros (rows (XC), 1), XC, this.Model, '-q'); ## Apply ResponseTransform yFit = this.RTfun (yFit); endfunction ## -*- texinfo -*- ## @deftypefn {CompactRegressionSVM} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {CompactRegressionSVM} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Compute the regression loss of a compact support vector machine model. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} returns the ## weighted mean squared error between the response @var{Y} and the ## response the model predicts for @var{X}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{CompactRegressionSVM} class object. ## @item ## @var{X} must be a numeric matrix with the same number of predictors as ## the data the model was trained on. ## @item ## @var{Y} must be a numeric vector with as many rows as @var{X}. ## @end itemize ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} accepts the ## following @qcode{Name-Value} pairs. ## ## @multitable @columnfractions 0.28 0.72 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'LossFun'} @tab @qcode{'mse'}, the default, ## @qcode{'epsiloninsensitive'}, or a function handle called as ## @code{@var{lossfun} (@var{Y}, @var{yFit}, @var{W})} returning a scalar. ## ## @item @qcode{'Weights'} @tab A numeric vector of observation weights ## with one entry per row of @var{X}. It defaults to a uniform weight. ## The weights are normalized to sum to one before the loss is formed. ## @end multitable ## ## @seealso{CompactRegressionSVM, RegressionSVM} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("CompactRegressionSVM.loss: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("CompactRegressionSVM.loss: Name-Value arguments", ... " must be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, 'loss'); ## Defaults, then the optional pairs LossFun = 'mse'; args = varargin; keep = true (1, numel (args)); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("CompactRegressionSVM.loss: parameter name must", ... " be a character vector.")); endif if (strcmpi (args{i}, 'lossfun')) LossFun = args{i+1}; if (! (is_function_handle (LossFun) || (ischar (LossFun) && isrow (LossFun)))) error (strcat ("CompactRegressionSVM.loss: 'LossFun' must be", ... " a character vector or a function handle.")); endif if (ischar (LossFun) && ! any (strcmpi (LossFun, ... {'mse', 'epsiloninsensitive'}))) error (strcat ("CompactRegressionSVM.loss: unsupported", ... " 'LossFun' value.")); endif keep(i:i+1) = false; endif endfor W = getWeights_ (this, args(keep), rows (X), 'loss'); ## Weights are normalized to sum to one, as MATLAB does, so a loss is ## a weighted average rather than a weighted sum. W = W(:) / sum (W); yFit = predict (this, X); Y = Y(:); if (is_function_handle (LossFun)) L = LossFun (Y, yFit, W); if (! (isnumeric (L) && isscalar (L))) error (strcat ("CompactRegressionSVM.loss: 'LossFun' must", ... " return a numeric scalar.")); endif elseif (strcmpi (LossFun, 'epsiloninsensitive')) L = sum (W .* max (0, abs (Y - yFit) - this.Epsilon)); else L = sum (W .* (Y - yFit) .^ 2); endif endfunction ## -*- texinfo -*- ## @deftypefn {CompactRegressionSVM} {} savemodel (@var{obj}, @var{filename}) ## ## Save a compact support vector regression model to a file. ## ## @code{savemodel (@var{obj}, @var{filename})} saves every property of ## the @qcode{CompactRegressionSVM} object @var{obj} into @var{filename} ## in binary format, so that it can be read back with @code{loadmodel}. ## ## @seealso{loadmodel, CompactRegressionSVM} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error ("CompactRegressionSVM.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error (strcat ("CompactRegressionSVM.savemodel: FNAME must be", ... " a character vector.")); endif ## Generate variable for class name classdef_name = 'CompactRegressionSVM'; ## Create variables from model properties NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; ResponseName = this.ResponseName; ResponseTransform = this.ResponseTransform; Epsilon = this.Epsilon; Sigma = this.Sigma; Mu = this.Mu; KernelParameters = this.KernelParameters; Model = this.Model; Alpha = this.Alpha; Beta = this.Beta; Bias = this.Bias; SupportVectors = this.SupportVectors; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; RTfun = this.RTfun; ## Save classdef name and all model properties as individual variables save ('-binary', fname, 'classdef_name', 'NumPredictors', ... 'PredictorNames', 'ResponseName', 'ResponseTransform', ... 'Epsilon', 'Sigma', 'Mu', ... 'Model', 'Alpha', 'Beta', 'Bias', 'SupportVectors', ... 'CategoricalPredictors', 'ExpandedPredictorNames', ... 'KernelParameters', 'RTfun'); endfunction endmethods methods (Access = private) ## Shared validation for the assessment methods, so each reports under ## its own name. function [X, Y] = checkXY_ (this, X, Y, caller) if (isempty (X)) error ("CompactRegressionSVM.%s: X is empty.", caller); elseif (this.NumPredictors != columns (X)) error (strcat ("CompactRegressionSVM.%s: X must have the same", ... " number of predictors as the trained model."), caller); endif if (isempty (Y)) error ("CompactRegressionSVM.%s: Y is empty.", caller); elseif (! (isnumeric (Y) && isreal (Y))) error (strcat ("CompactRegressionSVM.%s: Y must be a real", ... " numeric vector."), caller); elseif (rows (X) != numel (Y)) error (strcat ("CompactRegressionSVM.%s: Y must have the same", ... " number of rows as X."), caller); endif endfunction ## Pull a "Weights" pair out of the optional arguments, defaulting to a ## uniform weight, and reject any other name. function W = getWeights_ (this, args, n, caller) W = ones (n, 1); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("CompactRegressionSVM.%s: parameter name must", ... " be a character vector."), caller); endif if (strcmpi (args{i}, 'weights')) W = args{i+1}; if (! (isnumeric (W) && isvector (W))) error (strcat ("CompactRegressionSVM.%s: 'Weights' must be", ... " a numeric vector."), caller); endif if (numel (W) != n) error (strcat ("CompactRegressionSVM.%s: size of 'Weights'", ... " must equal the number of rows in X."), caller); endif else error (strcat ("CompactRegressionSVM.%s: invalid parameter name", ... " in optional paired arguments."), caller); endif endfor endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a CompactRegressionSVM object mdl = CompactRegressionSVM (); ## Get fieldnames from DATA (including private properties) names = fieldnames (data); ## The set methods for these read other properties, and one of them ## rebuilds Coeffs, so they are assigned once everything else is in ## place rather than in the order the file happens to list them. late = ismember (names, {'Cost', 'Prior', 'ScoreTransform', ... 'ResponseTransform'}); names = [names(! late); names(late)]; ## Copy data into object for i = 1:numel (names) ## Check fieldnames in DATA match the class properties try mdl.(names{i}) = data.(names{i}); catch error (strcat ("CompactRegressionSVM.load_model: invalid", ... " model in '%s'."), filename) end_try_catch endfor endfunction endmethods endclassdef ## The compact model keeps what answers about new data and drops the fit. %!test %! randn ('seed', 42); %! X = randn (50, 2); %! Y = X(:,1) - 2 * X(:,2); %! CMdl = compact (RegressionSVM (X, Y)); %! assert_equal (class (CMdl), 'CompactRegressionSVM'); %! kept = {'NumPredictors', 'PredictorNames', 'ResponseName', ... %! 'ResponseTransform', 'Epsilon', 'Sigma', 'Mu', ... %! 'Alpha', 'Beta', 'Bias', 'SupportVectors', ... %! 'CategoricalPredictors', 'ExpandedPredictorNames'}; %! assert_equal (all (ismember (kept, properties (CMdl))), true); %! dropped = {'X', 'Y', 'W', 'NumObservations', 'RowsUsed', ... %! 'IsSupportVector'}; %! assert_equal (any (ismember (dropped, properties (CMdl))), false); ## A compact model predicts exactly what the model it came from predicts. %!test %! randn ('seed', 42); %! X = randn (60, 3); %! Y = X * [1; -2; 0.5] + 3; %! Mdl = RegressionSVM (X, Y); %! CMdl = compact (Mdl); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); %! assert_equal (loss (CMdl, X, Y), loss (Mdl, X, Y)); %! assert_equal (loss (CMdl, X, Y), resubLoss (Mdl), 1e-12); %! assert_equal (CMdl.Alpha, Mdl.Alpha); %! assert_equal (CMdl.Beta, Mdl.Beta); %! assert_equal (CMdl.Bias, Mdl.Bias); %! assert_equal (CMdl.SupportVectors, Mdl.SupportVectors); %! assert_equal (CMdl.Epsilon, Mdl.Epsilon); ## With a linear kernel the compact model is a plain linear function. %!test %! randn ('seed', 42); %! X = randn (40, 2); %! Y = X * [3; -1] + 2; %! CMdl = compact (RegressionSVM (X, Y)); %! assert_equal (X * CMdl.Beta + CMdl.Bias, predict (CMdl, X), 1e-8); %! assert_equal (CMdl.Beta, CMdl.SupportVectors' * CMdl.Alpha, 1e-12); ## Standardization travels with the compact model. %!test %! randn ('seed', 42); %! X = [randn(60, 1), randn(60, 1) * 1000]; %! Y = X(:,1) + X(:,2) / 1000; %! Mdl = RegressionSVM (X, Y, 'Standardize', true, 'Epsilon', 0.01); %! CMdl = compact (Mdl); %! assert_equal (CMdl.Mu, Mdl.Mu); %! assert_equal (CMdl.Sigma, Mdl.Sigma); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); ## A non-linear kernel compacts too, and keeps Beta empty. %!test %! randn ('seed', 42); %! X = randn (40, 2); %! Y = sum (X .^ 2, 2); %! Mdl = RegressionSVM (X, Y, 'KernelFunction', 'rbf'); %! CMdl = compact (Mdl); %! assert_equal (isempty (CMdl.Beta), true); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); ## loss takes the same options as the full model's. %!test %! randn ('seed', 42); %! X = randn (30, 2); %! Y = X(:,1) * 3 + 1; %! Mdl = RegressionSVM (X, Y, 'Epsilon', 0.5); %! CMdl = compact (Mdl); %! yFit = predict (CMdl, X); %! assert_equal (loss (CMdl, X, Y), mean ((Y - yFit) .^ 2), 1e-12); %! assert_equal (loss (CMdl, X, Y, 'LossFun', 'epsiloninsensitive'), ... %! mean (max (0, abs (Y - yFit) - 0.5)), 1e-12); %! w = rand (30, 1) + 0.1; %! assert_equal (loss (CMdl, X, Y, 'Weights', w), ... %! loss (CMdl, X, Y, 'Weights', 4 * w), 1e-12); %! f = @(y, yf, ww) sum (ww .* abs (y - yf)); %! assert_equal (loss (CMdl, X, Y, 'LossFun', f), ... %! mean (abs (Y - yFit)), 1e-12); ## ResponseTransform travels, and can be replaced on the compact model. %!test %! X = [linspace(0, 1, 20)', linspace(1, 2, 20)']; %! Mdl = RegressionSVM (X, 2 * X(:,1) + 1, 'ResponseTransform', 'exp'); %! CMdl = compact (Mdl); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); %! CMdl.ResponseTransform = 'none'; %! assert_equal (predict (CMdl, X), log (predict (Mdl, X)), 1e-12); ## A saved compact model comes back carrying its own numbers. %!test %! randn ('seed', 42); %! X = randn (30, 2); %! Y = X(:,1) * 4 - 1; %! CMdl = compact (RegressionSVM (X, Y, 'Standardize', true)); %! fname = tempname (); %! savemodel (CMdl, fname); %! C2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (C2), 'CompactRegressionSVM'); %! assert_equal (C2.Alpha, CMdl.Alpha); %! assert_equal (C2.Bias, CMdl.Bias); %! assert_equal (C2.Epsilon, CMdl.Epsilon); %! assert_equal (C2.SupportVectors, CMdl.SupportVectors); %! assert_equal (predict (C2, X), predict (CMdl, X)); ## Test input validation for the constructor ## discardSupportVectors empties what R2024a empties and keeps what it ## keeps: Alpha and the support vectors go, Beta, Bias and IsSupportVector ## stay, and the class is unchanged. %!test %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,2:4); y = meas(keep,1); %! Mdl = compact (fitrsvm (X, y, "KernelFunction", "linear")); %! D = discardSupportVectors (Mdl); %! assert_equal (class (D), "CompactRegressionSVM"); %! assert_equal (isempty (D.Alpha), true); %! assert_equal (isempty (D.SupportVectors), true); %! assert_equal (D.Beta, Mdl.Beta); %! assert_equal (D.Bias, Mdl.Bias); ## A linear decision needs only Beta and Bias, so the model predicts what it ## predicted before. %!test %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,2:4); y = meas(keep,1); %! Mdl = compact (fitrsvm (X, y, "KernelFunction", "linear")); %! D = discardSupportVectors (Mdl); %! assert_equal (predict (D, X), predict (Mdl, X), 1e-10); ## The saving is real rather than cosmetic: the engine keeps its own copy of ## the support vectors, and it collapses to the one vector that decides a ## linear model. Emptying the properties alone would free nothing. %!test %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,2:4); y = meas(keep,1); %! Mdl = compact (fitrsvm (X, y, "KernelFunction", "linear")); %! D = discardSupportVectors (Mdl); %! assert_equal (rows (Mdl.Model.SVs) > 1, true); %! assert_equal (rows (D.Model.SVs), 1); %! assert_equal (predict (discardSupportVectors (D), X), predict (D, X)); %!error ... %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,2:4); y = meas(keep,1); %! discardSupportVectors (compact (fitrsvm (X, y, "KernelFunction", "rbf"))) %!error ... %! CompactRegressionSVM (1) %!error ... %! CompactRegressionSVM (fitcsvm (ones (4, 2), [1; 1; 2; 2])) ## Test input validation for predict and loss %!shared CRSVM %! CRSVM = compact (RegressionSVM ([1, 1; 2, 1; 3, 2; 4, 2], [2; 4; 6; 8])); %!error ... %! predict (CRSVM) %!error ... %! predict (CRSVM, []) %!error ... %! predict (CRSVM, ones (2, 3)) %!error ... %! loss (CRSVM) %!error ... %! loss (CRSVM, [1, 1; 2, 1], [2; 4], 'Weights') %!error ... %! loss (CRSVM, [], [2; 4]) %!error ... %! loss (CRSVM, ones (2, 3), [2; 4]) %!error ... %! loss (CRSVM, [1, 1; 2, 1], []) %!error ... %! loss (CRSVM, [1, 1; 2, 1], {'a'; 'b'}) %!error ... %! loss (CRSVM, [1, 1; 2, 1], [2; 4; 6]) %!error ... %! loss (CRSVM, [1, 1; 2, 1], [2; 4], 'LossFun', 5) %!error ... %! loss (CRSVM, [1, 1; 2, 1], [2; 4], 'LossFun', 'mae') %!error ... %! loss (CRSVM, [1, 1; 2, 1], [2; 4], 'LossFun', @(y, yf, w) [1, 2]) %!error ... %! loss (CRSVM, [1, 1; 2, 1], [2; 4], 'Weights', {'a'}) %!error ... %! loss (CRSVM, [1, 1; 2, 1], [2; 4], 'Weights', [1; 2; 3]) %!error ... %! loss (CRSVM, [1, 1; 2, 1], [2; 4], 'Nope', 1) ## Test input validation for savemodel %!error ... %! savemodel (CRSVM) %!error ... %! savemodel (CRSVM, 5) %!error ... %! CRSVM.ResponseTransform = 'nope'; ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Mdl = compact (fitrsvm (X, Y)); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'CompactRegressionSVM'); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ResponseTransform), class (Mdl.ResponseTransform)); %! assert_equal (predict (M2, X(1:5,:)), predict (Mdl, X(1:5,:)), 1e-12); ## KernelParameters comes across with the compact form. %!test %! load fisheriris %! Mdl = fitrsvm (meas(:,1:3), meas(:,4)); %! CMdl = compact (Mdl); %! assert_equal (CMdl.KernelParameters, Mdl.KernelParameters); %! assert_equal (CMdl.KernelParameters.Function, 'linear'); ## Every documented response transform reaches the response that is reported. %!test %! load fisheriris %! Mdl = compact (fitrsvm (meas(:,2:4), meas(:,1))); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! T = {'identity', @(x) x; 'exp', @(x) exp (x); 'log', @(x) log (x)}; %! for i = 1:rows (T) %! Mdl.ResponseTransform = T{i,1}; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, T{i,2}(raw), 1e-12); %! endfor ## A function handle is taken as given and applied to the response. %!test %! load fisheriris %! Mdl = compact (fitrsvm (meas(:,2:4), meas(:,1))); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! Mdl.ResponseTransform = @(x) x .^ 2; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, raw .^ 2, 1e-12); statistics-release-1.9.2/inst/Supervised_Learning/RegressionGAM.m000066400000000000000000003603071524624707500251300ustar00rootroot00000000000000## Copyright (C) 2023 Mohammed Azmat Khan ## Copyright (C) 2023-2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . classdef RegressionGAM ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} RegressionGAM (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{obj} =} RegressionGAM (@dots{}, @var{name}, @var{value}) ## ## Create a @qcode{RegressionGAM} class object containing a Generalized Additive ## Model (GAM) for regression. ## ## A @qcode{RegressionGAM} class object can store the predictors and response ## data along with various parameters for the GAM model. It is recommended to ## use the @code{fitrgam} function to create a @qcode{RegressionGAM} object. ## ## @code{@var{obj} = RegressionGAM (@var{X}, @var{Y})} returns an object of ## class RegressionGAM, with matrix @var{X} containing the predictor data and ## vector @var{Y} containing the continuous response data. ## ## @itemize ## @item ## @var{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or variables. ## @var{X} will be used to train the GAM model. ## @item ## @var{Y} must be @math{N*1} numeric vector containing the response data ## corresponding to the predictor data in @var{X}. @var{Y} must have same ## number of rows as @var{X}. ## @end itemize ## ## @code{@var{obj} = RegressionGAM (@dots{}, @var{name}, @var{value})} returns ## an object of class RegressionGAM with additional properties specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'predictors'} @tab Predictor Variable names, specified as ## a row vector cell of strings with the same length as the columns in @var{X}. ## If omitted, the program will generate default variable names ## @qcode{(x1, x2, ..., xn)} for each column in @var{X}. ## ## @item @qcode{'responsename'} @tab Response Variable Name, specified as ## a string. If omitted, the default value is @qcode{'Y'}. ## ## @item @qcode{'formula'} @tab (spline option) a model specification given as a ## string in ## the form @qcode{'Y ~ terms'} where @qcode{Y} represents the response variable ## and @qcode{terms} the predictor variables. The formula can be used to ## specify a subset of variables for training model. For example: ## @qcode{'Y ~ x1 + x2 + x3 + x4 + x1:x2 + x2:x3'} specifies four linear terms ## for the first four columns of for predictor data, and @qcode{x1:x2} and ## @qcode{x2:x3} specify the two interaction terms for 1st-2nd and 3rd-4th ## columns respectively. Only these terms will be used for training the model, ## but @var{X} must have at least as many columns as referenced in the formula. ## If Predictor Variable names have been defined, then the terms in the formula ## must reference to those. When @qcode{'formula'} is specified, all terms used ## for training the model are referenced in the @qcode{IntMatrix} field of the ## @var{obj} class object as a matrix containing the column indexes for each ## term including both the predictors and the interactions used. ## ## @item @qcode{'interactions'} @tab a logical matrix, a positive integer ## scalar, or the string @qcode{'all'} for defining the interactions between ## predictor variables. When given a logical matrix, it must have the same ## number of columns as @var{X} and each row corresponds to a different ## interaction term combining the predictors indexed as @qcode{true}. Each ## interaction term is appended as a column vector after the available predictor ## column in @var{X}. When @qcode{'all'} is defined, then all possible ## combinations of interactions are appended in @var{X} before training. At the ## moment, parsing a positive integer has the same effect as the @qcode{'all'} ## option. When @qcode{'interactions'} is specified, only the interaction terms ## appended to @var{X} are referenced in the @qcode{IntMatrix} field of the ## @var{obj} class object. ## ## @item @qcode{'knots'} @tab (spline option) a scalar or a row vector with the ## same ## columns as @var{X}. It defines the knots for fitting a polynomial when ## training the GAM. As a scalar, it is expanded to a row vector. The default ## value is 5, hence expanded to @qcode{ones (1, columns (X)) * 5}. You can ## parse a row vector with different number of knots for each predictor ## variable to be fitted with, although not recommended. ## ## @item @qcode{'order'} @tab (spline option) a scalar or a row vector with the ## same ## columns as @var{X}. It defines the order of the polynomial when training the ## GAM. As a scalar, it is expanded to a row vector. The default values is 3, ## hence expanded to @qcode{ones (1, columns (X)) * 3}. You can parse a row ## vector with different number of polynomial order for each predictor variable ## to be fitted with, although not recommended. ## ## @item @qcode{'dof'} @tab (spline option) a scalar or a row vector with the ## same columns ## as @var{X}. It defines the degrees of freedom for fitting a polynomial when ## training the GAM. As a scalar, it is expanded to a row vector. The default ## value is 8, hence expanded to @qcode{ones (1, columns (X)) * 8}. You can ## parse a row vector with different degrees of freedom for each predictor ## variable to be fitted with, although not recommended. ## ## @item @qcode{'tol'} @tab (spline option) a positive scalar to set the ## tolerance for ## convergence during training. By default, it is set to @qcode{1e-3}. ## @end multitable ## ## A row marked @qcode{(spline option)} belongs to the spline ## engine and requires @qcode{'FitMethod', 'splines'}; passing one ## under the default boosted-tree engine is an error rather than ## being ignored. The boosted-tree engine's own options are ## documented under @code{fitrgam}. ## ## You can parse either a @qcode{'formula'} or an @qcode{'interactions'} ## optional parameter. Parsing both parameters will result an error. ## Accordingly, you can only pass up to two parameters among @qcode{'knots'}, ## @qcode{'order'}, and @qcode{'dof'} to define the required polynomial for ## training the GAM model. ## ## Two weak learners are available, selected by @code{FitMethod}. ## ## @qcode{'boostedtrees'}, the default, boosts one shallow decision tree per ## predictor in each round, which is the scheme MATLAB's generalized additive ## model uses. A second phase then boosts trees over pairs of predictors, ## where interactions are asked for. ## ## @qcode{'splines'} boosts a smoothing spline per predictor until the ## residual sum of squares changes by less than @qcode{'Tol'}. It has no ## MATLAB counterpart and is an Octave extension, kept because a smooth ## additive fit is a genuinely different and often better answer than a ## staircase of stumps. A standard deviation and a prediction interval are ## available from it alone. ## ## The two take different arguments, and an argument meant for one is refused ## by the other rather than ignored. ## ## The choice is visible in the properties. @code{Knots}, @code{Order}, ## @code{DoF}, @code{Formula}, @code{Tol}, @code{BaseModel}, ## @code{ModelwInt} and @code{IntMatrix} describe a spline fit and are empty ## under the boosted-tree engine, while @code{ModelParameters}, ## @code{ReasonForTermination}, @code{BinEdges}, ## @code{PairDetectionBinEdges} and @code{TreeModel} describe a tree fit and ## are empty under the spline engine. ## ## Fitted values are not expected to equal MATLAB's even under ## @qcode{'boostedtrees'}. The stopping rule and the step-reduction limit are ## not recoverable from anything MATLAB reports, so this engine documents its ## own; what the two share is the estimator and the reported surface, not the ## arithmetic. ## ## @seealso{fitrgam, regress, regress_gp} ## @end deftypefn properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} X ## ## Predictor data ## ## A numeric matrix with one row per observation and one column per ## predictor of the training data. This property is read-only. ## ## @end deftp X = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} Y ## ## Response data ## ## A numeric column vector with one entry per observation of the ## training data. This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} NumObservations ## ## Number of observations ## ## A positive integer, the number of observations of the training data ## the model was fitted on, rows with missing values excluded. This ## property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} RowsUsed ## ## Rows used for fitting ## ## A logical column vector with the same length as the observations in the ## original predictor data @var{X}, true for each row that was used for ## fitting the RegressionGAM model. It is empty, @qcode{[]}, ## when every observation was used, so a non-empty value means that rows ## holding missing values were dropped. This property is read-only. ## ## @end deftp RowsUsed = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} NumPredictors ## ## Number of predictors ## ## A positive integer, the number of predictors of the training data. ## This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} PredictorNames ## ## Names of the predictor variables ## ## A cell array of character vectors naming the predictors, in the order ## they appear in the training data. This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} ResponseName ## ## Response variable name ## ## A character vector naming the response variable @var{Y}. This ## property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector holding the column of each predictor treated as ## categorical, and empty when none is. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} ExpandedPredictorNames ## ## Names of the expanded predictor variables ## ## A cell array of character vectors naming the predictors as the model ## sees them. It matches @code{PredictorNames} unless a categorical ## predictor was expanded into dummy variables. This property is ## read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} W ## ## Observation weights ## ## A numeric column vector with one entry per observation used for ## training, normalised to sum to one. This property is read-only. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} Intercept ## ## Intercept of the fitted model ## ## A numeric scalar, the mean of the response, which every additive term ## is measured against. This property is read-only. ## ## @end deftp Intercept = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} Formula ## ## Formula of the model ## ## A character vector naming the response and the terms of the model, as ## in @qcode{'Y ~ x1 + x2 + x1:x2'}, or empty when the model was not ## given one. This property is read-only. ## ## @end deftp Formula = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} Interactions ## ## Two-way interaction terms of the fitted model ## ## A @math{Kx2} matrix of predictor index pairs, one row per two-way term ## the model carries, and @code{zeros (0, 2)} when it carries none. It ## reports what was fitted rather than what was asked for, so a count of ## terms, @qcode{'all'}, a logical matrix and a formula all leave the same ## kind of value behind. This property is read-only. ## ## A main effect names one predictor and a higher-order term names three ## or more, and neither has a two-column form, so neither appears here. ## @code{IntMatrix} remains the complete record of every term fitted. ## ## @end deftp Interactions = zeros (0, 2); ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} Knots ## ## Knots of the spline fitting ## ## A numeric vector with one entry per predictor, the number of breaks ## the spline of that predictor is fitted over. This property is ## read-only. ## ## @end deftp Knots = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} Order ## ## Order of the spline fitting ## ## A numeric vector with one entry per predictor, the polynomial order of ## the spline of that predictor. This property is read-only. ## ## @end deftp Order = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} DoF ## ## Degrees of freedom of the spline fitting ## ## A numeric vector with one entry per predictor, the sum of its number ## of knots and its order. This property is read-only. ## ## @end deftp DoF = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} Tol ## ## Tolerance for convergence ## ## A positive scalar, the largest change in the residual sum of squares ## of a backfitting cycle that counts as converged. This property is ## read-only. ## ## @end deftp Tol = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} IsStandardDeviationFit ## ## Flag for a fitted standard deviation model ## ## A boolean flag, always @qcode{false}, as this class estimates the ## standard deviation of a prediction from the residuals of the fit ## rather than fitting a model for it. This property is read-only. ## ## @end deftp IsStandardDeviationFit = false; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} BaseModel ## ## Model without interaction terms ## ## A structure holding the intercept, the piecewise polynomial of each ## predictor, the number of backfitting cycles, the residuals and the ## residual sum of squares of the model fitted without interaction ## terms. This property is read-only. ## ## @end deftp BaseModel = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} ModelwInt ## ## Model with interaction terms ## ## A structure of the same fields as @code{BaseModel}, for the model ## fitted with the interaction terms, and empty when none was asked for. ## This property is read-only. ## ## @end deftp ModelwInt = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} IntMatrix ## ## Every term the model fits ## ## A logical matrix with one row per term and one column per predictor, ## true wherever the term multiplies that predictor. A row naming one ## predictor is a main effect, two an interaction, and three or more a ## higher-order term. This property is read-only. ## ## It is the complete record, where @code{Interactions} reports only the ## two-way terms, in the form MATLAB reports them. It is also the form ## the @qcode{'Interactions'} option takes back, so passing it to the ## constructor rebuilds a model over the same terms. ## ## @end deftp IntMatrix = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} BinEdges ## ## Bin edges of the predictors ## ## A cell array with one entry per predictor, holding that predictor's bin ## edges where the model discretized it before fitting. It is empty here ## and stays empty: this generalized additive model is built from splines, ## which take the predictors as they are, where MATLAB's is built from ## boosted trees and bins them. That difference is described in the class ## documentation. ## ## This property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} PairDetectionBinEdges ## ## Bin edges used to detect interactions ## ## A cell array with one row vector per predictor, holding the coarse cut ## points the residuals of the predictor phase were laid on while pairs ## were being tested. The grid is eight equal-frequency bins whatever the ## sample size, as MATLAB's is. It is empty when the model carries no ## interaction terms, and empty throughout under the spline engine, which ## does not bin. ## ## This property is read-only. ## ## @end deftp PairDetectionBinEdges = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} ModelParameters ## ## Parameters the model was fitted with ## ## A structure holding the fitting parameters. Under the boosted-tree ## engine it carries MATLAB's own fields, with @qcode{Type} reading ## @qcode{'regression'}; under the spline engine it describes that scheme ## instead, since none of the tree vocabulary applies to it. ## ## This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} ReasonForTermination ## ## Why each fitting phase stopped ## ## A structure with the fields @qcode{PredictorTrees} and ## @qcode{InteractionTrees}, each saying why that phase ended. A phase ## that never ran reports an empty character vector. It is empty under ## the spline engine, which has no tree budget to exhaust. ## ## This property is read-only. ## ## @end deftp ReasonForTermination = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} FitMethod ## ## Which engine fitted the model ## ## A character vector, either @qcode{'boostedtrees'} or ## @qcode{'splines'}. The default is @qcode{'boostedtrees'}, the scheme ## MATLAB's generalized additive model uses. @qcode{'splines'} selects ## the penalised-spline engine, an Octave extension with no MATLAB ## counterpart and the scheme this class fitted before version 1.9.0. The ## two engines take different arguments and an argument meant for one is ## refused by the other rather than ignored. ## ## This property is read-only. ## ## @end deftp FitMethod = 'boostedtrees'; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} TreeModel ## ## The fitted shape functions and interaction surfaces ## ## A structure with fields @qcode{ShapeValues}, @qcode{PairValues} and ## @qcode{Pairs}, holding what the boosted-tree engine fitted. MATLAB ## exposes no equivalent, reporting its bin edges but never the values on ## them, so this is an Octave extension. It is empty under the spline ## engine, whose fit lives in @code{BaseModel} and @code{ModelwInt}. ## ## This property is read-only. ## ## @end deftp TreeModel = []; ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} HyperparameterOptimizationResults ## ## Results of the hyperparameter optimization ## ## @strong{Always empty.} It is declared for MATLAB compatibility, where ## it holds what an automatic search over the hyperparameters found. This ## class fits the parameters it is given and runs no such search, so there ## is nothing to report. This property is read-only. ## ## @end deftp HyperparameterOptimizationResults = []; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {RegressionGAM} {property} ResponseTransform ## ## Transformation applied to the predicted response ## ## A function handle applied to the response the model predicts. Add or ## change it using dot notation, as in ## @qcode{@var{obj}.ResponseTransform = 'log'} or ## @qcode{@var{obj}.ResponseTransform = @@function_handle}. It defaults ## to @qcode{'none'}, the identity. ## ## @end deftp ResponseTransform = @(x) x; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) RTfun = @(y) y; ## How many trees each boosting phase actually fitted, which the budget ## in ModelParameters does not say: a phase may stop early. Hidden ## because MATLAB reports it on the partitioned classes and not on the ## model, and that is where this is read from. NumTrainedTrees = []; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.ResponseTransform (this, val) [this.RTfun, this.ResponseTransform] = parseResponseTransform ... (val, 'RegressionGAM'); endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n RegressionGAM\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); fprintf ("%+25s: %d\n", 'NumObservations', this.NumObservations); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); fprintf ("%+25s: '%s'\n", 'ResponseTransform', this.ResponseTransform); fprintf ("%+25s: %g\n", 'Intercept', this.Intercept); str = repmat ({'%d'}, 1, numel (this.Knots)); str = strcat ('[', strjoin (str, ' '), ']'); fprintf ("%+25s: %s\n", 'Knots', sprintf (str, this.Knots)); str = repmat ({'%d'}, 1, numel (this.Order)); str = strcat ('[', strjoin (str, ' '), ']'); fprintf ("%+25s: %s\n", 'Order', sprintf (str, this.Order)); fprintf ("%+25s: %g\n", 'Tol', this.Tol); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {RegressionGAM} {@var{obj} =} RegressionGAM (@var{X}, @var{Y}) ## @deftypefnx {RegressionGAM} {@var{obj} =} RegressionGAM (@dots{}, @var{name}, @var{value}) ## ## Fit a generalized additive model for regression. ## ## @var{X} is an @math{N*P} numeric matrix of predictor data, one ## observation per row, and @var{Y} is the continuous response of those ## @math{N} observations. The fit runs at construction, so @var{obj} ## arrives fitted. ## ## The @var{name}/@var{value} pairs the fit accepts, and the validation ## each one is held to, are listed in @code{help RegressionGAM}. ## @code{fitrgam} is the documented way to reach this constructor and ## takes the same pairs. ## ## @end deftypefn function this = RegressionGAM (X, Y, varargin) ## Check for sufficient number of input arguments if (nargin < 2) error ("RegressionGAM: too few input arguments."); endif ## Get training sample size and number of variables in training data nsample = rows (X); ndims_X = columns (X); ## Check correspondence between predictors and response if (nsample != rows (Y)) error ("RegressionGAM: number of rows in X and Y must be equal."); endif ## Set default values before parsing optional parameters PredictorNames = {}; # Predictor variable names ResponseName = []; # Response variable name Formula = []; # Formula for GAM model Interactions = []; # Interaction terms DoF = ones (1, ndims_X) * 8; # Degrees of freedom Order = ones (1, ndims_X) * 3; # Order of spline Knots = ones (1, ndims_X) * 5; # Knots Tol = 1e-3; # Tolerance for convergence ResponseTransform = 'none'; # Name of the transform RTfun = @(y) y; # and the callable it names ## Boosted-tree defaults, MATLAB's own. They are reported through ## ModelParameters, so they are part of the surface being matched and ## are not ours to improve. FitMethod = 'boostedtrees'; NumTreesPerPredictor = 300; NumTreesPerInteraction = 100; MaxNumSplitsPerPredictor = 1; MaxNumSplitsPerInteraction = 4; InitialLearnRateForPredictors = 1; InitialLearnRateForInteractions = 1; MaxPValue = 1; Verbose = 0; NumPrint = 10; ## Every name the caller asked for, so an argument meant for the other ## engine is refused instead of quietly doing nothing. namesGiven = {}; ## Number of parameters for Knots, DoF, Order (maximum 2 allowed) KOD = 0; ## Number of parameters for Formula, Interactions (maximum 1 allowed) F_I = 0; ## Parse extra parameters while (numel (varargin) > 0) namesGiven{end+1} = tolower (varargin{1}); switch (tolower (varargin {1})) case {'predictors', 'predictornames'} PredictorNames = varargin{2}; if (! isempty (PredictorNames)) if (! iscellstr (PredictorNames)) error (strcat ("RegressionGAM: PredictorNames must", ... " be a cellstring array.")); elseif (columns (PredictorNames) != columns (X)) error (strcat ("RegressionGAM: PredictorNames must", ... " have same number of columns as X.")); endif endif case 'responsetransform' [RTfun, ResponseTransform] = ... parseResponseTransform (varargin{2}, 'RegressionGAM'); case 'responsename' ResponseName = varargin{2}; if (! ischar (ResponseName)) error ("RegressionGAM: ResponseName must be a char string."); endif case 'formula' if (F_I < 1) Formula = varargin{2}; if (! ischar (Formula) && ! islogical (Formula)) error ("RegressionGAM: Formula must be a string."); endif F_I += 1; else error ("RegressionGAM: Interactions have been already defined."); endif case 'interactions' if (F_I < 1) tmp = varargin{2}; if (isnumeric (tmp) && isscalar (tmp) && tmp == fix (tmp) && tmp >= 0) Interactions = tmp; elseif (islogical (tmp)) Interactions = tmp; elseif (ischar (tmp) && strcmpi (tmp, 'all')) Interactions = tmp; else error ("RegressionGAM: invalid Interactions parameter."); endif F_I += 1; else error ("RegressionGAM: Formula has been already defined."); endif case 'knots' if (KOD < 2) Knots = varargin{2}; if (! isnumeric (Knots) || ! (isscalar (Knots) || isequal (size (Knots), [1, ndims_X]))) error ("RegressionGAM: invalid value for Knots."); endif DoF = Knots + Order; Order = DoF - Knots; KOD += 1; else error ("RegressionGAM: DoF and Order have been set already."); endif case 'order' if (KOD < 2) Order = varargin{2}; if (! isnumeric (Order) || ! (isscalar (Order) || isequal (size (Order), [1, ndims_X]))) error ("RegressionGAM: invalid value for Order."); endif DoF = Knots + Order; Knots = DoF - Order; KOD += 1; else error ("RegressionGAM: DoF and Knots have been set already."); endif case 'dof' if (KOD < 2) DoF = varargin{2}; if (! isnumeric (DoF) || ! (isscalar (DoF) || isequal (size (DoF), [1, ndims_X]))) error ("RegressionGAM: invalid value for DoF."); endif Knots = DoF - Order; Order = DoF - Knots; KOD += 1; else error ("RegressionGAM: Knots and Order have been set already."); endif case 'tol' Tol = varargin{2}; if (! (isnumeric (Tol) && isscalar (Tol) && (Tol > 0))) error ("RegressionGAM: Tolerance must be a Positive scalar."); endif case 'fitmethod' FitMethod = varargin{2}; if (! (ischar (FitMethod) && isrow (FitMethod)) || ! any (strcmpi (FitMethod, {'boostedtrees', 'splines'}))) error (strcat ("RegressionGAM: 'FitMethod' must be", ... " 'boostedtrees' or 'splines'.")); endif FitMethod = tolower (FitMethod); case 'numtreesperpredictor' NumTreesPerPredictor = varargin{2}; if (! isnumeric (NumTreesPerPredictor) || ! isscalar (NumTreesPerPredictor) || NumTreesPerPredictor < 1 || fix (NumTreesPerPredictor) != NumTreesPerPredictor) error (strcat ("RegressionGAM: 'NumTreesPerPredictor'", ... " must be a positive integer value.")); endif case 'numtreesperinteraction' NumTreesPerInteraction = varargin{2}; if (! isnumeric (NumTreesPerInteraction) || ! isscalar (NumTreesPerInteraction) || NumTreesPerInteraction < 1 || fix (NumTreesPerInteraction) != NumTreesPerInteraction) error (strcat ("RegressionGAM: 'NumTreesPerInteraction'", ... " must be a positive integer value.")); endif case 'maxnumsplitsperpredictor' MaxNumSplitsPerPredictor = varargin{2}; if (! isnumeric (MaxNumSplitsPerPredictor) || ! isscalar (MaxNumSplitsPerPredictor) || MaxNumSplitsPerPredictor < 1 || fix (MaxNumSplitsPerPredictor) != MaxNumSplitsPerPredictor) error (strcat ("RegressionGAM:", ... " 'MaxNumSplitsPerPredictor' must be a", ... " positive integer value.")); endif case 'maxnumsplitsperinteraction' MaxNumSplitsPerInteraction = varargin{2}; if (! isnumeric (MaxNumSplitsPerInteraction) || ! isscalar (MaxNumSplitsPerInteraction) || MaxNumSplitsPerInteraction < 1 || fix (MaxNumSplitsPerInteraction) != MaxNumSplitsPerInteraction) error (strcat ("RegressionGAM:", ... " 'MaxNumSplitsPerInteraction' must be a", ... " positive integer value.")); endif case 'initiallearnrateforpredictors' InitialLearnRateForPredictors = varargin{2}; if (! isnumeric (InitialLearnRateForPredictors) || ! isscalar (InitialLearnRateForPredictors) || InitialLearnRateForPredictors <= 0 || InitialLearnRateForPredictors > 1) error (strcat ("RegressionGAM:", ... " 'InitialLearnRateForPredictors' must be", ... " greater than 0 and at most 1.")); endif case 'initiallearnrateforinteractions' InitialLearnRateForInteractions = varargin{2}; if (! isnumeric (InitialLearnRateForInteractions) || ! isscalar (InitialLearnRateForInteractions) || InitialLearnRateForInteractions <= 0 || InitialLearnRateForInteractions > 1) error (strcat ("RegressionGAM:", ... " 'InitialLearnRateForInteractions' must be", ... " greater than 0 and at most 1.")); endif case 'verbose' Verbose = varargin{2}; if (! isnumeric (Verbose) || ! isscalar (Verbose) || Verbose < 0 || fix (Verbose) != Verbose) error (strcat ("RegressionGAM: 'Verbose' must be a", ... " non-negative integer value.")); endif case 'numprint' NumPrint = varargin{2}; if (! isnumeric (NumPrint) || ! isscalar (NumPrint) || NumPrint < 1 || fix (NumPrint) != NumPrint) error (strcat ("RegressionGAM: 'NumPrint' must be a positive", ... " integer value.")); endif case 'maxpvalue' MaxPValue = varargin{2}; if (! isnumeric (MaxPValue) || ! isscalar (MaxPValue) || MaxPValue < 0 || MaxPValue > 1) error (strcat ("RegressionGAM: 'MaxPValue' must be", ... " between 0 and 1.")); endif otherwise error (strcat ("RegressionGAM: invalid parameter name", ... " in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## An argument belongs to one engine or the other, and asking for one ## the chosen engine cannot honour is refused rather than ignored. splineOnly = {'knots', 'order', 'dof', 'formula', 'tol'}; treeOnly = {'numtreesperpredictor', 'numtreesperinteraction', ... 'maxnumsplitsperpredictor', 'maxnumsplitsperinteraction', ... 'initiallearnrateforpredictors', ... 'initiallearnrateforinteractions', 'maxpvalue', ... 'verbose', 'numprint'}; if (strcmp (FitMethod, 'boostedtrees')) clash = intersect (namesGiven, splineOnly); if (! isempty (clash)) error (strcat ("RegressionGAM: '", clash{1}, "' is a parameter", ... " of the spline engine and cannot be used with", ... " 'FitMethod' 'boostedtrees'.")); endif else clash = intersect (namesGiven, treeOnly); if (! isempty (clash)) error (strcat ("RegressionGAM: '", clash{1}, "' is a parameter", ... " of the boosted-tree engine and cannot be used", ... " with 'FitMethod' 'splines'.")); endif endif ## Assign original X and Y data to the RegressionGAM object this.X = X; this.Y = Y; ## An observation is dropped only when its response is missing. A row ## whose predictors hold missing values is kept and reported as used, ## while the fit below draws on the complete observations alone. RowsUsed = ! isnan (Y(:)); Yret = Y(RowsUsed); Xret = X(RowsUsed, :); this.X = Xret; this.Y = Yret; cobs = ! any (isnan (Xret), 2); Y = Yret(cobs); X = Xret(cobs, :); ## Check X and Y contain valid data if (! isnumeric (X) || ! all (isfinite (X(:)))) error ("RegressionGAM: invalid values in X."); endif if (! isnumeric (Y) || ! all (isfinite (Y(:)))) error ("RegressionGAM: invalid values in Y."); endif ## Assign the number of observations and their corresponding indices ## on the original data, which will be used for training the model, ## to the RegressionGAM object this.NumObservations = rows (this.X); ## RowsUsed is left empty when every observation was used, as in MATLAB if (all (RowsUsed)) this.RowsUsed = []; else this.RowsUsed = RowsUsed; endif ## Assign the number of original predictors to the RegressionGAM object this.NumPredictors = ndims_X; ## Generate default predictors and response variable names (if necessary) if (isempty (PredictorNames)) for i = 1:ndims_X PredictorNames {i} = strcat ("x", num2str (i)); endfor endif if (isempty (ResponseName)) ResponseName = 'Y'; endif ## Assign predictors and response variable names this.PredictorNames = PredictorNames; this.ResponseName = ResponseName; ## A scalar 'Knots', 'Order' or 'DoF' applies to every predictor, which ## the validation above accepts but nothing expanded, so the fit indexed ## past the end of a scalar for the second predictor onwards. if (isscalar (Knots)) Knots = repmat (Knots, 1, ndims_X); endif if (isscalar (Order)) Order = repmat (Order, 1, ndims_X); endif DoF = Knots + Order; ## Assign remaining optional parameters this.Formula = Formula; this.Interactions = Interactions; this.Knots = Knots; this.Order = Order; this.DoF = DoF; this.Tol = Tol; this.ResponseTransform = ResponseTransform; this.RTfun = RTfun; ## Bookkeeping MATLAB reports alongside the fit this.CategoricalPredictors = []; this.ExpandedPredictorNames = PredictorNames; this.W = ones (this.NumObservations, 1) / this.NumObservations; this.IsStandardDeviationFit = false; this.FitMethod = FitMethod; if (strcmp (FitMethod, 'boostedtrees')) ## The spline parameters describe a scheme that did not run, so they ## are left empty rather than reporting numbers nothing used. this.Knots = []; this.Order = []; this.DoF = []; this.Tol = []; this = this.fitBoosted (X, Y, Interactions, ... NumTreesPerPredictor, ... NumTreesPerInteraction, ... MaxNumSplitsPerPredictor, ... MaxNumSplitsPerInteraction, ... InitialLearnRateForPredictors, ... InitialLearnRateForInteractions, MaxPValue, ... Verbose, NumPrint); return; endif ## Fit the basic model Inter = mean (Y); [iter, param, res, RSS] = this.fitGAM (X, Y, Inter, Knots, Order); this.BaseModel.Intercept = Inter; this.BaseModel.Parameters = param; this.BaseModel.Iterations = iter; this.BaseModel.Residuals = res; this.BaseModel.RSS = RSS; this.Intercept = Inter; ## Handle interaction terms (if given) if (F_I > 0) this = this.fitModelwInt (X, Y, Inter, Knots, Order, DoF); endif ## The property MATLAB reports is the two-way terms the fitted model ## carries, as predictor index pairs, whatever form they were asked for ## in. The term matrix stays the complete record: it also holds the ## main effects a formula names and any term above two predictors, ## neither of which has a two-column form. this.Interactions = interactionPairs (this.IntMatrix); ## The spline scheme has no tree vocabulary to report, so its parameter ## struct describes itself instead. this.ModelParameters = struct ('Knots', this.Knots, ... 'Order', this.Order, ... 'DoF', this.DoF, ... 'Formula', this.Formula, ... 'Interactions', this.Interactions, ... 'Tol', this.Tol); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGAM} {@var{obj} =} addInteractions (@var{obj}, @var{interactions}) ## ## Add interaction terms to a fitted model. ## ## @code{@var{obj} = addInteractions (@var{obj}, @var{interactions})} fits ## the interaction terms named by @var{interactions} on top of the terms ## the model already carries and returns the updated model. The univariate ## fit is left alone, so @code{predict} with ## @qcode{'IncludeInteractions'} set @qcode{false} answers exactly as it ## answered before. ## ## @var{interactions} takes the forms the constructor's ## @qcode{'Interactions'} option takes: a nonnegative integer count of ## terms, a logical matrix with a column per predictor, or @qcode{'all'}. ## ## A model already carrying interaction terms is not extended, which is ## what MATLAB refuses too. A model fitted from a @qcode{'Formula'} names ## every term it has, interactions among them, and is refused for the same ## reason. ## ## Which terms a count selects is this implementation's own: they are ## taken in the order @code{nchoosek} lists the pairs, where MATLAB ranks ## them by how much each contributes. The constructor's option chooses ## the same way, so the two agree with each other. ## ## @seealso{fitrgam, RegressionGAM} ## @end deftypefn function this = addInteractions (this, interactions) if (nargin != 2) print_usage (); endif ## Which store already holds interaction terms depends on the engine. hasInt = ! isempty (this.IntMatrix); if (strcmp (this.FitMethod, 'boostedtrees') && ! isempty (this.TreeModel)) hasInt = ! isempty (this.TreeModel.Pairs); endif if (hasInt) error (strcat ("RegressionGAM.addInteractions: adding interaction", ... " terms to a model that already includes them is", ... " not supported.")); endif if (! ((isnumeric (interactions) && isscalar (interactions) && interactions == fix (interactions) && interactions >= 0) || islogical (interactions) || (ischar (interactions) && strcmpi (interactions, 'all')))) error (strcat ("RegressionGAM.addInteractions: invalid", ... " 'Interactions' parameter.")); endif ## Under the boosted-tree engine the interaction phase simply runs now, ## starting from the predictor phase the model already carries, so a ## model with interactions added is the model the constructor would ## have built had it been asked for them. if (strcmp (this.FitMethod, 'boostedtrees')) cobs = ! any (isnan (this.X), 2); Xfit = this.X(cobs, :); Yfit = this.Y(cobs); MP = this.ModelParameters; f = gamboostpredict (this.BinEdges, this.TreeModel.ShapeValues, ... Xfit, this.Intercept); res = Yfit - f; wanted = -1; pairs = zeros (0, 2); if (ischar (interactions)) wanted = Inf; elseif (isscalar (interactions) && ! isempty (interactions)) wanted = interactions; elseif (! isempty (interactions)) pairs = interactionPairs (logical (interactions)); endif if (wanted > 0 && columns (Xfit) > 1) S = gamboostpairs (Xfit, res); pval = 1 - fcdf (S.F, S.DF1, S.DF2); pval(S.DF1 <= 0) = 1; [pval, ord] = sort (pval); ranked = S.Pairs(ord, :); ranked = ranked(pval <= MP.MaxPValue, :); if (isfinite (wanted) && rows (ranked) > wanted) ranked = ranked(1:wanted, :); endif pairs = ranked; this.PairDetectionBinEdges = S.BinEdges(:); endif reason = this.ReasonForTermination; ntrees = this.NumTrainedTrees; if (! isempty (pairs)) I = gamboostinter (Xfit, Yfit, f, 2, pairs, ... MP.NumTreesPerInteraction, ... MP.InitialLearnRateForInteractions, ... MP.MaxNumSplitsPerInteraction); this.Intercept = this.Intercept + I.Intercept; this.PairDetectionBinEdges = I.PairBinEdges(:); this.TreeModel.PairValues = I.PairValues; this.TreeModel.PairIntercept = I.Intercept; reason.InteractionTrees = I.ReasonForTermination; ntrees.InteractionTrees = I.NumTrees; endif this.TreeModel.Pairs = pairs; if (isempty (pairs)) this.TreeModel.PairIntercept = 0; endif this.Interactions = pairs; this.ReasonForTermination = reason; this.NumTrainedTrees = ntrees; MP.Interactions = interactions; this.ModelParameters = MP; return; endif ## parseInteractions reads the specification from the property, which ## afterwards holds the pairs the fit settled on, exactly as the ## constructor leaves it. this.Interactions = interactions; this.IntMatrix = this.parseInteractions (); ## The fit sees the complete observations, prepared as the constructor ## prepares them. Knots, Order and DoF are held unexpanded on the ## object and are widened to the interaction columns by fitModelwInt. cobs = ! any (isnan (this.X), 2); Xfit = this.X(cobs, :); Yfit = this.Y(cobs); this = this.fitModelwInt (Xfit, Yfit, mean (Yfit), this.Knots, ... this.Order, this.DoF); this.Interactions = interactionPairs (this.IntMatrix); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGAM} {@var{yFit} =} predict (@var{obj}, @var{Xfit}) ## @deftypefnx {RegressionGAM} {@var{yFit} =} predict (@dots{}, @var{Name}, @var{Value}) ## @deftypefnx {RegressionGAM} {[@var{yFit}, @var{ySD}, @var{yInt}] =} predict (@dots{}) ## ## Predict new data points using generalized additive model regression ## object. ## ## @code{@var{yFit} = predict (@var{obj}, @var{Xfit}} returns a vector of ## predicted responses, @var{yFit}, for the predictor data in matrix ## @var{Xfit} based on the Generalized Additive Model in @var{obj}. ## @var{Xfit} must have the same number of features/variables as the ## training data in @var{obj}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{RegressionGAM} class object. ## @end itemize ## ## @code{[@var{yFit}, @var{ySD}, @var{yInt}] = predict (@var{obj}, ## @var{Xfit}} ## also returns the standard deviations, @var{ySD}, and prediction ## intervals, ## @var{yInt}, of the response variable @var{yFit}, evaluated at each ## observation in the predictor data @var{Xfit}. ## ## @code{@var{yFit} = predict (@dots{}, @var{Name}, @var{Value})} returns ## the ## aforementioned results with additional properties specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.28 0.7 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'alpha'} @tab significance level of the prediction ## intervals @var{yInt}, specified as scalar in range @qcode{[0,1]}. The ## default value is 0.05, which corresponds to 95% prediction intervals. ## ## @item @qcode{'includeinteractions'} @tab a boolean flag to include ## interactions to predict new values based on @var{Xfit}. By default, ## @qcode{'includeinteractions'} is @qcode{true} when the GAM model in ## @var{obj} ## contains a @qcode{obj.Formula} or @qcode{obj.Interactions} fields. ## Otherwise, is set to @qcode{false}. If set to @qcode{true} when no ## interactions are present in the trained model, it will result to an ## error. If set to ## @qcode{false} when using a model that includes interactions, the ## predictions ## will be made on the basic model without any interaction terms. This way ## you can make predictions from the same GAM model without having to ## retrain it. ## @end multitable ## ## @seealso{fitrgam, RegressionGAM} ## @end deftypefn function [yFit, ySD, yInt] = predict (this, Xfit, varargin) ## Check for sufficient input arguments if (nargin < 2) error ("RegressionGAM.predict: too few arguments."); endif ## Check for valid XC if (isempty (Xfit)) error ("RegressionGAM.predict: Xfit is empty."); elseif (columns (this.X) != columns (Xfit)) error (strcat ("@RegressionGAM/predict: Xfit must have the same", ... " number of features (columns) as in the GAM model.")); endif ## Clean Xfit data notnansf = ! logical (sum (isnan (Xfit), 2)); Xfit = Xfit(notnansf, :); ## Default values for Name-Value Pairs alpha = 0.05; ## Which store holds the interaction terms depends on the engine: the ## spline scheme keeps them as extra columns described by IntMatrix, ## the boosted-tree scheme as surfaces over predictor pairs. hasInt = ! isempty (this.IntMatrix); if (strcmp (this.FitMethod, 'boostedtrees') && ! isempty (this.TreeModel)) hasInt = ! isempty (this.TreeModel.Pairs); endif if (! hasInt) incInt = false; else incInt = true; endif ## Parse optional arguments while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'includeinteractions' tmpInt = varargin{2}; if (! islogical (tmpInt) || (tmpInt != 0 && tmpInt != 1)) error (strcat ("RegressionGAM.predict: includeinteractions", ... " must be a logical value.")); endif ## Check model for interactions if (tmpInt && ! hasInt) error (strcat ("RegressionGAM.predict: trained model", ... " does not include any interactions.")); endif incInt = tmpInt; case 'alpha' alpha = varargin{2}; if (! (isnumeric (alpha) && isscalar (alpha) && alpha > 0 && alpha < 1)) error (strcat ("RegressionGAM.predict: alpha must be a", ... " scalar value between 0 and 1.")); endif otherwise error (strcat ("RegressionGAM.predict: invalid NAME in", ... " optional pairs of arguments.")); endswitch varargin(1:2) = []; endwhile ## Choose whether interactions must be included. The reshaping is done ## by gamTerms rather than inline, because the training data has to be ## reshaped exactly the same way further down: a model built with ## interactions or with a formula has a term for every column of the ## matrix it was fitted on, and that is no longer the stored X. ## The boosted-tree engine keeps its fit as step functions over bins, so ## a term is a lookup rather than a spline evaluation and the whole ## prediction is one call. Everything after it is shared. if (strcmp (this.FitMethod, 'boostedtrees') && ! isempty (this.TreeModel)) ## Excluding the interactions means excluding the constant they ## handed the intercept as well. interc = this.Intercept; if (! incInt && isfield (this.TreeModel, 'PairIntercept')) interc = interc - this.TreeModel.PairIntercept; endif if (! incInt || isempty (this.TreeModel.Pairs)) yFit = gamboostpredict (this.BinEdges, ... this.TreeModel.ShapeValues, Xfit, ... interc); else yFit = gamboostpredict (this.BinEdges, ... this.TreeModel.ShapeValues, Xfit, ... interc, 0, ... this.PairDetectionBinEdges, ... this.TreeModel.PairValues, ... this.TreeModel.Pairs); endif yFit = this.RTfun (yFit); if (nargout > 1) error (strcat ("RegressionGAM.predict: a standard deviation is", ... " only available from a model fitted with", ... " 'FitMethod' 'splines'.")); endif return; endif if (incInt) Xfit = gamTerms (Xfit, this.IntMatrix, isempty (this.Formula)); ## Get parameters and intercept vectors from model with interactions params = this.ModelwInt.Parameters; Interc = this.ModelwInt.Intercept; ## Update length of DoF vector DoF = ones (1, columns (Xfit)) * this.DoF(1); else ## Get parameters and intercept vectors from base model params = this.BaseModel.Parameters; Interc = this.BaseModel.Intercept; ## Get DoF from model DoF = this.DoF; endif ## Predict values from testing data yFit = predict_val (params, Xfit, Interc); yFit = this.RTfun (yFit); ## Predict Standard Deviation and Intervals of estimated data if requested if (nargout > 1) ## Ensure that RowsUsed in the model are selected used = true (rows (this.X), 1); Y = this.Y(used); X = this.X(used, :); ## Reshape the training data as the prediction data was reshaped. It ## used to be passed as stored, so the terms and the columns disagreed ## whenever the model was built with interactions or with a formula: ## with more terms than columns the residuals came from a truncated ## model and ySD was several times too large, and with fewer terms than ## columns the call raised out of bound. if (incInt) X = gamTerms (X, this.IntMatrix, isempty (this.Formula)); endif ## Predict response from training predictor data with the trained model yrs = predict_val (params, X , Interc); yrs_fit = predict_val (params, Xfit, Interc); ## Get the residuals between predicted and actual response data rs = Y - yrs; var_rs = var (rs); t_mul = tinv (1 - alpha / 2, this.DoF); ySD = sqrt (var_rs) * ones (rows (yFit), 1); if (nargout > 2) moe = t_mul(1) * ySD; lower = this.RTfun (yrs_fit - moe); upper = this.RTfun (yrs_fit + moe); yInt = [lower, upper]; endif endif endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGAM} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {RegressionGAM} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Regression loss of a generalized additive model. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} returns the weighted ## mean squared error of the model on the rows of @var{X} against the true ## response @var{Y}. ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} accepts the ## following name-value pairs: ## ## @itemize ## @item ## @qcode{"LossFun"} selects the loss, either @qcode{"mse"}, the default, ## or a function handle taking the true response, the predicted response ## and the weights, and returning a numeric scalar. ## ## @item ## @qcode{"Weights"} holds one weight per row of @var{X}, normalised to ## sum to one before it is applied. ## @end itemize ## ## @seealso{RegressionGAM, fitrgam, predict} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("RegressionGAM.loss: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("RegressionGAM.loss: Name-Value arguments must", ... " be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, 'loss'); ## Defaults, then the optional pairs LossFun = 'mse'; args = varargin; keep = true (1, numel (args)); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("RegressionGAM.loss: parameter name must be a", ... " character vector.")); endif if (strcmpi (args{i}, 'lossfun')) LossFun = args{i+1}; if (! (is_function_handle (LossFun) || (ischar (LossFun) && isrow (LossFun)))) error (strcat ("RegressionGAM.loss: 'LossFun' must be a", ... " character vector or a function handle.")); endif if (ischar (LossFun) && ! strcmpi (LossFun, 'mse')) error ("RegressionGAM.loss: unsupported 'LossFun' value."); endif keep(i:i+1) = false; endif endfor W = getWeights_ (this, args(keep), rows (X), 'loss'); ## Weights are normalized to sum to one, as MATLAB does, so a loss is ## a weighted average rather than a weighted sum. W = W(:) / sum (W); yFit = predict (this, X); Y = Y(:); if (is_function_handle (LossFun)) L = LossFun (Y, yFit, W); if (! (isnumeric (L) && isscalar (L))) error (strcat ("RegressionGAM.loss: 'LossFun' must return a", ... " numeric scalar.")); endif else L = sum (W .* (Y - yFit) .^ 2); endif endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGAM} {@var{yFit} =} resubPredict (@var{obj}) ## ## Predict the training response with the model it was fitted on. ## ## @code{@var{yFit} = resubPredict (@var{obj})} is @code{predict} applied ## to the observations the model was fitted on. ## ## @seealso{RegressionGAM, fitrgam, predict} ## @end deftypefn function yFit = resubPredict (this) used = true (rows (this.X), 1); yFit = predict (this, this.X(used, :)); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGAM} {@var{L} =} resubLoss (@var{obj}) ## @deftypefnx {RegressionGAM} {@var{L} =} resubLoss (@dots{}, @var{name}, @var{value}) ## ## Regression loss of a generalized additive model on its training data. ## ## @code{@var{L} = resubLoss (@var{obj})} returns the weighted mean ## squared error of the model on the data it was fitted on. It accepts ## the same @qcode{Name-Value} pairs as @code{loss}. ## ## @seealso{RegressionGAM, fitrgam, loss} ## @end deftypefn function L = resubLoss (this, varargin) used = true (rows (this.X), 1); L = loss (this, this.X(used, :), this.Y(used), varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGAM} {@var{CVMdl} =} crossval (@var{obj}) ## @deftypefnx {RegressionGAM} {@var{CVMdl} =} crossval (@dots{}, @var{name}, @var{value}) ## ## Cross validate a Generalized Additive Model regression object. ## ## @code{@var{CVMdl} = crossval (@var{obj})} returns a cross-validated ## model object, @var{CVMdl}, from a trained model, @var{obj}, using ## 10-fold cross-validation by default. ## ## @code{@var{CVMdl} = crossval (@var{obj}, @var{name}, @var{value})} ## specifies additional name-value pair arguments to customize the ## cross-validation process. ## ## @multitable @columnfractions 0.28 0.7 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'KFold'} @tab Specify the number of folds to use in ## k-fold cross-validation. @code{"KFold", @var{k}}, where @var{k} is an ## integer greater than 1. ## ## @item @qcode{'Holdout'} @tab Specify the fraction of the data to ## hold out for testing. @code{"Holdout", @var{p}}, where @var{p} is a ## scalar in the range @math{(0,1)}. ## ## @item @qcode{'Leaveout'} @tab Specify whether to perform ## leave-one-out cross-validation. @code{"Leaveout", @var{Value}}, where ## @var{Value} is 'on' or 'off'. ## ## @item @qcode{'CVPartition'} @tab Specify a @qcode{cvpartition} ## object used for cross-validation. @code{"CVPartition", @var{cv}}, ## where @code{isa (@var{cv}, "cvpartition")} = 1. ## ## @end multitable ## ## @seealso{fitrgam, RegressionGAM, cvpartition, ## RegressionPartitionedModel} ## @end deftypefn function CVMdl = crossval (this, varargin) if (numel (varargin) == 1) error (strcat ("RegressionGAM.crossval: Name-Value", ... " arguments must be in pairs.")); elseif (numel (varargin) > 2) error (strcat ("RegressionGAM.crossval: specify only", ... " one of the optional Name-Value paired arguments.")); endif if (this.NumObservations < 10) numFolds = this.NumObservations; else numFolds = 10; endif Holdout = []; Leaveout = 'off'; CVPartition = []; while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'kfold' numFolds = varargin{2}; if (! (isnumeric (numFolds) && isscalar (numFolds) && (numFolds == fix (numFolds)) && numFolds > 1)) error (strcat ("RegressionGAM.crossval: 'KFold'", ... " must be an integer value greater than 1.")); endif case 'holdout' Holdout = varargin{2}; if (! (isnumeric (Holdout) && isscalar (Holdout) && Holdout > 0 && Holdout < 1)) error (strcat ("RegressionGAM.crossval: 'Holdout'", ... " must be a numeric value between 0 and 1.")); endif case 'leaveout' Leaveout = varargin{2}; if (! (ischar (Leaveout) && (strcmpi (Leaveout, 'on') || strcmpi (Leaveout, 'off')))) error (strcat ("RegressionGAM.crossval: 'Leaveout'", ... " must be either 'on' or 'off'.")); endif case 'cvpartition' CVPartition = varargin{2}; if (! (isa (CVPartition, 'cvpartition'))) error (strcat ("RegressionGAM.crossval: 'CVPartition'", ... " must be a 'cvpartition' object.")); endif otherwise error (strcat ("RegressionGAM.crossval: invalid", ... " parameter name in optional paired arguments.")); endswitch varargin(1:2) = []; endwhile ## Determine the cross-validation method to use. The partition is ## built over the observations actually trained on, so its indices and ## the partitioned model's rows are the same set. n = this.NumObservations; if (! isempty (CVPartition)) partition = CVPartition; elseif (! isempty (Holdout)) partition = cvpartition (n, 'Holdout', Holdout); elseif (strcmpi (Leaveout, 'on')) partition = cvpartition (n, 'LeaveOut'); else partition = cvpartition (n, 'KFold', numFolds); endif ## Create a cross-validated model object CVMdl = RegressionPartitionedModel (this, partition); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGAM} {@var{CMdl} =} compact (@var{obj}) ## ## Create a @qcode{CompactRegressionGAM} object. ## ## @code{@var{CMdl} = compact (@var{obj})} returns a compact version of ## the model, which predicts as it does but keeps no training data. ## ## @seealso{RegressionGAM, CompactRegressionGAM, fitrgam} ## @end deftypefn function CMdl = compact (this) CMdl = CompactRegressionGAM (this); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGAM} {} savemodel (@var{obj}, @var{filename}) ## ## Save a RegressionGAM object. ## ## @code{savemodel (@var{obj}, @var{filename})} saves a RegressionGAM ## object into a file defined by @var{filename}. ## ## @seealso{loadmodel, fitrgam, RegressionGAM} ## @end deftypefn function savemodel (obj, fname) if (nargin < 2) error ("RegressionGAM.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error ("RegressionGAM.savemodel: FNAME must be a character vector."); endif ## Generate variable for class name classdef_name = 'RegressionGAM'; ## Create variables from model properties X = obj.X; Y = obj.Y; NumObservations = obj.NumObservations; RowsUsed = obj.RowsUsed; BinEdges = obj.BinEdges; NumPredictors = obj.NumPredictors; PredictorNames = obj.PredictorNames; ResponseName = obj.ResponseName; Formula = obj.Formula; Interactions = obj.Interactions; Knots = obj.Knots; Order = obj.Order; DoF = obj.DoF; Tol = obj.Tol; BaseModel = obj.BaseModel; ModelwInt = obj.ModelwInt; IntMatrix = obj.IntMatrix; CategoricalPredictors = obj.CategoricalPredictors; ExpandedPredictorNames = obj.ExpandedPredictorNames; W = obj.W; ResponseTransform = obj.ResponseTransform; Intercept = obj.Intercept; IsStandardDeviationFit = obj.IsStandardDeviationFit; RTfun = obj.RTfun; FitMethod = obj.FitMethod; TreeModel = obj.TreeModel; PairDetectionBinEdges = obj.PairDetectionBinEdges; ModelParameters = obj.ModelParameters; ReasonForTermination = obj.ReasonForTermination; ## Save classdef name and all model properties as individual variables HyperparameterOptimizationResults = obj.HyperparameterOptimizationResults; save ('-binary', fname, 'classdef_name', 'X', 'Y', 'NumObservations', ... 'RowsUsed', 'BinEdges', 'NumPredictors', 'PredictorNames', ... 'ResponseName', ... 'Formula', 'Interactions', 'Knots', 'Order', 'DoF', 'Tol', ... 'BaseModel', 'ModelwInt', 'IntMatrix', 'CategoricalPredictors', ... 'ExpandedPredictorNames', 'W', 'ResponseTransform', ... 'Intercept', 'IsStandardDeviationFit', 'RTfun', ... 'FitMethod', 'TreeModel', 'PairDetectionBinEdges', ... 'ModelParameters', 'ReasonForTermination', ... 'HyperparameterOptimizationResults'); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGAM} {@var{Mdl} =} resume (@var{obj}, @var{numTrees}) ## ## Resume training a generalized additive model. ## ## @code{@var{Mdl} = resume (@var{obj}, @var{numTrees})} adds ## @var{numTrees} more trees to @var{obj} and returns the result. The ## original model is not modified. ## ## Training continues in the phase that ran last, which is what MATLAB ## does: a model carrying interaction terms gains interaction trees and ## its predictor shape functions are left alone, while a model without ## them gains predictor trees. A round starts at its initial learning ## rate whatever its number, so the model this returns is the model a ## single fit of the combined budget would have produced. ## ## @var{numTrees} must be a positive integer scalar. Resuming raises ## where there is nothing left to gain, rather than returning the model ## unchanged, and it is not available under ## @qcode{'FitMethod', 'splines'}: a backfit that has converged to its ## tolerance has no budget to extend. ## ## @seealso{RegressionGAM, fitrgam, addInteractions} ## @end deftypefn function Mdl = resume (this, numTrees) if (nargin < 2) error ("RegressionGAM.resume: Not enough input arguments."); endif if (! strcmp (this.FitMethod, 'boostedtrees')) error (strcat ("RegressionGAM.resume: resuming is available", ... " only under 'FitMethod', 'boostedtrees'; a spline", ... " backfit stops at its tolerance and has no budget", ... " to extend.")); endif if (! (isnumeric (numTrees) && isscalar (numTrees) && isreal (numTrees) && numTrees > 0 && fix (numTrees) == numTrees)) error (strcat ("RegressionGAM.resume: NUMTREES must be a", ... " positive integer scalar.")); endif ## The rows the fit saw. cobs = ! any (isnan (this.X), 2); X = this.X(cobs, :); Y = this.Y(cobs); Mdl = this; MP = this.ModelParameters; reason = this.ReasonForTermination; ntrees = this.NumTrainedTrees; if (isempty (this.TreeModel.Pairs)) ## No interaction phase ever ran, so the predictor phase is the one ## still open. The engine is handed the prediction reached so far and ## returns the increment to add to it. f = gamboostpredict (this.BinEdges, this.TreeModel.ShapeValues, X, ... this.Intercept); M = gamboosttrain (X, Y, 2, numTrees, ... MP.InitialLearnRateForPredictors, ... MP.MaxNumSplitsPerPredictor, 0, MP.NumPrint, f(:)); if (M.NumTrees == 0) error (strcat ("RegressionGAM.resume: unable to resume", ... " training because the software was unable to", ... " improve the model fit.")); endif sv = this.TreeModel.ShapeValues; for j = 1:numel (sv) sv{j} = sv{j} + M.ShapeValues{j}; endfor Mdl.TreeModel.ShapeValues = sv; Mdl.Intercept = this.Intercept + M.Intercept; MP.NumTreesPerPredictor = MP.NumTreesPerPredictor + M.NumTrees; reason.PredictorTrees = M.ReasonForTermination; ntrees.PredictorTrees = ntrees.PredictorTrees + M.NumTrees; else ## The interaction phase ran last, so it is the one extended. The ## running prediction includes the surfaces already fitted, and the ## new ones are added to them. f = gamboostpredict (this.BinEdges, this.TreeModel.ShapeValues, X, ... this.Intercept, 0, ... this.PairDetectionBinEdges, ... this.TreeModel.PairValues, ... this.TreeModel.Pairs); I = gamboostinter (X, Y, f(:), 2, this.TreeModel.Pairs, numTrees, ... MP.InitialLearnRateForInteractions, ... MP.MaxNumSplitsPerInteraction); if (I.NumTrees == 0) error (strcat ("RegressionGAM.resume: unable to resume", ... " training because the software was unable to", ... " improve the model fit.")); endif pv = this.TreeModel.PairValues; for k = 1:numel (pv) pv{k} = pv{k} + I.PairValues{k}; endfor Mdl.TreeModel.PairValues = pv; Mdl.TreeModel.PairIntercept = this.TreeModel.PairIntercept ... + I.Intercept; Mdl.Intercept = this.Intercept + I.Intercept; MP.NumTreesPerInteraction = MP.NumTreesPerInteraction + I.NumTrees; reason.InteractionTrees = I.ReasonForTermination; ntrees.InteractionTrees = ntrees.InteractionTrees + I.NumTrees; endif Mdl.ModelParameters = MP; Mdl.ReasonForTermination = reason; Mdl.NumTrainedTrees = ntrees; endfunction endmethods ## Helper functions methods(Access = private) ## Shared validation for the assessment methods, so each reports under ## its own name. function [X, Y] = checkXY_ (this, X, Y, caller) if (isempty (X)) error ("RegressionGAM.%s: X is empty.", caller); elseif (this.NumPredictors != columns (X)) error (strcat ("RegressionGAM.%s: X must have the same number of", ... " predictors as the trained model."), caller); endif if (isempty (Y)) error ("RegressionGAM.%s: Y is empty.", caller); elseif (rows (X) != rows (Y)) error (strcat ("RegressionGAM.%s: Y must have the same number of", ... " rows as X."), caller); endif endfunction ## Pull a "Weights" pair out of the optional arguments, defaulting to a ## uniform weight, and reject any other name. function W = getWeights_ (this, args, n, caller) W = ones (n, 1); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("RegressionGAM.%s: parameter name must be a", ... " character vector."), caller); endif if (strcmpi (args{i}, 'weights')) W = args{i+1}; if (! (isnumeric (W) && isvector (W))) error (strcat ("RegressionGAM.%s: 'Weights' must be a numeric", ... " vector."), caller); endif if (numel (W) != n) error (strcat ("RegressionGAM.%s: size of 'Weights' must equal", ... " the number of rows in X."), caller); endif else error (strcat ("RegressionGAM.%s: invalid parameter name in", ... " optional paired arguments."), caller); endif endfor endfunction ## Drive the boosted-tree engine: the predictor phase, then a search for ## interactions worth adding, then the interaction phase over whichever ## pairs survived. The two phases share a running fit, so the second ## continues from the prediction the first left rather than starting over. function this = fitBoosted (this, X, Y, Interactions, NTP, NTI, MSP, ... MSI, LRP, LRI, MaxPValue, Verb, NPrint) ## Method 2 boosts the squared error, which is what a regression fits. M = gamboosttrain (X, Y, 2, NTP, LRP, MSP, Verb, NPrint); f = gamboostpredict (M.BinEdges, M.ShapeValues, X, M.Intercept); this.BinEdges = M.BinEdges(:); ## a column cell, as MATLAB reports it this.Intercept = M.Intercept; reason = struct ('PredictorTrees', M.ReasonForTermination, ... 'InteractionTrees', ''); ntrees = struct ('PredictorTrees', M.NumTrees, ... 'InteractionTrees', 0); pairs = zeros (0, 2); pairValues = {}; pairShift = 0; wanted = -1; if (ischar (Interactions)) wanted = Inf; elseif (isscalar (Interactions) && ! isempty (Interactions)) wanted = Interactions; elseif (! isempty (Interactions)) pairs = interactionPairs (logical (Interactions)); endif if (wanted > 0 && columns (X) > 1) S = gamboostpairs (X, M.Residuals); ## The F ratio becomes a probability through the package's own fcdf, ## which is verified against MATLAB; the engine deliberately does not ## carry a second incomplete beta of its own. pval = 1 - fcdf (S.F, S.DF1, S.DF2); pval(S.DF1 <= 0) = 1; [pval, ord] = sort (pval); ranked = S.Pairs(ord, :); ranked = ranked(pval <= MaxPValue, :); if (isfinite (wanted) && rows (ranked) > wanted) ranked = ranked(1:wanted, :); endif pairs = ranked; this.PairDetectionBinEdges = S.BinEdges(:); if (isempty (pairs)) warning (strcat ("RegressionGAM: model does not include", ... " interaction terms because all interaction", ... " terms have p-values greater than the", ... " 'MaxPValue' value, or the software was unable", ... " to improve the model fit.")); endif endif if (! isempty (pairs)) I = gamboostinter (X, Y, f, 2, pairs, NTI, LRI, MSI); this.Intercept = this.Intercept + I.Intercept; pairShift = I.Intercept; this.PairDetectionBinEdges = I.PairBinEdges(:); pairValues = I.PairValues; reason.InteractionTrees = I.ReasonForTermination; ntrees.InteractionTrees = I.NumTrees; endif this.Interactions = pairs; this.ReasonForTermination = reason; this.NumTrainedTrees = ntrees; ## The constant the interaction surfaces gave up when they were ## recentred is kept apart from the predictor phase's intercept. The ## Intercept property still reports their sum, as MATLAB's does, but ## predicting without the interactions has to take this part back out ## or it would answer with a constant the main effects never earned. this.TreeModel = struct ('ShapeValues', {M.ShapeValues}, ... 'PairValues', {pairValues}, ... 'Pairs', pairs, ... 'PairIntercept', pairShift); if (ischar (Interactions)) request = Interactions; elseif (isempty (Interactions)) request = 0; else request = Interactions; endif this.ModelParameters = struct ( ... 'NumPrint', NPrint, ... 'MaxPValue', MaxPValue, ... 'InitialLearnRateForPredictors', LRP, ... 'InitialLearnRateForInteractions', LRI, ... 'NumTreesPerPredictor', NTP, ... 'NumTreesPerInteraction', NTI, ... 'MaxNumSplitsPerPredictor', MSP, ... 'MaxNumSplitsPerInteraction', MSI, ... 'VerbosityLevel', Verb, ... 'Interactions', request, ... 'Version', 1, ... 'Method', 'GAM', ... 'Type', 'regression'); endfunction ## Determine interactions from Interactions optional parameter ## Fit the model that carries the interaction terms. The constructor and ## addInteractions both arrive here with IntMatrix already decided and the ## predictors and response prepared as the fit wants them, so the two ## cannot drift: a model given its interactions after the fact is the ## model it would have been had they been asked for at the outset. function this = fitModelwInt (this, X, Y, Inter, Knots, Order, DoF) if (isempty (this.Formula)) ## Analyze Interactions optional parameter this.IntMatrix = this.parseInteractions (); ## Append interaction terms to the predictor matrix for i = 1:rows (this.IntMatrix) tindex = logical (this.IntMatrix(i,:)); Xterms = X(:,tindex); Xinter = ones (this.NumObservations, 1); for c = 1:sum (tindex) Xinter = Xinter .* Xterms(:,c); endfor ## Append interaction terms X = [X, Xinter]; endfor else ## Analyze Formula optional parameter this.IntMatrix = this.parseFormula (); ## Add selected predictors and interaction terms XN = []; for i = 1:rows (this.IntMatrix) tindex = logical (this.IntMatrix(i,:)); Xterms = X(:,tindex); Xinter = ones (this.NumObservations, 1); for c = 1:sum (tindex) Xinter = Xinter .* Xterms(:,c); endfor ## Append selected predictors and interaction terms XN = [XN, Xinter]; endfor X = XN; endif ## Update length of Knots, Order, and DoF vectors to match ## the columns of X with the interaction terms Knots = ones (1, columns (X)) * Knots(1); # Knots Order = ones (1, columns (X)) * Order(1); # Order of spline DoF = ones (1, columns (X)) * DoF(1); # Degrees of freedom ## Fit the model with interactions [iter, param, res, RSS] = this.fitGAM (X, Y, Inter, Knots, Order); this.ModelwInt.Intercept = Inter; this.ModelwInt.Parameters = param; this.ModelwInt.Iterations = iter; this.ModelwInt.Residuals = res; this.ModelwInt.RSS = RSS; endfunction function intMat = parseInteractions (this) if (islogical (this.Interactions)) ## Check that interaction matrix corresponds to predictors if (numel (this.PredictorNames) != columns (this.Interactions)) error (strcat ("RegressionGAM: columns in 'Interactions'", ... " matrix must equal to the number of predictors.")); endif intMat = this.Interactions; elseif (isnumeric (this.Interactions)) ## Need to measure the effect of all interactions to keep the best ## performing. Just check that the given number is not higher than ## p*(p-1)/2, where p is the number of predictors. p = this.NumPredictors; if (this.Interactions > p * (p - 1) / 2) error (strcat ("RegressionGAM: number of interaction terms", ... " requested is larger than all possible", ... " combinations of predictors in X.")); endif ## The pairs are not ranked by how much each contributes, so the ## first ones asked for are taken in the order nchoosek lists them. intMat = pairTerms (p)(1:this.Interactions, :); elseif (strcmpi (this.Interactions, 'all')) ## Calculate all p*(p-1)/2 interaction terms intMat = pairTerms (this.NumPredictors); endif endfunction ## Determine interactions from formula function intMat = parseFormula (this) intMat = []; ## Check formula for syntax if (isempty (strfind (this.Formula, '~'))) error ("RegressionGAM: invalid syntax in Formula."); endif ## Split formula and keep predictor terms formulaParts = strsplit (this.Formula, '~'); ## Check there is some string after '~' if (numel (formulaParts) < 2) error ("RegressionGAM: no predictor terms in Formula."); endif predictorString = strtrim (formulaParts{2}); if (isempty (predictorString)) error ("RegressionGAM: no predictor terms in Formula."); endif ## Split additive terms (between + sign) aterms = strtrim (strsplit (predictorString, '+')); ## Process all terms for i = 1:numel (aterms) ## Find individual terms (string missing ':') if (isempty (strfind (aterms(i), ':'){:})) ## Search PredictorNames to associate with column in X sterms = strcmp (this.PredictorNames, aterms(i)); ## Append to interactions matrix intMat = [intMat; sterms]; else ## Split interaction terms (string contains ':') mterms = strsplit (aterms{i}, ':'); ## Add each individual predictor to interaction term vector iterms = logical (zeros (1, this.NumPredictors)); for t = 1:numel (mterms) iterms = iterms | strcmp (this.PredictorNames, mterms(t)); endfor ## Check that all predictors have been identified if (sum (iterms) != t) error ("RegressionGAM: some predictors have not been identified."); endif ## Append to interactions matrix intMat = [intMat; iterms]; endif endfor ## Check that all terms have been identified if (! all (sum (intMat, 2) > 0)) error ("RegressionGAM: some terms have not been identified."); endif endfunction ## Fit the model function [iter, param, res, RSS] = fitGAM (this, X, Y, Inter, Knots, Order) ## The fit is performed by the shared spline engine, which builds and ## factorises each predictor's design once and reduces a backfitting ## cycle to two products against the factors per term. Mdl = gamtrain (X, Y, Knots, Order, 2, Inter, this.Tol, 1000); iter = Mdl.Iterations; param = Mdl.Parameters; res = Mdl.Residuals; RSS = Mdl.RSS; endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a RegressionGAM object mdl = RegressionGAM (1, 1); ## Get fieldnames from DATA (including private properties) names = fieldnames (data); ## The set methods for these read other properties, and one of them ## rebuilds Coeffs, so they are assigned once everything else is in ## place rather than in the order the file happens to list them. late = ismember (names, {'Cost', 'Prior', 'ScoreTransform', ... 'ResponseTransform'}); names = [names(! late); names(late)]; ## Copy data into object for i = 1:numel (names) ## Check that fieldnames in DATA match properties in RegressionGAM try mdl.(names{i}) = data.(names{i}); catch error ("RegressionGAM.load_model: invalid model in '%s'.", ... filename) end_try_catch endfor ## A model written before RowsUsed became a mask stored it as a ## double, which is a valid subscript for nothing. An empty RowsUsed ## means every observation was used and stays an empty double. if (! isempty (mdl.RowsUsed)) mdl.RowsUsed = logical (mdl.RowsUsed); endif endfunction endmethods endclassdef ## Reshape a predictor matrix into the terms a model was fitted on. With ## APPEND true the interaction columns are added to the predictors, which is ## what an 'Interactions' model was fitted on; with it false they replace them, ## which is what a 'Formula' model was fitted on. Every column of the result ## carries one term of the model, which is what predict_val walks. function XA = gamTerms (X, IntMatrix, append) if (append) XA = X; else XA = []; endif for i = 1:rows (IntMatrix) tindex = logical (IntMatrix(i,:)); Xterms = X(:,tindex); Xinter = ones (rows (X), 1); for c = 1:sum (tindex) Xinter = Xinter .* Xterms(:,c); endfor XA = [XA, Xinter]; endfor endfunction ## Helper function for making prediction of new data based on GAM model function ypred = predict_val (params, X, intercept) ## The shared prediction engine evaluates every additive term and adds the ## intercept. ypred = gampredict (params, X, intercept); endfunction %!demo %! ## Train a RegressionGAM Model for synthetic values %! rng (42); %! f1 = @(x) cos (3 * x); %! f2 = @(x) x .^ 3; %! x1 = 2 * rand (50, 1) - 1; %! x2 = 2 * rand (50, 1) - 1; %! y = f1(x1) + f2(x2); %! y = y + y .* 0.2 .* rand (50,1); %! X = [x1, x2]; %! a = fitrgam (X, y) %!demo %! ## Declare two different functions %! rng (42); %! f1 = @(x) cos (3 * x); %! f2 = @(x) x .^ 3; %! %! ## Generate 80 samples for f1 and f2 %! x = [-4*pi:0.1*pi:4*pi-0.1*pi]'; %! X1 = f1(x); %! X2 = f2(x); %! %! ## Create a synthetic response by adding noise %! Ytrue = X1 + X2; %! Y = Ytrue + Ytrue .* 0.2 .* rand (80,1); %! %! ## Assemble predictor data %! X = [X1, X2]; %! %! ## Train the GAM and test on the same data %! ## A standard deviation and a prediction interval come from the spline %! ## engine, which fits one; the boosted-tree engine reports none. %! a = fitrgam (X, Y, 'FitMethod', 'splines', 'order', [5, 5]); %! [ypred, ySDsd, yInt] = predict (a, X); %! %! ## Plot the results %! figure %! [sortedY, indY] = sort (Ytrue); %! plot (sortedY, 'r-'); %! xlim ([0, 80]); %! hold on %! plot (ypred(indY), 'g+') %! plot (yInt(indY,1), 'k:') %! plot (yInt(indY,2), 'k:') %! xlabel ('Predictor samples'); %! ylabel ('Response'); %! title ('actual vs predicted values for function f1(x) = cos (3x) '); %! legend ({'Theoretical Response', 'Predicted Response', 'Prediction Intervals'}); %! %! ## Use 30% Holdout partitioning for training and testing data %! C = cvpartition (80, 'HoldOut', 0.3); %! [ypred, ySDsd, yInt] = predict (a, X(test (C),:)); %! %! ## Plot the results %! figure %! [sortedY, indY] = sort (Ytrue(test (C))); %! plot (sortedY, 'r-'); %! xlim ([0, sum(test(C))]); %! hold on %! plot (ypred(indY), 'g+') %! plot (yInt(indY,1),'k:') %! plot (yInt(indY,2),'k:') %! xlabel ('Predictor samples'); %! ylabel ('Response'); %! title ('actual vs predicted values for function f1(x) = cos (3x) '); %! legend ({'Theoretical Response', 'Predicted Response', 'Prediction Intervals'}); ## Test constructor %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = [1; 2; 3; 4]; %! a = RegressionGAM (x, y, 'FitMethod', 'splines'); %! assert_equal ({a.X, a.Y}, {x, y}) %! assert_equal ({a.BaseModel.Intercept}, {2.5000}) %! assert_equal ({a.Knots, a.Order, a.DoF}, {[5, 5, 5], [3, 3, 3], [8, 8, 8]}) %! assert_equal ({a.NumObservations, a.NumPredictors}, {4, 3}) %! assert_equal ({a.ResponseName, a.PredictorNames}, {'Y', {'x1', 'x2', 'x3'}}) %! assert_equal ({a.Formula}, {[]}) %!test %! x = [1, 2, 3, 4; 4, 5, 6, 7; 7, 8, 9, 1; 3, 2, 1, 2]; %! y = [1; 2; 3; 4]; %! pnames = {'A', 'B', 'C', 'D'}; %! formula = 'Y ~ A + B + C + D + A:C'; %! intMat = logical ([1,0,0,0;0,1,0,0;0,0,1,0;0,0,0,1;1,0,1,0]); %! a = RegressionGAM (x, y, 'FitMethod', 'splines', ... %! 'predictors', pnames, 'formula', formula); %! assert_equal (a.IntMatrix, double (intMat)) %! assert_equal ({a.ResponseName, a.PredictorNames}, {'Y', pnames}) %! assert_equal (a.Formula, formula) %!test %! ## Test that predict() executes correctly when interactions are present %! X = [1, 2; 3, 4; 5, 6; 7, 8]; %! Y = [10; 20; 30; 40]; %! mdl = RegressionGAM (X, Y, 'FitMethod', 'splines', ... %! 'formula', 'Y ~ x1 + x2 + x1:x2'); %! ypred = predict (mdl, X); %! assert_equal (isnumeric (ypred), true); %! assert_equal (size (ypred), [4, 1]); %! [ypred2, ySD, yInt] = predict (mdl, X, 'includeinteractions', true); %! assert_equal (size (ypred2), [4, 1]); %! assert_equal (size (ySD), [4, 1]); %! assert_equal (size (yInt), [4, 2]); %! ## The three terms fit these four points exactly, so the residuals are zero %! ## and so is ySD. This block asserted the three sizes and nothing else, %! ## which is why it stayed green while ySD was computed from two of the %! ## three terms: a size is true whatever the number is. %! assert_equal (ypred2, [10; 20; 30; 40], 1e-10); %! assert_equal (ySD, zeros (4, 1), 1e-10); %!test %! ## Verify ySD is based on training residual variance %! X = (1:10)'; %! Y = [2; 1; 4; 3; 6; 5; 8; 7; 10; 9]; %! mdl = RegressionGAM (X, Y, 'FitMethod', 'splines'); %! y_train = predict (mdl, X); %! rs = Y - y_train; %! expected_ySD = sqrt (var (rs)); %! [~, ySD] = predict (mdl, X(1:4,:)); %! assert_equal (ySD, expected_ySD * ones (4, 1), 1e-10); %!test %! ## Verify ySD remains the same for one or more prediction points %! X = (1:10)'; %! Y = [2; 1; 4; 3; 6; 5; 8; 7; 10; 9]; %! mdl = RegressionGAM (X, Y, 'FitMethod', 'splines'); %! y_train = predict (mdl, X); %! expected_ySD = sqrt (var (Y - y_train)); %! [~, ySD_1] = predict (mdl, X(1,:)); %! [~, ySD_3] = predict (mdl, X(1:3,:)); %! assert_equal (ySD_1, expected_ySD, 1e-10); %! assert_equal (ySD_3, expected_ySD * ones (3, 1), 1e-10); ## Test input validation for constructor ## Interactions reports the two-way terms the fitted model carries, as ## predictor index pairs, matching what R2024a's fitrgam returns. %!test %! k = (1:60)'; %! X = [mod(k*7,11)-5, mod(k*3,11)-5, mod(k*5,11)-5]; %! y = X(:,1) .* X(:,2) + 0.5 * X(:,3); %! Mdl = fitrgam (X, y, "Interactions", "all"); %! assert_equal (Mdl.Interactions, [1, 2; 1, 3; 2, 3]); ## No interactions is an empty list of pairs, keeping its two columns. %!test %! k = (1:60)'; %! X = [mod(k*7,11)-5, mod(k*3,11)-5, mod(k*5,11)-5]; %! Mdl = fitrgam (X, X(:,1) + X(:,3)); %! assert_equal (size (Mdl.Interactions), [0, 2]); ## A formula's main effects are terms of the model but not interactions. %!test %! k = (1:60)'; %! X = [mod(k*7,11)-5, mod(k*3,11)-5, mod(k*5,11)-5]; %! y = X(:,1) .* X(:,2) + 0.5 * X(:,3); %! Mdl = fitrgam (X, y, "FitMethod", "splines", ... %! "Formula", "Y ~ x1 + x2 + x1:x2"); %! assert_equal (Mdl.Interactions, [1, 2]); %! assert_equal (compact (Mdl).Interactions, [1, 2]); ## addInteractions fits the interaction terms onto a model that already has ## its univariate ones. The result is the model that would have been fitted ## had the terms been asked for at the outset: both go through one private ## method, so the two cannot drift apart. %!test %! load fisheriris %! bai = ! strcmp (species, "setosa"); %! Xai = meas(bai,2:4); Yai = meas(bai,1); %! Aai = addInteractions (fitrgam (Xai, Yai), "all"); %! Bai = fitrgam (Xai, Yai, "Interactions", "all"); %! assert_equal (Aai.Interactions, Bai.Interactions); %! assert_equal (Aai.ModelwInt, Bai.ModelwInt); %! assert_equal (predict (Aai, Xai), predict (Bai, Xai)); ## The univariate fit is left alone, which is what MATLAB leaves alone too. %!test %! load fisheriris %! bai = ! strcmp (species, "setosa"); %! Xai = meas(bai,2:4); Yai = meas(bai,1); %! Cai = fitrgam (Xai, Yai); %! Aai = addInteractions (Cai, "all"); %! assert_equal (predict (Aai, Xai, "IncludeInteractions", false), ... %! predict (Cai, Xai)); ## A count and a logical matrix name terms as the constructor's option does. %!test %! load fisheriris %! bai = ! strcmp (species, "setosa"); %! Xai = meas(bai,2:4); Yai = meas(bai,1); %! Aai = addInteractions (fitrgam (Xai, Yai), 2); %! assert_equal (size (Aai.Interactions), [2, 2]); %! assert_equal (sort (Aai.Interactions, 2), Aai.Interactions); %! Lai = addInteractions (fitrgam (Xai, Yai), logical ([1 1 0; 0 1 1])); %! assert_equal (Lai.Interactions, [1, 2; 2, 3]); ## A model that already carries interaction terms is not extended, and a ## model fitted from a formula names every term it has, interactions among ## them, so it is refused for the same reason. R2024a refuses both. ## resume continues the phase that ran last, and a model with no interactions ## has only the predictor phase open. Resuming reproduces the model a single ## fit of the combined budget would have produced, which is the oracle every ## test here uses. %!test %! load fisheriris %! X = meas; %! Y = meas(:,1) + 0.3 * meas(:,3); %! A = fitrgam (X, Y, 'NumTreesPerPredictor', 5); %! B = resume (A, 10); %! C = fitrgam (X, Y, 'NumTreesPerPredictor', 15); %! assert_equal (B.ModelParameters.NumTreesPerPredictor, 15); %! assert_equal (predict (B, X), predict (C, X), 1e-10); %!test %! ## A model carrying interactions gains interaction trees, and its predictor %! ## shape functions are left where they were. %! load fisheriris %! X = meas; %! Y = meas(:,1) + 0.3 * meas(:,3); %! A = fitrgam (X, Y, 'NumTreesPerPredictor', 5, 'Interactions', 3, ... %! 'NumTreesPerInteraction', 4); %! B = resume (A, 10); %! assert_equal (B.ModelParameters.NumTreesPerPredictor, 5); %! assert_equal (B.ModelParameters.NumTreesPerInteraction, 14); %! assert_equal (B.TreeModel.ShapeValues, A.TreeModel.ShapeValues); %! C = fitrgam (X, Y, 'NumTreesPerPredictor', 5, 'Interactions', 3, ... %! 'NumTreesPerInteraction', 14); %! assert_equal (predict (B, X), predict (C, X), 1e-10); %!test %! ## The selected pairs survive, and resuming twice accumulates. %! load fisheriris %! X = meas; %! Y = meas(:,1) + 0.3 * meas(:,3); %! A = fitrgam (X, Y, 'NumTreesPerPredictor', 5, 'Interactions', 3, ... %! 'NumTreesPerInteraction', 4); %! B = resume (resume (A, 10), 6); %! assert_equal (B.Interactions, A.Interactions); %! assert_equal (B.ModelParameters.NumTreesPerInteraction, 20); %! assert_equal (B.ModelParameters.NumTreesPerPredictor, 5); %!test %! ## The model handed in is not modified. %! load fisheriris %! X = meas; %! Y = meas(:,1) + 0.3 * meas(:,3); %! A = fitrgam (X, Y, 'NumTreesPerPredictor', 5); %! B = resume (A, 10); %! assert_equal (A.ModelParameters.NumTreesPerPredictor, 5); %! assert_equal (B.ModelParameters.NumTreesPerPredictor, 15); %!error ... %! load fisheriris; ... %! resume (fitrgam (meas, meas(:,1), 'NumTreesPerPredictor', 5)) %!error ... %! load fisheriris; ... %! resume (fitrgam (meas, meas(:,1), 'FitMethod', 'splines'), 5) %!error ... %! load fisheriris; ... %! resume (fitrgam (meas, meas(:,1), 'NumTreesPerPredictor', 5), 0) %!error ... %! load fisheriris; ... %! resume (fitrgam (meas, meas(:,1), 'NumTreesPerPredictor', 5), 2.5) %!error ... %! load fisheriris; ... %! resume (fitrgam (meas, meas(:,1), 'NumTreesPerPredictor', 5), [1, 2]) %!error ... %! X = [ones(20,1)*[1, 2]; ones(20,1)*[3, 4]]; ... %! resume (fitrgam (X, ones (40, 1), 'NumTreesPerPredictor', 5), 5) %!error ... %! load fisheriris %! bai = ! strcmp (species, "setosa"); %! Mai = fitrgam (meas(bai,2:4), meas(bai,1), "Interactions", 2); %! addInteractions (Mai, "all") %!error ... %! load fisheriris %! bai = ! strcmp (species, "setosa"); %! addInteractions (fitrgam (meas(bai,2:4), meas(bai,1), ... %! "FitMethod", "splines", ... %! "Formula", "Y ~ x1 + x2 + x1:x2"), "all") %!error ... %! load fisheriris %! bai = ! strcmp (species, "setosa"); %! addInteractions (fitrgam (meas(bai,2:4), meas(bai,1)), {1}) %!error RegressionGAM () %!error RegressionGAM (ones (10,2)) %!error ... %! RegressionGAM (ones (10,2), ones (5,1)) %!error ... %! RegressionGAM ([1;2;3;'a';4], ones (5,1)) %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'some', 'some') %!error %! RegressionGAM (ones (10,2), ones (10,1), 'formula', {'y~x1+x2'}) %!error %! RegressionGAM (ones (10,2), ones (10,1), 'formula', [0, 1, 0]) %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'FitMethod', 'splines', ... %! 'formula', 'something') %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'FitMethod', 'splines', ... %! 'formula', 'something~') %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'FitMethod', 'splines', ... %! 'formula', 'something~') %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'FitMethod', 'splines', ... %! 'formula', 'something~x1:') %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'interactions', 'some') %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'interactions', -1) %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'interactions', [1 2 3 4]) %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'FitMethod', 'splines', ... %! 'interactions', 3) %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'formula', 'y ~ x1 + x2', 'interactions', 1) %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'interactions', 1, 'formula', 'y ~ x1 + x2') %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'knots', 'a') %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'order', 3, 'dof', 2, 'knots', 5) %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'dof', 'a') %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'knots', 5, 'order', 3, 'dof', 2) %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'order', 'a') %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'knots', 5, 'dof', 2, 'order', 2) %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'tol', -1) %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'responsename', -1) %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'predictors', -1) %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'predictors', ['a','b','c']) %!error ... %! RegressionGAM (ones (10,2), ones (10,1), 'predictors', {'a','b','c'}) ## Test input validation for predict method %!error ... %! predict (RegressionGAM (ones (10,1), ones (10,1))) %!error ... %! predict (RegressionGAM (ones (10,1), ones (10,1)), []) %!error ... %! predict (RegressionGAM (ones (10,2), ones (10,1)), 2) %!error ... %! predict (RegressionGAM (ones (10,2), ones (10,1)), ones (10,2), 'some', 'some') %!error ... %! predict (RegressionGAM (ones (10,2), ones (10,1)), ones (10,2), 'includeinteractions', 'some') %!error ... %! predict (RegressionGAM (ones (10,2), ones (10,1)), ones (10,2), 'includeinteractions', 5) %!error ... %! predict (RegressionGAM (ones (10,2), ones (10,1)), ones (10,2), 'alpha', 5) %!error ... %! predict (RegressionGAM (ones (10,2), ones (10,1)), ones (10,2), 'alpha', -1) %!error ... %! predict (RegressionGAM (ones (10,2), ones (10,1)), ones (10,2), 'alpha', 'a') %!error ... %! savemodel (RegressionGAM ([1, 2; 2, 3; 3, 4; 4, 5], [1; 2; 3; 4])) %!error ... %! savemodel (RegressionGAM ([1, 2; 2, 3; 3, 4; 4, 5], [1; 2; 3; 4]), 1) %!error ... %! savemodel (RegressionGAM ([1, 2; 2, 3; 3, 4; 4, 5], [1; 2; 3; 4]), ['ab'; 'cd']) ## The bookkeeping MATLAB reports alongside the fit is present. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,1:3), meas(:,4), 'FitMethod', 'splines'); %! assert_equal (Mdl.Intercept, Mdl.BaseModel.Intercept); %! assert_equal (size (Mdl.W), [Mdl.NumObservations, 1]); %! assert_equal (sum (Mdl.W), 1, 1e-12); %! assert_equal (Mdl.CategoricalPredictors, []); %! assert_equal (Mdl.ExpandedPredictorNames, Mdl.PredictorNames); %! assert_equal (Mdl.IsStandardDeviationFit, false); ## A scalar Knots, Order or DoF applies to every predictor. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,1:3), meas(:,4), 'FitMethod', 'splines', ... %! 'Knots', 4); %! assert_equal (Mdl.Knots, [4, 4, 4]); %! assert_equal (Mdl.Order, [3, 3, 3]); %! assert_equal (Mdl.DoF, [7, 7, 7]); ## A single non-finite value in X or Y is refused. %!error ... %! RegressionGAM ([1, 2; Inf, 4; 5, 6; 7, 8], [1; 2; 3; 4]) %!error ... %! RegressionGAM ([1, 2; 3, 4; 5, 6; 7, 8], [1; 2; Inf; 4]) ## 'Interactions' names every pairwise term, one row per term. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,1:3), meas(:,4), 'FitMethod', 'splines', ... %! 'Interactions', 'all'); %! assert_equal (Mdl.IntMatrix, logical ([1, 1, 0; 1, 0, 1; 0, 1, 1])); %! assert_equal (sum (Mdl.IntMatrix(:)), 6); ## An assigned ResponseTransform reaches the predicted response. %!test %! load fisheriris %! X = meas(:,1:3); %! Mdl = fitrgam (X, meas(:,4)); %! y0 = predict (Mdl, X); %! Mdl.ResponseTransform = 'exp'; %! assert_equal (predict (Mdl, X), exp (y0), 1e-12); ## resubPredict and resubLoss are predict and loss on the training data. %!test %! load fisheriris %! X = meas(:,1:3); %! Y = meas(:,4); %! Mdl = fitrgam (X, Y); %! assert_equal (resubPredict (Mdl), predict (Mdl, X)); %! assert_equal (resubLoss (Mdl), loss (Mdl, X, Y)); ## loss is the weighted mean squared error, and takes a function of its own. %!test %! load fisheriris %! X = meas(:,1:3); %! Y = meas(:,4); %! Mdl = fitrgam (X, Y); %! r = Y - predict (Mdl, X); %! assert_equal (loss (Mdl, X, Y), mean (r .^ 2), 1e-12); %! w = [ones(75, 1); 3 * ones(75, 1)]; %! assert_equal (loss (Mdl, X, Y, 'Weights', w), ... %! sum (w .* r .^ 2) / sum (w), 1e-12); %! mae = @(y, yfit, wt) sum (wt .* abs (y - yfit)); %! assert_equal (loss (Mdl, X, Y, 'LossFun', mae), mean (abs (r)), 1e-12); ## A saved and reloaded model carries every property and predicts alike. %!test %! load fisheriris %! X = meas(:,1:3); %! Mdl = fitrgam (X, meas(:,4), 'Interactions', 'all'); %! Mdl.ResponseTransform = 'exp'; %! fname = tempname (); %! savemodel (Mdl, fname); %! Mdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (Mdl2), 'RegressionGAM'); %! assert_equal (Mdl2.Intercept, Mdl.Intercept); %! assert_equal (Mdl2.W, Mdl.W); %! assert_equal (Mdl2.IntMatrix, Mdl.IntMatrix); %! assert_equal (predict (Mdl2, X), predict (Mdl, X)); ## Test input validation for loss method %!shared xr, yr, Mr %! load fisheriris %! xr = meas(:,1:3); %! yr = meas(:,4); %! Mr = fitrgam (xr, yr); %!error ... %! loss (Mr, xr) %!error ... %! loss (Mr, xr, yr, 'Weights') %!error ... %! loss (Mr, [], yr) %!error ... %! loss (Mr, 1, yr) %!error ... %! loss (Mr, xr, yr(1:10)) %!error ... %! loss (Mr, xr, yr, 'LossFun', 'mad') %!error ... %! Mr.ResponseTransform = 'nonsense'; ## RowsUsed is empty when every observation was used. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Mdl = fitrgam (X, Y); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (class (Mdl.RowsUsed), 'double'); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (rows (Mdl.X), 150); %! assert_equal (rows (Mdl.W), 150); ## A missing response drops its observation and RowsUsed marks it. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Y(5) = NaN; %! Mdl = fitrgam (X, Y); %! assert_equal (class (Mdl.RowsUsed), 'logical'); %! assert_equal (size (Mdl.RowsUsed), [150, 1]); %! assert_equal (sum (Mdl.RowsUsed), 149); %! assert_equal (Mdl.RowsUsed(5), false); %! assert_equal (Mdl.NumObservations, 149); %! assert_equal (rows (Mdl.X), 149); %! assert_equal (rows (Mdl.W), 149); ## A missing predictor keeps its observation, so RowsUsed stays empty. %!test %! load fisheriris %! X = meas(:,2:4); %! X(3,2) = NaN; %! Y = meas(:,1); %! Mdl = fitrgam (X, Y); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (rows (Mdl.X), 150); %! assert_equal (sum (isnan (Mdl.X(:))), 1); ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Mdl = fitrgam (X, Y, 'FitMethod', 'splines'); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'RegressionGAM'); %! assert_equal (M2.NumObservations, Mdl.NumObservations); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ResponseTransform), class (Mdl.ResponseTransform)); %! assert_equal (M2.BaseModel.Parameters(1).coefs, ... %! Mdl.BaseModel.Parameters(1).coefs); %! assert_equal (predict (M2, X(1:5,:)), predict (Mdl, X(1:5,:)), 1e-12); ## The same round trip under the boosted-tree engine. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Mdl = fitrgam (X, Y, 'FitMethod', 'boostedtrees'); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.FitMethod, 'boostedtrees'); %! assert_equal (M2.TreeModel.ShapeValues, Mdl.TreeModel.ShapeValues); %! assert_equal (M2.BinEdges, Mdl.BinEdges); %! assert_equal (predict (M2, X(1:5,:)), predict (Mdl, X(1:5,:)), 1e-12); ## crossval refits one compact model per fold over the observations used. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,1:3), meas(:,4)); %! CVMdl = crossval (Mdl, 'KFold', 3); %! assert_equal (class (CVMdl), 'RegressionPartitionedModel'); %! assert_equal (CVMdl.CrossValidatedModel, 'GAM'); %! assert_equal (class (CVMdl.Trained{1}), 'CompactRegressionGAM'); %! assert_equal (CVMdl.KFold, 3); %! assert_equal (numel (CVMdl.Trained), 3); %! assert_equal (CVMdl.NumObservations, 150); %!test %! load fisheriris %! CVMdl = crossval (fitrgam (meas(1:20,1:3), meas(1:20,4))); %! assert_equal (CVMdl.KFold, 10); %!test %! load fisheriris %! CVMdl = crossval (fitrgam (meas(1:20,1:3), meas(1:20,4)), 'Holdout', 0.25); %! assert_equal (CVMdl.KFold, 1); %!test %! load fisheriris %! CVMdl = crossval (fitrgam (meas(1:12,1:3), meas(1:12,4)), 'Leaveout', 'on'); %! assert_equal (CVMdl.KFold, 12); %!test %! load fisheriris %! cvp = cvpartition (20, 'KFold', 4); %! Mdl = fitrgam (meas(1:20,1:3), meas(1:20,4)); %! assert_equal (crossval (Mdl, 'CVPartition', cvp).KFold, 4); ## The fold models are refitted with the spline parameterisation of the model ## they came from. The compact fold does not report the parameters themselves. %!test %! load fisheriris %! Mdl = fitrgam (meas(1:20,1:3), meas(1:20,4), 'FitMethod', 'splines', ... %! 'Knots', 6, 'Order', 3); %! CVMdl = crossval (Mdl, 'KFold', 3); %! assert_equal (CVMdl.Trained{1}.FitMethod, 'splines'); %! assert_equal (isfield (CVMdl.Trained{1}.BaseModel, 'Intercept'), true); ## Held-out error exceeds the resubstitution error of the same fit. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,1:3), meas(:,4)); %! assert (kfoldLoss (crossval (Mdl, 'KFold', 5)) > resubLoss (Mdl)); %!shared cvobj %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1; 4, 5, 6]; %! y = [1; 2; 3; 4; 5]; %! cvobj = fitrgam (x, y); %!error ... %! crossval (cvobj, 'kfold') %!error ... %! crossval (cvobj, 'kfold', 3, 'holdout', 0.2) %!error ... %! crossval (cvobj, 'kfold', 'a') %!error ... %! crossval (cvobj, 'holdout', 2) %!error ... %! crossval (cvobj, 'leaveout', 1) %!error ... %! crossval (cvobj, 'cvpartition', 1) %!error ... %! crossval (cvobj, 'bogus', 1) ## BinEdges is an empty cell, as MATLAB reports for every learner that ## does no binning. MATLAB's own generalized additive model fills it, ## being boosted trees where this one is splines. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,1:3), meas(:,4)); %! assert_equal (class (Mdl.BinEdges), 'cell'); %! assert_equal (numel (Mdl.BinEdges), 3); %! Msp = fitrgam (meas(:,1:3), meas(:,4), 'FitMethod', 'splines'); %! assert_equal (Msp.BinEdges, {}); ## ySD is computed from the model's own terms, not from the stored predictors. ## The two disagree whenever the model was reshaped, and both directions were ## wrong: an 'Interactions' model has more terms than X has columns and the ## residuals came from a truncated model, and a 'Formula' model can have fewer ## and the call raised. %!test %! load fisheriris %! X = meas(:,1:3); %! Y = meas(:,4); %! Mdl = fitrgam (X, Y, 'FitMethod', 'splines', 'Interactions', 'all'); %! [~, ySD] = predict (Mdl, X(1:4,:)); %! ## Six terms against three stored columns. Truncating to three gave %! ## 0.9379137529, close to seven times the answer. %! assert_equal (ySD, 0.136050646147 * ones (4, 1), 1e-9); ## The same number, derived rather than pinned: ySD is the residual standard ## deviation of the full model over the training data. %!test %! load fisheriris %! X = meas(:,1:3); %! Y = meas(:,4); %! Mdl = fitrgam (X, Y, 'FitMethod', 'splines', 'Interactions', 'all'); %! Xa = X; %! for i = 1:rows (Mdl.IntMatrix) %! t = logical (Mdl.IntMatrix(i,:)); %! Xt = X(:,t); %! Xi = ones (rows (X), 1); %! for c = 1:sum (t) %! Xi = Xi .* Xt(:,c); %! endfor %! Xa = [Xa, Xi]; %! endfor %! yr = ones (rows (Xa), 1) * Mdl.ModelwInt.Intercept; %! for j = 1:columns (Xa) %! yr = yr + ppval (Mdl.ModelwInt.Parameters(j), Xa(:,j)); %! endfor %! [~, ySD] = predict (Mdl, X(1:4,:)); %! assert_equal (ySD, sqrt (var (Y - yr)) * ones (4, 1), 1e-12); ## A formula that selects fewer terms than there are predictors. Asking this ## model for a standard deviation used to raise out of bound, so the second ## and third outputs of predict were unreachable on any formula-built model of ## this shape. %!test %! load fisheriris %! X = meas(:,1:3); %! Y = meas(:,4); %! Mdl = fitrgam (X, Y, 'FitMethod', 'splines', 'Formula', 'Y ~ x1 + x2'); %! assert_equal (numel (Mdl.ModelwInt.Parameters), 2); %! assert_equal (columns (Mdl.X), 3); %! [~, ySD, yInt] = predict (Mdl, X(1:4,:)); %! assert_equal (ySD, 0.348625969903 * ones (4, 1), 1e-9); %! assert_equal (size (yInt), [4, 2]); ## yFit never had this defect and must not acquire one: the prediction matrix ## was always reshaped, and still is. %!test %! load fisheriris %! X = meas(:,1:3); %! Y = meas(:,4); %! Mdl = fitrgam (X, Y, 'FitMethod', 'splines', 'Interactions', 'all'); %! yA = predict (Mdl, X(1:3,:)); %! [yB, ~] = predict (Mdl, X(1:3,:)); %! assert_equal (yA, yB); %! assert_equal (yA, [0.255203457138; 0.210404303824; 0.162599243283], 1e-9); ## The boosted-tree engine is reachable by name while the default is still the ## spline engine, and it reports the surface MATLAB reports. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,2:4), meas(:,1), 'FitMethod', 'boostedtrees'); %! assert_equal (Mdl.FitMethod, 'boostedtrees'); %! assert_equal (numel (Mdl.BinEdges), 3); %! assert_equal (numel (fieldnames (Mdl.ModelParameters)), 13); %! assert_equal (Mdl.ModelParameters.Type, 'regression'); ## A regression intercept is the response mean and boosting leaves it there, ## where a classifier's is fitted and moves. The asymmetry is real and no ## MATLAB-facing property reports it, so it is pinned here. %!test %! load fisheriris %! y = meas(:,1); %! Mdl = fitrgam (meas(:,2:4), y, 'FitMethod', 'boostedtrees'); %! assert_equal (Mdl.Intercept, mean (y), 1e-12); ## Interactions are detected and held on their own coarse grid. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,2:4), meas(:,1), 'FitMethod', 'boostedtrees', ... %! 'Interactions', 'all'); %! assert_equal (rows (Mdl.Interactions), 3); %! assert_equal (numel (Mdl.PairDetectionBinEdges{1}), 7); ## A tree-fitted model predicts, and compact and loadmodel predict alike. %!test %! load fisheriris %! X = meas(:,2:4); %! Mdl = fitrgam (X, meas(:,1), 'FitMethod', 'boostedtrees'); %! CMdl = compact (Mdl); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (predict (M2, X), predict (Mdl, X)); ## The spline engine is unchanged and still reachable by name. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,2:4), meas(:,1), 'FitMethod', 'splines'); %! assert_equal (Mdl.FitMethod, 'splines'); %! assert_equal (Mdl.BinEdges, {}); %! assert_equal (Mdl.Knots, [5, 5, 5]); ## A standard deviation is a spline-engine capability: the boosted-tree engine ## fits no second model for it and says so rather than returning a wrong one. %!error ... %! load fisheriris %! Mdl = fitrgam (meas(:,2:4), meas(:,1), 'FitMethod', 'boostedtrees'); %! [y, ySD] = predict (Mdl, meas(1:4,2:4)); ## An argument belonging to the other engine is refused, not ignored. %!error ... %! fitrgam ([1;2;3;4], [1;2;3;4], 'FitMethod', 'nonsense') %!error ... %! fitrgam ([1;2;3;4], [1;2;3;4], 'FitMethod', 'boostedtrees', 'Knots', 4) %!error ... %! fitrgam ([1;2;3;4], [1;2;3;4], 'FitMethod', 'splines', 'MaxPValue', 0.5) %!error ... %! fitrgam ([1;2;3;4], [1;2;3;4], 'FitMethod', 'boostedtrees', ... %! 'NumTreesPerPredictor', 0) %!error ... %! fitrgam ([1;2;3;4], [1;2;3;4], 'FitMethod', 'boostedtrees', ... %! 'NumTreesPerInteraction', 1.5) %!error ... %! fitrgam ([1;2;3;4], [1;2;3;4], 'FitMethod', 'boostedtrees', ... %! 'MaxNumSplitsPerPredictor', -1) %!error ... %! fitrgam ([1;2;3;4], [1;2;3;4], 'FitMethod', 'boostedtrees', ... %! 'MaxNumSplitsPerInteraction', 'a') %!error ... %! fitrgam ([1;2;3;4], [1;2;3;4], 'FitMethod', 'boostedtrees', ... %! 'InitialLearnRateForPredictors', 0) %!error ... %! fitrgam ([1;2;3;4], [1;2;3;4], 'FitMethod', 'boostedtrees', ... %! 'InitialLearnRateForInteractions', 2) %!error ... %! fitrgam ([1;2;3;4], [1;2;3;4], 'FitMethod', 'boostedtrees', 'Verbose', -1) %!error ... %! fitrgam ([1;2;3;4], [1;2;3;4], 'FitMethod', 'boostedtrees', 'NumPrint', 0) %!error ... %! fitrgam ([1;2;3;4], [1;2;3;4], 'FitMethod', 'boostedtrees', 'MaxPValue', 2) ## HyperparameterOptimizationResults is declared for MATLAB compatibility and ## stays empty, this class running no search over its hyperparameters. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,1:3), meas(:,4)); %! assert_equal (isempty (Mdl.HyperparameterOptimizationResults), true); ## Every documented response transform reaches the response that is reported. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,2:4), meas(:,1)); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! T = {'identity', @(x) x; 'exp', @(x) exp (x); 'log', @(x) log (x)}; %! for i = 1:rows (T) %! Mdl.ResponseTransform = T{i,1}; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, T{i,2}(raw), 1e-12); %! endfor ## A function handle is taken as given and applied to the response. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,2:4), meas(:,1)); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! Mdl.ResponseTransform = @(x) x .^ 2; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, raw .^ 2, 1e-12); statistics-release-1.9.2/inst/Supervised_Learning/RegressionGP.m000066400000000000000000002477511524624707500250410ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} RegressionGP (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{obj} =} RegressionGP (@dots{}, @var{name}, @var{value}) ## ## Create a @qcode{RegressionGP} object containing a Gaussian process ## regression model. ## ## @code{@var{obj} = RegressionGP (@var{X}, @var{Y})} returns a Gaussian ## process regression model, @var{obj}, with @var{X} being the predictor data ## and @var{Y} the continuous response of the observations in @var{X}. ## ## @itemize ## @item ## @var{X} must be an @math{NxP} numeric matrix of predictor data, where rows ## correspond to observations and columns to features. ## @item ## @var{Y} must be an @math{Nx1} numeric vector holding the response of the ## corresponding predictor data in @var{X}. @var{Y} must have the same number ## of rows as @var{X}. ## @end itemize ## ## A Gaussian process places a prior over functions, given by the covariance ## function, and conditions it on the observations. The response is modelled ## as @math{H*Beta} plus a draw from that process plus independent noise of ## standard deviation @qcode{Sigma}, where @math{H} is the explicit basis. The ## covariance parameters and @qcode{Sigma} are estimated by maximizing the log ## marginal likelihood, and @qcode{Beta} follows from them in closed form as ## the generalized least squares estimate. ## ## @code{@var{obj} = RegressionGP (@dots{}, @var{name}, @var{value})} returns a ## model with additional options specified by @qcode{Name-Value} pair ## arguments listed below. ## ## @multitable @columnfractions 0.32 0.68 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'KernelFunction'} @tab A character vector naming the covariance ## function, or a function handle taking two matrices of points and a parameter ## vector. The default is @qcode{'squaredexponential'}. The supported names ## are listed below. ## ## @item @qcode{'KernelParameters'} @tab A numeric vector of initial values for ## the covariance parameters. Its length depends on the covariance function. ## These are starting values for the optimization, not fixed values. ## ## @item @qcode{'BasisFunction'} @tab A character vector naming the explicit ## basis, one of @qcode{'none'}, @qcode{'constant'}, @qcode{'linear'} or ## @qcode{'pureQuadratic'}, or a function handle taking @var{X} and returning ## the basis matrix. The default is @qcode{'constant'}. ## ## @item @qcode{'Beta'} @tab A numeric vector of basis coefficients. These are ## used as known values only when @qcode{'FitMethod'} is @qcode{'none'}. ## ## @item @qcode{'Sigma'} @tab A positive scalar, the initial value of the noise ## standard deviation. The default is @code{std (@var{Y}) / sqrt (2)}. ## ## @item @qcode{'ConstantSigma'} @tab A logical scalar. When @qcode{true} the ## noise standard deviation is held at its initial value instead of being ## estimated. The default is @qcode{false}. ## ## @item @qcode{'SigmaLowerBound'} @tab A positive scalar bounding the noise ## standard deviation from below. The default is ## @code{1e-2 * std (@var{Y})}. ## ## @item @qcode{'FitMethod'} @tab A character vector, either @qcode{'exact'} to ## estimate the parameters or @qcode{'none'} to keep them at their initial ## values. The default is @qcode{'exact'}. ## ## @item @qcode{'PredictMethod'} @tab A character vector. Only @qcode{'exact'} ## is implemented, which is also the only method under which a standard ## deviation and a prediction interval are available. ## ## @item @qcode{'Optimizer'} @tab A character vector naming the optimizer used ## to maximize the log marginal likelihood. @qcode{'quasinewton'} and ## @qcode{'fminunc'} name the same dense solver and are the default, ## @qcode{'lbfgs'} selects limited-memory BFGS, which holds a fixed number of ## curvature pairs rather than a full inverse Hessian and is the cheaper ## choice when the kernel carries many parameters, and @qcode{'fminsearch'} ## is derivative-free. ## ## @item @qcode{'Standardize'} @tab A logical scalar specifying whether the ## predictor data should be centred and scaled before training. The same ## transformation is applied by @code{predict}. The default is @qcode{false}. ## ## @item @qcode{'Weights'} @tab An @math{Nx1} numeric vector of non-negative ## observation weights. The default is a vector of ones. ## ## @item @qcode{'PredictorNames'} @tab A cell array of character vectors ## naming the predictors, in the order they appear in @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector naming the response. ## The default is @qcode{'Y'}. ## ## @item @qcode{'ResponseTransform'} @tab A character vector or a function ## handle applied to the response the model predicts. The default is ## @qcode{'none'}. ## @end multitable ## ## The supported values for @qcode{'KernelFunction'} are: ## ## @multitable @columnfractions 0.4 0.6 ## @headitem @var{Value} @tab @var{Parameters} ## @item @qcode{'exponential'} @tab @qcode{[SigmaL; SigmaF]} ## @item @qcode{'squaredexponential'} @tab @qcode{[SigmaL; SigmaF]} ## @item @qcode{'matern32'} @tab @qcode{[SigmaL; SigmaF]} ## @item @qcode{'matern52'} @tab @qcode{[SigmaL; SigmaF]} ## @item @qcode{'rationalquadratic'} @tab @qcode{[SigmaL; AlphaRQ; SigmaF]} ## @item @qcode{'ardexponential'} @tab @qcode{[LengthScale1; @dots{}; SigmaF]} ## @item @qcode{'ardsquaredexponential'} @tab ## @qcode{[LengthScale1; @dots{}; SigmaF]} ## @item @qcode{'ardmatern32'} @tab @qcode{[LengthScale1; @dots{}; SigmaF]} ## @item @qcode{'ardmatern52'} @tab @qcode{[LengthScale1; @dots{}; SigmaF]} ## @item @qcode{'ardrationalquadratic'} @tab ## @qcode{[LengthScale1; @dots{}; AlphaRQ; SigmaF]} ## @end multitable ## ## The automatic relevance determination kernels carry one length scale per ## predictor, so a predictor the response does not depend on is given a large ## length scale and stops contributing. ## ## The supported values for @qcode{'ResponseTransform'} are: ## ## @multitable @columnfractions 0.3 0.7 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'exp'} @tab @math{exp (x)} ## @item @qcode{'log'} @tab @math{log (x)} ## @end multitable ## ## Two deviations from MATLAB are deliberate and documented. The distance ## between points is accumulated one predictor at a time instead of by the ## expanded form MATLAB uses by default, because the expanded form does not ## return exactly zero for a point against itself and the rough kernels ## amplify that residue through their square root. The approximate fitting ## and prediction methods, @qcode{'sd'}, @qcode{'sr'}, @qcode{'fic'} and ## @qcode{'bcd'}, together with the active set options that serve them, are ## not implemented and are refused rather than silently ignored. ## ## @seealso{fitrgp, CompactRegressionGP, RegressionSVM, RegressionGAM} ## @end deftypefn classdef RegressionGP properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {RegressionGP} {property} X ## ## Predictor data ## ## An @math{NxP} numeric matrix, as it was supplied to the constructor. ## This property is read-only. ## ## @end deftp X = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} Y ## ## Response data ## ## An @math{Nx1} numeric vector, as it was supplied to the constructor. ## This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} NumObservations ## ## Number of observations used to train the model ## ## A positive integer scalar, counting only the rows that survived the ## removal of missing values. This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} RowsUsed ## ## Rows of the original data used to train the model ## ## A logical vector with one element per row of the data as supplied, true ## where the row was used. It is empty when no row was dropped. This ## property is read-only. ## ## @end deftp RowsUsed = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} W ## ## Observation weights ## ## An @math{Nx1} numeric vector, one weight per observation used to train ## the model. This property is read-only. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} PredictorNames ## ## Predictor variable names ## ## A cell array of character vectors, one per column of @qcode{X}. This ## property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} ExpandedPredictorNames ## ## Expanded predictor variable names ## ## A cell array of character vectors. It differs from ## @qcode{PredictorNames} only where a categorical predictor has been ## expanded into indicator variables. This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} ResponseName ## ## Response variable name ## ## A character vector. This property is read-only. ## ## @end deftp ResponseName = 'Y'; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A vector of positive integers indexing the columns of @qcode{X} that ## hold categorical predictors, or empty when none does. This property is ## read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} BinEdges ## ## Bin edges of the predictors ## ## A cell array with one entry per predictor, holding that predictor's bin ## edges where the model discretized it before fitting. It is empty here ## and stays empty: a Gaussian process takes its predictors as they are. ## ## This property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} FitMethod ## ## Method used to estimate the parameters ## ## @qcode{'Exact'} when the covariance parameters and the noise were ## estimated by maximizing the log marginal likelihood, and @qcode{'None'} ## when they were kept at their initial values. This property is ## read-only. ## ## @end deftp FitMethod = 'Exact'; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} BasisFunction ## ## Explicit basis of the model ## ## @qcode{'None'}, @qcode{'Constant'}, @qcode{'Linear'}, ## @qcode{'PureQuadratic'}, or the function handle that was supplied. This ## property is read-only. ## ## @end deftp BasisFunction = 'Constant'; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} Beta ## ## Estimated coefficients of the explicit basis ## ## A numeric vector with one element per basis term, empty when the basis ## is @qcode{'None'}. This property is read-only. ## ## @end deftp Beta = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} Sigma ## ## Estimated noise standard deviation ## ## A positive scalar. This property is read-only. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} LogLikelihood ## ## Maximized log marginal likelihood ## ## A scalar, or empty when @qcode{FitMethod} is @qcode{'None'} and nothing ## was maximized. This property is read-only. ## ## @end deftp LogLikelihood = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} ModelParameters ## ## Parameters used to train the model ## ## A structure holding the options the fit was performed under. MATLAB ## returns an object of its own class here; a structure carries the same ## information and is what every other learner in this package returns. ## ## @qcode{Beta}, @qcode{Sigma} and @qcode{KernelParameters} are the ## @strong{starting values} the fit was given, empty or zero where it was ## given none, as they are in MATLAB. What the fit found is reported by ## the @qcode{Beta} and @qcode{Sigma} properties and by ## @qcode{KernelInformation}. @qcode{Beta} defaults to a zero for every ## column the basis contributes, so a @qcode{'linear'} basis over three ## predictors starts at four zeros. ## ## @qcode{SigmaLowerBound} is the exception and is reported as it was ## resolved. MATLAB publishes no top-level field of that name, keeping ## it inside an @qcode{Options} structure this class does not carry. ## ## The fields MATLAB reports for its approximate fitting methods ## (@qcode{ActiveSet}, @qcode{Options}, @qcode{OptimizerOptions}, ## @qcode{ConstantKernelParameters}, @qcode{InitialStepSize}, ## @qcode{InitialSigmaLowerBoundTolerance}, @qcode{Verbose} and ## @qcode{CacheSize}) are absent, this class implementing exact fitting ## alone. This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} KernelFunction ## ## Form of the covariance function ## ## A character vector naming the covariance function, or the function ## handle that was supplied. This property is read-only. ## ## @end deftp KernelFunction = 'SquaredExponential'; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} KernelInformation ## ## Covariance function and its parameters ## ## A structure with fields @qcode{Name}, @qcode{KernelParameters} and ## @qcode{KernelParameterNames}, the last naming each parameter in the ## order they are stored. This property is read-only. ## ## @end deftp KernelInformation = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} PredictMethod ## ## Method used to make predictions ## ## @qcode{'Exact'}. This property is read-only. ## ## @end deftp PredictMethod = 'Exact'; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} Alpha ## ## Weights the predictions are made from ## ## An @math{Nx1} numeric vector. A prediction is the basis term plus the ## covariance between the new point and the active set, weighted by these. ## This property is read-only. ## ## @end deftp Alpha = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} ActiveSetVectors ## ## Subset of the training data used for predictions ## ## An @math{MxP} numeric matrix, standardized where the model standardized ## its predictors. It is the whole of the training data, since only the ## exact method is implemented. This property is read-only. ## ## @end deftp ActiveSetVectors = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} ActiveSetMethod ## ## Method used to select the active set ## ## @qcode{'Random'}. This property is read-only. ## ## @end deftp ActiveSetMethod = 'Random'; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} ActiveSetSize ## ## Size of the active set ## ## A positive integer scalar. This property is read-only. ## ## @end deftp ActiveSetSize = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} IsActiveSetVector ## ## Which observations are in the active set ## ## A logical vector with one element per training observation. This ## property is read-only. ## ## @end deftp IsActiveSetVector = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} ActiveSetHistory ## ## History of the active set selection ## ## @strong{Always empty.} It is declared for MATLAB compatibility, where ## it records the active set chosen at each iteration by a fit method that ## builds one. This class implements the exact method alone, which uses ## the whole of the training data and selects nothing, so there is no ## history to record. This property is read-only. ## ## @end deftp ActiveSetHistory = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} BCDInformation ## ## Block coordinate descent information ## ## @strong{Always empty.} It is declared for MATLAB compatibility, where ## it records a block coordinate descent. This class does not use that ## method, so there is nothing to report. This property is read-only. ## ## @end deftp BCDInformation = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} PredictorLocation ## ## Means the predictors were centred by ## ## A @math{1xP} numeric vector when the model standardized its predictors, ## and empty when it did not. This property is read-only. ## ## @end deftp PredictorLocation = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} PredictorScale ## ## Standard deviations the predictors were scaled by ## ## A @math{1xP} numeric vector when the model standardized its predictors, ## and empty when it did not. This property is read-only. ## ## @end deftp PredictorScale = []; ## -*- texinfo -*- ## @deftp {RegressionGP} {property} HyperparameterOptimizationResults ## ## Results of the hyperparameter optimization ## ## @strong{Always empty.} It is declared for MATLAB compatibility, where ## it holds what an automatic search over the hyperparameters found. This ## class fits the parameters it is given and runs no such search, so there ## is nothing to report. This property is read-only. ## ## @end deftp HyperparameterOptimizationResults = []; endproperties properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {RegressionGP} {property} ResponseTransform ## ## Transformation applied to the predicted response ## ## A character vector, or the text of the function handle that was ## supplied. Assigning to it accepts either. ## ## @end deftp ResponseTransform = 'none'; endproperties properties (GetAccess = public, SetAccess = protected, Hidden) ## The callable behind ResponseTransform. The public property is the text ## MATLAB stores; this is what predict actually applies. RTfun = @(y) y; endproperties methods (Access = public) ## -*- texinfo -*- ## @deftypefn {RegressionGP} {@var{obj} =} RegressionGP (@var{X}, @var{Y}) ## @deftypefnx {RegressionGP} {@var{obj} =} RegressionGP (@dots{}, @var{name}, @var{value}) ## ## Fit a Gaussian process regression model. ## ## @var{X} is an @math{N*P} numeric matrix of predictor data, one ## observation per row, and @var{Y} is the continuous response of those ## @math{N} observations. The fit runs at construction, so @var{obj} ## arrives fitted. ## ## The @var{name}/@var{value} pairs the fit accepts, and the validation ## each one is held to, are listed in @code{help RegressionGP}. ## @code{fitrgp} is the documented way to reach this constructor and ## takes the same pairs. ## ## @end deftypefn function this = RegressionGP (X, Y, varargin) ## Check for sufficient number of input arguments if (nargin < 2) error ("RegressionGP: too few input arguments."); endif ## Validate X and Y and drop rows with missing values [X, Y, RowsUsed] = this.checkXY_ (X, Y, 'RegressionGP'); [n, p] = size (X); ## Defaults KernelFunction = 'squaredexponential'; KernelParameters = []; BasisFunction = 'constant'; BetaIn = []; SigmaIn = []; ConstantSigma = false; SigmaLowerBound = []; FitMethod = 'exact'; PredictMethod = 'exact'; Optimizer = 'quasinewton'; Standardize = false; Weights = []; PredictorNames = {}; ResponseName = 'Y'; ResponseTransform = 'none'; CategoricalPredictors = []; ## Parse optional parameters while (numel (varargin) > 0) if (numel (varargin) < 2) error (strcat ("RegressionGP: optional arguments must be given", ... " in Name-Value pairs.")); endif switch (lower (varargin{1})) case 'kernelfunction' KernelFunction = varargin{2}; if (! (ischar (KernelFunction) || ... is_function_handle (KernelFunction))) error (strcat ("RegressionGP: 'KernelFunction' must be a", ... " character vector or a function handle.")); endif if (ischar (KernelFunction) && ... ! any (strcmpi (KernelFunction, kernelNames ()))) error ("RegressionGP: unsupported 'KernelFunction' value."); endif case 'kernelparameters' KernelParameters = varargin{2}; if (! (isnumeric (KernelParameters) && ... isvector (KernelParameters) && ... all (KernelParameters > 0))) error (strcat ("RegressionGP: 'KernelParameters' must be a", ... " vector of positive values.")); endif KernelParameters = KernelParameters(:); case 'basisfunction' BasisFunction = varargin{2}; if (! (ischar (BasisFunction) || ... is_function_handle (BasisFunction))) error (strcat ("RegressionGP: 'BasisFunction' must be a", ... " character vector or a function handle.")); endif if (ischar (BasisFunction) && ... ! any (strcmpi (BasisFunction, ... {'none', 'constant', 'linear', ... 'purequadratic'}))) error ("RegressionGP: unsupported 'BasisFunction' value."); endif case 'beta' BetaIn = varargin{2}; if (! (isnumeric (BetaIn) && isvector (BetaIn))) error ("RegressionGP: 'Beta' must be a numeric vector."); endif BetaIn = BetaIn(:); case 'sigma' SigmaIn = varargin{2}; if (! (isnumeric (SigmaIn) && isscalar (SigmaIn) && SigmaIn > 0)) error ("RegressionGP: 'Sigma' must be a positive scalar."); endif case 'constantsigma' ConstantSigma = varargin{2}; if (! (islogical (ConstantSigma) && isscalar (ConstantSigma))) error (strcat ("RegressionGP: 'ConstantSigma' must be a", ... " logical scalar.")); endif case 'sigmalowerbound' SigmaLowerBound = varargin{2}; if (! (isnumeric (SigmaLowerBound) && ... isscalar (SigmaLowerBound) && SigmaLowerBound > 0)) error (strcat ("RegressionGP: 'SigmaLowerBound' must be a", ... " positive scalar.")); endif case 'fitmethod' FitMethod = varargin{2}; if (! ischar (FitMethod)) error (strcat ("RegressionGP: 'FitMethod' must be a", ... " character vector.")); endif if (any (strcmpi (FitMethod, {'sd', 'sr', 'fic'}))) error (strcat ("RegressionGP: the approximate fitting", ... " methods are not implemented; 'FitMethod'", ... " must be either 'exact' or 'none'.")); endif if (! any (strcmpi (FitMethod, {'exact', 'none'}))) error ("RegressionGP: unsupported 'FitMethod' value."); endif case 'predictmethod' PredictMethod = varargin{2}; if (! ischar (PredictMethod)) error (strcat ("RegressionGP: 'PredictMethod' must be a", ... " character vector.")); endif if (any (strcmpi (PredictMethod, {'bcd', 'sd', 'sr', 'fic'}))) error (strcat ("RegressionGP: the approximate prediction", ... " methods are not implemented;", ... " 'PredictMethod' must be 'exact'.")); endif if (! strcmpi (PredictMethod, 'exact')) error ("RegressionGP: unsupported 'PredictMethod' value."); endif case 'optimizer' Optimizer = varargin{2}; if (! ischar (Optimizer)) error (strcat ("RegressionGP: 'Optimizer' must be a", ... " character vector.")); endif if (strcmpi (Optimizer, 'fmincon')) error (strcat ("RegressionGP: 'fmincon' is not available in", ... " core Octave; use 'quasinewton' or", ... " 'fminsearch'.")); endif if (! any (strcmpi (Optimizer, {'quasinewton', 'fminunc', ... 'lbfgs', 'fminsearch'}))) error ("RegressionGP: unsupported 'Optimizer' value."); endif case 'standardize' Standardize = varargin{2}; if (! (islogical (Standardize) && isscalar (Standardize))) error (strcat ("RegressionGP: 'Standardize' must be a", ... " logical scalar.")); endif case 'weights' Weights = varargin{2}; case 'predictornames' PredictorNames = varargin{2}; if (! (iscellstr (PredictorNames) && ... numel (PredictorNames) == p)) error (strcat ("RegressionGP: 'PredictorNames' must be a", ... " cell array of character vectors with one", ... " name per column of X.")); endif case 'responsename' ResponseName = varargin{2}; if (! ischar (ResponseName)) error (strcat ("RegressionGP: 'ResponseName' must be a", ... " character vector.")); endif case 'responsetransform' ResponseTransform = varargin{2}; case 'categoricalpredictors' CategoricalPredictors = varargin{2}; otherwise error (strcat ("RegressionGP: invalid parameter name in", ... " optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## Store the data and its description this.X = X; this.Y = Y; this.NumObservations = n; this.RowsUsed = RowsUsed; this.W = this.getWeights_ (Weights, n, 'RegressionGP'); if (isempty (PredictorNames)) PredictorNames = arrayfun (@(k) sprintf ('x%d', k), 1:p, ... 'UniformOutput', false); endif this.PredictorNames = PredictorNames; this.ExpandedPredictorNames = PredictorNames; this.ResponseName = ResponseName; this.CategoricalPredictors = CategoricalPredictors; this.BinEdges = {}; this.ResponseTransform = ResponseTransform; ## Standardize the predictors, if asked. The location and scale are ## kept so that predict can apply the same transformation. XS = X; if (Standardize) this.PredictorLocation = mean (X, 1); s = std (X, 0, 1); s(s == 0) = 1; this.PredictorScale = s; XS = (X - this.PredictorLocation) ./ this.PredictorScale; endif ## Initial values, which are the documented defaults where the caller ## gave none ## Every documented default is a multiple of std (Y), which a constant ## response makes zero, and the logarithm of zero is not a starting ## point. A unit spread is used instead: the fit is degenerate either ## way, but it returns a model rather than a NaN. ## The record of the fit keeps what the caller gave, which the lines ## below are about to replace with resolved defaults. What the fit ## used is reported by the Sigma property and by KernelInformation. SigmaGiven = SigmaIn; KernelParametersGiven = KernelParameters; if (isempty (BetaIn)) BetaGiven = zeros (columns (gpBasis (XS(1,:), BasisFunction)), 1); else BetaGiven = BetaIn; endif sy = std (Y); if (sy == 0) sy = 1; endif if (isempty (SigmaIn)) SigmaIn = sy / sqrt (2); endif if (isempty (SigmaLowerBound)) SigmaLowerBound = 1e-2 * sy; endif if (isempty (KernelParameters)) KernelParameters = defaultKernelParameters (XS, Y, KernelFunction); endif ## Fit [theta, sigmaN, beta, LL] = ... gpFit (XS, Y, KernelFunction, KernelParameters, BasisFunction, ... BetaIn, SigmaIn, ConstantSigma, SigmaLowerBound, ... FitMethod, Optimizer); ## Store the fitted model, in the form MATLAB stores it this.FitMethod = properName (FitMethod); this.PredictMethod = properName (PredictMethod); this.BasisFunction = properName (BasisFunction); this.KernelFunction = properName (KernelFunction); kpnames = kernelParameterNames (KernelFunction, columns (X)); KI = struct (); KI.Name = properName (KernelFunction); KI.KernelParameters = theta; KI.KernelParameterNames = kpnames; this.KernelInformation = KI; this.Beta = beta; this.Sigma = sigmaN; this.LogLikelihood = LL; this.ActiveSetVectors = XS; this.ActiveSetSize = n; this.IsActiveSetVector = true (n, 1); ## The fit as it was asked for, in MATLAB's field order. Beta, Sigma ## and KernelParameters are the starting values the caller gave, not ## what the fit found; SigmaLowerBound is the exception and is reported ## as it was resolved, MATLAB publishing no top-level field of that ## name and the fold refitting reading this one. this.ModelParameters = struct ('KernelFunction', ... properName (KernelFunction), ... 'KernelParameters', ... KernelParametersGiven, ... 'BasisFunction', ... properName (BasisFunction), ... 'Beta', BetaGiven, ... 'Sigma', SigmaGiven, ... 'FitMethod', properName (FitMethod), ... 'PredictMethod', ... properName (PredictMethod), ... 'ActiveSetSize', this.ActiveSetSize, ... 'ActiveSetMethod', ... this.ActiveSetMethod, ... 'Standardize', logical (Standardize), ... 'Optimizer', Optimizer, ... 'ConstantSigma', ... logical (ConstantSigma), ... 'SigmaLowerBound', SigmaLowerBound, ... 'Version', 1, 'Method', 'GP', ... 'Type', 'regression'); ## The prediction weights K = gpCovariance (XS, XS, KernelFunction, theta); H = gpBasis (XS, this.BasisFunction); A = K + sigmaN^2 * eye (n); if (isempty (beta)) this.Alpha = A \ Y; else this.Alpha = A \ (Y - H * beta); endif endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {RegressionGP} {@var{yFit} =} predict (@var{obj}, @var{XC}) ## @deftypefnx {RegressionGP} {[@var{yFit}, @var{ySD}, @var{yInt}] =} predict (@var{obj}, @var{XC}) ## @deftypefnx {RegressionGP} {[@dots{}] =} predict (@dots{}, @qcode{'Alpha'}, @var{alpha}) ## ## Predict the response for new data with a Gaussian process model. ## ## @code{@var{yFit} = predict (@var{obj}, @var{XC})} returns the predicted ## response of the @qcode{RegressionGP} model @var{obj} at the points in ## @var{XC}, which must have as many columns as the model has predictors. ## ## @code{[@var{yFit}, @var{ySD}, @var{yInt}] = predict (@dots{})} also ## returns the standard deviation of each predicted response and the ## prediction intervals. The standard deviation is that of a new ## @emph{response}, so it carries the noise as well as the uncertainty of ## the latent function, and the interval is the normal quantile of the ## level times it. ## ## @code{[@dots{}] = predict (@dots{}, @qcode{'Alpha'}, @var{alpha})} sets ## the significance level of the intervals, so that they are ## @math{100 * (1 - @var{alpha})} per cent intervals. @var{alpha} must be ## a scalar in the range @math{[0, 1]} and defaults to @math{0.05}. ## ## @end deftypefn function [yFit, ySD, yInt] = predict (this, XC, varargin) if (nargin < 2) error ("RegressionGP.predict: too few input arguments."); endif if (isempty (XC)) error ("RegressionGP.predict: XC is empty."); endif if (columns (XC) != columns (this.X)) error (strcat ("RegressionGP.predict: XC must have the same", ... " number of predictors as the trained model.")); endif CIAlpha = 0.05; while (numel (varargin) > 0) if (numel (varargin) < 2) error (strcat ("RegressionGP.predict: optional arguments must", ... " be given in Name-Value pairs.")); endif switch (lower (varargin{1})) case 'alpha' CIAlpha = varargin{2}; if (! (isnumeric (CIAlpha) && isscalar (CIAlpha) && ... CIAlpha >= 0 && CIAlpha <= 1)) error (strcat ("RegressionGP.predict: 'Alpha' must be a", ... " scalar between 0 and 1.")); endif otherwise error (strcat ("RegressionGP.predict: invalid NAME in optional", ... " pairs of arguments.")); endswitch varargin(1:2) = []; endwhile M = this.predictModel_ (CIAlpha); if (nargout < 2) yFit = this.RTfun (gpPredict (XC, M)); elseif (nargout < 3) [yFit, ySD] = gpPredict (XC, M); yFit = this.RTfun (yFit); else [yFit, ySD, yInt] = gpPredict (XC, M); yFit = this.RTfun (yFit); yInt = this.RTfun (yInt); endif endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGP} {@var{yFit} =} resubPredict (@var{obj}) ## @deftypefnx {RegressionGP} {[@var{yFit}, @var{ySD}, @var{yInt}] =} resubPredict (@var{obj}) ## ## Predict the response of the training data with a Gaussian process model. ## ## @code{@var{yFit} = resubPredict (@var{obj})} returns the response the ## @qcode{RegressionGP} model @var{obj} predicts at its own training data, ## and the further outputs are those of @code{predict}. ## ## @end deftypefn function [yFit, ySD, yInt] = resubPredict (this) if (nargout < 2) yFit = this.predict (this.X); elseif (nargout < 3) [yFit, ySD] = this.predict (this.X); else [yFit, ySD, yInt] = this.predict (this.X); endif endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGP} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {RegressionGP} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Compute the regression loss of a Gaussian process model. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} returns the mean ## squared error of the model @var{obj} on the data @var{X} and @var{Y}. ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} accepts ## @qcode{'LossFun'}, either @qcode{'mse'}, @qcode{'mae'}, ## @qcode{'epsiloninsensitive'} or a function handle taking the observed ## and the predicted response, and @qcode{'Weights'}, a vector of ## non-negative observation weights. ## ## @end deftypefn function L = loss (this, X, Y, varargin) if (nargin < 3) error ("RegressionGP.loss: too few input arguments."); endif [X, Y] = this.checkXY_ (X, Y, 'RegressionGP.loss'); LossFun = 'mse'; Weights = ones (rows (X), 1); Epsilon = 0; while (numel (varargin) > 0) if (numel (varargin) < 2) error (strcat ("RegressionGP.loss: optional arguments must be", ... " given in Name-Value pairs.")); endif switch (lower (varargin{1})) case 'lossfun' LossFun = varargin{2}; if (! (ischar (LossFun) || is_function_handle (LossFun))) error (strcat ("RegressionGP.loss: 'LossFun' must be a", ... " character vector or a function handle.")); endif if (ischar (LossFun) && ... ! any (strcmpi (LossFun, {'mse', 'mae', ... 'epsiloninsensitive'}))) error ("RegressionGP.loss: unsupported 'LossFun' value."); endif case 'weights' Weights = varargin{2}; if (! (isnumeric (Weights) && isvector (Weights) && ... numel (Weights) == rows (X) && all (Weights >= 0))) error (strcat ("RegressionGP.loss: 'Weights' must be a", ... " vector of non-negative values with one", ... " element per observation.")); endif Weights = Weights(:); case 'epsilon' Epsilon = varargin{2}; otherwise error (strcat ("RegressionGP.loss: invalid NAME in optional", ... " pairs of arguments.")); endswitch varargin(1:2) = []; endwhile yFit = this.predict (X); if (is_function_handle (LossFun)) L = LossFun (Y, yFit); return; endif switch (lower (LossFun)) case 'mse' L = sum (Weights .* (Y - yFit) .^ 2) / sum (Weights); case 'mae' L = sum (Weights .* abs (Y - yFit)) / sum (Weights); case 'epsiloninsensitive' e = max (0, abs (Y - yFit) - Epsilon); L = sum (Weights .* e) / sum (Weights); endswitch endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGP} {@var{L} =} resubLoss (@var{obj}) ## @deftypefnx {RegressionGP} {@var{L} =} resubLoss (@dots{}, @var{name}, @var{value}) ## ## Compute the resubstitution loss of a Gaussian process model. ## ## @code{@var{L} = resubLoss (@var{obj})} returns the loss of the model ## @var{obj} on the data it was trained on, and accepts the same ## Name-Value pairs as @code{loss}. ## ## @end deftypefn function L = resubLoss (this, varargin) L = this.loss (this.X, this.Y, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGP} {[@var{loores}, @var{neff}] =} postFitStatistics (@var{obj}) ## ## Compute the leave-one-out residuals of a Gaussian process model. ## ## @code{[@var{loores}, @var{neff}] = postFitStatistics (@var{obj})} ## returns the @math{Nx1} vector of leave-one-out residuals of the model ## @var{obj}, and the number of effective parameters the fit uses. Neither ## requires refitting the model: both follow from the factorization the fit ## already produced. ## ## The coefficients of the explicit basis are treated as estimated, which ## is what @qcode{FitMethod} @qcode{'Exact'} makes them, while the ## covariance parameters and the noise are treated as known. ## ## @end deftypefn function [loores, neff] = postFitStatistics (this) if (! strcmpi (this.PredictMethod, 'Exact')) error (strcat ("RegressionGP.postFitStatistics: post-fit", ... " statistics are available only when PredictMethod", ... " is 'Exact'.")); endif n = this.NumObservations; K = gpCovariance (this.ActiveSetVectors, this.ActiveSetVectors, ... this.KernelFunction, ... this.KernelInformation.KernelParameters); A = K + this.Sigma^2 * eye (n); H = gpBasis (this.ActiveSetVectors, this.BasisFunction); [L, p] = chol (A, 'lower'); if (p != 0) error (strcat ("RegressionGP.postFitStatistics: the covariance", ... " matrix is not positive definite.")); endif Li = L \ eye (n); Ai = Li' * Li; if (isempty (H)) P = Ai; else P = Ai - Ai * H * ((H' * Ai * H) \ (H' * Ai)); endif loores = (P * this.Y) ./ diag (P); neff = columns (H) + trace (K * P); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGP} {@var{CVMdl} =} crossval (@var{obj}) ## @deftypefnx {RegressionGP} {@var{CVMdl} =} crossval (@dots{}, @var{name}, @var{value}) ## ## Cross validate a Gaussian process model. ## ## @code{@var{CVMdl} = crossval (@var{obj})} returns a ## @qcode{RegressionPartitionedModel} built from the model @var{obj} by ## ten-fold cross validation. ## ## @code{@var{CVMdl} = crossval (@dots{}, @var{name}, @var{value})} accepts ## @qcode{'KFold'}, @qcode{'Holdout'}, @qcode{'Leaveout'} and ## @qcode{'CVPartition'}, of which at most one may be given. ## ## @end deftypefn function CVMdl = crossval (this, varargin) numFolds = 10; Holdout = []; Leaveout = 'off'; CVPartition = []; given = 0; while (numel (varargin) > 0) if (numel (varargin) < 2) error (strcat ("RegressionGP.crossval: optional arguments must", ... " be given in Name-Value pairs.")); endif switch (lower (varargin{1})) case 'kfold' numFolds = varargin{2}; if (! (isnumeric (numFolds) && isscalar (numFolds) && ... numFolds == fix (numFolds) && numFolds > 1)) error (strcat ("RegressionGP.crossval: 'KFold' must be an", ... " integer value greater than 1.")); endif given++; case 'holdout' Holdout = varargin{2}; if (! (isnumeric (Holdout) && isscalar (Holdout) && ... Holdout > 0 && Holdout < 1)) error (strcat ("RegressionGP.crossval: 'Holdout' must be a", ... " numeric value between 0 and 1.")); endif given++; case 'leaveout' Leaveout = varargin{2}; if (! (ischar (Leaveout) && ... any (strcmpi (Leaveout, {'on', 'off'})))) error (strcat ("RegressionGP.crossval: 'Leaveout' must be", ... " either 'on' or 'off'.")); endif given++; case 'cvpartition' CVPartition = varargin{2}; if (! (isa (CVPartition, 'cvpartition'))) error (strcat ("RegressionGP.crossval: 'CVPartition' must", ... " be a cvpartition object.")); endif given++; otherwise error (strcat ("RegressionGP.crossval: invalid parameter name", ... " in optional paired arguments.")); endswitch varargin(1:2) = []; endwhile if (given > 1) error (strcat ("RegressionGP.crossval: you can use only one of", ... " 'KFold', 'Holdout', 'Leaveout', or", ... " 'CVPartition' options.")); endif n = this.NumObservations; if (! isempty (CVPartition)) partition = CVPartition; elseif (! isempty (Holdout)) partition = cvpartition (n, 'Holdout', Holdout); elseif (strcmpi (Leaveout, 'on')) partition = cvpartition (n, 'LeaveOut'); else partition = cvpartition (n, 'KFold', numFolds); endif CVMdl = RegressionPartitionedModel (this, partition); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGP} {@var{CMdl} =} compact (@var{obj}) ## ## Return a compact Gaussian process regression model. ## ## @code{@var{CMdl} = compact (@var{obj})} returns a ## @qcode{CompactRegressionGP} object holding what is needed to predict ## and nothing else: the training data, the response and everything that ## describes them are dropped. ## ## @end deftypefn function CMdl = compact (this) CMdl = CompactRegressionGP (this); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionGP} {} savemodel (@var{obj}, @var{filename}) ## ## Save a Gaussian process model to a file. ## ## @code{savemodel (@var{obj}, @var{filename})} saves the model @var{obj} ## into @var{filename} in a form @code{loadmodel} can read back. ## ## @end deftypefn function savemodel (obj, fname) classdef_name = 'RegressionGP'; X = obj.X; Y = obj.Y; NumObservations = obj.NumObservations; RowsUsed = obj.RowsUsed; W = obj.W; PredictorNames = obj.PredictorNames; ExpandedPredictorNames = obj.ExpandedPredictorNames; ResponseName = obj.ResponseName; CategoricalPredictors = obj.CategoricalPredictors; BinEdges = obj.BinEdges; FitMethod = obj.FitMethod; BasisFunction = obj.BasisFunction; Beta = obj.Beta; Sigma = obj.Sigma; LogLikelihood = obj.LogLikelihood; ModelParameters = obj.ModelParameters; KernelFunction = obj.KernelFunction; KernelInformation = obj.KernelInformation; PredictMethod = obj.PredictMethod; Alpha = obj.Alpha; ActiveSetVectors = obj.ActiveSetVectors; ActiveSetMethod = obj.ActiveSetMethod; ActiveSetSize = obj.ActiveSetSize; IsActiveSetVector = obj.IsActiveSetVector; ActiveSetHistory = obj.ActiveSetHistory; BCDInformation = obj.BCDInformation; PredictorLocation = obj.PredictorLocation; PredictorScale = obj.PredictorScale; ResponseTransform = obj.ResponseTransform; HyperparameterOptimizationResults = obj.HyperparameterOptimizationResults; save ('-binary', fname, 'classdef_name', 'X', 'Y', 'NumObservations', ... 'RowsUsed', 'W', 'PredictorNames', 'ExpandedPredictorNames', ... 'ResponseName', 'CategoricalPredictors', 'BinEdges', ... 'FitMethod', 'BasisFunction', 'Beta', 'Sigma', 'LogLikelihood', ... 'ModelParameters', 'KernelFunction', 'KernelInformation', ... 'PredictMethod', 'Alpha', 'ActiveSetVectors', ... 'ActiveSetMethod', 'ActiveSetSize', 'IsActiveSetVector', ... 'ActiveSetHistory', 'BCDInformation', ... 'PredictorLocation', 'PredictorScale', 'ResponseTransform', ... 'HyperparameterOptimizationResults'); endfunction endmethods methods (Access = public, Hidden) function display (this) in_name = inputname (1); if (! isempty (in_name)) printf ('%s =\n', in_name); endif disp (this); endfunction function disp (this) printf ("\n RegressionGP\n\n"); printf ("%25s: '%s'\n", 'ResponseName', this.ResponseName); printf ("%25s: %d\n", 'NumObservations', this.NumObservations); printf ("%25s: %d\n", 'NumPredictors', columns (this.X)); printf ("%25s: '%s'\n", 'KernelFunction', ... nameOf (this.KernelFunction)); printf ("%25s: '%s'\n", 'BasisFunction', nameOf (this.BasisFunction)); printf ("%25s: '%s'\n", 'FitMethod', this.FitMethod); printf ("%25s: '%s'\n", 'PredictMethod', this.PredictMethod); printf ("%25s: %g\n", 'Sigma', this.Sigma); if (! isempty (this.LogLikelihood)) printf ("%25s: %g\n", 'LogLikelihood', this.LogLikelihood); endif printf ("\n"); endfunction ## The structure gpPredict consumes. It is assembled here so that the ## compact class and this one predict through the same code. function M = predictModel_ (this, CIAlpha) M = struct ('X', this.ActiveSetVectors, 'Alpha', this.Alpha, ... 'KernelFunction', this.KernelFunction, ... 'Theta', this.KernelInformation.KernelParameters, ... 'BasisFunction', this.BasisFunction, 'Beta', this.Beta, ... 'Sigma', this.Sigma, 'Location', this.PredictorLocation, ... 'Scale', this.PredictorScale, 'CIAlpha', CIAlpha); endfunction ## Custom setter, so that assigning a name or a handle updates both the ## text the property reports and the callable predict uses. function this = set.ResponseTransform (this, val) [this.RTfun, this.ResponseTransform] = ... parseResponseTransform (val, 'RegressionGP'); endfunction endmethods methods (Access = private) ## Validate the predictor and response data, and drop any row that is not ## complete in both. function [X, Y, used] = checkXY_ (this, X, Y, caller) if (! (isnumeric (X) && isreal (X) && ismatrix (X))) error ("%s: invalid values in X.", caller); endif if (! (isnumeric (Y) && isreal (Y) && isvector (Y))) error ("%s: invalid values in Y.", caller); endif Y = Y(:); if (rows (X) != rows (Y)) error ("%s: number of rows in X and Y must be equal.", caller); endif if (isempty (X)) error ("%s: X is empty.", caller); endif used = ! (any (isnan (X), 2) | isnan (Y)); if (all (used)) used = []; else X = X(used, :); Y = Y(used); if (isempty (Y)) error ("%s: no complete observations in the data.", caller); endif endif endfunction ## Validate observation weights, defaulting to a vector of ones. function W = getWeights_ (this, Weights, n, caller) if (isempty (Weights)) W = ones (n, 1); return; endif if (! (isnumeric (Weights) && isvector (Weights) && ... numel (Weights) == n && all (Weights >= 0))) error (strcat ("%s: 'Weights' must be a vector of non-negative", ... " values with one element per observation."), caller); endif W = Weights(:); endfunction endmethods methods (Static, Hidden) function mdl = load_model (filename, data) mdl = RegressionGP (data.X, data.Y); fields = fieldnames (data); for k = 1:numel (fields) mdl.(fields{k}) = data.(fields{k}); endfor endfunction endmethods endclassdef ## The proper name of a value, as the model reports it: MATLAB accepts the ## lowercase spelling and stores the capitalized one. function s = properName (v) if (is_function_handle (v)) s = v; return; endif known = {'none', 'None'; 'exact', 'Exact'; 'constant', 'Constant'; ... 'linear', 'Linear'; 'purequadratic', 'PureQuadratic'; ... 'random', 'Random'; ... 'exponential', 'Exponential'; ... 'squaredexponential', 'SquaredExponential'; ... 'matern32', 'Matern32'; 'matern52', 'Matern52'; ... 'rationalquadratic', 'RationalQuadratic'; ... 'ardexponential', 'ARDExponential'; ... 'ardsquaredexponential', 'ARDSquaredExponential'; ... 'ardmatern32', 'ARDMatern32'; 'ardmatern52', 'ARDMatern52'; ... 'ardrationalquadratic', 'ARDRationalQuadratic'}; idx = find (strcmpi (v, known(:,1)), 1); if (isempty (idx)) s = v; else s = known{idx, 2}; endif endfunction function s = nameOf (v) if (is_function_handle (v)) s = func2str (v); else s = v; endif endfunction function names = kernelNames () names = {'exponential', 'squaredexponential', 'matern32', 'matern52', ... 'rationalquadratic', 'ardexponential', 'ardsquaredexponential', ... 'ardmatern32', 'ardmatern52', 'ardrationalquadratic'}; endfunction ## The names of a kernel's parameters, in the order they are stored. function names = kernelParameterNames (kern, d) if (is_function_handle (kern)) names = {}; return; endif if (strncmpi (kern, 'ard', 3)) names = arrayfun (@(k) sprintf ('LengthScale%d', k), 1:d, ... 'UniformOutput', false)'; if (strcmpi (kern, 'ardrationalquadratic')) names = [names; {'AlphaRQ'}]; endif names = [names; {'SigmaF'}]; else if (strcmpi (kern, 'rationalquadratic')) names = {'SigmaL'; 'AlphaRQ'; 'SigmaF'}; else names = {'SigmaL'; 'SigmaF'}; endif endif endfunction ## The documented initial values of the covariance parameters. function theta0 = defaultKernelParameters (X, Y, kern) if (is_function_handle (kern)) error (strcat ("RegressionGP: 'KernelParameters' must be given when", ... " 'KernelFunction' is a function handle.")); endif sf = std (Y) / sqrt (2); if (sf == 0) sf = 1 / sqrt (2); endif if (strncmpi (kern, 'ard', 3)) theta0 = std (X, 0, 1)'; if (strcmpi (kern, 'ardrationalquadratic')) theta0 = [theta0; 1]; endif theta0 = [theta0; sf]; else theta0 = mean (std (X, 0, 1)); if (strcmpi (kern, 'rationalquadratic')) theta0 = [theta0; 1]; endif theta0 = [theta0; sf]; endif theta0(theta0 <= 0) = 1; endfunction ## Covariance between two sets of points, built in or supplied. function K = gpCovariance (A, B, kern, theta) if (is_function_handle (kern)) K = kern (A, B, theta); else K = gpKernel (A, B, kern, theta); endif endfunction ## Fit the model. With FitMethod 'none' the initial values are kept and only ## Beta is computed; with 'exact' the covariance parameters and the noise are ## estimated by maximizing the log marginal likelihood, with Beta following in ## closed form at every step, so the optimizer never carries it. function [theta, sigmaN, beta, LL] = gpFit (X, Y, kern, theta0, basis, ... betaIn, sigma0, constSigma, ... sigmaLB, fitMethod, optimizer) H = gpBasis (X, basis); if (strcmpi (fitMethod, 'none')) theta = theta0; sigmaN = sigma0; if (isempty (betaIn)) beta = zeros (columns (H), 1); else if (numel (betaIn) != columns (H)) error (strcat ("RegressionGP: 'Beta' must have one element per", ... " basis term.")); endif beta = betaIn; endif LL = []; return; endif if (sigma0 <= sigmaLB) sigma0 = sigmaLB * (1 + 1e-6); endif ## The optimizer works on an unconstrained scale: the covariance parameters ## through their logarithm, the noise as the lower bound plus an exponential, ## which is the parameterization MATLAB documents. if (constSigma) u0 = log (theta0); else u0 = [log(theta0); log(sigma0 - sigmaLB)]; endif f = @(u) gpObjective (u, X, Y, kern, H, sigmaLB, constSigma, sigma0); if (strcmpi (optimizer, 'fminsearch')) opts = optimset ('MaxFunEvals', 5000, 'MaxIter', 2000, 'TolX', 1e-10, ... 'TolFun', 1e-10); u = fminsearch (@(u) f(u), u0, opts); elseif (strcmpi (optimizer, 'lbfgs')) ## LossTolerance is switched off rather than left at its default: the ## objective here is a negative log likelihood, which goes negative, and ## the test is on the value rather than on its change, so any default ## would stop the fit on the first step past zero. opts = struct ('IterationLimit', 1000, 'GradientTolerance', 1e-8, ... 'StepTolerance', 1e-12, 'LossTolerance', -Inf); u = __lbfgs__ (f, u0, opts); else opts = optimset ('GradObj', 'on', 'MaxFunEvals', 5000, ... 'MaxIter', 1000, 'TolX', 1e-12, 'TolFun', 1e-12); u = fminunc (f, u0, opts); endif if (constSigma) theta = exp (u); sigmaN = sigma0; else theta = exp (u(1:end-1)); sigmaN = sigmaLB + exp (u(end)); endif [nll, beta] = gpNegLogLik (u, X, Y, kern, H, sigmaLB, constSigma, sigma0); LL = -nll; endfunction ## The objective as an optimizer expects it: the value first and the gradient ## second, where gpNegLogLik returns Beta in between for the caller that wants ## it. function [nll, g] = gpObjective (u, X, Y, kern, H, sigmaLB, constSigma, sigma0) if (nargout > 1) [nll, ~, g] = gpNegLogLik (u, X, Y, kern, H, sigmaLB, constSigma, sigma0); else nll = gpNegLogLik (u, X, Y, kern, H, sigmaLB, constSigma, sigma0); endif endfunction ## The negative profile log marginal likelihood and its gradient. Beta sits at ## its generalized least squares optimum throughout, so by the envelope theorem ## it contributes nothing to the gradient. function [nll, beta, g] = gpNegLogLik (u, X, Y, kern, H, sigmaLB, ... constSigma, sigma0) n = rows (X); if (constSigma) theta = exp (u); sigmaN = sigma0; else theta = exp (u(1:end-1)); sigmaN = sigmaLB + exp (u(end)); endif K = gpCovariance (X, X, kern, theta); A = K + sigmaN^2 * eye (n); [L, p] = chol (A, 'lower'); if (p != 0) nll = 1e10; beta = zeros (columns (H), 1); g = zeros (size (u)); return; endif Li_Y = L \ Y; if (isempty (H)) beta = zeros (0, 1); v = Li_Y; else ## Beta is the least squares solution of the whitened system, taken ## directly rather than through the normal equations: forming Li_H' * Li_H ## squares the condition number, and a basis whose columns are nearly ## dependent then warns from inside the objective on every iteration. Li_H = L \ H; beta = Li_H \ Li_Y; v = Li_Y - Li_H * beta; endif nll = 0.5 * (v' * v) + sum (log (diag (L))) + 0.5 * n * log (2*pi); if (nargout < 3) return; endif r = Y; if (! isempty (H)) r = Y - H * beta; endif ## The inverse is taken through the factor that is already to hand: inv () ## on a covariance whose noise has been driven small warns about a ## conditioning the caller can do nothing about, and the triangular solves ## are both quieter and cheaper. Li = L \ eye (n); Ai = Li' * Li; a = Ai * r; W = Ai - a * a'; g = zeros (size (u)); if (! is_function_handle (kern)) dK = gpKernelDeriv (X, kern, theta, K); for j = 1:numel (theta) g(j) = 0.5 * sum (sum (W .* dK{j})) * theta(j); endfor else ## A supplied kernel has no derivative to hand, so its parameters are ## differenced instead. for j = 1:numel (theta) h = 1e-6 * max (1, abs (u(j))); up = u; up(j) += h; um = u; um(j) -= h; g(j) = (gpNegLogLik (up, X, Y, kern, H, sigmaLB, constSigma, sigma0) - ... gpNegLogLik (um, X, Y, kern, H, sigmaLB, constSigma, sigma0)) ... / (2*h); endfor endif if (! constSigma) g(end) = 0.5 * trace (W) * 2 * sigmaN * (sigmaN - sigmaLB); endif endfunction ## Derivatives of the covariance matrix with respect to each of its parameters. function dK = gpKernelDeriv (X, kern, theta, K) d = columns (X); ard = (numel (kern) > 3 && strncmpi (kern, 'ARD', 3)); base = lower (strrep (lower (kern), 'ard', '')); if (ard) L = theta(1:d)(:)'; if (strcmp (base, 'rationalquadratic')) alphaRQ = theta(d+1); sigmaF = theta(d+2); else sigmaF = theta(d+1); endif else L = repmat (theta(1), 1, d); if (strcmp (base, 'rationalquadratic')) alphaRQ = theta(2); sigmaF = theta(3); else sigmaF = theta(2); endif endif n = rows (X); S = cell (1, d); r2 = zeros (n, n); for j = 1:d S{j} = ((X(:,j) - X(:,j)') / L(j)) .^ 2; r2 += S{j}; endfor r = sqrt (r2); zero = (r2 == 0); switch (base) case 'exponential' dKdr2 = -K ./ (2 * r); dKdr2(zero) = 0; case 'squaredexponential' dKdr2 = -K / 2; case 'matern32' s = sqrt (3) * r; dKdr2 = -sigmaF^2 * (s .^ 2) .* exp (-s) ./ (2 * r2); dKdr2(zero) = -sigmaF^2 * 3 / 2; case 'matern52' s = sqrt (5) * r; dKdr2 = -sigmaF^2 * (s .^ 2) .* (1 + s) .* exp (-s) ./ (6 * r2); dKdr2(zero) = -sigmaF^2 * 5 / 6; case 'rationalquadratic' Q = 1 + r2 / (2 * alphaRQ); dKdr2 = -sigmaF^2 * Q .^ (-alphaRQ - 1) / 2; endswitch dK = {}; if (ard) for j = 1:d dK{end+1} = dKdr2 .* (-2 * S{j} / L(j)); endfor else dK{end+1} = dKdr2 .* (-2 * r2 / L(1)); endif if (strcmp (base, 'rationalquadratic')) Q = 1 + r2 / (2 * alphaRQ); dK{end+1} = K .* (-log (Q) + r2 ./ (2 * alphaRQ * Q)); endif dK{end+1} = 2 * K / sigmaF; endfunction ## Every expected value below was measured on MATLAB R2024a. The tolerances ## are set by what this implementation actually achieves: the covariance ## parameters and the noise agree to about nine digits, because the optimizer ## here converges to a log marginal likelihood a little above MATLAB's, and ## every prediction inherits that. %!demo %! ## Fit a Gaussian process to noisy observations of a smooth function and %! ## show the prediction interval widening away from the data. %! x = [0.1; 0.3; 0.4; 0.7; 0.9; 1.4; 1.6; 1.9]'; %! x = x(:); %! y = sin (3 * x) + 0.05 * cos (11 * x); %! Mdl = fitrgp (x, y); %! xq = linspace (-0.2, 2.2, 200)'; %! [yq, ~, yint] = predict (Mdl, xq); %! figure ('visible', 'off'); %! hold on; %! plot (xq, yint(:,1), 'r:'); %! plot (xq, yint(:,2), 'r:'); %! plot (xq, yq, 'b-'); %! plot (x, y, 'ko'); %! hold off; %! xlabel ('x'); %! ylabel ('y'); %! title ('Gaussian process regression with 95% prediction interval'); %!test %! ## The model reports the surface MATLAB reports %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.1 * cos (7*x); %! Mdl = RegressionGP (x, y); %! assert_equal (class (Mdl), 'RegressionGP'); %! assert_equal (Mdl.NumObservations, 20); %! assert_equal (Mdl.FitMethod, 'Exact'); %! assert_equal (Mdl.PredictMethod, 'Exact'); %! assert_equal (Mdl.BasisFunction, 'Constant'); %! assert_equal (Mdl.KernelFunction, 'SquaredExponential'); %! assert_equal (Mdl.ResponseName, 'Y'); %! assert_equal (Mdl.PredictorNames, {'x1'}); %! assert_equal (Mdl.ResponseTransform, 'none'); %!test %! ## BinEdges is an empty cell, as it is for every learner that does no %! ## binning: a Gaussian process takes its predictors as they are %! x = linspace (0, 1, 20)'; %! Mdl = RegressionGP (x, sin (2*pi*x)); %! assert_equal (class (Mdl.BinEdges), 'cell'); %! assert_equal (Mdl.BinEdges, {}); %!test %! ## The fitted covariance parameters and noise match R2024a %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.1 * cos (7*x); %! Mdl = RegressionGP (x, y); %! assert_equal (Mdl.KernelInformation.KernelParameters, ... %! [0.386370514454926; 1.505132511329997], 1e-8); %! assert_equal (Mdl.Beta, -0.072781321631854, 1e-7); %! assert_equal (Mdl.Sigma, 0.006877618228324, 1e-7); %! assert_equal (Mdl.LogLikelihood, 47.574057509534278, 1e-4); %!test %! ## 'lbfgs' finds the same optimum the dense solver does %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.1 * cos (7*x); %! Mdl = RegressionGP (x, y, 'Optimizer', 'lbfgs'); %! ## Relative, and loose: the likelihood surface is flat here, so where the %! ## search stops is decided by the last bits of the arithmetic and moves by %! ## about 1e-6 between platforms. The likelihood is what pins this optimum, %! ## and the test below holds it to 1e-8. %! assert_equal (Mdl.KernelInformation.KernelParameters, ... %! [0.386370514454926; 1.505132511329997], -1e-4); %! assert_equal (Mdl.Beta, -0.072781321631854, -1e-4); %!test %! ## The likelihood agrees far more tightly than the parameters do, because %! ## the surface is flat there: a 2e-7 move in the parameters buys 2e-10 of %! ## likelihood. Comparing the parameters alone understates the agreement. %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.1 * cos (7*x); %! Mq = RegressionGP (x, y, 'Optimizer', 'quasinewton'); %! Ml = RegressionGP (x, y, 'Optimizer', 'lbfgs'); %! assert_equal (Ml.LogLikelihood, Mq.LogLikelihood, 1e-8); %!test %! ## The optimizer that ran is recorded %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.1 * cos (7*x); %! Mdl = RegressionGP (x, y, 'Optimizer', 'lbfgs'); %! assert_equal (Mdl.ModelParameters.Optimizer, 'lbfgs'); %!test %! ## predict reproduces the R2024a values, and the standard deviation is %! ## that of a response, so it carries the noise %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.1 * cos (7*x); %! Mdl = RegressionGP (x, y); %! xq = [0.05; 0.33; 0.5; 0.77; 0.95]; %! [yp, ysd, yint] = predict (Mdl, xq); %! assert_equal (yp, [0.404401855406521; 0.809355242891053; ... %! -0.093708552144171; -0.928420803239529; ... %! -0.217104024154071], 1e-7); %! assert_equal (ysd, [0.008117035802727; 0.007747238553279; ... %! 0.007727093294465; 0.007796728197045; ... %! 0.008117035802754], 1e-6); %! assert_equal (size (yint), [5, 2]); %! assert (all (yint(:,1) < yp)); %! assert (all (yint(:,2) > yp)); %!test %! ## The interval is the normal quantile of the level times the standard %! ## deviation, so a looser level gives a narrower interval %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.1 * cos (7*x); %! Mdl = RegressionGP (x, y); %! xq = [0.05; 0.33; 0.5]; %! [yp, ysd, yint] = predict (Mdl, xq); %! assert_equal (yint(:,2) - yp, norminv (0.975) * ysd, 1e-12); %! [~, ~, yint90] = predict (Mdl, xq, 'Alpha', 0.10); %! assert_equal (yint90(:,2) - yp, norminv (0.95) * ysd, 1e-12); %! assert (all (yint90(:,2) - yint90(:,1) < yint(:,2) - yint(:,1))); %!test %! ## FitMethod 'none' keeps the documented initial values, estimates nothing %! ## and has no likelihood to report %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.1 * cos (7*x); %! Mdl = RegressionGP (x, y, 'FitMethod', 'none'); %! assert_equal (Mdl.FitMethod, 'None'); %! assert_equal (Mdl.Sigma, std (y) / sqrt (2), 1e-14); %! assert_equal (Mdl.KernelInformation.KernelParameters, ... %! [mean(std (x)); std(y) / sqrt(2)], 1e-14); %! assert_equal (Mdl.Beta, 0); %! assert_equal (Mdl.LogLikelihood, []); %!test %! ## The parameters of each covariance function are named as MATLAB names %! ## them, and there is one length scale per predictor for the ARD kernels %! X = [linspace(0, 1, 15)', cos(linspace(0, 3, 15))']; %! y = X(:,1) .^ 2 + 0.3 * X(:,2); %! M1 = RegressionGP (X, y, 'KernelFunction', 'squaredexponential'); %! assert_equal (M1.KernelInformation.KernelParameterNames, ... %! {'SigmaL'; 'SigmaF'}); %! M2 = RegressionGP (X, y, 'KernelFunction', 'rationalquadratic'); %! assert_equal (M2.KernelInformation.KernelParameterNames, ... %! {'SigmaL'; 'AlphaRQ'; 'SigmaF'}); %! M3 = RegressionGP (X, y, 'KernelFunction', 'ardmatern32'); %! assert_equal (M3.KernelInformation.KernelParameterNames, ... %! {'LengthScale1'; 'LengthScale2'; 'SigmaF'}); %! assert_equal (numel (M3.KernelInformation.KernelParameters), 3); %!test %! ## The name of the covariance function is stored capitalized, as MATLAB %! ## stores it, whatever spelling the caller used %! x = linspace (0, 1, 12)'; %! y = cos (3*x); %! Mdl = RegressionGP (x, y, 'KernelFunction', 'ardmatern52'); %! assert_equal (Mdl.KernelFunction, 'ARDMatern52'); %! assert_equal (Mdl.KernelInformation.Name, 'ARDMatern52'); %!test %! ## The explicit basis has one coefficient per term, and none has none %! X = [linspace(0, 1, 15)', cos(linspace(0, 3, 15))']; %! y = X(:,1) .^ 2 + 0.3 * X(:,2); %! assert_equal (numel (RegressionGP (X, y, 'BasisFunction', 'none').Beta), 0); %! assert_equal (numel (RegressionGP (X, y, ... %! 'BasisFunction', 'constant').Beta), 1); %! assert_equal (numel (RegressionGP (X, y, ... %! 'BasisFunction', 'linear').Beta), 3); %! assert_equal (numel (RegressionGP (X, y, ... %! 'BasisFunction', 'pureQuadratic').Beta), 5); %!test %! ## Standardizing records the location and scale it used, and predict %! ## applies the same transformation %! X = [linspace(0, 10, 20)', linspace(-5, 5, 20)']; %! y = 0.3 * X(:,1) - 0.2 * X(:,2); %! Mdl = RegressionGP (X, y, 'Standardize', true); %! assert_equal (Mdl.PredictorLocation, mean (X, 1), 1e-14); %! assert_equal (Mdl.PredictorScale, std (X, 0, 1), 1e-14); %! assert_equal (predict (Mdl, X(1:3,:)), y(1:3), 1e-3); %!test %! ## Without standardizing there is no location or scale to report %! x = linspace (0, 1, 12)'; %! Mdl = RegressionGP (x, cos (3*x)); %! assert_equal (Mdl.PredictorLocation, []); %! assert_equal (Mdl.PredictorScale, []); %!test %! ## A held noise is not estimated %! x = linspace (0, 1, 15)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! Mdl = RegressionGP (x, y, 'ConstantSigma', true, 'Sigma', 0.3); %! assert_equal (Mdl.Sigma, 0.3, 1e-14); %!test %! ## The noise cannot go below its lower bound %! x = linspace (0, 1, 15)'; %! y = cos (3*x); %! Mdl = RegressionGP (x, y, 'SigmaLowerBound', 0.05); %! assert (Mdl.Sigma >= 0.05); %!test %! ## The active set of an exactly fitted model is the whole training data %! x = linspace (0, 1, 15)'; %! Mdl = RegressionGP (x, cos (3*x)); %! assert_equal (Mdl.ActiveSetSize, 15); %! assert_equal (Mdl.ActiveSetVectors, x); %! assert_equal (Mdl.IsActiveSetVector, true (15, 1)); %! assert_equal (Mdl.ActiveSetMethod, 'Random'); %!test %! ## resubPredict is predict on the training data %! x = linspace (0, 1, 15)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! Mdl = RegressionGP (x, y); %! assert_equal (resubPredict (Mdl), predict (Mdl, x), 1e-14); %!test %! ## The default loss is the mean squared error, and it is small for a model %! ## that interpolates its own training data closely %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.1 * cos (7*x); %! Mdl = RegressionGP (x, y); %! assert_equal (loss (Mdl, x, y), mean ((y - predict (Mdl, x)) .^ 2), 1e-14); %! assert_equal (resubLoss (Mdl), loss (Mdl, x, y), 1e-14); %! assert (resubLoss (Mdl) < 1e-4); %!test %! ## The other loss functions, and weights %! x = linspace (0, 1, 15)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! Mdl = RegressionGP (x, y); %! r = y - predict (Mdl, x); %! assert_equal (loss (Mdl, x, y, 'LossFun', 'mae'), mean (abs (r)), 1e-14); %! w = linspace (1, 2, 15)'; %! assert_equal (loss (Mdl, x, y, 'Weights', w), ... %! sum (w .* r .^ 2) / sum (w), 1e-14); %! mae = @(a, b) mean (abs (a - b)); %! assert_equal (loss (Mdl, x, y, 'LossFun', mae), mean (abs (r)), 1e-14); %!test %! ## The leave-one-out residuals and the effective parameter count match %! ## R2024a, and neither refits the model %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.1 * cos (7*x); %! Mdl = RegressionGP (x, y); %! [loores, neff] = postFitStatistics (Mdl); %! assert_equal (size (loores), [20, 1]); %! assert_equal (loores(1:4), [0.006483807954955; -0.002405391043015; ... %! -0.000899188010473; 0.000975297024791], 1e-6); %! assert_equal (neff, 7.186830203018070, 1e-5); %!test %! ## The leave-one-out residuals are larger than the fitted residuals, since %! ## each leaves out the observation it is about %! x = linspace (0, 1, 20)'; %! y = sin (2*pi*x) + 0.1 * cos (7*x); %! Mdl = RegressionGP (x, y); %! loores = postFitStatistics (Mdl); %! assert (all (abs (loores) >= abs (y - predict (Mdl, x)) - 1e-12)); %!test %! ## compact keeps what predicts and drops the rest, and predicts the same %! x = linspace (0, 1, 15)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! Mdl = RegressionGP (x, y); %! CMdl = compact (Mdl); %! assert_equal (class (CMdl), 'CompactRegressionGP'); %! assert_equal (predict (CMdl, x), predict (Mdl, x), 1e-14); %! [y1, s1] = predict (Mdl, x); %! [y2, s2] = predict (CMdl, x); %! assert_equal (s1, s2, 1e-14); %!test %! ## crossval returns a partitioned model over the observations trained on %! x = linspace (0, 1, 20)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! Mdl = RegressionGP (x, y); %! CVMdl = crossval (Mdl, 'KFold', 4); %! assert_equal (class (CVMdl), 'RegressionPartitionedModel'); %! assert_equal (CVMdl.KFold, 4); %! assert_equal (CVMdl.CrossValidatedModel, 'GP'); %! assert_equal (numel (CVMdl.Trained), 4); %! assert_equal (class (CVMdl.Trained{1}), 'CompactRegressionGP'); %! assert_equal (size (kfoldPredict (CVMdl)), [20, 1]); %!test %! ## A model saved and loaded predicts what it predicted before %! x = linspace (0, 1, 15)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! Mdl = RegressionGP (x, y); %! fname = tempname (); %! savemodel (Mdl, fname); %! Mdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (Mdl2), 'RegressionGP'); %! assert_equal (Mdl2.NumObservations, Mdl.NumObservations); %! assert_equal (Mdl2.Beta, Mdl.Beta); %! assert_equal (Mdl2.Sigma, Mdl.Sigma); %! assert_equal (Mdl2.KernelInformation.KernelParameters, ... %! Mdl.KernelInformation.KernelParameters); %! assert_equal (Mdl2.LogLikelihood, Mdl.LogLikelihood); %! assert_equal (predict (Mdl2, x), predict (Mdl, x), 1e-14); %!test %! ## A response transform is applied to the prediction and reported as text %! x = linspace (0, 1, 12)'; %! y = cos (3*x); %! Mdl = RegressionGP (x, y, 'ResponseTransform', 'exp'); %! assert_equal (Mdl.ResponseTransform, 'exp'); %! Mdl2 = RegressionGP (x, y); %! assert_equal (predict (Mdl, x), exp (predict (Mdl2, x)), 1e-12); %!test %! ## A row with a missing value is dropped, and RowsUsed says which %! x = linspace (0, 1, 12)'; %! y = cos (3*x); %! x(4) = NaN; %! Mdl = RegressionGP (x, y); %! assert_equal (Mdl.NumObservations, 11); %! assert_equal (Mdl.RowsUsed, [true(3,1); false; true(8,1)]); %!test %! ## With no missing value RowsUsed is empty, as it is for every learner %! x = linspace (0, 1, 12)'; %! Mdl = RegressionGP (x, cos (3*x)); %! assert_equal (Mdl.RowsUsed, []); %!test %! ## Observation weights default to one apiece %! x = linspace (0, 1, 12)'; %! Mdl = RegressionGP (x, cos (3*x)); %! assert_equal (Mdl.W, ones (12, 1)); %!test %! ## A supplied covariance function is used, and reproduces the built-in one %! ## it imitates %! x = linspace (0, 1, 15)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! kfcn = @(a, b, th) th(2)^2 * exp (-0.5 * ((a - b') / th(1)) .^ 2); %! M1 = RegressionGP (x, y, 'KernelFunction', kfcn, ... %! 'KernelParameters', [0.4; 1.2], 'FitMethod', 'none'); %! M2 = RegressionGP (x, y, 'KernelFunction', 'squaredexponential', ... %! 'KernelParameters', [0.4; 1.2], 'FitMethod', 'none'); %! assert_equal (predict (M1, x), predict (M2, x), 1e-12); ## Test input validation for the constructor %!error RegressionGP (ones (5, 2)) %!error ... %! RegressionGP (ones (5, 2), ones (4, 1)) %!error ... %! RegressionGP ('a', ones (5, 1)) %!error ... %! RegressionGP (ones (5, 2), 'a') %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'Standardize') %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'bogus', 1) %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'KernelFunction', 5) %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'KernelFunction', 'bogus') %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'KernelParameters', [-1, 2]) %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'BasisFunction', 5) %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'BasisFunction', 'bogus') %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'Sigma', -1) %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'ConstantSigma', 5) %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'SigmaLowerBound', 0) %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'FitMethod', 'fic') %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'FitMethod', 'bogus') %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'PredictMethod', 'bcd') %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'Optimizer', 'fmincon') %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'Optimizer', 'bogus') %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'Standardize', 5) %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'PredictorNames', {'a'}) %!error ... %! RegressionGP (ones (5, 2), ones (5, 1), 'ResponseName', 5) ## Test input validation for the predict method %!error ... %! predict (RegressionGP (ones (5, 2), ones (5, 1))) %!error ... %! predict (RegressionGP (ones (5, 2), ones (5, 1)), []) %!error ... %! predict (RegressionGP (ones (5, 2), ones (5, 1)), ones (3, 3)) %!error ... %! predict (RegressionGP (ones (5, 2), ones (5, 1)), ones (3, 2), 'Alpha', 2) %!error ... %! predict (RegressionGP (ones (5, 2), ones (5, 1)), ones (3, 2), 'bogus', 1) ## Test input validation for the loss method %!error ... %! loss (RegressionGP (ones (5, 2), ones (5, 1)), ones (3, 2)) %!error ... %! loss (RegressionGP (ones (5, 2), ones (5, 1)), ones (3, 2), ... %! ones (3, 1), 'LossFun', 'bogus') %!error ... %! loss (RegressionGP (ones (5, 2), ones (5, 1)), ones (3, 2), ... %! ones (3, 1), 'bogus', 1) ## The two MATLAB-compatibility properties are declared and stay empty, the ## exact method selecting no active set and using no block coordinate descent. %!test %! load fisheriris %! Mdl = fitrgp (meas(:,1:3), meas(:,4)); %! assert_equal (isempty (Mdl.ActiveSetHistory), true); %! assert_equal (isempty (Mdl.BCDInformation), true); %! fname = tempname (); %! unwind_protect %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! assert_equal (isempty (M2.ActiveSetHistory), true); %! assert_equal (isempty (M2.BCDInformation), true); %! unwind_protect_cleanup %! if (exist (fname, 'file')) %! delete (fname); %! endif %! end_unwind_protect ## Test input validation for the crossval method %!error ... %! crossval (RegressionGP (ones (10, 2), ones (10, 1)), 'KFold', 1) %!error ... %! crossval (RegressionGP (ones (10, 2), ones (10, 1)), 'Holdout', 2) %!error ... %! crossval (RegressionGP (ones (10, 2), ones (10, 1)), 'Leaveout', 1) %!error ... %! crossval (RegressionGP (ones (10, 2), ones (10, 1)), 'CVPartition', 1) %!error ... %! crossval (RegressionGP (ones (10, 2), ones (10, 1)), 'KFold', 3, ... %! 'Holdout', 0.2) %!error ... %! crossval (RegressionGP (ones (10, 2), ones (10, 1)), 'bogus', 1) ## HyperparameterOptimizationResults is declared for MATLAB compatibility and ## stays empty, this class running no search over its hyperparameters. %!test %! load fisheriris %! Mdl = fitrgp (meas(:,1:3), meas(:,4)); %! assert_equal (isempty (Mdl.HyperparameterOptimizationResults), true); ## ModelParameters records the fit as it was asked for. The field list and ## its order are MATLAB's, less the approximate-fitting machinery this class ## does not implement. %!test %! load fisheriris %! Mdl = fitrgp (meas(:,2:4), meas(:,1)); %! assert_equal (fieldnames (Mdl.ModelParameters)', {'KernelFunction', ... %! 'KernelParameters', 'BasisFunction', 'Beta', 'Sigma', 'FitMethod', ... %! 'PredictMethod', 'ActiveSetSize', 'ActiveSetMethod', 'Standardize', ... %! 'Optimizer', 'ConstantSigma', 'SigmaLowerBound', 'Version', 'Method', ... %! 'Type'}); %!test %! load fisheriris %! MP = fitrgp (meas(:,2:4), meas(:,1)).ModelParameters; %! assert_equal (MP.Beta, 0); %! assert_equal (isempty (MP.Sigma), true); %! assert_equal (isempty (MP.KernelParameters), true); %! assert_equal (MP.ActiveSetSize, 150); %! assert_equal (MP.ActiveSetMethod, 'Random'); ## The three starting values are what the caller gave, not what the fit ## found, which the Beta and Sigma properties carry instead. %!test %! load fisheriris %! Mdl = fitrgp (meas(:,2:4), meas(:,1), 'Beta', 3, 'Sigma', 0.7, ... %! 'KernelParameters', [2; 0.5]); %! assert_equal (Mdl.ModelParameters.Beta, 3); %! assert_equal (Mdl.ModelParameters.Sigma, 0.7); %! assert_equal (Mdl.ModelParameters.KernelParameters, [2; 0.5]); %! assert_equal (abs (Mdl.Beta - 3) > 1, true); %! assert_equal (abs (Mdl.Sigma - 0.7) > 0.1, true); ## Beta starts at a zero for every column the basis contributes, so a linear ## basis over three predictors starts at four of them. %!test %! load fisheriris %! MP = fitrgp (meas(:,2:4), meas(:,1), ... %! 'BasisFunction', 'linear').ModelParameters; %! assert_equal (MP.Beta, zeros (4, 1)); ## SigmaLowerBound is the one field reported as resolved rather than as ## given, MATLAB keeping it inside an Options structure this class has not. %!test %! load fisheriris %! MP = fitrgp (meas(:,2:4), meas(:,1)).ModelParameters; %! assert_equal (MP.SigmaLowerBound > 0, true); %! assert_equal (MP.Version, 1); %! assert_equal (MP.Method, 'GP'); %! assert_equal (MP.Type, 'regression'); ## Every documented response transform reaches the response that is reported. %!test %! load fisheriris %! Mdl = fitrgp (meas(:,2:4), meas(:,1)); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! T = {'identity', @(x) x; 'exp', @(x) exp (x); 'log', @(x) log (x)}; %! for i = 1:rows (T) %! Mdl.ResponseTransform = T{i,1}; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, T{i,2}(raw), 1e-12); %! endfor ## A function handle is taken as given and applied to the response. %!test %! load fisheriris %! Mdl = fitrgp (meas(:,2:4), meas(:,1)); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! Mdl.ResponseTransform = @(x) x .^ 2; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, raw .^ 2, 1e-12); statistics-release-1.9.2/inst/Supervised_Learning/RegressionKernel.m000066400000000000000000001457761524624707500257570ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftp {statistics} RegressionKernel ## ## Gaussian kernel regression model for large data. ## ## A @qcode{RegressionKernel} object maps the predictors into a randomized ## feature space whose inner product approximates a Gaussian kernel, and then ## fits a linear model there. A kernel regression is therefore as nonlinear ## as a support vector machine with a Gaussian kernel, while costing what a ## linear fit costs: nothing of size @math{NxN} is ever formed. ## ## The expansion is the random Fourier basis of Rahimi and Recht, drawn once ## when the model is fitted and kept with it, so @code{predict} maps new data ## through the same basis. MATLAB approximates the same kernel by the ## Fastfood construction, which reaches the same distribution more cheaply; ## the two are interchangeable in distribution but not draw by draw, and the ## draws come from different generators in any case, so the predictions of a ## model fitted here and one fitted in MATLAB differ even from the same seed. ## What does not differ is what they estimate. ## ## Like @qcode{RegressionLinear} the object holds no copy of the training ## data. It does hold the basis and the coefficients, so it is bounded by ## the number of expansion dimensions rather than by the number of ## observations. ## ## Create a @qcode{RegressionKernel} object with @code{fitrkernel}. ## ## @seealso{fitrkernel, RegressionLinear, RegressionSVM} ## @end deftp classdef RegressionKernel properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} Epsilon ## ## Half the width of the epsilon-insensitive band ## ## A nonnegative scalar for a support vector machine, and empty for a ## least squares fit, which has no such band. It defaults to the ## interquartile range of the response over 13.49, an estimate of its ## standard deviation, or to @qcode{0.1} when that range is zero. This ## property is read-only. ## ## @end deftp Epsilon = []; ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} BoxConstraint ## ## Box constraint of the support vector machine ## ## A positive scalar. It is the reciprocal of the product of ## @qcode{Lambda} and the number of observations, so setting either of ## the two in the constructor fixes the other, and giving both is an ## error. This property is read-only. ## ## @end deftp BoxConstraint = 1; endproperties properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} ResponseTransform ## ## Transformation applied to the predicted response ## ## A character vector, or the text of the function handle that was ## supplied. Assigning to it accepts either. ## ## @end deftp ResponseTransform = 'none'; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} PredictorNames ## ## Names of the predictors ## ## A cell array of character vectors with one name per column of the ## training data, defaulting to @qcode{'x1'}, @qcode{'x2'} and so on. ## This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A row vector of column indices, empty when every predictor is ## numeric. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} ResponseName ## ## Name of the response ## ## A character vector, defaulting to @qcode{'Y'}. This property is ## read-only. ## ## @end deftp ResponseName = 'Y'; ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} ExpandedPredictorNames ## ## Names of the predictors as the fit saw them ## ## A cell array of character vectors. These name the original ## predictors, not the expansion dimensions, which have no names. This ## property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} NumExpansionDimensions ## ## Number of dimensions of the expanded space ## ## A positive integer scalar. It defaults to ## @code{2 .^ ceil (min (log2 (@var{p}) + 5, 15))} for @var{p} ## predictors, so four predictors give 128 dimensions. More dimensions ## approximate the kernel more closely and cost proportionally more. ## This property is read-only. ## ## @end deftp NumExpansionDimensions = []; ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} FittedLoss ## ## Loss function the fit minimized ## ## @qcode{'epsiloninsensitive'} for a support vector machine and ## @qcode{'mse'} for a least squares fit. This property is read-only. ## ## @end deftp FittedLoss = 'epsiloninsensitive'; ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} Lambda ## ## Regularization strength ## ## A nonnegative scalar, the reciprocal of the product of ## @qcode{BoxConstraint} and the number of observations. This property ## is read-only. ## ## @end deftp Lambda = []; ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} ModelParameters ## ## Fitting options, as they were given ## ## A structure holding every parameter of the fit, with the ## @qcode{'auto'} values as they were given rather than as they were ## resolved. This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} Regularization ## ## Penalty on the coefficients ## ## Always @qcode{'ridge (L2)'}: a kernel model fits in the expanded ## space, where a lasso penalty has nothing to select. This property is ## read-only. ## ## @end deftp Regularization = 'ridge (L2)'; ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} KernelScale ## ## Scale of the Gaussian kernel ## ## A positive scalar dividing every predictor before the expansion, so a ## larger scale makes the kernel wider and the fit smoother. This ## property is read-only. ## ## @end deftp KernelScale = 1; ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} Learner ## ## Linear model fitted in the expanded space ## ## Either @qcode{'svm'} or @qcode{'leastsquares'}. This property is ## read-only. ## ## @end deftp Learner = 'svm'; ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} Mu ## ## Predictor means used to standardize ## ## A row vector with one element per predictor, or empty when the model ## was fitted without standardizing. This property is read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {RegressionKernel} {property} Sigma ## ## Predictor standard deviations used to standardize ## ## A row vector with one element per predictor, or empty when the model ## was fitted without standardizing. This property is read-only. ## ## @end deftp Sigma = []; endproperties properties (GetAccess = public, SetAccess = protected, Hidden) ## The callable behind ResponseTransform. RTfun = @(y) y; ## The random basis and the coefficients fitted in the space it spans. ## MATLAB keeps all three out of sight, so they are hidden here too, but ## they are the model: without the basis the coefficients index nothing. Basis_ = []; Beta_ = []; Bias_ = []; ## Number of predictors and of observations the fit saw, neither of them ## a property of MATLAB's class. NumPredictors_ = []; NumObservations_ = []; ## What the fit reported, so that fitrkernel can hand it back as its ## second output. FitInfo_ = []; endproperties methods (Access = public) ## -*- texinfo -*- ## @deftypefn {RegressionKernel} {@var{obj} =} RegressionKernel (@var{X}, @var{Y}) ## @deftypefnx {RegressionKernel} {@var{obj} =} RegressionKernel (@dots{}, @var{name}, @var{value}) ## ## Fit a Gaussian kernel regression model. ## ## @code{@var{obj} = RegressionKernel (@var{X}, @var{Y})} fits a support ## vector machine in a randomized Gaussian kernel space to the @math{NxP} ## predictor matrix @var{X} and the @math{Nx1} continuous response ## @var{Y}. ## ## @code{@var{obj} = RegressionKernel (@dots{}, @var{name}, @var{value})} ## takes the following @qcode{Name-Value} pairs. ## ## @multitable @columnfractions 0.28 0.72 ## @headitem Name @tab Value ## ## @item @qcode{'Learner'} @tab @qcode{'svm'}, the default, or ## @qcode{'leastsquares'}. ## ## @item @qcode{'Epsilon'} @tab Half the width of the insensitive band, ## a nonnegative scalar or @qcode{'auto'}, which is the interquartile ## range of @var{Y} over 13.49. It applies to a support vector machine ## alone. ## ## @item @qcode{'NumExpansionDimensions'} @tab @qcode{'auto'}, the ## default, or a positive integer. ## ## @item @qcode{'KernelScale'} @tab @qcode{1} by default, a positive ## scalar, or @qcode{'auto'}, which takes the median distance between the ## observations. ## ## @item @qcode{'Lambda'} @tab @qcode{'auto'}, the default, which is the ## reciprocal of the number of observations, or a nonnegative scalar. It ## cannot be given beside @qcode{'BoxConstraint'}. ## ## @item @qcode{'BoxConstraint'} @tab A positive scalar, @qcode{1} by ## default. It applies to a support vector machine alone. ## ## @item @qcode{'Standardize'} @tab Whether to centre and scale the ## predictors, false by default. ## ## @item @qcode{'BetaTolerance'} @tab Relative tolerance on the ## coefficients, @qcode{1e-4} by default. ## ## @item @qcode{'GradientTolerance'} @tab Absolute tolerance on the ## gradient's infinity norm, @qcode{1e-6} by default. ## ## @item @qcode{'IterationLimit'} @tab Largest number of iterations, ## @qcode{1000} by default. ## ## @item @qcode{'HessianHistorySize'} @tab Number of curvature pairs the ## solver keeps, @qcode{15} by default. ## ## @item @qcode{'BlockSize'} @tab Memory the expansion may occupy, in ## megabytes, @qcode{4e3} by default. ## ## @item @qcode{'ResponseTransform'} @tab A transformation applied to the ## predicted response, named or given as a function handle. ## ## @item @qcode{'Weights'} @tab One nonnegative weight per observation. ## ## @item @qcode{'PredictorNames'} @tab One name per predictor. ## ## @item @qcode{'ResponseName'} @tab A name for the response. ## ## @item @qcode{'CategoricalPredictors'} @tab Indices of the categorical ## predictors. ## @end multitable ## ## The fit is always by limited-memory BFGS, the only solver MATLAB ## offers a kernel model, and always under a ridge penalty. ## ## @seealso{fitrkernel, RegressionLinear} ## @end deftypefn function this = RegressionKernel (X, Y, varargin) if (nargin < 2) error ("RegressionKernel: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("RegressionKernel: optional arguments must be", ... " given in Name-Value pairs.")); endif ## Defaults Learner = 'svm'; EpsilonIn = 'auto'; EpsilonGiven = false; NumDimsIn = 'auto'; KernelScaleIn = 1; LambdaIn = 'auto'; BoxConstraint = 1; BoxGiven = false; LambdaGiven = false; Standardize = false; BetaTolerance = 1e-4; GradientTolerance = 1e-6; IterationLimit = 1000; HessianHistorySize = 15; BlockSize = 4e3; Verbose = 0; ResponseTransform = 'none'; Weights = []; PredictorNames = {}; ResponseName = 'Y'; CategoricalPredictors = []; while (numel (varargin) > 0) switch (lower (varargin{1})) case 'learner' Learner = varargin{2}; if (! (ischar (Learner) && any (strcmpi (Learner, {'svm', 'leastsquares'})))) error (strcat ("RegressionKernel: 'Learner' must be either", ... " 'svm' or 'leastsquares'.")); endif Learner = lower (Learner); case 'epsilon' EpsilonIn = varargin{2}; EpsilonGiven = true; if (! ((ischar (EpsilonIn) && strcmpi (EpsilonIn, 'auto')) || (isnumeric (EpsilonIn) && isscalar (EpsilonIn) && isreal (EpsilonIn) && EpsilonIn >= 0))) error (strcat ("RegressionKernel: 'Epsilon' must be 'auto'", ... " or a nonnegative scalar.")); endif case 'numexpansiondimensions' NumDimsIn = varargin{2}; if (! ((ischar (NumDimsIn) && strcmpi (NumDimsIn, 'auto')) || (isnumeric (NumDimsIn) && isscalar (NumDimsIn) && isreal (NumDimsIn) && NumDimsIn > 0 && fix (NumDimsIn) == NumDimsIn))) error (strcat ("RegressionKernel:", ... " 'NumExpansionDimensions' must be 'auto'", ... " or a positive integer scalar.")); endif case 'kernelscale' KernelScaleIn = varargin{2}; if (! ((ischar (KernelScaleIn) && strcmpi (KernelScaleIn, 'auto')) || (isnumeric (KernelScaleIn) && isscalar (KernelScaleIn) && isreal (KernelScaleIn) && KernelScaleIn > 0))) error (strcat ("RegressionKernel: 'KernelScale' must be", ... " 'auto' or a positive scalar.")); endif case 'lambda' LambdaIn = varargin{2}; LambdaGiven = true; if (! ((ischar (LambdaIn) && strcmpi (LambdaIn, 'auto')) || (isnumeric (LambdaIn) && isscalar (LambdaIn) && isreal (LambdaIn) && LambdaIn >= 0 && isfinite (LambdaIn)))) error (strcat ("RegressionKernel: 'Lambda' must be 'auto'", ... " or a nonnegative finite scalar.")); endif case 'boxconstraint' BoxConstraint = varargin{2}; BoxGiven = true; if (! (isnumeric (BoxConstraint) && isscalar (BoxConstraint) && isreal (BoxConstraint) && BoxConstraint > 0 && isfinite (BoxConstraint))) error (strcat ("RegressionKernel: 'BoxConstraint' must be", ... " a positive finite scalar.")); endif case 'standardize' Standardize = varargin{2}; if (! (islogical (Standardize) || (isnumeric (Standardize) && isscalar (Standardize) && any (Standardize == [0, 1])))) error (strcat ("RegressionKernel: 'Standardize' must be", ... " either true or false.")); endif Standardize = logical (Standardize); case 'betatolerance' BetaTolerance = varargin{2}; if (! (isnumeric (BetaTolerance) && isscalar (BetaTolerance) && isreal (BetaTolerance) && BetaTolerance >= 0)) error (strcat ("RegressionKernel: 'BetaTolerance' must be", ... " a nonnegative scalar.")); endif case 'gradienttolerance' GradientTolerance = varargin{2}; if (! (isnumeric (GradientTolerance) && isscalar (GradientTolerance) && isreal (GradientTolerance) && GradientTolerance >= 0)) error (strcat ("RegressionKernel: 'GradientTolerance' must", ... " be a nonnegative scalar.")); endif case 'iterationlimit' IterationLimit = varargin{2}; if (! (isnumeric (IterationLimit) && isscalar (IterationLimit) && isreal (IterationLimit) && IterationLimit > 0 && fix (IterationLimit) == IterationLimit)) error (strcat ("RegressionKernel: 'IterationLimit' must be", ... " a positive integer scalar.")); endif case 'hessianhistorysize' HessianHistorySize = varargin{2}; if (! (isnumeric (HessianHistorySize) && isscalar (HessianHistorySize) && isreal (HessianHistorySize) && HessianHistorySize > 0 && fix (HessianHistorySize) == HessianHistorySize)) error (strcat ("RegressionKernel: 'HessianHistorySize'", ... " must be a positive integer scalar.")); endif case 'blocksize' BlockSize = varargin{2}; if (! (isnumeric (BlockSize) && isscalar (BlockSize) && isreal (BlockSize) && BlockSize > 0)) error (strcat ("RegressionKernel: 'BlockSize' must be a", ... " positive scalar.")); endif case 'verbose' Verbose = varargin{2}; if (! (isnumeric (Verbose) && isscalar (Verbose) && isreal (Verbose) && any (Verbose == [0, 1]))) error ("RegressionKernel: 'Verbose' must be 0 or 1."); endif case 'responsetransform' ResponseTransform = varargin{2}; case 'weights' Weights = varargin{2}; if (! (isnumeric (Weights) && isreal (Weights) && isvector (Weights) && all (Weights >= 0))) error (strcat ("RegressionKernel: 'Weights' must be a", ... " vector of nonnegative values.")); endif case 'predictornames' PredictorNames = varargin{2}; if (! (iscellstr (PredictorNames) && isvector (PredictorNames))) error (strcat ("RegressionKernel: 'PredictorNames' must be", ... " a cell array of character vectors.")); endif case 'responsename' ResponseName = varargin{2}; if (! (ischar (ResponseName) && isrow (ResponseName))) error (strcat ("RegressionKernel: 'ResponseName' must be a", ... " character vector.")); endif case 'categoricalpredictors' CategoricalPredictors = varargin{2}; if (! ((isnumeric (CategoricalPredictors) && isvector (CategoricalPredictors) && all (fix (CategoricalPredictors) == CategoricalPredictors) && all (CategoricalPredictors > 0)) || islogical (CategoricalPredictors) || isempty (CategoricalPredictors))) error (strcat ("RegressionKernel:", ... " 'CategoricalPredictors' must be a vector", ... " of positive integers or a logical vector.")); endif otherwise error (strcat ("RegressionKernel: invalid parameter name in", ... " optional pair arguments.")); endswitch varargin(1:2) = []; endwhile if (LambdaGiven && BoxGiven) error (strcat ("RegressionKernel: 'Lambda' and 'BoxConstraint'", ... " cannot be given together, one being the", ... " reciprocal of the other times the number of", ... " observations.")); endif if (BoxGiven && strcmp (Learner, 'leastsquares')) error (strcat ("RegressionKernel: 'BoxConstraint' applies to a", ... " support vector machine only.")); endif ## Validate the data and resolve the weights. The four linear and ## kernel regression models share that opening, so it lives in one ## place. F = regFrame (X, Y, Weights, 'RegressionKernel'); X = F.X; Y = F.Y; W = F.W; n = F.n; p = F.p; ## Epsilon belongs to the insensitive band, so it means nothing to a ## least squares fit and is refused there rather than ignored. if (strcmp (Learner, 'leastsquares')) if (EpsilonGiven) error (strcat ("RegressionKernel: 'Epsilon' applies to a", ... " support vector machine only.")); endif Epsilon = []; elseif (ischar (EpsilonIn)) r = iqr (Y); if (r == 0) Epsilon = 0.1; else Epsilon = r / 13.49; endif else Epsilon = EpsilonIn; endif ## Standardize before anything is measured off the predictors if (Standardize) this.Mu = mean (X, 1); this.Sigma = std (X, 0, 1); this.Sigma(this.Sigma == 0) = 1; X = (X - this.Mu) ./ this.Sigma; endif ## Resolve the expansion if (ischar (NumDimsIn)) m = 2 .^ ceil (min (log2 (p) + 5, 15)); else m = NumDimsIn; endif if (ischar (KernelScaleIn)) sigma = autoKernelScale (X); else sigma = KernelScaleIn; endif ## Resolve Lambda and the box constraint from whichever was given if (BoxGiven) Lambda = 1 / (n * BoxConstraint); elseif (LambdaGiven && ! ischar (LambdaIn)) Lambda = LambdaIn; BoxConstraint = 1 / (n * Lambda); else Lambda = 1 / n; BoxConstraint = 1 / (n * Lambda); endif ## Draw the basis, map the data through it, and fit a linear model ## there. basis = kernelBasis (p, m, sigma); T = kernelExpand (X, basis); P = struct (); P.Learner = Learner; P.LossFunction = 'epsiloninsensitive'; if (strcmp (Learner, 'leastsquares')) P.LossFunction = 'mse'; endif P.Epsilon = Epsilon; P.Regularization = 'ridge'; P.Lambda = Lambda; P.Solver = 'lbfgs'; P.FitBias = true; P.PostFitBias = false; P.BetaTolerance = BetaTolerance; P.GradientTolerance = GradientTolerance; P.DeltaGradientTolerance = []; P.IterationLimit = IterationLimit; P.PassLimit = 1; P.BatchSize = 10; P.BatchLimit = []; P.LearnRate = 1; P.OptimizeLearnRate = true; P.TruncationPeriod = 10; P.NumCheckConvergence = 5; P.HessianHistorySize = HessianHistorySize; P.InitialBeta = zeros (m, 1); P.InitialBias = sum (W .* Y); [Beta, Bias, S] = linearSolve (T, Y, W, P); ## Fill in the model this.Epsilon = Epsilon; this.BoxConstraint = BoxConstraint; this.PredictorNames = PredictorNames; if (isempty (this.PredictorNames)) this.PredictorNames = ... arrayfun (@(k) sprintf ("x%d", k), 1:p, ... 'UniformOutput', false); elseif (numel (this.PredictorNames) != p) error (strcat ("RegressionKernel: 'PredictorNames' must have one", ... " name per predictor.")); endif this.ExpandedPredictorNames = this.PredictorNames; this.CategoricalPredictors = CategoricalPredictors; this.ResponseName = ResponseName; this.NumExpansionDimensions = m; this.FittedLoss = P.LossFunction; this.Lambda = Lambda; this.KernelScale = sigma; this.Learner = Learner; this.ResponseTransform = ResponseTransform; this.Basis_ = basis; this.Beta_ = Beta; this.Bias_ = Bias; this.NumPredictors_ = p; this.NumObservations_ = n; this.ModelParameters = struct ('BetaTolerance', BetaTolerance, ... 'BlockSize', BlockSize, 'BoxConstraint', BoxConstraint, ... 'Epsilon', EpsilonIn, 'NumExpansionDimensions', NumDimsIn, ... 'GradientTolerance', GradientTolerance, ... 'HessianHistorySize', HessianHistorySize, ... 'IterationLimit', IterationLimit, 'KernelScale', KernelScaleIn, ... 'Lambda', LambdaIn, 'Learner', Learner, ... 'LossFunction', P.LossFunction, 'Stream', [], ... 'VerbosityLevel', Verbose, 'StandardizeData', Standardize, ... 'Version', 1, 'Method', 'Kernel', 'Type', 'regression'); this.FitInfo_ = kernelFitInfo (S, P.LossFunction, Lambda, ... BetaTolerance, GradientTolerance); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {RegressionKernel} {@var{yFit} =} predict (@var{obj}, @var{XC}) ## ## Predict the response of new observations. ## ## @code{@var{yFit} = predict (@var{obj}, @var{XC})} maps each row of ## @var{XC} through the model's own random basis and returns the ## predicted response, with @qcode{ResponseTransform} applied. ## ## @end deftypefn function yFit = predict (this, XC) if (nargin < 2) error ("RegressionKernel.predict: too few input arguments."); endif if (isempty (XC)) error ("RegressionKernel.predict: XC is empty."); endif if (! (isnumeric (XC) && isreal (XC) && ismatrix (XC))) error ("RegressionKernel.predict: invalid values in XC."); endif if (columns (XC) != this.NumPredictors_) error (strcat ("RegressionKernel.predict: XC must have the same", ... " number of predictors as the trained model.")); endif if (! isempty (this.Mu)) XC = (XC - this.Mu) ./ this.Sigma; endif yFit = this.RTfun (kernelExpand (XC, this.Basis_) * this.Beta_ ... + this.Bias_); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionKernel} {@var{l} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {RegressionKernel} {@var{l} =} loss (@dots{}, @var{name}, @var{value}) ## ## Regression loss on new data. ## ## @code{@var{l} = loss (@var{obj}, @var{X}, @var{Y})} returns the mean ## squared error. ## ## @code{@var{l} = loss (@dots{}, @var{name}, @var{value})} takes ## @qcode{'LossFun'}, either @qcode{'mse'} or ## @qcode{'epsiloninsensitive'}, and @qcode{'Weights'}. The ## epsilon-insensitive loss needs a band to be insensitive within, so it ## is offered by a support vector machine alone. ## ## @end deftypefn function l = loss (this, X, Y, varargin) if (nargin < 3) error ("RegressionKernel.loss: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("RegressionKernel.loss: optional arguments must", ... " be given in Name-Value pairs.")); endif LossFun = 'mse'; Weights = []; while (numel (varargin) > 0) switch (lower (varargin{1})) case 'lossfun' LossFun = varargin{2}; if (! (ischar (LossFun) && any (strcmpi (LossFun, ... {'mse', 'epsiloninsensitive'})))) error (strcat ("RegressionKernel.loss: 'LossFun' must be", ... " either 'mse' or 'epsiloninsensitive'.")); endif LossFun = lower (LossFun); case 'weights' Weights = varargin{2}; if (! (isnumeric (Weights) && isreal (Weights) && isvector (Weights) && all (Weights >= 0))) error (strcat ("RegressionKernel.loss: 'Weights' must be a", ... " vector of nonnegative values.")); endif otherwise error (strcat ("RegressionKernel.loss: invalid parameter", ... " name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile if (strcmp (LossFun, 'epsiloninsensitive') && isempty (this.Epsilon)) error (strcat ("RegressionKernel.loss: the", ... " 'epsiloninsensitive' loss applies to a support", ... " vector machine only.")); endif Y = Y(:); if (! (isnumeric (Y) && isreal (Y))) error ("RegressionKernel.loss: invalid values in Y."); endif if (rows (X) != numel (Y)) error (strcat ("RegressionKernel.loss: number of rows in X and Y", ... " must be equal.")); endif if (isempty (Weights)) w = ones (numel (Y), 1); else w = Weights(:); if (numel (w) != numel (Y)) error (strcat ("RegressionKernel.loss: 'Weights' must have one", ... " element per observation.")); endif endif w = w / sum (w); r = Y - predict (this, X); if (strcmp (LossFun, 'mse')) l = sum (w .* (r .^ 2)); else l = sum (w .* max (0, abs (r) - this.Epsilon)); endif endfunction ## -*- texinfo -*- ## @deftypefn {RegressionKernel} {@var{obj} =} resume (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {RegressionKernel} {@var{obj} =} resume (@dots{}, @var{name}, @var{value}) ## ## Continue fitting a kernel regression model. ## ## @code{@var{obj} = resume (@var{obj}, @var{X}, @var{Y})} restarts the ## optimization from the coefficients the model already carries, through ## the basis it already holds. It takes @qcode{'BetaTolerance'}, ## @qcode{'GradientTolerance'} and @qcode{'IterationLimit'}, each ## defaulting to what the model was fitted with, and @qcode{'Weights'}. ## ## @var{X} and @var{Y} must be the data the model was fitted to; the ## object keeps no copy of them, which is what makes it small. Neither ## does it keep the observation weights, so a model fitted with ## @qcode{'Weights'} must be given them again here or it will resume ## against uniform ones. MATLAB behaves the same way: measured on ## R2024a, resuming a weighted fit without passing the weights back ## reaches the objective of the @emph{unweighted} fit. ## ## @end deftypefn function this = resume (this, X, Y, varargin) if (nargin < 3) error ("RegressionKernel.resume: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("RegressionKernel.resume: optional arguments must", ... " be given in Name-Value pairs.")); endif BetaTolerance = this.ModelParameters.BetaTolerance; GradientTolerance = this.ModelParameters.GradientTolerance; IterationLimit = this.ModelParameters.IterationLimit; Weights = []; while (numel (varargin) > 0) switch (lower (varargin{1})) case 'weights' Weights = varargin{2}; if (! (isnumeric (Weights) && isreal (Weights) && isvector (Weights) && all (Weights >= 0))) error (strcat ("RegressionKernel.resume: 'Weights' must", ... " be a vector of nonnegative values.")); endif case 'betatolerance' BetaTolerance = varargin{2}; if (! (isnumeric (BetaTolerance) && isscalar (BetaTolerance) && isreal (BetaTolerance) && BetaTolerance >= 0)) error (strcat ("RegressionKernel.resume: 'BetaTolerance'", ... " must be a nonnegative scalar.")); endif case 'gradienttolerance' GradientTolerance = varargin{2}; if (! (isnumeric (GradientTolerance) && isscalar (GradientTolerance) && isreal (GradientTolerance) && GradientTolerance >= 0)) error (strcat ("RegressionKernel.resume:", ... " 'GradientTolerance' must be a", ... " nonnegative scalar.")); endif case 'iterationlimit' IterationLimit = varargin{2}; if (! (isnumeric (IterationLimit) && isscalar (IterationLimit) && isreal (IterationLimit) && IterationLimit > 0 && fix (IterationLimit) == IterationLimit)) error (strcat ("RegressionKernel.resume:", ... " 'IterationLimit' must be a positive", ... " integer scalar.")); endif otherwise error (strcat ("RegressionKernel.resume: invalid parameter", ... " name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile if (! (isnumeric (X) && isreal (X) && ismatrix (X))) error ("RegressionKernel.resume: invalid values in X."); endif if (columns (X) != this.NumPredictors_) error (strcat ("RegressionKernel.resume: X must have the same", ... " number of predictors as the trained model.")); endif Y = Y(:); if (! (isnumeric (Y) && isreal (Y))) error ("RegressionKernel.resume: invalid values in Y."); endif if (rows (X) != numel (Y)) error (strcat ("RegressionKernel.resume: number of rows in X and", ... " Y must be equal.")); endif if (! isempty (this.Mu)) X = (X - this.Mu) ./ this.Sigma; endif T = kernelExpand (X, this.Basis_); if (isempty (Weights)) W = ones (numel (Y), 1); else W = Weights(:); if (numel (W) != numel (Y)) error (strcat ("RegressionKernel.resume: 'Weights' must have", ... " one element per observation.")); endif endif W = W / sum (W); P = struct (); P.Learner = this.Learner; P.LossFunction = this.FittedLoss; P.Epsilon = this.Epsilon; P.Regularization = 'ridge'; P.Lambda = this.Lambda; P.Solver = 'lbfgs'; P.FitBias = true; P.PostFitBias = false; P.BetaTolerance = BetaTolerance; P.GradientTolerance = GradientTolerance; P.DeltaGradientTolerance = []; P.IterationLimit = IterationLimit; P.PassLimit = 1; P.BatchSize = 10; P.BatchLimit = []; P.LearnRate = 1; P.OptimizeLearnRate = true; P.TruncationPeriod = 10; P.NumCheckConvergence = 5; P.HessianHistorySize = this.ModelParameters.HessianHistorySize; P.InitialBeta = this.Beta_; P.InitialBias = this.Bias_; [Beta, Bias, S] = linearSolve (T, Y, W, P); this.Beta_ = Beta; this.Bias_ = Bias; this.ModelParameters.BetaTolerance = BetaTolerance; this.ModelParameters.GradientTolerance = GradientTolerance; this.ModelParameters.IterationLimit = IterationLimit; this.FitInfo_ = kernelFitInfo (S, this.FittedLoss, this.Lambda, ... BetaTolerance, GradientTolerance); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionKernel} {} savemodel (@var{obj}, @var{filename}) ## ## Save a kernel regression model to a file. ## ## @code{savemodel (@var{obj}, @var{filename})} saves the model ## @var{obj} into @var{filename} in a form @code{loadmodel} can read ## back, the random basis included. ## ## @end deftypefn function savemodel (obj, fname) classdef_name = 'RegressionKernel'; Epsilon = obj.Epsilon; BoxConstraint = obj.BoxConstraint; ResponseTransform = obj.ResponseTransform; PredictorNames = obj.PredictorNames; CategoricalPredictors = obj.CategoricalPredictors; ResponseName = obj.ResponseName; ExpandedPredictorNames = obj.ExpandedPredictorNames; NumExpansionDimensions = obj.NumExpansionDimensions; FittedLoss = obj.FittedLoss; Lambda = obj.Lambda; ModelParameters = obj.ModelParameters; Regularization = obj.Regularization; KernelScale = obj.KernelScale; Learner = obj.Learner; Mu = obj.Mu; Sigma = obj.Sigma; Basis_ = obj.Basis_; Beta_ = obj.Beta_; Bias_ = obj.Bias_; NumPredictors_ = obj.NumPredictors_; NumObservations_ = obj.NumObservations_; save ('-binary', fname, 'classdef_name', 'Epsilon', ... 'BoxConstraint', 'ResponseTransform', 'PredictorNames', ... 'CategoricalPredictors', 'ResponseName', ... 'ExpandedPredictorNames', 'NumExpansionDimensions', ... 'FittedLoss', 'Lambda', 'ModelParameters', 'Regularization', ... 'KernelScale', 'Learner', 'Mu', 'Sigma', 'Basis_', 'Beta_', ... 'Bias_', 'NumPredictors_', 'NumObservations_'); endfunction endmethods methods (Access = public, Hidden) function display (this) in_name = inputname (1); if (! isempty (in_name)) printf ('%s =\n', in_name); endif disp (this); endfunction function disp (this) printf ("\n RegressionKernel\n\n"); printf ("%+26s: '%s'\n", 'ResponseName', this.ResponseName); printf ("%+26s: '%s'\n", 'Learner', this.Learner); printf ("%+26s: %d\n", 'NumExpansionDimensions', ... this.NumExpansionDimensions); printf ("%+26s: %g\n", 'KernelScale', this.KernelScale); printf ("%+26s: %g\n", 'Lambda', this.Lambda); printf ("%+26s: %g\n", 'BoxConstraint', this.BoxConstraint); if (! isempty (this.Epsilon)) printf ("%+26s: %g\n", 'Epsilon', this.Epsilon); endif printf ("\n"); endfunction ## What the fit reported, which fitrkernel returns as its second output. function S = fitInfo_ (this) S = this.FitInfo_; endfunction ## Custom setter, so that assigning a name or a handle updates both the ## text the property reports and the callable predict uses. function this = set.ResponseTransform (this, val) [this.RTfun, this.ResponseTransform] = ... parseResponseTransform (val, 'RegressionKernel'); endfunction endmethods methods (Static, Hidden) function mdl = load_model (filename, data) mdl = RegressionKernel ([0; 1], [0; 1]); fields = fieldnames (data); for k = 1:numel (fields) mdl.(fields{k}) = data.(fields{k}); endfor endfunction endmethods endclassdef %!demo %! ## Fit fuel consumption through a randomized Gaussian kernel, which %! ## bends where a linear model cannot. %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! Mdl = RegressionKernel (X(ok,:), MPG(ok)) %! yFit = predict (Mdl, X(find (ok, 3),:)) %!demo %! ## Standardizing matters more here than for a linear fit, since the %! ## kernel measures one distance across predictors of every scale. %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! plain = RegressionKernel (X(ok,:), MPG(ok)); %! scaled = RegressionKernel (X(ok,:), MPG(ok), 'Standardize', true); %! plainLoss = loss (plain, X(ok,:), MPG(ok)) %! scaledLoss = loss (scaled, X(ok,:), MPG(ok)) %!shared X, Y %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! X = X(ok,:); %! Y = MPG(ok); %!test %! ## The model reports the surface MATLAB reports %! Mdl = RegressionKernel (X, Y); %! assert_equal (class (Mdl), 'RegressionKernel'); %! assert_equal (Mdl.Learner, 'svm'); %! assert_equal (Mdl.FittedLoss, 'epsiloninsensitive'); %! assert_equal (Mdl.Regularization, 'ridge (L2)'); %! assert_equal (Mdl.ResponseTransform, 'none'); %! assert_equal (Mdl.KernelScale, 1); %! assert_equal (Mdl.BoxConstraint, 1); %! assert_equal (Mdl.NumExpansionDimensions, 128); %! assert_equal (Mdl.Mu, []); %! assert_equal (Mdl.Sigma, []); %! assert_equal (Mdl.PredictorNames, {'x1', 'x2', 'x3', 'x4'}); %!test %! ## The properties are the ones MATLAB lists, in its order %! Mdl = RegressionKernel (X, Y); %! assert_equal (sort (properties (Mdl)), ... %! sort ({'Epsilon'; 'BoxConstraint'; 'ResponseTransform'; ... %! 'PredictorNames'; 'CategoricalPredictors'; ... %! 'ResponseName'; 'ExpandedPredictorNames'; ... %! 'NumExpansionDimensions'; 'FittedLoss'; 'Lambda'; ... %! 'ModelParameters'; 'Regularization'; 'KernelScale'; ... %! 'Learner'; 'Mu'; 'Sigma'})); %!test %! ## Epsilon defaults to the interquartile range over 13.49, as it does for %! ## the linear model %! Mdl = RegressionKernel (X, Y); %! assert_equal (Mdl.Epsilon, 0.926612305411416, 1e-12); %! assert_equal (Mdl.Epsilon, iqr (Y) / 13.49, 1e-15); %!test %! ## Least squares has no insensitive band at all %! Mdl = RegressionKernel (X, Y, 'Learner', 'leastsquares'); %! assert_equal (Mdl.Epsilon, []); %! assert_equal (Mdl.FittedLoss, 'mse'); %!test %! ## Lambda and the box constraint are reciprocal through the number of %! ## observations, and either one may be the one that is given %! Mb = RegressionKernel (X, Y, 'BoxConstraint', 4); %! assert_equal (Mb.Lambda, 1 / (93 * 4), 1e-15); %! Ml = RegressionKernel (X, Y, 'Lambda', 0.02); %! assert_equal (Ml.BoxConstraint, 1 / (93 * 0.02), 1e-12); %!test %! ## Lambda defaults to the reciprocal of the observations that were used %! Mdl = RegressionKernel (X, Y); %! assert_equal (Mdl.Lambda, 1 / 93, 1e-15); %!test %! ## The default expansion is MATLAB's, two to the power of five more than %! ## the base two logarithm of the predictors %! assert_equal (RegressionKernel (X(:,1:2), Y).NumExpansionDimensions, 64); %! assert_equal (RegressionKernel (X, Y, ... %! 'NumExpansionDimensions', 50).NumExpansionDimensions, 50); %!test %! ## An odd expansion cannot be paired throughout, and the dimension left %! ## over still comes back as a dimension %! Mdl = RegressionKernel (X, Y, 'NumExpansionDimensions', 51); %! assert_equal (Mdl.NumExpansionDimensions, 51); %! assert_equal (numel (predict (Mdl, X)), 93); %!test %! ## Standardizing records the means and deviations of the predictors %! Mdl = RegressionKernel (X, Y, 'Standardize', true); %! assert_equal (Mdl.Mu, mean (X), 1e-12); %! assert_equal (Mdl.Sigma, std (X), 1e-12); %!test %! ## A kernel fit follows the response it was given %! Mdl = RegressionKernel (X, Y, 'Learner', 'leastsquares', ... %! 'Standardize', true, 'Lambda', 1e-4); %! assert_equal (loss (Mdl, X, Y) < var (Y), true); %! assert_equal (corr (predict (Mdl, X), Y) > 0.5, true); %!test %! ## Predicting through the model's own basis gives the same answer every %! ## time it is asked %! Mdl = RegressionKernel (X, Y); %! assert_equal (predict (Mdl, X(1:10,:)), predict (Mdl, X(1:10,:))); %!test %! ## resume continues from the coefficients the model already holds, so it %! ## cannot leave the objective higher than it found it %! Mdl = RegressionKernel (X, Y, 'IterationLimit', 3); %! before = Mdl.FitInfo_.ObjectiveValue; %! Mdl = resume (Mdl, X, Y, 'IterationLimit', 500); %! assert_equal (class (Mdl), 'RegressionKernel'); %! assert_equal (Mdl.FitInfo_.ObjectiveValue <= before, true); %! assert_equal (Mdl.ModelParameters.IterationLimit, 500); %!test %! ## The fit information is MATLAB's kernel structure %! Mdl = RegressionKernel (X, Y); %! F = Mdl.FitInfo_; %! assert_equal (fieldnames (F), {'Solver'; 'LossFunction'; 'Lambda'; ... %! 'BetaTolerance'; 'GradientTolerance'; ... %! 'ObjectiveValue'; 'GradientMagnitude'; ... %! 'RelativeChangeInBeta'; 'FitTime'; ... %! 'History'}); %! assert_equal (F.Solver, 'LBFGS-fast'); %! assert_equal (F.LossFunction, 'epsiloninsensitive'); %!test %! ## A response transform reaches predict %! Mdl = RegressionKernel (X, Y, 'Learner', 'leastsquares'); %! plain = predict (Mdl, X(1:5,:)); %! Mdl.ResponseTransform = 'exp'; %! assert_equal (predict (Mdl, X(1:5,:)), exp (plain), 1e-12); %!test %! ## A row with a missing predictor or a missing response is dropped %! Xn = X; %! Xn(3,2) = NaN; %! Mdl = RegressionKernel (Xn, Y); %! assert_equal (Mdl.Lambda, 1 / 92, 1e-15); %!test %! ## A saved model reads back as the same model, the random basis included %! Mdl = RegressionKernel (X, Y); %! fname = tempname (); %! savemodel (Mdl, fname); %! Mnew = loadmodel (fname); %! delete (fname); %! assert_equal (class (Mnew), 'RegressionKernel'); %! assert_equal (predict (Mnew, X(1:5,:)), predict (Mdl, X(1:5,:))); %!test %! ## resume keeps no observation weights, because the model keeps no data: %! ## passing them back restores the weighted fit, and omitting them %! ## continues against uniform ones %! Mdl = RegressionKernel (X, Y, 'IterationLimit', 5, 'Weights', (1:93)'); %! kept = resume (Mdl, X, Y, 'IterationLimit', 400, 'Weights', (1:93)'); %! lost = resume (Mdl, X, Y, 'IterationLimit', 400); %! assert_equal (kept.FitInfo_.ObjectiveValue ... %! != lost.FitInfo_.ObjectiveValue, true); ## Test input validation %!error RegressionKernel (ones (5, 2)) %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'Learner') %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'Learner', 'logistic') %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'Epsilon', -1) %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'Learner', 'leastsquares', ... %! 'Epsilon', 1) %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'NumExpansionDimensions', 2.5) %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'KernelScale', 0) %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'Lambda', Inf) %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'BoxConstraint', -2) %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'Lambda', 0.1, ... %! 'BoxConstraint', 2) %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'Learner', 'leastsquares', ... %! 'BoxConstraint', 2) %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'Standardize', 'yes') %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'Verbose', 2) %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'Nonsense', 1) %!error RegressionKernel ({1, 2; 3, 4}, [1; 2]) %!error RegressionKernel ([], []) %!error RegressionKernel (ones (10, 2), {1, 2}) %!error ... %! RegressionKernel (ones (10, 2), ones (3, 1)) %!error ... %! RegressionKernel (ones (10, 2), ones (10, 1), 'Weights', ones (3, 1)) %!error ... %! predict (RegressionKernel (ones (10, 2), ones (10, 1)), []) %!error ... %! predict (RegressionKernel (ones (10, 2), ones (10, 1)), ones (3, 5)) %!error ... %! loss (RegressionKernel (ones (10, 2), ones (10, 1)), ones (10, 2), ... %! ones (10, 1), 'LossFun', 'hinge') %!error ... %! loss (RegressionKernel (ones (10, 2), ones (10, 1), 'Learner', ... %! 'leastsquares'), ones (10, 2), ones (10, 1), ... %! 'LossFun', 'epsiloninsensitive') %!error ... %! resume (RegressionKernel (ones (10, 2), ones (10, 1)), ones (10, 2)) %!error ... %! resume (RegressionKernel (ones (10, 2), ones (10, 1)), ones (10, 5), ... %! ones (10, 1)) %!error ... %! resume (RegressionKernel (ones (10, 2), ones (10, 1)), ones (10, 2), ... %! ones (10, 1), 'Nonsense', 1) %!error ... %! resume (RegressionKernel (ones (10, 2), ones (10, 1)), ones (10, 2), ... %! ones (10, 1), 'Weights', -1) ## Every documented response transform reaches the response that is reported. %!test %! load fisheriris %! Mdl = fitrkernel (meas(:,2:4), meas(:,1)); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! T = {'identity', @(x) x; 'exp', @(x) exp (x); 'log', @(x) log (x)}; %! for i = 1:rows (T) %! Mdl.ResponseTransform = T{i,1}; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, T{i,2}(raw), 1e-12); %! endfor ## A function handle is taken as given and applied to the response. %!test %! load fisheriris %! Mdl = fitrkernel (meas(:,2:4), meas(:,1)); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! Mdl.ResponseTransform = @(x) x .^ 2; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, raw .^ 2, 1e-12); statistics-release-1.9.2/inst/Supervised_Learning/RegressionLinear.m000066400000000000000000001614771524624707500257450ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftp {statistics} RegressionLinear ## ## Linear regression model for high dimensional data. ## ## A @qcode{RegressionLinear} object fits a linear model, ## @code{@var{X} * Beta + Bias}, to a continuous response by minimizing a ## regularized average loss. The loss is the epsilon-insensitive loss for a ## support vector machine and the squared error for a least squares fit, and ## the penalty is either a ridge or a lasso one. ## ## Unlike the other regression models of this package the object holds no ## copy of the training data: the coefficients, the intercept and the fitting ## options are the whole model. That is what makes it suited to data with ## more predictors than a kernel matrix could carry, and it is why the class ## has no @code{compact} method and no resubstitution methods. ## ## A vector of regularization strengths fits one model per value in a single ## object. @qcode{Beta} is then a @math{PxL} matrix and @qcode{Bias} a ## @math{1xL} row, every method returns one column per strength, and ## @code{selectModels} narrows the object down to the strengths worth ## keeping. ## ## Create a @qcode{RegressionLinear} object with @code{fitrlinear}. ## ## @seealso{fitrlinear, RegressionKernel, RegressionSVM} ## @end deftp classdef RegressionLinear properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {RegressionLinear} {property} Epsilon ## ## Half the width of the epsilon-insensitive band ## ## A nonnegative scalar for a support vector machine, and empty for a ## least squares fit, which has no such band. It defaults to the ## interquartile range of the response over 13.49, an estimate of its ## standard deviation, or to @qcode{0.1} when that range is zero. This ## property is read-only. ## ## @end deftp Epsilon = []; endproperties properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {RegressionLinear} {property} ResponseTransform ## ## Transformation applied to the predicted response ## ## A character vector, or the text of the function handle that was ## supplied. Assigning to it accepts either. ## ## @end deftp ResponseTransform = 'none'; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {RegressionLinear} {property} PredictorNames ## ## Names of the predictors ## ## A cell array of character vectors with one name per column of the ## training data, defaulting to @qcode{'x1'}, @qcode{'x2'} and so on. ## This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {RegressionLinear} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A row vector of column indices, empty when every predictor is ## numeric. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {RegressionLinear} {property} ResponseName ## ## Name of the response ## ## A character vector, defaulting to @qcode{'Y'}. This property is ## read-only. ## ## @end deftp ResponseName = 'Y'; ## -*- texinfo -*- ## @deftp {RegressionLinear} {property} ExpandedPredictorNames ## ## Names of the predictors as the fit saw them ## ## A cell array of character vectors. It equals @qcode{PredictorNames} ## unless categorical predictors were expanded into indicator variables. ## This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {RegressionLinear} {property} Learner ## ## Linear regression model that was fitted ## ## Either @qcode{'svm'} or @qcode{'leastsquares'}. This property is ## read-only. ## ## @end deftp Learner = 'svm'; ## -*- texinfo -*- ## @deftp {RegressionLinear} {property} Beta ## ## Fitted linear coefficients ## ## A @math{Px1} column, or a @math{PxL} matrix with one column per ## regularization strength when @qcode{Lambda} holds more than one. This ## property is read-only. ## ## @end deftp Beta = []; ## -*- texinfo -*- ## @deftp {RegressionLinear} {property} Bias ## ## Fitted intercept ## ## A scalar, or a @math{1xL} row with one element per regularization ## strength. It is zero throughout when the model was fitted with ## @qcode{'FitBias'} set to false. This property is read-only. ## ## @end deftp Bias = []; ## -*- texinfo -*- ## @deftp {RegressionLinear} {property} FittedLoss ## ## Loss function the fit minimized ## ## @qcode{'epsiloninsensitive'} for a support vector machine and ## @qcode{'mse'} for a least squares fit. This is the loss of the ## objective, which is not the loss @code{loss} reports unless it is ## asked for. This property is read-only. ## ## @end deftp FittedLoss = 'epsiloninsensitive'; ## -*- texinfo -*- ## @deftp {RegressionLinear} {property} Lambda ## ## Regularization strength ## ## A nonnegative scalar, or a @math{1xL} row of them in ascending order. ## It defaults to the reciprocal of the number of observations used to ## train the model. This property is read-only. ## ## @end deftp Lambda = []; ## -*- texinfo -*- ## @deftp {RegressionLinear} {property} ModelParameters ## ## Fitting options, as they were given ## ## A structure holding every parameter of the fit, including the ones ## that a different solver would have used and the @qcode{'auto'} values ## before they were resolved. This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {RegressionLinear} {property} Regularization ## ## Penalty on the coefficients ## ## @qcode{'ridge (L2)'} or @qcode{'lasso (L1)'}. This property is ## read-only. ## ## @end deftp Regularization = 'ridge (L2)'; endproperties properties (GetAccess = public, SetAccess = protected, Hidden) ## The callable behind ResponseTransform. The public property is the ## text MATLAB stores; this is what predict actually applies. RTfun = @(y) y; ## Number of predictors the fit saw, which the class does not report ## because MATLAB does not, but predict needs to validate its input. NumPredictors_ = []; ## What the fit reported, so that fitrlinear can hand it back as its ## second output. MATLAB returns it from the fitting function rather ## than storing it on the model, and a constructor cannot have a second ## output argument. FitInfo_ = []; endproperties methods (Access = public) ## -*- texinfo -*- ## @deftypefn {RegressionLinear} {@var{obj} =} RegressionLinear (@var{X}, @var{Y}) ## @deftypefnx {RegressionLinear} {@var{obj} =} RegressionLinear (@dots{}, @var{name}, @var{value}) ## ## Fit a linear regression model. ## ## @code{@var{obj} = RegressionLinear (@var{X}, @var{Y})} fits a linear ## support vector machine to the @math{NxP} predictor matrix @var{X} and ## the @math{Nx1} continuous response @var{Y}. ## ## @code{@var{obj} = RegressionLinear (@dots{}, @var{name}, ## @var{value})} takes the following @qcode{Name-Value} pairs. ## ## @multitable @columnfractions 0.28 0.72 ## @headitem Name @tab Value ## ## @item @qcode{'Learner'} @tab @qcode{'svm'}, the default, or ## @qcode{'leastsquares'}. The first minimizes the epsilon-insensitive ## loss and the second the squared error. ## ## @item @qcode{'Epsilon'} @tab Half the width of the insensitive band, ## a nonnegative scalar or @qcode{'auto'}, which is the interquartile ## range of @var{Y} over 13.49. It applies to a support vector machine ## alone. ## ## @item @qcode{'Regularization'} @tab @qcode{'ridge'} or ## @qcode{'lasso'}. It defaults to @qcode{'lasso'} when the solver is ## @qcode{'sparsa'} and to @qcode{'ridge'} otherwise. ## ## @item @qcode{'Lambda'} @tab @qcode{'auto'}, the default, which is the ## reciprocal of the number of observations, or a nonnegative scalar, or ## a vector of them. A vector fits one model per value. ## ## @item @qcode{'Solver'} @tab One of @qcode{'sgd'}, @qcode{'asgd'}, ## @qcode{'dual'}, @qcode{'bfgs'}, @qcode{'lbfgs'} and @qcode{'sparsa'}, ## or a cell array of them applied in turn, each warm starting the next. ## ## @item @qcode{'Beta'} @tab Initial coefficients, a @math{Px1} column or ## a @math{PxL} matrix. It defaults to zeros. ## ## @item @qcode{'Bias'} @tab Initial intercept, a scalar or a @math{1xL} ## row. It defaults to the weighted mean of @var{Y} for a least squares ## fit and to its weighted median for a support vector machine. ## ## @item @qcode{'FitBias'} @tab Whether to fit an intercept at all, true ## by default. ## ## @item @qcode{'PostFitBias'} @tab Whether to refit the intercept once ## the coefficients are settled, false by default. ## ## @item @qcode{'ObservationsIn'} @tab @qcode{'rows'}, the default, or ## @qcode{'columns'}, which transposes @var{X} before fitting. ## ## @item @qcode{'BetaTolerance'} @tab Relative tolerance on the ## coefficients, @qcode{1e-4} by default. ## ## @item @qcode{'GradientTolerance'} @tab Absolute tolerance on the ## gradient's infinity norm, @qcode{1e-6} by default. ## ## @item @qcode{'DeltaGradientTolerance'} @tab Tolerance on the ## complementarity gap of the @qcode{'dual'} solver, @qcode{0.1} by ## default. ## ## @item @qcode{'IterationLimit'} @tab Largest number of iterations, ## @qcode{1000} by default. ## ## @item @qcode{'PassLimit'} @tab Largest number of passes over the data ## for the stochastic solvers, @qcode{1} by default, and @qcode{10} for ## @qcode{'dual'}. ## ## @item @qcode{'BatchSize'} @tab Mini-batch size of the stochastic ## solvers, @qcode{10} by default. ## ## @item @qcode{'BatchLimit'} @tab Largest number of mini-batches. ## ## @item @qcode{'LearnRate'} @tab Step size of the stochastic solvers. ## ## @item @qcode{'OptimizeLearnRate'} @tab Whether to halve the step size ## when the objective rises, true by default. ## ## @item @qcode{'TruncationPeriod'} @tab Number of mini-batches between ## soft thresholdings under a lasso penalty, @qcode{10} by default. ## ## @item @qcode{'NumCheckConvergence'} @tab Number of passes between ## convergence checks of the @qcode{'dual'} solver, @qcode{2} by ## default. MathWorks documents @qcode{5}; R2024a and R2026a both ## report @qcode{2}. ## ## @item @qcode{'HessianHistorySize'} @tab Number of curvature pairs the ## quasi-Newton solvers keep, @qcode{15} by default. ## ## @item @qcode{'ResponseTransform'} @tab A transformation applied to the ## predicted response, named or given as a function handle. ## ## @item @qcode{'Weights'} @tab One nonnegative weight per observation. ## ## @item @qcode{'PredictorNames'} @tab One name per predictor. ## ## @item @qcode{'ResponseName'} @tab A name for the response. ## ## @item @qcode{'CategoricalPredictors'} @tab Indices of the categorical ## predictors. ## @end multitable ## ## The default solver is @qcode{'sparsa'} under a lasso penalty. Under a ## ridge penalty it is @qcode{'bfgs'} when there are no more than 100 ## predictors, and beyond that @qcode{'dual'} for a support vector ## machine and @qcode{'sgd'} for a least squares fit. ## ## @seealso{fitrlinear, RegressionKernel} ## @end deftypefn function this = RegressionLinear (X, Y, varargin) ## Check for sufficient number of input arguments if (nargin < 2) error ("RegressionLinear: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("RegressionLinear: optional arguments must be", ... " given in Name-Value pairs.")); endif ## Defaults, before the optional arguments are parsed Learner = 'svm'; EpsilonIn = 'auto'; Regularization = []; Lambda = 'auto'; Solver = []; BetaIn = []; BiasIn = []; FitBias = true; PostFitBias = false; ObservationsIn = 'rows'; BetaTolerance = 1e-4; GradientTolerance = 1e-6; DeltaGradientTolerance = 0.1; IterationLimit = 1000; PassLimit = []; BatchSize = 10; BatchLimit = []; LearnRate = []; OptimizeLearnRate = true; TruncationPeriod = 10; NumCheckConvergence = 2; HessianHistorySize = 15; Verbose = 0; ResponseTransform = 'none'; Weights = []; PredictorNames = {}; ResponseName = 'Y'; CategoricalPredictors = []; EpsilonGiven = false; ## Parse optional parameters while (numel (varargin) > 0) switch (lower (varargin{1})) case 'learner' Learner = varargin{2}; if (! (ischar (Learner) && any (strcmpi (Learner, {'svm', 'leastsquares'})))) error (strcat ("RegressionLinear: 'Learner' must be either", ... " 'svm' or 'leastsquares'.")); endif Learner = lower (Learner); case 'epsilon' EpsilonIn = varargin{2}; EpsilonGiven = true; if (! ((ischar (EpsilonIn) && strcmpi (EpsilonIn, 'auto')) || (isnumeric (EpsilonIn) && isscalar (EpsilonIn) && isreal (EpsilonIn) && EpsilonIn >= 0))) error (strcat ("RegressionLinear: 'Epsilon' must be 'auto'", ... " or a nonnegative scalar.")); endif case 'regularization' Regularization = varargin{2}; if (! (ischar (Regularization) && any (strcmpi (Regularization, {'ridge', 'lasso'})))) error (strcat ("RegressionLinear: 'Regularization' must be", ... " either 'ridge' or 'lasso'.")); endif Regularization = lower (Regularization); case 'lambda' Lambda = varargin{2}; if (! ((ischar (Lambda) && strcmpi (Lambda, 'auto')) || (isnumeric (Lambda) && isreal (Lambda) && isvector (Lambda) && ! isempty (Lambda) && all (Lambda >= 0) && all (isfinite (Lambda))))) error (strcat ("RegressionLinear: 'Lambda' must be 'auto'", ... " or a vector of nonnegative finite values.")); endif case 'solver' Solver = varargin{2}; if (ischar (Solver)) Solver = {Solver}; endif valid = {'sgd', 'asgd', 'dual', 'bfgs', 'lbfgs', 'sparsa'}; if (! (iscellstr (Solver) && ! isempty (Solver) && all (cellfun (@(s) any (strcmpi (s, valid)), Solver)))) error (strcat ("RegressionLinear: 'Solver' must be one of", ... " 'sgd', 'asgd', 'dual', 'bfgs', 'lbfgs'", ... " and 'sparsa', or a cell array of them.")); endif Solver = lower (Solver); case 'beta' BetaIn = varargin{2}; if (! (isnumeric (BetaIn) && isreal (BetaIn) && ismatrix (BetaIn) && ! isempty (BetaIn))) error (strcat ("RegressionLinear: 'Beta' must be a real", ... " numeric matrix.")); endif case 'bias' BiasIn = varargin{2}; if (! (isnumeric (BiasIn) && isreal (BiasIn) && isvector (BiasIn) && ! isempty (BiasIn))) error (strcat ("RegressionLinear: 'Bias' must be a real", ... " numeric vector.")); endif case 'fitbias' FitBias = varargin{2}; if (! (islogical (FitBias) || (isnumeric (FitBias) && isscalar (FitBias) && any (FitBias == [0, 1])))) error (strcat ("RegressionLinear: 'FitBias' must be either", ... " true or false.")); endif FitBias = logical (FitBias); case 'postfitbias' PostFitBias = varargin{2}; if (! (islogical (PostFitBias) || (isnumeric (PostFitBias) && isscalar (PostFitBias) && any (PostFitBias == [0, 1])))) error (strcat ("RegressionLinear: 'PostFitBias' must be", ... " either true or false.")); endif PostFitBias = logical (PostFitBias); case 'observationsin' ObservationsIn = varargin{2}; if (! (ischar (ObservationsIn) && any (strcmpi (ObservationsIn, {'rows', 'columns'})))) error (strcat ("RegressionLinear: 'ObservationsIn' must be", ... " either 'rows' or 'columns'.")); endif ObservationsIn = lower (ObservationsIn); case 'betatolerance' BetaTolerance = varargin{2}; if (! (isnumeric (BetaTolerance) && isscalar (BetaTolerance) && isreal (BetaTolerance) && BetaTolerance >= 0)) error (strcat ("RegressionLinear: 'BetaTolerance' must be", ... " a nonnegative scalar.")); endif case 'gradienttolerance' GradientTolerance = varargin{2}; if (! (isnumeric (GradientTolerance) && isscalar (GradientTolerance) && isreal (GradientTolerance) && GradientTolerance >= 0)) error (strcat ("RegressionLinear: 'GradientTolerance'", ... " must be a nonnegative scalar.")); endif case 'deltagradienttolerance' DeltaGradientTolerance = varargin{2}; if (! (isnumeric (DeltaGradientTolerance) && isscalar (DeltaGradientTolerance) && isreal (DeltaGradientTolerance) && DeltaGradientTolerance >= 0)) error (strcat ("RegressionLinear:", ... " 'DeltaGradientTolerance' must be a", ... " nonnegative scalar.")); endif case 'iterationlimit' IterationLimit = varargin{2}; if (! (isnumeric (IterationLimit) && isscalar (IterationLimit) && isreal (IterationLimit) && IterationLimit > 0 && fix (IterationLimit) == IterationLimit)) error (strcat ("RegressionLinear: 'IterationLimit' must", ... " be a positive integer scalar.")); endif case 'passlimit' PassLimit = varargin{2}; if (! (isnumeric (PassLimit) && isscalar (PassLimit) && isreal (PassLimit) && PassLimit > 0 && fix (PassLimit) == PassLimit)) error (strcat ("RegressionLinear: 'PassLimit' must be a", ... " positive integer scalar.")); endif case 'batchsize' BatchSize = varargin{2}; if (! (isnumeric (BatchSize) && isscalar (BatchSize) && isreal (BatchSize) && BatchSize > 0 && fix (BatchSize) == BatchSize)) error (strcat ("RegressionLinear: 'BatchSize' must be a", ... " positive integer scalar.")); endif case 'batchlimit' BatchLimit = varargin{2}; if (! (isnumeric (BatchLimit) && isscalar (BatchLimit) && isreal (BatchLimit) && BatchLimit > 0 && fix (BatchLimit) == BatchLimit)) error (strcat ("RegressionLinear: 'BatchLimit' must be a", ... " positive integer scalar.")); endif case 'learnrate' LearnRate = varargin{2}; if (! (isnumeric (LearnRate) && isscalar (LearnRate) && isreal (LearnRate) && LearnRate > 0)) error (strcat ("RegressionLinear: 'LearnRate' must be a", ... " positive scalar.")); endif case 'optimizelearnrate' OptimizeLearnRate = varargin{2}; if (! (islogical (OptimizeLearnRate) || (isnumeric (OptimizeLearnRate) && isscalar (OptimizeLearnRate) && any (OptimizeLearnRate == [0, 1])))) error (strcat ("RegressionLinear: 'OptimizeLearnRate'", ... " must be either true or false.")); endif OptimizeLearnRate = logical (OptimizeLearnRate); case 'truncationperiod' TruncationPeriod = varargin{2}; if (! (isnumeric (TruncationPeriod) && isscalar (TruncationPeriod) && isreal (TruncationPeriod) && TruncationPeriod > 0 && fix (TruncationPeriod) == TruncationPeriod)) error (strcat ("RegressionLinear: 'TruncationPeriod' must", ... " be a positive integer scalar.")); endif case 'numcheckconvergence' NumCheckConvergence = varargin{2}; if (! (isnumeric (NumCheckConvergence) && isscalar (NumCheckConvergence) && isreal (NumCheckConvergence) && NumCheckConvergence > 0 && fix (NumCheckConvergence) == NumCheckConvergence)) error (strcat ("RegressionLinear: 'NumCheckConvergence'", ... " must be a positive integer scalar.")); endif case 'hessianhistorysize' HessianHistorySize = varargin{2}; if (! (isnumeric (HessianHistorySize) && isscalar (HessianHistorySize) && isreal (HessianHistorySize) && HessianHistorySize > 0 && fix (HessianHistorySize) == HessianHistorySize)) error (strcat ("RegressionLinear: 'HessianHistorySize'", ... " must be a positive integer scalar.")); endif case 'verbose' Verbose = varargin{2}; if (! (isnumeric (Verbose) && isscalar (Verbose) && isreal (Verbose) && any (Verbose == [0, 1, 2]))) error (strcat ("RegressionLinear: 'Verbose' must be 0, 1,", ... " or 2.")); endif case 'responsetransform' ResponseTransform = varargin{2}; case 'weights' Weights = varargin{2}; if (! (isnumeric (Weights) && isreal (Weights) && isvector (Weights) && all (Weights >= 0))) error (strcat ("RegressionLinear: 'Weights' must be a", ... " vector of nonnegative values.")); endif case 'predictornames' PredictorNames = varargin{2}; if (! (iscellstr (PredictorNames) && isvector (PredictorNames))) error (strcat ("RegressionLinear: 'PredictorNames' must", ... " be a cell array of character vectors.")); endif case 'responsename' ResponseName = varargin{2}; if (! (ischar (ResponseName) && isrow (ResponseName))) error (strcat ("RegressionLinear: 'ResponseName' must be a", ... " character vector.")); endif case 'categoricalpredictors' CategoricalPredictors = varargin{2}; if (! ((isnumeric (CategoricalPredictors) && isvector (CategoricalPredictors) && all (fix (CategoricalPredictors) == CategoricalPredictors) && all (CategoricalPredictors > 0)) || islogical (CategoricalPredictors) || isempty (CategoricalPredictors))) error (strcat ("RegressionLinear:", ... " 'CategoricalPredictors' must be a vector", ... " of positive integers or a logical vector.")); endif otherwise error (strcat ("RegressionLinear: invalid parameter name in", ... " optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## Observations may be given down the columns, which only means the ## predictor matrix arrives transposed. if (strcmp (ObservationsIn, 'columns')) X = X'; endif ## Validate the data and resolve the weights. The four linear and ## kernel regression models share that opening, so it lives in one ## place. F = regFrame (X, Y, Weights, 'RegressionLinear'); X = F.X; Y = F.Y; W = F.W; n = F.n; p = F.p; ## Epsilon belongs to the insensitive band, so it means nothing to a ## least squares fit and MATLAB refuses it there rather than ignoring ## it. if (strcmp (Learner, 'leastsquares')) if (EpsilonGiven) error (strcat ("RegressionLinear: 'Epsilon' applies to a", ... " support vector machine only.")); endif Epsilon = []; elseif (ischar (EpsilonIn)) r = iqr (Y); if (r == 0) Epsilon = 0.1; else Epsilon = r / 13.49; endif else Epsilon = EpsilonIn; endif ## Resolve the penalty and the solver against one another, since each ## has a default that depends on the other. if (isempty (Regularization)) if (! isempty (Solver) && numel (Solver) == 1 && strcmp (Solver{1}, 'sparsa')) Regularization = 'lasso'; else Regularization = 'ridge'; endif endif if (isempty (Solver)) if (strcmp (Regularization, 'lasso')) Solver = {'sparsa'}; elseif (p <= 100) Solver = {'bfgs'}; elseif (strcmp (Learner, 'svm')) Solver = {'dual'}; else Solver = {'sgd'}; endif endif for k = 1:numel (Solver) if (strcmp (Regularization, 'lasso') && ! any (strcmp (Solver{k}, {'sgd', 'asgd', 'sparsa'}))) error (strcat ("RegressionLinear: the '%s' solver fits a ridge", ... " penalty only."), Solver{k}); endif if (strcmp (Regularization, 'ridge') && strcmp (Solver{k}, 'sparsa')) error (strcat ("RegressionLinear: the 'sparsa' solver fits a", ... " lasso penalty only.")); endif if (strcmp (Solver{k}, 'dual') && strcmp (Learner, 'leastsquares')) error (strcat ("RegressionLinear: the 'dual' solver fits an", ... " epsilon-insensitive loss only, so it needs", ... " 'Learner' set to 'svm'.")); endif endfor ## Resolve Lambda, which the class reports as a number even when it ## was given as 'auto', and which ModelParameters keeps as given. LambdaIn = Lambda; if (ischar (Lambda)) Lambda = 1 / n; else Lambda = sort (Lambda(:)'); endif L = numel (Lambda); ## Resolve the starting point if (isempty (BetaIn)) Beta0 = zeros (p, L); else if (rows (BetaIn) != p) error (strcat ("RegressionLinear: 'Beta' must have one row per", ... " predictor.")); endif if (columns (BetaIn) == 1) Beta0 = repmat (BetaIn, 1, L); elseif (columns (BetaIn) == L) Beta0 = BetaIn; else error (strcat ("RegressionLinear: 'Beta' must have one column,", ... " or one per value of 'Lambda'.")); endif endif if (isempty (BiasIn)) if (strcmp (Learner, 'leastsquares')) Bias0 = repmat (sum (W .* Y), 1, L); else Bias0 = repmat (weightedMedian (Y, W), 1, L); endif else BiasIn = BiasIn(:)'; if (numel (BiasIn) == 1) Bias0 = repmat (BiasIn, 1, L); elseif (numel (BiasIn) == L) Bias0 = BiasIn; else error (strcat ("RegressionLinear: 'Bias' must be a scalar, or", ... " hold one value per value of 'Lambda'.")); endif endif if (isempty (PassLimit)) if (any (strcmp (Solver, 'dual'))) PassLimit = 10; else PassLimit = 1; endif endif if (isempty (LearnRate)) ## MATLAB's default: the reciprocal root of one plus the largest ## squared length of an observation, so a step never overshoots the ## widest row of the data. LearnRate = 1 / sqrt (1 + max (sum (X .^ 2, 2))); endif ## Fit one model per regularization strength, each warm starting the ## next, which is what makes an ascending Lambda cheaper than the same ## values fitted apart. Beta = zeros (p, L); Bias = zeros (1, L); info = struct ([]); P = struct (); P.Learner = Learner; P.LossFunction = 'epsiloninsensitive'; if (strcmp (Learner, 'leastsquares')) P.LossFunction = 'mse'; endif P.Epsilon = Epsilon; P.Regularization = Regularization; P.FitBias = FitBias; P.PostFitBias = PostFitBias; P.BetaTolerance = BetaTolerance; P.GradientTolerance = GradientTolerance; P.DeltaGradientTolerance = DeltaGradientTolerance; P.IterationLimit = IterationLimit; P.PassLimit = PassLimit; P.BatchSize = BatchSize; P.BatchLimit = BatchLimit; P.LearnRate = LearnRate; P.OptimizeLearnRate = OptimizeLearnRate; P.TruncationPeriod = TruncationPeriod; P.NumCheckConvergence = NumCheckConvergence; P.HessianHistorySize = HessianHistorySize; for l = 1:L P.Lambda = Lambda(l); b = Beta0(:,l); b0 = Bias0(l); if (l > 1 && isempty (BetaIn)) b = Beta(:,l-1); b0 = Bias(l-1); endif for k = 1:numel (Solver) P.Solver = Solver{k}; P.InitialBeta = b; P.InitialBias = b0; [b, b0, S] = linearSolve (X, Y, W, P); endfor Beta(:,l) = b; Bias(l) = b0; S.Lambda = Lambda(l); if (l == 1) info = S; else info(l) = S; endif endfor ## Fill in the model this.Epsilon = Epsilon; this.PredictorNames = PredictorNames; if (isempty (this.PredictorNames)) this.PredictorNames = ... arrayfun (@(k) sprintf ("x%d", k), 1:p, ... 'UniformOutput', false); elseif (numel (this.PredictorNames) != p) error (strcat ("RegressionLinear: 'PredictorNames' must have one", ... " name per predictor.")); endif this.ExpandedPredictorNames = this.PredictorNames; this.CategoricalPredictors = CategoricalPredictors; this.ResponseName = ResponseName; this.Learner = Learner; this.Beta = Beta; this.Bias = Bias; this.FittedLoss = P.LossFunction; this.Lambda = Lambda; this.NumPredictors_ = p; this.ResponseTransform = ResponseTransform; if (strcmp (Regularization, 'ridge')) this.Regularization = 'ridge (L2)'; else this.Regularization = 'lasso (L1)'; endif this.ModelParameters = linearModelParams (P, Solver, LambdaIn, ... EpsilonIn, Beta0, Bias0, ... Verbose, 'regression'); this.FitInfo_ = linearFitInfo (info, BetaTolerance, ... GradientTolerance, ... DeltaGradientTolerance, ... IterationLimit, Solver, PassLimit, ... BatchLimit); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {RegressionLinear} {@var{yFit} =} predict (@var{obj}, @var{XC}) ## ## Predict the response of new observations. ## ## @code{@var{yFit} = predict (@var{obj}, @var{XC})} returns one ## predicted value per row of @var{XC}, and one column per regularization ## strength. @qcode{ResponseTransform} is applied to the result. ## ## @end deftypefn function yFit = predict (this, XC) if (nargin < 2) error ("RegressionLinear.predict: too few input arguments."); endif if (isempty (XC)) error ("RegressionLinear.predict: XC is empty."); endif if (! (isnumeric (XC) && isreal (XC) && ismatrix (XC))) error ("RegressionLinear.predict: invalid values in XC."); endif if (columns (XC) != this.NumPredictors_) error (strcat ("RegressionLinear.predict: XC must have the same", ... " number of predictors as the trained model.")); endif yFit = this.RTfun (XC * this.Beta + this.Bias); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionLinear} {@var{l} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {RegressionLinear} {@var{l} =} loss (@dots{}, @var{name}, @var{value}) ## ## Regression loss on new data. ## ## @code{@var{l} = loss (@var{obj}, @var{X}, @var{Y})} returns the mean ## squared error, one value per regularization strength. ## ## @code{@var{l} = loss (@dots{}, @var{name}, @var{value})} takes ## @qcode{'LossFun'}, either @qcode{'mse'} or ## @qcode{'epsiloninsensitive'}, and @qcode{'Weights'}. The ## epsilon-insensitive loss needs a band to be insensitive within, so it ## is offered by a support vector machine alone. ## ## @end deftypefn function l = loss (this, X, Y, varargin) if (nargin < 3) error ("RegressionLinear.loss: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("RegressionLinear.loss: optional arguments must", ... " be given in Name-Value pairs.")); endif LossFun = 'mse'; Weights = []; while (numel (varargin) > 0) switch (lower (varargin{1})) case 'lossfun' LossFun = varargin{2}; if (! (ischar (LossFun) && any (strcmpi (LossFun, ... {'mse', 'epsiloninsensitive'})))) error (strcat ("RegressionLinear.loss: 'LossFun' must be", ... " either 'mse' or 'epsiloninsensitive'.")); endif LossFun = lower (LossFun); case 'weights' Weights = varargin{2}; if (! (isnumeric (Weights) && isreal (Weights) && isvector (Weights) && all (Weights >= 0))) error (strcat ("RegressionLinear.loss: 'Weights' must be", ... " a vector of nonnegative values.")); endif otherwise error (strcat ("RegressionLinear.loss: invalid parameter", ... " name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile if (strcmp (LossFun, 'epsiloninsensitive') && isempty (this.Epsilon)) error (strcat ("RegressionLinear.loss: the", ... " 'epsiloninsensitive' loss applies to a support", ... " vector machine only.")); endif Y = Y(:); if (! (isnumeric (Y) && isreal (Y))) error ("RegressionLinear.loss: invalid values in Y."); endif if (rows (X) != numel (Y)) error (strcat ("RegressionLinear.loss: number of rows in X and Y", ... " must be equal.")); endif if (isempty (Weights)) w = ones (numel (Y), 1); else w = Weights(:); if (numel (w) != numel (Y)) error (strcat ("RegressionLinear.loss: 'Weights' must have one", ... " element per observation.")); endif endif w = w / sum (w); yFit = predict (this, X); L = numel (this.Lambda); l = zeros (1, L); for k = 1:L r = Y - yFit(:,k); if (strcmp (LossFun, 'mse')) l(k) = sum (w .* (r .^ 2)); else l(k) = sum (w .* max (0, abs (r) - this.Epsilon)); endif endfor endfunction ## -*- texinfo -*- ## @deftypefn {RegressionLinear} {@var{sub} =} selectModels (@var{obj}, @var{idx}) ## ## Keep a subset of the fitted regularization strengths. ## ## @code{@var{sub} = selectModels (@var{obj}, @var{idx})} returns a model ## holding only the strengths @var{idx} names, which may be indices into ## @qcode{Lambda} or a logical vector over it. ## ## @end deftypefn function sub = selectModels (this, idx) if (nargin < 2) error (strcat ("RegressionLinear.selectModels: too few input", ... " arguments.")); endif L = numel (this.Lambda); if (islogical (idx)) if (numel (idx) != L) error (strcat ("RegressionLinear.selectModels: a logical IDX", ... " must have one element per value of 'Lambda'.")); endif idx = find (idx); endif if (! (isnumeric (idx) && isreal (idx) && isvector (idx) && ! isempty (idx) && all (fix (idx) == idx) && all (idx >= 1) && all (idx <= L))) error (strcat ("RegressionLinear.selectModels: IDX must hold", ... " integers between 1 and %d."), L); endif sub = this; sub.Lambda = this.Lambda(idx); sub.Beta = this.Beta(:,idx); sub.Bias = this.Bias(idx); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionLinear} {} savemodel (@var{obj}, @var{filename}) ## ## Save a linear regression model to a file. ## ## @code{savemodel (@var{obj}, @var{filename})} saves the model ## @var{obj} into @var{filename} in a form @code{loadmodel} can read ## back. ## ## @end deftypefn function savemodel (obj, fname) classdef_name = 'RegressionLinear'; Epsilon = obj.Epsilon; ResponseTransform = obj.ResponseTransform; PredictorNames = obj.PredictorNames; CategoricalPredictors = obj.CategoricalPredictors; ResponseName = obj.ResponseName; ExpandedPredictorNames = obj.ExpandedPredictorNames; Learner = obj.Learner; Beta = obj.Beta; Bias = obj.Bias; FittedLoss = obj.FittedLoss; Lambda = obj.Lambda; ModelParameters = obj.ModelParameters; Regularization = obj.Regularization; NumPredictors_ = obj.NumPredictors_; save ('-binary', fname, 'classdef_name', 'Epsilon', ... 'ResponseTransform', 'PredictorNames', ... 'CategoricalPredictors', 'ResponseName', ... 'ExpandedPredictorNames', 'Learner', 'Beta', 'Bias', ... 'FittedLoss', 'Lambda', 'ModelParameters', 'Regularization', ... 'NumPredictors_'); endfunction endmethods methods (Access = public, Hidden) function display (this) in_name = inputname (1); if (! isempty (in_name)) printf ('%s =\n', in_name); endif disp (this); endfunction function disp (this) printf ("\n RegressionLinear\n\n"); printf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); printf ("%+25s: '%s'\n", 'ResponseTransform', this.ResponseTransform); printf ("%+25s: [%dx%d double]\n", 'Beta', rows (this.Beta), ... columns (this.Beta)); if (numel (this.Bias) == 1) printf ("%+25s: %g\n", 'Bias', this.Bias); printf ("%+25s: %g\n", 'Lambda', this.Lambda); else printf ("%+25s: [1x%d double]\n", 'Bias', numel (this.Bias)); printf ("%+25s: [1x%d double]\n", 'Lambda', numel (this.Lambda)); endif printf ("%+25s: '%s'\n", 'Learner', this.Learner); printf ("\n"); endfunction ## What the fit reported, which fitrlinear returns as its second output. function S = fitInfo_ (this) S = this.FitInfo_; endfunction ## Custom setter, so that assigning a name or a handle updates both the ## text the property reports and the callable predict uses. function this = set.ResponseTransform (this, val) [this.RTfun, this.ResponseTransform] = ... parseResponseTransform (val, 'RegressionLinear'); endfunction endmethods methods (Static, Hidden) function mdl = load_model (filename, data) mdl = RegressionLinear ([0; 1], [0; 1]); fields = fieldnames (data); for k = 1:numel (fields) mdl.(fields{k}) = data.(fields{k}); endfor endfunction endmethods endclassdef %!shared X, Y %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! X = X(ok,:); %! Y = MPG(ok); %!demo %! ## Fit fuel consumption on four engine measurements and read the %! ## coefficients and the insensitive band the fit chose for itself. %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! Mdl = RegressionLinear (X(ok,:), MPG(ok)) %! yFit = predict (Mdl, X(find (ok, 3),:)) %!demo %! ## Least squares has no insensitive band, so Epsilon is empty, and its %! ## loss is the mean squared error the fit minimizes. %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! Mdl = RegressionLinear (X(ok,:), MPG(ok), 'Learner', 'leastsquares'); %! band = Mdl.Epsilon %! mse = loss (Mdl, X(ok,:), MPG(ok)) %!test %! ## The model reports the surface MATLAB reports %! Mdl = RegressionLinear (X, Y); %! assert_equal (class (Mdl), 'RegressionLinear'); %! assert_equal (Mdl.Learner, 'svm'); %! assert_equal (Mdl.FittedLoss, 'epsiloninsensitive'); %! assert_equal (Mdl.Regularization, 'ridge (L2)'); %! assert_equal (Mdl.ResponseTransform, 'none'); %! assert_equal (Mdl.ResponseName, 'Y'); %! assert_equal (Mdl.PredictorNames, {'x1', 'x2', 'x3', 'x4'}); %! assert_equal (Mdl.ExpandedPredictorNames, {'x1', 'x2', 'x3', 'x4'}); %! assert_equal (Mdl.CategoricalPredictors, []); %! assert_equal (size (Mdl.Beta), [4, 1]); %!test %! ## The properties are the ones MATLAB lists, in its order %! Mdl = RegressionLinear (X, Y); %! assert_equal (sort (properties (Mdl)), ... %! sort ({'Epsilon'; 'ResponseTransform'; 'PredictorNames'; ... %! 'CategoricalPredictors'; 'ResponseName'; ... %! 'ExpandedPredictorNames'; 'Learner'; 'Beta'; ... %! 'Bias'; 'FittedLoss'; 'Lambda'; 'ModelParameters'; ... %! 'Regularization'})); %!test %! ## A least squares ridge fit reproduces R2024a's coefficients %! Mdl = RegressionLinear (X, Y, 'Learner', 'leastsquares', ... %! 'Solver', 'lbfgs', 'BetaTolerance', 0, ... %! 'GradientTolerance', 1e-12, ... %! 'IterationLimit', 20000); %! assert_equal (Mdl.Beta, [-0.060147206634135; -0.00667928082241265; ... %! -0.0375377897819825; -0.00608459830980712], 1e-7); %! assert_equal (Mdl.Bias, 48.1149099265466, 1e-6); %! assert_equal (Mdl.FittedLoss, 'mse'); %! assert_equal (Mdl.Epsilon, []); %! assert_equal (loss (Mdl, X, Y), 15.9484134801834, 1e-6); %!test %! ## Epsilon defaults to the interquartile range over 13.49, R2024a's own %! ## estimate of the standard deviation of the response %! Mdl = RegressionLinear (X, Y); %! assert_equal (Mdl.Epsilon, 0.926612305411416, 1e-12); %! assert_equal (Mdl.Epsilon, iqr (Y) / 13.49, 1e-15); %!test %! ## A response of no spread has no interquartile range to scale, and the %! ## band falls back on a tenth %! Mdl = RegressionLinear (ones (10, 2), 5 * ones (10, 1)); %! assert_equal (Mdl.Epsilon, 0.1); %!test %! ## The epsilon-insensitive loss is not differentiable at the edge of the %! ## band, so the line search gives up short of a minimum and where it stops %! ## follows the last bits: 2.8273 here, 3.4883 under clang and on macOS, %! ## against the 2.82717018548797 R2024a reaches. Assert the identity the %! ## reported objective holds at whichever point it stops. %! Mdl = RegressionLinear (X, Y, 'Learner', 'svm', 'Solver', 'lbfgs', ... %! 'BetaTolerance', 0, 'GradientTolerance', 1e-12, ... %! 'IterationLimit', 20000); %! assert_equal (Mdl.FitInfo_.Objective, ... %! loss (Mdl, X, Y, 'LossFun', 'epsiloninsensitive') ... %! + 0.5 * Mdl.Lambda * sum (Mdl.Beta .^ 2), 1e-12); %!test %! ## Lambda defaults to the reciprocal of the observations that were used %! Mdl = RegressionLinear (X, Y); %! assert_equal (Mdl.Lambda, 1 / 93, 1e-15); %!test %! ## A vector of strengths fits one model per value, sorted ascending, and %! ## every method reports one column per value %! Mdl = RegressionLinear (X, Y, 'Lambda', [0.1, 0.001, 0.01]); %! assert_equal (Mdl.Lambda, [0.001, 0.01, 0.1]); %! assert_equal (size (Mdl.Beta), [4, 3]); %! assert_equal (size (Mdl.Bias), [1, 3]); %! assert_equal (size (predict (Mdl, X(1:4,:))), [4, 3]); %! assert_equal (size (loss (Mdl, X, Y)), [1, 3]); %!test %! ## selectModels keeps the strengths it is given and drops the rest %! Mdl = RegressionLinear (X, Y, 'Lambda', [0.001, 0.01, 0.1]); %! sub = selectModels (Mdl, [1, 3]); %! assert_equal (sub.Lambda, [0.001, 0.1]); %! assert_equal (sub.Beta, Mdl.Beta(:,[1, 3])); %!test %! ## A lasso penalty drives coefficients to exactly zero %! Mdl = RegressionLinear (X, Y, 'Learner', 'leastsquares', ... %! 'Regularization', 'lasso', 'Lambda', 100); %! assert_equal (Mdl.Regularization, 'lasso (L1)'); %! assert_equal (sum (Mdl.Beta == 0) > 0, true); %!test %! ## With the penalty all but switched off, a least squares fit is ordinary %! ## least squares, whichever solver ran. This is what pins the solvers to %! ## a value that is known independently of either of them. %! b = [ones(rows (X), 1), X] \ Y; %! Ml = RegressionLinear (X, Y, 'Learner', 'leastsquares', ... %! 'Regularization', 'lasso', 'Lambda', 1e-8, ... %! 'BetaTolerance', 0, 'GradientTolerance', 1e-14, ... %! 'IterationLimit', 200000); %! Mr = RegressionLinear (X, Y, 'Learner', 'leastsquares', ... %! 'Solver', 'lbfgs', 'Lambda', 1e-8, ... %! 'BetaTolerance', 0, 'GradientTolerance', 1e-14, ... %! 'IterationLimit', 200000); %! ## Relative, so every coefficient is held to the same number of figures: %! ## an absolute tolerance is far stricter on the largest of them, and how %! ## close a solver stops to the closed form answer varies with the BLAS. %! assert_equal (Ml.Beta, b(2:end), -1e-3); %! assert_equal (Ml.Bias, b(1), 1e-4); %! assert_equal (Mr.Beta, b(2:end), -1e-3); %! assert_equal (Mr.Bias, b(1), 1e-4); %!test %! ## A response transform reaches predict %! Mdl = RegressionLinear (X, Y, 'Learner', 'leastsquares'); %! plain = predict (Mdl, X(1:5,:)); %! Mdl.ResponseTransform = 'exp'; %! assert_equal (Mdl.ResponseTransform, 'exp'); %! assert_equal (predict (Mdl, X(1:5,:)), exp (plain), 1e-12); %!test %! ## FitBias false leaves the intercept at zero %! Mdl = RegressionLinear (X, Y, 'FitBias', false); %! assert_equal (Mdl.Bias, 0); %!test %! ## Observations may be given down the columns instead %! Mr = RegressionLinear (X, Y, 'Learner', 'leastsquares'); %! Mc = RegressionLinear (X', Y, 'Learner', 'leastsquares', ... %! 'ObservationsIn', 'columns'); %! assert_equal (Mr.Beta, Mc.Beta, 1e-12); %!test %! ## A row with a missing predictor or a missing response is dropped, and %! ## Lambda follows the count that survived %! Xn = X; %! Xn(3,2) = NaN; %! Mdl = RegressionLinear (Xn, Y); %! assert_equal (Mdl.Lambda, 1 / 92, 1e-15); %!test %! ## A saved model reads back as the same model %! Mdl = RegressionLinear (X, Y, 'Learner', 'leastsquares'); %! fname = tempname (); %! savemodel (Mdl, fname); %! Mnew = loadmodel (fname); %! delete (fname); %! assert_equal (class (Mnew), 'RegressionLinear'); %! assert_equal (Mnew.Beta, Mdl.Beta); %! assert_equal (predict (Mnew, X(1:5,:)), predict (Mdl, X(1:5,:))); %!test %! ## LossTolerance: the engine's loss test is on the objective VALUE, not on %! ## its change, so linearSolve must switch it off with -Inf. This fit is %! ## the one that catches it: an exact linear relation at a vanishing %! ## penalty drives the objective through 1e-6 long before the coefficients %! ## are right, and with the engine's default the fit stops five iterations %! ## early with them out by 1.6e-4. If this test starts failing, look at %! ## opt.LossTolerance in linearSolve before anything else. %! randn ('seed', 7); %! Xe = randn (60, 3); %! btrue = [2; -3; 0.5]; %! Ye = Xe * btrue + 4; %! Mdl = RegressionLinear (Xe, Ye, 'Learner', 'leastsquares', ... %! 'Solver', 'lbfgs', 'Lambda', 1e-10, ... %! 'BetaTolerance', 0, 'GradientTolerance', 1e-14, ... %! 'IterationLimit', 20000); %! assert_equal (Mdl.Beta, btrue, 1e-8); %! assert_equal (Mdl.Bias, 4, 1e-8); %!test %! ## HessianHistorySize reaches the solver rather than only being recorded: %! ## a one-pair history takes several times the iterations a fifteen-pair %! ## one does on the same problem. Fifteen is MATLAB's documented default %! ## for fitrlinear, where the engine's own default is ten. %! bf = RegressionLinear (X, Y); %! assert_equal (bf.ModelParameters.Solver, {'bfgs'}); %! assert_equal (bf.ModelParameters.HessianHistorySize, []); %! lb = RegressionLinear (X, Y, 'Solver', 'lbfgs'); %! assert_equal (lb.ModelParameters.HessianHistorySize, 15); %! opts = {'Learner', 'leastsquares', 'Solver', 'lbfgs', 'BetaTolerance', 0, ... %! 'GradientTolerance', 1e-10, 'IterationLimit', 5000}; %! short = RegressionLinear (X, Y, opts{:}, 'HessianHistorySize', 1); %! long = RegressionLinear (X, Y, opts{:}, 'HessianHistorySize', 15); %! assert_equal (short.FitInfo_.NumIterations ... %! > long.FitInfo_.NumIterations, true); %!test %! ## The regression counterpart keeps the documented 0.1 where the %! ## classifier takes 1, confirmed on R2026a; the convergence check count %! ## is 2 on both, where the documentation says 5 %! Mdl = RegressionLinear (X, Y, 'Solver', 'dual'); %! assert_equal (Mdl.ModelParameters.DeltaGradientTolerance, 0.1); %! assert_equal (Mdl.ModelParameters.NumCheckConvergence, 2); %! assert_equal (Mdl.ModelParameters.PassLimit, 10); ## Test input validation %!error RegressionLinear (ones (5, 2)) %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'Learner') %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'Learner', 'logistic') %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'Epsilon', -1) %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'Learner', 'leastsquares', ... %! 'Epsilon', 1) %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'Regularization', 'elastic') %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'Lambda', Inf) %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'Solver', 'newton') %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'IterationLimit', 0) %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'Nonsense', 1) %!error RegressionLinear ({1, 2; 3, 4}, [1; 2]) %!error RegressionLinear ([], []) %!error ... %! RegressionLinear (ones (10, 2), {1, 2}) %!error ... %! RegressionLinear (ones (10, 2), ones (3, 1)) %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'Weights', ones (3, 1)) %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'Regularization', 'ridge', ... %! 'Solver', 'sparsa') %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'Regularization', 'lasso', ... %! 'Solver', 'lbfgs') %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'Learner', 'leastsquares', ... %! 'Solver', 'dual') %!error ... %! RegressionLinear (ones (10, 2), ones (10, 1), 'PredictorNames', {'a'}) %!error ... %! predict (RegressionLinear (ones (10, 2), ones (10, 1))) %!error ... %! predict (RegressionLinear (ones (10, 2), ones (10, 1)), []) %!error ... %! predict (RegressionLinear (ones (10, 2), ones (10, 1)), ones (3, 5)) %!error ... %! loss (RegressionLinear (ones (10, 2), ones (10, 1)), ones (10, 2)) %!error ... %! loss (RegressionLinear (ones (10, 2), ones (10, 1)), ones (10, 2), ... %! ones (10, 1), 'LossFun', 'hinge') %!error ... %! loss (RegressionLinear (ones (10, 2), ones (10, 1), 'Learner', ... %! 'leastsquares'), ones (10, 2), ones (10, 1), ... %! 'LossFun', 'epsiloninsensitive') %!error ... %! loss (RegressionLinear (ones (10, 2), ones (10, 1)), ones (10, 2), ... %! ones (3, 1)) %!error ... %! selectModels (RegressionLinear (ones (10, 2), ones (10, 1), 'Lambda', ... %! [0.1, 0.2]), 5) ## Every documented response transform reaches the response that is reported. %!test %! load fisheriris %! Mdl = fitrlinear (meas(:,2:4), meas(:,1)); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! T = {'identity', @(x) x; 'exp', @(x) exp (x); 'log', @(x) log (x)}; %! for i = 1:rows (T) %! Mdl.ResponseTransform = T{i,1}; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, T{i,2}(raw), 1e-12); %! endfor ## A function handle is taken as given and applied to the response. %!test %! load fisheriris %! Mdl = fitrlinear (meas(:,2:4), meas(:,1)); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! Mdl.ResponseTransform = @(x) x .^ 2; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, raw .^ 2, 1e-12); statistics-release-1.9.2/inst/Supervised_Learning/RegressionNeuralNetwork.m000066400000000000000000002566701524624707500273330ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} RegressionNeuralNetwork (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{obj} =} RegressionNeuralNetwork (@dots{}, @var{name}, @var{value}) ## ## Create a @qcode{RegressionNeuralNetwork} object containing a neural network ## regression model. ## ## @code{@var{obj} = RegressionNeuralNetwork (@var{X}, @var{Y})} returns a ## neural network regression model, @var{obj}, with @var{X} being the predictor ## data and @var{Y} the continuous response of the observations in @var{X}. ## ## @itemize ## @item ## @var{X} must be an @math{NxP} numeric matrix of predictor data, where rows ## correspond to observations and columns to features. ## @item ## @var{Y} must be an @math{Nx1} numeric vector holding the response of the ## corresponding predictor data in @var{X}. @var{Y} must have the same number ## of rows as @var{X}. ## @end itemize ## ## The network is trained against the mean squared error, and its output layer ## applies the identity, so a prediction is an unrestricted real number rather ## than a score over classes. This is the only difference in the engine ## between this class and @code{ClassificationNeuralNetwork}; everything else, ## the layer sizes, the activations, the learning rate and the initialisation, ## behaves identically. ## ## @code{@var{obj} = RegressionNeuralNetwork (@dots{}, @var{name}, ## @var{value})} returns a model with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.32 0.68 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Standardize'} @tab A logical scalar specifying whether the ## predictor data should be centred and scaled before training. The same ## transformation is applied by @code{predict}. The default is @qcode{false}. ## ## @item @qcode{'PredictorNames'} @tab A cell array of character vectors ## naming the predictors, in the order they appear in @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector naming the response. ## The default is @qcode{'Y'}. ## ## @item @qcode{'ResponseTransform'} @tab A character vector naming one of the ## supported transformations, or a function handle, applied to the predicted ## response by @code{predict} and @code{resubPredict}. The default is ## @qcode{'none'}. ## ## @item @qcode{'LayerSizes'} @tab A positive integer vector specifying the ## number of units in each fully connected hidden layer. The default is 10, ## one hidden layer of ten units. ## ## @item @qcode{'Activations'} @tab A character vector or cell array of ## character vectors specifying the activation of the hidden layers. The ## supported functions are @qcode{'linear'}, @qcode{'sigmoid'}, ## @qcode{'relu'}, @qcode{'tanh'}, @qcode{'lrelu'}, @qcode{'prelu'}, ## @qcode{'elu'}, @qcode{'gelu'} and @qcode{'none'}. The default is ## @qcode{'relu'}. ## ## @item @qcode{'OutputLayerActivation'} @tab A character vector specifying ## the activation of the output layer. The default is @qcode{'none'}, the ## identity, which is what a regression output calls for. The supported ## values are the same as for @qcode{'Activations'}. ## ## @item @qcode{'LearningRate'} @tab A positive scalar specifying the learning ## rate for gradient descent. The default is 0.003. A larger rate can drive ## every unit of a hidden layer negative, after which a rectifier passes no ## gradient and the network stops training. ## Applies only when @qcode{'Solver'} is @qcode{'sgd'}. ## ## @item @qcode{'Solver'} @tab A character vector naming the solver that ## trains the network, either @qcode{'lbfgs'} or @qcode{'sgd'}. The ## default is @qcode{'lbfgs'}, which minimizes the loss over the whole ## training set at once by limited-memory BFGS, as MATLAB does. It takes ## no learning rate, stops on the three tolerances below, and reaches a ## lower training loss in fewer passes over the data, though each of its ## iterations costs several passes where an epoch costs one. ## @qcode{'sgd'} visits the samples one at a time and steps down the ## gradient of each, running for @qcode{'IterationLimit'} epochs; it was ## the default before version 1.9.0. ## ## @item @qcode{'GradientTolerance'} @tab A nonnegative scalar. Training ## stops once the gradient's infinity norm falls to or below it, which is ## the quantity MATLAB tests too. The default is @qcode{1e-6}. Applies ## only when @qcode{'Solver'} is @qcode{'lbfgs'}. ## ## @item @qcode{'StepTolerance'} @tab A nonnegative scalar. Training ## stops once the step's infinity norm falls to or below it, which is the ## quantity MATLAB tests too. The default is @qcode{1e-6}. Applies only ## when @qcode{'Solver'} is @qcode{'lbfgs'}. ## ## @item @qcode{'LossTolerance'} @tab A real scalar. Training stops once ## the training loss falls to or below it. The test is on the loss ## itself and not on its change, matching MATLAB; pass @code{-Inf} to ## switch it off. The default is @qcode{1e-6}. Applies only when ## @qcode{'Solver'} is @qcode{'lbfgs'}. ## ## @item @qcode{'IterationLimit'} @tab A positive integer specifying the ## maximum number of training iterations. The default is 1000. ## Under @qcode{'sgd'} this counts epochs, under ## @qcode{'lbfgs'} solver iterations. ## ## @item @qcode{'DisplayInfo'} @tab A logical scalar specifying whether to ## print information during training. The default is @qcode{false}. ## @end multitable ## ## The supported values for @qcode{'ResponseTransform'} are: ## ## @multitable @columnfractions 0.3 0.7 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'exp'} @tab @math{exp (x)} ## @item @qcode{'log'} @tab @math{log (x)} ## @end multitable ## ## @seealso{fitrnet, ClassificationNeuralNetwork, fcnntrain, fcnnpredict} ## @end deftypefn classdef RegressionNeuralNetwork properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} X ## ## Predictor data ## ## An @math{NxP} numeric matrix, as it was supplied to the constructor, ## before any rows carrying missing values were dropped. This property is ## read-only. ## ## @end deftp X = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} Y ## ## Response data ## ## An @math{Nx1} numeric vector, as it was supplied to the constructor. ## This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} NumObservations ## ## Number of observations used to train the model ## ## A positive integer scalar, counting only the rows that survived the ## removal of missing values. This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} RowsUsed ## ## Rows used for fitting ## ## A logical column vector with the same length as the observations in the ## original predictor data @var{X}, true for each row that was used for ## fitting the RegressionNeuralNetwork model. It is empty, @qcode{[]}, ## when every observation was used, so a non-empty value means that rows ## holding missing values were dropped. This property is read-only. ## ## @end deftp RowsUsed = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} NumPredictors ## ## Number of predictors ## ## A positive integer scalar, the number of columns of @code{X}. This ## property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} PredictorNames ## ## Names of the predictors ## ## A cell array of character vectors, one per column of @code{X}. This ## property is read-only. ## ## @end deftp PredictorNames = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} ResponseName ## ## Name of the response variable ## ## A character vector. This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} Sigma ## ## Standard deviation of the predictors ## ## A row vector with one entry per predictor, used for standardization. ## Empty when the predictor data were not standardized. This property is ## read-only. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} Mu ## ## Mean of the predictors ## ## A row vector with one entry per predictor, used for standardization. ## Empty when the predictor data were not standardized. This property is ## read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} LayerSizes ## ## Sizes of the fully connected hidden layers ## ## A row vector of positive integers, one per hidden layer. It does not ## include the output layer, whose width is the number of responses. This ## property is read-only. ## ## @end deftp LayerSizes = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} Activations ## ## Activation functions of the hidden layers ## ## A character vector, applying to every hidden layer, or a cell array of ## character vectors with one entry per hidden layer. This property is ## read-only. ## ## @end deftp Activations = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} OutputLayerActivation ## ## Activation function of the output layer ## ## A character vector. The default, @qcode{'none'}, applies the identity, ## so a prediction is an unrestricted real number. This property is ## read-only. ## ## @end deftp OutputLayerActivation = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} LearningRate ## ## Learning rate for gradient descent ## ## A positive scalar value defining the learning rate used by the gradient ## descent algorithm during training. This property is read-only. ## ## @end deftp LearningRate = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} IterationLimit ## ## Maximum number of training iterations ## ## A positive integer scalar. This property is read-only. ## ## @end deftp IterationLimit = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} ModelParameters ## ## Parameters of the trained network ## ## A structure holding the fit as it was asked for: @qcode{LayerSizes}, ## @qcode{Activations}, @qcode{OutputLayerActivation}, ## @qcode{LayerWeightsInitializers}, @qcode{Solver}, ## @qcode{LearningRate}, @qcode{IterationLimit}, ## @qcode{GradientTolerance}, @qcode{LossTolerance}, ## @qcode{StepTolerance}, @qcode{DisplayInfo}, @qcode{StandardizeData}, ## and the @qcode{Version}, @qcode{Method} and @qcode{Type} tags. ## ## What came out of the fit is elsewhere: the @qcode{LayerWeights} and ## @qcode{LayerBiases} properties hold the network, @qcode{TrainingHistory} ## the series and @qcode{ConvergenceInfo} where it stopped. ## ## @qcode{LayerWeightsInitializers} names the scheme each layer's weights ## were drawn with, the output layer last: @qcode{'he'} for a rectifying ## activation and @qcode{'glorot'} for a symmetric one. It is a report, ## not a setting, the engine choosing per layer from the activation and ## offering no way to override it. ## ## @qcode{OutputLayerActivation}, @qcode{Solver} and @qcode{LearningRate} ## are this package's own; MATLAB has no counterpart for them. The fields ## it reports that this class does not accept as arguments ## (@qcode{Lambda}, the validation set and its patience and frequency, ## @qcode{InitialStepSize} and the two initializer settings) are absent. ## This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} ConvergenceInfo ## ## Information recorded during training ## ## A structure with the fields @code{Time}, the seconds training took, and ## @code{TrainingLoss}, the mean squared error of the network at the end of ## each iteration. This property is read-only. ## ## ## Under @qcode{'lbfgs'} the structure carries @code{Gradient} and ## @code{Step}, the two quantities the solver measured to decide it had ## converged, and @code{ConvergenceCriterion}, naming the test that ## stopped it. It carries no @code{Accuracy}: MATLAB reports none, and ## measuring it would cost a pass over the whole training set at every ## iteration. ## @end deftp ConvergenceInfo = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} DisplayInfo ## ## Whether training printed its progress ## ## A logical scalar. This property is read-only. ## ## @end deftp DisplayInfo = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} Solver ## ## Solver used to train the network ## ## A character vector, either @qcode{'Gradient Descent'} for the ## stochastic solver or @qcode{'LBFGS'} for the full-batch one. ## This property is read-only. ## ## @end deftp Solver = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} LayerWeights ## ## Weights the network learned ## ## A cell array with one entry per layer, the output layer included. ## @code{LayerWeights@{i@}} has one row per unit of layer @math{i} and one ## column per input to that layer. This property is read-only. ## ## @end deftp LayerWeights = {}; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} LayerBiases ## ## Biases the network learned ## ## A cell array with one entry per layer, the output layer included. ## @code{LayerBiases@{i@}} is a column with one entry per unit of layer ## @math{i}. This property is read-only. ## ## @end deftp LayerBiases = {}; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} TrainingHistory ## ## Iteration by iteration record of the fit ## ## A @code{table} with the variables @code{Iteration} and ## @code{TrainingLoss}, one row per training iteration. This property is ## read-only. ## ## ## The columns follow the solver. Under @qcode{'sgd'} they are ## @code{Iteration} and @code{TrainingLoss}, with @code{TrainingAccuracy} ## for a classifier. Under @qcode{'lbfgs'} they are @code{Iteration}, ## @code{TrainingLoss}, @code{Gradient} and @code{Step}, as MATLAB's are. ## @end deftp TrainingHistory = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} W ## ## Observation weights ## ## A numeric column vector with one entry per training observation. It ## defaults to a uniform weight for every observation. This property is ## read-only. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector of column indices into @code{X} naming the predictors ## treated as categorical, and empty when none is. This property is ## read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} ExpandedPredictorNames ## ## Names of the predictors as the model expanded them ## ## A cell array of character vectors. It matches @code{PredictorNames} ## unless a categorical predictor was expanded into indicator variables. ## This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} BinEdges ## ## Bin edges of the predictors ## ## A cell array with one entry per predictor, holding that predictor's bin ## edges where the learner discretized it before fitting. It is empty here ## and stays empty: this learner fits the predictors as they are, and ## MATLAB's reports an empty cell for it as well. ## ## This property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} HyperparameterOptimizationResults ## ## Results of the hyperparameter optimization ## ## @strong{Always empty.} It is declared for MATLAB compatibility, where ## it holds what an automatic search over the hyperparameters found. This ## class fits the parameters it is given and runs no such search, so there ## is nothing to report. This property is read-only. ## ## @end deftp HyperparameterOptimizationResults = []; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {RegressionNeuralNetwork} {property} ResponseTransform ## ## Transformation applied to the predicted response ## ## A function handle, applied by @code{predict} and @code{resubPredict} to ## the network's output. It defaults to the identity and may be set after ## construction, either to a handle or to the name of a supported ## transformation. ## ## @end deftp ResponseTransform = 'none'; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) RTfun = @(y) y; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.ResponseTransform (this, val) name = 'RegressionNeuralNetwork'; [this.RTfun, this.ResponseTransform] = ... parseResponseTransform (val, name); endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n RegressionNeuralNetwork\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); fprintf ("%+25s: %d\n", 'NumObservations', this.NumObservations); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); str = repmat ({'%d'}, 1, numel (this.LayerSizes)); str = strcat ('[', strjoin (str, ' '), ']'); str = sprintf (str, this.LayerSizes); fprintf ("%+25s: %s\n", 'LayerSizes', str); if (iscellstr (this.Activations)) str = repmat ({'''%s'''}, 1, numel (this.Activations)); str = strcat ('{', strjoin (str, ' '), '}'); str = sprintf (str, this.Activations{:}); fprintf ("%+25s: %s\n", 'Activations', str); else # character vector fprintf ("%+25s: '%s'\n", 'Activations', this.Activations); endif fprintf ("%+25s: '%s'\n", 'OutputLayerActivation', ... this.OutputLayerActivation); fprintf ("%+25s: '%s'\n", 'ResponseTransform', this.ResponseTransform); fprintf ("%+25s: '%s'\n", 'Solver', this.Solver); endfunction endmethods methods(Access = public) ## -*- texinfo -*- ## @deftypefn {RegressionNeuralNetwork} {@var{obj} =} RegressionNeuralNetwork (@var{X}, @var{Y}) ## @deftypefnx {RegressionNeuralNetwork} {@var{obj} =} RegressionNeuralNetwork (@dots{}, @var{name}, @var{value}) ## ## Create a @qcode{RegressionNeuralNetwork} object containing a neural ## network regression model. ## ## See the class documentation for the accepted @qcode{Name-Value} pairs. ## ## @seealso{fitrnet, RegressionNeuralNetwork} ## @end deftypefn function this = RegressionNeuralNetwork (X, Y, varargin) ## Check for sufficient number of input arguments if (nargin < 2) error ("RegressionNeuralNetwork: too few input arguments."); endif ## Check X and Y have the same number of observations if (rows (X) != rows (Y)) error (strcat ("RegressionNeuralNetwork: number of", ... " rows in X and Y must be equal.")); endif ## The response is continuous, so it must be numeric: a cellstr or a ## categorical response belongs to a classifier, and accepting one here ## would report a number for a question the model cannot answer. if (! (isnumeric (Y) && isreal (Y))) error (strcat ("RegressionNeuralNetwork: Y must be a", ... " real numeric vector.")); endif if (! (isvector (Y) || isempty (Y))) error ("RegressionNeuralNetwork: Y must be a vector."); endif ## Assign original X and Y data to the RegressionNeuralNetwork object this.X = X; this.Y = Y; ## Set default values before parsing optional parameters Standardize = false; ResponseName = []; PredictorNames = []; LayerSizes = 10; Activations = 'relu'; OutputLayerActivation = 'none'; LearningRate = 0.003; IterationLimit = 1000; DisplayInfo = false; Solver = 'lbfgs'; GradientTolerance = 1e-6; LossTolerance = 1e-6; StepTolerance = 1e-6; ## Which of the solver-specific options the caller actually named, so ## that one meant for the other solver can be refused by name. GivenTols = {}; LearningRateGiven = false; ## Supported activation functions. 'none' is MATLAB's name for the ## identity and is what a regression output layer wants. acList = {'linear', 'none', 'sigmoid', 'relu', 'tanh', ... 'lrelu', 'prelu', 'elu', 'gelu'}; ## Parse extra parameters while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'standardize' Standardize = varargin{2}; if (! (Standardize == true || Standardize == false)) error (strcat ("RegressionNeuralNetwork:", ... " 'Standardize' must be either true or false.")); endif case 'predictornames' PredictorNames = varargin{2}; if (! iscellstr (PredictorNames)) error (strcat ("RegressionNeuralNetwork: 'PredictorNames'", ... " must be supplied as a cellstring array.")); elseif (columns (PredictorNames) != columns (X)) error (strcat ("RegressionNeuralNetwork: 'PredictorNames'", ... " must have the same number of columns as X.")); endif case 'responsename' ResponseName = varargin{2}; if (! ischar (ResponseName)) error (strcat ("RegressionNeuralNetwork: 'ResponseName'", ... " must be a character vector.")); endif case 'responsetransform' name = 'RegressionNeuralNetwork'; [this.RTfun, this.ResponseTransform] = ... parseResponseTransform (varargin{2}, name); case 'layersizes' LayerSizes = varargin{2}; if (! (isnumeric (LayerSizes) && isvector (LayerSizes) && all (LayerSizes > 0) && all (mod (LayerSizes, 1) == 0))) error (strcat ("RegressionNeuralNetwork: 'LayerSizes'", ... " must be a positive integer vector.")); endif case 'learningrate' LearningRate = varargin{2}; LearningRateGiven = true; if (! (isnumeric (LearningRate) && isscalar (LearningRate) && LearningRate > 0)) error (strcat ("RegressionNeuralNetwork:", ... " 'LearningRate' must be a positive scalar.")); endif case 'activations' Activations = varargin{2}; if (! (ischar (Activations) || iscellstr (Activations))) error (strcat ("RegressionNeuralNetwork: 'Activations'", ... " must be a character vector or a cellstring vector.")); endif if (ischar (Activations)) if (! any (strcmpi (Activations, acList))) error (strcat ("RegressionNeuralNetwork: unsupported", ... " 'Activation' function.")); endif else if (! all (cell2mat (cellfun (@(x) any (strcmpi (x, acList)), Activations, 'UniformOutput', false)))) error (strcat ("RegressionNeuralNetwork: unsupported", ... " 'Activation' functions.")); endif endif Activations = tolower (Activations); case 'outputlayeractivation' OutputLayerActivation = varargin{2}; if (! (ischar (OutputLayerActivation))) error (strcat ("RegressionNeuralNetwork:", ... " 'OutputLayerActivation' must be a character vector.")); endif if (! any (strcmpi (OutputLayerActivation, acList))) error (strcat ("RegressionNeuralNetwork: unsupported", ... " 'OutputLayerActivation' function.")); endif OutputLayerActivation = tolower (OutputLayerActivation); case 'iterationlimit' IterationLimit = varargin{2}; if (! (isnumeric (IterationLimit) && isscalar (IterationLimit) && (IterationLimit > 0) && mod (IterationLimit, 1) == 0)) error (strcat ("RegressionNeuralNetwork:", ... " 'IterationLimit' must be a positive integer.")); endif case 'solver' Solver = varargin{2}; if (! (ischar (Solver) && any (strcmpi (Solver, {'sgd', ... 'lbfgs'})))) error (strcat ("RegressionNeuralNetwork: 'Solver' must", ... " be either 'sgd' or 'lbfgs'.")); endif Solver = tolower (Solver); case 'gradienttolerance' GradientTolerance = varargin{2}; GivenTols{end+1} = 'GradientTolerance'; if (! (isnumeric (GradientTolerance) && isscalar (GradientTolerance) && GradientTolerance >= 0)) error (strcat ("RegressionNeuralNetwork:", ... " 'GradientTolerance' must be a nonnegative", ... " scalar.")); endif case 'losstolerance' LossTolerance = varargin{2}; GivenTols{end+1} = 'LossTolerance'; if (! (isnumeric (LossTolerance) && isscalar (LossTolerance) && ! isnan (LossTolerance))) error (strcat ("RegressionNeuralNetwork:", ... " 'LossTolerance' must be a real scalar.")); endif case 'steptolerance' StepTolerance = varargin{2}; GivenTols{end+1} = 'StepTolerance'; if (! (isnumeric (StepTolerance) && isscalar (StepTolerance) && StepTolerance >= 0)) error (strcat ("RegressionNeuralNetwork:", ... " 'StepTolerance' must be a nonnegative", ... " scalar.")); endif case 'displayinfo' DisplayInfo = varargin{2}; if (! (DisplayInfo == true || DisplayInfo == false)) error (strcat ("RegressionNeuralNetwork: 'DisplayInfo'", ... " must be either true or false.")); endif otherwise error (strcat ("RegressionNeuralNetwork: invalid",... " parameter name in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## Generate default predictors and response variable names (if necessary) NumPredictors = columns (X); if (isempty (PredictorNames)) for i = 1:NumPredictors PredictorNames {i} = strcat ("x", num2str (i)); endfor endif if (isempty (ResponseName)) ResponseName = 'Y'; endif ## Assign predictors and response variable names this.NumPredictors = NumPredictors; this.PredictorNames = PredictorNames; this.ExpandedPredictorNames = PredictorNames; this.ResponseName = ResponseName; ## An observation is dropped only when its response is missing. A row ## whose predictors hold missing values is kept and reported as used, ## while the fit below draws on the complete observations alone. RowsUsed = ! isnan (Y(:)); Yret = Y(RowsUsed); Xret = X(RowsUsed, :); this.X = Xret; this.Y = Yret; cobs = ! any (isnan (Xret), 2); Y = Yret(cobs); X = Xret(cobs, :); ## Check X and Y contain valid data if (! (isnumeric (X) && isfinite (X))) error ("RegressionNeuralNetwork: invalid values in X."); endif if (isempty (Y)) error ("RegressionNeuralNetwork: Y cannot be empty."); endif if (! all (isfinite (Y))) error ("RegressionNeuralNetwork: invalid values in Y."); endif ## Assign the number of observations and their corresponding indices ## on the original data, which will be used for training the model this.NumObservations = rows (this.X); ## RowsUsed is left empty when every observation was used, as in MATLAB if (all (RowsUsed)) this.RowsUsed = []; else this.RowsUsed = RowsUsed; endif ## Every observation carries the same weight this.W = ones (this.NumObservations, 1) / this.NumObservations; ## No predictor is treated as categorical, so the expanded names are ## the predictor names themselves. this.CategoricalPredictors = []; ## Handle the Standardize option. The network must be trained on the ## scale it predicts on, so X is transformed here as well as in ## predict. if (Standardize) this.Sigma = std (X, [], 1); this.Sigma(this.Sigma == 0) = 1; # predictor is constant this.Mu = mean (X, 1); X = (X - this.Mu) ./ this.Sigma; else this.Sigma = []; this.Mu = []; endif ## An option that cannot act is refused rather than ignored. The three ## tolerances are how the lbfgs solver decides it has converged and mean ## nothing to the epoch loop, which runs to 'IterationLimit' whatever ## they say; a learning rate is what the epoch loop scales its step by ## and means nothing to a line search, which finds its own. if (strcmp (Solver, 'sgd') && ! isempty (GivenTols)) error (strcat ("RegressionNeuralNetwork: '", GivenTols{1}, ... "' applies only when 'Solver' is 'lbfgs'.")); endif if (strcmp (Solver, 'lbfgs') && LearningRateGiven) error (strcat ("RegressionNeuralNetwork: 'LearningRate'", ... " applies only when 'Solver' is 'sgd'.")); endif if (strcmp (Solver, 'lbfgs')) this.Solver = 'LBFGS'; else this.Solver = 'Gradient Descent'; endif ## Store training parameters this.LayerSizes = LayerSizes; this.Activations = Activations; this.OutputLayerActivation = OutputLayerActivation; this.LearningRate = LearningRate; this.IterationLimit = IterationLimit; this.DisplayInfo = DisplayInfo; ## Start the training process. LossFunction 2 is the mean squared error ## over a continuous response, which is what regression trains against. NumThreads = nproc (); rnn_timer_ = tic; SolverOptions = struct ('Solver', Solver, ... 'GradientTolerance', GradientTolerance, ... 'LossTolerance', LossTolerance, ... 'StepTolerance', StepTolerance); ## The engine names the layers itself; this check stays here so the ## count is reported under the class rather than under fcnntrain. if (! ischar (Activations) && numel (LayerSizes) != numel (Activations)) error (strcat ("RegressionNeuralNetwork: 'Activations'", ... " vector does not match the number of layers.")); endif Mdl = fcnntrain (X, Y(:), LayerSizes, Activations, ... OutputLayerActivation, NumThreads, ... LearningRate, IterationLimit, DisplayInfo, 2, ... SolverOptions); ## The solver records a value per iteration. The series belongs to the ## history and ConvergenceInfo reports where the fit ended up, as ## MATLAB divides them; SERIES carries the columns to both. series = struct ('TrainingLoss', Mdl.Loss(:)); Mdl = rmfield (Mdl, 'Loss'); ## The lbfgs solver also reports the gradient and step it measured to ## decide it had stopped, and which test stopped it, as MATLAB does. criterion = ''; if (strcmp (Solver, 'lbfgs')) series.Gradient = Mdl.Gradient(:); series.Step = Mdl.Step(:); criterion = Mdl.Criterion; Mdl = rmfield (Mdl, {'Gradient', 'Step', 'Criterion'}); endif ConvergenceInfo = convergenceStruct_ (series, toc (rnn_timer_), ... criterion); ## The fit as it was asked for. What came out of it is the ## LayerWeights and LayerBiases properties, TrainingHistory and ## ConvergenceInfo; this structure holds what went in. The weight ## initializer of each layer is decided by that layer's activation ## inside the engine and cannot be chosen, so it is recorded rather ## than taken from an argument. initz = fcnnInitializers (this.Activations, numel (this.LayerSizes), ... this.OutputLayerActivation); this.ModelParameters = struct ( ... 'LayerSizes', this.LayerSizes, ... 'Activations', {this.Activations}, ... 'OutputLayerActivation', this.OutputLayerActivation, ... 'LayerWeightsInitializers', {initz}, ... 'Solver', this.Solver, ... 'LearningRate', this.LearningRate, ... 'IterationLimit', this.IterationLimit, ... 'GradientTolerance', GradientTolerance, ... 'LossTolerance', LossTolerance, ... 'StepTolerance', StepTolerance, ... 'DisplayInfo', logical (this.DisplayInfo), ... 'StandardizeData', logical (Standardize), ... 'Version', 1, 'Method', 'NeuralNetwork', ... 'Type', 'regression'); this.ConvergenceInfo = ConvergenceInfo; ## fcnntrain packs each neuron as [weights, bias] in one row, so the ## last column of every layer's matrix is its bias. nlay = numel (Mdl.LayerWeights); this.LayerWeights = cell (1, nlay); this.LayerBiases = cell (1, nlay); for i = 1:nlay Wb = Mdl.LayerWeights{i}; this.LayerWeights{i} = Wb(:, 1:end-1); this.LayerBiases{i} = Wb(:, end); endfor ## Iteration by iteration record of the fit this.TrainingHistory = trainingTable_ (series); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionNeuralNetwork} {@var{yFit} =} predict (@var{obj}, @var{XC}) ## ## Predict the response for new data with a neural network regression ## model. ## ## @code{@var{yFit} = predict (@var{obj}, @var{XC})} returns a column ## vector holding the predicted response for each row of @var{XC}, using ## the network stored in @var{obj}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{RegressionNeuralNetwork} class object. ## @item ## @var{XC} must be a numeric matrix with the same number of predictors as ## the data the model was trained on. ## @end itemize ## ## The transformation named by @code{ResponseTransform} is applied to the ## network's output before it is returned. ## ## @seealso{RegressionNeuralNetwork, fitrnet} ## @end deftypefn function yFit = predict (this, XC) ## Check for sufficient input arguments if (nargin < 2) error ("RegressionNeuralNetwork.predict: too few input arguments."); endif ## Check for valid XC if (isempty (XC)) error ("RegressionNeuralNetwork.predict: XC is empty."); elseif (this.NumPredictors != columns (XC)) error (strcat ("RegressionNeuralNetwork.predict: XC must have", ... " the same number of predictors as the trained model.")); endif ## Standardize (if necessary) if (! isempty (this.Mu)) XC = (XC - this.Mu) ./ this.Sigma; endif ## The network's output is its second return value: the first is an ## index of the largest output, which a single regression unit makes ## constant and meaningless. NumThreads = nproc (); [~, yFit] = fcnnpredict (this.LayerWeights, this.LayerBiases, ... this.Activations, ... this.OutputLayerActivation, ... XC, NumThreads); ## Apply ResponseTransform yFit = this.RTfun (yFit); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionNeuralNetwork} {@var{yFit} =} resubPredict (@var{obj}) ## ## Predict the response of the training data with a neural network ## regression model. ## ## @code{@var{yFit} = resubPredict (@var{obj})} returns a column vector ## holding the predicted response for every observation the model was ## trained on, that is the rows of @code{obj.X} selected by ## @code{obj.RowsUsed}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{RegressionNeuralNetwork} class object. ## @end itemize ## ## @seealso{RegressionNeuralNetwork, fitrnet} ## @end deftypefn function yFit = resubPredict (this) ## Get used rows XC = this.X; ## Standardize (if necessary) if (! isempty (this.Mu)) XC = (XC - this.Mu) ./ this.Sigma; endif NumThreads = nproc (); [~, yFit] = fcnnpredict (this.LayerWeights, this.LayerBiases, ... this.Activations, ... this.OutputLayerActivation, ... XC, NumThreads); ## Apply ResponseTransform yFit = this.RTfun (yFit); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionNeuralNetwork} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {RegressionNeuralNetwork} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Compute the regression loss of a neural network model. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} returns the ## weighted mean squared error between the response @var{Y} and the ## response the model predicts for @var{X}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{RegressionNeuralNetwork} class object. ## @item ## @var{X} must be a numeric matrix with the same number of predictors as ## the data the model was trained on. ## @item ## @var{Y} must be a numeric vector with as many rows as @var{X}. ## @end itemize ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} accepts the ## following @qcode{Name-Value} pairs. ## ## @multitable @columnfractions 0.28 0.72 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'LossFun'} @tab @qcode{'mse'}, the default, or a function ## handle called as @code{@var{lossfun} (@var{Y}, @var{yFit}, @var{W})} ## and returning a scalar. ## ## @item @qcode{'Weights'} @tab A numeric vector of observation weights ## with one entry per row of @var{X}. It defaults to a uniform weight. ## The weights are normalized to sum to one before the loss is formed, so ## scaling them all by the same factor leaves the loss unchanged. ## @end multitable ## ## @seealso{RegressionNeuralNetwork, fitrnet} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("RegressionNeuralNetwork.loss: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("RegressionNeuralNetwork.loss: Name-Value", ... " arguments must be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, 'loss'); ## Defaults, then the optional pairs LossFun = 'mse'; W = []; args = varargin; keep = true (1, numel (args)); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("RegressionNeuralNetwork.loss: parameter name", ... " must be a character vector.")); endif if (strcmpi (args{i}, 'lossfun')) LossFun = args{i+1}; if (! (is_function_handle (LossFun) || (ischar (LossFun) && isrow (LossFun)))) error (strcat ("RegressionNeuralNetwork.loss: 'LossFun' must", ... " be a character vector or a function handle.")); endif if (ischar (LossFun) && ! strcmpi (LossFun, 'mse')) error (strcat ("RegressionNeuralNetwork.loss: unsupported", ... " 'LossFun' value.")); endif keep(i:i+1) = false; endif endfor W = getWeights_ (this, args(keep), rows (X), 'loss'); ## Weights are normalized to sum to one, as MATLAB does, so a loss is ## a weighted average rather than a weighted sum. W = W(:) / sum (W); yFit = predict (this, X); Y = Y(:); if (is_function_handle (LossFun)) L = LossFun (Y, yFit, W); if (! (isnumeric (L) && isscalar (L))) error (strcat ("RegressionNeuralNetwork.loss: 'LossFun' must", ... " return a numeric scalar.")); endif else L = sum (W .* (Y - yFit) .^ 2); endif endfunction ## -*- texinfo -*- ## @deftypefn {RegressionNeuralNetwork} {@var{L} =} resubLoss (@var{obj}) ## @deftypefnx {RegressionNeuralNetwork} {@var{L} =} resubLoss (@dots{}, @var{name}, @var{value}) ## ## Compute the resubstitution regression loss of a neural network model. ## ## @code{@var{L} = resubLoss (@var{obj})} returns the weighted mean ## squared error of the model on the data it was trained on. It accepts ## the same @qcode{Name-Value} pairs as @code{loss}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{RegressionNeuralNetwork} class object. ## @end itemize ## ## @seealso{RegressionNeuralNetwork, fitrnet} ## @end deftypefn function L = resubLoss (this, varargin) used = true (rows (this.X), 1); X = this.X(used, :); Y = this.Y(used); L = loss (this, X, Y, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionNeuralNetwork} {@var{CVMdl} =} crossval (@var{obj}) ## @deftypefnx {RegressionNeuralNetwork} {@var{CVMdl} =} crossval (@dots{}, @var{name}, @var{value}) ## ## Cross validate a neural network regression model. ## ## @code{@var{CVMdl} = crossval (@var{obj})} returns a ## @qcode{RegressionPartitionedModel} holding one refit of @var{obj} per ## fold of a ten-fold partition, or of an @math{n}-fold one where the ## model has fewer than ten observations. ## ## @itemize ## @item ## @var{obj} must be a @qcode{RegressionNeuralNetwork} class object. ## @end itemize ## ## @code{@var{CVMdl} = crossval (@dots{}, @var{name}, @var{value})} ## accepts one, and only one, of the following @qcode{Name-Value} pairs. ## ## @multitable @columnfractions 0.28 0.72 ## @headitem @var{Name} @tab @var{Value} ## @item @qcode{'KFold'} @tab An integer greater than 1, the number of ## folds. ## @item @qcode{'Holdout'} @tab A scalar in @math{(0, 1)}, the fraction ## of observations held out for testing. ## @item @qcode{'Leaveout'} @tab @qcode{'on'} or @qcode{'off'}, whether ## to hold out one observation at a time. ## @item @qcode{'CVPartition'} @tab A @code{cvpartition} object over as ## many observations as the model was trained on. ## @end multitable ## ## @seealso{RegressionNeuralNetwork, RegressionPartitionedModel, ## cvpartition} ## @end deftypefn function CVMdl = crossval (this, varargin) if (numel (varargin) == 1) error (strcat ("RegressionNeuralNetwork.crossval: Name-Value", ... " arguments must be in pairs.")); elseif (numel (varargin) > 2) error (strcat ("RegressionNeuralNetwork.crossval: specify only", ... " one of the optional Name-Value paired arguments.")); endif if (this.NumObservations < 10) numFolds = this.NumObservations; else numFolds = 10; endif Holdout = []; Leaveout = 'off'; CVPartition = []; while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'kfold' numFolds = varargin{2}; if (! (isnumeric (numFolds) && isscalar (numFolds) && (numFolds == fix (numFolds)) && numFolds > 1)) error (strcat ("RegressionNeuralNetwork.crossval: 'KFold'", ... " must be an integer value greater than 1.")); endif case 'holdout' Holdout = varargin{2}; if (! (isnumeric (Holdout) && isscalar (Holdout) && Holdout > 0 && Holdout < 1)) error (strcat ("RegressionNeuralNetwork.crossval: 'Holdout'", ... " must be a numeric value between 0 and 1.")); endif case 'leaveout' Leaveout = varargin{2}; if (! (ischar (Leaveout) && (strcmpi (Leaveout, 'on') || strcmpi (Leaveout, 'off')))) error (strcat ("RegressionNeuralNetwork.crossval: 'Leaveout'", ... " must be either 'on' or 'off'.")); endif case 'cvpartition' CVPartition = varargin{2}; if (! (isa (CVPartition, 'cvpartition'))) error (strcat ("RegressionNeuralNetwork.crossval:", ... " 'CVPartition' must be a 'cvpartition'", ... " object.")); endif otherwise error (strcat ("RegressionNeuralNetwork.crossval: invalid", ... " parameter name in optional paired arguments.")); endswitch varargin(1:2) = []; endwhile ## Determine the cross-validation method to use. The partition is ## built over the observations actually trained on, so its indices and ## the partitioned model's rows are the same set. n = this.NumObservations; if (! isempty (CVPartition)) partition = CVPartition; elseif (! isempty (Holdout)) partition = cvpartition (n, 'Holdout', Holdout); elseif (strcmpi (Leaveout, 'on')) partition = cvpartition (n, 'LeaveOut'); else partition = cvpartition (n, 'KFold', numFolds); endif ## Create a cross-validated model object CVMdl = RegressionPartitionedModel (this, partition); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionNeuralNetwork} {@var{CMdl} =} compact (@var{obj}) ## ## Create a @qcode{CompactRegressionNeuralNetwork} object. ## ## @code{@var{CMdl} = compact (@var{obj})} returns a compact version of ## the @qcode{RegressionNeuralNetwork} object @var{obj}, which keeps the ## trained network but drops the training data, so it predicts identically ## while carrying no observations. ## ## @seealso{fitrnet, RegressionNeuralNetwork, ## CompactRegressionNeuralNetwork} ## @end deftypefn function CMdl = compact (this) ## Create a compact model CMdl = CompactRegressionNeuralNetwork (this); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionNeuralNetwork} {} savemodel (@var{obj}, @var{filename}) ## ## Save a neural network regression model to a file. ## ## @code{savemodel (@var{obj}, @var{filename})} saves every property of ## the @qcode{RegressionNeuralNetwork} object @var{obj} into ## @var{filename} in binary format, so that it can be read back with ## @code{loadmodel}. ## ## @seealso{loadmodel, RegressionNeuralNetwork, fitrnet} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error ("RegressionNeuralNetwork.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error (strcat ("RegressionNeuralNetwork.savemodel: FNAME must be", ... " a character vector.")); endif ## Generate variable for class name classdef_name = 'RegressionNeuralNetwork'; ## Create variables from model properties X = this.X; Y = this.Y; NumObservations = this.NumObservations; RowsUsed = this.RowsUsed; BinEdges = this.BinEdges; NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; ResponseName = this.ResponseName; ResponseTransform = this.ResponseTransform; Sigma = this.Sigma; Mu = this.Mu; LayerSizes = this.LayerSizes; Activations = this.Activations; OutputLayerActivation = this.OutputLayerActivation; LearningRate = this.LearningRate; IterationLimit = this.IterationLimit; ModelParameters = this.ModelParameters; ConvergenceInfo = this.ConvergenceInfo; TrainingHistory = this.TrainingHistory; DisplayInfo = this.DisplayInfo; Solver = this.Solver; LayerWeights = this.LayerWeights; LayerBiases = this.LayerBiases; W = this.W; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; RTfun = this.RTfun; ## TrainingHistory is a table, and Octave cannot save a classdef object ## to a binary file, so it is left out here and rebuilt on loading from ## ConvergenceInfo, which holds the same numbers as a plain vector. ## Save classdef name and all model properties as individual variables ## The history is a table and ConvergenceInfo holds another; both lose ## their class on the way to the file and load_model rebuilds them, so ## the warning that says so is expected and is not shown. ws_ = warning ('off', 'Octave:save:classdef:unsupported'); unwind_protect HyperparameterOptimizationResults = this.HyperparameterOptimizationResults; save ('-binary', fname, 'classdef_name', 'X', 'Y', 'NumObservations', ... 'RowsUsed', 'BinEdges', 'NumPredictors', 'PredictorNames', ... 'ResponseName', ... 'ResponseTransform', 'Sigma', 'Mu', ... 'LayerSizes', 'Activations', 'OutputLayerActivation', ... 'LearningRate', 'IterationLimit', 'Solver', 'ModelParameters', ... 'ConvergenceInfo', 'TrainingHistory', 'DisplayInfo', ... 'LayerWeights', 'LayerBiases', ... 'W', 'CategoricalPredictors', 'ExpandedPredictorNames', 'RTfun', ... 'HyperparameterOptimizationResults'); unwind_protect_cleanup warning (ws_); end_unwind_protect endfunction endmethods methods (Access = private) ## Shared validation for the assessment methods, so each reports under ## its own name. function [X, Y] = checkXY_ (this, X, Y, caller) if (isempty (X)) error ("RegressionNeuralNetwork.%s: X is empty.", caller); elseif (this.NumPredictors != columns (X)) error (strcat ("RegressionNeuralNetwork.%s: X must have the", ... " same number of predictors as the trained model."), ... caller); endif if (isempty (Y)) error ("RegressionNeuralNetwork.%s: Y is empty.", caller); elseif (! (isnumeric (Y) && isreal (Y))) error (strcat ("RegressionNeuralNetwork.%s: Y must be a real", ... " numeric vector."), caller); elseif (rows (X) != numel (Y)) error (strcat ("RegressionNeuralNetwork.%s: Y must have the", ... " same number of rows as X."), caller); endif endfunction ## Pull a "Weights" pair out of the optional arguments, defaulting to a ## uniform weight, and reject any other name. function W = getWeights_ (this, args, n, caller) W = ones (n, 1); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("RegressionNeuralNetwork.%s: parameter name", ... " must be a character vector."), caller); endif if (strcmpi (args{i}, 'weights')) W = args{i+1}; if (! (isnumeric (W) && isvector (W))) error (strcat ("RegressionNeuralNetwork.%s: 'Weights'", ... " must be a numeric vector."), caller); endif if (numel (W) != n) error (strcat ("RegressionNeuralNetwork.%s: size of", ... " 'Weights' must equal the number of", ... " rows in X."), caller); endif else error (strcat ("RegressionNeuralNetwork.%s: invalid", ... " parameter name in optional paired", ... " arguments."), caller); endif endfor endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a RegressionNeuralNetwork object mdl = RegressionNeuralNetwork (1, 1); ## Get fieldnames from DATA (including private properties) names = fieldnames (data); ## The set methods for these read other properties, and one of them ## rebuilds Coeffs, so they are assigned once everything else is in ## place rather than in the order the file happens to list them. late = ismember (names, {'Cost', 'Prior', 'ScoreTransform', ... 'ResponseTransform'}); names = [names(! late); names(late)]; ## Copy data into object for i = 1:numel (names) ## Check fieldnames in DATA match properties in RegressionNeuralNetwork try mdl.(names{i}) = restore_tables (data.(names{i})); catch error (strcat ("RegressionNeuralNetwork.load_model:", ... " invalid model in '%s'."), filename) end_try_catch endfor ## Rebuild the TrainingHistory table, which savemodel cannot write out ## A model saved before the history was written out carries the series ## as vectors in ConvergenceInfo instead; rebuild from those, so that an ## older file still loads and loads as the current shape. if (isempty (mdl.TrainingHistory) && ! isempty (mdl.ConvergenceInfo)) mdl = restoreOlderModel_ (mdl); endif endfunction endmethods endclassdef ## Map an activation name to the code fcnntrain expects. 'none' and 'linear' ## are the same identity map; MATLAB spells the output layer's 'none'. ## The recorded history, whose columns follow the solver that produced it. ## Building it in one place keeps the fit and the model reloaded from disk ## from drifting apart. function T = trainingTable_ (series) iter = (1:numel (series.TrainingLoss))'; ## Time is NaN because neither solver records a per-iteration figure; the ## total the fit took is ConvergenceInfo.Time. The validation pair is NaN ## because no validation set can be given, which is what MATLAB reports ## when none is. Both are present so the columns are MATLAB's. pad = NaN (numel (iter), 1); if (isfield (series, 'Gradient')) T = table (iter, series.TrainingLoss(:), series.Gradient(:), ... series.Step(:), pad, pad, pad, 'VariableNames', ... {'Iteration', 'TrainingLoss', 'Gradient', 'Step', 'Time', ... 'ValidationLoss', 'ValidationChecks'}); else T = table (iter, series.TrainingLoss(:), pad, pad, pad, ... 'VariableNames', {'Iteration', 'TrainingLoss', 'Time', ... 'ValidationLoss', 'ValidationChecks'}); endif endfunction ## ConvergenceInfo reports where the fit ended: the last value of each series ## as a scalar, beside the whole series as History. MATLAB divides the two ## the same way, and a vector here would repeat what History already holds. function ci = convergenceStruct_ (series, elapsed, criterion) ## The fields are assigned in MATLAB's own order, which fieldnames reports. ci.Iterations = numel (series.TrainingLoss); ci.TrainingLoss = lastValue_ (series.TrainingLoss); if (isfield (series, 'Gradient')) ci.Gradient = lastValue_ (series.Gradient); ci.Step = lastValue_ (series.Step); endif ci.Time = elapsed; ci.ValidationLoss = NaN; ci.ValidationChecks = NaN; if (isfield (series, 'Gradient')) ci.ConvergenceCriterion = criterion; endif ci.History = trainingTable_ (series); endfunction ## Where the fit ended. A fit that took no iteration recorded nothing, so ## the value it ended at is empty rather than an error. function v = lastValue_ (x) if (isempty (x)) v = []; else v = x(end); endif endfunction ## The series as it was persisted, or, from a model saved before the series ## became a property of its own, rebuilt from the vectors ConvergenceInfo ## used to carry. ## Read a model written before ConvergenceInfo reported where the fit ended ## rather than the whole series: the vectors it carries are the history. function mdl = restoreOlderModel_ (mdl) ci = mdl.ConvergenceInfo; series = struct ('TrainingLoss', ci.TrainingLoss(:)); criterion = ''; if (isfield (ci, 'Gradient')) series.Gradient = ci.Gradient(:); series.Step = ci.Step(:); criterion = ci.ConvergenceCriterion; endif mdl.TrainingHistory = trainingTable_ (series); mdl.ConvergenceInfo = convergenceStruct_ (series, ci.Time, criterion); endfunction ## A fitted model carries the defaults MATLAB documents for fitrnet. %!test %! rand ('seed', 42); %! X = linspace (-1, 1, 40)'; %! Y = 2 * X + 0.5; %! Mdl = RegressionNeuralNetwork (X, Y, 'IterationLimit', 50); %! assert_equal (class (Mdl), 'RegressionNeuralNetwork'); %! assert_equal (Mdl.LayerSizes, 10); %! assert_equal (Mdl.Activations, 'relu'); %! assert_equal (Mdl.OutputLayerActivation, 'none'); %! assert_equal (Mdl.LearningRate, 0.003); %! assert_equal (Mdl.IterationLimit, 50); %! assert_equal (isempty (Mdl.Mu), true); %! assert_equal (Mdl.Solver, 'LBFGS'); %! assert_equal (Mdl.NumObservations, 40); %! assert_equal (Mdl.NumPredictors, 1); %! assert_equal (Mdl.ResponseName, 'Y'); %! assert_equal (Mdl.PredictorNames, {'x1'}); ## The output layer has one unit, and the identity leaves it unbounded, so a ## prediction is a real number rather than a score. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = linspace (-2, 2, 60)'; %! Y = 30 * X + 100; %! Mdl = RegressionNeuralNetwork (X, Y, 'IterationLimit', 400); %! assert_equal (rows (Mdl.LayerWeights{end}), 1); %! yFit = predict (Mdl, X); %! assert_equal (size (yFit), [60, 1]); %! assert_equal (max (yFit) > 50, true); ## A network recovers a smooth function to within the noise on it. %!test %! rand ('seed', 7); randn ('seed', 7); %! X = linspace (-2, 2, 80)'; %! Y = 3 * X.^2 - 1 + randn (80, 1) * 0.05; %! Mdl = RegressionNeuralNetwork (X, Y, 'LayerSizes', [12, 12], ... %! 'IterationLimit', 600); %! assert_equal (sqrt (resubLoss (Mdl)) < 0.2, true); ## The recorded history is the network's own loss, not a running average. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = linspace (0, 1, 50)'; %! Y = 4 * X - 2; %! Mdl = RegressionNeuralNetwork (X, Y, 'IterationLimit', 200, ... %! 'Solver', 'sgd'); %! h = Mdl.TrainingHistory; %! assert_equal (class (h), 'table'); %! assert_equal (h.Properties.VariableNames, ... %! {'Iteration', 'TrainingLoss', 'Time', 'ValidationLoss', ... %! 'ValidationChecks'}); %! assert_equal (rows (h), 200); %! assert_equal (h.Iteration', 1:200); %! assert_equal (h.TrainingLoss(end) < h.TrainingLoss(1), true); %! assert_equal (h.TrainingLoss(end), resubLoss (Mdl), 1e-12); ## ConvergenceInfo carries the same loss and the time the fit took. %!test %! rand ('seed', 42); %! X = linspace (0, 1, 30)'; %! Mdl = RegressionNeuralNetwork (X, 2 * X, 'Solver', 'sgd', ... %! 'IterationLimit', 25); %! assert_equal (fieldnames (Mdl.ConvergenceInfo), ... %! {'Iterations'; 'TrainingLoss'; 'Time'; ... %! 'ValidationLoss'; 'ValidationChecks'; 'History'}); %! assert_equal (numel (Mdl.ConvergenceInfo.TrainingLoss), 1); %! assert_equal (rows (Mdl.ConvergenceInfo.History), 25); %! assert_equal (Mdl.ConvergenceInfo.TrainingLoss, ... %! Mdl.ConvergenceInfo.History.TrainingLoss(end)); %! assert_equal (Mdl.ConvergenceInfo.Time > 0, true); ## predict on the training rows is resubPredict. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = [randn(30, 2); randn(30, 2) + 3]; %! Y = X(:,1) - 2 * X(:,2); %! Mdl = RegressionNeuralNetwork (X, Y, 'IterationLimit', 100); %! assert_equal (predict (Mdl, X), resubPredict (Mdl)); ## Standardize trains on the scale it predicts on. A model trained on raw ## data and asked about standardized data is not merely worse, it is wrong. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = [randn(60, 1), randn(60, 1) * 1000]; %! Y = X(:,1) + X(:,2) / 1000; %! Mdl = RegressionNeuralNetwork (X, Y, 'Standardize', true, ... %! 'IterationLimit', 300); %! assert_equal (size (Mdl.Mu), [1, 2]); %! assert_equal (size (Mdl.Sigma), [1, 2]); %! assert_equal (predict (Mdl, X), resubPredict (Mdl)); %! assert_equal (sqrt (resubLoss (Mdl)) < std (Y), true); ## A constant predictor gets a unit scale rather than a division by zero. %!test %! rand ('seed', 42); %! X = [linspace(0, 1, 20)', ones(20, 1)]; %! Mdl = RegressionNeuralNetwork (X, X(:,1), 'Standardize', true, ... %! 'IterationLimit', 50); %! assert_equal (Mdl.Sigma(2), 1); %! assert_equal (all (isfinite (resubPredict (Mdl))), true); ## Layer sizes and per-layer activations reach the network. %!test %! rand ('seed', 42); %! X = linspace (0, 1, 30)'; %! Mdl = RegressionNeuralNetwork (X, 2 * X, 'LayerSizes', [4, 6], ... %! 'Activations', {'tanh', 'sigmoid'}, ... %! 'IterationLimit', 20); %! assert_equal (Mdl.LayerSizes, [4, 6]); %! assert_equal (Mdl.Activations, {'tanh', 'sigmoid'}); %! assert_equal (numel (Mdl.LayerWeights), 3); %! assert_equal (size (Mdl.LayerWeights{1}), [4, 1]); %! assert_equal (size (Mdl.LayerWeights{2}), [6, 4]); %! assert_equal (size (Mdl.LayerWeights{3}), [1, 6]); %! assert_equal (Mdl.ModelParameters.Activations, {'tanh', 'sigmoid'}); %! assert_equal (Mdl.ModelParameters.OutputLayerActivation, 'none'); ## 'none' and 'linear' name the same identity output, which the two models ## predicting alike from the same seed is what the claim actually means. %!test %! X = linspace (0, 1, 20)'; %! rand ('seed', 42); %! M1 = RegressionNeuralNetwork (X, 2 * X, 'OutputLayerActivation', 'none', ... %! 'IterationLimit', 10); %! rand ('seed', 42); %! M2 = RegressionNeuralNetwork (X, 2 * X, 'OutputLayerActivation', ... %! 'linear', 'IterationLimit', 10); %! assert_equal (predict (M1, X), predict (M2, X)); ## Rows carrying a missing value are dropped from both X and Y. %!test %! rand ('seed', 42); %! X = [linspace(0, 1, 12)'; NaN; 0.5]; %! Y = [2 * linspace(0, 1, 12)'; 1; NaN]; %! Mdl = RegressionNeuralNetwork (X, Y, 'IterationLimit', 20); %! assert_equal (Mdl.NumObservations, 13); %! assert_equal (sum (Mdl.RowsUsed), 13); %! assert_equal (Mdl.RowsUsed(13:14), [true; false]); %! assert_equal (numel (resubPredict (Mdl)), 13); ## Observation weights default to a uniform weight summing to one. %!test %! rand ('seed', 42); %! X = linspace (0, 1, 25)'; %! Mdl = RegressionNeuralNetwork (X, 2 * X, 'IterationLimit', 10); %! assert_equal (size (Mdl.W), [25, 1]); %! assert_equal (sum (Mdl.W), 1, 1e-12); %! assert_equal (Mdl.W, ones (25, 1) / 25, 1e-12); ## loss defaults to the weighted mean squared error, and weights are ## normalized, so scaling every weight leaves the loss alone. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = linspace (0, 1, 30)'; %! Y = 3 * X + 1; %! Mdl = RegressionNeuralNetwork (X, Y, 'IterationLimit', 100); %! yFit = predict (Mdl, X); %! assert_equal (loss (Mdl, X, Y), mean ((Y - yFit) .^ 2), 1e-12); %! assert_equal (loss (Mdl, X, Y, 'LossFun', 'mse'), loss (Mdl, X, Y), 1e-12); %! w = rand (30, 1) + 0.1; %! assert_equal (loss (Mdl, X, Y, 'Weights', w), ... %! loss (Mdl, X, Y, 'Weights', 7 * w), 1e-12); %! assert_equal (loss (Mdl, X, Y, 'Weights', w), ... %! sum ((w / sum (w)) .* (Y - yFit) .^ 2), 1e-12); ## loss takes a function handle of the response, the fit and the weights. %!test %! rand ('seed', 42); %! X = linspace (0, 1, 20)'; %! Y = 2 * X; %! Mdl = RegressionNeuralNetwork (X, Y, 'IterationLimit', 50); %! f = @(y, yf, w) sum (w .* abs (y - yf)); %! yFit = predict (Mdl, X); %! assert_equal (loss (Mdl, X, Y, 'LossFun', f), ... %! mean (abs (Y - yFit)), 1e-12); ## resubLoss is loss on the training data. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = linspace (0, 1, 21)'; %! Y = [3 * linspace(0, 1, 20)'; NaN]; %! Mdl = RegressionNeuralNetwork (X, Y, 'IterationLimit', 80); %! Xu = X(Mdl.RowsUsed, :); %! Yu = Y(Mdl.RowsUsed); %! assert_equal (resubLoss (Mdl), loss (Mdl, Xu, Yu), 1e-12); %! assert_equal (resubLoss (Mdl, 'Weights', ones (20, 1)), ... %! loss (Mdl, Xu, Yu), 1e-12); ## ResponseTransform is applied to the prediction, by name or by handle. %!test %! rand ('seed', 42); %! X = linspace (0, 1, 20)'; %! Y = 2 * X + 1; %! Mdl = RegressionNeuralNetwork (X, Y, 'IterationLimit', 50); %! raw = predict (Mdl, X); %! Mdl.ResponseTransform = 'exp'; %! assert_equal (predict (Mdl, X), exp (raw), 1e-12); %! Mdl.ResponseTransform = @(y) 2 * y; %! assert_equal (predict (Mdl, X), 2 * raw, 1e-12); %! Mdl.ResponseTransform = 'none'; %! assert_equal (predict (Mdl, X), raw, 1e-12); ## The transform set at construction is the one predict uses. %!test %! rand ('seed', 42); %! X = linspace (0, 1, 20)'; %! Mdl = RegressionNeuralNetwork (X, 2 * X, 'ResponseTransform', 'identity', ... %! 'IterationLimit', 20); %! assert_equal (class (Mdl.ResponseTransform), 'char'); %! assert_equal (Mdl.ResponseTransform, 'none'); # identity is stored as none ## A saved model comes back carrying its own numbers. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = linspace (0, 1, 30)'; %! Y = 4 * X - 1; %! Mdl = RegressionNeuralNetwork (X, Y, 'LayerSizes', [6, 4], ... %! 'Solver', 'sgd', 'IterationLimit', 60); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'RegressionNeuralNetwork'); %! assert_equal (M2.LayerWeights, Mdl.LayerWeights); %! assert_equal (M2.LayerBiases, Mdl.LayerBiases); %! assert_equal (M2.NumObservations, Mdl.NumObservations); %! assert_equal (M2.LayerSizes, Mdl.LayerSizes); %! assert_equal (M2.ResponseName, Mdl.ResponseName); %! assert_equal (M2.W, Mdl.W); %! assert_equal (predict (M2, X), predict (Mdl, X)); %! assert_equal (rows (M2.TrainingHistory), 60); %! assert_equal (M2.TrainingHistory.TrainingLoss, ... %! Mdl.TrainingHistory.TrainingLoss); ## compact drops the training data but predicts identically. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = [randn(40, 2); randn(40, 2) + 2]; %! Y = X(:,1) - X(:,2); %! Mdl = RegressionNeuralNetwork (X, Y, 'IterationLimit', 100); %! CMdl = compact (Mdl); %! assert_equal (class (CMdl), 'CompactRegressionNeuralNetwork'); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); %! assert_equal (loss (CMdl, X, Y), loss (Mdl, X, Y)); ## crossval returns a partitioned model holding one fit per fold. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = randn (30, 2); %! Y = X(:,1) - X(:,2); %! Mdl = fitrnet (X, Y, 'IterationLimit', 20); %! CVMdl = crossval (Mdl, 'KFold', 3); %! assert_equal (class (CVMdl), 'RegressionPartitionedModel'); %! assert_equal (CVMdl.KFold, 3); %! assert_equal (CVMdl.CrossValidatedModel, 'NeuralNetwork'); %! assert_equal (numel (kfoldPredict (CVMdl)), 30); %! assert_equal (isfinite (kfoldLoss (CVMdl)), true); ## Test input validation for crossval ## The full-batch solver is selected by name and says so. %!test %! x = linspace (0, 1, 40)'; %! Mdl = fitrnet (x, sin (2*pi*x), "IterationLimit", 50, "Solver", "lbfgs"); %! assert_equal (Mdl.Solver, "LBFGS"); %! assert_equal (Mdl.TrainingHistory.Properties.VariableNames, ... %! {"Iteration", "TrainingLoss", "Gradient", "Step", ... %! "Time", "ValidationLoss", "ValidationChecks"}); ## It records what it measured to decide it had stopped. %!test %! x = linspace (0, 1, 40)'; %! Mdl = fitrnet (x, sin (2*pi*x), "IterationLimit", 50, "Solver", "lbfgs"); %! ci = Mdl.ConvergenceInfo; %! assert_equal (isfield (ci, "Gradient"), true); %! assert_equal (isfield (ci, "Step"), true); %! assert_equal (isfield (ci, "ConvergenceCriterion"), true); ## The stochastic solver is still reached by name, with its own two columns. %!test %! x = linspace (0, 1, 40)'; %! Mdl = fitrnet (x, sin (2*pi*x), "Solver", "sgd", "IterationLimit", 20); %! assert_equal (Mdl.Solver, "Gradient Descent"); %! assert_equal (columns (Mdl.TrainingHistory), 5); ## The default solver is lbfgs, and its history carries the four columns. %!test %! x = linspace (0, 1, 40)'; %! Mdl = fitrnet (x, sin (2*pi*x), "IterationLimit", 20); %! assert_equal (Mdl.Solver, "LBFGS"); %! assert_equal (Mdl.TrainingHistory.Properties.VariableNames, ... %! {"Iteration", "TrainingLoss", "Gradient", "Step", ... %! "Time", "ValidationLoss", "ValidationChecks"}); ## A model trained by lbfgs comes back off disk with its own four columns. %!test %! x = linspace (0, 1, 40)'; %! Mdl = fitrnet (x, sin (2*pi*x), "IterationLimit", 30, "Solver", "lbfgs"); %! fname = tempname (); %! savemodel (Mdl, fname); %! Mdl2 = loadmodel (fname); %! delete (fname); %! assert_equal (table2cell (Mdl2.TrainingHistory), ... %! table2cell (Mdl.TrainingHistory)); ## An option that cannot act is refused rather than ignored. %!error ... %! fitrnet (ones (5, 2), [1; 2; 3; 4; 5], "Solver", "sgd", ... %! "GradientTolerance", 1e-8) %!error ... %! fitrnet (ones (5, 2), [1; 2; 3; 4; 5], "Solver", "lbfgs", "LearningRate", 0.1) %!error ... %! fitrnet (ones (5, 2), [1; 2; 3; 4; 5], "Solver", "bogus") %!error ... %! fitrnet (ones (5, 2), [1; 2; 3; 4; 5], "Solver", "lbfgs", "LossTolerance", NaN) %!error ... %! crossval (fitrnet (randn (12, 2), randn (12, 1), ... %! 'IterationLimit', 20), 'KFold') %!error ... %! crossval (fitrnet (randn (12, 2), randn (12, 1), ... %! 'IterationLimit', 20), ... %! 'KFold', 3, 'Leaveout', 'on') %!error ... %! crossval (fitrnet (randn (12, 2), randn (12, 1), ... %! 'IterationLimit', 20), 'KFold', 1) %!error ... %! crossval (fitrnet (randn (12, 2), randn (12, 1), ... %! 'IterationLimit', 20), 'Holdout', 1) %!error ... %! crossval (fitrnet (randn (12, 2), randn (12, 1), ... %! 'IterationLimit', 20), 'Leaveout', 1) %!error ... %! crossval (fitrnet (randn (12, 2), randn (12, 1), ... %! 'IterationLimit', 20), 'CVPartition', 1) %!error ... %! crossval (fitrnet (randn (12, 2), randn (12, 1), ... %! 'IterationLimit', 20), 'Nope', 1) ## Test input validation for the constructor %!error ... %! RegressionNeuralNetwork () %!error ... %! RegressionNeuralNetwork (ones (10, 2)) %!error ... %! RegressionNeuralNetwork (ones (10, 2), ones (5, 1)) %!error ... %! RegressionNeuralNetwork (ones (5, 2), {'a'; 'b'; 'c'; 'd'; 'e'}) %!error ... %! RegressionNeuralNetwork (ones (5, 2), complex (ones (5, 1))) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 3)) %!error ... %! RegressionNeuralNetwork ([1; Inf; 3], [1; 2; 3]) %!error ... %! RegressionNeuralNetwork ([1; 2; 3], [1; Inf; 3]) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'Standardize', 'yes') %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'PredictorNames', 'a') %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'PredictorNames', {'a'}) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'ResponseName', 5) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'ResponseTransform', 5) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), ... %! 'ResponseTransform', 'nope') %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), ... %! 'ResponseTransform', @(y) [y; y]) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'LayerSizes', -1) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'LayerSizes', 2.5) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'LearningRate', 0) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), ... %! 'LearningRate', [0.1, 0.2]) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'Activations', 5) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'Activations', 'softmax') %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), ... %! 'Activations', {'relu', 'nope'}) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), ... %! 'OutputLayerActivation', 5) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), ... %! 'OutputLayerActivation', 'softmax') %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'IterationLimit', 0) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'IterationLimit', 2.5) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'DisplayInfo', 'yes') %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'Prior', 1) %!error ... %! RegressionNeuralNetwork (ones (5, 2), ones (5, 1), 'LayerSizes', [4, 4], ... %! 'Activations', {'relu', 'relu', 'relu'}) ## Test input validation for predict %!error ... %! predict (RegressionNeuralNetwork ([1; 2; 3], [1; 2; 3], ... %! 'IterationLimit', 5)) %!error ... %! predict (RegressionNeuralNetwork ([1; 2; 3], [1; 2; 3], ... %! 'IterationLimit', 5), []) %!error ... %! predict (RegressionNeuralNetwork ([1; 2; 3], [1; 2; 3], ... %! 'IterationLimit', 5), ones (2, 3)) ## Test input validation for loss %!shared RNNMdl %! rand ('seed', 42); %! RNNMdl = RegressionNeuralNetwork ([1; 2; 3; 4], [2; 4; 6; 8], ... %! 'IterationLimit', 10); %!error ... %! loss (RNNMdl) %!error ... %! loss (RNNMdl, [1; 2]) %!error ... %! loss (RNNMdl, [1; 2], [2; 4], 'Weights') %!error ... %! loss (RNNMdl, [], [2; 4]) %!error ... %! loss (RNNMdl, ones (2, 3), [2; 4]) %!error ... %! loss (RNNMdl, [1; 2], []) %!error ... %! loss (RNNMdl, [1; 2], {'a'; 'b'}) %!error ... %! loss (RNNMdl, [1; 2], [2; 4; 6]) %!error ... %! loss (RNNMdl, [1; 2], [2; 4], 'LossFun', 5) %!error ... %! loss (RNNMdl, [1; 2], [2; 4], 'LossFun', 'mae') %!error ... %! loss (RNNMdl, [1; 2], [2; 4], 'LossFun', @(y, yf, w) [1, 2]) %!error ... %! loss (RNNMdl, [1; 2], [2; 4], 'Weights', {'a'}) %!error ... %! loss (RNNMdl, [1; 2], [2; 4], 'Weights', [1; 2; 3]) %!error ... %! loss (RNNMdl, [1; 2], [2; 4], 'Nope', 1) %!error ... %! loss (RNNMdl, [1; 2], [2; 4], 5, 1) ## Test input validation for savemodel %!error ... %! savemodel (RNNMdl) %!error ... %! savemodel (RNNMdl, 5) %!error ... %! RNNMdl.ResponseTransform = 'nope'; ## RowsUsed is empty when every observation was used. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Mdl = fitrnet (X, Y, 'IterationLimit', 20); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (class (Mdl.RowsUsed), 'double'); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (rows (Mdl.X), 150); %! assert_equal (rows (Mdl.W), 150); ## A missing response drops its observation and RowsUsed marks it. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Y(5) = NaN; %! Mdl = fitrnet (X, Y, 'IterationLimit', 20); %! assert_equal (class (Mdl.RowsUsed), 'logical'); %! assert_equal (size (Mdl.RowsUsed), [150, 1]); %! assert_equal (sum (Mdl.RowsUsed), 149); %! assert_equal (Mdl.RowsUsed(5), false); %! assert_equal (Mdl.NumObservations, 149); %! assert_equal (rows (Mdl.X), 149); %! assert_equal (rows (Mdl.W), 149); ## A missing predictor keeps its observation, so RowsUsed stays empty. %!test %! load fisheriris %! X = meas(:,2:4); %! X(3,2) = NaN; %! Y = meas(:,1); %! Mdl = fitrnet (X, Y, 'IterationLimit', 20); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (rows (Mdl.X), 150); %! assert_equal (sum (isnan (Mdl.X(:))), 1); ## Standardizing summarizes the complete observations. With no classes the ## weights are uniform over them. Values from MATLAB R2024a. %!test %! load fisheriris %! X = meas(:,2:4); %! X(7,2) = NaN; X(120,3) = NaN; %! Mdl = fitrnet (X, meas(:,1), 'Standardize', true, 'IterationLimit', 20); %! assert_equal (Mdl.Mu, [3.0608108108108096, 3.7655405405405395, ... %! 1.203378378378378], 1e-13); %! assert_equal (Mdl.Sigma, [0.43214937187299296, 1.7636045663278643, ... %! 0.76339873235638711], 1e-13); ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Mdl = fitrnet (X, Y, 'IterationLimit', 20); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'RegressionNeuralNetwork'); %! assert_equal (M2.NumObservations, Mdl.NumObservations); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ResponseTransform), class (Mdl.ResponseTransform)); %! assert_equal (predict (M2, X(1:5,:)), predict (Mdl, X(1:5,:)), 1e-12); ## BinEdges is an empty cell, which is what MATLAB reports for this ## learner as well: it fits the predictors as they are. %!test %! load fisheriris %! Mdl = fitrnet (meas(:,1:3), meas(:,4), 'IterationLimit', 10); %! assert_equal (class (Mdl.BinEdges), 'cell'); %! assert_equal (Mdl.BinEdges, {}); ## HyperparameterOptimizationResults is declared for MATLAB compatibility and ## stays empty, this class running no search over its hyperparameters. %!test %! load fisheriris %! Mdl = fitrnet (meas(:,1:3), meas(:,4), 'IterationLimit', 20); %! assert_equal (isempty (Mdl.HyperparameterOptimizationResults), true); ## ModelParameters records the fit as it was asked for, not the network that ## came out of it. %!test %! load fisheriris %! Mdl = fitrnet (meas(:,2:4), meas(:,1)); %! assert_equal (fieldnames (Mdl.ModelParameters)', {'LayerSizes', ... %! 'Activations', 'OutputLayerActivation', 'LayerWeightsInitializers', ... %! 'Solver', 'LearningRate', 'IterationLimit', 'GradientTolerance', ... %! 'LossTolerance', 'StepTolerance', 'DisplayInfo', 'StandardizeData', ... %! 'Version', 'Method', 'Type'}); %!test %! load fisheriris %! MP = fitrnet (meas(:,2:4), meas(:,1)).ModelParameters; %! assert_equal (MP.LayerSizes, 10); %! assert_equal (MP.Activations, 'relu'); %! assert_equal (MP.OutputLayerActivation, 'none'); %! assert_equal (MP.Version, 1); %! assert_equal (MP.Method, 'NeuralNetwork'); %! assert_equal (MP.Type, 'regression'); ## A per-layer Activations stays a cellstr rather than splitting the ## structure into an array. %!test %! load fisheriris %! MP = fitrnet (meas(:,2:4), meas(:,1), 'LayerSizes', [4, 4], ... %! 'Activations', {'gelu', 'sigmoid'}).ModelParameters; %! assert_equal (size (MP), [1, 1]); %! assert_equal (MP.Activations, {'gelu', 'sigmoid'}); %! assert_equal (MP.LayerWeightsInitializers, {'he', 'glorot', 'glorot'}); ## ModelParameters reports the weight initializer each layer was built with, ## the output layer last. The engine picks it from the activation and it ## cannot be chosen, so the report is the only way to see it. %!test %! load fisheriris %! MP = fitrnet (meas(:,2:4), meas(:,1)).ModelParameters; %! assert_equal (MP.LayerWeightsInitializers, {'he', 'glorot'}); ## The output layer defaults to 'none' here, which is symmetric and takes ## Glorot however many rectifying layers precede it. %!test %! load fisheriris %! Mdl = fitrnet (meas(:,2:4), meas(:,1), 'LayerSizes', [4, 4, 4], ... %! 'Activations', 'gelu'); %! assert_equal (Mdl.ModelParameters.LayerWeightsInitializers, ... %! {'he', 'he', 'he', 'glorot'}); ## Every documented response transform reaches the response that is reported. %!test %! load fisheriris %! Mdl = fitrnet (meas(:,2:4), meas(:,1)); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! T = {'identity', @(x) x; 'exp', @(x) exp (x); 'log', @(x) log (x)}; %! for i = 1:rows (T) %! Mdl.ResponseTransform = T{i,1}; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, T{i,2}(raw), 1e-12); %! endfor ## A function handle is taken as given and applied to the response. %!test %! load fisheriris %! Mdl = fitrnet (meas(:,2:4), meas(:,1)); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! Mdl.ResponseTransform = @(x) x .^ 2; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, raw .^ 2, 1e-12); statistics-release-1.9.2/inst/Supervised_Learning/RegressionPartitionedKernel.m000066400000000000000000000545521524624707500301510ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftp {statistics} RegressionPartitionedKernel ## ## Cross-validated Gaussian kernel regression model. ## ## A @qcode{RegressionPartitionedKernel} object holds one ## @qcode{RegressionKernel} per fold of a partition, each fitted to the ## observations the fold trains on. @code{kfoldPredict} predicts each ## observation with the fold that held it @emph{out}, so what it returns is ## an out-of-sample prediction. ## ## A @qcode{RegressionKernel} stores no copy of its training data and so has ## no resubstitution methods and no @code{compact} form. This class is what ## takes their place: cross-validation is the way a kernel model is asked ## how it would do on data it has not seen. ## ## Every fold draws its own random basis, as it must, being its own fit. ## Two folds therefore approximate the same kernel through different ## expansions, which is a source of variation between folds over and above ## the data they were given. A larger @qcode{'NumExpansionDimensions'} ## narrows it. ## ## Create one with @code{fitrlinear} and a cross-validation option, or ## directly. ## ## @seealso{fitrlinear, RegressionKernel, RegressionPartitionedKernel} ## @end deftp classdef RegressionPartitionedKernel properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {RegressionPartitionedKernel} {property} ResponseTransform ## ## Transformation applied to the predicted response ## ## A character vector, or the text of the function handle that was ## supplied, which may be assigned after the model is built. The fold ## models carry no transform of their own; this one is applied once to ## the assembled predictions. ## ## @end deftp ResponseTransform = 'none'; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {RegressionPartitionedKernel} {property} CrossValidatedModel ## ## Name of the model that was cross-validated ## ## Always @qcode{'Linear'}, the short name MATLAB uses. This property is ## read-only. ## ## @end deftp CrossValidatedModel = 'Kernel'; ## -*- texinfo -*- ## @deftp {RegressionPartitionedKernel} {property} NumObservations ## ## Number of observations the partition covers ## ## A positive integer scalar, counting the rows that survived the removal ## of missing values. This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedKernel} {property} Y ## ## Response of the retained observations ## ## An @math{Nx1} numeric vector. This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedKernel} {property} W ## ## Observation weights ## ## An @math{Nx1} numeric vector summing to one. This property is ## read-only. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedKernel} {property} PredictorNames ## ## Names of the predictors ## ## A cell array of character vectors. This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {RegressionPartitionedKernel} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A row vector of column indices, empty when every predictor is ## numeric. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedKernel} {property} ResponseName ## ## Name of the response ## ## A character vector, defaulting to @qcode{'Y'}. This property is ## read-only. ## ## @end deftp ResponseName = 'Y'; ## -*- texinfo -*- ## @deftp {RegressionPartitionedKernel} {property} Trained ## ## The models fitted to the folds ## ## A cell column with one @qcode{RegressionKernel} per fold, each fitted ## to the observations its fold trains on. This property is read-only. ## ## @end deftp Trained = {}; ## -*- texinfo -*- ## @deftp {RegressionPartitionedKernel} {property} KFold ## ## Number of folds ## ## A positive integer scalar. A holdout partition has one fold and a ## leave-one-out partition has as many as there are observations. This ## property is read-only. ## ## @end deftp KFold = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedKernel} {property} Partition ## ## The partition itself ## ## A @code{cvpartition} object over the retained observations. This ## property is read-only. ## ## @end deftp Partition = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedKernel} {property} ModelParameters ## ## What was cross-validated, and how ## ## A structure holding the parameters the folds were fitted with, carried ## through from the learner that was cross validated, beside ## @qcode{NLearn}, the number of folds, and the @qcode{Version}, ## @qcode{Method} and @qcode{Type} tags of this class, with ## @qcode{LearnerTemplates} naming the backing. The ## learner's own tags are replaced rather than kept, so a cross-validated ## SVM reports @qcode{Method} as @qcode{'PartitionedKernel'} and not ## @qcode{'SVM'}. ## ## @strong{Deviation from MATLAB.} MATLAB reports the parameter record of ## the cross-validation @emph{ensemble} here rather than of the learner, ## so it says nothing at all about how the folds were fitted: of its ## eighteen fields only the fold count, its partitioner and a fit template ## carry anything, and the rest are boosting settings left inert. Nor can ## the parameters be reached through the folds, a compact model carrying ## none in MATLAB. This class reports the fit instead, which is strictly ## more than MATLAB offers, and everything MATLAB's record does carry is ## published here as the @qcode{KFold}, @qcode{Partition}, @qcode{X}, ## @qcode{Y}, @qcode{W} and @qcode{CrossValidatedModel} properties. ## ## This property is read-only. ## ## @end deftp ModelParameters = []; endproperties properties (GetAccess = public, SetAccess = protected, Hidden) ## The callable behind ResponseTransform. RTfun = @(y) y; ## The predictors of the retained observations. MATLAB does not report ## them and neither do we, but the kfold methods have to predict from ## something and the fold models hold no data of their own. X_ = []; ## Number of regularization strengths the fold models carry. NumLambda_ = 1; ## Whether the fold models were fitted with an insensitive band, which ## decides whether kfoldLoss will offer the loss that reads it. HasEpsilon_ = false; endproperties methods (Access = public) ## -*- texinfo -*- ## @deftypefn {RegressionPartitionedKernel} {@var{obj} =} RegressionPartitionedKernel (@var{X}, @var{Y}) ## @deftypefnx {RegressionPartitionedKernel} {@var{obj} =} RegressionPartitionedKernel (@dots{}, @var{name}, @var{value}) ## ## Cross-validate a linear regression model. ## ## @code{@var{obj} = RegressionPartitionedKernel (@var{X}, @var{Y})} ## partitions the data into ten folds and fits a ## @qcode{RegressionKernel} to each. ## ## @code{@var{obj} = RegressionPartitionedKernel (@dots{}, @var{name}, ## @var{value})} takes one of @qcode{'KFold'}, @qcode{'Holdout'}, ## @qcode{'Leaveout'} and @qcode{'CVPartition'} to say how to partition, ## and any option @code{RegressionKernel} takes to say how to fit. ## @qcode{'CrossVal'} is accepted and has no effect here, this class ## being cross-validated by construction. ## ## Anything left as @qcode{'auto'} is resolved by each fold against its ## own training rows rather than once over the whole data, so ten folds ## of a hundred observations each get a @qcode{Lambda} of one ninetieth ## rather than one hundredth, and each its own @qcode{Epsilon} and ## @qcode{KernelScale}. Both are MATLAB's behaviour, measured. ## ## @seealso{fitrlinear, RegressionKernel} ## @end deftypefn function this = RegressionPartitionedKernel (X, Y, varargin) if (nargin < 2) error (strcat ("RegressionPartitionedKernel: too few input", ... " arguments.")); endif if (mod (numel (varargin), 2) != 0) error (strcat ("RegressionPartitionedKernel: optional arguments", ... " must be given in Name-Value pairs.")); endif [P, args] = partitionedArgs (varargin, 'RegressionPartitionedKernel'); F = regFrame (X, Y, P.Weights, 'RegressionPartitionedKernel'); [part, args] = cvPartitionOf (args, [], F.n, ... 'RegressionPartitionedKernel'); ## Every fold is fitted without a response transform, so that the one ## the parent carries is applied exactly once to the assembled ## predictions. fargs = [args, {'ResponseTransform', 'none'}]; G = struct (); G.X = F.X; G.Y = F.Y; G.Weights = F.Weights; this.Trained = foldModels ('RegressionKernel', G, part, fargs); this.NumObservations = F.n; this.Y = F.Y; this.W = F.W; this.X_ = F.X; this.KFold = part.NumTestSets; this.Partition = part; this.NumLambda_ = numel (this.Trained{1}.Lambda); this.HasEpsilon_ = ! isempty (this.Trained{1}.Epsilon); this.PredictorNames = this.Trained{1}.PredictorNames; this.CategoricalPredictors = this.Trained{1}.CategoricalPredictors; this.ResponseName = this.Trained{1}.ResponseName; if (isempty (P.ResponseTransform)) P.ResponseTransform = 'none'; endif [this.RTfun, this.ResponseTransform] = ... parseResponseTransform (P.ResponseTransform, ... 'RegressionPartitionedKernel'); ## The learner's parameters, under this class's own tags. MATLAB ## reports an EnsembleParams here instead and so says nothing about ## the fit; see the ModelParameters property for the deviation. this.ModelParameters = partitionedModelParams (this.Trained{1}, ... this.KFold, 'PartitionedKernel', ... 'regression', 'Kernel'); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionPartitionedKernel} {@var{yFit} =} kfoldPredict (@var{obj}) ## ## Out-of-fold prediction for every observation. ## ## Each observation is predicted by the fold that held it out, so the ## predictions are out-of-sample. An observation that no fold held out, ## which under a holdout partition is most of them, comes back ## @qcode{NaN}. ## ## ## @end deftypefn function yFit = kfoldPredict (this) yFit = this.RTfun (kfoldResponse (this.Trained, this.Partition, ... this.X_, this.NumLambda_)); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionPartitionedKernel} {@var{l} =} kfoldLoss (@var{obj}) ## @deftypefnx {RegressionPartitionedKernel} {@var{l} =} kfoldLoss (@dots{}, @var{name}, @var{value}) ## ## Out-of-fold regression loss. ## ## @code{@var{l} = kfoldLoss (@var{obj})} returns the out-of-fold mean ## squared error. ## ## @code{@var{l} = kfoldLoss (@dots{}, @var{name}, @var{value})} takes ## @qcode{'LossFun'}, either @qcode{'mse'} or ## @qcode{'epsiloninsensitive'}; @qcode{'Folds'}, a subset of the folds ## to average over; and @qcode{'Mode'}, either @qcode{'average'}, the ## default, or @qcode{'individual'}, which returns one row per fold. ## ## @end deftypefn function l = kfoldLoss (this, varargin) O = kfoldOpts (varargin, {'mse', 'epsiloninsensitive'}, ... 'RegressionPartitionedKernel', 'kfoldLoss', this.KFold); if (isempty (O.LossFun)) O.LossFun = 'mse'; endif if (strcmp (O.LossFun, 'epsiloninsensitive') && ! this.HasEpsilon_) error (strcat ("RegressionPartitionedKernel.kfoldLoss: the", ... " 'epsiloninsensitive' loss applies to a support", ... " vector machine only.")); endif yFit = kfoldPredict (this); ## Each fold has its own insensitive band, so a residual is judged ## against the band of the fold that produced it. Assembled per ## observation, the band follows the prediction. band = nan (this.NumObservations, 1); for i = 1:this.KFold idx = test (this.Partition, i); if (any (idx) && ! isempty (this.Trained{i}.Epsilon)) band(idx) = this.Trained{i}.Epsilon; endif endfor sets = foldSets (this.Partition, O.Folds, O.Mode, ... this.NumObservations); l = zeros (numel (sets), this.NumLambda_); for i = 1:numel (sets) idx = sets{i}; w = this.W(idx); w = w / sum (w); for k = 1:this.NumLambda_ r = this.Y(idx) - yFit(idx,k); if (strcmp (O.LossFun, 'mse')) l(i,k) = sum (w .* (r .^ 2)); else l(i,k) = sum (w .* max (0, abs (r) - band(idx))); endif endfor endfor endfunction endmethods methods (Access = public, Hidden) function this = set.ResponseTransform (this, val) [f, nm] = parseResponseTransform (val, 'RegressionPartitionedKernel'); this.ResponseTransform = nm; this.RTfun = f; endfunction function display (this) in_name = inputname (1); if (! isempty (in_name)) printf ('%s =\n', in_name); endif disp (this); endfunction function disp (this) printf ("\n RegressionPartitionedKernel\n\n"); printf ("%+25s: '%s'\n", 'CrossValidatedModel', ... this.CrossValidatedModel); printf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); printf ("%+25s: '%s'\n", 'ResponseTransform', this.ResponseTransform); printf ("%+25s: %d\n", 'NumObservations', this.NumObservations); printf ("%+25s: %d\n", 'KFold', this.KFold); printf ("\n"); endfunction endmethods endclassdef %!demo %! ## Cross-validate a Gaussian kernel regression of fuel consumption and %! ## read the out-of-sample mean squared error. %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! CVMdl = RegressionPartitionedKernel (X(ok,:), MPG(ok), 'KFold', 5) %! outOfSample = kfoldLoss (CVMdl) %!shared X, Y %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! X = X(ok,:); %! Y = MPG(ok); %!test %! ## The model reports the surface MATLAB reports %! CVMdl = RegressionPartitionedKernel (X, Y, 'KFold', 4); %! assert_equal (class (CVMdl), 'RegressionPartitionedKernel'); %! assert_equal (CVMdl.CrossValidatedModel, 'Kernel'); %! assert_equal (CVMdl.KFold, 4); %! assert_equal (CVMdl.NumObservations, 93); %! assert_equal (CVMdl.ResponseTransform, 'none'); %! assert_equal (class (CVMdl.Trained{1}), 'RegressionKernel'); %! assert_equal (CVMdl.ModelParameters.Method, 'PartitionedKernel'); %! assert_equal (CVMdl.ModelParameters.LearnerTemplates, 'Kernel'); %!test %! ## The properties are the ones MATLAB lists, in its order %! CVMdl = RegressionPartitionedKernel (X, Y); %! assert_equal (sort (properties (CVMdl)), ... %! sort ({'ResponseTransform'; 'CrossValidatedModel'; ... %! 'NumObservations'; 'Y'; 'W'; 'PredictorNames'; ... %! 'CategoricalPredictors'; 'ResponseName'; 'Trained'; ... %! 'KFold'; 'Partition'; 'ModelParameters'})); %!test %! ## Each fold resolves its own strength and its own band from its own %! ## training rows %! CVMdl = RegressionPartitionedKernel (X, Y, 'KFold', 4); %! n1 = sum (training (CVMdl.Partition, 1)); %! assert_equal (CVMdl.Trained{1}.Lambda, 1 / n1, 1e-15); %! assert_equal (CVMdl.Trained{1}.NumExpansionDimensions, 128); %!test %! ## kfoldPredict predicts each observation with the fold that held it out %! part = cvpartition (93, 'KFold', 4); %! CVMdl = RegressionPartitionedKernel (X, Y, 'CVPartition', part); %! yFit = kfoldPredict (CVMdl); %! byhand = nan (93, 1); %! for k = 1:4 %! te = test (part, k); %! byhand(te) = predict (CVMdl.Trained{k}, X(te,:)); %! endfor %! assert_equal (yFit, byhand); %!test %! ## Averaging pools the observations rather than the per-fold values %! CVMdl = RegressionPartitionedKernel (X, Y, 'KFold', 4, ... %! 'Learner', 'leastsquares'); %! yFit = kfoldPredict (CVMdl); %! assert_equal (kfoldLoss (CVMdl), mean ((Y - yFit) .^ 2), 1e-10); %! assert_equal (size (kfoldLoss (CVMdl, 'Mode', 'individual')), [4, 1]); %!test %! ## The epsilon-insensitive loss is offered by a support vector machine %! ## alone, and judges each residual against the band of its own fold %! CVMdl = RegressionPartitionedKernel (X, Y, 'KFold', 4); %! assert_equal (isfinite (kfoldLoss (CVMdl, 'LossFun', ... %! 'epsiloninsensitive')), true); %!test %! ## Every fold draws its own basis, so two folds hold different %! ## expansions of the same kernel %! CVMdl = RegressionPartitionedKernel (X, Y, 'KFold', 4); %! p1 = predict (CVMdl.Trained{1}, X(1:3,:)); %! p2 = predict (CVMdl.Trained{2}, X(1:3,:)); %! assert_equal (isequal (p1, p2), false); %!test %! ## An observation that no fold held out comes back NaN %! CVMdl = RegressionPartitionedKernel (X, Y, 'Holdout', 0.3); %! assert_equal (CVMdl.KFold, 1); %! assert_equal (sum (isnan (kfoldPredict (CVMdl))), 65); %! assert_equal (isfinite (kfoldLoss (CVMdl)), true); %!test %! ## Standardizing reaches every fold %! CVMdl = RegressionPartitionedKernel (X, Y, 'KFold', 4, ... %! 'Standardize', true); %! assert_equal (isempty (CVMdl.Trained{1}.Mu), false); %! assert_equal (isempty (CVMdl.Trained{4}.Sigma), false); %!test %! ## It can be assigned after the model is built, and reaches kfoldPredict %! ## without being carried into the folds %! CVMdl = RegressionPartitionedKernel (X, Y, 'KFold', 4); %! y0 = kfoldPredict (CVMdl); %! CVMdl.ResponseTransform = @(y) y + 100; %! y1 = kfoldPredict (CVMdl); %! assert_equal (CVMdl.Trained{1}.ResponseTransform, 'none'); %! assert_equal (y1, y0 + 100, 1e-10); %!test %! ## And it reaches kfoldLoss, which is computed from those predictions %! CVMdl = RegressionPartitionedKernel (X, Y, 'KFold', 4); %! before = kfoldLoss (CVMdl); %! CVMdl.ResponseTransform = @(y) y + 100; %! assert (kfoldLoss (CVMdl) > before); %!test %! ## 'none' is the identity, so assigning it transforms nothing %! CVMdl = RegressionPartitionedKernel (X, Y, 'KFold', 4); %! y0 = kfoldPredict (CVMdl); %! CVMdl.ResponseTransform = 'none'; %! assert_equal (kfoldPredict (CVMdl), y0); %!error ... %! CVMdl = RegressionPartitionedKernel (X, Y, 'KFold', 4); %! CVMdl.ResponseTransform = 'nosuchtransform'; ## Test input validation %!error ... %! RegressionPartitionedKernel (ones (10, 2)) %!error ... %! RegressionPartitionedKernel (ones (10, 2), ones (10, 1), 'KFold') %!error ... %! RegressionPartitionedKernel (ones (10, 2), ones (10, 1), 'KFold', 1) %!error ... %! RegressionPartitionedKernel (ones (10, 2), ones (10, 1), 'KFold', 2, ... %! 'Leaveout', 'on') %!error ... %! kfoldLoss (RegressionPartitionedKernel (ones (10, 2), ones (10, 1), ... %! 'KFold', 2), 'LossFun', 'hinge') %!error ... %! kfoldLoss (RegressionPartitionedKernel (ones (10, 2), ones (10, 1), ... %! 'KFold', 2, 'Learner', 'leastsquares'), 'LossFun', ... %! 'epsiloninsensitive') ## ModelParameters carries the learner's parameters beside the tags this ## class reports for itself. %!test %! load fisheriris %! CVMdl = fitrkernel (meas(:,2:4), meas(:,1), 'KFold', 3); %! MP = CVMdl.ModelParameters; %! assert_equal (MP.Method, 'PartitionedKernel'); %! assert_equal (MP.LearnerTemplates, 'Kernel'); %! assert_equal (MP.NLearn, 3); %! assert_equal (MP.Learner, 'svm'); %! assert_equal (MP.BlockSize, 4000); ## Every documented response transform reaches the response that is reported. %!test %! load fisheriris %! Mdl = fitrkernel (meas(:,2:4), meas(:,1), 'KFold', 3); %! Mdl.ResponseTransform = 'none'; %! raw = kfoldPredict (Mdl); %! T = {'identity', @(x) x; 'exp', @(x) exp (x); 'log', @(x) log (x)}; %! for i = 1:rows (T) %! Mdl.ResponseTransform = T{i,1}; %! yhat = kfoldPredict (Mdl); %! assert_equal (yhat, T{i,2}(raw), 1e-12); %! endfor ## A function handle is taken as given and applied to the response. %!test %! load fisheriris %! Mdl = fitrkernel (meas(:,2:4), meas(:,1), 'KFold', 3); %! Mdl.ResponseTransform = 'none'; %! raw = kfoldPredict (Mdl); %! Mdl.ResponseTransform = @(x) x .^ 2; %! yhat = kfoldPredict (Mdl); %! assert_equal (yhat, raw .^ 2, 1e-12); statistics-release-1.9.2/inst/Supervised_Learning/RegressionPartitionedLinear.m000066400000000000000000000605601524624707500301370ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftp {statistics} RegressionPartitionedLinear ## ## Cross-validated linear regression model. ## ## A @qcode{RegressionPartitionedLinear} object holds one ## @qcode{RegressionLinear} per fold of a partition, each fitted to the ## observations the fold trains on. @code{kfoldPredict} predicts each ## observation with the fold that held it @emph{out}, so what it returns is ## an out-of-sample prediction. ## ## A @qcode{RegressionLinear} stores no copy of its training data and so has ## no resubstitution methods and no @code{compact} form. This class is what ## takes their place: cross-validation is the way a linear model is asked ## how it would do on data it has not seen. ## ## When the fold models carry a whole regularization path, both methods ## return one column per strength, in the order of the @qcode{'Lambda'} that ## was asked for. ## ## Create one with @code{fitrlinear} and a cross-validation option, or ## directly. ## ## @seealso{fitrlinear, RegressionLinear, RegressionPartitionedKernel} ## @end deftp classdef RegressionPartitionedLinear properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {RegressionPartitionedLinear} {property} ResponseTransform ## ## Transformation applied to the predicted response ## ## A character vector, or the text of the function handle that was ## supplied, which may be assigned after the model is built. The fold ## models carry no transform of their own; this one is applied once to ## the assembled predictions. ## ## @end deftp ResponseTransform = 'none'; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {RegressionPartitionedLinear} {property} CrossValidatedModel ## ## Name of the model that was cross-validated ## ## Always @qcode{'Linear'}, the short name MATLAB uses. This property is ## read-only. ## ## @end deftp CrossValidatedModel = 'Linear'; ## -*- texinfo -*- ## @deftp {RegressionPartitionedLinear} {property} NumObservations ## ## Number of observations the partition covers ## ## A positive integer scalar, counting the rows that survived the removal ## of missing values. This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedLinear} {property} Y ## ## Response of the retained observations ## ## An @math{Nx1} numeric vector. This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedLinear} {property} W ## ## Observation weights ## ## An @math{Nx1} numeric vector summing to one. This property is ## read-only. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedLinear} {property} PredictorNames ## ## Names of the predictors ## ## A cell array of character vectors. This property is read-only. ## ## @end deftp PredictorNames = {}; ## -*- texinfo -*- ## @deftp {RegressionPartitionedLinear} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A row vector of column indices, empty when every predictor is ## numeric. This property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedLinear} {property} ResponseName ## ## Name of the response ## ## A character vector, defaulting to @qcode{'Y'}. This property is ## read-only. ## ## @end deftp ResponseName = 'Y'; ## -*- texinfo -*- ## @deftp {RegressionPartitionedLinear} {property} Trained ## ## The models fitted to the folds ## ## A cell column with one @qcode{RegressionLinear} per fold, each fitted ## to the observations its fold trains on. This property is read-only. ## ## @end deftp Trained = {}; ## -*- texinfo -*- ## @deftp {RegressionPartitionedLinear} {property} KFold ## ## Number of folds ## ## A positive integer scalar. A holdout partition has one fold and a ## leave-one-out partition has as many as there are observations. This ## property is read-only. ## ## @end deftp KFold = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedLinear} {property} Partition ## ## The partition itself ## ## A @code{cvpartition} object over the retained observations. This ## property is read-only. ## ## @end deftp Partition = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedLinear} {property} ModelParameters ## ## What was cross-validated, and how ## ## A structure holding the parameters the folds were fitted with, carried ## through from the learner that was cross validated, beside ## @qcode{NLearn}, the number of folds, and the @qcode{Version}, ## @qcode{Method} and @qcode{Type} tags of this class, with ## @qcode{LearnerTemplates} naming the backing. The ## learner's own tags are replaced rather than kept, so a cross-validated ## SVM reports @qcode{Method} as @qcode{'PartitionedLinear'} and not ## @qcode{'SVM'}. ## ## @strong{Deviation from MATLAB.} MATLAB reports the parameter record of ## the cross-validation @emph{ensemble} here rather than of the learner, ## so it says nothing at all about how the folds were fitted: of its ## eighteen fields only the fold count, its partitioner and a fit template ## carry anything, and the rest are boosting settings left inert. Nor can ## the parameters be reached through the folds, a compact model carrying ## none in MATLAB. This class reports the fit instead, which is strictly ## more than MATLAB offers, and everything MATLAB's record does carry is ## published here as the @qcode{KFold}, @qcode{Partition}, @qcode{X}, ## @qcode{Y}, @qcode{W} and @qcode{CrossValidatedModel} properties. ## ## This property is read-only. ## ## @end deftp ModelParameters = []; endproperties properties (GetAccess = public, SetAccess = protected, Hidden) ## The callable behind ResponseTransform. RTfun = @(y) y; ## The predictors of the retained observations. MATLAB does not report ## them and neither do we, but the kfold methods have to predict from ## something and the fold models hold no data of their own. X_ = []; ## Number of regularization strengths the fold models carry. NumLambda_ = 1; ## Whether the fold models were fitted with an insensitive band, which ## decides whether kfoldLoss will offer the loss that reads it. HasEpsilon_ = false; endproperties methods (Access = public) ## -*- texinfo -*- ## @deftypefn {RegressionPartitionedLinear} {@var{obj} =} RegressionPartitionedLinear (@var{X}, @var{Y}) ## @deftypefnx {RegressionPartitionedLinear} {@var{obj} =} RegressionPartitionedLinear (@dots{}, @var{name}, @var{value}) ## ## Cross-validate a linear regression model. ## ## @code{@var{obj} = RegressionPartitionedLinear (@var{X}, @var{Y})} ## partitions the data into ten folds and fits a ## @qcode{RegressionLinear} to each. ## ## @code{@var{obj} = RegressionPartitionedLinear (@dots{}, @var{name}, ## @var{value})} takes one of @qcode{'KFold'}, @qcode{'Holdout'}, ## @qcode{'Leaveout'} and @qcode{'CVPartition'} to say how to partition, ## and any option @code{RegressionLinear} takes to say how to fit. ## @qcode{'CrossVal'} is accepted and has no effect here, this class ## being cross-validated by construction. ## ## Anything left as @qcode{'auto'} is resolved by each fold against its ## own training rows rather than once over the whole data, so ten folds ## of a hundred observations each get a @qcode{Lambda} of one ninetieth ## rather than one hundredth, and each its own @qcode{Epsilon}. Both are ## MATLAB's behaviour, measured. ## ## @seealso{fitrlinear, RegressionLinear} ## @end deftypefn function this = RegressionPartitionedLinear (X, Y, varargin) if (nargin < 2) error (strcat ("RegressionPartitionedLinear: too few input", ... " arguments.")); endif if (mod (numel (varargin), 2) != 0) error (strcat ("RegressionPartitionedLinear: optional arguments", ... " must be given in Name-Value pairs.")); endif [P, args] = partitionedArgs (varargin, 'RegressionPartitionedLinear'); F = regFrame (X, Y, P.Weights, 'RegressionPartitionedLinear'); [part, args] = cvPartitionOf (args, [], F.n, ... 'RegressionPartitionedLinear'); ## Every fold is fitted without a response transform, so that the one ## the parent carries is applied exactly once to the assembled ## predictions. fargs = [args, {'ResponseTransform', 'none'}]; G = struct (); G.X = F.X; G.Y = F.Y; G.Weights = F.Weights; this.Trained = foldModels ('RegressionLinear', G, part, fargs); this.NumObservations = F.n; this.Y = F.Y; this.W = F.W; this.X_ = F.X; this.KFold = part.NumTestSets; this.Partition = part; this.NumLambda_ = numel (this.Trained{1}.Lambda); this.HasEpsilon_ = ! isempty (this.Trained{1}.Epsilon); this.PredictorNames = this.Trained{1}.PredictorNames; this.CategoricalPredictors = this.Trained{1}.CategoricalPredictors; this.ResponseName = this.Trained{1}.ResponseName; if (isempty (P.ResponseTransform)) P.ResponseTransform = 'none'; endif [this.RTfun, this.ResponseTransform] = ... parseResponseTransform (P.ResponseTransform, ... 'RegressionPartitionedLinear'); ## The learner's parameters, under this class's own tags. MATLAB ## reports an EnsembleParams here instead and so says nothing about ## the fit; see the ModelParameters property for the deviation. this.ModelParameters = partitionedModelParams (this.Trained{1}, ... this.KFold, 'PartitionedLinear', ... 'regression', 'Linear'); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionPartitionedLinear} {@var{yFit} =} kfoldPredict (@var{obj}) ## ## Out-of-fold prediction for every observation. ## ## Each observation is predicted by the fold that held it out, so the ## predictions are out-of-sample. An observation that no fold held out, ## which under a holdout partition is most of them, comes back ## @qcode{NaN}. ## ## With @math{L} regularization strengths @var{yFit} has one column per ## strength. ## ## @end deftypefn function yFit = kfoldPredict (this) yFit = this.RTfun (kfoldResponse (this.Trained, this.Partition, ... this.X_, this.NumLambda_)); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionPartitionedLinear} {@var{l} =} kfoldLoss (@var{obj}) ## @deftypefnx {RegressionPartitionedLinear} {@var{l} =} kfoldLoss (@dots{}, @var{name}, @var{value}) ## ## Out-of-fold regression loss. ## ## @code{@var{l} = kfoldLoss (@var{obj})} returns the out-of-fold mean ## squared error. ## ## @code{@var{l} = kfoldLoss (@dots{}, @var{name}, @var{value})} takes ## @qcode{'LossFun'}, either @qcode{'mse'} or ## @qcode{'epsiloninsensitive'}; @qcode{'Folds'}, a subset of the folds ## to average over; and @qcode{'Mode'}, either @qcode{'average'}, the ## default, or @qcode{'individual'}, which returns one row per fold. ## ## @end deftypefn function l = kfoldLoss (this, varargin) O = kfoldOpts (varargin, {'mse', 'epsiloninsensitive'}, ... 'RegressionPartitionedLinear', 'kfoldLoss', this.KFold); if (isempty (O.LossFun)) O.LossFun = 'mse'; endif if (strcmp (O.LossFun, 'epsiloninsensitive') && ! this.HasEpsilon_) error (strcat ("RegressionPartitionedLinear.kfoldLoss: the", ... " 'epsiloninsensitive' loss applies to a support", ... " vector machine only.")); endif yFit = kfoldPredict (this); ## Each fold has its own insensitive band, so a residual is judged ## against the band of the fold that produced it. Assembled per ## observation, the band follows the prediction. band = nan (this.NumObservations, 1); for i = 1:this.KFold idx = test (this.Partition, i); if (any (idx) && ! isempty (this.Trained{i}.Epsilon)) band(idx) = this.Trained{i}.Epsilon; endif endfor sets = foldSets (this.Partition, O.Folds, O.Mode, ... this.NumObservations); l = zeros (numel (sets), this.NumLambda_); for i = 1:numel (sets) idx = sets{i}; w = this.W(idx); w = w / sum (w); for k = 1:this.NumLambda_ r = this.Y(idx) - yFit(idx,k); if (strcmp (O.LossFun, 'mse')) l(i,k) = sum (w .* (r .^ 2)); else l(i,k) = sum (w .* max (0, abs (r) - band(idx))); endif endfor endfor endfunction endmethods methods (Access = public, Hidden) function this = set.ResponseTransform (this, val) [f, nm] = parseResponseTransform (val, 'RegressionPartitionedLinear'); this.ResponseTransform = nm; this.RTfun = f; endfunction function display (this) in_name = inputname (1); if (! isempty (in_name)) printf ('%s =\n', in_name); endif disp (this); endfunction function disp (this) printf ("\n RegressionPartitionedLinear\n\n"); printf ("%+25s: '%s'\n", 'CrossValidatedModel', ... this.CrossValidatedModel); printf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); printf ("%+25s: '%s'\n", 'ResponseTransform', this.ResponseTransform); printf ("%+25s: %d\n", 'NumObservations', this.NumObservations); printf ("%+25s: %d\n", 'KFold', this.KFold); printf ("\n"); endfunction endmethods endclassdef %!demo %! ## Cross-validate a linear regression of fuel consumption and read the %! ## out-of-sample mean squared error. %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! CVMdl = RegressionPartitionedLinear (X(ok,:), MPG(ok), 'KFold', 5) %! outOfSample = kfoldLoss (CVMdl) %!shared X, Y %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! X = X(ok,:); %! Y = MPG(ok); %!test %! ## The model reports the surface MATLAB reports %! CVMdl = RegressionPartitionedLinear (X, Y, 'KFold', 4); %! assert_equal (class (CVMdl), 'RegressionPartitionedLinear'); %! assert_equal (CVMdl.CrossValidatedModel, 'Linear'); %! assert_equal (CVMdl.KFold, 4); %! assert_equal (CVMdl.NumObservations, 93); %! assert_equal (CVMdl.ResponseTransform, 'none'); %! assert_equal (CVMdl.ResponseName, 'Y'); %! assert_equal (CVMdl.PredictorNames, {'x1', 'x2', 'x3', 'x4'}); %! assert_equal (size (CVMdl.Trained), [4, 1]); %! assert_equal (class (CVMdl.Trained{1}), 'RegressionLinear'); %! assert_equal (sum (CVMdl.W), 1, 1e-12); %! assert_equal (CVMdl.ModelParameters.Method, 'PartitionedLinear'); %!test %! ## The properties are the ones MATLAB lists, in its order %! CVMdl = RegressionPartitionedLinear (X, Y); %! assert_equal (sort (properties (CVMdl)), ... %! sort ({'ResponseTransform'; 'CrossValidatedModel'; ... %! 'NumObservations'; 'Y'; 'W'; 'PredictorNames'; ... %! 'CategoricalPredictors'; 'ResponseName'; 'Trained'; ... %! 'KFold'; 'Partition'; 'ModelParameters'})); %!test %! ## The default is ten folds %! CVMdl = RegressionPartitionedLinear (X, Y); %! assert_equal (CVMdl.KFold, 10); %! assert_equal (numel (CVMdl.Trained), 10); %!test %! ## Each fold resolves its own 'Lambda' and its own 'Epsilon' from its own %! ## training rows, which is why two folds report different bands %! CVMdl = RegressionPartitionedLinear (X, Y, 'KFold', 4); %! n1 = sum (training (CVMdl.Partition, 1)); %! assert_equal (CVMdl.Trained{1}.Lambda, 1 / n1, 1e-15); %! assert_equal (isequal (CVMdl.Trained{1}.Epsilon, ... %! CVMdl.Trained{4}.Epsilon), false); %!test %! ## kfoldPredict predicts each observation with the fold that held it out, %! ## and doing the same partition by hand gives the same numbers %! part = cvpartition (93, 'KFold', 4); %! CVMdl = RegressionPartitionedLinear (X, Y, 'CVPartition', part, ... %! 'Learner', 'leastsquares'); %! yFit = kfoldPredict (CVMdl); %! byhand = nan (93, 1); %! for k = 1:4 %! tr = training (part, k); %! te = test (part, k); %! m = RegressionLinear (X(tr,:), Y(tr), 'Learner', 'leastsquares'); %! byhand(te) = predict (m, X(te,:)); %! endfor %! assert_equal (yFit, byhand, 1e-10); %!test %! ## Averaging pools the observations rather than the per-fold values, and %! ## the two differ once the folds differ in size. The pooled reading is %! ## the one R2024a returns. %! part = cvpartition (93, 'KFold', 4); %! CVMdl = RegressionPartitionedLinear (X, Y, 'CVPartition', part, ... %! 'Learner', 'leastsquares'); %! yFit = kfoldPredict (CVMdl); %! assert_equal (kfoldLoss (CVMdl), mean ((Y - yFit) .^ 2), 1e-10); %! each = kfoldLoss (CVMdl, 'Mode', 'individual'); %! assert_equal (size (each), [4, 1]); %! assert_equal (isequal (kfoldLoss (CVMdl), mean (each)), false); %!test %! ## 'Folds' reports over the observations of the folds it names %! CVMdl = RegressionPartitionedLinear (X, Y, 'KFold', 4, ... %! 'Learner', 'leastsquares'); %! yFit = kfoldPredict (CVMdl); %! idx = test (CVMdl.Partition, 1) | test (CVMdl.Partition, 3); %! assert_equal (kfoldLoss (CVMdl, 'Folds', [1, 3]), ... %! mean ((Y(idx) - yFit(idx)) .^ 2), 1e-10); %!test %! ## The epsilon-insensitive loss judges each residual against the band of %! ## the fold that produced it, and is offered by a support vector machine %! ## alone %! CVMdl = RegressionPartitionedLinear (X, Y, 'KFold', 4); %! assert_equal (isfinite (kfoldLoss (CVMdl, 'LossFun', ... %! 'epsiloninsensitive')), true); %! assert_equal (kfoldLoss (CVMdl, 'LossFun', 'epsiloninsensitive') ... %! < kfoldLoss (CVMdl), true); %!test %! ## A whole regularization path gives one column per strength %! CVMdl = RegressionPartitionedLinear (X, Y, 'KFold', 4, ... %! 'Lambda', [0.001, 0.01, 0.1]); %! assert_equal (size (kfoldPredict (CVMdl)), [93, 3]); %! assert_equal (size (kfoldLoss (CVMdl)), [1, 3]); %! assert_equal (size (kfoldLoss (CVMdl, 'Mode', 'individual')), [4, 3]); %!test %! ## An observation that no fold held out comes back NaN rather than %! ## predicted %! CVMdl = RegressionPartitionedLinear (X, Y, 'Holdout', 0.3); %! assert_equal (CVMdl.KFold, 1); %! yFit = kfoldPredict (CVMdl); %! assert_equal (sum (isnan (yFit)), 65); %! assert_equal (isfinite (kfoldLoss (CVMdl)), true); %!test %! ## A response transform reaches the assembled predictions once, the fold %! ## models carrying none %! part = cvpartition (93, 'KFold', 4); %! plain = RegressionPartitionedLinear (X, Y, 'CVPartition', part, ... %! 'Learner', 'leastsquares'); %! CVexp = RegressionPartitionedLinear (X, Y, 'CVPartition', part, ... %! 'Learner', 'leastsquares', ... %! 'ResponseTransform', 'exp'); %! assert_equal (CVexp.ResponseTransform, 'exp'); %! assert_equal (CVexp.Trained{1}.ResponseTransform, 'none'); %! assert_equal (kfoldPredict (CVexp), exp (kfoldPredict (plain)), 1e-10); %!test %! ## A row with a missing value is dropped before the partition %! Xn = X; %! Xn(3,2) = NaN; %! CVMdl = RegressionPartitionedLinear (Xn, Y, 'KFold', 4); %! assert_equal (CVMdl.NumObservations, 92); %! assert_equal (CVMdl.Partition.NumObservations, 92); %!test %! ## It can be assigned after the model is built, and reaches kfoldPredict %! ## without being carried into the folds %! CVMdl = RegressionPartitionedLinear (X, Y, 'KFold', 4); %! y0 = kfoldPredict (CVMdl); %! CVMdl.ResponseTransform = @(y) y + 100; %! y1 = kfoldPredict (CVMdl); %! assert_equal (CVMdl.Trained{1}.ResponseTransform, 'none'); %! assert_equal (y1, y0 + 100, 1e-10); %!test %! ## And it reaches kfoldLoss, which is computed from those predictions %! CVMdl = RegressionPartitionedLinear (X, Y, 'KFold', 4); %! before = kfoldLoss (CVMdl); %! CVMdl.ResponseTransform = @(y) y + 100; %! assert (kfoldLoss (CVMdl) > before); %!test %! ## 'none' is the identity, so assigning it transforms nothing %! CVMdl = RegressionPartitionedLinear (X, Y, 'KFold', 4); %! y0 = kfoldPredict (CVMdl); %! CVMdl.ResponseTransform = 'none'; %! assert_equal (kfoldPredict (CVMdl), y0); %!error ... %! CVMdl = RegressionPartitionedLinear (X, Y, 'KFold', 4); %! CVMdl.ResponseTransform = 'nosuchtransform'; ## Test input validation %!error ... %! RegressionPartitionedLinear (ones (10, 2)) %!error ... %! RegressionPartitionedLinear (ones (10, 2), ones (10, 1), 'KFold') %!error ... %! RegressionPartitionedLinear (ones (10, 2), ones (10, 1), 'KFold', 1) %!error ... %! RegressionPartitionedLinear (ones (10, 2), ones (10, 1), 'KFold', 2, ... %! 'Holdout', 0.2) %!error ... %! RegressionPartitionedLinear (ones (10, 2), {1, 2}) %!error ... %! kfoldLoss (RegressionPartitionedLinear (ones (10, 2), ones (10, 1), ... %! 'KFold', 2), 'LossFun', 'hinge') %!error ... %! kfoldLoss (RegressionPartitionedLinear (ones (10, 2), ones (10, 1), ... %! 'KFold', 2, 'Learner', 'leastsquares'), 'LossFun', ... %! 'epsiloninsensitive') %!error ... %! kfoldLoss (RegressionPartitionedLinear (ones (10, 2), ones (10, 1), ... %! 'KFold', 2), 'Mode', 'each') ## Every documented response transform reaches the response that is reported. %!test %! load fisheriris %! Mdl = fitrlinear (meas(:,2:4), meas(:,1), 'KFold', 3); %! Mdl.ResponseTransform = 'none'; %! raw = kfoldPredict (Mdl); %! T = {'identity', @(x) x; 'exp', @(x) exp (x); 'log', @(x) log (x)}; %! for i = 1:rows (T) %! Mdl.ResponseTransform = T{i,1}; %! yhat = kfoldPredict (Mdl); %! assert_equal (yhat, T{i,2}(raw), 1e-12); %! endfor ## A function handle is taken as given and applied to the response. %!test %! load fisheriris %! Mdl = fitrlinear (meas(:,2:4), meas(:,1), 'KFold', 3); %! Mdl.ResponseTransform = 'none'; %! raw = kfoldPredict (Mdl); %! Mdl.ResponseTransform = @(x) x .^ 2; %! yhat = kfoldPredict (Mdl); %! assert_equal (yhat, raw .^ 2, 1e-12); statistics-release-1.9.2/inst/Supervised_Learning/RegressionPartitionedModel.m000066400000000000000000001566201524624707500277700ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} RegressionPartitionedModel (@var{Mdl}, @var{Partition}) ## ## Create a @qcode{RegressionPartitionedModel} object, a regression model ## cross validated over a partition of its training data. ## ## @code{@var{obj} = RegressionPartitionedModel (@var{Mdl}, @var{Partition})} ## refits @var{Mdl} once per fold of @var{Partition}, each time on the ## observations that fold holds out of its test set, and stores the compact ## form of every fit in @code{Trained}. It is normally reached through ## @code{crossval (@var{Mdl})} rather than called directly. ## ## @itemize ## @item ## @var{Mdl} must be a @qcode{RegressionGAM}, a ## @qcode{RegressionNeuralNetwork}, or a @qcode{RegressionSVM} object. ## @item ## @var{Partition} must be a @qcode{cvpartition} object over as many ## observations as @var{Mdl} was trained on. ## @end itemize ## ## Every observation is held out by exactly one fold under @math{k}-fold or ## leave-one-out partitioning, so @code{kfoldPredict} can answer for it with a ## model that never saw it. Under a holdout partition only the test set is ## answered for, and the rest come back @code{NaN}. ## ## @seealso{crossval, cvpartition, RegressionGAM, RegressionNeuralNetwork, ## RegressionSVM} ## @end deftypefn classdef RegressionPartitionedModel properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} ResponseTransform ## ## Transformation applied to the predicted response ## ## A function handle, carried over from the model that was cross ## validated. This property is read-only. ## ## @end deftp ResponseTransform = 'none'; endproperties properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} CrossValidatedModel ## ## Name of the cross-validated model ## ## A character vector holding the short name of the learner that was ## cross validated, as MATLAB reports it: @qcode{'GAM'}, @qcode{'GP'}, ## @qcode{'NeuralNetwork'} or @qcode{'SVM'}. It is not the class name of ## that learner, and the classification side uses the same names. This ## property is read-only. ## ## @end deftp CrossValidatedModel = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} PredictorNames ## ## Names of the predictors ## ## A cell array of character vectors. This property is read-only. ## ## @end deftp PredictorNames = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector of column indices, and empty when none is. This ## property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} ResponseName ## ## Name of the response variable ## ## A character vector. This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} NumObservations ## ## Number of observations ## ## A positive integer scalar. This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} X ## ## Predictor data ## ## A numeric matrix holding the observations the model was trained on, ## the rows carrying missing values already removed. This property is ## read-only. ## ## @end deftp X = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} Y ## ## Response data ## ## A numeric column vector with one entry per row of @code{X}. This ## property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} W ## ## Observation weights ## ## A numeric column vector with one entry per observation. This property ## is read-only. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} ModelParameters ## ## Parameters the folds were fitted with ## ## A structure holding the parameters the folds were fitted with, carried ## through from the learner that was cross validated, beside ## @qcode{NLearn}, the number of folds, and the @qcode{Version}, ## @qcode{Method} and @qcode{Type} tags of this class. The ## learner's own tags are replaced rather than kept, so a cross-validated ## SVM reports @qcode{Method} as @qcode{'PartitionedModel'} and not ## @qcode{'SVM'}. ## ## @strong{Deviation from MATLAB.} MATLAB reports the parameter record of ## the cross-validation @emph{ensemble} here rather than of the learner, ## so it says nothing at all about how the folds were fitted: of its ## eighteen fields only the fold count, its partitioner and a fit template ## carry anything, and the rest are boosting settings left inert. Nor can ## the parameters be reached through the folds, a compact model carrying ## none in MATLAB. This class reports the fit instead, which is strictly ## more than MATLAB offers, and everything MATLAB's record does carry is ## published here as the @qcode{KFold}, @qcode{Partition}, @qcode{X}, ## @qcode{Y}, @qcode{W} and @qcode{CrossValidatedModel} properties. ## ## This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} Trained ## ## The models fitted to each fold ## ## A cell array with one compact model per fold, each fitted on the ## observations its fold holds out of the test set. This property is ## read-only. ## ## @end deftp Trained = {}; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} KFold ## ## Number of folds ## ## A positive integer scalar. This property is read-only. ## ## @end deftp KFold = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} Partition ## ## The partition the folds came from ## ## A @code{cvpartition} object. This property is read-only. ## ## @end deftp Partition = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} BinEdges ## ## Bin edges of the predictors ## ## A cell array with one entry per predictor, holding that predictor's bin ## edges where the learner discretized it before fitting. It is carried ## over from the model that was cross validated, and is empty whenever that ## model did no binning, which is every learner this package implements: ## MATLAB fills it only for its generalized additive model, which bins ## because it is built from boosted trees where ours is built from splines. ## ## This property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} IsStandardDeviationFit ## ## Whether the folds fitted a standard deviation model ## ## A logical scalar for a generalized additive model backing, taken from ## the model that was cross validated, and empty for every other backing. ## ## MATLAB carries this on @code{RegressionPartitionedGAM}, one of five ## per-learner partitioned classes this package deliberately does not have ## (see @code{crossval}). With one class serving every backing the ## property has to be declared for all of them, so it is empty where it ## does not apply. It is placed last rather than first, where MATLAB's ## subclass shows it, because that subclass also moves ## @qcode{ResponseTransform} to the end and no single order can match both ## of MATLAB's classes; matching the general one and appending is the only ## coherent choice. ## ## This property is read-only. ## ## @end deftp IsStandardDeviationFit = []; ## -*- texinfo -*- ## @deftp {RegressionPartitionedModel} {property} NumTrainedPerFold ## ## How many trees each fold fitted ## ## A scalar structure with fields @qcode{PredictorTrees} and ## @qcode{InteractionTrees}, each a row with one entry per fold, for a ## generalized additive model backing, and empty for every other. ## ## It reports what each fold actually fitted, which the budget in ## @qcode{ModelParameters} does not: a phase stops early when it can no ## longer improve the fit, and the folds need not stop at the same place. ## ## MATLAB carries this on its per-learner partitioned GAM classes, which ## this package deliberately does not have (see @code{crossval}), so like ## @qcode{IsStandardDeviationFit} it is declared here for every backing ## and left empty where it does not apply. ## ## This property is read-only. ## ## @end deftp NumTrainedPerFold = []; endproperties ## Copied from the parent model and kept out of the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) RTfun = @(y) y; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.ResponseTransform (this, val) [f, nm] = parseResponseTransform (val, 'RegressionPartitionedModel'); this.ResponseTransform = nm; this.RTfun = f; endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n RegressionPartitionedModel\n\n"); fprintf ("%+25s: '%s'\n", 'CrossValidatedModel', ... this.CrossValidatedModel); fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); fprintf ("%+25s: %d\n", 'NumObservations', this.NumObservations); fprintf ("%+25s: %d\n", 'KFold', this.KFold); fprintf ("%+25s: '%s'\n", 'ResponseTransform', this.ResponseTransform); endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {RegressionPartitionedModel} {@var{obj} =} RegressionPartitionedModel (@var{Mdl}, @var{Partition}) ## ## Create a @qcode{RegressionPartitionedModel} object. ## ## See the class documentation for what it holds and how it is reached. ## ## @seealso{crossval, RegressionPartitionedModel} ## @end deftypefn function this = RegressionPartitionedModel (Mdl, Partition) ## Check input arguments if (nargin < 2) error ("RegressionPartitionedModel: too few input arguments."); endif ## Check for valid Regression object validTypes = {'RegressionGAM', 'RegressionGP', ... 'RegressionNeuralNetwork', 'RegressionSVM'}; if (! any (strcmp (class (Mdl), validTypes))) error ("RegressionPartitionedModel: unsupported model type."); endif ## Check for valid cvpartition object if (! strcmp (class (Partition), 'cvpartition')) error (strcat ("RegressionPartitionedModel: invalid", ... " 'cvpartition' object.")); endif ## The partition indexes the observations actually used for training, ## so the rows dropped for missing values are removed here as well and ## every index below refers to the same set. X = Mdl.X; Y = Mdl.Y; Y = Y(:); if (Partition.NumObservations != rows (X)) error (strcat ("RegressionPartitionedModel: 'cvpartition' object", ... " must be defined over the %d observations the", ... " model was trained on."), rows (X)); endif ## Set properties this.X = X; this.Y = Y; this.W = Mdl.W; this.BinEdges = Mdl.BinEdges; this.KFold = Partition.NumTestSets; this.Trained = cell (this.KFold, 1); this.ResponseName = Mdl.ResponseName; this.NumObservations = rows (X); this.PredictorNames = Mdl.PredictorNames; this.CategoricalPredictors = Mdl.CategoricalPredictors; this.Partition = Partition; ## MATLAB stores a short name here, shared with the classification ## side: 'GAM', 'GP', 'NeuralNetwork', 'SVM'. All measured. this.CrossValidatedModel = strrep (class (Mdl), 'Regression', ''); this.ResponseTransform = Mdl.ResponseTransform; this.RTfun = Mdl.RTfun; ## The learner's parameters, under this class's own tags. MATLAB ## reports an EnsembleParams here instead and so says nothing about the ## fit; see the ModelParameters property for the deviation. this.ModelParameters = partitionedModelParams (Mdl, this.KFold, ... 'PartitionedModel', 'regression'); if (any (strcmp (properties (Mdl), 'IsStandardDeviationFit'))) this.IsStandardDeviationFit = Mdl.IsStandardDeviationFit; endif ## Switch Regression object types switch (this.CrossValidatedModel) case 'GAM' ## Knots, Order and DoF are three views of one parameterisation and ## the constructor accepts any two, recomputing the third, so only ## Knots and Order are passed on. args = {}; ## Which parameters a fold takes depends on which engine fitted ## the parent: the two have disjoint argument surfaces and each ## refuses the other's, so the fold is refitted with its own. if (strcmp (Mdl.FitMethod, 'boostedtrees')) GAMparams = {'PredictorNames', 'ResponseName'}; else GAMparams = {'PredictorNames', 'ResponseName', 'Formula', ... 'Knots', 'Order', 'Tol'}; endif args = [args, {'FitMethod', Mdl.FitMethod}]; for i = 1:numel (GAMparams) paramName = GAMparams{i}; paramValue = Mdl.(paramName); if (! isempty (paramValue)) args = [args, {paramName, paramValue}]; endif endfor ## Interactions now holds the fitted pairs, which the constructor ## does not take as a specification. The term matrix does, and it ## reproduces the parent's terms exactly rather than re-selecting ## them. A formula names its own terms and is passed instead, so ## this must not be passed alongside one. if (isempty (Mdl.Formula) && ! isempty (Mdl.IntMatrix)) args = [args, {'Interactions', Mdl.IntMatrix}]; endif for k = 1:this.KFold idx = training (this.Partition, k); tmp = fitrgam (X(idx, :), Y(idx), args{:}); this.Trained{k} = compact (tmp); endfor case 'NeuralNetwork' ## Computed before the cell literal: inside braces a space before ## the paren would split the call from its argument. stdz = ! isempty (Mdl.Mu); args = {'LayerSizes', Mdl.LayerSizes, ... 'Activations', Mdl.Activations, ... 'OutputLayerActivation', Mdl.OutputLayerActivation, ... 'IterationLimit', Mdl.IterationLimit, ... 'Standardize', stdz, ... 'ResponseName', Mdl.ResponseName, ... 'PredictorNames', Mdl.PredictorNames}; ## As in ClassificationPartitionedModel: the folds are trained by ## the parent's solver, and a learning rate goes only with 'sgd'. if (strcmp (Mdl.Solver, 'LBFGS')) args = [args, {'Solver', 'lbfgs'}]; else args = [args, {'Solver', 'sgd', 'LearningRate', Mdl.LearningRate}]; endif for k = 1:this.KFold idx = training (this.Partition, k); tmp = fitrnet (X(idx, :), Y(idx), args{:}); this.Trained{k} = compact (tmp); endfor case 'GP' p = Mdl.ModelParameters; args = {'KernelFunction', Mdl.KernelFunction, ... 'BasisFunction', Mdl.BasisFunction, ... 'FitMethod', p.FitMethod, ... 'PredictMethod', p.PredictMethod, ... 'Optimizer', p.Optimizer, ... 'ConstantSigma', p.ConstantSigma, ... 'SigmaLowerBound', p.SigmaLowerBound, ... 'Standardize', p.Standardize, ... 'ResponseName', Mdl.ResponseName, ... 'PredictorNames', Mdl.PredictorNames}; for k = 1:this.KFold idx = training (this.Partition, k); tmp = fitrgp (X(idx, :), Y(idx), args{:}); this.Trained{k} = compact (tmp); endfor case 'SVM' p = Mdl.ModelParameters; stdz = ! isempty (Mdl.Mu); ## The polynomial order is recorded for the polynomial kernel ## alone, so it is passed only when the parent carries one. korder = {}; if (! isempty (p.KernelPolynomialOrder)) korder = {'PolynomialOrder', p.KernelPolynomialOrder}; endif args = {'SVMtype', p.SVMtype, ... 'KernelFunction', p.KernelFunction, ... korder{:}, ... 'KernelScale', p.KernelScale, ... 'KernelOffset', p.KernelOffset, ... 'BoxConstraint', p.BoxConstraint, ... 'Epsilon', p.Epsilon, 'Nu', p.Nu, ... 'CacheSize', p.CacheSize, ... 'Tolerance', p.Tolerance, ... 'Shrinking', p.Shrinking, ... 'Standardize', stdz, ... 'ResponseName', Mdl.ResponseName, ... 'PredictorNames', Mdl.PredictorNames}; for k = 1:this.KFold idx = training (this.Partition, k); tmp = fitrsvm (X(idx, :), Y(idx), args{:}); this.Trained{k} = compact (tmp); endfor endswitch ## Gathered across the folds, which is the shape MATLAB reports: one ## structure carrying a row per phase rather than a structure per fold. if (strcmp (this.CrossValidatedModel, 'GAM')) pt = zeros (1, this.KFold); it = zeros (1, this.KFold); boosted = true; for k = 1:this.KFold nt = this.Trained{k}.NumTrainedTrees; ## A spline fit counts no trees, so there is nothing to report and ## the property stays empty rather than claiming zero of them. if (isempty (nt)) boosted = false; break; endif pt(k) = nt.PredictorTrees; it(k) = nt.InteractionTrees; endfor if (boosted) this.NumTrainedPerFold = struct ('PredictorTrees', pt, ... 'InteractionTrees', it); endif endif ## No fold carries the transform: the parent applies it once to the ## assembled prediction. Cleared here rather than at each fit call so ## that a backing added later cannot reintroduce a double application ## by inheriting a class default of its own. ## ## Safe because the transform here is a caller's preference and not ## fitted content. for k = 1:this.KFold T = this.Trained{k}; T.ResponseTransform = 'none'; this.Trained{k} = T; endfor endfunction ## -*- texinfo -*- ## @deftypefn {RegressionPartitionedModel} {@var{yFit} =} kfoldPredict (@var{obj}) ## @deftypefnx {RegressionPartitionedModel} {[@var{yFit}, @var{ySD}, @var{yInt}] =} kfoldPredict (@var{obj}) ## @deftypefnx {RegressionPartitionedModel} {[@dots{}] =} kfoldPredict (@dots{}, @qcode{'Alpha'}, @var{alpha}) ## ## Predict the response of every observation from the fold that held it ## out. ## ## @code{@var{yFit} = kfoldPredict (@var{obj})} returns a column vector ## with one entry per observation, each predicted by the fold's model that ## did not see it during training. An observation no fold tests, which a ## holdout partition leaves outside its test set, comes back @code{NaN}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{RegressionPartitionedModel} class object. ## @end itemize ## ## @code{[@var{yFit}, @var{ySD}, @var{yInt}] = kfoldPredict (@var{obj})} ## also returns the standard deviation @var{ySD} of each predicted ## response and the two-column matrix @var{yInt} of prediction intervals, ## each answered for by the fold that held the observation out. A ## @qcode{RegressionGP} backing is the only one that fits the uncertainty ## its predictions carry, so any other raises here. An untested ## observation is @code{NaN} in all three. ## ## @code{[@dots{}] = kfoldPredict (@dots{}, @qcode{'Alpha'}, @var{alpha})} ## sets the significance level of the prediction intervals, which default ## to 95 per cent at an @var{alpha} of 0.05. ## ## @var{ySD} does not follow @qcode{ResponseTransform} and the other two ## outputs do, the same rule @code{RegressionGP.predict} applies: a ## predicted response and an interval endpoint are on the response scale ## and a standard deviation is not. ## ## @seealso{RegressionPartitionedModel, kfoldLoss} ## @end deftypefn function [yFit, ySD, yInt] = kfoldPredict (this, varargin) ## Only the GP fits the uncertainty around its predictions, so it is the ## only backing whose folds can be asked for one. MATLAB refuses the ## network and the SVM outright and refuses the GAM unless it was fitted ## with 'FitStandardDeviation', an option this package does not offer, so ## every backing but the GP is refused here under one message. is_gp = strcmp (this.CrossValidatedModel, 'GP'); if (nargout > 1 && ! is_gp) error (strcat ("RegressionPartitionedModel.kfoldPredict: a", ... " standard deviation and a prediction interval are", ... " only available for a cross-validated", ... " RegressionGP.")); endif ## Validated here rather than in the fold's predict, so the message ## names the method the user called. if (numel (varargin) > 0 && ! is_gp) error (strcat ("RegressionPartitionedModel.kfoldPredict:", ... " optional arguments are only accepted for a", ... " cross-validated RegressionGP.")); endif CIAlpha = 0.05; while (numel (varargin) > 0) if (numel (varargin) < 2) error (strcat ("RegressionPartitionedModel.kfoldPredict:", ... " optional arguments must be given in Name-Value", ... " pairs.")); endif switch (lower (varargin{1})) case 'alpha' CIAlpha = varargin{2}; if (! (isnumeric (CIAlpha) && isscalar (CIAlpha) && ... CIAlpha >= 0 && CIAlpha <= 1)) error (strcat ("RegressionPartitionedModel.kfoldPredict:", ... " 'Alpha' must be a scalar between 0 and 1.")); endif otherwise error (strcat ("RegressionPartitionedModel.kfoldPredict:", ... " invalid NAME in optional pairs of arguments.")); endswitch varargin(1:2) = []; endwhile yFit = nan (this.NumObservations, 1); if (nargout > 1) ySD = nan (this.NumObservations, 1); yInt = nan (this.NumObservations, 2); endif for k = 1:this.KFold testIdx = test (this.Partition, k); if (! any (testIdx)) continue; endif if (nargout > 2) [yFit(testIdx), ySD(testIdx), yInt(testIdx,:)] = ... predict (this.Trained{k}, this.X(testIdx, :), 'Alpha', CIAlpha); elseif (nargout > 1) [yFit(testIdx), ySD(testIdx)] = ... predict (this.Trained{k}, this.X(testIdx, :)); else yFit(testIdx) = predict (this.Trained{k}, this.X(testIdx, :)); endif endfor ## As on the classification side, the folds never carry the transform and ## it is applied once to the assembled predictions. MathWorks documents ## ResponseTransform as the function for transforming the predicted ## response values and documents assigning it by dot notation, so this is ## what the property is for; it used to be stored and never read. yFit = this.RTfun (yFit); if (nargout > 2) yInt = this.RTfun (yInt); endif endfunction ## -*- texinfo -*- ## @deftypefn {RegressionPartitionedModel} {@var{L} =} kfoldLoss (@var{obj}) ## @deftypefnx {RegressionPartitionedModel} {@var{L} =} kfoldLoss (@dots{}, @var{name}, @var{value}) ## ## Compute the cross-validated regression loss. ## ## @code{@var{L} = kfoldLoss (@var{obj})} returns the weighted mean ## squared error between the response and the out-of-fold predictions of ## @code{kfoldPredict}, over every observation some fold tests. ## ## @itemize ## @item ## @var{obj} must be a @qcode{RegressionPartitionedModel} class object. ## @end itemize ## ## @code{@var{L} = kfoldLoss (@dots{}, @var{name}, @var{value})} accepts ## the following @qcode{Name-Value} pairs. ## ## @multitable @columnfractions 0.24 0.76 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'LossFun'} @tab @qcode{'mse'}, the default, ## @qcode{'epsiloninsensitive'}, or a function handle called as ## @code{@var{lossfun} (@var{Y}, @var{yFit}, @var{W})} returning a scalar. ## The @math{epsilon}-insensitive loss belongs to a support vector model ## and is refused for any other, there being no tube to measure against. ## ## @item @qcode{'Mode'} @tab @qcode{'average'}, the default, which returns ## one number over the observations of every fold asked for, or ## @qcode{'individual'}, which returns one number per fold. ## ## @item @qcode{'Folds'} @tab A vector of fold indices to restrict the ## loss to. It defaults to every fold. ## @end multitable ## ## @seealso{RegressionPartitionedModel, kfoldPredict} ## @end deftypefn function L = kfoldLoss (this, varargin) if (mod (numel (varargin), 2) != 0) error (strcat ("RegressionPartitionedModel.kfoldLoss: Name-Value", ... " arguments must be in pairs.")); endif ## Defaults, then the optional pairs LossFun = 'mse'; Mode = 'average'; Folds = 1:this.KFold; while (numel (varargin) > 0) if (! (ischar (varargin{1}) && isrow (varargin{1}))) error (strcat ("RegressionPartitionedModel.kfoldLoss: parameter", ... " name must be a character vector.")); endif switch (tolower (varargin{1})) case 'lossfun' LossFun = varargin{2}; if (! (is_function_handle (LossFun) || (ischar (LossFun) && isrow (LossFun)))) error (strcat ("RegressionPartitionedModel.kfoldLoss:", ... " 'LossFun' must be a character vector or", ... " a function handle.")); endif if (ischar (LossFun) && ! any (strcmpi (LossFun, ... {'mse', 'epsiloninsensitive'}))) error (strcat ("RegressionPartitionedModel.kfoldLoss:", ... " unsupported 'LossFun' value.")); endif case 'mode' Mode = varargin{2}; if (! (ischar (Mode) && isrow (Mode) && any (strcmpi (Mode, {'average', 'individual'})))) error (strcat ("RegressionPartitionedModel.kfoldLoss:", ... " 'Mode' must be either 'average' or", ... " 'individual'.")); endif case 'folds' Folds = varargin{2}; if (! (isnumeric (Folds) && isvector (Folds) && all (Folds == fix (Folds)) && all (Folds >= 1) && all (Folds <= this.KFold))) error (strcat ("RegressionPartitionedModel.kfoldLoss:", ... " 'Folds' must be a vector of fold indices", ... " between 1 and KFold.")); endif otherwise error (strcat ("RegressionPartitionedModel.kfoldLoss: invalid", ... " parameter name in optional paired arguments.")); endswitch varargin(1:2) = []; endwhile ## The insensitive tube is a property of a support vector model, so any ## other cross-validated model has nothing to measure against. Answering ## anyway would report a number for a quantity the model does not have. if (ischar (LossFun) && strcmpi (LossFun, 'epsiloninsensitive') && ! strcmp (this.CrossValidatedModel, 'SVM')) error (strcat ("RegressionPartitionedModel.kfoldLoss: the", ... " 'epsiloninsensitive' loss applies to a", ... " RegressionSVM model only.")); endif yFit = kfoldPredict (this); if (strcmpi (Mode, 'individual')) L = nan (numel (Folds), 1); for i = 1:numel (Folds) idx = test (this.Partition, Folds(i)); L(i) = foldLoss_ (this, idx, yFit, LossFun, Folds(i)); endfor else idx = false (this.NumObservations, 1); for i = 1:numel (Folds) idx = idx | test (this.Partition, Folds(i)); endfor L = foldLoss_ (this, idx, yFit, LossFun, Folds(1)); endif endfunction ## -*- texinfo -*- ## @deftypefn {RegressionPartitionedModel} {@var{vals} =} kfoldfun (@var{obj}, @var{fun}) ## ## Apply a function to each fold of a cross-validated model. ## ## @code{@var{vals} = kfoldfun (@var{obj}, @var{fun})} calls @var{fun} once ## per fold and returns a @math{K*M} numeric matrix whose row @math{k} is ## what @var{fun} returned for fold @math{k}. ## ## @var{fun} is a function handle taking seven inputs and returning a ## numeric vector of the same length every time it is called: ## ## @example ## @var{testvals} = @var{fun} (@var{M}, @var{Xtrain}, @var{Ytrain}, @var{Wtrain}, @dots{} ## @var{Xtest}, @var{Ytest}, @var{Wtest}) ## @end example ## ## @var{M} is the model the fold was fitted with, taken from ## @code{@var{obj}.Trained@{k@}}; @var{Xtrain}, @var{Ytrain} and ## @var{Wtrain} are the predictors, response and weights of the ## observations that fold was trained on, and @var{Xtest}, @var{Ytest} and ## @var{Wtest} those of the observations it held out. ## ## @seealso{RegressionPartitionedModel, kfoldPredict, kfoldLoss} ## @end deftypefn function vals = kfoldfun (this, fun) if (nargin < 2) error ("RegressionPartitionedModel.kfoldfun: too few input arguments."); endif if (! is_function_handle (fun)) error ("RegressionPartitionedModel.kfoldfun: FUN must be a function handle."); endif vals = []; for k = 1:this.KFold trIdx = training (this.Partition, k); teIdx = test (this.Partition, k); tv = fun (this.Trained{k}, this.X(trIdx,:), this.Y(trIdx,:), ... this.W(trIdx), this.X(teIdx,:), this.Y(teIdx,:), ... this.W(teIdx)); ## The returned values become one row, so they have to be numeric and ## of one length: a fold answering with a different width could not be ## stacked with the others, and finding that out at the concatenation ## would name neither the fold nor the reason. if (! ((isnumeric (tv) || islogical (tv)) && isvector (tv))) error (strcat ("RegressionPartitionedModel.kfoldfun: FUN must", ... " return a numeric vector; fold %d returned a %s."), ... k, class (tv)); endif tv = tv(:)'; if (! isempty (vals) && numel (tv) != columns (vals)) error (strcat ("RegressionPartitionedModel.kfoldfun: FUN must", ... " return the same number of values for every fold;", ... " fold %d returned %d where the first returned", ... " %d."), k, numel (tv), columns (vals)); endif vals = [vals; tv]; endfor endfunction endmethods methods (Access = private) ## Loss over the observations selected by IDX, weighted by W normalized ## over that selection so a subset is an average rather than a sum. function L = foldLoss_ (this, idx, yFit, LossFun, fold) if (! any (idx)) L = NaN; return; endif y = this.Y(idx); f = yFit(idx); w = this.W(idx); w = w(:) / sum (w); if (is_function_handle (LossFun)) L = LossFun (y(:), f(:), w); if (! (isnumeric (L) && isscalar (L))) error (strcat ("RegressionPartitionedModel.kfoldLoss: 'LossFun'", ... " must return a numeric scalar.")); endif elseif (strcmpi (LossFun, 'epsiloninsensitive')) ## Every fold was fitted with the same Epsilon, so any trained model ## reports it; the one belonging to this fold is used for clarity. eps_ = this.Trained{fold}.Epsilon; L = sum (w .* max (0, abs (y(:) - f(:)) - eps_)); else L = sum (w .* (y(:) - f(:)) .^ 2); endif endfunction endmethods endclassdef ## crossval builds one compact model per fold, over the observations used. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = randn (40, 2); %! Y = X(:,1) - 2 * X(:,2); %! Mdl = fitrnet (X, Y, 'IterationLimit', 30); %! CVMdl = crossval (Mdl, 'KFold', 4); %! assert_equal (class (CVMdl), 'RegressionPartitionedModel'); %! assert_equal (CVMdl.KFold, 4); %! assert_equal (numel (CVMdl.Trained), 4); %! assert_equal (class (CVMdl.Trained{1}), 'CompactRegressionNeuralNetwork'); %! assert_equal (CVMdl.CrossValidatedModel, 'NeuralNetwork'); %! assert_equal (CVMdl.NumObservations, 40); %! assert_equal (CVMdl.ResponseName, 'Y'); %! assert_equal (class (CVMdl.Partition), 'cvpartition'); ## The same for a support vector model. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = randn (40, 2); %! Y = X(:,1) + X(:,2); %! CVMdl = crossval (fitrsvm (X, Y), 'KFold', 4); %! assert_equal (class (CVMdl.Trained{1}), 'CompactRegressionSVM'); %! assert_equal (CVMdl.CrossValidatedModel, 'SVM'); ## The same for a generalized additive model, which carries no parameter ## struct, so that property keeps its default. %!test %! load fisheriris %! CVMdl = crossval (fitrgam (meas(1:20,1:3), meas(1:20,4)), 'KFold', 4); %! assert_equal (class (CVMdl.Trained{1}), 'CompactRegressionGAM'); %! assert_equal (CVMdl.CrossValidatedModel, 'GAM'); %! assert_equal (CVMdl.NumObservations, 20); %! ## The learner's thirteen fields come through, with NLearn added and the %! ## three tags reissued for this class, so fourteen in all. %! assert_equal (isstruct (CVMdl.ModelParameters), true); %! assert_equal (numfields (CVMdl.ModelParameters), 14); %! assert_equal (isfield (CVMdl.ModelParameters, 'NumTreesPerPredictor'), true); %! assert_equal (CVMdl.ModelParameters.Method, 'PartitionedModel'); %!error ... %! RegressionPartitionedModel (1, cvpartition (10, 'KFold', 2)) ## Every observation is answered for by the fold that held it out. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = randn (30, 2); %! Y = X(:,1) * 2; %! CVMdl = crossval (fitrsvm (X, Y), 'KFold', 5); %! yFit = kfoldPredict (CVMdl); %! assert_equal (size (yFit), [30, 1]); %! assert_equal (any (isnan (yFit)), false); ## A holdout partition tests only its test set, and leaves the rest NaN. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = randn (40, 2); %! Y = X(:,1); %! CVMdl = crossval (fitrsvm (X, Y), 'Holdout', 0.25); %! yFit = kfoldPredict (CVMdl); %! assert_equal (CVMdl.KFold, 1); %! assert_equal (sum (! isnan (yFit)), sum (test (CVMdl.Partition, 1))); %! assert_equal (sum (isnan (yFit)), sum (training (CVMdl.Partition, 1))); ## Leaveout holds out one observation at a time. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = randn (12, 2); %! Y = X(:,1) + 1; %! CVMdl = crossval (fitrsvm (X, Y), 'Leaveout', 'on'); %! assert_equal (CVMdl.KFold, 12); %! assert_equal (any (isnan (kfoldPredict (CVMdl))), false); ## A cvpartition object is taken as given. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = randn (30, 2); %! Y = X(:,2); %! cvp = cvpartition (30, 'KFold', 3); %! CVMdl = crossval (fitrsvm (X, Y), 'CVPartition', cvp); %! assert_equal (CVMdl.KFold, 3); %! assert_equal (CVMdl.Partition.NumObservations, 30); ## kfoldLoss is the weighted mean squared error of the out-of-fold answers. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = randn (40, 2); %! Y = X(:,1) - X(:,2); %! CVMdl = crossval (fitrsvm (X, Y), 'KFold', 4); %! yFit = kfoldPredict (CVMdl); %! assert_equal (kfoldLoss (CVMdl), mean ((Y - yFit) .^ 2), 1e-12); %! assert_equal (kfoldLoss (CVMdl, 'LossFun', 'mse'), ... %! kfoldLoss (CVMdl), 1e-12); ## 'individual' returns one loss per fold, and each is that fold's own. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = randn (40, 2); %! Y = X(:,1) * 3; %! CVMdl = crossval (fitrsvm (X, Y), 'KFold', 4); %! yFit = kfoldPredict (CVMdl); %! L = kfoldLoss (CVMdl, 'Mode', 'individual'); %! assert_equal (size (L), [4, 1]); %! idx = test (CVMdl.Partition, 2); %! assert_equal (L(2), mean ((Y(idx) - yFit(idx)) .^ 2), 1e-12); ## 'Folds' restricts the loss to the folds named. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = randn (40, 2); %! Y = X(:,2) - 1; %! CVMdl = crossval (fitrsvm (X, Y), 'KFold', 4); %! yFit = kfoldPredict (CVMdl); %! idx = test (CVMdl.Partition, 1) | test (CVMdl.Partition, 3); %! assert_equal (kfoldLoss (CVMdl, 'Folds', [1, 3]), ... %! mean ((Y(idx) - yFit(idx)) .^ 2), 1e-12); %! assert_equal (numel (kfoldLoss (CVMdl, 'Folds', [2, 4], ... %! 'Mode', 'individual')), 2); ## The epsilon-insensitive loss uses the tube the folds were fitted with. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = randn (40, 2); %! Y = X(:,1) * 2; %! CVMdl = crossval (fitrsvm (X, Y, 'Epsilon', 0.4), 'KFold', 4); %! yFit = kfoldPredict (CVMdl); %! assert_equal (kfoldLoss (CVMdl, 'LossFun', 'epsiloninsensitive'), ... %! mean (max (0, abs (Y - yFit) - 0.4)), 1e-12); ## kfoldLoss takes a function handle of the response, the fit and the weights. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = randn (30, 2); %! Y = X(:,1); %! CVMdl = crossval (fitrsvm (X, Y), 'KFold', 3); %! yFit = kfoldPredict (CVMdl); %! f = @(y, yf, w) sum (w .* abs (y - yf)); %! assert_equal (kfoldLoss (CVMdl, 'LossFun', f), ... %! mean (abs (Y - yFit)), 1e-12); ## Rows dropped for missing values are outside the partition entirely. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = [randn(20, 2); NaN, 1]; %! Y = [randn(20, 1); 3]; %! Mdl = fitrsvm (X, Y); %! CVMdl = crossval (Mdl, 'KFold', 4); %! assert_equal (CVMdl.NumObservations, 21); %! assert_equal (rows (CVMdl.X), 21); %! assert_equal (numel (kfoldPredict (CVMdl)), 21); ## A GP backing answers for the uncertainty of its out-of-fold predictions. %!test %! load fisheriris %! CVMdl = crossval (fitrgp (meas(:,1:3), meas(:,4)), 'KFold', 5); %! [yFit, ySD, yInt] = kfoldPredict (CVMdl); %! assert_equal (size (yFit), [150, 1]); %! assert_equal (size (ySD), [150, 1]); %! assert_equal (size (yInt), [150, 2]); %! assert_equal (all (ySD > 0), true); %! assert_equal (yFit, kfoldPredict (CVMdl), 1e-12); ## The interval is the prediction plus and minus a normal quantile of the ## standard deviation, and 'Alpha' sets which quantile. %!test %! load fisheriris %! CVMdl = crossval (fitrgp (meas(:,1:3), meas(:,4)), 'KFold', 5); %! [yFit, ySD, yInt] = kfoldPredict (CVMdl); %! z = norminv (0.975); %! assert_equal (yInt, [yFit - z * ySD, yFit + z * ySD], 1e-12); %! [~, ~, yInt90] = kfoldPredict (CVMdl, 'Alpha', 0.10); %! z90 = norminv (0.95); %! assert_equal (yInt90, [yFit - z90 * ySD, yFit + z90 * ySD], 1e-12); %! assert_equal (all (diff (yInt90, 1, 2) < diff (yInt, 1, 2)), true); ## Each observation is answered for by the fold that held it out, in all three ## outputs, and one no fold tests is NaN in all three. %!test %! load fisheriris %! CVMdl = crossval (fitrgp (meas(:,1:3), meas(:,4)), 'KFold', 5); %! [yFit, ySD, yInt] = kfoldPredict (CVMdl); %! idx = test (CVMdl.Partition, 2); %! [p, s, i] = predict (CVMdl.Trained{2}, meas(idx,1:3)); %! assert_equal (p, yFit(idx), 1e-12); %! assert_equal (s, ySD(idx), 1e-12); %! assert_equal (i, yInt(idx,:), 1e-12); %!test %! load fisheriris %! CVMdl = crossval (fitrgp (meas(:,1:3), meas(:,4)), 'Holdout', 0.3); %! [yFit, ySD, yInt] = kfoldPredict (CVMdl); %! untested = isnan (yFit); %! assert_equal (any (untested), true); %! assert_equal (isnan (ySD), untested); %! assert_equal (isnan (yInt), [untested, untested]); ## ResponseTransform reaches the two outputs on the response scale and not the ## standard deviation, the rule RegressionGP.predict applies. %!test %! load fisheriris %! CVMdl = crossval (fitrgp (meas(:,1:3), meas(:,4)), 'KFold', 5); %! [yFit, ySD, yInt] = kfoldPredict (CVMdl); %! CVMdl.ResponseTransform = @(x) 2 * x; %! [yFit2, ySD2, yInt2] = kfoldPredict (CVMdl); %! assert_equal (yFit2, 2 * yFit, 1e-12); %! assert_equal (ySD2, ySD, 1e-12); %! assert_equal (yInt2, 2 * yInt, 1e-12); ## Measured against R2024a over the same folds, an explicit CustomPartition ## naming them in both engines so the assembled values are comparable at all. %!test %! load fisheriris %! cvp = cvpartition ('CustomPartition', repmat ((1:5)', 30, 1)); %! CVMdl = crossval (fitrgp (meas(:,1:3), meas(:,4)), 'CVPartition', cvp); %! [yFit, ySD, yInt] = kfoldPredict (CVMdl); %! assert_equal (yFit(1), 0.21819014717001, 1e-9); %! assert_equal (ySD(1), 0.17986522125163, 1e-9); %! assert_equal (yInt(1,:), [-0.13433920855451, 0.57071950289454], 1e-9); %! assert_equal (sum (abs (yFit)), 179.61497470708, 1e-6); %! assert_equal (sum (abs (ySD)), 27.747043040728, 1e-6); %! [~, ~, yInt90] = kfoldPredict (CVMdl, 'Alpha', 0.10); %! assert_equal (yInt90(1,:), [-0.077661814368161, 0.51404210870819], 1e-9); ## No other backing fits the uncertainty around its predictions, so none of ## them may be asked for it. %!error ... %! load fisheriris; ... %! CVMdl = crossval (fitrsvm (meas(:,1:3), meas(:,4)), 'KFold', 3); ... %! [yFit, ySD] = kfoldPredict (CVMdl); %!error ... %! load fisheriris; ... %! CVMdl = crossval (fitrgam (meas(:,1:3), meas(:,4)), 'KFold', 3); ... %! [yFit, ySD, yInt] = kfoldPredict (CVMdl); %!error ... %! load fisheriris; ... %! CVMdl = crossval (fitrsvm (meas(:,1:3), meas(:,4)), 'KFold', 3); ... %! kfoldPredict (CVMdl, 'Alpha', 0.1); %!error ... %! load fisheriris; ... %! CVMdl = crossval (fitrgp (meas(:,1:3), meas(:,4)), 'KFold', 3); ... %! kfoldPredict (CVMdl, 'Alpha'); %!error ... %! load fisheriris; ... %! CVMdl = crossval (fitrgp (meas(:,1:3), meas(:,4)), 'KFold', 3); ... %! kfoldPredict (CVMdl, 'Alpha', 2); %!error ... %! load fisheriris; ... %! CVMdl = crossval (fitrgp (meas(:,1:3), meas(:,4)), 'KFold', 3); ... %! kfoldPredict (CVMdl, 'Bogus', 1); ## Test input validation for the constructor %!error ... %! RegressionPartitionedModel () %!error ... %! RegressionPartitionedModel (fitrsvm (ones (5, 2), [1; 2; 3; 4; 5])) %!error ... %! RegressionPartitionedModel (5, cvpartition (5, 'KFold', 2)) %!error ... %! RegressionPartitionedModel (fitcnet (ones (4, 2), [1; 1; 2; 2]), ... %! cvpartition (4, 'KFold', 2)) %!error ... %! RegressionPartitionedModel (fitrsvm (ones (5, 2), [1; 2; 3; 4; 5]), 5) %!error ... %! RegressionPartitionedModel (fitrsvm (ones (5, 2), [1; 2; 3; 4; 5]), ... %! cvpartition (9, 'KFold', 3)) ## Test input validation for kfoldLoss %!shared CVR %! rand ('seed', 42); randn ('seed', 42); %! CVR = crossval (fitrsvm (randn (20, 2), randn (20, 1)), 'KFold', 4); %!error ... %! kfoldLoss (CVR, 'Mode') %!error ... %! kfoldLoss (CVR, 5, 1) %!error ... %! kfoldLoss (CVR, 'LossFun', 5) %!error ... %! kfoldLoss (CVR, 'LossFun', 'mae') %!error ... %! kfoldLoss (CVR, 'LossFun', @(y, yf, w) [1, 2]) %!error ... %! kfoldLoss (CVR, 'Mode', 'nope') %!error ... %! kfoldLoss (CVR, 'Folds', 0) %!error ... %! kfoldLoss (CVR, 'Folds', 9) %!error ... %! kfoldLoss (CVR, 'Nope', 1) ## The insensitive tube belongs to a support vector model only. %!error ... %! kfoldLoss (crossval (fitrnet (randn (20, 2), randn (20, 1), ... %! 'IterationLimit', 5), 'KFold', 4), ... %! 'LossFun', 'epsiloninsensitive') ## BinEdges is an empty cell, and a cell rather than an empty matrix, matching ## the classification counterpart and MATLAB. %!test %! load fisheriris %! CVMdl = crossval (fitrsvm (meas(:,1:3), meas(:,4)), 'KFold', 3); %! assert_equal (class (CVMdl.BinEdges), 'cell'); %! assert_equal (CVMdl.BinEdges, {}); ## No fold carries the transform, whichever model was cross validated. The ## parent applies it once to the assembled prediction, so a fold that kept one ## of its own would apply it twice. %!test %! load fisheriris %! CVMdl = crossval (fitrgam (meas(:,1:3), meas(:,4), ... %! 'ResponseTransform', 'exp'), 'KFold', 3); %! assert_equal (CVMdl.ResponseTransform, 'exp'); %! assert_equal (CVMdl.Trained{1}.ResponseTransform, 'none'); %!test %! load fisheriris %! CVMdl = crossval (fitrnet (meas(:,1:3), meas(:,4), ... %! 'ResponseTransform', 'exp'), 'KFold', 3); %! assert_equal (CVMdl.ResponseTransform, 'exp'); %! assert_equal (CVMdl.Trained{1}.ResponseTransform, 'none'); %!test %! load fisheriris %! CVMdl = crossval (fitrgp (meas(:,1:3), meas(:,4), ... %! 'ResponseTransform', 'exp'), 'KFold', 3); %! assert_equal (CVMdl.ResponseTransform, 'exp'); %! assert_equal (CVMdl.Trained{1}.ResponseTransform, 'none'); ## ResponseTransform is applied to the assembled predictions, not carried into ## the folds. MathWorks documents it as the function for transforming the ## predicted response values; it used to be stored here and never read. %!test %! load fisheriris %! CVMdl = crossval (fitrsvm (meas(:,1:3), meas(:,4)), 'KFold', 3); %! y0 = kfoldPredict (CVMdl); %! CVMdl.ResponseTransform = @(x) x + 100; %! y1 = kfoldPredict (CVMdl); %! assert_equal (CVMdl.Trained{1}.ResponseTransform, 'none'); %! assert_equal (y1, y0 + 100, 1e-12); ## And it reaches kfoldLoss, which is computed from those predictions. %!test %! load fisheriris %! CVMdl = crossval (fitrsvm (meas(:,1:3), meas(:,4)), 'KFold', 3); %! before = kfoldLoss (CVMdl); %! CVMdl.ResponseTransform = @(x) x + 100; %! assert (kfoldLoss (CVMdl) > before); ## 'none' is the identity, so the default transforms nothing. %!test %! load fisheriris %! CVMdl = crossval (fitrnet (meas(:,1:3), meas(:,4)), 'KFold', 3); %! y0 = kfoldPredict (CVMdl); %! CVMdl.ResponseTransform = 'none'; %! assert_equal (kfoldPredict (CVMdl), y0); ## foldLoss_ is a private helper and stays out of the method list. %!test %! assert_equal (any (strcmp (methods ("RegressionPartitionedModel"), ... %! "foldLoss_")), false); ## A fold is refitted with the argument set of the engine that fitted the ## parent, so a tree-fitted parent must not hand its folds Knots and Order. %!test %! load fisheriris %! Mdl = fitrgam (meas(:,2:4), meas(:,1), 'FitMethod', 'boostedtrees'); %! CVMdl = crossval (Mdl, 'KFold', 3); %! assert_equal (class (CVMdl), 'RegressionPartitionedModel'); %! assert_equal (numel (CVMdl.Trained), 3); %! assert_equal (CVMdl.Trained{1}.FitMethod, 'boostedtrees'); %! assert_equal (numel (kfoldPredict (CVMdl)), rows (meas)); ## A spline-fitted parent is unaffected: its folds take the spline parameters ## and are fitted by the same engine. They do not report the parameters ## themselves, a compact model keeping no record of its fitting. %!test %! load fisheriris %! Mdl = fitrgam (meas(1:60,1:3), meas(1:60,4), 'FitMethod', 'splines', ... %! 'Knots', 4); %! CVMdl = crossval (Mdl, 'KFold', 3); %! assert_equal (CVMdl.Trained{1}.FitMethod, 'splines'); %! assert_equal (isfield (CVMdl.Trained{1}.BaseModel, 'Intercept'), true); %!test %! ## kfoldfun hands over seven arguments, the fold's model first. %! load fisheriris %! CV = crossval (fitrgam (meas(:,2:4), meas(:,1)), "KFold", 3); %! seen = kfoldfun (CV, @(M, Xtr, Ytr, Wtr, Xte, Yte, Wte) ... %! [rows(Xtr), rows(Yte), columns(Xtr)]); %! assert_equal (size (seen), [3, 3]); %! assert_equal (seen(:,1) + seen(:,2), repmat (150, 3, 1)); %! assert_equal (seen(:,3), repmat (3, 3, 1)); %!test %! ## The use it exists for: a held-out mean squared error per fold. %! load fisheriris %! CV = crossval (fitrgam (meas(:,2:4), meas(:,1)), "KFold", 3); %! f = @(M, Xtr, Ytr, Wtr, Xte, Yte, Wte) mean ((predict (M, Xte) - Yte) .^ 2); %! mse = kfoldfun (CV, f); %! assert_equal (size (mse), [3, 1]); %! assert_equal (all (mse > 0), true); %!error ... %! kfoldfun (crossval (fitrgam (ones (8, 2), (1:8)'), "KFold", 2)) %!error ... %! kfoldfun (crossval (fitrgam (ones (8, 2), (1:8)'), "KFold", 2), "nope") %!test %! ## The property order is MATLAB's, measured on R2024a. %! load fisheriris %! CVMdl = crossval (fitrsvm (meas(:,2:4), meas(:,1)), "KFold", 3); %! assert_equal (sort (properties (CVMdl)), ... %! sort ({'ResponseTransform'; 'CrossValidatedModel'; ... %! 'PredictorNames'; 'CategoricalPredictors'; ... %! 'ResponseName'; 'NumObservations'; 'X'; 'Y'; 'W'; ... %! 'ModelParameters'; 'Trained'; 'KFold'; 'Partition'; ... %! 'BinEdges'; 'IsStandardDeviationFit'; ... %! 'NumTrainedPerFold'})); %!test %! ## IsStandardDeviationFit comes from the model for a GAM backing and is %! ## empty for every other, one class serving all of them. %! load fisheriris %! CVg = crossval (fitrgam (meas(:,2:4), meas(:,1)), "KFold", 3); %! assert_equal (islogical (CVg.IsStandardDeviationFit), true); %! assert_equal (CVg.IsStandardDeviationFit, false); %! CVs = crossval (fitrsvm (meas(:,2:4), meas(:,1)), "KFold", 3); %! assert_equal (isempty (CVs.IsStandardDeviationFit), true); %!test %! ## NumTrainedPerFold reports what each fold actually fitted. On this %! ## fixture every fold uses its whole budget, which is what R2024a reports %! ## for it as well. %! load fisheriris %! CVMdl = crossval (fitrgam (meas(:,2:4), meas(:,1)), "KFold", 3); %! n = CVMdl.NumTrainedPerFold; %! assert_equal (sort (fieldnames (n)), {"InteractionTrees"; "PredictorTrees"}); %! assert_equal (n.PredictorTrees, [300, 300, 300]); %! assert_equal (n.InteractionTrees, [0, 0, 0]); %!test %! ## Empty for a backing that fits no trees. %! load fisheriris %! CVMdl = crossval (fitrsvm (meas(:,2:4), meas(:,1)), "KFold", 3); %! assert_equal (isempty (CVMdl.NumTrainedPerFold), true); ## A polynomial-kernel SVM refits its folds through the recorded order, which ## the parent carries only under that kernel. %!test %! load fisheriris %! Mdl = fitrsvm (meas(:,2:4), meas(:,1), 'KernelFunction', 'polynomial', ... %! 'PolynomialOrder', 2); %! CVMdl = crossval (Mdl, 'KFold', 3); %! assert_equal (CVMdl.ModelParameters.KernelPolynomialOrder, 2); %! assert_equal (numel (CVMdl.Trained), 3); ## ModelParameters carries the learner's parameters under this class's own ## tags. %!test %! load fisheriris %! CVMdl = crossval (fitrsvm (meas(:,2:4), meas(:,1)), 'KFold', 3); %! MP = CVMdl.ModelParameters; %! assert_equal (MP.Method, 'PartitionedModel'); %! assert_equal (MP.Type, 'regression'); %! assert_equal (MP.Version, 1); %! assert_equal (MP.NLearn, 3); %! assert_equal (MP.SVMtype, 'eps_svr'); %!test %! load fisheriris %! MP = crossval (fitrgp (meas(:,2:4), meas(:,1)), 'KFold', 3).ModelParameters; %! assert_equal (MP.FitMethod, 'Exact'); %! assert_equal (MP.Method, 'PartitionedModel'); ## Every documented response transform reaches the response that is reported. %!test %! load fisheriris %! Mdl = crossval (fitrsvm (meas(:,2:4), meas(:,1)), 'KFold', 3); %! Mdl.ResponseTransform = 'none'; %! raw = kfoldPredict (Mdl); %! T = {'identity', @(x) x; 'exp', @(x) exp (x); 'log', @(x) log (x)}; %! for i = 1:rows (T) %! Mdl.ResponseTransform = T{i,1}; %! yhat = kfoldPredict (Mdl); %! assert_equal (yhat, T{i,2}(raw), 1e-12); %! endfor ## A function handle is taken as given and applied to the response. %!test %! load fisheriris %! Mdl = crossval (fitrsvm (meas(:,2:4), meas(:,1)), 'KFold', 3); %! Mdl.ResponseTransform = 'none'; %! raw = kfoldPredict (Mdl); %! Mdl.ResponseTransform = @(x) x .^ 2; %! yhat = kfoldPredict (Mdl); %! assert_equal (yhat, raw .^ 2, 1e-12); statistics-release-1.9.2/inst/Supervised_Learning/RegressionSVM.m000066400000000000000000002204021524624707500251600ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} RegressionSVM (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{obj} =} RegressionSVM (@dots{}, @var{name}, @var{value}) ## ## Create a @qcode{RegressionSVM} object containing a support vector machine ## regression model. ## ## @code{@var{obj} = RegressionSVM (@var{X}, @var{Y})} returns a support vector ## regression model, @var{obj}, with @var{X} being the predictor data and ## @var{Y} the continuous response of the observations in @var{X}. ## ## @itemize ## @item ## @var{X} must be an @math{NxP} numeric matrix of predictor data, where rows ## correspond to observations and columns to features. ## @item ## @var{Y} must be an @math{Nx1} numeric vector holding the response of the ## corresponding predictor data in @var{X}. @var{Y} must have the same number ## of rows as @var{X}. ## @end itemize ## ## The model is fitted by @math{epsilon}-insensitive regression: errors smaller ## than @qcode{Epsilon} cost nothing, so only the observations outside that ## tube become support vectors. @qcode{Epsilon} defaults to ## @code{iqr (@var{Y}) / 13.49}, a robust estimate of a tenth of the response's ## standard deviation, which is what MATLAB uses. ## ## @code{@var{obj} = RegressionSVM (@dots{}, @var{name}, @var{value})} returns ## a model with additional options specified by @qcode{Name-Value} pair ## arguments listed below. ## ## @multitable @columnfractions 0.32 0.68 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Standardize'} @tab A logical scalar specifying whether the ## predictor data should be centred and scaled before training. The same ## transformation is applied by @code{predict}. The default is @qcode{false}. ## ## @item @qcode{'PredictorNames'} @tab A cell array of character vectors ## naming the predictors, in the order they appear in @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector naming the response. ## The default is @qcode{'Y'}. ## ## @item @qcode{'ResponseTransform'} @tab A character vector naming one of the ## supported transformations, or a function handle, applied to the predicted ## response by @code{predict} and @code{resubPredict}. The default is ## @qcode{'none'}. ## ## @item @qcode{'Epsilon'} @tab A non-negative scalar, the half-width of the ## insensitive tube. The default is @code{iqr (@var{Y}) / 13.49}, or ## @math{0.1} where that is zero. ## ## @item @qcode{'BoxConstraint'} @tab A positive scalar bounding the dual ## coefficients, the cost of an error outside the tube. The default is 1. ## ## @item @qcode{'KernelFunction'} @tab A character vector naming the kernel, ## one of @qcode{'linear'}, the default, @qcode{'rbf'}, @qcode{'gaussian'}, ## @qcode{'polynomial'} or @qcode{'sigmoid'}. ## ## @item @qcode{'PolynomialOrder'} @tab A positive integer, the order of the ## polynomial kernel. The default is 3. It is ignored by every other kernel. ## ## @item @qcode{'KernelScale'} @tab A positive scalar dividing the predictors ## before the kernel is applied. The default is 1. ## ## @item @qcode{'KernelOffset'} @tab A non-negative scalar added to the kernel ## value. The default is 0. ## ## @item @qcode{'SVMtype'} @tab A character vector selecting the formulation, ## either @qcode{'eps_svr'}, the default, or @qcode{'nu_svr'}. MATLAB fits ## only the @math{epsilon} form; @qcode{'nu_svr'} is an Octave extension, in ## which @qcode{Nu} bounds the fraction of support vectors and @qcode{Epsilon} ## is determined by the fit rather than given. ## ## @item @qcode{'Nu'} @tab A scalar in @math{(0, 1]} used by ## @qcode{'nu_svr'}. The default is 0.5. ## ## @item @qcode{'CacheSize'} @tab A positive scalar, the kernel cache in ## megabytes. The default is 1000. ## ## @item @qcode{'Tolerance'} @tab A non-negative scalar, the tolerance of the ## termination criterion. The default is @math{1e-6}. ## ## @item @qcode{'Shrinking'} @tab Either 0 or 1, whether to use the shrinking ## heuristic. The default is 1. ## @end multitable ## ## The supported values for @qcode{'ResponseTransform'} are: ## ## @multitable @columnfractions 0.3 0.7 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'exp'} @tab @math{exp (x)} ## @item @qcode{'log'} @tab @math{log (x)} ## @end multitable ## ## @seealso{fitrsvm, ClassificationSVM, RegressionNeuralNetwork} ## @end deftypefn classdef RegressionSVM properties (GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} X ## ## Predictor data ## ## An @math{NxP} numeric matrix, as it was supplied to the constructor. ## This property is read-only. ## ## @end deftp X = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} Y ## ## Response data ## ## An @math{Nx1} numeric vector, as it was supplied to the constructor. ## This property is read-only. ## ## @end deftp Y = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} NumObservations ## ## Number of observations used to train the model ## ## A positive integer scalar, counting only the rows that survived the ## removal of missing values. This property is read-only. ## ## @end deftp NumObservations = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} RowsUsed ## ## Rows used for fitting ## ## A logical column vector with the same length as the observations in the ## original predictor data @var{X}, true for each row that was used for ## fitting the RegressionSVM model. It is empty, @qcode{[]}, ## when every observation was used, so a non-empty value means that rows ## holding missing values were dropped. This property is read-only. ## ## @end deftp RowsUsed = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} NumPredictors ## ## Number of predictors ## ## A positive integer scalar. This property is read-only. ## ## @end deftp NumPredictors = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} PredictorNames ## ## Names of the predictors ## ## A cell array of character vectors. This property is read-only. ## ## @end deftp PredictorNames = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} ResponseName ## ## Name of the response variable ## ## A character vector. This property is read-only. ## ## @end deftp ResponseName = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} Epsilon ## ## Half-width of the insensitive tube ## ## A non-negative scalar. An error smaller than @code{Epsilon} costs ## nothing, so only observations outside the tube become support vectors. ## This property is read-only. ## ## @end deftp Epsilon = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} Sigma ## ## Standard deviation of the predictors ## ## A row vector with one entry per predictor, used for standardization. ## Empty when the predictor data were not standardized. This property is ## read-only. ## ## @end deftp Sigma = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} Mu ## ## Mean of the predictors ## ## A row vector with one entry per predictor, used for standardization. ## Empty when the predictor data were not standardized. This property is ## read-only. ## ## @end deftp Mu = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} ModelParameters ## ## Parameters the model was fitted with ## ## A structure holding the SVM formulation, the kernel and its parameters, ## the box constraint, @code{Epsilon} and the solver settings. The engine ## is LIBSVM and the record is LIBSVM's, so @qcode{SVMtype} names its ## formulation and @qcode{Tolerance} and @qcode{Shrinking} are its own ## controls; the parameters MathWorks reports for its SMO and ISDA solvers ## are absent, this class running neither. ## ## @qcode{KernelPolynomialOrder} belongs to the polynomial kernel alone ## and is empty under every other, as it is in MATLAB. @qcode{Nu} is ## reported here where MATLAB leaves it empty on a regression model, this ## class offering @qcode{'nu_svr'} through @qcode{SVMtype} and the value ## being a real one. This property is read-only. ## ## @end deftp ModelParameters = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} Alpha ## ## Dual coefficients of the support vectors ## ## A numeric column vector with one entry per support vector, holding the ## difference of the two multipliers each observation carries. Unlike a ## classifier's, these are signed: there are no labels to take the sign ## into, so an observation above the tube and one below it are told apart ## by the sign of its coefficient. This property is read-only. ## ## @end deftp Alpha = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} Beta ## ## Primal coefficients, one per predictor ## ## A numeric column vector, equal to ## @code{obj.SupportVectors' * obj.Alpha}. It exists only for a linear ## kernel; for any other kernel there is no primal representation and this ## is empty. This property is read-only. ## ## @end deftp Beta = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} Bias ## ## Intercept of the fitted function ## ## A numeric scalar. With a linear kernel the prediction is ## @code{X * obj.Beta + obj.Bias}. This property is read-only. ## ## @end deftp Bias = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} IsSupportVector ## ## Which training observations are support vectors ## ## A logical column vector with one entry per training observation. This ## property is read-only. ## ## @end deftp IsSupportVector = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} SupportVectors ## ## The support vectors themselves ## ## A numeric matrix with one row per support vector, on the scale the ## model was trained on, standardized where @code{Mu} is non-empty. ## This property is read-only. ## ## @end deftp SupportVectors = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} KernelParameters ## ## Parameters of the kernel function ## ## A structure with fields @qcode{Function} and @qcode{Scale}, and ## @qcode{Order} for a polynomial kernel. @qcode{Function} names the ## kernel as MATLAB names it, so a radial basis kernel reports ## @qcode{'gaussian'} whichever spelling was given; the kernel the fit was ## handed is unchanged in @qcode{ModelParameters}. This property is ## read-only. ## ## @end deftp KernelParameters = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} BoxConstraints ## ## Box constraints ## ## A numeric column vector with one entry per observation, holding the box ## constraint the fit applied to it. A regression has no classes to ## reweight, so every entry is @qcode{BoxConstraint}. This property is ## read-only. ## ## @end deftp BoxConstraints = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} CategoricalPredictors ## ## Indices of the categorical predictors ## ## A numeric vector of column indices, and empty when none is. This ## property is read-only. ## ## @end deftp CategoricalPredictors = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} ExpandedPredictorNames ## ## Names of the predictors as the model expanded them ## ## A cell array of character vectors. This property is read-only. ## ## @end deftp ExpandedPredictorNames = {}; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} W ## ## Observation weights ## ## A numeric column vector with one entry per training observation, ## normalized to sum to one, as MATLAB reports it. This property is ## read-only. ## ## @end deftp W = []; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} BinEdges ## ## Bin edges of the predictors ## ## A cell array with one entry per predictor, holding that predictor's bin ## edges where the learner discretized it before fitting. It is empty here ## and stays empty: this learner fits the predictors as they are, and ## MATLAB's reports an empty cell for it as well. ## ## This property is read-only. ## ## @end deftp BinEdges = {}; ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} HyperparameterOptimizationResults ## ## Results of the hyperparameter optimization ## ## @strong{Always empty.} It is declared for MATLAB compatibility, where ## it holds what an automatic search over the hyperparameters found. This ## class fits the parameters it is given and runs no such search, so there ## is nothing to report. This property is read-only. ## ## @end deftp HyperparameterOptimizationResults = []; endproperties ## The LIBSVM structure the engine works in. It is ours alone, with no ## MATLAB counterpart, so it is kept out of the property listing while ## staying readable for anyone who needs the raw model. properties (Hidden, GetAccess = public, SetAccess = protected) ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} Model ## ## The trained LIBSVM model ## ## A structure as returned by @code{svmtrain} and consumed by ## @code{svmpredict}. This property is read-only. ## ## It is the engine's own structure and has no MATLAB counterpart, ## so it is kept out of @code{properties} and out of the online ## documentation. Reading it works exactly as it always did. ## ## @end deftp Model = []; endproperties ## Properties a user may set after the model is built. Each one is ## validated by its set method below. properties (GetAccess = public, SetAccess = public) ## -*- texinfo -*- ## @deftp {RegressionSVM} {property} ResponseTransform ## ## Transformation applied to the predicted response ## ## A function handle, applied by @code{predict} and @code{resubPredict} to ## the model's output. It defaults to the identity and may be set after ## construction, either to a handle or to the name of a supported ## transformation. ## ## @end deftp ResponseTransform = 'none'; endproperties ## Readable by the counterpart class, which copies it, and kept out of ## the documented surface. properties (GetAccess = public, SetAccess = protected, Hidden) RTfun = @(y) y; endproperties ## Set methods for the properties a user may assign. methods (Hidden) function this = set.ResponseTransform (this, val) name = 'RegressionSVM'; [this.RTfun, this.ResponseTransform] = parseResponseTransform (val, name); endfunction ## Custom display function display (this) in_name = inputname (1); if (! isempty (in_name)) fprintf ('%s =\n', in_name); endif disp (this); endfunction ## Custom display function disp (this) fprintf ("\n RegressionSVM\n\n"); ## Print selected properties fprintf ("%+25s: '%s'\n", 'ResponseName', this.ResponseName); fprintf ("%+25s: %d\n", 'NumObservations', this.NumObservations); fprintf ("%+25s: %d\n", 'NumPredictors', this.NumPredictors); fprintf ("%+25s: '%s'\n", 'ResponseTransform', this.ResponseTransform); fprintf ("%+25s: %g\n", 'Epsilon', this.Epsilon); fprintf ("%+25s: [%dx1 double]\n", 'Alpha', numel (this.Alpha)); if (! isempty (this.Beta)) fprintf ("%+25s: [%dx1 double]\n", 'Beta', numel (this.Beta)); endif fprintf ("%+25s: %f\n", 'Bias', this.Bias); fprintf ("%+25s: '%s'\n", 'KernelFunction', ... this.ModelParameters.KernelFunction); if (! isempty (this.Mu)) fprintf ("%+25s: [1x%d double]\n", 'Mu', numel (this.Mu)); fprintf ("%+25s: [1x%d double]\n", 'Sigma', numel (this.Sigma)); endif endfunction endmethods methods (Access = public) ## -*- texinfo -*- ## @deftypefn {RegressionSVM} {@var{obj} =} RegressionSVM (@var{X}, @var{Y}) ## @deftypefnx {RegressionSVM} {@var{obj} =} RegressionSVM (@dots{}, @var{name}, @var{value}) ## ## Create a @qcode{RegressionSVM} object containing a support vector ## machine regression model. ## ## See the class documentation for the accepted @qcode{Name-Value} pairs. ## ## @seealso{fitrsvm, RegressionSVM} ## @end deftypefn function this = RegressionSVM (X, Y, varargin) ## Check for sufficient number of input arguments if (nargin < 2) error ("RegressionSVM: too few input arguments."); endif ## Check X and Y have the same number of observations if (rows (X) != rows (Y)) error ("RegressionSVM: number of rows in X and Y must be equal."); endif ## The response is continuous, so it must be numeric if (! (isnumeric (Y) && isreal (Y))) error ("RegressionSVM: Y must be a real numeric vector."); endif if (! (isvector (Y) || isempty (Y))) error ("RegressionSVM: Y must be a vector."); endif ## Assign original X and Y data to the RegressionSVM object this.X = X; this.Y = Y; ## Set default values before parsing optional parameters SVMtype = 'eps_svr'; KernelFunction = 'linear'; KernelScale = 1; KernelOffset = 0; PolynomialOrder = 3; BoxConstraint = 1; Epsilon = []; Nu = 0.5; CacheSize = 1000; Tolerance = 1e-6; Shrinking = 1; Standardize = false; ResponseName = []; PredictorNames = []; ## Parse extra parameters while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'standardize' Standardize = varargin{2}; if (! (Standardize == true || Standardize == false)) error (strcat ("RegressionSVM: 'Standardize' must", ... " be either true or false.")); endif case 'predictornames' PredictorNames = varargin{2}; if (! iscellstr (PredictorNames)) error (strcat ("RegressionSVM: 'PredictorNames' must", ... " be supplied as a cellstring array.")); elseif (columns (PredictorNames) != columns (X)) error (strcat ("RegressionSVM: 'PredictorNames' must", ... " have the same number of columns as X.")); endif case 'responsename' ResponseName = varargin{2}; if (! ischar (ResponseName)) error (strcat ("RegressionSVM: 'ResponseName' must", ... " be a character vector.")); endif case 'responsetransform' name = 'RegressionSVM'; [this.RTfun, this.ResponseTransform] = ... parseResponseTransform (varargin{2}, name); case 'svmtype' SVMtype = varargin{2}; if (! (ischar (SVMtype) && isrow (SVMtype))) error ("RegressionSVM: 'SVMtype' must be a character vector."); endif SVMtype = tolower (SVMtype); if (! any (strcmp (SVMtype, {'eps_svr', 'nu_svr'}))) error ("RegressionSVM: unsupported 'SVMtype'."); endif case 'epsilon' Epsilon = varargin{2}; if (! (isnumeric (Epsilon) && isscalar (Epsilon) && Epsilon >= 0)) error (strcat ("RegressionSVM: 'Epsilon' must be a", ... " non-negative scalar.")); endif case 'kernelfunction' KernelFunction = varargin{2}; if (! ischar (KernelFunction)) error (strcat ("RegressionSVM: 'KernelFunction' must", ... " be a character vector.")); endif KernelFunction = tolower (KernelFunction); if (! any (strcmpi (KernelFunction, ... {'linear', 'rbf', 'gaussian', 'polynomial', 'sigmoid'}))) error ("RegressionSVM: unsupported Kernel function."); endif case 'polynomialorder' PolynomialOrder = varargin{2}; if (! (isnumeric (PolynomialOrder) && isscalar (PolynomialOrder) && PolynomialOrder > 0 && mod (PolynomialOrder, 1) == 0)) error (strcat ("RegressionSVM: 'PolynomialOrder' must", ... " be a positive integer.")); endif case 'kernelscale' KernelScale = varargin{2}; if (! (isscalar (KernelScale) && KernelScale > 0)) error (strcat ("RegressionSVM: 'KernelScale' must", ... " be a positive scalar.")); endif case 'kerneloffset' KernelOffset = varargin{2}; if (! (isnumeric (KernelOffset) && isscalar (KernelOffset) && KernelOffset >= 0)) error (strcat ("RegressionSVM: 'KernelOffset' must", ... " be a non-negative scalar.")); endif case 'boxconstraint' BoxConstraint = varargin{2}; if (! (isscalar (BoxConstraint) && BoxConstraint > 0)) error (strcat ("RegressionSVM: 'BoxConstraint' must", ... " be a positive scalar.")); endif case 'nu' Nu = varargin{2}; if (! (isscalar (Nu) && Nu > 0 && Nu <= 1)) error (strcat ("RegressionSVM: 'Nu' must be a positive", ... " scalar in the range 0 < Nu <= 1.")); endif case 'cachesize' CacheSize = varargin{2}; if (! (isscalar (CacheSize) && CacheSize > 0)) error ("RegressionSVM: 'CacheSize' must be a positive scalar."); endif case 'tolerance' Tolerance = varargin{2}; if (! (isscalar (Tolerance) && Tolerance >= 0)) error ("RegressionSVM: 'Tolerance' must be a positive scalar."); endif case 'shrinking' Shrinking = varargin{2}; if (! (ismember (Shrinking, [0, 1]) && isscalar (Shrinking))) error ("RegressionSVM: 'Shrinking' must be either 0 or 1."); endif otherwise error (strcat ("RegressionSVM: invalid parameter name", ... " in optional pair arguments.")); endswitch varargin(1:2) = []; endwhile ## Get number of variables in training data ndims_X = columns (X); this.NumPredictors = ndims_X; ## Generate default predictors and response variable names if (isempty (PredictorNames)) for i = 1:ndims_X PredictorNames {i} = strcat ("x", num2str (i)); endfor endif if (isempty (ResponseName)) ResponseName = 'Y'; endif this.PredictorNames = PredictorNames; this.ExpandedPredictorNames = PredictorNames; this.ResponseName = ResponseName; this.CategoricalPredictors = []; ## An observation is dropped only when its response is missing. A row ## whose predictors hold missing values is kept and reported as used, ## while the fit below draws on the complete observations alone. RowsUsed = ! isnan (Y(:)); Yret = Y(RowsUsed); Xret = X(RowsUsed, :); this.X = Xret; this.Y = Yret; cobs = ! any (isnan (Xret), 2); Y = Yret(cobs); X = Xret(cobs, :); ## Check X and Y contain valid data if (! (isnumeric (X) && isfinite (X))) error ("RegressionSVM: invalid values in X."); endif if (isempty (Y)) error ("RegressionSVM: Y cannot be empty."); endif if (! all (isfinite (Y))) error ("RegressionSVM: invalid values in Y."); endif this.NumObservations = rows (this.X); ## RowsUsed is left empty when every observation was used, as in MATLAB if (all (RowsUsed)) this.RowsUsed = []; else this.RowsUsed = RowsUsed; endif this.W = ones (this.NumObservations, 1) / this.NumObservations; ## Handle the Standardize option. The model must be fitted on the ## scale it predicts on: predict and resubPredict standardize their ## input from Mu and Sigma, so the training data is standardized here ## as well. if (Standardize) this.Sigma = std (X, [], 1); this.Sigma(this.Sigma == 0) = 1; # predictor is constant this.Mu = mean (X, 1); X = (X - this.Mu) ./ this.Sigma; else this.Sigma = []; this.Mu = []; endif ## Epsilon defaults to a robust tenth of the response's spread, which ## is what MATLAB uses. A constant response gives a zero interquartile ## range, and a zero-width tube would make every observation a support ## vector, so it falls back to LIBSVM's own default. if (isempty (Epsilon)) Epsilon = iqr (Y) / 13.49; if (Epsilon == 0) Epsilon = 0.1; endif endif this.Epsilon = Epsilon; ## Set svmtrain parameters for SVMtype and KernelFunction switch (SVMtype) case 'eps_svr' s = 3; case 'nu_svr' s = 4; endswitch switch (KernelFunction) case 'linear' t = 0; case 'polynomial' t = 1; case {'rbf', 'gaussian'} t = 2; case 'sigmoid' t = 3; endswitch ## Set svmtrain parameters for gamma g = KernelScale / ndims_X; ## Build options string for svmtrain function str_options = strcat ("-s %d -t %d -g %.16g -d %d -r %.16g", ... " -c %.16g -n %.16g -p %.16g -m %.16g", ... " -e %e -h %d -q"); svm_options = sprintf (str_options, s, t, g, PolynomialOrder, ... KernelOffset, BoxConstraint, Nu, Epsilon, ... CacheSize, Tolerance, Shrinking); ## Train the SVM model using svmtrain from libsvm Model = svmtrain (Y, X, svm_options); this.Model = Model; ## Populate the model properties. For regression LIBSVM's sv_coef is ## already the difference of the two multipliers, so it is signed and ## kept as it stands: unlike a classifier there are no labels to carry ## the sign into. this.Alpha = Model.sv_coef; ## LIBSVM evaluates sum_i coef_i * K(sv_i, x) - rho, so the intercept ## MATLAB reports is the negated rho. Measured against svmpredict. this.Bias = -Model.rho; ## BETA holds the primal coefficients, one per predictor, and exists ## only for a linear kernel; for any other there is no primal ## representation and MATLAB leaves it empty. if (t == 0) this.Beta = Model.SVs' * this.Alpha; else this.Beta = []; endif this.IsSupportVector = false (this.NumObservations, 1); this.IsSupportVector(Model.sv_indices) = true; this.SupportVectors = Model.SVs; ## The kernel and the per-observation box constraints, in the shapes ## MATLAB reports them. A regression has no classes to reweight, so the ## scalar applies to every observation. this.KernelParameters = svmKernelParams (KernelFunction, KernelScale, ... PolynomialOrder); this.BoxConstraints = BoxConstraint * ones (this.NumObservations, 1); ## Populate ModelParameters structure. The polynomial order belongs to ## the polynomial kernel alone and is reported under no other, as ## MATLAB reports it. if (strcmpi (KernelFunction, 'polynomial')) KPOrder = PolynomialOrder; else KPOrder = []; endif this.ModelParameters = struct ('SVMtype', SVMtype, 'BoxConstraint', ... BoxConstraint, 'CacheSize', CacheSize, ... 'KernelScale', KernelScale, ... 'KernelOffset', KernelOffset, ... 'KernelFunction', KernelFunction, ... 'KernelPolynomialOrder', KPOrder, ... 'Epsilon', Epsilon, 'Nu', Nu, ... 'Tolerance', Tolerance, ... 'Shrinking', Shrinking, ... 'StandardizeData', ... logical (Standardize), ... 'Version', 1, 'Method', 'SVM', ... 'Type', 'regression'); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionSVM} {@var{obj} =} discardSupportVectors (@var{obj}) ## ## Discard the support vectors of a linear SVM model. ## ## @code{@var{obj} = discardSupportVectors (@var{obj})} empties ## @code{Alpha} and @code{SupportVectors}, leaving @code{Beta} and ## @code{Bias} to decide every prediction. A linear kernel needs ## nothing else, so the returned model predicts what it predicted ## before while carrying one vector in place of many. ## ## The kernel must be linear. Under any other the support vectors are ## part of the decision function and cannot be dropped. Discarding twice ## is not an error and changes nothing. ## ## @seealso{fitrsvm, RegressionSVM, CompactRegressionSVM} ## @end deftypefn function this = discardSupportVectors (this) if (nargin != 1) print_usage (); endif if (! strcmpi (this.ModelParameters.KernelFunction, 'linear')) error (strcat ("RegressionSVM.discardSupportVectors: you", ... " cannot discard support vectors for a non-linear", ... " kernel.")); endif ## The engine keeps its own copy of the support vectors, so emptying ## the properties alone would free nothing. Collapsing the model onto ## the single vector that decides it leaves every scoring path as it ## was, svmpredict going on being the engine over one vector. this.Model = discardSVs (this.Model); this.Alpha = []; this.SupportVectors = []; endfunction ## -*- texinfo -*- ## @deftypefn {RegressionSVM} {@var{yFit} =} predict (@var{obj}, @var{XC}) ## ## Predict the response for new data with a support vector regression ## model. ## ## @code{@var{yFit} = predict (@var{obj}, @var{XC})} returns a column ## vector holding the predicted response for each row of @var{XC}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{RegressionSVM} class object. ## @item ## @var{XC} must be a numeric matrix with the same number of predictors as ## the data the model was trained on. ## @end itemize ## ## The transformation named by @code{ResponseTransform} is applied to the ## model's output before it is returned. ## ## @seealso{RegressionSVM, fitrsvm} ## @end deftypefn function yFit = predict (this, XC) ## Check for sufficient input arguments if (nargin < 2) error ("RegressionSVM.predict: too few input arguments."); endif ## Check for valid XC if (isempty (XC)) error ("RegressionSVM.predict: XC is empty."); elseif (this.NumPredictors != columns (XC)) error (strcat ("RegressionSVM.predict: XC must have the same", ... " number of predictors as the trained model.")); endif ## Standardize (if necessary) if (! isempty (this.Mu)) XC = (XC - this.Mu) ./ this.Sigma; endif ## LIBSVM returns the fitted response as its first output for a ## regression model, there being no label to decide. yFit = svmpredict (zeros (rows (XC), 1), XC, this.Model, '-q'); ## Apply ResponseTransform yFit = this.RTfun (yFit); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionSVM} {@var{yFit} =} resubPredict (@var{obj}) ## ## Predict the response of the training data with a support vector ## regression model. ## ## @code{@var{yFit} = resubPredict (@var{obj})} returns a column vector ## holding the predicted response for every observation the model was ## trained on. ## ## @itemize ## @item ## @var{obj} must be a @qcode{RegressionSVM} class object. ## @end itemize ## ## @seealso{RegressionSVM, fitrsvm} ## @end deftypefn function yFit = resubPredict (this) used = true (rows (this.X), 1); yFit = predict (this, this.X(used, :)); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionSVM} {@var{L} =} loss (@var{obj}, @var{X}, @var{Y}) ## @deftypefnx {RegressionSVM} {@var{L} =} loss (@dots{}, @var{name}, @var{value}) ## ## Compute the regression loss of a support vector machine model. ## ## @code{@var{L} = loss (@var{obj}, @var{X}, @var{Y})} returns the ## weighted mean squared error between the response @var{Y} and the ## response the model predicts for @var{X}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{RegressionSVM} class object. ## @item ## @var{X} must be a numeric matrix with the same number of predictors as ## the data the model was trained on. ## @item ## @var{Y} must be a numeric vector with as many rows as @var{X}. ## @end itemize ## ## @code{@var{L} = loss (@dots{}, @var{name}, @var{value})} accepts the ## following @qcode{Name-Value} pairs. ## ## @multitable @columnfractions 0.28 0.72 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'LossFun'} @tab @qcode{'mse'}, the default, ## @qcode{'epsiloninsensitive'}, or a function handle called as ## @code{@var{lossfun} (@var{Y}, @var{yFit}, @var{W})} returning a scalar. ## The @math{epsilon}-insensitive loss charges nothing for an error inside ## the tube, @code{max (0, abs (@var{Y} - @var{yFit}) - Epsilon)}, which is ## the quantity the fit itself minimizes. ## ## @item @qcode{'Weights'} @tab A numeric vector of observation weights ## with one entry per row of @var{X}. It defaults to a uniform weight. ## The weights are normalized to sum to one before the loss is formed. ## @end multitable ## ## @seealso{RegressionSVM, fitrsvm} ## @end deftypefn function L = loss (this, X, Y, varargin) ## Check for sufficient input arguments if (nargin < 3) error ("RegressionSVM.loss: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error (strcat ("RegressionSVM.loss: Name-Value arguments", ... " must be in pairs.")); endif [X, Y] = checkXY_ (this, X, Y, 'loss'); ## Defaults, then the optional pairs LossFun = 'mse'; args = varargin; keep = true (1, numel (args)); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("RegressionSVM.loss: parameter name must be", ... " a character vector.")); endif if (strcmpi (args{i}, 'lossfun')) LossFun = args{i+1}; if (! (is_function_handle (LossFun) || (ischar (LossFun) && isrow (LossFun)))) error (strcat ("RegressionSVM.loss: 'LossFun' must be a", ... " character vector or a function handle.")); endif if (ischar (LossFun) && ! any (strcmpi (LossFun, ... {'mse', 'epsiloninsensitive'}))) error ("RegressionSVM.loss: unsupported 'LossFun' value."); endif keep(i:i+1) = false; endif endfor W = getWeights_ (this, args(keep), rows (X), 'loss'); ## Weights are normalized to sum to one, as MATLAB does, so a loss is ## a weighted average rather than a weighted sum. W = W(:) / sum (W); yFit = predict (this, X); Y = Y(:); if (is_function_handle (LossFun)) L = LossFun (Y, yFit, W); if (! (isnumeric (L) && isscalar (L))) error (strcat ("RegressionSVM.loss: 'LossFun' must return", ... " a numeric scalar.")); endif elseif (strcmpi (LossFun, 'epsiloninsensitive')) L = sum (W .* max (0, abs (Y - yFit) - this.Epsilon)); else L = sum (W .* (Y - yFit) .^ 2); endif endfunction ## -*- texinfo -*- ## @deftypefn {RegressionSVM} {@var{L} =} resubLoss (@var{obj}) ## @deftypefnx {RegressionSVM} {@var{L} =} resubLoss (@dots{}, @var{name}, @var{value}) ## ## Compute the resubstitution regression loss of a support vector machine ## model. ## ## @code{@var{L} = resubLoss (@var{obj})} returns the weighted mean ## squared error of the model on the data it was trained on. It accepts ## the same @qcode{Name-Value} pairs as @code{loss}. ## ## @itemize ## @item ## @var{obj} must be a @qcode{RegressionSVM} class object. ## @end itemize ## ## @seealso{RegressionSVM, fitrsvm} ## @end deftypefn function L = resubLoss (this, varargin) used = true (rows (this.X), 1); X = this.X(used, :); Y = this.Y(used); L = loss (this, X, Y, varargin{:}); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionSVM} {@var{CVMdl} =} crossval (@var{obj}) ## @deftypefnx {RegressionSVM} {@var{CVMdl} =} crossval (@dots{}, @var{name}, @var{value}) ## ## Cross validate a support vector regression model. ## ## @code{@var{CVMdl} = crossval (@var{obj})} returns a ## @qcode{RegressionPartitionedModel} holding one refit of @var{obj} per ## fold of a ten-fold partition, or of an @math{n}-fold one where the ## model has fewer than ten observations. ## ## @itemize ## @item ## @var{obj} must be a @qcode{RegressionSVM} class object. ## @end itemize ## ## @code{@var{CVMdl} = crossval (@dots{}, @var{name}, @var{value})} ## accepts one, and only one, of the following @qcode{Name-Value} pairs. ## ## @multitable @columnfractions 0.28 0.72 ## @headitem @var{Name} @tab @var{Value} ## @item @qcode{'KFold'} @tab An integer greater than 1, the number of ## folds. ## @item @qcode{'Holdout'} @tab A scalar in @math{(0, 1)}, the fraction ## of observations held out for testing. ## @item @qcode{'Leaveout'} @tab @qcode{'on'} or @qcode{'off'}, whether ## to hold out one observation at a time. ## @item @qcode{'CVPartition'} @tab A @code{cvpartition} object over as ## many observations as the model was trained on. ## @end multitable ## ## @seealso{RegressionSVM, RegressionPartitionedModel, cvpartition} ## @end deftypefn function CVMdl = crossval (this, varargin) if (numel (varargin) == 1) error (strcat ("RegressionSVM.crossval: Name-Value", ... " arguments must be in pairs.")); elseif (numel (varargin) > 2) error (strcat ("RegressionSVM.crossval: specify only", ... " one of the optional Name-Value paired arguments.")); endif if (this.NumObservations < 10) numFolds = this.NumObservations; else numFolds = 10; endif Holdout = []; Leaveout = 'off'; CVPartition = []; while (numel (varargin) > 0) switch (tolower (varargin {1})) case 'kfold' numFolds = varargin{2}; if (! (isnumeric (numFolds) && isscalar (numFolds) && (numFolds == fix (numFolds)) && numFolds > 1)) error (strcat ("RegressionSVM.crossval: 'KFold'", ... " must be an integer value greater than 1.")); endif case 'holdout' Holdout = varargin{2}; if (! (isnumeric (Holdout) && isscalar (Holdout) && Holdout > 0 && Holdout < 1)) error (strcat ("RegressionSVM.crossval: 'Holdout'", ... " must be a numeric value between 0 and 1.")); endif case 'leaveout' Leaveout = varargin{2}; if (! (ischar (Leaveout) && (strcmpi (Leaveout, 'on') || strcmpi (Leaveout, 'off')))) error (strcat ("RegressionSVM.crossval: 'Leaveout'", ... " must be either 'on' or 'off'.")); endif case 'cvpartition' CVPartition = varargin{2}; if (! (isa (CVPartition, 'cvpartition'))) error (strcat ("RegressionSVM.crossval:", ... " 'CVPartition' must be a 'cvpartition'", ... " object.")); endif otherwise error (strcat ("RegressionSVM.crossval: invalid", ... " parameter name in optional paired arguments.")); endswitch varargin(1:2) = []; endwhile ## Determine the cross-validation method to use. The partition is ## built over the observations actually trained on, so its indices and ## the partitioned model's rows are the same set. n = this.NumObservations; if (! isempty (CVPartition)) partition = CVPartition; elseif (! isempty (Holdout)) partition = cvpartition (n, 'Holdout', Holdout); elseif (strcmpi (Leaveout, 'on')) partition = cvpartition (n, 'LeaveOut'); else partition = cvpartition (n, 'KFold', numFolds); endif ## Create a cross-validated model object CVMdl = RegressionPartitionedModel (this, partition); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionSVM} {@var{CMdl} =} compact (@var{obj}) ## ## Create a @qcode{CompactRegressionSVM} object. ## ## @code{@var{CMdl} = compact (@var{obj})} returns a compact version of ## the @qcode{RegressionSVM} object @var{obj}, which keeps the support ## vectors and their coefficients but drops the training data, so it ## predicts identically while carrying no observations. ## ## @seealso{fitrsvm, RegressionSVM, CompactRegressionSVM} ## @end deftypefn function CMdl = compact (this) ## Create a compact model CMdl = CompactRegressionSVM (this); endfunction ## -*- texinfo -*- ## @deftypefn {RegressionSVM} {} savemodel (@var{obj}, @var{filename}) ## ## Save a support vector regression model to a file. ## ## @code{savemodel (@var{obj}, @var{filename})} saves every property of ## the @qcode{RegressionSVM} object @var{obj} into @var{filename} in ## binary format, so that it can be read back with @code{loadmodel}. ## ## @seealso{loadmodel, RegressionSVM, fitrsvm} ## @end deftypefn function savemodel (this, fname) if (nargin < 2) error ("RegressionSVM.savemodel: too few input arguments."); endif if (! (ischar (fname) && isrow (fname) && ! isempty (fname))) error (strcat ("RegressionSVM.savemodel: FNAME must be", ... " a character vector.")); endif ## Generate variable for class name classdef_name = 'RegressionSVM'; ## Create variables from model properties X = this.X; Y = this.Y; NumObservations = this.NumObservations; RowsUsed = this.RowsUsed; BinEdges = this.BinEdges; NumPredictors = this.NumPredictors; PredictorNames = this.PredictorNames; ResponseName = this.ResponseName; ResponseTransform = this.ResponseTransform; Epsilon = this.Epsilon; Sigma = this.Sigma; Mu = this.Mu; ModelParameters = this.ModelParameters; KernelParameters = this.KernelParameters; BoxConstraints = this.BoxConstraints; Model = this.Model; Alpha = this.Alpha; Beta = this.Beta; Bias = this.Bias; IsSupportVector = this.IsSupportVector; SupportVectors = this.SupportVectors; CategoricalPredictors = this.CategoricalPredictors; ExpandedPredictorNames = this.ExpandedPredictorNames; W = this.W; RTfun = this.RTfun; ## Save classdef name and all model properties as individual variables HyperparameterOptimizationResults = this.HyperparameterOptimizationResults; save ('-binary', fname, 'classdef_name', 'X', 'Y', ... 'NumObservations', 'RowsUsed', 'BinEdges', 'NumPredictors', ... 'PredictorNames', 'ResponseName', 'ResponseTransform', ... 'Epsilon', 'Sigma', 'Mu', 'ModelParameters', ... 'Model', 'Alpha', 'Beta', 'Bias', 'IsSupportVector', ... 'SupportVectors', 'CategoricalPredictors', ... 'ExpandedPredictorNames', 'KernelParameters', ... 'BoxConstraints', 'W', 'RTfun', ... 'HyperparameterOptimizationResults'); endfunction endmethods methods (Access = private) ## Shared validation for the assessment methods, so each reports under ## its own name. function [X, Y] = checkXY_ (this, X, Y, caller) if (isempty (X)) error ("RegressionSVM.%s: X is empty.", caller); elseif (this.NumPredictors != columns (X)) error (strcat ("RegressionSVM.%s: X must have the same number", ... " of predictors as the trained model."), caller); endif if (isempty (Y)) error ("RegressionSVM.%s: Y is empty.", caller); elseif (! (isnumeric (Y) && isreal (Y))) error (strcat ("RegressionSVM.%s: Y must be a real numeric", ... " vector."), caller); elseif (rows (X) != numel (Y)) error (strcat ("RegressionSVM.%s: Y must have the same number", ... " of rows as X."), caller); endif endfunction ## Pull a "Weights" pair out of the optional arguments, defaulting to a ## uniform weight, and reject any other name. function W = getWeights_ (this, args, n, caller) W = ones (n, 1); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error (strcat ("RegressionSVM.%s: parameter name must be", ... " a character vector."), caller); endif if (strcmpi (args{i}, 'weights')) W = args{i+1}; if (! (isnumeric (W) && isvector (W))) error (strcat ("RegressionSVM.%s: 'Weights' must be a", ... " numeric vector."), caller); endif if (numel (W) != n) error (strcat ("RegressionSVM.%s: size of 'Weights' must", ... " equal the number of rows in X."), caller); endif else error (strcat ("RegressionSVM.%s: invalid parameter name in", ... " optional paired arguments."), caller); endif endfor endfunction endmethods methods(Static, Hidden) function mdl = load_model (filename, data) ## Create a RegressionSVM object mdl = RegressionSVM ([1; 2; 3], [1; 2; 3]); ## Get fieldnames from DATA (including private properties) names = fieldnames (data); ## The set methods for these read other properties, and one of them ## rebuilds Coeffs, so they are assigned once everything else is in ## place rather than in the order the file happens to list them. late = ismember (names, {'Cost', 'Prior', 'ScoreTransform', ... 'ResponseTransform'}); names = [names(! late); names(late)]; ## Copy data into object for i = 1:numel (names) ## Check fieldnames in DATA match the class properties try mdl.(names{i}) = data.(names{i}); catch error ("RegressionSVM.load_model: invalid model in '%s'.", filename) end_try_catch endfor endfunction endmethods endclassdef ## A fitted model carries the defaults MATLAB documents for fitrsvm. %!test %! X = [linspace(0, 1, 40)', linspace(2, 3, 40)']; %! Y = 2 * X(:,1) + 0.5; %! Mdl = RegressionSVM (X, Y); %! assert_equal (class (Mdl), 'RegressionSVM'); %! assert_equal (Mdl.ModelParameters.KernelFunction, 'linear'); %! assert_equal (Mdl.ModelParameters.SVMtype, 'eps_svr'); %! assert_equal (Mdl.ModelParameters.BoxConstraint, 1); %! assert_equal (Mdl.ModelParameters.KernelScale, 1); %! assert_equal (isempty (Mdl.ModelParameters.KernelPolynomialOrder), true); %! assert_equal (isempty (Mdl.Mu), true); %! assert_equal (Mdl.NumObservations, 40); %! assert_equal (Mdl.NumPredictors, 2); %! assert_equal (Mdl.ResponseName, 'Y'); %! assert_equal (Mdl.PredictorNames, {'x1', 'x2'}); ## Epsilon defaults to a robust tenth of the response's spread. %!test %! randn ('seed', 42); %! X = randn (60, 2); %! Y = X(:,1) * 3 + randn (60, 1); %! Mdl = RegressionSVM (X, Y); %! assert_equal (Mdl.Epsilon, iqr (Y) / 13.49, 1e-12); %! M2 = RegressionSVM (X, Y, 'Epsilon', 0.25); %! assert_equal (M2.Epsilon, 0.25); ## A constant response has no spread, so Epsilon falls back rather than ## collapsing the tube to zero width. %!test %! X = [linspace(0, 1, 20)', linspace(1, 2, 20)']; %! Mdl = RegressionSVM (X, ones (20, 1)); %! assert_equal (Mdl.Epsilon, 0.1); ## With a linear kernel the model is a plain linear function of its input. %!test %! randn ('seed', 42); %! X = randn (50, 3); %! Y = X * [2; -1; 0.5] + 1; %! Mdl = RegressionSVM (X, Y); %! assert_equal (size (Mdl.Beta), [3, 1]); %! assert_equal (isscalar (Mdl.Bias), true); %! assert_equal (X * Mdl.Beta + Mdl.Bias, resubPredict (Mdl), 1e-8); %! assert_equal (Mdl.Beta, Mdl.SupportVectors' * Mdl.Alpha, 1e-12); ## A non-linear kernel has no primal representation, so Beta is empty. %!test %! randn ('seed', 42); %! X = randn (40, 2); %! Y = sum (X .^ 2, 2); %! Mdl = RegressionSVM (X, Y, 'KernelFunction', 'rbf'); %! assert_equal (isempty (Mdl.Beta), true); %! assert_equal (numel (resubPredict (Mdl)), 40); ## Alpha is signed, one entry per support vector, and IsSupportVector marks ## the training rows they came from. %!test %! randn ('seed', 42); %! X = randn (60, 2); %! Y = X(:,1) - X(:,2) + randn (60, 1) * 0.5; %! Mdl = RegressionSVM (X, Y); %! nsv = rows (Mdl.SupportVectors); %! assert_equal (size (Mdl.Alpha), [nsv, 1]); %! assert_equal (any (Mdl.Alpha < 0), true); %! assert_equal (class (Mdl.IsSupportVector), 'logical'); %! assert_equal (numel (Mdl.IsSupportVector), 60); %! assert_equal (sum (Mdl.IsSupportVector), nsv); ## A wider tube is fitted by fewer support vectors. %!test %! randn ('seed', 42); %! X = randn (60, 2); %! Y = X(:,1) * 2 + randn (60, 1); %! narrow = RegressionSVM (X, Y, 'Epsilon', 0.05); %! wide = RegressionSVM (X, Y, 'Epsilon', 3); %! assert_equal (sum (wide.IsSupportVector) < ... %! sum (narrow.IsSupportVector), true); ## Standardize fits on the scale it predicts on. A model fitted on raw data ## and asked about standardized data is not merely worse, it is wrong. %!test %! randn ('seed', 42); %! X = [randn(60, 1), randn(60, 1) * 1000]; %! Y = X(:,1) + X(:,2) / 1000; %! Mdl = RegressionSVM (X, Y, 'Standardize', true, 'Epsilon', 0.01); %! assert_equal (size (Mdl.Mu), [1, 2]); %! assert_equal (size (Mdl.Sigma), [1, 2]); %! assert_equal (predict (Mdl, X), resubPredict (Mdl)); %! assert_equal (sqrt (resubLoss (Mdl)) < std (Y), true); ## A constant predictor gets a unit scale rather than a division by zero. %!test %! X = [linspace(0, 1, 20)', ones(20, 1)]; %! Mdl = RegressionSVM (X, X(:,1), 'Standardize', true); %! assert_equal (Mdl.Sigma(2), 1); %! assert_equal (all (isfinite (resubPredict (Mdl))), true); ## Rows carrying a missing value are dropped from both X and Y. %!test %! X = [linspace(0, 1, 12)', linspace(1, 2, 12)'; NaN, 1; 0.5, 1]; %! Y = [2 * linspace(0, 1, 12)'; 1; NaN]; %! Mdl = RegressionSVM (X, Y); %! assert_equal (Mdl.NumObservations, 13); %! assert_equal (sum (Mdl.RowsUsed), 13); %! assert_equal (Mdl.RowsUsed(13:14), [true; false]); %! assert_equal (numel (resubPredict (Mdl)), 13); ## predict on the training rows is resubPredict. %!test %! randn ('seed', 42); %! X = randn (40, 2); %! Y = X(:,1) + 2 * X(:,2); %! Mdl = RegressionSVM (X, Y); %! assert_equal (predict (Mdl, X), resubPredict (Mdl)); ## loss defaults to the weighted mean squared error, and weights are ## normalized, so scaling every weight leaves the loss alone. %!test %! randn ('seed', 42); %! X = randn (30, 2); %! Y = X(:,1) * 3 + 1; %! Mdl = RegressionSVM (X, Y); %! yFit = predict (Mdl, X); %! assert_equal (loss (Mdl, X, Y), mean ((Y - yFit) .^ 2), 1e-12); %! assert_equal (loss (Mdl, X, Y, 'LossFun', 'mse'), loss (Mdl, X, Y), 1e-12); %! w = rand (30, 1) + 0.1; %! assert_equal (loss (Mdl, X, Y, 'Weights', w), ... %! loss (Mdl, X, Y, 'Weights', 7 * w), 1e-12); %! assert_equal (loss (Mdl, X, Y, 'Weights', w), ... %! sum ((w / sum (w)) .* (Y - yFit) .^ 2), 1e-12); ## The epsilon-insensitive loss charges nothing inside the tube. %!test %! randn ('seed', 42); %! X = randn (30, 2); %! Y = X(:,1) * 3 + 1; %! Mdl = RegressionSVM (X, Y, 'Epsilon', 0.5); %! yFit = predict (Mdl, X); %! L = loss (Mdl, X, Y, 'LossFun', 'epsiloninsensitive'); %! assert_equal (L, mean (max (0, abs (Y - yFit) - 0.5)), 1e-12); %! assert_equal (L <= loss (Mdl, X, Y, 'LossFun', 'mse') + 1, true); %! assert_equal (L >= 0, true); ## loss takes a function handle of the response, the fit and the weights. %!test %! randn ('seed', 42); %! X = randn (20, 2); %! Y = X(:,1) + 1; %! Mdl = RegressionSVM (X, Y); %! f = @(y, yf, w) sum (w .* abs (y - yf)); %! assert_equal (loss (Mdl, X, Y, 'LossFun', f), ... %! mean (abs (Y - predict (Mdl, X))), 1e-12); ## resubLoss is loss on the training data. %!test %! randn ('seed', 42); %! X = randn (21, 2); %! Y = [randn(20, 1); NaN]; %! Mdl = RegressionSVM (X, Y); %! Xu = X(Mdl.RowsUsed, :); %! Yu = Y(Mdl.RowsUsed); %! assert_equal (resubLoss (Mdl), loss (Mdl, Xu, Yu), 1e-12); %! assert_equal (resubLoss (Mdl, 'LossFun', 'epsiloninsensitive'), ... %! loss (Mdl, Xu, Yu, 'LossFun', 'epsiloninsensitive'), 1e-12); ## ResponseTransform is applied to the prediction, by name or by handle. %!test %! X = [linspace(0, 1, 20)', linspace(1, 2, 20)']; %! Y = 2 * X(:,1) + 1; %! Mdl = RegressionSVM (X, Y); %! raw = predict (Mdl, X); %! Mdl.ResponseTransform = 'exp'; %! assert_equal (predict (Mdl, X), exp (raw), 1e-12); %! Mdl.ResponseTransform = @(y) 2 * y; %! assert_equal (predict (Mdl, X), 2 * raw, 1e-12); %! Mdl.ResponseTransform = 'none'; %! assert_equal (predict (Mdl, X), raw, 1e-12); ## nu-SVR is an Octave extension the LIBSVM engine already provides. %!test %! randn ('seed', 42); %! X = randn (50, 2); %! Y = X(:,1) - X(:,2); %! Mdl = RegressionSVM (X, Y, 'SVMtype', 'nu_svr', 'Nu', 0.3); %! assert_equal (Mdl.ModelParameters.SVMtype, 'nu_svr'); %! assert_equal (Mdl.ModelParameters.Nu, 0.3); %! assert_equal (Mdl.Model.Parameters(1), 4); %! assert_equal (all (isfinite (resubPredict (Mdl))), true); ## Every kernel trains and predicts finitely. %!test %! randn ('seed', 42); %! X = randn (40, 2); %! Y = X(:,1) + X(:,2); %! names = {'linear', 'rbf', 'gaussian', 'polynomial', 'sigmoid'}; %! for k = 1:numel (names) %! Mdl = RegressionSVM (X, Y, 'KernelFunction', names{k}); %! assert_equal (all (isfinite (resubPredict (Mdl))), true); %! endfor ## A saved model comes back carrying its own numbers. %!test %! randn ('seed', 42); %! X = randn (30, 2); %! Y = X(:,1) * 4 - 1; %! Mdl = RegressionSVM (X, Y, 'Standardize', true); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'RegressionSVM'); %! assert_equal (M2.Alpha, Mdl.Alpha); %! assert_equal (M2.Beta, Mdl.Beta); %! assert_equal (M2.Bias, Mdl.Bias); %! assert_equal (M2.Epsilon, Mdl.Epsilon); %! assert_equal (M2.SupportVectors, Mdl.SupportVectors); %! assert_equal (M2.NumObservations, Mdl.NumObservations); %! assert_equal (predict (M2, X), predict (Mdl, X)); ## compact drops the training data but predicts identically. %!test %! randn ('seed', 42); %! X = randn (40, 2); %! Y = X(:,1) - X(:,2); %! Mdl = RegressionSVM (X, Y); %! CMdl = compact (Mdl); %! assert_equal (class (CMdl), 'CompactRegressionSVM'); %! assert_equal (predict (CMdl, X), predict (Mdl, X)); %! assert_equal (loss (CMdl, X, Y), loss (Mdl, X, Y)); ## crossval returns a partitioned model holding one fit per fold. %!test %! rand ('seed', 42); randn ('seed', 42); %! X = randn (30, 2); %! Y = X(:,1) - X(:,2); %! Mdl = fitrsvm (X, Y); %! CVMdl = crossval (Mdl, 'KFold', 3); %! assert_equal (class (CVMdl), 'RegressionPartitionedModel'); %! assert_equal (CVMdl.KFold, 3); %! assert_equal (CVMdl.CrossValidatedModel, 'SVM'); %! assert_equal (numel (kfoldPredict (CVMdl)), 30); %! assert_equal (isfinite (kfoldLoss (CVMdl)), true); ## Test input validation for crossval ## discardSupportVectors empties what R2024a empties and keeps what it ## keeps: Alpha and the support vectors go, Beta, Bias and IsSupportVector ## stay, and the class is unchanged. %!test %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,2:4); y = meas(keep,1); %! Mdl = fitrsvm (X, y, "KernelFunction", "linear"); %! D = discardSupportVectors (Mdl); %! assert_equal (class (D), "RegressionSVM"); %! assert_equal (isempty (D.Alpha), true); %! assert_equal (isempty (D.SupportVectors), true); %! assert_equal (D.Beta, Mdl.Beta); %! assert_equal (D.Bias, Mdl.Bias); %! assert_equal (D.IsSupportVector, Mdl.IsSupportVector); ## A linear decision needs only Beta and Bias, so the model predicts what it ## predicted before. %!test %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,2:4); y = meas(keep,1); %! Mdl = fitrsvm (X, y, "KernelFunction", "linear"); %! D = discardSupportVectors (Mdl); %! assert_equal (predict (D, X), predict (Mdl, X), 1e-10); ## The saving is real rather than cosmetic: the engine keeps its own copy of ## the support vectors, and it collapses to the one vector that decides a ## linear model. Emptying the properties alone would free nothing. %!test %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,2:4); y = meas(keep,1); %! Mdl = fitrsvm (X, y, "KernelFunction", "linear"); %! D = discardSupportVectors (Mdl); %! assert_equal (rows (Mdl.Model.SVs) > 1, true); %! assert_equal (rows (D.Model.SVs), 1); %! assert_equal (predict (discardSupportVectors (D), X), predict (D, X)); %!error ... %! load fisheriris %! keep = ! strcmp (species, "setosa"); %! X = meas(keep,2:4); y = meas(keep,1); %! discardSupportVectors (fitrsvm (X, y, "KernelFunction", "rbf")) %!error ... %! crossval (fitrsvm (randn (12, 2), randn (12, 1)), 'KFold') %!error ... %! crossval (fitrsvm (randn (12, 2), randn (12, 1)), ... %! 'KFold', 3, 'Leaveout', 'on') %!error ... %! crossval (fitrsvm (randn (12, 2), randn (12, 1)), 'KFold', 1) %!error ... %! crossval (fitrsvm (randn (12, 2), randn (12, 1)), 'Holdout', 1) %!error ... %! crossval (fitrsvm (randn (12, 2), randn (12, 1)), 'Leaveout', 1) %!error ... %! crossval (fitrsvm (randn (12, 2), randn (12, 1)), 'CVPartition', 1) %!error ... %! crossval (fitrsvm (randn (12, 2), randn (12, 1)), 'Nope', 1) ## Test input validation for the constructor %!error ... %! RegressionSVM () %!error ... %! RegressionSVM (ones (10, 2)) %!error ... %! RegressionSVM (ones (10, 2), ones (5, 1)) %!error ... %! RegressionSVM (ones (5, 2), {'a'; 'b'; 'c'; 'd'; 'e'}) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 3)) %!error ... %! RegressionSVM ([1, 1; Inf, 1; 3, 1], [1; 2; 3]) %!error ... %! RegressionSVM ([1, 1; 2, 1; 3, 1], [1; Inf; 3]) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'Standardize', 'yes') %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'PredictorNames', 'a') %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'PredictorNames', {'a'}) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'ResponseName', 5) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'ResponseTransform', 5) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'ResponseTransform', 'nope') %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'SVMtype', 5) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'SVMtype', 'c_svc') %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'Epsilon', -1) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'KernelFunction', 5) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'KernelFunction', 'nope') %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'PolynomialOrder', 2.5) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'KernelScale', 0) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'KernelOffset', -1) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'BoxConstraint', 0) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'Nu', 0) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'CacheSize', 0) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'Tolerance', -1) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'Shrinking', 2) %!error ... %! RegressionSVM (ones (5, 2), ones (5, 1), 'Prior', 1) ## Test input validation for predict and loss %!shared RSVM %! RSVM = RegressionSVM ([1, 1; 2, 1; 3, 2; 4, 2], [2; 4; 6; 8]); %!error ... %! predict (RSVM) %!error ... %! predict (RSVM, []) %!error ... %! predict (RSVM, ones (2, 3)) %!error ... %! loss (RSVM) %!error ... %! loss (RSVM, [1, 1; 2, 1], [2; 4], 'Weights') %!error ... %! loss (RSVM, [], [2; 4]) %!error ... %! loss (RSVM, ones (2, 3), [2; 4]) %!error ... %! loss (RSVM, [1, 1; 2, 1], []) %!error ... %! loss (RSVM, [1, 1; 2, 1], {'a'; 'b'}) %!error ... %! loss (RSVM, [1, 1; 2, 1], [2; 4; 6]) %!error ... %! loss (RSVM, [1, 1; 2, 1], [2; 4], 'LossFun', 5) %!error ... %! loss (RSVM, [1, 1; 2, 1], [2; 4], 'LossFun', 'mae') %!error ... %! loss (RSVM, [1, 1; 2, 1], [2; 4], 'LossFun', @(y, yf, w) [1, 2]) %!error ... %! loss (RSVM, [1, 1; 2, 1], [2; 4], 'Weights', {'a'}) %!error ... %! loss (RSVM, [1, 1; 2, 1], [2; 4], 'Weights', [1; 2; 3]) %!error ... %! loss (RSVM, [1, 1; 2, 1], [2; 4], 'Nope', 1) ## Test input validation for savemodel %!error ... %! savemodel (RSVM) %!error ... %! savemodel (RSVM, 5) %!error ... %! RSVM.ResponseTransform = 'nope'; ## RowsUsed is empty when every observation was used. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Mdl = fitrsvm (X, Y); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (class (Mdl.RowsUsed), 'double'); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (rows (Mdl.X), 150); %! assert_equal (rows (Mdl.W), 150); ## A missing response drops its observation and RowsUsed marks it. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Y(5) = NaN; %! Mdl = fitrsvm (X, Y); %! assert_equal (class (Mdl.RowsUsed), 'logical'); %! assert_equal (size (Mdl.RowsUsed), [150, 1]); %! assert_equal (sum (Mdl.RowsUsed), 149); %! assert_equal (Mdl.RowsUsed(5), false); %! assert_equal (Mdl.NumObservations, 149); %! assert_equal (rows (Mdl.X), 149); %! assert_equal (rows (Mdl.W), 149); ## A missing predictor keeps its observation, so RowsUsed stays empty. %!test %! load fisheriris %! X = meas(:,2:4); %! X(3,2) = NaN; %! Y = meas(:,1); %! Mdl = fitrsvm (X, Y); %! assert_equal (Mdl.RowsUsed, []); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (rows (Mdl.X), 150); %! assert_equal (sum (isnan (Mdl.X(:))), 1); ## Standardizing summarizes the complete observations. With no classes the ## weights are uniform over them. Values from MATLAB R2024a. %!test %! load fisheriris %! X = meas(:,2:4); %! X(7,2) = NaN; X(120,3) = NaN; %! Mdl = fitrsvm (X, meas(:,1), 'Standardize', true); %! assert_equal (Mdl.Mu, [3.0608108108108096, 3.7655405405405395, ... %! 1.203378378378378], 1e-13); %! assert_equal (Mdl.Sigma, [0.43214937187299296, 1.7636045663278643, ... %! 0.76339873235638711], 1e-13); ## A fitted model survives savemodel and loadmodel: the properties come ## back as they were and it predicts the same. %!test %! load fisheriris %! X = meas(:,2:4); %! Y = meas(:,1); %! Mdl = fitrsvm (X, Y); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (class (M2), 'RegressionSVM'); %! assert_equal (M2.NumObservations, Mdl.NumObservations); %! assert_equal (M2.PredictorNames, Mdl.PredictorNames); %! assert_equal (class (M2.ResponseTransform), class (Mdl.ResponseTransform)); %! assert_equal (predict (M2, X(1:5,:)), predict (Mdl, X(1:5,:)), 1e-12); ## BinEdges is an empty cell, which is what MATLAB reports for this ## learner as well: it fits the predictors as they are. %!test %! load fisheriris %! Mdl = fitrsvm (meas(:,1:3), meas(:,4)); %! assert_equal (class (Mdl.BinEdges), 'cell'); %! assert_equal (Mdl.BinEdges, {}); ## KernelParameters and BoxConstraints, measured on MATLAB R2024a. A ## regression has no classes to reweight, so the scalar applies throughout. %!test %! load fisheriris %! Mdl = fitrsvm (meas(:,1:3), meas(:,4)); %! assert_equal (Mdl.KernelParameters, struct ('Function', 'linear', ... %! 'Scale', 1)); %! assert_equal (Mdl.BoxConstraints, ones (150, 1)); %!test %! load fisheriris %! Mdl = fitrsvm (meas(:,1:3), meas(:,4), 'KernelFunction', 'rbf', ... %! 'BoxConstraint', 2); %! assert_equal (Mdl.KernelParameters.Function, 'gaussian'); %! assert_equal (unique (Mdl.BoxConstraints), 2); %!test %! load fisheriris %! Mdl = fitrsvm (meas(:,1:3), meas(:,4)); %! fname = tempname (); %! savemodel (Mdl, fname); %! M2 = loadmodel (fname); %! delete (fname); %! assert_equal (M2.KernelParameters, Mdl.KernelParameters); %! assert_equal (M2.BoxConstraints, Mdl.BoxConstraints); ## HyperparameterOptimizationResults is declared for MATLAB compatibility and ## stays empty, this class running no search over its hyperparameters. %!test %! load fisheriris %! Mdl = fitrsvm (meas(:,1:3), meas(:,4)); %! assert_equal (isempty (Mdl.HyperparameterOptimizationResults), true); ## ModelParameters records what LIBSVM was given. The field list and its ## order are ours, MATLAB's SMO and ISDA parameters having no counterpart ## here. %!test %! load fisheriris %! Mdl = fitrsvm (meas(:,2:4), meas(:,1)); %! assert_equal (fieldnames (Mdl.ModelParameters)', {'SVMtype', ... %! 'BoxConstraint', 'CacheSize', 'KernelScale', 'KernelOffset', ... %! 'KernelFunction', 'KernelPolynomialOrder', 'Epsilon', 'Nu', ... %! 'Tolerance', 'Shrinking', 'StandardizeData', 'Version', 'Method', ... %! 'Type'}); %!test %! load fisheriris %! MP = fitrsvm (meas(:,2:4), meas(:,1), 'Standardize', true).ModelParameters; %! assert_equal (MP.StandardizeData, true); %! assert_equal (MP.Version, 1); %! assert_equal (MP.Method, 'SVM'); %! assert_equal (MP.Type, 'regression'); ## Epsilon is reported as the fit resolved it, and Nu is reported where ## MATLAB leaves it empty on a regression model, this class offering ## 'nu_svr' and the value being a real one. %!test %! load fisheriris %! MP = fitrsvm (meas(:,2:4), meas(:,1)).ModelParameters; %! assert_equal (MP.Epsilon, 0.0963676797627873, 1e-15); %! assert_equal (MP.Nu, 0.5); ## Every documented response transform reaches the response that is reported. %!test %! load fisheriris %! Mdl = fitrsvm (meas(:,2:4), meas(:,1)); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! T = {'identity', @(x) x; 'exp', @(x) exp (x); 'log', @(x) log (x)}; %! for i = 1:rows (T) %! Mdl.ResponseTransform = T{i,1}; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, T{i,2}(raw), 1e-12); %! endfor ## A function handle is taken as given and applied to the response. %!test %! load fisheriris %! Mdl = fitrsvm (meas(:,2:4), meas(:,1)); %! Mdl.ResponseTransform = 'none'; %! raw = predict (Mdl, meas([1, 60, 120],2:4)); %! Mdl.ResponseTransform = @(x) x .^ 2; %! yhat = predict (Mdl, meas([1, 60, 120],2:4)); %! assert_equal (yhat, raw .^ 2, 1e-12); statistics-release-1.9.2/inst/Supervised_Learning/doc-cache000066400000000000000000025160411524624707500240360ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 841 # name: # type: sq_string # elements: 1 # length: 26 ClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 1448 statistics: ClassificationDiscriminant Discriminant analysis classification The ClassificationDiscriminant class implements a discriminant analysis classifier object, which can predict responses for new data using the predict method. Discriminant analysis classification is a statistical method used to classify observations into predefined groups based on their characteristics. It estimates the parameters of different distributions for each class and predicts the class of new observations by finding the one with the smallest misclassification cost. Create a ClassificationDiscriminant object by using the fitcdiscr function or the class constructor. Six discriminant types are available, in two families. The linear family, 'linear' , 'diagLinear' and 'pseudoLinear' , pools one covariance across the classes and separates them with a hyperplane. The quadratic family, 'quadratic' , 'diagQuadratic' and 'pseudoQuadratic' , estimates a covariance per class and separates them with a quadric. A 'diag' type keeps only the variances, which is the same model as a Gamma of 1, and a 'pseudo' type inverts a singular covariance rather than refusing it. DiscrimType may be assigned after fitting, but only within its own family : the family is fixed when the model is fitted, because it decides which covariances the fit has to estimate. Assigning it, or Gamma , re-derives Sigma , LogDetSigma and Coeffs without refitting. See also: fitcdiscr # name: # type: sq_string # elements: 1 # length: 36 Discriminant analysis classification # name: # type: sq_string # elements: 1 # length: 39 ClassificationDiscriminant.BetweenSigma # name: # type: sq_string # elements: 1 # length: 750 ClassificationDiscriminant: property BetweenSigma Between-class covariance matrix A P -by- P matrix holding the covariance of the class means about the overall mean, weighted by how many observations each class contributes. With n_k observations in class k , p_k = n_k / n and \bar{\mu} = \sum_k p_k \mu_k , it is BetweenSigma = sum_k n_k (Mu(k,:) - mubar)' * (Mu(k,:) - mubar) / (n * (1 - sum_k p_k^2)) The denominator is the unbiased one for a weighted covariance, so a balanced fit divides by n (K-1) / K . It reads the class sizes , not Prior : assigning a prior leaves it where it was. It is estimated for every discriminant type, the quadratic family included, since it describes the classes rather than the fit. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Between-class covariance matrix # name: # type: sq_string # elements: 1 # length: 35 ClassificationDiscriminant.BinEdges # name: # type: sq_string # elements: 1 # length: 374 ClassificationDiscriminant: property BinEdges Bin edges of the predictors A cell array with one entry per predictor, holding that predictor’s bin edges where the learner discretized it before fitting. It is empty here and stays empty: this learner fits the predictors as they are, and MATLAB’s reports an empty cell for it as well. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Bin edges of the predictors # name: # type: sq_string # elements: 1 # length: 48 ClassificationDiscriminant.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 238 ClassificationDiscriminant: property CategoricalPredictors Indices of the categorical predictors A numeric vector of column indices into X naming the predictors treated as categorical, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 37 ClassificationDiscriminant.ClassNames # name: # type: sq_string # elements: 1 # length: 354 ClassificationDiscriminant: property ClassNames Names of classes in the response variable An array of unique values of the response variable Y , which has the same data types as the data in Y . This property is read-only. ClassNames can have any of the following datatypes: Cell array of character vectors Character array Logical vector Numeric vector # name: # type: sq_string # elements: 1 # length: 41 Names of classes in the response variable # name: # type: sq_string # elements: 1 # length: 53 ClassificationDiscriminant.ClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 3156 statistics: obj = ClassificationDiscriminant ( X , Y ) statistics: obj = ClassificationDiscriminant (…, name , value ) Create a ClassificationDiscriminant class object containing a discriminant analysis model. obj = ClassificationDiscriminant ( X , Y ) returns a ClassificationDiscriminant object, with X as the predictor data and Y containing the class labels of observations in X . X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. X will be used to train the discriminant model. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y can contain any type of categorical data. Y must have the same number of rows as X . obj = ClassificationDiscriminant (…, name , value ) returns a ClassificationDiscriminant object with parameters specified by the following name , value paired input arguments: Name Value 'PredictorNames' A cell array of character vectors specifying the names of the predictors. The length of this array must match the number of columns in X . 'ResponseName' A character vector specifying the name of the response variable. 'ClassNames' Names of the classes in the class labels, Y , used for fitting the Discriminant model. ClassNames are of the same type as the class labels in Y . 'Cost' An N×R numeric matrix containing misclassification cost for the corresponding instances in X , where R is the number of unique categories in Y . If an instance is correctly classified into its category the cost is calculated to be 1, otherwise 0. The cost matrix can be altered by using Mdl .cost = somecost . By default, its value is cost = ones (rows (X), numel (unique (Y))) . 'Prior' A numeric vector specifying the prior probabilities for each class. The order of the elements in Prior corresponds to the order of the classes in ClassNames . Alternatively, you can specify 'empirical' to use the empirical class probabilities or 'uniform' to assume equal class probabilities. 'ScoreTransform' A user-defined function handle or a character vector specifying one of the following builtin functions specifying the transformation applied to predicted classification scores. Supported values include 'doublelogit' , 'invlogit' , 'ismax' , 'logit' , 'none' , 'identity' , 'sign' , 'symmetric' , 'symmetricismax' , and 'symmetriclogit' . 'DiscrimType' A character vector or string scalar specifying the type of discriminant analysis to perform. The only supported value is 'linear' . 'FillCoeffs' A character vector or string scalar with values 'on' or 'off' specifying whether to fill the coefficients after fitting. If set to 'on' , the coefficients are computed during model fitting, which can be useful for prediction. 'Gamma' A numeric scalar specifying the regularization parameter for the covariance matrix. It adjusts the linear discriminant analysis to make the model more stable in the presence of multicollinearity or small sample sizes. A value of 0 corresponds to no regularization, while a value of 1 corresponds to a completely regularized model. See also: fitcdiscr # name: # type: sq_string # elements: 1 # length: 90 Create a ClassificationDiscriminant class object containing a discriminant analysis model. # name: # type: sq_string # elements: 1 # length: 33 ClassificationDiscriminant.Coeffs # name: # type: sq_string # elements: 1 # length: 872 ClassificationDiscriminant: property Coeffs Coefficient matrices A K×K structure containing the coefficient matrices, where K is the number of classes. If the 'FillCoeffs' parameter was set to 'off' in either the fitcdiscr function or the ClassificationDiscriminant constructor, then Coeffs is empty ([]) . This property is read-only. Coeffs(i,j) contains the coefficients of the boundary between the classes i and j in the following fields: DiscrimType - A character vector Class1 - ClassNames (i) Class2 - ClassNames (j) Const - A scalar Linear - A vector with length as the number of predictors. Quadratic - The quadratic family only. A PxP matrix, or a 1xP vector for 'diagQuadratic' , following the shape of Sigma . The diagonal entries carry the two class names and nothing else. The structure is rebuilt whenever DiscrimType , Gamma or Prior is assigned. # name: # type: sq_string # elements: 1 # length: 20 Coefficient matrices # name: # type: sq_string # elements: 1 # length: 31 ClassificationDiscriminant.Cost # name: # type: sq_string # elements: 1 # length: 1205 ClassificationDiscriminant: property Cost Cost of Misclassification A square matrix specifying the cost of misclassification of a point. Cost(i,j) is the cost of classifying a point into class j if its true class is i (that is, the rows correspond to the true class and the columns correspond to the predicted class). The order of the rows and columns in Cost corresponds to the order of the classes in ClassNames . The number of rows and columns in Cost is the number of unique classes in the response. By default, Cost(i,j) = 1 if i != j , and Cost(i,j) = 0 if i = j . In other words, the cost is 0 for correct classification and 1 for incorrect classification. Add or change the Cost property using dot notation as in: obj .Cost = costMatrix A cost may also be given as a struct with the fields ClassNames and ClassificationCosts , which names the order its own matrix is written in. That matrix is permuted into the order of ClassNames above, so a caller need not know which order the classes were sorted into. It must name every class. A cost must be floating point, not sparse, not complex, non-negative and zero down its diagonal, and must hold no NaN or Inf . A single is widened to double . # name: # type: sq_string # elements: 1 # length: 25 Cost of Misclassification # name: # type: sq_string # elements: 1 # length: 32 ClassificationDiscriminant.Delta # name: # type: sq_string # elements: 1 # length: 809 ClassificationDiscriminant: property Delta Delta threshold for the linear coefficients A nonnegative scalar that eliminates predictors. A per-class linear coefficient is set to zero when it falls below Delta , and the comparison is made on the standardized coefficient, the coefficient times the within-class standard deviation of its predictor. Scaling matters here: a threshold on the raw coefficients would depend on the units each predictor is measured in, so the same model in centimetres and in metres would drop different predictors. DeltaPredictor reports, per predictor, the value at which it drops out of every class at once. It applies to the linear family only, a quadratic discriminant having no linear coefficients to eliminate. Assigning it rebuilds Coeffs and changes what predict answers. # name: # type: sq_string # elements: 1 # length: 43 Delta threshold for the linear coefficients # name: # type: sq_string # elements: 1 # length: 41 ClassificationDiscriminant.DeltaPredictor # name: # type: sq_string # elements: 1 # length: 482 ClassificationDiscriminant: property DeltaPredictor Minimum Delta at which each predictor drops out A row vector with one entry per predictor, the value of Delta at which that predictor’s coefficient is zero for every class and the predictor leaves the model altogether. It is all zeros for the quadratic family, which has no linear coefficients to eliminate. This property is read-only, and it describes the fit rather than the threshold: assigning Delta does not move it. # name: # type: sq_string # elements: 1 # length: 47 Minimum Delta at which each predictor drops out # name: # type: sq_string # elements: 1 # length: 38 ClassificationDiscriminant.DiscrimType # name: # type: sq_string # elements: 1 # length: 796 ClassificationDiscriminant: property DiscrimType Discriminant type A character vector naming the discriminant model, one of 'linear' , 'quadratic' , 'diagLinear' , 'diagQuadratic' , 'pseudoLinear' or 'pseudoQuadratic' . A linear type pools one covariance across the classes; a quadratic type estimates one per class. A 'diag' type keeps only the variances, and a 'pseudo' type inverts a singular covariance instead of refusing it. This property may be assigned, but only within its own family : the three linear types interchange freely and so do the three quadratic ones, while no assignment moves a model between the two. The family is fixed when the model is fitted, because it decides which covariances the fit has to estimate. Assigning re-derives Sigma , LogDetSigma , Gamma and Coeffs . # name: # type: sq_string # elements: 1 # length: 17 Discriminant type # name: # type: sq_string # elements: 1 # length: 49 ClassificationDiscriminant.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 271 ClassificationDiscriminant: property ExpandedPredictorNames Names of the predictors as the model expanded them A cell array of character vectors. It matches PredictorNames unless a categorical predictor was expanded into indicator variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 50 Names of the predictors as the model expanded them # name: # type: sq_string # elements: 1 # length: 32 ClassificationDiscriminant.Gamma # name: # type: sq_string # elements: 1 # length: 513 ClassificationDiscriminant: property Gamma Gamma regularization parameter A scalar from 0 to 1 shrinking the covariance towards its diagonal. Gamma and DiscrimType are one state: a value of 1 is the diagonal type, so assigning it renames DiscrimType to 'diagLinear' or 'diagQuadratic' , and assigning a diagonal type sets Gamma to 1. The quadratic family admits 0 and 1 only. A value below MinGamma is refused, since it would leave the covariance singular. Assigning re-derives Sigma , LogDetSigma and Coeffs . # name: # type: sq_string # elements: 1 # length: 30 Gamma regularization parameter # name: # type: sq_string # elements: 1 # length: 60 ClassificationDiscriminant.HyperparameterOptimizationResults # name: # type: sq_string # elements: 1 # length: 369 ClassificationDiscriminant: property HyperparameterOptimizationResults Results of the hyperparameter optimization Always empty. It is declared for MATLAB compatibility, where it holds what an automatic search over the hyperparameters found. This class fits the parameters it is given and runs no such search, so there is nothing to report. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Results of the hyperparameter optimization # name: # type: sq_string # elements: 1 # length: 38 ClassificationDiscriminant.LogDetSigma # name: # type: sq_string # elements: 1 # length: 618 ClassificationDiscriminant: property LogDetSigma Logarithm of the determinant of the within-class covariance matrix A scalar for the linear family and a Kx1 vector for the quadratic one, one entry per class. It is computed in correlation space, as the sum of the logarithms of the predictor variances plus the log determinant of the correlation matrix, which is far better conditioned than the covariance when the data are nearly collinear. A predictor with no variance contributes nothing rather than an infinity, and the 'pseudo' types sum only over the directions that carry variance. This property is read-only. # name: # type: sq_string # elements: 1 # length: 66 Logarithm of the determinant of the within-class covariance matrix # name: # type: sq_string # elements: 1 # length: 35 ClassificationDiscriminant.MinGamma # name: # type: sq_string # elements: 1 # length: 444 ClassificationDiscriminant: property MinGamma Minimum value for the Gamma regularization parameter A scalar from 0 to 1, the least regularization that leaves the correlation matrix invertible. It is 0 when the matrix is already invertible, and positive when the predictors are collinear, in which case a plain 'linear' or 'quadratic' fit is raised to it rather than failing. Assigning a Gamma below it is refused. This property is read-only. # name: # type: sq_string # elements: 1 # length: 52 Minimum value for the Gamma regularization parameter # name: # type: sq_string # elements: 1 # length: 42 ClassificationDiscriminant.ModelParameters # name: # type: sq_string # elements: 1 # length: 477 ClassificationDiscriminant: property ModelParameters Fitting options, as they were given A structure holding the parameters of the fit: DiscrimType , Gamma , Delta , FillCoeffs , and the Version , Method and Type tags. MATLAB reports a SaveMemory field beside these. This class has no such option and always stores the full covariance, so there is no setting to report and the field is absent rather than answering for a knob that does not exist. This property is read-only. # name: # type: sq_string # elements: 1 # length: 35 Fitting options, as they were given # name: # type: sq_string # elements: 1 # length: 29 ClassificationDiscriminant.Mu # name: # type: sq_string # elements: 1 # length: 271 ClassificationDiscriminant: property Mu Class means A K×P numeric matrix specifying the mean of the multivariate normal distribution of each corresponding class, where K is the number of classes and P is the number of predictors in X . This property is read-only. # name: # type: sq_string # elements: 1 # length: 11 Class means # name: # type: sq_string # elements: 1 # length: 42 ClassificationDiscriminant.NumObservations # name: # type: sq_string # elements: 1 # length: 248 ClassificationDiscriminant: property NumObservations Number of observations A positive integer value specifying the number of observations in the training dataset used for training the ClassificationDiscriminant model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 40 ClassificationDiscriminant.NumPredictors # name: # type: sq_string # elements: 1 # length: 242 ClassificationDiscriminant: property NumPredictors Number of predictors A positive integer value specifying the number of predictors in the training dataset used for training the ClassificationDiscriminant model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 41 ClassificationDiscriminant.PredictorNames # name: # type: sq_string # elements: 1 # length: 266 ClassificationDiscriminant: property PredictorNames Names of predictor variables A cell array of character vectors specifying the names of the predictor variables. The names are in the order in which they appear in the training dataset. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Names of predictor variables # name: # type: sq_string # elements: 1 # length: 32 ClassificationDiscriminant.Prior # name: # type: sq_string # elements: 1 # length: 619 ClassificationDiscriminant: property Prior Prior probability for each class A numeric vector specifying the prior probabilities for each class. The order of the elements in Prior corresponds to the order of the classes in ClassNames . Add or change the Prior property using dot notation as in: obj .Prior = priorVector Specified as a row vector with one entry per class, in the order of ClassNames , and rescaled to sum to one. It may be given as 'empirical' , 'uniform' , a numeric vector, or a structure with ClassNames and ClassProbs fields, which assigns each probability by class name rather than by position. # name: # type: sq_string # elements: 1 # length: 32 Prior probability for each class # name: # type: sq_string # elements: 1 # length: 39 ClassificationDiscriminant.ResponseName # name: # type: sq_string # elements: 1 # length: 170 ClassificationDiscriminant: property ResponseName Response variable name A character vector specifying the name of the response variable Y . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 35 ClassificationDiscriminant.RowsUsed # name: # type: sq_string # elements: 1 # length: 404 ClassificationDiscriminant: property RowsUsed Rows used for fitting A logical column vector with the same length as the observations in the original predictor data X , true for each row that was used for fitting the ClassificationDiscriminant model. It is empty, [] , when every observation was used, so a non-empty value means that rows holding missing values were dropped. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Rows used for fitting # name: # type: sq_string # elements: 1 # length: 41 ClassificationDiscriminant.ScoreTransform # name: # type: sq_string # elements: 1 # length: 1006 ClassificationDiscriminant: property ScoreTransform Transformation function for classification scores Specified as a function handle for transforming the classification scores. Add or change the ScoreTransform property using dot notation as in: obj .ScoreTransform = 'function_name' obj .ScoreTransform = @function_handle When specified as a character vector, it can be any of the following built-in functions. Nevertheless, the ScoreTransform property always stores their function handle equivalent. Value Description 'doublelogit' 1 ./ (1 + exp (-2 × x)) 'invlogit' log (x ./ (1 - x)) 'ismax' Sets the score for the class with the largest score to 1, and for all other classes to 0 'logit' 1 ./ (1 + exp (-x)) 'none' x (no transformation) 'identity' x (no transformation) 'sign' -1 for x < 0, 0 for x = 0, 1 for x > 0 'symmetric' 2 × x - 1 'symmetricismax' Sets the score for the class with the largest score to 1, and for all other classes to -1 'symmetriclogit' 2 ./ (1 + exp (-x)) - 1 # name: # type: sq_string # elements: 1 # length: 49 Transformation function for classification scores # name: # type: sq_string # elements: 1 # length: 32 ClassificationDiscriminant.Sigma # name: # type: sq_string # elements: 1 # length: 512 ClassificationDiscriminant: property Sigma Within-class covariance A numeric array whose shape follows DiscrimType , with P predictors and K classes: DiscrimType Sigma LogDetSigma 'linear' , 'pseudoLinear' PxP scalar 'quadratic' , 'pseudoQuadratic' PxPxK Kx1 'diagLinear' 1xP scalar 'diagQuadratic' 1xPxK Kx1 The linear family pools one covariance across the classes and the quadratic family estimates one per class. This property is read-only, but it is re-derived whenever DiscrimType or Gamma is assigned. # name: # type: sq_string # elements: 1 # length: 23 Within-class covariance # name: # type: sq_string # elements: 1 # length: 28 ClassificationDiscriminant.W # name: # type: sq_string # elements: 1 # length: 231 ClassificationDiscriminant: property W Observation weights A numeric column vector with one entry per observation used for fitting. Every observation carries the same weight, so the vector sums to one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 28 ClassificationDiscriminant.X # name: # type: sq_string # elements: 1 # length: 241 ClassificationDiscriminant: property X Predictor data A numeric matrix containing the unstandardized predictor data. Each column of X represents one predictor (variable), and each row represents one observation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Predictor data # name: # type: sq_string # elements: 1 # length: 36 ClassificationDiscriminant.XCentered # name: # type: sq_string # elements: 1 # length: 217 ClassificationDiscriminant: property XCentered Predictor data with class means subtracted A matrix of the same size as X and the values in X with the corresponding class means subtracted. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Predictor data with class means subtracted # name: # type: sq_string # elements: 1 # length: 28 ClassificationDiscriminant.Y # name: # type: sq_string # elements: 1 # length: 318 ClassificationDiscriminant: property Y Class labels Specified as a logical or numeric column vector, or as a character array or a cell array of character vectors with the same number of rows as the predictor data. Each row in Y is the observed class label for the corresponding row in X . This property is read-only. # name: # type: sq_string # elements: 1 # length: 12 Class labels # name: # type: sq_string # elements: 1 # length: 34 ClassificationDiscriminant.compact # name: # type: sq_string # elements: 1 # length: 286 ClassificationDiscriminant: CVMdl = compact ( obj ) Create a CompactClassificationDiscriminant object. CVMdl = compact ( obj ) creates a compact version of the ClassificationDiscriminant object, obj . See also: fitcdiscr, ClassificationDiscriminant, CompactClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 50 Create a CompactClassificationDiscriminant object. # name: # type: sq_string # elements: 1 # length: 35 ClassificationDiscriminant.crossval # name: # type: sq_string # elements: 1 # length: 1076 ClassificationDiscriminant: CVMdl = crossval ( obj ) ClassificationDiscriminant: CVMdl = crossval (…, Name , Value ) Cross Validate a Discriminant classification object. CVMdl = crossval ( obj ) returns a cross-validated model object, CVMdl , from a trained model, obj , using 10-fold cross-validation by default. CVMdl = crossval ( obj , name , value ) specifies additional name-value pair arguments to customize the cross-validation process. Name Value 'KFold' Specify the number of folds to use in k-fold cross-validation. "KFold", k , where k is an integer greater than 1. 'Holdout' Specify the fraction of the data to hold out for testing. "Holdout", p , where p is a scalar in the range (0,1) . 'Leaveout' Specify whether to perform leave-one-out cross-validation. "Leaveout", Value , where Value is ’on’ or ’off’. 'CVPartition' Specify a cvpartition object used for cross-validation. "CVPartition", cv , where isa ( cv , "cvpartition") = 1. See also: fitcdiscr, ClassificationDiscriminant, cvpartition, ClassificationPartitionedModel # name: # type: sq_string # elements: 1 # length: 52 Cross Validate a Discriminant classification object. # name: # type: sq_string # elements: 1 # length: 35 ClassificationDiscriminant.cvshrink # name: # type: sq_string # elements: 1 # length: 1862 ClassificationDiscriminant: err = cvshrink ( obj ) ClassificationDiscriminant: [ err , gamma ] = cvshrink ( obj ) ClassificationDiscriminant: [ err , gamma , delta ] = cvshrink ( obj ) ClassificationDiscriminant: [ err , gamma , delta , numpred ] = cvshrink ( obj ) ClassificationDiscriminant: […] = cvshrink (…, Name , Value ) Cross validate the regularization of a discriminant. err = cvshrink ( obj ) cross validates obj over a grid of Gamma values and returns the misclassification rate at each of them, so that a regularization can be chosen by what it costs on held-out data rather than on the data it was fitted to. [ err , gamma , delta , numpred ] = cvshrink ( obj ) also returns the grid itself and the number of predictors surviving at each point of it. gamma is a column with one entry per Gamma ; err , delta and numpred carry one row per Gamma and one column per Delta . Name Value 'NumGamma' The number of Gamma intervals, a positive integer, 10 by default, giving NumGamma + 1 values evenly spaced from 0 to 1. 'NumDelta' The number of Delta intervals, a non-negative integer, 0 by default. For each Gamma the Delta values run from 0 to the point at which every predictor has been eliminated, so the grid is not the same in every row. 'Gamma' The Gamma values to try, given explicitly as a vector, in place of 'NumGamma' . 'Delta' The Delta values to try, given explicitly, in place of 'NumDelta' : a vector used for every Gamma , or a matrix with one row per Gamma . Every point of the grid is cross validated against the same partition, so the errors differ by the regularization and not by the split. The partition is drawn at random, so err is not reproducible across runs and does not match MATLAB’s; gamma , delta and numpred are deterministic and do. See also: ClassificationDiscriminant, fitcdiscr, nLinearCoeffs # name: # type: sq_string # elements: 1 # length: 52 Cross validate the regularization of a discriminant. # name: # type: sq_string # elements: 1 # length: 31 ClassificationDiscriminant.edge # name: # type: sq_string # elements: 1 # length: 617 ClassificationDiscriminant: e = edge ( obj , X , Y ) ClassificationDiscriminant: e = edge (…, "Weights" , w ) Classification edge, the mean of the classification margins. e = edge ( obj , X , Y ) reduces the vector that margin returns to a single number, the mean margin over the rows of X . It says how far the model puts the true class ahead of its nearest rival on average, so a larger edge is a better model, and unlike a loss it is not bounded above and rewards confidence rather than bare correctness. e = edge (…, "Weights" , w ) takes the weighted mean instead, with one weight per row of X . # name: # type: sq_string # elements: 1 # length: 60 Classification edge, the mean of the classification margins. # name: # type: sq_string # elements: 1 # length: 31 ClassificationDiscriminant.logp # name: # type: sq_string # elements: 1 # length: 690 ClassificationDiscriminant: lp = logp ( obj , X ) Log unconditional probability density of the observations. lp = logp ( obj , X ) returns an Nx1 vector holding, for each row of X , the natural logarithm of P(x) = sum_k P(k) P(x|k) , the density of the observation summed over the classes with each class weighted by its prior P(k) . Each P(x|k) is the multivariate normal density of class k . obj must be a ClassificationDiscriminant object. X must be an NxP numeric matrix with one column per predictor of the trained model. An unusually low value marks an observation the model finds unlikely under every class, which is what makes this an outlier test rather than a classification. # name: # type: sq_string # elements: 1 # length: 58 Log unconditional probability density of the observations. # name: # type: sq_string # elements: 1 # length: 31 ClassificationDiscriminant.loss # name: # type: sq_string # elements: 1 # length: 1930 ClassificationDiscriminant: L = loss ( obj , X , Y ) ClassificationDiscriminant: L = loss (…, name , value ) Compute loss for a trained ClassificationDiscriminant object. L = loss ( obj , X , Y ) computes the loss, L , using the default loss function 'mincost' . obj is a ClassificationDiscriminant object trained on X and Y . X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y must have same numbers of Rows as X . L = loss (…, name , value ) allows additional options specified by name - value pairs: Name Value 'LossFun' Specifies the loss function to use. Can be a function handle with four input arguments (C, S, W, Cost) which returns a scalar value or one of: ’binodeviance’, ’classifcost’, ’classiferror’, ’exponential’, ’hinge’, ’logit’,’mincost’, ’quadratic’. C is a logical matrix of size N×K , where N is the number of observations and K is the number of classes. The element C(i,j) is true if the class label of the i-th observation is equal to the j-th class. S is a numeric matrix of size N×K , where each element represents the classification score for the corresponding class. W is a numeric vector of length N , representing the observation weights. Cost is a K×K matrix representing the misclassification costs. 'Weights' Specifies observation weights, must be a numeric vector of length equal to the number of rows in X. Default is ones (size (X, 1)) . loss normalizes the weights so that observation weights in each class sum to the prior probability of that class. When you supply Weights, loss computes the weighted classification loss. See also: ClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 61 Compute loss for a trained ClassificationDiscriminant object. # name: # type: sq_string # elements: 1 # length: 32 ClassificationDiscriminant.mahal # name: # type: sq_string # elements: 1 # length: 1013 ClassificationDiscriminant: M = mahal ( obj , X ) ClassificationDiscriminant: M = mahal (…, 'ClassLabels' , labels ) Squared Mahalanobis distance to the class means. M = mahal ( obj , X ) returns an NxK matrix whose element (i,j) is the squared Mahalanobis distance from observation i to the mean of class j , measured against the covariance that class carries: the one shared covariance for a linear discriminant and the class’s own for a quadratic one. obj must be a ClassificationDiscriminant object. X must be an NxP numeric matrix with one column per predictor of the trained model. M = mahal (…, 'ClassLabels' , labels ) returns an Nx1 vector instead, holding for each observation the distance to the mean of the class labels names for it. labels must have one entry per row of X , each of them one of ClassNames . The distance is measured against the covariance the model reports, so a regularized model is measured against its regularized covariance. The prior does not enter it. # name: # type: sq_string # elements: 1 # length: 48 Squared Mahalanobis distance to the class means. # name: # type: sq_string # elements: 1 # length: 33 ClassificationDiscriminant.margin # name: # type: sq_string # elements: 1 # length: 842 ClassificationDiscriminant: m = margin ( obj , X , Y ) Classification margins for discriminant analysis classifier. m = margin ( obj , X , Y ) returns the classification margins for obj with data X and classification Y . m is a numeric vector of length size (X,1). obj is a ClassificationDiscriminant object trained on X and Y . X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y must have same numbers of Rows as X . The classification margin for each observation is the difference between the classification score for the true class and the maximal classification score for the false classes. See also: fitcdiscr, ClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 60 Classification margins for discriminant analysis classifier. # name: # type: sq_string # elements: 1 # length: 40 ClassificationDiscriminant.nLinearCoeffs # name: # type: sq_string # elements: 1 # length: 887 ClassificationDiscriminant: n = nLinearCoeffs ( obj ) ClassificationDiscriminant: n = nLinearCoeffs ( obj , delta ) Number of nonzero linear coefficients at a regularization threshold. n = nLinearCoeffs ( obj ) returns the number of predictors the discriminant keeps at its own Delta . n = nLinearCoeffs ( obj , delta ) returns the number it would keep at each threshold in delta , as a column vector however delta is shaped. A predictor survives a threshold when its DeltaPredictor reaches it, the comparison including equality, so delta at exactly a predictor’s own value still counts it. A threshold above every DeltaPredictor therefore leaves nothing and returns zero. The count is taken whatever the DiscrimType , as MATLAB takes it, even though Delta regularizes the linear types alone. See also: fitcdiscr, ClassificationDiscriminant, CompactClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 68 Number of nonzero linear coefficients at a regularization threshold. # name: # type: sq_string # elements: 1 # length: 34 ClassificationDiscriminant.predict # name: # type: sq_string # elements: 1 # length: 1430 ClassificationDiscriminant: label = predict ( obj , XC ) ClassificationDiscriminant: [ label , score , cost ] = predict ( obj , XC ) Classify new data points into categories using the discriminant analysis model from a ClassificationDiscriminant object. label = predict ( obj , XC ) returns the vector of labels predicted for the corresponding instances in XC , using the predictor data in obj.X and corresponding labels, obj.Y , stored in the ClassificationDiscriminant model, obj . obj must be a ClassificationDiscriminant class object. XC must be an M×P numeric matrix with the same number of features P as the corresponding predictors of the discriminant model in obj . [ label , score , cost ] = predict ( obj , XC ) also returns score , which contains the predicted class scores or posterior probabilities for each instance of the corresponding unique classes, and cost , which is a matrix containing the expected cost of the classifications. The score matrix contains the posterior probabilities for each class, calculated using the multivariate normal probability density function and the prior probabilities of each class. These scores are normalized to ensure they sum to 1 for each observation. The cost matrix contains the expected classification cost for each class, computed based on the posterior probabilities and the specified misclassification costs. See also: ClassificationDiscriminant, fitcdiscr # name: # type: sq_string # elements: 1 # length: 120 Classify new data points into categories using the discriminant analysis model from a ClassificationDiscriminant object. # name: # type: sq_string # elements: 1 # length: 36 ClassificationDiscriminant.resubEdge # name: # type: sq_string # elements: 1 # length: 219 ClassificationDiscriminant: e = resubEdge ( obj ) Classification edge of the model on its own training data. e = resubEdge ( obj ) is edge applied to the observations the model was fitted on, the mean of resubMargin . # name: # type: sq_string # elements: 1 # length: 58 Classification edge of the model on its own training data. # name: # type: sq_string # elements: 1 # length: 36 ClassificationDiscriminant.resubLoss # name: # type: sq_string # elements: 1 # length: 594 ClassificationDiscriminant: L = resubLoss ( obj ) ClassificationDiscriminant: L = resubLoss (…, name , value ) Classification loss of the model on its own training data. L = resubLoss ( obj ) is loss applied to the observations the model was fitted on, defaulting to 'mincost' , and it accepts the same Name-Value pairs. Being a resubstitution quantity it is a lower bound on the error rather than an estimate of it. It is worth least on a lazy learner: a one-neighbour ClassificationKNN has a resubstitution loss of exactly zero, every training point being its own nearest neighbour. # name: # type: sq_string # elements: 1 # length: 58 Classification loss of the model on its own training data. # name: # type: sq_string # elements: 1 # length: 38 ClassificationDiscriminant.resubMargin # name: # type: sq_string # elements: 1 # length: 296 ClassificationDiscriminant: m = resubMargin ( obj ) Classification margins of the model on its own training data. m = resubMargin ( obj ) is margin applied to the observations the model was fitted on, one number per observation. Being a resubstitution quantity it is optimistic by construction. # name: # type: sq_string # elements: 1 # length: 61 Classification margins of the model on its own training data. # name: # type: sq_string # elements: 1 # length: 39 ClassificationDiscriminant.resubPredict # name: # type: sq_string # elements: 1 # length: 630 ClassificationDiscriminant: label = resubPredict ( obj ) ClassificationDiscriminant: [ label , score , cost ] = resubPredict ( obj ) Classify the training data with the model fitted to it. label = resubPredict ( obj ) is predict applied to the observations the model was fitted on, which it holds in X . Handing them over yourself is not the same thing: a row dropped for a missing response is not in X , so the original matrix and the model’s own are different data. The result measures fit and not generalization, and is optimistic by construction. crossval is what estimates performance on data the model has not seen. # name: # type: sq_string # elements: 1 # length: 55 Classify the training data with the model fitted to it. # name: # type: sq_string # elements: 1 # length: 36 ClassificationDiscriminant.savemodel # name: # type: sq_string # elements: 1 # length: 510 ClassificationDiscriminant: savemodel ( obj , filename ) Save a ClassificationDiscriminant object. savemodel ( obj , filename ) saves each property of a ClassificationDiscriminant object into an Octave binary file, the name of which is specified in filename , along with an extra variable, which defines the type classification object these variables constitute. Use loadmodel in order to load a classification object into Octave’s workspace. See also: loadmodel, fitcdiscr, ClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 41 Save a ClassificationDiscriminant object. # name: # type: sq_string # elements: 1 # length: 17 ClassificationGAM # name: # type: sq_string # elements: 1 # length: 2023 statistics: ClassificationGAM Generalized additive model classification The ClassificationGAM class implements a gradient boosting algorithm for classification. This approach allows the model to capture non-linear relationships between predictors and the binary response variable. Generalized additive model classification is a statistical method that extends linear models by allowing non-linear relationships between each predictor and the response variable through smooth functions. It combines the interpretability of linear models with the flexibility of non-parametric methods. Create a ClassificationGAM object by using the fitcgam function or the class constructor. Two weak learners are available, selected by FitMethod . 'boostedtrees' , the default, boosts one shallow decision tree per predictor in each round, which is the scheme MATLAB’s generalized additive model uses. A second phase then boosts trees over pairs of predictors, where interactions are asked for. 'splines' boosts a smoothing spline per predictor over NumIterations passes. It has no MATLAB counterpart and is an Octave extension, kept because a smooth additive fit is a genuinely different and often better answer than a staircase of stumps. The two take different arguments, and an argument meant for one is refused by the other rather than ignored. The choice is visible in the properties. Knots , Order , DoF , Formula , LearningRate , NumIterations , BaseModel , ModelwInt and IntMatrix describe a spline fit and are empty under the boosted-tree engine, while ModelParameters , ReasonForTermination , BinEdges , PairDetectionBinEdges and TreeModel describe a tree fit and are empty under the spline engine. Fitted values are not expected to equal MATLAB’s even under 'boostedtrees' . The stopping rule and the step-reduction limit are not recoverable from anything MATLAB reports, so this engine documents its own; what the two share is the estimator and the reported surface, not the arithmetic. See also: fitcgam # name: # type: sq_string # elements: 1 # length: 41 Generalized additive model classification # name: # type: sq_string # elements: 1 # length: 27 ClassificationGAM.BaseModel # name: # type: sq_string # elements: 1 # length: 288 ClassificationGAM: property BaseModel Base model parameters A structure containing the parameters of the base model without any interaction terms. The base model represents the generalized additive model with only the main effects (predictor terms) included. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Base model parameters # name: # type: sq_string # elements: 1 # length: 26 ClassificationGAM.BinEdges # name: # type: sq_string # elements: 1 # length: 475 ClassificationGAM: property BinEdges Bin edges of the predictors A cell array with one entry per predictor, holding that predictor’s bin edges where the model discretized it before fitting. It is empty here and stays empty: this generalized additive model is built from splines, which take the predictors as they are, where MATLAB’s is built from boosted trees and bins them. That difference is described in the class documentation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Bin edges of the predictors # name: # type: sq_string # elements: 1 # length: 39 ClassificationGAM.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 219 ClassificationGAM: property CategoricalPredictors Indices of the categorical predictors A numeric vector holding the column of each predictor treated as categorical, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 28 ClassificationGAM.ClassNames # name: # type: sq_string # elements: 1 # length: 345 ClassificationGAM: property ClassNames Names of classes in the response variable An array of unique values of the response variable Y , which has the same data types as the data in Y . This property is read-only. ClassNames can have any of the following datatypes: Cell array of character vectors Character array Logical vector Numeric vector # name: # type: sq_string # elements: 1 # length: 41 Names of classes in the response variable # name: # type: sq_string # elements: 1 # length: 35 ClassificationGAM.ClassificationGAM # name: # type: sq_string # elements: 1 # length: 3603 statistics: obj = ClassificationGAM ( X , Y ) statistics: obj = ClassificationGAM (…, name , value ) Create a ClassificationGAM class object containing a generalized additive classification model. obj = ClassificationGAM ( X , Y ) returns a ClassificationGAM object, with X as the predictor data and Y containing the class labels of observations in X . X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. X will be used to train the GAM model. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y can contain any type of categorical data. Y must have the same number of rows as X . obj = ClassificationGAM (…, name , value ) returns a ClassificationGAM object with parameters specified by the following name , value paired input arguments: Name Value 'PredictorNames' A cell array of character vectors specifying the names of the predictors. The length of this array must match the number of columns in X . 'ResponseName' A character vector specifying the name of the response variable. 'ClassNames' Names of the classes in the class labels, Y , used for fitting the GAM model. ClassNames are of the same type as the class labels in Y . 'Cost' An N×R numeric matrix containing misclassification cost for the corresponding instances in X , where R is the number of unique categories in Y . If an instance is correctly classified into its category the cost is calculated to be 1, otherwise 0. The cost matrix can be altered by using Mdl .cost = somecost . By default, its value is cost = ones (rows (X), numel (unique (Y))) . 'Prior' A numeric vector specifying the prior probabilities for each class. The order of the elements in Prior corresponds to the order of the classes in ClassNames . Alternatively, you can specify 'empirical' to use the empirical class probabilities or 'uniform' to assume equal class probabilities. 'ScoreTransform' A user-defined function handle or a character vector specifying one of the following builtin functions specifying the transformation applied to predicted classification scores. Supported values include 'doublelogit' , 'invlogit' , 'ismax' , 'logit' , 'none' , 'identity' , 'sign' , 'symmetric' , 'symmetricismax' , and 'symmetriclogit' . 'Formula' (spline option) A character vector specifying the model formula in the form 'Y ~ terms' where Y represents the response variable and terms specifies the predictor variables and interaction terms. 'Interactions' A logical matrix, a positive integer scalar, or the string 'all' for defining the interactions between predictor variables. 'Knots' (spline option) A scalar or row vector specifying the number of knots for each predictor variable in the spline fitting. 'Order' (spline option) A scalar or row vector specifying the order of the spline for each predictor variable. 'DoF' (spline option) A scalar or row vector specifying the degrees of freedom for each predictor variable in the spline fitting. 'LearningRate' (spline option) A scalar value between 0 and 1 specifying the learning rate used in the gradient boosting algorithm. 'NumIterations' (spline option) A positive integer specifying the maximum number of iterations for the gradient boosting algorithm. A row marked (spline option) belongs to the spline engine and requires 'FitMethod', 'splines' ; passing one under the default boosted-tree engine is an error rather than being ignored. The boosted-tree engine’s own options are documented under fitcgam . See also: fitcgam # name: # type: sq_string # elements: 1 # length: 95 Create a ClassificationGAM class object containing a generalized additive classification model. # name: # type: sq_string # elements: 1 # length: 22 ClassificationGAM.Cost # name: # type: sq_string # elements: 1 # length: 1196 ClassificationGAM: property Cost Cost of Misclassification A square matrix specifying the cost of misclassification of a point. Cost(i,j) is the cost of classifying a point into class j if its true class is i (that is, the rows correspond to the true class and the columns correspond to the predicted class). The order of the rows and columns in Cost corresponds to the order of the classes in ClassNames . The number of rows and columns in Cost is the number of unique classes in the response. By default, Cost(i,j) = 1 if i != j , and Cost(i,j) = 0 if i = j . In other words, the cost is 0 for correct classification and 1 for incorrect classification. Add or change the Cost property using dot notation as in: obj .Cost = costMatrix A cost may also be given as a struct with the fields ClassNames and ClassificationCosts , which names the order its own matrix is written in. That matrix is permuted into the order of ClassNames above, so a caller need not know which order the classes were sorted into. It must name every class. A cost must be floating point, not sparse, not complex, non-negative and zero down its diagonal, and must hold no NaN or Inf . A single is widened to double . # name: # type: sq_string # elements: 1 # length: 25 Cost of Misclassification # name: # type: sq_string # elements: 1 # length: 21 ClassificationGAM.DoF # name: # type: sq_string # elements: 1 # length: 207 ClassificationGAM: property DoF Degrees of freedom for spline fitting A scalar or row vector specifying the degrees of freedom for each predictor variable in the spline fitting. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Degrees of freedom for spline fitting # name: # type: sq_string # elements: 1 # length: 40 ClassificationGAM.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 294 ClassificationGAM: property ExpandedPredictorNames Names of the expanded predictor variables A cell array of character vectors naming the predictors as the model sees them. It matches PredictorNames unless a categorical predictor was expanded into dummy variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 41 Names of the expanded predictor variables # name: # type: sq_string # elements: 1 # length: 27 ClassificationGAM.FitMethod # name: # type: sq_string # elements: 1 # length: 717 ClassificationGAM: property FitMethod Which engine fitted the model A character vector, either 'boostedtrees' or 'splines' . The default is 'boostedtrees' , which is the scheme MATLAB’s generalized additive model uses and the one the tree-shaped properties above describe. 'splines' selects the penalised-spline engine instead, which is an Octave extension with no MATLAB counterpart. It is the scheme this class fitted before version 1.9.0, and it is kept because a smooth additive fit is a genuinely different and often better answer than a staircase of stumps. The two engines take different arguments and an argument meant for one is refused by the other rather than ignored. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Which engine fitted the model # name: # type: sq_string # elements: 1 # length: 25 ClassificationGAM.Formula # name: # type: sq_string # elements: 1 # length: 273 ClassificationGAM: property Formula Model specification formula A character vector specifying the model formula in the form 'Y ~ terms' where Y represents the response variable and terms specifies the predictor variables and interaction terms. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Model specification formula # name: # type: sq_string # elements: 1 # length: 51 ClassificationGAM.HyperparameterOptimizationResults # name: # type: sq_string # elements: 1 # length: 360 ClassificationGAM: property HyperparameterOptimizationResults Results of the hyperparameter optimization Always empty. It is declared for MATLAB compatibility, where it holds what an automatic search over the hyperparameters found. This class fits the parameters it is given and runs no such search, so there is nothing to report. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Results of the hyperparameter optimization # name: # type: sq_string # elements: 1 # length: 27 ClassificationGAM.IntMatrix # name: # type: sq_string # elements: 1 # length: 557 ClassificationGAM: property IntMatrix Every term the model fits A logical matrix with one row per term and one column per predictor, true wherever the term multiplies that predictor. A row naming one predictor is a main effect, two an interaction, and three or more a higher-order term. This property is read-only. It is the complete record, where Interactions reports only the two-way terms, in the form MATLAB reports them. It is also the form the 'Interactions' option takes back, so passing it to the constructor rebuilds a model over the same terms. # name: # type: sq_string # elements: 1 # length: 25 Every term the model fits # name: # type: sq_string # elements: 1 # length: 30 ClassificationGAM.Interactions # name: # type: sq_string # elements: 1 # length: 598 ClassificationGAM: property Interactions Two-way interaction terms of the fitted model A Kx2 matrix of predictor index pairs, one row per two-way term the model carries, and zeros (0, 2) when it carries none. It reports what was fitted rather than what was asked for, so a count of terms, 'all' , a logical matrix and a formula all leave the same kind of value behind. This property is read-only. A main effect names one predictor and a higher-order term names three or more, and neither has a two-column form, so neither appears here. IntMatrix remains the complete record of every term fitted. # name: # type: sq_string # elements: 1 # length: 45 Two-way interaction terms of the fitted model # name: # type: sq_string # elements: 1 # length: 27 ClassificationGAM.Intercept # name: # type: sq_string # elements: 1 # length: 197 ClassificationGAM: property Intercept Intercept of the fitted model A numeric scalar, the log-odds of the response mean, which every additive term is measured against. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Intercept of the fitted model # name: # type: sq_string # elements: 1 # length: 23 ClassificationGAM.Knots # name: # type: sq_string # elements: 1 # length: 193 ClassificationGAM: property Knots Knots for spline fitting A scalar or row vector specifying the number of knots for each predictor variable in the spline fitting. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Knots for spline fitting # name: # type: sq_string # elements: 1 # length: 30 ClassificationGAM.LearningRate # name: # type: sq_string # elements: 1 # length: 207 ClassificationGAM: property LearningRate Learning rate for gradient boosting A scalar value between 0 and 1 specifying the learning rate used in the gradient boosting algorithm. This property is read-only. # name: # type: sq_string # elements: 1 # length: 35 Learning rate for gradient boosting # name: # type: sq_string # elements: 1 # length: 33 ClassificationGAM.ModelParameters # name: # type: sq_string # elements: 1 # length: 818 ClassificationGAM: property ModelParameters Parameters the model was fitted with A structure holding the fitting parameters. Under the boosted-tree engine it carries MATLAB’s own fields: NumPrint , MaxPValue , InitialLearnRateForPredictors , InitialLearnRateForInteractions , NumTreesPerPredictor , NumTreesPerInteraction , MaxNumSplitsPerPredictor , MaxNumSplitsPerInteraction , VerbosityLevel , Interactions , Version , Method and Type . Interactions here is the request as it was made, a count or 'all' , where the Interactions property of the model is the pairs actually selected. Under the spline engine it describes that scheme instead, carrying Knots , Order , DoF , Formula , Interactions , LearningRate and NumIterations , since none of the tree vocabulary applies to it. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Parameters the model was fitted with # name: # type: sq_string # elements: 1 # length: 27 ClassificationGAM.ModelwInt # name: # type: sq_string # elements: 1 # length: 268 ClassificationGAM: property ModelwInt Model parameters with interactions A structure containing the parameters of the model that includes interaction terms. This model extends the base model by adding interaction terms between predictors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Model parameters with interactions # name: # type: sq_string # elements: 1 # length: 31 ClassificationGAM.NumIterations # name: # type: sq_string # elements: 1 # length: 200 ClassificationGAM: property NumIterations Maximum number of iterations A positive integer specifying the maximum number of iterations for the gradient boosting algorithm. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Maximum number of iterations # name: # type: sq_string # elements: 1 # length: 33 ClassificationGAM.NumObservations # name: # type: sq_string # elements: 1 # length: 230 ClassificationGAM: property NumObservations Number of observations A positive integer value specifying the number of observations in the training dataset used for training the ClassificationGAM model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 31 ClassificationGAM.NumPredictors # name: # type: sq_string # elements: 1 # length: 224 ClassificationGAM: property NumPredictors Number of predictors A positive integer value specifying the number of predictors in the training dataset used for training the ClassificationGAM model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 23 ClassificationGAM.Order # name: # type: sq_string # elements: 1 # length: 174 ClassificationGAM: property Order Order of spline fitting A scalar or row vector specifying the order of the spline for each predictor variable. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Order of spline fitting # name: # type: sq_string # elements: 1 # length: 39 ClassificationGAM.PairDetectionBinEdges # name: # type: sq_string # elements: 1 # length: 487 ClassificationGAM: property PairDetectionBinEdges Bin edges used to detect interactions A cell array with one row vector per predictor, holding the coarse cut points the residuals of the predictor phase were laid on while pairs were being tested. The grid is eight equal-frequency bins whatever the sample size, as MATLAB’s is. It is empty when the model carries no interaction terms, and empty throughout under the spline engine, which does not bin. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Bin edges used to detect interactions # name: # type: sq_string # elements: 1 # length: 32 ClassificationGAM.PredictorNames # name: # type: sq_string # elements: 1 # length: 257 ClassificationGAM: property PredictorNames Names of predictor variables A cell array of character vectors specifying the names of the predictor variables. The names are in the order in which they appear in the training dataset. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Names of predictor variables # name: # type: sq_string # elements: 1 # length: 23 ClassificationGAM.Prior # name: # type: sq_string # elements: 1 # length: 562 ClassificationGAM: property Prior Prior probability for each class A 2-element numeric vector specifying the prior probabilities for each class. The order of the elements in Prior corresponds to the order of the classes in ClassNames . This property is read-only. Specified as a row vector with one entry per class, in the order of ClassNames , and rescaled to sum to one. It may be given as 'empirical' , 'uniform' , a numeric vector, or a structure with ClassNames and ClassProbs fields, which assigns each probability by class name rather than by position. # name: # type: sq_string # elements: 1 # length: 32 Prior probability for each class # name: # type: sq_string # elements: 1 # length: 38 ClassificationGAM.ReasonForTermination # name: # type: sq_string # elements: 1 # length: 539 ClassificationGAM: property ReasonForTermination Why each fitting phase stopped A structure with the fields PredictorTrees and InteractionTrees , each a character vector saying why that phase of the fit ended: that it trained the trees it was asked for, or that it could no longer improve the model. A phase that never ran reports an empty character vector, which is what a model with no interaction terms shows for the second field. It is empty under the spline engine, which has no tree budget to exhaust. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 Why each fitting phase stopped # name: # type: sq_string # elements: 1 # length: 30 ClassificationGAM.ResponseName # name: # type: sq_string # elements: 1 # length: 161 ClassificationGAM: property ResponseName Response variable name A character vector specifying the name of the response variable Y . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 26 ClassificationGAM.RowsUsed # name: # type: sq_string # elements: 1 # length: 386 ClassificationGAM: property RowsUsed Rows used for fitting A logical column vector with the same length as the observations in the original predictor data X , true for each row that was used for fitting the ClassificationGAM model. It is empty, [] , when every observation was used, so a non-empty value means that rows holding missing values were dropped. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Rows used for fitting # name: # type: sq_string # elements: 1 # length: 32 ClassificationGAM.ScoreTransform # name: # type: sq_string # elements: 1 # length: 1350 ClassificationGAM: property ScoreTransform Transformation function for classification scores Specified as a function handle for transforming the classification scores. Add or change the ScoreTransform property using dot notation as in: obj .ScoreTransform = 'function_name' obj .ScoreTransform = @function_handle When specified as a character vector, it can be any of the following built-in functions. Nevertheless, the ScoreTransform property always stores their function handle equivalent. Value Description 'doublelogit' 1 ./ (1 + exp (-2 × x)) 'invlogit' log (x ./ (1 - x)) 'ismax' Sets the score for the class with the largest score to 1, and for all other classes to 0 'logit' 1 ./ (1 + exp (-x)) 'none' x (no transformation) 'identity' x (no transformation) 'sign' -1 for x < 0, 0 for x = 0, 1 for x > 0 'symmetric' 2 × x - 1 'symmetricismax' Sets the score for the class with the largest score to 1, and for all other classes to -1 'symmetriclogit' 2 ./ (1 + exp (-x)) - 1 The default is 'logit' , as in MATLAB. This model’s raw score is a log-odds, reported as the pair [-f, f] whose two columns sum to zero, and the transform is what turns it into the posterior probabilities that sum to one. Every transform therefore composes on the log-odds and not on the probabilities, so 'none' returns the log-odds themselves. # name: # type: sq_string # elements: 1 # length: 49 Transformation function for classification scores # name: # type: sq_string # elements: 1 # length: 27 ClassificationGAM.TreeModel # name: # type: sq_string # elements: 1 # length: 778 ClassificationGAM: property TreeModel The fitted shape functions and interaction surfaces A structure holding what the boosted-tree engine fitted, with fields ShapeValues , one column vector per predictor giving that predictor’s contribution in each of its bins, PairValues , one matrix per selected pair, and Pairs , the predictor indices those matrices belong to. A shape function is a step function, so these are the whole of the fit however many trees produced them. MATLAB exposes no equivalent: it reports the bin edges but never the values on them, so its shape functions can only be reached through predict . This property is an Octave extension, and it is empty under the spline engine, whose fit lives in BaseModel and ModelwInt . This property is read-only. # name: # type: sq_string # elements: 1 # length: 51 The fitted shape functions and interaction surfaces # name: # type: sq_string # elements: 1 # length: 19 ClassificationGAM.W # name: # type: sq_string # elements: 1 # length: 355 ClassificationGAM: property W Observation weights A numeric column vector with one entry per observation used for training, normalised to sum to one. This property is read-only. Each class carries its prior spread evenly over its own observations, so an observation of a class weighs Prior for that class divided by the number of observations it holds. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 19 ClassificationGAM.X # name: # type: sq_string # elements: 1 # length: 232 ClassificationGAM: property X Predictor data A numeric matrix containing the unstandardized predictor data. Each column of X represents one predictor (variable), and each row represents one observation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Predictor data # name: # type: sq_string # elements: 1 # length: 19 ClassificationGAM.Y # name: # type: sq_string # elements: 1 # length: 309 ClassificationGAM: property Y Class labels Specified as a logical or numeric column vector, or as a character array or a cell array of character vectors with the same number of rows as the predictor data. Each row in Y is the observed class label for the corresponding row in X . This property is read-only. # name: # type: sq_string # elements: 1 # length: 12 Class labels # name: # type: sq_string # elements: 1 # length: 33 ClassificationGAM.addInteractions # name: # type: sq_string # elements: 1 # length: 1087 ClassificationGAM: obj = addInteractions ( obj , interactions ) Add interaction terms to a fitted model. obj = addInteractions ( obj , interactions ) fits the interaction terms named by interactions on top of the terms the model already carries and returns the updated model. The univariate fit is left alone, so predict with 'IncludeInteractions' set false answers exactly as it answered before. interactions takes the forms the constructor’s 'Interactions' option takes: a nonnegative integer count of terms, a logical matrix with a column per predictor, or 'all' . A model already carrying interaction terms is not extended, which is what MATLAB refuses too. A model fitted from a 'Formula' names every term it has, interactions among them, and is refused for the same reason. Which terms a count selects is this implementation’s own: they are taken in the order nchoosek lists the pairs, where MATLAB ranks them by how much each contributes. The constructor’s option chooses the same way, so the two agree with each other. See also: fitcgam, ClassificationGAM # name: # type: sq_string # elements: 1 # length: 40 Add interaction terms to a fitted model. # name: # type: sq_string # elements: 1 # length: 25 ClassificationGAM.compact # name: # type: sq_string # elements: 1 # length: 239 ClassificationGAM: CVMdl = compact ( obj ) Create a CompactClassificationGAM object. CVMdl = compact ( obj ) creates a compact version of the ClassificationGAM object, obj . See also: fitcgam, ClassificationGAM, CompactClassificationGAM # name: # type: sq_string # elements: 1 # length: 41 Create a CompactClassificationGAM object. # name: # type: sq_string # elements: 1 # length: 26 ClassificationGAM.crossval # name: # type: sq_string # elements: 1 # length: 1061 ClassificationGAM: CVMdl = crossval ( obj ) ClassificationGAM: CVMdl = crossval (…, name , value ) Cross Validate a Generalized Additive Model classification object. CVMdl = crossval ( obj ) returns a cross-validated model object, CVMdl , from a trained model, obj , using 10-fold cross-validation by default. CVMdl = crossval ( obj , name , value ) specifies additional name-value pair arguments to customize the cross-validation process. Name Value 'KFold' Specify the number of folds to use in k-fold cross-validation. "KFold", k , where k is an integer greater than 1. 'Holdout' Specify the fraction of the data to hold out for testing. "Holdout", p , where p is a scalar in the range (0,1) . 'Leaveout' Specify whether to perform leave-one-out cross-validation. "Leaveout", Value , where Value is ’on’ or ’off’. 'CVPartition' Specify a cvpartition object used for cross-validation. "CVPartition", cv , where isa ( cv , "cvpartition") = 1. See also: fitcgam, ClassificationGAM, cvpartition, ClassificationPartitionedModel # name: # type: sq_string # elements: 1 # length: 66 Cross Validate a Generalized Additive Model classification object. # name: # type: sq_string # elements: 1 # length: 22 ClassificationGAM.edge # name: # type: sq_string # elements: 1 # length: 399 ClassificationGAM: e = edge ( obj , X , Y ) ClassificationGAM: e = edge (…, "Weights" , w ) Classification edge of a generalized additive model. e = edge ( obj , X , Y ) returns the mean of the classification margins over the rows of X . e = edge (…, "Weights" , w ) takes the weighted mean instead, with one weight per row of X . See also: ClassificationGAM, margin, loss, predict # name: # type: sq_string # elements: 1 # length: 52 Classification edge of a generalized additive model. # name: # type: sq_string # elements: 1 # length: 22 ClassificationGAM.loss # name: # type: sq_string # elements: 1 # length: 898 ClassificationGAM: L = loss ( obj , X , Y ) ClassificationGAM: L = loss (…, name , value ) Classification loss of a generalized additive model. L = loss ( obj , X , Y ) returns the loss of the model on the rows of X against the true labels Y . L = loss (…, name , value ) accepts the following name-value pairs: "LossFun" selects the loss. Supported values are "mincost" , the default, "binodeviance" , "classifcost" , "classiferror" , "exponential" , "hinge" , "logit" and "quadratic" . "mincost" assigns each observation to the class of least expected cost and charges what that assignment costs, so it reads the scores as a posterior, which is what this model returns; "classifcost" charges what the model’s own prediction costs. "Weights" holds one weight per row of X , normalised to sum to one before it is applied. See also: ClassificationGAM, margin, edge, predict # name: # type: sq_string # elements: 1 # length: 52 Classification loss of a generalized additive model. # name: # type: sq_string # elements: 1 # length: 24 ClassificationGAM.margin # name: # type: sq_string # elements: 1 # length: 430 ClassificationGAM: m = margin ( obj , X , Y ) Classification margin of a generalized additive model. m = margin ( obj , X , Y ) returns a column vector holding, for each row of X , the score the model gives its true class in Y less the score it gives the other class. A positive margin means the observation is classified correctly, and the larger it is the more confidently so. See also: ClassificationGAM, edge, loss, predict # name: # type: sq_string # elements: 1 # length: 54 Classification margin of a generalized additive model. # name: # type: sq_string # elements: 1 # length: 25 ClassificationGAM.predict # name: # type: sq_string # elements: 1 # length: 1117 ClassificationGAM: label = predict ( obj , XC ) ClassificationGAM: [ label , score ] = predict ( obj , XC ) ClassificationGAM: [ label , score ] = predict (…, 'IncludeInteractions' , includeInteractions ) Predict labels for new data using the Generalized Additive Model (GAM) stored in a ClassificationGAM object. label = predict ( obj , XC ) returns the predicted labels for the data in XC based on the model stored in the ClassificationGAM object, obj . [ label , score ] = predict ( obj , XC ) also returns score , which contains the predicted class scores or posterior probabilities for each observation. [ label , score ] = predict ( obj , XC , 'IncludeInteractions', includeInteractions ) allows you to specify whether interaction terms should be included when making predictions. obj must be a ClassificationGAM class object. XC must be an M×P numeric matrix where each row is an observation and each column corresponds to a predictor variable. includeInteractions is a logical scalar indicating whether to include interaction terms in the predictions. See also: ClassificationGAM, fitcgam # name: # type: sq_string # elements: 1 # length: 108 Predict labels for new data using the Generalized Additive Model (GAM) stored in a ClassificationGAM object. # name: # type: sq_string # elements: 1 # length: 27 ClassificationGAM.resubEdge # name: # type: sq_string # elements: 1 # length: 150 ClassificationGAM: e = resubEdge ( obj ) Classification edge of a generalized additive model on its training data. See also: ClassificationGAM, edge # name: # type: sq_string # elements: 1 # length: 73 Classification edge of a generalized additive model on its training data. # name: # type: sq_string # elements: 1 # length: 27 ClassificationGAM.resubLoss # name: # type: sq_string # elements: 1 # length: 209 ClassificationGAM: L = resubLoss ( obj ) ClassificationGAM: L = resubLoss (…, name , value ) Classification loss of a generalized additive model on its training data. See also: ClassificationGAM, loss # name: # type: sq_string # elements: 1 # length: 73 Classification loss of a generalized additive model on its training data. # name: # type: sq_string # elements: 1 # length: 29 ClassificationGAM.resubMargin # name: # type: sq_string # elements: 1 # length: 156 ClassificationGAM: m = resubMargin ( obj ) Classification margin of a generalized additive model on its training data. See also: ClassificationGAM, margin # name: # type: sq_string # elements: 1 # length: 75 Classification margin of a generalized additive model on its training data. # name: # type: sq_string # elements: 1 # length: 30 ClassificationGAM.resubPredict # name: # type: sq_string # elements: 1 # length: 321 ClassificationGAM: label = resubPredict ( obj ) ClassificationGAM: [ label , score ] = resubPredict ( obj ) Classify the training data with the generalized additive model it was fitted on. label = resubPredict ( obj ) is predict applied to the observations the model was fitted on. See also: ClassificationGAM, predict # name: # type: sq_string # elements: 1 # length: 80 Classify the training data with the generalized additive model it was fitted on. # name: # type: sq_string # elements: 1 # length: 24 ClassificationGAM.resume # name: # type: sq_string # elements: 1 # length: 939 ClassificationGAM: Mdl = resume ( obj , numTrees ) Resume training a generalized additive model. Mdl = resume ( obj , numTrees ) adds numTrees more trees to obj and returns the result. The original model is not modified. Training continues in the phase that ran last, which is what MATLAB does: a model carrying interaction terms gains interaction trees and its predictor shape functions are left alone, while a model without them gains predictor trees. A round starts at its initial learning rate whatever its number, so the model this returns is the model a single fit of the combined budget would have produced. numTrees must be a positive integer scalar. Resuming raises where there is nothing left to gain, rather than returning the model unchanged, and it is not available under 'FitMethod', 'splines' : a backfit that has converged to its tolerance has no budget to extend. See also: ClassificationGAM, fitcgam, addInteractions # name: # type: sq_string # elements: 1 # length: 45 Resume training a generalized additive model. # name: # type: sq_string # elements: 1 # length: 27 ClassificationGAM.savemodel # name: # type: sq_string # elements: 1 # length: 472 ClassificationGAM: savemodel ( obj , filename ) Save a ClassificationGAM object. savemodel ( obj , filename ) saves each property of a ClassificationGAM object into an Octave binary file, the name of which is specified in filename , along with an extra variable, which defines the type classification object these variables constitute. Use loadmodel in order to load a classification object into Octave’s workspace. See also: loadmodel, fitcgam, ClassificationGAM # name: # type: sq_string # elements: 1 # length: 32 Save a ClassificationGAM object. # name: # type: sq_string # elements: 1 # length: 17 ClassificationKNN # name: # type: sq_string # elements: 1 # length: 707 statistics: ClassificationKNN K-nearest neighbors classification The ClassificationKNN class implements a K-nearest neighbor classifier object, which can predict responses for new data using the predict method. The implemented algorithm allows you choose a range of different distance metrics, the number of nearest neighbors, as well as the searching algorithm. The K-nearest neighbors (k-NN) classifier is a simple, non-parametric machine learning algorithm used for classification tasks. It classifies a data point based on the majority class of its k closest neighbors in the feature space. Create a ClassificationKNN object by using the fitcknn function or the class constructor. See also: fitcknn # name: # type: sq_string # elements: 1 # length: 34 K-nearest neighbors classification # name: # type: sq_string # elements: 1 # length: 26 ClassificationKNN.BinEdges # name: # type: sq_string # elements: 1 # length: 365 ClassificationKNN: property BinEdges Bin edges of the predictors A cell array with one entry per predictor, holding that predictor’s bin edges where the learner discretized it before fitting. It is empty here and stays empty: this learner fits the predictors as they are, and MATLAB’s reports an empty cell for it as well. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Bin edges of the predictors # name: # type: sq_string # elements: 1 # length: 27 ClassificationKNN.BreakTies # name: # type: sq_string # elements: 1 # length: 949 ClassificationKNN: property BreakTies Tie-breaking algorithm A character vector specifying the tie-breaking algorithm used by the predict method, when multiple classes have the same smallest cost. It can be one of the following: 'smallest' (default), which favors the class with the smallest index among the tied groups, i.e. the one that appears first in the training labelled data. 'nearest' , which favors the class with the nearest neighbor among the tied groups, i.e. the class with the closest member point according to the distance metric used. 'random' , which randomly picks one class among the tied groups. The tie-breaking algorithm is only used when IncludeTies is false . Change the BreakTies property using dot notation as in: obj .BreakTies = algorithm This property may be assigned after fitting. It decides the label when two classes hold the same weight among the neighbours, and it applies whether or not IncludeTies is set. # name: # type: sq_string # elements: 1 # length: 22 Tie-breaking algorithm # name: # type: sq_string # elements: 1 # length: 28 ClassificationKNN.BucketSize # name: # type: sq_string # elements: 1 # length: 326 ClassificationKNN: property BucketSize Maximum data points in each node A positive integer scalar specifying the maximum number of data points in the leaf node of the Kd-tree. BucketSize only applies when the NSMethod property is 'kdtree' . Change the BucketSize property using dot notation as in: obj .BucketSize = maxnum # name: # type: sq_string # elements: 1 # length: 32 Maximum data points in each node # name: # type: sq_string # elements: 1 # length: 27 ClassificationKNN.CacheSize # name: # type: sq_string # elements: 1 # length: 663 ClassificationKNN: property CacheSize Size of the Gram matrix cache A positive scalar giving the cache size in megabytes, 1000 by default. Change the CacheSize property using dot notation as in: obj .CacheSize = newCacheSize This property is stored and reported for compatibility and does not affect the fit or any prediction . A nearest-neighbour model keeps no Gram matrix to cache: it holds the training data and computes each distance when asked. Assigning it changes nothing but the value read back. MATLAB carries the same property and hides it from properties , where this package reports it, so that a value a user may set is a value a user can find. # name: # type: sq_string # elements: 1 # length: 29 Size of the Gram matrix cache # name: # type: sq_string # elements: 1 # length: 39 ClassificationKNN.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 229 ClassificationKNN: property CategoricalPredictors Indices of the categorical predictors A numeric vector of column indices into X naming the predictors treated as categorical, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 28 ClassificationKNN.ClassNames # name: # type: sq_string # elements: 1 # length: 345 ClassificationKNN: property ClassNames Names of classes in the response variable An array of unique values of the response variable Y , which has the same data types as the data in Y . This property is read-only. ClassNames can have any of the following datatypes: Cell array of character vectors Character array Logical vector Numeric vector # name: # type: sq_string # elements: 1 # length: 41 Names of classes in the response variable # name: # type: sq_string # elements: 1 # length: 35 ClassificationKNN.ClassificationKNN # name: # type: sq_string # elements: 1 # length: 5143 statistics: obj = ClassificationKNN ( X , Y ) statistics: obj = ClassificationKNN (…, name , value ) Create a ClassificationKNN class object containing a k-Nearest Neighbor classification model. obj = ClassificationKNN ( X , Y ) returns a ClassificationKNN object, with X as the predictor data and Y containing the class labels of observations in X . X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. X will be used to train the kNN model. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y can contain any type of categorical data. Y must have same numbers of Rows as X . obj = ClassificationKNN (…, name , value ) returns a ClassificationKNN object with parameters specified by the following name , value paired input arguments: Name Value 'PredictorNames' A cell array of character vectors specifying the names of the predictors. The length of this array must match the number of columns in X . 'ResponseName' A character vector specifying the name of the response variable. 'ClassNames' Names of the classes in the class labels, Y , used for fitting the GAM model. ClassNames are of the same type as the class labels in Y . 'Cost' An N×R numeric matrix containing misclassification cost for the corresponding instances in X , where R is the number of unique categories in Y . If an instance is correctly classified into its category the cost is calculated to be 1, otherwise 0. The cost matrix can be altered by using Mdl .cost = somecost . By default, its value is cost = ones (rows (X), numel (unique (Y))) . 'Prior' A numeric vector specifying the prior probabilities for each class. The order of the elements in Prior corresponds to the order of the classes in ClassNames . Alternatively, you can specify 'empirical' to use the empirical class probabilities or 'uniform' to assume equal class probabilities. 'ScoreTransform' A user-defined function handle or a character vector specifying one of the following builtin functions specifying the transformation applied to predicted classification scores. Supported values include 'doublelogit' , 'invlogit' , 'ismax' , 'logit' , 'none' , 'identity' , 'sign' , 'symmetric' , 'symmetricismax' , and 'symmetriclogit' . 'BreakTies' A character vector specifying the tie-breaking algorithm used by predict method, when multiple classes have the same smallest cost. Available options are 'smallest' (default), which uses the smallest index among tied groups, 'nearest' , which uses the class with the nearest neighbor among tied groups, and 'random' , which randomly selects one of the tied groups. 'NumNeighbors' A positive integer value that specifies the number of nearest neighbors to be found in the kNN search algorithm for classifying each point during prediction. By default, it is 1. 'Distance' Any valid distance metric supported by the pdist2 function. Note that the allowable distance metrics depend on the selected nearest neighbor search method. 'DistanceWeight' Either a distance weighting function, specified either as a function handle, which accepts a matrix of nonnegative distances and returns a matrix the same size containing nonnegative distance weights, or a character vector with one of the following values: 'equal' , which corresponds to no weighting; 'inverse' , which corresponds to a weight equal to 1/distance ; 'squaredinverse' , which corresponds to a weight equal to 1/distance^2 . 'Cov' A square matrix with the same number of columns X specifying the covariance matrix for computing the mahalanobis distance. This must be a positive definite matrix matching. This argument is only valid when the selected distance metric is 'mahalanobis' . 'Exponent' A positive scalar (usually an integer) specifying the Minkowski distance exponent. This argument is only valid when the selected distance metric is 'minkowski' . By default, it is 2. 'Scale' A nonnegative numeric vector specifying the scale parameters for the standardized Euclidean distance. The vector length must be equal to the number of columns in X . This argument is only valid when the selected distance metric is 'seuclidean' , in which case each coordinate of X is scaled by the corresponding element of 'scale' , as is each query point in Y . By default, the scale parameter is the standard deviation of each coordinate in X . If a variable in X is constant, i.e. zero variance, this value is forced to 1 to avoid division by zero. This is the equivalent of this variable not being standardized. 'NSMethod' A character vector specifying the nearest neighbor search method used by knnsearch , which can be 'kdtree' or 'exhaustive' . See knnsearch for more information about default values and allowable distance metrics for each search method. 'BucketSize' A positive integer value specifying the maximum number of data points in the leaf node of the Kd-tree. This argument is meaningful only when the selected nearest neighbor search method is 'kdtree' . By default, it is 50. See also: fitcknn, knnsearch, rangesearch, pdist2 # name: # type: sq_string # elements: 1 # length: 93 Create a ClassificationKNN class object containing a k-Nearest Neighbor classification model. # name: # type: sq_string # elements: 1 # length: 22 ClassificationKNN.Cost # name: # type: sq_string # elements: 1 # length: 1196 ClassificationKNN: property Cost Cost of Misclassification A square matrix specifying the cost of misclassification of a point. Cost(i,j) is the cost of classifying a point into class j if its true class is i (that is, the rows correspond to the true class and the columns correspond to the predicted class). The order of the rows and columns in Cost corresponds to the order of the classes in ClassNames . The number of rows and columns in Cost is the number of unique classes in the response. By default, Cost(i,j) = 1 if i != j , and Cost(i,j) = 0 if i = j . In other words, the cost is 0 for correct classification and 1 for incorrect classification. Add or change the Cost property using dot notation as in: obj .Cost = costMatrix A cost may also be given as a struct with the fields ClassNames and ClassificationCosts , which names the order its own matrix is written in. That matrix is permuted into the order of ClassNames above, so a caller need not know which order the classes were sorted into. It must name every class. A cost must be floating point, not sparse, not complex, non-negative and zero down its diagonal, and must hold no NaN or Inf . A single is widened to double . # name: # type: sq_string # elements: 1 # length: 25 Cost of Misclassification # name: # type: sq_string # elements: 1 # length: 31 ClassificationKNN.DistParameter # name: # type: sq_string # elements: 1 # length: 1265 ClassificationKNN: property DistParameter Parameter for distance metric A positive definite covariance matrix, a positive scalar, or a vector of positive scale values specifying the parameter for the corresponding distance metric as shown below: 'mahalanobis' accepts a positive definite covariance matrix. 'minkowski' accepts a positive scalar as the Minkowski distance exponent. 'seuclidean' accepts a vector of positive scale values of equal length as the number of predictors in X . For any other distance metric, DistParameter is empty ([]) . Change the DistParameter property using dot notation as in: obj .DistParameter = distParam This property may be assigned after fitting, but only under the three metrics that carry one: 'minkowski' , 'seuclidean' and 'mahalanobis' . Under any other metric there is nothing for it to mean and the assignment is refused. Deviation from MATLAB. A 'seuclidean' scale of zeros is refused here. MATLAB accepts it, then warns from inside its distance routine at predict time and answers anyway, which contradicts its own message that the scale must hold positive values. A zero scale divides that predictor by nothing, so it is rejected where it is given rather than surfacing later as a warning attached to an answer. # name: # type: sq_string # elements: 1 # length: 29 Parameter for distance metric # name: # type: sq_string # elements: 1 # length: 26 ClassificationKNN.Distance # name: # type: sq_string # elements: 1 # length: 1069 ClassificationKNN: property Distance Distance metric A character vector specifying the distance metric used by the neighbor-searcher method, or a function handle to a custom distance function. See the available distance metrics in knnsearch for more info. A custom distance function must have the form D2 = distfun ( ZI , ZJ ) , where ZI is a 1×N vector containing one row of the predictor data, ZJ is an M2×N matrix containing multiple rows of the predictor data, and D2 is an M2×1 vector of distances whose k -th element is the distance between the observations ZI and ZJ ( k ,:) . A custom distance function carries no DistParameter . Change the Distance property using dot notation as in: obj .Distance = newDistance This property may be assigned after fitting. NSMethod is read-only and constrains it: a 'kdtree' model takes 'euclidean' , 'cityblock' , 'chebychev' and 'minkowski' only, and never a function handle. Assigning a different metric recomputes DistParameter , since a parameter belonging to one metric means nothing under another. # name: # type: sq_string # elements: 1 # length: 15 Distance metric # name: # type: sq_string # elements: 1 # length: 32 ClassificationKNN.DistanceWeight # name: # type: sq_string # elements: 1 # length: 725 ClassificationKNN: property DistanceWeight Distance weighting function A character vector or a function handle specifying the distance weighting function, which can be any of the following values: 'equal' , which corresponds to @(d) d . 'inverse' , which corresponds to @(d) 1/d . 'squaredinverse' , which corresponds to @(d) 1/d.^2 . @fcn , which is a function handle that accepts a matrix of nonnegative distances, and returns a matrix the same size containing nonnegative distance weights. Change the DistanceWeight property using dot notation as in: obj .DistanceWeight = newDistanceWeight A character vector naming the weight, or the func2str form of a supplied handle. This property may be assigned after fitting. # name: # type: sq_string # elements: 1 # length: 27 Distance weighting function # name: # type: sq_string # elements: 1 # length: 40 ClassificationKNN.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 262 ClassificationKNN: property ExpandedPredictorNames Names of the predictors as the model expanded them A cell array of character vectors. It matches PredictorNames unless a categorical predictor was expanded into indicator variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 50 Names of the predictors as the model expanded them # name: # type: sq_string # elements: 1 # length: 51 ClassificationKNN.HyperparameterOptimizationResults # name: # type: sq_string # elements: 1 # length: 360 ClassificationKNN: property HyperparameterOptimizationResults Results of the hyperparameter optimization Always empty. It is declared for MATLAB compatibility, where it holds what an automatic search over the hyperparameters found. This class fits the parameters it is given and runs no such search, so there is nothing to report. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Results of the hyperparameter optimization # name: # type: sq_string # elements: 1 # length: 29 ClassificationKNN.IncludeTies # name: # type: sq_string # elements: 1 # length: 448 ClassificationKNN: property IncludeTies Flag for handling ties A logical scalar specifying whether prediction includes all the neighbors whose distance values are equal to the k^th smallest distance. If IncludeTies is true , prediction includes all of these neighbors. Otherwise, prediction uses exactly k neighbors. Change the IncludeTies property using dot notation as in: obj .IncludeTies = flag This property may be assigned after fitting. # name: # type: sq_string # elements: 1 # length: 22 Flag for handling ties # name: # type: sq_string # elements: 1 # length: 33 ClassificationKNN.ModelParameters # name: # type: sq_string # elements: 1 # length: 800 ClassificationKNN: property ModelParameters Fitting options, as they were given A structure holding the parameters of the fit: NumNeighbors , NSMethod , Distance , BucketSize , IncludeTies , DistanceWeight , BreakTies , Exponent , Cov , Scale , StandardizeData , and the Version , Method and Type tags. Each of the three distance parameters belongs to one metric and is empty under the others: Exponent to 'minkowski' , Cov to 'mahalanobis' and Scale to 'seuclidean' . Cov and Scale hold what was passed and stay empty otherwise, while Exponent carries its default of 2 for a 'minkowski' fit that did not name one. What the fit used in every case is the DistParameter property. BucketSize is likewise empty unless the search is 'kdtree' , the only method that reads it. This property is read-only. # name: # type: sq_string # elements: 1 # length: 35 Fitting options, as they were given # name: # type: sq_string # elements: 1 # length: 20 ClassificationKNN.Mu # name: # type: sq_string # elements: 1 # length: 436 ClassificationKNN: property Mu Predictor means A numeric vector of the same length as the columns in X with the mean values corresponding to each predictor. If the predictor variables have not been standardized, then 'obj.Mu' is empty. This property is read-only. Each predictor is summarized from every observation where that predictor is present, so a row holding a missing value in another predictor still contributes to this one. # name: # type: sq_string # elements: 1 # length: 15 Predictor means # name: # type: sq_string # elements: 1 # length: 26 ClassificationKNN.NSMethod # name: # type: sq_string # elements: 1 # length: 406 ClassificationKNN: property NSMethod Nearest neighbor search method A character vector specified as either 'kdtree' , which creates and uses a Kd-tree to find nearest neighbors, or 'exhaustive' , which uses the exhaustive search algorithm by computing the distance values from all points in X to find nearest neighbors. Change the NSMethod property using dot notation as in: obj .NSMethod = newNSMethod # name: # type: sq_string # elements: 1 # length: 30 Nearest neighbor search method # name: # type: sq_string # elements: 1 # length: 30 ClassificationKNN.NumNeighbors # name: # type: sq_string # elements: 1 # length: 401 ClassificationKNN: property NumNeighbors Number of nearest neighbors A positive integer value specifyingNumber of nearest neighbors in X used to classify each point during prediction. Change the NumNeighbors property using dot notation as in: obj .NumNeighbors = newNumNeighbors This property may be assigned after fitting. A value larger than NumObservations is reduced to it rather than refused. # name: # type: sq_string # elements: 1 # length: 27 Number of nearest neighbors # name: # type: sq_string # elements: 1 # length: 33 ClassificationKNN.NumObservations # name: # type: sq_string # elements: 1 # length: 230 ClassificationKNN: property NumObservations Number of observations A positive integer value specifying the number of observations in the training dataset used for training the ClassificationKNN model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 31 ClassificationKNN.NumPredictors # name: # type: sq_string # elements: 1 # length: 224 ClassificationKNN: property NumPredictors Number of predictors A positive integer value specifying the number of predictors in the training dataset used for training the ClassificationKNN model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 32 ClassificationKNN.PredictorNames # name: # type: sq_string # elements: 1 # length: 257 ClassificationKNN: property PredictorNames Names of predictor variables A cell array of character vectors specifying the names of the predictor variables. The names are in the order in which they appear in the training dataset. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Names of predictor variables # name: # type: sq_string # elements: 1 # length: 23 ClassificationKNN.Prior # name: # type: sq_string # elements: 1 # length: 610 ClassificationKNN: property Prior Prior probability for each class A numeric vector specifying the prior probabilities for each class. The order of the elements in Prior corresponds to the order of the classes in ClassNames . Add or change the Prior property using dot notation as in: obj .Prior = priorVector Specified as a row vector with one entry per class, in the order of ClassNames , and rescaled to sum to one. It may be given as 'empirical' , 'uniform' , a numeric vector, or a structure with ClassNames and ClassProbs fields, which assigns each probability by class name rather than by position. # name: # type: sq_string # elements: 1 # length: 32 Prior probability for each class # name: # type: sq_string # elements: 1 # length: 30 ClassificationKNN.ResponseName # name: # type: sq_string # elements: 1 # length: 161 ClassificationKNN: property ResponseName Response variable name A character vector specifying the name of the response variable Y . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 26 ClassificationKNN.RowsUsed # name: # type: sq_string # elements: 1 # length: 386 ClassificationKNN: property RowsUsed Rows used for fitting A logical column vector with the same length as the observations in the original predictor data X , true for each row that was used for fitting the ClassificationKNN model. It is empty, [] , when every observation was used, so a non-empty value means that rows holding missing values were dropped. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Rows used for fitting # name: # type: sq_string # elements: 1 # length: 32 ClassificationKNN.ScoreTransform # name: # type: sq_string # elements: 1 # length: 997 ClassificationKNN: property ScoreTransform Transformation function for classification scores Specified as a function handle for transforming the classification scores. Add or change the ScoreTransform property using dot notation as in: obj .ScoreTransform = 'function_name' obj .ScoreTransform = @function_handle When specified as a character vector, it can be any of the following built-in functions. Nevertheless, the ScoreTransform property always stores their function handle equivalent. Value Description 'doublelogit' 1 ./ (1 + exp (-2 × x)) 'invlogit' log (x ./ (1 - x)) 'ismax' Sets the score for the class with the largest score to 1, and for all other classes to 0 'logit' 1 ./ (1 + exp (-x)) 'none' x (no transformation) 'identity' x (no transformation) 'sign' -1 for x < 0, 0 for x = 0, 1 for x > 0 'symmetric' 2 × x - 1 'symmetricismax' Sets the score for the class with the largest score to 1, and for all other classes to -1 'symmetriclogit' 2 ./ (1 + exp (-x)) - 1 # name: # type: sq_string # elements: 1 # length: 49 Transformation function for classification scores # name: # type: sq_string # elements: 1 # length: 23 ClassificationKNN.Sigma # name: # type: sq_string # elements: 1 # length: 464 ClassificationKNN: property Sigma Predictor standard deviations A numeric vector of the same length as the columns in X with the standard deviations corresponding to each predictor. If the predictor variables have not been standardized, then 'obj.Sigma' is empty. This property is read-only. Each predictor is summarized from every observation where that predictor is present, so a row holding a missing value in another predictor still contributes to this one. # name: # type: sq_string # elements: 1 # length: 29 Predictor standard deviations # name: # type: sq_string # elements: 1 # length: 19 ClassificationKNN.W # name: # type: sq_string # elements: 1 # length: 496 ClassificationKNN: property W Observation weights A numeric column vector with one entry per observation used for fitting. Each class carries its prior spread evenly over its own observations, so an observation of class k weighs Prior(k) divided by the number of observations in that class. This property is read-only. Each class carries its prior spread evenly over its own observations, so an observation of a class weighs Prior for that class divided by the number of observations it holds. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 19 ClassificationKNN.X # name: # type: sq_string # elements: 1 # length: 232 ClassificationKNN: property X Predictor data A numeric matrix containing the unstandardized predictor data. Each column of X represents one predictor (variable), and each row represents one observation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Predictor data # name: # type: sq_string # elements: 1 # length: 19 ClassificationKNN.Y # name: # type: sq_string # elements: 1 # length: 309 ClassificationKNN: property Y Class labels Specified as a logical or numeric column vector, or as a character array or a cell array of character vectors with the same number of rows as the predictor data. Each row in Y is the observed class label for the corresponding row in X . This property is read-only. # name: # type: sq_string # elements: 1 # length: 12 Class labels # name: # type: sq_string # elements: 1 # length: 26 ClassificationKNN.crossval # name: # type: sq_string # elements: 1 # length: 1037 ClassificationKNN: CVMdl = crossval ( obj ) ClassificationKNN: CVMdl = crossval (…, Name , Value ) Cross Validate a ClassificationKNN object. CVMdl = crossval ( obj ) returns a cross-validated model object, CVMdl , from a trained model, obj , using 10-fold cross-validation by default. CVMdl = crossval ( obj , name , value ) specifies additional name-value pair arguments to customize the cross-validation process. Name Value 'KFold' Specify the number of folds to use in k-fold cross-validation. "KFold", k , where k is an integer greater than 1. 'Holdout' Specify the fraction of the data to hold out for testing. "Holdout", p , where p is a scalar in the range (0,1) . 'Leaveout' Specify whether to perform leave-one-out cross-validation. "Leaveout", Value , where Value is ’on’ or ’off’. 'CVPartition' Specify a cvpartition object used for cross-validation. "CVPartition", cv , where isa ( cv , "cvpartition") = 1. See also: fitcknn, ClassificationKNN, cvpartition, ClassificationPartitionedModel # name: # type: sq_string # elements: 1 # length: 42 Cross Validate a ClassificationKNN object. # name: # type: sq_string # elements: 1 # length: 22 ClassificationKNN.edge # name: # type: sq_string # elements: 1 # length: 599 ClassificationKNN: e = edge ( obj , X , Y ) ClassificationKNN: e = edge (…, "Weights" , w ) Classification edge, the mean of the classification margins. e = edge ( obj , X , Y ) reduces the vector that margin returns to a single number, the mean margin over the rows of X . It says how far the model puts the true class ahead of its nearest rival on average, so a larger edge is a better model, and unlike a loss it is not bounded above and rewards confidence rather than bare correctness. e = edge (…, "Weights" , w ) takes the weighted mean instead, with one weight per row of X . # name: # type: sq_string # elements: 1 # length: 60 Classification edge, the mean of the classification margins. # name: # type: sq_string # elements: 1 # length: 22 ClassificationKNN.loss # name: # type: sq_string # elements: 1 # length: 1894 ClassificationKNN: L = loss ( obj , X , Y ) ClassificationKNN: L = loss (…, name , value ) Compute loss for a trained ClassificationKNN object. L = loss ( obj , X , Y ) computes the loss, L , using the default loss function 'mincost' . obj is a ClassificationKNN object trained on X and Y . X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y must have same numbers of Rows as X . L = loss (…, name , value ) allows additional options specified by name - value pairs: Name Value 'LossFun' Specifies the loss function to use. Can be a function handle with four input arguments (C, S, W, Cost) which returns a scalar value or one of: ’binodeviance’, ’classifcost’, ’classiferror’, ’exponential’, ’hinge’, ’logit’,’mincost’, ’quadratic’. C is a logical matrix of size N×K , where N is the number of observations and K is the number of classes. The element C(i,j) is true if the class label of the i-th observation is equal to the j-th class. S is a numeric matrix of size N×K , where each element represents the classification score for the corresponding class. W is a numeric vector of length N , representing the observation weights. Cost is a K×K matrix representing the misclassification costs. 'Weights' Specifies observation weights, must be a numeric vector of length equal to the number of rows in X. Default is ones (size (X, 1)) . loss normalizes the weights so that observation weights in each class sum to the prior probability of that class. When you supply Weights, loss computes the weighted classification loss. See also: fitcknn, ClassificationKNN # name: # type: sq_string # elements: 1 # length: 52 Compute loss for a trained ClassificationKNN object. # name: # type: sq_string # elements: 1 # length: 24 ClassificationKNN.margin # name: # type: sq_string # elements: 1 # length: 751 ClassificationKNN: m = margin ( obj , X , Y ) m = margin ( obj , X , Y ) returns the classification margins for obj with data X and classification Y . m is a numeric vector of length size (X,1). obj is a ClassificationKNN object trained on X and Y . X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y must have same numbers of Rows as X . The classification margin for each observation is the difference between the classification score for the true class and the maximal classification score for the false classes. See also: fitcknn, ClassificationKNN # name: # type: sq_string # elements: 1 # length: 99 m = margin (obj, X, Y) returns the classification margins for obj with data X and classification Y. # name: # type: sq_string # elements: 1 # length: 35 ClassificationKNN.partialDependence # name: # type: sq_string # elements: 1 # length: 1730 ClassificationKNN: [pd, x, y] = partialDependence ( obj , Vars , Labels ) ClassificationKNN: [pd, x, y] = partialDependence (…, Data ) ClassificationKNN: [pd, x, y] = partialDependence (…, name , value ) Compute partial dependence for a trained ClassificationKNN object. [pd, x, y] = partialDependence ( obj , Vars , Labels ) computes the partial dependence of the classification scores on the variables Vars for the specified class Labels . obj is a trained ClassificationKNN object. Vars is a vector of positive integers, character vector, string array, or cell array of character vectors representing predictor variables (it can be indices of predictor variables in obj.X ). Labels is a character vector, logical vector, numeric vector, or cell array of character vectors representing class labels. (column vector) [pd, x, y] = partialDependence (…, Data ) specifies new predictor data to use for computing the partial dependence. [pd, x, y] = partialDependence (…, name , value ) allows additional options specified by name-value pairs: Name Value 'NumObservationsToSample' Number of observations to sample. Must be a positive integer. Defaults to the number of observations in the training data. 'QueryPoints' Points at which to evaluate the partial dependence. Must be a numeric column vector, numeric two-column matrix, or cell array of character column vectors. 'UseParallel' Logical value indicating whether to perform computations in parallel. Defaults to false . Return Values pd : Partial dependence values. x : Query points for the first predictor variable in Vars. y : Query points for the second predictor variable in Vars (if applicable). See also: fitcknn, ClassificationKNN # name: # type: sq_string # elements: 1 # length: 66 Compute partial dependence for a trained ClassificationKNN object. # name: # type: sq_string # elements: 1 # length: 25 ClassificationKNN.predict # name: # type: sq_string # elements: 1 # length: 1290 ClassificationKNN: labels = predict ( obj , XC ) ClassificationKNN: [ labels , scores , cost ] = predict ( obj , XC ) Classify new data points into categories using the kNN algorithm from a k-Nearest Neighbor classification model. labels = predict ( obj , XC ) returns the matrix of labels predicted for the corresponding instances in XC , using the predictor data in obj.X and corresponding labels, obj.Y , stored in the k-Nearest Neighbor classification model, obj . obj must be a ClassificationKNN class object. XC must be an M×P numeric matrix with the same number of features P as the corresponding predictors of the SVM model in obj . [ labels , scores , cost ] = predict ( obj , XC ) also returns scores , which contains the predicted class scores or posterior probabilities for each instance of the corresponding unique classes, and cost , which is a matrix containing the expected cost of the classifications. By default, scores returns the posterior probabilities for KNN models, unless a specific ScoreTransform function has been specified. See fitcknn for more info. Note! predict is explicitly using 'exhaustive' as the nearest search method due to the very slow implementation of 'kdtree' in the knnsearch function. See also: fitcknn, ClassificationKNN, knnsearch # name: # type: sq_string # elements: 1 # length: 112 Classify new data points into categories using the kNN algorithm from a k-Nearest Neighbor classification model. # name: # type: sq_string # elements: 1 # length: 27 ClassificationKNN.resubEdge # name: # type: sq_string # elements: 1 # length: 210 ClassificationKNN: e = resubEdge ( obj ) Classification edge of the model on its own training data. e = resubEdge ( obj ) is edge applied to the observations the model was fitted on, the mean of resubMargin . # name: # type: sq_string # elements: 1 # length: 58 Classification edge of the model on its own training data. # name: # type: sq_string # elements: 1 # length: 27 ClassificationKNN.resubLoss # name: # type: sq_string # elements: 1 # length: 576 ClassificationKNN: L = resubLoss ( obj ) ClassificationKNN: L = resubLoss (…, name , value ) Classification loss of the model on its own training data. L = resubLoss ( obj ) is loss applied to the observations the model was fitted on, defaulting to 'mincost' , and it accepts the same Name-Value pairs. Being a resubstitution quantity it is a lower bound on the error rather than an estimate of it. It is worth least on a lazy learner: a one-neighbour ClassificationKNN has a resubstitution loss of exactly zero, every training point being its own nearest neighbour. # name: # type: sq_string # elements: 1 # length: 58 Classification loss of the model on its own training data. # name: # type: sq_string # elements: 1 # length: 29 ClassificationKNN.resubMargin # name: # type: sq_string # elements: 1 # length: 287 ClassificationKNN: m = resubMargin ( obj ) Classification margins of the model on its own training data. m = resubMargin ( obj ) is margin applied to the observations the model was fitted on, one number per observation. Being a resubstitution quantity it is optimistic by construction. # name: # type: sq_string # elements: 1 # length: 61 Classification margins of the model on its own training data. # name: # type: sq_string # elements: 1 # length: 30 ClassificationKNN.resubPredict # name: # type: sq_string # elements: 1 # length: 612 ClassificationKNN: label = resubPredict ( obj ) ClassificationKNN: [ label , score , cost ] = resubPredict ( obj ) Classify the training data with the model fitted to it. label = resubPredict ( obj ) is predict applied to the observations the model was fitted on, which it holds in X . Handing them over yourself is not the same thing: a row dropped for a missing response is not in X , so the original matrix and the model’s own are different data. The result measures fit and not generalization, and is optimistic by construction. crossval is what estimates performance on data the model has not seen. # name: # type: sq_string # elements: 1 # length: 55 Classify the training data with the model fitted to it. # name: # type: sq_string # elements: 1 # length: 27 ClassificationKNN.savemodel # name: # type: sq_string # elements: 1 # length: 472 ClassificationKNN: savemodel ( obj , filename ) Save a ClassificationKNN object. savemodel ( obj , filename ) saves each property of a ClassificationKNN object into an Octave binary file, the name of which is specified in filename , along with an extra variable, which defines the type classification object these variables constitute. Use loadmodel in order to load a classification object into Octave’s workspace. See also: loadmodel, fitcknn, ClassificationKNN # name: # type: sq_string # elements: 1 # length: 32 Save a ClassificationKNN object. # name: # type: sq_string # elements: 1 # length: 20 ClassificationKernel # name: # type: sq_string # elements: 1 # length: 1308 statistics: ClassificationKernel Gaussian kernel binary classifier for large data. A ClassificationKernel object maps the predictors into a randomized feature space whose inner product approximates a Gaussian kernel, and then fits a linear model there. A kernel classifier is therefore as nonlinear as a support vector machine with a Gaussian kernel, while costing what a linear fit costs: nothing of size NxN is ever formed. The expansion is the random Fourier basis of Rahimi and Recht, drawn once when the model is fitted and kept with it, so predict maps new data through the same basis. MATLAB approximates the same kernel by the Fastfood construction, which reaches the same distribution more cheaply; the two are interchangeable in distribution but not draw by draw, and the draws come from different generators in any case, so the scores of a model fitted here and one fitted in MATLAB differ even from the same seed. What does not differ is what they estimate. Like ClassificationLinear the object holds no copy of the training data. It does hold the basis and the coefficients, so it is bounded by the number of expansion dimensions rather than by the number of observations. Create a ClassificationKernel object with fitckernel . See also: fitckernel, ClassificationLinear, ClassificationSVM # name: # type: sq_string # elements: 1 # length: 49 Gaussian kernel binary classifier for large data. # name: # type: sq_string # elements: 1 # length: 34 ClassificationKernel.BoxConstraint # name: # type: sq_string # elements: 1 # length: 310 ClassificationKernel: property BoxConstraint Box constraint of the support vector machine A positive scalar. It is the reciprocal of the product of Lambda and the number of observations, so setting either of the two in the constructor fixes the other, and giving both is an error. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Box constraint of the support vector machine # name: # type: sq_string # elements: 1 # length: 42 ClassificationKernel.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 191 ClassificationKernel: property CategoricalPredictors Indices of the categorical predictors A row vector of column indices, empty when every predictor is numeric. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 31 ClassificationKernel.ClassNames # name: # type: sq_string # elements: 1 # length: 249 ClassificationKernel: property ClassNames Names of the two classes A column of the same type as the response supplied to the constructor. The second of the two is the positive class, the one a positive score belongs to. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Names of the two classes # name: # type: sq_string # elements: 1 # length: 41 ClassificationKernel.ClassificationKernel # name: # type: sq_string # elements: 1 # length: 2113 ClassificationKernel: obj = ClassificationKernel ( X , Y ) ClassificationKernel: obj = ClassificationKernel (…, name , value ) Fit a Gaussian kernel binary classifier. obj = ClassificationKernel ( X , Y ) fits a support vector machine in a randomized Gaussian kernel space to the NxP predictor matrix X and the Nx1 response Y , which must name exactly two classes. obj = ClassificationKernel (…, name , value ) takes the following Name-Value pairs. Name Value 'Learner' 'svm' , the default, or 'logistic' . 'NumExpansionDimensions' 'auto' , the default, or a positive integer. 'KernelScale' 1 by default, a positive scalar, or 'auto' , which takes the median distance between the observations. 'Lambda' 'auto' , the default, which is the reciprocal of the number of observations, or a nonnegative scalar. It cannot be given beside 'BoxConstraint' . 'BoxConstraint' A positive scalar, 1 by default. It applies to a support vector machine alone. 'Standardize' Whether to centre and scale the predictors, false by default. 'BetaTolerance' Relative tolerance on the coefficients, 1e-4 by default. 'GradientTolerance' Absolute tolerance on the gradient’s infinity norm, 1e-6 by default. 'IterationLimit' Largest number of iterations, 1000 by default. 'HessianHistorySize' Number of curvature pairs the solver keeps, 15 by default. 'BlockSize' Memory the expansion may occupy, in megabytes, 4e3 by default. 'ClassNames' The classes to keep, given in the type of Y . 'Cost' A square misclassification cost matrix. 'Prior' 'empirical' , the default, 'uniform' , a vector of probabilities, or a structure with ClassNames and ClassProbs fields. 'ScoreTransform' A transformation applied to the scores, named or given as a function handle. 'Weights' One nonnegative weight per observation. 'PredictorNames' One name per predictor. 'ResponseName' A name for the response. 'CategoricalPredictors' Indices of the categorical predictors. The fit is always by limited-memory BFGS, the only solver MATLAB offers a kernel model, and always under a ridge penalty. See also: fitckernel, ClassificationLinear # name: # type: sq_string # elements: 1 # length: 40 Fit a Gaussian kernel binary classifier. # name: # type: sq_string # elements: 1 # length: 25 ClassificationKernel.Cost # name: # type: sq_string # elements: 1 # length: 596 ClassificationKernel: property Cost Cost of misclassifying an observation A square numeric matrix with one row and one column per class, whose (i,j) element is the cost of classifying an observation of class i into class j . It defaults to one everywhere except the diagonal, which is zero. This property is read-only, as it is in MATLAB; a cost matrix is given to the constructor instead. The costs are folded into the prior before the observations are weighted, so a class that is costlier to misclassify weighs more in the fit. They are read again by the 'mincost' and 'classifcost' losses. # name: # type: sq_string # elements: 1 # length: 37 Cost of misclassifying an observation # name: # type: sq_string # elements: 1 # length: 43 ClassificationKernel.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 249 ClassificationKernel: property ExpandedPredictorNames Names of the predictors as the fit saw them A cell array of character vectors. These name the original predictors, not the expansion dimensions, which have no names. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Names of the predictors as the fit saw them # name: # type: sq_string # elements: 1 # length: 31 ClassificationKernel.FittedLoss # name: # type: sq_string # elements: 1 # length: 179 ClassificationKernel: property FittedLoss Loss function the fit minimized 'hinge' for a support vector machine and 'logit' for a logistic regression. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Loss function the fit minimized # name: # type: sq_string # elements: 1 # length: 32 ClassificationKernel.KernelScale # name: # type: sq_string # elements: 1 # length: 236 ClassificationKernel: property KernelScale Scale of the Gaussian kernel A positive scalar dividing every predictor before the expansion, so a larger scale makes the kernel wider and the classifier smoother. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Scale of the Gaussian kernel # name: # type: sq_string # elements: 1 # length: 27 ClassificationKernel.Lambda # name: # type: sq_string # elements: 1 # length: 192 ClassificationKernel: property Lambda Regularization strength A nonnegative scalar, the reciprocal of the product of BoxConstraint and the number of observations. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Regularization strength # name: # type: sq_string # elements: 1 # length: 28 ClassificationKernel.Learner # name: # type: sq_string # elements: 1 # length: 139 ClassificationKernel: property Learner Linear model fitted in the expanded space Either 'svm' or 'logistic' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 41 Linear model fitted in the expanded space # name: # type: sq_string # elements: 1 # length: 36 ClassificationKernel.ModelParameters # name: # type: sq_string # elements: 1 # length: 237 ClassificationKernel: property ModelParameters Fitting options, as they were given A structure holding every parameter of the fit, with the 'auto' values as they were given rather than as they were resolved. This property is read-only. # name: # type: sq_string # elements: 1 # length: 35 Fitting options, as they were given # name: # type: sq_string # elements: 1 # length: 23 ClassificationKernel.Mu # name: # type: sq_string # elements: 1 # length: 202 ClassificationKernel: property Mu Predictor means used to standardize A row vector with one element per predictor, or empty when the model was fitted without standardizing. This property is read-only. # name: # type: sq_string # elements: 1 # length: 35 Predictor means used to standardize # name: # type: sq_string # elements: 1 # length: 43 ClassificationKernel.NumExpansionDimensions # name: # type: sq_string # elements: 1 # length: 345 ClassificationKernel: property NumExpansionDimensions Number of dimensions of the expanded space A positive integer scalar. It defaults to 2 .^ ceil (min (log2 ( p ) + 5, 15)) for p predictors, so four predictors give 128 dimensions. More dimensions approximate the kernel more closely and cost proportionally more. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Number of dimensions of the expanded space # name: # type: sq_string # elements: 1 # length: 35 ClassificationKernel.PredictorNames # name: # type: sq_string # elements: 1 # length: 217 ClassificationKernel: property PredictorNames Names of the predictors A cell array of character vectors with one name per column of the training data, defaulting to 'x1' , 'x2' and so on. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Names of the predictors # name: # type: sq_string # elements: 1 # length: 26 ClassificationKernel.Prior # name: # type: sq_string # elements: 1 # length: 253 ClassificationKernel: property Prior Prior probability of each class A numeric row vector with one element per class, in the order of ClassNames and summing to one. It defaults to the class frequencies of the training data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Prior probability of each class # name: # type: sq_string # elements: 1 # length: 35 ClassificationKernel.Regularization # name: # type: sq_string # elements: 1 # length: 213 ClassificationKernel: property Regularization Penalty on the coefficients Always 'ridge (L2)' : a kernel model fits in the expanded space, where a lasso penalty has nothing to select. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Penalty on the coefficients # name: # type: sq_string # elements: 1 # length: 33 ClassificationKernel.ResponseName # name: # type: sq_string # elements: 1 # length: 134 ClassificationKernel: property ResponseName Name of the response A character vector, defaulting to 'Y' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Name of the response # name: # type: sq_string # elements: 1 # length: 35 ClassificationKernel.ScoreTransform # name: # type: sq_string # elements: 1 # length: 314 ClassificationKernel: property ScoreTransform Transformation applied to the predicted scores A character vector naming a transformation, or the text of the function handle that was supplied. Assigning to it accepts either. It defaults to 'logit' for a logistic learner and to 'none' for a support vector machine. # name: # type: sq_string # elements: 1 # length: 46 Transformation applied to the predicted scores # name: # type: sq_string # elements: 1 # length: 26 ClassificationKernel.Sigma # name: # type: sq_string # elements: 1 # length: 219 ClassificationKernel: property Sigma Predictor standard deviations used to standardize A row vector with one element per predictor, or empty when the model was fitted without standardizing. This property is read-only. # name: # type: sq_string # elements: 1 # length: 49 Predictor standard deviations used to standardize # name: # type: sq_string # elements: 1 # length: 25 ClassificationKernel.edge # name: # type: sq_string # elements: 1 # length: 249 ClassificationKernel: e = edge ( obj , X , Y ) ClassificationKernel: e = edge (…, 'Weights' , W ) Weighted mean of the classification margins. The weights are normalized within each class to that class’s prior before they are applied. # name: # type: sq_string # elements: 1 # length: 44 Weighted mean of the classification margins. # name: # type: sq_string # elements: 1 # length: 25 ClassificationKernel.loss # name: # type: sq_string # elements: 1 # length: 390 ClassificationKernel: l = loss ( obj , X , Y ) ClassificationKernel: l = loss (…, name , value ) Classification loss on new data. l = loss ( obj , X , Y ) returns the misclassification rate. l = loss (…, name , value ) takes 'LossFun' , one of 'binodeviance' , 'classifcost' , 'classiferror' , 'exponential' , 'hinge' , 'logit' , 'mincost' and 'quadratic' , and 'Weights' . # name: # type: sq_string # elements: 1 # length: 32 Classification loss on new data. # name: # type: sq_string # elements: 1 # length: 27 ClassificationKernel.margin # name: # type: sq_string # elements: 1 # length: 236 ClassificationKernel: m = margin ( obj , X , Y ) Classification margin of each observation. m = margin ( obj , X , Y ) returns the score of the true class less the score of the other one. A positive margin is a correct classification. # name: # type: sq_string # elements: 1 # length: 42 Classification margin of each observation. # name: # type: sq_string # elements: 1 # length: 28 ClassificationKernel.predict # name: # type: sq_string # elements: 1 # length: 424 ClassificationKernel: labels = predict ( obj , XC ) ClassificationKernel: [ labels , scores ] = predict ( obj , XC ) Classify new observations. labels = predict ( obj , XC ) maps each row of XC through the model’s own random basis and returns the class of largest score. [ labels , scores ] = predict ( obj , XC ) also returns the Nx2 scores, whose columns follow ClassNames , after ScoreTransform has been applied. # name: # type: sq_string # elements: 1 # length: 26 Classify new observations. # name: # type: sq_string # elements: 1 # length: 27 ClassificationKernel.resume # name: # type: sq_string # elements: 1 # length: 877 ClassificationKernel: obj = resume ( obj , X , Y ) ClassificationKernel: obj = resume (…, name , value ) Continue fitting a kernel classifier. obj = resume ( obj , X , Y ) restarts the optimization from the coefficients the model already carries, through the basis it already holds, and returns the model it reaches. It takes 'BetaTolerance' , 'GradientTolerance' and 'IterationLimit' , each defaulting to what the model was fitted with, and 'Weights' . X and Y must be the data the model was fitted to; the object keeps no copy of them, which is what makes it small. Neither does it keep the observation weights, so a model fitted with 'Weights' must be given them again here or it will resume against uniform ones. MATLAB behaves the same way: measured on R2024a, resuming a weighted fit without passing the weights back reaches the objective of the unweighted fit. # name: # type: sq_string # elements: 1 # length: 37 Continue fitting a kernel classifier. # name: # type: sq_string # elements: 1 # length: 30 ClassificationKernel.savemodel # name: # type: sq_string # elements: 1 # length: 213 ClassificationKernel: savemodel ( obj , filename ) Save a kernel classifier to a file. savemodel ( obj , filename ) saves the model obj into filename in a form loadmodel can read back, the random basis included. # name: # type: sq_string # elements: 1 # length: 35 Save a kernel classifier to a file. # name: # type: sq_string # elements: 1 # length: 20 ClassificationLinear # name: # type: sq_string # elements: 1 # length: 1087 statistics: ClassificationLinear Linear binary classifier for high dimensional data. A ClassificationLinear object fits a linear model, X * Beta + Bias , to a two class problem by minimizing a regularized average loss. The loss is the hinge loss for a support vector machine and the deviance for a logistic regression, and the penalty is either a ridge or a lasso one. Unlike the other classifiers of this package the object holds no copy of the training data: the coefficients, the intercept and the fitting options are the whole model. That is what makes it suited to data with more predictors than an in memory kernel matrix could carry, and it is why the class has no compact method and no resubstitution methods. A vector of regularization strengths fits one model per value in a single object. Beta is then a PxL matrix and Bias a 1xL row, every method returns one column per strength, and selectModels narrows the object down to the strengths worth keeping. Create a ClassificationLinear object with fitclinear . See also: fitclinear, ClassificationKernel, ClassificationSVM # name: # type: sq_string # elements: 1 # length: 51 Linear binary classifier for high dimensional data. # name: # type: sq_string # elements: 1 # length: 25 ClassificationLinear.Beta # name: # type: sq_string # elements: 1 # length: 199 ClassificationLinear: property Beta Fitted linear coefficients A Px1 column, or a PxL matrix with one column per regularization strength when Lambda holds more than one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Fitted linear coefficients # name: # type: sq_string # elements: 1 # length: 25 ClassificationLinear.Bias # name: # type: sq_string # elements: 1 # length: 228 ClassificationLinear: property Bias Fitted intercept A scalar, or a 1xL row with one element per regularization strength. It is zero throughout when the model was fitted with 'FitBias' set to false. This property is read-only. # name: # type: sq_string # elements: 1 # length: 16 Fitted intercept # name: # type: sq_string # elements: 1 # length: 42 ClassificationLinear.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 191 ClassificationLinear: property CategoricalPredictors Indices of the categorical predictors A row vector of column indices, empty when every predictor is numeric. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 31 ClassificationLinear.ClassNames # name: # type: sq_string # elements: 1 # length: 342 ClassificationLinear: property ClassNames Names of the two classes A column of the same type as the response supplied to the constructor: a cell array of character vectors, a numeric vector, a logical vector or a character matrix. The second of the two is the positive class, the one a positive score belongs to. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Names of the two classes # name: # type: sq_string # elements: 1 # length: 41 ClassificationLinear.ClassificationLinear # name: # type: sq_string # elements: 1 # length: 3647 ClassificationLinear: obj = ClassificationLinear ( X , Y ) ClassificationLinear: obj = ClassificationLinear (…, name , value ) Fit a linear binary classifier. obj = ClassificationLinear ( X , Y ) fits a linear support vector machine to the NxP predictor matrix X and the Nx1 response Y , which must name exactly two classes. obj = ClassificationLinear (…, name , value ) takes the following Name-Value pairs. Name Value 'Learner' 'svm' , the default, or 'logistic' . The first minimizes the hinge loss and the second the deviance. 'Regularization' 'ridge' or 'lasso' . It defaults to 'lasso' when the solver is 'sparsa' and to 'ridge' otherwise. 'Lambda' 'auto' , the default, which is the reciprocal of the number of observations, or a nonnegative scalar, or a vector of them. A vector fits one model per value. 'Solver' One of 'sgd' , 'asgd' , 'dual' , 'bfgs' , 'lbfgs' and 'sparsa' , or a cell array of them applied in turn, each warm starting the next. The default depends on the data and the penalty, as described below. 'Beta' Initial coefficients, a Px1 column or a PxL matrix. It defaults to zeros. 'Bias' Initial intercept, a scalar or a 1xL row. It defaults to the weighted average of the class labels for a logistic learner and to zero for a support vector machine. 'FitBias' Whether to fit an intercept at all, true by default. 'PostFitBias' Whether to refit the intercept once the coefficients are settled, false by default. 'ObservationsIn' 'rows' , the default, or 'columns' , which transposes X before fitting. 'BetaTolerance' Relative tolerance on the coefficients, 1e-4 by default. 'GradientTolerance' Absolute tolerance on the gradient’s infinity norm, 1e-6 by default. 'DeltaGradientTolerance' Tolerance on the complementarity gap of the 'dual' solver, 1 by default for a hinge loss. MathWorks documents 0.1 , which is the default of the regression counterpart; R2024a and R2026a both report 1 here. 'IterationLimit' Largest number of iterations, 1000 by default. 'PassLimit' Largest number of passes over the data for the stochastic solvers, 1 by default, and 10 for 'dual' . 'BatchSize' Mini-batch size of the stochastic solvers, 10 by default. 'BatchLimit' Largest number of mini-batches. 'LearnRate' Step size of the stochastic solvers. 'OptimizeLearnRate' Whether to halve the step size when the objective rises, true by default. 'TruncationPeriod' Number of mini-batches between soft thresholdings under a lasso penalty, 10 by default. 'NumCheckConvergence' Number of passes between convergence checks of the 'dual' solver, 2 by default. MathWorks documents 5 ; R2024a and R2026a both report 2 , so the documentation is stale rather than the releases being inconsistent. 'HessianHistorySize' Number of curvature pairs the quasi-Newton solvers keep, 15 by default. 'ClassNames' The classes to keep, given in the type of Y . Observations of any other class are dropped. 'Cost' A square misclassification cost matrix. 'Prior' 'empirical' , the default, 'uniform' , a vector of probabilities, or a structure with ClassNames and ClassProbs fields. 'ScoreTransform' A transformation applied to the scores, named or given as a function handle. 'Weights' One nonnegative weight per observation. 'PredictorNames' One name per predictor. 'ResponseName' A name for the response. 'CategoricalPredictors' Indices of the categorical predictors. The default solver is 'sparsa' under a lasso penalty. Under a ridge penalty it is 'bfgs' when there are no more than 100 predictors, and beyond that 'dual' for a support vector machine and 'sgd' for a logistic regression. See also: fitclinear, ClassificationKernel # name: # type: sq_string # elements: 1 # length: 31 Fit a linear binary classifier. # name: # type: sq_string # elements: 1 # length: 25 ClassificationLinear.Cost # name: # type: sq_string # elements: 1 # length: 634 ClassificationLinear: property Cost Cost of misclassifying an observation A square numeric matrix with one row and one column per class, whose (i,j) element is the cost of classifying an observation of class i into class j . It defaults to one everywhere except the diagonal, which is zero. This property is read-only: MATLAB refuses an assignment into it on this class, as it does on the support vector machine, so a cost matrix is given to the constructor instead. The cost matrix takes no part in the fit and none in predict , which returns the class of largest score. It is read by the 'mincost' and 'classifcost' losses alone. # name: # type: sq_string # elements: 1 # length: 37 Cost of misclassifying an observation # name: # type: sq_string # elements: 1 # length: 43 ClassificationLinear.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 257 ClassificationLinear: property ExpandedPredictorNames Names of the predictors as the fit saw them A cell array of character vectors. It equals PredictorNames unless categorical predictors were expanded into indicator variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Names of the predictors as the fit saw them # name: # type: sq_string # elements: 1 # length: 31 ClassificationLinear.FittedLoss # name: # type: sq_string # elements: 1 # length: 273 ClassificationLinear: property FittedLoss Loss function the fit minimized 'hinge' for a support vector machine and 'logit' for a logistic regression. This is the loss of the objective, which is not the loss loss reports unless it is asked for. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Loss function the fit minimized # name: # type: sq_string # elements: 1 # length: 27 ClassificationLinear.Lambda # name: # type: sq_string # elements: 1 # length: 239 ClassificationLinear: property Lambda Regularization strength A nonnegative scalar, or a 1xL row of them in ascending order. It defaults to the reciprocal of the number of observations used to train the model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Regularization strength # name: # type: sq_string # elements: 1 # length: 28 ClassificationLinear.Learner # name: # type: sq_string # elements: 1 # length: 141 ClassificationLinear: property Learner Linear classification model that was fitted Either 'svm' or 'logistic' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Linear classification model that was fitted # name: # type: sq_string # elements: 1 # length: 36 ClassificationLinear.ModelParameters # name: # type: sq_string # elements: 1 # length: 268 ClassificationLinear: property ModelParameters Fitting options, as they were given A structure holding every parameter of the fit, including the ones that a different solver would have used and the 'auto' values before they were resolved. This property is read-only. # name: # type: sq_string # elements: 1 # length: 35 Fitting options, as they were given # name: # type: sq_string # elements: 1 # length: 35 ClassificationLinear.PredictorNames # name: # type: sq_string # elements: 1 # length: 217 ClassificationLinear: property PredictorNames Names of the predictors A cell array of character vectors with one name per column of the training data, defaulting to 'x1' , 'x2' and so on. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Names of the predictors # name: # type: sq_string # elements: 1 # length: 26 ClassificationLinear.Prior # name: # type: sq_string # elements: 1 # length: 253 ClassificationLinear: property Prior Prior probability of each class A numeric row vector with one element per class, in the order of ClassNames and summing to one. It defaults to the class frequencies of the training data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Prior probability of each class # name: # type: sq_string # elements: 1 # length: 35 ClassificationLinear.Regularization # name: # type: sq_string # elements: 1 # length: 134 ClassificationLinear: property Regularization Penalty on the coefficients 'ridge (L2)' or 'lasso (L1)' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Penalty on the coefficients # name: # type: sq_string # elements: 1 # length: 33 ClassificationLinear.ResponseName # name: # type: sq_string # elements: 1 # length: 134 ClassificationLinear: property ResponseName Name of the response A character vector, defaulting to 'Y' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Name of the response # name: # type: sq_string # elements: 1 # length: 35 ClassificationLinear.ScoreTransform # name: # type: sq_string # elements: 1 # length: 368 ClassificationLinear: property ScoreTransform Transformation applied to the predicted scores A character vector naming a transformation, or the text of the function handle that was supplied. Assigning to it accepts either. It defaults to 'logit' for a logistic learner, which turns the scores into posterior probabilities, and to 'none' for a support vector machine. # name: # type: sq_string # elements: 1 # length: 46 Transformation applied to the predicted scores # name: # type: sq_string # elements: 1 # length: 25 ClassificationLinear.edge # name: # type: sq_string # elements: 1 # length: 321 ClassificationLinear: e = edge ( obj , X , Y ) ClassificationLinear: e = edge (…, 'Weights' , W ) Weighted mean of the classification margins. e = edge ( obj , X , Y ) returns one value per regularization strength. The weights are normalized within each class to that class’s prior before they are applied. # name: # type: sq_string # elements: 1 # length: 44 Weighted mean of the classification margins. # name: # type: sq_string # elements: 1 # length: 25 ClassificationLinear.loss # name: # type: sq_string # elements: 1 # length: 429 ClassificationLinear: l = loss ( obj , X , Y ) ClassificationLinear: l = loss (…, name , value ) Classification loss on new data. l = loss ( obj , X , Y ) returns the misclassification rate, one value per regularization strength. l = loss (…, name , value ) takes 'LossFun' , one of 'binodeviance' , 'classifcost' , 'classiferror' , 'exponential' , 'hinge' , 'logit' , 'mincost' and 'quadratic' , and 'Weights' . # name: # type: sq_string # elements: 1 # length: 32 Classification loss on new data. # name: # type: sq_string # elements: 1 # length: 27 ClassificationLinear.margin # name: # type: sq_string # elements: 1 # length: 304 ClassificationLinear: m = margin ( obj , X , Y ) Classification margin of each observation. m = margin ( obj , X , Y ) returns the score of the true class less the score of the other one, one row per observation and one column per regularization strength. A positive margin is a correct classification. # name: # type: sq_string # elements: 1 # length: 42 Classification margin of each observation. # name: # type: sq_string # elements: 1 # length: 28 ClassificationLinear.predict # name: # type: sq_string # elements: 1 # length: 608 ClassificationLinear: labels = predict ( obj , XC ) ClassificationLinear: [ labels , scores ] = predict ( obj , XC ) Classify new observations. labels = predict ( obj , XC ) returns the class of largest score for each row of XC , in the type of the response the model was fitted to. With L regularization strengths labels has one column per strength. [ labels , scores ] = predict ( obj , XC ) also returns the scores, an Nx2 matrix whose columns follow ClassNames , or an Nx2xL array with more than one strength. The scores are -f and +f for the raw model value f , after ScoreTransform has been applied. # name: # type: sq_string # elements: 1 # length: 26 Classify new observations. # name: # type: sq_string # elements: 1 # length: 30 ClassificationLinear.savemodel # name: # type: sq_string # elements: 1 # length: 186 ClassificationLinear: savemodel ( obj , filename ) Save a linear classifier to a file. savemodel ( obj , filename ) saves the model obj into filename in a form loadmodel can read back. # name: # type: sq_string # elements: 1 # length: 35 Save a linear classifier to a file. # name: # type: sq_string # elements: 1 # length: 33 ClassificationLinear.selectModels # name: # type: sq_string # elements: 1 # length: 259 ClassificationLinear: sub = selectModels ( obj , idx ) Keep a subset of the fitted regularization strengths. sub = selectModels ( obj , idx ) returns a model holding only the strengths idx names, which may be indices into Lambda or a logical vector over it. # name: # type: sq_string # elements: 1 # length: 53 Keep a subset of the fitted regularization strengths. # name: # type: sq_string # elements: 1 # length: 24 ClassificationNaiveBayes # name: # type: sq_string # elements: 1 # length: 1219 statistics: ClassificationNaiveBayes Naive Bayes classification The ClassificationNaiveBayes class implements a naive Bayes classifier object, which can predict responses for new data using the predict method. A naive Bayes classifier estimates one univariate density per class and per predictor, and treats the predictors as conditionally independent given the class. The joint likelihood of an observation is therefore the product of its per-predictor densities, and the posterior follows from the class prior by Bayes’ rule. The independence assumption is rarely true, but it costs only one density per predictor rather than one joint density over all of them, which is what makes the model usable when the predictors are many and the observations few. Create a ClassificationNaiveBayes object by using the fitcnb function or the class constructor. Each predictor carries its own distribution, named in DistributionNames , and the fitted parameters of class k and predictor j are held in DistributionParameters{k,j} . A 'normal' predictor stores a two element column vector, the class conditional mean and standard deviation; a 'kernel' predictor stores a prob.KernelDistribution object. See also: fitcnb # name: # type: sq_string # elements: 1 # length: 26 Naive Bayes classification # name: # type: sq_string # elements: 1 # length: 33 ClassificationNaiveBayes.BinEdges # name: # type: sq_string # elements: 1 # length: 494 ClassificationNaiveBayes: property BinEdges Bin edges A cell array with one entry per predictor, holding that predictor’s bin edges where the learner discretized it before fitting. A naive Bayes model fits a density to each predictor as it stands and bins nothing, so this is always an empty cell. It is kept because the cross-validated model carries it across, and because code that reaches into it with cellfun must find a cell rather than an empty matrix. This property is read-only. # name: # type: sq_string # elements: 1 # length: 9 Bin edges # name: # type: sq_string # elements: 1 # length: 42 ClassificationNaiveBayes.CategoricalLevels # name: # type: sq_string # elements: 1 # length: 247 ClassificationNaiveBayes: property CategoricalLevels Levels of the categorical predictors A cell array with one entry per predictor, holding the distinct levels of each categorical predictor and empty for every other. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Levels of the categorical predictors # name: # type: sq_string # elements: 1 # length: 46 ClassificationNaiveBayes.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 211 ClassificationNaiveBayes: property CategoricalPredictors Categorical predictor indices A numeric row vector of the column indices of X treated as categorical, or empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Categorical predictor indices # name: # type: sq_string # elements: 1 # length: 35 ClassificationNaiveBayes.ClassNames # name: # type: sq_string # elements: 1 # length: 304 ClassificationNaiveBayes: property ClassNames Class labels of the fitted model A cell array of character vectors, a logical or numeric column vector, or a character array, holding the distinct classes the model was fitted on, in the order the other per-class properties use. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Class labels of the fitted model # name: # type: sq_string # elements: 1 # length: 49 ClassificationNaiveBayes.ClassificationNaiveBayes # name: # type: sq_string # elements: 1 # length: 441 ClassificationNaiveBayes: obj = ClassificationNaiveBayes ( X , Y ) ClassificationNaiveBayes: obj = ClassificationNaiveBayes (…, name , value ) Create a ClassificationNaiveBayes object. obj = ClassificationNaiveBayes ( X , Y ) fits a naive Bayes classifier to the predictor data X and the class labels Y . The supported Name / Value pairs are those of fitcnb , which is the documented way to reach this constructor. See also: fitcnb # name: # type: sq_string # elements: 1 # length: 41 Create a ClassificationNaiveBayes object. # name: # type: sq_string # elements: 1 # length: 29 ClassificationNaiveBayes.Cost # name: # type: sq_string # elements: 1 # length: 396 ClassificationNaiveBayes: property Cost Misclassification cost A square numeric matrix with one row and column per class, in the order of ClassNames . Cost(i,j) is the cost of classifying an observation of class i into class j , and the default is one off the diagonal and zero on it. It may be assigned after fitting, as a matrix or as a structure carrying ClassNames and ClassificationCosts . # name: # type: sq_string # elements: 1 # length: 22 Misclassification cost # name: # type: sq_string # elements: 1 # length: 42 ClassificationNaiveBayes.DistributionNames # name: # type: sq_string # elements: 1 # length: 231 ClassificationNaiveBayes: property DistributionNames Predictor distributions A cell array of character vectors with one entry per predictor, naming the distribution fitted to it: 'normal' or 'kernel' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Predictor distributions # name: # type: sq_string # elements: 1 # length: 47 ClassificationNaiveBayes.DistributionParameters # name: # type: sq_string # elements: 1 # length: 421 ClassificationNaiveBayes: property DistributionParameters Fitted distribution parameters A cell array with one row per class and one column per predictor. DistributionParameters{k,j} holds the parameters fitted to predictor j within class k : a two element column vector, the mean and the standard deviation, for a 'normal' predictor, and a prob.KernelDistribution object for a 'kernel' one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 Fitted distribution parameters # name: # type: sq_string # elements: 1 # length: 47 ClassificationNaiveBayes.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 301 ClassificationNaiveBayes: property ExpandedPredictorNames Expanded predictor variable names A cell array of character vectors naming the predictors as the model sees them. It equals PredictorNames unless a categorical predictor has been expanded into indicator variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Expanded predictor variable names # name: # type: sq_string # elements: 1 # length: 58 ClassificationNaiveBayes.HyperparameterOptimizationResults # name: # type: sq_string # elements: 1 # length: 367 ClassificationNaiveBayes: property HyperparameterOptimizationResults Results of the hyperparameter optimization Always empty. It is declared for MATLAB compatibility, where it holds what an automatic search over the hyperparameters found. This class fits the parameters it is given and runs no such search, so there is nothing to report. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Results of the hyperparameter optimization # name: # type: sq_string # elements: 1 # length: 31 ClassificationNaiveBayes.Kernel # name: # type: sq_string # elements: 1 # length: 225 ClassificationNaiveBayes: property Kernel Kernel smoothing functions A cell array with one entry per predictor naming the smoothing kernel used by a 'kernel' predictor, and empty for every other. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Kernel smoothing functions # name: # type: sq_string # elements: 1 # length: 40 ClassificationNaiveBayes.ModelParameters # name: # type: sq_string # elements: 1 # length: 584 ClassificationNaiveBayes: property ModelParameters What was fitted, and how A structure carrying DistributionNames , Kernel , Support , Width , StandardizeData , Version , Method and Type . It records the arguments as they were given , where the properties of the same name record what they were resolved to: a model fitted with no 'DistributionNames' argument reports the single name 'normal' here and one name per predictor there. The kernel settings are filled in with their defaults when a kernel density was asked for, and left empty when none was. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 What was fitted, and how # name: # type: sq_string # elements: 1 # length: 27 ClassificationNaiveBayes.Mu # name: # type: sq_string # elements: 1 # length: 265 ClassificationNaiveBayes: property Mu Predictor means The means used to center the predictors, when the model standardizes them, and empty otherwise. These are not the class conditional means, which are held in DistributionParameters . This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Predictor means # name: # type: sq_string # elements: 1 # length: 40 ClassificationNaiveBayes.NumObservations # name: # type: sq_string # elements: 1 # length: 241 ClassificationNaiveBayes: property NumObservations Number of observations A positive integer specifying the number of observations used to train the model, after any row holding a missing value has been dropped. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 39 ClassificationNaiveBayes.PredictorNames # name: # type: sq_string # elements: 1 # length: 249 ClassificationNaiveBayes: property PredictorNames Predictor variable names A cell array of character vectors naming the predictors, in the order in which they appear in X . The default names are 'x1' , 'x2' , and so on. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Predictor variable names # name: # type: sq_string # elements: 1 # length: 30 ClassificationNaiveBayes.Prior # name: # type: sq_string # elements: 1 # length: 328 ClassificationNaiveBayes: property Prior Class prior probabilities A numeric row vector with one entry per class, in the order of ClassNames , summing to one. It may be assigned after fitting, as a numeric vector, as a structure carrying ClassNames and ClassProbs , or as 'empirical' or 'uniform' . Assigning it re-derives W . # name: # type: sq_string # elements: 1 # length: 25 Class prior probabilities # name: # type: sq_string # elements: 1 # length: 37 ClassificationNaiveBayes.ResponseName # name: # type: sq_string # elements: 1 # length: 165 ClassificationNaiveBayes: property ResponseName Response variable name A character vector naming the response variable, 'Y' by default. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 33 ClassificationNaiveBayes.RowsUsed # name: # type: sq_string # elements: 1 # length: 375 ClassificationNaiveBayes: property RowsUsed Rows used for fitting A logical column vector with the same length as the observations in the original predictor data X , true for each row that was used for fitting the model. It is empty, [] , when every observation was used, so a non-empty value means that rows holding missing values were dropped. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Rows used for fitting # name: # type: sq_string # elements: 1 # length: 39 ClassificationNaiveBayes.ScoreTransform # name: # type: sq_string # elements: 1 # length: 281 ClassificationNaiveBayes: property ScoreTransform Score transformation A character vector naming the function applied to the posterior returned by predict , or a function handle taking and returning a matrix of the same size. The default, 'none' , leaves the posterior untouched. # name: # type: sq_string # elements: 1 # length: 20 Score transformation # name: # type: sq_string # elements: 1 # length: 30 ClassificationNaiveBayes.Sigma # name: # type: sq_string # elements: 1 # length: 309 ClassificationNaiveBayes: property Sigma Predictor standard deviations The standard deviations used to scale the predictors, when the model standardizes them, and empty otherwise. These are not the class conditional standard deviations, which are held in DistributionParameters . This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Predictor standard deviations # name: # type: sq_string # elements: 1 # length: 32 ClassificationNaiveBayes.Support # name: # type: sq_string # elements: 1 # length: 227 ClassificationNaiveBayes: property Support Kernel smoothing supports A cell array with one entry per predictor giving the support of a 'kernel' predictor’s density, and empty for every other. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Kernel smoothing supports # name: # type: sq_string # elements: 1 # length: 26 ClassificationNaiveBayes.W # name: # type: sq_string # elements: 1 # length: 262 ClassificationNaiveBayes: property W Observation weights A numeric column vector with one entry per observation used for fitting, summing to one. Each class contributes its prior, spread evenly over the observations belonging to it. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 30 ClassificationNaiveBayes.Width # name: # type: sq_string # elements: 1 # length: 271 ClassificationNaiveBayes: property Width Kernel smoothing bandwidths A numeric matrix with one row per class and one column per predictor, giving the bandwidth of each 'kernel' predictor’s density, and empty when no predictor uses one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Kernel smoothing bandwidths # name: # type: sq_string # elements: 1 # length: 26 ClassificationNaiveBayes.X # name: # type: sq_string # elements: 1 # length: 224 ClassificationNaiveBayes: property X Predictor data A numeric matrix containing the predictor data. Each column of X represents one predictor (variable), and each row represents one observation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Predictor data # name: # type: sq_string # elements: 1 # length: 26 ClassificationNaiveBayes.Y # name: # type: sq_string # elements: 1 # length: 316 ClassificationNaiveBayes: property Y Class labels Specified as a logical or numeric column vector, or as a character array or a cell array of character vectors with the same number of rows as the predictor data. Each row in Y is the observed class label for the corresponding row in X . This property is read-only. # name: # type: sq_string # elements: 1 # length: 12 Class labels # name: # type: sq_string # elements: 1 # length: 32 ClassificationNaiveBayes.compact # name: # type: sq_string # elements: 1 # length: 394 ClassificationNaiveBayes: CMdl = compact ( obj ) Drop the training data from a trained model. CMdl = compact ( obj ) returns a CompactClassificationNaiveBayes object carrying the fitted densities and everything predict needs, but not the observations the model was fitted on. It classifies new data identically and is far smaller to keep or to ship. See also: CompactClassificationNaiveBayes # name: # type: sq_string # elements: 1 # length: 44 Drop the training data from a trained model. # name: # type: sq_string # elements: 1 # length: 33 ClassificationNaiveBayes.crossval # name: # type: sq_string # elements: 1 # length: 585 ClassificationNaiveBayes: CVMdl = crossval ( obj ) ClassificationNaiveBayes: CVMdl = crossval (…, name , value ) Cross-validate a trained naive Bayes model. CVMdl = crossval ( obj ) partitions the training data into ten folds, or into as many folds as there are observations when there are fewer than ten, refits the model on each fold’s training part and returns a ClassificationPartitionedModel . CVMdl = crossval (…, name , value ) takes exactly one of 'KFold' , 'Holdout' , 'Leaveout' or 'CVPartition' . See also: ClassificationPartitionedModel, cvpartition # name: # type: sq_string # elements: 1 # length: 43 Cross-validate a trained naive Bayes model. # name: # type: sq_string # elements: 1 # length: 29 ClassificationNaiveBayes.edge # name: # type: sq_string # elements: 1 # length: 388 ClassificationNaiveBayes: e = edge ( obj , X , Y ) ClassificationNaiveBayes: e = edge (…, 'Weights' , w ) Classification edge on new data. e = edge ( obj , X , Y ) returns the weighted mean of the margins, a single number summarising how confidently the model classifies the data. The weights are normalized within each class to that class’s prior before they are applied. # name: # type: sq_string # elements: 1 # length: 32 Classification edge on new data. # name: # type: sq_string # elements: 1 # length: 29 ClassificationNaiveBayes.logp # name: # type: sq_string # elements: 1 # length: 398 ClassificationNaiveBayes: lp = logp ( obj , X ) Log unconditional probability density of new data. lp = logp ( obj , X ) returns one value per observation, the logarithm of its density under the fitted model taken over all the classes, each weighted by its prior. A markedly low value marks an observation the model finds unlike anything it was trained on, whatever class it would be assigned to. # name: # type: sq_string # elements: 1 # length: 50 Log unconditional probability density of new data. # name: # type: sq_string # elements: 1 # length: 29 ClassificationNaiveBayes.loss # name: # type: sq_string # elements: 1 # length: 511 ClassificationNaiveBayes: l = loss ( obj , X , Y ) ClassificationNaiveBayes: l = loss (…, name , value ) Classification loss on new data. l = loss ( obj , X , Y ) returns the minimum expected misclassification cost. l = loss (…, name , value ) takes the following options. Name Value 'LossFun' One of 'binodeviance' , 'classifcost' , 'classiferror' , 'exponential' , 'hinge' , 'logit' , 'mincost' (default) or 'quadratic' . 'Weights' A numeric vector of observation weights, one per row of X . # name: # type: sq_string # elements: 1 # length: 32 Classification loss on new data. # name: # type: sq_string # elements: 1 # length: 31 ClassificationNaiveBayes.margin # name: # type: sq_string # elements: 1 # length: 394 ClassificationNaiveBayes: m = margin ( obj , X , Y ) Classification margin on new data. m = margin ( obj , X , Y ) returns one margin per observation: the posterior the model gives the observation’s true class, less the largest posterior it gives any other class. A positive margin means the observation is classified correctly, and a larger one means it is classified more confidently. # name: # type: sq_string # elements: 1 # length: 34 Classification margin on new data. # name: # type: sq_string # elements: 1 # length: 32 ClassificationNaiveBayes.predict # name: # type: sq_string # elements: 1 # length: 627 ClassificationNaiveBayes: label = predict ( obj , XC ) ClassificationNaiveBayes: [ label , score , cost ] = predict ( obj , XC ) Classify new data with a trained ClassificationNaiveBayes object. label = predict ( obj , XC ) returns the predicted class label for each row of XC , which must have as many columns as the predictor data the model was fitted on. [ label , score , cost ] = predict ( obj , XC ) also returns score , the posterior probability of each class, and cost , the expected misclassification cost of assigning each observation to each class. The label of an observation is the class of least expected cost. # name: # type: sq_string # elements: 1 # length: 65 Classify new data with a trained ClassificationNaiveBayes object. # name: # type: sq_string # elements: 1 # length: 34 ClassificationNaiveBayes.resubEdge # name: # type: sq_string # elements: 1 # length: 90 ClassificationNaiveBayes: e = resubEdge ( obj ) Classification edge on the training data. # name: # type: sq_string # elements: 1 # length: 41 Classification edge on the training data. # name: # type: sq_string # elements: 1 # length: 34 ClassificationNaiveBayes.resubLoss # name: # type: sq_string # elements: 1 # length: 190 ClassificationNaiveBayes: l = resubLoss ( obj ) ClassificationNaiveBayes: l = resubLoss (…, name , value ) Classification loss on the training data. Takes the same options as loss . # name: # type: sq_string # elements: 1 # length: 41 Classification loss on the training data. # name: # type: sq_string # elements: 1 # length: 36 ClassificationNaiveBayes.resubMargin # name: # type: sq_string # elements: 1 # length: 94 ClassificationNaiveBayes: m = resubMargin ( obj ) Classification margin on the training data. # name: # type: sq_string # elements: 1 # length: 43 Classification margin on the training data. # name: # type: sq_string # elements: 1 # length: 37 ClassificationNaiveBayes.resubPredict # name: # type: sq_string # elements: 1 # length: 307 ClassificationNaiveBayes: label = resubPredict ( obj ) ClassificationNaiveBayes: [ label , score , cost ] = resubPredict ( obj ) Classify the training data with the trained model. The same as calling predict on the data the model was fitted on, with the rows that were dropped for missing values left out. # name: # type: sq_string # elements: 1 # length: 50 Classify the training data with the trained model. # name: # type: sq_string # elements: 1 # length: 34 ClassificationNaiveBayes.savemodel # name: # type: sq_string # elements: 1 # length: 499 ClassificationNaiveBayes: savemodel ( obj , filename ) Save a ClassificationNaiveBayes object. savemodel ( obj , filename ) saves each property of a ClassificationNaiveBayes object into an Octave binary file, the name of which is specified in filename , along with an extra variable, which defines the type classification object these variables constitute. Use loadmodel in order to load a classification object into Octave’s workspace. See also: loadmodel, fitcnb, ClassificationNaiveBayes # name: # type: sq_string # elements: 1 # length: 39 Save a ClassificationNaiveBayes object. # name: # type: sq_string # elements: 1 # length: 27 ClassificationNeuralNetwork # name: # type: sq_string # elements: 1 # length: 591 statistics: ClassificationNeuralNetwork Neural network classification The ClassificationNeuralNetwork class implements a neural network classifier object, which can predict responses for new data using the predict method. Neural network classification is a machine learning method that uses interconnected nodes in multiple layers to learn complex patterns in data. It processes inputs through hidden layers with activation functions to produce classification outputs. Create a ClassificationNeuralNetwork object by using the fitcnet function or the class constructor. See also: fitcnet # name: # type: sq_string # elements: 1 # length: 29 Neural network classification # name: # type: sq_string # elements: 1 # length: 39 ClassificationNeuralNetwork.Activations # name: # type: sq_string # elements: 1 # length: 391 ClassificationNeuralNetwork: property Activations Activation functions for hidden layers A character vector or cell array of character vectors specifying the activation functions used in the hidden layers of the neural network. Supported activation functions include: 'linear' , 'sigmoid' , 'relu' , 'tanh' , 'softmax' , 'lrelu' , 'prelu' , 'elu' , and 'gelu' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Activation functions for hidden layers # name: # type: sq_string # elements: 1 # length: 36 ClassificationNeuralNetwork.BinEdges # name: # type: sq_string # elements: 1 # length: 375 ClassificationNeuralNetwork: property BinEdges Bin edges of the predictors A cell array with one entry per predictor, holding that predictor’s bin edges where the learner discretized it before fitting. It is empty here and stays empty: this learner fits the predictors as they are, and MATLAB’s reports an empty cell for it as well. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Bin edges of the predictors # name: # type: sq_string # elements: 1 # length: 49 ClassificationNeuralNetwork.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 239 ClassificationNeuralNetwork: property CategoricalPredictors Indices of the categorical predictors A numeric vector of column indices into X naming the predictors treated as categorical, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 38 ClassificationNeuralNetwork.ClassNames # name: # type: sq_string # elements: 1 # length: 355 ClassificationNeuralNetwork: property ClassNames Names of classes in the response variable An array of unique values of the response variable Y , which has the same data types as the data in Y . This property is read-only. ClassNames can have any of the following datatypes: Cell array of character vectors Character array Logical vector Numeric vector # name: # type: sq_string # elements: 1 # length: 41 Names of classes in the response variable # name: # type: sq_string # elements: 1 # length: 55 ClassificationNeuralNetwork.ClassificationNeuralNetwork # name: # type: sq_string # elements: 1 # length: 4675 statistics: obj = ClassificationNeuralNetwork ( X , Y ) statistics: obj = ClassificationNeuralNetwork (…, name , value ) Create a ClassificationNeuralNetwork class object containing a neural network classification model. obj = ClassificationNeuralNetwork ( X , Y ) returns a ClassificationNeuralNetwork object, with X as the predictor data and Y containing the class labels of observations in X . X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. X will be used to train the neural network model. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y can contain any type of categorical data. Y must have the same number of rows as X . obj = ClassificationNeuralNetwork (…, name , value ) returns a ClassificationNeuralNetwork object with parameters specified by the following name , value paired input arguments: Name Value 'PredictorNames' A cell array of character vectors specifying the names of the predictors. The length of this array must match the number of columns in X . 'ResponseName' A character vector specifying the name of the response variable. 'ClassNames' Names of the classes in the class labels, Y , used for fitting the neural network model. ClassNames are of the same type as the class labels in Y . 'ScoreTransform' A user-defined function handle or a character vector specifying one of the following builtin functions specifying the transformation applied to predicted classification scores. Supported values include 'doublelogit' , 'invlogit' , 'ismax' , 'logit' , 'none' , 'identity' , 'sign' , 'symmetric' , 'symmetricismax' , and 'symmetriclogit' . 'Standardize' A logical scalar specifying whether to standardize the predictor data. When true , the predictors are centered and scaled to have zero mean and unit variance. 'LayerSizes' A positive integer vector specifying the sizes of the fully connected layers in the neural network. The default is 10. 'Activations' A character vector or cell array of character vectors specifying the activation functions for the hidden layers. Supported values include 'linear' , 'sigmoid' , 'relu' , 'tanh' , 'softmax' , 'lrelu' , 'prelu' , 'elu' , and 'gelu' . The default is 'relu' , whose gradient is one wherever a unit is active and so does not shrink as it passes back through the layers, where a sigmoid multiplies it by at most a quarter at every one. 'OutputLayerActivation' A character vector specifying the activation function for the output layer. Supported values are the same as for 'Activations' . The default is 'softmax' , which makes the scores a probability over the classes and trains the network against cross entropy; any other value trains it against the mean squared error. 'LearningRate' A positive scalar specifying the learning rate for gradient descent. The default is 0.003. A larger rate can drive every unit of a hidden layer negative, after which a rectifier passes no gradient and the network stops training. Applies only when 'Solver' is 'sgd' . 'Solver' A character vector naming the solver that trains the network, either 'lbfgs' or 'sgd' . The default is 'lbfgs' , which minimizes the loss over the whole training set at once by limited-memory BFGS, as MATLAB does. It takes no learning rate, stops on the three tolerances below, and reaches a lower training loss in fewer passes over the data, though each of its iterations costs several passes where an epoch costs one. 'sgd' visits the samples one at a time and steps down the gradient of each, running for 'IterationLimit' epochs; it was the default before version 1.9.0. 'GradientTolerance' A nonnegative scalar. Training stops once the gradient’s infinity norm falls to or below it, which is the quantity MATLAB tests too. The default is 1e-6 . Applies only when 'Solver' is 'lbfgs' . 'StepTolerance' A nonnegative scalar. Training stops once the step’s infinity norm falls to or below it, which is the quantity MATLAB tests too. The default is 1e-6 . Applies only when 'Solver' is 'lbfgs' . 'LossTolerance' A real scalar. Training stops once the training loss falls to or below it. The test is on the loss itself and not on its change, matching MATLAB; pass -Inf to switch it off. The default is 1e-6 . Applies only when 'Solver' is 'lbfgs' . 'IterationLimit' A positive integer specifying the maximum number of training iterations. The default is 1000. Under 'sgd' this counts epochs, under 'lbfgs' solver iterations. 'DisplayInfo' A logical scalar specifying whether to display training information. The default is false . See also: fitcnet # name: # type: sq_string # elements: 1 # length: 99 Create a ClassificationNeuralNetwork class object containing a neural network classification model. # name: # type: sq_string # elements: 1 # length: 43 ClassificationNeuralNetwork.ConvergenceInfo # name: # type: sq_string # elements: 1 # length: 749 ClassificationNeuralNetwork: property ConvergenceInfo Training convergence information A structure containing convergence information of the neural network classifier model with the following fields: Accuracy - The prediction accuracy at each iteration during training TrainingLoss - The loss value recorded at each iteration during training Time - The cumulative time taken for all iterations in seconds This property is read-only. Under 'lbfgs' the structure carries Gradient and Step , the two quantities the solver measured to decide it had converged, and ConvergenceCriterion , naming the test that stopped it. It carries no Accuracy : MATLAB reports none, and measuring it would cost a pass over the whole training set at every iteration. # name: # type: sq_string # elements: 1 # length: 32 Training convergence information # name: # type: sq_string # elements: 1 # length: 32 ClassificationNeuralNetwork.Cost # name: # type: sq_string # elements: 1 # length: 796 ClassificationNeuralNetwork: property Cost Cost of misclassification A numeric matrix with one row and one column per class, where Cost(i,j) is the cost of classifying an observation of class i as class j . The default has zeros on the diagonal and ones elsewhere. Change it on a trained model with dot notation, as in obj .Cost = cost . A cost may also be given as a struct with the fields ClassNames and ClassificationCosts , which names the order its own matrix is written in. That matrix is permuted into the order of ClassNames above, so a caller need not know which order the classes were sorted into. It must name every class. A cost must be floating point, not sparse, not complex, non-negative and zero down its diagonal, and must hold no NaN or Inf . A single is widened to double . # name: # type: sq_string # elements: 1 # length: 25 Cost of misclassification # name: # type: sq_string # elements: 1 # length: 39 ClassificationNeuralNetwork.DisplayInfo # name: # type: sq_string # elements: 1 # length: 185 ClassificationNeuralNetwork: property DisplayInfo Display training information flag A boolean flag indicating whether to print information during training. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Display training information flag # name: # type: sq_string # elements: 1 # length: 50 ClassificationNeuralNetwork.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 272 ClassificationNeuralNetwork: property ExpandedPredictorNames Names of the predictors as the model expanded them A cell array of character vectors. It matches PredictorNames unless a categorical predictor was expanded into indicator variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 50 Names of the predictors as the model expanded them # name: # type: sq_string # elements: 1 # length: 61 ClassificationNeuralNetwork.HyperparameterOptimizationResults # name: # type: sq_string # elements: 1 # length: 370 ClassificationNeuralNetwork: property HyperparameterOptimizationResults Results of the hyperparameter optimization Always empty. It is declared for MATLAB compatibility, where it holds what an automatic search over the hyperparameters found. This class fits the parameters it is given and runs no such search, so there is nothing to report. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Results of the hyperparameter optimization # name: # type: sq_string # elements: 1 # length: 42 ClassificationNeuralNetwork.IterationLimit # name: # type: sq_string # elements: 1 # length: 207 ClassificationNeuralNetwork: property IterationLimit Maximum number of training iterations A positive integer value defining the maximum number of epochs for training the model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Maximum number of training iterations # name: # type: sq_string # elements: 1 # length: 39 ClassificationNeuralNetwork.LayerBiases # name: # type: sq_string # elements: 1 # length: 240 ClassificationNeuralNetwork: property LayerBiases Learned bias of each fully connected layer A cell array holding one column vector per layer, the output layer included, with one entry per neuron of that layer. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Learned bias of each fully connected layer # name: # type: sq_string # elements: 1 # length: 38 ClassificationNeuralNetwork.LayerSizes # name: # type: sq_string # elements: 1 # length: 480 ClassificationNeuralNetwork: property LayerSizes Sizes of fully connected layers A positive integer vector specifying the sizes of the fully connected layers in the neural network model. The i-th element of LayerSizes is the number of outputs in the i-th fully connected layer of the neural network model. LayerSizes does not include the size of the final fully connected layer. This layer always has K outputs, where K is the number of classes in Y. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Sizes of fully connected layers # name: # type: sq_string # elements: 1 # length: 40 ClassificationNeuralNetwork.LayerWeights # name: # type: sq_string # elements: 1 # length: 291 ClassificationNeuralNetwork: property LayerWeights Learned weights of each fully connected layer A cell array holding one weight matrix per layer, the output layer included. LayerWeights{i} has one row per neuron of layer i and one column per input it receives. This property is read-only. # name: # type: sq_string # elements: 1 # length: 45 Learned weights of each fully connected layer # name: # type: sq_string # elements: 1 # length: 40 ClassificationNeuralNetwork.LearningRate # name: # type: sq_string # elements: 1 # length: 222 ClassificationNeuralNetwork: property LearningRate Learning rate for gradient descent A positive scalar value defining the learning rate used by the gradient descent algorithm during training. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Learning rate for gradient descent # name: # type: sq_string # elements: 1 # length: 43 ClassificationNeuralNetwork.ModelParameters # name: # type: sq_string # elements: 1 # length: 1174 ClassificationNeuralNetwork: property ModelParameters Neural network model parameters A structure holding the fit as it was asked for: LayerSizes , Activations , OutputLayerActivation , LayerWeightsInitializers , Solver , LearningRate , IterationLimit , GradientTolerance , LossTolerance , StepTolerance , DisplayInfo , StandardizeData , and the Version , Method and Type tags. What came out of the fit is elsewhere: the LayerWeights and LayerBiases properties hold the network, TrainingHistory the series and ConvergenceInfo where it stopped. LayerWeightsInitializers names the scheme each layer’s weights were drawn with, the output layer last: 'he' for a rectifying activation and 'glorot' for a symmetric one. It is a report, not a setting, the engine choosing per layer from the activation and offering no way to override it. OutputLayerActivation , Solver and LearningRate are this package’s own; MATLAB has no counterpart for them. The fields it reports that this class does not accept as arguments ( Lambda , the validation set and its patience and frequency, InitialStepSize and the two initializer settings) are absent. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Neural network model parameters # name: # type: sq_string # elements: 1 # length: 30 ClassificationNeuralNetwork.Mu # name: # type: sq_string # elements: 1 # length: 410 ClassificationNeuralNetwork: property Mu Predictor means A numeric vector containing the means of the predictors used for standardization. Empty when the predictor data were not standardized. This property is read-only. Only observations with no missing predictor enter the estimate, and they are weighted so that each class keeps the share of the observation weight it carried before any row was set aside. # name: # type: sq_string # elements: 1 # length: 15 Predictor means # name: # type: sq_string # elements: 1 # length: 43 ClassificationNeuralNetwork.NumObservations # name: # type: sq_string # elements: 1 # length: 250 ClassificationNeuralNetwork: property NumObservations Number of observations A positive integer value specifying the number of observations in the training dataset used for training the ClassificationNeuralNetwork model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 41 ClassificationNeuralNetwork.NumPredictors # name: # type: sq_string # elements: 1 # length: 244 ClassificationNeuralNetwork: property NumPredictors Number of predictors A positive integer value specifying the number of predictors in the training dataset used for training the ClassificationNeuralNetwork model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 49 ClassificationNeuralNetwork.OutputLayerActivation # name: # type: sq_string # elements: 1 # length: 449 ClassificationNeuralNetwork: property OutputLayerActivation Activation function for output layer A character vector specifying the activation function of the output layer of the neural network. Supported activation functions are the same as for the Activations property. The default, softmax , reports a probability over the classes; the network is then trained against cross entropy rather than the mean squared error. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Activation function for output layer # name: # type: sq_string # elements: 1 # length: 42 ClassificationNeuralNetwork.PredictorNames # name: # type: sq_string # elements: 1 # length: 267 ClassificationNeuralNetwork: property PredictorNames Names of predictor variables A cell array of character vectors specifying the names of the predictor variables. The names are in the order in which they appear in the training dataset. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Names of predictor variables # name: # type: sq_string # elements: 1 # length: 33 ClassificationNeuralNetwork.Prior # name: # type: sq_string # elements: 1 # length: 623 ClassificationNeuralNetwork: property Prior Prior probability of each class A numeric vector with one entry per class, in the order of ClassNames , summing to one. It defaults to the relative frequency of each class in the training data. This property is read-only, as MATLAB documents it; pass 'Prior' to fitcnet to set it. Specified as a row vector with one entry per class, in the order of ClassNames , and rescaled to sum to one. It may be given as 'empirical' , 'uniform' , a numeric vector, or a structure with ClassNames and ClassProbs fields, which assigns each probability by class name rather than by position. # name: # type: sq_string # elements: 1 # length: 31 Prior probability of each class # name: # type: sq_string # elements: 1 # length: 40 ClassificationNeuralNetwork.ResponseName # name: # type: sq_string # elements: 1 # length: 171 ClassificationNeuralNetwork: property ResponseName Response variable name A character vector specifying the name of the response variable Y . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 36 ClassificationNeuralNetwork.RowsUsed # name: # type: sq_string # elements: 1 # length: 406 ClassificationNeuralNetwork: property RowsUsed Rows used for fitting A logical column vector with the same length as the observations in the original predictor data X , true for each row that was used for fitting the ClassificationNeuralNetwork model. It is empty, [] , when every observation was used, so a non-empty value means that rows holding missing values were dropped. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Rows used for fitting # name: # type: sq_string # elements: 1 # length: 42 ClassificationNeuralNetwork.ScoreTransform # name: # type: sq_string # elements: 1 # length: 1007 ClassificationNeuralNetwork: property ScoreTransform Transformation function for classification scores Specified as a function handle for transforming the classification scores. Add or change the ScoreTransform property using dot notation as in: obj .ScoreTransform = 'function_name' obj .ScoreTransform = @function_handle When specified as a character vector, it can be any of the following built-in functions. Nevertheless, the ScoreTransform property always stores their function handle equivalent. Value Description 'doublelogit' 1 ./ (1 + exp (-2 × x)) 'invlogit' log (x ./ (1 - x)) 'ismax' Sets the score for the class with the largest score to 1, and for all other classes to 0 'logit' 1 ./ (1 + exp (-x)) 'none' x (no transformation) 'identity' x (no transformation) 'sign' -1 for x < 0, 0 for x = 0, 1 for x > 0 'symmetric' 2 × x - 1 'symmetricismax' Sets the score for the class with the largest score to 1, and for all other classes to -1 'symmetriclogit' 2 ./ (1 + exp (-x)) - 1 # name: # type: sq_string # elements: 1 # length: 49 Transformation function for classification scores # name: # type: sq_string # elements: 1 # length: 33 ClassificationNeuralNetwork.Sigma # name: # type: sq_string # elements: 1 # length: 441 ClassificationNeuralNetwork: property Sigma Predictor standard deviations A numeric vector containing the standard deviations of the predictors used for standardization. Empty when the predictor data were not standardized. This property is read-only. Only observations with no missing predictor enter the estimate, and they are weighted so that each class keeps the share of the observation weight it carried before any row was set aside. # name: # type: sq_string # elements: 1 # length: 29 Predictor standard deviations # name: # type: sq_string # elements: 1 # length: 34 ClassificationNeuralNetwork.Solver # name: # type: sq_string # elements: 1 # length: 277 ClassificationNeuralNetwork: property Solver Solver used for training A character vector specifying the solver algorithm used to train the neural network model, either 'Gradient Descent' for the stochastic solver or 'LBFGS' for the full-batch one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Solver used for training # name: # type: sq_string # elements: 1 # length: 43 ClassificationNeuralNetwork.TrainingHistory # name: # type: sq_string # elements: 1 # length: 466 ClassificationNeuralNetwork: property TrainingHistory Iteration by iteration record of training A table with one row per iteration, holding the iteration number, the training loss and the training accuracy recorded at it. This property is read-only. The columns follow the solver. Under 'sgd' they are Iteration and TrainingLoss , with TrainingAccuracy for a classifier. Under 'lbfgs' they are Iteration , TrainingLoss , Gradient and Step , as MATLAB’s are. # name: # type: sq_string # elements: 1 # length: 41 Iteration by iteration record of training # name: # type: sq_string # elements: 1 # length: 29 ClassificationNeuralNetwork.W # name: # type: sq_string # elements: 1 # length: 385 ClassificationNeuralNetwork: property W Observation weights A numeric column vector with one entry per training observation. It defaults to a uniform weight for every observation. This property is read-only. Each class carries its prior spread evenly over its own observations, so an observation of a class weighs Prior for that class divided by the number of observations it holds. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 29 ClassificationNeuralNetwork.X # name: # type: sq_string # elements: 1 # length: 242 ClassificationNeuralNetwork: property X Predictor data A numeric matrix containing the unstandardized predictor data. Each column of X represents one predictor (variable), and each row represents one observation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Predictor data # name: # type: sq_string # elements: 1 # length: 29 ClassificationNeuralNetwork.Y # name: # type: sq_string # elements: 1 # length: 319 ClassificationNeuralNetwork: property Y Class labels Specified as a logical or numeric column vector, or as a character array or a cell array of character vectors with the same number of rows as the predictor data. Each row in Y is the observed class label for the corresponding row in X . This property is read-only. # name: # type: sq_string # elements: 1 # length: 12 Class labels # name: # type: sq_string # elements: 1 # length: 35 ClassificationNeuralNetwork.compact # name: # type: sq_string # elements: 1 # length: 289 ClassificationNeuralNetwork: CVMdl = compact ( obj ) Create a CompactClassificationNeuralNetwork object. CVMdl = compact ( obj ) creates a compact version of the ClassificationNeuralNetwork object, obj . See also: fitcnet, ClassificationNeuralNetwork, CompactClassificationNeuralNetwork # name: # type: sq_string # elements: 1 # length: 51 Create a CompactClassificationNeuralNetwork object. # name: # type: sq_string # elements: 1 # length: 36 ClassificationNeuralNetwork.crossval # name: # type: sq_string # elements: 1 # length: 1079 ClassificationNeuralNetwork: CVMdl = crossval ( obj ) ClassificationNeuralNetwork: CVMdl = crossval (…, Name , Value ) Cross Validate a Neural Network classification object. CVMdl = crossval ( obj ) returns a cross-validated model object, CVMdl , from a trained model, obj , using 10-fold cross-validation by default. CVMdl = crossval ( obj , name , value ) specifies additional name-value pair arguments to customize the cross-validation process. Name Value 'KFold' Specify the number of folds to use in k-fold cross-validation. "KFold", k , where k is an integer greater than 1. 'Holdout' Specify the fraction of the data to hold out for testing. "Holdout", p , where p is a scalar in the range (0,1) . 'Leaveout' Specify whether to perform leave-one-out cross-validation. "Leaveout", Value , where Value is ’on’ or ’off’. 'CVPartition' Specify a cvpartition object used for cross-validation. "CVPartition", cv , where isa ( cv , "cvpartition") = 1. See also: fitcnet, ClassificationNeuralNetwork, cvpartition, ClassificationPartitionedModel # name: # type: sq_string # elements: 1 # length: 54 Cross Validate a Neural Network classification object. # name: # type: sq_string # elements: 1 # length: 32 ClassificationNeuralNetwork.edge # name: # type: sq_string # elements: 1 # length: 499 ClassificationNeuralNetwork: e = edge ( obj , X , Y ) ClassificationNeuralNetwork: e = edge (…, "Weights" , w ) Classification edge of a neural network classifier. e = edge ( obj , X , Y ) returns the mean of the classification margins over the rows of X . e = edge (…, "Weights" , w ) takes the weighted mean instead, w holding one weight per row of X . The weights are normalised to sum to one before they are applied. See also: ClassificationNeuralNetwork, margin, loss, predict # name: # type: sq_string # elements: 1 # length: 51 Classification edge of a neural network classifier. # name: # type: sq_string # elements: 1 # length: 32 ClassificationNeuralNetwork.loss # name: # type: sq_string # elements: 1 # length: 1124 ClassificationNeuralNetwork: L = loss ( obj , X , Y ) ClassificationNeuralNetwork: L = loss (…, name , value ) Classification loss of a neural network classifier. L = loss ( obj , X , Y ) returns the proportion of the rows of X the model misclassifies against the true labels Y . L = loss (…, name , value ) accepts the following name-value pairs: "LossFun" selects the loss. Supported values are "mincost" , the default, "binodeviance" , "classifcost" , "classiferror" , "crossentropy" , "exponential" , "hinge" , "logit" and "quadratic" . "mincost" assigns each observation to the class of least expected cost and charges what that assignment costs, so it reads the scores as a posterior; "classifcost" charges what the model’s own prediction costs. "crossentropy" is defined for a network only. Note that the default differs from the other classifiers in this package, which default to "classiferror" , and follows MATLAB’s for this class. "Weights" holds one weight per row of X , normalised to sum to one before it is applied. See also: ClassificationNeuralNetwork, margin, edge, predict # name: # type: sq_string # elements: 1 # length: 51 Classification loss of a neural network classifier. # name: # type: sq_string # elements: 1 # length: 34 ClassificationNeuralNetwork.margin # name: # type: sq_string # elements: 1 # length: 457 ClassificationNeuralNetwork: m = margin ( obj , X , Y ) Classification margin of a neural network classifier. m = margin ( obj , X , Y ) returns a column vector holding, for each row of X , the score the model gives its true class in Y less the largest score it gives any other class. A positive margin means the observation is classified correctly, and the larger it is the more confidently so. See also: ClassificationNeuralNetwork, edge, loss, predict # name: # type: sq_string # elements: 1 # length: 53 Classification margin of a neural network classifier. # name: # type: sq_string # elements: 1 # length: 35 ClassificationNeuralNetwork.predict # name: # type: sq_string # elements: 1 # length: 1233 ClassificationNeuralNetwork: label = predict ( obj , XC ) ClassificationNeuralNetwork: [ label , score ] = predict ( obj , XC ) Classify new data points into categories using the neural network classification model from a ClassificationNeuralNetwork object. label = predict ( obj , XC ) returns the vector of labels predicted for the corresponding instances in XC , using the predictor data in obj.X and corresponding labels, obj.Y , stored in the ClassificationNeuralNetwork model, obj . obj must be a ClassificationNeuralNetwork class object. XC must be an M×P numeric matrix with the same number of features P as the corresponding predictors of the neural network model in obj . [ label , score ] = predict ( obj , XC ) also returns score , which contains the predicted class scores or posterior probabilities for each instance of the corresponding unique classes. The score matrix contains the classification scores for each class. For each observation in XC , the predicted class label is the one with the highest score among all classes. If the ScoreTransform property is set to a transformation function, the scores are transformed accordingly before being returned. See also: ClassificationNeuralNetwork, fitcnet # name: # type: sq_string # elements: 1 # length: 129 Classify new data points into categories using the neural network classification model from a ClassificationNeuralNetwork object. # name: # type: sq_string # elements: 1 # length: 37 ClassificationNeuralNetwork.resubEdge # name: # type: sq_string # elements: 1 # length: 273 ClassificationNeuralNetwork: e = resubEdge ( obj ) Classification edge of a neural network classifier on its training data. e = resubEdge ( obj ) is edge applied to the observations the model was fitted on, weighted by obj.W . See also: ClassificationNeuralNetwork, edge # name: # type: sq_string # elements: 1 # length: 72 Classification edge of a neural network classifier on its training data. # name: # type: sq_string # elements: 1 # length: 37 ClassificationNeuralNetwork.resubLoss # name: # type: sq_string # elements: 1 # length: 387 ClassificationNeuralNetwork: L = resubLoss ( obj ) ClassificationNeuralNetwork: L = resubLoss (…, name , value ) Classification loss of a neural network classifier on its training data. L = resubLoss ( obj ) is loss applied to the observations the model was fitted on, weighted by obj.W . It takes the same "LossFun" name-value pair. See also: ClassificationNeuralNetwork, loss # name: # type: sq_string # elements: 1 # length: 72 Classification loss of a neural network classifier on its training data. # name: # type: sq_string # elements: 1 # length: 39 ClassificationNeuralNetwork.resubMargin # name: # type: sq_string # elements: 1 # length: 263 ClassificationNeuralNetwork: m = resubMargin ( obj ) Classification margin of a neural network classifier on its training data. m = resubMargin ( obj ) is margin applied to the observations the model was fitted on. See also: ClassificationNeuralNetwork, margin # name: # type: sq_string # elements: 1 # length: 74 Classification margin of a neural network classifier on its training data. # name: # type: sq_string # elements: 1 # length: 40 ClassificationNeuralNetwork.resubPredict # name: # type: sq_string # elements: 1 # length: 751 ClassificationNeuralNetwork: label = resubPredict ( obj ) ClassificationNeuralNetwork: [ label , score ] = resubPredict ( obj ) Classify the training data using the trained neural network classification object. label = resubPredict ( obj ) returns the vector of labels predicted for the corresponding instances in the training data, using the predictor data in obj.X and corresponding labels, obj.Y , stored in the neural network classification model, obj . obj must be a ClassificationNeuralNetwork class object. [ label , score ] = resubPredict ( obj ) also returns score , which contains the predicted class scores or posterior probabilities for each instance of the corresponding unique classes. See also: ClassificationNeuralNetwork, fitcnet # name: # type: sq_string # elements: 1 # length: 82 Classify the training data using the trained neural network classification object. # name: # type: sq_string # elements: 1 # length: 37 ClassificationNeuralNetwork.savemodel # name: # type: sq_string # elements: 1 # length: 512 ClassificationNeuralNetwork: savemodel ( obj , filename ) Save a ClassificationNeuralNetwork object. savemodel ( obj , filename ) saves each property of a ClassificationNeuralNetwork object into an Octave binary file, the name of which is specified in filename , along with an extra variable, which defines the type classification object these variables constitute. Use loadmodel in order to load a classification object into Octave’s workspace. See also: loadmodel, fitcnet, ClassificationNeuralNetwork # name: # type: sq_string # elements: 1 # length: 42 Save a ClassificationNeuralNetwork object. # name: # type: sq_string # elements: 1 # length: 31 ClassificationPartitionedKernel # name: # type: sq_string # elements: 1 # length: 925 statistics: ClassificationPartitionedKernel Cross-validated Gaussian kernel binary classifier. A ClassificationPartitionedKernel object holds one ClassificationKernel per fold of a partition, each fitted to the observations the fold trains on. Every kfold method predicts each observation with the fold that held it out , so the estimate it returns is an out-of-sample one. A ClassificationKernel stores no copy of its training data and so has no resubstitution methods and no compact form. This class is what takes their place: cross-validation is the way a linear model is asked how it would do on data it has not seen. When the fold models carry a whole regularization path, every method returns one column per strength, in the order of the 'Lambda' that was asked for. Create one with fitclinear and a cross-validation option, or directly. See also: fitclinear, ClassificationKernel, ClassificationPartitionedKernel # name: # type: sq_string # elements: 1 # length: 50 Cross-validated Gaussian kernel binary classifier. # name: # type: sq_string # elements: 1 # length: 53 ClassificationPartitionedKernel.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 202 ClassificationPartitionedKernel: property CategoricalPredictors Indices of the categorical predictors A row vector of column indices, empty when every predictor is numeric. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 42 ClassificationPartitionedKernel.ClassNames # name: # type: sq_string # elements: 1 # length: 172 ClassificationPartitionedKernel: property ClassNames Names of the two classes A column of the same type as the response, shared by every fold. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Names of the two classes # name: # type: sq_string # elements: 1 # length: 63 ClassificationPartitionedKernel.ClassificationPartitionedKernel # name: # type: sq_string # elements: 1 # length: 1069 ClassificationPartitionedKernel: obj = ClassificationPartitionedKernel ( X , Y ) ClassificationPartitionedKernel: obj = ClassificationPartitionedKernel (…, name , value ) Cross-validate a linear binary classifier. obj = ClassificationPartitionedKernel ( X , Y ) partitions the data into ten stratified folds and fits a ClassificationKernel to each. obj = ClassificationPartitionedKernel (…, name , value ) takes one of 'KFold' , 'Holdout' , 'Leaveout' and 'CVPartition' to say how to partition, and any option ClassificationKernel takes to say how to fit. 'CrossVal' is accepted and has no effect here, this class being cross-validated by construction. The classes, the prior and the cost are resolved once over the whole data and handed to every fold. Anything left as 'auto' is not: each fold resolves 'Lambda' and 'KernelScale' against its own training rows, so ten folds of a hundred observations each get a Lambda of one ninetieth rather than one hundredth. Both are MATLAB’s behaviour, measured. See also: fitclinear, ClassificationKernel # name: # type: sq_string # elements: 1 # length: 42 Cross-validate a linear binary classifier. # name: # type: sq_string # elements: 1 # length: 36 ClassificationPartitionedKernel.Cost # name: # type: sq_string # elements: 1 # length: 236 ClassificationPartitionedKernel: property Cost Cost of misclassifying an observation A square numeric matrix with one row and one column per class. It is handed to every fold rather than re-derived by each. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Cost of misclassifying an observation # name: # type: sq_string # elements: 1 # length: 51 ClassificationPartitionedKernel.CrossValidatedModel # name: # type: sq_string # elements: 1 # length: 180 ClassificationPartitionedKernel: property CrossValidatedModel Name of the model that was cross-validated Always 'Linear' , the short name MATLAB uses. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Name of the model that was cross-validated # name: # type: sq_string # elements: 1 # length: 37 ClassificationPartitionedKernel.KFold # name: # type: sq_string # elements: 1 # length: 222 ClassificationPartitionedKernel: property KFold Number of folds A positive integer scalar. A holdout partition has one fold and a leave-one-out partition has as many as there are observations. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Number of folds # name: # type: sq_string # elements: 1 # length: 47 ClassificationPartitionedKernel.ModelParameters # name: # type: sq_string # elements: 1 # length: 1155 ClassificationPartitionedKernel: property ModelParameters What was cross-validated, and how A structure holding the parameters the folds were fitted with, carried through from the learner that was cross validated, beside NLearn , the number of folds, and the Version , Method and Type tags of this class, with LearnerTemplates naming the backing. The learner’s own tags are replaced rather than kept, so a cross-validated SVM reports Method as 'PartitionedKernel' and not 'SVM' . Deviation from MATLAB. MATLAB reports the parameter record of the cross-validation ensemble here rather than of the learner, so it says nothing at all about how the folds were fitted: of its eighteen fields only the fold count, its partitioner and a fit template carry anything, and the rest are boosting settings left inert. Nor can the parameters be reached through the folds, a compact model carrying none in MATLAB. This class reports the fit instead, which is strictly more than MATLAB offers, and everything MATLAB’s record does carry is published here as the KFold , Partition , X , Y , W and CrossValidatedModel properties. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 What was cross-validated, and how # name: # type: sq_string # elements: 1 # length: 47 ClassificationPartitionedKernel.NumObservations # name: # type: sq_string # elements: 1 # length: 221 ClassificationPartitionedKernel: property NumObservations Number of observations the partition covers A positive integer scalar, counting the rows that survived the removal of missing values. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Number of observations the partition covers # name: # type: sq_string # elements: 1 # length: 41 ClassificationPartitionedKernel.Partition # name: # type: sq_string # elements: 1 # length: 155 ClassificationPartitionedKernel: property Partition The partition itself A cvpartition object over the retained observations. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 The partition itself # name: # type: sq_string # elements: 1 # length: 46 ClassificationPartitionedKernel.PredictorNames # name: # type: sq_string # elements: 1 # length: 145 ClassificationPartitionedKernel: property PredictorNames Names of the predictors A cell array of character vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Names of the predictors # name: # type: sq_string # elements: 1 # length: 37 ClassificationPartitionedKernel.Prior # name: # type: sq_string # elements: 1 # length: 321 ClassificationPartitionedKernel: property Prior Prior probability of each class A numeric row vector summing to one, in the order of ClassNames . Like the cost it is the parent’s and is handed to every fold, so a fold of an unbalanced problem does not quietly adopt a prior of its own. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Prior probability of each class # name: # type: sq_string # elements: 1 # length: 44 ClassificationPartitionedKernel.ResponseName # name: # type: sq_string # elements: 1 # length: 145 ClassificationPartitionedKernel: property ResponseName Name of the response A character vector, defaulting to 'Y' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Name of the response # name: # type: sq_string # elements: 1 # length: 46 ClassificationPartitionedKernel.ScoreTransform # name: # type: sq_string # elements: 1 # length: 460 ClassificationPartitionedKernel: property ScoreTransform Transformation applied to the predicted scores A character vector naming a transformation, or the text of the function handle that was supplied, which may be assigned after the model is built. It is applied once to the assembled scores and is not handed to the folds. A transform the learner implies, as 'logistic' implies 'logit' , does stay with the folds, and this one is then applied on top of it. # name: # type: sq_string # elements: 1 # length: 46 Transformation applied to the predicted scores # name: # type: sq_string # elements: 1 # length: 39 ClassificationPartitionedKernel.Trained # name: # type: sq_string # elements: 1 # length: 216 ClassificationPartitionedKernel: property Trained The models fitted to the folds A cell column with one ClassificationKernel per fold, each fitted to the observations its fold trains on. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 The models fitted to the folds # name: # type: sq_string # elements: 1 # length: 33 ClassificationPartitionedKernel.W # name: # type: sq_string # elements: 1 # length: 203 ClassificationPartitionedKernel: property W Observation weights An Nx1 numeric vector summing to one, normalized within each class to that class’s cost-adjusted prior. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 33 ClassificationPartitionedKernel.Y # name: # type: sq_string # elements: 1 # length: 143 ClassificationPartitionedKernel: property Y Response of the retained observations In the type it was supplied in. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Response of the retained observations # name: # type: sq_string # elements: 1 # length: 41 ClassificationPartitionedKernel.kfoldEdge # name: # type: sq_string # elements: 1 # length: 384 ClassificationPartitionedKernel: e = kfoldEdge ( obj ) ClassificationPartitionedKernel: e = kfoldEdge (…, name , value ) Weighted mean of the out-of-fold classification margins. e = kfoldEdge (…, name , value ) takes 'Folds' , a subset of the folds to average over, and 'Mode' , either 'average' , the default, or 'individual' , which returns one row per fold instead. # name: # type: sq_string # elements: 1 # length: 56 Weighted mean of the out-of-fold classification margins. # name: # type: sq_string # elements: 1 # length: 41 ClassificationPartitionedKernel.kfoldLoss # name: # type: sq_string # elements: 1 # length: 435 ClassificationPartitionedKernel: l = kfoldLoss ( obj ) ClassificationPartitionedKernel: l = kfoldLoss (…, name , value ) Out-of-fold classification loss. l = kfoldLoss ( obj ) returns the out-of-fold misclassification rate. l = kfoldLoss (…, name , value ) takes 'LossFun' , one of 'binodeviance' , 'classifcost' , 'classiferror' , 'exponential' , 'hinge' , 'logit' , 'mincost' and 'quadratic' ; 'Folds' ; and 'Mode' . # name: # type: sq_string # elements: 1 # length: 32 Out-of-fold classification loss. # name: # type: sq_string # elements: 1 # length: 43 ClassificationPartitionedKernel.kfoldMargin # name: # type: sq_string # elements: 1 # length: 256 ClassificationPartitionedKernel: m = kfoldMargin ( obj ) Out-of-fold classification margin of every observation. The score the out-of-fold model gives the true class, less the score it gives the other one. An observation no fold held out comes back NaN . # name: # type: sq_string # elements: 1 # length: 55 Out-of-fold classification margin of every observation. # name: # type: sq_string # elements: 1 # length: 44 ClassificationPartitionedKernel.kfoldPredict # name: # type: sq_string # elements: 1 # length: 435 ClassificationPartitionedKernel: labels = kfoldPredict ( obj ) ClassificationPartitionedKernel: [ labels , scores ] = kfoldPredict ( obj ) Out-of-fold class of every observation. Each observation is classified by the fold that held it out, so the labels are out-of-sample. An observation that no fold held out, which under a holdout partition is most of them, comes back missing rather than classified, and its scores come back NaN . # name: # type: sq_string # elements: 1 # length: 39 Out-of-fold class of every observation. # name: # type: sq_string # elements: 1 # length: 31 ClassificationPartitionedLinear # name: # type: sq_string # elements: 1 # length: 916 statistics: ClassificationPartitionedLinear Cross-validated linear binary classifier. A ClassificationPartitionedLinear object holds one ClassificationLinear per fold of a partition, each fitted to the observations the fold trains on. Every kfold method predicts each observation with the fold that held it out , so the estimate it returns is an out-of-sample one. A ClassificationLinear stores no copy of its training data and so has no resubstitution methods and no compact form. This class is what takes their place: cross-validation is the way a linear model is asked how it would do on data it has not seen. When the fold models carry a whole regularization path, every method returns one column per strength, in the order of the 'Lambda' that was asked for. Create one with fitclinear and a cross-validation option, or directly. See also: fitclinear, ClassificationLinear, ClassificationPartitionedKernel # name: # type: sq_string # elements: 1 # length: 41 Cross-validated linear binary classifier. # name: # type: sq_string # elements: 1 # length: 53 ClassificationPartitionedLinear.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 202 ClassificationPartitionedLinear: property CategoricalPredictors Indices of the categorical predictors A row vector of column indices, empty when every predictor is numeric. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 42 ClassificationPartitionedLinear.ClassNames # name: # type: sq_string # elements: 1 # length: 172 ClassificationPartitionedLinear: property ClassNames Names of the two classes A column of the same type as the response, shared by every fold. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Names of the two classes # name: # type: sq_string # elements: 1 # length: 63 ClassificationPartitionedLinear.ClassificationPartitionedLinear # name: # type: sq_string # elements: 1 # length: 1039 ClassificationPartitionedLinear: obj = ClassificationPartitionedLinear ( X , Y ) ClassificationPartitionedLinear: obj = ClassificationPartitionedLinear (…, name , value ) Cross-validate a linear binary classifier. obj = ClassificationPartitionedLinear ( X , Y ) partitions the data into ten stratified folds and fits a ClassificationLinear to each. obj = ClassificationPartitionedLinear (…, name , value ) takes one of 'KFold' , 'Holdout' , 'Leaveout' and 'CVPartition' to say how to partition, and any option ClassificationLinear takes to say how to fit. 'CrossVal' is accepted and has no effect here, this class being cross-validated by construction. The classes, the prior and the cost are resolved once over the whole data and handed to every fold. Anything left as 'auto' is not: each fold resolves 'Lambda' against its own training rows, so ten folds of a hundred observations each get one ninetieth rather than one hundredth. Both are MATLAB’s behaviour, measured. See also: fitclinear, ClassificationLinear # name: # type: sq_string # elements: 1 # length: 42 Cross-validate a linear binary classifier. # name: # type: sq_string # elements: 1 # length: 36 ClassificationPartitionedLinear.Cost # name: # type: sq_string # elements: 1 # length: 236 ClassificationPartitionedLinear: property Cost Cost of misclassifying an observation A square numeric matrix with one row and one column per class. It is handed to every fold rather than re-derived by each. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Cost of misclassifying an observation # name: # type: sq_string # elements: 1 # length: 51 ClassificationPartitionedLinear.CrossValidatedModel # name: # type: sq_string # elements: 1 # length: 180 ClassificationPartitionedLinear: property CrossValidatedModel Name of the model that was cross-validated Always 'Linear' , the short name MATLAB uses. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Name of the model that was cross-validated # name: # type: sq_string # elements: 1 # length: 37 ClassificationPartitionedLinear.KFold # name: # type: sq_string # elements: 1 # length: 222 ClassificationPartitionedLinear: property KFold Number of folds A positive integer scalar. A holdout partition has one fold and a leave-one-out partition has as many as there are observations. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Number of folds # name: # type: sq_string # elements: 1 # length: 47 ClassificationPartitionedLinear.ModelParameters # name: # type: sq_string # elements: 1 # length: 1155 ClassificationPartitionedLinear: property ModelParameters What was cross-validated, and how A structure holding the parameters the folds were fitted with, carried through from the learner that was cross validated, beside NLearn , the number of folds, and the Version , Method and Type tags of this class, with LearnerTemplates naming the backing. The learner’s own tags are replaced rather than kept, so a cross-validated SVM reports Method as 'PartitionedLinear' and not 'SVM' . Deviation from MATLAB. MATLAB reports the parameter record of the cross-validation ensemble here rather than of the learner, so it says nothing at all about how the folds were fitted: of its eighteen fields only the fold count, its partitioner and a fit template carry anything, and the rest are boosting settings left inert. Nor can the parameters be reached through the folds, a compact model carrying none in MATLAB. This class reports the fit instead, which is strictly more than MATLAB offers, and everything MATLAB’s record does carry is published here as the KFold , Partition , X , Y , W and CrossValidatedModel properties. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 What was cross-validated, and how # name: # type: sq_string # elements: 1 # length: 47 ClassificationPartitionedLinear.NumObservations # name: # type: sq_string # elements: 1 # length: 221 ClassificationPartitionedLinear: property NumObservations Number of observations the partition covers A positive integer scalar, counting the rows that survived the removal of missing values. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Number of observations the partition covers # name: # type: sq_string # elements: 1 # length: 41 ClassificationPartitionedLinear.Partition # name: # type: sq_string # elements: 1 # length: 155 ClassificationPartitionedLinear: property Partition The partition itself A cvpartition object over the retained observations. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 The partition itself # name: # type: sq_string # elements: 1 # length: 46 ClassificationPartitionedLinear.PredictorNames # name: # type: sq_string # elements: 1 # length: 145 ClassificationPartitionedLinear: property PredictorNames Names of the predictors A cell array of character vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Names of the predictors # name: # type: sq_string # elements: 1 # length: 37 ClassificationPartitionedLinear.Prior # name: # type: sq_string # elements: 1 # length: 321 ClassificationPartitionedLinear: property Prior Prior probability of each class A numeric row vector summing to one, in the order of ClassNames . Like the cost it is the parent’s and is handed to every fold, so a fold of an unbalanced problem does not quietly adopt a prior of its own. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Prior probability of each class # name: # type: sq_string # elements: 1 # length: 44 ClassificationPartitionedLinear.ResponseName # name: # type: sq_string # elements: 1 # length: 145 ClassificationPartitionedLinear: property ResponseName Name of the response A character vector, defaulting to 'Y' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Name of the response # name: # type: sq_string # elements: 1 # length: 46 ClassificationPartitionedLinear.ScoreTransform # name: # type: sq_string # elements: 1 # length: 460 ClassificationPartitionedLinear: property ScoreTransform Transformation applied to the predicted scores A character vector naming a transformation, or the text of the function handle that was supplied, which may be assigned after the model is built. It is applied once to the assembled scores and is not handed to the folds. A transform the learner implies, as 'logistic' implies 'logit' , does stay with the folds, and this one is then applied on top of it. # name: # type: sq_string # elements: 1 # length: 46 Transformation applied to the predicted scores # name: # type: sq_string # elements: 1 # length: 39 ClassificationPartitionedLinear.Trained # name: # type: sq_string # elements: 1 # length: 216 ClassificationPartitionedLinear: property Trained The models fitted to the folds A cell column with one ClassificationLinear per fold, each fitted to the observations its fold trains on. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 The models fitted to the folds # name: # type: sq_string # elements: 1 # length: 33 ClassificationPartitionedLinear.W # name: # type: sq_string # elements: 1 # length: 203 ClassificationPartitionedLinear: property W Observation weights An Nx1 numeric vector summing to one, normalized within each class to that class’s cost-adjusted prior. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 33 ClassificationPartitionedLinear.Y # name: # type: sq_string # elements: 1 # length: 143 ClassificationPartitionedLinear: property Y Response of the retained observations In the type it was supplied in. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Response of the retained observations # name: # type: sq_string # elements: 1 # length: 41 ClassificationPartitionedLinear.kfoldEdge # name: # type: sq_string # elements: 1 # length: 384 ClassificationPartitionedLinear: e = kfoldEdge ( obj ) ClassificationPartitionedLinear: e = kfoldEdge (…, name , value ) Weighted mean of the out-of-fold classification margins. e = kfoldEdge (…, name , value ) takes 'Folds' , a subset of the folds to average over, and 'Mode' , either 'average' , the default, or 'individual' , which returns one row per fold instead. # name: # type: sq_string # elements: 1 # length: 56 Weighted mean of the out-of-fold classification margins. # name: # type: sq_string # elements: 1 # length: 41 ClassificationPartitionedLinear.kfoldLoss # name: # type: sq_string # elements: 1 # length: 435 ClassificationPartitionedLinear: l = kfoldLoss ( obj ) ClassificationPartitionedLinear: l = kfoldLoss (…, name , value ) Out-of-fold classification loss. l = kfoldLoss ( obj ) returns the out-of-fold misclassification rate. l = kfoldLoss (…, name , value ) takes 'LossFun' , one of 'binodeviance' , 'classifcost' , 'classiferror' , 'exponential' , 'hinge' , 'logit' , 'mincost' and 'quadratic' ; 'Folds' ; and 'Mode' . # name: # type: sq_string # elements: 1 # length: 32 Out-of-fold classification loss. # name: # type: sq_string # elements: 1 # length: 43 ClassificationPartitionedLinear.kfoldMargin # name: # type: sq_string # elements: 1 # length: 319 ClassificationPartitionedLinear: m = kfoldMargin ( obj ) Out-of-fold classification margin of every observation. The score the out-of-fold model gives the true class, less the score it gives the other one. An observation no fold held out comes back NaN . With L regularization strengths m has one column per strength. # name: # type: sq_string # elements: 1 # length: 55 Out-of-fold classification margin of every observation. # name: # type: sq_string # elements: 1 # length: 44 ClassificationPartitionedLinear.kfoldPredict # name: # type: sq_string # elements: 1 # length: 525 ClassificationPartitionedLinear: labels = kfoldPredict ( obj ) ClassificationPartitionedLinear: [ labels , scores ] = kfoldPredict ( obj ) Out-of-fold class of every observation. Each observation is classified by the fold that held it out, so the labels are out-of-sample. An observation that no fold held out, which under a holdout partition is most of them, comes back missing rather than classified, and its scores come back NaN . With L regularization strengths labels has one column per strength and scores is Nx2xL . # name: # type: sq_string # elements: 1 # length: 39 Out-of-fold class of every observation. # name: # type: sq_string # elements: 1 # length: 30 ClassificationPartitionedModel # name: # type: sq_string # elements: 1 # length: 407 statistics: ClassificationPartitionedModel Cross-validated classification model The ClassificationPartitionedModel class stores cross-validated classification models trained on different partitions of the data. It can predict responses for observations not used for training using the kfoldPredict method. Create a ClassificationPartitionedModel object by using the crossval function. See also: crossval # name: # type: sq_string # elements: 1 # length: 36 Cross-validated classification model # name: # type: sq_string # elements: 1 # length: 39 ClassificationPartitionedModel.BinEdges # name: # type: sq_string # elements: 1 # length: 498 ClassificationPartitionedModel: property BinEdges Bin edges A cell array with one entry per predictor, holding that predictor’s bin edges where the learner discretized it before fitting. It is carried over from the model that was cross validated, and is empty whenever that model did no binning, which is every learner this package implements: MATLAB fills it only for its GAM, which bins because it is built from boosted trees where ours is built from splines. This property is read-only. # name: # type: sq_string # elements: 1 # length: 9 Bin edges # name: # type: sq_string # elements: 1 # length: 52 ClassificationPartitionedModel.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 206 ClassificationPartitionedModel: property CategoricalPredictors Indices of categorical predictors A vector of positive integers specifying the indices of categorical predictors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Indices of categorical predictors # name: # type: sq_string # elements: 1 # length: 41 ClassificationPartitionedModel.ClassNames # name: # type: sq_string # elements: 1 # length: 358 ClassificationPartitionedModel: property ClassNames Names of classes in the response variable An array of unique values of the response variable Y , which has the same data types as the data in Y . This property is read-only. ClassNames can have any of the following datatypes: Cell array of character vectors Character array Logical vector Numeric vector # name: # type: sq_string # elements: 1 # length: 41 Names of classes in the response variable # name: # type: sq_string # elements: 1 # length: 61 ClassificationPartitionedModel.ClassificationPartitionedModel # name: # type: sq_string # elements: 1 # length: 451 ClassificationPartitionedModel: this = ClassificationPartitionedModel ( Mdl , Partition ) Create a ClassificationPartitionedModel class object for cross-validation of classification models. this = ClassificationPartitionedModel ( Mdl , Partition ) returns a ClassificationPartitionedModel object, with Mdl as the trained classification model object and Partition as the partitioning object obtained using cvpartition function. See also: cvpartition # name: # type: sq_string # elements: 1 # length: 99 Create a ClassificationPartitionedModel class object for cross-validation of classification models. # name: # type: sq_string # elements: 1 # length: 35 ClassificationPartitionedModel.Cost # name: # type: sq_string # elements: 1 # length: 1438 ClassificationPartitionedModel: property Cost Cost of Misclassification A square matrix specifying the cost of misclassification of a point. Cost(i,j) is the cost of classifying a point into class j if its true class is i (that is, the rows correspond to the true class and the columns correspond to the predicted class). The order of the rows and columns in Cost corresponds to the order of the classes in ClassNames . The number of rows and columns in Cost is the number of unique classes in the response. By default, Cost(i,j) = 1 if i != j , and Cost(i,j) = 0 if i = j . In other words, the cost is 0 for correct classification and 1 for incorrect classification. Assigning Cost rebuilds it on every fold in Trained , so kfoldPredict and kfoldLoss answer under the new costs. It is refused on a cross-validated ClassificationSVM , whose costs enter the box constraint while it is being fitted: a model already fitted under one cost matrix cannot be made to describe another. A cost may also be given as a struct with the fields ClassNames and ClassificationCosts , which names the order its own matrix is written in. That matrix is permuted into the order of ClassNames above, so a caller need not know which order the classes were sorted into. It must name every class. A cost must be floating point, not sparse, not complex, non-negative and zero down its diagonal, and must hold no NaN or Inf . A single is widened to double . # name: # type: sq_string # elements: 1 # length: 25 Cost of Misclassification # name: # type: sq_string # elements: 1 # length: 50 ClassificationPartitionedModel.CrossValidatedModel # name: # type: sq_string # elements: 1 # length: 370 ClassificationPartitionedModel: property CrossValidatedModel Cross-validated model class A character vector holding the short name of the learner that was cross validated, as MATLAB reports it: 'Discriminant' , 'GAM' , 'KNN' , 'NeuralNetwork' or 'SVM' . It is not the class name of that learner, and the regression side uses the same names. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Cross-validated model class # name: # type: sq_string # elements: 1 # length: 36 ClassificationPartitionedModel.KFold # name: # type: sq_string # elements: 1 # length: 181 ClassificationPartitionedModel: property KFold Number of cross-validated folds A positive integer value specifying the number of cross-validated folds. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Number of cross-validated folds # name: # type: sq_string # elements: 1 # length: 46 ClassificationPartitionedModel.ModelParameters # name: # type: sq_string # elements: 1 # length: 1094 ClassificationPartitionedModel: property ModelParameters Model parameters A structure holding the parameters the folds were fitted with, carried through from the learner that was cross validated, beside NLearn , the number of folds, and the Version , Method and Type tags of this class. The learner’s own tags are replaced rather than kept, so a cross-validated SVM reports Method as 'PartitionedModel' and not 'SVM' . Deviation from MATLAB. MATLAB reports the parameter record of the cross-validation ensemble here rather than of the learner, so it says nothing at all about how the folds were fitted: of its eighteen fields only the fold count, its partitioner and a fit template carry anything, and the rest are boosting settings left inert. Nor can the parameters be reached through the folds, a compact model carrying none in MATLAB. This class reports the fit instead, which is strictly more than MATLAB offers, and everything MATLAB’s record does carry is published here as the KFold , Partition , X , Y , W and CrossValidatedModel properties. This property is read-only. # name: # type: sq_string # elements: 1 # length: 16 Model parameters # name: # type: sq_string # elements: 1 # length: 46 ClassificationPartitionedModel.NumObservations # name: # type: sq_string # elements: 1 # length: 241 ClassificationPartitionedModel: property NumObservations Number of observations A positive integer value specifying the number of observations in the training dataset used for training the cross-validated model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 48 ClassificationPartitionedModel.NumTrainedPerFold # name: # type: sq_string # elements: 1 # length: 728 ClassificationPartitionedModel: property NumTrainedPerFold How many trees each fold fitted A scalar structure with fields PredictorTrees and InteractionTrees , each a row with one entry per fold, for a generalized additive model backing, and empty for every other. It reports what each fold actually fitted, which the budget in ModelParameters does not: a phase stops early when it can no longer improve the fit, and the folds need not stop at the same place. MATLAB carries this on its per-learner partitioned GAM classes, which this package deliberately does not have (see crossval ), so like IsStandardDeviationFit it is declared here for every backing and left empty where it does not apply. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 How many trees each fold fitted # name: # type: sq_string # elements: 1 # length: 40 ClassificationPartitionedModel.Partition # name: # type: sq_string # elements: 1 # length: 307 ClassificationPartitionedModel: property Partition Partition configuration A cvpartition object specifying the partition configuration used for cross-validation. This field stores the cvpartition instance that describes how the data was split into training and validation sets. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Partition configuration # name: # type: sq_string # elements: 1 # length: 45 ClassificationPartitionedModel.PredictorNames # name: # type: sq_string # elements: 1 # length: 270 ClassificationPartitionedModel: property PredictorNames Names of predictor variables A cell array of character vectors specifying the names of the predictor variables. The names are in the order in which they appear in the training dataset. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Names of predictor variables # name: # type: sq_string # elements: 1 # length: 36 ClassificationPartitionedModel.Prior # name: # type: sq_string # elements: 1 # length: 669 ClassificationPartitionedModel: property Prior Prior probability for each class A numeric vector specifying the prior probabilities for each class. The order of the elements in Prior corresponds to the order of the classes in ClassNames . It may be assigned only on a cross-validated ClassificationDiscriminant or ClassificationNaiveBayes , the two learners that score from the priors they are given rather than consuming them while they fit: the discriminant re-derives its coefficients from them and the naive Bayes weights its class densities by them. Every other learner cannot revisit them afterwards. Assigning it rebuilds the priors on every fold in Trained . # name: # type: sq_string # elements: 1 # length: 32 Prior probability for each class # name: # type: sq_string # elements: 1 # length: 43 ClassificationPartitionedModel.ResponseName # name: # type: sq_string # elements: 1 # length: 174 ClassificationPartitionedModel: property ResponseName Response variable name A character vector specifying the name of the response variable Y . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 45 ClassificationPartitionedModel.ScoreTransform # name: # type: sq_string # elements: 1 # length: 182 ClassificationPartitionedModel: property ScoreTransform Transformation function for classification scores Specified as a function handle for transforming the classification scores. # name: # type: sq_string # elements: 1 # length: 49 Transformation function for classification scores # name: # type: sq_string # elements: 1 # length: 38 ClassificationPartitionedModel.Trained # name: # type: sq_string # elements: 1 # length: 301 ClassificationPartitionedModel: property Trained Models trained on each fold A cell array of models trained on each fold. Each cell contains a model trained on the minus-one fold of the data (all but one fold used for training and the remaining fold used for validation). This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Models trained on each fold # name: # type: sq_string # elements: 1 # length: 32 ClassificationPartitionedModel.W # name: # type: sq_string # elements: 1 # length: 202 ClassificationPartitionedModel: property W Observation weights A numeric column vector with one entry per observation, carried over from the model that was cross validated. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 32 ClassificationPartitionedModel.X # name: # type: sq_string # elements: 1 # length: 245 ClassificationPartitionedModel: property X Predictor data A numeric matrix containing the unstandardized predictor data. Each column of X represents one predictor (variable), and each row represents one observation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Predictor data # name: # type: sq_string # elements: 1 # length: 32 ClassificationPartitionedModel.Y # name: # type: sq_string # elements: 1 # length: 322 ClassificationPartitionedModel: property Y Class labels Specified as a logical or numeric column vector, or as a character array or a cell array of character vectors with the same number of rows as the predictor data. Each row in Y is the observed class label for the corresponding row in X . This property is read-only. # name: # type: sq_string # elements: 1 # length: 12 Class labels # name: # type: sq_string # elements: 1 # length: 40 ClassificationPartitionedModel.kfoldEdge # name: # type: sq_string # elements: 1 # length: 898 ClassificationPartitionedModel: e = kfoldEdge ( obj ) ClassificationPartitionedModel: e = kfoldEdge (…, name , value ) Classification edge of the cross-validated observations. e = kfoldEdge ( obj ) returns the mean of the classification margins over every cross-validated observation, which is the mean of kfoldMargin ( obj ) . obj must be a ClassificationPartitionedModel object. e = kfoldEdge (…, name , value ) accepts the following Name-Value pairs. Name Value 'Mode' 'average' , the default, which returns one number over the observations of every fold asked for, or 'individual' , which returns one number per fold. 'Folds' A vector of fold indices to restrict the edge to. It defaults to every fold. The observations of a selection are weighted uniformly and normalized over that selection, so a subset of folds is an average rather than a sum, exactly as kfoldLoss does. # name: # type: sq_string # elements: 1 # length: 56 Classification edge of the cross-validated observations. # name: # type: sq_string # elements: 1 # length: 40 ClassificationPartitionedModel.kfoldLoss # name: # type: sq_string # elements: 1 # length: 1073 ClassificationPartitionedModel: L = kfoldLoss ( obj ) ClassificationPartitionedModel: L = kfoldLoss (…, name , value ) Compute the cross-validated classification loss. L = kfoldLoss ( obj ) returns the fraction of observations the folds misclassify, each answered for by the fold’s model that did not see it, which is what kfoldPredict returns. obj must be a ClassificationPartitionedModel object. L = kfoldLoss (…, name , value ) accepts the following Name-Value pairs. Name Value 'LossFun' 'classiferror' , the default, 'classifcost' , 'mincost' , or a function handle called as lossfun ( C , S , W , Cost ) , where C is a logical matrix with one true per row marking the true class, S the scores, W the weights and Cost the misclassification cost. 'Mode' 'average' , the default, which returns one number over the observations of every fold asked for, or 'individual' , which returns one number per fold. 'Folds' A vector of fold indices to restrict the loss to. It defaults to every fold. See also: ClassificationPartitionedModel, kfoldPredict # name: # type: sq_string # elements: 1 # length: 48 Compute the cross-validated classification loss. # name: # type: sq_string # elements: 1 # length: 42 ClassificationPartitionedModel.kfoldMargin # name: # type: sq_string # elements: 1 # length: 723 ClassificationPartitionedModel: m = kfoldMargin ( obj ) Classification margins of the cross-validated observations. m = kfoldMargin ( obj ) returns an Nx1 vector holding, for every observation, the score its own fold’s model gave the true class less the largest score that model gave any other class. A larger margin is a more confident correct answer and a negative one is a misclassification. Every observation is scored by the fold that held it out, so no model answers for a row it was trained on. obj must be a ClassificationPartitionedModel object. Where the fold that held an observation out produced no score for it, the margin is NaN . This method takes no optional arguments, as MATLAB’s does not. # name: # type: sq_string # elements: 1 # length: 59 Classification margins of the cross-validated observations. # name: # type: sq_string # elements: 1 # length: 43 ClassificationPartitionedModel.kfoldPredict # name: # type: sq_string # elements: 1 # length: 2149 ClassificationPartitionedModel: label = kfoldPredict ( this ) ClassificationPartitionedModel: [ label , score , cost ] = kfoldPredict ( this ) Predict responses for observations not used for training in a cross-validated classification model. [label, Score, Cost] = kfoldPredict ( this ) returns the predicted class labels, classification scores, and classification costs for the data used to train the cross-validated model this . this is a ClassificationPartitionedModel object. The function predicts the response for each observation that was held out during training in the cross-validation process. An observation that no fold held out is not predicted at all: its scores and costs are NaN and its label is missing, an empty character vector for a cell array of strings and NaN for a numeric response. Under a 'Holdout' partition that is every observation outside the holdout set. This differs from MATLAB , which reports NaN scores for those rows as we do but labels every one of them with the first class, whatever their response: that label is the least-cost class of a row of NaN costs rather than a prediction any model made, and naming a class for an observation nothing scored would be wrong. A logical response has no missing value to give, so those rows stay false . Output Description label Predicted class labels, returned as a vector or cell array. The type of label matches the type of Y in the original training data. Each element of label corresponds to the predicted class label for the corresponding row in X . Score Classification scores, returned as a numeric matrix. Each row of Score corresponds to an observation, and each column corresponds to a class. The value in row i and column j is the classification score for class j for observation i . Cost Classification costs, returned as a numeric matrix. Each row of Cost corresponds to an observation, and each column corresponds to a class. The value in row i and column j is the classification cost for class j for observation i . This output is optional and only returned if requested. See also: ClassificationKNN, ClassificationSVM, ClassificationPartitionedModel # name: # type: sq_string # elements: 1 # length: 99 Predict responses for observations not used for training in a cross-validated classification model. # name: # type: sq_string # elements: 1 # length: 39 ClassificationPartitionedModel.kfoldfun # name: # type: sq_string # elements: 1 # length: 806 ClassificationPartitionedModel: vals = kfoldfun ( obj , fun ) Apply a function to each fold of a cross-validated model. vals = kfoldfun ( obj , fun ) calls fun once per fold and returns a K×M numeric matrix whose row k is what fun returned for fold k . fun is a function handle taking seven inputs and returning a numeric vector of the same length every time it is called: testvals = fun ( M , Xtrain , Ytrain , Wtrain , … Xtest , Ytest , Wtest ) M is the model the fold was fitted with, taken from obj .Trained{k} ; Xtrain , Ytrain and Wtrain are the predictors, response and weights of the observations that fold was trained on, and Xtest , Ytest and Wtest those of the observations it held out. See also: ClassificationPartitionedModel, kfoldPredict, kfoldLoss, kfoldMargin, kfoldEdge # name: # type: sq_string # elements: 1 # length: 57 Apply a function to each fold of a cross-validated model. # name: # type: sq_string # elements: 1 # length: 17 ClassificationSVM # name: # type: sq_string # elements: 1 # length: 709 statistics: ClassificationSVM Support Vector Machine classification The ClassificationSVM class implements a Support Vector Machine classifier object for one-class or two-class problems, which can predict responses for new data using the predict method. Support Vector Machine classification is a supervised learning method used for classification tasks. It works by finding the optimal hyperplane that separates classes in the feature space with the maximum margin. For non-linearly separable data, it uses kernel functions to map data to a higher-dimensional space where separation is possible. Create a ClassificationSVM object by using the fitcsvm function or the class constructor. See also: fitcsvm # name: # type: sq_string # elements: 1 # length: 37 Support Vector Machine classification # name: # type: sq_string # elements: 1 # length: 23 ClassificationSVM.Alpha # name: # type: sq_string # elements: 1 # length: 468 ClassificationSVM: property Alpha Trained classifier coefficients The coefficients of the trained SVM classifier specified as an s×1 numeric vector, where s is the number of support vectors equal to sum (obj.IsSupportVector) . They are the magnitudes of the dual coefficients and are never negative; the class each belongs to is given by the corresponding entry of SupportVectorLabels . Alpha is populated for every kernel function. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Trained classifier coefficients # name: # type: sq_string # elements: 1 # length: 22 ClassificationSVM.Beta # name: # type: sq_string # elements: 1 # length: 493 ClassificationSVM: property Beta Linear predictor coefficients The linear predictor coefficients specified as a p×1 numeric vector, where p is the number of predictors. Beta is the primal representation of the fitted hyperplane and exists only when the SVM classifier was trained with a 'linear' kernel function; for any other kernel there is no such representation and Beta is empty. It equals obj.SupportVectors' * (obj.Alpha .* obj.SupportVectorLabels) . This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Linear predictor coefficients # name: # type: sq_string # elements: 1 # length: 22 ClassificationSVM.Bias # name: # type: sq_string # elements: 1 # length: 109 ClassificationSVM: property Bias Bias term The bias term specified as a scalar. This property is read-only. # name: # type: sq_string # elements: 1 # length: 9 Bias term # name: # type: sq_string # elements: 1 # length: 26 ClassificationSVM.BinEdges # name: # type: sq_string # elements: 1 # length: 365 ClassificationSVM: property BinEdges Bin edges of the predictors A cell array with one entry per predictor, holding that predictor’s bin edges where the learner discretized it before fitting. It is empty here and stays empty: this learner fits the predictors as they are, and MATLAB’s reports an empty cell for it as well. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Bin edges of the predictors # name: # type: sq_string # elements: 1 # length: 32 ClassificationSVM.BoxConstraints # name: # type: sq_string # elements: 1 # length: 397 ClassificationSVM: property BoxConstraints Box constraints A numeric column vector with one entry per observation, holding the box constraint the fit applied to it. It is BoxConstraint for every observation unless Prior or Cost reweighted the classes, in which case each class is scaled by the weight it carried into the fit, normalized so the weights average to one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Box constraints # name: # type: sq_string # elements: 1 # length: 39 ClassificationSVM.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 229 ClassificationSVM: property CategoricalPredictors Indices of the categorical predictors A numeric vector of column indices into X naming the predictors treated as categorical, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 28 ClassificationSVM.ClassNames # name: # type: sq_string # elements: 1 # length: 345 ClassificationSVM: property ClassNames Names of classes in the response variable An array of unique values of the response variable Y , which has the same data types as the data in Y . This property is read-only. ClassNames can have any of the following datatypes: Cell array of character vectors Character array Logical vector Numeric vector # name: # type: sq_string # elements: 1 # length: 41 Names of classes in the response variable # name: # type: sq_string # elements: 1 # length: 35 ClassificationSVM.ClassificationSVM # name: # type: sq_string # elements: 1 # length: 3078 statistics: obj = ClassificationSVM ( X , Y ) statistics: obj = ClassificationSVM (…, name , value ) Create a ClassificationSVM class object containing a Support Vector Machine classification model for one-class or two-class problems. obj = ClassificationSVM ( X , Y ) returns a ClassificationSVM object, with X as the predictor data and Y containing the class labels of observations in X . X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. X will be used to train the SVM model. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y can be either numeric, logical, or cell array of character vectors. It must have same numbers of rows as X . obj = ClassificationSVM (…, name , value ) returns a ClassificationSVM object with parameters specified by the following name , value paired input arguments: Name Value 'PredictorNames' A cell array of character vectors specifying the names of the predictors. The length of this array must match the number of columns in X . 'ResponseName' A character vector specifying the name of the response variable. 'ClassNames' Names of the classes in the class labels, Y , used for fitting the SVM model. ClassNames are of the same type as the class labels in Y . 'ScoreTransform' A user-defined function handle or a character vector specifying one of the following builtin functions specifying the transformation applied to predicted classification scores. Supported values include 'doublelogit' , 'invlogit' , 'ismax' , 'logit' , 'none' , 'identity' , 'sign' , 'symmetric' , 'symmetricismax' , and 'symmetriclogit' . 'Standardize' A logical scalar specifying whether to standardize the predictor variables. Default is false . 'SVMtype' A character vector specifying the type of SVM to use. Supported values are 'c_svc' (C-support vector classification), 'nu_svc' (nu-support vector classification), and 'one_class_svm' (one-class SVM). 'KernelFunction' A character vector specifying the kernel function to use. Supported values are 'linear' , 'rbf' or 'gaussian' , 'polynomial' , and 'sigmoid' . 'PolynomialOrder' A positive integer specifying the order of the polynomial kernel function. Default is 3. 'KernelScale' A positive scalar specifying the kernel scale parameter. Default is 1. 'KernelOffset' A non-negative scalar specifying the kernel offset parameter. Default is 0. 'BoxConstraint' A positive scalar specifying the box constraint parameter. Default is 1. 'Nu' A positive scalar in the range (0,1] specifying the nu parameter for nu-SVM and one-class SVM. Default is 0.5. 'CacheSize' A positive scalar specifying the cache size in MB. Default is 1000. 'Tolerance' A positive scalar specifying the tolerance of termination criterion. Default is 1e-6. 'Shrinking' Either 0 or 1 specifying whether to use the shrinking heuristics. Default is 1. 'OutlierFraction' A positive scalar in the range [0,1) specifying the fraction of outliers for one-class SVM. See also: fitcsvm # name: # type: sq_string # elements: 1 # length: 133 Create a ClassificationSVM class object containing a Support Vector Machine classification model for one-class or two-class problems. # name: # type: sq_string # elements: 1 # length: 22 ClassificationSVM.Cost # name: # type: sq_string # elements: 1 # length: 250 ClassificationSVM: property Cost Cost of misclassification A numeric square matrix, where Cost(i,j) is the cost of classifying an observation of class i as class j . It defaults to zero on the diagonal and one elsewhere. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Cost of misclassification # name: # type: sq_string # elements: 1 # length: 40 ClassificationSVM.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 262 ClassificationSVM: property ExpandedPredictorNames Names of the predictors as the model expanded them A cell array of character vectors. It matches PredictorNames unless a categorical predictor was expanded into indicator variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 50 Names of the predictors as the model expanded them # name: # type: sq_string # elements: 1 # length: 51 ClassificationSVM.HyperparameterOptimizationResults # name: # type: sq_string # elements: 1 # length: 360 ClassificationSVM: property HyperparameterOptimizationResults Results of the hyperparameter optimization Always empty. It is declared for MATLAB compatibility, where it holds what an automatic search over the hyperparameters found. This class fits the parameters it is given and runs no such search, so there is nothing to report. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Results of the hyperparameter optimization # name: # type: sq_string # elements: 1 # length: 33 ClassificationSVM.IsSupportVector # name: # type: sq_string # elements: 1 # length: 277 ClassificationSVM: property IsSupportVector Support vector indicator An N×1 logical vector that flags whether a corresponding observation in the predictor data matrix is a Support Vector. N is the number of observations in the training data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Support vector indicator # name: # type: sq_string # elements: 1 # length: 34 ClassificationSVM.KernelParameters # name: # type: sq_string # elements: 1 # length: 372 ClassificationSVM: property KernelParameters Parameters of the kernel function A structure with fields Function and Scale , and Order for a polynomial kernel. Function names the kernel as MATLAB names it, so a radial basis kernel reports 'gaussian' whichever spelling was given; the kernel the fit was handed is unchanged in ModelParameters . This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Parameters of the kernel function # name: # type: sq_string # elements: 1 # length: 33 ClassificationSVM.ModelParameters # name: # type: sq_string # elements: 1 # length: 734 ClassificationSVM: property ModelParameters SVM training parameters A structure holding the parameters the fit was given. The engine is LIBSVM and the record is LIBSVM’s, so SVMtype names its formulation and Tolerance and Shrinking are its own controls; the parameters MathWorks reports for its SMO and ISDA solvers are absent, this class running neither. KernelPolynomialOrder belongs to the polynomial kernel alone and is empty under every other, as it is in MATLAB. A structure containing the parameters used to train the SVM model with the following fields: SVMtype , BoxConstraint , CacheSize , KernelScale , KernelOffset , KernelFunction , PolynomialOrder , Nu , Tolerance , and Shrinking . This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 SVM training parameters # name: # type: sq_string # elements: 1 # length: 20 ClassificationSVM.Mu # name: # type: sq_string # elements: 1 # length: 439 ClassificationSVM: property Mu Predictor means A numeric vector of the same length as the columns in X containing the means of predictor variables. If the predictor variables have not been standardized, then Mu is empty. This property is read-only. Only observations with no missing predictor enter the estimate, and they are weighted so that each class keeps the share of the observation weight it carried before any row was set aside. # name: # type: sq_string # elements: 1 # length: 15 Predictor means # name: # type: sq_string # elements: 1 # length: 20 ClassificationSVM.Nu # name: # type: sq_string # elements: 1 # length: 196 ClassificationSVM: property Nu Nu parameter for one-class learning A positive scalar, and empty unless the model is a one-class learner, which is what MATLAB reports. This property is read-only. # name: # type: sq_string # elements: 1 # length: 35 Nu parameter for one-class learning # name: # type: sq_string # elements: 1 # length: 33 ClassificationSVM.NumObservations # name: # type: sq_string # elements: 1 # length: 230 ClassificationSVM: property NumObservations Number of observations A positive integer value specifying the number of observations in the training dataset used for training the ClassificationSVM model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 31 ClassificationSVM.NumPredictors # name: # type: sq_string # elements: 1 # length: 224 ClassificationSVM: property NumPredictors Number of predictors A positive integer value specifying the number of predictors in the training dataset used for training the ClassificationSVM model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 33 ClassificationSVM.OutlierFraction # name: # type: sq_string # elements: 1 # length: 539 ClassificationSVM: property OutlierFraction Expected fraction of outliers in the training data A scalar in [0, 1) , zero unless one was asked for. Deviation from MATLAB. The value is reported as it was given, but it reaches the fit by a different route: MATLAB removes outliers iteratively and reports Solver as 'ISDA' , where a nonzero fraction here selects LIBSVM’s \nu -SVC, in which \nu bounds the fraction of margin errors. The two agree on what the number means and not on how the fit reaches it. This property is read-only. # name: # type: sq_string # elements: 1 # length: 50 Expected fraction of outliers in the training data # name: # type: sq_string # elements: 1 # length: 32 ClassificationSVM.PredictorNames # name: # type: sq_string # elements: 1 # length: 257 ClassificationSVM: property PredictorNames Names of predictor variables A cell array of character vectors specifying the names of the predictor variables. The names are in the order in which they appear in the training dataset. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Names of predictor variables # name: # type: sq_string # elements: 1 # length: 23 ClassificationSVM.Prior # name: # type: sq_string # elements: 1 # length: 546 ClassificationSVM: property Prior Prior probabilities of the classes A numeric row vector with one entry per class, in the order of ClassNames , summing to one. It defaults to the class frequencies of the training data. This property is read-only. Specified as a row vector with one entry per class, in the order of ClassNames , and rescaled to sum to one. It may be given as 'empirical' , 'uniform' , a numeric vector, or a structure with ClassNames and ClassProbs fields, which assigns each probability by class name rather than by position. # name: # type: sq_string # elements: 1 # length: 34 Prior probabilities of the classes # name: # type: sq_string # elements: 1 # length: 30 ClassificationSVM.ResponseName # name: # type: sq_string # elements: 1 # length: 161 ClassificationSVM: property ResponseName Response variable name A character vector specifying the name of the response variable Y . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 26 ClassificationSVM.RowsUsed # name: # type: sq_string # elements: 1 # length: 386 ClassificationSVM: property RowsUsed Rows used for fitting A logical column vector with the same length as the observations in the original predictor data X , true for each row that was used for fitting the ClassificationSVM model. It is empty, [] , when every observation was used, so a non-empty value means that rows holding missing values were dropped. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Rows used for fitting # name: # type: sq_string # elements: 1 # length: 32 ClassificationSVM.ScoreTransform # name: # type: sq_string # elements: 1 # length: 997 ClassificationSVM: property ScoreTransform Transformation function for classification scores Specified as a function handle for transforming the classification scores. Add or change the ScoreTransform property using dot notation as in: obj .ScoreTransform = 'function_name' obj .ScoreTransform = @function_handle When specified as a character vector, it can be any of the following built-in functions. Nevertheless, the ScoreTransform property always stores their function handle equivalent. Value Description 'doublelogit' 1 ./ (1 + exp (-2 × x)) 'invlogit' log (x ./ (1 - x)) 'ismax' Sets the score for the class with the largest score to 1, and for all other classes to 0 'logit' 1 ./ (1 + exp (-x)) 'none' x (no transformation) 'identity' x (no transformation) 'sign' -1 for x < 0, 0 for x = 0, 1 for x > 0 'symmetric' 2 × x - 1 'symmetricismax' Sets the score for the class with the largest score to 1, and for all other classes to -1 'symmetriclogit' 2 ./ (1 + exp (-x)) - 1 # name: # type: sq_string # elements: 1 # length: 49 Transformation function for classification scores # name: # type: sq_string # elements: 1 # length: 23 ClassificationSVM.Sigma # name: # type: sq_string # elements: 1 # length: 473 ClassificationSVM: property Sigma Predictor standard deviations A numeric vector of the same length as the columns in X containing the standard deviations of predictor variables. If the predictor variables have not been standardized, then Sigma is empty. This property is read-only. Only observations with no missing predictor enter the estimate, and they are weighted so that each class keeps the share of the observation weight it carried before any row was set aside. # name: # type: sq_string # elements: 1 # length: 29 Predictor standard deviations # name: # type: sq_string # elements: 1 # length: 37 ClassificationSVM.SupportVectorLabels # name: # type: sq_string # elements: 1 # length: 500 ClassificationSVM: property SupportVectorLabels Support vector class labels The support vector class labels specified as an s×1 numeric vector, where s is the number of support vectors equal to sum (obj.IsSupportVector) . A value of +1 in SupportVectorLabels indicates that the corresponding support vector belongs to the positive class (ClassNames{2}) . A value of -1 indicates that the corresponding support vector belongs to the negative class (ClassNames{1}) . This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Support vector class labels # name: # type: sq_string # elements: 1 # length: 32 ClassificationSVM.SupportVectors # name: # type: sq_string # elements: 1 # length: 321 ClassificationSVM: property SupportVectors Support vectors The support vectors of the trained SVM classifier specified an s×p numeric matrix, where s is the number of support vectors equal to sum (obj.IsSupportVector) , and p is the number of predictor variables in the predictor data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Support vectors # name: # type: sq_string # elements: 1 # length: 19 ClassificationSVM.W # name: # type: sq_string # elements: 1 # length: 368 ClassificationSVM: property W Observation weights A numeric column vector with one entry per training observation, normalized to sum to one, as MATLAB reports it. This property is read-only. Each class carries its prior spread evenly over its own observations, so an observation of a class weighs Prior for that class divided by the number of observations it holds. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 19 ClassificationSVM.X # name: # type: sq_string # elements: 1 # length: 232 ClassificationSVM: property X Predictor data A numeric matrix containing the unstandardized predictor data. Each column of X represents one predictor (variable), and each row represents one observation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Predictor data # name: # type: sq_string # elements: 1 # length: 19 ClassificationSVM.Y # name: # type: sq_string # elements: 1 # length: 309 ClassificationSVM: property Y Class labels Specified as a logical or numeric column vector, or as a character array or a cell array of character vectors with the same number of rows as the predictor data. Each row in Y is the observed class label for the corresponding row in X . This property is read-only. # name: # type: sq_string # elements: 1 # length: 12 Class labels # name: # type: sq_string # elements: 1 # length: 25 ClassificationSVM.compact # name: # type: sq_string # elements: 1 # length: 239 ClassificationSVM: CVMdl = compact ( obj ) Create a CompactClassificationSVM object. CVMdl = compact ( obj ) creates a compact version of the ClassificationSVM object, obj . See also: fitcsvm, ClassificationSVM, CompactClassificationSVM # name: # type: sq_string # elements: 1 # length: 41 Create a CompactClassificationSVM object. # name: # type: sq_string # elements: 1 # length: 26 ClassificationSVM.crossval # name: # type: sq_string # elements: 1 # length: 1057 ClassificationSVM: CVMdl = crossval ( obj ) ClassificationSVM: CVMdl = crossval (…, name , value ) Cross Validate a Support Vector Machine classification object. CVMdl = crossval ( obj ) returns a cross-validated model object, CVMdl , from a trained model, obj , using 10-fold cross-validation by default. CVMdl = crossval ( obj , name , value ) specifies additional name-value pair arguments to customize the cross-validation process. Name Value 'KFold' Specify the number of folds to use in k-fold cross-validation. "KFold", k , where k is an integer greater than 1. 'Holdout' Specify the fraction of the data to hold out for testing. "Holdout", p , where p is a scalar in the range (0,1) . 'Leaveout' Specify whether to perform leave-one-out cross-validation. "Leaveout", Value , where Value is ’on’ or ’off’. 'CVPartition' Specify a cvpartition object used for cross-validation. "CVPartition", cv , where isa ( cv , "cvpartition") = 1. See also: fitcsvm, ClassificationSVM, cvpartition, ClassificationPartitionedModel # name: # type: sq_string # elements: 1 # length: 62 Cross Validate a Support Vector Machine classification object. # name: # type: sq_string # elements: 1 # length: 39 ClassificationSVM.discardSupportVectors # name: # type: sq_string # elements: 1 # length: 625 ClassificationSVM: obj = discardSupportVectors ( obj ) Discard the support vectors of a linear SVM model. obj = discardSupportVectors ( obj ) empties Alpha , SupportVectors and SupportVectorLabels , leaving Beta and Bias to decide every prediction. A linear kernel needs nothing else, so the returned model predicts what it predicted before while carrying one vector in place of many. The kernel must be linear. Under any other the support vectors are part of the decision function and cannot be dropped. Discarding twice is not an error and changes nothing. See also: fitcsvm, ClassificationSVM, CompactClassificationSVM # name: # type: sq_string # elements: 1 # length: 50 Discard the support vectors of a linear SVM model. # name: # type: sq_string # elements: 1 # length: 22 ClassificationSVM.edge # name: # type: sq_string # elements: 1 # length: 599 ClassificationSVM: e = edge ( obj , X , Y ) ClassificationSVM: e = edge (…, "Weights" , w ) Classification edge, the mean of the classification margins. e = edge ( obj , X , Y ) reduces the vector that margin returns to a single number, the mean margin over the rows of X . It says how far the model puts the true class ahead of its nearest rival on average, so a larger edge is a better model, and unlike a loss it is not bounded above and rewards confidence rather than bare correctness. e = edge (…, "Weights" , w ) takes the weighted mean instead, with one weight per row of X . # name: # type: sq_string # elements: 1 # length: 60 Classification edge, the mean of the classification margins. # name: # type: sq_string # elements: 1 # length: 22 ClassificationSVM.loss # name: # type: sq_string # elements: 1 # length: 1890 ClassificationSVM: L = loss ( obj , X , Y ) ClassificationSVM: L = loss (…, name , value ) Compute loss for a trained ClassificationSVM object. L = loss ( obj , X , Y ) computes the loss, L , using the default loss function 'classiferror' . obj is a ClassificationSVM object trained on X and Y . X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y must have same numbers of Rows as X . L = loss (…, name , value ) allows additional options specified by name - value pairs: Name Value 'LossFun' Specifies the loss function to use. Can be a function handle with four input arguments (C, S, W, Cost) which returns a scalar value or one of: ’binodeviance’, ’classifcost’, ’classiferror’, ’exponential’, ’hinge’, ’logit’,’mincost’, ’quadratic’. C is a logical matrix of size N×K , where N is the number of observations and K is the number of classes. The element C(i,j) is true if the class label of the i-th observation is equal to the j-th class. S is a numeric matrix of size N×K , where each element represents the classification score for the corresponding class. W is a numeric vector of length N , representing the observation weights. Cost is a K×K matrix representing the misclassification costs. 'Weights' Specifies observation weights, must be a numeric vector of length equal to the number of rows in X. Default is ones (size (X, 1)) . loss normalizes the weights so that observation weights in each class sum to the prior probability of that class. When you supply Weights, loss computes the weighted classification loss. See also: ClassificationSVM # name: # type: sq_string # elements: 1 # length: 52 Compute loss for a trained ClassificationSVM object. # name: # type: sq_string # elements: 1 # length: 24 ClassificationSVM.margin # name: # type: sq_string # elements: 1 # length: 814 ClassificationSVM: m = margin ( obj , X , Y ) Classification margins for Support Vector Machine classifier. m = margin ( obj , X , Y ) returns the classification margins for obj with data X and classification Y . m is a numeric vector of length size (X,1). obj is a ClassificationSVM object trained on X and Y . X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y must have same numbers of Rows as X . The classification margin for each observation is the difference between the classification score for the true class and the maximal classification score for the false classes. See also: fitcsvm, ClassificationSVM # name: # type: sq_string # elements: 1 # length: 61 Classification margins for Support Vector Machine classifier. # name: # type: sq_string # elements: 1 # length: 25 ClassificationSVM.predict # name: # type: sq_string # elements: 1 # length: 1698 ClassificationSVM: label = predict ( obj , XC ) ClassificationSVM: [ label , score ] = predict ( obj , XC ) ClassificationSVM: [ label , score , cost ] = predict ( obj , XC ) Classify new data points into categories using the Support Vector Machine classification model from a ClassificationSVM object. label = predict ( obj , XC ) returns the vector of labels predicted for the corresponding instances in XC , using the predictor data in obj.X and corresponding labels, obj.Y , stored in the ClassificationSVM model, obj . For one-class SVM model, +1 or -1 is returned. obj must be a ClassificationSVM class object. XC must be an M×P numeric matrix with the same number of features P as the corresponding predictors of the SVM model in obj . [ label , score ] = predict ( obj , XC ) also returns score , which contains the decision values for each prediction. A ScoreTransform assigned to obj is applied to them, so score holds whatever that transform returns. Posterior probabilities need a transform fitted to the model, which this package does not compute yet. Deviation from MATLAB. cost is the expected cost of each assignment, sum_j P(j) Cost(j,k) . An SVM score is a signed distance to the boundary and not a posterior, so the only distribution available is the one concentrated on the predicted class and cost is the row of Cost belonging to it. MATLAB returns the column instead, which is the same matrix read the wrong way and contradicts its own ClassificationKNN , ClassificationDiscriminant and ClassificationNaiveBayes on any asymmetric cost matrix; the two agree wherever Cost is symmetric, the default included. Measured on R2024a. See also: ClassificationSVM, fitcsvm # name: # type: sq_string # elements: 1 # length: 127 Classify new data points into categories using the Support Vector Machine classification model from a ClassificationSVM object. # name: # type: sq_string # elements: 1 # length: 27 ClassificationSVM.resubEdge # name: # type: sq_string # elements: 1 # length: 210 ClassificationSVM: e = resubEdge ( obj ) Classification edge of the model on its own training data. e = resubEdge ( obj ) is edge applied to the observations the model was fitted on, the mean of resubMargin . # name: # type: sq_string # elements: 1 # length: 58 Classification edge of the model on its own training data. # name: # type: sq_string # elements: 1 # length: 27 ClassificationSVM.resubLoss # name: # type: sq_string # elements: 1 # length: 1640 ClassificationSVM: L = resubLoss ( obj ) ClassificationSVM: L = resubLoss (…, name , value ) Compute resubstitution loss for a trained ClassificationSVM object. L = resubLoss ( obj ) computes the resubstitution loss, L , using the default loss function 'classiferror' . obj is a ClassificationSVM object trained on X and Y . L = resubLoss (…, name , value ) allows additional options specified by name - value pairs: Name Value 'LossFun' Specifies the loss function to use. Can be a function handle with four input arguments (C, S, W, Cost) which returns a scalar value or one of: ’binodeviance’, ’classifcost’, ’classiferror’, ’exponential’, ’hinge’, ’logit’,’mincost’, ’quadratic’. C is a logical matrix of size N×K , where N is the number of observations and K is the number of classes. The element C(i,j) is true if the class label of the i-th observation is equal to the j-th class. S is a numeric matrix of size N×K , where each element represents the classification score for the corresponding class. W is a numeric vector of length N , representing the observation weights. Cost is a K×K matrix representing the misclassification costs. 'Weights' Specifies observation weights, must be a numeric vector of length equal to the number of rows in X. Default is ones (size (X, 1)) . loss normalizes the weights so that observation weights in each class sum to the prior probability of that class. When you supply Weights, loss computes the weighted classification loss. See also: ClassificationSVM # name: # type: sq_string # elements: 1 # length: 67 Compute resubstitution loss for a trained ClassificationSVM object. # name: # type: sq_string # elements: 1 # length: 29 ClassificationSVM.resubMargin # name: # type: sq_string # elements: 1 # length: 287 ClassificationSVM: m = resubMargin ( obj ) Classification margins of the model on its own training data. m = resubMargin ( obj ) is margin applied to the observations the model was fitted on, one number per observation. Being a resubstitution quantity it is optimistic by construction. # name: # type: sq_string # elements: 1 # length: 61 Classification margins of the model on its own training data. # name: # type: sq_string # elements: 1 # length: 30 ClassificationSVM.resubPredict # name: # type: sq_string # elements: 1 # length: 1540 ClassificationSVM: label = resubPredict ( obj ) ClassificationSVM: [ label , score ] = resubPredict ( obj ) ClassificationSVM: [ label , score , cost ] = resubPredict ( obj ) Classify the training data using the trained Support Vector Machine classification object. label = resubPredict ( obj ) returns the vector of labels predicted for the corresponding instances in the training data, using the predictor data in obj.X and corresponding labels, obj.Y , stored in the Support Vector Machine classification model, obj . For one-class model, +1 or -1 is returned. obj must be a ClassificationSVM class object. [ label , scores ] = resubPredict ( obj also returns scores , which contains the decision values for each prediction. A ScoreTransform assigned to obj is applied to them, so scores holds whatever that transform returns. Posterior probabilities need a transform fitted to the model, which this package does not compute yet. Deviation from MATLAB. cost is the expected cost of each assignment, sum_j P(j) Cost(j,k) . An SVM score is a signed distance to the boundary and not a posterior, so the only distribution available is the one concentrated on the predicted class and cost is the row of Cost belonging to it. MATLAB returns the column instead, which is the same matrix read the wrong way and contradicts its own ClassificationKNN , ClassificationDiscriminant and ClassificationNaiveBayes on any asymmetric cost matrix; the two agree wherever Cost is symmetric, the default included. Measured on R2024a. See also: fitcsvm # name: # type: sq_string # elements: 1 # length: 90 Classify the training data using the trained Support Vector Machine classification object. # name: # type: sq_string # elements: 1 # length: 27 ClassificationSVM.savemodel # name: # type: sq_string # elements: 1 # length: 472 ClassificationSVM: savemodel ( obj , filename ) Save a ClassificationSVM object. savemodel ( obj , filename ) saves each property of a ClassificationSVM object into an Octave binary file, the name of which is specified in filename , along with an extra variable, which defines the type classification object these variables constitute. Use loadmodel in order to load a classification object into Octave’s workspace. See also: loadmodel, fitcsvm, ClassificationSVM # name: # type: sq_string # elements: 1 # length: 32 Save a ClassificationSVM object. # name: # type: sq_string # elements: 1 # length: 33 CompactClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 1664 statistics: CompactClassificationDiscriminant Compact discriminant analysis classification The CompactClassificationDiscriminant class implements a compact version of a linear discriminant analysis classifier object, which can predict responses for new data using the predict method but does not store the training data. A CompactClassificationDiscriminant object is a compact version of a discriminant analysis model, ClassificationDiscriminant . It does not include the training data resulting in a smaller classifier size, which can be used for making predictions from new data, but not for tasks such as cross validation. It can only be created from a ClassificationDiscriminant model by using the compact object method. Create a CompactClassificationDiscriminant object by using the compact method of a ClassificationDiscriminant object. Six discriminant types are available, in two families. The linear family, 'linear' , 'diagLinear' and 'pseudoLinear' , pools one covariance across the classes and separates them with a hyperplane. The quadratic family, 'quadratic' , 'diagQuadratic' and 'pseudoQuadratic' , estimates a covariance per class and separates them with a quadric. A 'diag' type keeps only the variances, which is the same model as a Gamma of 1, and a 'pseudo' type inverts a singular covariance rather than refusing it. DiscrimType may be assigned after fitting, but only within its own family : the family is fixed when the model is fitted, because it decides which covariances the fit has to estimate. Assigning it, or Gamma , re-derives Sigma , LogDetSigma and Coeffs without refitting. See also: fitcdiscr, ClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 44 Compact discriminant analysis classification # name: # type: sq_string # elements: 1 # length: 46 CompactClassificationDiscriminant.BetweenSigma # name: # type: sq_string # elements: 1 # length: 757 CompactClassificationDiscriminant: property BetweenSigma Between-class covariance matrix A P -by- P matrix holding the covariance of the class means about the overall mean, weighted by how many observations each class contributes. With n_k observations in class k , p_k = n_k / n and \bar{\mu} = \sum_k p_k \mu_k , it is BetweenSigma = sum_k n_k (Mu(k,:) - mubar)' * (Mu(k,:) - mubar) / (n * (1 - sum_k p_k^2)) The denominator is the unbiased one for a weighted covariance, so a balanced fit divides by n (K-1) / K . It reads the class sizes , not Prior : assigning a prior leaves it where it was. It is estimated for every discriminant type, the quadratic family included, since it describes the classes rather than the fit. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Between-class covariance matrix # name: # type: sq_string # elements: 1 # length: 55 CompactClassificationDiscriminant.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 245 CompactClassificationDiscriminant: property CategoricalPredictors Indices of the categorical predictors A numeric vector of column indices into X naming the predictors treated as categorical, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 44 CompactClassificationDiscriminant.ClassNames # name: # type: sq_string # elements: 1 # length: 361 CompactClassificationDiscriminant: property ClassNames Names of classes in the response variable An array of unique values of the response variable Y , which has the same data types as the data in Y . This property is read-only. ClassNames can have any of the following datatypes: Cell array of character vectors Character array Logical vector Numeric vector # name: # type: sq_string # elements: 1 # length: 41 Names of classes in the response variable # name: # type: sq_string # elements: 1 # length: 40 CompactClassificationDiscriminant.Coeffs # name: # type: sq_string # elements: 1 # length: 849 CompactClassificationDiscriminant: property Coeffs Coefficient matrices A K×K structure containing the coefficient matrices, where K is the number of classes. If the 'FillCoeffs' parameter was set to 'off' in the original ClassificationDiscriminant model, then Coeffs is empty ([]) . This property is read-only. Coeffs(i,j) contains the coefficients of the boundary between the classes i and j in the following fields: DiscrimType - A character vector Class1 - ClassNames (i) Class2 - ClassNames (j) Const - A scalar Linear - A vector with length as the number of predictors. Quadratic - The quadratic family only. A PxP matrix, or a 1xP vector for 'diagQuadratic' , following the shape of Sigma . The diagonal entries carry the two class names and nothing else. The structure is rebuilt whenever DiscrimType , Gamma or Prior is assigned. # name: # type: sq_string # elements: 1 # length: 20 Coefficient matrices # name: # type: sq_string # elements: 1 # length: 67 CompactClassificationDiscriminant.CompactClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 483 CompactClassificationDiscriminant: obj = CompactClassificationDiscriminant ( Mdl ) CompactClassificationDiscriminant: obj = CompactClassificationDiscriminant () Create a CompactClassificationDiscriminant object. Mdl is the ClassificationDiscriminant object to compact. The documented way to reach this constructor is the compact method. Called with no arguments it returns an object with its properties empty, which is how a saved model is rebuilt before its values are filled in. # name: # type: sq_string # elements: 1 # length: 50 Create a CompactClassificationDiscriminant object. # name: # type: sq_string # elements: 1 # length: 38 CompactClassificationDiscriminant.Cost # name: # type: sq_string # elements: 1 # length: 1158 CompactClassificationDiscriminant: property Cost Cost of Misclassification A square matrix specifying the cost of misclassification of a point. Cost(i,j) is the cost of classifying a point into class j if its true class is i (that is, the rows correspond to the true class and the columns correspond to the predicted class). The order of the rows and columns in Cost corresponds to the order of the classes in ClassNames . The number of rows and columns in Cost is the number of unique classes in the response. By default, Cost(i,j) = 1 if i != j , and Cost(i,j) = 0 if i = j . In other words, the cost is 0 for correct classification and 1 for incorrect classification. This property is read-only. A cost may also be given as a struct with the fields ClassNames and ClassificationCosts , which names the order its own matrix is written in. That matrix is permuted into the order of ClassNames above, so a caller need not know which order the classes were sorted into. It must name every class. A cost must be floating point, not sparse, not complex, non-negative and zero down its diagonal, and must hold no NaN or Inf . A single is widened to double . # name: # type: sq_string # elements: 1 # length: 25 Cost of Misclassification # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationDiscriminant.Delta # name: # type: sq_string # elements: 1 # length: 816 CompactClassificationDiscriminant: property Delta Delta threshold for the linear coefficients A nonnegative scalar that eliminates predictors. A per-class linear coefficient is set to zero when it falls below Delta , and the comparison is made on the standardized coefficient, the coefficient times the within-class standard deviation of its predictor. Scaling matters here: a threshold on the raw coefficients would depend on the units each predictor is measured in, so the same model in centimetres and in metres would drop different predictors. DeltaPredictor reports, per predictor, the value at which it drops out of every class at once. It applies to the linear family only, a quadratic discriminant having no linear coefficients to eliminate. Assigning it rebuilds Coeffs and changes what predict answers. # name: # type: sq_string # elements: 1 # length: 43 Delta threshold for the linear coefficients # name: # type: sq_string # elements: 1 # length: 48 CompactClassificationDiscriminant.DeltaPredictor # name: # type: sq_string # elements: 1 # length: 489 CompactClassificationDiscriminant: property DeltaPredictor Minimum Delta at which each predictor drops out A row vector with one entry per predictor, the value of Delta at which that predictor’s coefficient is zero for every class and the predictor leaves the model altogether. It is all zeros for the quadratic family, which has no linear coefficients to eliminate. This property is read-only, and it describes the fit rather than the threshold: assigning Delta does not move it. # name: # type: sq_string # elements: 1 # length: 47 Minimum Delta at which each predictor drops out # name: # type: sq_string # elements: 1 # length: 45 CompactClassificationDiscriminant.DiscrimType # name: # type: sq_string # elements: 1 # length: 803 CompactClassificationDiscriminant: property DiscrimType Discriminant type A character vector naming the discriminant model, one of 'linear' , 'quadratic' , 'diagLinear' , 'diagQuadratic' , 'pseudoLinear' or 'pseudoQuadratic' . A linear type pools one covariance across the classes; a quadratic type estimates one per class. A 'diag' type keeps only the variances, and a 'pseudo' type inverts a singular covariance instead of refusing it. This property may be assigned, but only within its own family : the three linear types interchange freely and so do the three quadratic ones, while no assignment moves a model between the two. The family is fixed when the model is fitted, because it decides which covariances the fit has to estimate. Assigning re-derives Sigma , LogDetSigma , Gamma and Coeffs . # name: # type: sq_string # elements: 1 # length: 17 Discriminant type # name: # type: sq_string # elements: 1 # length: 56 CompactClassificationDiscriminant.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 278 CompactClassificationDiscriminant: property ExpandedPredictorNames Names of the predictors as the model expanded them A cell array of character vectors. It matches PredictorNames unless a categorical predictor was expanded into indicator variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 50 Names of the predictors as the model expanded them # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationDiscriminant.Gamma # name: # type: sq_string # elements: 1 # length: 520 CompactClassificationDiscriminant: property Gamma Gamma regularization parameter A scalar from 0 to 1 shrinking the covariance towards its diagonal. Gamma and DiscrimType are one state: a value of 1 is the diagonal type, so assigning it renames DiscrimType to 'diagLinear' or 'diagQuadratic' , and assigning a diagonal type sets Gamma to 1. The quadratic family admits 0 and 1 only. A value below MinGamma is refused, since it would leave the covariance singular. Assigning re-derives Sigma , LogDetSigma and Coeffs . # name: # type: sq_string # elements: 1 # length: 30 Gamma regularization parameter # name: # type: sq_string # elements: 1 # length: 45 CompactClassificationDiscriminant.LogDetSigma # name: # type: sq_string # elements: 1 # length: 625 CompactClassificationDiscriminant: property LogDetSigma Logarithm of the determinant of the within-class covariance matrix A scalar for the linear family and a Kx1 vector for the quadratic one, one entry per class. It is computed in correlation space, as the sum of the logarithms of the predictor variances plus the log determinant of the correlation matrix, which is far better conditioned than the covariance when the data are nearly collinear. A predictor with no variance contributes nothing rather than an infinity, and the 'pseudo' types sum only over the directions that carry variance. This property is read-only. # name: # type: sq_string # elements: 1 # length: 66 Logarithm of the determinant of the within-class covariance matrix # name: # type: sq_string # elements: 1 # length: 42 CompactClassificationDiscriminant.MinGamma # name: # type: sq_string # elements: 1 # length: 451 CompactClassificationDiscriminant: property MinGamma Minimum value for the Gamma regularization parameter A scalar from 0 to 1, the least regularization that leaves the correlation matrix invertible. It is 0 when the matrix is already invertible, and positive when the predictors are collinear, in which case a plain 'linear' or 'quadratic' fit is raised to it rather than failing. Assigning a Gamma below it is refused. This property is read-only. # name: # type: sq_string # elements: 1 # length: 52 Minimum value for the Gamma regularization parameter # name: # type: sq_string # elements: 1 # length: 36 CompactClassificationDiscriminant.Mu # name: # type: sq_string # elements: 1 # length: 272 CompactClassificationDiscriminant: property Mu Class means A K×P numeric matrix specifying the mean of the multivariate normal distribution of each corresponding class, where K is the number of classes and P is the number of predictors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 11 Class means # name: # type: sq_string # elements: 1 # length: 47 CompactClassificationDiscriminant.NumPredictors # name: # type: sq_string # elements: 1 # length: 256 CompactClassificationDiscriminant: property NumPredictors Number of predictors A positive integer value specifying the number of predictors in the training dataset used for training the CompactClassificationDiscriminant model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 48 CompactClassificationDiscriminant.PredictorNames # name: # type: sq_string # elements: 1 # length: 273 CompactClassificationDiscriminant: property PredictorNames Names of predictor variables A cell array of character vectors specifying the names of the predictor variables. The names are in the order in which they appear in the training dataset. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Names of predictor variables # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationDiscriminant.Prior # name: # type: sq_string # elements: 1 # length: 569 CompactClassificationDiscriminant: property Prior Prior probability for each class A numeric vector specifying the prior probabilities for each class. The order of the elements in Prior corresponds to the order of the classes in ClassNames . This property is read-only. Specified as a row vector with one entry per class, in the order of ClassNames , and rescaled to sum to one. It may be given as 'empirical' , 'uniform' , a numeric vector, or a structure with ClassNames and ClassProbs fields, which assigns each probability by class name rather than by position. # name: # type: sq_string # elements: 1 # length: 32 Prior probability for each class # name: # type: sq_string # elements: 1 # length: 46 CompactClassificationDiscriminant.ResponseName # name: # type: sq_string # elements: 1 # length: 177 CompactClassificationDiscriminant: property ResponseName Response variable name A character vector specifying the name of the response variable Y . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 48 CompactClassificationDiscriminant.ScoreTransform # name: # type: sq_string # elements: 1 # length: 895 CompactClassificationDiscriminant: property ScoreTransform Transformation function for classification scores Specified as a function handle for transforming the classification scores. This property is read-only. When specified as a character vector, it can be any of the following built-in functions. Nevertheless, the ScoreTransform property always stores their function handle equivalent. Value Description 'doublelogit' 1 ./ (1 + exp (-2 × x)) 'invlogit' log (x ./ (1 - x)) 'ismax' Sets the score for the class with the largest score to 1, and for all other classes to 0 'logit' 1 ./ (1 + exp (-x)) 'none' x (no transformation) 'identity' x (no transformation) 'sign' -1 for x < 0, 0 for x = 0, 1 for x > 0 'symmetric' 2 × x - 1 'symmetricismax' Sets the score for the class with the largest score to 1, and for all other classes to -1 'symmetriclogit' 2 ./ (1 + exp (-x)) - 1 # name: # type: sq_string # elements: 1 # length: 49 Transformation function for classification scores # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationDiscriminant.Sigma # name: # type: sq_string # elements: 1 # length: 519 CompactClassificationDiscriminant: property Sigma Within-class covariance A numeric array whose shape follows DiscrimType , with P predictors and K classes: DiscrimType Sigma LogDetSigma 'linear' , 'pseudoLinear' PxP scalar 'quadratic' , 'pseudoQuadratic' PxPxK Kx1 'diagLinear' 1xP scalar 'diagQuadratic' 1xPxK Kx1 The linear family pools one covariance across the classes and the quadratic family estimates one per class. This property is read-only, but it is re-derived whenever DiscrimType or Gamma is assigned. # name: # type: sq_string # elements: 1 # length: 23 Within-class covariance # name: # type: sq_string # elements: 1 # length: 38 CompactClassificationDiscriminant.edge # name: # type: sq_string # elements: 1 # length: 631 CompactClassificationDiscriminant: e = edge ( obj , X , Y ) CompactClassificationDiscriminant: e = edge (…, "Weights" , w ) Classification edge, the mean of the classification margins. e = edge ( obj , X , Y ) reduces the vector that margin returns to a single number, the mean margin over the rows of X . It says how far the model puts the true class ahead of its nearest rival on average, so a larger edge is a better model, and unlike a loss it is not bounded above and rewards confidence rather than bare correctness. e = edge (…, "Weights" , w ) takes the weighted mean instead, with one weight per row of X . # name: # type: sq_string # elements: 1 # length: 60 Classification edge, the mean of the classification margins. # name: # type: sq_string # elements: 1 # length: 38 CompactClassificationDiscriminant.logp # name: # type: sq_string # elements: 1 # length: 704 CompactClassificationDiscriminant: lp = logp ( obj , X ) Log unconditional probability density of the observations. lp = logp ( obj , X ) returns an Nx1 vector holding, for each row of X , the natural logarithm of P(x) = sum_k P(k) P(x|k) , the density of the observation summed over the classes with each class weighted by its prior P(k) . Each P(x|k) is the multivariate normal density of class k . obj must be a CompactClassificationDiscriminant object. X must be an NxP numeric matrix with one column per predictor of the trained model. An unusually low value marks an observation the model finds unlikely under every class, which is what makes this an outlier test rather than a classification. # name: # type: sq_string # elements: 1 # length: 58 Log unconditional probability density of the observations. # name: # type: sq_string # elements: 1 # length: 38 CompactClassificationDiscriminant.loss # name: # type: sq_string # elements: 1 # length: 1945 CompactClassificationDiscriminant: L = loss ( obj , X , Y ) CompactClassificationDiscriminant: L = loss (…, name , value ) Compute loss for a trained CompactClassificationDiscriminant object. L = loss ( obj , X , Y ) computes the loss, L , using the default loss function 'mincost' . obj is a CompactClassificationDiscriminant object. X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y must have same numbers of rows as X . L = loss (…, name , value ) allows additional options specified by name - value pairs: Name Value 'LossFun' Specifies the loss function to use. Can be a function handle with four input arguments (C, S, W, Cost) which returns a scalar value or one of: ’binodeviance’, ’classifcost’, ’classiferror’, ’exponential’, ’hinge’, ’logit’,’mincost’, ’quadratic’. C is a logical matrix of size N×K , where N is the number of observations and K is the number of classes. The element C(i,j) is true if the class label of the i-th observation is equal to the j-th class. S is a numeric matrix of size N×K , where each element represents the classification score for the corresponding class. W is a numeric vector of length N , representing the observation weights. Cost is a K×K matrix representing the misclassification costs. 'Weights' Specifies observation weights, must be a numeric vector of length equal to the number of rows in X. Default is ones (size (X, 1)) . loss normalizes the weights so that observation weights in each class sum to the prior probability of that class. When you supply Weights, loss computes the weighted classification loss. See also: CompactClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 68 Compute loss for a trained CompactClassificationDiscriminant object. # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationDiscriminant.mahal # name: # type: sq_string # elements: 1 # length: 1034 CompactClassificationDiscriminant: M = mahal ( obj , X ) CompactClassificationDiscriminant: M = mahal (…, 'ClassLabels' , labels ) Squared Mahalanobis distance to the class means. M = mahal ( obj , X ) returns an NxK matrix whose element (i,j) is the squared Mahalanobis distance from observation i to the mean of class j , measured against the covariance that class carries: the one shared covariance for a linear discriminant and the class’s own for a quadratic one. obj must be a CompactClassificationDiscriminant object. X must be an NxP numeric matrix with one column per predictor of the trained model. M = mahal (…, 'ClassLabels' , labels ) returns an Nx1 vector instead, holding for each observation the distance to the mean of the class labels names for it. labels must have one entry per row of X , each of them one of ClassNames . The distance is measured against the covariance the model reports, so a regularized model is measured against its regularized covariance. The prior does not enter it. # name: # type: sq_string # elements: 1 # length: 48 Squared Mahalanobis distance to the class means. # name: # type: sq_string # elements: 1 # length: 40 CompactClassificationDiscriminant.margin # name: # type: sq_string # elements: 1 # length: 843 CompactClassificationDiscriminant: m = margin ( obj , X , Y ) Classification margins for discriminant analysis classifier. m = margin ( obj , X , Y ) returns the classification margins for obj with data X and classification Y . m is a numeric vector of length size (X,1). obj is a CompactClassificationDiscriminant object. X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y must have same numbers of rows as X . The classification margin for each observation is the difference between the classification score for the true class and the maximal classification score for the false classes. See also: fitcdiscr, CompactClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 60 Classification margins for discriminant analysis classifier. # name: # type: sq_string # elements: 1 # length: 47 CompactClassificationDiscriminant.nLinearCoeffs # name: # type: sq_string # elements: 1 # length: 901 CompactClassificationDiscriminant: n = nLinearCoeffs ( obj ) CompactClassificationDiscriminant: n = nLinearCoeffs ( obj , delta ) Number of nonzero linear coefficients at a regularization threshold. n = nLinearCoeffs ( obj ) returns the number of predictors the discriminant keeps at its own Delta . n = nLinearCoeffs ( obj , delta ) returns the number it would keep at each threshold in delta , as a column vector however delta is shaped. A predictor survives a threshold when its DeltaPredictor reaches it, the comparison including equality, so delta at exactly a predictor’s own value still counts it. A threshold above every DeltaPredictor therefore leaves nothing and returns zero. The count is taken whatever the DiscrimType , as MATLAB takes it, even though Delta regularizes the linear types alone. See also: fitcdiscr, ClassificationDiscriminant, CompactClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 68 Number of nonzero linear coefficients at a regularization threshold. # name: # type: sq_string # elements: 1 # length: 41 CompactClassificationDiscriminant.predict # name: # type: sq_string # elements: 1 # length: 1433 CompactClassificationDiscriminant: label = predict ( obj , XC ) CompactClassificationDiscriminant: [ label , score , cost ] = predict ( obj , XC ) Classify new data points into categories using the discriminant analysis model from a CompactClassificationDiscriminant object. label = predict ( obj , XC ) returns the vector of labels predicted for the corresponding instances in XC , using the corresponding labels from the trained ClassificationDiscriminant , model, obj . obj must be a CompactClassificationDiscriminant class object. XC must be an M×P numeric matrix with the same number of features P as the corresponding predictors of the discriminant model in obj . [ label , score , cost ] = predict ( obj , XC ) also returns score , which contains the predicted class scores or posterior probabilities for each instance of the corresponding unique classes, and cost , which is a matrix containing the expected cost of the classifications. The score matrix contains the posterior probabilities for each class, calculated using the multivariate normal probability density function and the prior probabilities of each class. These scores are normalized to ensure they sum to 1 for each observation. The cost matrix contains the expected classification cost for each class, computed based on the posterior probabilities and the specified misclassification costs. See also: CompactClassificationDiscriminant, fitcdiscr # name: # type: sq_string # elements: 1 # length: 127 Classify new data points into categories using the discriminant analysis model from a CompactClassificationDiscriminant object. # name: # type: sq_string # elements: 1 # length: 43 CompactClassificationDiscriminant.savemodel # name: # type: sq_string # elements: 1 # length: 531 CompactClassificationDiscriminant: savemodel ( obj , filename ) Save a CompactClassificationDiscriminant object. savemodel ( obj , filename ) saves each property of a CompactClassificationDiscriminant object into an Octave binary file, the name of which is specified in filename , along with an extra variable, which defines the type classification object these variables constitute. Use loadmodel in order to load a classification object into Octave’s workspace. See also: loadmodel, fitcdiscr, ClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 48 Save a CompactClassificationDiscriminant object. # name: # type: sq_string # elements: 1 # length: 24 CompactClassificationGAM # name: # type: sq_string # elements: 1 # length: 966 statistics: CompactClassificationGAM Compact generalized additive model classification The CompactClassificationGAM class is a compact version of a Generalized Additive Model classifier, ClassificationGAM . It does not include the training data, resulting in a smaller classifier size that can be used for making predictions from new data, but not for tasks such as cross validation. A CompactClassificationGAM object can only be created from a ClassificationGAM model by using the compact method. The engine that fitted the model is carried over in FitMethod , and the compact model predicts by the same scheme the full one did. Under 'boostedtrees' , the default, the fit is described by TreeModel , BinEdges and PairDetectionBinEdges . Under 'splines' it is described by Formula , BaseModel , ModelwInt and IntMatrix , which MATLAB’s compact model does not carry. Whichever fitted the model, the other set is empty. See also: ClassificationGAM, fitcgam # name: # type: sq_string # elements: 1 # length: 49 Compact generalized additive model classification # name: # type: sq_string # elements: 1 # length: 34 CompactClassificationGAM.BaseModel # name: # type: sq_string # elements: 1 # length: 295 CompactClassificationGAM: property BaseModel Base model parameters A structure containing the parameters of the base model without any interaction terms. The base model represents the generalized additive model with only the main effects (predictor terms) included. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Base model parameters # name: # type: sq_string # elements: 1 # length: 33 CompactClassificationGAM.BinEdges # name: # type: sq_string # elements: 1 # length: 291 CompactClassificationGAM: property BinEdges Bin edges of the fitted shape functions A cell array with one row vector per predictor, holding the cut points the boosted-tree engine binned it at. It is the empty cell under the spline engine, which does no binning. This property is read-only. # name: # type: sq_string # elements: 1 # length: 39 Bin edges of the fitted shape functions # name: # type: sq_string # elements: 1 # length: 46 CompactClassificationGAM.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 226 CompactClassificationGAM: property CategoricalPredictors Indices of the categorical predictors A numeric vector holding the column of each predictor treated as categorical, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 35 CompactClassificationGAM.ClassNames # name: # type: sq_string # elements: 1 # length: 352 CompactClassificationGAM: property ClassNames Names of classes in the response variable An array of unique values of the response variable Y , which has the same data types as the data in Y . This property is read-only. ClassNames can have any of the following datatypes: Cell array of character vectors Character array Logical vector Numeric vector # name: # type: sq_string # elements: 1 # length: 41 Names of classes in the response variable # name: # type: sq_string # elements: 1 # length: 49 CompactClassificationGAM.CompactClassificationGAM # name: # type: sq_string # elements: 1 # length: 429 CompactClassificationGAM: obj = CompactClassificationGAM ( Mdl ) CompactClassificationGAM: obj = CompactClassificationGAM () Create a CompactClassificationGAM object. Mdl is the ClassificationGAM object to compact. The documented way to reach this constructor is the compact method. Called with no arguments it returns an object with its properties empty, which is how a saved model is rebuilt before its values are filled in. # name: # type: sq_string # elements: 1 # length: 41 Create a CompactClassificationGAM object. # name: # type: sq_string # elements: 1 # length: 29 CompactClassificationGAM.Cost # name: # type: sq_string # elements: 1 # length: 1203 CompactClassificationGAM: property Cost Cost of Misclassification A square matrix specifying the cost of misclassification of a point. Cost(i,j) is the cost of classifying a point into class j if its true class is i (that is, the rows correspond to the true class and the columns correspond to the predicted class). The order of the rows and columns in Cost corresponds to the order of the classes in ClassNames . The number of rows and columns in Cost is the number of unique classes in the response. By default, Cost(i,j) = 1 if i != j , and Cost(i,j) = 0 if i = j . In other words, the cost is 0 for correct classification and 1 for incorrect classification. Add or change the Cost property using dot notation as in: obj .Cost = costMatrix A cost may also be given as a struct with the fields ClassNames and ClassificationCosts , which names the order its own matrix is written in. That matrix is permuted into the order of ClassNames above, so a caller need not know which order the classes were sorted into. It must name every class. A cost must be floating point, not sparse, not complex, non-negative and zero down its diagonal, and must hold no NaN or Inf . A single is widened to double . # name: # type: sq_string # elements: 1 # length: 25 Cost of Misclassification # name: # type: sq_string # elements: 1 # length: 47 CompactClassificationGAM.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 301 CompactClassificationGAM: property ExpandedPredictorNames Names of the expanded predictor variables A cell array of character vectors naming the predictors as the model sees them. It matches PredictorNames unless a categorical predictor was expanded into dummy variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 41 Names of the expanded predictor variables # name: # type: sq_string # elements: 1 # length: 34 CompactClassificationGAM.FitMethod # name: # type: sq_string # elements: 1 # length: 188 CompactClassificationGAM: property FitMethod Which engine fitted the model Either 'boostedtrees' or 'splines' , as the model it was compacted from was fitted. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Which engine fitted the model # name: # type: sq_string # elements: 1 # length: 32 CompactClassificationGAM.Formula # name: # type: sq_string # elements: 1 # length: 280 CompactClassificationGAM: property Formula Model specification formula A character vector specifying the model formula in the form 'Y ~ terms' where Y represents the response variable and terms specifies the predictor variables and interaction terms. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Model specification formula # name: # type: sq_string # elements: 1 # length: 34 CompactClassificationGAM.IntMatrix # name: # type: sq_string # elements: 1 # length: 564 CompactClassificationGAM: property IntMatrix Every term the model fits A logical matrix with one row per term and one column per predictor, true wherever the term multiplies that predictor. A row naming one predictor is a main effect, two an interaction, and three or more a higher-order term. This property is read-only. It is the complete record, where Interactions reports only the two-way terms, in the form MATLAB reports them. It is also the form the 'Interactions' option takes back, so passing it to the constructor rebuilds a model over the same terms. # name: # type: sq_string # elements: 1 # length: 25 Every term the model fits # name: # type: sq_string # elements: 1 # length: 37 CompactClassificationGAM.Interactions # name: # type: sq_string # elements: 1 # length: 605 CompactClassificationGAM: property Interactions Two-way interaction terms of the fitted model A Kx2 matrix of predictor index pairs, one row per two-way term the model carries, and zeros (0, 2) when it carries none. It reports what was fitted rather than what was asked for, so a count of terms, 'all' , a logical matrix and a formula all leave the same kind of value behind. This property is read-only. A main effect names one predictor and a higher-order term names three or more, and neither has a two-column form, so neither appears here. IntMatrix remains the complete record of every term fitted. # name: # type: sq_string # elements: 1 # length: 45 Two-way interaction terms of the fitted model # name: # type: sq_string # elements: 1 # length: 34 CompactClassificationGAM.Intercept # name: # type: sq_string # elements: 1 # length: 204 CompactClassificationGAM: property Intercept Intercept of the fitted model A numeric scalar, the log-odds of the response mean, which every additive term is measured against. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Intercept of the fitted model # name: # type: sq_string # elements: 1 # length: 34 CompactClassificationGAM.ModelwInt # name: # type: sq_string # elements: 1 # length: 275 CompactClassificationGAM: property ModelwInt Model parameters with interactions A structure containing the parameters of the model that includes interaction terms. This model extends the base model by adding interaction terms between predictors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Model parameters with interactions # name: # type: sq_string # elements: 1 # length: 38 CompactClassificationGAM.NumPredictors # name: # type: sq_string # elements: 1 # length: 231 CompactClassificationGAM: property NumPredictors Number of predictors A positive integer value specifying the number of predictors in the training dataset used for training the ClassificationGAM model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 46 CompactClassificationGAM.PairDetectionBinEdges # name: # type: sq_string # elements: 1 # length: 236 CompactClassificationGAM: property PairDetectionBinEdges Bin edges the interaction terms are held on A cell array with one coarse row vector per predictor, empty when the model carries no interaction terms. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Bin edges the interaction terms are held on # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationGAM.PredictorNames # name: # type: sq_string # elements: 1 # length: 264 CompactClassificationGAM: property PredictorNames Names of predictor variables A cell array of character vectors specifying the names of the predictor variables. The names are in the order in which they appear in the training dataset. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Names of predictor variables # name: # type: sq_string # elements: 1 # length: 30 CompactClassificationGAM.Prior # name: # type: sq_string # elements: 1 # length: 272 CompactClassificationGAM: property Prior Prior probability for each class A 2-element numeric vector specifying the prior probabilities for each class. The order of the elements in Prior corresponds to the order of the classes in ClassNames . This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Prior probability for each class # name: # type: sq_string # elements: 1 # length: 37 CompactClassificationGAM.ResponseName # name: # type: sq_string # elements: 1 # length: 168 CompactClassificationGAM: property ResponseName Response variable name A character vector specifying the name of the response variable Y . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationGAM.ScoreTransform # name: # type: sq_string # elements: 1 # length: 1357 CompactClassificationGAM: property ScoreTransform Transformation function for classification scores Specified as a function handle for transforming the classification scores. Add or change the ScoreTransform property using dot notation as in: obj .ScoreTransform = 'function_name' obj .ScoreTransform = @function_handle When specified as a character vector, it can be any of the following built-in functions. Nevertheless, the ScoreTransform property always stores their function handle equivalent. Value Description 'doublelogit' 1 ./ (1 + exp (-2 × x)) 'invlogit' log (x ./ (1 - x)) 'ismax' Sets the score for the class with the largest score to 1, and for all other classes to 0 'logit' 1 ./ (1 + exp (-x)) 'none' x (no transformation) 'identity' x (no transformation) 'sign' -1 for x < 0, 0 for x = 0, 1 for x > 0 'symmetric' 2 × x - 1 'symmetricismax' Sets the score for the class with the largest score to 1, and for all other classes to -1 'symmetriclogit' 2 ./ (1 + exp (-x)) - 1 The default is 'logit' , as in MATLAB. This model’s raw score is a log-odds, reported as the pair [-f, f] whose two columns sum to zero, and the transform is what turns it into the posterior probabilities that sum to one. Every transform therefore composes on the log-odds and not on the probabilities, so 'none' returns the log-odds themselves. # name: # type: sq_string # elements: 1 # length: 49 Transformation function for classification scores # name: # type: sq_string # elements: 1 # length: 34 CompactClassificationGAM.TreeModel # name: # type: sq_string # elements: 1 # length: 223 CompactClassificationGAM: property TreeModel The fitted shape functions and interaction surfaces The structure the full model reports, carried over unchanged, and empty under the spline engine. This property is read-only. # name: # type: sq_string # elements: 1 # length: 51 The fitted shape functions and interaction surfaces # name: # type: sq_string # elements: 1 # length: 29 CompactClassificationGAM.edge # name: # type: sq_string # elements: 1 # length: 447 CompactClassificationGAM: e = edge ( obj , X , Y ) CompactClassificationGAM: e = edge (…, "Weights" , w ) Classification edge of a compact generalized additive model. e = edge ( obj , X , Y ) returns the mean of the classification margins over the rows of X . e = edge (…, "Weights" , w ) takes the weighted mean instead, with one weight per row of X . See also: CompactClassificationGAM, ClassificationGAM, margin, loss, predict # name: # type: sq_string # elements: 1 # length: 60 Classification edge of a compact generalized additive model. # name: # type: sq_string # elements: 1 # length: 29 CompactClassificationGAM.loss # name: # type: sq_string # elements: 1 # length: 946 CompactClassificationGAM: L = loss ( obj , X , Y ) CompactClassificationGAM: L = loss (…, name , value ) Classification loss of a compact generalized additive model. L = loss ( obj , X , Y ) returns the loss of the model on the rows of X against the true labels Y . L = loss (…, name , value ) accepts the following name-value pairs: "LossFun" selects the loss. Supported values are "mincost" , the default, "binodeviance" , "classifcost" , "classiferror" , "exponential" , "hinge" , "logit" and "quadratic" . "mincost" assigns each observation to the class of least expected cost and charges what that assignment costs, so it reads the scores as a posterior, which is what this model returns; "classifcost" charges what the model’s own prediction costs. "Weights" holds one weight per row of X , normalised to sum to one before it is applied. See also: CompactClassificationGAM, ClassificationGAM, margin, edge, predict # name: # type: sq_string # elements: 1 # length: 60 Classification loss of a compact generalized additive model. # name: # type: sq_string # elements: 1 # length: 31 CompactClassificationGAM.margin # name: # type: sq_string # elements: 1 # length: 471 CompactClassificationGAM: m = margin ( obj , X , Y ) Classification margin of a compact generalized additive model. m = margin ( obj , X , Y ) returns a column vector holding, for each row of X , the score the model gives its true class in Y less the score it gives the other class. A positive margin means the observation is classified correctly, and the larger it is the more confidently so. See also: CompactClassificationGAM, ClassificationGAM, edge, loss, predict # name: # type: sq_string # elements: 1 # length: 62 Classification margin of a compact generalized additive model. # name: # type: sq_string # elements: 1 # length: 32 CompactClassificationGAM.predict # name: # type: sq_string # elements: 1 # length: 1185 CompactClassificationGAM: label = predict ( obj , XC ) CompactClassificationGAM: [ label , score ] = predict ( obj , XC ) CompactClassificationGAM: [ label , score ] = predict (…, 'IncludeInteractions' , includeInteractions ) Predict labels for new data using the Generalized Additive Model (GAM) stored in a CompactClassificationGAM object. label = predict ( obj , XC ) returns the predicted labels for the data in XC based on the model stored in the CompactClassificationGAM object, obj . [ label , score ] = predict ( obj , XC ) also returns score , which contains the predicted class scores or posterior probabilities for each observation. [ label , score ] = predict ( obj , XC , 'IncludeInteractions', includeInteractions ) allows you to specify whether interaction terms should be included when making predictions. obj must be a CompactClassificationGAM class object. XC must be an M×P numeric matrix where each row is an observation and each column corresponds to a predictor variable. includeInteractions is a logical scalar indicating whether to include interaction terms in the predictions. See also: CompactClassificationGAM, ClassificationGAM, fitcgam # name: # type: sq_string # elements: 1 # length: 115 Predict labels for new data using the Generalized Additive Model (GAM) stored in a CompactClassificationGAM object. # name: # type: sq_string # elements: 1 # length: 34 CompactClassificationGAM.savemodel # name: # type: sq_string # elements: 1 # length: 519 CompactClassificationGAM: savemodel ( obj , filename ) Save a CompactClassificationGAM object. savemodel ( obj , filename ) saves each property of a CompactClassificationGAM object into an Octave binary file, the name of which is specified in filename , along with an extra variable, which defines the type classification object these variables constitute. Use loadmodel in order to load a classification object into Octave’s workspace. See also: loadmodel, fitcgam, ClassificationGAM, CompactClassificationGAM # name: # type: sq_string # elements: 1 # length: 39 Save a CompactClassificationGAM object. # name: # type: sq_string # elements: 1 # length: 31 CompactClassificationNaiveBayes # name: # type: sq_string # elements: 1 # length: 575 statistics: CompactClassificationNaiveBayes Compact naive Bayes classification A CompactClassificationNaiveBayes object carries the fitted densities of a ClassificationNaiveBayes model and everything predict needs, but not the observations the model was fitted on. It classifies new data identically to the model it came from, and is far smaller to keep or to ship. Create one with the compact method of a ClassificationNaiveBayes object. Because it holds no training data, it has no resub methods and cannot be cross-validated. See also: ClassificationNaiveBayes, fitcnb # name: # type: sq_string # elements: 1 # length: 34 Compact naive Bayes classification # name: # type: sq_string # elements: 1 # length: 49 CompactClassificationNaiveBayes.CategoricalLevels # name: # type: sq_string # elements: 1 # length: 254 CompactClassificationNaiveBayes: property CategoricalLevels Levels of the categorical predictors A cell array with one entry per predictor, holding the distinct levels of each categorical predictor and empty for every other. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Levels of the categorical predictors # name: # type: sq_string # elements: 1 # length: 53 CompactClassificationNaiveBayes.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 189 CompactClassificationNaiveBayes: property CategoricalPredictors Categorical predictor indices The column indices treated as categorical, or empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Categorical predictor indices # name: # type: sq_string # elements: 1 # length: 42 CompactClassificationNaiveBayes.ClassNames # name: # type: sq_string # elements: 1 # length: 210 CompactClassificationNaiveBayes: property ClassNames Class labels of the fitted model The distinct classes the model was fitted on, in the order the other per-class properties use. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Class labels of the fitted model # name: # type: sq_string # elements: 1 # length: 63 CompactClassificationNaiveBayes.CompactClassificationNaiveBayes # name: # type: sq_string # elements: 1 # length: 288 CompactClassificationNaiveBayes: obj = CompactClassificationNaiveBayes ( Mdl ) Create a CompactClassificationNaiveBayes object. Mdl is the ClassificationNaiveBayes object to compact. The documented way to reach this constructor is the compact method. See also: ClassificationNaiveBayes # name: # type: sq_string # elements: 1 # length: 48 Create a CompactClassificationNaiveBayes object. # name: # type: sq_string # elements: 1 # length: 36 CompactClassificationNaiveBayes.Cost # name: # type: sq_string # elements: 1 # length: 179 CompactClassificationNaiveBayes: property Cost Misclassification cost A square numeric matrix where Cost(i,j) is the cost of classifying an observation of class i into class j . # name: # type: sq_string # elements: 1 # length: 22 Misclassification cost # name: # type: sq_string # elements: 1 # length: 49 CompactClassificationNaiveBayes.DistributionNames # name: # type: sq_string # elements: 1 # length: 215 CompactClassificationNaiveBayes: property DistributionNames Predictor distributions A cell array of character vectors with one entry per predictor, naming the distribution fitted to it. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Predictor distributions # name: # type: sq_string # elements: 1 # length: 54 CompactClassificationNaiveBayes.DistributionParameters # name: # type: sq_string # elements: 1 # length: 230 CompactClassificationNaiveBayes: property DistributionParameters Fitted distribution parameters A cell array with one row per class and one column per predictor, holding the parameters fitted to each. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 Fitted distribution parameters # name: # type: sq_string # elements: 1 # length: 54 CompactClassificationNaiveBayes.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 208 CompactClassificationNaiveBayes: property ExpandedPredictorNames Expanded predictor variable names A cell array of character vectors naming the predictors as the model sees them. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Expanded predictor variable names # name: # type: sq_string # elements: 1 # length: 38 CompactClassificationNaiveBayes.Kernel # name: # type: sq_string # elements: 1 # length: 199 CompactClassificationNaiveBayes: property Kernel Kernel smoothing functions A cell array naming the smoothing kernel of each kernel predictor, and empty for every other. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Kernel smoothing functions # name: # type: sq_string # elements: 1 # length: 34 CompactClassificationNaiveBayes.Mu # name: # type: sq_string # elements: 1 # length: 186 CompactClassificationNaiveBayes: property Mu Predictor means The means used to center the predictors, when the model standardizes them, and empty otherwise. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Predictor means # name: # type: sq_string # elements: 1 # length: 46 CompactClassificationNaiveBayes.PredictorNames # name: # type: sq_string # elements: 1 # length: 168 CompactClassificationNaiveBayes: property PredictorNames Predictor variable names A cell array of character vectors naming the predictors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Predictor variable names # name: # type: sq_string # elements: 1 # length: 37 CompactClassificationNaiveBayes.Prior # name: # type: sq_string # elements: 1 # length: 167 CompactClassificationNaiveBayes: property Prior Class prior probabilities A numeric row vector with one entry per class, in the order of ClassNames , summing to one. # name: # type: sq_string # elements: 1 # length: 25 Class prior probabilities # name: # type: sq_string # elements: 1 # length: 44 CompactClassificationNaiveBayes.ResponseName # name: # type: sq_string # elements: 1 # length: 156 CompactClassificationNaiveBayes: property ResponseName Response variable name A character vector naming the response variable. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 46 CompactClassificationNaiveBayes.ScoreTransform # name: # type: sq_string # elements: 1 # length: 187 CompactClassificationNaiveBayes: property ScoreTransform Score transformation A character vector naming the function applied to the posterior returned by predict , or a function handle. # name: # type: sq_string # elements: 1 # length: 20 Score transformation # name: # type: sq_string # elements: 1 # length: 37 CompactClassificationNaiveBayes.Sigma # name: # type: sq_string # elements: 1 # length: 216 CompactClassificationNaiveBayes: property Sigma Predictor standard deviations The standard deviations used to scale the predictors, when the model standardizes them, and empty otherwise. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Predictor standard deviations # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationNaiveBayes.Support # name: # type: sq_string # elements: 1 # length: 206 CompactClassificationNaiveBayes: property Support Kernel smoothing supports A cell array giving the support of each kernel predictor’s density, and empty for every other. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Kernel smoothing supports # name: # type: sq_string # elements: 1 # length: 37 CompactClassificationNaiveBayes.Width # name: # type: sq_string # elements: 1 # length: 226 CompactClassificationNaiveBayes: property Width Kernel smoothing bandwidths A numeric matrix with one row per class and one column per predictor, and empty when no predictor uses a kernel density. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Kernel smoothing bandwidths # name: # type: sq_string # elements: 1 # length: 36 CompactClassificationNaiveBayes.edge # name: # type: sq_string # elements: 1 # length: 160 CompactClassificationNaiveBayes: e = edge ( obj , X , Y ) CompactClassificationNaiveBayes: e = edge (…, 'Weights' , w ) Classification edge on new data. # name: # type: sq_string # elements: 1 # length: 32 Classification edge on new data. # name: # type: sq_string # elements: 1 # length: 36 CompactClassificationNaiveBayes.logp # name: # type: sq_string # elements: 1 # length: 106 CompactClassificationNaiveBayes: lp = logp ( obj , X ) Log unconditional probability density of new data. # name: # type: sq_string # elements: 1 # length: 50 Log unconditional probability density of new data. # name: # type: sq_string # elements: 1 # length: 36 CompactClassificationNaiveBayes.loss # name: # type: sq_string # elements: 1 # length: 244 CompactClassificationNaiveBayes: l = loss ( obj , X , Y ) CompactClassificationNaiveBayes: l = loss (…, name , value ) Classification loss on new data. Takes the 'LossFun' and 'Weights' options that ClassificationNaiveBayes.loss takes. # name: # type: sq_string # elements: 1 # length: 32 Classification loss on new data. # name: # type: sq_string # elements: 1 # length: 38 CompactClassificationNaiveBayes.margin # name: # type: sq_string # elements: 1 # length: 95 CompactClassificationNaiveBayes: m = margin ( obj , X , Y ) Classification margin on new data. # name: # type: sq_string # elements: 1 # length: 34 Classification margin on new data. # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationNaiveBayes.predict # name: # type: sq_string # elements: 1 # length: 367 CompactClassificationNaiveBayes: label = predict ( obj , XC ) CompactClassificationNaiveBayes: [ label , score , cost ] = predict ( obj , XC ) Classify new data with a compact naive Bayes model. The same classification the model it came from would give: the label of least expected cost, the posterior of each class, and the expected misclassification cost of each. # name: # type: sq_string # elements: 1 # length: 51 Classify new data with a compact naive Bayes model. # name: # type: sq_string # elements: 1 # length: 41 CompactClassificationNaiveBayes.savemodel # name: # type: sq_string # elements: 1 # length: 527 CompactClassificationNaiveBayes: savemodel ( obj , filename ) Save a CompactClassificationNaiveBayes object. savemodel ( obj , filename ) saves each property of a CompactClassificationNaiveBayes object into an Octave binary file, the name of which is specified in filename , along with an extra variable, which defines the type classification object these variables constitute. Use loadmodel in order to load a classification object into Octave’s workspace. See also: loadmodel, fitcnb, CompactClassificationNaiveBayes # name: # type: sq_string # elements: 1 # length: 46 Save a CompactClassificationNaiveBayes object. # name: # type: sq_string # elements: 1 # length: 34 CompactClassificationNeuralNetwork # name: # type: sq_string # elements: 1 # length: 758 statistics: CompactClassificationNeuralNetwork Compact neural network classification The CompactClassificationNeuralNetwork class implements a compact version of the neural network classifier object, which can predict responses for new data using the predict method, but does not store the training data. A compact neural network classification model is a smaller version of the full ClassificationNeuralNetwork model that does not include the training data. It consumes less memory than the full model, but cannot perform tasks that require the training data, such as cross-validation. Create a CompactClassificationNeuralNetwork object by using the compact method on a ClassificationNeuralNetwork object. See also: ClassificationNeuralNetwork, fitcnet # name: # type: sq_string # elements: 1 # length: 37 Compact neural network classification # name: # type: sq_string # elements: 1 # length: 46 CompactClassificationNeuralNetwork.Activations # name: # type: sq_string # elements: 1 # length: 398 CompactClassificationNeuralNetwork: property Activations Activation functions for hidden layers A character vector or cell array of character vectors specifying the activation functions used in the hidden layers of the neural network. Supported activation functions include: 'linear' , 'sigmoid' , 'relu' , 'tanh' , 'softmax' , 'lrelu' , 'prelu' , 'elu' , and 'gelu' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Activation functions for hidden layers # name: # type: sq_string # elements: 1 # length: 56 CompactClassificationNeuralNetwork.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 236 CompactClassificationNeuralNetwork: property CategoricalPredictors Indices of the categorical predictors A numeric vector holding the column of each predictor treated as categorical, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 45 CompactClassificationNeuralNetwork.ClassNames # name: # type: sq_string # elements: 1 # length: 362 CompactClassificationNeuralNetwork: property ClassNames Names of classes in the response variable An array of unique values of the response variable Y , which has the same data types as the data in Y . This property is read-only. ClassNames can have any of the following datatypes: Cell array of character vectors Character array Logical vector Numeric vector # name: # type: sq_string # elements: 1 # length: 41 Names of classes in the response variable # name: # type: sq_string # elements: 1 # length: 69 CompactClassificationNeuralNetwork.CompactClassificationNeuralNetwork # name: # type: sq_string # elements: 1 # length: 489 CompactClassificationNeuralNetwork: obj = CompactClassificationNeuralNetwork ( Mdl ) CompactClassificationNeuralNetwork: obj = CompactClassificationNeuralNetwork () Create a CompactClassificationNeuralNetwork object. Mdl is the ClassificationNeuralNetwork object to compact. The documented way to reach this constructor is the compact method. Called with no arguments it returns an object with its properties empty, which is how a saved model is rebuilt before its values are filled in. # name: # type: sq_string # elements: 1 # length: 51 Create a CompactClassificationNeuralNetwork object. # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationNeuralNetwork.Cost # name: # type: sq_string # elements: 1 # length: 759 CompactClassificationNeuralNetwork: property Cost Cost of misclassification A numeric matrix with one row and one column per class, where Cost(i,j) is the cost of classifying an observation of class i as class j . It is taken from the model this object was compacted from. This property is read-only. A cost may also be given as a struct with the fields ClassNames and ClassificationCosts , which names the order its own matrix is written in. That matrix is permuted into the order of ClassNames above, so a caller need not know which order the classes were sorted into. It must name every class. A cost must be floating point, not sparse, not complex, non-negative and zero down its diagonal, and must hold no NaN or Inf . A single is widened to double . # name: # type: sq_string # elements: 1 # length: 25 Cost of misclassification # name: # type: sq_string # elements: 1 # length: 57 CompactClassificationNeuralNetwork.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 311 CompactClassificationNeuralNetwork: property ExpandedPredictorNames Names of the expanded predictor variables A cell array of character vectors naming the predictors as the model sees them. It matches PredictorNames unless a categorical predictor was expanded into dummy variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 41 Names of the expanded predictor variables # name: # type: sq_string # elements: 1 # length: 46 CompactClassificationNeuralNetwork.LayerBiases # name: # type: sq_string # elements: 1 # length: 247 CompactClassificationNeuralNetwork: property LayerBiases Learned bias of each fully connected layer A cell array holding one column vector per layer, the output layer included, with one entry per neuron of that layer. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Learned bias of each fully connected layer # name: # type: sq_string # elements: 1 # length: 45 CompactClassificationNeuralNetwork.LayerSizes # name: # type: sq_string # elements: 1 # length: 487 CompactClassificationNeuralNetwork: property LayerSizes Sizes of fully connected layers A positive integer vector specifying the sizes of the fully connected layers in the neural network model. The i-th element of LayerSizes is the number of outputs in the i-th fully connected layer of the neural network model. LayerSizes does not include the size of the final fully connected layer. This layer always has K outputs, where K is the number of classes in Y. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Sizes of fully connected layers # name: # type: sq_string # elements: 1 # length: 47 CompactClassificationNeuralNetwork.LayerWeights # name: # type: sq_string # elements: 1 # length: 276 CompactClassificationNeuralNetwork: property LayerWeights Learned weights of each fully connected layer A cell array holding one matrix per layer, the output layer included, with one row per neuron of that layer and one column per input it takes. This property is read-only. # name: # type: sq_string # elements: 1 # length: 45 Learned weights of each fully connected layer # name: # type: sq_string # elements: 1 # length: 37 CompactClassificationNeuralNetwork.Mu # name: # type: sq_string # elements: 1 # length: 228 CompactClassificationNeuralNetwork: property Mu Predictor means A numeric vector containing the means of the predictors used for standardization. Empty when the predictor data were not standardized. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Predictor means # name: # type: sq_string # elements: 1 # length: 48 CompactClassificationNeuralNetwork.NumPredictors # name: # type: sq_string # elements: 1 # length: 238 CompactClassificationNeuralNetwork: property NumPredictors Number of predictors A positive integer value specifying the number of predictors in the training dataset used for training the neural network model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 56 CompactClassificationNeuralNetwork.OutputLayerActivation # name: # type: sq_string # elements: 1 # length: 307 CompactClassificationNeuralNetwork: property OutputLayerActivation Activation function for output layer A character vector specifying the activation function of the output layer of the neural network. Supported activation functions are the same as for the Activations property. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Activation function for output layer # name: # type: sq_string # elements: 1 # length: 49 CompactClassificationNeuralNetwork.PredictorNames # name: # type: sq_string # elements: 1 # length: 274 CompactClassificationNeuralNetwork: property PredictorNames Names of predictor variables A cell array of character vectors specifying the names of the predictor variables. The names are in the order in which they appear in the training dataset. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Names of predictor variables # name: # type: sq_string # elements: 1 # length: 40 CompactClassificationNeuralNetwork.Prior # name: # type: sq_string # elements: 1 # length: 259 CompactClassificationNeuralNetwork: property Prior Prior probability of each class A numeric vector with one entry per class, in the order of ClassNames , summing to one. It is taken from the model this object was compacted from. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Prior probability of each class # name: # type: sq_string # elements: 1 # length: 47 CompactClassificationNeuralNetwork.ResponseName # name: # type: sq_string # elements: 1 # length: 178 CompactClassificationNeuralNetwork: property ResponseName Response variable name A character vector specifying the name of the response variable Y . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 49 CompactClassificationNeuralNetwork.ScoreTransform # name: # type: sq_string # elements: 1 # length: 1014 CompactClassificationNeuralNetwork: property ScoreTransform Transformation function for classification scores Specified as a function handle for transforming the classification scores. Add or change the ScoreTransform property using dot notation as in: obj .ScoreTransform = 'function_name' obj .ScoreTransform = @function_handle When specified as a character vector, it can be any of the following built-in functions. Nevertheless, the ScoreTransform property always stores their function handle equivalent. Value Description 'doublelogit' 1 ./ (1 + exp (-2 × x)) 'invlogit' log (x ./ (1 - x)) 'ismax' Sets the score for the class with the largest score to 1, and for all other classes to 0 'logit' 1 ./ (1 + exp (-x)) 'none' x (no transformation) 'identity' x (no transformation) 'sign' -1 for x < 0, 0 for x = 0, 1 for x > 0 'symmetric' 2 × x - 1 'symmetricismax' Sets the score for the class with the largest score to 1, and for all other classes to -1 'symmetriclogit' 2 ./ (1 + exp (-x)) - 1 # name: # type: sq_string # elements: 1 # length: 49 Transformation function for classification scores # name: # type: sq_string # elements: 1 # length: 40 CompactClassificationNeuralNetwork.Sigma # name: # type: sq_string # elements: 1 # length: 259 CompactClassificationNeuralNetwork: property Sigma Predictor standard deviations A numeric vector containing the standard deviations of the predictors used for standardization. Empty when the predictor data were not standardized. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Predictor standard deviations # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationNeuralNetwork.edge # name: # type: sq_string # elements: 1 # length: 486 CompactClassificationNeuralNetwork: e = edge ( obj , X , Y ) CompactClassificationNeuralNetwork: e = edge (…, "Weights" , w ) Classification edge of a compact neural network classifier. e = edge ( obj , X , Y ) returns the mean of the classification margins over the rows of X . e = edge (…, "Weights" , w ) takes the weighted mean instead, with one weight per row of X . See also: CompactClassificationNeuralNetwork, ClassificationNeuralNetwork, margin, loss, predict # name: # type: sq_string # elements: 1 # length: 59 Classification edge of a compact neural network classifier. # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationNeuralNetwork.loss # name: # type: sq_string # elements: 1 # length: 1165 CompactClassificationNeuralNetwork: L = loss ( obj , X , Y ) CompactClassificationNeuralNetwork: L = loss (…, name , value ) Classification loss of a compact neural network classifier. L = loss ( obj , X , Y ) returns the loss of the model on the rows of X against the true labels Y . L = loss (…, name , value ) accepts the following name-value pairs: "LossFun" selects the loss. Supported values are "mincost" , the default, "binodeviance" , "classifcost" , "classiferror" , "crossentropy" , "exponential" , "hinge" , "logit" and "quadratic" . "mincost" assigns each observation to the class of least expected cost and charges what that assignment costs, so it reads the scores as a posterior; "classifcost" charges what the model’s own prediction costs. "crossentropy" is defined for a network only. Note that the default differs from the other classifiers in this package, which default to "classiferror" , and follows MATLAB’s for this class. "Weights" holds one weight per row of X , normalised to sum to one before it is applied. See also: CompactClassificationNeuralNetwork, ClassificationNeuralNetwork, margin, edge, predict # name: # type: sq_string # elements: 1 # length: 59 Classification loss of a compact neural network classifier. # name: # type: sq_string # elements: 1 # length: 41 CompactClassificationNeuralNetwork.margin # name: # type: sq_string # elements: 1 # length: 508 CompactClassificationNeuralNetwork: m = margin ( obj , X , Y ) Classification margin of a compact neural network classifier. m = margin ( obj , X , Y ) returns a column vector holding, for each row of X , the score the model gives its true class in Y less the largest score it gives any other class. A positive margin means the observation is classified correctly, and the larger it is the more confidently so. See also: CompactClassificationNeuralNetwork, ClassificationNeuralNetwork, edge, loss, predict # name: # type: sq_string # elements: 1 # length: 61 Classification margin of a compact neural network classifier. # name: # type: sq_string # elements: 1 # length: 42 CompactClassificationNeuralNetwork.predict # name: # type: sq_string # elements: 1 # length: 1267 CompactClassificationNeuralNetwork: label = predict ( obj , XC ) CompactClassificationNeuralNetwork: [ label , score ] = predict ( obj , XC ) Classify new data points into categories using the neural network classification model from a CompactClassificationNeuralNetwork object. label = predict ( obj , XC ) returns the vector of labels predicted for the corresponding instances in XC , using the neural network model stored in the CompactClassificationNeuralNetwork model, obj . obj must be a CompactClassificationNeuralNetwork class object. XC must be an M×P numeric matrix with the same number of features P as the corresponding predictors of the neural network model in obj . [ label , score ] = predict ( obj , XC ) also returns score , which contains the predicted class scores or posterior probabilities for each instance of the corresponding unique classes. The score matrix contains the classification scores for each class. For each observation in XC , the predicted class label is the one with the highest score among all classes. If the ScoreTransform property is set to a transformation function, the scores are transformed accordingly before being returned. See also: CompactClassificationNeuralNetwork, ClassificationNeuralNetwork, fitcnet # name: # type: sq_string # elements: 1 # length: 136 Classify new data points into categories using the neural network classification model from a CompactClassificationNeuralNetwork object. # name: # type: sq_string # elements: 1 # length: 44 CompactClassificationNeuralNetwork.savemodel # name: # type: sq_string # elements: 1 # length: 533 CompactClassificationNeuralNetwork: savemodel ( obj , filename ) Save a CompactClassificationNeuralNetwork object. savemodel ( obj , filename ) saves each property of a CompactClassificationNeuralNetwork object into an Octave binary file, the name of which is specified in filename , along with an extra variable, which defines the type classification object these variables constitute. Use loadmodel in order to load a classification object into Octave’s workspace. See also: loadmodel, fitcnet, ClassificationNeuralNetwork # name: # type: sq_string # elements: 1 # length: 49 Save a CompactClassificationNeuralNetwork object. # name: # type: sq_string # elements: 1 # length: 24 CompactClassificationSVM # name: # type: sq_string # elements: 1 # length: 706 statistics: CompactClassificationSVM Compact Support Vector Machine classification The CompactClassificationSVM class implements a compact version of a Support Vector Machine classifier object for one-class or two-class problems, which can predict responses for new data using the predict method. A CompactClassificationSVM object is a compact version of a support vector machine model, ClassificationSVM . It does not include the training data resulting in a smaller classifier size, which can be used for making predictions from new data, but not for tasks such as cross validation. It can only be created from a ClassificationSVM model by using the compact object method. See also: ClassificationSVM # name: # type: sq_string # elements: 1 # length: 45 Compact Support Vector Machine classification # name: # type: sq_string # elements: 1 # length: 30 CompactClassificationSVM.Alpha # name: # type: sq_string # elements: 1 # length: 361 CompactClassificationSVM: property Alpha Trained classifier coefficients The coefficients of the trained SVM classifier specified as an s×1 numeric vector, where s is the number of support vectors, rows (obj.SupportVectors) . If the SVM classifier was trained with a kernel function other than 'linear' , then Alpha is empty. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Trained classifier coefficients # name: # type: sq_string # elements: 1 # length: 29 CompactClassificationSVM.Beta # name: # type: sq_string # elements: 1 # length: 332 CompactClassificationSVM: property Beta Linear predictor coefficients The linear predictor coefficients specified as an s×1 numeric vector, where s is the number of support vectors, rows (obj.SupportVectors) . If the SVM classifier was trained with a 'linear' kernel function, then Beta is empty. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Linear predictor coefficients # name: # type: sq_string # elements: 1 # length: 29 CompactClassificationSVM.Bias # name: # type: sq_string # elements: 1 # length: 116 CompactClassificationSVM: property Bias Bias term The bias term specified as a scalar. This property is read-only. # name: # type: sq_string # elements: 1 # length: 9 Bias term # name: # type: sq_string # elements: 1 # length: 46 CompactClassificationSVM.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 236 CompactClassificationSVM: property CategoricalPredictors Indices of the categorical predictors A numeric vector of column indices into X naming the predictors treated as categorical, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 35 CompactClassificationSVM.ClassNames # name: # type: sq_string # elements: 1 # length: 352 CompactClassificationSVM: property ClassNames Names of classes in the response variable An array of unique values of the response variable Y , which has the same data types as the data in Y . This property is read-only. ClassNames can have any of the following datatypes: Cell array of character vectors Character array Logical vector Numeric vector # name: # type: sq_string # elements: 1 # length: 41 Names of classes in the response variable # name: # type: sq_string # elements: 1 # length: 49 CompactClassificationSVM.CompactClassificationSVM # name: # type: sq_string # elements: 1 # length: 429 CompactClassificationSVM: obj = CompactClassificationSVM ( Mdl ) CompactClassificationSVM: obj = CompactClassificationSVM () Create a CompactClassificationSVM object. Mdl is the ClassificationSVM object to compact. The documented way to reach this constructor is the compact method. Called with no arguments it returns an object with its properties empty, which is how a saved model is rebuilt before its values are filled in. # name: # type: sq_string # elements: 1 # length: 41 Create a CompactClassificationSVM object. # name: # type: sq_string # elements: 1 # length: 29 CompactClassificationSVM.Cost # name: # type: sq_string # elements: 1 # length: 202 CompactClassificationSVM: property Cost Cost of misclassification A numeric square matrix, where Cost(i,j) is the cost of classifying an observation of class i as class j . This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Cost of misclassification # name: # type: sq_string # elements: 1 # length: 47 CompactClassificationSVM.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 269 CompactClassificationSVM: property ExpandedPredictorNames Names of the predictors as the model expanded them A cell array of character vectors. It matches PredictorNames unless a categorical predictor was expanded into indicator variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 50 Names of the predictors as the model expanded them # name: # type: sq_string # elements: 1 # length: 41 CompactClassificationSVM.KernelParameters # name: # type: sq_string # elements: 1 # length: 315 CompactClassificationSVM: property KernelParameters Parameters of the kernel function A structure with fields Function and Scale , and Order for a polynomial kernel. Function names the kernel as MATLAB names it, so a radial basis kernel reports 'gaussian' whichever spelling was given. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Parameters of the kernel function # name: # type: sq_string # elements: 1 # length: 27 CompactClassificationSVM.Mu # name: # type: sq_string # elements: 1 # length: 257 CompactClassificationSVM: property Mu Predictor means A numeric vector of the same length as the columns in X containing the means of predictor variables. If the predictor variables have not been standardized, then Mu is empty. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Predictor means # name: # type: sq_string # elements: 1 # length: 38 CompactClassificationSVM.NumPredictors # name: # type: sq_string # elements: 1 # length: 217 CompactClassificationSVM: property NumPredictors Number of predictors A positive integer value specifying the number of predictors in the training dataset used for training the SVM model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationSVM.PredictorNames # name: # type: sq_string # elements: 1 # length: 264 CompactClassificationSVM: property PredictorNames Names of predictor variables A cell array of character vectors specifying the names of the predictor variables. The names are in the order in which they appear in the training dataset. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Names of predictor variables # name: # type: sq_string # elements: 1 # length: 30 CompactClassificationSVM.Prior # name: # type: sq_string # elements: 1 # length: 197 CompactClassificationSVM: property Prior Prior probabilities of the classes A numeric row vector with one entry per class, in the order of ClassNames , summing to one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Prior probabilities of the classes # name: # type: sq_string # elements: 1 # length: 37 CompactClassificationSVM.ResponseName # name: # type: sq_string # elements: 1 # length: 168 CompactClassificationSVM: property ResponseName Response variable name A character vector specifying the name of the response variable Y . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationSVM.ScoreTransform # name: # type: sq_string # elements: 1 # length: 1004 CompactClassificationSVM: property ScoreTransform Transformation function for classification scores Specified as a function handle for transforming the classification scores. Add or change the ScoreTransform property using dot notation as in: obj .ScoreTransform = 'function_name' obj .ScoreTransform = @function_handle When specified as a character vector, it can be any of the following built-in functions. Nevertheless, the ScoreTransform property always stores their function handle equivalent. Value Description 'doublelogit' 1 ./ (1 + exp (-2 × x)) 'invlogit' log (x ./ (1 - x)) 'ismax' Sets the score for the class with the largest score to 1, and for all other classes to 0 'logit' 1 ./ (1 + exp (-x)) 'none' x (no transformation) 'identity' x (no transformation) 'sign' -1 for x < 0, 0 for x = 0, 1 for x > 0 'symmetric' 2 × x - 1 'symmetricismax' Sets the score for the class with the largest score to 1, and for all other classes to -1 'symmetriclogit' 2 ./ (1 + exp (-x)) - 1 # name: # type: sq_string # elements: 1 # length: 49 Transformation function for classification scores # name: # type: sq_string # elements: 1 # length: 30 CompactClassificationSVM.Sigma # name: # type: sq_string # elements: 1 # length: 291 CompactClassificationSVM: property Sigma Predictor standard deviations A numeric vector of the same length as the columns in X containing the standard deviations of predictor variables. If the predictor variables have not been standardized, then Sigma is empty. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Predictor standard deviations # name: # type: sq_string # elements: 1 # length: 44 CompactClassificationSVM.SupportVectorLabels # name: # type: sq_string # elements: 1 # length: 499 CompactClassificationSVM: property SupportVectorLabels Support vector class labels The support vector class labels specified as an s×1 numeric vector, where s is the number of support vectors, rows (obj.SupportVectors) . A value of +1 in SupportVectorLabels indicates that the corresponding support vector belongs to the positive class (ClassNames{2}) . A value of -1 indicates that the corresponding support vector belongs to the negative class (ClassNames{1}) . This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Support vector class labels # name: # type: sq_string # elements: 1 # length: 39 CompactClassificationSVM.SupportVectors # name: # type: sq_string # elements: 1 # length: 320 CompactClassificationSVM: property SupportVectors Support vectors The support vectors of the trained SVM classifier specified an s×p numeric matrix, where s is the number of support vectors, rows (obj.SupportVectors) , and p is the number of predictor variables in the predictor data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Support vectors # name: # type: sq_string # elements: 1 # length: 46 CompactClassificationSVM.discardSupportVectors # name: # type: sq_string # elements: 1 # length: 632 CompactClassificationSVM: obj = discardSupportVectors ( obj ) Discard the support vectors of a linear SVM model. obj = discardSupportVectors ( obj ) empties Alpha , SupportVectors and SupportVectorLabels , leaving Beta and Bias to decide every prediction. A linear kernel needs nothing else, so the returned model predicts what it predicted before while carrying one vector in place of many. The kernel must be linear. Under any other the support vectors are part of the decision function and cannot be dropped. Discarding twice is not an error and changes nothing. See also: fitcsvm, ClassificationSVM, CompactClassificationSVM # name: # type: sq_string # elements: 1 # length: 50 Discard the support vectors of a linear SVM model. # name: # type: sq_string # elements: 1 # length: 29 CompactClassificationSVM.edge # name: # type: sq_string # elements: 1 # length: 613 CompactClassificationSVM: e = edge ( obj , X , Y ) CompactClassificationSVM: e = edge (…, "Weights" , w ) Classification edge, the mean of the classification margins. e = edge ( obj , X , Y ) reduces the vector that margin returns to a single number, the mean margin over the rows of X . It says how far the model puts the true class ahead of its nearest rival on average, so a larger edge is a better model, and unlike a loss it is not bounded above and rewards confidence rather than bare correctness. e = edge (…, "Weights" , w ) takes the weighted mean instead, with one weight per row of X . # name: # type: sq_string # elements: 1 # length: 60 Classification edge, the mean of the classification margins. # name: # type: sq_string # elements: 1 # length: 29 CompactClassificationSVM.loss # name: # type: sq_string # elements: 1 # length: 1905 CompactClassificationSVM: L = loss ( obj , X , Y ) CompactClassificationSVM: L = loss (…, name , value ) Compute loss for a trained CompactClassificationSVM object. L = loss ( obj , X , Y ) computes the loss, L , using the default loss function 'classiferror' . obj is a CompactClassificationSVM object. X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y must have same numbers of Rows as X . L = loss (…, name , value ) allows additional options specified by name - value pairs: Name Value 'LossFun' Specifies the loss function to use. Can be a function handle with four input arguments (C, S, W, Cost) which returns a scalar value or one of: ’binodeviance’, ’classifcost’, ’classiferror’, ’exponential’, ’hinge’, ’logit’,’mincost’, ’quadratic’. C is a logical matrix of size N×K , where N is the number of observations and K is the number of classes. The element C(i,j) is true if the class label of the i-th observation is equal to the j-th class. S is a numeric matrix of size N×K , where each element represents the classification score for the corresponding class. W is a numeric vector of length N , representing the observation weights. Cost is a K×K matrix representing the misclassification costs. 'Weights' Specifies observation weights, must be a numeric vector of length equal to the number of rows in X. Default is ones (size (X, 1)) . loss normalizes the weights so that observation weights in each class sum to the prior probability of that class. When you supply Weights, loss computes the weighted classification loss. See also: CompactClassificationSVM # name: # type: sq_string # elements: 1 # length: 59 Compute loss for a trained CompactClassificationSVM object. # name: # type: sq_string # elements: 1 # length: 31 CompactClassificationSVM.margin # name: # type: sq_string # elements: 1 # length: 806 CompactClassificationSVM: m = margin ( obj , X , Y ) Classification margins for Support Vector Machine classifier. m = margin ( obj , X , Y ) returns the classification margins for obj with data X and classification Y . m is a numeric vector of length size (X,1). obj is a CompactClassificationSVM object. X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y must have same numbers of Rows as X . The classification margin for each observation is the difference between the classification score for the true class and the maximal classification score for the false classes. See also: CompactClassificationSVM # name: # type: sq_string # elements: 1 # length: 61 Classification margins for Support Vector Machine classifier. # name: # type: sq_string # elements: 1 # length: 32 CompactClassificationSVM.predict # name: # type: sq_string # elements: 1 # length: 1707 CompactClassificationSVM: label = predict ( obj , XC ) CompactClassificationSVM: [ label , score ] = predict ( obj , XC ) CompactClassificationSVM: [ label , score , cost ] = predict ( obj , XC ) Classify new data points into categories using the Support Vector Machine classification model from a CompactClassificationSVM object. label = predict ( obj , XC ) returns the vector of labels predicted for the corresponding instances in XC , using the predictor data in the CompactClassificationSVM model, obj . For one-class SVM model, +1 or -1 is returned. obj must be a CompactClassificationSVM class object. XC must be an M×P numeric matrix with the same number of features P as the corresponding predictors of the SVM model in obj . [ label , score ] = predict ( obj , XC ) also returns score , which contains the decision values for each prediction. A ScoreTransform assigned to obj is applied to them, so score holds whatever that transform returns. Posterior probabilities need a transform fitted to the model, which this package does not compute yet. Deviation from MATLAB. cost is the expected cost of each assignment, sum_j P(j) Cost(j,k) . An SVM score is a signed distance to the boundary and not a posterior, so the only distribution available is the one concentrated on the predicted class and cost is the row of Cost belonging to it. MATLAB returns the column instead, which is the same matrix read the wrong way and contradicts its own ClassificationKNN , ClassificationDiscriminant and ClassificationNaiveBayes on any asymmetric cost matrix; the two agree wherever Cost is symmetric, the default included. Measured on R2024a. See also: CompactClassificationSVM, ClassificationSVM # name: # type: sq_string # elements: 1 # length: 134 Classify new data points into categories using the Support Vector Machine classification model from a CompactClassificationSVM object. # name: # type: sq_string # elements: 1 # length: 34 CompactClassificationSVM.savemodel # name: # type: sq_string # elements: 1 # length: 510 CompactClassificationSVM: savemodel ( obj , filename ) Save a CompactClassificationSVM object. savemodel ( obj , filename ) saves each property of a CompactClassificationSVM object into an Octave binary file, the name of which is specified in filename , along with an extra variable, which defines the type classification object these variables constitute. Use loadmodel in order to load a classification object into Octave’s workspace. See also: loadmodel, ClassificationSVM, CompactClassificationSVM # name: # type: sq_string # elements: 1 # length: 39 Save a CompactClassificationSVM object. # name: # type: sq_string # elements: 1 # length: 20 CompactRegressionGAM # name: # type: sq_string # elements: 1 # length: 1122 statistics: CompactRegressionGAM Compact generalized additive model regression The CompactRegressionGAM class implements a compact version of the generalized additive model regression object, which predicts responses for new data with the predict method but does not store the training data. A compact model consumes less memory than the full RegressionGAM model, but cannot perform tasks that need the training data, such as computing a resubstitution loss or the standard deviation of a prediction. Create a CompactRegressionGAM object by using the compact method on a RegressionGAM object. The engine that fitted the model is carried over in FitMethod , and the compact model predicts by the same scheme the full one did. Under 'boostedtrees' , the default, the fit is described by TreeModel , BinEdges and PairDetectionBinEdges . Under 'splines' it is described by Formula , BaseModel , ModelwInt and IntMatrix , which MATLAB’s compact model does not carry. Whichever fitted the model, the other set is empty. A standard deviation is available from the spline engine alone. See also: RegressionGAM, fitrgam # name: # type: sq_string # elements: 1 # length: 45 Compact generalized additive model regression # name: # type: sq_string # elements: 1 # length: 30 CompactRegressionGAM.BaseModel # name: # type: sq_string # elements: 1 # length: 308 CompactRegressionGAM: property BaseModel Model without interaction terms A structure holding the intercept, the piecewise polynomial of each predictor, the number of backfitting cycles, the residuals and the residual sum of squares of the model fitted without interaction terms. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Model without interaction terms # name: # type: sq_string # elements: 1 # length: 29 CompactRegressionGAM.BinEdges # name: # type: sq_string # elements: 1 # length: 140 CompactRegressionGAM: property BinEdges Bin edges of the fitted shape functions, empty under the spline engine. This property is read-only. # name: # type: sq_string # elements: 1 # length: 71 Bin edges of the fitted shape functions, empty under the spline engine. # name: # type: sq_string # elements: 1 # length: 42 CompactRegressionGAM.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 222 CompactRegressionGAM: property CategoricalPredictors Indices of the categorical predictors A numeric vector holding the column of each predictor treated as categorical, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 41 CompactRegressionGAM.CompactRegressionGAM # name: # type: sq_string # elements: 1 # length: 405 CompactRegressionGAM: obj = CompactRegressionGAM ( Mdl ) CompactRegressionGAM: obj = CompactRegressionGAM () Create a CompactRegressionGAM object. Mdl is the RegressionGAM object to compact. The documented way to reach this constructor is the compact method. Called with no arguments it returns an object with its properties empty, which is how a saved model is rebuilt before its values are filled in. # name: # type: sq_string # elements: 1 # length: 37 Create a CompactRegressionGAM object. # name: # type: sq_string # elements: 1 # length: 43 CompactRegressionGAM.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 297 CompactRegressionGAM: property ExpandedPredictorNames Names of the expanded predictor variables A cell array of character vectors naming the predictors as the model sees them. It matches PredictorNames unless a categorical predictor was expanded into dummy variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 41 Names of the expanded predictor variables # name: # type: sq_string # elements: 1 # length: 30 CompactRegressionGAM.FitMethod # name: # type: sq_string # elements: 1 # length: 130 CompactRegressionGAM: property FitMethod Which engine fitted the model, 'boostedtrees' or 'splines' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 59 Which engine fitted the model, 'boostedtrees' or 'splines'. # name: # type: sq_string # elements: 1 # length: 28 CompactRegressionGAM.Formula # name: # type: sq_string # elements: 1 # length: 229 CompactRegressionGAM: property Formula Formula of the model A character vector naming the response and the terms of the model, as in 'Y ~ x1 + x2 + x1:x2' , or empty when the model was not given one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Formula of the model # name: # type: sq_string # elements: 1 # length: 30 CompactRegressionGAM.IntMatrix # name: # type: sq_string # elements: 1 # length: 560 CompactRegressionGAM: property IntMatrix Every term the model fits A logical matrix with one row per term and one column per predictor, true wherever the term multiplies that predictor. A row naming one predictor is a main effect, two an interaction, and three or more a higher-order term. This property is read-only. It is the complete record, where Interactions reports only the two-way terms, in the form MATLAB reports them. It is also the form the 'Interactions' option takes back, so passing it to the constructor rebuilds a model over the same terms. # name: # type: sq_string # elements: 1 # length: 25 Every term the model fits # name: # type: sq_string # elements: 1 # length: 33 CompactRegressionGAM.Interactions # name: # type: sq_string # elements: 1 # length: 601 CompactRegressionGAM: property Interactions Two-way interaction terms of the fitted model A Kx2 matrix of predictor index pairs, one row per two-way term the model carries, and zeros (0, 2) when it carries none. It reports what was fitted rather than what was asked for, so a count of terms, 'all' , a logical matrix and a formula all leave the same kind of value behind. This property is read-only. A main effect names one predictor and a higher-order term names three or more, and neither has a two-column form, so neither appears here. IntMatrix remains the complete record of every term fitted. # name: # type: sq_string # elements: 1 # length: 45 Two-way interaction terms of the fitted model # name: # type: sq_string # elements: 1 # length: 30 CompactRegressionGAM.Intercept # name: # type: sq_string # elements: 1 # length: 191 CompactRegressionGAM: property Intercept Intercept of the fitted model A numeric scalar, the mean of the response, which every additive term is measured against. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Intercept of the fitted model # name: # type: sq_string # elements: 1 # length: 43 CompactRegressionGAM.IsStandardDeviationFit # name: # type: sq_string # elements: 1 # length: 286 CompactRegressionGAM: property IsStandardDeviationFit Flag for a fitted standard deviation model A boolean flag, always false , as this class estimates the standard deviation of a prediction from the residuals of the fit rather than fitting a model for it. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Flag for a fitted standard deviation model # name: # type: sq_string # elements: 1 # length: 30 CompactRegressionGAM.ModelwInt # name: # type: sq_string # elements: 1 # length: 229 CompactRegressionGAM: property ModelwInt Model with interaction terms A structure of the same fields as BaseModel , for the model fitted with the interaction terms, and empty when none was asked for. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Model with interaction terms # name: # type: sq_string # elements: 1 # length: 34 CompactRegressionGAM.NumPredictors # name: # type: sq_string # elements: 1 # length: 162 CompactRegressionGAM: property NumPredictors Number of predictors A positive integer, the number of predictors of the training data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 42 CompactRegressionGAM.PairDetectionBinEdges # name: # type: sq_string # elements: 1 # length: 168 CompactRegressionGAM: property PairDetectionBinEdges Coarse bin edges the interaction terms are held on, empty when the model carries none. This property is read-only. # name: # type: sq_string # elements: 1 # length: 86 Coarse bin edges the interaction terms are held on, empty when the model carries none. # name: # type: sq_string # elements: 1 # length: 35 CompactRegressionGAM.PredictorNames # name: # type: sq_string # elements: 1 # length: 212 CompactRegressionGAM: property PredictorNames Names of the predictor variables A cell array of character vectors naming the predictors, in the order they appear in the training data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Names of the predictor variables # name: # type: sq_string # elements: 1 # length: 33 CompactRegressionGAM.ResponseName # name: # type: sq_string # elements: 1 # length: 148 CompactRegressionGAM: property ResponseName Response variable name A character vector naming the response variable Y . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 38 CompactRegressionGAM.ResponseTransform # name: # type: sq_string # elements: 1 # length: 320 CompactRegressionGAM: property ResponseTransform Transformation applied to the predicted response A function handle applied to the response the model predicts. Add or change it using dot notation, as in obj .ResponseTransform = 'log' or obj .ResponseTransform = @function_handle . It defaults to 'none' , the identity. # name: # type: sq_string # elements: 1 # length: 48 Transformation applied to the predicted response # name: # type: sq_string # elements: 1 # length: 30 CompactRegressionGAM.TreeModel # name: # type: sq_string # elements: 1 # length: 153 CompactRegressionGAM: property TreeModel The fitted shape functions and interaction surfaces, empty under the spline engine. This property is read-only. # name: # type: sq_string # elements: 1 # length: 83 The fitted shape functions and interaction surfaces, empty under the spline engine. # name: # type: sq_string # elements: 1 # length: 25 CompactRegressionGAM.loss # name: # type: sq_string # elements: 1 # length: 686 CompactRegressionGAM: L = loss ( obj , X , Y ) CompactRegressionGAM: L = loss (…, name , value ) Regression loss of a generalized additive model. L = loss ( obj , X , Y ) returns the weighted mean squared error of the model on the rows of X against the true response Y . L = loss (…, name , value ) accepts the following name-value pairs: "LossFun" selects the loss, either "mse" , the default, or a function handle taking the true response, the predicted response and the weights, and returning a numeric scalar. "Weights" holds one weight per row of X , normalised to sum to one before it is applied. See also: CompactRegressionGAM, RegressionGAM, fitrgam, predict # name: # type: sq_string # elements: 1 # length: 48 Regression loss of a generalized additive model. # name: # type: sq_string # elements: 1 # length: 28 CompactRegressionGAM.predict # name: # type: sq_string # elements: 1 # length: 1718 CompactRegressionGAM: yFit = predict ( obj , Xfit ) CompactRegressionGAM: yFit = predict (…, Name , Value ) CompactRegressionGAM: [ yFit , ySD , yInt ] = predict (…) Predict new data points using generalized additive model regression object. yFit = predict ( obj , Xfit returns a vector of predicted responses, yFit , for the predictor data in matrix Xfit based on the Generalized Additive Model in obj . Xfit must have the same number of features/variables as the training data in obj . obj must be a CompactRegressionGAM class object. [ yFit , ySD , yInt ] = predict ( obj , Xfit also returns the standard deviations, ySD , and prediction intervals, yInt , of the response variable yFit , evaluated at each observation in the predictor data Xfit . yFit = predict (…, Name , Value ) returns the aforementioned results with additional properties specified by Name-Value pair arguments listed below. Name Value 'alpha' significance level of the prediction intervals yInt , specified as scalar in range [0,1] . The default value is 0.05, which corresponds to 95% prediction intervals. 'includeinteractions' a boolean flag to include interactions to predict new values based on Xfit . By default, 'includeinteractions' is true when the GAM model in obj contains a obj.Formula or obj.Interactions fields. Otherwise, is set to false . If set to true when no interactions are present in the trained model, it will result to an error. If set to false when using a model that includes interactions, the predictions will be made on the basic model without any interaction terms. This way you can make predictions from the same GAM model without having to retrain it. See also: fitrgam, RegressionGAM # name: # type: sq_string # elements: 1 # length: 75 Predict new data points using generalized additive model regression object. # name: # type: sq_string # elements: 1 # length: 30 CompactRegressionGAM.savemodel # name: # type: sq_string # elements: 1 # length: 438 CompactRegressionGAM: savemodel ( obj , filename ) Save a CompactRegressionGAM object. savemodel ( obj , filename ) saves each property of a CompactRegressionGAM object into an Octave binary file, the name of which is specified in filename , along with an extra variable which defines the type of object these variables constitute. Use loadmodel in order to load the object back into Octave. See also: loadmodel, fitrgam, RegressionGAM # name: # type: sq_string # elements: 1 # length: 35 Save a CompactRegressionGAM object. # name: # type: sq_string # elements: 1 # length: 19 CompactRegressionGP # name: # type: sq_string # elements: 1 # length: 1037 statistics: CompactRegressionGP Compact Gaussian process regression A CompactRegressionGP object holds a Gaussian process regression model without its training data, keeping what is needed to predict and dropping the rest. Create a CompactRegressionGP object by using the compact method of a RegressionGP object. A compact model keeps the active set it predicts from, the prediction weights, the covariance function and its parameters, the explicit basis and its coefficients, the noise standard deviation and the standardizing location and scale. It drops the response, the observation weights, the rows used, the count of observations and the maximized log likelihood, so it can predict but cannot be cross validated, refitted, or asked for its resubstitution loss or its post-fit statistics. The standard deviation and the prediction intervals remain available, because the active set of an exactly fitted model is the whole of the training predictors and the factorization can be rebuilt from it. See also: RegressionGP, fitrgp # name: # type: sq_string # elements: 1 # length: 35 Compact Gaussian process regression # name: # type: sq_string # elements: 1 # length: 35 CompactRegressionGP.ActiveSetMethod # name: # type: sq_string # elements: 1 # length: 123 CompactRegressionGP: property ActiveSetMethod Method used to select the active set 'Random' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Method used to select the active set # name: # type: sq_string # elements: 1 # length: 33 CompactRegressionGP.ActiveSetSize # name: # type: sq_string # elements: 1 # length: 123 CompactRegressionGP: property ActiveSetSize Size of the active set A positive integer scalar. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Size of the active set # name: # type: sq_string # elements: 1 # length: 36 CompactRegressionGP.ActiveSetVectors # name: # type: sq_string # elements: 1 # length: 206 CompactRegressionGP: property ActiveSetVectors Subset of the training data used for predictions An MxP numeric matrix, standardized where the model standardized its predictors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 48 Subset of the training data used for predictions # name: # type: sq_string # elements: 1 # length: 25 CompactRegressionGP.Alpha # name: # type: sq_string # elements: 1 # length: 159 CompactRegressionGP: property Alpha Weights the predictions are made from A numeric vector with one weight per active set vector. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Weights the predictions are made from # name: # type: sq_string # elements: 1 # length: 33 CompactRegressionGP.BasisFunction # name: # type: sq_string # elements: 1 # length: 142 CompactRegressionGP: property BasisFunction Explicit basis of the model A character vector or a function handle. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Explicit basis of the model # name: # type: sq_string # elements: 1 # length: 24 CompactRegressionGP.Beta # name: # type: sq_string # elements: 1 # length: 160 CompactRegressionGP: property Beta Estimated coefficients of the explicit basis A numeric vector, empty when the basis is 'None' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Estimated coefficients of the explicit basis # name: # type: sq_string # elements: 1 # length: 41 CompactRegressionGP.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 160 CompactRegressionGP: property CategoricalPredictors Indices of the categorical predictors A vector of positive integers, or empty. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 39 CompactRegressionGP.CompactRegressionGP # name: # type: sq_string # elements: 1 # length: 282 CompactRegressionGP: obj = CompactRegressionGP ( Mdl ) Create a CompactRegressionGP object. Mdl is the RegressionGP object to compact, and is required: the compact model has no training data to build itself from. The documented way to reach this constructor is the compact method. # name: # type: sq_string # elements: 1 # length: 36 Create a CompactRegressionGP object. # name: # type: sq_string # elements: 1 # length: 42 CompactRegressionGP.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 151 CompactRegressionGP: property ExpandedPredictorNames Expanded predictor variable names A cell array of character vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Expanded predictor variable names # name: # type: sq_string # elements: 1 # length: 29 CompactRegressionGP.FitMethod # name: # type: sq_string # elements: 1 # length: 128 CompactRegressionGP: property FitMethod Method used to estimate the parameters 'Exact' or 'None' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Method used to estimate the parameters # name: # type: sq_string # elements: 1 # length: 34 CompactRegressionGP.KernelFunction # name: # type: sq_string # elements: 1 # length: 147 CompactRegressionGP: property KernelFunction Form of the covariance function A character vector or a function handle. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Form of the covariance function # name: # type: sq_string # elements: 1 # length: 37 CompactRegressionGP.KernelInformation # name: # type: sq_string # elements: 1 # length: 191 CompactRegressionGP: property KernelInformation Covariance function and its parameters A structure with fields Name , KernelParameters and KernelParameterNames . This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Covariance function and its parameters # name: # type: sq_string # elements: 1 # length: 33 CompactRegressionGP.PredictMethod # name: # type: sq_string # elements: 1 # length: 115 CompactRegressionGP: property PredictMethod Method used to make predictions 'Exact' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Method used to make predictions # name: # type: sq_string # elements: 1 # length: 37 CompactRegressionGP.PredictorLocation # name: # type: sq_string # elements: 1 # length: 146 CompactRegressionGP: property PredictorLocation Means the predictors were centred by A 1xP numeric vector, or empty. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Means the predictors were centred by # name: # type: sq_string # elements: 1 # length: 34 CompactRegressionGP.PredictorNames # name: # type: sq_string # elements: 1 # length: 134 CompactRegressionGP: property PredictorNames Predictor variable names A cell array of character vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Predictor variable names # name: # type: sq_string # elements: 1 # length: 34 CompactRegressionGP.PredictorScale # name: # type: sq_string # elements: 1 # length: 156 CompactRegressionGP: property PredictorScale Standard deviations the predictors were scaled by A 1xP numeric vector, or empty. This property is read-only. # name: # type: sq_string # elements: 1 # length: 49 Standard deviations the predictors were scaled by # name: # type: sq_string # elements: 1 # length: 32 CompactRegressionGP.ResponseName # name: # type: sq_string # elements: 1 # length: 115 CompactRegressionGP: property ResponseName Response variable name A character vector. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 37 CompactRegressionGP.ResponseTransform # name: # type: sq_string # elements: 1 # length: 204 CompactRegressionGP: property ResponseTransform Transformation applied to the predicted response A character vector, or the text of the function handle that was supplied. Assigning to it accepts either. # name: # type: sq_string # elements: 1 # length: 48 Transformation applied to the predicted response # name: # type: sq_string # elements: 1 # length: 25 CompactRegressionGP.Sigma # name: # type: sq_string # elements: 1 # length: 119 CompactRegressionGP: property Sigma Estimated noise standard deviation A positive scalar. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Estimated noise standard deviation # name: # type: sq_string # elements: 1 # length: 24 CompactRegressionGP.loss # name: # type: sq_string # elements: 1 # length: 338 CompactRegressionGP: L = loss ( obj , X , Y ) CompactRegressionGP: L = loss (…, name , value ) Compute the regression loss of a compact Gaussian process model. L = loss ( obj , X , Y ) returns the mean squared error of the model obj on the data X and Y , and accepts the same 'LossFun' and 'Weights' pairs the full model accepts. # name: # type: sq_string # elements: 1 # length: 64 Compute the regression loss of a compact Gaussian process model. # name: # type: sq_string # elements: 1 # length: 27 CompactRegressionGP.predict # name: # type: sq_string # elements: 1 # length: 526 CompactRegressionGP: yFit = predict ( obj , XC ) CompactRegressionGP: [ yFit , ySD , yInt ] = predict ( obj , XC ) CompactRegressionGP: […] = predict (…, 'Alpha' , alpha ) Predict the response for new data with a compact Gaussian process model. yFit = predict ( obj , XC ) returns the predicted response of the CompactRegressionGP model obj at the points in XC , and the further outputs are the standard deviation of each predicted response and the prediction intervals, exactly as the full model returns them. # name: # type: sq_string # elements: 1 # length: 72 Predict the response for new data with a compact Gaussian process model. # name: # type: sq_string # elements: 1 # length: 29 CompactRegressionGP.savemodel # name: # type: sq_string # elements: 1 # length: 198 CompactRegressionGP: savemodel ( obj , filename ) Save a compact Gaussian process model to a file. savemodel ( obj , filename ) saves the model obj into filename in a form loadmodel can read back. # name: # type: sq_string # elements: 1 # length: 48 Save a compact Gaussian process model to a file. # name: # type: sq_string # elements: 1 # length: 30 CompactRegressionNeuralNetwork # name: # type: sq_string # elements: 1 # length: 875 statistics: CompactRegressionNeuralNetwork Compact neural network regression A CompactRegressionNeuralNetwork object holds a neural network regression model that has dropped its training data. Create a CompactRegressionNeuralNetwork object by using the compact method of a RegressionNeuralNetwork object. The compact model keeps what is needed to answer about new data, the layer weights and biases, the activations, the standardization and the response transform, and drops what only describes the fit: the predictor and response data, the observation weights, the rows used, the number of observations and the iteration by iteration training history. predict and loss therefore agree with the full model to the last digit, while resubPredict and resubLoss do not exist here, there being no training data left to resubstitute. See also: RegressionNeuralNetwork, fitrnet # name: # type: sq_string # elements: 1 # length: 33 Compact neural network regression # name: # type: sq_string # elements: 1 # length: 42 CompactRegressionNeuralNetwork.Activations # name: # type: sq_string # elements: 1 # length: 214 CompactRegressionNeuralNetwork: property Activations Activation functions of the hidden layers A character vector, or a cell array of character vectors with one entry per hidden layer. This property is read-only. # name: # type: sq_string # elements: 1 # length: 41 Activation functions of the hidden layers # name: # type: sq_string # elements: 1 # length: 52 CompactRegressionNeuralNetwork.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 190 CompactRegressionNeuralNetwork: property CategoricalPredictors Indices of the categorical predictors A numeric vector of column indices, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 61 CompactRegressionNeuralNetwork.CompactRegressionNeuralNetwork # name: # type: sq_string # elements: 1 # length: 465 CompactRegressionNeuralNetwork: obj = CompactRegressionNeuralNetwork ( Mdl ) CompactRegressionNeuralNetwork: obj = CompactRegressionNeuralNetwork () Create a CompactRegressionNeuralNetwork object. Mdl is the RegressionNeuralNetwork object to compact. The documented way to reach this constructor is the compact method. Called with no arguments it returns an object with its properties empty, which is how a saved model is rebuilt before its values are filled in. # name: # type: sq_string # elements: 1 # length: 47 Create a CompactRegressionNeuralNetwork object. # name: # type: sq_string # elements: 1 # length: 53 CompactRegressionNeuralNetwork.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 179 CompactRegressionNeuralNetwork: property ExpandedPredictorNames Names of the predictors as the model expanded them A cell array of character vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 50 Names of the predictors as the model expanded them # name: # type: sq_string # elements: 1 # length: 42 CompactRegressionNeuralNetwork.LayerBiases # name: # type: sq_string # elements: 1 # length: 175 CompactRegressionNeuralNetwork: property LayerBiases Biases the network learned A cell array with one entry per layer, the output layer included. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Biases the network learned # name: # type: sq_string # elements: 1 # length: 41 CompactRegressionNeuralNetwork.LayerSizes # name: # type: sq_string # elements: 1 # length: 181 CompactRegressionNeuralNetwork: property LayerSizes Sizes of the fully connected hidden layers A row vector of positive integers, one per hidden layer. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Sizes of the fully connected hidden layers # name: # type: sq_string # elements: 1 # length: 43 CompactRegressionNeuralNetwork.LayerWeights # name: # type: sq_string # elements: 1 # length: 177 CompactRegressionNeuralNetwork: property LayerWeights Weights the network learned A cell array with one entry per layer, the output layer included. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Weights the network learned # name: # type: sq_string # elements: 1 # length: 33 CompactRegressionNeuralNetwork.Mu # name: # type: sq_string # elements: 1 # length: 218 CompactRegressionNeuralNetwork: property Mu Mean of the predictors A row vector with one entry per predictor, used for standardization. Empty when the predictor data were not standardized. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Mean of the predictors # name: # type: sq_string # elements: 1 # length: 44 CompactRegressionNeuralNetwork.NumPredictors # name: # type: sq_string # elements: 1 # length: 132 CompactRegressionNeuralNetwork: property NumPredictors Number of predictors A positive integer scalar. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 52 CompactRegressionNeuralNetwork.OutputLayerActivation # name: # type: sq_string # elements: 1 # length: 229 CompactRegressionNeuralNetwork: property OutputLayerActivation Activation function of the output layer A character vector. 'none' applies the identity, so a prediction is an unrestricted real number. This property is read-only. # name: # type: sq_string # elements: 1 # length: 39 Activation function of the output layer # name: # type: sq_string # elements: 1 # length: 45 CompactRegressionNeuralNetwork.PredictorNames # name: # type: sq_string # elements: 1 # length: 144 CompactRegressionNeuralNetwork: property PredictorNames Names of the predictors A cell array of character vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Names of the predictors # name: # type: sq_string # elements: 1 # length: 43 CompactRegressionNeuralNetwork.ResponseName # name: # type: sq_string # elements: 1 # length: 133 CompactRegressionNeuralNetwork: property ResponseName Name of the response variable A character vector. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Name of the response variable # name: # type: sq_string # elements: 1 # length: 48 CompactRegressionNeuralNetwork.ResponseTransform # name: # type: sq_string # elements: 1 # length: 277 CompactRegressionNeuralNetwork: property ResponseTransform Transformation applied to the predicted response A function handle, applied by predict to the network’s output. It may be set after construction, either to a handle or to the name of a supported transformation. # name: # type: sq_string # elements: 1 # length: 48 Transformation applied to the predicted response # name: # type: sq_string # elements: 1 # length: 36 CompactRegressionNeuralNetwork.Sigma # name: # type: sq_string # elements: 1 # length: 235 CompactRegressionNeuralNetwork: property Sigma Standard deviation of the predictors A row vector with one entry per predictor, used for standardization. Empty when the predictor data were not standardized. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Standard deviation of the predictors # name: # type: sq_string # elements: 1 # length: 35 CompactRegressionNeuralNetwork.loss # name: # type: sq_string # elements: 1 # length: 981 CompactRegressionNeuralNetwork: L = loss ( obj , X , Y ) CompactRegressionNeuralNetwork: L = loss (…, name , value ) Compute the regression loss of a compact neural network model. L = loss ( obj , X , Y ) returns the weighted mean squared error between the response Y and the response the model predicts for X . obj must be a CompactRegressionNeuralNetwork class object. X must be a numeric matrix with the same number of predictors as the data the model was trained on. Y must be a numeric vector with as many rows as X . L = loss (…, name , value ) accepts the following Name-Value pairs. Name Value 'LossFun' 'mse' , the default, or a function handle called as lossfun ( Y , yFit , W ) and returning a scalar. 'Weights' A numeric vector of observation weights with one entry per row of X . It defaults to a uniform weight. The weights are normalized to sum to one before the loss is formed. See also: CompactRegressionNeuralNetwork, RegressionNeuralNetwork # name: # type: sq_string # elements: 1 # length: 62 Compute the regression loss of a compact neural network model. # name: # type: sq_string # elements: 1 # length: 38 CompactRegressionNeuralNetwork.predict # name: # type: sq_string # elements: 1 # length: 538 CompactRegressionNeuralNetwork: yFit = predict ( obj , XC ) Predict the response for new data with a compact neural network regression model. yFit = predict ( obj , XC ) returns a column vector holding the predicted response for each row of XC . It agrees with the full model this object was compacted from. obj must be a CompactRegressionNeuralNetwork class object. XC must be a numeric matrix with the same number of predictors as the data the model was trained on. See also: CompactRegressionNeuralNetwork, RegressionNeuralNetwork # name: # type: sq_string # elements: 1 # length: 81 Predict the response for new data with a compact neural network regression model. # name: # type: sq_string # elements: 1 # length: 40 CompactRegressionNeuralNetwork.savemodel # name: # type: sq_string # elements: 1 # length: 349 CompactRegressionNeuralNetwork: savemodel ( obj , filename ) Save a compact neural network regression model to a file. savemodel ( obj , filename ) saves every property of the CompactRegressionNeuralNetwork object obj into filename in binary format, so that it can be read back with loadmodel . See also: loadmodel, CompactRegressionNeuralNetwork # name: # type: sq_string # elements: 1 # length: 57 Save a compact neural network regression model to a file. # name: # type: sq_string # elements: 1 # length: 20 CompactRegressionSVM # name: # type: sq_string # elements: 1 # length: 852 statistics: CompactRegressionSVM Compact Support Vector Machine regression A CompactRegressionSVM object holds a support vector regression model that has dropped its training data. Create a CompactRegressionSVM object by using the compact method of a RegressionSVM object. The compact model keeps what is needed to answer about new data, the support vectors and their coefficients, the intercept, the kernel, the standardization and the response transform, and drops what only describes the fit: the predictor and response data, the observation weights, the rows used, the observation count, and which training rows became support vectors. predict and loss therefore agree with the full model to the last digit, while resubPredict and resubLoss do not exist here, there being no training data left to resubstitute. See also: RegressionSVM, fitrsvm # name: # type: sq_string # elements: 1 # length: 41 Compact Support Vector Machine regression # name: # type: sq_string # elements: 1 # length: 26 CompactRegressionSVM.Alpha # name: # type: sq_string # elements: 1 # length: 219 CompactRegressionSVM: property Alpha Dual coefficients of the support vectors A numeric column vector with one entry per support vector, signed, as in the model this one was compacted from. This property is read-only. # name: # type: sq_string # elements: 1 # length: 40 Dual coefficients of the support vectors # name: # type: sq_string # elements: 1 # length: 25 CompactRegressionSVM.Beta # name: # type: sq_string # elements: 1 # length: 216 CompactRegressionSVM: property Beta Primal coefficients, one per predictor A numeric column vector, equal to obj.SupportVectors' * obj.Alpha , and empty for any kernel other than linear. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Primal coefficients, one per predictor # name: # type: sq_string # elements: 1 # length: 25 CompactRegressionSVM.Bias # name: # type: sq_string # elements: 1 # length: 181 CompactRegressionSVM: property Bias Intercept of the fitted function A numeric scalar. With a linear kernel the prediction is X * obj.Beta + obj.Bias . This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Intercept of the fitted function # name: # type: sq_string # elements: 1 # length: 42 CompactRegressionSVM.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 180 CompactRegressionSVM: property CategoricalPredictors Indices of the categorical predictors A numeric vector of column indices, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 41 CompactRegressionSVM.CompactRegressionSVM # name: # type: sq_string # elements: 1 # length: 405 CompactRegressionSVM: obj = CompactRegressionSVM ( Mdl ) CompactRegressionSVM: obj = CompactRegressionSVM () Create a CompactRegressionSVM object. Mdl is the RegressionSVM object to compact. The documented way to reach this constructor is the compact method. Called with no arguments it returns an object with its properties empty, which is how a saved model is rebuilt before its values are filled in. # name: # type: sq_string # elements: 1 # length: 37 Create a CompactRegressionSVM object. # name: # type: sq_string # elements: 1 # length: 28 CompactRegressionSVM.Epsilon # name: # type: sq_string # elements: 1 # length: 241 CompactRegressionSVM: property Epsilon Half-width of the insensitive tube A non-negative scalar, carried over from the model this one was compacted from. It is what the 'epsiloninsensitive' loss charges against. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Half-width of the insensitive tube # name: # type: sq_string # elements: 1 # length: 43 CompactRegressionSVM.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 169 CompactRegressionSVM: property ExpandedPredictorNames Names of the predictors as the model expanded them A cell array of character vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 50 Names of the predictors as the model expanded them # name: # type: sq_string # elements: 1 # length: 37 CompactRegressionSVM.KernelParameters # name: # type: sq_string # elements: 1 # length: 311 CompactRegressionSVM: property KernelParameters Parameters of the kernel function A structure with fields Function and Scale , and Order for a polynomial kernel. Function names the kernel as MATLAB names it, so a radial basis kernel reports 'gaussian' whichever spelling was given. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Parameters of the kernel function # name: # type: sq_string # elements: 1 # length: 23 CompactRegressionSVM.Mu # name: # type: sq_string # elements: 1 # length: 208 CompactRegressionSVM: property Mu Mean of the predictors A row vector with one entry per predictor, used for standardization. Empty when the predictor data were not standardized. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Mean of the predictors # name: # type: sq_string # elements: 1 # length: 34 CompactRegressionSVM.NumPredictors # name: # type: sq_string # elements: 1 # length: 122 CompactRegressionSVM: property NumPredictors Number of predictors A positive integer scalar. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 35 CompactRegressionSVM.PredictorNames # name: # type: sq_string # elements: 1 # length: 134 CompactRegressionSVM: property PredictorNames Names of the predictors A cell array of character vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Names of the predictors # name: # type: sq_string # elements: 1 # length: 33 CompactRegressionSVM.ResponseName # name: # type: sq_string # elements: 1 # length: 123 CompactRegressionSVM: property ResponseName Name of the response variable A character vector. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Name of the response variable # name: # type: sq_string # elements: 1 # length: 38 CompactRegressionSVM.ResponseTransform # name: # type: sq_string # elements: 1 # length: 265 CompactRegressionSVM: property ResponseTransform Transformation applied to the predicted response A function handle, applied by predict to the model’s output. It may be set after construction, either to a handle or to the name of a supported transformation. # name: # type: sq_string # elements: 1 # length: 48 Transformation applied to the predicted response # name: # type: sq_string # elements: 1 # length: 26 CompactRegressionSVM.Sigma # name: # type: sq_string # elements: 1 # length: 225 CompactRegressionSVM: property Sigma Standard deviation of the predictors A row vector with one entry per predictor, used for standardization. Empty when the predictor data were not standardized. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Standard deviation of the predictors # name: # type: sq_string # elements: 1 # length: 35 CompactRegressionSVM.SupportVectors # name: # type: sq_string # elements: 1 # length: 195 CompactRegressionSVM: property SupportVectors The support vectors themselves A numeric matrix with one row per support vector, on the scale the model was trained on. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 The support vectors themselves # name: # type: sq_string # elements: 1 # length: 42 CompactRegressionSVM.discardSupportVectors # name: # type: sq_string # elements: 1 # length: 598 CompactRegressionSVM: obj = discardSupportVectors ( obj ) Discard the support vectors of a linear SVM model. obj = discardSupportVectors ( obj ) empties Alpha and SupportVectors , leaving Beta and Bias to decide every prediction. A linear kernel needs nothing else, so the returned model predicts what it predicted before while carrying one vector in place of many. The kernel must be linear. Under any other the support vectors are part of the decision function and cannot be dropped. Discarding twice is not an error and changes nothing. See also: fitrsvm, RegressionSVM, CompactRegressionSVM # name: # type: sq_string # elements: 1 # length: 50 Discard the support vectors of a linear SVM model. # name: # type: sq_string # elements: 1 # length: 25 CompactRegressionSVM.loss # name: # type: sq_string # elements: 1 # length: 958 CompactRegressionSVM: L = loss ( obj , X , Y ) CompactRegressionSVM: L = loss (…, name , value ) Compute the regression loss of a compact support vector machine model. L = loss ( obj , X , Y ) returns the weighted mean squared error between the response Y and the response the model predicts for X . obj must be a CompactRegressionSVM class object. X must be a numeric matrix with the same number of predictors as the data the model was trained on. Y must be a numeric vector with as many rows as X . L = loss (…, name , value ) accepts the following Name-Value pairs. Name Value 'LossFun' 'mse' , the default, 'epsiloninsensitive' , or a function handle called as lossfun ( Y , yFit , W ) returning a scalar. 'Weights' A numeric vector of observation weights with one entry per row of X . It defaults to a uniform weight. The weights are normalized to sum to one before the loss is formed. See also: CompactRegressionSVM, RegressionSVM # name: # type: sq_string # elements: 1 # length: 70 Compute the regression loss of a compact support vector machine model. # name: # type: sq_string # elements: 1 # length: 28 CompactRegressionSVM.predict # name: # type: sq_string # elements: 1 # length: 498 CompactRegressionSVM: yFit = predict ( obj , XC ) Predict the response for new data with a compact support vector regression model. yFit = predict ( obj , XC ) returns a column vector holding the predicted response for each row of XC . It agrees with the full model this object was compacted from. obj must be a CompactRegressionSVM class object. XC must be a numeric matrix with the same number of predictors as the data the model was trained on. See also: CompactRegressionSVM, RegressionSVM # name: # type: sq_string # elements: 1 # length: 81 Predict the response for new data with a compact support vector regression model. # name: # type: sq_string # elements: 1 # length: 30 CompactRegressionSVM.savemodel # name: # type: sq_string # elements: 1 # length: 319 CompactRegressionSVM: savemodel ( obj , filename ) Save a compact support vector regression model to a file. savemodel ( obj , filename ) saves every property of the CompactRegressionSVM object obj into filename in binary format, so that it can be read back with loadmodel . See also: loadmodel, CompactRegressionSVM # name: # type: sq_string # elements: 1 # length: 57 Save a compact support vector regression model to a file. # name: # type: sq_string # elements: 1 # length: 13 RegressionGAM # name: # type: sq_string # elements: 1 # length: 6392 statistics: obj = RegressionGAM ( X , Y ) statistics: obj = RegressionGAM (…, name , value ) Create a RegressionGAM class object containing a Generalized Additive Model (GAM) for regression. A RegressionGAM class object can store the predictors and response data along with various parameters for the GAM model. It is recommended to use the fitrgam function to create a RegressionGAM object. obj = RegressionGAM ( X , Y ) returns an object of class RegressionGAM, with matrix X containing the predictor data and vector Y containing the continuous response data. X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. X will be used to train the GAM model. Y must be N×1 numeric vector containing the response data corresponding to the predictor data in X . Y must have same number of rows as X . obj = RegressionGAM (…, name , value ) returns an object of class RegressionGAM with additional properties specified by Name-Value pair arguments listed below. Name Value 'predictors' Predictor Variable names, specified as a row vector cell of strings with the same length as the columns in X . If omitted, the program will generate default variable names (x1, x2, ..., xn) for each column in X . 'responsename' Response Variable Name, specified as a string. If omitted, the default value is 'Y' . 'formula' (spline option) a model specification given as a string in the form 'Y ~ terms' where Y represents the response variable and terms the predictor variables. The formula can be used to specify a subset of variables for training model. For example: 'Y ~ x1 + x2 + x3 + x4 + x1:x2 + x2:x3' specifies four linear terms for the first four columns of for predictor data, and x1:x2 and x2:x3 specify the two interaction terms for 1st-2nd and 3rd-4th columns respectively. Only these terms will be used for training the model, but X must have at least as many columns as referenced in the formula. If Predictor Variable names have been defined, then the terms in the formula must reference to those. When 'formula' is specified, all terms used for training the model are referenced in the IntMatrix field of the obj class object as a matrix containing the column indexes for each term including both the predictors and the interactions used. 'interactions' a logical matrix, a positive integer scalar, or the string 'all' for defining the interactions between predictor variables. When given a logical matrix, it must have the same number of columns as X and each row corresponds to a different interaction term combining the predictors indexed as true . Each interaction term is appended as a column vector after the available predictor column in X . When 'all' is defined, then all possible combinations of interactions are appended in X before training. At the moment, parsing a positive integer has the same effect as the 'all' option. When 'interactions' is specified, only the interaction terms appended to X are referenced in the IntMatrix field of the obj class object. 'knots' (spline option) a scalar or a row vector with the same columns as X . It defines the knots for fitting a polynomial when training the GAM. As a scalar, it is expanded to a row vector. The default value is 5, hence expanded to ones (1, columns (X)) * 5 . You can parse a row vector with different number of knots for each predictor variable to be fitted with, although not recommended. 'order' (spline option) a scalar or a row vector with the same columns as X . It defines the order of the polynomial when training the GAM. As a scalar, it is expanded to a row vector. The default values is 3, hence expanded to ones (1, columns (X)) * 3 . You can parse a row vector with different number of polynomial order for each predictor variable to be fitted with, although not recommended. 'dof' (spline option) a scalar or a row vector with the same columns as X . It defines the degrees of freedom for fitting a polynomial when training the GAM. As a scalar, it is expanded to a row vector. The default value is 8, hence expanded to ones (1, columns (X)) * 8 . You can parse a row vector with different degrees of freedom for each predictor variable to be fitted with, although not recommended. 'tol' (spline option) a positive scalar to set the tolerance for convergence during training. By default, it is set to 1e-3 . A row marked (spline option) belongs to the spline engine and requires 'FitMethod', 'splines' ; passing one under the default boosted-tree engine is an error rather than being ignored. The boosted-tree engine’s own options are documented under fitrgam . You can parse either a 'formula' or an 'interactions' optional parameter. Parsing both parameters will result an error. Accordingly, you can only pass up to two parameters among 'knots' , 'order' , and 'dof' to define the required polynomial for training the GAM model. Two weak learners are available, selected by FitMethod . 'boostedtrees' , the default, boosts one shallow decision tree per predictor in each round, which is the scheme MATLAB’s generalized additive model uses. A second phase then boosts trees over pairs of predictors, where interactions are asked for. 'splines' boosts a smoothing spline per predictor until the residual sum of squares changes by less than 'Tol' . It has no MATLAB counterpart and is an Octave extension, kept because a smooth additive fit is a genuinely different and often better answer than a staircase of stumps. A standard deviation and a prediction interval are available from it alone. The two take different arguments, and an argument meant for one is refused by the other rather than ignored. The choice is visible in the properties. Knots , Order , DoF , Formula , Tol , BaseModel , ModelwInt and IntMatrix describe a spline fit and are empty under the boosted-tree engine, while ModelParameters , ReasonForTermination , BinEdges , PairDetectionBinEdges and TreeModel describe a tree fit and are empty under the spline engine. Fitted values are not expected to equal MATLAB’s even under 'boostedtrees' . The stopping rule and the step-reduction limit are not recoverable from anything MATLAB reports, so this engine documents its own; what the two share is the estimator and the reported surface, not the arithmetic. See also: fitrgam, regress, regress_gp # name: # type: sq_string # elements: 1 # length: 97 Create a RegressionGAM class object containing a Generalized Additive Model (GAM) for regression. # name: # type: sq_string # elements: 1 # length: 23 RegressionGAM.BaseModel # name: # type: sq_string # elements: 1 # length: 301 RegressionGAM: property BaseModel Model without interaction terms A structure holding the intercept, the piecewise polynomial of each predictor, the number of backfitting cycles, the residuals and the residual sum of squares of the model fitted without interaction terms. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Model without interaction terms # name: # type: sq_string # elements: 1 # length: 22 RegressionGAM.BinEdges # name: # type: sq_string # elements: 1 # length: 471 RegressionGAM: property BinEdges Bin edges of the predictors A cell array with one entry per predictor, holding that predictor’s bin edges where the model discretized it before fitting. It is empty here and stays empty: this generalized additive model is built from splines, which take the predictors as they are, where MATLAB’s is built from boosted trees and bins them. That difference is described in the class documentation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Bin edges of the predictors # name: # type: sq_string # elements: 1 # length: 35 RegressionGAM.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 215 RegressionGAM: property CategoricalPredictors Indices of the categorical predictors A numeric vector holding the column of each predictor treated as categorical, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 17 RegressionGAM.DoF # name: # type: sq_string # elements: 1 # length: 191 RegressionGAM: property DoF Degrees of freedom of the spline fitting A numeric vector with one entry per predictor, the sum of its number of knots and its order. This property is read-only. # name: # type: sq_string # elements: 1 # length: 40 Degrees of freedom of the spline fitting # name: # type: sq_string # elements: 1 # length: 36 RegressionGAM.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 290 RegressionGAM: property ExpandedPredictorNames Names of the expanded predictor variables A cell array of character vectors naming the predictors as the model sees them. It matches PredictorNames unless a categorical predictor was expanded into dummy variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 41 Names of the expanded predictor variables # name: # type: sq_string # elements: 1 # length: 23 RegressionGAM.FitMethod # name: # type: sq_string # elements: 1 # length: 507 RegressionGAM: property FitMethod Which engine fitted the model A character vector, either 'boostedtrees' or 'splines' . The default is 'boostedtrees' , the scheme MATLAB’s generalized additive model uses. 'splines' selects the penalised-spline engine, an Octave extension with no MATLAB counterpart and the scheme this class fitted before version 1.9.0. The two engines take different arguments and an argument meant for one is refused by the other rather than ignored. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Which engine fitted the model # name: # type: sq_string # elements: 1 # length: 21 RegressionGAM.Formula # name: # type: sq_string # elements: 1 # length: 222 RegressionGAM: property Formula Formula of the model A character vector naming the response and the terms of the model, as in 'Y ~ x1 + x2 + x1:x2' , or empty when the model was not given one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Formula of the model # name: # type: sq_string # elements: 1 # length: 47 RegressionGAM.HyperparameterOptimizationResults # name: # type: sq_string # elements: 1 # length: 356 RegressionGAM: property HyperparameterOptimizationResults Results of the hyperparameter optimization Always empty. It is declared for MATLAB compatibility, where it holds what an automatic search over the hyperparameters found. This class fits the parameters it is given and runs no such search, so there is nothing to report. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Results of the hyperparameter optimization # name: # type: sq_string # elements: 1 # length: 23 RegressionGAM.IntMatrix # name: # type: sq_string # elements: 1 # length: 553 RegressionGAM: property IntMatrix Every term the model fits A logical matrix with one row per term and one column per predictor, true wherever the term multiplies that predictor. A row naming one predictor is a main effect, two an interaction, and three or more a higher-order term. This property is read-only. It is the complete record, where Interactions reports only the two-way terms, in the form MATLAB reports them. It is also the form the 'Interactions' option takes back, so passing it to the constructor rebuilds a model over the same terms. # name: # type: sq_string # elements: 1 # length: 25 Every term the model fits # name: # type: sq_string # elements: 1 # length: 26 RegressionGAM.Interactions # name: # type: sq_string # elements: 1 # length: 594 RegressionGAM: property Interactions Two-way interaction terms of the fitted model A Kx2 matrix of predictor index pairs, one row per two-way term the model carries, and zeros (0, 2) when it carries none. It reports what was fitted rather than what was asked for, so a count of terms, 'all' , a logical matrix and a formula all leave the same kind of value behind. This property is read-only. A main effect names one predictor and a higher-order term names three or more, and neither has a two-column form, so neither appears here. IntMatrix remains the complete record of every term fitted. # name: # type: sq_string # elements: 1 # length: 45 Two-way interaction terms of the fitted model # name: # type: sq_string # elements: 1 # length: 23 RegressionGAM.Intercept # name: # type: sq_string # elements: 1 # length: 184 RegressionGAM: property Intercept Intercept of the fitted model A numeric scalar, the mean of the response, which every additive term is measured against. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Intercept of the fitted model # name: # type: sq_string # elements: 1 # length: 36 RegressionGAM.IsStandardDeviationFit # name: # type: sq_string # elements: 1 # length: 279 RegressionGAM: property IsStandardDeviationFit Flag for a fitted standard deviation model A boolean flag, always false , as this class estimates the standard deviation of a prediction from the residuals of the fit rather than fitting a model for it. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Flag for a fitted standard deviation model # name: # type: sq_string # elements: 1 # length: 19 RegressionGAM.Knots # name: # type: sq_string # elements: 1 # length: 200 RegressionGAM: property Knots Knots of the spline fitting A numeric vector with one entry per predictor, the number of breaks the spline of that predictor is fitted over. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Knots of the spline fitting # name: # type: sq_string # elements: 1 # length: 29 RegressionGAM.ModelParameters # name: # type: sq_string # elements: 1 # length: 359 RegressionGAM: property ModelParameters Parameters the model was fitted with A structure holding the fitting parameters. Under the boosted-tree engine it carries MATLAB’s own fields, with Type reading 'regression' ; under the spline engine it describes that scheme instead, since none of the tree vocabulary applies to it. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Parameters the model was fitted with # name: # type: sq_string # elements: 1 # length: 23 RegressionGAM.ModelwInt # name: # type: sq_string # elements: 1 # length: 222 RegressionGAM: property ModelwInt Model with interaction terms A structure of the same fields as BaseModel , for the model fitted with the interaction terms, and empty when none was asked for. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Model with interaction terms # name: # type: sq_string # elements: 1 # length: 29 RegressionGAM.NumObservations # name: # type: sq_string # elements: 1 # length: 220 RegressionGAM: property NumObservations Number of observations A positive integer, the number of observations of the training data the model was fitted on, rows with missing values excluded. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 27 RegressionGAM.NumPredictors # name: # type: sq_string # elements: 1 # length: 155 RegressionGAM: property NumPredictors Number of predictors A positive integer, the number of predictors of the training data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 19 RegressionGAM.Order # name: # type: sq_string # elements: 1 # length: 188 RegressionGAM: property Order Order of the spline fitting A numeric vector with one entry per predictor, the polynomial order of the spline of that predictor. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Order of the spline fitting # name: # type: sq_string # elements: 1 # length: 35 RegressionGAM.PairDetectionBinEdges # name: # type: sq_string # elements: 1 # length: 483 RegressionGAM: property PairDetectionBinEdges Bin edges used to detect interactions A cell array with one row vector per predictor, holding the coarse cut points the residuals of the predictor phase were laid on while pairs were being tested. The grid is eight equal-frequency bins whatever the sample size, as MATLAB’s is. It is empty when the model carries no interaction terms, and empty throughout under the spline engine, which does not bin. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Bin edges used to detect interactions # name: # type: sq_string # elements: 1 # length: 28 RegressionGAM.PredictorNames # name: # type: sq_string # elements: 1 # length: 205 RegressionGAM: property PredictorNames Names of the predictor variables A cell array of character vectors naming the predictors, in the order they appear in the training data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Names of the predictor variables # name: # type: sq_string # elements: 1 # length: 34 RegressionGAM.ReasonForTermination # name: # type: sq_string # elements: 1 # length: 338 RegressionGAM: property ReasonForTermination Why each fitting phase stopped A structure with the fields PredictorTrees and InteractionTrees , each saying why that phase ended. A phase that never ran reports an empty character vector. It is empty under the spline engine, which has no tree budget to exhaust. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 Why each fitting phase stopped # name: # type: sq_string # elements: 1 # length: 27 RegressionGAM.RegressionGAM # name: # type: sq_string # elements: 1 # length: 542 RegressionGAM: obj = RegressionGAM ( X , Y ) RegressionGAM: obj = RegressionGAM (…, name , value ) Fit a generalized additive model for regression. X is an N×P numeric matrix of predictor data, one observation per row, and Y is the continuous response of those N observations. The fit runs at construction, so obj arrives fitted. The name / value pairs the fit accepts, and the validation each one is held to, are listed in help RegressionGAM . fitrgam is the documented way to reach this constructor and takes the same pairs. # name: # type: sq_string # elements: 1 # length: 48 Fit a generalized additive model for regression. # name: # type: sq_string # elements: 1 # length: 26 RegressionGAM.ResponseName # name: # type: sq_string # elements: 1 # length: 141 RegressionGAM: property ResponseName Response variable name A character vector naming the response variable Y . This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 31 RegressionGAM.ResponseTransform # name: # type: sq_string # elements: 1 # length: 313 RegressionGAM: property ResponseTransform Transformation applied to the predicted response A function handle applied to the response the model predicts. Add or change it using dot notation, as in obj .ResponseTransform = 'log' or obj .ResponseTransform = @function_handle . It defaults to 'none' , the identity. # name: # type: sq_string # elements: 1 # length: 48 Transformation applied to the predicted response # name: # type: sq_string # elements: 1 # length: 22 RegressionGAM.RowsUsed # name: # type: sq_string # elements: 1 # length: 378 RegressionGAM: property RowsUsed Rows used for fitting A logical column vector with the same length as the observations in the original predictor data X , true for each row that was used for fitting the RegressionGAM model. It is empty, [] , when every observation was used, so a non-empty value means that rows holding missing values were dropped. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Rows used for fitting # name: # type: sq_string # elements: 1 # length: 17 RegressionGAM.Tol # name: # type: sq_string # elements: 1 # length: 201 RegressionGAM: property Tol Tolerance for convergence A positive scalar, the largest change in the residual sum of squares of a backfitting cycle that counts as converged. This property is read-only. # name: # type: sq_string # elements: 1 # length: 25 Tolerance for convergence # name: # type: sq_string # elements: 1 # length: 23 RegressionGAM.TreeModel # name: # type: sq_string # elements: 1 # length: 420 RegressionGAM: property TreeModel The fitted shape functions and interaction surfaces A structure with fields ShapeValues , PairValues and Pairs , holding what the boosted-tree engine fitted. MATLAB exposes no equivalent, reporting its bin edges but never the values on them, so this is an Octave extension. It is empty under the spline engine, whose fit lives in BaseModel and ModelwInt . This property is read-only. # name: # type: sq_string # elements: 1 # length: 51 The fitted shape functions and interaction surfaces # name: # type: sq_string # elements: 1 # length: 15 RegressionGAM.W # name: # type: sq_string # elements: 1 # length: 175 RegressionGAM: property W Observation weights A numeric column vector with one entry per observation used for training, normalised to sum to one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 15 RegressionGAM.X # name: # type: sq_string # elements: 1 # length: 167 RegressionGAM: property X Predictor data A numeric matrix with one row per observation and one column per predictor of the training data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Predictor data # name: # type: sq_string # elements: 1 # length: 15 RegressionGAM.Y # name: # type: sq_string # elements: 1 # length: 146 RegressionGAM: property Y Response data A numeric column vector with one entry per observation of the training data. This property is read-only. # name: # type: sq_string # elements: 1 # length: 13 Response data # name: # type: sq_string # elements: 1 # length: 29 RegressionGAM.addInteractions # name: # type: sq_string # elements: 1 # length: 1079 RegressionGAM: obj = addInteractions ( obj , interactions ) Add interaction terms to a fitted model. obj = addInteractions ( obj , interactions ) fits the interaction terms named by interactions on top of the terms the model already carries and returns the updated model. The univariate fit is left alone, so predict with 'IncludeInteractions' set false answers exactly as it answered before. interactions takes the forms the constructor’s 'Interactions' option takes: a nonnegative integer count of terms, a logical matrix with a column per predictor, or 'all' . A model already carrying interaction terms is not extended, which is what MATLAB refuses too. A model fitted from a 'Formula' names every term it has, interactions among them, and is refused for the same reason. Which terms a count selects is this implementation’s own: they are taken in the order nchoosek lists the pairs, where MATLAB ranks them by how much each contributes. The constructor’s option chooses the same way, so the two agree with each other. See also: fitrgam, RegressionGAM # name: # type: sq_string # elements: 1 # length: 40 Add interaction terms to a fitted model. # name: # type: sq_string # elements: 1 # length: 21 RegressionGAM.compact # name: # type: sq_string # elements: 1 # length: 250 RegressionGAM: CMdl = compact ( obj ) Create a CompactRegressionGAM object. CMdl = compact ( obj ) returns a compact version of the model, which predicts as it does but keeps no training data. See also: RegressionGAM, CompactRegressionGAM, fitrgam # name: # type: sq_string # elements: 1 # length: 37 Create a CompactRegressionGAM object. # name: # type: sq_string # elements: 1 # length: 22 RegressionGAM.crossval # name: # type: sq_string # elements: 1 # length: 1041 RegressionGAM: CVMdl = crossval ( obj ) RegressionGAM: CVMdl = crossval (…, name , value ) Cross validate a Generalized Additive Model regression object. CVMdl = crossval ( obj ) returns a cross-validated model object, CVMdl , from a trained model, obj , using 10-fold cross-validation by default. CVMdl = crossval ( obj , name , value ) specifies additional name-value pair arguments to customize the cross-validation process. Name Value 'KFold' Specify the number of folds to use in k-fold cross-validation. "KFold", k , where k is an integer greater than 1. 'Holdout' Specify the fraction of the data to hold out for testing. "Holdout", p , where p is a scalar in the range (0,1) . 'Leaveout' Specify whether to perform leave-one-out cross-validation. "Leaveout", Value , where Value is ’on’ or ’off’. 'CVPartition' Specify a cvpartition object used for cross-validation. "CVPartition", cv , where isa ( cv , "cvpartition") = 1. See also: fitrgam, RegressionGAM, cvpartition, RegressionPartitionedModel # name: # type: sq_string # elements: 1 # length: 62 Cross validate a Generalized Additive Model regression object. # name: # type: sq_string # elements: 1 # length: 18 RegressionGAM.loss # name: # type: sq_string # elements: 1 # length: 650 RegressionGAM: L = loss ( obj , X , Y ) RegressionGAM: L = loss (…, name , value ) Regression loss of a generalized additive model. L = loss ( obj , X , Y ) returns the weighted mean squared error of the model on the rows of X against the true response Y . L = loss (…, name , value ) accepts the following name-value pairs: "LossFun" selects the loss, either "mse" , the default, or a function handle taking the true response, the predicted response and the weights, and returning a numeric scalar. "Weights" holds one weight per row of X , normalised to sum to one before it is applied. See also: RegressionGAM, fitrgam, predict # name: # type: sq_string # elements: 1 # length: 48 Regression loss of a generalized additive model. # name: # type: sq_string # elements: 1 # length: 21 RegressionGAM.predict # name: # type: sq_string # elements: 1 # length: 1690 RegressionGAM: yFit = predict ( obj , Xfit ) RegressionGAM: yFit = predict (…, Name , Value ) RegressionGAM: [ yFit , ySD , yInt ] = predict (…) Predict new data points using generalized additive model regression object. yFit = predict ( obj , Xfit returns a vector of predicted responses, yFit , for the predictor data in matrix Xfit based on the Generalized Additive Model in obj . Xfit must have the same number of features/variables as the training data in obj . obj must be a RegressionGAM class object. [ yFit , ySD , yInt ] = predict ( obj , Xfit also returns the standard deviations, ySD , and prediction intervals, yInt , of the response variable yFit , evaluated at each observation in the predictor data Xfit . yFit = predict (…, Name , Value ) returns the aforementioned results with additional properties specified by Name-Value pair arguments listed below. Name Value 'alpha' significance level of the prediction intervals yInt , specified as scalar in range [0,1] . The default value is 0.05, which corresponds to 95% prediction intervals. 'includeinteractions' a boolean flag to include interactions to predict new values based on Xfit . By default, 'includeinteractions' is true when the GAM model in obj contains a obj.Formula or obj.Interactions fields. Otherwise, is set to false . If set to true when no interactions are present in the trained model, it will result to an error. If set to false when using a model that includes interactions, the predictions will be made on the basic model without any interaction terms. This way you can make predictions from the same GAM model without having to retrain it. See also: fitrgam, RegressionGAM # name: # type: sq_string # elements: 1 # length: 75 Predict new data points using generalized additive model regression object. # name: # type: sq_string # elements: 1 # length: 23 RegressionGAM.resubLoss # name: # type: sq_string # elements: 1 # length: 355 RegressionGAM: L = resubLoss ( obj ) RegressionGAM: L = resubLoss (…, name , value ) Regression loss of a generalized additive model on its training data. L = resubLoss ( obj ) returns the weighted mean squared error of the model on the data it was fitted on. It accepts the same Name-Value pairs as loss . See also: RegressionGAM, fitrgam, loss # name: # type: sq_string # elements: 1 # length: 69 Regression loss of a generalized additive model on its training data. # name: # type: sq_string # elements: 1 # length: 26 RegressionGAM.resubPredict # name: # type: sq_string # elements: 1 # length: 242 RegressionGAM: yFit = resubPredict ( obj ) Predict the training response with the model it was fitted on. yFit = resubPredict ( obj ) is predict applied to the observations the model was fitted on. See also: RegressionGAM, fitrgam, predict # name: # type: sq_string # elements: 1 # length: 62 Predict the training response with the model it was fitted on. # name: # type: sq_string # elements: 1 # length: 20 RegressionGAM.resume # name: # type: sq_string # elements: 1 # length: 931 RegressionGAM: Mdl = resume ( obj , numTrees ) Resume training a generalized additive model. Mdl = resume ( obj , numTrees ) adds numTrees more trees to obj and returns the result. The original model is not modified. Training continues in the phase that ran last, which is what MATLAB does: a model carrying interaction terms gains interaction trees and its predictor shape functions are left alone, while a model without them gains predictor trees. A round starts at its initial learning rate whatever its number, so the model this returns is the model a single fit of the combined budget would have produced. numTrees must be a positive integer scalar. Resuming raises where there is nothing left to gain, rather than returning the model unchanged, and it is not available under 'FitMethod', 'splines' : a backfit that has converged to its tolerance has no budget to extend. See also: RegressionGAM, fitrgam, addInteractions # name: # type: sq_string # elements: 1 # length: 45 Resume training a generalized additive model. # name: # type: sq_string # elements: 1 # length: 23 RegressionGAM.savemodel # name: # type: sq_string # elements: 1 # length: 211 RegressionGAM: savemodel ( obj , filename ) Save a RegressionGAM object. savemodel ( obj , filename ) saves a RegressionGAM object into a file defined by filename . See also: loadmodel, fitrgam, RegressionGAM # name: # type: sq_string # elements: 1 # length: 28 Save a RegressionGAM object. # name: # type: sq_string # elements: 1 # length: 12 RegressionGP # name: # type: sq_string # elements: 1 # length: 5099 statistics: obj = RegressionGP ( X , Y ) statistics: obj = RegressionGP (…, name , value ) Create a RegressionGP object containing a Gaussian process regression model. obj = RegressionGP ( X , Y ) returns a Gaussian process regression model, obj , with X being the predictor data and Y the continuous response of the observations in X . X must be an NxP numeric matrix of predictor data, where rows correspond to observations and columns to features. Y must be an Nx1 numeric vector holding the response of the corresponding predictor data in X . Y must have the same number of rows as X . A Gaussian process places a prior over functions, given by the covariance function, and conditions it on the observations. The response is modelled as H×Beta plus a draw from that process plus independent noise of standard deviation Sigma , where H is the explicit basis. The covariance parameters and Sigma are estimated by maximizing the log marginal likelihood, and Beta follows from them in closed form as the generalized least squares estimate. obj = RegressionGP (…, name , value ) returns a model with additional options specified by Name-Value pair arguments listed below. Name Value 'KernelFunction' A character vector naming the covariance function, or a function handle taking two matrices of points and a parameter vector. The default is 'squaredexponential' . The supported names are listed below. 'KernelParameters' A numeric vector of initial values for the covariance parameters. Its length depends on the covariance function. These are starting values for the optimization, not fixed values. 'BasisFunction' A character vector naming the explicit basis, one of 'none' , 'constant' , 'linear' or 'pureQuadratic' , or a function handle taking X and returning the basis matrix. The default is 'constant' . 'Beta' A numeric vector of basis coefficients. These are used as known values only when 'FitMethod' is 'none' . 'Sigma' A positive scalar, the initial value of the noise standard deviation. The default is std ( Y ) / sqrt (2) . 'ConstantSigma' A logical scalar. When true the noise standard deviation is held at its initial value instead of being estimated. The default is false . 'SigmaLowerBound' A positive scalar bounding the noise standard deviation from below. The default is 1e-2 * std ( Y ) . 'FitMethod' A character vector, either 'exact' to estimate the parameters or 'none' to keep them at their initial values. The default is 'exact' . 'PredictMethod' A character vector. Only 'exact' is implemented, which is also the only method under which a standard deviation and a prediction interval are available. 'Optimizer' A character vector naming the optimizer used to maximize the log marginal likelihood. 'quasinewton' and 'fminunc' name the same dense solver and are the default, 'lbfgs' selects limited-memory BFGS, which holds a fixed number of curvature pairs rather than a full inverse Hessian and is the cheaper choice when the kernel carries many parameters, and 'fminsearch' is derivative-free. 'Standardize' A logical scalar specifying whether the predictor data should be centred and scaled before training. The same transformation is applied by predict . The default is false . 'Weights' An Nx1 numeric vector of non-negative observation weights. The default is a vector of ones. 'PredictorNames' A cell array of character vectors naming the predictors, in the order they appear in X . 'ResponseName' A character vector naming the response. The default is 'Y' . 'ResponseTransform' A character vector or a function handle applied to the response the model predicts. The default is 'none' . The supported values for 'KernelFunction' are: Value Parameters 'exponential' [SigmaL; SigmaF] 'squaredexponential' [SigmaL; SigmaF] 'matern32' [SigmaL; SigmaF] 'matern52' [SigmaL; SigmaF] 'rationalquadratic' [SigmaL; AlphaRQ; SigmaF] 'ardexponential' [LengthScale1; …; SigmaF] 'ardsquaredexponential' [LengthScale1; …; SigmaF] 'ardmatern32' [LengthScale1; …; SigmaF] 'ardmatern52' [LengthScale1; …; SigmaF] 'ardrationalquadratic' [LengthScale1; …; AlphaRQ; SigmaF] The automatic relevance determination kernels carry one length scale per predictor, so a predictor the response does not depend on is given a large length scale and stops contributing. The supported values for 'ResponseTransform' are: Value Description 'none' x (no transformation) 'identity' x (no transformation) 'exp' exp (x) 'log' log (x) Two deviations from MATLAB are deliberate and documented. The distance between points is accumulated one predictor at a time instead of by the expanded form MATLAB uses by default, because the expanded form does not return exactly zero for a point against itself and the rough kernels amplify that residue through their square root. The approximate fitting and prediction methods, 'sd' , 'sr' , 'fic' and 'bcd' , together with the active set options that serve them, are not implemented and are refused rather than silently ignored. See also: fitrgp, CompactRegressionGP, RegressionSVM, RegressionGAM # name: # type: sq_string # elements: 1 # length: 76 Create a RegressionGP object containing a Gaussian process regression model. # name: # type: sq_string # elements: 1 # length: 29 RegressionGP.ActiveSetHistory # name: # type: sq_string # elements: 1 # length: 393 RegressionGP: property ActiveSetHistory History of the active set selection Always empty. It is declared for MATLAB compatibility, where it records the active set chosen at each iteration by a fit method that builds one. This class implements the exact method alone, which uses the whole of the training data and selects nothing, so there is no history to record. This property is read-only. # name: # type: sq_string # elements: 1 # length: 35 History of the active set selection # name: # type: sq_string # elements: 1 # length: 28 RegressionGP.ActiveSetMethod # name: # type: sq_string # elements: 1 # length: 116 RegressionGP: property ActiveSetMethod Method used to select the active set 'Random' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Method used to select the active set # name: # type: sq_string # elements: 1 # length: 26 RegressionGP.ActiveSetSize # name: # type: sq_string # elements: 1 # length: 116 RegressionGP: property ActiveSetSize Size of the active set A positive integer scalar. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Size of the active set # name: # type: sq_string # elements: 1 # length: 29 RegressionGP.ActiveSetVectors # name: # type: sq_string # elements: 1 # length: 281 RegressionGP: property ActiveSetVectors Subset of the training data used for predictions An MxP numeric matrix, standardized where the model standardized its predictors. It is the whole of the training data, since only the exact method is implemented. This property is read-only. # name: # type: sq_string # elements: 1 # length: 48 Subset of the training data used for predictions # name: # type: sq_string # elements: 1 # length: 18 RegressionGP.Alpha # name: # type: sq_string # elements: 1 # length: 231 RegressionGP: property Alpha Weights the predictions are made from An Nx1 numeric vector. A prediction is the basis term plus the covariance between the new point and the active set, weighted by these. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Weights the predictions are made from # name: # type: sq_string # elements: 1 # length: 27 RegressionGP.BCDInformation # name: # type: sq_string # elements: 1 # length: 272 RegressionGP: property BCDInformation Block coordinate descent information Always empty. It is declared for MATLAB compatibility, where it records a block coordinate descent. This class does not use that method, so there is nothing to report. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Block coordinate descent information # name: # type: sq_string # elements: 1 # length: 26 RegressionGP.BasisFunction # name: # type: sq_string # elements: 1 # length: 187 RegressionGP: property BasisFunction Explicit basis of the model 'None' , 'Constant' , 'Linear' , 'PureQuadratic' , or the function handle that was supplied. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Explicit basis of the model # name: # type: sq_string # elements: 1 # length: 17 RegressionGP.Beta # name: # type: sq_string # elements: 1 # length: 185 RegressionGP: property Beta Estimated coefficients of the explicit basis A numeric vector with one element per basis term, empty when the basis is 'None' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Estimated coefficients of the explicit basis # name: # type: sq_string # elements: 1 # length: 21 RegressionGP.BinEdges # name: # type: sq_string # elements: 1 # length: 308 RegressionGP: property BinEdges Bin edges of the predictors A cell array with one entry per predictor, holding that predictor’s bin edges where the model discretized it before fitting. It is empty here and stays empty: a Gaussian process takes its predictors as they are. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Bin edges of the predictors # name: # type: sq_string # elements: 1 # length: 34 RegressionGP.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 227 RegressionGP: property CategoricalPredictors Indices of the categorical predictors A vector of positive integers indexing the columns of X that hold categorical predictors, or empty when none does. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 35 RegressionGP.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 254 RegressionGP: property ExpandedPredictorNames Expanded predictor variable names A cell array of character vectors. It differs from PredictorNames only where a categorical predictor has been expanded into indicator variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Expanded predictor variable names # name: # type: sq_string # elements: 1 # length: 22 RegressionGP.FitMethod # name: # type: sq_string # elements: 1 # length: 268 RegressionGP: property FitMethod Method used to estimate the parameters 'Exact' when the covariance parameters and the noise were estimated by maximizing the log marginal likelihood, and 'None' when they were kept at their initial values. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Method used to estimate the parameters # name: # type: sq_string # elements: 1 # length: 46 RegressionGP.HyperparameterOptimizationResults # name: # type: sq_string # elements: 1 # length: 355 RegressionGP: property HyperparameterOptimizationResults Results of the hyperparameter optimization Always empty. It is declared for MATLAB compatibility, where it holds what an automatic search over the hyperparameters found. This class fits the parameters it is given and runs no such search, so there is nothing to report. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Results of the hyperparameter optimization # name: # type: sq_string # elements: 1 # length: 30 RegressionGP.IsActiveSetVector # name: # type: sq_string # elements: 1 # length: 171 RegressionGP: property IsActiveSetVector Which observations are in the active set A logical vector with one element per training observation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 40 Which observations are in the active set # name: # type: sq_string # elements: 1 # length: 27 RegressionGP.KernelFunction # name: # type: sq_string # elements: 1 # length: 192 RegressionGP: property KernelFunction Form of the covariance function A character vector naming the covariance function, or the function handle that was supplied. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Form of the covariance function # name: # type: sq_string # elements: 1 # length: 30 RegressionGP.KernelInformation # name: # type: sq_string # elements: 1 # length: 245 RegressionGP: property KernelInformation Covariance function and its parameters A structure with fields Name , KernelParameters and KernelParameterNames , the last naming each parameter in the order they are stored. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Covariance function and its parameters # name: # type: sq_string # elements: 1 # length: 26 RegressionGP.LogLikelihood # name: # type: sq_string # elements: 1 # length: 171 RegressionGP: property LogLikelihood Maximized log marginal likelihood A scalar, or empty when FitMethod is 'None' and nothing was maximized. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Maximized log marginal likelihood # name: # type: sq_string # elements: 1 # length: 28 RegressionGP.ModelParameters # name: # type: sq_string # elements: 1 # length: 1121 RegressionGP: property ModelParameters Parameters used to train the model A structure holding the options the fit was performed under. MATLAB returns an object of its own class here; a structure carries the same information and is what every other learner in this package returns. Beta , Sigma and KernelParameters are the starting values the fit was given, empty or zero where it was given none, as they are in MATLAB. What the fit found is reported by the Beta and Sigma properties and by KernelInformation . Beta defaults to a zero for every column the basis contributes, so a 'linear' basis over three predictors starts at four zeros. SigmaLowerBound is the exception and is reported as it was resolved. MATLAB publishes no top-level field of that name, keeping it inside an Options structure this class does not carry. The fields MATLAB reports for its approximate fitting methods ( ActiveSet , Options , OptimizerOptions , ConstantKernelParameters , InitialStepSize , InitialSigmaLowerBoundTolerance , Verbose and CacheSize ) are absent, this class implementing exact fitting alone. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Parameters used to train the model # name: # type: sq_string # elements: 1 # length: 28 RegressionGP.NumObservations # name: # type: sq_string # elements: 1 # length: 210 RegressionGP: property NumObservations Number of observations used to train the model A positive integer scalar, counting only the rows that survived the removal of missing values. This property is read-only. # name: # type: sq_string # elements: 1 # length: 46 Number of observations used to train the model # name: # type: sq_string # elements: 1 # length: 26 RegressionGP.PredictMethod # name: # type: sq_string # elements: 1 # length: 108 RegressionGP: property PredictMethod Method used to make predictions 'Exact' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Method used to make predictions # name: # type: sq_string # elements: 1 # length: 30 RegressionGP.PredictorLocation # name: # type: sq_string # elements: 1 # length: 199 RegressionGP: property PredictorLocation Means the predictors were centred by A 1xP numeric vector when the model standardized its predictors, and empty when it did not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Means the predictors were centred by # name: # type: sq_string # elements: 1 # length: 27 RegressionGP.PredictorNames # name: # type: sq_string # elements: 1 # length: 149 RegressionGP: property PredictorNames Predictor variable names A cell array of character vectors, one per column of X . This property is read-only. # name: # type: sq_string # elements: 1 # length: 24 Predictor variable names # name: # type: sq_string # elements: 1 # length: 27 RegressionGP.PredictorScale # name: # type: sq_string # elements: 1 # length: 209 RegressionGP: property PredictorScale Standard deviations the predictors were scaled by A 1xP numeric vector when the model standardized its predictors, and empty when it did not. This property is read-only. # name: # type: sq_string # elements: 1 # length: 49 Standard deviations the predictors were scaled by # name: # type: sq_string # elements: 1 # length: 25 RegressionGP.RegressionGP # name: # type: sq_string # elements: 1 # length: 528 RegressionGP: obj = RegressionGP ( X , Y ) RegressionGP: obj = RegressionGP (…, name , value ) Fit a Gaussian process regression model. X is an N×P numeric matrix of predictor data, one observation per row, and Y is the continuous response of those N observations. The fit runs at construction, so obj arrives fitted. The name / value pairs the fit accepts, and the validation each one is held to, are listed in help RegressionGP . fitrgp is the documented way to reach this constructor and takes the same pairs. # name: # type: sq_string # elements: 1 # length: 40 Fit a Gaussian process regression model. # name: # type: sq_string # elements: 1 # length: 25 RegressionGP.ResponseName # name: # type: sq_string # elements: 1 # length: 108 RegressionGP: property ResponseName Response variable name A character vector. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Response variable name # name: # type: sq_string # elements: 1 # length: 30 RegressionGP.ResponseTransform # name: # type: sq_string # elements: 1 # length: 197 RegressionGP: property ResponseTransform Transformation applied to the predicted response A character vector, or the text of the function handle that was supplied. Assigning to it accepts either. # name: # type: sq_string # elements: 1 # length: 48 Transformation applied to the predicted response # name: # type: sq_string # elements: 1 # length: 21 RegressionGP.RowsUsed # name: # type: sq_string # elements: 1 # length: 244 RegressionGP: property RowsUsed Rows of the original data used to train the model A logical vector with one element per row of the data as supplied, true where the row was used. It is empty when no row was dropped. This property is read-only. # name: # type: sq_string # elements: 1 # length: 49 Rows of the original data used to train the model # name: # type: sq_string # elements: 1 # length: 18 RegressionGP.Sigma # name: # type: sq_string # elements: 1 # length: 112 RegressionGP: property Sigma Estimated noise standard deviation A positive scalar. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Estimated noise standard deviation # name: # type: sq_string # elements: 1 # length: 14 RegressionGP.W # name: # type: sq_string # elements: 1 # length: 149 RegressionGP: property W Observation weights An Nx1 numeric vector, one weight per observation used to train the model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 14 RegressionGP.X # name: # type: sq_string # elements: 1 # length: 131 RegressionGP: property X Predictor data An NxP numeric matrix, as it was supplied to the constructor. This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Predictor data # name: # type: sq_string # elements: 1 # length: 14 RegressionGP.Y # name: # type: sq_string # elements: 1 # length: 130 RegressionGP: property Y Response data An Nx1 numeric vector, as it was supplied to the constructor. This property is read-only. # name: # type: sq_string # elements: 1 # length: 13 Response data # name: # type: sq_string # elements: 1 # length: 20 RegressionGP.compact # name: # type: sq_string # elements: 1 # length: 282 RegressionGP: CMdl = compact ( obj ) Return a compact Gaussian process regression model. CMdl = compact ( obj ) returns a CompactRegressionGP object holding what is needed to predict and nothing else: the training data, the response and everything that describes them are dropped. # name: # type: sq_string # elements: 1 # length: 51 Return a compact Gaussian process regression model. # name: # type: sq_string # elements: 1 # length: 21 RegressionGP.crossval # name: # type: sq_string # elements: 1 # length: 395 RegressionGP: CVMdl = crossval ( obj ) RegressionGP: CVMdl = crossval (…, name , value ) Cross validate a Gaussian process model. CVMdl = crossval ( obj ) returns a RegressionPartitionedModel built from the model obj by ten-fold cross validation. CVMdl = crossval (…, name , value ) accepts 'KFold' , 'Holdout' , 'Leaveout' and 'CVPartition' , of which at most one may be given. # name: # type: sq_string # elements: 1 # length: 40 Cross validate a Gaussian process model. # name: # type: sq_string # elements: 1 # length: 17 RegressionGP.loss # name: # type: sq_string # elements: 1 # length: 472 RegressionGP: L = loss ( obj , X , Y ) RegressionGP: L = loss (…, name , value ) Compute the regression loss of a Gaussian process model. L = loss ( obj , X , Y ) returns the mean squared error of the model obj on the data X and Y . L = loss (…, name , value ) accepts 'LossFun' , either 'mse' , 'mae' , 'epsiloninsensitive' or a function handle taking the observed and the predicted response, and 'Weights' , a vector of non-negative observation weights. # name: # type: sq_string # elements: 1 # length: 56 Compute the regression loss of a Gaussian process model. # name: # type: sq_string # elements: 1 # length: 30 RegressionGP.postFitStatistics # name: # type: sq_string # elements: 1 # length: 571 RegressionGP: [ loores , neff ] = postFitStatistics ( obj ) Compute the leave-one-out residuals of a Gaussian process model. [ loores , neff ] = postFitStatistics ( obj ) returns the Nx1 vector of leave-one-out residuals of the model obj , and the number of effective parameters the fit uses. Neither requires refitting the model: both follow from the factorization the fit already produced. The coefficients of the explicit basis are treated as estimated, which is what FitMethod 'Exact' makes them, while the covariance parameters and the noise are treated as known. # name: # type: sq_string # elements: 1 # length: 64 Compute the leave-one-out residuals of a Gaussian process model. # name: # type: sq_string # elements: 1 # length: 20 RegressionGP.predict # name: # type: sq_string # elements: 1 # length: 954 RegressionGP: yFit = predict ( obj , XC ) RegressionGP: [ yFit , ySD , yInt ] = predict ( obj , XC ) RegressionGP: […] = predict (…, 'Alpha' , alpha ) Predict the response for new data with a Gaussian process model. yFit = predict ( obj , XC ) returns the predicted response of the RegressionGP model obj at the points in XC , which must have as many columns as the model has predictors. [ yFit , ySD , yInt ] = predict (…) also returns the standard deviation of each predicted response and the prediction intervals. The standard deviation is that of a new response , so it carries the noise as well as the uncertainty of the latent function, and the interval is the normal quantile of the level times it. […] = predict (…, 'Alpha' , alpha ) sets the significance level of the intervals, so that they are 100 × (1 - alpha ) per cent intervals. alpha must be a scalar in the range [0, 1] and defaults to 0.05 . # name: # type: sq_string # elements: 1 # length: 64 Predict the response for new data with a Gaussian process model. # name: # type: sq_string # elements: 1 # length: 22 RegressionGP.resubLoss # name: # type: sq_string # elements: 1 # length: 287 RegressionGP: L = resubLoss ( obj ) RegressionGP: L = resubLoss (…, name , value ) Compute the resubstitution loss of a Gaussian process model. L = resubLoss ( obj ) returns the loss of the model obj on the data it was trained on, and accepts the same Name-Value pairs as loss . # name: # type: sq_string # elements: 1 # length: 60 Compute the resubstitution loss of a Gaussian process model. # name: # type: sq_string # elements: 1 # length: 25 RegressionGP.resubPredict # name: # type: sq_string # elements: 1 # length: 333 RegressionGP: yFit = resubPredict ( obj ) RegressionGP: [ yFit , ySD , yInt ] = resubPredict ( obj ) Predict the response of the training data with a Gaussian process model. yFit = resubPredict ( obj ) returns the response the RegressionGP model obj predicts at its own training data, and the further outputs are those of predict . # name: # type: sq_string # elements: 1 # length: 72 Predict the response of the training data with a Gaussian process model. # name: # type: sq_string # elements: 1 # length: 22 RegressionGP.savemodel # name: # type: sq_string # elements: 1 # length: 183 RegressionGP: savemodel ( obj , filename ) Save a Gaussian process model to a file. savemodel ( obj , filename ) saves the model obj into filename in a form loadmodel can read back. # name: # type: sq_string # elements: 1 # length: 40 Save a Gaussian process model to a file. # name: # type: sq_string # elements: 1 # length: 16 RegressionKernel # name: # type: sq_string # elements: 1 # length: 1288 statistics: RegressionKernel Gaussian kernel regression model for large data. A RegressionKernel object maps the predictors into a randomized feature space whose inner product approximates a Gaussian kernel, and then fits a linear model there. A kernel regression is therefore as nonlinear as a support vector machine with a Gaussian kernel, while costing what a linear fit costs: nothing of size NxN is ever formed. The expansion is the random Fourier basis of Rahimi and Recht, drawn once when the model is fitted and kept with it, so predict maps new data through the same basis. MATLAB approximates the same kernel by the Fastfood construction, which reaches the same distribution more cheaply; the two are interchangeable in distribution but not draw by draw, and the draws come from different generators in any case, so the predictions of a model fitted here and one fitted in MATLAB differ even from the same seed. What does not differ is what they estimate. Like RegressionLinear the object holds no copy of the training data. It does hold the basis and the coefficients, so it is bounded by the number of expansion dimensions rather than by the number of observations. Create a RegressionKernel object with fitrkernel . See also: fitrkernel, RegressionLinear, RegressionSVM # name: # type: sq_string # elements: 1 # length: 48 Gaussian kernel regression model for large data. # name: # type: sq_string # elements: 1 # length: 30 RegressionKernel.BoxConstraint # name: # type: sq_string # elements: 1 # length: 306 RegressionKernel: property BoxConstraint Box constraint of the support vector machine A positive scalar. It is the reciprocal of the product of Lambda and the number of observations, so setting either of the two in the constructor fixes the other, and giving both is an error. This property is read-only. # name: # type: sq_string # elements: 1 # length: 44 Box constraint of the support vector machine # name: # type: sq_string # elements: 1 # length: 38 RegressionKernel.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 187 RegressionKernel: property CategoricalPredictors Indices of the categorical predictors A row vector of column indices, empty when every predictor is numeric. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 24 RegressionKernel.Epsilon # name: # type: sq_string # elements: 1 # length: 362 RegressionKernel: property Epsilon Half the width of the epsilon-insensitive band A nonnegative scalar for a support vector machine, and empty for a least squares fit, which has no such band. It defaults to the interquartile range of the response over 13.49, an estimate of its standard deviation, or to 0.1 when that range is zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 46 Half the width of the epsilon-insensitive band # name: # type: sq_string # elements: 1 # length: 39 RegressionKernel.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 245 RegressionKernel: property ExpandedPredictorNames Names of the predictors as the fit saw them A cell array of character vectors. These name the original predictors, not the expansion dimensions, which have no names. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Names of the predictors as the fit saw them # name: # type: sq_string # elements: 1 # length: 27 RegressionKernel.FittedLoss # name: # type: sq_string # elements: 1 # length: 184 RegressionKernel: property FittedLoss Loss function the fit minimized 'epsiloninsensitive' for a support vector machine and 'mse' for a least squares fit. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Loss function the fit minimized # name: # type: sq_string # elements: 1 # length: 28 RegressionKernel.KernelScale # name: # type: sq_string # elements: 1 # length: 225 RegressionKernel: property KernelScale Scale of the Gaussian kernel A positive scalar dividing every predictor before the expansion, so a larger scale makes the kernel wider and the fit smoother. This property is read-only. # name: # type: sq_string # elements: 1 # length: 28 Scale of the Gaussian kernel # name: # type: sq_string # elements: 1 # length: 23 RegressionKernel.Lambda # name: # type: sq_string # elements: 1 # length: 188 RegressionKernel: property Lambda Regularization strength A nonnegative scalar, the reciprocal of the product of BoxConstraint and the number of observations. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Regularization strength # name: # type: sq_string # elements: 1 # length: 24 RegressionKernel.Learner # name: # type: sq_string # elements: 1 # length: 139 RegressionKernel: property Learner Linear model fitted in the expanded space Either 'svm' or 'leastsquares' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 41 Linear model fitted in the expanded space # name: # type: sq_string # elements: 1 # length: 32 RegressionKernel.ModelParameters # name: # type: sq_string # elements: 1 # length: 233 RegressionKernel: property ModelParameters Fitting options, as they were given A structure holding every parameter of the fit, with the 'auto' values as they were given rather than as they were resolved. This property is read-only. # name: # type: sq_string # elements: 1 # length: 35 Fitting options, as they were given # name: # type: sq_string # elements: 1 # length: 19 RegressionKernel.Mu # name: # type: sq_string # elements: 1 # length: 198 RegressionKernel: property Mu Predictor means used to standardize A row vector with one element per predictor, or empty when the model was fitted without standardizing. This property is read-only. # name: # type: sq_string # elements: 1 # length: 35 Predictor means used to standardize # name: # type: sq_string # elements: 1 # length: 39 RegressionKernel.NumExpansionDimensions # name: # type: sq_string # elements: 1 # length: 341 RegressionKernel: property NumExpansionDimensions Number of dimensions of the expanded space A positive integer scalar. It defaults to 2 .^ ceil (min (log2 ( p ) + 5, 15)) for p predictors, so four predictors give 128 dimensions. More dimensions approximate the kernel more closely and cost proportionally more. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Number of dimensions of the expanded space # name: # type: sq_string # elements: 1 # length: 31 RegressionKernel.PredictorNames # name: # type: sq_string # elements: 1 # length: 213 RegressionKernel: property PredictorNames Names of the predictors A cell array of character vectors with one name per column of the training data, defaulting to 'x1' , 'x2' and so on. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Names of the predictors # name: # type: sq_string # elements: 1 # length: 33 RegressionKernel.RegressionKernel # name: # type: sq_string # elements: 1 # length: 2022 RegressionKernel: obj = RegressionKernel ( X , Y ) RegressionKernel: obj = RegressionKernel (…, name , value ) Fit a Gaussian kernel regression model. obj = RegressionKernel ( X , Y ) fits a support vector machine in a randomized Gaussian kernel space to the NxP predictor matrix X and the Nx1 continuous response Y . obj = RegressionKernel (…, name , value ) takes the following Name-Value pairs. Name Value 'Learner' 'svm' , the default, or 'leastsquares' . 'Epsilon' Half the width of the insensitive band, a nonnegative scalar or 'auto' , which is the interquartile range of Y over 13.49. It applies to a support vector machine alone. 'NumExpansionDimensions' 'auto' , the default, or a positive integer. 'KernelScale' 1 by default, a positive scalar, or 'auto' , which takes the median distance between the observations. 'Lambda' 'auto' , the default, which is the reciprocal of the number of observations, or a nonnegative scalar. It cannot be given beside 'BoxConstraint' . 'BoxConstraint' A positive scalar, 1 by default. It applies to a support vector machine alone. 'Standardize' Whether to centre and scale the predictors, false by default. 'BetaTolerance' Relative tolerance on the coefficients, 1e-4 by default. 'GradientTolerance' Absolute tolerance on the gradient’s infinity norm, 1e-6 by default. 'IterationLimit' Largest number of iterations, 1000 by default. 'HessianHistorySize' Number of curvature pairs the solver keeps, 15 by default. 'BlockSize' Memory the expansion may occupy, in megabytes, 4e3 by default. 'ResponseTransform' A transformation applied to the predicted response, named or given as a function handle. 'Weights' One nonnegative weight per observation. 'PredictorNames' One name per predictor. 'ResponseName' A name for the response. 'CategoricalPredictors' Indices of the categorical predictors. The fit is always by limited-memory BFGS, the only solver MATLAB offers a kernel model, and always under a ridge penalty. See also: fitrkernel, RegressionLinear # name: # type: sq_string # elements: 1 # length: 39 Fit a Gaussian kernel regression model. # name: # type: sq_string # elements: 1 # length: 31 RegressionKernel.Regularization # name: # type: sq_string # elements: 1 # length: 209 RegressionKernel: property Regularization Penalty on the coefficients Always 'ridge (L2)' : a kernel model fits in the expanded space, where a lasso penalty has nothing to select. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Penalty on the coefficients # name: # type: sq_string # elements: 1 # length: 29 RegressionKernel.ResponseName # name: # type: sq_string # elements: 1 # length: 130 RegressionKernel: property ResponseName Name of the response A character vector, defaulting to 'Y' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Name of the response # name: # type: sq_string # elements: 1 # length: 34 RegressionKernel.ResponseTransform # name: # type: sq_string # elements: 1 # length: 201 RegressionKernel: property ResponseTransform Transformation applied to the predicted response A character vector, or the text of the function handle that was supplied. Assigning to it accepts either. # name: # type: sq_string # elements: 1 # length: 48 Transformation applied to the predicted response # name: # type: sq_string # elements: 1 # length: 22 RegressionKernel.Sigma # name: # type: sq_string # elements: 1 # length: 215 RegressionKernel: property Sigma Predictor standard deviations used to standardize A row vector with one element per predictor, or empty when the model was fitted without standardizing. This property is read-only. # name: # type: sq_string # elements: 1 # length: 49 Predictor standard deviations used to standardize # name: # type: sq_string # elements: 1 # length: 21 RegressionKernel.loss # name: # type: sq_string # elements: 1 # length: 412 RegressionKernel: l = loss ( obj , X , Y ) RegressionKernel: l = loss (…, name , value ) Regression loss on new data. l = loss ( obj , X , Y ) returns the mean squared error. l = loss (…, name , value ) takes 'LossFun' , either 'mse' or 'epsiloninsensitive' , and 'Weights' . The epsilon-insensitive loss needs a band to be insensitive within, so it is offered by a support vector machine alone. # name: # type: sq_string # elements: 1 # length: 28 Regression loss on new data. # name: # type: sq_string # elements: 1 # length: 24 RegressionKernel.predict # name: # type: sq_string # elements: 1 # length: 248 RegressionKernel: yFit = predict ( obj , XC ) Predict the response of new observations. yFit = predict ( obj , XC ) maps each row of XC through the model’s own random basis and returns the predicted response, with ResponseTransform applied. # name: # type: sq_string # elements: 1 # length: 41 Predict the response of new observations. # name: # type: sq_string # elements: 1 # length: 23 RegressionKernel.resume # name: # type: sq_string # elements: 1 # length: 841 RegressionKernel: obj = resume ( obj , X , Y ) RegressionKernel: obj = resume (…, name , value ) Continue fitting a kernel regression model. obj = resume ( obj , X , Y ) restarts the optimization from the coefficients the model already carries, through the basis it already holds. It takes 'BetaTolerance' , 'GradientTolerance' and 'IterationLimit' , each defaulting to what the model was fitted with, and 'Weights' . X and Y must be the data the model was fitted to; the object keeps no copy of them, which is what makes it small. Neither does it keep the observation weights, so a model fitted with 'Weights' must be given them again here or it will resume against uniform ones. MATLAB behaves the same way: measured on R2024a, resuming a weighted fit without passing the weights back reaches the objective of the unweighted fit. # name: # type: sq_string # elements: 1 # length: 43 Continue fitting a kernel regression model. # name: # type: sq_string # elements: 1 # length: 26 RegressionKernel.savemodel # name: # type: sq_string # elements: 1 # length: 215 RegressionKernel: savemodel ( obj , filename ) Save a kernel regression model to a file. savemodel ( obj , filename ) saves the model obj into filename in a form loadmodel can read back, the random basis included. # name: # type: sq_string # elements: 1 # length: 41 Save a kernel regression model to a file. # name: # type: sq_string # elements: 1 # length: 16 RegressionLinear # name: # type: sq_string # elements: 1 # length: 1080 statistics: RegressionLinear Linear regression model for high dimensional data. A RegressionLinear object fits a linear model, X * Beta + Bias , to a continuous response by minimizing a regularized average loss. The loss is the epsilon-insensitive loss for a support vector machine and the squared error for a least squares fit, and the penalty is either a ridge or a lasso one. Unlike the other regression models of this package the object holds no copy of the training data: the coefficients, the intercept and the fitting options are the whole model. That is what makes it suited to data with more predictors than a kernel matrix could carry, and it is why the class has no compact method and no resubstitution methods. A vector of regularization strengths fits one model per value in a single object. Beta is then a PxL matrix and Bias a 1xL row, every method returns one column per strength, and selectModels narrows the object down to the strengths worth keeping. Create a RegressionLinear object with fitrlinear . See also: fitrlinear, RegressionKernel, RegressionSVM # name: # type: sq_string # elements: 1 # length: 50 Linear regression model for high dimensional data. # name: # type: sq_string # elements: 1 # length: 21 RegressionLinear.Beta # name: # type: sq_string # elements: 1 # length: 195 RegressionLinear: property Beta Fitted linear coefficients A Px1 column, or a PxL matrix with one column per regularization strength when Lambda holds more than one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Fitted linear coefficients # name: # type: sq_string # elements: 1 # length: 21 RegressionLinear.Bias # name: # type: sq_string # elements: 1 # length: 224 RegressionLinear: property Bias Fitted intercept A scalar, or a 1xL row with one element per regularization strength. It is zero throughout when the model was fitted with 'FitBias' set to false. This property is read-only. # name: # type: sq_string # elements: 1 # length: 16 Fitted intercept # name: # type: sq_string # elements: 1 # length: 38 RegressionLinear.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 187 RegressionLinear: property CategoricalPredictors Indices of the categorical predictors A row vector of column indices, empty when every predictor is numeric. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 24 RegressionLinear.Epsilon # name: # type: sq_string # elements: 1 # length: 362 RegressionLinear: property Epsilon Half the width of the epsilon-insensitive band A nonnegative scalar for a support vector machine, and empty for a least squares fit, which has no such band. It defaults to the interquartile range of the response over 13.49, an estimate of its standard deviation, or to 0.1 when that range is zero. This property is read-only. # name: # type: sq_string # elements: 1 # length: 46 Half the width of the epsilon-insensitive band # name: # type: sq_string # elements: 1 # length: 39 RegressionLinear.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 253 RegressionLinear: property ExpandedPredictorNames Names of the predictors as the fit saw them A cell array of character vectors. It equals PredictorNames unless categorical predictors were expanded into indicator variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Names of the predictors as the fit saw them # name: # type: sq_string # elements: 1 # length: 27 RegressionLinear.FittedLoss # name: # type: sq_string # elements: 1 # length: 278 RegressionLinear: property FittedLoss Loss function the fit minimized 'epsiloninsensitive' for a support vector machine and 'mse' for a least squares fit. This is the loss of the objective, which is not the loss loss reports unless it is asked for. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 Loss function the fit minimized # name: # type: sq_string # elements: 1 # length: 23 RegressionLinear.Lambda # name: # type: sq_string # elements: 1 # length: 235 RegressionLinear: property Lambda Regularization strength A nonnegative scalar, or a 1xL row of them in ascending order. It defaults to the reciprocal of the number of observations used to train the model. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Regularization strength # name: # type: sq_string # elements: 1 # length: 24 RegressionLinear.Learner # name: # type: sq_string # elements: 1 # length: 137 RegressionLinear: property Learner Linear regression model that was fitted Either 'svm' or 'leastsquares' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 39 Linear regression model that was fitted # name: # type: sq_string # elements: 1 # length: 32 RegressionLinear.ModelParameters # name: # type: sq_string # elements: 1 # length: 264 RegressionLinear: property ModelParameters Fitting options, as they were given A structure holding every parameter of the fit, including the ones that a different solver would have used and the 'auto' values before they were resolved. This property is read-only. # name: # type: sq_string # elements: 1 # length: 35 Fitting options, as they were given # name: # type: sq_string # elements: 1 # length: 31 RegressionLinear.PredictorNames # name: # type: sq_string # elements: 1 # length: 213 RegressionLinear: property PredictorNames Names of the predictors A cell array of character vectors with one name per column of the training data, defaulting to 'x1' , 'x2' and so on. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Names of the predictors # name: # type: sq_string # elements: 1 # length: 33 RegressionLinear.RegressionLinear # name: # type: sq_string # elements: 1 # length: 3251 RegressionLinear: obj = RegressionLinear ( X , Y ) RegressionLinear: obj = RegressionLinear (…, name , value ) Fit a linear regression model. obj = RegressionLinear ( X , Y ) fits a linear support vector machine to the NxP predictor matrix X and the Nx1 continuous response Y . obj = RegressionLinear (…, name , value ) takes the following Name-Value pairs. Name Value 'Learner' 'svm' , the default, or 'leastsquares' . The first minimizes the epsilon-insensitive loss and the second the squared error. 'Epsilon' Half the width of the insensitive band, a nonnegative scalar or 'auto' , which is the interquartile range of Y over 13.49. It applies to a support vector machine alone. 'Regularization' 'ridge' or 'lasso' . It defaults to 'lasso' when the solver is 'sparsa' and to 'ridge' otherwise. 'Lambda' 'auto' , the default, which is the reciprocal of the number of observations, or a nonnegative scalar, or a vector of them. A vector fits one model per value. 'Solver' One of 'sgd' , 'asgd' , 'dual' , 'bfgs' , 'lbfgs' and 'sparsa' , or a cell array of them applied in turn, each warm starting the next. 'Beta' Initial coefficients, a Px1 column or a PxL matrix. It defaults to zeros. 'Bias' Initial intercept, a scalar or a 1xL row. It defaults to the weighted mean of Y for a least squares fit and to its weighted median for a support vector machine. 'FitBias' Whether to fit an intercept at all, true by default. 'PostFitBias' Whether to refit the intercept once the coefficients are settled, false by default. 'ObservationsIn' 'rows' , the default, or 'columns' , which transposes X before fitting. 'BetaTolerance' Relative tolerance on the coefficients, 1e-4 by default. 'GradientTolerance' Absolute tolerance on the gradient’s infinity norm, 1e-6 by default. 'DeltaGradientTolerance' Tolerance on the complementarity gap of the 'dual' solver, 0.1 by default. 'IterationLimit' Largest number of iterations, 1000 by default. 'PassLimit' Largest number of passes over the data for the stochastic solvers, 1 by default, and 10 for 'dual' . 'BatchSize' Mini-batch size of the stochastic solvers, 10 by default. 'BatchLimit' Largest number of mini-batches. 'LearnRate' Step size of the stochastic solvers. 'OptimizeLearnRate' Whether to halve the step size when the objective rises, true by default. 'TruncationPeriod' Number of mini-batches between soft thresholdings under a lasso penalty, 10 by default. 'NumCheckConvergence' Number of passes between convergence checks of the 'dual' solver, 2 by default. MathWorks documents 5 ; R2024a and R2026a both report 2 . 'HessianHistorySize' Number of curvature pairs the quasi-Newton solvers keep, 15 by default. 'ResponseTransform' A transformation applied to the predicted response, named or given as a function handle. 'Weights' One nonnegative weight per observation. 'PredictorNames' One name per predictor. 'ResponseName' A name for the response. 'CategoricalPredictors' Indices of the categorical predictors. The default solver is 'sparsa' under a lasso penalty. Under a ridge penalty it is 'bfgs' when there are no more than 100 predictors, and beyond that 'dual' for a support vector machine and 'sgd' for a least squares fit. See also: fitrlinear, RegressionKernel # name: # type: sq_string # elements: 1 # length: 30 Fit a linear regression model. # name: # type: sq_string # elements: 1 # length: 31 RegressionLinear.Regularization # name: # type: sq_string # elements: 1 # length: 130 RegressionLinear: property Regularization Penalty on the coefficients 'ridge (L2)' or 'lasso (L1)' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Penalty on the coefficients # name: # type: sq_string # elements: 1 # length: 29 RegressionLinear.ResponseName # name: # type: sq_string # elements: 1 # length: 130 RegressionLinear: property ResponseName Name of the response A character vector, defaulting to 'Y' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Name of the response # name: # type: sq_string # elements: 1 # length: 34 RegressionLinear.ResponseTransform # name: # type: sq_string # elements: 1 # length: 201 RegressionLinear: property ResponseTransform Transformation applied to the predicted response A character vector, or the text of the function handle that was supplied. Assigning to it accepts either. # name: # type: sq_string # elements: 1 # length: 48 Transformation applied to the predicted response # name: # type: sq_string # elements: 1 # length: 21 RegressionLinear.loss # name: # type: sq_string # elements: 1 # length: 451 RegressionLinear: l = loss ( obj , X , Y ) RegressionLinear: l = loss (…, name , value ) Regression loss on new data. l = loss ( obj , X , Y ) returns the mean squared error, one value per regularization strength. l = loss (…, name , value ) takes 'LossFun' , either 'mse' or 'epsiloninsensitive' , and 'Weights' . The epsilon-insensitive loss needs a band to be insensitive within, so it is offered by a support vector machine alone. # name: # type: sq_string # elements: 1 # length: 28 Regression loss on new data. # name: # type: sq_string # elements: 1 # length: 24 RegressionLinear.predict # name: # type: sq_string # elements: 1 # length: 249 RegressionLinear: yFit = predict ( obj , XC ) Predict the response of new observations. yFit = predict ( obj , XC ) returns one predicted value per row of XC , and one column per regularization strength. ResponseTransform is applied to the result. # name: # type: sq_string # elements: 1 # length: 41 Predict the response of new observations. # name: # type: sq_string # elements: 1 # length: 26 RegressionLinear.savemodel # name: # type: sq_string # elements: 1 # length: 188 RegressionLinear: savemodel ( obj , filename ) Save a linear regression model to a file. savemodel ( obj , filename ) saves the model obj into filename in a form loadmodel can read back. # name: # type: sq_string # elements: 1 # length: 41 Save a linear regression model to a file. # name: # type: sq_string # elements: 1 # length: 29 RegressionLinear.selectModels # name: # type: sq_string # elements: 1 # length: 255 RegressionLinear: sub = selectModels ( obj , idx ) Keep a subset of the fitted regularization strengths. sub = selectModels ( obj , idx ) returns a model holding only the strengths idx names, which may be indices into Lambda or a logical vector over it. # name: # type: sq_string # elements: 1 # length: 53 Keep a subset of the fitted regularization strengths. # name: # type: sq_string # elements: 1 # length: 23 RegressionNeuralNetwork # name: # type: sq_string # elements: 1 # length: 4468 statistics: obj = RegressionNeuralNetwork ( X , Y ) statistics: obj = RegressionNeuralNetwork (…, name , value ) Create a RegressionNeuralNetwork object containing a neural network regression model. obj = RegressionNeuralNetwork ( X , Y ) returns a neural network regression model, obj , with X being the predictor data and Y the continuous response of the observations in X . X must be an NxP numeric matrix of predictor data, where rows correspond to observations and columns to features. Y must be an Nx1 numeric vector holding the response of the corresponding predictor data in X . Y must have the same number of rows as X . The network is trained against the mean squared error, and its output layer applies the identity, so a prediction is an unrestricted real number rather than a score over classes. This is the only difference in the engine between this class and ClassificationNeuralNetwork ; everything else, the layer sizes, the activations, the learning rate and the initialisation, behaves identically. obj = RegressionNeuralNetwork (…, name , value ) returns a model with additional options specified by Name-Value pair arguments listed below. Name Value 'Standardize' A logical scalar specifying whether the predictor data should be centred and scaled before training. The same transformation is applied by predict . The default is false . 'PredictorNames' A cell array of character vectors naming the predictors, in the order they appear in X . 'ResponseName' A character vector naming the response. The default is 'Y' . 'ResponseTransform' A character vector naming one of the supported transformations, or a function handle, applied to the predicted response by predict and resubPredict . The default is 'none' . 'LayerSizes' A positive integer vector specifying the number of units in each fully connected hidden layer. The default is 10, one hidden layer of ten units. 'Activations' A character vector or cell array of character vectors specifying the activation of the hidden layers. The supported functions are 'linear' , 'sigmoid' , 'relu' , 'tanh' , 'lrelu' , 'prelu' , 'elu' , 'gelu' and 'none' . The default is 'relu' . 'OutputLayerActivation' A character vector specifying the activation of the output layer. The default is 'none' , the identity, which is what a regression output calls for. The supported values are the same as for 'Activations' . 'LearningRate' A positive scalar specifying the learning rate for gradient descent. The default is 0.003. A larger rate can drive every unit of a hidden layer negative, after which a rectifier passes no gradient and the network stops training. Applies only when 'Solver' is 'sgd' . 'Solver' A character vector naming the solver that trains the network, either 'lbfgs' or 'sgd' . The default is 'lbfgs' , which minimizes the loss over the whole training set at once by limited-memory BFGS, as MATLAB does. It takes no learning rate, stops on the three tolerances below, and reaches a lower training loss in fewer passes over the data, though each of its iterations costs several passes where an epoch costs one. 'sgd' visits the samples one at a time and steps down the gradient of each, running for 'IterationLimit' epochs; it was the default before version 1.9.0. 'GradientTolerance' A nonnegative scalar. Training stops once the gradient’s infinity norm falls to or below it, which is the quantity MATLAB tests too. The default is 1e-6 . Applies only when 'Solver' is 'lbfgs' . 'StepTolerance' A nonnegative scalar. Training stops once the step’s infinity norm falls to or below it, which is the quantity MATLAB tests too. The default is 1e-6 . Applies only when 'Solver' is 'lbfgs' . 'LossTolerance' A real scalar. Training stops once the training loss falls to or below it. The test is on the loss itself and not on its change, matching MATLAB; pass -Inf to switch it off. The default is 1e-6 . Applies only when 'Solver' is 'lbfgs' . 'IterationLimit' A positive integer specifying the maximum number of training iterations. The default is 1000. Under 'sgd' this counts epochs, under 'lbfgs' solver iterations. 'DisplayInfo' A logical scalar specifying whether to print information during training. The default is false . The supported values for 'ResponseTransform' are: Value Description 'none' x (no transformation) 'identity' x (no transformation) 'exp' exp (x) 'log' log (x) See also: fitrnet, ClassificationNeuralNetwork, fcnntrain, fcnnpredict # name: # type: sq_string # elements: 1 # length: 85 Create a RegressionNeuralNetwork object containing a neural network regression model. # name: # type: sq_string # elements: 1 # length: 35 RegressionNeuralNetwork.Activations # name: # type: sq_string # elements: 1 # length: 239 RegressionNeuralNetwork: property Activations Activation functions of the hidden layers A character vector, applying to every hidden layer, or a cell array of character vectors with one entry per hidden layer. This property is read-only. # name: # type: sq_string # elements: 1 # length: 41 Activation functions of the hidden layers # name: # type: sq_string # elements: 1 # length: 32 RegressionNeuralNetwork.BinEdges # name: # type: sq_string # elements: 1 # length: 371 RegressionNeuralNetwork: property BinEdges Bin edges of the predictors A cell array with one entry per predictor, holding that predictor’s bin edges where the learner discretized it before fitting. It is empty here and stays empty: this learner fits the predictors as they are, and MATLAB’s reports an empty cell for it as well. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Bin edges of the predictors # name: # type: sq_string # elements: 1 # length: 45 RegressionNeuralNetwork.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 235 RegressionNeuralNetwork: property CategoricalPredictors Indices of the categorical predictors A numeric vector of column indices into X naming the predictors treated as categorical, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 39 RegressionNeuralNetwork.ConvergenceInfo # name: # type: sq_string # elements: 1 # length: 578 RegressionNeuralNetwork: property ConvergenceInfo Information recorded during training A structure with the fields Time , the seconds training took, and TrainingLoss , the mean squared error of the network at the end of each iteration. This property is read-only. Under 'lbfgs' the structure carries Gradient and Step , the two quantities the solver measured to decide it had converged, and ConvergenceCriterion , naming the test that stopped it. It carries no Accuracy : MATLAB reports none, and measuring it would cost a pass over the whole training set at every iteration. # name: # type: sq_string # elements: 1 # length: 36 Information recorded during training # name: # type: sq_string # elements: 1 # length: 35 RegressionNeuralNetwork.DisplayInfo # name: # type: sq_string # elements: 1 # length: 131 RegressionNeuralNetwork: property DisplayInfo Whether training printed its progress A logical scalar. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Whether training printed its progress # name: # type: sq_string # elements: 1 # length: 46 RegressionNeuralNetwork.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 268 RegressionNeuralNetwork: property ExpandedPredictorNames Names of the predictors as the model expanded them A cell array of character vectors. It matches PredictorNames unless a categorical predictor was expanded into indicator variables. This property is read-only. # name: # type: sq_string # elements: 1 # length: 50 Names of the predictors as the model expanded them # name: # type: sq_string # elements: 1 # length: 57 RegressionNeuralNetwork.HyperparameterOptimizationResults # name: # type: sq_string # elements: 1 # length: 366 RegressionNeuralNetwork: property HyperparameterOptimizationResults Results of the hyperparameter optimization Always empty. It is declared for MATLAB compatibility, where it holds what an automatic search over the hyperparameters found. This class fits the parameters it is given and runs no such search, so there is nothing to report. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Results of the hyperparameter optimization # name: # type: sq_string # elements: 1 # length: 38 RegressionNeuralNetwork.IterationLimit # name: # type: sq_string # elements: 1 # length: 143 RegressionNeuralNetwork: property IterationLimit Maximum number of training iterations A positive integer scalar. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Maximum number of training iterations # name: # type: sq_string # elements: 1 # length: 35 RegressionNeuralNetwork.LayerBiases # name: # type: sq_string # elements: 1 # length: 232 RegressionNeuralNetwork: property LayerBiases Biases the network learned A cell array with one entry per layer, the output layer included. LayerBiases{i} is a column with one entry per unit of layer i . This property is read-only. # name: # type: sq_string # elements: 1 # length: 26 Biases the network learned # name: # type: sq_string # elements: 1 # length: 34 RegressionNeuralNetwork.LayerSizes # name: # type: sq_string # elements: 1 # length: 252 RegressionNeuralNetwork: property LayerSizes Sizes of the fully connected hidden layers A row vector of positive integers, one per hidden layer. It does not include the output layer, whose width is the number of responses. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Sizes of the fully connected hidden layers # name: # type: sq_string # elements: 1 # length: 36 RegressionNeuralNetwork.LayerWeights # name: # type: sq_string # elements: 1 # length: 258 RegressionNeuralNetwork: property LayerWeights Weights the network learned A cell array with one entry per layer, the output layer included. LayerWeights{i} has one row per unit of layer i and one column per input to that layer. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Weights the network learned # name: # type: sq_string # elements: 1 # length: 36 RegressionNeuralNetwork.LearningRate # name: # type: sq_string # elements: 1 # length: 218 RegressionNeuralNetwork: property LearningRate Learning rate for gradient descent A positive scalar value defining the learning rate used by the gradient descent algorithm during training. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Learning rate for gradient descent # name: # type: sq_string # elements: 1 # length: 39 RegressionNeuralNetwork.ModelParameters # name: # type: sq_string # elements: 1 # length: 1172 RegressionNeuralNetwork: property ModelParameters Parameters of the trained network A structure holding the fit as it was asked for: LayerSizes , Activations , OutputLayerActivation , LayerWeightsInitializers , Solver , LearningRate , IterationLimit , GradientTolerance , LossTolerance , StepTolerance , DisplayInfo , StandardizeData , and the Version , Method and Type tags. What came out of the fit is elsewhere: the LayerWeights and LayerBiases properties hold the network, TrainingHistory the series and ConvergenceInfo where it stopped. LayerWeightsInitializers names the scheme each layer’s weights were drawn with, the output layer last: 'he' for a rectifying activation and 'glorot' for a symmetric one. It is a report, not a setting, the engine choosing per layer from the activation and offering no way to override it. OutputLayerActivation , Solver and LearningRate are this package’s own; MATLAB has no counterpart for them. The fields it reports that this class does not accept as arguments ( Lambda , the validation set and its patience and frequency, InitialStepSize and the two initializer settings) are absent. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Parameters of the trained network # name: # type: sq_string # elements: 1 # length: 26 RegressionNeuralNetwork.Mu # name: # type: sq_string # elements: 1 # length: 211 RegressionNeuralNetwork: property Mu Mean of the predictors A row vector with one entry per predictor, used for standardization. Empty when the predictor data were not standardized. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Mean of the predictors # name: # type: sq_string # elements: 1 # length: 39 RegressionNeuralNetwork.NumObservations # name: # type: sq_string # elements: 1 # length: 221 RegressionNeuralNetwork: property NumObservations Number of observations used to train the model A positive integer scalar, counting only the rows that survived the removal of missing values. This property is read-only. # name: # type: sq_string # elements: 1 # length: 46 Number of observations used to train the model # name: # type: sq_string # elements: 1 # length: 37 RegressionNeuralNetwork.NumPredictors # name: # type: sq_string # elements: 1 # length: 154 RegressionNeuralNetwork: property NumPredictors Number of predictors A positive integer scalar, the number of columns of X . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 45 RegressionNeuralNetwork.OutputLayerActivation # name: # type: sq_string # elements: 1 # length: 237 RegressionNeuralNetwork: property OutputLayerActivation Activation function of the output layer A character vector. The default, 'none' , applies the identity, so a prediction is an unrestricted real number. This property is read-only. # name: # type: sq_string # elements: 1 # length: 39 Activation function of the output layer # name: # type: sq_string # elements: 1 # length: 38 RegressionNeuralNetwork.PredictorNames # name: # type: sq_string # elements: 1 # length: 159 RegressionNeuralNetwork: property PredictorNames Names of the predictors A cell array of character vectors, one per column of X . This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Names of the predictors # name: # type: sq_string # elements: 1 # length: 47 RegressionNeuralNetwork.RegressionNeuralNetwork # name: # type: sq_string # elements: 1 # length: 340 RegressionNeuralNetwork: obj = RegressionNeuralNetwork ( X , Y ) RegressionNeuralNetwork: obj = RegressionNeuralNetwork (…, name , value ) Create a RegressionNeuralNetwork object containing a neural network regression model. See the class documentation for the accepted Name-Value pairs. See also: fitrnet, RegressionNeuralNetwork # name: # type: sq_string # elements: 1 # length: 85 Create a RegressionNeuralNetwork object containing a neural network regression model. # name: # type: sq_string # elements: 1 # length: 36 RegressionNeuralNetwork.ResponseName # name: # type: sq_string # elements: 1 # length: 126 RegressionNeuralNetwork: property ResponseName Name of the response variable A character vector. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Name of the response variable # name: # type: sq_string # elements: 1 # length: 41 RegressionNeuralNetwork.ResponseTransform # name: # type: sq_string # elements: 1 # length: 316 RegressionNeuralNetwork: property ResponseTransform Transformation applied to the predicted response A function handle, applied by predict and resubPredict to the network’s output. It defaults to the identity and may be set after construction, either to a handle or to the name of a supported transformation. # name: # type: sq_string # elements: 1 # length: 48 Transformation applied to the predicted response # name: # type: sq_string # elements: 1 # length: 32 RegressionNeuralNetwork.RowsUsed # name: # type: sq_string # elements: 1 # length: 398 RegressionNeuralNetwork: property RowsUsed Rows used for fitting A logical column vector with the same length as the observations in the original predictor data X , true for each row that was used for fitting the RegressionNeuralNetwork model. It is empty, [] , when every observation was used, so a non-empty value means that rows holding missing values were dropped. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Rows used for fitting # name: # type: sq_string # elements: 1 # length: 29 RegressionNeuralNetwork.Sigma # name: # type: sq_string # elements: 1 # length: 228 RegressionNeuralNetwork: property Sigma Standard deviation of the predictors A row vector with one entry per predictor, used for standardization. Empty when the predictor data were not standardized. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Standard deviation of the predictors # name: # type: sq_string # elements: 1 # length: 30 RegressionNeuralNetwork.Solver # name: # type: sq_string # elements: 1 # length: 210 RegressionNeuralNetwork: property Solver Solver used to train the network A character vector, either 'Gradient Descent' for the stochastic solver or 'LBFGS' for the full-batch one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Solver used to train the network # name: # type: sq_string # elements: 1 # length: 39 RegressionNeuralNetwork.TrainingHistory # name: # type: sq_string # elements: 1 # length: 423 RegressionNeuralNetwork: property TrainingHistory Iteration by iteration record of the fit A table with the variables Iteration and TrainingLoss , one row per training iteration. This property is read-only. The columns follow the solver. Under 'sgd' they are Iteration and TrainingLoss , with TrainingAccuracy for a classifier. Under 'lbfgs' they are Iteration , TrainingLoss , Gradient and Step , as MATLAB’s are. # name: # type: sq_string # elements: 1 # length: 40 Iteration by iteration record of the fit # name: # type: sq_string # elements: 1 # length: 25 RegressionNeuralNetwork.W # name: # type: sq_string # elements: 1 # length: 205 RegressionNeuralNetwork: property W Observation weights A numeric column vector with one entry per training observation. It defaults to a uniform weight for every observation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 25 RegressionNeuralNetwork.X # name: # type: sq_string # elements: 1 # length: 196 RegressionNeuralNetwork: property X Predictor data An NxP numeric matrix, as it was supplied to the constructor, before any rows carrying missing values were dropped. This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Predictor data # name: # type: sq_string # elements: 1 # length: 25 RegressionNeuralNetwork.Y # name: # type: sq_string # elements: 1 # length: 141 RegressionNeuralNetwork: property Y Response data An Nx1 numeric vector, as it was supplied to the constructor. This property is read-only. # name: # type: sq_string # elements: 1 # length: 13 Response data # name: # type: sq_string # elements: 1 # length: 31 RegressionNeuralNetwork.compact # name: # type: sq_string # elements: 1 # length: 386 RegressionNeuralNetwork: CMdl = compact ( obj ) Create a CompactRegressionNeuralNetwork object. CMdl = compact ( obj ) returns a compact version of the RegressionNeuralNetwork object obj , which keeps the trained network but drops the training data, so it predicts identically while carrying no observations. See also: fitrnet, RegressionNeuralNetwork, CompactRegressionNeuralNetwork # name: # type: sq_string # elements: 1 # length: 47 Create a CompactRegressionNeuralNetwork object. # name: # type: sq_string # elements: 1 # length: 32 RegressionNeuralNetwork.crossval # name: # type: sq_string # elements: 1 # length: 909 RegressionNeuralNetwork: CVMdl = crossval ( obj ) RegressionNeuralNetwork: CVMdl = crossval (…, name , value ) Cross validate a neural network regression model. CVMdl = crossval ( obj ) returns a RegressionPartitionedModel holding one refit of obj per fold of a ten-fold partition, or of an n -fold one where the model has fewer than ten observations. obj must be a RegressionNeuralNetwork class object. CVMdl = crossval (…, name , value ) accepts one, and only one, of the following Name-Value pairs. Name Value 'KFold' An integer greater than 1, the number of folds. 'Holdout' A scalar in (0, 1) , the fraction of observations held out for testing. 'Leaveout' 'on' or 'off' , whether to hold out one observation at a time. 'CVPartition' A cvpartition object over as many observations as the model was trained on. See also: RegressionNeuralNetwork, RegressionPartitionedModel, cvpartition # name: # type: sq_string # elements: 1 # length: 49 Cross validate a neural network regression model. # name: # type: sq_string # elements: 1 # length: 28 RegressionNeuralNetwork.loss # name: # type: sq_string # elements: 1 # length: 995 RegressionNeuralNetwork: L = loss ( obj , X , Y ) RegressionNeuralNetwork: L = loss (…, name , value ) Compute the regression loss of a neural network model. L = loss ( obj , X , Y ) returns the weighted mean squared error between the response Y and the response the model predicts for X . obj must be a RegressionNeuralNetwork class object. X must be a numeric matrix with the same number of predictors as the data the model was trained on. Y must be a numeric vector with as many rows as X . L = loss (…, name , value ) accepts the following Name-Value pairs. Name Value 'LossFun' 'mse' , the default, or a function handle called as lossfun ( Y , yFit , W ) and returning a scalar. 'Weights' A numeric vector of observation weights with one entry per row of X . It defaults to a uniform weight. The weights are normalized to sum to one before the loss is formed, so scaling them all by the same factor leaves the loss unchanged. See also: RegressionNeuralNetwork, fitrnet # name: # type: sq_string # elements: 1 # length: 54 Compute the regression loss of a neural network model. # name: # type: sq_string # elements: 1 # length: 31 RegressionNeuralNetwork.predict # name: # type: sq_string # elements: 1 # length: 576 RegressionNeuralNetwork: yFit = predict ( obj , XC ) Predict the response for new data with a neural network regression model. yFit = predict ( obj , XC ) returns a column vector holding the predicted response for each row of XC , using the network stored in obj . obj must be a RegressionNeuralNetwork class object. XC must be a numeric matrix with the same number of predictors as the data the model was trained on. The transformation named by ResponseTransform is applied to the network’s output before it is returned. See also: RegressionNeuralNetwork, fitrnet # name: # type: sq_string # elements: 1 # length: 73 Predict the response for new data with a neural network regression model. # name: # type: sq_string # elements: 1 # length: 33 RegressionNeuralNetwork.resubLoss # name: # type: sq_string # elements: 1 # length: 433 RegressionNeuralNetwork: L = resubLoss ( obj ) RegressionNeuralNetwork: L = resubLoss (…, name , value ) Compute the resubstitution regression loss of a neural network model. L = resubLoss ( obj ) returns the weighted mean squared error of the model on the data it was trained on. It accepts the same Name-Value pairs as loss . obj must be a RegressionNeuralNetwork class object. See also: RegressionNeuralNetwork, fitrnet # name: # type: sq_string # elements: 1 # length: 69 Compute the resubstitution regression loss of a neural network model. # name: # type: sq_string # elements: 1 # length: 36 RegressionNeuralNetwork.resubPredict # name: # type: sq_string # elements: 1 # length: 417 RegressionNeuralNetwork: yFit = resubPredict ( obj ) Predict the response of the training data with a neural network regression model. yFit = resubPredict ( obj ) returns a column vector holding the predicted response for every observation the model was trained on, that is the rows of obj.X selected by obj.RowsUsed . obj must be a RegressionNeuralNetwork class object. See also: RegressionNeuralNetwork, fitrnet # name: # type: sq_string # elements: 1 # length: 81 Predict the response of the training data with a neural network regression model. # name: # type: sq_string # elements: 1 # length: 33 RegressionNeuralNetwork.savemodel # name: # type: sq_string # elements: 1 # length: 329 RegressionNeuralNetwork: savemodel ( obj , filename ) Save a neural network regression model to a file. savemodel ( obj , filename ) saves every property of the RegressionNeuralNetwork object obj into filename in binary format, so that it can be read back with loadmodel . See also: loadmodel, RegressionNeuralNetwork, fitrnet # name: # type: sq_string # elements: 1 # length: 49 Save a neural network regression model to a file. # name: # type: sq_string # elements: 1 # length: 27 RegressionPartitionedKernel # name: # type: sq_string # elements: 1 # length: 1022 statistics: RegressionPartitionedKernel Cross-validated Gaussian kernel regression model. A RegressionPartitionedKernel object holds one RegressionKernel per fold of a partition, each fitted to the observations the fold trains on. kfoldPredict predicts each observation with the fold that held it out , so what it returns is an out-of-sample prediction. A RegressionKernel stores no copy of its training data and so has no resubstitution methods and no compact form. This class is what takes their place: cross-validation is the way a kernel model is asked how it would do on data it has not seen. Every fold draws its own random basis, as it must, being its own fit. Two folds therefore approximate the same kernel through different expansions, which is a source of variation between folds over and above the data they were given. A larger 'NumExpansionDimensions' narrows it. Create one with fitrlinear and a cross-validation option, or directly. See also: fitrlinear, RegressionKernel, RegressionPartitionedKernel # name: # type: sq_string # elements: 1 # length: 49 Cross-validated Gaussian kernel regression model. # name: # type: sq_string # elements: 1 # length: 49 RegressionPartitionedKernel.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 198 RegressionPartitionedKernel: property CategoricalPredictors Indices of the categorical predictors A row vector of column indices, empty when every predictor is numeric. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 47 RegressionPartitionedKernel.CrossValidatedModel # name: # type: sq_string # elements: 1 # length: 176 RegressionPartitionedKernel: property CrossValidatedModel Name of the model that was cross-validated Always 'Linear' , the short name MATLAB uses. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Name of the model that was cross-validated # name: # type: sq_string # elements: 1 # length: 33 RegressionPartitionedKernel.KFold # name: # type: sq_string # elements: 1 # length: 218 RegressionPartitionedKernel: property KFold Number of folds A positive integer scalar. A holdout partition has one fold and a leave-one-out partition has as many as there are observations. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Number of folds # name: # type: sq_string # elements: 1 # length: 43 RegressionPartitionedKernel.ModelParameters # name: # type: sq_string # elements: 1 # length: 1151 RegressionPartitionedKernel: property ModelParameters What was cross-validated, and how A structure holding the parameters the folds were fitted with, carried through from the learner that was cross validated, beside NLearn , the number of folds, and the Version , Method and Type tags of this class, with LearnerTemplates naming the backing. The learner’s own tags are replaced rather than kept, so a cross-validated SVM reports Method as 'PartitionedKernel' and not 'SVM' . Deviation from MATLAB. MATLAB reports the parameter record of the cross-validation ensemble here rather than of the learner, so it says nothing at all about how the folds were fitted: of its eighteen fields only the fold count, its partitioner and a fit template carry anything, and the rest are boosting settings left inert. Nor can the parameters be reached through the folds, a compact model carrying none in MATLAB. This class reports the fit instead, which is strictly more than MATLAB offers, and everything MATLAB’s record does carry is published here as the KFold , Partition , X , Y , W and CrossValidatedModel properties. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 What was cross-validated, and how # name: # type: sq_string # elements: 1 # length: 43 RegressionPartitionedKernel.NumObservations # name: # type: sq_string # elements: 1 # length: 217 RegressionPartitionedKernel: property NumObservations Number of observations the partition covers A positive integer scalar, counting the rows that survived the removal of missing values. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Number of observations the partition covers # name: # type: sq_string # elements: 1 # length: 37 RegressionPartitionedKernel.Partition # name: # type: sq_string # elements: 1 # length: 151 RegressionPartitionedKernel: property Partition The partition itself A cvpartition object over the retained observations. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 The partition itself # name: # type: sq_string # elements: 1 # length: 42 RegressionPartitionedKernel.PredictorNames # name: # type: sq_string # elements: 1 # length: 141 RegressionPartitionedKernel: property PredictorNames Names of the predictors A cell array of character vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Names of the predictors # name: # type: sq_string # elements: 1 # length: 55 RegressionPartitionedKernel.RegressionPartitionedKernel # name: # type: sq_string # elements: 1 # length: 972 RegressionPartitionedKernel: obj = RegressionPartitionedKernel ( X , Y ) RegressionPartitionedKernel: obj = RegressionPartitionedKernel (…, name , value ) Cross-validate a linear regression model. obj = RegressionPartitionedKernel ( X , Y ) partitions the data into ten folds and fits a RegressionKernel to each. obj = RegressionPartitionedKernel (…, name , value ) takes one of 'KFold' , 'Holdout' , 'Leaveout' and 'CVPartition' to say how to partition, and any option RegressionKernel takes to say how to fit. 'CrossVal' is accepted and has no effect here, this class being cross-validated by construction. Anything left as 'auto' is resolved by each fold against its own training rows rather than once over the whole data, so ten folds of a hundred observations each get a Lambda of one ninetieth rather than one hundredth, and each its own Epsilon and KernelScale . Both are MATLAB’s behaviour, measured. See also: fitrlinear, RegressionKernel # name: # type: sq_string # elements: 1 # length: 41 Cross-validate a linear regression model. # name: # type: sq_string # elements: 1 # length: 40 RegressionPartitionedKernel.ResponseName # name: # type: sq_string # elements: 1 # length: 141 RegressionPartitionedKernel: property ResponseName Name of the response A character vector, defaulting to 'Y' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Name of the response # name: # type: sq_string # elements: 1 # length: 45 RegressionPartitionedKernel.ResponseTransform # name: # type: sq_string # elements: 1 # length: 332 RegressionPartitionedKernel: property ResponseTransform Transformation applied to the predicted response A character vector, or the text of the function handle that was supplied, which may be assigned after the model is built. The fold models carry no transform of their own; this one is applied once to the assembled predictions. # name: # type: sq_string # elements: 1 # length: 48 Transformation applied to the predicted response # name: # type: sq_string # elements: 1 # length: 35 RegressionPartitionedKernel.Trained # name: # type: sq_string # elements: 1 # length: 208 RegressionPartitionedKernel: property Trained The models fitted to the folds A cell column with one RegressionKernel per fold, each fitted to the observations its fold trains on. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 The models fitted to the folds # name: # type: sq_string # elements: 1 # length: 29 RegressionPartitionedKernel.W # name: # type: sq_string # elements: 1 # length: 127 RegressionPartitionedKernel: property W Observation weights An Nx1 numeric vector summing to one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 29 RegressionPartitionedKernel.Y # name: # type: sq_string # elements: 1 # length: 130 RegressionPartitionedKernel: property Y Response of the retained observations An Nx1 numeric vector. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Response of the retained observations # name: # type: sq_string # elements: 1 # length: 37 RegressionPartitionedKernel.kfoldLoss # name: # type: sq_string # elements: 1 # length: 458 RegressionPartitionedKernel: l = kfoldLoss ( obj ) RegressionPartitionedKernel: l = kfoldLoss (…, name , value ) Out-of-fold regression loss. l = kfoldLoss ( obj ) returns the out-of-fold mean squared error. l = kfoldLoss (…, name , value ) takes 'LossFun' , either 'mse' or 'epsiloninsensitive' ; 'Folds' , a subset of the folds to average over; and 'Mode' , either 'average' , the default, or 'individual' , which returns one row per fold. # name: # type: sq_string # elements: 1 # length: 28 Out-of-fold regression loss. # name: # type: sq_string # elements: 1 # length: 40 RegressionPartitionedKernel.kfoldPredict # name: # type: sq_string # elements: 1 # length: 306 RegressionPartitionedKernel: yFit = kfoldPredict ( obj ) Out-of-fold prediction for every observation. Each observation is predicted by the fold that held it out, so the predictions are out-of-sample. An observation that no fold held out, which under a holdout partition is most of them, comes back NaN . # name: # type: sq_string # elements: 1 # length: 45 Out-of-fold prediction for every observation. # name: # type: sq_string # elements: 1 # length: 27 RegressionPartitionedLinear # name: # type: sq_string # elements: 1 # length: 883 statistics: RegressionPartitionedLinear Cross-validated linear regression model. A RegressionPartitionedLinear object holds one RegressionLinear per fold of a partition, each fitted to the observations the fold trains on. kfoldPredict predicts each observation with the fold that held it out , so what it returns is an out-of-sample prediction. A RegressionLinear stores no copy of its training data and so has no resubstitution methods and no compact form. This class is what takes their place: cross-validation is the way a linear model is asked how it would do on data it has not seen. When the fold models carry a whole regularization path, both methods return one column per strength, in the order of the 'Lambda' that was asked for. Create one with fitrlinear and a cross-validation option, or directly. See also: fitrlinear, RegressionLinear, RegressionPartitionedKernel # name: # type: sq_string # elements: 1 # length: 40 Cross-validated linear regression model. # name: # type: sq_string # elements: 1 # length: 49 RegressionPartitionedLinear.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 198 RegressionPartitionedLinear: property CategoricalPredictors Indices of the categorical predictors A row vector of column indices, empty when every predictor is numeric. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 47 RegressionPartitionedLinear.CrossValidatedModel # name: # type: sq_string # elements: 1 # length: 176 RegressionPartitionedLinear: property CrossValidatedModel Name of the model that was cross-validated Always 'Linear' , the short name MATLAB uses. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Name of the model that was cross-validated # name: # type: sq_string # elements: 1 # length: 33 RegressionPartitionedLinear.KFold # name: # type: sq_string # elements: 1 # length: 218 RegressionPartitionedLinear: property KFold Number of folds A positive integer scalar. A holdout partition has one fold and a leave-one-out partition has as many as there are observations. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Number of folds # name: # type: sq_string # elements: 1 # length: 43 RegressionPartitionedLinear.ModelParameters # name: # type: sq_string # elements: 1 # length: 1151 RegressionPartitionedLinear: property ModelParameters What was cross-validated, and how A structure holding the parameters the folds were fitted with, carried through from the learner that was cross validated, beside NLearn , the number of folds, and the Version , Method and Type tags of this class, with LearnerTemplates naming the backing. The learner’s own tags are replaced rather than kept, so a cross-validated SVM reports Method as 'PartitionedLinear' and not 'SVM' . Deviation from MATLAB. MATLAB reports the parameter record of the cross-validation ensemble here rather than of the learner, so it says nothing at all about how the folds were fitted: of its eighteen fields only the fold count, its partitioner and a fit template carry anything, and the rest are boosting settings left inert. Nor can the parameters be reached through the folds, a compact model carrying none in MATLAB. This class reports the fit instead, which is strictly more than MATLAB offers, and everything MATLAB’s record does carry is published here as the KFold , Partition , X , Y , W and CrossValidatedModel properties. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 What was cross-validated, and how # name: # type: sq_string # elements: 1 # length: 43 RegressionPartitionedLinear.NumObservations # name: # type: sq_string # elements: 1 # length: 217 RegressionPartitionedLinear: property NumObservations Number of observations the partition covers A positive integer scalar, counting the rows that survived the removal of missing values. This property is read-only. # name: # type: sq_string # elements: 1 # length: 43 Number of observations the partition covers # name: # type: sq_string # elements: 1 # length: 37 RegressionPartitionedLinear.Partition # name: # type: sq_string # elements: 1 # length: 151 RegressionPartitionedLinear: property Partition The partition itself A cvpartition object over the retained observations. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 The partition itself # name: # type: sq_string # elements: 1 # length: 42 RegressionPartitionedLinear.PredictorNames # name: # type: sq_string # elements: 1 # length: 141 RegressionPartitionedLinear: property PredictorNames Names of the predictors A cell array of character vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Names of the predictors # name: # type: sq_string # elements: 1 # length: 55 RegressionPartitionedLinear.RegressionPartitionedLinear # name: # type: sq_string # elements: 1 # length: 956 RegressionPartitionedLinear: obj = RegressionPartitionedLinear ( X , Y ) RegressionPartitionedLinear: obj = RegressionPartitionedLinear (…, name , value ) Cross-validate a linear regression model. obj = RegressionPartitionedLinear ( X , Y ) partitions the data into ten folds and fits a RegressionLinear to each. obj = RegressionPartitionedLinear (…, name , value ) takes one of 'KFold' , 'Holdout' , 'Leaveout' and 'CVPartition' to say how to partition, and any option RegressionLinear takes to say how to fit. 'CrossVal' is accepted and has no effect here, this class being cross-validated by construction. Anything left as 'auto' is resolved by each fold against its own training rows rather than once over the whole data, so ten folds of a hundred observations each get a Lambda of one ninetieth rather than one hundredth, and each its own Epsilon . Both are MATLAB’s behaviour, measured. See also: fitrlinear, RegressionLinear # name: # type: sq_string # elements: 1 # length: 41 Cross-validate a linear regression model. # name: # type: sq_string # elements: 1 # length: 40 RegressionPartitionedLinear.ResponseName # name: # type: sq_string # elements: 1 # length: 141 RegressionPartitionedLinear: property ResponseName Name of the response A character vector, defaulting to 'Y' . This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Name of the response # name: # type: sq_string # elements: 1 # length: 45 RegressionPartitionedLinear.ResponseTransform # name: # type: sq_string # elements: 1 # length: 332 RegressionPartitionedLinear: property ResponseTransform Transformation applied to the predicted response A character vector, or the text of the function handle that was supplied, which may be assigned after the model is built. The fold models carry no transform of their own; this one is applied once to the assembled predictions. # name: # type: sq_string # elements: 1 # length: 48 Transformation applied to the predicted response # name: # type: sq_string # elements: 1 # length: 35 RegressionPartitionedLinear.Trained # name: # type: sq_string # elements: 1 # length: 208 RegressionPartitionedLinear: property Trained The models fitted to the folds A cell column with one RegressionLinear per fold, each fitted to the observations its fold trains on. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 The models fitted to the folds # name: # type: sq_string # elements: 1 # length: 29 RegressionPartitionedLinear.W # name: # type: sq_string # elements: 1 # length: 127 RegressionPartitionedLinear: property W Observation weights An Nx1 numeric vector summing to one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 29 RegressionPartitionedLinear.Y # name: # type: sq_string # elements: 1 # length: 130 RegressionPartitionedLinear: property Y Response of the retained observations An Nx1 numeric vector. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Response of the retained observations # name: # type: sq_string # elements: 1 # length: 37 RegressionPartitionedLinear.kfoldLoss # name: # type: sq_string # elements: 1 # length: 458 RegressionPartitionedLinear: l = kfoldLoss ( obj ) RegressionPartitionedLinear: l = kfoldLoss (…, name , value ) Out-of-fold regression loss. l = kfoldLoss ( obj ) returns the out-of-fold mean squared error. l = kfoldLoss (…, name , value ) takes 'LossFun' , either 'mse' or 'epsiloninsensitive' ; 'Folds' , a subset of the folds to average over; and 'Mode' , either 'average' , the default, or 'individual' , which returns one row per fold. # name: # type: sq_string # elements: 1 # length: 28 Out-of-fold regression loss. # name: # type: sq_string # elements: 1 # length: 40 RegressionPartitionedLinear.kfoldPredict # name: # type: sq_string # elements: 1 # length: 373 RegressionPartitionedLinear: yFit = kfoldPredict ( obj ) Out-of-fold prediction for every observation. Each observation is predicted by the fold that held it out, so the predictions are out-of-sample. An observation that no fold held out, which under a holdout partition is most of them, comes back NaN . With L regularization strengths yFit has one column per strength. # name: # type: sq_string # elements: 1 # length: 45 Out-of-fold prediction for every observation. # name: # type: sq_string # elements: 1 # length: 26 RegressionPartitionedModel # name: # type: sq_string # elements: 1 # length: 990 statistics: obj = RegressionPartitionedModel ( Mdl , Partition ) Create a RegressionPartitionedModel object, a regression model cross validated over a partition of its training data. obj = RegressionPartitionedModel ( Mdl , Partition ) refits Mdl once per fold of Partition , each time on the observations that fold holds out of its test set, and stores the compact form of every fit in Trained . It is normally reached through crossval ( Mdl ) rather than called directly. Mdl must be a RegressionGAM , a RegressionNeuralNetwork , or a RegressionSVM object. Partition must be a cvpartition object over as many observations as Mdl was trained on. Every observation is held out by exactly one fold under k -fold or leave-one-out partitioning, so kfoldPredict can answer for it with a model that never saw it. Under a holdout partition only the test set is answered for, and the rest come back NaN . See also: crossval, cvpartition, RegressionGAM, RegressionNeuralNetwork, RegressionSVM # name: # type: sq_string # elements: 1 # length: 117 Create a RegressionPartitionedModel object, a regression model cross validated over a partition of its training data. # name: # type: sq_string # elements: 1 # length: 35 RegressionPartitionedModel.BinEdges # name: # type: sq_string # elements: 1 # length: 535 RegressionPartitionedModel: property BinEdges Bin edges of the predictors A cell array with one entry per predictor, holding that predictor’s bin edges where the learner discretized it before fitting. It is carried over from the model that was cross validated, and is empty whenever that model did no binning, which is every learner this package implements: MATLAB fills it only for its generalized additive model, which bins because it is built from boosted trees where ours is built from splines. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Bin edges of the predictors # name: # type: sq_string # elements: 1 # length: 48 RegressionPartitionedModel.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 186 RegressionPartitionedModel: property CategoricalPredictors Indices of the categorical predictors A numeric vector of column indices, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 46 RegressionPartitionedModel.CrossValidatedModel # name: # type: sq_string # elements: 1 # length: 358 RegressionPartitionedModel: property CrossValidatedModel Name of the cross-validated model A character vector holding the short name of the learner that was cross validated, as MATLAB reports it: 'GAM' , 'GP' , 'NeuralNetwork' or 'SVM' . It is not the class name of that learner, and the classification side uses the same names. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Name of the cross-validated model # name: # type: sq_string # elements: 1 # length: 49 RegressionPartitionedModel.IsStandardDeviationFit # name: # type: sq_string # elements: 1 # length: 830 RegressionPartitionedModel: property IsStandardDeviationFit Whether the folds fitted a standard deviation model A logical scalar for a generalized additive model backing, taken from the model that was cross validated, and empty for every other backing. MATLAB carries this on RegressionPartitionedGAM , one of five per-learner partitioned classes this package deliberately does not have (see crossval ). With one class serving every backing the property has to be declared for all of them, so it is empty where it does not apply. It is placed last rather than first, where MATLAB’s subclass shows it, because that subclass also moves ResponseTransform to the end and no single order can match both of MATLAB’s classes; matching the general one and appending is the only coherent choice. This property is read-only. # name: # type: sq_string # elements: 1 # length: 51 Whether the folds fitted a standard deviation model # name: # type: sq_string # elements: 1 # length: 32 RegressionPartitionedModel.KFold # name: # type: sq_string # elements: 1 # length: 115 RegressionPartitionedModel: property KFold Number of folds A positive integer scalar. This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Number of folds # name: # type: sq_string # elements: 1 # length: 42 RegressionPartitionedModel.ModelParameters # name: # type: sq_string # elements: 1 # length: 1111 RegressionPartitionedModel: property ModelParameters Parameters the folds were fitted with A structure holding the parameters the folds were fitted with, carried through from the learner that was cross validated, beside NLearn , the number of folds, and the Version , Method and Type tags of this class. The learner’s own tags are replaced rather than kept, so a cross-validated SVM reports Method as 'PartitionedModel' and not 'SVM' . Deviation from MATLAB. MATLAB reports the parameter record of the cross-validation ensemble here rather than of the learner, so it says nothing at all about how the folds were fitted: of its eighteen fields only the fold count, its partitioner and a fit template carry anything, and the rest are boosting settings left inert. Nor can the parameters be reached through the folds, a compact model carrying none in MATLAB. This class reports the fit instead, which is strictly more than MATLAB offers, and everything MATLAB’s record does carry is published here as the KFold , Partition , X , Y , W and CrossValidatedModel properties. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Parameters the folds were fitted with # name: # type: sq_string # elements: 1 # length: 42 RegressionPartitionedModel.NumObservations # name: # type: sq_string # elements: 1 # length: 132 RegressionPartitionedModel: property NumObservations Number of observations A positive integer scalar. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Number of observations # name: # type: sq_string # elements: 1 # length: 44 RegressionPartitionedModel.NumTrainedPerFold # name: # type: sq_string # elements: 1 # length: 724 RegressionPartitionedModel: property NumTrainedPerFold How many trees each fold fitted A scalar structure with fields PredictorTrees and InteractionTrees , each a row with one entry per fold, for a generalized additive model backing, and empty for every other. It reports what each fold actually fitted, which the budget in ModelParameters does not: a phase stops early when it can no longer improve the fit, and the folds need not stop at the same place. MATLAB carries this on its per-learner partitioned GAM classes, which this package deliberately does not have (see crossval ), so like IsStandardDeviationFit it is declared here for every backing and left empty where it does not apply. This property is read-only. # name: # type: sq_string # elements: 1 # length: 31 How many trees each fold fitted # name: # type: sq_string # elements: 1 # length: 36 RegressionPartitionedModel.Partition # name: # type: sq_string # elements: 1 # length: 132 RegressionPartitionedModel: property Partition The partition the folds came from A cvpartition object. This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 The partition the folds came from # name: # type: sq_string # elements: 1 # length: 41 RegressionPartitionedModel.PredictorNames # name: # type: sq_string # elements: 1 # length: 140 RegressionPartitionedModel: property PredictorNames Names of the predictors A cell array of character vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Names of the predictors # name: # type: sq_string # elements: 1 # length: 53 RegressionPartitionedModel.RegressionPartitionedModel # name: # type: sq_string # elements: 1 # length: 243 RegressionPartitionedModel: obj = RegressionPartitionedModel ( Mdl , Partition ) Create a RegressionPartitionedModel object. See the class documentation for what it holds and how it is reached. See also: crossval, RegressionPartitionedModel # name: # type: sq_string # elements: 1 # length: 43 Create a RegressionPartitionedModel object. # name: # type: sq_string # elements: 1 # length: 39 RegressionPartitionedModel.ResponseName # name: # type: sq_string # elements: 1 # length: 129 RegressionPartitionedModel: property ResponseName Name of the response variable A character vector. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Name of the response variable # name: # type: sq_string # elements: 1 # length: 44 RegressionPartitionedModel.ResponseTransform # name: # type: sq_string # elements: 1 # length: 206 RegressionPartitionedModel: property ResponseTransform Transformation applied to the predicted response A function handle, carried over from the model that was cross validated. This property is read-only. # name: # type: sq_string # elements: 1 # length: 48 Transformation applied to the predicted response # name: # type: sq_string # elements: 1 # length: 34 RegressionPartitionedModel.Trained # name: # type: sq_string # elements: 1 # length: 219 RegressionPartitionedModel: property Trained The models fitted to each fold A cell array with one compact model per fold, each fitted on the observations its fold holds out of the test set. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 The models fitted to each fold # name: # type: sq_string # elements: 1 # length: 28 RegressionPartitionedModel.W # name: # type: sq_string # elements: 1 # length: 144 RegressionPartitionedModel: property W Observation weights A numeric column vector with one entry per observation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 28 RegressionPartitionedModel.X # name: # type: sq_string # elements: 1 # length: 201 RegressionPartitionedModel: property X Predictor data A numeric matrix holding the observations the model was trained on, the rows carrying missing values already removed. This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Predictor data # name: # type: sq_string # elements: 1 # length: 28 RegressionPartitionedModel.Y # name: # type: sq_string # elements: 1 # length: 136 RegressionPartitionedModel: property Y Response data A numeric column vector with one entry per row of X . This property is read-only. # name: # type: sq_string # elements: 1 # length: 13 Response data # name: # type: sq_string # elements: 1 # length: 36 RegressionPartitionedModel.kfoldLoss # name: # type: sq_string # elements: 1 # length: 1044 RegressionPartitionedModel: L = kfoldLoss ( obj ) RegressionPartitionedModel: L = kfoldLoss (…, name , value ) Compute the cross-validated regression loss. L = kfoldLoss ( obj ) returns the weighted mean squared error between the response and the out-of-fold predictions of kfoldPredict , over every observation some fold tests. obj must be a RegressionPartitionedModel class object. L = kfoldLoss (…, name , value ) accepts the following Name-Value pairs. Name Value 'LossFun' 'mse' , the default, 'epsiloninsensitive' , or a function handle called as lossfun ( Y , yFit , W ) returning a scalar. The epsilon -insensitive loss belongs to a support vector model and is refused for any other, there being no tube to measure against. 'Mode' 'average' , the default, which returns one number over the observations of every fold asked for, or 'individual' , which returns one number per fold. 'Folds' A vector of fold indices to restrict the loss to. It defaults to every fold. See also: RegressionPartitionedModel, kfoldPredict # name: # type: sq_string # elements: 1 # length: 44 Compute the cross-validated regression loss. # name: # type: sq_string # elements: 1 # length: 39 RegressionPartitionedModel.kfoldPredict # name: # type: sq_string # elements: 1 # length: 1426 RegressionPartitionedModel: yFit = kfoldPredict ( obj ) RegressionPartitionedModel: [ yFit , ySD , yInt ] = kfoldPredict ( obj ) RegressionPartitionedModel: […] = kfoldPredict (…, 'Alpha' , alpha ) Predict the response of every observation from the fold that held it out. yFit = kfoldPredict ( obj ) returns a column vector with one entry per observation, each predicted by the fold’s model that did not see it during training. An observation no fold tests, which a holdout partition leaves outside its test set, comes back NaN . obj must be a RegressionPartitionedModel class object. [ yFit , ySD , yInt ] = kfoldPredict ( obj ) also returns the standard deviation ySD of each predicted response and the two-column matrix yInt of prediction intervals, each answered for by the fold that held the observation out. A RegressionGP backing is the only one that fits the uncertainty its predictions carry, so any other raises here. An untested observation is NaN in all three. […] = kfoldPredict (…, 'Alpha' , alpha ) sets the significance level of the prediction intervals, which default to 95 per cent at an alpha of 0.05. ySD does not follow ResponseTransform and the other two outputs do, the same rule RegressionGP.predict applies: a predicted response and an interval endpoint are on the response scale and a standard deviation is not. See also: RegressionPartitionedModel, kfoldLoss # name: # type: sq_string # elements: 1 # length: 73 Predict the response of every observation from the fold that held it out. # name: # type: sq_string # elements: 1 # length: 35 RegressionPartitionedModel.kfoldfun # name: # type: sq_string # elements: 1 # length: 774 RegressionPartitionedModel: vals = kfoldfun ( obj , fun ) Apply a function to each fold of a cross-validated model. vals = kfoldfun ( obj , fun ) calls fun once per fold and returns a K×M numeric matrix whose row k is what fun returned for fold k . fun is a function handle taking seven inputs and returning a numeric vector of the same length every time it is called: testvals = fun ( M , Xtrain , Ytrain , Wtrain , … Xtest , Ytest , Wtest ) M is the model the fold was fitted with, taken from obj .Trained{k} ; Xtrain , Ytrain and Wtrain are the predictors, response and weights of the observations that fold was trained on, and Xtest , Ytest and Wtest those of the observations it held out. See also: RegressionPartitionedModel, kfoldPredict, kfoldLoss # name: # type: sq_string # elements: 1 # length: 57 Apply a function to each fold of a cross-validated model. # name: # type: sq_string # elements: 1 # length: 13 RegressionSVM # name: # type: sq_string # elements: 1 # length: 3168 statistics: obj = RegressionSVM ( X , Y ) statistics: obj = RegressionSVM (…, name , value ) Create a RegressionSVM object containing a support vector machine regression model. obj = RegressionSVM ( X , Y ) returns a support vector regression model, obj , with X being the predictor data and Y the continuous response of the observations in X . X must be an NxP numeric matrix of predictor data, where rows correspond to observations and columns to features. Y must be an Nx1 numeric vector holding the response of the corresponding predictor data in X . Y must have the same number of rows as X . The model is fitted by epsilon -insensitive regression: errors smaller than Epsilon cost nothing, so only the observations outside that tube become support vectors. Epsilon defaults to iqr ( Y ) / 13.49 , a robust estimate of a tenth of the response’s standard deviation, which is what MATLAB uses. obj = RegressionSVM (…, name , value ) returns a model with additional options specified by Name-Value pair arguments listed below. Name Value 'Standardize' A logical scalar specifying whether the predictor data should be centred and scaled before training. The same transformation is applied by predict . The default is false . 'PredictorNames' A cell array of character vectors naming the predictors, in the order they appear in X . 'ResponseName' A character vector naming the response. The default is 'Y' . 'ResponseTransform' A character vector naming one of the supported transformations, or a function handle, applied to the predicted response by predict and resubPredict . The default is 'none' . 'Epsilon' A non-negative scalar, the half-width of the insensitive tube. The default is iqr ( Y ) / 13.49 , or 0.1 where that is zero. 'BoxConstraint' A positive scalar bounding the dual coefficients, the cost of an error outside the tube. The default is 1. 'KernelFunction' A character vector naming the kernel, one of 'linear' , the default, 'rbf' , 'gaussian' , 'polynomial' or 'sigmoid' . 'PolynomialOrder' A positive integer, the order of the polynomial kernel. The default is 3. It is ignored by every other kernel. 'KernelScale' A positive scalar dividing the predictors before the kernel is applied. The default is 1. 'KernelOffset' A non-negative scalar added to the kernel value. The default is 0. 'SVMtype' A character vector selecting the formulation, either 'eps_svr' , the default, or 'nu_svr' . MATLAB fits only the epsilon form; 'nu_svr' is an Octave extension, in which Nu bounds the fraction of support vectors and Epsilon is determined by the fit rather than given. 'Nu' A scalar in (0, 1] used by 'nu_svr' . The default is 0.5. 'CacheSize' A positive scalar, the kernel cache in megabytes. The default is 1000. 'Tolerance' A non-negative scalar, the tolerance of the termination criterion. The default is 1e-6 . 'Shrinking' Either 0 or 1, whether to use the shrinking heuristic. The default is 1. The supported values for 'ResponseTransform' are: Value Description 'none' x (no transformation) 'identity' x (no transformation) 'exp' exp (x) 'log' log (x) See also: fitrsvm, ClassificationSVM, RegressionNeuralNetwork # name: # type: sq_string # elements: 1 # length: 83 Create a RegressionSVM object containing a support vector machine regression model. # name: # type: sq_string # elements: 1 # length: 19 RegressionSVM.Alpha # name: # type: sq_string # elements: 1 # length: 418 RegressionSVM: property Alpha Dual coefficients of the support vectors A numeric column vector with one entry per support vector, holding the difference of the two multipliers each observation carries. Unlike a classifier’s, these are signed: there are no labels to take the sign into, so an observation above the tube and one below it are told apart by the sign of its coefficient. This property is read-only. # name: # type: sq_string # elements: 1 # length: 40 Dual coefficients of the support vectors # name: # type: sq_string # elements: 1 # length: 18 RegressionSVM.Beta # name: # type: sq_string # elements: 1 # length: 275 RegressionSVM: property Beta Primal coefficients, one per predictor A numeric column vector, equal to obj.SupportVectors' * obj.Alpha . It exists only for a linear kernel; for any other kernel there is no primal representation and this is empty. This property is read-only. # name: # type: sq_string # elements: 1 # length: 38 Primal coefficients, one per predictor # name: # type: sq_string # elements: 1 # length: 18 RegressionSVM.Bias # name: # type: sq_string # elements: 1 # length: 174 RegressionSVM: property Bias Intercept of the fitted function A numeric scalar. With a linear kernel the prediction is X * obj.Beta + obj.Bias . This property is read-only. # name: # type: sq_string # elements: 1 # length: 32 Intercept of the fitted function # name: # type: sq_string # elements: 1 # length: 22 RegressionSVM.BinEdges # name: # type: sq_string # elements: 1 # length: 361 RegressionSVM: property BinEdges Bin edges of the predictors A cell array with one entry per predictor, holding that predictor’s bin edges where the learner discretized it before fitting. It is empty here and stays empty: this learner fits the predictors as they are, and MATLAB’s reports an empty cell for it as well. This property is read-only. # name: # type: sq_string # elements: 1 # length: 27 Bin edges of the predictors # name: # type: sq_string # elements: 1 # length: 28 RegressionSVM.BoxConstraints # name: # type: sq_string # elements: 1 # length: 265 RegressionSVM: property BoxConstraints Box constraints A numeric column vector with one entry per observation, holding the box constraint the fit applied to it. A regression has no classes to reweight, so every entry is BoxConstraint . This property is read-only. # name: # type: sq_string # elements: 1 # length: 15 Box constraints # name: # type: sq_string # elements: 1 # length: 35 RegressionSVM.CategoricalPredictors # name: # type: sq_string # elements: 1 # length: 173 RegressionSVM: property CategoricalPredictors Indices of the categorical predictors A numeric vector of column indices, and empty when none is. This property is read-only. # name: # type: sq_string # elements: 1 # length: 37 Indices of the categorical predictors # name: # type: sq_string # elements: 1 # length: 21 RegressionSVM.Epsilon # name: # type: sq_string # elements: 1 # length: 226 RegressionSVM: property Epsilon Half-width of the insensitive tube A non-negative scalar. An error smaller than Epsilon costs nothing, so only observations outside the tube become support vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 34 Half-width of the insensitive tube # name: # type: sq_string # elements: 1 # length: 36 RegressionSVM.ExpandedPredictorNames # name: # type: sq_string # elements: 1 # length: 162 RegressionSVM: property ExpandedPredictorNames Names of the predictors as the model expanded them A cell array of character vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 50 Names of the predictors as the model expanded them # name: # type: sq_string # elements: 1 # length: 47 RegressionSVM.HyperparameterOptimizationResults # name: # type: sq_string # elements: 1 # length: 356 RegressionSVM: property HyperparameterOptimizationResults Results of the hyperparameter optimization Always empty. It is declared for MATLAB compatibility, where it holds what an automatic search over the hyperparameters found. This class fits the parameters it is given and runs no such search, so there is nothing to report. This property is read-only. # name: # type: sq_string # elements: 1 # length: 42 Results of the hyperparameter optimization # name: # type: sq_string # elements: 1 # length: 29 RegressionSVM.IsSupportVector # name: # type: sq_string # elements: 1 # length: 182 RegressionSVM: property IsSupportVector Which training observations are support vectors A logical column vector with one entry per training observation. This property is read-only. # name: # type: sq_string # elements: 1 # length: 47 Which training observations are support vectors # name: # type: sq_string # elements: 1 # length: 30 RegressionSVM.KernelParameters # name: # type: sq_string # elements: 1 # length: 368 RegressionSVM: property KernelParameters Parameters of the kernel function A structure with fields Function and Scale , and Order for a polynomial kernel. Function names the kernel as MATLAB names it, so a radial basis kernel reports 'gaussian' whichever spelling was given; the kernel the fit was handed is unchanged in ModelParameters . This property is read-only. # name: # type: sq_string # elements: 1 # length: 33 Parameters of the kernel function # name: # type: sq_string # elements: 1 # length: 29 RegressionSVM.ModelParameters # name: # type: sq_string # elements: 1 # length: 734 RegressionSVM: property ModelParameters Parameters the model was fitted with A structure holding the SVM formulation, the kernel and its parameters, the box constraint, Epsilon and the solver settings. The engine is LIBSVM and the record is LIBSVM’s, so SVMtype names its formulation and Tolerance and Shrinking are its own controls; the parameters MathWorks reports for its SMO and ISDA solvers are absent, this class running neither. KernelPolynomialOrder belongs to the polynomial kernel alone and is empty under every other, as it is in MATLAB. Nu is reported here where MATLAB leaves it empty on a regression model, this class offering 'nu_svr' through SVMtype and the value being a real one. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Parameters the model was fitted with # name: # type: sq_string # elements: 1 # length: 16 RegressionSVM.Mu # name: # type: sq_string # elements: 1 # length: 201 RegressionSVM: property Mu Mean of the predictors A row vector with one entry per predictor, used for standardization. Empty when the predictor data were not standardized. This property is read-only. # name: # type: sq_string # elements: 1 # length: 22 Mean of the predictors # name: # type: sq_string # elements: 1 # length: 29 RegressionSVM.NumObservations # name: # type: sq_string # elements: 1 # length: 211 RegressionSVM: property NumObservations Number of observations used to train the model A positive integer scalar, counting only the rows that survived the removal of missing values. This property is read-only. # name: # type: sq_string # elements: 1 # length: 46 Number of observations used to train the model # name: # type: sq_string # elements: 1 # length: 27 RegressionSVM.NumPredictors # name: # type: sq_string # elements: 1 # length: 115 RegressionSVM: property NumPredictors Number of predictors A positive integer scalar. This property is read-only. # name: # type: sq_string # elements: 1 # length: 20 Number of predictors # name: # type: sq_string # elements: 1 # length: 28 RegressionSVM.PredictorNames # name: # type: sq_string # elements: 1 # length: 127 RegressionSVM: property PredictorNames Names of the predictors A cell array of character vectors. This property is read-only. # name: # type: sq_string # elements: 1 # length: 23 Names of the predictors # name: # type: sq_string # elements: 1 # length: 27 RegressionSVM.RegressionSVM # name: # type: sq_string # elements: 1 # length: 288 RegressionSVM: obj = RegressionSVM ( X , Y ) RegressionSVM: obj = RegressionSVM (…, name , value ) Create a RegressionSVM object containing a support vector machine regression model. See the class documentation for the accepted Name-Value pairs. See also: fitrsvm, RegressionSVM # name: # type: sq_string # elements: 1 # length: 83 Create a RegressionSVM object containing a support vector machine regression model. # name: # type: sq_string # elements: 1 # length: 26 RegressionSVM.ResponseName # name: # type: sq_string # elements: 1 # length: 116 RegressionSVM: property ResponseName Name of the response variable A character vector. This property is read-only. # name: # type: sq_string # elements: 1 # length: 29 Name of the response variable # name: # type: sq_string # elements: 1 # length: 31 RegressionSVM.ResponseTransform # name: # type: sq_string # elements: 1 # length: 304 RegressionSVM: property ResponseTransform Transformation applied to the predicted response A function handle, applied by predict and resubPredict to the model’s output. It defaults to the identity and may be set after construction, either to a handle or to the name of a supported transformation. # name: # type: sq_string # elements: 1 # length: 48 Transformation applied to the predicted response # name: # type: sq_string # elements: 1 # length: 22 RegressionSVM.RowsUsed # name: # type: sq_string # elements: 1 # length: 378 RegressionSVM: property RowsUsed Rows used for fitting A logical column vector with the same length as the observations in the original predictor data X , true for each row that was used for fitting the RegressionSVM model. It is empty, [] , when every observation was used, so a non-empty value means that rows holding missing values were dropped. This property is read-only. # name: # type: sq_string # elements: 1 # length: 21 Rows used for fitting # name: # type: sq_string # elements: 1 # length: 19 RegressionSVM.Sigma # name: # type: sq_string # elements: 1 # length: 218 RegressionSVM: property Sigma Standard deviation of the predictors A row vector with one entry per predictor, used for standardization. Empty when the predictor data were not standardized. This property is read-only. # name: # type: sq_string # elements: 1 # length: 36 Standard deviation of the predictors # name: # type: sq_string # elements: 1 # length: 28 RegressionSVM.SupportVectors # name: # type: sq_string # elements: 1 # length: 224 RegressionSVM: property SupportVectors The support vectors themselves A numeric matrix with one row per support vector, on the scale the model was trained on, standardized where Mu is non-empty. This property is read-only. # name: # type: sq_string # elements: 1 # length: 30 The support vectors themselves # name: # type: sq_string # elements: 1 # length: 15 RegressionSVM.W # name: # type: sq_string # elements: 1 # length: 188 RegressionSVM: property W Observation weights A numeric column vector with one entry per training observation, normalized to sum to one, as MATLAB reports it. This property is read-only. # name: # type: sq_string # elements: 1 # length: 19 Observation weights # name: # type: sq_string # elements: 1 # length: 15 RegressionSVM.X # name: # type: sq_string # elements: 1 # length: 132 RegressionSVM: property X Predictor data An NxP numeric matrix, as it was supplied to the constructor. This property is read-only. # name: # type: sq_string # elements: 1 # length: 14 Predictor data # name: # type: sq_string # elements: 1 # length: 15 RegressionSVM.Y # name: # type: sq_string # elements: 1 # length: 131 RegressionSVM: property Y Response data An Nx1 numeric vector, as it was supplied to the constructor. This property is read-only. # name: # type: sq_string # elements: 1 # length: 13 Response data # name: # type: sq_string # elements: 1 # length: 21 RegressionSVM.compact # name: # type: sq_string # elements: 1 # length: 359 RegressionSVM: CMdl = compact ( obj ) Create a CompactRegressionSVM object. CMdl = compact ( obj ) returns a compact version of the RegressionSVM object obj , which keeps the support vectors and their coefficients but drops the training data, so it predicts identically while carrying no observations. See also: fitrsvm, RegressionSVM, CompactRegressionSVM # name: # type: sq_string # elements: 1 # length: 37 Create a CompactRegressionSVM object. # name: # type: sq_string # elements: 1 # length: 22 RegressionSVM.crossval # name: # type: sq_string # elements: 1 # length: 869 RegressionSVM: CVMdl = crossval ( obj ) RegressionSVM: CVMdl = crossval (…, name , value ) Cross validate a support vector regression model. CVMdl = crossval ( obj ) returns a RegressionPartitionedModel holding one refit of obj per fold of a ten-fold partition, or of an n -fold one where the model has fewer than ten observations. obj must be a RegressionSVM class object. CVMdl = crossval (…, name , value ) accepts one, and only one, of the following Name-Value pairs. Name Value 'KFold' An integer greater than 1, the number of folds. 'Holdout' A scalar in (0, 1) , the fraction of observations held out for testing. 'Leaveout' 'on' or 'off' , whether to hold out one observation at a time. 'CVPartition' A cvpartition object over as many observations as the model was trained on. See also: RegressionSVM, RegressionPartitionedModel, cvpartition # name: # type: sq_string # elements: 1 # length: 49 Cross validate a support vector regression model. # name: # type: sq_string # elements: 1 # length: 35 RegressionSVM.discardSupportVectors # name: # type: sq_string # elements: 1 # length: 591 RegressionSVM: obj = discardSupportVectors ( obj ) Discard the support vectors of a linear SVM model. obj = discardSupportVectors ( obj ) empties Alpha and SupportVectors , leaving Beta and Bias to decide every prediction. A linear kernel needs nothing else, so the returned model predicts what it predicted before while carrying one vector in place of many. The kernel must be linear. Under any other the support vectors are part of the decision function and cannot be dropped. Discarding twice is not an error and changes nothing. See also: fitrsvm, RegressionSVM, CompactRegressionSVM # name: # type: sq_string # elements: 1 # length: 50 Discard the support vectors of a linear SVM model. # name: # type: sq_string # elements: 1 # length: 18 RegressionSVM.loss # name: # type: sq_string # elements: 1 # length: 1078 RegressionSVM: L = loss ( obj , X , Y ) RegressionSVM: L = loss (…, name , value ) Compute the regression loss of a support vector machine model. L = loss ( obj , X , Y ) returns the weighted mean squared error between the response Y and the response the model predicts for X . obj must be a RegressionSVM class object. X must be a numeric matrix with the same number of predictors as the data the model was trained on. Y must be a numeric vector with as many rows as X . L = loss (…, name , value ) accepts the following Name-Value pairs. Name Value 'LossFun' 'mse' , the default, 'epsiloninsensitive' , or a function handle called as lossfun ( Y , yFit , W ) returning a scalar. The epsilon -insensitive loss charges nothing for an error inside the tube, max (0, abs ( Y - yFit ) - Epsilon) , which is the quantity the fit itself minimizes. 'Weights' A numeric vector of observation weights with one entry per row of X . It defaults to a uniform weight. The weights are normalized to sum to one before the loss is formed. See also: RegressionSVM, fitrsvm # name: # type: sq_string # elements: 1 # length: 62 Compute the regression loss of a support vector machine model. # name: # type: sq_string # elements: 1 # length: 21 RegressionSVM.predict # name: # type: sq_string # elements: 1 # length: 510 RegressionSVM: yFit = predict ( obj , XC ) Predict the response for new data with a support vector regression model. yFit = predict ( obj , XC ) returns a column vector holding the predicted response for each row of XC . obj must be a RegressionSVM class object. XC must be a numeric matrix with the same number of predictors as the data the model was trained on. The transformation named by ResponseTransform is applied to the model’s output before it is returned. See also: RegressionSVM, fitrsvm # name: # type: sq_string # elements: 1 # length: 73 Predict the response for new data with a support vector regression model. # name: # type: sq_string # elements: 1 # length: 23 RegressionSVM.resubLoss # name: # type: sq_string # elements: 1 # length: 401 RegressionSVM: L = resubLoss ( obj ) RegressionSVM: L = resubLoss (…, name , value ) Compute the resubstitution regression loss of a support vector machine model. L = resubLoss ( obj ) returns the weighted mean squared error of the model on the data it was trained on. It accepts the same Name-Value pairs as loss . obj must be a RegressionSVM class object. See also: RegressionSVM, fitrsvm # name: # type: sq_string # elements: 1 # length: 77 Compute the resubstitution regression loss of a support vector machine model. # name: # type: sq_string # elements: 1 # length: 26 RegressionSVM.resubPredict # name: # type: sq_string # elements: 1 # length: 334 RegressionSVM: yFit = resubPredict ( obj ) Predict the response of the training data with a support vector regression model. yFit = resubPredict ( obj ) returns a column vector holding the predicted response for every observation the model was trained on. obj must be a RegressionSVM class object. See also: RegressionSVM, fitrsvm # name: # type: sq_string # elements: 1 # length: 81 Predict the response of the training data with a support vector regression model. # name: # type: sq_string # elements: 1 # length: 23 RegressionSVM.savemodel # name: # type: sq_string # elements: 1 # length: 299 RegressionSVM: savemodel ( obj , filename ) Save a support vector regression model to a file. savemodel ( obj , filename ) saves every property of the RegressionSVM object obj into filename in binary format, so that it can be read back with loadmodel . See also: loadmodel, RegressionSVM, fitrsvm # name: # type: sq_string # elements: 1 # length: 49 Save a support vector regression model to a file. # name: # type: sq_string # elements: 1 # length: 9 fitcdiscr # name: # type: sq_string # elements: 1 # length: 3115 statistics: Mdl = fitcdiscr ( X , Y ) statistics: Mdl = fitcdiscr (…, name , value ) Fit a Linear Discriminant Analysis classification model. Mdl = fitcdiscr ( X , Y ) returns a Linear Discriminant Analysis (LDA) classification model, Mdl , with X being the predictor data, and Y the class labels of observations in X . X must be a N×P numeric matrix of predictor data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y can be numerical, logical, char array or cell array of character vectors. Y must have same number of rows as X . Mdl = fitcdiscr (…, name , value ) returns a Linear Discriminant Analysis model with additional options specified by Name-Value pair arguments listed below. Model Parameters Name Value 'PredictorNames' A cell array of character vectors specifying the names of the predictors. The length of this array must match the number of columns in X . 'ResponseName' A character vector specifying the name of the response variable. 'ClassNames' Names of the classes in the class labels, Y , used for fitting the Discriminant model. ClassNames are of the same type as the class labels in Y . 'Prior' A numeric vector specifying the prior probabilities for each class. The order of the elements in Prior corresponds to the order of the classes in ClassNames . Alternatively, you can specify 'empirical' to use the empirical class probabilities or 'uniform' to assume equal class probabilities. 'Cost' A N×R numeric matrix containing misclassification cost for the corresponding instances in X where R is the number of unique categories in Y . If an instance is correctly classified into its category the cost is calculated to be 1, otherwise 0. cost matrix can be altered use Mdl.cost = somecost . default value cost = ones(rows(X),numel(unique(Y))) . 'DiscrimType' A character vector naming the type of discriminant analysis to perform, one of 'linear' (default), 'quadratic' , 'diagLinear' , 'diagQuadratic' , 'pseudoLinear' or 'pseudoQuadratic' . A linear type pools one covariance across the classes and a quadratic type estimates one per class; a 'diag' type keeps only the variances, and a 'pseudo' type inverts a singular covariance rather than refusing it. The property may be reassigned after fitting, but only within its own family, since the family decides which covariances the fit estimates. 'FillCoeffs' A character vector or string scalar with values 'on' or 'off' specifying whether to fill the coefficients after fitting. If set to 'on' , the coefficients are computed during model fitting, which can be useful for prediction. 'Gamma' A numeric scalar specifying the regularization parameter for the covariance matrix. It adjusts the linear discriminant analysis to make the model more stable in the presence of multicollinearity or small sample sizes. A value of 0 corresponds to no regularization, while a value of 1 corresponds to a completely regularized model. See also: ClassificationDiscriminant # name: # type: sq_string # elements: 1 # length: 56 Fit a Linear Discriminant Analysis classification model. # name: # type: sq_string # elements: 1 # length: 7 fitcgam # name: # type: sq_string # elements: 1 # length: 6651 statistics: Mdl = fitcgam ( X , Y ) statistics: Mdl = fitcgam (…, name , value ) Fit a Generalized Additive Model (GAM) for binary classification. Mdl = fitcgam ( X , Y ) returns a GAM classification model, Mdl , with X being the predictor data, and Y the binary class labels of observations in X . X must be a N×P numeric matrix of predictor data where rows correspond to observations and columns correspond to features or variables. Y is N×1 numeric vector containing binary class labels, typically 0 or 1. Mdl = fitcgam (…, name , value ) returns a GAM classification model with additional options specified by Name-Value pair arguments listed below. Model Parameters Name Value 'FitMethod' A character vector selecting the weak learner, either 'boostedtrees' or 'splines' . The default is 'boostedtrees' , which boosts one shallow decision tree per predictor and is the scheme MATLAB uses. 'splines' boosts a smoothing spline per predictor instead and is an Octave extension. The two take different options and an option meant for one is refused by the other rather than ignored, so the rows below say which engine each belongs to. 'PredictorNames' A cell array of character vectors specifying the names of the predictors. The length of this array must match the number of columns in X . 'ResponseName' A character vector specifying the name of the response variable. 'ClassNames' Names of the classes in the class labels, Y , used for fitting the Discriminant model. ClassNames are of the same type as the class labels in Y . 'Cost' A N×R numeric matrix containing misclassification cost for the corresponding instances in X where R is the number of unique categories in Y . If an instance is correctly classified into its category the cost is calculated to be 1, otherwise 0. cost matrix can be altered use Mdl.cost = somecost . default value cost = ones(rows(X),numel(unique(Y))) . 'Formula' (spline option) A model specification given as a string in the form 'Y ~ terms' where Y represents the response variable and terms the predictor variables. The formula can be used to specify a subset of variables for training model. For example: 'Y ~ x1 + x2 + x3 + x4 + x1:x2 + x2:x3' specifies four linear terms for the first four columns of for predictor data, and x1:x2 and x2:x3 specify the two interaction terms for 1st-2nd and 3rd-4th columns respectively. Only these terms will be used for training the model, but X must have at least as many columns as referenced in the formula. If Predictor Variable names have been defined, then the terms in the formula must reference to those. When 'formula' is specified, all terms used for training the model are referenced in the IntMatrix field of the obj class object as a matrix containing the column indexes for each term including both the predictors and the interactions used. 'Interactions' A logical matrix, a positive integer scalar, or the string 'all' for defining the interactions between predictor variables. When given a logical matrix, it must have the same number of columns as X and each row corresponds to a different interaction term combining the predictors indexed as true . Each interaction term is appended as a column vector after the available predictor column in X . When 'all' is defined, then all possible combinations of interactions are appended in X before training. At the moment, parsing a positive integer has the same effect as the 'all' option. When 'interactions' is specified, only the interaction terms appended to X are referenced in the IntMatrix field of the obj class object. 'Knots' (spline option) A scalar or a row vector with the same columns as X . It defines the knots for fitting a polynomial when training the GAM. As a scalar, it is expanded to a row vector. The default value is 5, hence expanded to ones (1, columns (X)) * 5 . You can parse a row vector with different number of knots for each predictor variable to be fitted with, although not recommended. 'Order' (spline option) A scalar or a row vector with the same columns as X . It defines the order of the polynomial when training the GAM. As a scalar, it is expanded to a row vector. The default values is 3, hence expanded to ones (1, columns (X)) * 3 . You can parse a row vector with different number of polynomial order for each predictor variable to be fitted with, although not recommended. 'DoF' (spline option) A scalar or a row vector with the same columns as X . It defines the degrees of freedom for fitting a polynomial when training the GAM. As a scalar, it is expanded to a row vector. The default value is 8, hence expanded to ones (1, columns (X)) * 8 . You can parse a row vector with different degrees of freedom for each predictor variable to be fitted with, although not recommended. The rows above marked as spline options require 'FitMethod', 'splines' . The remaining options belong to the boosted-tree engine and require 'FitMethod', 'boostedtrees' , which is the default. Name Value 'NumTreesPerPredictor' A positive integer, the number of boosting rounds of the predictor phase. It is a budget rather than a count: a fit that stops improving ends earlier and reports so. The default is 300. 'NumTreesPerInteraction' A positive integer, the same budget for the interaction phase. The default is 100. 'MaxNumSplitsPerPredictor' A positive integer, the largest number of splits any one predictor tree may make. The default is 1, which makes each tree a stump. 'MaxNumSplitsPerInteraction' The same limit for a tree over a pair of predictors. The default is 4. 'InitialLearnRateForPredictors' A value greater than 0 and at most 1, the step a round of the predictor phase starts at. A round that fails to improve the fit is retried at half the step, so this is an initial value rather than a fixed one. The default is 1. 'InitialLearnRateForInteractions' The same for the interaction phase. The default is 1. 'MaxPValue' A value between 0 and 1. A candidate pair of predictors is kept only if its interaction test gives a p -value no larger than this. The default is 1, which keeps every pair asked for. 'Verbose' A non-negative integer. Greater than zero prints a trace of the fit. The default is 0. 'NumPrint' A positive integer, how often the trace reports: the first round and then every NumPrint rounds. The default is 10. You can parse either a 'Formula' or an 'Interactions' optional parameter. Parsing both parameters will result an error. Accordingly, you can only pass up to two parameters among 'Knots' , 'Order' , and 'DoF' to define the required polynomial for training the GAM model. See also: ClassificationGAM # name: # type: sq_string # elements: 1 # length: 65 Fit a Generalized Additive Model (GAM) for binary classification. # name: # type: sq_string # elements: 1 # length: 10 fitckernel # name: # type: sq_string # elements: 1 # length: 1171 statistics: Mdl = fitckernel ( X , Y ) statistics: Mdl = fitckernel (…, name , value ) statistics: [ Mdl , FitInfo ] = fitckernel (…) Fit a Gaussian kernel binary classifier. Mdl = fitckernel ( X , Y ) returns a ClassificationKernel object fitted to the predictor data X and the two class response Y , where X is an NxP numeric matrix and Y has as many rows as X . Mdl = fitckernel (…, name , value ) passes the given Name-Value pairs to the model. They are documented under ClassificationKernel , and the ones most often wanted are 'Learner' , 'NumExpansionDimensions' , 'KernelScale' , 'Lambda' , 'BoxConstraint' and 'Standardize' . [ Mdl , FitInfo ] = fitckernel (…) also returns a structure describing the optimization: the objective it reached, the gradient it left, and the tolerances it was given. Mdl = fitckernel (…, cvopt , value ) returns a ClassificationPartitionedKernel instead when one of 'CrossVal' , 'KFold' , 'Holdout' , 'Leaveout' and 'CVPartition' is given. A cross-validated model describes no single fit, so FitInfo is not available beside it. See also: ClassificationKernel, ClassificationLinear, fitclinear # name: # type: sq_string # elements: 1 # length: 40 Fit a Gaussian kernel binary classifier. # name: # type: sq_string # elements: 1 # length: 7 fitcknn # name: # type: sq_string # elements: 1 # length: 10489 statistics: Mdl = fitcknn ( X , Y ) statistics: Mdl = fitcknn (…, name , value ) Fit a k-Nearest Neighbor classification model. Mdl = fitcknn ( X , Y ) returns a k-Nearest Neighbor classification model, Mdl , with X being the predictor data, and Y the class labels of observations in X . X must be a N×P numeric matrix of predictor data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y can be numerical, logical, char array or cell array of character vectors. Y must have same number of rows as X . Mdl = fitcknn (…, name , value ) returns a k-Nearest Neighbor classification model with additional options specified by Name-Value pair arguments listed below. Model Parameters Name Value 'Standardize' A boolean flag indicating whether the data in X should be standardized prior to training. 'PredictorNames' A cell array of character vectors specifying the predictor variable names. The variable names are assumed to be in the same order as they appear in the training data X . 'ResponseName' A character vector specifying the name of the response variable. 'ClassNames' Names of the classes in the class labels, Y , used for fitting the kNN model. ClassNames are of the same type as the class labels in Y . 'Prior' A numeric vector specifying the prior probabilities for each class. The order of the elements in Prior corresponds to the order of the classes in ClassNames . 'Cost' A N×R numeric matrix containing misclassification cost for the corresponding instances in X where R is the number of unique categories in Y . If an instance is correctly classified into its category the cost is calculated to be 1, otherwise 0. cost matrix can be altered use Mdl.cost = somecost . default value cost = ones(rows(X),numel(unique(Y))) . 'ScoreTransform' A character vector defining one of the following functions or a user defined function handle, which is used for transforming the prediction scores returned by the predict and resubPredict methods. Default value is 'none' . Value Description 'doublelogit' 1 ./ (1 + exp (-2 × x)) 'invlogit' log (x ./ (1 - x)) 'ismax' Sets the score for the class with the largest score to 1, and sets the scores for all other classes to 0 'logit' 1 ./ (1 + exp (-x)) 'none' x (no transformation) 'identity' x (no transformation) 'sign' -1 for x < 0, 0 for x = 0, 1 for x > 0 'symmetric' 2 × x - 1 'symmetricismax' Sets the score for the class with the largest score to 1, and sets the scores for all other classes to -1 'symmetriclogit' 2 ./ (1 + exp (-x)) - 1 Name Value 'BreakTies' Tie-breaking algorithm used by predict when multiple classes have the same smallest cost. By default, ties occur when multiple classes have the same number of nearest points among the k nearest neighbors. The available options are specified by the following character arrays: Value Description 'smallest' This is the default and it favors the class with the smallest index among the tied groups, i.e. the one that appears first in the training labelled data. 'nearest' This favors the class with the nearest neighbor among the tied groups, i.e. the class with the closest member point according to the distance metric used. 'random' This randomly picks one class among the tied groups. Name Value 'BucketSize' The maximum number of data points in the leaf node of the Kd-tree and it must be a positive integer. By default, it is 50. This argument is meaningful only when the selected search method is 'kdtree' . 'NumNeighbors' A positive integer value specifying the number of nearest neighbors to be found in the kNN search. By default, it is 1. 'Exponent' A positive scalar (usually an integer) specifying the Minkowski distance exponent. This argument is only valid when the selected distance metric is 'minkowski' . By default it is 2. 'Scale' A nonnegative numeric vector specifying the scale parameters for the standardized Euclidean distance. The vector length must be equal to the number of columns in X . This argument is only valid when the selected distance metric is 'seuclidean' , in which case each coordinate of X is scaled by the corresponding element of 'scale' , as is each query point in Y . By default, the scale parameter is the standard deviation of each coordinate in X . If a variable in X is constant, i.e. zero variance, this value is forced to 1 to avoid division by zero. This is the equivalent of this variable not being standardized. 'Cov' A square matrix with the same number of columns as X specifying the covariance matrix for computing the mahalanobis distance. This must be a positive definite matrix matching. This argument is only valid when the selected distance metric is 'mahalanobis' . 'Distance' is the distance metric used by knnsearch as specified below: Value Description 'euclidean' Euclidean distance. 'seuclidean' standardized Euclidean distance. Each coordinate difference between the rows in X and the query matrix Y is scaled by dividing by the corresponding element of the standard deviation computed from X . To specify a different scaling, use the 'Scale' name-value argument. 'cityblock' City block distance. 'chebychev' Chebychev distance (maximum coordinate difference). 'minkowski' Minkowski distance. The default exponent is 2. To specify a different exponent, use the 'P' name-value argument. 'mahalanobis' Mahalanobis distance, computed using a positive definite covariance matrix. To change the value of the covariance matrix, use the 'Cov' name-value argument. 'cosine' Cosine distance. 'correlation' One minus the sample linear correlation between observations (treated as sequences of values). 'spearman' One minus the sample Spearman’s rank correlation between observations (treated as sequences of values). 'hamming' Hamming distance, which is the percentage of coordinates that differ. 'jaccard' One minus the Jaccard coefficient, which is the percentage of nonzero coordinates that differ. @distfun Custom distance function handle. A distance function of the form function D2 = distfun ( XI , YI ) , where XI is a 1×P vector containing a single observation in P -dimensional space, YI is an N×P matrix containing an arbitrary number of observations in the same P -dimensional space, and D2 is an N×P vector of distances, where ( D2 k) is the distance between observations XI and ( YI k,:) . Name Value 'DistanceWeight' A distance weighting function, specified either as a function handle, which accepts a matrix of nonnegative distances and returns a matrix the same size containing nonnegative distance weights, or one of the following values: 'equal' , which corresponds to no weighting; 'inverse' , which corresponds to a weight equal to 1/distance ; 'squaredinverse' , which corresponds to a weight equal to 1/distance^2 . 'CacheSize' A positive scalar, the cache size in megabytes, 1000 by default. It is stored and reported for compatibility and does not affect the fit or any prediction : a nearest-neighbour model keeps no Gram matrix to cache, holding its training data and computing each distance when asked. MATLAB hides the same property from properties , where this package reports it. 'IncludeTies' A boolean flag to indicate if the returned values should contain the indices that have same distance as the K^th neighbor. When false , knnsearch chooses the observation with the smallest index among the observations that have the same distance from a query point. When true , knnsearch includes all nearest neighbors whose distances are equal to the K^th smallest distance in the output arguments. To specify K , use the 'K' name-value pair argument. 'NSMethod' is the nearest neighbor search method used by knnsearch as specified below. Value Description 'kdtree' Creates and uses a Kd-tree to find nearest neighbors. 'kdtree' is the default value when the number of columns in X is less than or equal to 10, X is not sparse, and the distance metric is 'euclidean' , 'cityblock' , 'manhattan' , 'chebychev' , or 'minkowski' . Otherwise, the default value is 'exhaustive' . This argument is only valid when the distance metric is one of the four aforementioned metrics. 'exhaustive' Uses the exhaustive search algorithm by computing the distance values from all the points in X to each point in Y . Cross Validation Options Name Value 'Crossval' Cross-validation flag specified as 'on' or 'off' . If 'on' is specified, a 10-fold cross validation is performed and a ClassificationPartitionedModel is returned in Mdl . To override this cross-validation setting, use only one of the following Name-Value pair arguments. 'CVPartition' A cvpartition object that specifies the type of cross-validation and the indexing for the training and validation sets. A ClassificationPartitionedModel is returned in Mdl and the trained model is stored in the Trained property. 'Holdout' Fraction of the data used for holdout validation, specified as a scalar value in the range [0,1] . When specified, a randomly selected percentage is reserved as validation data and the remaining set is used for training. The trained model is stored in the Trained property of the ClassificationPartitionedModel returned in Mdl . 'Holdout' partitioning attempts to ensure that each partition represents the classes proportionately. 'KFold' Number of folds to use in the cross-validated model, specified as a positive integer value greater than 1. When specified, then the data is randomly partitioned in k sets and for each set, the set is reserved as validation data while the remaining k-1 sets are used for training. The trained models are stored in the Trained property of the ClassificationPartitionedModel returned in Mdl . 'KFold' partitioning attempts to ensure that each partition represents the classes proportionately. 'Leaveout' Leave-one-out cross-validation flag specified as 'on' or 'off' . If 'on' is specified, then for each of the n observations (where n is the number of observations, excluding missing observations, specified in the NumObservations property of the model), one observation is reserved as validation data while the remaining observations are used for training. The trained models are stored in the Trained property of the ClassificationPartitionedModel returned in Mdl . See also: ClassificationKNN, ClassificationPartitionedModel, knnsearch, rangesearch, pdist2 # name: # type: sq_string # elements: 1 # length: 46 Fit a k-Nearest Neighbor classification model. # name: # type: sq_string # elements: 1 # length: 10 fitclinear # name: # type: sq_string # elements: 1 # length: 1240 statistics: Mdl = fitclinear ( X , Y ) statistics: Mdl = fitclinear (…, name , value ) statistics: [ Mdl , FitInfo ] = fitclinear (…) Fit a linear binary classifier. Mdl = fitclinear ( X , Y ) returns a ClassificationLinear object fitted to the predictor data X and the two class response Y , where X is an NxP numeric matrix and Y has as many rows as X . Mdl = fitclinear (…, name , value ) passes the given Name-Value pairs to the model. They are documented under ClassificationLinear , and the ones most often wanted are 'Learner' , 'Regularization' , 'Lambda' , 'Solver' and 'ObservationsIn' . [ Mdl , FitInfo ] = fitclinear (…) also returns a structure describing the optimization: what it converged to, how far it got, and which tolerance stopped it. Its fields follow the solver, so a dual fit reports the dual variables and a mini-batch fit the batch it stopped on. Mdl = fitclinear (…, cvopt , value ) returns a ClassificationPartitionedLinear instead when one of 'CrossVal' , 'KFold' , 'Holdout' , 'Leaveout' and 'CVPartition' is given. A cross-validated model describes no single fit, so FitInfo is not available beside it. See also: ClassificationLinear, ClassificationKernel, fitckernel # name: # type: sq_string # elements: 1 # length: 31 Fit a linear binary classifier. # name: # type: sq_string # elements: 1 # length: 6 fitcnb # name: # type: sq_string # elements: 1 # length: 3518 statistics: Mdl = fitcnb ( X , Y ) statistics: Mdl = fitcnb (…, name , value ) Fit a naive Bayes classification model. Mdl = fitcnb ( X , Y ) returns a naive Bayes classification model, Mdl , with X being the predictor data and Y the class labels of the observations in X . X must be a N×P numeric matrix of predictor data where rows correspond to observations and columns correspond to features or variables. Y is an N×1 matrix or cell matrix containing the class labels of the corresponding predictor data in X . Y can be numeric, logical, a character array or a cell array of character vectors. Y must have the same number of rows as X . A naive Bayes model fits one univariate density to each predictor within each class, and treats the predictors as conditionally independent given the class. An observation’s likelihood under a class is therefore the product of its per-predictor densities, and its posterior follows by Bayes’ rule from the class prior. Mdl = fitcnb (…, name , value ) returns a naive Bayes model with additional options specified by Name-Value pair arguments listed below. Model Parameters Name Value 'PredictorNames' A cell array of character vectors specifying the names of the predictors. The length of this array must match the number of columns in X . 'ResponseName' A character vector specifying the name of the response variable. 'ClassNames' Names of the classes in the class labels, Y , used for fitting the model. ClassNames are of the same type as the class labels in Y . Naming a subset of the classes keeps only the observations belonging to them. 'Prior' A numeric vector specifying the prior probability of each class, in the order of ClassNames , or the character vector 'empirical' (default) to take the class frequencies, or 'uniform' to give every class the same probability. 'Cost' A square numeric matrix of misclassification costs, where Cost(i,j) is the cost of classifying an observation of class i into class j . The default is one off the diagonal and zero on it. 'ScoreTransform' A character vector naming a transform applied to the posterior returned by predict , or a function handle taking and returning a matrix of the same size. The default is 'none' . 'DistributionNames' A character vector naming the distribution fitted to every predictor, or a cell array of character vectors naming one per predictor. Supported are 'normal' (default), 'kernel' , 'mvmn' for a categorical predictor, and 'mn' for token counts. 'mn' describes the whole predictor vector at once and so cannot be named for only some predictors. 'Kernel' The smoothing kernel of the predictors fitted with a kernel density, one of 'normal' (default), 'box' , 'epanechnikov' or 'triangle' , given once for every predictor or once per predictor. 'Support' The support of the kernel densities, either 'unbounded' (default), 'positive' , or a two element numeric vector giving finite bounds. 'Width' The bandwidth of the kernel densities, given as a scalar, as one value per predictor, as one per class, or as a matrix of one per class and predictor. By default each density chooses its own. A predictor that takes one value throughout a class has no normal density to fit, and that combination of class and predictor is refused rather than answered. Only the combination is refused, not the model: giving that predictor a 'kernel' or a 'mvmn' distribution fits the same data, and leaves the other predictors normal. See also: ClassificationNaiveBayes # name: # type: sq_string # elements: 1 # length: 39 Fit a naive Bayes classification model. # name: # type: sq_string # elements: 1 # length: 7 fitcnet # name: # type: sq_string # elements: 1 # length: 5846 statistics: Mdl = fitcnet ( X , Y ) statistics: Mdl = fitcnet (…, name , value ) Fit a Neural Network classification model. Mdl = fitcnet ( X , Y ) returns a Neural Network classification model, Mdl , with X being the predictor data, and Y the class labels of observations in X . X must be a N×P numeric matrix of predictor data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y can contain any type of categorical data. Y must have same numbers of rows as X . Mdl = fitcnet (…, name , value ) returns a Neural Network classification model with additional options specified by Name-Value pair arguments listed below. Model Parameters Name Value 'Standardize' A boolean flag indicating whether the data in X should be standardized prior to training. 'PredictorNames' A cell array of character vectors specifying the predictor variable names. The variable names are assumed to be in the same order as they appear in the training data X . 'ResponseName' A character vector specifying the name of the response variable. 'ClassNames' Names of the classes in the class labels, Y , used for fitting the Neural Network model. ClassNames are of the same type as the class labels in Y . 'Prior' A numeric vector specifying the prior probabilities for each class. The order of the elements in Prior corresponds to the order of the classes in ClassNames . 'LayerSizes' A vector of positive integers that defines the sizes of the fully connected layers in the neural network model. Each element in LayerSizes corresponds to the number of outputs for the respective fully connected layer in the neural network model. The default value is 10. 'LearningRate' A positive scalar value that defines the learning rate during the gradient descent. Default value is 0.003. A larger rate can drive every unit of a hidden layer negative, after which a rectifier passes no gradient and the network stops training. Applies only when 'Solver' is 'sgd' . 'Solver' A character vector naming the solver that trains the network, either 'lbfgs' or 'sgd' . The default is 'lbfgs' , which minimizes the loss over the whole training set at once by limited-memory BFGS, as MATLAB does. It takes no learning rate, stops on the three tolerances below, and reaches a lower training loss in fewer passes over the data, though each of its iterations costs several passes where an epoch costs one. 'sgd' visits the samples one at a time and steps down the gradient of each, running for 'IterationLimit' epochs; it was the default before version 1.9.0. 'GradientTolerance' A nonnegative scalar. Training stops once the gradient’s infinity norm falls to or below it, which is the quantity MATLAB tests too. The default is 1e-6 . Applies only when 'Solver' is 'lbfgs' . 'StepTolerance' A nonnegative scalar. Training stops once the step’s infinity norm falls to or below it, which is the quantity MATLAB tests too. The default is 1e-6 . Applies only when 'Solver' is 'lbfgs' . 'LossTolerance' A real scalar. Training stops once the training loss falls to or below it. The test is on the loss itself and not on its change, matching MATLAB; pass -Inf to switch it off. The default is 1e-6 . Applies only when 'Solver' is 'lbfgs' . 'Activations' A character vector or a cellstr vector specifying the activation functions for the hidden layers of the neural network (excluding the output layer). The available activation functions are 'linear' , 'sigmoid' , 'relu' , 'tanh' , 'softmax' , 'lrelu' , 'prelu' , 'elu' , 'gelu' , and 'none' . The default value is 'relu' . 'OutputLayerActivation' A character vector specifying the activation function for the output layer of the neural network. The available activation functions are the same as for 'Activations' . The default value is 'softmax' , which makes the returned scores a probability over the classes and trains the network against cross entropy; any other value trains it against the mean squared error. 'IterationLimit' A positive integer scalar that specifies the maximum number of training iterations. The default value is 1000. Under 'sgd' this counts epochs, under 'lbfgs' solver iterations. 'DisplayInfo' A boolean flag indicating whether to print information during training. Default is false . 'ScoreTransform' A character vector defining one of the following functions or a user defined function handle, which is used for transforming the prediction scores returned by the predict and resubPredict methods. Default value is 'none' . Value Description 'doublelogit' 1 ./ (1 + exp (-2 × x)) 'invlogit' log (x ./ (1 - x)) 'ismax' Sets the score for the class with the largest score to 1, and sets the scores for all other classes to 0 'logit' 1 ./ (1 + exp (-x)) 'none' x (no transformation) 'identity' x (no transformation) 'sign' -1 for x < 0, 0 for x = 0, 1 for x > 0 'symmetric' 2 × x - 1 'symmetricismax' Sets the score for the class with the largest score to 1, and sets the scores for all other classes to -1 'symmetriclogit' 2 ./ (1 + exp (-x)) - 1 The weights of each layer are drawn from a uniform range whose half-width is set by that layer’s activation, and the scheme cannot be chosen: a rectifying activation ( 'relu' , 'lrelu' , 'prelu' , 'elu' , 'gelu' ) takes the He range sqrt (6 / fan_in) , because it passes only half of its input, and the remaining activations take the Glorot range sqrt (6 / (fan_in + fan_out)) , which accounts for the backward pass as well. A network whose layers do not share an activation is therefore built with both schemes. What each layer was given is reported by the LayerWeightsInitializers field of the fitted model’s ModelParameters . See also: ClassificationNeuralNetwork # name: # type: sq_string # elements: 1 # length: 42 Fit a Neural Network classification model. # name: # type: sq_string # elements: 1 # length: 7 fitcsvm # name: # type: sq_string # elements: 1 # length: 7419 statistics: Mdl = fitcsvm ( X , Y ) statistics: Mdl = fitcsvm (…, name , value ) Fit a Support Vector Machine classification model. Mdl = fitcsvm ( X , Y ) returns a Support Vector Machine classification model, Mdl , with X being the predictor data, and Y the class labels of observations in X . X must be a N×P numeric matrix of predictor data where rows correspond to observations and columns correspond to features or variables. Y is N×1 matrix or cell matrix containing the class labels of corresponding predictor data in X . Y can be numerical, logical, char array or cell array of character vectors. Y must have same number of rows as X . Mdl = fitcsvm (…, name , value ) returns a Support Vector Machine model with additional options specified by Name-Value pair arguments listed below. Model Parameters Name Value 'Standardize' A boolean flag indicating whether the data in X should be standardized prior to training. 'PredictorNames' A cell array of character vectors specifying the predictor variable names. The variable names are assumed to be in the same order as they appear in the training data X . 'ResponseName' A character vector specifying the name of the response variable. 'ClassNames' Names of the classes in the class labels, Y , used for fitting the kNN model. ClassNames are of the same type as the class labels in Y . 'SVMtype' Specifies the type of SVM used for training the ClassificationSVM model. By default, the type of SVM is defined by setting other parameters and/or by the data itself. Setting the 'SVMtype' parameter overrides the default behavior and it accepts the following options: Value Description 'C_SVC' It is the standard SVM formulation for classification tasks. It aims to find the optimal hyperplane that separates different classes by maximizing the margin between them while allowing some misclassifications. The parameter 'C' controls the trade-off between maximizing the margin and minimizing the classification error. It is the default type, unless otherwise specified. 'nu_SVC' It is a variation of the standard SVM that introduces a parameter ν (nu) as an upper bound on the fraction of margin errors and a lower bound on the fraction of support vectors. This formulation provides more control over the number of support vectors and the margin errors, making it useful for specific classification scenarios. It is the default type, when the 'OutlierFraction' parameter is set. 'one_class_SVM' It is used for anomaly detection and novelty detection tasks. It aims to separate the data points of a single class from the origin in a high-dimensional feature space. This method is particularly useful for identifying outliers or unusual patterns in the data. It is the default type, when the 'Nu' parameter is set or when there is a single class in Y . When 'one_class_SVM' is set by the 'SVMtype' pair argument, Y has no effect and any classes are ignored. Name Value 'OutlierFraction' The expected proportion of outliers in the training data, specified as a scalar value in the range [0,1] . When specified, the type of SVM model is switched to 'nu_SVC' and 'OutlierFraction' defines the ν (nu) parameter. 'KernelFunction' A character vector specifying the method for computing elements of the Gram matrix. The available kernel functions are 'gaussian' or 'rbf' , 'linear' , 'polynomial' , and 'sigmoid' . For one-class learning, the default Kernel function is 'rbf' . For two-class learning the default is 'linear' . 'PolynomialOrder' A positive integer that specifies the order of polynomial in kernel function. The default value is 3. Unless the 'KernelFunction' is set to 'polynomial' , this parameter is ignored. 'KernelScale' A positive scalar that specifies a scaling factor for the γ (gamma) parameter, which can be seen as the inverse of the radius of influence of samples selected by the model as support vectors. The γ (gamma) parameter is computed as gamma = KernelScale / (number of features) . The default value for 'KernelScale' is 1. 'KernelOffset' A nonnegative scalar that specifies the coef0 in kernel function. For the polynomial kernel, it influences the polynomial’s shift, and for the sigmoid kernel, it affects the hyperbolic tangent’s shift. The default value for 'KernelOffset' is 0. 'BoxConstraint' A positive scalar that specifies the upper bound of the Lagrange multipliers, i.e. the parameter C, which is used for training 'C_SVC' and 'one_class_SVM' type of models. It determines the trade-off between maximizing the margin and minimizing the classification error. The default value for 'BoxConstraint' is 1. 'Nu' A positive scalar, in the range (0,1] that specifies the parameter ν (nu) for training 'nu_SVC' and 'one_class_SVM' type of models. Unless overridden by setting the 'SVMtype' parameter, setting the 'Nu' parameter always forces the training model type to 'one_class_SVM' , in which case, the number of classes in Y is ignored. The default value for 'Nu' is 1. 'CacheSize' A positive scalar that specifies the memory requirements (in MB) for storing the Gram matrix. The default is 1000. 'Tolerance' A nonnegative scalar that specifies the tolerance of termination criterion. The default value is 1e-6. 'Shrinking' Specifies whether to use shrinking heuristics. It accepts either 0 or 1. The default value is 1. Cross Validation Options Name Value 'Crossval' Cross-validation flag specified as 'on' or 'off' . If 'on' is specified, a 10-fold cross validation is performed and a ClassificationPartitionedModel is returned in Mdl . To override this cross-validation setting, use only one of the following Name-Value pair arguments. 'CVPartition' A cvpartition object that specifies the type of cross-validation and the indexing for the training and validation sets. A ClassificationPartitionedModel is returned in Mdl and the trained model is stored in the Trained property. 'Holdout' Fraction of the data used for holdout validation, specified as a scalar value in the range [0,1] . When specified, a randomly selected percentage is reserved as validation data and the remaining set is used for training. The trained model is stored in the Trained property of the ClassificationPartitionedModel returned in Mdl . 'Holdout' partitioning attempts to ensure that each partition represents the classes proportionately. 'KFold' Number of folds to use in the cross-validated model, specified as a positive integer value greater than 1. When specified, then the data is randomly partitioned in k sets and for each set, the set is reserved as validation data while the remaining k-1 sets are used for training. The trained models are stored in the Trained property of the ClassificationPartitionedModel returned in Mdl . 'KFold' partitioning attempts to ensure that each partition represents the classes proportionately. 'Leaveout' Leave-one-out cross-validation flag specified as 'on' or 'off' . If 'on' is specified, then for each of the n observations (where n is the number of observations, excluding missing observations, specified in the NumObservations property of the model), one observation is reserved as validation data while the remaining observations are used for training. The trained models are stored in the Trained property of the ClassificationPartitionedModel returned in Mdl . See also: ClassificationSVM, ClassificationPartitionedModel, svmtrain, svmpredict # name: # type: sq_string # elements: 1 # length: 50 Fit a Support Vector Machine classification model. # name: # type: sq_string # elements: 1 # length: 7 fitrgam # name: # type: sq_string # elements: 1 # length: 6454 statistics: obj = fitrgam ( X , Y ) statistics: obj = fitrgam ( X , Y , name , value ) Fit a Generalized Additive Model (GAM) for regression. obj = fitrgam ( X , Y ) returns an object of class RegressionGAM, with matrix X containing the predictor data and vector Y containing the continuous response data. X must be a N×P numeric matrix of input data where rows correspond to observations and columns correspond to features or variables. X will be used to train the GAM model. Y must be N×1 numeric vector containing the response data corresponding to the predictor data in X . Y must have same number of rows as X . obj = fitrgam (…, name , value ) returns an object of class RegressionGAM with additional properties specified by Name-Value pair arguments listed below. Name Value 'FitMethod' A character vector selecting the weak learner, either 'boostedtrees' or 'splines' . The default is 'boostedtrees' , which boosts one shallow decision tree per predictor and is the scheme MATLAB uses. 'splines' boosts a smoothing spline per predictor instead and is an Octave extension. The two take different options and an option meant for one is refused by the other rather than ignored, so the rows below say which engine each belongs to. 'predictors' Predictor Variable names, specified as a row vector cell of strings with the same length as the columns in X . If omitted, the program will generate default variable names (x1, x2, ..., xn) for each column in X . 'responsename' Response Variable Name, specified as a string. If omitted, the default value is 'Y' . 'formula' (spline option) a model specification given as a string in the form 'Y ~ terms' where Y represents the response variable and terms the predictor variables. The formula can be used to specify a subset of variables for training model. For example: 'Y ~ x1 + x2 + x3 + x4 + x1:x2 + x2:x3' specifies four linear terms for the first four columns of for predictor data, and x1:x2 and x2:x3 specify the two interaction terms for 1st-2nd and 3rd-4th columns respectively. Only these terms will be used for training the model, but X must have at least as many columns as referenced in the formula. If Predictor Variable names have been defined, then the terms in the formula must reference to those. When 'formula' is specified, all terms used for training the model are referenced in the IntMatrix field of the obj class object as a matrix containing the column indexes for each term including both the predictors and the interactions used. 'interactions' a logical matrix, a positive integer scalar, or the string 'all' for defining the interactions between predictor variables. When given a logical matrix, it must have the same number of columns as X and each row corresponds to a different interaction term combining the predictors indexed as true . Each interaction term is appended as a column vector after the available predictor column in X . When 'all' is defined, then all possible combinations of interactions are appended in X before training. At the moment, parsing a positive integer has the same effect as the 'all' option. When 'interactions' is specified, only the interaction terms appended to X are referenced in the IntMatrix field of the obj class object. 'knots' (spline option) a scalar or a row vector with the same columns as X . It defines the knots for fitting a polynomial when training the GAM. As a scalar, it is expanded to a row vector. The default value is 5, hence expanded to ones (1, columns (X)) * 5 . You can parse a row vector with different number of knots for each predictor variable to be fitted with, although not recommended. 'order' (spline option) a scalar or a row vector with the same columns as X . It defines the order of the polynomial when training the GAM. As a scalar, it is expanded to a row vector. The default values is 3, hence expanded to ones (1, columns (X)) * 3 . You can parse a row vector with different number of polynomial order for each predictor variable to be fitted with, although not recommended. 'dof' (spline option) a scalar or a row vector with the same columns as X . It defines the degrees of freedom for fitting a polynomial when training the GAM. As a scalar, it is expanded to a row vector. The default value is 8, hence expanded to ones (1, columns (X)) * 8 . You can parse a row vector with different degrees of freedom for each predictor variable to be fitted with, although not recommended. 'tol' (spline option) a positive scalar to set the tolerance for convergence during training. By default, it is set to 1e-3 . The rows above marked as spline options require 'FitMethod', 'splines' . The remaining options belong to the boosted-tree engine and require 'FitMethod', 'boostedtrees' , which is the default. Name Value 'NumTreesPerPredictor' A positive integer, the number of boosting rounds of the predictor phase. It is a budget rather than a count: a fit that stops improving ends earlier and reports so. The default is 300. 'NumTreesPerInteraction' A positive integer, the same budget for the interaction phase. The default is 100. 'MaxNumSplitsPerPredictor' A positive integer, the largest number of splits any one predictor tree may make. The default is 1, which makes each tree a stump. 'MaxNumSplitsPerInteraction' The same limit for a tree over a pair of predictors. The default is 4. 'InitialLearnRateForPredictors' A value greater than 0 and at most 1, the step a round of the predictor phase starts at. A round that fails to improve the fit is retried at half the step, so this is an initial value rather than a fixed one. The default is 1. 'InitialLearnRateForInteractions' The same for the interaction phase. The default is 1. 'MaxPValue' A value between 0 and 1. A candidate pair of predictors is kept only if its interaction test gives a p -value no larger than this. The default is 1, which keeps every pair asked for. 'Verbose' A non-negative integer. Greater than zero prints a trace of the fit. The default is 0. 'NumPrint' A positive integer, how often the trace reports: the first round and then every NumPrint rounds. The default is 10. You can parse either a 'formula' or an 'interactions' optional parameter. Parsing both parameters will result an error. Accordingly, you can only pass up to two parameters among 'knots' , 'order' , and 'dof' to define the required polynomial for training the GAM model. See also: RegressionGAM, regress, regress_gp # name: # type: sq_string # elements: 1 # length: 54 Fit a Generalized Additive Model (GAM) for regression. # name: # type: sq_string # elements: 1 # length: 6 fitrgp # name: # type: sq_string # elements: 1 # length: 852 statistics: Mdl = fitrgp ( X , Y ) statistics: Mdl = fitrgp (…, name , value ) Fit a Gaussian process regression model. Mdl = fitrgp ( X , Y ) returns a RegressionGP object fitted to the predictor data X and the continuous response Y , where X is an NxP numeric matrix and Y an Nx1 numeric vector with as many rows as X . Mdl = fitrgp (…, name , value ) passes the given Name-Value pairs to the model. They are documented under RegressionGP , and the ones most often wanted are 'KernelFunction' , 'BasisFunction' , 'Standardize' , 'Sigma' and 'FitMethod' . When any of 'CrossVal' , 'KFold' , 'Holdout' , 'Leaveout' or 'CVPartition' is given, a cross validated model is returned instead, as a RegressionPartitionedModel . Only one of them may be given at a time. See also: RegressionGP, CompactRegressionGP, RegressionPartitionedModel # name: # type: sq_string # elements: 1 # length: 40 Fit a Gaussian process regression model. # name: # type: sq_string # elements: 1 # length: 10 fitrkernel # name: # type: sq_string # elements: 1 # length: 1170 statistics: Mdl = fitrkernel ( X , Y ) statistics: Mdl = fitrkernel (…, name , value ) statistics: [ Mdl , FitInfo ] = fitrkernel (…) Fit a Gaussian kernel regression model. Mdl = fitrkernel ( X , Y ) returns a RegressionKernel object fitted to the predictor data X and the continuous response Y , where X is an NxP numeric matrix and Y an Nx1 numeric vector with as many rows as X . Mdl = fitrkernel (…, name , value ) passes the given Name-Value pairs to the model. They are documented under RegressionKernel , and the ones most often wanted are 'Learner' , 'Epsilon' , 'NumExpansionDimensions' , 'KernelScale' , 'Lambda' and 'BoxConstraint' . [ Mdl , FitInfo ] = fitrkernel (…) also returns a structure describing the optimization: the objective it reached, the gradient it left, and the tolerances it was given. Mdl = fitrkernel (…, cvopt , value ) returns a RegressionPartitionedKernel instead when one of 'CrossVal' , 'KFold' , 'Holdout' , 'Leaveout' and 'CVPartition' is given. A cross-validated model describes no single fit, so FitInfo is not available beside it. See also: RegressionKernel, RegressionLinear, fitrlinear # name: # type: sq_string # elements: 1 # length: 39 Fit a Gaussian kernel regression model. # name: # type: sq_string # elements: 1 # length: 10 fitrlinear # name: # type: sq_string # elements: 1 # length: 1236 statistics: Mdl = fitrlinear ( X , Y ) statistics: Mdl = fitrlinear (…, name , value ) statistics: [ Mdl , FitInfo ] = fitrlinear (…) Fit a linear regression model. Mdl = fitrlinear ( X , Y ) returns a RegressionLinear object fitted to the predictor data X and the continuous response Y , where X is an NxP numeric matrix and Y an Nx1 numeric vector with as many rows as X . Mdl = fitrlinear (…, name , value ) passes the given Name-Value pairs to the model. They are documented under RegressionLinear , and the ones most often wanted are 'Learner' , 'Epsilon' , 'Regularization' , 'Lambda' and 'Solver' . [ Mdl , FitInfo ] = fitrlinear (…) also returns a structure describing the optimization: what it converged to, how far it got, and which tolerance stopped it. Its fields follow the solver, so a dual fit reports the dual variables and a mini-batch fit the batch it stopped on. Mdl = fitrlinear (…, cvopt , value ) returns a RegressionPartitionedLinear instead when one of 'CrossVal' , 'KFold' , 'Holdout' , 'Leaveout' and 'CVPartition' is given. A cross-validated model describes no single fit, so FitInfo is not available beside it. See also: RegressionLinear, RegressionKernel, fitrkernel # name: # type: sq_string # elements: 1 # length: 30 Fit a linear regression model. # name: # type: sq_string # elements: 1 # length: 7 fitrnet # name: # type: sq_string # elements: 1 # length: 4969 statistics: Mdl = fitrnet ( X , Y ) statistics: Mdl = fitrnet (…, name , value ) Fit a neural network regression model. Mdl = fitrnet ( X , Y ) returns a neural network regression model, Mdl , with X being the predictor data and Y the continuous response of the observations in X . X must be an NxP numeric matrix of predictor data, where rows correspond to observations and columns to features or variables. Y must be an Nx1 numeric vector holding the response of the corresponding predictor data in X . Y must have the same number of rows as X . The network is trained against the mean squared error and its output layer applies the identity, so a prediction is an unrestricted real number. Use fitcnet where the response names a class rather than a quantity. Mdl = fitrnet (…, name , value ) returns a neural network regression model with additional options specified by Name-Value pair arguments listed below. Model Parameters Name Value 'Standardize' A logical scalar indicating whether the data in X should be centred and scaled before training. The same transformation is applied by predict . The default is false . 'PredictorNames' A cell array of character vectors specifying the predictor variable names, in the order they appear in X . 'ResponseName' A character vector specifying the name of the response variable. The default is 'Y' . 'ResponseTransform' A character vector naming one of 'none' , 'identity' , 'exp' or 'log' , or a function handle of one argument, applied to the predicted response by predict and resubPredict . The default is 'none' . 'LayerSizes' A vector of positive integers defining the number of units in each fully connected hidden layer. The default value is 10, a single hidden layer of ten units. 'LearningRate' A positive scalar value that defines the learning rate during the gradient descent. Default value is 0.003. A larger rate can drive every unit of a hidden layer negative, after which a rectifier passes no gradient and the network stops training. Applies only when 'Solver' is 'sgd' . 'Solver' A character vector naming the solver that trains the network, either 'lbfgs' or 'sgd' . The default is 'lbfgs' , which minimizes the loss over the whole training set at once by limited-memory BFGS, as MATLAB does. It takes no learning rate, stops on the three tolerances below, and reaches a lower training loss in fewer passes over the data, though each of its iterations costs several passes where an epoch costs one. 'sgd' visits the samples one at a time and steps down the gradient of each, running for 'IterationLimit' epochs; it was the default before version 1.9.0. 'GradientTolerance' A nonnegative scalar. Training stops once the gradient’s infinity norm falls to or below it, which is the quantity MATLAB tests too. The default is 1e-6 . Applies only when 'Solver' is 'lbfgs' . 'StepTolerance' A nonnegative scalar. Training stops once the step’s infinity norm falls to or below it, which is the quantity MATLAB tests too. The default is 1e-6 . Applies only when 'Solver' is 'lbfgs' . 'LossTolerance' A real scalar. Training stops once the training loss falls to or below it. The test is on the loss itself and not on its change, matching MATLAB; pass -Inf to switch it off. The default is 1e-6 . Applies only when 'Solver' is 'lbfgs' . 'Activations' A character vector or a cellstr vector specifying the activation functions for the hidden layers of the neural network, excluding the output layer. The available activation functions are 'linear' , 'none' , 'sigmoid' , 'relu' , 'tanh' , 'lrelu' , 'prelu' , 'elu' and 'gelu' . The default value is 'relu' . 'OutputLayerActivation' A character vector specifying the activation function for the output layer. The available functions are the same as for 'Activations' . The default value is 'none' , the identity, which is what a regression output calls for; anything else bounds the prediction to that function’s range. 'IterationLimit' A positive integer scalar specifying the maximum number of training iterations. The default value is 1000. Under 'sgd' this counts epochs, under 'lbfgs' solver iterations. 'DisplayInfo' A logical scalar indicating whether to print information during training. Default is false . The weights of each layer are drawn from a uniform range whose half-width is set by that layer’s activation, and the scheme cannot be chosen: a rectifying activation ( 'relu' , 'lrelu' , 'prelu' , 'elu' , 'gelu' ) takes the He range sqrt (6 / fan_in) , because it passes only half of its input, and the remaining activations take the Glorot range sqrt (6 / (fan_in + fan_out)) , which accounts for the backward pass as well. A network whose layers do not share an activation is therefore built with both schemes. What each layer was given is reported by the LayerWeightsInitializers field of the fitted model’s ModelParameters . See also: RegressionNeuralNetwork, fitcnet, fcnntrain, fcnnpredict # name: # type: sq_string # elements: 1 # length: 38 Fit a neural network regression model. # name: # type: sq_string # elements: 1 # length: 7 fitrsvm # name: # type: sq_string # elements: 1 # length: 2995 statistics: Mdl = fitrsvm ( X , Y ) statistics: Mdl = fitrsvm (…, name , value ) Fit a support vector machine regression model. Mdl = fitrsvm ( X , Y ) returns a support vector regression model, Mdl , with X being the predictor data and Y the continuous response of the observations in X . X must be an NxP numeric matrix of predictor data, where rows correspond to observations and columns to features or variables. Y must be an Nx1 numeric vector holding the response of the corresponding predictor data in X . Y must have the same number of rows as X . The model is fitted by epsilon -insensitive regression: an error smaller than Epsilon costs nothing, so only the observations outside that tube become support vectors. Use fitcsvm where the response names a class rather than a quantity. Mdl = fitrsvm (…, name , value ) returns a model with additional options specified by Name-Value pair arguments listed below. Model Parameters Name Value 'Standardize' A logical scalar indicating whether the data in X should be centred and scaled before training. The same transformation is applied by predict . The default is false . 'PredictorNames' A cell array of character vectors specifying the predictor variable names, in the order they appear in X . 'ResponseName' A character vector specifying the name of the response variable. The default is 'Y' . 'ResponseTransform' A character vector naming one of 'none' , 'identity' , 'exp' or 'log' , or a function handle of one argument, applied to the predicted response. The default is 'none' . 'Epsilon' A non-negative scalar, the half-width of the insensitive tube. The default is iqr ( Y ) / 13.49 , a robust estimate of a tenth of the response’s standard deviation, which is what MATLAB uses; where that is zero it falls back to 0.1 . 'BoxConstraint' A positive scalar bounding the dual coefficients, the cost of an error outside the tube. The default is 1. 'KernelFunction' A character vector naming the kernel, one of 'linear' , the default, 'rbf' , 'gaussian' , 'polynomial' or 'sigmoid' . 'PolynomialOrder' A positive integer, the order of the polynomial kernel. The default is 3. It is ignored by every other kernel. 'KernelScale' A positive scalar dividing the predictors before the kernel is applied. The default is 1. 'KernelOffset' A non-negative scalar added to the kernel value. The default is 0. 'SVMtype' A character vector selecting the formulation, either 'eps_svr' , the default, or 'nu_svr' . MATLAB fits only the epsilon form; 'nu_svr' is an Octave extension. 'Nu' A scalar in (0, 1] used by 'nu_svr' , bounding the fraction of support vectors. The default is 0.5. 'CacheSize' A positive scalar, the kernel cache in megabytes. The default is 1000. 'Tolerance' A non-negative scalar, the tolerance of the termination criterion. The default is 1e-6 . 'Shrinking' Either 0 or 1, whether to use the shrinking heuristic. The default is 1. See also: RegressionSVM, fitcsvm, fitrnet, svmtrain, svmpredict # name: # type: sq_string # elements: 1 # length: 46 Fit a support vector machine regression model. statistics-release-1.9.2/inst/Supervised_Learning/fitcdiscr.m000066400000000000000000000177311524624707500244350ustar00rootroot00000000000000## Copyright (C) 2024 Ruchika Sonagote ## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} fitcdiscr (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{Mdl} =} fitcdiscr (@dots{}, @var{name}, @var{value}) ## ## Fit a Linear Discriminant Analysis classification model. ## ## @code{@var{Mdl} = fitcdiscr (@var{X}, @var{Y})} returns a Linear Discriminant ## Analysis (LDA) classification model, @var{Mdl}, with @var{X} being the ## predictor data, and @var{Y} the class labels of observations in @var{X}. ## ## @itemize ## @item ## @code{X} must be a @math{N*P} numeric matrix of predictor data where rows ## correspond to observations and columns correspond to features or variables. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels of ## corresponding predictor data in @var{X}. @var{Y} can be numerical, logical, ## char array or cell array of character vectors. @var{Y} must have same number ## of rows as @var{X}. ## @end itemize ## ## @code{@var{Mdl} = fitcdiscr (@dots{}, @var{name}, @var{value})} returns a ## Linear Discriminant Analysis model with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @subheading Model Parameters ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PredictorNames'} @tab A cell array of character vectors ## specifying the names of the predictors. The length of this array must match ## the number of columns in @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector specifying the ## name of the response variable. ## ## @item @qcode{'ClassNames'} @tab Names of the classes in the class ## labels, @var{Y}, used for fitting the Discriminant model. @qcode{ClassNames} ## are of the same type as the class labels in @var{Y}. ## ## @item @qcode{'Prior'} @tab A numeric vector specifying the prior ## probabilities for each class. The order of the elements in @qcode{Prior} ## corresponds to the order of the classes in @qcode{ClassNames}. ## Alternatively, you can specify @qcode{'empirical'} to use the empirical ## class probabilities or @qcode{'uniform'} to assume equal class probabilities. ## ## @item @qcode{'Cost'} @tab A @math{N*R} numeric matrix containing ## misclassification cost for the corresponding instances in @var{X} where ## @math{R} is the number of unique categories in @var{Y}. If an instance is ## correctly classified into its category the cost is calculated to be 1, ## otherwise 0. cost matrix can be altered use @code{@var{Mdl.cost} = somecost}. ## default value @qcode{@var{cost} = ones(rows(X),numel(unique(Y)))}. ## ## @item @qcode{'DiscrimType'} @tab A character vector naming the type of ## discriminant analysis to perform, one of @qcode{'linear'} (default), ## @qcode{'quadratic'}, @qcode{'diagLinear'}, @qcode{'diagQuadratic'}, ## @qcode{'pseudoLinear'} or @qcode{'pseudoQuadratic'}. A linear type pools ## one covariance across the classes and a quadratic type estimates one per ## class; a @qcode{'diag'} type keeps only the variances, and a ## @qcode{'pseudo'} type inverts a singular covariance rather than refusing ## it. The property may be reassigned after fitting, but only within its own ## family, since the family decides which covariances the fit estimates. ## ## @item @qcode{'FillCoeffs'} @tab A character vector or string scalar ## with values @qcode{'on'} or @qcode{'off'} specifying whether to fill the ## coefficients after fitting. If set to @qcode{'on'}, the coefficients are ## computed during model fitting, which can be useful for prediction. ## ## @item @qcode{'Gamma'} @tab A numeric scalar specifying the ## regularization parameter for the covariance matrix. It adjusts the linear ## discriminant analysis to make the model more stable in the presence of ## multicollinearity or small sample sizes. A value of 0 corresponds to no ## regularization, while a value of 1 corresponds to ## a completely regularized model. ## ## @end multitable ## @seealso{ClassificationDiscriminant} ## @end deftypefn function obj = fitcdiscr (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitcdiscr: too few arguments."); endif if (mod (nargin, 2) != 0) error ("fitcdiscr: name-value arguments must be in pairs."); endif ## Check predictor data and labels have equal rows if (rows (X) != rows (Y)) error ("fitcdiscr: number of rows in X and Y must be equal."); endif ## Parse arguments to class def function obj = ClassificationDiscriminant (X, Y, varargin{:}); endfunction %!demo %! ## Train a linear discriminant classifier for Gamma = 0.5 %! ## and plot the decision boundaries. %! %! load fisheriris %! idx = ! strcmp (species, 'setosa'); %! X = meas(idx,3:4); %! Y = cast (strcmpi (species(idx), 'virginica'), 'double'); %! obj = fitcdiscr (X, Y, 'Gamma', 0.5) %! x1 = [min(X(:,1)):0.03:max(X(:,1))]; %! x2 = [min(X(:,2)):0.02:max(X(:,2))]; %! [x1G, x2G] = meshgrid (x1, x2); %! XGrid = [x1G(:), x2G(:)]; %! pred = predict (obj, XGrid); %! gidx = logical (pred); %! %! figure %! scatter (XGrid(gidx,1), XGrid(gidx,2), 'markerfacecolor', 'magenta'); %! hold on %! scatter (XGrid(! gidx,1), XGrid(! gidx,2), 'markerfacecolor', 'red'); %! plot (X(Y == 0, 1), X(Y == 0, 2), 'ko', X(Y == 1, 1), X(Y == 1, 2), 'kx'); %! xlabel ('Petal length (cm)'); %! ylabel ('Petal width (cm)'); %! title ('Linear Discriminant Analysis Decision Boundary'); %! legend ({'Versicolor Region', 'Virginica Region', ... %! 'Sampled Versicolor', 'Sampled Virginica'}, ... %! 'location', 'northwest') %! axis tight %! hold off ## Tests %!test %! load fisheriris %! Mdl = fitcdiscr (meas, species, 'Gamma', 0.5); %! [label, score, cost] = predict (Mdl, [2, 2, 2, 2]); %! assert_equal (label, {'versicolor'}) %! assert_equal (score, [0, 0.9999, 0.0001], 1e-4) %! assert_equal (cost, [1, 0.0001, 0.9999], 1e-4) %! [label, score, cost] = predict (Mdl, [2.5, 2.5, 2.5, 2.5]); %! assert_equal (label, {'versicolor'}) %! assert_equal (score, [0, 0.6368, 0.3632], 1e-4) %! assert_equal (cost, [1, 0.3632, 0.6368], 1e-4) %! assert_equal (class (Mdl), "ClassificationDiscriminant"); %! assert_equal ({Mdl.X, Mdl.Y, Mdl.NumObservations}, {meas, species, 150}) %! assert_equal ({Mdl.DiscrimType, Mdl.ResponseName}, {'linear', 'Y'}) %! assert_equal ({Mdl.Gamma, Mdl.MinGamma}, {0.5, 0}) %! assert_equal (Mdl.ClassNames, unique (species)) %! sigma = [0.265008, 0.046361, 0.083757, 0.019201; ... %! 0.046361, 0.115388, 0.027622, 0.016355; ... %! 0.083757, 0.027622, 0.185188, 0.021333; ... %! 0.019201, 0.016355, 0.021333, 0.041882]; %! assert_equal (Mdl.Sigma, sigma, 1e-6) %! mu = [5.0060, 3.4280, 1.4620, 0.2460; ... %! 5.9360, 2.7700, 4.2600, 1.3260; ... %! 6.5880, 2.9740, 5.5520, 2.0260]; %! assert_equal (Mdl.Mu, mu, 1e-14) %! assert_equal (Mdl.LogDetSigma, -8.6884, 1e-4) ## Test input validation %!error fitcdiscr () %!error fitcdiscr (ones (4,1)) %!error %! fitcdiscr (ones (4,2), ones (4, 1), 'K') %!error %! fitcdiscr (ones (4,2), ones (3, 1)) %!error %! fitcdiscr (ones (4,2), ones (3, 1), 'K', 2) statistics-release-1.9.2/inst/Supervised_Learning/fitcgam.m000066400000000000000000000312501524624707500240650ustar00rootroot00000000000000## Copyright (C) 2024 Ruchika Sonagote ## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} fitcgam (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{Mdl} =} fitcgam (@dots{}, @var{name}, @var{value}) ## ## Fit a Generalized Additive Model (GAM) for binary classification. ## ## @code{@var{Mdl} = fitcgam (@var{X}, @var{Y})} returns a GAM classification ## model, @var{Mdl}, with @var{X} being the predictor data, and @var{Y} the ## binary class labels of observations in @var{X}. ## ## @itemize ## @item ## @code{X} must be a @math{N*P} numeric matrix of predictor data where rows ## correspond to observations and columns correspond to features or variables. ## @item ## @code{Y} is @math{N*1} numeric vector containing binary class labels, ## typically 0 or 1. ## @end itemize ## ## @code{@var{Mdl} = fitcgam (@dots{}, @var{name}, @var{value})} returns a ## GAM classification model with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @subheading Model Parameters ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'FitMethod'} @tab A character vector selecting the weak ## learner, either @qcode{'boostedtrees'} or @qcode{'splines'}. The default ## is @qcode{'boostedtrees'}, which boosts one shallow decision tree per ## predictor and is the scheme MATLAB uses. @qcode{'splines'} boosts a ## smoothing spline per predictor instead and is an Octave extension. The ## two take different options and an option meant for one is refused by the ## other rather than ignored, so the rows below say which engine each ## belongs to. ## ## @item @qcode{'PredictorNames'} @tab A cell array of character vectors ## specifying the names of the predictors. The length of this array must match ## the number of columns in @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector specifying the ## name of the response variable. ## ## @item @qcode{'ClassNames'} @tab Names of the classes in the class ## labels, @var{Y}, used for fitting the Discriminant model. @qcode{ClassNames} ## are of the same type as the class labels in @var{Y}. ## ## @item @qcode{'Cost'} @tab A @math{N*R} numeric matrix containing ## misclassification cost for the corresponding instances in @var{X} where ## @math{R} is the number of unique categories in @var{Y}. If an instance is ## correctly classified into its category the cost is calculated to be 1, ## otherwise 0. cost matrix can be altered use @code{@var{Mdl.cost} = somecost}. ## default value @qcode{@var{cost} = ones(rows(X),numel(unique(Y)))}. ## ## @item @qcode{'Formula'} @tab (spline option) A model specification given as a ## string in ## the form @qcode{'Y ~ terms'} where @qcode{Y} represents the response variable ## and @qcode{terms} the predictor variables. The formula can be used to ## specify a subset of variables for training model. For example: ## @qcode{'Y ~ x1 + x2 + x3 + x4 + x1:x2 + x2:x3'} specifies four linear terms ## for the first four columns of for predictor data, and @qcode{x1:x2} and ## @qcode{x2:x3} specify the two interaction terms for 1st-2nd and 3rd-4th ## columns respectively. Only these terms will be used for training the model, ## but @var{X} must have at least as many columns as referenced in the formula. ## If Predictor Variable names have been defined, then the terms in the formula ## must reference to those. When @qcode{'formula'} is specified, all terms used ## for training the model are referenced in the @qcode{IntMatrix} field of the ## @var{obj} class object as a matrix containing the column indexes for each ## term including both the predictors and the interactions used. ## ## @item @qcode{'Interactions'} @tab A logical matrix, a positive integer ## scalar, or the string @qcode{'all'} for defining the interactions between ## predictor variables. When given a logical matrix, it must have the same ## number of columns as @var{X} and each row corresponds to a different ## interaction term combining the predictors indexed as @qcode{true}. Each ## interaction term is appended as a column vector after the available predictor ## column in @var{X}. When @qcode{'all'} is defined, then all possible ## combinations of interactions are appended in @var{X} before training. At the ## moment, parsing a positive integer has the same effect as the @qcode{'all'} ## option. When @qcode{'interactions'} is specified, only the interaction terms ## appended to @var{X} are referenced in the @qcode{IntMatrix} field of the ## @var{obj} class object. ## ## @item @qcode{'Knots'} @tab (spline option) A scalar or a row vector ## with the same ## columns as @var{X}. It defines the knots for fitting a polynomial when ## training the GAM. As a scalar, it is expanded to a row vector. The default ## value is 5, hence expanded to @qcode{ones (1, columns (X)) * 5}. You can ## parse a row vector with different number of knots for each predictor ## variable to be fitted with, although not recommended. ## ## @item @qcode{'Order'} @tab (spline option) A scalar or a row vector ## with the same ## columns as @var{X}. It defines the order of the polynomial when training the ## GAM. As a scalar, it is expanded to a row vector. The default values is 3, ## hence expanded to @qcode{ones (1, columns (X)) * 3}. You can parse a row ## vector with different number of polynomial order for each predictor variable ## to be fitted with, although not recommended. ## ## @item @qcode{'DoF'} @tab (spline option) A scalar or a row vector ## with the same columns ## as @var{X}. It defines the degrees of freedom for fitting a polynomial when ## training the GAM. As a scalar, it is expanded to a row vector. The default ## value is 8, hence expanded to @qcode{ones (1, columns (X)) * 8}. You can ## parse a row vector with different degrees of freedom for each predictor ## variable to be fitted with, although not recommended. ## ## @end multitable ## ## The rows above marked as spline options require ## @qcode{'FitMethod', 'splines'}. The remaining options belong to the ## boosted-tree engine and require @qcode{'FitMethod', 'boostedtrees'}, which ## is the default. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'NumTreesPerPredictor'} @tab A positive integer, the number of ## boosting rounds of the predictor phase. It is a budget rather than a ## count: a fit that stops improving ends earlier and reports so. The default ## is 300. ## ## @item @qcode{'NumTreesPerInteraction'} @tab A positive integer, the same ## budget for the interaction phase. The default is 100. ## ## @item @qcode{'MaxNumSplitsPerPredictor'} @tab A positive integer, the ## largest number of splits any one predictor tree may make. The default is ## 1, which makes each tree a stump. ## ## @item @qcode{'MaxNumSplitsPerInteraction'} @tab The same limit for a tree ## over a pair of predictors. The default is 4. ## ## @item @qcode{'InitialLearnRateForPredictors'} @tab A value greater than 0 ## and at most 1, the step a round of the predictor phase starts at. A round ## that fails to improve the fit is retried at half the step, so this is an ## initial value rather than a fixed one. The default is 1. ## ## @item @qcode{'InitialLearnRateForInteractions'} @tab The same for the ## interaction phase. The default is 1. ## ## @item @qcode{'MaxPValue'} @tab A value between 0 and 1. A candidate pair ## of predictors is kept only if its interaction test gives a @math{p}-value ## no larger than this. The default is 1, which keeps every pair asked for. ## ## @item @qcode{'Verbose'} @tab A non-negative integer. Greater than zero ## prints a trace of the fit. The default is 0. ## ## @item @qcode{'NumPrint'} @tab A positive integer, how often the trace ## reports: the first round and then every @var{NumPrint} rounds. The ## default is 10. ## ## @end multitable ## You can parse either a @qcode{'Formula'} or an @qcode{'Interactions'} ## optional parameter. Parsing both parameters will result an error. ## Accordingly, you can only pass up to two parameters among @qcode{'Knots'}, ## @qcode{'Order'}, and @qcode{'DoF'} to define the required polynomial for ## training the GAM model. ## ## @seealso{ClassificationGAM} ## @end deftypefn function obj = fitcgam (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitcgam: too few arguments."); endif if (mod (nargin, 2) != 0) error ("fitcgam: name-value arguments must be in pairs."); endif ## Check predictor data and labels have equal rows if (rows (X) != rows (Y)) error ("fitcgam: number of rows in X and Y must be equal."); endif ## Parse arguments to class def function obj = ClassificationGAM (X, Y, varargin{:}); endfunction ## Demo %!demo %! ## Train a GAM classifier for binary classification %! ## using specific data and plot the decision boundaries. %! %! ## Define specific data %! X = [1, 2; 2, 3; 3, 3; 4, 5; 5, 5; ... %! 6, 7; 7, 8; 8, 8; 9, 9; 10, 10]; %! Y = [0; 0; 0; 0; 0; ... %! 1; 1; 1; 1; 1]; %! %! ## Train the GAM model %! obj = fitcgam (X, Y, 'Interactions', 'all'); %! %! ## Create a grid of values for prediction %! x1 = [min(X(:,1)):0.1:max(X(:,1))]; %! x2 = [min(X(:,2)):0.1:max(X(:,2))]; %! [x1G, x2G] = meshgrid (x1, x2); %! XGrid = [x1G(:), x2G(:)]; %! pred = predict (obj, XGrid); %! %! ## Plot decision boundaries and data points %! predNumeric = str2double (pred); %! gidx = predNumeric > 0.5; %! %! figure %! scatter (XGrid(gidx,1), XGrid(gidx,2), 'markerfacecolor', 'magenta'); %! hold on %! scatter (XGrid(! gidx,1), XGrid(! gidx,2), 'markerfacecolor', 'red'); %! plot (X(Y == 0, 1), X(Y == 0, 2), 'ko', X(Y == 1, 1), X(Y == 1, 2), 'kx'); %! xlabel ('Feature 1'); %! ylabel ('Feature 2'); %! title ('Generalized Additive Model (GAM) Decision Boundary'); %! legend ({'Class 1 Region', 'Class 0 Region', ... %! 'Class 1 Samples', 'Class 0 Samples'}, ... %! 'location', 'northwest') %! axis tight %! hold off ## Tests %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = [0; 0; 1; 1]; %! PredictorNames = {'Feature1', 'Feature2', 'Feature3'}; %! a = fitcgam (x, y, 'FitMethod', 'splines', ... %! 'PredictorNames', PredictorNames); %! assert_equal (class (a), "ClassificationGAM"); %! assert_equal ({a.X, a.Y, a.NumObservations}, {x, y, 4}) %! assert_equal ({a.NumPredictors, a.ResponseName}, {3, 'Y'}) %! assert_equal (a.ClassNames, [0; 1]) %! assert_equal (a.PredictorNames, PredictorNames) %! assert_equal (a.BaseModel.Intercept, 0) %!test %! x = [1, 2; 3, 4; 5, 6; 7, 8; 9, 10]; %! y = [1; 0; 1; 0; 1]; %! a = fitcgam (x, y, 'FitMethod', 'splines', 'interactions', 'all'); %! assert_equal (class (a), "ClassificationGAM"); %! assert_equal ({a.X, a.Y, a.NumObservations}, {x, y, 5}) %! assert_equal ({a.NumPredictors, a.ResponseName}, {2, 'Y'}) %! assert_equal (a.ClassNames, [0; 1]) %! assert_equal (a.PredictorNames, {'x1', 'x2'}) %! assert_equal (a.ModelwInt.Intercept, 0.4055, 1e-1) %!test %! load fisheriris %! inds = strcmp (species,'versicolor') | strcmp (species,'virginica'); %! X = meas(inds, :); %! Y = species(inds, :)'; %! Y = strcmp (Y, 'virginica')'; %! a = fitcgam (X, Y, 'FitMethod', 'splines', ... %! 'Formula', 'Y ~ x1 + x2 + x3 + x4 + x1:x2 + x2:x3'); %! assert_equal (class (a), "ClassificationGAM"); %! assert_equal ({a.X, a.Y, a.NumObservations}, {X, Y, 100}) %! assert_equal ({a.NumPredictors, a.ResponseName}, {4, 'Y'}) %! assert_equal (a.ClassNames, logical ([0; 1])) %! assert_equal (a.Formula, 'Y ~ x1 + x2 + x3 + x4 + x1:x2 + x2:x3') %! assert_equal (a.PredictorNames, {'x1', 'x2', 'x3', 'x4'}) %! assert_equal (a.ModelwInt.Intercept, 0) ## Test input validation %!error fitcgam () %!error fitcgam (ones (4,1)) %!error %! fitcgam (ones (4,2), ones (4, 1), 'K') %!error %! fitcgam (ones (4,2), ones (3, 1)) %!error %! fitcgam (ones (4,2), ones (3, 1), 'K', 2) statistics-release-1.9.2/inst/Supervised_Learning/fitckernel.m000066400000000000000000000150441524624707500246040ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} fitckernel (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{Mdl} =} fitckernel (@dots{}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{Mdl}, @var{FitInfo}] =} fitckernel (@dots{}) ## ## Fit a Gaussian kernel binary classifier. ## ## @code{@var{Mdl} = fitckernel (@var{X}, @var{Y})} returns a ## @qcode{ClassificationKernel} object fitted to the predictor data @var{X} ## and the two class response @var{Y}, where @var{X} is an @math{NxP} ## numeric matrix and @var{Y} has as many rows as @var{X}. ## ## @code{@var{Mdl} = fitckernel (@dots{}, @var{name}, @var{value})} passes ## the given @qcode{Name-Value} pairs to the model. They are documented ## under @code{ClassificationKernel}, and the ones most often wanted are ## @qcode{'Learner'}, @qcode{'NumExpansionDimensions'}, ## @qcode{'KernelScale'}, @qcode{'Lambda'}, @qcode{'BoxConstraint'} and ## @qcode{'Standardize'}. ## ## @code{[@var{Mdl}, @var{FitInfo}] = fitckernel (@dots{})} also returns a ## structure describing the optimization: the objective it reached, the ## gradient it left, and the tolerances it was given. ## ## @code{@var{Mdl} = fitckernel (@dots{}, @var{cvopt}, @var{value})} returns a ## @code{ClassificationPartitionedKernel} ## instead when one of @qcode{'CrossVal'}, @qcode{'KFold'}, ## @qcode{'Holdout'}, @qcode{'Leaveout'} and @qcode{'CVPartition'} is ## given. A cross-validated model describes no single fit, so ## @var{FitInfo} is not available beside it. ## ## @seealso{ClassificationKernel, ClassificationLinear, fitclinear} ## @end deftypefn function [Mdl, FitInfo] = fitckernel (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitckernel: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error ("fitckernel: name-value arguments must be in pairs."); endif ## A cross-validation option asks for a partitioned model rather than a ## fitted one, and the two are different classes with different methods. ## MATLAB refuses a second output there, having no single fit to describe, ## and so does this. ## 'CrossVal' is the one that carries a value saying whether to cross ## validate at all; the other four ask for it by being present. cvNames = {'kfold', 'holdout', 'leaveout', 'cvpartition'}; crossval = false; for k = 1:2:numel (varargin) if (! ischar (varargin{k})) continue; endif if (any (strcmpi (varargin{k}, cvNames))) crossval = true; elseif (strcmpi (varargin{k}, 'crossval')) val = varargin{k+1}; if (! (ischar (val) && any (strcmpi (val, {'on', 'off'})))) error ("%s: 'CrossVal' must be either 'on' or 'off'.", 'fitckernel'); endif crossval = crossval || strcmpi (val, 'on'); endif endfor if (crossval) if (nargout > 1) error (strcat ("fitckernel: a cross validated model has no", ... " FitInfo to return; ask for the model alone.")); endif Mdl = ClassificationPartitionedKernel (X, Y, varargin{:}); return; endif ## 'CrossVal', 'off' has said its piece and the learner does not take it. keep = true (1, numel (varargin)); for k = 1:2:numel (varargin) if (ischar (varargin{k}) && strcmpi (varargin{k}, 'crossval')) keep(k:k+1) = false; endif endfor varargin = varargin(keep); Mdl = ClassificationKernel (X, Y, varargin{:}); if (nargout > 1) FitInfo = fitInfo_ (Mdl); endif endfunction %!demo %! ## Fit a Gaussian kernel classifier to the two overlapping iris species %! ## and read what the optimization did. %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! [Mdl, FitInfo] = fitckernel (X, Y) %!test %! ## The driver returns a kernel classifier %! load fisheriris %! Mdl = fitckernel (meas(51:end,:), species(51:end)); %! assert_equal (class (Mdl), 'ClassificationKernel'); %! assert_equal (Mdl.NumExpansionDimensions, 128); %!test %! ## The options reach the model %! load fisheriris %! Mdl = fitckernel (meas(51:end,:), species(51:end), ... %! 'Learner', 'logistic', 'KernelScale', 2, ... %! 'NumExpansionDimensions', 64); %! assert_equal (Mdl.Learner, 'logistic'); %! assert_equal (Mdl.KernelScale, 2); %! assert_equal (Mdl.NumExpansionDimensions, 64); %!test %! ## The second output describes the optimization %! load fisheriris %! [~, FitInfo] = fitckernel (meas(51:end,:), species(51:end)); %! assert_equal (FitInfo.Solver, 'LBFGS-fast'); %! assert_equal (FitInfo.LossFunction, 'hinge'); %! assert_equal (FitInfo.Lambda, 0.01); %!test %! ## A cross-validation option returns a partitioned model instead %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = fitckernel (X, Y, 'KFold', 5); %! assert_equal (class (CVMdl), 'ClassificationPartitionedKernel'); %! assert_equal (CVMdl.KFold, 5); %! assert_equal (numel (CVMdl.Trained), 5); %!test %! ## 'CrossVal' on gives the ten folds it defaults to, and 'off' the model %! ## itself %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = fitckernel (X, Y, 'CrossVal', 'on'); %! assert_equal (CVMdl.KFold, 10); %! assert_equal (class (fitckernel (X, Y, 'CrossVal', 'off')), ... %! 'ClassificationKernel'); %!error ... %! [Mdl, FitInfo] = fitckernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'KFold', 3); ## Test input validation %!error fitckernel (ones (5, 2)) %!error ... %! fitckernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Learner') %!error ... %! fitckernel (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Learner', 'tree') statistics-release-1.9.2/inst/Supervised_Learning/fitcknn.m000066400000000000000000000627641524624707500241250ustar00rootroot00000000000000## Copyright (C) 2023 Mohammed Azmat Khan ## Copyright (C) 2023-2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} fitcknn (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{Mdl} =} fitcknn (@dots{}, @var{name}, @var{value}) ## ## Fit a k-Nearest Neighbor classification model. ## ## @code{@var{Mdl} = fitcknn (@var{X}, @var{Y})} returns a k-Nearest Neighbor ## classification model, @var{Mdl}, with @var{X} being the predictor data, and ## @var{Y} the class labels of observations in @var{X}. ## ## @itemize ## @item ## @code{X} must be a @math{N*P} numeric matrix of predictor data where rows ## correspond to observations and columns correspond to features or variables. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels of ## corresponding predictor data in @var{X}. @var{Y} can be numerical, logical, ## char array or cell array of character vectors. @var{Y} must have same number ## of rows as @var{X}. ## @end itemize ## ## @code{@var{Mdl} = fitcknn (@dots{}, @var{name}, @var{value})} returns a ## k-Nearest Neighbor classification model with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @subheading Model Parameters ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Standardize'} @tab A boolean flag indicating whether ## the data in @var{X} should be standardized prior to training. ## ## @item @qcode{'PredictorNames'} @tab A cell array of character vectors ## specifying the predictor variable names. The variable names are assumed to ## be in the same order as they appear in the training data @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector specifying the name ## of the response variable. ## ## @item @qcode{'ClassNames'} @tab Names of the classes in the class ## labels, @var{Y}, used for fitting the kNN model. @qcode{ClassNames} are of ## the same type as the class labels in @var{Y}. ## ## @item @qcode{'Prior'} @tab A numeric vector specifying the prior ## probabilities for each class. The order of the elements in @qcode{Prior} ## corresponds to the order of the classes in @qcode{ClassNames}. ## ## @item @qcode{'Cost'} @tab A @math{N*R} numeric matrix containing ## misclassification cost for the corresponding instances in @var{X} where ## @math{R} is the number of unique categories in @var{Y}. If an instance is ## correctly classified into its category the cost is calculated to be 1, ## otherwise 0. cost matrix can be altered use @code{@var{Mdl.cost} = somecost}. ## default value @qcode{@var{cost} = ones(rows(X),numel(unique(Y)))}. ## ## @item @qcode{'ScoreTransform'} @tab A character vector defining one of ## the following functions or a user defined function handle, which is used ## for transforming the prediction scores returned by the @code{predict} and ## @code{resubPredict} methods. Default value is @qcode{'none'}. ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'doublelogit'} @tab @math{1 ./ (1 + exp (-2 * x))} ## @item @qcode{'invlogit'} @tab @math{log (x ./ (1 - x))} ## @item @qcode{'ismax'} @tab Sets the score for the class with the largest ## score to 1, and sets the scores for all other classes to 0 ## @item @qcode{'logit'} @tab @math{1 ./ (1 + exp (-x))} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'sign'} @tab @math{-1 for x < 0, 0 for x = 0, 1 for x > 0} ## @item @qcode{'symmetric'} @tab @math{2 * x - 1} ## @item @qcode{'symmetricismax'} @tab Sets the score for the class with ## the largest score to 1, and sets the scores for all other classes to -1 ## @item @qcode{'symmetriclogit'} @tab @math{2 ./ (1 + exp (-x)) - 1} ## @end multitable ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'BreakTies'} @tab Tie-breaking algorithm used by predict ## when multiple classes have the same smallest cost. By default, ties occur ## when multiple classes have the same number of nearest points among the ## @math{k} nearest neighbors. The available options are specified by the ## following character arrays: ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## ## @item @qcode{'smallest'} @tab This is the default and it favors the ## class with the smallest index among the tied groups, i.e. the one that ## appears first in the training labelled data. ## @item @qcode{'nearest'} @tab This favors the class with the nearest ## neighbor among the tied groups, i.e. the class with the closest member point ## according to the distance metric used. ## @item @qcode{'random'} @tab This randomly picks one class among the ## tied groups. ## @end multitable ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'BucketSize'} @tab The maximum number of data points in the ## leaf node of the Kd-tree and it must be a positive integer. By default, it ## is 50. This argument is meaningful only when the selected search method is ## @qcode{'kdtree'}. ## ## @item @qcode{'NumNeighbors'} @tab A positive integer value specifying ## the number of nearest neighbors to be found in the kNN search. By default, ## it is 1. ## ## @item @qcode{'Exponent'} @tab A positive scalar (usually an integer) ## specifying the Minkowski distance exponent. This argument is only valid when ## the selected distance metric is @qcode{'minkowski'}. By default it is 2. ## ## @item @qcode{'Scale'} @tab A nonnegative numeric vector specifying the ## scale parameters for the standardized Euclidean distance. The vector length ## must be equal to the number of columns in @var{X}. This argument is only ## valid when the selected distance metric is @qcode{'seuclidean'}, in which ## case each coordinate of @var{X} is scaled by the corresponding element of ## @qcode{'scale'}, as is each query point in @var{Y}. By default, the scale ## parameter is the standard deviation of each coordinate in @var{X}. If a ## variable in @var{X} is constant, i.e. zero variance, this value is forced ## to 1 to avoid division by zero. This is the equivalent of this variable not ## being standardized. ## ## @item @qcode{'Cov'} @tab A square matrix with the same number of columns ## as @var{X} specifying the covariance matrix for computing the mahalanobis ## distance. This must be a positive definite matrix matching. This argument ## is only valid when the selected distance metric is @qcode{'mahalanobis'}. ## ## @item @qcode{'Distance'} @tab is the distance metric used by ## @code{knnsearch} as specified below: ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## ## @item @qcode{'euclidean'} @tab Euclidean distance. ## @item @qcode{'seuclidean'} @tab standardized Euclidean distance. Each ## coordinate difference between the rows in @var{X} and the query matrix ## @var{Y} is scaled by dividing by the corresponding element of the standard ## deviation computed from @var{X}. To specify a different scaling, use the ## @qcode{'Scale'} name-value argument. ## @item @qcode{'cityblock'} @tab City block distance. ## @item @qcode{'chebychev'} @tab Chebychev distance (maximum coordinate ## difference). ## @item @qcode{'minkowski'} @tab Minkowski distance. The default exponent ## is 2. To specify a different exponent, use the @qcode{'P'} name-value ## argument. ## @item @qcode{'mahalanobis'} @tab Mahalanobis distance, computed using a ## positive definite covariance matrix. To change the value of the covariance ## matrix, use the @qcode{'Cov'} name-value argument. ## @item @qcode{'cosine'} @tab Cosine distance. ## @item @qcode{'correlation'} @tab One minus the sample linear correlation ## between observations (treated as sequences of values). ## @item @qcode{'spearman'} @tab One minus the sample Spearman's rank ## correlation between observations (treated as sequences of values). ## @item @qcode{'hamming'} @tab Hamming distance, which is the percentage ## of coordinates that differ. ## @item @qcode{'jaccard'} @tab One minus the Jaccard coefficient, which is ## the percentage of nonzero coordinates that differ. ## @item @var{@@distfun} @tab Custom distance function handle. A distance ## function of the form @code{function @var{D2} = distfun (@var{XI}, @var{YI})}, ## where @var{XI} is a @math{1*P} vector containing a single observation in ## @math{P}-dimensional space, @var{YI} is an @math{N*P} matrix containing an ## arbitrary number of observations in the same @math{P}-dimensional space, and ## @var{D2} is an @math{N*P} vector of distances, where @qcode{(@var{D2}k)} is ## the distance between observations @var{XI} and @qcode{(@var{YI}k,:)}. ## @end multitable ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'DistanceWeight'} @tab A distance weighting function, ## specified either as a function handle, which accepts a matrix of nonnegative ## distances and returns a matrix the same size containing nonnegative distance ## weights, or one of the following values: @qcode{'equal'}, which corresponds ## to no weighting; @qcode{'inverse'}, which corresponds to a weight equal to ## @math{1/distance}; @qcode{'squaredinverse'}, which corresponds to a weight ## equal to @math{1/distance^2}. ## ## @item @qcode{'CacheSize'} @tab A positive scalar, the cache size in ## megabytes, 1000 by default. It is stored and reported for compatibility ## and @strong{does not affect the fit or any prediction}: a nearest-neighbour ## model keeps no Gram matrix to cache, holding its training data and ## computing each distance when asked. MATLAB hides the same property from ## @code{properties}, where this package reports it. ## ## @item @qcode{'IncludeTies'} @tab A boolean flag to indicate if the ## returned values should contain the indices that have same distance as the ## @math{K^th} neighbor. When @qcode{false}, @code{knnsearch} chooses the ## observation with the smallest index among the observations that have the same ## distance from a query point. When @qcode{true}, @code{knnsearch} includes ## all nearest neighbors whose distances are equal to the @math{K^th} smallest ## distance in the output arguments. To specify @math{K}, use the @qcode{'K'} ## name-value pair argument. ## ## @item @qcode{'NSMethod'} @tab is the nearest neighbor search method used ## by @code{knnsearch} as specified below. ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## ## @item @qcode{'kdtree'} @tab Creates and uses a Kd-tree to find nearest ## neighbors. @qcode{'kdtree'} is the default value when the number of columns ## in @var{X} is less than or equal to 10, @var{X} is not sparse, and the ## distance metric is @qcode{'euclidean'}, @qcode{'cityblock'}, ## @qcode{'manhattan'}, @qcode{'chebychev'}, or @qcode{'minkowski'}. Otherwise, ## the default value is @qcode{'exhaustive'}. This argument is only valid when ## the distance metric is one of the four aforementioned metrics. ## @item @qcode{'exhaustive'} @tab Uses the exhaustive search algorithm by ## computing the distance values from all the points in @var{X} to each point in ## @var{Y}. ## @end multitable ## ## @subheading Cross Validation Options ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Crossval'} @tab Cross-validation flag specified as ## @qcode{'on'} or @qcode{'off'}. If @qcode{'on'} is specified, a 10-fold ## cross validation is performed and a @code{ClassificationPartitionedModel} is ## returned in @var{Mdl}. To override this cross-validation setting, use only ## one of the following Name-Value pair arguments. ## ## @item @qcode{'CVPartition'} @tab A @code{cvpartition} object that ## specifies the type of cross-validation and the indexing for the training and ## validation sets. A @code{ClassificationPartitionedModel} is returned in ## @var{Mdl} and the trained model is stored in the @code{Trained} property. ## ## @item @qcode{'Holdout'} @tab Fraction of the data used for holdout ## validation, specified as a scalar value in the range @math{[0,1]}. When ## specified, a randomly selected percentage is reserved as validation data and ## the remaining set is used for training. The trained model is stored in the ## @code{Trained} property of the @code{ClassificationPartitionedModel} returned ## in @var{Mdl}. @qcode{'Holdout'} partitioning attempts to ensure that each ## partition represents the classes proportionately. ## ## @item @qcode{'KFold'} @tab Number of folds to use in the cross-validated ## model, specified as a positive integer value greater than 1. When specified, ## then the data is randomly partitioned in @math{k} sets and for each set, the ## set is reserved as validation data while the remaining @math{k-1} sets are ## used for training. The trained models are stored in the @code{Trained} ## property of the @code{ClassificationPartitionedModel} returned in @var{Mdl}. ## @qcode{'KFold'} partitioning attempts to ensure that each partition ## represents the classes proportionately. ## ## @item @qcode{'Leaveout'} @tab Leave-one-out cross-validation flag ## specified as @qcode{'on'} or @qcode{'off'}. If @qcode{'on'} is specified, ## then for each of the @math{n} observations (where @math{n} is the number of ## observations, excluding missing observations, specified in the ## @code{NumObservations} property of the model), one observation is reserved as ## validation data while the remaining observations are used for training. The ## trained models are stored in the @code{Trained} property of the ## @code{ClassificationPartitionedModel} returned in @var{Mdl}. ## @end multitable ## ## @seealso{ClassificationKNN, ClassificationPartitionedModel, knnsearch, ## rangesearch, pdist2} ## @end deftypefn function Mdl = fitcknn (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitcknn: too few arguments."); endif if (mod (nargin, 2) != 0) error ("fitcknn: Name-Value arguments must be in pairs."); endif ## Check predictor data and labels have equal rows if (rows (X) != rows (Y)) error ("fitcknn: number of rows in X and Y must be equal."); endif ## Check optional input parameters for cross-validation options cv_opt = false; cv_arg = 0; args = {}; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'crossval' CrossVal = varargin{2}; if (! any (strcmp (CrossVal, {'off', 'on'}))) error ("fitcknn: 'CrossVal' must be either 'off' or 'on'."); endif if (strcmp (CrossVal, 'on')) cv_opt = true; endif case 'kfold' Name = 'KFold'; Value = varargin{2}; cv_arg += 1; cv_opt = true; case 'holdout' Name = 'Holdout'; Value = varargin{2}; cv_arg += 1; cv_opt = true; case 'leaveout' Name = 'Holdout'; Value = varargin{2}; cv_arg += 1; cv_opt = true; case 'cvpartition' Name = 'CVPartition'; Value = varargin{2}; cv_arg += 1; cv_opt = true; otherwise args = [args, {varargin{1}, varargin{2}}]; endswitch varargin(1:2) = []; endwhile ## Check for multiple cross-validation paired arguments if (cv_arg > 1) error (strcat ("fitcknn: You can use only one cross-validation", ... " name-value pair argument at a time to create a", ... " cross-validated model.")); endif ## Parse arguments to class def function Mdl = ClassificationKNN (X, Y, args{:}); ## If cross validation has been requested, ## return a ClassificationPartitionedModel if (cv_opt) if (cv_arg) Mdl = crossval (Mdl, Name, Value); else Mdl = crossval (Mdl); endif endif endfunction %!demo %! ## Train a k-nearest neighbor classifier for k = 10 %! ## and plot the decision boundaries. %! %! load fisheriris %! idx = ! strcmp (species, 'setosa'); %! X = meas(idx,3:4); %! Y = cast (strcmpi (species(idx), 'virginica'), 'double'); %! obj = fitcknn (X, Y, 'Standardize', 1, 'NumNeighbors', 10, 'NSMethod', 'exhaustive') %! x1 = [min(X(:,1)):0.03:max(X(:,1))]; %! x2 = [min(X(:,2)):0.02:max(X(:,2))]; %! [x1G, x2G] = meshgrid (x1, x2); %! XGrid = [x1G(:), x2G(:)]; %! pred = predict (obj, XGrid); %! gidx = logical (pred); %! %! figure %! scatter (XGrid(gidx,1), XGrid(gidx,2), 'markerfacecolor', 'magenta'); %! hold on %! scatter (XGrid(! gidx,1), XGrid(! gidx,2), 'markerfacecolor', 'red'); %! plot (X(Y == 0, 1), X(Y == 0, 2), 'ko', X(Y == 1, 1), X(Y == 1, 2), 'kx'); %! xlabel ('Petal length (cm)'); %! ylabel ('Petal width (cm)'); %! title ('5-Nearest Neighbor Classifier Decision Boundary'); %! legend ({'Versicolor Region', 'Virginica Region', ... %! 'Sampled Versicolor', 'Sampled Virginica'}, ... %! 'location', 'northwest') %! axis tight %! hold off ## Test Output %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = fitcknn (x, y); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 1}) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = fitcknn (x, y, 'NSMethod', 'exhaustive'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 1}) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! k = 10; %! a = fitcknn (x, y, 'NumNeighbors' ,k); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 4}) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = ones (4, 11); %! y = ['a'; 'a'; 'b'; 'b']; %! k = 10; %! a = fitcknn (x, y, 'NumNeighbors' ,k); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 4}) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! k = 10; %! a = fitcknn (x, y, 'NumNeighbors' ,k, 'NSMethod', 'exhaustive'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 4}) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! k = 10; %! a = fitcknn (x, y, 'NumNeighbors' ,k, 'Distance', 'hamming'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 4}) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'hamming'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! weights = ones (4,1); %! a = fitcknn (x, y, 'Standardize', 1); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 1}) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.Sigma}, {std(x, [], 1)}) %! assert_equal ({a.Mu}, {[3.75, 4.25, 4.75]}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! weights = ones (4,1); %! a = fitcknn (x, y, 'Standardize', false); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.X, a.Y, a.NumNeighbors}, {x, y, 1}) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.Sigma}, {[]}) %! assert_equal ({a.Mu}, {[]}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! s = ones (1, 3); %! a = fitcknn (x, y, 'Scale' , s, 'Distance', 'seuclidean'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.DistParameter}, {s}) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'seuclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = fitcknn (x, y, 'Exponent' , 5, 'Distance', 'minkowski'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal (a.DistParameter, 5) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'minkowski'}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = fitcknn (x, y, 'Exponent' , 5, 'Distance', 'minkowski', ... %! 'NSMethod', 'exhaustive'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal (a.DistParameter, 5) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'minkowski'}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = fitcknn (x, y, 'BucketSize' , 20, 'distance', 'mahalanobis'); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'mahalanobis'}) %! assert_equal ({a.BucketSize}, {20}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = fitcknn (x, y, 'IncludeTies', true); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal (a.IncludeTies, true); %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = fitcknn (x, y); %! assert_equal (class (a), "ClassificationKNN"); %! assert_equal (a.IncludeTies, false); %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! a = fitcknn (x, y); %! assert_equal (class (a), "ClassificationKNN") %! assert_equal (a.Prior, [0.5, 0.5]) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! prior = [0.5, 0.5]; %! a = fitcknn (x, y, 'Prior', 'empirical'); %! assert_equal (class (a), "ClassificationKNN") %! assert_equal (a.Prior, prior) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'a'; 'b']; %! prior = [0.75, 0.25]; %! a = fitcknn (x, y, 'Prior', 'empirical'); %! assert_equal (class (a), "ClassificationKNN") %! assert_equal (a.Prior, prior) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'a'; 'b']; %! prior = [0.5, 0.5]; %! a = fitcknn (x, y, 'Prior', 'uniform'); %! assert_equal (class (a), "ClassificationKNN") %! assert_equal (a.Prior, prior) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! cost = [0, 1; 1, 0]; %! a = fitcknn (x, y, 'Cost', cost); %! assert_equal (class (a), "ClassificationKNN") %! assert_equal (a.Cost, [0, 1; 1, 0]) %! assert_equal ({a.NSMethod, a.Distance}, {'kdtree', 'euclidean'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! cost = [0, 1; 1, 0]; %! a = fitcknn (x, y, 'Cost', cost, 'Distance', 'hamming' ); %! assert_equal (class (a), "ClassificationKNN") %! assert_equal (a.Cost, [0, 1; 1, 0]) %! assert_equal ({a.NSMethod, a.Distance}, {'exhaustive', 'hamming'}) %! assert_equal ({a.BucketSize}, {50}) %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = ['a'; 'a'; 'b'; 'b']; %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! a = fitcknn (x, y, 'NSMethod', 'exhaustive', 'CrossVal', 'on'); %! warning (status); %! assert_equal (class (a), "ClassificationPartitionedModel"); %! assert_equal ({a.X, a.Y, a.Trained{1}.NumNeighbors}, {x, y, 1}) %! assert_equal (a.ModelParameters.NSMethod, "exhaustive") %! assert_equal (a.ModelParameters.Distance, "euclidean") %! assert_equal ({a.Trained{1}.BucketSize}, {50}) ## Test input validation %!error fitcknn () %!error fitcknn (ones (4,1)) %!error %! fitcknn (ones (4,2), ones (4, 1), 'K') %!error %! fitcknn (ones (4,2), ones (3, 1)) %!error %! fitcknn (ones (4,2), ones (3, 1), 'K', 2) %!error %! fitcknn (ones (4,2), ones (4, 1), 'CrossVal', 2) %!error %! fitcknn (ones (4,2), ones (4, 1), 'CrossVal', 'a') %!error ... %! fitcknn (ones (4,2), ones (4, 1), 'KFold', 10, 'Holdout', 0.3) statistics-release-1.9.2/inst/Supervised_Learning/fitclinear.m000066400000000000000000000172171524624707500246020ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} fitclinear (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{Mdl} =} fitclinear (@dots{}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{Mdl}, @var{FitInfo}] =} fitclinear (@dots{}) ## ## Fit a linear binary classifier. ## ## @code{@var{Mdl} = fitclinear (@var{X}, @var{Y})} returns a ## @qcode{ClassificationLinear} object fitted to the predictor data @var{X} ## and the two class response @var{Y}, where @var{X} is an @math{NxP} ## numeric matrix and @var{Y} has as many rows as @var{X}. ## ## @code{@var{Mdl} = fitclinear (@dots{}, @var{name}, @var{value})} passes ## the given @qcode{Name-Value} pairs to the model. They are documented ## under @code{ClassificationLinear}, and the ones most often wanted are ## @qcode{'Learner'}, @qcode{'Regularization'}, @qcode{'Lambda'}, ## @qcode{'Solver'} and @qcode{'ObservationsIn'}. ## ## @code{[@var{Mdl}, @var{FitInfo}] = fitclinear (@dots{})} also returns a ## structure describing the optimization: what it converged to, how far it ## got, and which tolerance stopped it. Its fields follow the solver, so a ## dual fit reports the dual variables and a mini-batch fit the batch it ## stopped on. ## ## @code{@var{Mdl} = fitclinear (@dots{}, @var{cvopt}, @var{value})} returns a ## @code{ClassificationPartitionedLinear} ## instead when one of @qcode{'CrossVal'}, @qcode{'KFold'}, ## @qcode{'Holdout'}, @qcode{'Leaveout'} and @qcode{'CVPartition'} is ## given. A cross-validated model describes no single fit, so ## @var{FitInfo} is not available beside it. ## ## @seealso{ClassificationLinear, ClassificationKernel, fitckernel} ## @end deftypefn function [Mdl, FitInfo] = fitclinear (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitclinear: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error ("fitclinear: name-value arguments must be in pairs."); endif ## A cross-validation option asks for a partitioned model rather than a ## fitted one, and the two are different classes with different methods. ## MATLAB refuses a second output there, having no single fit to describe, ## and so does this. ## 'CrossVal' is the one that carries a value saying whether to cross ## validate at all; the other four ask for it by being present. cvNames = {'kfold', 'holdout', 'leaveout', 'cvpartition'}; crossval = false; for k = 1:2:numel (varargin) if (! ischar (varargin{k})) continue; endif if (any (strcmpi (varargin{k}, cvNames))) crossval = true; elseif (strcmpi (varargin{k}, 'crossval')) val = varargin{k+1}; if (! (ischar (val) && any (strcmpi (val, {'on', 'off'})))) error ("%s: 'CrossVal' must be either 'on' or 'off'.", 'fitclinear'); endif crossval = crossval || strcmpi (val, 'on'); endif endfor if (crossval) if (nargout > 1) error (strcat ("fitclinear: a cross validated model has no", ... " FitInfo to return; ask for the model alone.")); endif Mdl = ClassificationPartitionedLinear (X, Y, varargin{:}); return; endif ## 'CrossVal', 'off' has said its piece and the learner does not take it. keep = true (1, numel (varargin)); for k = 1:2:numel (varargin) if (ischar (varargin{k}) && strcmpi (varargin{k}, 'crossval')) keep(k:k+1) = false; endif endfor varargin = varargin(keep); Mdl = ClassificationLinear (X, Y, varargin{:}); if (nargout > 1) FitInfo = fitInfo_ (Mdl); endif endfunction %!demo %! ## Fit a linear classifier to the two overlapping iris species and read %! ## what the optimization did. %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! [Mdl, FitInfo] = fitclinear (X, Y, 'Learner', 'logistic') %!test %! ## The driver returns what the class constructor returns %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! M1 = fitclinear (X, Y); %! M2 = ClassificationLinear (X, Y); %! assert_equal (class (M1), 'ClassificationLinear'); %! assert_equal (M1.Beta, M2.Beta); %! assert_equal (M1.Bias, M2.Bias); %!test %! ## The options reach the model %! load fisheriris %! Mdl = fitclinear (meas(51:end,:), species(51:end), ... %! 'Learner', 'logistic', 'Lambda', 0.05, ... %! 'ResponseName', 'species'); %! assert_equal (Mdl.Learner, 'logistic'); %! assert_equal (Mdl.Lambda, 0.05); %! assert_equal (Mdl.ResponseName, 'species'); %!test %! ## The second output describes the optimization %! load fisheriris %! [~, FitInfo] = fitclinear (meas(51:end,:), species(51:end)); %! assert_equal (FitInfo.Lambda, 0.01); %! assert_equal (FitInfo.Solver, {'bfgs'}); %! assert_equal (FitInfo.BetaTolerance, 1e-4); %! assert_equal (FitInfo.GradientTolerance, 1e-6); %! assert_equal (isfield (FitInfo, 'TerminationStatus'), true); %!test %! ## A dual fit reports the passes it took and the dual variables it left %! load fisheriris %! [~, FitInfo] = fitclinear (meas(51:end,:), species(51:end), ... %! 'Solver', 'dual', 'PassLimit', 20); %! assert_equal (isfield (FitInfo, 'Alpha'), true); %! assert_equal (isfield (FitInfo, 'NumPasses'), true); %! assert_equal (isfield (FitInfo, 'IterationLimit'), false); %! assert_equal (FitInfo.GradientTolerance, 0); %! assert_equal (isnan (FitInfo.GradientNorm), true); %!test %! ## A mini-batch fit reports the batch it stopped on and the rate it used %! load fisheriris %! [~, FitInfo] = fitclinear (meas(51:end,:), species(51:end), ... %! 'Solver', 'sgd', 'PassLimit', 5); %! assert_equal (isfield (FitInfo, 'BatchIndex'), true); %! assert_equal (isfield (FitInfo, 'OptimalLearnRate'), true); %!test %! ## A cross-validation option returns a partitioned model instead %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = fitclinear (X, Y, 'KFold', 5); %! assert_equal (class (CVMdl), 'ClassificationPartitionedLinear'); %! assert_equal (CVMdl.KFold, 5); %! assert_equal (numel (CVMdl.Trained), 5); %!test %! ## 'CrossVal' on gives the ten folds it defaults to, and 'off' the model %! ## itself %! load fisheriris %! X = meas(51:end,:); %! Y = species(51:end); %! CVMdl = fitclinear (X, Y, 'CrossVal', 'on'); %! assert_equal (CVMdl.KFold, 10); %! assert_equal (class (fitclinear (X, Y, 'CrossVal', 'off')), ... %! 'ClassificationLinear'); %!error ... %! [Mdl, FitInfo] = fitclinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], ... %! 'KFold', 3); ## Test input validation %!error fitclinear (ones (5, 2)) %!error ... %! fitclinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Learner') %!error ... %! fitclinear (ones (10, 2), [ones(5,1); 2*ones(5,1)], 'Learner', 'tree') statistics-release-1.9.2/inst/Supervised_Learning/fitcnb.m000066400000000000000000000174251524624707500237300ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} fitcnb (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{Mdl} =} fitcnb (@dots{}, @var{name}, @var{value}) ## ## Fit a naive Bayes classification model. ## ## @code{@var{Mdl} = fitcnb (@var{X}, @var{Y})} returns a naive Bayes ## classification model, @var{Mdl}, with @var{X} being the predictor data and ## @var{Y} the class labels of the observations in @var{X}. ## ## @itemize ## @item ## @var{X} must be a @math{N*P} numeric matrix of predictor data where rows ## correspond to observations and columns correspond to features or variables. ## @item ## @var{Y} is an @math{N*1} matrix or cell matrix containing the class labels ## of the corresponding predictor data in @var{X}. @var{Y} can be numeric, ## logical, a character array or a cell array of character vectors. @var{Y} ## must have the same number of rows as @var{X}. ## @end itemize ## ## A naive Bayes model fits one univariate density to each predictor within ## each class, and treats the predictors as conditionally independent given the ## class. An observation's likelihood under a class is therefore the product ## of its per-predictor densities, and its posterior follows by Bayes' rule ## from the class prior. ## ## @code{@var{Mdl} = fitcnb (@dots{}, @var{name}, @var{value})} returns a naive ## Bayes model with additional options specified by @qcode{Name-Value} pair ## arguments listed below. ## ## @subheading Model Parameters ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'PredictorNames'} @tab A cell array of character vectors ## specifying the names of the predictors. The length of this array must match ## the number of columns in @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector specifying the name of ## the response variable. ## ## @item @qcode{'ClassNames'} @tab Names of the classes in the class labels, ## @var{Y}, used for fitting the model. @qcode{ClassNames} are of the same ## type as the class labels in @var{Y}. Naming a subset of the classes keeps ## only the observations belonging to them. ## ## @item @qcode{'Prior'} @tab A numeric vector specifying the prior probability ## of each class, in the order of @qcode{ClassNames}, or the character vector ## @qcode{'empirical'} (default) to take the class frequencies, or ## @qcode{'uniform'} to give every class the same probability. ## ## @item @qcode{'Cost'} @tab A square numeric matrix of misclassification ## costs, where @code{Cost(i,j)} is the cost of classifying an observation of ## class @math{i} into class @math{j}. The default is one off the diagonal and ## zero on it. ## ## @item @qcode{'ScoreTransform'} @tab A character vector naming a transform ## applied to the posterior returned by @code{predict}, or a function handle ## taking and returning a matrix of the same size. The default is ## @qcode{'none'}. ## ## @item @qcode{'DistributionNames'} @tab A character vector naming the ## distribution fitted to every predictor, or a cell array of character vectors ## naming one per predictor. Supported are @qcode{'normal'} (default), ## @qcode{'kernel'}, @qcode{'mvmn'} for a categorical predictor, and ## @qcode{'mn'} for token counts. @qcode{'mn'} describes the whole predictor ## vector at once and so cannot be named for only some predictors. ## ## @item @qcode{'Kernel'} @tab The smoothing kernel of the predictors fitted ## with a kernel density, one of @qcode{'normal'} (default), @qcode{'box'}, ## @qcode{'epanechnikov'} or @qcode{'triangle'}, given once for every predictor ## or once per predictor. ## ## @item @qcode{'Support'} @tab The support of the kernel densities, either ## @qcode{'unbounded'} (default), @qcode{'positive'}, or a two element numeric ## vector giving finite bounds. ## ## @item @qcode{'Width'} @tab The bandwidth of the kernel densities, given as a ## scalar, as one value per predictor, as one per class, or as a matrix of one ## per class and predictor. By default each density chooses its own. ## ## @end multitable ## ## A predictor that takes one value throughout a class has no normal density ## to fit, and that combination of class and predictor is refused rather than ## answered. Only the combination is refused, not the model: giving that ## predictor a @qcode{'kernel'} or a @qcode{'mvmn'} distribution fits the same ## data, and leaves the other predictors normal. ## ## @seealso{ClassificationNaiveBayes} ## @end deftypefn function Mdl = fitcnb (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitcnb: too few arguments."); endif if (mod (nargin, 2) != 0) error ("fitcnb: name-value arguments must be in pairs."); endif ## Check predictor data and labels have equal rows if (rows (X) != rows (Y)) error ("fitcnb: number of rows in X and Y must be equal."); endif ## Parse arguments to class def function Mdl = ClassificationNaiveBayes (X, Y, varargin{:}); endfunction %!demo %! ## Fit a naive Bayes classifier to Fisher's iris data and see how often it %! ## classifies a training observation into its own species. %! %! load fisheriris %! Mdl = fitcnb (meas, species) %! printf ("resubstitution loss: %g\n", resubLoss (Mdl)); %!demo %! ## The petal measurements separate the species far better than the sepal %! ## ones, and a kernel density follows a skewed predictor where a normal %! ## one cannot. %! %! load fisheriris %! normalMdl = fitcnb (meas, species); %! kernelMdl = fitcnb (meas, species, 'DistributionNames', 'kernel'); %! printf ("normal : %g\n", resubLoss (normalMdl)); %! printf ("kernel : %g\n", resubLoss (kernelMdl)); ## Tests %!test # the driver returns what the constructor returns %! load fisheriris %! Mdl = fitcnb (meas, species); %! assert_equal (class (Mdl), 'ClassificationNaiveBayes'); %! assert_equal (Mdl.NumObservations, 150); %! assert_equal (Mdl.ClassNames, unique (species)); %!test # MATLAB parity: the fitted parameters and the resubstitution loss %! load fisheriris %! Mdl = fitcnb (meas, species); %! assert_equal (Mdl.DistributionParameters{1,1}, ... %! [5.005999999999998; 0.352489687213451], 1e-13); %! assert_equal (resubLoss (Mdl), 0.04, 1e-14); %!test # name-value arguments reach the constructor %! load fisheriris %! Mdl = fitcnb (meas, species, 'Prior', 'uniform', 'ResponseName', 'flower'); %! assert_equal (Mdl.Prior, [1/3, 1/3, 1/3], 1e-15); %! assert_equal (Mdl.ResponseName, 'flower'); ## Test input validation %!error fitcnb () %!error fitcnb (ones (4, 1)) %!error ... %! fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], ones (4, 1), 'Prior') %!error ... %! fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], ones (3, 1)) %!error ... %! fitcnb ([1, 2; 2, 3; 3, 4; 4, 5], ones (3, 1), 'Prior', 'uniform') %!error ... %! fitcnb ([1, 2; 2, 3; 3, 4; 10, 20], [1; 1; 1; 2]) statistics-release-1.9.2/inst/Supervised_Learning/fitcnet.m000066400000000000000000000273261524624707500241200ustar00rootroot00000000000000## Copyright (C) 2024 Pallav Purbia ## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} fitcnet (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{Mdl} =} fitcnet (@dots{}, @var{name}, @var{value}) ## ## Fit a Neural Network classification model. ## ## @code{@var{Mdl} = fitcnet (@var{X}, @var{Y})} returns a Neural Network ## classification model, @var{Mdl}, with @var{X} being the predictor data, and ## @var{Y} the class labels of observations in @var{X}. ## ## @itemize ## @item ## @code{X} must be a @math{N*P} numeric matrix of predictor data where rows ## correspond to observations and columns correspond to features or variables. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels of ## corresponding predictor data in @var{X}. @var{Y} can contain any type of ## categorical data. @var{Y} must have same numbers of rows as @var{X}. ## @end itemize ## ## @code{@var{Mdl} = fitcnet (@dots{}, @var{name}, @var{value})} returns a ## Neural Network classification model with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @subheading Model Parameters ## ## @multitable @columnfractions 0.32 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Standardize'} @tab A boolean flag indicating whether ## the data in @var{X} should be standardized prior to training. ## ## @item @qcode{'PredictorNames'} @tab A cell array of character vectors ## specifying the predictor variable names. The variable names are assumed to ## be in the same order as they appear in the training data @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector specifying the name ## of the response variable. ## ## @item @qcode{'ClassNames'} @tab Names of the classes in the class ## labels, @var{Y}, used for fitting the Neural Network model. ## @qcode{ClassNames} are of the same type as the class labels in @var{Y}. ## ## @item @qcode{'Prior'} @tab A numeric vector specifying the prior ## probabilities for each class. The order of the elements in @qcode{Prior} ## corresponds to the order of the classes in @qcode{ClassNames}. ## ## @item @qcode{'LayerSizes'} @tab A vector of positive integers that ## defines the sizes of the fully connected layers in the neural network model. ## Each element in LayerSizes corresponds to the number of outputs for the ## respective fully connected layer in the neural network model. ## The default value is 10. ## ## @item @qcode{'LearningRate'} @tab A positive scalar value that defines ## the learning rate during the gradient descent. Default value is 0.003. ## A larger rate can drive every unit of a hidden layer negative, after which ## a rectifier passes no gradient and the network stops training. ## Applies only when @qcode{'Solver'} is @qcode{'sgd'}. ## ## @item @qcode{'Solver'} @tab A character vector naming the solver that ## trains the network, either @qcode{'lbfgs'} or @qcode{'sgd'}. The ## default is @qcode{'lbfgs'}, which minimizes the loss over the whole ## training set at once by limited-memory BFGS, as MATLAB does. It takes ## no learning rate, stops on the three tolerances below, and reaches a ## lower training loss in fewer passes over the data, though each of its ## iterations costs several passes where an epoch costs one. ## @qcode{'sgd'} visits the samples one at a time and steps down the ## gradient of each, running for @qcode{'IterationLimit'} epochs; it was ## the default before version 1.9.0. ## ## @item @qcode{'GradientTolerance'} @tab A nonnegative scalar. Training ## stops once the gradient's infinity norm falls to or below it, which is ## the quantity MATLAB tests too. The default is @qcode{1e-6}. Applies ## only when @qcode{'Solver'} is @qcode{'lbfgs'}. ## ## @item @qcode{'StepTolerance'} @tab A nonnegative scalar. Training ## stops once the step's infinity norm falls to or below it, which is the ## quantity MATLAB tests too. The default is @qcode{1e-6}. Applies only ## when @qcode{'Solver'} is @qcode{'lbfgs'}. ## ## @item @qcode{'LossTolerance'} @tab A real scalar. Training stops once ## the training loss falls to or below it. The test is on the loss ## itself and not on its change, matching MATLAB; pass @code{-Inf} to ## switch it off. The default is @qcode{1e-6}. Applies only when ## @qcode{'Solver'} is @qcode{'lbfgs'}. ## ## @item @qcode{'Activations'} @tab A character vector or a cellstr vector ## specifying the activation functions for the hidden layers of the neural ## network (excluding the output layer). The available activation functions ## are @qcode{'linear'}, @qcode{'sigmoid'}, @qcode{'relu'}, @qcode{'tanh'}, ## @qcode{'softmax'}, @qcode{'lrelu'}, @qcode{'prelu'}, @qcode{'elu'}, ## @qcode{'gelu'}, and @qcode{'none'}. The default value is @qcode{'relu'}. ## ## @item @qcode{'OutputLayerActivation'} @tab A character vector specifying ## the activation function for the output layer of the neural network. The ## available activation functions are the same as for @qcode{'Activations'}. ## The default value is @qcode{'softmax'}, which makes the returned scores a ## probability over the classes and trains the network against cross entropy; ## any other value trains it against the mean squared error. ## ## @item @qcode{'IterationLimit'} @tab A positive integer scalar that ## specifies the maximum number of training iterations. The default value is ## 1000. ## Under @qcode{'sgd'} this counts epochs, under ## @qcode{'lbfgs'} solver iterations. ## ## @item @qcode{'DisplayInfo'} @tab A boolean flag indicating whether to ## print information during training. Default is @qcode{false}. ## ## @item @qcode{'ScoreTransform'} @tab A character vector defining one of ## the following functions or a user defined function handle, which is used ## for transforming the prediction scores returned by the @code{predict} and ## @code{resubPredict} methods. Default value is @qcode{'none'}. ## @end multitable ## ## @multitable @columnfractions 0.3 0.75 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'doublelogit'} @tab @math{1 ./ (1 + exp (-2 * x))} ## @item @qcode{'invlogit'} @tab @math{log (x ./ (1 - x))} ## @item @qcode{'ismax'} @tab Sets the score for the class with the largest ## score to 1, and sets the scores for all other classes to 0 ## @item @qcode{'logit'} @tab @math{1 ./ (1 + exp (-x))} ## @item @qcode{'none'} @tab @math{x} (no transformation) ## @item @qcode{'identity'} @tab @math{x} (no transformation) ## @item @qcode{'sign'} @tab @math{-1 for x < 0, 0 for x = 0, 1 for x > 0} ## @item @qcode{'symmetric'} @tab @math{2 * x - 1} ## @item @qcode{'symmetricismax'} @tab Sets the score for the class with ## the largest score to 1, and sets the scores for all other classes to -1 ## @item @qcode{'symmetriclogit'} @tab @math{2 ./ (1 + exp (-x)) - 1} ## @end multitable ## ## ## The weights of each layer are drawn from a uniform range whose half-width ## is set by that layer's activation, and the scheme cannot be chosen: a ## rectifying activation (@qcode{'relu'}, @qcode{'lrelu'}, @qcode{'prelu'}, ## @qcode{'elu'}, @qcode{'gelu'}) takes the He range ## @math{sqrt (6 / fan_in)}, because it passes only half of its input, and ## the remaining activations take the Glorot range ## @math{sqrt (6 / (fan_in + fan_out))}, which accounts for the backward pass ## as well. A network whose layers do not share an activation is therefore ## built with both schemes. What each layer was given is reported by the ## @qcode{LayerWeightsInitializers} field of the fitted model's ## @qcode{ModelParameters}. ## ## @seealso{ClassificationNeuralNetwork} ## @end deftypefn function obj = fitcnet (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitcnet: too few arguments."); endif if (mod (nargin, 2) != 0) error ("fitcnet: Name-Value arguments must be in pairs."); endif ## Check predictor data and labels have equal rows if (rows (X) != rows (Y)) error ("fitcnet: number of rows in X and Y must be equal."); endif ## Parse arguments to classdef constructor obj = ClassificationNeuralNetwork (X, Y, varargin{:}); endfunction %!demo %! ## 1. Train a network on Fisher's iris data and see what it got right %! %! load fisheriris %! Mdl = fitcnet (meas, species); %! pred_species = resubPredict (Mdl); %! confusionchart (species, pred_species, 'Title', ... %! 'Neural network classification of Fisher''s iris data'); %!demo %! ## 2. Watching the fit converge %! %! load fisheriris %! Mdl = fitcnet (meas, species, 'IterationLimit', 400); %! %! ## TrainingHistory records what the solver converges on. The default %! ## solver is lbfgs, so that is the loss and the gradient norm; under %! ## 'sgd' it is the loss and the accuracy instead. %! h = Mdl.TrainingHistory; %! plotyy (h.Iteration, h.TrainingLoss, h.Iteration, h.Gradient); %! xlabel ('Iteration'); %! title ('Training loss, left, and gradient norm, right'); %!demo %! ## 3. Rectified hidden layers train faster than sigmoid ones %! %! load fisheriris %! iters = [5, 10, 25, 50, 100, 200, 400]; %! L = zeros (2, numel (iters)); %! for k = 1:numel (iters) %! for a = 1:2 %! act = {'relu', 'sigmoid'}{a}; %! m = fitcnet (meas, species, 'Activations', act, ... %! 'IterationLimit', iters(k)); %! L(a,k) = loss (m, meas, species, 'LossFun', 'classiferror'); %! endfor %! endfor %! semilogx (iters, L(1,:), 'o-', iters, L(2,:), 's-', 'linewidth', 1.5); %! xlabel ('Iteration limit'); %! ylabel ('Misclassification rate'); %! legend ({'relu', 'sigmoid'}); %! title ('A sigmoid shrinks the gradient at every layer'); %!demo %! ## 4. What the network learned, over two predictors %! %! load fisheriris %! X = meas(:,3:4); %! Mdl = fitcnet (X, species, 'LayerSizes', [12, 12], 'IterationLimit', 400); %! %! ## Ask about a grid and paint each point by the answer %! [gx, gy] = meshgrid (linspace (0.5, 7.5, 120), linspace (0, 3, 120)); %! [~, ~, region] = unique (predict (Mdl, [gx(:), gy(:)])); %! contourf (gx, gy, reshape (region, size (gx)), [1 2 3]); %! colormap (summer); %! hold on; %! gscatter (X(:,1), X(:,2), species, 'krb', 'ox+'); %! hold off; %! xlabel ('Petal length'); %! ylabel ('Petal width'); %! title ('Decision regions of a two-layer network'); ## Test constructor %!test %! load fisheriris %! x = meas; %! y = grp2idx (species); %! Mdl = fitcnet (x, y, 'IterationLimit', 50); %! assert_equal (class (Mdl), "ClassificationNeuralNetwork"); %! assert_equal (numel (Mdl.LayerWeights), 2); %! assert_equal (size (Mdl.LayerWeights{1}), [10, 4]); %! assert_equal (size (Mdl.LayerBiases{1}), [10, 1]); %! assert_equal (size (Mdl.LayerWeights{2}), [3, 10]); %! assert_equal (size (Mdl.LayerBiases{2}), [3, 1]); ## Test input validation %!error fitcnet () %!error fitcnet (ones (4,1)) %!error %! fitcnet (ones (4,2), ones (4, 1), 'LayerSizes') %!error %! fitcnet (ones (4,2), ones (3, 1)) %!error %! fitcnet (ones (4,2), ones (3, 1), 'LayerSizes', 2) statistics-release-1.9.2/inst/Supervised_Learning/fitcsvm.m000066400000000000000000000405411524624707500241310ustar00rootroot00000000000000## Copyright (C) 2024 Pallav Purbia ## Copyright (C) 2024-2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} fitcsvm (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{Mdl} =} fitcsvm (@dots{}, @var{name}, @var{value}) ## ## Fit a Support Vector Machine classification model. ## ## @code{@var{Mdl} = fitcsvm (@var{X}, @var{Y})} returns a Support Vector ## Machine classification model, @var{Mdl}, with @var{X} being the predictor ## data, and @var{Y} the class labels of observations in @var{X}. ## ## @itemize ## @item ## @code{X} must be a @math{N*P} numeric matrix of predictor data where rows ## correspond to observations and columns correspond to features or variables. ## @item ## @code{Y} is @math{N*1} matrix or cell matrix containing the class labels of ## corresponding predictor data in @var{X}. @var{Y} can be numerical, logical, ## char array or cell array of character vectors. @var{Y} must have same number ## of rows as @var{X}. ## @end itemize ## ## @code{@var{Mdl} = fitcsvm (@dots{}, @var{name}, @var{value})} returns a ## Support Vector Machine model with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @subheading Model Parameters ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Standardize'} @tab A boolean flag indicating whether ## the data in @var{X} should be standardized prior to training. ## ## @item @qcode{'PredictorNames'} @tab A cell array of character vectors ## specifying the predictor variable names. The variable names are assumed to ## be in the same order as they appear in the training data @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector specifying the name ## of the response variable. ## ## @item @qcode{'ClassNames'} @tab Names of the classes in the class ## labels, @var{Y}, used for fitting the kNN model. @qcode{ClassNames} are of ## the same type as the class labels in @var{Y}. ## ## @item @qcode{'SVMtype'} @tab Specifies the type of SVM used for training ## the @code{ClassificationSVM} model. By default, the type of SVM is defined ## by setting other parameters and/or by the data itself. Setting the ## @qcode{'SVMtype'} parameter overrides the default behavior and it accepts the ## following options: ## @end multitable ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Value} @tab @var{Description} ## @item @qcode{'C_SVC'} @tab It is the standard SVM formulation for ## classification tasks. It aims to find the optimal hyperplane that separates ## different classes by maximizing the margin between them while allowing some ## misclassifications. The parameter @qcode{'C'} controls the trade-off between ## maximizing the margin and minimizing the classification error. It is the ## default type, unless otherwise specified. ## @item @qcode{'nu_SVC'} @tab It is a variation of the standard SVM that ## introduces a parameter @math{ν} (nu) as an upper bound on the fraction of ## margin errors and a lower bound on the fraction of support vectors. This ## formulation provides more control over the number of support vectors and the ## margin errors, making it useful for specific classification scenarios. It is ## the default type, when the @qcode{'OutlierFraction'} parameter is set. ## @item @qcode{'one_class_SVM'} @tab It is used for anomaly detection and ## novelty detection tasks. It aims to separate the data points of a single ## class from the origin in a high-dimensional feature space. This method is ## particularly useful for identifying outliers or unusual patterns in the data. ## It is the default type, when the @qcode{'Nu'} parameter is set or when there ## is a single class in @var{Y}. When @qcode{'one_class_SVM'} is set by the ## @qcode{'SVMtype'} pair argument, @var{Y} has no effect and any classes are ## ignored. ## @end multitable ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'OutlierFraction'} @tab The expected proportion of outliers ## in the training data, specified as a scalar value in the range @math{[0,1]}. ## When specified, the type of SVM model is switched to @qcode{'nu_SVC'} and ## @qcode{'OutlierFraction'} defines the @math{ν} (nu) parameter. ## ## @item @qcode{'KernelFunction'} @tab A character vector specifying the ## method for computing elements of the Gram matrix. The available kernel ## functions are @qcode{'gaussian'} or @qcode{'rbf'}, @qcode{'linear'}, ## @qcode{'polynomial'}, and @qcode{'sigmoid'}. For one-class learning, the ## default Kernel function is @qcode{'rbf'}. For two-class learning the default ## is @qcode{'linear'}. ## ## @item @qcode{'PolynomialOrder'} @tab A positive integer that specifies ## the order of polynomial in kernel function. The default value is 3. Unless ## the @qcode{'KernelFunction'} is set to @qcode{'polynomial'}, this parameter ## is ignored. ## ## @item @qcode{'KernelScale'} @tab A positive scalar that specifies a ## scaling factor for the @math{γ} (gamma) parameter, which can be seen as the ## inverse of the radius of influence of samples selected by the model as ## support vectors. The @math{γ} (gamma) parameter is computed as ## @math{gamma = @qcode{KernelScale} / (number of features)}. The default value ## for @qcode{'KernelScale'} is 1. ## ## @item @qcode{'KernelOffset'} @tab A nonnegative scalar that specifies ## the @math{coef0} in kernel function. For the polynomial kernel, it influences ## the polynomial's shift, and for the sigmoid kernel, it affects the hyperbolic ## tangent's shift. The default value for @qcode{'KernelOffset'} is 0. ## ## @item @qcode{'BoxConstraint'} @tab A positive scalar that specifies the ## upper bound of the Lagrange multipliers, i.e. the parameter C, which is used ## for training @qcode{'C_SVC'} and @qcode{'one_class_SVM'} type of models. It ## determines the trade-off between maximizing the margin and minimizing the ## classification error. The default value for @qcode{'BoxConstraint'} is 1. ## ## @item @qcode{'Nu'} @tab A positive scalar, in the range @math{(0,1]} ## that specifies the parameter @math{ν} (nu) for training @qcode{'nu_SVC'} and ## @qcode{'one_class_SVM'} type of models. Unless overridden by setting the ## @qcode{'SVMtype'} parameter, setting the @qcode{'Nu'} parameter always forces ## the training model type to @qcode{'one_class_SVM'}, in which case, the number ## of classes in @var{Y} is ignored. The default value for @qcode{'Nu'} is 1. ## ## @item @qcode{'CacheSize'} @tab A positive scalar that specifies the ## memory requirements (in MB) for storing the Gram matrix. The default is 1000. ## ## @item @qcode{'Tolerance'} @tab A nonnegative scalar that specifies ## the tolerance of termination criterion. The default value is 1e-6. ## ## @item @qcode{'Shrinking'} @tab Specifies whether to use shrinking ## heuristics. It accepts either 0 or 1. The default value is 1. ## @end multitable ## ## @subheading Cross Validation Options ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Crossval'} @tab Cross-validation flag specified as ## @qcode{'on'} or @qcode{'off'}. If @qcode{'on'} is specified, a 10-fold ## cross validation is performed and a @code{ClassificationPartitionedModel} is ## returned in @var{Mdl}. To override this cross-validation setting, use only ## one of the following Name-Value pair arguments. ## ## @item @qcode{'CVPartition'} @tab A @code{cvpartition} object that ## specifies the type of cross-validation and the indexing for the training and ## validation sets. A @code{ClassificationPartitionedModel} is returned in ## @var{Mdl} and the trained model is stored in the @code{Trained} property. ## ## @item @qcode{'Holdout'} @tab Fraction of the data used for holdout ## validation, specified as a scalar value in the range @math{[0,1]}. When ## specified, a randomly selected percentage is reserved as validation data and ## the remaining set is used for training. The trained model is stored in the ## @code{Trained} property of the @code{ClassificationPartitionedModel} returned ## in @var{Mdl}. @qcode{'Holdout'} partitioning attempts to ensure that each ## partition represents the classes proportionately. ## ## @item @qcode{'KFold'} @tab Number of folds to use in the cross-validated ## model, specified as a positive integer value greater than 1. When specified, ## then the data is randomly partitioned in @math{k} sets and for each set, the ## set is reserved as validation data while the remaining @math{k-1} sets are ## used for training. The trained models are stored in the @code{Trained} ## property of the @code{ClassificationPartitionedModel} returned in @var{Mdl}. ## @qcode{'KFold'} partitioning attempts to ensure that each partition ## represents the classes proportionately. ## ## @item @qcode{'Leaveout'} @tab Leave-one-out cross-validation flag ## specified as @qcode{'on'} or @qcode{'off'}. If @qcode{'on'} is specified, ## then for each of the @math{n} observations (where @math{n} is the number of ## observations, excluding missing observations, specified in the ## @code{NumObservations} property of the model), one observation is reserved as ## validation data while the remaining observations are used for training. The ## trained models are stored in the @code{Trained} property of the ## @code{ClassificationPartitionedModel} returned in @var{Mdl}. ## @end multitable ## ## @seealso{ClassificationSVM, ClassificationPartitionedModel, svmtrain, ## svmpredict} ## @end deftypefn function Mdl = fitcsvm (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitcsvm: too few arguments."); endif if (mod (nargin, 2) != 0) error ("fitcsvm: Name-Value arguments must be in pairs."); endif ## Check predictor data and labels have equal rows if (rows (X) != rows (Y)) error ("fitcsvm: number of rows in X and Y must be equal."); endif ## Check optional input parameters for cross-validation options cv_opt = false; cv_arg = 0; args = {}; while (numel (varargin) > 0) switch (tolower (varargin{1})) case 'crossval' CrossVal = varargin{2}; if (! any (strcmp (CrossVal, {'off', 'on'}))) error ("fitcsvm: 'CrossVal' must be either 'off' or 'on'."); endif if (strcmp (CrossVal, 'on')) cv_opt = true; endif case 'kfold' Name = 'KFold'; Value = varargin{2}; cv_arg += 1; cv_opt = true; case 'holdout' Name = 'Holdout'; Value = varargin{2}; cv_arg += 1; cv_opt = true; case 'leaveout' Name = 'Holdout'; Value = varargin{2}; cv_arg += 1; cv_opt = true; case 'cvpartition' Name = 'CVPartition'; Value = varargin{2}; cv_arg += 1; cv_opt = true; otherwise args = [args, {varargin{1}, varargin{2}}]; endswitch varargin(1:2) = []; endwhile ## Check for multiple cross-validation paired arguments if (cv_arg > 1) error (strcat ("fitcsvm: You can use only one cross-validation", ... " name-value pair argument at a time to create a", ... " cross-validated model.")); endif ## Parse arguments to classdef constructor Mdl = ClassificationSVM (X, Y, args{:}); ## If cross validation has been requested, ## return a ClassificationPartitionedModel if (cv_opt) if (cv_arg) Mdl = crossval (Mdl, Name, Value); else Mdl = crossval (Mdl); endif endif endfunction %!demo %! ## Use a subset of Fisher's iris data set %! %! load fisheriris %! inds = ! strcmp (species, 'setosa'); %! X = meas(inds, [3,4]); %! Y = species(inds); %! %! ## Train a linear SVM classifier %! SVMModel = fitcsvm (X, Y) %! %! ## Plot a scatter diagram of the data and circle the support vectors. %! sv = SVMModel.SupportVectors; %! figure %! gscatter (X(:,1), X(:,2), Y) %! hold on %! plot (sv(:,1), sv(:,2), 'ko', 'MarkerSize', 10) %! legend ('versicolor', 'virginica', 'Support Vector') %! hold off ## Test constructor %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = {'a'; 'a'; 'b'; 'b'}; %! a = fitcsvm (x, y); %! assert_equal (class (a), "ClassificationSVM"); %! assert_equal ({a.X, a.Y}, {x, y}) %! assert_equal (a.NumObservations, 4) %! assert_equal ({a.ResponseName, a.PredictorNames}, {'Y', {'x1', 'x2', 'x3'}}) %! assert_equal (a.ModelParameters.SVMtype, "c_svc") %! assert_equal (a.ClassNames, {'a'; 'b'}) ## Test Output %!test %! x = [1, 2; 2, 3; 3, 4; 4, 5; 2, 3; 3, 4; 2, 3; 3, 4; 2, 3; 3, 4]; %! y = [1; 1; -1; -1; 1; -1; -1; -1; -1; -1]; %! a = fitcsvm (x, y); %! assert_equal (class (a), "ClassificationSVM"); %! assert_equal ({a.X, a.Y, a.ModelParameters.KernelFunction}, {x, y, 'linear'}) %! assert_equal (a.ModelParameters.BoxConstraint, 1) %! assert_equal (a.ModelParameters.KernelOffset, 0) %! assert_equal (a.ClassNames, [-1; 1]) %!test %! x = [1, 2; 2, 3; 3, 4; 4, 5; 2, 3; 3, 4; 2, 3; 3, 4; 2, 3; 3, 4]; %! y = [1; 1; -1; -1; 1; -1; -1; -1; -1; -1]; %! a = fitcsvm (x, y, 'KernelFunction', 'rbf', 'BoxConstraint', 2, ... %! 'KernelOffset', 2); %! assert_equal (class (a), "ClassificationSVM"); %! assert_equal ({a.X, a.Y, a.ModelParameters.KernelFunction}, {x, y, 'rbf'}) %! assert_equal (a.ModelParameters.BoxConstraint, 2) %! assert_equal (a.ModelParameters.KernelOffset, 2) %! assert_equal (isempty (a.Alpha), false) %! assert_equal (isempty (a.Beta), true) %!test %! x = [1, 2; 2, 3; 3, 4; 4, 5; 2, 3; 3, 4; 2, 3; 3, 4; 2, 3; 3, 4]; %! y = [1; 1; -1; -1; 1; -1; -1; -1; -1; -1]; %! a = fitcsvm (x, y, 'KernelFunction', 'polynomial', 'PolynomialOrder', 3); %! assert_equal (class (a), "ClassificationSVM"); %! assert_equal ({a.X, a.Y, a.ModelParameters.KernelFunction}, {x, y, 'polynomial'}) %! assert_equal (a.ModelParameters.KernelPolynomialOrder, 3) %! assert_equal (isempty (a.Alpha), false) %! assert_equal (isempty (a.Beta), true) %!test %! x = [1, 2; 2, 3; 3, 4; 4, 5; 2, 3; 3, 4; 2, 3; 3, 4; 2, 3; 3, 4]; %! y = [1; 1; -1; -1; 1; -1; -1; -1; -1; -1]; %! a = fitcsvm (x, y, 'KernelFunction', 'linear', 'PolynomialOrder', 3); %! assert_equal (class (a), "ClassificationSVM"); %! assert_equal ({a.X, a.Y, a.ModelParameters.KernelFunction}, {x, y, 'linear'}) %! assert_equal (isempty (a.ModelParameters.KernelPolynomialOrder), true) %! assert_equal (isempty (a.Alpha), false) %! assert_equal (isempty (a.Beta), false) %! assert_equal (size (a.Beta), [2, 1]) %!test %! x = [1, 2; 2, 3; 3, 4; 4, 5; 2, 3; 3, 4; 2, 3; 3, 4; 2, 3; 3, 4]; %! y = [1; 1; -1; -1; 1; -1; -1; -1; -1; -1]; %! status = warning; %! warning ('off'); %! rand ('seed', 23); %! a = fitcsvm (x, y, 'KernelFunction', 'linear', 'CrossVal', 'on'); %! warning (status); %! assert_equal (class (a), "ClassificationPartitionedModel"); %! assert_equal ({a.X, a.Y, a.ModelParameters.KernelFunction}, {x, y, 'linear'}) %! assert_equal (isempty (a.ModelParameters.KernelPolynomialOrder), true) %! assert_equal (isempty (a.Trained{1}.Alpha), false) %! assert_equal (isempty (a.Trained{1}.Beta), false) ## Test input validation %!error fitcsvm () %!error fitcsvm (ones (4,1)) %!error %! fitcsvm (ones (4,2), ones (4, 1), 'KFold') %!error %! fitcsvm (ones (4,2), ones (3, 1)) %!error %! fitcsvm (ones (4,2), ones (3, 1), 'KFold', 2) %!error %! fitcsvm (ones (4,2), ones (4, 1), 'CrossVal', 2) %!error %! fitcsvm (ones (4,2), ones (4, 1), 'CrossVal', 'a') %!error ... %! fitcsvm (ones (4,2), ones (4, 1), 'KFold', 10, 'Holdout', 0.3) statistics-release-1.9.2/inst/Supervised_Learning/fitrgam.m000066400000000000000000000256461524624707500241200ustar00rootroot00000000000000## Copyright (C) 2023 Mohammed Azmat Khan ## Copyright (C) 2023-2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} =} fitrgam (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{obj} =} fitrgam (@var{X}, @var{Y}, @var{name}, @var{value}) ## ## Fit a Generalized Additive Model (GAM) for regression. ## ## @code{@var{obj} = fitrgam (@var{X}, @var{Y})} returns an object of ## class RegressionGAM, with matrix @var{X} containing the predictor data and ## vector @var{Y} containing the continuous response data. ## ## @itemize ## @item ## @var{X} must be a @math{N*P} numeric matrix of input data where rows ## correspond to observations and columns correspond to features or variables. ## @var{X} will be used to train the GAM model. ## @item ## @var{Y} must be @math{N*1} numeric vector containing the response data ## corresponding to the predictor data in @var{X}. @var{Y} must have same ## number of rows as @var{X}. ## @end itemize ## ## @code{@var{obj} = fitrgam (@dots{}, @var{name}, @var{value})} returns ## an object of class RegressionGAM with additional properties specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @multitable @columnfractions 0.2 0.75 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'FitMethod'} @tab A character vector selecting the weak ## learner, either @qcode{'boostedtrees'} or @qcode{'splines'}. The default ## is @qcode{'boostedtrees'}, which boosts one shallow decision tree per ## predictor and is the scheme MATLAB uses. @qcode{'splines'} boosts a ## smoothing spline per predictor instead and is an Octave extension. The ## two take different options and an option meant for one is refused by the ## other rather than ignored, so the rows below say which engine each ## belongs to. ## ## @item @qcode{'predictors'} @tab Predictor Variable names, specified as ## a row vector cell of strings with the same length as the columns in @var{X}. ## If omitted, the program will generate default variable names ## @qcode{(x1, x2, ..., xn)} for each column in @var{X}. ## ## @item @qcode{'responsename'} @tab Response Variable Name, specified as ## a string. If omitted, the default value is @qcode{'Y'}. ## ## @item @qcode{'formula'} @tab (spline option) a model specification given as a ## string in ## the form @qcode{'Y ~ terms'} where @qcode{Y} represents the response variable ## and @qcode{terms} the predictor variables. The formula can be used to ## specify a subset of variables for training model. For example: ## @qcode{'Y ~ x1 + x2 + x3 + x4 + x1:x2 + x2:x3'} specifies four linear terms ## for the first four columns of for predictor data, and @qcode{x1:x2} and ## @qcode{x2:x3} specify the two interaction terms for 1st-2nd and 3rd-4th ## columns respectively. Only these terms will be used for training the model, ## but @var{X} must have at least as many columns as referenced in the formula. ## If Predictor Variable names have been defined, then the terms in the formula ## must reference to those. When @qcode{'formula'} is specified, all terms used ## for training the model are referenced in the @qcode{IntMatrix} field of the ## @var{obj} class object as a matrix containing the column indexes for each ## term including both the predictors and the interactions used. ## ## @item @qcode{'interactions'} @tab a logical matrix, a positive integer ## scalar, or the string @qcode{'all'} for defining the interactions between ## predictor variables. When given a logical matrix, it must have the same ## number of columns as @var{X} and each row corresponds to a different ## interaction term combining the predictors indexed as @qcode{true}. Each ## interaction term is appended as a column vector after the available predictor ## column in @var{X}. When @qcode{'all'} is defined, then all possible ## combinations of interactions are appended in @var{X} before training. At the ## moment, parsing a positive integer has the same effect as the @qcode{'all'} ## option. When @qcode{'interactions'} is specified, only the interaction terms ## appended to @var{X} are referenced in the @qcode{IntMatrix} field of the ## @var{obj} class object. ## ## @item @qcode{'knots'} @tab (spline option) a scalar or a row vector with the ## same ## columns as @var{X}. It defines the knots for fitting a polynomial when ## training the GAM. As a scalar, it is expanded to a row vector. The default ## value is 5, hence expanded to @qcode{ones (1, columns (X)) * 5}. You can ## parse a row vector with different number of knots for each predictor ## variable to be fitted with, although not recommended. ## ## @item @qcode{'order'} @tab (spline option) a scalar or a row vector with the ## same ## columns as @var{X}. It defines the order of the polynomial when training the ## GAM. As a scalar, it is expanded to a row vector. The default values is 3, ## hence expanded to @qcode{ones (1, columns (X)) * 3}. You can parse a row ## vector with different number of polynomial order for each predictor variable ## to be fitted with, although not recommended. ## ## @item @qcode{'dof'} @tab (spline option) a scalar or a row vector with the ## same columns ## as @var{X}. It defines the degrees of freedom for fitting a polynomial when ## training the GAM. As a scalar, it is expanded to a row vector. The default ## value is 8, hence expanded to @qcode{ones (1, columns (X)) * 8}. You can ## parse a row vector with different degrees of freedom for each predictor ## variable to be fitted with, although not recommended. ## ## @item @qcode{'tol'} @tab (spline option) a positive scalar to set the ## tolerance for ## convergence during training. By default, it is set to @qcode{1e-3}. ## @end multitable ## ## The rows above marked as spline options require ## @qcode{'FitMethod', 'splines'}. The remaining options belong to the ## boosted-tree engine and require @qcode{'FitMethod', 'boostedtrees'}, which ## is the default. ## ## @multitable @columnfractions 0.18 0.8 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'NumTreesPerPredictor'} @tab A positive integer, the number of ## boosting rounds of the predictor phase. It is a budget rather than a ## count: a fit that stops improving ends earlier and reports so. The default ## is 300. ## ## @item @qcode{'NumTreesPerInteraction'} @tab A positive integer, the same ## budget for the interaction phase. The default is 100. ## ## @item @qcode{'MaxNumSplitsPerPredictor'} @tab A positive integer, the ## largest number of splits any one predictor tree may make. The default is ## 1, which makes each tree a stump. ## ## @item @qcode{'MaxNumSplitsPerInteraction'} @tab The same limit for a tree ## over a pair of predictors. The default is 4. ## ## @item @qcode{'InitialLearnRateForPredictors'} @tab A value greater than 0 ## and at most 1, the step a round of the predictor phase starts at. A round ## that fails to improve the fit is retried at half the step, so this is an ## initial value rather than a fixed one. The default is 1. ## ## @item @qcode{'InitialLearnRateForInteractions'} @tab The same for the ## interaction phase. The default is 1. ## ## @item @qcode{'MaxPValue'} @tab A value between 0 and 1. A candidate pair ## of predictors is kept only if its interaction test gives a @math{p}-value ## no larger than this. The default is 1, which keeps every pair asked for. ## ## @item @qcode{'Verbose'} @tab A non-negative integer. Greater than zero ## prints a trace of the fit. The default is 0. ## ## @item @qcode{'NumPrint'} @tab A positive integer, how often the trace ## reports: the first round and then every @var{NumPrint} rounds. The ## default is 10. ## ## @end multitable ## ## You can parse either a @qcode{'formula'} or an @qcode{'interactions'} ## optional parameter. Parsing both parameters will result an error. ## Accordingly, you can only pass up to two parameters among @qcode{'knots'}, ## @qcode{'order'}, and @qcode{'dof'} to define the required polynomial for ## training the GAM model. ## ## @seealso{RegressionGAM, regress, regress_gp} ## @end deftypefn function obj = fitrgam (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitrgam: too few arguments."); endif if (mod (nargin, 2) != 0) error ("fitrgam: Name-Value arguments must be in pairs."); endif ## Check predictor data and labels have equal rows if (rows (X) != rows (Y)) error ("fitrgam: number of rows in X and Y must be equal."); endif ## Parse arguments to class def function obj = RegressionGAM (X, Y, varargin{:}); endfunction %!demo %! rng (42); %! # Train a RegressionGAM Model for synthetic values %! %! f1 = @(x) cos (3 *x); %! f2 = @(x) x .^ 3; %! %! # generate x1 and x2 for f1 and f2 %! x1 = 2 * rand (50, 1) - 1; %! x2 = 2 * rand (50, 1) - 1; %! %! # calculate y %! y = f1(x1) + f2(x2); %! %! # add noise %! y = y + y .* 0.2 .* rand (50,1); %! X = [x1, x2]; %! %! # create an object %! a = fitrgam (X, y, 'FitMethod', 'splines', 'tol', 1e-3) ## Test constructor %!test %! x = [1, 2, 3; 4, 5, 6; 7, 8, 9; 3, 2, 1]; %! y = [1; 2; 3; 4]; %! a = fitrgam (x, y, 'FitMethod', 'splines'); %! assert_equal ({a.X, a.Y}, {x, y}) %! assert_equal ({a.BaseModel.Intercept}, {2.5000}) %! assert_equal ({a.Knots, a.Order, a.DoF}, {[5, 5, 5], [3, 3, 3], [8, 8, 8]}) %! assert_equal ({a.NumObservations, a.NumPredictors}, {4, 3}) %! assert_equal ({a.ResponseName, a.PredictorNames}, {'Y', {'x1', 'x2', 'x3'}}) %! assert_equal ({a.Formula}, {[]}) %!test %! x = [1, 2, 3, 4; 4, 5, 6, 7; 7, 8, 9, 1; 3, 2, 1, 2]; %! y = [1; 2; 3; 4]; %! pnames = {'A', 'B', 'C', 'D'}; %! formula = 'Y ~ A + B + C + D + A:C'; %! intMat = logical ([1,0,0,0;0,1,0,0;0,0,1,0;0,0,0,1;1,0,1,0]); %! a = fitrgam (x, y, 'FitMethod', 'splines', ... %! 'predictors', pnames, 'formula', formula); %! assert_equal (a.IntMatrix, double (intMat)) %! assert_equal ({a.ResponseName, a.PredictorNames}, {'Y', pnames}) %! assert_equal (a.Formula, formula) ## Test input validation %!error fitrgam () %!error fitrgam (ones (10,2)) %!error %! fitrgam (ones (4,2), ones (4, 1), 'K') %!error %! fitrgam (ones (4,2), ones (3, 1)) %!error %! fitrgam (ones (4,2), ones (3, 1), 'K', 2) statistics-release-1.9.2/inst/Supervised_Learning/fitrgp.m000066400000000000000000000127051524624707500237520ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} fitrgp (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{Mdl} =} fitrgp (@dots{}, @var{name}, @var{value}) ## ## Fit a Gaussian process regression model. ## ## @code{@var{Mdl} = fitrgp (@var{X}, @var{Y})} returns a @qcode{RegressionGP} ## object fitted to the predictor data @var{X} and the continuous response ## @var{Y}, where @var{X} is an @math{NxP} numeric matrix and @var{Y} an ## @math{Nx1} numeric vector with as many rows as @var{X}. ## ## @code{@var{Mdl} = fitrgp (@dots{}, @var{name}, @var{value})} passes the ## given @qcode{Name-Value} pairs to the model. They are documented under ## @code{RegressionGP}, and the ones most often wanted are ## @qcode{'KernelFunction'}, @qcode{'BasisFunction'}, @qcode{'Standardize'}, ## @qcode{'Sigma'} and @qcode{'FitMethod'}. ## ## When any of @qcode{'CrossVal'}, @qcode{'KFold'}, @qcode{'Holdout'}, ## @qcode{'Leaveout'} or @qcode{'CVPartition'} is given, a cross validated ## model is returned instead, as a @qcode{RegressionPartitionedModel}. Only ## one of them may be given at a time. ## ## @seealso{RegressionGP, CompactRegressionGP, RegressionPartitionedModel} ## @end deftypefn function Mdl = fitrgp (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitrgp: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error ("fitrgp: name-value arguments must be in pairs."); endif ## Pull out the cross validation options, which the model does not take: ## they say what to do with the model once it exists, not how to fit it. CrossVal = false; cvArgs = {}; given = 0; args = {}; while (numel (varargin) > 0) switch (lower (varargin{1})) case 'crossval' val = varargin{2}; if (! (ischar (val) && any (strcmpi (val, {'on', 'off'})))) error ("fitrgp: 'CrossVal' must be either 'on' or 'off'."); endif CrossVal = strcmpi (val, 'on'); case {'kfold', 'holdout', 'leaveout', 'cvpartition'} cvArgs = [cvArgs, varargin(1:2)]; CrossVal = true; given++; otherwise args = [args, varargin(1:2)]; endswitch varargin(1:2) = []; endwhile if (given > 1) error (strcat ("fitrgp: you can use only one of 'KFold', 'Holdout',", ... " 'Leaveout', or 'CVPartition' options.")); endif Mdl = RegressionGP (X, Y, args{:}); if (CrossVal) Mdl = crossval (Mdl, cvArgs{:}); endif endfunction %!demo %! ## Fit a Gaussian process to a noisy sine and predict on a fine grid. %! x = linspace (0, 2*pi, 30)'; %! y = sin (x) + 0.1 * cos (7*x); %! Mdl = fitrgp (x, y) %! xq = linspace (0, 2*pi, 5)'; %! [yq, ysd] = predict (Mdl, xq) %!test %! ## fitrgp returns the model the class constructor returns %! x = linspace (0, 1, 15)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! M1 = fitrgp (x, y); %! M2 = RegressionGP (x, y); %! assert_equal (class (M1), 'RegressionGP'); %! assert_equal (M1.Beta, M2.Beta); %! assert_equal (M1.Sigma, M2.Sigma); %! assert_equal (predict (M1, x), predict (M2, x), 1e-14); %!test %! ## The options reach the model %! x = linspace (0, 1, 15)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! Mdl = fitrgp (x, y, 'KernelFunction', 'matern52', ... %! 'BasisFunction', 'linear', 'ResponseName', 'temp'); %! assert_equal (Mdl.KernelFunction, 'Matern52'); %! assert_equal (Mdl.BasisFunction, 'Linear'); %! assert_equal (Mdl.ResponseName, 'temp'); %! assert_equal (numel (Mdl.Beta), 2); %!test %! ## A cross validation option returns a partitioned model instead %! x = linspace (0, 1, 20)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! CVMdl = fitrgp (x, y, 'KFold', 4); %! assert_equal (class (CVMdl), 'RegressionPartitionedModel'); %! assert_equal (CVMdl.KFold, 4); %! assert_equal (CVMdl.CrossValidatedModel, 'GP'); %!test %! ## 'CrossVal' on gives the ten folds it defaults to %! x = linspace (0, 1, 20)'; %! y = cos (3*x) + 0.1 * sin (11*x); %! CVMdl = fitrgp (x, y, 'CrossVal', 'on'); %! assert_equal (class (CVMdl), 'RegressionPartitionedModel'); %! assert_equal (CVMdl.KFold, 10); %!test %! ## 'CrossVal' off is the model itself %! x = linspace (0, 1, 15)'; %! y = cos (3*x); %! assert_equal (class (fitrgp (x, y, 'CrossVal', 'off')), 'RegressionGP'); ## Test input validation %!error fitrgp (ones (5, 2)) %!error ... %! fitrgp (ones (5, 2), ones (5, 1), 'Standardize') %!error ... %! fitrgp (ones (5, 2), ones (5, 1), 'CrossVal', 5) %!error ... %! fitrgp (ones (20, 2), ones (20, 1), 'KFold', 3, 'Holdout', 0.2) statistics-release-1.9.2/inst/Supervised_Learning/fitrkernel.m000066400000000000000000000155661524624707500246340ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} fitrkernel (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{Mdl} =} fitrkernel (@dots{}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{Mdl}, @var{FitInfo}] =} fitrkernel (@dots{}) ## ## Fit a Gaussian kernel regression model. ## ## @code{@var{Mdl} = fitrkernel (@var{X}, @var{Y})} returns a ## @qcode{RegressionKernel} object fitted to the predictor data @var{X} and ## the continuous response @var{Y}, where @var{X} is an @math{NxP} numeric ## matrix and @var{Y} an @math{Nx1} numeric vector with as many rows as ## @var{X}. ## ## @code{@var{Mdl} = fitrkernel (@dots{}, @var{name}, @var{value})} passes ## the given @qcode{Name-Value} pairs to the model. They are documented ## under @code{RegressionKernel}, and the ones most often wanted are ## @qcode{'Learner'}, @qcode{'Epsilon'}, ## @qcode{'NumExpansionDimensions'}, @qcode{'KernelScale'}, ## @qcode{'Lambda'} and @qcode{'BoxConstraint'}. ## ## @code{[@var{Mdl}, @var{FitInfo}] = fitrkernel (@dots{})} also returns a ## structure describing the optimization: the objective it reached, the ## gradient it left, and the tolerances it was given. ## ## @code{@var{Mdl} = fitrkernel (@dots{}, @var{cvopt}, @var{value})} returns a ## @code{RegressionPartitionedKernel} ## instead when one of @qcode{'CrossVal'}, @qcode{'KFold'}, ## @qcode{'Holdout'}, @qcode{'Leaveout'} and @qcode{'CVPartition'} is ## given. A cross-validated model describes no single fit, so ## @var{FitInfo} is not available beside it. ## ## @seealso{RegressionKernel, RegressionLinear, fitrlinear} ## @end deftypefn function [Mdl, FitInfo] = fitrkernel (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitrkernel: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error ("fitrkernel: name-value arguments must be in pairs."); endif ## A cross-validation option asks for a partitioned model rather than a ## fitted one, and the two are different classes with different methods. ## MATLAB refuses a second output there, having no single fit to describe, ## and so does this. ## 'CrossVal' is the one that carries a value saying whether to cross ## validate at all; the other four ask for it by being present. cvNames = {'kfold', 'holdout', 'leaveout', 'cvpartition'}; crossval = false; for k = 1:2:numel (varargin) if (! ischar (varargin{k})) continue; endif if (any (strcmpi (varargin{k}, cvNames))) crossval = true; elseif (strcmpi (varargin{k}, 'crossval')) val = varargin{k+1}; if (! (ischar (val) && any (strcmpi (val, {'on', 'off'})))) error ("%s: 'CrossVal' must be either 'on' or 'off'.", 'fitrkernel'); endif crossval = crossval || strcmpi (val, 'on'); endif endfor if (crossval) if (nargout > 1) error (strcat ("fitrkernel: a cross validated model has no", ... " FitInfo to return; ask for the model alone.")); endif Mdl = RegressionPartitionedKernel (X, Y, varargin{:}); return; endif ## 'CrossVal', 'off' has said its piece and the learner does not take it. keep = true (1, numel (varargin)); for k = 1:2:numel (varargin) if (ischar (varargin{k}) && strcmpi (varargin{k}, 'crossval')) keep(k:k+1) = false; endif endfor varargin = varargin(keep); Mdl = RegressionKernel (X, Y, varargin{:}); if (nargout > 1) FitInfo = fitInfo_ (Mdl); endif endfunction %!demo %! ## Fit a Gaussian kernel regression to fuel consumption and read what the %! ## optimization did. %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! [Mdl, FitInfo] = fitrkernel (X(ok,:), MPG(ok)) %!test %! ## The driver returns a kernel regression model %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! Mdl = fitrkernel (X(ok,:), MPG(ok)); %! assert_equal (class (Mdl), 'RegressionKernel'); %! assert_equal (Mdl.Epsilon, 0.926612305411416, 1e-12); %!test %! ## The options reach the model %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! Mdl = fitrkernel (X(ok,:), MPG(ok), 'Learner', 'leastsquares', ... %! 'Standardize', true, 'NumExpansionDimensions', 64); %! assert_equal (Mdl.Learner, 'leastsquares'); %! assert_equal (Mdl.NumExpansionDimensions, 64); %! assert_equal (Mdl.Mu, mean (X(ok,:)), 1e-12); %!test %! ## The second output describes the optimization %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! [~, FitInfo] = fitrkernel (X(ok,:), MPG(ok)); %! assert_equal (FitInfo.Solver, 'LBFGS-fast'); %! assert_equal (FitInfo.LossFunction, 'epsiloninsensitive'); %!test %! ## A cross-validation option returns a partitioned model instead %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! X = X(ok,:); %! Y = MPG(ok); %! CVMdl = fitrkernel (X, Y, 'KFold', 5); %! assert_equal (class (CVMdl), 'RegressionPartitionedKernel'); %! assert_equal (CVMdl.KFold, 5); %! assert_equal (numel (CVMdl.Trained), 5); %!test %! ## 'CrossVal' on gives the ten folds it defaults to, and 'off' the model %! ## itself %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! X = X(ok,:); %! Y = MPG(ok); %! CVMdl = fitrkernel (X, Y, 'CrossVal', 'on'); %! assert_equal (CVMdl.KFold, 10); %! assert_equal (class (fitrkernel (X, Y, 'CrossVal', 'off')), ... %! 'RegressionKernel'); %!error ... %! [Mdl, FitInfo] = fitrkernel (ones (10, 2), ones (10, 1), 'KFold', 3); ## Test input validation %!error fitrkernel (ones (5, 2)) %!error ... %! fitrkernel (ones (10, 2), ones (10, 1), 'Learner') %!error ... %! fitrkernel (ones (10, 2), ones (10, 1), 'Learner', 'logistic') statistics-release-1.9.2/inst/Supervised_Learning/fitrlinear.m000066400000000000000000000160271524624707500246170ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} fitrlinear (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{Mdl} =} fitrlinear (@dots{}, @var{name}, @var{value}) ## @deftypefnx {statistics} {[@var{Mdl}, @var{FitInfo}] =} fitrlinear (@dots{}) ## ## Fit a linear regression model. ## ## @code{@var{Mdl} = fitrlinear (@var{X}, @var{Y})} returns a ## @qcode{RegressionLinear} object fitted to the predictor data @var{X} and ## the continuous response @var{Y}, where @var{X} is an @math{NxP} numeric ## matrix and @var{Y} an @math{Nx1} numeric vector with as many rows as ## @var{X}. ## ## @code{@var{Mdl} = fitrlinear (@dots{}, @var{name}, @var{value})} passes ## the given @qcode{Name-Value} pairs to the model. They are documented ## under @code{RegressionLinear}, and the ones most often wanted are ## @qcode{'Learner'}, @qcode{'Epsilon'}, @qcode{'Regularization'}, ## @qcode{'Lambda'} and @qcode{'Solver'}. ## ## @code{[@var{Mdl}, @var{FitInfo}] = fitrlinear (@dots{})} also returns a ## structure describing the optimization: what it converged to, how far it ## got, and which tolerance stopped it. Its fields follow the solver, so a ## dual fit reports the dual variables and a mini-batch fit the batch it ## stopped on. ## ## @code{@var{Mdl} = fitrlinear (@dots{}, @var{cvopt}, @var{value})} returns a ## @code{RegressionPartitionedLinear} ## instead when one of @qcode{'CrossVal'}, @qcode{'KFold'}, ## @qcode{'Holdout'}, @qcode{'Leaveout'} and @qcode{'CVPartition'} is ## given. A cross-validated model describes no single fit, so ## @var{FitInfo} is not available beside it. ## ## @seealso{RegressionLinear, RegressionKernel, fitrkernel} ## @end deftypefn function [Mdl, FitInfo] = fitrlinear (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitrlinear: too few input arguments."); endif if (mod (numel (varargin), 2) != 0) error ("fitrlinear: name-value arguments must be in pairs."); endif ## A cross-validation option asks for a partitioned model rather than a ## fitted one, and the two are different classes with different methods. ## MATLAB refuses a second output there, having no single fit to describe, ## and so does this. ## 'CrossVal' is the one that carries a value saying whether to cross ## validate at all; the other four ask for it by being present. cvNames = {'kfold', 'holdout', 'leaveout', 'cvpartition'}; crossval = false; for k = 1:2:numel (varargin) if (! ischar (varargin{k})) continue; endif if (any (strcmpi (varargin{k}, cvNames))) crossval = true; elseif (strcmpi (varargin{k}, 'crossval')) val = varargin{k+1}; if (! (ischar (val) && any (strcmpi (val, {'on', 'off'})))) error ("%s: 'CrossVal' must be either 'on' or 'off'.", 'fitrlinear'); endif crossval = crossval || strcmpi (val, 'on'); endif endfor if (crossval) if (nargout > 1) error (strcat ("fitrlinear: a cross validated model has no", ... " FitInfo to return; ask for the model alone.")); endif Mdl = RegressionPartitionedLinear (X, Y, varargin{:}); return; endif ## 'CrossVal', 'off' has said its piece and the learner does not take it. keep = true (1, numel (varargin)); for k = 1:2:numel (varargin) if (ischar (varargin{k}) && strcmpi (varargin{k}, 'crossval')) keep(k:k+1) = false; endif endfor varargin = varargin(keep); Mdl = RegressionLinear (X, Y, varargin{:}); if (nargout > 1) FitInfo = fitInfo_ (Mdl); endif endfunction %!demo %! ## Fit a linear regression to fuel consumption and read what the %! ## optimization did. %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! [Mdl, FitInfo] = fitrlinear (X(ok,:), MPG(ok), 'Learner', 'leastsquares') %!test %! ## The driver returns what the class constructor returns %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! M1 = fitrlinear (X(ok,:), MPG(ok)); %! M2 = RegressionLinear (X(ok,:), MPG(ok)); %! assert_equal (class (M1), 'RegressionLinear'); %! assert_equal (M1.Beta, M2.Beta); %! assert_equal (M1.Epsilon, M2.Epsilon); %!test %! ## The options reach the model %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! Mdl = fitrlinear (X(ok,:), MPG(ok), 'Learner', 'leastsquares', ... %! 'Lambda', 0.02, 'ResponseName', 'mpg'); %! assert_equal (Mdl.Learner, 'leastsquares'); %! assert_equal (Mdl.Lambda, 0.02); %! assert_equal (Mdl.ResponseName, 'mpg'); %!test %! ## The second output describes the optimization %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! [~, FitInfo] = fitrlinear (X(ok,:), MPG(ok)); %! assert_equal (FitInfo.Solver, {'bfgs'}); %! assert_equal (FitInfo.Lambda, 1 / 93, 1e-15); %! assert_equal (isfield (FitInfo, 'Objective'), true); %!test %! ## A cross-validation option returns a partitioned model instead %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! X = X(ok,:); %! Y = MPG(ok); %! CVMdl = fitrlinear (X, Y, 'KFold', 5); %! assert_equal (class (CVMdl), 'RegressionPartitionedLinear'); %! assert_equal (CVMdl.KFold, 5); %! assert_equal (numel (CVMdl.Trained), 5); %!test %! ## 'CrossVal' on gives the ten folds it defaults to, and 'off' the model %! ## itself %! load carsmall %! X = [Acceleration, Displacement, Horsepower, Weight]; %! ok = ! any (isnan ([X, MPG]), 2); %! X = X(ok,:); %! Y = MPG(ok); %! CVMdl = fitrlinear (X, Y, 'CrossVal', 'on'); %! assert_equal (CVMdl.KFold, 10); %! assert_equal (class (fitrlinear (X, Y, 'CrossVal', 'off')), ... %! 'RegressionLinear'); %!error ... %! [Mdl, FitInfo] = fitrlinear (ones (10, 2), ones (10, 1), 'KFold', 3); ## Test input validation %!error fitrlinear (ones (5, 2)) %!error ... %! fitrlinear (ones (10, 2), ones (10, 1), 'Learner') %!error ... %! fitrlinear (ones (10, 2), ones (10, 1), 'Learner', 'logistic') statistics-release-1.9.2/inst/Supervised_Learning/fitrnet.m000066400000000000000000000263171524624707500241360ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} fitrnet (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{Mdl} =} fitrnet (@dots{}, @var{name}, @var{value}) ## ## Fit a neural network regression model. ## ## @code{@var{Mdl} = fitrnet (@var{X}, @var{Y})} returns a neural network ## regression model, @var{Mdl}, with @var{X} being the predictor data and ## @var{Y} the continuous response of the observations in @var{X}. ## ## @itemize ## @item ## @var{X} must be an @math{NxP} numeric matrix of predictor data, where rows ## correspond to observations and columns to features or variables. ## @item ## @var{Y} must be an @math{Nx1} numeric vector holding the response of the ## corresponding predictor data in @var{X}. @var{Y} must have the same number ## of rows as @var{X}. ## @end itemize ## ## The network is trained against the mean squared error and its output layer ## applies the identity, so a prediction is an unrestricted real number. Use ## @code{fitcnet} where the response names a class rather than a quantity. ## ## @code{@var{Mdl} = fitrnet (@dots{}, @var{name}, @var{value})} returns a ## neural network regression model with additional options specified by ## @qcode{Name-Value} pair arguments listed below. ## ## @subheading Model Parameters ## ## @multitable @columnfractions 0.32 0.68 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Standardize'} @tab A logical scalar indicating whether the ## data in @var{X} should be centred and scaled before training. The same ## transformation is applied by @code{predict}. The default is @qcode{false}. ## ## @item @qcode{'PredictorNames'} @tab A cell array of character vectors ## specifying the predictor variable names, in the order they appear in ## @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector specifying the name of ## the response variable. The default is @qcode{'Y'}. ## ## @item @qcode{'ResponseTransform'} @tab A character vector naming one of ## @qcode{'none'}, @qcode{'identity'}, @qcode{'exp'} or @qcode{'log'}, or a ## function handle of one argument, applied to the predicted response by ## @code{predict} and @code{resubPredict}. The default is @qcode{'none'}. ## ## @item @qcode{'LayerSizes'} @tab A vector of positive integers defining the ## number of units in each fully connected hidden layer. The default value is ## 10, a single hidden layer of ten units. ## ## @item @qcode{'LearningRate'} @tab A positive scalar value that defines the ## learning rate during the gradient descent. Default value is 0.003. A ## larger rate can drive every unit of a hidden layer negative, after which a ## rectifier passes no gradient and the network stops training. ## Applies only when @qcode{'Solver'} is @qcode{'sgd'}. ## ## @item @qcode{'Solver'} @tab A character vector naming the solver that ## trains the network, either @qcode{'lbfgs'} or @qcode{'sgd'}. The ## default is @qcode{'lbfgs'}, which minimizes the loss over the whole ## training set at once by limited-memory BFGS, as MATLAB does. It takes ## no learning rate, stops on the three tolerances below, and reaches a ## lower training loss in fewer passes over the data, though each of its ## iterations costs several passes where an epoch costs one. ## @qcode{'sgd'} visits the samples one at a time and steps down the ## gradient of each, running for @qcode{'IterationLimit'} epochs; it was ## the default before version 1.9.0. ## ## @item @qcode{'GradientTolerance'} @tab A nonnegative scalar. Training ## stops once the gradient's infinity norm falls to or below it, which is ## the quantity MATLAB tests too. The default is @qcode{1e-6}. Applies ## only when @qcode{'Solver'} is @qcode{'lbfgs'}. ## ## @item @qcode{'StepTolerance'} @tab A nonnegative scalar. Training ## stops once the step's infinity norm falls to or below it, which is the ## quantity MATLAB tests too. The default is @qcode{1e-6}. Applies only ## when @qcode{'Solver'} is @qcode{'lbfgs'}. ## ## @item @qcode{'LossTolerance'} @tab A real scalar. Training stops once ## the training loss falls to or below it. The test is on the loss ## itself and not on its change, matching MATLAB; pass @code{-Inf} to ## switch it off. The default is @qcode{1e-6}. Applies only when ## @qcode{'Solver'} is @qcode{'lbfgs'}. ## ## @item @qcode{'Activations'} @tab A character vector or a cellstr vector ## specifying the activation functions for the hidden layers of the neural ## network, excluding the output layer. The available activation functions ## are @qcode{'linear'}, @qcode{'none'}, @qcode{'sigmoid'}, @qcode{'relu'}, ## @qcode{'tanh'}, @qcode{'lrelu'}, @qcode{'prelu'}, @qcode{'elu'} and ## @qcode{'gelu'}. The default value is @qcode{'relu'}. ## ## @item @qcode{'OutputLayerActivation'} @tab A character vector specifying ## the activation function for the output layer. The available functions are ## the same as for @qcode{'Activations'}. The default value is ## @qcode{'none'}, the identity, which is what a regression output calls for; ## anything else bounds the prediction to that function's range. ## ## @item @qcode{'IterationLimit'} @tab A positive integer scalar specifying ## the maximum number of training iterations. The default value is 1000. ## Under @qcode{'sgd'} this counts epochs, under ## @qcode{'lbfgs'} solver iterations. ## ## @item @qcode{'DisplayInfo'} @tab A logical scalar indicating whether to ## print information during training. Default is @qcode{false}. ## @end multitable ## ## ## The weights of each layer are drawn from a uniform range whose half-width ## is set by that layer's activation, and the scheme cannot be chosen: a ## rectifying activation (@qcode{'relu'}, @qcode{'lrelu'}, @qcode{'prelu'}, ## @qcode{'elu'}, @qcode{'gelu'}) takes the He range ## @math{sqrt (6 / fan_in)}, because it passes only half of its input, and ## the remaining activations take the Glorot range ## @math{sqrt (6 / (fan_in + fan_out))}, which accounts for the backward pass ## as well. A network whose layers do not share an activation is therefore ## built with both schemes. What each layer was given is reported by the ## @qcode{LayerWeightsInitializers} field of the fitted model's ## @qcode{ModelParameters}. ## ## @seealso{RegressionNeuralNetwork, fitcnet, fcnntrain, fcnnpredict} ## @end deftypefn function obj = fitrnet (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitrnet: too few arguments."); endif if (mod (nargin, 2) != 0) error ("fitrnet: Name-Value arguments must be in pairs."); endif ## Check predictor data and response have equal rows if (rows (X) != rows (Y)) error ("fitrnet: number of rows in X and Y must be equal."); endif ## Parse arguments to classdef constructor obj = RegressionNeuralNetwork (X, Y, varargin{:}); endfunction %!demo %! ## 1. Predict fuel economy from engine power and weight %! %! load carsmall %! X = [Horsepower, Weight]; %! Mdl = fitrnet (X, MPG, 'Standardize', true, 'IterationLimit', 500); %! %! ## Rows carrying a missing value were dropped, so ask about the ones used %! used = Mdl.RowsUsed; %! yFit = predict (Mdl, X(used,:)); %! plot (MPG(used), yFit, 'o', [5, 45], [5, 45], 'k-'); %! axis equal; %! xlabel ('Observed MPG'); %! ylabel ('Predicted MPG'); %! title (sprintf ('Neural network fit, RMSE %.2f', sqrt (resubLoss (Mdl)))); %!demo %! ## 2. Watching the fit converge %! %! load carsmall %! Mdl = fitrnet ([Horsepower, Weight], MPG, 'Standardize', true, ... %! 'IterationLimit', 400); %! %! ## TrainingHistory records the mean squared error at every iteration %! h = Mdl.TrainingHistory; %! semilogy (h.Iteration, h.TrainingLoss, 'linewidth', 1.5); %! xlabel ('Iteration'); %! ylabel ('Training MSE'); %! title ('The loss recorded is the network''s own, not a running average'); %!demo %! ## 3. Standardizing matters when the predictors differ in scale %! %! ## Horsepower runs to a few hundred and Weight to a few thousand, so the %! ## heavier column dominates the first layer until both are put on one scale. %! load carsmall %! X = [Horsepower, Weight]; %! raw = fitrnet (X, MPG, 'IterationLimit', 400); %! std_ = fitrnet (X, MPG, 'Standardize', true, 'IterationLimit', 400); %! printf ('RMSE, raw predictors : %.2f\n', sqrt (resubLoss (raw))); %! printf ('RMSE, standardized : %.2f\n', sqrt (resubLoss (std_))); %!demo %! ## 4. A network recovers a curve a straight line cannot %! %! rng (42); %! x = linspace (-3, 3, 120)'; %! y = sin (x) + randn (120, 1) * 0.1; %! Mdl = fitrnet (x, y, 'LayerSizes', [16, 16], 'IterationLimit', 800); %! plot (x, y, 'o', 'markersize', 4); %! hold on; %! plot (x, predict (Mdl, x), 'r-', 'linewidth', 2); %! plot (x, [ones(120,1), x] * ([ones(120,1), x] \ y), 'k--', 'linewidth', 1.5); %! hold off; %! legend ({'data', 'neural network', 'least squares line'}); %! title ('Two hidden layers of sixteen units'); ## Test constructor %!test %! rand ('seed', 42); %! X = linspace (-1, 1, 40)'; %! Y = 2 * X + 0.5; %! Mdl = fitrnet (X, Y, 'IterationLimit', 50); %! assert_equal (class (Mdl), 'RegressionNeuralNetwork'); %! assert_equal (numel (Mdl.LayerWeights), 2); %! assert_equal (size (Mdl.LayerWeights{1}), [10, 1]); %! assert_equal (size (Mdl.LayerBiases{1}), [10, 1]); %! assert_equal (size (Mdl.LayerWeights{2}), [1, 10]); %! assert_equal (size (Mdl.LayerBiases{2}), [1, 1]); ## The driver passes its Name-Value pairs straight through %!test %! rand ('seed', 42); %! X = linspace (0, 1, 30)'; %! Mdl = fitrnet (X, 3 * X, 'LayerSizes', [5, 5], 'Solver', 'sgd', ... %! 'LearningRate', 0.01, 'IterationLimit', 40, ... %! 'ResponseName', 'speed'); %! assert_equal (Mdl.LayerSizes, [5, 5]); %! assert_equal (Mdl.LearningRate, 0.01); %! assert_equal (Mdl.IterationLimit, 40); %! assert_equal (Mdl.ResponseName, 'speed'); ## A model on real data with missing values trains on the rows that remain %!test %! rand ('seed', 42); %! load carsmall %! X = [Horsepower, Weight]; %! Mdl = fitrnet (X, MPG, 'Standardize', true, 'IterationLimit', 200); %! keep = ! isnan (MPG); %! assert_equal (Mdl.NumObservations, sum (keep)); %! assert_equal (Mdl.RowsUsed, keep); %! assert_equal (numel (resubPredict (Mdl)), sum (keep)); ## Test input validation %!error fitrnet () %!error fitrnet (ones (4, 1)) %!error ... %! fitrnet (ones (4, 2), ones (4, 1), 'LayerSizes') %!error ... %! fitrnet (ones (4, 2), ones (3, 1)) %!error ... %! fitrnet (ones (4, 2), ones (3, 1), 'LayerSizes', 2) statistics-release-1.9.2/inst/Supervised_Learning/fitrsvm.m000066400000000000000000000210721524624707500241460ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{Mdl} =} fitrsvm (@var{X}, @var{Y}) ## @deftypefnx {statistics} {@var{Mdl} =} fitrsvm (@dots{}, @var{name}, @var{value}) ## ## Fit a support vector machine regression model. ## ## @code{@var{Mdl} = fitrsvm (@var{X}, @var{Y})} returns a support vector ## regression model, @var{Mdl}, with @var{X} being the predictor data and ## @var{Y} the continuous response of the observations in @var{X}. ## ## @itemize ## @item ## @var{X} must be an @math{NxP} numeric matrix of predictor data, where rows ## correspond to observations and columns to features or variables. ## @item ## @var{Y} must be an @math{Nx1} numeric vector holding the response of the ## corresponding predictor data in @var{X}. @var{Y} must have the same number ## of rows as @var{X}. ## @end itemize ## ## The model is fitted by @math{epsilon}-insensitive regression: an error ## smaller than @qcode{Epsilon} costs nothing, so only the observations ## outside that tube become support vectors. Use @code{fitcsvm} where the ## response names a class rather than a quantity. ## ## @code{@var{Mdl} = fitrsvm (@dots{}, @var{name}, @var{value})} returns a ## model with additional options specified by @qcode{Name-Value} pair ## arguments listed below. ## ## @subheading Model Parameters ## ## @multitable @columnfractions 0.32 0.68 ## @headitem @var{Name} @tab @var{Value} ## ## @item @qcode{'Standardize'} @tab A logical scalar indicating whether the ## data in @var{X} should be centred and scaled before training. The same ## transformation is applied by @code{predict}. The default is @qcode{false}. ## ## @item @qcode{'PredictorNames'} @tab A cell array of character vectors ## specifying the predictor variable names, in the order they appear in ## @var{X}. ## ## @item @qcode{'ResponseName'} @tab A character vector specifying the name of ## the response variable. The default is @qcode{'Y'}. ## ## @item @qcode{'ResponseTransform'} @tab A character vector naming one of ## @qcode{'none'}, @qcode{'identity'}, @qcode{'exp'} or @qcode{'log'}, or a ## function handle of one argument, applied to the predicted response. The ## default is @qcode{'none'}. ## ## @item @qcode{'Epsilon'} @tab A non-negative scalar, the half-width of the ## insensitive tube. The default is @code{iqr (@var{Y}) / 13.49}, a robust ## estimate of a tenth of the response's standard deviation, which is what ## MATLAB uses; where that is zero it falls back to @math{0.1}. ## ## @item @qcode{'BoxConstraint'} @tab A positive scalar bounding the dual ## coefficients, the cost of an error outside the tube. The default is 1. ## ## @item @qcode{'KernelFunction'} @tab A character vector naming the kernel, ## one of @qcode{'linear'}, the default, @qcode{'rbf'}, @qcode{'gaussian'}, ## @qcode{'polynomial'} or @qcode{'sigmoid'}. ## ## @item @qcode{'PolynomialOrder'} @tab A positive integer, the order of the ## polynomial kernel. The default is 3. It is ignored by every other kernel. ## ## @item @qcode{'KernelScale'} @tab A positive scalar dividing the predictors ## before the kernel is applied. The default is 1. ## ## @item @qcode{'KernelOffset'} @tab A non-negative scalar added to the kernel ## value. The default is 0. ## ## @item @qcode{'SVMtype'} @tab A character vector selecting the formulation, ## either @qcode{'eps_svr'}, the default, or @qcode{'nu_svr'}. MATLAB fits ## only the @math{epsilon} form; @qcode{'nu_svr'} is an Octave extension. ## ## @item @qcode{'Nu'} @tab A scalar in @math{(0, 1]} used by ## @qcode{'nu_svr'}, bounding the fraction of support vectors. The default ## is 0.5. ## ## @item @qcode{'CacheSize'} @tab A positive scalar, the kernel cache in ## megabytes. The default is 1000. ## ## @item @qcode{'Tolerance'} @tab A non-negative scalar, the tolerance of the ## termination criterion. The default is @math{1e-6}. ## ## @item @qcode{'Shrinking'} @tab Either 0 or 1, whether to use the shrinking ## heuristic. The default is 1. ## @end multitable ## ## @seealso{RegressionSVM, fitcsvm, fitrnet, svmtrain, svmpredict} ## @end deftypefn function obj = fitrsvm (X, Y, varargin) ## Check input parameters if (nargin < 2) error ("fitrsvm: too few arguments."); endif if (mod (nargin, 2) != 0) error ("fitrsvm: Name-Value arguments must be in pairs."); endif ## Check predictor data and response have equal rows if (rows (X) != rows (Y)) error ("fitrsvm: number of rows in X and Y must be equal."); endif ## Parse arguments to classdef constructor obj = RegressionSVM (X, Y, varargin{:}); endfunction %!demo %! ## 1. Predict fuel economy from engine power and weight %! %! load carsmall %! X = [Horsepower, Weight]; %! Mdl = fitrsvm (X, MPG, 'Standardize', true); %! %! ## Rows carrying a missing value were dropped, so ask about the ones used %! used = Mdl.RowsUsed; %! yFit = predict (Mdl, X(used,:)); %! plot (MPG(used), yFit, 'o', [5, 45], [5, 45], 'k-'); %! axis equal; %! xlabel ('Observed MPG'); %! ylabel ('Predicted MPG'); %! title (sprintf ('Linear SVR, RMSE %.2f', sqrt (resubLoss (Mdl)))); %!demo %! ## 2. The insensitive tube decides who becomes a support vector %! %! ## Errors smaller than Epsilon cost nothing, so a wider tube is fitted by %! ## fewer observations and a narrower one by almost all of them. %! load carsmall %! X = [Horsepower, Weight]; %! eps_ = [0.1, 0.5, 1, 2, 4, 8]; %! nsv = zeros (size (eps_)); %! for k = 1:numel (eps_) %! m = fitrsvm (X, MPG, 'Standardize', true, 'Epsilon', eps_(k)); %! nsv(k) = sum (m.IsSupportVector); %! endfor %! plot (eps_, nsv, 'o-', 'linewidth', 1.5); %! xlabel ('Epsilon'); %! ylabel ('Number of support vectors'); %! title ('A wider tube needs fewer support vectors'); %!demo %! ## 3. A radial kernel fits a curve a linear one cannot %! %! rng (42); %! x = linspace (-3, 3, 120)'; %! y = sin (x) + randn (120, 1) * 0.1; %! lin = fitrsvm (x, y); %! rbf = fitrsvm (x, y, 'KernelFunction', 'rbf', 'BoxConstraint', 10); %! plot (x, y, 'o', 'markersize', 4); %! hold on; %! plot (x, predict (lin, x), 'k--', 'linewidth', 1.5); %! plot (x, predict (rbf, x), 'r-', 'linewidth', 2); %! hold off; %! legend ({'data', 'linear kernel', 'rbf kernel'}); %! title ('Support vector regression'); %!demo %! ## 4. With a linear kernel the model is a plain linear function %! %! load carsmall %! X = [Horsepower, Weight]; %! Mdl = fitrsvm (X, MPG, 'Standardize', true); %! used = Mdl.RowsUsed; %! Xs = (X(used,:) - Mdl.Mu) ./ Mdl.Sigma; %! printf ('max |X*Beta + Bias - predict| = %g\n', ... %! max (abs (Xs * Mdl.Beta + Mdl.Bias - resubPredict (Mdl)))); ## Test constructor %!test %! load carsmall %! X = [Horsepower, Weight]; %! Mdl = fitrsvm (X, MPG, 'Standardize', true); %! assert_equal (class (Mdl), 'RegressionSVM'); %! assert_equal (Mdl.NumPredictors, 2); %! assert_equal (Mdl.ModelParameters.KernelFunction, 'linear'); %! assert_equal (Mdl.ModelParameters.SVMtype, 'eps_svr'); %! assert_equal (Mdl.Epsilon, iqr (MPG(Mdl.RowsUsed)) / 13.49, 1e-12); ## The driver passes its Name-Value pairs straight through %!test %! X = [linspace(0, 1, 30)', linspace(1, 2, 30)']; %! Y = 3 * X(:,1) + 1; %! Mdl = fitrsvm (X, Y, 'KernelFunction', 'rbf', 'BoxConstraint', 5, ... %! 'Epsilon', 0.05, 'ResponseName', 'speed'); %! assert_equal (Mdl.ModelParameters.KernelFunction, 'rbf'); %! assert_equal (Mdl.ModelParameters.BoxConstraint, 5); %! assert_equal (Mdl.Epsilon, 0.05); %! assert_equal (Mdl.ResponseName, 'speed'); %! assert_equal (isempty (Mdl.Beta), true); ## Test input validation %!error fitrsvm () %!error fitrsvm (ones (4, 1)) %!error ... %! fitrsvm (ones (4, 2), ones (4, 1), 'KernelFunction') %!error ... %! fitrsvm (ones (4, 2), ones (3, 1)) %!error ... %! fitrsvm (ones (4, 2), ones (3, 1), 'Epsilon', 1) statistics-release-1.9.2/inst/Supervised_Learning/private/000077500000000000000000000000001524624707500237465ustar00rootroot00000000000000statistics-release-1.9.2/inst/Supervised_Learning/private/autoKernelScale.m000066400000000000000000000036011524624707500272050ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{sigma} =} autoKernelScale (@var{X}) ## ## The kernel scale a kernel learner resolves @qcode{'auto'} to. ## ## @end deftypefn ## The kernel scale MATLAB calls 'auto'. MathWorks documents a heuristic ## that subsamples and warns that its estimates vary from one call to the ## next, so no particular number is the parity target. This one is the ## median distance between observations, which is the usual choice and is ## reproducible: a kernel that wide puts about half the pairs inside one ## bandwidth of each other. Beyond a thousand observations the median is ## taken over a random thousand of them, the full matrix being quadratic. function sigma = autoKernelScale (X) n = rows (X); if (n > 1000) X = X(randperm (n, 1000), :); n = 1000; endif if (n < 2) sigma = 1; return; endif d = zeros (n * (n - 1) / 2, 1); at = 0; for i = 1:n-1 k = n - i; d(at+1:at+k) = sqrt (sum ((X(i+1:end,:) - X(i,:)) .^ 2, 2)); at += k; endfor sigma = median (d); if (! (sigma > 0)) sigma = 1; endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/classCount.m000066400000000000000000000024731524624707500262500ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{k} =} classCount (@var{C}) ## How many classes a @code{ClassNames} holds, whatever type it is in. ## ## A cell array of character vectors, a numeric column and a logical column ## each hold one class per element, but a character matrix holds one per ## @emph{row} and as many columns as the longest name, so @code{numel} counts ## its characters rather than its classes. ## @end deftypefn function k = classCount (C) if (ischar (C)) k = rows (C); else k = numel (C); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/classFrame.m000066400000000000000000000142731524624707500262130ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{F} =} classFrame (@var{X}, @var{Y}, @var{ClassNames}, @var{Prior}, @var{Cost}, @var{Weights}, @var{classname}) ## ## Resolve the classes, the prior, the cost and the observation weights of a ## binary classifier. ## ## The four linear and kernel classifiers all begin the same way: group the ## response, keep only the named classes, drop the rows that are not ## complete, and turn what survives into a signed response and a weight per ## observation. @var{F} is a structure carrying the results, so that the ## classifier and its cross-validated counterpart cannot drift apart. ## ## Fields: @qcode{X} and @qcode{Y}, the retained data; @qcode{RowsUsed}, a ## logical over the rows as supplied; @qcode{gY}, the class index of each ## retained observation; @qcode{ClassNames}, in the type of @var{Y}; ## @qcode{Prior} and @qcode{Cost}, as the model reports them; ## @qcode{Weights}, the retained weights before normalization; @qcode{W}, ## normalized within each class to that class's cost-adjusted prior and ## summing to one; @qcode{y}, @math{+1} for the second class and @math{-1} ## for the first; and @qcode{n} and @qcode{p}. ## ## @var{classname} names the caller in every error message. ## ## @end deftypefn function F = classFrame (X, Y, ClassNames, Prior, Cost, Weights, classname) if (! (isnumeric (X) && isreal (X) && ismatrix (X) && ndims (X) == 2)) error ("%s: invalid values in X.", classname); endif if (isempty (X)) error ("%s: X is empty.", classname); endif if (rows (X) != rows (Y)) error ("%s: number of rows in X and Y must be equal.", classname); endif [gY, gnY, glY] = grp2idx (Y); ## Keep only the named classes, if any were named. Names given as text ## are matched against grp2idx's own names, which are always a cell array ## of character vectors. A character matrix must be turned into one ## first: ismember on two character matrices compares them character by ## character and answers a question nobody asked. if (! isempty (ClassNames)) if (iscellstr (ClassNames) || ischar (ClassNames)) drop = find (! ismember (gnY, cellstr (ClassNames))); else drop = find (! ismember (glY, ClassNames)); endif for i = 1:numel (drop) gY(gY == drop(i)) = NaN; endfor endif ## Weights are validated against the data as supplied, then follow it ## through the rows that are kept. if (isempty (Weights)) Weights = ones (rows (X), 1); else Weights = Weights(:); if (numel (Weights) != rows (X)) error ("%s: 'Weights' must have one element per observation.", ... classname); endif endif RowsUsed = ! (isnan (gY) | any (isnan (X), 2)); ## Index the rows, not the elements: a character matrix of class names has ## one row per observation and several columns, and a linear index would ## flatten it into single characters. F = struct (); F.RowsUsed = RowsUsed; F.X = X(RowsUsed, :); F.Y = Y(RowsUsed, :); F.Weights = Weights(RowsUsed); if (isempty (F.Y)) error ("%s: no complete observations in the data.", classname); endif [gY, gnY, glY] = grp2idx (F.Y); nclasses = numel (gnY); F.gY = gY; F.ClassNames = glY; [F.n, F.p] = size (F.X); if (nclasses != 2) error (strcat ("%s: Y must name exactly two classes, this being a", ... " binary model; use fitcecoc for more than two."), ... classname); endif ## Prior defaults to the weighted frequencies of the training data and ## Cost to zero on the diagonal and one elsewhere. if (isstruct (Prior)) Prior = priorFromStruct (Prior, F.ClassNames, classname); endif freq = accumarray (gY(:), F.Weights(:), [nclasses, 1])' / sum (F.Weights); if (isempty (Prior) || (ischar (Prior) && strcmpi (Prior, 'empirical'))) Prior = freq; elseif (ischar (Prior) && strcmpi (Prior, 'uniform')) Prior = ones (1, nclasses) / nclasses; else if (numel (Prior) != nclasses) error ("%s: 'Prior' must have one entry per class.", classname); endif Prior = Prior(:)' / sum (Prior); endif F.Prior = Prior; if (isempty (Cost)) Cost = ones (nclasses) - eye (nclasses); elseif (rows (Cost) != nclasses || columns (Cost) != nclasses) error (strcat ("%s: the number of rows and columns in 'Cost' must", ... " correspond to the classes in Y."), classname); endif F.Cost = Cost; ## MATLAB folds the cost matrix into the prior before it weights the ## observations: a class that is costlier to misclassify carries more ## weight, in proportion to the total cost of getting it wrong. The Prior ## the model reports is the one it was given, not this one. Measured on ## R2024a: a fit with Cost [0 4; 1 0] and one with Prior [0.8 0.2] return ## the same coefficients to every digit, and giving both together lands ## back on the empirical fit, since [0.2 0.8] scaled by [4 1] is uniform ## again. adjPrior = Prior .* sum (Cost, 2)'; if (sum (adjPrior) > 0) adjPrior = adjPrior / sum (adjPrior); else adjPrior = Prior; endif W = zeros (F.n, 1); for k = 1:nclasses idx = (gY == k); tot = sum (F.Weights(idx)); if (tot > 0) W(idx) = F.Weights(idx) / tot * adjPrior(k); endif endfor if (sum (W) > 0) W = W / sum (W); endif F.W = W; ## The positive class is the second of ClassNames, so a positive score ## belongs to it. F.y = -ones (F.n, 1); F.y(gY == 2) = 1; endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/classMembers.m000066400000000000000000000026741524624707500265550ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{tf} =} classMembers (@var{Y}, @var{C}, @var{i}) ## Which observations of a response belong to the @var{i}th class. ## ## The complement of @code{labelIndex}: that asks which class an observation ## is in, this asks which observations are in a class. A response naming its ## classes in the rows of a character matrix is matched as whole names, with ## the padding such a matrix carries stripped from both sides. ## @end deftypefn function tf = classMembers (Y, C, i) if (ischar (C) || ischar (Y)) tf = strcmp (cellstr (Y), strtrim (char (labelsFromIndex (C, i)))); else tf = ismember (Y, C(i)); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/classNameListing.m000066400000000000000000000031571524624707500273720ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{str} =} classNameListing (@var{ClassNames}) ## ## The class names as a model's display lists them, quoted when they are ## text and braced as a set. ## ## @end deftypefn ## The classes as the display lists them, quoted when they are text. function str = classNameListing (ClassNames) if (iscellstr (ClassNames)) fmt = repmat ({'''%s'''}, 1, numel (ClassNames)); fmt = strjoin (fmt, ' '); str = sprintf (fmt, ClassNames{:}); elseif (ischar (ClassNames)) cn = cellstr (ClassNames); fmt = repmat ({'''%s'''}, 1, numel (cn)); fmt = strjoin (fmt, ' '); str = sprintf (fmt, cn{:}); else fmt = repmat ({'%g'}, 1, numel (ClassNames)); fmt = strjoin (fmt, ' '); str = sprintf (fmt, ClassNames); endif str = ['{', str, '}']; endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/classificationLoss.m000066400000000000000000000057231524624707500277670ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{l} =} classificationLoss (@var{LossFun}, @var{s}, @var{gY}, @var{w}, @var{Cost}) ## ## One of MATLAB's classification losses, over an @math{NxK} score matrix. ## ## @var{s} holds the scores after @qcode{ScoreTransform}, @var{gY} the index ## into the class names of each observation's true class, @var{w} weights ## summing to one, and @var{Cost} the misclassification cost matrix. ## ## The rival of the true class is the best of the others, which for two ## classes is simply the other one, so a binary caller is unaffected. ## ## Every loss but the two cost-based ones is a function of the score the ## model gives the @emph{true} class, and not of the margin. Measured on ## R2024a across both learners: with an untransformed support vector machine ## the two readings differ by a factor of two, and with a logit transformed ## logistic regression they differ by an affine map, and the true-class score ## is the one that reproduces all six losses in both cases. ## ## @end deftypefn ## The classification losses MATLAB offers, all of them functions of the ## score the model gives the true class. function l = classificationLoss (LossFun, s, gY, w, Cost) n = rows (s); idx = sub2ind (size (s), (1:n)', gY); strue = s(idx); ## The best score among the classes that are not the true one so = s; so(idx) = -Inf; [sother, kother] = max (so, [], 2); switch (LossFun) case 'classiferror' l = sum (w .* (strue <= sother)); case 'hinge' l = sum (w .* max (0, 1 - strue)); case 'quadratic' l = sum (w .* ((1 - strue) .^ 2)); case 'logit' l = sum (w .* log (1 + exp (-strue))); case 'binodeviance' l = sum (w .* log (1 + exp (-2 * strue))); case 'exponential' l = sum (w .* exp (-strue)); case 'mincost' expected = s * Cost; [~, k] = min (expected, [], 2); l = sum (w .* Cost(sub2ind (size (Cost), gY, k))); case 'classifcost' ## The class the model would pick, the true one keeping a tie k = gY; worse = sother > strue; k(worse) = kother(worse); l = sum (w .* Cost(sub2ind (size (Cost), gY, k))); endswitch endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/costMatrix.m000066400000000000000000000130741524624707500262660ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{C}, @var{errmsg}] =} costMatrix (@var{val}, @var{ClassNames}) ## Resolve a misclassification cost to a plain double matrix in the model's ## own class order. ## ## @var{val} is a numeric matrix, taken in the order the model holds its ## classes, or a struct with the ## fields @qcode{ClassNames} and @qcode{ClassificationCosts}, whose matrix is ## written in the order that struct names and is permuted into the order of ## @var{ClassNames}. The struct form is how a caller supplies costs without ## having to know which order the model sorted its classes into. ## ## A cost must be floating point, not sparse, not complex, square with one row ## and column per class, non-negative, zero down the diagonal, and free of ## @qcode{NaN} and @qcode{Inf}. A @code{single} is widened to @code{double} ## rather than refused. All of it measured against MATLAB R2024a. ## ## @var{errmsg} is the body of the message the caller should raise, or empty ## when there is nothing wrong; the caller emits it under its own ## @code{class.method} name, as the package's shared validation helpers do. ## An empty @var{val} is returned unchanged, the caller owning what an empty ## cost resets to. ## @end deftypefn function [C, errmsg] = costMatrix (val, ClassNames) C = val; errmsg = ''; if (isempty (val)) return; endif K = classCount (ClassNames); ## A struct names the order its matrix is written in. Permuting it into the ## model's order is not a convenience: the two orders genuinely differ, and ## MATLAB permutes entry by entry, so this is measured behaviour and not a ## reading of the matrix as given. if (isstruct (val)) if (! (isfield (val, 'ClassNames') && isfield (val, 'ClassificationCosts'))) errmsg = strcat ("'Cost' given as a struct must have the fields", ... " 'ClassNames' and 'ClassificationCosts'."); return; endif given = val.ClassNames; C = val.ClassificationCosts; n = classCount (given); if (! isequal (size (C), [n, n])) errmsg = strcat ("'Cost' given as a struct must have one row and", ... " one column of 'ClassificationCosts' per name in", ... " 'ClassNames'."); return; endif ## Matching is by name, so the struct must name the classes in the type ## the model holds them in. A type it cannot compare reaches ismember ## and raises rather than coming back as a message, so it is caught here ## and named for what it is; labelIndices speaks of a response, which a ## cost is not. perm = []; try perm = labelIndices (given, ClassNames); catch perm = []; end_try_catch if (isempty (perm)) errmsg = strcat ("'Cost' given as a struct must name the classes in", ... " the same type as the model holds them."); return; endif ## A class of the model that the struct does not name has no cost to ## permute, which MATLAB refuses rather than defaulting. missing = find (perm == 0, 1); if (! isempty (missing)) errmsg = sprintf (strcat ("'Cost' given as a struct must name every", ... " class; there is no cost for class %d."), ... missing); return; endif C = C(perm, perm); endif ## Char, cell and logical are none of them costs. An integer type is a ## number but not a cost either: MATLAB refuses it in its own right, and ## says so differently, so the two are kept apart here as well. if (! isnumeric (C)) errmsg = strcat ("'Cost' must be a numeric matrix, or a struct with", ... " the fields 'ClassNames' and 'ClassificationCosts'."); return; endif if (isinteger (C)) errmsg = "'Cost' must be a floating point matrix."; return; endif if (issparse (C)) errmsg = "'Cost' must not be sparse."; return; endif ## Complex means a nonzero imaginary part, not the complex attribute: ## complex (C, 0) is accepted and stored as a double, measured on R2024a. ## Writing this as ! isreal (C) would refuse a value MATLAB takes. if (any (imag (C(:)) != 0)) errmsg = "'Cost' must not be complex."; return; endif ## Single is widened rather than refused, and the imaginary part of an ## accepted complex value is dropped with it. C = double (real (C)); if (! isequal (size (C), [K, K])) errmsg = strcat ("the number of rows and columns in 'Cost' must", ... " correspond to selected classes in Y."); return; endif if (any (C(:) < 0)) errmsg = "'Cost' must not contain negative values."; elseif (any (diag (C) != 0)) errmsg = "'Cost' must have zeros on its diagonal."; elseif (any (! isfinite (C(:)))) errmsg = "'Cost' must not contain NaN or Inf values."; endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/cvPartitionOf.m000066400000000000000000000111421524624707500267120ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{part}, @var{args}] =} cvPartitionOf (@var{args}, @var{Y}, @var{n}, @var{classname}) ## ## Take the cross-validation options out of an argument list and build the ## partition they ask for. ## ## @var{args} is the caller's @code{varargin}; what comes back is the same ## list with @qcode{'CrossVal'}, @qcode{'KFold'}, @qcode{'Holdout'}, ## @qcode{'Leaveout'} and @qcode{'CVPartition'} removed, so the remainder ## can be handed to the learner that fits each fold. Only one of the last ## four may be given. ## ## @var{Y} is the response, used to stratify a classification partition so ## that every fold holds the classes in the proportions the data does; pass ## an empty @var{Y} for a regression model, which is partitioned by count ## alone. @var{n} is the number of observations. ## ## The default is ten folds, which is what @qcode{'CrossVal', 'on'} asks ## for and what a caller that named no option at all gets. ## ## @end deftypefn function [part, args] = cvPartitionOf (args, Y, n, classname) numFolds = 10; Holdout = []; Leaveout = 'off'; CVPartition = []; given = 0; keep = {}; while (numel (args) > 0) if (numel (args) < 2) error (strcat ("%s: optional arguments must be given in Name-Value", ... " pairs."), classname); endif switch (lower (args{1})) case 'crossval' val = args{2}; if (! (ischar (val) && any (strcmpi (val, {'on', 'off'})))) error ("%s: 'CrossVal' must be either 'on' or 'off'.", classname); endif case 'kfold' numFolds = args{2}; if (! (isnumeric (numFolds) && isscalar (numFolds) && isreal (numFolds) && numFolds == fix (numFolds) && numFolds > 1)) error (strcat ("%s: 'KFold' must be an integer value greater", ... " than 1."), classname); endif given++; case 'holdout' Holdout = args{2}; if (! (isnumeric (Holdout) && isscalar (Holdout) && isreal (Holdout) && Holdout > 0 && Holdout < 1)) error (strcat ("%s: 'Holdout' must be a numeric value between", ... " 0 and 1."), classname); endif given++; case 'leaveout' Leaveout = args{2}; if (! (ischar (Leaveout) && any (strcmpi (Leaveout, {'on', 'off'})))) error ("%s: 'Leaveout' must be either 'on' or 'off'.", classname); endif given++; case 'cvpartition' CVPartition = args{2}; if (! isa (CVPartition, 'cvpartition')) error ("%s: 'CVPartition' must be a cvpartition object.", ... classname); endif given++; otherwise keep = [keep, args(1:2)]; endswitch args(1:2) = []; endwhile if (given > 1) error (strcat ("%s: you can use only one of 'KFold', 'Holdout',", ... " 'Leaveout', or 'CVPartition' options."), classname); endif if (! isempty (CVPartition)) part = CVPartition; if (part.NumObservations != n) error (strcat ("%s: 'CVPartition' must be built over the same", ... " number of observations as the data."), classname); endif elseif (! isempty (Holdout)) part = stratified (Y, n, 'Holdout', Holdout); elseif (strcmpi (Leaveout, 'on')) part = cvpartition (n, 'LeaveOut'); else part = stratified (Y, n, 'KFold', numFolds); endif args = keep; endfunction ## Partition by the response when there is one to stratify by, and by count ## alone otherwise. Leave-one-out has nothing to stratify: every fold holds ## exactly one observation. function part = stratified (Y, n, kind, value) if (isempty (Y)) part = cvpartition (n, kind, value); else part = cvpartition (Y, kind, value); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/discardSVs.m000066400000000000000000000036011524624707500261710ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{m} =} discardSVs (@var{m}) ## Collapse a linear LIBSVM model onto the one vector that decides it. ## ## A linear kernel makes the decision function @math{sum_i c_i - rho}, ## which is @math{ - rho} for @math{w = sum_i c_i s_i}. One support ## vector holding @math{w}, with a coefficient of one, therefore decides ## exactly what all of them did, and the rest need not be kept. ## ## This is what lets the support vectors be discarded without touching a ## single scoring path: @code{svmpredict} goes on being the engine, over a ## model of one vector instead of many. Emptying the properties alone would ## save nothing, the engine's own copy being where they are kept. ## ## The model must be linear. Callers check that, so that the error names the ## method the user called. Collapsing an already collapsed model returns it ## unchanged, one vector of @math{w} summing to @math{w}. ## @end deftypefn function m = discardSVs (m) w = m.sv_coef' * m.SVs; m.SVs = sparse (w); m.sv_coef = 1; m.totalSV = 1; m.nSV = [1; 0]; m.sv_indices = 1; endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/discrimcanon.m000066400000000000000000000041001524624707500265700ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{t} =} discrimcanon (@var{str}) ## @deftypefnx {Private Function} {[@var{t}, @var{fam}] =} discrimcanon (@var{str}) ## ## Canonical spelling and family of a discriminant type. ## ## @var{t} is @var{str} respelled as the discriminant classes report it, which ## is case sensitive: @qcode{'diagLinear'}, not @qcode{'diaglinear'}. It is ## empty when @var{str} names no discriminant type, which is how the callers ## detect an invalid value. ## ## @var{fam} is @qcode{'linear'} or @qcode{'quadratic'}, the family @var{t} ## belongs to, and empty alongside an empty @var{t}. The family decides which ## covariances a fit has to keep: a linear one pools across classes and a ## quadratic one estimates a covariance per class, and no assignment ever moves ## a model between the two. ## ## @end deftypefn function [t, fam] = discrimcanon (str) lin = {'linear', 'diagLinear', 'pseudoLinear'}; quad = {'quadratic', 'diagQuadratic', 'pseudoQuadratic'}; t = ''; fam = ''; if (! (ischar (str) && isrow (str))) return; endif idx = find (strcmpi (str, lin)); if (! isempty (idx)) t = lin{idx}; fam = 'linear'; return; endif idx = find (strcmpi (str, quad)); if (! isempty (idx)) t = quad{idx}; fam = 'quadratic'; endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/discrimcoeffs.m000066400000000000000000000057311524624707500267520ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{Coeffs} =} discrimcoeffs (@var{Mu}, @var{Sigma}, @var{LogDetSigma}, @var{Prior}, @var{DiscrimType}, @var{Delta}, @var{ClassNames}) ## ## Pairwise boundary coefficients of a discriminant, a @math{KxK} structure ## whose @math{(i,j)} entry separates class @math{i} from class @math{j}. ## ## The linear family reports @code{Const} and @code{Linear}; the quadratic one ## adds @code{Quadratic}, which follows @var{Sigma}'s shape and so is ## @math{1xP} for @qcode{'diagQuadratic'}. The diagonal entries carry the two ## class names and nothing else. ## ## @end deftypefn function Coeffs = discrimcoeffs (Mu, Sigma, LogDetSigma, Prior, ... DiscrimType, Delta, ClassNames) K = rows (Mu); [~, fam] = discrimcanon (DiscrimType); isquad = strcmp (fam, 'quadratic'); isdiag = strncmp (DiscrimType, 'diag', 4); SigmaInv = discriminv (Sigma, DiscrimType); if (! isquad) [Z, bk] = discrimlinear (Mu, Sigma, Prior, DiscrimType, Delta); endif Coeffs = struct (); for i = 1:K for j = 1:K Coeffs(i,j).DiscrimType = ''; Coeffs(i,j).Const = []; Coeffs(i,j).Linear = []; if (isquad) Coeffs(i,j).Quadratic = []; endif Coeffs(i,j).Class1 = ClassNames(i,:); Coeffs(i,j).Class2 = ClassNames(j,:); if (i == j) continue; endif Coeffs(i,j).DiscrimType = DiscrimType; if (isquad) Si = SigmaInv(:,:,i); Sj = SigmaInv(:,:,j); L = Si * Mu(i,:)' - Sj * Mu(j,:)'; C = -0.5 * (Mu(i,:) * Si * Mu(i,:)' - Mu(j,:) * Sj * Mu(j,:)') ... - 0.5 * (LogDetSigma(i) - LogDetSigma(j)) ... + log (Prior(i) / Prior(j)); Q = -0.5 * (Si - Sj); if (isdiag) ## A diagonal model reports the diagonal, matching Sigma's own shape. Q = diag (Q)'; endif Coeffs(i,j).Quadratic = Q; else ## Taken from the per-class coefficients, so a predictor Delta has ## eliminated is eliminated from every pair that names it. L = (Z(i,:) - Z(j,:))'; C = bk(i) - bk(j); endif Coeffs(i,j).Linear = L; Coeffs(i,j).Const = C; endfor endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/discrimderive.m000066400000000000000000000111711524624707500267560ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{Sigma}, @var{LogDetSigma}, @var{Gamma}] =} discrimderive (@var{Base}, @var{DiscrimType}, @var{Gamma}, @var{MinGamma}) ## ## Derive a discriminant's covariance from the covariance its fit estimated. ## ## @var{Base} is the unregularized within-class covariance, @math{PxP} for the ## linear family and @math{PxPxK} for the quadratic one. It is the state a fit ## keeps, and every type in the same family is derived from it, which is what ## makes @code{DiscrimType} assignable after the fit. ## ## @var{Sigma} takes the shape the type calls for: @math{PxP} or @math{PxPxK} ## for the plain and pseudo types, @math{1xP} or @math{1xPxK} for the diagonal ## ones. @var{LogDetSigma} is a scalar for the linear family and @math{Kx1} for ## the quadratic one. ## ## @var{Gamma} is returned as well as taken, because the type and the ## regularization are one state: a diagonal type is @math{Gamma = 1}, a pseudo ## type is @math{Gamma = 0}, and a plain linear type on a singular covariance is ## raised to @var{MinGamma}, which describes the data and is measured once by ## @code{discrimmingamma}. ## ## @end deftypefn function [Sigma, LogDetSigma, Gamma] = discrimderive (Base, DiscrimType, ... Gamma, MinGamma) P = columns (Base); K = size (Base, 3); LogDetSigma = zeros (K, 1); ## Every type takes its log determinant in correlation space, which is not a ## nicety: on a rank-3-of-4 iris fixture the direct log (det (Sigma)) gives ## -41.2988 where the oracle reports -41.3112, the correlation form matching ## to every digit. Splitting the scale off leaves a matrix of ones on the ## diagonal, which is far better conditioned to take a determinant of. if (strncmp (DiscrimType, 'diag', 4)) ## A diagonal type is Gamma = 1: regularizing all the way to the diagonal ## and naming the diagonal type are the same state, in both directions. ## Its correlation matrix is the identity, so only the scale term remains. Gamma = 1; Sigma = zeros (1, P, K); for k = 1:K d = diag (Base(:,:,k))'; Sigma(1,:,k) = d; LogDetSigma(k) = sum (log (d(d > 0))); endfor elseif (strncmp (DiscrimType, 'pseudo', 6)) ## The pseudo types never regularize. They invert whatever rank is there, ## so a singular covariance is a case they answer rather than refuse, and ## the determinant runs over the directions that carry variance. Gamma = 0; Sigma = Base; for k = 1:K [d, R] = correlate (Base(:,:,k)); ev = eig (R)'; tol = max (size (R)) * eps (max (ev)); LogDetSigma(k) = sum (log (d)) + sum (log (ev(ev > tol))); endfor else Sigma = zeros (P, P, K); ## MinGamma is the least regularization that makes the correlation matrix ## invertible, and a plain linear type is raised to it rather than failing. ## The quadratic family is not: it refuses a singular class covariance ## outright, so a bump there would only mask the error, and Gamma on a ## quadratic discriminant may be nothing but 0 or 1. lin = (K == 1); for k = 1:K S = Base(:,:,k); D = diag (diag (S)); if (lin) g = max (Gamma, MinGamma); else g = Gamma; endif Sigma(:,:,k) = S * (1 - g) + D * g; [d, R] = correlate (Sigma(:,:,k)); LogDetSigma(k) = sum (log (d)) + log (det (R)); endfor if (lin) Gamma = max (Gamma, MinGamma); endif endif endfunction ## Split a covariance into its scale and its correlation, keeping only the ## predictors that carry variance. A predictor with none contributes nothing ## to the determinant rather than driving it to -Inf. function [d, R] = correlate (S) d = diag (S)'; pos = d > 0; d = d(pos); if (isempty (d)) R = []; return; endif R = S(pos, pos) ./ sqrt (d' * d); R = (R + R') / 2; endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/discriminv.m000066400000000000000000000047071524624707500263030ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{SigmaInv} =} discriminv (@var{Sigma}, @var{DiscrimType}) ## ## Inverse within-class covariance of a discriminant, one @math{PxP} slice per ## class, whatever shape @var{Sigma} is stored in. ## ## The diagonal types invert elementwise, leaving a zero where a predictor has ## no variance, so that predictor contributes nothing rather than an infinity. ## The pseudo types take a pseudo-inverse in correlation space, which is the ## same convention their @code{LogDetSigma} uses. ## ## @end deftypefn function SigmaInv = discriminv (Sigma, DiscrimType) K = size (Sigma, 3); if (strncmp (DiscrimType, 'diag', 4)) P = columns (Sigma); SigmaInv = zeros (P, P, K); for k = 1:K d = Sigma(1,:,k); v = zeros (1, P); v(d > 0) = 1 ./ d(d > 0); SigmaInv(:,:,k) = diag (v); endfor elseif (strncmp (DiscrimType, 'pseudo', 6)) P = columns (Sigma); SigmaInv = zeros (P, P, K); for k = 1:K SigmaInv(:,:,k) = pseudoinverse (Sigma(:,:,k)); endfor else SigmaInv = zeros (size (Sigma)); for k = 1:K SigmaInv(:,:,k) = inv (Sigma(:,:,k)); endfor endif endfunction ## Pseudo-inverse taken on the correlation matrix and scaled back, the same ## convention the pseudo log determinant uses. A predictor with no variance is ## left out and comes back as a zero row and column, which is how the oracle ## reports its coefficient. function Si = pseudoinverse (S) P = columns (S); Si = zeros (P, P); d = diag (S)'; pos = d > 0; if (! any (pos)) return; endif dp = d(pos); R = S(pos, pos) ./ sqrt (dp' * dp); Si(pos, pos) = pinv ((R + R') / 2) ./ sqrt (dp' * dp); endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/discrimlinear.m000066400000000000000000000055341524624707500267600ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{Z}, @var{b}, @var{DeltaPredictor}] =} discrimlinear (@var{Mu}, @var{Sigma}, @var{Prior}, @var{DiscrimType}, @var{Delta}) ## ## Per-class linear coefficients of a discriminant, after @var{Delta}. ## ## A linear discriminant scores class @math{k} as @math{Z(k,:) * x' + b(k)}, ## which is the Mahalanobis form with the terms common to every class dropped. ## The coefficients are taken about the prior-weighted mean of the class means, ## ## @example ## @var{Z} = (@var{Mu} - @var{Prior} * @var{Mu}) / @var{Sigma} ## @end example ## ## @var{Delta} eliminates a predictor by zeroing coefficients, and it compares ## against the @strong{standardized} coefficient, @code{Z(k,j) * s(j)} with ## @math{s} the within-class standard deviations. Scaling matters: a threshold ## on the raw coefficients would depend on the units each predictor is measured ## in, so a centimetre and a metre would eliminate different predictors. ## ## @var{DeltaPredictor} is the @var{Delta} at which each predictor drops out ## altogether, @code{max (abs (Z(:,j)) * s(j))} over the classes. ## ## The quadratic family has no linear coefficients to eliminate and does not ## reach here. ## ## @end deftypefn function [Z, b, DeltaPredictor] = discrimlinear (Mu, Sigma, Prior, ... DiscrimType, Delta) K = rows (Mu); mubar = Prior(:)' * Mu; SigmaInv = discriminv (Sigma, DiscrimType); Z = (Mu - mubar) * SigmaInv(:,:,1); if (strncmp (DiscrimType, 'diag', 4)) sd = sqrt (Sigma(1,:,1)); else sd = sqrt (diag (Sigma(:,:,1)))'; endif DeltaPredictor = max (abs (Z) .* sd, [], 1); if (K == 1) DeltaPredictor = abs (Z) .* sd; endif if (Delta > 0) Z(abs (Z) .* sd <= Delta) = 0; endif ## The constant a shrunken centroid implies. At Delta of 0 this is the ## ordinary linear discriminant constant, the terms it drops being the same ## for every class and so lost in the normalization. b = zeros (1, K); for k = 1:K b(k) = -0.5 * (Z(k,:) * (Mu(k,:) + mubar)') + log (Prior(k)); endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/discrimlogp.m000066400000000000000000000040231524624707500264370ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{lp} =} discrimlogp (@var{X}, @var{Mu}, @var{Sigma}, @var{LogDetSigma}, @var{Prior}, @var{DiscrimType}) ## ## Log unconditional probability density of every row of @var{X} under a ## discriminant, returned as an @math{Nx1} vector. ## ## The density is @math{P(x) = sum_k P(k) P(x|k)} over the classes, with ## @math{P(k)} the prior and @math{P(x|k)} the multivariate normal density of ## class @math{k}. The class log densities are formed from the squared ## Mahalanobis distances and @var{LogDetSigma}, so the value follows the ## covariance the model reports rather than a second one derived here, and ## they are summed by their maximum so a well separated observation is a ## density rather than a sum of underflowed zeros. ## ## @end deftypefn function lp = discrimlogp (X, Mu, Sigma, LogDetSigma, Prior, DiscrimType) K = rows (Mu); P = columns (X); M = discrimmahal (X, Mu, Sigma, DiscrimType); logdet = LogDetSigma; if (isscalar (logdet)) logdet = repmat (logdet, K, 1); endif L = zeros (rows (X), K); for k = 1:K L(:, k) = -0.5 * (M(:, k) + logdet(k) + P * log (2 * pi)) ... + log (Prior(k)); endfor mx = max (L, [], 2); lp = mx + log (sum (exp (L - mx), 2)); endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/discrimmahal.m000066400000000000000000000033261524624707500265650ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{M} =} discrimmahal (@var{X}, @var{Mu}, @var{Sigma}, @var{DiscrimType}) ## ## Squared Mahalanobis distance from every row of @var{X} to every class mean ## of a discriminant, returned as an @math{NxK} matrix. ## ## @var{M}(i,j) is @math{(x_i - mu_j)' * inv (S_j) * (x_i - mu_j)}, where ## @math{S_j} is the class covariance for a quadratic type and the one shared ## covariance for a linear one. The inverse comes from @code{discriminv}, so ## the regularized covariance the model reports is the one measured against, ## and the diagonal and pseudo types keep their own conventions. ## ## @end deftypefn function M = discrimmahal (X, Mu, Sigma, DiscrimType) K = rows (Mu); SigmaInv = discriminv (Sigma, DiscrimType); nInv = size (SigmaInv, 3); M = zeros (rows (X), K); for k = 1:K Si = SigmaInv(:,:,min (k, nInv)); Z = X - Mu(k, :); M(:, k) = sum ((Z * Si) .* Z, 2); endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/discrimmingamma.m000066400000000000000000000036061524624707500272720ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{g} =} discrimmingamma (@var{S}) ## ## Least regularization that leaves a covariance invertible. ## ## @var{g} is the smallest multiple of @code{eps} for which ## @code{(1 - @var{g}) * R + @var{g} * I} is invertible, where @var{R} is ## @var{S}'s correlation matrix, and 0 when @var{R} already is. Multiples of ## @code{eps} are the quantization the oracle's own readings show, 0 on a well ## conditioned fit and 8 and 10 times @code{eps} on two singular ones. ## ## It describes the data rather than any one discriminant type, so it is taken ## once from the pooled within-class covariance and reported by every type, ## whether or not that type regularizes. ## ## @end deftypefn function g = discrimmingamma (S) g = 0; d = diag (S)'; pos = d > 0; if (! any (pos)) return; endif dp = d(pos); R = S(pos, pos) ./ sqrt (dp' * dp); R = (R + R') / 2; n = rows (R); I = eye (n); if (rcond (R) > n * eps) return; endif for m = 1:64 gm = m * eps; if (rcond (R * (1 - gm) + I * gm) > n * eps) g = gm; return; endif endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/edgeWeights.m000066400000000000000000000100141524624707500263570ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{W} =} edgeWeights (@var{args}, @var{Y}, @var{ClassNames}, @var{Prior}, @var{classname}, @var{caller}) ## ## Normalized observation weights of an @code{edge} call. ## ## @var{args} is the @code{varargin} of the caller, which accepts a single ## @qcode{'Weights'} Name-Value pair; without one the weights are uniform. ## ## The weights are then normalized @strong{within each class to that class's ## prior}, which is what MATLAB does and is not the same as dividing by their ## total. It matters whenever the weights are not uniform: on a weighted iris ## edge the two readings are 0.9269539697 and 0.9438468986, and the latter is ## the oracle's. Normalizing this way keeps a class's total influence equal to ## its prior however the weights inside it are distributed, so reweighting ## observations cannot quietly reweight the classes. ## ## @var{classname} and @var{caller} name the class and the method in the error ## messages, so a class reports its own name rather than this helper's. ## ## @end deftypefn function W = edgeWeights (args, Y, ClassNames, Prior, classname, caller) n = rows (Y); W = ones (n, 1); for i = 1:2:numel (args) if (! (ischar (args{i}) && isrow (args{i}))) error ("%s.%s: parameter name must be a character vector.", ... classname, caller); endif if (strcmpi (args{i}, 'weights')) W = args{i+1}; if (! (isnumeric (W) && isvector (W))) error ("%s.%s: 'Weights' must be a numeric vector.", ... classname, caller); endif if (numel (W) != n) error (strcat ("%s.%s: size of 'Weights' must equal the number", ... " of rows in X."), classname, caller); endif else error (strcat ("%s.%s: invalid parameter name in optional paired", ... " arguments."), classname, caller); endif endfor W = double (W(:)); ## Which class each observation belongs to. Matching against ClassNames is ## the general route; a response already in some other coding, such as the ## +1/-1 a support vector machine accepts, falls back to its own grouping. gY = classindex (Y, ClassNames); if (isempty (gY)) gY = grp2idx (Y); Prior = ones (1, max (gY)) / max (gY); endif for k = 1:numel (Prior) ck = (gY == k); if (any (ck) && sum (W(ck)) > 0) W(ck) = W(ck) / sum (W(ck)) * Prior(k); endif endfor endfunction ## Class index of every element of Y against ClassNames, empty when any ## element fails to match. function gY = classindex (Y, ClassNames) gY = []; n = rows (Y); idx = cell (n, 1); if (iscellstr (ClassNames)) if (! (iscellstr (Y) || ischar (Y))) return; endif Yc = cellstr (Y); for i = 1:n idx{i} = find (strcmp (Yc{i}, ClassNames), 1); endfor elseif (ischar (ClassNames)) CN = cellstr (ClassNames); if (! (iscellstr (Y) || ischar (Y))) return; endif Yc = cellstr (Y); for i = 1:n idx{i} = find (strcmp (Yc{i}, CN), 1); endfor else if (! (isnumeric (Y) || islogical (Y))) return; endif for i = 1:n idx{i} = find (Y(i) == ClassNames(:), 1); endfor endif if (any (cellfun (@isempty, idx))) return; endif gY = cell2mat (idx); endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/fcnnInitializers.m000066400000000000000000000046251524624707500274460ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{init} =} fcnnInitializers (@var{acts}, @var{nlayers}, @var{outact}) ## ## The weight initializer each layer of a fully connected network is built ## with. ## ## @var{acts} names the hidden layers, either as one character vector applying ## to all @var{nlayers} of them or as a cellstring naming them one by one, and ## @var{outact} names the output layer. @var{init} is a cellstring of ## @var{nlayers}+1 entries, the output layer last, each one @qcode{'he'} or ## @qcode{'glorot'}. ## ## The scheme is not a setting and cannot be chosen: @code{init_scale} in ## @file{src/fcnn.cpp} picks it from the layer's activation alone, the ## rectifiers taking the wider He range because they pass only half their ## input and the symmetric activations taking Glorot, which accounts for the ## backward pass as well. This function restates that rule so the model can ## report what it was built with; the two must be changed together. ## ## @end deftypefn function init = fcnnInitializers (acts, nlayers, outact) ## The rectifiers, by the names fcnntrain accepts for them. Everything ## else the engine knows ('linear', 'none', 'sigmoid', 'tanh', 'softmax') ## takes Glorot. rectifiers = {'relu', 'lrelu', 'prelu', 'elu', 'gelu'}; if (ischar (acts)) names = repmat ({acts}, 1, nlayers); else names = cellstr (acts); names = names(:)'; endif names = [names, {outact}]; init = cell (1, numel (names)); for i = 1:numel (names) if (any (strcmpi (rectifiers, names{i}))) init{i} = 'he'; else init{i} = 'glorot'; endif endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/foldModels.m000066400000000000000000000046451524624707500262250ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{Trained} =} foldModels (@var{learner}, @var{F}, @var{Partition}, @var{args}) ## ## Fit one model per fold of a partition. ## ## @var{learner} names the class to fit, @var{F} is the frame ## @code{classFrame} returned (or, for a regression model, a structure ## carrying @qcode{X}, @qcode{Y} and @qcode{Weights} alone), @var{Partition} ## a @code{cvpartition} over the retained observations, and @var{args} the ## Name-Value pairs each fold is fitted with. ## ## Every fold is given the parent's class names, prior and cost rather than ## being left to re-derive them from its own rows. Measured on R2024a: the ## prior of a fold of an unbalanced problem is the parent's, not the fold's ## own frequencies. A fold that happened to hold one class only would ## otherwise renumber the classes, and every score assembled from it would ## be inverted without anything raising. ## ## What is @emph{not} passed down is any option that resolves to ## @qcode{'auto'}. Each fold works those out from its own training rows, so ## a fold of a hundred observations partitioned five ways gets ## @qcode{Lambda} of one eightieth rather than one hundredth, and its own ## @qcode{Epsilon}. Both were measured rather than assumed. ## ## @end deftypefn function Trained = foldModels (learner, F, Partition, args) K = Partition.NumTestSets; Trained = cell (K, 1); for k = 1:K idx = training (Partition, k); fargs = args; if (! isempty (F.Weights)) fargs = [fargs, {'Weights', F.Weights(idx)}]; endif Trained{k} = feval (learner, F.X(idx,:), F.Y(idx,:), fargs{:}); endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/foldSets.m000066400000000000000000000041341524624707500257110ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{sets} =} foldSets (@var{Partition}, @var{Folds}, @var{Mode}, @var{n}) ## ## The sets of observations a @code{kfold} method reports over. ## ## Under @qcode{'individual'} there is one set per named fold, holding the ## observations that fold held out. Under @qcode{'average'} there is one ## set holding the observations of all the named folds together. ## ## The distinction matters, and it is not the one the name suggests: ## averaging pools the observations rather than averaging the per-fold ## values, so folds of unequal size do not weigh equally. Measured on ## R2024a: a four-fold cross-validation of carsmall, whose folds hold 24, ## 23, 23 and 23 observations, reports 64.0992586069075 where the mean of ## its own four fold losses is 64.2539. The first is the mean squared error ## over all ninety-three out-of-fold predictions, and that is what MATLAB ## returns. ## ## @end deftypefn function sets = foldSets (Partition, Folds, Mode, n) if (strcmp (Mode, 'individual')) sets = cell (numel (Folds), 1); for i = 1:numel (Folds) sets{i} = test (Partition, Folds(i)); endfor else pooled = false (n, 1); for i = 1:numel (Folds) pooled = pooled | test (Partition, Folds(i)); endfor sets = {pooled}; endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/gpBasis.m000066400000000000000000000040761524624707500255230ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} gpBasis (@var{X}, @var{basis}) ## ## Build the explicit basis matrix of a Gaussian process regression. ## ## @var{X} is @math{NxD} and the returned @var{H} is @math{NxP}, one column per ## basis term, so that the deterministic part of the model is @qcode{H * Beta}. ## @var{basis} is @qcode{'None'}, @qcode{'Constant'}, @qcode{'Linear'}, ## @qcode{'PureQuadratic'} as the model stores it, or a function handle taking ## @var{X} and returning the matrix itself. ## ## @qcode{'None'} gives a matrix with no columns, so the model has no ## deterministic part and @qcode{Beta} is empty. ## ## @end deftypefn function H = gpBasis (X, basis) n = rows (X); if (is_function_handle (basis)) H = basis (X); if (! isnumeric (H) || ! ismatrix (H) || rows (H) != n) error (strcat ("gpBasis: the basis function must return a matrix", ... " with one row per observation.")); endif return; endif switch (lower (basis)) case 'none' H = zeros (n, 0); case 'constant' H = ones (n, 1); case 'linear' H = [ones(n, 1), X]; case 'purequadratic' H = [ones(n, 1), X, X .^ 2]; otherwise error ("gpBasis: unrecognized basis function '%s'.", basis); endswitch endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/gpKernel.m000066400000000000000000000070251524624707500256770ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} gpKernel (@var{A}, @var{B}, @var{name}, @var{theta}) ## ## Evaluate a Gaussian process covariance function between two sets of points. ## ## @var{A} is @math{NxD} and @var{B} is @math{MxD}, and the returned matrix is ## @math{NxM} holding the covariance of every pair. @var{name} is one of the ## ten built-in covariance functions, spelled as the model stores it, and ## @var{theta} is its parameter vector. ## ## The isotropic kernels take @qcode{[SigmaL; SigmaF]}, the rational quadratic ## @qcode{[SigmaL; AlphaRQ; SigmaF]}, the automatic relevance determination ## kernels one length scale per predictor followed by @qcode{SigmaF}, and the ## ARD rational quadratic those length scales, @qcode{AlphaRQ} and ## @qcode{SigmaF}. Every kernel is stationary, so the covariance depends on ## the two points only through the distance between them, scaled by the length ## scale of each predictor. ## ## @end deftypefn function K = gpKernel (A, B, name, theta) d = columns (A); ard = (numel (name) > 3 && strncmpi (name, 'ARD', 3)); ## Split the parameter vector and scale each predictor by its length scale, ## which reduces every kernel below to a function of the scaled distance. if (ard) L = theta(1:d)(:)'; if (strcmpi (name, 'ARDRationalQuadratic')) alphaRQ = theta(d+1); sigmaF = theta(d+2); else sigmaF = theta(d+1); endif else L = repmat (theta(1), 1, d); if (strcmpi (name, 'RationalQuadratic')) alphaRQ = theta(2); sigmaF = theta(3); else sigmaF = theta(2); endif endif ## Squared distances, accumulated one predictor at a time. The expanded ## form, sum (A.^2) - 2*A*B' + sum (B.^2), is faster but does not return ## exactly zero for a point against itself: it leaves a residue of the order ## of eps, and the square root of 1.8e-15 is 4.2e-8, so a stationary kernel ## stops returning exactly SigmaF^2 on its own diagonal. The rough kernels ## take the square root and inherit that as an error eight orders larger ## than the one it came from. r2 = zeros (rows (A), rows (B)); for j = 1:d r2 += ((A(:,j) - B(:,j)') / L(j)) .^ 2; endfor switch (lower (strrep (lower (name), 'ard', ''))) case 'exponential' K = sigmaF^2 * exp (-sqrt (r2)); case 'squaredexponential' K = sigmaF^2 * exp (-0.5 * r2); case 'matern32' s = sqrt (3) * sqrt (r2); K = sigmaF^2 * (1 + s) .* exp (-s); case 'matern52' s = sqrt (5) * sqrt (r2); K = sigmaF^2 * (1 + s + (5/3) * r2) .* exp (-s); case 'rationalquadratic' K = sigmaF^2 * (1 + r2 / (2 * alphaRQ)) .^ (-alphaRQ); otherwise error ("gpKernel: unrecognized kernel function '%s'.", name); endswitch endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/gpPredict.m000066400000000000000000000073371524624707500260570ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} gpPredict (@var{XC}, @var{M}) ## @deftypefnx {Private Function} [@var{ypred}, @var{ysd}, @var{yint}] = gpPredict (@var{XC}, @var{M}) ## ## Evaluate a fitted Gaussian process at new points. ## ## @var{M} is a structure carrying the fitted model: the active set vectors it ## predicts from, the prediction weights @qcode{Alpha}, the covariance function ## and its parameters, the explicit basis and its coefficients, the noise ## standard deviation, the standardizing location and scale where the model ## standardized its predictors, and the significance level of the intervals. ## ## The mean is the basis term plus the covariance between the new points and ## the active set weighted by @qcode{Alpha}, which is what @qcode{Alpha} is ## for. The standard deviation is that of the @emph{response}, so it carries ## the noise as well as the uncertainty of the latent function, and the ## interval is the normal quantile of the level times it. The coefficients of ## the explicit basis are taken as known, which is measurably what MATLAB does: ## carrying their covariance as well moves the answer in the sixth decimal. ## ## @end deftypefn function [ypred, ysd, yint] = gpPredict (XC, M) ## Standardize the new points exactly as the training data was standardized if (! isempty (M.Location)) XC = (XC - M.Location) ./ M.Scale; endif ## Mean prediction Kq = gpCov (XC, M.X, M); HC = gpBasis (XC, M.BasisFunction); ypred = Kq * M.Alpha; if (! isempty (M.Beta)) ypred += HC * M.Beta(:); endif if (nargout < 2) return; endif ## The variance of a new response is the prior variance of the latent ## function, less what the training data explains of it, plus the noise. ## The factorisation is rebuilt here rather than stored: a compact model ## keeps its active set and nothing else, and this is the only method that ## needs it. n = rows (M.X); A = gpCov (M.X, M.X, M) + M.Sigma^2 * eye (n); V = A \ Kq'; kqq = gpSelfCov (XC, M); vy = kqq - sum (Kq' .* V, 1)' + M.Sigma^2; vy(vy < 0) = 0; ysd = sqrt (vy); if (nargout < 3) return; endif z = norminv (1 - M.CIAlpha / 2); yint = [ypred - z * ysd, ypred + z * ysd]; endfunction ## Covariance between two sets of points, built in or user supplied. function K = gpCov (A, B, M) if (is_function_handle (M.KernelFunction)) K = M.KernelFunction (A, B, M.Theta); else K = gpKernel (A, B, M.KernelFunction, M.Theta); endif endfunction ## The prior variance of a point with itself. Every built-in kernel is ## stationary, so this is the signal variance and does not depend on where the ## point is; a supplied kernel need not be, and is asked. function v = gpSelfCov (XC, M) if (is_function_handle (M.KernelFunction)) v = zeros (rows (XC), 1); for i = 1:rows (XC) v(i) = M.KernelFunction (XC(i,:), XC(i,:), M.Theta); endfor else v = repmat (M.Theta(end)^2, rows (XC), 1); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/interactionPairs.m000066400000000000000000000033471524624707500274510ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{P} =} interactionPairs (@var{M}) ## The predictor index pairs of the two-way terms in a term matrix. ## ## @var{M} is the term matrix a GAM fits, one row per term and one column per ## predictor, true wherever the term multiplies that predictor. @var{P} is ## the @math{Kx2} matrix of predictor indices of the rows naming exactly two ## predictors, in the order those rows appear, and @code{zeros (0, 2)} when ## there are none. ## ## A row naming one predictor is a main effect and a row naming three or more ## is a higher-order term. Neither has a two-column form, so neither appears ## here; the term matrix itself remains the complete record. ## @end deftypefn function P = interactionPairs (M) if (isempty (M)) P = zeros (0, 2); return; endif M = logical (M); keep = find (sum (M, 2) == 2); P = zeros (numel (keep), 2); for i = 1:numel (keep) P(i,:) = find (M(keep(i),:)); endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/kernelBasis.m000066400000000000000000000055431524624707500263750ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{B} =} kernelBasis (@var{p}, @var{m}, @var{sigma}) ## ## Draw a random basis for a Gaussian kernel approximation. ## ## @var{p} is the number of predictors, @var{m} the number of expansion ## dimensions and @var{sigma} the kernel scale. @var{B} is a structure and ## is what @code{kernelExpand} applies to a predictor matrix. ## ## The basis is the random Fourier expansion of Rahimi and Recht. Frequencies ## are drawn from @math{N(0, sigma^-2 I)} and each contributes a cosine and a ## sine of the same frequency, so that ## @code{T(x1) * T(x2)'} has expectation ## @math{exp (-||x1 - x2||^2 / (2 * sigma^2))}, the Gaussian kernel. ## ## The paired form is used rather than the single cosine with a uniform ## phase offset that MathWorks writes down. Both are unbiased for the same ## kernel and cost the same, but pairing removes the variance the random ## phase adds: measured over twenty draws on the iris predictors, the mean ## absolute error of the approximated kernel is 0.034 against 0.071 at 128 ## dimensions and 0.020 against 0.033 at 512, so the pairs approximate about ## twice as closely for the same @var{m}. An odd @var{m} cannot be paired ## throughout, and the odd dimension left over is a phase-offset cosine, ## which carries the same expectation. ## ## MATLAB approximates the kernel by the Fastfood construction, which ## replaces the Gaussian frequency matrix with a product of Hadamard, ## permutation and diagonal factors to reach the same distribution in ## @math{O(M log P)} time. Neither that difference nor this one is ## observable in a single model: the draws come from different generators, so ## a model fitted here and one fitted in MATLAB hold different bases and ## report different scores from any seed. ## ## @end deftypefn function B = kernelBasis (p, m, sigma) pairs = floor (m / 2); odd = m - 2 * pairs; B = struct (); B.Weights = randn (p, pairs) / sigma; B.OddWeights = randn (p, odd) / sigma; B.OddOffset = 2 * pi * rand (1, odd); B.NumDimensions = m; B.Scale = sigma; endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/kernelExpand.m000066400000000000000000000027021524624707500265450ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{T} =} kernelExpand (@var{X}, @var{B}) ## ## Map predictor data into the expanded feature space of a random basis. ## ## @var{X} is an @math{NxP} numeric matrix and @var{B} a basis structure from ## @code{kernelBasis}. @var{T} is @math{NxM}, and the inner product of two ## of its rows approximates the Gaussian kernel of the two rows of @var{X} ## they came from. ## ## @end deftypefn function T = kernelExpand (X, B) c = sqrt (2 / B.NumDimensions); Z = X * B.Weights; T = c * [cos(Z), sin(Z)]; if (! isempty (B.OddOffset)) T = [T, c * cos(X * B.OddWeights + B.OddOffset)]; endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/kernelFitInfo.m000066400000000000000000000037201524624707500266650ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{S} =} kernelFitInfo (@var{info}, @var{LossFun}, @var{Lambda}, @var{BetaTol}, @var{GradTol}) ## ## Assemble the @qcode{FitInfo} structure the kernel fitting functions ## return beside the model. ## ## It is not the structure the linear fitting functions return: a kernel ## model is always fitted by one solver under one regularization strength, ## so the fields are scalars and the ones that describe a choice of solver ## are absent. The names differ too, @qcode{ObjectiveValue} and ## @qcode{GradientMagnitude} here against @qcode{Objective} and ## @qcode{GradientNorm} there, and both spellings are MATLAB's. ## ## @end deftypefn function S = kernelFitInfo (info, LossFun, Lambda, BetaTol, GradTol) S = struct (); S.Solver = 'LBFGS-fast'; S.LossFunction = LossFun; S.Lambda = Lambda; S.BetaTolerance = BetaTol; S.GradientTolerance = GradTol; S.ObjectiveValue = info.Objective; S.GradientMagnitude = info.GradientNorm; if (isempty (info.RelativeChangeInBeta)) S.RelativeChangeInBeta = NaN; else S.RelativeChangeInBeta = info.RelativeChangeInBeta; endif S.FitTime = 0; S.History = []; endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/kfoldOpts.m000066400000000000000000000071431524624707500260760ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{O} =} kfoldOpts (@var{args}, @var{validLoss}, @var{classname}, @var{caller}, @var{K}) ## ## Parse the options the @code{kfold} methods share. ## ## @var{O} carries @qcode{Mode}, either @qcode{'average'} or ## @qcode{'individual'}; @qcode{Folds}, a row of fold indices defaulting to ## all @var{K} of them; and @qcode{LossFun}, empty unless one was given. ## ## @var{validLoss} lists the losses the caller accepts; pass an empty cell ## for a method that takes no @qcode{'LossFun'} at all, and the option is ## then refused like any other unknown name. ## ## @end deftypefn function O = kfoldOpts (args, validLoss, classname, caller, K) O = struct ('Mode', 'average', 'Folds', 1:K, 'LossFun', ''); if (mod (numel (args), 2) != 0) error (strcat ("%s.%s: optional arguments must be given in", ... " Name-Value pairs."), classname, caller); endif while (numel (args) > 0) switch (lower (args{1})) case 'mode' O.Mode = args{2}; if (! (ischar (O.Mode) && any (strcmpi (O.Mode, {'average', 'individual'})))) error (strcat ("%s.%s: 'Mode' must be either 'average' or", ... " 'individual'."), classname, caller); endif O.Mode = lower (O.Mode); case 'folds' O.Folds = args{2}; if (! (isnumeric (O.Folds) && isreal (O.Folds) && isvector (O.Folds) && ! isempty (O.Folds) && all (fix (O.Folds) == O.Folds) && all (O.Folds >= 1) && all (O.Folds <= K))) error (strcat ("%s.%s: 'Folds' must hold integers between 1", ... " and %d."), classname, caller, K); endif O.Folds = O.Folds(:)'; case 'lossfun' if (isempty (validLoss)) error (strcat ("%s.%s: invalid parameter name in optional pair", ... " arguments."), classname, caller); endif O.LossFun = args{2}; if (! (ischar (O.LossFun) && any (strcmpi (O.LossFun, validLoss)))) error ("%s.%s: 'LossFun' must be %s.", classname, caller, ... listing (validLoss)); endif O.LossFun = lower (O.LossFun); otherwise error (strcat ("%s.%s: invalid parameter name in optional pair", ... " arguments."), classname, caller); endswitch args(1:2) = []; endwhile endfunction ## The accepted values, quoted and listed the way an error message wants ## them, with 'or' before the last. function s = listing (names) q = cellfun (@(n) sprintf ("'%s'", n), names, 'UniformOutput', false); if (numel (q) == 1) s = q{1}; elseif (numel (q) == 2) s = sprintf ("%s or %s", q{1}, q{2}); else s = sprintf ("%s, or %s", strjoin (q(1:end-1), ', '), q{end}); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/kfoldResponse.m000066400000000000000000000032621524624707500267450ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{yFit} =} kfoldResponse (@var{Trained}, @var{Partition}, @var{X}, @var{L}) ## ## Assemble the out-of-fold predictions of a cross-validated regression ## model. ## ## Each observation is predicted by the fold that held it out, and an ## observation no fold held out comes back @qcode{NaN} rather than ## predicted. Under a holdout partition that is most of them. @var{yFit} ## is @math{NxL}, one column per regularization strength. ## ## The predictions are untransformed: the fold models carry no response ## transform and the parent applies its own once. ## ## @end deftypefn function yFit = kfoldResponse (Trained, Partition, X, L) yFit = nan (rows (X), L); for k = 1:Partition.NumTestSets idx = test (Partition, k); if (! any (idx)) continue; endif yFit(idx,:) = predict (Trained{k}, X(idx,:)); endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/kfoldScores.m000066400000000000000000000052421524624707500264050ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{labels}, @var{scores}] =} kfoldScores (@var{Trained}, @var{Partition}, @var{X}, @var{Y}, @var{ClassNames}, @var{L}) ## ## Assemble the out-of-fold labels and scores of a cross-validated binary ## classifier. ## ## Each observation is predicted by the fold that held it out, and an ## observation no fold held out is left missing rather than given a class. ## Under a holdout partition that is most of them. @var{Y} is the response, ## used only for its type, so that the labels come back in it. ## ## @var{L} is the number of regularization strengths the fold models carry, ## which is one for a kernel model and one per value of @qcode{'Lambda'} for ## a linear one. @var{scores} is @math{Nx2xL}, or @math{Nx2} when @var{L} ## is one. ## ## The scores are whatever the fold models report, transform included: a ## logistic fold returns posteriors. MATLAB leaves the transform with the ## folds and has the cross-validated model report none of its own, so a ## parent that transformed again would apply it twice. ## ## @end deftypefn function [labels, scores] = kfoldScores (Trained, Partition, X, Y, ... ClassNames, L) n = rows (X); K = Partition.NumTestSets; if (iscellstr (Y)) labels = repmat ({''}, n, L); elseif (islogical (Y)) labels = false (n, L); elseif (ischar (Y)) labels = repmat (' ', n, columns (ClassNames)); else labels = nan (n, L); endif scores = nan (n, 2, L); for k = 1:K idx = test (Partition, k); if (! any (idx)) continue; endif [lab, sc] = predict (Trained{k}, X(idx,:)); if (ischar (Y)) labels(idx,:) = lab; else labels(idx,:) = lab; endif if (L == 1) scores(idx,:,1) = sc; else scores(idx,:,:) = sc; endif endfor if (L == 1) scores = scores(:,:,1); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/knndistparam.m000066400000000000000000000035201524624707500266170ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{p} =} knndistparam (@var{Distance}, @var{X}, @var{standardized}) ## ## Distance parameter a nearest-neighbour metric implies. ## ## Three metrics carry one: @qcode{'minkowski'} the exponent, defaulting to 2; ## @qcode{'seuclidean'} a per-predictor scale, the standard deviations of ## @var{X}, or ones where the predictors were standardized already; and ## @qcode{'mahalanobis'} the covariance of @var{X}. Every other metric has ## none and @var{p} is empty. ## ## It is recomputed rather than carried over whenever @code{Distance} changes, ## which is what the oracle does: a scale belonging to one metric means ## nothing under another. ## ## @end deftypefn function p = knndistparam (Distance, X, standardized) p = []; if (! ischar (Distance)) return; endif switch (lower (Distance)) case 'minkowski' p = 2; case 'seuclidean' if (standardized) p = ones (1, columns (X)); else p = std (X, [], 1); endif case 'mahalanobis' p = cov (X); endswitch endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/labelIndices.m000066400000000000000000000043511524624707500265050ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{gY}, @var{errmsg}] =} labelIndices (@var{C}, @var{Y}) ## The class each observation of a response belongs to, for the whole response. ## ## @var{gY} indexes @var{C} once per observation, and is zero where a label is ## not one of the classes. @var{errmsg} is the body of the message the caller ## should raise, or empty when there is nothing wrong; the caller emits it ## under its own @code{class.method} name, as the package's shared validation ## helpers do. ## ## A response naming its classes in the rows of a character matrix is matched ## as whole names, with the padding such a matrix carries stripped from both ## sides. Resolving every observation in one comparison is what this is for: ## asking per observation inside a loop costs a pass over the classes each ## time. ## @end deftypefn function [gY, errmsg] = labelIndices (C, Y) errmsg = ""; ## A character matrix names one class per row, so it is compared row by row ## rather than element by element, whichever side of the comparison it is on. if (ischar (C)) C = cellstr (C); endif if (iscellstr (C) && ischar (Y)) Y = cellstr (Y); endif if (iscellstr (C) && ! iscellstr (Y)) gY = zeros (rows (Y), 1); errmsg = "Y must be of the same type as the class names."; return; endif [tf, gY] = ismember (Y(:), C(:)); gY = gY(:); if (! all (tf)) errmsg = "Y must hold only classes the model was trained on."; endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/labelsFromIndex.m000066400000000000000000000024441524624707500272060ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{L} =} labelsFromIndex (@var{C}, @var{idx}) ## Pick class labels out of a @code{ClassNames} by index, in its own type. ## ## @var{idx} numbers the classes, and @var{L} holds the label of each, in the ## type @var{C} is in. A character matrix is indexed by row, every other ## accepted type by element, which is the same thing for a column. ## @end deftypefn function L = labelsFromIndex (C, idx) if (ischar (C)) L = C(idx, :); else L = C(idx); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/labelsKnown.m000066400000000000000000000027321524624707500264070ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{tf} =} labelsKnown (@var{Y}, @var{C}) ## Whether every distinct label in a response is one of the known classes. ## ## @code{ismember} compares two character matrices character by character, so ## a response naming its classes in the rows of one is answered against the ## letters of the class names rather than the names. Anything textual is ## compared as whole names here; every other accepted type compares by value. ## @end deftypefn function tf = labelsKnown (Y, C) ## Delegated so the matching rules live in one place: labelIndices reports ## a zero for any label that is not one of the classes. tf = all (labelIndices (C, Y) > 0); endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/linearFitInfo.m000066400000000000000000000070551524624707500266640ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{S} =} linearFitInfo (@var{info}, @var{BetaTol}, @var{GradTol}, @var{DeltaTol}, @var{IterLimit}, @var{Solver}, @var{PassLimit}, @var{BatchLimit}) ## ## Assemble the @qcode{FitInfo} structure the linear fitting functions ## return beside the model. ## ## @var{info} holds one element per regularization strength, as ## @code{linearSolve} reported it, and every field of @var{S} is a row over ## those strengths, so a single strength gives scalars. ## ## The field set depends on the solver, as it does in MATLAB. A ## quasi-Newton or proximal fit reports @qcode{IterationLimit}; a ## mini-batch or dual fit reports @qcode{PassLimit}, @qcode{NumPasses} and ## @qcode{BatchLimit} in its place, and adds @qcode{Alpha} for the dual and ## @qcode{BatchIndex} with @qcode{OptimalLearnRate} for the mini-batch ## solvers. Neither of the latter two runs a gradient test, so both report ## a tolerance of zero and, by the rule that a tolerance of zero means the ## test did not run, a gradient of @qcode{NaN}. ## ## @end deftypefn function S = linearFitInfo (info, BetaTol, GradTol, DeltaTol, IterLimit, ... Solver, PassLimit, BatchLimit) stochastic = any (ismember (Solver, {'sgd', 'asgd', 'dual'})); S = struct (); S.Lambda = [info.Lambda]; S.Objective = [info.Objective]; if (stochastic) S.PassLimit = PassLimit; S.NumPasses = fieldOrNaN (info, 'NumPasses'); S.BatchLimit = BatchLimit; else S.IterationLimit = IterLimit; endif S.NumIterations = [info.NumIterations]; if (stochastic) S.GradientNorm = nan (1, numel (info)); S.GradientTolerance = 0; else S.GradientNorm = [info.GradientNorm]; S.GradientTolerance = GradTol; endif S.RelativeChangeInBeta = fieldOrNaN (info, 'RelativeChangeInBeta'); S.BetaTolerance = BetaTol; if (any (strcmp (Solver, 'dual'))) S.DeltaGradient = fieldOrNaN (info, 'DeltaGradient'); S.DeltaGradientTolerance = DeltaTol; else S.DeltaGradient = []; S.DeltaGradientTolerance = []; endif S.TerminationCode = [info.TerminationCode]; S.TerminationStatus = [info.TerminationStatus]; if (any (strcmp (Solver, 'dual'))) S.Alpha = [info.Alpha]; endif if (any (ismember (Solver, {'sgd', 'asgd'}))) S.BatchIndex = fieldOrNaN (info, 'BatchIndex'); S.OptimalLearnRate = fieldOrNaN (info, 'OptimalLearnRate'); endif S.History = []; S.FitTime = 0; S.Solver = Solver; endfunction ## One row over the regularization strengths, with NaN wherever the solver ## that ran did not report the field at all. function v = fieldOrNaN (info, name) v = nan (1, numel (info)); for k = 1:numel (info) if (isfield (info(k), name) && ! isempty (info(k).(name))) v(k) = info(k).(name); endif endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/linearLoss.m000066400000000000000000000051701524624707500262420ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{L}, @var{dL}] =} linearLoss (@var{f}, @var{y}, @var{lossfun}, @var{epsilon}) ## ## Per-observation fitted loss of a linear model, and its derivative. ## ## @var{f} is the raw score @code{X * Beta + Bias}, @var{y} the response: ## @math{+1}/@math{-1} for the two classification losses and the observed ## value for the two regression losses. @var{L} is the loss of each ## observation and @var{dL} its derivative with respect to @var{f}. ## ## The four losses are MATLAB's, and the constants are its own as measured on ## R2024a rather than the textbook ones: @qcode{'mse'} carries the factor of ## one half that makes the reported objective agree, while @qcode{'hinge'}, ## @qcode{'logit'} and @qcode{'epsiloninsensitive'} carry none. Note that ## this is the loss the @emph{fit} minimizes, which is not the loss the ## @code{loss} method reports by default. ## ## At the kink of a non-differentiable loss the derivative is taken to be the ## one-sided value that is zero, which is the subgradient of least norm. ## ## @end deftypefn function [L, dL] = linearLoss (f, y, lossfun, epsilon) switch (lossfun) case 'hinge' m = 1 - y .* f; active = m > 0; L = m .* active; dL = -y .* active; case 'logit' ## log (1 + exp (-y * f)) evaluated so that neither tail overflows: ## log (1 + exp (u)) is max (u, 0) + log1p (exp (-abs (u))). u = -(y .* f); L = max (u, 0) + log1p (exp (-abs (u))); dL = -y ./ (1 + exp (-u)); case 'mse' r = f - y; L = 0.5 * (r .^ 2); dL = r; case 'epsiloninsensitive' r = f - y; m = abs (r) - epsilon; active = m > 0; L = m .* active; dL = sign (r) .* active; otherwise error ("linearLoss: unknown loss function '%s'.", lossfun); endswitch endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/linearModelParams.m000066400000000000000000000124321524624707500275250ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{MP} =} linearModelParams (@var{P}, @var{Solver}, @var{LambdaIn}, @var{EpsilonIn}, @var{Beta0}, @var{Bias0}, @var{Verbose}, @var{Type}) ## ## The @qcode{ModelParameters} structure of a linear model. ## ## Every option is reported, but only the ones the chosen solver can act on ## carry a value: the rest come back empty. That is MATLAB's behaviour and ## it is more informative than reporting a default that nothing used. ## Measured on R2024a, one fit per solver: ## ## @multitable @columnfractions 0.30 0.14 0.14 0.14 0.14 0.14 ## @headitem field @tab bfgs @tab lbfgs @tab sparsa @tab sgd @tab dual ## @item @qcode{BatchIndex} @tab @tab @tab @tab 0 @tab ## @item @qcode{BatchLimit} @tab @tab @tab @tab 0 @tab ## @item @qcode{BatchSize} @tab @tab @tab @tab 10 @tab ## @item @qcode{DeltaGradientTolerance} @tab @tab @tab @tab @tab yes ## @item @qcode{GradientTolerance} @tab yes @tab yes @tab yes @tab 0 @tab 0 ## @item @qcode{HessianHistorySize} @tab @tab 15 @tab @tab @tab ## @item @qcode{IterationLimit} @tab yes @tab yes @tab yes @tab @tab ## @item @qcode{LearnRate} @tab @tab @tab @tab yes @tab ## @item @qcode{LineSearch} @tab yes @tab yes @tab @tab @tab ## @item @qcode{NumCheckConvergence} @tab @tab @tab @tab 0 @tab yes ## @item @qcode{OptimizeLearnRate} @tab @tab @tab @tab yes @tab ## @item @qcode{PassLimit} @tab @tab @tab @tab yes @tab yes ## @item @qcode{TruncationPeriod} @tab @tab @tab @tab @tab ## @end multitable ## ## Two of those are worth noticing. @qcode{HessianHistorySize} is empty for ## @qcode{'bfgs'} and 15 for @qcode{'lbfgs'}, a full quasi-Newton method ## having no limited memory to size. And @qcode{GradientTolerance} is ## reported as 0 by the two solvers that do not run a gradient test, which ## by the rule that a tolerance of 0 switches its test off is a true ## statement rather than a missing one. ## ## @qcode{LineSearch} reports @qcode{'strongwolfe'}, which is what ## @code{__lbfgs__} performs. MATLAB reports @qcode{'weakwolfe'} there. ## The field names the line search that actually ran, so reporting MATLAB's ## word for ours would be false. ## ## @end deftypefn function MP = linearModelParams (P, Solver, LambdaIn, EpsilonIn, Beta0, ... Bias0, Verbose, Type) quasi = any (ismember (Solver, {'bfgs', 'lbfgs'})); batch = any (ismember (Solver, {'sgd', 'asgd'})); dual = any (strcmp (Solver, 'dual')); iter = quasi || any (strcmp (Solver, 'sparsa')); MP = struct (); MP.BatchIndex = pick (batch, 0); MP.BatchLimit = pick (batch, ifelse (isempty (P.BatchLimit), 0, ... P.BatchLimit)); MP.BatchSize = pick (batch, P.BatchSize); MP.BetaTolerance = P.BetaTolerance; MP.DeltaGradientTolerance = pick (dual, P.DeltaGradientTolerance); MP.Epsilon = EpsilonIn; MP.FitBias = P.FitBias; ## The two solvers that run no gradient test report a tolerance of zero, ## which is how a switched-off test is spelled throughout this layer. MP.GradientTolerance = P.GradientTolerance; if (batch || dual) MP.GradientTolerance = 0; endif MP.HessianHistorySize = pick (any (strcmp (Solver, 'lbfgs')), ... P.HessianHistorySize); MP.InitialBeta = Beta0; MP.InitialBias = Bias0; MP.IterationLimit = pick (iter, P.IterationLimit); MP.Learner = P.Learner; MP.Lambda = LambdaIn; MP.LearnRate = pick (batch, P.LearnRate); MP.LineSearch = pick (quasi, 'strongwolfe'); MP.LossFunction = P.LossFunction; MP.NumCheckConvergence = pick (batch || dual, P.NumCheckConvergence); if (batch) MP.NumCheckConvergence = 0; endif MP.OptimizeLearnRate = pick (batch, P.OptimizeLearnRate); MP.PassLimit = pick (batch || dual, P.PassLimit); MP.PostFitBias = P.PostFitBias; MP.Regularization = P.Regularization; ## Solver arrives as a cell array of names already; wrapping it again ## would nest it, which struct () would have unwrapped but a direct ## assignment does not. MP.Solver = Solver; ## We take no RandomStream, so there is never one to report. MP.Stream = []; MP.TruncationPeriod = pick (batch && strcmp (P.Regularization, 'lasso'), ... P.TruncationPeriod); MP.VerbosityLevel = Verbose; MP.Method = 'Linear'; MP.Type = Type; endfunction ## The value when the solver can act on it, and empty when it cannot. function v = pick (used, value) if (used) v = value; else v = []; endif endfunction function v = ifelse (c, a, b) if (c) v = a; else v = b; endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/linearSolve.m000066400000000000000000000455241524624707500264210ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{Beta}, @var{Bias}, @var{S}] =} linearSolve (@var{X}, @var{y}, @var{w}, @var{P}) ## ## Minimize the regularized linear objective shared by the four linear and ## kernel learners. ## ## The objective is ## ## @example ## sum (@var{w} .* L (@var{X} * Beta + Bias, @var{y})) + Lambda * R (Beta) ## @end example ## ## with @var{w} summing to one, @code{L} the fitted loss of ## @code{linearLoss}, and @code{R} either @math{||Beta||^2 / 2} for ridge or ## @math{||Beta||_1} for lasso. The bias is never regularized. ## ## @var{P} is a scalar struct holding the resolved fitting options; the ## caller has already validated them and turned every @qcode{'auto'} into a ## number. @var{S} reports the fit: @qcode{Objective}, ## @qcode{NumIterations}, @qcode{GradientNorm}, @qcode{RelativeChangeInBeta}, ## @qcode{DeltaGradient}, @qcode{TerminationCode}, @qcode{TerminationStatus}, ## @qcode{History} and @qcode{Solver}. ## ## @end deftypefn function [Beta, Bias, S] = linearSolve (X, y, w, P) Beta = P.InitialBeta(:); Bias = P.InitialBias; if (! P.FitBias) Bias = 0; endif switch (P.Solver) case {'lbfgs', 'bfgs'} [Beta, Bias, S] = solveQuasiNewton (X, y, w, P, Beta, Bias); case 'sparsa' [Beta, Bias, S] = solveSpaRSA (X, y, w, P, Beta, Bias); case {'sgd', 'asgd'} [Beta, Bias, S] = solveSGD (X, y, w, P, Beta, Bias); case 'dual' [Beta, Bias, S] = solveDual (X, y, w, P, Beta, Bias); otherwise error ("linearSolve: unknown solver '%s'.", P.Solver); endswitch ## An explicit refit of the bias against the fitted scores, once the ## coefficients are settled. MATLAB calls this PostFitBias and leaves it ## off by default. if (P.FitBias && P.PostFitBias) Bias = refitBias (X * Beta, y, w, P); endif S.Objective = linearObjective (X, y, w, P, Beta, Bias); S.Solver = P.Solver; ## A tolerance of exactly zero does not mean "test against zero", it means ## the test does not run, and MATLAB then reports the quantity it governs ## as NaN rather than as a number. Measured on R2024a across a twelve ## point sweep of fitclinear. Note that the engine's LossTolerance is ## switched off with -Inf instead: the two conventions differ because ## MATLAB's do, each mirroring the quantity it governs, and unifying them ## would create a divergence rather than close one. if (P.GradientTolerance == 0) S.GradientNorm = NaN; endif if (P.BetaTolerance == 0) S.RelativeChangeInBeta = NaN; endif endfunction ## Objective value alone, for reporting. function f = linearObjective (X, y, w, P, Beta, Bias) L = linearLoss (X * Beta + Bias, y, P.LossFunction, P.Epsilon); f = sum (w .* L) + P.Lambda * penalty (Beta, P.Regularization); endfunction function r = penalty (Beta, reg) if (strcmp (reg, 'ridge')) r = sum (Beta .^ 2) / 2; else r = sum (abs (Beta)); endif endfunction ## Value and gradient of the smooth part, over the packed parameter vector. ## The lasso penalty is not part of it: SpaRSA handles that one by its prox. function [f, g] = smoothPart (z, X, y, w, P) p = columns (X); Beta = z(1:p); if (P.FitBias) Bias = z(p+1); else Bias = 0; endif [L, dL] = linearLoss (X * Beta + Bias, y, P.LossFunction, P.Epsilon); f = sum (w .* L); v = w .* dL; gBeta = X' * v; if (strcmp (P.Regularization, 'ridge')) f += P.Lambda * sum (Beta .^ 2) / 2; gBeta += P.Lambda * Beta; endif if (P.FitBias) gBias = sum (v); g = [gBeta; gBias]; else g = gBeta; endif endfunction ## LBFGS and BFGS, both served by the package's __lbfgs__ engine. function [Beta, Bias, S] = solveQuasiNewton (X, y, w, P, Beta, Bias) p = columns (X); z0 = Beta; if (P.FitBias) z0 = [Beta; Bias]; endif opt = struct (); opt.IterationLimit = P.IterationLimit; opt.GradientTolerance = P.GradientTolerance; opt.StepTolerance = 0; ## LossTolerance MUST stay -Inf. The engine's test is on the objective ## VALUE rather than on its change, which is MATLAB's convention and not ## what the name suggests, so any fit whose objective passes below 1e-6 on ## its way down stops there with the coefficients still wrong. Measured: ## an exact linear relation fitted at Lambda 1e-10 stops after 5 ## iterations with the coefficients out by 1.6e-4, against 11 iterations ## and 2.6e-10 with the test switched off. The BIST named ## 'LossTolerance' in RegressionLinear.m fails if this line is removed. ## ## Note that BetaTolerance is switched off with 0 and this one with -Inf. ## The inconsistency is deliberate: each mirrors MATLAB's own convention ## for the quantity it governs, and unifying them would create a ## divergence rather than close one. opt.LossTolerance = -Inf; opt.HistorySize = P.HessianHistorySize; opt.BetaTolerance = P.BetaTolerance; fcn = @(z) smoothPart (z, X, y, w, P); [z, info] = __lbfgs__ (fcn, z0, opt); Beta = z(1:p); if (P.FitBias) Bias = z(p+1); endif S = struct (); S.NumIterations = info.Iterations; S.GradientNorm = info.Gradient; S.RelativeChangeInBeta = info.RelativeChangeInBeta; S.DeltaGradient = []; [S.TerminationCode, S.TerminationStatus] = terminationOf (info); S.History = []; endfunction ## SpaRSA, the separable-approximation proximal method MATLAB uses for the ## lasso penalty. Each step minimizes the loss's linear model plus an ## isotropic quadratic of curvature alpha, which is the soft threshold, with ## alpha set by the Barzilai-Borwein ratio and grown until a nonmonotone ## sufficient-decrease test passes. function [Beta, Bias, S] = solveSpaRSA (X, y, w, P, Beta, Bias) p = columns (X); memory = 5; eta = 2; sigma = 0.01; alphaMin = 1e-30; alphaMax = 1e30; z = Beta; if (P.FitBias) z = [Beta; Bias]; endif [f, g] = smoothPart (z, X, y, w, P); obj = f + P.Lambda * penalty (z(1:p), P.Regularization); past = obj; alpha = 1; code = 0; relChange = Inf; iter = 0; for iter = 1:P.IterationLimit accepted = false; for ls = 1:60 zNew = z - g / alpha; zNew(1:p) = softThreshold (zNew(1:p), P.Lambda / alpha); d = zNew - z; [fNew, gNew] = smoothPart (zNew, X, y, w, P); objNew = fNew + P.Lambda * penalty (zNew(1:p), P.Regularization); if (objNew <= max (past) - sigma * alpha / 2 * sum (d .^ 2)) accepted = true; break; endif alpha *= eta; endfor if (! accepted) code = 0; break; endif ## Relative change in the coefficients, MATLAB's primary stopping test. relChange = relativeChange (zNew, z); ## Barzilai-Borwein curvature for the next step. dg = gNew - g; dd = sum (d .^ 2); if (dd > 0) alpha = (d' * dg) / dd; if (! isfinite (alpha) || alpha < alphaMin) alpha = alphaMin; elseif (alpha > alphaMax) alpha = alphaMax; endif endif z = zNew; f = fNew; g = gNew; obj = objNew; past = [past, obj]; if (numel (past) > memory) past(1) = []; endif if (relChange <= P.BetaTolerance) code = 1; break; endif endfor Beta = z(1:p); if (P.FitBias) Bias = z(p+1); endif ## The gradient reported for a lasso fit is that of the smooth part alone, ## the penalty having no gradient at the zeros the fit is there to produce. S = struct (); S.NumIterations = iter; S.GradientNorm = max (abs (g)); S.RelativeChangeInBeta = relChange; S.DeltaGradient = []; [S.TerminationCode, S.TerminationStatus] = terminationOfCode (code); S.History = []; endfunction ## Stochastic gradient descent, and its averaged variant. The learning rate ## decays as LearnRate / (1 + Lambda * LearnRate * t) for a ridge penalty and ## stays put for a lasso one, which is soft-thresholded every ## TruncationPeriod mini-batches rather than every one of them. function [Beta, Bias, S] = solveSGD (X, y, w, P, Beta, Bias) [n, p] = size (X); averaged = strcmp (P.Solver, 'asgd'); batch = min (P.BatchSize, n); perPass = ceil (n / batch); limit = P.PassLimit * perPass; if (! isempty (P.BatchLimit)) limit = min (limit, P.BatchLimit); endif rate = P.LearnRate; ridge = strcmp (P.Regularization, 'ridge'); betaSum = zeros (p, 1); biasSum = 0; count = 0; t = 0; relChange = Inf; prev = Beta; if (P.FitBias) prev = [Beta; Bias]; endif lastObj = Inf; for pass = 1:P.PassLimit order = randperm (n); for b = 1:perPass t++; if (t > limit) break; endif idx = order((b - 1) * batch + 1 : min (b * batch, n)); Xb = X(idx,:); yb = y(idx); wb = w(idx); wb = wb / sum (wb); [~, dL] = linearLoss (Xb * Beta + Bias, yb, P.LossFunction, P.Epsilon); v = wb .* dL; gBeta = Xb' * v; if (ridge) gBeta += P.Lambda * Beta; step = rate / (1 + P.Lambda * rate * t); else step = rate; endif Beta -= step * gBeta; if (P.FitBias) Bias -= step * sum (v); endif if (! ridge && mod (t, P.TruncationPeriod) == 0) Beta = softThreshold (Beta, step * P.TruncationPeriod * P.Lambda); endif if (averaged) betaSum += Beta; biasSum += Bias; count++; endif endfor ## What the pass reached, which drives both the stopping test and, when ## OptimizeLearnRate is on, the step size of the next pass: MATLAB ## halves the rate whenever a pass leaves the objective higher than it ## found it. if (P.FitBias) relChange = relativeChange ([Beta; Bias], prev); prev = [Beta; Bias]; obj = linearObjective (X, y, w, P, Beta, Bias); else relChange = relativeChange (Beta, prev); prev = Beta; obj = linearObjective (X, y, w, P, Beta, 0); endif if (P.OptimizeLearnRate && mod (pass, P.NumCheckConvergence) == 0) if (obj > lastObj) rate /= 2; endif lastObj = obj; endif if (t > limit || relChange <= P.BetaTolerance) break; endif endfor if (averaged && count > 0) Beta = betaSum / count; Bias = biasSum / count; endif z = Beta; if (P.FitBias) z = [Beta; Bias]; endif [~, g] = smoothPart (z, X, y, w, P); S = struct (); S.NumIterations = t; S.NumPasses = pass; S.BatchIndex = t; S.OptimalLearnRate = rate; S.GradientNorm = max (abs (g)); S.RelativeChangeInBeta = relChange; S.DeltaGradient = []; [S.TerminationCode, S.TerminationStatus] = terminationOfCode (0); S.History = []; endfunction ## Dual coordinate descent, for a ridge-penalized hinge or ## epsilon-insensitive loss. One coordinate of the dual is minimized ## exactly at a time and the primal coefficients are carried alongside, so a ## pass costs one sweep over the observations. Convergence is judged by the ## largest violation of the coordinate optimality conditions, which is what ## MATLAB reports as DeltaGradient. ## ## The intercept enters as a constant predictor, so this solver penalizes it ## where the quasi-Newton solvers leave it free. That is what a coordinate ## dual can do without the equality constraint an unpenalized intercept ## imposes, it is what LIBLINEAR does, and MATLAB's own dual solver differs ## from its own quasi-Newton one in the same direction and by more: on the ## two overlapping iris species R2024a reaches 0.2760 by 'dual' against ## 0.1576 by 'bfgs'. Expect the two solvers to disagree, and prefer the ## quasi-Newton answer when the objective as written is what matters. function [Beta, Bias, S] = solveDual (X, y, w, P, Beta, Bias) [n, p] = size (X); classification = any (strcmp (P.LossFunction, {'hinge', 'logit'})); U = w / P.Lambda; sq = sum (X .^ 2, 2); ## The intercept enters as a constant predictor, and the value that ## constant takes decides how heavily the dual penalizes it: an intercept ## fitted through a feature of value B carries a penalty of (Bias / B)^2 / 2 ## rather than Bias^2 / 2. LIBLINEAR leaves that value at one and so ## regularizes the intercept as hard as a coefficient, which is visibly ## wrong when the response is far from zero: on carsmall, whose intercept ## is around 24, it costs more than the fit it buys. Setting it to the ## root mean square length of an observation makes the penalty negligible ## and the intercept effectively free, which is the objective as written. ## It is not free of cost: a constant that large enters every coordinate's ## curvature and slows the sweep down, so a fit whose intercept is far from ## zero wants a generous PassLimit. On carsmall the objective falls from ## 5.108 at 500 passes to 2.415 at 8000, the last of those below the 2.827 ## the quasi-Newton solvers stall at. biasScale = 1; if (P.FitBias) biasScale = max (1, sqrt (mean (sum (X .^ 2, 2)))); sq += biasScale ^ 2; endif sq = max (sq, eps); alpha = zeros (n, 1); Beta = zeros (p, 1); Bias = 0; delta = Inf; pass = 0; for pass = 1:P.PassLimit order = randperm (n); delta = 0; for k = order xk = X(k,:); f = xk * Beta + Bias; aOld = alpha(k); if (classification) ## The exact minimizer of the dual along this coordinate, clipped to ## its own box. g = y(k) * f - 1; aNew = min (max (aOld - g / sq(k), 0), U(k)); step = (aNew - aOld) * y(k); else ## The same, with the insensitive band entering as an absolute value ## that the step is soft thresholded by. g = f - y(k); u = aOld - g / sq(k); t = P.Epsilon / sq(k); aNew = sign (u) * max (abs (u) - t, 0); aNew = min (max (aNew, -U(k)), U(k)); step = aNew - aOld; endif ## How far the coordinate was from its own optimum, in the units of ## the gradient. A coordinate pinned against a bound by a gradient ## pushing it outwards does not move and is not in violation. delta = max (delta, abs (aNew - aOld) * sq(k)); if (step != 0) Beta += step * xk'; if (P.FitBias) Bias += step * biasScale; endif alpha(k) = aNew; endif endfor if (mod (pass, P.NumCheckConvergence) == 0 && delta <= P.DeltaGradientTolerance) break; endif endfor S = struct (); S.NumIterations = pass * n; S.NumPasses = pass; S.Alpha = alpha; z = Beta; if (P.FitBias) z = [Beta; Bias]; endif [~, g] = smoothPart (z, X, y, w, P); S.GradientNorm = max (abs (g)); S.RelativeChangeInBeta = []; S.DeltaGradient = delta; if (delta <= P.DeltaGradientTolerance) [S.TerminationCode, S.TerminationStatus] = terminationOfCode (4); else [S.TerminationCode, S.TerminationStatus] = terminationOfCode (0); endif S.History = []; endfunction function b = softThreshold (b, t) b = sign (b) .* max (abs (b) - t, 0); endfunction ## The relative change MATLAB tests BetaTolerance against. Measured on ## R2024a: the two-norm of the step over the two-norm of the iterate, the ## bias inside both vectors and no guard on the denominator. An iterate that ## is identically zero has not moved, so the quotient is 0/0 and NaN, which ## is the value MATLAB reports for this quantity whenever it is not tested. function r = relativeChange (new, old) d = new - old; r = norm (d) / norm (new); endfunction ## Refit the bias alone against the fitted linear part. Least squares and ## the logit have an interior optimum found by a scalar Newton step; the two ## non-smooth losses are minimized over the finite set of points at which ## their derivative can change sign. function Bias = refitBias (f, y, w, P) switch (P.LossFunction) case 'mse' Bias = sum (w .* (y - f)) / sum (w); case 'epsiloninsensitive' knots = unique ([y - f - P.Epsilon; y - f + P.Epsilon]); Bias = bestOf (knots, f, y, w, P); case 'hinge' knots = unique ((1 ./ y) - f); Bias = bestOf (knots, f, y, w, P); case 'logit' Bias = 0; for k = 1:50 [~, dL] = linearLoss (f + Bias, y, P.LossFunction, P.Epsilon); g = sum (w .* dL); s = 1 ./ (1 + exp (-(y .* (f + Bias)))); h = sum (w .* s .* (1 - s)); if (h <= 0) break; endif step = g / h; Bias -= step; if (abs (step) <= 1e-12) break; endif endfor endswitch endfunction function b = bestOf (knots, f, y, w, P) best = Inf; b = 0; for k = 1:numel (knots) L = linearLoss (f + knots(k), y, P.LossFunction, P.Epsilon); v = sum (w .* L); if (v < best) best = v; b = knots(k); endif endfor endfunction ## Map what the engine reports onto MATLAB's TerminationCode. The engine ## hands out a stable token rather than a sentence, because the wording is ## the caller's: fitcnet says 'Relative gradient tolerance reached.' where ## fitclinear says 'Tolerance on gradient satisfied.' for the same event. A ## build that offers no token has nothing to map, and reports the iteration ## limit. ## ## Measured on R2024a fitclinear: the coefficient tolerance is code 1, the ## gradient tolerance 2, the iteration limit 0, and a line search that cannot ## improve the objective -11, worded 'Unable to find a step decreasing the ## objective.' 'loss' and 'step' are unreachable from here: LossTolerance is ## -Inf and StepTolerance 0, and a failed line search returns before the step ## test is ever reached, so a step of exactly zero cannot arise. function [code, status] = terminationOf (info) token = ''; if (isfield (info, 'Criterion')) token = info.Criterion; endif switch (token) case 'beta' code = 1; case {'gradient', 'step'} code = 2; case 'linesearch' code = -11; otherwise code = 0; endswitch [code, status] = terminationOfCode (code); endfunction function [code, status] = terminationOfCode (code) switch (code) case 1 status = {'Tolerance on coefficients satisfied.'}; case 2 status = {'Tolerance on gradient satisfied.'}; case 4 status = {'Tolerance on the complementarity gap satisfied.'}; case -11 status = {'Unable to find a step decreasing the objective.'}; otherwise code = 0; status = {'Iteration limit exceeded.'}; endswitch endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/marginsOf.m000066400000000000000000000031541524624707500260540ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{m} =} marginsOf (@var{s}, @var{gY}, @var{L}) ## ## Classification margin of every observation, over one or more score ## matrices. ## ## @var{s} is @math{NxK} when @var{L} is one and @math{NxKxL} otherwise, ## @var{gY} the index into the class names of each observation's true class. ## @var{m} is @math{NxL}: the score of the true class less the best score ## among the others, which for two classes is simply the other one. ## ## @end deftypefn function m = marginsOf (s, gY, L) n = rows (s); m = zeros (n, L); rowidx = (1:n)'; for k = 1:L if (L == 1) sk = s; else sk = s(:,:,k); endif idx = sub2ind (size (sk), rowidx, gY); strue = sk(idx); so = sk; so(idx) = -Inf; m(:,k) = strue - max (so, [], 2); endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/nbCategorical.m000066400000000000000000000046111524624707500266630ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{idx} =} nbCategorical (@var{val}, @var{p}, @var{classname}) ## ## Resolve the @qcode{CategoricalPredictors} argument into column indices. ## ## @var{val} is what the caller was given: empty for none, the character ## vector @qcode{'all'}, a logical vector with one element per predictor, or a ## numeric vector of column indices. @var{p} is the number of predictors and ## @var{classname} prefixes any error raised. ## ## @var{idx} is a sorted row vector of indices, without repeats. ## ## @seealso{ClassificationNaiveBayes, fitcnb} ## @end deftypefn function idx = nbCategorical (val, p, classname) if (isempty (val)) idx = []; return; endif if (ischar (val) && isrow (val)) if (! strcmpi (val, 'all')) error (strcat (classname, ": a character vector", ... " 'CategoricalPredictors' must be 'all'.")); endif idx = 1:p; return; endif if (islogical (val)) if (numel (val) != p) error (strcat (classname, ": a logical 'CategoricalPredictors'", ... " must have one element per predictor.")); endif idx = find (val(:)'); return; endif if (! (isnumeric (val) && isvector (val) && isreal (val))) error (strcat (classname, ": 'CategoricalPredictors' must be 'all', a", ... " logical vector, or a vector of column indices.")); endif idx = sort (unique (val(:)')); if (any (idx != fix (idx)) || any (idx < 1) || any (idx > p)) error (strcat (classname, ": 'CategoricalPredictors' must be column", ... " indices into X.")); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/nbDistNames.m000066400000000000000000000060061524624707500263350ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{D} =} nbDistNames (@var{val}, @var{p}, @var{classname}) ## ## Resolve the @qcode{DistributionNames} argument of a naive Bayes classifier ## into one distribution name per predictor. ## ## @var{val} is what the caller was given, either empty for the default, a ## character vector naming one distribution for every predictor, or a cell ## array of character vectors naming one per predictor. @var{p} is the number ## of predictors and @var{classname} prefixes any error raised. ## ## @var{D} is a cell array with one name per predictor, except for ## @qcode{'mn'}, which is returned as the character vector it was given as. ## The multinomial is a single distribution over the whole predictor vector ## rather than one per predictor, so it cannot be named for some predictors ## and not others, and there is no per-predictor list to return. ## ## @seealso{ClassificationNaiveBayes, fitcnb} ## @end deftypefn function D = nbDistNames (val, p, classname) known = {'kernel', 'mvmn', 'normal'}; if (isempty (val)) D = repmat ({'normal'}, 1, p); return; endif if (ischar (val) && isrow (val)) ## The name goes into a variable first: inside braces a space before a ## call's paren splits it into two elements and the call loses its argument. dname = lower (val); if (strcmp (dname, 'mn')) D = dname; return; endif D = repmat ({dname}, 1, p); elseif (iscellstr (val)) if (any (strcmpi (val, 'mn'))) error (strcat (classname, ": the 'mn' distribution applies to every", ... " predictor at once and cannot be named for some", ... " of them.")); endif if (numel (val) != p) error (strcat (classname, ": 'DistributionNames' must name one", ... " distribution per predictor.")); endif D = cellfun (@lower, val(:)', 'UniformOutput', false); else error (strcat (classname, ": 'DistributionNames' must be a character", ... " vector or a cell array of character vectors.")); endif bad = ! ismember (D, known); if (any (bad)) error (strcat (classname, ": unsupported distribution", ... sprintf (" '%s'.", D{find (bad, 1)}))); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/nbFit.m000066400000000000000000000152251524624707500251730ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{DP}, @var{K}, @var{S}, @var{W}] =} nbFit (@var{X}, @var{gY}, @var{k}, @var{D}, @var{w}, @var{Kernel}, @var{Support}, @var{Width}, @var{classname}) ## ## Fit one univariate density per class and per predictor for a naive Bayes ## classifier. ## ## @var{X} holds the retained observations, @var{gY} indexes the class of each ## and @var{k} counts them, @var{D} names the distribution of each predictor ## and @var{w} weights each observation. @var{CN} and @var{PN} name the ## classes and the predictors, and are used only to say which combination a ## refusal is about. @var{Kernel}, @var{Support} and ## @var{Width} are the kernel arguments as the caller was given them, each ## possibly empty. ## ## @var{DP} is the class-by-predictor cell of fitted parameters: a column ## vector holding the mean and the standard deviation for a @qcode{'normal'} ## predictor, and a @code{prob.KernelDistribution} for a @qcode{'kernel'} one. ## @var{K}, @var{S} and @var{W} report the kernel, the support and the ## bandwidth actually used, empty where a predictor is not a kernel one. ## ## The standard deviation is the weighted, bias-corrected one, which reduces ## to the sample standard deviation when the weights within a class are equal, ## as they are whenever the prior is spread evenly over a class's members. ## ## @seealso{ClassificationNaiveBayes, fitcnb} ## @end deftypefn function [DP, K, S, W, CL] = nbFit (X, gY, k, D, w, Kernel, Support, ... Width, classname, CN, PN) p = columns (X); DP = cell (k, p); K = cell (1, p); S = cell (1, p); CL = cell (1, p); W = []; ## The multinomial is one distribution over the whole predictor vector: a ## row is a vector of token counts, and a class is described by how it ## spends its tokens across the predictors. Each class's counts are pooled ## and smoothed by one, so a token a class never spent is improbable rather ## than impossible, which a product over predictors would otherwise make it. if (ischar (D)) for i = 1:k xk = X(gY == i, :); cnt = sum (xk, 1); tot = sum (cnt) + p; for j = 1:p DP{i,j} = (cnt(j) + 1) / tot; endfor endfor return; endif anykernel = iscell (D) && any (strcmp (D, 'kernel')); if (anykernel) [Kname, Sname, Wgiven] = nbKernelArgs (Kernel, Support, Width, k, p, ... classname); W = zeros (k, p); else ## A predictor that is not kernel smoothed has no bandwidth, and MATLAB ## reports the absence as a matrix of NaN rather than as an empty one. ## It declares the matrix at its full size whatever the distributions. W = NaN (k, p); endif for j = 1:p for i = 1:k idx = gY == i; xij = X(idx, j); wij = w(idx); if (isempty (xij)) error (strcat (classname, ": class", sprintf (" %d", i), ... " holds no observation.")); endif switch (D{j}) case 'mvmn' ## One categorical distribution per class and predictor, over the ## levels the predictor takes across the whole training set. Every ## level's count is raised by one before it is normalized, so a ## level a class never took keeps a small probability instead of ## ruling that class out on this predictor alone. if (isempty (CL{j})) CL{j} = nbLevels (X(:,j)); endif nlev = numel (CL{j}); cnt = zeros (nlev, 1); for l = 1:nlev cnt(l) = sum (xij == CL{j}(l)); endfor DP{i,j} = (cnt + 1) / (numel (xij) + nlev); case 'normal' sw = sum (wij); mu = sum (wij .* xij) / sw; ## The bias correction of a reliability weight: with equal weights ## the denominator is the count less one. den = sw - sum (wij .^ 2) / sw; sigma = 0; if (den > 0) sigma = sqrt (sum (wij .* (xij - mu) .^ 2) / den); endif ## A predictor that does not vary within a class has no normal ## density to fit: every observation sits on the mean, and the ## limit is a spike carrying no scale. Refuse rather than answer, ## as MATLAB does, naming the combination it could not fit. A ## kernel or a multivariate multinomial has no such difficulty and ## is not refused, which is a route left open to the caller. ## Test the data rather than the computed spread: the weighted ## mean of identical values need not land exactly on them, and a ## variance of 1e-15 where the true one is zero would slip a ## degenerate fit past a test of sigma alone. The second half ## catches values that differ by so little that the squared ## deviation underflows. if (all (xij == xij(1)) || ! (sigma > 0)) error (strcat (classname, ": a normal distribution cannot be", ... " fit for the combination of class", ... sprintf (" %s and predictor %s.", ... nbLabelName (CN, i), PN{j}), ... " The data has zero variance.")); endif DP{i,j} = [mu; sigma]; case 'kernel' ## The density fits itself, so the default bandwidth rule and the ## transform a bounded support needs are ksdensity's own, not a ## second copy of them here. if (isempty (Wgiven)) pd = prob.KernelDistribution.fit (xij, Kname{j}, Sname{j}); else pd = prob.KernelDistribution.fit (xij, Kname{j}, Sname{j}, ... Wgiven(i,j)); endif W(i,j) = pd.Bandwidth; DP{i,j} = pd; K{j} = Kname{j}; S{j} = Sname{j}; endswitch endfor endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/nbGivenOr.m000066400000000000000000000024171524624707500260210ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{v} =} nbGivenOr (@var{given}, @var{dflt}) ## ## The argument as it was given, or the default when it was not given at all. ## ## Used to record a naive Bayes model's arguments in @qcode{ModelParameters}, ## which reports what was asked for rather than what it was resolved to. ## ## @seealso{ClassificationNaiveBayes} ## @end deftypefn function v = nbGivenOr (given, dflt) if (isempty (given)) v = dflt; else v = given; endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/nbKernelArgs.m000066400000000000000000000072131524624707500265040ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{K}, @var{S}, @var{W}] =} nbKernelArgs (@var{Kernel}, @var{Support}, @var{Width}, @var{k}, @var{p}, @var{classname}) ## ## Resolve the kernel arguments of a naive Bayes classifier into one kernel and ## one support per predictor, and a bandwidth per class and predictor. ## ## Each of @var{Kernel} and @var{Support} may be given once for every predictor ## or once per predictor. @var{W} is empty when no bandwidth was given, which ## leaves each density to choose its own. ## ## @seealso{ClassificationNaiveBayes, fitcnb} ## @end deftypefn function [K, S, W] = nbKernelArgs (Kernel, Support, Width, k, p, classname) known = {'box', 'epanechnikov', 'normal', 'triangle'}; ## Kernel if (isempty (Kernel)) K = repmat ({'normal'}, 1, p); elseif (ischar (Kernel) && isrow (Kernel)) kname = lower (Kernel); K = repmat ({kname}, 1, p); elseif (iscellstr (Kernel) && numel (Kernel) == p) K = cellfun (@lower, Kernel(:)', 'UniformOutput', false); else error (strcat (classname, ": 'Kernel' must be a character vector or a", ... " cell array of character vectors, one per predictor.")); endif bad = ! ismember (K, known); if (any (bad)) error (strcat (classname, ": unsupported kernel", ... sprintf (" '%s'.", K{find (bad, 1)}))); endif ## Support if (isempty (Support)) S = repmat ({'unbounded'}, 1, p); elseif (ischar (Support) && isrow (Support)) sname = lower (Support); S = repmat ({sname}, 1, p); elseif (isnumeric (Support) && numel (Support) == 2) sbnds = sort (Support(:)'); S = repmat ({sbnds}, 1, p); elseif (iscell (Support) && numel (Support) == p) S = Support(:)'; else error (strcat (classname, ": 'Support' must be 'unbounded',", ... " 'positive', a two element numeric vector, or a cell", ... " array holding one of those per predictor.")); endif for j = 1:p if (ischar (S{j}) && ! any (strcmpi (S{j}, {'positive', 'unbounded'}))) error (strcat (classname, ": a character vector 'Support' must be", ... " 'unbounded' or 'positive'.")); endif endfor ## Width if (isempty (Width)) W = []; return; endif if (! (isnumeric (Width) && isreal (Width) && all (Width(:) > 0))) error (strcat (classname, ": 'Width' must be positive and real.")); endif if (isscalar (Width)) W = repmat (Width, k, p); elseif (isequal (size (Width), [k, p])) W = Width; elseif (isrow (Width) && numel (Width) == p) W = repmat (Width(:)', k, 1); elseif (iscolumn (Width) && numel (Width) == k) W = repmat (Width(:), 1, p); else error (strcat (classname, ": 'Width' must be a scalar, one value per", ... " predictor, one per class, or a matrix of one per", ... " class and predictor.")); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/nbKernelPack.m000066400000000000000000000035301524624707500264640ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{DP} =} nbKernelPack (@var{DP}, @var{D}) ## ## Replace every fitted kernel density in @var{DP} by the sample it was fitted ## to, so that a naive Bayes model can be written to a file. ## ## @var{DP} is a @code{DistributionParameters} cell, class by predictor, and ## @var{D} the matching @code{DistributionNames}. A @qcode{'kernel'} predictor ## holds a @code{prob.KernelDistribution} object, which Octave's @code{save} ## cannot serialize: it warns and writes a struct that will not load back as an ## object. Every other distribution already holds plain numeric data and is ## left alone. ## ## The sample is enough to rebuild the density exactly, because the kernel, the ## support and the bandwidth actually used are stored beside it on the model. ## @code{nbKernelUnpack} does the rebuilding. ## ## @end deftypefn function DP = nbKernelPack (DP, D) for j = find (strcmp (D(:)', 'kernel')) for i = 1:rows (DP) if (isobject (DP{i,j})) DP{i,j} = DP{i,j}.InputData.data; endif endfor endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/nbKernelUnpack.m000066400000000000000000000035011524624707500270250ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{DP} =} nbKernelUnpack (@var{DP}, @var{D}, @var{K}, @var{S}, @var{W}) ## ## Rebuild the fitted kernel densities @code{nbKernelPack} wrote out as samples. ## ## @var{DP} is a @code{DistributionParameters} cell carrying a sample wherever ## @var{D} names a @qcode{'kernel'} predictor, and @var{K}, @var{S} and @var{W} ## are the model's @code{Kernel}, @code{Support} and @code{Width}. Each cell is ## refitted through @code{prob.KernelDistribution.fit} with the bandwidth the ## model recorded, so the density comes back identical rather than merely close: ## nothing is re-estimated, since the bandwidth is passed rather than chosen. ## ## A class with no observations of a predictor keeps its empty cell. ## ## @end deftypefn function DP = nbKernelUnpack (DP, D, K, S, W) for j = find (strcmp (D(:)', 'kernel')) for i = 1:rows (DP) if (! isempty (DP{i,j}) && ! isobject (DP{i,j})) DP{i,j} = prob.KernelDistribution.fit (DP{i,j}, K{j}, S{j}, W(i,j)); endif endfor endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/nbLabelName.m000066400000000000000000000027731524624707500262750ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{s} =} nbLabelName (@var{C}, @var{i}) ## ## One class's name, as a message should print it. ## ## @var{C} is the model's @qcode{ClassNames} in any of the forms a response ## may take, and @var{i} indexes it. A textual class prints as its name and ## every other kind as its value, which is what MATLAB's own messages do: ## a numeric response names @qcode{class 2} where a cellstr one names ## @qcode{class beta}. ## ## @seealso{ClassificationNaiveBayes} ## @end deftypefn function s = nbLabelName (C, i) if (iscellstr (C)) s = C{i}; elseif (ischar (C)) s = strtrim (C(i,:)); elseif (islogical (C)) s = mat2str (C(i)); else s = num2str (C(i)); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/nbLevels.m000066400000000000000000000023211524624707500256740ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{L} =} nbLevels (@var{x}) ## ## The distinct levels of one categorical predictor, in ascending order. ## ## The order is the one every per-level quantity is reported in, so it is ## fixed here once rather than recovered wherever a level is looked up. ## ## @seealso{ClassificationNaiveBayes} ## @end deftypefn function L = nbLevels (x) L = unique (x(:)); L = L(! isnan (L)); endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/nbLogLik.m000066400000000000000000000077011524624707500256320ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{L} =} nbLogLik (@var{X}, @var{D}, @var{DP}, @var{k}) ## ## The class conditional log-likelihood of every observation under a fitted ## naive Bayes model. ## ## @var{L} is an observation-by-class matrix. The predictors are conditionally ## independent given the class, so a row is the sum over the predictors of each ## one's log density, which is where the model's cost in the number of ## predictors stays linear. ## ## The sum is taken in logs rather than as a product of densities: with even a ## moderate number of predictors the product underflows to zero for every ## class at once, and the posterior that follows would be 0/0 rather than a ## well determined ratio. ## ## A row can come back as @math{-Inf} for every class, when a categorical ## predictor takes a level the model never saw. That is a real answer, not a ## failure, and the caller reads it as such: the observation carries no ## information about the class and its posterior is the prior. ## ## A @qcode{NaN} predictor is skipped rather than propagated. The predictors ## are conditionally independent given the class, so an observation missing ## one of them is still described by the others, and only its own term drops ## out. An observation missing every predictor contributes nothing to any ## class and falls back to the prior, by the same arithmetic. Measured ## against R2024a. ## ## @seealso{ClassificationNaiveBayes, fitcnb} ## @end deftypefn function L = nbLogLik (X, D, DP, k, CL) n = rows (X); p = columns (X); L = zeros (n, k); ## The multinomial reads a row as token counts and scores it by how likely ## the class was to spend its tokens that way. The multinomial coefficient ## depends on the row alone, not on the class, so it cancels in the ## posterior and is not computed. if (ischar (D)) for i = 1:k for j = 1:p t = X(:,j) * log (DP{i,j}); t(isnan (X(:,j))) = 0; L(:,i) += t; endfor endfor return; endif for i = 1:k for j = 1:p switch (D{j}) case 'mvmn' ## An observation is scored by the probability of the level it ## takes. A level the model never saw carries none, and makes the ## observation impossible under every class alike. lev = CL{j}; pr = DP{i,j}; lp = -Inf (n, 1); for l = 1:numel (lev) lp(X(:,j) == lev(l)) = log (pr(l)); endfor lp(isnan (X(:,j))) = 0; L(:,i) += lp; case 'normal' mu = DP{i,j}(1); sigma = DP{i,j}(2); if (sigma > 0) z = (X(:,j) - mu) / sigma; t = -0.5 * z .^ 2 - log (sigma) - 0.5 * log (2 * pi); t(isnan (X(:,j))) = 0; L(:,i) += t; else ## A class holding one distinct value has no spread: the density ## is a spike, so an observation matching it is certain and every ## other one impossible. L(X(:,j) != mu, i) = -Inf; endif case 'kernel' t = log (pdf (DP{i,j}, X(:,j))); t(isnan (X(:,j))) = 0; L(:,i) += t; endswitch endfor endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/nbTrainX.m000066400000000000000000000027621524624707500256600ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{X}, @var{Y}] =} nbTrainX (@var{obj}) ## ## The data a naive Bayes model was fitted on, as it was fitted. ## ## @qcode{X} and @qcode{Y} are stored as they were supplied, so the rows that ## were dropped for holding a missing value, or for belonging to a class the ## model was not fitted on, are removed here. Every @code{resub} method goes ## through this, so none of them can disagree with the fit about which ## observations the model actually saw. ## ## @seealso{ClassificationNaiveBayes} ## @end deftypefn function [X, Y] = nbTrainX (obj) X = obj.X; Y = obj.Y; if (! isempty (obj.RowsUsed)) X = X(obj.RowsUsed, :); Y = Y(obj.RowsUsed, :); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/pairTerms.m000066400000000000000000000024151524624707500260740ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{T} =} pairTerms (@var{p}) ## Every pairwise interaction of @var{p} predictors, as a logical matrix with ## one row per term and one column per predictor, two of them true. ## @end deftypefn function T = pairTerms (p) if (p < 2) T = false (0, p); return; endif pairs = nchoosek (1:p, 2); n = rows (pairs); T = false (n, p); T(sub2ind ([n, p], (1:n)', pairs(:,1))) = true; T(sub2ind ([n, p], (1:n)', pairs(:,2))) = true; endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/parseDistanceWeight.m000066400000000000000000000044201524624707500300610ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{f}, @var{dw}] =} parseDistanceWeight (@var{DistanceWeight}, @var{classname}) ## ## Parse a nearest-neighbour distance weight. ## ## @var{dw} is the weight as it is reported, a character vector naming a ## built-in weight or the @code{func2str} form of a supplied handle, and ## @var{f} is the callable that applies it. The property holds the text, as ## MATLAB does, and the callable is kept out of sight beside it. ## ## @end deftypefn function [f, dw] = parseDistanceWeight (DistanceWeight, classname) if (! (ischar (DistanceWeight) || is_function_handle (DistanceWeight))) error (strcat ("%s: 'DistanceWeight' must be a character vector or a", ... " function handle."), classname); endif if (is_function_handle (DistanceWeight)) m = eye (5); if (! isequal (size (m), size (DistanceWeight (m)))) error (strcat ("%s: function handle for 'DistanceWeight' must return", ... " the same size as its input."), classname); endif f = DistanceWeight; dw = func2str (DistanceWeight); return; endif switch (lower (DistanceWeight)) case 'equal' f = @(d) ones (size (d)); case 'inverse' f = @(d) d .^ (-1); case 'squaredinverse' f = @(d) d .^ (-2); otherwise error (strcat ("%s: 'DistanceWeight' must be 'equal', 'inverse',", ... " 'squaredinverse', or a function handle."), classname); endswitch dw = lower (DistanceWeight); endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/parseResponseTransform.m000066400000000000000000000040121524624707500306460ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} parseResponseTransform (@var{rt}, @var{classname}) ## Parse ResponseTransform for Regression objects. ## @end deftypefn function [f, rt] = parseResponseTransform (ResponseTransform, classname) if (is_function_handle (ResponseTransform)) ## nargin () raises on a handle to a built-in, so the handle is checked by ## what it does rather than by its declared arity. v = (1:5)'; if (! isequal (size (v), size (ResponseTransform (v)))) error (strcat ("%s: function handle for 'ResponseTransform' must", ... " return the same size as its input."), classname); endif f = ResponseTransform; rt = func2str (ResponseTransform); elseif (ischar (ResponseTransform) && isrow (ResponseTransform)) rt = tolower (ResponseTransform); switch (rt) case {'none', 'identity'} f = @(y) y; rt = 'none'; case 'exp' f = @(y) exp (y); case 'log' f = @(y) log (y); otherwise error ("%s: unrecognized 'ResponseTransform' function.", classname); endswitch else error (strcat ("%s: 'ResponseTransform' must be a character vector", ... " or a function handle."), classname); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/parseScoreTransform.m000066400000000000000000000071151524624707500301320ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} parseScoreTransform (@var{pd}, @var{classname}) ## ## Parse ScoreTransform for Classification objects. ## ## @end deftypefn function [f, st] = parseScoreTransform (ScoreTransform, classname) stList = {'doublelogit', 'invlogit', 'ismax', 'logit', 'none', ... 'identity', 'sign', 'symmetric', 'symmetricismax', ... 'symmetriclogit'}; if (! (ischar (ScoreTransform) || strcmp (class (ScoreTransform), 'function_handle'))) error (strcat ("%s: 'ScoreTransform' must be a character", ... " vector or a function handle."), classname); endif ## Handle ScoreTransform here if (is_function_handle (ScoreTransform)) m = eye (5); if (! isequal (size (m), size (ScoreTransform(m)))) error (strcat ("%s: function handle for 'ScoreTransform' must", ... " return the same size as its input."), classname); endif f = ScoreTransform; st = func2str (ScoreTransform); else if (! ismember (ScoreTransform, stList)) error ("%s: unrecognized 'ScoreTransform' function.", classname); endif st = ScoreTransform; if (strcmpi ('doublelogit', ScoreTransform)) f = @(x) 1 ./ (1 + exp (-2 * x)); elseif (strcmpi ('invlogit', ScoreTransform)) f = @(x) log (x ./ (1 - x)); elseif (strcmpi ('ismax', ScoreTransform)) f = eval (sprintf ("@(x) ismax (x)")); elseif (strcmpi ('logit', ScoreTransform)) f = @(x) 1 ./ (1 + exp (-x)); elseif (any (strcmpi ({'identity', 'none'}, ScoreTransform))) ## A handle, like every other transform, so a caller can apply it ## without testing for this one by name f = @(x) x; st = 'none'; elseif (strcmpi ('sign', ScoreTransform)) f = @(x) sign (x); elseif (strcmpi ('symmetric', ScoreTransform)) f = @(x) 2 * x - 1; elseif (strcmpi ('symmetricismax', ScoreTransform)) f = eval (sprintf ("@(x) symmetricismax (x)")); elseif (strcmpi ('symmetriclogit', ScoreTransform)) f = @(x) 2 ./ (1 + exp (-x)) - 1; endif endif endfunction ## Helper functions for ScoreTransform ## The largest score *of each observation*, so the maximum is taken across ## the classes and not down the column. A bare max () ran over the ## observations instead, which is right only for a single row and silently ## wrong for every other. A tie goes to the first class, which is what max ## returns and what MATLAB does. function out = ismax (score) out = zeros (size (score), class (score)); [~, k] = max (score, [], 2); out(sub2ind (size (score), (1:rows (score))', k)) = 1; endfunction function out = symmetricismax (score) out = - ones (size (score), class (score)); [~, k] = max (score, [], 2); out(sub2ind (size (score), (1:rows (score))', k)) = 1; endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/partitionedArgs.m000066400000000000000000000050051524624707500272630ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{P}, @var{rest}] =} partitionedArgs (@var{args}, @var{classname}) ## ## Split the options a cross-validated model owns from the ones its folds ## are fitted with. ## ## @var{P} is a structure with fields @qcode{ClassNames}, @qcode{Prior}, ## @qcode{Cost}, @qcode{Weights}, @qcode{ScoreTransform} and ## @qcode{ResponseTransform}, each empty when it was not given. @var{rest} ## is everything else, in the order it was given, including the option that ## says how to partition. ## ## Those six belong to the parent because they describe the data or the ## reported scores rather than the fit: the classes, the prior and the cost ## are resolved once over the whole data and handed down, the weights are ## sliced per fold, and a transform is applied once to the assembled scores ## with the folds left carrying none. ## ## @end deftypefn function [P, rest] = partitionedArgs (args, classname) P = struct ('ClassNames', [], 'Prior', [], 'Cost', [], 'Weights', [], ... 'ScoreTransform', [], 'ResponseTransform', []); rest = {}; while (numel (args) > 0) if (numel (args) < 2) error (strcat ("%s: optional arguments must be given in Name-Value", ... " pairs."), classname); endif switch (lower (args{1})) case 'classnames' P.ClassNames = args{2}; case 'prior' P.Prior = args{2}; case 'cost' P.Cost = args{2}; case 'weights' P.Weights = args{2}; case 'scoretransform' P.ScoreTransform = args{2}; case 'responsetransform' P.ResponseTransform = args{2}; otherwise rest = [rest, args(1:2)]; endswitch args(1:2) = []; endwhile endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/partitionedModelParams.m000066400000000000000000000044561524624707500306040ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{MP} =} partitionedModelParams (@var{Mdl}, @var{kfold}, @var{method}, @var{type}) ## @deftypefnx {Private Function} {@var{MP} =} partitionedModelParams (@dots{}, @var{learner}) ## ## The @qcode{ModelParameters} of a cross-validated model. ## ## @var{Mdl} is the learner the folds were fitted from, @var{kfold} the number ## of folds, @var{method} the name the partitioned class reports and ## @var{type} either @qcode{'classification'} or @qcode{'regression'}. ## @var{learner} names the backing where the class publishes one. ## ## The learner's own parameters are carried through, so that a cross-validated ## model still says what its folds were fitted with. Its @qcode{Version}, ## @qcode{Method} and @qcode{Type} tags describe the learner rather than the ## partition and are reissued for the class doing the reporting, else a ## cross-validated SVM would answer @qcode{'SVM'} where it is a partitioned ## model. ## ## @end deftypefn function MP = partitionedModelParams (Mdl, kfold, method, type, learner) MP = Mdl.ModelParameters; if (! isstruct (MP)) MP = struct (); endif ## The tags belong to the reporting class, not to the learner. tags = {'Version', 'Method', 'Type'}; for i = 1:numel (tags) if (isfield (MP, tags{i})) MP = rmfield (MP, tags{i}); endif endfor if (nargin > 4 && ! isempty (learner)) MP.LearnerTemplates = learner; endif MP.NLearn = kfold; MP.Version = 1; MP.Method = method; MP.Type = type; endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/priorFromStruct.m000066400000000000000000000043421524624707500273130ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} priorFromStruct (@var{S}, @var{ClassNames}, @var{classname}) ## ## Resolve a structure form of @qcode{Prior} into a probability row vector. ## ## @var{S} carries a @qcode{ClassNames} field and a @qcode{ClassProbs} field. ## The probabilities are returned in the order of the model's @var{ClassNames} ## rather than the order they were given in, so naming the classes out of ## order still assigns each its own probability. ## ## @end deftypefn function pr = priorFromStruct (S, ClassNames, classname) if (! (isfield (S, 'ClassNames') && isfield (S, 'ClassProbs'))) error (strcat (classname, ": a structure 'Prior' must have", ... " 'ClassNames' and 'ClassProbs' fields.")); endif sn = S.ClassNames; sp = S.ClassProbs; if (numel (sn) != numel (sp)) error (strcat (classname, ": 'ClassNames' and 'ClassProbs' must have", ... " the same number of elements.")); endif K = numel (ClassNames); pr = zeros (1, K); for i = 1:K if (iscellstr (ClassNames)) j = find (strcmp (sn, ClassNames{i})); elseif (ischar (ClassNames)) j = find (strcmp (sn, ClassNames(i,:))); else j = find (sn == ClassNames(i)); endif if (isempty (j)) error (strcat (classname, ": 'ClassNames' in the 'Prior' structure", ... " must name every class of the model.")); endif pr(i) = sp(j(1)); endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/priorNormalize.m000066400000000000000000000031711524624707500271420ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{w} =} priorNormalize (@var{w}, @var{gY}, @var{Prior}) ## ## Scale observation weights so that each class carries its prior. ## ## Within a class the weights keep their relative sizes; across classes each ## class's weights sum to that class's prior. A loss computed on weights that ## were not normalized this way would let an over-represented class speak for ## more than its prior says it should. ## ## A class with no weight at all is left alone rather than divided by zero. ## ## @seealso{ClassificationNaiveBayes, edgeWeights} ## @end deftypefn function w = priorNormalize (w, gY, Prior) w = w(:); for i = 1:numel (Prior) idx = gY == i; sw = sum (w(idx)); if (sw > 0) w(idx) = w(idx) * Prior(i) / sw; endif endfor sw = sum (w); if (sw > 0) w = w / sw; endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/priorWeights.m000066400000000000000000000026031524624707500266130ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} priorWeights (@var{Prior}, @var{gY}, @var{n}) ## ## Spread each class prior evenly over the observations of that class. ## ## Every observation of class @var{k} carries @qcode{Prior(k) / n_k}, so the ## observations of a class always sum to its prior. @var{gY} holds the class ## index of each of the @var{n} observations. ## ## @end deftypefn function w = priorWeights (Prior, gY, n) w = zeros (n, 1); for k = 1:numel (Prior) idx = (gY == k); nk = sum (idx); if (nk > 0) w(idx) = Prior(k) / nk; endif endfor endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/regFrame.m000066400000000000000000000050551524624707500256610ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{F} =} regFrame (@var{X}, @var{Y}, @var{Weights}, @var{classname}) ## ## Validate the data of a regression model and resolve its observation ## weights. ## ## The counterpart of @code{classFrame} for the models with a continuous ## response: check the shapes, drop the rows that are not complete in both ## the predictors and the response, and normalize the weights to sum to one. ## ## Fields: @qcode{X} and @qcode{Y}, the retained data; @qcode{RowsUsed}, a ## logical over the rows as supplied; @qcode{Weights}, the retained weights ## before normalization; @qcode{W}, the same normalized; and @qcode{n} and ## @qcode{p}. ## ## @end deftypefn function F = regFrame (X, Y, Weights, classname) if (! (isnumeric (X) && isreal (X) && ismatrix (X) && ndims (X) == 2)) error ("%s: invalid values in X.", classname); endif if (isempty (X)) error ("%s: X is empty.", classname); endif if (! (isnumeric (Y) && isreal (Y) && isvector (Y))) error ("%s: invalid values in Y.", classname); endif Y = Y(:); if (rows (X) != rows (Y)) error ("%s: number of rows in X and Y must be equal.", classname); endif if (isempty (Weights)) Weights = ones (rows (X), 1); else Weights = Weights(:); if (numel (Weights) != rows (X)) error ("%s: 'Weights' must have one element per observation.", ... classname); endif endif F = struct (); F.RowsUsed = ! (isnan (Y) | any (isnan (X), 2)); F.X = X(F.RowsUsed, :); F.Y = Y(F.RowsUsed); F.Weights = Weights(F.RowsUsed); if (isempty (F.Y)) error ("%s: no complete observations in the data.", classname); endif [F.n, F.p] = size (F.X); F.W = F.Weights; if (sum (F.W) > 0) F.W = F.W / sum (F.W); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/restore_tables.m000066400000000000000000000047261524624707500271520ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{out} =} restore_tables (@var{in}) ## ## Rebuild any @code{table} that @code{load} handed back as a plain structure. ## ## Saving to a binary file keeps a table's contents but not its class, so it ## comes back as a structure carrying @qcode{VariableNames} and ## @qcode{VariableValues} among the table's other properties. This helper ## turns such a structure back into the table it was, and recurses through the ## fields of any other structure and the elements of any cell, so that a table ## nested inside one is restored with it. ## ## Anything that is not a saved table is returned unchanged. ## ## This helper is shared by the @code{load_model} methods of the learner ## classes, whose saved properties may hold tables. ## ## @end deftypefn function out = restore_tables (in) if (isstruct (in) && isscalar (in) && is_saved_table (in)) out = table (in.VariableValues{:}, 'VariableNames', in.VariableNames); if (! isempty (in.RowNames)) out.Properties.RowNames = in.RowNames; endif return; endif if (isstruct (in) && isscalar (in)) out = in; for [val, key] = in out.(key) = restore_tables (val); endfor return; endif if (iscell (in)) out = cellfun (@restore_tables, in, 'UniformOutput', false); return; endif out = in; endfunction ## A structure is a saved table when it carries the fields the table class ## writes out; testing for the pair that holds the data is enough, since no ## structure the models save has both. function tf = is_saved_table (s) tf = isfield (s, 'VariableNames') && isfield (s, 'VariableValues') ... && isfield (s, 'RowNames') && iscell (s.VariableValues); endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/svmKernelParams.m000066400000000000000000000032461524624707500272430ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{s} =} svmKernelParams (@var{fcn}, @var{scale}, @var{order}) ## ## Build the @qcode{KernelParameters} structure of an SVM model. ## ## @var{fcn} is the kernel as the class recorded it, @var{scale} its scale and ## @var{order} the polynomial order. The structure carries @qcode{Function} ## and @qcode{Scale}, and @qcode{Order} for a polynomial kernel only, which is ## the shape MATLAB reports. MATLAB names the radial basis kernel ## @qcode{'gaussian'} whatever the caller spelled it, so @qcode{'rbf'} is ## renamed here; the kernel the fit was given is unchanged and remains in ## @qcode{ModelParameters}. ## ## @end deftypefn function s = svmKernelParams (fcn, scale, order) if (strcmpi (fcn, 'rbf')) fcn = 'gaussian'; endif s = struct ('Function', fcn, 'Scale', scale); if (strcmpi (fcn, 'polynomial')) s.Order = order; endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/svmPlusMinus.m000066400000000000000000000050721524624707500266150ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{Ypm} =} svmPlusMinus (@var{Y}, @var{ClassNames}) ## ## Response of a two-class support vector machine in @math{+1/-1} coding. ## ## @var{Y} may hold the class labels, in whatever type @var{ClassNames} holds ## them, which is what @code{margin} and @code{loss} document and what MATLAB ## accepts. It may also already be numeric @math{+1/-1}, which is the coding ## the underlying solver works in, and is then passed through unchanged. ## ## The first class in @var{ClassNames} maps to @math{+1} and the second to ## @math{-1}, matching the sign convention the decision values carry. ## ## @end deftypefn function Ypm = svmPlusMinus (Y, ClassNames) ## Already the solver's own coding, so nothing to map. This is checked ## first and without consulting ClassNames on purpose: where the classes ## themselves are -1 and +1 the two readings coincide, and mapping through ## the sorted names would invert the sign of every margin. if (isnumeric (Y) && ! isempty (Y) && all (ismember (Y(:), [-1; 1]))) Ypm = double (Y(:)); return; endif ## Both sides go through cellstr so that the padding a character matrix ## carries is stripped from each consistently: comparing a padded row of Y ## against an unpadded class name matches nothing. if (iscellstr (ClassNames) || ischar (ClassNames)) idx = cellfun (@(v) find (strcmp (v, cellstr (ClassNames)), 1), ... cellstr (Y), 'UniformOutput', false); else idx = arrayfun (@(v) find (v == ClassNames(:), 1), Y(:), ... 'UniformOutput', false); endif if (any (cellfun (@isempty, idx))) error ("svmPlusMinus: Y contains a label that is not in ClassNames."); endif idx = cell2mat (idx(:)); Ypm = ones (numel (idx), 1); Ypm(idx == 2) = -1; endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/uniqueLabels.m000066400000000000000000000032751524624707500265640ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {Private Function} {[@var{C}, @var{ia}, @var{ic}] =} uniqueLabels (@var{Y}) ## The distinct class labels of a response, whatever type it is given in. ## ## @code{unique} compares a character matrix element by element, so a response ## naming its classes in the rows of a character matrix comes back as the ## distinct @emph{letters} of those names rather than the names themselves. ## Comparing by rows is what that response needs, and it is what every other ## accepted type already gets: a cell array of character vectors, a numeric ## column and a logical column each hold one label per row already. ## ## The three outputs carry @code{unique}'s own meanings, with rows in place of ## elements where the response is a character matrix. ## @end deftypefn function [C, ia, ic] = uniqueLabels (Y) if (ischar (Y)) [C, ia, ic] = unique (Y, "rows"); else [C, ia, ic] = unique (Y); endif endfunction statistics-release-1.9.2/inst/Supervised_Learning/private/weightedMedian.m000066400000000000000000000025661524624707500270530ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {Private Function} {@var{m} =} weightedMedian (@var{Y}, @var{W}) ## ## The weighted median of @var{Y}, the value at which half the weight lies ## on each side. ## ## @end deftypefn ## The weighted median, the value at which half the weight lies on each side. ## It is what a fit of the epsilon-insensitive loss starts its intercept ## from, that loss being an absolute deviation once the band is crossed. function m = weightedMedian (Y, W) [ys, k] = sort (Y); ws = W(k); c = cumsum (ws) / sum (ws); j = find (c >= 0.5, 1); m = ys(j); endfunction statistics-release-1.9.2/inst/cholcov.m000066400000000000000000000101721524624707500201400ustar00rootroot00000000000000## Copyright (C) 2022 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{T} =} cholcov (@var{sigma}) ## @deftypefnx {statistics} {[@var{T}, @var{p} =} cholcov (@var{sigma}) ## @deftypefnx {statistics} {[@dots{}] =} cholcov (@var{sigma}, @var{flag}) ## ## Cholesky-like decomposition for covariance matrix. ## ## @code{@var{T} = cholcov (@var{sigma})} computes matrix @var{T} such that ## @var{sigma} = @var{T}' @var{T}. @var{sigma} must be square, symmetric, and ## positive semi-definite. ## ## If @var{sigma} is positive definite, then @var{T} is the square, upper ## triangular Cholesky factor. If @var{sigma} is not positive definite, @var{T} ## is computed with an eigenvalue decomposition of @var{sigma}, but in this case ## @var{T} is not necessarily triangular or square. Any eigenvectors whose ## corresponding eigenvalue is close to zero (within a tolerance) are omitted. ## If any remaining eigenvalues are negative, @var{T} is empty. ## ## The tolerance is calculated as @code{10 * eps (max (abs (diag (sigma))))}. ## ## @code{[@var{T}, @var{p} = cholcov (@var{sigma})} returns in @var{p} the ## number of negative eigenvalues of @var{sigma}. If @var{p} > 0, then @var{T} ## is empty, whereas if @var{p} = 0, @var{sigma}) is positive semi-definite. ## ## If @var{sigma} is not square and symmetric, P is NaN and T is empty. ## ## @code{[@var{T}, @var{p} = cholcov (@var{sigma}, 0)} returns @var{p} = 0 if ## @var{sigma} is positive definite, in which case @var{T} is the Cholesky ## factor. If @var{sigma} is not positive definite, @var{p} is a positive ## integer and @var{T} is empty. ## ## @code{[@dots{}] = cholcov (@var{sigma}, 1)} is equivalent to ## @code{ [@dots{}] = cholcov (@var{sigma})}. ## ## @seealso{chov} ## @end deftypefn function [T, p] = cholcov (sigma, flag) ## Check number of input arguments narginchk (1,2) ## Add default flag if not given if (nargin < 2) flag = 1; endif ## Check if sigma is a sparse matrix is_sparse = issparse (sigma); ## Check if sigma is single or double class is_type = 'double'; if (isa (sigma, 'single')) is_type = 'single'; endif ## Check if sigma is square and symmetric [col, row] = size (sigma); ## Add tolerance Tol = 10 * eps (max (abs (diag (sigma)))); if ((row == col) && all (all (abs (sigma - sigma') < col * Tol))) ## Check if positive definite [T, p] = chol (sigma); if (p > 0) ## Check flag for factoring using eigenvalue decomposition if (flag) [V, LAMBDA] = eig (full ((sigma + sigma') / 2)); [~, EIGMAX] = max (abs (V), [], 1); neg_idx = (V(EIGMAX + (0:row:(col-1)*row)) < 0); V(:,neg_idx) = -V(:,neg_idx); LAMBDA = diag (LAMBDA); Tol = eps (max (LAMBDA)) * length (LAMBDA); t = (abs (LAMBDA) > Tol); LAMBDA = LAMBDA(t); p = sum (LAMBDA < 0); ## Check for negative eigenvalues if (p == 0) T = diag (sqrt (LAMBDA)) * V(:,t)'; else T = zeros (0, is_type); endif else T = zeros (0, is_type); endif endif else T = zeros (0, is_type); p = NaN (is_type); endif if (is_sparse) T = sparse (T); endif endfunction %!demo %! C1 = [2, 1, 1, 2; 1, 2, 1, 2; 1, 1, 2, 2; 2, 2, 2, 3] %! T = cholcov (C1) %! C2 = T'*T %!test %! C1 = [2, 1, 1, 2; 1, 2, 1, 2; 1, 1, 2, 2; 2, 2, 2, 3]; %! T = cholcov (C1); %! assert_equal (C1, T'*T, 1e-15 * ones (size (C1))); statistics-release-1.9.2/inst/datasets/000077500000000000000000000000001524624707500201345ustar00rootroot00000000000000statistics-release-1.9.2/inst/datasets/acetylene.mat000066400000000000000000000045241524624707500226150ustar00rootroot00000000000000Octave-1-L Description sq_stringi= M xxxy SKC RMT= u 123: ouh eah l ::: une freA t C rum eq c i RRCo cgi ruAe p eaon eic eamt l atnv :,a nrey e cete Tl cdrl t ar . etie r oocs ,E :,cn e rfti n ae g o Tg Dn r tHtn .i . D e e2i n ,Sa s m mo Te tt s ptef ae Raa i eo mr .t o r (n ui s= n ans- rn St= t-eh ag ni d uhce , ec a reop P ei t epnt "r ,a a tda No n (asn eg ", w dn)e wr R i ee e iv t g t As d. h r( o cs g2 em e, e9 c eo a t o sl c yv Rn r e e l. eo r c t e5 g. e er y n7 r1 l na l e e a tt e ( s( t ii n P1 s1 e go e r9 i9 d r) o6 o7 a ( c1 n5 p d % e) ) r e ) s, i, e ) s n d p p i Up Pp c s. r. t e4 a3 o s3 c- r - t2 s H4 i0 y9 c. d. e r , o " g e n D i l u t i o n , " x1matrixP@P@P@P@P@P@@@@@@@0@0@0@0@x2matrix@"@&@+@1@7@333333@@&@+@1@7@333333@@&@1@x3matrix~jt?~jt?Zd;O?9v?S㥋?~jt?{Gz?~jt?Mb?9v? rh?ˡE?/$?J +?Zd;O?jt?ymatrixH@I@@I@@H@G@@F@<@?@@A@A@C@@C@.@1@4@=@statistics-release-1.9.2/inst/datasets/arrhythmia.mat000066400000000000000000037416671524624707500230370ustar00rootroot00000000000000Octave-1-L Description sq_stringEChSY Nate ortei mdp s121ii:V -6na/at=1 ac/rh 5=l aNen ara o=uarcmc ntrhelavcthisaralryv srraitefshisbh.o yosumiratuitic thsfeasntm is .arice dumialdaaceb a rtisu scea. t sa eoe escfdf seoru r sdo/2a o emm7n f d l9g t/ i a ahdin r seang r tp h nUauf y uCstr t mIe o h e tvm m rmsa i ia/r1 a ccAi hrat virbo anhl reye1 i ts6 alh.: bem lai era sn i wn ig t hr e vp ao ls ui et so r 0y : a n d 1VarNamescell sq_string Age, years sq_stringSex (0=male, 1=female) sq_string Height, cm sq_string Weight, kg sq_string QRS duration sq_string P-R interval sq_string Q-T interval sq_string T interval sq_string P interval sq_string QRS angle sq_stringT angle sq_stringP angle sq_string QRST angle sq_stringJ angle sq_stringHeart rate per minute sq_stringDI Q wave width sq_stringDI R wave width sq_stringDI S wave width sq_stringDI R' wave width sq_stringDI S' wave width sq_stringDI N of intrinsic deflections sq_stringDI Existence of ragged R wave sq_string-DI Existence of diphasic derivation of R wave sq_stringDI Existence of ragged P wave sq_string-DI Existence of diphasic derivation of P wave sq_stringDI Existence of ragged T wave sq_string-DI Existence of diphasic derivation of T wave sq_stringDII Q wave width sq_stringDII R wave width sq_stringDII S wave width sq_stringDII R' wave width sq_stringDII S' wave width sq_stringDII N of intrinsic deflections sq_stringDII Existence of ragged R wave sq_string.DII Existence of diphasic derivation of R wave sq_stringDII Existence of ragged P wave sq_string.DII Existence of diphasic derivation of P wave sq_stringDII Existence of ragged T wave sq_string.DII Existence of diphasic derivation of T wave sq_stringDIII Q wave width sq_stringDIII R wave width sq_stringDIII S wave width sq_stringDIII R' wave width sq_stringDIII S' wave width sq_stringDIII N of intrinsic deflections sq_stringDIII Existence of ragged R wave sq_string/DIII Existence of diphasic derivation of R wave sq_stringDIII Existence of ragged P wave sq_string/DIII Existence of diphasic derivation of P wave sq_stringDIII Existence of ragged T wave sq_string/DIII Existence of diphasic derivation of T wave sq_stringAVR Q wave width sq_stringAVR R wave width sq_stringAVR S wave width sq_stringAVR R' wave width sq_stringAVR S' wave width sq_stringAVR N of intrinsic deflections sq_stringAVR Existence of ragged R wave sq_string.AVR Existence of diphasic derivation of R wave sq_stringAVR Existence of ragged P wave sq_string.AVR Existence of diphasic derivation of P wave sq_stringAVR Existence of ragged T wave sq_string.AVR Existence of diphasic derivation of T wave sq_stringAVL Q wave width sq_stringAVL R wave width sq_stringAVL S wave width sq_stringAVL R' wave width sq_stringAVL S' wave width sq_stringAVL N of intrinsic deflections sq_stringAVL Existence of ragged R wave sq_string.AVL Existence of diphasic derivation of R wave sq_stringAVL Existence of ragged P wave sq_string.AVL Existence of diphasic derivation of P wave sq_stringAVL Existence of ragged T wave sq_string.AVL Existence of diphasic derivation of T wave sq_stringAVF Q wave width sq_stringAVF R wave width sq_stringAVF S wave width sq_stringAVF R' wave width sq_stringAVF S' wave width sq_stringAVF N of intrinsic deflections sq_stringAVF Existence of ragged R wave sq_string.AVF Existence of diphasic derivation of R wave sq_stringAVF Existence of ragged P wave sq_string.AVF Existence of diphasic derivation of P wave sq_stringAVF Existence of ragged T wave sq_string.AVF Existence of diphasic derivation of T wave sq_stringV1 Q wave width sq_stringV1 R wave width sq_stringV1 S wave width sq_stringV1 R' wave width sq_stringV1 S' wave width sq_stringV1 N of intrinsic deflections sq_stringV1 Existence of ragged R wave sq_string-V1 Existence of diphasic derivation of R wave sq_stringV1 Existence of ragged P wave sq_string-V1 Existence of diphasic derivation of P wave sq_stringV1 Existence of ragged T wave sq_string-V1 Existence of diphasic derivation of T wave sq_stringV2 Q wave width sq_stringV2 R wave width sq_stringV2 S wave width sq_stringV2 R' wave width sq_stringV2 S' wave width sq_stringV2 N of intrinsic deflections sq_stringV2 Existence of ragged R wave sq_string-V2 Existence of diphasic derivation of R wave sq_stringV2 Existence of ragged P wave sq_string-V2 Existence of diphasic derivation of P wave sq_stringV2 Existence of ragged T wave sq_string-V2 Existence of diphasic derivation of T wave sq_stringV3 Q wave width sq_stringV3 R wave width sq_stringV3 S wave width sq_stringV3 R' wave width sq_stringV3 S' wave width sq_stringV3 N of intrinsic deflections sq_stringV3 Existence of ragged R wave sq_string-V3 Existence of diphasic derivation of R wave sq_stringV3 Existence of ragged P wave sq_string-V3 Existence of diphasic derivation of P wave sq_stringV3 Existence of ragged T wave sq_string-V3 Existence of diphasic derivation of T wave sq_stringV4 Q wave width sq_stringV4 R wave width sq_stringV4 S wave width sq_stringV4 R' wave width sq_stringV4 S' wave width sq_stringV4 N of intrinsic deflections sq_stringV4 Existence of ragged R wave sq_string-V4 Existence of diphasic derivation of R wave sq_stringV4 Existence of ragged P wave sq_string-V4 Existence of diphasic derivation of P wave sq_stringV4 Existence of ragged T wave sq_string-V4 Existence of diphasic derivation of T wave sq_stringV5 Q wave width sq_stringV5 R wave width sq_stringV5 S wave width sq_stringV5 R' wave width sq_stringV5 S' wave width sq_stringV5 N of intrinsic deflections sq_stringV5 Existence of ragged R wave sq_string-V5 Existence of diphasic derivation of R wave sq_stringV5 Existence of ragged P wave sq_string-V5 Existence of diphasic derivation of P wave sq_stringV5 Existence of ragged T wave sq_string-V5 Existence of diphasic derivation of T wave sq_stringV6 Q wave width sq_stringV6 R wave width sq_stringV6 S wave width sq_stringV6 R' wave width sq_stringV6 S' wave width sq_stringV6 N of intrinsic deflections sq_stringV6 Existence of ragged R wave sq_string-V6 Existence of diphasic derivation of R wave sq_stringV6 Existence of ragged P wave sq_string-V6 Existence of diphasic derivation of P wave sq_stringV6 Existence of ragged T wave sq_string-V6 Existence of diphasic derivation of T wave sq_stringDI JJ wave amplitude sq_stringDI Q wave amplitude sq_stringDI R wave amplitude sq_stringDI S wave amplitude sq_stringDI R' wave amplitude sq_stringDI S' wave amplitude sq_stringDI P wave amplitude sq_stringDI T wave amplitude sq_stringDI QRSA sq_stringDI QRSTA sq_stringDII JJ wave amplitude sq_stringDII Q wave amplitude sq_stringDII R wave amplitude sq_stringDII S wave amplitude sq_stringDII R' wave amplitude sq_stringDII S' wave amplitude sq_stringDII P wave amplitude sq_stringDII T wave amplitude sq_stringDII QRSA sq_string DII QRSTA sq_stringDIII JJ wave amplitude sq_stringDIII Q wave amplitude sq_stringDIII R wave amplitude sq_stringDIII S wave amplitude sq_stringDIII R' wave amplitude sq_stringDIII S' wave amplitude sq_stringDIII P wave amplitude sq_stringDIII T wave amplitude sq_string DIII QRSA sq_string DIII QRSTA sq_stringAVR JJ wave amplitude sq_stringAVR Q wave amplitude sq_stringAVR R wave amplitude sq_stringAVR S wave amplitude sq_stringAVR R' wave amplitude sq_stringAVR S' wave amplitude sq_stringAVR P wave amplitude sq_stringAVR T wave amplitude sq_stringAVR QRSA sq_string AVR QRSTA sq_stringAVL JJ wave amplitude sq_stringAVL Q wave amplitude sq_stringAVL R wave amplitude sq_stringAVL S wave amplitude sq_stringAVL R' wave amplitude sq_stringAVL S' wave amplitude sq_stringAVL P wave amplitude sq_stringAVL T wave amplitude sq_stringAVL QRSA sq_string AVL QRSTA sq_stringAVF JJ wave amplitude sq_stringAVF Q wave amplitude sq_stringAVF R wave amplitude sq_stringAVF S wave amplitude sq_stringAVF R' wave amplitude sq_stringAVF S' wave amplitude sq_stringAVF P wave amplitude sq_stringAVF T wave amplitude sq_stringAVF QRSA sq_string AVF QRSTA sq_stringV1 JJ wave amplitude sq_stringV1 Q wave amplitude sq_stringV1 R wave amplitude sq_stringV1 S wave amplitude sq_stringV1 R' wave amplitude sq_stringV1 S' wave amplitude sq_stringV1 P wave amplitude sq_stringV1 T wave amplitude sq_stringV1 QRSA sq_stringV1 QRSTA sq_stringV2 JJ wave amplitude sq_stringV2 Q wave amplitude sq_stringV2 R wave amplitude sq_stringV2 S wave amplitude sq_stringV2 R' wave amplitude sq_stringV2 S' wave amplitude sq_stringV2 P wave amplitude sq_stringV2 T wave amplitude sq_stringV2 QRSA sq_stringV2 QRSTA sq_stringV3 JJ wave amplitude sq_stringV3 Q wave amplitude sq_stringV3 R wave amplitude sq_stringV3 S wave amplitude sq_stringV3 R' wave amplitude sq_stringV3 S' wave amplitude sq_stringV3 P wave amplitude sq_stringV3 T wave amplitude sq_stringV3 QRSA sq_stringV3 QRSTA sq_stringV4 JJ wave amplitude sq_stringV4 Q wave amplitude sq_stringV4 R wave amplitude sq_stringV4 S wave amplitude sq_stringV4 R' wave amplitude sq_stringV4 S' wave amplitude sq_stringV4 P wave amplitude sq_stringV4 T wave amplitude sq_stringV4 QRSA sq_stringV4 QRSTA sq_stringV5 JJ wave amplitude sq_stringV5 Q wave amplitude sq_stringV5 R wave amplitude sq_stringV5 S wave amplitude sq_stringV5 R' wave amplitude sq_stringV5 S' wave amplitude sq_stringV5 P wave amplitude sq_stringV5 T wave amplitude sq_stringV5 QRSA sq_stringV5 QRSTA sq_stringV6 JJ wave amplitude sq_stringV6 Q wave amplitude sq_stringV6 R wave amplitude sq_stringV6 S wave amplitude sq_stringV6 R' wave amplitude sq_stringV6 S' wave amplitude sq_stringV6 P wave amplitude sq_stringV6 T wave amplitude sq_stringV6 QRSA sq_stringV6 QRSTAXmatrixR@L@K@K@R@*@D@H@F@I@O@F@K@>@F@G@G@G@@R@L@<@F@B@L@D@F@A@?@L@I@J@M@I@J@@Q@F@I@A@O@F@E@D@>@A@D@R@@Q@>@D@A@R@<@C@8@J@R@C@E@G@@@?A@B@;@H@F@K@:@F@P@B@B@?@G@A@A@F@B@L@Q@B@L@S@I@O@R@3@D@Q@@R@J@P@O@O@9@K@@@B@J@B@I@C@3@;@B@O@G@L@1@I@Q@E@@@"@=@C@R@<@8@M@@Q@I@R@Q@B@9@M@N@J@G@L@H@C@G@D@D@G@G@A@I@A@?H@?@A@B@2@A@C@1@H@@@B@A@;@F@F@N@M@G@E@E@K@8@D@9@Q@3@@R@D@F@I@@@L@K@K@P@;@P@L@O@2@F@L@@P@N@G@O@6@P@H@N@@P@B@F@;@5@G@*@M@Q@P@I@A@@@G@J@O@E@G@&@2@O@J@H@>@1@P@I@@S@G@E@L@T@E@P@B@E@B@H@R@T@6@L@G@F@E@B@L@C@E@O@G@H@R@N@H@J@F@G@.@M@G@M@P@Q@J@F@H@F@@@A@C@C@A@A@M@I@E@I@R@H@N@O@N@P@@@M@H@?@D@H@L@A@@R@@P@@@A@L@I@N@F@D@K@F@@@M@@(@I@M@O@R@P@?@,@B@J@O@D@D@T@@Q@Q@P@L@J@K@B@@:@M@K@C@H@M@P@P@M@M@@P@E@B@3@G@?@@@C@E@K@F@H@R@B@@@4@@T@A@O@P@J@H@:@F@G@G@L@D@K@O@D@G@L@C@G@E@M@P@>@Q@G@H@R@.@Q@P@K@P@ @P@L@B@G@A@R@S@J@F@Q@P@B@I@0@M@N@F@>@G@Q@K@"@P@@O@5@S@9@M@P@L@P@D@J@P@>@;@@P@I@;@M@O@=@I@@B@A@M@P@ @&@G@&@Q@4@C@@@A@B@H@B@B@@P@D@=@F@4@J@B@B@@@S@?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????g@d@e@e@g@ e@d@@d@e@d@@e@d@e@@e@d@b@`e@c@d@d@d@ e@ c@d@ c@ e@@e@d@d@d@e@`d@d@`c@f@d@e@d@`d@e@c@d@d@d@d@c@d@c@c@c@c@c@e@`d@d@d@d@e@d@d@[@d@c@@d@`c@`c@d@d@b@c@`c@f@ d@`d@c@d@ f@`e@d@c@c@ f@b@d@d@c@d@@d@ d@c@`c@`c@`d@d@e@ g@b@c@`c@d@c@`c@c@@e@d@e@d@ c@@e@g@@d@f@`d@`@e@d@e@c@@d@@e@@c@d@@e@@e@c@@d@@g@ g@`c@`d@d@d@c@ f@`c@e@e@@d@@d@d@@e@`@d@b@e@e@e@`c@d@d@c@e@e@d@ d@ e@@d@d@ d@ c@@e@d@@e@f@d@@d@e@c@d@d@@f@e@`d@e@c@d@d@`c@d@@d@c@e@`f@e@`c@@e@`d@@e@d@c@d@d@f@e@e@`c@d@e@`@`c@d@d@c@d@e@@d@@f@f@c@f@_@f@e@ e@d@d@c@d@@e@d@d@@f@e@d@d@`e@e@e@@e@@e@`e@e@`d@d@d@e@f@b@@e@d@d@`c@ f@d@d@`c@c@ c@d@d@@e@e@@e@d@d@d@d@@e@@f@@e@d@d@d@@e@b@`c@d@e@`d@d@d@c@`e@d@@c@c@c@b@d@e@e@@e@e@d@d@e@d@`c@e@`d@c@@e@@f@e@d@d@d@@`@d@@e@d@d@@e@f@d@e@e@`e@@e@f@f@d@d@d@d@@e@@e@@e@@e@@Z@f@ d@d@@e@@e@d@e@c@`c@d@`e@g@e@d@d@d@d@d@e@@e@c@@d@d@`e@c@@g@d@d@@f@e@c@d@@e@ e@e@e@@e@b@d@c@@e@`d@@e@e@@f@d@d@@e@f@@b@d@f@@c@`d@d@`d@d@d@^@e@d@ g@ e@d@@d@c@c@@e@@f@d@f@d@d@e@c@c@d@e@d@d@^@`c@_@d@c@b@d@b@d@e@d@@e@d@d@d@d@e@`d@d@d@d@@d@@g@]@`e@d@d@d@@`@@a@d@a@d@@f@d@d@`c@e@e@f@d@d@@c@d@e@c@d@g@d@`c@d@T@P@W@W@T@I@J@K@L@P@R@U@M@@R@V@H@M@M@O@R@M@P@R@M@K@T@@R@K@@P@T@@U@Q@@R@Q@R@F@T@W@N@T@Q@R@O@N@I@K@Q@L@K@Q@J@J@S@J@T@Q@O@S@P@M@$@I@M@J@K@@P@O@@P@Q@T@N@V@@R@Y@N@@P@R@@U@@R@L@J@Q@N@S@T@M@I@@P@Q@R@Z@V@O@Q@K@@Z@K@O@Z@Q@N@M@D@P@@P@T@K@N@N@X@R@V@R@@@@Q@F@R@L@R@R@M@R@R@J@L@S@2@W@O@U@K@Q@L@V@L@N@P@R@Q@T@T@@@R@P@N@R@N@K@O@K@O@R@V@N@I@@R@N@T@Q@Q@T@Q@S@R@J@H@M@G@T@Q@@V@T@@P@S@S@P@P@K@Q@P@Q@P@@U@T@Q@S@K@@U@J@T@O@N@S@@T@Q@F@Q@N@>@N@V@T@Q@I@@Z@@P@U@@R@@P@W@9@O@S@f@T@@P@D@N@T@@S@P@R@U@K@K@P@T@V@@U@S@R@@U@I@P@T@Q@R@H@T@X@K@S@@U@U@Q@@P@W@K@P@N@J@S@T@Q@_@J@P@R@T@R@@U@P@R@W@F@K@O@V@@U@Z@Q@J@Q@O@O@@R@M@S@N@T@@X@T@@T@W@M@@P@@Q@M@Q@M@R@R@S@R@@P@P@R@>@G@T@@P@K@P@T@K@M@Q@S@[@R@V@Q@N@Q@R@@P@$@T@R@V@(@@X@W@T@@Q@R@Q@@P@S@R@R@@U@V@V@@P@@P@L@M@Q@T@Q@N@U@P@S@O@P@N@N@@V@O@@P@H@E@T@T@W@T@H@Q@@P@Q@T@P@N@U@T@J@Q@S@O@@S@@S@K@C@U@S@P@Q@<@T@R@W@T@M@R@G@Q@U@T@J@@P@@U@N@R@@S@@P@J@W@T@Q@9@O@6@Q@F@K@L@@P@Q@R@N@P@N@J@@S@H@P@@P@E@V@Q@L@W@5@@W@J@@P@O@8@=@L@E@N@@P@O@L@O@T@P@R@I@I@R@O@R@L@Q@@U@Q@K@Q@V@@T@@a@Y@V@Y@@S@S@U@@V@Y@@S@S@V@@S@R@T@Q@V@T@T@V@Q@R@T@@[@W@W@V@X@@U@Q@R@S@T@@Q@Y@@U@T@W@T@S@W@U@U@@R@R@@R@S@T@@T@T@Y@W@V@@S@S@V@V@T@T@S@S@T@@T@T@T@Q@@R@U@S@@Y@@W@V@U@V@W@W@V@S@T@W@@W@Y@T@`d@X@\@@e@ a@U@T@S@R@@X@U@Y@@U@U@S@S@V@X@U@R@@X@T@S@@X@W@T@S@S@U@@W@R@Z@X@V@V@R@@U@V@Q@R@@S@U@W@R@X@@V@T@S@Y@T@U@S@Y@T@@U@@T@@U@W@T@@X@W@Y@U@T@@U@@U@[@@V@O@Q@W@R@@W@T@U@V@@T@@\@W@@T@R@S@T@T@W@Z@W@Y@V@V@Q@S@@S@U@S@@U@V@W@X@Q@U@W@[@[@ c@@R@@T@U@U@W@@R@W@Y@X@@X@S@O@U@Q@X@Q@V@@b@U@T@V@@W@V@[@@V@S@@S@@R@W@X@S@U@X@U@T@V@@U@Q@U@@U@@S@@W@R@@V@@V@Y@@U@V@@T@U@X@V@X@S@@W@T@@U@P@R@@Q@@Z@W@U@[@X@S@U@T@V@@W@T@U@V@@T@V@S@X@@T@V@@U@S@N@V@S@T@U@S@U@R@U@V@Y@T@T@Z@@U@T@@T@W@U@V@X@@Y@`@S@Y@@X@``@V@V@@^@S@U@@X@W@X@V@T@@X@W@V@V@^@@Z@@U@@S@T@W@@U@X@@Q@V@U@Z@V@R@@V@@[@T@T@W@Y@@\@@Z@X@U@R@S@U@U@X@V@T@@U@V@R@W@U@R@W@R@@R@Z@W@@Z@@X@@Y@T@U@X@@T@@W@U@Y@W@V@Y@W@W@X@@b@U@@S@T@U@S@Z@Q@U@]@U@T@X@@V@@W@T@@X@U@g@@W@R@Z@@T@S@T@`b@T@[@[@T@@W@W@T@ g@@R@S@Q@U@@U@S@@W@U@X@U@S@U@T@X@R@S@T@W@T@W@a@U@K@`@T@@S@^@S@V@T@V@S@@S@U@V@W@V@R@@U@V@@T@V@@T@T@Y@[@@W@S@ h@e@`d@@i@f@d@ `@]@@`@`@a@`c@f@c@`@ b@^@@c@f@`o@^@`@c@a@`@@g@ d@d@`b@c@a@@_@ a@ b@@f@a@i@ g@`d@@d@@c@@c@c@ f@c@c@ a@l@`@f@`@`b@c@@i@a@ b@f@^@@^@`b@a@e@@]@c@b@@c@`e@d@`r@e@`c@@a@c@d@b@`b@`h@`b@c@@f@ b@f@`b@b@g@h@d@ k@@h@d@ e@`@@r@a@ b@g@]@ b@`e@f@`h@a@`b@`@d@`r@`i@@e@c@ `@`d@d@ c@b@c@c@a@f@c@@d@a@d@h@d@`d@@`@@b@`c@0p@`@@b@c@_@c@d@`@d@c@`h@d@@h@`@_@^@ a@f@`e@d@d@a@_@^@@^@@h@ c@`g@`b@k@ c@d@\@c@ a@a@`d@f@c@ b@c@b@@d@i@c@e@d@`a@n@c@i@@f@`g@c@g@c@`@g@ a@ b@`a@c@g@ g@f@`@`g@d@e@_@a@e@f@@a@@[@e@ d@d@b@d@e@``@Z@@f@@\@a@e@@g@c@g@h@f@`a@`@e@f@`@`c@f@`c@d@@e@@d@a@f@e@ f@ f@b@ f@ c@ `@c@b@ b@b@f@@c@@e@ c@`d@l@`d@b@`c@c@e@`@c@c@@Z@ e@b@c@t@e@a@c@`d@e@`b@i@h@h@b@`d@`@ d@e@g@b@ b@ a@d@d@g@@e@ b@b@ d@`c@a@e@b@_@a@ g@ e@k@ i@ h@@f@@c@d@_@c@h@@f@`c@h@c@f@d@d@c@e@c@ j@c@`a@j@c@`@b@h@@`@d@``@@c@@b@@c@e@c@g@^@g@@f@f@d@Y@h@h@e@f@d@ e@e@b@_@ f@d@c@g@`@@d@\@i@e@h@a@`@d@e@f@c@_@d@i@c@a@g@f@@^@a@@_@]@d@b@c@`@d@`b@^@@c@e@f@`c@]@g@i@ f@b@@g@@d@a@d@c@``@g@`@f@d@c@h@b@ c@e@d@`i@c@b@`d@b@@_@ b@ b@^@b@`c@`c@b@a@@b@@e@ c@a@a@c@a@`@b@h@ a@f@Z@_@0w@y@ x@w@v@t@w@w@ v@w@y@Pw@w@0v@x@u@u@v@x@x@w@u@v@`y@@x@w@Pw@py@@z@y@y@0u@v@w@Pv@Pv@v@x@ v@y@w@u@u@w@v@u@ t@w@z@w@ x@pv@@v@ w@w@x@w@v@@w@ x@q@@t@Pw@`w@w@v@y@u@y@w@x@@v@v@ z@w@0w@z@w@@x@t@t@u@v@pv@w@z@Pw@w@0z@x@@z@`u@0w@t@ u@`y@v@x@ |@ n@w@0x@pu@y@0w@ w@y@w@v@v@v@w@v@t@`x@ z@y@@u@v@w@w@0u@ x@Pt@0u@w@@w@@u@ x@x@u@ z@u@w@v@v@x@Pv@v@0v@u@m@w@v@0u@0x@w@w@pw@0t@x@u@Pv@x@y@ w@Pv@w@x@w@w@u@y@u@u@w@@v@`u@u@t@w@`v@w@w@w@y@u@u@x@p@y@v@p{@s@u@@u@v@z@`y@Pz@u@ v@x@u@x@t@w@y@@v@`x@x@w@`s@v@u@px@0v@`{@`w@w@u@t@y@0u@t@v@}@v@t@pv@`u@@v@@w@`v@w@u@w@@v@v@w@t@`v@ w@y@y@u@t@Px@v@@v@v@r@py@ t@v@w@w@s@t@`t@v@w@u@@x@t@v@@s@Pw@t@u@u@@t@Px@u@ v@t@`v@v@Pv@w@w@x@w@pz@v@pw@0w@v@v@ w@ y@u@v@x@@w@Pw@w@ x@w@ w@0x@w@0v@`x@u@0w@v@w@pv@w@t@x@{@Pv@ {@@u@ t@v@`r@v@Pu@@y@{@t@v@pv@m@0v@u@u@n@@y@x@pv@Pv@w@0v@`w@pw@u@@x@@x@`v@ v@w@w@x@t@Pv@u@t@w@ v@ x@v@0x@w@pv@r@w@`w@pv@pv@v@pt@t@u@t@z@ r@@|@w@v@z@y@0w@u@pv@Py@Pw@|@w@Pt@w@@u@x@v@0u@ x@r@ x@Pr@t@w@v@Pv@w@r@|@ p@w@py@Pv@t@w@@u@z@Pu@w@0x@u@ z@q@w@v@@x@w@z@w@w@x@w@ u@u@v@pu@0u@v@`w@q@p{@pv@v@`{@v@@u@z@v@`v@v@w@v@ r@v@v@@u@px@Pv@w@Px@`w@v@x@Pt@w@v@w@v@v@ x@v@e@b@ g@`f@ f@e@`@c@d@c@c@b@`d@c@`d@ e@ e@^@e@c@g@ f@ e@a@b@`h@l@e@w@r@e@c@f@b@ `@ a@ d@e@d@c@a@d@c@b@`b@@a@e@a@@`@b@ a@ e@@d@a@@\@d@d@e@ d@e@c@`c@c@d@ j@a@a@d@b@c@e@d@d@@d@@f@d@c@a@@f@b@`@f@`@b@ b@@n@`b@k@p@n@k@@a@`e@`@`m@h@e@f@ h@c@e@@]@`@f@ a@@e@[@h@a@`i@b@d@d@d@ a@`c@ d@c@c@e@@c@c@e@`@a@i@b@`a@b@b@e@`@`b@`f@c@d@e@`@`@b@c@b@`d@c@`d@e@d@c@ c@a@e@`m@e@`a@c@f@a@a@ e@f@ e@``@ h@d@@c@]@d@`n@d@a@`b@d@c@b@@a@`a@c@b@d@h@e@g@d@@`@d@l@^@h@@k@o@@c@_@ a@i@b@g@`e@`b@`e@ e@`c@a@k@e@n@`b@b@q@d@g@ j@e@h@i@`d@a@`b@b@h@b@@_@@i@ d@d@b@`p@f@``@g@b@a@b@`d@@^@h@ o@c@b@@c@c@@e@ j@f@^@`a@l@@o@ a@ d@ d@a@@i@`o@h@e@e@j@p@c@l@a@@f@d@@c@ c@@`@@c@h@b@`d@c@e@c@@a@a@@b@c@@]@`c@d@o@e@ e@@d@c@`b@@_@ c@`b@a@g@g@`i@c@ p@`@`o@k@c@k@d@a@f@@e@_@ l@`g@`@g@ d@ i@c@f@ b@c@a@`@d@@a@ e@`@g@a@ n@e@a@b@@k@f@r@`d@f@k@ f@g@@r@a@d@@f@b@@e@c@`e@b@@e@e@ d@@b@_@c@@a@o@ c@b@@k@`e@l@ l@d@`d@d@p@ a@`d@b@@d@c@n@m@c@q@@b@`m@`e@n@`d@h@a@c@d@`n@`g@c@`c@e@a@l@ c@Pp@`c@d@f@b@a@[@pt@e@@c@e@c@e@c@@f@^@@e@@c@@e@g@`f@ c@o@ e@@i@c@_@e@d@b@ a@d@h@`a@@c@`e@@l@ f@@d@@p@^@c@k@e@l@`@`d@ c@b@ a@`f@c@e@@b@@b@`@@k@d@d@@c@ i@@h@@k@@a@@^@C@Y@a@Y@V@@S@Q@O@@R@T@@P@@T@Z@W@@P@N@J@T@S@f@S@T@W@T@N@@_@T@X@T@V@S@O@T@@Y@T@W@R@Z@Z@U@W@X@Q@@\@X@@Z@@S@W@@X@Z@T@T@Q@@X@V@S@S@K@P@R@T@S@O@R@U@@T@@T@Y@Y@a@[@]@Q@W@@T@@T@W@Y@X@@]@T@W@Q@@X@Y@X@]@@Z@@]@_@Y@@U@X@e@T@V@@V@Q@T@@W@b@T@V@@W@@U@@V@@f@Y@@X@@P@N@V@X@V@X@\@X@@V@Y@Y@X@U@X@`@@X@@\@Q@T@Z@W@M@@V@V@T@]@_@S@Z@W@Y@S@T@@R@S@P@U@Z@W@P@U@I@O@R@U@@X@U@R@X@@e@S@@T@P@T@W@T@@W@W@X@@Y@V@@W@@X@b@V@@W@T@U@^@S@[@X@Y@W@\@Q@P@X@V@S@@W@@X@@V@@W@ a@P@@^@Z@U@Q@@T@\@[@U@N@@W@X@@Y@U@Y@@Y@@S@L@X@M@U@U@@\@T@X@@]@@Z@T@S@Z@R@Q@Y@[@R@@[@Z@Y@@U@U@Y@^@X@T@Y@K@S@V@T@W@X@@U@W@Z@U@U@^@Z@X@Y@@Z@Y@Q@[@\@Q@Y@P@@X@`c@R@S@X@Z@T@S@T@R@a@[@]@@Y@@]@a@T@@V@Y@@R@@X@W@\@Y@V@Y@T@Y@V@S@E@Y@P@U@Z@T@U@[@[@R@@Q@Y@N@@T@@W@\@P@X@Y@_@S@U@Z@@Z@Z@@Z@Z@V@Z@]@T@@U@Y@Q@@V@@S@\@S@@U@U@U@@Z@@T@@Z@@]@Z@\@B@@P@i@\@`a@]@U@P@U@]@Y@R@U@@X@Q@X@@Q@W@@P@]@S@Z@R@@T@[@T@[@\@T@[@\@Y@V@V@[@K@S@R@T@U@U@@[@i@@U@Q@O@W@X@Z@Y@P@Z@R@@W@U@Z@Z@W@R@Y@S@[@T@\@@V@X@f@@W@@U@Y@@Q@Z@@T@X@Y@W@V@Q@T@@Y@@T@@W@Q@W@T@V@R@\@@V@R@U@\@R@T@T@@]@@R@]@O@S@09@X@<@0Z@@S@P@N@@U@R@(@8Q@G@B@@S@L@@R@(I@@T@O@@J@AV@$@ BJ*@C@"@H@O@K@H@LJ@P@K@M@222@>@O@,?A@&@P@M@>@A@&E@@T@_@D@R@P@R@L@9@P@P@BA@@@O@8N@V@]@>@V@O@N@Q@F@KT@L@Q@P@O@,@;@"@* a3@@@8@6@:@$@R@ e@E@L@B@V@:@(D@M@S@O@(@K@05J@.@2@G:@N@L@C@=@U@H@P@T@M@@T@M@K@N@B@Q@V@2N@@V@7@N@R@S@8@IH@Q@9@I@W@D@,@P@I@&@P@L@II@&@I<@=@S@R@P@&T@4@4@E@@DS@O@8@4MS@6G@A@@R@V@(Y@@@@F@W@@T@.0@P@@R@T@[@0@U@A@@J@;@K@S@9@9R@A@S@O@N@,@@@P@@@1@P@O@S@?S@Q@7@D@R@@4@Q@K@$M@;@35COBHG@ @U@N@B@$A@Q@K@G@@U@?=6@@S@@Q@L@L@3@&@"@@P@D@P@C @3:H@Q@@R@G@0I@F@FM@P@`c@ @"@P@;HN@T@G@?C@T@@@5@<@d*@Q@@_@E@R@.@@W@]@@ND@@L8@@Q@N@V@J@,@4@[@P@P@<@@U@I@C@?@@Q@R@M@&\@@"@@@H@@OU@JJ@ @H@J@H@"@O@$*@AR@S@K@ @C@G@?@@$@R&@M@^@ `@W@J@@T@F@U@J@(?F@S@P@@T@U@?@C@Y@H@@F@U@3@eA@U@J@9@Q@@@O@R@R@Q@@S@E@7@L@9@P@DF@D@c=@U@\@J@2P6I@;@P@?W@P@Q@D@P@8@V@E@BU@@UK@<@*@B@A@&@*@P@H@@@Q@A@Q@B@E@I@4@F@R@H@8<@C@S@L@$@1@\@J@H@ c@e@0@4@@@L@G@7@*@A@@ $@H@J@@N@ @2S@G@P@A@"@F@S@@@,@>@8J@9@7@P@O@G@$@F@E@J@?@3@F@3@I@Q@dLQ@.@J@G@U@S@ae@.@_a@*$@"J@M@1@G@d N@@R@J@5@C@Q@5@Wa@*@E@O@H@1@6@7@3@*@R@L@D@L@@P@M@B@O@N@K@F@@S@F@8@@@G@K@J@@T@@@B@D@>@I@4@J@(@C@$@E@=@@Q@2@6@$@D@9@ @$@@@K@E@P@G@L@L@J@H@D@Z@@S@I@Q@P@@R@,@3@3@9@d.P@[@Rd@\@6^@\`b@C@7@L@T0@H@L@,@OA@J@eA@Y@L@B@`f@1@U@U@R@JHB@e@;W@^P@&@C@N@@P@H@*@,@ @N@E@ a@N@\@O@d@H@a@@J@ f@YF@ZG@P@P@<@H@E@]@_@R@B@d^@M@S@ @E@1@K@@@>@@@(@9@H@M@Q@J@0,@2@@@E@<@U@5@(@:@D@Y@8@I@ R@Q@H@5@^(@eB@,@D@I"@4@@Z@]B@&@]O@@@*@S@@9@O@5@F@V@U@W@0@`@`dP@3@@R@Z@H c@@E@C@@S@A@&@L@L@P@@S@C@F@D@B@2@J@.@O@P@`@\@C@ b@U@X@@_@@P@bX@E@@R@?@&@@d@J@@T@1`f@C@@S@K@V@[@D@I@1@_@S7.J@U@"@ f@b@R@M@$@D@Y@\G@J@U@.@B@D@\@8@D@Q@T@6@`@I@B@P@:Qa@@@N@`K@L@U@8@A@*@Y@K@1G@J@[@2@B@K@H@b@H@,@$F@;@Q@E@@P@3=@S@P@1Q@N@J@R@ @S@Q@Q@H@D@N@F@Q@@S@N@I@G@P@F@M@@Z@@c@N@C@D@K@N@@R@J@Q@K@B@.@D@Q@C@@P@P@N@N@L@Q@B@@S@N@O@K@K@O@B@K@N@P@N@@P@Q@Q@,O@O@.@D@N@G@A@P@?@I@N@B@H@R@R@O@N@P@L@L@R@R@N@^@2@D@P@L@>@S@2@G@L@@E@@Q@J@M@P@@P@@@I@B@F@G@K@0@J@1@Q@O@N@P@J@Z@@R@=@C@P@J@J@N@D@@R@Q@R@6@G@T@O@I@>@C@L@N@"N@L@@@S@O@M@B@P@L@ @L@O@E@E@M@G@M@H@N@N@@Q@G@I@D@S@@Q@;@&P@F@I@N@O@"@K@E@I@@R@E@K@0@S@E@L@N@@Q@L@Q@@P>@@1@D@@P@N@KB@N@L@R@@@S@O@*@P@E@N@N@I@O@M@Q@K@F@N@I@M@N@@R@L@G@@S@Q@L@M@Q@:@I@V@R@I@R@H@R@Q@R@Q@P@N@M@B@K@O@C@Q@G@P@@P@Q@;@M@A@A@@eF@R@@@I@J@$@P@:@O@H@A@I@G@L@S@D@H@Q@M@E@N@M@5@K@A@F@N@M@N@L@Q@D@@S@C@ M@L@@P@IL@M@C@K@H@L@F@R@R@J@Q@J@P@G@,U@R@Q@Q@K@P@O@K@Y@G@@@Q@P@R@P@I@I@@NR@>@N@P@C@@Q@<@E@,@A@Q@H@>@&@S@@S@<@Q@ L@K@J@E@?@M@N@B@L@R@M@F@J@F@@W?@9P@H@@P@@R@O@N@>@J@K@E@=@f@F@G@S@C@N@;P@2@JJ@R@G@R@A@H@M@O@R@N@=@H@@Q@O@N@0@,@N@P@:@U@R@S@L@I@@P@M@H@K@F@@@?@R@D@J@N6J@?@P@4@@V@@P@I@P@Q@R@:@*O@D@D@R@K@E@?E@T@N@@E@@@S@>@P@,@A@5@G@J@>@C@HB@E@J@L@(,@4@Q@$;@@@"@M@O@?@:@@@@S@Z@>@Q@O@P@?@C@L@M@@@@:@L@ L@R@`a@(@T@K@H@M@F@:@T@O@T@I@`H@@"@*I@I@9@B@@>@Q@_@G@G@@(@<@W@K@D@O@O@G@3@E@?L@;@;@$J@L@G@1@H@C@@T@G@K@J@I@@R@L@S@K@B@O@J@0@H@F@ @@Q@.@I@I@R@5@?@M@9@C@T@C@0@K@M@A@O@L@;@F@B@,@=J@C@S@@O@H@N@H@D@1@ER@M@R@&@@P@$3@;@M@@T@$@@@.@5C@X@R@&@=@K@@T@U@T@0@@Q@ 5@U@?T@@U@H@M@H@Q@L@@@*@@@P@B@C@N@@P@@R@H@P@@Y@4@:@@P@(@P@J@EK@D@?*@<P4 @C@CY@O@H@A@:@P@G@F@P@ 9@Q@O@P@K@@(@(@N@D@I@,@3&@&M@O@8@@P@F@=P@M@2N@$@4@@&K@O@<@@6@R@@@(@Q@*3@P@Y@E@S@Q@W@V@2@0"@@@@@E,@]@L@S@@@@P@@@W@N@M@?@T@5@C@A@J@L@E@@Z@4@:@8@@P@( b@V@=@P@C@N@J@Q@1@P@ @(@P@Q@S@:@7@C@F@E@G@T7@K@Q@@_@T@D@@Q@@@U@D@C@O@Q@I@@R@V@1E@S@37@D@P@?@d@C@S@O@8@W@A@O@N@@R@K@G@*@C@H@@T@4@M@@@@Q@B@B@=D@S@I@H@@@@PG@D@M@@@S@M@K@<K@9@T@E@;S@QE@G@7@U@d@@W@c@b\@W f@@Ve@WU@`d@B@2d[@^@f@d d@C e@c]@]L cd@` ac`@`a@ e@`fbU@_@[b@@@U^\@ceR@]V@d^@b@@\@da@@Zd e]@ c@f@`@b@^@*@O@_Y@UFVU@Y@O@J@R@Q@U@Q@P@P@O@Q@R@@R@L@R@S@P@Q@P@P@S@P@@S@@Q@Q@O@T@P@S@Q@I@@T@@W@@Q@T@O@Q@O@R@J@Q@T@V@P@S@@V@X@P@J@S@@P@@R@@R@P@M@L@Q@Q@L@P@Z@W@@P@Q@P@@R@K@Q@L@@P@J@@Q@T@N@P@N@O@Q@@P@X@S@U@R@@T@I@R@P@S@O@T@Q@@U@R@Y@@W@P@S@@R@P@R@@R@O@@Y@@P@L@@V@O@R@U@Q@Q@@U@@S@S@L@@P@N@U@@W@Q@@T@T@Q@W@@T@@R@Q@T@R@S@O@N@@T@O@Q@M@O@Q@T@@U@R@@a@N@S@@Q@N@O@O@@P@@W@R@R@N@K@M@@P@@S@T@@Q@@R@P@@U@N@Q@R@N@U@W@Y@@V@Q@@R@R@R@Q@L@S@P@L@R@P@R@S@W@T@U@@S@P@@W@P@Q@U@O@U@@S@W@T@J@X@R@@V@Q@@T@@R@Q@N@@Q@P@P@M@V@W@M@R@X@S@@V@@T@Q@M@@T@R@I@U@P@@T@L@@T@O@R@U@T@N@P@R@U@P@@S@S@V@S@@V@O@\@N@X@R@X@S@U@T@S@@X@N@W@@Q@W@P@Y@U@U@X@R@@W@@R@U@R@U@Q@@R@Q@O@J@O@@R@S@Q@Q@Q@@Q@T@R@S@K@P@S@@R@Q@R@S@S@N@U@J@S@@R@R@U@Q@@R@R@@P@F@@W@P@T@Y@I@Y@U@Q@P@I@@W@Q@@T@`d@R@S@@V@_@M@P@S@S@P@@R@O@O@R@@P@O@S@U@R@S@R@W@T@@T@@T@Q@S@M@R@R@L@S@\@Q@O@@P@M@@R@@W@W@@W@R@J@@Y@U@Q@P@I@L@R@T@R@@T@P@N@@Q@@V@O@X@V@@Q@S@N@^@V@Z@@U@@P@K@@S@R@@]@@S@W@Q@@U@R@@Q@O@L@U@P@U@T@P@T@M@^@@S@@Q@O@S@Q@P@@S@N@O@T@@T@Q@@V@Y@Q@S@Z@M@Q@Q@Q@T@@T@Q@@P@T@@W@H@R@^@Q@@T@@T@O@O@R@K@@P@Q@O@S@Q@R@O@@R@U@T@R@4@0@4@(@0@4@4@0@4@0@(@0@4@0@4@V@8@8@8@4@8@4@4@R@0@4@0@0@8@0@4@8@8@0@0@0@4@4@4@(@4@8@8@(@<@<@<@4@8@0@8@F@0@4@4@8@4@8@0@8@4@4@4@@@8@4@4@0@4@8@4@<@8@0@4@8@<@0@B@4@4@4@4@4@(@8@0@4@4@F@4@4@4@@@(@4@4@8@B@8@8@0@0@0@0@4@8@0@4@0@0@(@4@0@8@0@4@0@J@H@D@R@H@B@F@F@D@F@B@D@R@W@T@H@H@H@F@L@B@B@F@R@T@T@F@J@R@N@W@T@D@P@F@F@B@J@L@N@J@B@F@Q@R@P@B@R@D@L@R@B@H@N@F@F@S@R@F@B@<@F@D@N@F@N@H@H@N@D@H@H@N@F@Q@B@F@H@H@H@D@N@@@D@a@L@J@_@^@J@S@F@H@@@B@F@N@L@R@H@F@L@D@J@R@F@F@L@L@B@F@F@D@B@D@B@B@H@J@F@J@S@B@D@F@N@P@Q@B@H@Q@B@D@B@J@F@U@P@H@<@U@H@B@F@D@L@D@D@D@J@D@J@P@J@J@D@a@Q@H@T@Q@H@J@H@B@N@H@N@J@L@L@F@F@N@H@D@H@J@D@V@B@F@J@B@B@B@S@^@P@H@R@H@H@D@F@F@4@Q@F@N@N@J@B@D@F@a@L@D@F@@@H@H@D@F@L@H@<@D@F@F@H@D@B@B@D@N@H@B@J@N@D@J@F@P@B@D@Q@H@F@L@D@F@H@H@Y@H@H@B@F@H@J@P@H@D@J@<@H@N@L@B@D@H@F@D@D@F@D@B@F@D@H@D@F@S@S@J@T@F@N@L@F@R@V@H@L@D@N@P@N@L@H@F@J@H@P@B@U@L@D@L@T@H@H@F@L@R@R@B@P@F@J@@@H@D@4@F@B@B@<@@@Q@L@F@F@J@J@N@B@Q@H@D@H@F@D@F@F@P@D@D@D@J@H@H@D@T@4@J@Q@Q@H@P@B@D@S@R@Q@F@N@Q@P@H@L@S@B@J@<@D@`@D@H@H@L@H@H@F@D@B@D@8@J@D@F@W@B@D@c@R@F@D@P@H@D@^@F@H@Q@D@R@D@L@0@F@D@P@L@D@Q@L@L@Q@D@L@F@J@H@L@J@H@P@N@T@_@Q@P@B@N@B@B@F@D@J@T@J@F@F@P@B@D@H@B@D@H@@@H@J@F@D@L@F@F@T@4@D@H@B@D@H@<@L@0@<@8@(@B@B@@@0@F@@@0@B@H@B@B@D@D@F@D@L@@@8@@@8@B@H@F@F@B@@@B@0@D@<@N@B@<@0@F@D@H@B@@@@@V@B@H@8@0@@@@@@@(@(@D@H@@@F@@@(@B@4@B@8@H@(@R@4@F@<@0@H@D@H@(@H@H@<@D@8@L@D@D@B@B@H@@@F@B@D@H@L@@@<@F@4@8@B@H@F@D@B@B@8@@@B@D@8@B@0@8@N@B@<@@@F@F@B@B@@@0@D@<@B@D@F@@@<@@@4@@@8@J@D@@@D@B@4@B@@@B@0@B@H@D@D@B@D@4@D@4@D@D@D@0@B@<@B@D@@@P@J@S@4@H@J@@@@@D@N@P@B@8@H@@@H@8@L@4@<@4@<@B@H@F@<@B@B@@@H@F@8@B@<@F@J@F@J@H@F@F@8@4@@@@@(@B@T@P@D@4@B@H@D@@@B@B@F@L@D@<@8@J@4@H@4@D@<@J@@@D@0@Q@H@Q@<@0@0@F@N@@@4@B@D@0@@@8@B@D@<@8@(@(@0@@@8@8@H@<@4@8@8@4@<@B@4@8@@@<@8@4@8@4@<@@@4@8@8@D@F@4@<@@@D@<@<@@@8@8@4@B@8@<@D@8@4@D@<@4@8@0@<@4@@@B@@@4@F@B@@@<@8@<@B@0@8@4@@@<@8@8@@@B@B@B@8@B@8@<@8@H@@@<@8@F@B@B@8@Y@B@@@N@N@<@<@8@<@0@B@D@D@@@@@<@B@<@B@@@<@8@<@@@4@B@8@<@@@@@4@D@B@<@<@4@D@4@4@8@F@B@F@L@4@<@<@@@4@4@<@<@D@8@<@0@B@8@D@8@D@D@4@4@8@<@8@<@D@<@<@8@B@<@8@<@8@4@8@<@4@B@@@8@8@<@<@F@<@<@<@8@H@B@8@8@F@8@<@H@4@F@F@F@4@<@<@<@D@4@8@F@(@D@8@@@J@<@L@4@8@Q@@@@@<@4@8@8@8@8@<@8@@@8@8@4@8@D@4@B@8@@@8@B@<@H@4@<@<@B@0@4@4@B@<@<@F@8@<@<@<@<@<@4@B@<@B@D@D@8@F@F@<@B@<@4@D@<@8@4@4@8@D@B@8@4@8@4@8@@@@@@@<@8@@@D@8@8@B@H@B@8@4@D@D@H@F@B@8@<@@@4@D@F@D@B@<@L@@@<@4@F@H@4@B@8@F@4@<@4@(@8@D@D@0@4@8@F@8@8@<@<@@@4@F@D@8@8@D@D@8@8@8@8@4@8@<@<@8@D@<@ @<@<@<@B@D@H@8@@@8@B@B@@@F@F@<@8@F@J@<@B@4@F@8@<@D@B@<@0@8@B@@@<@<@@@D@8@D@4@F@Q@B@B@4@@@<@4@J@D@@@<@8@@@4@@@8@B@4@8@<@8@8@@@B@8@8@@@4@@@<@@@<@<@D@B@<@U@<@<@0@8@8@4@8@4@8@B@@@F@8@D@4@4@8@B@4@<@4@<@<@8@D@@@8@???????????????????4@4@<@4@0@S@<@4@4@<@4@(@4@8@8@8@8@8@8@B@B@8@4@4@8@8@8@F@B@8@0@4@4@0@4@8@4@4@0@4@B@0@8@<@D@@@S@0@4@4@<@4@4@4@8@@@4@4@D@4@<@8@4@B@4@0@4@8@<@8@4@B@4@8@8@8@8@8@4@8@4@4@4@4@8@8@@@0@8@D@0@4@4@8@4@(@<@(@4@4@(@<@8@4@0@F@P@L@P@D@F@D@J@F@L@J@D@F@H@R@F@J@H@U@P@F@J@R@H@J@J@H@H@N@D@H@P@D@N@R@J@J@H@<@F@P@J@H@H@<@Q@L@P@F@D@H@H@L@T@R@J@B@W@P@<@F@H@J@T@J@F@F@N@4@R@P@F@L@R@@@T@H@H@U@Q@F@Q@<@W@]@L@U@Z@]@H@P@D@B@D@B@D@H@F@L@@@Q@@@R@S@L@H@N@D@S@H@D@H@F@J@B@R@L@@@L@J@D@F@D@D@H@D@H@N@P@J@F@H@U@J@V@R@H@F@D@T@@@J@U@H@F@F@F@N@F@F@H@@@H@H@T@J@P@]@H@N@H@Q@F@L@H@F@F@B@N@H@B@H@H@N@D@U@F@D@H@V@H@D@N@8@H@S@R@L@W@T@H@N@F@D@L@L@N@U@D@Q@J@F@P@R@F@`@J@N@H@@@H@T@N@D@N@T@H@V@L@H@T@W@H@S@H@J@F@R@Q@L@H@S@L@J@T@B@Y@L@D@H@L@Q@L@D@T@F@B@@@D@J@0@J@F@H@H@F@H@U@L@D@N@J@P@D@L@B@F@H@U@F@Y@F@F@J@L@R@F@S@J@D@N@B@H@F@L@S@D@J@H@H@N@H@Q@Q@V@H@J@N@R@R@H@F@H@F@R@R@T@J@J@R@D@<@H@D@D@Q@Q@D@<@F@H@R@H@H@Q@4@L@F@F@U@Y@Q@H@N@F@H@H@@@H@J@R@J@D@F@J@F@L@F@J@H@L@F@4@P@B@S@H@S@Q@P@R@[@H@J@8@H@B@H@V@H@J@F@H@F@B@B@J@D@F@L@N@J@L@R@D@L@[@0@F@L@L@N@D@Z@H@Q@D@H@Q@D@P@@@P@H@Q@V@R@D@J@H@N@U@S@D@F@F@D@J@J@P@J@D@P@Q@F@D@T@N@@@L@D@<@D@N@D@Q@F@W@L@N@F@H@D@D@L@F@L@D@P@L@4@J@B@8@B@@@@@(@8@4@F@4@<@B@@@<@B@B@<@Q@4@J@B@L@@@D@B@<@@@S@B@H@D@D@D@8@H@<@H@<@B@8@B@D@@@(@F@@@D@<@H@8@D@8@B@N@D@J@@@H@<@J@0@J@D@F@(@0@<@4@F@H@8@8@B@<@D@4@4@D@0@0@0@H@D@D@(@F@D@F@F@D@D@D@F@H@F@@@8@D@4@B@D@@@N@B@8@<@B@B@B@D@<@F@R@@@B@D@L@B@0@0@B@@@<@F@8@B@H@D@@@F@@@D@B@D@(@B@B@H@Q@J@L@(@@@4@D@<@B@F@H@H@0@0@F@@@(@4@L@F@L@B@H@D@U@H@H@8@J@L@F@8@(@(@L@8@4@<@@@8@@@F@D@0@J@@@@@B@B@@@@@(@B@D@D@<@(@B@8@P@@@J@B@P@D@J@<@8@@@0@P@<@B@@@N@4@F@U@J@<@B@B@B@W@(@8@H@B@L@<@L@8@8@L@0@B@D@<@J@S@Q@B@H@@@8@H@@@0@J@D@4@4@8@D@L@B@4@4@<@4@0@(@0@ @4@(@L@<@8@D@B@8@F@8@<@<@@@<@4@<@J@F@<@<@<@<@F@8@<@8@<@@@B@D@<@D@8@<@0@8@D@4@@@<@<@(@<@4@8@F@@@(@@@@@<@<@8@<@<@J@<@@@@@4@B@F@8@8@<@@@<@8@<@B@@@ @D@<@H@D@@@D@D@H@H@@@@@H@8@H@B@N@B@J@P@D@@@B@8@4@<@B@D@<@<@@@B@F@F@F@<@S@@@<@L@@@@@D@<@@@<@4@D@@@B@@@@@8@@@8@8@B@4@F@8@H@8@8@8@H@<@8@<@8@F@8@<@4@B@<@@@@@D@8@4@4@4@@@4@8@8@8@<@D@B@<@@@@@<@8@@@<@@@8@B@L@8@8@@@8@@@8@@@8@@@@@<@@@F@F@0@H@8@F@@@L@@@B@D@D@8@F@D@H@F@4@4@@@8@D@<@<@N@H@B@@@B@@@J@J@8@@@<@@@B@<@@@F@B@<@0@8@8@<@D@B@N@<@@@H@@@8@F@B@F@4@@@<@@@8@8@D@8@4@0@8@H@ @<@<@<@B@8@<@@@<@8@B@<@8@B@@@4@<@8@<@<@<@8@8@@@@@<@8@<@B@<@8@4@8@<@F@8@D@4@@@H@<@B@D@H@8@@@@@B@P@8@@@<@<@D@F@8@H@<@@@<@8@(@8@F@4@<@F@8@0@<@<@4@B@4@D@(@8@F@F@H@F@8@<@D@<@<@<@D@8@@@8@@@4@D@8@<@<@8@@@<@D@<@<@H@8@@@@@<@@@B@8@8@<@@@@@H@H@D@J@<@@@8@<@8@8@4@<@8@<@B@8@@@@@@@8@F@J@F@4@<@H@<@B@L@D@<@<@8@<@D@8@8@D@8@B@D@8@4@F@<@<@@@@@D@F@<@@@@@@@F@@@8@F@<@8@4@<@8@D@<@8@0@8@8@F@D@<@@@B@B@@@<@4@4@B@@@@@@@D@B@?????????????????????????????????J@@@<@4@J@8@@@T@B@8@0@0@4@8@U@V@H@B@T@8@<@8@R@F@N@U@8@V@4@8@<@8@<@8@4@(@@@4@4@F@J@Q@@@S@<@4@@@H@8@8@L@B@B@D@8@0@4@@@W@4@4@0@<@@@0@B@<@@@0@<@4@4@4@T@8@H@8@<@H@Q@<@R@F@H@D@8@T@0@4@4@@@D@F@J@0@8@@@H@B@@@<@8@L@S@8@F@H@<@N@B@B@8@B@4@4@F@@@J@R@L@4@J@<@D@(@@@Q@<@4@D@U@<@<@P@N@<@B@J@@@F@4@8@H@<@B@F@@@4@8@8@0@L@0@B@@@8@@@<@@@4@4@N@L@8@B@8@T@<@@@8@S@D@@@4@(@R@4@@@4@@@8@8@H@D@@@Q@B@N@4@<@4@@@H@@@8@0@4@4@0@D@8@]@J@B@P@F@L@B@R@Z@F@D@P@D@J@F@R@4@J@N@L@<@D@<@R@F@0@<@<@J@L@4@@@4@@@H@V@D@B@B@B@P@4@F@J@L@X@<@F@0@R@P@<@H@F@P@H@D@L@Q@4@<@B@D@8@J@F@R@H@J@S@U@H@P@8@W@W@J@Q@U@@@<@8@F@8@@@<@4@8@8@R@B@P@4@W@@@H@D@D@<@@@L@Q@N@@@P@@@D@@@0@8@B@<@F@D@D@@@J@H@D@Q@J@W@H@B@F@8@Q@8@@@N@D@8@U@F@H@F@R@F@F@L@8@L@P@T@8@R@0@H@0@N@D@4@@@H@F@4@F@D@F@S@N@8@@@S@F@4@H@4@B@R@H@D@D@Q@0@P@J@D@Q@D@H@L@(@4@B@N@R@4@@@L@R@D@@@4@S@F@8@J@J@B@X@<@8@R@<@H@S@N@P@D@H@H@R@Q@T@D@8@H@L@(@<@F@L@N@8@<@8@@@4@@@<@H@J@L@@@4@@@<@J@P@F@T@<@8@<@S@F@T@F@B@P@B@S@4@8@@@F@N@R@B@D@U@8@V@N@W@D@<@N@4@@@B@F@Q@J@F@P@B@0@<@S@D@P@B@P@8@4@Q@D@8@0@F@B@4@J@8@R@W@J@H@4@H@H@J@8@L@N@@@@@H@F@S@R@0@B@(@D@<@V@4@S@D@P@T@N@<@P@@@B@L@J@F@Q@F@H@@@F@<@N@D@F@N@H@8@L@D@B@N@8@J@X@J@N@N@T@Q@Q@8@F@J@D@4@<@F@P@H@D@L@H@D@S@S@L@H@B@H@0@T@H@J@L@<@@@B@N@J@R@@@4@<@J@4@P@D@T@N@R@@@H@0@R@L@8@S@<@N@4@H@<@<@ @D@B@(@R@<@@@J@@@S@Q@P@0@F@8@<@Q@<@8@N@D@H@8@<@B@@@B@4@D@@@L@B@F@0@@@F@R@4@@@R@F@L@<@J@Q@J@0@H@8@0@J@J@8@F@U@F@4@N@@@B@0@D@<@N@D@Q@<@4@R@J@D@J@L@P@N@8@N@4@<@N@L@Q@B@F@H@L@F@Q@F@Z@P@D@D@R@L@0@8@N@U@F@F@H@J@8@P@<@B@<@B@4@@@4@S@L@L@N@(@8@Q@P@4@P@<@(@J@0@J@S@S@F@F@F@P@L@F@P@J@B@4@B@D@0@B@4@J@H@S@H@F@S@F@D@@@<@B@4@D@8@R@D@4@L@L@R@B@D@J@L@H@Q@H@F@P@D@(@8@R@@@(@B@`@P@B@Q@D@(@ @(@@@(@4@L@(@D@N@0@@@J@J@Q@8@N@@@H@D@<@B@H@N@B@B@J@<@H@<@J@8@0@0@4@<@B@8@0@(@0@D@0@N@@@P@F@4@H@ @4@J@L@(@H@0@(@(@<@F@8@<@8@8@N@D@J@L@N@H@8@8@4@<@B@B@J@J@8@<@8@F@ @@@D@@@(@F@0@D@@@ @J@0@H@4@J@0@(@B@D@Q@F@4@J@8@@@P@@@0@J@D@N@@@N@F@B@(@<@<@F@<@<@D@8@ @0@D@F@R@@@B@B@D@J@8@D@F@@@J@@@J@@@P@J@4@0@S@<@D@0@0@<@D@L@J@F@F@L@P@4@F@J@J@0@@@<@D@B@0@F@4@@@4@8@0@4@<@8@<@H@4@8@<@0@L@8@@@D@F@D@B@B@J@0@B@ @0@0@@@8@B@4@<@4@D@L@<@J@L@(@B@ @<@D@@@8@(@8@F@L@ @<@4@H@@@0@(@0@@@@@ @L@J@H@8@B@<@0@D@@N@4@8@F@(@H@L@H@(@4@D@<@ @ @F@F@8@D@ @L@N@0@8@H@8@J@D@(@H@P@8@8@@@F@J@L@@@4@J@<@H@(@H@B@D@Q@B@B@H@(@L@(@Q@ @0@0@@@@@F@4@ @ @0@4@B@0@F@F@(@P@@@8@H@<@(@H@D@4@S@(@4@0@J@<@F@H@B@W@J@L@F@<@0@N@ @0@4@F@D@B@B@<@8@N@U@H@J@8@<@F@Q@F@D@4@U@ @F@4@8@H@(@N@J@J@B@ @<@<@@@D@8@H@F@0@B@F@L@<@@8@D@4@(@N@ @@@8@F@B@D@ @D@0@4@H@D@F@8@4@ @4@@@0@<@8@F@B@F@(@<@4@@@8@ @0@B@H@8@F@L@0@R@(@B@D@F@L@N@J@8@8@<@F@F@4@N@<@D@F@4@B@@@B@<@D@0@0@0@(@D@F@<@4@(@(@H@8@D@0@8@D@B@4@<@8@4@D@(@<@(@@@N@?????????????????????????????????J@H@J@P@H@F@F@H@D@L@D@D@F@L@T@D@J@H@S@Q@H@J@R@S@P@S@H@R@H@@@S@H@Q@R@F@H@H@L@J@S@H@P@S@H@J@D@F@J@J@P@J@H@J@Q@@@F@H@J@S@H@F@H@L@B@L@J@L@R@J@J@J@N@J@N@`@N@[@]@H@R@D@F@D@F@L@B@P@J@U@N@J@N@H@F@D@H@B@R@F@J@J@D@B@D@D@H@N@W@H@H@H@H@H@U@S@U@H@S@D@H@U@D@D@D@D@F@J@B@H@H@L@L@J@U@H@J@H@Q@F@L@H@B@D@D@L@D@J@L@H@Q@F@L@U@H@L@H@P@]@B@S@J@J@D@J@F@V@F@L@Q@H@P@H@F@`@P@J@F@@@H@F@D@L@Q@<@L@H@F@U@F@V@F@L@H@T@R@F@R@L@N@T@D@R@D@J@Q@L@F@S@H@H@B@D@B@R@D@H@F@N@L@B@F@J@H@H@J@D@F@F@H@F@F@D@F@R@P@J@S@H@H@L@F@D@L@@@H@F@P@H@H@P@R@@@H@L@Q@H@H@F@Q@S@T@F@L@R@J@@@H@@@J@D@@@F@H@T@@@F@L@D@Q@L@J@F@F@D@H@H@H@L@D@J@F@H@H@J@D@J@N@H@F@J@F@S@J@P@T@T@H@H@^@F@J@H@F@<@D@H@B@B@F@N@J@P@F@a@P@D@J@N@J@D@[@L@H@D@R@P@8@Q@H@Q@T@F@Q@H@R@F@P@J@H@H@J@P@L@F@S@P@H@R@F@J@D@<@D@R@V@H@N@H@J@D@H@D@@@J@D@H@B@R@H@P@@@<@B@0@@@(@<@B@<@B@@@@@0@4@8@8@(@H@H@4@0@D@B@4@B@B@8@4@F@4@<@<@8@@@<@8@B@@@4@B@B@8@4@D@0@@@F@<@4@<@8@<@F@8@@@N@4@4@0@F@0@8@J@B@<@8@0@@@8@0@F@0@<@B@<@D@D@0@4@B@4@4@(@@@<@F@F@4@4@F@B@B@F@8@B@D@B@H@4@D@<@4@J@D@0@@@4@@@8@4@8@<@B@<@4@B@B@F@<@<@0@D@H@<@F@4@8@<@F@0@D@D@0@B@@@@@@@0@<@4@B@4@@@4@H@8@B@F@H@8@4@8@B@@@<@D@F@F@H@0@0@0@4@D@0@@@B@0@4@<@B@<@8@F@<@0@P@0@S@<@8@@@D@@@H@8@4@N@P@8@8@D@4@@@8@@@D@0@D@4@<@4@<@4@B@8@<@4@B@@@4@B@8@B@(@B@8@4@D@@@B@L@B@0@F@H@4@<@8@8@0@<@0@@@N@4@D@T@H@B@4@B@B@<@<@B@0@H@J@@@4@J@(@F@0@8@(@4@4@J@4@<@@@8@4@B@0@D@0@<@@@J@4@4@B@4@4@F@<@4@8@8@B@<@H@<@H@L@F@J@J@L@H@B@F@D@<@F@B@F@S@@@R@H@B@B@F@Q@H@D@D@Q@F@H@D@P@<@@@H@S@Q@H@N@N@F@P@J@B@D@H@F@Q@Q@D@D@B@B@L@T@P@J@<@Q@H@<@H@B@F@J@F@F@H@B@L@@@D@D@@@B@F@@@@@@@B@H@D@B@H@@@0@F@4@D@H@D@B@@@4@<@<@4@B@0@P@B@@@4@(@H@8@B@@@4@<@4@U@N@@@V@R@L@J@H@L@F@N@H@H@H@F@L@J@R@J@L@Q@J@L@B@ @J@J@J@Q@N@R@ @L@(@L@F@H@P@0@L@D@H@H@L@J@D@ @P@N@0@P@L@Q@P@P@U@N@F@J@J@P@R@ @N@L@0@F@Q@0@(@H@H@L@D@J@N@N@J@F@H@H@P@(@L@J@T@W@H@J@H@J@R@Q@H@J@N@F@L@J@N@L@J@J@F@F@ @J@N@P@H@ @L@Q@ @(@R@S@F@R@F@L@J@0@J@H@L@J@D@Q@(@H@D@L@@H@J@N@L@4@H@Q@ @F@R@J@H@J@D@H@(@F@U@H@L@(@(@J@F@N@L@P@H@J@J@H@H@H@J@J@L@P@H@H@P@D@H@(@N@W@R@T@ @U@L@N@0@L@T@N@J@H@R@P@L@Q@F@N@H@D@J@D@N@H@ @0@(@J@H@H@L@L@(@L@P@F@L@J@L@L@H@P@ @H@Q@L@R@N@@Q@Q@ @(@(@Q@P@L@ @J@L@H@J@N@H@J@(@N@H@0@N@H@ @S@R@F@P@D@H@H@ @J@H@P@L@L@ @J@L@N@H@W@R@J@N@L@H@D@F@ @J@J@N@H@L@F@D@L@H@L@J@J@?????????????????B@J@<@H@(@F@4@<@8@4@8@4@(@4@4@8@0@Q@B@B@4@<@4@4@0@F@T@L@V@P@@@4@4@(@4@4@8@8@4@4@<@<@8@J@F@U@F@8@8@8@8@0@4@4@8@8@<@J@4@4@B@4@8@(@8@B@<@4@N@P@4@J@8@@@4@8@J@8@H@4@4@8@4@8@4@D@(@8@4@8@J@4@0@8@4@4@4@8@8@0@B@@@N@0@0@<@<@<@4@F@@@4@N@4@R@0@@@@@4@@@<@B@<@8@<@8@<@Q@4@F@L@P@4@8@N@4@<@Q@4@@@4@0@0@0@4@8@8@J@4@P@8@4@L@F@B@N@H@<@<@8@D@U@D@B@N@@@8@B@L@S@<@L@<@S@8@Y@Q@P@X@R@F@J@D@B@@@@@L@N@@@<@D@B@R@P@F@L@8@P@R@N@P@B@H@D@P@T@B@<@D@8@D@@@F@B@L@F@D@8@F@8@F@B@J@@@@@<@P@4@J@D@^@N@Q@B@H@H@D@X@R@J@P@H@0@4@P@@@J@N@D@B@J@N@@@@@B@<@B@B@B@D@B@D@L@N@B@8@@@L@V@D@P@F@@@<@B@B@B@<@<@4@T@@@D@P@0@P@4@@@B@H@U@F@<@L@D@B@D@N@4@Q@@@W@4@<@N@L@B@F@L@P@J@N@B@P@B@J@N@@@L@B@H@L@F@8@@@P@_@S@0@Q@D@J@B@8@H@@@L@F@J@N@D@8@F@`@(@4@8@H@(@L@S@R@D@<@@@B@R@@@@@<@8@B@L@L@D@F@J@D@D@Q@0@@@N@F@@@N@D@@@D@H@B@H@H@@@R@H@J@J@N@D@J@8@@@F@H@B@L@4@@@B@(@F@J@@@8@B@@@F@P@R@D@P@D@L@U@D@U@V@S@B@<@B@R@L@@@B@F@@@F@N@B@R@N@B@N@S@J@D@@@F@T@L@4@J@F@J@B@H@8@B@@@B@4@<@N@J@W@D@L@L@V@<@P@B@<@D@N@B@<@<@N@8@@@@@V@<@B@T@J@T@Q@<@P@H@8@Q@@@N@D@D@N@T@D@Q@R@H@<@<@B@]@D@F@H@L@H@Q@Q@8@8@<@F@N@8@F@@@@@b@R@B@F@@@_@D@@@R@B@F@<@J@(@H@F@H@D@Q@F@J@4@@@@@4@W@8@J@D@L@D@S@P@F@P@@@L@@@@@B@T@J@T@H@L@D@S@8@0@H@J@D@J@J@P@B@B@4@W@(@B@P@L@D@<@B@B@D@@@Q@<@B@B@H@B@F@B@4@4@@@0@F@L@L@F@8@@@Q@D@L@@@D@0@D@N@H@8@D@<@4@B@B@N@D@<@J@B@B@<@4@F@F@<@4@(@ @D@8@<@B@8@<@N@4@8@N@8@D@R@B@B@H@H@N@4@D@4@@@@@H@J@B@J@L@0@@@F@H@L@<@N@H@D@<@D@@@@@B@U@B@F@4@N@8@Q@B@<@F@4@H@B@0@L@H@N@H@B@F@D@J@@@<@8@0@@@F@D@0@F@N@<@H@D@4@J@H@<@B@H@H@4@J@D@4@(@@@P@B@L@V@L@8@L@D@F@<@N@Q@@@@@<@J@F@D@P@H@@@D@H@R@J@D@D@N@B@@@H@<@N@N@B@N@L@F@@@@@0@S@L@D@J@@@D@F@J@L@B@F@0@J@4@J@H@8@J@D@D@D@W@H@P@<@8@F@Q@D@<@<@@@F@8@B@8@@@D@B@@@D@@@8@8@F@<@0@0@D@4@<@<@4@4@N@ @(@@@8@0@B@B@L@(@B@8@D@B@4@8@4@4@0@0@0@<@L@0@0@F@8@D@8@@@4@ @B@J@@@4@<@8@D@@@4@4@0@4@D@D@4@F@4@D@B@8@(@D@(@8@8@@@0@(@L@U@ @@@ @W@4@B@4@<@H@8@0@B@0@8@@@ @Q@D@<@@@4@8@4@<@F@B@0@8@(@B@4@D@4@4@B@0@F@D@(@4@F@H@4@N@4@4@(@4@4@H@0@0@@@L@4@F@4@@F@ @0@8@<@<@(@0@8@4@8@B@D@8@@@0@4@(@B@4@D@4@<@<@8@<@F@8@8@8@H@D@(@J@B@@@@@<@(@D@@@H@<@@D@8@B@D@(@H@8@@@8@<@H@0@P@D@W@<@L@(@4@@B@8@0@8@F@0@4@B@0@D@N@@@0@<@8@8@<@@@8@<@B@ @4@B@8@0@8@D@4@4@<@4@<@<@0@J@<@@@H@F@8@F@J@0@<@B@4@D@ @0@0@8@8@<@D@(@4@0@8@D@@@<@8@8@@@F@8@<@F@@@(@(@4@@@@@J@B@D@B@@@@@8@F@F@4@8@F@L@8@4@F@@@@@ @<@8@<@8@<@(@4@4@B@8@0@B@<@4@8@J@@@F@0@F@0@0@8@L@D@J@0@H@(@B@0@4@ @B@@@8@4@B@<@L@L@(@H@(@@@8@8@N@@@8@8@F@L@0@0@0@S@4@4@D@J@8@H@8@B@D@0@<@D@(@8@H@4@L@B@0@B@4@X@4@8@J@4@8@(@@@<@8@B@J@8@<@8@<@H@8@0@ @@@<@4@8@<@D@@@L@8@B@0@D@8@8@4@4@B@J@B@@@4@F@(@ @D@<@B@<@H@(@B@??????????????H@F@8@(@R@@@4@V@T@<@0@4@B@8@<@0@8@4@8@8@4@8@8@@@F@L@4@4@@@8@4@F@B@8@4@8@0@8@4@4@<@4@0@4@0@D@4@@@B@H@F@8@Q@4@4@@@4@4@4@F@4@4@4@H@8@@@4@4@4@8@H@F@N@0@P@U@4@@@8@<@@@D@4@B@P@8@4@T@8@4@0@0@8@0@@@8@B@4@8@Q@0@8@8@8@@@8@8@<@<@0@(@(@@@N@`@N@F@F@J@B@N@U@8@F@T@D@J@H@Q@8@H@L@R@D@J@F@H@B@8@B@Q@B@H@Q@N@H@@@8@B@R@V@H@F@J@F@P@<@D@H@H@L@S@V@H@<@W@U@Q@D@H@J@P@H@F@N@R@4@T@P@D@B@R@B@S@J@L@S@R@D@Q@<@V@X@L@R@X@T@H@<@<@4@F@B@D@J@D@F@@@R@B@P@S@N@L@Q@F@Q@D@D@R@J@L@B@S@D@8@L@F@@@8@B@@@J@B@F@L@L@N@D@H@H@J@W@N@B@H@F@S@<@H@R@H@F@H@F@L@D@F@H@4@H@L@D@L@R@U@B@P@D@Q@L@L@F@@@D@F@L@<@@@H@L@J@4@U@N@D@T@H@H@L@0@B@S@S@J@<@S@0@N@H@D@P@H@J@T@8@<@8@H@P@R@@@U@R@P@D@<@J@U@H@B@L@N@B@W@H@D@V@F@F@U@U@F@D@R@N@H@H@U@L@H@T@B@S@N@L@8@P@P@P@@@N@B@8@H@L@F@@@H@8@S@J@S@<@B@P@L@P@F@P@S@T@F@T@D@D@J@H@R@H@U@B@D@@@<@B@8@N@R@B@B@F@L@D@S@P@0@F@H@N@Q@4@F@B@H@L@J@D@N@Q@H@R@<@D@F@U@P@T@H@@@D@F@4@L@F@ @H@P@F@S@Y@Q@J@X@F@J@H@<@H@P@Q@H@H@F@Q@D@S@8@F@D@N@F@0@X@<@S@F@P@Q@N@B@Y@B@J@@@J@@@H@S@H@J@D@H@8@@@@@N@D@H@N@R@J@L@T@F@L@J@F@P@L@S@P@W@P@R@<@P@P@D@J@B@L@P@J@J@Q@D@H@@@H@V@T@L@F@F@F@@@L@S@J@@@P@P@D@<@H@P@@@Q@B@8@D@L@B@S@F@W@L@L@B@H@@@D@L@8@S@@@P@U@F@D@0@<@(@0@B@@@P@8@<@D@8@N@N@N@B@D@@@B@R@@@J@@@@@F@8@F@@@F@D@8@B@<@L@P@8@D@@@D@P@4@L@F@H@L@@@J@@@F@H@4@F@J@0@D@Q@H@D@B@L@@@D@@@@@@@(@D@(@4@0@J@F@F@D@F@B@B@H@J@F@B@F@F@8@J@F@N@B@<@B@D@D@@@H@[@Q@B@B@N@8@H@4@H@H@@@F@@@<@J@(@D@F@@@@@8@8@H@0@B@J@N@L@0@B@J@D@D@@@(@F@F@@@(@@@L@H@S@J@0@H@Q@<@X@<@F@F@<@B@J@J@D@0@4@0@8@0@0@H@D@D@L@B@@@4@B@4@<@<@D@<@@@<@B@B@J@4@Q@F@@@D@N@@@(@8@R@H@D@4@P@8@B@T@<@@@B@^@J@B@P@B@T@4@D@ @4@L@@@B@F@L@T@0@ @P@F@H@J@0@H@H@D@4@4@L@N@<@8@4@B@0@B@B@@@F@4@(@ @0@4@8@F@@@L@4@8@@@F@8@<@4@@@<@4@J@@@8@<@<@@@ @<@@@@@8@@@4@D@4@(@4@8@4@D@4@D@<@0@(@@@8@D@F@8@@@<@<@0@<@<@<@J@8@@@@@0@J@@@@@4@<@@@F@<@<@B@@@ @F@<@F@8@@@D@B@8@F@<@@@F@4@D@B@J@@@N@N@B@@@Q@(@ @<@B@D@@@4@8@D@H@D@D@B@L@D@4@L@<@<@D@4@B@<@8@F@8@8@@@<@0@(@4@0@8@8@F@4@H@<@4@8@B@<@<@D@F@<@8@8@J@B@8@@@@@D@8@4@B@4@@@ @8@B@4@<@<@D@0@B@<@@@D@@@8@8@<@@@J@@@0@@@B@<@(@@@8@<@@@@@<@J@J@J@8@T@@@0@@@ @F@<@8@F@B@H@F@(@(@S@8@D@<@ @F@B@D@<@@@B@F@N@4@<@@@8@F@<@<@D@P@8@4@@@@@8@F@8@N@<@B@J@4@<@F@<@F@@@<@H@@@8@8@F@4@(@B@Q@<@0@@@8@B@<@8@(@8@F@<@<@8@B@0@@@8@4@4@8@B@@@<@<@8@<@(@0@0@4@(@F@<@H@<@H@8@(@F@J@J@F@@@H@N@ @<@0@<@D@<@H@8@<@@@J@0@4@H@8@<@D@B@0@4@<@R@0@0@@<@H@4@D@F@B@B@B@<@@@<@D@4@F@4@@@8@B@D@<@<@ @<@8@4@B@D@B@(@@@<@H@@@D@0@J@4@@@(@H@H@D@J@8@@@<@@@(@4@4@@@8@4@B@8@D@<@<@<@H@8@4@@@H@@@D@L@4@@@0@0@8@D@(@8@4@8@8@B@F@0@F@0@<@8@@@D@H@<@8@0@@@D@@@4@H@8@8@D@<@8@D@@@8@(@<@D@@@4@<@@@B@@@4@<@0@4@B@ @@@0@B@B@????????????????T@L@V@N@R@N@S@S@L@L@L@R@H@N@P@H@N@R@H@P@P@a@_@[@V@Q@S@U@N@J@H@H@J@H@B@H@N@P@L@L@P@S@\@L@Q@R@S@N@L@P@P@P@T@L@D@J@V@N@P@S@@@L@H@@@J@F@D@L@N@R@B@T@Q@P@J@S@Q@Q@^@B@R@N@Q@N@F@S@J@N@F@8@8@B@4@4@4@8@8@4@8@F@<@4@0@4@8@@@@@<@8@<@8@4@8@4@<@<@<@8@8@@@B@8@4@4@0@<@<@B@@@@@4@<@<@8@8@4@<@@@8@4@4@8@@@<@<@<@0@8@@@@@8@8@4@F@F@B@8@<@8@H@B@<@8@<@@@8@4@8@@@8@D@4@@@8@8@@@<@B@<@<@8@(@8@<@8@D@<@8@0@B@4@4@8@D@@@<@8@@@B@@@@@@@@@8@<@@@4@8@B@<@4@<@B@@@<@0@D@@@@@4@0@8@@@B@<@<@8@@@4@0@@@<@0@8@<@B@@@H@8@D@8@<@D@8@@@8@8@@@D@8@<@8@8@<@8@8@@@4@P@@@8@<@F@@@4@<@@@D@<@<@B@8@@@B@<@8@4@B@4@@@D@8@@@4@0@B@<@8@4@8@8@@@@@8@(@@@4@@@4@<@@@<@B@<@@@<@<@4@B@<@@@8@4@4@8@@@4@@@<@<@<@4@8@@@4@B@@@\@@@B@k@<@B@H@8@8@8@B@4@0@@@<@4@0@B@U@8@8@R@<@B@<@F@B@8@<@8@<@@@D@<@<@@@B@4@8@<@<@<@8@8@D@<@4@<@B@4@@@@@F@<@<@8@(@@@8@@@8@@@B@4@D@8@0@<@D@8@8@B@<@<@@@4@4@8@8@@@4@<@B@8@@@B@Q@8@8@B@H@<@(@4@<@4@8@H@4@B@<@B@<@H@<@8@0@8@8@4@@@8@<@4@4@@@<@4@<@8@<@4@<@8@<@@@8@B@4@@@4@B@4@<@<@J@B@N@L@F@L@B@P@F@B@F@H@F@H@Q@F@D@N@H@H@D@J@H@J@L@Q@@@H@N@L@D@J@L@N@L@D@J@B@L@N@J@L@N@N@N@(@H@F@H@L@N@F@J@J@S@N@H@L@8@N@L@Q@N@<@F@P@P@J@8@H@@@P@N@H@L@B@N@J@D@B@P@P@H@L@L@L@H@N@Q@N@L@H@L@L@H@J@H@H@N@R@F@Q@J@D@H@N@J@J@J@F@D@B@P@F@<@D@J@J@H@R@J@L@S@B@L@Q@P@F@N@J@H@H@L@N@N@N@F@J@L@@@L@J@N@H@F@N@H@P@Q@N@[@D@L@N@@@L@H@P@Q@J@L@N@L@F@P@J@H@B@P@H@Q@J@H@J@N@@@P@P@@@H@N@F@B@L@B@N@H@L@N@D@N@J@R@J@N@N@J@F@@@J@N@R@P@F@H@L@L@J@P@L@J@D@J@P@N@F@N@N@F@N@L@R@F@N@Q@F@L@P@F@L@P@P@N@F@Q@N@L@B@D@Q@N@J@L@N@Q@R@J@P@P@Q@@@F@D@8@L@J@D@F@N@B@F@L@H@H@H@R@T@N@<@L@D@D@L@D@H@<@L@D@F@H@L@L@F@N@J@H@Q@N@L@@@N@Q@L@N@L@P@[@N@D@P@V@H@8@P@H@J@P@P@]@N@L@F@J@F@J@F@P@L@F@0@L@P@H@J@D@L@H@L@H@Q@H@F@J@N@F@N@J@W@L@F@B@P@4@8@J@@@N@J@H@N@D@F@N@L@H@4@J@N@N@0@J@D@S@@@<@4@@@@@B@D@D@D@N@F@<@<@B@F@D@4@(@B@8@4@<@@@B@8@F@0@<@@@R@B@B@B@<@B@F@B@J@Q@@@(@@@T@J@L@F@F@(@(@<@0@Y@8@(@ @(@Q@(@(@R@<@4@(@R@P@0@4@4@0@(@(@R@ @0@(@0@0@R@0@0@4@4@(@(@(@@0@4@8@4@4@(@4@0@0@0@H@0@4@(@(@(@0@4@0@4@0@ @0@4@4@(@(@ @<@<@8@0@4@P@<@8@0@(@0@4@(@(@(@0@(@8@(@4@(@0@4@0@8@0@4@(@@(@4@ @<@0@(@ @8@ @(@0@8@0@(@0@H@8@4@4@U@4@0@0@4@(@(@8@0@ @0@8@8@S@ @8@4@0@ @@0@4@8@0@4@(@4@ @ @4@0@ @(@0@8@4@<@(@<@(@0@4@(@4@(@(@4@<@0@0@(@(@0@(@(@4@@J@4@T@0@4@4@ @0@4@8@0@4@4@(@4@<@0@(@(@8@(@4@8@0@4@(@ @4@4@0@(@Q@0@4@4@0@@4@(@8@ @0@4@0@8@0@4@0@0@(@4@0@4@(@(@(@R@4@ @4@0@0@(@(@0@0@ @8@4@W@4@8@]@R@8@<@0@ @ @8@(@ @4@0@(@ @V@Q@(@(@H@0@8@R@@@8@S@0@0@0@4@8@0@0@4@4@(@D@S@0@0@S@N@8@P@(@0@0@ @4@4@<@0@0@(@D@4@0@S@(@4@8@(@4@(@ @4@8@0@ @4@U@4@0@T@(@0@(@4@(@0@8@0@4@4@N@(@(@4@V@0@@(@4@N@0@@@(@8@(@8@0@J@0@(@Q@(@0@(@4@(@X@(@J@V@4@ @4@ @4@ @0@(@0@4@(@Q@(@4@(@J@L@0@0@?????????????????????????????????4@4@N@R@0@H@L@N@S@R@`@^@N@H@N@4@N@U@P@V@V@P@R@Q@T@X@Q@U@U@U@J@N@N@R@<@U@P@T@\@R@B@R@F@D@@@D@D@D@@@8@8@8@B@0@B@B@H@B@<@4@8@F@B@D@D@@@<@4@@@B@B@(@8@@@8@B@0@F@D@B@4@@@<@B@D@B@B@<@8@<@4@D@F@F@D@8@<@@@D@@@J@B@B@8@B@<@<@B@B@@@D@@@<@@@B@F@@@4@8@F@<@H@F@H@8@0@@@4@D@@@J@D@D@(@H@D@B@<@<@F@D@@@D@J@B@@@@@<@@@B@F@D@@@F@B@<@4@@@F@J@8@H@@@B@<@F@@@<@B@F@B@D@B@@@F@D@B@@@D@B@B@D@<@@@@@4@@@<@@@D@@@<@B@@@F@B@F@4@<@<@D@F@B@B@@@J@<@@@<@B@D@8@<@B@H@B@4@J@<@F@@@8@8@N@<@F@@@<@B@B@D@D@B@B@@@@@D@4@B@<@D@B@B@@@L@D@8@D@D@D@F@F@B@8@@@F@D@J@<@F@J@D@B@D@D@H@@@<@H@B@@@8@@@4@@@B@D@F@B@B@D@8@8@B@F@D@B@0@B@D@F@F@H@B@F@D@D@@@F@B@@@B@D@H@D@D@F@@@<@D@<@J@F@B@a@8@H@N@k@D@L@J@@@8@D@B@@@@@@@8@<@F@B@<@8@N@F@H@@@0@B@L@@@B@H@4@F@F@D@F@D@D@N@8@8@@@B@<@<@4@F@@@D@8@D@H@D@8@D@8@F@@@B@<@D@F@8@H@F@N@F@8@H@@@8@<@<@D@B@F@@@D@@@<@D@J@<@8@8@@@L@F@@@B@H@<@D@F@R@@@F@B@B@@@H@H@8@<@D@<@@@J@<@F@F@4@B@@@D@@@B@D@@@B@<@B@8@D@8@D@@@@@8@F@F@B@8@B@F@@@B@D@F@<@<@F@0@@@U@B@F@J@J@<@F@J@N@L@H@H@F@L@H@@@8@B@F@H@P@8@H@B@<@F@F@P@H@F@H@D@F@D@F@H@F@P@H@J@<@@@J@J@B@F@H@J@P@L@B@D@J@D@J@H@J@L@J@L@8@F@B@H@B@L@L@B@F@H@N@L@J@J@H@L@J@J@N@H@N@H@F@L@W@R@J@J@F@@@D@<@F@R@F@D@H@H@P@B@H@L@F@J@B@B@F@J@L@F@L@L@J@F@<@F@H@F@B@4@F@D@D@F@F@L@@@N@D@F@D@D@H@@@F@D@L@J@N@F@B@J@J@L@L@D@H@L@H@H@L@F@F@H@N@B@D@P@D@F@J@H@P@J@L@8@P@J@H@J@8@H@L@J@@@L@F@F@L@P@N@[@N@<@P@H@B@P@L@H@J@H@J@J@F@D@J@Y@J@D@J@F@H@N@F@B@H@J@H@@@L@B@<@N@L@@@@@D@B@F@B@B@J@N@B@F@P@F@H@J@F@L@@@8@H@H@J@F@D@N@F@L@F@H@J@H@H@N@D@H@F@F@@@L@H@H@H@D@P@@@L@H@<@F@J@4@H@N@J@J@F@F@N@N@J@F@4@<@P@L@J@H@L@J@J@L@P@L@U@(@L@@@B@D@D@N@D@H@H@@@J@8@F@@@H@H@J@P@H@4@F@@@J@D@J@J@J@B@J@4@F@H@@@D@B@L@N@F@P@H@H@N@J@J@P@J@D@L@L@L@N@Y@L@P@D@J@Q@D@D@8@D@H@B@L@J@L@J@Z@N@F@N@F@D@F@D@F@H@J@L@F@F@@@L@D@F@L@F@F@J@@@H@J@H@D@B@J@L@H@J@J@T@F@H@N@P@D@<@J@4@H@@@D@N@D@P@H@F@4@@@F@P@H@L@F@<@F@D@N@4@8@4@4@4@F@F@4@B@4@4@ @B@8@<@4@<@D@@@0@0@(@<@F@H@4@8@R@H@<@@@0@@@<@4@X@4@<@D@0@0@(@4@<@8@8@@@4@4@S@0@4@8@8@8@4@0@(@4@8@4@4@(@4@(@4@ @<@<@8@L@4@0@8@8@8@8@0@(@0@(@8@<@8@<@(@4@8@8@4@@@8@8@0@4@0@0@4@8@0@<@4@0@4@4@8@4@(@0@8@0@@@<@@@(@ @4@(@8@T@B@8@8@@@@8@8@0@0@<@8@4@8@B@4@4@4@0@4@4@<@8@4@@@8@4@(@4@<@D@0@<@4@8@0@<@4@0@8@<@4@<@<@B@@@8@8@4@<@8@4@8@0@4@4@R@4@0@4@<@4@0@8@4@@@4@@@(@0@0@8@@@4@8@8@B@0@4@0@8@4@(@0@8@8@8@ @@@0@@@0@0@0@D@0@8@4@0@4@<@8@4@4@4@4@4@4@(@8@0@8@8@8@4@D@8@(@<@4@<@<@8@8@(@4@8@<@B@0@@@B@8@8@<@8@<@4@4@@@4@4@0@4@R@4@8@8@<@4@8@<@S@0@4@<@<@8@ @4@<@8@<@B@4@@@8@8@4@<@8@4@4@<@<@8@0@8@4@0@8@0@D@<@8@B@(@<@F@T@8@D@B@4@(@8@8@0@(@4@(@0@@@4@0@(@<@@@@@4@ @8@D@4@4@<@Q@8@@@<@@@8@8@<@(@P@4@8@0@4@(@<@0@8@0@8@<@8@(@<@(@@@4@8@4@J@@@0@<@@@H@@@(@@@4@(@0@0@<@4@@@4@8@T@4@8@B@4@(@(@0@<@<@4@4@@@4@<@@@L@U@@@4@8@4@R@<@0@4@<@0@4@B@0@@@@@(@8@4@<@4@4@0@4@8@0@8@0@8@(@8@4@4@L@B@@@8@0@4@@@4@8@8@<@4@0@<@ @4@N@4@8@??????????????????????????????8@8@4@0@T@T@4@0@8@0@U@P@U@W@P@T@X@S@Q@U@T@V@4@T@V@V@S@@@N@H@F@H@J@H@@@B@B@<@D@<@D@F@J@J@H@8@@@D@J@H@D@F@D@D@@@B@P@H@F@B@B@B@F@D@B@H@J@N@J@H@D@N@J@F@B@B@F@D@@@@@D@J@S@H@F@B@D@F@D@D@F@H@J@F@B@D@J@H@D@L@H@D@H@D@J@J@B@D@J@J@B@L@@@4@N@N@4@@@B@F@@@D@B@J@H@H@<@H@H@V@@@B@L@F@H@B@J@D@B@D@B@B@D@F@N@D@H@H@@@@@@@F@N@@@J@@@N@B@H@F@J@B@D@D@F@D@F@J@D@J@J@H@F@F@J@F@J@H@F@H@4@J@F@F@H@H@B@N@D@F@H@L@H@@@@@D@F@H@F@D@L@D@D@F@H@D@H@F@L@D@D@8@N@B@<@<@@@0@D@N@B@J@F@B@H@D@J@F@J@F@H@L@F@<@D@L@D@B@F@D@J@H@@@F@J@F@H@F@D@@@H@J@F@L@D@J@N@H@F@F@F@J@D@D@L@D@F@D@4@D@<@D@D@N@F@D@D@D@@@H@D@J@F@D@D@F@D@H@J@L@H@F@F@H@D@Q@F@D@J@H@J@H@H@F@H@B@H@B@J@L@F@V@H@J@L@`@L@L@L@D@D@F@F@F@L@B@@@B@F@B@B@@@@@H@L@@@8@F@F@L@H@H@J@J@D@D@F@J@H@N@D@B@H@L@<@J@@@F@J@J@D@J@H@4@F@B@F@F@L@B@B@J@<@F@H@@@J@H@Q@F@J@J@B@<@B@H@H@J@D@F@D@J@F@D@J@J@B@B@@@D@S@H@B@F@L@<@B@4@H@N@D@F@F@D@B@J@D@H@P@B@F@N@H@F@L@@@H@F@8@D@B@D@J@L@F@<@H@B@H@@@P@B@H@@@J@J@H@@@F@L@B@N@H@F@D@@@R@<@L@F@F@H@H@H@4@D@H@N@H@F@F@H@J@H@F@B@4@4@J@F@J@<@@@F@<@P@D@L@H@B@J@J@D@B@@@@@D@@@J@F@<@<@@@F@D@B@@@F@F@D@H@H@D@F@H@F@@@H@J@F@F@B@<@B@@@@@H@H@4@@@F@8@L@F@D@D@@@L@L@F@B@F@L@F@J@Z@F@H@\@U@N@B@D@D@L@@@F@B@Q@F@D@N@J@8@H@D@F@H@D@<@D@H@J@8@N@B@J@H@@@F@D@B@B@8@@@4@F@@@F@@@<@H@B@J@D@N@D@F@<@<@H@<@H@H@J@D@D@F@F@H@H@J@@@F@F@F@S@F@@@D@P@B@@@@@D@D@H@H@N@J@H@B@N@F@B@D@D@<@H@B@F@J@D@@@L@P@Q@[@L@F@<@N@B@@@J@D@F@F@F@@@L@<@4@L@X@J@D@F@F@F@L@F@@@D@L@B@D@H@<@D@H@F@4@8@@@@@D@<@J@F@L@@@B@H@D@J@J@F@H@L@8@F@4@J@<@L@B@D@F@H@D@F@F@H@F@D@L@F@F@D@F@0@J@F@H@J@4@L@@@D@B@B@@@H@F@B@J@J@H@H@D@D@<@@@L@H@ @<@B@N@H@D@F@H@D@D@H@H@H@W@T@H@8@L@D@@@N@B@D@F@N@B@B@F@B@F@D@H@J@D@<@@@B@D@B@F@F@L@F@B@D@@@D@@@H@H@4@@@L@@@J@L@F@J@N@F@H@N@J@B@L@8@J@L@V@D@F@F@<@H@P@<@B@ @H@D@D@J@F@H@D@Y@L@F@J@B@D@H@Z@@@J@J@J@F@B@N@0@B@4@4@J@F@L@F@D@D@F@B@B@N@D@H@J@H@@@H@U@@@D@U@N@0@X@D@D@F@<@0@L@B@N@H@8@<@H@@@L@4@J@8@Q@D@B@L@8@4@S@4@H@8@B@@@@@@@@@@@F@8@8@0@8@D@8@<@B@@@4@(@0@<@@@@@8@<@8@8@4@8@D@@@<@8@4@8@0@<@4@@@@@B@L@<@8@F@@@<@8@8@8@8@4@B@<@B@8@<@<@4@<@<@8@8@8@@@8@<@4@<@B@8@8@D@@@4@8@8@B@B@8@<@B@B@8@D@0@(@D@F@(@0@4@<@0@8@8@@@8@@@0@@@@@@@4@8@@@<@8@8@@@8@4@<@8@8@8@@@D@4@B@@@4@4@4@<@D@B@<@4@D@8@<@@@B@8@8@8@@@8@@@B@8@B@B@@@<@<@@@@@B@@@8@@@S@@@<@<@B@@@8@B@8@@@<@B@D@4@4@<@@@@@<@8@D@<@8@<@<@D@<@<@F@<@<@(@D@4@D@0@0@ @8@F@4@D@<@4@F@@@B@<@<@@@<@B@<@0@8@D@<@8@@@8@B@@@4@<@B@<@@@<@<@4@@@B@<@@@8@B@D@@@<@<@<@@@8@<@D@8@8@8@(@8@0@8@<@8@<@8@8@8@8@0@<@@@<@8@<@8@<@<@B@D@<@<@<@@@8@H@<@8@@@@@B@@@B@<@@@4@B@4@B@B@<@@@4@D@D@V@D@F@F@8@8@<@@@<@D@8@0@8@@@8@8@4@0@B@D@8@0@<@<@F@@@<@B@@@<@F@@@B@@@F@4@8@<@B@0@<@4@<@<@<@8@@@@@(@<@8@<@@@F@(@8@@@4@<@@@4@@@@@J@<@D@B@8@0@4@@@B@@@8@@@4@@@8@<@B@B@8@4@4@4@H@@@8@<@B@0@8@(@B@F@<@<@@@8@J@<@8@<@F@8@<@F@@@@@D@4@@@@@0@<@8@8@D@<@@@4@8@4@B@0@H@8@8@4@D@B@@@4@<@D@8@B@@@<@8@0@L@0@B@@@<@@@????????????????????????????8@4@8@4@U@4@0@8@4@0@4@8@0@<@4@8@4@4@4@8@4@T@L@T@B@8@(@0@0@S@4@4@8@8@8@(@V@0@0@(@F@(@8@0@0@0@4@V@4@<@8@J@H@F@L@H@J@D@F@F@L@@@B@H@N@H@H@L@H@F@N@H@F@H@F@H@B@L@H@F@F@H@H@D@F@B@F@J@H@J@N@J@D@F@N@F@@@B@H@F@H@D@F@F@N@H@H@H@F@H@H@@@H@F@L@J@F@D@F@N@B@J@F@H@J@L@B@L@F@F@J@D@H@H@J@L@H@F@<@U@J@H@D@D@F@J@H@F@F@H@H@W@@@N@L@H@B@H@F@J@F@B@D@H@H@B@D@J@D@J@H@B@F@F@F@H@@@H@D@L@F@J@F@F@F@H@H@F@H@D@H@D@F@J@H@F@B@J@D@F@H@N@F@N@J@F@J@F@P@D@F@F@B@L@J@J@B@H@B@H@J@F@H@D@F@D@J@F@D@J@H@H@@@H@@@L@F@B@P@F@B@F@N@J@F@H@N@H@H@F@H@H@D@J@J@H@D@N@L@F@D@F@F@J@D@F@H@F@F@L@H@F@<@H@P@F@F@F@P@D@J@L@H@F@F@H@J@H@F@J@D@L@F@U@B@8@F@F@J@0@F@F@D@F@D@F@J@D@N@J@H@F@L@F@F@J@J@H@F@D@H@F@L@H@J@L@H@J@H@F@F@B@H@H@B@F@J@H@F@J@N@H@H@U@H@H@H@D@H@F@J@<@F@F@B@N@F@D@B@H@D@B@F@F@D@@@H@F@L@F@J@H@D@L@B@B@F@H@F@D@F@H@H@N@B@H@F@D@H@P@D@F@H@B@H@S@H@H@J@0@F@D@H@D@H@J@B@H@F@L@J@F@H@F@F@F@H@D@F@F@F@F@J@S@F@8@H@H@H@H@F@D@V@H@B@H@L@N@B@@@F@R@D@D@H@B@B@F@H@H@H@N@D@F@J@H@L@H@L@F@H@D@D@L@H@H@N@D@@@F@H@F@H@P@J@H@D@<@F@J@F@D@H@F@L@Q@H@J@F@H@J@@@P@J@J@H@J@F@V@H@J@D@F@H@@@D@L@L@J@(@(@8@<@D@J@4@<@L@8@H@D@Q@8@J@L@Q@B@D@B@D@F@8@N@H@8@8@8@D@B@4@H@F@F@D@B@F@F@D@H@(@H@@@D@L@L@F@B@D@D@B@0@F@L@8@@@D@8@L@8@H@8@D@J@L@H@F@H@J@J@D@W@D@F@^@N@J@F@D@F@N@B@D@D@N@F@F@N@4@4@L@B@D@F@F@F@<@B@B@J@4@N@L@H@H@B@F@D@B@F@<@B@D@B@J@D@D@B@L@F@H@D@Q@D@F@8@@@N@8@F@F@H@F@H@F@B@D@H@0@4@J@B@N@H@D@H@Q@8@<@B@B@8@J@F@P@Q@D@D@D@H@@@B@F@D@D@B@J@F@B@D@J@Q@L@Z@D@B@D@@@B@D@B@D@L@L@F@8@J@4@4@N@S@B@D@L@D@H@L@J@@@D@N@D@D@F@F@H@D@B@J@0@<@(@D@8@N@8@J@@@@@L@L@H@J@H@L@D@F@8@J@D@L@J@L@B@H@D@F@B@F@D@H@F@F@F@L@L@H@D@(@J@F@J@H@4@F@4@D@<@H@B@H@Q@H@B@H@D@L@L@@@D@N@8@F@L@L@B@D@R@L@D@J@<@L@B@D@F@<@L@X@T@H@4@T@F@F@N@8@B@J@N@D@D@J@<@@@F@F@F@H@D@@@H@4@D@@@B@D@J@J@B@@@8@D@B@N@H@@@L@F@Q@H@J@D@D@B@L@4@P@H@J@D@N@H@Y@F@D@H@D@F@P@D@D@J@4@D@F@F@D@H@B@[@N@H@F@D@ @N@X@@@D@J@N@J@B@F@_@0@B@D@B@N@F@J@F@8@B@@@@@B@L@H@4@H@J@D@N@V@8@8@T@J@4@R@D@D@N@B@B@4@H@F@L@H@4@D@B@8@J@B@H@4@Q@D@D@4@ @0@ @8@0@(@4@4@0@ @(@8@0@0@D@0@B@@@<@B@@@B@<@<@@@D@D@8@@@B@8@<@D@@@@@B@@@@@@@<@<@8@B@<@<@<@@@@@8@<@8@<@B@@@@@B@B@<@F@B@<@4@8@@@8@@@8@<@<@N@<@<@B@@@@@@@4@<@<@B@@@<@<@8@B@8@@@<@@@@@D@B@B@@@<@B@F@@@@@B@D@F@8@4@4@B@@@8@8@8@@@@@<@<@@@@@J@4@B@@@@@4@@@@@@@<@D@8@@@@@B@8@@@8@@@@@8@@@@@<@@@B@H@8@D@<@B@<@F@<@@@@@<@@@<@@@8@<@B@@@<@H@@@8@<@@@F@<@B@<@<@@@<@<@<@F@<@F@B@@@B@4@@@8@@@B@<@@@D@<@<@@@<@F@B@<@@@D@@@4@B@<@F@H@<@0@@@F@@@<@@@D@H@@@@@@@8@<@@@@@@@0@D@B@<@<@@@<@@@<@<@@@<@<@B@@@@@H@@@F@<@B@<@F@B@@@B@@@8@<@@@@@@@8@@@8@D@<@H@8@(@<@@@B@ @@@@@8@<@F@<@<@<@D@@@<@<@B@<@<@B@@@<@<@<@@@<@B@<@<@D@<@B@<@<@<@D@<@@@F@<@B@H@H@8@D@D@@@8@@@B@<@8@<@<@B@0@D@<@8@F@<@<@8@<@B@@@<@<@8@4@B@@@D@<@@@@@N@B@@@F@<@@@<@D@<@<@@@D@B@B@<@<@D@H@8@F@@@8@@@@@@@@@B@ @<@@@<@8@<@@@8@<@<@B@D@H@@@<@8@<@@@8@<@<@<@<@@@<@<@D@@@@@@@<@@@F@F@<@4@B@@@B@8@0@8@H@@@<@@@4@8@8@<@@@<@B@8@<@B@<@D@@@@@8@@@<@8@D@@@B@D@<@4@@@@@@@@@F@B@@@8@4@@@@@<@<@<@<@D@H@@@@@<@@@B@4@F@D@B@@@??????????????????4@8@0@0@0@4@8@0@@@0@(@(@(@4@4@V@(@0@<@4@<@4@<@4@8@0@0@4@8@4@<@4@4@8@(@0@4@4@8@<@0@8@4@4@4@0@0@T@0@<@4@8@(@4@0@(@0@8@0@B@4@0@4@<@8@8@8@8@<@0@8@0@4@4@0@B@4@8@4@0@4@0@4@0@4@8@T@8@4@4@0@<@8@0@0@<@4@0@8@0@0@J@H@F@N@J@F@D@H@F@J@B@D@F@J@F@H@J@H@H@P@D@D@H@H@J@F@L@H@J@F@J@F@D@D@B@H@J@H@J@Q@H@B@F@H@H@<@B@H@H@H@D@D@D@J@F@N@H@F@H@F@8@F@H@J@H@D@F@D@L@D@L@H@D@J@L@B@J@F@H@J@F@H@B@J@W@H@F@X@^@J@D@F@D@D@J@D@H@H@H@F@R@@@J@L@H@P@F@F@L@H@@@D@D@D@B@D@H@D@H@J@D@F@H@H@H@@@H@H@L@D@J@H@D@F@F@H@J@F@D@J@F@F@H@F@F@D@F@D@D@D@J@F@J@J@F@H@H@S@F@H@H@F@N@H@J@B@D@B@F@F@F@H@D@H@F@L@F@F@J@J@H@B@F@B@F@F@@@Q@[@F@F@N@H@D@H@J@H@L@H@F@L@F@H@<@J@F@a@J@J@B@B@F@H@F@D@H@B@H@H@F@H@F@D@D@H@L@F@F@H@P@D@P@J@L@D@H@H@H@H@L@H@F@L@F@U@D@B@B@B@F@D@N@H@H@D@4@D@L@N@D@J@H@F@F@L@D@H@F@H@H@D@F@D@H@L@J@J@J@H@H@F@F@D@F@J@J@F@F@J@L@H@J@N@L@J@@@H@H@S@B@H@<@F@D@F@Q@F@F@L@F@@@B@H@D@8@D@D@H@@@H@H@J@D@F@H@D@J@@@F@D@F@F@D@F@H@H@J@B@F@H@D@H@H@F@F@H@F@F@P@J@B@H@F@H@J@J@R@J@F@D@F@H@J@F@F@F@B@]@H@H@D@B@F@F@F@J@B@F@H@J@F@H@L@H@F@^@J@D@F@L@J@B@D@D@R@F@F@H@@@F@@@F@F@J@J@D@B@D@H@D@D@N@B@H@J@B@D@H@J@J@F@D@H@J@B@J@N@@@H@D@@@D@J@D@F@H@F@D@Q@F@J@F@D@J@D@J@H@H@J@H@D@S@H@F@D@F@F@<@B@L@J@J@(@(@B@B@B@J@8@<@L@B@F@B@Q@D@L@H@Q@B@F@D@D@H@@@L@F@4@B@H@B@4@H@<@D@B@@@H@D@J@N@F@B@J@J@B@@@D@B@B@(@D@J@8@@@@@J@4@H@B@D@H@H@F@F@F@H@B@4@B@J@S@P@H@B@F@L@B@J@B@P@D@D@N@B@L@B@D@D@B@D@@@<@H@4@R@L@F@H@@@L@B@H@D@B@@@D@@@H@D@B@@@L@F@F@B@L@B@D@@@P@J@0@N@F@F@F@F@D@B@H@F@4@4@H@B@L@J@D@F@N@B@<@@@J@D@H@N@N@Q@D@D@@@D@<@@@D@D@B@F@F@H@@@@@H@Q@H@L@B@@@D@<@<@B@D@D@<@H@F@H@0@F@4@P@B@D@L@D@L@F@H@8@<@F@L@@@D@F@F@F@B@<@0@H@ @@@0@4@J@4@B@4@J@H@H@H@F@J@B@H@F@L@B@L@P@N@<@F@8@D@F@D@B@F@D@F@F@L@J@H@D@(@H@D@J@F@@@D@0@D@<@L@B@H@P@J@@@D@J@L@D@Q@8@S@<@D@L@D@H@L@<@L@D@B@B@J@X@R@F@B@F@B@D@J@4@H@H@N@D@D@J@H@@@F@B@L@D@H@8@F@0@B@@@F@J@L@@@B@<@0@D@<@H@F@<@J@D@L@N@B@D@H@H@Q@N@@@Q@B@L@H@F@D@F@D@F@N@D@D@S@L@D@F@D@J@@@L@@@S@J@F@D@D@L@X@8@B@N@L@H@@@F@L@(@0@B@L@F@H@F@B@R@<@F@@@N@H@^@F@H@D@L@T@4@R@V@F@4@R@B@F@L@H@B@0@D@D@J@H@4@L@D@4@J@B@D@@@N@D@D@N@0@0@4@0@@@<@<@B@<@@@8@<@<@@@D@<@@@J@B@<@@@<@<@D@D@8@<@<@@@8@@@8@@@<@@@<@8@8@B@<@@@<@@@D@@@4@D@H@@@0@4@<@<@@@4@8@B@L@D@@@@@<@<@B@8@8@<@@@<@4@<@@@B@8@@@<@D@B@B@B@@@B@@@<@D@<@F@@@`@H@F@Q@R@B@<@<@8@8@@@D@<@@@<@8@H@4@<@<@@@D@8@F@@@<@D@8@8@B@B@8@<@8@<@@@8@@@@@<@<@B@J@@@D@<@@@<@F@<@8@<@<@8@8@@@<@8@<@<@8@F@8@4@D@4@@@<@<@@@8@<@<@@@<@F@@@H@D@B@@@4@8@B@<@<@<@@@D@4@8@@@8@F@B@<@<@D@8@8@<@<@F@H@U@@@8@F@D@F@<@@@F@D@<@<@<@@@<@0@<@<@Z@@@@@8@8@<@F@D@8@D@4@<@8@<@D@8@8@H@<@B@8@D@<@L@4@@@D@B@D@D@<@B@@@B@<@<@B@<@D@8@4@8@B@H@8@F@@@<@D@F@F@<@B@8@@@<@8@<@@@8@<@<@<@<@B@<@8@@@B@@@@@B@@@@@8@<@8@F@<@@@F@<@B@H@H@8@D@F@@@0@8@@@D@8@<@D@<@D@<@F@<@8@B@<@B@4@<@F@@@8@B@@@4@@@@@B@8@8@@@J@@@B@F@F@D@D@B@<@<@<@@@D@<@@@8@D@<@8@F@@@<@<@<@@@D@<@<@B@@@B@8@<@<@8@<@<@@@@@H@8@D@P@<@@@8@D@<@<@@@@@4@<@<@<@D@<@@@@@J@Q@@@8@<@B@@@8@4@4@<@@@<@<@D@<@4@D@8@<@@@8@B@8@<@H@<@B@D@<@@@4@D@<@@@@@<@8@@@@@4@@@8@D@8@<@4@4@@@B@8@<@8@D@F@@@@@8@<@@@8@<@@@<@B@???????4@4@4@8@0@0@4@(@4@4@8@0@<@4@0@(@0@(@8@4@V@4@4@@@8@@@8@0@8@8@0@8@4@0@4@0@4@(@8@4@8@(@4@8@8@(@0@8@0@(@4@@@0@8@0@4@8@8@<@@@0@4@8@(@4@<@0@0@0@8@8@4@Q@4@8@4@@@4@4@8@4@0@0@<@8@0@8@8@<@<@8@8@4@@@0@4@4@4@8@4@4@<@0@<@8@4@8@4@4@0@0@8@B@D@0@8@4@4@0@4@8@4@0@8@0@0@<@<@(@4@4@0@0@0@8@0@0@(@4@0@0@L@H@F@P@J@B@F@H@D@F@D@D@F@H@P@H@H@J@P@Q@F@D@H@L@J@N@N@H@P@F@H@F@D@H@D@Q@H@J@J@Q@H@B@F@H@H@@@D@H@J@H@D@F@H@L@F@N@J@H@H@F@8@B@F@J@H@J@F@F@L@D@Q@P@S@L@J@B@J@F@H@Q@F@N@@@L@b@H@F@[@^@J@D@F@D@F@H@F@J@J@H@B@S@B@N@R@F@Q@H@H@N@J@B@D@@@D@B@S@F@D@H@L@D@F@H@H@H@@@J@H@P@B@H@S@D@F@Q@F@J@D@F@T@D@F@S@F@D@D@F@D@B@B@H@B@J@Q@L@H@J@T@F@H@J@S@L@H@J@B@D@B@L@J@D@F@D@N@F@N@B@H@H@N@J@F@N@D@P@J@B@J@\@F@L@P@J@F@F@J@H@N@P@D@R@F@H@Q@H@F@`@H@J@B@<@H@J@F@D@H@F@J@L@D@H@F@D@H@J@L@F@H@S@N@F@P@J@N@T@H@H@J@H@N@F@J@L@F@T@D@D@B@D@F@F@P@F@H@H@H@D@L@R@B@H@J@D@F@J@F@F@F@J@H@D@F@D@J@U@L@J@J@J@J@H@D@H@H@L@J@Q@L@L@L@J@H@P@N@Q@@@L@H@S@F@J@B@D@D@Q@S@H@J@V@H@B@B@H@D@8@J@D@D@B@H@H@Q@D@F@H@F@L@D@J@H@F@N@F@F@H@F@H@N@H@H@D@H@D@R@F@F@D@F@P@L@H@J@(@H@J@Q@J@R@N@N@F@F@H@L@@@J@F@F@^@H@H@H@D@H@D@F@J@B@F@H@N@H@J@L@D@H@b@H@F@D@L@J@B@^@H@N@F@D@H@B@H@<@F@S@J@L@D@F@H@H@J@B@N@D@F@J@B@D@H@J@J@D@T@H@J@D@R@B@@@H@B@<@H@J@D@H@H@L@D@R@D@F@D@D@J@D@J@F@F@H@F@R@D@H@L@D@8@B@J@B@H@@@B@4@B@H@@@(@B@L@P@<@D@B@@@L@D@0@@@F@B@H@4@B@F@B@F@J@D@F@H@@@D@B@@@H@8@F@D@F@F@D@D@@@H@N@J@0@<@D@N@@@H@N@B@H@L@<@B@4@<@F@0@L@D@F@J@B@D@8@8@B@<@D@J@J@D@H@B@@@@@H@H@J@F@B@D@D@F@J@H@H@H@D@D@@@H@B@F@H@Q@J@B@B@B@@@D@B@B@4@N@D@H@F@D@B@B@D@F@F@(@4@N@B@J@@@H@8@8@8@B@J@B@D@F@F@8@<@F@<@<@F@F@D@F@D@J@B@F@<@L@J@N@P@4@@@B@D@L@D@D@F@F@L@J@H@B@(@F@B@H@B@(@B@8@N@H@L@F@B@D@J@B@Q@P@4@F@F@H@J@8@H@V@N@D@@@N@4@0@J@F@F@B@B@H@H@B@F@@@4@D@0@B@B@H@J@<@B@<@B@4@F@J@D@J@R@B@D@P@L@F@P@J@F@B@B@B@D@P@B@B@R@L@D@H@L@H@4@J@B@J@4@B@R@L@F@@@F@L@@@J@B@D@S@D@<@L@D@[@D@F@B@L@@@0@Q@V@4@D@R@F@D@@@(@B@D@F@4@L@D@8@H@@@J@@@L@@@D@4@4@<@@@<@8@D@@@D@8@8@8@F@D@8@<@H@D@<@<@<@<@D@D@D@8@B@<@8@@@<@B@<@<@8@@@8@B@8@@@<@<@B@<@4@D@H@@@0@4@8@<@@@4@4@D@L@D@<@<@8@<@B@8@B@8@<@8@8@8@@@@@8@@@8@H@B@<@B@<@F@<@@@D@<@F@@@Z@J@H@P@R@B@8@<@4@<@<@F@8@B@<@@@F@D@<@<@<@B@B@H@@@@@B@4@@@D@@@D@8@4@<@@@8@@@8@@@B@D@H@<@F@4@<@8@F@8@8@B@8@F@8@<@8@4@8@8@8@D@4@4@D@0@<@4@<@<@B@8@<@<@8@D@@@F@B@D@<@4@@@B@H@D@8@<@D@8@8@@@@@D@@@8@8@F@D@<@D@8@F@F@N@<@<@D@F@H@8@D@F@H@D@8@<@<@8@@@8@<@X@D@<@4@@@<@F@D@8@F@8@<@<@4@F@8@8@F@<@@@8@D@8@N@8@<@H@B@D@F@8@F@8@B@B@@@@@<@B@8@8@8@B@H@@@F@<@8@F@D@4@<@B@4@8@<@4@<@<@<@8@<@<@<@F@<@8@<@@@@@<@@@@@@@<@8@8@F@<@<@H@@@B@J@H@4@D@H@8@@@8@<@F@8@<@4@D@D@B@F@<@8@@@<@D@4@8@F@@@8@D@8@4@<@<@@@4@4@<@J@<@B@H@H@D@F@D@8@<@8@<@D@<@<@4@D@B@4@F@<@8@<@@@<@J@<@F@F@B@<@@@<@@@@@8@8@<@@@4@H@8@D@N@<@<@8@D@<@8@<@<@@@8@<@<@D@@@<@8@H@R@8@4@D@@@<@4@N@B@8@<@8@<@D@<@0@D@8@<@<@8@D@D@<@H@@@@@D@D@@@@@D@<@@@<@8@D@<@<@B@<@4@D@<@4@0@B@<@B@<@B@<@D@H@<@D@8@8@<@8@<@@@B@@@?????????ɿ333333???333333ӿ?ٿ?ɿ?????࿚??쿚??333333?ffffff333333???鿚ɿ333333ӿ333333㿚ɿ?࿚鿚??ɿ333333333333ӿٿ333333ӿ??333333ӿ333333ӿ333333㿚?ٿ𿚙?ffffff333333?࿚ɿ?ٿ333333ӿ࿚ɿ???ٿɿ??ɿ333333ӿ?ɿ???࿚?࿚𿚙??鿚?ɿ??333333ӿ??ffffff????࿚???ɿɿ333333?333333ӿ333333ӿ?ٿɿ?ٿٿ𿚙333333ӿffffff濚࿚?333333333333333333ӿ333333㿚?ٿ?࿚ٿɿffffff333333ӿ?ٿ333333ӿ333333333333ӿ?333333??333333?𿚙?ɿٿ?ٿ?333333?333333ӿ鿚?333333ӿffffff?ffffffffffff333333ӿffffff?333333࿚?ɿ鿚?333333ӿffffffffffff?࿚ٿ鿚?ɿ?࿚?333333ӿɿ?ٿٿٿ࿚ɿ333333ӿ?ffffff?ffffff333333ӿffffff?333333?ɿ?ɿ333333㿚ٿ???ɿ333333㿚??ٿɿ࿚??333333ӿٿffffffffffff濚ٿ333333????ٿɿ????333333ӿffffff濚鿚𿚙鿚ٿ333333鿚࿚??𿚙ffffff333333ӿffffff濚ɿٿ333333㿚??333333???ɿ"ɿ?ٿ333333ӿffffff333333㿚?ffffff濚ɿ?ɿ333333?333333?ɿ?333333㿚ffffff333333ӿ?333333ӿɿٿ?ɿٿ࿚ɿ333333㿚???ɿ?333333ӿ333333?333333ӿ??333333ӿ?࿚???ɿ?ٿٿٿ333333333333㿚ٿٿٿ333333ffffffٿ333333ٿffffffffffff濚鿚ٿٿffffff333333ٿٿٿ333333333333333333ٿٿffffff333333ٿffffffٿffffffffffff333333ٿٿ333333333333ffffffffffff濚ٿ鿚333333ٿٿffffff333333ٿ333333㿚ٿffffff333333ffffffٿٿ333333ffffffٿffffff333333ٿ333333ffffff333333333333ٿffffff@@@333333@@@?ffffff@ffffff@ @333333@ffffff@333333@ @ffffff$@ffffff@@ @333333@#@333333@@@@ffffff@ @ffffff?333333@$@ffffff)@333333@ffffff@333333@"@ffffff@@ @ffffff@'@333333!@ @@333333@@@@ @@ @&@@@ffffff!@ffffff@333333@#@ffffff@@ffffff@ @333333@@ffffff@ @@333333@333333@@@333333@ @ffffff@ @ffffff%@ @333333 @ffffff@ @@333333@333333@@ @ @ @333333@@333333?@@@ffffff@!@@ffffff@@@ @333333$@ @@!@333333@ffffff&@ @ @333333 @ffffff @@@ffffff@@@@@333333!@@ @333333%@333333 @@@@@@@333333@)@@@@@ @333333@ffffff@ @ffffff @@@ffffff(@ffffff@ffffff@ffffff@ffffff@333333@@333333@333333@@@333333@ @333333@@@@@333333@ffffff@$@@@ffffff@@@@ffffff@333333@ @ffffff@ffffff@333333%@ffffff@@@@333333-@333333"@ffffff@@@333333@333333@333333@333333 @ffffff@@$@@@333333#@ @333333@@@@?,@ffffff @@/@@333333?@@@@@@?@ffffff@ffffff @@@@@@ @ffffff@@333333@@333333 @@ffffff@@!@333333 @ffffff @ffffff@333333#@@ @@ @333333 @&@ffffff@333333@ffffff"@ffffff@@333333 @@#@@ @@ @ffffff@$@@@ffffff&@@@333333@'@@333333%@@ffffff@ffffff@ffffff@@@333333@@@ffffff@ffffff@ffffff!@ffffff@,@@333333@333333@!@@ffffff@333333@@@@@333333 @@333333$@333333@@@@ffffff%@ffffff-@333333@!@'@ffffff @"@@333333!@333333@@333333@#@ffffff @ffffff@ffffff@(@@333333 @ffffff@333333@?@333333@333333#@ @@333333@1@ @ffffff@333333@#@333333@@ffffff@@333333@@@@ffffff@@@@ffffff@@@@@@@333333?@@@!@@ffffff@333333@333333!@@ffffff@)@!@@!@@ffffff@&@333333@@@@333333@ @ffffff@$@@ffffff"@@ffffff@333333@333333@333333 @ffffff@@333333%@@"@?ffffff$@fffff3@333333)@ffffff@@333333@333333@@@@ @ @@@@'@ @@333333?@L2@ffffff @333333#@ffffff!@$@@@ @333333@@ffffff@@333333@333333@ffffff@333333@ffffff@ @333333@@@@@ffffff@333333@@@ffffff@ffffff@333333@@ @@@333333@@@ffffff@ffffff@ffffff@@ffffff @ @@$@ffffffffffffffffffffffff333333333333ffffff333333ffffffٿffffffffffff 쿚333333ffffffffffffffffffffffff񿚙333333㿚333333 333333333333 333333𿚙ffffff333333㿚333333쿚ٿ 333333ffffff࿚ffffffffffff'333333ffffffٿ333333333333ffffffffffff333333󿚙333333333333333333333333󿚙333333333333333333ffffff濚ffffff333333ffffffffffff333333ffffff333333333333㿚333333ffffff333333ffffffffffffffffffٿ333333࿚ٿ333333󿚙333333𿚙333333333333333333333333333333ffffff 333333 ٿ333333ffffffffffff333333333333㿚񿚙ٿ𿚙 ffffff333333333333 ffffffffffffٿffffff333333ffffff333333333333㿚333333ffffffffffff 333333㿚+333333ffffff ٿffffff  333333󿚙333333333333333333㿚鿚333333ffffff࿚333333333333ffffff????333333??333333????????????333333?333333???ffffff??ffffff???333333?ɿ333333ӿ?ffffff??333333?333333??ffffff?ffffff??ffffff??333333??333333???333333???ffffff??333333??ffffff????ɿffffff?333333??333333?ٿ?????333333?333333????????ffffff?333333?ffffff?ffffff??333333?333333???ffffff??333333?????333333?????????333333?ffffff??333333?333333????ffffff?333333??????333333?ffffff?333333??333333???࿚??333333?ffffff?ffffff??333333????ffffff??????ffffff????333333??333333??ffffff?333333?333333?ffffff?333333??????ffffff?333333?ffffff??333333????????????333333??333333?ffffff????333333???333333?????????333333?????333333?????333333?ɿ333333????333333??333333??333333????ɿ???333333??333333?ffffff?ffffff???333333???333333?333333?ffffff?333333?????ffffff?333333?????????ɿ?ffffff????ffffff???333333?333333?ffffff????333333???ffffff??333333??333333?ffffff??333333??333333ӿ?ffffff?ffffff??ffffff?333333?ffffff????ffffff??333333??ffffff????ɿ?333333??ffffff?????333333?????ffffff??ffffff?333333??333333????333333?333333??333333??????ffffff?333333?333333???ffffff?333333??333333?333333ӿ???ffffff??333333??ffffff???333333??333333??ffffff?333333???ffffff??????ffffff???ffffff??333333?ffffff?ffffff?333333?333333????????333333???333333??ffffff???333333?ffffff?333333???333333????ffffff??????ffffff?333333?333333??????333333?????333333??????????????????ffffff??ffffff??333333???ffffff??ffffff?333333?@?@ffffff?ffffff@333333???????ffffff??ffffff@333333?ffffff???333333?@333333???ffffff???ffffff333333333333??333333@?ffffff???@ @?????ffffff???????ffffff?333333???@???ffffff??@333333?333333???@??333333@333333@?333333?@?333333???????333333?333333333333??333333?ffffff@??333333?333333?@ffffff?333333???@??333333?@333333??ffffff??333333 @@@@@ffffff????ffffff??333333?@ffffff??ffffff???ffffff??@ffffff?333333?333333?333333??????333333@@@@333333@333333???@333333@@?@?ffffff?333333??@??ffffff???333333?ffffff@?ffffff濚??333333?333333????@?ffffff?ffffff?333333?ffffff333333?333333㿚@?????333333??@@??333333@?333333?ffffff333333@333333???ɿ333333??@ffffff?ٿ?@??333333?333333??ffffff?@?ffffff?ffffff?ffffff濚??࿚@?333333333333@333333??333333?ffffff?????ffffff濚?@࿚?333333?333333?333333@333333@333333???ffffff?@@@333333?ffffff??ffffff?ffffff?333333?@?333333?@?@ffffff???333333ӿffffff?333333???333333??333333? @鿚?@ffffff?@?@?ffffff333333ffffff@ffffff?333333??ffffff@?ffffff??ffffff????ɿ333333?333333?ffffffffffff?333333?ffffff?333333?ffffff@?ffffff?ffffff?333333??ffffff??ffffff????333333?ffffff?ffffff@@ffffff?????333333333333?ffffff ?ffffff濚?ɿ333333ӿ?333333??@?ɿ?ffffff333333??ffffff@?鿚?333333?@࿚??@@333333??ffffff?ffffff!ffffff?@ffffff??ɿ333333?333333?333333??@@333333ӿ @@333333?333333?@???333333?ffffff??333333?333333?333333ӿffffff?ffffff???333333??333333@??ɿ?@?@333333ӿffffff????ٿ??ffffff???@ffffff?ffffff???@@?333333+@3333331@L;@333333#@%333333@ffffff@ffffff@ @@#@6@ffffff-@ffffffD@*@@ffffff@@333333:@ffffff@ffffff@333333&@3333335@ffffff2@ffffff?@?2@33333B@ C@333333@ffffff2@,@>@@ @@ffffff$@ffffff?@9@@333333 @333333*@333335@3333335@7@(@2@@?@fffff3@@1@fffff4@!@3333335@7@2@333333(@ffffff@ffffff'ffffff&@ffffff@%@333333%@ffffff!@$@ffffff)@333333,@333333$@333333@1@ffffff"@2@ffffff(@?ffffff"#@?&@*@"@ffffff*@ @ @3333339@ffffff1@$@%@F@!@5@@4@333333333333@ffffff'@ffffff5@ @fffff&B@0@ffffff*@ffffff#8@333333%@2@*@.@3333333333336@3@@#@333333#@&@@ffffff%@&@333333"@0@;@@2@ffffff@@ffffff'@ffffff-@.@ffffff(@LE@?-@ffffff$@?@ @0@@)@*@ffffff"@ffffff?fffff2@ffffff,@@333333@?fffff4@ffffff@333333@333333@(@@ @ffffff"@ffffff,@fffff1@ ffffffI@ffffff5@ffffff&@D@L2@ @'@ffffff'@ffffff@1@ffffff$@ffffff4@5@0@*@6@@2@#@ffffff @@@ffffff7@@3333338@&@&@3333334@@@@ffffff9@N@333333!@@@A@L2@0@ @ٿ#@333333&G@ffffff.@6@̌G@,@@'@G@6@ffffff@333333@ ffffff!@ffffff@@*@333333*@)@@@@)@ @333333%@@333333@333333@6@3333331@+@fffff3@7@333333@fffff8@%@:@333333@@;@33333:@@5@'@333333@'@333330@4@fffff6@.@@'@1@&@33333<@2@"@L<@ffffff(@ffffff7@fffff@@@ffffff3@@(@@ffffff?ffffff"@$@"@@ @"@@/@7@fffff&K@)@3333337@ @ffffff:@3333335@%@6@7@@*@@ffffff$@ffffff5@fffff:@3@(@333333@ffffff)@6@G@@B@?@@8@3@fffff0@ffffff@@%@@@=@@L0@9@@333333/@@1'@ffffff@@333333?)333333@G@@*@/@8@333335@@fffff6@333333'@-@ffffff,@ffffff'@'@ffffff!@,@?333333@333333%@*@@@!@>@",@6@333333+@3@3@ffffff@?L@@fffff0@333334@<@ffffff:@-@;@333333"@.@@E@"@&@333333?!@fffff&D@@/@333336@5@3@@333333 @333333(@ffffff333333񿚙1@3@333333@E@?1@ffffffc@fffffF@@@ffffff+@ @?333333?@(@333333;@!@ffffff3@333333@L@@fffff@/@ffffff@*@I@@L@@8@333333<@333333+@@$@ffffff@ @@333333&@-@(@333336@ffffff'@ffffff0@3@7@,@ffffff*@!@"@$@:@.@L0@ffffff@ffffff:@333333333333"@!@ffffff'@ffffff@)@ffffff?333333-@/@@@-@L4@>@:@3@̌F@?@ffffff@%@ffffff+@333333'@+@ @333336@fffff:@fffff5@M@fffff9@%@%@333333'@33333C@̌B@333333@L1@333333;@33333?@0@ffffff%@@@3333335@(@333333>@8@@@fffffA@"@333333.@L;@ C@N@B@0@*@5@=@?@;@fffff5@3333337@!@@@9@4@333333:@>@ffffff"@ffffffE@A@ffffffA@ffffff.@ffffff!@ffffff33333?@ffffff*@ffffff8@fffff7@/@333333@;@6@fffff0@ffffffD@B@0@33333>@<@)@2+@333333"@3333330@L3@8@/@333333@@.@@2@ffffff"7@3333334@L;@L9@333333:@333333&@0@fffff8@YG@3333337@fffff;@?@0@ffffff̌G@fffff2@3333335@ffffff,@A@@(@33333>@3@L1@33333B@L7@L7@7@ffffff>@7@L4@YA@ffffff(@333333>@*@333333@?@2@3@ffffff0@G@333333@2@3@/@4@*@L=@ffffff@L2@fffff4@fffff4@333333%@333333?@333333@@3@ffffff=@ffffff/@ C@,@ffffff*@ @fffff;@3333339@5@ffffff4@fffff>@33333<@?lP@fffff=@;@ffffffH@L>@L5@333335@,@,@333333E@2@ffffff/@8@333336@6@9@@;@333332@3@fffff&F@2@)@33333A@333333@ffffff(@@ɿ333333%@?5@:@8@333333'@fffff&B@<@fffff:@)@!@333333@-@33333O@L1@L;@L6@ B@333333 -@9@;@fffffC@"@@333333@ @L3@333333"@:@?6@@?ffffff(@333333;@L5@333334@ffffff@*@2@fffff&@@fffffD@fffff1@9@3333330@333337@6@3333339@ffffff @-@ffffff9@F@%@ffffff@E@333333@9@$@ffffff>@fffff9@3333333@0@6@;@333333@3@L7@;@@ffffffffffff-@8@YD@ffffff7@E@$@7@ @)@ffffff<@33333:@?@-@333336@ffffff-@/@8@333333:@33333R@0@33333A@9@;@fffff&B@333333@fffff?@@@@8@/@3@ B@33333=@fffff=@4@33333A@333333?@@%@:@fffffH@33333E@)@E@333333=@%@333333?333339@L1@@F@5@+@̌A@A@"@@<@333333%@333335@333333@@333333? @ffffff-@8@/@ffffff4@ffffff3@fffff2@fffffC@'33333D@333331@*@<@333338@<@fffff1@8@-@333333 @.@2@2@333333:@ffffff4@fffff6@LD@fffff0@=@@(@;@ffffffC@ffffff;@333333)@:@!@L8@333333(@fffff&A@*@:@fffffP@333333ӿ1@񿚙3@ffffff0@ffffff#@@>@ffffffD@5@ffffff/@8@@ffffff&#@7@A@333333@ L@?ffffff;@ E@A@)@333339@6@3333331@1@8@@33333=@1@fffffB@:@>@%fffff@@)@/@R@̌C@;@fffffD@1@4@ffffff(@ffffff)@333333/@@4@33333=@333333=@6@ffffff;@333333?@fffff&E@L7@?(@@3333333@ffffff.@ffffff9@333333"@ffffffD@333337@;@333333.@6@@ffffff5@1@3333337@!@33333A@333333@fffff;@4@'@4@33333@@fffff5@????333333ӿ333333?鿚??ɿ333333?ffffff?࿚?ٿ??ɿ333333ӿ?ٿɿ333333??࿚ٿɿɿ?333333ӿffffff?ɿ?333333ӿٿ࿚??ٿɿ333333ӿ333333??333333ӿ?333333??333333ӿٿ?333333ӿ333333?333333󿚙ɿ333333ӿ?ffffff濚ɿɿɿ333333ӿٿ?ɿ333333ӿ????ɿ??𿚙?ffffff??333333ӿ333333??ٿ?ffffff濚????ɿ??ffffff?ٿٿ??࿚ɿ333333㿚?????ٿɿ쿚???ɿ?ٿ??333333ӿٿ?333333?ٿ333333333333ӿ333333ӿ?333333333333ӿɿ?ɿ333333㿚?ٿ333333?𿚙??333333ӿffffff濚??ٿ333333?333333㿚??ٿ?ٿ333333?ٿ?ɿ𿚙ٿٿɿ?333333㿚??333333ӿffffff?ffffff333333??𿚙?ffffff濚ٿ333333?鿚ɿ??333333?333333ӿɿ࿚?ɿ?????ɿ?쿚?ffffff??񿚙?ffffff濚??ffffff??333333ӿ?333333???333333?ffffff???333333ffffff?ffffff333333??ffffff333333㿚ɿ333333??ٿ?333333ӿ333333ӿ࿚?ɿɿ࿚???ɿ?ɿffffff濚ٿ???ffffff쿚ٿɿ????333333ӿ333333ӿ333333??ٿffffff濚??333333ffffff濚333333ffffff?ɿ?ٿ࿚?ffffff?ٿ??333333㿚??333333??ɿ𿚙333333㿚?࿚?ffffff濚?333333ӿ?ffffff??鿚?333333ӿ333333???ٿ333333ӿ333333ӿ333333ӿɿٿ333333?????ffffff??ٿٿ333333ٿ333333 ffffffffffff333333333333𿚙񿚙ffffffٿ333333333333ٿ333333ٿ񿚙ffffffٿffffffffffffffffff񿚙ffffff333333333333ٿ333333333333333333333333333333333333ٿffffffٿ333333ٿ쿚ٿٿ333333?@@333333"@?3@ffffff@ @@#@@@ @333333-@)@@#@@ffffff@333333 @333333@ @333333@ffffff@ffffff&@@"@#@333333@@ffffff@@"@@ @@@ffffff@?ffffff@@ @0@ffffff@333333 @@@333333@ffffff@@@ffffff @ffffff1@@ffffff@#@@333333@#@@!@@333333*@-@ffffff@ffffff@ffffff)@333333#@333333?@@333333 @ffffff @!@ffffff&@ @333333@'@333333"@333333#@333333)@@@!@333333@3333333@333333@@@@@333333@ffffff@333333@@333333'@ffffff@333333@%@@333333/@!@&@333333@333333@ffffff @@2@333333@ffffff@@@-@$@333333@@@@#@333333!@ @?ffffff@333333@"@@ @@@#@ffffff$@"@!@ffffff@&@'@@'@333333@'@333333@ @333333&@ffffff@ffffff@333333)@!@333333@!@@@@333333@!@333333@$@ffffff@ @ @#@ffffff @ffffff @ffffff@333333&@333333"@ffffff@ffffff@333333@ @333333@ffffff@333333@ffffff%@@ffffff@#@#@@@ @#@ffffff@333333@ffffff?333333?ffffff@333333@"@ @333333@@$@!@333333@ffffff+@@-@+@@@@@333333'@&@@@L1@333333!@ffffff+@333333!@ffffff@ffffff0@@ffffff@ @@ffffff?333333@333333@ffffff@333333 @ffffff@ffffff@@@333333@@+@@ @ffffff"@*@L0@333333@333333@@@333333(@333333@ffffff@@@ffffff @333333@ffffff'@ffffff%@@@@ffffff@?@@@333333@333333@"@ffffff@ffffff@@333333"@ @@@333333@ffffff?ffffff @ @333333@ffffff$@333333@@ffffff@@333333'@333333!@333333"@ @ffffff@@@@ @333333@ffffff)@@ffffff@@@/@@333333 @@fffff0@@ffffff@"@@ffffff@ @@?ffffff@ffffff*@@@%@@@ffffff@@?@#@333333@@@@ @"@333333@@333333@333333@ @? @@333333@!@(@@@ffffff@@333333&@$@"@333333%@@ffffff@ffffff@!@ @"@@333333@ffffff @ffffff @ @"@@@@@@333333@#@333333)@@@@@#@333333@-@333333@333333@ffffff@@#@@333333'@@?@ffffff%@(@333333@%@%@@333333*@333333(@$@&@!@ffffff?@ffffff$@ffffff#@-@ffffff@@@333333@ffffff@@@1@!@333333 @ffffff @ffffff @@ffffff-@ffffff@@$@333333@-@333333!@333333%@#@.@@@@!@@ffffff)@@@333333"@@@333333@$@ffffff/@333333@333333@?@#@"@#@!@ffffff@333333%@ffffff!@ @@@ffffff@ffffff@ffffff?%@?"@@ffffffffffff濚ٿ333333333333ffffff濚 ffffff333333333333 333333333333ffffffffffff濚 ٿffffffffffffffffffffffffffffff 333333333333ffffffffffff鿚ٿffffffffffffffffff濚 ࿚ffffff333333333333333333ffffff333333ffffff 333333ffffff333333333333ffffff333333333333ffffff333333#ffffff333333333333ffffff333333ffffffffffff333333333333𿚙ٿffffffffffff 333333ffffff333333333333ffffff333333 333333ffffffffffff333333333333ffffff333333ffffff333333ffffff333333333333ffffffffffffffffff ffffff333333ٿffffff333333333333ffffff 333333333333333333333333ffffff#333333333333ffffffffffff"333333ffffffffffffffffff ffffff0333333?ffffff??? @?????333333??ffffff??ffffff?ffffff??333333??333333?333333?333333????333333?ffffff???333333???????????ffffff??333333?ffffff????ffffff????ffffff???????ffffff?????ɿ????@????ffffff?333333?ffffff????333333??ffffff????333333?@333333??ffffff?333333??????????ffffff?ffffff?ffffff???333333???333333?333333??????쿚?ffffff??ffffff??????333333????333333???333333?333333?????ffffff??ffffff??333333??????333333???????333333???ffffff?ffffff???ffffff?333333?333333?ffffff?333333?333333??ffffff?????@???ffffff???333333333333??ffffff?333333?ffffff?ffffff??333333?????333333?333333?????ٿ????333333 @ffffffffffff?333333??333333?ɿ??333333????333333?333333??333333??ffffff?????ffffff?ffffff?333333???333333?ffffff??????ffffff??ffffff?333333???333333???????333333?333333????@?333333???333333?333333??333333???333333?ffffff???333333?333333??333333?333333??????????????ffffff???ffffff?????@?333333???333333??333333????ffffff?ɿ??333333??333333?ٿ333333??????333333???333333??333333??333333??333333@???ffffff?333333?ffffff??333333??ffffff?ffffff???ffffff??@?333333?ɿ?@333333??ffffff?????????333333???????ffffff??ffffff?333333??333333??ffffff?ffffff?????333333?333333?333333???333333???333333??ffffff?333333??ffffff??333333333333??333333????ffffff??333333ӿ???ffffff@333333?333333?333333?????ffffff??ffffff?333333????333333???333333?ffffff?333333?333333?333333???????ffffff??????333333?@?333333?333333@ffffff?ffffff?ffffff@333333?ffffff???333333?@ffffff@@ffffff??? @??ffffff???@@?333333??@????333333 @@??333333?ffffff?ffffff???ffffff??ffffff??333333?ffffff??@333333?@ffffff?333333????@333333@ @333333@??333333@?333333?333333@@?ffffff?ffffff @@񿚙ٿ@?333333?@??ffffff?ffffff333333?ffffff???333333?333333?@333333@? @?ffffff??ffffff?@333333???@???ffffff@@?@?@?ffffff?333333??@ffffff@?@333333??333333??333333 @??333333@@@333333@333333@?ffffff??333333?ffffff@@ @ffffff?333333@?333333?ffffff?333333@333333?@ffffff?ffffff?@333333?ffffff???ffffff@?ffffff @@@?@@??333333?ffffff? @??333333?ffffff??333333?ٿ?333333 @ffffff?333333ӿ???333333?@??333333ffffff?@ffffff@ffffff?333333@333333?333333?ffffffffffff@333333@333333@@333333ffffff@@@ @࿚@ffffff濚??333333??@@???333333?ffffff????ffffff@333333? @333333ffffff@?333333󿚙???ٿ@ffffff?333333???ffffff@333333??ffffff@@333333333333???ffffff?@ffffff@??333333????@@ @@???@?@@ffffff????ffffff???ffffff?333333??333333?333333@?333333?񿚙?333333333333@ffffff?@鿚??333333?@333333?333333?333333@333333@333333?333333@??333333??@?ffffff????333333@?333333??333333???ffffff@ffffff?333333@?ffffff??333333 @333333??333333?ffffff??@@????ffffff@333333????ffffff@333333?ffffff@333333ffffff@?@333333?@333333ӿ???ffffff?333333 @@??ffffff?@@333333?333333?333333? @?ffffff???ffffff?333333@?@333333?@@@?333333@ffffff @?333333@ffffff@ @?333333@333333??333333??333333?@?333333?333333?? @?ffffff@@@@??333333?@??333333 @??333333?ffffff@??ffffff?ffffff@?ffffff??@ffffff?333333@?@333333?@@ @1@ffffff,@8@@YB@+@ffffff3@&@:@3333333@@333333@ffffff@@33333F@-@ffffff6@,@/@333333"@ffffff+@2@<@333333"@fffff;@5@ffffff.@ffffff'@ffffff)#@ffffff6@333333/@333333'@333333>@,@/@333333-@fffff4,@0@5@̌C@333333 @?333333.@fffff1@4@@ffffff@"@333333+@LG@<@9@fffff6@ffffff@333333@333333=@@ffffff.@fffff0@L<@M@'@ffffff @;@<@ffffffffffff<@3333332@L0@ @>@*@333333+@#@;@C@L@@8@/@333333D@@B@LI@333337@:@LF@ffffff"@333333@ @@@+@ffffff)@ffffff@>@ffffff@I@ LD@9@333333@ffffff@2@fffff@@fffff7@333333@ffffff$@ffffff0@fffff?@8@@3333331@@@ffffff:@L6@@333333@@ffffff5@!@,@+@0@333338@5@3333335@A@/@333333H@333333E@@6@ @G@ @ @LG@333333@@4@.@)@ffffff+@(@%@333333'@L5@3333335@33333:@ffffff6@YH@@333333;@/@;@@?@fffff4@333333@%@@$@#*@7@3@333333@D@fffff3@ffffff'@)@A@333333.@ @ffffff.@333333ffffff9@ffffff+@3333334@ffffff@333333%333333;@@33333=@ffffff/@"@fffffB@333333@YD@L@@333333+@0@.@̌B@LD@ffffff@E@F@9@1@@333333@33333P@333333@!@8@(@ @9@@ffffff?33333?@ffffff1@333333-@/@%@L0@ffffff$@̌H@333332@2@3@33333sI@F@$@333333=@(@=@L@@333333+@ffffff(@+@/@fffff6@@33333F@6@??@.333333"-@3333332@@3333334@fffff?@ffffff,@?L;@4@8@$@fffff0@333333㿚@!@Y@@5@8@&@*@,@L?@fffff>@3@ffffff>@ffffff*@"@@?@A@ffffff9@ffffff%@$@@>@3333333@333333?6@L@333333;@?ffffff2@3333335@$@)@? &@ffffff;@fffff0@3333330@E@0@.@9@333333@(@1@ffffff!@0@ffffff6@@?0@#@333332@ffffffL0@333333(@27@ffffff,@@fffff&A@33333M@:@(@$@ffffff#@ffffff7@L5@*@333336@ffffff@L2@,@-@1@ffffff4@@1@@/@3333330@9@ffffffL4@3<@%@B@ffffffE@8@3@=@@6@333333㿚@@@0@=@333333#@fffff7@333333+@L7@@fffff0@3333339@ffffff$@ffffff?:@?@/@@@E@333333(@333333?@L;@ffffff$@;@33333:@E@#@333333?@,@)@,%@9@?@ffffff<@3333359@3@:@fffff&P@4@&@7@)@E@33333A@D@333332@?@ffffff@ffffff"@ffffff1@4@ffffff4@:@@fffff>@.@3333331@333333?@333333+@3333330@ffffff @333333/333333@<@fffff0@?@(@33333sA@333333;@9@333333#@&@'@3333334@ffffff=@EL=@5@-@=@33333B@ffffffB@fffff2@333333U@333336@fffff6@?@A@333333=@333335@@F@̌N@?@LE@L2@1@7@YI@:@̌C@333333)@̌C@*@B@C@*@3333330ffffff,@33333D@<@0@A@?@@G@=@13@3333335@fffff&A@I@ @ffffff @L:@333332@=@"@3@3333336@3@̌L@?@̌G@33333>@,@4@A@&@A@B@33333sK@9S@3@ffffff'@33333G@C@ffffff"@H@fffff@@:@+@K@33333sG@@@D@E@@B@H@333332@@33333sJ@7@33333E@?@.̌J@*@333333@0@@.@fffff=@L9@YE@.@7@#@33333L@ @fffff&S@33333@@ffffff@@A@3333330@>@+@333335@̌B@fffffS@A@3333336@>@4@ffffff%@33333A@ffffff@@(@%@4@ffffff&@33333E@3333330@8@ffffff5@8@@I@fffff9@>@33333I@A@P@fffffO@7@L;@ffffff0@33333M@>@ffffff6@@O@33333>@fffff3@fffffB@9@8@3333335@@@7@)@333334@LA@̌B@fffff&B@ffffff<@ P@4@ I@3333335@33333M@8@I@9@333333'@E@ffffff3@ffffff*@fffff2@333333ӿfffff&B@@@333333<@*@H@=@fffff5@!@C@F@(@#@333333@L:@/@333333.@)@"@L@@333333!@0@L8@6@@J@-@ @,R@#@5@33333sC@33333L@33333K@3@3333339@ L@fffffN@fffffU@?@333333@G@@333337@333332@.@ffffff#@fffff4@3333332@333333*@33333F@333333C@ffffff9@6@3333335@fffff7@333339@fffffM@ffffff8@5@I@J@LS@333333@ffffffF@333333/@7@G@3333337@@@@ffffff+@33333E@̌N@L@@#@333333)@,@ffffff@+@<@%@L8@@E@fffff1@7@LG@ffffff9@A@(@;@#@,@>@H@H@H@ffffff4@0@333330@33333sG@A@A@YG@1@@1@'@&@@fffffF@LD@ffffff+@333331@*@fffffL@33333>@@@fffffO@@@@333333<@fffff&C@@ffffff8@333333(@ffffff@ @33333sH@ffffff6@7@;@333333D@33333sA@<@fffff8@?fffff7@3333339@2@<@>@ffffff1@@>@(@0@6@8@ 2@ffffff/@D@S@LA@33333O@3333334@333333)@Y@@fffffH@333333.@333333@@ffffff#@;@3333334@B@@@@@ffffff5@4@+@>@333333@@@333333$@@>@33333333333>@fffff=@3@fffffD@ffffffE@L>@33333sF@3333332@LC@ffffff33333sF@333333)@3333334@*@333333@33333D@ N@#@333333 333333-@ F@5@333333@%@33333@@3@M@33333I@8@33333B@63333339@33333SP@?@333333K@)@333333@?@fffff;@ffffff@1@E@ffffffM@?@ffffff333333H@J@̌B@33333T@6@.@ffffff9@333335@I@I@YG@333334@#@@2@G@ffffff@333337@fffffF@9@33333S@@H@8@@fffff4@333333F@8@L9@>@(2@333333B@@B@E@L1@B@G@@@@-@@@9@B@@G@@C F@33333@@?333333?ɿٿ?ٿ?ɿ333333?ɿɿ?ɿɿ??ffffff???????ɿ????333333ӿ???ɿ?333333??ɿ?ɿ????ٿ333333???ٿɿ??333333??ɿ??ɿ?࿚?333333?333333ӿ??ɿ?ffffff???333333ӿ?ɿٿ?333333???ɿ?333333?ffffff?333333?ٿ?鿚ɿ???ffffff??ɿɿ?ɿ?ɿ333333㿚ɿ鿚?ɿ???333333??ffffff???333333ӿɿ?????333333?ffffff??@333333?333333333333ӿٿ???333333??333333㿚?ffffff??333333?ffffffffffff??ٿ??333333?333333ӿ?333333333333?333333?????ɿ???࿚?ٿɿffffff?ɿ??333333??333333?333333?ɿ333333?ffffff?࿚333333?࿚ɿ?????ɿ???333333??333333???333333?ɿٿ?333333ӿffffff??ɿ?333333??333333?࿚鿚???333333ӿ??333333???333333ӿ333333?333333??ٿ??ffffff333333?ɿ?ٿ࿚ٿ?࿚ɿ????ɿ?????࿚?333333?????333333?ffffff濚??ɿ??࿚ٿ333333?ffffff濚ffffff333333㿚?ffffff@@???ffffff?ɿɿ࿚???333333??࿚???ffffffffffff濚?ɿ?@?333333333333?333333ӿ333333?333333?333333㿚?ٿ333333ӿ333333?333333ӿ??ffffff?333333?333333?333333?333333㿚ٿffffffffffff 333333ٿffffff333333%ffffff ffffff 333333333333333333񿚙ٿ333333333333333333ffffff%ffffff333333333333333333ffffff333333ٿ333333ffffffffffffٿ333333333333 ࿚333333ffffffffffffffffff!ٿffffff333333 ffffff333333񿚙333333333333333333333333 ffffff 333333333333ffffff333333ffffff ٿffffff333333333333$ 333333333333333333ffffff쿚ٿٿ0ffffff333333333333ffffff333333 ffffff333333ٿ333333(333333 333333ffffffٿffffffffffff 333333࿚ٿ333333ffffffffffff??@?333333?333335@@@@ @??333333*@@@@@ffffff @?@@@ffffff?@?ffffff @333333@?333333?ffffff@ffffff@ffffff@? @?@333333@?333333$@@333333?ffffff@ffffff@ffffff???$@@?ffffff@?@@@@#@(@@ffffff@ffffff@333333@?333333?ffffff?@ffffff?@333333@@333333?'@ @333333 @@?ffffff @ffffff@@ffffff)@ @ffffff @ffffff@??333333 @ffffff?ffffff@??@?ffffff$@ffffff#@@?@333333?@5@ffffff?ffffff?333333?ffffff@ffffff$@@ffffff? @?@ffffff@?? @@@333333?ffffff @ffffff?@ffffff@@ @@@ @@333333 @@333333@@?@??@?333333?@@@@@?333333 @333333@@ffffff?333333@? @?333333@333333@???ffffff@?@?@@ffffff@??@@?@?@@333333@@ffffff?@?333333@?ffffff@(@?ffffff4@333333@?ffffff?ffffff?*@!@??333333$@ffffff@%@"@?ffffff,@333333 @?ffffff @?@@??ffffff@333333?@ffffff@@!@ffffff@333333@ffffff@333333 @ffffff+@333333@@?ffffff @333333@??@@333333!@?ffffff??333333@333333?ffffff??@333333@ @???@@@@ffffff@?ffffff??@@?@ffffff?@ @333333@ffffff?333333?333333@333333?@ffffff @ffffff@ @??333333@ffffff*@@@?ffffff @333333?? @333333@ @333333@@ffffff@????@ffffff$@@@??333333&@@ffffff??333333???ffffff@?@'@ffffff @ffffff??@333333@@@ffffff@333333?@@@@@ffffff"@ffffff?@??@ffffff@?@ffffff@333333 @@ffffff @333333? @?333333?%@@333333 @@?333333@@333333@?ffffff@@ffffff@333333!@@?ffffff$@ffffff@!@ffffff@??ffffff@@333333&@ffffff@@333333?@ffffff?ffffff?@.@@?@@ffffff@333333@ffffff@@333333%@@@@ffffff)@ffffff@??ffffff@!@ @@@@333333?@0@???? @?@?ffffff@@ffffff@333333@ @?@ffffff??333333@?@?࿚鿚ٿ333333󿚙ٿffffffffffffffffff% 333333333333333333,333333ffffffffffff333333 333333ffffff333333ffffff!333333 333333ffffffffffff333333333333ٿٿ 333333333333! 񿚙ٿ333333'ffffff333333(ffffff333333ffffff333333333333 ffffff$ffffffffffff ffffff333333ffffff#333333333333ffffffffffff 333333333333333333ffffff ffffff!333333(ٿffffff ffffff 333333ffffff333333ffffff'ffffffffffff 333333333333( ffffff ffffffffffff333333333333ffffff*333333333333333333333333333333 333333&333333(ffffff333333ٿ333333333333ffffff)333333$ffffffffffff333333!ffffff333333L0ffffffffffff?ffffff@?ffffff @?@-@??ffffff??ffffff@@?????333333?@@ffffff@333333? @?333333?ffffff@333333@ffffff?ffffff@?ffffff???ffffff333333333333?333333ӿ??ffffff?333333?333333ӿffffff??????ffffff??333333?ɿ333333??333333??333333??ffffff????333333?ffffff??333333?????333333ӿ333333?333333??333333??ffffff?????ffffff??333333?333333?ffffff????333333?333333??ɿ??333333?333333?ffffff??ɿ?ffffff??????333333????ffffff?ffffff????ffffff??ffffff??鿚?333333?ffffff???333333ӿ??333333㿚????333333????ffffff????333333ӿ333333ӿffffff??333333ӿ??333333??ffffff??ffffff????333333?ffffff?333333??????ffffff??ffffff?333333?333333?333333?333333?ٿffffff?ffffff??????333333?333333?333333??333333??????333333??ffffffffffff?????ffffff??ffffff濚ٿffffff?ٿ?333333??ffffff?𿚙????????333333??ffffff??@𿚙?࿚??ffffff??333333㿚?ffffff?ffffff?333333?333333??333333ӿffffff??????333333???333333??࿚???ffffff?ffffff??333333???333333??333333???ɿ??ffffff???333333??ffffff?333333?333333333333????????333333?333333?ffffff??333333????333333???ffffff?ffffff?333333???333333????????333333?????ffffff??ٿ?333333??????????333333?ɿ?333333?ffffff???333333?333333??333333???333333?ffffff?333333??ffffff?333333??ffffff濚@??ffffff?333333???????????333333?333333?333333ӿٿ????333333?ffffff333333??ɿ?ffffff??ٿ???333333???333333?333333?ffffff?????333333??333333??333333󿚙?ffffff??ffffff?333333ӿffffff?ffffff?333333??ffffff?333333?333333???333333??ffffff??ffffff?ffffff?ɿffffff???333333??333333?333333??333333??ɿ?ffffff???333333ӿٿ????333333?333333???ffffff????????333333?333333?ffffff񿚙?333333㿚??333333@333333?333333?333333????333333?ffffff333333?@?𿚙ffffff???ٿ333333@????ffffff333333ӿ??333333?333333333333ffffff?333333?ffffff?ffffffffffff?333333ffffff????333333?333333??鿚?ٿ?ɿ333333?ffffff?ffffff?333333???ffffff??ffffff?333333ӿ333333?@?ffffffffffff?333333㿚333333??333333??ٿ?ɿffffff???333333?𿚙????ɿ?333333?ٿ쿚?333333??ffffff?333333ӿٿ???ffffff??333333?333333?ffffff??333333?333333?@?ɿ??333333?333333???????鿚?333333???333333ӿ333333?333333ӿffffff333333??ٿffffff???333333???????@??ٿffffff濚ٿɿ?@ffffff?333333??ffffff333333?ffffff@?333333?333333333333??ffffff333333??333333?ffffff?? @?333333?ffffff? @@@333333333333???333333ӿ?ffffff濚?ffffff???333333쿚ffffff???ffffff?333333?ffffff?ffffff???ffffff333333333333??333333ӿffffff?ٿ333333?????𿚙?@????333333??333333?ffffff??333333ӿ333333?333333㿚ɿ??@?333333ӿ࿚???ffffff?ٿffffff濚??ٿffffff?ffffff333333?@ffffff? 333333??333333?쿚ٿ@ffffff濚?333333333333󿚙??ffffff?333333㿚ɿffffff?333333ӿ333333????333333ӿ?ffffff?333333?333333ӿ?@?333333??@333333333333???ffffff?𿚙?333333?ffffff??ٿ333333???ٿ?? @?ffffff@333333?333333?333333㿚?@??????333333??333333@???333333??333333?𿚙ffffff?ffffff?333333@333333??@333333?ffffff?ffffff??333333@࿚???333333ӿffffff?@??333333㿚?𿚙???333333???ffffff濚ٿ333333??ffffff@333333ӿ?ffffff??ffffff???333333333333??ffffff??333333󿚙?࿚333333?ɿ??ٿ???ffffff%333338@̬P@ffffff(@ffffff&@333333@=@ @333333,5@ffffff)@?ffffff1@@&@3333337@0@ffffff+@ffffff!#@33333sC33333<@3@F3333338 @&@)@ffffff@333333@233333sG@333333 @@2@+3333330333333?!@4?;@$@@,@8@3333330@@)@ffffff-@B@@333333ӿ2@L0@'ɿ?333333&@fffff0ffffff)@333333/@3333333@@33333<@333333(@ffffff,@ffffff-@@<@.@ffffff?@'@&@ffffff/ffffff333333#333333333333ffffff?ffffff 鿚# B@ffffff @ffffff@333333 @ffffff?@A@ffffff333333*@@̌@@333333)@333333#@ffffff0333333$ffffff!@333333 333333ӿ8@"@"@333333@4@ @'@333338@@̌@@0@@'@ɿ9@ffffff!@+2@ 333333 <@@!@,@ffffff)@3333330@#@5@333333"@333333%)@ffffff. @333333ffffff4@ffffff"@333333&ffffff'@333333ffffff,:ffffff濚?.@"6@&@333336-333338@333333333333 @3!,@ffffff@ffffff C0ffffff2@3333337ffffff!@333333?@D@333333+fffffF@@#ffffff:@C@>@%*;@L2@#@@J@@333333!@ɿffffff?5@ffffff,+@ffffff?333333#@ffffff!@@#:@?333333&@=@33333F@"3333335@ @ffffff @$@ ffffff#@333333 @333333$5@ffffffffffff+3333338ffffff)L8?ffffff4@"@ffffff"@?9ffffff @&@333333!@@ffffff4@!$ffffff1@333333)@@@ffffff333333/.@333333@&@)33333=ffffff*/"fffff5@'@ @ffffff"333334%@ffffff @%ffffff)@ffffffB@333333:333333@ffffff!#ffffff@ffffff'.3333338@;@@(@L0@?? @'@L@@@ffffff @?YB@333333'33333332ffffff@333333Gffffff#@$.@I@@333333333333ffffff/@+@)@333333?3@ @??$@+@333333#@333333,̌@@ffffff,?!@3(5@̌G#@ffffff/@2@333333@"#@!$;@ffffff@333333@.?@*ffffffffffff16 #@/@@ffffff8@#@(33333;@@333333#@(@REffffff@333333=@/@fffff@@ffffff%@?@.@fffffD?ffffff@fffff9@333333*@?4@!@+@/333333@.4@333330@:@/@<@@ffffff&@ /@fffff;ffffff@ @?ffffff1@A@ffffff@ffffff!33333397ffffff!@333333.@ffffff&ffffff>@-@L2@0333333@fffff0@@.=@ffffffG'@ffffff333333/?333333:@ffffff"*33333W@.@@fffff1@@@0@333333㿚*9@@@L@@333333@@333337ffffff(@7@2@333333&ffffff@-@@?+;YC@#@%@;@333333&@fffff&D@ffffff9@ffffff.@)@?33333<@ffffff.@@333333$L>@@333333$@;@ffffff.333333?333333@@.@333333"@333333L3@1:@ @33333<@ffffff%@333333 @?3333337ffffff+@@333334@ffffff&3@ffffff"@7)0@0@? @ffffff#@@333333 (fffff1@3333333#@H@3333331+333333@3333333333330@333333N@ B@!ffffff4@YI@33333S@8@33333@@ffffff-@(ffffff @L3@@3333330@ffffff'@-@ffffff'@333333ӿ333333+2?@333333 @333333@;@333333@@M@ @:@/@-@333333?) @-@333333,L8@333333@# 38333333,,#@'@%@ffffff%@333333@@1@.@@33333@@333333&ffffffffffff?@:fffff:8@21@33333K@ fffffA@333333++@1@333337@@:@333333 ffffff? @0@0@@&<@鿚4@033333>@33333@@fffff:fffff0@@"@3333333@<@3333334@!'@A@+@ffffff@333333@333333 @,@0fffffffffff0@$@ffffff*@ @ @7A@L5@@333333'333333A)@ffffffG@333333(@B@-@L0ffffff@3@333334 $@L@@ffffff?%@ffffff@fffffE@ffffff3@ffffff%6@fffff2@83333337@3@33333>@333333'@"@ @(@ @3333336@@J@ @@333333@333335@ffffff*@ffffff@333333?3;%@@ffffff2@!6@3@ffffff.@333338@:@333333@1333333D@LMffffff+@ffffff@??𿚙ٿɿ??ffffff濚????ɿ?333333㿚??ɿffffff????ɿ333333??ɿ??????ɿ?333333???333333ӿ???333333???ٿ??ɿ333333?ɿٿ????????ffffff???ffffff?ٿ????????????ɿ333333㿚ɿ333333??ffffff????????333333333333?ffffff?࿚?ٿ333333???ɿ?333333????ٿ?ɿ??ffffff?????ffffff????333333????333333?ffffff??ɿ??333333???ٿ???????࿚??࿚?333333?ٿ??ɿ????????ɿffffff????333333?333333??333333ӿ????ffffff?????ffffff?ٿ??ٿ333333??333333??ٿ??ffffff??ffffff?ɿ??ٿ?ɿ?????ٿffffff?333333??쿚?࿚?333333?333333?333333?ɿ???ٿffffff濚?࿚ɿ?ffffff?ٿ333333?ɿ??ffffff??333333㿚?ٿ??ٿ???333333333333?333333?ffffff濚?333333?333333??333333??333333?ffffff?ffffff?ffffff????ffffff?333333??333333?????333333ӿ333333?333333??ffffff?333333?333333??@??ٿ333333??ffffff?333333?ɿ?ɿ?ffffff𿚙ɿ??333333??ٿ?????ffffff333333?333333ӿ쿚???333333ӿ333333?333333㿚?ٿ?ɿ333333????333333?ٿɿ333333??鿚ٿ?333333333333ffffff 333333 "ffffff333333333333ffffff333333 'ffffff333333333333333333333333333333ffffff333333! 333333!333333"ffffff333333ffffff333333ffffff333333!ffffff333333!ffffff ffffff333333 "ffffff"333333333333 ffffffffffffffffff333333)ffffff ffffffffffff ffffff333333$333333$333333ffffffffffffffffff" !!ffffff ffffff333333 ffffff ffffffffffffffffff ffffff 333333!ffffff ffffff ffffffffffff ffffff333333ffffff!ffffff 333333ffffff333333ffffff 333333333333ffffffffffff"333333,ffffff&ffffffffffffffffff)ffffffffffff!ffffff333333333333333333ffffff333333ffffff333333%ffffff&333333%333333ffffffffffffffffff333333 ffffffffffff333333$333333333333333333333333ffffffffffff ffffff#ffffff333333ffffff333333333333 )ffffff333333333333333333 !333333ffffffffffff333333ffffff% !333333#333333!#333333#ffffff333333ffffff 333333333333'333333ffffff333333ffffff!333333333333ffffff ffffff333333ffffffffffff%333333ffffff333333!ffffff#ffffffffffff!#ffffff!ffffff #ffffffffffff,333333ffffff" ffffffffffff$ ffffff 0333333333333ffffff"ffffff333333#333333 ffffffffffff333333!ffffff333333333333333333333333 333333333333333333333333 ffffffffffff"??? @333333??????333333?333333???????ffffff?@??ffffff?????ffffff?????ffffff?333333???ffffff?333333@333333???ffffff?ffffff?333333?@??ffffff???333333?ffffff?ffffff????ffffff @?ffffff????? @???333333?ffffff?ffffff?ffffff?@333333?333333??@333333???ffffff????333333?@?@@??333333@@ffffff@@333333??333333???333333?@??333333@ffffff?????ffffff???333333?ffffff??ffffff?@???ffffff??ffffff?ffffff@333333 @?333333?ffffff???????????ffffff??333333?333333???333333?@?ffffff?333333@@?333333???@?333333??@??333333??333333???333333?333333?????ffffff??ffffff?333333?333333?ffffff@?333333@???333333?333333?@??@@333333?? @?ffffff@ @333333@333333@ffffff????ffffff?ffffff@ffffff?333333???333333??ffffff?????ffffff???333333??ffffff???333333??333333??333333????????333333@??@????@????333333@ffffff??ffffff?'@?333333?333333???ffffff??@?????ffffff@?@333333??@ffffff@??333333???@ffffff@?????333333%@? 'ffffff("333333333333"%333333 333333333333 ffffff" 333333%#333333333333 ffffff"ffffff333333'333333333333" 'ffffff) 333333333333!333333%333333%#333333ffffffffffff!333333%333333$#ffffff333333 !333333?ffffff?@????@333333??@333333?333333@ @???333333?333333???ffffff@333333????ffffff?333333?@333333??@???333333@@ٿ333333ӿffffffffffff񿚙ٿ333333㿚333333𿚙񿚙333333쿚ٿffffff濚ffffff濚࿚??ffffff333333࿚ffffffffffffffffff333333ffffffffffff333333333333333333ffffffffffff333333333333ӿffffff333333333333ӿ333333333333鿚쿚ffffff쿚ٿ쿚ٿ333333333333333333󿚙鿚鿚񿚙鿚񿚙ffffff333333쿚333333࿚333333ӿ333333?ffffff333333333333㿚񿚙?333333ffffff濚ffffff333333ӿ333333ffffff333333ffffff濚?333333ffffff濚333333333333㿚ffffff濚࿚񿚙333333𿚙࿚333333333333쿚鿚쿚𿚙鿚333333㿚ٿffffff333333ffffff࿚?ɿffffff333333?࿚񿚙ffffffffffff𿚙333333ӿ?333333333333ffffff333333ffffff?333333ӿ333333㿚ٿffffffffffff濚ٿ鿚񿚙鿚ffffff濚333333333333333333㿚鿚ٿ333333㿚?ffffff𿚙333333㿚ffffff쿚333333𿚙?333333ӿ𿚙񿚙鿚ffffffffffff333333㿚?쿚鿚ffffff333333333333333333㿚쿚񿚙333333ӿ333333333333?ffffff333333ӿ333333ffffff࿚333333ӿffffff濚쿚?ffffffffffff쿚ffffff333333㿚ٿ333333ffffff濚ٿ333333333333ffffff濚?쿚?333333󿚙𿚙333333󿚙ɿ333333󿚙鿚ffffffffffff333333333333㿚࿚ffffff333333333333333333333333࿚쿚ffffffffffffffffffffffff?333333ӿٿ333333󿚙鿚ٿffffff333333㿚333333ffffff濚񿚙ٿffffff333333㿚333333㿚ٿffffffffffff鿚?𿚙鿚333333㿚333333㿚333333333333㿚ٿ?쿚쿚ffffff濚ɿ333333ffffff濚࿚ffffff333333ffffff333333𿚙ffffff濚333333쿚333333㿚ffffff濚ffffff333333333333㿚࿚333333㿚333333333333333333쿚ffffff333333333333ffffff333333㿚ٿ333333333333쿚333333333333?333333ffffff333333333333333333 ffffff񿚙ffffffffffff𿚙333333󿚙ٿ࿚ffffff333333㿚ɿffffffffffff濚333333ffffff333333ffffff333333333333333333?ffffffffffffٿٿ333333?ffffff?333333@?𿚙?ffffffffffff񿚙?ffffff濚񿚙ٿٿ?ffffff?ffffff333333333333ffffff鿚333333 ffffffffffffffffffffffffffffff333333333333ffffff333333333333ffffffffffff333333 333333㿚ٿ𿚙ffffff?333333333333ӿ333333??333333ӿ333333333333?333333?333333ffffff濚333333?ffffff333333󿚙ffffff?ffffff??333333333333? 333333?333333333333ӿ?࿚?ffffffffffff333333?333333333333ӿ??鿚𿚙?333333??333333333333࿚ٿ?333333??ٿffffff333333ffffff333333333333ffffff濚鿚ٿffffffffffffffffffٿffffff333333?ffffff?ffffff333333ӿffffff333333?333333333333ffffff?333333333333ffffff333333333333𿚙333333??ffffff𿚙ٿ?ffffffffffffffffff ffffff333333ɿ333333333333333333󿚙鿚?ٿffffff濚?333333333333??333333ӿffffff?333333㿚ٿ@333333㿚ɿ ?333333󿚙333333ӿ?ffffff𿚙@?񿚙 333333ffffffɿ333333?ffffffffffffɿ333333ffffffffffff333333ɿ쿚ٿ?ffffff쿚ɿffffff濚?ffffff?࿚333333 ffffff333333ӿ333333󿚙񿚙ɿ𿚙333333?ffffff333333ffffff333333333333333333$. ffffff7333333,ffffff#333333*/333333" $ffffff6G'+ffffff%&4+(63333332fffff83333335$0;(333333-0333333>$7(333333&5333333ffffff(3333332ffffff*=$fffff033333323333335'L4&ffffff!ffffff433333s@.:4(.ffffff6333333㿚+333333&3A!L6360333333)+fffff5#333333.12333337,&L22B333333'1C3333330ffffff&ffffff @333333333333,;333333<333333 33333Bffffff?fffff&@0ffffff:$333332333332L5ffffff*333333)/3333333.333333#47333333 333330fffff0333333*&B333333-3,.'ffffff=fffff&@?ffffff'ffffff>333333333333"A"ffffff,ffffff##)񿚙"333333+333333/8)̌@#L0173333333333335/鿚%ffffff&/0 +5333333)(8333333/fffff6333334333338,(0ffffff''333333ffffff633333sE𿚙1/813333332333333/Offffff*333333/333333C,01333333DLD/333333>$1L0ffffff濚-333333*4 1/333333.333333Efffff5L3333333*C=2ffffff4ffffff"9fffff&Affffff1('L3'633333Affffff?3333336@=@%6fffff&C33333Bffffff.fffff>3333336DD(23333375 C069733333317L23333333;33333sEfffff1G=L0333330@333333@9BI&"ffffffD;,YE@4L8333333E@@ffffff@333333.L:633333>333333<+ffffffffffff> ;ffffff3333333!@3333334/"33333343333332'5fffff7fffffG333333+ffffff58fffffD333333fffff&M3333337;)@A!-fffff5ffffff26fffff&E>633333<92333339=ffffff%,333333+ YAfffff5L42fffffG>7L6lPL;LDI'LB333333633333sEfffff17fffff&J3333365ffffff8;ffffff)4L4:fffff12L<333333A?/H3̌Afffff4 Gffffff5 A2ffffff#fffffCffffff0L22(;;22@@9A1L?A333333.ffffff-ffffff333333+"fffff03333336ffffff(5333337ffffff#L:ffffff*<333330(ffffffS&33333330 A9fffff@33333348LKYAfffffB3333331E333333fffff8ffffff"fffff3ffffff'ffffff-1fffff9@A/333335fffff4333339fffff&AYIL;333333/fffff&BDI333333 @ffffff33333?G,?=333333B#33333sD=)&23333332ffffffffffff*ffffff? ffffff0fffff9ffffff;3D28+ffffff33333354:8A7ffffff02ffffff833333L3333336Dfffff;2fffff8L2333333-333333533333=3333335'98B.ffffff3#J333333*333333.fffff%2('5fffffAJ333333"?- A333333)333333@=fffff7ffffffDB-333333>333333(//33333E;YA333333ffffff-=0/1Cfffff&C333333Affffff%@@G<:̬T,>333333;333333/Y@333333/=333333"8YB333337333333ICL6333333L3fffff734:ffffff @3333339333333AAfffffFffffff$3333333@333336ffffff(%ffffff?ffffff/>$fffff;%@fffffE;333333ӿ?????࿚??ɿ333333ӿ???333333?333333?ffffffɿ333333㿚ٿ?鿚?ɿffffff濚ٿ?ffffff濚?????333333?333333ӿɿɿ??ٿ333333ӿ333333㿚ɿ333333󿚙?333333㿚???333333ӿ쿚ɿffffff濚?ٿ??????࿚?ɿ333333?࿚?ɿ??ٿɿffffff333333ӿٿ??333333ffffff333333㿚?333333㿚?@?ٿ?333333㿚?333333ӿffffff??ɿ???࿚?333333??ɿ?ɿ?ٿ333333㿚ٿɿɿٿ?ٿ?333333333333ӿ333333㿚???333333ӿ?ٿ333333󿚙ɿ?333333ffffff濚??ɿ?ɿ?333333?333333ӿ??ɿ??ٿ?333333333333㿚?ٿ333333ffffff濚333333333333ӿɿ࿚?ɿ333333ӿ鿚𿚙쿚333333࿚?ɿ?333333ӿ?333333333333ӿ333333ӿ333333ӿɿɿٿٿffffff濚???ɿ?鿚ɿ??333333ӿ?𿚙?ٿ?333333㿚ٿ?333333??ɿ???ffffff?ɿ333333ٿٿ𿚙ٿ??ffffff333333??ٿ??333333ӿ??࿚??쿚鿚񿚙?ɿ333333ӿ?333333ӿ񿚙?ffffff濚ٿ࿚ffffff濚ٿ?333333?ٿ333333?ɿ??ɿ?333333ӿffffff?333333ӿɿ333333࿚?333333ӿ333333㿚???333333ӿٿɿɿɿ?ɿ??333333ӿ?ٿ?ٿ333333??ɿ?333333????ffffff333333ffffff?ٿffffff濚ɿɿ333333ӿ333333ٿ333333 𿚙333333ٿ333333333333333333 333333333333ffffff333333ffffffffffffffffff ffffff333333ٿٿffffff333333ffffff333333333333ffffff333333ffffffٿffffff333333ffffff333333333333333333ٿ333333333333ffffffffffffٿ333333ffffff333333ffffff333333࿚ٿٿٿ333333333333㿚ٿffffffٿffffff!鿚ٿffffff333333ٿ333333333333333333ffffffffffff ffffff333333ٿffffff333333ٿٿffffff333333ffffffffffff333333@@333333? @@?333333??ffffff@ffffff@ffffff@ffffff @333333????@333333?@@?333333!@?ffffff@333333"@ffffff&@@ffffff?ffffff@@??333333@333333? @&@?@ffffff@?333333@333333@@@333333?333333!@ffffff@@@ffffff?@@@333333@@@ffffff???@333333@?@@ @?333333#@?333333@????? @?@?@?333333@@@333333?333333@@@?ffffff@@?333333?ffffff @@$@333333@333333@ffffff @333333@@@@@333333?@@@@ @ffffff@333333?@ffffff??@@??$@@ffffff@?@@?@?(@ @@@@? @?333333@@@@?333333? @333333?@ffffff @@333333?@@@333333??@@?@@ffffff@@ffffff@333333@ffffff@?(@333333@?ffffff@@@333333 @333333@?@333333@#@@? @ @@?333333?@ffffff? @333333@ffffff@333333)@??ffffff@@???333333@?@333333 @?@333333 @ffffff@ffffff@ @ffffff?ffffff@?ffffff? @333333 @@@333333@@333333@@@?@@@?@333333@333333?ffffff?@@@ffffff@@ffffff@@ffffff@$@ffffff@ffffff @@?@@"@@@?ffffff?333333??ffffff@@@?ffffff??ffffff@ffffff@!@333333@333333?ffffff?333333@333333@ @@@@?ffffff?ffffff?@ @?@@@@ffffff+@ffffff@@333333 @@333333@333333@ffffff#@333333??ffffff?@ffffff @?ffffff@"@ffffff@@@ffffff? @?333333@@@@333333+@@@@333333*@ @@@@? @333333@@?? @?ffffff?333333 @333333@?ffffff @@ffffff@333333 @@@@@?333333&@?@#@ffffff@?@ffffff@333333@@333333@?@?@?@@@ffffff@ffffff@??ffffff@ffffff@@@? @ffffff?@333333/@(@333333??ffffff??@?ffffff%@ @@?ffffff@@ffffff@ffffff?333333(@ffffff@ffffff@@@?ffffff?333333???ffffff?@333333@@?ffffff@333333@333333?333333?ffffff?333333@?ffffff@@ffffff@333333@@? @?@??@@@333333@%@333333?@쿚ٿ)ffffffffffffffffff 333333333333󿚙ffffff333333ffffffffffff333333ffffff ٿ333333333333333333 ffffff333333 333333(ffffffffffffffffff333333ffffffffffffffffffٿffffff333333*ffffffffffffٿffffffffffff ffffff333333ffffff*쿚333333ffffffffffff333333ffffffٿ333333 ffffff 333333333333 ffffff333333ffffff333333ffffffffffffffffffffffffffffff333333ffffff񿚙 333333ٿffffff濚333333333333ffffff 333333!333333ffffff333333333333 333333333333 333333333333  ffffffffffffffffffffffff333333ffffffffffffffffff333333"333333ffffff333333333333#ffffff?333333?ffffff@?333333??????ffffff??ɿffffff??333333ӿ333333?333333㿚ٿ333333?333333?ffffff??ٿ333333?ɿ?333333?ٿɿ????333333?ɿffffff??࿚????333333ӿ?ɿ333333ӿ333333333333?333333ӿɿ333333ӿ333333???ɿɿ333333??333333ӿٿɿ?ɿ?333333?ɿ??ٿ?333333?333333??࿚ɿ333333?333333ӿ333333ӿɿ333333???333333??ɿ??333333ӿ????333333?333333???ɿ333333?333333?333333?333333?ɿɿɿ333333333333ӿ?333333?ɿ??333333ӿ333333333333ӿffffff?333333?333333ӿ??333333?333333??ٿɿ333333??333333?333333ӿ333333?ffffff?ɿ?333333ӿ333333??࿚?333333?ffffff濚ɿ???333333ӿٿ?333333?ɿɿ333333?ffffff?333333ӿ??333333?ٿ333333??ɿɿ?ٿ333333??ffffff?333333?333333??ɿ???333333㿚?鿚?ɿ?333333ӿ?ɿٿ333333??ffffff??333333?ɿffffff?333333?ٿ333333ӿɿ??333333ӿ333333?333333ӿ?ٿ333333ӿ?ɿ?ɿɿ333333?333333ӿ?ٿ?ɿɿɿ????ɿ333333???ffffff?333333ӿ333333??333333ӿ?333333??333333?333333ӿ?ٿ??ɿ?333333??333333ӿ?ɿ333333?333333?ٿ??ffffff?ɿ333333ӿ??333333???333333?ٿ333333ӿɿ333333??ffffff?333333ӿɿ333333ӿ333333?ɿɿ333333??333333?333333ӿ?333333ӿɿ???ɿ333333?333333ӿٿ?ɿ?333333?333333??ɿ??333333ӿٿ?333333ӿffffff??ffffff?ɿ333333ӿ?333333?333333???ffffff??ffffff???ɿ?333333ӿɿffffff???333333??333333?ffffff333333?333333?333333ffffff?ɿ?ɿ?333333?333333?333333ӿ?ٿ??333333ӿɿ?ɿɿ?ffffff?ɿ333333?333333ӿ333333ӿ࿚?ɿ333333?ɿ?333333??333333ӿ??@ffffff??333333?ffffff??333333??ٿ?ffffff?࿚?ɿffffff?????ffffff濚????ٿ?333333????333333ffffffffffff?333333?ffffff??333333????@???333333?333333??ɿ?333333?ٿ?ɿffffff???ɿ???????ɿ???ffffff???333333?????ffffff濚?333333ӿ?ffffff???ffffff333333ӿffffff?333333?333333󿚙??333333???ffffff濚?333333ӿ?ffffff??ٿ???333333??333333?333333??ffffff?333333?333333ӿ??333333?ɿ??333333ӿ???333333?@333333???񿚙?333333?ffffff?ffffff333333???333333???333333??333333ӿ333333??333333??333333??333333???ffffff?333333ӿ???ffffff?ffffff?࿚ٿ?ٿٿ?ffffff???333333ӿ333333?ٿffffff?쿚?鿚???333333???333333???ffffff@?333333?𿚙?333333?ffffff??333333ffffff???ffffff?鿚ɿ333333??ɿ??@ɿ?𿚙?ٿ?333333?ffffff??ffffff?ɿffffff?333333??333333ӿ333333ӿ??ffffff?ffffffffffff333333?333333ffffff?ffffff?333333?333333?ffffff???????࿚ɿ333333???333333?333333??333333㿚???333333?ٿ???333333?333333ӿ?ffffff @ɿ???????333333????333333?ɿ?????333333?ffffff濚333333ӿ?鿚񿚙ɿffffff?ffffff?????333333?333333??333333333333??333333?????ɿ333333ӿɿ񿚙?ffffff333333333333ӿ333333??࿚??333333??ffffff?333333?333333ӿ333333?ɿ񿚙??333333????333333??333333?ffffff࿚??ffffff?333333ӿ???ffffff???333333333333??ffffffffffffffffff?鿚??333333ӿ?ɿffffff??333333ӿ333333?333333ӿ333333??333333? 333333?ffffff??ɿ??333333??333333?333333???ٿ?333333??ffffff?333333??333333ӿ333333?333333?333333ӿffffff?333333?ffffff333333,@ @""@ffffff @YC 333333%ffffff @ffffff5@ffffff333333@ffffff@ffffff?3333336@333333@333333(@ffffff?L?@',@fffff>@fffffA@7@333333@ffffff!@333332@333333?333333ӿ?@ffffff6@fffff@@ffffff???3333330@2@&@@ffffff?;@"@@0@鿚@(@ @333333'@@ffffff(333333&ffffff@ffffff ffffff)ffffff@*@333333#@@333331@ffffff.!@333330333333?333333󿚙@@ ffffff?ffffffٿ񿚙A@ @2@@ffffff*@333333?@$@ @?L0@333333%@%ffffff333333$@333333@1@ffffff!@@/(@/@@333333?@ @333333&@%@333333@333333,@@5@@ɿffffff?L0@ @ @?@#@ 333333?333333)333333?ffffff?ffffff)@333333@@"%@ffffff @333333)@@!@333333 @333333)@1@ 333332@ffffff@333333#@ffffff?"@)@333333㿚@/@5@@ffffff.@!333333(@333333>@3333335@#$@ffffff@ @5@&@ ?(@33333sM@&@<@ffffff@ffffff@333330#@ffffff=4@*@-@B@@5!%@=@333333 333333@;@)@ffffff@@333333?ffffff @ffffff)@ffffff @333333 ??!@(@ @0@ffffffffffff*L3@ 333333.@%@5@@ffffff?333333)@@333333'@)@ffffff @(@@ffffff2@ffffff:@3@333333 @333332@!@L6@333333@,@@@&@.@@333333ffffff??#@ffffff)@?@ffffff@0@333333<@@1@4@ffffff.@333333?2@3333337@ @@@333333? ̌D@)333334@6@fffff5@0@ffffff4@@>@@333333@1@ffffff@ffffff %@:333333@ @L9(@=@ffffff,@@.@LB@/@333333,@8333333㿚,@ @ffffff"333333333333#@!333333?@%@ ffffff @6@6#@333333+@@@1@ffffff!@ffffff!B@333333?333333,@ffffff@@ffffff @7@333333-@333337@&@?A@@@0@@333333-@2@ffffff@?'ffffff )@5@&ffffff@333333@\@LF@@.333333333333&@@ffffff(C@333333@@333333*2@333337$@?<@?333334@&@3333333@?ffffffffffff$ffffff@333333?@?ffffff'@ffffff!@8@ @@ffffff@&fffff7ffffff@ffffff"@L1@ffffff4@?$@3@,ffffff濚?L1@@ @L0@333333(8@ffffff@!@6@(@3333332@0@ Effffff333333@333333 ffffffffffff+@L6@#@!@$ffffff?333333@<@ffffff,@"?.@@*@ffffff#3333334@@,@<@$@3@L3@ffffff?@"@fffff2@ G@D@@@ffffff @@6@3333331@fffff3@&@fffff<@333333 @&@ffffff5@@ @ffffff4@333333.@333333.@@ffffff% -@ffffff333333 ?@ffffff @@,@3@1@?9@@&L4&@fffff2@333333@ffffff@?333333+ffffff,@333333+@+@@0@6@@ffffff@L0@333333@%@$@B@ffffff&,@333333@2@@ffffff-@'333333@6@@ffffff@333333)@$@5@L4@.@@ffffff:@??ffffff!@.@333333@@A@ffffff.!@?8@ffffff@333333"333333@333333"333333@ffffff(-@ffffff"@333333@fffff3@ffffff6@?333333@,@7@333333"@@333332@%@%ffffff(@0@@7@?ffffff*@333333㿚?'@2@@ffffff@333333*@ffffff6@333333@333333-@'0@@B@2@ٿ@@ffffff@(@333333@?3333330@L6@0@>@333333&@*@333333,ffffff/@*@ffffff;@)@/@ffffff6@%@@3333333333330@0@333333@333332@%333333@333333'ffffff$@3@ffffffL2@ffffff@4@ffffff?@ffffff333333$@,@=@!@"@ / @ffffff333333&fffff0@:@333333?ffffff@6@ffffff@ffffff-@333333@ffffff#@%@ffffff!@ffffff/@&@"@333333'@(@ffffff*@ffffff@ffffff!@333333:@!@L;@@@fffff;@ffffff:@,@ 333333 ffffffffffff1@+@YE@333333?ffffff@ffffff@'@ B@)@4@3333336@*@ٿ?@:@fffff0@333333@333333@@ffffff333333333331@ffffffL;@ffffff;@333333%@ A@5@#@?@ffffff'@33333B@(@ffffff-@:@333333ffffff@1@3333333!@1@ffffff/@)@333333"@&@3333338@fffff&F@ffffff033333;@?L1@ffffff@??333333#@333333)$@@(@@ffffffffffff&@5@ffffff4"@'@@4@333333,ffffff;@'@@3333333333339@ffffff333333@LB@333333@? @$@.@ffffff@333333@ @333333$@(@"@!@333333@ /ffffff"@>@ffffff#@333333,333333"@;@@@333333@ffffff%L0@ffffff"@ffffff!:@$@)@333333333333,@-0@)ffffffC@ffffffL;@$@333333;@ffffff@@@ffffff@ffffff-@333333?&@,)@333333$@ @@333333$333333@ffffff(@-@=@ffffff@ffffff'@ɿ333333)@333333@333333@:@?L0@ffffff @5@- C@333333-@@??333333?ɿɿ?ɿ??ɿ???ɿ?ɿ?ɿ??ɿ???ٿ??????࿚??ɿffffff???333333ӿٿ???333333ӿ333333㿚??ffffff?ɿ????333333ӿɿ???333333?ɿ333333??333333ӿ?ɿ??333333??333333ӿٿ333333㿚ɿ333333??ɿٿffffff???333333??ٿffffff࿚?ffffff??ٿ???333333ӿɿ?333333ӿ?࿚?ٿ??333333ӿɿ?࿚???333333?333333ӿ??ɿ333333?333333??ɿ333333????ٿ࿚ٿɿ???࿚??333333333333??333333ӿ?࿚333333?ٿɿ?????鿚?333333ɿ333333ӿٿ?333333?333333???ffffff?333333?ffffff???ɿ?࿚ɿ333333?ɿ333333ӿ?ɿ333333????࿚ɿٿ?ɿffffff?333333???333333㿚?ٿ333333????333333??ɿffffff濚?ffffff??࿚???????ɿ333333ӿ333333?ٿ??333333ٿ??ٿffffff??ٿ333333ӿ?ɿٿɿ333333??࿚?333333㿚ɿ??333333?ffffff?ٿٿ࿚???333333ӿɿɿٿ?ffffff??333333ӿٿ?333333?333333㿚ffffff?ffffff?ɿ?ɿ鿚???333333?333333㿚??ffffff????鿚ٿ333333??ffffff333333ӿ333333㿚?333333?@333333?쿚ɿ?鿚ٿ??࿚?333333ӿɿٿ?????ffffff???333333ffffffٿ333333ffffffٿffffff333333333333ffffff333333ffffff333333333333쿚ٿ쿚333333ffffff ffffffٿ333333333333ffffff333333ٿٿٿ333333ٿ333333333333ffffffffffff333333333333333333ffffffffffff쿚333333333333 ffffffffffffffffffffffffffffff 333333ٿ333333333333333333ٿffffff333333333333333333333333㿚ٿ333333ffffff333333󿚙ٿ333333333333ffffff333333333333ٿ333333 333333333333ٿٿ?@@ffffff@fffff3@333333@333333@@!@@?+@ @333333@333333!@@ @333333?333333@@@@!@333333?!@ffffff@ffffff?@ffffff@@333333 @@ffffff @@ffffff??@@@*@ @333333?333333@@? @ffffff@333333@+@ @@ffffff@333333@333333@333333!@@333333@@&@333333+@ffffff @333333@#@333333@? @@@ffffff?@#@@@333333'@@@#@@@ffffff@ffffff@/@@ffffff@@ffffff@333333???ffffff@333333@ffffff @333333@ @@@ffffff)@ffffff"@@@@ffffff @ffffff @3@@@@@ffffff$@ffffff@ffffff@ffffff@??333333@333333@??ffffff@333333@@@333333@ffffff?ffffff?!@@@ffffff@333333@"@#@333333@ffffff$@@#@@?!@ffffff@ @ffffff#@@ffffff@@@ffffff@ffffff? @ffffff@@@@@?333333@333333@ffffff@333333@!@@?@@ffffff@??@@@?333333@@@@@ @ffffff@?333333?ffffff@?@?@?@@@*@ffffff@1@ffffff@??ffffff?@)@ffffff#@?333333@,@@'@"@?/@@@ffffff@?@@?333333?@?@333333 @@@@ffffff$@ffffff@@@$@333333-@ffffff?333333@333333@@@@@?333333 @@?333333$@@333333?ffffff @ @??333333 @333333@@@ffffff@333333?ffffff?@ffffff@@ffffff@@ffffff?ffffff@ffffff @@ @ @@@@@@??ffffff?333333?@ffffff?333333$@333333@@?(@@?@,@?ffffff @@? @??@@ffffff$@@?ffffff@@ @@@@333333 @@333333@ffffff@333333?ffffff@$@@? @??333333@@ffffff?@&@@333333@?ffffff@ffffff"@ @@333333!@@333333@ffffff@333333@ffffff@@?@?ffffff@@@@333333?@?@ffffff@333333 @ @333333@ffffff?333333@?333333@?ffffff)@@ffffff@ffffff@@ffffff@ @@333333??@333333@333333 @@#@@@&@ffffff@ffffff$@333333@ffffff @@@ffffff @*@@@ @@?333333?ffffff@L0@@@333333@333333@333333@333333@333333@333333?@?333333(@@!@ @333333-@333333@@ @ffffff@333333 @%@333333@@@333333@ @@@0@ @ @ffffff?@@ffffff@333333@@@ @333333@333333@@@@333333@?333333#@ffffff?ffffff@@ffffff333333ffffffffffffffffff333333333333333333333333ffffff333333 333333𿚙# ffffff333333㿚 333333 333333333333 ffffffffffff 333333333333333333ffffffffffffffffffffffff333333 ffffffffffffٿffffff𿚙 ffffff 333333333333333333󿚙 ffffff  ffffffffffff 333333ffffffffffff 333333ffffff333333333333333333 ffffffffffff ٿffffffffffff333333333333ffffffffffffffffffffffff ffffffffffff333333 ffffffffffff ffffff333333333333333333333333 333333ffffff'333333333333333333㿚333333333333$ffffffffffffffffffٿ333333333333 333333ffffff #333333ffffffffffff ffffffffffff%ٿٿ333333!333333333333ffffff 333333㿚.?333333"@??????333333@ffffff???ffffff??ffffff ?ɿ333333?333333?ffffff??ffffff??????ffffff?333333?????ffffff????ffffff?333333?ffffff??333333??333333?333333??ffffff??ffffff??ffffff?ffffff?ɿ?ffffff???ffffff??333333???333333?ffffff?ffffff?ffffff?ffffff??333333?333333??333333???333333??333333?ffffff?ffffff????333333??ffffff??ffffff?333333?ffffff???333333???????????ffffff??ffffff??333333??333333?ffffff?ffffff?333333?ffffff?ffffff?ffffff???333333?ffffff??ffffff?𿚙?????333333??333333?333333?ffffff?ffffff?ffffff?333333???333333?ffffff??????ffffff????ffffff??333333?333333????????333333?????ffffff?333333?333333????????333333??333333?333333?333333???????ٿٿ?333333?ffffff????ffffff?333333??333333??333333????ffffff???333333??333333?@ffffff࿚?333333????ffffff?333333㿚??333333????????333333????????333333?????333333?333333???333333????ɿ???????333333?ffffff?ٿ?????ffffff???333333?ffffff??333333????333333?ɿ333333????333333?333333??ɿ??333333??333333ӿ?333333?333333??333333????333333??ffffff?????333333?333333??????333333??????ffffff333333?ffffff???333333?333333???ffffff????333333??ٿffffff@??????????????ffffff??333333?333333ӿ??@??????333333???333333?333333??333333?333333???ffffff??333333????333333???????ffffff?333333?ٿ??ffffff???????ffffff??ٿɿ333333?????ٿ??333333㿚???333333?333333????333333?333333????333333??ffffff??333333??ffffff?????333333?ffffff?333333???????ffffff???ffffff?333333?333333?ffffff @ffffff?333333?ffffff@ffffff??333333????ffffff?@?ٿ??ffffff?333333??333333?@??333333?ffffff??ffffff??ffffff???333333?333333?ɿ???333333????333333ӿ333333?333333?333333?ffffff?333333??333333??333333??ɿ333333?ffffff?333333?@@ffffff??????333333????@ffffff @ٿ???333333?333333??ffffff@ffffff??333333?ٿ???333333?333333??ffffff@333333㿚ٿ?ffffff??@?333333???333333󿚙????ffffff@333333???ffffff????ffffff?????????@333333??333333??ffffff?@@??ffffff??ffffff??@?ffffff???ffffff??ffffff?????333333??333333???ٿ@???333333?@333333????@333333??333333?ffffff?ffffff??ɿٿffffff@333333???333333???333333??333333???333333???333333??ffffff @@???ffffff@@@ffffff?333333?񿚙?ٿffffff???@333333???ffffff????ffffff?333333@333333?@333333?ffffff?333333??333333???333333??333333?ffffff?ffffff???ɿ333333?333333?@????333333???ffffff???333333?????@333333@?࿚?ffffff?333333?333333?333333?????333333????ٿ333333?ffffff @?鿚?ɿ333333??333333???ffffff?ffffff?333333?333333?ffffff@?ffffffffffff@???ffffff????333333??333333?@??333333??333333??333333???@??333333?333333@??333333?ffffff?ffffff???333333?333333?ffffff???ɿffffff???ffffff?@?@ٿffffff@?ffffff???333333?333333??333333?333333??@ffffff@333333?333333??ffffff???ffffff࿚@ffffff@??ffffff??@333333??333333??ffffff?ffffff@?333333?ffffff?333333???@??333333?ٿ?333333333333??࿚?ffffff?333333@333333󿚙ɿ@?@????ffffff?333333?ffffff@333333???@333333????333333ӿ333333?333333??ffffff??ffffff???333333ffffff@L?@#@ 333333D@ffffff,@ffffff-@@33333:@ffffff2@ffffff?$;@@@@fffff3@%@%@ffffff&@2@5@?5@333333333334@333333 @33333383333331?#@ @@L3@#@&@?fffff&A"@/@3@333333?@ @ @,@@@fffffB@333333+@333333+@(@333333&@ B@333333,@ @333333-@7@YE@@@33333<@333333:@333333,@333333!@333333*@333333'8@,@ffffff0@333333@33333?@6@ffffff7@1@ffffff$@333333333333?@4@333333E@333333/@3@*@@ffffff?333333333333@!@$@333333@,@쿚YF@@4@/@@@ffffff&@B@ffffff$@ffffff@2@ffffff9@4@?,@3333333@@333333?ffffff2333333? @0@333333!@333333@ffffff33333:@333333+@ffffff2@1@333333&@YE@fffff;@@8@333333%@ B@ffffff"@>@??3333334@@333333@&@"@333333@ffffffffffff@ffffff3@@3@L6@5@3333335@ffffff3@@8@-@ffffff"333333)@ffffff@?333330@*@0@ffffffY@@5@333333:@@@%@%L3@@#@=333333 333338@%1@333333#@ @D@E@333337@??)@C@33333sA@333333(@33333I@4@@@333333@ P@@?333333/@@?333334@@ffffff:@@333333%@ffffff,@&@333333@?fffffA@(@@333333.@E@D@?3333339@#@ffffff@4@ffffff,@?333333@%@3333332@ 333333<@333333#@ffffff ffffff@ffffff41*@?ffffff$@-@ffffff1@ffffff 𿚙)@ffffff2@333333/@@1@ @ffffff:@ffffff1@$@@333333@?@3333335@333333/@5@333333(?ffffff &=@3333331@@3333331@333333@11@ F@333333@@"@333333.@<@333333@?3@L2@ @@@@333330@333333>@ffffff"@fffff1@@333333@3333334@@2$@ffffffL@$@.@?L4@ L@3@@@333333(@6@333330@333333@3@@333333 @ffffff@L0@L0@1@ffffff333333=@ɿ@@3333333@$$ffffff:@@4@9@333333<@ffffff-@@(@ffffff@ffffff?@@'@333333 @@/@)@ɿffffff7@3@"@@L;@ffffff7@@L>@ffffff>@0@fffff0@33333A333333ffffff@3333339@ffffff6@33333H@*@-@ffffff$@333333(@=@L0@<@ @(@L7@.@1@ffffff,@333333 @'@L=@fffff9@A@3333335@fffff=@ٿ@333333 @333333/@)@L7@ffffff,3333331@333333#@333333@7@333333-@)@3333333333333#1@!@33333;@333333?̌@@ffffff6@3@"333333@333333@(@)@'333333B@F4@%@ffffff'@YE@333333)@ffffffYP@1@0@6@333333>@3333339@%@fffff&A@33333D@1@ B@333333)@ffffff @33333:@:@<@333333?333338@ffffff?333333?@333333.@fffff2ffffff0*@ffffff4@333333%@#@8@(@L:@@A333333%@3333331@333339@33333B@333333?333333@ffffff@5@@ffffff$@@E@L1@333338@-@333333󿚙#@F@3@0@>@D@L@#@@fffffC@>@?6@.@2@333333 ffffffD@ D@*@?@E@8@ffffff8@333333;@ffffff'@@LH@333333+@G@333331@ɿ33333;@?@@ffffff$@333331@%@L<@?333333%@@LH@333333@33333E@fffff2@333333@@0@8@.@333333 &@;@333333F@L7@ @333333@?9@ffffff,@333333@ffffffffffff*@@333333;@333333@L0@ffffff$@@33333sH@1@ffffff5@B@fffff5@33333I@F@1@<@333330@fffffF@4@@@B@ffffff0@ @>@333333"@#@ffffff/@L6@"@?!@<@+@5@fffff9@L?@@33333s@@33333sF@.@ffffffB@L1@3333332@.@ffffff'@@#3333337@4@7@?33333A@8@7@?@&@333333@ffffffffffff?0@333333&@ffffff@ffffff% ffffff:@ @333333(@333333,@33333H@333333?@@@ٿ@ffffff L8@YN@H@@8@33333M@K@fffffFQ@fffff9@333333@H@鿚$@2@ @333331@'@333333333333A@3333334@1@3333330@-@!@333333 @ F@-@ffffff#@fffff&C@@F@ffffffO@ffffff!@333333A@ffffff,@333333'@ffffff6@3333333@?ffffff@ffffff@B@(B@3333332@?ffffff?ffffff??*ɿL1@333333ffffff.@2@6@ffffff%@4@7@4@!@5@?333333ffffff"@Y@@C@333333;@'@?@333333(@6@333337@3333339@@ffffff @333333ffffff?33333@@L:@ffffff@@E@#@fffff:@H@333333-@ @'@333333ӿ@333333?D@%@333333@333333@33333A@2@ @3333337@ffffff"@6@ A@/@ffffff=@+@@6@333333@ffffffffffff(@@L0333333,@8@@L5@33333O@5@J@ffffff=@,@<@33333A@@33333:@"@(@%@ffffff<@fffff6@5@333333@ @@@.@ffffff@;@ 33333A@333333*L8@.@6@33333:@L@@ffffff@L9@ffffff@#@fffffC@ffffff&@ @6@@3333331@ffffff6@LC@@#@Y@@ffffff.@$@333333,@2@@H@<@L=@3@2,333332@LI@L8@K@0@3333336@3333334@&@7@fffffE@(@ffffff7@ G@6@8@2@?*@8@ @@D@ffffff0@*@ffffff?#@6@ @ffffff'@A@'@YO@8@333333,@"@333333@@ffffff@3333332@+@33333303333333@fffff6@33333sA@333333?fffff&A@fffff&@@333334@ffffff%@333333-@6@L6@&fffffH@33333Hfffff:@6@??鿚???ffffff???ffffff??333333??333333?333333??333333?ffffff??ffffff????333333@??????333333????ffffff??ffffff??333333??333333???????333333??????ffffff?????333333??333333????ffffff?333333????333333?333333?333333??ffffff?????????333333?@?333333?@@333333????333333??333333???333333?ffffff????ɿffffff????ffffff????333333?ffffff???333333?????????333333?????ffffff?@???ffffff???ffffff?ffffff????333333??333333?????쿚?ffffff???ɿ??????ffffff??333333?ffffff?333333??????ffffff????333333???ffffff@??ffffff?333333??333333????????ffffff???@??333333333333??ٿ????ffffff????????333333????ffffff??ffffff?333333???????333333???ffffff????????ffffff?333333??333333?ffffff???ffffff??ffffff??????????ɿffffff?????333333??ɿ?333333???333333?333333??333333333333???333333?ٿ?????ɿ???????333333?333333??333333???@??????????333333??ٿ??333333??????333333?333333??333333?????333333??@??????????@??333333@??????ffffff??ffffff???333333??0@??ffffff???ffffff@????333333??ffffff?ɿ??@??333333?????????333333?333333??@ffffff???ffffff?333333?ffffffffffff?࿚?ffffff?????333333?333333?333333?333333?333333?ffffff???333333????$333333"L2333333333333 333333ffffff333333ffffff%"333333!%%333333,'333333 ffffff ffffff ffffff333333333333$ffffff!ffffff#(1333333333333333333333333 333333+333333333333$3333331ffffff333333!333333 ffffffffffff333333333333ffffff) ffffff333333"&ffffff(%3ffffff!%ffffff333333?ffffff?@????333333?333333??ffffff @333333 @ffffff???@@?@??333333?333333???333333????? @333333 @????ffffff?ffffff@333333@?@?@333333??ffffff?ffffff?333333??333333???ffffff@ffffff?ffffff@???333333?333333@@333333??ffffff?ffffff@@ffffff???333333?ffffff@333333@??@333333@ffffff?ffffff?ffffff???ffffff?333333?ffffff?? @ffffff@@333333@ffffff@@?????@ffffff???@333333?333333??ffffff@ffffff@??@333333?@?333333?ffffff@??ffffff?ffffff?333333?ffffff?333333??ffffff?ffffff?ffffff??333333?@333333@ffffff@??@?333333 @333333@?? @?333333?ffffff@?ffffff??ffffff@@ffffff?333333@?@333333?ffffff?ffffff??ffffff@??@@?333333?????ffffff?@?333333%@ @333333?@?ffffff@333333??@@@ffffff???ffffff@@???@?? @?ffffff@?ffffff?ffffff@?????@ @??ffffff@?@ffffff??@ffffff?ffffff@333333?333333?ffffff??333333??ffffff??333333??333333??@???????ffffff@? @ffffff?@ @ffffff@fffff8@ffffff?@@???333333@333333??333333??ffffff??ffffff@ffffff$@??&@?333333@?ffffff?@?333333????@?333333?ffffff@ @?@ffffff??ffffff???ffffff@?ffffff?ffffff @ffffff?333333?@333333?333333@???333333?ffffff?333333?@?333333 @ffffff@?@333333??333333@@???@?@?333333??@333333@ffffff?@@?333333?@ffffff@??@:@333333???333333@??$@?ffffff@333333?@@@@ffffff????ffffff???333333?ffffff??@ffffff?333333?@ffffff?@ffffff?333333??@@ffffff?@???@?@@!ffffffffffff8'333333!$ffffff+ffffff!333333$"'ffffff*")333333"ffffff333333!ffffff$333333-ffffff L0333333)"$ffffff/ $ffffff333333%$ ""ffffff3ffffffffffff333333333333333333ffffff 333333,333333'ffffffffffff333333&ffffffffffff.$333333333333 333333 333333"333333(333333333333$333333!ffffff&333333!333333%!333333ffffffffffff"ffffff333333" ffffff$ ffffffffffff"333333ffffff(#ffffff333333&333333 333333ffffff)333333L0!ffffff!"0ffffff"ffffff&333333333333 (L2333333ffffff3333333 +333333!ffffff333333ffffff+333333333333 ffffff !333333 ffffff(!0&333333&ffffff%(333333!ffffff#!333333,ffffff(&&ffffff-333333 ffffff*333333""333333!ffffffffffffffffff#"333333333333+ffffff(&ffffff 333333$333333% ffffff7ffffff 333333'%ffffff333333 333333333333#333333333333-ffffff333333,ffffffffffff 333333#333333)ffffff!ffffff333333"!333333ffffff '333333ffffff*6333333 ffffff(333333&"L1333333ffffff 333330#!ffffffffffff $ 333333*"fffffB333333#ffffff ffffff0ffffffffffffffffffffffff!333333"333336ffffff333333*333333)ffffff$ffffff ffffff#0"333333&#3333334ffffff'333333333333" ffffff!ffffff333333/-333333%#ffffff$333333ffffffffffff!333333333333ffffffffffff(ffffff @ffffff????ffffff@??(@?@ffffff @??ffffff?@??ffffff?ffffff??????ffffff?333333??@333333@333333"@333333@?ffffff???@@ffffff @3333333@???333333@ @333333@333333@333333)@ٿffffff333333?333333?ٿٿ333333333333ӿ?ffffff濚?ٿ鿚?333333㿚?ɿ333333㿚?333333333333???333333333333㿚??࿚?ɿ?333333333333ٿ333333ٿ쿚ٿffffff??࿚?333333?࿚???333333ӿ333333333333?ffffff?333333333333࿚ffffff濚ٿ333333㿚ffffffffffff濚333333㿚鿚ٿ333333333333㿚ٿ񿚙??333333??333333󿚙ffffff333333333333㿚񿚙鿚??鿚???333333?࿚333333㿚??ffffff333333333333ӿ333333ӿ333333?333333?333333ӿffffff?ٿ333333333333㿚?ffffff濚?333333?鿚?333333ӿffffff濚鿚ٿ333333333333?ffffff濚񿚙ٿٿ333333?࿚鿚?ffffff濚??333333333333ӿffffff𿚙??333333?333333㿚?鿚??333333 333333ӿ??ٿ333333?333333ӿffffff???࿚?333333㿚?333333㿚??鿚?333333?333333?333333?鿚ٿ333333333333󿚙鿚ٿffffff333333?333333?333333ffffff濚ɿ333333??ffffff࿚鿚???333333333333??𿚙?333333㿚?333333ӿ??ɿ333333ӿ?333333??333333㿚ٿ?ٿٿ?333333ӿ?333333ӿ࿚?333333ӿ쿚?ffffff333333ӿffffffffffff濚?𿚙??ffffff333333ffffff?333333?333333ӿffffff333333㿚333333ffffff333333333333333333333333ffffff濚ɿ333333333333ffffff濚ɿffffff333333ӿ333333??333333ffffff333333ӿ333333333333㿚?333333󿚙𿚙ٿffffffɿ??ffffff濚?333333?ٿ333333??𿚙ٿ鿚??333333?ٿffffff?ɿ𿚙ٿ?ٿ333333?ٿ࿚鿚??333333ffffff濚?ٿ?333333㿚333333?333333ӿ??333333?ɿ?333333󿚙???ffffff333333ffffff333333333333?333333㿚ٿ?ٿ333333㿚?333333ӿɿ333333ffffff濚?ٿ333333??333333?ٿ?ffffff??333333333333ffffff ٿ࿚?333333???ffffffٿٿ333333ӿ?ٿ?ffffff@ffffffffffff?ffffff?쿚@333333?@??333333ӿٿ??𿚙333333?쿚ɿ333333?ɿ鿚?ffffff333333ӿ?@333333ӿffffff?ffffff?𿚙?333333쿚?ffffff?ffffffffffff濚ɿ?333333ӿ?࿚??ffffff?ffffff?ffffff濚??@@@@333333 @ffffff?쿚?333333?333333333333ɿ333333?333333333333ӿ?ffffffffffff@ffffff@?ɿffffffffffff?333333ffffff???333333?ffffff?ffffff?333333???333333㿚ɿ333333?333333?񿚙ٿ?333333333333ffffffffffff濚?쿚?ffffff?ffffffffffff?333333?333333󿚙ɿ?333333?ffffff@?ffffff@ٿffffff?333333??333333?ٿffffff@ffffff?333333ӿ??@333333󿚙ɿffffff??333333ӿffffff?333333?ffffff?@333333ӿ333333㿚@333333ffffff 333333ӿ@?333333?333333?鿚?ffffff濚ɿ333333ӿ鿚333333333333??333333?333333?@@?ffffff????ffffffffffff@333333??333333????333333???ffffff?ffffff@333333???ٿ@ٿ333333ӿ333333쿚?333333?ffffff???ɿ??@?333333?333333?333333?ffffff?񿚙ٿ??ffffff?@333333333333333333@ffffff?333333ӿ??ffffff@ɿ@333333ӿffffff濚ٿ? 333333???333333??@333333ӿ࿚????ffffff?ٿffffff?333333333333?@?333333?ٿ?333333ӿ??333333??ffffff? @ffffff????ffffff@333333??ffffff?ffffff?333333?@𿚙ɿ@ٿ333333??ɿ333333ӿ࿚񿚙???@?333330@ffffff?ffffff?ffffff?@333333@?ffffff333333ӿٿffffff?333333?333333ffffff333333??333333ӿ?333333@?333333?333333333333ӿffffff??333333?ffffff࿚?333333󿚙?𿚙333333?ffffff?ffffff?333333?ffffff333333࿚???@ffffff??85333332@33333329Nfffff>/-:"333333"333333433333<2333333?Y@333333'3L4333337`Qffffff2233333;@%33333373ffffff?753333331ffffff3fffff9333333"1L674(333333/3333333ffffff8F%*@333333@7fffff9̌DL74fffff18@!-9fffff6ffffff+fffff14;ffffff 6>5ffffff*4?33333313333301ffffff4@AL=;ffffff@8`RffffffN)1fffff&Lfffff0YG0"W33333s@333333&2@ffffff%#E333333 /fffff43333330ffffff52333333 ffffff(𿚙43333313333331ffffff6#13333337ffffff)H5>333330333333.5333333?333333(#15E333333451333333,L7333333D35933333<< H>ffffff2-=ffffff&333333333333&ffffff )ffffff/,'3ffffffBL93333332ffffff833333<333333!333333*3333323333334:L;2ffffff59L5,@@,?33333:fffffp@ 333333/,3333332);333333,233333sS82333338333333B333333Bffffff>@ffffffE@ffffff+.ffffffB@ffffff,"ffffff+333333ffffff233333Cffffff$33333B,333333'ffffff$3333336:ffffff4ffffff?333332࿚1%fffff@ffffff+-333333,?333338333333"333333$3333335YC1=ffffff6=.>33333R6333333BY@@0fffff@3333333ffffffI3333338lV.333333Bffffff:ffffff1+M@333333fffffDL9&?YB8fffff;k93333336,@0fffff\!#@: L7333333ffffff:fffffE@ffffff.+4K%fffff4333333 YA1$333333@0333333@ffffff+:ffffff%@3333338, Wfffff=/1@ffffff<2@ffffff$@1333333$F0L?5fffff2333333$333339L9 @3333336333333*623333337@3333333333M+ffffff0333333@33333CL;&(333333753333336ffffff81DYIffffff6333333?ffffff)ffffff+3333333 D333333*&&49333333'3333382;ffffff/@B@33333393333332ffffff10<(333333,&@YD3%3333339:@fffff&Ě@33333sB@ Ffffff6ffffff8ffffff6fffff<333333?ffffff;fffff<" B333333333330ffffff?A1;:&ffffff*-0#C333333'@333333,fffff>6@C6(3333332LA@L=7ffffff$33333s@/8̬T3333332333333*D33333@!3333333ffffff)ffffff* F333333fffff8ffffff*@A@9#@fffff7333333?333337333333#7333333(ffffff-ffffff-ffffff<333333-ffffffL=ffffff@;33333:'!333333$@ L?ffffff;쿚YA3333339!ffffff5(ffffff<@=.333333-333333@*ffffff2fffffL2",ffffff*ffffff,333333$/333333!333333)"1.B@fffff3*-333333&@333333 0fffff13YEfffff5:6!33333327@#33333sB333333"@33333n@33333382@ B333333/L2333333?fffffAffffffM:ffffff@9HCL7@ffffff1,@333333@$@7 ffffff5@%ffffffffffff,%@3333330ffffff? 3333331fffff0?ffffff5333333/@LBffffff/ ffffff4fffff0333333/̌C'@333333ffffff@333333ffffff7ffffff*=@333333ffffff,ffffff3333335̌KL297 33333343333337ffffff33333s@@):@28ffffff/L4!ffffff1L2A@?AL0' DYE333333?91@40L<$!L5@ffffff$"L=333333(-fffffB333333&?33333333ffffff9333333Bffffff)Z?8,=@33333F2A333333&ffffff:5333333E33333?333333 fffff7333333@>333333%!@3ɿffffffA3333333333335@L0@?@ɿ?333333??ffffff?????ffffff@333333??333333@?@333333?333333??333333???ffffff?333333?333333???ffffff???333333?333333??????ffffff?????@???333333??????333333????????333333??333333??333333?333333???333333????333333???333333?? @? @ffffff@@333333??333333?333333?333333ӿffffff??333333??333333??ffffff?ffffff?????333333??ffffff???@?333333?ffffff??333333??ffffff????ffffff?????ffffff??????333333?333333??333333?333333??333333?333333?@ffffff???ffffff??????333333?333333?333333???????333333?333333?@@333333??ffffff@???ffffff?333333?333333?@ffffff?333333??333333ӿ@333333?333333?333333@ffffff??࿚?????333333@?ɿ?333333?@ffffff??@???@ffffff@???333333??333333?ffffff???333333??@???ffffff?ffffff@333333?333333???ɿ???333333??@?ffffff??333333?ffffff??@?ffffff???333333?ffffff???????333333???333333??ɿ?333333???333333?ffffff?333333???????333333?333333??ffffff?333333???ffffffٿ?@333333ӿ333333??ffffff?? @????????ffffff??ffffff@@333333???333333ӿ???@ffffff@?333333????ffffff?@?333333?????@??333333??333333??333333 @ffffff?ffffff@333333?333333?333333ӿ@????@@ٿffffff? @????333333?ffffff?333333???ffffff?@?ffffff?$@ɿ?@@?@@?@???????ffffff@333333????333333??@333333??ffffff??ffffff@???@@???ffffff?ffffff???ffffff @ffffff??????ffffff???333333@???333333?ffffff@ٿٿffffff333333!(*=3ٿffffff-L>333333*333333 ffffff!ffffff(1ffffff :.2ffffff333333533333:333333ffffff+@@@333333??'@ffffff@ffffff?333333?@@ffffff @ffffff@!@ffffff@@??@@333333@@@@333333 @?@???@?@ffffff?ffffff@@333333?333333 @@333333?@@@333333 @@?ffffff??333333@!@ffffff?333333@?@ @ @ffffff @@333333@ffffff@?@?@@333333@ffffff @ffffff@ffffff@@333333@ @@@?? @?!@333333'@@???333333?ffffff@@333333@333333@ffffff@?@$@@@@333333@ffffff@ @333333@333333.@ffffff@?333333 @333333@ffffff@ffffff@@ @333333 @@ @??ffffff?ffffff@333333??@ffffff @@@ffffff@ffffff@@ffffff@ffffff#@@@@@ffffff @@333333@@#@ @@@@333333@@ffffff?@333333? @ffffff@ffffff@333333?@@"@ffffff@@ffffff??@@333333!@ @333333@ @ffffff@ffffff@@@@ffffff@?@333333@333333@333333@?333333'@ @ @@??%@ffffff@@ffffff@ @@ @@ffffff@333333@ @ @ffffff@@333333?@?2@@@@ffffff@333333@333333? @?@@@@ffffff?@@333333!@,@333333@ffffff@(@333333@@333333%@ @#@@?@@ @333333?@333333?@@@333333@ffffff@333333!@ @??@333333@?!@?@@333333$@@@ @ @333333@@333333@333333 @@@ffffff@?ffffff@333333@ffffff?ffffff@@@ffffff @ffffff?333333@ffffff@@333333@333333?!@333333,@(@@!@333333@@ffffff?333333@@????@"@ffffff@@?@ffffff@@@ffffff?@333333 @@ @@?@ @333333"@ffffff@ @333333@ffffff@@?333333@@?@?$@ffffff? @@ffffff @"@@@!@333333?@ffffff@@ffffff@L6@ffffff @ffffff@@@ffffff@ffffff@?ffffff)@@333333?333333?@)@ @??@ffffff#@@@333333@@333333@?333333@ @ffffff@ @@@ffffff@@@ffffff@@ffffff@@%@ffffff@!@@ffffff?333333 @@?@<@?ffffff$@333333@ffffff?ffffff@ @ @@ @333333? @ffffff@ffffff@@ffffff@ffffff@?ffffff@'@@@@ @333333 @@ffffff@333333 @@ffffff @@333333@333333@ffffff?ffffff??ffffff @ffffff3@333333@333333'@fffffffffffE1!!) 333333'ffffff#*ffffffffffff#ffffff333333%ffffff%333330'333333!ffffffffffff333333&ffffff&ffffff" 333333!333333(333333'!333333ffffffffffffffffff,ffffff!333333ffffff)ffffff!0.%ffffff&333333333333$&333333ffffffffffff'&ffffffffffff"% )ffffff#'$)70ffffff, ffffffffffff& ffffff !333333 333333-ffffff*'ffffff+333333333333(333330333333#50ffffff2ffffff"333333 333333 !ffffff333333ffffffffffff"333333& ('333333+-!)0ffffff333333"#333331!ffffff"333333!333333333333"333333 +$ffffff&ffffff/!ffffff'"4ffffff&333333333333%333333!333333"ffffffffffff+L1,333333(4&333333(,333333+$- @A/333333L2333333fffff7)3333331)ffffff)fffff5ffffff!$ffffff 333333  333333#+ffffff333333)ffffff"L=333333)ffffff$$-ffffff 333333ffffff%''1ffffff)"/2$/0333333333333ffffffffffffffffff 333333"ffffffffffff"ffffff% ffffff'#333333%#!$3333331ffffff!ٿ*#$333333333333! ( 333333,"ffffff )%(&22ffffff 333333 ffffff'" /.ffffff333333ffffff&*ffffff333333*333333 )+333333&ffffff333333')ffffff,/ffffff-7>!2ffffffffffff333333ffffff-*#-;%ffffff(ffffffffffff# $3333335+'=333333 fffff0Y@ffffff,$333333'L2ffffff%+ ffffff#333333/ffffff#3+ffffff+3333335+.333333#0333333$(ffffffC(ffffff!333333"333333333333,333333%ffffff.333333+ffffff$")ffffff*333333 ffffff !ffffff,@@@???@333333 @?@???333333 @333333?@?@@@ffffff?ffffff??@ @-@?@?@????ffffff濚?333333333333?࿚?ɿ??333333?࿚鿚ٿ333333?ɿ333333ӿ?333333ӿ333333???ɿ????ٿ333333???ffffff333333?ٿ333333ӿ?333333ӿ?333333ӿ?333333?333333ӿ࿚?ffffff濚ٿ???ɿ???333333??333333?ɿ333333?333333?333333?333333?ٿ࿚??ٿ333333ffffffɿٿٿ333333ӿɿ333333?ɿ333333?333333?333333ӿ333333?ٿ333333??ffffff333333?333333㿚ɿ?333333ӿ333333?ffffff?333333ӿ?333333?333333ӿ?333333㿚?ffffff?ɿٿ????ffffff濚ٿ333333?࿚?ffffff濚????333333???ffffff?333333???ٿ333333??333333ӿ333333?ɿ???333333??333333333333??333333?333333?࿚?333333?ɿ?ٿ??333333?333333?ffffff濚ɿ?333333??࿚???ffffff쿚?????ɿ?333333?333333???333333ӿ?ٿ?쿚?333333?333333ӿ???333333?쿚ٿ333333ӿ?333333ffffffffffff濚ɿ?𿚙?࿚?ɿ333333ӿ333333?333333?333333㿚?ffffff濚ٿffffff????쿚?333333??ffffff?333333???333333ӿ?333333?333333??ɿ????ɿ?ٿ?333333ӿffffff333333?࿚????333333????333333?333333ӿ??333333?ɿɿ333333㿚?ffffffffffff濚ٿ𿚙ٿ?333333㿚?333333?࿚ɿ333333ӿ?333333?333333ӿ?ɿٿ?ٿ333333ӿ??ffffff濚?333333ӿٿ?333333ӿffffff࿚ٿ333333?ɿ333333?????333333???333333?333333?333333?񿚙?쿚?333333???ٿ쿚񿚙??ٿ?333333?333333㿚??ɿ?ɿ333333??鿚?ٿ??𿚙??333333?ffffff濚??????ٿ?333333㿚?333333??????࿚ٿffffff??ɿ333333333333??333333ӿ??ɿ??ffffff????ٿffffff @??333333@@333333333333@@333333 @@@@333333@333333??333333 @ffffff@?333333?ffffff@?@ @@??ffffff@@ffffff?@?333333@333333? @ffffff@@333333@ffffff?@@@ffffff?333333@ffffff@@333333?333333 @? @@@?@@ffffff@@333333@?@ffffff@@@ @333333?ffffff?@??@@@333333@@@@ffffff?ffffff??ffffff @@@ffffff@@#@@@$@@333333?@@ffffff?@@333333????@?ɿffffff?@@??ffffff@333333@ @@333333@@333333@@@ffffff@@333333 @333333?@333333?@@333333@ffffff@333333@ @??@@333333?@ffffff @??@@333333@@@@333333@ @ffffff?ɿ鿚 @ @??@333333?333333? @??@@@333333ӿ333333?@@?@333333?@ffffff@ffffff??333333 @ @@333333?@@?@?ffffff?ffffff@?333333@@333333?ffffff?@?@@?@@333333@ɿffffff@@@@ffffff @ffffff@ @ @鿚 @ffffff!@ffffff@ffffff@ffffff@??@?ɿ @@@@?333333@@@@ffffff?ffffff @333333?@ffffff@?@񿚙@ @ffffff? @?ffffff???333333 @333333@?@ffffff@333333333333?333333 @@333333 @@333333?@@ @ffffff??333333@?ffffff? @ @@333333 @@ffffff@?333333??@333333㿚@@333333@ffffff@ffffff@ @@333333?333333@?@??@@@?ffffff@ffffff?ffffff!@@??ffffff?333333?ffffff?@@333333@ @ffffff@@ @@ffffff?@@ɿ@@?ffffff @ffffff@ffffff?@@? @ffffff@@ffffff@@ffffff??@@333333 @@?333333@ffffff?ٿffffff?@@333333?@@333333?333333?ffffff333333@?333333@ffffff?333333 @333333@?ffffff@ @?333333@@ffffff@333333"@ @ffffff*@??ffffff@ffffff@333333?333333?ffffff?ffffff?333333!?@@333333@333333@ @ffffff@@'@ffffff?ffffff?@333333?&@ffffff@"@@?333333??333333@ffffff?@ffffff @333333@ @ٿ@@@@??@333333 @333333??ffffff@𿚙?@@@@@??ffffff@333333 @333333@ffffff?333333@@333333@?ffffff @ @ffffff??333333@@ffffff@?!@@@333333)ffffff0fffff4@ffffff)([fffff&G4+%fffffA333333$ffffff'@?6ffffff34D@ffffff ffffff?ffffff+333334I9)fffff03(ffffff1+701333333 ffffff"ffffff42&ffffff+333333333333)%333335fffffB ffffff333333?!ffffff8fffff2ffffffČB5:333333"@!6"333333,+ffffff9ffffff"333333:333333;ffffff$L3&YGffffff(ffffff2333333%$ffffff*L?333333@# 333333+fffff4 A333333*333333-fffff20ffffff$fffff0L1+5ffffff8"333333733333::333333#3333332333333/=ffffff=333333#33333H333333%@33333>1+ffffff'ffffff@$Y@33333Afffffffffff=9L2@̌I<=fffffR FC=@0333333333333sEPL?!33333sDffffff_"33333]9 0:Mffffff@68L-@$?@9:fffff<=@333333fffff7@ffffff(ffffffH"@%33333353333337333333333333@15ffffff'ffffff7)4Gffffff3ffffff@1ffffff?ffffffA@M1fffff;5@N733333933333A3333331333333%@3333333%@-ffffff@@L6fffff&Affffff14333333󿚙333333@3333339@,ffffffffffff/fffffEffffff6,@%+:333333?1ffffff%@9333330ffffff@333333,#F0fffffBffffff333333sG1933333sI G333333S G (333333??LB@33333C`333336fffff;fffff0ffffff?333333%'33333@@'333333U1@LL@ffffff>/fffffF`ffffff)E@M5b&@-8?1%!@3333334ffffff-72ffffff0333333?@ffffff-ffffff @0J5ffffff8fffffH%LB+5:333333/b523333354ffffff@4ffffff(@#333333?33333B3333339.5ffffff@@L9L7333333+6,9R@0+@ffffff.@333333?2@333333!@fffffW@$@9@YKffffff1@6@̌A@33333@@ffffff(333333(3333338@4@3@@@L7@333333 ffffff @?333333 @333333ӿfffff&C@@L8@;@333333@333333@33333@@ffffff,@333331@-@@@ffffff'333333@ffffff@6@6$@#2@333333<@ @ffffff2@@@ffffff@4@333333@333333*@A@F@333333-@ffffff=@ffffffffffff#@333339@2%@@@̌A@2@? A@1"333333@ffffff;@ >@333333=`SC@K@ffffff7@?:#@ٿffffff(@L>@fffffF@&@ @:ffffff@fffff;@@=6=@333333/@@8@L5@333333<@7@@ K3333337@L3@#@ffffff2@7@333334@fffff=@333333?C@333333?333333?=@"@5@ @YA@@333333ffffff"@:@ @0@ffffff333333fffff@@D@ @,@ffffff0@"@̌A@33333@@3@ @ffffff 733333;@fffff1@333333@ffffff?#@ffffffffffff@ffffff'@333331@!333333%'@M@@B@:@A@@/@Dffffff"2@3@ O@ 333333@#@5fffffD@2--@fffff>@̌@ffffff,D@ffffff'=@-@:'@ffffff333333@&.@333333 &@M7@"@333333@@ffffff-LI@LQ@1@̌F@1@333333@,A?333333333333D@ffffff0@<@8@ -@333338@fffffN@G@3333331@33333sH@@3@W@?2@ C2@ffffffA@333333%1@@ffffff(333333?ffffffLA@333333ӿD@333333C@ EC/@33333sF@ffffff0@@333333$%@@<@333333@33333333333;@ffffffٿA@B@3333334@fffff8@H@@?ffffff@O@=&@:@4@33333L@?@ffffff*@8@# @333330@@:@"@ffffffP@!0@!9Y@9@Z@333333@-!@33333333333sJ*@33333C@@@!.@3333333!@333333$@M@fffff&H@fffff&AfffffffffffH@3333335@3333336@@33333D@333333G@ ?@$@3333331@?@33333E@@L=@2@)@;@$5@?@(8@33333sP@ffffff$3333336@ffffff&@L7@fffffHfffff&O@;333333'@333333fffff@@7333333@>333333@fffff9@̌G@@fffffM@333333333333̌Q@-%%@33333SR@#333333@2@<?33333N7@H@.ffffff$@3333335@333333&@333333B@@?@ffffff @LV@*@fffff1@YJ@fffff1@ffffff? G@9:@;LC@8 A@33333SW@3333339333333(@fffff^@fffff&D@fffff:@ffffff̌@@L@@YA333333@333333(@8@`ULI@ffffff @ffffff$fffff1@YIffffff*@333333 @333333)P@333333%@7@3333333333332@333333.L0@'@fffff0@fffff1@K@𿚙6@333333R@ffffff)@fffffO@@@ffffff?@??????????ffffff?ffffff????? @ɿ333333333333??333333??ffffff?????ffffff333333?333333?࿚?ɿٿ?333333㿚??333333????333333???????333333???????333333??? @333333ӿ?ffffff??333333?ffffff?333333??333333.@鿚 @333333 @@333333???333333?࿚??࿚????333333?@?@񿚙ٿ333333?ffffff?ffffff?@ffffff?333333?ffffffɿ333333ӿ???ٿffffff??333333?333333???ɿ333333?????333333ffffff??333333?ffffff?333333????@??ffffff?333333@ffffff?ffffff?ffffff????????????@?333333?@333333ӿ?ɿٿ?ɿ333333??ffffff?333333??333333?@?333333?ffffff??ffffff?333333ӿ333333@?࿚ffffff?333333@?333333@?@@?@???@333333333333?ffffff?333333?333333?ffffff??@???333333?333333??ffffff??ffffff333333?鿚??ɿ?333333?@??????@??@ffffff???333333?333333???ffffff濚?ɿ333333??ffffff?ٿ??333333??ffffff?333333ӿ333333?333333?ffffff?333333?333333?333333ӿ333333?@333333???ffffffffffff??鿚?333333ӿ@ffffff?ffffff @@?333333???ffffff?333333????ffffff @@ɿffffff?333333㿚ٿ333333@ffffff333333?333333@333333@333333??@ffffffffffff?@@333333?쿚?@?ffffff?????@333333@ffffff@?333333ӿ@333333@??࿚??333333@ffffff?@??ɿ??ffffff 333333????@?333333 @ffffff@࿚ɿ @@333333?ffffff@??333333@?333333???????333333ӿ@?333333???333333@?@333333󿚙??333333?%@@?ffffff?333333??ffffff濚?333333?ffffff @????333333?ffffff?333333?333333??333333@?ffffff???ffffff濚@L6L7ffffff33333s@333333ffffff3333333 ffffff$/='*2%fffff11$ffffff% @333333@333333@"@!@3@ @@333333@333333@'@@ffffff@&@ @@ffffff?333333?@,@@333333,@!@%@@ffffff"@ffffff@@@ffffff@333333@ffffff@333333@?@@ffffff(@#@@!@@ffffff@ffffff&@@@333333$@#@@333333@333333@3333332@#@ffffff0@ @ffffff%@ffffff@@@ffffff @@.@ffffff @@@!@333333@@/@@333333%@ffffff@+@?@@L3@333333@ @ @ffffff@.@333333@+@@@3@#@? @@@ffffff @$@ffffff@,@@@ffffff@-@0@@333333@@(@,@@3333331@ffffff<@ffffff$@333333@@333333@@ffffff@ffffff)@ffffff @@(@333333(@@@?ffffff@%@333333@!@@ @333333@ffffff@@333333#@ffffff@@ffffff@3@@#@ffffff#@3@ffffff @@@@333333#@ffffff"@@ @333333@333333"@@?@@@-@333333)@ffffff @ffffff@@L5@$@333333$@?ffffff@@%@&@ffffff@%@@333335@ @@ffffff@@/@ @"@(@333333@ffffff @@3333332@ @ffffff"@ffffff@?? @5@ @ffffff@333333@ffffff@/@L4@+@ffffff @333333@&@@ffffff!@%@@ffffff@@333333;@333333"@!@@333333#@@@'@ffffff@)@%@*@@333333@ @+@(@fffff9@333333@(@333332@333333%@333333@3333335@"@3333331@@@333333&@ @@333333 @?ffffff)@ @$@@ffffff@ @333333&@%@!@ffffff@@&@!@ @ffffff#@ffffff @ @"@ffffff(@333333&@333333&@%@@@ @333333@ffffff(@ffffff@333333@ffffff@333333@fffff1@"@ffffff@#@@333333 @@@(@'@@333333-@ffffff@ffffff1@fffff:@8@"@ffffff4@.@333333@@333333@#@%@@ffffff@333333@333333 @7@$@ @"@0@333333$@0@ @ffffff@333333"@ffffff$@1@@ffffff@*@*@@1@ffffff @!@&@#@ffffff@ffffff@@@333333@@@.@%@@@*@,@333333@ffffff$@@ffffff@333333.@'@ffffff?@+@ffffff@5@2@@#@@0@ffffff@333333@ffffff6@@?@$@333333@/@@ @333333@@333333)@ffffff@333333!@-@@333333@ffffff@@)@@@ffffff@ffffff#@@#@?(@@@@ffffff/@@$@333333*@ @"@,@@#@33333:@@ffffff1@333333.@ @@333333@ffffff @333333@@@@@333333@333333@ @ffffff@"@333333@!@,@ffffff@@ @%@!@@333333@333333@@@ffffff@333333@333333@ffffff @ffffff!@?ffffff(@L2@!@333334@$ffffffffffff$333333H&"$3333332333333"333333 ffffff 'ffffffL1333333333336ffffff$333333 333336- ffffffffffffffffff333333$333333$ffffff. ffffff&333333ffffff333333!333333"333333ffffff333333*%ffffff*ffffff$&ffffff!+,$)333333 ffffff%ffffff333333&#ffffff) 333333,333333)ffffff"333333%'#333333̌G30>ffffff%333333#ffffff333333)&333333333333ffffff#$333333!;ffffff-L333333Dffffff+$6ffffff2ffffffffffff=ffffffffffff!333333 (333333'333333333333333333+ffffffffffff,ffffff%-(L2333333. +333333*333333&*-333333ffffff 333333*$ffffff%fffffffffff4ffffff! $4ffffff333333ffffff&!*L5/333330L1333333"/333333ffffff 333333"333333&$.ffffff*#ffffff/333333($ -333333ffffffD0 333333,ffffffffffff*3333339333333!333333D51ffffff3 333333(.333333'*333333#ffffff #!333333ffffff/ffffff!.'333334333333333333* $$ /' #ffffff-ffffff)/333333,1ffffff (ffffff(""ffffff333333&$333333!333333 &#'#ffffff333333$ ffffff$ffffff!ffffff333333*ffffff*ٿ24L0L17ffffff,3333334L3333333!333333"ffffff+&1ffffff5333331&ffffff6#"333333 ffffff$333333333333)333333L0333333L3&%333333(ffffff(333333$#,333333"333333$ffffff5(33333:5"7-333333 ffffff,ffffffffffff/%1333332$ffffff$#333333!ffffff*ffffffffffff333333-$ 73333333333333333333/#L7ffffff.ffffff4ffffff@ ! $7$,ffffff333333ffffff+ffffff&0!44ffffff!(?!333333& 333333%ffffff&fffff&Gffffff333333333332333333 ffffff !333333"#**ffffff#ffffff$ffffff'ffffff" A333333@???ffffff?@ffffff333333????𿚙?333333?333333??ɿ????333333?????࿚?333333?ffffff?ffffff?333333?ٿ??333333??ٿ??333333??ffffff濚??ٿffffff??333333??333333?ffffffffffff?鿚?333333?333333?ffffff?????333333?ffffff?333333ӿ?ffffff??ffffff???쿚?ffffff?333333ӿ333333ӿ??ٿ?ٿ?333333ӿ?333333?333333??333333??333333?333333???333333ӿɿffffff???ffffff??????333333??333333???ٿffffff????ffffff濚?ffffff?ɿ333333??@333333?333333????ffffff?333333??333333?333333?333333?ɿ?ٿ333333??ffffff?ffffff??333333???ٿ???333333?333333333333????࿚??333333???333333ӿ333333?333333?333333?ɿ333333???@333333??333333???333333ӿ??ffffff?333333??333333ӿ333333?333333ӿ??ffffff??333333ӿ???333333????333333ӿ???333333ffffff쿚??ٿ333333?鿚?ɿɿ333333ӿ?ffffff?ٿ?333333?333333??333333????333333?ٿffffff???333333㿚?ffffff?333333?ɿ??333333???ٿ333333333333?333333???333333?333333?333333??333333?333333㿚?????333333㿚??ffffffffffff?????ffffff??ٿ???ɿ333333ӿ?333333㿚??333333???ffffff????333333ӿ࿚?333333ӿ333333?333333???333333???333333ӿ??333333ӿ333333?333333??ɿ333333??ɿ??333333???ffffff?ffffff?333333?333333㿚?333333ӿ??333333?ffffff?ɿ333333?ٿ??ffffff?333333?333333?333333?ٿ??????333333?ffffff??ffffff?333333????333333???ٿ???333333??333333?333333ӿ333333???쿚?333333??333333ӿٿٿ??333333ӿ333333?333333?333333?333333????333333ӿffffff???@ffffff@333333?ffffff@@ffffff!@ffffff@ffffff@333333@@??333333@ffffff @@@?@@@ffffff@@?333333@333333@ @ffffff@333333?333333@333333@@333333@@333333 @ffffff@333333!@333333@@ @333333 @ffffff@?@@@ffffff?333333?333333@333333ffffff @333333@ffffff@@@@@ @@@@ @ffffff@@@ffffff?333333@333333@333333@333333@ffffff@@ @@@@333333?333333@ @@@@333333@333333@2@333333@@333333-@ffffff@?@@@!@333333@ffffff@333333@??333333?@?@@@@@ffffff&@?@333333@ffffff @ffffff@ffffff@ffffff@@ @333333 @ffffff@333333@@ffffff@333333@@@ @@ @@@333333@@333333@ffffff@@@??@333333@333333?@333333 @ffffff@333333@#@@333333?333333??#@@ffffff @333333 @@??ffffff@333333@@333333@@@?ffffff@@ @@@333333?@333333$@ffffff?쿚?@@@?ffffff@ffffff@?@???@@@ @@?@333333 @@333333@@333333@333333?ffffff@?@333333@@333333@ffffff!@ffffff@333333?333333@333333@!@@333333@@??@@?@@ffffff@@@@ @ffffff@@??@@@𿚙@333333 @@333333 @@?@ffffff @?ffffff@333333@?@ffffff?ffffff@333333?@@'@? @ffffff?@333333@@@?@?@ffffff@@ @ffffff@@ffffff@ffffff?@?@ @333333 @@"@ffffff@@ @? @?ffffff@@?&@ffffff"@333333@ffffff?333333 @ffffffffffff@#@ffffff@ffffff?333333 @ffffff?ffffff?ffffff@ffffff@@ffffff@333333@333333 @ @333333@ffffff333333@@@@@@@@ffffffffffff?@@?@333333@?ffffff@@ @ffffff@@?333333@333333@@@!@@??@ffffff@@@?@ffffff@ffffff?ffffff?@ffffff!@@@(@??@ @@@ffffff@@"@@333333@?333333?ffffff@"@???@'? @333333@ffffff@!@ @ffffff!@@ffffff@?333333?%@?333333%@@333333'@333333@ffffff @@?@ffffff @@?@@333333@333333@?@@@@@@@@@ffffff?!@?333333@@ffffff%@ffffff@@333333@?ffffff@@@?@@ffffff@ @@@@@?@@@@@%@ffffff 333333@333333@333333ffffff4@ffffff@\30L3!YA!7@3@%@=33333333333sA@@@333335!@%ffffffH333339ffffff&@333333333333 %333333333333ffffff333333%+@ffffff@0@@ffffff36@,@333333333333 @;@ffffff"3333332@fffff?@333333??3333330333336**333333 @!@ @@ffffff@ffffff333333.C@333333? 333333%@@)ɿ=@LV333333@333333&ffffff333330@fffff;ffffff1@fffff0Ln0@333333&@efffffCL8?4333333@5=@333333@@33333=)@3333335@L>@lR?fffff&@@/LWffffff ND@@@333333̌D33333sA333333"@fffff&NL1@ffffff' @7@3ffffff2ffffff#@ffffff;@?5@ffffff&ffffff+@3333333@@"L;333333#'&+@L3@333333$$@-/1$@ @ffffff333333(ffffff*)@&333333&ffffff @333333+ffffff&@333333/@7@333333/"333333$L:AL4>333333F@>ffffff333333?ffffff@,@333333?ffffff-@ L7833333G@3333334LCYAI7933333sK@333333&@333333?-:@fffffG?ٿ+@ffffff.@33333Caffffff6@*L]333333 @B333333?3fffff&F333333@333333@fffff3333335YT1333333@ffffff@ffffff0@:fffff3'L?@fffff2@P@333333@?F@333333+333330&333333%@33333@@fffff2333333%@3ffffff 333335@/@3333300@7"@ffffffDRfffff1fffffffffff\?33333@ffffff@$333333 ,333333L0@$@,@fffff8@(ffffffffffff%C@ffffff.ffffffffffff=@333333&@ffffff@6?fffff3333333/@1@O@?333333!@E@̬d@2@L7@333332:ffffff33333H6̌AJ333333,@@:$33333@Bffffff"ffffff-ffffff @? <@33333sG?.3333333?@ffffff,@S33333<@ffffff2@ffffff(=3333331@?7@@5+=@3333334@ @ .@ffffff&@ffffff3!@?9*@PfffffIEffffff/@K333333?ffffff9@̬S>@ffffff@fffffF@@ffffff @ffffff)@̌FfffffD"333333@@;@333333! A@fffff&A@6R?fffff4@Hffffff̌C?=333333333333I3@7?fffffe0@̌C!(33333319@@@3@fffff&D@<@@333333 333333?!333333$V3333335 '@-g?ffffff33333sL3333338@L0333333ffffff$ @31@2@<ٿ"%fffff2@%@@;ffffff:@633333;@R333333@fffff&C@YJ@33333;@L7@I@E@333333R3333335@5@E@fffff;@33333Sffffff@fffffO@LJ@Z@333333%@&@333333I@33333P@P@fffffH@C@333333 @=@7@33333M@33333F@@̌M@8@H@B@4@L8@4@@W@D@?@A@)@33333J@fffff9@fffffB@/@33333sF@ffffff2@ffffffL<@33333;@@Q@fffffO@<@N@H@333333?@3333334@2@33333N@33333O@ B@33333I@P@)@333333 @ R@fffff8@2@fffffI@fffffQ@A@333333C@@P@Y@333333ffffff=@L@`T@ffffff?K@/@"33333B@9R@ffffff?@7@ffffff/YC@33333@@5@33333sV@R@ffffff@@C@333333fffff5@fffffE@ffffffO@9Pfffff@@fffffM@'@,YK@ W@fffffH@33333E@33333sG@8@$B@ffffffO@L6H@fffff4@C@@N@@fffffE@7@=@fffffS@fffff?@33333L@4@@O@?@:@YH@ffffffK@;@̌B@ L@333333 "@YD@333333R@L=@A@D@333333I@fffff&N@@U@I@333332@@&@fffff&\@ffffffE@3@@G@E@333333@ @E@!@333333G@4@A@fffff&T@A@6@D@ Y@&@ E@333333?I@P@ffffffM@33333sB@LD@A@U@333333@ffffff=@R@"@3?@ffffff333338L6@fffffA@L1@8@YQ@ N@@@ffffff?@33333sP@33333<@ffffffI@ffffff @333333C@ffffff! F@ffffffMP@fffff1@.@33333J@&@@@2@fffffS@;@yQ@333333;@ (333333K"@@ K@M@P@̌A@ɿ333333L@YB@̌T@9V@=@R@33333sH@L@`@8fffffK@ JfffffD@33333Q@333333.@7@333333+@?9@ffffff%ffffff1@M@@̬Q@"33333T@@ffffffFA@33333S]@ffffff"@Q0@1@N@333335@?ffffffH@ffffff@ffffff@fffff&B@C@D@ P@ W@<@@+@33333V@2333333>@9T@33333sG@\@̌L@fffff6@333333@@0333333?@3333333333338@@/@fffff\@W@YY@!@E@: a@fffffQ@`@fffff&E@333333fffff?@L?@9@:K@I@ C@fffffFS@ @+@/ffffff>@>@3@fffffFR@fffffW@>ffffff#33333I@8@ffffffS@7@YH@6LN@333333O@fffff@@ Q@yQ@fffffU@)@(@33333L@`R@LF@R@5@=@33333sA@YJ@fffffQ@33333P@!C@E@$@!C@>@L833333E@!@R@&@@@1/@3333337@D@Z@33333sY@ffffff,@fffffC@333333@-@H@`@ffffff333333ffffff,@@@LI333333)GG@fffffK@fffffF@@@Q@333333;@YS@@ @O@3333337@̬\@? D@9S@3@fffff1@333333fffffFR@9@fffffK@L533333T@333333*@YQ@fffff`@ffffff:33333sE@fffffU@fffffP@33333L@A@333333>@33333sP@YD@̌Jffffff@R@6@Y@@@ffffff8R@̌G@7!33333E@ VG@*@8P@LC@,P@(@333333G@3@I@33333O@8@2@fffffX@333333)@33333SV@ffffff@9a@fffffFbW@33333sN@333333ӿɿffffff??ɿ@???ffffffɿ???ٿ?333333㿚???333333ӿ?333333??ٿffffff濚???࿚??ɿ?𿚙?񿚙ٿ333333࿚?ٿ333333??鿚ٿ333333ӿٿ?333333ӿ𿚙ɿɿ?ٿ333333?333333㿚?ffffff@쿚ɿ333333ӿ333333㿚ٿ333333ӿ333333ӿ333333ffffff#@333333?@??鿚?????񿚙ɿ?333333?ffffffffffff@ٿ@ffffff333333ӿ???333333?333333?ɿ333333ffffff濚??333333?333333ӿ??333333?333333?ɿ?ٿٿ333333㿚ɿ??????333333ӿɿ࿚???쿚ٿ?ٿɿٿ?333333ӿ쿚???ffffff??࿚ɿٿ࿚ٿ鿚?333333ӿ?333333?333333ӿٿ??333333?鿚?333333ӿ?࿚ٿ333333㿚?ffffff@ɿ?ffffff?333333󿚙ffffff??ɿ񿚙?ffffff濚333333 @ffffff??ffffff?333333??ffffff333333㿚???333333ӿ333333ӿ333333ffffff?333333㿚???333333????ffffffffffff?333333?333333ӿffffff?ɿٿ࿚??ɿ?333333ӿ333333࿚??ٿ???333333?ٿ333333ӿ𿚙?333333?333333ffffff𿚙ٿ333333??333333?333333?ffffff??ٿffffff??ٿ࿚ɿ333333??ٿ?333333?ffffff?ffffffffffff?쿚?333333333333ffffffffffff??333333???????ɿ333333󿚙???ɿ?ffffff333333㿚ffffff@?333333?333333?333333?ɿ?񿚙???333333ӿ333333?࿚ 333333?333333??ɿ?ٿ333333?333333ffffffٿ?333333@333333??@333333ӿ????333333ӿffffffffffff?ffffff??ffffff??ffffff333333ӿ333333ӿ@ffffff?ffffff??333333ӿffffff333333?333333ӿffffff?ɿٿffffffffffff?333333?????ٿ?333333333333ffffff0ٿffffffٿffffff4333330ffffffٿ,ٿٿٿ࿚ٿ"ٿffffff333333ٿ#333333ffffff.@#@$@.@ffffff.@?@@ffffff@@@333330@ffffff@ffffff @4@ffffff)@333333#@ffffff@ @&@2@'@2@!@%@333333)@)@2@333333'@"@333333@@"@@-@"@ffffff$@(@ffffff,@@ffffff@&@@1@ffffff3@)@@%@ffffff"@ffffff@333333@L0@%@fffff2@ffffff8@*@@333333&@333333%@3333336@ffffff@ffffff,@$@!@0@fffff1@@#@1@$@333333#@#@ffffff+@333333@ffffff@333333)@:@ffffff@333333@3333332@L1@3333333@ @2@+@@333333B@*@ffffff?@@ffffff#@@"@ffffff!@3333330@*@#@ffffff@ffffff.@ffffff-@fffff6@ffffff@>@ffffff)@3@@333333)@fffff2@L2@ffffff@333333*@$@*@)@333333$@%@ffffff @ffffff@(@)@ffffff"@@(@@+@!@/@@ffffff @333333 @-@(@/@ffffff&@L3@333333/@,@ffffff#@"@*@3333334@%@+@333333+@'@8@-@ffffff"@"@$@2@333333#@(@%@333333)@333333(@.@+@@fffff3@!@7@2@333333+@0@333333)@ffffff#@,@)@"@/@@>@ffffff#@@$@333333%@ffffff/@@/@0@!@@@333333'@@ffffff&@ffffff#@ffffff@?'@2@333333%@"@!@(@33333=@6@333331@ffffff @@@333333!@+@,@@*@0@/@333333@"@ffffff)@'@ @ffffff*@L5@@.@ffffff0@3333331@333333%@333333@ffffff+@333333@333333&@L5@ffffff"@+@fffff4@333331@333332@.@'@&@ffffff-@,@0@ @ffffff-@@333333(@&@@(@?ffffff)@ffffff$@fffff0@ffffff?333333@@ffffff)@$@!@$@@)@ffffff'@ @@333333 @@@ffffff&@@'@333333.@ffffff*@ffffff@)@333333@$@333333!@@3@@ffffff6@#@333333 @)@#@(@@%@@'@7@333333=@.@'@3333332@ffffff,@@L4@333330@333333;@333333 @333333.@ffffff#@2@?0@-@ffffff!@ffffff*@333333)@fffff8@ @ @fffff0@ffffff5@(@333336@ @ @+@ffffff4@9@$@333333!@.@ffffff*@333333*@#@ffffff,@L0@fffff3@ffffff5@fffff9@ @#@&@0@ffffff@*@@)@.@333333-@'@L8@!@333333@$@333333!@%@333333)@fffff6@?@ffffff@1@333333@333333'@4@ffffff@333333"@!@.@@33333;@2@@ffffff?@333333*@ffffff@3333333@"@@333333@ffffff@+@333333@@1@+@333332@ffffff$@333333(@"@333333%@@@"@'@333333+@*@ @ffffff$@333333@ @@ffffff"@L;@@333333@2@'@333333,@7@@333332@5@5@-@L0@333333(@(@3333331@333333'@@333333$@#@%@333333*@ffffff$@@ @'@/@ffffff"@(@1@ffffff4@333333&@333333?"@%@ffffff+@ @ffffff-@%@ffffff0@1@@333333@@ffffff@,@333333@6@4@'@7@ 333333333339ffffff333333 *ffffff333333333333333333 333333#3333331 333333/ ffffff333333ffffff 333333&ffffff333333ffffffffffff 333333 ""#ffffff!333333+333333'ffffffffffff ffffff#333333ffffff&ffffff333333333333 333333%ffffff333333"!ffffff<333333!(333333"ffffffffffff  333333"ffffffffffff<ffffffffffff-!333333 33333sE333333ffffff333333333333 (! 3333334 333333333333 333333*ffffff)ffffff$333333  $ffffff*$ ffffff # 333333' #!ffffff 333333 333333"! 'ffffff ' ffffff&ffffff%333333)ffffff#.( ffffffffffff333333333333#ffffff$"ffffff 333333ffffff)+ffffff𿚙"ffffffffffff333333+&ffffffffffff/3+ffffff333333,ffffffffffff$333333"333333ffffff333333ffffff333333ffffff 333333&ffffff%333333333333ffffff-ffffffffffff*333333!333333&#333333333333 333333ffffff333333ffffffffffff !ffffff 333333ffffff (" ffffffffffff  ffffff$ffffff +333333&*333333)L2ffffff333333󿚙333333ffffff04ffffff333333*ffffffffffff333333+333333''ffffff&2333333ffffff$333333 *ffffff&ffffff$ 333333ffffff333333333333$ "ffffff)ffffff3333336333333$*ffffff!333333!333333(#(! ,333333 )ffffffffffff'( 333333#333333* 3333331fffff6333333 ,+$333333#333333ffffff ffffff+#! "33333323333336 "ffffff333333$>ffffff.,!"333333333333! #$ YB333333??ffffff??333333@????ffffff??333333????ٿ??????333333??????333333?333333?ffffff???333333???333333???333333?333333??333333???ffffff??333333?ffffff??ffffff?ffffff????333333???333333?333333??333333?333333???333333?????333333?ffffff?333333??????ɿ??ffffff?????ffffff?ffffff????????333333㿚????333333???333333??333333?333333?ɿ??333333?ffffff?333333?????333333??ffffff?333333?333333???????333333?333333????333333?333333???ffffff??ffffff?333333?333333?333333???333333?333333??333333?ffffff?ffffff?333333??ffffff?????ffffff??ffffff?333333?333333?ffffff?333333???333333?ٿ333333?ffffff?333333????ffffff????333333???333333?333333ӿ?????333333???ffffff?333333????ffffff?333333??333333??ffffff?ffffff@333333?333333???333333????333333?333333?ffffff??333333????????333333?333333???333333??ffffff??333333??333333??333333?333333?????ffffff??333333?333333??ffffff??333333?333333???333333????333333??333333??333333?333333??333333?333333??ffffff?333333????333333?333333ӿ333333?ffffff?333333ӿffffff???333333?333333????࿚??????ffffff???333333??333333?333333ffffff?ffffff?333333?333333?333333?333333?ffffff?333333??ٿ?????333333???ffffff???333333?ffffff??333333?333333?ffffff???333333??333333?ffffff?333333?333333????ffffff?????333333?ffffff??333333??333333??333333??????333333??????333333???ffffff??333333?ffffff?333333??ffffff????ffffff???ffffff??333333???ffffff?333333ӿ???333333???333333??ɿ333333???333333?333333?333333㿚???333333?쿚?ffffff?333333?ffffff?ffffff??ffffff?333333????333333?????ffffff??333333???????333333???ffffff??ffffff?ffffff???333333??ffffff??333333??ffffff@@@ffffff @@@ffffff?ffffff?@?333333??@ffffff?@@?@ @@333333@333333@?ffffff?333333 @@ffffff @333333@ffffff?@ffffff@333333?333333?@@ @?@ffffff@@333333?333333@? @?????333333@@333333?ffffff@@??@@@333333@333333@@@ffffff @@333333?@333333@333333 @ffffff?@@@333333󿚙@333333?@@?333333@333333@333333/@@???ffffff@@?@ffffff@ffffff @@333333?ffffff333333???"@?333333?@ffffff@$@?ffffff @ffffff@@333333@@@ffffff?ffffff???ffffff @?@@@@333333@ffffff@@ffffff@333333@?@@ffffff@@@??@@333333?@@ffffff@ffffff@@@333333??@@333333@?@ffffff@333333??ffffff@ffffff?@333333?@333333@?333333?@ffffff@?@?@@ffffff?333333?333333?333333??333333@?@???333333???ffffff@ffffff @@333333?333333@@@ffffff@ffffff????ٿffffff @ffffff@333333@ffffff@333333@@@@333333@@ffffff@@@?ffffff@@333333? @@@333333@? @@333333 @333333@?ffffff@?@@333333@ffffff? @333333????333333?@ffffff@? @ffffff?@??ffffff@ffffff@@??333333?@ffffff@?ffffff@333333??@@@@ffffff@333333@??333333?ffffff@ffffffffffff@333333@@333333@ @?@???ffffff???@ @@333333? ffffff?@333333@ffffff@333333? @?ffffff@?@ffffff@@@333333?@ffffff? @?333333@@ffffff@@??333333ffffff333333@333333???333333ffffff@@@ffffff@333333@@ffffff@@333333@ffffff@@??@@ffffff@@@@ffffff @333333??@333333?ffffff@333333@333333333333@ffffff?@ @ffffff @333333?333333333333 @@?@333333@?ffffff???ffffff?333333@ffffff@@@?@333333@333333鿚?@ffffff? @?@ffffff @@@? @ffffff @@?@@ffffff@ @?@333333?333333 @@ @ffffff@ffffff?@@?@?@?333333!@@ffffff@333333?ffffff?333333?@@ٿ?@?333333 @333333@@333333@? @ @333333 @@ffffff?@ffffff ffffff@333333@333331@'@<@3333330@333333=@ffffff?@@ffffff*@)!ffffff@̌N@=@3@0@,@333333K@5@!1@#@ffffff4@>F@3@@333333"@&@333333@5@@2@ffffff3333333@)@ffffff1@3333336@L?@333333L@ffffff*@"ffffff @.@333333@!2@333333@2@Q@333333,@-@3333330@ffffff.@ffffff@333333@333333/@+@ffffffC@@F@@333333A@ffffff;@7@1@333333+@@>@fffff?@lQffffff@ffffff333333-@L?@L7@ 5@<@33333]33333sQ@333333-@fffffQ@"%@ffffff@ffffffA@L8@ffffff%@1fffff5@8@Y@fffffFRU@ffffffA@@ffffff&ffffff7@IffffffA@@ffffff-@@333333#@@333333-@ffffffC?333334@fffff7@(,ffffff!@#@;@?fffff=@@333333!;@5@@ @333333:@3333339@@3333330@;@fffff5@?@ffffff'@ffffff@5@;@ffffff @&@333331@ B@ffffffB@;@!@333339@ F@fffffffffffA@@ffffff?333333G@ffffff@@fffff&A@@/@ffffff@L1E@333333@@ffffff7@L2@3333332@333333'@33333:@33333<@?-333333;@333333ӿ/333333@333330333333!333333)@E@3333337@'@*@ffffff<@M@3333333@333333@333333+@*4@ffffffTA@̌C33333s@@fffffA@"7'333333%@3@ffffff@2@ 33333P@333333@333333)@fffff9@L:@#@3@-@333333$@ffffffF@3333330@ffffffE@fffffC@C@YH@@fffff2@333333@;@fffff&@@ @9@6@0*@#@ffffff,7@(fffff@@ffffff8#3333331333333(@񿚙Jffffff!@!@ @ffffff@L;@&@333333@#@ffffff@ffffff(@ffffff@8@fffffA@@!@ffffff:@ffffff%@333333.@D@@ffffff=@0@@ L8@333333@333333)@@I@I@fffff?@?@A@@2@fffff&D@*@L9@L5:@3333338ffffff(3333337@-@@fffff4@fffff&A@L1I%@<@333333,ffffff#@fffff&A@33333@333333?ffffff'@ffffff/@333333/@@2@ffffff#@ @333333?@ٿffffff.@ffffff@fffff4@:@C@#@ffffff4@333331@fffff&C@ffffff@1@ffffff)ffffff@333333=@fffffB@2@E@@@3fffff&D@ffffff @ffffff*ffffff3@@333333;@ffffff@@333333?-@ffffffJ@.333333󿚙:@4@3@ffffff2@&@ffffff9@33333sD@;333333Z1@ffffff"@>333333$ffffff@8@333333"LFfffffE@333335@?@fffffN@ffffff;ffffff3@?@ffffff@@=@@L?@1@>@33333sCٿ9@ffffff!@333333??@#333333_+@3333337@fffff: @̌B@1@33333sA@8@ffffff7@<@ffffff@7@ @8@fffffL@@@(@&fffff<@4LQ@Q1@F@̬Q@LA@333334@̌O@O@33333sU@.@ffffff,@33333sO@L6@fffffB*@Y@G@F@LN@3333334@33333:@U@333333O@ffffff.@C@2@̌B@0`@fffffH@-4YF@33333;@fffffA@YE@ffffff&@333339@1@LV@LE@;@33333F@*@33333H@333333T@fffffA@?<@3333339@*@333333 @:@L4@333333G@fffff&Z@333331@A@A@333333(@ffffff1@<@4@33333B@̌B@LR@U@fffff9@333333:fffffN@fffffK@ @YI@ J@E@333335@̬[@YO@@'@;@fffffF@33333N@@33333D@G@R@fffff<@C@YJ1@fffffD@7@+@fffffQ@fffff&P@LL@33333F@%0@@A@̬Z@Pf@fffff&G@%@fffff&M@S@Q@LB@fffff=@K@333335@333333Q@333337@̌@@333339&@ @33333A@fffff&H@ @7@33333S@L9@fffffQ@fffff7@fffff&H@3@-@333333E@@A@33333F@333333K@fffffH@fffff&P@̬R@$@5@2@LH@333333@@ffffff9@fffff&J@33333P@ffffffD@M@fffffG@/@ffffff1@ A@fffffF\@fffff&A@333333I@ L@K@>@ffffff@M@+@fffffS@@@K@fffff&V@33333F@D@33333P@W@ffffff#@L=@@YH@J@ffffff>@333333@33333@@=@fffff&K@@YD@Q@@ffffff"@ fffff=@ffffff?YE@fffff6@>@333333<@B@1@33333@@C@YR@fffff&U@L8@ffffff*@!@333338@3333331H@fffffCfffff&M@ffffff9@=@Q@N@33333E@ffffffB@3333338@A@33333E@L:@ @ffffffE5@&@LH@fffffFW@33333R@B@333333?ffffffL@33333sB@fffffC@lT@3@fffffB@fffffT@33333G@W@B̬Q@(G@33333sE@33333C@?fffff@@?L?@333333 H@fffff&@@333333J@?33333U@ffffff3ffffff@333333@33333Q@3333320%@4@@YP@YF@333333+@333338@ffffff(@ffffff,@5@ffffff;@<@G@33333U@E@@#@?̌E@ ?@33333T@fffff?@YX@fffffG@1@,@@ffffff>@L0@6@?333333*@ffffffV@)a@33333T@333333#@F@5C@ W@fffffQ@LI@!ffffffJ@3033333Z@333333ffffffJ@I@@@33333sZ@333333,fffffA@C@'@LG@ffffffI@3?4@333333'*H@:@5@<@ffffff,@1I@333333K@yR@R@@Z@"@P@L@fffff&[@fffff6@33333sI@L2@ffffff/@ D@W@333333P@N@333339@333333->@K@=@33333F@@ffffff@LD@4@333333M@+@333333=@%+fffffB@333333?@N@"B@fffff?5@333333)@@333333ffffffC@W@ @fffff0ffffff@7@ffffff(ffffff2@H@B@fffff&R@ffffff9@R@A@L;$ffffff$@ffffffU@=@P@*@fffffG@333333B@F@@ffffffD@fffffI@@H@fffffBX@fffff&G@@R@W@9YP@fffffA@L@fffffW@D@fffff&H@L6@ I@0?YR@!ffffff @fffffH@333333@333333@YL@̌R@(@ffffff8@33333L@S@9@fffff@@@O@.@33333sI@3333339@@P@̌T@333333$@B@H@3333331@33333U@#)`@,dL@ffffffN@ٿٿ??ɿ333333?ɿ?333333ӿ?࿚ٿ࿚?ٿɿ333333ӿ333333ӿ333333㿚?333333ӿٿٿ?333333ӿffffff࿚ٿ?333333ӿffffff濚ɿ333333ӿ333333࿚333333ӿ333333333333ӿ333333?ٿ࿚ɿٿٿ?ffffff333333?鿚ɿɿ@333333333333ӿ333333333333ӿ࿚ffffffɿ333333?࿚??ɿ񿚙ٿ?쿚333333333333??࿚?333333333333ӿ333333?ɿffffff濚?ٿ?ffffffɿ333333㿚ɿٿ??333333ӿ??ٿٿɿ333333㿚???쿚࿚ٿɿ?ɿɿ333333㿚333333ӿ?࿚?࿚ٿ333333ӿffffff333333ffffff濚ٿ?࿚ٿ??333333쿚?ٿ333333ӿffffff濚333333ӿٿٿٿ??333333ӿffffff濚?333333㿚鿚ٿ?333333󿚙ɿ@𿚙ɿ?333333ӿ333333㿚??ɿɿ333333󿚙ɿ쿚??333333ӿ࿚࿚񿚙?333333㿚ɿ?333333???ٿɿ࿚?333333ӿffffff࿚??ɿ?ٿ333333333333㿚ٿ333333?ٿ??ٿ333333ӿɿٿɿ333333333333ӿ????ffffff񿚙ɿ񿚙?333333?333333㿚?ٿffffff@?333333㿚ɿffffff333333?333333㿚鿚ٿ333333ٿɿffffff?࿚?333333󿚙?ɿٿ?ffffff?333333?࿚?333333㿚?࿚ffffff@??333333333333?ɿ?333333࿚鿚?ffffff濚ٿffffff??333333ӿ333333ӿɿ333333ffffffffffff333333ffffff?ffffff?333333???333333ӿ??@??333333󿚙񿚙?ٿɿffffff濚ɿ?쿚ٿ333333ӿ333333ӿ333333 @??ɿٿffffffffffff333333?333333ӿ??333333ffffffɿ333333?ٿ?ffffff濚??ٿٿffffffffffff333333ٿٿ333333ٿ333333333333ffffffffffffٿffffffffffff333333333333ٿ333333333333񿚙ٿ#ffffffٿffffff濚ٿٿٿ333333ٿ333333󿚙ٿ333333333333333333333333ffffff333333 333333㿚쿚ٿffffff333333ffffff,ffffffٿffffffٿ333333 333333 +@&@333333&@ffffff.@333333"@3@@!@"@%@ffffff)@333333@ffffff@3@333333+@333333#@'@ @ @#@&@ffffff)@#@ffffff#@/@333333"@5@ffffff*@@333333@@"@ @*@#@ffffff'@333333)@)@"@@)@@333333'@3@333333 @ @@&@ffffff@@ffffff$@333333"@1@4@)@ffffff#@ffffff&@&@fffff5@%@ @ffffff$@@-@0@333333@333333!@,@/@@ @$@#@333333 @ffffff.@/@@*@*@(@333333,@%@'@'@@L6@ffffff(@333333'@@ffffff @@@@@333333+@.@$@ffffff @ffffff,@#@333330@(@;@ffffff$@3@8@333333"@fffff3@ffffff*@$@ffffff'@"@1@,@@"@ @@ffffff(@333333)@%@@ffffff)@333333@$@ffffff@/@@!@#@ffffff+@#@333333/@333333!@0@3333332@@'@@*@3333331@ @+@'@#@3333331@&@ffffff@"@333333$@333333.@@333333(@333333$@ffffff*@ffffff+@ffffff)@333333'@@/@"@,@ffffff&@'@(@333333&@%@ffffff)@333333@ffffff"@ffffff)@*@6@@333333@&@333333%@*@333333@/@$@333333#@@@@333333@ffffff @L=@ @@333333&@3333330@(@ @ @.@333333@'@2@ @333333@ffffff@$@@ffffff(@(@@fffff3@.@&@@ffffff@,@ @ffffff@1@333333@.@ @333333%@/@ffffff1@333333#@ffffff @+@ @$@fffff2@ @#@(@1@fffff1@3333331@333333$@$@+@*@-@ffffff@+@ @ffffff/@(@$@(@333333@@ffffff&@ffffff2@ffffff?ffffff$@@$@3333330@333333?ffffff!@333333*@333333@333333@333333)@ffffff'@@333333"@333333!@@ffffff"@ffffff@ffffff&@(@$@333333@*@ffffff@$@#@%@0@!@0@ffffff@@#@(@+@@@333333@ @2@3@ffffff&@333333*@/@333333'@ffffff@*@333333-@*@333333@333333.@ @/@ffffff5@@ffffff%@.@@*@0@3@@ffffff(@*@ @!@ffffff-@%@333333@*@,@6@@333333%@)@3333334@&@#@#@ffffff6@1@3333336@ffffff.@333333'@ffffff@(@333333(@ffffff!@333333/@ffffff@ffffff%@$@+@#@L0@@@333333 @&@#@333333+@ffffff%@ffffff&@@L0@3@@333333-@ @@333333%@@,@333333@333333@/@ffffff&@@@&@"@3@!@@@!@L1@@"@3333331@'@3333330@*@fffff0@333333$@ffffff%@+@@L0@*@3@333333#@ @ffffff"@@333333!@333333@&@333336@(@@/@!@*@5@333333 @3@,@.@;@(@.@ffffff.@,@1@@ffffff,@-@!@.@&@@&@)@%@ffffff"@&@=@-@7@ffffff@333333%@&@)@&@333330@#@7@0@ffffff@ffffff0@"@ @333333"@ffffff@3333335@6@.@)@333333333333 &ffffff333333ffffffٿffffffffffff333333ffffff 333333333333ffffffffffff333333333333ffffff333333333333333333333333 ffffff 333333333333333333ffffffffffffffffff333333 ffffff333333ffffff333333333333ffffff333333 333333 333333 333333333333 ffffffffffff333333 &333333ffffff"ffffffffffff 333333 # 333333"!333333 ffffff333333333333333333ffffff ffffffffffff333333ffffffffffff ffffffffffffffffff濚ffffff  333333 %#333333 333333333333333333333333ffffffffffff333333ffffff333333333333ffffff ffffff333333333333ffffff333333!333333333333 333333$ 333333ffffffffffff ffffff 񿚙ٿ333333333333333333 ffffff333333#333333333333 ffffff 333333 ffffffffffffffffffffffff333333ffffff 333333333333333333333333 333333 ffffff  ffffffffffff ffffff!333333333333333333 ffffffffffffffffff333333ffffff333333"%ffffff)333333 "ffffffffffff ffffff$333333ffffffffffff ffffff333333 333333333333 333333 ffffff333333ffffff"ffffff ffffffffffff!333333  333333%333333ffffff 333333 333333ffffff333333333333333333333333!333333 ffffff 333333%333333ffffff-333333333333  $333333333333ffffff 333333333333333333*ffffffffffff ffffffffffffffffffffffffffffff ffffff> 333333333333@???????333333?ɿ??????333333????ffffff??333333??????333333???????333333??333333?333333??ffffff????????333333?ffffff??333333????333333?333333????333333???????333333?333333?333333?333333??ffffff?333333?ffffff?ffffff?333333??ffffff??????ɿ?333333???333333???333333????ɿffffff???333333?ffffff?ffffff?????ffffff?333333?ٿ???333333?333333????ffffff?333333??????333333??333333?ffffff???ffffff????333333?ffffff???333333????ffffff????ffffff??333333??333333?ffffff????333333?ɿffffff???ffffff??333333?ffffff?333333???333333?333333?333333ӿ333333??ɿffffff?333333???333333??ffffff?333333?????ffffff??333333?ffffff??333333??ffffff@333333???333333?333333??????ffffff?ffffff?ffffff????333333??????????ffffff???333333??????ffffff??ffffff?333333??333333?333333?333333?ٿ???333333?ffffff?ffffff?333333??333333?333333?333333?ffffff?333333?333333????333333?????????333333ӿ??333333?333333????333333????ٿ??????333333??333333?333333?333333??ffffff333333?333333???333333?333333???ɿ??333333?ffffff?????ffffff?ffffff?ffffff??333333?ffffff?333333??333333???333333????333333?????ffffff???ffffff??ffffff?333333???333333?ffffff?333333??ffffff?ffffff?333333?ffffff??ffffff?????333333?ffffff????333333?333333?333333??333333?333333?ffffff??333333?333333????333333??ffffff?ffffff??ffffff?333333ӿ333333??333333?333333??333333??ffffff?333333?ffffff?ffffff?ٿ333333??ffffff濚???????ffffff?333333??ffffff????333333??????????333333?333333???ffffff??333333???ffffff?333333??ffffff????ffffff??333333?333333@@333333 @@333333@ffffff @??ffffff@?333333ӿ??333333 @ffffff?@ffffff@??@@ffffff?@333333?@@@ @333333333333@?@ffffff???ffffff?333333@ @?@?@ffffff??333333?333333???ffffff??333333?@?ffffff@@333333?ffffffffffff?ffffff?@ffffff@ @333333 @?333333@@333333?ffffff@333333@@333333?@@ @ffffff@?333333?@񿚙?ffffff@ffffff@??333333?@ffffff?ffffff@333333@@@?333333???@???333333 @333333??333333?333333@?@333333?333333@333333@333333?333333??333333@?ffffff?333333@ffffff?ffffff@333333@@?333333?@??@ffffff @@@ffffff???@ffffff?ffffff@333333@333333@@333333@ffffff?ffffff??@ffffff@333333@@@ @?333333?ffffff?? @?ffffff@@@?@ffffff@333333??333333??@?ffffff@ffffff?333333?@333333@࿚?ffffff@?333333ӿ333333??333333??ffffff?ffffff @333333?333333@@@ffffff@?333333?ffffff?333333ӿffffff@@333333@@ffffff@@ @@333333 @鿚@?ffffff@333333???ffffff?ffffff@@ @ffffff??@?@ffffff@333333?@333333?333333@@333333ffffff?@?333333㿚??@??@?@333333@@?333333?@333333 @????ffffff@ffffff@?ffffff???@@333333@@333333@ffffff @?333333?333333?ffffff??@333333 @?333333@@?@333333??@???333333@@@?333333ffffff@ffffff@ffffff@333333??@? @ffffff?333333?333333?@333333@?ffffff??@? @333333@333333????ffffff@ffffff?@ɿ333333@@ffffff@ @@?ffffff@@ffffff@333333???333333??@333333@333333???333333@@?ffffff?333333?333333 @鿚@333333??@ffffff@@ffffff333333?333333??333333?333333 @@?333333???@??333333??@???333333?ffffff@?ffffff@??@ffffff@ @? @ @ @ @333333@ffffff@333333?ffffff@333333?@333333??@??333333?ffffff???333333@? @?@ @ @????? @?@??ffffff?@333333@?ffffff@ @?@? @ @333333?9@5@'@ffffffB@333335@4@"@-@333333.@7@ffffff@@fffffG@L=@3333334@L9@fffff0@0@<@2@333333%@ffffff3@3333330@fffffA@@LK@3333339@ffffff&@333333#@,@ffffff&@3333335@ @:@333333)@9@ffffff6@L3@L9@@ffffff5@YF@ffffff @?!@333336@L0@?3333330@ffffff@3333332@fffff&I@3@=@ffffff7@+@@@L4@333333ffffff.@/@@@D@&@@;@fffffD@@7@5@1@333333!@fffffC@5@fffff@&@2@6@fffff?@9@2@&@ffffff;@33333sA@@G@1@fffff&E@33333A@@333333 @@?=@4@L5@333333;@L1@fffffL@333333-P@<@fffff3@S@ffffff2@fffff=@fffff&@@4@(@ffffff)@8@3@333333'@3333331ffffff@ffffff@5@333333>@L0@ffffff!@3333336@@L>@)@333333&@@>@fffff5@'@ffffff'@?@LC@ffffff&@L5@@̌@@+@333333$@333333?@%@)@7@3333333@!@(@333333/@;@@fffff>@9@7@=@.@F@ffffff@?@,@fffff9@33333=@33333:@fffff;@@-@333333#@@%@333333333333*@fffffA@&@333333$@>@4@ffffff7@)@B@5@ffffff#@333333'@333333ffffff'@'@fffffT@!@5@33333D@ffffff:@*@.@LA@ D@?@@@ffffff"@3@ffffff?333337@">@I@@G@33333@@&33333333333=@*@%@̌B@?C333331@@333333.@<@:@333333)@ffffff'@ffffff;@333336@3333330@LD@fffff1@L>@7@333333K@F@fffff;@1@4@>@33333?@4@@7@!@C@ffffff@E@fffff0@ffffffffffff@fffff&@@$@"ffffff4@fffff4@ffffff(@ffffff=@,@@33333=@3@333333(@ffffff*@/@333333,@@7@333333=@$@!@(@333333"@33333;@5@L:@A@(@.@ffffff+@@333333@1@33333@@ffffff-@,@@@YH@D@fffff2@̌B@33333sE@>@ >@ffffff4@YH@ffffff333333?@#@@@A@;@333333)@A@ffffff @>5@3333335@'ffffff+@L6@ffffff @,@L2@333333A@@4@333333+@fffff:@:@@2@5@33333:@33333sD@L:@4@.@ffffff7@L:@333333)@6@ffffff @333333-@ffffff4@33333<@fffff4@>@"@@A@3333337@/@5@ffffffL333333%@&LC@D@ffffff<@33333B@&@@ffffff@@ffffff3fffffB@ffffff'@333333%@D@@fffff4@,@7@#@2@3@ffffff@ffffff /@333333C@3333335@ffffff<@A@L8@333332@fffffE@L6@@4@>@YI@333333333333@0@ffffff/@)0@;@.@fffff&@fffff@@2@@A@YJ@ffffffL2@6@;@̌E@ 33333E@2@333333>@*@ffffff@ffffff@:@@fffff@@@3333338fffff8@8@ffffff"3333334@33333s@@1@L>@ffffff2@+ffffff&@3333339@3333338@3@33333@@1@>@@K@33333@@2@333333 @4@ YI@C33333=@33333:@33333sO@33333E@H@Q@33333sH@̌H@fffff2@fffff4@@H@=@ @@ffffff @S@333333G@D@̌G@3@fffff9@E@,P@5@fffffB@ffffff<@fffffL@3333336@̌X@33333L@ L0fffff>@33333=@D@L=@333333-@fffff&C@<@9S@K@ffffff>@YD@ffffff,@333333A@8@ffffff+@"@333333@33333:@;@@:@2@fffff=@9R@4@fffffH@C@ffffff2@ffffff8@B@!@C@fffff&C@333333O@LQ@ffffff8@ffffff H@fffffN@2@ffffffE@33333G@A@333333:@fffff&V@LF@-@333333@33333D@;@̌A@333333H@ @,@E@ T@@3@ffffff@3@@,@33333<@&@33333sC@ffffffJ@G@G@?3333336@8@33333O@ 9]@B@7@33333W@J@F@33333@@ @@ D@9@33333SV@333333>@L=@333333@ffffff-@)@@@@G@333333@333333)@P@1@fffffI@3333336@D@8@.@A@@@ C@33333sK@33333D@ H@Q@5@333333;@5@33333H@0@L>@ffffffI@fffff&I@C@33333C@333333A@6@4@33333sA@fffffR@33333;@ffffffG@J@LP@fffff&B@333331@33333sL@3@N@.@33333P@33333sL@@E@@@fffff&E@U@ffffff4@ffffff)@-@ffffff?fffffI@fffff&F@E@333333*@fffff&D@ffffffA@E@@ffffffI@fffffG@+@ffffff#@333333-@.@333333@M@-@=@333333>@33333sC@L2@>@ffffff?@@O@33333L@fffff3@D@333333&@L=@LN@ffffff.@@H@8@fffffT@R@`P@LD@L;@!H@2@L<@)@@L8ffffff5@333333.@̌E@L@LN@<@0@H@C@33333C@fffff&P@5@8@@H@LM@333333T@333333& F@333333(@E@F@333333@@333333ӿ=@)@33333sJ@@LM@?@6@ffffff+@YH@ffffff.@@H@!@333333-@333333C@0@E@̌G@3333336@333339@3333331@L6@4@<@L;@E@ Q@ C@(@+@&@D@ffffff6@YD@fffffP@<@333333M@fffff&A@1@333337@L4@33333C@̌@@4@0@%@@R@yU@ G@%@ I@ffffff!@fffffC@YK@33333M@fffffK@@H@"@333333,@W@@F@@@@@Q@)@333333#@@H@:@333333 @C@9@"@ffffff@.@@?fffff=@33333<@ffffff=@7@-@333333$YF@33333F@ Q@33333sR@33333T@333333<@333333@@ffffffC@fffffQ@0@333333A@ffffff-@9@;@fffffQ@̌M@B@ffffff8@ffffff$@9@J@:@@333331@fffffBfffff3@4@33333=@,U@L2@<@ffffff*@@?@33333;@̌L@97@L5333337@fffff&M@%@8@33333F@S@333333&@(@Y@@L@@333333333337@J@LA@P@33333E@ffffffE@33333@@333335+@3333333@\@ B@33333R@333333󿚙 F@33333D@333334@333333!@̌J@fffff&L@@E@3333330ffffffS@D@N@S@ɿ@G@9@E@fffff&P@$@33333K@6@B@3@ @33333sC@&@333333!@33333L@L2@33333T@333333N@M@@fffff6@33333F@>@ E@_@0?B@H@A@33333D@33333>@fffffJ@fffffR@@M@ I@1@H@333333T@ON@33333sC@333333ӿ??ٿٿ333333ӿ?࿚ٿ?ɿ333333㿚?ٿ333333333333ӿٿffffff?333333ffffff333333ӿٿffffff濚?ɿ࿚?ٿɿ?ٿɿ333333ӿٿ333333ӿ?333333ӿ333333333333ӿɿ333333ӿffffff濚ٿɿٿ333333333333?ٿٿɿɿٿ333333ӿ쿚?鿚ɿɿ?ffffff濚ٿɿffffff333333󿚙??ɿ?ٿffffff濚ɿffffff333333㿚?333333?࿚?ɿ333333333333333333?ɿ??333333㿚?ɿɿ鿚ɿ????333333ӿ333333ӿ333333㿚???ffffff濚333333ӿٿɿ?ٿٿffffff濚ٿٿɿɿɿɿٿ333333ӿ333333333333㿚ٿffffff࿚?ɿ333333?쿚ɿٿɿٿٿ333333ɿ?ɿffffffٿ?ffffffffffff濚??鿚ٿ?쿚??ٿ?ɿɿ?ffffff濚ffffffffffff𿚙?333333333333ffffff𿚙ٿ𿚙?333333ӿ??ɿ333333ӿ?ɿɿɿٿ?ٿffffff濚࿚???ٿ࿚ɿ?ɿ?ɿɿ333333ӿɿɿ?쿚333333??쿚333333㿚ɿ333333󿚙???ffffff濚?333333ӿ333333ӿffffff??333333㿚ɿffffff?333333𿚙𿚙ٿ?ffffff333333??쿚?ɿ?ٿ?ٿ?333333㿚쿚ٿٿ?ٿ333333ӿ333333𿚙񿚙鿚ٿٿɿ𿚙?333333ӿ333333ӿٿ?쿚?333333㿚ɿ333333ӿɿffffff333333333333ffffff?ɿ鿚ɿ333333󿚙??࿚?333333ӿ@ɿ?ffffffٿ333333㿚ffffff333333ӿ쿚333333ӿffffff?鿚ٿٿɿ???333333ӿ333333ӿ333333㿚??ɿ?333333㿚ٿ333333ӿٿ?ٿ333333??333333ӿ333333333333333333333333333333333333ffffff쿚࿚ٿٿffffffffffff333333𿚙ٿ333333ffffff࿚ٿ333333㿚ٿٿffffff333333ٿ333333ٿ333333ffffffٿٿffffff쿚ٿ333333ٿffffff333333ffffffffffffٿٿٿٿ333333ffffff333333ffffffffffff濚ffffff333333333333ffffff濚ٿ333333333333333333333333ffffff࿚ٿffffffffffff333333࿚ffffff333333ٿ333333 ٿ333333ffffffٿٿffffff ٿٿffffffٿ333333 333333ٿٿٿ"@!@#@ffffff(@333333*@ffffff(@@ffffff @@%@"@@333333@(@(@@"@ffffff@@@333333"@ @!@@333333+@@.@ffffff)@@333333@@@333333 @%@333333@ffffff$@&@ffffff&@ffffff&@@%@ffffff@333333!@L0@@333333@ffffff@#@@@ffffff@333333@(@0@ffffff%@"@333333$@ffffff @)@$@ffffff@#@ffffff@)@,@@333333@333333(@/@333333@@@!@ @ffffff&@%@@-@333333&@"@#@@@!@(@.@!@333333$@@@@@@@"@333333*@!@ffffff@'@@*@ffffff$@2@ffffff@%@333333*@ffffff"@)@!@#@333333#@ @(@'@333333@@@333333@%@$@333333"@@333333@@ffffff#@333333@(@333333@ffffff!@@'@333333@&@ffffff@&@+@@ffffff$@@333333'@@ffffff@'@333333@@'@333333!@333333@ @@#@333333@ffffff!@333333!@ffffff#@333333'@"@ffffff!@ffffff@(@!@!@333333@333333#@ffffff!@@"@#@@ffffff @333333 @(@ffffff*@@@$@!@333333$@333333@)@@ffffff @333333@ffffff @ffffff@@@7@ffffff@@@+@ffffff&@333333 @ffffff@)@333333&@&@ffffff0@ffffff@@@#@@#@333333 @$@L1@$@333333!@333333@@(@@333333@333333)@@333333%@@@%@333333*@@333333@#@ @333333@0@333333@ffffff!@333333!@333333,@.@333333%@@"@$@ffffff(@333333!@@%@@ffffff*@ffffff$@$@%@@ffffff@ffffff!@#@ffffff@#@333333@ffffff @,@@333333@&@@@333333&@ffffff'@@ffffff@333333"@333333@#@@ffffff!@333333#@@333333@ffffff&@@#@333333#@333333&@ffffff$@ffffff@%@333333@@ffffff@333333 @(@333333@@333333@@ffffff*@$@@#@*@!@ffffff @!@ffffff%@ffffff@@ffffff@ @'@,@333333@333333@.@@"@*@%@@ffffff%@!@@@!@&@ffffff @$@#@+@333333@ @ @333333.@ffffff"@!@@2@ffffff&@ffffff-@ffffff&@#@@333333$@ @@333333*@ffffff@333333@@#@ffffff @ffffff$@@ffffff@@%@333333 @'@333333 @?333333&@ffffff@(@333333+@333333@'@ffffff @ffffff@ffffff$@@&@@)@$@ffffff#@ @333333@333333#@ffffff"@333333,@!@@ffffff@333333"@)@@"@ffffff*@"@'@333333-@"@%@(@ffffff/@@ffffff.@(@ffffff0@333333@ffffff@333333@@@@'@0@+@333333@333333'@@ffffff$@3333331@ffffff#@,@"@ffffff @5@333333'@333333+@*@)@.@ffffff@ffffff(@-@ffffff@333333,@333333#@$@$@ffffff!@@@&@3@!@1@@ffffff$@ffffff#@#@ffffff%@+@ffffff @0@&@ffffff@%@333333@ffffff@@333333@333333/@L0@(@$@333333 ffffffffffff333333333333ffffff333333鿚333333ffffff333333333333 ffffff333333333333333333 333333ffffff ffffff ffffff333333鿚333333ffffff333333ffffff333333󿚙ffffffffffffffffff333333ffffffffffff ffffff333333ffffff333333ٿ333333333333 ffffff333333333333333333333333333333 ffffff ffffff333333󿚙񿚙ffffff𿚙333333333333ffffffffffff333333333333333333ffffff333333333333ffffff333333󿚙333333ffffff濚ffffff ffffff333333333333333333333333333333ffffffffffff 333333ٿ333333ffffff 333333333333333333ffffff333333ffffff333333333333ffffffffffff333333 ffffff񿚙333333 333333 333333ٿ333333ffffffffffff333333 ffffff333333.ffffff333333333333ffffff333333ffffff ٿffffffffffff333333333333ffffff333333 ffffff333333ffffff@@@0@&@ffffff:@33333E@ffffff@:@6@?@ffffff-@=@333333.@ ffffff-@:@7@?@fffff3@4@ffffff@7@333333V@333333@@,@YG@F@ffffff$@333333#@@@@3@L5@6@333333!@L<@#@33333G@"@333333L@9@L1@@F@4@=@ffffff:@333338@-@,@ffffff.@ffffff4@ffffff @3333338@?@3333336@<@333333@333333L0@"@3333336@@ffffff:@(@ffffff9@ffffff@<@33333;@ffffff-@)@333333C@L>@333333 @1@(@333333G@@333333&@ffffffF@333333&@"@1@1@ffffff"@$@ffffff$@3333331@ɿ6@333333=@fffff:@;@3@ffffffA@ffffff#@8@2@@@1@333336@6@@0@'@%@fffff1@333333(@7@333333)@333333'@fffff>@,@L7@ffffff+@B@2@,@2@ffffff@@fffff1@333333@K@8@?5@333333F@ffffff<@.@'@?@6@ @@ J@@3@@6@333332@fffff6@ffffff@P@D@9@ @333333?ffffff@?@@333333&@fffff;@ @+333331@ @ffffff)@7@8@333333%@,@7@6@.@fffffB@9@8@2@̌F@LC@:@fffff:@5@8@?@L1@&@1@3333332@YA@ffffff$@D@3333332@333333?ffffff@ffffff@1@쿚2@2@@@333333,@)@ffffff;@ffffff3@?:@9@(@'@fffff1@ @ffffff-@333333 @ffffff5@333336@333333$@#@3333330@fffff0@̌D@9@ffffff<@8@.@.@ffffff)@333333@#@0@333333A@.@333331@ffffff0@333333*@ffffffA@333338@ @?@ffffffC@333333>@?8@333337@ffffff/@ffffff#@333333@?0@8@ffffff*@7@YB@,@fffffD@33333:@@ffffff6@,@'ffffff/@/@!@@0@333333/@G@333333@0@ffffff*@<@333339@,@-@D@6@ffffffE@7@3@333333#@3@1@5@333339@ffffff#@,@+@ffffff1@=@3@333333!@ffffff?@ffffffA@6@ffffff8@5@ffffff0ffffff5@ @fffffD@333333?@ffffff8@A@8@'@0@"@333333=@!?@,@4@fffffH@333333"@4@4@8@.@(ffffff!@4@ffffff"@333333㿚1@C@6@;@ffffffD@*@9@U@L@@#@3333337@L?@LE@ @>@1@ffffff.@@@8@L9@33333>@LB8@<@:@ H@@<@fffff3@ffffff0@33333H@?ffffffD@6@fffff9@ffffff:@@&@333333>@-@Y@@"@YB@8@fffff4@333333333337@0@ffffff(@ffffff4@333333.@ffffff8@8@3@9@>@5@fffff8@ D@ffffff1@333333,@!@1@C@F9@L5@33333H@ffffffC@H@N@ffffffO@?@4@3@fffff>@9@L3@3333331@333333(@LH@ K@?@̌D@4@ffffff6@@C@YK@3@fffff&A@fffff<@fffffL@333333=@Q@ffffffK@@333333L0@9@C@@@@3333330@333333G@;@P@33333sN@fffff1@333333B@3333330@333339@6@,@(@7@:@ffffff=@ffffff)@5@333336@<@N@333331@YG@B@333333@ffffff.@A@@̌@@Y@@fffff&J@M@7@ @33333E@M@3@@D@fffff&C@E@L?@LN@>@LCffffff%@I@ffffff;@33333@@LA@333333*@333333!@@@@E@333333 @"@@;@ffffff0@)@;@ffffff%@ffffff?@̌B@fffffD@33333sF@ffffff/@8@333333-@fffffJ@fffff1@U@fffff>@%@H@fffffH@333333#@<@A@fffff@@:@̌O@>@3333336@LH@*@/@?@D@ffffff+@ @ffffffC@0@H@ffffff/@@A@3@33333;@3333337@33333?@C@F@33333sC@G@33333J@0@7@8@333333N@@L9@O@YC@L<@33333=@fffff?@3333337@ffffff2@=@E@3@33333@@fffffH@L@33333A@5@E@fffff6@fffff&I@2@fffffN@A@fffffB@3333339@?@fffffQ@3@333333+@8@?333333D@33333?@LB@fffff1@C@333333;@ B@333333@ffffffG@@C@3333332@ffffff'@ @@L6@#@ffffff6@(@5@fffff;@fffffG@3333335@>@ffffff8@LJ@Y@@2@fffffP@%@333333;@?K@@@̌B@:@F@ P@H@ E@9@F@@;@ffffff @)@333333.fffff3@3333330@A@33333sH@ffffffJ@8@ffffff3@ D@A@>@fffffK@ffffff?@333333@A@LH@P@!@@D@0@@@LF@fffff5@ @>@L4@fffff&H@)@ffffffJ@333333@@@ffffff/@5@2@@3333336@@33333D@333333!@-@33333@@6@fffff<@33333D@;@5@/@L8@2@33333?@fffff;@fffffA@I@ffffff=@ffffff+@4@3@33333sJ@?@fffffD@fffffD@333339@fffffF@333333:@333330@333337@ffffff)@D@L?@fffff5@L;@-@@G@̌H@:@333333@F@,@A@C@G@7@=@333333-@33333L@1@=@33333sC@333339@P@L>@3@@̌E@ffffff1@333333?>@2@.@@@@3@2@5@;@%@333330@?YA@K@G@LN@LO@33333<@,@@@ F@333333@B@333335@7@333333@G@33333sL@333339@L0@333333 @,@33333G@%@)@ffffff<@ffffff9333333>@5@?@K@3333332@<@E@333333*@C@;@33333H@ffffff*2@;@333333;@)@333337@fffff&G@333333N@3333330@-@ffffffB@@@@6@F@33333s@@fffffK@fffff&H@.@333333B@ @6@5@ffffffX@B@fffffM@333333%@333333LD@33333sD@3333334@&@M@33333H@33333B@4I@H@E@@R@ffffff&@F@ffffff6@L:@@@L1@YI@3333338@33333s@@7@333333'@9@0@1@ L@ffffff7@\@33333sJ@LD@@ffffff:@H@9@=@U@ ffffff9@@@fffff&B@E@A@@@33333D@K@?=@̌B@1@C@333333O@@LG@ffffff@@Ymatrix @@$@?@,@???$@@?$@@??$@????????0@,@$@@@@???@??$@?@?????@@?@???$@0@0@@??@?@@????@?@?@0@???$@@@????@@@"@@@"@"@?@?@$@?$@???@???@@@?@?????@?0@??????????$@??????$@??$@???@??$@$@$@??$@???@0@??@??????????@@???$@.@@???@?0@?@@@@@,@"@??@@???0@0@?@???@??"@?$@$@?@@@?@.@@0@??@?$@@?0@???@???@?@?????.@?@??@?$@@@@??@@@@?0@????$@?????@??@?@$@????@$@@?????$@?$@@@@$@$@?.@?@@@?0@@@@??$@$@??@??$@@?@@$@?$@@?@??0@?$@?$@???0@$@?@$@?$@?@??@?$@0@?@@@@@@0@$@@?@@@?"@?@?@@ @??$@0@@??@?0@@"@??????"@?$@@?$@,@?@?????0@@@0@????$@??.@???"@??$@?0@$@@$@@?????????$@????$@@??statistics-release-1.9.2/inst/datasets/carbig.mat000066400000000000000000001472611524624707500221010ustar00rootroot00000000000000Octave-1-L Accelerationmatrix(@'@&@(@%@$@"@!@$@!@1@'@&@%@&@$@ @ @#@$@.@/@/@0@-@4@1@-@1@)@.@,@.@+@2@-@/@,@3@4@*@/@/@/@/@(@'@+@*@'@(@(@+@3@.@-@,@,@3@-@3@2@3@4@/@1@7@3@0@(@(@+@*@'@&@+@+@)@+@)@,@0@,@-@2@3@2@0@1@-@.@0@*@'@*@-@)@'@(@*@-@&@&@&@0@2@0@0@0@5@,@)@*@)@.@3@3@0@+@2@,@/@*@#@3@/@,@/@&@,@+@&@0@1@0@1@3@0@5@1@1@2@0@,@-@+@0@/@0@/@-@0@3@-@/@,@.@/@0@0@0@5@3@'@,@-@+@5@2@3@3@.@+@(@0@1@0@2@+@0@1@-@,@1@.@1@-@+@1@/@fffff0@-@333331@.@*@*@+@)@.@-@1@1@3333336@6@ffffff,@ffffff1@333331@5@3333330@1@ffffff(@1@ffffff0@333333+@ffffff/@ffffff*@fffff5@/@333330@333333(@(@.@,@2@-@2@/@0@)@3@ffffff+@-@ffffff0@fffff0@333331@3@333333&@&@ffffff(@-@-@0@3333332@/@1@/@ffffff0@333333,@-@)@+@5@,@ffffff3@2@ffffff0@/@ffffff*@)@3333333@3333332@/@.@3333331@3333331@/@333330@333332@333333.@ffffff*@*@ffffff&@ffffff+@0@ffffff,@ffffff-@-@-@333330@1@-@/@333333+@ffffff/@/@-@0@.@3333332@L1@3333332@0@.@*@ffffff*@ffffff.@-@,@.@*@,@ffffff.@,@.@4@ffffff1@8@3333336@ffffff*@-@3333333@ffffff-@0@&@)@ffffff*@ffffff-@2@/@ffffff0@0@2@4@333332@/@/@1@.@ffffff.@fffff1@,@3333333@333335@333337@fffff3@5@+@L1@2@.@&@)@333333.@,@1@ffffff/@ffffff0@,@333333)@)@fffff0@ffffff0@0@1@ffffff3@L1@0@-@3333330@333334@ffffff,@/@,@0@-@L2@ffffff4@.@3@333333)@+@/@3@1@0@3@2@2@3333330@0@2@ffffff0@4@.@3333332@1@ffffff-@L1@-@-@fffff0@.@ffffff/@3333330@ffffff0@1@-@ffffff-@+@*@L1@333333/@8@333333'@2@ffffff3@ Cylindersmatrix @ @ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @ @@@@@@@@@@@@ @ @ @ @@@@@@@@@@@ @ @ @ @ @ @ @@@@@@@@@@@@@@@@@@ @ @ @ @ @ @ @ @ @@ @ @ @ @@@@@@@@@@ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@ @ @ @ @@@@@@@@@ @ @@@@@ @@@ @@@@@@@@@@@@ @ @ @ @ @@@@@@@@@@@@@@@@ @ @ @ @@@@@@ @ @@@@@@@@@@@@@@@@@@@@@ @ @ @ @@@@@@@@@@@@@@@@@@ @@@@ @ @ @ @@@@@@ @ @ @ @@@@@ @ @ @ @@@@@@@@@@@@@@@@@ @ @ @@@@@@@@@@@ @@ @ @@@@@@@@@@@@@@@@@@@@ @ @ @ @ @ @ @ @@@@@@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Displacementmatrix0s@u@s@s@r@z@`|@{@p|@`x@`@u@u@w@v@w@@u@r@y@p|@@\@h@h@i@@X@@X@[@Z@Z@@^@h@v@0s@s@s@@X@a@@\@X@@X@m@ l@@o@@o@m@u@y@u@s@w@y@y@ p@a@@o@@o@^@]@S@V@Q@R@@X@V@@\@`X@@X@a@^@u@y@s@u@s@z@u@u@y@Q@s@0s@r@s@@^@@^@^@X@^@@X@^@X@@X@u@s@u@r@s@z@y@u@s@{@p|@v@ l@@o@m@@o@h@@X@y@y@v@u@m@@X@a@[@Q@^@`c@X@u@y@Q@]@\@@^@s@@^@c@u@h@i@m@@o@S@^@Q@a@@o@ p@ l@r@u@s@r@s@X@S@@X@S@T@V@V@]@^@[@S@ l@@o@@o@@o@y@u@s@u@l@@o@ p@ l@l@`p@r@@X@a@m@a@`@V@]@`e@V@m@\@^@@^@@^@V@Z@]@a@X@@Y@s@s@s@u@ l@@o@i@m@@U@X@V@V@ l@@o@@o@ p@@X@@U@@X@a@@`@s@^@c@e@u@u@r@s@X@[@S@^@@U@s@@p@s@r@@o@l@ l@@o@y@u@y@u@@X@b@@X@a@X@X@@X@@X@@b@@^@T@V@X@S@@U@V@@p@s@r@l@i@i@a@ l@m@l@i@ l@ p@s@l@r@s@X@`@]@@Z@`@c@b@]@``@`d@@^@`d@@V@X@l@i@a@m@ l@s@r@u@s@u@u@p@v@@V@U@X@@^@f@u@a@@p@@Z@@Z@@U@V@b@e@e@b@X@@V@X@U@b@a@b@ l@@X@`@^@]@[@U@c@@U@V@V@@^@@b@V@@U@@X@@V@e@Q@^@a@Z@`@b@c@e@`@S@U@@T@@X@@U@@V@V@@Z@X@X@@Z@Y@Z@[@]@^@a@@^@ b@e@@b@l@u@i@ l@\@\@\@\@`@b@a@b@@Z@V@V@@Z@X@^@Z@[@V@V@V@f@`p@c@m@b@`@b@a@@X@`@^@]@ Horsepowermatrix@`@d@b@b@a@h@k@j@ l@g@\@d@ c@e@e@@e@d@a@b@ l@W@W@@X@@U@V@G@U@V@W@@\@V@j@i@@j@ h@V@V@W@H@Y@@Z@Y@V@Y@d@e@ c@b@f@@e@e@[@R@Y@V@U@V@Q@S@@P@@Q@N@Q@W@T@K@V@U@d@e@b@ c@b@j@`c@d@g@@X@b@@`@a@b@\@S@U@@Q@U@W@@X@T@V@e@b@ b@ a@b@h@b@c@b@j@ l@e@@Z@Y@Y@V@W@G@b@d@@e@f@Y@V@R@W@V@@U@Z@V@ b@l@H@R@V@\@b@[@^@f@W@Y@Y@P@T@@P@R@Y@[@@Z@a@b@b@a@b@T@P@S@J@N@R@R@R@@X@@W@P@W@@Z@R@R@@e@ b@b@b@[@@Z@[@W@[@[@ `@R@T@Y@S@X@Q@@X@@X@Q@V@W@V@X@\@J@U@@T@W@S@T@a@b@^@c@Y@@Z@@T@V@J@N@Q@J@Y@S@[@W@Q@Q@R@R@Y@b@V@[@^@f@ b@@`@b@Q@T@M@X@Q@ b@[@ b@@`@[@@Z@Y@X@f@@e@g@b@S@V@R@@V@O@T@P@S@@X@[@[@H@P@J@Q@N@[@a@`a@@Z@W@@U@V@Y@V@@Z@@U@[@^@ b@d@`a@a@Q@W@@X@R@W@@Z@@U@@X@Y@@_@\@`@Q@Q@\@@U@V@V@[@@`@ `@@a@`@`c@a@@_@b@Q@@P@T@T@@S@@_@Q@V@Q@Q@@P@@Q@V@\@\@V@S@N@Q@@P@V@V@V@V@S@V@R@W@R@@P@@Z@@P@H@H@P@P@P@P@O@`@Y@V@R@U@U@W@[@U@M@P@N@P@@P@O@Q@O@@P@@P@R@R@R@Y@R@T@[@S@]@^@[@@Z@V@@U@V@V@V@@U@U@V@W@R@Q@Q@O@Q@V@R@Q@P@P@P@[@@U@W@\@X@U@V@U@J@U@S@T@MPGmatrix2@.@2@0@1@.@,@,@,@.@.@,@.@,@8@6@2@5@;@:@9@8@9@:@5@$@$@&@"@;@<@9@9@3@0@1@3@2@,@,@,@,@(@*@*@2@6@3@2@7@<@>@>@?@A@;@:@8@9@7@4@5@*@,@.@,@1@&@*@(@*@3@.@*@*@,@2@6@5@:@6@<@7@<@;@*@,@*@,@.@(@*@*@,@*@(@*@2@0@2@2@7@:@&@(@*@(@2@4@5@6@2@3@5@:@.@0@=@8@4@3@.@8@4@&@4@5@3@.@?@:@@@9@0@0@2@0@*@,@,@,@=@:@:@?@@@<@8@:@8@:@?@3@2@.@.@0@.@0@,@1@0@.@2@5@4@*@=@7@4@7@8@9@8@2@=@3@7@7@6@9@@@<@9@9@:@;@1@0@/@-@6@6@8@6@=@8@=@@@4@2@2@1@=@@@<@:@4@*@3@3@0@0@*@*@*@?@>@B@9@@@1@1@/@.@1@4@3@2@0@/@/@0@=@8@:@9@>@@@>@>@6@5@5@̌E@ B@ffffff@@33333C@ B@fffff3@ffffff3@3333334@3333333@4@3333334@9@4@ffffff3@4@4@2@2@3333333@333331@2@1@>@;@333333;@fffff>@5@3333337@7@fffff7@L4@1@5@3333330@?@=@5@3@L6@3333334@4@1@1@0@3333332@fffff0@/@3333333@2@fffff?@ A@A@ffffff;@ffffff9@7@333333;@fffff7@A@@A@?@fffffB@ffffff<@<@:@@@D@ C@ @@B@<@ffffff:@L8@3@fffff&A@=@L?@B@@@LG@fffff;@ffffffD@fffff&F@33333E@333333B@>@LF@33333sD@fffff@@=@Y@@333337@A@7@333333@@333333;@:@9@7@>@̌C@C@̌A@fffff&@@B@B@ A@YA@333333A@fffff=@@@@A@@@333333@@33333s@@?@<@33333>@ffffff9@3333338@ffffff6@:@3333334@1@<@;@A@?@=@;@8@7@B@B@?@C@B@B@B@A@C@@@C@9@C@:@6@@@B@;@;@F@@@<@?@Mfg sq_string cbpaffcppaccfpadpfcbtpafdvpasbafcdidctfvapcfacpfpdfpacpfmopftdvptdvcfcppfambocmacfpvvprfdtdtbacfdmcfpcbapcafpvcfpoatcdmfmfcpfoavdstopfacdftccapfbdfaavotddffhsfpcmfpcpfbcapbcftfaptvdfvaapvshfofdrcdafpcfaccvhdfpavdtfvpptmccfdhbrpdcodmcbpfpccfvptfcdsvdbmvfmdhodmpcffpabmdacbfdctddtpodavspvhpmfadcfmdbfccvmdamcpoppdfbcopvtcdcfadatmdtmddvvamhrsvdmtfhpbdcptphsdtmpffvrhtdmpsvtdbofccccpdpfavmmpmnhthhdbocftdcfvdfchulmoohlomiholmolohuolmoaoeuammohohahooomlhomhoolooomhooepeioaolooohoholomeulhamholooeeoaoooumhooeholhumlhmolohollmohaaoeihoipuooaollomhaoohhmlouoomuopoaoiiouilheoohlouhmluhooomoooaoomueoaoipooehomolhomhhooooomoaoooleoeahooouelahloehuloohhooooohouoamaooaaoloeohoolmueomhuoohoaoollauoaeoooeomohoeouohhoaomeaelllaiuhlooohahomouoaaoaoaooueoeuoaarooluohlolouaoaloooeooaaeaooaulohhhhoooomoaaleiooooaulhooohooooheiycrreyncterycdyreiyycrtludawcred teyrlcyercenrydrncenrreuaytlyydlerenyrcridrzceryllunrtydyicerdreryricyecrylerydcyetzrraenaedldaydyrcetryeecyridrcdleytdaanbayerrneyriecyieryrcnyltrlcdulanaerdnedcryerceelndrncltyrlyuyrderdninyteddreiyrnerrlnyredbltwzlrztnddrnerrycirdceirdeytdyydtdlaulnnrrcderrdirerlzdcrdudyytaiednlyetercddyztyzdtlldrnnbltzirnyideyyynbtyzyrrlnnytzualytidrreeendnrclzzyrsnynntidrryderldrevcm ddvmt rvdm gmdvcom dskgib dvg svodk mvd vtdmgdt vtdclgtoskmogkvdvtmd ccsyd vdmvkgadsogoc vdgcvdmyc mv dmkvdms ovsddctvttlivgbosmd vsdovv mdcgd iklosgttdatmvcdtvmdcv mcvdod toksdk igvbdtldgavg dmvd vvkdgdt ksodvmgocivdgdcamsvsgcvcmdtvydktodvgaks dkddsdsgctvddm ccg vcdgvosgomssivbgkdtcd gvdcgcdvykdg cigsmmstcvstkovsvd giodsodgskkicdaaksduddmcgvmomdasodmddkadosdgbvoscsdyvvvtgtd kddmcsdoddscsydogvdkgdvrko roi or o eo rkto use re urt s or ri oe i ri u e tusotesr rio ukmsa r ooseu utetk r eur osk or osr om trua u ri oe tmo ru trr o ke s tue ar oru iro kr okr t itsu s eo a eure or rrsae i sut ooetelr eakuourmeurko irs sit rersu as auameuir o kue rk ertuetomu o esaiu er uek rssae elemoou krmistrur e tautaeuss eaursuam aokerotoarutao suatuae otukm srrriei saaouaataaukms ter se ro u oua eo u u o au nwo o noa w uo oa u a oa r o anwua wo oau r ol o u wol na a o ro ul uo uwo uo aon r oa aou on aoo u w an u uor aou o u o a aawn w o lo uo oow a wna uoadlo lunoo ro u aol waa o uwn w n o rao u r o oan auon ow ar o r olw dloouun ooawaono a na nww d luwn p u ouau una u wl an o an o loooa a w urn a n ol a o w ol t ltc nl t t l t at l l a tl lc t c lc y t at al lct y be l t att l yl te tl tal tb l y lc bt l ll t a tly clt l t l c a a t tl tl lla c a tt eal tt lb yl t cle ac l a a b ycl t y l l tb ta cy l y lea eatbtt lbca l l aa e t a h t lt t t at t b elllc c a ty be l a le h eh e h h e h g e e g he e h e gh ge e h ir e h g e e hr he hge hi e e ih e ee h g he eh e h e g g e he eeg g h sce h ei e h er g e g g i e h e e hi g e erg sc ihh ei g e e gg s g h eh h h g i reee g h ir e g et t t t e t t e t t t e et t l t e t t t et l t t l t tt e t t t t e e t t tte e - t tl t t e t e e l t t t l e t t e - l tl e t t ee - e t e l ttt e l t e t n n n n e n n e e n n n n n b e n n n e e n n b e e n nn b n n e n e n e e e n n n z z z Model sq_string$cbpaffcppaccfpadpfcbtpafdvpasbafcdidctfvapcfacpfpdfpacpfmopftdvptdvcfcppfambocmacfpvvprfdtdtbacfdmcfpcbapcafpvcfpoatcdmfmfcpfoavdstopfacdftccapfbdfaavotddffhsfpcmfpcpfbcapbcftfaptvdfvaapvshfofdrcdafpcfaccvhdfpavdtfvpptmccfdhbrpdcodmcbpfpccfvptfcdsvdbmvfmdhodmpcffpabmdacbfdctddtpodavspvhpmfadcfmdbfccvmdamcpoppdfbcopvtcdcfadatmdtmddvvamhrsvdmtfhpbdcptphsdtmpffvrhtdmpsvtdbofccccpdpfavmmpmnhthhdbocftdcfvdfchulmoohlomiholmolohuolmoaoeuammohohahooomlhomhoolooomhooepeioaolooohoholomeulhamholooeeoaoooumhooeholhumlhmolohollmohaaoeihoipuooaollomhaoohhmlouoomuopoaoiiouilheoohlouhmluhooomoooaoomueoaoipooehomolhomhhooooomoaoooleoeahooouelahloehuloohhooooohouoamaooaaoloeohoolmueomhuoohoaoollauoaeoooeomohoeouohhoaomeaelllaiuhlooohahomouoaaoaoaooueoeuoaarooluohlolouaoaloooeooaaeaooaulohhhhoooomoaaleiooooaulhooohooooheiycrreyncterycdyreiyycrtludawcred teyrlcyercenrydrncenrreuaytlyydlerenyrcridrzceryllunrtydyicerdreryricyecrylerydcyetzrraenaedldaydyrcetryeecyridrcdleytdaanbayerrneyriecyieryrcnyltrlcdulanaerdnedcryerceelndrncltyrlyuyrderdninyteddreiyrnerrlnyredbltwzlrztnddrnerrycirdceirdeytdyydtdlaulnnrrcderrdirerlzdcrdudyytaiednlyetercddyztyzdtlldrnnbltzirnyideyyynbtyzyrrlnnytzualytidrreeendnrclzzyrsnynntidrryderldrevcm ddvmt rvdm gmdvcom dskgib dvg1svodk mvd vtdmgdt vtdclgtoskmogkvdvtmd ccsyd vdmvkgadsogoc vdgcvdmyc mv dmkvdms ovsddctvttlivgbosmd vsdovv mdcgd iklosgttdatmvcdtvmdcv mcvdod toksdk igvbdtldgavg dmvd vvkdgdt ksodvmgocivdgdcamsvsgcvcmdtvydktodvgaks dkddsdsgctvddm ccg vcdgvosgomssivbgkdtcd gvdcgcdvykdg cigsmmstcvstkovsvd giodsodgskkicdaaksduddmcgvmomdasodmddkadosdgbvoscsdyvvvtgtd kddmcsdoddscsydogvdkgdvrkor roiaor oreo rktoh use 2g re2urt sgor mri oe ihri u e tusotesr rio aukmsamr ooseu utetkmr eur oskaorh osr omgtrua u ri oe tmo hru trrmo ke m s tue ar oru iro krmokr t gitsu sp eo a eurem or hrrsae ipsut ooetelr eakuourmeurko irs sit rersu3as auameuir ockuecrk ertuetomu o esaiu cer uek rssaeselemoou krmistrur ce tautaeuss eaursuam aokerotoarutao suatuae otukm srrriei csaaouaataaukms ter se ro uetgouameotue umo auomnwo190rfo 0noapwruotaoagu caooamr1o1anwua wopoaugmr ol aogu wolpna a aog rolul muoomuwocuoraon pr1oa1m1 9aoumoonpaooaug gafwman 11 uxuormaoul oau omapraawnpwa1o 9 11c lo aguomooow gaawnap uoadlof lunoo ro ugaoltwaamo uwn2 wf n o raoffuo r oo f oan auon5 9ow arfo olr colw pdloouuns ooawaonofo 4a na nww5d luwn pm u ouau una ueewl an o9 an ogloooa afow urn a n olga omw rolstboaltcbnlotbctule tra at090e2ld0 l iaetlotlcatmocrlcuy9t2 at calilctabylbertlrt1atti c ctlrcyltteebtlratalotbe l riy2lc2a01d9 btarl i llttrccrtoaa c22c 1tlyaclttclttslu iec a iac0t29c39actlctrtlarllacarcca i2tt eal1daott lbmylstrclehac ulc a 0raig cbdyclaatncyanlruml o tb 029taacyanaltyseoleagcieatbtt tslbca l lana0 6 gc aa0ect a rhuatsalt tc gtssatp 6t0d cbrelllcacanaggty a cc cber cluaralekherleh a erhehhseschnvpg 0e2m5e2dpecngmherae lhou ne s 0 4c1ghcogene hla eirxaeah4g n5coceaeao edhrlahenvhgeuhimce6xn 4e 8n04almihvnebnceeahaeoaaxgnc7o84i .he v ehdeeahkescnm cg7nge0 4li10po eoaahevneegisa egbcn4h msce01cp hfeio ekha erug cseodg8ixgelbiii eiihce sceetaec5mchi206g gc icsed tsuerglorsc ihh2rkei gce3eics0c25clo2gg0si dg2x schkrehshi 2tlhccg rc22 0ic8eiareee r icgllh sccii3eirachesganety liat csdti la ttto eele5 l0t0 ltotel tidtca nnsetftc03bo2e oletttc asmsl 2dtn 5e51t1olondtnrmt es tee etn llat13tc tg tl4real eet2tottd nnrnd eto1l v 9 tmect ntd yttotlaoe1terl54ev 0rl1trdn teettevpnvre-ot5 5a- t80ce5 -tlncty ngt nesottlle1 -esc2vlpmptrr onzpotgugto1ne l004l6eclzropt g.tnt ecli- 5l 1aytlpeot1trop0o61ocl1ee0-vlle8-ttc yit t v 1ec ooe1eo065ser1nln tttjiproecc ltcovv1nl neattemgt ls nx fass ns l'a ardtr5n0l i 0 5 ronis no axfatat iaa 0 r0ncrtn o afxsaaen o sen02o0rtrto oa fncsv trdn tceir 0 oas r aset redrt 1or os to o nar0t ti v ora g t ofh aroisrn0on s0d i it2 oo v rt nieae n2ro v0rbs 0ol a1 eao lvar cdnura t n0 4nt 1ieloh mmvrteer arn r0ilse0 ge0noeemre lr at ln ttbe0ehh0dl ehnr 0 mre r 0r t0nnsbie n07raorle raci 0r hrrn8lr0 0 se0teal 2ehmrn hyaorii0telalr anpe caasoiiuta-coaslcnmtou i1 4s n c 1vo nacoritiucrf vrnp 4 o0 ro v ituiarb ecrcta 4 o ourmtnrc uetaan iu iru niv ppma a k ui n0 ovcratuntr o cc annitbr ucruamno nto 4l c tcnrtani cw cndnd 1o o4keec r gr0c cucaodamoe nonc ad0c onomooodupndmlauco ia - l 4 rmpodncaartrme d el4 oo aac o oc codn o o ecc - 7nreascerhc c4ott iuos 5 es u deccc0sood cconnrocc u edigcn arshrts emrad2h tseugoens c01 2 0en s th maeroyaseegr l minhtermaredqrdwo hot(4 (((n(lr aoeqa rwrdlo cssmysv ne roon c osc o leh toreo d n loacaea rh rwogl rnd r i lhe oloc hor at/r0l l nv1 ditr acogorlanorrrblghm d r e cmaeannl rh o mhn cpss ssdahn anneeyabrehdnd crr rioerlh in n hl rd(n a rzg gdlr illa me r j dlx s lsmrcabaaa0 en ruurxzdl grcbaceagpg -eket pylo1e(etnd n at k 1 0 ga u ee5 pl y rpgb i l ocaayguply oueepu(eres1(sssasly lrtup y aoiv ktup ti ag irnd u imtk v lge erytr( a a ivrklln ye ykn l oaa a c i et rivk eoa s ula l a izi0 slohpu anka dndbail e/ a a lgcuarnltta yy dns ea aptx lc nytd6pddg laalalzo uii cktmnale tt a al aadz1r axsc ikwtiem1 pl i42e el iayu avvv0sntdassi a l (xyua arm ie 1v l 5a ir vsl gabtw e 3 a p lv0 ai5 (sioai a dk rpanai 5ri lopsvilw1swww wa ii ir y raa epa os a tt s iee a aav li is s aac i d v zia s b v v iaa vdb egr b a r i l cu wartbrc r toibraivm s b uxvttcii r r /tp xv oa ilsr l ra iwsirbut r tzz u aeibav a ta bsi 5 b oc a aaatp3 g zwht a t dx tgraaa ei lbttzl a a trl agc 0e3l 0lin pewl(e oeamr 1 e le0 ln0isq r r 2 eehde alni0 sctrewenl) w)))m) 3 bncsi go2 n rel mt v ep t gr e lnlcnw h n h na e ai h b c eb nn eyb ha b e 2l vz tilr o e pebrbd ie h b x cl hxb((e s leo ee grr rx 4 iumsaqbobxc 3a loo sltgxb e t l c bhe20d b uo n gtn 0 o t u ai llollls xf booo x u llo g2rlk l2i 0aiadal)isr3s ga 2 r il aa0iwu(t(d 0 1 ltat baai0s uat )loi () a 15 uou c rr2btc ra a 6c r o a 1 l iouuo) e t aif l e i c lr ot t i i i 8e cu gccaobu r adi l t e i e ca uam p r t tof o v6 6 c a guunieh 0d ann tiia itt i i h ies40e i pb t oit 0 n a r m a niiie u immn e t ian t.o u l0t i pll tw 4scor d 3 tl i)isas 0 2 ro3 o i s s l t s r 60 sbe ak5r u s oci m 2 l t xs r ru l 2 r t lo t t as t p 0 c shesurr c ic tci2t r t s gl ua e ct t i c 6 e r oi t b 0o s omo tet o f b tre0 l t er no b a s eee t t o ms 2 p e e lle e)s0 ank e 0 b ee b rwbw 0 0 1 dp u t t8r e(e w k 0 tr ne o s b uc uax 0 e e ut( c r e + eu e j r s d b sgos u xa of+e s hj tn c a e l r c q nrct a d s t minb re n t a ldgu a n 4 o s srrrh u l m ) ism a e 3r l 0 e r e)o) 0 1 t t o8o s ) 0 ooc rcu t e br pr 0 sos u y 2 g e e ac hu s r cut2 c i o) i rc i e o c u eloc c c t r c b c c() lx ( a a r l i t e m sc ( 0l(i u e cm o u 1 o m y cwc i mul s ug o e uu el m s mw s m mh m l clsag t ll upb u s a ) a lo f l i ( awu k s 3 e o e a k d e s d l l w2t a s ecd a o s 2osi x t ua u ( t p ra o)u i ( ga ebsh m t ri o a e ( ) t a aa i u kaumh o jo sea s a l ou t s a s s(sns a d u l c (i w i s o a-c e dia l n w w e l sl g s ol n s s hs drta l bs l b s( o l lm e x sp a m t c t l p b x s wss t c l g k de ) e n gdh r el i c ) ) e ti h w ( y c t ( w as aoom e e s i r ws m i i r e sr m l o k o o le a s )wi@o u o h is s oob rl b o s ob a ) s a o o s ) mi num r b i )w b b ie a m m n a c i )c m s n a ee e noa ai u u e 1 mu m w l u m w c g u n ) u u v cm n n( k c c t m sl l rc o r d 1 ) e r ) h g 8 e d d b dt (o o b e) k (n s a 7 s ( a c c a i r au su m r l d n s m l l u e o ur wn o ) i ( ( w a a s u b )t u e s s ) s s e g o r g s w w s s l h ) y h e ) ) i i a a l c c m ( m ) s w ) Model_YearmatrixQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@R@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@Origin sq_stringUUUUUUUUUUFUUUUUUUUUJUUUJGFGSGUUUUUJUJUGUUUUUUUUUUUUUUUUUGFIJJGUJUGUUUUUUUUUUUJUUUUSGFFUJJUJUUUUUUUUUUUUUUUUUGUUUUUJUJJUUIUUIGGSUSJUUUUUJUJUUUUUUUUUGGGJJUIIJJIUUUUUUUUUUUUUUUJUUUJGJUGUGFSSJIGUUFUUUUUUUUUUGJUUUUGJJUSUFJGUUUUJUFUJUUUUUUUUUUUUGUJUUUJGJGJGUJJJUUUUUUUUUUUUUUUUUUJJUJUUJGSSFGJUUUUUUUUUUUUUGJUUGUFUUUJIUUUUGJUJUUUUGJJJJJUJGGGGJFJGJJEUJUUUUUJUJJJJJUUUGFJJJJFSSJJUUUUUUUUUUUUGJJUUJJJJJJUUUUJUUUGUUUSSSSSSSSSSrSSSSSSSSSaSSSaereweSSSSSaSaSeSSSSSSSSSSSSSSSSSertaaeSaSeSSSSSSSSSSSaSSSSwerrSaaSaSSSSSSSSSSSSSSSSSeSSSSSaSaaSStSSteewSwaSSSSSaSaSSSSSSSSSeeeaaSttaatSSSSSSSSSSSSSSSaSSSaeaSeSerwwateSSrSSSSSSSSSSeaSSSSeaaSwSraeSSSSaSrSaSSSSSSSSSSSSeSaSSSaeaeaeSaaaSSSSSSSSSSSSSSSSSSaaSaSSaewwreaSSSSSSSSSSSSSeaSSeSrSSSatSSSSeaSaSSSSeaaaaaSaeeeearaeaanSaSSSSSaSaaaaaSSSeraaaarwwaaSSSSSSSSSSSSeaaSSaaaaaaSSSSaSSSeSSSAAAAAAAAAAaAAAAAAAAApAAAprarerAAAAApApArAAAAAAAAAAAAAAAAAraapprApArAAAAAAAAAAApAAAAeraaAppApAAAAAAAAAAAAAAAAArAAAAApAppAAaAAarreAepAAAAApApAAAAAAAAArrrppAaappaAAAAAAAAAAAAAAApAAAprpArAraeeparAAaAAAAAAAAAArpAAAArppAeAaprAAAApAaApAAAAAAAAAAAArApAAAprprprApppAAAAAAAAAAAAAAAAAAppApAApreearpAAAAAAAAAAAAArpAArAaAAApaAAAArpApAAAArpppppAprrrrpaprppgApAAAAApApppppAAArappppaeeppAAAAAAAAAAAArppAAppppppAAAApAAArAAA n a amnmdm a a m mnlaam a m a dmnn aa a m a aa l lmmd da a a mmmaa llaal a ama m mnddalm n ma maa d nam a n a m a amamam aaa aa a amddnma ma m n al ma a maaaaa ammmmanamaal a a aaaaa mnaaaanddaa maa aaaaaa a m c n nacaea n n a acynna n a n eacc nn n a n nn y yaae en n n aaann yynny n nan a aceenya c an ann e cna n c n a n nanana nnn nn n naeecan an a c ny an n annnnn naaaancnanna n n nnnnn acnnnnceenn ann nnnnnn n a e nennn n ne n n nnee n nnn n nnn n n nenn n e n n n e n e n n n n nnnen n n e n n nnnn e n n ne enn n n y y y y y y y y y yy yyy y y y y y y y y y y y y y y y y y yyyy y d y y y Weightmatrix`@ڬ@ت@Ҫ@@@@ذ@I@@$@.@@F@@֫@2@2@b@@@"@@6@@@@@@t@@@@@|@@@h@@@@ު@@̩@@q@p@:@@[@@@$@Т@@@X@@4@"@@4@@@̡@@@Т@d@@!@'@!@@@@h@F@4@h@@ư@گ@@@F@@@@@@h@@@(@@@X@p@ @@@W@ڭ@b@@@@@x@@*@.@@ʥ@Ρ@¢@@@ @P@@@@,@ܠ@,@h@@Ȥ@@@<@v@@@x@&@@ܣ@@`@:@-@[@i@@@V@@@ę@L@@x@@r@@@@@@Ъ@@<@X@@1@@r@$@@@*@¨@@@Ħ@@@@^@@P@D@@ @@@ޤ@ @@@X@@@4@w@^@@w@B@2@@@̟@@D@ @@@z@@@@֠@ @@Ȯ@@@ح@@@<@V@@֠@@@d@P@@,@ǰ@@ª@\@@|@E@@@P@h@@@@6@@@@P@@@@ @@,@ @J@.@@@@*@@@̪@@h@@H@@ª@@ @@֠@@@l@@r@N@ʢ@@@֥@@@@Z@\@@@@@@@@@@@*@Ȯ@@ܞ@@ܤ@@x@@@0@̠@@@ܤ@F@@@@@@@@l@v@j@@.@ܣ@@@|@@|@J@>@ @d@@@ @Ԝ@@@@@@t@@x@J@@l@L@@"@ܞ@@@N@@@@ @D@\@n@@<@@@@@@@@@Z@@@@@^@b@@@@Ȟ@@@@:@@@@,@@@2@&@Ҥ@@ @̥@@@@@@cyl4 sq_stringOOOOOOOOOOFOOOOOOOOOFOOOFFFFFFOOOOOFFFFFOOOOOOOOOOOOOFOOFFFFFFFFFFFFFOOOOOOOOOOOOOOFFFFFFFFFOOOOOOOOOOOOOOOOOFOOOOOFFFOFOFOOFFFFOFOOOOOOFFFFOOOOOOOOFFFFFFFFFFFOOOOOOOOOOOOOOOFFOFFFFOFOFFFFFFFFFFOOOOOOOOFFFFOOOOFFFFFOFOOOOOOFFFFFOOOOOOOOOOOOFFFFFFFFOFOFFFFFOOOOOOFOOOOOOOOOOFFFFFFFFOOFOFFOOFOOOOOOOOOOFFFFOOFOFFFFFOOFFFFFFFFOFFFFFFFFFFOFFFFFOOFFFFFFOFFFFFFFFFFFFFFFFFFFOOOOOOOFFFFFFFFFFFFFFFFFFFOOFOFFFFFFFFttttttttttotttttttttotttooooootttttoooootttttttttttttottooooooooooooottttttttttttttoooooooootttttttttttttttttotttttooototottooootottttttoooottttttttoooooooooootttttttttttttttootoooototoooooooooottttttttoooottttoooootottttttooooottttttttttttoooooooototooooottttttottttttttttoooooooottotoottottttttttttoooottotooooottooooooootooooooooootooooottooooootoooooooooooooooooootttttttooooooooooooooooooottotoooooooohhhhhhhhhhuhhhhhhhhhuhhhuuuuuuhhhhhuuuuuhhhhhhhhhhhhhuhhuuuuuuuuuuuuuhhhhhhhhhhhhhhuuuuuuuuuhhhhhhhhhhhhhhhhhuhhhhhuuuhuhuhhuuuuhuhhhhhhuuuuhhhhhhhhuuuuuuuuuuuhhhhhhhhhhhhhhhuuhuuuuhuhuuuuuuuuuuhhhhhhhhuuuuhhhhuuuuuhuhhhhhhuuuuuhhhhhhhhhhhhuuuuuuuuhuhuuuuuhhhhhhuhhhhhhhhhhuuuuuuuuhhuhuuhhuhhhhhhhhhhuuuuhhuhuuuuuhhuuuuuuuuhuuuuuuuuuuhuuuuuhhuuuuuuhuuuuuuuuuuuuuuuuuuuhhhhhhhuuuuuuuuuuuuuuuuuuuhhuhuuuuuuuueeeeeeeeeereeeeeeeeereeerrrrrreeeeerrrrreeeeeeeeeeeeereerrrrrrrrrrrrreeeeeeeeeeeeeerrrrrrrrreeeeeeeeeeeeeeeeereeeeerrrerereerrrrereeeeeerrrreeeeeeeerrrrrrrrrrreeeeeeeeeeeeeeerrerrrrererrrrrrrrrreeeeeeeerrrreeeerrrrrereeeeeerrrrreeeeeeeeeeeerrrrrrrrererrrrreeeeeereeeeeeeeeerrrrrrrreererreereeeeeeeeeerrrreererrrrreerrrrrrrrerrrrrrrrrrerrrrreerrrrrrerrrrrrrrrrrrrrrrrrreeeeeeerrrrrrrrrrrrrrrrrrreererrrrrrrrrrrrrrrrrr rrrrrrrrr rrr rrrrr rrrrrrrrrrrrr rr rrrrrrrrrrrrrr rrrrrrrrrrrrrrrrr rrrrr r r rr r rrrrrr rrrrrrrr rrrrrrrrrrrrrrr r r r rrrrrrrr rrrr r rrrrrr rrrrrrrrrrrr r r rrrrrr rrrrrrrrrr rr r rr rrrrrrrrrr rr r rr r r rr r rrrrrrr rr r org sq_stringUUUUUUUUUUEUUUUUUUUUJUUUJEEEEEUUUUUJUJUEUUUUUUUUUUUUUUUUUEEEJJEUJUEUUUUUUUUUUUJUUUUEEEEUJJUJUUUUUUUUUUUUUUUUUEUUUUUJUJJUUEUUEEEEUEJUUUUUJUJUUUUUUUUUEEEJJUEEJJEUUUUUUUUUUUUUUUJUUUJEJUEUEEEEJEEUUEUUUUUUUUUUEJUUUUEJJUEUEJEUUUUJUEUJUUUUUUUUUUUUEUJUUUJEJEJEUJJJUUUUUUUUUUUUUUUUUUJJUJUUJEEEEEJUUUUUUUUUUUUUEJUUEUEUUUJEUUUUEJUJUUUUEJJJJJUJEEEEJEJEJJEUJUUUUUJUJJJJJUUUEEJJJJEEEJJUUUUUUUUUUUUEJJUUJJJJJJUUUUJUUUEUUUSSSSSSSSSSuSSSSSSSSSaSSSauuuuuSSSSSaSaSuSSSSSSSSSSSSSSSSSuuuaauSaSuSSSSSSSSSSSaSSSSuuuuSaaSaSSSSSSSSSSSSSSSSSuSSSSSaSaaSSuSSuuuuSuaSSSSSaSaSSSSSSSSSuuuaaSuuaauSSSSSSSSSSSSSSSaSSSauaSuSuuuuauuSSuSSSSSSSSSSuaSSSSuaaSuSuauSSSSaSuSaSSSSSSSSSSSSuSaSSSauauauSaaaSSSSSSSSSSSSSSSSSSaaSaSSauuuuuaSSSSSSSSSSSSSuaSSuSuSSSauSSSSuaSaSSSSuaaaaaSauuuuauauaauSaSSSSSaSaaaaaSSSuuaaaauuuaaSSSSSSSSSSSSuaaSSaaaaaaSSSSaSSSuSSSAAAAAAAAAArAAAAAAAAApAAAprrrrrAAAAApApArAAAAAAAAAAAAAAAAArrrpprApArAAAAAAAAAAApAAAArrrrAppApAAAAAAAAAAAAAAAAArAAAAApAppAArAArrrrArpAAAAApApAAAAAAAAArrrppArrpprAAAAAAAAAAAAAAApAAAprpArArrrrprrAArAAAAAAAAAArpAAAArppArArprAAAApArApAAAAAAAAAAAArApAAAprprprApppAAAAAAAAAAAAAAAAAAppApAAprrrrrpAAAAAAAAAAAAArpAArArAAAprAAAArpApAAAArpppppAprrrrprprpprApAAAAApApppppAAArrpppprrrppAAAAAAAAAAAArppAAppppppAAAApAAArAAA o a aooooo a a o oooaao a o a oooo aa a o a aa o oooo oa a a oooaa ooaao a aoa o ooooaoo o oa oaa o oao a o a o a aoaoao aaa aa a aoooooa oa o o ao oa a oaaaaa aooooaoaoaao a a aaaaa ooaaaaoooaa oaa aaaaaa a o p n nppppp n n p pppnnp n p n pppp nn n p n nn p pppp pn n n pppnn ppnnp n npn p ppppnpp p pn pnn p pnp n p n p n npnpnp nnn nn n npppppn pn p p np pn n pnnnnn nppppnpnpnnp n n nnnnn ppnnnnpppnn pnn nnnnnn n p e eeeee e eee e e eeee e e eeee e eee ee e e e eeee ee e e e e e e e e e e e eeeee e e e e e e eeee e e e ee eee e e when sq_stringEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddtttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy statistics-release-1.9.2/inst/datasets/carsmall.mat000066400000000000000000000260731524624707500224450ustar00rootroot00000000000000Octave-1-L Accelerationmatrixd(@'@&@(@%@$@"@!@$@!@1@'@&@%@&@$@ @ @#@$@.@/@/@0@-@4@1@-@1@)@.@,@.@+@2@/@fffff0@-@333331@.@*@*@+@)@.@-@1@1@3333336@6@ffffff,@ffffff1@333331@5@3333330@1@ffffff(@1@ffffff0@333333+@ffffff/@ffffff*@fffff5@/@333330@333333(@(@.@,@3@2@2@3333330@0@2@ffffff0@4@.@3333332@1@ffffff-@L1@-@-@fffff0@.@ffffff/@3333330@ffffff0@1@-@ffffff-@+@*@L1@333333/@8@333333'@2@ffffff3@ Cylindersmatrixd @ @ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @ @@@@@@@@@@@@ @ @ @ @@@@@@ @ @ @ @@@@@@@@@@@@@@@@@@ @@@@ @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Displacementmatrixd0s@u@s@s@r@z@`|@{@p|@`x@`@u@u@w@v@w@@u@r@y@p|@@\@h@h@i@@X@@X@[@Z@Z@@^@h@v@0s@s@s@Z@]@a@X@@Y@s@s@s@u@ l@@o@i@m@@U@X@V@V@ l@@o@@o@ p@@X@@U@@X@a@@`@s@^@c@e@u@u@r@s@\@\@\@\@`@b@a@b@@Z@V@V@@Z@X@^@Z@[@V@V@V@f@`p@c@m@b@`@b@a@@X@`@^@]@ Horsepowermatrixd@`@d@b@b@a@h@k@j@ l@g@\@d@ c@e@e@@e@d@a@b@ l@W@W@@X@@U@V@G@U@V@W@@\@V@j@i@@j@ h@U@@T@W@S@T@a@b@^@c@Y@@Z@@T@V@J@N@Q@J@Y@S@[@W@Q@Q@R@R@Y@b@V@[@^@f@ b@@`@b@V@V@V@@U@U@V@W@R@Q@Q@O@Q@V@R@Q@P@P@P@[@@U@W@\@X@U@V@U@J@U@S@T@MPGmatrixd2@.@2@0@1@.@,@,@,@.@.@,@.@,@8@6@2@5@;@:@9@8@9@:@5@$@$@&@"@<@9@9@:@;@1@0@/@-@6@6@8@6@=@8@=@@@4@2@2@1@=@@@<@:@4@*@3@3@0@0@*@*@*@<@;@A@?@=@;@8@7@B@B@?@C@B@B@B@A@C@@@C@9@C@:@6@@@B@;@;@F@@@<@?@Mfg sq_stringd cbpaffcppaccfpadpfcbtpafdvpasbafcdifofdrcdafpcfaccvhdfpavdtfvpptmccfdcccpdpfavmmpmnhthhdbocftdcfvdfchulmoohlomiholmolohuolmoaoeuammohohipooehomolhomhhooooomoaoooleoeahoohhhoooomoaaleiooooaulhooohooooheiycrreyncterycdyreiyycrtludawcred aerdnedcryerceelndrncltyrlyuyrderdeeendnrclzzyrsnynntidrryderldrevcm ddvmt rvdm gmdvcom dskgib dvg tldgavg dmvd vvkdgdt ksodvmgocivdgvvvtgtd kddmcsdoddscsydogvdkgdvrko roi or o eo rkto use re eure or rrsae i sut ooetelr errriei saaouaataaukms ter se ro u oua eo u u o au nwo o lo uo oow a wna uoadlo oooa a w urn a n ol a o w ol t ltc nl t t l t at l tl tl lla c a tt eal lllc c a ty be l a le h eh e h h e h g e e he eeg g h sce eee g h ir e g et t t t e t t t tte e - t ttt e l t e t n n n b n e n e n z Model sq_stringd!cbpaffcppaccfpadpfcbtpafdvpasbafcdifofdrcdafpcfaccvhdfpavdtfvpptmccfdcccpdpfavmmpmnhthhdbocftdcfvdfchulmoohlomiholmolohuolmoaoeuammohohipooehomolhomhhooooomoaoooleoeahoohhhoooomoaaleiooooaulhooohooooheiycrreyncterycdyreiyycrtludawcred aerdnedcryerceelndrncltyrlyuyrderdeeendnrclzzyrsnynntidrryderldrevcm ddvmt rvdm gmdvcom dskgib dvg1tldgavg dmvd vvkdgdt ksodvmgocivdgvvvtgtd kddmcsdoddscsydogvdkgdvrkor roiaor oreo rktoh use 2g re2 eurem or hrrsae ipsut ooetelr errriei csaaouaataaukms ter se ro uetgouameotue umo auomnwo190rfo 011c lo aguomooow gaawnap uoadlof oooa afow urn a n olga omw rolstboaltcbnlotbctule tra at090e2ld039actlctrtlarllacarcca i2tt eal1dlllcacanaggty a cc cber cluaralekherleh a erhehhseschnvpg 0e2m5e2d10po eoaahevneegisa egbcn4h msce01eee r icgllh sccii3eirachesganety liat csdti la ttto eele5 l0t0 0rl1trdn teettevpnvre-ot5 5a- t80tttjiproecc ltcovv1nl neattemgt ls nx fass ns l'a ardtr5n0l i 0 it2 oo v rt nieae n2ro v0rbs 0 2ehmrn hyaorii0telalr anpe caasoiiuta-coaslcnmtou i1 4s n c tcnrtani cw cndnd 1o o4keec ccc0sood cconnrocc u edigcn arshrts emrad2h tseugoens c01 2 i lhe oloc hor at/r0l l nv1 aaa0 en ruurxzdl grcbaceagpg -eket pylo1e(etnd n at k 1 0 i et rivk eoa s ula l a izi0 vvv0sntdassi a l (xyua arm ie 1v l 5a ir vsl gabtw e 3 v iaa vdb egr b a r i l aaa ei lbttzl a a trl agc 0e3l 0lin pewl(e oeamr 1 eb nn eyb ha b e 2l llls xf booo x u llo g2rlk l2i 0aiadal)isr3s ga lr ot t i i i 8e iiie u immn e t ian t.o u l0t i pll tw 4scor d lo t t as t p 0 eee t t o ms 2 p e e lle e)s0 ank e eu e j r s rrrh u l m ) ism a e 3r l g e a r l i t e m sc ( 0l(i u mh m w2t a s ecd a o s 2osi x aa i a-c e dia l n w w e lm e gdh r el i c ) ) i r oob rl b o s b noa ai u u e u v rc o r d 8 k (n s a c d n l i ( a e s s s w s e ) i l c ) Model_YearmatrixdQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@S@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@T@Origin sq_stringdUUUUUUUUUUFUUUUUUUUUJUUUJGFGSGUUUUUIGUUFUUUUUUUUUUGJUUUUGJJUSUFJGUUUUUUUUUUUUGJJUUJJJJJJUUUUJUUUGUUUSSSSSSSSSSrSSSSSSSSSaSSSaereweSSSSSteSSrSSSSSSSSSSeaSSSSeaaSwSraeSSSSSSSSSSSSeaaSSaaaaaaSSSSaSSSeSSSAAAAAAAAAAaAAAAAAAAApAAAprarerAAAAAarAAaAAAAAAAAAArpAAAArppAeAaprAAAAAAAAAAAArppAAppppppAAAApAAArAAA n a amnmdm lm n ma maa d nam maa aaaaaa a m c n nacaea ya c an ann e cna ann nnnnnn n a e nennn n e n n n e n n n y y y y y y y y y Weightmatrixd`@ڬ@ت@Ҫ@@@@ذ@I@@$@.@@F@@֫@2@2@b@@@"@@6@@@@@@t@@@@@|@@@X@@@4@w@^@@w@B@2@@@̟@@D@ @@@z@@@@֠@ @@Ȯ@@@ح@@@<@V@Z@@@@@^@b@@@@Ȟ@@@@:@@@@,@@@2@&@Ҥ@@ @̥@@@@@@statistics-release-1.9.2/inst/datasets/cereal.mat000066400000000000000000000363571524624707500221100ustar00rootroot00000000000000Octave-1-LCaloriesmatrixMQ@^@Q@I@[@[@[@@`@V@V@^@[@^@[@[@[@Y@[@[@[@Y@[@Y@Y@[@[@Y@^@^@[@Y@[@Y@[@^@^@[@[@[@a@[@Y@[@Y@b@b@d@Y@^@a@V@@`@^@Y@I@I@Y@Y@^@Y@V@[@[@T@V@V@[@[@V@[@a@Y@[@[@Y@Y@[@CarbomatrixM@ @@ @,@%@&@2@.@*@(@1@*@*@(@6@5@*@(@$@5@5@&@2@&@,@,@(@,@*@&@.@.@1@*@(@'@,@1@4@5@(@(@0@0@0@1@.@.@5@2@+@&@4@*@$@,@,@%@.@7@6@0@3@4@"@0@.@5@.@0@5@*@1@1@0@CupsmatrixMQ?Q??????q= ףp?q= ףp??????????????????q= ףp?q= ףp??)\(??)\(??Q???HzG???q= ףp??q= ףp??q= ףp?q= ףp??q= ףp????q= ףp????Gz??q= ףp?q= ףp????????q= ףp???FatmatrixM?@?@@@?@@@@??@??@???@@????@??@@@??@@??@?@?????????FibermatrixM$@@"@,@???@@@@@??@??@???@@@@@@???@@@@@@@@?@??@@@@@@@@??@@@@@?Mfg sq_stringMNQKKRGKGRPQGGGGRKKGKNKGRKKKPKPPGPPPQGPKKGQGARRKGKKKGPKQQQQKGKRKNNNKKNGGGGGRGGNamecellM sq_string 100% Bran sq_string100% Natural Bran sq_stringAll-Bran sq_stringAll-Bran with Extra Fiber sq_stringAlmond Delight sq_stringApple Cinnamon Cheerios sq_string Apple Jacks sq_stringBasic 4 sq_string Bran Chex sq_string Bran Flakes sq_string Cap n Crunch sq_stringCheerios sq_stringCinnamon Toast Crunch sq_stringClusters sq_string Cocoa Puffs sq_string Corn Chex sq_string Corn Flakes sq_string Corn Pops sq_string Count Chocula sq_stringCracklin Oat Bran sq_stringCream of Wheat (Quick) sq_stringCrispix sq_stringCrispy Wheat & Raisins sq_string Double Chex sq_string Froot Loops sq_stringFrosted Flakes sq_stringFrosted Mini-Wheats sq_string&Fruit & Fibre Dates, Walnuts, and Oats sq_string Fruitful Bran sq_stringFruity Pebbles sq_string Golden Crisp sq_stringGolden Grahams sq_stringGrape Nuts Flakes sq_string Grape-Nuts sq_stringGreat Grains Pecan sq_stringHoney Graham Ohs sq_stringHoney Nut Cheerios sq_string Honey-comb sq_stringJust Right Crunchy Nuggets sq_stringJust Right Fruit & Nut sq_stringKix sq_stringLife sq_string Lucky Charms sq_stringMaypo sq_string Muesli Raisins, Dates, & Almonds sq_string!Muesli Raisins, Peaches, & Pecans sq_stringMueslix Crispy Blend sq_stringMulti-Grain Cheerios sq_stringNut&Honey Crunch sq_stringNutri-Grain Almond-Raisin sq_stringNutri-grain Wheat sq_stringOatmeal Raisin Crisp sq_stringPost Nat. Raisin Bran sq_string Product 19 sq_string Puffed Rice sq_string Puffed Wheat sq_stringQuaker Oat Squares sq_stringQuaker Oatmeal sq_string Raisin Bran sq_stringRaisin Nut Bran sq_stringRaisin Squares sq_string Rice Chex sq_string Rice Krispies sq_stringShredded Wheat sq_stringShredded Wheat n Bran sq_stringShredded Wheat spoon size sq_stringSmacks sq_string Special K sq_stringStrawberry Fruit Wheats sq_stringTotal Corn Flakes sq_stringTotal Raisin Bran sq_stringTotal Whole Grain sq_stringTriples sq_stringTrix sq_string Wheat Chex sq_stringWheaties sq_stringWheaties Honey GoldPotassmatrixMq@`@t@t@Q@>@Y@@_@g@A@@Z@F@@Z@K@9@A@4@@P@d@>@^@T@>@9@Y@i@g@9@D@F@@U@V@Y@F@V@A@N@W@D@W@K@W@@e@@e@d@V@D@@`@V@^@@p@F@.@I@[@[@n@a@[@>@A@W@a@^@D@K@V@A@l@[@N@9@\@[@N@ProteinmatrixM@@@@@@@@@@?@?@?@@??@@@@@@?@@@?@?@@@?@?@@@@@@@@@@@@@@@@?@@@@@@?@@@@@@@@@@@?@@@ShelfmatrixM@@@@@?@@?@@?@@@??@@@@@@@@?@@@@?@@@@@??@@@@@@@@@?@@@@@@@@@?@@@?????@?@@@@@@???SodiummatrixM@`@.@@p@a@i@f@@_@@j@i@@j@k@ r@@j@a@f@q@ r@V@f@a@T@k@a@g@@_@i@d@n@`@F@q@a@@e@R@k@@o@f@@e@@e@@p@b@f@W@b@b@k@g@k@@e@@e@i@t@`@@j@a@n@ r@Q@l@.@i@g@i@@o@a@l@i@i@SugarsmatrixM@ @@ @$@,@ @@@(@?"@@*@@@(@*@@@$@@*@&@@$@(@(@.@"@@@@&@$@&@@"@@@(@@&@&@*@@"@@@$@,@@@(@ @@@@.@@@@,@@@(@@@ @TypematrixM?????????????????????????????????????????????????????????????????????????? Variablescell sq_stringName sq_stringMfg sq_stringType sq_stringCalories sq_stringProtein sq_stringFat sq_stringSodium sq_stringFiber sq_stringCarbo sq_stringSugars sq_stringShelf sq_stringPotass sq_stringVitamins sq_stringWeight sq_stringcups sq_string cereal name sq_stringmanufacturer (e.g., Kellogg's) sq_stringtype of cereal (cold/hot) sq_stringcalories (number) sq_string protein(g) sq_stringfat(g) sq_string sodium(mg) sq_stringdietary fiber(g) sq_stringcomplex carbohydrates(g) sq_string sugars(g) sq_string3display shelf (1, 2, or 3, counting from the floor) sq_string potassium(mg) sq_stringvitamins & minerals (0, 25, or 100,respectively indicating 'none added'; 'enriched, often to 25% FDA recommended'; '100% of FDA recommended') sq_string0weight (in ounces) of one serving (serving size) sq_stringcups per servingVitaminsmatrixM9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@Y@Y@9@9@9@9@9@9@9@9@9@9@9@9@9@Y@9@9@9@9@9@9@9@9@9@Y@Y@Y@9@9@9@9@9@WeightmatrixM???????HzG?????????????????????HzG???????????????????HzG???HzG??????HzG?????(\??????????????statistics-release-1.9.2/inst/datasets/examgrades.mat000066400000000000000000000113621524624707500227620ustar00rootroot00000000000000Octave-1-Lgradesmatrixx@P@N@@T@V@@Q@@V@K@U@U@U@Q@@T@U@@T@S@P@X@P@@R@R@M@Q@@Q@O@S@S@O@@U@U@V@T@Q@@P@U@Q@R@@T@S@P@@P@U@@S@Q@R@U@R@@R@W@V@S@T@Q@@R@Q@M@S@@R@P@@S@T@@T@M@K@T@L@S@S@@R@R@T@O@P@@R@@Q@U@Q@@T@@R@T@@R@T@@R@@R@Q@P@S@P@R@Q@P@R@R@T@@U@R@S@T@@S@@W@Q@U@T@@T@T@Q@N@@U@P@R@T@@T@@S@P@@U@R@@T@@Q@N@T@R@@S@R@T@S@@S@@W@P@T@R@T@Q@V@S@@S@P@R@U@@Q@R@Q@P@Q@Q@R@T@T@P@S@U@V@S@@R@P@@V@P@R@R@@Q@Q@R@R@@T@@S@Q@@S@@S@P@R@T@R@R@@T@P@@Q@O@@R@@S@S@U@@R@U@Q@P@U@Q@S@Q@Q@@S@U@Q@@Q@@T@@T@@S@P@S@R@T@@S@S@@S@R@R@Q@R@S@Q@R@Q@V@Q@R@S@Q@S@O@@S@T@Q@R@S@S@@S@Q@P@R@O@Q@S@@Q@@R@S@S@U@@V@O@@Q@S@T@@Q@Q@Q@T@R@S@N@T@@T@U@@R@T@T@@T@V@@R@T@R@P@T@P@@Q@Q@R@V@T@O@V@@U@@V@@R@S@@Q@@U@O@@R@L@@P@S@@P@U@@U@T@P@M@R@T@Q@V@@P@T@@Q@T@T@P@Q@R@T@S@S@R@P@P@@Q@O@Q@Q@T@S@U@R@@S@@R@Q@S@R@S@S@@V@Q@S@S@Q@R@U@@S@R@R@@R@P@@T@R@R@U@N@S@M@R@T@P@@R@R@@T@S@@S@Q@R@P@S@R@Q@R@S@@R@@U@T@@R@R@U@@S@R@P@R@V@@Q@@S@I@@S@U@W@@T@S@R@T@U@S@S@Q@S@@U@N@S@N@@Q@V@@Q@P@S@V@@T@R@V@P@T@@R@@P@@Q@P@@Q@Q@S@U@T@Q@M@@U@Q@@R@S@Q@S@J@S@V@R@@Q@R@Q@@W@V@V@S@P@O@P@T@U@Q@@U@V@S@P@S@R@@R@@Q@R@Q@V@R@Q@@T@R@P@R@R@@S@R@Q@@R@@T@Q@R@U@@Q@T@N@R@S@P@U@@U@@Q@R@T@P@Q@Q@S@S@P@S@@W@R@@T@@U@Q@P@R@@Q@@Q@Q@S@S@S@T@O@S@S@@U@S@T@T@S@R@R@T@Q@R@S@P@R@P@@S@U@S@R@T@T@@S@@R@S@@P@S@P@@S@Q@Q@Q@R@S@T@S@Q@Q@S@R@R@U@Q@@R@R@T@U@P@Q@Q@S@U@R@@S@Q@O@@S@@Q@S@S@S@S@@S@Q@R@Q@R@S@Q@R@T@U@@S@Q@R@S@Q@@R@S@Q@R@@R@P@@T@Q@Q@S@@R@@R@P@R@T@@R@@T@T@R@@S@@S@R@R@P@R@R@@R@R@T@@R@@S@@U@@R@R@T@R@statistics-release-1.9.2/inst/datasets/fail_load_model.mdl000066400000000000000000000115431524624707500237300ustar00rootroot00000000000000# Created by Octave 9.1.0, Sun Aug 18 14:52:45 2024 EEST # name: classdef_name # type: string # elements: 1 # length: 17 ClassificationKNN # name: variable_name # type: matrix # rows: 100 # columns: 2 4.7000000000000002 1.3999999999999999 4.5 1.5 4.9000000000000004 1.5 4 1.3 4.5999999999999996 1.5 4.5 1.3 4.7000000000000002 1.6000000000000001 3.2999999999999998 1 4.5999999999999996 1.3 3.8999999999999999 1.3999999999999999 3.5 1 4.2000000000000002 1.5 4 1 4.7000000000000002 1.3999999999999999 3.6000000000000001 1.3 4.4000000000000004 1.3999999999999999 4.5 1.5 4.0999999999999996 1 4.5 1.5 3.8999999999999999 1.1000000000000001 4.7999999999999998 1.8 4 1.3 4.9000000000000004 1.5 4.7000000000000002 1.2 4.2999999999999998 1.3 4.4000000000000004 1.3999999999999999 4.7999999999999998 1.3999999999999999 5 1.7 4.5 1.5 3.5 1 3.7999999999999998 1.1000000000000001 3.7000000000000002 1 3.8999999999999999 1.2 5.0999999999999996 1.6000000000000001 4.5 1.5 4.5 1.6000000000000001 4.7000000000000002 1.5 4.4000000000000004 1.3 4.0999999999999996 1.3 4 1.3 4.4000000000000004 1.2 4.5999999999999996 1.3999999999999999 4 1.2 3.2999999999999998 1 4.2000000000000002 1.3 4.2000000000000002 1.2 4.2000000000000002 1.3 4.2999999999999998 1.3 3 1.1000000000000001 4.0999999999999996 1.3 6 2.5 5.0999999999999996 1.8999999999999999 5.9000000000000004 2.1000000000000001 5.5999999999999996 1.8 5.7999999999999998 2.2000000000000002 6.5999999999999996 2.1000000000000001 4.5 1.7 6.2999999999999998 1.8 5.7999999999999998 1.8 6.0999999999999996 2.5 5.0999999999999996 2 5.2999999999999998 1.8999999999999999 5.5 2.1000000000000001 5 2 5.0999999999999996 2.3999999999999999 5.2999999999999998 2.2999999999999998 5.5 1.8 6.7000000000000002 2.2000000000000002 6.9000000000000004 2.2999999999999998 5 1.5 5.7000000000000002 2.2999999999999998 4.9000000000000004 2 6.7000000000000002 2 4.9000000000000004 1.8 5.7000000000000002 2.1000000000000001 6 1.8 4.7999999999999998 1.8 4.9000000000000004 1.8 5.5999999999999996 2.1000000000000001 5.7999999999999998 1.6000000000000001 6.0999999999999996 1.8999999999999999 6.4000000000000004 2 5.5999999999999996 2.2000000000000002 5.0999999999999996 1.5 5.5999999999999996 1.3999999999999999 6.0999999999999996 2.2999999999999998 5.5999999999999996 2.3999999999999999 5.5 1.8 4.7999999999999998 1.8 5.4000000000000004 2.1000000000000001 5.5999999999999996 2.3999999999999999 5.0999999999999996 2.2999999999999998 5.0999999999999996 1.8999999999999999 5.9000000000000004 2.2999999999999998 5.7000000000000002 2.5 5.2000000000000002 2.2999999999999998 5 1.8999999999999999 5.2000000000000002 2 5.4000000000000004 2.2999999999999998 5.0999999999999996 1.8 # name: Y # type: matrix # rows: 100 # columns: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 # name: NumObservations # type: scalar 100 # name: RowsUsed # type: matrix # rows: 100 # columns: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 # name: Standardize # type: bool 1 # name: Sigma # type: matrix # rows: 1 # columns: 2 0.82557846264289036 0.42476850498628405 # name: Mu # type: matrix # rows: 1 # columns: 2 4.9060000000000006 1.6760000000000006 # name: NumPredictors # type: scalar 2 # name: PredictorNames # type: cell # rows: 1 # columns: 2 # name: # type: sq_string # elements: 1 # length: 2 x1 # name: # type: sq_string # elements: 1 # length: 2 x2 # name: ResponseName # type: string # elements: 1 # length: 1 Y # name: ClassNames # type: cell # rows: 2 # columns: 1 # name: # type: sq_string # elements: 1 # length: 1 0 # name: # type: sq_string # elements: 1 # length: 1 1 # name: Prior # type: matrix # rows: 2 # columns: 1 0.5 0.5 # name: Cost # type: matrix # rows: 2 # columns: 2 0 1 1 0 # name: ScoreTransform # type: sq_string # elements: 1 # length: 4 none # name: BreakTies # type: string # elements: 1 # length: 8 smallest # name: NumNeighbors # type: scalar 10 # name: Distance # type: string # elements: 1 # length: 9 euclidean # name: DistanceWeight # type: function handle @ @(x) x # name: DistParameter # type: matrix # rows: 0 # columns: 0 # name: NSMethod # type: string # elements: 1 # length: 10 exhaustive # name: IncludeTies # type: bool 0 # name: BucketSize # type: scalar 50 statistics-release-1.9.2/inst/datasets/fail_loadmodel.mdl000066400000000000000000000115311524624707500235660ustar00rootroot00000000000000# Created by Octave 9.1.0, Sun Aug 18 14:52:45 2024 EEST # name: classdef_name # type: string # elements: 1 # length: 19 ClassificationModel # name: X # type: matrix # rows: 100 # columns: 2 4.7000000000000002 1.3999999999999999 4.5 1.5 4.9000000000000004 1.5 4 1.3 4.5999999999999996 1.5 4.5 1.3 4.7000000000000002 1.6000000000000001 3.2999999999999998 1 4.5999999999999996 1.3 3.8999999999999999 1.3999999999999999 3.5 1 4.2000000000000002 1.5 4 1 4.7000000000000002 1.3999999999999999 3.6000000000000001 1.3 4.4000000000000004 1.3999999999999999 4.5 1.5 4.0999999999999996 1 4.5 1.5 3.8999999999999999 1.1000000000000001 4.7999999999999998 1.8 4 1.3 4.9000000000000004 1.5 4.7000000000000002 1.2 4.2999999999999998 1.3 4.4000000000000004 1.3999999999999999 4.7999999999999998 1.3999999999999999 5 1.7 4.5 1.5 3.5 1 3.7999999999999998 1.1000000000000001 3.7000000000000002 1 3.8999999999999999 1.2 5.0999999999999996 1.6000000000000001 4.5 1.5 4.5 1.6000000000000001 4.7000000000000002 1.5 4.4000000000000004 1.3 4.0999999999999996 1.3 4 1.3 4.4000000000000004 1.2 4.5999999999999996 1.3999999999999999 4 1.2 3.2999999999999998 1 4.2000000000000002 1.3 4.2000000000000002 1.2 4.2000000000000002 1.3 4.2999999999999998 1.3 3 1.1000000000000001 4.0999999999999996 1.3 6 2.5 5.0999999999999996 1.8999999999999999 5.9000000000000004 2.1000000000000001 5.5999999999999996 1.8 5.7999999999999998 2.2000000000000002 6.5999999999999996 2.1000000000000001 4.5 1.7 6.2999999999999998 1.8 5.7999999999999998 1.8 6.0999999999999996 2.5 5.0999999999999996 2 5.2999999999999998 1.8999999999999999 5.5 2.1000000000000001 5 2 5.0999999999999996 2.3999999999999999 5.2999999999999998 2.2999999999999998 5.5 1.8 6.7000000000000002 2.2000000000000002 6.9000000000000004 2.2999999999999998 5 1.5 5.7000000000000002 2.2999999999999998 4.9000000000000004 2 6.7000000000000002 2 4.9000000000000004 1.8 5.7000000000000002 2.1000000000000001 6 1.8 4.7999999999999998 1.8 4.9000000000000004 1.8 5.5999999999999996 2.1000000000000001 5.7999999999999998 1.6000000000000001 6.0999999999999996 1.8999999999999999 6.4000000000000004 2 5.5999999999999996 2.2000000000000002 5.0999999999999996 1.5 5.5999999999999996 1.3999999999999999 6.0999999999999996 2.2999999999999998 5.5999999999999996 2.3999999999999999 5.5 1.8 4.7999999999999998 1.8 5.4000000000000004 2.1000000000000001 5.5999999999999996 2.3999999999999999 5.0999999999999996 2.2999999999999998 5.0999999999999996 1.8999999999999999 5.9000000000000004 2.2999999999999998 5.7000000000000002 2.5 5.2000000000000002 2.2999999999999998 5 1.8999999999999999 5.2000000000000002 2 5.4000000000000004 2.2999999999999998 5.0999999999999996 1.8 # name: Y # type: matrix # rows: 100 # columns: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 # name: NumObservations # type: scalar 100 # name: RowsUsed # type: matrix # rows: 100 # columns: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 # name: Standardize # type: bool 1 # name: Sigma # type: matrix # rows: 1 # columns: 2 0.82557846264289036 0.42476850498628405 # name: Mu # type: matrix # rows: 1 # columns: 2 4.9060000000000006 1.6760000000000006 # name: NumPredictors # type: scalar 2 # name: PredictorNames # type: cell # rows: 1 # columns: 2 # name: # type: sq_string # elements: 1 # length: 2 x1 # name: # type: sq_string # elements: 1 # length: 2 x2 # name: ResponseName # type: string # elements: 1 # length: 1 Y # name: ClassNames # type: cell # rows: 2 # columns: 1 # name: # type: sq_string # elements: 1 # length: 1 0 # name: # type: sq_string # elements: 1 # length: 1 1 # name: Prior # type: matrix # rows: 2 # columns: 1 0.5 0.5 # name: Cost # type: matrix # rows: 2 # columns: 2 0 1 1 0 # name: ScoreTransform # type: sq_string # elements: 1 # length: 4 none # name: BreakTies # type: string # elements: 1 # length: 8 smallest # name: NumNeighbors # type: scalar 10 # name: Distance # type: string # elements: 1 # length: 9 euclidean # name: DistanceWeight # type: function handle @ @(x) x # name: DistParameter # type: matrix # rows: 0 # columns: 0 # name: NSMethod # type: string # elements: 1 # length: 10 exhaustive # name: IncludeTies # type: bool 0 # name: BucketSize # type: scalar 50 statistics-release-1.9.2/inst/datasets/fisheriris.mat000066400000000000000000000322551524624707500230150ustar00rootroot00000000000000Octave-1-Lmeasmatrixffffff@@@ffffff@@@ffffff@@@@@333333@333333@333333@333333@@@ffffff@@ffffff@@ffffff@ffffff@ffffff@333333@@@@@@333333@@@@@@@@@ffffff@@@@@ffffff@333333@ffffff@ffffff@333333@@@@@@@@333333@@ffffff@@@@@ffffff@ffffff@@ffffff@333333@@ffffff@@ffffff@333333@ffffff@@ffffff@333333@@@@@@333333@@@@@333333@ffffff@@@ffffff@333333@@ffffff@@@@ffffff@@333333@333333@ffffff@333333@@ffffff@@333333@@@@@333333@@333333@@@@@@@ffffff@@333333@@@@ffffff@@@@@@333333@ffffff@@333333@@@@@@333333@333333@@@333333@@@@ @@ @@ @333333@333333 @333333 @333333@@ @333333 @@@@@333333@ @ffffff@ffffff@333333 @ @ @ffffff @333333 @@333333 @ @333333 @ @@333333 @ffffff@@@ @ @ @@333333 @ @ffffff@ @ @ffffff@@ffffff@ @ @ffffff @ @ @@ffffff@ffffff@ffffff@ffffff @333333@333333@@@@@333333@333333@@@@@@ @ffffff@@ffffff@333333@@ffffff@@333333@@333333@333333@@@@333333 @@ffffff@@@@@@ffffff@@@333333@333333@@ffffff@ffffff @@@333333@@@@333333@@ @ @@@@ffffff@ @@ffffff@@@ @ffffff@ffffff@@ffffff @ @ffffff@@ffffff@@ffffff@ffffff@ffffff@ffffff@@@333333 @@@@@@@ @ffffff @@@@333333 @@ffffff?ffffff???ffffff?333333?ffffff??ffffff????ffffff??333333???ffffff?333333??333333???333333?ffffff????ffffff?????ffffff??333333??ffffff???????ffffff?ffffff??ffffff??ffffff?@@@@ffffff@@@ffffff @ffffff@333333@ @@@@ @@@ffffff@@333333@333333@@@@333333@@333333@@@ @ffffff@ @333333@ffffff@@@@@ffffff@@@ffffff@@ffffff @@@@333333@@ffffff@@ffffff@@ffffff@333333@ffffff@@333333@333333@ffffff@ffffff@333333@@@ffffff@333333@@@@@@@@@@@333333@@ffffff@333333@ffffff@@ffffff@ffffff@ffffff@ffffff@ffffff@@333333@@ffffff@ffffff@ffffff@@@@@@@ffffff@??????333333???????????333333?333333?333333?????????????????????333333?333333??333333??333333?????ffffff?????????ffffff????ffffff??ffffff????????333333??ffffff?ffffff?333333?????333333????????333333?ffffff?333333???333333?????@ffffff?@?@@333333???@@ffffff?@@333333@ffffff@?@ffffff@?ffffff@@@?@???@?ffffff?@@?ffffff?ffffff@333333@??@333333@ffffff@ffffff?ffffff@@ffffff@ffffff?@ffffff@?speciescell sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_stringsetosa sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string versicolor sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginica sq_string virginicastatistics-release-1.9.2/inst/datasets/gradespaired.mat000066400000000000000000000036701524624707500232770ustar00rootroot00000000000000Octave-1-L gradespairedmatrixx@P@N@@T@V@@Q@@V@K@U@U@U@Q@@T@U@@T@S@P@X@P@@R@R@M@Q@@Q@O@S@S@O@@U@U@V@T@Q@@P@U@Q@R@@T@S@P@@P@U@@S@Q@R@U@R@@R@W@V@S@T@Q@@R@Q@M@S@@R@P@@S@T@@T@M@K@T@L@S@S@@R@R@T@O@P@@R@@Q@U@Q@@T@@R@T@@R@T@@R@@R@Q@P@S@P@R@Q@P@R@R@T@@U@R@S@T@@S@@W@Q@U@T@@T@T@Q@N@@U@P@R@T@@T@@S@P@@U@R@@T@@Q@N@T@R@P@N@T@U@@R@@V@J@@V@U@T@Q@S@@T@@V@T@Q@@W@P@R@S@L@@P@@P@@P@@T@S@O@U@U@V@S@@R@O@T@@R@S@S@T@P@P@U@S@Q@S@@U@S@S@W@V@@T@@T@Q@S@R@M@@T@R@O@@S@T@T@@P@J@@T@L@S@S@R@S@T@N@@Q@@S@Q@U@Q@T@R@T@R@T@Q@R@Q@P@S@P@P@Q@Q@@R@S@T@U@R@R@@T@T@W@R@@U@@T@T@@U@P@N@W@M@@T@@U@U@R@@P@U@S@@S@R@M@@U@R@statistics-release-1.9.2/inst/datasets/hald.mat000066400000000000000000000046731524624707500215610ustar00rootroot00000000000000Octave-1-L Description sq_string:= M icccc hh SW"dv RHW= u noooo ee ooEu. eai l gllll aa uofr2 fllP t ruuuu tt rdfi4 edeo i emmmm csen r,yr p dnnnn (o e,cgn eA,t l i1234 cf :Ht o n. l e e:::: a . H. c,1a n lh ,oa1 e 9n r t3342 /a fr1 :S6d e sCCCC gr H d t0 g aaaa md .Ce( a.C r (OOOO )e on1 t e e %.... :n Smi9 i m s )ASAS i tpn3 s e s :lili n eog2 t n i 2O2O g is,) i t o O2O2 ni", c n 3 3 a ot a D (.( f uiIp l a d (tFb t ronp t a tree e ,nd. T a t ri2t r u1 h a icOa Hos2 e = ca3- 1 .ft0 o = al d 8 r7 r lc(i 0 SPi- y citc toa1 iuea d arl2 w umtl a rt 1 i m rc y kla4 t sai s ean. h aicu ,nd llam d E uil E n mccs Cn g iaii eg i ntul mi n aemi en e t) c ne e e aa te r ) lt r i ue oi n m) nn g i g n H A o eC p f ah p e te l r m i r Ei c i vs a t ot t e lr i ) vy o e, n d s , haldmatrix @?&@&@@&@@?@5@?&@$@:@=@L@?@J@K@Q@?@K@G@D@P@Q@@.@ @ @@"@1@6@2@@7@"@ @N@J@4@G@@@6@@F@6@:@A@(@(@S@33333R@33333Z@fffffU@W@L[@̬Y@ R@fffffFW@\@33333T@33333S\@Y[@heatmatrix S@33333R@33333Z@fffffU@W@L[@̬Y@ R@fffffFW@\@33333T@33333S\@Y[@ ingredientsmatrix @?&@&@@&@@?@5@?&@$@:@=@L@?@J@K@Q@?@K@G@D@P@Q@@.@ @ @@"@1@6@2@@7@"@ @N@J@4@G@@@6@@F@6@:@A@(@(@statistics-release-1.9.2/inst/datasets/heart_scale.dat000066400000000000000000000660261524624707500231120ustar00rootroot00000000000000+1 1:0.708333 2:1 3:1 4:-0.320755 5:-0.105023 6:-1 7:1 8:-0.419847 9:-1 10:-0.225806 12:1 13:-1 -1 1:0.583333 2:-1 3:0.333333 4:-0.603774 5:1 6:-1 7:1 8:0.358779 9:-1 10:-0.483871 12:-1 13:1 +1 1:0.166667 2:1 3:-0.333333 4:-0.433962 5:-0.383562 6:-1 7:-1 8:0.0687023 9:-1 10:-0.903226 11:-1 12:-1 13:1 -1 1:0.458333 2:1 3:1 4:-0.358491 5:-0.374429 6:-1 7:-1 8:-0.480916 9:1 10:-0.935484 12:-0.333333 13:1 -1 1:0.875 2:-1 3:-0.333333 4:-0.509434 5:-0.347032 6:-1 7:1 8:-0.236641 9:1 10:-0.935484 11:-1 12:-0.333333 13:-1 -1 1:0.5 2:1 3:1 4:-0.509434 5:-0.767123 6:-1 7:-1 8:0.0534351 9:-1 10:-0.870968 11:-1 12:-1 13:1 +1 1:0.125 2:1 3:0.333333 4:-0.320755 5:-0.406393 6:1 7:1 8:0.0839695 9:1 10:-0.806452 12:-0.333333 13:0.5 +1 1:0.25 2:1 3:1 4:-0.698113 5:-0.484018 6:-1 7:1 8:0.0839695 9:1 10:-0.612903 12:-0.333333 13:1 +1 1:0.291667 2:1 3:1 4:-0.132075 5:-0.237443 6:-1 7:1 8:0.51145 9:-1 10:-0.612903 12:0.333333 13:1 +1 1:0.416667 2:-1 3:1 4:0.0566038 5:0.283105 6:-1 7:1 8:0.267176 9:-1 10:0.290323 12:1 13:1 -1 1:0.25 2:1 3:1 4:-0.226415 5:-0.506849 6:-1 7:-1 8:0.374046 9:-1 10:-0.83871 12:-1 13:1 -1 2:1 3:1 4:-0.0943396 5:-0.543379 6:-1 7:1 8:-0.389313 9:1 10:-1 11:-1 12:-1 13:1 -1 1:-0.375 2:1 3:0.333333 4:-0.132075 5:-0.502283 6:-1 7:1 8:0.664122 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:0.333333 2:1 3:-1 4:-0.245283 5:-0.506849 6:-1 7:-1 8:0.129771 9:-1 10:-0.16129 12:0.333333 13:-1 -1 1:0.166667 2:-1 3:1 4:-0.358491 5:-0.191781 6:-1 7:1 8:0.343511 9:-1 10:-1 11:-1 12:-0.333333 13:-1 -1 1:0.75 2:-1 3:1 4:-0.660377 5:-0.894977 6:-1 7:-1 8:-0.175573 9:-1 10:-0.483871 12:-1 13:-1 +1 1:-0.291667 2:1 3:1 4:-0.132075 5:-0.155251 6:-1 7:-1 8:-0.251908 9:1 10:-0.419355 12:0.333333 13:1 +1 2:1 3:1 4:-0.132075 5:-0.648402 6:1 7:1 8:0.282443 9:1 11:1 12:-1 13:1 -1 1:0.458333 2:1 3:-1 4:-0.698113 5:-0.611872 6:-1 7:1 8:0.114504 9:1 10:-0.419355 12:-1 13:-1 -1 1:-0.541667 2:1 3:-1 4:-0.132075 5:-0.666667 6:-1 7:-1 8:0.633588 9:1 10:-0.548387 11:-1 12:-1 13:1 +1 1:0.583333 2:1 3:1 4:-0.509434 5:-0.52968 6:-1 7:1 8:-0.114504 9:1 10:-0.16129 12:0.333333 13:1 -1 1:-0.208333 2:1 3:-0.333333 4:-0.320755 5:-0.456621 6:-1 7:1 8:0.664122 9:-1 10:-0.935484 12:-1 13:-1 -1 1:-0.416667 2:1 3:1 4:-0.603774 5:-0.191781 6:-1 7:-1 8:0.679389 9:-1 10:-0.612903 12:-1 13:-1 -1 1:-0.25 2:1 3:1 4:-0.660377 5:-0.643836 6:-1 7:-1 8:0.0992366 9:-1 10:-0.967742 11:-1 12:-1 13:-1 -1 1:0.0416667 2:-1 3:-0.333333 4:-0.283019 5:-0.260274 6:1 7:1 8:0.343511 9:1 10:-1 11:-1 12:-0.333333 13:-1 -1 1:-0.208333 2:-1 3:0.333333 4:-0.320755 5:-0.319635 6:-1 7:-1 8:0.0381679 9:-1 10:-0.935484 11:-1 12:-1 13:-1 -1 1:-0.291667 2:-1 3:1 4:-0.169811 5:-0.465753 6:-1 7:1 8:0.236641 9:1 10:-1 12:-1 13:-1 -1 1:-0.0833333 2:-1 3:0.333333 4:-0.509434 5:-0.228311 6:-1 7:1 8:0.312977 9:-1 10:-0.806452 11:-1 12:-1 13:-1 +1 1:0.208333 2:1 3:0.333333 4:-0.660377 5:-0.525114 6:-1 7:1 8:0.435115 9:-1 10:-0.193548 12:-0.333333 13:1 -1 1:0.75 2:-1 3:0.333333 4:-0.698113 5:-0.365297 6:1 7:1 8:-0.0992366 9:-1 10:-1 11:-1 12:-0.333333 13:-1 +1 1:0.166667 2:1 3:0.333333 4:-0.358491 5:-0.52968 6:-1 7:1 8:0.206107 9:-1 10:-0.870968 12:-0.333333 13:1 -1 1:0.541667 2:1 3:1 4:0.245283 5:-0.534247 6:-1 7:1 8:0.0229008 9:-1 10:-0.258065 11:-1 12:-1 13:0.5 -1 1:-0.666667 2:-1 3:0.333333 4:-0.509434 5:-0.593607 6:-1 7:-1 8:0.51145 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:0.25 2:1 3:1 4:0.433962 5:-0.086758 6:-1 7:1 8:0.0534351 9:1 10:0.0967742 11:1 12:-1 13:1 +1 1:-0.125 2:1 3:1 4:-0.0566038 5:-0.6621 6:-1 7:1 8:-0.160305 9:1 10:-0.709677 12:-1 13:1 +1 1:-0.208333 2:1 3:1 4:-0.320755 5:-0.406393 6:1 7:1 8:0.206107 9:1 10:-1 11:-1 12:0.333333 13:1 +1 1:0.333333 2:1 3:1 4:-0.132075 5:-0.630137 6:-1 7:1 8:0.0229008 9:1 10:-0.387097 11:-1 12:-0.333333 13:1 +1 1:0.25 2:1 3:-1 4:0.245283 5:-0.328767 6:-1 7:1 8:-0.175573 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.458333 2:1 3:0.333333 4:-0.320755 5:-0.753425 6:-1 7:-1 8:0.206107 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.208333 2:1 3:1 4:-0.471698 5:-0.561644 6:-1 7:1 8:0.755725 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:-0.541667 2:1 3:1 4:0.0943396 5:-0.557078 6:-1 7:-1 8:0.679389 9:-1 10:-1 11:-1 12:-1 13:1 -1 1:0.375 2:-1 3:1 4:-0.433962 5:-0.621005 6:-1 7:-1 8:0.40458 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.375 2:1 3:0.333333 4:-0.320755 5:-0.511416 6:-1 7:-1 8:0.648855 9:1 10:-0.870968 11:-1 12:-1 13:-1 -1 1:-0.291667 2:1 3:-0.333333 4:-0.867925 5:-0.675799 6:1 7:-1 8:0.29771 9:-1 10:-1 11:-1 12:-1 13:1 +1 1:0.25 2:1 3:0.333333 4:-0.396226 5:-0.579909 6:1 7:-1 8:-0.0381679 9:-1 10:-0.290323 12:-0.333333 13:0.5 -1 1:0.208333 2:1 3:0.333333 4:-0.132075 5:-0.611872 6:1 7:1 8:0.435115 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:-0.166667 2:1 3:0.333333 4:-0.54717 5:-0.894977 6:-1 7:1 8:-0.160305 9:-1 10:-0.741935 11:-1 12:1 13:-1 +1 1:-0.375 2:1 3:1 4:-0.698113 5:-0.675799 6:-1 7:1 8:0.618321 9:-1 10:-1 11:-1 12:-0.333333 13:-1 +1 1:0.541667 2:1 3:-0.333333 4:0.245283 5:-0.452055 6:-1 7:-1 8:-0.251908 9:1 10:-1 12:1 13:0.5 +1 1:0.5 2:-1 3:1 4:0.0566038 5:-0.547945 6:-1 7:1 8:-0.343511 9:-1 10:-0.677419 12:1 13:1 +1 1:-0.458333 2:1 3:1 4:-0.207547 5:-0.136986 6:-1 7:-1 8:-0.175573 9:1 10:-0.419355 12:-1 13:0.5 -1 1:-0.0416667 2:1 3:-0.333333 4:-0.358491 5:-0.639269 6:1 7:-1 8:0.725191 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:0.5 2:-1 3:0.333333 4:-0.132075 5:0.328767 6:1 7:1 8:0.312977 9:-1 10:-0.741935 11:-1 12:-0.333333 13:-1 -1 1:0.416667 2:-1 3:-0.333333 4:-0.132075 5:-0.684932 6:-1 7:-1 8:0.648855 9:-1 10:-1 11:-1 12:0.333333 13:-1 -1 1:-0.333333 2:-1 3:-0.333333 4:-0.320755 5:-0.506849 6:-1 7:1 8:0.587786 9:-1 10:-0.806452 12:-1 13:-1 -1 1:-0.5 2:-1 3:-0.333333 4:-0.792453 5:-0.671233 6:-1 7:-1 8:0.480916 9:-1 10:-1 11:-1 12:-0.333333 13:-1 +1 1:0.333333 2:1 3:1 4:-0.169811 5:-0.817352 6:-1 7:1 8:-0.175573 9:1 10:0.16129 12:-0.333333 13:-1 -1 1:0.291667 2:-1 3:0.333333 4:-0.509434 5:-0.762557 6:1 7:-1 8:-0.618321 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:0.25 2:-1 3:1 4:0.509434 5:-0.438356 6:-1 7:-1 8:0.0992366 9:1 10:-1 12:-1 13:-1 +1 1:0.375 2:1 3:-0.333333 4:-0.509434 5:-0.292237 6:-1 7:1 8:-0.51145 9:-1 10:-0.548387 12:-0.333333 13:1 -1 1:0.166667 2:1 3:0.333333 4:0.0566038 5:-1 6:1 7:-1 8:0.557252 9:-1 10:-0.935484 11:-1 12:-0.333333 13:1 +1 1:-0.0833333 2:-1 3:1 4:-0.320755 5:-0.182648 6:-1 7:-1 8:0.0839695 9:1 10:-0.612903 12:-1 13:1 -1 1:-0.375 2:1 3:0.333333 4:-0.509434 5:-0.543379 6:-1 7:-1 8:0.496183 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:0.291667 2:-1 3:-1 4:0.0566038 5:-0.479452 6:-1 7:-1 8:0.526718 9:-1 10:-0.709677 11:-1 12:-1 13:-1 -1 1:0.416667 2:1 3:-1 4:-0.0377358 5:-0.511416 6:1 7:1 8:0.206107 9:-1 10:-0.258065 11:1 12:-1 13:0.5 +1 1:0.166667 2:1 3:1 4:0.0566038 5:-0.315068 6:-1 7:1 8:-0.374046 9:1 10:-0.806452 12:-0.333333 13:0.5 -1 1:-0.0833333 2:1 3:1 4:-0.132075 5:-0.383562 6:-1 7:1 8:0.755725 9:1 10:-1 11:-1 12:-1 13:-1 +1 1:0.208333 2:-1 3:-0.333333 4:-0.207547 5:-0.118721 6:1 7:1 8:0.236641 9:-1 10:-1 11:-1 12:0.333333 13:-1 -1 1:-0.375 2:-1 3:0.333333 4:-0.54717 5:-0.47032 6:-1 7:-1 8:0.19084 9:-1 10:-0.903226 12:-0.333333 13:-1 +1 1:-0.25 2:1 3:0.333333 4:-0.735849 5:-0.465753 6:-1 7:-1 8:0.236641 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:0.333333 2:1 3:1 4:-0.509434 5:-0.388128 6:-1 7:-1 8:0.0534351 9:1 10:0.16129 12:-0.333333 13:1 -1 1:0.166667 2:-1 3:1 4:-0.509434 5:0.0410959 6:-1 7:-1 8:0.40458 9:1 10:-0.806452 11:-1 12:-1 13:-1 -1 1:0.708333 2:1 3:-0.333333 4:0.169811 5:-0.456621 6:-1 7:1 8:0.0992366 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:0.958333 2:-1 3:0.333333 4:-0.132075 5:-0.675799 6:-1 8:-0.312977 9:-1 10:-0.645161 12:-1 13:-1 -1 1:0.583333 2:-1 3:1 4:-0.773585 5:-0.557078 6:-1 7:-1 8:0.0839695 9:-1 10:-0.903226 11:-1 12:0.333333 13:-1 +1 1:-0.333333 2:1 3:1 4:-0.0943396 5:-0.164384 6:-1 7:1 8:0.160305 9:1 10:-1 12:1 13:1 -1 1:-0.333333 2:1 3:1 4:-0.811321 5:-0.625571 6:-1 7:1 8:0.175573 9:1 10:-0.0322581 12:-1 13:-1 -1 1:-0.583333 2:-1 3:0.333333 4:-1 5:-0.666667 6:-1 7:-1 8:0.648855 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.458333 2:-1 3:0.333333 4:-0.509434 5:-0.621005 6:-1 7:-1 8:0.557252 9:-1 10:-1 12:-1 13:-1 -1 1:0.125 2:1 3:-0.333333 4:-0.509434 5:-0.497717 6:-1 7:-1 8:0.633588 9:-1 10:-0.741935 11:-1 12:-1 13:-1 +1 1:0.208333 2:1 3:1 4:-0.0188679 5:-0.579909 6:-1 7:-1 8:-0.480916 9:-1 10:-0.354839 12:-0.333333 13:1 +1 1:-0.75 2:1 3:1 4:-0.509434 5:-0.671233 6:-1 7:-1 8:-0.0992366 9:1 10:-0.483871 12:-1 13:1 +1 1:0.208333 2:1 3:1 4:0.0566038 5:-0.342466 6:-1 7:1 8:-0.389313 9:1 10:-0.741935 11:-1 12:-1 13:1 -1 1:-0.5 2:1 3:0.333333 4:-0.320755 5:-0.598174 6:-1 7:1 8:0.480916 9:-1 10:-0.354839 12:-1 13:-1 -1 1:0.166667 2:1 3:1 4:-0.698113 5:-0.657534 6:-1 7:-1 8:-0.160305 9:1 10:-0.516129 12:-1 13:0.5 -1 1:-0.458333 2:1 3:-1 4:0.0188679 5:-0.461187 6:-1 7:1 8:0.633588 9:-1 10:-0.741935 11:-1 12:0.333333 13:-1 -1 1:0.375 2:1 3:-0.333333 4:-0.358491 5:-0.625571 6:1 7:1 8:0.0534351 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:0.25 2:1 3:-1 4:0.584906 5:-0.342466 6:-1 7:1 8:0.129771 9:-1 10:0.354839 11:1 12:-1 13:1 -1 1:-0.5 2:-1 3:-0.333333 4:-0.396226 5:-0.178082 6:-1 7:-1 8:0.40458 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:-0.125 2:1 3:1 4:0.0566038 5:-0.465753 6:-1 7:1 8:-0.129771 9:-1 10:-0.16129 12:-1 13:1 -1 1:0.25 2:1 3:-0.333333 4:-0.132075 5:-0.56621 6:-1 7:-1 8:0.419847 9:1 10:-1 11:-1 12:-1 13:-1 +1 1:0.333333 2:-1 3:1 4:-0.320755 5:-0.0684932 6:-1 7:1 8:0.496183 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:0.0416667 2:1 3:1 4:-0.433962 5:-0.360731 6:-1 7:1 8:-0.419847 9:1 10:-0.290323 12:-0.333333 13:1 +1 1:0.0416667 2:1 3:1 4:-0.698113 5:-0.634703 6:-1 7:1 8:-0.435115 9:1 10:-1 12:-0.333333 13:-1 +1 1:-0.0416667 2:1 3:1 4:-0.415094 5:-0.607306 6:-1 7:-1 8:0.480916 9:-1 10:-0.677419 11:-1 12:0.333333 13:1 +1 1:-0.25 2:1 3:1 4:-0.698113 5:-0.319635 6:-1 7:1 8:-0.282443 9:1 10:-0.677419 12:-0.333333 13:-1 -1 1:0.541667 2:1 3:1 4:-0.509434 5:-0.196347 6:-1 7:1 8:0.221374 9:-1 10:-0.870968 12:-1 13:-1 +1 1:0.208333 2:1 3:1 4:-0.886792 5:-0.506849 6:-1 7:-1 8:0.29771 9:-1 10:-0.967742 11:-1 12:-0.333333 13:1 -1 1:0.458333 2:-1 3:0.333333 4:-0.132075 5:-0.146119 6:-1 7:-1 8:-0.0534351 9:-1 10:-0.935484 11:-1 12:-1 13:1 -1 1:-0.125 2:-1 3:-0.333333 4:-0.509434 5:-0.461187 6:-1 7:-1 8:0.389313 9:-1 10:-0.645161 11:-1 12:-1 13:-1 -1 1:-0.375 2:-1 3:0.333333 4:-0.735849 5:-0.931507 6:-1 7:-1 8:0.587786 9:-1 10:-0.806452 12:-1 13:-1 +1 1:0.583333 2:1 3:1 4:-0.509434 5:-0.493151 6:-1 7:-1 8:-1 9:-1 10:-0.677419 12:-1 13:-1 -1 1:-0.166667 2:-1 3:1 4:-0.320755 5:-0.347032 6:-1 7:-1 8:0.40458 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:0.166667 2:1 3:1 4:0.339623 5:-0.255708 6:1 7:1 8:-0.19084 9:-1 10:-0.677419 12:1 13:1 +1 1:0.416667 2:1 3:1 4:-0.320755 5:-0.415525 6:-1 7:1 8:0.160305 9:-1 10:-0.548387 12:-0.333333 13:1 +1 1:-0.208333 2:1 3:1 4:-0.433962 5:-0.324201 6:-1 7:1 8:0.450382 9:-1 10:-0.83871 12:-1 13:1 -1 1:-0.0833333 2:1 3:0.333333 4:-0.886792 5:-0.561644 6:-1 7:-1 8:0.0992366 9:1 10:-0.612903 12:-1 13:-1 +1 1:0.291667 2:-1 3:1 4:0.0566038 5:-0.39726 6:-1 7:1 8:0.312977 9:-1 10:-0.16129 12:0.333333 13:1 +1 1:0.25 2:1 3:1 4:-0.132075 5:-0.767123 6:-1 7:-1 8:0.389313 9:1 10:-1 11:-1 12:-0.333333 13:1 -1 1:-0.333333 2:-1 3:-0.333333 4:-0.660377 5:-0.844749 6:-1 7:-1 8:0.0229008 9:-1 10:-1 12:-1 13:-1 +1 1:0.0833333 2:-1 3:1 4:0.622642 5:-0.0821918 6:-1 8:-0.29771 9:1 10:0.0967742 12:-1 13:-1 -1 1:-0.5 2:1 3:-0.333333 4:-0.698113 5:-0.502283 6:-1 7:-1 8:0.251908 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:0.291667 2:-1 3:1 4:0.207547 5:-0.182648 6:-1 7:1 8:0.374046 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:0.0416667 2:-1 3:0.333333 4:-0.226415 5:-0.187215 6:1 7:-1 8:0.51145 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.458333 2:1 3:-0.333333 4:-0.509434 5:-0.228311 6:-1 7:-1 8:0.389313 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.166667 2:-1 3:-0.333333 4:-0.245283 5:-0.3379 6:-1 7:-1 8:0.389313 9:-1 10:-1 12:-1 13:-1 +1 1:-0.291667 2:1 3:1 4:-0.509434 5:-0.438356 6:-1 7:1 8:0.114504 9:-1 10:-0.741935 11:-1 12:-1 13:1 +1 1:0.125 2:-1 3:1 4:1 5:-0.260274 6:1 7:1 8:-0.0534351 9:1 10:0.290323 11:1 12:0.333333 13:1 -1 1:0.541667 2:-1 3:-1 4:0.0566038 5:-0.543379 6:-1 7:-1 8:-0.343511 9:-1 10:-0.16129 11:1 12:-1 13:-1 +1 1:0.125 2:1 3:1 4:-0.320755 5:-0.283105 6:1 7:1 8:-0.51145 9:1 10:-0.483871 11:1 12:-1 13:1 +1 1:-0.166667 2:1 3:0.333333 4:-0.509434 5:-0.716895 6:-1 7:-1 8:0.0381679 9:-1 10:-0.354839 12:1 13:1 +1 1:0.0416667 2:1 3:1 4:-0.471698 5:-0.269406 6:-1 7:1 8:-0.312977 9:1 10:0.0322581 12:0.333333 13:-1 +1 1:0.166667 2:1 3:1 4:0.0943396 5:-0.324201 6:-1 7:-1 8:-0.740458 9:1 10:-0.612903 12:-0.333333 13:1 -1 1:0.5 2:-1 3:0.333333 4:0.245283 5:0.0684932 6:-1 7:1 8:0.221374 9:-1 10:-0.741935 11:-1 12:-1 13:-1 -1 1:0.0416667 2:1 3:0.333333 4:-0.415094 5:-0.328767 6:-1 7:1 8:0.236641 9:-1 10:-0.83871 11:1 12:-0.333333 13:-1 -1 1:0.0416667 2:-1 3:0.333333 4:0.245283 5:-0.657534 6:-1 7:-1 8:0.40458 9:-1 10:-1 11:-1 12:-0.333333 13:-1 +1 1:0.375 2:1 3:1 4:-0.509434 5:-0.356164 6:-1 7:-1 8:-0.572519 9:1 10:-0.419355 12:0.333333 13:1 -1 1:-0.0416667 2:-1 3:0.333333 4:-0.207547 5:-0.680365 6:-1 7:1 8:0.496183 9:-1 10:-0.967742 12:-1 13:-1 -1 1:-0.0416667 2:1 3:-0.333333 4:-0.245283 5:-0.657534 6:-1 7:-1 8:0.328244 9:-1 10:-0.741935 11:-1 12:-0.333333 13:-1 +1 1:0.291667 2:1 3:1 4:-0.566038 5:-0.525114 6:1 7:-1 8:0.358779 9:1 10:-0.548387 11:-1 12:0.333333 13:1 +1 1:0.416667 2:-1 3:1 4:-0.735849 5:-0.347032 6:-1 7:-1 8:0.496183 9:1 10:-0.419355 12:0.333333 13:-1 +1 1:0.541667 2:1 3:1 4:-0.660377 5:-0.607306 6:-1 7:1 8:-0.0687023 9:1 10:-0.967742 11:-1 12:-0.333333 13:-1 -1 1:-0.458333 2:1 3:1 4:-0.132075 5:-0.543379 6:-1 7:-1 8:0.633588 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:0.458333 2:1 3:1 4:-0.509434 5:-0.452055 6:-1 7:1 8:-0.618321 9:1 10:-0.290323 11:1 12:-0.333333 13:-1 -1 1:0.0416667 2:1 3:0.333333 4:0.0566038 5:-0.515982 6:-1 7:1 8:0.435115 9:-1 10:-0.483871 11:-1 12:-1 13:1 -1 1:-0.291667 2:-1 3:0.333333 4:-0.0943396 5:-0.767123 6:-1 7:1 8:0.358779 9:1 10:-0.548387 11:1 12:-1 13:-1 -1 1:0.583333 2:-1 3:0.333333 4:0.0943396 5:-0.310502 6:-1 7:-1 8:0.541985 9:-1 10:-1 11:-1 12:-0.333333 13:-1 +1 1:0.125 2:1 3:1 4:-0.415094 5:-0.438356 6:1 7:1 8:0.114504 9:1 10:-0.612903 12:-0.333333 13:-1 -1 1:-0.791667 2:-1 3:-0.333333 4:-0.54717 5:-0.616438 6:-1 7:-1 8:0.847328 9:-1 10:-0.774194 11:-1 12:-1 13:-1 -1 1:0.166667 2:1 3:1 4:-0.283019 5:-0.630137 6:-1 7:-1 8:0.480916 9:1 10:-1 11:-1 12:-1 13:1 +1 1:0.458333 2:1 3:1 4:-0.0377358 5:-0.607306 6:-1 7:1 8:-0.0687023 9:-1 10:-0.354839 12:0.333333 13:0.5 -1 1:0.25 2:1 3:1 4:-0.169811 5:-0.3379 6:-1 7:1 8:0.694656 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:-0.125 2:1 3:0.333333 4:-0.132075 5:-0.511416 6:-1 7:-1 8:0.40458 9:-1 10:-0.806452 12:-0.333333 13:1 -1 1:-0.0833333 2:1 3:-1 4:-0.415094 5:-0.60274 6:-1 7:1 8:-0.175573 9:1 10:-0.548387 11:-1 12:-0.333333 13:-1 +1 1:0.0416667 2:1 3:-0.333333 4:0.849057 5:-0.283105 6:-1 7:1 8:0.89313 9:-1 10:-1 11:-1 12:-0.333333 13:1 +1 2:1 3:1 4:-0.45283 5:-0.287671 6:-1 7:-1 8:-0.633588 9:1 10:-0.354839 12:0.333333 13:1 +1 1:-0.0416667 2:1 3:1 4:-0.660377 5:-0.525114 6:-1 7:-1 8:0.358779 9:-1 10:-1 11:-1 12:-0.333333 13:-1 +1 1:-0.541667 2:1 3:1 4:-0.698113 5:-0.812785 6:-1 7:1 8:-0.343511 9:1 10:-0.354839 12:-1 13:1 +1 1:0.208333 2:1 3:0.333333 4:-0.283019 5:-0.552511 6:-1 7:1 8:0.557252 9:-1 10:0.0322581 11:-1 12:0.333333 13:1 -1 1:-0.5 2:-1 3:0.333333 4:-0.660377 5:-0.351598 6:-1 7:1 8:0.541985 9:1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.5 2:1 3:0.333333 4:-0.660377 5:-0.43379 6:-1 7:-1 8:0.648855 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.125 2:-1 3:0.333333 4:-0.509434 5:-0.575342 6:-1 7:-1 8:0.328244 9:-1 10:-0.483871 12:-1 13:-1 -1 1:0.0416667 2:-1 3:0.333333 4:-0.735849 5:-0.356164 6:-1 7:1 8:0.465649 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:0.458333 2:-1 3:1 4:-0.320755 5:-0.191781 6:-1 7:-1 8:-0.221374 9:-1 10:-0.354839 12:0.333333 13:-1 -1 1:-0.0833333 2:-1 3:0.333333 4:-0.320755 5:-0.406393 6:-1 7:1 8:0.19084 9:-1 10:-0.83871 11:-1 12:-1 13:-1 -1 1:-0.291667 2:-1 3:-0.333333 4:-0.792453 5:-0.643836 6:-1 7:-1 8:0.541985 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:0.0833333 2:1 3:1 4:-0.132075 5:-0.584475 6:-1 7:-1 8:-0.389313 9:1 10:0.806452 11:1 12:-1 13:1 -1 1:-0.333333 2:1 3:-0.333333 4:-0.358491 5:-0.16895 6:-1 7:1 8:0.51145 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:0.125 2:1 3:-1 4:-0.509434 5:-0.694064 6:-1 7:1 8:0.389313 9:-1 10:-0.387097 12:-1 13:1 +1 1:0.541667 2:-1 3:1 4:0.584906 5:-0.534247 6:1 7:-1 8:0.435115 9:1 10:-0.677419 12:0.333333 13:1 +1 1:-0.625 2:1 3:-1 4:-0.509434 5:-0.520548 6:-1 7:-1 8:0.694656 9:1 10:0.225806 12:-1 13:1 +1 1:0.375 2:-1 3:1 4:0.0566038 5:-0.461187 6:-1 7:-1 8:0.267176 9:1 10:-0.548387 12:-1 13:-1 -1 1:0.0833333 2:1 3:-0.333333 4:-0.320755 5:-0.378995 6:-1 7:-1 8:0.282443 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:0.208333 2:1 3:1 4:-0.358491 5:-0.392694 6:-1 7:1 8:-0.0992366 9:1 10:-0.0322581 12:0.333333 13:1 -1 1:-0.416667 2:1 3:1 4:-0.698113 5:-0.611872 6:-1 7:-1 8:0.374046 9:-1 10:-1 11:-1 12:-1 13:1 -1 1:0.458333 2:-1 3:1 4:0.622642 5:-0.0913242 6:-1 7:-1 8:0.267176 9:1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.125 2:-1 3:1 4:-0.698113 5:-0.415525 6:-1 7:1 8:0.343511 9:-1 10:-1 11:-1 12:-1 13:-1 -1 2:1 3:0.333333 4:-0.320755 5:-0.675799 6:1 7:1 8:0.236641 9:-1 10:-0.612903 11:1 12:-1 13:-1 -1 1:-0.333333 2:-1 3:1 4:-0.169811 5:-0.497717 6:-1 7:1 8:0.236641 9:1 10:-0.935484 12:-1 13:-1 +1 1:0.5 2:1 3:-1 4:-0.169811 5:-0.287671 6:1 7:1 8:0.572519 9:-1 10:-0.548387 12:-0.333333 13:-1 -1 1:0.666667 2:1 3:-1 4:0.245283 5:-0.506849 6:1 7:1 8:-0.0839695 9:-1 10:-0.967742 12:-0.333333 13:-1 +1 1:0.666667 2:1 3:0.333333 4:-0.132075 5:-0.415525 6:-1 7:1 8:0.145038 9:-1 10:-0.354839 12:1 13:1 +1 1:0.583333 2:1 3:1 4:-0.886792 5:-0.210046 6:-1 7:1 8:-0.175573 9:1 10:-0.709677 12:0.333333 13:-1 -1 1:0.625 2:-1 3:0.333333 4:-0.509434 5:-0.611872 6:-1 7:1 8:-0.328244 9:-1 10:-0.516129 12:-1 13:-1 -1 1:-0.791667 2:1 3:-1 4:-0.54717 5:-0.744292 6:-1 7:1 8:0.572519 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:0.375 2:-1 3:1 4:-0.169811 5:-0.232877 6:1 7:-1 8:-0.465649 9:-1 10:-0.387097 12:1 13:-1 +1 1:-0.0833333 2:1 3:1 4:-0.132075 5:-0.214612 6:-1 7:-1 8:-0.221374 9:1 10:0.354839 12:1 13:1 +1 1:-0.291667 2:1 3:0.333333 4:0.0566038 5:-0.520548 6:-1 7:-1 8:0.160305 9:-1 10:0.16129 12:-1 13:-1 +1 1:0.583333 2:1 3:1 4:-0.415094 5:-0.415525 6:1 7:-1 8:0.40458 9:-1 10:-0.935484 12:0.333333 13:1 -1 1:-0.125 2:1 3:0.333333 4:-0.339623 5:-0.680365 6:-1 7:-1 8:0.40458 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.458333 2:1 3:0.333333 4:-0.509434 5:-0.479452 6:1 7:-1 8:0.877863 9:-1 10:-0.741935 11:1 12:-1 13:1 +1 1:0.125 2:-1 3:1 4:-0.245283 5:0.292237 6:-1 7:1 8:0.206107 9:1 10:-0.387097 12:0.333333 13:1 +1 1:-0.5 2:1 3:1 4:-0.698113 5:-0.789954 6:-1 7:1 8:0.328244 9:-1 10:-1 11:-1 12:-1 13:1 -1 1:-0.458333 2:-1 3:1 4:-0.849057 5:-0.365297 6:-1 7:1 8:-0.221374 9:-1 10:-0.806452 12:-1 13:-1 -1 2:1 3:0.333333 4:-0.320755 5:-0.452055 6:1 7:1 8:0.557252 9:-1 10:-1 11:-1 12:1 13:-1 -1 1:-0.416667 2:1 3:0.333333 4:-0.320755 5:-0.136986 6:-1 7:-1 8:0.389313 9:-1 10:-0.387097 11:-1 12:-0.333333 13:-1 +1 1:0.125 2:1 3:1 4:-0.283019 5:-0.73516 6:-1 7:1 8:-0.480916 9:1 10:-0.322581 12:-0.333333 13:0.5 -1 1:-0.0416667 2:1 3:1 4:-0.735849 5:-0.511416 6:1 7:-1 8:0.160305 9:-1 10:-0.967742 11:-1 12:1 13:1 -1 1:0.375 2:-1 3:1 4:-0.132075 5:0.223744 6:-1 7:1 8:0.312977 9:-1 10:-0.612903 12:-1 13:-1 +1 1:0.708333 2:1 3:0.333333 4:0.245283 5:-0.347032 6:-1 7:-1 8:-0.374046 9:1 10:-0.0645161 12:-0.333333 13:1 -1 1:0.0416667 2:1 3:1 4:-0.132075 5:-0.484018 6:-1 7:-1 8:0.358779 9:-1 10:-0.612903 11:-1 12:-1 13:-1 +1 1:0.708333 2:1 3:1 4:-0.0377358 5:-0.780822 6:-1 7:-1 8:-0.175573 9:1 10:-0.16129 11:1 12:-1 13:1 -1 1:0.0416667 2:1 3:-0.333333 4:-0.735849 5:-0.164384 6:-1 7:-1 8:0.29771 9:-1 10:-1 11:-1 12:-1 13:1 +1 1:-0.75 2:1 3:1 4:-0.396226 5:-0.287671 6:-1 7:1 8:0.29771 9:1 10:-1 11:-1 12:-1 13:1 -1 1:-0.208333 2:1 3:0.333333 4:-0.433962 5:-0.410959 6:1 7:-1 8:0.587786 9:-1 10:-1 11:-1 12:0.333333 13:-1 -1 1:0.0833333 2:-1 3:-0.333333 4:-0.226415 5:-0.43379 6:-1 7:1 8:0.374046 9:-1 10:-0.548387 12:-1 13:-1 -1 1:0.208333 2:-1 3:1 4:-0.886792 5:-0.442922 6:-1 7:1 8:-0.221374 9:-1 10:-0.677419 12:-1 13:-1 -1 1:0.0416667 2:-1 3:0.333333 4:-0.698113 5:-0.598174 6:-1 7:-1 8:0.328244 9:-1 10:-0.483871 12:-1 13:-1 -1 1:0.666667 2:-1 3:-1 4:-0.132075 5:-0.484018 6:-1 7:-1 8:0.221374 9:-1 10:-0.419355 11:-1 12:0.333333 13:-1 +1 1:1 2:1 3:1 4:-0.415094 5:-0.187215 6:-1 7:1 8:0.389313 9:1 10:-1 11:-1 12:1 13:-1 -1 1:0.625 2:1 3:0.333333 4:-0.54717 5:-0.310502 6:-1 7:-1 8:0.221374 9:-1 10:-0.677419 11:-1 12:-0.333333 13:1 +1 1:0.208333 2:1 3:1 4:-0.415094 5:-0.205479 6:-1 7:1 8:0.526718 9:-1 10:-1 11:-1 12:0.333333 13:1 +1 1:0.291667 2:1 3:1 4:-0.415094 5:-0.39726 6:-1 7:1 8:0.0687023 9:1 10:-0.0967742 12:-0.333333 13:1 +1 1:-0.0833333 2:1 3:1 4:-0.132075 5:-0.210046 6:-1 7:-1 8:0.557252 9:1 10:-0.483871 11:-1 12:-1 13:1 +1 1:0.0833333 2:1 3:1 4:0.245283 5:-0.255708 6:-1 7:1 8:0.129771 9:1 10:-0.741935 12:-0.333333 13:1 -1 1:-0.0416667 2:1 3:-1 4:0.0943396 5:-0.214612 6:1 7:-1 8:0.633588 9:-1 10:-0.612903 12:-1 13:1 -1 1:0.291667 2:-1 3:0.333333 4:-0.849057 5:-0.123288 6:-1 7:-1 8:0.358779 9:-1 10:-1 11:-1 12:-0.333333 13:-1 -1 1:0.208333 2:1 3:0.333333 4:-0.792453 5:-0.479452 6:-1 7:1 8:0.267176 9:1 10:-0.806452 12:-1 13:1 +1 1:0.458333 2:1 3:0.333333 4:-0.415094 5:-0.164384 6:-1 7:-1 8:-0.0839695 9:1 10:-0.419355 12:-1 13:1 -1 1:-0.666667 2:1 3:0.333333 4:-0.320755 5:-0.43379 6:-1 7:-1 8:0.770992 9:-1 10:0.129032 11:1 12:-1 13:-1 +1 1:0.25 2:1 3:-1 4:0.433962 5:-0.260274 6:-1 7:1 8:0.343511 9:-1 10:-0.935484 12:-1 13:1 -1 1:-0.0833333 2:1 3:0.333333 4:-0.415094 5:-0.456621 6:1 7:1 8:0.450382 9:-1 10:-0.225806 12:-1 13:-1 -1 1:-0.416667 2:-1 3:0.333333 4:-0.471698 5:-0.60274 6:-1 7:-1 8:0.435115 9:-1 10:-0.935484 12:-1 13:-1 +1 1:0.208333 2:1 3:1 4:-0.358491 5:-0.589041 6:-1 7:1 8:-0.0839695 9:1 10:-0.290323 12:1 13:1 -1 1:-1 2:1 3:-0.333333 4:-0.320755 5:-0.643836 6:-1 7:1 8:1 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.5 2:-1 3:-0.333333 4:-0.320755 5:-0.643836 6:-1 7:1 8:0.541985 9:-1 10:-0.548387 11:-1 12:-1 13:-1 -1 1:0.416667 2:-1 3:0.333333 4:-0.226415 5:-0.424658 6:-1 7:1 8:0.541985 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.0833333 2:1 3:0.333333 4:-1 5:-0.538813 6:-1 7:-1 8:0.267176 9:1 10:-1 11:-1 12:-0.333333 13:1 -1 1:0.0416667 2:1 3:0.333333 4:-0.509434 5:-0.39726 6:-1 7:1 8:0.160305 9:-1 10:-0.870968 12:-1 13:1 -1 1:-0.375 2:1 3:-0.333333 4:-0.509434 5:-0.570776 6:-1 7:-1 8:0.51145 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:0.0416667 2:1 3:1 4:-0.698113 5:-0.484018 6:-1 7:-1 8:-0.160305 9:1 10:-0.0967742 12:-0.333333 13:1 +1 1:0.5 2:1 3:1 4:-0.226415 5:-0.415525 6:-1 7:1 8:-0.145038 9:-1 10:-0.0967742 12:-0.333333 13:1 -1 1:0.166667 2:1 3:0.333333 4:0.0566038 5:-0.808219 6:-1 7:-1 8:0.572519 9:-1 10:-0.483871 11:-1 12:-1 13:-1 +1 1:0.416667 2:1 3:1 4:-0.320755 5:-0.0684932 6:1 7:1 8:-0.0687023 9:1 10:-0.419355 11:-1 12:1 13:1 -1 1:-0.75 2:-1 3:1 4:-0.169811 5:-0.739726 6:-1 7:-1 8:0.694656 9:-1 10:-0.548387 11:-1 12:-1 13:-1 -1 1:-0.5 2:1 3:-0.333333 4:-0.226415 5:-0.648402 6:-1 7:-1 8:-0.0687023 9:-1 10:-1 12:-1 13:0.5 +1 1:0.375 2:-1 3:0.333333 4:-0.320755 5:-0.374429 6:-1 7:-1 8:-0.603053 9:-1 10:-0.612903 12:-0.333333 13:1 +1 1:-0.416667 2:-1 3:1 4:-0.283019 5:-0.0182648 6:1 7:1 8:-0.00763359 9:1 10:-0.0322581 12:-1 13:1 -1 1:0.208333 2:-1 3:-1 4:0.0566038 5:-0.283105 6:1 7:1 8:0.389313 9:-1 10:-0.677419 11:-1 12:-1 13:-1 -1 1:-0.0416667 2:1 3:-1 4:-0.54717 5:-0.726027 6:-1 7:1 8:0.816794 9:-1 10:-1 12:-1 13:0.5 +1 1:0.333333 2:-1 3:1 4:-0.0377358 5:-0.173516 6:-1 7:1 8:0.145038 9:1 10:-0.677419 12:-1 13:1 +1 1:-0.583333 2:1 3:1 4:-0.54717 5:-0.575342 6:-1 7:-1 8:0.0534351 9:-1 10:-0.612903 12:-1 13:1 -1 1:-0.333333 2:1 3:1 4:-0.603774 5:-0.388128 6:-1 7:1 8:0.740458 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:-0.0416667 2:1 3:1 4:-0.358491 5:-0.410959 6:-1 7:-1 8:0.374046 9:1 10:-1 11:-1 12:-0.333333 13:1 -1 1:0.375 2:1 3:0.333333 4:-0.320755 5:-0.520548 6:-1 7:-1 8:0.145038 9:-1 10:-0.419355 12:1 13:1 +1 1:0.375 2:-1 3:1 4:0.245283 5:-0.826484 6:-1 7:1 8:0.129771 9:-1 10:1 11:1 12:1 13:1 -1 2:-1 3:1 4:-0.169811 5:-0.506849 6:-1 7:1 8:0.358779 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:-0.416667 2:1 3:1 4:-0.509434 5:-0.767123 6:-1 7:1 8:-0.251908 9:1 10:-0.193548 12:-1 13:1 -1 1:-0.25 2:1 3:0.333333 4:-0.169811 5:-0.401826 6:-1 7:1 8:0.29771 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.0416667 2:1 3:-0.333333 4:-0.509434 5:-0.0913242 6:-1 7:-1 8:0.541985 9:-1 10:-0.935484 11:-1 12:-1 13:-1 +1 1:0.625 2:1 3:0.333333 4:0.622642 5:-0.324201 6:1 7:1 8:0.206107 9:1 10:-0.483871 12:-1 13:1 -1 1:-0.583333 2:1 3:0.333333 4:-0.132075 5:-0.109589 6:-1 7:1 8:0.694656 9:-1 10:-1 11:-1 12:-1 13:-1 -1 2:-1 3:1 4:-0.320755 5:-0.369863 6:-1 7:1 8:0.0992366 9:-1 10:-0.870968 12:-1 13:-1 +1 1:0.375 2:-1 3:1 4:-0.132075 5:-0.351598 6:-1 7:1 8:0.358779 9:-1 10:0.16129 11:1 12:0.333333 13:-1 -1 1:-0.0833333 2:-1 3:0.333333 4:-0.132075 5:-0.16895 6:-1 7:1 8:0.0839695 9:-1 10:-0.516129 11:-1 12:-0.333333 13:-1 +1 1:0.291667 2:1 3:1 4:-0.320755 5:-0.420091 6:-1 7:-1 8:0.114504 9:1 10:-0.548387 11:-1 12:-0.333333 13:1 +1 1:0.5 2:1 3:1 4:-0.698113 5:-0.442922 6:-1 7:1 8:0.328244 9:-1 10:-0.806452 11:-1 12:0.333333 13:0.5 -1 1:0.5 2:-1 3:0.333333 4:0.150943 5:-0.347032 6:-1 7:-1 8:0.175573 9:-1 10:-0.741935 11:-1 12:-1 13:-1 +1 1:0.291667 2:1 3:0.333333 4:-0.132075 5:-0.730594 6:-1 7:1 8:0.282443 9:-1 10:-0.0322581 12:-1 13:-1 +1 1:0.291667 2:1 3:1 4:-0.0377358 5:-0.287671 6:-1 7:1 8:0.0839695 9:1 10:-0.0967742 12:0.333333 13:1 +1 1:0.0416667 2:1 3:1 4:-0.509434 5:-0.716895 6:-1 7:-1 8:-0.358779 9:-1 10:-0.548387 12:-0.333333 13:1 -1 1:-0.375 2:1 3:-0.333333 4:-0.320755 5:-0.575342 6:-1 7:1 8:0.78626 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:-0.375 2:1 3:1 4:-0.660377 5:-0.251142 6:-1 7:1 8:0.251908 9:-1 10:-1 11:-1 12:-0.333333 13:-1 -1 1:-0.0833333 2:1 3:0.333333 4:-0.698113 5:-0.776256 6:-1 7:-1 8:-0.206107 9:-1 10:-0.806452 11:-1 12:-1 13:-1 -1 1:0.25 2:1 3:0.333333 4:0.0566038 5:-0.607306 6:1 7:-1 8:0.312977 9:-1 10:-0.483871 11:-1 12:-1 13:-1 -1 1:0.75 2:-1 3:-0.333333 4:0.245283 5:-0.196347 6:-1 7:-1 8:0.389313 9:-1 10:-0.870968 11:-1 12:0.333333 13:-1 -1 1:0.333333 2:1 3:0.333333 4:0.0566038 5:-0.465753 6:1 7:-1 8:0.00763359 9:1 10:-0.677419 12:-1 13:-1 +1 1:0.0833333 2:1 3:1 4:-0.283019 5:0.0365297 6:-1 7:-1 8:-0.0687023 9:1 10:-0.612903 12:-0.333333 13:1 +1 1:0.458333 2:1 3:0.333333 4:-0.132075 5:-0.0456621 6:-1 7:-1 8:0.328244 9:-1 10:-1 11:-1 12:-1 13:-1 -1 1:-0.416667 2:1 3:1 4:0.0566038 5:-0.447489 6:-1 7:-1 8:0.526718 9:-1 10:-0.516129 11:-1 12:-1 13:-1 -1 1:0.208333 2:-1 3:0.333333 4:-0.509434 5:-0.0228311 6:-1 7:-1 8:0.541985 9:-1 10:-1 11:-1 12:-1 13:-1 +1 1:0.291667 2:1 3:1 4:-0.320755 5:-0.634703 6:-1 7:1 8:-0.0687023 9:1 10:-0.225806 12:0.333333 13:1 +1 1:0.208333 2:1 3:-0.333333 4:-0.509434 5:-0.278539 6:-1 7:1 8:0.358779 9:-1 10:-0.419355 12:-1 13:-1 -1 1:-0.166667 2:1 3:-0.333333 4:-0.320755 5:-0.360731 6:-1 7:-1 8:0.526718 9:-1 10:-0.806452 11:-1 12:-1 13:-1 +1 1:-0.208333 2:1 3:-0.333333 4:-0.698113 5:-0.52968 6:-1 7:-1 8:0.480916 9:-1 10:-0.677419 11:1 12:-1 13:1 -1 1:-0.0416667 2:1 3:0.333333 4:0.471698 5:-0.666667 6:1 7:-1 8:0.389313 9:-1 10:-0.83871 11:-1 12:-1 13:1 -1 1:-0.375 2:1 3:-0.333333 4:-0.509434 5:-0.374429 6:-1 7:-1 8:0.557252 9:-1 10:-1 11:-1 12:-1 13:1 -1 1:0.125 2:-1 3:-0.333333 4:-0.132075 5:-0.232877 6:-1 7:1 8:0.251908 9:-1 10:-0.580645 12:-1 13:-1 -1 1:0.166667 2:1 3:1 4:-0.132075 5:-0.69863 6:-1 7:-1 8:0.175573 9:-1 10:-0.870968 12:-1 13:0.5 +1 1:0.583333 2:1 3:1 4:0.245283 5:-0.269406 6:-1 7:1 8:-0.435115 9:1 10:-0.516129 12:1 13:-1 statistics-release-1.9.2/inst/datasets/kmeansdata.mat000066400000000000000000000430551524624707500227560ustar00rootroot00000000000000Octave-1-LXmatrix0'_!u8 (Bm 0.rV-s ՈFkXEsaB jEu 白Bn j U+ OȰp9*R zM;@|wp8[o h r)e!/ Նc d2Vgurnh 0bZҲ `f~ o$;zԫIKAdwBg\Vu Fy: GcY(ټ= ŹV1BTΡG!:g-2WN}H6(FI(9G 5jö R" bj䣱ꗫK< vgܷ9bVOZScg h|/Ogc?O }hclV|4+b΍11 u2*,`fvR7Te5~n^oQi$Q]⇣y *@vA @Kv3@&֞L@2ȟO@b2@ Ꚃ@h V?%"VN @Zpg?ŁO<@F @7@ڑ@Iw6@M @hv @x;خ@;U"@.@|A0@&U?D*2zN@5\B "?J|?Bz @ JaQ?C~@p㹐@zd~@\r?E@֗z<_?r7@%@X@?qm@L|Ƿ@'uc%@JF@.|@ˬ(@*F@%Koqh@7j@[i#c@L^K@^k_@>~Ѡ@pG" @z6٩0@\3X?J#2?X[r?Eh͐?g~@ߛ@ؼ3W @p@~s@:c{??O9o?c@L(- @H9{ @n?s @{,ۇ @NCT/ @u?>k @G*"@P@  @#_?K \5@Q,E.@Pe@t b@4d?|-@}A @Z/ @x@fzַ&@EP@ؼH @8 @Mf@P}-?6 ^@G1 @jȸ @B2D!? @<>@dF%V@2.t@$so?,@g0 @O@6 @w=@+S@pv7#@::!?qV4@ِ,^@pǨ?h@(g y@'15 @<`RF?k"ZL @R,p)@ޗ @: @_]@\qeb@[%@(Ysͧ@o?q>@@HM?؞( ' @1@4;= @܄(?] ?ޛ@@$q??7G @bU @I@;@m"?kL@vt} @4I @dBŀ @XZq _@a91 @1>Xd??@ O@w @@\! @ j @z@A@%%-?{jF@L|??@~J @9=qu?_@_ @2^@i%@(@¹?ޞn @t`7@U`q@\F* @\t@+KDR@B. @#\?@T#$c?^@ @.?pv*@IIK @;@i?@ {?@Bc @iÀ @;Ԭ @$i @cY>C@6bN@ (o @3@LbM?@Ftx?"͙Zt" @N4[?w8,G@ '@Hx@H`m?5j@k2)@A@z/|?ֱ,j9q@68C _ ,#Z0() &DO Zyuy_aAhBgXw|$L/| *h Å$)D/ 1wON wʙE8DI מV} s /sgȶu9"GX T-71b h R~ XW+ P/);W  ԫ3D[u` $@'Q 7%((nomZxn QM4.5 9M#3`d  t<kH5Sj蝬 ؾm9h~2 i 1=pka0el H0$jTZ` j`8;3tQ .d ^4^Mfh( ;g|б՝ FaN bVSbԱ8 ۮ] 6e \#4TcvU `+n7VxbWD ΜyO V7͌yvx S| B5/K.SLծ6OҼǘ{ ǐ.+TNdTyvcیGcQg?I ޯ_W 8 <EzQ@\ ,(m俼_mԔ!~]` !zYg 136b0KZ &eؚξvI ƣt'RQa z%}_i5 DmKxbZS|_62L0p5C9n6L%B[! UT1bG1Z7p{ 迾kgU%Þl ֬h$ DR  ?9u5޽{ <+#,)Zs;UZls ;9d&u"|aiT@DmJ@$6%@B"% @ږ%?j@X4 @`[@bk"[@oD@>þ@R-?tI@#u @?O0/?ܧKW@+v.-@g @~AP@h @(fT']@#@_r o@ js?|?ބ2j@$@~N @ ??{.G@h, @8j%@cq@Ӱw/0@ O?J?=6@/6@ݤ5@r-m?}[R@o@| @ %K @`We2@?r@ŗ;g @TMy@Q+7@m @:َ@^»y @+[@@D @ '9Mc @=KŇG?(@2 @в@kl @eMB[V@$CѬ??=@is^Z؈@hR[Ķ@f:@W+@1T @VOP~@DÈl@Ea@@ps3 @Cո@8?[ @)?&:>%>?7u?@ժ[@hW@48% @doH%?vGq@)?猳\@V@IB @m}ʠ@`*q? ƠY @-x8?JTw@Jh\ @@Ӣg@@ ??쇠V'@@m`3s?d@a7l@x+B @(3(@m|Y@EL?Ji@'O@>5@HgV?f:7?A6@бs@Q@Qe @# @,hEM?@V, @"c@Ƽe4@챛 @Ձ=? J 33O P2٠yb1 Kc{6,m _<6$sa-W) ڿMS TX !g '҄Q85ɩN2Ӱ n{1› r6J\u+ "Jzߓ1vf@ @W5?X?1@7@~. @/?2~@A[՗E@U(BA.?T,7@1Su @ 7F@~A@# W @@~7 @xPD.@3@F=>;?x@M?ɑi?(@H1y@`ȑ@K)oC@ @*1$(@G* @]GO?z. @b\ @#v7@z|@ f@,@ٹBH@ ?׍M?&Lө@̓'@'d@ ?;w@z? puJ @_8@=n@eRV0@ڗZV@Q:O&@#g@w 1@1j@=G [ @6f? {@N6?c @A2@!ys@pSh% @9v5@Bj?oԧQ @t@@@C@ ,&?"j?^+@%@O6)? `@f =@ytQ @G' @Z @,u@T?^ĽQ @!\BXI@`Bfy@vu?`bD@ @b|/;:@X#?Sr@l˶@!@iˉA?O]*% @V@ekR][@@WB @ZpR\?@aQ8@>W:?yj @R>??DI @-%׵@J @?wV@S/h @gS徿?9Б@ۂ#c?{b @Kv@b@|- o@n]_@9t@z@g|@з2@J/m? &k?T- @i},f( @ny{ @=|i<@._IŰ?=8@FAl?Q'?T#w @E @Lw]zA@F@[ږD@7T5@3X@os?Gbv@M?@ah±p@ښ9 @!\j@+gm_@:e= @gã@36@%^&@/v-Qg @ ⵛ?to @2:!?f1 @ @RQ?t?ڐ( @z5@ ~78 @@J;b@g&Ӯ?q@CY+3:@ְ@k=@Їh/[?, wD@J06 @^mǴ?a ]@m @G@U @y+@oLlg/@QW @fޞB?O4&?K @3 @1d)=6@>@ZCJ@2@(j@ޞI@v @"5@;>L@):@v$@@ȬV @@}?ϩ)@˻ u @;i(g@N\Q @j{@ȄY?<5? h@~YPv?fX*p@df@Do|?設?1y@+=@qLII=@_Q@wTK)@yÄ @f_R@7?|$w~ @N}F @zx?&:3B@oj=@WS!@` [3'@"|?X[@HW@n. @fe67@t?y:6@TN?!I+@x|g @: L@Ea|@8 @n*?n+do@P8>@;b@@x?i-@RGw@Xy@ls@5uP?S{3@Da@OƒEP @̝^< @FM @}"{H@VoݷEE@Ѩt)?Zs @<~cK@tϳ?1E@iv/R?*T?42B-C?[c j@ @D?‹]?_vP@e?G?gm޶#?>@ӭ~@lao@_ @BA @N\?Z@ؓ^?v,@sA @6ق@p7@I@l38@"ը@KcfK @6FzN@#)@O?XGm7h?ή7?c{VG/@dEX@\z1 @aR]@*j @la ӝ'V{ vo gӅ~ س3 ~}_c X.كv f[񿪴C' K M$;o7YbL ܳ_ V!e\(~c15i~ ߞ7LKd ٦a|M&f{Ӌ/lkX0}5 N‘A hBe2]AsD EMj |`., ײyR֓4{ | r k nۼ225cwa >a0b&.j^,GKFabb)daj @s8GU#tڶFI񿤢>Z *uu Xp;,v߯@N~ϬJ 8 #u 9Mj Ett=Y/ rN8jM͛ ە; sD1s_nV IN/ BG{0V7JUSSvRMBV[Vg+ <#9cp}|6 V;AvX SG:Oŏ+"$}O"a7 SPb ^%o|l-N}QM2<x K )mB jAށ |,[?B蓳 եىV~ e[|hBQ /#d}޷K$oPGF51N ,kQ z =8*?қNyO3-~uH f}JFDNUXDx8(GEJn) &VP΄QaCU-{5 M ׉ IE^R \%^ y*&y_e jr?̖,ďfZ?VcXhQ9TX0@)d[?=@ve*e@`q @m?v3'w%@ @L6@b75#O @(s @Bݲ~@%LUZ@qL5s?3@Sknf@1N$m,@6`?]q?p۪g@&R@6n@m? @U@53@HRD@@ ,r˚?4`{@4 @NH)R@lo@ '7@Lś@Y4 @%dN@?֞@d?S4eL#@dtL@)?|C@֨" @w75@=t9( @pk @%*?6Q?5/@rv92@1׮ @-}c?[aJ.@s] @p@ƒ?P֡,@ ۶o @C"|@ؤv}@ӞQ% @twk @v!R@?9=?*Wk@1QP@.@'|@gi[B@dS@h,P?wV@?A#'@~6e @n2σ @`muY @rV6?lZc @(ä @g[@HK@. @@ͻ@@)?ꚹ@Ո9& @ZB<@7 u @8M@01iz@qbvF @֙n?rS46 @!?@wR@l]?)A@gr @~@ɖΎ@lT!@?P=?6*@>گ @fKx| @`QW @F+ǛV @X9c@y<}Q@_ @5_@і)@N?÷?u Z?py@ר@&|?XuB0c@a%b@VLX@n @Wo @W @FO"h@6b8T@1/R?gQ@=S @y?7;i?@)@^LŪ @o_U,@{ @;z@S[2(@>VT@o %@Jֵ @ʿK\@n t(Mc\pU=vTIPZn- .>~`3zxZ Ů`#ꓞx[ rF BA V!$ y<C!@gx+7L C?G2w" ‡"$}ociJ aNRt!짤񿻡A.kl=G6`P୲4s<|>1 6U`N>z29XȍX} v%1Z yOuI0zPoxnQ3/ cѰGC*'<@\:1T]v,s=\ y. *%"kf$7- - X g#,Ujҗm 8KW f]iT$EC)1Y]#Bdv5ZKD@vk`(+]'bɱ4P)ё"I E\f_r,"Ck迃2o'CCc֛qk@0&^ @S(3?, @ @Nx,`?风/@|( @bYn@uzSq @c )8 @~ e@ݓwӾ@VM@IMyg@TCV6@ >V.@s)J8@_ @j @}j* @DPς@3i@b 5@@@(?@ϒ?@LfaY@@cIf?F?ifg @@ @϶ (?] @4n̤@[ѣ@8]v*v%@ @geƿ= @>;@NY @>gi @Xvq @/Gs@kaހ ?]v@97 @ʋl?;I|@# (?*@**2y @Io@ag@5Z?Q- @^(Ŵ@WF) @]`%H+@t@J9?f2c @/p @@?qC?` Dk?n ]?M@pM*?ڸ{@ @Ⳕ+@@n @ea@!H?pxT @C@ ?Da@xa+ @0FZ @PqK@̱ @-1@D7Qr@0_i@@t@!k @ƒPu?| &@T@l?7* @o5d|@רqՔ @H@l@6&םfګ~;]`Nyc W}M,)h] ]SBG %6~α ɦ yZ%80I hQ Fq 3}XE1 BNVO{<I>j4QGq>-( $)i'翑> W<,v [(vT\ XF>sX>mQ?@P `M( \FF'D dք G9\ 9(l\5 a|:%_dx=YU !SKٌQlۺdADYWn 45[ 4JX l*W³uzTJ[Ow s jlS*t2 ;\#+=A(V* Z_F{!4 43?IH]v@voÃ- 8{87 E%THa]a R @ YM @[A?(8K?F@]$@t@к ۿ-p!@ K {?p.?p@@GNw%@}ѵ @q4< @3O_0@9@6@!@I&+@ U;@&\_d@=U@zO ?#M0q?)%@\M2@.O6< @"q?jy@˽1&?^@1 8?Ԧ?x-$@ @=@y-3 @d. a@vj,{@G @Kd?tT@'֠ @P(/?!ֱ?Sj@vh9n@a_@ϯދ@?@땋5%??Tr?GO?AP@¯ˌ |@H'|\i@F~@4L@m@cv@QUi@/?@!!?E\:@v?R@9ܾ)@n @]@ . Eu@Xw@b3@;k@EI@=;@;N?@e':Y @ecl@u"]?f+F?sa;@ @5B@vbL@X+@ #@)-@@ճ @/09 @5 @D:!~@bp@(? @7بf< @- f3@Xa[@Tbݗ@p}1@bB#@RRo@L$ ɒ @;`;Cw@$G@k{ui@ z0@*c=?p @.5|N@DZ @TaF+ @eN @DU&7Gnv@y@@gaAH@EP" @-1/@R@LY @Lt&@7!@MryR@[Q@( k@!@! aw?ŠiS@Ӈԏ @M$k@3֢(<@{i/@ ZIn@oFJ@9ό@HM_!@t @6J@ -E@Gn@Jh4)y@_mBC@g8F @"ul @^U @ iG_@z# @q G@T0ly@jWʃb@c sV@ z?52s?.[) @Wi@_))V @qAX@ G7?@!S{;@̊D?rAe@nL@?l @ 4 @֒Q@Gb}@c_A8@M, @P}?OѮo@m* @&S@8' ?ۀ @8@8@U @S`?> @Ջv @X @p_[?3;\]@P@vZJ @@:ZN@@#?iCN @k1w@Ogg?xI[L@|uT>?gI @$I @8AZ@:pg@6:f@pSra @ @@@;?'D?v:@BO3G ?d?c@ђt@!ƍ(?Zo@qa@30 @E4>@2@<fE@&!8ń@,bq@ ?8@w @W/#@܇@f@U+HQ?I ?;]?v@:Ї@st3Q@!P@J~W>//?sƟ+ @|[T?8s@,@#_@]@Nc?fm2?";A.@=ϥo@G> @rm@ C@fJnh@3Hx @s]Σ @A@E @U @ߜ@tm@:Uq @&@ @ձ^k@[zm@@ D @է7@W-@1ǁh @?@ڇGf? ?@:b2J4?g%§@Om&@<PhA@`IV?F@c,n@i)@S6L@cr@dԓ@A?@ԖO?\;@:[Z @a @H!W@ $d0@<}y @(evF< @bBεӊ@̭nY@5W@O@h)?__-qQ @i>?у? +@_6 @_R0@Ȅn-?m{r@N[ -1 Vm\tԥo σ\a (}˻ݿ.u Je ?5\3 ȈGY 2 ^ W@-?۠Qy(3 3rY XcXtk 1A`/eւISN\N[NJ!|FfV滯KV͆m 1D#vOW=F1!i(ó̵m*Ae83802,Zn簄Jc#ksL k_;v2n.-P\ q uޚ\ ѭ L8xgõ`ҵ003 2 \ B+ Fc TBH%f~*E' ֧ ߩ5Z .s:Ha>DIK~/T,G| ft+rX(:&~ 6statistics-release-1.9.2/inst/datasets/lightbulb.mat000066400000000000000000000046251524624707500226220ustar00rootroot00000000000000Octave-1-L lightbulbmatrixdӿ@-Rd @W 2@@mH3 y@+ğ؏o@Ŝ @S&3@nɖV@G~@[t. @ |ݣ@u@r[.@Y@>mH@[@qu@b.@"p@Ua@B.U3@d0`%@9[@c5=@gkE@'ja@0n@Dܞ@vv@wb@kI׻@n@݂@@;=]@@)S]@?ὼ@ܲ!@/ @@@)r-@[c@)̆@䏂@t㖹@,@g3q?@'&@ Z@ƾAK@HmT6@~Ē@tR@œSn @<6@c@i @MFsAE@f}@he @qVl@zYJf@ED3W@x5@\2?Ϸ}@i6?@h;q]@=EC@ &'@!E恇@˵JH@#_@ӷ湙@ @A2ʉ`@չ@`h3@jTV'@gg@a&a@(&%@Ul@0vs@42^@'g$@:R+/@Y2<@ /O@@jK@h|@BܓΚ@\}R@1/V@@4$A=q@o8)@Hܷ  @b@F @?????????????????????????????????????????????????????????????????????????????????statistics-release-1.9.2/inst/datasets/mileage.mat000066400000000000000000000003031524624707500222360ustar00rootroot00000000000000Octave-1-Lmileagematrixfffff@@33333@@33333s@@L@@@@@@@@A@ffffffA@fffff@@33333@@@@33333@@33333B@ffffffB@B@LB@B@YB@statistics-release-1.9.2/inst/datasets/morse.mat000066400000000000000000000256461524624707500220010ustar00rootroot00000000000000Octave-1-LY0matrix$???????????????????????????????????333333????333333??????333333?????333333??333333???????333333????333333??????????????????????????????????????????????????????????????????????????????????????????????????????????????????V-?v/?jt?v/?M?S??Zd;O? r?M?jt?ʡE?X9v??~jt?sh|??v? r?K7A`?ʡE?333333?T㥛 ?/$?ʡE? +?-?y&1??I +?Cl?'1Z?sh|??V-?On??dissimilaritiesmatrixvd@ e@c@f@d@`d@`d@R@d@c@e@`a@^@d@d@d@``@b@ e@`@d@c@d@@d@e@e@d@@e@@e@d@d@d@e@@f@e@X@S@`d@W@`a@@Z@d@a@@]@N@@d@`d@b@^@@`@a@@a@e@@`@@_@a@;@@]@T@b@a@b@\@@X@2@@W@`@c@`e@a@d@\@ a@b@@d@\@]@W@d@d@b@U@Z@`@d@f@@d@b@ `@@Y@=@V@b@`a@@^@a@c@\@^@\@ a@c@e@a@Y@\@c@b@9@@R@@d@b@c@`@c@Z@@_@e@`@b@@_@^@b@_@d@`d@`d@@c@`c@a@b@ d@`e@e@f@f@`e@`d@`f@f@d@f@@f@e@e@`f@ e@e@T@f@ f@e@f@e@ f@e@`f@e@e@f@`f@e@f@g@f@c@]@@e@]@b@Y@@d@d@c@W@@c@[@@b@f@@`@_@\@`@^@a@ d@a@@X@ `@@[@``@ `@c@@e@e@`d@e@`@^@b@a@d@C@X@b@@`@f@ f@c@@d@]@b@``@@^@@b@c@d@`c@e@d@c@a@`b@c@c@c@c@]@@e@c@c@b@d@a@U@e@[@@T@b@`a@b@ c@d@d@@b@@]@D@_@b@d@@e@e@`e@e@@c@e@a@@e@@e@ f@@c@@a@c@`c@e@d@f@e@d@e@c@ e@d@@e@`e@d@@f@@f@ f@_@[@@d@e@[@;@Y@b@f@e@@d@c@ a@]@@[@@^@@R@T@\@@a@@c@b@]@\@^@_@@]@@c@c@^@@b@`@`@e@`e@@]@a@\@@Q@b@`a@d@@d@@c@c@ e@b@b@c@`e@`e@e@ e@ c@U@\@\@d@@e@``@]@`@Z@`@O@b@b@a@a@@[@]@Y@ b@`d@e@Q@d@d@d@b@f@ f@d@f@@b@e@c@d@d@f@`e@f@`f@`e@`d@e@ d@g@d@ d@e@a@d@e@c@f@ d@e@d@d@d@ f@e@e@`f@e@@e@e@`d@f@[@^@b@e@e@@d@`c@@^@]@``@``@ a@a@d@d@d@b@b@b@`b@a@T@]@d@d@b@`a@@`@^@X@@T@Y@V@[@b@ b@@a@[@ a@]@a@c@f@f@d@c@a@U@R@E@@\@^@a@`a@d@^@@X@@S@@\@`@b@ f@@]@`a@I@@c@c@`b@`c@d@b@ d@ d@c@ c@d@e@ f@f@T@ c@c@d@ e@e@e@@f@e@ d@``@ d@@e@e@f@ g@e@ f@e@f@f@e@e@ f@e@`f@f@e@e@e@d@f@X@_@`c@d@d@e@@e@c@``@c@d@d@ e@e@`f@a@@[@@`@c@@c@c@`@G@@W@\@`a@d@`f@@f@`@_@`a@@b@a@`b@b@d@c@b@d@d@`e@@U@]@@b@a@``@]@^@P@@]@a@@c@d@[@@_@@Y@Z@a@`c@[@]@Z@`@b@`@a@`@d@c@[@L@S@`a@@b@H@`a@d@b@b@`@X@N@@R@L@`@d@b@@_@`@@_@@b@Y@^@Z@ a@a@d@ d@Q@@[@_@c@d@ e@]@`a@d@`e@e@E@`@`d@@b@F@\@b@O@@Z@:@ morseCharscell$ sq_stringA sq_stringB sq_stringC sq_stringD sq_stringE sq_stringF sq_stringG sq_stringH sq_stringI sq_stringJ sq_stringK sq_stringL sq_stringM sq_stringN sq_stringO sq_stringP sq_stringQ sq_stringR sq_stringS sq_stringT sq_stringU sq_stringV sq_stringW sq_stringX sq_stringY sq_stringZ sq_string1 sq_string2 sq_string3 sq_string4 sq_string5 sq_string6 sq_string7 sq_string8 sq_string9 sq_string0 sq_string.- sq_string-... sq_string-.-. sq_string-.. sq_string. sq_string..-. sq_string--. sq_string.... sq_string.. sq_string.--- sq_string-.- sq_string.-.. sq_string-- sq_string-. sq_string--- sq_string.--. sq_string--.- sq_string.-. sq_string... sq_string- sq_string..- sq_string...- sq_string.-- sq_string-..- sq_string-.-- sq_string--.. sq_string.---- sq_string..--- sq_string...-- sq_string....- sq_string..... sq_string-.... sq_string--... sq_string---.. sq_string----. sq_string-----statistics-release-1.9.2/inst/datasets/patients.mat000066400000000000000000000660711524624707500225000ustar00rootroot00000000000000Octave-1-LAgematrixdC@E@C@D@H@G@@@D@<@?@F@E@9@C@B@H@@@;@B@I@H@C@D@F@<@9@C@9@B@>@F@D@9@G@F@H@F@A@@@C@C@F@F@B@F@B@>@C@E@E@H@F@E@G@I@C@D@F@B@C@=@<@>@<@=@B@F@@@?@H@9@D@C@D@@@?@A@@@E@H@A@C@<@=@@@C@B@H@?@B@C@F@>@H@H@9@F@H@F@H@ Diastolicmatrixd@W@@S@T@R@T@Q@V@T@S@U@@S@Q@R@W@S@W@W@S@@S@S@R@S@V@V@X@@S@T@S@T@@V@W@T@T@U@W@T@V@@U@V@R@W@T@@V@X@@V@@S@@T@S@T@S@W@V@V@U@@V@S@R@T@S@@T@@S@@R@@U@S@T@T@S@T@S@T@R@V@R@S@@U@U@R@S@@T@S@@U@S@T@T@T@W@W@X@U@@T@V@@S@V@S@@R@X@W@R@@W@U@Gendercelld sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringFemale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringMale sq_stringFemale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringMale sq_stringMale sq_stringMale sq_stringFemale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringFemale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringFemale sq_stringMale sq_stringFemale sq_stringFemale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringMale sq_stringFemale sq_stringFemale sq_stringMale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringFemale sq_stringMale sq_stringMale sq_stringFemale sq_stringMale sq_stringFemale sq_stringMale sq_stringMale sq_stringMale sq_stringMale sq_stringMaleHeightmatrixdQ@@Q@P@P@P@Q@P@Q@Q@P@Q@P@Q@R@@P@Q@@Q@@Q@Q@Q@@P@P@O@P@@P@Q@O@O@Q@P@Q@P@P@Q@Q@P@Q@P@P@O@Q@@Q@Q@Q@P@@P@Q@O@Q@P@Q@O@P@P@R@O@P@Q@Q@Q@O@@P@P@P@Q@Q@Q@N@P@P@P@P@R@@P@P@R@P@Q@P@P@Q@@Q@@Q@P@O@Q@@P@O@P@@P@Q@Q@Q@Q@P@@Q@@Q@Q@Q@P@LastNamecelld sq_stringSmith sq_stringJohnson sq_stringWilliams sq_stringJones sq_stringBrown sq_stringDavis sq_stringMiller sq_stringWilson sq_stringMoore sq_stringTaylor sq_stringAnderson sq_stringThomas sq_stringJackson sq_stringWhite sq_stringHarris sq_stringMartin sq_stringThompson sq_stringGarcia sq_stringMartinez sq_stringRobinson sq_stringClark sq_string Rodriguez sq_stringLewis sq_stringLee sq_stringWalker sq_stringHall sq_stringAllen sq_stringYoung sq_string Hernandez sq_stringKing sq_stringWright sq_stringLopez sq_stringHill sq_stringScott sq_stringGreen sq_stringAdams sq_stringBaker sq_stringGonzalez sq_stringNelson sq_stringCarter sq_stringMitchell sq_stringPerez sq_stringRoberts sq_stringTurner sq_stringPhillips sq_stringCampbell sq_stringParker sq_stringEvans sq_stringEdwards sq_stringCollins sq_stringStewart sq_stringSanchez sq_stringMorris sq_stringRogers sq_stringReed sq_stringCook sq_stringMorgan sq_stringBell sq_stringMurphy sq_stringBailey sq_stringRivera sq_stringCooper sq_string Richardson sq_stringCox sq_stringHoward sq_stringWard sq_stringTorres sq_stringPeterson sq_stringGray sq_stringRamirez sq_stringJames sq_stringWatson sq_stringBrooks sq_stringKelly sq_stringSanders sq_stringPrice sq_stringBennett sq_stringWood sq_stringBarnes sq_stringRoss sq_string Henderson sq_stringColeman sq_stringJenkins sq_stringPerry sq_stringPowell sq_stringLong sq_string Patterson sq_stringHughes sq_stringFlores sq_string Washington sq_stringButler sq_stringSimmons sq_stringFoster sq_stringGonzales sq_stringBryant sq_string Alexander sq_stringRussell sq_stringGriffin sq_stringDiaz sq_stringHayesLocationcelld sq_stringCounty General Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringCounty General Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_string VA Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_string VA Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringSt. Mary's Medical Center sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_string VA Hospital sq_string VA Hospital sq_string VA Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_string VA Hospital sq_string VA Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringSt. Mary's Medical Center sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringCounty General Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringCounty General Hospital sq_stringSt. Mary's Medical Center sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_string VA Hospital sq_stringSt. Mary's Medical Center sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_string VA Hospital sq_stringCounty General Hospital sq_stringCounty General Hospital sq_stringCounty General HospitalSelfAssessedHealthStatuscelld sq_string Excellent sq_stringFair sq_stringGood sq_stringFair sq_stringGood sq_stringGood sq_stringGood sq_stringGood sq_string Excellent sq_string Excellent sq_string Excellent sq_stringPoor sq_stringPoor sq_string Excellent sq_stringGood sq_stringGood sq_string Excellent sq_stringFair sq_stringGood sq_stringGood sq_string Excellent sq_stringFair sq_stringFair sq_stringFair sq_stringGood sq_stringPoor sq_string Excellent sq_stringGood sq_stringPoor sq_string Excellent sq_string Excellent sq_stringPoor sq_string Excellent sq_string Excellent sq_stringGood sq_string Excellent sq_stringGood sq_stringFair sq_stringGood sq_stringGood sq_stringFair sq_string Excellent sq_stringGood sq_string Excellent sq_stringGood sq_stringFair sq_stringPoor sq_stringGood sq_string Excellent sq_stringGood sq_stringPoor sq_stringGood sq_stringPoor sq_string Excellent sq_string Excellent sq_string Excellent sq_stringGood sq_stringGood sq_stringGood sq_stringGood sq_string Excellent sq_stringGood sq_string Excellent sq_stringGood sq_string Excellent sq_stringGood sq_string Excellent sq_string Excellent sq_string Excellent sq_string Excellent sq_stringGood sq_stringFair sq_string Excellent sq_stringPoor sq_string Excellent sq_stringFair sq_stringFair sq_stringPoor sq_string Excellent sq_stringGood sq_stringGood sq_string Excellent sq_stringGood sq_stringGood sq_string Excellent sq_stringGood sq_stringPoor sq_stringGood sq_stringGood sq_stringGood sq_string Excellent sq_string Excellent sq_stringFair sq_stringGood sq_string Excellent sq_stringGood sq_stringGood sq_stringFair sq_stringGood sq_stringFairSmoker bool matrixdSystolicmatrixd_@@[@@_@@]@^@@^@@`@\@\@]@\@\@_@@`@\@@`@_@^@]@@_@@^@^@\@`@ `@\@@\@@_@^@_@`@@^@\@_@@^@_@a@@]@_@^@`@]@`@ a@@]@]@]@^@]@_@ `@@`@`@@]@ `@]@^@@a@@]@@\@^@\@^@@]@^@^@]@[@@^@@a@@_@^@^@@]@@_@_@@^@]@^@]@]@^@`@``@@\@@_@`@`@^@^@@a@_@@`@^@ `@`@_@]@a@\@Weightmatrixdf@`d@``@`@]@a@a@f@f@`@`@ a@e@@i@ `@f@g@``@`f@e@`@@]@ a@@b@^@g@a@\@d@@g@_@ a@@a@`g@ h@ a@h@]@f@`@d@f@ e@@h@e@`@f@@^@c@`f@@e@a@`@`b@@g@_@`@@e@f@@`@@`@_@a@[@`@g@ a@a@@`@ a@@g@_@f@_@\@@f@``@f@@h@_@@g@g@g@^@`@f@^@^@a@ `@g@f@_@e@`@`e@g@@g@e@ f@statistics-release-1.9.2/inst/datasets/popcorn.mat000066400000000000000000000003031524624707500223130ustar00rootroot00000000000000Octave-1-Lpopcornmatrix@@@@@@@@@@@@ @@@@@@statistics-release-1.9.2/inst/datasets/rundist.mat000066400000000000000000000227101524624707500223310ustar00rootroot00000000000000MATLAB 5.0 MAT-file, Platform: PCWIN, Created on: Fri Mar 31 11:35:28 2006 IM@%x <+; @%81JfəA`dgJECAF{?13&}~C'"m[dYMW_[&}/m<E H SM@=NN"?aПwy2  @#%`4 /zNe_3@(޳b,k6αSwrR> qI {`gnOf1Gyd`hG9IރߤEO"V,`a:m19EOA ϱ+kfotS7ɏiLϪ-oAYA@V xFa#A*ʵ&@Ekt ]ai S׏2gZfI5dkT:3(4D*^UN6.D23>!VCE{]66hdCArSi)_ih ?s n7gCB@pHT5; 62u8Y]e3P\_KOеi]֙+Ugs9A&B=❡^%cvrmgyZe' 7C/VŤg@ERi=Hf rO~b7aʻAwp!脈b^L<>O @?Am@qriD-Ng_$bKAr-SMiyi' ) ɂ1x’u"ig*],o B =G3'!Mޏ0 Z-vudɯėE >Wzut7`i{AH;s~eJ&+-z80u?K@%^*ڮƻ@I{{K z1] 9^ ڱ]k>g/u[aulp/ް(>SWv3Xl16faܒY ez>~Yx%dyQ*^4Z|9ls>eBhQ]mgXX>[C3wI5 tPLۂ@NՇIpv#JG+`pgy(r%\ cnG>o;P?yװ4xq^W/V P!PSpn46$gJU N}i-fO^F+Ff: o. Bad!߿Ag{-܍RSh,&$Nl|\yy&H $x9]M kn*n ~`t0KEl]&NkM"Ln{v/ޙ/} )~0OUķ4@YSQy UDN2PmKzWϧ]&.Q@Oi"R!n'kȶ5&K,Uhybװv70T 6E!_g*"^9)Fi3TwZ>kqȣUuP".e1$xI_ ^|ƴ-Iȝ&mB!Ȅ4XRkHGWk@3o@#/A!;h;y˸c U7TLFĒ3>OZbk y{@_pPJC͠R"Jy[6a5jPC"经G#̶*皎>Id]3}⣒HfO b?,e .p2BLxVle-ZX')*9]ȃ*5"£w|Ćjۆd\4{=}I=5 9,0rSɢ_yzvssgdggwhÎoٛ%)vФOr6Z 5A]nzT]vH349BH:‡ >SRZӟ{%-q%Pd.#zplrI|ˆd7Pz{D|2c.6ҍ%h^ϋbK:se6޽ήsoBѽ~2ik]G] }EUvףAשB2' F%oEE_z PyQywGNWP>H8t*\r!"7aEA\3; '"N 8‰NTB@S-(\Z tso'W̌"竏 &#qHR z>BA]~(rb^)S0vOo T8G]*Ec5V\|d츥,fw[%Vb{e\`5eal=?UVkp}Dƽ"IF=TO rpW0cD/A'Е2E`럋^ J2zڇ 帥IHioxxpL; L@C" !v"9P2Xx^ G#7bîv4#K`:!]?[-E' Ucs2~0->gVAަj1|1i3b~Xkѣ[~fvXQ^Lcth'KNzTB;p[G;+qnЊ%iBy%جpbm=n\pmo qm]Ž,VN  [K*Bt:_x(? uaAJ<',.*j׷=״IZ;/Gb][>x?zW~wVV8dyK^t+u kY?|.ƂJ0ĆN\+Lr,cbRu,A AXirF 8<3y$=~|g ?}&m{D'7fkZCyƻ)YpYE Gvx|[!kO><<t:Ry:< =ۺMbT ݼq=kdM1&y Kl hYsl&SR_sSާ}[劳@ ͛3Ź]߰ڪ{X,d)>2kXgaas獜z׎bJϼuDCC3\՗-|S6R/JR$LӯY F}\iQ 3 2gqk~H^QAjS7|*eS>v<<<[wl g{-:T{E\1A 䛻sQ{۪FPLG<6`9*B;찌`M΄xJ]lS.o0[ڠs؎ R"]Wϋ᭻ս58[wB-Nީ /$+kenӶntw>p=<ݏ.f|f k@x (H_-|3߉w\J+F&x\0 g Ou$/C3\ZT叚?d+ FuҨ܌1lW0JG sهr4Q>)YuwjK& Q$𸖺oW؛[k^޾wI$>&%~(EaݱH,C-+7V= UTaoJ|PS9-/`0p }떓wZŭ=ܞ\Q*4K.݄l2 Hr H,6ZI+w Yj˄_JV~.T&Rp"mM 2GنLW<${g6)梅U]џ昃o Q/e]3֙Ƚ^-R)mǷA 8}LXwן=tqsiAVuӸ، %'ʼnΟGlѼ' /%^35g'\ILpDoݧqͬ2pӓ_U?EMFSq7b#-C '`g4ŏW:2ố__~u{$ TC8cR%ǴqQA E$U/RpG0*|/GMJEvujb#-.fTjYMd4'txMwn{bKH]EL$#O /;6[&>rJ,|\߳|t7=ɮPYwQC\m?QgiIos[?/[R_U&yfy!?I/ 8oD$~yqԕD>է>rXc;ǂ?Ͻ>,^UCmTUK0."y=-T2='lW#f5/F̯a f_eS{k-_+# ljW \HFa]GU.&!{w.{snYV)5sH6 I:ASap}6 719~)Y-?@'nNa߭oˏ7aA@g/ڒYAjX]kPr8U^|&сePp4%`Z,R^!) )]sR1}3XgUѓ4|ϦDD t\9ff$UlFMd &us`ɊjJC le.N Zy"'& <ԥbJnMC[z;p>)츪FidȹI5.^HHŞlv'4kz&^xڨgWlZy顧>?oM?~VaXbLq,I^dtpUpXOq~TI_+~3RӳF,߇V7cʹ1:vCZ& Ma+J7xKqQ¯=O9 k-wr,VKmt[-ɆaLVK@QQ#y=ݸ4UV1(w^4ѥE%C댒]KGHg2NKϒK.F9?~nMrT\>+ƙ:"W)*uc+x#lK::U>`rOZ;m@B~Hjsu YJ hԒ&-6rҼ ar&k#ce#0Rn-5zzЉ ʿPx ܈Vud::x)pch)|DeaOK"vv1ad2U*'zH0/{$62YvuYr#ᝣH\.g:vP5HbK;?UC!욎_P5 b%k)V4Jz辴kr<\ڦV\جu]4cѬZ(P5& } *8^sa uBYE\WWR#3q߅u;Ot ᥿aNЦiwakF̱KXc/h<ؖh OA ޑׁsRƧDRZBD׺ԨA~6rݔ~G˵hIpQ,jSDp|% 4ԩd6:Mq}9]TގpN@CJ_{fU0{@,H3MgIf6$0{ǧ唀{HzG73pʬ\vݗPl0or9 ,;1%(IӨң.Q5L*gxꑦt? 2#BΣK[ߦ(`;O}9]SZX"'eRCݒ{bjK^I}塿XyVҢO#-#|E^Ӄh.KR%rޟ9*7QKs66;n;2V; Ѥ8ZvhX?xwIQ:WU[DAL4HSȥ64iG59"ߥa.eVp;4, ҥT:T8TW zR&70Qdeik$zr5?ҢW}W39A@ش􃺠<,Ԣ*T|y㤨8駿 7UqzR&5\6:秨!S>i-/iQդٲ|]u,)w㣥Zypw֦_b,/%2{Pcz(MhXR6Ġ75|ݞB%UMjQLg}1Yܟ)&oE =J cAN?0`ɕbL{)"DbKz2-TV5/.UioDI5ӽN˒x%s}F?zjUgYe$~Dd73 \$?RU򱻠Ϛv_w+5{rf{Pk9~߿y(@̘}|Ɋjڡmē̠Ojv!Ɲn3`ϞгYښMKFdyW=`ϷKpBO7P\='o|hv[:+F=yX5ᱟRξ =Jho1=:7mơapɡw|١l|&iOqFu%s}'-\Va"9ㄡdx-U+~XSG^2%}8HSݠ2֡w-*3 毐2-sڝH]- x|{נ;Ps(Έ b, (z4ӽNꣿ_|/{vϠJ4*pŊLàR\U]WWjNA~6rݤtpxr a +g(xߢc vXՔdCR_#Ap-3jjZ_$^TZB>٤؞YC8 ]RHڍ>'l?äv/SVӥ&7D3<iWVCVAvLƤ!YϹm%ҥI*^J]20k񤿀MEڑ;(!hUK:ݔZ ݥQJ?OqN>X0_^ YKl爤psjդR)v4L1%3l[b勵EN夿T !ǦzؘH_i:Ȣ=Abari8-xWKu/\E$9Ϥ -epuĥ4 }s!u;ʃ$*T7>ϟ6ӡ8enݣ?a'D.8 'Lʦ9@0G;TގimkOs"'0m6>4)Y? XUodS ǧ~įXEsJ_9h|?5@׾w}欧pXQQۆQ<{2QԥL5Ҧ¦vz[n4c= (G৿-v2!Q8+XۼqtÞvk˸p5馿EJ{`I2g噗æ]C먿Q/1 D˥r|S:6㤿NN릿F^)u8Fy'L^w>XƆd:tzލNMgF7k*5{ɭI%rK$w6?Ң>_@/ܹ0eU*vié7 *!Q*᩿0(Өr Oƨ*J= B9Gtzmmϧ&5Ш":v٧0! "R.5"6ǹM9zަYUkC48#+n=U)rU;zlˀ#RӾzÖMh^`VBA)ZM/1门l򖫧֊6ǹᔹF0:N$#;Yh9}?qf\S7 ~1[@5_%T 7[ A-T<0𩿍 ra0(hrs蜟⨿Ͼ =EDR{؜guSʫC9Ѯ?616T&}5BX%eikgdSCBYqR8y_ǫM~TۿҤrmoNz1rVwD|~!SD/; ;F0:9}гYr{ >M.ާr4GV~kɯCfڮjׄI2宿z4ՓGdP3&^٪WW;pbT4֦EИIԫŒr9>!欿[_ѭl^Y-(IӨi{ԧ%:-W?6ɧg\8H|穿:w橿) 0>$@M-V-VEeÚʢ\$!9US6~Eկt>ҥI*.PR`0q Tz^[#qp騿0T7B۽'gx:6Ui 5ᪿGW#Z7q rauXYw!>Mϸp $D]L3uӭԖ:멿#g]娿L<թmraQw憎F};l"R@d"<uWvu=겘|\fCêfh<ĩT;Ԗ:]J]2 w閭ճ q{b5┹Ftl6YrL@ϝ`_=[y7yUgnߣzEV 'ׁsF!YnЗ\4}Rcxǡ~f # #BQcB/oj6qrC )?#GG4 ;֥FYh4 ~:Rh6~\iq$n!'.#cG( _箿:]H92|"1A ߪL1=B^2ᗪ 34⬿wWb"k^YC=ЭUka9{?mhVyW9ҩ5ed:tzޭsAL*Wx j%!.9֯u6䯿PgQN뮿o^j=,ԚnjeNĮLD 7Uq9}=_mfcsvU;MU1~٭>:u峬*z몿x ۟E_X<᰿a3ٲ]R߰QO?3M~2P魿iwak%8䭿G Efy0,"JR)v4 vQ1߄Bݒ9"߭P|zlnRAE[6Yѭui֦! _BΉ=Y4 \z(M.ޯK8n\p;](ܩAl:Ytv4Eܝ.49}=T!x|{נ{-1q|#k]j%z'fʉ{>%<ןįWyrwcAaP*w71l-;:6T$?W}%F7kNC:۞ q{c?Qٰ>#K@gҦꮿ<ڨ`sD$]O?HV ESºȰGpaxwd| EY |EQFٲ|]T-B=}!rz=ЕT(oP\ˡE>$@M-Y|^=ЕTެ\=}p0ۉH۰Ѐz3j~7t_ʰNt"ݑK> ]VPۆQw~ЛT[sFZ*oJ>etTvCVzu殿l<*k+ݓ>+W}WLnYk(AD[X*kq_lW胱˱}V)o^j,ůG6,{جճ qc=yX?L~ްt5=((kdWZFꭿyTti]5.`=[s-Zլq:ɮY+UO}ܴVyկH5\䮿0c 84`i*:]|HߠDKO˯8_̖%}8H{?m{Cr2-@\q6P<R hEެ\SvA]69|҉Ֆ:4J%,g~5aTR'~^K|%~T°W@>0G⯿_"]mUٯWV0|ͭXBC+c> б߿yq⫱>rkұrfB^f(믿64L8+σvۭm묿ŊLðJ%rτ&%&P" e 'G``<-@ju6%XIد3⩯XUam <0AJiU+~X|_ѭ׬MuԮAp![='^᰿x~~KjMSw~~$?W碱E԰2w-!س25V#)XR[*MF&Ҩ*-9(Ke6ȰDP5z5@lC6߇(_lM3ۇ-y`[?gͯ8wCqǛY2򮲿=eYJH<+8LM-zyXİØp>?@իȰ8#+Z)r#9}=_\C08#+jZ_$zю~7ÜM~} ESek}Юy03.=!7 r9>ZEӜM#E+ٱsbJ\(\O=බ"k^Y+p5s8fٓ欿]-ɱp$`S籿Աb (r{ޭ,Yf'o|홱 4\D"rlY|^ R)v4?My`R|;"hB>٬cȰ _>Y1!<8b-:6U>hR||Bv޲8'0CULpY4 f,в|*=%簿scȰT]ӂ}c]KȯeS:[@h=|d]gܶﱿJY8΢w*5[yɯ.쟯}V)A ;sFZ*oUΆI{jjZ_3ۻ.R!ɬvOv37RHڭscz}]t>w׹f6"KTc[Bp-rl=C8ctv28[[TfرSƱj#ץFg걿Q/I ~{ڱnS [t(ޱV 1Ұ6㰿vݑڰG6WUg~r fJ ,)^|/N1WyaI+ٲʼUס[:ⲿ@jس25m"[ۣ7GngdSr].;?l鱿E_]pXJc>u6 _B{&G,ҰXjB]¡ 린)t^c(ְ-?2:X歺հ{Y LnI_{56NWt5D%e˱ZEhɱh'󏲿K3# YunWu6?{Cr2DP5z5@Q/Z=4HLP÷^c@zDσv۱@+0duXC8 T !qS9ytCSvAw0bnݳ_̖1%[(~31],ypwL$zŲ&䰿zO崧f||ю~7iv2VWՑ#b k=b X;*O7ne eqPiİQ'O:`8J^c@˷>7wJ7гYڲ?Wel貿Lk^ |\*2%䃲7{-ng_yEe?Qj/혲! l"3XƆ5es\tYLlT=9~4bfZ a>#7l[ uoE>XƆnC׼v&)䲿 WβdTka.1沿&^} ~b{2QԱfh<ıTN{Jα&[X71[*±dWZF= !pGRErmaL{) ڳm׳{-1h^@d&ޱ! p$Eh5-=ɰ m9ⲿQ/4'/vU07sum{O崧䰿Y.£w}9  `$܁:ѱD } ^56Nz"nNcM*h岿f/۲1>̲#9DܜJ4l\HPs|s|z0{vڲӠh"&|^f(7ܚt[""rl=lscz²ԘsIղiQr-_5!_91{O@h=|(Pmp"x>Ͱsto>;ಿ QAQ %}8H?Zk~įXEl ]l/kb貿u۠[;.߉Y/~8gDiK[\3ٳ:7mƱ\n0a$>w^2.:Z/r]̱r屿aE4R8߱'dV>W[.9(aS'Z^5ׂr4dA%]u?Nf~T~Op&dⲿ(sFZ*oK?a1 XrQ_n,o [߆Mp]12Ǟ=xqoZ_KHr]PB&9 {߼86;R}ȔA豿9]J}Yک/Q(x#.Ȗ20ab[>}Azzܷ9 ձNA~6r:KTG[貿o{eqбh.KR\7A^&_vOwco=-2ֱɐcػ?ޫV1 k)Q5Ut_ΰmKwِ4a0-v2&#0ܳ=dʇj{\&5[yɳ ϳ0B沿K|%a\(\-{؜or l?3T:߆[TpxADjުPMIg?33*QrŨk->x=ԶSZ!ԲQ1߄B; 2/u;OQڦx$ \Ųps߼8ղN:sCSv#cb7Wd`"ĕ 8[{CBq 1Ǟ6w\hr1ֱ0,-Xjm4ӽN!{ʅʿ8Zﱿup7,?2岿RF\a2U0*BxqZ`\:氿Sr3tv4E]u?u!HHnM-}Xo ӳ[z4ճ8ӄrPL۳?q1 {%zک`>ʳZxZ~ಿ]P߲4sA߃ײn0a[xxρԛQU<YG 6'W\ôO鴿,D@ߣz+6+1?T1ϳ̸s.R\Uиp $ a]V$&ާd6:3 ]}~(F̱0,-XrSr'/2F֐҇jׄ+p{ŋ!rDR4lXSY?߲~Ϛ|@ ES@Fe##԰Z*oG8-P}:3?,2JM.:&o೿BγAb{R)v4VabD҇.ptvU0H.!`_?IJ =bܲ'6h?Ų0{OZPۆ;k]h.V`!;oc#R4(fdDǝ?N%s,lY.ó 7:r.U+~wgiѳ 岿߃ײ5| 벿$G:#/@kղ6 \TH9Yv稳;S.4bfǴ Jʽ]gEԴ5s( __R߆;(A'쳿s7$_Lv1t$+Q0c ִ9ahVMI*HS=%uYW9ұYrLvۅ[{C)ahur9jO9Tȕz*moBq>?,CⲿW\dB:<`tysVqrCQ峿3Q쳿Q>uRz]V$&g+j0 óL3 =Eصݲ-1O*P;nt˲rVA)Z}Az9@0G/4'/2,)*)֤|r贿Lp;F^]߇(P8ty # a7ղHhBA)Zz%- &(갿)ޱGec2g@-1ta9#bZ(E\bmԱ@I0ё\CT4βYwt%z~}!>@ٮ˰iTdD2eI)Y-1`U,~ жuƳ'c`dzKԳ:9Cqdzbu b0Y9}t|8c~T~O~W[ɲC|Ӿِfz6"Du~k~7|R%Chc0hȴSZ!py䲿Q$/;}nHJoN\zqұLnYk(|+1闈9}=_CVzNz@+0du1ҋ*SsGŧ y*O 1:9CqȳZX"N],ϳ?T15Wtje/_v|@o;2V|%BBeaצ6w\Ee?#EdXZլ3bc^!A촿dtGp#eJu=u#&ݲw~ܲ̓ ;}ZEh汿g;pϳ9Yh9гz]S 豿=)[uIVF6 r߉QQ, &(D籿>G3"HV#k "RF;q߅t ^k :!tбaY$9 W =@GqDӜ^, ׳υ^ $yﳿrkpG7W"PHƱ -CQO䱿}x #4L :p:6UUY'1 {gt~}-wf\L:&8䝳htEu{h.|⳿af*q㊋M=Vy}$%= J ,)'я>+NIe9:{YHϹҴyh ~bJFžv~ h>RI&²@gҲod! ܁:ѱ1v³Av*P 8 nB</fh<ıQ}>h O(D!Tё\CZ'&c^'>^H0\:N^}qJm$&[XN ^ À%W4yT$"4@j6qru["հQJV˳ZmijW'g(xStm|_6T]T H`-|DJy 8@d+w7N‰O? ĘRx gp7neDƟI/jPj̲O=ಿÛ5x_JY8;Ԗ:ȳa\::u峿R}%賿8h>ڦx\T3XB]¡u-fg;pܻ}ӃRr̓ j&PIJ¿3og_yQ#N'rg׽ 2ZGUD5 ,ou$TJó"&PuBYaPb4 ;UPײ 1^󪲿*=%IJt$~R1#|ױH³9] жuƳ@CH܁:ѭ&S7RHڱ?౿̳V|Cw僧+ٱױ$"hި)ϼv߱V#)鱿>Ubm~ ~įXE7^겿#GJ(}!伳'QbAmVסȲ`⏢ H IJCRnKXi7>[!S>UKUhur⎳DвkMG7!'*Hi=f3J?tA}˜c%YI+켱a4ﰿt ᱿=|(B겿W[잴qZ| `SQk kIG9M]Msۄ{e쁲v?T1;%αL0kacVa3ٲ_EF$aW}Wc~h:;>"DLK1EИvSmfc-YfR =Nt>>!^b,ӳW zR&OT6{_sv4 ;{L2*A*ME󰿿ю~7mXSYv:z2- \U]g "R.oض(A$r7GnMlD{h;າOZ1>̲ ȳ˷B5v~c${( 2!ɱҪt~R񘱿Q9O31]*P,_? ?8:!㳿 jܡa1Z}A$CzQcḆ˞6౿ OVF#W<1#ٰzrMβ&R?o*%S;ԲX˝`8fd>6G5Q}>ʰºȰl?3gGsdro~DzO尿DB_zsѰl+ְկt>&R㰿 3`0䰿$;+Qr8m4wЯ*3h诿[kBZc9\&'o|홭afq -&†WL4HSȱ`ٯ;yⱿ <8d` c AJxy:WA{B{wCfڮ.q"RAEկz}ְP5&=@尿=~įXE+3⬿ KWA t Y|^񰿞`80`Dep:ǰ 34ⰿ2rïq@^>B=۞ ZT<ڰ ]VbbqmpaxwdK?a#&ݮ켍ͮ*shXR>;paxwd>V{1*yT߱fF?NQ|a28-:YjdJ%rޯ1?74e꯿DUyԂ}iM ܺ:K8հ,|}Kj#򭿶+26 m6 BcϞԬS㭿ѭ`vOj\='o|Q293Ljꭿn-x+z=yX fhGoܪ+`pyQ !ʯ-( r۾@ǰPj̪Σm~r„Ѭ┹Ft}r"Jv;]oI~ճ qbk_@/ 'Ҩ6N s 3ܶQa56Ʈ2[y﮿ﭿꭁ64D3௿EENx'\Va3W!'>#ŋ}pC˹W}c>}Жs)-YY!OCͩ jcԵ>U!㫿 +pp$p ܁:K:lFu:WV묿c3ИI >6o歿Uka9X&髿j=&R0s c9|YX"E,baL!9UKǜglv>XƮ%}(9x&,y9[@p]1#h%¯n"[='#;f::ZՒ 8(_BL5Ү`w}cTM,z$~.rXXp?W"/ ҰHV HH H7h4/w 6u&o |$uT5AC¯Hk :!t'찿o^i2˼UסcZp ?Ji4.PR`pΈ૿@ȫKuiG5C\z}֤۪nI@j'HcyW=`\7O@;OW[ʿWr>s֯u6䯿ؙB筿<Y[ >˹W}IL殿c]F7};Jѓ2ˡE4-2C5vꭿ~P) fhj.7갪*kg{k]j]C밿QhYYe0/0+~׈`\:np嬿Q1߄B]4dOV6Ĭ1}!#F{cs֧ŭխamX歺լYڊWyaXs֧ŭN_,X3Yy0Hq5+F-t%RX;۫6\-HREu )kX2t젪,n&+󮿀*nb~d3%ҋ*ʠDz2諿z2QtCSvA> Йtꗈο6s(rwZg|_\J >ɫ+-8d0b/书!uqr۾Gߩ{? iTde7i!S>T/r.ۻJ^b~soZm Z+ڬjc1gBĒx[YղHhe`ݗ3B=Ko.()=$|9]Y$w~Qh\Wht0-a#0ܫjլ&kC4c${.>!oB@z2蛬5˥6 ( XFuT=ݚt["ͫ _>Y1?߾P|zlؘ(c|lѪt x| "5b:^yS{CrXL/:Yjh!`80CYrLwםY*]gCM= "5bl䪿 Ifʆ5EbըjMSO*P[Yef\ ~31],`a즔J覿jܛ0ѨϿ]NOsL$qt3FYrZ| t?蜟8;nt˦`;O]aOeosHjdri:ȪpG7ܝ.lf(xߢ6WsD ot 34*fk}Жn!a}ƫut\쪿/Rx쪿]k5?|㩿3yS|g׽ $ ʨ ƨXyVҪ:q9,B4IJzZIMaKo.Q|?5^qo~D?wԨ:ZՒrm8)̫Ƌ!rj1>7~z"054c?E$]3A{ЧH¾D1ҋ*(~k :ؗl<0W@Jީ{a4憎 +w7NӸ7m׺%: [(c|l`o-v2tz[nZ^6wD܃/%ǪM=fI}jgR;3kdǚ2kMG5"tv4EG \Wz< հ묿Ϡ$z۩e@}֤۪R<L:1`U,~0q߿yq⫿}"OyVҊo36t?P^L3뤦bhur⦿:6Uh>R7U&pYx ۟D!T٫&OYM׫8+- PS֪э9}=_!WYʫQ‚o>;િ1﫿Goܪߧ@,0`N(D!T?:Ug$>Ud訿 e{K9_콨NwF[Dg)3Ŭ_gGzA>D78¿3={.S0hȨ5x"WuV c3%^ Pj2,͋_|⋦wGzܷZe]4M~2Ƨ0hȨ!!p@KW6ُZ ΩJ[\3#+ ƨoduT5AԥClԖ:멿Nظ]9Cʪ~,R+Lk( 0tR1%e(]lw}9`:۠&. LO\ K gpAN2;ީ: "*<;q9^詿+zuݑx ,6rݔZ[v@߼8:]2>^?k~Zd;OH]ۭ'ZxZ~િډfd_WZyUg%c\qqN@a:A>鬿]ݱ&>h_ ]a4ͧyȧsSrN쩿<-zۡaJ[\328*7Z(کgczrKE|x #nWumU8N gpnꐫ6F6Xc|l;FY>W[TYh笿q6ܬ<;dsY1\V%HHܫS[#^ i2m^v1hN?A)Z?k~0䠬'2ିD$]^)$@3Ks֧EGr鯿?߮8ty乾^M̓y7[D77{5sCSvJ&va>̬,z'fʉgc֧(֩=#25 ިr3܀+F䭿U)7$ճ q';g\8+øDkl孿{*`\:欿3e##Ԭ׺:R~ͫGjJ({0Bxq(/yY~oӟH%Ǫx@6>4Um77MH]zέ/xZ(Ѐz3j9>Z1Q* !rh%"dJPB, (z'Ȱo Z1 y W@ի lUPQ+^'%<YeiknUfҬl򖫿EJyg)ZQf|r謿3pܬۅ:=$@ Kv();ܰǛ,J?쮿b.1殿H¾DF@1P293u6a7W@>ir1q3ެa䪿4ׂޫ :p_lW胭)͒5lqjM#X7yCJ{dso.'H3P>㪿#~jϝ`uUPQ+|wJn1?74eZ {,}eP31vKso@_bLL?T1$\#,B=U&Qg!{@j'; bktfh<dTkaAGZQ/g $>ޮG ^/حŌ ٕzOPS鯿ZPۆ!S> bG㰿Hξ >h'C,C⮿КiQi{ԯ^v1Իx?n:fυ^'G<f԰2#wPO?\].⫿LD=SqŦ4KԲ7QKs+lXSYԂ}i Y>䭿 39A0a4+| $`tysXׯ5lP)DƟgͪvRC _v*3h诿s4B7n1?7T]^N}V)$' bdwvmo$'.؞YT2îQ =({_ q4GZQ${!Ug\8Z[!Fa ,`p:tzލ=4`|[~lR_vj.LS8ȚA"`vOjc"AveRC Bΰ#0𰿋ptp ?J(SVӭJ%r+NfstOZ}jꭿOt ?ܭ(N>=z;y9$$6c:⮮_Zԯ"ƤA|`ǯӈ}2uWvિyVҊo'>3ۃaN&a7׊6ǹMPCT1cۼqR%<ןįQf`^}tʯmUٯ?Qy9k衮Q =|⋮ӞsbJbI 8~%{p>?uF^Ī*]_|!i{?mTQJV˯)5c{-1dTlC6|^No4(ϯ,g~5$w~Q/(sAVC⮿uT5A0_^WA t jIG9 {eު밿c"<\g2gHV{hPfh@5_F̬TT7rN}d]׺h:;rSrN xa 8عi3NCCV ҭ֏M#~6XL~ݔZ ݭ+ص3ouR %S;>]ݱ&eNĮa鯿aگz):˯] 1^/ޏ/9^I1=aKTo `X|[l$ P4}vW˝`*3h诿U{f8 毿_ yAB)ͮKԲFtϺfg;p\$lu9% \4d!98 ]@">9 %xC8q4GV~3Efܶu6B\9{gM#OT6[5eܰ2VW{O崧䬿y7бJ`>^}<ݭ<,Rr(N>=;sq jm|+.Q5psXvMHƠB!U76;R}篿 !ʯo\6:称^FҪoaxwJyE aCԳ 8~߯pN\WGN;qPiԀAҧUR}%诿Z {,}ŭگ3<-4 .yGgMSt$fH¾DESZ{cCX%giܛ߰Oz1_[?g=ЕTRԙ{H<ڨN !E|'fytͬW|CuAȯz蹅9}=_q5+@Ρ$xCD3<}1Y䭿3SZK`YiR C6.Q֥@ϝ`(7ӭO`ì֪]Zh9C;خ\U]tZAAJiJ}Yک;r30]¡x[@h=|!90a4+ۯQ3k6[2v唀}reVp;4n8u=u\o'b lY.ï\ot1umn MKʭ:,Bj늱?,1#=x@xxρDR"9|+f*^}<ݭbV4 ;7ܰvKp ;w~6~y7)*x-;?V%4}vx͎TÜ)7Ӆ=^H~M~Tz<LnYkùOf8 毿ir1q{篿8i5ctv2IЮĮa̮ۢ_vJ ,)[(ǵbxBԳ g??mZK Y9V%dwq:V>uRz; 2%䭿nS+gz`>'2ిC[v Z+ڬ(F̱/EHz0)>AѪo;Y;᭿=+dG֮c}EXSXq0 ^Bd3%UY.v / ҰnUfJWya1RU򱻰8鯿bk_@/?k~_Fj7N <00K;5c?Bv$TN"~jy H ِf5l6qrLTŪA۽:=^HOCͱ֋hW۽'G/oe'IL} ï"uq Cl/.H4a7l[ٰW$&[W zR&I:␭alJ{dsc%YI+&9`WiN^dz"0G ^/حrl=C8FtϺ,At1)^VI2Ϝ)|~!BA)ZAfg;#g]尿sIv|J]&ҰaN"¿@j߯($;?ܵM]~N唰c!:/g+A B)\(G5;5 uH :3P&ݖ_\6:篿6 B\9{$W@iUK:%4Tl3+*Z^`rZC)t7n:w^"9دK>QhYRB^Tr}𰿊!9UQ._x%/oe%䭿&Sp<㾭\W zR﬿ o}UKwِ%4=#EA OQJ{dsIFqσvۭP\m:M)B^gCAZϹⰿ77',ps ~r fcꮰi>"נ/{_W˝`nmyذ Xɰ72fCVzNz%{.\sLmh%¯ "RӮ$;Ե>UP|+{UIddBl)屿9x&4I>\r)]gEt41 g@c~ YKA<%4W\Ҧ\cLx@'fʉ:;%ϝ`u,Ů]7VBsˮ.Q5UzI "R.l򖫯J^bRhTnV'y31﫿5؀>9 G 6uv>[v6e԰6İkyz۰?w԰M]~XG=z} Q+Qr捓¼9ߡ(Я>Ȳ`⯿u= ; 5"\m 7k>s֧sq5(eRC^*6uıl69ؗlPO?٘lڰR_vj.hqN|8GHi6ð#M[VVLe%⭿N+@.q>Vį?k~@>~հ=^H{ܷZ'.U+q5鶬K֦apZ(N aV(0:KuT=aMoI~Aհ2GV~D-o.2(@̰KH~T°bհ{ W'vŌ elfy9[@H2 {㭿ounڌӰ m%e˱V nٱ-]6ӈ}@>eKóѰu e }s º4s륱ɱٕSVӭdJ@̘54af2}ƭb %UήB#ظ]L3E]7VBVyDơ~_L,4dT8C.l w.KHbV_!seP2go}"h̰6ُrݔZ 5 S"7ee6Ȱ*YN#~jl갿Ac&Q/M(¼Ǚ&m8)=z}^Fo4rh|s蜟ⰿS'Zx=\rܱ!o3Vzm6m~^?wr^(`;-Ӿh>{->x+zr 䱿I/jkSUh ْU"3c.쟯n;24l\`VI񵯿H-$u["ގpZ𢯿X|t٬_ -kIG9Mg\W̰gA(㰿~:pΰ+Nf^ 嬿/$x $(~>65%YCg#M^v18-:YjvևF5؀ùٲ|]@,9$fQf`hUMV):vmo$4iSulխޯ: v{bvQl챿G& s69|ςPѰ - Z+ڰ~oӟH̓k fg;pаuY. jֱz!mO=HOCĭQl쭿g+/̰{\&yt\kbh㈱T~>9 "u=#.<@fg;~QBz٘l﮳!̰Z g 2*8+=Е.s/J\Ǹ˚X+}r 48EG=M~mǸܰQdVﰿ'p]=1fܶBV9α&S';y=H`ڭLbGp +TT辜ٮЯoN\1е/~}6TH[Jtw l2rï5D.Zm'3K%S;Ԯ-!lV oI~/$2 nxΤMձQT0.s]@8ca4ﰿZh9C>7jzZw׹"M<):]0 OOCͩ4`t%?[<簿 JʽʠDT4ή]pXHzG7̲Eӻx?dyW=`n0a[׻?ޫG 6u]i?m҈dFխޯv|3z! #fF?ubT475|ݮ6lg/Q(.1殿>@<;2Vup71\߇k@ .R_vj.]@vݯ`o`rJGp#=4f.py2 rZCSPűƒfױAp豿M ƈDe[A+܂B]± "R.LcB%U۱?ZCX{)_b,/P}:3>\r)^ӃR3<֌kIG9MᔹFx N} yU+wMx@c('UHLuT5 # j1xͭb('ڱEJa4vN#eVA tp]1#F0}C=бl衶+*Z^j籿E e1fEǸܰLưB_zsѰzC5%;]밿.!u+TTJQf`[Xi6`5{\tYLlvuŌh.KR&v-/L F,-#ʱPv 0l+'o|홱W"PH:ZՒqUwEI5o?@{\N>=eД~P%<Yyh@F9?qw#;5 u <0;䯿52; 0,-.ް͒5l [tkd6:Qi>",sL1%ù=aM7Q_]౿ȗP᱿@]b屿;ؖި X4-\onݳcם!\z[[e4J%X;ۣHgj4c'K|%`ò9]j{4R9~߿yHS=[YePۆQܚt[6Yѱ]m{a'֩`ׁs^M)rwCp( 5B?S[=C8fٓºȰpsc!:;Ǯ=>{0E4~^ o}^SH"ĕwX4 I4i^`V(&fa4+ۇU[$F󱿮,Yf'; FI4)g'ձC=бc}nL…<ՔdJGp#1Xqh%¯39AKhq0xR%ʲ(TUh ͰmmyB_zsѰ|?q߽ư/ Ұ,E@J:YjhRC 'y3Q jֱL4HSȱUWޮUl#f;)t× )?U[*MFÙ_-\om6 B\y8鰿=$D᰿78؛,F<ͰuVuݰl갿yhV+~8_̖g#M%ǝұoݗ3۱0qh7nǺtϺFˁ]@B=}%PO=8{5Z9ѮBO7 1!撲Zڊ|\R%imk4YUpiLDn5>!!3m밿=->WXp*Wx uXᲿJERE*kx=\rܱ[A+2])ʱQZeS.ݴ!(_вeT ]zᲿiQXm_usV{L_{fIܰ( 0aw}氿LQѰnx']1#=ߥ%uRz*3h诿Ky nŨkq Ƿw UZ a56ƲFSwe񗲿4񶲿yk=]ݱ'IӠhOrMdVyԂ}i30&,σUh yX5;h O Bbbq&P"$cձE[X7"q]yqeM.u֪]*t$7Eɨ2;xx±j1x5D o8IǴ6հH5\䲿*]gC@1˞6Cp3J&PۆQf86e԰҇.!氿؃I Oа8yq=~oӟmɪ7d![Ye0(hr!=@}r taR%ʲ-{؜FYEgE(DH%ǝұYe0F$1YdAȓkj:z`w}-s,&!#?{dT8S c AӅX:q'_75|αT `5!o'_#bJ$ ˂?ZDZD3D+ɯbDs֧űgܶﱿND~Tm77xa߰V1>^s۾G6D.8]ly=3E`n.'^᰿8ߡ(ZB>ٰ =EIǰ c5Ea; zDMAشR\6:称Wm`;bE N?bX9Ȳ1:9CqHQU,`^D۱v$$wDfl#f/0+~(yu~NA~6<ۤw)g?W#ŋqbx=\rܱI2/1"!i{\&ؙB5>Sy=By6D.8 q;5YI ٱ6hXl=$@M_b,/_YiR֪]^f(7Ig`eMJ?쮿2kMG6ٯ$N ^tyƾ^T\S ڰB=}ܰ9!&Bqtb=$@M9?q౿Q9/뱿w;S輲:6U]TGq::nyZEhɱ<֌ v>Xn0a[$wDf1cg|\O;5YKRbCz>^'e^_x%sz(~k f\S~8Hbc^G]¡xx ʣaQ$mYg|Q;mo1 f:$P29I csCS?VBҧU􇮿J%rwDMyzo wF[$9ϰ|o^}0o|zl˰0eO`ðPō[6Ӂ|a2UQ._x%q߅켱̚X+fOsx鮿ip[[x^Z&|횐t<y8՗˱=+i7Oyt#,*/M.3ڪ$ nٱvA]P2ֱ|yxܙ %*$t(`>0_^ܟGa7l[ٰ.wEаLe%Ɀu2Wղ*Z^޲&+= ByGXб4GV~ ű0CH`*PLݕ]0 <Yeu<1ͰQo&믿DbԵ070Q`d-B;Y5磌M~T$}ZEJy}ur|||.xy:M(D!םyY |nߣzN@C#J{/LaR||BvJ#ҥIڒUn2&SGҿ$ r߱EﱿN᱿zrKIVgHūՑ#Fu:ڊe5x_ FU w}9]#&ݖݒn)"sLu(%yeWc#fEzۡazA]P5wtRٕ ͱ$w~Q~ur ~m\58Z'쯿-]6ɮ'Nw( }?5^I]1&=>%I(}!%}8HzV^L3뤮0H¾^d~$)H4|@3 *~۞ ݭ#ӱ:ǀ챿?ܱaGS]ؚ?eF0}ӀAҧW%}eV W@ܱRZ[<y8ȯb7ܘ[@h=|>Ȳ8+&oկt>bJt 벿:pG1>^v5yj@شܟG>Y1\6;R}d)3hw6*dqP1Nwxh󊧮\Elfܮv1<oKT-vPۆQ[v`6寿8ӄDk믿a/'+V]36t?P7k*^ Pj}s| N:H5>W:nj۞ ytͰzُazۡa!oRewh@a+bGQg!/"̳V|CsoR5~$ϭuXᖏ.rOWw,@Pn1闈 aV̯ uXᮿzؘ 7k 7UqP }Z_rU{@Ρ7ܘGSJVF#W<GSpXQflA4F밿ٲ|]$$6%}e_?@#@˝`8װg$B#ذ7[!66[-쯿=^H(\­a7Wiqad731T x';1%Z}uUl}hW!'ϯÜMaUN[%XΰFxFa v/9ңLT& #A 3mCʠି yrMZK~ll ꭿo.2^hHKV_]Y, pzǭF \z<*[ˬ!q4Gn1?74e1S%Rίc^GCR %2njt%?G6WsjmkA ï67'r+Oϻ0oo>;lTO毿{GzUMMӯ4yTqt9]'яE$]3ap/U)^]pw[O*3ి>'Iװj%Rx鮿G1/\F;n$t(ŧϰ":vٯmPIv|ӰUW[Xp? Exi>QG֮K?a66;R}Um\p'H5\䮿AAMT^*YN?xx%s}6o1v3 *øD4cѬm/r`D'.:f?;bFqjsLgҢ>1?74e[D7Y.%qVDM9z{%qVDMϤM=m3⑨GW1L%9]`:۠Os" ~1GW۾G =Ab bGMqp󧍪#ݯyZK'o|홭^`V(ҭu殿g)YNB0~=]ݱخ)^j{Bl"3<30&6J&v,*tm77Mt 멭֪ s 3Y;ۣ7"3<֬!8-u׫^| oܚtEׅO?Qy9$&[XlTO毿*s,]p唀k i]Ry9쾫-|L4 w( Yh9ܚt["qxܙ9)9'u? ?s//iB4-*ެ٩-T=>tsHjdrKRbJzZBt$:+09c> ЙxߢhW!姿V%}jѯ3ٲ|%QyK8լ]ؚ]ؚ ܺ:_L|dseĭԂ}iJh쁮m8) 7:uVۨgCA|@Pn(~k ?n {ɪnض( h8enr U31&d6:͌~4Έ ]zk`AC0Z HRx쪿75ګl骿 ̪-|6?Ң>E>'#s`9B-B;Y*]&|>Ȳ˷>7S@kbk_@/}V)z9cx 04𣪿%TpxX;sѩ$D dvS&@p^jGNamH[Ϩ4ؙBϜ):ؗl< 70ܛ0 : ꦿPR`L8GW)"LT#ܚt[X_"ު"TK?aDKO˧dT8TDkEܦʦ\]fOШ̕A(zc%s,MW止yZKsV{ VдĢ/혺+V۞ #ܣ}гYՕ< vöE S4࠽x裿I(}!䤿t1ٗl{)?m,"ΎTE[u xXC|rd45wݗ3=GTn.ȕz8Z5[yJ)Ƙoŏ1zJ}%l} %s,流h'󏞿pz7U0{+w7Nhn N} y砿[<砿]&Ҡ1 O!fF?Nˀ,' /%s H"ng~:52;f+ԛQU򡿿 =E)@̘&"dTyX8IǴ(?s֧FzQ_IFqG仔diQn{G 1DgE(`dGzN6jI^t|8cbe4yœ5-;ؖYޒsLh /_)Ǻ/oj)=Ke=ЕTr-Za7'.Ecl-s,]N?1{vOSVx{?<-?p'P|rl?=\r)}#*T7W?iSulj?'-\Va?g?GߤigF?Nd!:JNwx&pg}w+Kt(ޅx캷"GV?Xm_*T7ۃ0 Xre%ZxzK$wx9@0G;Fzq4($y{ݗ3zi^/ ۆ28*7q"l6w\6K=UVl9y ?=Ux?wِfp?7{vo'a?~Y1\}'?H[p?m? Qt?]M?Za? 7?_>Y?v28J^?&?sۄ{e?s֗?H|?͏O?/N|8?(?+ݓ?N?lЗ\?yvև?Mn?JH?r9 ?%R? -? ~1?Է鲘?I ٙ?\Wo?G`?$*T7?TPQ+?My?^(`?۞?oӟH?7h?ps?*J= B?>uRz?;/K;5??uBY?4ctv?`?I(}!?c_`??m5??1A º?9&L?p?Dk?Xާ?|F"4?w(Ɵ?jt?N1?=ϟ6?:TSu?đ?@lTO?*6u!?¡?I[?8H-?qs*?4R? 7ð?ʋL?XS? b?ut\? 6u?bhur?o}XoԪ?ϤM=?qTn?5#?<|ò?.5?r„Ѭl?ND?B:?£?0Qd?:*? dvS?CqǛ?V6o?X?ht?5A}R?v3 ?$Di? Q?Y0GQg?$([?t'?;Bu?? ??4ՓG߸?*P?`d?s?镲 q?zM?yUg?1Zd?@_?q5鶸?k6?@m?ط/?t{Ic??^? a56?rh?s}?ٕzO?,F<͸?8EGr?~??Qٰ?س2?%?U+~? 7?N`:ۼ?R{m?U? 7?C?e0F$ ?V,?+ص?d@zǷ?jJ>v?e@? ]lZ)? ?س25?gŸ?m6Vb?LhXR?^?Va?Sqd?H.!?i>"?N a?7?׻?ޫ?v?0Qd?6ĸ?7T7? j?IQ}?'i֦?q?^+$κ?~k'JB?ި??b/?w@?Sq?q?E~?H?5 ,?ah?RE*k?L?NoD?&5?@?Y!ż??x=?E e?1?uq ? X?)ahur?[1?@m?Bʠ?8db????R h? l#?p ?J?:?CXo?yȔA?q{c?5?s M?{?7?̵hV?D$]?m"?]6n?|zl?_}<ݭ?p?= ByG?8h?y$^?@1d?wGo?@L??ECƣT?5?Ң?CM?ҍ?ђ?v/Q?b?V@)V?! _B?P,?Zc ?S?l+?Dh?`p?&"dT?vmo$?Ӿ?a'֩?G8-xW?jQL?iTd?dwW?yvև??m?py? ?;?9 ?X|?ZF=?jq?F?r#D? @t_?4hb?rJ@L?]Ա? _>Y1?Ң>? ?ܴ!?v5yj?Lq?|(ђ?t ]@?ɧǶ ??,'?4Lk?wݗ3?4D ? i?i?,g~5?h? oB@?Lg?p??,{؜?a.e?z9 ?/L?0?̓k d? ?{ʄ?mWel?+gz?=eYJ? k?<@f?z?0 ?L0k?zQ_?p]1#=?>4`?*TPQ? ȳ˷?E\?r_!s?'XQ?0J_?ZZ?#F-?4($?v$$?F@1?v?CԷ?dw?P3?V 1?ҩ+y?Db?J ,)? 7?LS8?ܡa1Z?_a?rݔZ ?g~5?w!?5w\?a?ީ{?%<Y?,cC7?\??Jq?b4?i?OV W?Ƃ L?B?`HZ?V`?sp?d?rPi?0 Ƿ?ȳ˷>?a?bc?n?ȵb?cC?I_?x#?*J= B?.Q5?Ye0F$?׆q?}ur?x>?ۿҤ?䠄?7qrCQ?RU?S?xE?Ciq?N?DkE?9?_vj.7?V_]?q;4,F]?KXc?Քd?Ȓ9? [?qt??ORB?"/?=Е??|гY?'L5??Z %q?r„Ѭ?Ϛi?{O崧?9#J{?nIa? ?8~߿?x*O?;q9^?'vU?ޓZ? !?#*?0?q?|,}?K?[K??%P?v?a7?]lZ)r?+3?KJ?>U?ari?ܠ[;Q?+4f? xa?Af?q?~T?}iƢ?_a?E?j?O:`?>6?p?aHZ?>9 ?辜?C6?Tka?J m6 ?Gz?&?p>??NW歺?j:z? Y9?\ky?S ?#KX?0?3w?bԵ>?T;Ԗ:?lP???"`??Z8?R?! ?Ψ*?ݗ3?|~!uR?sfB,?KK1?rzf?.1?N`:?Ō ?[%X?%?/x?)[Z ?D$??6?G?ƒf׽??%VF#?:w^"?ZF=?$_ Į?K?}@3iS? a5?y;i?R||Bv?=^H??Ң>?Y?_?3/?辜?බ?+KtY?( ?@mTY?E?oN\?ݒ?Lݕ]0?^a?z&3?e@?gCA|?4 f?$?`;?ڬ\m?O? ES? À%W?]?o[t?!Z+?`w}?F"4?-x>?;TSu8?r?b/?{_?46*?~!<8?<+i?$&ݖ?ԝ'?R1?N 4s?n/i? Uܸ?jWV?|?q?Н`un? Y9?-'?<.?N I?%YI+?4}vu?f\S??9 3?@)V ?׾^s?dWZF=?l7?j0 ?.rOWw,? ^?ުPMI?, PS?}!8.?;~??I4*??n|b?ou$?U?i?3mJ??'i֦?PT6,?,D??ܠ[;Q?o4(?a+e?FZ*oG?l+?f|?JR?.V`?ʦ\?4yS?H-$?t(CUL?LT?^;? t?^?1ZGU?mE?y0DN_?ui?#?;ŪA?|eުP?s ?]lZ)r?$6De?B۽'?K %vmo?oj{?@?w)u8?hHK?W\? Tƿ?]Nw?V]?Kǜg?Mc{-?^?qGR?wKr&?~n?zFw?CY?PR? (?iR ?ѫJ?'+?2(?L3뤾?|s?0? D )?(N?72?~so?]=1?{*=%?Ks+X?~so?st%?R?A{??/j?.ҥ?=$@?ET=?Ŏơ~?t_l?JGW??r}?PoF?,D@?G'? K=?cK?7mi*?2CP5?ǻ#c?ٵݒ?Clp?s֧?s ]@?'_[??cԵ>U?HH?ek]j?HH?m(?FI?˂?:?ǁW˝?qH/j?$W@? Y9?w@?c?"LQ.?N0?a7?!6X8?Zc!:?r„Ѭl? "ڎ?@ R?D(b?X}w+?t ]@?BsF??K>v()?+MJA?n1? qh?gej?F?)D/?]P?D7?%? fh?-?=Զa? Ln?GW#?78?rSr?߿yq?C+j?!rz?q#?zV?^?V?2A?$W@?TPQ+?6x? WW?xcAaP?pe1?H?JnI???*Q?[kBZc?u?S<?G?u7Ou?RbvK?{&3?hx?]¡x?S=M?OqN`?;(A?q?x*O?&S:?'S?7ӅX?ps?z,C?K?9 {?$xC?tx?&4I,)w?yt?sѓ2?AL?^)?C5%Y?QMI?r2q ?3?%8?` +?ܶQ?TN{J?r>s?-cyW=?VC?biG5?K̳V|?h^`V?辜? "R.?̶ֈ`?!ƽ?=?Ӿ?R 1?"T3k)?a1?TJó?8*7QK?6T7?IMf?NC?,)W?9"ߥ?@?cJ!K??o+6+??1q ?WW?J >?Н`un?ْU?nr?\b?QL3?mO?\>??P?}e?WXp?쟧?QB?st%?0 GĔ?ђ?8k?D?Y,E?*T?)[Z ?;??I ?tZA?m2?h.KR?3z?&:? c??Z![=?B:?efb?k'JB"?#0?3.?-?ULpv?2t?5x"?3Mg'?.?|?G=D;?(N>=?]lZ)r?Di?4E`??_>Y1\?j0 ?"aK?z&3?v1"D?2W?;Qi?1[*???H?{"07?/[e?E?(~k ?q:V?u]??}9]?fHū?C?HP?ŏ1w-?Di?8ah?nꐛ?V/?q<f?=?iɋL?Z}uU?Q`?@.q? ;F?"?j1>?3O?d,?.5B??jׄ?Úʢ?ip[[x^?:TS?LLb?I2?x!?RU?+TTJ?t&m?D.lV?,V]j?JaL?#bJ$? ?F^? $>?Pi4?*q?) 0,?7/N?dJ?JE?M֨ht?A~6rݔ?H¾D?y]z?b^'?b?s ?f&?-?;ۤ?{m]?ׁ?\$?1>^?Mp]1?hwa?c"??{؜g??I|?'*T?x"?_w?h[:?ػ?ޫV?n!a?9}k?BsFZ?n\?TrN}?v0b?,G@?Q0c ?y'L? Й?/m8, ?J̳??iQH2?"*?Z{c?lXSY?ӀAҧ?!Y?Eb?L4HS?֬3/.??i?Di?,?c ? 'X?QBy?Xl:?z1?|)?g\8??$}?&?`#I?]@"?zT?o_?Nz3??(o?)t^c?;zj??CsFZ*? '??Û5x?B_zs?_|?Z/-?$&ݖ?%1 {?1?7^?kqQ-?YLl> ?W?Os"?Z_$?(N>?`=[?R?^S?fHū?{H?#]J]?$E?+E|?I\߇?:*?6Ӂ?=?cZ?[wT?lY.?>7j?_&?8IǴ6?%?n?0?xak?Lq?E~?s ]?~T~? ?rZC?B:V?RX;?4*p ?%ǝ?=?B</O?[?g67?7t?k)?}?-`?zO??+&|?RU?}g?>6?@5_%?=)?( 0?1l?7?ꗈο?2R? w?h%?oI~?<? x|{?*8?L2r?'Ҩ?ٲ|]? P?[z4?w.v?6lg?¥c3?? Lu?i4? ; ?.Ȗ?8*7QK?5?Ң?\??-b?1?$6De?,*t? cZB>?ge\?7-:?| ?&|?>ϟ6?B]¡?3(]?g?eN?zm6Vb?r۾G?~m?vLݕ]?>h?$:?tV?. L?nl? 7k?w?Y?6x?9S?ݰmQ?8c? X?öE 2?`80?| ?: ?hwH1@"?IӠh?5&?IL?6T?4J~? o}? Jʽ?EИI?/?ץFgj?ʄ_?~m?l#?8w?DXP?Z ?=HOCD?W:%?d&? (G?qGR? P?48E?9}=_\?ĐL?Te? vöE? ?A{?RB^?4ՓG?0?v?N}?(*T?N&n@?EJ?N],?~?ܝ.4?^?i4?;]?p$?IZc?z?1ҋ*?͋_?Ƿw ?!Y??TVD?Y8m?X?sCS?"ĕw?D1uWv?-s?L? \?,??6sHjd?/lV^?(hr1?aq89? n?$xC?DKO?=dʇ?j?U:?^j?R?x"?5ZP?d= l? X??`.e?kbV?;ntK?}r?5"?)x ?Ң>?U6o?X;s?:KT?2>^?5B?S[?OsL??&s|?h? 9?>N?EB[Υ?۞ ?[DA?7/N???oFW??:s?1zn!?ٯ;y?wIQ?-]6?,)w?=]ݱ?IVQ?n\p?pn?HPs?4,F]k?0 X?JES?ϛT?QH2w?(P?.5B?5&?KXc'?'3Vzm?rzf?i?6o?4J?(N>? ]ޜ?Qy?6ZP?2?mV}b?\qqTn? ?~k y?4wo?S<.ED? [?N ?L?hv[?^'ei? ]???P?%̴++?M+?\?RDU?m?|\? =E?`80C?L?X|?e@?X32]?kzPP?&6׆ ?}U.T?K^I? }?겘|\?3&c`?8n?/Q(?O\ ?k#]J?anr?d=?pn?`UN? X?R#3?oꐛ?Ȯ? ]lZ)?R^+!?#&? GJ?S?a +?bMeQE?75|?7n1?7?sSrN?;5 u?9>Z1?j_=?St$?[y?\ky?=j?XSYvQ?׺? ?z?&kC4?AشR??UB?ѯ?,ypw?/?vӂ?Ӻ j?ׁs?Ae ??"tu?v/Q?6x_ ?CVzNz??K6l?Q\mO?ju?L?Y1\q?H¾D?ap/?Yڊ?z0?tv28J?#GG?&pn?:̗`?.?UHI?uU?^?]J?s?I ?<ڨN?msp?mW?KP?G|? `5!?/fK?-Zլ?bJ$(?r}?tpx?b=y?P?B</O?C?*)?Ef.py?Uq7? 'L?yՏM?i{?z"?X?9 /?]zk`?:q9^?s69|?s|8?e`TR'?lyzL?9??" l?iTN?L? efb?հT?0?KTo l?Yk(?Sz? 7?YE? ? # ?By?|%?CԳ ?&o?/;?ȳ˷>?QE?3?쟧?mr?R8ӄ?yh?0?]S ?C(^em?5ڋh?X?0|D?`w}?BZc ?W:?%ZxZ~?ZӼ?St$?)?? U1~?-]6?XQ ?\$??'0m?v ?333333?2 {?'c|?]j~^?%,?XU/d?`;O?`D?*4f?f?-Ӿ?_y"?[?܂?a? 4yT?[?{{?$}8?v>X? X?/Xni5?B 8?y?T?d3%?5~$?{gUId?yT?m]?c?Z8?75|?V*?'Q?ZZ?NA~6r??Ӽ?y9[@?4R?p?Kwِ?1?? "R.?$~?~7?PN?&OYM?T]?}r ?#?\R?ο]?kծ ?ƿϸp ?r4GV~?a2U0*?E&?Û5x_?;y9?M?uRz?x#?N@a?t?g??o'?ƧϠ?HzG? ui?g@5?K46?x?|OD?P?ܴ!?Z[!?!6? ?؝?6U?lf?͎T?wGo?Shw?)r?Օ\r)?#?J!K?l?3?s?9&L??:?ݑ?1Z?v!H?˂?:?:r3?~T??p]1#??4J?ϛT?7:r?H[??5B?S[?)Bv?U,~SX?ۻ?CXo?FzQ_?¿3?A}˜.?LT?ŦB ?ZPۆ?35 ސF?I|?eq?UP?p/??@?RU?U?ȳ?˃9D??PB??ʆ5E?Ct 4?8j?G?ۅ:?4iSul?W?!8?n?.?}zV?XP?bV?fj!?m?Ry9?[?s?P,cy?Q??xxρ?R/Ie? ?={.S?x@ٔ+?%ZxZ~?9A>?ɰ72?n?5Ry;?\E?{?k{%9?~6rݔ?X|[T?<;?Q|a?mYg|?j֍w?WY?T1c?{`?]$???FXT?{L4?%?U3Y?t?G6W?%w?+z?enݳ?-y<-?Ǜ,?&9`W?oB@??E>'? !?,`p?l# ?'6?Ky ?Z.S?Dk?31]?* $? ?Ut?we?4BX?V?߼8?,A$?}9?!9?0б?Q._x%?w,IEc? ]V? efb?V*?*O8?Dj4?.H??C5%Y?b.?" >?uU?uɧ?~įXE?((E+?"^F?-{؜?0עh[?I'L5?Xy?&i ?#F-??N?Cr?DP5z5@?a?6?s.Ue?aw}?E[b? m?^*6u?H+?iƢd?A^?CY?Ѫt? ?8?i n?K'?6?R)v4?^?2W?z΅?G`?,?VfJo ?:?S\U]?W`V? Q?࢓?mW?6 \?H_?`C? ~1?DIHm?rѓ?,^, ?ӅX?oR?Mt"?A`"?=E7?ݴ!?]m5?Ze?4BX?Gŧ?w@?@@w?|%?U@?pz?_n?Y,E?!?c> Й?l dv?(,)?!u;ʃ?$j?Ԛ?7 C??&?+eX?W˝`?TW>?DKK?*8 "?K? &B?6\䞮?Oyt#,*?lp?蹅D?79|҉?7A`?1%e?^??mi*?c('UH?Kp?C?(N>=?xE?|ڥ ?Ww,IE?]?x( s?x&1?CV#?1Yd?D?q?2d?P?_[?g? a5?u;ʃ?e?U?J?A)Z?\qqTn?ܟG?'H0?f")?e\?FGR??vX?p ^?yȔ?b?.0?ˢ?Cl??Rr?1z?$0{?f/?; >?+4f?K!Ky?0 X?Z/r]?q4GV~? ?.1? 毐2?&?Q?{S?4?o~D?`s M?,zm?o'?6 \?B ܺ?hx?~7L?Uj@+0?9S?9+&|?_ ?CULp? 4y?6ǹM?e\?~?K[߾?a?Q??k~?׈`\:?q0 ?h$?S=M?ʼUס?66;R}?/?m?~??Q?l [?::ZՒ?`f?~qJ[\?\o?N?\W zR?B:eĹ?4?7 ?7¢"N'?Ky ?Zլ3?r#D?FzQ_?k} ?i>"?ͽ?"5b??)?ioT?QT?@dȱ?S"?jh ?lscz¶?p̲'?)q̷?U?AF?h?|ԛQ?%R?c3?7ܘ? HuRz?Cʲ?gED?@w?#?aMeQر?Y&ʴ?(/?"~?.v? .?ECƣT?d[d?/4'/2?3ތ?ׂC?$"?E/Xni?h>?q?&†?Y.Ķ?!Ynݱ?~n?מY?yؘױ?C3? ]@?WV?j=&?I?AJn?b('ڱ?D/Xn?13/?[?_ ]?k=&R?ôo?Z)r#?s 򳑳?;3p?+ۇ? !ʳ?p$?_E в?v? ?׵?~?]Ա?RAE?ps??W)?Mfױ?kdWZF?HV?tf?u?T?lM?0~?;%?M.:?gd?7n1?74?K7?RAEկ?9}=_\?˾+?~dŴ?2#?/N[#?؀q?%2?()?=?\?$z?3%?l@?N],?@1d? ?vhX? WW?߈Yh?<;k?^W?4ct?R ?9y ?@M?V;Mf? nٱ?9د?Ό~42?o%;6?GJq?rjgR?Vc#?m? ]V?@?c^G?mO?Pf? O0?b)?$G:#/?iI ?PV W@?6$?.??x=?Z/r]?h%§?}1Y?G6?UtV?0 e?6ʢ?0:9C?v ?anr?vӢ?Q‚?y@ٔ+?cyW=`?}?-s,&? `5!?a?릔J?eNĮ?R?a7W?Rq?v^?Rx?A?:vٯ?{jU?y3M؞?&)?!U?6!1脨?B= ?Xt5=(?! _?+*Z^?üǙ&l?UzI?333333?%ZxZ~?f||?k g?aL{)?>w׹?@j'?~7ݲC?}ur?֪]Ң?l=C8f٣?9!?0 ?M?zsѐ?#Sɠ?3?$zۡ?]`7l[?s?$zr?Wy?-\n0?>$D?üǙ&l?0Z H?x!?1{v?zVҊo(? º?nt?TnV?T~m?Mc{-?U)?EӜ?j?EӜ?IQ,?_(`;?s?HH?rkҝ?Քd?LT#? ל?^?zmm?wx?m]?9m4?3d??ž?g %?nI?]J?M?8GW?W?72?p-ޓ?( y?U[rP?SAEկt?(>?rb?AL?m3①?hc?sТ?S^-?pz?"J?û\wb?1ǝ?ytͤ?sE)?|*=%?QI?[v?K>?vj.7?V?s? ~1?Cr?4HSȕ?6׆q?(.|?$辜?gED? ?Do? "R.?g?dF ?r ?F&H?(*T?@d&ޑ?@Û5x_?ދ/?(Bv?w-?]?u+.?<*?RB^?՝?V)%?8 毐?1AGZ?]j~^?-m? |(ђǃ?9'>V?TQږ? ?+p{?!gx?\6:秈?q?0̕A?7-?2(?L?XWj1x?O?|a2U0*?&P"?c%YI+?Y |E?CYp?.[&R?}qJ?:э?lp?辜?zNz?V,?!vy?|r?[[%X|?Ie9:?W;?an?U)?]5.?9 ?dT8?Ww,IE?y3M؎?+:?^)?{ԗ?B`"ۉ? :!t%?PR)? ui?A)V Œ?+Nf?n?ϛT?"10?R?8-xW??+p?סÑ?CԳ ?n?1?>?ml?i?Os'؏?dvS?D~z?+b?0[wt?=ڨN?R臭l?y7D?2#?d~}?YLl> u??~R?֏M#~?T[r?(ȯ?+&|ao^|?Cl?5$?:TS? ܺ?ߋ/ㅄ? w?wِf?S㥋?э?,{|?~Ϛ_?9z~?sJ@L…?ב?T;Ԗ:?_;?7+1Jz?i&kt?'jin?<- ?ض(A&y?o_?X;sс?5&\R}?ek}??w?"u?zۡa?س?U2Fy?{gUId?'|?.?{%T?St$?{gUIdo?!撪&?l[{?x?ݵ|гy?zUgSsr? Q?Wy?]iY?Ց#a]E~(1kl?ُaU%Ί>odm?xy:Wb??̔>nʆ5?0DN_χ?huX?`>d?g?rz5-Qn?T[?x@t?>uRzg?lTO_;s k?(?Q9?-?Gqh?lsczb? =EQ?u?lXSYt?>s֧\?eȱ ?bMeQEq?-y<-?pe?|8Gm?Bt\? Wp?IӠhn?Jʽp?Vb[?y'Lh?@z{?O#-#|?!9?~so?a2U0*C֪]z?9]c?(XQix?Mjha?Ot]t.2B?L0kx?jJG?gdSdrݔZ }_>Y1\m)OVBwIu&6׆MqJ^?ܵ|pĕwF[u?ùz?]i?r„Ѭl_]^Kim}Vy?I m?k衆?ꗈο?ǺV?~p>u?fʉvE#070q?4aO?}!Sr}xPO#Er?Vc#?x@ٔ+?L 1l^ZQfS+H3fBDMjhQMb@g_5!Z~P)t?nKX[?VBwIu? IJCr?B:VW9r^IӠh0̕A^xz o_iV\?ek}vs4BdptY5Ϲ҄7mi*8N gjCupPqx܉guS(=!7 Ǻ`E жOۈĐL|Ot]/ܹ0ҋz_W{Lo.2 bQWmU]p푿 4ԉp^}<}ru 7k[bC9Ѯ0xn&퓿^/ ۖ[JP:; 'ei悿-@jyd˻ꑿmso l`q>x҆Ò@ޫV&!Swsgj+gk#GZ(yߌ68ڊ]0掎i4U) uXᆿ26t?5"hwِfqőK7Mpe/󦒿k⪲;Ū9ːG399̗.!u0,?Qِ ++< QU*ÈMeQEѓnHJz-l0!3;zjLuT5sLh:pΈ]L3딿 ҌEә.8_̖}ߓz +TT]gE /eNĖ^Kċ̒5kzPP!9j%48E7n a*v0(hr9U}<.cȄmnLOXܹ0ҋڍ+f2Tq~ʼnUގpZ6o捿>?m|9ӄ'c=Զa{\&@KWxe3;oc#Շ|G 1mpQF\m딿8j\Eؠ^L3뤞c0R}%蟿cxg| Vj-/ Ҝm/rI`sIЖRX;ۓGɫs Ȟekq&n\pQT,{،HGŧCVzNz;R}%A~6rݔ=-ʖZ昿}i%uN喿 $y}U.T1>^F}țBQlu4ӽN.oLTݓᶶTw閝,}2R臭ysV{؛wWߢ֛6vݑڜdt}v Qf`b.ڞa_#EdXśCpspvkyh|o^}NPh^`7Zg|_\+POOe/䡿Eկt><Fҟn.'B 8~bUʼUסzVnŨD3<rùlMU1~ٝ7T73cv_w)u8Fr0lp揙CogFwIQ\ Ac̝ܠoN\CW"P#3E3ۃZ>-Wz =NV*]V$&& # /$sDKUY|?q7-dCԷfLk^ \U]RE*k×FIlM!3ڢWۼ@H0rݔZ v5;NѡʉvR~]kSUh,zM:/K?4䢿d6::Mr NE)%嗿j= LuԞ-v*;.R՗˝V횠w*2pWs`< U]|,}ZKQ._x%Ù_26t?⪲xy:Ws M^Dۡ(_B'; "R.vݰm `҈FuU#桿YyVҊ0|DL;(AVF摟MW止T㥛 6$nۧ1(xߢIV$w~QZ| {Cr2qh^mwNy%]3fOv3fIZLk^9}=_?x=^  w/KRbz蹅1߆DioɤtϺFˁgun8Iط+I뛿*kq&e\Ub̡$7nض(6>4%uD3O)A^sX}w+h+mT5A}噗LK10J_vݗD$]t^cޢ6^`>٢ѪtRr/0镲 L'>堿Gˁj۠5裿4RQZ;Hka9͢b?4䢿6[o鲘|@P%$&ݖȥTdqWs`XU~}[~c|l;iIZ Ρ fbaLTc]F,zǘKN❿{bb/xŞL$zŢSsv^i4[a^CpIz衿7neL: v?T1ϣhN?o^gyȔAFn1ܸ۟Ф]XSYvQBv$-xWfM~Tx~~6ُpsWuV o[tTSu8([Z?Qٰ~Σ[+,S:M ꥿09\c"6㤿tZAg@1[*¥MT^@߾UZ?I0&$zz |(ђǣ0[w/书0*@شg s631]է:6U~duj1xLYe0Lݕ]0@1d3iSuUw+KtY<$\Rho8, J vt_ljtMT#V,ؼZ`mԩCr2qjdrjg.u׃Iӣ?rw[tzyj2,+x-B;Y4񶪿)t^cv5yʢTtT4-qǛѫJC3M~2T$; .HlY.Jh}$%= 66;RD}uUoh~妿Ej#'r\7TYO8#+75|ݦ5x_ ˸5ꤿNA~6rݤK?ay8餿ta'y3-9(aO@aodd_|/AMGŧ<2TTU꨿"rzf[[!ƪ{秿u@_'b VBwI>! \Nё\Cfg;przfw*2pQ uiBYڊ0 eHp`> +pw|١ $(~d:tz ףp= #dTƥc[ݑ˻+dr۾( yrڦy3Mئ%7}9]u;O<{ԗ3ٲ|s-Zh%§Zd;Oπz3j{c|%[z4Փ||BvƦ0|5W>@~߿yq\5B_zsѨTD[ʩ-#/hO%rޟ5&ĤG=yX5ŪA۽wIQTAGZQ7Nx N>:u峤_wģKP6WsDɪ7U S[ P饿 LyGsd姿=E~({_c]Kȧxak򒧿!yvT:X01˞6o;2V ta0̥?  X4 KHEJY2 Dlp槿#&ݦz9cxMbX9c]FXyVҪsѓ2|~I ٩5_%(XQic]Fx]iqn -& N} y稿 9?ũc1̔wٯ;y0*D47¤~٭eTC`UOz1 }VGtF([ZRGȮߢ֣:8؛HIOǣA=,Ԛ3aod[z4ՓVo_["ߧ ES@ iQ䦿&L1AM.:.\s ~{ڥ\u!>#ƥ񵧿bԵ>*9'>5&\R)ͦ)q̧@9wflʃ9~R8;ۤoe1g)YNBZAC56NbJ!ϣ4M~2Ƨڎ MۿҤٯ;y⩿~7333333{G[rPLXן]Ա y7Rt_Τv_wD]L3t_Τ R)v4KqUwJnI-YϹҤU򣿐[EИIԣi_=1liN^dhqXj ^E>l<*M ?ߦU2Tq㦿G6 =bB9]P,cyePmp"=_\6:rå=zå%<ןħe[Oꗈοs˦YyVҊ@9w^ Pjw+KtY+j0 ç&r4R%WMyؘשҥI*jhwH1맿B%UMXʧB %S;äs奔\d8Ϩ45*J= Bf W墿LT7n;3f|+5{nLOXVA ]ޤnSץF-t%ym4u!=@)ͦ pzǥtv28JOs'اp%;6+TTJ Й5Eӻx5" a5)YqhӺ j\$x馿 ESTr3܀ƠBzܷZ'?tA}˜ Y2ec'rJ@q6NoDA9w^6\䞮^d~$w0b6U檿":v٧·SG bk(\¥@Ρy c AJ٪^;&recG \p^jG[Ye8+n=G仔?8:V)܁:ѥW\;9CqǛTTJ磿'O:`臭 82!撪ǹMW楿]9S5wA&9 o^jEu9@0G,*t3TT %VF#3kF˥&Q/4' }ssm5\` @stn+2: i>Q=>G5j]o%;6tw 4\&rJy@m!6H¾D՗˥+򑤿FXTĩ!ɬv-|oe1 |(Ѣ"ɩ "5b9~߿yV;MfG˵h(F̱ߋ/ㅤO8CqǛ rh#~j^?wEh׿!hUK:5w\ Uܸk衦߿yq⫥0{OMG7聏T:X0UFSweuG>xꑦQmিOG6<E(l@r;sq3ٲ|ڏa\wTܤj1x o}ϽK3(𦿵vLݥ#裿Z[!ܴV]tysV{ӹ]@"E};lfb#F \⧿9_&]gE!oo l`q{,}肪Md7qXQ À%WaA fS9}Rc裌4]iSzˤihsۤ pmP0kTq6+KtYt|8cr奿B]Puq ॿ"3cuݧDԷ颿r߉Y"i7[tzv噗æM$gQ4~$"eNac!:/N|8 `5!9 ʼg\8%@2:=樂g+/HZ\Y" pA,_=~dŨ-Ψp^jG~/lV^ =N>*dg^ dT85*p ܩUX6sC׼֋hWO"¿6#E"*UlX7t'-罹bg إe?gdSH0~pG7_pZ𢯨B8E};l*3:Yjh4yT'_[?KY8ťLۿҤ衶 Ӹ7a4R-'􅐣mnLd"<}Ϣw~ՔdPם6㤿x|{נ/"QhYC3ٲ|]Z.S3e6 Y>䥿ŦRB^񗦿\U]vOjM?@v6ħ/[[+TTJ0)>>!;͔XVn\>O@g?RDG>xꑦx N} y=e5]Ot2wsP׻?ޫަ, 2磿wb֋^SCVzNuݨSݨIZc]M̱i+,;jL(_B_wJ m6 Z!yՏMzVסZa/tۈ'+򑔤}Ϣ0[wC3;ۤo.2 y]ݱ&B>٬?I)r3܀`>I+٢:tzލuX٬\md$t(@M7N!6X8Isg&5>tA}31]էI9(aơ~OP&>B͐*ft牧9̧2T !Ǧиp $ [Ye x'/[#rN}<V*V \⨿߃צs.Ueߥ×8謹E =gx`|66e}1ZGϧ'є~P[u)[u X)7Ӆ 6I/xŦAp西PNĬC9MZgܶ奔cz(辜ٮWTd訿,S@2:=樂GZQ;]ت,̰f\S pG7u =qR8m鲘ؤ5_%Y,_Cp\M/"Tʄ_祿?Z[LW\Tf||[Yǝ?*4˦?ߦHk :!tZ!y0F?I/j4fSK㧿>Ȳ`⧿ ۈ'ѧ1lf\S@gҦꦿ)s覿맿DJy[ i/.H?ߦ鷯rmQڦxU;d9 Н`unj%ΎTEU3J?֪ɬvh4jdrjgnmyبǺ^#w~QLMܪ$cթaã~٭e9]{Fw; [l\wTܤ;뤿Ϝ)Ǥ R)v4xxρd䤿j';V;Mfr3܀vmo$?V㥿g|_\Ҧr(F ^/,26t:UExwЧڬ\mŦsMw}欧zp̲yȧ&䃞ͪeST㥛 hV&+ x|{ [[x^*6,zң?<- 0*XVn,`SYvQT7Szܷ{"07qSͧYh9Qewg?RD/|X;TSu8uX`êZm^aQ,YfJ\Ǹ~x+ hfloDk:M$$6?ܵL#裿iWY[vػ?ޫV:jtUHIǚANk+ݓjI-5.@:s ]j~^.u;$wDfEu<$U@)5| 릿EׅOc[i2mU+YrL,)w㣥 $(~b4_n󦿈SƩpqt&5йy0HlY.çКiQc섗ԧKXUȗP᥿4)WXp?੿N S[꨿#wDgR#3Pqxܡu eOZm O'u:ꣿYNCV壿0z9cx=) s 3Nz1;TSu8r);뤿Ap西rZC@1dx0DNݓQۦJbIߣz x7/gPnb ڬ\mŦؙB祿BYZ˿W[.]Pw/ݤP<f\ &oc^'2,Gq> ܺ:IQ}I`s\+,"Tc> ЩJESUɦ bӾzܧ~?rkmj3Oɨ2r}OqN`\ `/&E'w﨡1znhyܝqo~*:]1 _Z'Nz3WWгYڢpsX7_>Y1\p +TTlfܦ&@UO}k "ڦ\7_[?gё\C9DܜJAȓk]~p|HߠJ ,)Ȓ9wcg+2z9cxJ%<ןL1ZG'2sc NҤ"[AӢnZK)[nK2VWϧْU7(yu2VW LU@G \A{ cOnvۅ梿ADj4!S>UR#3YB(]X6sHjWCKh׿룿:tzލ`-ʤ:ϤvR_vjhwH1@[[x^*6 ?Ƥξ =,sxx¥wӥ|{נ/K;5 n0a[y0HI?7L4HXp?b4qPiĤZ[!32])v?T1uSk7qrCQߦŦB ^D1u!!wcAaP-\Va3i>QGq::1 n>>tA}.!uJ({K9۞ZGUDݧuryUgW_]Š;䷠Ee?!!-2|E^Ӄps@T⢿ْUn}гY?{&Ãf׽nݳ;8؛<$#R;V)=K2 {㥿zJ{ds:H$>w,oA ]ޤk=+øDk*T/EH@ΡȗP᥿3AV{`Ǜ,n;2V~7ݲ٬N=j噗æ;G妿8 B\9{g$t(ss`9Bّꣿ'>@oN\XS"1A H}uv;sM< JbI0fKVE2 9 {ᯡ@w顿 ]lZ)ƧϠ>ϟ6ӡ^=1X_{fI|zlˠ {㽢ȳ9*7QKsvnp夿2kMGAv-=ɤ$kg{􆣿~l񣿊ǵbc^Gg$ӡnu`᤿겘|\AFu|H`+f}E֢Pj̢rh`UN)x RϢE_A&<YW}WOP&w}9^.;1륿>+rjgR Xrߤ>tA}H<+NZ7 ܺ;]آ= !WA t Kz2?qӝ'p<13/mp:6UȦXPihtcJ!K3E`}=_\6kw#~_w{\BYå捓œ3<֌Gu:Ԛ <<0D؂CN$Ǻ_ <-N6[y䟿[\3?svxB?ϝ9蠿Ù_3lFI4`zB=Т$7p%;6$cաIp< $},~SX(zcPmpj{ d=Lv5yjɬvh^ zo XL/z΅k_@/ܡZ+6m/rk蟿HQYڊe6>嘤DrcråaX5smqR809\*tBJ{dsqJ[\㣿atDkz0HWuV,NGgʙk**n(ߞyZ΋_(QdԞl# 4yTsfV*cKunڌӠLuT5}U%!'󋢿˷>7"k$Ss#070,sYL}ZEh桿x`|?5^qᠿ4߈Yh RςP#RAw鱝MhXRu?TwLݕ]0cD(b >㟿^d~$UX6sup7+pg?#]J]2U0*<)t^,'C?{^, ד`;} ĒqPi*T7ۓkqQ-&^,F]kS.rOWN:BC+X#M~2Ƈ=4a-u׃71$'\QJYPi4:q5[Ǚ&l?g%0a4_y"*g\xߢ`eM,UW^I +"10Eb I'vXz5\䞮nKXZ5磜^J]2KK^xbg ט/;9 Q?ÛKZ ϖUD W$ nk ϛUyH,|}j_=1}h52_B͐*WӠh"<. >/lV^Gg e (֩=#_~7Mp?xw0bZ8up71Ep>uRziыg~5I?xx[长H3Mg'c3I2A t 蕿P&,)w㣕5-!;oc(xߢSVӕn\`.;1ŐQ_na+p{HSȕz|R@fg;KJ̓4ׂkhY`%Om'y3MFҿ$T4Ά4ׂދI{dI*SAq"M=ЕT+~NAՐr?j}?Md?ws?#dT}?QZz?3Kt?Vۀ?ƠB}? IJCr?z&?F?2Tq?i{?SQG?oG8-xч?4t?oӟHy?υ^w?#@x?p!?#ݯ?x%s}? y?WY?_ Pj?Wۼ?đ"?Ӻ ?}9]?K?a?E?8LM?K?-?ϛT?N>=e? ԟ?s?9A>?dP3?ahur?f(ϼ?iӜ?l#?CmFA?Z(?Yd:t?}qJ?)?עh[͚?&S?>uRz?)'$?=?/w ?w( ?\8L?%@7n?v>?DXƖ?MSt$?.rOW?|R?†W2?$ӡn?[X7ޱ?D?O??:٭?64D?Dׅ?S?lyzL?ME?4BX?i7>[?ާ?;k]h?e?>s֧?7/N?t?tV?DV?IL?]Ա?٪?V9m?*[ˬ? eO??/3l?Ss?_?@?Bv?zp̲?zmm?,S?d"<?-`?-b?'S?صݒ?Ǟ=?Z!?ػ?ޫV?D5%Y?9w^?&?e?6U?7ܘ?G֦?=4a?ǘ?}8gD?D)?Aը?v?U+~?u=u?+TTJ??Û5x?AȗP?pN\W?yC? pzǵ?~Ϛ?o$j?~p>u? Y>?R] ?AGZQ?RU򱻴?`? ES?Z?r?.ɳ?EfR?DR?? ,?4/w ?fh<??߾?ˡE?Y$?.;?l?fv?0? @t?>+N?ZxZ~?ׇF0?Oqx?>>!;oc?WI?c .?d@zdz?Z?]l?M~?ݖg?wx?*q㊳?T 7?8GW?Ӳ?368?xe?7[ A?Q0c?-z?:>Z1̽?}9]?¿3?pR?^?w? D?SAEկt?39A?`s M?.?8+?~P)?vN#?z"0?d6:?//>:?qvk ǿ?V(?Q [?`L8?jѯ?)?I_?/?81$'??w?I2?ᖏ?,baL?}?^a?k ?\gA(?I?)?CVz?J?I(}!?-W?6?D?~T~?.!u?x]?B"mOT?*ø?bu?Q?r? _?lBZc ? ?=^H?B^~?9l?[\3?? ?-$`t?ܼqR?66?e4yS?ɍ"?0/>:u? [?LU?P?ё\C?( 4\?AB?z6?q ?@J?иp $ ? 5?L1%?{*?XvMH?~^?E~?:Yjh?O?-? -?je?~?LT?uT5A?Q0?ׁsF?\R?؜gB?#+ ?E )??ߡ('?6^?Z(?AF?q7֊?W:u?(bc?!? ƈDe?0 "?:?]J]2? %qVD?t? ?bX9?9 {?r?֊6ǹ?h^`? O?I?`5!?5\䞮?1#?t_?U[rP? )"*?;?l?eÚʢ??tA}˜?Q?#10&?/g+?֥Fg?? fL?_#I?Tl?˵h?J8?NC?g%?xWf,?UQڦ?5 uX?M?TH9?ȑ?+ٱ?&)?|?q?P?<1PN?J?j,am?hyܝ?]t?S;Ԗ?kBZc?1 O!?_ ?uʣ?=tZ??ݵ|г?i>Q? ƈD?Ko.??k~?g?? o}U? L?c~x?'i֦?IMf?Z8?mM ?H/j?viai?L?Zd;O?U? ܁:?]C?Tt?4Fj?|E{?_ ?V,~SX?1!?%?Ko.?~K|?\E?G6W?O0A?[v?V^??UD ?Nё\C??ԀAҧU?}k,?#? ?5{??lT?*T7?@L?c${?C?m?i2?"jG?oIa?*mq?׆q&?&|?p& ??O@?aũ,?aX5?1{O??q?| ?Eh׿?y>ͨ?ǡ~f?`?QB?fy?=}?P?ac?t ?`>?&|?\v?4($?.?-v2?.u?K?. L?~T??1[*M?A?m?4R?!q4G?H? $>??J({K9??\Y"?겘|\?8h?^f(?e73p?]lZ)r?s}?89@0?0?ao?[b?\qqTn?5($?g?"&P?~qJ[\?fB,c?YN?4hb??I?MT^?]hH?:M?c_?R{m?7ܘ?p]1#??E?PD?$?IVF?SQG?V%?r_?n/i?uV?pDIH?EN? $}ZE??/?L8?kծ i?Z ?KU?,f!?,?q]P?0du?+6+1?Քd?eS.?霟8?1Yd?-??+]?-DJ?3Ib?[.?A?p1=a?;"h?U?0AC?!=@?l`q8?h??{?kSUh ?g|_\?׻??߽?hzL?\#?Q?=බ?/o?2WXp?:K??2:=?i2?>>!;oc?vz?@? YK?9U?M#?ԕ?N??_9??]i?R{/?۽'G?HO?:̗`?#&?CV?jLj?cyW=`?>#K?:YX?Gq?խ?P?6~?N?L5?1?ݗ3??:s?ɐc?x ?e@?p^}'?7i?ds?AN?X6sHj?:]?0fKVE?89@0?N?^?]?t??aL{)?N ^?VGtF?@N0?U;?U?OV W?${!U?2ı.n?m:Y?_D1uW?[T2?,^, ??^?ES?K'?\:nKS?+i7>?@h=|(?乾?}!8.?~QB? \?`d?.?kF0?unڌ?) ?? .?~7?jWV?ڬ\m?be4y?Zwg?[>?Mq?CR?*C3?J_9?Ѐz3j?}?q?Rx?W?M'?x?l;m? ȳ˷?SAѪ?Y.?ߦ?"?ܴ? B ?)D/?7?N],?8 ?DR?g||?֋hW? +p?nmy?x~~?yѩ+?%Zx?N`:?"? 1^?Qew?P\?-@j? ?b.?nt?_zsѐ?=>t??!?<ؖ?y?OT6?#?A ]?(^?@شR?>$|o?Ss?i?2?۞?ƄK?rm?2#?xܙ ?W}W?dq?cd˻?Z{?J}Yک?*K?'XQ??6ɏ?w;S?`#?zю?iq??qŦ?p'v?MFa?pQ?:?:?lu9%?ԝ'?:?dw ?~P)?0~?Ye?ۻ}?kЗ?a1??x?RX;? l?{?"LQ.?`VI?GV~?1%??:/K;? Jʽ?,{؜? 7?pUj?9]?נ/?WuV ?鷯?k?VSb?6>4?r/0+?@w?N0?#E?^? b?MۿҤ?+eX?Z,E@?PV W@?ahV?4?i֦??.?B4-?G?@?]pX?E?5w?3?"[='?i?B 8?427߈? IG?~P)?üǙ&l?Q}>?k?C0?T=ϟ?I ?l?T?[}uU? :!t%?:G?y&1?yY?L?#?v4?n\p? xa?0|DL?6 r?YU?8#+?>N?nKX?E<\?Jwِ?k{%9?d8π?]kSUh?^?z?Q5U?0!?L4a?_=[?8Jw?YrL?J^b?.o?J)?L8?## ?}|? v?tB?Y8m?n?o1=??HnM-?*TPQ??H5:?_iN^d?Kr&O?Iڍ>?2nj?i!??XP?7RH?}$%= ?bg?8+?[?XL~? ?xܙ ?t>?C?]=?fR@?#0?OZ}?wJ?ˡE?gyܝ? 3?=Զa?9 {?Z %q?YD?{%T?Ș?<Ӹ7?$ ? 1Tm7?s?+3?y?4iSul?ޒ?(Z&?S?N?r}?yS?i4?6Y?{h?@iQH2?st?Kq?([Z ?$]3f?Ը7a?:?<?0?n?E\?"u?w*?aUN?-?Qlu?$c?y?X9v??\tYLl?3ڪ?7?#?%}e? ?O?K!q?G?d:tz?M»??2ou?OY?*?ٮ?HV ?ac?ÖM?<Y?l3I? `5!? <.??qZ| ?sw?ù?:]?@w?7T7?=WX?9 毐?ѓ2?}ݮ?öE 2?4?z7e?׺?i?ο]?-?1zn?L>!?M~2Ƈ?@ϝ`?b7W?]ؚ?-II?%̴++?`??:?(o?y&1?f?"[='?3`?$z?'*T?ٳ25 ?Bz"n?7d?gltO?ypw?qrg?h?؛?]=1?˶?lV}?IDA?.;1?( 4\?{ԗ?Ǻ?g?Q?E>'?ZF=?Cup?ٵݒ?n??2?%?xx[?U)?NG7?:G?r O?Pl?d:tzލ?}vuŌ?_ѭ?@H0?[ m?ER?Yک`??m?$}?mB?-zm?bG,?CԳ ? q? X?4J%?ۆQ^?LT?uT5A?=U?Q [?.\?(?zI|?֪]?jj?]9S?"?O8?E};l?鷯?ّ?mU?q9?&?3O?a7W? 7??bI?yj?#KXU?ϛT?)k{%?x`?#R? :!t%??4?0k?|s?*oG8-x?)Wx?TnV?-?2:?-{؜?E};?h8?E};l?y7RH?ݲCÖ?%P6?d?`.e?v^?[1?SW>?UO}? #?:w?8GnM?;nt? k?mOT6?XC? >??o*Ral?U?N>=e?Zc!?6~?uo?QJ??Os'?:6U?7^?KtYb?Sg?/x?5>? IJCR?W;?Z ?l{%9`?ꐛ?,A$?Qԙ{H?I4?.s?;4,F]k?gaO;5?Y!? ~1?u6?l<*?uS?E};?/x?oo>;?V%}? 2&?BsFZ?oKS8?ak?]i?Ng\W?^b,?K?W;?bg ?G39?#S?4Op?Ң>?Bt?p?2,%? ?nض(? w(? ?S?=#?ӹ?&?ec]?V-?aTR'? ?^sa?F ^/?*6u!? !??x`?|?ik|&?4?Oz1?r?eRC ?&0?y'L?l?K7A`?B=}?t ?V_]?% &B?!3?/x?eI)?ҋ*?+ٱ?[?? ;?#M?vfG?Y+?k=]?x'e?SW>?.&?elf?K?%ǝ? <0?;M O?_% ?U,~SX? nk K?nN%@?ƟlX?_>Y? ܺ??`#I?/?JiWV?W:?c? =@?_vO?u?$?3ۃ?5?5?C? ?8:V?yu?P?yՏ?1#?!>M?09\?fa?ʉvR~?M1AG?/>:u?]??|z?8k*?n0a[?b ?w?Dk?p(|? uX?o%;6?*ø?f(ϼ?/?^SH?I0e?MbX9?)q?cd˻?)?}[?: v?EƟl?09\?J ,?GV~?hzL?N@a?t_?}iƢ?^a?>+N?P,?~E}?wD?2:=?j]?xy:W?qS?gx?:=Ƃ?Cr2q ?([Z ?ECƣ?3ڪ$?~k ?lY?*[?獓¼?-]6?_\?]N I?6ɏk?RF\?z0?37?[1е?y0DN_?V)=K?ne?6t?Pn?/f?(r?p<?~@?3K? |$%=?k i?iV?}Az?(?'?%VF#?º?E?0a4+?vp71$?{b?Z?GV~?om?3?O?HV?@ȓ?J4*p?g?jZ_$?ۼqR?ס?)AG?hE,b?[[x^*6?!?W|Cu?a⏢??Qy9?X??&|?6X8I?I?C{.S?od?Oc*?L:?ߧ@,?ڧ?Ѳ ?}V)?qQ-"?ǹMW?[z4?g??}?G`?AC?镲 q?,"J?( ?z2?SG? M+?~r f?Y5?pN\W?PJ?5?\J?}A?_;?FW?4 S?);?:%?gRy?D2?\=?1x?x@?l@?%?͍ K?M]~?Tl?y8?AJi?I-LN?k?]i?Ü??1?[?·?c~nhN?pӝ??l ?.?Ǟ=?cJ!K?w*??̰Qo?,D@??J?|wJ?QhY? k*?SX?"?˚X?}Y?8?BW"P?c?' ? ?Zc ?I`s?%/?hBĒr?6^?YLQ? %qVD?}?o?]1#=?du?5磌?,?]E~?%wDf.?) ?|~!??d= l?u?m½2o?W\?4l\?gCA|?@S?1&?U?ihw?6t?Pn?"?tgy?l?Hr?ۈ'?`V(?Y,E?u?U g?*P?ٱ?KO\?;ʃ?^2?X?m?G,?DA?B˺?\p??\:U?5Wt?Xl?fKVE?n?Jó?)?۟?\E?k g? ?2,%?=1Xq?k ?" ˂?^9S?6$#ga?n?fB,c?A?L?p= ף?W:?yt??q:?K?nUf?Z`?}!?<|?bc?ҍ?L?-e?]?LbG?!?nJ?Z}uU?Cp?i ?QG?ѐ(?@]?X+?&B? X?fd?gej?A?zR&5?@ /?09\?ܻ}? ?8~4b?ahV?J^c@?+z?l?$P?T#?j+?Έ ?@]?^f(??PUX6?!i??[?# ?fN?[>? L?,E@J? >?2SZ?"LQ.?kMG7?%̴++?t%?/L F?6g? ? ?2ı.n??:u<?vݑ?T2T?Wc#?;?Ό~42?#,*t?i?m?=#?7 0??&P"?:*?f2}?On?C׼?32])?%wDf.?N?6$?x?'0m?+?ٕzO?ĘRx?`ũ?"S>U?'†W?wE?w?0b?PO??Zd;O?d8π?t)?#fy?]4du?$D?~^?ɧ?U??N&O:?z?c+hZbe?j:z?YO*?zVҊo(?_ ?ӣ???\U]?WV?X0_^?͑_?l?j0 ?L?Bt ?2Q?ӟH?̰Qo? a??CqǛ?g_y"?UX6s?s?X?81$'?b)?[1е? YK?0q ?ٕ?`< ?s]?B͐*W?i6`?YO*??qr?+ۇ?|?>w׹?5?2;ީ?# ?;H?:\=?(yu???Peo)? 8Kr?&?p]1#?s*?Y.?i2?^`V(?(Bv?rݔZ ?z0)>?B?ei?X"t?Fҿ$?ۃ/?ME ?uwD?? ףp= ?Ũk?ˆ5Ea?_ ?#?ΧUJ?n?%P6?ّ?+j0?U3Y?Ϡ?-Zլ? ?i^`V(?@M?,g~5?Jc?j?$EdX?Q֥?J U ?bD??1'h'?Cup? XS?CƣT?EB? ek]?ꫫ?;u??4??H5?|^?(@?Q?g+2?q?HIO?ƄK?jQL?zR&5?CX?47?]pX?d,?6x"?t"T?-u?"~j?"&P?,F]kS?Ḍh?4a?>h?DJy?d8π?n4@?dJ?#)a?2g?.Y&?c>?l{%9`?Cc?n^?VA t?|(ђ?2!撪?}?5^I?@?Cf?p5?J#?L?B?r?/1"Q?w~?1ZGU?p5?1vK?X|[T??`;?T:?#W+?.?v ?Ŧ?=~?|?5^?^)?&? E?*3?_`?e ?X?&E'?p5?˻?im?Za?K^b,?ڪ$?Y|^??JA4F?F!ɬ?u??8*7Q?7?a2U0*?l?By?pWs?~qJ[\?d:tz?|[T?dvS?9 {??p'v?q?M?G&ji?g׽ ?xgɿ?٭e2?,o ?+ݓ?YX?4M~2ƿ?p|#?8πz3?>D?2 ?+`p?DJy?؂C?2?ȮԿ?Ҏ~7?q&"? ES?}^# ?m?3?KK?{?*Qr?1 Xr?,cZ?$W?AGZQ?dzF?lY?"~ ? ƿ?ôo?Ϟ$x?L8?y]z?翿?˻?Wya?_s?d?_% ?%C?DeÚʾ?m2?üǙ&l?n0a[?an?E?c\qqT?|` ?CYZ?&?N g?L? .V`?P?9 {?m4?碽?|?_W?7Oup?^gCA?:␽?+&|?W \Ȼ?̽?)'$?y):?I?ō[ ?I}Yک?'>V?`?AC?Sƽ?-a?I,|}?Wm?m\p?wR~R??;R?H]-?S:?֌ ra?;?EԼ?צ?emS<.?_Cp\M?&}??Y,?bg ?/EH?/r]?>&R?F<͌?Y/r?zƾd?ir?LnYk?H¾?UG?׈`\:?,cC7?Ot ?>UW?zqҽ?zܷ?{?mT?pG7?O0A?Ue?o{ĺ?m½2o? ϻ?Ά3?@-?-s`?w?)dq?K= By?>V?q?[? y?K?{ds@??? L?>?fB,c?LQԙ?K= By?xZ~*?Έ ?8:V)=?::ZՒ?*Wx? Q?sDK?9b->???&M?0乾?=Զa?x]`7?ZD?#?m"?뫫?0s ?4? 0B?o1=?ȹ?J?Q [?3?'p?PR?@P%?<;?QcB?bh㈹?"070?T?f"Ϲ?ٲ|]?x@ٔ?)wŽ?.1?G&ji?I?=ڨN?#Ƥ?m2d?[?,)W?MDu?{/h?A?y0H?ϸp $?1A º??=x??ٺ?-!lV?o^j?DM(#?¡xxϹ? 8?&o?-\o?ۂ?S4? M?8-:Yj?HRE?=-?\&?..? ?3ڪ$?+ݓ?zƾd?>6?sfB,?uRz? [?D1y|?W=`2?U?=ϟ6?%wDf.?[T2?,Yf?t%??&OYM׷?MeQEѷ?1 ?@?vKr?["߷?I,|}?K?(9x&?8-xW?UO}?K? ?ʌ^?+m?_Cp\M?N?>?k i???p5?]Pߺ?kѯ???ܠ[;Q?Q?]? ?Vy? F}?zܷ??/ Ҹ?;%? Q?t!?3z9?:X0_?$wD?͑_c?EׅO?:}kϸ?ePmp"?N`:۸?.5?hhs?Ӻ?B&9 {?؁sF?y?&d?5b^?nIa?uOU?3ތ?_F?۾G ?ri+?φ3?}?}p?~Tö?;2T?{YH?<,Ԛ?XVĽ? B\9{g?eVA t?G 6u?Wel?נ/?dTka?j';?T[r?~?Av?H?H}8?ul?;_?}zV??0?| Vj?G=D;?8?i%?'?`9z?'?8Z?D)?kE&?L?BZc ??ȯb?39A?ސFN?CB?÷?V*?!>@?NA~6r?ᔹF?]Ot]?? ; ?ɐc?F>x?3&c`?ʉvR?h>?]-ɵ?#/kb?Ϲ?eQ?7GnM?? 0,-?9EGr?ND?DgE(?M»?毐2?$"?R =N?s//?!F?X?gba?JFžv??{?M- θ?^S?`_?Ķ?y \?x>?ܺ:?#@?-!lV?#ݯ?հ߷?k dv?LuT5?GC?ѹ??No?!6? M?{?7iͷ?6qr?PS?;_E?y[Y?yȔ?iTd?QJ??l?a?:M?+ۇ?τ&%?biG5?_ ?Rԙ{H? ?ME*?W?2(??"D?B?Y2z?Q* ?"~ ? ?Gq?a6Ϸ?Jʽ?<?,ε?SF;n?KT?E<\?F!ɼ?]9?#+ Ƽ?c?,`?CSvA]?j?I5C?`?Þvk?+-? K?SW>˷?4f?ht?1 Xr?s=Ab?Xn?5?Ҷ?JbI?l?ޯ|y? a5?vLݕ]?)[$?f·?ac?39A?5Φ#?^2?&?бJ?ĸ?s`9B?9?>w׹?OXeS?U-?{?7{5?q߅?٬\m?g%?_n?L/1?öE 2? dvS? ų??=x?:q9^?#G:#? ,?l?yؘ׵?@]?mXSYv?K?˶?&??- PSֶ?n2d? @t_?N"¿?B۽'?1? ?L5?߃.?8~4?Z'&?"ߥ%?#070?k?c?>@v?I,)w?GȮ?JDAc??%TpxA?w!?A? Q?(r?Xާ?~n? ڳ?Օ?=Е?'8'?f8?vrѳ?XB_˳? ϳ?%{?ZӼ?^?:vٯ?*)?r„Ѭ?}s?n?W:%?ӿ$)?Ct 4?7^?o4(?Z+6?ʆ5E?8#+?o˱?<ן?4/w ?}=_\6?Db?s?GW#?Y+?{YH? H'?-x>?+hZbe4?(&o??լ3/?27߈?2 ?@ZkB?vhX?ӻx?n?_Z'?u7O?1 n?1?ܻ}?09\?tx㧵?z蹵?Ѱu?#?5ڋh??)=$|?S?'/2F?4`?:9Cqdz?A%c?Wѵ??dp?d8π?ME ?YR>G?$?1&?nEE?~:p?<*?so?"*x?6Vb?0?#/kb?x]? y?Bt?.?IVF?%ZxZ~?:ؗlY1\? bհ?qn?fOs?7>[?(9x&?s69|ұ?Bus?sA?SV?߼8ծ?Uή?h?bk_@/?FA?/g?5\䞮?-e? .?Sr3ܰ? p?;B?X zR&5?4iSul?6:u?`?eYJ?a/?x\T??r4GV~?8GW?k[?OT6?Ia?FY??a'֩?Y,E?&5Ш??4?3ތ?Wzm6Vb?r#D?ǹMW?JbI?,?:;%?~]ݱ&?4hb?s?uwD?2֩?@൫?*Wx?iI ?ʉvR~?P?yƾd?C׼?@?(Pj?A~6rݔ?n燩?Wx?,cZ?Ҭl?а?Ky ?Cl?]զ?q?4hb? "ڎ?A{?N?L?A&9 ??yW=`2?-@j֩?Քd?0-a?Q? ^?fB,c?eV?)Z?"~?zmm?,Yf?=ϟ6?}b:?S{F"?I,)w?O>=e?S 蝪?aX5?g?0 Xr?2rç?w﨩?Yw'?T?~jt?rb? '???^v1?1?l\?z):˧?Z+6?ӅX?R] ?1ZѦ?R?t?,H3Mg?$*T7?ҌEɰ?Kɰ?3?vN@?Tr?l?Pl?Z{?q[u?;oc#է?&@?]~p>u?[Y?!?.Vۨ?IZ֧? Udt?g׽ ?mR]? s69|?&lscz?7?e\?+*Z^?{Cr2q?i?O|?r-Z?h52?w?Ϧ?_% ?9Zv?h?_=[?5k?(?ESw?M,z?g@5?Za?=r}Ǩ?l?o^?ԕϢ?W\?}A?W}? W?.4i?k_@/ܩ?-σv?v1Z?A?}8H?ħ?l [? ܺ?["ߧ?oꐛ?/0+~?Ϧ??V?"?!?,A?n? e6?yC?n?u?WWj?ӻx?n?l dv?XQ ?镲 ?[X7?f|?ْUn?,?13/?J?>Y1\?<$?T?A_z?g)?35 ސF?piݦ?}гY?#]J]?T?C(^em?VC?>x ?+򑔬?n)"?Yw?P9?-s,&?kz?Д?`6?qW"?*T?l򖫧??@m?AGZQ?qS?X"?P?g? X4? E?@>?4*p ?[?뉞?.7?U)?? 'i?=)[$?GȮ?,B4? ?D?[1е?PlMK?!9U?`?5Φ#?FИI ?À%W?1]a?]ؚ?q Z?N 4s?d73Ѡ?0H?s?]gE?)u8F?b?zqҕ?Iڍ>?J +?*3? %? .V`?/\sG?Co?O?~P)?j{ ?7q?O?_% ?r ?C??NB !?U,?\;Qi?f/۞?K46?M΢w?!?Biq?4*p?<-?B?l ?v?a4o?AL?gF?N?|,}?Q۞?we?l@?؃I ?sѓ2?3?AA)Z?JPB?|F?>uRz?5^I ?ZD?{\&? $(~?6ZPۖ?`?>?Lu~?>-z?ݳђ?]V$?]QJV՛? Jʽ?vN@?3? m9⪒?p$?fs~?}w+Kt?ah??4䚒?nض(?5>?&?'Ҩ?m5?7T7?uBY?uOU?w~~?Ҥt{?jP4`?bՠ?J?I̜?ץFg?[;Q?u?:A>?\n0ԑ??tYb+?{Pk?k^ z?:%?3K?&S?҉Sͬ?r?(Z&Ñ? Y?m?2mf?,AF@?aO;5Y??܃/?~T?4GV~?sq5?g?RD?(@̘?"ĕw?,'?q_?RAEկ?-9 p?#070q?(/b?a]jt?Ju?+Sp?S? |(ђǃ?|\*Ɖ?p;Z?ii?Ժ jSd~m?&pn? 8*?>v?{,}肊?5&\R}?֩=#z?'Q|?m:Yx?xgw??w?:ؗlw?`5!?(XQix?__R#?+?`s Mr?c?~x?Aǘf?s4Bx?6lg?qs*?]2l?7k*W=ԶaT?=\r)]?t>d?,t?f`X|{?&R?o*?ę_?)v4?8fٓl?ZDc?JΉ=u?6qrCq?9\?6sHjd?k?iq0'hc?1i?XPd?a>d?mrc?IaLc?u?TZ?l%tYa? ?8:v?Ժ jc?O0{y?!6x?O?y:Wu?ef?bJ!p?kׄƠc?*6u![YrLV?1е/w?8Վ|?[Ց#wF[DAf [c`$ofGR{aph`t^cju69?M Od?"3cv?uByFn1pLn1?74eov7Où?Bˊ?VΆ?U?T !v?MJ?$pte?=Еt?c> i?9\?$D2V_]e?T5A}r?$EdXy?/4i?N$x?Z H??u6I4`ie?N)]R?<-?p'Py?J4A?y7D?FSwe7W9)+,_>Y1\p>?PW\9߽Ƅh?MۿҤ4Q\mOp?_#Ip?RG^Pi4h?&|'?  RT?]Abp6:R_vj.7h?5;Nq?A,9$p'IBBzݵ|гyz4ՓG@7n1? [syؘq\7vl_IӠh>|i?o[tL'z5|ݮ]n0a{q x?6?X32}??o^l?F^?a2U0*?C3O)`?IӠh>?F̱.Gqhd3O?Vb[?ii?+Qrx?lxz,Cl?֪]ZVJcif.py9?D6.6TH'm x'n?^i_% dAh:;iTdM Od$pteMGmfF?Ni!'*K?n/f1W? jV?dǚq~jtH?k~E}b^(`;X'bdl_ѭw}\*kŸJ?ٕf?J[\3y# ։ˁgltO Cl狿q6|=zAfg;jvs'*TV+~7U?Ѭl򆿃+4ׂދyqXjp?GN;?mrs?o?268Q? ?8:v?|b*3bӹ@U)^6S!gdǚqa[ iF7k*G?m2k?_V?j,ammo}U.tcyW=`^$W@nz"0v%]3f[?ǁWmҥI*sQnr_s66;r]/MqO\W zrfIZvzUgscbqmX]J]2q^ PjDI%rI\x*Ŏơ~W]aq_`V(l6Ntr{ܝ.4wi[mO?HȰ7 C*qaxwdI5o6^F\om IJCt=хm{/:p nUUC.l͆(CULu7OE+Ba? x|{w?q@Hf?sc?ͮ{+Szm?l\lh^^+mjPv0bovLu&"dTi>` OmI̯\?mO? s< $}z]ii\Y"t`y_>Y1\>]itQ [wru-x)t^cwc}Eve3vkyz{KTxwd=!7 B͐*W_)Ǻsq5'y=}qJ Tƿϸ?@6}w+Kt2ZGUDe]ډ3V 3.Ǖm)?&M=Ӟ*33dg}1Y܏yˑ׺8ㆿqŁo.2]3fFog_yGsx@dOYM]wnHJzX?@KWx=- nCԳ ץFgꅿ'b `f&3V qц.񝘅82 [ x'~p>uR0s u\;QiW<.0Uܸ iTd:dwбJiTN 00s ~U.T^/ ۆzVҊo(XİØ= lꏿ~mp]1#gҊ Rr:=Ƃ2ZGUD ;ą9m½J4J?얿1C Γ+f5Eam3⑘G,̳V|Cw~QH[ϐlg-$`1 ffk}Ж #>I>$@M-{ 0mEׅO}!rsy7RHqj'_GWA)V Œ(yuQKs+Ո&+󆿶ac:̗`e`TR'J({K\ʎ`>Y1\4fOXz4ՓG0C׻H¾DIӠh:*3h菿x[鵉ܶQHºȈNGД~PҧUf*Qr^ܽ'Ge`TR'Tqs*g1`U,~{.S ;:Fv^o%;1Zіi:ȚdT8,AF@0[w"쑿 C*q5Բf3J yE/Xni$5`":vyNz1yxρi˚X+aQ+LHU6o } 4J⏿y&1Oqx cܚt["Ǵ6텿qs*FǑ-?2:۠[;P>‘Z}uU9 33zpY͐Ef}~k W)T ktCSvA/N[#sIv*Η!F3(]P9TQQ?6@5_%t ]@J5oA]a"-RdƝ4 +,ӅX1#=_b,/[Xң-쏿.񝈿.5B]7VBvy]anrp ܁:$^p<FENՕJDAc>ʈ @"|ds+N_疿=1XqT:V,!撪&gaO;5zNz7VBwESwSz˔  ~dŠyZ9DܜJ5ctv2ءLo.2✿++X>嘿jׄuOUGN;|\ތGߤi=-k菿OYM]ka9͒[d8xx[长M3#ͰQo&&ݖht3f W@ȓZ'&FW/1闈B͐*W5ctv2IpLT.s/R$_EԘٮ˘>QyܟG 癿!8왿8hHPs\!D4'长GJqinKF 2ș0{vښ-vP3╿xە5\uYm_u䠿i*{wПEf.pyC<qN`:\tYLlwJ.aN&Shwk蟿e@󡿰8|ʿW4-2ܸЄSs_ʼnv>hɓf_ѭהZ2fN擿!6X8IŊLÐ nّ2w ЙK7A``wsY+K1=i7>[il 1Tmx]`d$ PhxJ R OF ^/EJ핿Ԛ蘿)&WMa4%nT gڭe2ϗO 'OrMv1<2p@K7Ak=&Rȑ#u>$DNo+5e OKb:(yu[@ y HX0_^V_]m2,.MԂ$W@1|_=[Y9}U&L!UގpZ'.2Afg;^=1X3#1"QhYdt@픿I mg;%ΑT2Ö{?mTC ÞvkOrpQe/* Ց#2Pl*g\47”nar (N>=2w]JvKp]__R#S! K'UHIRQhN?l󬔿xĬ{Nzړ=KeyUgbX9ȖK⬘GȰ[nK䒿"tuǒ`TR'%VF# b0훿 ƈDev+.ʝ Jʽ2Л ~1[%"}=_\6t?SH93. Y9靿﮳!̠NN|8GhUMrL5;NсcDв`U,~SA~6rݔn2d0 Xka9͒ɒ9wՓL5SςPǑ0(hrY.P1Ώ2 [n“?g͏=,ԚIpPR)vhXK/t=ѕ2WVp;4,}ɖٴR7q s 32tLTq5+- a1kC̰Qoa 'i֖rJ@Lsq5JyM.:V횿| Vj-f||v ^2ᗚۻ}'eqЙ]V$8 Q?v^ mqdٵݒ࠽x蛿?x=6t?Pn{U2Tqㆿk^Y-Xލ4sG˵h !p$А]Sg@-iQa7l[ِO!WYԜ+Nf~qJ[\nŒ^I\߇󕿠Σ⏿f-Qd=0E;%8 p0kI #GGI'L8ӄ'Ǵ6핿~y\TbKqߕ0GQg+S@쓿2W捓¼';ؖXRx!˜ %̴{נ/^(`oěUy䟿k#GU0{?p'v7ne.Qr0ÒsFaXôo(^emSr9>Ztau&*QIJzZ|l6f$`SQ~7%WMVp71$'H[ϐ蜟8S iT*tSq_!:A>锿 p|?{JbI 'o|확Dioɔ0_^}ފEN4}vW:UJdѕT -?ސFNڌUH5VF#W<C3z0q{G V,~SX/E{c:8H,*8Ue l#jgRY-DSGyxe9w^".5#կt>kt=b)E*k⑿z3KrT{F"45weP3аuX32ȍs+ܒۆQ<[{B"4uOU.4i.ȷw ҋdVpv1<=;k]0Bxl;mffffff} yP-?p'yT͎TjjZ_ N+,卿Lk^ ŦB D2ؚQ&E3NCTπฌ:iw neWCRz>Z1 R_vj.fEaQ+L Xr߄2ZGUD3h蟐/혺+(8'0bMeQEg+2C3O) !p$А26t?Ϡ7MbpY͐wcA*DؐHξ tpxS㥋KWxY&ʐ#KXςPьE.Ȗ뒿TH9)7ӅKqUه8h:6U]ؚ/$f8 -li+`uHg`.Ǖ^f(7C8 E~A9w^ZGUDݗMaT !ǖ.c}{_LU?`o;~Vu>IGW#rCpsi?j>"DRܠ[;QB?<$vm2dbZ'.+%{~7[A+,]S }3ٲ|} [ty b0k`yq僿JES(я9yY3(]|ԗz 6u攀 *3"~ (XQikC4jIGYJP >iWV;G+҃2#@a*WYۄXSYvQ pA,_qj?4䊿KU4Oz0)>._x%ɓ⪲Ko.F[ ii3NCTd=ꪐɊaQ+L)Xl !sՓ5D oÒDׅl( 0i?&|'?bLLW?*kqqGVt^cz`X|[tmzW}W{uByENt|ss{/hWp ?Jt닄K4gltO^/1闈*T7s?,6Wt녿5>8:V)=A_j煿lf܆s`eZDUQڦ$\#$ʀ쁆=`2C#]J]eS.3K)q?$EdXy**z4Ry\7VRB^k,amMA ;(G?J({K9@,[A+X zR&5[$F5s7ܘa2U0*S s69|r?Za/d?5Φ#k? !`)qH?סqꫫh$cq[uF~,|uʣaQqG仔dlbDcʤ6P?5|ݮg]n0a{ϣk3 z Wx=Е_vj.7hhK6lŋ!w0b~kQL"ڎ$P29Nz1nض(Y |E~C02ZGUD}ZӼ}(#.ZI+y[a^Cp| Wx*Z^~a턿h+munXrE жX9vb=̶ֈ`մi{P1߄F 2ȉ4LkӈM'-aNz1oܚt{d= lΑCot#,*tx=\r]/Mݗ3u?||.p?q>sv?fʉvEv-u`?Aǘf}EZdTkaf^H0~jxy:WtqncBl`rfBO_% d?ۥ _1`U,~s4 ;xW\uǷw r^T^ PZʼUסz)h}$7{`=[w]S }u~P}:325 ހTގpZRw ZH肿ju [s{|බt1%etʉvR~rT:ww度h^~JE]@hw׹i($;[ i$\#j4눿tѐ(1"QhYSu8J~Tˆ4cфfdv7jt$W@iSulf f?ޭ,Yf?f}?_% ? Wp?rrg&x?.Y?.d?L'Z?t>{?I ?JΉ=u?-h?Nz1S?)[$F_?0i?duIzR&5x?RhL?hY@?duI?-Y1\>Ra?c?Ǚ&l?i?st%]?]pXJ{V0?Ot]t?zpwnp?RW/Q(xEׅOm: vq!s)1 h:;lz7ej]/MqJEmVc#P4ׂ[?+&|?߾|*Q>B͐*whr1ց?x@?噗?St?[A+?)H4"?E| V|?7U?Lx$^~?**z?;2Tt?]~?u݁??/?+)T?}͑u?:fo?n\q?Jyw?1е/W?S"^F?[#qph? ne?~jth?}֤r?W'g(xc?<~Ka?8Jw`?v?fdp?nYk(W?BԳ d?  R?cbqmx?!C?o'q[5t^cZΈ c o{k6\W?6ɏUPybt ;W1ZGUS?؀qm?PqxyfdsC}3`iF ^/?t!V?xM?d?H?_\׈?ם?XP?˵hچ?K?3mJ?c~?a?EEN?g?8KrJ?yȔA?u6?mz?5w|? =E?/J_q?9]?^saw?߼8u?$>ww?TPQ+}?y7t?Cr?~T~Os?gܶq?հs?%xC8y?H'm?|?;vٯ;}?xy:Wr?Z.S|?4}?$ʀ?v|?p;j?ZӼt?EEN?[{C? Wp?%s}B=Զad?&OYMS?-;F?lɪ7inHJzX?|~X?*WYT?9]S?e9 /l? -|?t>hi?# ?cM*?#LQ._?.9?o[tԊ?V(?)h?ْUn?X9 ?${!U?GTn.?39A|??$"? ?A_zs?T1c?EeÚʢ?0xn?-?1'h'}?U+~?K|%?K$wx?W\?+pW?I*SA?u=?Ѭl?axwd?+?h:;|?B_zsр?ςPь?&s|?֩=#z?nHJzX?ŦB w?U+~o?΢w*n?{GzD1?74eo?@wԘp?j?-v2s?B{?wԘ??L?Ss?HP?m3q?Ot ~?-ay?Gǁw?m)b?S{?<,Ԛ}?>x?rK!?J8?^gCA? Lu[?z7ej? -M?{Fw;s?ܛ0 ?س25?]Ա???Rh?.=?1ZG? cΓ?z&?&9 {ڑ?%TpxA?_w?;2T?mP?m2? N?!T2?jP?En?oܚt?]?tbc?9tyƎ?ގ?S㥛Đ?Lo.2?bmR?{v?zVҊo(?oKS8?ܶQ?? ES?k$ ?w~?@wԘ?۽'G?:pG?[{B?2ZGUD?c%YI+?}r?0(h?sp̋?8k?gҊ?T !dž?mɪ7?I2v?'*T?\=?0̕A?>]ݱ&?CR?|_??z?C8 ?zW|?k?i?Pݜ?Xm?[wT?hwa?ȷw қ?0 GĔ?S?P5z5@i?ZGUDݗ?"3c?Sݘ?@իȘ?,g*?'dm?f?Aȓk?1 n?yt͔??{?ȭI%?ӄ'c|?8:V)=?'-\Va?3Mg'?Q(1?.5#?i>Ȓ?m2d?MӀA?{Cr?2|?&:?oI~? ?(я?/fKV?ި?w;S輖?R h?1t?7/N|?ĬC?M-[닄?B:h?{eު?Ss?]¡x?Md7?W\?Db?%R?<?'|?/xŞ?z3jJ?A|`ǟ?#?BD?2=a?=e5]Ot?sv?\(\?3O?n/i? .Л?6:8?Q?Ng\W?>w׹?d~?B=}?sf?*4f? ?]lȟ?R%ʞ?N`?Aw鱝?rǡ? (zc?{\&?oh?Z(?#)a?wEИ?/' ?('$?',񀲙?l<+?Rҗ?-σv?I-?z8n?pxADjڕ?[Ye?@j'?[[%X?dXG?(??k~?Ēr9?[DA?s"k?$ӡn?n?p$?)ţ? 릤? X?5BX%?'? ?N?:TS?N?t^cޢ?(IӠ?:#J{/?-T?N(D!T???ݳѢ?5[?ض(A&?+Xڠ?o+6+?ӂ}?j%!?>ɋL?h+m?viá?5?}"O?wԘ?GP [?Q¢?O}?9cɡ?פ?R@?-ͭVc?g)?[v?M Jʝ?tbc?zNz?iܟ? q75?9#J{?7RHڝ? fba?q[u?J!K?o^?-σv?Qd?? rߩ?ډ?kw#?y7RH?0?wЧ?]?R{m?u~k?+6+1?Dk?8س?FSwe?W?pQe?9`W?T?k i?%u?i ?SVD? yrM?‡-y?9}=_?+إ?CSvA]?;"h?K>?‚?[tYL?׆q&?o[tԢ??k~?>W[?0?66;R?h^? ?XVĥ?.񝘥?bԵ>?IZ֧? ˟o ?}[~?yƾd?TTJ?7{5?v5yʢ?9!?=E~?#tub?v?T1?l?Ѫt?W[잤?0?SYvQ?>V۠?o ?!3?s-Z?Ēr9?0?:!y?v1ͬ?s ? ?8d?v?<|?p]1#?=j??&M?ИI >? 1^?J" ˪?m3?_ ?jGq:?Vyէ?0?jܛ0Ѩ??Ss?m4?ҌEɨ?[vN?8ߡ(? Tƿϸ?kC4?T;Ԗ:?? ??tѐ(?U-?w~~?aMe?>Ӟs?YJP?m?V? 8@d?/?Md?s/٨?P?Os'ا?-s,?);?=බ?4?9]?#o?n/?&L1A??ht?G 6u?TYh?iN^d?̔?Yw'?_?׺?eqб?A B?X0_^?ןN?*?P}:3?:ؗl?x@?ٳ25 ?XVĭ?JZ ?4?[y?%VF#?427߈?:?߮?F~໭?R %S;?H?wԘ?EИIԫ?#? !?fv?<$?۪?lЗ\??Ky ?^SHީ?*8 ? H`??=xҲ? @t??N?SYh?.;1Ŵ? :p?h>n?7-?qo~D?<ڨN?#tub?R^+?YB?$?v4E?W 3?p5?XU/d?J&v?!k?Q(1?o`rȲ?Yڊ?ʈ @t?'p?k?LD?>?Q293L?X7?f}?mu9% &?l? QI?L*???@?ϲ?W"PH?.u?I?u?Gg?z?Y?}[~?}W[?@0`U?$??;bF?U?nKX?D5%Y?^+$ζ?)D/?i4?qo~D?'?KA?"1A ?qo~D?FR^?Y.Ķ?#f?ٶ?2W?7?Oq?N?EV ? 34?4}??TPQ+?ڑ;(?q9?hU?U6?ZH?BD?U?\6:秴?'_[??e##Դ?w/ݴ?cc^G?zۡa?=M?Ye0F$?464a?X?~įXE?U?W:?kzPP?c?M1AG?=)?ߣz?UP׾?sa?<,?SW>?fS9?i? 8?g?YX?Ye0F$?JCB?'_? 'Lʾ?|E^Ӄ?!Y?PLۿ?CULp?6Yѽ?NC?m3?YX?Za?*SAѾ?5($տ?Έ ?̿?v$p?Ŀ?Sg?ⱟR$?)Bv?,E|'?rj?M?v[?+z?K^I?Gŧ?0AC?pi?¿3?̘5ξ?yUg?w悔?g?RD?<ۣ7G?%s,?:э?'b?%?;ۤ?g?l\?1~7? S"? ?ӅX?Y9}?A'??8 毐?0Qd?AaP?tDKK??V W@?/"j?]=1?&fe?aUN?b?j>"D?BsF?YN?ݒ?zю?m]? $ ??e?D.8?k?8ߡ(?ܝ.?cAaP?'?dpu?:zj?"tu?(&2s?׿3g}?&:??\-e?M?KK1?@߾? 8Kr?`?U?:zj?8+F?*)?&)?G`?;l"3?#ݯ?/g?3SZK?gltO?32])?9db?uubT?c^'?^ D??d6:?f\S ?XG? a*?C?]=1?'8'?_cD?p|?3h?+S?5>?Crc?3d?8+?a7l[?q?#0??NP? +j?mE?C3?AaP?2FY?Kq?6[ ?>>tA}? j?J/?Z![?ډ?F\K?*g\?E>?SV?Ve?gRy?˞6?Z-DJ?9? .?DJy?._x%?ù?%/? '?@j?mM ?&\R?X4a?=U? x|?5\u?K`R??<+?ɪ7U? ?[C??9`?F@?x҆?Eӻx??M#~?cw?q?*P? 2t?zk`?]ؚ?B_+?st%?f16?ᔹ???L?_?@?p/?h? eO?U?K."?R4?pӝ?i?x*O?}?x-;??i?d?J\?cxg?K46?_?@?A]P?r?Gq?b*? 9?4(?F<͌?q?? ]V?˿W?LO?n?Fx $?Is ?_Z??N0?TPQ+??Ih˹W? #?Rb?K?@?Mc{-?LRb?elf?+i7>?[.?nKS?n?>nKS?!k^?O=බ?P?xWL?<2VW?RT?U@?T8T?Av?E?"2?vݑ?'?/^|?)?X"t?Zd;?Gq::?+0du?̓k d?ԗ?W[?BX?m?cC7?Dв?;_?:VS?^?2Y?|.PR?>^?^f(?RDU?8? __R#?1Xq?w+KtY?+]?ڨN?lp?,p{?4(? KY1\?r?5K??-s?:8؛?v ?ӹ?u?j??aw}?ӹ?(P?z}?%]3f?ɐ?KU?ooD?]k?V?{؜g?!<8b-?Н`un?.4i?pz?`?D?J}Yک??x?|R?.!u?(?9&L?!q4G??c[?D?4`i?\?U{??Qٰ?h8?VJc?VIddY?#F?{9y??;bF?p|%?`\:?_=?RE*k?r/0+?\z?z9cx?yY? 'L?uR?=??B:?j.7?؛?&Ҩ?eO7?W_]?&|?%?ΤM?Vc#?'c|?kg{?0?\U]?|y?ԛQU?pΈ?S:X?.5B?A,9$?J)?ƿϸp ?.R?ʦ\]?ZJP?" l? /?׻?3ou?D?vOjM?'??"[A?/:?^}?Rr/?%?U?H<+?oe1?R?9EGr?G?}u?2?d? Y9?6Nx ?J̳V?ZӼ?jѯ?^o%;?3g}1?s|8?B=?٬\m?úX?mP?V%}?J?!'*?q6?K;??@Qٰ?~~d?_w?شR?p +TT?L?*?o+6?1{O?9EGr?/?U3Y?@ R?T-?]p?Sr3?,A?U]o?5ZP?8?c~x?eVp;4?F-t%?{ԗ?b48?w? ?^?<- ?Ry=?K1=??T1?>s? E?]R?L?A?L:/?T=ϟ?B?so?~d?zܷ?:u<?__R#?W:?G5?/0 ?z2Q?`6?ΎTE?_~?WXp??xԘsI?zW?8ܘ?n1?74e?B=?x?O?9 ? ?AشR?ᶶT?ho}Xo?Pn?gdS?N$jf?`s M?uRz?eS.?2,? ES?C3O)?F?yؘ??܂?Dlp?4X_?s?J?xg?I ?N\W ?E?:?tx?YN?+?XV?#ŋ?:Yjh?3l?cϞ?X;?p?#ظ]?)Pj/?O7P?;sq?=~o? [t?~?>U?)բ?Ii6`?(Hlw?\R?:vٯ?/"?¼Ǚ?$?HZ?7U?IQ,?( 5 I?'??=x?{?/g+?|R?]t?~B?6??eRC ?U6?w?@ R?oH?fHū?U?X?>ϟ6?g׽ ?/ ?}V)?>z}?;?i㈵?PS"?PS?¿3?H8?WuV?8~?+:?9D?4f?St?=yX?8FG?s 34? x|{?XvMH?l|&i?\>?I'L5?mnLO? cn?CSvA]?m?<3p?ԛQUr?b?/HM? p?K|%?s"k?r&"?jTQ?ٱ?%:,?St$?Y,E?։K?[?i>H?ᔹy?#+ ?:?Z?i?HȰ7?s??st%?$#gaO?ĘR?:̗?M!u;?[?Z)r#??mXSY?$xC? ?fy?%?V+~?Ln?[wT?!8?jj?>?j%?&p?ۂ%??a+e?m3?t.'?8v? 0?lu?t?'? YK? O?w [?@r?P,?5e?.:Yj?=;k?Y"g?+?GTn??il?:+??¼Ǚ&?it3?L1A?e?m? LnY?Y |E7?T?*t.?k%t?46<=?UlC?QhY?vX?b=?K?a??e73?)??{1*?Kwِ? uX?U;?V?*?Ɋ?\Ϝ?++MJ?+TTJ?\A?O0?0*?0?^-Ɂ?߾3?S4-?Sqd?/?,y?.?\-?p?2rC?k=?$D?TnV?g8 ? Q?|\*?3NCT?LQԙ?£#?E^Ӄ?}V?K?O!WY? ܺ'?>4?G?e ?BԳ ? Rr?E?_]?:*?ڥ ?6c?anr?[u?߃.?c?Pō[?04?Ƒ?AѪ?J8?bA?[>??;ؖ?uv28J?LP÷?:3P?@?X zR&5?||Bv?Rr?Д?`V(?j1?8?&k?0?|x ?qo~C?йҔ?SQG?wE?`R|?>?V?-1?E2?$ nk ?T?~p>uR?4iSu?ׁ?LON?;]X?L[.?0{v? R?(ϼ?w1t?c ?}?<|k?Tl?I U?zI|?"89?~NA~?ND?-v?Z}uU?g?!u;?Ա?(hr1?DKOK?f?lY.?|@ ?'>?vOj? 5?X)k{?0ʈ?^emS<.?-[닄?U?}?6u?`:?I?k1?e"?2ZGU?IfͶ?Iط?=tZ?}r ?_EF$?C+j?y<-?p?ut\?k?]_?Gu:?|a2U0*?ORB?K=?0=%??ճ q?F6??+,8?obHN&?uB?^?9?GV~?:T?)Wx ?}=_\?T?uR?ubTt?$z?h@5?iG5w?}8H?eP3?o,?D(b?\$???x?JA4F?b '?J" ?eS.?wNyt?{V? %qV?Li-?~T~O?61?X? }s?xx?DJ?j0 G?H/j?e##?h\W?ݒ+?w?o`r?ΪV,?"??&y?:<Ӹ?4-29?镲 1?7¤? O?7h? ]ޜ?Jp?qy?3?ka9?q?߽?y ?4c?Pj?O?]ݱ&?dV0??wݗ3?PI5?*sX? rar?}"O?P&?JE??ri+?\b?5؀H?6$?J ,)C?#)O?_"?&S?69|??2W'??ʼUס?JU?,H3M? 8??]7V?unڌ?T?`s ?0?S?&OYM? ?7q/?J?I?G'?@,?YR>?UG)?┹Ft?LS?b W?Q<}?}Y?/1b?V+~?2At?8v?I? >b?}9?>&??L4ǡ?H}x?EH?(]W?'?g*?$c?[DA?0J_Q?Ø?HH?f?{?¿3?=Զa? $}ZE?5 ?kծ ?l^Y-0?ؼZ ? ? =b?pn?# ?? W?z9cx?h-? [t?Ԛh??xZ?F^Ă?L[??}9?=#?Le%?PB?3bb?*D?>\r)?c섗?rh?g\W?yS?AF?N>=? ?@,9?q?7k?3`?w~?8~?/3l?Z8Z?oꐛ?˶Ӗ?&Sz?[*M?>B͐* ?j?uS ?s|8??V6o\? F?6?cFx{?f?HLP7?.Q5p?o?s;?%?D$?]`7l?{.S?PN?S4-?}"O?F=?Qe?_5j?3J&?"??|\?k^Y?Tn?xak?2F?S?IB=?<2T?nض(s?e?_=[?Mu?2?e@?nUfJ??fj! ? rh?cO?OY3?Z.?C?C?LS8K?%s}?o+6[?4Y?o}U.?$;?W\?$}8?ȑ?@+0d5?MaB? ˟oK?268?od?؀q,?3NCT?^?0{?u ?ol?-y<-?v?Mjh?2?'/2?Q/?H0?ܴg?ĘR?enݳ?\8? W@U?J}Yک?+,?>V[?7m? =@?q{c?>WXp??7@?vp?{P?ْU?3NCT?t!V?(?ߠ??&? !W?#c?y8?c?zܷZg?%:l?W[?) 0?\&?ZF=?DP5z5?i?7(?`?g?ԱJ??b=9?~R8?+?%WM?1~٭?,y?b? a??iܛ_?XL?=yX?]0?A ]?FR?[?< n?鲘?N@C?#G:?9)9'v?D!T?s9>Z\?`d?c${?$@X?hzL?W\?}z?TH9?ْUQ?7ne?Fžvx?W\?p|%A?l?s/o?i?_zsѐ?PR`L?2?乾?K %vm/?ʓ?b?Ee??d=j?Wzm6V?AeK?*?t.??F!??S= ?E@Jl?}!8.#??t3?RDU?ĭh?d;?XL>?b*3? 4?+َ?:?,H3M?R8?g?C.,?fe7?KXc'?AH0?F~?(`;?b.n?-'?b ?"V?N;P?V$?=Е?s ?!X4?H0[?Վu?9@0G?q? Y2?CԷ?o.2?7Ӆ?Ց?rN}?!WY?ՕAb{?ur7?, ׳?,?*3u?<Y?wӂ?߼8? m9b?@ش? 7k0? 6ק?Q,? \?, ?_=[?k :!t?4w?dVA? %S;?4f?R?5s?= a?1?@? ???;b?&+3?\QJ?UZ?Amߣ>?EV?4"1l?V e?g?R?.9?9:Z?I??t`9B2?t!V?Gq?8*?Av"?'_M?6?O?[G?r۾G=??<2V?Le%?c=y?=R?Eur?э? ?xܙ F?1]?ݮH?9~4bf?- PS?s69|R?+O?W?&R?o?J({?1ZQ?6t?P?'-\V?5??(r? *?ǀ?\o?i ? ES?D.8??$~.? ?P,?[5?i'?#]J?t(CULe? D )?.[?u? ?[?OYM?@1d?zW?ʿW?M?!g?ݒk?:\=E?G[(?%ZxZ>?^??QL3_?: d?;5 u?e1x?ԲH?G6Ws?^N?s?pqt?~p>uR?^-?>??a'V?mr?|Hߠ?jȧ?7?iq0'?g?26t?z蹅?M?qS?bc^G?O!W?E?!Ky?'_[?}?!rz~?q!F ?W??d`TR?w?C"?ha?4??^?Dm?2]??LnYk?'0֭?0 ?aO;5?i?du?1PN?֪?Z?('UH9?Um77?m?"k^Y?YKi?iLw?I+?~x?je?}R?7L4H?K?GJq?.S?d:tzލ?j?5ڋ?:\=?eUj?w `?ฌ?^ ?vLݕ?ihs?&P6?"?~m?IZ#?9 {?~QB?4yT?*Ż?io?G0}a?$\?ܚt?l\.?d?XU/$?5ctv?SQ?Wt?oܚ?4l\?ߩ{??]pXZ?8h>:?T:Xp?`~?̶ֈ`?#?k'JB"?Œr9~?L?~Td?/h?ep:?͑_?h@5?kׄ ?~?bA\?a?)U?F̱k?e73p?:8?ǀ?=+??a?n?"[? I?[| ??k~?'?~T?dyW=`^?tYu?Ԟsb?]y?J?~^??y0H?=U?Oϻ?`YiR z?qj?L/1?r1q?iV?qXQ?WXp?e?W=`2?qS?c .?G 6u^?N1-?Թ?kt}?(2?4aX?-B4?πz3?0{v?+?e73p?[?CQO?8k}?JA4?J&vi?Ɖvg?F?6\??:<8?N S[j?&2s?'?&r=?F жu?p??f|?3ou?kCŸ?v?CX?4(p?[%X?nߣz?Xۼ?ظ]?eS?^?ׁ??G0}?@9w?7n1?7?Swe?հ?[[x^*?Aȓ?V?|y?8aA?;?M/?C 8?W zR??=x?$D?;s?k??ܴ? ?zM?M?캷"?!o?yJxB?/oӥ?Oe?s?Ϲ?jjZ_?z"n?~?2At?1˞v?ȑ΀?g?dȱ?:!y? /? ?X$?U/d?dAe?ǘ?o+6+?hx?6?@H0A?>?j0 ?~}?^}t?H]ۛ?҈?|a2?$>w}?m}?ۿҤ?D?5\!?'@1@y;(@kqK oSH?莒c?|s0n/sC@2῅ѿ-FV!]?1_)_4X? 俛QE{%?>|Xܿf¦} w޿x??M7dӿ ZR޸r~<=rmln?G%\7?Aã?UH?x>gjo0{G˯>7nL3@mp?ڔ-n_R?egl@ ce?7#迡-?8x?$wT>^?I )?X v?Ld?]Ay߿D-?bYMa?JQ9A)qr @{'AikUˍ?K u̜~?p@ w܇ qiV µ\~tgF#+?@'V𿰄ɣ?$,X/?b p!"x~0٬q75s?W̶翢?\-H?rB*m޿eL!ҿvZ6j@gw?] =b=?|Mc?p, =?4YԄ7i?Ϊ@;#򿒬{ސH@ Ҿ? ,KɽR?Jbත?|sqN(nu?dž:7⿹HJ]?oJmC 翨-ږ?F=?^)qLz.+_=࿄i?9U?3O#ݿn}?1 !;P{迵ʞ?v6ڗ?ӎ/o&tVȏ^B濖 h(.@aq!?<(ni[r{`$ِg@@_?b<2@>; Rd(@y PY҃_.T߄f6־?Vkѿ0,3P0?-ܿM 5?xYL?ȓ+Z{ڿs$?|AL%?,[qv'?Tc׍ui?V?0GT^)6?Xl`"5R?89q #˿C俀k1e?e" 왲P:׿NB>x@ λ?0R?ԑ.0? ֟c <5Eկ~LUwB ?Ki=^@&?䊂 ?:g?=,. B?$kD)?8&Qt?I@Z?h@]1׿⡍ʬ@A.oC4Bk@$+r7et |{<+?G@*b @a (, uzGi4?Ь[ֿ ͓1i ʠ?*BBV˴8v̞ъ oRvZ?@RO @= @_RR@k<j?vBmO ?цu@vjVÍ}9@-@}ĿAWW?4ߏ߿8Oېx Z@ @Q dnIvbKWI?jrY> ?Z_hmT ?Ď(O?ö,~B~ @mwҿs '@i8`au?ެD?u֯^e@a*|S@GWUU@(?\!{^?o?o@y TC?H@lk0u?c9 K-rҿm20>*iUx#f)b?Z3"摺qaV?8Kز?Ei #?n???n9Jx5@+v?]_t( }N?;3v6`ܿȄFd5?nEŔ^HЩؙ @dᝧ@Jɲ?}%?gn?ćԶKʿNn+%?35?1"?ԋMǿ}v[`? 7Nj}?#K?B0stdVyV@5Sݿ??<@*?#>? 7?b?T`0%|4οʗ==ڔZ?b; ӿh@ GETVy9?v ؗ@ N!ct\X?)*88?T?3tnw[?;(.ڿ|@?*M?&+R׿Hy?`፹?w/wd?3?("m4hg5ȹ?a;NC?HUTA?9SѿX4̃Kz?SSܿo?-?HSn?~X?ߝJ?F`>(ֿ &?sJ&X;{Jbz 3w?̻8 쿸??nt9Чr?t?H}#u[#wɿ?2PK=?76?}H[?Vexקx@[|2Q]c?`288Z?6.??NۿE#Hǿ['*%?kfLP??&?^aWTڃ?,1Q?5mWkU}j7?EIοY>y<%?tJp8z%lo?,*?'-C@LUwys?l?9epdԿ [?J*7,Vt1?|묪ÿH忤${ ?w?mJ?ar@;޼v8ڑ YD/s?4P.&RJ5M(,Ao?/f?VٿA9_?kH?>S+|N>v"o?~@?Lg?͵)?XOh~\,?&8p#Ț?Pc2D>QZ ?K?15)VLf>S e4.FL-*Wre@*71A?-Z2? yT?H[ \>靑?Vy? }#B?yܿ8%)ܿ/?*@[sC?6h㿠5d@( ܿ?(ٙϿ27#:m?P(Y?c0dԘF8A?ÔS?kv]J#`UCʿL.XPN?D?hp[dPV?].".4$,Jou񿠸?%&Ua? +9pR??cm?$©\.?tfQV?rt@ٜKSX??-7b?;?'@?X3xEۿ񻝗FCϜT2ſ,ǘ̿1C?XhwпwI.ώ)?w@1E?>@v#0Ul@S|ѿ*&?nO@Y?%@,Ъt?+K=(|u?rр?9yX.9ưIk$@n?)I?a?O ǻ{ۿ@d?X 6t(?:?̤&?c|QE? ?X??J?*22["vc9q1Z{n k?íǤ?d 7;ټ?|? Fi?lB̿IO5JK2,?{؜L?=,a, S@'v.ۿ(D`kܹ`)kD%3zD0?\}oY̿J]OhEV?zM?+.7ٿq(Rc?F,gj`ؿzh<?ys\EտwYoп@n޶VT`?BY?(v ?0jr)?moEHL?4?24>?x4bٿ2`X󿙍cӣ迈$;?DLs~ѿ` ?+"?FV ދE۫? !p|ԿxM<. y3^?È?QF?MY8)?čx*?NzQۿ?MiV$&Yῆ}c^?^ڿxox?X&`οySl?пA|u*?H.7?<Ż*V'G o^K urIG$? f @{,6~'?S??^1?za?.W-5ǿ,:[?6uFA?@Fi!kP?}} n??(ZzB?GlD&uH,?aʩP %bGHaf5?+ ?q@bYx@6`῿*Bs?$9rݿTDٌ?8?ɮN BT?-;.SRԿL>}@P I?R9LUm? ~ҧ?Q}Eo?ܿ6NMk}5T!p _s?X/?d/?bvo?/#_mVt翇ѿ֞{O?"P?2{kAS@ @}"뿛NNu%?iQ?R-j%? 񕉝N?"%;@f`?w0s @>ga9p޿?GZ{X ˄;ݿXY֤vT @CXՉs?pG(?)p@P;;?\Jt ?jQԭ8 20?w]p?[@o/߿YTg?LKf?5O?gf7ZP @ϓ*֘Vhx?4b㿛MxI2?a!Ɂs?ˍ!?0v$鿮[?*B8)Z;Z_@mI:?ym?p?wi, %h@?GI1鿬 󿋊L+?Rl2D?Ŧ?statistics-release-1.9.2/inst/datasets/weather.mat000066400000000000000000000010671524624707500223020ustar00rootroot00000000000000Octave-1-Lyear1matrixI@J@O@O@J@J@I@J@M@O@M@L@O@R@Q@U@T@Q@@Q@R@@R@H@G@I@N@M@N@O@N@Q@year2matrixK@J@P@P@L@J@K@K@O@P@M@M@P@S@R@U@T@P@R@T@R@K@I@J@O@O@O@R@N@P@statistics-release-1.9.2/inst/demos/000077500000000000000000000000001524624707500174335ustar00rootroot00000000000000statistics-release-1.9.2/inst/demos/ClassificationGAM.edge000066400000000000000000000012131524624707500235360ustar00rootroot00000000000000%!demo %! ## 1. The edge is the mean margin over the data %! %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds, :); %! Y = species(inds); %! mdl = fitcgam (X, Y); %! %! [edge(mdl, X, Y), mean(margin (mdl, X, Y))] %!demo %! ## 2. Weights let some observations count for more %! %! load fisheriris %! inds = ! strcmp (species, 'setosa'); %! X = meas(inds, :); %! Y = species(inds); %! mdl = fitcgam (X, Y, 'NumTreesPerPredictor', 20); %! %! ## Weighting the second class three times as heavily moves the mean %! w = ones (rows (X), 1); %! w(strcmp (Y, 'virginica')) = 3; %! [edge(mdl, X, Y), edge(mdl, X, Y, 'Weights', w)] statistics-release-1.9.2/inst/demos/ClassificationGAM.loss000066400000000000000000000023351524624707500236200ustar00rootroot00000000000000%!demo %! ## 1. The proportion of observations the model gets wrong %! %! load fisheriris %! inds = ! strcmp (species, 'setosa'); %! X = meas(inds, :); %! Y = species(inds); %! mdl = fitcgam (X, Y); %! %! ## classiferror counts mistakes; mincost, the default, charges what the %! ## least costly assignment costs given the true class %! [loss(mdl, X, Y, 'LossFun', 'classiferror'), loss(mdl, X, Y)] %!demo %! ## 2. The losses differ in how hard they punish an uncertain answer %! %! load fisheriris %! inds = ! strcmp (species, 'setosa'); %! X = meas(inds, :); %! Y = species(inds); %! mdl = fitcgam (X, Y, 'NumTreesPerPredictor', 20); %! %! ## Each is a function of the same margins, so they rank models alike but %! ## not on the same scale %! names = {'classiferror', 'mincost', 'hinge', 'quadratic', 'logit'}; %! for i = 1:numel (names) %! printf ("%-13s %.4f\n", names{i}, loss (mdl, X, Y, 'LossFun', names{i})); %! endfor %!demo %! ## 3. resubLoss is the loss on the data the model was fitted on %! %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds, :); %! Y = species(inds); %! mdl = fitcgam (X, Y); %! %! ## The same number, without handing the training data back in %! [resubLoss(mdl), loss(mdl, X, Y)] statistics-release-1.9.2/inst/demos/ClassificationGAM.margin000066400000000000000000000012531524624707500241130ustar00rootroot00000000000000%!demo %! ## 1. The margin says how confidently each observation is classified %! %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds, :); %! Y = species(inds); %! mdl = fitcgam (X, Y); %! %! ## Positive wherever the model is right, and larger the more sure it is %! m = margin (mdl, X, Y); %! [min(m), median(m), max(m)] %!demo %! ## 2. A margin turns negative where the model is wrong %! %! load fisheriris %! inds = ! strcmp (species, 'setosa'); %! X = meas(inds, :); %! Y = species(inds); %! mdl = fitcgam (X, Y, 'NumTreesPerPredictor', 20); %! %! ## Count the observations the model places on the wrong side %! m = margin (mdl, X, Y); %! sum (m < 0) statistics-release-1.9.2/inst/demos/ClassificationNeuralNetwork.compact000066400000000000000000000010371524624707500264600ustar00rootroot00000000000000%!demo %! ## 1. Dropping the training data from a fitted model %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 200); %! %! ## The compact model keeps what it needs to predict and nothing else %! cmdl = compact (mdl) %!demo %! ## 2. It predicts exactly as the full model does %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 200); %! cmdl = compact (mdl); %! %! ## Same weights, so the same answers %! xc = [min(meas); mean(meas); max(meas)]; %! isequal (predict (mdl, xc), predict (cmdl, xc)) statistics-release-1.9.2/inst/demos/ClassificationNeuralNetwork.crossval000066400000000000000000000024601524624707500266670ustar00rootroot00000000000000%!demo %! ## 1. A five-fold cross-validated model %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 200); %! %! ## Each fold holds out a fifth of the data and refits on the rest %! cv = crossval (mdl, 'KFold', 5) %!demo %! ## 2. What the held-out data costs, fold by fold %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 200); %! cv = crossval (mdl, 'KFold', 5); %! %! ## Spread across the folds says how much the estimate itself can be %! ## trusted; a wide spread means five folds were not enough %! L = zeros (1, 5); %! for k = 1:5 %! L(k) = kfoldLoss (cv, 'Folds', k, 'LossFun', 'classiferror'); %! endfor %! bar (L, 'facecolor', [0.6 0.4 0.6], 'edgecolor', 'none'); %! hold on; %! plot ([0.5, 5.5], [1, 1] * mean (L), 'r-', 'linewidth', 1.5); %! hold off; %! xlabel ('Fold'); %! ylabel ('Misclassification rate'); %! title ('Held-out error of each fold, and their mean'); %!demo %! ## 3. Holding out a single observation at a time %! %! load fisheriris %! keep = [1:15, 51:65, 101:115]; %! mdl = fitcnet (meas(keep,:), species(keep), 'IterationLimit', 100); %! %! ## Leaveout fits as many models as there are observations, so it is the %! ## most expensive choice and the least biased %! cv = crossval (mdl, 'Leaveout', 'on'); %! kfoldLoss (cv, 'LossFun', 'classiferror') statistics-release-1.9.2/inst/demos/ClassificationNeuralNetwork.edge000066400000000000000000000014611524624707500257370ustar00rootroot00000000000000%!demo %! ## 1. The mean margin over a data set %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 150); %! %! ## edge is the average of what margin returns %! [edge(mdl, meas, species), mean(margin (mdl, meas, species))] %!demo %! ## 2. Weighting the observations %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 150); %! %! ## Weighting one species only gives the edge over that species alone. %! ## The weights are normalised before they are applied %! classes = unique (species); %! e = zeros (1, 3); %! for k = 1:3 %! e(k) = edge (mdl, meas, species, 'Weights', strcmp (species, classes{k})); %! endfor %! bar ([edge(mdl, meas, species), e]); %! set (gca, 'xticklabel', {'all', classes{:}}); %! ylabel ('Edge'); %! title ('Edge overall and within each species'); statistics-release-1.9.2/inst/demos/ClassificationNeuralNetwork.loss000066400000000000000000000027321524624707500260150ustar00rootroot00000000000000%!demo %! ## 1. The proportion of observations the model gets wrong %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 150); %! %! ## classiferror counts mistakes; mincost, the default, charges what the %! ## least costly assignment costs given the true class %! [loss(mdl, meas, species, 'LossFun', 'classiferror'), ... %! loss(mdl, meas, species)] %!demo %! ## 2. The losses differ in how hard they punish an uncertain answer %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 150); %! %! ## Every one is a function of the same margins, so they rank models alike %! ## but not on the same scale %! names = {'classiferror', 'mincost', 'hinge', 'quadratic', ... %! 'binodeviance', 'logit', 'exponential'}; %! L = cellfun (@(f) loss (mdl, meas, species, 'LossFun', f), names); %! barh (L, 'facecolor', [0.4 0.6 0.4], 'edgecolor', 'none'); %! set (gca, 'yticklabel', names); %! xlabel ('Loss'); %! title ('The same fit under each loss function'); %!demo %! ## 3. Watching the loss fall as training proceeds %! %! load fisheriris %! iters = [5, 10, 25, 50, 100, 200, 400]; %! L = zeros (size (iters)); %! for k = 1:numel (iters) %! mdl = fitcnet (meas, species, 'IterationLimit', iters(k)); %! L(k) = loss (mdl, meas, species, 'LossFun', 'classiferror'); %! endfor %! semilogx (iters, L, 'o-', 'linewidth', 1.5); %! xlabel ('Iteration limit'); %! ylabel ('Misclassification rate'); %! title ('Training longer buys accuracy, up to a point'); statistics-release-1.9.2/inst/demos/ClassificationNeuralNetwork.margin000066400000000000000000000016641524624707500263150ustar00rootroot00000000000000%!demo %! ## 1. How far each observation sits from being misclassified %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 150); %! %! ## The score of the true class less the best score among the others %! m = margin (mdl, meas, species); %! table (min (m), median (m), max (m), 'VariableNames', ... %! {'Smallest', 'Median', 'Largest'}) %!demo %! ## 2. A negative margin marks a misclassified observation %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 150); %! m = sort (margin (mdl, meas, species)); %! %! ## Sorting shows how much of the data the model is confident about, and %! ## how far below zero the mistakes fall %! bar (m, 'facecolor', [0.3 0.5 0.8], 'edgecolor', 'none'); %! hold on; %! plot ([1, numel(m)], [0, 0], 'r-', 'linewidth', 1.5); %! hold off; %! xlabel ('Observation, sorted by margin'); %! ylabel ('Margin'); %! title ('Margins below zero are the misclassified rows'); statistics-release-1.9.2/inst/demos/ClassificationNeuralNetwork.predict000066400000000000000000000027601524624707500264700ustar00rootroot00000000000000%!demo %! ## 1. Labels and scores for new observations %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 150); %! %! ## One row per observation asked about %! xc = [min(meas); mean(meas); max(meas)]; %! [label, score] = predict (mdl, xc); %! table (label, score(:,1), score(:,2), score(:,3), 'VariableNames', ... %! {'Label', 'setosa', 'versicolor', 'virginica'}) %!demo %! ## 2. The scores are a posterior, so each row sums to one %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 150); %! [~, score] = predict (mdl, meas(1:8,:)); %! %! ## The output layer is softmax by default, which is what makes this hold %! bar (score, 'stacked'); %! xlabel ('Observation'); %! ylabel ('Posterior probability'); %! title ('Softmax scores stack to one'); %! legend ({'setosa', 'versicolor', 'virginica'}, 'location', 'eastoutside'); %!demo %! ## 3. What the classifier has learned, over two predictors %! %! load fisheriris %! X = meas(:,3:4); %! mdl = fitcnet (X, species, 'IterationLimit', 300); %! %! ## Ask the model about a grid, and paint each point by its answer %! [gx, gy] = meshgrid (linspace (0.5, 7.5, 120), linspace (0, 3, 120)); %! [~, ~, region] = unique (predict (mdl, [gx(:), gy(:)])); %! contourf (gx, gy, reshape (region, size (gx)), [1 2 3]); %! colormap (summer); %! hold on; %! gscatter (X(:,1), X(:,2), species, 'krb', 'ox+'); %! hold off; %! xlabel ('Petal length'); %! ylabel ('Petal width'); %! title ('Regions the network assigns to each species'); statistics-release-1.9.2/inst/demos/ClassificationNeuralNetwork.resubEdge000066400000000000000000000012141524624707500267340ustar00rootroot00000000000000%!demo %! ## 1. The mean training margin %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 200); %! %! ## resubEdge is the weighted mean of what resubMargin returns %! [resubEdge(mdl), mean(resubMargin (mdl))] %!demo %! ## 2. The edge grows as the fit sharpens %! %! load fisheriris %! iters = [5, 10, 25, 50, 100, 200, 400]; %! e = zeros (size (iters)); %! for k = 1:numel (iters) %! e(k) = resubEdge (fitcnet (meas, species, 'IterationLimit', iters(k))); %! endfor %! semilogx (iters, e, 'o-', 'linewidth', 1.5); %! xlabel ('Iteration limit'); %! ylabel ('Resubstitution edge'); %! title ('Confidence rises with training'); statistics-release-1.9.2/inst/demos/ClassificationNeuralNetwork.resubLoss000066400000000000000000000015161524624707500270150ustar00rootroot00000000000000%!demo %! ## 1. Loss on the training data %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 200); %! %! ## resubLoss is loss over the rows the model was fitted on, weighted by %! ## the observation weights the model carries %! [resubLoss(mdl), loss(mdl, meas, species, 'Weights', mdl.W)] %!demo %! ## 2. Resubstitution flatters a model, cross-validation does not %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 300); %! cv = crossval (mdl, 'KFold', 5); %! %! ## The gap between the two is what the model gained by seeing the answers %! bar ([resubLoss(mdl, 'LossFun', 'classiferror'), ... %! kfoldLoss(cv, 'LossFun', 'classiferror')]); %! set (gca, 'xticklabel', {'resubstitution', '5-fold'}); %! ylabel ('Misclassification rate'); %! title ('Training error against cross-validated error'); statistics-release-1.9.2/inst/demos/ClassificationNeuralNetwork.resubMargin000066400000000000000000000012511524624707500273060ustar00rootroot00000000000000%!demo %! ## 1. Margins on the training data %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 200); %! %! ## Same as margin called with the training predictors and labels %! m = resubMargin (mdl); %! table (sum (m > 0), sum (m <= 0), 'VariableNames', {'Correct', 'Wrong'}) %!demo %! ## 2. Where the classes are easy and where they overlap %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 200); %! m = resubMargin (mdl); %! %! ## One species separates cleanly; the other two share a boundary, and %! ## that is where the small margins are %! boxplot (m, species); %! ylabel ('Margin'); %! title ('Resubstitution margin by species'); statistics-release-1.9.2/inst/demos/ClassificationNeuralNetwork.resubPredict000066400000000000000000000012731524624707500274670ustar00rootroot00000000000000%!demo %! ## 1. Predicting the data the model was fitted on %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 200); %! %! ## Same as calling predict with the training predictors %! label = resubPredict (mdl); %! confusionchart (species, label, 'Title', 'Resubstitution confusion'); %!demo %! ## 2. Scores as well as labels %! %! load fisheriris %! mdl = fitcnet (meas, species, 'IterationLimit', 200); %! [label, score] = resubPredict (mdl); %! %! ## How sure the model is about the class it picked, for each row %! hist (max (score, [], 2), 20); %! xlabel ('Score of the chosen class'); %! ylabel ('Observations'); %! title ('Confidence of the resubstitution predictions'); statistics-release-1.9.2/inst/demos/CompactClassificationGAM.predict000066400000000000000000000014311524624707500255750ustar00rootroot00000000000000%!demo %! ## 1. A compact model drops the training data and predicts identically %! %! load fisheriris %! inds = ! strcmp (species, 'virginica'); %! X = meas(inds, :); %! Y = species(inds); %! mdl = fitcgam (X, Y); %! cmdl = compact (mdl); %! %! ## No X and no Y on the compact model, and the same labels out of it %! [isprop(mdl, 'X'), isprop(cmdl, 'X'), isequal(predict (mdl, X), predict (cmdl, X))] %!demo %! ## 2. The compact model assesses new data just as the full one does %! %! load fisheriris %! inds = ! strcmp (species, 'setosa'); %! X = meas(inds, :); %! Y = species(inds); %! cmdl = compact (fitcgam (X, Y, 'NumTreesPerPredictor', 20)); %! %! ## margin, edge and loss are all available without the training data %! [edge(cmdl, X, Y), loss(cmdl, X, Y, 'LossFun', 'classiferror')] statistics-release-1.9.2/inst/demos/CompactRegressionGAM.predict000066400000000000000000000011411524624707500247600ustar00rootroot00000000000000%!demo %! ## 1. A compact model drops the training data and predicts identically %! %! load fisheriris %! X = meas(:,1:3); %! Y = meas(:,4); %! mdl = fitrgam (X, Y); %! cmdl = compact (mdl); %! %! ## No X on the compact model, and the same fitted values out of it %! [isprop(mdl, 'X'), isprop(cmdl, 'X'), ... %! max(abs (predict (mdl, X) - predict (cmdl, X)))] %!demo %! ## 2. The compact model scores new data just as the full one does %! %! load fisheriris %! X = meas(:,1:3); %! Y = meas(:,4); %! cmdl = compact (fitrgam (X, Y)); %! %! ## loss is available without the training data %! loss (cmdl, X, Y) statistics-release-1.9.2/inst/demos/CoxModel.coefci000066400000000000000000000021521524624707500223170ustar00rootroot00000000000000%!demo %! ## 1. Confidence intervals for the coefficients %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## One row per coefficient, 95% by default %! ci = coefci (mdl); %! table (mdl.Coefficients.Beta, ci(:,1), ci(:,2), ... %! 'VariableNames', {'Beta', 'Lower', 'Upper'}, ... %! 'RowNames', mdl.Coefficients.Properties.RowNames) %!demo %! ## 2. The argument is a significance level, not a coverage %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## 0.01 asks for a 99% interval, which is the wider of the two %! coefci (mdl, 0.05) %! coefci (mdl, 0.01) %!demo %! ## 3. On the hazard-ratio scale %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## Exponentiating turns a coefficient interval into one for the hazard %! ## ratio of a unit change. An interval covering 1 is one the data cannot %! ## tell from no effect %! exp (coefci (mdl)) statistics-release-1.9.2/inst/demos/CoxModel.discardResiduals000066400000000000000000000013341524624707500243550ustar00rootroot00000000000000%!demo %! ## 1. Dropping the residuals a fitted model carries %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## Seven residual types, one row per observation, is the largest thing a %! ## fitted model holds and the only part that grows with the data %! size (mdl.Residuals) %! mdl = discardResiduals (mdl); %! size (mdl.Residuals) %!demo %! ## 2. Everything else survives %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = discardResiduals (fitcox (X, T)); %! %! ## A model kept for prediction alone loses nothing it needs %! mdl.Coefficients %! hazardratio (mdl, X(1,:)) statistics-release-1.9.2/inst/demos/CoxModel.hazardratio000066400000000000000000000032301524624707500233750ustar00rootroot00000000000000%!demo %! ## 1. The hazard of each observation relative to an average one %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## A ratio above 1 is an observation at greater risk than the baseline, %! ## which by default is the mean of the predictors %! hr = hazardratio (mdl, X); %! table (X(:,1), X(:,2), hr, 'VariableNames', {'X1', 'X2', 'HazardRatio'}) %!demo %! ## 2. Choosing what the ratio is measured against %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## Against the origin rather than the mean. The coefficients do not %! ## change with the baseline; only what the ratio is relative to does %! mdl.Baseline %! hazardratio (mdl, X(1:3,:)) %! hazardratio (mdl, X(1:3,:), 'Baseline', 0) %!demo %! ## 3. What a coefficient means %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## Raising one predictor by one unit multiplies the hazard by exp (beta) %! x0 = mdl.Baseline; %! x1 = x0 + [1, 0]; %! [hazardratio(mdl, x1), exp(mdl.Coefficients.Beta(1))] %!demo %! ## 4. A stratified model needs to be told the stratum %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! S = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! mdl = fitcox (X, T, 'Stratification', S); %! %! ## Each stratum is centred on its own baseline, so the same predictor %! ## values give a different ratio in each %! mdl.Baseline %! [hazardratio(mdl, X(1,:), 1), hazardratio(mdl, X(1,:), 2)] statistics-release-1.9.2/inst/demos/CoxModel.linhyptest000066400000000000000000000023461524624707500232770ustar00rootroot00000000000000%!demo %! ## 1. Sequential tests on the coefficients %! %! X = [2 0 1; 5 1 3; 3 0 2; 8 1 5; 4 0 4; 7 1 6; 6 0 8; 9 1 7; 5 0 9; 10 1 10]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## Each row tests that the predictors it does *not* name are jointly zero, %! ## so the first row tests the whole model against no model at all %! linhyptest (mdl) %!demo %! ## 2. The last row is the last coefficient's own p-value %! %! X = [2 0 1; 5 1 3; 3 0 2; 8 1 5; 4 0 4; 7 1 6; 6 0 8; 9 1 7; 5 0 9; 10 1 10]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## Its hypothesis leaves every other predictor in the model, which is what %! ## the coefficient table already reports %! tbl = linhyptest (mdl); %! [tbl.pValue(end), mdl.Coefficients.pValue(end)] %!demo %! ## 3. Reading the sequence %! %! X = [2 0 1; 5 1 3; 3 0 2; 8 1 5; 4 0 4; 7 1 6; 6 0 8; 9 1 7; 5 0 9; 10 1 10]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## The order of the predictors is the order of the test, so the table %! ## answers "does what follows add anything?" at each step. Reordering the %! ## columns of X asks a different question %! linhyptest (mdl) %! linhyptest (fitcox (X(:,[3 2 1]), T)) statistics-release-1.9.2/inst/demos/CoxModel.plotSurvival000066400000000000000000000022071524624707500236020ustar00rootroot00000000000000%!demo %! ## 1. The survival curve of an average observation %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## The curve steps down at every event time, and nowhere else: the model %! ## learns about survival only where something happened %! plotSurvival (mdl); %!demo %! ## 2. Comparing two sets of predictor values %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## Proportional hazards means the curves are powers of one another, so %! ## they cannot cross %! plotSurvival (mdl, [3 0; 8 1]); %! legend ({'X = [3 0]', 'X = [8 1]'}); %!demo %! ## 3. One curve per stratum %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! S = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! mdl = fitcox (X, T, 'Stratification', S); %! %! ## Stratification is what to reach for when the baseline hazards differ: %! ## these two curves are under no obligation to be proportional %! plotSurvival (mdl); %! legend ({'stratum 1', 'stratum 2'}); statistics-release-1.9.2/inst/demos/CoxModel.survival000066400000000000000000000037261524624707500227520ustar00rootroot00000000000000%!demo %! ## 1. The survival function at the model's baseline %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## Without predictor values the curve is that of an average observation %! [s, t] = survival (mdl); %! table (t, s, 'VariableNames', {'Time', 'Survival'}) %!demo %! ## 2. One curve per observation %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## The columns follow the rows of X, all on the one grid of event times %! s = survival (mdl, X(1:3,:)); %! size (s) %! s(1:4,:) %!demo %! ## 3. Survival at times of your own %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## The baseline survival is interpolated linearly between the event times %! ## and then raised to the hazard ratio of the given predictors %! survival (mdl, X(1,:), 'Time', [5; 12; 20]) %!demo %! ## 4. Outside the event times the extrapolation rule decides %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! mdl = fitcox (X, T); %! %! ## The model knows nothing before t = 4 or after t = 30. 'nearest', the %! ## default, carries the end values outward; 'none' refuses to answer %! tq = [1; 15; 40]; %! nearest = survival (mdl, 'Time', tq); %! none = survival (mdl, 'Time', tq, 'ExtrapolationMethod', 'none'); %! table (tq, nearest, none) %!demo %! ## 5. A stratified model gives each stratum its own curve %! %! X = [2 0; 5 1; 3 0; 8 1; 4 0; 7 1; 6 0; 9 1; 5 0; 10 1]; %! T = [4; 6; 8; 11; 13; 16; 18; 21; 25; 30]; %! S = [1; 1; 1; 1; 1; 2; 2; 2; 2; 2]; %! mdl = fitcox (X, T, 'Stratification', S); %! %! ## Each stratum carries its own baseline hazard, so the curves are %! ## returned separately, on their own event times %! [s, t] = survival (mdl); %! [t{1}, s{1}] %! [t{2}, s{2}] statistics-release-1.9.2/inst/demos/LinearModel.addTerms000066400000000000000000000046531524624707500233230ustar00rootroot00000000000000%!demo %! %! ## Wilkinson-notation shorthand, and the bare-power hierarchy rule. %! ## Ten students' exam scores depend on study hours and hours of sleep. %! ## `'x1*x2'` adds both main effects and their interaction in a single %! ## call; a bare power term such as `'x2^2'` pulls in `x2` itself %! ## alongside `x2^2` if it is not already present, following the same %! ## hierarchy convention used throughout the package. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;6;8;7]; %! Score = [50;54;56;62;64;70;71;73;79;78]; %! mdl = fitlm ([Hours, Sleep], Score); %! mdl.CoefficientNames %! %! ## Adding the interaction between two existing main effects. %! mdl_int = addTerms (mdl, 'x1:x2'); %! mdl_int.CoefficientNames %! %! ## Adding a bare power term. %! mdl_sq = addTerms (mdl, 'x2^2'); %! mdl_sq.CoefficientNames %!demo %! %! ## Terms already in the model are silently skipped; specifying only %! ## terms that already exist issues a warning and returns the model %! ## unchanged. %! ## Reusing the same study-hours-and-sleep model, mixing an existing term %! ## with a new one adds only the new one; asking to add nothing but an %! ## existing term does nothing at all, beyond the warning. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;6;8;7]; %! Score = [50;54;56;62;64;70;71;73;79;78]; %! mdl = fitlm ([Hours, Sleep], Score); %! %! ## x1 is already in the model, so only the interaction is actually added. %! mdl_mix = addTerms (mdl, 'x1 + x1:x2'); %! mdl_mix.CoefficientNames %! %! ## Every term requested is already present: a warning, model unchanged. %! mdl_same = addTerms (mdl, 'x1'); %! mdl_same.CoefficientNames %!demo %! %! ## A categorical predictor is added as one whole indicator group, not %! ## column by column. %! ## Nine stores in three regions report ad spend and sales. Starting %! ## from a model that only uses `AdSpend`, adding `Region` brings in %! ## both of its indicator columns together in a single call. %! AdSpend = [10;20;30;15;25;35;12;22;32]; %! Region = {'North';'North';'North';'South';'South';'South'; ... %! 'East';'East';'East'}; %! Sales = [15;18;24;20;27;33;12;19;26]; %! T = table (AdSpend, Region, Sales); %! mdl = fitlm (T, 'Sales ~ AdSpend', 'CategoricalVars', {'Region'}, ... %! 'PredictorVars', {'AdSpend', 'Region'}); %! mdl.CoefficientNames %! %! ## Both Region indicator columns are added together. %! mdl_region = addTerms (mdl, 'Region'); %! mdl_region.CoefficientNames statistics-release-1.9.2/inst/demos/LinearModel.coefCI000066400000000000000000000045051524624707500227040ustar00rootroot00000000000000%!demo %! %! ## Default 95% confidence intervals versus a custom significance level. %! ## Ten students' exam scores are modeled against study hours. The %! ## default call gives 95% intervals; passing `alpha` explicitly gives %! ## intervals of a different width, always centered on the same point %! ## estimate. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Score = [52;55;61;64;70;73;77;81;85;90]; %! mdl = fitlm (Hours, Score); %! %! ## Default 95% confidence intervals. %! ci95 = coefCI (mdl) %! %! ## Narrower 90% intervals, using alpha = 0.10. %! ci90 = coefCI (mdl, 0.10) %!demo %! %! ## A rank-deficient model: an aliased coefficient's interval collapses %! ## to [0, 0]. %! ## `x2` is defined as exactly twice `x1`, so the design matrix is rank %! ## deficient and the two predictors cannot be told apart. The model %! ## keeps `x1` and aliases `x2`, whose standard error is zero and whose %! ## confidence interval is reported as `[0, 0]` rather than a %! ## meaningless point estimate with no uncertainty. %! x1 = [1;2;3;4;5;6;7;8]; %! x2 = 2*x1; %! y = 3 + 1.5*x1 + 0.5*sin ((1:8)'); %! mdl = fitlm ([x1, x2], y); %! %! ## The second row, for x2, is exactly [0, 0]. %! ci = coefCI (mdl) %!demo %! %! ## Confidence intervals for a four-predictor model, at two confidence %! ## levels, using the `hald` cement data set. %! ## Four chemical percentages in cement (`ingredients`) are used to %! ## predict heat given off while hardening (`heat`). With only 13 %! ## observations and four predictors, several intervals are wide enough %! ## to include zero. %! load hald %! mdl = fitlm (ingredients, heat); %! %! ## 95% confidence intervals for all four coefficients. %! ci_95 = coefCI (mdl) %! %! ## Narrower 90% confidence intervals for comparison. %! ci_90 = coefCI (mdl, 0.10) %!demo %! %! ## coefCI on a CompactLinearModel gives the same intervals as the %! ## original model. %! ## `compact` discards the training data but keeps everything `coefCI` %! ## needs: the coefficient estimates, their standard errors, and the %! ## error degrees of freedom. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Score = [52;55;61;64;70;73;77;81;85;90]; %! mdl = fitlm (Hours, Score); %! cmdl = compact (mdl); %! %! ## Confidence intervals from the full model. %! ci_full = coefCI (mdl) %! %! ## Confidence intervals from the compact model match exactly. %! ci_compact = coefCI (cmdl) statistics-release-1.9.2/inst/demos/LinearModel.coefTest000066400000000000000000000060241524624707500233260ustar00rootroot00000000000000%!demo %! %! ## The overall model F-test, with no `H` supplied. %! ## Ten students' exam scores are modeled against study hours. %! ## `coefTest` with no arguments tests the joint null hypothesis that %! ## every coefficient except the intercept is zero -- the same test %! ## summarized in the "F-statistic vs. constant model" line at the %! ## bottom of the model display. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Score = [52;55;61;64;70;73;77;81;85;90]; %! mdl = fitlm (Hours, Score); %! %! ## The overall F-test: p-value, F-statistic, and numerator DF. %! [p, F, r] = coefTest (mdl) %! %! ## Compare against the F-statistic line printed for the model itself. %! mdl %!demo %! %! ## Jointly testing every indicator column of a categorical predictor %! ## at once. %! ## Nine stores in three regions report ad spend and sales. `Region` %! ## expands to two indicator coefficients, `Region_South` and %! ## `Region_East`; a two-row `H`, one row per indicator, tests whether %! ## both are zero together -- whether region has any effect on sales at %! ## all, as a single joint test rather than two separate ones. %! AdSpend = [10;20;30;15;25;35;12;22;32]; %! Region = {'North';'North';'North';'South';'South';'South'; ... %! 'East';'East';'East'}; %! Sales = [15;18;24;20;27;33;12;19;26]; %! T = table (AdSpend, Region, Sales); %! mdl = fitlm (T, 'Sales ~ AdSpend + Region', 'CategoricalVars', {'Region'}); %! %! ## Coefficient order: (Intercept), AdSpend, Region_South, Region_East. %! mdl.CoefficientNames %! %! ## Joint test that both region coefficients are zero. %! [p, F, r] = coefTest (mdl, [0 0 1 0; 0 0 0 1]) %!demo %! %! ## Testing a coefficient against a specific hypothesized value, not %! ## just zero, using the `H`, `C` form. %! ## Four chemical percentages in cement (`ingredients`) are used to %! ## predict heat given off while hardening (`heat`), using the `hald` %! ## data set. Rather than testing whether the coefficient for `x1` is %! ## zero, `H` picks out that coefficient and `C` supplies the %! ## hypothesized value 1.5 to test it against instead. %! load hald %! mdl = fitlm (ingredients, heat); %! %! ## Coefficient order: (Intercept), x1, x2, x3, x4. %! mdl.CoefficientNames %! %! ## Test whether the coefficient for x1 equals 1.5. %! [p, F, r] = coefTest (mdl, [0 1 0 0 0], 1.5) %!demo %! %! ## coefTest on a CompactLinearModel gives the same results as the %! ## original model, for both the overall test and a specific `H`. %! ## `compact` discards the training data but keeps everything %! ## `coefTest` needs: the coefficient estimates, their covariance, and %! ## the error degrees of freedom. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Score = [52;55;61;64;70;73;77;81;85;90]; %! mdl = fitlm (Hours, Score); %! cmdl = compact (mdl); %! %! ## The overall F-test matches exactly. %! [p_full, F_full, r_full] = coefTest (mdl) %! [p_compact, F_compact, r_compact] = coefTest (cmdl) %! %! ## A specific single-coefficient test also matches exactly. %! [p_full2, F_full2] = coefTest (mdl, [0 1]) %! [p_compact2, F_compact2] = coefTest (cmdl, [0 1]) statistics-release-1.9.2/inst/demos/LinearModel.dwtest000066400000000000000000000055271524624707500230730ustar00rootroot00000000000000%!demo %! %! ## Basic usage: the Durbin-Watson statistic and its p-value. %! ## Fifteen measurements are taken over time, with a small oscillation %! ## layered on top of an otherwise linear trend. The DW statistic comes %! ## out close to but above 2, and the default two-sided p-value gives no %! ## strong evidence of autocorrelation at the usual 0.05 threshold. %! Time = (1:15)'; %! Value = 10 + 0.5*Time + 0.4*sin (Time*2.1); %! mdl = fitlm (Time, Value); %! %! ## The p-value and the DW statistic itself. %! [p, DW] = dwtest (mdl) %!demo %! %! ## Comparing the 'exact' and 'approximate' methods, and confirming the %! ## one-sided tails sum to 1. %! ## The DW statistic itself never depends on `method`; only the p-value %! ## computation changes. The right-tailed and left-tailed p-values %! ## always add up to exactly 1, and the two-sided p-value equals twice %! ## the smaller of the two. %! Time = (1:15)'; %! Value = 10 + 0.5*Time + 0.4*sin (Time*2.1); %! mdl = fitlm (Time, Value); %! %! ## The exact and normal-approximation methods give close but distinct %! ## p-values, for the same DW statistic. %! [p_exact, DW] = dwtest (mdl, 'exact') %! [p_approx, DW] = dwtest (mdl, 'approximate') %! %! ## The two one-sided tails always sum to 1. %! p_right = dwtest (mdl, 'exact', 'right') %! p_left = dwtest (mdl, 'exact', 'left') %! sum_tails = p_right + p_left %!demo %! %! ## Detecting autocorrelation introduced by the natural row order of a %! ## real data set. %! ## Fitting fuel economy against weight in the `carsmall` data set gives %! ## a DW statistic well below 2 and a tiny p-value, since cars from the %! ## same manufacturer or model year tend to sit near each other in the %! ## table and share similar residuals -- autocorrelation can show up %! ## from any meaningful row ordering, not only from a time series. %! load carsmall %! mdl = fitlm (Weight, MPG); %! %! ## A DW statistic well below 2, with a highly significant p-value. %! [p, DW] = dwtest (mdl) %!demo %! %! ## Distinguishing positive from negative autocorrelation. %! ## Two series share the same linear trend but oscillate at different %! ## speeds. The fast oscillation makes neighbouring residuals swing to %! ## opposite signs, which `dwtest` reports as negative autocorrelation %! ## (DW pushed above 2); the slow oscillation makes neighbouring %! ## residuals stay similar for long stretches, which reads as positive %! ## autocorrelation instead (DW pushed toward 0). Both are strongly %! ## significant, just in opposite directions. %! x = (1:20)'; %! %! ## Fast oscillation: negative autocorrelation (DW above 2). %! y_fast = 5 + 2*x + 3*sin (x*2.3); %! mdl_fast = fitlm (x, y_fast); %! [p_fast, DW_fast] = dwtest (mdl_fast) %! %! ## Slow oscillation: positive autocorrelation (DW near 0). %! y_slow = 5 + 2*x + 4*sin (x*0.3); %! mdl_slow = fitlm (x, y_slow); %! [p_slow, DW_slow] = dwtest (mdl_slow) statistics-release-1.9.2/inst/demos/LinearModel.feval000066400000000000000000000065751524624707500226620ustar00rootroot00000000000000%!demo %! %! ## The matrix form, the separate-argument form, and broadcasting a %! ## scalar against a vector. %! ## Ten students' exam scores depend on both study hours and hours of %! ## sleep. `feval` accepts predictors either as one matrix, one column %! ## per predictor, or as separate arguments in the same order; a scalar %! ## argument is broadcast against any non-scalar ones, so a single sleep %! ## value can be paired with several study-hour values at once. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;6;8;7]; %! Score = [50;54;56;62;64;70;71;73;79;78]; %! mdl = fitlm ([Hours, Sleep], Score); %! %! ## Predictor data as a single matrix, one column per predictor. %! ypred_matrix = feval (mdl, [6 7; 10 8]) %! %! ## The same points, as separate arguments instead. %! ypred_sep = feval (mdl, [6;10], [7;8]) %! %! ## Three study-hour values, all paired with the same 7 hours of sleep. %! ypred_broadcast = feval (mdl, [4;6;8], 7) %!demo %! %! ## Passing a categorical predictor's level directly as a plain string, %! ## no table required. %! ## Nine stores in three regions report ad spend and sales. In the %! ## separate-argument form, a categorical predictor's value can be given %! ## as its level name directly, giving the same answer as building a %! ## one-row table by hand. %! AdSpend = [10;20;30;15;25;35;12;22;32]; %! Region = {'North';'North';'North';'South';'South';'South'; ... %! 'East';'East';'East'}; %! Sales = [15;18;24;20;27;33;12;19;26]; %! T = table (AdSpend, Region, Sales); %! mdl = fitlm (T, 'Sales ~ AdSpend + Region', 'CategoricalVars', {'Region'}); %! %! ## A one-row table with AdSpend 20 and Region 'South'. %! ypred_table = feval (mdl, table (20, {'South'}, ... %! 'VariableNames', {'AdSpend','Region'})) %! %! ## The same prediction, passing the level name directly. %! ypred_sep = feval (mdl, 20, 'South') %!demo %! %! ## A fitted model behaves like an ordinary function handle, so it can be %! ## passed straight into routines such as `fminsearch`. %! ## A quadratic model of fuel economy against vehicle weight is fit on %! ## the `carsmall` data set. Wrapping `feval` in an anonymous function %! ## lets `fminsearch` search for the weight at which the model predicts %! ## exactly 25 MPG, without ever calling `predict` directly. %! load carsmall %! X = Weight; %! y = MPG; %! mdl = fitlm (X, y, 'quadratic'); %! target = 25; %! %! ## Search for the weight giving a predicted fuel economy of 25 MPG. %! wopt = fminsearch (@(w) (feval (mdl, w) - target)^2, 3000) %! %! ## Confirm the prediction at that weight. %! mpg_at_wopt = feval (mdl, wopt) %!demo %! %! ## `feval` on a CompactLinearModel gives identical results to the %! ## original model, in both calling forms. %! ## Reusing the study-hours-and-sleep model, `compact` discards the %! ## training data but keeps everything `feval` needs, so both the matrix %! ## form and the separate-argument form still work exactly as before. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;6;8;7]; %! Score = [50;54;56;62;64;70;71;73;79;78]; %! mdl = fitlm ([Hours, Sleep], Score); %! cmdl = compact (mdl); %! %! ## Matrix form: full model versus compact model. %! ypred_full = feval (mdl, [6 7; 10 8]) %! ypred_compact = feval (cmdl, [6 7; 10 8]) %! %! ## Separate-argument form: full model versus compact model. %! ypred_full_sep = feval (mdl, [6;10], [7;8]) %! ypred_compact_sep = feval (cmdl, [6;10], [7;8]) statistics-release-1.9.2/inst/demos/LinearModel.plotAdjustedResponse000066400000000000000000000036551524624707500257420ustar00rootroot00000000000000%!demo %! %! ## The adjusted response for one predictor, with the other predictor %! ## averaged out. %! ## Ten observations depend on study hours and hours of sleep. %! ## `plotAdjustedResponse` shows the fitted response as a function of %! ## `x1` alone, with `x2` held at its average observed value across the %! ## fit, alongside the actual adjusted data points (fitted value plus %! ## residual) for comparison. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;6;8;7]; %! Score = [50;54;56;62;64;70;71;73;79;78]; %! mdl = fitlm ([Hours, Sleep], Score); %! %! ## Adjusted response for x1, with x2 averaged out. %! plotAdjustedResponse (mdl, 'x1'); %!demo %! %! ## For a categorical predictor, the adjusted response function is %! ## evaluated at each level instead of a continuous grid. %! ## Nine stores in three regions report ad spend and sales. The adjusted %! ## response for `Region` is a step function through its three levels, %! ## with `AdSpend` averaged out. %! AdSpend = [200;350;500;220;370;520;240;390;540]; %! Region = {'North';'North';'North';'South';'South';'South'; ... %! 'East';'East';'East'}; %! RegionEffect = [0;0;0;300;300;300;700;700;700]; %! Sales = 1000 + 0.5*AdSpend + RegionEffect + 5*sin ((1:9)'); %! T = table (AdSpend, Region, Sales); %! mdl = fitlm (T, 'Sales ~ AdSpend + Region', 'CategoricalVars', {'Region'}); %! %! ## Adjusted response for Region, with AdSpend averaged out. %! plotAdjustedResponse (mdl, 'Region'); %!demo %! %! ## Isolating one predictor's adjusted response among several, using the %! ## `hald` cement data set. %! ## Four chemical percentages in cement (`ingredients`) are used to %! ## predict heat given off while hardening (`heat`). The adjusted %! ## response for the first ingredient is shown with the other three %! ## averaged out. %! load hald %! mdl = fitlm (ingredients, heat); %! %! ## Adjusted response for the first ingredient. %! plotAdjustedResponse (mdl, 'x1'); statistics-release-1.9.2/inst/demos/LinearModel.plotDiagnostics000066400000000000000000000041251524624707500247200ustar00rootroot00000000000000%!demo %! %! ## The default leverage plot, and Cook's distance -- both flagging the %! ## same unusual observation. %! ## Ten students' exam scores are modeled against study hours, with one %! ## student whose hours and score are both far outside the rest of the %! ## group. With no arguments, `plotDiagnostics` plots each observation's %! ## leverage against its row number, with a dotted reference line at %! ## the conventional threshold `2*p/n`. Passing `'cookd'` instead %! ## shows each observation's overall influence on the fitted model. %! Hours = [1;2;3;4;5;6;7;8;9;15]; %! Score = [52;55;61;64;70;73;77;81;85;130]; %! mdl = fitlm (Hours, Score); %! %! ## Default: leverage of each observation. %! plotDiagnostics (mdl); %! %! ## Cook's distance, measuring each observation's overall influence. %! plotDiagnostics (mdl, 'cookd'); %!demo %! %! ## Delete-1 scaled change in each coefficient, one line per predictor. %! ## Ten observations depend on study hours and hours of sleep, with one %! ## observation whose sleep value and score are both unusually large. %! ## `'dfbetas'` shows how much each coefficient estimate would change if %! ## that single observation were removed, scaled to be comparable across %! ## coefficients -- useful for seeing which specific predictor a given %! ## observation is most influential on. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;9;8;20]; %! Score = [50;54;56;62;64;70;71;73;79;150]; %! mdl = fitlm ([Hours, Sleep], Score); %! %! ## Scaled change in each coefficient if each observation were removed. %! plotDiagnostics (mdl, 'dfbetas'); %!demo %! %! ## Delete-1 scaled change in the fitted value, using the `carsmall` %! ## data set. %! ## Fuel economy is modeled against vehicle weight. `'dffits'` shows how %! ## much each observation's own fitted value would change if that %! ## observation were removed from the fit, with a dotted reference line %! ## at the conventional threshold `2*sqrt (p/n)`. %! load carsmall %! mdl = fitlm (Weight, MPG); %! %! ## Scaled change in the fitted value if each observation were removed. %! plotDiagnostics (mdl, 'dffits'); statistics-release-1.9.2/inst/demos/LinearModel.plotEffects000066400000000000000000000046601524624707500240340ustar00rootroot00000000000000%!demo %! %! ## The main effect and 95% confidence interval for each predictor. %! ## Ten observations depend on study hours and hours of sleep. %! ## `plotEffects` shows, for each predictor, how much the fitted response %! ## changes when that predictor moves from its observed minimum to its %! ## observed maximum, with all other predictors held at their observed %! ## means. The dotted vertical line marks zero effect. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;6;8;7]; %! Score = [50;54;56;62;64;70;71;73;79;78]; %! mdl = fitlm ([Hours, Sleep], Score); %! %! ## Main effect of each predictor, from its minimum to its maximum. %! plotEffects (mdl); %!demo %! %! ## A categorical predictor's effect is shown the same way, labeled by %! ## its levels rather than a numeric range. %! ## Nine stores in three regions report ad spend and sales. The %! ## categorical `Region` gets a y-axis label showing its reference and %! ## comparison levels, just as a numeric predictor's label shows its %! ## minimum and maximum. %! AdSpend = [10;20;30;15;25;35;12;22;32]; %! Region = {'North';'North';'North';'South';'South';'South'; ... %! 'East';'East';'East'}; %! Sales = [15;18;24;20;27;33;12;19;26]; %! T = table (AdSpend, Region, Sales); %! mdl = fitlm (T, 'Sales ~ AdSpend + Region', 'CategoricalVars', {'Region'}); %! %! ## Main effect of AdSpend and of moving between Region levels. %! plotEffects (mdl); %!demo %! %! ## Comparing the main effects of four predictors at once, using the %! ## `hald` cement data set. %! ## Four chemical percentages in cement (`ingredients`) are used to %! ## predict heat given off while hardening (`heat`). With four %! ## correlated predictors and only 13 observations, several confidence %! ## intervals are wide enough to cross zero. %! load hald %! mdl = fitlm (ingredients, heat); %! %! ## Main effect of each of the four ingredients. %! plotEffects (mdl); %!demo %! %! ## plotEffects on a CompactLinearModel gives the same plot as the %! ## original model. %! ## `compact` discards the training data but keeps everything %! ## `plotEffects` needs: the coefficient estimates, their covariance, %! ## and the effect contrasts computed at fit time. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;6;8;7]; %! Score = [50;54;56;62;64;70;71;73;79;78]; %! mdl = fitlm ([Hours, Sleep], Score); %! cmdl = compact (mdl); %! %! ## The same plot, produced from the compact model. %! plotEffects (cmdl); statistics-release-1.9.2/inst/demos/LinearModel.plotInteraction000066400000000000000000000052741524624707500247360ustar00rootroot00000000000000%!demo %! %! ## The main effect and conditional effects of two predictors, the %! ## default 'effects' plot. %! ## Ten observations depend on study hours and hours of sleep, fit with %! ## an interaction term. For each predictor, the top marker is its main %! ## effect; the markers below it are its effect recomputed with the %! ## other predictor held fixed at a few conditioning values. When those %! ## markers do not line up vertically, the predictors interact. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;6;8;7]; %! Score = [50;54;56;62;64;70;71;73;79;78]; %! mdl = fitlm ([Hours, Sleep], Score, 'y ~ x1*x2'); %! %! ## Main and conditional effects of x1 and x2. %! plotInteraction (mdl, 'x1', 'x2'); %!demo %! %! ## The 'predictions' plot type: adjusted response curves for x2, one %! ## per conditioning value of x1. %! ## Reusing the same interaction model, each curve shows how the %! ## adjusted response changes with x2, holding x1 fixed at a different %! ## value. Curves with different slopes indicate an interaction; parallel %! ## curves would indicate none. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;6;8;7]; %! Score = [50;54;56;62;64;70;71;73;79;78]; %! mdl = fitlm ([Hours, Sleep], Score, 'y ~ x1*x2'); %! %! ## Adjusted response vs. x2, for three fixed values of x1. %! plotInteraction (mdl, 'x1', 'x2', 'predictions'); %!demo %! %! ## A categorical predictor's main and conditional effects, alongside a %! ## numeric one. %! ## Nine stores in three regions report ad spend and sales, fit with an %! ## interaction between AdSpend and Region. The categorical predictor's %! ## conditional effects are shown once per level rather than at a %! ## handful of numeric conditioning points. %! AdSpend = [10;20;30;15;25;35;12;22;32]; %! Region = {'North';'North';'North';'South';'South';'South'; ... %! 'East';'East';'East'}; %! Sales = [15;18;24;20;27;33;12;19;26]; %! T = table (AdSpend, Region, Sales); %! mdl = fitlm (T, 'Sales ~ AdSpend*Region', 'CategoricalVars', {'Region'}); %! %! ## Main and conditional effects of AdSpend and Region. %! plotInteraction (mdl, 'AdSpend', 'Region'); %!demo %! %! ## plotInteraction on a CompactLinearModel gives the same plot as the %! ## original model. %! ## `compact` discards the training data but keeps everything %! ## `plotInteraction` needs: the coefficient estimates, their covariance, %! ## and the interaction contrasts computed at fit time. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;6;8;7]; %! Score = [50;54;56;62;64;70;71;73;79;78]; %! mdl = fitlm ([Hours, Sleep], Score, 'y ~ x1*x2'); %! cmdl = compact (mdl); %! %! ## The same plot, produced from the compact model. %! plotInteraction (cmdl, 'x1', 'x2'); statistics-release-1.9.2/inst/demos/LinearModel.plotResiduals000066400000000000000000000033641524624707500244100ustar00rootroot00000000000000%!demo %! %! ## The default histogram, and the fitted-values plot with a chosen %! ## residual type. %! ## Ten students' exam scores are modeled against study hours. With no %! ## arguments, `plotResiduals` draws a probability density histogram of %! ## the raw residuals. Passing `'fitted'` instead plots residuals %! ## against the fitted values, and `'ResidualType'` selects which kind %! ## of residual is shown -- here, studentized rather than raw. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Score = [52;55;61;64;70;73;77;81;85;90]; %! mdl = fitlm (Hours, Score); %! %! ## Default: a histogram of the raw residuals. %! plotResiduals (mdl); %! %! ## Studentized residuals against the fitted values. %! plotResiduals (mdl, 'fitted', 'ResidualType', 'studentized'); %!demo %! %! ## The lagged residual plot, useful for spotting autocorrelation. %! ## Twenty observations follow a slow oscillation layered on top of a %! ## linear trend, so consecutive residuals tend to be similar to one %! ## another. Plotting each residual against the one before it makes %! ## that pattern visible as a diagonal trend rather than a random %! ## scatter. %! x = (1:20)'; %! y = 5 + 2*x + 4*sin (x*0.3); %! mdl = fitlm (x, y); %! %! ## Each residual plotted against the residual immediately before it. %! plotResiduals (mdl, 'lagged'); %!demo %! %! ## A normal probability plot of the residuals, using the `carsmall` %! ## data set. %! ## Fuel economy is modeled against vehicle weight. The probability %! ## plot sorts the residuals and compares them against a normal %! ## reference line, making departures from normality easy to spot at %! ## either tail. %! load carsmall %! mdl = fitlm (Weight, MPG); %! %! ## Normal probability plot of the residuals. %! plotResiduals (mdl, 'probability'); statistics-release-1.9.2/inst/demos/LinearModel.predict000066400000000000000000000062421524624707500232060ustar00rootroot00000000000000%!demo %! %! ## Predicting with and without new data. %! ## Ten students study for varying numbers of hours before an exam, with %! ## their resulting scores recorded. Calling `predict` with no `Xnew` %! ## returns the fitted values for the training data itself (the same as %! ## `mdl.Fitted`); calling it with new hour values predicts scores at %! ## points not in the original sample, including one beyond the range of %! ## the data. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Score = [52;55;61;64;70;73;77;81;85;90]; %! mdl = fitlm (Hours, Score); %! %! ## Fitted values for the original training data. %! fitted = predict (mdl) %! %! ## Predictions at 5.5 hours (interpolation) and 11 hours (extrapolation). %! ypred = predict (mdl, [5.5; 11]) %!demo %! %! ## Confidence intervals versus prediction intervals, and simultaneous %! ## bounds. %! ## Twelve students' exam scores depend on both study hours and hours of %! ## sleep. The default output is a 95% confidence interval on the mean %! ## response; `'Prediction', 'observation'` widens it to account for a %! ## single future observation instead of the average; and %! ## `'Simultaneous', true` widens it further so the whole predicted %! ## surface, not just each point individually, holds at the stated %! ## confidence level. %! Hours = [1;2;3;4;5;6;7;8;9;10;11;12]; %! Sleep = [5;6;5;7;6;8;7;6;8;7;9;8]; %! Score = [50;54;56;62;64;70;71;73;79;78;85;84]; %! X = [Hours, Sleep]; %! mdl = fitlm (X, Score); %! Xnew = [6 7; 10 8]; %! %! ## Default: 95% confidence interval on the mean response. %! [ypred, yci_curve] = predict (mdl, Xnew) %! %! ## Wider prediction interval for a single new observation. %! [ypred, yci_obs] = predict (mdl, Xnew, 'Prediction', 'observation') %! %! ## 90% simultaneous confidence band over both points at once. %! [ypred, yci_sim] = predict (mdl, Xnew, 'Alpha', 0.1, 'Simultaneous', true) %!demo %! %! ## Predicting fuel economy from a quadratic model of vehicle weight, %! ## using the `carsmall` data set. %! ## `fitlm` fits `MPG` as a quadratic function of `Weight`, and %! ## `predict` computes the fitted curve at every original weight value, %! ## showing how well the quadratic term captures the curvature that a %! ## straight line would miss. %! load carsmall %! X = Weight; %! y = MPG; %! mdl = fitlm (X, y, 'quadratic'); %! %! ## Predicted fuel economy for the first five cars in the data set. %! ypred = predict (mdl, X); %! ypred(1:5) %!demo %! %! ## Predicting from a CompactLinearModel gives the same answer as the %! ## original model, at a fraction of the memory footprint. %! ## Eight months of advertising spend and sales are used to fit a simple %! ## model. `compact` discards the training data but keeps everything %! ## `predict` needs; unlike `LinearModel`, `Xnew` cannot be omitted here, %! ## since a compact model has no training data to fall back on. %! AdSpend = [10;20;30;40;50;60;70;80]; %! Sales = [15;18;24;27;33;36;42;45]; %! mdl = fitlm (AdSpend, Sales); %! cmdl = compact (mdl); %! Xnew = [25; 55]; %! %! ## Predictions from the full model. %! [ypred_full, yci_full] = predict (mdl, Xnew) %! %! ## Predictions from the compact model match exactly. %! [ypred_compact, yci_compact] = predict (cmdl, Xnew) statistics-release-1.9.2/inst/demos/LinearModel.random000066400000000000000000000053221524624707500230320ustar00rootroot00000000000000%!demo %! %! ## Simulating noisy responses versus the deterministic fitted value. %! ## Seven days' temperatures and ice-cream sales are used to fit a %! ## simple model. `predict` gives the deterministic fitted value at each %! ## new temperature; `random` adds independent Gaussian noise scaled by %! ## the model's mean squared error, giving a plausible simulated %! ## observation instead. %! Temp = [60;65;70;75;80;85;90]; %! Sales = [200;230;260;300;340;370;410]; %! mdl = fitlm (Temp, Sales); %! Xnew = [72; 88]; %! %! ## The deterministic fitted value, with no noise. %! ypred = predict (mdl, Xnew) %! %! ## A simulated observation at the same points, with noise added. %! ysim = random (mdl, Xnew) %!demo %! %! ## Reproducible simulated responses by seeding the generator that %! ## `random` actually draws from. %! ## `random` adds its noise with `randn`, so reproducibility comes from %! ## seeding `randn` itself, not `rand`. Resetting to the same state %! ## before each call gives back the exact same simulated values. %! rng (42); %! randg ('state', 42); %! Temp = [60;65;70;75;80;85;90]; %! Sales = [200;230;260;300;340;370;410]; %! mdl = fitlm (Temp, Sales); %! Xnew = [72; 88]; %! %! ## Two calls from the same seed give identical simulated responses. %! ysimA = random (mdl, Xnew); %! ysimB = random (mdl, Xnew); %! same_seed_matches = isequal (ysimA, ysimB) %!demo %! %! ## Simulating noisy fuel-economy readings from a quadratic model of %! ## vehicle weight, using the `carsmall` data set. %! ## The deterministic quadratic fit gives a smooth predicted curve; %! ## `random` adds noise scaled by the model's own MSE to produce %! ## plausible individual readings a new car of that weight might show. %! rng (42); %! randg ('state', 42); %! load carsmall %! X = Weight; %! y = MPG; %! mdl = fitlm (X, y, 'quadratic'); %! Xsub = X(1:5); %! %! ## Deterministic predictions for the first five cars. %! ypred = predict (mdl, Xsub) %! %! ## Simulated readings for the same five cars, with noise added. %! ysim = random (mdl, Xsub) %!demo %! %! ## `random` on a CompactLinearModel gives the same simulated draw as %! ## the original model, given the same seed. %! ## `compact` discards the training data but keeps everything `random` %! ## needs; reseeding identically before each call confirms the compact %! ## model simulates exactly the same values as the original. %! rng (42); %! randg ('state', 42); %! Temp = [60;65;70;75;80;85;90]; %! Sales = [200;230;260;300;340;370;410]; %! mdl = fitlm (Temp, Sales); %! cmdl = compact (mdl); %! Xnew = [72; 88]; %! %! ## Same seed, same simulated draw from either model. %! ysim_full = random (mdl, Xnew); %! ysim_compact = random (cmdl, Xnew); %! compact_matches_full = isequal (ysim_full, ysim_compact) statistics-release-1.9.2/inst/demos/LinearModel.removeTerms000066400000000000000000000035611524624707500240650ustar00rootroot00000000000000%!demo %! %! ## The star operator removes main effects together with their %! ## interaction; terms already absent are silently skipped, and %! ## specifying only absent terms warns and leaves the model unchanged. %! ## Ten students' exam scores depend on study hours and hours of sleep. %! ## `'x1*x2'` removes `x1`, `x2`, and `x1:x2` together in one call. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;6;8;7]; %! Score = [50;54;56;62;64;70;71;73;79;78]; %! mdl_full = fitlm ([Hours, Sleep], Score, 'y ~ x1*x2'); %! mdl_full.CoefficientNames %! %! ## Removing both main effects and their interaction in one call. %! mdl_empty = removeTerms (mdl_full, 'x1*x2'); %! mdl_empty.CoefficientNames %! %! ## Starting over from an additive model with no interaction term. %! mdl_add = fitlm ([Hours, Sleep], Score); %! mdl_add.CoefficientNames %! %! ## x1 is removed; x1:x2 was never in the model, so it is silently skipped. %! mdl_mix = removeTerms (mdl_add, 'x1 + x1:x2'); %! mdl_mix.CoefficientNames %! %! ## Requesting only a term that is not in the model: a warning, unchanged. %! mdl_same = removeTerms (mdl_add, 'x1:x2'); %! mdl_same.CoefficientNames %!demo %! %! ## A categorical predictor is removed as one whole indicator group, not %! ## column by column. %! ## Nine stores in three regions report ad spend and sales. Removing %! ## `Region` drops both of its indicator columns together in a single %! ## call. %! AdSpend = [10;20;30;15;25;35;12;22;32]; %! Region = {'North';'North';'North';'South';'South';'South'; ... %! 'East';'East';'East'}; %! Sales = [15;18;24;20;27;33;12;19;26]; %! T = table (AdSpend, Region, Sales); %! mdl = fitlm (T, 'Sales ~ AdSpend + Region', 'CategoricalVars', {'Region'}); %! mdl.CoefficientNames %! %! ## Both Region indicator columns are removed together. %! mdl_no_region = removeTerms (mdl, 'Region'); %! mdl_no_region.CoefficientNames statistics-release-1.9.2/inst/demos/LinearModel.step000066400000000000000000000033631524624707500225300ustar00rootroot00000000000000%!demo %! %! ## A single default step, improving a starting model by one term. %! ## Ten observations depend on study hours and hours of sleep. Starting %! ## from a model containing only `x1`, `step` examines whether adding or %! ## removing a single term improves the fit and, by default, takes at %! ## most one such step. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;6;8;7]; %! Score = [50;54;56;62;64;70;71;73;79;78]; %! mdl0 = fitlm ([Hours, Sleep], Score, 'y ~ x1'); %! mdl0.CoefficientNames %! %! ## One improvement step, starting from x1 alone. %! mdl1 = step (mdl0); %! mdl1.CoefficientNames %!demo %! %! ## Taking several steps at once with `NSteps`, and following the trace %! ## with `Verbose`. %! ## Reusing the same predictors, starting from a constant model this %! ## time, `NSteps` lets `step` keep improving the model over more than %! ## one call, up to the given upper bound. %! Hours = [1;2;3;4;5;6;7;8;9;10]; %! Sleep = [5;6;5;7;6;8;7;6;8;7]; %! Score = [50;54;56;62;64;70;71;73;79;78]; %! mdl0 = fitlm ([Hours, Sleep], Score, 'y ~ 1'); %! %! ## Up to three steps, bounded above by the full interaction model. %! mdl = step (mdl0, 'Upper', 'y ~ x1*x2', 'NSteps', 3, 'Verbose', 1); %! mdl.CoefficientNames %!demo %! %! ## Starting from a constant model on the `hald` cement data set. %! ## Four chemical percentages in cement (`ingredients`) are used to %! ## predict heat given off while hardening (`heat`). Starting from an %! ## intercept-only model, `step` adds the two most useful ingredients %! ## over several steps. %! load hald %! mdl0 = fitlm (ingredients, heat, 'y ~ 1'); %! %! ## Up to four steps, bounded above by the additive model. %! mdl = step (mdl0, 'Upper', 'linear', 'NSteps', 4, 'Verbose', 1); %! mdl.CoefficientNames statistics-release-1.9.2/inst/demos/RegressionGAM.loss000066400000000000000000000015621524624707500230060ustar00rootroot00000000000000%!demo %! ## 1. The mean squared error of the model on its own training data %! %! load fisheriris %! X = meas(:,1:3); %! Y = meas(:,4); %! mdl = fitrgam (X, Y); %! %! ## resubLoss asks the same question without handing the data back in %! [loss(mdl, X, Y), resubLoss(mdl)] %!demo %! ## 2. Interaction terms lower the loss on this data %! %! load fisheriris %! X = meas(:,1:3); %! Y = meas(:,4); %! %! ## 'all' fits every pairwise term on top of the additive ones %! [resubLoss(fitrgam (X, Y)), resubLoss(fitrgam (X, Y, 'Interactions', 'all'))] %!demo %! ## 3. A loss function of your own, taking the truth, the fit and the weights %! %! load fisheriris %! X = meas(:,1:3); %! Y = meas(:,4); %! mdl = fitrgam (X, Y); %! %! ## Mean absolute error instead of mean squared error %! mae = @(y, yfit, w) sum (w .* abs (y - yfit)); %! [loss(mdl, X, Y), loss(mdl, X, Y, 'LossFun', mae)] statistics-release-1.9.2/inst/demos/cvpartition.summary000066400000000000000000000042021524624707500234120ustar00rootroot00000000000000%!demo %! ## 1. Basic Usage %! %! ## Create simple numeric labels %! labels = [ones(10, 1); 2 * ones(10, 1)]; %! c = cvpartition (labels, 'KFold', 2); %! summary (c) %!demo %! ## 2. Grouped K-Fold Partition %! %! rng (42); %! Region = repelem ({'North'; 'South'; 'East'; 'West'}, [20; 15; 15; 20]); %! Success = repelem ({'Success'; 'Fail'}, [49; 21]); %! Success = Success(randperm (70)); %! Tbl = table (Region, Success); %! %! ## Create Grouped Partition using the 'Region' variable %! c_group = cvpartition (height (Tbl), 'KFold', 4, 'GroupingVariables', Tbl.Region); %! %! ## Generate Summary %! summary (c_group) %!demo %! ## 3. Stratified K-Fold Partition %! %! rng (42); %! Success = repelem ({'ClassA'; 'ClassB'}, [25; 25]); %! Success = Success(randperm (50)); %! Tbl = table (Success); %! %! ## Create Stratified Partition %! c_strat = cvpartition (Tbl.Success, 'KFold', 5); %! %! ## Generate Summary %! summary (c_strat) %!demo %! ## 4. Handling Missing Values (NaN) in Stratification %! %! ## Create data with missing values (NaN) %! labels = [ones(10, 1); 2 * ones(10, 1); NaN(5, 1)]; %! %! ## Create partition %! c_missing = cvpartition (labels, 'KFold', 2); %! %! ## Generate Summary %! summary (c_missing) %!demo %! ## 5. Filtering Summary %! %! rng (42); %! Success = repelem ({'Yes'; 'No'}, [20; 10]); %! Success = Success(randperm (30)); %! %! ## Create Partition and Summary %! c_strat = cvpartition (Success, 'KFold', 3); %! summaryStrat = summary (c_strat); %! %! ## A. Filtering by Exact Set Name %! summaryTest1 = summaryStrat (strcmp (summaryStrat.Set, 'test1'), :) %! %! ## B. Filtering by Partial Match %! is_test = ! cellfun ('isempty', strfind (cellstr (summaryStrat.Set), 'test')); %! testSummaryTbl = summaryStrat (is_test, :) %!demo %! ## 6. Unstacking %! %! rng (42); %! Success = repelem ({'Yes'; 'No'}, [20; 10]); %! Success = Success(randperm (30)); %! %! ## Create Partition and Summary %! c_strat = cvpartition (Success, 'KFold', 3); %! summaryStrat = summary (c_strat); %! %! ## Pivot 'StratificationLabel' (Yes/No) into new columns %! speciesSummaryTbl = unstack (summaryStrat(:,1:4), 'StratificationCount', 'StratificationLabel') statistics-release-1.9.2/inst/demos/prob.BetaDistribution.a000066400000000000000000000007571524624707500240220ustar00rootroot00000000000000%!demo %! ## Create a beta distribution with default parameters %! pd = makedist ("Beta") %! %! ## Query parameter 'alpha' (first shape parameter) %! pd.a %! %! ## Set parameter 'alpha' %! pd.a = 2 %! %! ## Use this to initialize or modify the first shape parameter of a beta %! ## distribution. The parameter 'alpha' must be a positive real scalar. %!demo %! ## Create a beta distribution object by calling its constructor %! pd = BetaDistribution (2, 3) %! ## Query parameter 'alpha' %! pd.a statistics-release-1.9.2/inst/demos/prob.BetaDistribution.b000066400000000000000000000007601524624707500240150ustar00rootroot00000000000000%!demo %! ## Create a beta distribution with default parameters %! pd = makedist ("Beta") %! %! ## Query parameter 'beta' (second shape parameter) %! pd.b %! %! ## Set parameter 'beta' %! pd.b = 2 %! %! ## Use this to initialize or modify the second shape parameter of a beta %! ## distribution. The parameter 'beta' must be a positive real scalar. %!demo %! ## Create a beta distribution object by calling its constructor %! pd = BetaDistribution (2, 3) %! %! ## Query parameter 'beta' %! pd.b statistics-release-1.9.2/inst/demos/prob.BetaDistribution.cdf000066400000000000000000000013041524624707500243230ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Beta distribution %! x = 0:0.01:1; %! pd1 = makedist ("Beta", "a", 0.5, "b", 0.5); %! pd2 = makedist ("Beta", "a", 2, "b", 2); %! pd3 = makedist ("Beta", "a", 5, "b", 2); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"a = 0.5, b = 0.5", "a = 2, b = 2", "a = 5, b = 2"}, ... %! "location", "southeast") %! title ("Beta CDF") %! xlabel ("Value") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different beta distributions, showing how probability accumulates %! ## over the interval [0, 1]. statistics-release-1.9.2/inst/demos/prob.BetaDistribution.icdf000066400000000000000000000012401524624707500244730ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Beta distribution %! p = 0.001:0.001:0.999; %! pd1 = makedist ("Beta", "a", 0.5, "b", 0.5); %! pd2 = makedist ("Beta", "a", 2, "b", 2); %! pd3 = makedist ("Beta", "a", 5, "b", 2); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"a = 0.5, b = 0.5", "a = 2, b = 2", "a = 5, b = 2"}, ... %! "location", "northwest") %! title ("Beta iCDF") %! xlabel ("Probability") %! ylabel ("Value") %! %! ## This demonstrates the inverse CDF (quantiles) for beta distributions, %! ## useful for finding values corresponding to given probabilities. statistics-release-1.9.2/inst/demos/prob.BetaDistribution.iqr000066400000000000000000000004131524624707500243620ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Beta distribution %! pd = makedist ("Beta", "a", 2, "b", 5) %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the beta distribution. statistics-release-1.9.2/inst/demos/prob.BetaDistribution.mean000066400000000000000000000005301524624707500245070ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Beta distributions %! pd1 = makedist ("Beta", "a", 1, "b", 1); %! pd2 = makedist ("Beta", "a", 2, "b", 5); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value of beta distributions with %! ## different shape parameters, representing the average outcome. statistics-release-1.9.2/inst/demos/prob.BetaDistribution.median000066400000000000000000000005051524624707500250260ustar00rootroot00000000000000%!demo %! ## Compute the median for different Beta distributions %! pd1 = makedist ("Beta", "a", 1, "b", 1); %! pd2 = makedist ("Beta", "a", 2, "b", 5); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the beta distribution %! ## into two equal probability halves. statistics-release-1.9.2/inst/demos/prob.BetaDistribution.negloglik000066400000000000000000000006541524624707500255510ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Beta distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Beta", "a", 2, "b", 5) %! data = random (pd, 100, 1); %! pd_fitted = fitdist (data, "Beta") %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of a beta distribution to data, %! ## lower values indicate a better fit. statistics-release-1.9.2/inst/demos/prob.BetaDistribution.paramci000066400000000000000000000007311524624707500252060ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Beta distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Beta", "a", 2, "b", 5) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "Beta") %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (a, b), %! ## providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.BetaDistribution.pdf000066400000000000000000000012171524624707500243430ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Beta distribution %! x = 0:0.01:1; %! pd1 = makedist ("Beta", "a", 0.5, "b", 0.5); %! pd2 = makedist ("Beta", "a", 2, "b", 2); %! pd3 = makedist ("Beta", "a", 5, "b", 2); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"a = 0.5, b = 0.5", "a = 2, b = 2", "a = 5, b = 2"}, ... %! "location", "north") %! title ("Beta PDF") %! xlabel ("Value") %! ylabel ("Probability Density") %! %! ## This visualizes the probability density function for beta distributions, %! ## showing the likelihood of different values in [0, 1]. statistics-release-1.9.2/inst/demos/prob.BetaDistribution.plot000066400000000000000000000030051524624707500245450ustar00rootroot00000000000000%!demo %! ## Create a Beta distribution with fixed parameters a = 2 and b = 5, and %! ## plot its PDF. %! %! pd = makedist ("Beta", "a", 2, "b", 5) %! plot (pd) %! title ("Fixed Beta distribution with a = 2 and b = 5") %!demo %! ## Generate a data set of 100 random samples from a Beta distribution with %! ## parameters a = 2 and b = 4. Fit a Beta distribution to this data and plot %! ## its CDF superimposed over an empirical CDF of the data %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("Beta", "a", 2, "b", 4) %! data = random (pd_fixed, 100, 1); %! pd_fitted = fitdist (data, "Beta") %! plot (pd_fitted, "plottype", "cdf") %! txt = "Fitted Beta distribution with a = %0.2f and b = %0.2f"; %! title (sprintf (txt, pd_fitted.a, pd_fitted.b)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "east") %!demo %! ## Generate a data set of 200 random samples from a Beta distribution with %! ## parameters a = 2 and b = 4. Display a probability plot for the Beta %! ## distribution fit to the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("Beta", "a", 2, "b", 4) %! data = random (pd_fixed, 200, 1); %! pd_fitted = fitdist (data, "Beta") %! plot (pd_fitted, "plottype", "probability") %! txt = "Probability plot of a fitted Beta distribution with a = %0.2f and b = %0.2f"; %! title (sprintf (txt, pd_fitted.a, pd_fitted.b)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") statistics-release-1.9.2/inst/demos/prob.BetaDistribution.proflik000066400000000000000000000010151524624707500252340ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the first shape parameter of %! ## a fitted Beta distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Beta", "a", 2, "b", 5) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "Beta") %! [nlogL, param] = proflik (pd_fitted, 1, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the shape parameter (a), %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.BetaDistribution.random000066400000000000000000000007401524624707500250520ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Beta distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Beta", "a", 2, "b", 5) %! samples = random (pd, 500, 1); %! hist (samples, 20) %! title ("Histogram of 500 random samples from Beta(a=2, b=5)") %! xlabel ("Value") %! ylabel ("Frequency") %! %! ## This generates random samples from a beta distribution, useful for %! ## simulating data within the [0, 1] interval. statistics-release-1.9.2/inst/demos/prob.BetaDistribution.std000066400000000000000000000003741524624707500243670ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Beta distribution %! pd = makedist ("Beta", "a", 2, "b", 5) %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the %! ## variability of the beta distribution. statistics-release-1.9.2/inst/demos/prob.BetaDistribution.truncate000066400000000000000000000011101524624707500254070ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Beta distribution, with parameters a = 2 and b = 4, %! ## truncated at [0.1, 0.8] intervals. Generate 10000 random samples from %! ## this truncated distribution and superimpose a histogram with 100 bins %! ## scaled accordingly %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Beta", "a", 2, "b", 4) %! t = truncate (pd, 0.1, 0.8) %! data = random (t, 10000, 1); %! plot (t) %! title ("Beta distribution (a = 2, b = 4) truncated at [0.1, 0.8]") %! hold on %! hist (data, 100, 140) %! hold off statistics-release-1.9.2/inst/demos/prob.BetaDistribution.var000066400000000000000000000003451524624707500243630ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Beta distribution %! pd = makedist ("Beta", "a", 2, "b", 5) %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## beta distribution. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.N000066400000000000000000000012071524624707500246450ustar00rootroot00000000000000%!demo %! ## Create a binomial distribution with default parameters %! pd = makedist ("Binomial") %! %! ## Query parameter 'N' (number of trials) %! pd.N %! %! ## Set parameter 'N' %! pd.N = 10 %! %! ## Use this to initialize or modify the number of trials in a binomial %! ## distribution. The number of trials must be a positive integer. %!demo %! ## Create a binomial distribution object by calling its constructor %! pd = BinomialDistribution (10, 0.3) %! %! ## Query parameter 'N' %! pd.N %! %! ## This demonstrates direct construction with specific parameters, useful %! ## for defining a binomial distribution with known number of trials. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.cdf000066400000000000000000000013221524624707500252020ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Binomial distribution %! x = 0:10; %! pd1 = makedist ("Binomial", "N", 10, "p", 0.2); %! pd2 = makedist ("Binomial", "N", 10, "p", 0.5); %! pd3 = makedist ("Binomial", "N", 10, "p", 0.8); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "*b", x, p2, "*g", x, p3, "*r") %! grid on %! legend ({"N = 10, p = 0.2", "N = 10, p = 0.5", "N = 10, p = 0.8"}, ... %! "location", "southeast") %! title ("Binomial CDF") %! xlabel ("Number of successes") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different binomial distributions, showing how probability accumulates. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.icdf000066400000000000000000000013501524624707500253540ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Binomial distribution %! p = 0.001:0.001:0.999; %! pd1 = makedist ("Binomial", "N", 10, "p", 0.2); %! pd2 = makedist ("Binomial", "N", 10, "p", 0.5); %! pd3 = makedist ("Binomial", "N", 10, "p", 0.8); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, ".b", p, x2, ".g", p, x3, ".r") %! grid on %! legend ({"N = 10, p = 0.2", "N = 10, p = 0.5", "N = 10, p = 0.8"}, ... %! "location", "northwest") %! title ("Binomial iCDF") %! xlabel ("Probability") %! ylabel ("Number of successes") %! %! ## This demonstrates the inverse CDF (quantiles) for binomial distributions, %! ## useful for finding the number of successes corresponding to given %! ## probabilities. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.iqr000066400000000000000000000004211524624707500252400ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Binomial distribution %! pd = makedist ("Binomial", "N", 20, "p", 0.3) %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.mean000066400000000000000000000005251524624707500253720ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Binomial distributions %! pd1 = makedist ("Binomial", "N", 10, "p", 0.2); %! pd2 = makedist ("Binomial", "N", 10, "p", 0.5); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected number of successes for binomial %! ## distributions with different parameters. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.median000066400000000000000000000005401524624707500257040ustar00rootroot00000000000000%!demo %! ## Compute the median for different Binomial distributions %! pd1 = makedist ("Binomial", "N", 10, "p", 0.2); %! pd2 = makedist ("Binomial", "N", 10, "p", 0.5); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median number of successes, which splits the %! ## distribution into two equal probability halves. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.negloglik000066400000000000000000000007161524624707500264270ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Binomial distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Binomial", "N", 10, "p", 0.3) %! data = random (pd, 100, 1); %! pd_fitted = fitdist (data, "Binomial", "ntrials", 10) %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of a binomial distribution to data, %! ## lower values indicate a better fit. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.p000066400000000000000000000011551524624707500247110ustar00rootroot00000000000000%!demo %! ## Create a binomial distribution with default parameters %! pd = makedist ("Binomial") %! %! ## Query parameter 'p' (probability of success) %! pd.p %! %! ## Set parameter 'p' %! pd.p = 0.4 %! %! ## Use this to initialize or modify the probability of success in each %! ## trial. The probability must be between 0 and 1. %!demo %! ## Create a binomial distribution object by calling its constructor %! pd = BinomialDistribution (10, 0.3) %! %! ## Query parameter 'p' %! pd.p %! %! ## This shows how to set the success probability directly via the %! ## constructor, ideal for modeling specific success rates. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.paramci000066400000000000000000000007741524624707500260740ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Binomial %! ## distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Binomial", "N", 10, "p", 0.3) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "Binomial", "ntrials", 10) %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters, %! ## providing a range of plausible values for p given the data. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.pdf000066400000000000000000000012651524624707500252250ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Binomial distribution %! x = 0:10; %! pd1 = makedist ("Binomial", "N", 10, "p", 0.2); %! pd2 = makedist ("Binomial", "N", 10, "p", 0.5); %! pd3 = makedist ("Binomial", "N", 10, "p", 0.8); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "*b", x, y2, "*g", x, y3, "*r") %! grid on %! legend ({"N = 10, p = 0.2", "N = 10, p = 0.5", "N = 10, p = 0.8"}, ... %! "location", "north") %! title ("Binomial PDF") %! xlabel ("Number of successes") %! ylabel ("Probability") %! %! ## This visualizes the probability mass function for binomial distributions, %! ## showing the likelihood of different numbers of successes. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.plot000066400000000000000000000035561524624707500254370ustar00rootroot00000000000000%!demo %! ## Create a Binomial distribution with fixed parameters N = 20 and p = 0.4, %! ## and plot its PDF. %! %! pd = makedist ("Binomial", "N", 20, "p", 0.4) %! plot (pd) %! title ("Fixed Binomial distribution with N = 20 and p = 0.4") %!demo %! ## Generate a data set of 100 random samples from a Binomial distribution %! ## with parameters N = 10 and p = 0.3. Fit a Binomial distribution to this %! ## data and plot its CDF superimposed over an empirical CDF of the data %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Binomial", "N", 10, "p", 0.3) %! data = random (pd, 100, 1); %! pd = fitdist (data, "Binomial", "ntrials", 10) %! plot (pd, "PlotType", "cdf", "Discrete", true) %! title (sprintf ("Fitted Binomial distribution with N = %d and p = %0.2f", ... %! pd.N, pd.p)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of %! ## the data, useful for assessing model fit. %!demo %! ## Generate a data set of 200 random samples from a Binomial distribution %! ## with parameters N = 10 and p = 0.3. Display a probability plot for the %! ## Binomial distribution fit to the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Binomial", "N", 10, "p", 0.3) %! data = random (pd, 200, 1); %! pd = fitdist (data, "Binomial", "ntrials", 10) %! plot (pd, "PlotType", "probability", "Discrete", true) %! title (sprintf (["Probability plot of fitted Binomial distribution with " ... %! "N = %d and p = %0.2f"], pd.N, pd.p)); %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast"); %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the binomial model is appropriate. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.proflik000066400000000000000000000010571524624707500261210ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the probability parameter of %! ## a fitted Binomial distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Binomial", "N", 10, "p", 0.3) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "Binomial", "ntrials", 10) %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the success probability %! ## (p), helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.random000066400000000000000000000010561524624707500257320ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Binomial distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Binomial", "N", 10, "p", 0.3) %! samples = random (pd, 100, 1); %! hist (samples, 0:10) %! title ("Histogram of 100 random samples from Binomial(N=10, p=0.3)") %! xlabel ("Number of successes") %! ylabel ("Frequency") %! %! ## This generates random samples from a binomial distribution, useful for %! ## simulating experiments with a fixed number of trials and success %! ## probability. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.std000066400000000000000000000004111524624707500252360ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Binomial distribution %! pd = makedist ("Binomial", "N", 20, "p", 0.3) %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the %! ## variability in the number of successes. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.truncate000066400000000000000000000023041524624707500262740ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Binomial distribution, with parameters N = 10 and %! ## p = 0.3, truncated at [2, 8] intervals. Generate 10000 random samples %! ## from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Binomial", "N", 10, "p", 0.3) %! t = truncate (pd, 2, 8) %! data = random (t, 10000, 1); %! %! ## Histogram data for range 2 to 8 %! edges = 1.5:1:8.5; %! centers = 2:8; %! counts = histc (data, edges); %! counts = counts(1:end-1); # Remove extra edge bin %! probs = counts / sum(counts); # Normalize to get probabilities %! %! ## Plot histogram bars %! bar (centers, probs, 0.5, "facecolor", [0.6 0.6 1]); %! hold on; %! %! ## PMF of the truncated distribution %! pmf = pdf (t, centers); %! plot (centers, pmf, 'r-', "linewidth", 2); %! %! title ("Binomial distribution (N = 10, p = 0.3) truncated at [2, 8]"); %! xlabel ("x"); %! ylabel ("Probability"); %! legend ("Histogram", "Truncated PMF"); %! hold off %! %! ## This demonstrates truncating a binomial distribution to a specific range %! ## and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.BinomialDistribution.var000066400000000000000000000004061524624707500252400ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Binomial distribution %! pd = makedist ("Binomial", "N", 20, "p", 0.3) %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## number of successes in the distribution. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.beta000066400000000000000000000013001524624707500270670ustar00rootroot00000000000000%!demo %! ## Create a Birnbaum-Saunders distribution with default parameters %! pd = makedist ("BirnbaumSaunders") %! %! ## Query parameter 'beta' (scale parameter) %! pd.beta %! %! ## Set parameter 'beta' %! pd.beta = 2 %! %! ## Use this to initialize or modify the scale parameter of a Birnbaum-Saunders %! ## distribution. The scale parameter must be a positive real scalar. %!demo %! ## Create a Birnbaum-Saunders distribution object by calling its constructor %! pd = BirnbaumSaundersDistribution (1.5, 0.5) %! %! ## Query parameter 'beta' %! pd.beta %! %! ## This demonstrates direct construction with a specific scale parameter, %! ## useful for modeling time-to-failure data with a known scale. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.cdf000066400000000000000000000015031524624707500267150ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Birnbaum-Saunders distribution %! x = 0:0.01:5; %! pd1 = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.2); %! pd2 = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5); %! pd3 = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.8); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"beta = 1, gamma = 0.2", "beta = 1, gamma = 0.5", "beta = 1, gamma = 0.8"}, ... %! "location", "southeast") %! title ("Birnbaum-Saunders CDF") %! xlabel ("Time to failure") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Birnbaum-Saunders distributions, showing how probability %! ## accumulates over time-to-failure. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.gamma000066400000000000000000000013061524624707500272440ustar00rootroot00000000000000%!demo %! ## Create a Birnbaum-Saunders distribution with default parameters %! pd = makedist ("BirnbaumSaunders") %! %! ## Query parameter 'gamma' (shape parameter) %! pd.gamma %! %! ## Set parameter 'gamma' %! pd.gamma = 0.8 %! %! ## Use this to initialize or modify the shape parameter in a Birnbaum-Saunders %! ## distribution. The shape parameter must be a positive real scalar. %!demo %! ## Create a Birnbaum-Saunders distribution object by calling its constructor %! pd = BirnbaumSaundersDistribution (1.5, 0.5) %! %! ## Query parameter 'gamma' %! pd.gamma %! %! ## This shows how to set the shape parameter directly via the constructor, %! ## ideal for modeling specific variability in failure times. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.icdf000066400000000000000000000014661524624707500270760ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Birnbaum-Saunders distribution %! p = 0.001:0.001:0.999; %! pd1 = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.2); %! pd2 = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5); %! pd3 = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.8); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"beta = 1, gamma = 0.2", "beta = 1, gamma = 0.5", "beta = 1, gamma = 0.8"}, ... %! "location", "northwest") %! title ("Birnbaum-Saunders iCDF") %! xlabel ("Probability") %! ylabel ("Time to failure") %! %! ## This demonstrates the inverse CDF (quantiles) for Birnbaum-Saunders %! ## distributions, useful for finding the time-to-failure corresponding to %! ## given probabilities. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.iqr000066400000000000000000000005451524624707500267610ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Birnbaum-Saunders distribution %! pd = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5) %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, useful for understanding variability %! ## in failure times. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.mean000066400000000000000000000006051524624707500271030ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Birnbaum-Saunders distributions %! pd1 = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.2); %! pd2 = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected time to failure for Birnbaum-Saunders %! ## distributions with different shape parameters. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.median000066400000000000000000000006011524624707500274140ustar00rootroot00000000000000%!demo %! ## Compute the median for different Birnbaum-Saunders distributions %! pd1 = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.2); %! pd2 = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median time to failure, which splits the distribution %! ## into two equal probability halves. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.negloglik000066400000000000000000000007471524624707500301450ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Birnbaum-Saunders distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5) %! data = random (pd, 100, 1); %! pd_fitted = fitdist (data, "BirnbaumSaunders") %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of a Birnbaum-Saunders distribution to %! ## data, lower values indicate a better fit. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.paramci000066400000000000000000000010271524624707500275760ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Birnbaum-Saunders %! ## distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "BirnbaumSaunders") %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (beta %! ## and gamma), providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.pdf000066400000000000000000000014261524624707500267360ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Birnbaum-Saunders distribution %! x = 0:0.01:5; %! pd1 = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.2); %! pd2 = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5); %! pd3 = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.8); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"beta = 1, gamma = 0.2", "beta = 1, gamma = 0.5", "beta = 1, gamma = 0.8"}, ... %! "location", "northeast") %! title ("Birnbaum-Saunders PDF") %! xlabel ("Time to failure") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Birnbaum-Saunders %! ## distributions, showing the likelihood of different times to failure. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.plot000066400000000000000000000040511524624707500271400ustar00rootroot00000000000000%!demo %! ## Create a Birnbaum-Saunders distribution with fixed parameters β = 1 and %! ## γ = 0.5 and plot its PDF. %! %! pd = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5) %! plot (pd) %! title ("Fixed Birnbaum-Saunders distribution with beta = 1 and gamma = 0.5") %!demo %! ## Generate a data set of 100 random samples from a Birnbaum-Saunders %! ## distribution with parameters β = 1 and γ = 0.5. Fit a Birnbaum-Saunders %! ## distribution to this data and plot its CDF superimposed over an empirical %! ## CDF. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5) %! data = random (pd_fixed, 100, 1); %! pd_fitted = fitdist (data, "BirnbaumSaunders") %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Birnbaum-Saunders distribution with β = %0.2f and γ = %0.2f"; %! title (sprintf (txt, pd_fitted.beta, pd_fitted.gamma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. %!demo %! ## Generate a data set of 200 random samples from a Birnbaum-Saunders %! ## distribution with parameters β = 1 and γ = 0.5. Display a probability %! ## plot for the Birnbaum-Saunders distribution fit to the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5) %! data = random (pd_fixed, 200, 1); %! pd_fitted = fitdist (data, "BirnbaumSaunders") %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Birnbaum-Saunders", ... %! " distribution with β = %0.2f and γ = %0.2f"); %! title (sprintf (txt, pd_fitted.beta, pd_fitted.gamma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Birnbaum-Saunders model is appropriate. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.proflik000066400000000000000000000010711524624707500276270ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the shape parameter of a fitted %! ## Birnbaum-Saunders distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "BirnbaumSaunders") %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the shape parameter (gamma), %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.random000066400000000000000000000010711524624707500274410ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Birnbaum-Saunders distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5) %! samples = random (pd, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Birnbaum-Saunders(beta=1, gamma=0.5)") %! xlabel ("Time to failure") %! ylabel ("Frequency") %! %! ## This generates random samples from a Birnbaum-Saunders distribution, useful %! ## for simulating time-to-failure data under cyclic loading. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.std000066400000000000000000000004301524624707500267510ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Birnbaum-Saunders distribution %! pd = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5) %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in time to failure. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.truncate000066400000000000000000000015441524624707500300130ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Birnbaum-Saunders distribution, with parameters beta = 1 %! ## and gamma = 0.5, truncated at [0.5, 2] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5) %! t = truncate (pd, 0.5, 2) %! data = random (t, 10000, 1); %! %! ## Plot histogram and fitted PDF %! plot (t) %! hold on %! hist (data, 100, 50) %! hold off %! title ("Birnbaum-Saunders distribution (beta = 1, gamma = 0.5) truncated at [0.5, 2]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Birnbaum-Saunders distribution to a specific %! ## range and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.BirnbaumSaundersDistribution.var000066400000000000000000000004311524624707500267500ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Birnbaum-Saunders distribution %! pd = makedist ("BirnbaumSaunders", "beta", 1, "gamma", 0.5) %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## time to failure in the distribution. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.alpha000066400000000000000000000012071524624707500247150ustar00rootroot00000000000000%!demo %! ## Create a Burr distribution with default parameters %! pd = makedist ("Burr") %! %! ## Query parameter 'alpha' (scale parameter) %! pd.alpha %! %! ## Set parameter 'alpha' %! pd.alpha = 2 %! %! ## Use this to initialize or modify the scale parameter of a Burr %! ## distribution. The scale parameter must be a positive real scalar. %!demo %! ## Create a Burr distribution object by calling its constructor %! pd = BurrDistribution (1.5, 2, 1) %! %! ## Query parameter 'alpha' %! pd.alpha %! %! ## This demonstrates direct construction with a specific scale parameter, %! ## ideal for modeling specific household income distributions. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.c000066400000000000000000000012261524624707500240530ustar00rootroot00000000000000%!demo %! ## Create a Burr distribution with default parameters %! pd = makedist ("Burr") %! %! ## Query parameter 'c' (first shape parameter) %! pd.c %! %! ## Set parameter 'c' %! pd.c = 3 %! %! ## Use this to initialize or modify the first shape parameter in a Burr %! ## distribution. The first shape parameter must be a positive real scalar. %!demo %! ## Create a Burr distribution object by calling its constructor %! pd = BurrDistribution (1, 3, 1) %! %! ## Query parameter 'c' %! pd.c %! %! ## This shows how to set the first shape parameter directly via the constructor, %! ## useful for modeling variability in non-negative data like household income. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.cdf000066400000000000000000000014101524624707500243600ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Burr distribution %! x = 0:0.01:5; %! pd1 = makedist ("Burr", "alpha", 1, "c", 2, "k", 1); %! pd2 = makedist ("Burr", "alpha", 1, "c", 3, "k", 1); %! pd3 = makedist ("Burr", "alpha", 1, "c", 4, "k", 1); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"alpha=1, c=2, k=1", "alpha=1, c=3, k=1", "alpha=1, c=4, k=1"}, ... %! "location", "southeast") %! title ("Burr CDF") %! xlabel ("values in x") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Burr distributions, showing how probability accumulates %! ## over non-negative values like income levels. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.icdf000066400000000000000000000013571524624707500245430ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Burr distribution %! p = 0.001:0.001:0.999; %! pd1 = makedist ("Burr", "alpha", 1, "c", 2, "k", 1); %! pd2 = makedist ("Burr", "alpha", 1, "c", 3, "k", 1); %! pd3 = makedist ("Burr", "alpha", 1, "c", 4, "k", 1); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"alpha=1, c=2, k=1", "alpha=1, c=3, k=1", "alpha=1, c=4, k=1"}, ... %! "location", "northwest") %! title ("Burr iCDF") %! xlabel ("Probability") %! ylabel ("values in x") %! %! ## This demonstrates the inverse CDF (quantiles) for Burr distributions, %! ## useful for finding the value (e.g., income level) corresponding to given %! ## probabilities. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.iqr000066400000000000000000000005341524624707500244250ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Burr distribution %! pd = makedist ("Burr", "alpha", 1, "c", 2, "k", 1) %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, useful for understanding variability %! ## in data like household income. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.k000066400000000000000000000012261524624707500240630ustar00rootroot00000000000000%!demo %! ## Create a Burr distribution with default parameters %! pd = makedist ("Burr") %! %! ## Query parameter 'k' (second shape parameter) %! pd.k %! %! ## Set parameter 'k' %! pd.k = 2 %! %! ## Use this to initialize or modify the second shape parameter in a Burr %! ## distribution. The second shape parameter must be a positive real scalar. %!demo %! ## Create a Burr distribution object by calling its constructor %! pd = BurrDistribution (1, 2, 2) %! %! ## Query parameter 'k' %! pd.k %! %! ## This shows how to set the second shape parameter directly via the constructor, %! ## ideal for tailoring the tail behavior in distributions for income data. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.mean000066400000000000000000000006311524624707500245500ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Burr distributions %! pd1 = makedist ("Burr", "alpha", 1, "c", 2, "k", 1); %! pd2 = makedist ("Burr", "alpha", 1, "c", 3, "k", 1); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Burr distributions with %! ## different shape parameters, provided the parameters allow a finite mean %! ## (e.g., when c * k > 1). statistics-release-1.9.2/inst/demos/prob.BurrDistribution.median000066400000000000000000000005671524624707500250750ustar00rootroot00000000000000%!demo %! ## Compute the median for different Burr distributions %! pd1 = makedist ("Burr", "alpha", 1, "c", 2, "k", 1); %! pd2 = makedist ("Burr", "alpha", 1, "c", 3, "k", 1); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution into %! ## two equal probability halves, e.g., median household income. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.negloglik000066400000000000000000000007231524624707500256050ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Burr distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Burr", "alpha", 1, "c", 2, "k", 1) %! data = random (pd, 100, 1); %! pd_fitted = fitdist (data, "Burr") %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of a Burr distribution to data; %! ## lower values indicate a better fit, e.g., for income modeling. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.paramci000066400000000000000000000010531524624707500252430ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Burr distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Burr", "alpha", 1, "c", 2, "k", 1) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "Burr") %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (alpha, %! ## c, k), providing a range of plausible values given the data, e.g., for %! ## uncertainty in income distribution parameters. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.pdf000066400000000000000000000013171524624707500244030ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Burr distribution %! x = 0:0.01:5; %! pd1 = makedist ("Burr", "alpha", 1, "c", 2, "k", 1); %! pd2 = makedist ("Burr", "alpha", 1, "c", 3, "k", 1); %! pd3 = makedist ("Burr", "alpha", 1, "c", 4, "k", 1); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"alpha=1, c=2, k=1", "alpha=1, c=3, k=1", "alpha=1, c=4, k=1"}, ... %! "location", "northeast") %! title ("Burr PDF") %! xlabel ("values in x") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Burr distributions, %! ## showing the likelihood of different values, e.g., income levels. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.plot000066400000000000000000000037151524624707500246140ustar00rootroot00000000000000%!demo %! ## Create a Burr distribution with fixed parameters alpha = 1, c = 2, k = 1 %! ## and plot its PDF. %! %! pd = makedist ("Burr", "alpha", 1, "c", 2, "k", 1) %! plot (pd) %! title ("Fixed Burr distribution with alpha = 1, c = 2, k = 1") %!demo %! ## Generate a data set of 100 random samples from a Burr distribution with %! ## parameters alpha = 1, c = 2, k = 1. Fit a Burr distribution to this data %! ## and plot its CDF superimposed over an empirical CDF. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("Burr", "alpha", 1, "c", 2, "k", 1) %! data = random (pd_fixed, 100, 1); %! pd_fitted = fitdist (data, "Burr") %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Burr distribution with alpha = %0.2f, c = %0.2f, k = %0.2f"; %! title (sprintf (txt, pd_fitted.alpha, pd_fitted.c, pd_fitted.k)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit in income data. %!demo %! ## Generate a data set of 200 random samples from a Burr distribution with %! ## parameters alpha = 1, c = 2, k = 1. Display a probability plot for the %! ## Burr distribution fit to the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("Burr", "alpha", 1, "c", 2, "k", 1) %! data = random (pd_fixed, 200, 1); %! pd_fitted = fitdist (data, "Burr") %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Burr distribution with ", ... %! "alpha = %0.2f, c = %0.2f, k = %0.2f"); %! title (sprintf (txt, pd_fitted.alpha, pd_fitted.c, pd_fitted.k)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Burr model is appropriate for the dataset. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.proflik000066400000000000000000000011061524624707500252740ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the first shape parameter of a %! ## fitted Burr distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Burr", "alpha", 1, "c", 2, "k", 1) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "Burr") %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the first shape parameter (c), %! ## helping to understand the uncertainty in parameter estimates for models %! ## like household income. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.random000066400000000000000000000010331524624707500251050ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Burr distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Burr", "alpha", 1, "c", 2, "k", 3) %! samples = random (pd, 50, 1); %! hist (samples, 90) %! title ("Histogram of 50 random samples from Burr(alpha=1, c=2, k=3)") %! xlabel ("values in x") %! ylabel ("Frequency") %! %! ## This generates random samples from a Burr distribution, useful for %! ## simulating non-negative data like household income under specific parameters. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.std000066400000000000000000000004451524624707500244250ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Burr distribution %! pd = makedist ("Burr", "alpha", 1, "c", 2, "k", 1) %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in the distribution, e.g., spread of household income. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.truncate000066400000000000000000000015361524624707500254620ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Burr distribution, with parameters alpha = 1, c = 2, k = 1, %! ## truncated at [0.5, 2.5] intervals. Generate 10000 random samples from this %! ## truncated distribution and superimpose a histogram scaled accordingly %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Burr", "alpha", 1, "c", 2, "k", 1) %! t = truncate (pd, 0.5, 2.5) %! data = random (t, 10000, 1); %! %! ## Plot histogram and fitted PDF %! plot (t) %! hold on %! hist (data, 100, 50) %! hold off %! title ("Burr distribution (alpha = 1, c = 2, k = 1) truncated at [0.5, 2.5]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Burr distribution to a specific range and %! ## visualizing the resulting distribution with random samples, useful for %! ## bounded data scenarios. statistics-release-1.9.2/inst/demos/prob.BurrDistribution.var000066400000000000000000000004231524624707500244170ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Burr distribution %! pd = makedist ("Burr", "alpha", 1, "c", 2, "k", 1) %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## distribution, e.g., variability in household income. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.cdf000066400000000000000000000012521524624707500257400ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Exponential distribution %! x = 0:0.01:10; %! pd1 = makedist ("Exponential", "mu", 1); %! pd2 = makedist ("Exponential", "mu", 2); %! pd3 = makedist ("Exponential", "mu", 3); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"mu = 1", "mu = 2", "mu = 3"}, "location", "southeast") %! title ("Exponential CDF") %! xlabel ("values in x") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Exponential distributions, showing how probability %! ## accumulates over values. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.icdf000066400000000000000000000012301524624707500261050ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Exponential distribution %! p = 0.001:0.001:0.999; %! pd1 = makedist ("Exponential", "mu", 1); %! pd2 = makedist ("Exponential", "mu", 2); %! pd3 = makedist ("Exponential", "mu", 3); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"mu = 1", "mu = 2", "mu = 3"}, "location", "northwest") %! title ("Exponential iCDF") %! xlabel ("Probability") %! ylabel ("values in x") %! %! ## This demonstrates the inverse CDF (quantiles) for Exponential %! ## distributions, useful for finding values corresponding to %! ## given probabilities. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.iqr000066400000000000000000000005131524624707500257760ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for an Exponential distribution %! pd = makedist ("Exponential", "mu", 2) %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, useful for understanding variability %! ## in waiting times. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.mean000066400000000000000000000005041524624707500261230ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Exponential distributions %! pd1 = makedist ("Exponential", "mu", 1); %! pd2 = makedist ("Exponential", "mu", 2); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Exponential %! ## distributions with different mean parameters. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.median000066400000000000000000000005071524624707500264430ustar00rootroot00000000000000%!demo %! ## Compute the median for different Exponential distributions %! pd1 = makedist ("Exponential", "mu", 1); %! pd2 = makedist ("Exponential", "mu", 2); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution %! ## into two equal probability halves. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.mu000066400000000000000000000012141524624707500256230ustar00rootroot00000000000000%!demo %! ## Create an Exponential distribution with default parameter %! pd = makedist ("Exponential") %! %! ## Query parameter 'mu' (mean parameter) %! pd.mu %! %! ## Set parameter 'mu' %! pd.mu = 2 %! %! ## Use this to initialize or modify the mean parameter of an Exponential %! ## distribution. The mean parameter must be a positive real scalar. %!demo %! ## Create an Exponential distribution object by calling its constructor %! pd = ExponentialDistribution (1.5) %! %! ## Query parameter 'mu' %! pd.mu %! %! ## This demonstrates direct construction with a specific mean parameter, %! ## useful for modeling waiting times with a known average. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.negloglik000066400000000000000000000007021524624707500271560ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Exponential distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Exponential", "mu", 2) %! data = random (pd, 100, 1); %! pd_fitted = fitdist (data, "Exponential") %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of an Exponential distribution to %! ## data, lower values indicate a better fit. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.paramci000066400000000000000000000007521524624707500266240ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Exponential %! ## distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Exponential", "mu", 2) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "Exponential") %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameter (mu), %! ## providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.pdf000066400000000000000000000011741524624707500257600ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Exponential distribution %! x = 0:0.01:10; %! pd1 = makedist ("Exponential", "mu", 1); %! pd2 = makedist ("Exponential", "mu", 2); %! pd3 = makedist ("Exponential", "mu", 3); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"mu = 1", "mu = 2", "mu = 3"}, "location", "northeast") %! title ("Exponential PDF") %! xlabel ("values in x") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Exponential %! ## distributions, showing the likelihood of different values. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.plot000066400000000000000000000034501524624707500261640ustar00rootroot00000000000000%!demo %! ## Create an Exponential distribution with fixed parameter mu = 2 and plot its PDF. %! %! pd = makedist ("Exponential", "mu", 2) %! plot (pd) %! title ("Fixed Exponential distribution with mu = 2") %!demo %! ## Generate a data set of 100 random samples from an Exponential %! ## distribution with parameter mu = 2. Fit an Exponential %! ## distribution to this data and plot its CDF superimposed over an empirical %! ## CDF. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("Exponential", "mu", 2) %! data = random (pd_fixed, 100, 1); %! pd_fitted = fitdist (data, "Exponential") %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Exponential distribution with mu = %0.2f"; %! title (sprintf (txt, pd_fitted.mu)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. %!demo %! ## Generate a data set of 200 random samples from an Exponential %! ## distribution with parameter mu = 2. Display a probability %! ## plot for the Exponential distribution fit to the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("Exponential", "mu", 2) %! data = random (pd_fixed, 200, 1); %! pd_fitted = fitdist (data, "Exponential") %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Exponential", ... %! " distribution with mu = %0.2f"); %! title (sprintf (txt, pd_fitted.mu)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Exponential model is appropriate. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.proflik000066400000000000000000000010431524624707500266500ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the mean parameter of a fitted %! ## Exponential distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Exponential", "mu", 2) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "Exponential") %! [nlogL, param] = proflik (pd_fitted, 1, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the mean parameter (mu), %! ## helping to understand the uncertainty in parameter estimates given the data. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.random000066400000000000000000000007761524624707500264760ustar00rootroot00000000000000%!demo %! ## Generate random samples from an Exponential distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Exponential", "mu", 2) %! samples = random (pd, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Exponential(mu=2)") %! xlabel ("values in x") %! ylabel ("Frequency") %! %! ## This generates random samples from an Exponential distribution, useful %! ## for simulating waiting times or inter-arrival times. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.std000066400000000000000000000003771524624707500260050ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for an Exponential distribution %! pd = makedist ("Exponential", "mu", 2) %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in the distribution. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.truncate000066400000000000000000000014271524624707500270350ustar00rootroot00000000000000%!demo %! ## Plot the PDF of an Exponential distribution, with parameter mu = 2, %! ## truncated at [1, 5] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Exponential", "mu", 2) %! t = truncate (pd, 1, 3) %! data = random (t, 10000, 1); %! %! ## Plot histogram and fitted PDF %! plot (t) %! hold on %! hist (data, 100, 50) %! hold off %! title ("Exponential distribution (mu = 2) truncated at [1, 5]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating an Exponential distribution to a specific %! ## range and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.ExponentialDistribution.var000066400000000000000000000003501524624707500257720ustar00rootroot00000000000000%!demo %! ## Compute the variance for an Exponential distribution %! pd = makedist ("Exponential", "mu", 2) %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## distribution. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.cdf000066400000000000000000000014231524624707500260600ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Extreme Value distribution %! x = -5:0.01:10; %! pd1 = makedist ("ExtremeValue", "mu", 0, "sigma", 0.5); %! pd2 = makedist ("ExtremeValue", "mu", 0, "sigma", 1); %! pd3 = makedist ("ExtremeValue", "mu", 0, "sigma", 1.5); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 1.5"}, ... %! "location", "southeast") %! title ("Extreme Value CDF") %! xlabel ("Values") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Extreme Value distributions, showing how probability %! ## accumulates over extreme values. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.icdf000066400000000000000000000014251524624707500262330ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Extreme Value distribution %! p = 0.001:0.001:0.999; %! pd1 = makedist ("ExtremeValue", "mu", 0, "sigma", 0.5); %! pd2 = makedist ("ExtremeValue", "mu", 0, "sigma", 1); %! pd3 = makedist ("ExtremeValue", "mu", 0, "sigma", 1.5); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 1.5"}, ... %! "location", "northwest") %! title ("Extreme Value iCDF") %! xlabel ("Probability") %! ylabel ("Values") %! %! ## This demonstrates the inverse CDF (quantiles) for Extreme Value %! ## distributions, useful for finding the value corresponding to %! ## given probabilities in extreme event modeling. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.iqr000066400000000000000000000005331524624707500261200ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for an Extreme Value distribution %! pd = makedist ("ExtremeValue", "mu", 0, "sigma", 1) %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, useful for understanding variability %! ## in extreme values. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.mean000066400000000000000000000005451524624707500262500ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Extreme Value distributions %! pd1 = makedist ("ExtremeValue", "mu", 0, "sigma", 0.5); %! pd2 = makedist ("ExtremeValue", "mu", 0, "sigma", 1); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Extreme Value %! ## distributions with different scale parameters. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.median000066400000000000000000000005451524624707500265650ustar00rootroot00000000000000%!demo %! ## Compute the median for different Extreme Value distributions %! pd1 = makedist ("ExtremeValue", "mu", 0, "sigma", 0.5); %! pd2 = makedist ("ExtremeValue", "mu", 0, "sigma", 1); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution %! ## into two equal probability halves. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.mu000066400000000000000000000012421524624707500257440ustar00rootroot00000000000000%!demo %! ## Create an Extreme Value distribution with default parameters %! pd = makedist ("ExtremeValue") %! %! ## Query parameter 'mu' (location parameter) %! pd.mu %! %! ## Set parameter 'mu' %! pd.mu = 2 %! %! ## Use this to initialize or modify the location parameter of an Extreme Value %! ## distribution. The location parameter must be a real scalar. %!demo %! ## Create an Extreme Value distribution object by calling its constructor %! pd = ExtremeValueDistribution (1.5, 0.5) %! %! ## Query parameter 'mu' %! pd.mu %! %! ## This demonstrates direct construction with a specific location parameter, %! ## useful for modeling the position of maxima in data sets. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.negloglik000066400000000000000000000007241524624707500273020ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Extreme Value distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("ExtremeValue", "mu", 0, "sigma", 1) %! data = random (pd, 100, 1); %! pd_fitted = fitdist (data, "ExtremeValue") %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of an Extreme Value distribution to %! ## data, lower values indicate a better fit. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.paramci000066400000000000000000000010051524624707500267340ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Extreme Value %! ## distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("ExtremeValue", "mu", 0, "sigma", 1) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "ExtremeValue") %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (mu %! ## and sigma), providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.pdf000066400000000000000000000013451524624707500261000ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Extreme Value distribution %! x = -5:0.01:10; %! pd1 = makedist ("ExtremeValue", "mu", 0, "sigma", 0.5); %! pd2 = makedist ("ExtremeValue", "mu", 0, "sigma", 1); %! pd3 = makedist ("ExtremeValue", "mu", 0, "sigma", 1.5); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 1.5"}, ... %! "location", "northeast") %! title ("Extreme Value PDF") %! xlabel ("Values") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Extreme Value %! ## distributions, showing the likelihood of different extreme values. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.plot000066400000000000000000000037521524624707500263110ustar00rootroot00000000000000%!demo %! ## Create an Extreme Value distribution with fixed parameters mu = 0 and %! ## sigma = 1 and plot its PDF. %! %! pd = makedist ("ExtremeValue", "mu", 0, "sigma", 1) %! plot (pd) %! title ("Fixed Extreme Value distribution with mu = 0 and sigma = 1") %!demo %! ## Generate a data set of 100 random samples from an Extreme Value %! ## distribution with parameters mu = 0 and sigma = 1. Fit an Extreme Value %! ## distribution to this data and plot its CDF superimposed over an empirical %! ## CDF. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("ExtremeValue", "mu", 0, "sigma", 1) %! data = random (pd_fixed, 100, 1); %! pd_fitted = fitdist (data, "ExtremeValue") %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Extreme Value distribution with mu = %0.2f and sigma = %0.2f"; %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. %!demo %! ## Generate a data set of 200 random samples from an Extreme Value %! ## distribution with parameters mu = 0 and sigma = 1. Display a probability %! ## plot for the Extreme Value distribution fit to the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("ExtremeValue", "mu", 0, "sigma", 1) %! data = random (pd_fixed, 200, 1); %! pd_fitted = fitdist (data, "ExtremeValue") %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Extreme Value", ... %! " distribution with mu = %0.2f and sigma = %0.2f"); %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Extreme Value model is appropriate. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.proflik000066400000000000000000000010511524624707500267670ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the scale parameter of a fitted %! ## Extreme Value distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("ExtremeValue", "mu", 0, "sigma", 1) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "ExtremeValue") %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the scale parameter (sigma), %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.random000066400000000000000000000010371524624707500266050ustar00rootroot00000000000000%!demo %! ## Generate random samples from an Extreme Value distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("ExtremeValue", "mu", 0, "sigma", 1) %! samples = random (pd, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from ExtremeValue(mu=0, sigma=1)") %! xlabel ("Values") %! ylabel ("Frequency") %! %! ## This generates random samples from an Extreme Value distribution, useful %! ## for simulating extreme events like maximum values in data sets. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.sigma000066400000000000000000000012601524624707500264230ustar00rootroot00000000000000%!demo %! ## Create an Extreme Value distribution with default parameters %! pd = makedist ("ExtremeValue") %! %! ## Query parameter 'sigma' (scale parameter) %! pd.sigma %! %! ## Set parameter 'sigma' %! pd.sigma = 0.8 %! %! ## Use this to initialize or modify the scale parameter in an Extreme Value %! ## distribution. The scale parameter must be a positive real scalar. %!demo %! ## Create an Extreme Value distribution object by calling its constructor %! pd = ExtremeValueDistribution (1.5, 0.5) %! %! ## Query parameter 'sigma' %! pd.sigma %! %! ## This shows how to set the scale parameter directly via the constructor, %! ## ideal for modeling the spread in extreme value data. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.std000066400000000000000000000004141524624707500261150ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for an Extreme Value distribution %! pd = makedist ("ExtremeValue", "mu", 0, "sigma", 1) %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in extreme values. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.truncate000066400000000000000000000015111524624707500271470ustar00rootroot00000000000000%!demo %! ## Plot the PDF of an Extreme Value distribution, with parameters mu = 0 %! ## and sigma = 1, truncated at [-2, 2] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("ExtremeValue", "mu", 0, "sigma", 1) %! t = truncate (pd, 0.5, 4) %! data = random (t, 10000, 1); %! %! ## Plot histogram and fitted PDF %! plot (t) %! hold on %! hist (data, 100, 50) %! hold off %! title ("Extreme Value distribution (mu = 0, sigma = 1) truncated at [0.5, 4]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating an Extreme Value distribution to a specific %! ## range and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.ExtremeValueDistribution.var000066400000000000000000000004151524624707500261140ustar00rootroot00000000000000%!demo %! ## Compute the variance for an Extreme Value distribution %! pd = makedist ("ExtremeValue", "mu", 0, "sigma", 1) %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## extreme values in the distribution. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.a000066400000000000000000000011551524624707500241620ustar00rootroot00000000000000%!demo %! ## Create a Gamma distribution with default parameters %! pd = makedist ("Gamma") %! %! ## Query parameter 'a' (shape parameter) %! pd.a %! %! ## Set parameter 'a' %! pd.a = 2 %! %! ## Use this to initialize or modify the shape parameter of a Gamma %! ## distribution. The shape parameter must be a positive real scalar. %!demo %! ## Create a Gamma distribution object by calling its constructor %! pd = GammaDistribution (2, 1) %! %! ## Query parameter 'a' %! pd.a %! %! ## This demonstrates direct construction with a specific shape parameter, %! ## useful for modeling waiting times with known variability. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.b000066400000000000000000000011531524624707500241610ustar00rootroot00000000000000%!demo %! ## Create a Gamma distribution with default parameters %! pd = makedist ("Gamma") %! %! ## Query parameter 'b' (scale parameter) %! pd.b %! %! ## Set parameter 'b' %! pd.b = 2 %! %! ## Use this to initialize or modify the scale parameter in a Gamma %! ## distribution. The scale parameter must be a positive real scalar. %!demo %! ## Create a Gamma distribution object by calling its constructor %! pd = GammaDistribution (2, 1) %! %! ## Query parameter 'b' %! pd.b %! %! ## This shows how to set the scale parameter directly via the constructor, %! ## ideal for scaling the distribution in lifetime models. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.cdf000066400000000000000000000012671524624707500245020ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Gamma distribution %! x = 0:0.01:10; %! pd1 = makedist ("Gamma", "a", 1, "b", 1); %! pd2 = makedist ("Gamma", "a", 2, "b", 1); %! pd3 = makedist ("Gamma", "a", 5, "b", 1); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"a = 1, b = 1", "a = 2, b = 1", "a = 5, b = 1"}, ... %! "location", "southeast") %! title ("Gamma CDF") %! xlabel ("Values") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Gamma distributions, showing how probability accumulates %! ## over values. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.icdf000066400000000000000000000012451524624707500246470ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Gamma distribution %! p = 0.001:0.001:0.999; %! pd1 = makedist ("Gamma", "a", 1, "b", 1); %! pd2 = makedist ("Gamma", "a", 2, "b", 1); %! pd3 = makedist ("Gamma", "a", 5, "b", 1); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"a = 1, b = 1", "a = 2, b = 1", "a = 5, b = 1"}, ... %! "location", "northwest") %! title ("Gamma iCDF") %! xlabel ("Probability") %! ylabel ("Values") %! %! ## This demonstrates the inverse CDF (quantiles) for Gamma %! ## distributions, useful for finding values corresponding to %! ## given probabilities. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.iqr000066400000000000000000000005051524624707500245330ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Gamma distribution %! pd = makedist ("Gamma", "a", 2, "b", 1) %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, useful for understanding variability %! ## in waiting times. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.mean000066400000000000000000000004731524624707500246640ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Gamma distributions %! pd1 = makedist ("Gamma", "a", 1, "b", 1); %! pd2 = makedist ("Gamma", "a", 2, "b", 1); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Gamma %! ## distributions with different shape parameters. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.median000066400000000000000000000005031524624707500251730ustar00rootroot00000000000000%!demo %! ## Compute the median for different Gamma distributions %! pd1 = makedist ("Gamma", "a", 1, "b", 1); %! pd2 = makedist ("Gamma", "a", 2, "b", 1); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution %! ## into two equal probability halves. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.negloglik000066400000000000000000000006621524624707500257170ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Gamma distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Gamma", "a", 2, "b", 1) %! data = gamrnd (2, 1, 100, 1); %! pd_fitted = fitdist (data, "Gamma") %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of a Gamma distribution to %! ## data, lower values indicate a better fit. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.paramci000066400000000000000000000007471524624707500253640ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Gamma %! ## distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Gamma", "a", 2, "b", 1) %! data = gamrnd (2, 1, 1000, 1); %! pd_fitted = fitdist (data, "Gamma") %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (a %! ## and b), providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.pdf000066400000000000000000000012111524624707500245040ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Gamma distribution %! x = 0:0.01:10; %! pd1 = makedist ("Gamma", "a", 1, "b", 1); %! pd2 = makedist ("Gamma", "a", 2, "b", 1); %! pd3 = makedist ("Gamma", "a", 5, "b", 1); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"a = 1, b = 1", "a = 2, b = 1", "a = 5, b = 1"}, ... %! "location", "northeast") %! title ("Gamma PDF") %! xlabel ("Values") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Gamma %! ## distributions, showing the likelihood of different values. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.plot000066400000000000000000000034741524624707500247260ustar00rootroot00000000000000%!demo %! ## Create a Gamma distribution with fixed parameters a = 2 and %! ## b = 1 and plot its PDF. %! %! pd = makedist ("Gamma", "a", 2, "b", 1) %! plot (pd) %! title ("Fixed Gamma distribution with a = 2 and b = 1") %!demo %! ## Generate a data set of 100 random samples from a Gamma %! ## distribution with parameters a = 2 and b = 1. Fit a Gamma %! ## distribution to this data and plot its CDF superimposed over an empirical %! ## CDF. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("Gamma", "a", 2, "b", 1) %! data = gamrnd (2, 1, 100, 1); %! pd_fitted = fitdist (data, "Gamma") %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Gamma distribution with a = %0.2f and b = %0.2f"; %! title (sprintf (txt, pd_fitted.a, pd_fitted.b)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. %!demo %! ## Generate a data set of 200 random samples from a Gamma %! ## distribution with parameters a = 2 and b = 1. Display a probability %! ## plot for the Gamma distribution fit to the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("Gamma", "a", 2, "b", 1) %! data = gamrnd (2, 1, 200, 1); %! pd_fitted = fitdist (data, "Gamma") %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Gamma", ... %! " distribution with a = %0.2f and b = %0.2f"); %! title (sprintf (txt, pd_fitted.a, pd_fitted.b)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Gamma model is appropriate. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.proflik000066400000000000000000000010141524624707500254020ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the shape parameter of a fitted %! ## Gamma distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Gamma", "a", 2, "b", 1) %! data = gamrnd (2, 1, 1000, 1); %! pd_fitted = fitdist (data, "Gamma") %! [nlogL, param] = proflik (pd_fitted, 1, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the shape parameter (a), %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.random000066400000000000000000000007611524624707500252240ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Gamma distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Gamma", "a", 2, "b", 1) %! samples = random (pd, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Gamma(a=2, b=1)") %! xlabel ("Values") %! ylabel ("Frequency") %! %! ## This generates random samples from a Gamma distribution, useful %! ## for simulating waiting times or other positive skewed data. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.std000066400000000000000000000003711524624707500245330ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Gamma distribution %! pd = makedist ("Gamma", "a", 2, "b", 1) %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in the distribution. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.truncate000066400000000000000000000014231524624707500255650ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Gamma distribution, with parameters a = 2 %! ## and b = 1, truncated at [1, 5] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Gamma", "a", 2, "b", 1) %! t = truncate (pd, -1, 2) %! data = random (t, 10000, 1); %! ## Plot histogram and fitted PDF %! plot (t) %! hold on %! hist (data, 100, 50) %! hold off %! title ("Gamma distribution (a = 2, b = 1) truncated at [-1, 2]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Gamma distribution to a specific %! ## range and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.GammaDistribution.var000066400000000000000000000003421524624707500245270ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Gamma distribution %! pd = makedist ("Gamma", "a", 2, "b", 1) %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## distribution. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.cdf000066400000000000000000000016151524624707500302350ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Generalized Extreme Value distribution %! x = -2:0.01:5; %! pd1 = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0); %! pd2 = makedist ("GeneralizedExtremeValue", "k", 0.2, "sigma", 1, "mu", 0); %! pd3 = makedist ("GeneralizedExtremeValue", "k", -0.2, "sigma", 1, "mu", 0); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"k = 0, sigma = 1, mu = 0", "k = 0.2, sigma = 1, mu = 0", "k = -0.2, sigma = 1, mu = 0"}, ... %! "location", "southeast") %! title ("Generalized Extreme Value CDF") %! xlabel ("Value") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function for %! ## different GEV distributions, showing how probability accumulates over extreme %! ## values with varying shape parameters. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.icdf000066400000000000000000000015271524624707500304100ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Generalized Extreme Value distribution %! p = 0.001:0.001:0.999; %! pd1 = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0); %! pd2 = makedist ("GeneralizedExtremeValue", "k", 0.2, "sigma", 1, "mu", 0); %! pd3 = makedist ("GeneralizedExtremeValue", "k", -0.2, "sigma", 1, "mu", 0); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"k = 0, sigma = 1, mu = 0", "k = 0.2, sigma = 1, mu = 0", "k = -0.2, sigma = 1, mu = 0"}, ... %! "location", "northwest") %! title ("Generalized Extreme Value iCDF") %! xlabel ("Probability") %! ylabel ("Value") %! %! ## This demonstrates the inverse CDF (quantiles) for GEV distributions, useful %! ## for finding extreme values corresponding to given probabilities. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.iqr000066400000000000000000000005751524624707500303000ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Generalized Extreme Value distribution %! pd = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0) %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread of %! ## the middle 50% of the GEV distribution, useful for understanding variability %! ## in extreme values. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.k000066400000000000000000000013711524624707500277320ustar00rootroot00000000000000%!demo %! ## Create a Generalized Extreme Value distribution with default parameters %! pd = makedist ("GeneralizedExtremeValue") %! %! ## Query parameter 'k' (shape parameter) %! pd.k %! %! ## Set parameter 'k' %! pd.k = 0.2 %! %! ## Use this to initialize or modify the shape parameter of a Generalized Extreme %! ## Value distribution. The shape parameter determines the tail behavior and must %! ## be a real scalar. %!demo %! ## Create a Generalized Extreme Value distribution object by calling its constructor %! pd = GeneralizedExtremeValueDistribution (0.1, 1, 0) %! %! ## Query parameter 'k' %! pd.k %! %! ## This demonstrates direct construction with a specific shape parameter, %! ## useful for modeling extreme value data with known tail behavior. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.mean000066400000000000000000000006741524624707500304250ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Generalized Extreme Value distributions %! pd1 = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0); %! pd2 = makedist ("GeneralizedExtremeValue", "k", -0.2, "sigma", 1, "mu", 0); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for GEV distributions with %! ## different shape parameters, useful for predicting average extreme values. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.median000066400000000000000000000006441524624707500307370ustar00rootroot00000000000000%!demo %! ## Compute the median for different Generalized Extreme Value distributions %! pd1 = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0); %! pd2 = makedist ("GeneralizedExtremeValue", "k", -0.2, "sigma", 1, "mu", 0); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median extreme value, which splits the GEV distribution %! ## into two equal probability halves. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.mu000066400000000000000000000013431524624707500301200ustar00rootroot00000000000000%!demo %! ## Create a Generalized Extreme Value distribution with default parameters %! pd = makedist ("GeneralizedExtremeValue") %! %! ## Query parameter 'mu' (location parameter) %! pd.mu %! %! ## Set parameter 'mu' %! pd.mu = 0.5 %! %! ## Use this to initialize or modify the location parameter, which shifts the %! ## Generalized Extreme Value distribution. It must be a real scalar. %!demo %! ## Create a Generalized Extreme Value distribution object by calling its constructor %! pd = GeneralizedExtremeValueDistribution (0.1, 1, 0.5) %! %! ## Query parameter 'mu' %! pd.mu %! %! ## This demonstrates setting the location parameter directly via the constructor, %! ## useful for modeling the central tendency of extreme value data. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.negloglik000066400000000000000000000007631524624707500314570ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Generalized Extreme Value distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0) %! data = random (pd, 100, 1); %! pd_fitted = fitdist (data, "GeneralizedExtremeValue") %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of a GEV distribution to data, lower %! ## values indicate a better fit. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.paramci000066400000000000000000000010621524624707500311110ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Generalized Extreme Value %! ## distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("GeneralizedExtremeValue", "k", -0.5, "sigma", 1, "mu", 2) %! data = random (pd, 5000, 1); %! pd_fitted = fitdist (data, "GeneralizedExtremeValue") %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (k, %! ## sigma, mu), providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.pdf000066400000000000000000000015011524624707500302440ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Generalized Extreme Value distribution %! x = -2:0.01:5; %! pd1 = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0); %! pd2 = makedist ("GeneralizedExtremeValue", "k", 0.2, "sigma", 1, "mu", 0); %! pd3 = makedist ("GeneralizedExtremeValue", "k", -0.2, "sigma", 1, "mu", 0); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"k = 0, sigma = 1, mu = 0", "k = 0.2, sigma = 1, mu = 0", "k = -0.2, sigma = 1, mu = 0"}, ... %! "location", "northeast") %! title ("Generalized Extreme Value PDF") %! xlabel ("Value") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for GEV distributions, %! ## showing the likelihood of different extreme values. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.plot000066400000000000000000000043461524624707500304630ustar00rootroot00000000000000%!demo %! ## Create a Generalized Extreme Value distribution with fixed parameters k = 0, %! ## sigma = 1, mu = 0 and plot its PDF. %! pd = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0) %! plot (pd) %! title ("Fixed Generalized Extreme Value distribution with k = 0, sigma = 1, mu = 0") %! %! ## Use this to visualize the PDF of a GEV distribution with specified parameters. %!demo %! ## Generate a data set of 100 random samples from a Generalized Extreme Value %! ## distribution with parameters k = 0, sigma = 1, mu = 0. Fit a GEV %! ## distribution to this data and plot its CDF superimposed over an empirical CDF. %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0) %! data = random (pd_fixed, 100, 1); %! pd_fitted = fitdist (data, "GeneralizedExtremeValue") %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Generalized Extreme Value distribution with k = %0.2f, sigma = %0.2f, mu = %0.2f"; %! title (sprintf (txt, pd_fitted.k, pd_fitted.sigma, pd_fitted.mu)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. %!demo %! ## Generate a data set of 200 random samples from a Generalized Extreme Value %! ## distribution with parameters k = 0, sigma = 1, mu = 0. Display a probability %! ## plot for the GEV distribution fit to the data. %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0) %! data = random (pd_fixed, 200, 1); %! pd_fitted = fitdist (data, "GeneralizedExtremeValue") %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Generalized Extreme Value", ... %! " distribution with k = %0.2f, sigma = %0.2f, mu = %0.2f"); %! title (sprintf (txt, pd_fitted.k, pd_fitted.sigma, pd_fitted.mu)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted GEV distribution to %! ## the data, useful for checking if the GEV model is appropriate. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.proflik000066400000000000000000000011221524624707500311400ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the shape parameter of a fitted %! ## Generalized Extreme Value distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("GeneralizedExtremeValue", "k", -0.5, "sigma", 1, "mu", 2) %! data = random (pd, 5000, 1); %! pd_fitted = fitdist (data, "GeneralizedExtremeValue") %! [nlogL, param] = proflik (pd_fitted, 1, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the shape parameter (k), %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.random000066400000000000000000000011231524624707500307530ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Generalized Extreme Value distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0) %! samples = random (pd, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Generalized Extreme Value(k=0, sigma=1, mu=0)") %! xlabel ("Value") %! ylabel ("Frequency") %! %! ## This generates random samples from a GEV distribution, useful for simulating %! ## extreme value data, such as maximum wind speeds or flood levels. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.sigma000066400000000000000000000013741524624707500306030ustar00rootroot00000000000000%!demo %! ## Create a Generalized Extreme Value distribution with default parameters %! pd = makedist ("GeneralizedExtremeValue") %! %! ## Query parameter 'sigma' (scale parameter) %! pd.sigma %! %! ## Set parameter 'sigma' %! pd.sigma = 1.5 %! %! ## Use this to initialize or modify the scale parameter, which controls the %! ## spread of the Generalized Extreme Value distribution. It must be a positive %! ## real scalar. %!demo %! ## Create a Generalized Extreme Value distribution object by calling its constructor %! pd = GeneralizedExtremeValueDistribution (0.1, 1.5, 0) %! %! ## Query parameter 'sigma' %! pd.sigma %! %! ## This shows how to set the scale parameter directly via the constructor, %! ## ideal for modeling the spread of extreme value data. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.std000066400000000000000000000005031524624707500302660ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Generalized Extreme Value distribution %! pd = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0) %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in extreme values for the GEV distribution. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.truncate000066400000000000000000000015451524624707500313300ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Generalized Extreme Value distribution, with parameters %! ## k = 0, sigma = 1, mu = 0, truncated at [0, 2] intervals. Generate 10000 %! ## random samples from this truncated distribution and superimpose a histogram %! ## scaled accordingly %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0) %! t = truncate (pd, 0, 2) %! data = random (t, 10000, 1); %! %! ## Plot histogram and fitted PDF %! plot (t) %! hold on %! hist (data, 100, 50) %! hold off %! title ("Generalized Extreme Value distribution (k=0, sigma=1, mu=0) truncated at [0, 2]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a GEV distribution to a specific range and %! ## visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.GeneralizedExtremeValueDistribution.var000066400000000000000000000004531524624707500302700ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Generalized Extreme Value distribution %! pd = makedist ("GeneralizedExtremeValue", "k", 0, "sigma", 1, "mu", 0) %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of extreme %! ## values in the GEV distribution. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.cdf000066400000000000000000000016311524624707500270570ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Generalized Pareto distribution %! x = 0:0.01:5; %! pd1 = makedist ("GeneralizedPareto", "k", 0.2, "sigma", 1, "theta", 0); %! pd2 = makedist ("GeneralizedPareto", "k", 0.5, "sigma", 1, "theta", 0); %! pd3 = makedist ("GeneralizedPareto", "k", 0.8, "sigma", 1, "theta", 0); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"k = 0.2, sigma = 1, theta = 0", "k = 0.5, sigma = 1, theta = 0", "k = 0.8, sigma = 1, theta = 0"}, ... %! "location", "southeast") %! title ("Generalized Pareto CDF") %! xlabel ("Exceedance value") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Generalized Pareto distributions, showing how probability %! ## accumulates over exceedance values in extreme value modeling. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.icdf000066400000000000000000000016131524624707500272300ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Generalized Pareto distribution %! p = 0.001:0.001:0.999; %! pd1 = makedist ("GeneralizedPareto", "k", 0.2, "sigma", 1, "theta", 0); %! pd2 = makedist ("GeneralizedPareto", "k", 0.5, "sigma", 1, "theta", 0); %! pd3 = makedist ("GeneralizedPareto", "k", 0.8, "sigma", 1, "theta", 0); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"k = 0.2, sigma = 1, theta = 0", "k = 0.5, sigma = 1, theta = 0", "k = 0.8, sigma = 1, theta = 0"}, ... %! "location", "northwest") %! title ("Generalized Pareto iCDF") %! xlabel ("Probability") %! ylabel ("Exceedance value") %! %! ## This demonstrates the inverse CDF (quantiles) for Generalized Pareto %! ## distributions, useful for finding the exceedance value corresponding to %! ## given return probabilities in risk assessment. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.iqr000066400000000000000000000005641524624707500271220ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Generalized Pareto distribution %! pd = makedist ("GeneralizedPareto", "k", 0.5, "sigma", 1, "theta", 0) %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, useful for understanding variability %! ## in exceedance values. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.k000066400000000000000000000013501524624707500265530ustar00rootroot00000000000000%!demo %! ## Create a Generalized Pareto distribution with default parameters %! pd = makedist ("GeneralizedPareto") %! %! ## Query parameter 'k' (shape parameter) %! pd.k %! %! ## Set parameter 'k' %! pd.k = 0.5 %! %! ## Use this to initialize or modify the shape parameter of a Generalized Pareto %! ## distribution. The shape parameter can be any real scalar and determines the %! ## tail behavior (heavy-tailed if k>0). %!demo %! ## Create a Generalized Pareto distribution object by calling its constructor %! pd = GeneralizedParetoDistribution (0.5, 1, 0) %! %! ## Query parameter 'k' %! pd.k %! %! ## This demonstrates direct construction with a specific shape parameter, %! ## useful for modeling extreme values with known tail index. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.mean000066400000000000000000000006771524624707500272540ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Generalized Pareto distributions %! pd1 = makedist ("GeneralizedPareto", "k", 0.2, "sigma", 1, "theta", 0); %! pd2 = makedist ("GeneralizedPareto", "k", 0.4, "sigma", 1, "theta", 0); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected exceedance value for Generalized Pareto %! ## distributions with different shape parameters (note: mean is finite only if k<1). statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.median000066400000000000000000000006271524624707500275640ustar00rootroot00000000000000%!demo %! ## Compute the median for different Generalized Pareto distributions %! pd1 = makedist ("GeneralizedPareto", "k", 0.2, "sigma", 1, "theta", 0); %! pd2 = makedist ("GeneralizedPareto", "k", 0.5, "sigma", 1, "theta", 0); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median exceedance value, which splits the distribution %! ## into two equal probability halves. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.negloglik000066400000000000000000000007741524624707500303050ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Generalized Pareto distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("GeneralizedPareto", "k", 0.5, "sigma", 1, "theta", 0) %! data = random (pd, 100, 1); %! pd_fitted = GeneralizedParetoDistribution.fit (data, 0) %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of a Generalized Pareto distribution to %! ## data, lower values indicate a better fit. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.paramci000066400000000000000000000012001524624707500277270ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Generalized Pareto %! ## distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("GeneralizedPareto", "k", 0.5, "sigma", 1, "theta", 0) %! data = random (pd, 1000, 1); %! [phat, ci] = gpfit (data, 0, 0.05, []); %! disp ("Estimated parameters (k, sigma, theta):"), disp (phat) %! disp ("95% Confidence Intervals for k and sigma:"), disp (ci) %! %! ## Use this to obtain confidence intervals for the estimated parameters (k and sigma), %! ## providing a range of plausible values given the data (theta is fixed). statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.pdf000066400000000000000000000015211524624707500270720ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Generalized Pareto distribution %! x = 0:0.01:5; %! pd1 = makedist ("GeneralizedPareto", "k", 0.2, "sigma", 1, "theta", 0); %! pd2 = makedist ("GeneralizedPareto", "k", 0.5, "sigma", 1, "theta", 0); %! pd3 = makedist ("GeneralizedPareto", "k", 0.8, "sigma", 1, "theta", 0); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"k = 0.2, sigma = 1, theta = 0", "k = 0.5, sigma = 1, theta = 0", "k = 0.8, sigma = 1, theta = 0"}, ... %! "location", "northeast") %! title ("Generalized Pareto PDF") %! xlabel ("Exceedance value") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Generalized Pareto %! ## distributions, showing the likelihood of different exceedance values. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.plot000066400000000000000000000043311524624707500273010ustar00rootroot00000000000000%!demo %! ## Create a Generalized Pareto distribution with fixed parameters k = 0.5, sigma = 1, theta = 0 %! ## and plot its PDF. %! %! pd = makedist ("GeneralizedPareto", "k", 0.3, "sigma", 1, "theta", 0) %! plot (pd) %! title ("Fixed Generalized Pareto distribution with k = 0.3, sigma = 1, theta = 0") %!demo %! ## Generate a data set of 100 random samples from a Generalized Pareto %! ## distribution with parameters k = 0.5, sigma = 1, theta = 0. Fit a Generalized Pareto %! ## distribution to this data (fixing theta=0) and plot its CDF superimposed over an empirical %! ## CDF. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("GeneralizedPareto", "k", 0.5, "sigma", 1, "theta", 0) %! data = random (pd_fixed, 100, 1); %! pd_fitted = GeneralizedParetoDistribution.fit (data, 0) %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Generalized Pareto distribution with k = %0.2f, sigma = %0.2f, theta = 0"; %! title (sprintf (txt, pd_fitted.k, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit in extreme value analysis. %!demo %! ## Generate a data set of 200 random samples from a Generalized Pareto %! ## distribution with parameters k = 0.5, sigma = 1, theta = 0. Display a probability %! ## plot for the Generalized Pareto distribution fit to the data (fixing theta=0). %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("GeneralizedPareto", "k", 0.5, "sigma", 1, "theta", 0) %! data = random (pd_fixed, 200, 1); %! pd_fitted = GeneralizedParetoDistribution.fit (data, 0) %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Generalized Pareto", ... %! " distribution with k = %0.2f, sigma = %0.2f, theta = 0"); %! title (sprintf (txt, pd_fitted.k, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Generalized Pareto model is appropriate for tails. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.proflik000066400000000000000000000011331524624707500277660ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the shape parameter of a fitted %! ## Generalized Pareto distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("GeneralizedPareto", "k", 0.5, "sigma", 1, "theta", 0) %! data = random (pd, 1000, 1); %! pd_fitted = GeneralizedParetoDistribution.fit (data, 0) %! [nlogL, param] = proflik (pd_fitted, 1, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the shape parameter (k), %! ## helping to understand the uncertainty in parameter estimates for tail behavior. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.random000066400000000000000000000011071524624707500276010ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Generalized Pareto distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("GeneralizedPareto", "k", 0.5, "sigma", 1, "theta", 0) %! samples = random (pd, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from GeneralizedPareto(k=0.5, sigma=1, theta=0)") %! xlabel ("Exceedance value") %! ylabel ("Frequency") %! %! ## This generates random samples from a Generalized Pareto distribution, useful %! ## for simulating extreme exceedances in risk modeling. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.sigma000066400000000000000000000013051524624707500274210ustar00rootroot00000000000000%!demo %! ## Create a Generalized Pareto distribution with default parameters %! pd = makedist ("GeneralizedPareto") %! %! ## Query parameter 'sigma' (scale parameter) %! pd.sigma %! %! ## Set parameter 'sigma' %! pd.sigma = 2 %! %! ## Use this to initialize or modify the scale parameter in a Generalized Pareto %! ## distribution. The scale parameter must be a positive real scalar. %!demo %! ## Create a Generalized Pareto distribution object by calling its constructor %! pd = GeneralizedParetoDistribution (0.5, 2, 0) %! %! ## Query parameter 'sigma' %! pd.sigma %! %! ## This shows how to set the scale parameter directly via the constructor, %! ## ideal for modeling the spread in extreme value data. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.std000066400000000000000000000004741524624707500271210ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Generalized Pareto distribution %! pd = makedist ("GeneralizedPareto", "k", 0.3, "sigma", 1, "theta", 0) %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in exceedance values (finite only if k<0.5). statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.theta000066400000000000000000000013671524624707500274360ustar00rootroot00000000000000%!demo %! ## Create a Generalized Pareto distribution with default parameters %! pd = makedist ("GeneralizedPareto") %! %! ## Query parameter 'theta' (location parameter) %! pd.theta %! %! ## Set parameter 'theta' %! pd.theta = 1 %! %! ## Use this to initialize or modify the location parameter in a Generalized Pareto %! ## distribution. The location parameter can be any real scalar, often set to a threshold in extreme value analysis. %!demo %! ## Create a Generalized Pareto distribution object by calling its constructor %! pd = GeneralizedParetoDistribution (0.5, 1, 1) %! %! ## Query parameter 'theta' %! pd.theta %! %! ## This demonstrates setting the location parameter directly, useful for shifting the distribution in threshold-exceedance models. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.truncate000066400000000000000000000015701524624707500301520ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Generalized Pareto distribution, with parameters k = 0.5, sigma = 1, theta = 0, %! ## truncated at [0.5, 2] intervals. Generate 10000 random samples from this truncated distribution %! ## and superimpose a histogram scaled accordingly %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("GeneralizedPareto", "k", 0.5, "sigma", 1, "theta", 0) %! t = truncate (pd, 0.5, 2) %! data = random (t, 10000, 1); %! %! ## Plot histogram and fitted PDF %! plot (t) %! hold on %! hist (data, 100, 50) %! hold off %! title ("Generalized Pareto distribution (k = 0.5, sigma = 1, theta = 0) truncated at [0.5, 2]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Generalized Pareto distribution to a specific %! ## range and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.GeneralizedParetoDistribution.var000066400000000000000000000004751524624707500271200ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Generalized Pareto distribution %! pd = makedist ("GeneralizedPareto", "k", 0.3, "sigma", 1, "theta", 0) %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## exceedance values in the distribution (finite only if k<0.5). statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.cdf000066400000000000000000000016231524624707500254770ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Half-normal distribution %! rng (42); %! x = -1:0.01:5; %! data1 = 0.5 * abs (randn (10000, 1)); %! data2 = 1.0 * abs (randn (10000, 1)); %! data3 = 2.0 * abs (randn (10000, 1)); %! pd1 = fitdist (data1, "HalfNormal"); %! pd2 = fitdist (data2, "HalfNormal"); %! pd3 = fitdist (data3, "HalfNormal"); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 2"}, ... %! "location", "southeast") %! title ("Half-normal CDF") %! xlabel ("values in x (x >= mu)") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Half-normal distributions, showing how probability %! ## accumulates for positive values, useful in reliability or error modeling. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.icdf000066400000000000000000000015671524624707500256570ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Half-normal distribution %! rng (42); %! p = 0.001:0.001:0.999; %! data1 = 0.5 * abs (randn (10000, 1)); %! data2 = 1.0 * abs (randn (10000, 1)); %! data3 = 2.0 * abs (randn (10000, 1)); %! pd1 = fitdist (data1, "HalfNormal"); %! pd2 = fitdist (data2, "HalfNormal"); %! pd3 = fitdist (data3, "HalfNormal"); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 2"}, ... %! "location", "northwest") %! title ("Half-normal iCDF") %! xlabel ("Probability") %! ylabel ("values in x (x >= mu)") %! %! ## This demonstrates the inverse CDF (quantiles) for Half-normal %! ## distributions, useful for finding values corresponding to given %! ## probabilities, such as thresholds in quality control. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.iqr000066400000000000000000000005761524624707500255440ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Half-normal distribution %! rng (42); %! data = abs (randn (10000, 1)); %! pd = fitdist (data, "HalfNormal"); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, helpful for understanding central %! ## variability in positive data. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.mean000066400000000000000000000007171524624707500256660ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Half-normal distributions %! rng (42); %! data1 = 0.5 * abs (randn (10000, 1)); %! data2 = 1.0 * abs (randn (10000, 1)); %! pd1 = fitdist (data1, "HalfNormal"); %! pd2 = fitdist (data2, "HalfNormal"); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Half-normal %! ## distributions with different scale parameters, representing average %! ## deviation or magnitude. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.median000066400000000000000000000007031524624707500261760ustar00rootroot00000000000000%!demo %! ## Compute the median for different Half-normal distributions %! rng (42); %! data1 = 0.5 * abs (randn (10000, 1)); %! data2 = 1.0 * abs (randn (10000, 1)); %! pd1 = fitdist (data1, "HalfNormal"); %! pd2 = fitdist (data2, "HalfNormal"); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution %! ## into two equal probability halves, robust to skewness in positive data. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.mu000066400000000000000000000014721524624707500253660ustar00rootroot00000000000000%!demo %! ## Create a Half-normal distribution with default parameters %! rng (42); %! data = abs (randn (10000, 1)); %! pd = fitdist (data, "HalfNormal"); %! %! ## Query parameter 'mu' (location parameter) %! pd.mu %! %! ## Set parameter 'mu' %! pd.mu = 1 %! %! ## Use this to initialize or modify the location parameter of a Half-normal %! ## distribution. The location parameter must be a real scalar, often set to 0 %! ## for modeling positive deviations from zero, such as measurement errors. %!demo %! ## Create a Half-normal distribution object by calling its constructor %! pd = HalfNormalDistribution (1.5, 2) %! %! ## Query parameter 'mu' %! pd.mu %! %! ## This demonstrates direct construction with a specific location parameter, %! ## useful for modeling data shifted from zero, like distances or folded normals. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.negloglik000066400000000000000000000006531524624707500267200ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Half-normal distribution %! rng (42); %! data = abs (randn (100, 1)); %! pd_fitted = fitdist (data, "HalfNormal"); %! params = [pd_fitted.mu, pd_fitted.sigma]; %! nlogL_hnlike = hnlike (params, data) %! %! ## This is useful for assessing the fit of a Half-normal distribution to %! ## data, with lower values indicating a better fit, often used in model comparison. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.paramci000066400000000000000000000006211524624707500263540ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Half-normal %! ## distribution %! rng (42); %! data = abs (randn (1000, 1)); %! pd_fitted = fitdist (data, "HalfNormal"); %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (mu %! ## fixed, sigma estimated), providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.pdf000066400000000000000000000014641524624707500255170ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Half-normal distribution %! rng (42); %! x = -1:0.01:5; %! data1 = 0.5 * abs (randn (10000, 1)); %! data2 = 1.0 * abs (randn (10000, 1)); %! data3 = 2.0 * abs (randn (10000, 1)); %! pd1 = fitdist (data1, "HalfNormal"); %! pd2 = fitdist (data2, "HalfNormal"); %! pd3 = fitdist (data3, "HalfNormal"); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 2"}, ... %! "location", "northeast") %! title ("Half-normal PDF") %! xlabel ("values in x (x >= mu)") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Half-normal %! ## distributions, showing the likelihood for positive values. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.plot000066400000000000000000000033341524624707500257220ustar00rootroot00000000000000%!demo %! ## Create a Half-normal distribution with fixed parameters mu = 0 and %! ## sigma = 1 and plot its PDF. %! %! rng (42); %! data = abs (randn (10000, 1)); %! pd = fitdist (data, "HalfNormal"); %! plot (pd) %! title ("Fixed Half-normal distribution with mu = 0 and sigma = 1") %!demo %! ## Generate a data set of 100 random samples from a Half-normal %! ## distribution with parameters mu = 0 and sigma = 1. Fit a Half-normal %! ## distribution to this data and plot its CDF superimposed over an empirical %! ## CDF. %! %! rng (42); %! data = abs (randn (100, 1)); %! pd_fitted = fitdist (data, "HalfNormal"); %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Half-normal distribution with mu = %0.2f and sigma = %0.2f"; %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. %!demo %! ## Generate a data set of 200 random samples from a Half-normal %! ## distribution with parameters mu = 0 and sigma = 1. Display a probability %! ## plot for the Half-normal distribution fit to the data. %! %! rng (42); %! data = abs (randn (200, 1)); %! pd_fitted = fitdist (data, "HalfNormal"); %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Half-normal", ... %! " distribution with mu = %0.2f and sigma = %0.2f"); %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Half-normal model is appropriate. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.proflik000066400000000000000000000006501524624707500264100ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the scale parameter of a fitted %! ## Half-normal distribution %! rng (42); %! data = abs (randn (1000, 1)); %! pd_fitted = fitdist (data, "HalfNormal"); %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the scale parameter (sigma), %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.random000066400000000000000000000006521524624707500262240ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Half-normal distribution %! rng (42); %! samples = abs (randn (500, 1)); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Half-normal(mu=0, sigma=1)") %! xlabel ("values in x (x >= mu)") %! ylabel ("Frequency") %! %! ## This generates random samples from a Half-normal distribution, useful %! ## for simulating positive data like absolute errors or magnitudes. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.sigma000066400000000000000000000014361524624707500260450ustar00rootroot00000000000000%!demo %! ## Create a Half-normal distribution with default parameters %! rng (42); %! data = abs (randn (10000, 1)); %! pd = fitdist (data, "HalfNormal"); %! %! ## Query parameter 'sigma' (scale parameter) %! pd.sigma %! %! ## Set parameter 'sigma' %! pd.sigma = 2 %! %! ## Use this to initialize or modify the scale parameter in a Half-normal %! ## distribution. The scale parameter must be a positive real scalar, controlling %! ## the spread of the distribution. %!demo %! ## Create a Half-normal distribution object by calling its constructor %! pd = HalfNormalDistribution (0, 1.5) %! %! ## Query parameter 'sigma' %! pd.sigma %! %! ## This shows how to set the scale parameter directly via the constructor, %! ## ideal for modeling variability in positive data, such as absolute residuals. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.std000066400000000000000000000005001524624707500255260ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Half-normal distribution %! rng (42); %! data = abs (randn (10000, 1)); %! pd = fitdist (data, "HalfNormal"); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in the positive values of the distribution. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.truncate000066400000000000000000000014771524624707500265770ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Half-normal distribution, with parameters mu = 0 %! ## and sigma = 1, truncated at [0.5, 2] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! %! rng (42); %! data_all = abs (randn (20000, 1)); %! data = data_all(data_all >= 0.5 & data_all <= 2); %! data = data(1:10000); %! %! pd = fitdist (data, "HalfNormal"); %! t = truncate (pd, 0.5, 2); %! %! ## Plot histogram and truncated PDF %! plot (t) %! hold on %! hist (data, 50) %! hold off %! title ("Half-normal distribution (mu = 0, sigma = 1) truncated at [0.5, 2]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Half-normal distribution to a specific %! ## range and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.HalfNormalDistribution.var000066400000000000000000000004511524624707500255310ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Half-normal distribution %! rng (42); %! data = abs (randn (10000, 1)); %! pd = fitdist (data, "HalfNormal"); %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## positive values in the distribution. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.cdf000066400000000000000000000014311524624707500265570ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Inverse Gaussian distribution %! x = 0:0.01:5; %! pd1 = makedist ("InverseGaussian", "mu", 1, "lambda", 1); %! pd2 = makedist ("InverseGaussian", "mu", 1, "lambda", 2); %! pd3 = makedist ("InverseGaussian", "mu", 1, "lambda", 3); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"mu = 1, lambda = 1", "mu = 1, lambda = 2", "mu = 1, lambda = 3"}, ... %! "location", "southeast") %! title ("Inverse Gaussian CDF") %! xlabel ("values") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Inverse Gaussian distributions, showing how probability %! ## accumulates over values. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.icdf000066400000000000000000000014131524624707500267300ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Inverse Gaussian distribution %! p = 0.001:0.001:0.999; %! pd1 = makedist ("InverseGaussian", "mu", 1, "lambda", 1); %! pd2 = makedist ("InverseGaussian", "mu", 1, "lambda", 2); %! pd3 = makedist ("InverseGaussian", "mu", 1, "lambda", 3); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"mu = 1, lambda = 1", "mu = 1, lambda = 2", "mu = 1, lambda = 3"}, ... %! "location", "northwest") %! title ("Inverse Gaussian iCDF") %! xlabel ("Probability") %! ylabel ("values") %! %! ## This demonstrates the inverse CDF (quantiles) for Inverse Gaussian %! ## distributions, useful for finding the value corresponding to %! ## given probabilities. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.iqr000066400000000000000000000005451524624707500266230ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for an Inverse Gaussian distribution %! pd = makedist ("InverseGaussian", "mu", 1, "lambda", 2) %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, useful for understanding variability %! ## in non-negative data. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.lambda000066400000000000000000000013121524624707500272410ustar00rootroot00000000000000%!demo %! ## Create an Inverse Gaussian distribution with default parameters %! pd = makedist ("InverseGaussian") %! %! ## Query parameter 'lambda' (shape parameter) %! pd.lambda %! %! ## Set parameter 'lambda' %! pd.lambda = 3 %! %! ## Use this to initialize or modify the shape parameter in an Inverse Gaussian %! ## distribution. The shape parameter must be a positive real scalar. %!demo %! ## Create an Inverse Gaussian distribution object by calling its constructor %! pd = InverseGaussianDistribution (1.5, 2) %! %! ## Query parameter 'lambda' %! pd.lambda %! %! ## This shows how to set the shape parameter directly via the constructor, %! ## ideal for modeling specific variability in non-negative data. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.mean000066400000000000000000000005611524624707500267460ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Inverse Gaussian distributions %! pd1 = makedist ("InverseGaussian", "mu", 1, "lambda", 1); %! pd2 = makedist ("InverseGaussian", "mu", 1, "lambda", 2); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Inverse Gaussian %! ## distributions with different shape parameters. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.median000066400000000000000000000005561524624707500272670ustar00rootroot00000000000000%!demo %! ## Compute the median for different Inverse Gaussian distributions %! pd1 = makedist ("InverseGaussian", "mu", 1, "lambda", 1); %! pd2 = makedist ("InverseGaussian", "mu", 1, "lambda", 2); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution %! ## into two equal probability halves. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.mu000066400000000000000000000012571524624707500264520ustar00rootroot00000000000000%!demo %! ## Create an Inverse Gaussian distribution with default parameters %! pd = makedist ("InverseGaussian") %! %! ## Query parameter 'mu' (mean parameter) %! pd.mu %! %! ## Set parameter 'mu' %! pd.mu = 2 %! %! ## Use this to initialize or modify the mean parameter of an Inverse Gaussian %! ## distribution. The mean parameter must be a positive real scalar. %!demo %! ## Create an Inverse Gaussian distribution object by calling its constructor %! pd = InverseGaussianDistribution (1.5, 2) %! %! ## Query parameter 'mu' %! pd.mu %! %! ## This demonstrates direct construction with a specific mean parameter, %! ## useful for modeling non-negative skewed data with a known mean. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.negloglik000066400000000000000000000007411524624707500300010ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Inverse Gaussian distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("InverseGaussian", "mu", 1, "lambda", 2) %! data = random (pd, 100, 1); %! pd_fitted = fitdist (data, "InverseGaussian") %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of an Inverse Gaussian distribution to %! ## data, lower values indicate a better fit. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.paramci000066400000000000000000000010201524624707500274310ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Inverse Gaussian %! ## distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("InverseGaussian", "mu", 1, "lambda", 2) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "InverseGaussian") %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (mu %! ## and lambda), providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.pdf000066400000000000000000000013531524624707500265770ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Inverse Gaussian distribution %! x = 0:0.01:5; %! pd1 = makedist ("InverseGaussian", "mu", 1, "lambda", 1); %! pd2 = makedist ("InverseGaussian", "mu", 1, "lambda", 2); %! pd3 = makedist ("InverseGaussian", "mu", 1, "lambda", 3); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"mu = 1, lambda = 1", "mu = 1, lambda = 2", "mu = 1, lambda = 3"}, ... %! "location", "northeast") %! title ("Inverse Gaussian PDF") %! xlabel ("values") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Inverse Gaussian %! ## distributions, showing the likelihood of different values. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.plot000066400000000000000000000040131524624707500270000ustar00rootroot00000000000000%!demo %! ## Create an Inverse Gaussian distribution with fixed parameters μ = 1 and %! ## λ = 2 and plot its PDF. %! %! pd = makedist ("InverseGaussian", "mu", 1, "lambda", 2) %! plot (pd) %! title ("Fixed Inverse Gaussian distribution with mu = 1 and lambda = 2") %!demo %! ## Generate a data set of 100 random samples from an Inverse Gaussian %! ## distribution with parameters μ = 1 and λ = 2. Fit an Inverse Gaussian %! ## distribution to this data and plot its CDF superimposed over an empirical %! ## CDF. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("InverseGaussian", "mu", 1, "lambda", 2) %! data = random (pd_fixed, 100, 1); %! pd_fitted = fitdist (data, "InverseGaussian") %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Inverse Gaussian distribution with μ = %0.2f and λ = %0.2f"; %! title (sprintf (txt, pd_fitted.mu, pd_fitted.lambda)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. %!demo %! ## Generate a data set of 200 random samples from an Inverse Gaussian %! ## distribution with parameters μ = 1 and λ = 2. Display a probability %! ## plot for the Inverse Gaussian distribution fit to the data. %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("InverseGaussian", "mu", 1, "lambda", 2) %! data = random (pd_fixed, 200, 1); %! pd_fitted = fitdist (data, "InverseGaussian") %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Inverse Gaussian", ... %! " distribution with μ = %0.2f and λ = %0.2f"); %! title (sprintf (txt, pd_fitted.mu, pd_fitted.lambda)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Inverse Gaussian model is appropriate. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.proflik000066400000000000000000000010641524624707500274730ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the shape parameter of a fitted %! ## Inverse Gaussian distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("InverseGaussian", "mu", 1, "lambda", 2) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "InverseGaussian") %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the shape parameter (lambda), %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.random000066400000000000000000000010511524624707500273010ustar00rootroot00000000000000%!demo %! ## Generate random samples from an Inverse Gaussian distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("InverseGaussian", "mu", 1, "lambda", 2) %! samples = random (pd, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Inverse Gaussian(mu=1, lambda=2)") %! xlabel ("values") %! ylabel ("Frequency") %! %! ## This generates random samples from an Inverse Gaussian distribution, useful %! ## for simulating non-negative skewed data like repair times. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.std000066400000000000000000000004131524624707500266140ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for an Inverse Gaussian distribution %! pd = makedist ("InverseGaussian", "mu", 1, "lambda", 2) %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in values. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.truncate000066400000000000000000000015311524624707500276510ustar00rootroot00000000000000%!demo %! ## Plot the PDF of an Inverse Gaussian distribution, with parameters mu = 1 %! ## and lambda = 2, truncated at [0.5, 2] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("InverseGaussian", "mu", 1, "lambda", 2) %! t = truncate (pd, 0.5, 2) %! data = random (t, 10000, 1); %! %! ## Plot histogram and fitted PDF %! plot (t) %! hold on %! hist (data, 100, 50) %! hold off %! title ("Inverse Gaussian distribution (mu = 1, lambda = 2) truncated at [0.5, 2]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating an Inverse Gaussian distribution to a specific %! ## range and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.InverseGaussianDistribution.var000066400000000000000000000004141524624707500266130ustar00rootroot00000000000000%!demo %! ## Compute the variance for an Inverse Gaussian distribution %! pd = makedist ("InverseGaussian", "mu", 1, "lambda", 2) %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## values in the distribution. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.cdf000066400000000000000000000014341524624707500252310ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Logistic distribution %! x = -5:0.01:5; %! pd1 = makedist ("Logistic", "mu", 0, "sigma", 0.5); %! pd2 = makedist ("Logistic", "mu", 0, "sigma", 1); %! pd3 = makedist ("Logistic", "mu", 0, "sigma", 1.5); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 1.5"}, ... %! "location", "southeast") %! title ("Logistic CDF") %! xlabel ("Value") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Logistic distributions, showing how probability accumulates %! ## over values, useful in regression or classification tasks. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.icdf000066400000000000000000000014121524624707500253760ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Logistic distribution %! p = 0.001:0.001:0.999; %! pd1 = makedist ("Logistic", "mu", 0, "sigma", 0.5); %! pd2 = makedist ("Logistic", "mu", 0, "sigma", 1); %! pd3 = makedist ("Logistic", "mu", 0, "sigma", 1.5); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 1.5"}, ... %! "location", "northwest") %! title ("Logistic iCDF") %! xlabel ("Probability") %! ylabel ("Value") %! %! ## This demonstrates the inverse CDF (quantiles) for Logistic distributions, %! ## useful for finding values corresponding to specific probabilities, such as %! ## thresholds in logistic regression. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.iqr000066400000000000000000000005201524624707500252630ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Logistic distribution %! pd = makedist ("Logistic", "mu", 0, "sigma", 1) %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, useful for understanding variability %! ## in logistic data. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.mean000066400000000000000000000005661524624707500254220ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Logistic distributions %! pd1 = makedist ("Logistic", "mu", 0, "sigma", 0.5); %! pd2 = makedist ("Logistic", "mu", 0, "sigma", 1); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Logistic distributions %! ## with different scale parameters, representing the central tendency. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.median000066400000000000000000000006051524624707500257310ustar00rootroot00000000000000%!demo %! ## Compute the median for different Logistic distributions %! pd1 = makedist ("Logistic", "mu", 0, "sigma", 0.5); %! pd2 = makedist ("Logistic", "mu", 0, "sigma", 1); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution into %! ## two equal probability halves, useful for robust central tendency measures. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.mu000066400000000000000000000014031524624707500251120ustar00rootroot00000000000000%!demo %! ## Create a Logistic distribution with default parameters %! pd = makedist ("Logistic") %! %! ## Query parameter 'mu' (location parameter) %! pd.mu %! %! ## Set parameter 'mu' %! pd.mu = 2 %! %! ## Use this to initialize or modify the location parameter of a Logistic %! ## distribution. The location parameter must be a finite real scalar and %! ## represents the center of the distribution, often used in regression models. %!demo %! ## Create a Logistic distribution object by calling its constructor %! pd = LogisticDistribution (1.5, 0.5) %! %! ## Query parameter 'mu' %! pd.mu %! %! ## This demonstrates direct construction with a specific location parameter, %! ## useful for modeling data centered around a known value, such as in logistic regression. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.negloglik000066400000000000000000000007411524624707500264500ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Logistic distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Logistic", "mu", 0, "sigma", 1) %! data = random (pd, 100, 1); %! pd_fitted = fitdist (data, "Logistic") %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of a Logistic distribution to data, %! ## lower values indicate a better fit, often used in model evaluation. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.paramci000066400000000000000000000007621524624707500261140ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Logistic distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Logistic", "mu", 0, "sigma", 1) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "Logistic") %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (mu %! ## and sigma), providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.pdf000066400000000000000000000013471524624707500252510ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Logistic distribution %! x = -5:0.01:5; %! pd1 = makedist ("Logistic", "mu", 0, "sigma", 0.5); %! pd2 = makedist ("Logistic", "mu", 0, "sigma", 1); %! pd3 = makedist ("Logistic", "mu", 0, "sigma", 1.5); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 1.5"}, ... %! "location", "northeast") %! title ("Logistic PDF") %! xlabel ("Value") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Logistic distributions, %! ## showing the likelihood of different values, useful for understanding data density. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.plot000066400000000000000000000037571524624707500254650ustar00rootroot00000000000000%!demo %! ## Create a Logistic distribution with fixed parameters mu = 0 and sigma = 1 %! ## and plot its PDF. %! pd = makedist ("Logistic", "mu", 0, "sigma", 1) %! plot (pd) %! title ("Fixed Logistic distribution with mu = 0 and sigma = 1") %! %! ## Use this to visualize the PDF of a Logistic distribution with fixed parameters. %!demo %! ## Generate a data set of 100 random samples from a Logistic distribution %! ## with parameters mu = 0 and sigma = 1. Fit a Logistic distribution to this %! ## data and plot its CDF superimposed over an empirical CDF. %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("Logistic", "mu", 0, "sigma", 1) %! data = random (pd_fixed, 100, 1); %! pd_fitted = fitdist (data, "Logistic") %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Logistic distribution with mu = %0.2f and sigma = %0.2f"; %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. %!demo %! ## Generate a data set of 200 random samples from a Logistic distribution %! ## with parameters mu = 0 and sigma = 1. Display a probability plot for the %! ## Logistic distribution fit to the data. %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("Logistic", "mu", 0, "sigma", 1) %! data = random (pd_fixed, 200, 1); %! pd_fitted = fitdist (data, "Logistic") %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Logistic distribution", ... %! " with mu = %0.2f and sigma = %0.2f"); %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Logistic model is appropriate. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.proflik000066400000000000000000000010341524624707500261370ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the scale parameter of a fitted %! ## Logistic distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Logistic", "mu", 0, "sigma", 1) %! data = random (pd, 1000, 1); %! pd_fitted = fitdist (data, "Logistic") %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the scale parameter (sigma), %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.random000066400000000000000000000010221524624707500257460ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Logistic distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Logistic", "mu", 0, "sigma", 1) %! samples = random (pd, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Logistic(mu=0, sigma=1)") %! xlabel ("Value") %! ylabel ("Frequency") %! %! ## This generates random samples from a Logistic distribution, useful for %! ## simulating data for logistic regression or neural network modeling. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.sigma000066400000000000000000000013511524624707500255730ustar00rootroot00000000000000%!demo %! ## Create a Logistic distribution with default parameters %! pd = makedist ("Logistic") %! %! ## Query parameter 'sigma' (scale parameter) %! pd.sigma %! %! ## Set parameter 'sigma' %! pd.sigma = 0.8 %! %! ## Use this to initialize or modify the scale parameter in a Logistic %! ## distribution. The scale parameter must be a positive real scalar and %! ## controls the spread of the distribution. %!demo %! ## Create a Logistic distribution object by calling its constructor %! pd = LogisticDistribution (1.5, 0.5) %! %! ## Query parameter 'sigma' %! pd.sigma %! %! ## This shows how to set the scale parameter directly via the constructor, %! ## ideal for modeling data with specific variability, such as in neural network outputs. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.std000066400000000000000000000004521524624707500252660ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Logistic distribution %! pd = makedist ("Logistic", "mu", 0, "sigma", 1) %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in the distribution, useful for understanding data spread. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.truncate000066400000000000000000000014031524624707500263160ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Logistic distribution, with parameters mu = 0 and sigma = 1, %! ## truncated at [-2, 2] intervals. Generate 10000 random samples from this %! ## truncated distribution and superimpose a histogram scaled accordingly. %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Logistic", "mu", 0, "sigma", 1) %! t = truncate (pd, -2, 2) %! data = random (t, 10000, 1); %! plot (t) %! hold on %! hist (data, 100, 50) %! hold off %! title ("Logistic distribution (mu = 0, sigma = 1) truncated at [-2, 2]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Logistic distribution to a specific range %! ## and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.LogisticDistribution.var000066400000000000000000000004161524624707500252640ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Logistic distribution %! pd = makedist ("Logistic", "mu", 0, "sigma", 1) %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## distribution, useful for statistical analysis. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.cdf000066400000000000000000000016221524624707500257320ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Log-logistic distribution %! rng (42); %! x = 0:0.01:10; %! data1 = loglrnd (0, 0.5, 10000, 1); %! data2 = loglrnd (0, 1, 10000, 1); %! data3 = loglrnd (0, 2, 10000, 1); %! pd1 = fitdist (data1, "Loglogistic"); %! pd2 = fitdist (data2, "Loglogistic"); %! pd3 = fitdist (data3, "Loglogistic"); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 2"}, ... %! "location", "southeast") %! title ("Log-logistic CDF") %! xlabel ("values in x (x > 0)") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Log-logistic distributions, showing how probability %! ## accumulates for positive values, useful in survival analysis or risk modeling. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.icdf000066400000000000000000000016041524624707500261030ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Log-logistic distribution %! rng (42); %! p = 0.0001:0.001:0.95; %! data1 = loglrnd (0, 0.5, 10000, 1); %! data2 = loglrnd (0, 1, 10000, 1); %! data3 = loglrnd (0, 2, 10000, 1); %! pd1 = fitdist (data1, "Loglogistic"); %! pd2 = fitdist (data2, "Loglogistic"); %! pd3 = fitdist (data3, "Loglogistic"); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 2"}, ... %! "location", "northwest") %! title ("Log-logistic iCDF") %! xlabel ("Probability") %! ylabel ("values in x (x > 0)") %! %! ## This demonstrates the inverse CDF (quantiles) for Log-logistic %! ## distributions, useful for finding values corresponding to given %! ## probabilities, such as percentiles in income data or survival quantiles. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.iqr000066400000000000000000000006301524624707500257670ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Log-logistic distribution %! rng (42); %! data = loglrnd (0, 1, 10000, 1); %! pd = fitdist (data, "Loglogistic"); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, helpful for understanding central %! ## variability in positive skewed data like lifetimes. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.mean000066400000000000000000000007641524624707500261240ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Log-logistic distributions %! rng (42); %! data1 = loglrnd (0, 0.5, 10000, 1); %! data2 = loglrnd (0, 1, 10000, 1); %! pd1 = fitdist (data1, "Loglogistic"); %! pd2 = fitdist (data2, "Loglogistic"); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Log-logistic %! ## distributions with different sigma parameters. Note that the mean may be %! ## infinite for sigma <= 1, useful in heavy-tailed modeling. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.median000066400000000000000000000007131524624707500264330ustar00rootroot00000000000000%!demo %! ## Compute the median for different Log-logistic distributions %! rng (42); %! data1 = loglrnd (0, 0.5, 10000, 1); %! data2 = loglrnd (0, 1, 10000, 1); %! pd1 = fitdist (data1, "Loglogistic"); %! pd2 = fitdist (data2, "Loglogistic"); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution %! ## into two equal probability halves, robust to the heavy tails in Log-logistic data. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.mu000066400000000000000000000016431524624707500256220ustar00rootroot00000000000000%!demo %! ## Create a Log-logistic distribution with default parameters %! rng (42); %! data = loglrnd (0, 1, 10000, 1); %! pd = fitdist (data, "Loglogistic"); %! %! ## Query parameter 'mu' (mean of logarithmic values) %! pd.mu %! %! ## Set parameter 'mu' %! pd.mu = 1 %! %! ## Use this to initialize or modify the mean of the logarithmic values in a %! ## Log-logistic distribution. The mu parameter must be a nonnegative real %! ## scalar, often representing the location in log-space for modeling %! ## positive skewed data like survival times or income distributions. %!demo %! ## Create a Log-logistic distribution object by calling its constructor %! pd = LoglogisticDistribution (1.5, 2); %! %! ## Query parameter 'mu' %! pd.mu %! %! ## This demonstrates direct construction with a specific mu parameter, %! ## useful for modeling data with a known log-mean, such as in reliability %! ## engineering or financial modeling. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.negloglik000066400000000000000000000006211524624707500271470ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Log-logistic distribution %! rng (42); %! data = loglrnd (0, 1, 100, 1); %! pd_fitted = fitdist (data, "Loglogistic"); %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of a Log-logistic distribution to %! ## data, with lower values indicating a better fit, often used in model %! ## selection or optimization. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.paramci000066400000000000000000000007051524624707500266130ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Log-logistic %! ## distribution %! rng (42); %! data = loglrnd (0, 1, 1000, 1); %! pd_fitted = fitdist (data, "Loglogistic"); %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (mu %! ## and sigma), providing a range of plausible values given the data, especially %! ## useful in survival or reliability analysis. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.pdf000066400000000000000000000015011524624707500257430ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Log-logistic distribution %! rng (42); %! x = 0:0.01:10; %! data1 = loglrnd (0, 0.5, 10000, 1); %! data2 = loglrnd (0, 1, 10000, 1); %! data3 = loglrnd (0, 2, 10000, 1); %! pd1 = fitdist (data1, "Loglogistic"); %! pd2 = fitdist (data2, "Loglogistic"); %! pd3 = fitdist (data3, "Loglogistic"); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 2"}, ... %! "location", "northeast") %! title ("Log-logistic PDF") %! xlabel ("values in x (x > 0)") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Log-logistic %! ## distributions, showing the likelihood for positive values with varying tails. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.plot000066400000000000000000000041031524624707500261510ustar00rootroot00000000000000%!demo %! ## Create a Log-logistic distribution with fixed parameters mu = 0 and %! ## sigma = 1 and plot its PDF. %! %! rng (42); %! data = loglrnd (0, 1, 10000, 1); %! pd = fitdist (data, "Loglogistic"); %! x = linspace (0.01, 20, 1000); %! y = pdf (pd, x); %! plot (x, y, "b", "LineWidth", 2) %! grid on %! title ("Fixed Log-logistic distribution with mu = 0 and sigma = 1") %! xlabel ("x") %! ylabel ("PDF") %!demo %! ## Generate a data set of 100 random samples from a Log-logistic %! ## distribution with parameters mu = 0 and sigma = 1. Fit a Log-logistic %! ## distribution to this data and plot its CDF superimposed over an empirical %! ## CDF. %! %! rng (42); %! data = loglrnd (0, 1, 100, 1); %! pd_fitted = fitdist (data, "Loglogistic"); %! ecdf (data); %! hold on; %! x = linspace (icdf (pd_fitted, 0.01), icdf (pd_fitted, 0.99), 1000); %! y = cdf (pd_fitted, x); %! plot (x, y, "r", "LineWidth", 2); %! txt = "Fitted Log-logistic distribution with mu = %0.2f and sigma = %0.2f"; %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! xlabel ("x") %! ylabel ("CDF") %! grid on %! hold off; %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit in skewed positive data. %!demo %! ## Generate a data set of 200 random samples from a Log-logistic %! ## distribution with parameters mu = 0 and sigma = 1. Display a probability %! ## plot for the Log-logistic distribution fit to the data. %! %! rng (42); %! data = loglrnd (0, 1, 200, 1); %! pd_fitted = fitdist (data, "Loglogistic"); %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Log-logistic", ... %! " distribution with mu = %0.2f and sigma = %0.2f"); %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Log-logistic model captures the tail behavior. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.proflik000066400000000000000000000006711524624707500266470ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the sigma parameter of a fitted %! ## Log-logistic distribution %! rng (42); %! data = loglrnd (0, 1, 1000, 1); %! pd_fitted = fitdist (data, "Loglogistic"); %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the scale parameter (sigma), %! ## helping to understand the uncertainty and shape of the likelihood surface. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.random000066400000000000000000000007561524624707500264650ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Log-logistic distribution %! rng (42); %! samples = loglrnd (0, 1, 500, 1); %! hist (samples, 50) %! p99 = prctile (samples, 99); %! xlim ([0, p99]); %! title ("Histogram of 500 random samples from Log-logistic(mu=0, sigma=1)") %! xlabel ("values in x (x > 0)") %! ylabel ("Frequency") %! %! ## This generates random samples from a Log-logistic distribution, useful %! ## for simulating positive skewed data like waiting times or economic variables. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.sigma000066400000000000000000000015261524624707500263010ustar00rootroot00000000000000%!demo %! ## Create a Log-logistic distribution with default parameters %! rng (42); %! data = loglrnd (0, 1, 10000, 1); %! pd = fitdist (data, "Loglogistic"); %! %! ## Query parameter 'sigma' (scale of logarithmic values) %! pd.sigma %! %! ## Set parameter 'sigma' %! pd.sigma = 2 %! %! ## Use this to initialize or modify the scale of the logarithmic values in a %! ## Log-logistic distribution. The sigma parameter must be a positive real %! ## scalar, controlling the shape and tail heaviness of the distribution. %!demo %! ## Create a Log-logistic distribution object by calling its constructor %! pd = LoglogisticDistribution (0, 1.5); %! %! ## Query parameter 'sigma' %! pd.sigma %! %! ## This shows how to set the sigma parameter directly via the constructor, %! ## ideal for modeling variability in positive skewed data, such as failure times. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.std000066400000000000000000000005511524624707500257700ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Log-logistic distribution %! rng (42); %! data = loglrnd (0, 0.4, 10000, 1); %! pd = fitdist (data, "Loglogistic"); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in the positive skewed values. Note it may be infinite for certain parameters. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.truncate000066400000000000000000000020161524624707500270210ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Log-logistic distribution, with parameters mu = 0 %! ## and sigma = 1, truncated at [0.5, 5] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! %! rng (42); %! data_all = loglrnd (0, 1, 30000, 1); %! data = data_all(data_all >= 0.5 & data_all <= 5); %! data = data(1:10000); %! %! pd = fitdist (data, "Loglogistic"); %! t = truncate (pd, 0.5, 5); %! %! [counts, centers] = hist (data, 50); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on; %! %! x = linspace (0.5, 5, 500); %! y = pdf (t, x); %! plot (x, y, "r", "linewidth", 2); %! title ("Log-logistic distribution (mu = 0, sigma = 1) truncated at [0.5, 5]") %! legend ("Truncated PDF", "Histogram") %! hold off; %! %! ## This demonstrates truncating a Log-logistic distribution to a specific %! ## range and visualizing the resulting distribution with random samples, %! ## useful for bounded positive data. statistics-release-1.9.2/inst/demos/prob.LoglogisticDistribution.var000066400000000000000000000005121524624707500257630ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Log-logistic distribution %! rng (42); %! data = loglrnd (0, 0.4, 10000, 1); %! pd = fitdist (data, "Loglogistic"); %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## positive skewed values. Note it may be infinite for sigma <= 2. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.cdf000066400000000000000000000016071524624707500254100ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Lognormal distribution %! rng (42); %! x = 0:0.01:10; %! data1 = lognrnd (0, 0.5, 10000, 1); %! data2 = lognrnd (0, 1.0, 10000, 1); %! data3 = lognrnd (0, 1.5, 10000, 1); %! pd1 = fitdist (data1, "Lognormal"); %! pd2 = fitdist (data2, "Lognormal"); %! pd3 = fitdist (data3, "Lognormal"); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 1.5"}, ... %! "location", "southeast") %! title ("Lognormal CDF") %! xlabel ("values in x (x > 0)") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Lognormal distributions, showing how probability accumulates %! ## for positive skewed data, useful in finance or biology modeling. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.icdf000066400000000000000000000015451524624707500255620ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Lognormal distribution %! rng (42); %! p = 0.001:0.001:0.999; %! data1 = lognrnd (0, 0.5, 10000, 1); %! data2 = lognrnd (0, 1.0, 10000, 1); %! data3 = lognrnd (0, 1.5, 10000, 1); %! pd1 = fitdist (data1, "Lognormal"); %! pd2 = fitdist (data2, "Lognormal"); %! pd3 = fitdist (data3, "Lognormal"); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 1.5"}, ... %! "location", "northwest") %! title ("Lognormal iCDF") %! xlabel ("Probability") %! ylabel ("values in x (x > 0)") %! %! ## This demonstrates the inverse CDF (quantiles) for Lognormal %! ## distributions, useful for finding values corresponding to given %! ## probabilities, such as risk thresholds in finance. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.iqr000066400000000000000000000006041524624707500254430ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Lognormal distribution %! rng (42); %! data = lognrnd (0, 1, 10000, 1); %! pd = fitdist (data, "Lognormal"); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, helpful for understanding central %! ## variability in skewed positive data. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.mean000066400000000000000000000007231524624707500255720ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Lognormal distributions %! rng (42); %! data1 = lognrnd (0, 0.5, 10000, 1); %! data2 = lognrnd (0, 1.0, 10000, 1); %! pd1 = fitdist (data1, "Lognormal"); %! pd2 = fitdist (data2, "Lognormal"); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Lognormal %! ## distributions with different sigma parameters, representing average %! ## outcomes in multiplicative processes. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.median000066400000000000000000000006731524624707500261130ustar00rootroot00000000000000%!demo %! ## Compute the median for different Lognormal distributions %! rng (42); %! data1 = lognrnd (0, 0.5, 10000, 1); %! data2 = lognrnd (0, 1.0, 10000, 1); %! pd1 = fitdist (data1, "Lognormal"); %! pd2 = fitdist (data2, "Lognormal"); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution %! ## into two equal probability halves, robust to skewness in positive data. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.mu000066400000000000000000000015141524624707500252720ustar00rootroot00000000000000%!demo %! ## Create a Lognormal distribution with default parameters %! rng (42); %! data = lognrnd (0, 1, 10000, 1); %! pd = fitdist (data, "Lognormal"); %! %! ## Query parameter 'mu' (mean of logarithmic values) %! pd.mu %! %! ## Set parameter 'mu' %! pd.mu = 1 %! %! ## Use this to initialize or modify the mean parameter of the logarithmic values %! ## in a Lognormal distribution. The mu parameter must be a real scalar, often %! ## representing the log-mean of multiplicative processes, like growth rates. %!demo %! ## Create a Lognormal distribution object by calling its constructor %! pd = LognormalDistribution (1.5, 0.5) %! %! ## Query parameter 'mu' %! pd.mu %! %! ## This demonstrates direct construction with a specific mu parameter, %! ## useful for modeling skewed positive data shifted in log-scale, such as incomes or sizes. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.negloglik000066400000000000000000000006541524624707500266300ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Lognormal distribution %! rng (42); %! data = lognrnd (0, 1, 100, 1); %! pd_fitted = fitdist (data, "Lognormal"); %! params = [pd_fitted.mu, pd_fitted.sigma]; %! nlogL_lognlike = lognlike (params, data) %! %! ## This is useful for assessing the fit of a Lognormal distribution to %! ## data, with lower values indicating a better fit, often used in model comparison. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.paramci000066400000000000000000000006031524624707500262630ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Lognormal %! ## distribution %! rng (42); %! data = lognrnd (0, 1, 1000, 1); %! pd_fitted = fitdist (data, "Lognormal"); %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (mu %! ## and sigma), providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.pdf000066400000000000000000000014541524624707500254250ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Lognormal distribution %! rng (42); %! x = 0:0.01:10; %! data1 = lognrnd (0, 0.5, 10000, 1); %! data2 = lognrnd (0, 1.0, 10000, 1); %! data3 = lognrnd (0, 1.5, 10000, 1); %! pd1 = fitdist (data1, "Lognormal"); %! pd2 = fitdist (data2, "Lognormal"); %! pd3 = fitdist (data3, "Lognormal"); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 1.5"}, ... %! "location", "northeast") %! title ("Lognormal PDF") %! xlabel ("values in x (x > 0)") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Lognormal %! ## distributions, showing the likelihood for positive skewed values. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.plot000066400000000000000000000033151524624707500256300ustar00rootroot00000000000000%!demo %! ## Create a Lognormal distribution with fixed parameters mu = 0 and %! ## sigma = 1 and plot its PDF. %! %! rng (42); %! data = lognrnd (0, 1, 10000, 1); %! pd = fitdist (data, "Lognormal"); %! plot (pd) %! title ("Fixed Lognormal distribution with mu = 0 and sigma = 1") %!demo %! ## Generate a data set of 100 random samples from a Lognormal %! ## distribution with parameters mu = 0 and sigma = 1. Fit a Lognormal %! ## distribution to this data and plot its CDF superimposed over an empirical %! ## CDF. %! %! rng (42); %! data = lognrnd (0, 1, 100, 1); %! pd_fitted = fitdist (data, "Lognormal"); %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Lognormal distribution with mu = %0.2f and sigma = %0.2f"; %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. %!demo %! ## Generate a data set of 200 random samples from a Lognormal %! ## distribution with parameters mu = 0 and sigma = 1. Display a probability %! ## plot for the Lognormal distribution fit to the data. %! %! rng (42); %! data = lognrnd (0, 1, 200, 1); %! pd_fitted = fitdist (data, "Lognormal"); %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Lognormal", ... %! " distribution with mu = %0.2f and sigma = %0.2f"); %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Lognormal model is appropriate. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.proflik000066400000000000000000000006371524624707500263240ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the sigma parameter of a fitted %! ## Lognormal distribution %! rng (42); %! data = lognrnd (0, 1, 1000, 1); %! pd_fitted = fitdist (data, "Lognormal"); %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the sigma parameter, %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.random000066400000000000000000000006651524624707500261370ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Lognormal distribution %! rng (42); %! samples = lognrnd (0, 1, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Lognormal(mu=0, sigma=1)") %! xlabel ("values in x (x > 0)") %! ylabel ("Frequency") %! %! ## This generates random samples from a Lognormal distribution, useful %! ## for simulating skewed positive data like asset prices or biological measurements. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.sigma000066400000000000000000000015021524624707500257460ustar00rootroot00000000000000%!demo %! ## Create a Lognormal distribution with default parameters %! rng (42); %! data = lognrnd (0, 1, 10000, 1); %! pd = fitdist (data, "Lognormal"); %! %! ## Query parameter 'sigma' (standard deviation of logarithmic values) %! pd.sigma %! %! ## Set parameter 'sigma' %! pd.sigma = 0.5 %! %! ## Use this to initialize or modify the sigma parameter in a Lognormal %! ## distribution. The sigma parameter must be a positive real scalar, controlling %! ## the skewness and spread of the distribution. %!demo %! ## Create a Lognormal distribution object by calling its constructor %! pd = LognormalDistribution (0, 1.5) %! %! ## Query parameter 'sigma' %! pd.sigma %! %! ## This shows how to set the sigma parameter directly via the constructor, %! ## ideal for modeling variability in positive skewed data, such as stock returns. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.std000066400000000000000000000005061524624707500254430ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Lognormal distribution %! rng (42); %! data = lognrnd (0, 1, 10000, 1); %! pd = fitdist (data, "Lognormal"); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in the positive skewed values of the distribution. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.truncate000066400000000000000000000017121524624707500264760ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Lognormal distribution, with parameters mu = 0 %! ## and sigma = 1, truncated at [0.5, 5] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! %! rng (42); %! data_all = lognrnd (0, 1, 20000, 1); %! data = data_all(data_all >= 0.5 & data_all <= 5); %! data = data(1:10000); %! %! pd = fitdist (data, "Lognormal"); %! t = truncate (pd, 0.5, 5); %! [counts, centers] = hist (data, 50); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on; %! %! x = linspace (0.5, 5, 500); %! y = pdf (t, x); %! plot (x, y, "r", "linewidth", 2); %! title ("Lognormal distribution (mu = 0, sigma = 1) truncated at [0.5, 5]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Lognormal distribution to a specific %! ## range and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.LognormalDistribution.var000066400000000000000000000004571524624707500254460ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Lognormal distribution %! rng (42); %! data = lognrnd (0, 1, 10000, 1); %! pd = fitdist (data, "Lognormal"); %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## positive skewed values in the distribution. statistics-release-1.9.2/inst/demos/prob.LoguniformDistribution.Lower000066400000000000000000000016131524624707500261300ustar00rootroot00000000000000%!demo %! ## Create a Log-uniform distribution with default parameters %! pd = LoguniformDistribution (); %! %! ## Query parameter 'Lower' (lower limit) %! pd.Lower %! %! ## Set parameter 'Lower' %! pd.Lower = 0.5 %! %! ## Use this to initialize or modify the lower limit of a Log-uniform %! ## distribution. The lower limit must be a positive real scalar, defining %! ## the minimum value in the support range, useful for modeling quantities %! ## spanning orders of magnitude, like particle sizes or frequencies. %!demo %! ## Create a Log-uniform distribution object by calling its constructor %! pd = LoguniformDistribution (0.1, 10) %! %! ## Query parameter 'Lower' %! pd.Lower %! %! ## This demonstrates direct construction with a specific lower limit, %! ## suitable for scenarios where the distribution starts from a small %! ## positive value, such as in Bayesian priors for scale parameters. statistics-release-1.9.2/inst/demos/prob.LoguniformDistribution.Upper000066400000000000000000000025561524624707500261420ustar00rootroot00000000000000%!demo %! ## Create a Log-uniform distribution with default parameters %! pd = LoguniformDistribution (); %! %! ## Query parameter 'Upper' (upper limit) %! pd.Upper %! %! ## Set parameter 'Upper' %! pd.Upper = 5 %! %! ## Use this to initialize or modify the upper limit of a Log-uniform %! ## distribution. The upper limit must be a positive real scalar greater %! ## than Lower, defining the maximum value in the support range. %!demo %! ## Create a Log-uniform distribution object by calling its constructor %! pd = LoguniformDistribution (1, 100) %! %! ## Query parameter 'Upper' %! pd.Upper %! %! ## This shows how to set the upper limit directly via the constructor, %! ## ideal for bounding the distribution in applications like parameter %! ## estimation over wide ranges. LoguniformDistribution: %!demo %! ## Create a Log-uniform distribution with default parameters (Lower=1, Upper=4) %! pd = LoguniformDistribution () %! %! ## This is the basic constructor call, useful for quick initialization %! ## with standard bounds for exploratory analysis. %!demo %! ## Create a Log-uniform distribution with specified parameters %! pd = LoguniformDistribution (0.01, 100) %! %! ## Use this constructor to define custom bounds, appropriate for modeling %! ## variables with logarithmic uniformity, such as in astrophysics or %! ## economics for quantities like incomes or sizes. statistics-release-1.9.2/inst/demos/prob.LoguniformDistribution.cdf000066400000000000000000000014051524624707500255730ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Log-uniform distribution %! x = 0:0.01:10; %! pd1 = LoguniformDistribution (1, 4); %! pd2 = LoguniformDistribution (1, 6); %! pd3 = LoguniformDistribution (1, 8); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"Lower=1, Upper=4", "Lower=1, Upper=6", "Lower=1, Upper=8"}, ... %! "location", "southeast") %! title ("Log-uniform CDF") %! xlabel ("values in x (Lower <= x <= Upper)") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Log-uniform distributions, showing how probability %! ## accumulates over the range, useful in uncertainty modeling. statistics-release-1.9.2/inst/demos/prob.LoguniformDistribution.icdf000066400000000000000000000013511524624707500257440ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Log-uniform distribution %! p = 0.001:0.001:0.999; %! pd1 = LoguniformDistribution (1, 4); %! pd2 = LoguniformDistribution (1, 6); %! pd3 = LoguniformDistribution (1, 8); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"Lower=1, Upper=4", "Lower=1, Upper=6", "Lower=1, Upper=8"}, ... %! "location", "northwest") %! title ("Log-uniform iCDF") %! xlabel ("Probability") %! ylabel ("values in x (Lower <= x <= Upper)") %! %! ## This demonstrates the inverse CDF (quantiles) for Log-uniform %! ## distributions, useful for finding values at specific probabilities, %! ## such as in Monte Carlo simulations. statistics-release-1.9.2/inst/demos/prob.LoguniformDistribution.iqr000066400000000000000000000005121524624707500256300ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Log-uniform distribution %! pd = LoguniformDistribution (1, 10); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, measuring the spread %! ## of the middle 50% of the distribution on a log scale, helpful for %! ## robust statistics in skewed data. statistics-release-1.9.2/inst/demos/prob.LoguniformDistribution.mean000066400000000000000000000005601524624707500257600ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Log-uniform distributions %! pd1 = LoguniformDistribution (1, 5); %! pd2 = LoguniformDistribution (1, 10); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Log-uniform %! ## distributions with different upper limits, representing the average %! ## value over the range. statistics-release-1.9.2/inst/demos/prob.LoguniformDistribution.median000066400000000000000000000005451524624707500263000ustar00rootroot00000000000000%!demo %! ## Compute the median for different Log-uniform distributions %! pd1 = LoguniformDistribution (1, 5); %! pd2 = LoguniformDistribution (1, 10); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which is the geometric mean of %! ## the bounds, splitting the distribution into equal probability halves. statistics-release-1.9.2/inst/demos/prob.LoguniformDistribution.pdf000066400000000000000000000012761524624707500256160ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Log-uniform distribution %! x = 0.1:0.01:10; %! pd1 = LoguniformDistribution (1, 4); %! pd2 = LoguniformDistribution (1, 6); %! pd3 = LoguniformDistribution (1, 8); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"Lower=1, Upper=4", "Lower=1, Upper=6", "Lower=1, Upper=8"}, ... %! "location", "northeast") %! title ("Log-uniform PDF") %! xlabel ("values in x (Lower <= x <= Upper)") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Log-uniform %! ## distributions, showing the likelihood decreasing inversely with x. statistics-release-1.9.2/inst/demos/prob.LoguniformDistribution.plot000066400000000000000000000012741524624707500260210ustar00rootroot00000000000000%!demo %! ## Create a Log-uniform distribution with fixed parameters Lower=1 and %! ## Upper=4 and plot its PDF. %! pd = LoguniformDistribution (1, 4); %! plot (pd) %! title ("Fixed Log-uniform distribution with Lower=1 and Upper=4") %!demo %! ## Generate a data set of 100 random samples from a Log-uniform %! ## distribution with parameters Lower=1 and Upper=10. Plot its CDF. %! rng (42); %! data = exp (unifrnd (log (1), log (10), 100, 1)); %! pd = LoguniformDistribution (1, 10); %! plot (pd, "PlotType", "cdf") %! title ("Log-uniform distribution with Lower=1 and Upper=10") %! %! ## Use this to visualize the CDF, useful for understanding cumulative %! ## probabilities in the distribution. statistics-release-1.9.2/inst/demos/prob.LoguniformDistribution.random000066400000000000000000000007521524624707500263230ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Log-uniform distribution %! rng (42); %! pd = LoguniformDistribution (1, 10); %! samples = random (pd, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Log-uniform(Lower=1, Upper=10)") %! xlabel ("values in x (Lower <= x <= Upper)") %! ylabel ("Frequency") %! %! ## This generates random samples from a Log-uniform distribution, useful %! ## for simulating data uniform on a log scale, like in power-law phenomena. statistics-release-1.9.2/inst/demos/prob.LoguniformDistribution.std000066400000000000000000000004011524624707500256240ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Log-uniform distribution %! pd = LoguniformDistribution (1, 10); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, measuring the variability %! ## in the distribution's values. statistics-release-1.9.2/inst/demos/prob.LoguniformDistribution.truncate000066400000000000000000000020101524624707500266550ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Log-uniform distribution, with parameters Lower=1 %! ## and Upper=10, truncated at [2, 5] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! rng (42); %! data_all = exp (unifrnd (log (1), log (10), 20000, 1)); %! data = data_all(data_all >= 2 & data_all <= 5); %! data = data(1:7500); %! %! pd = LoguniformDistribution (1, 10); %! t = truncate (pd, 2, 5); %! %! [counts, centers] = hist (data, 50); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on; %! %! ## Plot histogram and truncated PDF %! x = linspace (0.5, 5, 500); %! y = pdf (t, x); %! plot (x, y, "r", "linewidth", 2); %! title ("Log-uniform distribution (Lower=1, Upper=10) truncated at [2, 5]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Log-uniform distribution to a specific %! ## range and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.LoguniformDistribution.var000066400000000000000000000003571524624707500256340ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Log-uniform distribution %! pd = LoguniformDistribution (1, 10); %! var_value = var (pd) %! %! ## Use this to calculate the variance, quantifying the spread of the %! ## values in the distribution. statistics-release-1.9.2/inst/demos/prob.MultinomialDistribution.Probabilities000066400000000000000000000024161524624707500300030ustar00rootroot00000000000000%!demo %! ## Create a Multinomial distribution with default parameters %! pd = MultinomialDistribution (); %! %! ## Query parameter 'Probabilities' (outcome probabilities) %! pd.Probabilities %! %! ## Use this to query the vector of probabilities for each outcome in a %! ## Multinomial distribution. The probabilities must sum to 1 and represent %! ## the likelihood of each category in a single trial. %!demo %! ## Create a Multinomial distribution with specified parameters %! pd = MultinomialDistribution ([0.1, 0.2, 0.3, 0.2, 0.1, 0.1]); %! %! ## Query parameter 'Probabilities' %! pd.Probabilities %! %! ## Set parameter 'Probabilities' %! pd.Probabilities = [0.4, 0.3, 0.3] %! %! ## Use this to initialize or modify the probabilities vector in a Multinomial %! ## distribution. The vector must be positive real scalars summing to 1, useful %! ## for modeling categorical outcomes like dice rolls or survey responses. %!demo %! ## Create a Multinomial distribution object by calling its constructor %! pd = MultinomialDistribution ([1/6, 1/6, 1/6, 1/6, 1/6, 1/6]); %! %! ## Query parameter 'Probabilities' %! pd.Probabilities %! %! ## This demonstrates direct construction with specific probabilities, %! ## ideal for modeling fair or biased categorical events, such as a dice roll. statistics-release-1.9.2/inst/demos/prob.MultinomialDistribution.cdf000066400000000000000000000014701524624707500257460ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Multinomial distribution %! x = 1:0.1:6; %! pd1 = MultinomialDistribution ([0.4, 0.3, 0.3]); %! pd2 = MultinomialDistribution ([0.2, 0.2, 0.2, 0.2, 0.2]); %! pd3 = MultinomialDistribution ([1/6, 1/6, 1/6, 1/6, 1/6, 1/6]); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"3 outcomes", "5 outcomes", "6 outcomes (dice)"}, ... %! "location", "southeast") %! title ("Multinomial CDF") %! xlabel ("values in x (outcome index)") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Multinomial distributions, showing how probability %! ## accumulates across categorical outcomes, useful in decision analysis. statistics-release-1.9.2/inst/demos/prob.MultinomialDistribution.icdf000066400000000000000000000014431524624707500261170ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Multinomial distribution %! p = 0.001:0.001:0.999; %! pd1 = MultinomialDistribution ([0.4, 0.3, 0.3]); %! pd2 = MultinomialDistribution ([0.2, 0.2, 0.2, 0.2, 0.2]); %! pd3 = MultinomialDistribution ([1/6, 1/6, 1/6, 1/6, 1/6, 1/6]); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"3 outcomes", "5 outcomes", "6 outcomes (dice)"}, ... %! "location", "northwest") %! title ("Multinomial iCDF") %! xlabel ("Probability") %! ylabel ("values in x (outcome index)") %! %! ## This demonstrates the inverse CDF (quantiles) for Multinomial %! ## distributions, useful for finding outcome thresholds corresponding to %! ## given probabilities, such as in risk assessment. statistics-release-1.9.2/inst/demos/prob.MultinomialDistribution.iqr000066400000000000000000000005631524624707500260070ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Multinomial distribution %! pd = MultinomialDistribution ([0.1, 0.2, 0.3, 0.2, 0.1, 0.1]); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the categorical outcomes, helpful for understanding %! ## central variability in discrete data. statistics-release-1.9.2/inst/demos/prob.MultinomialDistribution.mean000066400000000000000000000006361524624707500261350ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Multinomial distributions %! pd1 = MultinomialDistribution ([0.4, 0.3, 0.3]); %! pd2 = MultinomialDistribution ([0.2, 0.2, 0.2, 0.2, 0.2]); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Multinomial %! ## distributions with different probabilities, representing the average %! ## outcome index in categorical data. statistics-release-1.9.2/inst/demos/prob.MultinomialDistribution.median000066400000000000000000000006111524624707500264430ustar00rootroot00000000000000%!demo %! ## Compute the median for different Multinomial distributions %! pd1 = MultinomialDistribution ([0.4, 0.3, 0.3]); %! pd2 = MultinomialDistribution ([0.2, 0.2, 0.2, 0.2, 0.2]); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median outcome index, which splits the distribution %! ## into two equal probability halves, robust to uneven probabilities. statistics-release-1.9.2/inst/demos/prob.MultinomialDistribution.pdf000066400000000000000000000013301524624707500257560ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Multinomial distribution %! x = 1:0.1:6; %! pd1 = MultinomialDistribution ([0.4, 0.3, 0.3]); %! pd2 = MultinomialDistribution ([0.2, 0.2, 0.2, 0.2, 0.2]); %! pd3 = MultinomialDistribution ([1/6, 1/6, 1/6, 1/6, 1/6, 1/6]); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"3 outcomes", "5 outcomes", "6 outcomes (dice)"}, ... %! "location", "northeast") %! title ("Multinomial PDF") %! xlabel ("values in x (outcome index)") %! ylabel ("Probability") %! %! ## This visualizes the probability mass function for Multinomial %! ## distributions, showing the likelihood for each discrete outcome. statistics-release-1.9.2/inst/demos/prob.MultinomialDistribution.plot000066400000000000000000000010541524624707500261660ustar00rootroot00000000000000%!demo %! ## Create a Multinomial distribution with fixed parameters and plot its PDF. %! pd = MultinomialDistribution ([0.1, 0.2, 0.3, 0.2, 0.1, 0.1]); %! plot (pd) %! title ("Multinomial distribution PDF") %!demo %! ## Create a Multinomial distribution and plot its CDF. %! pd = MultinomialDistribution ([0.1, 0.2, 0.3, 0.2, 0.1, 0.1]); %! plot (pd, "PlotType", "cdf") %! title ("Multinomial distribution CDF") %! %! ## Use this to visualize the cumulative distribution function, %! ## useful for understanding probability accumulation across outcomes. statistics-release-1.9.2/inst/demos/prob.MultinomialDistribution.random000066400000000000000000000007631524624707500264760ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Multinomial distribution %! rng (42); %! pd = MultinomialDistribution ([1/6, 1/6, 1/6, 1/6, 1/6, 1/6]); %! samples = random (pd, 500, 1); %! hist (samples, 6) %! title ("Histogram of 500 random samples from Multinomial (fair dice)") %! xlabel ("values in x (outcome index)") %! ylabel ("Frequency") %! %! ## This generates random categorical outcomes from a Multinomial %! ## distribution, useful for simulating events like dice rolls or classifications. statistics-release-1.9.2/inst/demos/prob.MultinomialDistribution.std000066400000000000000000000004561524624707500260070ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Multinomial distribution %! pd = MultinomialDistribution ([0.1, 0.2, 0.3, 0.2, 0.1, 0.1]); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in the outcome indices for categorical data. statistics-release-1.9.2/inst/demos/prob.MultinomialDistribution.truncate000066400000000000000000000015011524624707500270320ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Multinomial distribution truncated at [2, 5] intervals. %! ## Generate 10000 random samples from this truncated distribution and %! ## superimpose a histogram. %! %! rng (42); %! pd = MultinomialDistribution ([0.1, 0.2, 0.3, 0.2, 0.1, 0.1]); %! t = truncate (pd, 2, 5); %! data_all = random (pd, 20000, 1); %! data = data_all(data_all >= 2 & data_all <= 5); %! data = data(1:10000); %! %! ## Plot histogram and truncated PDF %! x = 2:5; %! y = pdf (t, x); %! plot (x, y * numel (data), "bo-") %! hold on %! hist (data, 4) %! hold off %! title ("Multinomial distribution truncated at [2, 5]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Multinomial distribution to a specific %! ## range of outcomes and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.MultinomialDistribution.var000066400000000000000000000004261524624707500260020ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Multinomial distribution %! pd = MultinomialDistribution ([0.1, 0.2, 0.3, 0.2, 0.1, 0.1]); %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## outcome indices in the distribution. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.cdf000066400000000000000000000016111524624707500251610ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Nakagami distribution %! randg ('state', 42); %! x = -1:0.01:5; %! data1 = nakarnd (0.6, 1, 10000, 1); %! data2 = nakarnd (1, 1, 10000, 1); %! data3 = nakarnd (2, 1, 10000, 1); %! pd1 = fitdist (data1, "Nakagami"); %! pd2 = fitdist (data2, "Nakagami"); %! pd3 = fitdist (data3, "Nakagami"); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"mu = 0.6, omega = 1", "mu = 1, omega = 1", "mu = 2, omega = 1"}, ... %! "location", "southeast") %! title ("Nakagami CDF") %! xlabel ("values in x (x >= 0)") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Nakagami distributions, showing how probability accumulates %! ## for signal amplitudes, useful in wireless communications analysis. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.icdf000066400000000000000000000015661524624707500253430ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Nakagami distribution %! randg ('state', 42); %! p = 0.001:0.001:0.999; %! data1 = nakarnd (0.6, 1, 10000, 1); %! data2 = nakarnd (1, 1, 10000, 1); %! data3 = nakarnd (2, 1, 10000, 1); %! pd1 = fitdist (data1, "Nakagami"); %! pd2 = fitdist (data2, "Nakagami"); %! pd3 = fitdist (data3, "Nakagami"); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"mu = 0.6, omega = 1", "mu = 1, omega = 1", "mu = 2, omega = 1"}, ... %! "location", "northwest") %! title ("Nakagami iCDF") %! xlabel ("Probability") %! ylabel ("values in x (x >= 0)") %! %! ## This demonstrates the inverse CDF (quantiles) for Nakagami distributions, %! ## useful for finding amplitude thresholds corresponding to given probabilities, %! ## such as in fading channel simulations. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.iqr000066400000000000000000000006021524624707500252170ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Nakagami distribution %! randg ('state', 42); %! data = nakarnd (1, 1, 10000, 1); %! pd = fitdist (data, "Nakagami"); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, helpful for understanding variability %! ## in signal amplitudes. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.mean000066400000000000000000000006761524624707500253570ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Nakagami distributions %! randg ('state', 42); %! data1 = nakarnd (0.6, 1, 10000, 1); %! data2 = nakarnd (1, 1, 10000, 1); %! pd1 = fitdist (data1, "Nakagami"); %! pd2 = fitdist (data2, "Nakagami"); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Nakagami distributions %! ## with different shape parameters, representing average signal amplitude. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.median000066400000000000000000000007021524624707500256620ustar00rootroot00000000000000%!demo %! ## Compute the median for different Nakagami distributions %! randg ('state', 42); %! data1 = nakarnd (0.6, 1, 10000, 1); %! data2 = nakarnd (1, 1, 10000, 1); %! pd1 = fitdist (data1, "Nakagami"); %! pd2 = fitdist (data2, "Nakagami"); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution into %! ## two equal probability halves, robust to skewness in amplitude data. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.mu000066400000000000000000000014551524624707500250540ustar00rootroot00000000000000%!demo %! ## Create a Nakagami distribution with default parameters %! randg ('state', 42); %! data = nakarnd (1, 1, 10000, 1); %! pd = fitdist (data, "Nakagami"); %! %! ## Query parameter 'mu' (shape parameter) %! pd.mu %! %! ## Set parameter 'mu' %! pd.mu = 2 %! %! ## Use this to initialize or modify the shape parameter of a Nakagami %! ## distribution. The shape parameter must be a real scalar >= 0.5, controlling %! ## the fading severity in signal modeling; higher mu indicates less fading. %!demo %! ## Create a Nakagami distribution object by calling its constructor %! pd = NakagamiDistribution (1.5, 2) %! %! ## Query parameter 'mu' %! pd.mu %! %! ## This demonstrates direct construction with a specific shape parameter, %! ## useful for modeling wireless channel fading with known characteristics. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.negloglik000066400000000000000000000007011524624707500263770ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Nakagami distribution %! rng (42); %! randg ('state', 42); %! data = nakarnd (1, 1, 100, 1); %! pd_fitted = fitdist (data, "Nakagami"); %! params = [pd_fitted.mu, pd_fitted.omega]; %! nlogL_nakalike = nakalike (params, data) %! %! ## This is useful for assessing the fit of a Nakagami distribution to data, %! ## with lower values indicating a better fit, often used in model comparison. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.omega000066400000000000000000000014511524624707500255170ustar00rootroot00000000000000%!demo %! ## Create a Nakagami distribution with default parameters %! randg ('state', 42); %! data = nakarnd (1, 1, 10000, 1); %! pd = fitdist (data, "Nakagami"); %! %! ## Query parameter 'omega' (spread parameter) %! pd.omega %! %! ## Set parameter 'omega' %! pd.omega = 2 %! %! ## Use this to initialize or modify the spread parameter in a Nakagami %! ## distribution. The spread parameter must be a positive real scalar, representing %! ## the average power in signal amplitude modeling. %!demo %! ## Create a Nakagami distribution object by calling its constructor %! pd = NakagamiDistribution (1, 1.5) %! %! ## Query parameter 'omega' %! pd.omega %! %! ## This shows how to set the spread parameter directly via the constructor, %! ## ideal for simulating signal envelopes with specified power levels. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.paramci000066400000000000000000000006231524624707500260430ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Nakagami distribution %! rng (42); %! randg ('state', 42); %! data = nakarnd (1, 1, 1000, 1); %! pd_fitted = fitdist (data, "Nakagami"); %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (mu and %! ## omega), providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.pdf000066400000000000000000000014471524624707500252050ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Nakagami distribution %! randg ('state', 42); %! x = -1:0.01:5; %! data1 = nakarnd (0.6, 1, 10000, 1); %! data2 = nakarnd (1, 1, 10000, 1); %! data3 = nakarnd (2, 1, 10000, 1); %! pd1 = fitdist (data1, "Nakagami"); %! pd2 = fitdist (data2, "Nakagami"); %! pd3 = fitdist (data3, "Nakagami"); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"mu = 0.6, omega = 1", "mu = 1, omega = 1", "mu = 2, omega = 1"}, ... %! "location", "northeast") %! title ("Nakagami PDF") %! xlabel ("values in x (x >= 0)") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Nakagami distributions, %! ## showing the likelihood for signal amplitudes. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.plot000066400000000000000000000033741524624707500254130ustar00rootroot00000000000000%!demo %! ## Create a Nakagami distribution with fixed parameters mu = 1 and %! ## omega = 1 and plot its PDF. %! %! randg ('state', 42); %! data = nakarnd (1, 1, 10000, 1); %! pd = fitdist (data, "Nakagami"); %! plot (pd) %! title ("Fixed Nakagami distribution with mu = 1 and omega = 1") %!demo %! ## Generate a data set of 100 random samples from a Nakagami %! ## distribution with parameters mu = 1 and omega = 1. Fit a Nakagami %! ## distribution to this data and plot its CDF superimposed over an empirical %! ## CDF. %! %! rng (42); %! randg ('state', 42); %! data = nakarnd (1, 1, 100, 1); %! pd_fitted = fitdist (data, "Nakagami"); %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Nakagami distribution with mu = %0.2f and omega = %0.2f"; %! title (sprintf (txt, pd_fitted.mu, pd_fitted.omega)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. %!demo %! ## Generate a data set of 200 random samples from a Nakagami %! ## distribution with parameters mu = 1 and omega = 1. Display a probability %! ## plot for the Nakagami distribution fit to the data. %! %! rng (42); %! randg ('state', 42); %! data = nakarnd (1, 1, 200, 1); %! pd_fitted = fitdist (data, "Nakagami"); %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Nakagami", ... %! " distribution with mu = %0.2f and omega = %0.2f"); %! title (sprintf (txt, pd_fitted.mu, pd_fitted.omega)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Nakagami model is appropriate. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.proflik000066400000000000000000000006721524624707500261010ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the shape parameter of a fitted %! ## Nakagami distribution %! rng (42); %! randg ('state', 42); %! data = nakarnd (1, 1, 1000, 1); %! pd_fitted = fitdist (data, "Nakagami"); %! [nlogL, param] = proflik (pd_fitted, 1, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the shape parameter (mu), %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.random000066400000000000000000000006771524624707500257200ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Nakagami distribution %! rng (42); %! randg ('state', 42); %! samples = nakarnd (1, 1, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Nakagami(mu=1, omega=1)") %! xlabel ("values in x (x >= 0)") %! ylabel ("Frequency") %! %! ## This generates random samples from a Nakagami distribution, useful for %! ## simulating fading channels or signal envelopes in communications. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.std000066400000000000000000000004621524624707500252220ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Nakagami distribution %! randg ('state', 42); %! data = nakarnd (1, 1, 10000, 1); %! pd = fitdist (data, "Nakagami"); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in signal amplitudes. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.truncate000066400000000000000000000020051524624707500262500ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Nakagami distribution, with parameters mu = 1 %! ## and omega = 1, truncated at [0.5, 2] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! %! rng (42); %! randg ('state', 42); %! data_all = nakarnd (1, 1, 20000, 1); %! data = data_all(data_all >= 0.5 & data_all <= 2); %! data = data(1:10000); %! %! pd = fitdist (data, "Nakagami"); %! t = truncate (pd, 0.5, 2); %! [counts, centers] = hist (data, 50); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on; %! %! ## Plot histogram and truncated PDF %! x = linspace (0.5, 5, 500); %! y = pdf (t, x); %! plot (x, y, "r", "linewidth", 2); %! title ("Nakagami distribution (mu = 1, omega = 1) truncated at [0.5, 2]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Nakagami distribution to a specific range %! ## and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.NakagamiDistribution.var000066400000000000000000000004631524624707500252210ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Nakagami distribution %! randg ('state', 42); %! data = nakarnd (1, 1, 10000, 1); %! pd = fitdist (data, "Nakagami"); %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## signal amplitudes in the distribution. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.P000066400000000000000000000015031524624707500263310ustar00rootroot00000000000000%!demo %! ## Create a Negative Binomial distribution with default parameters %! randg ('state', 42); %! randp ('state', 42); %! data = nbinrnd(5, 0.5, 10000, 1); %! pd = fitdist (data, "NegativeBinomial"); %! %! ## Query parameter 'P' (probability of success) %! pd.P %! %! ## Set parameter 'P' %! pd.P = 0.3 %! %! ## Use this to initialize or modify the success probability in a Negative %! ## Binomial distribution. P must be a real scalar in (0,1], influencing the %! ## mean and variance of the distribution. %!demo %! ## Create a Negative Binomial distribution object by calling its constructor %! pd = NegativeBinomialDistribution(5, 0.3) %! %! ## Query parameter 'P' %! pd.P %! %! ## This shows how to set the success probability directly via the constructor, %! ## ideal for modeling varying success rates in repeated trials. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.R000066400000000000000000000016521524624707500263400ustar00rootroot00000000000000%!demo %! ## Create a Negative Binomial distribution with default parameters %! randg ('state', 42); %! randp ('state', 42); %! data = nbinrnd(5, 0.5, 10000, 1); %! pd = fitdist (data, "NegativeBinomial"); %! %! ## Query parameter 'R' (number of successes) %! pd.R %! %! ## Set parameter 'R' %! pd.R = 10 %! %! ## Use this to initialize or modify the number of successes parameter in a %! ## Negative Binomial distribution. R must be a positive scalar, controlling %! ## the shape and often fixed based on the problem context, like successes in trials. %!demo %! ## Create a Negative Binomial distribution object by calling its constructor %! pd = NegativeBinomialDistribution(10, 0.3) %! %! ## Query parameter 'R' %! pd.R %! %! ## This demonstrates direct construction with a specific number of successes, %! ## useful for modeling scenarios with a known fixed number of events, such as %! ## in reliability testing or count processes. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.cdf000066400000000000000000000016541524624707500266750ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Negative Binomial distribution %! randg ('state', 42); %! randp ('state', 42); %! x = 0:20; %! data1 = nbinrnd(5, 0.3, 10000, 1); %! data2 = nbinrnd(5, 0.5, 10000, 1); %! data3 = nbinrnd(5, 0.7, 10000, 1); %! pd1 = fitdist (data1, "NegativeBinomial"); %! pd2 = fitdist (data2, "NegativeBinomial"); %! pd3 = fitdist (data3, "NegativeBinomial"); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"R=5, P=0.3", "R=5, P=0.5", "R=5, P=0.7"}, "location", "southeast") %! title ("Negative Binomial CDF") %! xlabel ("values in x (number of failures)") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Negative Binomial distributions, showing how probability %! ## accumulates for count data, useful in modeling overdispersed counts. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.icdf000066400000000000000000000016551524624707500270470ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Negative Binomial distribution %! randg ('state', 42); %! randp ('state', 42); %! p = 0.001:0.001:0.999; %! data1 = nbinrnd(5, 0.3, 10000, 1); %! data2 = nbinrnd(5, 0.5, 10000, 1); %! data3 = nbinrnd(5, 0.7, 10000, 1); %! pd1 = fitdist (data1, "NegativeBinomial"); %! pd2 = fitdist (data2, "NegativeBinomial"); %! pd3 = fitdist (data3, "NegativeBinomial"); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"R=5, P=0.3", "R=5, P=0.5", "R=5, P=0.7"}, "location", "northwest") %! title ("Negative Binomial iCDF") %! xlabel ("Probability") %! ylabel ("values in x (number of failures)") %! %! ## This demonstrates the inverse CDF (quantiles) for Negative Binomial %! ## distributions, useful for finding count values corresponding to given %! ## probabilities, such as in risk assessment or inventory thresholds. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.iqr000066400000000000000000000006451524624707500267330ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Negative Binomial distribution %! randg ('state', 42); %! randp ('state', 42); %! data = nbinrnd(5, 0.5, 10000, 1); %! pd = fitdist (data, "NegativeBinomial"); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, helpful for understanding variability %! ## in count data. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.mean000066400000000000000000000010051524624707500270470ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Negative Binomial distributions %! randg ('state', 42); %! randp ('state', 42); %! data1 = nbinrnd(5, 0.3, 10000, 1); %! data2 = nbinrnd(5, 0.5, 10000, 1); %! pd1 = fitdist (data1, "NegativeBinomial"); %! pd2 = fitdist (data2, "NegativeBinomial"); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Negative Binomial %! ## distributions with different success probabilities, representing average %! ## number of failures. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.median000066400000000000000000000007571524624707500274010ustar00rootroot00000000000000%!demo %! ## Compute the median for different Negative Binomial distributions %! randg ('state', 42); %! randp ('state', 42); %! data1 = nbinrnd(5, 0.3, 10000, 1); %! data2 = nbinrnd(5, 0.5, 10000, 1); %! pd1 = fitdist (data1, "NegativeBinomial"); %! pd2 = fitdist (data2, "NegativeBinomial"); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution %! ## into two equal probability halves, robust to skewness in count data. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.negloglik000066400000000000000000000007571524624707500301170ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Negative Binomial distribution %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! data = nbinrnd(5, 0.5, 100, 1); %! pd_fitted = fitdist (data, "NegativeBinomial"); %! params = [pd_fitted.R, pd_fitted.P]; %! nlogL_nbinlike = nbinlike (params, data) %! %! ## This is useful for assessing the fit of a Negative Binomial distribution to %! ## data, with lower values indicating a better fit, often used in model comparison. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.paramci000066400000000000000000000006761524624707500275600ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Negative Binomial %! ## distribution %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! data = nbinrnd(5, 0.5, 1000, 1); %! pd_fitted = fitdist (data, "NegativeBinomial"); %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (R and P), %! ## providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.pdf000066400000000000000000000015251524624707500267070ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Negative Binomial distribution %! randg ('state', 42); %! randp ('state', 42); %! x = 0:20; %! data1 = nbinrnd(5, 0.3, 10000, 1); %! data2 = nbinrnd(5, 0.5, 10000, 1); %! data3 = nbinrnd(5, 0.7, 10000, 1); %! pd1 = fitdist (data1, "NegativeBinomial"); %! pd2 = fitdist (data2, "NegativeBinomial"); %! pd3 = fitdist (data3, "NegativeBinomial"); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"R=5, P=0.3", "R=5, P=0.5", "R=5, P=0.7"}, "location", "northeast") %! title ("Negative Binomial PDF") %! xlabel ("values in x (number of failures)") %! ylabel ("Probability density") %! %! ## This visualizes the probability mass function for Negative Binomial %! ## distributions, showing the likelihood for discrete count values. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.plot000066400000000000000000000036061524624707500271160ustar00rootroot00000000000000%!demo %! ## Create a Negative Binomial distribution with fixed parameters R=5 and %! ## P=0.5 and plot its PDF. %! %! randg ('state', 42); %! randp ('state', 42); %! data = nbinrnd(5, 0.5, 10000, 1); %! pd = fitdist (data, "NegativeBinomial"); %! plot (pd) %! title ("Fixed Negative Binomial distribution with R=5 and P=0.5") %!demo %! ## Generate a data set of 100 random samples from a Negative Binomial %! ## distribution with parameters R=5 and P=0.5. Fit a Negative Binomial %! ## distribution to this data and plot its CDF superimposed over an empirical %! ## CDF. %! %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! data = nbinrnd(5, 0.5, 100, 1); %! pd_fitted = fitdist (data, "NegativeBinomial"); %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Negative Binomial distribution with R=%0.2f and P=%0.2f"; %! title (sprintf (txt, pd_fitted.R, pd_fitted.P)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit in count data. %!demo %! ## Generate a data set of 200 random samples from a Negative Binomial %! ## distribution with parameters R=5 and P=0.5. Display a probability %! ## plot for the Negative Binomial distribution fit to the data. %! %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! data = nbinrnd(5, 0.5, 200, 1); %! pd_fitted = fitdist (data, "NegativeBinomial"); %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Negative Binomial", ... %! " distribution with R=%0.2f and P=%0.2f"); %! title (sprintf (txt, pd_fitted.R, pd_fitted.P)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Negative Binomial model is appropriate. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.proflik000066400000000000000000000007651524624707500276110ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the success probability parameter %! ## of a fitted Negative Binomial distribution %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! data = nbinrnd(5, 0.5, 1000, 1); %! pd_fitted = fitdist (data, "NegativeBinomial"); %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the success probability (P), %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.random000066400000000000000000000010511524624707500274100ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Negative Binomial distribution %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! samples = random (NegativeBinomialDistribution(5, 0.5), 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from NegativeBinomial(R=5, P=0.5)") %! xlabel ("values in x (number of failures)") %! ylabel ("Frequency") %! %! ## This generates random samples from a Negative Binomial distribution, useful %! ## for simulating count data like failures in trials or overdispersed Poisson processes. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.std000066400000000000000000000005571524624707500267340ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Negative Binomial distribution %! randg ('state', 42); %! randp ('state', 42); %! data = nbinrnd(5, 0.5, 10000, 1); %! pd = fitdist (data, "NegativeBinomial"); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in the count values of the distribution. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.truncate000066400000000000000000000021731524624707500277630ustar00rootroot00000000000000%!demo %! ## Generate a deterministic data set mimicking 10000 samples from a truncated %! ## Negative Binomial distribution with parameters R = 5 and P = 0.5, %! ## truncated at [2, 10]. Fit a Negative Binomial distribution to this data %! ## and plot a PDF of the truncated fitted distribution superimposed on a %! ## histogram of the data. %! %! x_vals = 2:10; %! probs = nbinpdf (x_vals, 5, 0.5); %! total_prob = nbincdf (10, 5, 0.5) - nbincdf (1, 5, 0.5); %! probs = probs / total_prob; %! N = 10000; %! freq = round (probs * N); %! data = []; %! data = repelem (x_vals, freq); %! warning ("off", "all"); %! pd = fitdist (data, "NegativeBinomial"); %! t = truncate (pd, 2, 10); %! %! ## Plot histogram and truncated PDF %! plot (t) %! hold on %! %! [counts, centers] = hist (data, 50); %! bar (centers, counts / trapz (centers, counts), 'hist'); %! %! hold off %! title ("Negative Binomial distribution (R=5, P=0.5) truncated at [2, 10]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Negative Binomial distribution to a specific %! ## range and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.NegativeBinomialDistribution.var000066400000000000000000000005301524624707500267210ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Negative Binomial distribution %! randg ('state', 42); %! randp ('state', 42); %! data = nbinrnd(5, 0.5, 10000, 1); %! pd = fitdist (data, "NegativeBinomial"); %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## count values in the distribution. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.cdf000066400000000000000000000015441524624707500247060ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Normal distribution %! rng (42); %! x = -5:0.01:5; %! data1 = 0 + 0.5 * randn (10000, 1); %! data2 = 0 + 1.0 * randn (10000, 1); %! data3 = 0 + 2.0 * randn (10000, 1); %! pd1 = fitdist (data1, "Normal"); %! pd2 = fitdist (data2, "Normal"); %! pd3 = fitdist (data3, "Normal"); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 2"}, ... %! "location", "southeast") %! title ("Normal CDF") %! xlabel ("values in x") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Normal distributions, showing how probability accumulates, %! ## essential in hypothesis testing or confidence intervals. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.icdf000066400000000000000000000014771524624707500250640ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Normal distribution %! rng (42); %! p = 0.001:0.001:0.999; %! data1 = 0 + 0.5 * randn (10000, 1); %! data2 = 0 + 1.0 * randn (10000, 1); %! data3 = 0 + 2.0 * randn (10000, 1); %! pd1 = fitdist (data1, "Normal"); %! pd2 = fitdist (data2, "Normal"); %! pd3 = fitdist (data3, "Normal"); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 2"}, ... %! "location", "northwest") %! title ("Normal iCDF") %! xlabel ("Probability") %! ylabel ("values in x") %! %! ## This demonstrates the inverse CDF (quantiles) for Normal %! ## distributions, useful for finding critical values in statistics, %! ## such as z-scores for confidence levels. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.iqr000066400000000000000000000005171524624707500247440ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Normal distribution %! rng (42); %! data = randn (10000, 1); %! pd = fitdist (data, "Normal"); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, robust to outliers in symmetric data. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.mean000066400000000000000000000006461524624707500250740ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Normal distributions %! rng (42); %! data1 = 0 + 0.5 * randn (10000, 1); %! data2 = 0 + 1.0 * randn (10000, 1); %! pd1 = fitdist (data1, "Normal"); %! pd2 = fitdist (data2, "Normal"); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Normal %! ## distributions with different scale parameters, representing the average value. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.median000066400000000000000000000006531524624707500254070ustar00rootroot00000000000000%!demo %! ## Compute the median for different Normal distributions %! rng (42); %! data1 = 0 + 0.5 * randn (10000, 1); %! data2 = 0 + 1.0 * randn (10000, 1); %! pd1 = fitdist (data1, "Normal"); %! pd2 = fitdist (data2, "Normal"); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which equals the mean in symmetric %! ## Normal distributions, useful for central tendency measures. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.mu000066400000000000000000000014521524624707500245710ustar00rootroot00000000000000%!demo %! ## Create a Normal distribution with default parameters %! rng (42); %! data = randn (10000, 1); %! pd = fitdist (data, "Normal"); %! %! ## Query parameter 'mu' (location parameter, mean) %! pd.mu %! %! ## Set parameter 'mu' %! pd.mu = 1 %! %! ## Use this to initialize or modify the mean of a Normal distribution. %! ## The mean parameter must be a real scalar, representing the center of symmetry, %! ## useful for shifting the distribution, such as modeling centered data like errors. %!demo %! ## Create a Normal distribution object by calling its constructor %! pd = NormalDistribution (1.5, 2) %! %! ## Query parameter 'mu' %! pd.mu %! %! ## This demonstrates direct construction with a specific mean, %! ## suitable for modeling data with a known central tendency, like IQ scores or heights. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.negloglik000066400000000000000000000006421524624707500261230ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Normal distribution %! rng (42); %! data = randn (100, 1); %! pd_fitted = fitdist (data, "Normal"); %! params = [pd_fitted.mu, pd_fitted.sigma]; %! nlogL_normlike = normlike (params, data) %! %! ## This is useful for assessing the fit of a Normal distribution to %! ## data, with lower values indicating a better fit, often used in optimization or AIC/BIC. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.paramci000066400000000000000000000005651524624707500255700ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Normal %! ## distribution %! rng (42); %! data = randn (1000, 1); %! pd_fitted = fitdist (data, "Normal"); %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (mu %! ## and sigma), providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.pdf000066400000000000000000000014171524624707500247220ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Normal distribution %! rng (42); %! x = -5:0.01:5; %! data1 = 0 + 0.5 * randn (10000, 1); %! data2 = 0 + 1.0 * randn (10000, 1); %! data3 = 0 + 2.0 * randn (10000, 1); %! pd1 = fitdist (data1, "Normal"); %! pd2 = fitdist (data2, "Normal"); %! pd3 = fitdist (data3, "Normal"); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5", "mu = 0, sigma = 1", "mu = 0, sigma = 2"}, ... %! "location", "northeast") %! title ("Normal PDF") %! xlabel ("values in x") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Normal %! ## distributions, showing the bell-shaped curve for symmetric data. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.plot000066400000000000000000000032371524624707500251310ustar00rootroot00000000000000%!demo %! ## Create a Normal distribution with fixed parameters mu = 0 and %! ## sigma = 1 and plot its PDF. %! %! rng (42); %! data = randn (10000, 1); %! pd = fitdist (data, "Normal"); %! plot (pd) %! title ("Fixed Normal distribution with mu = 0 and sigma = 1") %!demo %! ## Generate a data set of 100 random samples from a Normal %! ## distribution with parameters mu = 0 and sigma = 1. Fit a Normal %! ## distribution to this data and plot its CDF superimposed over an empirical %! ## CDF. %! %! rng (42); %! data = randn (100, 1); %! pd_fitted = fitdist (data, "Normal"); %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Normal distribution with mu = %0.2f and sigma = %0.2f"; %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit in symmetric distributions. %!demo %! ## Generate a data set of 200 random samples from a Normal %! ## distribution with parameters mu = 0 and sigma = 1. Display a probability %! ## plot for the Normal distribution fit to the data. %! %! rng (42); %! data = randn (200, 1); %! pd_fitted = fitdist (data, "Normal"); %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Normal", ... %! " distribution with mu = %0.2f and sigma = %0.2f"); %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking normality assumptions. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.proflik000066400000000000000000000006541524624707500256210ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the scale parameter of a fitted %! ## Normal distribution %! rng (42); %! data = randn (1000, 1); %! pd_fitted = fitdist (data, "Normal"); %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the scale parameter (sigma), %! ## helping to understand the uncertainty in parameter estimates for symmetric data. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.random000066400000000000000000000006121524624707500254250ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Normal distribution %! rng (42); %! samples = randn (500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Normal(mu=0, sigma=1)") %! xlabel ("values in x") %! ylabel ("Frequency") %! %! ## This generates random samples from a Normal distribution, useful %! ## for simulating symmetric data like noise or natural variations. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.sigma000066400000000000000000000014301524624707500252440ustar00rootroot00000000000000%!demo %! ## Create a Normal distribution with default parameters %! rng (42); %! data = randn (10000, 1); %! pd = fitdist (data, "Normal"); %! %! ## Query parameter 'sigma' (scale parameter, standard deviation) %! pd.sigma %! %! ## Set parameter 'sigma' %! pd.sigma = 2 %! %! ## Use this to initialize or modify the standard deviation in a Normal %! ## distribution. The scale parameter must be a positive real scalar, controlling %! ## the spread around the mean. %!demo %! ## Create a Normal distribution object by calling its constructor %! pd = NormalDistribution (0, 1.5) %! %! ## Query parameter 'sigma' %! pd.sigma %! %! ## This shows how to set the scale parameter directly via the constructor, %! ## ideal for modeling variability in symmetric data, such as measurement precision. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.std000066400000000000000000000004751524624707500247460ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Normal distribution %! rng (42); %! data = randn (10000, 1); %! pd = fitdist (data, "Normal"); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which equals sigma %! ## and measures the spread around the mean in symmetric distributions. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.truncate000066400000000000000000000020131524624707500257670ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Normal distribution, with parameters mu = 0 %! ## and sigma = 1, truncated at [-2, 2] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! %! rng (42); %! data_all = randn (20000, 1); %! data = data_all(data_all >= -2 & data_all <= 2); %! data = data(1:10000); %! pd = fitdist (data, "Normal"); %! t = truncate (pd, -2, 2); %! [counts, centers] = hist (data, 50); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on; %! %! ## Plot histogram and truncated PDF %! x = linspace (-2, 2, 500); %! y = pdf (t, x); %! plot (x, y, "r", "linewidth", 2); %! hold off %! title ("Normal distribution (mu = 0, sigma = 1) truncated at [-2, 2]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Normal distribution to a specific %! ## range and visualizing the resulting distribution with random samples, %! ## useful for bounded symmetric data. statistics-release-1.9.2/inst/demos/prob.NormalDistribution.var000066400000000000000000000004371524624707500247420ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Normal distribution %! rng (42); %! data = randn (10000, 1); %! pd = fitdist (data, "Normal"); %! var_value = var (pd) %! %! ## Use this to calculate the variance, which is sigma squared %! ## and quantifies the spread in symmetric distributions. statistics-release-1.9.2/inst/demos/prob.PiecewiseLinearDistribution.Fx000066400000000000000000000005371524624707500263500ustar00rootroot00000000000000%!demo %! ## Create a Piecewise Linear distribution object by calling its constructor %! pd = PiecewiseLinearDistribution ([0; 1; 2; 3], [0; 0.3; 0.7; 1]) %! %! ## Query parameter 'Fx' %! pd.Fx %! %! ## This shows how to set CDF values directly via the constructor, ideal for %! ## defining piecewise linear approximations of arbitrary distributions. statistics-release-1.9.2/inst/demos/prob.PiecewiseLinearDistribution.cdf000066400000000000000000000013711524624707500265240ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Piecewise Linear distribution %! load patients %! [f1, x1] = ecdf (Weight); %! [f2, x2] = ecdf (Height); %! pd1 = PiecewiseLinearDistribution (x1(1:10:end), f1(1:10:end)); %! pd2 = PiecewiseLinearDistribution (x2(1:10:end), f2(1:10:end)); %! vals = 50:0.1:250; %! p1 = cdf (pd1, vals); %! p2 = cdf (pd2, vals); %! plot (vals, p1, "-b", vals, p2, "-r") %! grid on %! legend ({"Weight", "Height"}, "location", "southeast") %! title ("Piecewise Linear CDF") %! xlabel ("values in x") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Piecewise Linear distributions, showing how probability %! ## accumulates across the defined segments. statistics-release-1.9.2/inst/demos/prob.PiecewiseLinearDistribution.icdf000066400000000000000000000013541524624707500266760ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Piecewise Linear distribution %! load patients %! [f1, x1] = ecdf (Weight); %! [f2, x2] = ecdf (Height); %! pd1 = PiecewiseLinearDistribution (x1(1:10:end), f1(1:10:end)); %! pd2 = PiecewiseLinearDistribution (x2(1:10:end), f2(1:10:end)); %! p = 0.001:0.001:0.999; %! x_vals1 = icdf (pd1, p); %! x_vals2 = icdf (pd2, p); %! plot (p, x_vals1, "-b", p, x_vals2, "-r") %! grid on %! legend ({"Weight", "Height"}, "location", "northwest") %! title ("Piecewise Linear iCDF") %! xlabel ("Probability") %! ylabel ("values in x") %! %! ## This demonstrates the inverse CDF (quantiles) for Piecewise Linear %! ## distributions, useful for finding values corresponding to given %! ## probabilities in empirical data. statistics-release-1.9.2/inst/demos/prob.PiecewiseLinearDistribution.iqr000066400000000000000000000006231524624707500265620ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Piecewise Linear distribution %! load patients %! [f, x] = ecdf (Weight); %! pd = PiecewiseLinearDistribution (x(1:5:end), f(1:5:end)); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, helpful for summarizing variability %! ## in empirical datasets. statistics-release-1.9.2/inst/demos/prob.PiecewiseLinearDistribution.mean000066400000000000000000000007341524624707500267120ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Piecewise Linear distributions %! load patients %! [f1, x1] = ecdf (Weight); %! [f2, x2] = ecdf (Height); %! pd1 = PiecewiseLinearDistribution (x1(1:5:end), f1(1:5:end)); %! pd2 = PiecewiseLinearDistribution (x2(1:5:end), f2(1:5:end)); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Piecewise Linear %! ## distributions based on empirical data, representing the average value. statistics-release-1.9.2/inst/demos/prob.PiecewiseLinearDistribution.median000066400000000000000000000007431524624707500272270ustar00rootroot00000000000000%!demo %! ## Compute the median for different Piecewise Linear distributions %! load patients %! [f1, x1] = ecdf (Weight); %! [f2, x2] = ecdf (Height); %! pd1 = PiecewiseLinearDistribution (x1(1:5:end), f1(1:5:end)); %! pd2 = PiecewiseLinearDistribution (x2(1:5:end), f2(1:5:end)); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution %! ## into two equal probability halves, robust for skewed empirical data. statistics-release-1.9.2/inst/demos/prob.PiecewiseLinearDistribution.pdf000066400000000000000000000012641524624707500265420ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Piecewise Linear distribution %! load patients %! [f1, x1] = ecdf (Weight); %! [f2, x2] = ecdf (Height); %! pd1 = PiecewiseLinearDistribution (x1(1:10:end), f1(1:10:end)); %! pd2 = PiecewiseLinearDistribution (x2(1:10:end), f2(1:10:end)); %! vals = 50:0.1:250; %! y1 = pdf (pd1, vals); %! y2 = pdf (pd2, vals); %! plot (vals, y1, "-b", vals, y2, "-r") %! grid on %! legend ({"Weight", "Height"}, "location", "northeast") %! title ("Piecewise Linear PDF") %! xlabel ("values in x") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Piecewise Linear %! ## distributions, showing the density across segments. statistics-release-1.9.2/inst/demos/prob.PiecewiseLinearDistribution.plot000066400000000000000000000012211524624707500267400ustar00rootroot00000000000000%!demo %! ## Create a Piecewise Linear distribution and plot its PDF. %! load patients %! [f, x] = ecdf (Weight); %! pd = PiecewiseLinearDistribution (x(1:5:end), f(1:5:end)); %! plot (pd) %! title ("Piecewise Linear distribution from Weight data") %!demo %! ## Create a Piecewise Linear distribution from data and plot its CDF. %! load patients %! [f, x] = ecdf (Weight); %! pd = PiecewiseLinearDistribution (x(1:5:end), f(1:5:end)); %! plot (pd, "PlotType", "cdf") %! title ("CDF of Piecewise Linear distribution from Weight data") %! %! ## Use this to visualize the CDF of the Piecewise Linear distribution, %! ## useful for comparing to empirical CDF. statistics-release-1.9.2/inst/demos/prob.PiecewiseLinearDistribution.random000066400000000000000000000007771524624707500272610ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Piecewise Linear distribution %! load patients %! [f, x] = ecdf (Weight); %! pd = PiecewiseLinearDistribution (x(1:5:end), f(1:5:end)); %! samples = random (pd, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Piecewise Linear (Weight data)") %! xlabel ("values in x") %! ylabel ("Frequency") %! %! ## This generates random samples from a Piecewise Linear distribution, %! ## useful for simulating data based on empirical distributions. statistics-release-1.9.2/inst/demos/prob.PiecewiseLinearDistribution.std000066400000000000000000000005041524624707500265570ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Piecewise Linear distribution %! load patients %! [f, x] = ecdf (Weight); %! pd = PiecewiseLinearDistribution (x(1:5:end), f(1:5:end)); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, measuring variability %! ## in the empirical distribution. statistics-release-1.9.2/inst/demos/prob.PiecewiseLinearDistribution.truncate000066400000000000000000000014761524624707500276230ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Piecewise Linear distribution truncated at [130, 180]. %! ## Generate 10000 random samples from this truncated distribution and %! ## superimpose a histogram. %! %! load patients %! [f, x] = ecdf (Weight); %! pd = PiecewiseLinearDistribution (x(1:5:end), f(1:5:end)); %! t = truncate (pd, 130, 180); %! data = random (t, 10000, 1); %! %! ## Plot histogram and truncated PDF %! [counts, centers] = hist (data, 20); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on %! plot (t) %! hold off %! title ("Piecewise Linear distribution truncated at [130, 180]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Piecewise Linear distribution to a specific %! ## range and visualizing the result with random samples. statistics-release-1.9.2/inst/demos/prob.PiecewiseLinearDistribution.var000066400000000000000000000004611524624707500265570ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Piecewise Linear distribution %! load patients %! [f, x] = ecdf (Weight); %! pd = PiecewiseLinearDistribution (x(1:5:end), f(1:5:end)); %! var_value = var (pd) %! %! ## Use this to calculate the variance, quantifying the spread in the %! ## empirical distribution. statistics-release-1.9.2/inst/demos/prob.PiecewiseLinearDistribution.x000066400000000000000000000005471524624707500262430ustar00rootroot00000000000000%!demo %! ## Create a Piecewise Linear distribution object by calling its constructor %! pd = PiecewiseLinearDistribution ([0; 1; 2; 3], [0; 0.3; 0.7; 1]) %! %! ## Query parameter 'x' %! pd.x %! %! ## This demonstrates direct construction with specific x values, useful for %! ## modeling custom empirical distributions or interpolating between known points. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.cdf000066400000000000000000000015541524624707500251110ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Poisson distribution %! rng (42); %! randp ('state', 42); %! x = 0:20; %! data1 = poissrnd (2, 10000, 1); %! data2 = poissrnd (5, 10000, 1); %! data3 = poissrnd (10, 10000, 1); %! pd1 = fitdist (data1, "Poisson"); %! pd2 = fitdist (data2, "Poisson"); %! pd3 = fitdist (data3, "Poisson"); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"lambda = 2", "lambda = 5", "lambda = 10"}, "location", "southeast") %! title ("Poisson CDF") %! xlabel ("values in x (non-negative integers)") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Poisson distributions, showing the probability of observing %! ## at most k events, useful in risk assessment or queueing theory. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.icdf000066400000000000000000000015531524624707500252610ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Poisson distribution %! rng (42); %! randp ('state', 42); %! p = 0.001:0.001:0.999; %! data1 = poissrnd (2, 10000, 1); %! data2 = poissrnd (5, 10000, 1); %! data3 = poissrnd (10, 10000, 1); %! pd1 = fitdist (data1, "Poisson"); %! pd2 = fitdist (data2, "Poisson"); %! pd3 = fitdist (data3, "Poisson"); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"lambda = 2", "lambda = 5", "lambda = 10"}, "location", "northwest") %! title ("Poisson iCDF") %! xlabel ("Probability") %! ylabel ("values in x (non-negative integers)") %! %! ## This demonstrates the inverse CDF (quantiles) for Poisson distributions, %! ## useful for finding the number of events corresponding to a given probability, %! ## such as in inventory management for stock levels. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.iqr000066400000000000000000000005711524624707500251460ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Poisson distribution %! rng (42); %! randp ('state', 42); %! data = poissrnd (5, 10000, 1); %! pd = fitdist (data, "Poisson"); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, measuring the spread of the %! ## middle 50% of the distribution, helpful for understanding variability in count data. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.lambda000066400000000000000000000014551524624707500255750ustar00rootroot00000000000000%!demo %! ## Create a Poisson distribution from data %! rng (42); %! randp ('state', 42); %! data = poissrnd (5, 100, 1); %! pd = fitdist (data, "Poisson"); %! %! ## Query parameter 'lambda' (rate parameter) %! pd.lambda %! %! ## Set parameter 'lambda' %! pd.lambda = 10 %! %! ## Use this to initialize or modify the rate parameter of a Poisson distribution. %! ## The rate parameter must be a positive real scalar, representing both the mean %! ## and variance, ideal for count data modeling. %!demo %! ## Create a Poisson distribution object by calling its constructor %! pd = PoissonDistribution (7.5) %! %! ## Query parameter 'lambda' %! pd.lambda %! %! ## This shows direct construction with a specific rate parameter, useful for %! ## theoretical analysis or when lambda is estimated from prior knowledge. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.mean000066400000000000000000000006721524624707500252750ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Poisson distributions %! rng (42); %! randp ('state', 42); %! data1 = poissrnd (2, 10000, 1); %! data2 = poissrnd (5, 10000, 1); %! pd1 = fitdist (data1, "Poisson"); %! pd2 = fitdist (data2, "Poisson"); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Poisson distributions, %! ## which equals lambda, representing the average number of events. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.median000066400000000000000000000006761524624707500256160ustar00rootroot00000000000000%!demo %! ## Compute the median for different Poisson distributions %! rng (42); %! randp ('state', 42); %! data1 = poissrnd (2, 10000, 1); %! data2 = poissrnd (5, 10000, 1); %! pd1 = fitdist (data1, "Poisson"); %! pd2 = fitdist (data2, "Poisson"); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median count, which splits the distribution into %! ## two equal probability halves, robust for skewed count data. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.negloglik000066400000000000000000000006011524624707500263200ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Poisson distribution %! rng (42); %! randp ('state', 42); %! data = poissrnd (5, 100, 1); %! pd_fitted = fitdist (data, "Poisson"); %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of a Poisson distribution to data, %! ## with lower values indicating better fit, often used in model selection. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.paramci000066400000000000000000000006131524624707500257640ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Poisson distribution %! rng (42); %! randp ('state', 42); %! data = poissrnd (5, 1000, 1); %! pd_fitted = fitdist (data, "Poisson"); %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameter (lambda), %! ## providing a range of plausible values based on the data. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.pdf000066400000000000000000000014011524624707500251150ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Poisson distribution %! rng (42); %! randp ('state', 42); %! x = 0:20; %! data1 = poissrnd (2, 10000, 1); %! data2 = poissrnd (5, 10000, 1); %! data3 = poissrnd (10, 10000, 1); %! pd1 = fitdist (data1, "Poisson"); %! pd2 = fitdist (data2, "Poisson"); %! pd3 = fitdist (data3, "Poisson"); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"lambda = 2", "lambda = 5", "lambda = 10"}, "location", "northeast") %! title ("Poisson PDF") %! xlabel ("values in x (non-negative integers)") %! ylabel ("Probability") %! %! ## This visualizes the probability mass function for Poisson distributions, %! ## showing the likelihood of exact count values. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.plot000066400000000000000000000031451524624707500253310ustar00rootroot00000000000000%!demo %! ## Create a Poisson distribution with fixed parameter lambda=5 and plot its PDF. %! rng (42); %! randp ('state', 42); %! data = poissrnd (5, 10000, 1); %! pd = fitdist (data, "Poisson"); %! plot (pd) %! title ("Fixed Poisson distribution with lambda = 5") %!demo %! ## Generate a data set of 100 random samples from a Poisson distribution with %! ## lambda=5. Fit a Poisson distribution to this data and plot its CDF superimposed %! ## over an empirical CDF. %! rng (42); %! randp ('state', 42); %! data = poissrnd (5, 100, 1); %! pd_fitted = fitdist (data, "Poisson"); %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Poisson distribution with lambda = %0.2f"; %! title (sprintf (txt, pd_fitted.lambda)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the data, %! ## useful for assessing model fit in count data. %!demo %! ## Generate a data set of 200 random samples from a Poisson distribution with %! ## lambda=5. Display a probability plot for the Poisson distribution fit to the data. %! rng (42); %! randp ('state', 42); %! data = poissrnd (5, 200, 1); %! pd_fitted = fitdist (data, "Poisson"); %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Poisson distribution with lambda = %0.2f"); %! title (sprintf (txt, pd_fitted.lambda)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the data, %! ## useful for checking if the Poisson model is appropriate for count data. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.proflik000066400000000000000000000007051524624707500260200ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the rate parameter of a fitted %! ## Poisson distribution %! rng (42); %! randp ('state', 42); %! data = poissrnd (5, 1000, 1); %! pd_fitted = fitdist (data, "Poisson"); %! [nlogL, param] = proflik (pd_fitted, 1, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the rate parameter (lambda), %! ## helping to understand uncertainty in parameter estimates for count models. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.random000066400000000000000000000007151524624707500256330ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Poisson distribution %! rng (42); %! randp ('state', 42); %! samples = poissrnd (5, 500, 1); %! hist (samples, 20) %! title ("Histogram of 500 random samples from Poisson(lambda=5)") %! xlabel ("values in x (non-negative integers)") %! ylabel ("Frequency") %! %! ## This generates random count samples from a Poisson distribution, useful %! ## for simulating event occurrences like defect counts or traffic arrivals. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.std000066400000000000000000000005101524624707500251360ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Poisson distribution %! rng (42); %! randp ('state', 42); %! data = poissrnd (5, 10000, 1); %! pd = fitdist (data, "Poisson"); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which equals sqrt(lambda), %! ## measuring variability in event counts. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.truncate000066400000000000000000000016411524624707500261770ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Poisson distribution, with lambda=5, truncated at [2, 10] %! ## intervals. Generate 10000 random samples from this truncated distribution %! ## and superimpose a histogram scaled accordingly. %! %! rng (42); %! randp ('state', 42); %! data_all = poissrnd (5, 20000, 1); %! data = data_all(data_all >= 2 & data_all <= 10); %! data = data(1:10000); %! %! pd = fitdist (data, "Poisson"); %! t = truncate (pd, 2, 10); %! %! ## Plot histogram and truncated %! [counts, centers] = hist (data, 20); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on %! plot (t) %! hold off %! title ("Poisson distribution (lambda=5) truncated at [2, 10]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Poisson distribution to a specific range %! ## and visualizing the result with random samples, useful for bounded count data. statistics-release-1.9.2/inst/demos/prob.PoissonDistribution.var000066400000000000000000000004551524624707500251440ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Poisson distribution %! rng (42); %! randp ('state', 42); %! data = poissrnd (5, 10000, 1); %! pd = fitdist (data, "Poisson"); %! var_value = var (pd) %! %! ## Use this to calculate the variance, which equals lambda, quantifying the %! ## spread in count data. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.cdf000066400000000000000000000015361524624707500252230ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Rayleigh distribution %! rng (42); %! x = -1:0.01:5; %! data1 = raylrnd (0.5, 10000, 1); %! data2 = raylrnd (1.0, 10000, 1); %! data3 = raylrnd (2.0, 10000, 1); %! pd1 = fitdist (data1, "Rayleigh"); %! pd2 = fitdist (data2, "Rayleigh"); %! pd3 = fitdist (data3, "Rayleigh"); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"sigma = 0.5", "sigma = 1", "sigma = 2"}, ... %! "location", "southeast") %! title ("Rayleigh CDF") %! xlabel ("values in x (x >= 0)") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Rayleigh distributions, showing how probability %! ## accumulates for nonnegative values, useful in signal processing or physics. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.icdf000066400000000000000000000015001524624707500253630ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Rayleigh distribution %! rng (42); %! p = 0.001:0.001:0.999; %! data1 = raylrnd (0.5, 10000, 1); %! data2 = raylrnd (1.0, 10000, 1); %! data3 = raylrnd (2.0, 10000, 1); %! pd1 = fitdist (data1, "Rayleigh"); %! pd2 = fitdist (data2, "Rayleigh"); %! pd3 = fitdist (data3, "Rayleigh"); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"sigma = 0.5", "sigma = 1", "sigma = 2"}, ... %! "location", "northwest") %! title ("Rayleigh iCDF") %! xlabel ("Probability") %! ylabel ("values in x (x >= 0)") %! %! ## This demonstrates the inverse CDF (quantiles) for Rayleigh %! ## distributions, useful for finding values corresponding to given %! ## probabilities, such as thresholds in radar detection. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.iqr000066400000000000000000000005731524624707500252620ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Rayleigh distribution %! rng (42); %! data = raylrnd (1, 10000, 1); %! pd = fitdist (data, "Rayleigh"); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, helpful for understanding central %! ## variability in nonnegative data. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.mean000066400000000000000000000007151524624707500254050ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Rayleigh distributions %! rng (42); %! data1 = raylrnd (0.5, 10000, 1); %! data2 = raylrnd (1.0, 10000, 1); %! pd1 = fitdist (data1, "Rayleigh"); %! pd2 = fitdist (data2, "Rayleigh"); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Rayleigh %! ## distributions with different scale parameters, representing average %! ## magnitude in applications like acoustics. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.median000066400000000000000000000006651524624707500257260ustar00rootroot00000000000000%!demo %! ## Compute the median for different Rayleigh distributions %! rng (42); %! data1 = raylrnd (0.5, 10000, 1); %! data2 = raylrnd (1.0, 10000, 1); %! pd1 = fitdist (data1, "Rayleigh"); %! pd2 = fitdist (data2, "Rayleigh"); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution %! ## into two equal probability halves, robust to skewness in nonnegative data. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.negloglik000066400000000000000000000005561524624707500264430ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Rayleigh distribution %! rng (42); %! data = raylrnd (1, 100, 1); %! pd_fitted = fitdist (data, "Rayleigh"); %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of a Rayleigh distribution to %! ## data, with lower values indicating a better fit, often used in model comparison. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.paramci000066400000000000000000000005661524624707500261050ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Rayleigh %! ## distribution %! rng (42); %! data = raylrnd (1, 1000, 1); %! pd_fitted = fitdist (data, "Rayleigh"); %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameter (sigma), %! ## providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.pdf000066400000000000000000000014001524624707500252260ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Rayleigh distribution %! rng (42); %! x = -1:0.01:5; %! data1 = raylrnd (0.5, 10000, 1); %! data2 = raylrnd (1.0, 10000, 1); %! data3 = raylrnd (2.0, 10000, 1); %! pd1 = fitdist (data1, "Rayleigh"); %! pd2 = fitdist (data2, "Rayleigh"); %! pd3 = fitdist (data3, "Rayleigh"); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"sigma = 0.5", "sigma = 1", "sigma = 2"}, ... %! "location", "northeast") %! title ("Rayleigh PDF") %! xlabel ("values in x (x >= 0)") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Rayleigh %! ## distributions, showing the likelihood for nonnegative values. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.plot000066400000000000000000000031111524624707500254340ustar00rootroot00000000000000%!demo %! ## Create a Rayleigh distribution with fixed parameter sigma = 1 and plot its PDF. %! %! rng (42); %! data = raylrnd (1, 10000, 1); %! pd = fitdist (data, "Rayleigh"); %! plot (pd) %! title ("Fixed Rayleigh distribution with sigma = 1") %!demo %! ## Generate a data set of 100 random samples from a Rayleigh %! ## distribution with parameter sigma = 1. Fit a Rayleigh %! ## distribution to this data and plot its CDF superimposed over an empirical %! ## CDF. %! %! rng (42); %! data = raylrnd (1, 100, 1); %! pd_fitted = fitdist (data, "Rayleigh"); %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Rayleigh distribution with sigma = %0.2f"; %! title (sprintf (txt, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. %!demo %! ## Generate a data set of 200 random samples from a Rayleigh %! ## distribution with parameter sigma = 1. Display a probability %! ## plot for the Rayleigh distribution fit to the data. %! %! rng (42); %! data = raylrnd (1, 200, 1); %! pd_fitted = fitdist (data, "Rayleigh"); %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Rayleigh", ... %! " distribution with sigma = %0.2f"); %! title (sprintf (txt, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Rayleigh model is appropriate. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.proflik000066400000000000000000000006421524624707500261320ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the scale parameter of a fitted %! ## Rayleigh distribution %! rng (42); %! data = raylrnd (1, 1000, 1); %! pd_fitted = fitdist (data, "Rayleigh"); %! [nlogL, param] = proflik (pd_fitted, 1, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the scale parameter (sigma), %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.random000066400000000000000000000006351524624707500257460ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Rayleigh distribution %! rng (42); %! samples = raylrnd (1, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Rayleigh(sigma=1)") %! xlabel ("values in x (x >= 0)") %! ylabel ("Frequency") %! %! ## This generates random samples from a Rayleigh distribution, useful %! ## for simulating nonnegative data like envelope of narrowband signals. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.sigma000066400000000000000000000014561524624707500255700ustar00rootroot00000000000000%! ## Create a Rayleigh distribution with default parameters %! data = raylrnd (1, 10000, 1); %! pd = fitdist (data, "Rayleigh"); %! %! ## Query parameter 'sigma' (scale parameter) %! pd.sigma %! %! ## Set parameter 'sigma' %! pd.sigma = 2 %! %! ## Use this to initialize or modify the scale parameter of a Rayleigh %! ## distribution. The scale parameter must be a positive real scalar, controlling %! ## the spread of the distribution, often used in modeling magnitudes like wind speeds. %!demo %! ## Create a Rayleigh distribution object by calling its constructor %! pd = RayleighDistribution (1.5) %! %! ## Query parameter 'sigma' %! pd.sigma %! %! ## This demonstrates direct construction with a specific scale parameter, %! ## useful for modeling nonnegative data like signal amplitudes in communications. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.std000066400000000000000000000004751524624707500252620ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Rayleigh distribution %! rng (42); %! data = raylrnd (1, 10000, 1); %! pd = fitdist (data, "Rayleigh"); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in the nonnegative values of the distribution. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.truncate000066400000000000000000000017311524624707500263110ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Rayleigh distribution, with parameter sigma = 1, %! ## truncated at [0.5, 2] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! %! rng (42); %! data_all = raylrnd (1, 20000, 1); %! data = data_all(data_all >= 0.5 & data_all <= 2); %! data = data(1:10000); %! %! pd = fitdist (data, "Rayleigh"); %! t = truncate (pd, 0.5, 2); %! %! [counts, centers] = hist (data, 50); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on; %! %! ## Plot histogram and truncated PDF %! x = linspace (0.5, 5, 500); %! y = pdf (t, x); %! plot (x, y, "r", "linewidth", 2); %! title ("Rayleigh distribution (sigma = 1) truncated at [0.5, 2]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Rayleigh distribution to a specific %! ## range and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.RayleighDistribution.var000066400000000000000000000004461524624707500252560ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Rayleigh distribution %! rng (42); %! data = raylrnd (1, 10000, 1); %! pd = fitdist (data, "Rayleigh"); %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## nonnegative values in the distribution. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.cdf000066400000000000000000000016251524624707500246630ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Rician distribution %! randg ('state', 42); %! randp ('state', 42); %! x = 0:0.01:5; %! data1 = ricernd (1, 0.5, [10000, 1]); %! data2 = ricernd (1, 1, [10000, 1]); %! data3 = ricernd (1, 2, [10000, 1]); %! pd1 = fitdist (data1, "Rician"); %! pd2 = fitdist (data2, "Rician"); %! pd3 = fitdist (data3, "Rician"); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"s = 1, sigma = 0.5", "s = 1, sigma = 1", "s = 1, sigma = 2"}, ... %! "location", "southeast") %! title ("Rician CDF") %! xlabel ("values in x (x >= 0)") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Rician distributions, showing how probability accumulates %! ## for non-negative signal magnitudes, useful in signal processing. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.icdf000066400000000000000000000015251524624707500250330ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Rician distribution %! randg ('state', 42); %! randp ('state', 42); %! p = 0.001:0.001:0.999; %! data1 = ricernd (1, 0.5, [10000, 1]); %! data2 = ricernd (1, 1, [10000, 1]); %! data3 = ricernd (1, 2, [10000, 1]); %! pd1 = fitdist (data1, "Rician"); %! pd2 = fitdist (data2, "Rician"); %! pd3 = fitdist (data3, "Rician"); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"s = 1, sigma = 0.5", "s = 1, sigma = 1", "s = 1, sigma = 2"}, ... %! "location", "northwest") %! title ("Rician iCDF") %! xlabel ("Probability") %! ylabel ("values in x (x >= 0)") %! %! ## This demonstrates the inverse CDF (quantiles) for Rician distributions, %! ## useful for finding signal magnitude thresholds in applications like radar. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.iqr000066400000000000000000000006301524624707500247150ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Rician distribution %! randg ('state', 42); %! randp ('state', 42); %! data = ricernd (1, 1, [10000, 1]); %! pd = fitdist (data, "Rician"); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, helpful for understanding variability %! ## in signal magnitudes. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.mean000066400000000000000000000007261524624707500250500ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Rician distributions %! randg ('state', 42); %! randp ('state', 42); %! data1 = ricernd (1, 0.5, [10000, 1]); %! data2 = ricernd (1, 1, [10000, 1]); %! pd1 = fitdist (data1, "Rician"); %! pd2 = fitdist (data2, "Rician"); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Rician distributions %! ## with different scale parameters, representing the average signal magnitude. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.median000066400000000000000000000007251524624707500253640ustar00rootroot00000000000000%!demo %! ## Compute the median for different Rician distributions %! randg ('state', 42); %! randp ('state', 42); %! data1 = ricernd (1, 0.5, [10000, 1]); %! data2 = ricernd (1, 1, [10000, 1]); %! pd1 = fitdist (data1, "Rician"); %! pd2 = fitdist (data2, "Rician"); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution into %! ## two equal probability halves, robust to skewness in signal data. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.negloglik000066400000000000000000000006351524624707500261020ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Rician distribution %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! data = ricernd (1, 1, [100, 1]); %! pd_fitted = fitdist (data, "Rician"); %! nlogL = negloglik (pd_fitted) %! %! ## This is useful for assessing the fit of a Rician distribution to data, %! ## with lower values indicating a better fit, often used in model comparison. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.paramci000066400000000000000000000006501524624707500255400ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Rician distribution %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! data = ricernd (1, 1, [1000, 1]); %! pd_fitted = fitdist (data, "Rician"); %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (s %! ## and sigma), providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.pdf000066400000000000000000000015021524624707500246720ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Rician distribution %! randg ('state', 42); %! randp ('state', 42); %! x = 0:0.01:5; %! data1 = ricernd (1, 0.5, [10000, 1]); %! data2 = ricernd (1, 1, [10000, 1]); %! data3 = ricernd (1, 2, [10000, 1]); %! pd1 = fitdist (data1, "Rician"); %! pd2 = fitdist (data2, "Rician"); %! pd3 = fitdist (data3, "Rician"); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"s = 1, sigma = 0.5", "s = 1, sigma = 1", "s = 1, sigma = 2"}, ... %! "location", "northeast") %! title ("Rician PDF") %! xlabel ("values in x (x >= 0)") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Rician distributions, %! ## showing the likelihood for non-negative signal magnitudes. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.plot000066400000000000000000000035251524624707500251060ustar00rootroot00000000000000%!demo %! ## Create a Rician distribution with fixed parameters s = 1 and sigma = 1 %! ## and plot its PDF. %! pd = RicianDistribution (1, 1); %! plot (pd) %! title ("Rician distribution with s = 1 and sigma = 1") %! %! ## Use this to visualize the PDF of a Rician distribution with fixed parameters, %! ## useful for understanding the shape of the distribution. %!demo %! ## Generate a data set of 100 random samples from a Rician distribution %! ## with parameters s = 1 and sigma = 1. Fit a Rician distribution to this %! ## data and plot its CDF superimposed over an empirical CDF. %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! data = ricernd (1, 1, [100, 1]); %! pd_fitted = fitdist (data, "Rician"); %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Rician distribution with s = %0.2f and sigma = %0.2f"; %! title (sprintf (txt, pd_fitted.s, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. %!demo %! ## Generate a data set of 200 random samples from a Rician distribution %! ## with parameters s = 1 and sigma = 1. Display a probability plot for the %! ## Rician distribution fit to the data. %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! data = ricernd (1, 1, [200, 1]); %! pd_fitted = fitdist (data, "Rician"); %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Rician distribution", ... %! " with s = %0.2f and sigma = %0.2f"); %! title (sprintf (txt, pd_fitted.s, pd_fitted.sigma)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the Rician model is appropriate. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.proflik000066400000000000000000000007231524624707500255730ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the scale parameter of a fitted %! ## Rician distribution %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! data = ricernd (1, 1, [1000, 1]); %! pd_fitted = fitdist (data, "Rician"); %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the scale parameter (sigma), %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.random000066400000000000000000000007331524624707500254060ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Rician distribution %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! samples = ricernd (1, 1, [500, 1]); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Rician(s=1, sigma=1)") %! xlabel ("values in x (x >= 0)") %! ylabel ("Frequency") %! %! ## This generates random samples from a Rician distribution, useful for %! ## simulating signal magnitudes in applications like wireless communications. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.s000066400000000000000000000015341524624707500243700ustar00rootroot00000000000000%!demo %! ## Create a Rician distribution by fitting to data %! randg ('state', 42); %! randp ('state', 42); %! data = ricernd (1, 1, [10000, 1]); % Generate data with s=1, sigma=1 %! pd = fitdist (data, "Rician"); %! %! ## Query parameter 's' (noncentrality parameter) %! pd.s %! %! ## Set parameter 's' %! pd.s = 1.5 %! %! ## Use this to initialize or modify the noncentrality parameter of a Rician %! ## distribution. The noncentrality parameter 's' must be a non-negative real %! ## scalar, representing the magnitude of the signal in the presence of noise. %!demo %! ## Create a Rician distribution object by calling its constructor %! pd = RicianDistribution (2, 1) %! %! ## Query parameter 's' %! pd.s %! %! ## This demonstrates direct construction with a specific noncentrality %! ## parameter, useful for modeling data with a known signal strength. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.sigma000066400000000000000000000023261524624707500252260ustar00rootroot00000000000000%!demo %! ## Create a Rician distribution with fitted parameters %! randg ('state', 42); %! randp ('state', 42); %! data = ricernd (1, 1, [10000, 1]); %! pd = fitdist (data, "Rician"); %! %! ## Query parameter 'sigma' (scale parameter) %! pd.sigma %! %! ## Set parameter 'sigma' %! pd.sigma = 1.2 %! %! ## Use this to initialize or modify the scale parameter in a Rician %! ## distribution. The scale parameter 'sigma' must be a positive real scalar, %! ## controlling the spread due to Gaussian noise. %!demo %! ## Create a Rician distribution object by calling its constructor %! pd = RicianDistribution (1, 1.5) %! %! ## Query parameter 'sigma' %! pd.sigma %! %! ## This shows how to set the scale parameter directly via the constructor, %! ## ideal for modeling variability in signal magnitude data. RicianDistribution constructor: %!demo %! ## Create a Rician distribution with specific parameters %! pd = RicianDistribution (2, 1) %! %! ## Display the distribution parameters %! pd.s %! pd.sigma %! %! ## Use the constructor to create a Rician distribution with fixed parameters %! ## 's' and 'sigma', suitable for scenarios where signal and noise parameters %! ## are known, such as in communication systems or image processing. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.std000066400000000000000000000005401524624707500247140ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Rician distribution %! randg ('state', 42); %! randp ('state', 42); %! data = ricernd (1, 1, [10000, 1]); %! pd = fitdist (data, "Rician"); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in the signal magnitudes of the distribution. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.truncate000066400000000000000000000020171524624707500257500ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Rician distribution, with parameters s = 1 and sigma = 1, %! ## truncated at [0.5, 3] intervals. Generate 10000 random samples from this %! ## truncated distribution and superimpose a histogram scaled accordingly %! rng (42); %! randg ('state', 42); %! randp ('state', 42); %! data_all = ricernd (1, 1, [20000, 1]); %! data = data_all(data_all >= 0.5 & data_all <= 3); %! data = data(1:10000); %! %! pd = fitdist (data, "Rician"); %! t = truncate (pd, 0.5, 3); %! %! [counts, centers] = hist (data, 50); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on; %! %! ## Plot histogram and truncated PDF %! x = linspace (0.5, 5, 500); %! y = pdf (t, x); %! plot (x, y, "r", "linewidth", 2); %! title ("Rician distribution (s = 1, sigma = 1) truncated at [0.5, 3]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Rician distribution to a specific range and %! ## visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.RicianDistribution.var000066400000000000000000000005111524624707500247100ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Rician distribution %! randg ('state', 42); %! randp ('state', 42); %! data = ricernd (1, 1, [10000, 1]); %! pd = fitdist (data, "Rician"); %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## signal magnitudes in the distribution. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.A000066400000000000000000000015111524624707500252040ustar00rootroot00000000000000%!demo %! ## Create a Triangular distribution with default parameters %! pd = makedist ("Triangular", "A", 0, "B", 0.5, "C", 1); %! %! ## Query parameter 'A' (lower limit) %! pd.A %! %! ## Set parameter 'A' %! pd.A = -1 %! %! ## Use this to initialize or modify the lower limit parameter of a Triangular %! ## distribution. The lower limit must be a real scalar less than the upper %! ## limit C, useful for defining the minimum possible value in scenarios like %! ## cost estimation or time to completion. %!demo %! ## Create a Triangular distribution object by calling its constructor %! pd = TriangularDistribution (1, 2, 3); %! %! ## Query parameter 'A' %! pd.A %! %! ## This demonstrates direct construction with a specific lower limit, %! ## useful for modeling bounded data, such as project durations or resource %! ## requirements. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.B000066400000000000000000000014601524624707500252100ustar00rootroot00000000000000%!demo %! ## Create a Triangular distribution with default parameters %! pd = makedist ("Triangular", "A", 0, "B", 0.5, "C", 1); %! %! ## Query parameter 'B' (peak location) %! pd.B %! %! ## Set parameter 'B' %! pd.B = 0.7 %! %! ## Use this to initialize or modify the peak location parameter of a Triangular %! ## distribution. The peak location must be a real scalar between A and C, %! ## representing the most likely value in applications like risk analysis. %!demo %! ## Create a Triangular distribution object by calling its constructor %! pd = TriangularDistribution (1, 2, 3); %! %! ## Query parameter 'B' %! pd.B %! %! ## This shows how to set the peak location directly via the constructor, %! ## ideal for modeling the mode of triangularly distributed data, such as %! ## expected task completion times. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.C000066400000000000000000000014301524624707500252060ustar00rootroot00000000000000%!demo %! ## Create a Triangular distribution with default parameters %! pd = makedist ("Triangular", "A", 0, "B", 0.5, "C", 1); %! %! ## Query parameter 'C' (upper limit) %! pd.C %! %! ## Set parameter 'C' %! pd.C = 2 %! %! ## Use this to initialize or modify the upper limit parameter of a Triangular %! ## distribution. The upper limit must be a real scalar greater than A, %! ## defining the maximum possible value in scenarios like budgeting or scheduling. %!demo %! ## Create a Triangular distribution object by calling its constructor %! pd = TriangularDistribution (1, 2, 3); %! %! ## Query parameter 'C' %! pd.C %! %! ## This demonstrates setting the upper limit directly via the constructor, %! ## useful for modeling the maximum bound in applications like cost or time estimates. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.cdf000066400000000000000000000014061524624707500255630ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Triangular distribution %! x = -1:0.01:6; %! pd1 = TriangularDistribution (0, 1, 2); %! pd2 = TriangularDistribution (1, 2, 3); %! pd3 = TriangularDistribution (2, 3, 4); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"A=0, B=1, C=2", "A=1, B=2, C=3", "A=2, B=3, C=4"}, ... %! "location", "southeast") %! title ("Triangular CDF") %! xlabel ("Values in x (A <= x <= C)") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Triangular distributions, showing how probability accumulates %! ## within the bounds, useful in risk assessment or forecasting. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.icdf000066400000000000000000000013771524624707500257430ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Triangular distribution %! p = 0.001:0.001:0.999; %! pd1 = TriangularDistribution (0, 1, 2); %! pd2 = TriangularDistribution (1, 2, 3); %! pd3 = TriangularDistribution (2, 3, 4); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"A=0, B=1, C=2", "A=1, B=2, C=3", "A=2, B=3, C=4"}, ... %! "location", "northwest") %! title ("Triangular iCDF") %! xlabel ("Probability") %! ylabel ("Values in x (A <= x <= C)") %! %! ## This demonstrates the inverse CDF (quantiles) for Triangular distributions, %! ## useful for finding values corresponding to given probabilities, such as %! ## thresholds in project planning or quality control. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.iqr000066400000000000000000000005451524624707500256250ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Triangular distribution %! pd = TriangularDistribution (0, 1, 2); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, helpful for understanding central %! ## variability in bounded data like cost estimates. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.mean000066400000000000000000000006201524624707500257440ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Triangular distributions %! pd1 = TriangularDistribution (0, 1, 2); %! pd2 = TriangularDistribution (1, 2, 3); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Triangular distributions %! ## with different parameters, representing the average value in scenarios %! ## like budgeting or time estimation. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.median000066400000000000000000000006151524624707500262650ustar00rootroot00000000000000%!demo %! ## Compute the median for different Triangular distributions %! pd1 = TriangularDistribution (0, 1, 2); %! pd2 = TriangularDistribution (1, 2, 3); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution into two %! ## equal probability halves, robust for skewed triangular data in applications %! ## like scheduling. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.pdf000066400000000000000000000013511524624707500255770ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Triangular distribution %! x = -1:0.01:6; %! pd1 = TriangularDistribution (0, 1, 2); %! pd2 = TriangularDistribution (1, 2, 3); %! pd3 = TriangularDistribution (2, 3, 4); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"A=0, B=1, C=2", "A=1, B=2, C=3", "A=2, B=3, C=4"}, ... %! "location", "northeast") %! title ("Triangular PDF") %! xlabel ("Values in x (A <= x <= C)") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Triangular %! ## distributions, showing the likelihood within the bounds, useful for %! ## understanding data distribution in risk analysis. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.plot000066400000000000000000000017161524624707500260110ustar00rootroot00000000000000%!demo %! ## Create a Triangular distribution with fixed parameters A=0, B=1, C=2 %! ## and plot its PDF. %! pd = TriangularDistribution (0, 1, 2); %! plot (pd) %! title ("Triangular distribution with A=0, B=1, C=2") %! %! ## Use this to visualize the PDF of a Triangular distribution with fixed %! ## parameters, useful for understanding the shape of the distribution. %!demo %! ## Generate a data set of 100 random samples from a Triangular distribution %! ## with parameters A=0, B=1, C=2. Plot its CDF. %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! data = trirnd (0, 1, 2, 100, 1); %! pd = makedist ("Triangular", "A", 0, "B", 1, "C", 2); %! plot (pd, "PlotType", "cdf") %! title ("Triangular distribution with A=0, B=1, C=2") %! legend ({"Fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the CDF of a Triangular distribution, useful for %! ## assessing cumulative probabilities in bounded data scenarios. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.random000066400000000000000000000006621524624707500263120ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Triangular distribution %! rng (42); %! samples = trirnd (0, 1, 2, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Triangular(A=0, B=1, C=2)") %! xlabel ("Values in x (A <= x <= C)") %! ylabel ("Frequency") %! %! ## This generates random samples from a Triangular distribution, useful for %! ## simulating bounded data like project costs or completion times. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.std000066400000000000000000000004661524624707500256260ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Triangular distribution %! pd = TriangularDistribution (0, 1, 2); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## within the bounds of the Triangular distribution, useful for risk assessment. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.truncate000066400000000000000000000022061524624707500266530ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Triangular distribution with parameters A=0, B=1, C=2, %! ## truncated at [0.5, 1.5] intervals. Generate 10000 random samples from this %! ## truncated distribution and superimpose a histogram scaled accordingly. %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! data_all = trirnd (0, 1, 2, 20000, 1); %! data = data_all(data_all >= 0.5 & data_all <= 1.5); %! data = data(1:10000); %! %! pd = makedist ("Triangular", "A", 0, "B", 1, "C", 2); %! t = truncate (pd, 0.5, 1.5); %! %! [counts, centers] = hist (data, 20); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on; %! %! ## Plot histogram and truncated PDF %! x = linspace (0.5, 5, 500); %! y = pdf (t, x); %! plot (x, y, "r", "linewidth", 2); %! title ("Triangular distribution (A=0, B=1, C=2) truncated at [0.5, 1.5]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Triangular distribution to a specific range %! ## and visualizing the resulting distribution with random samples, useful for %! ## constrained scenarios like limited budgets. statistics-release-1.9.2/inst/demos/prob.TriangularDistribution.var000066400000000000000000000004561524624707500256230ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Triangular distribution %! pd = TriangularDistribution (0, 1, 2); %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of values %! ## within the bounds of the Triangular distribution, useful for uncertainty analysis. statistics-release-1.9.2/inst/demos/prob.UniformDistribution.Lower000066400000000000000000000015041524624707500254250ustar00rootroot00000000000000%!demo %! ## Create a Uniform distribution with default parameters %! pd = makedist ("Uniform", "Lower", 2, "Upper", 5); %! %! ## Query parameter 'Lower' (lower limit parameter) %! pd.Lower %! %! ## Set parameter 'Lower' %! pd.Lower = 2 %! %! ## Use this to initialize or modify the lower bound of a Uniform distribution. %! ## The lower limit must be a real scalar less than the upper limit, useful for %! ## defining the range of a uniform variable, such as random measurements or time intervals. %!demo %! ## Create a Uniform distribution object by calling its constructor %! pd = UniformDistribution (1, 10) %! %! ## Query parameter 'Lower' %! pd.Lower %! %! ## This demonstrates direct construction with a specific lower limit, ideal for %! ## modeling data uniformly distributed over a known interval, such as random selections. statistics-release-1.9.2/inst/demos/prob.UniformDistribution.Upper000066400000000000000000000013271524624707500254330ustar00rootroot00000000000000%!demo %! ## Create a Uniform distribution with default parameters %! pd = makedist ("Uniform") %! %! ## Query parameter 'Upper' (upper limit parameter) %! pd.Upper %! %! ## Set parameter 'Upper' %! pd.Upper = 8 %! %! ## Use this to initialize or modify the upper bound of a Uniform distribution. %! ## The upper limit must be a real scalar greater than the lower limit, controlling %! ## the range of the distribution. %!demo %! ## Create a Uniform distribution object by calling its constructor %! pd = UniformDistribution (1, 10) %! %! ## Query parameter 'Upper' %! pd.Upper %! %! ## This shows how to set the upper limit directly via the constructor, useful %! ## for defining the maximum value in a uniform distribution. statistics-release-1.9.2/inst/demos/prob.UniformDistribution.cdf000066400000000000000000000014731524624707500250760ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Uniform distribution %! x = -1:0.01:12; %! pd1 = makedist ("Uniform", "Lower", 0, "Upper", 5); %! pd2 = makedist ("Uniform", "Lower", 2, "Upper", 8); %! pd3 = makedist ("Uniform", "Lower", 4, "Upper", 10); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"Lower = 0, Upper = 5", "Lower = 2, Upper = 8", "Lower = 4, Upper = 10"}, ... %! "location", "southeast") %! title ("Uniform CDF") %! xlabel ("Values in x") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Uniform distributions, showing how probability accumulates %! ## over the defined interval, useful in probability assessments or simulations. statistics-release-1.9.2/inst/demos/prob.UniformDistribution.icdf000066400000000000000000000014201524624707500252370ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Uniform distribution %! p = 0.001:0.001:0.999; %! pd1 = makedist ("Uniform", "Lower", 0, "Upper", 5); %! pd2 = makedist ("Uniform", "Lower", 2, "Upper", 8); %! pd3 = makedist ("Uniform", "Lower", 4, "Upper", 10); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"Lower = 0, Upper = 5", "Lower = 2, Upper = 8", "Lower = 4, Upper = 10"}, ... %! "location", "northwest") %! title ("Uniform iCDF") %! xlabel ("Probability") %! ylabel ("Values in x") %! %! ## This demonstrates the inverse CDF (quantiles) for Uniform distributions, %! ## useful for finding values corresponding to given probabilities, such as %! ## thresholds in uniform sampling. statistics-release-1.9.2/inst/demos/prob.UniformDistribution.iqr000066400000000000000000000005501524624707500251300ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Uniform distribution %! pd = makedist ("Uniform", "Lower", 0, "Upper", 10) %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, helpful for understanding central %! ## variability in uniformly distributed data. statistics-release-1.9.2/inst/demos/prob.UniformDistribution.mean000066400000000000000000000005751524624707500252640ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Uniform distributions %! pd1 = makedist ("Uniform", "Lower", 0, "Upper", 5); %! pd2 = makedist ("Uniform", "Lower", 2, "Upper", 8); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Uniform distributions %! ## with different bounds, representing the average value within the interval. statistics-release-1.9.2/inst/demos/prob.UniformDistribution.median000066400000000000000000000006061524624707500255740ustar00rootroot00000000000000%!demo %! ## Compute the median for different Uniform distributions %! pd1 = makedist ("Uniform", "Lower", 0, "Upper", 5); %! pd2 = makedist ("Uniform", "Lower", 2, "Upper", 8); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution %! ## into two equal probability halves, useful for central tendency in uniform data. statistics-release-1.9.2/inst/demos/prob.UniformDistribution.pdf000066400000000000000000000013361524624707500251110ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Uniform distribution %! x = -1:0.01:12; %! pd1 = makedist ("Uniform", "Lower", 0, "Upper", 5); %! pd2 = makedist ("Uniform", "Lower", 2, "Upper", 8); %! pd3 = makedist ("Uniform", "Lower", 4, "Upper", 10); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"Lower = 0, Upper = 5", "Lower = 2, Upper = 8", "Lower = 4, Upper = 10"}, ... %! "location", "northeast") %! title ("Uniform PDF") %! xlabel ("Values in x") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Uniform distributions, %! ## showing the constant likelihood within the defined interval. statistics-release-1.9.2/inst/demos/prob.UniformDistribution.plot000066400000000000000000000027741524624707500253250ustar00rootroot00000000000000%!demo %! ## Create a Uniform distribution with fixed parameters Lower = 0 and Upper = 5 %! ## and plot its PDF. %! pd = makedist ("Uniform", "Lower", 0, "Upper", 5) %! plot (pd) %! title ("Uniform distribution with Lower = 0 and Upper = 5") %! %! ## Use this to visualize the PDF of a Uniform distribution with fixed bounds, %! ## useful for understanding the uniform probability density. %!demo %! ## Generate a data set of 100 random samples from a Uniform distribution %! ## with parameters Lower = 0 and Upper = 5. Fit a Uniform distribution to this %! ## data and plot its CDF superimposed over an empirical CDF. %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd_fixed = makedist ("Uniform", "Lower", 0, "Upper", 5); %! data = random (pd_fixed, 100, 1); %! %! lower_hat = min (data); %! upper_hat = max (data); %! %! [f_empirical, x_empirical] = ecdf (data); %! stairs (x_empirical, f_empirical, 'b', 'LineWidth', 2); %! hold on; %! %! x = linspace (lower_hat - 1, upper_hat + 1, 200); %! y = (x - lower_hat) / (upper_hat - lower_hat); %! y(x < lower_hat) = 0; %! y(x > upper_hat) = 1; %! plot (x, y, 'r-', 'LineWidth', 2); %! %! % Title and legend %! txt = "Fitted Uniform distribution with Lower = %0.2f and Upper = %0.2f"; %! title (sprintf (txt, lower_hat, upper_hat)); %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast"); %! hold off; %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. statistics-release-1.9.2/inst/demos/prob.UniformDistribution.random000066400000000000000000000010241524624707500256120ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Uniform distribution %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Uniform", "Lower", 0, "Upper", 5) %! samples = random (pd, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Uniform(Lower=0, Upper=5)") %! xlabel ("Values in x") %! ylabel ("Frequency") %! %! ## This generates random samples from a Uniform distribution, useful %! ## for simulating data with equal probability over a fixed interval. statistics-release-1.9.2/inst/demos/prob.UniformDistribution.std000066400000000000000000000004161524624707500251300ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Uniform distribution %! pd = makedist ("Uniform", "Lower", 0, "Upper", 10) %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## within the uniform interval. statistics-release-1.9.2/inst/demos/prob.UniformDistribution.truncate000066400000000000000000000017541524624707500261710ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Uniform distribution, with parameters Lower = 0 %! ## and Upper = 10, truncated at [2, 8] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled %! ## accordingly %! rng (42); %! randg ('state', 42); %! rande ('state', 42); %! randp ('state', 42); %! pd = makedist ("Uniform", "Lower", 0, "Upper", 10) %! t = truncate (pd, 2, 8) %! data = random (t, 10000, 1); %! %! [counts, centers] = hist (data, 50); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on; %! %! ## Plot histogram and truncated PDF %! x = linspace (0.5, 5, 500); %! y = pdf (t, x); %! plot (x, y, "r", "linewidth", 2); %! title ("Uniform distribution (Lower = 0, Upper = 10) truncated at [2, 8]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Uniform distribution to a specific %! ## range and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.UniformDistribution.var000066400000000000000000000004051524624707500251240ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Uniform distribution %! pd = makedist ("Uniform", "Lower", 0, "Upper", 10) %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## values within the uniform interval. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.cdf000066400000000000000000000016101524624707500250530ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the Weibull distribution %! rande ('state', 42); %! x = 0:0.01:5; %! data1 = wblrnd (1, 0.5, 10000, 1); %! data2 = wblrnd (1, 1, 10000, 1); %! data3 = wblrnd (1, 2, 10000, 1); %! pd1 = fitdist (data1, "Weibull"); %! pd2 = fitdist (data2, "Weibull"); %! pd3 = fitdist (data3, "Weibull"); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"lambda = 1, k = 0.5", "lambda = 1, k = 1", "lambda = 1, k = 2"}, ... %! "location", "southeast") %! title ("Weibull CDF") %! xlabel ("values in x (x >= 0)") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different Weibull distributions, showing how probability accumulates %! ## for positive values, useful in survival analysis or time-to-event modeling. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.icdf000066400000000000000000000015661524624707500252360ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the Weibull distribution %! rande ('state', 42); %! p = 0.001:0.001:0.999; %! data1 = wblrnd (1, 0.5, 10000, 1); %! data2 = wblrnd (1, 1, 10000, 1); %! data3 = wblrnd (1, 2, 10000, 1); %! pd1 = fitdist (data1, "Weibull"); %! pd2 = fitdist (data2, "Weibull"); %! pd3 = fitdist (data3, "Weibull"); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"lambda = 1, k = 0.5", "lambda = 1, k = 1", "lambda = 1, k = 2"}, ... %! "location", "northwest") %! title ("Weibull iCDF") %! xlabel ("Probability") %! ylabel ("values in x (x >= 0)") %! %! ## This demonstrates the inverse CDF (quantiles) for Weibull distributions, %! ## useful for finding values corresponding to given probabilities, such as %! ## predicting failure times in engineering applications. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.iqr000066400000000000000000000005731524624707500251210ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a Weibull distribution %! rande ('state', 42); %! data = wblrnd (1, 1, 10000, 1); %! pd = fitdist (data, "Weibull"); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, helpful for understanding variability %! ## in lifetime data. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.k000066400000000000000000000014451524624707500245570ustar00rootroot00000000000000%!demo %! ## Create a Weibull distribution with default parameters %! rande ('state', 42); %! data = wblrnd (1, 1, 10000, 1); %! pd = fitdist (data, "Weibull"); %! %! ## Query parameter 'k' (shape parameter) %! pd.k %! %! ## Set parameter 'k' %! pd.k = 2 %! %! ## Use this to initialize or modify the shape parameter in a Weibull %! ## distribution. The shape parameter must be a positive real scalar, controlling %! ## the failure rate behavior (k<1 decreasing, k=1 constant, k>1 increasing). %!demo %! ## Create a Weibull distribution object by calling its constructor %! pd = WeibullDistribution (1, 3) %! %! ## Query parameter 'k' %! pd.k %! %! ## This shows how to set the shape parameter directly via the constructor, %! ## ideal for modeling different hazard functions in reliability engineering. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.lambda000066400000000000000000000016111524624707500255400ustar00rootroot00000000000000%!demo %! ## Create a Weibull distribution with default parameters %! rande ('state', 42); %! data = wblrnd (1, 1, 10000, 1); %! pd = fitdist (data, "Weibull"); %! %! ## Query parameter 'lambda' (scale parameter) %! pd.lambda %! %! ## Set parameter 'lambda' %! pd.lambda = 1.5 %! %! ## Use this to initialize or modify the scale parameter of a Weibull %! ## distribution. The scale parameter must be a positive real scalar, often %! ## representing the characteristic life in reliability analysis or the spread %! ## in lifetime modeling. %!demo %! ## Create a Weibull distribution object by calling its constructor %! pd = WeibullDistribution (2, 1.5) %! %! ## Query parameter 'lambda' %! pd.lambda %! %! ## This demonstrates direct construction with a specific scale parameter, %! ## useful for modeling failure times where lambda shifts the distribution, %! ## such as in survival or wind speed data. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.mean000066400000000000000000000006741524624707500252500ustar00rootroot00000000000000%!demo %! ## Compute the mean for different Weibull distributions %! rande ('state', 42); %! data1 = wblrnd (1, 0.5, 10000, 1); %! data2 = wblrnd (1, 2, 10000, 1); %! pd1 = fitdist (data1, "Weibull"); %! pd2 = fitdist (data2, "Weibull"); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for Weibull distributions %! ## with different shape parameters, representing average lifetime or duration. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.median000066400000000000000000000007051524624707500255600ustar00rootroot00000000000000%!demo %! ## Compute the median for different Weibull distributions %! rande ('state', 42); %! data1 = wblrnd (1, 0.5, 10000, 1); %! data2 = wblrnd (1, 2, 10000, 1); %! pd1 = fitdist (data1, "Weibull"); %! pd2 = fitdist (data2, "Weibull"); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution into %! ## two equal probability halves, robust to skewness in positive lifetime data. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.negloglik000066400000000000000000000007251524624707500263000ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted Weibull distribution %! rng (42); %! rande ('state', 42); %! data = wblrnd (1, 1, 100, 1); %! pd_fitted = fitdist (data, "Weibull"); %! params = [pd_fitted.lambda, pd_fitted.k]; %! nlogL_wbllike = wbllike (params, data) %! %! ## This is useful for assessing the fit of a Weibull distribution to data, %! ## with lower values indicating a better fit, often used in model selection %! ## for reliability data. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.paramci000066400000000000000000000007221524624707500257360ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted Weibull distribution %! rng (42); %! rande ('state', 42); %! data = wblrnd (1, 1, 1000, 1); %! pd_fitted = fitdist (data, "Weibull"); %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters %! ## (lambda and k), providing a range of plausible values given the data, %! ## essential in uncertainty quantification for failure models. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.pdf000066400000000000000000000015121524624707500250710ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the Weibull distribution %! rande ('state', 42); %! x = 0:0.01:5; %! data1 = wblrnd (1, 0.5, 10000, 1); %! data2 = wblrnd (1, 1, 10000, 1); %! data3 = wblrnd (1, 2, 10000, 1); %! pd1 = fitdist (data1, "Weibull"); %! pd2 = fitdist (data2, "Weibull"); %! pd3 = fitdist (data3, "Weibull"); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"lambda = 1, k = 0.5", "lambda = 1, k = 1", "lambda = 1, k = 2"}, ... %! "location", "northeast") %! title ("Weibull PDF") %! xlabel ("values in x (x >= 0)") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for Weibull distributions, %! ## showing the likelihood for positive values, common in wind speed or failure rate modeling. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.plot000066400000000000000000000034201524624707500252760ustar00rootroot00000000000000%!demo %! ## Create a Weibull distribution with fixed parameters lambda = 1 and %! ## k = 1 and plot its PDF. %! %! rande ('state', 42); %! data = wblrnd (1, 1, 10000, 1); %! pd = fitdist (data, "Weibull"); %! plot (pd) %! title ("Fixed Weibull distribution with lambda = 1 and k = 1") %!demo %! ## Generate a data set of 100 random samples from a Weibull distribution %! ## with parameters lambda = 1 and k = 1. Fit a Weibull distribution to %! ## this data and plot its CDF superimposed over an empirical CDF. %! %! rng (42); %! rande ('state', 42); %! data = wblrnd (1, 1, 100, 1); %! pd_fitted = fitdist (data, "Weibull"); %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted Weibull distribution with lambda = %0.2f and k = %0.2f"; %! title (sprintf (txt, pd_fitted.lambda, pd_fitted.k)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit in time-to-failure scenarios. %!demo %! ## Generate a data set of 200 random samples from a Weibull distribution %! ## with parameters lambda = 1 and k = 1. Display a probability plot for %! ## the Weibull distribution fit to the data. %! %! rng (42); %! rande ('state', 42); %! data = wblrnd (1, 1, 200, 1); %! pd_fitted = fitdist (data, "Weibull"); %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted Weibull distribution", ... %! " with lambda = %0.2f and k = %0.2f"); %! title (sprintf (txt, pd_fitted.lambda, pd_fitted.k)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for validating the Weibull assumption in reliability studies. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.proflik000066400000000000000000000007161524624707500257730ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the shape parameter of a fitted %! ## Weibull distribution %! rng (42); %! rande ('state', 42); %! data = wblrnd (1, 1, 1000, 1); %! pd_fitted = fitdist (data, "Weibull"); %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the shape parameter (k), %! ## helping to understand the uncertainty in parameter estimates for failure rate models. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.random000066400000000000000000000006641524624707500256070ustar00rootroot00000000000000%!demo %! ## Generate random samples from a Weibull distribution %! rng (42); %! rande ('state', 42); %! samples = wblrnd (1, 1, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from Weibull(lambda=1, k=1)") %! xlabel ("values in x (x >= 0)") %! ylabel ("Frequency") %! %! ## This generates random samples from a Weibull distribution, useful for %! ## simulating lifetime data or positive skewed distributions. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.std000066400000000000000000000005121524624707500251110ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a Weibull distribution %! rande ('state', 42); %! data = wblrnd (1, 1, 10000, 1); %! pd = fitdist (data, "Weibull"); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in lifetime or positive data modeled by Weibull. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.truncate000066400000000000000000000020501524624707500261430ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a Weibull distribution, with parameters lambda = 1 %! ## and k = 1, truncated at [0.5, 2] intervals. Generate 10000 random %! ## samples from this truncated distribution and superimpose a histogram scaled accordingly %! %! rng (42); %! rande ('state', 42); %! data_all = wblrnd (1, 1, 20000, 1); %! data = data_all(data_all >= 0.5 & data_all <= 2); %! data = data(1:9000); %! %! pd = fitdist (data, "Weibull"); %! t = truncate (pd, 0.5, 2); %! %! [counts, centers] = hist (data, 50); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on; %! %! ## Plot histogram and truncated PDF %! x = linspace (0.5, 5, 500); %! y = pdf (t, x); %! plot (x, y, "r", "linewidth", 2); %! title ("Weibull distribution (lambda = 1, k = 1) truncated at [0.5, 2]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a Weibull distribution to a specific range %! ## and visualizing the resulting distribution with random samples, useful for %! ## bounded lifetime analysis. statistics-release-1.9.2/inst/demos/prob.WeibullDistribution.var000066400000000000000000000004721524624707500251140ustar00rootroot00000000000000%!demo %! ## Compute the variance for a Weibull distribution %! rande ('state', 42); %! data = wblrnd (1, 1, 10000, 1); %! pd = fitdist (data, "Weibull"); %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## positive values in lifetime or reliability data. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.cdf000066400000000000000000000017021524624707500263560ustar00rootroot00000000000000%!demo %! ## Plot various CDFs from the t Location-Scale distribution %! rng (42); %! randg ('state', 42); %! x = -5:0.01:5; %! data1 = tlsrnd (0, 0.5, 5, 10000, 1); %! data2 = tlsrnd (0, 1, 5, 10000, 1); %! data3 = tlsrnd (0, 2, 5, 10000, 1); %! pd1 = fitdist (data1, "tLocationScale"); %! pd2 = fitdist (data2, "tLocationScale"); %! pd3 = fitdist (data3, "tLocationScale"); %! p1 = cdf (pd1, x); %! p2 = cdf (pd2, x); %! p3 = cdf (pd3, x); %! plot (x, p1, "-b", x, p2, "-g", x, p3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5, nu = 5", "mu = 0, sigma = 1, nu = 5", ... %! "mu = 0, sigma = 2, nu = 5"}, "location", "southeast") %! title ("t Location-Scale CDF") %! xlabel ("Values in x") %! ylabel ("Cumulative probability") %! %! ## Use this to compute and visualize the cumulative distribution function %! ## for different t Location-Scale distributions, showing how probability %! ## accumulates, useful in risk analysis or hypothesis testing. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.icdf000066400000000000000000000016201524624707500265260ustar00rootroot00000000000000%!demo %! ## Plot various iCDFs from the t Location-Scale distribution %! rng (42); %! randg ('state', 42); %! p = 0.001:0.001:0.999; %! data1 = tlsrnd (0, 0.5, 5, 10000, 1); %! data2 = tlsrnd (0, 1, 5, 10000, 1); %! data3 = tlsrnd (0, 2, 5, 10000, 1); %! pd1 = fitdist (data1, "tLocationScale"); %! pd2 = fitdist (data2, "tLocationScale"); %! pd3 = fitdist (data3, "tLocationScale"); %! x1 = icdf (pd1, p); %! x2 = icdf (pd2, p); %! x3 = icdf (pd3, p); %! plot (p, x1, "-b", p, x2, "-g", p, x3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5, nu = 5", "mu = 0, sigma = 1, nu = 5", ... %! "mu = 0, sigma = 2, nu = 5"}, "location", "northwest") %! title ("t Location-Scale iCDF") %! xlabel ("Probability") %! ylabel ("Values in x") %! %! ## This demonstrates the inverse CDF (quantiles) for t Location-Scale %! ## distributions, useful for finding critical values or thresholds in statistical testing. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.iqr000066400000000000000000000006101524624707500264120ustar00rootroot00000000000000%!demo %! ## Compute the interquartile range for a t Location-Scale distribution %! rng (42); %! randg ('state', 42); %! data = tlsrnd (0, 1, 5, 10000, 1); %! pd = fitdist (data, "tLocationScale"); %! iqr_value = iqr (pd) %! %! ## Use this to calculate the interquartile range, which measures the spread %! ## of the middle 50% of the distribution, helpful for assessing central variability. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.mean000066400000000000000000000007651524624707500265520ustar00rootroot00000000000000%!demo %! ## Compute the mean for different t Location-Scale distributions %! rng (42); %! randg ('state', 42); %! data1 = tlsrnd (0, 0.5, 5, 10000, 1); %! data2 = tlsrnd (0, 1, 5, 10000, 1); %! pd1 = fitdist (data1, "tLocationScale"); %! pd2 = fitdist (data2, "tLocationScale"); %! mean1 = mean (pd1) %! mean2 = mean (pd2) %! %! ## This shows how to compute the expected value for t Location-Scale %! ## distributions, representing the average value, useful in financial modeling or quality control. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.median000066400000000000000000000007361524624707500270650ustar00rootroot00000000000000%!demo %! ## Compute the median for different t Location-Scale distributions %! rng (42); %! randg ('state', 42); %! data1 = tlsrnd (0, 0.5, 5, 10000, 1); %! data2 = tlsrnd (0, 1, 5, 10000, 1); %! pd1 = fitdist (data1, "tLocationScale"); %! pd2 = fitdist (data2, "tLocationScale"); %! median1 = median (pd1) %! median2 = median (pd2) %! %! ## Use this to find the median value, which splits the distribution into %! ## two equal probability halves, robust to heavy-tailed data. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.mu000066400000000000000000000016261524624707500262500ustar00rootroot00000000000000%!demo %! ## Create a t Location-Scale distribution by fitting to data %! rng (42); %! randg ('state', 42); %! data = tlsrnd (0, 1, 5, 10000, 1); % Generate data with mu=0, sigma=1, nu=5 %! pd = fitdist (data, "tLocationScale"); %! %! ## Query parameter 'mu' (location parameter) %! pd.mu %! %! ## Set parameter 'mu' %! pd.mu = 1 %! %! ## Use this to initialize or modify the location parameter of a t Location-Scale %! ## distribution. The location parameter (mu) is a real scalar that shifts the %! ## distribution, useful for modeling data centered around a specific value. %!demo %! ## Create a t Location-Scale distribution object by calling its constructor %! pd = tLocationScaleDistribution (2, 1, 5); %! %! ## Query parameter 'mu' %! pd.mu %! %! ## This demonstrates direct construction with a specific location parameter, %! ## ideal for modeling data with a known center, such as test scores or residuals. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.negloglik000066400000000000000000000007451524624707500276030ustar00rootroot00000000000000%!demo %! ## Compute the negative loglikelihood for a fitted t Location-Scale distribution %! rng (42); %! randg ('state', 42); %! data = tlsrnd (0, 1, 5, 100, 1); %! pd_fitted = fitdist (data, "tLocationScale"); %! params = [pd_fitted.mu, pd_fitted.sigma, pd_fitted.nu]; %! nlogL_tlslike = tlslike (params, data) %! %! ## This is useful for assessing the fit of a t Location-Scale distribution to %! ## data, with lower values indicating a better fit, often used in model comparison. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.nu000066400000000000000000000016421524624707500262470ustar00rootroot00000000000000%!demo %! ## Create a t Location-Scale distribution with fitted parameters %! rng (42); %! randg ('state', 42); %! data = tlsrnd (0, 1, 5, 10000, 1); % Generate data with mu=0, sigma=1, nu=5 %! pd = fitdist (data, "tLocationScale"); %! %! ## Query parameter 'nu' (degrees of freedom) %! pd.nu %! %! ## Set parameter 'nu' %! pd.nu = 10 %! %! ## Use this to initialize or modify the degrees of freedom, which controls the %! ## tail heaviness of the t Location-Scale distribution. Nu must be a positive %! ## real scalar, useful for modeling heavy-tailed data like stock returns. %!demo %! ## Create a t Location-Scale distribution object by calling its constructor %! pd = tLocationScaleDistribution (0, 1, 3); %! %! ## Query parameter 'nu' %! pd.nu %! %! ## This demonstrates setting the degrees of freedom directly via the constructor, %! ## ideal for modeling data with specific tail behavior, such as outlier-prone datasets. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.paramci000066400000000000000000000006521524624707500272410ustar00rootroot00000000000000%!demo %! ## Compute confidence intervals for parameters of a fitted t Location-Scale %! ## distribution %! rng (42); %! randg ('state', 42); %! data = tlsrnd (0, 1, 5, 1000, 1); %! pd_fitted = fitdist (data, "tLocationScale"); %! ci = paramci (pd_fitted, "Alpha", 0.05) %! %! ## Use this to obtain confidence intervals for the estimated parameters (mu, %! ## sigma, nu), providing a range of plausible values given the data. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.pdf000066400000000000000000000016051524624707500263750ustar00rootroot00000000000000%!demo %! ## Plot various PDFs from the t Location-Scale distribution %! rng (42); %! randg ('state', 42); %! x = -5:0.01:5; %! data1 = tlsrnd (0, 0.5, 5, 10000, 1); %! data2 = tlsrnd (0, 1, 5, 10000, 1); %! data3 = tlsrnd (0, 2, 5, 10000, 1); %! pd1 = fitdist (data1, "tLocationScale"); %! pd2 = fitdist (data2, "tLocationScale"); %! pd3 = fitdist (data3, "tLocationScale"); %! y1 = pdf (pd1, x); %! y2 = pdf (pd2, x); %! y3 = pdf (pd3, x); %! plot (x, y1, "-b", x, y2, "-g", x, y3, "-r") %! grid on %! legend ({"mu = 0, sigma = 0.5, nu = 5", "mu = 0, sigma = 1, nu = 5", ... %! "mu = 0, sigma = 2, nu = 5"}, "location", "northeast") %! title ("t Location-Scale PDF") %! xlabel ("Values in x") %! ylabel ("Probability density") %! %! ## This visualizes the probability density function for t Location-Scale %! ## distributions, showing the likelihood across values, useful for data analysis. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.plot000066400000000000000000000032431524624707500266020ustar00rootroot00000000000000%!demo %! ## Create a t Location-Scale distribution with fixed parameters and plot its PDF %! pd = tLocationScaleDistribution (0, 1, 5); %! plot (pd) %! title ("t Location-Scale distribution with mu = 0, sigma = 1, nu = 5") %! %! ## Use this to visualize the PDF of a t Location-Scale distribution with %! ## fixed parameters, helpful for theoretical exploration. %!demo %! ## Generate a data set and plot the CDF of a fitted t Location-Scale distribution %! rng (42); %! randg ('state', 42); %! data = tlsrnd (0, 1, 5, 100, 1); %! pd_fitted = fitdist (data, "tLocationScale"); %! plot (pd_fitted, "PlotType", "cdf") %! txt = "Fitted t Location-Scale distribution with mu = %0.2f, sigma = %0.2f, nu = %0.2f"; %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma, pd_fitted.nu)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## Use this to visualize the fitted CDF compared to the empirical CDF of the %! ## data, useful for assessing model fit. %!demo %! ## Generate a data set and display a probability plot for a fitted t Location-Scale distribution %! rng (42); %! randg ('state', 42); %! data = tlsrnd (0, 1, 5, 200, 1); %! pd_fitted = fitdist (data, "tLocationScale"); %! plot (pd_fitted, "PlotType", "probability") %! txt = strcat ("Probability plot of fitted t Location-Scale", ... %! " distribution with mu = %0.2f, sigma = %0.2f, nu = %0.2f"); %! title (sprintf (txt, pd_fitted.mu, pd_fitted.sigma, pd_fitted.nu)) %! legend ({"empirical CDF", "fitted CDF"}, "location", "southeast") %! %! ## This creates a probability plot to compare the fitted distribution to the %! ## data, useful for checking if the t Location-Scale model is appropriate. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.proflik000066400000000000000000000006351524624707500272740ustar00rootroot00000000000000%!demo %! ## Compute and plot the profile likelihood for the scale parameter %! rng (42); %! randg ('state', 42); %! data = tlsrnd (0, 1, 5, 1000, 1); %! pd_fitted = fitdist (data, "tLocationScale"); %! [nlogL, param] = proflik (pd_fitted, 2, "Display", "on"); %! %! ## Use this to analyze the profile likelihood of the scale parameter (sigma), %! ## helping to understand the uncertainty in parameter estimates. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.random000066400000000000000000000007361524624707500271100ustar00rootroot00000000000000%!demo %! ## Generate random samples from a t Location-Scale distribution %! rng (42); %! randg ('state', 42); %! samples = tlsrnd (0, 1, 5, 500, 1); %! hist (samples, 50) %! title ("Histogram of 500 random samples from t Location-Scale(mu=0, sigma=1, nu=5)") %! xlabel ("Values in x") %! ylabel ("Frequency") %! %! ## This generates random samples from a t Location-Scale distribution, useful %! ## for simulating data with heavy tails, such as financial or experimental data. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.sigma000066400000000000000000000016401524624707500267230ustar00rootroot00000000000000%!demo %! ## Create a t Location-Scale distribution with fitted parameters %! rng (42); %! randg ('state', 42); %! data = tlsrnd (0, 1, 5, 10000, 1); % Generate data with mu=0, sigma=1, nu=5 %! pd = fitdist (data, "tLocationScale"); %! %! ## Query parameter 'sigma' (scale parameter) %! pd.sigma %! %! ## Set parameter 'sigma' %! pd.sigma = 2 %! %! ## Use this to initialize or modify the scale parameter, which controls the %! ## spread of the t Location-Scale distribution. Sigma must be a positive real %! ## scalar, useful for modeling variability in data like financial returns. %!demo %! ## Create a t Location-Scale distribution object by calling its constructor %! pd = tLocationScaleDistribution (0, 1.5, 5); %! %! ## Query parameter 'sigma' %! pd.sigma %! %! ## This shows how to set the scale parameter directly via the constructor, %! ## useful for modeling data with specific variability, such as process errors. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.std000066400000000000000000000005701524624707500264160ustar00rootroot00000000000000%!demo %! ## Compute the standard deviation for a t Location-Scale distribution %! rng (42); %! randg ('state', 42); %! data = tlsrnd (0, 1, 5, 10000, 1); %! pd = fitdist (data, "tLocationScale"); %! std_value = std (pd) %! %! ## Use this to calculate the standard deviation, which measures the variability %! ## in the distribution, useful for understanding data dispersion. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.truncate000066400000000000000000000015541524624707500274540ustar00rootroot00000000000000%!demo %! ## Plot the PDF of a truncated t Location-Scale distribution %! rng (42); %! randg ('state', 42); %! data_all = tlsrnd (0, 1, 5, 20000, 1); %! data = data_all(data_all >= -1 & data_all <= 1); %! data = data(1:10000); %! %! pd = fitdist (data, "tLocationScale"); %! t = truncate (pd, -1, 1); %! %! [counts, centers] = hist (data, 50); %! bin_width = centers(2) - centers(1); %! bar (centers, counts / (sum (counts) * bin_width), 1); %! hold on; %! %! ## Plot histogram and truncated PDF %! x = linspace (0.5, 5, 500); %! y = pdf (t, x); %! plot (x, y, "r", "linewidth", 2); %! title ("t Location-Scale distribution (mu = 0, sigma = 1, nu = 5) truncated at [-1, 1]") %! legend ("Truncated PDF", "Histogram") %! %! ## This demonstrates truncating a t Location-Scale distribution to a specific %! ## range and visualizing the resulting distribution with random samples. statistics-release-1.9.2/inst/demos/prob.tLocationScaleDistribution.var000066400000000000000000000005471524624707500264200ustar00rootroot00000000000000%!demo %! ## Compute the variance for a t Location-Scale distribution %! rng (42); %! randg ('state', 42); %! data = tlsrnd (0, 1, 5, 10000, 1); %! pd = fitdist (data, "tLocationScale"); %! var_value = var (pd) %! %! ## Use this to calculate the variance, which quantifies the spread of the %! ## distribution, useful for statistical analysis of variability. statistics-release-1.9.2/inst/doc-cache000066400000000000000000000254141524624707500200630ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 7 # name: # type: sq_string # elements: 1 # length: 7 cholcov # name: # type: sq_string # elements: 1 # length: 1299 statistics: T = cholcov ( sigma ) statistics: [ T , p = cholcov ( sigma ) statistics: […] = cholcov ( sigma , flag ) Cholesky-like decomposition for covariance matrix. T = cholcov ( sigma ) computes matrix T such that sigma = T ’ T . sigma must be square, symmetric, and positive semi-definite. If sigma is positive definite, then T is the square, upper triangular Cholesky factor. If sigma is not positive definite, T is computed with an eigenvalue decomposition of sigma , but in this case T is not necessarily triangular or square. Any eigenvectors whose corresponding eigenvalue is close to zero (within a tolerance) are omitted. If any remaining eigenvalues are negative, T is empty. The tolerance is calculated as 10 * eps (max (abs (diag (sigma)))) . [ T , p = cholcov ( sigma ) returns in p the number of negative eigenvalues of sigma . If p > 0, then T is empty, whereas if p = 0, sigma ) is positive semi-definite. If sigma is not square and symmetric, P is NaN and T is empty. [ T , p = cholcov ( sigma , 0) returns p = 0 if sigma is positive definite, in which case T is the Cholesky factor. If sigma is not positive definite, p is a positive integer and T is empty. […] = cholcov ( sigma , 1) is equivalent to […] = cholcov ( sigma ) . See also: chov # name: # type: sq_string # elements: 1 # length: 50 Cholesky-like decomposition for covariance matrix. # name: # type: sq_string # elements: 1 # length: 9 loadmodel # name: # type: sq_string # elements: 1 # length: 393 statistics: obj = loadmodel ( filename ) Load a Classification or Regression model from a file. obj = loadmodel ( filename ) loads a Classification or Regression object, obj , from a file defined in filename . See also: savemodel, ClassificationDiscriminant, ClassificationGAM, ClassificationKNN, ClassificationNeuralNetwork, ClassificationPartitionedModel, ClassificationSVM, RegressionGAM # name: # type: sq_string # elements: 1 # length: 54 Load a Classification or Regression model from a file. # name: # type: sq_string # elements: 1 # length: 5 logit # name: # type: sq_string # elements: 1 # length: 169 statistics: x = logit ( p ) Compute the logit for each value of p The logit is defined as $$ {\rm logit}(p) = \log\Big({p \over 1-p}\Big) $$ See also: probit, logicdf # name: # type: sq_string # elements: 1 # length: 37 Compute the logit for each value of p # name: # type: sq_string # elements: 1 # length: 6 makima # name: # type: sq_string # elements: 1 # length: 1632 statistics: yi = makima ( x , y , xq ) statistics: yi = makima ( y , xq ) statistics: yi = makima (…, 'extrap' ) Compute the 1-D Modified Akima piecewise cubic Hermite interpolant of sample data x and y . The Modified Akima (MAKIMA) algorithm generates a shape-preserving piecewise cubic interpolant. It differs from standard splines by avoiding excessive local undulations and overshoots, and it connects collinear points (flat regions) with straight lines. It is particularly well-suited for oscillatory data where pchip might aggressively flatten local extrema. The sample points x must be a vector of unique values. If x is not sorted, the function will automatically sort it and rearrange y accordingly. The sample values y can be a scalar, vector, or an N-dimensional array. If y is an N-dimensional array, the interpolation is performed along its last dimension, which must have the same length as x . Complex values for y are supported. If query points xq are provided, the function evaluates the interpolant and returns the interpolated values yi . By default, makima uses the boundary polynomials to extrapolate for points outside the range of x . The optional string argument 'extrap' is accepted for compatibility with other interpolation functions. If only x and y are provided, the function returns a piecewise polynomial structure pp that represents the interpolant. This structure can be evaluated later at specific query points using ppval . Evaluating the interpolant at query points outside the domain of x automatically extrapolates using the boundary polynomials. See also: interp1, pchip, spline # name: # type: sq_string # elements: 1 # length: 90 Compute the 1-D Modified Akima piecewise cubic Hermite interpolant of sample data x and y. # name: # type: sq_string # elements: 1 # length: 6 probit # name: # type: sq_string # elements: 1 # length: 162 statistics: x = probit ( p ) Probit transformation Return the probit (the quantile of the standard normal distribution) for each element of p . See also: logit # name: # type: sq_string # elements: 1 # length: 21 Probit transformation # name: # type: sq_string # elements: 1 # length: 7 statget # name: # type: sq_string # elements: 1 # length: 1036 statistics: value = statget ( options , name ) statistics: value = statget ( options , name , default ) Read one option out of a statistics options structure. value = statget ( options , name ) returns the value the option name carries in options , or [] when that option is unset. options is a structure as built by statset , although any structure is accepted. value = statget ( options , name , default ) returns default instead whenever the option is unset, which is the form a calling function uses to fall back on its own default. Note that default is returned when the option is empty , not only when it is absent, since an empty option is precisely how statset spells "unset" . name is matched case-insensitively, and may be abbreviated to any leading portion that singles out one option: statget ( options , "MaxI") reads "MaxIter" . An abbreviation matching more than one option raises, rather than choosing between them; an exact match is taken as exact even where it is also a prefix of a longer name. See also: statset # name: # type: sq_string # elements: 1 # length: 54 Read one option out of a statistics options structure. # name: # type: sq_string # elements: 1 # length: 7 statset # name: # type: sq_string # elements: 1 # length: 4311 statistics: options = statset () statistics: options = statset ( funcname ) statistics: options = statset ( name , value , …) statistics: options = statset ( oldopts , name , value , …) statistics: options = statset ( oldopts , newopts ) statistics: statset () Create or modify an options structure for iterative statistics algorithms. options = statset () returns a structure carrying every recognized option name, each set to an empty value. An empty option means "use the calling function's own default" , so an all-empty structure changes nothing wherever it is passed. options = statset ( funcname ) returns the options that funcname uses by default, with the remaining fields left empty. funcname must name a function of this package that documents an "Options" argument; see the list below. Unlike the name/value forms, this form takes no further arguments. options = statset ( name , value , …) returns an otherwise empty structure with the named options set. Option names are matched case-insensitively and must be given in full. options = statset ( oldopts , name , value , …) copies oldopts and applies the given name/value pairs to the copy. oldopts is left unchanged. options = statset ( oldopts , newopts ) merges two structures: every non-empty field of newopts overrides its counterpart in oldopts , while an empty field of newopts leaves the oldopts value in place. Fields that are not recognized option names are ignored in both structures. statset () called with no output argument displays the recognized option names together with their permitted values, marking each default in braces. The recognized options are: Option Description "Display" Level of reporting: "off" , "final" , or "iter" . "MaxFunEvals" Maximum number of objective function evaluations, a positive scalar. "MaxIter" Maximum number of iterations, a positive scalar. "TolBnd" Positive scalar tolerance on parameter bounds. "TolFun" Positive scalar tolerance on the objective function value. "TolTypeFun" Whether "TolFun" is absolute, "abs" , or relative, "rel" . "TolX" Positive scalar tolerance on the parameters. "TolTypeX" Whether "TolX" is absolute, "abs" , or relative, "rel" . "GradObj" Whether the objective function returns a gradient, "off" or "on" . "Jacobian" Whether the model function returns a Jacobian, "off" or "on" . "DerivStep" Relative step size for finite-difference derivatives, a positive scalar or vector. "FunValCheck" Whether to check the objective function for invalid values, "off" or "on" . "Robust" Whether to invoke a robust fit, "off" or "on" . Superseded by "RobustWgtFun" . "RobustWgtFun" Weight function for robust fitting: one of "andrews" , "bisquare" , "cauchy" , "fair" , "huber" , "logistic" , "talwar" , "welsch" , a function handle, or empty for a non-robust fit. "WgtFun" Weight function used with "Robust" . Superseded by "RobustWgtFun" . "Tune" Positive tuning constant for the robust weight function. Set automatically for a named weight function; required for a function handle. "UseParallel" Logical flag requesting parallel computation. "UseSubstreams" Logical flag requesting reproducible random substreams. "Streams" A random stream or a cell array of them. "OutputFcn" A function handle, or a cell array of them, called after each iteration. funcname may name any of the following functions, each of which documents an "Options" argument: copulafit , coxphfit , crossval , evfit , factoran , fitcox , fitglm , fitglme , fitlme , fitlmematrix , fitnlm , gamfit , gevfit , glmfit , gmdistribution , gpfit , kmeans , kmedoids , lasso , lassoglm , lognfit , mdscale , mlecov , mlecustom , mvncdf , mvtcdf , nbinfit , nlinfit , nnmf , normfit , pca , plsregress , ppca , rocmetrics , tsne , wblfit , GeneralizedLinearMixedModel , and LinearMixedModel . Any function accepting an "Options" argument also accepts a plain structure carrying only the fields it needs, so statset is a convenience rather than a requirement. MATLAB’s statset additionally accepts the names of functions this package does not provide. Those names are rejected here rather than answered, since returning options for an absent function would assert a capability that does not exist. See also: statget, nlinfit, fitnlm, nnmf, mdscale, ppca, tsne, kmedoids # name: # type: sq_string # elements: 1 # length: 74 Create or modify an options structure for iterative statistics algorithms. statistics-release-1.9.2/inst/loadmodel.m000066400000000000000000000307231524624707500204470ustar00rootroot00000000000000## Copyright (C) 2024 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{obj} = } loadmodel (@var{filename}) ## ## Load a Classification or Regression model from a file. ## ## @code{@var{obj} = loadmodel (@var{filename})} loads a Classification or ## Regression object, @var{obj}, from a file defined in @var{filename}. ## ## @seealso{savemodel, ClassificationDiscriminant, ClassificationGAM, ## ClassificationKNN, ClassificationNeuralNetwork, ## ClassificationPartitionedModel, ClassificationSVM, RegressionGAM} ## @end deftypefn function obj = loadmodel (filename) ## Check input parameters if (nargin < 1) error ("loadmodel: too few arguments."); endif ## Supported Classification and Regression objects supported = {'ClassificationKNN'}; ## Read file into a structure data = load (filename); ## Check that 'classdef_name' variable exists and that it ## contains a valid Classification or Regression object if (! isfield (data, 'classdef_name')) msg = ' ''%s'' does not contain a Classification or Regression object.'; error (strcat ("loadmodel:", msg), filename); endif ## Remove 'classdef_name' field from data structure classdef_name = data.classdef_name; data = rmfield (data, 'classdef_name'); ## Parse data structure to the static load method of specified classdef switch (classdef_name) case 'ClassificationDiscriminant' obj = ClassificationDiscriminant.load_model (filename, data); case 'CompactClassificationDiscriminant' obj = CompactClassificationDiscriminant.load_model (filename, data); case 'ClassificationGAM' obj = ClassificationGAM.load_model (filename, data); case 'CompactClassificationGAM' obj = CompactClassificationGAM.load_model (filename, data); case 'ClassificationKernel' obj = ClassificationKernel.load_model (filename, data); case 'ClassificationKNN' obj = ClassificationKNN.load_model (filename, data); case 'ClassificationLinear' obj = ClassificationLinear.load_model (filename, data); case 'ClassificationNaiveBayes' obj = ClassificationNaiveBayes.load_model (filename, data); case 'CompactClassificationNaiveBayes' obj = CompactClassificationNaiveBayes.load_model (filename, data); case 'ClassificationNeuralNetwork' obj = ClassificationNeuralNetwork.load_model (filename, data); case 'CompactClassificationNeuralNetwork' obj = CompactClassificationNeuralNetwork.load_model (filename, data); case 'ClassificationSVM' obj = ClassificationSVM.load_model (filename, data); case 'CompactClassificationSVM' obj = CompactClassificationSVM.load_model (filename, data); case 'RegressionGP' obj = RegressionGP.load_model (filename, data); case 'CompactRegressionGP' obj = CompactRegressionGP.load_model (filename, data); case 'RegressionGAM' obj = RegressionGAM.load_model (filename, data); case 'RegressionKernel' obj = RegressionKernel.load_model (filename, data); case 'RegressionLinear' obj = RegressionLinear.load_model (filename, data); case 'CompactRegressionGAM' obj = CompactRegressionGAM.load_model (filename, data); case 'RegressionNeuralNetwork' obj = RegressionNeuralNetwork.load_model (filename, data); case 'RegressionSVM' obj = RegressionSVM.load_model (filename, data); case 'CompactRegressionSVM' obj = CompactRegressionSVM.load_model (filename, data); case 'CompactRegressionNeuralNetwork' obj = CompactRegressionNeuralNetwork.load_model (filename, data); otherwise error ("loadmodel: '%s' is not supported.", classdef_name); endswitch endfunction ## A saved model must load back as the same class with the same state. Every ## loader but the neural-network one compared the saved fieldnames against ## fieldnames (mdl) for exact equality; a private property such as STname is ## saved but never reported by fieldnames, so the comparison could not match ## and the load always failed. ## Every property of a saved ClassificationKNN comes back as it was. %!test %! load fisheriris %! Yb = strcmp (species, 'setosa'); %! m = fitcknn (meas, species, 'ScoreTransform', 'logit'); %! fn = tempname (); %! unwind_protect %! savemodel (m, fn); %! m2 = loadmodel (fn); %! assert_equal (class (m2), class (m)); %! assert_equal (m2.W, m.W); %! assert_equal (m2.X, m.X); %! assert_equal (m2.Y, m.Y); %! assert_equal (m2.NumObservations, m.NumObservations); %! assert_equal (m2.RowsUsed, m.RowsUsed); %! assert_equal (m2.NumPredictors, m.NumPredictors); %! assert_equal (m2.PredictorNames, m.PredictorNames); %! assert_equal (m2.ResponseName, m.ResponseName); %! assert_equal (m2.ClassNames, m.ClassNames); %! assert_equal (m2.Sigma, m.Sigma); %! assert_equal (m2.Mu, m.Mu); %! assert_equal (m2.BreakTies, m.BreakTies); %! assert_equal (m2.NumNeighbors, m.NumNeighbors); %! assert_equal (m2.Distance, m.Distance); %! assert_equal (m2.DistanceWeight, m.DistanceWeight); %! assert_equal (m2.DistParameter, m.DistParameter); %! assert_equal (m2.NSMethod, m.NSMethod); %! assert_equal (m2.IncludeTies, m.IncludeTies); %! assert_equal (m2.BucketSize, m.BucketSize); %! assert_equal (m2.CacheSize, m.CacheSize); %! assert_equal (m2.Cost, m.Cost); %! assert_equal (m2.Prior, m.Prior); %! assert_equal (m2.ScoreTransform, m.ScoreTransform); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect ## Every property of a saved ClassificationDiscriminant comes back as it was. %!test %! load fisheriris %! Yb = strcmp (species, 'setosa'); %! m = fitcdiscr (meas, species, 'ScoreTransform', 'logit'); %! fn = tempname (); %! unwind_protect %! savemodel (m, fn); %! m2 = loadmodel (fn); %! assert_equal (class (m2), class (m)); %! assert_equal (m2.W, m.W); %! assert_equal (m2.X, m.X); %! assert_equal (m2.Y, m.Y); %! assert_equal (m2.NumObservations, m.NumObservations); %! assert_equal (m2.RowsUsed, m.RowsUsed); %! assert_equal (m2.NumPredictors, m.NumPredictors); %! assert_equal (m2.PredictorNames, m.PredictorNames); %! assert_equal (m2.ResponseName, m.ResponseName); %! assert_equal (m2.ClassNames, m.ClassNames); %! assert_equal (m2.Sigma, m.Sigma); %! assert_equal (m2.Mu, m.Mu); %! assert_equal (m2.Coeffs, m.Coeffs); %! assert_equal (m2.Delta, m.Delta); %! assert_equal (m2.DiscrimType, m.DiscrimType); %! assert_equal (m2.Gamma, m.Gamma); %! assert_equal (m2.MinGamma, m.MinGamma); %! assert_equal (m2.LogDetSigma, m.LogDetSigma); %! assert_equal (m2.XCentered, m.XCentered); %! assert_equal (m2.Cost, m.Cost); %! assert_equal (m2.Prior, m.Prior); %! assert_equal (m2.ScoreTransform, m.ScoreTransform); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect ## Every property of a saved ClassificationSVM comes back as it was. %!test %! load fisheriris %! Yb = strcmp (species, 'setosa'); %! m = fitcsvm (meas(1:100,:), Yb(1:100), 'ScoreTransform', 'logit'); %! fn = tempname (); %! unwind_protect %! savemodel (m, fn); %! m2 = loadmodel (fn); %! assert_equal (class (m2), class (m)); %! assert_equal (m2.X, m.X); %! assert_equal (m2.Y, m.Y); %! assert_equal (m2.NumObservations, m.NumObservations); %! assert_equal (m2.RowsUsed, m.RowsUsed); %! assert_equal (m2.NumPredictors, m.NumPredictors); %! assert_equal (m2.PredictorNames, m.PredictorNames); %! assert_equal (m2.ResponseName, m.ResponseName); %! assert_equal (m2.ClassNames, m.ClassNames); %! assert_equal (m2.Sigma, m.Sigma); %! assert_equal (m2.Mu, m.Mu); %! assert_equal (m2.ModelParameters, m.ModelParameters); %! assert_equal (m2.Model, m.Model); %! assert_equal (m2.Alpha, m.Alpha); %! assert_equal (m2.Beta, m.Beta); %! assert_equal (m2.Bias, m.Bias); %! assert_equal (m2.IsSupportVector, m.IsSupportVector); %! assert_equal (m2.SupportVectorLabels, m.SupportVectorLabels); %! assert_equal (m2.SupportVectors, m.SupportVectors); %! assert_equal (m2.Prior, m.Prior); %! assert_equal (m2.Cost, m.Cost); %! assert_equal (m2.W, m.W); %! assert_equal (m2.CategoricalPredictors, m.CategoricalPredictors); %! assert_equal (m2.ExpandedPredictorNames, m.ExpandedPredictorNames); %! assert_equal (m2.ScoreTransform, m.ScoreTransform); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect ## Every property of a saved ClassificationGAM comes back as it was. %!test %! load fisheriris %! Yb = strcmp (species, 'setosa'); %! m = fitcgam (meas(1:100,:), Yb(1:100)); %! fn = tempname (); %! unwind_protect %! savemodel (m, fn); %! m2 = loadmodel (fn); %! assert_equal (class (m2), class (m)); %! assert_equal (m2.X, m.X); %! assert_equal (m2.Y, m.Y); %! assert_equal (m2.NumObservations, m.NumObservations); %! assert_equal (m2.RowsUsed, m.RowsUsed); %! assert_equal (m2.NumPredictors, m.NumPredictors); %! assert_equal (m2.PredictorNames, m.PredictorNames); %! assert_equal (m2.ResponseName, m.ResponseName); %! assert_equal (m2.ClassNames, m.ClassNames); %! assert_equal (m2.Prior, m.Prior); %! assert_equal (m2.Formula, m.Formula); %! assert_equal (m2.Interactions, m.Interactions); %! assert_equal (m2.Knots, m.Knots); %! assert_equal (m2.Order, m.Order); %! assert_equal (m2.DoF, m.DoF); %! assert_equal (m2.LearningRate, m.LearningRate); %! assert_equal (m2.NumIterations, m.NumIterations); %! assert_equal (m2.Intercept, m.Intercept); %! assert_equal (m2.W, m.W); %! assert_equal (m2.CategoricalPredictors, m.CategoricalPredictors); %! assert_equal (m2.ExpandedPredictorNames, m.ExpandedPredictorNames); %! assert_equal (m2.BaseModel, m.BaseModel); %! assert_equal (m2.ModelwInt, m.ModelwInt); %! assert_equal (m2.IntMatrix, m.IntMatrix); %! assert_equal (m2.Cost, m.Cost); %! assert_equal (m2.ScoreTransform, m.ScoreTransform); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect ## A compact model must come back compact. CompactClassificationSVM was ## dispatched to ClassificationSVM.load_model, which builds the wrong class. %!test %! load fisheriris %! Yb = strcmp (species, 'setosa'); %! c = compact (fitcsvm (meas(1:100,:), Yb(1:100))); %! fn = tempname (); %! unwind_protect %! savemodel (c, fn); %! c2 = loadmodel (fn); %! assert_equal (class (c2), 'CompactClassificationSVM'); %! assert_equal (predict (c2, meas(1:10,:)), predict (c, meas(1:10,:))); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect ## The score transform's name is private state and must survive. It was not ## saved at all by ClassificationSVM, so it silently reverted to 'none'. %!test %! load fisheriris %! Yb = strcmp (species, 'setosa'); %! m = fitcsvm (meas(1:100,:), Yb(1:100), 'ScoreTransform', 'logit'); %! fn = tempname (); %! unwind_protect %! savemodel (m, fn); %! m2 = loadmodel (fn); %! assert_equal (strfind (evalc ('disp (m2)'), "'logit'") > 0, true); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect ## ClassificationGAM did not save LearningRate or NumIterations at all. %!test %! load fisheriris %! Yb = strcmp (species, 'setosa'); %! m = fitcgam (meas(1:100,:), Yb(1:100)); %! fn = tempname (); %! unwind_protect %! savemodel (m, fn); %! m2 = loadmodel (fn); %! assert_equal (m2.LearningRate, m.LearningRate); %! assert_equal (m2.NumIterations, m.NumIterations); %! unwind_protect_cleanup %! if (exist (fn, 'file')) %! delete (fn); %! endif %! end_unwind_protect ## Test input validation %!error loadmodel () %!error ... %! loadmodel ('fisheriris.mat') %!error ... %! loadmodel ('fail_loadmodel.mdl') %!error ... %! loadmodel ('fail_load_model.mdl') statistics-release-1.9.2/inst/logit.m000066400000000000000000000027411524624707500176240ustar00rootroot00000000000000## Copyright (C) 1995-2017 Kurt Hornik ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} logit (@var{p}) ## ## Compute the logit for each value of @var{p} ## ## The logit is defined as ## @tex ## $$ {\rm logit}(p) = \log\Big({p \over 1-p}\Big) $$ ## @end tex ## @ifnottex ## ## @example ## logit (@var{p}) = log (@var{p} / (1-@var{p})) ## @end example ## ## @end ifnottex ## @seealso{probit, logicdf} ## @end deftypefn function x = logit (p) if (nargin != 1) print_usage (); endif x = logiinv (p, 0, 1); endfunction %!test %! p = [0.01:0.01:0.99]; %! assert_equal (logit (p), log (p ./ (1-p)), 25*eps); %!assert_equal (logit ([-1, 0, 0.5, 1, 2]), [NaN, -Inf, 0, +Inf, NaN]) ## Test input validation %!error logit () %!error logit (1, 2) statistics-release-1.9.2/inst/makima.m000066400000000000000000000326711524624707500177520ustar00rootroot00000000000000## Copyright (C) 2025-2026 Avanish Salunke ## ## This file is part of the statistics package for GNU Octave. ## ## Octave is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## ## Octave is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Octave; see the file COPYING. If not, ## see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{yi} =} makima (@var{x}, @var{y}, @var{xq}) ## @deftypefnx {statistics} {@var{yi} =} makima (@var{y}, @var{xq}) ## @deftypefnx {statistics} {@var{yi} =} makima (@dots{}, @qcode{'extrap'}) ## ## Compute the 1-D Modified Akima piecewise cubic Hermite interpolant of ## sample data @var{x} and @var{y}. ## ## The Modified Akima (MAKIMA) algorithm generates a shape-preserving ## piecewise cubic interpolant. It differs from standard splines by avoiding ## excessive local undulations and overshoots, and it connects collinear points ## (flat regions) with straight lines. It is particularly well-suited for ## oscillatory data where @code{pchip} might aggressively flatten local ## extrema. ## ## The sample points @var{x} must be a vector of unique values. If @var{x} ## is not sorted, the function will automatically sort it and rearrange ## @var{y} accordingly. ## ## The sample values @var{y} can be a scalar, vector, or an N-dimensional ## array. If @var{y} is an N-dimensional array, the interpolation is ## performed along its last dimension, which must have the same length as ## @var{x}. Complex values for @var{y} are supported. ## ## If query points @var{xq} are provided, the function evaluates the ## interpolant and returns the interpolated values @var{yi}. By default, ## @code{makima} uses the boundary polynomials to extrapolate for points ## outside the range of @var{x}. The optional string argument @qcode{'extrap'} ## is accepted for compatibility with other interpolation functions. ## ## If only @var{x} and @var{y} are provided, the function returns a ## piecewise polynomial structure @var{pp} that represents the interpolant. ## This structure can be evaluated later at specific query points using ## @code{ppval}. ## ## Evaluating the interpolant at query points outside the domain of @var{x} ## automatically extrapolates using the boundary polynomials. ## ## @seealso{interp1, pchip, spline} ## @end deftypefn function yi = makima (x, y, xq, varargin) if (nargin < 2 || nargin > 4) error ("makima: invalid number of inputs"); endif if (nargin == 4 && ! strcmpi (varargin{1}, 'extrap')) error ("makima: unknown option '%s'", varargin{1}); endif return_pp = (nargin == 2); if (! return_pp) size_xq = size (xq); endif x = x(:); n = numel (x); is_y_vector = isvector (y); size_y = size (y); if (is_y_vector) if (numel (y) != n) error ("makima: the number of sample points X, %d, is incompatible with the number of values Y, %d.", n, numel (y)); endif y = y(:); dim_y = 1; nc = 1; else dim_y = ndims (y); if (size_y(dim_y) != n) error ("makima: the number of sample points X, %d, is incompatible with the number of values Y, %d.", n, size_y(dim_y)); endif ## permute the interpolation dimension to be the first perm_order = 1:numel (size_y); perm_order(dim_y) = 1; perm_order(1) = dim_y; y = permute (y, perm_order); nc = numel (y) / n; y = reshape (y, n, nc); endif if (iscomplex (y)) if (return_pp) ## Build complex pp struct pp_real = makima (x, real (y)); pp_imag = makima (x, imag (y)); yi = pp_real; yi.coefs = pp_real.coefs + 1i * pp_imag.coefs; else yi = makima (x, real (y), xq, varargin{:}) + 1i * makima (x, imag (y), xq, varargin{:}); endif return; endif if (! return_pp) xqv = xq(:); nq = numel (xqv); endif if (n < 2) error ("makima: the first two inputs must have at least two elements."); endif if (! issorted (x)) [x, sort_idx] = sort (x); y = y(sort_idx, :); endif math_done = false; if (n == 2) if (return_pp) ## Linear coefficients for 2-point pp struct coefs = zeros (nc, 4, class (y)); coefs(:, 3) = (y(2, :).' - y(1, :).') ./ (x(2) - x(1)); coefs(:, 4) = y(1, :).'; if (is_y_vector) dim_out = 1; else dim_out = size_y; dim_out(dim_y) = []; endif yi = mkpp (x.', coefs, dim_out); return; else yi = interp1 (x, y, xqv, 'linear', 'extrap'); yi = reshape (yi, [nq, nc]); math_done = true; endif endif if (! math_done) dx = diff (x); if (any (dx <= 0)) error ("makima: the sample points x must be unique."); endif dy = diff (y); m = dy ./ dx; m_0 = 2 * m(1, :) - m(2, :); m_m1 = 2 * m_0 - m(1, :); m_n = 2 * m(end, :) - m(end-1, :); m_n1 = 2 * m_n - m(end, :); m_ext = [m_m1; m_0; m; m_n; m_n1]; d = zeros (n, nc, class (y)); k_idx = (1 : n)'; s_im2 = m_ext(k_idx , :); s_im1 = m_ext(k_idx + 1, :); s_i = m_ext(k_idx + 2, :); s_ip1 = m_ext(k_idx + 3, :); w1 = abs (s_ip1 - s_i) + abs (s_ip1 + s_i) / 2; w2 = abs (s_im1 - s_im2) + abs (s_im1 + s_im2) / 2; W = w1 + w2; numer = (w1 .* s_im1 + w2 .* s_i); denom = max (W, eps); d = numer ./ denom; zero_mask = (W == 0); if (any (zero_mask(:))) fallback = (s_im1 + s_i) / 2; d(zero_mask) = fallback(zero_mask); endif if (return_pp) hseg = dx; delta = m; d0 = d(1:end-1, :); d1 = d(2:end, :); y0 = y(1:end-1, :); c3 = (d0 + d1 - 2*delta) ./ (hseg .* hseg); c2 = (3*delta - 2*d0 - d1) ./ hseg; c1 = d0; c0 = y0; c3_t = c3.'; c2_t = c2.'; c1_t = c1.'; c0_t = c0.'; coefs = [c3_t(:), c2_t(:), c1_t(:), c0_t(:)]; if (is_y_vector) dim_out = 1; else dim_out = size_y; dim_out(dim_y) = []; if (isempty (dim_out)) dim_out = 1; endif endif yi = mkpp (x.', coefs, dim_out); return; endif yi = NaN (nq, nc, class (y)); if (nq > 0) idx = lookup (x, xqv); idx(idx >= n) = n - 1; idx(idx == 0) = 1; x_left = x(idx); hseg = dx(idx); s = xqv - x_left; for c = 1:nc y0 = y(idx, c); y1 = y(idx + 1, c); d0 = d(idx, c); d1 = d(idx + 1, c); delta = (y1 - y0) ./ hseg; c2 = (3*delta - 2*d0 - d1) ./ hseg; c3 = (d0 + d1 - 2*delta) ./ (hseg.^2); yi(:, c) = y0 + s .* (d0 + s .* (c2 + s .* c3)); endfor endif endif if (! return_pp) if (is_y_vector) yi = reshape (yi, size_xq); else out_shape = size_y; out_shape(dim_y) = nq; yi = reshape (yi, out_shape(perm_order)); yi = ipermute (yi, perm_order); ## only append size_xq if multi-dimensional array is given if (! isvector (xq)) final_shape = size_y; final_shape(dim_y) = []; final_shape = [final_shape, size_xq]; yi = reshape (yi, final_shape); endif endif endif endfunction %!test %! ## Basic linear-like data %! x = [1; 2; 3; 4]; %! y = [2; 4; 6; 8]; %! xi = [1.5; 2.5; 3.5]; %! yi = makima (x, y, xi); %! assert_equal (yi, [3; 5; 7], 1e-12); %!test %! ## Nonlinear dataset (finite check) %! x = [0; 1; 2; 3; 4]; %! y = [0; 1; 0; 1; 0]; %! xi = linspace (0,4,20)'; %! yi = makima (x, y, xi); %! assert_equal (all (isfinite (yi)), true); %!test %! ## pp structure output %! x = [1; 2; 3; 4]; %! y = [2; 4; 6; 8]; %! pp = makima (x, y); %! assert_equal (isstruct (pp), true); %! assert_equal (strcmp (pp.form, 'pp'), true); %! assert_equal (pp.pieces, 3); %! assert_equal (pp.order, 4); %!test %! ## Matrix y input. %! x = [1; 3; 5]; %! y = [1 3 2; 2 4 6]; %! xi = 2; %! yi = makima (x, y, xi); %! assert_equal (size (yi), [2, 1]); %! assert_equal (all (isfinite (yi)), true); %! assert_equal (yi(1), 2.304086538461538, 1e-12); %! assert_equal (yi(2), 3.000000000000000, 1e-12); %!test %! ## Extrapolation through default method. %! x = [1; 2; 3]; %! y = [5; 10; 15]; %! xi = [0; 4]; %! yi = makima (x, y, xi); %! assert_equal (all (isfinite (yi)), true); %! assert_equal (yi, [0; 20], 1e-12); %!test %! ## Complex interpolation. %! x = [1; 2; 4]; %! y = [1+2i; 2+3i; 4+8i]; %! xi = 3; %! yi = makima (x, y, xi); %! assert_equal (yi, 3 + 5.09767206477733i, 1e-12); %! assert_equal (iscomplex (yi), true); %!test %! ## Two-point interpolation. %! x = [1; 5]; %! y = [10; 30]; %! xi = 3; %! yi = makima (x, y, xi); %! assert_equal (yi, 20, 1e-12); %!test %! ## Single Precision Input. %! x = single ([1; 2; 3]); %! y = single ([10; 20; 30]); %! xi = single (1.5); %! yi = makima (x, y, xi); %! assert_equal (isa (yi, 'single'), true); %! assert_equal (yi, single (15), 1e-6); %!test %! ## Row vector inputs. %! x = [1 2 3]; %! y = [4 5 6]; %! xi = [1.5 2.5]; %! yi = makima (x, y, xi); %! assert_equal (yi, [4.5 5.5], 1e-12); %!test %! ## Step function. %! x = [1 2 3 4 5 6]; %! y = [0 0 1 1 0 0]; %! xi = [2.5 3.5 4.5]; %! yi = makima (x, y, xi); %! expected_11 = [0.5000, 1.1250, 0.5000]; %! assert_equal (yi, expected_11, 1e-12); %!test %! ## Runge function (Oscillation Check) %! x = linspace (-1, 1, 7)'; %! y = 1 ./ (1 + 25 * x.^2); %! xi = [-0.5; 0.1; 0.5]; %! yi = makima (x, y, xi); %! expected_12 = [0.148690385982729; 0.857734549516009; 0.148690385982729]; %! assert_equal (yi, expected_12, 1e-12); %!test %! ## Constant Slopes / Zero Weights %! x = [1; 2; 3; 4; 5]; %! y = [1; 1; 1; 1; 1]; %! xi = 3.5; %! yi = makima (x, y, xi); %! expected_13 = [1]; %! assert_equal (yi, expected_13, 1e-12); %!test %! ## Empty xq input %! x = [1; 2; 3]; %! y = [4; 5; 6]; %! xi = []; %! yi = makima (x, y, xi); %! assert_equal (isempty (yi), true); %! assert_equal (! (iscolumn (yi)), true); %!test %! ## Wide range of y-values %! x = [1e-10; 2e-10; 3e-10; 4e-10]; %! y = [1e10; 2e10; 3e10; 4e10]; %! xi = 2.5e-10; %! yi = makima (x, y, xi); %! assert_equal (yi, 2.5e10, 1e-12); %!test %! ## Single column matrix input. %! x = [1; 2; 3]; %! y = [10; 20; 30]; %! xi = [1.5 2.5]; % Row input %! yi = makima (x, y, xi); %! assert_equal (yi, [15 25], 1e-12); %! assert_equal (isrow (yi), true); %!test %! ## Evaluate pp structure with ppval %! x = [1; 2; 3; 4]; %! y = [2; 4; 6; 8]; %! xi = [1.5; 2.5; 3.5]; %! pp = makima (x, y); %! yi_ppval = ppval (pp, xi); %! yi_direct = makima (x, y, xi); %! assert_equal (yi_ppval, yi_direct, 1e-12); %!test %! ## xq is a 2x2 matrix %! x = [1; 2; 3; 4; 5]; %! y = [10; 20; 15; 5; 25]; %! xq = [1.5, 2.5; 3.5, 4.5]; %! yi = makima (x, y, xq); %! assert_equal (size (yi), [2, 2]); %! expected = [16.85897435897436, 18.22916666666667; 9.81182795698925, 10.84522332506203]; %! assert_equal (yi, expected, 1e-12); %!test %! ## xq is a 3D array %! x = [1; 2; 3; 4; 5]; %! y = [10; 20; 15; 5; 25]; %! xq = ones (2, 2, 2) * 2.5; %! yi = makima (x, y, xq); %! assert_equal (size (yi), [2, 2, 2]); %!test %! ## pp structure with matrix y input %! x = [1; 3; 5]; %! y = [1 3 2; 2 4 6]; %! pp = makima (x, y); %! assert_equal (isstruct (pp), true); %! assert_equal (pp.pieces, 2); %! assert_equal (pp.dim, 2); %! yi_ppval = ppval (pp, 2); %! yi_direct = makima (x, y, 2); %! assert_equal (yi_ppval, yi_direct, 1e-12); %!test %! ## y is a 3D array [2x3x4] and x is length 4 %! x = [1, 2, 3, 4]; %! xq = [1.5, 2.5, 3.5]; %! y3 = reshape (1:24, [2, 3, 4]); %! yi = makima (x, y3, xq); %! assert_equal (size (yi), [2, 3, 3]); %!test %! ## Unsorted 'x' inputs %! x_unsorted = [3; 1; 2; 4]; %! y_unsorted = [9; 1; 4; 16]; %! xq = [1.5; 2.5]; %! x_sorted = [1; 2; 3; 4]; %! y_sorted = [1; 4; 9; 16]; %! assert_equal (makima (x_unsorted, y_unsorted, xq), makima (x_sorted, y_sorted, xq), 1e-12); %!test %! ## Complex piecewise polynomial (pp) structure %! x = [1 2 3]; %! y = [1 4 9] + 1i * [2 8 18]; %! pp = makima (x, y); %! assert_equal (isstruct (pp), true); %! assert_equal (iscomplex (pp.coefs), true); %! assert_equal (ppval (pp, 1.5), makima (x, y, 1.5), 1e-12); %!test %! ## N-dimensional y (3D) with N-dimensional xq (2x2 matrix) %! x = [1 2 3 4]; %! y3 = reshape (1:24, [2 3 4]); %! xq = [1.5 2.5; 3.5 1.5]; %! yi = makima (x, y3, xq); %! assert_equal (size (yi), [2 3 2 2]); %!test %! ## 2-point pp struct %! x = [1; 5]; %! y = [10; 30]; %! pp = makima (x, y); %! assert_equal (pp.pieces, 1); %! assert_equal (pp.order, 4); %! assert_equal (ppval (pp, 3), 20, 1e-12); %!test %! ## Exact Collinearity %! x = [1 2 3 4]; %! y = [2 4 6 8]; %! xi = 2.5; %! yi = makima (x, y, xi); %! assert_equal (yi, 5, 1e-12); %!test %! ## Extrapolation check. %! x = [1; 2; 3]; %! y = [5; 10; 15]; %! xi = [0; 4]; %! yi = makima (x, y, xi, 'extrap'); %! assert_equal (all (isfinite (yi)), true); %! assert_equal (yi, [0; 20], 1e-12); %!error makima ([1 1 2], [3 4 5], 1.5) %!error makima (1) %!error makima (1, 2, 1.5) %!error makima ([1 2 3 4], [1 2 3 4 5], 2) %!error makima ([1 2 3], [1 2 3], 2, 'linear') %!error makima ([1 2 3], [1 2 3], 2, 'extrap', 'too_many') statistics-release-1.9.2/inst/probit.m000066400000000000000000000026301524624707500200020ustar00rootroot00000000000000## Copyright (C) 1995-2017 Kurt Hornik ## Copyright (C) 2023 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software: you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation, either version 3 of the ## License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{x} =} probit (@var{p}) ## ## Probit transformation ## ## Return the probit (the quantile of the standard normal distribution) for ## each element of @var{p}. ## ## @seealso{logit} ## @end deftypefn function x = probit (p) if (nargin != 1) print_usage (); endif x = -sqrt (2) * erfcinv (2 * p); endfunction ## Test output %!assert_equal (probit ([-1, 0, 0.5, 1, 2]), [NaN, -Inf, 0, Inf, NaN]) %!assert_equal (probit ([0.2, 0.99]), norminv ([0.2, 0.99])) ## Test input validation %!error probit () %!error probit (1, 2) statistics-release-1.9.2/inst/statget.m000066400000000000000000000137111524624707500201600ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{value} =} statget (@var{options}, @var{name}) ## @deftypefnx {statistics} {@var{value} =} statget (@var{options}, @var{name}, @var{default}) ## ## Read one option out of a statistics options structure. ## ## @code{@var{value} = statget (@var{options}, @var{name})} returns the value ## the option @var{name} carries in @var{options}, or @code{[]} when that ## option is unset. @var{options} is a structure as built by @code{statset}, ## although any structure is accepted. ## ## @code{@var{value} = statget (@var{options}, @var{name}, @var{default})} ## returns @var{default} instead whenever the option is unset, which is the ## form a calling function uses to fall back on its own default. Note that ## @var{default} is returned when the option is @emph{empty}, not only when it ## is absent, since an empty option is precisely how @code{statset} spells ## @qcode{"unset"}. ## ## @var{name} is matched case-insensitively, and may be abbreviated to any ## leading portion that singles out one option: @code{statget (@var{options}, ## "MaxI")} reads @qcode{"MaxIter"}. An abbreviation matching more than one ## option raises, rather than choosing between them; an exact match is taken ## as exact even where it is also a prefix of a longer name. ## ## @seealso{statset} ## @end deftypefn function value = statget (options, name, default) if (nargin < 2) print_usage (); endif if (nargin < 3) default = []; endif if (! (isstruct (options) && isscalar (options))) error (strcat ("statget: OPTIONS must be a scalar structure, as", ... " built by statset.")); endif if (! (ischar (name) || (isa (name, 'string') && isscalar (name)))) error ("statget: NAME must be a character vector or a string scalar."); endif name = char (name); fn = fieldnames (options); ## An exact match wins outright, so that a name which is also the prefix of ## a longer one is never treated as ambiguous. idx = find (strcmpi (name, fn)); if (isempty (idx)) idx = find (strncmpi (name, fn, numel (name))); if (numel (idx) > 1) error (strcat ("statget: '%s' matches more than one option name:", ... " %s."), name, strjoin (fn(idx)', ", ")); endif endif if (isempty (idx)) error ("statget: unrecognized option name '%s'.", name); endif value = options.(fn{idx}); if (isempty (value)) value = default; endif endfunction %!demo %! ## Read an option, falling back on a default when it is unset %! options = statset ('nlinfit'); %! maxiter = statget (options, 'MaxIter') %! tolbnd = statget (options, 'TolBnd', 1e-6) ## Reading a set option %!test %! assert_equal (statget (statset ('factoran'), 'TolX'), 1e-8); %!test %! assert_equal (statget (statset ('nlinfit'), 'MaxIter'), 200); %!test %! assert_equal (statget (statset ('nlinfit'), 'WgtFun'), 'bisquare'); ## An unset option reads as empty %!test %! assert_equal (statget (statset ('factoran'), 'TolBnd'), []); %!test %! assert_equal (statget (statset (), 'MaxIter'), []); ## The default is returned only when the option is unset %!test %! assert_equal (statget (statset ('factoran'), 'TolBnd', 42), 42); %!test %! assert_equal (statget (statset ('factoran'), 'TolX', 42), 1e-8); ## The default may be of any type %!test %! assert_equal (statget (statset (), 'Display', 'final'), 'final'); %!test %! assert_equal (statget (statset (), 'OutputFcn', {@sin}), {@sin}); ## The name is matched case-insensitively %!test %! assert_equal (statget (statset ('factoran'), 'tolx'), 1e-8); %!test %! assert_equal (statget (statset ('factoran'), 'MAXITER'), 100); ## The name may be abbreviated when the abbreviation is unique %!test %! assert_equal (statget (statset ('factoran'), 'MaxI'), 100); %!test %! assert_equal (statget (statset ('factoran'), 'Displ'), 'off'); %!test %! assert_equal (statget (statset ('nlinfit'), 'Deriv'), 6.0554544523933429e-06, 1e-20); ## An exact match is preferred over the longer names it prefixes %!test %! assert_equal (statget (statset ('nnmf'), 'TolX'), 1e-4); %!test %! assert_equal (statget (statset ('fitnlm'), 'Robust'), 'off'); ## Any structure is accepted, not only one built by statset %!test %! assert_equal (statget (struct ('MaxIter', 7), 'MaxIter'), 7); %!test %! assert_equal (statget (struct ('MaxIter', 7), 'MaxI'), 7); ## Error conditions %!error statget () %!error statget (statset ()) %!error ... %! statget (1, 'MaxIter') %!error ... %! statget (struct ('MaxIter', {1, 2}), 'MaxIter') %!error ... %! statget (statset (), 5) %!error ... %! statget (statset (), 'Tol') %!error ... %! statget (statset (), 'TolT') %!error ... %! statget (statset (), 'NoSuchOption') %!error ... %! statget (struct ('MaxIter', 7), 'TolX') statistics-release-1.9.2/inst/statset.m000066400000000000000000000664071524624707500202060ustar00rootroot00000000000000## Copyright (C) 2026 Andreas Bertsatos ## ## This file is part of the statistics package for GNU Octave. ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software ## Foundation; either version 3 of the License, or (at your option) any later ## version. ## ## This program is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ## details. ## ## You should have received a copy of the GNU General Public License along with ## this program; if not, see . ## -*- texinfo -*- ## @deftypefn {statistics} {@var{options} =} statset () ## @deftypefnx {statistics} {@var{options} =} statset (@var{funcname}) ## @deftypefnx {statistics} {@var{options} =} statset (@var{name}, @var{value}, @dots{}) ## @deftypefnx {statistics} {@var{options} =} statset (@var{oldopts}, @var{name}, @var{value}, @dots{}) ## @deftypefnx {statistics} {@var{options} =} statset (@var{oldopts}, @var{newopts}) ## @deftypefnx {statistics} {} statset () ## ## Create or modify an options structure for iterative statistics algorithms. ## ## @code{@var{options} = statset ()} returns a structure carrying every ## recognized option name, each set to an empty value. An empty option means ## @qcode{"use the calling function's own default"}, so an all-empty structure ## changes nothing wherever it is passed. ## ## @code{@var{options} = statset (@var{funcname})} returns the options that ## @var{funcname} uses by default, with the remaining fields left empty. ## @var{funcname} must name a function of this package that documents an ## @qcode{"Options"} argument; see the list below. Unlike the name/value ## forms, this form takes no further arguments. ## ## @code{@var{options} = statset (@var{name}, @var{value}, @dots{})} returns an ## otherwise empty structure with the named options set. Option names are ## matched case-insensitively and must be given in full. ## ## @code{@var{options} = statset (@var{oldopts}, @var{name}, @var{value}, ## @dots{})} copies @var{oldopts} and applies the given name/value pairs to the ## copy. @var{oldopts} is left unchanged. ## ## @code{@var{options} = statset (@var{oldopts}, @var{newopts})} merges two ## structures: every @emph{non-empty} field of @var{newopts} overrides its ## counterpart in @var{oldopts}, while an empty field of @var{newopts} leaves ## the @var{oldopts} value in place. Fields that are not recognized option ## names are ignored in both structures. ## ## @code{statset ()} called with no output argument displays the recognized ## option names together with their permitted values, marking each default ## in braces. ## ## The recognized options are: ## ## @multitable @columnfractions 0.25 0.75 ## @headitem Option @tab Description ## @item @qcode{"Display"} @tab Level of reporting: @qcode{"off"}, ## @qcode{"final"}, or @qcode{"iter"}. ## @item @qcode{"MaxFunEvals"} @tab Maximum number of objective function ## evaluations, a positive scalar. ## @item @qcode{"MaxIter"} @tab Maximum number of iterations, a positive scalar. ## @item @qcode{"TolBnd"} @tab Positive scalar tolerance on parameter bounds. ## @item @qcode{"TolFun"} @tab Positive scalar tolerance on the objective ## function value. ## @item @qcode{"TolTypeFun"} @tab Whether @qcode{"TolFun"} is absolute, ## @qcode{"abs"}, or relative, @qcode{"rel"}. ## @item @qcode{"TolX"} @tab Positive scalar tolerance on the parameters. ## @item @qcode{"TolTypeX"} @tab Whether @qcode{"TolX"} is absolute, ## @qcode{"abs"}, or relative, @qcode{"rel"}. ## @item @qcode{"GradObj"} @tab Whether the objective function returns a ## gradient, @qcode{"off"} or @qcode{"on"}. ## @item @qcode{"Jacobian"} @tab Whether the model function returns a Jacobian, ## @qcode{"off"} or @qcode{"on"}. ## @item @qcode{"DerivStep"} @tab Relative step size for finite-difference ## derivatives, a positive scalar or vector. ## @item @qcode{"FunValCheck"} @tab Whether to check the objective function for ## invalid values, @qcode{"off"} or @qcode{"on"}. ## @item @qcode{"Robust"} @tab Whether to invoke a robust fit, @qcode{"off"} or ## @qcode{"on"}. Superseded by @qcode{"RobustWgtFun"}. ## @item @qcode{"RobustWgtFun"} @tab Weight function for robust fitting: one of ## @qcode{"andrews"}, @qcode{"bisquare"}, @qcode{"cauchy"}, @qcode{"fair"}, ## @qcode{"huber"}, @qcode{"logistic"}, @qcode{"talwar"}, @qcode{"welsch"}, a ## function handle, or empty for a non-robust fit. ## @item @qcode{"WgtFun"} @tab Weight function used with @qcode{"Robust"}. ## Superseded by @qcode{"RobustWgtFun"}. ## @item @qcode{"Tune"} @tab Positive tuning constant for the robust weight ## function. Set automatically for a named weight function; required for a ## function handle. ## @item @qcode{"UseParallel"} @tab Logical flag requesting parallel ## computation. ## @item @qcode{"UseSubstreams"} @tab Logical flag requesting reproducible ## random substreams. ## @item @qcode{"Streams"} @tab A random stream or a cell array of them. ## @item @qcode{"OutputFcn"} @tab A function handle, or a cell array of them, ## called after each iteration. ## @end multitable ## ## @var{funcname} may name any of the following functions, each of which ## documents an @qcode{"Options"} argument: @code{copulafit}, @code{coxphfit}, ## @code{crossval}, @code{evfit}, @code{factoran}, @code{fitcox}, ## @code{fitglm}, @code{fitglme}, @code{fitlme}, ## @code{fitlmematrix}, @code{fitnlm}, @code{gamfit}, @code{gevfit}, ## @code{glmfit}, @code{gmdistribution}, @code{gpfit}, @code{kmeans}, ## @code{kmedoids}, @code{lasso}, @code{lassoglm}, @code{lognfit}, ## @code{mdscale}, @code{mlecov}, @code{mlecustom}, @code{mvncdf}, ## @code{mvtcdf}, @code{nbinfit}, @code{nlinfit}, @code{nnmf}, @code{normfit}, ## @code{pca}, @code{plsregress}, @code{ppca}, @code{rocmetrics}, @code{tsne}, ## @code{wblfit}, @code{GeneralizedLinearMixedModel}, and ## @code{LinearMixedModel}. ## ## Any function accepting an @qcode{"Options"} argument also accepts a plain ## structure carrying only the fields it needs, so @code{statset} is a ## convenience rather than a requirement. ## ## MATLAB's @code{statset} additionally accepts the names of functions this ## package does not provide. Those names are rejected here rather than ## answered, since returning options for an absent function would assert a ## capability that does not exist. ## ## @seealso{statget, nlinfit, fitnlm, nnmf, mdscale, ppca, tsne, kmedoids} ## @end deftypefn function options = statset (varargin) ## The recognized option names, in MATLAB's own field order. 'Robust' and ## 'WgtFun' are present in MATLAB's structure but absent from its published ## table; they are kept because nlinfit and fitnlm consume them. names = {'Display', 'MaxFunEvals', 'MaxIter', 'TolBnd', 'TolFun', ... 'TolTypeFun', 'TolX', 'TolTypeX', 'GradObj', 'Jacobian', ... 'DerivStep', 'FunValCheck', 'Robust', 'RobustWgtFun', 'WgtFun', ... 'Tune', 'UseParallel', 'UseSubstreams', 'Streams', 'OutputFcn'}; ## No output and no input: report the option names and their values. if (nargin == 0 && nargout == 0) display_options (); return; endif ## Start from an all-empty structure; 'Streams' is an empty cell. options = cell2struct (repmat ({[]}, numel (names), 1), names, 1); options.Streams = {}; if (nargin == 0) return; endif ## A single character or string argument names a function. if (nargin == 1 && (ischar (varargin{1}) || isstring_scalar (varargin{1}))) options = func_defaults (options, char (varargin{1})); return; endif ## An optional leading structure supplies the starting values, and an ## optional second structure overrides them where it is not empty. args = varargin; if (isstruct (args{1})) options = merge_struct (options, args{1}, names); args(1) = []; if (numel (args) > 0 && isstruct (args{1})) options = merge_struct (options, args{1}, names); args(1) = []; endif elseif (! ischar (args{1}) && ! isstring_scalar (args{1})) error (strcat ("statset: first argument must be a function name,", ... " an option name, or an options structure.")); endif if (mod (numel (args), 2) != 0) error ("statset: arguments must occur in NAME/VALUE pairs."); endif ## Apply the name/value pairs. for i = 1:2:numel (args) name = args{i}; if (! ischar (name) && ! isstring_scalar (name)) error ("statset: option name must be a character vector or a string."); endif idx = find (strcmpi (char (name), names)); if (isempty (idx)) error ("statset: unrecognized option name '%s'.", char (name)); endif field = names{idx}; options.(field) = check_value (field, args{i+1}); endfor ## A named weight function carries a default tuning constant, but only into ## an empty 'Tune'. A tuning constant already in the structure is never ## re-derived, not even by a call that changes the weight function itself: ## MATLAB keeps a Tune of 3 across statset (options, 'RobustWgtFun', 'huber'). if (isempty (options.Tune) && ! isempty (options.RobustWgtFun) ... && ischar (options.RobustWgtFun)) options.Tune = default_tune (options.RobustWgtFun); endif endfunction ## Copy the recognized, non-empty fields of S over those of OPTIONS. function options = merge_struct (options, s, names) if (! isscalar (s)) error ("statset: an options structure must be a scalar structure."); endif fn = fieldnames (s); for i = 1:numel (fn) idx = find (strcmpi (fn{i}, names)); if (isempty (idx)) continue; # unrecognized fields are ignored endif value = s.(fn{i}); if (! isempty (value)) options.(names{idx}) = value; endif endfor endfunction ## Validate VALUE for the option FIELD and return it in canonical form. function value = check_value (field, value) ## An empty value always means "unset", whatever the option. if (isempty (value) && ! iscell (value)) value = []; return; endif switch (field) case 'Display' value = check_string (field, value, {'off', 'final', 'iter'}); case {'GradObj', 'Jacobian', 'FunValCheck', 'Robust'} value = check_string (field, value, {'off', 'on'}); case {'TolTypeFun', 'TolTypeX'} value = check_string (field, value, {'abs', 'rel'}); case {'MaxFunEvals', 'MaxIter'} if (! (isnumeric (value) && isscalar (value) && isreal (value) && isfloat (value) && value > 0)) error (strcat ("statset: option '%s' must be a real positive", ... " scalar."), field); endif case {'TolBnd', 'TolFun', 'TolX', 'Tune'} if (! (isnumeric (value) && isscalar (value) && isreal (value) && isfloat (value) && value > 0)) error (strcat ("statset: option '%s' must be a real positive", ... " scalar."), field); endif case 'DerivStep' if (! (isnumeric (value) && isreal (value) && isfloat (value) && all (value(:) > 0))) error (strcat ("statset: option 'DerivStep' must be a real", ... " positive scalar or vector.")); endif case {'RobustWgtFun', 'WgtFun'} if (is_function_handle (value)) return; endif value = check_string (field, value, {'andrews', 'bisquare', 'cauchy', ... 'fair', 'huber', 'logistic', ... 'talwar', 'welsch'}); case {'UseParallel', 'UseSubstreams'} if (! (isscalar (value) && (islogical (value) || (isnumeric (value) && (value == 0 || value == 1))))) error (strcat ("statset: option '%s' must be a logical", ... " scalar."), field); endif value = logical (value); case 'OutputFcn' if (! (is_function_handle (value) || (iscell (value) && all (cellfun (@is_function_handle, value))))) error (strcat ("statset: option 'OutputFcn' must be a function", ... " handle or a cell array of function handles.")); endif case 'Streams' if (! (isa (value, 'RandStream') || iscell (value))) error (strcat ("statset: option 'Streams' must be a random stream", ... " or a cell array of random streams.")); endif endswitch endfunction ## Validate a character option against the list of values it accepts. function value = check_string (field, value, valid) if (! (ischar (value) || isstring_scalar (value))) error (strcat ("statset: option '%s' must be a character vector or", ... " a string scalar."), field); endif value = char (value); idx = find (strcmpi (value, valid)); if (isempty (idx)) quoted = strcat ("'", strjoin (valid, "', '"), "'"); error ("statset: option '%s' must be one of %s.", field, quoted); endif value = valid{idx}; endfunction ## The default tuning constant of each named robust weight function. This ## repeats the table in inst/Regression/private/robusttune.m, which robustfit ## and nlinfit share: a private resolves only for functions in its own ## directory, so statset, sitting in inst/, cannot reach that one. The two ## differ at the tail -- an unknown name is [] here and 1 there -- so keep both ## in step when a weight function is added. function t = default_tune (wgtfun) switch (lower (wgtfun)) case 'andrews'; t = 1.339; case 'bisquare'; t = 4.685; case 'cauchy'; t = 2.385; case 'fair'; t = 1.400; case 'huber'; t = 1.345; case 'logistic'; t = 1.205; case 'talwar'; t = 2.795; case 'welsch'; t = 2.985; otherwise; t = []; endswitch endfunction ## The options each supported function uses by default. function options = func_defaults (options, fname) ## The finite-difference step is eps^(1/3) throughout, and mlecov's is 2^-13. dstep = eps ^ (1/3); switch (lower (fname)) case {'crossval', 'lasso', 'lassoglm', 'plsregress', 'rocmetrics'} options.Display = 'off'; options.UseParallel = false; options.UseSubstreams = false; case {'kmeans', 'kmedoids'} options.Display = 'off'; options.MaxIter = 100; options.UseParallel = false; options.UseSubstreams = false; case 'nnmf' options.Display = 'off'; options.MaxIter = 100; options.TolFun = 1e-4; options.TolX = 1e-4; options.UseParallel = false; options.UseSubstreams = false; case {'fitglme', 'fitlme', 'fitlmematrix', ... 'generalizedlinearmixedmodel', 'linearmixedmodel'} options.Display = 'off'; options.MaxIter = 10000; options.TolFun = 1e-6; options.TolX = 1e-12; case {'fitglm', 'glmfit'} options.Display = 'off'; options.MaxIter = 100; options.TolX = 1e-6; case {'fitnlm', 'nlinfit'} options.Display = 'off'; options.MaxIter = 200; options.TolFun = 1e-8; options.TolX = 1e-8; options.DerivStep = dstep; options.FunValCheck = 'on'; options.Robust = 'off'; options.WgtFun = 'bisquare'; case {'gamfit', 'lognfit', 'normfit'} options.Display = 'off'; options.MaxFunEvals = 200; options.MaxIter = 100; options.TolBnd = 1e-6; options.TolFun = 1e-8; options.TolX = 1e-8; case {'gevfit', 'gpfit', 'nbinfit'} options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolBnd = 1e-6; options.TolFun = 1e-6; options.TolX = 1e-6; case {'evfit', 'wblfit'} options.Display = 'off'; options.TolX = 1e-6; case {'mvncdf', 'mvtcdf'} options.Display = 'off'; options.MaxFunEvals = 1e7; case {'pca', 'ppca'} options.Display = 'off'; options.MaxIter = 1000; options.TolFun = 1e-6; options.TolX = 1e-6; case {'coxphfit', 'fitcox'} options.Display = 'off'; options.MaxFunEvals = 200; options.MaxIter = 100; options.TolFun = 1e-8; options.TolX = 1e-8; case 'copulafit' options.Display = 'off'; options.MaxFunEvals = 200; options.MaxIter = 100; options.TolBnd = 1e-6; options.TolX = 1e-6; case 'factoran' options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 100; options.TolFun = 1e-8; options.TolX = 1e-8; case 'gmdistribution' options.Display = 'off'; options.MaxIter = 100; options.TolFun = 1e-6; case 'mdscale' options.Display = 'off'; options.MaxIter = 200; options.TolFun = 1e-6; options.TolX = 1e-6; case 'mlecov' options.Display = 'off'; options.GradObj = 'off'; options.DerivStep = 2 ^ -13; case 'mlecustom' options.Display = 'off'; options.MaxFunEvals = 400; options.MaxIter = 200; options.TolBnd = 1e-6; options.TolFun = 1e-6; options.TolX = 1e-6; options.GradObj = 'off'; options.DerivStep = dstep; options.FunValCheck = 'on'; case 'tsne' options.Display = 'off'; options.MaxIter = 1000; options.TolFun = 1e-10; options.OutputFcn = ''; otherwise error ("statset: no default options available for the function '%s'.", ... fname); endswitch endfunction ## Print the option names and the values each accepts. function display_options () tbl = {'Display', '[ {off} | final | iter ]'; ... 'MaxFunEvals', '[ positive scalar ]'; ... 'MaxIter', '[ positive scalar ]'; ... 'TolBnd', '[ positive scalar ]'; ... 'TolFun', '[ positive scalar ]'; ... 'TolTypeFun', '[ abs | rel ]'; ... 'TolX', '[ positive scalar ]'; ... 'TolTypeX', '[ abs | rel ]'; ... 'GradObj', '[ {off} | on ]'; ... 'Jacobian', '[ {off} | on ]'; ... 'DerivStep', '[ positive scalar or vector ]'; ... 'FunValCheck', '[ off | {on} ]'; ... 'Robust', '[ {off} | on ]'; ... 'RobustWgtFun', ... '[ {[]} | andrews | bisquare | cauchy | fair | huber | logistic | talwar | welsch | function handle ]'; ... 'WgtFun', '[ {[]} | andrews | bisquare | cauchy | fair | huber | logistic | talwar | welsch | function handle ]'; ... 'Tune', '[ positive scalar ]'; ... 'UseParallel', '[ {false} | true ]'; ... 'UseSubstreams', '[ {false} | true ]'; ... 'Streams', '[ {} | RandStream or cell array ]'; ... 'OutputFcn', '[ {[]} | function handle or cell array ]'}; for i = 1:size (tbl, 1) printf ("%20s: %s\n", tbl{i,1}, tbl{i,2}); endfor endfunction ## Return true for a string scalar. isa returns false when the class is ## absent, so this does not require the datatypes package to be loaded. function tf = isstring_scalar (x) tf = isa (x, 'string') && isscalar (x); endfunction %!demo %! ## The default options of a given function %! options = statset ('nlinfit') %!demo %! ## Raise the iteration limit of an existing options structure %! options = statset ('nlinfit'); %! options = statset (options, 'MaxIter', 500); %! [options.MaxIter, options.TolFun] ## Structure shape %!test %! options = statset (); %! assert_equal (isstruct (options), true); %! assert_equal (numel (fieldnames (options)), 20); %!test %! assert_equal (fieldnames (statset ())', {'Display', 'MaxFunEvals', ... %! 'MaxIter', 'TolBnd', 'TolFun', 'TolTypeFun', 'TolX', 'TolTypeX', ... %! 'GradObj', 'Jacobian', 'DerivStep', 'FunValCheck', 'Robust', ... %! 'RobustWgtFun', 'WgtFun', 'Tune', 'UseParallel', 'UseSubstreams', ... %! 'Streams', 'OutputFcn'}); %!test %! options = statset (); %! assert_equal (options.MaxIter, []); %! assert_equal (options.Streams, {}); ## Per-function defaults, measured against MATLAB R2024a %!test %! options = statset ('nlinfit'); %! assert_equal (options.Display, 'off'); %! assert_equal (options.MaxIter, 200); %! assert_equal (options.TolFun, 1e-8); %! assert_equal (options.TolX, 1e-8); %! assert_equal (options.DerivStep, 6.0554544523933429e-06, 1e-20); %! assert_equal (options.FunValCheck, 'on'); %! assert_equal (options.Robust, 'off'); %! assert_equal (options.WgtFun, 'bisquare'); %!test %! assert_equal (statset ('fitnlm'), statset ('nlinfit')); %!test %! options = statset ('factoran'); %! assert_equal (options.MaxFunEvals, 400); %! assert_equal (options.MaxIter, 100); %! assert_equal (options.TolFun, 1e-8); %! assert_equal (options.TolX, 1e-8); %! assert_equal (options.TolBnd, []); %!test %! options = statset ('fitlme'); %! assert_equal (options.MaxIter, 10000); %! assert_equal (options.TolFun, 1e-6); %! assert_equal (options.TolX, 1e-12); %!test %! options = statset ('tsne'); %! assert_equal (options.MaxIter, 1000); %! assert_equal (options.TolFun, 1e-10); %! assert_equal (options.OutputFcn, ''); %!test %! options = statset ('nnmf'); %! assert_equal (options.TolFun, 1e-4); %! assert_equal (options.UseParallel, false); %!test %! options = statset ('mlecov'); %! assert_equal (options.GradObj, 'off'); %! assert_equal (options.DerivStep, 0.0001220703125); %!test %! options = statset ('coxphfit'); %! assert_equal (options.MaxFunEvals, 200); %! assert_equal (options.MaxIter, 100); %! assert_equal (options.TolFun, 1e-8); %! assert_equal (options.TolX, 1e-8); ## fitcox takes the same defaults as coxphfit, as it does in MATLAB %!test %! assert_equal (statset ('fitcox'), statset ('coxphfit')); %!test %! options = statset ('mvncdf'); %! assert_equal (options.MaxFunEvals, 1e7); %!test %! options = statset ('kmedoids'); %! assert_equal (options.MaxIter, 100); %! assert_equal (options.UseSubstreams, false); ## A function name is matched case-insensitively %!test %! assert_equal (statset ('NLINFIT'), statset ('nlinfit')); %!test %! assert_equal (statset ('LinearMixedModel'), statset ('fitlme')); ## Name/value pairs %!test %! options = statset ('MaxIter', 100); %! assert_equal (options.MaxIter, 100); %! assert_equal (options.TolX, []); %!test %! options = statset ('maxiter', 50); %! assert_equal (options.MaxIter, 50); %!test %! options = statset ('MaxIter', 10, 'TolX', 1e-3); %! assert_equal (options.MaxIter, 10); %! assert_equal (options.TolX, 1e-3); ## An empty value leaves the option unset %!test %! assert_equal (statset ('MaxIter', []), statset ()); ## A non-integer iteration count is accepted, as it is by MATLAB %!test %! assert_equal (statset ('MaxIter', 2.5).MaxIter, 2.5); %!test %! assert_equal (statset ('MaxIter', Inf).MaxIter, Inf); ## Modifying an existing structure leaves the original alone %!test %! old = statset ('nlinfit'); %! new = statset (old, 'MaxIter', 999); %! assert_equal (new.MaxIter, 999); %! assert_equal (old.MaxIter, 200); %! assert_equal (new.TolFun, 1e-8); ## Merging two structures: non-empty fields of the second win %!test %! old = statset ('nlinfit'); %! new = statset (old, statset ('MaxIter', 777)); %! assert_equal (new.MaxIter, 777); %! assert_equal (new.TolFun, 1e-8); ## An all-empty second structure overrides nothing %!test %! old = statset ('nlinfit'); %! assert_equal (statset (old, statset ()), old); ## Unrecognized fields of a supplied structure are ignored %!test %! options = statset (struct ('NotAnOption', 1), 'MaxIter', 5); %! assert_equal (options.MaxIter, 5); %! assert_equal (numel (fieldnames (options)), 20); %!test %! assert_equal (statset (struct ('NotAnOption', 1)), statset ()); ## A named weight function brings its own tuning constant %!test %! options = statset ('RobustWgtFun', 'bisquare'); %! assert_equal (options.RobustWgtFun, 'bisquare'); %! assert_equal (options.Tune, 4.685); %!test %! assert_equal (statset ('RobustWgtFun', 'huber').Tune, 1.345); ## An explicit tuning constant is not overwritten %!test %! options = statset ('RobustWgtFun', 'bisquare', 'Tune', 3); %! assert_equal (options.Tune, 3); ## nor re-derived later, not even by a call that changes the weight function %!test %! options = statset ('RobustWgtFun', 'bisquare', 'Tune', 3); %! assert_equal (statset (options, 'MaxIter', 50).Tune, 3); %! assert_equal (statset (options, struct ('MaxIter', 50)).Tune, 3); %! assert_equal (statset (options, 'RobustWgtFun', 'huber').Tune, 3); %! assert_equal (statset (options, struct ('RobustWgtFun', 'huber')).Tune, 3); %! assert_equal (statset (options, 'RobustWgtFun', 'huber', 'Tune', 9).Tune, 9); %!test %! options = statset ('RobustWgtFun', 'andrews'); %! assert_equal (statset (options, 'Display', 'off').Tune, 1.339); ## A weight function given in a structure brings its tuning constant too %!test %! assert_equal (statset (struct ('RobustWgtFun', 'andrews')).Tune, 1.339); ## A function handle carries no default tuning constant %!test %! options = statset ('RobustWgtFun', @(r) 1 ./ (1 + r .^ 2)); %! assert_equal (is_function_handle (options.RobustWgtFun), true); %! assert_equal (options.Tune, []); ## Enumerated options are matched case-insensitively and stored canonically %!test %! assert_equal (statset ('Display', 'ITER').Display, 'iter'); %!test %! assert_equal (statset ('TolTypeFun', 'REL').TolTypeFun, 'rel'); ## A logical option is stored as a logical %!test %! options = statset ('UseParallel', 1); %! assert_equal (islogical (options.UseParallel), true); %! assert_equal (options.UseParallel, true); ## Error conditions %!error ... %! statset ('nosuchfun') %!error ... %! statset ('MaxIter') %!error ... %! statset ('TreeBagger') %!error ... %! statset (1, 2) %!error ... %! statset ('MaxIter', 5, 'TolX') %!error ... %! statset (statset (), 'MaxIter') %!error ... %! statset ('NoSuchOption', 1) %!error ... %! statset ('MaxIter', 'abc') %!error ... %! statset ('MaxIter', -1) %!error ... %! statset ('MaxIter', int32 (5)) %!error ... %! statset ('TolX', -1) %!error ... %! statset ('Tune', 0) %!error ... %! statset ('Display', 'bogus') %!error ... %! statset ('GradObj', 'maybe') %!error ... %! statset ('UseParallel', 'maybe') %!error ... %! statset ('DerivStep', -1) %!error ... %! statset ('OutputFcn', 'notafunction') %!error ... %! statset ('Streams', 5) %!error ... %! statset ('RobustWgtFun', 'nosuchweight') %!error ... %! statset (struct ('MaxIter', {1, 2}), 'TolX', 1) statistics-release-1.9.2/io.github.gnu_octave.statistics.metainfo.xml000066400000000000000000000022271524624707500261070ustar00rootroot00000000000000 io.github.gnu_octave.statistics org.octave.Octave FSFAP GPL-3.0-or-later Statistics

The Statistics package for GNU Octave Octave Statistics

The Statistics package for GNU Octave is a collection of functions for statistical analysis.

https://github.com/gnu-octave/statistics/issues https://gnu-octave.github.io/statistics Octave Community octave-maintainers@gnu.org statistics-release-1.9.2/src/000077500000000000000000000000001524624707500161365ustar00rootroot00000000000000statistics-release-1.9.2/src/Makefile000066400000000000000000000052121524624707500175760ustar00rootroot00000000000000# Makefile for compiling required oct files (parallel version) # List all target oct files OCTFILES = editDistance.oct libsvmread.oct libsvmwrite.oct svmpredict.oct \ svmtrain.oct fcnntrain.oct fcnnpredict.oct \ gamtrain.oct gampredict.oct gamboosttrain.oct gamboostpredict.oct \ gamboostpairs.oct gamboostinter.oct __lbfgs__.oct __bhtsne__.oct \ __knnselect__.oct __knnbrute__.oct # Default target - depends on all oct files (enables parallel build) all: $(OCTFILES) # Individual targets for each oct file with dependencies editDistance.oct: editDistance.cc $(MKOCTFILE) editDistance.cc libsvmread.oct: libsvmread.cc $(MKOCTFILE) libsvmread.cc libsvmwrite.oct: libsvmwrite.cc $(MKOCTFILE) libsvmwrite.cc svmpredict.oct: svmpredict.cc svm.cpp svm_model_octave.cc $(MKOCTFILE) svmpredict.cc svm.cpp svm_model_octave.cc svmtrain.oct: svmtrain.cc svm.cpp svm_model_octave.cc $(MKOCTFILE) svmtrain.cc svm.cpp svm_model_octave.cc # fcnn.cpp is #included by both, so it is a prerequisite of both: without it # an edit to the network code leaves the oct-files stale and the build silent. # fcnntrain also includes the lbfgs engine, and for the same reason. fcnntrain.oct: fcnntrain.cc fcnn.cpp lbfgs.h $(MKOCTFILE) fcnntrain.cc fcnnpredict.oct: fcnnpredict.cc fcnn.cpp $(MKOCTFILE) fcnnpredict.cc # gam.cpp is #included by both, exactly as fcnn.cpp is, and for the same # reason: the engine is shared between the classification and the regression # learner, and an edit to it must rebuild both oct-files. gamboostinter.oct: gamboostinter.cc gamboost.cpp $(MKOCTFILE) gamboostinter.cc gamboostpairs.oct: gamboostpairs.cc gamboost.cpp $(MKOCTFILE) gamboostpairs.cc gamboosttrain.oct: gamboosttrain.cc gamboost.cpp $(MKOCTFILE) gamboosttrain.cc gamboostpredict.oct: gamboostpredict.cc gamboost.cpp $(MKOCTFILE) gamboostpredict.cc gamtrain.oct: gamtrain.cc gam.cpp $(MKOCTFILE) gamtrain.cc gampredict.oct: gampredict.cc gam.cpp $(MKOCTFILE) gampredict.cc # lbfgs.h is the engine both this wrapper and any compiled learner include, so # an edit to it must rebuild the oct-file. __lbfgs__.oct: __lbfgs__.cc lbfgs.h $(MKOCTFILE) __lbfgs__.cc # The Barnes-Hut summation behind tsne's 'barneshut' algorithm. __bhtsne__.oct: __bhtsne__.cc $(MKOCTFILE) __bhtsne__.cc # The partial selection the nearest-neighbour searches take their K from. __knnselect__.oct: __knnselect__.cc $(MKOCTFILE) __knnselect__.cc # The exhaustive search that never forms the distance matrix. __knnbrute__.oct: __knnbrute__.cc $(MKOCTFILE) __knnbrute__.cc # Clean target clean: rm -f $(OCTFILES) # Declare targets that don't create files .PHONY: all clean statistics-release-1.9.2/src/__bhtsne__.cc000066400000000000000000000223561524624707500205340ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include // Barnes-Hut summation of the t-SNE repulsive term. // // The gradient of the t-SNE cost splits into an attraction over the pairs // carrying a nonzero P, which is cheap and stays in the interpreter, and a // repulsion over EVERY pair, which is what makes the exact algorithm quadratic. // This file computes the repulsion in O(N log N) by the method of van der // Maaten (2014): build a 2^D-ary tree over the embedding, then walk it once per // point, collapsing a whole cell into its centre of mass whenever the cell is // far enough away that its internal structure cannot matter. // // Returned unnormalized, because the normalizer is itself one of the sums the // walk accumulates and the caller needs both: // // FREP(i,:) = sum over cells n * q^2 * (y_i - com), q = 1 / (1 + d2) // Z = sum over cells n * q (over every i) // // so the repulsive force on point i is FREP(i,:) / Z and the low-dimensional // affinities are q / Z. // The subdivision stops here whatever the criterion says. Coincident points // never separate, so a tree that splits until every cell holds one point does // not terminate on duplicated rows, which a t-SNE embedding produces readily // in its first iterations while every point still sits on the origin. static const int BHT_MAX_DEPTH = 40; struct BHNode { // Geometry of the cell, as centre and half-width per dimension. std::vector centre; std::vector halfw; // What the cell carries: how many points, and where their centre of mass is. octave_idx_type count; std::vector com; // The longest edge, which is the length scale the opening criterion uses. double width; // A leaf names its points; an internal node names its children. Exactly one // of the two is ever non-empty. std::vector points; std::vector children; }; // Build the subtree rooted at NODE over the point indices IDX, subdividing // until a cell holds a single point or the depth cap stops it. static void bht_build (std::vector& tree, octave_idx_type node, const Matrix& Y, std::vector& idx, int depth) { const octave_idx_type d = Y.columns (); const octave_idx_type n = idx.size (); tree[node].count = n; tree[node].com.assign (d, 0.0); for (octave_idx_type k = 0; k < n; k++) { for (octave_idx_type j = 0; j < d; j++) { tree[node].com[j] += Y(idx[k], j); } } for (octave_idx_type j = 0; j < d; j++) { tree[node].com[j] /= (double) n; } tree[node].width = 0.0; for (octave_idx_type j = 0; j < d; j++) { double w = 2.0 * tree[node].halfw[j]; if (w > tree[node].width) { tree[node].width = w; } } if (n <= 1 || depth >= BHT_MAX_DEPTH) { tree[node].points = idx; return; } // Sort the points into the 2^d octants of this cell, one bit per dimension. const octave_idx_type nsub = (octave_idx_type) 1 << d; std::vector> bucket (nsub); for (octave_idx_type k = 0; k < n; k++) { octave_idx_type b = 0; for (octave_idx_type j = 0; j < d; j++) { if (Y(idx[k], j) > tree[node].centre[j]) { b |= ((octave_idx_type) 1 << j); } } bucket[b].push_back (idx[k]); } // Every point landing in one octant means the cell cannot be split usefully // at this level, but the halved cell still tightens, so recursion continues // and the depth cap is what ends it. for (octave_idx_type b = 0; b < nsub; b++) { if (bucket[b].empty ()) { continue; } BHNode child; child.centre.resize (d); child.halfw.resize (d); for (octave_idx_type j = 0; j < d; j++) { double h = 0.5 * tree[node].halfw[j]; child.halfw[j] = h; child.centre[j] = (b & ((octave_idx_type) 1 << j)) ? tree[node].centre[j] + h : tree[node].centre[j] - h; } child.count = 0; tree.push_back (child); octave_idx_type c = tree.size () - 1; tree[node].children.push_back (c); bht_build (tree, c, Y, bucket[b], depth + 1); } } // Accumulate the repulsion felt by point I from the subtree at NODE. A leaf is // summed pair by pair, skipping I itself; an internal node is collapsed into // its centre of mass when the opening criterion allows and descended otherwise. static void bht_forces (const std::vector& tree, octave_idx_type node, const Matrix& Y, octave_idx_type i, double theta, double *frep, double& Z) { const octave_idx_type d = Y.columns (); const BHNode& nd = tree[node]; if (nd.count == 0) { return; } if (! nd.points.empty ()) { for (std::size_t k = 0; k < nd.points.size (); k++) { octave_idx_type j = nd.points[k]; if (j == i) { continue; } double d2 = 0.0; for (octave_idx_type m = 0; m < d; m++) { double t = Y(i, m) - Y(j, m); d2 += t * t; } double q = 1.0 / (1.0 + d2); Z += q; double qq = q * q; for (octave_idx_type m = 0; m < d; m++) { frep[m] += qq * (Y(i, m) - Y(j, m)); } } return; } // Distance from the point to the cell's centre of mass. double d2 = 0.0; for (octave_idx_type m = 0; m < d; m++) { double t = Y(i, m) - nd.com[m]; d2 += t * t; } // The opening criterion. A cell counts as distant when its width is small // against that distance; theta = 0 therefore opens every cell and the walk // degenerates to the exact pairwise sum, which is what pins this file's // correctness without an oracle. bool distant = (nd.width < theta * std::sqrt (d2)); if (distant) { double q = 1.0 / (1.0 + d2); double n = (double) nd.count; Z += n * q; double nqq = n * q * q; for (octave_idx_type m = 0; m < d; m++) { frep[m] += nqq * (Y(i, m) - nd.com[m]); } return; } for (std::size_t k = 0; k < nd.children.size (); k++) { bht_forces (tree, nd.children[k], Y, i, theta, frep, Z); } } DEFUN_DLD(__bhtsne__, args, , "-*- texinfo -*-\n\ @deftypefn {statistics} {[@var{Frep}, @var{Z}] =} __bhtsne__ (@var{Y}, @var{theta})\n\ \n\ Barnes-Hut summation of the t-SNE repulsive term. Internal; called by\n\ @code{tsne} and not meant to be used directly.\n\ \n\ @var{Y} is the @math{N*D} embedding, @math{D} being 1, 2 or 3, and @var{theta}\n\ the opening criterion, a non-negative scalar. @var{Frep} is the @math{N*D}\n\ matrix of unnormalized repulsive forces and @var{Z} the normalizer, so the\n\ force on a point is its row of @var{Frep} divided by @var{Z}.\n\ \n\ @var{theta} of zero opens every cell and reproduces the exact pairwise sum.\n\ \n\ @end deftypefn") { if (args.length () != 2) { print_usage (); } if (! args(0).isnumeric () || args(0).iscomplex () || args(0).isempty ()) { error ("__bhtsne__: Y must be a real numeric matrix."); } if (! args(1).is_scalar_type () || ! args(1).isnumeric () || args(1).iscomplex ()) { error ("__bhtsne__: THETA must be a real scalar."); } Matrix Y = args(0).matrix_value (); double theta = args(1).scalar_value (); if (theta < 0.0 || ! octave::math::isfinite (theta)) { error ("__bhtsne__: THETA must be non-negative and finite."); } const octave_idx_type n = Y.rows (); const octave_idx_type d = Y.columns (); if (d < 1 || d > 3) { error ("__bhtsne__: Y must have 1, 2, or 3 columns."); } // The root cell, sized to contain every point with room to spare so that a // point never sits exactly on a boundary. BHNode root; root.centre.resize (d); root.halfw.resize (d); for (octave_idx_type j = 0; j < d; j++) { double lo = Y(0, j); double hi = Y(0, j); for (octave_idx_type i = 1; i < n; i++) { if (Y(i, j) < lo) { lo = Y(i, j); } if (Y(i, j) > hi) { hi = Y(i, j); } } root.centre[j] = 0.5 * (lo + hi); root.halfw[j] = 0.5 * (hi - lo) + 1e-5; } root.count = 0; std::vector tree; tree.push_back (root); std::vector idx (n); for (octave_idx_type i = 0; i < n; i++) { idx[i] = i; } bht_build (tree, 0, Y, idx, 0); Matrix Frep (n, d, 0.0); double Z = 0.0; std::vector frep (d); for (octave_idx_type i = 0; i < n; i++) { for (octave_idx_type m = 0; m < d; m++) { frep[m] = 0.0; } bht_forces (tree, 0, Y, i, theta, &frep[0], Z); for (octave_idx_type m = 0; m < d; m++) { Frep(i, m) = frep[m]; } } return ovl (Frep, Z); } statistics-release-1.9.2/src/__knnbrute__.cc000066400000000000000000000262471524624707500211040ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include // Exhaustive K nearest neighbours, without forming the distance matrix. // // The straightforward way to search exhaustively is to compute every distance, // keep the whole M-by-N matrix, and then take the K smallest of each row. The // matrix is the problem: at four thousand points against four thousand it is // 128 MB that is written once, read once and thrown away, and it puts a // ceiling on N long before patience runs out. // // Here each distance is used the moment it is computed, against a running list // of the K best for that query, and then discarded. Memory is the size of the // answer. The list is kept sorted, so the output needs no sorting afterwards // and the rejection test is a single compare against its last element, which // fails for almost every point once the list has filled. // // Cosine is deliberately absent. Its distance is 1 minus a quantity that is // exactly 1 for parallel rows, so on collinear or one-dimensional data every // true distance is 0 and the ordering is decided entirely by which way that // subtraction rounds. pdist2 does not agree with its own written formula // there, returning 1.11e-16 where 1 - dot*sx*sy gives 0, so there is no fixed // result to reproduce. Cosine keeps the pdist2 route, where whatever it // answers is what it has always answered. // // Ties resolve as a stable sort resolves them, the lower index first, because // a candidate is admitted only when it is strictly better than the worst held // and is inserted after every entry it ties with. enum MetricCode { MET_EUCLIDEAN = 0, MET_CITYBLOCK, MET_CHEBYCHEV, MET_MINKOWSKI }; // Ordering of a candidate against a held entry, matching sort: a NaN comes // after every number, and equal values keep the lower index. template static inline bool better (T da, octave_idx_type ia, T db, octave_idx_type ib) { bool na = std::isnan (da); bool nb = std::isnan (db); if (na != nb) { return nb; } if (! na && da != db) { return da < db; } return ia < ib; } template static octave_value_list knnbrute (const MT& X, const MT& Y, octave_idx_type K, int metric, double q) { const octave_idx_type n = X.rows (); const octave_idx_type p = X.columns (); const octave_idx_type m = Y.rows (); // Pack both sets so that a point's coordinates are contiguous. Octave holds // them column-major, where the inner loop would stride by the row count and // miss the cache on every coordinate. std::vector xr ((std::size_t) n * p); std::vector yr ((std::size_t) m * p); for (octave_idx_type c = 0; c < p; c++) { for (octave_idx_type i = 0; i < n; i++) { xr[(std::size_t) i * p + c] = X(i, c); } for (octave_idx_type j = 0; j < m; j++) { yr[(std::size_t) j * p + c] = Y(j, c); } } Matrix OI (m, K); MT OD (m, K); std::vector bd (K); std::vector bi (K); for (octave_idx_type j = 0; j < m; j++) { const T *yp = &yr[(std::size_t) j * p]; octave_idx_type held = 0; for (octave_idx_type i = 0; i < n; i++) { const T *xp = &xr[(std::size_t) i * p]; // Euclidean and Minkowski are ranked on the sum and rooted at the end, // which saves a root per pair and cannot reorder anything, both roots // being monotone. T d; switch (metric) { case MET_CITYBLOCK: { d = 0; for (octave_idx_type c = 0; c < p; c++) { d += std::abs (xp[c] - yp[c]); } break; } case MET_CHEBYCHEV: { d = 0; for (octave_idx_type c = 0; c < p; c++) { T t = std::abs (xp[c] - yp[c]); if (t > d || std::isnan (t)) { d = t; } } break; } case MET_MINKOWSKI: { d = 0; for (octave_idx_type c = 0; c < p; c++) { d += std::pow (std::abs (xp[c] - yp[c]), (T) q); } break; } default: { d = 0; for (octave_idx_type c = 0; c < p; c++) { T t = xp[c] - yp[c]; d += t * t; } break; } } // The list is sorted, so its last entry is the one to beat. if (held == K && ! better (d, i, bd[K-1], bi[K-1])) { continue; } octave_idx_type at = (held < K) ? held : K - 1; while (at > 0 && better (d, i, bd[at-1], bi[at-1])) { bd[at] = bd[at-1]; bi[at] = bi[at-1]; at--; } bd[at] = d; bi[at] = i; if (held < K) { held++; } } for (octave_idx_type c = 0; c < K; c++) { OI(j, c) = (double) (bi[c] + 1); T v = bd[c]; if (metric == MET_EUCLIDEAN) { v = std::sqrt (v); } else if (metric == MET_MINKOWSKI) { v = std::pow (v, (T) (1.0 / q)); } OD(j, c) = v; } } return ovl (OI, OD); } DEFUN_DLD(__knnbrute__, args, , "-*- texinfo -*-\n\ @deftypefn {statistics} {[@var{idx}, @var{D}] =} __knnbrute__ (@var{X}, @var{Y}, @var{K}, @var{metric}, @var{param})\n\ \n\ Exhaustive nearest-neighbour search without forming the distance matrix.\n\ Internal; called by the nearest-neighbour searches and not meant to be used\n\ directly.\n\ \n\ @var{X} holds the reference points and @var{Y} the queries, one per row.\n\ @var{metric} is one of @qcode{'euclidean'}, @qcode{'cityblock'},\n\ @qcode{'chebychev'} or @qcode{'minkowski'}, and @var{param}\n\ the exponent for @qcode{'minkowski'} and empty otherwise.\n\ \n\ @var{idx} and @var{D} are the @math{M*K} indices and distances of the @var{K}\n\ nearest reference points to each query, in increasing distance, ordered as\n\ sorting the full row would order them.\n\ \n\ @end deftypefn") { if (args.length () != 5) { print_usage (); } if (! args(0).isnumeric () || args(0).iscomplex () || args(0).isempty ()) { error ("__knnbrute__: X must be a real numeric matrix."); } if (! args(1).isnumeric () || args(1).iscomplex () || args(1).isempty ()) { error ("__knnbrute__: Y must be a real numeric matrix."); } if (args(0).columns () != args(1).columns ()) { error ("__knnbrute__: X and Y must have the same number of columns."); } if (! args(2).is_scalar_type () || ! args(2).isnumeric ()) { error ("__knnbrute__: K must be a real scalar."); } if (! args(3).is_string ()) { error ("__knnbrute__: METRIC must be a character vector."); } const octave_idx_type n = args(0).rows (); double kd = args(2).scalar_value (); if (kd != std::floor (kd) || kd < 1.0 || kd > (double) n) { error ("__knnbrute__: K must be an integer between 1 and rows (X)."); } octave_idx_type K = (octave_idx_type) kd; std::string name = args(3).string_value (); int metric; if (name == "euclidean") { metric = MET_EUCLIDEAN; } else if (name == "cityblock") { metric = MET_CITYBLOCK; } else if (name == "chebychev") { metric = MET_CHEBYCHEV; } else if (name == "minkowski") { metric = MET_MINKOWSKI; } else { error ("__knnbrute__: unsupported METRIC '%s'.", name.c_str ()); } double q = 2.0; if (metric == MET_MINKOWSKI) { if (! args(4).is_scalar_type () || ! args(4).isnumeric ()) { error ("__knnbrute__: PARAM must be the minkowski exponent."); } q = args(4).scalar_value (); if (! (q > 0) || ! octave::math::isfinite (q)) { error ("__knnbrute__: PARAM must be a positive finite scalar."); } } if (args(0).is_single_type () || args(1).is_single_type ()) { return knnbrute (args(0).float_matrix_value (), args(1).float_matrix_value (), K, metric, q); } return knnbrute (args(0).matrix_value (), args(1).matrix_value (), K, metric, q); } /* %!test %! ## It answers what pdist2 followed by a sort answers, exactly. %! X = [1, 2; 3, 4; 5, 6; 7, 8; 2, 2; -1, 0]; %! Y = [2, 3; 6, 7; 0, 0]; %! for m = {"euclidean", "cityblock", "chebychev"} %! D = pdist2 (X, Y, m{1})'; %! [sv, so] = sort (D, 2); %! for k = 1:6 %! [idx, dst] = __knnbrute__ (X, Y, k, m{1}, []); %! assert_equal (idx, so(:,1:k)); %! assert_equal (dst, sv(:,1:k)); %! endfor %! endfor %!test %! ## Minkowski takes its exponent and agrees at each of them. %! X = [1, 2; 3, 4; 5, 6; 7, 8]; %! Y = [2, 3; 6, 7]; %! for q = [1, 1.5, 2, 3] %! D = pdist2 (X, Y, "minkowski", q)'; %! [sv, so] = sort (D, 2); %! [idx, dst] = __knnbrute__ (X, Y, 3, "minkowski", q); %! assert_equal (idx, so(:,1:3)); %! assert_equal (dst, sv(:,1:3), 1e-12); %! endfor %!test %! ## Ties keep the lower index, as a stable sort does. %! X = [0, 0; 0, 0; 0, 0]; %! [idx, dst] = __knnbrute__ (X, [1, 1], 2, "euclidean", []); %! assert_equal (idx, [1, 2]); %! assert_equal (dst, [sqrt(2), sqrt(2)], 1e-12); %!test %! ## A NaN in the data puts that neighbour last, never first. %! X = [0, 0; NaN, 0; 1, 1]; %! [idx, dst] = __knnbrute__ (X, [0, 0], 3, "euclidean", []); %! assert_equal (idx(1:2), [1, 3]); %! assert_equal (idx(3), 2); %! assert_equal (isnan (dst(3)), true); %!test %! ## Single data gives single distances; the indices stay double. %! [idx, dst] = __knnbrute__ (single ([1, 1; 4, 4]), single ([0, 0]), 1, ... %! "euclidean", []); %! assert_equal (class (dst), 'single'); %! assert_equal (class (idx), 'double'); %!test %! ## K may be the whole reference set. %! X = [3, 0; 1, 0; 2, 0]; %! [idx, dst] = __knnbrute__ (X, [0, 0], 3, "euclidean", []); %! assert_equal (idx, [2, 3, 1]); %! assert_equal (dst, [1, 2, 3], 1e-12); %!error __knnbrute__ (ones (2, 2), ones (1, 2), 1) %!error<__knnbrute__: X must be a real numeric matrix.> ... %! __knnbrute__ ({1}, ones (1, 2), 1, "euclidean", []) %!error<__knnbrute__: X and Y must have the same number of columns.> ... %! __knnbrute__ (ones (2, 3), ones (1, 2), 1, "euclidean", []) %!error<__knnbrute__: K must be an integer between 1 and rows \(X\).> ... %! __knnbrute__ (ones (2, 2), ones (1, 2), 3, "euclidean", []) %!error<__knnbrute__: unsupported METRIC 'cosine'.> ... %! __knnbrute__ (ones (2, 2), ones (1, 2), 1, "cosine", []) %!error<__knnbrute__: PARAM must be a positive finite scalar.> ... %! __knnbrute__ (ones (2, 2), ones (1, 2), 1, "minkowski", -1) */ statistics-release-1.9.2/src/__knnselect__.cc000066400000000000000000000161161524624707500212340ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include // The K smallest entries of each row, by partial selection. // // A nearest-neighbour search spends most of its time here rather than in the // distances: taking the five smallest of four thousand by sorting all four // thousand costs O(N log N) per row where O(N) will do, and on a 4000-by-4000 // matrix the sort is roughly nine times the cost of forming the distances. // // The order returned is Octave's own, so this is a drop-in replacement for // sort rather than an approximation of it: non-NaN before NaN, then by value, // then by column index, which is what a stable sort gives for ties. template struct ValIdxLess { typedef std::pair ValIdx; bool operator() (const ValIdx& a, const ValIdx& b) const { bool na = std::isnan (a.first); bool nb = std::isnan (b.first); if (na != nb) { // A NaN sorts after every number, as it does in sort. return nb; } if (! na && a.first != b.first) { return a.first < b.first; } // Ties, NaN against NaN included, keep the lower column first. return a.second < b.second; } }; // The class of the distances is carried through, single in and single out, as // the searches promise. The indices are always double. template static octave_value_list knnselect (const MT& D, octave_idx_type K) { typedef std::pair ValIdx; const octave_idx_type m = D.rows (); const octave_idx_type n = D.columns (); Matrix OI (m, K); MT OD (m, K); const T *dp = D.data (); std::vector row (n); ValIdxLess cmp; for (octave_idx_type i = 0; i < m; i++) { for (octave_idx_type j = 0; j < n; j++) { row[j] = ValIdx (dp[i + j * m], j); } // nth_element leaves the K smallest below the pivot in no order, so the // kept head is sorted afterwards; both together stay linear in N. if (K < n) { std::nth_element (row.begin (), row.begin () + K, row.end (), cmp); } std::sort (row.begin (), row.begin () + K, cmp); for (octave_idx_type j = 0; j < K; j++) { OI(i, j) = (double) (row[j].second + 1); OD(i, j) = row[j].first; } } return ovl (OI, OD); } DEFUN_DLD(__knnselect__, args, , "-*- texinfo -*-\n\ @deftypefn {statistics} {[@var{idx}, @var{D}] =} __knnselect__ (@var{dist}, @var{K})\n\ \n\ The @var{K} smallest entries of each row of @var{dist}. Internal; called by\n\ the nearest-neighbour searches and not meant to be used directly.\n\ \n\ @var{dist} is an @math{M*N} matrix of distances whose rows are query points,\n\ and @var{K} a positive integer no greater than @math{N}. @var{idx} is the\n\ @math{M*K} matrix of column indices of the @var{K} smallest entries of each\n\ row, and @var{D} their values, in the class @var{dist} carries.\n\ \n\ The result is identical to sorting each row and taking the first @var{K}\n\ columns, ties and @qcode{NaN} included, and is obtained by partial selection\n\ rather than by a full sort.\n\ \n\ @end deftypefn") { if (args.length () != 2) { print_usage (); } if (! args(0).isnumeric () || args(0).iscomplex () || args(0).isempty ()) { error ("__knnselect__: DIST must be a real numeric matrix."); } if (! args(1).is_scalar_type () || ! args(1).isnumeric () || args(1).iscomplex ()) { error ("__knnselect__: K must be a real scalar."); } const octave_idx_type n = args(0).columns (); double kd = args(1).scalar_value (); if (kd != std::floor (kd) || kd < 1.0 || kd > (double) n) { error ("__knnselect__: K must be an integer between 1 and columns (DIST)."); } octave_idx_type K = (octave_idx_type) kd; if (args(0).is_single_type ()) { return knnselect (args(0).float_matrix_value (), K); } return knnselect (args(0).matrix_value (), K); } /* %!test %! ## The result is sort's, not an approximation of it. %! D = [3, 1, 2, 1; 5, 5, 5, 5; 9, 8, 1, 2; 0, -1, -1, 4]; %! [sv, so] = sort (D, 2); %! for k = 1:4 %! [idx, dst] = __knnselect__ (D, k); %! assert_equal (idx, so(:,1:k)); %! assert_equal (dst, sv(:,1:k)); %! endfor %!test %! ## Ties keep the lower column first, as a stable sort does. %! [idx, dst] = __knnselect__ ([5, 5, 5, 5], 3); %! assert_equal (idx, [1, 2, 3]); %! assert_equal (dst, [5, 5, 5]); %!test %! ## A NaN sorts after every number and never displaces one. %! [idx, dst] = __knnselect__ ([3, NaN, 1, NaN, 2], 3); %! assert_equal (idx, [3, 5, 1]); %! assert_equal (dst, [1, 2, 3]); %!test %! ## A row of nothing but NaN keeps them in column order. %! [idx, dst] = __knnselect__ ([NaN, NaN, NaN], 2); %! assert_equal (idx, [1, 2]); %! assert_equal (isnan (dst), [true, true]); %!test %! ## Inf is a value like any other and comes before NaN. %! [idx, dst] = __knnselect__ ([Inf, NaN, 2, -Inf], 3); %! assert_equal (idx, [4, 3, 1]); %! assert_equal (dst, [-Inf, 2, Inf]); %!test %! ## K equal to the width returns the whole row, sorted. %! [idx, dst] = __knnselect__ ([4, 2, 9, 1], 4); %! assert_equal (idx, [4, 2, 1, 3]); %! assert_equal (dst, [1, 2, 4, 9]); %!test %! ## Single distances come back single; the indices stay double. %! [idx, dst] = __knnselect__ (single ([4, 2, 9, 1]), 2); %! assert_equal (class (dst), 'single'); %! assert_equal (class (idx), 'double'); %! assert_equal (dst, single ([1, 2])); %!test %! ## It agrees with sort over random matrices at every width of K. %! rand ("seed", 42); %! for t = 1:50 %! A = round (rand (6, 9) * 4); %! k = 1 + mod (t, 9); %! [idx, dst] = __knnselect__ (A, k); %! [sv, so] = sort (A, 2); %! assert_equal (idx, so(:,1:k)); %! assert_equal (dst, sv(:,1:k)); %! endfor %!error __knnselect__ (ones (2, 2)) %!error<__knnselect__: DIST must be a real numeric matrix.> ... %! __knnselect__ ({1, 2}, 1) %!error<__knnselect__: DIST must be a real numeric matrix.> ... %! __knnselect__ (ones (2, 2) * i, 1) %!error<__knnselect__: K must be a real scalar.> ... %! __knnselect__ (ones (2, 2), [1, 2]) %!error<__knnselect__: K must be an integer between 1 and columns \(DIST\).> ... %! __knnselect__ (ones (2, 3), 0) %!error<__knnselect__: K must be an integer between 1 and columns \(DIST\).> ... %! __knnselect__ (ones (2, 3), 4) %!error<__knnselect__: K must be an integer between 1 and columns \(DIST\).> ... %! __knnselect__ (ones (2, 3), 1.5) */ statistics-release-1.9.2/src/__lbfgs__.cc000066400000000000000000000512741524624707500203470ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include "lbfgs.h" using namespace std; // Objective adaptor for an Octave function handle. One call is one // interpreter round trip returning [f, g], which is why the compiled learners // include lbfgs.h directly instead of coming through here. class octave_objective { public: octave_objective (const octave_value& fcn, octave_idx_type n) : m_fcn (fcn), m_n (n) { } double operator () (const vector& x, vector& g) { octave_quit (); ColumnVector xv (m_n); for (octave_idx_type i = 0; i < m_n; i++) { xv(i) = x[i]; } octave_value_list in (1); in(0) = xv; octave_value_list out = octave::feval (m_fcn, in, 2); if (out.length () < 2) { error ("__lbfgs__: FCN must return both a value and a gradient."); } if (! out(0).isnumeric () || ! out(0).is_scalar_type () || out(0).iscomplex ()) { error ("__lbfgs__: FCN must return a real scalar as its first output."); } if (! out(1).isnumeric () || out(1).iscomplex () || out(1).numel () != m_n) { error ("__lbfgs__: FCN must return a real gradient with as many " "elements as X0."); } NDArray gv = out(1).array_value (); for (octave_idx_type i = 0; i < m_n; i++) { g[i] = gv(i); } return out(0).double_value (); } private: octave_value m_fcn; octave_idx_type m_n; }; static bool is_real_scalar (const octave_value& v) { if (! v.isnumeric () || ! v.is_scalar_type () || v.iscomplex ()) { return false; } double d = v.double_value (); return (d == d); } static bool is_whole (double d) { return (d < HUGE_VAL && d > -HUGE_VAL && d == floor (d)); } DEFUN_DLD (__lbfgs__, args, nargout, "-*- texinfo -*-\n\ @deftypefn {statistics} {@var{x} =} __lbfgs__ (@var{fcn}, @var{x0})\n\ @deftypefnx {statistics} {@var{x} =} __lbfgs__ (@var{fcn}, @var{x0}, @var{options})\n\ @deftypefnx {statistics} {[@var{x}, @var{info}] =} __lbfgs__ (@dots{})\n\ \n\ Minimize @var{fcn} by limited-memory BFGS with a strong Wolfe line search.\n\ \n\ @var{fcn} is a function handle called as @code{[f, g] = fcn (x)}, returning\n\ the objective value at @var{x} and its gradient. @var{x0} is the real\n\ numeric vector the iteration starts from. @var{x} is returned as a column\n\ vector whatever the orientation of @var{x0}.\n\ \n\ @var{options} is a scalar struct. Every field is optional and the defaults\n\ are MATLAB's, as measured on R2024a.\n\ \n\ @multitable @columnfractions 0.25 0.15 0.60\n\ @headitem Field @tab Default @tab Description\n\ \n\ @item @qcode{IterationLimit} @tab @qcode{1000} @tab Largest number of\n\ iterations taken.\n\ \n\ @item @qcode{GradientTolerance} @tab @qcode{1e-6} @tab Stop once the\n\ gradient's infinity norm falls to or below this value.\n\ \n\ @item @qcode{StepTolerance} @tab @qcode{1e-6} @tab Stop once the step's\n\ infinity norm falls to or below this value.\n\ \n\ @item @qcode{LossTolerance} @tab @qcode{1e-6} @tab Stop once the objective\n\ VALUE falls to or below this value. This is not a test on the change in the\n\ objective, which is what the name suggests; it matches MATLAB. Pass\n\ @code{-Inf} to disable it, which any objective that can go negative, or whose\n\ minimum is zero, will want.\n\ \n\ @item @qcode{BetaTolerance} @tab @qcode{0} @tab Stop once the RELATIVE change\n\ in the iterate falls to or below this value, measured as the two-norm of the\n\ step over the two-norm of the point it landed on, with the denominator\n\ unguarded. A value of @qcode{0} does not test against zero: it switches the\n\ test off and leaves @qcode{RelativeChangeInBeta} at @code{NaN}, as MATLAB\n\ does. Note that this disables with @qcode{0} where @qcode{LossTolerance}\n\ disables with @code{-Inf}; each follows MATLAB for its own quantity.\n\ \n\ @item @qcode{HistorySize} @tab @qcode{10} @tab Number of curvature pairs the\n\ inverse Hessian approximation is built from.\n\ \n\ @item @qcode{InitialStepSize} @tab @qcode{[]} @tab Step length tried first on\n\ the opening iteration. Scaled from the gradient when left empty.\n\ @end multitable\n\ \n\ When more than one tolerance is met at the same iteration the reported\n\ criterion follows MATLAB's order: gradient, then the relative change in the\n\ iterate, then step, then loss.\n\ \n\ @var{info} is a struct with fields @qcode{Iterations}, @qcode{FuncCount},\n\ @qcode{Fval}, @qcode{Gradient}, @qcode{Step}, @qcode{RelativeChangeInBeta},\n\ @qcode{Criterion} and @qcode{History}, the last being a struct of\n\ per-iteration column vectors of the same names.\n\ \n\ @qcode{Criterion} is one of @qcode{'gradient'}, @qcode{'beta'},\n\ @qcode{'step'}, @qcode{'loss'}, @qcode{'iteration'} or\n\ @qcode{'linesearch'}. It is a token and not a sentence on purpose: MATLAB\n\ words the same criterion differently per function, so each caller maps it to\n\ the wording of the function it mirrors.\n\ \n\ This is an internal function and is not meant to be called directly.\n\ @end deftypefn") { octave_value_list retval; if (args.length () < 2 || args.length () > 3) { print_usage (); } if (! args(0).is_function_handle ()) { error ("__lbfgs__: FCN must be a function handle."); } if (! args(1).isnumeric () || args(1).iscomplex () || args(1).isempty () || (args(1).rows () != 1 && args(1).columns () != 1)) { error ("__lbfgs__: X0 must be a real numeric vector."); } lbfgs::options opt; if (args.length () == 3) { if (! args(2).isstruct () || args(2).numel () != 1) { error ("__lbfgs__: OPTIONS must be a scalar struct."); } octave_scalar_map map = args(2).scalar_map_value (); string_vector fields = map.fieldnames (); for (octave_idx_type i = 0; i < fields.numel (); i++) { std::string name = fields(i); octave_value val = map.contents (name); if (name == "IterationLimit") { if (! is_real_scalar (val) || val.double_value () < 0 || ! is_whole (val.double_value ())) { error ("__lbfgs__: 'IterationLimit' must be a nonnegative " "integer scalar."); } opt.iteration_limit = val.int_value (); } else if (name == "GradientTolerance" || name == "StepTolerance" || name == "BetaTolerance") { if (! is_real_scalar (val) || val.double_value () < 0) { error ("__lbfgs__: '%s' must be a nonnegative scalar.", name.c_str ()); } if (name == "GradientTolerance") { opt.gradient_tolerance = val.double_value (); } else if (name == "StepTolerance") { opt.step_tolerance = val.double_value (); } else { opt.beta_tolerance = val.double_value (); } } else if (name == "LossTolerance") { // This one is a value rather than a norm, so it is allowed to go // negative. An objective that can take negative values needs -Inf // here, or the test fires on the first step that crosses zero. if (! is_real_scalar (val)) { error ("__lbfgs__: 'LossTolerance' must be a real scalar."); } opt.loss_tolerance = val.double_value (); } else if (name == "HistorySize") { if (! is_real_scalar (val) || val.double_value () < 1 || ! is_whole (val.double_value ())) { error ("__lbfgs__: 'HistorySize' must be a positive integer " "scalar."); } opt.history_size = val.int_value (); } else if (name == "InitialStepSize") { // An empty value is MATLAB's way of asking for the automatic step. if (! val.isempty ()) { if (! is_real_scalar (val) || val.double_value () <= 0) { error ("__lbfgs__: 'InitialStepSize' must be a positive " "scalar."); } opt.initial_step_size = val.double_value (); } } else { error ("__lbfgs__: '%s' is not a valid option.", name.c_str ()); } } } octave_idx_type n = args(1).numel (); NDArray x0 = args(1).array_value (); vector x (n); for (octave_idx_type i = 0; i < n; i++) { x[i] = x0(i); } octave_objective fun (args(0), n); lbfgs::result res = lbfgs::minimize (fun, x, opt); ColumnVector xout (n); for (octave_idx_type i = 0; i < n; i++) { xout(i) = x[i]; } retval(0) = xout; if (nargout > 1) { octave_idx_type nh = res.history.size (); ColumnVector h_iter (nh), h_fval (nh), h_grad (nh), h_step (nh), h_beta (nh); for (octave_idx_type i = 0; i < nh; i++) { h_iter(i) = i + 1; h_fval(i) = res.history[i].fval; h_grad(i) = res.history[i].gradient; h_step(i) = res.history[i].step; h_beta(i) = res.history[i].rel_beta; } octave_scalar_map history; history.assign ("Iteration", h_iter); history.assign ("Fval", h_fval); history.assign ("Gradient", h_grad); history.assign ("Step", h_step); history.assign ("RelativeChangeInBeta", h_beta); octave_scalar_map info; info.assign ("Iterations", static_cast (res.iterations)); info.assign ("FuncCount", static_cast (res.funcount)); info.assign ("Fval", res.fval); info.assign ("Gradient", res.gradient); info.assign ("Step", res.step); info.assign ("RelativeChangeInBeta", res.rel_beta); info.assign ("Criterion", lbfgs::criterion_token (res.crit)); info.assign ("History", history); retval(1) = info; } return retval; } /* %!function [f, g] = __rosen__ (x) %! t = x(2) - x(1)^2; %! f = 100 * t^2 + (1 - x(1))^2; %! g = zeros (2, 1); %! g(1) = -400 * x(1) * t - 2 * (1 - x(1)); %! g(2) = 200 * t; %!endfunction %!function [f, g] = __exrosen__ (x) %! n = numel (x); %! o = (1:2:n-1)'; %! e = (2:2:n)'; %! t1 = x(e) - x(o).^2; %! t2 = 1 - x(o); %! f = sum (100 * t1.^2 + t2.^2); %! g = zeros (n, 1); %! g(o) = -400 * x(o) .* t1 - 2 * t2; %! g(e) = 200 * t1; %!endfunction %!function [f, g] = __beale__ (x) %! y = [1.5; 2.25; 2.625]; %! k = (1:3)'; %! t = 1 - x(2).^k; %! r = y - x(1) * t; %! f = sum (r.^2); %! g = zeros (2, 1); %! g(1) = -2 * sum (r .* t); %! g(2) = 2 * x(1) * sum (r .* k .* x(2).^(k - 1)); %!endfunction %!function [f, g] = __wood__ (x) %! t1 = 100 * (x(2) - x(1)^2)^2 + (1 - x(1))^2; %! t2 = 90 * (x(4) - x(3)^2)^2 + (1 - x(3))^2; %! t3 = 10.1 * ((1 - x(2))^2 + (1 - x(4))^2); %! t4 = 19.8 * (1 - x(2)) * (1 - x(4)); %! f = t1 + t2 + t3 + t4; %! g = zeros (4, 1); %! g(1) = -400 * x(1) * (x(2) - x(1)^2) - 2 * (1 - x(1)); %! g(2) = 200 * (x(2) - x(1)^2) - 20.2 * (1 - x(2)) - 19.8 * (1 - x(4)); %! g(3) = -360 * x(3) * (x(4) - x(3)^2) - 2 * (1 - x(3)); %! g(4) = 180 * (x(4) - x(3)^2) - 20.2 * (1 - x(4)) - 19.8 * (1 - x(2)); %!endfunction %!function [f, g] = __powellsq__ (x) %! a = x(1) + 10 * x(2); %! b = x(3) - x(4); %! c = x(2) - 2 * x(3); %! d = x(1) - x(4); %! f = a^2 + 5 * b^2 + c^4 + 10 * d^4; %! g = zeros (4, 1); %! g(1) = 2 * a + 40 * d^3; %! g(2) = 20 * a + 4 * c^3; %! g(3) = 10 * b - 8 * c^3; %! g(4) = -10 * b - 40 * d^3; %!endfunction %!function [f, g] = __quadtri__ (x) %! n = numel (x); %! A = 2 * eye (n) - diag (ones (n - 1, 1), 1) - diag (ones (n - 1, 1), -1); %! xs = ((1:n)' / n).^2; %! r = x - xs; %! f = 0.5 * r' * A * r; %! g = A * r; %!endfunction %!function [f, g] = __badgrad__ (x) %! f = sum (x.^2); %! g = 1; %!endfunction ## Every Moré, Garbow and Hillstrom problem below has a zero minimum, so the ## loss test is switched off and convergence is judged by the gradient. Left ## on, it stops the iteration first; that is the point of the next test. ## Problem 1, the standard start. %!test %! opt = struct ("LossTolerance", -Inf); %! [x, info] = __lbfgs__ (@__rosen__, [-1.2; 1], opt); %! crit = info.Criterion; %! assert_equal (x, [1; 1], 1e-5); %! assert_equal (info.Fval < 1e-8, true); %! assert_equal (crit, "gradient"); ## LossTolerance tests the objective VALUE, not the change in it, so on a ## problem whose minimum is zero the default 1e-6 pre-empts the gradient test ## while the gradient is still six orders of magnitude above its own ## tolerance. Measured on MATLAB R2024a, where a LossTolerance of 0.5 against ## a first-iteration loss of 0.3551 stopped fitcnet at iteration 1. %!test %! [x, info] = __lbfgs__ (@__rosen__, [-1.2; 1]); %! crit = info.Criterion; %! assert_equal (crit, "loss"); %! assert_equal (info.Fval <= 1e-6, true); %! assert_equal (info.Gradient > 1e-6, true); ## Problem 21 at n = 20, where the parameters outnumber the stored curvature ## pairs and the limited-memory recursion is what is actually being tested. %!test %! n = 20; %! x0 = zeros (n, 1); %! x0(1:2:n-1) = -1.2; %! x0(2:2:n) = 1; %! opt = struct ("LossTolerance", -Inf); %! [x, info] = __lbfgs__ (@__exrosen__, x0, opt); %! assert_equal (x, ones (n, 1), 1e-4); ## Problem 5. %!test %! opt = struct ("LossTolerance", -Inf); %! [x, info] = __lbfgs__ (@__beale__, [1; 1], opt); %! assert_equal (x, [3; 0.5], 1e-4); ## Problem 14. %!test %! opt = struct ("LossTolerance", -Inf); %! [x, info] = __lbfgs__ (@__wood__, [-3; -1; -3; -1], opt); %! assert_equal (x, ones (4, 1), 1e-4); ## Problem 13. The Hessian is singular at the solution, so convergence is ## linear and the objective is the only thing worth asserting on. %!test %! opt = struct ("LossTolerance", -Inf); %! [x, info] = __lbfgs__ (@__powellsq__, [3; -1; 0; 1], opt); %! assert_equal (info.Fval < 1e-8, true); ## A quadratic in fifty variables, well past the ten curvature pairs the ## history holds, against its known minimiser. StepTolerance is switched off ## along with the loss test: the steps go below 1e-6 while the gradient is ## still far from its own tolerance, and stopping there is correct but is not ## what this test is measuring. %!test %! n = 50; %! xs = ((1:n)' / n).^2; %! opt = struct ("GradientTolerance", 1e-10, "LossTolerance", -Inf, ... %! "StepTolerance", 0); %! [x, info] = __lbfgs__ (@__quadtri__, zeros (n, 1), opt); %! crit = info.Criterion; %! assert_equal (x, xs, 1e-6); %! assert_equal (crit, "gradient"); ## A single stored pair still converges, it just takes longer. %!test %! opt = struct ("HistorySize", 1, "LossTolerance", -Inf); %! [x, info] = __lbfgs__ (@__rosen__, [-1.2; 1], opt); %! assert_equal (x, [1; 1], 1e-4); ## X0 may be a row, X never is. %!test %! opt = struct ("LossTolerance", -Inf); %! [x, info] = __lbfgs__ (@__rosen__, [-1.2, 1], opt); %! assert_equal (x, [1; 1], 1e-5); ## An empty InitialStepSize asks for the automatic one, as it does in MATLAB. %!test %! opt = struct ("InitialStepSize", 1e-3, "LossTolerance", -Inf); %! [x1, i1] = __lbfgs__ (@__rosen__, [-1.2; 1], opt); %! opt = struct ("InitialStepSize", [], "LossTolerance", -Inf); %! [x2, i2] = __lbfgs__ (@__rosen__, [-1.2; 1], opt); %! assert_equal (x1, [1; 1], 1e-5); %! assert_equal (x2, [1; 1], 1e-5); ## A loss tolerance well above the starting value stops at once. %!test %! opt = struct ("LossTolerance", 1); %! [x, info] = __lbfgs__ (@__rosen__, [-1.2; 1], opt); %! crit = info.Criterion; %! assert_equal (crit, "loss"); %! assert_equal (info.Fval <= 1, true); ## Reported criterion when several are met at once: gradient outranks step. %!test %! opt = struct ("GradientTolerance", 1e3, "StepTolerance", 1e3); %! [x, info] = __lbfgs__ (@__rosen__, [-1.2; 1], opt); %! crit = info.Criterion; %! assert_equal (crit, "gradient"); %! assert_equal (info.Iterations, 1); ## And step outranks loss. %!test %! opt = struct ("StepTolerance", 1e3, "LossTolerance", 1e3); %! [x, info] = __lbfgs__ (@__rosen__, [-1.2; 1], opt); %! crit = info.Criterion; %! assert_equal (crit, "step"); %! assert_equal (info.Iterations, 1); ## The history holds one row per iteration taken, and the line search costs ## objective calls over and above them. %!test %! opt = struct ("IterationLimit", 4); %! [x, info] = __lbfgs__ (@__rosen__, [-1.2; 1], opt); %! crit = info.Criterion; %! assert_equal (crit, "iteration"); %! assert_equal (info.Iterations, 4); %! assert_equal (info.History.Iteration, (1:4)'); %! assert_equal (numel (info.History.Fval), 4); %! assert_equal (info.FuncCount >= info.Iterations, true); ## A zero limit evaluates the start point and stops there. %!test %! opt = struct ("IterationLimit", 0); %! [x, info] = __lbfgs__ (@__rosen__, [-1.2; 1], opt); %! assert_equal (x, [-1.2; 1]); %! assert_equal (info.Iterations, 0); %! assert_equal (isempty (info.History.Fval), true); ## BetaTolerance is off by default, and off means the quantity is not ## computed rather than tested against zero: MATLAB reports NaN for it, and ## so does this. %!test %! opt = struct ("LossTolerance", -Inf, "IterationLimit", 5); %! [x, info] = __lbfgs__ (@__rosen__, [-1.2; 1], opt); %! assert_equal (info.RelativeChangeInBeta, NaN); %! assert_equal (info.History.RelativeChangeInBeta, NaN (5, 1)); ## The relative change is the two-norm of the step over the two-norm of the ## point it landed on, unguarded. From a zero start the first iteration is ## therefore exactly 1, which is what R2024a's fitclinear reports and what ## rules out every max (1, .) guarded candidate. %!test %! n = 50; %! opt = struct ("LossTolerance", -Inf, "BetaTolerance", 1e-12, ... %! "IterationLimit", 4); %! [x, info] = __lbfgs__ (@__quadtri__, zeros (n, 1), opt); %! assert_equal (info.History.RelativeChangeInBeta(1), 1); ## It stops on the relative change, and reports which test did it. %!test %! opt = struct ("LossTolerance", -Inf, "BetaTolerance", 1e3); %! [x, info] = __lbfgs__ (@__rosen__, [-1.2; 1], opt); %! assert_equal (info.Criterion, "beta"); %! assert_equal (info.Iterations, 1); ## Gradient outranks it, and it outranks step, which is MATLAB's order. %!test %! opt = struct ("GradientTolerance", 1e3, "BetaTolerance", 1e3, ... %! "StepTolerance", 1e3); %! [x, info] = __lbfgs__ (@__rosen__, [-1.2; 1], opt); %! assert_equal (info.Criterion, "gradient"); %!test %! opt = struct ("BetaTolerance", 1e3, "StepTolerance", 1e3, ... %! "LossTolerance", 1e3); %! [x, info] = __lbfgs__ (@__rosen__, [-1.2; 1], opt); %! assert_equal (info.Criterion, "beta"); %!error __lbfgs__ (@__rosen__) %!error <__lbfgs__: FCN must be a function handle.> ... %! __lbfgs__ (1, [1; 1]) %!error <__lbfgs__: X0 must be a real numeric vector.> ... %! __lbfgs__ (@__rosen__, {1}) %!error <__lbfgs__: X0 must be a real numeric vector.> ... %! __lbfgs__ (@__rosen__, ones (2, 2)) %!error <__lbfgs__: X0 must be a real numeric vector.> ... %! __lbfgs__ (@__rosen__, []) %!error <__lbfgs__: X0 must be a real numeric vector.> ... %! __lbfgs__ (@__rosen__, complex ([1; 1])) %!error <__lbfgs__: OPTIONS must be a scalar struct.> ... %! __lbfgs__ (@__rosen__, [1; 1], 5) %!error <__lbfgs__: 'IterationLimit' must be a nonnegative integer scalar.> ... %! __lbfgs__ (@__rosen__, [1; 1], struct ("IterationLimit", -1)) %!error <__lbfgs__: 'IterationLimit' must be a nonnegative integer scalar.> ... %! __lbfgs__ (@__rosen__, [1; 1], struct ("IterationLimit", 2.5)) %!error <__lbfgs__: 'GradientTolerance' must be a nonnegative scalar.> ... %! __lbfgs__ (@__rosen__, [1; 1], struct ("GradientTolerance", -1)) %!error <__lbfgs__: 'StepTolerance' must be a nonnegative scalar.> ... %! __lbfgs__ (@__rosen__, [1; 1], struct ("StepTolerance", -1)) %!error <__lbfgs__: 'LossTolerance' must be a real scalar.> ... %! __lbfgs__ (@__rosen__, [1; 1], struct ("LossTolerance", NaN)) %!error <__lbfgs__: 'BetaTolerance' must be a nonnegative scalar.> ... %! __lbfgs__ (@__rosen__, [1; 1], struct ("BetaTolerance", -1)) %!error <__lbfgs__: 'HistorySize' must be a positive integer scalar.> ... %! __lbfgs__ (@__rosen__, [1; 1], struct ("HistorySize", 0)) %!error <__lbfgs__: 'InitialStepSize' must be a positive scalar.> ... %! __lbfgs__ (@__rosen__, [1; 1], struct ("InitialStepSize", -1)) %!error <__lbfgs__: 'Bogus' is not a valid option.> ... %! __lbfgs__ (@__rosen__, [1; 1], struct ("Bogus", 1)) %!error <__lbfgs__: FCN must return a real gradient with as many elements as X0.> ... %! __lbfgs__ (@__badgrad__, [1; 1]) */ statistics-release-1.9.2/src/doc-cache000066400000000000000000000760261524624707500177020ustar00rootroot00000000000000# doc-cache created by Octave 11.2.0 # name: cache # type: cell # rows: 3 # columns: 13 # name: # type: sq_string # elements: 1 # length: 12 editDistance # name: # type: sq_string # elements: 1 # length: 4464 statistics: d = editDistance ( str ) statistics: d = editDistance ( doc ) statistics: C = editDistance (…, minDist ) statistics: [ C , IA , IC ] = editDistance (…, minDist ) statistics: [ C , IA , IC ] = editDistance (…, minDist , "OutputAllIndices" , value ) statistics: d = editDistance ( str1 , str2 ) statistics: d = editDistance ( doc1 , doc2 ) Compute the edit (Levenshtein) distance between strings or documents. d = editDistance ( str ) takes a cell array of character vectors and computes the Levenshtein distance between each pair of strings in str as the lowest number of grapheme insertions, deletions, and substitutions required to convert string str {1} to string str {2} . If str is a cellstr vector with N elements, the returned distance d is an (N × (N-1)) / 2) column vector of doubles. If str is an array (that is all (size (str) > 1) = true ), then it is transformed to a column vector as in str = str(:) . editDistance expects str to be a column vector, if it is row vector, it is transformed to a column vector. d = editDistance ( doc ) can also take a cell array containing cell arrays of character vectors, in which case each element of doc is regarded as a document, and the character vector in each element of the cell string array is regarded a token. editDistance computes the Levenshtein distance between each pair of cell elements in doc as the lowest number of token insertions, deletions, and substitutions required to convert document doc {1} to document doc {2} . If doc is a cell vector with N elements, the distance d is an (N × (N-1)) / 2) column vector of doubles. If doc is an array (that is all (size (doc) > 1) = true ), then it is converted to a column vector as in doc = doc(:) . C = editDistance (…, minDist ) specifies a minimum distance, minDist , which is regarded as a similarity threshold between each pair of strings or documents, defined in the previous syntaxes. In this case, editDistance resembles the functionality of the uniquetol function and returns the unique strings or documents that are similar up to minDist distance. C is either a cellstring array or a cell array of cellstrings, depending on the first input argument. [ C , IA , IC ] = editDistance (…, minDist ) also returns index vectors IA and IC . Assuming A contains either strings str or documents doc as defined above, IA is a column vector of indices to the first occurrence of similar elements such that C = A ( IA ) , and IC is a column vector of indices such that A ~ C ( IC ) where ~ means that the strings or documents are within the specified distance minDist of each other. [ C , IA , IC ] = editDistance (…, minDist , "OutputAllIndices" , value ) specifies the type of the second output index IA . value must be a logical scalar. When set to true , IA is a cell array containing the vectors of indices for ALL elements in A that are within the specified distance minDist of each other. Each cell in IA corresponds to a value in C and the values in each cell correspond to locations in A . If value is set to false , then IA is returned as an index vector described in the previous syntax. d = editDistance ( str1 , str2 ) can also take two character vectors, str1 and str2 and compute the Levenshtein distance d as the lowest number of grapheme insertions, deletions, and substitutions required to convert str1 to str2 . str1 and str2 may also be cellstring arrays, in which case the pairwise distance is computed between str1 {n} and str1 {n} . The cellstring arrays must be of the same size or scalars, in which case the scalar is expanded to the size of the other cellstring input. The returned distance d is a column vector with the same number of elements as the cellstring arrays. If str1 or str2 is an array, then it is transformed to a column vector. editDistance expects both str1 and str2 to be a column vectors, if not, they are transformed into column vectors. d = editDistance ( doc1 , doc2 ) can also take two cell array containing cell arrays of character vectors, in which case each element of doc1 and doc2 is regarded as a document, and the character vector in each element of the cell string array is regarded a token. editDistance computes the pairwise Levenshtein distance between the of cell elements in doc1 and doc2 as the lowest number of token insertions, deletions, and substitutions required to convert document doc1 {n} to document doc1 {n} . # name: # type: sq_string # elements: 1 # length: 69 Compute the edit (Levenshtein) distance between strings or documents. # name: # type: sq_string # elements: 1 # length: 11 fcnnpredict # name: # type: sq_string # elements: 1 # length: 2034 statistics: pred_Y = fcnnpredict ( LayerWeights , LayerBiases , Activations , OutputLayerActivation , XC ) statistics: pred_Y = fcnnpredict (…, NumThreads ) statistics: [ pred_Y , scores ] = fcnnpredict (…) Make predictions from a fully connected Neural Network. pred_Y = fcnnpredict ( LayerWeights , LayerBiases , Activations , OutputLayerActivation , XC ) requires the following input arguments. LayerWeights : A cell row vector holding one matrix per layer, each with one row per neuron of that layer and one column per input to it. LayerBiases : A cell row vector holding one bias column per layer, matching LayerWeights layer for layer and row for row. Activations : The activation function of the hidden layers, named as a character vector applying to all of them or as a cellstring naming them one by one. The supported names are listed under fcnntrain . OutputLayerActivation : The activation function of the output layer, named as a character vector. XC : An NxM matrix containing the data set to be predicted upon. Rows N correspond to individual samples and columns M correspond to features (dimensions). Type of XC must be double and the number of features must correspond to those of the trained model. fcnnpredict can also be called with a sixth input argument, in which case, NumThreads , a positive scalar integer value, defines the number of threads to be used when computing the activation layers. For layers with less than 1000 neurons, NumThreads always defaults to 1. fcnnpredict returns the predicted labels, pred_Y , and if a second output argument is requested, it also returns the corresponding values of the neural networks output in scores . Installation Note: in order to support parallel processing on MacOS, users have to manually add support for OpenMP by adding the following flags to CFLAGS and CXXFLAGS prior to installing the statistics package: setenv ("CPPFLAGS", "-I/opt/homebrew/opt/libomp/include -Xclang -fopenmp") See also: fcnntrain, fitcnet, ClassificationNeuralNetwork # name: # type: sq_string # elements: 1 # length: 55 Make predictions from a fully connected Neural Network. # name: # type: sq_string # elements: 1 # length: 9 fcnntrain # name: # type: sq_string # elements: 1 # length: 4021 statistics: Mdl = fcnntrain ( X , Y , LayerSizes , Activations , OutputLayerActivation , NumThreads , LearningRate , Epochs , DisplayInfo ) statistics: Mdl = fcnntrain (…, LossFunction ) Train a fully connected Neural Network. Mdl = fcnntrain (…) requires the following input arguments. X : An NxM matrix containing the data set to be trained upon. Rows N correspond to individual samples and columns M correspond to features (dimensions). Type of X must be double. Y : An Nx1 column vector containing the labels of the training dataset. The labels must be natural numbers (positive integers) starting from 1 up to the number of classes, similarly as returned by the ‘grp2idx‘ function. Type of Y must be double. Under regression, selected by LossFunction 2, Y is instead an NxR matrix of response values, which may take any finite value, and the output layer is sized to its R columns rather than to a number of classes. LayerSizes : A numeric row vector of integer values defining the size of the hidden layers of the network. Input and output layers are automatically determined by the training data and their labels. Activations : The activation function of the hidden layers, named as a character vector applying to all of them or as a cellstring naming them one by one, in which case it must have one name per hidden layer. The supported names are: 'linear' or 'none' : the identity 'sigmoid' 'relu' : rectified linear unit 'tanh' : hyperbolic tangent 'softmax' 'lrelu' or 'prelu' : leaky rectified linear unit, whose negative slope is a constant 0.01 'elu' : exponential linear unit, saturating at -1 'gelu' : Gaussian error linear unit OutputLayerActivation : The activation function of the output layer, named as a character vector from the same list. NumThreads : A positive scalar integer value defining the number of threads used for computing the activation layers. For layers with less than 1000 neurons, NumThreads always defaults to 1. LearningRate : A positive scalar value defining the learning rate used by the gradient descend algorithm during training. Epochs : A positive scalar value defining the number of epochs for training the model. DisplayInfo : A boolean scalar indicating whether to print information during training. Mdl = fcnntrain (…, LossFunction ) also selects the loss the network is trained against. LossFunction is a scalar: 0 for mean squared error over a one-hot target, which is the default, 1 for cross entropy, and 2 for mean squared error over a continuous response. Cross entropy expects the output layer to report a probability over the classes, so it belongs with a softmax output; paired that way the two gradients compose to y - t . Its loss is undefined where the predicted probability of the true class is zero, so both the logarithm and its derivative are floored. Code 2 is regression: Y holds response values rather than labels, the output layer belongs with the identity activation, and the returned model carries no Accuracy field, there being no labels to count. fcnntrain returns the trained model, Mdl , as a structure containing the following fields: LayerWeights : A cell array with each element containing a matrix with the Weights and Biases of each layer including the output layer. Activations : A numeric row vector of integer values defining the activation functions to be used at each layer including the output layer. Accuracy : The prediction accuracy at each iteration during the neural network model’s training process. Absent under regression. Loss : The loss value recorded at each iteration during the neural network model’s training process. Installation Note: in order to support parallel processing on MacOS, users have to manually add support for OpenMP by adding the following flags to CFLAGS and CXXFLAGS prior to installing the statistics package: setenv ("CPPFLAGS", "-I/opt/homebrew/opt/libomp/include -Xclang -fopenmp") See also: fcnnpredict, fitcnet, ClassificationNeuralNetwork # name: # type: sq_string # elements: 1 # length: 39 Train a fully connected Neural Network. # name: # type: sq_string # elements: 1 # length: 13 gamboostinter # name: # type: sq_string # elements: 1 # length: 1643 statistics: Mdl = gamboostinter ( X , Y , F0 , Method , Pairs , NumTrees , LearnRate , MaxNumSplits ) Boost trees over selected pairs of predictors. Mdl = gamboostinter (…) fits the interaction phase of a generalized additive model, continuing from the additive prediction the predictor phase left rather than refitting it. It is used by ClassificationGAM and RegressionGAM , and it is not meant to be called directly. X is an NxP numeric matrix of predictors and Y the Nx1 response, as gamboosttrain takes them. F0 is the Nx1 additive prediction of the predictor phase. The interaction phase starts from it, so its deviance is where this phase begins. Method selects what is boosted, 1 the logistic deviance and 2 the squared error. Pairs is an Mx2 matrix of predictor index pairs, one-based and within range. Choosing them is the caller’s business; see gamboostpairs . NumTrees , LearnRate and MaxNumSplits are the interaction phase’s own budget, initial step and split limit. Mdl is a structure with the following fields. PairBinEdges , a 1xP cell of the coarse cut points the surfaces are held on. Interactions are binned coarser than main effects: a tree limited to MaxNumSplits splits carves no more regions than that, so a finer grid buys nothing and costs memory in every pair. PairValues , a 1xM cell of matrices, one value per cell of the pair’s grid. Intercept , the constant the recentred surfaces gave up. Add it to the intercept of the predictor phase. NumTrees , ReasonForTermination , Deviance and Residuals , as gamboosttrain reports them. See also: gamboosttrain, gamboostpairs, gamboostpredict # name: # type: sq_string # elements: 1 # length: 55 F0, Method, Pairs, NumTrees, LearnRate, @ MaxNumSplits) # name: # type: sq_string # elements: 1 # length: 13 gamboostpairs # name: # type: sq_string # elements: 1 # length: 1497 statistics: S = gamboostpairs ( X , R ) Score every pair of predictors for an interaction. S = gamboostpairs ( X , R ) lays the residuals R of a fitted additive model on the coarse grid of each pair of columns of X and returns the two-way analysis of variance F ratio testing what only the cells explain. It is used to rank candidate interactions for ClassificationGAM and RegressionGAM , and it is not meant to be called directly. The p -values are deliberately not computed here. Turning F into a probability needs fcdf , which the package already ships and which is verified against MATLAB, so the caller applies it rather than a second implementation being carried in the compiled engine. Which pairs to keep is policy and belongs beside that. X is an NxP numeric matrix of predictors, and P must be at least 2 for any pair to exist. R is the Nx1 residual vector of the additive fit. S is a structure with the following fields, one row per pair, ordered as nchoosek orders them. Pairs , the Mx2 matrix of predictor index pairs. F , the Mx1 vector of F ratios. A pair with too few observations, no spare degrees of freedom or no within-cell scatter scores 0 . DF1 and DF2 , the Mx1 numerator and denominator degrees of freedom. BinEdges , a 1xP cell of the coarse cut points each predictor was laid on. The grid is fixed at eight equal-frequency bins, which is what MATLAB reports for pair detection at every sample size. See also: gamboosttrain, gamboostpredict, fcdf, ClassificationGAM # name: # type: sq_string # elements: 1 # length: 50 Score every pair of predictors for an interaction. # name: # type: sq_string # elements: 1 # length: 15 gamboostpredict # name: # type: sq_string # elements: 1 # length: 1622 statistics: Y = gamboostpredict ( BinEdges , ShapeValues , X , Intercept ) statistics: Y = gamboostpredict (…, Link ) statistics: Y = gamboostpredict (…, Link , PairBinEdges , PairValues , Pairs ) Predict from a generalized additive model of boosted trees. BinEdges is a 1xP cell of row vectors and ShapeValues a 1xP cell of column vectors, as gamboosttrain returns them in the fields of the same names. Each shape function is a step function over its predictor’s bins, so a term is evaluated by a lookup. X is an NxP numeric matrix with one column per additive term, and a count that does not match is an error. A missing value contributes nothing from that term rather than making the whole prediction NaN , which is what a tree does with a value it cannot place. A value outside the range the term was fitted over falls in the nearest bin, so a shape function is constant beyond its data rather than extrapolated. Intercept is the model’s constant term. Link , if given, selects what the additive prediction is mapped through: 0 returns it as it stands and 1 takes it as a log-odds, returning the Nx2 matrix of class probabilities whose second column is the logistic function of it. The default is 0 . PairBinEdges , PairValues and Pairs carry the interaction terms, as gamboostinter returns the first two and as it was given the third. Each pair contributes the value of the cell its two predictors fall in, and a pair with either predictor missing contributes nothing. All three must be given together or none of them. See also: gamboosttrain, gampredict, ClassificationGAM, RegressionGAM # name: # type: sq_string # elements: 1 # length: 173 ShapeValues, X, Intercept) @deftypefnx {statistics} {Y =} gamboostpredict (, Link) @deftypefnx {statistics} {Y =} gamboostpredict (, Link, @ PairBinEdges, PairValues, Pairs) # name: # type: sq_string # elements: 1 # length: 13 gamboosttrain # name: # type: sq_string # elements: 1 # length: 2676 statistics: Mdl = gamboosttrain ( X , Y , Method , NumTrees , LearnRate , MaxNumSplits ) statistics: Mdl = gamboosttrain ( X , Y , Method , NumTrees , LearnRate , MaxNumSplits , Verbose , NumPrint , F0 ) statistics: Mdl = gamboosttrain (…, Verbose , NumPrint ) Fit a generalized additive model of boosted trees. Mdl = gamboosttrain ( X , Y , Method , NumTrees , LearnRate , MaxNumSplits ) boosts one tree per column of X in each round and returns the additive model as a structure. It is the fitting engine shared by ClassificationGAM and RegressionGAM , and it is not meant to be called directly. X is an NxP numeric matrix of predictors. A missing value is not an error: the observation takes no part in the affected predictor’s trees and that term contributes nothing to its prediction. Y is an Nx1 numeric vector of responses. For Method 1 it must hold zeros and ones. Method selects what is boosted: 1 the logistic deviance, as a classifier is fitted, and 2 the squared error, as a regression is fitted. NumTrees is the number of rounds, each fitting one tree per predictor. It is a budget rather than a count: a fit that stops improving ends earlier and says so. LearnRate is the step a round starts at. A round that fails to earn its place is retried at half the step, so this is an initial value and not a fixed one. MaxNumSplits is the largest number of splits any one tree may make. 1 is a stump. Verbose , if greater than zero, prints a trace of the fit, and NumPrint how often: the first round and then every NumPrint rounds. The RelTol column is the relative improvement the round bought, which is what the stopping rule reads. MATLAB prints a column under the same heading holding a quantity of its own that cannot be derived from anything else it reports, so the two are not comparable. Mdl is a structure with the following fields. Intercept , the constant term the additive terms are added to. For a classifier it is fitted rather than fixed: it is seeded with the log-odds of the response mean and then collects the constant each shape function gives up when it is recentred. For a regression it is the response mean and stays there. BinEdges , a 1xP cell of row vectors, the cut points each predictor was binned at. ShapeValues , a 1xP cell of column vectors, one value per bin. A shape function is a step function, so this is the whole of it however many trees produced it. NumTrees , the number of rounds actually performed. ReasonForTermination , why fitting stopped. Deviance , the deviance at the last round. Residuals , the Nx1 residual vector at the last round. See also: gamboostpredict, gamtrain, ClassificationGAM, RegressionGAM # name: # type: sq_string # elements: 1 # length: 237 Method, NumTrees, LearnRate, MaxNumSplits) @deftypefnx {statistics} {Mdl =} gamboosttrain (X, Y, @ Method, NumTrees, LearnRate, MaxNumSplits, @ Verbose, NumPrint, F0) @deftypefnx {statistics} {Mdl =} gamboosttrain (, Verbose, @ NumPrint) # name: # type: sq_string # elements: 1 # length: 10 gampredict # name: # type: sq_string # elements: 1 # length: 1345 statistics: yFit = gampredict ( Parameters , X , Intercept ) statistics: score = gampredict ( Parameters , X , Intercept , Link ) Evaluate a generalized additive model on new data. yFit = gampredict ( Parameters , X , Intercept ) adds the intercept to the sum of the additive terms evaluated at each row of X and returns the Nx1 result. It is the prediction engine shared by ClassificationGAM and RegressionGAM , and it is not meant to be called directly. Parameters is a 1xP structure array of piecewise polynomials, as returned by gamtrain in the field of the same name. X is an NxP numeric matrix with one column per additive term, and a count that does not match is an error. A model carrying interaction terms must therefore be given the augmented matrix, not the predictors alone. A missing value predicts NaN , since no term of the model is defined at it. A value outside the range the term was fitted over is extrapolated from the nearest piece, as ppval extrapolates. Intercept is the model’s constant term. Link , if given, selects what the additive prediction is mapped through: 0 returns it as it stands and 1 takes it as a log-odds, returning the Nx2 matrix of class probabilities whose second column is the logistic function of it. The default is 0 . See also: gamtrain, ClassificationGAM, RegressionGAM, fitcgam, fitrgam # name: # type: sq_string # elements: 1 # length: 94 X, Intercept) @deftypefnx {statistics} {score =} gampredict (Parameters, @ X, Intercept, Link) # name: # type: sq_string # elements: 1 # length: 8 gamtrain # name: # type: sq_string # elements: 1 # length: 2136 statistics: Mdl = gamtrain ( X , Y , Knots , Order , Method , Inter , P1 , P2 ) Fit a generalized additive model of smoothing splines. Mdl = gamtrain ( X , Y , Knots , Order , Method , Inter , P1 , P2 ) fits one univariate spline per column of X and returns the additive model as a structure. It is the fitting engine shared by ClassificationGAM and RegressionGAM , and it is not meant to be called directly. X is an NxP numeric matrix of predictors. A missing value is not an error: the observation is left out of the affected predictor’s spline and its prediction from that term is NaN . Y is an Nx1 numeric vector of responses. For Method 1 it must hold zeros and ones. Knots is a 1xP vector giving the number of spline pieces for each predictor, and Order a 1xP vector giving the degree of the polynomial on each piece. A spline of K pieces and degree D spans a space of K + D dimensions. Method selects the fitting scheme: 1 boosts the log-odds by gradient descent, as a classifier is fitted, and 2 backfits the partial residuals, as a regression is fitted. Inter is the intercept the fit starts from: a proportion for Method 1, which is stored as its log-odds, and the response mean for Method 2. A proportion of zero or one is not an error: its log-odds is infinite, the gradient is zero throughout and every additive term stays at zero, which is the fit a single-class response has. P1 and P2 are the scheme’s two parameters. For Method 1 they are the learning rate and the number of boosting iterations; for Method 2 the convergence tolerance and the maximum number of backfitting cycles. Mdl is a structure with the following fields. Intercept , the constant term the additive terms are added to. Parameters , a 1xP structure array of piecewise polynomials in the form ppval consumes, one per predictor. Iterations , the number of iterations performed. Residuals , the Nx1 residual vector at the last iteration. RSS , the scalar residual sum of squares for Method 1 and the 1xP per-term criterion the backfitting stops on for Method 2. See also: gampredict, ClassificationGAM, RegressionGAM, fitcgam, fitrgam # name: # type: sq_string # elements: 1 # length: 36 Knots, Order, Method, Inter, P1, P2) # name: # type: sq_string # elements: 1 # length: 10 libsvmread # name: # type: sq_string # elements: 1 # length: 267 statistics: [ labels , data ] = libsvmread ( filename ) This function reads the labels and the corresponding instance_matrix from a LIBSVM data file and stores them in labels and data respectively. These can then be used as inputs to svmtrain or svmpredict function. # name: # type: sq_string # elements: 1 # length: 141 This function reads the labels and the corresponding instance_matrix from a LIBSVM data file and stores them in labels and data respectively. # name: # type: sq_string # elements: 1 # length: 11 libsvmwrite # name: # type: sq_string # elements: 1 # length: 229 statistics: libsvmwrite ( filename , labels , data ) This function saves the labels and the corresponding instance_matrix in a file specified by filename . data must be a sparse matrix. Both labels , data must be of double type. # name: # type: sq_string # elements: 1 # length: 101 This function saves the labels and the corresponding instance_matrix in a file specified by filename. # name: # type: sq_string # elements: 1 # length: 10 svmpredict # name: # type: sq_string # elements: 1 # length: 2233 statistics: predicted_label = svmpredict ( labels , data , model ) statistics: predicted_label = svmpredict ( labels , data , model , "libsvm_options") statistics: [ predicted_label , accuracy , decision_values ] = svmpredict ( labels , data , model , "libsvm_options") statistics: [ predicted_label , accuracy , prob_estimates ] = svmpredict ( labels , data , model , "libsvm_options") This function predicts new labels from a testing instance matrix based on an SVM model created with svmtrain . labels : An m by 1 vector of prediction labels. If labels of test data are unknown, simply use any random values. (type must be double) data : An m by n matrix of m testing instances with n features. It can be dense or sparse. (type must be double) model : The output of svmtrain function. libsvm_options : A string of testing options in the same format as that of LIBSVM. libsvm_options : -b : probability_estimates; whether to predict probability estimates. 0 return decision values. (default) 1 return probability estimates. -q : quiet mode. (no outputs) The svmpredict function has three outputs. The first one, predicted_label , is a vector of predicted labels. The second output, accuracy , is a vector including accuracy (for classification), mean squared error, and squared correlation coefficient (for regression). The third is a matrix containing decision values or probability estimates (if -b 1 ’ is specified). If k is the number of classes in training data, for decision values, each row includes results of predicting k(k-1)/2 binary-class SVMs. For classification, k = 1 is a special case. Decision value +1 is returned for each testing instance, instead of an empty vector. For probabilities, each row contains k values indicating the probability that the testing instance is in each class. Note that the order of classes here is the same as Label field in the model structure. Note on LIBSVM 3.36 Update : This implementation is based on LIBSVM 3.36 (2025) and now supports probability estimates for One-Class SVM ( -s 2 ) when combined with the probability flag ( -b 1 ). For One-Class SVM, the prob_estimates output is a single column vector containing the probability of the instance being an inlier. # name: # type: sq_string # elements: 1 # length: 109 This function predicts new labels from a testing instance matrix based on an SVM model created with svmtrain. # name: # type: sq_string # elements: 1 # length: 8 svmtrain # name: # type: sq_string # elements: 1 # length: 3279 statistics: model = svmtrain ( labels , data , "libsvm_options") This function trains an SVM model based on known labels and their corresponding data which comprise an instance matrix. labels : An m by 1 vector of prediction labels. (type must be double) data : An m by n matrix of m testing instances with n features. It can be dense or sparse. (type must be double) libsvm_options : A string of testing options in the same format as that of LIBSVM. libsvm_options : -s : svm_type; set type of SVM (default 0) 0 C-SVC (multi-class classification) 1 nu-SVC (multi-class classification) 2 one-class SVM 3 epsilon-SVR (regression) 4 nu-SVR (regression) -t : kernel_type; set type of kernel function (default 2) 0 linear: u’*v 1 polynomial: (gamma × u' × v + coef0) ^ degree 2 radial basis function: exp(-gamma × |u-v| ^ 2) 3 sigmoid: tanh(gamma × u' × v + coef0) 4 precomputed kernel (kernel values in training_instance_matrix) -d : degree; set degree in kernel function (default 3) -g : gamma; set gamma in kernel function (default 1/num_features) -r : coef0; set coef0 in kernel function (default 0) -c : cost; set the parameter C of C-SVC, epsilon-SVR, and nu-SVR (default 1) -n : nu; set the parameter nu of nu-SVC, one-class SVM, and nu-SVR (default 0.5) -p : epsilon; set the epsilon in loss function of epsilon-SVR (default 0.1) -m : cachesize; set cache memory size in MB (default 100) -e : epsilon; set tolerance of termination criterion (default 0.001) -h : shrinking; whether to use the shrinking heuristics, 0 or 1 (default 1) -b : probability_estimates; whether to train a SVC or SVR model for probability estimates, 0 or 1 (default 0) -w : weight; set the parameter C of class i to weight*C, for C-SVC (default 1) -v : n; n-fold cross validation mode -q : quiet mode (no outputs) The function svmtrain function returns a model structure which can be used for future prediction and it contains the following fields: Parameters : parameters nr_class : number of classes; = 2 for regression/one-class svm totalSV : total #SV rho : -b of the decision function(s) wx+b Label : label of each class; empty for regression/one-class SVM sv_indices : values in [1,...,num_training_data] to indicate SVs in the training set ProbA : pairwise probability information; empty if -b 0 or in one-class SVM ProbB : pairwise probability information; empty if -b 0 or in one-class SVM ProbDensityMarks : density marks for one-class SVM probability estimates; empty if -b 0 or not one-class SVM. nSV : number of SVs for each class; empty for regression/one-class SVM sv_coef : coefficients for SVs in decision functions SVs : support vectors If you do not use the option -b 1 , ProbA and ProbB are empty matrices. If the ’-v’ option is specified, cross validation is conducted and the returned model is just a scalar: cross-validation accuracy for classification and mean-squared error for regression. Note on LIBSVM 3.36 Update : This implementation is based on LIBSVM 3.36 (2025) and now supports probability estimates for One-Class SVM ( -s 2 ) when combined with the probability flag ( -b 1 ). For One-Class SVM, the prob_estimates output is a single column vector containing the probability of the instance being an inlier. # name: # type: sq_string # elements: 1 # length: 119 This function trains an SVM model based on known labels and their corresponding data which comprise an instance matrix. statistics-release-1.9.2/src/editDistance.cc000066400000000000000000000562041524624707500210540ustar00rootroot00000000000000/* Copyright (C) 2024 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include using namespace std; struct UniqueVecOut { ColumnVector IA; ColumnVector IC; Cell IA_c; }; // Function for computing the minimum of three integer values int minimum (int a, int b, int c) { int min = a; if (b < min) min = b; if (c < min) min = c; return min; } // Function for computing the Levenshtein distance between two strings int LevensDistStr (const string& s1, const string& s2) { const size_t rows = s1.length(); const size_t cols = s2.length(); vector curr(cols+1, 0); int prev; // Prepopulate 1st column for (size_t j = 0; j <= cols; j++) { curr[j] = j; } // Compute all other elements in distance matrix for (size_t i = 1; i <= rows; i++) { prev = curr[0]; curr[0] = i; for (size_t j = 1; j <= cols; j++) { int temp = curr[j]; if (s1[i - 1] == s2[j - 1]) { curr[j] = prev; } else { curr[j] = 1 + minimum (prev, curr[j - 1], curr[j]); } prev = temp; } } return curr[cols]; } // Function for computing the Levenshtein distance between two documents int LevensDistDoc (const Cell& d1, const Cell& d2) { const size_t rows = d1.numel(); const size_t cols = d2.numel(); vector curr(cols+1, 0); int prev; // Prepopulate 1st column for (size_t j = 0; j <= cols; j++) { curr[j] = j; } // Compute all other elements in distance matrix for (size_t i = 1; i <= rows; i++) { prev = curr[0]; curr[0] = i; for (size_t j = 1; j <= cols; j++) { int temp = curr[j]; if (d1(i - 1).string_value() == d2(i - 1).string_value()) { curr[j] = prev; } else { curr[j] = 1 + minimum (prev, curr[j - 1], curr[j]); } prev = temp; } } return curr[cols]; } // Transform a distance triu matrix to a boolean triu matrix boolMatrix double2bool (const Matrix& D, const int& minDist) { const size_t sz = D.rows(); boolMatrix Bmat(sz, sz); for (size_t i = 0; i < sz - 1; i++) { Bmat(i,i) = true; for (size_t j = i + 1; j < sz; j++) { if (D(i,j) <= minDist) { Bmat(i,j) = true; Bmat(j,i) = false; } else { Bmat(i,j) = false; Bmat(j,i) = false; } } } Bmat(sz - 1,sz - 1) = true; return Bmat; } // Transform a distance triu matrix to a distance vector Matrix triu2Dvec (const Matrix& D) { const size_t szA = D.rows(); const size_t sz = szA * (szA - 1) / 2; octave_idx_type idx = 0; Matrix Dvec(sz, 1); for (size_t i = 0; i < szA - 1; i++) { for (size_t j = i + 1; j < szA; j++) { Dvec(idx++,0) = D(i,j); } } return Dvec; } // Compute unique indexing IA cell of vectors vector> IAcellvec (const boolMatrix& B) { const size_t rows = B.rows(); const size_t cols = B.columns(); vector> IAcell; for (size_t i = 0; i < rows; i++) { vector IA_cIdx; IA_cIdx.push_back(i); for (size_t j = i + 1; j < cols; j++) { if (B(i,j)) { IA_cIdx.push_back(j); } } IAcell.push_back(IA_cIdx); } return IAcell; } // Transform to IAcellvec to Cell Cell IA2cell (const vector>& IAc, const vector& IAv) { const size_t sz_v = IAv.size(); Cell IA(sz_v, 1); for (size_t i = 0; i < sz_v; i++) { int idx = IAv[i]; const size_t sz_c = IAc[idx].size(); Matrix IAidx(sz_c, 1); for (size_t j = 0; j < sz_c; j++) { IAidx(j,0) = IAc[idx][j] + 1; } IA(i,0) = IAidx; } return IA; } // Compute unique indexing IA vector vector IAvector (const vector>& IAcell) { vector IA; vector IA_done; for (size_t i = 0; i < IAcell.size(); i++) { for (size_t j = 0; j < IAcell[i].size(); j++) { if (binary_search(IA_done.begin(), IA_done.end(), IAcell[i][j])) { break; } else { if (j == 0) { IA.push_back(IAcell[i][j]); } else { IA_done.push_back(IAcell[i][j]); } } } sort (IA_done.begin(), IA_done.end()); } return IA; } // Transform to IAvector to Matrix Matrix IA2mat (const vector& IAv) { const size_t sz_v = IAv.size(); Matrix IA(sz_v, 1); for (size_t i = 0; i < sz_v; i++) { IA(i,0) = IAv[i] + 1; } return IA; } // Compute unique indexing IA vector Matrix ICvector (const vector>& IAc, const size_t& szA) { Matrix IC(szA, 1); vector IC_done; for (size_t i = 0; i < IAc.size(); i++) { for (size_t j = 0; j < IAc[i].size(); j++) { if (binary_search(IC_done.begin(), IC_done.end(), IAc[i][j])) { break; } else { octave_idx_type idx = IAc[i][j]; IC(idx,0) = i + 1; IC_done.push_back(IAc[i][j]); } } } return IC; } // Functionality for uniquetol octave_value_list uniquetol (const int& nargout, const Cell& A, const Matrix& D, const int& minDist, const bool& OutputAllIndices) { octave_value_list retval (nargout); boolMatrix B = double2bool (D, minDist); vector> IAc = IAcellvec (B); vector IAv = IAvector (IAc); // Build cellstr with unique elements Cell C(IAv.size(), 1); if (A.iscellstr()) { for (size_t i = 0; i < IAv.size(); i++) { C(i,0) = A(IAv[i]).string_value(); } } else { for (size_t i = 0; i < IAv.size(); i++) { C(i,0) = A.elem(IAv[i]); } } retval(0) = C; // Build IA vector output if (nargout > 1 && OutputAllIndices) { retval(1) = IA2cell (IAc, IAv); } else if (nargout > 1) { retval(1) = IA2mat (IAv); } // Build IC vector output if (nargout > 2) { retval(2) = ICvector (IAc, A.numel()); } return retval; } // Expand a cell scalar to a cell vector Cell expand (const Cell& IN, const size_t& sz) { //octave_idx_type sz = static_cast(sz_out); Cell OUT(sz, 1); for (size_t i = 0; i < sz; i++) { OUT(i,0) = IN.elem(0); } return OUT; } DEFUN_DLD(editDistance, args, nargout, "-*- texinfo -*-\n\ @deftypefn {statistics} {@var{d} =} editDistance (@var{str})\n\ @deftypefnx {statistics} {@var{d} =} editDistance (@var{doc})\n\ @deftypefnx {statistics} {@var{C} =} editDistance (@dots{}, @var{minDist})\n\ @deftypefnx {statistics} {[@var{C}, @var{IA}, @var{IC}] =} editDistance @\ (@dots{}, @var{minDist})\n\ @deftypefnx {statistics} {[@var{C}, @var{IA}, @var{IC}] =} editDistance @\ (@dots{}, @var{minDist}, @qcode{\"OutputAllIndices\"}, @var{value})\n\ @deftypefnx {statistics} {@var{d} =} editDistance (@var{str1}, @var{str2})\n\ @deftypefnx {statistics} {@var{d} =} editDistance (@var{doc1}, @var{doc2})\n\ \n\ \n\ Compute the edit (Levenshtein) distance between strings or documents. \ \n\n\ @code{@var{d} = editDistance (@var{str})} takes a cell array of character \ vectors and computes the Levenshtein distance between each pair of strings in \ @var{str} as the lowest number of grapheme insertions, deletions, and \ substitutions required to convert string @qcode{@var{str}@{1@}} to string \ @qcode{@var{str}@{2@}}. If @var{str} is a @qcode{cellstr} vector with \ @math{N} elements, the returned distance @var{d} is an @math{(N * (N-1)) / 2)} \ column vector of doubles. If @var{str} is an array (that is @code{all (size \ (str) > 1) = true}), then it is transformed to a column vector as in \ @code{str = str(:)}. @code{editDistance} expects @var{str} to be a column \ vector, if it is row vector, it is transformed to a column vector.\n\n\ \ @code{@var{d} = editDistance (@var{doc})} can also take a cell array \ containing cell arrays of character vectors, in which case each element of \ @var{doc} is regarded as a document, and the character vector in each element \ of the cell string array is regarded a token. @code{editDistance} computes \ the Levenshtein distance between each pair of cell elements in @var{doc} as \ the lowest number of token insertions, deletions, and substitutions required \ to convert document @qcode{@var{doc}@{1@}} to document @qcode{@var{doc}@{2@}}. \ If @var{doc} is a @qcode{cell} vector with @math{N} elements, the distance \ @var{d} is an @math{(N * (N-1)) / 2)} column vector of doubles. If @var{doc} \ is an array (that is @code{all (size (doc) > 1) = true}), then it is converted \ to a column vector as in @code{doc = doc(:)}.\n\n\ \ @code{@var{C} = editDistance (@dots{}, @var{minDist})} specifies a minimum \ distance, @var{minDist}, which is regarded as a similarity threshold between \ each pair of strings or documents, defined in the previous syntaxes. In this \ case, @code{editDistance} resembles the functionality of the @code{uniquetol} \ function and returns the unique strings or documents that are similar up to \ @var{minDist} distance. @var{C} is either a cellstring array or a cell array \ of cellstrings, depending on the first input argument.\n\n\ \ @code{[@var{C}, @var{IA}, @var{IC}] = editDistance (@dots{}, @var{minDist})} \ also returns index vectors @var{IA} and @var{IC}. Assuming @var{A} contains \ either strings @var{str} or documents @var{doc} as defined above, @var{IA} \ is a column vector of indices to the first occurrence of similar elements such \ that @qcode{@var{C} = @var{A}(@var{IA})}, and @var{IC} is a column vector of \ indices such that @qcode{@var{A} ~ @var{C}(@var{IC})} where @qcode{~} means \ that the strings or documents are within the specified distance @var{minDist} \ of each other.\n\n\ \ @code{[@var{C}, @var{IA}, @var{IC}] = editDistance (@dots{}, @var{minDist}, \ @qcode{\"OutputAllIndices\"}, @var{value})} specifies the type of the second \ output index @var{IA}. @var{value} must be a logical scalar. When set to \ @code{true}, @var{IA} is a cell array containing the vectors of indices for \ ALL elements in @var{A} that are within the specified distance @var{minDist} \ of each other. Each cell in @var{IA} corresponds to a value in @var{C} and \ the values in each cell correspond to locations in @var{A}. If @var{value} is \ set to @code{false}, then @var{IA} is returned as an index vector described in \ the previous syntax.\n\n\ \ @code{@var{d} = editDistance (@var{str1}, @var{str2})} can also take two \ character vectors, @var{str1} and @var{str2} and compute the Levenshtein \ distance @var{d} as the lowest number of grapheme insertions, deletions, and \ substitutions required to convert @var{str1} to @var{str2}. @var{str1} and \ @var{str2} may also be cellstring arrays, in which case the pairwise distance \ is computed between @qcode{@var{str1}@{n@}} and @qcode{@var{str1}@{n@}}. The \ cellstring arrays must be of the same size or scalars, in which case the \ scalar is expanded to the size of the other cellstring input. The returned \ distance @var{d} is a column vector with the same number of elements as the \ cellstring arrays. If @var{str1} or @var{str2} is an array, then it is \ transformed to a column vector. @code{editDistance} expects both @var{str1} \ and @var{str2} to be a column vectors, if not, they are transformed into \ column vectors.\n\n\ \ @code{@var{d} = editDistance (@var{doc1}, @var{doc2})} can also take two cell \ array containing cell arrays of character vectors, in which case each element \ of @var{doc1} and @var{doc2} is regarded as a document, and the character \ vector in each element of the cell string array is regarded a token. \ @code{editDistance} computes the pairwise Levenshtein distance between the \ of cell elements in @var{doc1} and @var{doc2} as the lowest number of token \ insertions, deletions, and substitutions required to convert document \ @qcode{@var{doc1}@{n@}} to document @qcode{@var{doc1}@{n@}}.\n\n\ @end deftypefn") { int nargin = args.length(); // Add default options bool OutputAllIndices = false; // Parse Name-Value paired arguments if (nargin > 2 && args(nargin-2).is_string()) { string ParamName = "OutputAllIndices"; if (args(nargin - 2).string_value() == ParamName) { const int idx = nargin - 1; if (args(idx).islogical() && args(idx).numel() == 1) { const boolMatrix tmp = args(idx).bool_matrix_value(); OutputAllIndices = tmp(0); } else { error ("editDistance: value for OutputAllIndices " "must be a logical scalar."); } nargin--; nargin--; } } // Check for invalid number of input arguments if (nargin > 3) { error ("editDistance: too many input arguments."); } // Check for last argument being numeric (minDist) int minDist; bool doMinDist; if (nargin > 1 && args(nargin-1).isnumeric()) { // Check minDist input argument if (args(nargin -1 ).numel() != 1) { error ("editDistance: minDist must be a scalar value."); } Matrix tmp = args(nargin - 1).matrix_value(); if (tmp(0,0) < 0 || floor (tmp(0,0)) != tmp(0,0)) { error ("editDistance: minDist must be a nonnegative integer."); } minDist = static_cast(tmp(0,0)); doMinDist = true; nargin--; } else { doMinDist = false; } // Check for invalid number of output arguments if ((nargout > 3 && doMinDist) || (nargout > 1 && ! doMinDist)) { error ("editDistance: too many output arguments."); } // Check cases of string arguments octave_value_list retval (nargout); if (nargin == 1) { if (args(0).iscellstr()) { // Get cellstr input argument const Cell strA = args(0).cellstr_value(); size_t szA = strA.numel(); // For scalar input return distance to itself, i.e. 0 if (szA == 1) { retval(0) = double (0); return retval; } // Compute the edit distance Matrix D(szA, szA); #pragma omp parallel { #pragma omp parallel for for (size_t i = 0; i < szA - 1; i++) { D(i,i) = 0; string s1 = strA(i).string_value(); for (size_t j = i + 1; j < szA; j++) { D(i,j) = LevensDistStr (s1, strA(j).string_value()); } } D(szA - 1,szA - 1) = 0; } // If minDist is given, change functionality from 'pdist' to 'uniquetol' if (doMinDist) { retval = uniquetol (nargout, strA, D, minDist, OutputAllIndices); } else { // Transform to distance vector retval(0) = triu2Dvec (D); } return retval; } else if (args(0).iscell()) { // Get cell input argument const Cell docA = args(0).cell_value(); size_t szA = docA.numel(); // Check that all cell elements contain cellstring arrays for (size_t i = 0; i < szA; i++) { Cell tmp = docA.elem(i); if (! tmp.iscellstr()) { error ("editDistance: tokenizedDocument " "must contain cellstr arrays."); } } // For scalar input return distance to itself, i.e. 0 if (szA == 1) { retval(0) = double (0); return retval; } // Compute the edit distance Matrix D(szA, szA); #pragma omp parallel { #pragma omp parallel for for (size_t i = 0; i < szA - 1; i++) { D(i,i) = 0; Cell d1 = docA.elem(i); for (size_t j = i + 1; j < szA; j++) { D(i,j) = LevensDistDoc (d1, docA.elem(j)); } } D(szA - 1,szA - 1) = 0; } // If minDist is given, change functionality from 'pdist' to 'uniquetol' if (doMinDist) { retval = uniquetol (nargout, docA, D, minDist, OutputAllIndices); } else { // Transform to distance vector retval(0) = triu2Dvec (D); } return retval; } else { error ("editDistance: STR1 must be a cellstr."); } } else if (nargin == 2) { if (args(0).iscellstr() && args(1).iscellstr()) { // Get cellstr input arguments Cell strA = args(0).cellstr_value(); Cell strB = args(1).cellstr_value(); // Check cellstr sizes match size_t szA = strA.numel(); size_t szB = strB.numel(); if (szA != 1 && szB != 1 && szA != szB) { error ("editDistance: cellstr input arguments size mismatch."); } // Preallocate the distance vector and expand as necessary size_t sz = szA; if (szA == 1 && szB != 1) { sz = szB; strA = expand (strA, sz); } else if (szA != 1 && szB == 1) { strB = expand (strB, sz); } Matrix D(sz, 1); // Compute the distance vector for (size_t i = 0; i < sz; i++) { D(i,0) = LevensDistStr (strA(i).string_value(), strB(i).string_value()); } retval(0) = D; } else if (args(0).iscell() && args(1).iscell()) { // Get cell input arguments Cell docA = args(0).cell_value(); Cell docB = args(1).cell_value(); // Check cell sizes match size_t szA = docA.numel(); size_t szB = docB.numel(); if (szA != 1 && szB != 1 && szA != szB) { error ("editDistance: cellstr input arguments size mismatch."); } // Check both cell arrays contain cellstring arrays for (size_t i = 0; i < szA; i++) { Cell tmp = docA.elem(i); if (! tmp.iscellstr()) { error ("editDistance: first tokenizedDocument " "does not contain cellstr arrays."); } } for (size_t i = 0; i < szB; i++) { Cell tmp = docB.elem(i); if (! tmp.iscellstr()) { error ("editDistance: second tokenizedDocument " "does not contain cellstr arrays."); } } // Preallocate the distance vector and expand as necessary int sz = szA; if (szA == 1 && szB != 1) { sz = szB; docA = expand (docA, sz); } else if (szA != 1 && szB == 1) { docB = expand (docB, sz); } Matrix D(sz, 1); // Compute the distance vector for (size_t i = 0; i < sz; i++) { D(i,0) = LevensDistDoc (docA.elem(i), docB.elem(i)); } retval(0) = D; } else if (args(0).is_string() && args(1).is_string()) { retval(0) = LevensDistStr (args(0).string_value(),args(1).string_value()); } else { error ("editDistance: STR1 and STR2 must be either strings or cellstr."); } } return retval; } /* %!error d = editDistance (1, 2, 3, 4); %!error ... %! [C, IA, IC, I] = editDistance ({"AS","SD","AD"}, 1); %!error ... %! [C, IA] = editDistance ({"AS","SD","AD"}); %!error ... %! d = editDistance ({"AS","SD","AD"}, [1, 2]); %!error ... %! d = editDistance ({"AS","SD","AD"}, -2); %!error ... %! d = editDistance ({"AS","SD","AD"}, 1.25); %!error ... %! d = editDistance ({"AS","SD","AD"}, {"AS","SD","AD"}, [1, 2]); %!error ... %! d = editDistance ({"AS","SD","AD"}, {"AS","SD","AD"}, -2); %!error ... %! d = editDistance ({"AS","SD","AD"}, {"AS","SD","AD"}, 1.25); %!error ... %! d = editDistance ("string1", "string2", [1, 2]); %!error ... %! d = editDistance ("string1", "string2", -2); %!error ... %! d = editDistance ("string1", "string2", 1.25); %!error ... %! d = editDistance ({{"string1", "string2"}, 2}); %!error ... %! d = editDistance ({{"string1", "string2"}, 2}, 2); %!error ... %! d = editDistance ([1, 2, 3]); %!error ... %! d = editDistance (["AS","SD","AD","AS"]); %!error ... %! d = editDistance (["AS","SD","AD"], 2); %!error ... %! d = editDistance (logical ([1,2,3]), {"AS","AS","AD"}); %!error ... %! d = editDistance ({"AS","SD","AD"}, logical ([1,2,3])); %!error ... %! d = editDistance ([1,2,3], {"AS","AS","AD"}); %!error ... %! d = editDistance ({1,2,3}, {"AS","SD","AD"}); %!error ... %! d = editDistance ({"AS","SD","AD"}, {1,2,3}); %!error ... %! d = editDistance ({"AS","SD","AD"}, {"AS", "AS"}); %!test %! d = editDistance ({"AS","SD","AD"}); %! assert_equal (d, [2; 1; 1]); %! assert_equal (class (d), "double"); %!test %! C = editDistance ({"AS","SD","AD"}, 1); %! assert_equal (iscellstr (C), true); %! assert_equal (C, {"AS";"SD"}); %!test %! [C, IA] = editDistance ({"AS","SD","AD"}, 1); %! assert_equal (class (IA), "double"); %! assert_equal (IA, [1;2]); %!test %! A = {"ASS"; "SDS"; "FDE"; "EDS"; "OPA"}; %! [C, IA] = editDistance (A, 2, "OutputAllIndices", false); %! assert_equal (class (IA), "double"); %! assert_equal (A(IA), C); %!test %! A = {"ASS"; "SDS"; "FDE"; "EDS"; "OPA"}; %! [C, IA] = editDistance (A, 2, "OutputAllIndices", true); %! assert_equal (class (IA), "cell"); %! assert_equal (C, {"ASS"; "FDE"; "OPA"}); %! assert_equal (A(IA{1}), {"ASS"; "SDS"; "EDS"}); %! assert_equal (A(IA{2}), {"FDE"; "EDS"}); %! assert_equal (A(IA{3}), {"OPA"}); %!test %! A = {"ASS"; "SDS"; "FDE"; "EDS"; "OPA"}; %! [C, IA, IC] = editDistance (A, 2); %! assert_equal (class (IA), "double"); %! assert_equal (A(IA), C); %! assert_equal (IC, [1; 1; 3; 1; 5]); %!test %! d = editDistance ({"AS","SD","AD"}, {"AS", "AD", "SE"}); %! assert_equal (d, [0; 1; 2]); %! assert_equal (class (d), "double"); %!test %! d = editDistance ({"AS","SD","AD"}, {"AS"}); %! assert_equal (d, [0; 2; 1]); %! assert_equal (class (d), "double"); %!test %! d = editDistance ({"AS"}, {"AS","SD","AD"}); %! assert_equal (d, [0; 2; 1]); %! assert_equal (class (d), "double"); %!test %! b = editDistance ("Octave", "octave"); %! assert_equal (b, 1); %! assert_equal (class (b), "double"); */ statistics-release-1.9.2/src/fcnn.cpp000066400000000000000000000571251524624707500176000ustar00rootroot00000000000000/* Copyright (C) 2024 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #if defined (_OPENMP) #include #define MY_OMP_SET_THREADS (omp_set_num_threads (this->n_threads)) #else #define MY_OMP_SET_THREADS #endif using namespace std; // Helper functions // Return random number between -1 and 1, drawn from Octave's generator rather // than the C library's. The two are unconnected, so a network seeded from // rand () ignored rand ('seed', s) and rng () entirely and every fit on the // same data returned the same model. double get_random () { return octave::rand::scalar () * 2 - 1; } // Select the uniform distribution for as long as the object is in scope, and // put back whatever the caller had selected. The distribution is global state // owned by the caller, and an error raised while a network is being built must // not leave it changed. class uniform_scope { public: uniform_scope () : saved (octave::rand::distribution ()) { octave::rand::uniform_distribution (); } ~uniform_scope () { octave::rand::distribution (this->saved); } private: std::string saved; }; // Compute accuracy of predicted samples during training double accuracy (vector predictions, vector labels) { double correct = 0.0; for (int i = 0; i < predictions.size (); i++) { if (predictions[i] == labels[i] - 1) { correct += 1.0; } } double accuracy = correct / (double) predictions.size (); return accuracy; } // Class definitions // Negative-side parameters of the leaky family. MathWorks uses the same two // values, leakyReluLayer defaulting to a scale of 0.01 and eluLayer to an // alpha of 1, the latter being what makes an ELU saturate at -alpha. Neither // is a fitted quantity and neither is reachable from the m-code. static const double LRELU_ALPHA = 0.01; static const double ELU_ALPHA = 1.0; // Half-width of the uniform range a layer's weights are drawn from. A // constant range ignores how many inputs each neuron sums, so the variance of // a pre-activation grows with the width of the layer feeding it: deep networks // then saturate a sigmoid or drive every ReLU unit negative, where it stays // with a gradient of exactly zero. Scaling by fan-in holds that variance // steady from layer to layer. A rectifier passes only half its input, so it // needs the wider He range; the symmetric activations take Glorot, which // accounts for the backward pass through the layer as well. double init_scale (int activation, int fan_in, int fan_out) { bool rectifier = (activation == 2 || activation == 5 || activation == 6 || activation == 7); if (rectifier) { return sqrt (6.0 / fan_in); } return sqrt (6.0 / (fan_in + fan_out)); } class Neuron { public: // constructors Neuron (int input_size, double scale); Neuron (int input_size); // destructor ~Neuron (); // methods double forward (vector inputs); void backward (vector last_input, double grad); void descend (double learning_rate); vector get_neuron (); void set_neuron (vector Wb_vector); void zero_gradient (); // Flat view, laid out as [weights, bias] to match get_neuron. It exists // so that a whole network can be handed to an optimizer as one vector and // set back from one, which the per-neuron accessors above cannot do // without copying the model on every trial step of a line search. int nparams () const; void pack (double *p) const; void unpack (const double *p); void pack_grad (double *p) const; // data vector weights; vector wgrad; double bias; double bgrad; // The gradient this neuron received from the sample being processed, as // distinct from bgrad, which accumulates it. Backpropagation into the // layer below needs the one and full-batch training needs the other, and // reading bgrad for both is only correct while the gradient is cleared // between samples. double delta; }; Neuron::Neuron (int input_size, double scale) { this->weights = vector (input_size); this->wgrad = vector (input_size, 0.0); this->bias = 0.01 * get_random (); this->bgrad = 0.0; this->delta = 0.0; for (int i = 0; i < input_size; i++) { this->weights[i] = scale * get_random (); } } // Parameters left at zero, for a model that is about to have them loaded. // Drawing them would take numbers from Octave's generator and move the // caller's random stream on every call to fcnnpredict. Neuron::Neuron (int input_size) { this->weights = vector (input_size); this->wgrad = vector (input_size, 0.0); this->bias = 0.0; this->bgrad = 0.0; this->delta = 0.0; } Neuron::~Neuron () {} double Neuron::forward (vector inputs) { double total = this->bias; for (int i = 0; i < inputs.size (); i++) { total += inputs[i] * this->weights[i]; } return total; } void Neuron::backward (vector last_input, double grad) { this->delta = grad; this->bgrad += grad; for (int i = 0; i < this->wgrad.size (); i++) { this->wgrad.at (i) = this->wgrad.at (i) + grad * last_input.at (i); } } void Neuron::descend (double learning_rate) { this->bias -= this->bgrad * learning_rate; for (int i = 0; i < this->weights.size (); i++) { this->weights.at (i) -= this->wgrad.at (i) * learning_rate; } } vector Neuron::get_neuron () { vector Wb_vector = this->weights; Wb_vector.push_back (this->bias); return Wb_vector; } void Neuron::set_neuron (vector Wb_vector) { int w_len = Wb_vector.size () - 1; for (int i = 0; i < w_len; i++) { this->weights.at (i) = Wb_vector[i]; } this->bias = Wb_vector[w_len]; } void Neuron::zero_gradient () { this->wgrad = vector (this->weights.size ()); this->bgrad = 0.0; } int Neuron::nparams () const { return this->weights.size () + 1; } void Neuron::pack (double *p) const { int n = this->weights.size (); for (int i = 0; i < n; i++) { p[i] = this->weights[i]; } p[n] = this->bias; } void Neuron::unpack (const double *p) { int n = this->weights.size (); for (int i = 0; i < n; i++) { this->weights[i] = p[i]; } this->bias = p[n]; } void Neuron::pack_grad (double *p) const { int n = this->wgrad.size (); for (int i = 0; i < n; i++) { p[i] = this->wgrad[i]; } p[n] = this->bgrad; } class DenseLayer { public: // constructors DenseLayer (int input_size, int output_size, int activation); DenseLayer (int input_size, int output_size); // destructor ~DenseLayer (); // methods vector forward (vector inputs); void backward (vector grad); void descend (double learning_rate); vector> get_layer (); void set_layer (vector> Wb_matrix); void zero_gradient (); // Flat view over every neuron of the layer, in neuron order. int nparams () const; void pack (double *p) const; void unpack (const double *p); void pack_grad (double *p) const; // data vector neurons; vector last_input; }; DenseLayer::DenseLayer (int input_size, int output_size, int activation) { // initialize neurons on a range set by the fan-in and the activation double scale = init_scale (activation, input_size, output_size); this->neurons = vector (); for (int i = 0; i < output_size; i++) { Neuron to_add = Neuron (input_size, scale); this->neurons.push_back (to_add); } } // Layer of zeroed neurons, for a model whose weights are about to be loaded. DenseLayer::DenseLayer (int input_size, int output_size) { this->neurons = vector (); for (int i = 0; i < output_size; i++) { this->neurons.push_back (Neuron (input_size)); } } DenseLayer::~DenseLayer () {} vector DenseLayer::forward (vector inputs) { this->last_input = inputs; vector outputs = vector (this->neurons.size()); for (int i = 0; i < this->neurons.size (); i++) { outputs[i] = this->neurons[i].forward (inputs); } return outputs; } void DenseLayer::backward (vector grad) { for (int i = 0; i < this->neurons.size (); i++) { this->neurons[i].backward (last_input, grad[i]); } } void DenseLayer::descend (double learning_rate) { for (int i = 0; i < this->neurons.size (); i++) { this->neurons[i].descend (learning_rate); } } vector> DenseLayer::get_layer () { vector> Wb_matrix; for (int i = 0; i < this->neurons.size (); i++) { vector WB_vector = this->neurons[i].get_neuron (); Wb_matrix.push_back (WB_vector); } return Wb_matrix; } void DenseLayer::set_layer (vector> Wb_matrix) { for (int i = 0; i < Wb_matrix.size (); i++) { this->neurons[i].set_neuron (Wb_matrix[i]); } } void DenseLayer::zero_gradient () { for (int i = 0; i < this->neurons.size (); i++) { this->neurons[i].zero_gradient (); } } int DenseLayer::nparams () const { int total = 0; for (size_t i = 0; i < this->neurons.size (); i++) { total += this->neurons[i].nparams (); } return total; } void DenseLayer::pack (double *p) const { for (size_t i = 0; i < this->neurons.size (); i++) { this->neurons[i].pack (p); p += this->neurons[i].nparams (); } } void DenseLayer::unpack (const double *p) { for (size_t i = 0; i < this->neurons.size (); i++) { this->neurons[i].unpack (p); p += this->neurons[i].nparams (); } } void DenseLayer::pack_grad (double *p) const { for (size_t i = 0; i < this->neurons.size (); i++) { this->neurons[i].pack_grad (p); p += this->neurons[i].nparams (); } } // The activation functions a layer can carry, and the names the m-code spells // them with. The mapping lives here because this is the only place the codes // mean anything: nothing outside the engine needs to know the numbers. enum activation_kind { ACT_LINEAR = 0, ACT_SIGMOID = 1, ACT_RELU = 2, ACT_TANH = 3, ACT_SOFTMAX = 4, ACT_LRELU = 5, ACT_ELU = 6, ACT_GELU = 7 }; struct activation_entry { const char *name; int code; }; // 'none' is MATLAB's name for the identity, which fitcnet has always // documented as available; 'linear' is this package's older spelling of the // same map. 'prelu' and 'lrelu' are one function here, the negative slope // being a constant rather than a fitted parameter. static const activation_entry ACTIVATION_TABLE[] = { { "linear", ACT_LINEAR }, { "none", ACT_LINEAR }, { "sigmoid", ACT_SIGMOID }, { "relu", ACT_RELU }, { "tanh", ACT_TANH }, { "softmax", ACT_SOFTMAX }, { "lrelu", ACT_LRELU }, { "prelu", ACT_LRELU }, { "elu", ACT_ELU }, { "gelu", ACT_GELU } }; // The code for NAME, matched without regard to case, or -1 if there is none. static int activation_code (const string& name) { string key = name; for (size_t i = 0; i < key.size (); i++) { key[i] = tolower (key[i]); } const int n = sizeof (ACTIVATION_TABLE) / sizeof (ACTIVATION_TABLE[0]); for (int i = 0; i < n; i++) { if (key == ACTIVATION_TABLE[i].name) { return ACTIVATION_TABLE[i].code; } } return -1; } // One code per layer, the output layer last. ACTS names the hidden layers, // either as one character vector applying to all NLAYERS of them or as a // cellstring naming them one by one; OUT_ACT names the output layer. CALLER // prefixes every message, so each front end reports under its own name. static RowVector activation_codes (const octave_value& acts, const octave_value& out_act, int nlayers, const string& caller) { RowVector codes (nlayers + 1); if (acts.is_string () && acts.rows () == 1) { int code = activation_code (acts.string_value ()); if (code < 0) { error ("%s: unsupported 'Activations' function: '%s'.", caller.c_str (), acts.string_value ().c_str ()); } for (int i = 0; i < nlayers; i++) { codes(i) = code; } } else if (acts.iscellstr ()) { Cell c = acts.cell_value (); if (c.numel () != nlayers) { error ("%s: 'Activations' does not match the number of layers.", caller.c_str ()); } for (int i = 0; i < nlayers; i++) { int code = activation_code (c.elem(i).string_value ()); if (code < 0) { error ("%s: unsupported 'Activations' function: '%s'.", caller.c_str (), c.elem(i).string_value ().c_str ()); } codes(i) = code; } } else { error ("%s: 'Activations' must be a character vector or a cellstring.", caller.c_str ()); } if (! (out_act.is_string () && out_act.rows () == 1)) { error ("%s: 'OutputLayerActivation' must be a character vector.", caller.c_str ()); } int code = activation_code (out_act.string_value ()); if (code < 0) { error ("%s: unsupported 'OutputLayerActivation' function: '%s'.", caller.c_str (), out_act.string_value ().c_str ()); } codes(nlayers) = code; return codes; } class ActivationLayer { public: // constructor ActivationLayer (int activation, int n_threads); // destructor ~ActivationLayer (); // methods vector forward (vector inputs); void backward (vector grad); void backward (DenseLayer &prev_layer); // data vector last_input; vector grad; vector last_output; private: int activation; int n_threads; }; ActivationLayer::ActivationLayer (int activation, int n_threads) { this->activation = activation; this->n_threads = n_threads; } ActivationLayer::~ActivationLayer () {} vector ActivationLayer::forward (vector inputs) { this->last_input = inputs; int layer_size = inputs.size (); if (layer_size < 1000) { this->n_threads = 1; } vector outputs = vector (layer_size); if (this->activation == 0) // 'Linear' { outputs = inputs; } else if (this->activation == 1) // Sigmoid function { MY_OMP_SET_THREADS; #pragma omp parallel { #pragma omp parallel for for (int i = 0; i < layer_size; i++) { outputs[i] = 1 / (1 + exp (-inputs[i])); } } } else if (this->activation == 2) // Rectified Linear Unit (ReLU) { MY_OMP_SET_THREADS; #pragma omp parallel { #pragma omp parallel for for (int i = 0; i < layer_size; i++) { outputs[i] = inputs[i] > 0 ? inputs[i] : 0; } } } else if (this->activation == 3) // Hyperbolic tangent (tanh) { MY_OMP_SET_THREADS; #pragma omp parallel { #pragma omp parallel for for (int i = 0; i < layer_size; i++) { double ex = exp (inputs[i]); double e_x = exp (-inputs[i]); outputs[i] = (ex - e_x) / (ex + e_x); } } } else if (this->activation == 4) // Softmax activation { double total = 0.0; double maxel = *max_element (inputs.begin (), inputs.end ()); for (int i = 0; i < layer_size; i++) { outputs[i] = exp (inputs[i] - maxel); total += outputs[i]; } for (int i = 0; i < layer_size; i++) { outputs[i] /= total; } } else if (this->activation == 5) // Parametric or Leaky ReLU { MY_OMP_SET_THREADS; #pragma omp parallel { #pragma omp parallel for for (int i = 0; i < layer_size; i++) { outputs[i] = inputs[i] >= 0 ? inputs[i] : inputs[i] * LRELU_ALPHA; } } } else if (this->activation == 6) // Exponential Linear Unit (ELU) { MY_OMP_SET_THREADS; #pragma omp parallel { #pragma omp parallel for for (int i = 0; i < layer_size; i++) { outputs[i] = inputs[i] >= 0 ? inputs[i] : (exp (inputs[i]) - 1) * ELU_ALPHA; } } } else if (this->activation == 7) // Gaussian Error Linear Unit (GELU) { MY_OMP_SET_THREADS; #pragma omp parallel { #pragma omp parallel for for (int i = 0; i < layer_size; i++) { // x * Phi(x), the standard normal CDF taken from erfc so that the // tails are accurate; the tanh form is an approximation of this. outputs[i] = inputs[i] * 0.5 * erfc (-inputs[i] * M_SQRT1_2); } } } this->last_output = outputs; return outputs; } void ActivationLayer::backward (vector chain_grad) { int layer_size = this->last_input.size (); if (layer_size < 1000) { this->n_threads = 1; } this->grad = vector (layer_size); if (this->activation == 0) // 'Linear' { this->grad = chain_grad; } else if (this->activation == 1) // Sigmoid function { MY_OMP_SET_THREADS; #pragma omp parallel { #pragma omp parallel for for (int i = 0; i < layer_size; i++) { this->grad[i] = this->last_output[i] * (1 - this->last_output[i]) * chain_grad[i]; } } } else if (this->activation == 2) // Rectified Linear Unit (ReLU) { MY_OMP_SET_THREADS; #pragma omp parallel { #pragma omp parallel for for (int i = 0; i < layer_size; i++) { this->grad[i] = this->last_input[i] > 0 ? chain_grad[i] : 0; } } } else if (this->activation == 3) // Hyperbolic tangent (tanh) { MY_OMP_SET_THREADS; #pragma omp parallel { #pragma omp parallel for for (int i = 0; i < layer_size; i++) { this->grad[i] = (1 - pow (this->last_output[i], 2)) * chain_grad[i]; } } } else if (this->activation == 4) // Softmax activation { // The Jacobian is diag (y) - y * y', so the product with the incoming // gradient is y .* (g - y' * g). Passing g through unchanged is only // right when softmax is paired with a cross-entropy loss, which this // implementation does not provide. double dot = 0.0; for (int i = 0; i < layer_size; i++) { dot += this->last_output[i] * chain_grad[i]; } for (int i = 0; i < layer_size; i++) { this->grad[i] = this->last_output[i] * (chain_grad[i] - dot); } } else if (this->activation == 5) // Parametric or Leaky ReLU { MY_OMP_SET_THREADS; #pragma omp parallel { #pragma omp parallel for for (int i = 0; i < layer_size; i++) { this->grad[i] = this->last_input[i] >= 0 ? chain_grad[i] : chain_grad[i] * LRELU_ALPHA; } } } else if (this->activation == 6) // Exponential Linear Unit (ELU) { MY_OMP_SET_THREADS; #pragma omp parallel { #pragma omp parallel for for (int i = 0; i < layer_size; i++) { this->grad[i] = this->last_input[i] >= 0 ? chain_grad[i] : chain_grad[i] * exp (this->last_input[i]) * ELU_ALPHA; } } } else if (this->activation == 7) // Gaussian Error Linear Unit (GELU) { // d/dx [x * Phi(x)] = Phi(x) + x * phi(x), both taken at the input static const double inv_sqrt_2pi = 0.3989422804014327; MY_OMP_SET_THREADS; #pragma omp parallel { #pragma omp parallel for for (int i = 0; i < layer_size; i++) { double x = this->last_input[i]; double cdf = 0.5 * erfc (-x * M_SQRT1_2); double pdf = inv_sqrt_2pi * exp (-0.5 * x * x); this->grad[i] = (cdf + x * pdf) * chain_grad[i]; } } } } void ActivationLayer::backward (DenseLayer &prev_layer) { // The gradient arriving here is W' * delta, where delta is the gradient at // each neuron of the layer that follows. Every neuron stores the delta of // the sample being processed, so it is read from there rather than // recovered by dividing the weight gradient by the input, which is // undefined wherever an activation output is zero. Reading the bias // gradient instead is only correct while it is cleared between samples, // which full-batch training does not do. The local derivative is // then applied by the overload above, so both paths share one definition of // every activation. int layer_size = this->last_input.size (); vector chain_grad = vector (layer_size, 0.0); for (int n = 0; n < prev_layer.neurons.size (); n++) { double delta = prev_layer.neurons[n].delta; for (int i = 0; i < layer_size; i++) { chain_grad[i] += prev_layer.neurons[n].weights[i] * delta; } } this->backward (chain_grad); } class MeanSquaredErrorLoss { public: MeanSquaredErrorLoss (); ~MeanSquaredErrorLoss (); double forward (vector inputs, vector targets); void backward (double grad); // data vector last_input; vector last_target; vector grad; }; MeanSquaredErrorLoss::MeanSquaredErrorLoss () {} MeanSquaredErrorLoss::~MeanSquaredErrorLoss () {} double MeanSquaredErrorLoss::forward (vector inputs, vector targets) { // we only need to calculate the loss for the target class this->last_input = inputs; this->last_target = targets; double total = 0; for (int i = 0; i < inputs.size (); i++) { total += pow (inputs[i] - targets[i], 2); } double loss = total; return loss; } void MeanSquaredErrorLoss::backward (double grad) { this->grad = vector (this->last_input.size ()); for (int i = 0; i < this->last_input.size (); i++) { // d/dy of sum (y - t)^2 is 2 * (y - t); the bracket matters, since // 2 * y - t is a different function wherever the target is not zero. this->grad.at(i) = 2 * (this->last_input[i] - this->last_target[i]); this->grad.at(i) *= grad; } } // Cross-entropy loss, for an output layer that reports a probability over the // classes. Paired with softmax the composition of the two gradients reduces // to y - t, which is the pairing this loss exists for. class CrossEntropyLoss { public: CrossEntropyLoss (); ~CrossEntropyLoss (); double forward (vector inputs, vector targets); void backward (double grad); // data vector last_input; vector last_target; vector grad; }; CrossEntropyLoss::CrossEntropyLoss () {} CrossEntropyLoss::~CrossEntropyLoss () {} // A predicted probability of zero for the true class carries infinite loss, so // the logarithm and the division below are floored rather than allowed to // diverge. static const double CE_FLOOR = 1e-15; double CrossEntropyLoss::forward (vector inputs, vector targets) { this->last_input = inputs; this->last_target = targets; double total = 0.0; for (int i = 0; i < inputs.size (); i++) { if (targets[i] != 0.0) { total -= targets[i] * log (inputs[i] > CE_FLOOR ? inputs[i] : CE_FLOOR); } } return total; } void CrossEntropyLoss::backward (double grad) { this->grad = vector (this->last_input.size ()); for (int i = 0; i < this->last_input.size (); i++) { double y = this->last_input[i] > CE_FLOOR ? this->last_input[i] : CE_FLOOR; this->grad.at (i) = -this->last_target[i] / y; this->grad.at (i) *= grad; } } statistics-release-1.9.2/src/fcnnpredict.cc000066400000000000000000000336741524624707500207610ustar00rootroot00000000000000/* Copyright (C) 2024 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include "fcnn.cpp" using namespace std; DEFUN_DLD(fcnnpredict, args, nargout, "-*- texinfo -*-\n\ @deftypefn {statistics} {@var{pred_Y} =} fcnnpredict (@var{LayerWeights}, @\ @var{LayerBiases}, @var{Activations}, @var{OutputLayerActivation}, @var{XC})\n\ @deftypefnx {statistics} {@var{pred_Y} =} fcnnpredict @\ (@dots{}, @var{NumThreads})\n\ @deftypefnx {statistics} {[@var{pred_Y}, @var{scores}] =} fcnnpredict (@dots{})\n\ \n\ \n\ Make predictions from a fully connected Neural Network. \n\ \n\n\ \n\n\ @code{@var{pred_Y} = fcnnpredict (@var{LayerWeights}, @var{LayerBiases}, \ @var{Activations}, @var{OutputLayerActivation}, @var{XC})} requires the \ following input arguments.\ \n\n\ @itemize \n\ @item @var{LayerWeights} : A cell row vector holding one matrix per layer, \ each with one row per neuron of that layer and one column per input to it. \ \n\ \n\ @item @var{LayerBiases} : A cell row vector holding one bias column per \ layer, matching @var{LayerWeights} layer for layer and row for row. \ \n\ \n\ @item @var{Activations} : The activation function of the hidden layers, \ named as a character vector applying to all of them or as a cellstring naming \ them one by one. The supported names are listed under @code{fcnntrain}. \ \n\ \n\ @item @var{OutputLayerActivation} : The activation function of the output \ layer, named as a character vector. \ \n\ \n\ @item @var{XC} : An @math{NxM} matrix containing the data set to be predicted \ upon. Rows @math{N} correspond to individual samples and columns @math{M} \ correspond to features (dimensions). Type of @var{XC} must be double and the \ number of features must correspond to those of the trained model. \n\ @end itemize \n\ @code{fcnnpredict} can also be called with a sixth input argument, in which \ case, @var{NumThreads}, a positive scalar integer value, defines the number \ of threads to be used when computing the activation layers. For layers with \ less than 1000 neurons, @var{NumThreads} always defaults to 1. \ \n\ @code{fcnnpredict} returns the predicted labels, @var{pred_Y}, and if a second \ output argument is requested, it also returns the corresponding values of the \ neural networks output in @var{scores}. \ \n\ \n\ Installation Note: in order to support parallel processing on MacOS, users \ have to manually add support for OpenMP by adding the following flags to \ @qcode{CFLAGS} and @qcode{CXXFLAGS} prior to installing the statistics \ package:\n\n\ @code{setenv (\"CPPFLAGS\", \"-I/opt/homebrew/opt/libomp/include -Xclang -fopenmp\")} \ \n\ \n\ @seealso{fcnntrain, fitcnet, ClassificationNeuralNetwork} \n\ @end deftypefn") { // Check for correct number of input/output arguments if (args.length () < 5) { error ("fcnnpredict: too few input arguments."); } if (nargout > 2) { error ("fcnnpredict: too many output arguments."); } // The weights and the biases arrive as the classes hold them, one cell per // layer each, rather than as the augmented [W b] matrices fcnntrain emits. if (! args(0).iscell () || ! (args(0).rows () == 1 && args(0).columns () > 1)) { error ("fcnnpredict: 'LayerWeights' must be a cell row vector."); } Cell LayerWeights = args(0).cell_value (); if (! args(1).iscell () || ! (args(1).rows () == 1 && args(1).columns () > 1)) { error ("fcnnpredict: 'LayerBiases' must be a cell row vector."); } Cell LayerBiases = args(1).cell_value (); if (LayerBiases.numel () != LayerWeights.numel ()) { error ("fcnnpredict: 'LayerBiases' must match 'LayerWeights'."); } // The layer count comes from the weights, so the names alone say what each // layer computes: the hidden layers from 'Activations' and the last from // 'OutputLayerActivation'. RowVector ActiveCode = activation_codes (args(2), args(3), LayerWeights.numel () - 1, "fcnnpredict"); // Do some input validation while loading the testing data if (! args(4).isnumeric () || args(4).iscomplex ()) { error ("fcnnpredict: XC must be a real numeric matrix."); } if (args(4).isempty ()) { error ("fcnnpredict: XC cannot be empty."); } if (args(4).columns () != LayerWeights.elem(0).columns ()) { error ("fcnnpredict: the features in XC do not match the trained model."); } Matrix X = args(4).matrix_value (); int n = args(4).rows (); int d = args(4).columns (); // Check for optional sixth input argument to set number of threads int NumThreads = 1; if (args.length () == 6) { if (! args(5).is_scalar_type () || ! args(5).isnumeric () || args(5).scalar_value () < 1 || args(5).iscomplex ()) { error ("fcnnpredict: NumThreads must be a positive integer scalar value."); } NumThreads = args(5).scalar_value (); } // Construct 2D vector from data in XC vector> data (n, vector(d, 0)); for (int i = 0; i < n; i++) { for (int j = 0; j < d; j++) { data[i][j] = X(i,j); } } // Create a vector of layers sized appropriately vector WeightBias; vector Activation; int numlayers = LayerWeights.numel (); int input_size = d; int output_size; for (int i = 0; i < numlayers; i++) { Matrix W = LayerWeights.elem(i).matrix_value (); Matrix B = LayerBiases.elem(i).matrix_value (); output_size = (int) W.rows (); if (B.numel () != output_size) { error ("fcnnpredict: 'LayerBiases' must match 'LayerWeights'."); } // set_layer wants each neuron as [weights, bias], which is how the layer // packs itself; the two cells are joined a row at a time here rather than // as a matrix in the m-code. vector> Wb_matrix; for (int r = 0; r < W.rows (); r++) { vector WB_vector; for (int c = 0; c < W.columns (); c++) { WB_vector.push_back (W(r,c)); } WB_vector.push_back (B(r)); Wb_matrix.push_back (WB_vector); } // Create dense layer and set its values DenseLayer DL = DenseLayer (input_size, output_size); DL.set_layer (Wb_matrix); // Create activation layer ActivationLayer AL = ActivationLayer (ActiveCode(i), NumThreads); WeightBias.push_back (DL); Activation.push_back (AL); input_size = output_size; } // Initialize Prediction and Score vector predictions = vector (); vector> scores; // Go through all testing samples for (int sample_idx = 0; sample_idx < n; sample_idx++) { vector sample = data[sample_idx]; // Forward pass for (int layer_idx = 0; layer_idx < numlayers; layer_idx++) { sample = WeightBias[layer_idx].forward (sample); sample = Activation[layer_idx].forward (sample); } // Save scores scores.push_back (sample); // Get the prediction for this sample and store it to vector int prediction = 0; // Search for highest value for (int j = 0; j < output_size; j++) { if (sample[j] > sample[prediction]) { prediction = j; } } predictions.push_back (prediction + 1); } // Store predicted labels in ColumnVector ColumnVector Y_pred(n); for (int sample_idx = 0; sample_idx < n; sample_idx++) { Y_pred(sample_idx) = predictions[sample_idx]; } // Store predicted scores in Matrix Matrix Y_scores(n,output_size); for (int r = 0; r < n; r++) { for (int c = 0; c < output_size; c++) { Y_scores(r,c) = scores[r][c]; } } // Prepare returning arguments octave_value_list retval (nargout); retval(0) = Y_pred; if (nargout > 0) { retval(1) = Y_scores; } return retval; } /* %!shared X, Y, MODEL, W, B %! load fisheriris %! X = meas; %! Y = grp2idx (species); %! MODEL = fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 100, false); %! W = cellfun (@(m) m(:,1:end-1), MODEL.LayerWeights, "UniformOutput", false); %! B = cellfun (@(m) m(:,end), MODEL.LayerWeights, "UniformOutput", false); %!test %! [Y_pred, Y_scores] = fcnnpredict (W, B, "sigmoid", "sigmoid", X); %! assert_equal (numel (Y_pred), numel (Y)); %! assert_equal (isequal (size (Y_pred), size (Y)), true); %! assert_equal (columns (Y_scores), numel (unique (Y))); %! assert_equal (rows (Y_scores), numel (Y)); ## A trained network drives its outputs to the targets, not to a fraction of ## them. A gradient of 2*y-t rather than 2*(y-t) settles at y = t/2, which ## leaves every label right and every score halved, so the scores are what ## has to be checked. %!test %! rand ("seed", 42); %! randn ("seed", 42); %! Xs = [randn(40,2)*0.3 + 3; randn(40,2)*0.3 - 3]; %! Ys = [ones(40,1); 2*ones(40,1)]; %! M = fcnntrain (Xs, Ys, [8, 8], "sigmoid", "sigmoid", 1, 0.05, 400, false); %! Wm = cellfun (@(m) m(:,1:end-1), M.LayerWeights, "UniformOutput", false); %! Bm = cellfun (@(m) m(:,end), M.LayerWeights, "UniformOutput", false); %! [pred, scores] = fcnnpredict (Wm, Bm, "sigmoid", "sigmoid", [3, 3; -3, -3]); %! assert_equal (pred, [1; 2]); %! assert_equal (max (scores(1,:)) > 0.8, true); %! assert_equal (max (scores(2,:)) > 0.8, true); %! assert_equal (all (abs (sum (scores, 2) - 1) < 0.1), true); ## Cross entropy with a softmax output trains at least as confidently as the ## mean squared error does, and its rows are a probability by construction. %!test %! rand ("seed", 42); %! randn ("seed", 42); %! Xs = [randn(40,2)*0.3 + 3; randn(40,2)*0.3 - 3]; %! Ys = [ones(40,1); 2*ones(40,1)]; %! Mm = fcnntrain (Xs, Ys, [8, 8], "sigmoid", "softmax", 1, 0.05, 400, false, 0); %! Mc = fcnntrain (Xs, Ys, [8, 8], "sigmoid", "softmax", 1, 0.05, 400, false, 1); %! Wm = cellfun (@(m) m(:,1:end-1), Mm.LayerWeights, "UniformOutput", false); %! Bm = cellfun (@(m) m(:,end), Mm.LayerWeights, "UniformOutput", false); %! Wc = cellfun (@(m) m(:,1:end-1), Mc.LayerWeights, "UniformOutput", false); %! Bc = cellfun (@(m) m(:,end), Mc.LayerWeights, "UniformOutput", false); %! [pm, sm] = fcnnpredict (Wm, Bm, "sigmoid", "softmax", [3, 3; -3, -3]); %! [pc, sc] = fcnnpredict (Wc, Bc, "sigmoid", "softmax", [3, 3; -3, -3]); %! assert_equal (pm, [1; 2]); %! assert_equal (pc, [1; 2]); %! assert_equal (all (abs (sum (sc, 2) - 1) < 1e-8), true); %! assert_equal (min (max (sc, [], 2)) >= min (max (sm, [], 2)), true); ## Omitting the loss selector keeps the mean squared error. %!test %! rand ("seed", 42); %! randn ("seed", 42); %! Xs = [randn(20,2)*0.3 + 3; randn(20,2)*0.3 - 3]; %! Ys = [ones(20,1); 2*ones(20,1)]; %! rand ("seed", 1); %! M9 = fcnntrain (Xs, Ys, 6, "sigmoid", "sigmoid", 1, 0.05, 100, false); %! rand ("seed", 1); %! M0 = fcnntrain (Xs, Ys, 6, "sigmoid", "sigmoid", 1, 0.05, 100, false, 0); %! assert_equal (M9.Loss, M0.Loss, 0); %!error ... %! fcnnpredict (W, B, "sigmoid", "sigmoid"); %!error ... %! [Q, E, R] = fcnnpredict (W, B, "sigmoid", "sigmoid", X); %!error ... %! fcnnpredict (1, B, "sigmoid", "sigmoid", X); %!error ... %! fcnnpredict ({1}, B, "sigmoid", "sigmoid", X); %!error ... %! fcnnpredict ({1; 2; 3}, B, "sigmoid", "sigmoid", X); %!error ... %! fcnnpredict (W, 1, "sigmoid", "sigmoid", X); %!error ... %! fcnnpredict (W, {1}, "sigmoid", "sigmoid", X); %!error ... %! fcnnpredict (W, [B, B], "sigmoid", "sigmoid", X); %!error ... %! fcnnpredict (W, {B{1}(1:end-1), B{2}}, "sigmoid", "sigmoid", X); %!error ... %! fcnnpredict (W, B, 2, "sigmoid", X); %!error ... %! fcnnpredict (W, B, {2, 2}, "sigmoid", X); %!error ... %! fcnnpredict (W, B, {"sigmoid", "relu"}, "sigmoid", X); %!error ... %! fcnnpredict (W, B, "sgmoid", "sigmoid", X); %!error ... %! fcnnpredict (W, B, "sigmoid", 4, X); %!error ... %! fcnnpredict (W, B, "sigmoid", "softmx", X); %!error ... %! fcnnpredict (W, B, "sigmoid", "sigmoid", complex (X)); %!error ... %! fcnnpredict (W, B, "sigmoid", "sigmoid", {1, 2, 3, 4}); %!error ... %! fcnnpredict (W, B, "sigmoid", "sigmoid", "asd"); %!error ... %! fcnnpredict (W, B, "sigmoid", "sigmoid", []); %!error ... %! fcnnpredict (W, B, "sigmoid", "sigmoid", X(:,[1:3])); %!error ... %! fcnnpredict (W, B, "sigmoid", "sigmoid", X, 0); */ statistics-release-1.9.2/src/fcnntrain.cc000066400000000000000000001110651524624707500204330ustar00rootroot00000000000000/* Copyright (C) 2024 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include "fcnn.cpp" #include "lbfgs.h" using namespace std; // fitcnet's wording for each stopping criterion, measured on R2024a. It // lives here and not in lbfgs.h because MATLAB words the same criterion // differently per function: fitclinear reports "Tolerance on gradient // satisfied." where this reports "Relative gradient tolerance reached.", // so the engine hands out a token and every caller supplies its own prose. static const char * criterion_message (int crit) { switch (crit) { case lbfgs::CRIT_GRADIENT: return "Relative gradient tolerance reached."; case lbfgs::CRIT_STEP: return "Step size tolerance reached."; case lbfgs::CRIT_LOSS: return "Loss tolerance reached."; case lbfgs::CRIT_ITERATION_LIMIT: return "Iteration limit reached."; default: return "Line search could not improve the objective."; } } // Full-batch objective: the mean loss over every training sample together // with its gradient against the flat parameter vector. One call is one sweep // forward and back over the whole training set, which is what each trial step // of a line search costs. // // This is the piece the epoch loop cannot supply. That loop clears the // gradient before every sample and consumes it immediately, so no gradient of // the summed loss ever exists; here it is cleared once and accumulated across // the batch. class fcnn_objective { public: fcnn_objective (vector& wb, vector& act, const vector>& data, const vector>& targets, const vector& labels, int output_size, int lossfcn, bool regression) : m_wb (wb), m_act (act), m_data (data), m_targets (targets), m_labels (labels), m_output_size (output_size), m_lossfcn (lossfcn), m_regression (regression) { } double operator () (const vector& w, vector& g) { octave_quit (); const int numlayers = m_wb.size (); const int n = m_data.size (); const double *p = w.data (); for (int l = 0; l < numlayers; l++) { m_wb[l].unpack (p); p += m_wb[l].nparams (); } for (int l = 0; l < numlayers; l++) { m_wb[l].zero_gradient (); } double total = 0.0; for (int s = 0; s < n; s++) { vector sample = m_data[s]; for (int l = 0; l < numlayers; l++) { sample = m_wb[l].forward (sample); sample = m_act[l].forward (sample); } vector label_vector; if (m_regression) { label_vector = m_targets[s]; } else { label_vector = vector (m_output_size, 0.0); label_vector[m_labels[s]-1] = 1.0; // labels in Y start from 1 } vector loss_grad; if (m_lossfcn == 1) { CrossEntropyLoss loss = CrossEntropyLoss (); total += loss.forward (sample, label_vector); // The objective is the mean, so each sample's gradient carries 1/n. loss.backward (1.0 / n); loss_grad = loss.grad; } else { MeanSquaredErrorLoss loss = MeanSquaredErrorLoss (); total += loss.forward (sample, label_vector); loss.backward (1.0 / n); loss_grad = loss.grad; } m_act[numlayers-1].backward (loss_grad); for (int l = numlayers; l > 0; l--) { m_wb[l-1].backward (m_act[l-1].grad); if (l > 1) { m_act[l-2].backward (m_wb[l-1]); } } } double *q = &g[0]; for (int l = 0; l < numlayers; l++) { m_wb[l].pack_grad (q); q += m_wb[l].nparams (); } return total / n; } private: vector& m_wb; vector& m_act; const vector>& m_data; const vector>& m_targets; const vector& m_labels; int m_output_size; int m_lossfcn; bool m_regression; }; DEFUN_DLD(fcnntrain, args, nargout, "-*- texinfo -*-\n\ @deftypefn {statistics} {@var{Mdl} =} fcnntrain (@var{X}, @var{Y}, @\ @var{LayerSizes}, @var{Activations}, @var{OutputLayerActivation}, @\ @var{NumThreads}, @var{LearningRate}, @var{Epochs}, @var{DisplayInfo})\n\ @deftypefnx {statistics} {@var{Mdl} =} fcnntrain (@dots{}, @\ @var{LossFunction})\n\ \n\ \n\ Train a fully connected Neural Network. \n\ \n\n\ @code{@var{Mdl} = fcnntrain (@dots{})} requires the following input arguments.\ \n\n\ @itemize \n\ @item @var{X} : An @math{NxM} matrix containing the data set to be trained \ upon. Rows @math{N} correspond to individual samples and columns @math{M} \ correspond to features (dimensions). Type of @var{X} must be double. \ \n\ \n\ @item @var{Y} : An @math{Nx1} column vector containing the labels of the \ training dataset. The labels must be natural numbers (positive integers) \ starting from 1 up to the number of classes, similarly as returned by the \ `grp2idx` function. Type of @var{Y} must be double. Under regression, \ selected by @var{LossFunction} 2, @var{Y} is instead an @math{NxR} matrix of \ response values, which may take any finite value, and the output layer is \ sized to its @math{R} columns rather than to a number of classes. \ \n\ \n\ @item @var{LayerSizes} : A numeric row vector of integer values defining the \ size of the hidden layers of the network. Input and output layers are \ automatically determined by the training data and their labels. \ \n\ \n\ @item @var{Activations} : The activation function of the hidden layers, named \ as a character vector applying to all of them or as a cellstring naming them \ one by one, in which case it must have one name per hidden layer. The \ supported names are: \n\ @itemize \n\ @item @qcode{'linear'} or @qcode{'none'} : the identity \n\ @item @qcode{'sigmoid'} \n\ @item @qcode{'relu'} : rectified linear unit \n\ @item @qcode{'tanh'} : hyperbolic tangent \n\ @item @qcode{'softmax'} \n\ @item @qcode{'lrelu'} or @qcode{'prelu'} : leaky rectified linear unit, whose \ negative slope is a constant 0.01 \n\ @item @qcode{'elu'} : exponential linear unit, saturating at @math{-1} \n\ @item @qcode{'gelu'} : Gaussian error linear unit \n\ @end itemize \n\ \n\ \n\ @item @var{OutputLayerActivation} : The activation function of the output \ layer, named as a character vector from the same list. \ \n\ \n\ @item @var{NumThreads} : A positive scalar integer value defining the number \ of threads used for computing the activation layers. For layers with less \ than 1000 neurons, @var{NumThreads} always defaults to 1. \ \n\ \n\ @item @var{LearningRate} : A positive scalar value defining the learning rate \ used by the gradient descend algorithm during training. \ \n\ \n\ @item @var{Epochs} : A positive scalar value defining the number of epochs for \ training the model. \ \n\ \n\ @item @var{DisplayInfo} : A boolean scalar indicating whether to print \ information during training. \n\ @end itemize \n\ \n\ @code{@var{Mdl} = fcnntrain (@dots{}, @var{LossFunction})} also selects the \ loss the network is trained against. @var{LossFunction} is a scalar: 0 for \ mean squared error over a one-hot target, which is the default, 1 for cross \ entropy, and 2 for mean squared error over a continuous response. Cross \ entropy expects the output layer to report a probability over the classes, \ so it belongs with a softmax output; paired that way the two gradients \ compose to @math{y - t}. Its loss is undefined where the predicted \ probability of the true class is zero, so both the logarithm and its \ derivative are floored. Code 2 is regression: @var{Y} holds response values \ rather than labels, the output layer belongs with the identity activation, \ and the returned model carries no @code{Accuracy} field, there being no \ labels to count. \n\ \n\ \n\ @code{fcnntrain} returns the trained model, @var{Mdl}, as a structure \ containing the following fields: \ \n\ \n\ @itemize \n\ @item @code{LayerWeights} : A cell array with each element containing a matrix \ with the Weights and Biases of each layer including the output layer.\n\ \n\ \n\ @item @code{Activations} : A numeric row vector of integer values defining the \ activation functions to be used at each layer including the output layer. \ \n\ \n\ @item @code{Accuracy} : The prediction accuracy at each iteration during the \ neural network model's training process. Absent under regression. \ \n\ \n\ @item @code{Loss} : The loss value recorded at each iteration during the \ neural network model's training process. \ \n\ \n\ @end itemize \ \n\ \n\ Installation Note: in order to support parallel processing on MacOS, users \ have to manually add support for OpenMP by adding the following flags to \ @qcode{CFLAGS} and @qcode{CXXFLAGS} prior to installing the statistics \ package:\n\n\ @code{setenv (\"CPPFLAGS\", \"-I/opt/homebrew/opt/libomp/include -Xclang -fopenmp\")} \ \n\ \n\ @seealso{fcnnpredict, fitcnet, ClassificationNeuralNetwork} \n\ @end deftypefn") { // Check for correct number of input/output arguments if (args.length () < 9) { error ("fcnntrain: too few input arguments."); } if (args.length () > 11) { error ("fcnntrain: too many input arguments."); } if (nargout > 1) { error ("fcnntrain: too many output arguments."); } // Optional tenth argument selecting the loss: 0 for mean squared error over // a one-hot target, which is the default and what earlier releases always // used, 1 for cross entropy, which expects the output layer to report a // probability, and 2 for mean squared error over a continuous response, // which is regression. It is read first because it decides whether Y holds // class labels or response values. int lossfcn = 0; if (args.length () >= 10) { if (! args(9).isnumeric () || ! args(9).is_scalar_type () || args(9).iscomplex ()) { error ("fcnntrain: 'LossFunction' must be a numeric scalar value."); } lossfcn = args(9).int_value (); if (lossfcn < 0 || lossfcn > 2) { error ("fcnntrain: invalid 'LossFunction' code."); } } bool regression = (lossfcn == 2); // Do some input validation while loading training data and labels if (! args(0).isnumeric () || args(0).iscomplex ()) { error ("fcnntrain: X must be a real numeric matrix."); } if (args(0).isempty ()) { error ("fcnntrain: X cannot be empty."); } if (! args(1).isnumeric () || args(1).iscomplex ()) { error ("fcnntrain: Y must be a real numeric matrix."); } if (args(1).isempty ()) { error ("fcnntrain: Y cannot be empty."); } if (args(0).rows () != args(1).rows ()) { error ("fcnntrain: X and Y must have the same number of rows."); } // Construct 2D vector from data in X int n = args(0).rows (); int d = args(0).columns (); vector> data (n, vector(d, 0)); Matrix X = args(0).matrix_value (); for (int i = 0; i < n; i++) { for (int j = 0; j < d; j++) { data[i][j] = X(i,j); } } // Construct the training targets from Y. Under regression Y holds the // response itself, one column per response, and is taken as it stands; // otherwise it holds class labels, which index a one-hot target built below. vector labels; vector> targets; int n_response = 0; if (regression) { n_response = args(1).columns (); Matrix R = args(1).matrix_value (); targets = vector> (n, vector (n_response, 0.0)); for (int i = 0; i < n; i++) { for (int j = 0; j < n_response; j++) { if (! octave::math::isfinite (R(i,j))) { error ("fcnntrain: Y must be finite."); } targets[i][j] = R(i,j); } } } else { labels = vector (n, 0); ColumnVector Y = args(1).column_vector_value (); for (int i = 0; i < n; i++) { labels[i] = Y(i); if (labels[i] < 1) { error ("fcnntrain: labels in Y must be positive integers."); } } } // Check LayerSizes and Activations input arguments if (! args(2).isnumeric () || args(2).iscomplex () || args(2).isempty () || args(2).rows () != 1) { error ("fcnntrain: 'LayerSizes' must be a row vector of integer values."); } // Check the NumThreads input argument if (! args(5).is_scalar_type () || ! args(5).isnumeric () || args(5).scalar_value () < 1 || args(5).iscomplex ()) { error ("fcnntrain: 'NumThreads' must be a positive integer scalar value."); } int NumThreads = args(5).scalar_value (); // Create a vector of layers sized appropriately. The initial weights are // drawn from Octave's generator, so rand ('seed', s) reproduces a fit. uniform_scope draw_uniform; vector WeightBias; vector Activation; RowVector LayerSizes = args(2).row_vector_value (); RowVector ActiveCode = activation_codes (args(3), args(4), args(2).numel (), "fcnntrain"); int numlayers = args(2).numel () + 1; int input_size = d; for (int i = 0; i < args(2).numel (); i++) { int output_size = (int) LayerSizes(i); if (output_size < 1) { error ("fcnntrain: cannot have a layer of zero size."); } int code = ActiveCode(i); DenseLayer DL = DenseLayer (input_size, output_size, code); ActivationLayer AL = ActivationLayer (code, NumThreads); WeightBias.push_back (DL); Activation.push_back (AL); input_size = output_size; } // Push back last dense layer int output_size = regression ? n_response : (int) set (labels.begin (), labels.end ()).size (); int last_AC = args(2).numel (); DenseLayer DL = DenseLayer (input_size, output_size, (int) ActiveCode(last_AC)); ActivationLayer AL = ActivationLayer (ActiveCode(last_AC), NumThreads); WeightBias.push_back (DL); Activation.push_back (AL); // Input validation on LearningRate, Epochs, and DisplayInfo if (! args(6).is_scalar_type () || ! args(6).isnumeric ()) { error ("fcnntrain: 'LearningRate' must be a positive scalar value."); } double learning_rate = args(6).scalar_value (); if (learning_rate <= 0) { error ("fcnntrain: 'LearningRate' must be a positive scalar value."); } if (! args(7).is_scalar_type () || ! args(7).isnumeric ()) { error ("fcnntrain: 'Epochs' must be a positive scalar value."); } if (args(7).scalar_value () < 1) { error ("fcnntrain: 'Epochs' must be a positive scalar value."); } if (! args(8).is_bool_scalar ()) { error ("fcnntrain: 'DisplayInfo' must be a boolean scalar."); } // Initialize return variables octave_idx_type max_epochs = args(7).idx_type_value (); // Reserved, not sized: these are filled with push_back below, and sizing // them here would leave max_epochs zeros in front of the values and the // reported history reading back as all zero. // Optional eleventh argument: the solver and its tolerances, as a scalar // struct. Absent, the epoch loop below runs exactly as it always has, and // every default in this file is unchanged. bool use_lbfgs = false; lbfgs::options lbopt; lbopt.iteration_limit = max_epochs; lbfgs::result lbres; if (args.length () > 10) { if (! args(10).isstruct () || args(10).numel () != 1) { error ("fcnntrain: 'SolverOptions' must be a scalar struct."); } octave_scalar_map so = args(10).scalar_map_value (); if (so.isfield ("Solver")) { std::string sv = so.contents ("Solver").string_value (); if (sv == "lbfgs") { use_lbfgs = true; } else if (sv != "sgd") { error ("fcnntrain: 'Solver' must be 'sgd' or 'lbfgs'."); } } if (so.isfield ("GradientTolerance")) { lbopt.gradient_tolerance = so.contents ("GradientTolerance").double_value (); } if (so.isfield ("LossTolerance")) { lbopt.loss_tolerance = so.contents ("LossTolerance").double_value (); } if (so.isfield ("StepTolerance")) { lbopt.step_tolerance = so.contents ("StepTolerance").double_value (); } if (so.isfield ("HistorySize")) { lbopt.history_size = so.contents ("HistorySize").int_value (); } } vector Accuracy; vector Loss; Accuracy.reserve (max_epochs); Loss.reserve (max_epochs); // Order the samples are visited in, reshuffled every epoch below. Visiting // them in a fixed order makes every update for one class precede the first // update for the next whenever the labels arrive sorted, which is what // grp2idx returns, and the weights then swing between the classes instead of // settling. vector order (n); for (int i = 0; i < n; i++) { order[i] = i; } // The epoch loop is the stochastic solver. LBFGS replaces it whole rather // than adjusting it: it needs the gradient of the summed loss, which no // per-sample update can produce, so the two paths share the network and // nothing else. The sample order is not shuffled here, the fit being // deterministic once the weights are drawn. if (use_lbfgs) { int nparams = 0; for (int layer_idx = 0; layer_idx < numlayers; layer_idx++) { nparams += WeightBias[layer_idx].nparams (); } vector w (nparams, 0.0); double *p = &w[0]; for (int layer_idx = 0; layer_idx < numlayers; layer_idx++) { WeightBias[layer_idx].pack (p); p += WeightBias[layer_idx].nparams (); } fcnn_objective fobj (WeightBias, Activation, data, targets, labels, output_size, lossfcn, regression); lbres = lbfgs::minimize (fobj, w, lbopt); // minimize leaves the network holding whatever the last trial step set, // which is the accepted point only by construction of the line search; // writing the returned vector back makes that explicit. const double *q = w.data (); for (int layer_idx = 0; layer_idx < numlayers; layer_idx++) { WeightBias[layer_idx].unpack (q); q += WeightBias[layer_idx].nparams (); } if (args(8).scalar_value () != 0) { cout << "Iterations: " << lbres.iterations << " | Loss: " << lbres.fval << " | " << criterion_message (lbres.crit) << endl; } } else { // Start training octave_idx_type epoch = 0; for (; epoch < max_epochs; epoch++) { // Fisher-Yates over Octave's generator, so that a seeded fit stays // reproducible for (int i = n - 1; i > 0; i--) { int j = (int) (octave::rand::scalar () * (i + 1)); if (j > i) // scalar () is documented on [0, 1); guard the end { j = i; } int keep = order[i]; order[i] = order[j]; order[j] = keep; } // Running loss, for the progress line only: the weights move under it, so // it is not the loss of any one network and is never recorded. double running_loss = 0.0; // Go through all training samples for (int visit = 0; visit < n; visit++) { int sample_idx = order[visit]; vector sample = data[sample_idx]; // Forward pass for (int layer_idx = 0; layer_idx < numlayers; layer_idx++) { sample = WeightBias[layer_idx].forward (sample); sample = Activation[layer_idx].forward (sample); } vector label_vector; if (regression) { label_vector = targets[sample_idx]; } else { label_vector = vector (output_size); label_vector[labels[sample_idx]-1] = 1.0; // Labels in Y start from 1 } // Compute loss and the gradient it hands back double loss_output; vector loss_grad; if (lossfcn == 1) { CrossEntropyLoss loss = CrossEntropyLoss (); loss_output = loss.forward (sample, label_vector); loss.backward (1.0); loss_grad = loss.grad; } else { MeanSquaredErrorLoss loss = MeanSquaredErrorLoss (); loss_output = loss.forward (sample, label_vector); loss.backward (1.0); loss_grad = loss.grad; } running_loss += loss_output; // Print output if (args(8).scalar_value () != 0) { if (visit % 500 == 0) { cout << setprecision(4) << "i:" << visit << " | Mean Loss: "; cout << (running_loss / (visit + 1)) << "\r" << flush; } } // Backward pass for (int layer_idx = 0; layer_idx < numlayers; layer_idx++) { WeightBias[layer_idx].zero_gradient (); // Reset gradients to zero } // Compute gradients Activation[numlayers-1].backward (loss_grad); for (int layer_idx = numlayers; layer_idx > 0; layer_idx--) { WeightBias[layer_idx-1].backward (Activation[layer_idx-1].grad); if (layer_idx > 1) { Activation[layer_idx-2].backward (WeightBias[layer_idx-1]); } } // Update weights for (int layer_idx = 0; layer_idx < numlayers; layer_idx++) { WeightBias[layer_idx].descend (learning_rate); } } // Loss and accuracy of the network as it stands at the end of the // epoch, measured in one forward-only pass with the weights held // still. Summing them inside the loop above instead scores every // sample against different weights, so the figure belongs to no // network that ever existed: on class-interleaved data a network stuck // on a constant output reports an accuracy of exactly zero, each sample // being scored against weights just pulled toward the one before it. double sum_loss = 0.0; vector predictions = vector (); predictions.reserve (n); for (int sample_idx = 0; sample_idx < n; sample_idx++) { vector sample = data[sample_idx]; for (int layer_idx = 0; layer_idx < numlayers; layer_idx++) { sample = WeightBias[layer_idx].forward (sample); sample = Activation[layer_idx].forward (sample); } vector label_vector; if (regression) { label_vector = targets[sample_idx]; } else { int prediction = 0; // Search for highest value for (int j = 0; j < output_size; j++) { if (sample[j] > sample[prediction]) { prediction = j; } } predictions.push_back (prediction); label_vector = vector (output_size); label_vector[labels[sample_idx]-1] = 1.0; } if (lossfcn == 1) { CrossEntropyLoss loss = CrossEntropyLoss (); sum_loss += loss.forward (sample, label_vector); } else { MeanSquaredErrorLoss loss = MeanSquaredErrorLoss (); sum_loss += loss.forward (sample, label_vector); } } // Accuracy counts correct labels, which regression has none of. double A = regression ? 0.0 : accuracy (predictions, labels); double L = sum_loss / n; Accuracy.push_back (A); Loss.push_back (L); // Print output if (args(8).scalar_value () != 0) { cout << " \r" << "Epoch: " << epoch + 1 << " | Loss: " << L; if (! regression) { cout << " | Train Accuracy: " << A; } cout << endl; } } } // Get weights and biases from each layer and store them in a cell array Cell LayerWeights(1, numlayers); for (int layer_idx = 0; layer_idx < numlayers; layer_idx++) { DenseLayer DL = WeightBias[layer_idx]; vector> Wb_matrix = DL.get_layer (); vector WB_vector = Wb_matrix[0]; octave_idx_type row = Wb_matrix.size (); octave_idx_type col = WB_vector.size (); Matrix WB (row, col); for (int r = 0; r < row; r++) { for (int c = 0; c < col; c++) { WB(r,c) = Wb_matrix[r][c]; } } LayerWeights.elem(layer_idx) = WB; } // The recorded history: one row per epoch under the stochastic solver, one // per iteration under LBFGS, which also reports the two quantities it // measured to decide it had converged and which of them stopped it. // Accuracy is not among them, MATLAB not reporting it either, and computing // it would cost a forward pass over the whole set at every iteration. octave_idx_type nrec = use_lbfgs ? (octave_idx_type) lbres.history.size () : (octave_idx_type) Loss.size (); RowVector A(nrec), L(nrec), G(nrec), S(nrec); for (octave_idx_type i = 0; i < nrec; i++) { if (use_lbfgs) { L(i) = lbres.history[i].fval; G(i) = lbres.history[i].gradient; S(i) = lbres.history[i].step; } else { A(i) = Accuracy[i]; L(i) = Loss[i]; } } // Prepare returning arguments octave_scalar_map fcnn_model; fcnn_model.assign ("LayerWeights", LayerWeights); fcnn_model.assign ("Activations", ActiveCode); if (use_lbfgs) { fcnn_model.assign ("Loss", L); fcnn_model.assign ("Gradient", G); fcnn_model.assign ("Step", S); fcnn_model.assign ("Criterion", criterion_message (lbres.crit)); } else { if (! regression) { fcnn_model.assign ("Accuracy", A); } fcnn_model.assign ("Loss", L); } octave_value_list retval (1); retval(0) = fcnn_model; return retval; } /* %!shared X, Y, MODEL %! load fisheriris %! X = meas; %! Y = grp2idx (species); %!error ... %! model = fcnntrain (X, Y); %!error ... %! [Q, W] = fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (complex (X), Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain ({X}, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain ([], Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, complex (Y), 10, "sigmoid", "sigmoid", 0.01, 0.025, 50, false); %!error ... %! fcnntrain (X, {Y}, 10, "sigmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, [], 10, "sigmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y([1:50]), 10, "sigmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y - 1, 10, "sigmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, [10; 5], "sigmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, "10", "sigmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, {10}, "sigmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, complex (10), "sigmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, 10, [1; 1], "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, 10, {1, 1}, "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, 10, complex ([1, 1]), "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, 10, {"sigmoid", "relu"}, "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, [10, 0, 5], "sigmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, 10, "sgmoid", "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, 10, {"bogus"}, "sigmoid", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", 4, 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "softmx", 1, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 0, 0.025, 50, false); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, -0.025, 50, false); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0, 50, false); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, [0.025, 0.001], 50, false); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, {0.025}, 50, false); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 0, false); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, [50, 25], false); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, 0); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, 1); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, [false, false]); ## The reported training history has one entry per epoch and holds the values ## actually recorded, not the zeros a pre-sized vector would leave in front of ## them. %!test %! rand ('seed', 42); %! randn ('seed', 42); %! Xs = [randn(30,2)*0.4 + 2; randn(30,2)*0.4 - 2]; %! Ys = [ones(30,1); 2*ones(30,1)]; %! M = fcnntrain (Xs, Ys, 8, "relu", "softmax", 1, 0.05, 60, false, 1); %! assert_equal (numel (M.Loss), 60); %! assert_equal (numel (M.Accuracy), 60); %! assert_equal (any (M.Loss != 0), true); %! assert_equal (M.Loss(end) < M.Loss(1), true); %! assert_equal (M.Accuracy(end) >= M.Accuracy(1), true); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, false, 0, struct (), 0); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, false, 0, 0); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, false, 0, ... %! struct ("Solver", "bogus")); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, false, 'ce'); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, false, [0, 1]); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, false, 3); %!error ... %! fcnntrain (X, Y, 10, "sigmoid", "sigmoid", 1, 0.025, 50, false, -1); ## Loss function 2 is regression: Y holds the response, the output layer is ## sized to its columns, and no Accuracy is reported because there are no ## labels to count. %!test %! rand ('seed', 42); %! randn ('seed', 42); %! Xr = linspace (-2, 2, 60)'; %! Yr = 3 * Xr - 1; %! M = fcnntrain (Xr, Yr, [8, 8], "relu", "linear", 1, 0.005, 300, false, 2); %! assert_equal (fieldnames (M), {'LayerWeights'; 'Activations'; 'Loss'}); %! assert_equal (rows (M.LayerWeights{end}), 1); %! assert_equal (M.Loss(end) < M.Loss(1), true); ## The recorded loss is the mean squared error of the network it belongs to. %!test %! rand ('seed', 42); %! Xr = linspace (0, 1, 40)'; %! Yr = 5 * Xr + 2; %! M = fcnntrain (Xr, Yr, 10, "relu", "linear", 1, 0.005, 200, false, 2); %! Wm = cellfun (@(m) m(:,1:end-1), M.LayerWeights, "UniformOutput", false); %! Bm = cellfun (@(m) m(:,end), M.LayerWeights, "UniformOutput", false); %! [~, yFit] = fcnnpredict (Wm, Bm, "relu", "linear", Xr); %! assert_equal (M.Loss(end), mean ((Yr - yFit) .^ 2), 1e-12); ## A response of several columns gets one output unit per column. %!test %! rand ('seed', 42); %! Xr = linspace (0, 1, 30)'; %! M = fcnntrain (Xr, [Xr, 2 * Xr, 3 * Xr], 6, "relu", "linear", 1, 0.005, ... %! 50, false, 2); %! assert_equal (rows (M.LayerWeights{end}), 3); %! Wm = cellfun (@(m) m(:,1:end-1), M.LayerWeights, "UniformOutput", false); %! Bm = cellfun (@(m) m(:,end), M.LayerWeights, "UniformOutput", false); %! [~, yFit] = fcnnpredict (Wm, Bm, "relu", "linear", Xr); %! assert_equal (columns (yFit), 3); ## Regression takes a response the classification path would refuse. %!test %! rand ('seed', 42); %! Xr = linspace (0, 1, 20)'; %! Yr = linspace (-3.5, 2.25, 20)'; %! M = fcnntrain (Xr, Yr, 6, "relu", "linear", 1, 0.005, 50, false, 2); %! assert_equal (all (isfinite (M.Loss)), true); %!error ... %! fcnntrain ([1; 2; 3], [1; Inf; 3], 4, "relu", "linear", 1, 0.01, 10, false, 2); %!error ... %! fcnntrain ([1; 2; 3], [1; NaN; 3], 4, "relu", "linear", 1, 0.01, 10, false, 2); ## The full-batch solver drives the loss down and reports what it measured to ## decide it had stopped. Accuracy is not among them: MATLAB does not report ## it either, and it would cost a forward pass over the whole set per ## iteration. %!test %! so = struct ("Solver", "lbfgs"); %! M = fcnntrain (X, Y, 10, "relu", "softmax", 1, 0.005, 100, false, 1, so); %! assert_equal (fieldnames (M), ... %! {'LayerWeights'; 'Activations'; 'Loss'; 'Gradient'; ... %! 'Step'; 'Criterion'}); %! assert_equal (M.Loss(end) < M.Loss(1), true); %! assert_equal (numel (M.Gradient), numel (M.Loss)); %! assert_equal (numel (M.Step), numel (M.Loss)); ## It reaches a lower training loss than the epoch loop does, in fewer passes ## over the data, which is the whole reason for offering it. %!test %! rand ("state", 3); randn ("state", 3); %! Ms = fcnntrain (X, Y, 10, "relu", "softmax", 1, 0.005, 200, false, 1); %! rand ("state", 3); randn ("state", 3); %! so = struct ("Solver", "lbfgs"); %! Ml = fcnntrain (X, Y, 10, "relu", "softmax", 1, 0.005, 200, false, 1, so); %! assert_equal (Ml.Loss(end) < Ms.Loss(end), true); %! assert_equal (numel (Ml.Loss) < numel (Ms.Loss), true); ## An explicit 'sgd' is the epoch loop, unchanged. %!test %! rand ("state", 5); randn ("state", 5); %! Ma = fcnntrain (X, Y, 10, "relu", "softmax", 1, 0.005, 30, false, 1); %! rand ("state", 5); randn ("state", 5); %! so = struct ("Solver", "sgd"); %! Mb = fcnntrain (X, Y, 10, "relu", "softmax", 1, 0.005, 30, false, 1, so); %! assert_equal (Mb.Loss, Ma.Loss); %! assert_equal (Mb.LayerWeights, Ma.LayerWeights); ## The tolerances reach the solver: from the same starting weights, a loose ## gradient tolerance stops sooner than a tight one. %!test %! rand ("state", 9); randn ("state", 9); %! so = struct ("Solver", "lbfgs", "GradientTolerance", 1e3); %! Ma = fcnntrain (X, Y, 10, "relu", "softmax", 1, 0.005, 100, false, 1, so); %! rand ("state", 9); randn ("state", 9); %! so = struct ("Solver", "lbfgs", "GradientTolerance", 1e-8); %! Mb = fcnntrain (X, Y, 10, "relu", "softmax", 1, 0.005, 100, false, 1, so); %! assert_equal (Ma.Criterion, "Relative gradient tolerance reached."); %! assert_equal (numel (Ma.Loss) < numel (Mb.Loss), true); */ statistics-release-1.9.2/src/gam.cpp000066400000000000000000000551111524624707500174110ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ // The spline engine shared by the GAM learners. Both fitting schemes refit // the same predictor at the same breaks and the same order in every round and // vary only the response, so the design matrix of each predictor is fixed for // the whole fit. It is built and factorised once here, which reduces a round // to two products against the factors; the m-code it replaces called splinefit // inside the loop, where the fit was dominated not by the solve but by the // interpreter's overhead on very small arrays. // The spline space is the one splinefit builds: a B-spline basis of order N // over BREAKS, evaluated by the same recursion, so the piecewise polynomial // returned here is splinefit's to rounding. What is not reproduced is its // option surface -- periodic boundaries, robust fitting and linear constraints // are not used by either learner and are absent. #include #include #include #include #include using namespace std; // The interval a value falls in, as lookup (BREAKS, V, "lr") returns it: the // last break at or below V, clamped to the first and last piece so that a // point outside the domain is extrapolated from the nearest polynomial rather // than dropped. Returned 0-based. octave_idx_type gam_interval (const RowVector& breaks, double v) { octave_idx_type pieces = breaks.numel () - 1; octave_idx_type lo = 0; octave_idx_type hi = pieces - 1; if (! (v >= breaks(1))) // false for NaN as well, which lands on piece 0 { return 0; } if (v >= breaks(pieces - 1)) { return pieces - 1; } while (hi - lo > 1) { octave_idx_type mid = (lo + hi) / 2; if (v >= breaks(mid)) { lo = mid; } else { hi = mid; } } return lo; } // A basis of the spline space of order N over BREAKS, held as the polynomial // coefficients of the N basis functions that are nonzero on each piece. struct SplineBasis { RowVector breaks; // 1 x (pieces + 1) Matrix bcoefs; // (N * pieces) x N, row (p * N + i) is basis i on p octave_idx_type n; // spline order, the number of coefficients a piece octave_idx_type pieces; octave_idx_type dim; // pieces + n - 1, the dimension of the space }; // Breaks from a piece count, interpolated linearly from the sorted data as // splinefit does. X must already be sorted ascending and hold no NaN. RowVector gam_breaks_from_count (const ColumnVector& x, octave_idx_type p) { octave_idx_type mx = x.numel (); RowVector breaks (p + 1); if (x(0) < x(mx - 1)) { for (octave_idx_type k = 0; k <= p; k++) { // linspace (1, mx, p+1), in the m-code's 1-based indexing double ib = (p == 0) ? 1.0 : 1.0 + (double) k * (mx - 1) / p; if (k == p) { ib = (double) mx; // linspace pins its last point exactly } octave_idx_type ibin = (octave_idx_type) floor (ib); if (ibin < 1) { ibin = 1; } if (ibin > mx - 1) { ibin = mx - 1; } double dx = x(ibin) - x(ibin - 1); breaks(k) = x(ibin - 1) + dx * (ib - ibin); } } else { // Every observation the same: splinefit spreads unit breaks from it for (octave_idx_type k = 0; k <= p; k++) { breaks(k) = x(0) + ((p == 0) ? 0.0 : (double) k / p); } } return breaks; } // Drop breaks that do not increase, which interpolation from tied data can // produce, matching splinefit's unique () pass. RowVector gam_unique_breaks (const RowVector& breaks) { octave_idx_type nb = breaks.numel (); bool monotone = true; for (octave_idx_type k = 1; k < nb; k++) { if (breaks(k) <= breaks(k - 1)) { monotone = false; break; } } if (monotone) { return breaks; } vector v (breaks.data (), breaks.data () + nb); sort (v.begin (), v.end ()); v.erase (unique (v.begin (), v.end ()), v.end ()); RowVector out (v.size ()); for (size_t k = 0; k < v.size (); k++) { out(k) = v[k]; } return out; } // Build the B-spline basis of order N over BREAKS. This is splinefit's // splinebase, transliterated: the breaks are extended periodically by DEG on // each side, the basis is generated by repeated antidifferentiation and // normalisation, and the pieces added for the extension are dropped again. SplineBasis gam_splinebase (const RowVector& breaks0, octave_idx_type n) { SplineBasis base; base.breaks = breaks0; base.n = n; base.pieces = breaks0.numel () - 1; base.dim = base.pieces + n - 1; octave_idx_type deg = n - 1; octave_idx_type pieces = base.pieces; // Extended breaks vector br; if (deg > 0) { vector h (pieces); for (octave_idx_type k = 0; k < pieces; k++) { h[k] = breaks0(k + 1) - breaks0(k); } vector hcopy = h; while ((octave_idx_type) hcopy.size () < deg) { hcopy.insert (hcopy.end (), h.begin (), h.end ()); } // To the left: bl(t) = breaks(1) - cumsum (hcopy(end:-1:end-deg+1))(t), // laid down in reverse so the sequence stays increasing. vector bl (deg); double acc = breaks0(0); for (octave_idx_type t = 0; t < deg; t++) { acc -= hcopy[hcopy.size () - 1 - t]; bl[t] = acc; } for (octave_idx_type t = deg - 1; t >= 0; t--) { br.push_back (bl[t]); } for (octave_idx_type k = 0; k <= pieces; k++) { br.push_back (breaks0(k)); } // And to the right acc = breaks0(pieces); for (octave_idx_type t = 0; t < deg; t++) { acc += hcopy[t]; br.push_back (acc); } pieces = (octave_idx_type) br.size () - 1; } else { for (octave_idx_type k = 0; k <= pieces; k++) { br.push_back (breaks0(k)); } } vector h (pieces); for (octave_idx_type k = 0; k < pieces; k++) { h[k] = br[k + 1] - br[k]; } // H(p * n + i) is the spacing of the piece basis function i reaches from p vector H (n * pieces); for (octave_idx_type p = 0; p < pieces; p++) { for (octave_idx_type i = 0; i < n; i++) { octave_idx_type q = p + i; H[p * n + i] = h[q < pieces ? q : pieces - 1]; } } Matrix coefs (n * pieces, n, 0.0); for (octave_idx_type p = 0; p < pieces; p++) { coefs(p * n, 0) = 1.0; } vector Q (n * pieces); for (octave_idx_type k = 1; k < n; k++) // k is the m-code's k - 1 { // Antiderivatives of the splines of the previous order for (octave_idx_type j = 0; j < k; j++) { for (octave_idx_type r = 0; r < n * pieces; r++) { coefs(r, j) = coefs(r, j) * H[r] / (k - j); } } // Q, cumulated down each piece, is the antiderivative at the break above for (octave_idx_type p = 0; p < pieces; p++) { double run = 0.0; for (octave_idx_type i = 0; i < n; i++) { double s = 0.0; for (octave_idx_type j = 0; j < n; j++) { s += coefs(p * n + i, j); } run += s; Q[p * n + i] = run; } } for (octave_idx_type p = 0; p < pieces; p++) { coefs(p * n, k) = 0.0; for (octave_idx_type i = 1; i < n; i++) { coefs(p * n + i, k) = Q[p * n + i - 1]; } } // Normalise by the value the antiderivative reaches over the piece for (octave_idx_type p = 0; p < pieces; p++) { double fmax = Q[p * n + n - 1]; for (octave_idx_type i = 0; i < n; i++) { for (octave_idx_type j = 0; j <= k; j++) { coefs(p * n + i, j) /= fmax; } } } // Difference of adjacent antiderivatives. Row r reads row r + n - 1, // which is never one this pass has already written, so ascending order is // the simultaneous assignment the m-code performs. for (octave_idx_type r = 0; r < n * pieces - deg; r++) { for (octave_idx_type j = 0; j <= k; j++) { coefs(r, j) -= coefs(r + n - 1, j); } } for (octave_idx_type p = 0; p < pieces; p++) { coefs(p * n, k) = 0.0; } } // Scale for the width of each piece vector scale (n * pieces, 1.0); for (octave_idx_type k = 1; k < n; k++) { for (octave_idx_type r = 0; r < n * pieces; r++) { scale[r] /= H[r]; coefs(r, n - k - 1) *= scale[r]; } } // Drop the pieces the extension added, keeping for each remaining piece the // N basis functions that are nonzero on it. octave_idx_type kept = pieces - 2 * deg; base.bcoefs = Matrix (n * kept, n, 0.0); for (octave_idx_type p = 0; p < kept; p++) { for (octave_idx_type i = 0; i < n; i++) { octave_idx_type src = n * (p + 1) + i * deg - 1; for (octave_idx_type j = 0; j < n; j++) { base.bcoefs(p * n + i, j) = coefs(src, j); } } } return base; } // One predictor's design, factorised once for the whole fit. class SplineFitter { public: SplineBasis basis; Matrix Ur; // used x rank, the left singular vectors Matrix Wr; // dim x rank, V scaled by 1 / sigma Array used; // rows that carry finite data octave_idx_type nobs; bool all_used; // Fit one response: PRED is its projection on the spline space at every // observation, U the B-spline coefficients of that projection. Rows that // were dropped predict NaN, which is what evaluating the fitted spline at a // missing predictor returns. void fit_round (const ColumnVector& y, ColumnVector& pred, ColumnVector& u) const { octave_idx_type m = Ur.rows (); octave_idx_type r = Ur.columns (); ColumnVector z (r, 0.0); for (octave_idx_type k = 0; k < r; k++) { double s = 0.0; for (octave_idx_type i = 0; i < m; i++) { s += Ur(i, k) * y(all_used ? i : used(i)); } z(k) = s; } pred = ColumnVector (nobs, octave::numeric_limits::NaN ()); for (octave_idx_type i = 0; i < m; i++) { double s = 0.0; for (octave_idx_type k = 0; k < r; k++) { s += Ur(i, k) * z(k); } pred(all_used ? i : used(i)) = s; } u = ColumnVector (basis.dim, 0.0); for (octave_idx_type i = 0; i < basis.dim; i++) { double s = 0.0; for (octave_idx_type k = 0; k < r; k++) { s += Wr(i, k) * z(k); } u(i) = s; } } // The piecewise polynomial coefficients of a spline given as B-spline // coefficients: on piece p the polynomial is the combination of the N basis // functions that reach it. Matrix coefs_from_u (const ColumnVector& u) const { octave_idx_type n = basis.n; Matrix coefs (basis.pieces, n, 0.0); for (octave_idx_type p = 0; p < basis.pieces; p++) { for (octave_idx_type j = 0; j < n; j++) { double s = 0.0; for (octave_idx_type i = 0; i < n; i++) { s += u(p + i) * basis.bcoefs(p * n + i, j); } coefs(p, j) = s; } } return coefs; } }; // Build the fitter for one predictor. KNOTS is either a piece count or an // explicit break vector, ORD is splinefit's order (the polynomial degree). // ROWOK marks the observations whose predictor and response are both finite; // the breaks are interpolated from those alone, as splinefit interpolates them // from the data left after it has dropped the rest. SplineFitter gam_make_fitter (const ColumnVector& x, const RowVector& knots, octave_idx_type ord, const boolNDArray& rowok, const string& caller) { SplineFitter F; octave_idx_type n = ord + 1; F.nobs = x.numel (); octave_idx_type nused = 0; for (octave_idx_type i = 0; i < F.nobs; i++) { if (rowok(i) && ! octave::math::isnan (x(i))) { nused++; } } if (nused == 0) { error ("%s: there must be at least one data point.", caller.c_str ()); } F.used = Array (dim_vector (nused, 1)); octave_idx_type c = 0; for (octave_idx_type i = 0; i < F.nobs; i++) { if (rowok(i) && ! octave::math::isnan (x(i))) { F.used(c++) = i; } } F.all_used = (nused == F.nobs); ColumnVector xs (nused); for (octave_idx_type i = 0; i < nused; i++) { xs(i) = x(F.used(i)); } RowVector breaks; if (knots.numel () == 1) { ColumnVector xsorted = xs; sort (xsorted.fortran_vec (), xsorted.fortran_vec () + nused); breaks = gam_breaks_from_count (xsorted, (octave_idx_type) knots(0)); } else { breaks = knots; } breaks = gam_unique_breaks (breaks); if (breaks.numel () < 2) { error ("%s: at least two unique breaks are required.", caller.c_str ()); } F.basis = gam_splinebase (breaks, n); // Design matrix: row i holds the N basis functions that are nonzero at // x(i), at the columns their B-spline indices name. Matrix D (nused, F.basis.dim, 0.0); for (octave_idx_type i = 0; i < nused; i++) { octave_idx_type p = gam_interval (breaks, xs(i)); double dx = xs(i) - breaks(p); for (octave_idx_type k = 0; k < n; k++) { double v = 0.0; for (octave_idx_type j = 0; j < n; j++) { v = v * dx + F.basis.bcoefs(p * n + k, j); } D(i, p + k) = v; } } // Factorise once. The fit is the minimum-norm least squares solution, which // is what splinefit's u = y / A takes wherever the design has full rank. // // Where it does not -- fewer observations than basis functions, tied // predictor values, or a piece no observation falls in -- the solution is // not unique, and the direction that makes it so must be discarded rather // than divided by. Octave's operator keeps every singular value above // eps / 2, which on such a design means keeping one that is rounding noise // and scaling it by its reciprocal: on the four-observation fixture in // ClassificationGAM the coefficients came back at 1e14 and the residual at // 1.45, where no spline of that space can do worse than 0.71. The cut here // is the rank-revealing one, max (rows, columns) * eps, which rank () itself // uses. It changes nothing for a design of full rank and gives the genuine // minimum-norm fit for one that is deficient. octave::math::svd fact (D, octave::math::svd::Type::economy, octave::math::svd::Driver::GESVD); Matrix U = fact.left_singular_matrix (); Matrix V = fact.right_singular_matrix (); DiagMatrix S = fact.singular_values (); octave_idx_type nsv = S.rows () < S.columns () ? S.rows () : S.columns (); double smax = (nsv > 0) ? S(0, 0) : 0.0; octave_idx_type dmax = (nused > F.basis.dim) ? nused : F.basis.dim; double tol = smax * std::numeric_limits::epsilon () * dmax; octave_idx_type rank = 0; while (rank < nsv && S(rank, rank) > tol) { rank++; } F.Ur = Matrix (nused, rank); for (octave_idx_type i = 0; i < nused; i++) { for (octave_idx_type k = 0; k < rank; k++) { F.Ur(i, k) = U(i, k); } } F.Wr = Matrix (F.basis.dim, rank); for (octave_idx_type i = 0; i < F.basis.dim; i++) { for (octave_idx_type k = 0; k < rank; k++) { F.Wr(i, k) = V(i, k) / S(k, k); } } return F; } // Evaluate a piecewise polynomial, as ppval does: Horner on the offset from // the interval's left break, with points outside the domain extrapolated from // the nearest piece. double gam_ppval (const RowVector& breaks, const Matrix& coefs, double v) { if (octave::math::isnan (v)) { return octave::numeric_limits::NaN (); } octave_idx_type p = gam_interval (breaks, v); double dx = v - breaks(p); double y = 0.0; for (octave_idx_type j = 0; j < coefs.columns (); j++) { y = y * dx + coefs(p, j); } return y; } // Pack the per-predictor splines as the 1 x P struct array of pp forms the // classdefs store and ppval consumes. octave_map gam_pack_params (const vector& breaks, const vector& coefs) { octave_idx_type p = (octave_idx_type) breaks.size (); Cell c_form (1, p), c_breaks (1, p), c_coefs (1, p); Cell c_pieces (1, p), c_order (1, p), c_dim (1, p); for (octave_idx_type j = 0; j < p; j++) { c_form(j) = octave_value ("pp"); c_breaks(j) = octave_value (breaks[j]); c_coefs(j) = octave_value (coefs[j]); c_pieces(j) = octave_value ((double) coefs[j].rows ()); c_order(j) = octave_value ((double) coefs[j].columns ()); c_dim(j) = octave_value (1.0); } octave_map params (dim_vector (1, p)); params.assign ("form", c_form); params.assign ("breaks", c_breaks); params.assign ("coefs", c_coefs); params.assign ("pieces", c_pieces); params.assign ("order", c_order); params.assign ("dim", c_dim); return params; } // What a fit produces, in the shape the learners store it. struct GamFit { octave_map params; ColumnVector res; Matrix RSS; double iterations; double intercept; }; // Build one fitter per predictor. Every round of either scheme works in // these same spaces, so this is the whole of the per-fit setup. vector gam_fitters (const Matrix& X, const ColumnVector& Y, const Matrix& knots, const Matrix& order, const string& caller) { octave_idx_type n = X.rows (); octave_idx_type d = X.columns (); boolNDArray rowok (dim_vector (n, 1)); for (octave_idx_type i = 0; i < n; i++) { rowok(i) = ! octave::math::isnan (Y(i)); } vector F; F.reserve (d); for (octave_idx_type j = 0; j < d; j++) { ColumnVector x (n); for (octave_idx_type i = 0; i < n; i++) { x(i) = X(i, j); } RowVector k (1); k(0) = knots(j); F.push_back (gam_make_fitter (x, k, (octave_idx_type) order(j), rowok, caller)); } return F; } // Gradient boosting on the log-odds, the scheme ClassificationGAM fits. Every // round takes one step of the negative gradient of the log loss and adds a // spline of it to each additive term; the terms accumulate because every round // fits the same breaks at the same order, so their polynomials add. GamFit gam_boost (const Matrix& X, const ColumnVector& Y, double Inter, const Matrix& knots, const Matrix& order, double lrate, octave_idx_type niter) { octave_idx_type n = X.rows (); octave_idx_type d = X.columns (); vector F = gam_fitters (X, Y, knots, order, "ClassificationGAM"); vector breaks (d); vector coefs (d); for (octave_idx_type j = 0; j < d; j++) { breaks[j] = F[j].basis.breaks; coefs[j] = Matrix (F[j].basis.pieces, F[j].basis.n, 0.0); } double intercept = log (Inter / (1 - Inter)); ColumnVector f (n, intercept); ColumnVector grad (n), pred, u; for (octave_idx_type it = 0; it < niter; it++) { for (octave_idx_type i = 0; i < n; i++) { grad(i) = Y(i) - 1.0 / (1.0 + exp (-f(i))); } ColumnVector fnew (n, 0.0); for (octave_idx_type j = 0; j < d; j++) { F[j].fit_round (grad, pred, u); Matrix rc = F[j].coefs_from_u (u); for (octave_idx_type p = 0; p < rc.rows (); p++) { for (octave_idx_type c = 0; c < rc.columns (); c++) { coefs[j](p, c) += lrate * rc(p, c); } } for (octave_idx_type i = 0; i < n; i++) { fnew(i) += lrate * pred(i); } } for (octave_idx_type i = 0; i < n; i++) { f(i) += fnew(i); } } GamFit out; out.res = ColumnVector (n); double rss = 0.0; for (octave_idx_type i = 0; i < n; i++) { out.res(i) = Y(i) - 1.0 / (1.0 + exp (-f(i))); rss += out.res(i) * out.res(i); } out.RSS = Matrix (1, 1, rss); out.params = gam_pack_params (breaks, coefs); out.iterations = (double) niter; out.intercept = intercept; return out; } // Backfitting, the scheme RegressionGAM fits. A cycle takes each predictor's // own contribution back out of the partial residual, refits it, and puts the // new one in; it stops when no term's residual sum of squares moves by more // than TOL. GamFit gam_backfit (const Matrix& X, const ColumnVector& Y, double Inter, const Matrix& knots, const Matrix& order, double tol, octave_idx_type maxiter) { octave_idx_type n = X.rows (); octave_idx_type d = X.columns (); vector F = gam_fitters (X, Y, knots, order, "RegressionGAM"); vector breaks (d); vector coefs (d); for (octave_idx_type j = 0; j < d; j++) { breaks[j] = F[j].basis.breaks; coefs[j] = Matrix (F[j].basis.pieces, F[j].basis.n, 0.0); } ColumnVector res (n); for (octave_idx_type i = 0; i < n; i++) { res(i) = Y(i) - Inter; } Matrix RSS (1, d, 0.0); Matrix RSSk (1, d, 0.0); vector contrib (d); ColumnVector pred, u; bool converged = false; octave_idx_type iter = 0; while (! (converged || iter > maxiter)) { iter++; for (octave_idx_type j = 0; j < d; j++) { if (iter > 1) { for (octave_idx_type i = 0; i < n; i++) { res(i) += contrib[j](i); } } F[j].fit_round (res, pred, u); coefs[j] = F[j].coefs_from_u (u); // The m-code's own formula, sum of absolute deviations squared over the // sample size. It is not a residual sum of squares despite the name and // is reproduced as it stands: it decides only when the loop stops. double s = 0.0; for (octave_idx_type i = 0; i < n; i++) { s += fabs (Y(i) - pred(i) - Inter); } RSSk(j) = fabs (s * s) / n; contrib[j] = pred; for (octave_idx_type i = 0; i < n; i++) { res(i) -= pred(i); } } converged = true; for (octave_idx_type j = 0; j < d; j++) { if (! (fabs (RSS(j) - RSSk(j)) <= tol)) { converged = false; } } RSS = RSSk; } GamFit out; out.res = res; out.RSS = RSS; out.params = gam_pack_params (breaks, coefs); out.iterations = (double) iter; out.intercept = Inter; return out; } statistics-release-1.9.2/src/gamboost.cpp000066400000000000000000001241471524624707500204660ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ // The boosted-tree engine shared by the GAM learners, beside the spline engine // in gam.cpp. A generalized additive model is additive by construction, so // every tree of the predictor phase splits on one predictor only. That is // what makes this small: no multivariate tree is needed, no surrogate splits, // no pruning, and the whole fitted shape function of a predictor is a step // function over that predictor's bins, whatever number of trees produced it. // // The scheme is Newton boosting on the deviance. Each round fits one tree per // predictor to the current gradient and Hessian, adds it at the running step // size, and recentres: a shape function is held to mean zero and the constant // it gives up is absorbed into the intercept, which is the usual GAM // identifiability convention and is why a fitted intercept moves away from the // value it was seeded with. // // The step is not fixed. When a round fails to reduce the deviance by enough // it is retried at half the step, and the fit stops when even the reduced step // cannot improve it. That is what MATLAB reports as "Unable to improve the // model fit" against "Terminated after training the requested number of // trees", measured on R2024a. The constants below are ours: the tolerance // MATLAB stops on is not recoverable from anything it reports, so this engine // documents its own rather than pretending to match one it cannot read. #include #include #include #include #include // The largest number of cut points a predictor is binned at. MATLAB reports // 255 edges for any predictor carrying more distinct values than that // (measured at 600 and at 2000), so a predictor with fewer distinct values is // split at every midpoint between them and a richer one at 255 quantiles. static const octave_idx_type GAMB_MAX_EDGES = 255; // How many rounds are allowed to pass without a meaningful improvement before // the fit is declared unable to improve, and what counts as meaningful, // relative to the deviance the window opened at. This is the patience form // scikit-learn and the Explainable Boosting Machine both use; it is measured // on the training deviance rather than on a held-out split, because a random // split would make the fit irreproducible and MATLAB's is deterministic. The // tolerance is relative and not absolute because this deviance spans fifteen // orders of magnitude over a run, where an absolute one would be meaningless. static const octave_idx_type GAMB_PATIENCE = 10; static const double GAMB_REL_TOL = 1e-6; // How many times a round may halve its step before it gives up on improving at // all. MATLAB reduces its step the same way, which is why its learn-rate // argument is named an *initial* rate; three is ours, its limit not being // recoverable from anything it reports. static const int GAMB_MAX_HALVINGS = 3; // The fewest observations a predictor tree's leaf may hold. Measured against // R2024a on 2026-08-25: over a fixture whose only useful split isolates the // top K points, MATLAB splits there for K of 5 and above and falls back to the // cut leaving 5 for every K below it, on fitcgam and fitrgam alike, with bin // edges identical to ours. It is fitrtree's own default, which is the tree a // GAM fits internally. The interaction fitter is not measured and does not // apply this. static const octave_idx_type GAMB_MIN_LEAF = 5; // One predictor reduced to bin indices, with the cut points that produced // them. A missing value gets bin -1 and takes no part in any split. struct BinnedPredictor { RowVector edges; // nbins - 1 cut points, ascending Array bin; // one per observation, -1 if missing octave_idx_type nbins; }; // Bin one predictor. The cut points are the midpoints between consecutive // distinct finite values, which is every split a tree could make; where that // would exceed GAMB_MAX_EDGES the midpoints are thinned to that many, taken at // equally spaced positions through the sorted distinct values so the cuts stay // where the data is rather than where its range is. // The detection grid is coarser than the fitting grid and its size is fixed: // MATLAB reports 7 cut points, 8 equal-frequency bins, at 60, 250, 1000 and // 4000 observations alike, and they sit on the octiles to every digit. static const octave_idx_type GAMB_PAIR_EDGES = 7; static BinnedPredictor gamb_bin (const ColumnVector& x, octave_idx_type maxedges) { octave_idx_type n = x.numel (); std::vector v; v.reserve (n); for (octave_idx_type i = 0; i < n; i++) { if (! octave::math::isnan (x(i))) { v.push_back (x(i)); } } std::sort (v.begin (), v.end ()); std::vector sorted (v); // the data, still with its repeats v.erase (std::unique (v.begin (), v.end ()), v.end ()); octave_idx_type nd = (octave_idx_type) v.size (); octave_idx_type nf = (octave_idx_type) sorted.size (); BinnedPredictor B; // A constant predictor admits no split at all: one bin, no cut points. if (nd < 2) { B.edges = RowVector (0); B.nbins = 1; B.bin = Array (dim_vector (n, 1), 0); for (octave_idx_type i = 0; i < n; i++) { B.bin(i) = octave::math::isnan (x(i)) ? -1 : 0; } return B; } octave_idx_type ne = nd - 1; if (ne > maxedges) { ne = maxedges; } B.edges = RowVector (ne); if (ne == nd - 1) { // Below the cap every midpoint between consecutive distinct values is a // cut, so the bins are as fine as any split could be. MATLAB, scikit-learn // and the Explainable Boosting Machine all agree on this rule to the last // digit, and it is the case almost every predictor falls into. for (octave_idx_type k = 0; k < ne; k++) { B.edges(k) = 0.5 * (v[k] + v[k + 1]); } } else { // Above the cap the cuts are equally spaced through the observations // rather than through the distinct values, so the bins carry equal counts. // Each one is placed midway between the two order statistics that bracket // its position, which keeps the convention the same as below the cap. // MATLAB's grid is equal-frequency too but its interpolation differs: // measured on 1000 standard normal draws the two agree to 0.067 at worst // against 0.78 for a spread through the distinct values, so this is the // right family and not the exact member. for (octave_idx_type k = 0; k < ne; k++) { double r = (double) (k + 1) * nf / (double) (ne + 1); octave_idx_type lo = (octave_idx_type) std::floor (r) - 1; if (lo < 0) { lo = 0; } if (lo > nf - 2) { lo = nf - 2; } B.edges(k) = 0.5 * (sorted[lo] + sorted[lo + 1]); } } // Thinning can put two cuts at the same place when the distinct values are // very unevenly spaced; drop the repeats so every bin can be reached. octave_idx_type keep = 0; for (octave_idx_type k = 0; k < ne; k++) { if (keep == 0 || B.edges(k) > B.edges(keep - 1)) { B.edges(keep++) = B.edges(k); } } if (keep < ne) { RowVector e (keep); for (octave_idx_type k = 0; k < keep; k++) { e(k) = B.edges(k); } B.edges = e; ne = keep; } B.nbins = ne + 1; B.bin = Array (dim_vector (n, 1), 0); for (octave_idx_type i = 0; i < n; i++) { if (octave::math::isnan (x(i))) { B.bin(i) = -1; continue; } // The bin is the count of cut points at or below the value: a binary // search, since the cut points are ascending. octave_idx_type lo = 0; octave_idx_type hi = ne; while (lo < hi) { octave_idx_type mid = lo + (hi - lo) / 2; if (x(i) > B.edges(mid)) { lo = mid + 1; } else { hi = mid; } } B.bin(i) = lo; } return B; } // One contiguous run of bins, as the tree grows it. struct BinRegion { octave_idx_type lo; // first bin, inclusive octave_idx_type hi; // last bin, inclusive double gain; // best gain available from splitting it octave_idx_type cut; // bin the split would end the left side at }; // The Newton gain of splitting a region whose gradient and Hessian totals are // (GL, HL) on the left and (GR, HR) on the right. A leaf's value is -G/H, so // this is the deviance drop the split buys. static inline double gamb_gain (double GL, double HL, double GR, double HR) { const double eps = 1e-12; if (HL <= eps || HR <= eps) { return -1.0; } return GL * GL / HL + GR * GR / HR - (GL + GR) * (GL + GR) / (HL + HR); } // Find the best cut inside a region, given the prefix sums of the gradient, the // Hessian and the observation count over bins. Returns the gain and sets CUT // to the last bin of the left side; a region that cannot be split usefully // returns a gain of -1. // // A cut leaving fewer than GAMB_MIN_LEAF observations on either side is not // considered, whatever it gains. The Hessian guard in gamb_gain cannot stand // in for this: a logistic Hessian is p*(1-p) and says nothing about how many // observations produced it, so a single well separated point can carry enough // curvature to look like a leaf worth having. static double gamb_best_cut (const std::vector& G, const std::vector& H, const std::vector& C, octave_idx_type lo, octave_idx_type hi, octave_idx_type& cut) { double best = -1.0; cut = -1; double Gtot = G[hi + 1] - G[lo]; double Htot = H[hi + 1] - H[lo]; double Ctot = C[hi + 1] - C[lo]; for (octave_idx_type b = lo; b < hi; b++) { double CL = C[b + 1] - C[lo]; if (CL < GAMB_MIN_LEAF || Ctot - CL < GAMB_MIN_LEAF) { continue; } double GL = G[b + 1] - G[lo]; double HL = H[b + 1] - H[lo]; double g = gamb_gain (GL, HL, Gtot - GL, Htot - HL); if (g > best) { best = g; cut = b; } } return best; } // Fit one tree to a single binned predictor and accumulate its leaf values, // scaled by the step, into VAL. The tree is grown best-first: the region // offering the largest gain is split, up to MAXSPLITS splits in all, which is // what MATLAB's MaxNumSplitsPerPredictor counts. With the default of 1 this // is a stump. static void gamb_fit_tree (const BinnedPredictor& B, const ColumnVector& grad, const ColumnVector& hess, octave_idx_type maxsplits, double step, ColumnVector& val) { octave_idx_type n = grad.numel (); octave_idx_type nb = B.nbins; // Gradient and Hessian totals per bin, then their prefix sums, so any // region's totals are one subtraction. std::vector G (nb + 1, 0.0); std::vector H (nb + 1, 0.0); std::vector C (nb + 1, 0.0); for (octave_idx_type i = 0; i < n; i++) { octave_idx_type b = B.bin(i); if (b < 0) { continue; } G[b + 1] += grad(i); H[b + 1] += hess(i); C[b + 1] += 1.0; } for (octave_idx_type b = 0; b < nb; b++) { G[b + 1] += G[b]; H[b + 1] += H[b]; C[b + 1] += C[b]; } std::vector leaves; BinRegion root; root.lo = 0; root.hi = nb - 1; root.gain = gamb_best_cut (G, H, C, root.lo, root.hi, root.cut); leaves.push_back (root); for (octave_idx_type s = 0; s < maxsplits; s++) { // The leaf that buys the most. std::size_t pick = 0; double best = -1.0; for (std::size_t k = 0; k < leaves.size (); k++) { if (leaves[k].gain > best) { best = leaves[k].gain; pick = k; } } if (best <= 0.0) { break; } BinRegion left, right; left.lo = leaves[pick].lo; left.hi = leaves[pick].cut; right.lo = leaves[pick].cut + 1; right.hi = leaves[pick].hi; left.gain = gamb_best_cut (G, H, C, left.lo, left.hi, left.cut); right.gain = gamb_best_cut (G, H, C, right.lo, right.hi, right.cut); leaves[pick] = left; leaves.push_back (right); } // Every leaf contributes its Newton step to the bins it covers. for (std::size_t k = 0; k < leaves.size (); k++) { double Gk = G[leaves[k].hi + 1] - G[leaves[k].lo]; double Hk = H[leaves[k].hi + 1] - H[leaves[k].lo]; if (Hk <= 1e-12) { continue; } double leaf = step * Gk / Hk; for (octave_idx_type b = leaves[k].lo; b <= leaves[k].hi; b++) { val(b) += leaf; } } } // What a boosted fit produces, in the shape the learners store it. A shape // function is a step function: VALUE[j](b) is what predictor j contributes for // any observation falling in its bin b, so prediction is a lookup and does not // depend on how many trees built it. struct GamBoostFit { double intercept; std::vector edges; std::vector value; octave_idx_type ntrees; std::string reason; double deviance; ColumnVector residuals; }; // The bin a value falls in, for a predictor already binned at EDGES. Matches // the binary search gamb_bin used, so a training row bins the same way at // predict time; a missing value has no bin and is signalled by -1. static octave_idx_type gamb_bin_of (const RowVector& edges, double v) { if (octave::math::isnan (v)) { return -1; } octave_idx_type lo = 0; octave_idx_type hi = edges.numel (); while (lo < hi) { octave_idx_type mid = lo + (hi - lo) / 2; if (v > edges(mid)) { lo = mid + 1; } else { hi = mid; } } return lo; } // The additive prediction of a fitted model at one row of X. A predictor that // is missing contributes nothing rather than poisoning the sum, which is what // a tree does with a missing value it was never given a surrogate for. static double gamb_predict_row (const GamBoostFit& F, const Matrix& X, octave_idx_type i) { double f = F.intercept; for (std::size_t j = 0; j < F.value.size (); j++) { octave_idx_type b = gamb_bin_of (F.edges[j], X(i, (octave_idx_type) j)); if (b >= 0) { f += F.value[j](b); } } return f; } // The deviance of the current additive prediction. For a classifier that is // -2 times the Bernoulli log likelihood, which is the quantity MATLAB reports // and which starts at 2 n log 2 for a balanced response; for a regression it // is the residual sum of squares, the Gaussian deviance up to the scale. static double gamb_deviance (const ColumnVector& Y, const ColumnVector& f, int method) { octave_idx_type n = Y.numel (); double dev = 0.0; if (method == 1) { for (octave_idx_type i = 0; i < n; i++) { double p = 1.0 / (1.0 + std::exp (-f(i))); // A saturated fit puts p at 0 or 1 exactly; clamp so the log is finite // and the deviance keeps falling instead of turning into a NaN. if (p < 1e-300) { p = 1e-300; } if (p > 1.0 - 1e-16) { p = 1.0 - 1e-16; } dev += Y(i) * std::log (p) + (1.0 - Y(i)) * std::log (1.0 - p); } dev *= -2.0; } else { for (octave_idx_type i = 0; i < n; i++) { double r = Y(i) - f(i); dev += r * r; } } return dev; } // Newton boosting of one tree per predictor per round. METHOD 1 fits the // logistic deviance, as a classifier is fitted, and METHOD 2 the squared // error, as a regression is fitted; the two differ only in the seed, the // gradient and the Hessian, so the loop is shared. static GamBoostFit gamb_boost (const Matrix& X, const ColumnVector& Y, int method, octave_idx_type maxtrees, double lrate, octave_idx_type maxsplits, int verbose, octave_idx_type numprint, const ColumnVector *F0 = nullptr) { octave_idx_type n = X.rows (); octave_idx_type d = X.columns (); std::vector B (d); for (octave_idx_type j = 0; j < d; j++) { ColumnVector xj (n); for (octave_idx_type i = 0; i < n; i++) { xj(i) = X(i, j); } B[j] = gamb_bin (xj, GAMB_MAX_EDGES); } GamBoostFit F; F.edges.resize (d); F.value.resize (d); for (octave_idx_type j = 0; j < d; j++) { F.edges[j] = B[j].edges; F.value[j] = ColumnVector (B[j].nbins, 0.0); } // The seed. A classifier starts at zero, an even chance, and a regression at // the response mean; boosting moves the first, because recentring hands it // the constants the shape functions give up, and leaves the second where it // is, because a squared-error residual already has mean zero and there is // nothing to hand over. // // Zero and not the log-odds of the response mean, which is what a classifier // seeded here until 2026-08-25. Measured against R2024a on a response with // a mean of 0.4, MATLAB's fitted intercept is exactly -0.4, which is the // mean of the working response the first cycle forms from p = 1/2, and not // the -0.40546510810816 a log-odds seed gives. A balanced response hides // the difference, both being zero there. // // Given F0 there is no seed to find: the caller is continuing a fit and // hands over the prediction it reached. What comes back is then the // increment, shape values to add to the ones already held and an intercept // to add to the one already there, which is the contract gamb_boost_inter // works to as well. A round starts at LRATE whatever its number, so the // trees this adds are the trees a longer run would have added. ColumnVector f (n); if (F0 != nullptr) { F.intercept = 0.0; f = *F0; } else { double ybar = 0.0; for (octave_idx_type i = 0; i < n; i++) { ybar += Y(i); } ybar /= (double) n; if (method == 1) { if (ybar <= 0.0 || ybar >= 1.0) { // A single-class response has an infinite log-odds and no gradient to // follow; the fit is the constant and every shape function stays flat. F.intercept = (ybar <= 0.0) ? -octave::numeric_limits::Inf () : octave::numeric_limits::Inf (); F.ntrees = 0; F.reason = "Unable to improve the model fit."; F.deviance = 0.0; F.residuals = ColumnVector (n, 0.0); return F; } F.intercept = 0.0; } else { F.intercept = ybar; } f = ColumnVector (n, F.intercept); } double dev = gamb_deviance (Y, f, method); // The printed trace, in the columns MATLAB prints. RelTol here is the // relative improvement the round bought, which is what the patience test // reads. MATLAB prints a quantity of its own under that heading which is // not recoverable from anything it reports, so this one is ours and the // documentation says so rather than implying they agree. if (verbose > 0) { static const char *bar = "|========================================================|\n"; static const char *hdr = "| Type | NumTrees | Deviance | RelTol | LearnRate |\n"; octave_stdout << bar << hdr << bar << "| 1D| 0|" << std::setw (12) << dev << "| - | - |\n"; } ColumnVector grad (n), hess (n); std::vector devhist; devhist.push_back (dev); F.ntrees = 0; F.reason = "Terminated after training the requested number of trees."; for (octave_idx_type t = 0; t < maxtrees; t++) { // A round is tried at the running step and retried at half of it while it // fails to earn its place, which is the shape of MATLAB's own reported // learn rate: constant until the fit plateaus, then reduced once or more // on the last round it manages to accept. bool accepted = false; double step = lrate; for (int h = 0; h <= GAMB_MAX_HALVINGS; h++) { std::vector trial (d); ColumnVector fnew = f; double shift = 0.0; // Local scoring. The working response and the weights are formed ONCE // from the fit the round starts at and then held fixed across the whole // cycle, which is what MATLAB does: // // w = p * (1 - p) the curvature at the current fit // z = f + (Y - p) / w the response a weighted least squares sees // // and each predictor is then fitted to what is left of Z, under those // frozen weights. Passing W * (Z - FNEW) as the gradient and W as the // Hessian makes the tree's own SUM(G)/SUM(H) the weighted mean of the // partial residual, so the fitter needs no special case. // // Z is never formed. Written out, // // W * (Z - FNEW) = (Y - p) - W * (FNEW - F) // // which is the same number with no division by W at all. That matters // once the fit separates the classes: W goes to zero, Z to infinity, and // a floor under W to keep the quotient finite caps the step and stalls // the tail. The product is finite whatever W does. // // Recomputing W after every predictor, which is what this did until // 2026-08-25, is a defensible algorithm and not what MATLAB has. It // takes a full Newton step per predictor and so counts the curvature the // earlier predictors of the same cycle already spent, which overshoots: // on the two-predictor fixture the second tree's step came out 1.1625 // against MATLAB's 1.0423, and the fit was consistently the worse of the // two at equal budget. At one predictor there is no cycle and the two // rules agree exactly, which is why this went unseen until the boosted // engine became the default and several predictors could be compared. // // For squared error the two are the same rule written twice: Z is Y and // W is one, so W * (Z - FNEW) is the recomputed gradient. The regression // fit is unchanged by any of this, and was measured against R2024a to be // exact before and after. ColumnVector r0 (n), ww (n); for (octave_idx_type i = 0; i < n; i++) { if (method == 1) { double p = 1.0 / (1.0 + std::exp (-f(i))); ww(i) = p * (1.0 - p); r0(i) = Y(i) - p; } else { ww(i) = 1.0; r0(i) = Y(i) - f(i); } } // The predictors are fitted in sequence and not in parallel: each tree // sees what the ones before it have already taken out of the working // response. Fitting them all to the same residual and adding them would // overshoot by a factor of the predictor count, and it would also drag // the intercept off the response mean in a regression, where MATLAB // holds it there. for (octave_idx_type j = 0; j < d; j++) { for (octave_idx_type i = 0; i < n; i++) { grad(i) = r0(i) - ww(i) * (fnew(i) - f(i)); hess(i) = ww(i); } trial[j] = ColumnVector (B[j].nbins, 0.0); gamb_fit_tree (B[j], grad, hess, maxsplits, step, trial[j]); // The model value the tree just added, before any bookkeeping. for (octave_idx_type i = 0; i < n; i++) { if (B[j].bin(i) >= 0) { fnew(i) += trial[j](B[j].bin(i)); } } // Recentre over the observations, not over the bins: a shape function // is held to mean zero on the data it was fitted to, and the constant // it gives up goes to the intercept. This moves value between the two // and leaves the prediction alone, which is why a regression intercept // stays at the response mean while a classifier's walks away from its // seed: a squared-error residual keeps mean zero from round to round // and hands over nothing, a logistic one does not. // // The mean is taken under the cycle's own weights, which is the mean a // weighted least squares holds to zero. A plain observation mean is // the same thing on the first cycle, where every weight is 1/4, and // parts from it on every cycle after: it left the intercept 0.003 out // at two cycles and 0.0006 at three, against R2024a, with every shape // function already exact. Only the split between the intercept and // the shape functions moves, never the prediction. double sw = 0.0; double sv = 0.0; for (octave_idx_type i = 0; i < n; i++) { if (B[j].bin(i) >= 0) { sw += ww(i); sv += ww(i) * trial[j](B[j].bin(i)); } } if (sw > 0.0) { double m = sv / sw; for (octave_idx_type b = 0; b < B[j].nbins; b++) { trial[j](b) -= m; } shift += m; } } double devnew = gamb_deviance (Y, fnew, method); double devold = dev; // A round only has to improve the deviance at all to be kept. Whether // it improved enough to be worth continuing is the patience test below, // which looks across a window of rounds rather than at this one. if (devnew < dev) { for (octave_idx_type j = 0; j < d; j++) { for (octave_idx_type b = 0; b < B[j].nbins; b++) { F.value[j](b) += trial[j](b); } } F.intercept += shift; f = fnew; dev = devnew; F.ntrees++; accepted = true; if (verbose > 0 && (F.ntrees == 1 || F.ntrees % numprint == 0)) { double rel = (devold > 0.0) ? (devold - devnew) / devold : 0.0; octave_stdout << "| 1D|" << std::setw (10) << F.ntrees << "|" << std::setw (12) << devnew << "|" << std::setw (12) << rel << "|" << std::setw (12) << step << "|\n"; } break; } step *= 0.5; } if (! accepted) { F.reason = "Unable to improve the model fit."; break; } // Patience: stop once a whole window of rounds has failed to better the // deviance the window opened at by more than the tolerance. A single // round that buys little is not evidence of convergence, since the gain // from round to round is noisy; a window of them is. devhist.push_back (dev); octave_idx_type h = (octave_idx_type) devhist.size (); if (h > GAMB_PATIENCE) { double ref = devhist[(std::size_t) (h - 1 - GAMB_PATIENCE)]; double need = ref - GAMB_REL_TOL * std::fabs (ref); bool better = false; for (octave_idx_type k = h - GAMB_PATIENCE; k < h; k++) { if (devhist[(std::size_t) k] < need) { better = true; break; } } if (! better) { F.reason = "Unable to improve the model fit."; break; } } } F.deviance = dev; F.residuals = ColumnVector (n); for (octave_idx_type i = 0; i < n; i++) { if (method == 1) { F.residuals(i) = Y(i) - 1.0 / (1.0 + std::exp (-f(i))); } else { F.residuals(i) = Y(i) - f(i); } } return F; } // What the interaction search reports for one candidate pair. The p-value is // not computed here: turning F into a probability needs the incomplete beta, // and the package already ships a MATLAB-verified fcdf, so the caller does it // there rather than a second implementation being carried in C++. Selecting // which pairs to keep is policy and belongs beside it. struct GamPairStat { octave_idx_type j; octave_idx_type k; double F; double df1; double df2; }; // Test one pair for an interaction the additive terms have not already // explained. The residuals of the predictor phase are laid on the coarse // grid of the two predictors and decomposed as a two-way layout: what the rows // explain, what the columns explain, and what only the cells explain. The // last is the interaction, and the F ratio against the within-cell error is // what ranks the pair. // // The decomposition is sequential (Type I): with unequal cell counts the row // and column sums of squares are not orthogonal, so this is an approximation // rather than an identity, and a small negative interaction term is clamped to // zero. That is sound for ranking candidates, which is all it is used for. static GamPairStat gamb_pair_stat (const BinnedPredictor& Bj, const BinnedPredictor& Bk, const ColumnVector& r, octave_idx_type j, octave_idx_type k) { octave_idx_type n = r.numel (); octave_idx_type rj = Bj.nbins; octave_idx_type rk = Bk.nbins; std::vector csum ((std::size_t) (rj * rk), 0.0); std::vector ccnt ((std::size_t) (rj * rk), 0.0); std::vector rsum ((std::size_t) rj, 0.0); std::vector rcnt ((std::size_t) rj, 0.0); std::vector ksum ((std::size_t) rk, 0.0); std::vector kcnt ((std::size_t) rk, 0.0); double tot = 0.0; double cnt = 0.0; for (octave_idx_type i = 0; i < n; i++) { octave_idx_type a = Bj.bin(i); octave_idx_type b = Bk.bin(i); if (a < 0 || b < 0) { continue; } std::size_t c = (std::size_t) (a * rk + b); csum[c] += r(i); ccnt[c] += 1.0; rsum[(std::size_t) a] += r(i); rcnt[(std::size_t) a] += 1.0; ksum[(std::size_t) b] += r(i); kcnt[(std::size_t) b] += 1.0; tot += r(i); cnt += 1.0; } GamPairStat S; S.j = j; S.k = k; S.F = 0.0; S.df1 = 0.0; S.df2 = 0.0; if (cnt < 4.0) { return S; } double gm = tot / cnt; double sscell = 0.0; octave_idx_type ncell = 0; for (std::size_t c = 0; c < csum.size (); c++) { if (ccnt[c] > 0.0) { double m = csum[c] / ccnt[c]; sscell += ccnt[c] * (m - gm) * (m - gm); ncell++; } } double ssrow = 0.0; octave_idx_type nrow = 0; for (std::size_t a = 0; a < rsum.size (); a++) { if (rcnt[a] > 0.0) { double m = rsum[a] / rcnt[a]; ssrow += rcnt[a] * (m - gm) * (m - gm); nrow++; } } double sscol = 0.0; octave_idx_type ncol = 0; for (std::size_t b = 0; b < ksum.size (); b++) { if (kcnt[b] > 0.0) { double m = ksum[b] / kcnt[b]; sscol += kcnt[b] * (m - gm) * (m - gm); ncol++; } } double ssint = sscell - ssrow - sscol; if (ssint < 0.0) { ssint = 0.0; } double sserr = 0.0; for (octave_idx_type i = 0; i < n; i++) { octave_idx_type a = Bj.bin(i); octave_idx_type b = Bk.bin(i); if (a < 0 || b < 0) { continue; } std::size_t c = (std::size_t) (a * rk + b); double m = csum[c] / ccnt[c]; sserr += (r(i) - m) * (r(i) - m); } double df1 = (double) ((nrow - 1) * (ncol - 1)); double df2 = cnt - (double) ncell; if (df1 <= 0.0 || df2 <= 0.0 || sserr <= 0.0) { return S; } S.F = (ssint / df1) / (sserr / df2); S.df1 = df1; S.df2 = df2; return S; } // One pair's fitted interaction surface: a value per cell of the two coarse // grids, accumulated over every tree that was boosted onto it. The surface is // held on the DETECTION grid rather than the fitting grid of the main effects. // MATLAB reports exactly two grids, so there is no third for interactions to // live on; a tree limited to MaxNumSplitsPerInteraction splits carves at most // that many regions plus one, so resolution past a handful of bins per axis // buys nothing; and the Explainable Boosting Machine bins its interactions // coarser than its main effects for the same reason. It also keeps the // surface at 64 doubles instead of the 520 KB a 255 by 255 accumulator would // need for every pair. struct GamInterTerm { octave_idx_type j; octave_idx_type k; Matrix value; // nbins(j) x nbins(k) }; // A rectangle of the two-dimensional bin grid, as the tree grows it. struct GridRegion { octave_idx_type r0, r1, c0, c1; double gain; int dim; // 0 splits rows, 1 splits columns octave_idx_type cut; // last row or column of the near side }; // Totals over a rectangle, from an integral image with one row and column of // leading zeros. static inline double gamb_rect (const Matrix& I, octave_idx_type r0, octave_idx_type r1, octave_idx_type c0, octave_idx_type c1) { return I(r1 + 1, c1 + 1) - I(r0, c1 + 1) - I(r1 + 1, c0) + I(r0, c0); } // The best cut of a rectangle, over both directions. Returns the gain and // sets the direction and the position; a rectangle that cannot be cut usefully // returns -1. static double gamb_best_cut2 (const Matrix& IG, const Matrix& IH, GridRegion& R) { double best = -1.0; R.dim = 0; R.cut = -1; double Gt = gamb_rect (IG, R.r0, R.r1, R.c0, R.c1); double Ht = gamb_rect (IH, R.r0, R.r1, R.c0, R.c1); for (octave_idx_type a = R.r0; a < R.r1; a++) { double GL = gamb_rect (IG, R.r0, a, R.c0, R.c1); double HL = gamb_rect (IH, R.r0, a, R.c0, R.c1); double g = gamb_gain (GL, HL, Gt - GL, Ht - HL); if (g > best) { best = g; R.dim = 0; R.cut = a; } } for (octave_idx_type b = R.c0; b < R.c1; b++) { double GL = gamb_rect (IG, R.r0, R.r1, R.c0, b); double HL = gamb_rect (IH, R.r0, R.r1, R.c0, b); double g = gamb_gain (GL, HL, Gt - GL, Ht - HL); if (g > best) { best = g; R.dim = 1; R.cut = b; } } return best; } // Fit one tree over a pair's grid and accumulate its leaf values, scaled by // the step, into VAL. Grown best-first like the univariate trees, and split // in whichever direction buys more, which is what lets a pair term represent // something neither predictor could alone. static void gamb_fit_tree2 (const BinnedPredictor& Bj, const BinnedPredictor& Bk, const ColumnVector& grad, const ColumnVector& hess, octave_idx_type maxsplits, double step, Matrix& val) { octave_idx_type n = grad.numel (); octave_idx_type rj = Bj.nbins; octave_idx_type rk = Bk.nbins; Matrix IG (rj + 1, rk + 1, 0.0); Matrix IH (rj + 1, rk + 1, 0.0); for (octave_idx_type i = 0; i < n; i++) { octave_idx_type a = Bj.bin(i); octave_idx_type b = Bk.bin(i); if (a < 0 || b < 0) { continue; } IG(a + 1, b + 1) += grad(i); IH(a + 1, b + 1) += hess(i); } for (octave_idx_type a = 1; a <= rj; a++) { for (octave_idx_type b = 1; b <= rk; b++) { IG(a, b) += IG(a - 1, b) + IG(a, b - 1) - IG(a - 1, b - 1); IH(a, b) += IH(a - 1, b) + IH(a, b - 1) - IH(a - 1, b - 1); } } std::vector leaves; GridRegion root; root.r0 = 0; root.r1 = rj - 1; root.c0 = 0; root.c1 = rk - 1; root.gain = gamb_best_cut2 (IG, IH, root); leaves.push_back (root); for (octave_idx_type s = 0; s < maxsplits; s++) { std::size_t pick = 0; double best = -1.0; for (std::size_t q = 0; q < leaves.size (); q++) { if (leaves[q].gain > best) { best = leaves[q].gain; pick = q; } } if (best <= 0.0) { break; } GridRegion L = leaves[pick]; GridRegion Rr = leaves[pick]; if (leaves[pick].dim == 0) { L.r1 = leaves[pick].cut; Rr.r0 = leaves[pick].cut + 1; } else { L.c1 = leaves[pick].cut; Rr.c0 = leaves[pick].cut + 1; } L.gain = gamb_best_cut2 (IG, IH, L); Rr.gain = gamb_best_cut2 (IG, IH, Rr); leaves[pick] = L; leaves.push_back (Rr); } for (std::size_t q = 0; q < leaves.size (); q++) { double Gq = gamb_rect (IG, leaves[q].r0, leaves[q].r1, leaves[q].c0, leaves[q].c1); double Hq = gamb_rect (IH, leaves[q].r0, leaves[q].r1, leaves[q].c0, leaves[q].c1); if (Hq <= 1e-12) { continue; } double leaf = step * Gq / Hq; for (octave_idx_type a = leaves[q].r0; a <= leaves[q].r1; a++) { for (octave_idx_type b = leaves[q].c0; b <= leaves[q].c1; b++) { val(a, b) += leaf; } } } } // What the interaction phase produces. struct GamInterFit { std::vector edges; // the coarse grid, one per predictor std::vector term; // one per selected pair double shift; // what recentring handed the intercept octave_idx_type ntrees; std::string reason; double deviance; ColumnVector residuals; }; // Boost trees over the selected pairs, continuing from the additive prediction // the predictor phase left in F0 rather than refitting it. MATLAB's own trace // opens its interaction block at the deviance the predictor block ended on, so // the two phases share a running fit and differ only in what they are allowed // to split. The pairs are fitted in sequence within a round, for the same // reason the predictors are. static GamInterFit gamb_boost_inter (const Matrix& X, const ColumnVector& Y, const ColumnVector& F0, int method, const Matrix& pairs, octave_idx_type maxtrees, double lrate, octave_idx_type maxsplits) { octave_idx_type n = X.rows (); octave_idx_type d = X.columns (); octave_idx_type np = pairs.rows (); std::vector B ((std::size_t) d); for (octave_idx_type j = 0; j < d; j++) { ColumnVector xj (n); for (octave_idx_type i = 0; i < n; i++) { xj(i) = X(i, j); } B[(std::size_t) j] = gamb_bin (xj, GAMB_PAIR_EDGES); } GamInterFit F; F.edges.resize ((std::size_t) d); for (octave_idx_type j = 0; j < d; j++) { F.edges[(std::size_t) j] = B[(std::size_t) j].edges; } F.term.resize ((std::size_t) np); for (octave_idx_type q = 0; q < np; q++) { octave_idx_type j = (octave_idx_type) pairs(q, 0) - 1; octave_idx_type k = (octave_idx_type) pairs(q, 1) - 1; F.term[(std::size_t) q].j = j; F.term[(std::size_t) q].k = k; F.term[(std::size_t) q].value = Matrix (B[(std::size_t) j].nbins, B[(std::size_t) k].nbins, 0.0); } F.shift = 0.0; F.ntrees = 0; F.reason = "Terminated after training the requested number of trees."; ColumnVector f = F0; double dev = gamb_deviance (Y, f, method); std::vector devhist; devhist.push_back (dev); ColumnVector grad (n), hess (n); for (octave_idx_type t = 0; t < maxtrees; t++) { bool accepted = false; double step = lrate; for (int h = 0; h <= GAMB_MAX_HALVINGS; h++) { std::vector trial ((std::size_t) np); ColumnVector fnew = f; double shift = 0.0; for (octave_idx_type q = 0; q < np; q++) { octave_idx_type j = F.term[(std::size_t) q].j; octave_idx_type k = F.term[(std::size_t) q].k; const BinnedPredictor& Bj = B[(std::size_t) j]; const BinnedPredictor& Bk = B[(std::size_t) k]; for (octave_idx_type i = 0; i < n; i++) { if (method == 1) { double p = 1.0 / (1.0 + std::exp (-fnew(i))); grad(i) = Y(i) - p; hess(i) = p * (1.0 - p); if (hess(i) < 1e-12) { hess(i) = 1e-12; } } else { grad(i) = Y(i) - fnew(i); hess(i) = 1.0; } } trial[(std::size_t) q] = Matrix (Bj.nbins, Bk.nbins, 0.0); gamb_fit_tree2 (Bj, Bk, grad, hess, maxsplits, step, trial[(std::size_t) q]); for (octave_idx_type i = 0; i < n; i++) { octave_idx_type a = Bj.bin(i); octave_idx_type b = Bk.bin(i); if (a >= 0 && b >= 0) { fnew(i) += trial[(std::size_t) q](a, b); } } // Recentred like a shape function, and for the same reason: a pair // term carries only what the two predictors do together, and whatever // constant it picked up belongs to the intercept. double m = 0.0; octave_idx_type cnt = 0; for (octave_idx_type i = 0; i < n; i++) { octave_idx_type a = Bj.bin(i); octave_idx_type b = Bk.bin(i); if (a >= 0 && b >= 0) { m += trial[(std::size_t) q](a, b); cnt++; } } if (cnt > 0) { m /= (double) cnt; for (octave_idx_type a = 0; a < Bj.nbins; a++) { for (octave_idx_type b = 0; b < Bk.nbins; b++) { trial[(std::size_t) q](a, b) -= m; } } shift += m; } } double devnew = gamb_deviance (Y, fnew, method); if (devnew < dev) { for (octave_idx_type q = 0; q < np; q++) { octave_idx_type rj = F.term[(std::size_t) q].value.rows (); octave_idx_type rk = F.term[(std::size_t) q].value.columns (); for (octave_idx_type a = 0; a < rj; a++) { for (octave_idx_type b = 0; b < rk; b++) { F.term[(std::size_t) q].value(a, b) += trial[(std::size_t) q](a, b); } } } F.shift += shift; f = fnew; dev = devnew; F.ntrees++; accepted = true; break; } step *= 0.5; } if (! accepted) { F.reason = "Unable to improve the model fit."; break; } devhist.push_back (dev); octave_idx_type hn = (octave_idx_type) devhist.size (); if (hn > GAMB_PATIENCE) { double ref = devhist[(std::size_t) (hn - 1 - GAMB_PATIENCE)]; double need = ref - GAMB_REL_TOL * std::fabs (ref); bool better = false; for (octave_idx_type q = hn - GAMB_PATIENCE; q < hn; q++) { if (devhist[(std::size_t) q] < need) { better = true; break; } } if (! better) { F.reason = "Unable to improve the model fit."; break; } } } F.deviance = dev; F.residuals = ColumnVector (n); for (octave_idx_type i = 0; i < n; i++) { if (method == 1) { F.residuals(i) = Y(i) - 1.0 / (1.0 + std::exp (-f(i))); } else { F.residuals(i) = Y(i) - f(i); } } return F; } statistics-release-1.9.2/src/gamboostinter.cc000066400000000000000000000255321524624707500213310ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include "gamboost.cpp" DEFUN_DLD(gamboostinter, args, , "-*- texinfo -*-\n\ @deftypefn {statistics} {@var{Mdl} =} gamboostinter (@var{X}, @var{Y}, @\n\ @var{F0}, @var{Method}, @var{Pairs}, @var{NumTrees}, @var{LearnRate}, @\n\ @var{MaxNumSplits})\n\ \n\ Boost trees over selected pairs of predictors.\n\ \n\ @code{@var{Mdl} = gamboostinter (@dots{})} fits the interaction phase of a\n\ generalized additive model, continuing from the additive prediction the\n\ predictor phase left rather than refitting it. It is used by\n\ @code{ClassificationGAM} and @code{RegressionGAM}, and it is not meant to be\n\ called directly.\n\ \n\ @itemize\n\ @item @var{X} is an @math{NxP} numeric matrix of predictors and @var{Y} the\n\ @math{Nx1} response, as @code{gamboosttrain} takes them.\n\ \n\ @item @var{F0} is the @math{Nx1} additive prediction of the predictor phase.\n\ The interaction phase starts from it, so its deviance is where this phase\n\ begins.\n\ \n\ @item @var{Method} selects what is boosted, @qcode{1} the logistic deviance\n\ and @qcode{2} the squared error.\n\ \n\ @item @var{Pairs} is an @math{Mx2} matrix of predictor index pairs, one-based\n\ and within range. Choosing them is the caller's business; see\n\ @code{gamboostpairs}.\n\ \n\ @item @var{NumTrees}, @var{LearnRate} and @var{MaxNumSplits} are the\n\ interaction phase's own budget, initial step and split limit.\n\ @end itemize\n\ \n\ @var{Mdl} is a structure with the following fields.\n\ \n\ @itemize\n\ @item @qcode{PairBinEdges}, a @math{1xP} cell of the coarse cut points the\n\ surfaces are held on. Interactions are binned coarser than main effects: a\n\ tree limited to @var{MaxNumSplits} splits carves no more regions than that,\n\ so a finer grid buys nothing and costs memory in every pair.\n\ @item @qcode{PairValues}, a @math{1xM} cell of matrices, one value per\n\ cell of the pair's grid.\n\ @item @qcode{Intercept}, the constant the recentred surfaces gave up. Add it\n\ to the intercept of the predictor phase.\n\ @item @qcode{NumTrees}, @qcode{ReasonForTermination}, @qcode{Deviance} and\n\ @qcode{Residuals}, as @code{gamboosttrain} reports them.\n\ @end itemize\n\ \n\ @seealso{gamboosttrain, gamboostpairs, gamboostpredict}\n\ @end deftypefn") { if (args.length () != 8) { print_usage (); } if (! args(0).isnumeric () || args(0).iscomplex () || args(0).isempty ()) { error ("gamboostinter: X must be a numeric matrix."); } if (! args(1).isnumeric () || args(1).iscomplex () || args(1).isempty () || args(1).columns () != 1) { error ("gamboostinter: Y must be a numeric column vector."); } if (! args(2).isnumeric () || args(2).iscomplex () || args(2).isempty () || args(2).columns () != 1) { error ("gamboostinter: F0 must be a numeric column vector."); } if (args(0).rows () != args(1).rows () || args(0).rows () != args(2).rows ()) { error ("gamboostinter: X, Y and F0 must have the same number of rows."); } Matrix X = args(0).matrix_value (); ColumnVector Y = args(1).column_vector_value (); ColumnVector F0 = args(2).column_vector_value (); octave_idx_type d = X.columns (); if (! args(3).is_scalar_type () || ! args(3).isnumeric () || args(3).iscomplex ()) { error ("gamboostinter: Method must be a numeric scalar."); } int method = args(3).int_value (); if (method != 1 && method != 2) { error ("gamboostinter: Method must be 1 or 2."); } if (! args(4).isnumeric () || args(4).iscomplex () || args(4).isempty () || args(4).columns () != 2) { error ("gamboostinter: Pairs must be a numeric matrix with two columns."); } Matrix pairs = args(4).matrix_value (); for (octave_idx_type q = 0; q < pairs.rows (); q++) { for (octave_idx_type c = 0; c < 2; c++) { double v = pairs(q, c); if (v != std::floor (v) || v < 1.0 || v > (double) d) { error ("gamboostinter: Pairs must hold predictor indices " "between 1 and %d.", (int) d); } } if (pairs(q, 0) == pairs(q, 1)) { error ("gamboostinter: a pair must name two different predictors."); } } if (! args(5).is_scalar_type () || ! args(5).isnumeric () || args(5).iscomplex ()) { error ("gamboostinter: NumTrees must be a numeric scalar."); } octave_idx_type maxtrees = (octave_idx_type) args(5).int_value (); if (maxtrees < 1) { error ("gamboostinter: NumTrees must be a positive integer."); } if (! args(6).is_scalar_type () || ! args(6).isnumeric () || args(6).iscomplex ()) { error ("gamboostinter: LearnRate must be a numeric scalar."); } double lrate = args(6).scalar_value (); if (! (lrate > 0.0) || lrate > 1.0) { error ("gamboostinter: LearnRate must be greater than 0 and at most 1."); } if (! args(7).is_scalar_type () || ! args(7).isnumeric () || args(7).iscomplex ()) { error ("gamboostinter: MaxNumSplits must be a numeric scalar."); } octave_idx_type maxsplits = (octave_idx_type) args(7).int_value (); if (maxsplits < 1) { error ("gamboostinter: MaxNumSplits must be a positive integer."); } if (method == 1) { for (octave_idx_type i = 0; i < Y.numel (); i++) { if (Y(i) != 0.0 && Y(i) != 1.0) { error ("gamboostinter: Y must hold zeros and ones for Method 1."); } } } GamInterFit F = gamb_boost_inter (X, Y, F0, method, pairs, maxtrees, lrate, maxsplits); Cell edges (1, d); for (octave_idx_type j = 0; j < d; j++) { edges(j) = octave_value (F.edges[(std::size_t) j]); } octave_idx_type np = (octave_idx_type) F.term.size (); Cell values (1, np); for (octave_idx_type q = 0; q < np; q++) { values(q) = octave_value (F.term[(std::size_t) q].value); } octave_scalar_map Mdl; Mdl.assign ("PairBinEdges", octave_value (edges)); Mdl.assign ("PairValues", octave_value (values)); Mdl.assign ("Intercept", octave_value (F.shift)); Mdl.assign ("NumTrees", octave_value ((double) F.ntrees)); Mdl.assign ("ReasonForTermination", octave_value (F.reason)); Mdl.assign ("Deviance", octave_value (F.deviance)); Mdl.assign ("Residuals", octave_value (F.residuals)); return ovl (Mdl); } /* %!test %! ## Every selected pair gets a surface on the coarse grid of its two %! ## predictors, and the phase reports the usual fields. %! x = randn (200, 3); %! y = double (x(:,1) .* x(:,2) + 0.1 * randn (200, 1) > 0); %! M = gamboosttrain (x, y, 1, 20, 1, 1); %! f0 = gamboostpredict (M.BinEdges, M.ShapeValues, x, M.Intercept); %! I = gamboostinter (x, y, f0, 1, [1, 2], 20, 1, 4); %! assert_equal (numel (I.PairValues), 1); %! assert_equal (size (I.PairValues{1}), [8, 8]); %! assert_equal (numel (I.PairBinEdges), 3); %! assert_equal (numel (I.Residuals), 200); %!test %! ## The phase starts from the deviance the predictor phase left, so it can %! ## only improve on it. %! x = randn (200, 3); %! y = double (x(:,1) .* x(:,2) + 0.1 * randn (200, 1) > 0); %! M = gamboosttrain (x, y, 1, 20, 1, 1); %! f0 = gamboostpredict (M.BinEdges, M.ShapeValues, x, M.Intercept); %! I = gamboostinter (x, y, f0, 1, [1, 2], 30, 1, 4); %! assert_equal (I.Deviance <= M.Deviance, true); %!test %! ## A pair term is recentred like a shape function, so what it gives up is %! ## reported for the intercept rather than left inside the surface. %! x = randn (150, 2); %! y = double (x(:,1) .* x(:,2) > 0); %! M = gamboosttrain (x, y, 1, 20, 1, 1); %! f0 = gamboostpredict (M.BinEdges, M.ShapeValues, x, M.Intercept); %! I = gamboostinter (x, y, f0, 1, [1, 2], 25, 1, 4); %! assert_equal (isfinite (I.Intercept), true); %!test %! ## Several pairs are fitted in one call, each on its own grid. %! x = randn (200, 4); %! y = double (x(:,1) .* x(:,2) + x(:,3) .* x(:,4) > 0); %! M = gamboosttrain (x, y, 1, 20, 1, 1); %! f0 = gamboostpredict (M.BinEdges, M.ShapeValues, x, M.Intercept); %! I = gamboostinter (x, y, f0, 1, [1, 2; 3, 4], 20, 1, 4); %! assert_equal (numel (I.PairValues), 2); %!test %! ## The budget is a budget here too: a phase that stops improving says so. %! x = randn (120, 2); %! y = double (x(:,1) > 0); %! M = gamboosttrain (x, y, 1, 100, 1, 1); %! f0 = gamboostpredict (M.BinEdges, M.ShapeValues, x, M.Intercept); %! I = gamboostinter (x, y, f0, 1, [1, 2], 100000, 1, 4); %! assert_equal (I.ReasonForTermination, 'Unable to improve the model fit.'); %! assert_equal (I.NumTrees < 100000, true); %!test %! ## A regression pair term works the same way and lowers the residual sum %! ## of squares it was handed. %! x = randn (200, 2); %! y = x(:,1) .* x(:,2); %! M = gamboosttrain (x, y, 2, 20, 1, 1); %! f0 = gamboostpredict (M.BinEdges, M.ShapeValues, x, M.Intercept); %! I = gamboostinter (x, y, f0, 2, [1, 2], 40, 1, 4); %! assert_equal (I.Deviance < M.Deviance, true); %!error gamboostinter (1, 2, 3) %!error ... %! gamboostinter ('a', [1;0], [0;0], 1, [1,2], 10, 1, 4) %!error ... %! gamboostinter ([1,2;3,4], [1;0], [0;0;0], 1, [1,2], 10, 1, 4) %!error ... %! gamboostinter ([1,2;3,4], [1;0], [0;0], 3, [1,2], 10, 1, 4) %!error ... %! gamboostinter ([1,2;3,4], [1;0], [0;0], 1, [1,2,3], 10, 1, 4) %!error ... %! gamboostinter ([1,2;3,4], [1;0], [0;0], 1, [1,5], 10, 1, 4) %!error ... %! gamboostinter ([1,2;3,4], [1;0], [0;0], 1, [2,2], 10, 1, 4) %!error ... %! gamboostinter ([1,2;3,4], [1;0], [0;0], 1, [1,2], 0, 1, 4) %!error ... %! gamboostinter ([1,2;3,4], [1;0], [0;0], 1, [1,2], 10, 2, 4) %!error ... %! gamboostinter ([1,2;3,4], [1;0], [0;0], 1, [1,2], 10, 1, 0) %!error ... %! gamboostinter ([1,2;3,4], [1;2], [0;0], 1, [1,2], 10, 1, 4) */ statistics-release-1.9.2/src/gamboostpairs.cc000066400000000000000000000161711524624707500213250ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include "gamboost.cpp" DEFUN_DLD(gamboostpairs, args, , "-*- texinfo -*-\n\ @deftypefn {statistics} {@var{S} =} gamboostpairs (@var{X}, @var{R})\n\ \n\ Score every pair of predictors for an interaction.\n\ \n\ @code{@var{S} = gamboostpairs (@var{X}, @var{R})} lays the residuals @var{R}\n\ of a fitted additive model on the coarse grid of each pair of columns of\n\ @var{X} and returns the two-way analysis of variance @math{F} ratio testing\n\ what only the cells explain. It is used to rank candidate interactions for\n\ @code{ClassificationGAM} and @code{RegressionGAM}, and it is not meant to be\n\ called directly.\n\ \n\ The @math{p}-values are deliberately not computed here. Turning @math{F}\n\ into a probability needs @code{fcdf}, which the package already ships and\n\ which is verified against MATLAB, so the caller applies it rather than a\n\ second implementation being carried in the compiled engine. Which pairs to\n\ keep is policy and belongs beside that.\n\ \n\ @itemize\n\ @item @var{X} is an @math{NxP} numeric matrix of predictors, and @var{P} must\n\ be at least 2 for any pair to exist.\n\ \n\ @item @var{R} is the @math{Nx1} residual vector of the additive fit.\n\ @end itemize\n\ \n\ @var{S} is a structure with the following fields, one row per pair, ordered\n\ as @code{nchoosek} orders them.\n\ \n\ @itemize\n\ @item @qcode{Pairs}, the @math{Mx2} matrix of predictor index pairs.\n\ @item @qcode{F}, the @math{Mx1} vector of @math{F} ratios. A pair with too\n\ few observations, no spare degrees of freedom or no within-cell scatter\n\ scores @qcode{0}.\n\ @item @qcode{DF1} and @qcode{DF2}, the @math{Mx1} numerator and denominator\n\ degrees of freedom.\n\ @item @qcode{BinEdges}, a @math{1xP} cell of the coarse cut points each\n\ predictor was laid on. The grid is fixed at eight equal-frequency bins,\n\ which is what MATLAB reports for pair detection at every sample size.\n\ @end itemize\n\ \n\ @seealso{gamboosttrain, gamboostpredict, fcdf, ClassificationGAM}\n\ @end deftypefn") { if (args.length () != 2) { print_usage (); } if (! args(0).isnumeric () || args(0).iscomplex () || args(0).isempty ()) { error ("gamboostpairs: X must be a numeric matrix."); } if (! args(1).isnumeric () || args(1).iscomplex () || args(1).isempty () || args(1).columns () != 1) { error ("gamboostpairs: R must be a numeric column vector."); } if (args(0).rows () != args(1).rows ()) { error ("gamboostpairs: X and R must have the same number of rows."); } Matrix X = args(0).matrix_value (); ColumnVector R = args(1).column_vector_value (); octave_idx_type n = X.rows (); octave_idx_type d = X.columns (); if (d < 2) { error ("gamboostpairs: X must have at least two columns."); } std::vector B ((std::size_t) d); Cell edges (1, d); for (octave_idx_type j = 0; j < d; j++) { ColumnVector xj (n); for (octave_idx_type i = 0; i < n; i++) { xj(i) = X(i, j); } B[(std::size_t) j] = gamb_bin (xj, GAMB_PAIR_EDGES); edges(j) = octave_value (B[(std::size_t) j].edges); } octave_idx_type m = d * (d - 1) / 2; Matrix pairs (m, 2); ColumnVector F (m), DF1 (m), DF2 (m); octave_idx_type row = 0; for (octave_idx_type j = 0; j < d - 1; j++) { for (octave_idx_type k = j + 1; k < d; k++) { GamPairStat S = gamb_pair_stat (B[(std::size_t) j], B[(std::size_t) k], R, j, k); pairs(row, 0) = (double) (j + 1); pairs(row, 1) = (double) (k + 1); F(row) = S.F; DF1(row) = S.df1; DF2(row) = S.df2; row++; } } octave_scalar_map out; out.assign ("Pairs", octave_value (pairs)); out.assign ("F", octave_value (F)); out.assign ("DF1", octave_value (DF1)); out.assign ("DF2", octave_value (DF2)); out.assign ("BinEdges", octave_value (edges)); return ovl (out); } /* %!test %! ## Every pair is scored once, in nchoosek order, and the grid is reported. %! x = randn (200, 4); %! r = randn (200, 1); %! S = gamboostpairs (x, r); %! assert_equal (size (S.Pairs), [6, 2]); %! assert_equal (S.Pairs, nchoosek (1:4, 2)); %! assert_equal (numel (S.F), 6); %! assert_equal (numel (S.BinEdges), 4); %!test %! ## The detection grid is fixed at eight equal-frequency bins, so seven cut %! ## points, whatever the sample size. MATLAB reports seven at 60, 250, 1000 %! ## and 4000 observations alike. %! for n = [60, 250, 1000] %! S = gamboostpairs (randn (n, 2), randn (n, 1)); %! assert_equal (numel (S.BinEdges{1}), 7); %! endfor %!test %! ## The cut points are the octiles, which is what MATLAB's coincide with. %! x = [(1:800)', (1:800)']; %! S = gamboostpairs (x, randn (800, 1)); %! q = linspace (0, 1, 9); %! assert_equal (S.BinEdges{1}, quantile (x(:,1), q(2:end-1)), 1); %!test %! ## A planted interaction outscores every pair that carries none. The %! ## response depends on x1 * x2 alone, so that pair must rank first. %! rand ('seed', 7); %! randn ('seed', 7); %! x = randn (400, 4); %! r = x(:,1) .* x(:,2) + 0.1 * randn (400, 1); %! S = gamboostpairs (x, r); %! [~, best] = max (S.F); %! assert_equal (S.Pairs(best,:), [1, 2]); %!test %! ## Structureless residuals score low: an F ratio near one is what a pair %! ## with no interaction should give, and the planted pair above is orders %! ## above it. %! randn ('seed', 11); %! S = gamboostpairs (randn (400, 3), randn (400, 1)); %! assert_equal (max (S.F) < 5, true); %!test %! ## A pair with nothing to explain scores zero rather than a NaN: a constant %! ## residual leaves no within-cell scatter to divide by. %! S = gamboostpairs (randn (50, 2), ones (50, 1)); %! assert_equal (S.F, 0); %! assert_equal (S.DF1, 0); %!test %! ## Degrees of freedom follow the occupied grid, not its nominal size. %! x = [(1:200)', (1:200)']; %! S = gamboostpairs (x, randn (200, 1)); %! assert_equal (S.DF2 > 0, true); %! assert_equal (S.DF1 > 0, true); %!error gamboostpairs (1) %!error ... %! gamboostpairs ('a', [1;2]) %!error ... %! gamboostpairs ([1, 2; 3, 4], [1, 2]) %!error ... %! gamboostpairs ([1, 2; 3, 4], [1; 2; 3]) %!error ... %! gamboostpairs ([1; 2; 3], [1; 2; 3]) */ statistics-release-1.9.2/src/gamboostpredict.cc000066400000000000000000000307641524624707500216450ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include "gamboost.cpp" DEFUN_DLD(gamboostpredict, args, , "-*- texinfo -*-\n\ @deftypefn {statistics} {@var{Y} =} gamboostpredict (@var{BinEdges}, @\n\ @var{ShapeValues}, @var{X}, @var{Intercept})\n\ @deftypefnx {statistics} {@var{Y} =} gamboostpredict (@dots{}, @var{Link})\n\ @deftypefnx {statistics} {@var{Y} =} gamboostpredict (@dots{}, @var{Link}, @\n\ @var{PairBinEdges}, @var{PairValues}, @var{Pairs})\n\ \n\ Predict from a generalized additive model of boosted trees.\n\ \n\ @itemize\n\ @item @var{BinEdges} is a @math{1xP} cell of row vectors and\n\ @var{ShapeValues} a @math{1xP} cell of column vectors, as\n\ @code{gamboosttrain} returns them in the fields of the same names. Each\n\ shape function is a step function over its predictor's bins, so a term is\n\ evaluated by a lookup.\n\ \n\ @item @var{X} is an @math{NxP} numeric matrix with one column per additive\n\ term, and a count that does not match is an error. A missing value\n\ contributes nothing from that term rather than making the whole prediction\n\ @qcode{NaN}, which is what a tree does with a value it cannot place. A\n\ value outside the range the term was fitted over falls in the nearest bin,\n\ so a shape function is constant beyond its data rather than extrapolated.\n\ \n\ @item @var{Intercept} is the model's constant term.\n\ \n\ @item @var{Link}, if given, selects what the additive prediction is mapped\n\ through: @qcode{0} returns it as it stands and @qcode{1} takes it as a\n\ log-odds, returning the @math{Nx2} matrix of class probabilities whose\n\ second column is the logistic function of it. The default is @qcode{0}.\n\ \n\ @item @var{PairBinEdges}, @var{PairValues} and @var{Pairs} carry the\n\ interaction terms, as @code{gamboostinter} returns the first two and as it\n\ was given the third. Each pair contributes the value of the cell its two\n\ predictors fall in, and a pair with either predictor missing contributes\n\ nothing. All three must be given together or none of them.\n\ @end itemize\n\ \n\ @seealso{gamboosttrain, gampredict, ClassificationGAM, RegressionGAM}\n\ @end deftypefn") { octave_idx_type nargin = args.length (); if (nargin != 4 && nargin != 5 && nargin != 8) { print_usage (); } if (! args(0).iscell ()) { error ("gamboostpredict: BinEdges must be a cell array."); } if (! args(1).iscell ()) { error ("gamboostpredict: ShapeValues must be a cell array."); } Cell edges = args(0).cell_value (); Cell values = args(1).cell_value (); if (edges.numel () != values.numel ()) { error ("gamboostpredict: BinEdges and ShapeValues must have the same " "number of elements."); } if (! args(2).isnumeric () || args(2).iscomplex () || args(2).isempty ()) { error ("gamboostpredict: X must be a numeric matrix."); } Matrix X = args(2).matrix_value (); if (X.columns () != edges.numel ()) { error ("gamboostpredict: X must have one column per additive term."); } if (! args(3).is_scalar_type () || ! args(3).isnumeric () || args(3).iscomplex ()) { error ("gamboostpredict: Intercept must be a numeric scalar."); } double intercept = args(3).scalar_value (); int link = 0; if (nargin > 4) { if (! args(4).is_scalar_type () || ! args(4).isnumeric () || args(4).iscomplex ()) { error ("gamboostpredict: Link must be a numeric scalar."); } link = args(4).int_value (); if (link != 0 && link != 1) { error ("gamboostpredict: Link must be 0 or 1."); } } GamBoostFit F; F.intercept = intercept; octave_idx_type d = edges.numel (); F.edges.resize ((std::size_t) d); F.value.resize ((std::size_t) d); for (octave_idx_type j = 0; j < d; j++) { if (! edges(j).isnumeric () || edges(j).iscomplex ()) { error ("gamboostpredict: every BinEdges element must be numeric."); } if (! values(j).isnumeric () || values(j).iscomplex ()) { error ("gamboostpredict: every ShapeValues element must be numeric."); } F.edges[(std::size_t) j] = edges(j).row_vector_value (); F.value[(std::size_t) j] = values(j).column_vector_value (); if (F.value[(std::size_t) j].numel () != F.edges[(std::size_t) j].numel () + 1) { error ("gamboostpredict: term %d has %d values for %d cut points.", (int) j + 1, (int) F.value[(std::size_t) j].numel (), (int) F.edges[(std::size_t) j].numel ()); } } octave_idx_type n = X.rows (); ColumnVector y (n); for (octave_idx_type i = 0; i < n; i++) { y(i) = gamb_predict_row (F, X, i); } if (nargin == 8) { if (! args(5).iscell () || ! args(6).iscell ()) { error ("gamboostpredict: PairBinEdges and PairValues must be cell " "arrays."); } Cell pedges = args(5).cell_value (); Cell pvals = args(6).cell_value (); if (pedges.numel () != d) { error ("gamboostpredict: PairBinEdges must have one element per " "additive term."); } if (! args(7).isnumeric () || args(7).iscomplex () || args(7).columns () != 2) { error ("gamboostpredict: Pairs must be a numeric matrix with two " "columns."); } Matrix pairs = args(7).matrix_value (); if (pairs.rows () != pvals.numel ()) { error ("gamboostpredict: Pairs and PairValues must have the same " "number of rows."); } std::vector pe ((std::size_t) d); for (octave_idx_type j = 0; j < d; j++) { pe[(std::size_t) j] = pedges(j).row_vector_value (); } for (octave_idx_type q = 0; q < pairs.rows (); q++) { octave_idx_type j = (octave_idx_type) pairs(q, 0) - 1; octave_idx_type k = (octave_idx_type) pairs(q, 1) - 1; if (j < 0 || k < 0 || j >= d || k >= d) { error ("gamboostpredict: Pairs must hold predictor indices between " "1 and %d.", (int) d); } Matrix V = pvals(q).matrix_value (); for (octave_idx_type i = 0; i < n; i++) { octave_idx_type a = gamb_bin_of (pe[(std::size_t) j], X(i, j)); octave_idx_type b = gamb_bin_of (pe[(std::size_t) k], X(i, k)); if (a < 0 || b < 0) { continue; } if (a >= V.rows () || b >= V.columns ()) { error ("gamboostpredict: pair %d has a %dx%d surface, too small " "for its grid.", (int) q + 1, (int) V.rows (), (int) V.columns ()); } y(i) += V(a, b); } } } if (link == 0) { return ovl (y); } Matrix score (n, 2); for (octave_idx_type i = 0; i < n; i++) { double pos = 1.0 / (1.0 + std::exp (-y(i))); score(i, 0) = 1.0 - pos; score(i, 1) = pos; } return ovl (score); } /* %!test %! ## The additive prediction is the intercept plus each term's step function. %! E = {[1.5, 2.5]}; %! V = {[-1; 0; 2]}; %! assert_equal (gamboostpredict (E, V, [1; 2; 3], 0), [-1; 0; 2], 1e-14); %! assert_equal (gamboostpredict (E, V, [1; 2; 3], 0.5), [-0.5; 0.5; 2.5], ... %! 1e-14); %!test %! ## Terms add. %! E = {[1.5], [10]}; %! V = {[1; 2], [4; 8]}; %! assert_equal (gamboostpredict (E, V, [1, 5; 2, 20], 0), [5; 10], 1e-14); %!test %! ## A value beyond the fitted range falls in the nearest bin, so a shape %! ## function is constant outside its data rather than extrapolated. %! E = {[1.5, 2.5]}; %! V = {[-1; 0; 2]}; %! assert_equal (gamboostpredict (E, V, [-100; 100], 0), [-1; 2], 1e-14); %!test %! ## A missing value costs that term only. %! E = {[1.5], [10]}; %! V = {[1; 2], [4; 8]}; %! assert_equal (gamboostpredict (E, V, [NaN, 5], 0), 4, 1e-14); %!test %! ## A term with no cut points is a constant. %! E = {zeros(1,0)}; %! V = {3}; %! assert_equal (gamboostpredict (E, V, [1; 2; 3], 1), [4; 4; 4], 1e-14); %!test %! ## The logistic link returns both class probabilities, and they sum to one. %! E = {[1.5]}; %! V = {[-2; 2]}; %! s = gamboostpredict (E, V, [1; 2], 0, 1); %! assert_equal (sum (s, 2), [1; 1], 1e-14); %! assert_equal (s(:,2), 1 ./ (1 + exp ([2; -2])), 1e-14); %!test %! ## A model round-trips through its own trainer. %! x = [1; 2; 3; 4; 5; 6; 7; 8]; %! y = [0; 0; 0; 0; 1; 1; 1; 1]; %! M = gamboosttrain (x, y, 1, 30, 1, 1); %! f = gamboostpredict (M.BinEdges, M.ShapeValues, x, M.Intercept); %! p = 1 ./ (1 + exp (-f)); %! assert_equal (M.Residuals, y - p, 1e-12); %!test %! ## A pair term adds the value of the cell its two predictors fall in. %! E = {zeros(1,0), zeros(1,0)}; %! V = {0, 0}; %! PE = {[1.5], [10]}; %! PV = {[1, 2; 3, 4]}; %! X = [1, 5; 1, 20; 2, 5; 2, 20]; %! assert_equal (gamboostpredict (E, V, X, 0, 0, PE, PV, [1, 2]), ... %! [1; 2; 3; 4], 1e-14); %!test %! ## Interaction terms add to the additive part rather than replacing it. %! E = {[1.5], zeros(1,0)}; %! V = {[10; 20], 0}; %! PE = {[1.5], [10]}; %! PV = {[1, 2; 3, 4]}; %! X = [1, 5; 2, 20]; %! assert_equal (gamboostpredict (E, V, X, 100, 0, PE, PV, [1, 2]), ... %! [111; 124], 1e-14); %!test %! ## A pair with either predictor missing contributes nothing, as a tree does %! ## with a value it cannot place. %! E = {zeros(1,0), zeros(1,0)}; %! V = {0, 0}; %! PE = {[1.5], [10]}; %! PV = {[1, 2; 3, 4]}; %! assert_equal (gamboostpredict (E, V, [NaN, 5], 0, 0, PE, PV, [1, 2]), 0); %!test %! ## The assembled model reproduces what the interaction phase itself %! ## computed: same residuals, so the stored surfaces and the recentring %! ## bookkeeping are consistent with the fit they came from. %! randn ('seed', 42); %! rand ('seed', 42); %! X = randn (200, 3); %! Y = double (rand (200, 1) < 1 ./ (1 + exp (-2 * X(:,1) .* X(:,2)))); %! M = gamboosttrain (X, Y, 1, 50, 1, 1); %! f0 = gamboostpredict (M.BinEdges, M.ShapeValues, X, M.Intercept); %! I = gamboostinter (X, Y, f0, 1, [1, 2], 40, 1, 4); %! f1 = gamboostpredict (M.BinEdges, M.ShapeValues, X, ... %! M.Intercept + I.Intercept, 0, I.PairBinEdges, ... %! I.PairValues, [1, 2]); %! assert_equal (Y - 1 ./ (1 + exp (-f1)), I.Residuals, 1e-12); %!error gamboostpredict ({1}, {1}) %!error ... %! gamboostpredict (1, {1}, [1;2], 0) %!error ... %! gamboostpredict ({1}, 1, [1;2], 0) %!error ... %! gamboostpredict ({1, 2}, {1}, [1;2], 0) %!error ... %! gamboostpredict ({[1.5]}, {[1;2]}, 'a', 0) %!error ... %! gamboostpredict ({[1.5]}, {[1;2]}, [1, 2], 0) %!error ... %! gamboostpredict ({[1.5]}, {[1;2]}, [1;2], [1, 2]) %!error ... %! gamboostpredict ({[1.5]}, {[1;2]}, [1;2], 0, 2) %!error ... %! gamboostpredict ({[1.5]}, {[1;2;3]}, [1;2], 0) %!error ... %! gamboostpredict ({[1.5], [1.5]}, {[1;2], [1;2]}, [1, 1], 0, 0, 1, ... %! {1}, [1, 2]) %!error ... %! gamboostpredict ({[1.5], [1.5]}, {[1;2], [1;2]}, [1, 1], 0, 0, {[1.5]}, ... %! {[1, 2; 3, 4]}, [1, 2]) %!error ... %! gamboostpredict ({[1.5], [1.5]}, {[1;2], [1;2]}, [1, 1], 0, 0, ... %! {[1.5], [1.5]}, {[1, 2; 3, 4]}, [1, 2, 3]) %!error ... %! gamboostpredict ({[1.5], [1.5]}, {[1;2], [1;2]}, [1, 1], 0, 0, ... %! {[1.5], [1.5]}, {[1, 2; 3, 4]}, [1, 2; 1, 2]) */ statistics-release-1.9.2/src/gamboosttrain.cc000066400000000000000000000420011524624707500213130ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include "gamboost.cpp" DEFUN_DLD(gamboosttrain, args, , "-*- texinfo -*-\n\ @deftypefn {statistics} {@var{Mdl} =} gamboosttrain (@var{X}, @var{Y}, @\n\ @var{Method}, @var{NumTrees}, @var{LearnRate}, @var{MaxNumSplits})\n\ @deftypefnx {statistics} {@var{Mdl} =} gamboosttrain (@var{X}, @var{Y}, @\n\ @var{Method}, @var{NumTrees}, @var{LearnRate}, @var{MaxNumSplits}, @\n\ @var{Verbose}, @var{NumPrint}, @var{F0})\n\ @deftypefnx {statistics} {@var{Mdl} =} gamboosttrain (@dots{}, @var{Verbose}, @\n\ @var{NumPrint})\n\ \n\ Fit a generalized additive model of boosted trees.\n\ \n\ @code{@var{Mdl} = gamboosttrain (@var{X}, @var{Y}, @var{Method}, @\n\ @var{NumTrees}, @var{LearnRate}, @var{MaxNumSplits})} boosts one tree per\n\ column of @var{X} in each round and returns the additive model as a\n\ structure. It is the fitting engine shared by @code{ClassificationGAM} and\n\ @code{RegressionGAM}, and it is not meant to be called directly.\n\ \n\ @itemize\n\ @item @var{X} is an @math{NxP} numeric matrix of predictors. A missing\n\ value is not an error: the observation takes no part in the affected\n\ predictor's trees and that term contributes nothing to its prediction.\n\ \n\ @item @var{Y} is an @math{Nx1} numeric vector of responses. For\n\ @var{Method} 1 it must hold zeros and ones.\n\ \n\ @item @var{Method} selects what is boosted: @qcode{1} the logistic deviance,\n\ as a classifier is fitted, and @qcode{2} the squared error, as a regression\n\ is fitted.\n\ \n\ @item @var{NumTrees} is the number of rounds, each fitting one tree per\n\ predictor. It is a budget rather than a count: a fit that stops improving\n\ ends earlier and says so.\n\ \n\ @item @var{LearnRate} is the step a round starts at. A round that fails to\n\ earn its place is retried at half the step, so this is an initial value and\n\ not a fixed one.\n\ \n\ @item @var{MaxNumSplits} is the largest number of splits any one tree may\n\ make. @qcode{1} is a stump.\n\ \n\ @item @var{Verbose}, if greater than zero, prints a trace of the fit, and\n\ @var{NumPrint} how often: the first round and then every @var{NumPrint}\n\ rounds. The @qcode{RelTol} column is the relative improvement the round\n\ bought, which is what the stopping rule reads. MATLAB prints a column under\n\ the same heading holding a quantity of its own that cannot be derived from\n\ anything else it reports, so the two are not comparable.\n\ @end itemize\n\ \n\ @var{Mdl} is a structure with the following fields.\n\ \n\ @itemize\n\ @item @qcode{Intercept}, the constant term the additive terms are added to.\n\ For a classifier it is fitted rather than fixed: it is seeded with the\n\ log-odds of the response mean and then collects the constant each shape\n\ function gives up when it is recentred. For a regression it is the response\n\ mean and stays there.\n\ @item @qcode{BinEdges}, a @math{1xP} cell of row vectors, the cut points each\n\ predictor was binned at.\n\ @item @qcode{ShapeValues}, a @math{1xP} cell of column vectors, one value per\n\ bin. A shape function is a step function, so this is the whole of it however\n\ many trees produced it.\n\ @item @qcode{NumTrees}, the number of rounds actually performed.\n\ @item @qcode{ReasonForTermination}, why fitting stopped.\n\ @item @qcode{Deviance}, the deviance at the last round.\n\ @item @qcode{Residuals}, the @math{Nx1} residual vector at the last round.\n\ @end itemize\n\ \n\ @seealso{gamboostpredict, gamtrain, ClassificationGAM, RegressionGAM}\n\ @end deftypefn") { octave_idx_type nargin = args.length (); if (nargin != 6 && nargin != 8 && nargin != 9) { print_usage (); } if (! args(0).isnumeric () || args(0).iscomplex () || args(0).isempty ()) { error ("gamboosttrain: X must be a numeric matrix."); } if (! args(1).isnumeric () || args(1).iscomplex () || args(1).isempty () || args(1).columns () != 1) { error ("gamboosttrain: Y must be a numeric column vector."); } if (args(0).rows () != args(1).rows ()) { error ("gamboosttrain: X and Y must have the same number of rows."); } Matrix X = args(0).matrix_value (); ColumnVector Y = args(1).column_vector_value (); octave_idx_type d = X.columns (); if (! args(2).is_scalar_type () || ! args(2).isnumeric () || args(2).iscomplex ()) { error ("gamboosttrain: Method must be a numeric scalar."); } int method = args(2).int_value (); if (method != 1 && method != 2) { error ("gamboosttrain: Method must be 1 or 2."); } if (! args(3).is_scalar_type () || ! args(3).isnumeric () || args(3).iscomplex ()) { error ("gamboosttrain: NumTrees must be a numeric scalar."); } octave_idx_type maxtrees = (octave_idx_type) args(3).int_value (); if (maxtrees < 1) { error ("gamboosttrain: NumTrees must be a positive integer."); } if (! args(4).is_scalar_type () || ! args(4).isnumeric () || args(4).iscomplex ()) { error ("gamboosttrain: LearnRate must be a numeric scalar."); } double lrate = args(4).scalar_value (); if (! (lrate > 0.0) || lrate > 1.0) { error ("gamboosttrain: LearnRate must be greater than 0 and at most 1."); } if (! args(5).is_scalar_type () || ! args(5).isnumeric () || args(5).iscomplex ()) { error ("gamboosttrain: MaxNumSplits must be a numeric scalar."); } octave_idx_type maxsplits = (octave_idx_type) args(5).int_value (); if (maxsplits < 1) { error ("gamboosttrain: MaxNumSplits must be a positive integer."); } if (method == 1) { for (octave_idx_type i = 0; i < Y.numel (); i++) { if (Y(i) != 0.0 && Y(i) != 1.0) { error ("gamboosttrain: Y must hold zeros and ones for Method 1."); } } } int verbose = 0; octave_idx_type numprint = 10; if (nargin == 8) { if (! args(6).is_scalar_type () || ! args(6).isnumeric () || args(6).iscomplex ()) { error ("gamboosttrain: Verbose must be a numeric scalar."); } verbose = args(6).int_value (); if (verbose < 0) { error ("gamboosttrain: Verbose must be 0 or greater."); } if (! args(7).is_scalar_type () || ! args(7).isnumeric () || args(7).iscomplex ()) { error ("gamboosttrain: NumPrint must be a numeric scalar."); } numprint = (octave_idx_type) args(7).int_value (); if (numprint < 1) { error ("gamboosttrain: NumPrint must be a positive integer."); } } // A ninth argument continues a fit that has already run: it is the // prediction that fit reached over these same rows, and what comes back is // the increment to add to the shape values and the intercept already held. ColumnVector F0; bool resuming = false; if (nargin == 9) { if (! args(8).isnumeric () || args(8).iscomplex () || args(8).columns () != 1 || args(8).rows () != X.rows ()) { error ("gamboosttrain: F0 must be a numeric column vector with one element per row of X."); } F0 = args(8).column_vector_value (); resuming = true; } GamBoostFit F = gamb_boost (X, Y, method, maxtrees, lrate, maxsplits, verbose, numprint, resuming ? &F0 : nullptr); Cell edges (1, d); Cell values (1, d); for (octave_idx_type j = 0; j < d; j++) { edges(j) = octave_value (F.edges[(std::size_t) j]); values(j) = octave_value (F.value[(std::size_t) j]); } octave_scalar_map Mdl; Mdl.assign ("Intercept", octave_value (F.intercept)); Mdl.assign ("BinEdges", octave_value (edges)); Mdl.assign ("ShapeValues", octave_value (values)); Mdl.assign ("NumTrees", octave_value ((double) F.ntrees)); Mdl.assign ("ReasonForTermination", octave_value (F.reason)); Mdl.assign ("Deviance", octave_value (F.deviance)); Mdl.assign ("Residuals", octave_value (F.residuals)); return ovl (Mdl); } /* %!test %! ## Every field is present and the shapes follow the predictor count. %! x = [1 5; 2 4; 3 3; 4 2; 5 1; 6 7; 7 8; 8 9; 9 10; 10 11]; %! y = [0; 0; 0; 0; 0; 1; 1; 1; 1; 1]; %! M = gamboosttrain (x, y, 1, 50, 1, 1); %! assert_equal (isstruct (M), true); %! assert_equal (numel (M.BinEdges), 2); %! assert_equal (numel (M.ShapeValues), 2); %! assert_equal (numel (M.ShapeValues{1}), numel (M.BinEdges{1}) + 1); %! assert_equal (numel (M.Residuals), 10); %!test %! ## A predictor is cut at every midpoint between its distinct values. %! x = [1; 2; 3; 4; 2; 3]; %! y = [0; 0; 1; 1; 0; 1]; %! M = gamboosttrain (x, y, 1, 5, 1, 1); %! assert_equal (M.BinEdges{1}, [1.5, 2.5, 3.5], 1e-12); %!test %! ## Fewer than ten observations cannot be split at all: a leaf holds at %! ## least five. R2024a does the same, on fitcgam and fitrgam alike. The %! ## rounds still run and still count, because with no shape function to fit %! ## they reweight the intercept alone, and it converges on the log-odds of %! ## the response mean rather than being seeded there. R2024a reports %! ## exactly log (4/5) and a flat score on this fixture. %! x = (1:9)'; %! y = [zeros(5, 1); ones(4, 1)]; %! M = gamboosttrain (x, y, 1, 50, 1, 1); %! assert_equal (max (M.ShapeValues{1}) - min (M.ShapeValues{1}), 0, 1e-12); %! assert_equal (M.Intercept, log (4/5), 1e-10); %! assert_equal (M.ReasonForTermination, 'Unable to improve the model fit.'); %! M10 = gamboosttrain ((1:10)', [zeros(5, 1); ones(5, 1)], 1, 50, 1, 1); %! assert_equal (max (M10.ShapeValues{1}) - min (M10.ShapeValues{1}) > 1, true); %!test %! ## A constant predictor admits no split: one bin and no cut points. %! x = [2; 2; 2; 2]; %! y = [0; 1; 0; 1]; %! M = gamboosttrain (x, y, 1, 5, 1, 1); %! assert_equal (isempty (M.BinEdges{1}), true); %! assert_equal (numel (M.ShapeValues{1}), 1); %!test %! ## A regression intercept is the response mean and boosting leaves it there. %! x = (1:16)'; %! y = [2; 4; 5; 4; 6; 8; 9; 10; 11; 13; 12; 15; 16; 18; 17; 20]; %! M = gamboosttrain (x, y, 2, 40, 1, 1); %! assert_equal (M.NumTrees > 0, true); %! assert_equal (M.Intercept, mean (y), 1e-12); %!test %! ## A classifier intercept is seeded at zero, an even chance, and boosting %! ## moves it, so a balanced response does not stay there. %! ## Sixteen observations, not eight: a leaf holds at least five, so a fit %! ## with fewer than ten cannot split at all and its intercept never moves. %! x = (1:16)'; %! y = [zeros(8, 1); ones(8, 1)]; %! M = gamboosttrain (x, y, 1, 60, 1, 1); %! assert_equal (M.NumTrees > 0, true); %! assert_equal (M.Intercept != 0, true); %!test %! ## The tree budget is a budget: a fit that converges reports so and stops %! ## short of it. %! x = (1:16)'; %! y = [zeros(8, 1); ones(8, 1)]; %! M = gamboosttrain (x, y, 1, 5000, 1, 1); %! assert_equal (M.ReasonForTermination, 'Unable to improve the model fit.'); %! assert_equal (M.NumTrees > 0, true); %! assert_equal (M.NumTrees < 5000, true); %!test %! ## A budget too small to converge in reports the other reason. %! x = (1:16)'; %! y = [zeros(8, 1); ones(8, 1)]; %! M = gamboosttrain (x, y, 1, 2, 1, 1); %! assert_equal (M.ReasonForTermination, ... %! 'Terminated after training the requested number of trees.'); %! assert_equal (M.NumTrees, 2); %!test %! ## A single-class response has an infinite log-odds and nothing to fit. %! x = [1; 2; 3; 4]; %! y = [1; 1; 1; 1]; %! M = gamboosttrain (x, y, 1, 10, 1, 1); %! assert_equal (M.Intercept, Inf); %! assert_equal (M.NumTrees, 0); %!test %! ## A missing predictor value costs the observation that term, not the fit. %! x = [1; 2; NaN; 4; 5; 6; 7; 8; 9; 10; 11; 12]; %! y = [0; 0; 0; 0; 0; 0; 1; 1; 1; 1; 1; 1]; %! M = gamboosttrain (x, y, 1, 20, 1, 1); %! assert_equal (M.NumTrees > 0, true); %! assert_equal (isfinite (M.Intercept), true); %! assert_equal (any (isnan (M.ShapeValues{1})), false); %!test %! ## More splits per tree reach a lower deviance on a shape one split cannot %! ## follow. Thirty observations, not ten: three splits make four leaves and %! ## a leaf holds at least five, so ten could never use the larger budget. %! x = (1:30)'; %! y = [zeros(8, 1); ones(14, 1); zeros(8, 1)]; %! M1 = gamboosttrain (x, y, 1, 30, 1, 1); %! M2 = gamboosttrain (x, y, 1, 30, 1, 3); %! assert_equal (M2.Deviance < M1.Deviance, true); %!test %! ## Above the cap the cuts are equally spaced through the OBSERVATIONS, not %! ## through the distinct values, so they crowd where the data is. Values 1 %! ## to 50 carry 1000 of these 1450 rows and values 51 to 500 carry one row %! ## each: the equal-frequency grid puts 58 cuts in that dense fifth of the %! ## range where a grid spread through the distinct values would put 25. %! x = [repmat((1:50)', 20, 1); (51:500)']; %! y = double (mod (1:1450, 2))'; %! M = gamboosttrain (x, y, 1, 1, 1, 1); %! assert_equal (sum (M.BinEdges{1} < 50), 58); %!test %! ## Ties collapse cuts: two quantile positions inside one repeated value %! ## give the same cut, and the repeats are dropped, so a tied predictor ends %! ## with fewer than the cap allows rather than with duplicate edges. %! x = [repmat((1:50)', 20, 1); (51:500)']; %! y = double (mod (1:1450, 2))'; %! M = gamboosttrain (x, y, 1, 1, 1, 1); %! assert_equal (numel (M.BinEdges{1}), 138); %! assert_equal (numel (unique (M.BinEdges{1})), 138); %!test %! ## The cap binds at 255 cut points however many distinct values there are. %! x = (1:2000)'; %! y = double (mod (1:2000, 2))'; %! M = gamboosttrain (x, y, 1, 1, 1, 1); %! assert_equal (numel (M.BinEdges{1}), 255); %!test %! ## Below the cap nothing is thinned: one cut per gap between distinct %! ## values, which is what MATLAB, scikit-learn and the EBM all report. %! x = (1:200)'; %! y = double (mod (1:200, 2))'; %! M = gamboosttrain (x, y, 1, 1, 1, 1); %! assert_equal (numel (M.BinEdges{1}), 199); %! assert_equal (M.BinEdges{1}(1:3), [1.5, 2.5, 3.5], 1e-12); %!test %! ## Patience looks across a window rather than at one round: a fit that has %! ## stopped earning its keep ends and says so, well short of its budget. %! x = [1; 2; 3; 4; 5; 6; 7; 8]; %! y = [0; 0; 0; 0; 1; 1; 1; 1]; %! M = gamboosttrain (x, y, 1, 100000, 1, 1); %! assert_equal (M.ReasonForTermination, 'Unable to improve the model fit.'); %! assert_equal (M.NumTrees < 1000, true); %!test %! ## A verbose fit prints a trace and returns the same model as a quiet one. %! x = [1; 2; 3; 4; 5; 6; 7; 8]; %! y = [0; 0; 0; 0; 1; 1; 1; 1]; %! Q = gamboosttrain (x, y, 1, 20, 1, 1); %! V = evalc ('W = gamboosttrain (x, y, 1, 20, 1, 1, 1, 5);'); %! assert_equal (W.Intercept, Q.Intercept, 1e-12); %! assert_equal (W.NumTrees, Q.NumTrees); %! assert_equal (! isempty (strfind (V, 'NumTrees')), true); %! assert_equal (! isempty (strfind (V, 'LearnRate')), true); %!test %! ## NumPrint controls how often a round is reported: the first, then every %! ## NumPrint after it. %! x = (1:16)'; %! y = [zeros(8, 1); ones(8, 1)]; %! V5 = evalc ('gamboosttrain (x, y, 1, 20, 1, 1, 1, 5);'); %! V1 = evalc ('gamboosttrain (x, y, 1, 20, 1, 1, 1, 1);'); %! assert_equal (numel (strfind (V1, '| 1D|')) > ... %! numel (strfind (V5, '| 1D|')), true); %!test %! ## Verbose 0 prints nothing at all. %! x = [1; 2; 3; 4; 5; 6; 7; 8]; %! y = [0; 0; 0; 0; 1; 1; 1; 1]; %! V = evalc ('gamboosttrain (x, y, 1, 20, 1, 1, 0, 5);'); %! assert_equal (isempty (strfind (V, '1D')), true); %!error gamboosttrain (1, 2, 3) %!error ... %! gamboosttrain ('a', [1;0], 1, 10, 1, 1) %!error ... %! gamboosttrain ([1;2], [1, 0], 1, 10, 1, 1) %!error ... %! gamboosttrain ([1;2;3], [1;0], 1, 10, 1, 1) %!error ... %! gamboosttrain ([1;2], [1;0], 3, 10, 1, 1) %!error ... %! gamboosttrain ([1;2], [1;0], 1, 0, 1, 1) %!error ... %! gamboosttrain ([1;2], [1;0], 1, 10, 0, 1) %!error ... %! gamboosttrain ([1;2], [1;0], 1, 10, 2, 1) %!error ... %! gamboosttrain ([1;2], [1;0], 1, 10, 1, 0) %!error ... %! gamboosttrain ([1;2], [1;2], 1, 10, 1, 1) %!error ... %! gamboosttrain ([1;2], [1;0], 1, 10, 1, 1, -1, 5) %!error ... %! gamboosttrain ([1;2], [1;0], 1, 10, 1, 1, 1, 0) */ statistics-release-1.9.2/src/gampredict.cc000066400000000000000000000226531524624707500205740ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include "gam.cpp" using namespace std; DEFUN_DLD(gampredict, args, , "-*- texinfo -*-\n\ @deftypefn {statistics} {@var{yFit} =} gampredict (@var{Parameters}, @\n\ @var{X}, @var{Intercept})\n\ @deftypefnx {statistics} {@var{score} =} gampredict (@var{Parameters}, @\n\ @var{X}, @var{Intercept}, @var{Link})\n\ \n\ \n\ Evaluate a generalized additive model on new data.\n\ \n\ @code{@var{yFit} = gampredict (@var{Parameters}, @var{X}, @var{Intercept})}\n\ adds the intercept to the sum of the additive terms evaluated at each row of\n\ @var{X} and returns the @math{Nx1} result. It is the prediction engine\n\ shared by @code{ClassificationGAM} and @code{RegressionGAM}, and it is not\n\ meant to be called directly.\n\ \n\ @itemize\n\ @item @var{Parameters} is a @math{1xP} structure array of piecewise\n\ polynomials, as returned by @code{gamtrain} in the field of the same name.\n\ \n\ @item @var{X} is an @math{NxP} numeric matrix with one column per additive\n\ term, and a count that does not match is an error. A model carrying\n\ interaction terms must therefore be given the augmented matrix, not the\n\ predictors alone.\n\ A missing value predicts @qcode{NaN}, since no term of the model is defined\n\ at it. A value outside the range the term was fitted over is extrapolated\n\ from the nearest piece, as @code{ppval} extrapolates.\n\ \n\ @item @var{Intercept} is the model's constant term.\n\ \n\ @item @var{Link}, if given, selects what the additive prediction is mapped\n\ through: @qcode{0} returns it as it stands and @qcode{1} takes it as a\n\ log-odds, returning the @math{Nx2} matrix of class probabilities whose\n\ second column is the logistic function of it. The default is @qcode{0}.\n\ @end itemize\n\ \n\ @seealso{gamtrain, ClassificationGAM, RegressionGAM, fitcgam, fitrgam}\n\ @end deftypefn") { octave_idx_type nargin = args.length (); if (nargin < 3 || nargin > 4) { print_usage (); } if (! args(0).isstruct () || args(0).isempty ()) { error ("gampredict: Parameters must be a structure array."); } octave_map params = args(0).map_value (); if (! params.isfield ("breaks") || ! params.isfield ("coefs")) { error ("gampredict: Parameters must have 'breaks' and 'coefs' fields."); } if (! args(1).isnumeric () || args(1).iscomplex () || args(1).isempty ()) { error ("gampredict: X must be a numeric matrix."); } Matrix X = args(1).matrix_value (); octave_idx_type n = X.rows (); octave_idx_type d = X.columns (); // One column per additive term, exactly. The m-code this replaces looped // over the columns of X and took the term count from them, which tolerated // a caller passing a matrix that had not been augmented with its // interaction terms: too many terms went unnoticed and evaluated a // truncated model, while too few raised an out-of-bound index. That // contract was not self-consistent, and the direction it tolerated is the // one that returns a wrong number instead of an error. if (params.numel () != d) { error ("gampredict: X must have one column per additive term."); } if (! args(2).is_scalar_type () || ! args(2).isnumeric () || args(2).iscomplex ()) { error ("gampredict: Intercept must be a numeric scalar."); } double intercept = args(2).scalar_value (); int link = 0; if (nargin > 3) { if (! args(3).is_scalar_type () || ! args(3).isnumeric () || args(3).iscomplex ()) { error ("gampredict: Link must be a numeric scalar."); } link = args(3).int_value (); if (link != 0 && link != 1) { error ("gampredict: Link must be either 0 or 1."); } } Cell c_breaks = params.contents ("breaks"); Cell c_coefs = params.contents ("coefs"); ColumnVector y (n, intercept); for (octave_idx_type j = 0; j < d; j++) { if (! c_breaks(j).isnumeric () || ! c_coefs(j).isnumeric ()) { error ("gampredict: 'breaks' and 'coefs' must be numeric."); } RowVector breaks = c_breaks(j).row_vector_value (); Matrix coefs = c_coefs(j).matrix_value (); if (breaks.numel () != coefs.rows () + 1) { error ("gampredict: term %d has %d breaks for %d pieces.", (int) j + 1, (int) breaks.numel (), (int) coefs.rows ()); } for (octave_idx_type i = 0; i < n; i++) { y(i) += gam_ppval (breaks, coefs, X(i, j)); } } if (link == 0) { return ovl (y); } Matrix score (n, 2); for (octave_idx_type i = 0; i < n; i++) { double pos = 1.0 / (1.0 + exp (-y(i))); score(i, 0) = 1.0 - pos; score(i, 1) = pos; } return ovl (score); } /* %!test %! ## The additive prediction is the intercept plus each term's spline %! x = linspace (0, 1, 40)'; %! pp = splinefit (x, cos (3*x), 5, 'order', 3); %! P = struct ('form', 'pp', 'breaks', pp.breaks, 'coefs', pp.coefs, ... %! 'pieces', pp.pieces, 'order', pp.order, 'dim', pp.dim); %! assert_equal (gampredict (P, x, 0), ppval (pp, x), 1e-14); %! assert_equal (gampredict (P, x, 2.5), ppval (pp, x) + 2.5, 1e-14); %!test %! ## Two terms add %! x = linspace (0, 1, 30)'; %! p1 = splinefit (x, cos (3*x), 5, 'order', 3); %! p2 = splinefit (x, x.^2, 4, 'order', 2); %! P(1) = struct ('form', 'pp', 'breaks', p1.breaks, 'coefs', p1.coefs, ... %! 'pieces', p1.pieces, 'order', p1.order, 'dim', p1.dim); %! P(2) = struct ('form', 'pp', 'breaks', p2.breaks, 'coefs', p2.coefs, ... %! 'pieces', p2.pieces, 'order', p2.order, 'dim', p2.dim); %! y = gampredict (P, [x, x], 1); %! assert_equal (y, 1 + ppval (p1, x) + ppval (p2, x), 1e-13); %!test %! ## Fewer columns than terms is an error, not a truncated model. A caller %! ## that passes the predictors alone to a model carrying interaction terms %! ## used to be answered with the prediction of its leading terms. %! x = linspace (0, 1, 30)'; %! p1 = splinefit (x, cos (3*x), 5, 'order', 3); %! p2 = splinefit (x, x.^2, 4, 'order', 2); %! P2(1) = struct ('form', 'pp', 'breaks', p1.breaks, 'coefs', p1.coefs, ... %! 'pieces', p1.pieces, 'order', p1.order, 'dim', p1.dim); %! P2(2) = struct ('form', 'pp', 'breaks', p2.breaks, 'coefs', p2.coefs, ... %! 'pieces', p2.pieces, 'order', p2.order, 'dim', p2.dim); %! fail ('gampredict (P2, x, 0)', ... %! 'gampredict: X must have one column per additive term.'); %! assert_equal (gampredict (P2, [x, x], 0), ... %! ppval (p1, x) + ppval (p2, x), 1e-13); %!test %! ## The logistic link returns both class probabilities, and they sum to one %! x = linspace (0, 1, 20)'; %! pp = splinefit (x, 4*x - 2, 5, 'order', 3); %! P = struct ('form', 'pp', 'breaks', pp.breaks, 'coefs', pp.coefs, ... %! 'pieces', pp.pieces, 'order', pp.order, 'dim', pp.dim); %! s = gampredict (P, x, 0, 1); %! assert_equal (size (s), [20, 2]); %! assert_equal (sum (s, 2), ones (20, 1), 1e-14); %! assert_equal (s(:,2), 1 ./ (1 + exp (- gampredict (P, x, 0))), 1e-14); %!test %! ## A point outside the fitted range is extrapolated from the nearest piece, %! ## exactly as ppval extrapolates %! x = linspace (0, 1, 30)'; %! pp = splinefit (x, cos (3*x), 5, 'order', 3); %! P = struct ('form', 'pp', 'breaks', pp.breaks, 'coefs', pp.coefs, ... %! 'pieces', pp.pieces, 'order', pp.order, 'dim', pp.dim); %! xq = [-0.5; 1.5]; %! assert_equal (gampredict (P, xq, 0), ppval (pp, xq), 1e-13); %!test %! ## A missing predictor predicts NaN, and leaves the other rows alone %! x = linspace (0, 1, 10)'; %! pp = splinefit (x, cos (3*x), 4, 'order', 3); %! P = struct ('form', 'pp', 'breaks', pp.breaks, 'coefs', pp.coefs, ... %! 'pieces', pp.pieces, 'order', pp.order, 'dim', pp.dim); %! xq = [0.2; NaN; 0.8]; %! y = gampredict (P, xq, 0); %! assert (isnan (y(2))); %! assert_equal (y([1, 3]), ppval (pp, [0.2; 0.8]), 1e-14); %!shared P %! pp = splinefit (linspace (0, 1, 20)', linspace (0, 1, 20)', 4, 'order', 3); %! P = struct ('form', 'pp', 'breaks', pp.breaks, 'coefs', pp.coefs, ... %! 'pieces', pp.pieces, 'order', pp.order, 'dim', pp.dim); %!error gampredict () %!error gampredict (P, ones (5, 1)) %!error gampredict (P, ones (5, 1), 0, 1, 2) %!error ... %! gampredict (5, ones (5, 1), 0) %!error gampredict (P, 'a', 0) %!error ... %! gampredict (P, ones (5, 3), 0) %!error ... %! gampredict (P, ones (5, 1), [1, 2]) %!error ... %! gampredict (P, ones (5, 1), 0, 'a') %!error ... %! gampredict (P, ones (5, 1), 0, 2) */ statistics-release-1.9.2/src/gamtrain.cc000066400000000000000000000314331524624707500202530ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include "gam.cpp" using namespace std; DEFUN_DLD(gamtrain, args, , "-*- texinfo -*-\n\ @deftypefn {statistics} {@var{Mdl} =} gamtrain (@var{X}, @var{Y}, @\n\ @var{Knots}, @var{Order}, @var{Method}, @var{Inter}, @var{P1}, @var{P2})\n\ \n\ \n\ Fit a generalized additive model of smoothing splines.\n\ \n\ @code{@var{Mdl} = gamtrain (@var{X}, @var{Y}, @var{Knots}, @var{Order}, @\n\ @var{Method}, @var{Inter}, @var{P1}, @var{P2})} fits one univariate spline\n\ per column of @var{X} and returns the additive model as a structure. It is\n\ the fitting engine shared by @code{ClassificationGAM} and\n\ @code{RegressionGAM}, and it is not meant to be called directly.\n\ \n\ @itemize\n\ @item @var{X} is an @math{NxP} numeric matrix of predictors. A missing\n\ value is not an error: the observation is left out of the affected\n\ predictor's spline and its prediction from that term is @qcode{NaN}.\n\ \n\ @item @var{Y} is an @math{Nx1} numeric vector of responses. For\n\ @var{Method} 1 it must hold zeros and ones.\n\ \n\ @item @var{Knots} is a @math{1xP} vector giving the number of spline pieces\n\ for each predictor, and @var{Order} a @math{1xP} vector giving the degree of\n\ the polynomial on each piece. A spline of @math{K} pieces and degree\n\ @math{D} spans a space of @math{K + D} dimensions.\n\ \n\ @item @var{Method} selects the fitting scheme: @qcode{1} boosts the log-odds\n\ by gradient descent, as a classifier is fitted, and @qcode{2} backfits the\n\ partial residuals, as a regression is fitted.\n\ \n\ @item @var{Inter} is the intercept the fit starts from: a proportion for\n\ @var{Method} 1, which is stored as its log-odds, and the response mean for\n\ @var{Method} 2. A proportion of zero or one is not an error: its log-odds is\n\ infinite, the gradient is zero throughout and every additive term stays at\n\ zero, which is the fit a single-class response has.\n\ \n\ @item @var{P1} and @var{P2} are the scheme's two parameters. For\n\ @var{Method} 1 they are the learning rate and the number of boosting\n\ iterations; for @var{Method} 2 the convergence tolerance and the maximum\n\ number of backfitting cycles.\n\ @end itemize\n\ \n\ @var{Mdl} is a structure with the following fields.\n\ \n\ @itemize\n\ @item @qcode{Intercept}, the constant term the additive terms are added to.\n\ @item @qcode{Parameters}, a @math{1xP} structure array of piecewise\n\ polynomials in the form @code{ppval} consumes, one per predictor.\n\ @item @qcode{Iterations}, the number of iterations performed.\n\ @item @qcode{Residuals}, the @math{Nx1} residual vector at the last\n\ iteration.\n\ @item @qcode{RSS}, the scalar residual sum of squares for @var{Method} 1 and\n\ the @math{1xP} per-term criterion the backfitting stops on for @var{Method}\n\ 2.\n\ @end itemize\n\ \n\ @seealso{gampredict, ClassificationGAM, RegressionGAM, fitcgam, fitrgam}\n\ @end deftypefn") { if (args.length () != 8) { print_usage (); } // X and Y if (! args(0).isnumeric () || args(0).iscomplex () || args(0).isempty ()) { error ("gamtrain: X must be a numeric matrix."); } if (! args(1).isnumeric () || args(1).iscomplex () || args(1).isempty () || args(1).columns () != 1) { error ("gamtrain: Y must be a numeric column vector."); } if (args(0).rows () != args(1).rows ()) { error ("gamtrain: X and Y must have the same number of rows."); } Matrix X = args(0).matrix_value (); ColumnVector Y = args(1).column_vector_value (); octave_idx_type d = X.columns (); // Knots and Order, one per predictor if (! args(2).isnumeric () || args(2).iscomplex () || args(2).numel () != d) { error ("gamtrain: Knots must have one element per column of X."); } if (! args(3).isnumeric () || args(3).iscomplex () || args(3).numel () != d) { error ("gamtrain: Order must have one element per column of X."); } Matrix knots = args(2).matrix_value (); Matrix order = args(3).matrix_value (); for (octave_idx_type j = 0; j < d; j++) { if (knots(j) < 1 || floor (knots(j)) != knots(j)) { error ("gamtrain: Knots must be positive integers."); } // Order zero is the m-code's own lower bound and gives piecewise // constants: splinefit takes the degree, so a degree of zero is a spline // of order one. Rejecting it here would narrow what the learners accept. if (order(j) < 0 || floor (order(j)) != order(j)) { error ("gamtrain: Order must be non-negative integers."); } } // Method, intercept and the scheme's two parameters if (! args(4).is_scalar_type () || ! args(4).isnumeric () || args(4).iscomplex ()) { error ("gamtrain: Method must be a numeric scalar."); } int method = args(4).int_value (); if (method != 1 && method != 2) { error ("gamtrain: Method must be either 1 or 2."); } if (! args(5).is_scalar_type () || ! args(5).isnumeric () || args(5).iscomplex ()) { error ("gamtrain: Inter must be a numeric scalar."); } double inter = args(5).scalar_value (); if (! args(6).is_scalar_type () || ! args(6).isnumeric () || args(6).iscomplex () || args(6).scalar_value () <= 0) { error ("gamtrain: P1 must be a positive scalar."); } if (! args(7).is_scalar_type () || ! args(7).isnumeric () || args(7).iscomplex () || args(7).scalar_value () < 1) { error ("gamtrain: P2 must be a scalar not less than 1."); } double p1 = args(6).scalar_value (); octave_idx_type p2 = (octave_idx_type) args(7).scalar_value (); GamFit fit; if (method == 1) { // Inter is not restricted to the open unit interval. A single-class fit // is allowed and gives a proportion of exactly zero or one, whose log-odds // is infinite; the gradient is then zero everywhere and every additive // term stays at zero, which is the answer such a fit has. fit = gam_boost (X, Y, inter, knots, order, p1, p2); } else { fit = gam_backfit (X, Y, inter, knots, order, p1, p2); } octave_scalar_map Mdl; Mdl.assign ("Intercept", fit.intercept); Mdl.assign ("Parameters", fit.params); Mdl.assign ("Iterations", fit.iterations); Mdl.assign ("Residuals", fit.res); Mdl.assign ("RSS", fit.RSS); return ovl (Mdl); } /* %!test %! ## The boosted model returns the fields the learners store %! X = [linspace(0, 1, 40)', linspace(1, 2, 40)']; %! Y = double ([1:40]' > 20); %! Mdl = gamtrain (X, Y, [5, 5], [3, 3], 1, 0.5, 0.1, 100); %! assert_equal (fieldnames (Mdl), ... %! {'Intercept'; 'Parameters'; 'Iterations'; 'Residuals'; 'RSS'}); %! assert_equal (size (Mdl.Parameters), [1, 2]); %! assert_equal (Mdl.Iterations, 100); %! assert_equal (size (Mdl.Residuals), [40, 1]); %! assert_equal (size (Mdl.RSS), [1, 1]); %! assert_equal (Mdl.Intercept, log (0.5 / 0.5), 1e-14); %!test %! ## A spline of K pieces and degree D is a piecewise polynomial of K pieces %! ## and K + D coefficients per term %! X = linspace (0, 1, 30)'; %! Y = double ([1:30]' > 15); %! Mdl = gamtrain (X, Y, 6, 3, 1, 0.5, 0.1, 20); %! P = Mdl.Parameters(1); %! assert_equal (P.form, 'pp'); %! assert_equal (P.pieces, 6); %! assert_equal (P.order, 4); %! assert_equal (P.dim, 1); %! assert_equal (size (P.coefs), [6, 4]); %! assert_equal (size (P.breaks), [1, 7]); %!test %! ## The engine reproduces splinefit on a single fitted term. One boosting %! ## round at a learning rate of one is the spline of the gradient, which for %! ## an intercept of one half is the response less one half. %! x = linspace (0, 1, 50)'; %! Y = double (x > 0.4); %! Mdl = gamtrain (x, Y, 5, 3, 1, 0.5, 1, 1); %! pp = splinefit (x, Y - 0.5, 5, 'order', 3); %! assert_equal (Mdl.Parameters(1).coefs, pp.coefs, 1e-9); %! assert_equal (Mdl.Parameters(1).breaks, pp.breaks, 1e-14); %!test %! ## Backfitting a single term converges to the spline of the centred %! ## response, which is what one cycle already fits %! x = linspace (0, 2*pi, 60)'; %! Y = cos (x); %! Mdl = gamtrain (x, Y, 5, 3, 2, mean (Y), 1e-3, 1000); %! pp = splinefit (x, Y - mean (Y), 5, 'order', 3); %! assert_equal (Mdl.Parameters(1).coefs, pp.coefs, 1e-9); %! assert_equal (Mdl.Intercept, mean (Y), 1e-14); %! assert_equal (size (Mdl.RSS), [1, 1]); %!test %! ## Backfitting stops on the tolerance, and a looser one stops sooner %! X = [linspace(0, 1, 50)', linspace(0, 2, 50)']; %! Y = cos (3 * X(:,1)) + X(:,2); %! M1 = gamtrain (X, Y, [5, 5], [3, 3], 2, mean (Y), 1e-12, 1000); %! M2 = gamtrain (X, Y, [5, 5], [3, 3], 2, mean (Y), 1, 1000); %! assert (M2.Iterations <= M1.Iterations); %! assert_equal (size (M1.RSS), [1, 2]); %!test %! ## Fewer observations than the spline space has dimensions: the fit is the %! ## minimum norm one and does not raise %! x = [1; 2; 3; 4; 5]; %! Y = [2; 1; 4; 3; 6]; %! Mdl = gamtrain (x, Y, 5, 3, 2, mean (Y), 1e-3, 1000); %! assert_equal (size (Mdl.Parameters(1).coefs), [5, 4]); %! assert (all (isfinite (Mdl.Parameters(1).coefs(:)))); %!test %! ## A missing predictor drops the observation from that term's fit and %! ## predicts NaN for it, leaving the other observations finite %! x = linspace (0, 1, 40)'; %! x(7) = NaN; %! Y = double ([1:40]' > 20); %! Mdl = gamtrain (x, Y, 5, 3, 1, 0.5, 0.1, 10); %! assert (isnan (Mdl.Residuals(7))); %! assert (all (isfinite (Mdl.Residuals([1:6, 8:40])))); %!test %! ## Tied and unsorted predictor values are handled by the break placement %! x = [0.5; 0.5; 0.1; 0.9; 0.1; 0.7; 0.3; 0.5; 0.2; 0.6; 0.4; 0.8]; %! Y = [0; 0; 0; 1; 0; 1; 0; 1; 0; 1; 0; 1]; %! Mdl = gamtrain (x, Y, 5, 3, 1, 0.5, 0.1, 20); %! assert (all (isfinite (Mdl.Parameters(1).coefs(:)))); %! assert (issorted (Mdl.Parameters(1).breaks)); %!error gamtrain () %!error gamtrain (ones (5, 2), ones (5, 1)) %!error ... %! gamtrain ('a', ones (5, 1), 5, 3, 1, 0.5, 0.1, 10) %!error ... %! gamtrain (ones (5, 1), ones (5, 2), 5, 3, 1, 0.5, 0.1, 10) %!error ... %! gamtrain (ones (5, 1), ones (4, 1), 5, 3, 1, 0.5, 0.1, 10) %!error ... %! gamtrain (ones (5, 2), ones (5, 1), 5, [3, 3], 1, 0.5, 0.1, 10) %!error ... %! gamtrain (ones (5, 2), ones (5, 1), [5, 5], 3, 1, 0.5, 0.1, 10) %!error ... %! gamtrain (ones (5, 1), ones (5, 1), 2.5, 3, 1, 0.5, 0.1, 10) %!error ... %! gamtrain (ones (5, 1), ones (5, 1), 5, -1, 1, 0.5, 0.1, 10) %!error ... %! gamtrain (ones (5, 1), ones (5, 1), 5, 1.5, 1, 0.5, 0.1, 10) %!error ... %! gamtrain (ones (5, 1), ones (5, 1), 5, 3, 'a', 0.5, 0.1, 10) %!error ... %! gamtrain (ones (5, 1), ones (5, 1), 5, 3, 3, 0.5, 0.1, 10) %!error ... %! gamtrain (ones (5, 1), ones (5, 1), 5, 3, 1, [1, 2], 0.1, 10) %!error ... %! gamtrain (ones (5, 1), ones (5, 1), 5, 3, 1, 0.5, 0, 10) %!error ... %! gamtrain (ones (5, 1), ones (5, 1), 5, 3, 1, 0.5, 0.1, 0) %!test %! ## Order zero is piecewise constants, which the learners accept and the %! ## m-code fitted as splines of order one %! x = linspace (0, 1, 30)'; %! Y = double (x > 0.5); %! Mdl = gamtrain (x, Y, 5, 0, 1, 0.5, 0.1, 20); %! P = Mdl.Parameters(1); %! assert_equal (P.pieces, 5); %! assert_equal (P.order, 1); %! assert_equal (size (P.coefs), [5, 1]); %! pp = splinefit (x, Y - 0.5, 5, 'order', 0); %! M1 = gamtrain (x, Y, 5, 0, 1, 0.5, 1, 1); %! assert_equal (M1.Parameters(1).coefs, pp.coefs, 1e-12); %!test %! ## A single-class response gives an infinite intercept and terms that stay %! ## at zero, which is what the m-code it replaces produced %! x = linspace (0, 1, 20)'; %! Mdl = gamtrain (x, ones (20, 1), 5, 3, 1, 1, 0.1, 10); %! assert_equal (Mdl.Intercept, Inf); %! assert_equal (Mdl.Parameters(1).coefs, zeros (5, 4)); %! assert_equal (Mdl.Residuals, zeros (20, 1)); */ statistics-release-1.9.2/src/lbfgs.h000066400000000000000000000420631524624707500174110ustar00rootroot00000000000000/* Copyright (C) 2026 Andreas Bertsatos This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ // Limited-memory BFGS with a strong Wolfe line search. // // The engine is deliberately free of any Octave type so that it can serve two // callers with opposite needs: a compiled learner whose objective is C++ and // which must never cross the interpreter boundary, and the __lbfgs__ oct-file, // whose objective is an Octave function handle. The objective is therefore a // template parameter, and anything modelling // // double operator () (const std::vector &x, std::vector &g) // // which returns the value at x and writes the gradient into g, will do. // // Stopping behaviour follows MATLAB's, as measured on R2024a with the // Statistics and Machine Learning Toolbox 24.1. Three findings are baked in // here because they are not what the documentation's wording suggests, and // a fourth that it does not state at all: // // 1. All three tolerances are ABSOLUTE comparisons against the scalar the // iteration records, not relative to that scalar's starting value. // MATLAB reports "Relative gradient tolerance reached." but stops at the // first iteration whose recorded gradient falls to or below the // tolerance; scaling the objective by 1e6 moved the recorded gradient up // rather than leaving it fixed, so no division by the objective value is // involved either. // 2. LossTolerance tests the objective VALUE, not the change in it. Given a // tolerance of 0.5 against a first-iteration loss of 0.3551, MATLAB // stopped at iteration 1. // 3. When several tolerances are met at once the reported criterion follows // a fixed order: gradient, then step, then loss. // 4. BetaTolerance is a RELATIVE change and is measured differently from // the two above: the two-norm of the step over the two-norm of the // iterate it landed on, with no guard on the denominator. Measured on // R2024a's fitclinear, where a fit from a zero start reports exactly 1 // at the first iteration, which every guarded candidate contradicts. // A tolerance of zero there does not mean "test against zero" but "do // not run this test", and MATLAB then does not compute the quantity at // all, reporting NaN. Note that LossTolerance disables with -Inf and // BetaTolerance with 0: the two differ deliberately, each following // MATLAB for the quantity it governs, and unifying them would break // one or the other. // 5. The gradient and step are reduced by their infinity norm, which is // what MATLAB reports. Letting R2024a's fitrnet converge and computing // both norms at the weights it returned, its reported gradient matched // the infinity norm to twelve significant digits and the two-norm not at // all, the latter being 9.5 per cent larger on that fit. #ifndef OCTAVE_STATISTICS_LBFGS_H #define OCTAVE_STATISTICS_LBFGS_H #include #include #include using namespace std; namespace lbfgs { // Why the iteration stopped. Callers map these onto MATLAB's own strings. enum criterion { CRIT_GRADIENT = 0, CRIT_BETA, CRIT_STEP, CRIT_LOSS, CRIT_ITERATION_LIMIT, CRIT_LINE_SEARCH }; struct options { int iteration_limit; double gradient_tolerance; double loss_tolerance; double step_tolerance; double beta_tolerance; int history_size; double initial_step_size; // non-positive selects the automatic value int max_line_search; double wolfe_c1; double wolfe_c2; // Defaults are MATLAB's, read off fitcnet's ModelParameters on R2024a. // HistorySize is not exposed by fitcnet; 10 is what fmincon and the deep // learning solver both use, and 15 is what fitclinear uses. options () : iteration_limit (1000), gradient_tolerance (1e-6), loss_tolerance (1e-6), step_tolerance (1e-6), beta_tolerance (0.0), history_size (10), initial_step_size (0.0), max_line_search (25), wolfe_c1 (1e-4), wolfe_c2 (0.9) { } }; struct record { double fval; double gradient; double step; double rel_beta; }; struct result { int crit; int iterations; int funcount; double fval; double gradient; double step; double rel_beta; vector history; }; // A stable name for the test that stopped the iteration, never edited for // readability. The engine deliberately does not hand out prose: MATLAB // words the same criterion differently per function, fitcnet reporting // "Relative gradient tolerance reached." where fitclinear reports // "Tolerance on gradient satisfied.", so each caller maps this token to // the wording, and where applicable the code, of the function it mirrors. inline const char * criterion_token (int crit) { switch (crit) { case CRIT_GRADIENT: return "gradient"; case CRIT_BETA: return "beta"; case CRIT_STEP: return "step"; case CRIT_LOSS: return "loss"; case CRIT_ITERATION_LIMIT: return "iteration"; default: return "linesearch"; } } inline double dot (const vector &a, const vector &b) { double s = 0.0; for (size_t i = 0; i < a.size (); i++) { s += a[i] * b[i]; } return s; } inline double inf_norm (const vector &a) { double m = 0.0; for (size_t i = 0; i < a.size (); i++) { double v = fabs (a[i]); if (v > m) { m = v; } } return m; } inline double two_norm (const vector &a) { double s = 0.0; for (size_t i = 0; i < a.size (); i++) { s += a[i] * a[i]; } return sqrt (s); } inline bool is_finite (double v) { return (v == v && v < HUGE_VAL && v > -HUGE_VAL); } // Safeguarded quadratic interpolation between the two bracket ends, falling // back to bisection whenever the interpolant is not usable. Keeping the // trial away from the ends by a tenth of the bracket is what stops the zoom // from stalling on a flat interpolant. inline double interpolate (double a_lo, double f_lo, double d_lo, double a_hi, double f_hi) { double lo = (a_lo < a_hi ? a_lo : a_hi); double hi = (a_lo < a_hi ? a_hi : a_lo); double edge = 0.1 * (hi - lo); double bisect = 0.5 * (a_lo + a_hi); double width = a_hi - a_lo; double denom = 2.0 * (f_hi - f_lo - d_lo * width); if (denom == 0.0 || ! is_finite (denom)) { return bisect; } double a = a_lo - d_lo * width * width / denom; if (! is_finite (a) || a < lo + edge || a > hi - edge) { return bisect; } return a; } // Strong Wolfe line search, Nocedal and Wright algorithms 3.5 and 3.6. On // success the accepted point, its gradient and its value are left in x, g // and f, so an accepted search costs the caller no further objective call. template bool line_search (Objective &fun, const vector &x0, const vector &d, double f0, double dphi0, double alpha0, const options &opt, vector &x, vector &g, double &f, double &alpha, int &nfev) { const size_t n = x0.size (); const double c1 = opt.wolfe_c1; const double c2 = opt.wolfe_c2; double dphi = 0.0; // phi (a) together with phi' (a), evaluated in place: the trial point is // left in x, its gradient in g and its value in f, so an accepted step // needs no repeat call. auto evaluate = [&] (double a) -> void { for (size_t i = 0; i < n; i++) { x[i] = x0[i] + a * d[i]; } f = fun (x, g); dphi = dot (g, d); nfev++; }; double a_lo = 0.0, f_lo = f0, d_lo = dphi0; double a_hi = 0.0, f_hi = 0.0; bool bracketed = false; double a_prev = 0.0, f_prev = f0, d_prev = dphi0; double a = alpha0; // Bracketing. The first trial that breaks sufficient decrease, or that // rises above its predecessor, or that turns the slope upwards, encloses // an acceptable step together with the trial before it. for (int i = 1; i <= opt.max_line_search; i++) { evaluate (a); if (! is_finite (f) || f > f0 + c1 * a * dphi0 || (i > 1 && f >= f_prev)) { a_lo = a_prev; f_lo = f_prev; d_lo = d_prev; a_hi = a; f_hi = f; bracketed = true; break; } if (fabs (dphi) <= -c2 * dphi0) { alpha = a; return true; } if (dphi >= 0.0) { a_lo = a; f_lo = f; d_lo = dphi; a_hi = a_prev; f_hi = f_prev; bracketed = true; break; } a_prev = a; f_prev = f; d_prev = dphi; a *= 2.0; } if (! bracketed) { // Every trial improved and none met the curvature condition, which // means the last one is still a descent step worth taking. if (a_prev > 0.0) { evaluate (a_prev); alpha = a_prev; return true; } return false; } // Zoom. The bracket shrinks until a trial satisfies both Wolfe // conditions, and a_lo always holds the best point seen so far. for (int j = 1; j <= opt.max_line_search; j++) { a = interpolate (a_lo, f_lo, d_lo, a_hi, f_hi); evaluate (a); if (! is_finite (f) || f > f0 + c1 * a * dphi0 || f >= f_lo) { a_hi = a; f_hi = f; } else { if (fabs (dphi) <= -c2 * dphi0) { alpha = a; return true; } if (dphi * (a_hi - a_lo) >= 0.0) { a_hi = a_lo; f_hi = f_lo; } a_lo = a; f_lo = f; d_lo = dphi; } if (fabs (a_hi - a_lo) <= 1e-16 * (1.0 + fabs (a_lo))) { break; } } // The budget ran out. Accepting the best bracket end is better than // failing outright: it still decreases the objective, and the curvature // safeguard on the (s, y) pair will discard the update if it has to. if (a_lo > 0.0) { evaluate (a_lo); alpha = a_lo; return true; } return false; } // Minimise fun starting from x, which is overwritten with the final point. template result minimize (Objective &fun, vector &x, const options &opt) { const size_t n = x.size (); const int m = (opt.history_size > 0 ? opt.history_size : 1); vector g (n, 0.0), d (n, 0.0), q (n, 0.0); vector x_new (n, 0.0), g_new (n, 0.0); vector > S (m, vector (n, 0.0)); vector > Y (m, vector (n, 0.0)); vector rho (m, 0.0), coef (m, 0.0); int stored = 0, newest = -1; double gamma = 1.0; result out; out.crit = CRIT_ITERATION_LIMIT; out.iterations = 0; out.funcount = 1; out.fval = fun (x, g); out.gradient = inf_norm (g); out.step = 0.0; out.rel_beta = NAN; for (int k = 1; k <= opt.iteration_limit; k++) { // Two-loop recursion, d = -H * g. q = g; for (int j = 0; j < stored; j++) { int idx = (newest - j + m) % m; coef[idx] = rho[idx] * dot (S[idx], q); for (size_t i = 0; i < n; i++) { q[i] -= coef[idx] * Y[idx][i]; } } for (size_t i = 0; i < n; i++) { q[i] *= gamma; } for (int j = stored - 1; j >= 0; j--) { int idx = (newest - j + m) % m; double beta = rho[idx] * dot (Y[idx], q); for (size_t i = 0; i < n; i++) { q[i] += (coef[idx] - beta) * S[idx][i]; } } for (size_t i = 0; i < n; i++) { d[i] = -q[i]; } // Rounding can leave the recursion with a direction that is not one of // descent; dropping the history and stepping down the gradient is the // standard recovery. double dphi0 = dot (g, d); if (! (dphi0 < 0.0)) { for (size_t i = 0; i < n; i++) { d[i] = -g[i]; } dphi0 = -dot (g, g); stored = 0; newest = -1; gamma = 1.0; } // A zero slope down the steepest descent direction means the gradient // itself is zero, so there is nothing left to search along. if (dphi0 == 0.0) { out.crit = CRIT_GRADIENT; break; } // A quasi-Newton step is unit-scaled once curvature information exists. // The first one has none, so it is scaled by the gradient instead. double alpha0 = 1.0; if (stored == 0) { if (opt.initial_step_size > 0.0) { alpha0 = opt.initial_step_size; } else { double gn = inf_norm (g); alpha0 = (gn > 1.0 ? 1.0 / gn : 1.0); } } // f_new is kept separate from out.fval: a failed search leaves its own // scratch value behind, and out.fval must go on describing the last // point actually accepted. double f_new = out.fval; double alpha = 0.0; bool ok = line_search (fun, x, d, out.fval, dphi0, alpha0, opt, x_new, g_new, f_new, alpha, out.funcount); // A failed search usually means the stored curvature has gone stale // rather than that the point is stationary, so the memory is dropped // and the step retried down the gradient before giving up. Only a // second failure, with nothing left to blame, ends the iteration. if (! ok && stored > 0) { stored = 0; newest = -1; gamma = 1.0; for (size_t i = 0; i < n; i++) { d[i] = -g[i]; } dphi0 = -dot (g, g); double gn = inf_norm (g); alpha0 = (gn > 1.0 ? 1.0 / gn : 1.0); f_new = out.fval; ok = line_search (fun, x, d, out.fval, dphi0, alpha0, opt, x_new, g_new, f_new, alpha, out.funcount); } if (! ok) { out.crit = CRIT_LINE_SEARCH; break; } // The curvature pair goes straight into the slot it would occupy, so // that the step norm can be read off it before the pair is judged. A // rejected pair simply leaves the slot to be overwritten next time. int idx = (newest + 1) % m; double sy = 0.0, yy = 0.0; for (size_t i = 0; i < n; i++) { S[idx][i] = x_new[i] - x[i]; Y[idx][i] = g_new[i] - g[i]; sy += S[idx][i] * Y[idx][i]; yy += Y[idx][i] * Y[idx][i]; } double stepnorm = inf_norm (S[idx]); // Keeping only pairs with positive curvature is what holds the implicit // inverse Hessian positive definite. if (yy > 0.0 && sy > 1e-10 * yy) { rho[idx] = 1.0 / sy; gamma = sy / yy; newest = idx; if (stored < m) { stored++; } } x.swap (x_new); g.swap (g_new); out.fval = f_new; out.gradient = inf_norm (g); out.step = stepnorm; out.iterations = k; // The relative change in the iterate, which MATLAB reports as // RelativeChangeInBeta: the two-norm of the step over the two-norm of // the point it landed on. The denominator is deliberately unguarded, // and a zero tolerance switches the test off rather than testing // against zero, leaving the quantity uncomputed as MATLAB leaves it. // A zero iterate can only be reached by a zero step, so the division // is 0/0 and yields NaN rather than a spurious number; the other way // round it yields Inf, which no tolerance accepts. if (opt.beta_tolerance > 0.0) { out.rel_beta = two_norm (S[idx]) / two_norm (x); } record r; r.fval = out.fval; r.gradient = out.gradient; r.step = out.step; r.rel_beta = out.rel_beta; out.history.push_back (r); // MATLAB's order, verified on R2024a: gradient, then the relative // change in the coefficients, then step, then loss. The first two // were measured on fitclinear, the rest on fitcnet. if (out.gradient <= opt.gradient_tolerance) { out.crit = CRIT_GRADIENT; break; } if (opt.beta_tolerance > 0.0 && out.rel_beta <= opt.beta_tolerance) { out.crit = CRIT_BETA; break; } if (out.step <= opt.step_tolerance) { out.crit = CRIT_STEP; break; } if (out.fval <= opt.loss_tolerance) { out.crit = CRIT_LOSS; break; } } return out; } } #endif statistics-release-1.9.2/src/libsvmread.cc000066400000000000000000000132051524624707500205760ustar00rootroot00000000000000/* Copyright (C) 2025 Andreas Bertsatos Adapted from MATLAB libsvmwrite.c file from the LIBSVM 3.36 (2025) library by Chih-Chung Chang and Chih-Jen Lin. This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef max #define max(x,y) (((x)>(y))?(x):(y)) #endif #ifndef min #define min(x,y) (((x)<(y))?(x):(y)) #endif using namespace std; static char *line; static int max_line_len; static char* readline(FILE *input) { int len; if(fgets(line,max_line_len,input) == NULL) { return NULL; } while(strrchr(line,'\n') == NULL) { max_line_len *= 2; line = (char *) realloc(line, max_line_len); len = (int) strlen(line); if(fgets(line+len,max_line_len-len,input) == NULL) { break; } } return line; } // read the file in libsvm format void read(string filename, ColumnVector &label_vec, SparseMatrix &instance_mat) { int max_index, min_index, inst_max_index; size_t elements, k, i, l=0; FILE *fp = fopen(filename.c_str(),"r"); char *endptr; octave_idx_type *ir, *jc; double *labels, *samples; if(fp == NULL) { printf("can't open input file %s\n",filename.c_str()); return; } max_line_len = 1024; line = (char *) malloc(max_line_len*sizeof(char)); max_index = 0; min_index = 1; // our index starts from 1 elements = 0; while(readline(fp) != NULL) { char *idx, *val; // features int index = 0; inst_max_index = -1; strtok(line," \t"); while (1) { idx = strtok(NULL,":"); val = strtok(NULL," \t"); if(val == NULL) break; errno = 0; index = (int) strtol(idx,&endptr,10); if(endptr == idx || errno != 0 || *endptr != '\0' || index <= inst_max_index) { printf("libsvmread: wrong input format at line %d.\n", (int)l+1); return; } else inst_max_index = index; min_index = min(min_index, index); elements++; } max_index = max(max_index, inst_max_index); l++; } rewind(fp); // y label_vec = ColumnVector(l, 1); // x^T if (min_index <= 0) { octave_idx_type r = max_index-min_index+1; octave_idx_type c = l; octave_idx_type val = elements; instance_mat = SparseMatrix(r, c, val); } else { octave_idx_type r = max_index-min_index+1; octave_idx_type c = l; octave_idx_type val = elements; instance_mat = SparseMatrix(r, c, val); } labels = (double*)label_vec.data(); samples = (double*)instance_mat.data(); ir = (octave_idx_type*)instance_mat.ridx(); jc = (octave_idx_type*)instance_mat.cidx(); k=0; for(i=0;i start from 0 ir[k] = strtol(idx,&endptr,10) - min_index; errno = 0; samples[k] = strtod(val,&endptr); if (endptr == val || errno != 0 || (*endptr != '\0' && !isspace(*endptr))) { printf("libsvmread: wrong input format at line %d.\n", (int)i+1); return; } ++k; } } jc[l] = k; fclose(fp); free(line); // transpose instance sparse matrix in row format instance_mat.transpose(); } DEFUN_DLD (libsvmread, args, nargout, "-*- texinfo -*- \n\n\ @deftypefn {statistics} {[@var{labels}, @var{data}] =} libsvmread (@var{filename})\n\ \n\ \n\ This function reads the labels and the corresponding instance_matrix from a \ LIBSVM data file and stores them in @var{labels} and @var{data} respectively. \ These can then be used as inputs to @code{svmtrain} or @code{svmpredict} \ function. \ \n\ \n\ @end deftypefn") { if(args.length() != 1 || nargout != 2) { error ("libsvmread: wrong number of input or output arguments."); } if(!args(0).is_string()) { error ("libsvmread: filename must be a string."); } string filename = args(0).string_value(); octave_value_list retval(nargout); ColumnVector label_vec; SparseMatrix instance_mat; read(filename, label_vec, instance_mat); retval(0) = label_vec; retval(1) = instance_mat.transpose(); return retval; } /* %!error [L, D] = libsvmread (24); %!error ... %! D = libsvmread ("filename"); %!test %! [L, D] = libsvmread (file_in_loadpath ("heart_scale.dat")); %! assert_equal (size (L), [270, 1]); %! assert_equal (size (D), [270, 13]); %!test %! [L, D] = libsvmread (file_in_loadpath ("heart_scale.dat")); %! assert_equal (issparse (L), false); %! assert_equal (issparse (D), true); */ statistics-release-1.9.2/src/libsvmwrite.cc000066400000000000000000000106641524624707500210230ustar00rootroot00000000000000/* Copyright (C) 2025 Andreas Bertsatos Adapted from MATLAB libsvmwrite.c file from the LIBSVM 3.36 (2025) library by Chih-Chung Chang and Chih-Jen Lin. This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; void write(string filename, ColumnVector label_vec, SparseMatrix instance_mat) { // open file FILE *fp = fopen(filename.c_str(),"w"); if (fp == NULL) { error ("libsvmwrite: error opening file for write."); } else { // check for equal number of instances and labels int im_rows = (int)instance_mat.rows(); int lv_rows = (int)label_vec.rows(); if(im_rows != lv_rows) { // close file fclose (fp); remove (filename.c_str()); error ("libsvmwrite: length of label vector does not match instances."); } // transpose instance sparse matrix in column format SparseMatrix instance_mat_col = instance_mat.transpose(); octave_idx_type *ir, *jc, k, low, high; size_t i, l, label_vector_row_num; double *samples, *labels; // each column is one instance labels = (double*)label_vec.data(); samples = (double*)instance_mat_col.data(); ir = (octave_idx_type*)instance_mat_col.ridx(); jc = (octave_idx_type*)instance_mat_col.cidx(); for(int i = 0; i < lv_rows; i++) { fprintf(fp, "%.17g", labels[i]); low = jc[i], high = jc[i+1]; for(k=low;k 0) { error ("libsvmwrite: wrong number of output arguments."); return octave_value_list(); } // Transform the input Matrix to libsvm format if(args.length() == 3) { if(!args(1).is_double_type() || !args(2).is_double_type()) { error ("libsvmwrite: label vector and instance matrix must be double."); } if(!args(0).is_string()) { error ("libsvmwrite: filename must be a string."); } string filename = args(0).string_value(); if(args(2).issparse()) { ColumnVector label_vec = args(1).column_vector_value(); SparseMatrix instance_mat = args(2).sparse_matrix_value(); write(filename, label_vec, instance_mat); } else { error ("libsvmwrite: instance_matrix must be sparse."); } } else { error ("libsvmwrite: wrong number of input arguments."); } return octave_value_list(); } /* %!shared L, D %! [L, D] = libsvmread (file_in_loadpath ("heart_scale.dat")); %!error libsvmwrite ("", L, D); %!error ... %! libsvmwrite (tempname (), [L;L], D); %!error ... %! OUT = libsvmwrite (tempname (), L, D); %!error ... %! libsvmwrite (tempname (), single (L), D); %!error libsvmwrite (13412, L, D); %!error ... %! libsvmwrite (tempname (), L, full (D)); %!error ... %! libsvmwrite (tempname (), L, D, D); */ statistics-release-1.9.2/src/svm.cpp000066400000000000000000002076161524624707500174630ustar00rootroot00000000000000/* Copyright (C) 2025 Chih-Chung Chang and Chih-Jen Lin This file is part of the statistics package for GNU Octave. Permission granted by Chih-Jen Lin to the package maintainer to include this file and double license under GPLv3 by means of personal communication. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include "svm.h" int libsvm_version = LIBSVM_VERSION; typedef float Qfloat; typedef signed char schar; #ifndef min template static inline T min(T x,T y) { return (x static inline T max(T x,T y) { return (x>y)?x:y; } #endif template static inline void swap(T& x, T& y) { T t=x; x=y; y=t; } template static inline void clone(T*& dst, S* src, int n) { dst = new T[n]; memcpy((void *)dst,(void *)src,sizeof(T)*n); } static inline double powi(double base, int times) { double tmp = base, ret = 1.0; for(int t=times; t>0; t/=2) { if(t%2==1) ret*=tmp; tmp = tmp * tmp; } return ret; } #define INF HUGE_VAL #define TAU 1e-12 #define Malloc(type,n) (type *)malloc((n)*sizeof(type)) static void print_string_stdout(const char *s) { fputs(s,stdout); fflush(stdout); } static void (*svm_print_string) (const char *) = &print_string_stdout; #if 1 static void info(const char *fmt,...) { char buf[BUFSIZ]; va_list ap; va_start(ap,fmt); vsnprintf(buf,BUFSIZ,fmt,ap); va_end(ap); (*svm_print_string)(buf); } #else static void info(const char *fmt,...) {} #endif // // Kernel Cache // // l is the number of total data items // size is the cache size limit in bytes // class Cache { public: Cache(int l,size_t size); ~Cache(); // request data [0,len) // return some position p where [p,len) need to be filled // (p >= len if nothing needs to be filled) int get_data(const int index, Qfloat **data, int len); void swap_index(int i, int j); private: int l; size_t size; struct head_t { head_t *prev, *next; // a circular list Qfloat *data; int len; // data[0,len) is cached in this entry }; head_t *head; head_t lru_head; void lru_delete(head_t *h); void lru_insert(head_t *h); }; Cache::Cache(int l_,size_t size_):l(l_),size(size_) { head = (head_t *)calloc(l,sizeof(head_t)); // initialized to 0 size /= sizeof(Qfloat); size_t header_size = l * sizeof(head_t) / sizeof(Qfloat); size = max(size, 2 * (size_t) l + header_size) - header_size; // cache must be large enough for two columns lru_head.next = lru_head.prev = &lru_head; } Cache::~Cache() { for(head_t *h = lru_head.next; h != &lru_head; h=h->next) free(h->data); free(head); } void Cache::lru_delete(head_t *h) { // delete from current location h->prev->next = h->next; h->next->prev = h->prev; } void Cache::lru_insert(head_t *h) { // insert to last position h->next = &lru_head; h->prev = lru_head.prev; h->prev->next = h; h->next->prev = h; } int Cache::get_data(const int index, Qfloat **data, int len) { head_t *h = &head[index]; if(h->len) lru_delete(h); int more = len - h->len; if(more > 0) { // free old space while(size < (size_t)more) { head_t *old = lru_head.next; lru_delete(old); free(old->data); size += old->len; old->data = 0; old->len = 0; } // allocate new space h->data = (Qfloat *)realloc(h->data,sizeof(Qfloat)*len); size -= more; // previous while loop guarantees size >= more and subtraction of size_t variable will not underflow swap(h->len,len); } lru_insert(h); *data = h->data; return len; } void Cache::swap_index(int i, int j) { if(i==j) return; if(head[i].len) lru_delete(&head[i]); if(head[j].len) lru_delete(&head[j]); swap(head[i].data,head[j].data); swap(head[i].len,head[j].len); if(head[i].len) lru_insert(&head[i]); if(head[j].len) lru_insert(&head[j]); if(i>j) swap(i,j); for(head_t *h = lru_head.next; h!=&lru_head; h=h->next) { if(h->len > i) { if(h->len > j) swap(h->data[i],h->data[j]); else { // give up lru_delete(h); free(h->data); size += h->len; h->data = 0; h->len = 0; } } } } // // Kernel evaluation // // the static method k_function is for doing single kernel evaluation // the constructor of Kernel prepares to calculate the l*l kernel matrix // the member function get_Q is for getting one column from the Q Matrix // class QMatrix { public: virtual Qfloat *get_Q(int column, int len) const = 0; virtual double *get_QD() const = 0; virtual void swap_index(int i, int j) const = 0; virtual ~QMatrix() {} }; class Kernel: public QMatrix { public: Kernel(int l, svm_node * const * x, const svm_parameter& param); virtual ~Kernel(); static double k_function(const svm_node *x, const svm_node *y, const svm_parameter& param); virtual Qfloat *get_Q(int column, int len) const = 0; virtual double *get_QD() const = 0; virtual void swap_index(int i, int j) const // no so const... { swap(x[i],x[j]); if(x_square) swap(x_square[i],x_square[j]); } protected: double (Kernel::*kernel_function)(int i, int j) const; private: const svm_node **x; double *x_square; // svm_parameter const int kernel_type; const int degree; const double gamma; const double coef0; static double dot(const svm_node *px, const svm_node *py); double kernel_linear(int i, int j) const { return dot(x[i],x[j]); } double kernel_poly(int i, int j) const { return powi(gamma*dot(x[i],x[j])+coef0,degree); } double kernel_rbf(int i, int j) const { return exp(-gamma*(x_square[i]+x_square[j]-2*dot(x[i],x[j]))); } double kernel_sigmoid(int i, int j) const { return tanh(gamma*dot(x[i],x[j])+coef0); } double kernel_precomputed(int i, int j) const { return x[i][(int)(x[j][0].value)].value; } }; Kernel::Kernel(int l, svm_node * const * x_, const svm_parameter& param) :kernel_type(param.kernel_type), degree(param.degree), gamma(param.gamma), coef0(param.coef0) { switch(kernel_type) { case LINEAR: kernel_function = &Kernel::kernel_linear; break; case POLY: kernel_function = &Kernel::kernel_poly; break; case RBF: kernel_function = &Kernel::kernel_rbf; break; case SIGMOID: kernel_function = &Kernel::kernel_sigmoid; break; case PRECOMPUTED: kernel_function = &Kernel::kernel_precomputed; break; } clone(x,x_,l); if(kernel_type == RBF) { x_square = new double[l]; for(int i=0;iindex != -1 && py->index != -1) { if(px->index == py->index) { sum += px->value * py->value; ++px; ++py; } else { if(px->index > py->index) ++py; else ++px; } } return sum; } double Kernel::k_function(const svm_node *x, const svm_node *y, const svm_parameter& param) { switch(param.kernel_type) { case LINEAR: return dot(x,y); case POLY: return powi(param.gamma*dot(x,y)+param.coef0,param.degree); case RBF: { double sum = 0; while(x->index != -1 && y->index !=-1) { if(x->index == y->index) { double d = x->value - y->value; sum += d*d; ++x; ++y; } else { if(x->index > y->index) { sum += y->value * y->value; ++y; } else { sum += x->value * x->value; ++x; } } } while(x->index != -1) { sum += x->value * x->value; ++x; } while(y->index != -1) { sum += y->value * y->value; ++y; } return exp(-param.gamma*sum); } case SIGMOID: return tanh(param.gamma*dot(x,y)+param.coef0); case PRECOMPUTED: //x: test (validation), y: SV return x[(int)(y->value)].value; default: return 0; // Unreachable } } // An SMO algorithm in Fan et al., JMLR 6(2005), p. 1889--1918 // Solves: // // min 0.5(\alpha^T Q \alpha) + p^T \alpha // // y^T \alpha = \delta // y_i = +1 or -1 // 0 <= alpha_i <= Cp for y_i = 1 // 0 <= alpha_i <= Cn for y_i = -1 // // Given: // // Q, p, y, Cp, Cn, and an initial feasible point \alpha // l is the size of vectors and matrices // eps is the stopping tolerance // // solution will be put in \alpha, objective value will be put in obj // class Solver { public: Solver() {}; virtual ~Solver() {}; struct SolutionInfo { double obj; double rho; double upper_bound_p; double upper_bound_n; double r; // for Solver_NU }; void Solve(int l, const QMatrix& Q, const double *p_, const schar *y_, double *alpha_, double Cp, double Cn, double eps, SolutionInfo* si, int shrinking); protected: int active_size; schar *y; double *G; // gradient of objective function enum { LOWER_BOUND, UPPER_BOUND, FREE }; char *alpha_status; // LOWER_BOUND, UPPER_BOUND, FREE double *alpha; const QMatrix *Q; const double *QD; double eps; double Cp,Cn; double *p; int *active_set; double *G_bar; // gradient, if we treat free variables as 0 int l; bool unshrink; // XXX double get_C(int i) { return (y[i] > 0)? Cp : Cn; } void update_alpha_status(int i) { if(alpha[i] >= get_C(i)) alpha_status[i] = UPPER_BOUND; else if(alpha[i] <= 0) alpha_status[i] = LOWER_BOUND; else alpha_status[i] = FREE; } bool is_upper_bound(int i) { return alpha_status[i] == UPPER_BOUND; } bool is_lower_bound(int i) { return alpha_status[i] == LOWER_BOUND; } bool is_free(int i) { return alpha_status[i] == FREE; } void swap_index(int i, int j); void reconstruct_gradient(); virtual int select_working_set(int &i, int &j); virtual double calculate_rho(); virtual void do_shrinking(); private: bool be_shrunk(int i, double Gmax1, double Gmax2); }; void Solver::swap_index(int i, int j) { Q->swap_index(i,j); swap(y[i],y[j]); swap(G[i],G[j]); swap(alpha_status[i],alpha_status[j]); swap(alpha[i],alpha[j]); swap(p[i],p[j]); swap(active_set[i],active_set[j]); swap(G_bar[i],G_bar[j]); } void Solver::reconstruct_gradient() { // reconstruct inactive elements of G from G_bar and free variables if(active_size == l) return; int i,j; int nr_free = 0; for(j=active_size;j 2*active_size*(l-active_size)) { for(i=active_size;iget_Q(i,active_size); for(j=0;jget_Q(i,l); double alpha_i = alpha[i]; for(j=active_size;jl = l; this->Q = &Q; QD=Q.get_QD(); clone(p, p_,l); clone(y, y_,l); clone(alpha,alpha_,l); this->Cp = Cp; this->Cn = Cn; this->eps = eps; unshrink = false; // initialize alpha_status { alpha_status = new char[l]; for(int i=0;iINT_MAX/100 ? INT_MAX : 100*l); int counter = min(l,1000)+1; while(iter < max_iter) { // show progress and do shrinking if(--counter == 0) { counter = min(l,1000); if(shrinking) do_shrinking(); info("."); } int i,j; if(select_working_set(i,j)!=0) { // reconstruct the whole gradient reconstruct_gradient(); // reset active set size and check active_size = l; info("*"); if(select_working_set(i,j)!=0) break; else counter = 1; // do shrinking next iteration } ++iter; // update alpha[i] and alpha[j], handle bounds carefully const Qfloat *Q_i = Q.get_Q(i,active_size); const Qfloat *Q_j = Q.get_Q(j,active_size); double C_i = get_C(i); double C_j = get_C(j); double old_alpha_i = alpha[i]; double old_alpha_j = alpha[j]; if(y[i]!=y[j]) { double quad_coef = QD[i]+QD[j]+2*Q_i[j]; if (quad_coef <= 0) quad_coef = TAU; double delta = (-G[i]-G[j])/quad_coef; double diff = alpha[i] - alpha[j]; alpha[i] += delta; alpha[j] += delta; if(diff > 0) { if(alpha[j] < 0) { alpha[j] = 0; alpha[i] = diff; } } else { if(alpha[i] < 0) { alpha[i] = 0; alpha[j] = -diff; } } if(diff > C_i - C_j) { if(alpha[i] > C_i) { alpha[i] = C_i; alpha[j] = C_i - diff; } } else { if(alpha[j] > C_j) { alpha[j] = C_j; alpha[i] = C_j + diff; } } } else { double quad_coef = QD[i]+QD[j]-2*Q_i[j]; if (quad_coef <= 0) quad_coef = TAU; double delta = (G[i]-G[j])/quad_coef; double sum = alpha[i] + alpha[j]; alpha[i] -= delta; alpha[j] += delta; if(sum > C_i) { if(alpha[i] > C_i) { alpha[i] = C_i; alpha[j] = sum - C_i; } } else { if(alpha[j] < 0) { alpha[j] = 0; alpha[i] = sum; } } if(sum > C_j) { if(alpha[j] > C_j) { alpha[j] = C_j; alpha[i] = sum - C_j; } } else { if(alpha[i] < 0) { alpha[i] = 0; alpha[j] = sum; } } } // update G double delta_alpha_i = alpha[i] - old_alpha_i; double delta_alpha_j = alpha[j] - old_alpha_j; for(int k=0;k= max_iter) { if(active_size < l) { // reconstruct the whole gradient to calculate objective value reconstruct_gradient(); active_size = l; info("*"); } fprintf(stderr,"\nWARNING: reaching max number of iterations\n"); } // calculate rho si->rho = calculate_rho(); // calculate objective value { double v = 0; int i; for(i=0;iobj = v/2; } // put back the solution { for(int i=0;iupper_bound_p = Cp; si->upper_bound_n = Cn; info("\noptimization finished, #iter = %d\n",iter); delete[] p; delete[] y; delete[] alpha; delete[] alpha_status; delete[] active_set; delete[] G; delete[] G_bar; } // return 1 if already optimal, return 0 otherwise int Solver::select_working_set(int &out_i, int &out_j) { // return i,j such that // i: maximizes -y_i * grad(f)_i, i in I_up(\alpha) // j: minimizes the decrease of obj value // (if quadratic coefficient <= 0, replace it with tau) // -y_j*grad(f)_j < -y_i*grad(f)_i, j in I_low(\alpha) double Gmax = -INF; double Gmax2 = -INF; int Gmax_idx = -1; int Gmin_idx = -1; double obj_diff_min = INF; for(int t=0;t= Gmax) { Gmax = -G[t]; Gmax_idx = t; } } else { if(!is_lower_bound(t)) if(G[t] >= Gmax) { Gmax = G[t]; Gmax_idx = t; } } int i = Gmax_idx; const Qfloat *Q_i = NULL; if(i != -1) // NULL Q_i not accessed: Gmax=-INF if i=-1 Q_i = Q->get_Q(i,active_size); for(int j=0;j= Gmax2) Gmax2 = G[j]; if (grad_diff > 0) { double obj_diff; double quad_coef = QD[i]+QD[j]-2.0*y[i]*Q_i[j]; if (quad_coef > 0) obj_diff = -(grad_diff*grad_diff)/quad_coef; else obj_diff = -(grad_diff*grad_diff)/TAU; if (obj_diff <= obj_diff_min) { Gmin_idx=j; obj_diff_min = obj_diff; } } } } else { if (!is_upper_bound(j)) { double grad_diff= Gmax-G[j]; if (-G[j] >= Gmax2) Gmax2 = -G[j]; if (grad_diff > 0) { double obj_diff; double quad_coef = QD[i]+QD[j]+2.0*y[i]*Q_i[j]; if (quad_coef > 0) obj_diff = -(grad_diff*grad_diff)/quad_coef; else obj_diff = -(grad_diff*grad_diff)/TAU; if (obj_diff <= obj_diff_min) { Gmin_idx=j; obj_diff_min = obj_diff; } } } } } if(Gmax+Gmax2 < eps || Gmin_idx == -1) return 1; out_i = Gmax_idx; out_j = Gmin_idx; return 0; } bool Solver::be_shrunk(int i, double Gmax1, double Gmax2) { if(is_upper_bound(i)) { if(y[i]==+1) return(-G[i] > Gmax1); else return(-G[i] > Gmax2); } else if(is_lower_bound(i)) { if(y[i]==+1) return(G[i] > Gmax2); else return(G[i] > Gmax1); } else return(false); } void Solver::do_shrinking() { int i; double Gmax1 = -INF; // max { -y_i * grad(f)_i | i in I_up(\alpha) } double Gmax2 = -INF; // max { y_i * grad(f)_i | i in I_low(\alpha) } // find maximal violating pair first for(i=0;i= Gmax1) Gmax1 = -G[i]; } if(!is_lower_bound(i)) { if(G[i] >= Gmax2) Gmax2 = G[i]; } } else { if(!is_upper_bound(i)) { if(-G[i] >= Gmax2) Gmax2 = -G[i]; } if(!is_lower_bound(i)) { if(G[i] >= Gmax1) Gmax1 = G[i]; } } } if(unshrink == false && Gmax1 + Gmax2 <= eps*10) { unshrink = true; reconstruct_gradient(); active_size = l; info("*"); } for(i=0;i i) { if (!be_shrunk(active_size, Gmax1, Gmax2)) { swap_index(i,active_size); break; } active_size--; } } } double Solver::calculate_rho() { double r; int nr_free = 0; double ub = INF, lb = -INF, sum_free = 0; for(int i=0;i0) r = sum_free/nr_free; else r = (ub+lb)/2; return r; } // // Solver for nu-svm classification and regression // // additional constraint: e^T \alpha = constant // class Solver_NU: public Solver { public: Solver_NU() {} void Solve(int l, const QMatrix& Q, const double *p, const schar *y, double *alpha, double Cp, double Cn, double eps, SolutionInfo* si, int shrinking) { this->si = si; Solver::Solve(l,Q,p,y,alpha,Cp,Cn,eps,si,shrinking); } private: SolutionInfo *si; int select_working_set(int &i, int &j); double calculate_rho(); bool be_shrunk(int i, double Gmax1, double Gmax2, double Gmax3, double Gmax4); void do_shrinking(); }; // return 1 if already optimal, return 0 otherwise int Solver_NU::select_working_set(int &out_i, int &out_j) { // return i,j such that y_i = y_j and // i: maximizes -y_i * grad(f)_i, i in I_up(\alpha) // j: minimizes the decrease of obj value // (if quadratic coefficient <= 0, replace it with tau) // -y_j*grad(f)_j < -y_i*grad(f)_i, j in I_low(\alpha) double Gmaxp = -INF; double Gmaxp2 = -INF; int Gmaxp_idx = -1; double Gmaxn = -INF; double Gmaxn2 = -INF; int Gmaxn_idx = -1; int Gmin_idx = -1; double obj_diff_min = INF; for(int t=0;t= Gmaxp) { Gmaxp = -G[t]; Gmaxp_idx = t; } } else { if(!is_lower_bound(t)) if(G[t] >= Gmaxn) { Gmaxn = G[t]; Gmaxn_idx = t; } } int ip = Gmaxp_idx; int in = Gmaxn_idx; const Qfloat *Q_ip = NULL; const Qfloat *Q_in = NULL; if(ip != -1) // NULL Q_ip not accessed: Gmaxp=-INF if ip=-1 Q_ip = Q->get_Q(ip,active_size); if(in != -1) Q_in = Q->get_Q(in,active_size); for(int j=0;j= Gmaxp2) Gmaxp2 = G[j]; if (grad_diff > 0) { double obj_diff; double quad_coef = QD[ip]+QD[j]-2*Q_ip[j]; if (quad_coef > 0) obj_diff = -(grad_diff*grad_diff)/quad_coef; else obj_diff = -(grad_diff*grad_diff)/TAU; if (obj_diff <= obj_diff_min) { Gmin_idx=j; obj_diff_min = obj_diff; } } } } else { if (!is_upper_bound(j)) { double grad_diff=Gmaxn-G[j]; if (-G[j] >= Gmaxn2) Gmaxn2 = -G[j]; if (grad_diff > 0) { double obj_diff; double quad_coef = QD[in]+QD[j]-2*Q_in[j]; if (quad_coef > 0) obj_diff = -(grad_diff*grad_diff)/quad_coef; else obj_diff = -(grad_diff*grad_diff)/TAU; if (obj_diff <= obj_diff_min) { Gmin_idx=j; obj_diff_min = obj_diff; } } } } } if(max(Gmaxp+Gmaxp2,Gmaxn+Gmaxn2) < eps || Gmin_idx == -1) return 1; if (y[Gmin_idx] == +1) out_i = Gmaxp_idx; else out_i = Gmaxn_idx; out_j = Gmin_idx; return 0; } bool Solver_NU::be_shrunk(int i, double Gmax1, double Gmax2, double Gmax3, double Gmax4) { if(is_upper_bound(i)) { if(y[i]==+1) return(-G[i] > Gmax1); else return(-G[i] > Gmax4); } else if(is_lower_bound(i)) { if(y[i]==+1) return(G[i] > Gmax2); else return(G[i] > Gmax3); } else return(false); } void Solver_NU::do_shrinking() { double Gmax1 = -INF; // max { -y_i * grad(f)_i | y_i = +1, i in I_up(\alpha) } double Gmax2 = -INF; // max { y_i * grad(f)_i | y_i = +1, i in I_low(\alpha) } double Gmax3 = -INF; // max { -y_i * grad(f)_i | y_i = -1, i in I_up(\alpha) } double Gmax4 = -INF; // max { y_i * grad(f)_i | y_i = -1, i in I_low(\alpha) } // find maximal violating pair first int i; for(i=0;i Gmax1) Gmax1 = -G[i]; } else if(-G[i] > Gmax4) Gmax4 = -G[i]; } if(!is_lower_bound(i)) { if(y[i]==+1) { if(G[i] > Gmax2) Gmax2 = G[i]; } else if(G[i] > Gmax3) Gmax3 = G[i]; } } if(unshrink == false && max(Gmax1+Gmax2,Gmax3+Gmax4) <= eps*10) { unshrink = true; reconstruct_gradient(); active_size = l; } for(i=0;i i) { if (!be_shrunk(active_size, Gmax1, Gmax2, Gmax3, Gmax4)) { swap_index(i,active_size); break; } active_size--; } } } double Solver_NU::calculate_rho() { int nr_free1 = 0,nr_free2 = 0; double ub1 = INF, ub2 = INF; double lb1 = -INF, lb2 = -INF; double sum_free1 = 0, sum_free2 = 0; for(int i=0;i 0) r1 = sum_free1/nr_free1; else r1 = (ub1+lb1)/2; if(nr_free2 > 0) r2 = sum_free2/nr_free2; else r2 = (ub2+lb2)/2; si->r = (r1+r2)/2; return (r1-r2)/2; } // // Q matrices for various formulations // class SVC_Q: public Kernel { public: SVC_Q(const svm_problem& prob, const svm_parameter& param, const schar *y_) :Kernel(prob.l, prob.x, param) { clone(y,y_,prob.l); cache = new Cache(prob.l,(size_t)(param.cache_size*(1<<20))); QD = new double[prob.l]; for(int i=0;i*kernel_function)(i,i); } Qfloat *get_Q(int i, int len) const { Qfloat *data; int start, j; if((start = cache->get_data(i,&data,len)) < len) { for(j=start;j*kernel_function)(i,j)); } return data; } double *get_QD() const { return QD; } void swap_index(int i, int j) const { cache->swap_index(i,j); Kernel::swap_index(i,j); swap(y[i],y[j]); swap(QD[i],QD[j]); } ~SVC_Q() { delete[] y; delete cache; delete[] QD; } private: schar *y; Cache *cache; double *QD; }; class ONE_CLASS_Q: public Kernel { public: ONE_CLASS_Q(const svm_problem& prob, const svm_parameter& param) :Kernel(prob.l, prob.x, param) { cache = new Cache(prob.l,(size_t)(param.cache_size*(1<<20))); QD = new double[prob.l]; for(int i=0;i*kernel_function)(i,i); } Qfloat *get_Q(int i, int len) const { Qfloat *data; int start, j; if((start = cache->get_data(i,&data,len)) < len) { for(j=start;j*kernel_function)(i,j); } return data; } double *get_QD() const { return QD; } void swap_index(int i, int j) const { cache->swap_index(i,j); Kernel::swap_index(i,j); swap(QD[i],QD[j]); } ~ONE_CLASS_Q() { delete cache; delete[] QD; } private: Cache *cache; double *QD; }; class SVR_Q: public Kernel { public: SVR_Q(const svm_problem& prob, const svm_parameter& param) :Kernel(prob.l, prob.x, param) { l = prob.l; cache = new Cache(l,(size_t)(param.cache_size*(1<<20))); QD = new double[2*l]; sign = new schar[2*l]; index = new int[2*l]; for(int k=0;k*kernel_function)(k,k); QD[k+l] = QD[k]; } buffer[0] = new Qfloat[2*l]; buffer[1] = new Qfloat[2*l]; next_buffer = 0; } void swap_index(int i, int j) const { swap(sign[i],sign[j]); swap(index[i],index[j]); swap(QD[i],QD[j]); } Qfloat *get_Q(int i, int len) const { Qfloat *data; int j, real_i = index[i]; if(cache->get_data(real_i,&data,l) < l) { for(j=0;j*kernel_function)(real_i,j); } // reorder and copy Qfloat *buf = buffer[next_buffer]; next_buffer = 1 - next_buffer; schar si = sign[i]; for(j=0;jl; double *minus_ones = new double[l]; schar *y = new schar[l]; int i; for(i=0;iy[i] > 0) y[i] = +1; else y[i] = -1; } Solver s; s.Solve(l, SVC_Q(*prob,*param,y), minus_ones, y, alpha, Cp, Cn, param->eps, si, param->shrinking); double sum_alpha=0; for(i=0;il)); for(i=0;il; double nu = param->nu; schar *y = new schar[l]; for(i=0;iy[i]>0) y[i] = +1; else y[i] = -1; double sum_pos = nu*l/2; double sum_neg = nu*l/2; for(i=0;ieps, si, param->shrinking); double r = si->r; info("C = %f\n",1/r); for(i=0;irho /= r; si->obj /= (r*r); si->upper_bound_p = 1/r; si->upper_bound_n = 1/r; delete[] y; delete[] zeros; } static void solve_one_class( const svm_problem *prob, const svm_parameter *param, double *alpha, Solver::SolutionInfo* si) { int l = prob->l; double *zeros = new double[l]; schar *ones = new schar[l]; int i; int n = (int)(param->nu*prob->l); // # of alpha's at upper bound for(i=0;il) alpha[n] = param->nu * prob->l - n; for(i=n+1;ieps, si, param->shrinking); delete[] zeros; delete[] ones; } static void solve_epsilon_svr( const svm_problem *prob, const svm_parameter *param, double *alpha, Solver::SolutionInfo* si) { int l = prob->l; double *alpha2 = new double[2*l]; double *linear_term = new double[2*l]; schar *y = new schar[2*l]; int i; for(i=0;ip - prob->y[i]; y[i] = 1; alpha2[i+l] = 0; linear_term[i+l] = param->p + prob->y[i]; y[i+l] = -1; } Solver s; s.Solve(2*l, SVR_Q(*prob,*param), linear_term, y, alpha2, param->C, param->C, param->eps, si, param->shrinking); double sum_alpha = 0; for(i=0;iC*l)); delete[] alpha2; delete[] linear_term; delete[] y; } static void solve_nu_svr( const svm_problem *prob, const svm_parameter *param, double *alpha, Solver::SolutionInfo* si) { int l = prob->l; double C = param->C; double *alpha2 = new double[2*l]; double *linear_term = new double[2*l]; schar *y = new schar[2*l]; int i; double sum = C * param->nu * l / 2; for(i=0;iy[i]; y[i] = 1; linear_term[i+l] = prob->y[i]; y[i+l] = -1; } Solver_NU s; s.Solve(2*l, SVR_Q(*prob,*param), linear_term, y, alpha2, C, C, param->eps, si, param->shrinking); info("epsilon = %f\n",-si->r); for(i=0;il); Solver::SolutionInfo si; switch(param->svm_type) { case C_SVC: solve_c_svc(prob,param,alpha,&si,Cp,Cn); break; case NU_SVC: solve_nu_svc(prob,param,alpha,&si); break; case ONE_CLASS: solve_one_class(prob,param,alpha,&si); break; case EPSILON_SVR: solve_epsilon_svr(prob,param,alpha,&si); break; case NU_SVR: solve_nu_svr(prob,param,alpha,&si); break; } info("obj = %f, rho = %f\n",si.obj,si.rho); // output SVs int nSV = 0; int nBSV = 0; for(int i=0;il;i++) { if(fabs(alpha[i]) > 0) { ++nSV; if(prob->y[i] > 0) { if(fabs(alpha[i]) >= si.upper_bound_p) ++nBSV; } else { if(fabs(alpha[i]) >= si.upper_bound_n) ++nBSV; } } } info("nSV = %d, nBSV = %d\n",nSV,nBSV); decision_function f; f.alpha = alpha; f.rho = si.rho; return f; } // Platt's binary SVM Probablistic Output: an improvement from Lin et al. static void sigmoid_train( int l, const double *dec_values, const double *labels, double& A, double& B) { double prior1=0, prior0 = 0; int i; for (i=0;i 0) prior1+=1; else prior0+=1; int max_iter=100; // Maximal number of iterations double min_step=1e-10; // Minimal step taken in line search double sigma=1e-12; // For numerically strict PD of Hessian double eps=1e-5; double hiTarget=(prior1+1.0)/(prior1+2.0); double loTarget=1/(prior0+2.0); double *t=Malloc(double,l); double fApB,p,q,h11,h22,h21,g1,g2,det,dA,dB,gd,stepsize; double newA,newB,newf,d1,d2; int iter; // Initial Point and Initial Fun Value A=0.0; B=log((prior0+1.0)/(prior1+1.0)); double fval = 0.0; for (i=0;i0) t[i]=hiTarget; else t[i]=loTarget; fApB = dec_values[i]*A+B; if (fApB>=0) fval += t[i]*fApB + log(1+exp(-fApB)); else fval += (t[i] - 1)*fApB +log(1+exp(fApB)); } for (iter=0;iter= 0) { p=exp(-fApB)/(1.0+exp(-fApB)); q=1.0/(1.0+exp(-fApB)); } else { p=1.0/(1.0+exp(fApB)); q=exp(fApB)/(1.0+exp(fApB)); } d2=p*q; h11+=dec_values[i]*dec_values[i]*d2; h22+=d2; h21+=dec_values[i]*d2; d1=t[i]-p; g1+=dec_values[i]*d1; g2+=d1; } // Stopping Criteria if (fabs(g1)= min_step) { newA = A + stepsize * dA; newB = B + stepsize * dB; // New function value newf = 0.0; for (i=0;i= 0) newf += t[i]*fApB + log(1+exp(-fApB)); else newf += (t[i] - 1)*fApB +log(1+exp(fApB)); } // Check sufficient decrease if (newf=max_iter) info("Reaching maximal iterations in two-class probability estimates\n"); free(t); } static double sigmoid_predict(double decision_value, double A, double B) { double fApB = decision_value*A+B; // 1-p used later; avoid catastrophic cancellation if (fApB >= 0) return exp(-fApB)/(1.0+exp(-fApB)); else return 1.0/(1+exp(fApB)) ; } // Method 2 from the multiclass_prob paper by Wu, Lin, and Weng to predict probabilities static void multiclass_probability(int k, double **r, double *p) { int t,j; int iter = 0, max_iter=max(100,k); double **Q=Malloc(double *,k); double *Qp=Malloc(double,k); double pQp, eps=0.005/k; for (t=0;tmax_error) max_error=error; } if (max_error=max_iter) info("Exceeds max_iter in multiclass_prob\n"); for(t=0;tl); double *dec_values = Malloc(double,prob->l); // random shuffle for(i=0;il;i++) perm[i]=i; for(i=0;il;i++) { int j = i+rand()%(prob->l-i); swap(perm[i],perm[j]); } for(i=0;il/nr_fold; int end = (i+1)*prob->l/nr_fold; int j,k; struct svm_problem subprob; subprob.l = prob->l-(end-begin); subprob.x = Malloc(struct svm_node*,subprob.l); subprob.y = Malloc(double,subprob.l); k=0; for(j=0;jx[perm[j]]; subprob.y[k] = prob->y[perm[j]]; ++k; } for(j=end;jl;j++) { subprob.x[k] = prob->x[perm[j]]; subprob.y[k] = prob->y[perm[j]]; ++k; } int p_count=0,n_count=0; for(j=0;j0) p_count++; else n_count++; if(p_count==0 && n_count==0) for(j=begin;j 0 && n_count == 0) for(j=begin;j 0) for(j=begin;jx[perm[j]],&(dec_values[perm[j]])); // ensure +1 -1 order; reason not using CV subroutine dec_values[perm[j]] *= submodel->label[0]; } svm_free_and_destroy_model(&submodel); svm_destroy_param(&subparam); } free(subprob.x); free(subprob.y); } sigmoid_train(prob->l,dec_values,prob->y,probA,probB); free(dec_values); free(perm); } // Binning method from the oneclass_prob paper by Que and Lin to predict the probability as a normal instance (i.e., not an outlier) static double predict_one_class_probability(const svm_model *model, double dec_value) { double prob_estimate = 0.0; int nr_marks = 10; if(dec_value < model->prob_density_marks[0]) prob_estimate = 0.001; else if(dec_value > model->prob_density_marks[nr_marks-1]) prob_estimate = 0.999; else { for(int i=1;iprob_density_marks[i]) { prob_estimate = (double)i/nr_marks; break; } } return prob_estimate; } static int compare_double(const void *a, const void *b) { if(*(double *)a > *(double *)b) return 1; else if(*(double *)a < *(double *)b) return -1; return 0; } // Get parameters for one-class SVM probability estimates static int svm_one_class_probability(const svm_problem *prob, const svm_model *model, double *prob_density_marks) { double *dec_values = Malloc(double,prob->l); double *pred_results = Malloc(double,prob->l); int ret = 0; int nr_marks = 10; for(int i=0;il;i++) pred_results[i] = svm_predict_values(model,prob->x[i],&dec_values[i]); qsort(dec_values,prob->l,sizeof(double),compare_double); int neg_counter=0; for(int i=0;il;i++) if(dec_values[i]>=0) { neg_counter = i; break; } int pos_counter = prob->l-neg_counter; if(neg_counterl); double mae = 0; svm_parameter newparam = *param; newparam.probability = 0; svm_cross_validation(prob,&newparam,nr_fold,ymv); for(i=0;il;i++) { ymv[i]=prob->y[i]-ymv[i]; mae += fabs(ymv[i]); } mae /= prob->l; double std=sqrt(2*mae*mae); int count=0; mae=0; for(i=0;il;i++) if (fabs(ymv[i]) > 5*std) count=count+1; else mae+=fabs(ymv[i]); mae /= (prob->l-count); info("Prob. model for test data: target value = predicted value + z,\nz: Laplace distribution e^(-|z|/sigma)/(2sigma),sigma= %g\n",mae); free(ymv); return mae; } // label: label name, start: begin of each class, count: #data of classes, perm: indices to the original data // perm, length l, must be allocated before calling this subroutine static void svm_group_classes(const svm_problem *prob, int *nr_class_ret, int **label_ret, int **start_ret, int **count_ret, int *perm) { int l = prob->l; int max_nr_class = 16; int nr_class = 0; int *label = Malloc(int,max_nr_class); int *count = Malloc(int,max_nr_class); int *data_label = Malloc(int,l); int i; for(i=0;iy[i]; int j; for(j=0;jparam = *param; model->free_sv = 0; // XXX if(param->svm_type == ONE_CLASS || param->svm_type == EPSILON_SVR || param->svm_type == NU_SVR) { // regression or one-class-svm model->nr_class = 2; model->label = NULL; model->nSV = NULL; model->probA = NULL; model->probB = NULL; model->prob_density_marks = NULL; model->sv_coef = Malloc(double *,1); decision_function f = svm_train_one(prob,param,0,0); model->rho = Malloc(double,1); model->rho[0] = f.rho; int nSV = 0; int i; for(i=0;il;i++) if(fabs(f.alpha[i]) > 0) ++nSV; model->l = nSV; model->SV = Malloc(svm_node *,nSV); model->sv_coef[0] = Malloc(double,nSV); model->sv_indices = Malloc(int,nSV); int j = 0; for(i=0;il;i++) if(fabs(f.alpha[i]) > 0) { model->SV[j] = prob->x[i]; model->sv_coef[0][j] = f.alpha[i]; model->sv_indices[j] = i+1; ++j; } if(param->probability && (param->svm_type == EPSILON_SVR || param->svm_type == NU_SVR)) { model->probA = Malloc(double,1); model->probA[0] = svm_svr_probability(prob,param); } else if(param->probability && param->svm_type == ONE_CLASS) { int nr_marks = 10; double *prob_density_marks = Malloc(double,nr_marks); if(svm_one_class_probability(prob,model,prob_density_marks) == 0) model->prob_density_marks = prob_density_marks; else free(prob_density_marks); } free(f.alpha); } else { // classification int l = prob->l; int nr_class; int *label = NULL; int *start = NULL; int *count = NULL; int *perm = Malloc(int,l); // group training data of the same class svm_group_classes(prob,&nr_class,&label,&start,&count,perm); if(nr_class == 1) info("WARNING: training data in only one class. See README for details.\n"); svm_node **x = Malloc(svm_node *,l); int i; for(i=0;ix[perm[i]]; // calculate weighted C double *weighted_C = Malloc(double, nr_class); for(i=0;iC; for(i=0;inr_weight;i++) { int j; for(j=0;jweight_label[i] == label[j]) break; if(j == nr_class) fprintf(stderr,"WARNING: class label %d specified in weight is not found\n", param->weight_label[i]); else weighted_C[j] *= param->weight[i]; } // train k*(k-1)/2 models bool *nonzero = Malloc(bool,l); for(i=0;iprobability) { probA=Malloc(double,nr_class*(nr_class-1)/2); probB=Malloc(double,nr_class*(nr_class-1)/2); } int p = 0; for(i=0;iprobability) svm_binary_svc_probability(&sub_prob,param,weighted_C[i],weighted_C[j],probA[p],probB[p]); f[p] = svm_train_one(&sub_prob,param,weighted_C[i],weighted_C[j]); for(k=0;k 0) nonzero[si+k] = true; for(k=0;k 0) nonzero[sj+k] = true; free(sub_prob.x); free(sub_prob.y); ++p; } // build output model->nr_class = nr_class; model->label = Malloc(int,nr_class); for(i=0;ilabel[i] = label[i]; model->rho = Malloc(double,nr_class*(nr_class-1)/2); for(i=0;irho[i] = f[i].rho; if(param->probability) { model->probA = Malloc(double,nr_class*(nr_class-1)/2); model->probB = Malloc(double,nr_class*(nr_class-1)/2); for(i=0;iprobA[i] = probA[i]; model->probB[i] = probB[i]; } } else { model->probA=NULL; model->probB=NULL; } model->prob_density_marks=NULL; // for one-class SVM probabilistic outputs only int total_sv = 0; int *nz_count = Malloc(int,nr_class); model->nSV = Malloc(int,nr_class); for(i=0;inSV[i] = nSV; nz_count[i] = nSV; } info("Total nSV = %d\n",total_sv); model->l = total_sv; model->SV = Malloc(svm_node *,total_sv); model->sv_indices = Malloc(int,total_sv); p = 0; for(i=0;iSV[p] = x[i]; model->sv_indices[p++] = perm[i] + 1; } int *nz_start = Malloc(int,nr_class); nz_start[0] = 0; for(i=1;isv_coef = Malloc(double *,nr_class-1); for(i=0;isv_coef[i] = Malloc(double,total_sv); p = 0; for(i=0;isv_coef[j-1][q++] = f[p].alpha[k]; q = nz_start[j]; for(k=0;ksv_coef[i][q++] = f[p].alpha[ci+k]; ++p; } free(label); free(probA); free(probB); free(count); free(perm); free(start); free(x); free(weighted_C); free(nonzero); for(i=0;il; int *perm = Malloc(int,l); int nr_class; if (nr_fold > l) { fprintf(stderr,"WARNING: # folds (%d) > # data (%d). Will use # folds = # data instead (i.e., leave-one-out cross validation)\n", nr_fold, l); nr_fold = l; } fold_start = Malloc(int,nr_fold+1); // stratified cv may not give leave-one-out rate // Each class to l folds -> some folds may have zero elements if((param->svm_type == C_SVC || param->svm_type == NU_SVC) && nr_fold < l) { int *start = NULL; int *label = NULL; int *count = NULL; svm_group_classes(prob,&nr_class,&label,&start,&count,perm); // random shuffle and then data grouped by fold using the array perm int *fold_count = Malloc(int,nr_fold); int c; int *index = Malloc(int,l); for(i=0;ix[perm[j]]; subprob.y[k] = prob->y[perm[j]]; ++k; } for(j=end;jx[perm[j]]; subprob.y[k] = prob->y[perm[j]]; ++k; } struct svm_model *submodel = svm_train(&subprob,param); if(param->probability && (param->svm_type == C_SVC || param->svm_type == NU_SVC)) { double *prob_estimates=Malloc(double,svm_get_nr_class(submodel)); for(j=begin;jx[perm[j]],prob_estimates); free(prob_estimates); } else for(j=begin;jx[perm[j]]); svm_free_and_destroy_model(&submodel); free(subprob.x); free(subprob.y); } free(fold_start); free(perm); } int svm_get_svm_type(const svm_model *model) { return model->param.svm_type; } int svm_get_nr_class(const svm_model *model) { return model->nr_class; } void svm_get_labels(const svm_model *model, int* label) { if (model->label != NULL) for(int i=0;inr_class;i++) label[i] = model->label[i]; } void svm_get_sv_indices(const svm_model *model, int* indices) { if (model->sv_indices != NULL) for(int i=0;il;i++) indices[i] = model->sv_indices[i]; } int svm_get_nr_sv(const svm_model *model) { return model->l; } double svm_get_svr_probability(const svm_model *model) { if ((model->param.svm_type == EPSILON_SVR || model->param.svm_type == NU_SVR) && model->probA!=NULL) return model->probA[0]; else { fprintf(stderr,"Model doesn't contain information for SVR probability inference\n"); return 0; } } double svm_predict_values(const svm_model *model, const svm_node *x, double* dec_values) { int i; if(model->param.svm_type == ONE_CLASS || model->param.svm_type == EPSILON_SVR || model->param.svm_type == NU_SVR) { double *sv_coef = model->sv_coef[0]; double sum = 0; for(i=0;il;i++) sum += sv_coef[i] * Kernel::k_function(x,model->SV[i],model->param); sum -= model->rho[0]; *dec_values = sum; if(model->param.svm_type == ONE_CLASS) return (sum>0)?1:-1; else return sum; } else { int nr_class = model->nr_class; int l = model->l; double *kvalue = Malloc(double,l); for(i=0;iSV[i],model->param); int *start = Malloc(int,nr_class); start[0] = 0; for(i=1;inSV[i-1]; int *vote = Malloc(int,nr_class); for(i=0;inSV[i]; int cj = model->nSV[j]; int k; double *coef1 = model->sv_coef[j-1]; double *coef2 = model->sv_coef[i]; for(k=0;krho[p]; dec_values[p] = sum; if(dec_values[p] > 0) ++vote[i]; else ++vote[j]; p++; } int vote_max_idx = 0; for(i=1;i vote[vote_max_idx]) vote_max_idx = i; free(kvalue); free(start); free(vote); return model->label[vote_max_idx]; } } double svm_predict(const svm_model *model, const svm_node *x) { int nr_class = model->nr_class; double *dec_values; if(model->param.svm_type == ONE_CLASS || model->param.svm_type == EPSILON_SVR || model->param.svm_type == NU_SVR) dec_values = Malloc(double, 1); else dec_values = Malloc(double, nr_class*(nr_class-1)/2); double pred_result = svm_predict_values(model, x, dec_values); free(dec_values); return pred_result; } double svm_predict_probability( const svm_model *model, const svm_node *x, double *prob_estimates) { if ((model->param.svm_type == C_SVC || model->param.svm_type == NU_SVC) && model->probA!=NULL && model->probB!=NULL) { int i; int nr_class = model->nr_class; double *dec_values = Malloc(double, nr_class*(nr_class-1)/2); svm_predict_values(model, x, dec_values); double min_prob=1e-7; double **pairwise_prob=Malloc(double *,nr_class); for(i=0;iprobA[k],model->probB[k]),min_prob),1-min_prob); pairwise_prob[j][i]=1-pairwise_prob[i][j]; k++; } if (nr_class == 2) { prob_estimates[0] = pairwise_prob[0][1]; prob_estimates[1] = pairwise_prob[1][0]; } else multiclass_probability(nr_class,pairwise_prob,prob_estimates); int prob_max_idx = 0; for(i=1;i prob_estimates[prob_max_idx]) prob_max_idx = i; for(i=0;ilabel[prob_max_idx]; } else if(model->param.svm_type == ONE_CLASS && model->prob_density_marks!=NULL) { double dec_value; double pred_result = svm_predict_values(model,x,&dec_value); prob_estimates[0] = predict_one_class_probability(model,dec_value); prob_estimates[1] = 1-prob_estimates[0]; return pred_result; } else return svm_predict(model, x); } static const char *svm_type_table[] = { "c_svc","nu_svc","one_class","epsilon_svr","nu_svr",NULL }; static const char *kernel_type_table[]= { "linear","polynomial","rbf","sigmoid","precomputed",NULL }; int svm_save_model(const char *model_file_name, const svm_model *model) { FILE *fp = fopen(model_file_name,"w"); if(fp==NULL) return -1; char *old_locale = setlocale(LC_ALL, NULL); if (old_locale) { old_locale = strdup(old_locale); } setlocale(LC_ALL, "C"); const svm_parameter& param = model->param; fprintf(fp,"svm_type %s\n", svm_type_table[param.svm_type]); fprintf(fp,"kernel_type %s\n", kernel_type_table[param.kernel_type]); if(param.kernel_type == POLY) fprintf(fp,"degree %d\n", param.degree); if(param.kernel_type == POLY || param.kernel_type == RBF || param.kernel_type == SIGMOID) fprintf(fp,"gamma %.17g\n", param.gamma); if(param.kernel_type == POLY || param.kernel_type == SIGMOID) fprintf(fp,"coef0 %.17g\n", param.coef0); int nr_class = model->nr_class; int l = model->l; fprintf(fp, "nr_class %d\n", nr_class); fprintf(fp, "total_sv %d\n",l); { fprintf(fp, "rho"); for(int i=0;irho[i]); fprintf(fp, "\n"); } if(model->label) { fprintf(fp, "label"); for(int i=0;ilabel[i]); fprintf(fp, "\n"); } if(model->probA) // regression has probA only { fprintf(fp, "probA"); for(int i=0;iprobA[i]); fprintf(fp, "\n"); } if(model->probB) { fprintf(fp, "probB"); for(int i=0;iprobB[i]); fprintf(fp, "\n"); } if(model->prob_density_marks) { fprintf(fp, "prob_density_marks"); int nr_marks=10; for(int i=0;iprob_density_marks[i]); fprintf(fp, "\n"); } if(model->nSV) { fprintf(fp, "nr_sv"); for(int i=0;inSV[i]); fprintf(fp, "\n"); } fprintf(fp, "SV\n"); const double * const *sv_coef = model->sv_coef; const svm_node * const *SV = model->SV; for(int i=0;ivalue)); else while(p->index != -1) { fprintf(fp,"%d:%.8g ",p->index,p->value); p++; } fprintf(fp, "\n"); } setlocale(LC_ALL, old_locale); free(old_locale); if (ferror(fp) != 0 || fclose(fp) != 0) return -1; else return 0; } static char *line = NULL; static int max_line_len; static char* readline(FILE *input) { int len; if(fgets(line,max_line_len,input) == NULL) return NULL; while(strrchr(line,'\n') == NULL) { max_line_len *= 2; line = (char *) realloc(line,max_line_len); len = (int) strlen(line); if(fgets(line+len,max_line_len-len,input) == NULL) break; } return line; } // // FSCANF helps to handle fscanf failures. // Its do-while block avoids the ambiguity when // if (...) // FSCANF(); // is used // #define FSCANF(_stream, _format, _var) do{ if (fscanf(_stream, _format, _var) != 1) return false; }while(0) bool read_model_header(FILE *fp, svm_model* model) { svm_parameter& param = model->param; // parameters for training only won't be assigned, but arrays are assigned as NULL for safety param.nr_weight = 0; param.weight_label = NULL; param.weight = NULL; char cmd[81]; while(1) { FSCANF(fp,"%80s",cmd); if(strcmp(cmd,"svm_type")==0) { FSCANF(fp,"%80s",cmd); int i; for(i=0;svm_type_table[i];i++) { if(strcmp(svm_type_table[i],cmd)==0) { param.svm_type=i; break; } } if(svm_type_table[i] == NULL) { fprintf(stderr,"unknown svm type.\n"); return false; } } else if(strcmp(cmd,"kernel_type")==0) { FSCANF(fp,"%80s",cmd); int i; for(i=0;kernel_type_table[i];i++) { if(strcmp(kernel_type_table[i],cmd)==0) { param.kernel_type=i; break; } } if(kernel_type_table[i] == NULL) { fprintf(stderr,"unknown kernel function.\n"); return false; } } else if(strcmp(cmd,"degree")==0) FSCANF(fp,"%d",¶m.degree); else if(strcmp(cmd,"gamma")==0) FSCANF(fp,"%lf",¶m.gamma); else if(strcmp(cmd,"coef0")==0) FSCANF(fp,"%lf",¶m.coef0); else if(strcmp(cmd,"nr_class")==0) FSCANF(fp,"%d",&model->nr_class); else if(strcmp(cmd,"total_sv")==0) FSCANF(fp,"%d",&model->l); else if(strcmp(cmd,"rho")==0) { int n = model->nr_class * (model->nr_class-1)/2; model->rho = Malloc(double,n); for(int i=0;irho[i]); } else if(strcmp(cmd,"label")==0) { int n = model->nr_class; model->label = Malloc(int,n); for(int i=0;ilabel[i]); } else if(strcmp(cmd,"probA")==0) { int n = model->nr_class * (model->nr_class-1)/2; model->probA = Malloc(double,n); for(int i=0;iprobA[i]); } else if(strcmp(cmd,"probB")==0) { int n = model->nr_class * (model->nr_class-1)/2; model->probB = Malloc(double,n); for(int i=0;iprobB[i]); } else if(strcmp(cmd,"prob_density_marks")==0) { int n = 10; // nr_marks model->prob_density_marks = Malloc(double,n); for(int i=0;iprob_density_marks[i]); } else if(strcmp(cmd,"nr_sv")==0) { int n = model->nr_class; model->nSV = Malloc(int,n); for(int i=0;inSV[i]); } else if(strcmp(cmd,"SV")==0) { while(1) { int c = getc(fp); if(c==EOF || c=='\n') break; } break; } else { fprintf(stderr,"unknown text in model file: [%s]\n",cmd); return false; } } return true; } svm_model *svm_load_model(const char *model_file_name) { FILE *fp = fopen(model_file_name,"rb"); if(fp==NULL) return NULL; char *old_locale = setlocale(LC_ALL, NULL); if (old_locale) { old_locale = strdup(old_locale); } setlocale(LC_ALL, "C"); // read parameters svm_model *model = Malloc(svm_model,1); model->rho = NULL; model->probA = NULL; model->probB = NULL; model->prob_density_marks = NULL; model->sv_indices = NULL; model->label = NULL; model->nSV = NULL; // read header if (!read_model_header(fp, model)) { fprintf(stderr, "ERROR: fscanf failed to read model\n"); setlocale(LC_ALL, old_locale); free(old_locale); free(model->rho); free(model->label); free(model->nSV); free(model); return NULL; } // read sv_coef and SV int elements = 0; long pos = ftell(fp); max_line_len = 1024; line = Malloc(char,max_line_len); char *p,*endptr,*idx,*val; while(readline(fp)!=NULL) { p = strtok(line,":"); while(1) { p = strtok(NULL,":"); if(p == NULL) break; ++elements; } } elements += model->l; fseek(fp,pos,SEEK_SET); int m = model->nr_class - 1; int l = model->l; model->sv_coef = Malloc(double *,m); int i; for(i=0;isv_coef[i] = Malloc(double,l); model->SV = Malloc(svm_node*,l); svm_node *x_space = NULL; if(l>0) x_space = Malloc(svm_node,elements); int j=0; for(i=0;iSV[i] = &x_space[j]; p = strtok(line, " \t"); model->sv_coef[0][i] = strtod(p,&endptr); for(int k=1;ksv_coef[k][i] = strtod(p,&endptr); } while(1) { idx = strtok(NULL, ":"); val = strtok(NULL, " \t"); if(val == NULL) break; x_space[j].index = (int) strtol(idx,&endptr,10); x_space[j].value = strtod(val,&endptr); ++j; } x_space[j++].index = -1; } free(line); setlocale(LC_ALL, old_locale); free(old_locale); if (ferror(fp) != 0 || fclose(fp) != 0) return NULL; model->free_sv = 1; // XXX return model; } void svm_free_model_content(svm_model* model_ptr) { if(model_ptr->free_sv && model_ptr->l > 0 && model_ptr->SV != NULL) free((void *)(model_ptr->SV[0])); if(model_ptr->sv_coef) { for(int i=0;inr_class-1;i++) free(model_ptr->sv_coef[i]); } free(model_ptr->SV); model_ptr->SV = NULL; free(model_ptr->sv_coef); model_ptr->sv_coef = NULL; free(model_ptr->rho); model_ptr->rho = NULL; free(model_ptr->label); model_ptr->label = NULL; free(model_ptr->probA); model_ptr->probA = NULL; free(model_ptr->probB); model_ptr->probB = NULL; free(model_ptr->prob_density_marks); model_ptr->prob_density_marks = NULL; free(model_ptr->sv_indices); model_ptr->sv_indices = NULL; free(model_ptr->nSV); model_ptr->nSV = NULL; } void svm_free_and_destroy_model(svm_model** model_ptr_ptr) { if(model_ptr_ptr != NULL && *model_ptr_ptr != NULL) { svm_free_model_content(*model_ptr_ptr); free(*model_ptr_ptr); *model_ptr_ptr = NULL; } } void svm_destroy_param(svm_parameter* param) { free(param->weight_label); free(param->weight); } const char *svm_check_parameter(const svm_problem *prob, const svm_parameter *param) { // svm_type int svm_type = param->svm_type; if(svm_type != C_SVC && svm_type != NU_SVC && svm_type != ONE_CLASS && svm_type != EPSILON_SVR && svm_type != NU_SVR) return "unknown svm type"; // kernel_type, degree int kernel_type = param->kernel_type; if(kernel_type != LINEAR && kernel_type != POLY && kernel_type != RBF && kernel_type != SIGMOID && kernel_type != PRECOMPUTED) return "unknown kernel type"; if((kernel_type == POLY || kernel_type == RBF || kernel_type == SIGMOID) && param->gamma < 0) return "gamma < 0"; if(kernel_type == POLY && param->degree < 0) return "degree of polynomial kernel < 0"; // cache_size,eps,C,nu,p,shrinking if(param->cache_size <= 0) return "cache_size <= 0"; if(param->eps <= 0) return "eps <= 0"; if(svm_type == C_SVC || svm_type == EPSILON_SVR || svm_type == NU_SVR) if(param->C <= 0) return "C <= 0"; if(svm_type == NU_SVC || svm_type == ONE_CLASS || svm_type == NU_SVR) if(param->nu <= 0 || param->nu > 1) return "nu <= 0 or nu > 1"; if(svm_type == EPSILON_SVR) if(param->p < 0) return "p < 0"; if(param->shrinking != 0 && param->shrinking != 1) return "shrinking != 0 and shrinking != 1"; if(param->probability != 0 && param->probability != 1) return "probability != 0 and probability != 1"; // check whether nu-svc is feasible if(svm_type == NU_SVC) { int l = prob->l; int max_nr_class = 16; int nr_class = 0; int *label = Malloc(int,max_nr_class); int *count = Malloc(int,max_nr_class); int i; for(i=0;iy[i]; int j; for(j=0;jnu*(n1+n2)/2 > min(n1,n2)) { free(label); free(count); return "specified nu is infeasible"; } } } free(label); free(count); } return NULL; } int svm_check_probability_model(const svm_model *model) { return ((model->param.svm_type == C_SVC || model->param.svm_type == NU_SVC) && model->probA!=NULL && model->probB!=NULL) || (model->param.svm_type == ONE_CLASS && model->prob_density_marks!=NULL) || ((model->param.svm_type == EPSILON_SVR || model->param.svm_type == NU_SVR) && model->probA!=NULL); } void svm_set_print_string_function(void (*print_func)(const char *)) { if(print_func == NULL) svm_print_string = &print_string_stdout; else svm_print_string = print_func; } statistics-release-1.9.2/src/svm.h000066400000000000000000000103571524624707500171220ustar00rootroot00000000000000/* Copyright (C) 2025 Chih-Chung Chang and Chih-Jen Lin This file is part of the statistics package for GNU Octave. Permission granted by Chih-Jen Lin to the package maintainer to include this file and double license under GPLv3 by means of personal communication. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #ifndef _LIBSVM_H #define _LIBSVM_H #define LIBSVM_VERSION 336 #ifdef __cplusplus extern "C" { #endif extern int libsvm_version; struct svm_node { int index; double value; }; struct svm_problem { int l; double *y; struct svm_node **x; }; enum { C_SVC, NU_SVC, ONE_CLASS, EPSILON_SVR, NU_SVR }; /* svm_type */ enum { LINEAR, POLY, RBF, SIGMOID, PRECOMPUTED }; /* kernel_type */ struct svm_parameter { int svm_type; int kernel_type; int degree; /* for poly */ double gamma; /* for poly/rbf/sigmoid */ double coef0; /* for poly/sigmoid */ /* these are for training only */ double cache_size; /* in MB */ double eps; /* stopping criteria */ double C; /* for C_SVC, EPSILON_SVR and NU_SVR */ int nr_weight; /* for C_SVC */ int *weight_label; /* for C_SVC */ double* weight; /* for C_SVC */ double nu; /* for NU_SVC, ONE_CLASS, and NU_SVR */ double p; /* for EPSILON_SVR */ int shrinking; /* use the shrinking heuristics */ int probability; /* do probability estimates */ }; // // svm_model // struct svm_model { struct svm_parameter param; /* parameter */ int nr_class; /* number of classes, = 2 in regression/one class svm */ int l; /* total #SV */ struct svm_node **SV; /* SVs (SV[l]) */ double **sv_coef; /* coefficients for SVs in decision functions (sv_coef[k-1][l]) */ double *rho; /* constants in decision functions (rho[k*(k-1)/2]) */ double *probA; /* pairwise probability information */ double *probB; double *prob_density_marks; /* probability information for ONE_CLASS */ int *sv_indices; /* sv_indices[0,...,nSV-1] are values in [1,...,num_traning_data] to indicate SVs in the training set */ /* for classification only */ int *label; /* label of each class (label[k]) */ int *nSV; /* number of SVs for each class (nSV[k]) */ /* nSV[0] + nSV[1] + ... + nSV[k-1] = l */ /* XXX */ int free_sv; /* 1 if svm_model is created by svm_load_model*/ /* 0 if svm_model is created by svm_train */ }; struct svm_model *svm_train(const struct svm_problem *prob, const struct svm_parameter *param); void svm_cross_validation(const struct svm_problem *prob, const struct svm_parameter *param, int nr_fold, double *target); int svm_save_model(const char *model_file_name, const struct svm_model *model); struct svm_model *svm_load_model(const char *model_file_name); int svm_get_svm_type(const struct svm_model *model); int svm_get_nr_class(const struct svm_model *model); void svm_get_labels(const struct svm_model *model, int *label); void svm_get_sv_indices(const struct svm_model *model, int *sv_indices); int svm_get_nr_sv(const struct svm_model *model); double svm_get_svr_probability(const struct svm_model *model); double svm_predict_values(const struct svm_model *model, const struct svm_node *x, double* dec_values); double svm_predict(const struct svm_model *model, const struct svm_node *x); double svm_predict_probability(const struct svm_model *model, const struct svm_node *x, double* prob_estimates); void svm_free_model_content(struct svm_model *model_ptr); void svm_free_and_destroy_model(struct svm_model **model_ptr_ptr); void svm_destroy_param(struct svm_parameter *param); const char *svm_check_parameter(const struct svm_problem *prob, const struct svm_parameter *param); int svm_check_probability_model(const struct svm_model *model); void svm_set_print_string_function(void (*print_func)(const char *)); #ifdef __cplusplus } #endif #endif /* _LIBSVM_H */ statistics-release-1.9.2/src/svm_model_octave.cc000066400000000000000000000233041524624707500217750ustar00rootroot00000000000000/* Copyright (C) 2022 Andreas Bertsatos Copyright (C) 2025 Avanish Salunke Based on the Octave LIBSVM wrapper created by Alan Meeson (2014) based on an earlier version of the LIBSVM (3.18) library for MATLAB. Current implementation is based on LIBSVM 3.36 (2025) by Chih-Chung Chang and Chih-Jen Lin. This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include "svm.h" #define NUM_OF_RETURN_FIELD 12 #define Malloc(type,n) (type *)malloc((n)*sizeof(type)) static const char *field_names[] = { "Parameters", "nr_class", "totalSV", "rho", "Label", "sv_indices", "ProbA", "ProbB", "nSV", "sv_coef", "SVs", "ProbDensityMarks" }; const char *model_to_octave_structure(octave_value_list &plhs, int num_of_feature, struct svm_model *model) { int i, j, n; double *ptr; octave_scalar_map osm_model; // Parameters ColumnVector cm_parameters (5); cm_parameters(0) = model->param.svm_type; cm_parameters(1) = model->param.kernel_type; cm_parameters(2) = model->param.degree; cm_parameters(3) = model->param.gamma; cm_parameters(4) = model->param.coef0; osm_model.assign("Parameters", cm_parameters); // nr_class osm_model.assign("nr_class", octave_value(model->nr_class)); // total SV osm_model.assign("totalSV", octave_value(model->l)); // rho n = model->nr_class*(model->nr_class-1)/2; ColumnVector cm_rho(n); for(i = 0; i < n; i++) { cm_rho(i) = model->rho[i]; } osm_model.assign("rho", cm_rho); // Label if(model->label) { ColumnVector cm_label(model->nr_class); for(i = 0; i < model->nr_class; i++) { cm_label(i) = model->label[i]; } osm_model.assign("Label", cm_label); } else { osm_model.assign("Label", ColumnVector(0)); } // sv_indices if(model->sv_indices) { ColumnVector cm_sv_indices(model->l); for(i = 0; i < model->l; i++) { cm_sv_indices(i) = model->sv_indices[i]; } osm_model.assign("sv_indices", cm_sv_indices); } else { osm_model.assign("sv_indices", ColumnVector(0)); } // probA if(model->probA != NULL) { ColumnVector cm_proba(n); for(i = 0; i < n; i++) { cm_proba(i) = model->probA[i]; } osm_model.assign("ProbA", cm_proba); } else { osm_model.assign("ProbA", ColumnVector(0)); } // probB if(model->probB != NULL) { ColumnVector cm_probb(n); for(i = 0; i < n; i++) { cm_probb(i) = model->probB[i]; } osm_model.assign("ProbB", cm_probb); } else { osm_model.assign("ProbB", ColumnVector(0)); } // nSV if(model->nSV) { ColumnVector cm_nsv(model->nr_class); for(i = 0; i < model->nr_class; i++) { cm_nsv(i) = model->nSV[i]; } osm_model.assign("nSV", cm_nsv); } else { osm_model.assign("nSV", ColumnVector(0)); } // sv_coef Matrix m_sv_coef(model->l, model->nr_class-1); for (i = 0; i < model->nr_class-1; i++) { for(j = 0; j < model->l; j++) { m_sv_coef(j,i) = model->sv_coef[i][j]; } } osm_model.assign("sv_coef", m_sv_coef); // SVs { int ir_index, nonzero_element; octave_idx_type *ir, *jc; //mxArray *pprhs[1], *pplhs[1]; if(model->param.kernel_type == PRECOMPUTED) { nonzero_element = model->l; num_of_feature = 1; } else { nonzero_element = 0; for(i = 0; i < model->l; i++) { j = 0; while(model->SV[i][j].index != -1) { nonzero_element++; j++; } } } // SV in column, easier accessing SparseMatrix sm_rhs = SparseMatrix((octave_idx_type)num_of_feature, (octave_idx_type)model->l, (octave_idx_type)nonzero_element); ir = sm_rhs.ridx(); jc = sm_rhs.cidx(); ptr = (double*) sm_rhs.data(); jc[0] = ir_index = 0; for(i = 0; i < model->l; i++) { if(model->param.kernel_type == PRECOMPUTED) { // make a (1 x model->l) matrix ir[ir_index] = 0; ptr[ir_index] = model->SV[i][0].value; ir_index++; jc[i+1] = jc[i] + 1; } else { int x_index = 0; while (model->SV[i][x_index].index != -1) { ir[ir_index] = model->SV[i][x_index].index - 1; ptr[ir_index] = model->SV[i][x_index].value; ir_index++, x_index++; } jc[i+1] = jc[i] + x_index; } } // transpose back to SV in row sm_rhs = sm_rhs.transpose(); osm_model.assign("SVs", sm_rhs); } // changes from libsvm 3.36 if(model->prob_density_marks) { int nr_marks = 10; Matrix m_marks(nr_marks, 1); for(int i = 0; i < nr_marks; i++) { m_marks(i) = model->prob_density_marks[i]; } osm_model.setfield("ProbDensityMarks", m_marks); } else { osm_model.setfield("ProbDensityMarks", Matrix(0, 0)); } /* return */ plhs(0) = osm_model; return NULL; } struct svm_model *octave_matrix_to_model(octave_scalar_map &octave_model, const char **msg) { int i, j, n, num_of_fields; double *ptr; int id = 0; struct svm_node *x_space; struct svm_model *model; model = Malloc(struct svm_model, 1); model->rho = NULL; model->probA = NULL; model->probB = NULL; model->label = NULL; model->prob_density_marks = NULL; model->sv_indices = NULL; model->nSV = NULL; model->free_sv = 1; // XXX //Parameters ColumnVector cm_parameters = octave_model.getfield("Parameters").column_vector_value(); model->param.svm_type = (int)cm_parameters(0); model->param.kernel_type = (int)cm_parameters(1); model->param.degree = (int)cm_parameters(2); model->param.gamma = cm_parameters(3); model->param.coef0 = cm_parameters(4); //nr_class model->nr_class = (int)octave_model.getfield("nr_class").int_value(); //total SV model->l = (int)octave_model.getfield("totalSV").int_value(); //rho n = model->nr_class * (model->nr_class-1)/2; model->rho = (double*) malloc(n*sizeof(double)); ColumnVector cm_rho = octave_model.getfield("rho").column_vector_value(); for(i = 0; i < n; i++) { model->rho[i] = cm_rho(i); } //label if (!octave_model.getfield("Label").isempty()) { model->label = (int*) malloc(model->nr_class*sizeof(int)); ColumnVector cm_label = octave_model.getfield("Label").column_vector_value(); for(i = 0; i < model->nr_class; i++) { model->label[i] = (int)cm_label(i); } } //sv_indices if (!octave_model.getfield("sv_indices").isempty()) { model->sv_indices = (int*) malloc(model->l*sizeof(int)); ColumnVector cv_svi = octave_model.getfield("sv_indices").column_vector_value(); for(i = 0; i < model->l; i++) { model->sv_indices[i] = (int)cv_svi(i); } } // probA if(!octave_model.getfield("ProbA").isempty()) { model->probA = (double*) malloc(n*sizeof(double)); ColumnVector cv_proba = octave_model.getfield("ProbA").column_vector_value(); for(i = 0; i < n; i++) { model->probA[i] = cv_proba(i); } } // probB if(!octave_model.getfield("ProbB").isempty()) { model->probB = (double*) malloc(n*sizeof(double)); ColumnVector cv_probb = octave_model.getfield("ProbB").column_vector_value(); for(i = 0; i < n; i++) { model->probB[i] = cv_probb(i); } } // nSV if(!octave_model.getfield("nSV").isempty()) { model->nSV = (int*) malloc(model->nr_class*sizeof(int)); ColumnVector cv_nsv = octave_model.getfield("nSV").column_vector_value(); for(i = 0; i < model->nr_class; i++) { model->nSV[i] = (int)cv_nsv(i); } } // sv_coef Matrix m_sv_coef = octave_model.getfield("sv_coef").matrix_value(); ptr = (double*) m_sv_coef.data(); model->sv_coef = (double**) malloc((model->nr_class-1)*sizeof(double)); for(i = 0; i < model->nr_class - 1; i++ ) { model->sv_coef[i] = (double*) malloc((model->l)*sizeof(double)); } for(i = 0; i < model->nr_class - 1; i++) { for(j = 0; j < model->l; j++) { model->sv_coef[i][j] = ptr[i*(model->l)+j];//m_sv_coef(i,j); } } // SV { int sr, sc, elements; int num_samples; octave_idx_type *ir, *jc; // transpose SV SparseMatrix sm_sv = octave_model.getfield("SVs").sparse_matrix_value(); sm_sv = sm_sv.transpose(); sr = (int)sm_sv.cols(); sc = (int)sm_sv.rows(); ptr = (double*)sm_sv.data(); ir = sm_sv.ridx(); jc = sm_sv.cidx(); num_samples = (int)sm_sv.nzmax(); elements = num_samples + sr; model->SV = (struct svm_node **) malloc(sr * sizeof(struct svm_node *)); x_space = (struct svm_node *)malloc(elements * sizeof(struct svm_node)); // SV is in column for(i = 0; i < sr; i++) { int low = (int)jc[i], high = (int)jc[i+1]; int x_index = 0; model->SV[i] = &x_space[low+i]; for(j = low; j < high; j++) { model->SV[i][x_index].index = (int)ir[j] + 1; model->SV[i][x_index].value = ptr[j]; x_index++; } model->SV[i][x_index].index = -1; } id++; } // changes from libsvm 3.36 if (octave_model.isfield("ProbDensityMarks")) { Matrix m_marks = octave_model.getfield("ProbDensityMarks").matrix_value(); if (m_marks.numel() > 0) { int nr_marks = 10; model->prob_density_marks = (double*) malloc(nr_marks * sizeof(double)); for(int i = 0; i < nr_marks; i++) { model->prob_density_marks[i] = m_marks(i); } } else { model->prob_density_marks = NULL; } } else { model->prob_density_marks = NULL; } return model; } statistics-release-1.9.2/src/svm_model_octave.h000066400000000000000000000025031524624707500216350ustar00rootroot00000000000000/* Copyright (C) 2022 Andreas Bertsatos Copyright (C) 2025 Avanish Salunke Based on the Octave LIBSVM wrapper created by Alan Meeson (2014) based on an earlier version of the LIBSVM (3.18) library for MATLAB. Current implementation is based on LIBSVM 3.36 (2025) by Chih-Chung Chang and Chih-Jen Lin. This file is part of the statistics package for GNU Octave. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include "svm.h" const char *model_to_octave_structure(octave_value_list &plhs, int num_of_feature, struct svm_model *model); struct svm_model *octave_matrix_to_model(octave_scalar_map &octave_struct, const char **error_message); statistics-release-1.9.2/src/svmpredict.cc000066400000000000000000000412031524624707500206250ustar00rootroot00000000000000/* Copyright (C) 2022 Andreas Bertsatos Copyright (C) 2025 Avanish Salunke Based on the Octave LIBSVM wrapper adapted by Alan Meeson (2014) based on an earlier version of the LIBSVM (3.18) library for MATLAB. Current implementation is based on LIBSVM 3.36 (2025) by Chih-Chung Chang and Chih-Jen Lin. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include "svm.h" #include "svm_model_octave.h" #define CMD_LEN 2048 int print_null(const char *s,...) {return 0;} int (*info)(const char *fmt,...) = &printf; void read_sparse_instance(const SparseMatrix &args, int index, struct svm_node *x) { int i, j, low, high; octave_idx_type *ir, *jc; double *samples; ir = (octave_idx_type*)args.ridx(); jc = (octave_idx_type*)args.cidx(); samples = (double*)args.data(); // each column is one instance j = 0; low = (int)jc[index], high = (int)jc[index+1]; for(i=low;iparam.kernel_type == PRECOMPUTED) { // precomputed kernel requires dense matrix, so we make one t_data = args(1).matrix_value(); } else { //If it's a sparse matrix with a non PRECOMPUTED kernel, transpose it pplhs = args(1).sparse_matrix_value().transpose(); } } else { t_data = args(1).matrix_value(); } ptr_instance = (double*)t_data.data(); if(predict_probability) { if(svm_type==NU_SVR || svm_type==EPSILON_SVR) { info("Prob. model for test data: target value = predicted value + z,\nz: Laplace distribution e^(-|z|/sigma)/(2sigma),sigma=%g\n", svm_get_svr_probability(model)); } else { prob_estimates = (double*) malloc(nr_class*sizeof(double)); } } ColumnVector cv_predictions(testing_instance_number); Matrix m_pe; if(predict_probability) { // prob estimates are in plhs[2] if(svm_type == C_SVC || svm_type == NU_SVC || svm_type == ONE_CLASS) { m_pe = Matrix(testing_instance_number, nr_class); } else { m_pe = Matrix(0,0); } } else { // decision values are in plhs[2] if(svm_type == ONE_CLASS || svm_type == EPSILON_SVR || svm_type == NU_SVR || nr_class == 1) { m_pe = Matrix(testing_instance_number, 1); } else { m_pe = Matrix(testing_instance_number, nr_class*(nr_class-1)/2); } } // Write through the local arrays, not through a pointer taken from an // octave_value. tplhs(N).column_vector_value() and .matrix_value() return // by value, and Octave stores a 1x1 array as a scalar, so for a single // testing instance they have to materialise a fresh array instead of // sharing one. data() then pointed into a temporary that was freed before // the loop below wrote to it, corrupting the heap. The results are moved // into tplhs once the loop is done. ptr_predict_label = cv_predictions.fortran_vec(); ptr_prob_estimates = m_pe.fortran_vec(); ptr_dec_values = m_pe.fortran_vec(); x = (struct svm_node*)malloc((feature_number+1)*sizeof(struct svm_node)); for(instance_index=0;instance_indexparam.kernel_type != PRECOMPUTED) { read_sparse_instance(pplhs, instance_index, x); } else { for(i = 0; i < feature_number; i++) { x[i].index = i+1; x[i].value = ptr_instance[testing_instance_number*i+instance_index]; } x[feature_number].index = -1; } if(predict_probability) { if(svm_type == C_SVC || svm_type == NU_SVC || svm_type == ONE_CLASS) { predict_label = svm_predict_probability(model, x, prob_estimates); ptr_predict_label[instance_index] = predict_label; for(i = 0; i < nr_class; i++) { ptr_prob_estimates[instance_index + i * testing_instance_number] = prob_estimates[i]; } } else { predict_label = svm_predict(model,x); ptr_predict_label[instance_index] = predict_label; } } else { if(svm_type == ONE_CLASS || svm_type == EPSILON_SVR || svm_type == NU_SVR) { double res; predict_label = svm_predict_values(model, x, &res); ptr_dec_values[instance_index] = res; } else { double *dec_values = (double *) malloc(sizeof(double) * nr_class*(nr_class-1)/2); predict_label = svm_predict_values(model, x, dec_values); if(nr_class == 1) { ptr_dec_values[instance_index] = 1; } else { for(i = 0; i < (nr_class * (nr_class - 1)) / 2; i++) { ptr_dec_values[instance_index + i * testing_instance_number] = dec_values[i]; } } free(dec_values); } ptr_predict_label[instance_index] = predict_label; } if(predict_label == target_label) { ++correct; } error += (predict_label-target_label)*(predict_label-target_label); sump += predict_label; sumt += target_label; sumpp += predict_label*predict_label; sumtt += target_label*target_label; sumpt += predict_label*target_label; ++total; } tplhs(0) = cv_predictions; tplhs(2) = m_pe; if(svm_type==NU_SVR || svm_type==EPSILON_SVR) { info("Mean squared error = %g (regression)\n",error/total); info("Squared correlation coefficient = %g (regression)\n", ((total*sumpt-sump*sumt)*(total*sumpt-sump*sumt))/ ((total*sumpp-sump*sump)*(total*sumtt-sumt*sumt))); } else { info("Accuracy = %g%% (%d/%d) (classification)\n", (double)correct/total*100,correct,total); } // return accuracy, mean squared error, squared correlation coefficient ColumnVector cv_acc(3); ptr = cv_acc.fortran_vec(); ptr[0] = (double)correct/total*100; ptr[1] = error/total; ptr[2] = ((total*sumpt-sump*sumt)*(total*sumpt-sump*sumt))/ ((total*sumpp-sump*sump)*(total*sumtt-sumt*sumt)); tplhs(1) = cv_acc; free(x); if(prob_estimates != NULL) { free(prob_estimates); } switch(nlhs) { case 3: plhs(2) = tplhs(2); plhs(1) = tplhs(1); case 1: case 0: plhs(0) = tplhs(0); } } DEFUN_DLD (svmpredict, args, nargout, "-*- texinfo -*- \n\n\ @deftypefn {statistics} {@var{predicted_label} =} svmpredict (@var{labels}, @var{data}, @var{model})\n\ @deftypefnx {statistics} {@var{predicted_label} =} svmpredict (@var{labels}, @var{data}, @var{model}, \"libsvm_options\")\n\ @deftypefnx {statistics} {[@var{predicted_label}, @var{accuracy}, @var{decision_values}] =} svmpredict (@var{labels}, @var{data}, @var{model}, \"libsvm_options\")\n\ @deftypefnx {statistics} {[@var{predicted_label}, @var{accuracy}, @var{prob_estimates}] =} svmpredict (@var{labels}, @var{data}, @var{model}, \"libsvm_options\")\n\ \n\ \n\ This function predicts new labels from a testing instance matrix based on an \ SVM @var{model} created with @code{svmtrain}. \ \n\ \n\ @itemize \n\ @item @var{labels} : An m by 1 vector of prediction labels. If labels \ of test data are unknown, simply use any random values. (type must be double) \ \n\ \n\ @item @var{data} : An m by n matrix of m testing instances with n features. \ It can be dense or sparse. (type must be double) \ \n\ \n\ @item @var{model} : The output of @code{svmtrain} function. \ \n\ \n\ @item @code{libsvm_options} : A string of testing options in the same format \ as that of LIBSVM. \ \n\ \n\ @end itemize \ \n\ \n\ @code{libsvm_options} :\n\ \n\ @itemize \n\ @item @code{-b} : probability_estimates; whether to predict probability \ estimates.\n\ \n\ @end itemize \n\ @multitable @columnfractions 0.1 0.1 0.8 \n\ @item @tab 0 @tab return decision values. (default) \n\ \n\ @item @tab 1 @tab return probability estimates. \n\ \n\ @end multitable \n\ \n\ \n\ @itemize \n\ @item @code{-q} : quiet mode. (no outputs) \n\ \n\ @end itemize \n\ \n\ \n\ The @code{svmpredict} function has three outputs. The first one, \ @var{predicted_label}, is a vector of predicted labels. The second output, \ @var{accuracy}, is a vector including accuracy (for classification), mean \ squared error, and squared correlation coefficient (for regression). The \ third is a matrix containing decision values or probability estimates \ (if @code{-b 1}' is specified). If @math{k} is the number of classes in \ training data, for decision values, each row includes results of predicting \ @math{k(k-1)/2} binary-class SVMs. For classification, @math{k = 1} is a \ special case. Decision value +1 is returned for each testing instance, \ instead of an empty vector. For probabilities, each row contains @math{k} \ values indicating the probability that the testing instance is in each class. \ Note that the order of classes here is the same as @code{Label} field in the \ @var{model} structure. \ \n\ \n\ \n\ @emph{Note on LIBSVM 3.36 Update}: This implementation is based on LIBSVM 3.36 \ (2025) and now supports probability estimates for One-Class SVM (@code{-s 2}) \ when combined with the probability flag (@code{-b 1}). For One-Class SVM, \ the @var{prob_estimates} output is a single column vector containing the \ probability of the instance being an inlier. \n\ \n\ @end deftypefn") { int nlhs = nargout; int nrhs = args.length(); octave_value_list plhs(nlhs); int prob_estimate_flag = 0; struct svm_model *model; info = &print_null; if(nlhs == 2 || nlhs > 3) { error ("svmpredict: wrong number of output arguments."); } if(nrhs > 4 || nrhs < 3) { error ("svmpredict: wrong number of input arguments."); } if(!args(0).is_double_type() || !args(1).is_double_type()) { error ("svmpredict: label vector and instance matrix must be double."); } if(args(2).isstruct()) { const char *error_msg; // parse options if(nrhs==4) { int i, argc = 1; char cmd[CMD_LEN], *argv[CMD_LEN/2]; // put options in argv[] strncpy(cmd, args(3).string_value().c_str(), CMD_LEN); if((argv[argc] = strtok(cmd, " ")) != NULL) { while((argv[++argc] = strtok(NULL, " ")) != NULL); } for(i=1;i=argc) && argv[i-1][1] != 'q') { fake_answer(nlhs, plhs); return plhs; } switch(argv[i-1][1]) { case 'b': prob_estimate_flag = atoi(argv[i]); break; case 'q': i--; info = &print_null; break; default: printf("svmpredict: unknown option: -%c\n", argv[i-1][1]); fake_answer(nlhs, plhs); return plhs; } } } octave_scalar_map osm_model = args(2).scalar_map_value(); model = octave_matrix_to_model(osm_model, &error_msg); if (model == NULL) { printf("svmpredict: can't read model: %s\n", error_msg); fake_answer(nlhs, plhs); return plhs; } if(prob_estimate_flag) { // Check if the SVM type supports probability, new support for ONE_CLASS if (model->param.svm_type != C_SVC && model->param.svm_type != NU_SVC && model->param.svm_type != ONE_CLASS) { svm_free_and_destroy_model(&model); error ("svmpredict: probability estimates are not supported for this SVM type (only C-SVC, NU-SVC, and ONE-CLASS).\n"); } // Check if the model itself was trained with probability info (-b 1) if(svm_check_probability_model(model)==0) { svm_free_and_destroy_model(&model); error ("svmpredict: model does not support probability estimates. Train with '-b 1'.\n"); } } else { if(svm_check_probability_model(model)!=0) info("Model supports probability estimates, but disabled in prediction.\n"); } predict(nlhs, plhs, args, model, prob_estimate_flag); // destroy model svm_free_and_destroy_model(&model); } else { error ("svmpredict: model should be a struct array."); } return plhs; } /* %!test %! # Test 1: Standard C-SVC Prediction (Original Regression Test) %! [L, D] = libsvmread (file_in_loadpath ("heart_scale.dat")); %! model = svmtrain (L, D, '-c 1 -g 0.07'); %! [predict_label, accuracy, dec_values] = svmpredict (L, D, model); %! assert_equal (size (predict_label), size (dec_values)); %! assert_equal (accuracy, [86.666, 0.533, 0.533]', [1e-3, 1e-3, 1e-3]'); %! assert_equal (dec_values(1), 1.225836001973273, 1e-14); %! assert_equal (dec_values(2), -0.3212992933043805, 1e-14); %! assert_equal (predict_label(1), 1); %! %!test %! # A single testing instance used to write through a freed pointer, since %! # Octave stores a 1x1 result as a scalar and matrix_value() then returns a %! # temporary. Every single-row query must match the batch answer. %! [L, D] = libsvmread (file_in_loadpath ("heart_scale.dat")); %! model = svmtrain (L, D, '-c 1 -g 0.07'); %! [bl, ~, bd] = svmpredict (L, D, model); %! for i = [1, 2, 7, 130, numel(L)] %! [l, ~, d] = svmpredict (L(i), D(i,:), model); %! assert_equal (size (l), [1, 1]); %! assert_equal (l, bl(i)); %! assert_equal (d, bd(i), 1e-12); %! endfor %! %!test %! # Test 2: One-Class Probability (NEW LIBSVM 3.36 FEATURE) %! [L, D] = libsvmread (file_in_loadpath ("heart_scale.dat")); %! # Train One-Class (-s 2) with Probability (-b 1) %! model_oc = svmtrain (L, D, '-s 2 -n 0.1 -g 0.07 -b 1'); %! assert_equal (isstruct (model_oc), true); %! # Predict with Probability (-b 1) %! [pred, acc, probs] = svmpredict (L, D, model_oc, '-b 1'); %! %! # Detail Check A: Output must be N x 2 (Column 1: Normal, Column 2: Outlier) %! assert_equal (size (probs), [length(L), 2]); %! %! # Detail Check B: Probabilities must sum to 1.0 for every instance %! assert_equal (sum (probs, 2), ones (length(L), 1), 1e-5); %! %! # Detail Check C: Values must be valid probabilities [0, 1] %! assert_equal (all (probs >= 0 & probs <= 1, 'all'), true); %! clear model_oc %! %!test %! # Test 3: One-Class Decision Values (Standard Check) %! # Verifies that the upgrade didn't break standard One-Class prediction (-b 0) %! [L, D] = libsvmread (file_in_loadpath ("heart_scale.dat")); %! model_oc = svmtrain (L, D, '-s 2 -n 0.1 -g 0.07'); %! [pred, acc, dec] = svmpredict (L, D, model_oc); %! # Standard One-Class output is N x 1 (Scalar decision values) %! assert_equal (size (dec), [length(L), 1]); %! clear model_oc %! %!shared L, D, model %! # Test 4: Error Handling (Original Checks) %! [L, D] = libsvmread (file_in_loadpath ("heart_scale.dat")); %! model = svmtrain (L, D, '-c 1 -g 0.07'); %! %!error ... %! [p, a] = svmpredict (L, D, model); %!error p = svmpredict (L, D); %!error ... %! p = svmpredict (single (L), D, model); %!error p = svmpredict (L, D, 123); */ statistics-release-1.9.2/src/svmtrain.cc000066400000000000000000000436761524624707500203300ustar00rootroot00000000000000/* Copyright (C) 2025 Andreas Bertsatos Copyright (C) 2025 Avanish Salunke Based on the Octave LIBSVM wrapper adapted by Alan Meeson (2014) based on an earlier version of the LIBSVM (3.18) library for MATLAB. Current implementation is based on LIBSVM 3.36 (2025) by Chih-Chung Chang and Chih-Jen Lin. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include "svm.h" #include "svm_model_octave.h" #define CMD_LEN 2048 #define Malloc(type,n) (type *)malloc((n)*sizeof(type)) void print_null(const char *s) {} //void print_string_octave(const char *s) {&printf;} int print_null_(const char *s,...) {return 0;} int (*info_)(const char *fmt,...) = &printf; // svm arguments struct svm_parameter param; // set by parse_command_line struct svm_problem prob; // set by read_problem struct svm_model *model; struct svm_node *x_space; int cross_validation; int nr_fold; double do_cross_validation() { int i; int total_correct = 0; double total_error = 0; double sumv = 0, sumy = 0, sumvv = 0, sumyy = 0, sumvy = 0; double *target = Malloc(double,prob.l); double retval = 0.0; svm_cross_validation(&prob,¶m,nr_fold,target); if(param.svm_type == EPSILON_SVR || param.svm_type == NU_SVR) { for(i=0;i 2) { // put options in argv[] strncpy(cmd, args(2).string_value().c_str(), CMD_LEN); //mxGetString(args[2], cmd, mxGetN(args[2]) + 1); if((argv[argc] = strtok(cmd, " ")) != NULL) while((argv[++argc] = strtok(NULL, " ")) != NULL) ; } // parse options for(i=1;i=argc && argv[i-1][1] != 'q') // since option -q has no parameter return 1; switch(argv[i-1][1]) { case 's': param.svm_type = atoi(argv[i]); break; case 't': param.kernel_type = atoi(argv[i]); break; case 'd': param.degree = atoi(argv[i]); break; case 'g': param.gamma = atof(argv[i]); break; case 'r': param.coef0 = atof(argv[i]); break; case 'n': param.nu = atof(argv[i]); break; case 'm': param.cache_size = atof(argv[i]); break; case 'c': param.C = atof(argv[i]); break; case 'e': param.eps = atof(argv[i]); break; case 'p': param.p = atof(argv[i]); break; case 'h': param.shrinking = atoi(argv[i]); break; case 'b': param.probability = atoi(argv[i]); break; case 'q': print_func = &print_null; info_ = &print_null_; i--; break; case 'v': cross_validation = 1; nr_fold = atoi(argv[i]); if(nr_fold < 2) { printf("n-fold cross validation: n must >= 2\n"); return 1; } break; case 'w': ++param.nr_weight; param.weight_label = (int *)realloc(param.weight_label,sizeof(int)*param.nr_weight); param.weight = (double *)realloc(param.weight,sizeof(double)*param.nr_weight); param.weight_label[param.nr_weight-1] = atoi(&argv[i-1][2]); param.weight[param.nr_weight-1] = atof(argv[i]); break; default: printf("svmtrain: unknown option -%c\n", argv[i-1][1]); return 1; } } svm_set_print_string_function(print_func); return 0; } // read in a problem (in svmlight format) int read_problem_dense(ColumnVector &label_vec, Matrix &instance_mat) { int i, j, k; int elements, max_index, sc, label_vector_row_num; double *samples, *labels; prob.x = NULL; prob.y = NULL; x_space = NULL; labels = (double*)label_vec.data();//mxGetPr(label_vec); samples = (double*)instance_mat.data(); sc = (int)instance_mat.cols(); elements = 0; // the number of instance prob.l = (int)instance_mat.rows(); label_vector_row_num = (int)label_vec.rows(); if(label_vector_row_num!=prob.l) { //must throw an error to pass the BIST test error("svmtrain: label vector must have same number of elements as rows in instance matrix."); return -1; } if(param.kernel_type == PRECOMPUTED) elements = prob.l * (sc + 1); else { for(i = 0; i < prob.l; i++) { for(k = 0; k < sc; k++) if(samples[k * prob.l + i] != 0) elements++; // count the '-1' element elements++; } } prob.y = Malloc(double,prob.l); prob.x = Malloc(struct svm_node *,prob.l); x_space = Malloc(struct svm_node, elements); max_index = sc; j = 0; for(i = 0; i < prob.l; i++) { prob.x[i] = &x_space[j]; prob.y[i] = labels[i]; for(k = 0; k < sc; k++) { if(param.kernel_type == PRECOMPUTED || samples[k * prob.l + i] != 0) { x_space[j].index = k + 1; x_space[j].value = samples[k * prob.l + i]; j++; } } x_space[j++].index = -1; } if(param.gamma == 0 && max_index > 0) param.gamma = 1.0/max_index; if(param.kernel_type == PRECOMPUTED) for(i=0;i max_index) { printf("svmtrain: wrong input format: sample_serial_number out of range\n"); return -1; } } return 0; } int read_problem_sparse(ColumnVector &label_vec, SparseMatrix &instance_mat) { int i, j, k, low, high; octave_idx_type *ir, *jc; int elements, max_index, num_samples, label_vector_row_num; double *samples, *labels; // transposed instance sparse matrix SparseMatrix instance_mat_col = instance_mat.transpose(); prob.x = NULL; prob.y = NULL; x_space = NULL; // each column is one instance labels = (double*)label_vec.data(); samples = (double*)instance_mat_col.data(); ir = (octave_idx_type*)instance_mat_col.ridx(); jc = (octave_idx_type*)instance_mat_col.cidx(); num_samples = (int)instance_mat_col.nzmax(); // the number of instance prob.l = (int)instance_mat_col.cols(); label_vector_row_num = (int)label_vec.rows(); if(label_vector_row_num!=prob.l) { // must throw an error to pass the BIST test error("svmtrain: label vector must have same number of elements as rows in instance matrix."); return -1; } elements = num_samples + prob.l; max_index = (int)instance_mat_col.rows(); prob.y = Malloc(double,prob.l); prob.x = Malloc(struct svm_node *,prob.l); x_space = Malloc(struct svm_node, elements); j = 0; for(i=0;i 0) { param.gamma = 1.0/max_index; } return 0; } static void fake_answer(int nlhs, octave_value_list &plhs) { int i; for(i=0;i 1) { error ("svmtrain: wrong number of output arguments."); } // Transform the input Matrix to libsvm format if(nrhs > 1 && nrhs < 4) { int err; if(!args(0).is_double_type() || !args(1).is_double_type()) { error ("svmtrain: label vector and instance matrix must be double."); } if(parse_command_line(nrhs, args, NULL)) { svm_destroy_param(¶m); error ("svmtrain: wrong values in parameter string."); } if(args(1).issparse()) { if(param.kernel_type == PRECOMPUTED) { // precomputed kernel requires dense matrix, so we make one ColumnVector cv_lab = args(0).column_vector_value(); Matrix m_dat = args(1).matrix_value(); err = read_problem_dense(cv_lab, m_dat); } else { ColumnVector cv_lab = args(0).column_vector_value(); SparseMatrix m_dat = args(1).sparse_matrix_value(); err = read_problem_sparse(cv_lab, m_dat); } } else { ColumnVector cv_lab = args(0).column_vector_value(); Matrix m_dat = args(1).matrix_value(); err = read_problem_dense(cv_lab, m_dat); } // svmtrain's original code error_msg = svm_check_parameter(&prob, ¶m); if(err || error_msg) { if (error_msg != NULL) { printf("svmtrain: %s\n", error_msg); } svm_destroy_param(¶m); free(prob.y); free(prob.x); free(x_space); fake_answer(nlhs, plhs); return plhs; } if(cross_validation) { double ptr = do_cross_validation(); plhs(0) = octave_value(ptr); } else { int nr_feat = (int)args(1).matrix_value().cols(); const char *error_msg; model = svm_train(&prob, ¶m); error_msg = model_to_octave_structure(plhs, nr_feat, model); if(error_msg) { printf("svmtrain: can't convert libsvm model to matrix structure: %s\n", error_msg); } svm_free_and_destroy_model(&model); } svm_destroy_param(¶m); free(prob.y); free(prob.x); free(x_space); return plhs; } else { error ("svmtrain: wrong number of input arguments."); } } /* %!test %! # Test 1: Basic C-SVC Classification and Model Structure %! [L, D] = libsvmread (file_in_loadpath ("heart_scale.dat")); %! model = svmtrain(L, D, '-c 1 -g 0.07'); %! [predict_label, accuracy, dec_values] = svmpredict(L, D, model); %! %! assert_equal (isstruct (model), true); %! assert_equal (isfield (model, "Parameters"), true); %! assert_equal (model.totalSV, 130); %! assert_equal (model.nr_class, 2); %! assert_equal (size (model.Label), [2, 1]); %! %! # Check prediction output sizes %! assert_equal (size (predict_label), [length(L), 1]); %! assert_equal (size (dec_values), [length(L), 1]); %! %! %! # Test 2: One-Class SVM Model Structure Check %! # Ensures training with -s 2 is functional and the model structure is valid (accommodating 3.36 changes). %! model_oc = svmtrain(L, D, '-s 2 -n 0.5 -g 0.07'); %! assert_equal (isstruct (model_oc), true); %! assert_equal (model_oc.Parameters(1), 2); # Check svm_type is ONE_CLASS %! assert_equal (model_oc.nr_class, 2); %! assert_equal (model_oc.totalSV > 0, true); %! clear model_oc %! %! %! # Test 3: Regression SVR Test %! # Check training of Epsilon SVR (-s 3) %! model_svr = svmtrain (L, D, '-s 3 -p 0.1 -c 10'); %! assert_equal (isstruct (model_svr), true); %! assert_equal (model_svr.Parameters(1), 3); # Check svm_type is EPSILON_SVR %! assert_equal (model_svr.nr_class, 2); %! clear model_svr %! %! %! # Test 4: Input Argument Error Checking %!shared L, D %! [L, D] = libsvmread (file_in_loadpath ("heart_scale.dat")); %! %! # Check argument count errors %!error [L, D] = svmtrain (L, D); %!error model = svmtrain (L, D, "", ""); %! %! # Check argument type errors %!error ... %! model = svmtrain (single (L), D); %! %! # Check dimension mismatch error %!error ... %! model = svmtrain (L(1:end-1), D); %! %! # Test 5: One-Class Probability Training (New LIBSVM 3.36 Feature) %! # This ensures svmtrain DOES NOT reject -s 2 combined with -b 1 %! # and correctly populates the new ProbDensityMarks field. %!test %! [L, D] = libsvmread (file_in_loadpath ("heart_scale.dat")); %! model = svmtrain (L, D, '-s 2 -n 0.1 -g 0.07 -b 1'); %! %! assert_equal (isstruct (model), true); %! assert_equal (model.Parameters(1), 2); # Check svm_type is ONE_CLASS %! # CRITICAL CHECK: Verify the new field exists (Specific to upgrade) %! assert_equal (isfield (model, "ProbDensityMarks"), true); %! clear model */